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
@@ -11,7 +11,7 @@ const __filename = fileURLToPath(import.meta.url);
11
11
  const __dirname = dirname(__filename);
12
12
  import { runProcess } from './process-runner.js';
13
13
  import { resolvePaneTarget, resolveSessionToPane } from './tmux-injection.js';
14
- import { evaluatePaneInjectionReadiness, sendPaneInput } from './team-tmux-guard.js';
14
+ import { EXACT_PANE_UNAVAILABLE_REASON, evaluatePaneInjectionReadiness, normalizeExactPaneId, sendPaneInput, verifyExactPaneLive, verifyExactPaneOwnerLive, } from './team-tmux-guard.js';
15
15
  import { normalizeTmuxCapture, paneHasActiveTask, paneLooksReady, } from '../tmux-hook-engine.js';
16
16
  /**
17
17
  * Route dispatch state transitions through the Rust runtime binary.
@@ -419,26 +419,79 @@ async function writeBridgeDispatchCompat(stateDir, teamName, requests) {
419
419
  const records = [...otherTeams, ...requests.map(serializeDispatchRequestRecord)];
420
420
  await writeJsonAtomic(compatPath, { records });
421
421
  }
422
- function defaultInjectTarget(request, config) {
422
+ function explicitPaneIdentity(value) {
423
+ const rawPaneId = safeString(value).trim();
424
+ return { provided: rawPaneId !== '', paneId: normalizeExactPaneId(rawPaneId), rawPaneId };
425
+ }
426
+ function positivePanePid(value) {
427
+ return Number.isInteger(value) && Number(value) > 0 ? Number(value) : undefined;
428
+ }
429
+ function resolveAddressedWorker(request, config) {
430
+ const workers = Array.isArray(config?.workers) ? config.workers : [];
431
+ if (Number.isFinite(request?.worker_index)) {
432
+ const worker = workers.find((candidate) => Number(candidate?.index) === Number(request.worker_index));
433
+ if (worker)
434
+ return worker;
435
+ }
436
+ const recipient = safeString(request?.to_worker).trim();
437
+ return workers.find((candidate) => safeString(candidate?.name).trim() === recipient) || null;
438
+ }
439
+ function resolveConfiguredPaneIdentity(request, config) {
423
440
  if (request.to_worker === 'leader-fixed') {
424
- const leaderPaneId = resolveLeaderPaneId(config);
425
- if (leaderPaneId)
426
- return { type: 'pane', value: leaderPaneId };
427
- return null;
441
+ return { source: 'leader_pane_id', expectedPanePid: positivePanePid(config?.leader_pane_pid), expectedPaneOwnerId: safeString(config?.tmux_pane_owner_id).trim(), ...explicitPaneIdentity(config?.leader_pane_id) };
428
442
  }
429
- if (request.pane_id)
430
- return { type: 'pane', value: request.pane_id };
431
- if (typeof request.worker_index === 'number' && Array.isArray(config?.workers)) {
432
- const worker = config.workers.find((candidate) => Number(candidate?.index) === request.worker_index);
433
- if (worker?.pane_id)
434
- return { type: 'pane', value: worker.pane_id };
443
+ const worker = resolveAddressedWorker(request, config);
444
+ const expectedPanePid = positivePanePid(worker?.pid);
445
+ return { source: 'worker_pane_id', expectedPanePid, expectedPaneOwnerId: safeString(config?.tmux_pane_owner_id).trim(), ...explicitPaneIdentity(worker?.pane_id) };
446
+ }
447
+ function resolveDispatchTarget(request, config) {
448
+ const requestPane = explicitPaneIdentity(request?.pane_id);
449
+ const configuredPane = resolveConfiguredPaneIdentity(request, config);
450
+ const hudPaneId = explicitPaneIdentity(config?.hud_pane_id).paneId;
451
+ if (requestPane.provided) {
452
+ if (!requestPane.paneId) {
453
+ return { failure: 'invalid_explicit_pane', paneId: requestPane.paneId, paneSource: 'request_pane_id', rawPaneId: requestPane.rawPaneId };
454
+ }
455
+ if (requestPane.paneId === hudPaneId) {
456
+ return { failure: 'hud_pane_target', paneId: requestPane.paneId, paneSource: 'request_pane_id' };
457
+ }
458
+ if (configuredPane.provided) {
459
+ if (!configuredPane.paneId) {
460
+ return { failure: 'invalid_explicit_pane', paneId: configuredPane.paneId, paneSource: configuredPane.source, rawPaneId: configuredPane.rawPaneId };
461
+ }
462
+ if (requestPane.paneId !== configuredPane.paneId) {
463
+ return {
464
+ failure: 'exact_pane_mismatch',
465
+ paneId: requestPane.paneId,
466
+ paneSource: 'request_pane_id',
467
+ configuredPaneId: configuredPane.paneId,
468
+ };
469
+ }
470
+ }
471
+ if (!positivePanePid(configuredPane.expectedPanePid)) {
472
+ return { failure: 'missing_exact_pane_pid', paneId: requestPane.paneId, paneSource: configuredPane.source };
473
+ }
474
+ if (!configuredPane.expectedPaneOwnerId) {
475
+ return { failure: 'missing_expected_pane_owner', paneId: requestPane.paneId, paneSource: configuredPane.source };
476
+ }
477
+ return { target: { type: 'pane', value: requestPane.paneId }, exactPaneId: requestPane.paneId, expectedPanePid: configuredPane.expectedPanePid, expectedPaneOwnerId: configuredPane.expectedPaneOwnerId, expectedHudPaneId: hudPaneId, source: 'request_pane_id', reason: 'explicit_request_pane_id' };
435
478
  }
436
- if (typeof request.worker_index === 'number' && config.tmux_session) {
437
- return { type: 'pane', value: `${config.tmux_session}.${request.worker_index}` };
479
+ if (configuredPane.provided) {
480
+ if (!configuredPane.paneId) {
481
+ return { failure: 'invalid_explicit_pane', paneId: configuredPane.paneId, paneSource: configuredPane.source, rawPaneId: configuredPane.rawPaneId };
482
+ }
483
+ if (configuredPane.paneId === hudPaneId) {
484
+ return { failure: 'hud_pane_target', paneId: configuredPane.paneId, paneSource: configuredPane.source };
485
+ }
486
+ if (!positivePanePid(configuredPane.expectedPanePid)) {
487
+ return { failure: 'missing_exact_pane_pid', paneId: configuredPane.paneId, paneSource: configuredPane.source };
488
+ }
489
+ if (!configuredPane.expectedPaneOwnerId) {
490
+ return { failure: 'missing_expected_pane_owner', paneId: configuredPane.paneId, paneSource: configuredPane.source };
491
+ }
492
+ return { target: { type: 'pane', value: configuredPane.paneId }, exactPaneId: configuredPane.paneId, expectedPanePid: configuredPane.expectedPanePid, expectedPaneOwnerId: configuredPane.expectedPaneOwnerId, expectedHudPaneId: hudPaneId, source: configuredPane.source, reason: configuredPane.source };
438
493
  }
439
- if (config.tmux_session)
440
- return { type: 'session', value: config.tmux_session };
441
- return null;
494
+ return { target: null };
442
495
  }
443
496
  async function appendLeaderNotificationDeferredEvent({ stateDir, teamName, request, reason, nowIso, tmuxSession = '', leaderPaneId = '', sourceType = 'team_dispatch', }) {
444
497
  const eventsDir = join(stateDir, 'team', teamName, 'events');
@@ -695,13 +748,52 @@ function buildJoinedCapturePaneArgv(paneTarget, tailLines = 80) {
695
748
  const INJECT_VERIFY_DELAY_MS = 250;
696
749
  const INJECT_VERIFY_ROUNDS = 3;
697
750
  async function injectDispatchRequest(request, config, cwd, stateDir) {
698
- const target = defaultInjectTarget(request, config);
751
+ const dispatchTarget = resolveDispatchTarget(request, config);
752
+ if (dispatchTarget.failure) {
753
+ if (dispatchTarget.failure !== 'exact_pane_mismatch' && dispatchTarget.failure !== 'invalid_explicit_pane') {
754
+ return {
755
+ ok: false,
756
+ reason: dispatchTarget.failure,
757
+ pane: dispatchTarget.paneId || null,
758
+ pane_source: dispatchTarget.paneSource || null,
759
+ exact_pane_proof: null,
760
+ tmux_injection_attempted: false,
761
+ };
762
+ }
763
+ const paneProof = dispatchTarget.failure === 'exact_pane_mismatch'
764
+ ? {
765
+ reason: 'exact_pane_mismatch',
766
+ proof: {
767
+ status: 'unavailable',
768
+ paneId: dispatchTarget.paneId,
769
+ reason: 'pane_mismatch',
770
+ detail: `configured pane is ${dispatchTarget.configuredPaneId}`,
771
+ },
772
+ }
773
+ : await verifyExactPaneLive(dispatchTarget.rawPaneId);
774
+ return {
775
+ ok: false,
776
+ reason: paneProof.reason,
777
+ pane: dispatchTarget.paneId || null,
778
+ pane_source: dispatchTarget.paneSource || null,
779
+ exact_pane_proof: paneProof.proof || null,
780
+ tmux_injection_attempted: false,
781
+ };
782
+ }
783
+ const target = dispatchTarget.target;
699
784
  if (!target) {
700
785
  return { ok: false, reason: 'missing_tmux_target' };
701
786
  }
702
787
  const leaderTargeted = request.to_worker === 'leader-fixed';
703
788
  let resolution;
704
- if (target.type === 'session') {
789
+ if (dispatchTarget.exactPaneId) {
790
+ resolution = {
791
+ paneTarget: dispatchTarget.exactPaneId,
792
+ reason: dispatchTarget.reason,
793
+ source: dispatchTarget.source,
794
+ };
795
+ }
796
+ else if (target.type === 'session') {
705
797
  const paneId = await resolveSessionToPane(target.value).catch(() => null);
706
798
  resolution = paneId
707
799
  ? { paneTarget: paneId, reason: 'session_target_resolved' }
@@ -713,13 +805,25 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
713
805
  if (!resolution.paneTarget) {
714
806
  return { ok: false, reason: `target_resolution_failed:${resolution.reason}` };
715
807
  }
716
- const isLeaderMailboxDispatch = request.to_worker === 'leader-fixed';
808
+ const exactPaneId = dispatchTarget.exactPaneId || normalizeExactPaneId(resolution.paneTarget);
809
+ const exactPaneFailure = (paneProof, tmuxInjectionAttempted) => ({
810
+ ok: false,
811
+ reason: paneProof.reason,
812
+ pane: resolution.paneTarget,
813
+ pane_source: resolution.source || null,
814
+ exact_pane_proof: paneProof.proof || null,
815
+ tmux_injection_attempted: tmuxInjectionAttempted,
816
+ });
717
817
  const paneGuard = await evaluatePaneInjectionReadiness(resolution.paneTarget, {
718
818
  skipIfScrolling: true,
719
819
  requireRunningAgent: leaderTargeted,
720
820
  requireReady: false,
721
821
  requireIdle: false,
722
822
  requireObservableState: leaderTargeted,
823
+ exactPaneId,
824
+ expectedPanePid: dispatchTarget.expectedPanePid,
825
+ expectedPaneOwnerId: dispatchTarget.expectedPaneOwnerId,
826
+ expectedHudPaneId: dispatchTarget.expectedHudPaneId,
723
827
  });
724
828
  if (!paneGuard.ok) {
725
829
  return {
@@ -729,15 +833,25 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
729
833
  pane_source: resolution.source || null,
730
834
  readiness_evidence: paneGuard.readinessEvidence || null,
731
835
  pane_current_command: paneGuard.paneCurrentCommand || null,
836
+ exact_pane_proof: paneGuard.exactPaneProof || null,
732
837
  tmux_injection_attempted: false,
733
838
  };
734
839
  }
840
+ let exactPaneProof = paneGuard.exactPaneProof || null;
841
+ const verifyExplicitPane = async () => {
842
+ const paneProof = await verifyExactPaneOwnerLive(exactPaneId, dispatchTarget.expectedPanePid, dispatchTarget.expectedPaneOwnerId);
843
+ exactPaneProof = paneProof.proof || null;
844
+ return paneProof;
845
+ };
735
846
  const attemptCountAtStart = Number.isFinite(request.attempt_count)
736
847
  ? Math.max(0, Math.floor(request.attempt_count))
737
848
  : 0;
738
849
  const submitKeyPresses = resolveWorkerCliForRequest(request, config) === 'claude' ? 1 : 2;
739
850
  let preCaptureHasTrigger = false;
740
851
  if (attemptCountAtStart >= 1) {
852
+ const preCaptureProof = await verifyExplicitPane();
853
+ if (!preCaptureProof.ok)
854
+ return exactPaneFailure(preCaptureProof, false);
741
855
  try {
742
856
  // Narrow capture (8 lines) to scope check to input area, not scrollback output
743
857
  const preCapture = await runProcess('tmux', buildJoinedCapturePaneArgv(resolution.paneTarget, 8), 2000);
@@ -750,15 +864,20 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
750
864
  // Retype whenever trigger text is NOT in the narrow input area, regardless of attempt count.
751
865
  // Pre-0.7.4 bug: 80-line capture matched trigger in scrollback output, falsely skipping retype.
752
866
  const shouldTypePrompt = attemptCountAtStart === 0 || !preCaptureHasTrigger;
753
- if (shouldTypePrompt) {
754
- if (attemptCountAtStart >= 1) {
755
- // Clear stale text in input buffer before retyping (mirrors sync path tmux-session.ts:1270)
756
- await runProcess('tmux', ['send-keys', '-t', resolution.paneTarget, 'C-u'], 1000).catch(() => { });
757
- await new Promise((r) => setTimeout(r, 50));
758
- }
867
+ if (shouldTypePrompt && attemptCountAtStart >= 1) {
868
+ // Clear stale text in input buffer before retyping (mirrors sync path tmux-session.ts:1270)
869
+ const clearProof = await verifyExplicitPane();
870
+ if (!clearProof.ok)
871
+ return exactPaneFailure(clearProof, false);
872
+ await runProcess('tmux', ['send-keys', '-t', resolution.paneTarget, 'C-u'], 1000).catch(() => { });
873
+ await new Promise((r) => setTimeout(r, 50));
759
874
  }
760
875
  const sendResult = await sendPaneInput({
761
876
  paneTarget: resolution.paneTarget,
877
+ exactPaneId,
878
+ expectedPanePid: dispatchTarget.expectedPanePid,
879
+ expectedPaneOwnerId: dispatchTarget.expectedPaneOwnerId,
880
+ expectedHudPaneId: dispatchTarget.expectedHudPaneId,
762
881
  prompt: request.trigger_message,
763
882
  submitKeyPresses,
764
883
  typePrompt: shouldTypePrompt,
@@ -772,9 +891,11 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
772
891
  pane_source: resolution.source || null,
773
892
  readiness_evidence: paneGuard.readinessEvidence || null,
774
893
  pane_current_command: paneGuard.paneCurrentCommand || null,
894
+ exact_pane_proof: sendResult.exactPaneProof || null,
775
895
  tmux_injection_attempted: true,
776
896
  };
777
897
  }
898
+ exactPaneProof = sendResult.exactPaneProof || exactPaneProof;
778
899
  // Post-injection verification: confirm the trigger text was consumed.
779
900
  // Fixes #391: without this, dispatch marks 'notified' even when the worker
780
901
  // pane is sitting on an unsent draft (C-m was not effectively applied).
@@ -782,73 +903,105 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
782
903
  const verifyWideArgv = buildJoinedCapturePaneArgv(resolution.paneTarget);
783
904
  for (let round = 0; round < INJECT_VERIFY_ROUNDS; round++) {
784
905
  await new Promise((r) => setTimeout(r, INJECT_VERIFY_DELAY_MS));
906
+ const narrowProof = await verifyExplicitPane();
907
+ if (!narrowProof.ok)
908
+ return exactPaneFailure(narrowProof, true);
909
+ let narrowCap;
785
910
  try {
786
911
  // Primary: trigger text no longer in narrow input area.
787
912
  // Secondary guard: also inspect the recent non-empty tail of wide capture.
788
913
  // This avoids false confirmations when Codex leaves the unsent draft just
789
914
  // above a large blank area (narrow capture misses it) while still avoiding
790
915
  // full-scrollback false positives.
791
- const narrowCap = await runProcess('tmux', verifyNarrowArgv, 2000);
792
- const wideCap = await runProcess('tmux', verifyWideArgv, 2000);
793
- const triggerInNarrow = capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message);
794
- const triggerNearTail = capturedPaneContainsTriggerNearTail(wideCap.stdout, request.trigger_message);
795
- if (triggerInNarrow || triggerNearTail) {
796
- // Draft is still visible, so C-m has not actually submitted it yet.
797
- // Do not let transient spinner/active-task text mask an unsent draft.
798
- await sendPaneInput({
799
- paneTarget: resolution.paneTarget,
800
- prompt: request.trigger_message,
801
- submitKeyPresses,
802
- typePrompt: false,
803
- }).catch(() => { });
804
- continue;
805
- }
806
- // Worker is actively processing (mirrors sync path tmux-session.ts:1292-1294)
807
- if (paneHasActiveTask(wideCap.stdout)) {
808
- runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
809
- return {
810
- ok: true,
811
- reason: 'tmux_send_keys_confirmed_active_task',
812
- pane: resolution.paneTarget,
813
- pane_source: resolution.source || null,
814
- readiness_evidence: paneGuard.readinessEvidence || null,
815
- pane_current_command: paneGuard.paneCurrentCommand || null,
816
- tmux_injection_attempted: true,
817
- };
818
- }
819
- // Do not declare success while a pane is not input-ready. Otherwise a
820
- // pre-ready send can be marked "confirmed" and later appear as a stuck
821
- // unsent draft once the UI finishes loading. This includes leader-fixed:
822
- // its Codex UI can show "tab to queue message" while busy, and marking
823
- // delivered before queue/consumption confirmation loses the orchestration
824
- // nudge until a human presses Tab manually.
825
- if (!paneLooksReady(wideCap.stdout)) {
826
- continue;
827
- }
828
- if (!triggerInNarrow && !triggerNearTail) {
829
- runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
830
- return {
831
- ok: true,
832
- reason: 'tmux_send_keys_confirmed',
833
- pane: resolution.paneTarget,
834
- pane_source: resolution.source || null,
835
- readiness_evidence: paneGuard.readinessEvidence || null,
836
- pane_current_command: paneGuard.paneCurrentCommand || null,
837
- tmux_injection_attempted: true,
838
- };
839
- }
916
+ narrowCap = await runProcess('tmux', verifyNarrowArgv, 2000);
840
917
  }
841
918
  catch {
842
- // capture failed; fall through to retry C-m
919
+ // Capture failed; fall through to retry C-m.
920
+ }
921
+ if (narrowCap) {
922
+ const wideProof = await verifyExplicitPane();
923
+ if (!wideProof.ok)
924
+ return exactPaneFailure(wideProof, true);
925
+ try {
926
+ const wideCap = await runProcess('tmux', verifyWideArgv, 2000);
927
+ const triggerInNarrow = capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message);
928
+ const triggerNearTail = capturedPaneContainsTriggerNearTail(wideCap.stdout, request.trigger_message);
929
+ if (triggerInNarrow || triggerNearTail) {
930
+ // Draft is still visible, so C-m has not actually submitted it yet.
931
+ // Do not let transient spinner/active-task text mask an unsent draft.
932
+ const retrySend = await sendPaneInput({
933
+ paneTarget: resolution.paneTarget,
934
+ exactPaneId,
935
+ expectedPanePid: dispatchTarget.expectedPanePid,
936
+ expectedPaneOwnerId: dispatchTarget.expectedPaneOwnerId,
937
+ expectedHudPaneId: dispatchTarget.expectedHudPaneId,
938
+ prompt: request.trigger_message,
939
+ submitKeyPresses,
940
+ typePrompt: false,
941
+ });
942
+ if (!retrySend.ok && retrySend.reason === EXACT_PANE_UNAVAILABLE_REASON) {
943
+ return exactPaneFailure({ reason: retrySend.reason, proof: retrySend.exactPaneProof }, true);
944
+ }
945
+ exactPaneProof = retrySend.exactPaneProof || exactPaneProof;
946
+ continue;
947
+ }
948
+ // Worker is actively processing (mirrors sync path tmux-session.ts:1292-1294)
949
+ if (paneHasActiveTask(wideCap.stdout)) {
950
+ runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
951
+ return {
952
+ ok: true,
953
+ reason: 'tmux_send_keys_confirmed_active_task',
954
+ pane: resolution.paneTarget,
955
+ pane_source: resolution.source || null,
956
+ readiness_evidence: paneGuard.readinessEvidence || null,
957
+ pane_current_command: paneGuard.paneCurrentCommand || null,
958
+ exact_pane_proof: exactPaneProof,
959
+ tmux_injection_attempted: true,
960
+ };
961
+ }
962
+ // Do not declare success while a pane is not input-ready. Otherwise a
963
+ // pre-ready send can be marked "confirmed" and later appear as a stuck
964
+ // unsent draft once the UI finishes loading. This includes leader-fixed:
965
+ // its Codex UI can show "tab to queue message" while busy, and marking
966
+ // delivered before queue/consumption confirmation loses the orchestration
967
+ // nudge until a human presses Tab manually.
968
+ if (!paneLooksReady(wideCap.stdout)) {
969
+ continue;
970
+ }
971
+ if (!triggerInNarrow && !triggerNearTail) {
972
+ runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
973
+ return {
974
+ ok: true,
975
+ reason: 'tmux_send_keys_confirmed',
976
+ pane: resolution.paneTarget,
977
+ pane_source: resolution.source || null,
978
+ readiness_evidence: paneGuard.readinessEvidence || null,
979
+ pane_current_command: paneGuard.paneCurrentCommand || null,
980
+ exact_pane_proof: exactPaneProof,
981
+ tmux_injection_attempted: true,
982
+ };
983
+ }
984
+ }
985
+ catch {
986
+ // Capture failed; fall through to retry C-m.
987
+ }
843
988
  }
844
989
  // Draft still visible and no active task — retry C-m
845
- await sendPaneInput({
990
+ const retrySend = await sendPaneInput({
846
991
  paneTarget: resolution.paneTarget,
992
+ exactPaneId,
993
+ expectedPanePid: dispatchTarget.expectedPanePid,
994
+ expectedPaneOwnerId: dispatchTarget.expectedPaneOwnerId,
995
+ expectedHudPaneId: dispatchTarget.expectedHudPaneId,
847
996
  prompt: request.trigger_message,
848
997
  submitKeyPresses,
849
998
  typePrompt: false,
850
999
  queueFirstSubmit: leaderTargeted,
851
- }).catch(() => { });
1000
+ });
1001
+ if (!retrySend.ok && retrySend.reason === EXACT_PANE_UNAVAILABLE_REASON) {
1002
+ return exactPaneFailure({ reason: retrySend.reason, proof: retrySend.exactPaneProof }, true);
1003
+ }
1004
+ exactPaneProof = retrySend.exactPaneProof || exactPaneProof;
852
1005
  }
853
1006
  // Trigger text is still visible after all retry rounds.
854
1007
  return {
@@ -858,6 +1011,7 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
858
1011
  pane_source: resolution.source || null,
859
1012
  readiness_evidence: paneGuard.readinessEvidence || null,
860
1013
  pane_current_command: paneGuard.paneCurrentCommand || null,
1014
+ exact_pane_proof: exactPaneProof,
861
1015
  tmux_injection_attempted: true,
862
1016
  };
863
1017
  }
@@ -900,6 +1054,7 @@ function buildDispatchAttemptEvidence(result, fallback = {}) {
900
1054
  pane_source: safeString(result?.pane_source || fallback.pane_source || '').trim() || null,
901
1055
  readiness_evidence: safeString(result?.readiness_evidence || fallback.readiness_evidence || '').trim() || null,
902
1056
  pane_current_command: safeString(result?.pane_current_command || fallback.pane_current_command || '').trim() || null,
1057
+ exact_pane_proof: result?.exact_pane_proof ?? fallback.exact_pane_proof ?? null,
903
1058
  tmux_injection_attempted: typeof result?.tmux_injection_attempted === 'boolean'
904
1059
  ? result.tmux_injection_attempted
905
1060
  : (typeof fallback.tmux_injection_attempted === 'boolean' ? fallback.tmux_injection_attempted : null),
@@ -948,7 +1103,7 @@ export async function drainPendingTeamDispatch({ cwd, stateDir = resolveBridgeSt
948
1103
  skipped += 1;
949
1104
  continue;
950
1105
  }
951
- if (request.to_worker === 'leader-fixed' && !resolveLeaderPaneId(config)) {
1106
+ if (request.to_worker === 'leader-fixed' && !resolveLeaderPaneId(config) && !explicitPaneIdentity(request.pane_id).provided) {
952
1107
  const nowIso = new Date().toISOString();
953
1108
  const alreadyDeferred = safeString(request.last_reason).trim() === LEADER_PANE_MISSING_DEFERRED_REASON;
954
1109
  request.updated_at = nowIso;