oh-my-codex 0.20.2 → 0.20.3

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 (367) hide show
  1. package/Cargo.lock +7 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +26 -0
  4. package/crates/omx-runtime/Cargo.toml +1 -0
  5. package/crates/omx-runtime/src/main.rs +25 -2
  6. package/crates/omx-runtime/tests/execution.rs +219 -0
  7. package/crates/omx-runtime-core/src/dispatch.rs +96 -10
  8. package/crates/omx-runtime-core/src/engine.rs +839 -29
  9. package/crates/omx-runtime-core/src/lib.rs +8 -0
  10. package/dist/agents/__tests__/definitions.test.js +81 -0
  11. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  12. package/dist/agents/__tests__/native-config.test.js +25 -0
  13. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  14. package/dist/agents/native-config.d.ts +2 -1
  15. package/dist/agents/native-config.d.ts.map +1 -1
  16. package/dist/agents/native-config.js.map +1 -1
  17. package/dist/auth/hotswap.d.ts +6 -0
  18. package/dist/auth/hotswap.d.ts.map +1 -1
  19. package/dist/auth/hotswap.js +85 -15
  20. package/dist/auth/hotswap.js.map +1 -1
  21. package/dist/autopilot/__tests__/ralplan-gate.test.js +1 -1
  22. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  23. package/dist/cli/__tests__/auth.test.js +60 -6
  24. package/dist/cli/__tests__/auth.test.js.map +1 -1
  25. package/dist/cli/__tests__/codex-plugin-layout.test.js +80 -0
  26. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  27. package/dist/cli/__tests__/doctor-warning-copy.test.js +103 -1
  28. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  29. package/dist/cli/__tests__/exec.test.js +136 -0
  30. package/dist/cli/__tests__/exec.test.js.map +1 -1
  31. package/dist/cli/__tests__/index.test.js +258 -4
  32. package/dist/cli/__tests__/index.test.js.map +1 -1
  33. package/dist/cli/__tests__/launch-fallback.test.js +287 -1
  34. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  35. package/dist/cli/__tests__/mcp-parity.test.js +4 -1
  36. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  37. package/dist/cli/__tests__/native-hook-claim-journal.test.js +84 -6
  38. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -1
  39. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +3 -3
  40. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  41. package/dist/cli/__tests__/ralph.test.js +3 -3
  42. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  43. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +56 -171
  44. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -1
  45. package/dist/cli/__tests__/ralplan.test.js +37 -111
  46. package/dist/cli/__tests__/ralplan.test.js.map +1 -1
  47. package/dist/cli/__tests__/resume.test.js +11 -10
  48. package/dist/cli/__tests__/resume.test.js.map +1 -1
  49. package/dist/cli/__tests__/session-search-help.test.js +25 -1
  50. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  51. package/dist/cli/__tests__/setup-install-mode.test.js +42 -4
  52. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  53. package/dist/cli/__tests__/team.test.js +105 -7
  54. package/dist/cli/__tests__/team.test.js.map +1 -1
  55. package/dist/cli/__tests__/uninstall.test.js +46 -0
  56. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  57. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +2 -1
  58. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  59. package/dist/cli/doctor.d.ts +3 -0
  60. package/dist/cli/doctor.d.ts.map +1 -1
  61. package/dist/cli/doctor.js +15 -2
  62. package/dist/cli/doctor.js.map +1 -1
  63. package/dist/cli/index.d.ts +4 -1
  64. package/dist/cli/index.d.ts.map +1 -1
  65. package/dist/cli/index.js +138 -38
  66. package/dist/cli/index.js.map +1 -1
  67. package/dist/cli/mcp-parity.js +2 -2
  68. package/dist/cli/mcp-parity.js.map +1 -1
  69. package/dist/cli/native-hook-claim-journal.d.ts +16 -5
  70. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -1
  71. package/dist/cli/native-hook-claim-journal.js +42 -29
  72. package/dist/cli/native-hook-claim-journal.js.map +1 -1
  73. package/dist/cli/ralph.js +2 -2
  74. package/dist/cli/ralph.js.map +1 -1
  75. package/dist/cli/ralplan.d.ts +4 -8
  76. package/dist/cli/ralplan.d.ts.map +1 -1
  77. package/dist/cli/ralplan.js +19 -112
  78. package/dist/cli/ralplan.js.map +1 -1
  79. package/dist/cli/session-search.d.ts +5 -0
  80. package/dist/cli/session-search.d.ts.map +1 -1
  81. package/dist/cli/session-search.js +87 -0
  82. package/dist/cli/session-search.js.map +1 -1
  83. package/dist/cli/setup.d.ts +5 -0
  84. package/dist/cli/setup.d.ts.map +1 -1
  85. package/dist/cli/setup.js +80 -31
  86. package/dist/cli/setup.js.map +1 -1
  87. package/dist/cli/uninstall.d.ts +9 -0
  88. package/dist/cli/uninstall.d.ts.map +1 -1
  89. package/dist/cli/uninstall.js +61 -19
  90. package/dist/cli/uninstall.js.map +1 -1
  91. package/dist/compat/__tests__/rust-runtime-compat.test.js +1 -1
  92. package/dist/config/__tests__/generator-idempotent.test.js +330 -1
  93. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  94. package/dist/config/__tests__/generator-notify.test.js +2 -2
  95. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  96. package/dist/config/__tests__/models.test.js +67 -3
  97. package/dist/config/__tests__/models.test.js.map +1 -1
  98. package/dist/config/generator.d.ts +4 -20
  99. package/dist/config/generator.d.ts.map +1 -1
  100. package/dist/config/generator.js +499 -119
  101. package/dist/config/generator.js.map +1 -1
  102. package/dist/config/models.d.ts +12 -2
  103. package/dist/config/models.d.ts.map +1 -1
  104. package/dist/config/models.js +15 -1
  105. package/dist/config/models.js.map +1 -1
  106. package/dist/hooks/__tests__/agents-overlay.test.js +2 -2
  107. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  108. package/dist/hooks/__tests__/keyword-detector.test.js +4 -4
  109. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  110. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +238 -543
  111. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  112. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +38 -4
  113. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  114. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +96 -9
  115. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  116. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +531 -12
  117. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  118. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +155 -23
  119. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  120. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +482 -32
  121. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  122. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +68 -1
  123. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  124. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +7 -1
  125. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  126. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +43 -4
  127. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  128. package/dist/hooks/__tests__/session.test.js +348 -2
  129. package/dist/hooks/__tests__/session.test.js.map +1 -1
  130. package/dist/hooks/agents-overlay.js +1 -1
  131. package/dist/hooks/session.d.ts +26 -2
  132. package/dist/hooks/session.d.ts.map +1 -1
  133. package/dist/hooks/session.js +169 -13
  134. package/dist/hooks/session.js.map +1 -1
  135. package/dist/leader/__tests__/contract.test.js +57 -7
  136. package/dist/leader/__tests__/contract.test.js.map +1 -1
  137. package/dist/leader/contract.d.ts +16 -0
  138. package/dist/leader/contract.d.ts.map +1 -1
  139. package/dist/leader/contract.js +79 -4
  140. package/dist/leader/contract.js.map +1 -1
  141. package/dist/mcp/__tests__/code-intel-server.test.js +31 -3
  142. package/dist/mcp/__tests__/code-intel-server.test.js.map +1 -1
  143. package/dist/mcp/__tests__/wiki-server.test.js +18 -0
  144. package/dist/mcp/__tests__/wiki-server.test.js.map +1 -1
  145. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  146. package/dist/mcp/code-intel-server.js +13 -3
  147. package/dist/mcp/code-intel-server.js.map +1 -1
  148. package/dist/mcp/wiki-server.d.ts +11 -0
  149. package/dist/mcp/wiki-server.d.ts.map +1 -1
  150. package/dist/mcp/wiki-server.js +9 -2
  151. package/dist/mcp/wiki-server.js.map +1 -1
  152. package/dist/modes/__tests__/base-tmux-pane.test.js +4 -4
  153. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  154. package/dist/notifications/temp-contract.d.ts.map +1 -1
  155. package/dist/notifications/temp-contract.js +4 -0
  156. package/dist/notifications/temp-contract.js.map +1 -1
  157. package/dist/pipeline/__tests__/orchestrator.test.js +4 -4
  158. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  159. package/dist/pipeline/__tests__/stages.test.js +8 -8
  160. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  161. package/dist/ralplan/__tests__/consensus-gate.test.js +134 -10
  162. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  163. package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts +2 -0
  164. package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts.map +1 -0
  165. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +46 -0
  166. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -0
  167. package/dist/ralplan/__tests__/runtime.test.js +8 -4
  168. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  169. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  170. package/dist/ralplan/consensus-gate.js +58 -50
  171. package/dist/ralplan/consensus-gate.js.map +1 -1
  172. package/dist/ralplan/documented-leader-preflight.d.ts +34 -0
  173. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -0
  174. package/dist/ralplan/documented-leader-preflight.js +58 -0
  175. package/dist/ralplan/documented-leader-preflight.js.map +1 -0
  176. package/dist/ralplan/runtime.d.ts +1 -0
  177. package/dist/ralplan/runtime.d.ts.map +1 -1
  178. package/dist/ralplan/runtime.js +11 -7
  179. package/dist/ralplan/runtime.js.map +1 -1
  180. package/dist/runtime/__tests__/bridge.test.js +42 -1
  181. package/dist/runtime/__tests__/bridge.test.js.map +1 -1
  182. package/dist/runtime/bridge.d.ts +12 -0
  183. package/dist/runtime/bridge.d.ts.map +1 -1
  184. package/dist/runtime/bridge.js +52 -1
  185. package/dist/runtime/bridge.js.map +1 -1
  186. package/dist/scripts/__tests__/codex-native-hook.test.js +2620 -487
  187. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  188. package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts +2 -0
  189. package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts.map +1 -0
  190. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js +108 -0
  191. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js.map +1 -0
  192. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +46 -173
  193. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -1
  194. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +39 -159
  195. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -1
  196. package/dist/scripts/__tests__/smoke-packed-install.test.js +254 -2
  197. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  198. package/dist/scripts/codex-native-hook.d.ts +4 -1
  199. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  200. package/dist/scripts/codex-native-hook.js +11013 -1153
  201. package/dist/scripts/codex-native-hook.js.map +1 -1
  202. package/dist/scripts/notify-fallback-watcher.js +82 -108
  203. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  204. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  205. package/dist/scripts/notify-hook/auto-nudge.js +74 -3
  206. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  207. package/dist/scripts/notify-hook/team-dispatch.d.ts +9 -0
  208. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  209. package/dist/scripts/notify-hook/team-dispatch.js +234 -79
  210. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  211. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  212. package/dist/scripts/notify-hook/team-leader-nudge.js +39 -13
  213. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  214. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +12 -4
  215. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  216. package/dist/scripts/notify-hook/team-tmux-guard.js +365 -44
  217. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  218. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  219. package/dist/scripts/notify-hook/team-worker-stop.js +19 -18
  220. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  221. package/dist/scripts/notify-hook/team-worker.d.ts +3 -0
  222. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  223. package/dist/scripts/notify-hook/team-worker.js +37 -28
  224. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  225. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  226. package/dist/scripts/notify-hook/tmux-injection.js +28 -2
  227. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  228. package/dist/scripts/notify-hook.js +1 -0
  229. package/dist/scripts/notify-hook.js.map +1 -1
  230. package/dist/scripts/smoke-packed-install.d.ts +48 -3
  231. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  232. package/dist/scripts/smoke-packed-install.js +2796 -13
  233. package/dist/scripts/smoke-packed-install.js.map +1 -1
  234. package/dist/state/__tests__/mode-state-context.test.js +54 -0
  235. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  236. package/dist/state/__tests__/operations.test.js +2 -0
  237. package/dist/state/__tests__/operations.test.js.map +1 -1
  238. package/dist/state/mode-state-context.d.ts +5 -0
  239. package/dist/state/mode-state-context.d.ts.map +1 -1
  240. package/dist/state/mode-state-context.js +59 -0
  241. package/dist/state/mode-state-context.js.map +1 -1
  242. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +24 -245
  243. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -1
  244. package/dist/subagents/tracker.d.ts +1 -82
  245. package/dist/subagents/tracker.d.ts.map +1 -1
  246. package/dist/subagents/tracker.js +3 -275
  247. package/dist/subagents/tracker.js.map +1 -1
  248. package/dist/team/__tests__/api-interop.test.js +91 -1
  249. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  250. package/dist/team/__tests__/delivery-e2e-smoke.test.js +52 -10
  251. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  252. package/dist/team/__tests__/idle-nudge.test.js +45 -15
  253. package/dist/team/__tests__/idle-nudge.test.js.map +1 -1
  254. package/dist/team/__tests__/mcp-comm.test.js +93 -27
  255. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  256. package/dist/team/__tests__/model-contract.test.js +110 -3
  257. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  258. package/dist/team/__tests__/runtime-cli.test.js +9 -0
  259. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  260. package/dist/team/__tests__/runtime.test.js +3204 -335
  261. package/dist/team/__tests__/runtime.test.js.map +1 -1
  262. package/dist/team/__tests__/scaling.test.js +2359 -142
  263. package/dist/team/__tests__/scaling.test.js.map +1 -1
  264. package/dist/team/__tests__/state.test.js +804 -36
  265. package/dist/team/__tests__/state.test.js.map +1 -1
  266. package/dist/team/__tests__/team-ops-contract.test.js +6 -0
  267. package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
  268. package/dist/team/__tests__/tmux-session.test.js +3049 -784
  269. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  270. package/dist/team/__tests__/worker-runtime-identity.test.js +38 -2
  271. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  272. package/dist/team/__tests__/worktree.test.js +6 -0
  273. package/dist/team/__tests__/worktree.test.js.map +1 -1
  274. package/dist/team/api-interop.d.ts.map +1 -1
  275. package/dist/team/api-interop.js +15 -9
  276. package/dist/team/api-interop.js.map +1 -1
  277. package/dist/team/exact-pane.d.ts +23 -0
  278. package/dist/team/exact-pane.d.ts.map +1 -0
  279. package/dist/team/exact-pane.js +133 -0
  280. package/dist/team/exact-pane.js.map +1 -0
  281. package/dist/team/idle-nudge.d.ts +12 -1
  282. package/dist/team/idle-nudge.d.ts.map +1 -1
  283. package/dist/team/idle-nudge.js +41 -14
  284. package/dist/team/idle-nudge.js.map +1 -1
  285. package/dist/team/model-contract.d.ts +2 -5
  286. package/dist/team/model-contract.d.ts.map +1 -1
  287. package/dist/team/model-contract.js +59 -17
  288. package/dist/team/model-contract.js.map +1 -1
  289. package/dist/team/runtime.d.ts +1 -0
  290. package/dist/team/runtime.d.ts.map +1 -1
  291. package/dist/team/runtime.js +1244 -180
  292. package/dist/team/runtime.js.map +1 -1
  293. package/dist/team/scaling.d.ts +13 -1
  294. package/dist/team/scaling.d.ts.map +1 -1
  295. package/dist/team/scaling.js +1430 -384
  296. package/dist/team/scaling.js.map +1 -1
  297. package/dist/team/state/dispatch.d.ts.map +1 -1
  298. package/dist/team/state/dispatch.js +26 -8
  299. package/dist/team/state/dispatch.js.map +1 -1
  300. package/dist/team/state/locks.d.ts.map +1 -1
  301. package/dist/team/state/locks.js +61 -13
  302. package/dist/team/state/locks.js.map +1 -1
  303. package/dist/team/state/tasks.d.ts.map +1 -1
  304. package/dist/team/state/tasks.js +4 -3
  305. package/dist/team/state/tasks.js.map +1 -1
  306. package/dist/team/state.d.ts +67 -1
  307. package/dist/team/state.d.ts.map +1 -1
  308. package/dist/team/state.js +636 -129
  309. package/dist/team/state.js.map +1 -1
  310. package/dist/team/team-ops.d.ts +5 -0
  311. package/dist/team/team-ops.d.ts.map +1 -1
  312. package/dist/team/team-ops.js +4 -0
  313. package/dist/team/team-ops.js.map +1 -1
  314. package/dist/team/tmux-session.d.ts +123 -23
  315. package/dist/team/tmux-session.d.ts.map +1 -1
  316. package/dist/team/tmux-session.js +1250 -291
  317. package/dist/team/tmux-session.js.map +1 -1
  318. package/dist/team/worktree.d.ts.map +1 -1
  319. package/dist/team/worktree.js +4 -0
  320. package/dist/team/worktree.js.map +1 -1
  321. package/dist/utils/__tests__/file-durability.test.d.ts +2 -0
  322. package/dist/utils/__tests__/file-durability.test.d.ts.map +1 -0
  323. package/dist/utils/__tests__/file-durability.test.js +57 -0
  324. package/dist/utils/__tests__/file-durability.test.js.map +1 -0
  325. package/dist/utils/file-durability.d.ts +15 -0
  326. package/dist/utils/file-durability.d.ts.map +1 -0
  327. package/dist/utils/file-durability.js +37 -0
  328. package/dist/utils/file-durability.js.map +1 -0
  329. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +3 -0
  330. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  331. package/dist/wiki/lint.d.ts +4 -1
  332. package/dist/wiki/lint.d.ts.map +1 -1
  333. package/dist/wiki/lint.js +3 -2
  334. package/dist/wiki/lint.js.map +1 -1
  335. package/package.json +1 -1
  336. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  337. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +24 -0
  338. package/plugins/oh-my-codex/skills/ralph/SKILL.md +6 -4
  339. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +3 -2
  340. package/plugins/oh-my-codex/skills/team/SKILL.md +13 -9
  341. package/skills/ralph/SKILL.md +6 -4
  342. package/skills/ralplan/SKILL.md +3 -2
  343. package/skills/team/SKILL.md +13 -9
  344. package/src/scripts/__tests__/codex-native-hook.test.ts +2826 -497
  345. package/src/scripts/__tests__/reasoning-artifact-contract.test.ts +129 -0
  346. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +48 -185
  347. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +44 -177
  348. package/src/scripts/__tests__/smoke-packed-install.test.ts +320 -2
  349. package/src/scripts/codex-native-hook.ts +10715 -1069
  350. package/src/scripts/notify-fallback-watcher.ts +84 -115
  351. package/src/scripts/notify-hook/auto-nudge.ts +71 -3
  352. package/src/scripts/notify-hook/team-dispatch.ts +242 -75
  353. package/src/scripts/notify-hook/team-leader-nudge.ts +40 -18
  354. package/src/scripts/notify-hook/team-tmux-guard.ts +366 -39
  355. package/src/scripts/notify-hook/team-worker-stop.ts +20 -16
  356. package/src/scripts/notify-hook/team-worker.ts +39 -26
  357. package/src/scripts/notify-hook/tmux-injection.ts +28 -2
  358. package/src/scripts/notify-hook.ts +1 -0
  359. package/src/scripts/smoke-packed-install.ts +3196 -14
  360. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts +0 -2
  361. package/dist/subagents/__tests__/adapted-role-binding.test.d.ts.map +0 -1
  362. package/dist/subagents/__tests__/adapted-role-binding.test.js +0 -988
  363. package/dist/subagents/__tests__/adapted-role-binding.test.js.map +0 -1
  364. package/dist/subagents/adapted-role-binding.d.ts +0 -17
  365. package/dist/subagents/adapted-role-binding.d.ts.map +0 -1
  366. package/dist/subagents/adapted-role-binding.js +0 -75
  367. package/dist/subagents/adapted-role-binding.js.map +0 -1
@@ -17,10 +17,18 @@ function isolatedChildEnv(fakeBinDir) {
17
17
  WINDIR: process.env.WINDIR,
18
18
  };
19
19
  }
20
+ function liveExactPaneProof() {
21
+ return `if [[ "\${1:-}" == "list-panes" && "$#" -eq 4 && "\${2:-}" == "-a" && "\${3:-}" == "-F" && "\${4:-}" == "#{pane_id}\t#{pane_dead}\t#{pane_pid}" ]]; then
22
+ printf '%%42\\t0\\t4242\\n'
23
+ exit 0
24
+ fi`;
25
+ }
20
26
  function buildFakeTmux(tmuxLogPath) {
21
27
  const bufferPath = `${tmuxLogPath}.buffer`;
22
28
  return `#!/usr/bin/env bash
23
29
  set -eu
30
+ ${liveExactPaneProof()}
31
+
24
32
  printf '[%s]' "$@" >> "${tmuxLogPath}"
25
33
  printf '\n' >> "${tmuxLogPath}"
26
34
  cmd="$1"
@@ -49,6 +57,9 @@ function runSendPaneInputInChild(params) {
49
57
  tmuxBin: join(params.fakeBinDir, 'tmux'),
50
58
  typePrompt: params.typePrompt,
51
59
  queueFirstSubmit: params.queueFirstSubmit,
60
+ exactPaneId: params.exactPaneId,
61
+ expectedPanePid: params.expectedPanePid,
62
+ expectedPaneOwnerId: params.expectedPaneOwnerId,
52
63
  });
53
64
  const script = `
54
65
  const input = ${payload};
@@ -96,6 +107,7 @@ describe('notify-hook team tmux guard bridge', () => {
96
107
  fakeBinDir,
97
108
  moduleUrl,
98
109
  paneTarget: '%42',
110
+ exactPaneId: '%42',
99
111
  prompt: 'hello bridge',
100
112
  submitKeyPresses: 2,
101
113
  typePrompt: false,
@@ -107,9 +119,10 @@ describe('notify-hook team tmux guard bridge', () => {
107
119
  assert.doesNotMatch(log, /paste-buffer/);
108
120
  assert.doesNotMatch(log, /hello bridge/);
109
121
  const lines = log.trim().split('\n').filter(Boolean);
110
- assert.equal(lines.length, 2);
111
- assert.match(lines[0], /\[send-keys\]\[-t\]\[%42\]\[C-m\]/);
122
+ assert.equal(lines.length, 3);
123
+ assert.match(lines[0], /\[display-message\]\[-p\]\[-t\]\[%42\]\[#\{pane_start_command\}\]/);
112
124
  assert.match(lines[1], /\[send-keys\]\[-t\]\[%42\]\[C-m\]/);
125
+ assert.match(lines[2], /\[send-keys\]\[-t\]\[%42\]\[C-m\]/);
113
126
  }
114
127
  finally {
115
128
  await rm(cwd, { recursive: true, force: true });
@@ -128,6 +141,7 @@ describe('notify-hook team tmux guard bridge', () => {
128
141
  fakeBinDir,
129
142
  moduleUrl,
130
143
  paneTarget: '%42',
144
+ exactPaneId: '%42',
131
145
  prompt: 'Read /tmp/team/mailbox/leader-fixed.json; new msg from worker-1. Review it; decide next step.',
132
146
  submitKeyPresses: 2,
133
147
  typePrompt: true,
@@ -137,16 +151,17 @@ describe('notify-hook team tmux guard bridge', () => {
137
151
  assert.equal(result.error, undefined);
138
152
  assert.match(result.stdout, /"ok":true/);
139
153
  const lines = (await readFile(tmuxLogPath, 'utf-8')).trim().split('\n').filter(Boolean);
140
- assert.equal(lines.length, 8);
141
- assert.match(lines[0], /\[set-buffer\]\[-b\]\[omx-pane-input-/);
142
- assert.match(lines[0], /\[--\]\[Read \/tmp\/team\/mailbox\/leader-fixed\.json/);
143
- assert.match(lines[1], /\[show-buffer\]\[-b\]\[omx-pane-input-/);
144
- assert.match(lines[2], /\[send-keys\]\[-t\]\[%42\]\[C-u\]/);
145
- assert.match(lines[3], /\[paste-buffer\]\[-t\]\[%42\]\[-b\]\[omx-pane-input-.*\]\[-p\]\[-d\]/);
146
- assert.match(lines[4], /\[send-keys\]\[-t\]\[%42\]\[Tab\]/);
147
- assert.match(lines[5], /\[send-keys\]\[-t\]\[%42\]\[C-m\]/);
154
+ assert.equal(lines.length, 9);
155
+ assert.match(lines[0], /\[display-message\]\[-p\]\[-t\]\[%42\]\[#\{pane_start_command\}\]/);
156
+ assert.match(lines[1], /\[set-buffer\]\[-b\]\[omx-pane-input-/);
157
+ assert.match(lines[1], /\[--\]\[Read \/tmp\/team\/mailbox\/leader-fixed\.json/);
158
+ assert.match(lines[2], /\[show-buffer\]\[-b\]\[omx-pane-input-/);
159
+ assert.match(lines[3], /\[send-keys\]\[-t\]\[%42\]\[C-u\]/);
160
+ assert.match(lines[4], /\[paste-buffer\]\[-t\]\[%42\]\[-b\]\[omx-pane-input-.*\]\[-p\]\[-d\]/);
161
+ assert.match(lines[5], /\[send-keys\]\[-t\]\[%42\]\[Tab\]/);
148
162
  assert.match(lines[6], /\[send-keys\]\[-t\]\[%42\]\[C-m\]/);
149
- assert.match(lines[7], /\[delete-buffer\]\[-b\]\[omx-pane-input-/);
163
+ assert.match(lines[7], /\[send-keys\]\[-t\]\[%42\]\[C-m\]/);
164
+ assert.match(lines[8], /\[delete-buffer\]\[-b\]\[omx-pane-input-/);
150
165
  }
151
166
  finally {
152
167
  await rm(cwd, { recursive: true, force: true });
@@ -165,6 +180,7 @@ describe('notify-hook team tmux guard bridge', () => {
165
180
  fakeBinDir,
166
181
  moduleUrl,
167
182
  paneTarget: '%42',
183
+ exactPaneId: '%42',
168
184
  prompt: 'hello bridge',
169
185
  submitKeyPresses: 1,
170
186
  typePrompt: true,
@@ -179,9 +195,10 @@ describe('notify-hook team tmux guard bridge', () => {
179
195
  assert.match(log, /\[send-keys\]\[-t\]\[%42\]\[C-u\]/);
180
196
  assert.match(log, /\[paste-buffer\]\[-t\]\[%42\]\[-b\]\[omx-pane-input-.*\]\[-p\]\[-d\]/);
181
197
  const lines = log.trim().split('\n').filter(Boolean);
182
- assert.equal(lines.length, 6);
183
- assert.match(lines[4], /\[send-keys\]\[-t\]\[%42\]\[C-m\]/);
184
- assert.match(lines[5], /\[delete-buffer\]\[-b\]\[omx-pane-input-/);
198
+ assert.equal(lines.length, 7);
199
+ assert.match(lines[0], /\[display-message\]\[-p\]\[-t\]\[%42\]\[#\{pane_start_command\}\]/);
200
+ assert.match(lines[5], /\[send-keys\]\[-t\]\[%42\]\[C-m\]/);
201
+ assert.match(lines[6], /\[delete-buffer\]\[-b\]\[omx-pane-input-/);
185
202
  }
186
203
  finally {
187
204
  await rm(cwd, { recursive: true, force: true });
@@ -195,6 +212,8 @@ describe('notify-hook team tmux guard bridge', () => {
195
212
  await mkdir(fakeBinDir, { recursive: true });
196
213
  await writeFile(join(fakeBinDir, 'tmux'), `#!/usr/bin/env bash
197
214
  set -eu
215
+ ${liveExactPaneProof()}
216
+
198
217
  printf '[%s]' "$@" >> "${tmuxLogPath}"
199
218
  printf '\n' >> "${tmuxLogPath}"
200
219
  cmd="$1"
@@ -214,6 +233,7 @@ exit 0
214
233
  fakeBinDir,
215
234
  moduleUrl,
216
235
  paneTarget: '%42',
236
+ exactPaneId: '%42',
217
237
  prompt: 'intended supervisor handoff',
218
238
  submitKeyPresses: 1,
219
239
  typePrompt: true,
@@ -242,6 +262,8 @@ exit 0
242
262
  await mkdir(fakeBinDir, { recursive: true });
243
263
  await writeFile(join(fakeBinDir, 'tmux'), `#!/usr/bin/env bash
244
264
  set -eu
265
+ ${liveExactPaneProof()}
266
+
245
267
  printf '[%s]' "$@" >> "${tmuxLogPath}"
246
268
  printf '\n' >> "${tmuxLogPath}"
247
269
  cmd="$1"
@@ -261,6 +283,7 @@ exit 0
261
283
  fakeBinDir,
262
284
  moduleUrl,
263
285
  paneTarget: '%42',
286
+ exactPaneId: '%42',
264
287
  prompt: 'supervisor handoff after setup',
265
288
  submitKeyPresses: 1,
266
289
  typePrompt: true,
@@ -270,10 +293,11 @@ exit 0
270
293
  assert.equal(parsed.ok, false);
271
294
  assert.equal(parsed.reason, 'buffer_show_failed');
272
295
  const lines = (await readFile(tmuxLogPath, 'utf-8')).trim().split('\n').filter(Boolean);
273
- assert.match(lines[0] ?? '', /\[set-buffer\]\[-b\]\[omx-pane-input-/);
274
- assert.match(lines[1] ?? '', /\[show-buffer\]\[-b\]\[omx-pane-input-/);
275
- assert.match(lines[2] ?? '', /\[delete-buffer\]\[-b\]\[omx-pane-input-/);
276
- assert.equal(lines.length, 3);
296
+ assert.match(lines[0] ?? '', /\[display-message\]\[-p\]\[-t\]\[%42\]\[#\{pane_start_command\}\]/);
297
+ assert.match(lines[1] ?? '', /\[set-buffer\]\[-b\]\[omx-pane-input-/);
298
+ assert.match(lines[2] ?? '', /\[show-buffer\]\[-b\]\[omx-pane-input-/);
299
+ assert.match(lines[3] ?? '', /\[delete-buffer\]\[-b\]\[omx-pane-input-/);
300
+ assert.equal(lines.length, 4);
277
301
  }
278
302
  finally {
279
303
  await rm(cwd, { recursive: true, force: true });
@@ -288,6 +312,8 @@ exit 0
288
312
  await mkdir(fakeBinDir, { recursive: true });
289
313
  await writeFile(join(fakeBinDir, 'tmux'), `#!/usr/bin/env bash
290
314
  set -eu
315
+ ${liveExactPaneProof()}
316
+
291
317
  printf '[%s]' "$@" >> "${tmuxLogPath}"
292
318
  printf '\n' >> "${tmuxLogPath}"
293
319
  cmd="$1"
@@ -315,6 +341,7 @@ exit 0
315
341
  fakeBinDir,
316
342
  moduleUrl,
317
343
  paneTarget: '%42',
344
+ exactPaneId: '%42',
318
345
  prompt: 'supervisor handoff after verify',
319
346
  submitKeyPresses: 1,
320
347
  typePrompt: true,
@@ -324,12 +351,13 @@ exit 0
324
351
  assert.equal(parsed.ok, false);
325
352
  assert.equal(parsed.reason, 'buffer_paste_failed');
326
353
  const lines = (await readFile(tmuxLogPath, 'utf-8')).trim().split('\n').filter(Boolean);
327
- assert.match(lines[0] ?? '', /\[set-buffer\]\[-b\]\[omx-pane-input-/);
328
- assert.match(lines[1] ?? '', /\[show-buffer\]\[-b\]\[omx-pane-input-/);
329
- assert.match(lines[2] ?? '', /\[send-keys\]\[-t\]\[%42\]\[C-u\]/);
330
- assert.match(lines[3] ?? '', /\[paste-buffer\]\[-t\]\[%42\]\[-b\]\[omx-pane-input-.*\]\[-p\]\[-d\]/);
331
- assert.match(lines[4] ?? '', /\[delete-buffer\]\[-b\]\[omx-pane-input-/);
332
- assert.equal(lines.length, 5);
354
+ assert.match(lines[0] ?? '', /\[display-message\]\[-p\]\[-t\]\[%42\]\[#\{pane_start_command\}\]/);
355
+ assert.match(lines[1] ?? '', /\[set-buffer\]\[-b\]\[omx-pane-input-/);
356
+ assert.match(lines[2] ?? '', /\[show-buffer\]\[-b\]\[omx-pane-input-/);
357
+ assert.match(lines[3] ?? '', /\[send-keys\]\[-t\]\[%42\]\[C-u\]/);
358
+ assert.match(lines[4] ?? '', /\[paste-buffer\]\[-t\]\[%42\]\[-b\]\[omx-pane-input-.*\]\[-p\]\[-d\]/);
359
+ assert.match(lines[5] ?? '', /\[delete-buffer\]\[-b\]\[omx-pane-input-/);
360
+ assert.equal(lines.length, 6);
333
361
  }
334
362
  finally {
335
363
  await rm(cwd, { recursive: true, force: true });
@@ -343,6 +371,8 @@ exit 0
343
371
  await mkdir(fakeBinDir, { recursive: true });
344
372
  await writeFile(join(fakeBinDir, 'tmux'), `#!/usr/bin/env bash
345
373
  set -eu
374
+ ${liveExactPaneProof()}
375
+
346
376
  echo "$@" >> "${tmuxLogPath}"
347
377
  cmd="$1"
348
378
  shift || true
@@ -370,6 +400,7 @@ exit 0
370
400
  fakeBinDir,
371
401
  moduleUrl,
372
402
  paneTarget: '%42',
403
+ options: { exactPaneId: '%42' },
373
404
  });
374
405
  assert.equal(result.status, 0, result.stderr);
375
406
  assert.equal(result.error, undefined);
@@ -383,7 +414,7 @@ exit 0
383
414
  await rm(cwd, { recursive: true, force: true });
384
415
  }
385
416
  });
386
- it('treats capture-pane failure as non-blocking for a live codex pane', async () => {
417
+ it('fails closed without input effects when exact-pane readiness queries fail', async () => {
387
418
  const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-guard-'));
388
419
  const fakeBinDir = join(cwd, 'fake-bin');
389
420
  const tmuxLogPath = join(cwd, 'tmux.log');
@@ -391,6 +422,7 @@ exit 0
391
422
  await mkdir(fakeBinDir, { recursive: true });
392
423
  await writeFile(join(fakeBinDir, 'tmux'), `#!/usr/bin/env bash
393
424
  set -eu
425
+ ${liveExactPaneProof()}
394
426
  echo "$@" >> "${tmuxLogPath}"
395
427
  cmd="$1"
396
428
  shift || true
@@ -414,23 +446,441 @@ exit 0
414
446
  `);
415
447
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
416
448
  const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
417
- const result = runEvaluatePaneInjectionReadinessInChild({
449
+ const captureResult = runEvaluatePaneInjectionReadinessInChild({
450
+ fakeBinDir,
451
+ moduleUrl,
452
+ paneTarget: '%42',
453
+ options: { skipIfScrolling: true, exactPaneId: '%42' },
454
+ });
455
+ assert.equal(captureResult.status, 0, captureResult.stderr);
456
+ assert.equal(captureResult.error, undefined);
457
+ const captureParsed = JSON.parse(captureResult.stdout);
458
+ assert.equal(captureParsed.ok, false);
459
+ assert.equal(captureParsed.reason, 'pane_readiness_unverified');
460
+ assert.equal(captureParsed.readinessEvidence, 'capture_failed');
461
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf-8'), /set-buffer|paste-buffer|send-keys|delete-buffer/);
462
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/usr/bin/env bash
463
+ set -eu
464
+ ${liveExactPaneProof()}
465
+ echo "$@" >> "${tmuxLogPath}"
466
+ if [[ "$1" == "display-message" ]]; then
467
+ echo "pane current command query failed" >&2
468
+ exit 1
469
+ fi
470
+ exit 0
471
+ `);
472
+ const commandResult = runEvaluatePaneInjectionReadinessInChild({
418
473
  fakeBinDir,
419
474
  moduleUrl,
420
475
  paneTarget: '%42',
421
- options: { skipIfScrolling: true },
476
+ options: { exactPaneId: '%42' },
477
+ });
478
+ assert.equal(commandResult.status, 0, commandResult.stderr);
479
+ assert.equal(commandResult.error, undefined);
480
+ const commandParsed = JSON.parse(commandResult.stdout);
481
+ assert.equal(commandParsed.ok, false);
482
+ assert.equal(commandParsed.reason, 'pane_readiness_unverified');
483
+ assert.equal(commandParsed.readinessEvidence, 'start_command_failed');
484
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf-8'), /set-buffer|paste-buffer|send-keys|delete-buffer/);
485
+ }
486
+ finally {
487
+ await rm(cwd, { recursive: true, force: true });
488
+ }
489
+ });
490
+ it('rejects an invalid explicit pane identity without any tmux action', async () => {
491
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-guard-'));
492
+ const fakeBinDir = join(cwd, 'fake-bin');
493
+ const tmuxLogPath = join(cwd, 'tmux.log');
494
+ try {
495
+ await mkdir(fakeBinDir, { recursive: true });
496
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
497
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
498
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
499
+ const result = runSendPaneInputInChild({
500
+ fakeBinDir,
501
+ moduleUrl,
502
+ paneTarget: '%42',
503
+ exactPaneId: 'invalid-pane-id',
504
+ prompt: 'must not be sent',
505
+ submitKeyPresses: 1,
506
+ typePrompt: true,
422
507
  });
423
508
  assert.equal(result.status, 0, result.stderr);
424
- assert.equal(result.error, undefined);
425
509
  const parsed = JSON.parse(result.stdout);
426
- assert.equal(parsed.ok, true);
427
- assert.equal(parsed.reason, 'ok');
428
- assert.equal(parsed.paneCurrentCommand, 'codex');
429
- assert.equal(parsed.paneCapture, '');
510
+ assert.equal(parsed.ok, false);
511
+ assert.equal(parsed.reason, 'exact_pane_unavailable');
512
+ assert.equal(parsed.exactPaneProof?.reason, 'invalid_pane_id');
513
+ assert.equal(await readFile(tmuxLogPath, 'utf-8').catch(() => ''), '');
514
+ }
515
+ finally {
516
+ await rm(cwd, { recursive: true, force: true });
517
+ }
518
+ });
519
+ it('stops before input effects when authoritative exact-pane query fails', async () => {
520
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-proof-fail-'));
521
+ const fakeBinDir = join(cwd, 'fake-bin');
522
+ const tmuxLogPath = join(cwd, 'tmux.log');
523
+ try {
524
+ await mkdir(fakeBinDir, { recursive: true });
525
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/usr/bin/env bash\nprintf '[%s]' "$@" >> "${tmuxLogPath}"\nprintf '\\n' >> "${tmuxLogPath}"\nexit 1\n`);
526
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
527
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
528
+ const result = runSendPaneInputInChild({
529
+ fakeBinDir,
530
+ moduleUrl,
531
+ paneTarget: '%42',
532
+ exactPaneId: '%42',
533
+ prompt: 'must not be sent',
534
+ submitKeyPresses: 2,
535
+ typePrompt: true,
536
+ });
537
+ assert.equal(result.status, 0, result.stderr);
538
+ const parsed = JSON.parse(result.stdout);
539
+ assert.equal(parsed.ok, false);
540
+ assert.equal(parsed.reason, 'exact_pane_unavailable');
541
+ const log = await readFile(tmuxLogPath, 'utf8');
542
+ assert.match(log, /\[list-panes\]\[-a\]\[-F\]/);
543
+ assert.doesNotMatch(log, /set-buffer|paste-buffer|send-keys|delete-buffer/);
430
544
  }
431
545
  finally {
432
546
  await rm(cwd, { recursive: true, force: true });
433
547
  }
434
548
  });
435
549
  });
550
+ it('rejects omitted and mismatched exact pane identities before any tmux effect', async () => {
551
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-binding-'));
552
+ const fakeBinDir = join(cwd, 'fake-bin');
553
+ try {
554
+ await mkdir(fakeBinDir, { recursive: true });
555
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
556
+ const omitted = runSendPaneInputInChild({
557
+ fakeBinDir,
558
+ moduleUrl,
559
+ paneTarget: '%42',
560
+ prompt: 'must not send',
561
+ submitKeyPresses: 1,
562
+ typePrompt: false,
563
+ });
564
+ assert.equal(omitted.status, 0, omitted.stderr);
565
+ assert.equal(JSON.parse(omitted.stdout).exactPaneProof.reason, 'missing_exact_pane_id');
566
+ const mismatched = runSendPaneInputInChild({
567
+ fakeBinDir,
568
+ moduleUrl,
569
+ paneTarget: '%42',
570
+ exactPaneId: '%43',
571
+ prompt: 'must not send',
572
+ submitKeyPresses: 1,
573
+ typePrompt: false,
574
+ });
575
+ assert.equal(mismatched.status, 0, mismatched.stderr);
576
+ assert.equal(JSON.parse(mismatched.stdout).exactPaneProof.reason, 'pane_target_mismatch');
577
+ }
578
+ finally {
579
+ await rm(cwd, { recursive: true, force: true });
580
+ }
581
+ });
582
+ it('rejects an exact live pane whose start command identifies the HUD', async () => {
583
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-hud-target-'));
584
+ const fakeBinDir = join(cwd, 'fake-bin');
585
+ const tmuxLogPath = join(cwd, 'tmux.log');
586
+ try {
587
+ await mkdir(fakeBinDir, { recursive: true });
588
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/bin/sh
589
+ set -eu
590
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
591
+ if [ "$1" = "list-panes" ]; then printf '%%42\t0\t4242\n'; exit 0; fi
592
+ case "$*" in
593
+ *'#{pane_start_command}'*) echo 'node dist/cli/omx.js hud --watch' ;;
594
+ esac
595
+ `);
596
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
597
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
598
+ const result = runSendPaneInputInChild({
599
+ fakeBinDir,
600
+ moduleUrl,
601
+ paneTarget: '%42',
602
+ exactPaneId: '%42',
603
+ prompt: 'must not send',
604
+ submitKeyPresses: 1,
605
+ typePrompt: false,
606
+ });
607
+ assert.equal(result.status, 0, result.stderr);
608
+ const parsed = JSON.parse(result.stdout);
609
+ assert.equal(parsed.ok, false);
610
+ assert.equal(parsed.reason, 'hud_pane_target');
611
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf8'), /set-buffer|paste-buffer|send-keys/);
612
+ }
613
+ finally {
614
+ await rm(cwd, { recursive: true, force: true });
615
+ }
616
+ });
617
+ it('stops before input when a repeated exact proof observes a new pane PID', async () => {
618
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-pid-change-'));
619
+ const fakeBinDir = join(cwd, 'fake-bin');
620
+ const tmuxLogPath = join(cwd, 'tmux.log');
621
+ const countPath = join(cwd, 'proof-count');
622
+ try {
623
+ await mkdir(fakeBinDir, { recursive: true });
624
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/bin/sh
625
+ set -eu
626
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
627
+ if [ "$1" = "list-panes" ]; then
628
+ count=0; [ ! -f "${countPath}" ] || count=$(cat "${countPath}")
629
+ count=$((count + 1)); printf '%s' "$count" > "${countPath}"
630
+ if [ "$count" -eq 1 ]; then printf '%%42\t0\t4242\n'; else printf '%%42\t0\t4343\n'; fi
631
+ fi
632
+ `);
633
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
634
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
635
+ const result = runSendPaneInputInChild({
636
+ fakeBinDir,
637
+ moduleUrl,
638
+ paneTarget: '%42',
639
+ exactPaneId: '%42',
640
+ prompt: 'must not send',
641
+ submitKeyPresses: 1,
642
+ typePrompt: false,
643
+ });
644
+ assert.equal(result.status, 0, result.stderr);
645
+ const parsed = JSON.parse(result.stdout);
646
+ assert.equal(parsed.ok, false);
647
+ assert.equal(parsed.exactPaneProof.reason, 'pane_pid_changed');
648
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf8'), /send-keys/);
649
+ }
650
+ finally {
651
+ await rm(cwd, { recursive: true, force: true });
652
+ }
653
+ });
654
+ it('rejects a same-PID owner takeover before any Team pane input effect', async () => {
655
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-owner-change-'));
656
+ const fakeBinDir = join(cwd, 'fake-bin');
657
+ const tmuxLogPath = join(cwd, 'tmux.log');
658
+ try {
659
+ await mkdir(fakeBinDir, { recursive: true });
660
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/bin/sh
661
+ set -eu
662
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
663
+ if [ "$1" = "list-panes" ]; then printf '%%42\t0\t4242\n'; exit 0; fi
664
+ if [ "$1" = "display-message" ]; then echo codex; exit 0; fi
665
+ if [ "$1" = "show-option" ]; then echo team:foreign; exit 0; fi
666
+ `);
667
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
668
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
669
+ const result = runSendPaneInputInChild({
670
+ fakeBinDir,
671
+ moduleUrl,
672
+ paneTarget: '%42',
673
+ exactPaneId: '%42',
674
+ expectedPanePid: 4242,
675
+ expectedPaneOwnerId: 'team:alpha',
676
+ prompt: 'must not send',
677
+ submitKeyPresses: 1,
678
+ typePrompt: false,
679
+ });
680
+ assert.equal(result.status, 0, result.stderr);
681
+ const parsed = JSON.parse(result.stdout);
682
+ assert.equal(parsed.ok, false);
683
+ assert.equal(parsed.exactPaneProof.reason, 'pane_owner_changed');
684
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf8'), /send-keys|paste-buffer/);
685
+ }
686
+ finally {
687
+ await rm(cwd, { recursive: true, force: true });
688
+ }
689
+ });
690
+ it('rejects a replacement pane observed after a matching owner read', async () => {
691
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-owner-replacement-'));
692
+ const fakeBinDir = join(cwd, 'fake-bin');
693
+ const tmuxLogPath = join(cwd, 'tmux.log');
694
+ const countPath = join(cwd, 'proof-count');
695
+ try {
696
+ await mkdir(fakeBinDir, { recursive: true });
697
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/bin/sh
698
+ set -eu
699
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
700
+ if [ "$1" = "list-panes" ]; then
701
+ count=0; [ ! -f "${countPath}" ] || count=$(cat "${countPath}")
702
+ count=$((count + 1)); printf '%s' "$count" > "${countPath}"
703
+ if [ "$count" -le 3 ]; then printf '%%42\t0\t4242\n'; else printf '%%42\t0\t4343\n'; fi
704
+ exit 0
705
+ fi
706
+ if [ "$1" = "display-message" ]; then echo codex; exit 0; fi
707
+ if [ "$1" = "show-option" ]; then echo team:alpha; exit 0; fi
708
+ `);
709
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
710
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
711
+ const result = runSendPaneInputInChild({
712
+ fakeBinDir,
713
+ moduleUrl,
714
+ paneTarget: '%42',
715
+ exactPaneId: '%42',
716
+ expectedPanePid: 4242,
717
+ expectedPaneOwnerId: 'team:alpha',
718
+ prompt: 'must not send',
719
+ submitKeyPresses: 1,
720
+ typePrompt: false,
721
+ });
722
+ assert.equal(result.status, 0, result.stderr);
723
+ const parsed = JSON.parse(result.stdout);
724
+ assert.equal(parsed.ok, false);
725
+ assert.equal(parsed.exactPaneProof.reason, 'pane_pid_changed');
726
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf8'), /send-keys|paste-buffer/);
727
+ }
728
+ finally {
729
+ await rm(cwd, { recursive: true, force: true });
730
+ }
731
+ });
732
+ it('rejects a foreign-owner Team pane before inspecting readiness content', async () => {
733
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-readiness-owner-change-'));
734
+ const fakeBinDir = join(cwd, 'fake-bin');
735
+ const tmuxLogPath = join(cwd, 'tmux.log');
736
+ try {
737
+ await mkdir(fakeBinDir, { recursive: true });
738
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/bin/sh
739
+ set -eu
740
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
741
+ if [ "$1" = "list-panes" ]; then printf '%%42\t0\t4242\n'; exit 0; fi
742
+ if [ "$1" = "show-option" ]; then echo team:foreign; exit 0; fi
743
+ if [ "$1" = "display-message" ]; then echo foreign-command; exit 0; fi
744
+ if [ "$1" = "capture-pane" ]; then echo foreign-content; exit 0; fi
745
+ `);
746
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
747
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
748
+ const result = runEvaluatePaneInjectionReadinessInChild({
749
+ fakeBinDir,
750
+ moduleUrl,
751
+ paneTarget: '%42',
752
+ options: {
753
+ exactPaneId: '%42',
754
+ expectedPanePid: 4242,
755
+ expectedPaneOwnerId: 'team:alpha',
756
+ expectedHudPaneId: '%99',
757
+ },
758
+ });
759
+ assert.equal(result.status, 0, result.stderr);
760
+ const parsed = JSON.parse(result.stdout);
761
+ assert.equal(parsed.ok, false);
762
+ assert.equal(parsed.exactPaneProof.reason, 'pane_owner_changed');
763
+ assert.equal(parsed.paneCapture, '');
764
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf8'), /display-message|capture-pane/);
765
+ }
766
+ finally {
767
+ await rm(cwd, { recursive: true, force: true });
768
+ }
769
+ });
770
+ it('rejects a replacement Team pane after the owner proof and before readiness reads', async () => {
771
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-readiness-owner-replacement-'));
772
+ const fakeBinDir = join(cwd, 'fake-bin');
773
+ const tmuxLogPath = join(cwd, 'tmux.log');
774
+ const countPath = join(cwd, 'proof-count');
775
+ try {
776
+ await mkdir(fakeBinDir, { recursive: true });
777
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/bin/sh
778
+ set -eu
779
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
780
+ if [ "$1" = "list-panes" ]; then
781
+ count=0; [ ! -f "${countPath}" ] || count=$(cat "${countPath}")
782
+ count=$((count + 1)); printf '%s' "$count" > "${countPath}"
783
+ if [ "$count" -eq 1 ]; then printf '%%42\t0\t4242\n'; else printf '%%42\t0\t4343\n'; fi
784
+ exit 0
785
+ fi
786
+ if [ "$1" = "show-option" ]; then echo team:alpha; exit 0; fi
787
+ if [ "$1" = "display-message" ]; then echo foreign-command; exit 0; fi
788
+ if [ "$1" = "capture-pane" ]; then echo foreign-content; exit 0; fi
789
+ `);
790
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
791
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
792
+ const result = runEvaluatePaneInjectionReadinessInChild({
793
+ fakeBinDir,
794
+ moduleUrl,
795
+ paneTarget: '%42',
796
+ options: {
797
+ exactPaneId: '%42',
798
+ expectedPanePid: 4242,
799
+ expectedPaneOwnerId: 'team:alpha',
800
+ expectedHudPaneId: '%99',
801
+ },
802
+ });
803
+ assert.equal(result.status, 0, result.stderr);
804
+ const parsed = JSON.parse(result.stdout);
805
+ assert.equal(parsed.ok, false);
806
+ assert.equal(parsed.exactPaneProof.reason, 'pane_pid_changed');
807
+ assert.equal(parsed.paneCapture, '');
808
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf8'), /display-message|capture-pane/);
809
+ }
810
+ finally {
811
+ await rm(cwd, { recursive: true, force: true });
812
+ }
813
+ });
814
+ it('rejects the configured Team HUD without reading its command or capture', async () => {
815
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-readiness-hud-'));
816
+ const fakeBinDir = join(cwd, 'fake-bin');
817
+ const tmuxLogPath = join(cwd, 'tmux.log');
818
+ try {
819
+ await mkdir(fakeBinDir, { recursive: true });
820
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/bin/sh
821
+ set -eu
822
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
823
+ echo foreign-content
824
+ `);
825
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
826
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
827
+ const result = runEvaluatePaneInjectionReadinessInChild({
828
+ fakeBinDir,
829
+ moduleUrl,
830
+ paneTarget: '%42',
831
+ options: {
832
+ exactPaneId: '%42',
833
+ expectedPanePid: 4242,
834
+ expectedPaneOwnerId: 'team:alpha',
835
+ expectedHudPaneId: '%42',
836
+ },
837
+ });
838
+ assert.equal(result.status, 0, result.stderr);
839
+ const parsed = JSON.parse(result.stdout);
840
+ assert.equal(parsed.ok, false);
841
+ assert.equal(parsed.reason, 'hud_pane_target');
842
+ assert.equal(parsed.paneCapture, '');
843
+ await assert.rejects(readFile(tmuxLogPath, 'utf8'));
844
+ }
845
+ finally {
846
+ await rm(cwd, { recursive: true, force: true });
847
+ }
848
+ });
849
+ it('authorizes Team ownership before reading the start command', async () => {
850
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-team-tmux-start-command-owner-'));
851
+ const fakeBinDir = join(cwd, 'fake-bin');
852
+ const tmuxLogPath = join(cwd, 'tmux.log');
853
+ try {
854
+ await mkdir(fakeBinDir, { recursive: true });
855
+ await writeFile(join(fakeBinDir, 'tmux'), `#!/bin/sh
856
+ set -eu
857
+ printf '%s\n' "$*" >> "${tmuxLogPath}"
858
+ if [ "$1" = "list-panes" ]; then printf '%%42\t0\t4242\n'; exit 0; fi
859
+ if [ "$1" = "show-option" ]; then echo team:foreign; exit 0; fi
860
+ if [ "$1" = "display-message" ]; then echo foreign-command; exit 0; fi
861
+ `);
862
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
863
+ const moduleUrl = new URL('../../../dist/scripts/notify-hook/team-tmux-guard.js', import.meta.url).href;
864
+ const result = runSendPaneInputInChild({
865
+ fakeBinDir,
866
+ moduleUrl,
867
+ paneTarget: '%42',
868
+ exactPaneId: '%42',
869
+ expectedPanePid: 4242,
870
+ expectedPaneOwnerId: 'team:alpha',
871
+ prompt: 'must not inspect or send',
872
+ submitKeyPresses: 1,
873
+ typePrompt: false,
874
+ });
875
+ assert.equal(result.status, 0, result.stderr);
876
+ const parsed = JSON.parse(result.stdout);
877
+ assert.equal(parsed.ok, false);
878
+ assert.equal(parsed.exactPaneProof.reason, 'pane_owner_changed');
879
+ const log = await readFile(tmuxLogPath, 'utf8');
880
+ assert.doesNotMatch(log, /display-message|send-keys|paste-buffer/);
881
+ }
882
+ finally {
883
+ await rm(cwd, { recursive: true, force: true });
884
+ }
885
+ });
436
886
  //# sourceMappingURL=notify-hook-team-tmux-guard.test.js.map