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
@@ -12,7 +12,14 @@ const __filename = fileURLToPath(import.meta.url);
12
12
  const __dirname = dirname(__filename);
13
13
  import { runProcess } from './process-runner.js';
14
14
  import { resolvePaneTarget, resolveSessionToPane } from './tmux-injection.js';
15
- import { evaluatePaneInjectionReadiness, sendPaneInput } from './team-tmux-guard.js';
15
+ import {
16
+ EXACT_PANE_UNAVAILABLE_REASON,
17
+ evaluatePaneInjectionReadiness,
18
+ normalizeExactPaneId,
19
+ sendPaneInput,
20
+ verifyExactPaneLive,
21
+ verifyExactPaneOwnerLive,
22
+ } from './team-tmux-guard.js';
16
23
  import {
17
24
  buildCapturePaneArgv,
18
25
  normalizeTmuxCapture,
@@ -456,23 +463,83 @@ async function writeBridgeDispatchCompat(stateDir, teamName, requests) {
456
463
  await writeJsonAtomic(compatPath, { records });
457
464
  }
458
465
 
466
+ function explicitPaneIdentity(value) {
467
+ const rawPaneId = safeString(value).trim();
468
+ return { provided: rawPaneId !== '', paneId: normalizeExactPaneId(rawPaneId), rawPaneId };
469
+ }
470
+
471
+ function positivePanePid(value) {
472
+ return Number.isInteger(value) && Number(value) > 0 ? Number(value) : undefined;
473
+ }
474
+
475
+
476
+ function resolveAddressedWorker(request, config) {
477
+ const workers = Array.isArray(config?.workers) ? config.workers : [];
478
+ if (Number.isFinite(request?.worker_index)) {
479
+ const worker = workers.find((candidate) => Number(candidate?.index) === Number(request.worker_index));
480
+ if (worker) return worker;
481
+ }
482
+ const recipient = safeString(request?.to_worker).trim();
483
+ return workers.find((candidate) => safeString(candidate?.name).trim() === recipient) || null;
484
+ }
459
485
 
460
- function defaultInjectTarget(request, config) {
486
+ function resolveConfiguredPaneIdentity(request, config) {
461
487
  if (request.to_worker === 'leader-fixed') {
462
- const leaderPaneId = resolveLeaderPaneId(config);
463
- if (leaderPaneId) return { type: 'pane', value: leaderPaneId };
464
- return null;
488
+ return { source: 'leader_pane_id', expectedPanePid: positivePanePid(config?.leader_pane_pid), expectedPaneOwnerId: safeString(config?.tmux_pane_owner_id).trim(), ...explicitPaneIdentity(config?.leader_pane_id) };
465
489
  }
466
- if (request.pane_id) return { type: 'pane', value: request.pane_id };
467
- if (typeof request.worker_index === 'number' && Array.isArray(config?.workers)) {
468
- const worker = config.workers.find((candidate) => Number(candidate?.index) === request.worker_index);
469
- if (worker?.pane_id) return { type: 'pane', value: worker.pane_id };
490
+ const worker = resolveAddressedWorker(request, config);
491
+ const expectedPanePid = positivePanePid(worker?.pid);
492
+ return { source: 'worker_pane_id', expectedPanePid, expectedPaneOwnerId: safeString(config?.tmux_pane_owner_id).trim(), ...explicitPaneIdentity(worker?.pane_id) };
493
+ }
494
+
495
+ function resolveDispatchTarget(request, config) {
496
+ const requestPane = explicitPaneIdentity(request?.pane_id);
497
+ const configuredPane = resolveConfiguredPaneIdentity(request, config);
498
+ const hudPaneId = explicitPaneIdentity(config?.hud_pane_id).paneId;
499
+ if (requestPane.provided) {
500
+ if (!requestPane.paneId) {
501
+ return { failure: 'invalid_explicit_pane', paneId: requestPane.paneId, paneSource: 'request_pane_id', rawPaneId: requestPane.rawPaneId };
502
+ }
503
+ if (requestPane.paneId === hudPaneId) {
504
+ return { failure: 'hud_pane_target', paneId: requestPane.paneId, paneSource: 'request_pane_id' };
505
+ }
506
+ if (configuredPane.provided) {
507
+ if (!configuredPane.paneId) {
508
+ return { failure: 'invalid_explicit_pane', paneId: configuredPane.paneId, paneSource: configuredPane.source, rawPaneId: configuredPane.rawPaneId };
509
+ }
510
+ if (requestPane.paneId !== configuredPane.paneId) {
511
+ return {
512
+ failure: 'exact_pane_mismatch',
513
+ paneId: requestPane.paneId,
514
+ paneSource: 'request_pane_id',
515
+ configuredPaneId: configuredPane.paneId,
516
+ };
517
+ }
518
+ }
519
+ if (!positivePanePid(configuredPane.expectedPanePid)) {
520
+ return { failure: 'missing_exact_pane_pid', paneId: requestPane.paneId, paneSource: configuredPane.source };
521
+ }
522
+ if (!configuredPane.expectedPaneOwnerId) {
523
+ return { failure: 'missing_expected_pane_owner', paneId: requestPane.paneId, paneSource: configuredPane.source };
524
+ }
525
+ 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' };
470
526
  }
471
- if (typeof request.worker_index === 'number' && config.tmux_session) {
472
- return { type: 'pane', value: `${config.tmux_session}.${request.worker_index}` };
527
+ if (configuredPane.provided) {
528
+ if (!configuredPane.paneId) {
529
+ return { failure: 'invalid_explicit_pane', paneId: configuredPane.paneId, paneSource: configuredPane.source, rawPaneId: configuredPane.rawPaneId };
530
+ }
531
+ if (configuredPane.paneId === hudPaneId) {
532
+ return { failure: 'hud_pane_target', paneId: configuredPane.paneId, paneSource: configuredPane.source };
533
+ }
534
+ if (!positivePanePid(configuredPane.expectedPanePid)) {
535
+ return { failure: 'missing_exact_pane_pid', paneId: configuredPane.paneId, paneSource: configuredPane.source };
536
+ }
537
+ if (!configuredPane.expectedPaneOwnerId) {
538
+ return { failure: 'missing_expected_pane_owner', paneId: configuredPane.paneId, paneSource: configuredPane.source };
539
+ }
540
+ return { target: { type: 'pane', value: configuredPane.paneId }, exactPaneId: configuredPane.paneId, expectedPanePid: configuredPane.expectedPanePid, expectedPaneOwnerId: configuredPane.expectedPaneOwnerId, expectedHudPaneId: hudPaneId, source: configuredPane.source, reason: configuredPane.source };
473
541
  }
474
- if (config.tmux_session) return { type: 'session', value: config.tmux_session };
475
- return null;
542
+ return { target: null };
476
543
  }
477
544
 
478
545
  async function appendLeaderNotificationDeferredEvent({
@@ -754,13 +821,52 @@ const INJECT_VERIFY_DELAY_MS = 250;
754
821
  const INJECT_VERIFY_ROUNDS = 3;
755
822
 
756
823
  async function injectDispatchRequest(request, config, cwd, stateDir) {
757
- const target = defaultInjectTarget(request, config);
824
+ const dispatchTarget = resolveDispatchTarget(request, config);
825
+ if (dispatchTarget.failure) {
826
+ if (dispatchTarget.failure !== 'exact_pane_mismatch' && dispatchTarget.failure !== 'invalid_explicit_pane') {
827
+ return {
828
+ ok: false,
829
+ reason: dispatchTarget.failure,
830
+ pane: dispatchTarget.paneId || null,
831
+ pane_source: dispatchTarget.paneSource || null,
832
+ exact_pane_proof: null,
833
+ tmux_injection_attempted: false,
834
+ };
835
+ }
836
+ const paneProof = dispatchTarget.failure === 'exact_pane_mismatch'
837
+ ? {
838
+ reason: 'exact_pane_mismatch',
839
+ proof: {
840
+ status: 'unavailable',
841
+ paneId: dispatchTarget.paneId,
842
+ reason: 'pane_mismatch',
843
+ detail: `configured pane is ${dispatchTarget.configuredPaneId}`,
844
+ },
845
+ }
846
+ : await verifyExactPaneLive(dispatchTarget.rawPaneId);
847
+ return {
848
+ ok: false,
849
+ reason: paneProof.reason,
850
+ pane: dispatchTarget.paneId || null,
851
+ pane_source: dispatchTarget.paneSource || null,
852
+ exact_pane_proof: paneProof.proof || null,
853
+ tmux_injection_attempted: false,
854
+ };
855
+ }
856
+
857
+ const target = dispatchTarget.target;
758
858
  if (!target) {
759
859
  return { ok: false, reason: 'missing_tmux_target' };
760
860
  }
761
861
  const leaderTargeted = request.to_worker === 'leader-fixed';
762
862
  let resolution;
763
- if (target.type === 'session') {
863
+ if (dispatchTarget.exactPaneId) {
864
+ resolution = {
865
+ paneTarget: dispatchTarget.exactPaneId,
866
+ reason: dispatchTarget.reason,
867
+ source: dispatchTarget.source,
868
+ };
869
+ } else if (target.type === 'session') {
764
870
  const paneId = await resolveSessionToPane(target.value).catch(() => null);
765
871
  resolution = paneId
766
872
  ? { paneTarget: paneId, reason: 'session_target_resolved' }
@@ -771,13 +877,26 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
771
877
  if (!resolution.paneTarget) {
772
878
  return { ok: false, reason: `target_resolution_failed:${resolution.reason}` };
773
879
  }
774
- const isLeaderMailboxDispatch = request.to_worker === 'leader-fixed';
880
+
881
+ const exactPaneId = dispatchTarget.exactPaneId || normalizeExactPaneId(resolution.paneTarget);
882
+ const exactPaneFailure = (paneProof, tmuxInjectionAttempted) => ({
883
+ ok: false,
884
+ reason: paneProof.reason,
885
+ pane: resolution.paneTarget,
886
+ pane_source: resolution.source || null,
887
+ exact_pane_proof: paneProof.proof || null,
888
+ tmux_injection_attempted: tmuxInjectionAttempted,
889
+ });
775
890
  const paneGuard = await evaluatePaneInjectionReadiness(resolution.paneTarget, {
776
891
  skipIfScrolling: true,
777
892
  requireRunningAgent: leaderTargeted,
778
893
  requireReady: false,
779
894
  requireIdle: false,
780
895
  requireObservableState: leaderTargeted,
896
+ exactPaneId,
897
+ expectedPanePid: dispatchTarget.expectedPanePid,
898
+ expectedPaneOwnerId: dispatchTarget.expectedPaneOwnerId,
899
+ expectedHudPaneId: dispatchTarget.expectedHudPaneId,
781
900
  });
782
901
  if (!paneGuard.ok) {
783
902
  return {
@@ -787,16 +906,26 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
787
906
  pane_source: resolution.source || null,
788
907
  readiness_evidence: paneGuard.readinessEvidence || null,
789
908
  pane_current_command: paneGuard.paneCurrentCommand || null,
909
+ exact_pane_proof: paneGuard.exactPaneProof || null,
790
910
  tmux_injection_attempted: false,
791
911
  };
792
912
  }
793
913
 
914
+ let exactPaneProof = paneGuard.exactPaneProof || null;
915
+ const verifyExplicitPane = async () => {
916
+ const paneProof = await verifyExactPaneOwnerLive(exactPaneId, dispatchTarget.expectedPanePid, dispatchTarget.expectedPaneOwnerId);
917
+ exactPaneProof = paneProof.proof || null;
918
+ return paneProof;
919
+ };
920
+
794
921
  const attemptCountAtStart = Number.isFinite(request.attempt_count)
795
922
  ? Math.max(0, Math.floor(request.attempt_count))
796
923
  : 0;
797
924
  const submitKeyPresses = resolveWorkerCliForRequest(request, config) === 'claude' ? 1 : 2;
798
925
  let preCaptureHasTrigger = false;
799
926
  if (attemptCountAtStart >= 1) {
927
+ const preCaptureProof = await verifyExplicitPane();
928
+ if (!preCaptureProof.ok) return exactPaneFailure(preCaptureProof, false);
800
929
  try {
801
930
  // Narrow capture (8 lines) to scope check to input area, not scrollback output
802
931
  const preCapture = await runProcess('tmux', buildJoinedCapturePaneArgv(resolution.paneTarget, 8), 2000);
@@ -809,16 +938,20 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
809
938
  // Retype whenever trigger text is NOT in the narrow input area, regardless of attempt count.
810
939
  // Pre-0.7.4 bug: 80-line capture matched trigger in scrollback output, falsely skipping retype.
811
940
  const shouldTypePrompt = attemptCountAtStart === 0 || !preCaptureHasTrigger;
812
- if (shouldTypePrompt) {
813
- if (attemptCountAtStart >= 1) {
814
- // Clear stale text in input buffer before retyping (mirrors sync path tmux-session.ts:1270)
815
- await runProcess('tmux', ['send-keys', '-t', resolution.paneTarget, 'C-u'], 1000).catch(() => {});
816
- await new Promise((r) => setTimeout(r, 50));
817
- }
941
+ if (shouldTypePrompt && attemptCountAtStart >= 1) {
942
+ // Clear stale text in input buffer before retyping (mirrors sync path tmux-session.ts:1270)
943
+ const clearProof = await verifyExplicitPane();
944
+ if (!clearProof.ok) return exactPaneFailure(clearProof, false);
945
+ await runProcess('tmux', ['send-keys', '-t', resolution.paneTarget, 'C-u'], 1000).catch(() => {});
946
+ await new Promise((r) => setTimeout(r, 50));
818
947
  }
819
948
 
820
949
  const sendResult = await sendPaneInput({
821
950
  paneTarget: resolution.paneTarget,
951
+ exactPaneId,
952
+ expectedPanePid: dispatchTarget.expectedPanePid,
953
+ expectedPaneOwnerId: dispatchTarget.expectedPaneOwnerId,
954
+ expectedHudPaneId: dispatchTarget.expectedHudPaneId,
822
955
  prompt: request.trigger_message,
823
956
  submitKeyPresses,
824
957
  typePrompt: shouldTypePrompt,
@@ -832,9 +965,11 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
832
965
  pane_source: resolution.source || null,
833
966
  readiness_evidence: paneGuard.readinessEvidence || null,
834
967
  pane_current_command: paneGuard.paneCurrentCommand || null,
968
+ exact_pane_proof: sendResult.exactPaneProof || null,
835
969
  tmux_injection_attempted: true,
836
970
  };
837
971
  }
972
+ exactPaneProof = sendResult.exactPaneProof || exactPaneProof;
838
973
 
839
974
  // Post-injection verification: confirm the trigger text was consumed.
840
975
  // Fixes #391: without this, dispatch marks 'notified' even when the worker
@@ -843,72 +978,102 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
843
978
  const verifyWideArgv = buildJoinedCapturePaneArgv(resolution.paneTarget);
844
979
  for (let round = 0; round < INJECT_VERIFY_ROUNDS; round++) {
845
980
  await new Promise((r) => setTimeout(r, INJECT_VERIFY_DELAY_MS));
981
+ const narrowProof = await verifyExplicitPane();
982
+ if (!narrowProof.ok) return exactPaneFailure(narrowProof, true);
983
+
984
+ let narrowCap;
846
985
  try {
847
986
  // Primary: trigger text no longer in narrow input area.
848
987
  // Secondary guard: also inspect the recent non-empty tail of wide capture.
849
988
  // This avoids false confirmations when Codex leaves the unsent draft just
850
989
  // above a large blank area (narrow capture misses it) while still avoiding
851
990
  // full-scrollback false positives.
852
- const narrowCap = await runProcess('tmux', verifyNarrowArgv, 2000);
853
- const wideCap = await runProcess('tmux', verifyWideArgv, 2000);
854
- const triggerInNarrow = capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message);
855
- const triggerNearTail = capturedPaneContainsTriggerNearTail(wideCap.stdout, request.trigger_message);
856
- if (triggerInNarrow || triggerNearTail) {
857
- // Draft is still visible, so C-m has not actually submitted it yet.
858
- // Do not let transient spinner/active-task text mask an unsent draft.
859
- await sendPaneInput({
860
- paneTarget: resolution.paneTarget,
861
- prompt: request.trigger_message,
862
- submitKeyPresses,
863
- typePrompt: false,
864
- }).catch(() => {});
865
- continue;
866
- }
867
- // Worker is actively processing (mirrors sync path tmux-session.ts:1292-1294)
868
- if (paneHasActiveTask(wideCap.stdout)) {
869
- runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
870
- return {
871
- ok: true,
872
- reason: 'tmux_send_keys_confirmed_active_task',
873
- pane: resolution.paneTarget,
874
- pane_source: resolution.source || null,
875
- readiness_evidence: paneGuard.readinessEvidence || null,
876
- pane_current_command: paneGuard.paneCurrentCommand || null,
877
- tmux_injection_attempted: true,
878
- };
879
- }
880
- // Do not declare success while a pane is not input-ready. Otherwise a
881
- // pre-ready send can be marked "confirmed" and later appear as a stuck
882
- // unsent draft once the UI finishes loading. This includes leader-fixed:
883
- // its Codex UI can show "tab to queue message" while busy, and marking
884
- // delivered before queue/consumption confirmation loses the orchestration
885
- // nudge until a human presses Tab manually.
886
- if (!paneLooksReady(wideCap.stdout)) {
887
- continue;
888
- }
889
- if (!triggerInNarrow && !triggerNearTail) {
890
- runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
891
- return {
892
- ok: true,
893
- reason: 'tmux_send_keys_confirmed',
894
- pane: resolution.paneTarget,
895
- pane_source: resolution.source || null,
896
- readiness_evidence: paneGuard.readinessEvidence || null,
897
- pane_current_command: paneGuard.paneCurrentCommand || null,
898
- tmux_injection_attempted: true,
899
- };
900
- }
991
+ narrowCap = await runProcess('tmux', verifyNarrowArgv, 2000);
901
992
  } catch {
902
- // capture failed; fall through to retry C-m
993
+ // Capture failed; fall through to retry C-m.
994
+ }
995
+ if (narrowCap) {
996
+ const wideProof = await verifyExplicitPane();
997
+ if (!wideProof.ok) return exactPaneFailure(wideProof, true);
998
+ try {
999
+ const wideCap = await runProcess('tmux', verifyWideArgv, 2000);
1000
+ const triggerInNarrow = capturedPaneContainsTrigger(narrowCap.stdout, request.trigger_message);
1001
+ const triggerNearTail = capturedPaneContainsTriggerNearTail(wideCap.stdout, request.trigger_message);
1002
+ if (triggerInNarrow || triggerNearTail) {
1003
+ // Draft is still visible, so C-m has not actually submitted it yet.
1004
+ // Do not let transient spinner/active-task text mask an unsent draft.
1005
+ const retrySend = await sendPaneInput({
1006
+ paneTarget: resolution.paneTarget,
1007
+ exactPaneId,
1008
+ expectedPanePid: dispatchTarget.expectedPanePid,
1009
+ expectedPaneOwnerId: dispatchTarget.expectedPaneOwnerId,
1010
+ expectedHudPaneId: dispatchTarget.expectedHudPaneId,
1011
+ prompt: request.trigger_message,
1012
+ submitKeyPresses,
1013
+ typePrompt: false,
1014
+ });
1015
+ if (!retrySend.ok && retrySend.reason === EXACT_PANE_UNAVAILABLE_REASON) {
1016
+ return exactPaneFailure({ reason: retrySend.reason, proof: retrySend.exactPaneProof }, true);
1017
+ }
1018
+ exactPaneProof = retrySend.exactPaneProof || exactPaneProof;
1019
+ continue;
1020
+ }
1021
+ // Worker is actively processing (mirrors sync path tmux-session.ts:1292-1294)
1022
+ if (paneHasActiveTask(wideCap.stdout)) {
1023
+ runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
1024
+ return {
1025
+ ok: true,
1026
+ reason: 'tmux_send_keys_confirmed_active_task',
1027
+ pane: resolution.paneTarget,
1028
+ pane_source: resolution.source || null,
1029
+ readiness_evidence: paneGuard.readinessEvidence || null,
1030
+ pane_current_command: paneGuard.paneCurrentCommand || null,
1031
+ exact_pane_proof: exactPaneProof,
1032
+ tmux_injection_attempted: true,
1033
+ };
1034
+ }
1035
+ // Do not declare success while a pane is not input-ready. Otherwise a
1036
+ // pre-ready send can be marked "confirmed" and later appear as a stuck
1037
+ // unsent draft once the UI finishes loading. This includes leader-fixed:
1038
+ // its Codex UI can show "tab to queue message" while busy, and marking
1039
+ // delivered before queue/consumption confirmation loses the orchestration
1040
+ // nudge until a human presses Tab manually.
1041
+ if (!paneLooksReady(wideCap.stdout)) {
1042
+ continue;
1043
+ }
1044
+ if (!triggerInNarrow && !triggerNearTail) {
1045
+ runtimeExec({ command: 'MarkDelivered', request_id: request.request_id }, stateDir, request.team_name);
1046
+ return {
1047
+ ok: true,
1048
+ reason: 'tmux_send_keys_confirmed',
1049
+ pane: resolution.paneTarget,
1050
+ pane_source: resolution.source || null,
1051
+ readiness_evidence: paneGuard.readinessEvidence || null,
1052
+ pane_current_command: paneGuard.paneCurrentCommand || null,
1053
+ exact_pane_proof: exactPaneProof,
1054
+ tmux_injection_attempted: true,
1055
+ };
1056
+ }
1057
+ } catch {
1058
+ // Capture failed; fall through to retry C-m.
1059
+ }
903
1060
  }
904
1061
  // Draft still visible and no active task — retry C-m
905
- await sendPaneInput({
1062
+ const retrySend = await sendPaneInput({
906
1063
  paneTarget: resolution.paneTarget,
1064
+ exactPaneId,
1065
+ expectedPanePid: dispatchTarget.expectedPanePid,
1066
+ expectedPaneOwnerId: dispatchTarget.expectedPaneOwnerId,
1067
+ expectedHudPaneId: dispatchTarget.expectedHudPaneId,
907
1068
  prompt: request.trigger_message,
908
1069
  submitKeyPresses,
909
1070
  typePrompt: false,
910
1071
  queueFirstSubmit: leaderTargeted,
911
- }).catch(() => {});
1072
+ });
1073
+ if (!retrySend.ok && retrySend.reason === EXACT_PANE_UNAVAILABLE_REASON) {
1074
+ return exactPaneFailure({ reason: retrySend.reason, proof: retrySend.exactPaneProof }, true);
1075
+ }
1076
+ exactPaneProof = retrySend.exactPaneProof || exactPaneProof;
912
1077
  }
913
1078
 
914
1079
  // Trigger text is still visible after all retry rounds.
@@ -919,6 +1084,7 @@ async function injectDispatchRequest(request, config, cwd, stateDir) {
919
1084
  pane_source: resolution.source || null,
920
1085
  readiness_evidence: paneGuard.readinessEvidence || null,
921
1086
  pane_current_command: paneGuard.paneCurrentCommand || null,
1087
+ exact_pane_proof: exactPaneProof,
922
1088
  tmux_injection_attempted: true,
923
1089
  };
924
1090
  }
@@ -962,6 +1128,7 @@ function buildDispatchAttemptEvidence(result, fallback = {}) {
962
1128
  pane_source: safeString(result?.pane_source || fallback.pane_source || '').trim() || null,
963
1129
  readiness_evidence: safeString(result?.readiness_evidence || fallback.readiness_evidence || '').trim() || null,
964
1130
  pane_current_command: safeString(result?.pane_current_command || fallback.pane_current_command || '').trim() || null,
1131
+ exact_pane_proof: result?.exact_pane_proof ?? fallback.exact_pane_proof ?? null,
965
1132
  tmux_injection_attempted:
966
1133
  typeof result?.tmux_injection_attempted === 'boolean'
967
1134
  ? result.tmux_injection_attempted
@@ -1024,7 +1191,7 @@ export async function drainPendingTeamDispatch({
1024
1191
  continue;
1025
1192
  }
1026
1193
 
1027
- if (request.to_worker === 'leader-fixed' && !resolveLeaderPaneId(config)) {
1194
+ if (request.to_worker === 'leader-fixed' && !resolveLeaderPaneId(config) && !explicitPaneIdentity(request.pane_id).provided) {
1028
1195
  const nowIso = new Date().toISOString();
1029
1196
  const alreadyDeferred = safeString(request.last_reason).trim() === LEADER_PANE_MISSING_DEFERRED_REASON;
1030
1197
  request.updated_at = nowIso;
@@ -11,8 +11,7 @@ import { asNumber, safeString, isTerminalPhase } from './utils.js';
11
11
  import { readJsonIfExists, getScopedStateDirsForCurrentSession } from './state-io.js';
12
12
  import { runProcess } from './process-runner.js';
13
13
  import { logTmuxHookEvent } from './log.js';
14
- import { evaluatePaneInjectionReadiness, queuePaneInput, sendPaneInput } from './team-tmux-guard.js';
15
- import { resolvePaneTarget } from './tmux-injection.js';
14
+ import { evaluatePaneInjectionReadiness, normalizeExactPaneId, sendPaneInput } from './team-tmux-guard.js';
16
15
  import { listNotifyCanonicalActiveTeams } from './active-team.js';
17
16
  import {
18
17
  classifyLeaderActionState,
@@ -28,6 +27,7 @@ import { isDeepInterviewStateActive } from './auto-nudge.js';
28
27
  const LEADER_PANE_MISSING_NO_INJECTION_REASON = 'leader_pane_missing_no_injection';
29
28
  const LEADER_PANE_SHELL_NO_INJECTION_REASON = 'leader_pane_shell_no_injection';
30
29
  const TEAM_SHUTDOWN_NO_INJECTION_REASON = 'team_state_gone_or_shutdown';
30
+ const LEADER_PANE_OWNER_MISSING_NO_INJECTION_REASON = 'leader_pane_owner_missing_no_injection';
31
31
  const LEADER_PANE_SAME_CLASSIFIED_STATE_SUPPRESSED_REASON = 'pane_already_shows_same_classified_state';
32
32
  const LEADER_NOTIFICATION_DEFERRED_TYPE = 'leader_notification_deferred';
33
33
  const ACK_WITHOUT_START_EVIDENCE_REASON = 'ack_without_start_evidence';
@@ -37,6 +37,10 @@ const ACK_LIKE_PATTERNS = [
37
37
  /^(?:on it|will do|i(?:'|')ll do it|working on it)[.!]*$/i,
38
38
  ];
39
39
 
40
+ function positivePanePid(value) {
41
+ return Number.isInteger(value) && Number(value) > 0 ? Number(value) : undefined;
42
+ }
43
+
40
44
  let atomicJsonWriteCounter = 0;
41
45
 
42
46
  // Synchronous test-only callbacks let regression tests emulate filesystem races
@@ -729,6 +733,9 @@ export async function maybeNudgeTeamLeader({
729
733
  let leaderPaneId = '';
730
734
  let ownerSessionId = '';
731
735
  let workers = [];
736
+ let hudPaneId = '';
737
+ let leaderPanePid;
738
+ let tmuxPaneOwnerId = '';
732
739
  try {
733
740
  const manifestPath = join(omxDir, 'state', 'team', teamName, 'manifest.v2.json');
734
741
  const configPath = join(omxDir, 'state', 'team', teamName, 'config.json');
@@ -737,6 +744,9 @@ export async function maybeNudgeTeamLeader({
737
744
  const raw = JSON.parse(await readFile(srcPath, 'utf-8'));
738
745
  tmuxSession = safeString(raw && raw.tmux_session ? raw.tmux_session : '').trim();
739
746
  leaderPaneId = safeString(raw && raw.leader_pane_id ? raw.leader_pane_id : '').trim();
747
+ hudPaneId = safeString(raw && raw.hud_pane_id ? raw.hud_pane_id : '').trim();
748
+ leaderPanePid = positivePanePid(raw && raw.leader_pane_pid);
749
+ tmuxPaneOwnerId = typeof raw?.tmux_pane_owner_id === 'string' ? raw.tmux_pane_owner_id.trim() : '';
740
750
  ownerSessionId = safeString(raw && raw.leader && raw.leader.session_id ? raw.leader.session_id : '').trim();
741
751
  if (Array.isArray(raw && raw.workers)) workers = raw.workers;
742
752
  }
@@ -761,23 +771,20 @@ export async function maybeNudgeTeamLeader({
761
771
  const workerPaneIds = Array.isArray(workers)
762
772
  ? workers.map((w) => safeString(w && w.pane_id ? w.pane_id : '')).filter(Boolean)
763
773
  : [];
764
- const canonicalLeaderPaneId = safeString(leaderPaneId).trim();
774
+ const normalizedLeaderPaneId = normalizeExactPaneId(leaderPaneId);
775
+ const canonicalLeaderPaneId = normalizedLeaderPaneId && normalizedLeaderPaneId !== normalizeExactPaneId(hudPaneId)
776
+ ? normalizedLeaderPaneId
777
+ : '';
765
778
  if (!tmuxSession && !canonicalLeaderPaneId) continue;
766
- let tmuxTarget = canonicalLeaderPaneId;
767
- if (canonicalLeaderPaneId) {
768
- const resolvedLeaderTarget = await resolvePaneTarget(
769
- { type: 'pane', value: canonicalLeaderPaneId },
770
- '',
771
- canonicalLeaderPaneId,
772
- '',
773
- {},
774
- ).catch(() => null);
775
- if (resolvedLeaderTarget?.paneTarget) {
776
- tmuxTarget = safeString(resolvedLeaderTarget.paneTarget).trim();
777
- } else if (resolvedLeaderTarget && ['target_is_hud_pane', 'pane_cwd_mismatch'].includes(safeString(resolvedLeaderTarget.reason).trim())) {
778
- tmuxTarget = '';
779
- }
779
+ if (!tmuxPaneOwnerId) {
780
+ await recordSuppressedLeaderNudge({ logsDir, source, teamName, reason: LEADER_PANE_OWNER_MISSING_NO_INJECTION_REASON });
781
+ continue;
780
782
  }
783
+ if (canonicalLeaderPaneId && !leaderPanePid) {
784
+ await recordSuppressedLeaderNudge({ logsDir, source, teamName, reason: 'leader_pane_pid_missing' });
785
+ continue;
786
+ }
787
+ const tmuxTarget = canonicalLeaderPaneId;
781
788
  const paneStatus = tmuxSession
782
789
  ? await checkWorkerPanesAlive(tmuxSession, workerPaneIds)
783
790
  : { alive: false, paneCount: 0 };
@@ -1087,6 +1094,10 @@ export async function maybeNudgeTeamLeader({
1087
1094
  requireRunningAgent: true,
1088
1095
  requireReady: false,
1089
1096
  requireIdle: false,
1097
+ exactPaneId: canonicalLeaderPaneId,
1098
+ expectedPanePid: leaderPanePid,
1099
+ expectedPaneOwnerId: tmuxPaneOwnerId,
1100
+ expectedHudPaneId: hudPaneId,
1090
1101
  });
1091
1102
  if (!paneGuard.ok) {
1092
1103
  const deferredReason = paneGuard.reason === 'pane_running_shell'
@@ -1188,9 +1199,16 @@ export async function maybeNudgeTeamLeader({
1188
1199
  const leaderHasActiveTask = paneHasActiveTask(paneGuard.paneCapture);
1189
1200
  let deliveryMode = 'sent';
1190
1201
  if (leaderHasActiveTask) {
1191
- const sendResult = await queuePaneInput({
1202
+ const sendResult = await sendPaneInput({
1192
1203
  paneTarget: tmuxTarget,
1193
1204
  prompt: markedText,
1205
+ submitKeyPresses: 1,
1206
+ submitDelayMs: 80,
1207
+ queueFirstSubmit: true,
1208
+ exactPaneId: canonicalLeaderPaneId,
1209
+ expectedPanePid: leaderPanePid,
1210
+ expectedPaneOwnerId: tmuxPaneOwnerId,
1211
+ expectedHudPaneId: hudPaneId,
1194
1212
  });
1195
1213
  if (!sendResult.ok) {
1196
1214
  throw new Error(sendResult.error || sendResult.reason);
@@ -1202,6 +1220,10 @@ export async function maybeNudgeTeamLeader({
1202
1220
  prompt: markedText,
1203
1221
  submitKeyPresses: 2,
1204
1222
  submitDelayMs: 100,
1223
+ exactPaneId: canonicalLeaderPaneId,
1224
+ expectedPanePid: leaderPanePid,
1225
+ expectedPaneOwnerId: tmuxPaneOwnerId,
1226
+ expectedHudPaneId: hudPaneId,
1205
1227
  });
1206
1228
  if (!sendResult.ok) {
1207
1229
  throw new Error(sendResult.error || sendResult.reason);