oh-my-codex 0.20.3 → 0.20.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (661) hide show
  1. package/Cargo.lock +35 -18
  2. package/Cargo.toml +1 -1
  3. package/README.md +2 -2
  4. package/crates/omx-runtime/Cargo.toml +3 -0
  5. package/crates/omx-runtime/src/main.rs +390 -0
  6. package/crates/omx-runtime/tests/execution.rs +264 -0
  7. package/dist/adapt/__tests__/herdr-target.test.d.ts +2 -0
  8. package/dist/adapt/__tests__/herdr-target.test.d.ts.map +1 -0
  9. package/dist/adapt/__tests__/herdr-target.test.js +72 -0
  10. package/dist/adapt/__tests__/herdr-target.test.js.map +1 -0
  11. package/dist/adapt/contracts.d.ts +1 -1
  12. package/dist/adapt/contracts.d.ts.map +1 -1
  13. package/dist/adapt/contracts.js +1 -1
  14. package/dist/adapt/contracts.js.map +1 -1
  15. package/dist/adapt/herdr/__tests__/authority.test.d.ts +2 -0
  16. package/dist/adapt/herdr/__tests__/authority.test.d.ts.map +1 -0
  17. package/dist/adapt/herdr/__tests__/authority.test.js +49 -0
  18. package/dist/adapt/herdr/__tests__/authority.test.js.map +1 -0
  19. package/dist/adapt/herdr/__tests__/bridge.test.d.ts +2 -0
  20. package/dist/adapt/herdr/__tests__/bridge.test.d.ts.map +1 -0
  21. package/dist/adapt/herdr/__tests__/bridge.test.js +176 -0
  22. package/dist/adapt/herdr/__tests__/bridge.test.js.map +1 -0
  23. package/dist/adapt/herdr/__tests__/sanitize.test.d.ts +2 -0
  24. package/dist/adapt/herdr/__tests__/sanitize.test.d.ts.map +1 -0
  25. package/dist/adapt/herdr/__tests__/sanitize.test.js +45 -0
  26. package/dist/adapt/herdr/__tests__/sanitize.test.js.map +1 -0
  27. package/dist/adapt/herdr/__tests__/semantic.test.d.ts +2 -0
  28. package/dist/adapt/herdr/__tests__/semantic.test.d.ts.map +1 -0
  29. package/dist/adapt/herdr/__tests__/semantic.test.js +130 -0
  30. package/dist/adapt/herdr/__tests__/semantic.test.js.map +1 -0
  31. package/dist/adapt/herdr/__tests__/seq-store.test.d.ts +2 -0
  32. package/dist/adapt/herdr/__tests__/seq-store.test.d.ts.map +1 -0
  33. package/dist/adapt/herdr/__tests__/seq-store.test.js +54 -0
  34. package/dist/adapt/herdr/__tests__/seq-store.test.js.map +1 -0
  35. package/dist/adapt/herdr/__tests__/socket-live.test.d.ts +2 -0
  36. package/dist/adapt/herdr/__tests__/socket-live.test.d.ts.map +1 -0
  37. package/dist/adapt/herdr/__tests__/socket-live.test.js +104 -0
  38. package/dist/adapt/herdr/__tests__/socket-live.test.js.map +1 -0
  39. package/dist/adapt/herdr/__tests__/team-map.test.d.ts +2 -0
  40. package/dist/adapt/herdr/__tests__/team-map.test.d.ts.map +1 -0
  41. package/dist/adapt/herdr/__tests__/team-map.test.js +46 -0
  42. package/dist/adapt/herdr/__tests__/team-map.test.js.map +1 -0
  43. package/dist/adapt/herdr/__tests__/transport.test.d.ts +2 -0
  44. package/dist/adapt/herdr/__tests__/transport.test.d.ts.map +1 -0
  45. package/dist/adapt/herdr/__tests__/transport.test.js +160 -0
  46. package/dist/adapt/herdr/__tests__/transport.test.js.map +1 -0
  47. package/dist/adapt/herdr/__tests__/wiring.test.d.ts +2 -0
  48. package/dist/adapt/herdr/__tests__/wiring.test.d.ts.map +1 -0
  49. package/dist/adapt/herdr/__tests__/wiring.test.js +118 -0
  50. package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -0
  51. package/dist/adapt/herdr/authority.d.ts +34 -0
  52. package/dist/adapt/herdr/authority.d.ts.map +1 -0
  53. package/dist/adapt/herdr/authority.js +71 -0
  54. package/dist/adapt/herdr/authority.js.map +1 -0
  55. package/dist/adapt/herdr/bridge.d.ts +84 -0
  56. package/dist/adapt/herdr/bridge.d.ts.map +1 -0
  57. package/dist/adapt/herdr/bridge.js +222 -0
  58. package/dist/adapt/herdr/bridge.js.map +1 -0
  59. package/dist/adapt/herdr/sanitize.d.ts +20 -0
  60. package/dist/adapt/herdr/sanitize.d.ts.map +1 -0
  61. package/dist/adapt/herdr/sanitize.js +70 -0
  62. package/dist/adapt/herdr/sanitize.js.map +1 -0
  63. package/dist/adapt/herdr/semantic.d.ts +67 -0
  64. package/dist/adapt/herdr/semantic.d.ts.map +1 -0
  65. package/dist/adapt/herdr/semantic.js +170 -0
  66. package/dist/adapt/herdr/semantic.js.map +1 -0
  67. package/dist/adapt/herdr/seq-store.d.ts +32 -0
  68. package/dist/adapt/herdr/seq-store.d.ts.map +1 -0
  69. package/dist/adapt/herdr/seq-store.js +126 -0
  70. package/dist/adapt/herdr/seq-store.js.map +1 -0
  71. package/dist/adapt/herdr/team-map.d.ts +30 -0
  72. package/dist/adapt/herdr/team-map.d.ts.map +1 -0
  73. package/dist/adapt/herdr/team-map.js +37 -0
  74. package/dist/adapt/herdr/team-map.js.map +1 -0
  75. package/dist/adapt/herdr/transport.d.ts +115 -0
  76. package/dist/adapt/herdr/transport.d.ts.map +1 -0
  77. package/dist/adapt/herdr/transport.js +260 -0
  78. package/dist/adapt/herdr/transport.js.map +1 -0
  79. package/dist/adapt/herdr/wiring.d.ts +24 -0
  80. package/dist/adapt/herdr/wiring.d.ts.map +1 -0
  81. package/dist/adapt/herdr/wiring.js +62 -0
  82. package/dist/adapt/herdr/wiring.js.map +1 -0
  83. package/dist/adapt/herdr.d.ts +19 -0
  84. package/dist/adapt/herdr.d.ts.map +1 -0
  85. package/dist/adapt/herdr.js +115 -0
  86. package/dist/adapt/herdr.js.map +1 -0
  87. package/dist/adapt/index.d.ts +4 -0
  88. package/dist/adapt/index.d.ts.map +1 -1
  89. package/dist/adapt/index.js +48 -0
  90. package/dist/adapt/index.js.map +1 -1
  91. package/dist/adapt/registry.d.ts.map +1 -1
  92. package/dist/adapt/registry.js +11 -0
  93. package/dist/adapt/registry.js.map +1 -1
  94. package/dist/auth/__tests__/hotswap.test.js +60 -1
  95. package/dist/auth/__tests__/hotswap.test.js.map +1 -1
  96. package/dist/auth/__tests__/storage.test.js +33 -1
  97. package/dist/auth/__tests__/storage.test.js.map +1 -1
  98. package/dist/auth/hotswap.d.ts +16 -2
  99. package/dist/auth/hotswap.d.ts.map +1 -1
  100. package/dist/auth/hotswap.js +22 -7
  101. package/dist/auth/hotswap.js.map +1 -1
  102. package/dist/auth/storage.js +1 -1
  103. package/dist/auth/storage.js.map +1 -1
  104. package/dist/autopilot/__tests__/ralplan-gate.test.js +86 -1333
  105. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  106. package/dist/autopilot/ralplan-gate.d.ts.map +1 -1
  107. package/dist/autopilot/ralplan-gate.js +7 -6
  108. package/dist/autopilot/ralplan-gate.js.map +1 -1
  109. package/dist/cli/__tests__/agents-init.test.js +4 -23
  110. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  111. package/dist/cli/__tests__/api.test.js +61 -24
  112. package/dist/cli/__tests__/api.test.js.map +1 -1
  113. package/dist/cli/__tests__/auth.test.js +3 -1
  114. package/dist/cli/__tests__/auth.test.js.map +1 -1
  115. package/dist/cli/__tests__/codex-feature-probe.test.js +66 -1
  116. package/dist/cli/__tests__/codex-feature-probe.test.js.map +1 -1
  117. package/dist/cli/__tests__/codex-plugin-layout.test.js +70 -2
  118. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  119. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts +2 -0
  120. package/dist/cli/__tests__/detached-leader-teardown.test.d.ts.map +1 -0
  121. package/dist/cli/__tests__/detached-leader-teardown.test.js +473 -0
  122. package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -0
  123. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts +2 -0
  124. package/dist/cli/__tests__/doctor-process-identity-readiness.test.d.ts.map +1 -0
  125. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js +103 -0
  126. package/dist/cli/__tests__/doctor-process-identity-readiness.test.js.map +1 -0
  127. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts +2 -0
  128. package/dist/cli/__tests__/doctor-state-root-binding.test.d.ts.map +1 -0
  129. package/dist/cli/__tests__/doctor-state-root-binding.test.js +139 -0
  130. package/dist/cli/__tests__/doctor-state-root-binding.test.js.map +1 -0
  131. package/dist/cli/__tests__/doctor-warning-copy.test.js +35 -3
  132. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  133. package/dist/cli/__tests__/exec.test.js +75 -0
  134. package/dist/cli/__tests__/exec.test.js.map +1 -1
  135. package/dist/cli/__tests__/index.test.js +621 -460
  136. package/dist/cli/__tests__/index.test.js.map +1 -1
  137. package/dist/cli/__tests__/launch-fallback.test.js +1020 -143
  138. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  139. package/dist/cli/__tests__/mcp-parity.test.js +4 -3
  140. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  141. package/dist/cli/__tests__/native-assets.test.js +398 -16
  142. package/dist/cli/__tests__/native-assets.test.js.map +1 -1
  143. package/dist/cli/__tests__/native-hook-claim-journal.test.js +53 -5
  144. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -1
  145. package/dist/cli/__tests__/ralph.test.js +2 -2
  146. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  147. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +57 -57
  148. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -1
  149. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts +2 -0
  150. package/dist/cli/__tests__/ralplan-child-binding-3212.test.d.ts.map +1 -0
  151. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js +56 -0
  152. package/dist/cli/__tests__/ralplan-child-binding-3212.test.js.map +1 -0
  153. package/dist/cli/__tests__/ralplan.test.js +84 -12
  154. package/dist/cli/__tests__/ralplan.test.js.map +1 -1
  155. package/dist/cli/__tests__/session-scoped-runtime.test.js +1118 -43
  156. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  157. package/dist/cli/__tests__/setup-agents-overwrite.test.js +8 -55
  158. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  159. package/dist/cli/__tests__/setup-install-mode.test.js +183 -4
  160. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  161. package/dist/cli/__tests__/sparkshell-cli.test.js +93 -41
  162. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  163. package/dist/cli/__tests__/team.test.js +45 -1
  164. package/dist/cli/__tests__/team.test.js.map +1 -1
  165. package/dist/cli/__tests__/ultragoal.test.js +117 -2
  166. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  167. package/dist/cli/__tests__/uninstall.test.js +50 -5
  168. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  169. package/dist/cli/__tests__/update.test.js +669 -177
  170. package/dist/cli/__tests__/update.test.js.map +1 -1
  171. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +32 -2
  172. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  173. package/dist/cli/api.d.ts.map +1 -1
  174. package/dist/cli/api.js +9 -10
  175. package/dist/cli/api.js.map +1 -1
  176. package/dist/cli/codex-feature-probe.d.ts +16 -0
  177. package/dist/cli/codex-feature-probe.d.ts.map +1 -1
  178. package/dist/cli/codex-feature-probe.js +44 -0
  179. package/dist/cli/codex-feature-probe.js.map +1 -1
  180. package/dist/cli/codex-home.d.ts.map +1 -1
  181. package/dist/cli/codex-home.js +7 -4
  182. package/dist/cli/codex-home.js.map +1 -1
  183. package/dist/cli/doctor.d.ts +17 -0
  184. package/dist/cli/doctor.d.ts.map +1 -1
  185. package/dist/cli/doctor.js +277 -3
  186. package/dist/cli/doctor.js.map +1 -1
  187. package/dist/cli/index.d.ts +231 -12
  188. package/dist/cli/index.d.ts.map +1 -1
  189. package/dist/cli/index.js +2298 -735
  190. package/dist/cli/index.js.map +1 -1
  191. package/dist/cli/native-assets.d.ts +25 -0
  192. package/dist/cli/native-assets.d.ts.map +1 -1
  193. package/dist/cli/native-assets.js +448 -51
  194. package/dist/cli/native-assets.js.map +1 -1
  195. package/dist/cli/native-hook-claim-journal.d.ts +8 -4
  196. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -1
  197. package/dist/cli/native-hook-claim-journal.js +30 -8
  198. package/dist/cli/native-hook-claim-journal.js.map +1 -1
  199. package/dist/cli/package-manager-ownership.d.ts +63 -0
  200. package/dist/cli/package-manager-ownership.d.ts.map +1 -0
  201. package/dist/cli/package-manager-ownership.js +323 -0
  202. package/dist/cli/package-manager-ownership.js.map +1 -0
  203. package/dist/cli/ralph.js +2 -2
  204. package/dist/cli/ralph.js.map +1 -1
  205. package/dist/cli/ralplan.d.ts +4 -2
  206. package/dist/cli/ralplan.d.ts.map +1 -1
  207. package/dist/cli/ralplan.js +76 -34
  208. package/dist/cli/ralplan.js.map +1 -1
  209. package/dist/cli/setup-preferences.d.ts +15 -0
  210. package/dist/cli/setup-preferences.d.ts.map +1 -1
  211. package/dist/cli/setup-preferences.js +27 -1
  212. package/dist/cli/setup-preferences.js.map +1 -1
  213. package/dist/cli/setup.d.ts +3 -0
  214. package/dist/cli/setup.d.ts.map +1 -1
  215. package/dist/cli/setup.js +55 -32
  216. package/dist/cli/setup.js.map +1 -1
  217. package/dist/cli/sparkshell.d.ts.map +1 -1
  218. package/dist/cli/sparkshell.js +52 -9
  219. package/dist/cli/sparkshell.js.map +1 -1
  220. package/dist/cli/ultragoal.d.ts.map +1 -1
  221. package/dist/cli/ultragoal.js +2 -2
  222. package/dist/cli/ultragoal.js.map +1 -1
  223. package/dist/cli/uninstall.d.ts.map +1 -1
  224. package/dist/cli/uninstall.js +27 -28
  225. package/dist/cli/uninstall.js.map +1 -1
  226. package/dist/cli/update-worker.d.ts +2 -0
  227. package/dist/cli/update-worker.d.ts.map +1 -0
  228. package/dist/cli/update-worker.js +142 -0
  229. package/dist/cli/update-worker.js.map +1 -0
  230. package/dist/cli/update.d.ts +14 -22
  231. package/dist/cli/update.d.ts.map +1 -1
  232. package/dist/cli/update.js +192 -241
  233. package/dist/cli/update.js.map +1 -1
  234. package/dist/config/__tests__/generator-idempotent.test.js +173 -29
  235. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  236. package/dist/config/__tests__/generator-notify.test.js +3 -1
  237. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  238. package/dist/config/generator.d.ts +25 -3
  239. package/dist/config/generator.d.ts.map +1 -1
  240. package/dist/config/generator.js +113 -35
  241. package/dist/config/generator.js.map +1 -1
  242. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js +25 -0
  243. package/dist/goal-workflows/__tests__/codex-goal-snapshot.test.js.map +1 -1
  244. package/dist/goal-workflows/codex-goal-snapshot.d.ts +1 -1
  245. package/dist/goal-workflows/codex-goal-snapshot.d.ts.map +1 -1
  246. package/dist/goal-workflows/codex-goal-snapshot.js +2 -0
  247. package/dist/goal-workflows/codex-goal-snapshot.js.map +1 -1
  248. package/dist/hooks/__tests__/agents-overlay.test.js +3 -1
  249. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  250. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +27 -4
  251. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  252. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts +2 -0
  253. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.d.ts.map +1 -0
  254. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js +172 -0
  255. package/dist/hooks/__tests__/issue-3293-autopilot-handoff.test.js.map +1 -0
  256. package/dist/hooks/__tests__/keyword-detector.test.js +172 -33
  257. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  258. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +173 -11
  259. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  260. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +1 -0
  261. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  262. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +65 -17
  263. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  264. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +27 -0
  265. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  266. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +10 -118
  267. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  268. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +7 -0
  269. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  270. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +2 -2
  271. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  272. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +4 -1
  273. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  274. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js +2 -1
  275. package/dist/hooks/__tests__/notify-hook-team-worker-fail-closed.test.js.map +1 -1
  276. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +1 -0
  277. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  278. package/dist/hooks/__tests__/prompt-guidance-contract.test.js +9 -0
  279. package/dist/hooks/__tests__/prompt-guidance-contract.test.js.map +1 -1
  280. package/dist/hooks/__tests__/session.test.js +2976 -557
  281. package/dist/hooks/__tests__/session.test.js.map +1 -1
  282. package/dist/hooks/agents-overlay.js +1 -1
  283. package/dist/hooks/agents-overlay.js.map +1 -1
  284. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +108 -2
  285. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  286. package/dist/hooks/extensibility/__tests__/sdk.test.js +151 -1
  287. package/dist/hooks/extensibility/__tests__/sdk.test.js.map +1 -1
  288. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  289. package/dist/hooks/extensibility/dispatcher.js +20 -3
  290. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  291. package/dist/hooks/extensibility/plugin-runner.js +1 -0
  292. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  293. package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
  294. package/dist/hooks/extensibility/runtime.js.map +1 -1
  295. package/dist/hooks/extensibility/sdk/paths.d.ts +1 -1
  296. package/dist/hooks/extensibility/sdk/paths.d.ts.map +1 -1
  297. package/dist/hooks/extensibility/sdk/paths.js +2 -2
  298. package/dist/hooks/extensibility/sdk/paths.js.map +1 -1
  299. package/dist/hooks/extensibility/sdk/runtime-state.d.ts +1 -1
  300. package/dist/hooks/extensibility/sdk/runtime-state.d.ts.map +1 -1
  301. package/dist/hooks/extensibility/sdk/runtime-state.js +55 -15
  302. package/dist/hooks/extensibility/sdk/runtime-state.js.map +1 -1
  303. package/dist/hooks/extensibility/sdk.d.ts +1 -0
  304. package/dist/hooks/extensibility/sdk.d.ts.map +1 -1
  305. package/dist/hooks/extensibility/sdk.js +1 -1
  306. package/dist/hooks/extensibility/sdk.js.map +1 -1
  307. package/dist/hooks/extensibility/types.d.ts +7 -0
  308. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  309. package/dist/hooks/keyword-detector.d.ts +10 -3
  310. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  311. package/dist/hooks/keyword-detector.js +116 -20
  312. package/dist/hooks/keyword-detector.js.map +1 -1
  313. package/dist/hooks/session.d.ts +180 -14
  314. package/dist/hooks/session.d.ts.map +1 -1
  315. package/dist/hooks/session.js +1402 -281
  316. package/dist/hooks/session.js.map +1 -1
  317. package/dist/hud/__tests__/hud-tmux-injection.test.js +6 -6
  318. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  319. package/dist/hud/__tests__/index.test.js +26 -16
  320. package/dist/hud/__tests__/index.test.js.map +1 -1
  321. package/dist/hud/__tests__/reconcile.test.js +83 -78
  322. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  323. package/dist/hud/__tests__/state.test.js +36 -31
  324. package/dist/hud/__tests__/state.test.js.map +1 -1
  325. package/dist/hud/__tests__/tmux.test.js +155 -198
  326. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  327. package/dist/hud/tmux.d.ts +42 -4
  328. package/dist/hud/tmux.d.ts.map +1 -1
  329. package/dist/hud/tmux.js +1017 -139
  330. package/dist/hud/tmux.js.map +1 -1
  331. package/dist/leader/__tests__/contract.test.js +71 -34
  332. package/dist/leader/__tests__/contract.test.js.map +1 -1
  333. package/dist/leader/contract.d.ts +1 -6
  334. package/dist/leader/contract.d.ts.map +1 -1
  335. package/dist/leader/contract.js +25 -24
  336. package/dist/leader/contract.js.map +1 -1
  337. package/dist/mcp/__tests__/hermes-bridge.test.js +17 -0
  338. package/dist/mcp/__tests__/hermes-bridge.test.js.map +1 -1
  339. package/dist/mcp/__tests__/server-lifecycle.test.js +4 -4
  340. package/dist/mcp/__tests__/state-paths.test.js +735 -4
  341. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  342. package/dist/mcp/__tests__/state-server.test.js +1 -1
  343. package/dist/mcp/__tests__/state-server.test.js.map +1 -1
  344. package/dist/mcp/__tests__/trace-server.test.js +2 -2
  345. package/dist/mcp/__tests__/trace-server.test.js.map +1 -1
  346. package/dist/mcp/hermes-bridge.d.ts.map +1 -1
  347. package/dist/mcp/hermes-bridge.js +2 -2
  348. package/dist/mcp/hermes-bridge.js.map +1 -1
  349. package/dist/mcp/state-paths.d.ts +78 -4
  350. package/dist/mcp/state-paths.d.ts.map +1 -1
  351. package/dist/mcp/state-paths.js +543 -40
  352. package/dist/mcp/state-paths.js.map +1 -1
  353. package/dist/mcp/state-server.d.ts +4 -4
  354. package/dist/mcp/state-server.d.ts.map +1 -1
  355. package/dist/modes/__tests__/base-autopilot-gates.test.js +36 -7
  356. package/dist/modes/__tests__/base-autopilot-gates.test.js.map +1 -1
  357. package/dist/modes/__tests__/base-multi-state-compat.test.js +1 -1
  358. package/dist/modes/__tests__/base-multi-state-compat.test.js.map +1 -1
  359. package/dist/modes/__tests__/base-session-scope.test.js +17 -8
  360. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  361. package/dist/modes/base.d.ts +5 -5
  362. package/dist/modes/base.d.ts.map +1 -1
  363. package/dist/modes/base.js +76 -21
  364. package/dist/modes/base.js.map +1 -1
  365. package/dist/native-assets/archive.d.ts +17 -0
  366. package/dist/native-assets/archive.d.ts.map +1 -0
  367. package/dist/native-assets/archive.js +238 -0
  368. package/dist/native-assets/archive.js.map +1 -0
  369. package/dist/native-assets/policy.d.ts +42 -0
  370. package/dist/native-assets/policy.d.ts.map +1 -0
  371. package/dist/native-assets/policy.js +155 -0
  372. package/dist/native-assets/policy.js.map +1 -0
  373. package/dist/pipeline/__tests__/orchestrator.test.js +162 -40
  374. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  375. package/dist/pipeline/__tests__/stages.test.js +66 -149
  376. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  377. package/dist/pipeline/orchestrator.d.ts.map +1 -1
  378. package/dist/pipeline/orchestrator.js +124 -18
  379. package/dist/pipeline/orchestrator.js.map +1 -1
  380. package/dist/pipeline/stages/ralplan.d.ts.map +1 -1
  381. package/dist/pipeline/stages/ralplan.js +36 -8
  382. package/dist/pipeline/stages/ralplan.js.map +1 -1
  383. package/dist/ralplan/__tests__/consensus-gate.test.js +70 -1437
  384. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  385. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +328 -39
  386. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -1
  387. package/dist/ralplan/__tests__/runtime.test.js +82 -102
  388. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  389. package/dist/ralplan/consensus-gate.d.ts +9 -1
  390. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  391. package/dist/ralplan/consensus-gate.js +40 -304
  392. package/dist/ralplan/consensus-gate.js.map +1 -1
  393. package/dist/ralplan/documented-leader-preflight.d.ts +15 -8
  394. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
  395. package/dist/ralplan/documented-leader-preflight.js +356 -54
  396. package/dist/ralplan/documented-leader-preflight.js.map +1 -1
  397. package/dist/ralplan/runtime.d.ts +1 -1
  398. package/dist/ralplan/runtime.d.ts.map +1 -1
  399. package/dist/ralplan/runtime.js +68 -173
  400. package/dist/ralplan/runtime.js.map +1 -1
  401. package/dist/runtime/run-state.d.ts +5 -1
  402. package/dist/runtime/run-state.d.ts.map +1 -1
  403. package/dist/runtime/run-state.js +15 -8
  404. package/dist/runtime/run-state.js.map +1 -1
  405. package/dist/scripts/__tests__/codex-native-hook.test.js +14071 -8859
  406. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  407. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts +2 -0
  408. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.d.ts.map +1 -0
  409. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js +225 -0
  410. package/dist/scripts/__tests__/issue-3293-callsite-parity.test.js.map +1 -0
  411. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts +2 -0
  412. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.d.ts.map +1 -0
  413. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js +188 -0
  414. package/dist/scripts/__tests__/issue-3293-cancel-transaction.test.js.map +1 -0
  415. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts +2 -0
  416. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.d.ts.map +1 -0
  417. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js +259 -0
  418. package/dist/scripts/__tests__/issue-3293-hook-owned-cancel.test.js.map +1 -0
  419. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts +2 -0
  420. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.d.ts.map +1 -0
  421. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js +491 -0
  422. package/dist/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.js.map +1 -0
  423. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts +2 -0
  424. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.d.ts.map +1 -0
  425. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js +708 -0
  426. package/dist/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.js.map +1 -0
  427. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts +2 -0
  428. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.d.ts.map +1 -0
  429. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js +208 -0
  430. package/dist/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.js.map +1 -0
  431. package/dist/scripts/__tests__/notify-state-io.test.js +26 -0
  432. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  433. package/dist/scripts/__tests__/notify-tmux-injection.test.js +23 -1
  434. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -1
  435. package/dist/scripts/__tests__/postinstall.test.js +19 -1
  436. package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
  437. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +25 -8
  438. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -1
  439. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +60 -25
  440. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -1
  441. package/dist/scripts/__tests__/smoke-packed-install.test.js +155 -16
  442. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  443. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts +2 -0
  444. package/dist/scripts/__tests__/verify-native-release-assets.test.d.ts.map +1 -0
  445. package/dist/scripts/__tests__/verify-native-release-assets.test.js +235 -0
  446. package/dist/scripts/__tests__/verify-native-release-assets.test.js.map +1 -0
  447. package/dist/scripts/codex-native-hook.d.ts +50 -1
  448. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  449. package/dist/scripts/codex-native-hook.js +3559 -451
  450. package/dist/scripts/codex-native-hook.js.map +1 -1
  451. package/dist/scripts/codex-native-pre-post.d.ts +9 -0
  452. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  453. package/dist/scripts/codex-native-pre-post.js +35 -12
  454. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  455. package/dist/scripts/generate-native-release-manifest.js +80 -51
  456. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  457. package/dist/scripts/notify-fallback-watcher.js +28 -39
  458. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  459. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  460. package/dist/scripts/notify-hook/state-io.js +5 -0
  461. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  462. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  463. package/dist/scripts/notify-hook/team-leader-nudge.js +32 -3
  464. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  465. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  466. package/dist/scripts/notify-hook/team-worker-stop.js +23 -3
  467. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  468. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  469. package/dist/scripts/notify-hook/team-worker.js +45 -5
  470. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  471. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -0
  472. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  473. package/dist/scripts/notify-hook/tmux-injection.js +4 -1
  474. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  475. package/dist/scripts/notify-hook.js +78 -82
  476. package/dist/scripts/notify-hook.js.map +1 -1
  477. package/dist/scripts/postinstall-advisory.d.ts +14 -0
  478. package/dist/scripts/postinstall-advisory.d.ts.map +1 -0
  479. package/dist/scripts/postinstall-advisory.js +49 -0
  480. package/dist/scripts/postinstall-advisory.js.map +1 -0
  481. package/dist/scripts/postinstall.d.ts +1 -1
  482. package/dist/scripts/postinstall.d.ts.map +1 -1
  483. package/dist/scripts/postinstall.js +1 -1
  484. package/dist/scripts/postinstall.js.map +1 -1
  485. package/dist/scripts/smoke-packed-install.d.ts +32 -1
  486. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  487. package/dist/scripts/smoke-packed-install.js +413 -60
  488. package/dist/scripts/smoke-packed-install.js.map +1 -1
  489. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  490. package/dist/scripts/sync-plugin-mirror.js +16 -1
  491. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  492. package/dist/scripts/verify-native-release-assets.js +37 -23
  493. package/dist/scripts/verify-native-release-assets.js.map +1 -1
  494. package/dist/state/__tests__/mode-state-context.test.js +12 -5
  495. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  496. package/dist/state/__tests__/operations.test.js +460 -850
  497. package/dist/state/__tests__/operations.test.js.map +1 -1
  498. package/dist/state/__tests__/skill-active.test.js +400 -2
  499. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  500. package/dist/state/mode-state-context.d.ts +3 -0
  501. package/dist/state/mode-state-context.d.ts.map +1 -1
  502. package/dist/state/mode-state-context.js +27 -13
  503. package/dist/state/mode-state-context.js.map +1 -1
  504. package/dist/state/operations.d.ts +3 -0
  505. package/dist/state/operations.d.ts.map +1 -1
  506. package/dist/state/operations.js +144 -40
  507. package/dist/state/operations.js.map +1 -1
  508. package/dist/state/skill-active.d.ts +18 -1
  509. package/dist/state/skill-active.d.ts.map +1 -1
  510. package/dist/state/skill-active.js +420 -23
  511. package/dist/state/skill-active.js.map +1 -1
  512. package/dist/state/workflow-transition-reconcile.d.ts +3 -0
  513. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  514. package/dist/state/workflow-transition-reconcile.js +10 -4
  515. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  516. package/dist/state/workflow-transition.d.ts.map +1 -1
  517. package/dist/state/workflow-transition.js +3 -1
  518. package/dist/state/workflow-transition.js.map +1 -1
  519. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +27 -0
  520. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -1
  521. package/dist/subagents/__tests__/tracker.test.js +837 -1048
  522. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  523. package/dist/subagents/tracker.d.ts +64 -60
  524. package/dist/subagents/tracker.d.ts.map +1 -1
  525. package/dist/subagents/tracker.js +809 -358
  526. package/dist/subagents/tracker.js.map +1 -1
  527. package/dist/team/__tests__/delivery-e2e-smoke.test.js +6 -7
  528. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  529. package/dist/team/__tests__/mcp-comm.test.js +104 -3
  530. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  531. package/dist/team/__tests__/notice-ledger.test.d.ts +2 -0
  532. package/dist/team/__tests__/notice-ledger.test.d.ts.map +1 -0
  533. package/dist/team/__tests__/notice-ledger.test.js +131 -0
  534. package/dist/team/__tests__/notice-ledger.test.js.map +1 -0
  535. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts +2 -0
  536. package/dist/team/__tests__/pane-teardown-residue-e2e.test.d.ts.map +1 -0
  537. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js +410 -0
  538. package/dist/team/__tests__/pane-teardown-residue-e2e.test.js.map +1 -0
  539. package/dist/team/__tests__/phase-controller.test.js +20 -0
  540. package/dist/team/__tests__/phase-controller.test.js.map +1 -1
  541. package/dist/team/__tests__/runtime.test.js +725 -59
  542. package/dist/team/__tests__/runtime.test.js.map +1 -1
  543. package/dist/team/__tests__/scaling.test.js +222 -237
  544. package/dist/team/__tests__/scaling.test.js.map +1 -1
  545. package/dist/team/__tests__/state-root.test.js +117 -2
  546. package/dist/team/__tests__/state-root.test.js.map +1 -1
  547. package/dist/team/__tests__/tmux-session.test.js +887 -92
  548. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  549. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts +2 -0
  550. package/dist/team/__tests__/tmux-source-authority-realtmux.test.d.ts.map +1 -0
  551. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js +149 -0
  552. package/dist/team/__tests__/tmux-source-authority-realtmux.test.js.map +1 -0
  553. package/dist/team/__tests__/tmux-test-fixture.d.ts +69 -0
  554. package/dist/team/__tests__/tmux-test-fixture.d.ts.map +1 -1
  555. package/dist/team/__tests__/tmux-test-fixture.js +242 -15
  556. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  557. package/dist/team/__tests__/tmux-test-fixture.test.js +177 -1
  558. package/dist/team/__tests__/tmux-test-fixture.test.js.map +1 -1
  559. package/dist/team/__tests__/worker-runtime-identity.test.js +45 -72
  560. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  561. package/dist/team/notice-ledger.d.ts +54 -0
  562. package/dist/team/notice-ledger.d.ts.map +1 -0
  563. package/dist/team/notice-ledger.js +259 -0
  564. package/dist/team/notice-ledger.js.map +1 -0
  565. package/dist/team/phase-controller.d.ts.map +1 -1
  566. package/dist/team/phase-controller.js +2 -0
  567. package/dist/team/phase-controller.js.map +1 -1
  568. package/dist/team/runtime.d.ts +3 -1
  569. package/dist/team/runtime.d.ts.map +1 -1
  570. package/dist/team/runtime.js +314 -142
  571. package/dist/team/runtime.js.map +1 -1
  572. package/dist/team/scaling.d.ts.map +1 -1
  573. package/dist/team/scaling.js +161 -91
  574. package/dist/team/scaling.js.map +1 -1
  575. package/dist/team/state/monitor.d.ts +10 -0
  576. package/dist/team/state/monitor.d.ts.map +1 -1
  577. package/dist/team/state/monitor.js +13 -0
  578. package/dist/team/state/monitor.js.map +1 -1
  579. package/dist/team/state/types.d.ts +15 -0
  580. package/dist/team/state/types.d.ts.map +1 -1
  581. package/dist/team/state/types.js.map +1 -1
  582. package/dist/team/state-root.d.ts +12 -0
  583. package/dist/team/state-root.d.ts.map +1 -1
  584. package/dist/team/state-root.js +177 -0
  585. package/dist/team/state-root.js.map +1 -1
  586. package/dist/team/state.d.ts +18 -0
  587. package/dist/team/state.d.ts.map +1 -1
  588. package/dist/team/state.js +36 -0
  589. package/dist/team/state.js.map +1 -1
  590. package/dist/team/tmux-session.d.ts +40 -4
  591. package/dist/team/tmux-session.d.ts.map +1 -1
  592. package/dist/team/tmux-session.js +513 -166
  593. package/dist/team/tmux-session.js.map +1 -1
  594. package/dist/ultragoal/__tests__/artifacts.test.js +950 -2
  595. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  596. package/dist/ultragoal/__tests__/docs-contract.test.js +1 -0
  597. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  598. package/dist/ultragoal/artifacts.d.ts +35 -1
  599. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  600. package/dist/ultragoal/artifacts.js +232 -46
  601. package/dist/ultragoal/artifacts.js.map +1 -1
  602. package/dist/utils/__tests__/file-durability.test.js +58 -1
  603. package/dist/utils/__tests__/file-durability.test.js.map +1 -1
  604. package/dist/utils/__tests__/paths.test.js +21 -1
  605. package/dist/utils/__tests__/paths.test.js.map +1 -1
  606. package/dist/utils/file-durability.d.ts +10 -0
  607. package/dist/utils/file-durability.d.ts.map +1 -1
  608. package/dist/utils/file-durability.js +34 -4
  609. package/dist/utils/file-durability.js.map +1 -1
  610. package/dist/utils/paths.d.ts.map +1 -1
  611. package/dist/utils/paths.js +14 -6
  612. package/dist/utils/paths.js.map +1 -1
  613. package/dist/verification/__tests__/ci-rust-gates.test.js +49 -4
  614. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  615. package/dist/verification/__tests__/explore-harness-release-workflow.test.js +60 -0
  616. package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
  617. package/dist/verification/__tests__/native-release-manifest.test.js +157 -4
  618. package/dist/verification/__tests__/native-release-manifest.test.js.map +1 -1
  619. package/package.json +13 -5
  620. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  621. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +81 -17
  622. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +13 -5
  623. package/plugins/oh-my-codex/skills/cancel/SKILL.md +30 -85
  624. package/plugins/oh-my-codex/skills/ralph/SKILL.md +5 -5
  625. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +15 -17
  626. package/plugins/oh-my-codex/skills/ultragoal/SKILL.md +3 -2
  627. package/skills/autopilot/SKILL.md +13 -5
  628. package/skills/cancel/SKILL.md +30 -85
  629. package/skills/ralph/SKILL.md +5 -5
  630. package/skills/ralplan/SKILL.md +15 -17
  631. package/skills/ultragoal/SKILL.md +3 -2
  632. package/src/scripts/__tests__/codex-native-hook.test.ts +6091 -540
  633. package/src/scripts/__tests__/issue-3293-callsite-parity.test.ts +187 -0
  634. package/src/scripts/__tests__/issue-3293-cancel-transaction.test.ts +189 -0
  635. package/src/scripts/__tests__/issue-3293-hook-owned-cancel.test.ts +187 -0
  636. package/src/scripts/__tests__/issue-3311-ultragoal-native-outside-tmux.test.ts +591 -0
  637. package/src/scripts/__tests__/issue-3358-ultragoal-cancel-lifecycle.test.ts +701 -0
  638. package/src/scripts/__tests__/issue-3369-autopilot-ralplan-state-machine.test.ts +211 -0
  639. package/src/scripts/__tests__/notify-state-io.test.ts +29 -0
  640. package/src/scripts/__tests__/notify-tmux-injection.test.ts +26 -1
  641. package/src/scripts/__tests__/postinstall.test.ts +23 -1
  642. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +27 -8
  643. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +61 -27
  644. package/src/scripts/__tests__/smoke-packed-install.test.ts +209 -18
  645. package/src/scripts/__tests__/verify-native-release-assets.test.ts +240 -0
  646. package/src/scripts/codex-native-hook.ts +3638 -475
  647. package/src/scripts/codex-native-pre-post.ts +43 -12
  648. package/src/scripts/generate-native-release-manifest.ts +83 -79
  649. package/src/scripts/notify-fallback-watcher.ts +38 -38
  650. package/src/scripts/notify-hook/state-io.ts +11 -0
  651. package/src/scripts/notify-hook/team-leader-nudge.ts +32 -4
  652. package/src/scripts/notify-hook/team-worker-stop.ts +23 -4
  653. package/src/scripts/notify-hook/team-worker.ts +43 -5
  654. package/src/scripts/notify-hook/tmux-injection.ts +5 -1
  655. package/src/scripts/notify-hook.ts +83 -82
  656. package/src/scripts/postinstall-advisory.ts +67 -0
  657. package/src/scripts/postinstall.ts +1 -1
  658. package/src/scripts/smoke-packed-install.ts +446 -59
  659. package/src/scripts/sync-plugin-mirror.ts +17 -1
  660. package/src/scripts/verify-native-release-assets.ts +39 -31
  661. package/templates/AGENTS.md +1 -0
@@ -1,33 +1,109 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
- import { mkdir, mkdtemp, rm, writeFile, readFile } from 'fs/promises';
4
- import { existsSync } from 'fs';
3
+ import { chmod, mkdir, mkdtemp, readFile, readdir, rm, symlink, writeFile } from 'fs/promises';
4
+ import { existsSync, realpathSync } from 'fs';
5
5
  import { dirname, join } from 'path';
6
- import { tmpdir } from 'os';
6
+ import { tmpdir as osTmpdir } from 'os';
7
7
  import { spawnSync } from 'child_process';
8
8
  import { fileURLToPath } from 'url';
9
+ import { readModeState } from '../../modes/base.js';
10
+ import { readSkillActiveState } from '../../state/skill-active.js';
11
+ import { recordSkillActivation } from '../../hooks/keyword-detector.js';
12
+ import { cancelModesForTest } from '../index.js';
13
+ const tmpdir = () => realpathSync(osTmpdir());
14
+ async function createFakeCodexBin(root, output) {
15
+ const binDir = join(root, 'bin');
16
+ await mkdir(binDir, { recursive: true });
17
+ await writeFile(join(binDir, 'codex'), `#!/bin/sh\nprintf '%s\\n' '${output}'\n`);
18
+ await chmod(join(binDir, 'codex'), 0o755);
19
+ return binDir;
20
+ }
9
21
  const testDir = dirname(fileURLToPath(import.meta.url));
10
22
  const repoRoot = join(testDir, '..', '..', '..');
11
23
  const omxBin = join(repoRoot, 'dist', 'cli', 'omx.js');
24
+ function cleanOmxEnv(overrides = {}) {
25
+ const env = { ...process.env };
26
+ for (const name of [
27
+ 'OMX_ROOT',
28
+ 'OMX_STATE_ROOT',
29
+ 'OMX_TEAM_STATE_ROOT',
30
+ 'OMX_SESSION_ID',
31
+ 'CODEX_SESSION_ID',
32
+ 'SESSION_ID',
33
+ 'OMX_RUNS_DIR',
34
+ ]) {
35
+ delete env[name];
36
+ }
37
+ return {
38
+ ...env,
39
+ ...(overrides.FAKE_TMUX_SESSION_NAME ? {
40
+ FAKE_TMUX_PANE_PID: '4242',
41
+ FAKE_TMUX_INTERNAL_SESSION_ID: '$1',
42
+ FAKE_TMUX_SESSION_CREATED: '100',
43
+ } : {}),
44
+ ...overrides,
45
+ };
46
+ }
12
47
  function runOmx(cwd, ...args) {
13
48
  return spawnSync(process.execPath, [omxBin, ...args], {
14
49
  cwd,
15
50
  encoding: 'utf-8',
51
+ env: cleanOmxEnv(),
16
52
  });
17
53
  }
18
54
  function runOmxWithEnv(cwd, env, ...args) {
19
55
  return spawnSync(process.execPath, [omxBin, ...args], {
20
56
  cwd,
21
57
  encoding: 'utf-8',
22
- env: { ...process.env, ...env },
58
+ env: cleanOmxEnv(env),
23
59
  });
24
60
  }
61
+ async function createFakeTmuxBin(root) {
62
+ const binDir = join(root, 'bin');
63
+ const tmuxPath = join(binDir, 'tmux');
64
+ await mkdir(binDir, { recursive: true });
65
+ await writeFile(tmuxPath, `#!/bin/sh
66
+ case "$1" in
67
+ list-panes)
68
+ count=0
69
+ if [ -n "$FAKE_TMUX_COUNTER_FILE" ] && [ -f "$FAKE_TMUX_COUNTER_FILE" ]; then count=$(cat "$FAKE_TMUX_COUNTER_FILE"); fi
70
+ count=$((count + 1))
71
+ if [ -n "$FAKE_TMUX_COUNTER_FILE" ]; then printf '%s' "$count" > "$FAKE_TMUX_COUNTER_FILE"; fi
72
+ created="$FAKE_TMUX_SESSION_CREATED"
73
+ if [ "$FAKE_TMUX_REPLACE_AFTER_FIRST" = "1" ] && [ "$count" -gt 1 ]; then created=$((created + 1)); fi
74
+ printf '%%42\\t0\\t%s\\t%s\\t%s\\t%s\\t%s\\n' "$FAKE_TMUX_PANE_PID" "$FAKE_TMUX_SESSION_NAME" "$FAKE_TMUX_INTERNAL_SESSION_ID" "$created" "$FAKE_OMX_SESSION_ID"
75
+ ;;
76
+ *) exit 1 ;;
77
+ esac
78
+ `);
79
+ await chmod(tmuxPath, 0o755);
80
+ return binDir;
81
+ }
82
+ async function writeActiveRunRecord(options) {
83
+ const activeDir = join(options.runsRoot, 'active-detached');
84
+ await mkdir(activeDir, { recursive: true });
85
+ await writeFile(join(activeDir, `${options.contextKey}.json`), JSON.stringify({
86
+ version: 1,
87
+ context_key: options.contextKey,
88
+ created_at: '2026-07-19T00:00:00.000Z',
89
+ source_cwd: options.sourceCwd,
90
+ ...(options.worktreeCwd ? { worktree_cwd: options.worktreeCwd } : {}),
91
+ argv: ['--madmax'],
92
+ run_dir: options.runDir,
93
+ tmux_session_name: options.tmuxSessionName,
94
+ session_id: options.sessionId,
95
+ tmux_pane_id: '%42',
96
+ tmux_internal_session_id: '$1',
97
+ tmux_session_created: '100',
98
+ tmux_pane_pid: 4242,
99
+ }, null, 2));
100
+ }
25
101
  describe('CLI session-scoped state parity', () => {
26
102
  it('status and cancel include session-scoped states', async () => {
27
103
  const wd = await mkdtemp(join(tmpdir(), 'omx-cli-session-scope-'));
28
104
  try {
29
105
  await mkdir(join(wd, '.omx', 'state'), { recursive: true });
30
- await writeFile(join(wd, '.omx', 'state', 'session.json'), JSON.stringify({ session_id: 'sess1' }));
106
+ await writeFile(join(wd, '.omx', 'state', 'session.json'), JSON.stringify({ session_id: 'sess1', cwd: wd, state_root: join(wd, '.omx', 'state') }));
31
107
  const scopedDir = join(wd, '.omx', 'state', 'sessions', 'sess1');
32
108
  await mkdir(scopedDir, { recursive: true });
33
109
  await writeFile(join(scopedDir, 'team-state.json'), JSON.stringify({
@@ -60,15 +136,14 @@ describe('CLI session-scoped state parity', () => {
60
136
  const ralphPath = join(sessionDir, 'ralph-state.json');
61
137
  const nativeStopPath = join(sessionDir, 'native-stop-state.json');
62
138
  await mkdir(sessionDir, { recursive: true });
63
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
139
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
64
140
  await writeFile(ralphPath, JSON.stringify({ active: true, current_phase: 'executing' }));
65
141
  await writeFile(nativeStopPath, JSON.stringify({
66
142
  sessions: { [sessionId]: { last_signature: 'ralph-stop|pending' } },
67
143
  }));
68
144
  const cancelResult = runOmxWithEnv(wd, { OMX_SESSION_ID: 'unmatched-session' }, 'cancel', '--force');
69
- assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
70
- assert.match(cancelResult.stderr, /OMX_SESSION_ID is not bound to session\.json/);
71
- assert.match(cancelResult.stdout, /No active modes to cancel\./);
145
+ assert.notEqual(cancelResult.status, 0);
146
+ assert.match(cancelResult.stderr, /OMX_SESSION_ID does not match the live session recorded in session\.json/);
72
147
  assert.deepEqual(JSON.parse(await readFile(ralphPath, 'utf-8')), { active: true, current_phase: 'executing' });
73
148
  assert.deepEqual(JSON.parse(await readFile(nativeStopPath, 'utf-8')), { sessions: { [sessionId]: { last_signature: 'ralph-stop|pending' } } });
74
149
  }
@@ -76,6 +151,660 @@ describe('CLI session-scoped state parity', () => {
76
151
  await rm(wd, { recursive: true, force: true });
77
152
  }
78
153
  });
154
+ it('ignores ambient Codex session aliases that do not own writable cancellation', async () => {
155
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-codex-session-mismatch-'));
156
+ try {
157
+ const stateDir = join(wd, '.omx', 'state');
158
+ const ownerSession = 'owner-session';
159
+ const foreignSession = 'foreign-codex-session';
160
+ const ownerPath = join(stateDir, 'sessions', ownerSession, 'ralplan-state.json');
161
+ const foreignPath = join(stateDir, 'sessions', foreignSession, 'team-state.json');
162
+ const ownerState = JSON.stringify({ active: true, mode: 'ralplan', current_phase: 'executing' }, null, 2);
163
+ const foreignState = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
164
+ await mkdir(dirname(ownerPath), { recursive: true });
165
+ await mkdir(dirname(foreignPath), { recursive: true });
166
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: ownerSession, cwd: wd, state_root: stateDir }));
167
+ await writeFile(ownerPath, ownerState);
168
+ await writeFile(foreignPath, foreignState);
169
+ for (const environmentName of ['CODEX_SESSION_ID', 'SESSION_ID']) {
170
+ await writeFile(ownerPath, ownerState);
171
+ const cancelResult = runOmxWithEnv(wd, { [environmentName]: foreignSession }, 'cancel');
172
+ assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
173
+ assert.match(cancelResult.stdout, /Cancelled: ralplan/);
174
+ assert.doesNotMatch(cancelResult.stdout, /Cancelled: team/);
175
+ assert.equal(JSON.parse(await readFile(ownerPath, 'utf-8')).active, false);
176
+ assert.equal(await readFile(foreignPath, 'utf-8'), foreignState);
177
+ }
178
+ }
179
+ finally {
180
+ await rm(wd, { recursive: true, force: true });
181
+ }
182
+ });
183
+ it('keeps root cancellation authoritative despite ambient session aliases', async () => {
184
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-root-ambient-session-'));
185
+ try {
186
+ const stateDir = join(wd, '.omx', 'state');
187
+ const foreignSession = 'foreign-ambient-session';
188
+ const rootPath = join(stateDir, 'team-state.json');
189
+ const foreignPath = join(stateDir, 'sessions', foreignSession, 'ralplan-state.json');
190
+ const rootState = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
191
+ const foreignState = JSON.stringify({ active: true, mode: 'ralplan', current_phase: 'executing' }, null, 2);
192
+ await mkdir(dirname(foreignPath), { recursive: true });
193
+ await writeFile(rootPath, rootState);
194
+ await writeFile(foreignPath, foreignState);
195
+ for (const environmentName of ['CODEX_SESSION_ID', 'SESSION_ID']) {
196
+ await writeFile(rootPath, rootState);
197
+ const cancelResult = runOmxWithEnv(wd, { [environmentName]: foreignSession }, 'cancel');
198
+ assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
199
+ assert.match(cancelResult.stdout, /Cancelled: team/);
200
+ assert.doesNotMatch(cancelResult.stdout, /Cancelled: ralplan/);
201
+ assert.equal(JSON.parse(await readFile(rootPath, 'utf-8')).active, false);
202
+ assert.equal(await readFile(foreignPath, 'utf-8'), foreignState);
203
+ }
204
+ }
205
+ finally {
206
+ await rm(wd, { recursive: true, force: true });
207
+ }
208
+ });
209
+ it('rejects unsupported cancellation flags before mutation', async () => {
210
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-flags-'));
211
+ try {
212
+ const stateDir = join(wd, '.omx', 'state');
213
+ const statePath = join(stateDir, 'team-state.json');
214
+ const state = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
215
+ await mkdir(stateDir, { recursive: true });
216
+ await writeFile(statePath, state);
217
+ for (const args of [['--all'], ['--unknown'], ['--force', '--all']]) {
218
+ const result = runOmx(wd, 'cancel', ...args);
219
+ assert.notEqual(result.status, 0);
220
+ assert.equal(await readFile(statePath, 'utf-8'), state);
221
+ }
222
+ }
223
+ finally {
224
+ await rm(wd, { recursive: true, force: true });
225
+ }
226
+ });
227
+ it('rejects symlinked ordinary-scope state targets', async () => {
228
+ if (process.platform === 'win32')
229
+ return;
230
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-root-symlink-'));
231
+ const outside = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-root-symlink-outside-'));
232
+ try {
233
+ const stateDir = join(wd, '.omx', 'state');
234
+ const outsidePath = join(outside, 'team-state.json');
235
+ const state = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
236
+ await mkdir(stateDir, { recursive: true });
237
+ await writeFile(outsidePath, state);
238
+ await symlink(outsidePath, join(stateDir, 'team-state.json'));
239
+ const result = runOmx(wd, 'cancel');
240
+ assert.notEqual(result.status, 0);
241
+ assert.match(result.stderr, /non-regular state target/);
242
+ assert.equal(await readFile(outsidePath, 'utf-8'), state);
243
+ }
244
+ finally {
245
+ await rm(wd, { recursive: true, force: true });
246
+ await rm(outside, { recursive: true, force: true });
247
+ }
248
+ });
249
+ it('rejects symlinked session authority directories', async () => {
250
+ if (process.platform === 'win32')
251
+ return;
252
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-session-root-symlink-'));
253
+ const outside = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-session-root-outside-'));
254
+ try {
255
+ const stateDir = join(wd, '.omx', 'state');
256
+ const sessionId = 'session-root-link';
257
+ const outsidePath = join(outside, 'ralplan-state.json');
258
+ const state = JSON.stringify({ active: true, mode: 'ralplan', session_id: sessionId, current_phase: 'executing' }, null, 2);
259
+ await mkdir(join(stateDir, 'sessions'), { recursive: true });
260
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
261
+ await writeFile(outsidePath, state);
262
+ await symlink(outside, join(stateDir, 'sessions', sessionId), 'dir');
263
+ const result = runOmx(wd, 'cancel');
264
+ assert.notEqual(result.status, 0);
265
+ assert.match(result.stderr, /symlinked authority component|unusable/);
266
+ assert.equal(await readFile(outsidePath, 'utf-8'), state);
267
+ }
268
+ finally {
269
+ await rm(wd, { recursive: true, force: true });
270
+ await rm(outside, { recursive: true, force: true });
271
+ }
272
+ });
273
+ it('denies contradictory selected session state transaction-wide', async () => {
274
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-contradictory-'));
275
+ try {
276
+ const stateDir = join(wd, '.omx', 'state');
277
+ const sessionId = 'sess-contradictory';
278
+ const sessionDir = join(stateDir, 'sessions', sessionId);
279
+ const contradictoryPath = join(sessionDir, 'ralplan-state.json');
280
+ const validPath = join(sessionDir, 'autopilot-state.json');
281
+ const contradictory = JSON.stringify({ active: true, mode: 'team', session_id: 'foreign', current_phase: 'team-exec' }, null, 2);
282
+ const valid = JSON.stringify({ active: true, mode: 'autopilot', session_id: sessionId, current_phase: 'executing' }, null, 2);
283
+ await mkdir(sessionDir, { recursive: true });
284
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
285
+ await writeFile(contradictoryPath, contradictory);
286
+ await writeFile(validPath, valid);
287
+ const result = runOmx(wd, 'cancel');
288
+ assert.notEqual(result.status, 0);
289
+ assert.match(result.stderr, /contradictory mode state/);
290
+ assert.equal(await readFile(contradictoryPath, 'utf-8'), contradictory);
291
+ assert.equal(await readFile(validPath, 'utf-8'), valid);
292
+ }
293
+ finally {
294
+ await rm(wd, { recursive: true, force: true });
295
+ }
296
+ });
297
+ it('fails closed when owned session state is malformed', async () => {
298
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-malformed-owner-'));
299
+ try {
300
+ const stateDir = join(wd, '.omx', 'state');
301
+ const sessionId = 'malformed-owner';
302
+ const sessionDir = join(stateDir, 'sessions', sessionId);
303
+ const malformedPath = join(sessionDir, 'ralplan-state.json');
304
+ const teamPath = join(stateDir, 'team-state.json');
305
+ const malformedState = '{"active":true';
306
+ const rootTeamState = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
307
+ await mkdir(sessionDir, { recursive: true });
308
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
309
+ await writeFile(malformedPath, malformedState);
310
+ await writeFile(teamPath, rootTeamState);
311
+ const cancelResult = runOmx(wd, 'cancel');
312
+ assert.notEqual(cancelResult.status, 0);
313
+ assert.match(cancelResult.stderr, /Refusing partial cancellation/);
314
+ assert.doesNotMatch(cancelResult.stdout, /Cancelled: team/);
315
+ assert.equal(await readFile(malformedPath, 'utf-8'), malformedState);
316
+ assert.equal(await readFile(teamPath, 'utf-8'), rootTeamState);
317
+ }
318
+ finally {
319
+ await rm(wd, { recursive: true, force: true });
320
+ }
321
+ });
322
+ it('denies wrong-typed and nested ownership contradictions transaction-wide', async () => {
323
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-owner-evidence-'));
324
+ try {
325
+ const stateDir = join(wd, '.omx', 'state');
326
+ const sessionId = 'owner-evidence-session';
327
+ const sessionDir = join(stateDir, 'sessions', sessionId);
328
+ const skillPath = join(sessionDir, 'skill-active-state.json');
329
+ const modePath = join(sessionDir, 'autopilot-state.json');
330
+ const skillState = JSON.stringify({ active: true, skill: 'autopilot', owner_codex_session_id: 42, active_skills: [{ skill: 'autopilot', active: true, owner_codex_session_id: 'foreign-owner' }] }, null, 2);
331
+ const modeState = JSON.stringify({ active: true, mode: 'autopilot', session_id: sessionId }, null, 2);
332
+ await mkdir(sessionDir, { recursive: true });
333
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
334
+ await writeFile(skillPath, skillState);
335
+ await writeFile(modePath, modeState);
336
+ const result = runOmx(wd, 'cancel');
337
+ assert.notEqual(result.status, 0);
338
+ assert.match(result.stderr, /contradictory owner_codex_session_id/);
339
+ assert.equal(await readFile(skillPath, 'utf-8'), skillState);
340
+ assert.equal(await readFile(modePath, 'utf-8'), modeState);
341
+ }
342
+ finally {
343
+ await rm(wd, { recursive: true, force: true });
344
+ }
345
+ });
346
+ it('keeps root Team compatibility state read-only when current-session Ralplan owns cancellation', async () => {
347
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-cross-mode-scope-'));
348
+ try {
349
+ const stateDir = join(wd, '.omx', 'state');
350
+ const sessionId = 'sess-ralplan-owner';
351
+ const sessionDir = join(stateDir, 'sessions', sessionId);
352
+ const ralplanPath = join(sessionDir, 'ralplan-state.json');
353
+ const teamPath = join(stateDir, 'team-state.json');
354
+ const skillActivePath = join(stateDir, 'skill-active-state.json');
355
+ const nativeStopPath = join(stateDir, 'native-stop-state.json');
356
+ const ralplanState = JSON.stringify({
357
+ active: true,
358
+ mode: 'ralplan',
359
+ current_phase: 'executing',
360
+ }, null, 2);
361
+ const rootTeamState = JSON.stringify({
362
+ active: true,
363
+ mode: 'team',
364
+ current_phase: 'team-exec',
365
+ }, null, 2);
366
+ const rootSkillActiveState = JSON.stringify({
367
+ version: 1,
368
+ active: true,
369
+ skill: 'team',
370
+ phase: 'team-exec',
371
+ active_skills: [{ skill: 'team', phase: 'team-exec', active: true }],
372
+ }, null, 2);
373
+ const rootNativeStopState = JSON.stringify({
374
+ sessions: { [sessionId]: { last_signature: 'team-stop|pending' } },
375
+ }, null, 2);
376
+ await mkdir(sessionDir, { recursive: true });
377
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
378
+ await writeFile(ralplanPath, ralplanState);
379
+ await writeFile(teamPath, rootTeamState);
380
+ await writeFile(skillActivePath, rootSkillActiveState);
381
+ await writeFile(nativeStopPath, rootNativeStopState);
382
+ const cancelResult = runOmx(wd, 'cancel');
383
+ assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
384
+ assert.match(cancelResult.stdout, /Cancelled: ralplan/);
385
+ assert.doesNotMatch(cancelResult.stdout, /Cancelled: team/);
386
+ assert.equal(JSON.parse(await readFile(ralplanPath, 'utf-8')).active, false);
387
+ assert.equal(await readFile(teamPath, 'utf-8'), rootTeamState);
388
+ assert.equal(await readFile(skillActivePath, 'utf-8'), rootSkillActiveState);
389
+ assert.equal(await readFile(nativeStopPath, 'utf-8'), rootNativeStopState);
390
+ await writeFile(ralplanPath, ralplanState);
391
+ const forceResult = runOmx(wd, 'cancel', '--force');
392
+ assert.equal(forceResult.status, 0, forceResult.stderr || forceResult.stdout);
393
+ assert.match(forceResult.stdout, /Cancelled: ralplan/);
394
+ assert.doesNotMatch(forceResult.stdout, /Cancelled: team/);
395
+ assert.equal(JSON.parse(await readFile(ralplanPath, 'utf-8')).active, false);
396
+ assert.equal(await readFile(teamPath, 'utf-8'), rootTeamState);
397
+ assert.equal(await readFile(skillActivePath, 'utf-8'), rootSkillActiveState);
398
+ assert.equal(await readFile(nativeStopPath, 'utf-8'), rootNativeStopState);
399
+ }
400
+ finally {
401
+ await rm(wd, { recursive: true, force: true });
402
+ }
403
+ });
404
+ it('replaces only a stale top-level skill owner under complete current native authority', async () => {
405
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-stale-skill-owner-'));
406
+ try {
407
+ const stateDir = join(wd, '.omx', 'state');
408
+ const sessionId = 'current-session';
409
+ const nativeSessionId = 'current-native-owner';
410
+ const sessionDir = join(stateDir, 'sessions', sessionId);
411
+ const pointer = JSON.stringify({
412
+ session_id: sessionId,
413
+ native_session_id: nativeSessionId,
414
+ cwd: wd,
415
+ state_root: stateDir,
416
+ platform: process.platform,
417
+ }, null, 2);
418
+ const skillPath = join(sessionDir, 'skill-active-state.json');
419
+ const ralplanPath = join(sessionDir, 'ralplan-state.json');
420
+ const rootSkillPath = join(stateDir, 'skill-active-state.json');
421
+ const rootSkill = JSON.stringify({ active: true, skill: 'team' }, null, 2);
422
+ await mkdir(sessionDir, { recursive: true });
423
+ await writeFile(join(stateDir, 'session.json'), pointer);
424
+ await mkdir(join(stateDir, 'sessions', nativeSessionId), { recursive: true });
425
+ await writeFile(join(stateDir, 'sessions', nativeSessionId, 'session-owner.json'), JSON.stringify({
426
+ session_id: nativeSessionId,
427
+ native_session_id: nativeSessionId,
428
+ cwd: wd,
429
+ platform: process.platform,
430
+ }, null, 2));
431
+ await writeFile(skillPath, JSON.stringify({
432
+ active: true,
433
+ skill: 'ralplan',
434
+ owner_codex_session_id: 'stale-native-owner',
435
+ active_skills: [{ skill: 'ralplan', active: true, owner_codex_session_id: nativeSessionId }],
436
+ }, null, 2));
437
+ await writeFile(ralplanPath, JSON.stringify({ active: true, mode: 'ralplan' }, null, 2));
438
+ await writeFile(rootSkillPath, rootSkill);
439
+ const descendantCwd = join(wd, 'packages', 'app');
440
+ await mkdir(descendantCwd, { recursive: true });
441
+ const result = runOmxWithEnv(descendantCwd, { OMX_ROOT: wd }, 'cancel');
442
+ assert.equal(result.status, 0, result.stderr || result.stdout);
443
+ const skill = JSON.parse(await readFile(skillPath, 'utf-8'));
444
+ assert.equal(skill.owner_codex_session_id, nativeSessionId);
445
+ assert.equal(skill.active_skills[0].owner_codex_session_id, nativeSessionId);
446
+ assert.equal(skill.active, false);
447
+ assert.equal(await readFile(rootSkillPath, 'utf-8'), rootSkill);
448
+ const repeat = runOmx(wd, 'cancel');
449
+ assert.equal(repeat.status, 0, repeat.stderr || repeat.stdout);
450
+ assert.equal(JSON.parse(await readFile(skillPath, 'utf-8')).owner_codex_session_id, nativeSessionId);
451
+ const malformedNestedSkill = JSON.stringify({
452
+ active: true,
453
+ skill: 'ralplan',
454
+ owner_codex_session_id: nativeSessionId,
455
+ active_skills: [{ skill: 'ralplan', active: 'true', owner_codex_session_id: nativeSessionId }],
456
+ }, null, 2);
457
+ await writeFile(skillPath, malformedNestedSkill);
458
+ const malformedNestedResult = runOmx(wd, 'cancel');
459
+ assert.notEqual(malformedNestedResult.status, 0);
460
+ assert.match(malformedNestedResult.stderr, /malformed active/);
461
+ assert.equal(await readFile(skillPath, 'utf-8'), malformedNestedSkill);
462
+ const fieldSwappedSkill = JSON.stringify({
463
+ active: true,
464
+ skill: 'ralplan',
465
+ session_id: nativeSessionId,
466
+ owner_codex_session_id: nativeSessionId,
467
+ active_skills: [{ skill: 'ralplan', active: true, session_id: nativeSessionId, owner_codex_session_id: nativeSessionId }],
468
+ }, null, 2);
469
+ await writeFile(skillPath, fieldSwappedSkill);
470
+ const fieldSwappedResult = runOmx(wd, 'cancel');
471
+ assert.notEqual(fieldSwappedResult.status, 0);
472
+ assert.match(fieldSwappedResult.stderr, /contradictory session_id/);
473
+ assert.equal(await readFile(skillPath, 'utf-8'), fieldSwappedSkill);
474
+ const inactiveStaleSkill = JSON.stringify({
475
+ active: false,
476
+ skill: 'ralplan',
477
+ owner_codex_session_id: 'stale-native-owner',
478
+ active_skills: [{ skill: 'ralplan', active: false, owner_codex_session_id: nativeSessionId }],
479
+ }, null, 2);
480
+ await writeFile(skillPath, inactiveStaleSkill);
481
+ const noActiveResult = runOmx(wd, 'cancel');
482
+ assert.equal(noActiveResult.status, 0, noActiveResult.stderr || noActiveResult.stdout);
483
+ assert.match(noActiveResult.stdout, /No active modes to cancel/);
484
+ assert.equal(await readFile(skillPath, 'utf-8'), inactiveStaleSkill);
485
+ const displacedDir = join(stateDir, 'sessions', 'stale-native-owner');
486
+ await mkdir(displacedDir, { recursive: true });
487
+ await writeFile(join(displacedDir, 'session-owner.json'), JSON.stringify({
488
+ session_id: 'stale-native-owner',
489
+ native_session_id: 'stale-native-owner',
490
+ cwd: wd,
491
+ platform: process.platform,
492
+ }, null, 2));
493
+ const liveDisplacedSkill = JSON.stringify({
494
+ active: true,
495
+ skill: 'ralplan',
496
+ owner_codex_session_id: 'stale-native-owner',
497
+ active_skills: [{ skill: 'ralplan', active: true, owner_codex_session_id: nativeSessionId }],
498
+ }, null, 2);
499
+ const activeRalplan = JSON.stringify({ active: true, mode: 'ralplan' }, null, 2);
500
+ await writeFile(skillPath, liveDisplacedSkill);
501
+ await writeFile(ralplanPath, activeRalplan);
502
+ const liveDisplaced = runOmx(wd, 'cancel');
503
+ assert.notEqual(liveDisplaced.status, 0);
504
+ assert.match(liveDisplaced.stderr, /non-stale displaced owner evidence/);
505
+ assert.equal(await readFile(skillPath, 'utf-8'), liveDisplacedSkill);
506
+ assert.equal(await readFile(ralplanPath, 'utf-8'), activeRalplan);
507
+ }
508
+ finally {
509
+ await rm(wd, { recursive: true, force: true });
510
+ }
511
+ });
512
+ it('preserves active Team skill entries while cancelling Ralph', async () => {
513
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-ralph-team-peer-'));
514
+ try {
515
+ const stateDir = join(wd, '.omx', 'state');
516
+ const sessionId = 'canonical-session';
517
+ const nativeSessionId = 'native-session';
518
+ const sessionDir = join(stateDir, 'sessions', sessionId);
519
+ const skillPath = join(sessionDir, 'skill-active-state.json');
520
+ const ralphPath = join(sessionDir, 'ralph-state.json');
521
+ const teamPath = join(sessionDir, 'team-state.json');
522
+ const teamState = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
523
+ await mkdir(sessionDir, { recursive: true });
524
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({
525
+ session_id: sessionId,
526
+ native_session_id: nativeSessionId,
527
+ cwd: wd,
528
+ state_root: stateDir,
529
+ }, null, 2));
530
+ await mkdir(join(stateDir, 'sessions', nativeSessionId), { recursive: true });
531
+ await writeFile(join(stateDir, 'sessions', nativeSessionId, 'session-owner.json'), JSON.stringify({
532
+ session_id: nativeSessionId,
533
+ native_session_id: nativeSessionId,
534
+ cwd: wd,
535
+ }, null, 2));
536
+ await writeFile(skillPath, JSON.stringify({
537
+ active: true,
538
+ skill: 'ralph',
539
+ phase: 'executing',
540
+ session_id: sessionId,
541
+ owner_codex_session_id: nativeSessionId,
542
+ active_skills: [
543
+ { skill: 'ralph', phase: 'executing', session_id: sessionId, owner_codex_session_id: nativeSessionId },
544
+ { skill: 'team', phase: 'team-exec', session_id: sessionId, owner_codex_session_id: nativeSessionId },
545
+ ],
546
+ }, null, 2));
547
+ await writeFile(ralphPath, JSON.stringify({ active: true, mode: 'ralph' }, null, 2));
548
+ await writeFile(teamPath, teamState);
549
+ const result = runOmx(wd, 'cancel');
550
+ assert.equal(result.status, 0, result.stderr || result.stdout);
551
+ const skill = JSON.parse(await readFile(skillPath, 'utf-8'));
552
+ assert.equal(skill.active, true);
553
+ assert.equal(skill.skill, 'team');
554
+ assert.equal(skill.phase, 'team-exec');
555
+ assert.equal(skill.current_phase, 'team-exec');
556
+ assert.deepEqual(skill.active_skills.map((entry) => [entry.skill, entry.active]), [
557
+ ['ralph', false],
558
+ ['team', undefined],
559
+ ]);
560
+ assert.equal(JSON.parse(await readFile(ralphPath, 'utf-8')).active, false);
561
+ assert.equal(await readFile(teamPath, 'utf-8'), teamState);
562
+ }
563
+ finally {
564
+ await rm(wd, { recursive: true, force: true });
565
+ }
566
+ });
567
+ it('preserves a legacy top-level Team marker while cancelling Ralph', async () => {
568
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-legacy-team-ralph-'));
569
+ try {
570
+ const stateDir = join(wd, '.omx', 'state');
571
+ const sessionId = 'legacy-team-ralph-session';
572
+ const sessionDir = join(stateDir, 'sessions', sessionId);
573
+ await mkdir(sessionDir, { recursive: true });
574
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
575
+ const skillPath = join(sessionDir, 'skill-active-state.json');
576
+ const teamPath = join(sessionDir, 'team-state.json');
577
+ const ralphPath = join(sessionDir, 'ralph-state.json');
578
+ const legacyTeamSkill = JSON.stringify({ active: true, skill: 'team', phase: 'team-exec', session_id: sessionId, active_skills: [{ skill: 'code-review', active: false }] }, null, 2);
579
+ const activeTeam = JSON.stringify({ active: true, mode: 'team', session_id: sessionId }, null, 2);
580
+ await writeFile(skillPath, legacyTeamSkill);
581
+ await writeFile(teamPath, activeTeam);
582
+ await writeFile(ralphPath, JSON.stringify({ active: true, mode: 'ralph', session_id: sessionId }, null, 2));
583
+ const result = runOmx(wd, 'cancel');
584
+ assert.equal(result.status, 0, result.stderr || result.stdout);
585
+ assert.equal(await readFile(skillPath, 'utf-8'), legacyTeamSkill);
586
+ assert.equal(await readFile(teamPath, 'utf-8'), activeTeam);
587
+ assert.equal(JSON.parse(await readFile(ralphPath, 'utf-8')).active, false);
588
+ }
589
+ finally {
590
+ await rm(wd, { recursive: true, force: true });
591
+ }
592
+ });
593
+ it('cancels valid skill-active-only workflow names without a duplicate allowlist', async () => {
594
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-skill-only-name-'));
595
+ try {
596
+ const stateDir = join(wd, '.omx', 'state');
597
+ const sessionId = 'skill-only-name-session';
598
+ const nativeSessionId = 'skill-only-name-native';
599
+ const sessionDir = join(stateDir, 'sessions', sessionId);
600
+ await mkdir(sessionDir, { recursive: true });
601
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, native_session_id: nativeSessionId, cwd: wd, state_root: stateDir }));
602
+ const nativeOwnerDir = join(stateDir, 'sessions', nativeSessionId);
603
+ await mkdir(nativeOwnerDir, { recursive: true });
604
+ await writeFile(join(nativeOwnerDir, 'session-owner.json'), JSON.stringify({
605
+ session_id: nativeSessionId,
606
+ native_session_id: nativeSessionId,
607
+ cwd: wd,
608
+ platform: process.platform,
609
+ }));
610
+ const skillPath = join(sessionDir, 'skill-active-state.json');
611
+ await writeFile(skillPath, JSON.stringify({
612
+ active: true,
613
+ skill: 'code-review',
614
+ session_id: sessionId,
615
+ active_skills: [{ skill: 'code-review', active: true, session_id: sessionId, owner_codex_session_id: nativeSessionId }],
616
+ }, null, 2));
617
+ const result = runOmx(wd, 'cancel');
618
+ assert.equal(result.status, 0, result.stderr || result.stdout);
619
+ const skill = JSON.parse(await readFile(skillPath, 'utf-8'));
620
+ assert.equal(skill.active, false);
621
+ assert.equal(skill.active_skills[0].active, false);
622
+ }
623
+ finally {
624
+ await rm(wd, { recursive: true, force: true });
625
+ }
626
+ });
627
+ it('preserves mode-only cancellation without creating a skill marker', async () => {
628
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-mode-only-'));
629
+ try {
630
+ const stateDir = join(wd, '.omx', 'state');
631
+ const sessionId = 'mode-only-session';
632
+ const sessionDir = join(stateDir, 'sessions', sessionId);
633
+ const skillPath = join(sessionDir, 'skill-active-state.json');
634
+ const modePath = join(sessionDir, 'ralplan-state.json');
635
+ await mkdir(sessionDir, { recursive: true });
636
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
637
+ await writeFile(modePath, JSON.stringify({ active: true, mode: 'ralplan', session_id: sessionId }, null, 2));
638
+ const result = runOmx(wd, 'cancel');
639
+ assert.equal(result.status, 0, result.stderr || result.stdout);
640
+ assert.match(result.stdout, /Cancelled: ralplan/);
641
+ assert.equal(existsSync(skillPath), false);
642
+ assert.equal(JSON.parse(await readFile(modePath, 'utf-8')).active, false);
643
+ const repeat = runOmx(wd, 'cancel');
644
+ assert.equal(repeat.status, 0, repeat.stderr || repeat.stdout);
645
+ assert.equal(existsSync(skillPath), false);
646
+ }
647
+ finally {
648
+ await rm(wd, { recursive: true, force: true });
649
+ }
650
+ });
651
+ it('reports rollback restoration failures without false cancellation success', async () => {
652
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cancel-rollback-failure-'));
653
+ try {
654
+ const stateDir = join(wd, '.omx', 'state');
655
+ const sessionId = 'rollback-session';
656
+ const nativeSessionId = 'rollback-native';
657
+ const sessionDir = join(stateDir, 'sessions', sessionId);
658
+ const modePath = join(sessionDir, 'ralplan-state.json');
659
+ const skillPath = join(sessionDir, 'skill-active-state.json');
660
+ const modeState = JSON.stringify({ active: true, mode: 'ralplan', session_id: sessionId }, null, 2);
661
+ const skillState = JSON.stringify({
662
+ active: true,
663
+ skill: 'ralplan',
664
+ session_id: sessionId,
665
+ owner_codex_session_id: nativeSessionId,
666
+ active_skills: [{ skill: 'ralplan', active: true, session_id: sessionId, owner_codex_session_id: nativeSessionId }],
667
+ }, null, 2);
668
+ await mkdir(sessionDir, { recursive: true });
669
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, native_session_id: nativeSessionId, cwd: wd, state_root: stateDir }));
670
+ await mkdir(join(stateDir, 'sessions', nativeSessionId), { recursive: true });
671
+ await writeFile(join(stateDir, 'sessions', nativeSessionId, 'session-owner.json'), JSON.stringify({
672
+ session_id: nativeSessionId,
673
+ native_session_id: nativeSessionId,
674
+ cwd: wd,
675
+ }));
676
+ await writeFile(modePath, modeState);
677
+ await writeFile(skillPath, skillState);
678
+ const errors = [];
679
+ const logs = [];
680
+ const previousError = console.error;
681
+ const previousLog = console.log;
682
+ const previousExitCode = process.exitCode;
683
+ const previousStderrWrite = process.stderr.write;
684
+ console.error = (...args) => { errors.push(args.map(String).join(' ')); };
685
+ console.log = (...args) => { logs.push(args.map(String).join(' ')); };
686
+ process.stderr.write = ((chunk) => {
687
+ errors.push(String(chunk));
688
+ return true;
689
+ });
690
+ try {
691
+ await cancelModesForTest(wd, [], {
692
+ writeFailureMode: 'skill-active',
693
+ rollbackFailureMode: 'ralplan',
694
+ });
695
+ assert.equal(process.exitCode, 1);
696
+ }
697
+ finally {
698
+ console.error = previousError;
699
+ console.log = previousLog;
700
+ process.stderr.write = previousStderrWrite;
701
+ process.exitCode = previousExitCode;
702
+ }
703
+ assert.match(errors.join('\n'), /cancellation_rollback_failed:1:ralplan/);
704
+ assert.doesNotMatch(logs.join('\n'), /Cancelled:/);
705
+ assert.equal(await readFile(skillPath, 'utf-8'), skillState);
706
+ assert.equal(JSON.parse(await readFile(modePath, 'utf-8')).active, false);
707
+ }
708
+ finally {
709
+ await rm(wd, { recursive: true, force: true });
710
+ }
711
+ });
712
+ it('fails Ralplan preflight without mutating unproven session state', async () => {
713
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-ralplan-preflight-scope-'));
714
+ const codexBin = await createFakeCodexBin(wd, 'codex-cli 0.147.0');
715
+ try {
716
+ const stateDir = join(wd, '.omx', 'state');
717
+ const sessionId = 'sess-unproven-owner';
718
+ const sessionDir = join(stateDir, 'sessions', sessionId);
719
+ const ralplanPath = join(sessionDir, 'ralplan-state.json');
720
+ const ralplanState = JSON.stringify({
721
+ active: true,
722
+ mode: 'ralplan',
723
+ current_phase: 'starting',
724
+ }, null, 2);
725
+ await mkdir(sessionDir, { recursive: true });
726
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
727
+ await writeFile(ralplanPath, ralplanState);
728
+ const preflightResult = runOmxWithEnv(wd, { PATH: `${codexBin}:${process.env.PATH}` }, 'ralplan', 'preflight', '--json');
729
+ assert.equal(preflightResult.status, 1, preflightResult.stderr || preflightResult.stdout);
730
+ assert.deepEqual(JSON.parse(preflightResult.stdout), {
731
+ ok: false,
732
+ reason: 'unsupported_documented_leader_proof',
733
+ diagnostics: {
734
+ probe_status: 'ok',
735
+ detected_version: '0.147.0',
736
+ documented_root_identity: { status: 'unknown' },
737
+ },
738
+ });
739
+ assert.equal(await readFile(ralplanPath, 'utf-8'), ralplanState);
740
+ }
741
+ finally {
742
+ await rm(wd, { recursive: true, force: true });
743
+ }
744
+ });
745
+ it('keeps exact-owner routing-only Ralplan active after preflight denial', async () => {
746
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-ralplan-preflight-owner-'));
747
+ const codexBin = await createFakeCodexBin(wd, 'codex-cli 0.147.0');
748
+ try {
749
+ const stateDir = join(wd, '.omx', 'state');
750
+ const sessionId = 'sess-proven-owner';
751
+ const sessionDir = join(stateDir, 'sessions', sessionId);
752
+ const ralplanPath = join(sessionDir, 'ralplan-state.json');
753
+ const sessionSkillPath = join(sessionDir, 'skill-active-state.json');
754
+ const rootTeamPath = join(stateDir, 'team-state.json');
755
+ const rootSkillPath = join(stateDir, 'skill-active-state.json');
756
+ const rootStopPath = join(stateDir, 'native-stop-state.json');
757
+ const rootTeam = JSON.stringify({ active: true, mode: 'team', current_phase: 'team-exec' }, null, 2);
758
+ const rootSkill = JSON.stringify({ active: true, skill: 'team', phase: 'team-exec' }, null, 2);
759
+ const rootStop = JSON.stringify({ sessions: { foreign: { pending: true } } }, null, 2);
760
+ await mkdir(sessionDir, { recursive: true });
761
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
762
+ const activated = await recordSkillActivation({
763
+ stateDir,
764
+ sourceCwd: wd,
765
+ text: '$ralplan continue issue #3212',
766
+ sessionId,
767
+ threadId: 'thread-preflight-owner',
768
+ turnId: 'turn-preflight-owner',
769
+ nowIso: '2026-07-19T00:00:00.000Z',
770
+ });
771
+ assert.ok(activated);
772
+ const ralplanState = await readFile(ralplanPath, 'utf-8');
773
+ const sessionSkillState = await readFile(sessionSkillPath, 'utf-8');
774
+ await writeFile(rootTeamPath, rootTeam);
775
+ await writeFile(rootSkillPath, rootSkill);
776
+ await writeFile(rootStopPath, rootStop);
777
+ const sessionEntries = await readdir(sessionDir);
778
+ const preflightResult = runOmxWithEnv(wd, { OMX_SESSION_ID: sessionId, PATH: `${codexBin}:${process.env.PATH}` }, 'ralplan', 'preflight', '--json');
779
+ assert.equal(preflightResult.status, 1, preflightResult.stderr || preflightResult.stdout);
780
+ assert.deepEqual(JSON.parse(preflightResult.stdout), {
781
+ ok: false,
782
+ reason: 'unsupported_documented_leader_proof',
783
+ diagnostics: {
784
+ probe_status: 'ok',
785
+ detected_version: '0.147.0',
786
+ documented_root_identity: { status: 'unknown' },
787
+ },
788
+ });
789
+ assert.equal(await readFile(ralplanPath, 'utf-8'), ralplanState);
790
+ assert.equal(await readFile(sessionSkillPath, 'utf-8'), sessionSkillState);
791
+ const [state, skillState] = await Promise.all([
792
+ readModeState('ralplan', wd),
793
+ readSkillActiveState(sessionSkillPath),
794
+ ]);
795
+ assert.equal(state?.active, true);
796
+ assert.equal(skillState?.active, true);
797
+ const postPreflightEntries = await readdir(sessionDir);
798
+ assert.deepEqual(postPreflightEntries, sessionEntries);
799
+ assert.equal(postPreflightEntries.some((name) => name.startsWith('.ralplan-neutralization-')), false);
800
+ assert.equal(await readFile(rootTeamPath, 'utf-8'), rootTeam);
801
+ assert.equal(await readFile(rootSkillPath, 'utf-8'), rootSkill);
802
+ assert.equal(await readFile(rootStopPath, 'utf-8'), rootStop);
803
+ }
804
+ finally {
805
+ await rm(wd, { recursive: true, force: true });
806
+ }
807
+ });
79
808
  it('status does not report a root fallback mode as active after current-session clear', async () => {
80
809
  const wd = await mkdtemp(join(tmpdir(), 'omx-cli-session-clear-fallback-'));
81
810
  try {
@@ -83,7 +812,7 @@ describe('CLI session-scoped state parity', () => {
83
812
  const sessionId = 'sess-clear';
84
813
  const sessionDir = join(stateDir, 'sessions', sessionId);
85
814
  await mkdir(sessionDir, { recursive: true });
86
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
815
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
87
816
  await writeFile(join(stateDir, 'deep-interview-state.json'), JSON.stringify({
88
817
  active: true,
89
818
  mode: 'deep-interview',
@@ -106,17 +835,332 @@ describe('CLI session-scoped state parity', () => {
106
835
  await rm(wd, { recursive: true, force: true });
107
836
  }
108
837
  });
838
+ it('does not cancel a foreign hook-visible run-dir from session-owned scope', async () => {
839
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-cancel-foreign-owner-'));
840
+ const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-cancel-foreign-runs-'));
841
+ try {
842
+ const ownerSessionId = 'sess-canonical-owner';
843
+ const foreignSessionId = 'sess-foreign-run';
844
+ const stateDir = join(wd, '.omx', 'state');
845
+ const runDir = join(runsRoot, 'run-foreign-session');
846
+ const runStateDir = join(runDir, '.omx', 'state');
847
+ const foreignSessionDir = join(runStateDir, 'sessions', foreignSessionId);
848
+ const foreignStatePath = join(foreignSessionDir, 'autopilot-state.json');
849
+ const foreignState = JSON.stringify({
850
+ active: true,
851
+ mode: 'autopilot',
852
+ current_phase: 'deep-interview',
853
+ }, null, 2);
854
+ await mkdir(join(stateDir, 'sessions', ownerSessionId), { recursive: true });
855
+ await mkdir(foreignSessionDir, { recursive: true });
856
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: ownerSessionId }));
857
+ await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: foreignSessionId }));
858
+ await writeFile(foreignStatePath, foreignState);
859
+ await writeFile(join(runsRoot, 'registry.jsonl'), `${JSON.stringify({
860
+ source_cwd: wd,
861
+ run_dir: runDir,
862
+ })}\n`);
863
+ const cancelResult = runOmxWithEnv(wd, { OMX_RUNS_DIR: runsRoot }, 'cancel');
864
+ assert.notEqual(cancelResult.status, 0);
865
+ assert.match(cancelResult.stderr, /session\.json is present but unusable/);
866
+ assert.doesNotMatch(cancelResult.stdout, /Cancelled: autopilot/);
867
+ assert.equal(await readFile(foreignStatePath, 'utf-8'), foreignState);
868
+ }
869
+ finally {
870
+ await rm(wd, { recursive: true, force: true });
871
+ await rm(runsRoot, { recursive: true, force: true });
872
+ }
873
+ });
874
+ it('keeps stale registry-only run state read-only during root cancellation', async () => {
875
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-stale-registry-'));
876
+ const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-stale-runs-'));
877
+ try {
878
+ const sessionId = 'sess-stale-registry';
879
+ const runDir = join(runsRoot, 'run-stale-registry');
880
+ const runStateDir = join(runDir, '.omx', 'state');
881
+ const statePath = join(runStateDir, 'sessions', sessionId, 'autopilot-state.json');
882
+ const state = JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'deep-interview' }, null, 2);
883
+ await mkdir(dirname(statePath), { recursive: true });
884
+ await mkdir(join(wd, '.omx', 'state'), { recursive: true });
885
+ await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
886
+ await writeFile(statePath, state);
887
+ await writeFile(join(runsRoot, 'registry.jsonl'), `${JSON.stringify({ source_cwd: wd, run_dir: runDir })}\n`);
888
+ const cancelResult = runOmxWithEnv(wd, { OMX_RUNS_DIR: runsRoot }, 'cancel');
889
+ assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
890
+ assert.match(cancelResult.stdout, /No active modes to cancel\./);
891
+ assert.equal(await readFile(statePath, 'utf-8'), state);
892
+ }
893
+ finally {
894
+ await rm(wd, { recursive: true, force: true });
895
+ await rm(runsRoot, { recursive: true, force: true });
896
+ }
897
+ });
898
+ it('rejects a live run record whose run directory escapes through a symlink', async () => {
899
+ if (process.platform === 'win32')
900
+ return;
901
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-symlink-owner-'));
902
+ const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-symlink-runs-'));
903
+ const outsideRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-symlink-outside-'));
904
+ try {
905
+ const sessionId = 'sess-symlink-escape';
906
+ const tmuxSessionName = 'omx-symlink-run';
907
+ const linkRunDir = join(runsRoot, 'linked-run');
908
+ const outsideStateDir = join(outsideRoot, '.omx', 'state');
909
+ const statePath = join(outsideStateDir, 'sessions', sessionId, 'autopilot-state.json');
910
+ const state = JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'deep-interview' }, null, 2);
911
+ const fakeBin = await createFakeTmuxBin(wd);
912
+ await mkdir(dirname(statePath), { recursive: true });
913
+ await mkdir(join(wd, '.omx', 'state'), { recursive: true });
914
+ await writeFile(join(outsideStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: outsideStateDir }));
915
+ await writeFile(statePath, state);
916
+ await symlink(outsideRoot, linkRunDir, 'dir');
917
+ await writeActiveRunRecord({
918
+ runsRoot,
919
+ contextKey: 'symlink-run-context',
920
+ sourceCwd: wd,
921
+ runDir: linkRunDir,
922
+ sessionId,
923
+ tmuxSessionName,
924
+ });
925
+ const cancelResult = runOmxWithEnv(wd, {
926
+ OMX_RUNS_DIR: runsRoot,
927
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
928
+ FAKE_OMX_SESSION_ID: sessionId,
929
+ FAKE_TMUX_SESSION_NAME: tmuxSessionName,
930
+ }, 'cancel');
931
+ assert.notEqual(cancelResult.status, 0);
932
+ assert.match(cancelResult.stderr, /detached run authority is invalid/);
933
+ assert.equal(await readFile(statePath, 'utf-8'), state);
934
+ }
935
+ finally {
936
+ await rm(wd, { recursive: true, force: true });
937
+ await rm(runsRoot, { recursive: true, force: true });
938
+ await rm(outsideRoot, { recursive: true, force: true });
939
+ }
940
+ });
941
+ it('rejects nested session-directory and state-file symlink escapes', async () => {
942
+ if (process.platform === 'win32')
943
+ return;
944
+ for (const kind of ['session-dir', 'state-file']) {
945
+ const wd = await mkdtemp(join(tmpdir(), `omx-cli-nested-${kind}-owner-`));
946
+ const runsRoot = await mkdtemp(join(tmpdir(), `omx-cli-nested-${kind}-runs-`));
947
+ const outsideRoot = await mkdtemp(join(tmpdir(), `omx-cli-nested-${kind}-outside-`));
948
+ try {
949
+ const sessionId = `sess-nested-${kind}`;
950
+ const tmuxSessionName = `omx-nested-${kind}`;
951
+ const runDir = join(runsRoot, `run-nested-${kind}`);
952
+ const stateDir = join(runDir, '.omx', 'state');
953
+ const sessionDir = join(stateDir, 'sessions', sessionId);
954
+ const outsideStatePath = join(outsideRoot, 'autopilot-state.json');
955
+ const state = JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'executing' }, null, 2);
956
+ const fakeBin = await createFakeTmuxBin(wd);
957
+ await mkdir(join(stateDir, 'sessions'), { recursive: true });
958
+ await mkdir(join(wd, '.omx', 'state'), { recursive: true });
959
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
960
+ await writeFile(outsideStatePath, state);
961
+ if (kind === 'session-dir') {
962
+ await symlink(outsideRoot, sessionDir, 'dir');
963
+ }
964
+ else {
965
+ await mkdir(sessionDir, { recursive: true });
966
+ await symlink(outsideStatePath, join(sessionDir, 'autopilot-state.json'));
967
+ }
968
+ await writeActiveRunRecord({ runsRoot, contextKey: `nested-${kind}`, sourceCwd: wd, runDir, sessionId, tmuxSessionName });
969
+ const cancelResult = runOmxWithEnv(wd, {
970
+ OMX_RUNS_DIR: runsRoot,
971
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
972
+ FAKE_OMX_SESSION_ID: sessionId,
973
+ FAKE_TMUX_SESSION_NAME: tmuxSessionName,
974
+ }, 'cancel');
975
+ assert.notEqual(cancelResult.status, 0);
976
+ assert.match(cancelResult.stderr, /detached run (?:state )?authority is invalid/);
977
+ assert.equal(await readFile(outsideStatePath, 'utf-8'), state);
978
+ }
979
+ finally {
980
+ await rm(wd, { recursive: true, force: true });
981
+ await rm(runsRoot, { recursive: true, force: true });
982
+ await rm(outsideRoot, { recursive: true, force: true });
983
+ }
984
+ }
985
+ });
986
+ it('revalidates frozen tmux incarnation immediately before mutation', async () => {
987
+ if (process.platform === 'win32')
988
+ return;
989
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-toctou-owner-'));
990
+ const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-toctou-runs-'));
991
+ try {
992
+ const sessionId = 'sess-run-toctou';
993
+ const tmuxSessionName = 'omx-run-toctou';
994
+ const runDir = join(runsRoot, 'run-toctou');
995
+ const stateDir = join(runDir, '.omx', 'state');
996
+ const statePath = join(stateDir, 'sessions', sessionId, 'autopilot-state.json');
997
+ const state = JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'executing' }, null, 2);
998
+ const counterPath = join(wd, 'tmux-count');
999
+ const fakeBin = await createFakeTmuxBin(wd);
1000
+ await mkdir(dirname(statePath), { recursive: true });
1001
+ await mkdir(join(wd, '.omx', 'state'), { recursive: true });
1002
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
1003
+ await writeFile(statePath, state);
1004
+ await writeActiveRunRecord({ runsRoot, contextKey: 'toctou-run', sourceCwd: wd, runDir, sessionId, tmuxSessionName });
1005
+ const cancelResult = runOmxWithEnv(wd, {
1006
+ OMX_RUNS_DIR: runsRoot,
1007
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
1008
+ FAKE_OMX_SESSION_ID: sessionId,
1009
+ FAKE_TMUX_SESSION_NAME: tmuxSessionName,
1010
+ FAKE_TMUX_COUNTER_FILE: counterPath,
1011
+ FAKE_TMUX_REPLACE_AFTER_FIRST: '1',
1012
+ }, 'cancel');
1013
+ assert.notEqual(cancelResult.status, 0);
1014
+ assert.match(cancelResult.stderr, /detached run authority changed/);
1015
+ assert.equal(await readFile(statePath, 'utf-8'), state);
1016
+ }
1017
+ finally {
1018
+ await rm(wd, { recursive: true, force: true });
1019
+ await rm(runsRoot, { recursive: true, force: true });
1020
+ }
1021
+ });
1022
+ it('denies the entire run transaction when one candidate state is malformed', async () => {
1023
+ if (process.platform === 'win32')
1024
+ return;
1025
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-malformed-owner-'));
1026
+ const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-malformed-runs-'));
1027
+ try {
1028
+ const sessionId = 'sess-run-malformed';
1029
+ const tmuxSessionName = 'omx-run-malformed';
1030
+ const runDir = join(runsRoot, 'run-malformed');
1031
+ const stateDir = join(runDir, '.omx', 'state');
1032
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1033
+ const validPath = join(sessionDir, 'ralplan-state.json');
1034
+ const malformedPath = join(sessionDir, 'autopilot-state.json');
1035
+ const validState = JSON.stringify({ active: true, mode: 'ralplan', current_phase: 'executing' }, null, 2);
1036
+ const malformedState = '{"active":true';
1037
+ const fakeBin = await createFakeTmuxBin(wd);
1038
+ await mkdir(sessionDir, { recursive: true });
1039
+ await mkdir(join(wd, '.omx', 'state'), { recursive: true });
1040
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
1041
+ await writeFile(validPath, validState);
1042
+ await writeFile(malformedPath, malformedState);
1043
+ await writeActiveRunRecord({ runsRoot, contextKey: 'malformed-run', sourceCwd: wd, runDir, sessionId, tmuxSessionName });
1044
+ const cancelResult = runOmxWithEnv(wd, {
1045
+ OMX_RUNS_DIR: runsRoot,
1046
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
1047
+ FAKE_OMX_SESSION_ID: sessionId,
1048
+ FAKE_TMUX_SESSION_NAME: tmuxSessionName,
1049
+ }, 'cancel');
1050
+ assert.notEqual(cancelResult.status, 0);
1051
+ assert.match(cancelResult.stderr, /Refusing partial cancellation/);
1052
+ assert.equal(await readFile(validPath, 'utf-8'), validState);
1053
+ assert.equal(await readFile(malformedPath, 'utf-8'), malformedState);
1054
+ }
1055
+ finally {
1056
+ await rm(wd, { recursive: true, force: true });
1057
+ await rm(runsRoot, { recursive: true, force: true });
1058
+ }
1059
+ });
1060
+ it('uses the authorized run session for force native-stop cleanup', async () => {
1061
+ if (process.platform === 'win32')
1062
+ return;
1063
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-force-owner-'));
1064
+ const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-force-runs-'));
1065
+ try {
1066
+ const sessionId = 'sess-run-force';
1067
+ const tmuxSessionName = 'omx-run-force';
1068
+ const runDir = join(runsRoot, 'run-force');
1069
+ const stateDir = join(runDir, '.omx', 'state');
1070
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1071
+ const modePath = join(sessionDir, 'autopilot-state.json');
1072
+ const stopPath = join(sessionDir, 'native-stop-state.json');
1073
+ const fakeBin = await createFakeTmuxBin(wd);
1074
+ await mkdir(sessionDir, { recursive: true });
1075
+ await mkdir(join(wd, '.omx', 'state'), { recursive: true });
1076
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
1077
+ await writeFile(modePath, JSON.stringify({ active: true, mode: 'autopilot', current_phase: 'executing' }));
1078
+ await writeFile(stopPath, JSON.stringify({ sessions: { [sessionId]: { pending: true }, foreign: { pending: true } } }, null, 2));
1079
+ await writeActiveRunRecord({ runsRoot, contextKey: 'force-run', sourceCwd: wd, runDir, sessionId, tmuxSessionName });
1080
+ const cancelResult = runOmxWithEnv(wd, {
1081
+ OMX_RUNS_DIR: runsRoot,
1082
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
1083
+ FAKE_OMX_SESSION_ID: sessionId,
1084
+ FAKE_TMUX_SESSION_NAME: tmuxSessionName,
1085
+ }, 'cancel', '--force');
1086
+ assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
1087
+ assert.match(cancelResult.stdout, /Cancelled: autopilot/);
1088
+ const stopState = JSON.parse(await readFile(stopPath, 'utf-8'));
1089
+ assert.deepEqual(stopState.sessions, { foreign: { pending: true } });
1090
+ }
1091
+ finally {
1092
+ await rm(wd, { recursive: true, force: true });
1093
+ await rm(runsRoot, { recursive: true, force: true });
1094
+ }
1095
+ });
1096
+ it('fails closed when multiple live run records match the same root', async () => {
1097
+ if (process.platform === 'win32')
1098
+ return;
1099
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-duplicate-owner-'));
1100
+ const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-duplicate-runs-'));
1101
+ try {
1102
+ const sessionId = 'sess-duplicate-live';
1103
+ const tmuxSessionName = 'omx-duplicate-run';
1104
+ const fakeBin = await createFakeTmuxBin(wd);
1105
+ const states = [];
1106
+ await mkdir(join(wd, '.omx', 'state'), { recursive: true });
1107
+ for (const [index, mode] of ['autopilot', 'ralplan'].entries()) {
1108
+ const runDir = join(runsRoot, `run-duplicate-${index}`);
1109
+ const runStateDir = join(runDir, '.omx', 'state');
1110
+ const statePath = join(runStateDir, 'sessions', sessionId, `${mode}-state.json`);
1111
+ const content = JSON.stringify({ active: true, mode, current_phase: 'executing' }, null, 2);
1112
+ await mkdir(dirname(statePath), { recursive: true });
1113
+ await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: runStateDir }));
1114
+ await writeFile(statePath, content);
1115
+ await writeActiveRunRecord({
1116
+ runsRoot,
1117
+ contextKey: `duplicate-run-context-${index}`,
1118
+ sourceCwd: wd,
1119
+ runDir,
1120
+ sessionId,
1121
+ tmuxSessionName,
1122
+ });
1123
+ states.push({ path: statePath, content });
1124
+ }
1125
+ const cancelResult = runOmxWithEnv(wd, {
1126
+ OMX_RUNS_DIR: runsRoot,
1127
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
1128
+ FAKE_OMX_SESSION_ID: sessionId,
1129
+ FAKE_TMUX_SESSION_NAME: tmuxSessionName,
1130
+ }, 'cancel');
1131
+ assert.notEqual(cancelResult.status, 0);
1132
+ assert.match(cancelResult.stderr, /No active modes|multiple|authority/i);
1133
+ for (const state of states) {
1134
+ assert.equal(await readFile(state.path, 'utf-8'), state.content);
1135
+ }
1136
+ }
1137
+ finally {
1138
+ await rm(wd, { recursive: true, force: true });
1139
+ await rm(runsRoot, { recursive: true, force: true });
1140
+ }
1141
+ });
109
1142
  it('cancels hook-visible run-dir session state when worktree state list-active is empty', async () => {
110
1143
  const wd = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-cancel-worktree-'));
111
1144
  const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-cancel-runs-'));
112
1145
  try {
1146
+ if (process.platform === 'win32')
1147
+ return;
113
1148
  const sessionId = 'sess-run-dir-cancel';
1149
+ const tmuxSessionName = 'omx-live-run';
114
1150
  const runDir = join(runsRoot, 'run-20260610121751-b6c4');
115
1151
  const runStateDir = join(runDir, '.omx', 'state');
116
1152
  const runSessionDir = join(runStateDir, 'sessions', sessionId);
1153
+ const fakeBin = await createFakeTmuxBin(wd);
1154
+ const rootAutopilotPath = join(runStateDir, 'autopilot-state.json');
1155
+ const rootAutopilotState = JSON.stringify({
1156
+ active: true,
1157
+ mode: 'autopilot',
1158
+ current_phase: 'stale-root-copy',
1159
+ }, null, 2);
117
1160
  await mkdir(runSessionDir, { recursive: true });
118
1161
  await mkdir(join(wd, '.omx', 'state'), { recursive: true });
119
- await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
1162
+ await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: runStateDir }, null, 2));
1163
+ await writeFile(rootAutopilotPath, rootAutopilotState);
120
1164
  await writeFile(join(runSessionDir, 'autopilot-state.json'), JSON.stringify({
121
1165
  active: true,
122
1166
  mode: 'autopilot',
@@ -130,18 +1174,23 @@ describe('CLI session-scoped state parity', () => {
130
1174
  session_id: sessionId,
131
1175
  active_skills: [{ skill: 'autopilot', phase: 'deep-interview', active: true, session_id: sessionId }],
132
1176
  }, null, 2));
133
- await writeFile(join(runsRoot, 'registry.jsonl'), `${JSON.stringify({
134
- launcher: 'omx --madmax',
135
- created_at: '2026-06-10T12:17:51.000Z',
136
- cwd: runDir,
137
- source_cwd: wd,
138
- argv: ['codex'],
139
- run_dir: runDir,
140
- })}\n`);
1177
+ await writeActiveRunRecord({
1178
+ runsRoot,
1179
+ contextKey: 'live-run-context',
1180
+ sourceCwd: wd,
1181
+ runDir,
1182
+ sessionId,
1183
+ tmuxSessionName,
1184
+ });
141
1185
  const listResult = runOmx(wd, 'state', 'list-active', '--json');
142
1186
  assert.equal(listResult.status, 0, listResult.stderr || listResult.stdout);
143
1187
  assert.deepEqual(JSON.parse(listResult.stdout), { active_modes: [] });
144
- const cancelResult = runOmxWithEnv(wd, { OMX_RUNS_DIR: runsRoot }, 'cancel');
1188
+ const cancelResult = runOmxWithEnv(wd, {
1189
+ OMX_RUNS_DIR: runsRoot,
1190
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
1191
+ FAKE_OMX_SESSION_ID: sessionId,
1192
+ FAKE_TMUX_SESSION_NAME: tmuxSessionName,
1193
+ }, 'cancel');
145
1194
  assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
146
1195
  assert.match(cancelResult.stdout, /Cancelled: autopilot/);
147
1196
  assert.doesNotMatch(cancelResult.stdout, /No active modes to cancel/);
@@ -149,6 +1198,7 @@ describe('CLI session-scoped state parity', () => {
149
1198
  assert.equal(autopilot.active, false);
150
1199
  assert.equal(autopilot.current_phase, 'cancelled');
151
1200
  assert.ok(typeof autopilot.completed_at === 'string' && autopilot.completed_at.length > 0);
1201
+ assert.equal(await readFile(rootAutopilotPath, 'utf-8'), rootAutopilotState);
152
1202
  const skillActive = JSON.parse(await readFile(join(runSessionDir, 'skill-active-state.json'), 'utf-8'));
153
1203
  assert.equal(skillActive.active, false);
154
1204
  assert.equal(skillActive.current_phase, 'cancelled');
@@ -173,7 +1223,7 @@ describe('CLI session-scoped state parity', () => {
173
1223
  const runSessionDir = join(runStateDir, 'sessions', sessionId);
174
1224
  await mkdir(runSessionDir, { recursive: true });
175
1225
  await mkdir(join(wd, '.omx', 'state'), { recursive: true });
176
- await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
1226
+ await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: runStateDir }, null, 2));
177
1227
  await writeFile(join(runSessionDir, 'autopilot-state.json'), JSON.stringify({
178
1228
  active: true,
179
1229
  mode: 'autopilot',
@@ -205,35 +1255,39 @@ describe('CLI session-scoped state parity', () => {
205
1255
  const worktree = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-worktree-alias-wt-'));
206
1256
  const runsRoot = await mkdtemp(join(tmpdir(), 'omx-cli-run-dir-worktree-alias-runs-'));
207
1257
  try {
1258
+ if (process.platform === 'win32')
1259
+ return;
208
1260
  const sessionId = 'sess-run-dir-worktree-alias';
209
1261
  const runDir = join(runsRoot, 'run-20260610121751-a11a');
210
1262
  const runStateDir = join(runDir, '.omx', 'state');
211
1263
  const runSessionDir = join(runStateDir, 'sessions', sessionId);
1264
+ const fakeBin = await createFakeTmuxBin(worktree);
212
1265
  await mkdir(runSessionDir, { recursive: true });
213
1266
  await mkdir(join(worktree, '.omx', 'state'), { recursive: true });
214
- await mkdir(join(runsRoot, 'active-detached'), { recursive: true });
215
- await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
1267
+ await writeFile(join(runStateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: runStateDir }, null, 2));
216
1268
  await writeFile(join(runSessionDir, 'autopilot-state.json'), JSON.stringify({
217
1269
  active: true,
218
1270
  mode: 'autopilot',
219
1271
  current_phase: 'deep-interview',
220
1272
  }, null, 2));
221
- await writeFile(join(runsRoot, 'active-detached', 'ctx-worktree-alias.json'), `${JSON.stringify({
222
- version: 1,
223
- context_key: 'ctx-worktree-alias',
224
- created_at: '2026-06-10T12:17:51.000Z',
225
- source_cwd: wd,
226
- worktree_cwd: worktree,
227
- argv: ['--madmax', '--worktree', '--tmux'],
228
- run_dir: runDir,
229
- tmux_session_name: 'omx-detached',
230
- session_id: sessionId,
231
- tmux_pane_id: '%42',
232
- })}\n`);
1273
+ await writeActiveRunRecord({
1274
+ runsRoot,
1275
+ contextKey: 'ctx-worktree-alias',
1276
+ sourceCwd: wd,
1277
+ worktreeCwd: worktree,
1278
+ runDir,
1279
+ sessionId,
1280
+ tmuxSessionName: 'omx-detached',
1281
+ });
233
1282
  const statusResult = runOmxWithEnv(worktree, { OMX_RUNS_DIR: runsRoot }, 'status');
234
1283
  assert.equal(statusResult.status, 0, statusResult.stderr || statusResult.stdout);
235
1284
  assert.match(statusResult.stdout, /autopilot: ACTIVE \(phase: deep-interview\)/);
236
- const cancelResult = runOmxWithEnv(worktree, { OMX_RUNS_DIR: runsRoot }, 'cancel');
1285
+ const cancelResult = runOmxWithEnv(worktree, {
1286
+ OMX_RUNS_DIR: runsRoot,
1287
+ PATH: `${fakeBin}:${process.env.PATH ?? ''}`,
1288
+ FAKE_OMX_SESSION_ID: sessionId,
1289
+ FAKE_TMUX_SESSION_NAME: 'omx-detached',
1290
+ }, 'cancel');
237
1291
  assert.equal(cancelResult.status, 0, cancelResult.stderr || cancelResult.stdout);
238
1292
  assert.match(cancelResult.stdout, /Cancelled: autopilot/);
239
1293
  const autopilot = JSON.parse(await readFile(join(runSessionDir, 'autopilot-state.json'), 'utf-8'));
@@ -251,7 +1305,7 @@ describe('CLI session-scoped state parity', () => {
251
1305
  try {
252
1306
  const stateDir = join(wd, '.omx', 'state');
253
1307
  await mkdir(stateDir, { recursive: true });
254
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: 'sess-stale-autopilot' }, null, 2));
1308
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: 'sess-stale-autopilot', cwd: wd, state_root: stateDir }, null, 2));
255
1309
  const currentAutopilotPath = join(stateDir, 'current-autopilot.json');
256
1310
  const currentAutopilot = {
257
1311
  active: true,
@@ -281,7 +1335,7 @@ describe('CLI session-scoped state parity', () => {
281
1335
  const sessionId = 'sess-stale-autopilot-inactive';
282
1336
  const sessionDir = join(stateDir, 'sessions', sessionId);
283
1337
  await mkdir(sessionDir, { recursive: true });
284
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
1338
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }, null, 2));
285
1339
  const currentAutopilotPath = join(stateDir, 'current-autopilot.json');
286
1340
  const currentAutopilot = {
287
1341
  active: true,
@@ -313,7 +1367,7 @@ describe('CLI session-scoped state parity', () => {
313
1367
  const sessionId = 'sess-active-autopilot';
314
1368
  const sessionDir = join(stateDir, 'sessions', sessionId);
315
1369
  await mkdir(sessionDir, { recursive: true });
316
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
1370
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }, null, 2));
317
1371
  await writeFile(join(stateDir, 'current-autopilot.json'), JSON.stringify({
318
1372
  active: true,
319
1373
  current_phase: 'complete',
@@ -389,7 +1443,7 @@ describe('CLI session-scoped state parity', () => {
389
1443
  const ultragoalDir = join(wd, '.omx', 'ultragoal');
390
1444
  await mkdir(sessionDir, { recursive: true });
391
1445
  await mkdir(ultragoalDir, { recursive: true });
392
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
1446
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
393
1447
  await writeFile(join(sessionDir, 'ultragoal-state.json'), JSON.stringify({
394
1448
  active: true,
395
1449
  mode: 'ultragoal',
@@ -425,7 +1479,7 @@ describe('CLI session-scoped state parity', () => {
425
1479
  const sessionId = 'sess-link';
426
1480
  const sessionDir = join(stateDir, 'sessions', sessionId);
427
1481
  await mkdir(sessionDir, { recursive: true });
428
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }));
1482
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
429
1483
  await writeFile(join(sessionDir, 'ralph-state.json'), JSON.stringify({
430
1484
  active: true,
431
1485
  iteration: 2,
@@ -454,6 +1508,27 @@ describe('CLI session-scoped state parity', () => {
454
1508
  await rm(wd, { recursive: true, force: true });
455
1509
  }
456
1510
  });
1511
+ it('cancels linked ecomode before Ralph', async () => {
1512
+ const wd = await mkdtemp(join(tmpdir(), 'omx-cli-ralph-ecomode-link-'));
1513
+ try {
1514
+ const stateDir = join(wd, '.omx', 'state');
1515
+ const sessionId = 'sess-ecomode-link';
1516
+ const sessionDir = join(stateDir, 'sessions', sessionId);
1517
+ await mkdir(sessionDir, { recursive: true });
1518
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }));
1519
+ await writeFile(join(sessionDir, 'ralph-state.json'), JSON.stringify({ active: true, mode: 'ralph', session_id: sessionId, current_phase: 'executing', linked_ecomode: true }));
1520
+ await writeFile(join(sessionDir, 'ecomode-state.json'), JSON.stringify({ active: true, mode: 'ecomode', session_id: sessionId, current_phase: 'executing' }));
1521
+ const result = runOmx(wd, 'cancel');
1522
+ assert.equal(result.status, 0, result.stderr || result.stdout);
1523
+ assert.match(result.stdout, /Cancelled: ecomode/);
1524
+ assert.match(result.stdout, /Cancelled: ralph/);
1525
+ assert.equal(JSON.parse(await readFile(join(sessionDir, 'ecomode-state.json'), 'utf-8')).active, false);
1526
+ assert.equal(JSON.parse(await readFile(join(sessionDir, 'ralph-state.json'), 'utf-8')).active, false);
1527
+ }
1528
+ finally {
1529
+ await rm(wd, { recursive: true, force: true });
1530
+ }
1531
+ });
457
1532
  it('does not mutate unrelated sessions when cancelling current session mode', async () => {
458
1533
  const wd = await mkdtemp(join(tmpdir(), 'omx-cli-cross-session-'));
459
1534
  try {
@@ -462,7 +1537,7 @@ describe('CLI session-scoped state parity', () => {
462
1537
  const sessionB = join(stateDir, 'sessions', 'sessB');
463
1538
  await mkdir(sessionA, { recursive: true });
464
1539
  await mkdir(sessionB, { recursive: true });
465
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: 'sessA' }));
1540
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: 'sessA', cwd: wd, state_root: stateDir }));
466
1541
  await writeFile(join(sessionA, 'ralph-state.json'), JSON.stringify({
467
1542
  active: true,
468
1543
  current_phase: 'executing',
@@ -494,7 +1569,7 @@ describe('CLI session-scoped state parity', () => {
494
1569
  const sessionId = 'sess-stale-autopilot-mirror';
495
1570
  const sessionDir = join(stateDir, 'sessions', sessionId);
496
1571
  await mkdir(sessionDir, { recursive: true });
497
- await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId }, null, 2));
1572
+ await writeFile(join(stateDir, 'session.json'), JSON.stringify({ session_id: sessionId, cwd: wd, state_root: stateDir }, null, 2));
498
1573
  await writeFile(join(stateDir, 'autopilot-state.json'), JSON.stringify({
499
1574
  active: false,
500
1575
  mode: 'autopilot',