oh-my-codex 0.20.1 → 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 (791) hide show
  1. package/Cargo.lock +7 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +36 -1
  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/adapt/__tests__/hermes.test.js.map +1 -1
  11. package/dist/adapt/contracts.d.ts.map +1 -1
  12. package/dist/agents/__tests__/definitions.test.js +81 -0
  13. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  14. package/dist/agents/__tests__/native-config.test.js +25 -0
  15. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  16. package/dist/agents/native-config.d.ts +2 -1
  17. package/dist/agents/native-config.d.ts.map +1 -1
  18. package/dist/agents/native-config.js.map +1 -1
  19. package/dist/auth/__tests__/hotswap.test.d.ts +2 -0
  20. package/dist/auth/__tests__/hotswap.test.d.ts.map +1 -0
  21. package/dist/auth/__tests__/hotswap.test.js +183 -0
  22. package/dist/auth/__tests__/hotswap.test.js.map +1 -0
  23. package/dist/auth/hotswap.d.ts +6 -0
  24. package/dist/auth/hotswap.d.ts.map +1 -1
  25. package/dist/auth/hotswap.js +102 -19
  26. package/dist/auth/hotswap.js.map +1 -1
  27. package/dist/auth/paths.js.map +1 -1
  28. package/dist/auth/storage.js.map +1 -1
  29. package/dist/autopilot/__tests__/ralplan-gate.test.js +5 -5
  30. package/dist/autopilot/__tests__/ralplan-gate.test.js.map +1 -1
  31. package/dist/autopilot/completion-gate.js.map +1 -1
  32. package/dist/autopilot/fsm.d.ts +1 -1
  33. package/dist/autopilot/fsm.d.ts.map +1 -1
  34. package/dist/autoresearch/goal.js.map +1 -1
  35. package/dist/autoresearch/runtime.js.map +1 -1
  36. package/dist/capabilities/lockfile.js.map +1 -1
  37. package/dist/catalog/reader.js.map +1 -1
  38. package/dist/catalog/skill-mirror.js.map +1 -1
  39. package/dist/cli/__tests__/adapt-help.test.js.map +1 -1
  40. package/dist/cli/__tests__/agents-init.test.js.map +1 -1
  41. package/dist/cli/__tests__/agents.test.js.map +1 -1
  42. package/dist/cli/__tests__/api.test.js.map +1 -1
  43. package/dist/cli/__tests__/ask.test.js.map +1 -1
  44. package/dist/cli/__tests__/auth.test.js +60 -6
  45. package/dist/cli/__tests__/auth.test.js.map +1 -1
  46. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  47. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  48. package/dist/cli/__tests__/capabilities.test.js.map +1 -1
  49. package/dist/cli/__tests__/codex-plugin-layout.test.js +80 -0
  50. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  51. package/dist/cli/__tests__/doctor-context-window-warning.test.js.map +1 -1
  52. package/dist/cli/__tests__/doctor-invalid-config.test.js +47 -4
  53. package/dist/cli/__tests__/doctor-invalid-config.test.js.map +1 -1
  54. package/dist/cli/__tests__/doctor-team.test.js.map +1 -1
  55. package/dist/cli/__tests__/doctor-warning-copy.test.js +752 -15
  56. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  57. package/dist/cli/__tests__/exec.test.js +136 -0
  58. package/dist/cli/__tests__/exec.test.js.map +1 -1
  59. package/dist/cli/__tests__/imagegen-continuation.test.js.map +1 -1
  60. package/dist/cli/__tests__/index.test.js +473 -14
  61. package/dist/cli/__tests__/index.test.js.map +1 -1
  62. package/dist/cli/__tests__/launch-fallback.test.js +336 -4
  63. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  64. package/dist/cli/__tests__/mcp-parity.test.js +87 -12
  65. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -1
  66. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts +2 -0
  67. package/dist/cli/__tests__/native-hook-claim-journal.test.d.ts.map +1 -0
  68. package/dist/cli/__tests__/native-hook-claim-journal.test.js +206 -0
  69. package/dist/cli/__tests__/native-hook-claim-journal.test.js.map +1 -0
  70. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  71. package/dist/cli/__tests__/question.test.js.map +1 -1
  72. package/dist/cli/__tests__/ralph-deslop-contract.test.js.map +1 -1
  73. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +6 -0
  74. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  75. package/dist/cli/__tests__/ralph-prd-deep-interview.test.js.map +1 -1
  76. package/dist/cli/__tests__/ralph-prd-smoke.test.js.map +1 -1
  77. package/dist/cli/__tests__/ralph.test.js +8 -3
  78. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  79. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts +2 -0
  80. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.d.ts.map +1 -0
  81. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js +72 -0
  82. package/dist/cli/__tests__/ralplan-bootstrap-3181.test.js.map +1 -0
  83. package/dist/cli/__tests__/ralplan.test.d.ts +2 -0
  84. package/dist/cli/__tests__/ralplan.test.d.ts.map +1 -0
  85. package/dist/cli/__tests__/ralplan.test.js +55 -0
  86. package/dist/cli/__tests__/ralplan.test.js.map +1 -0
  87. package/dist/cli/__tests__/resume.test.js +17 -11
  88. package/dist/cli/__tests__/resume.test.js.map +1 -1
  89. package/dist/cli/__tests__/session-scoped-runtime.test.js +25 -0
  90. package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
  91. package/dist/cli/__tests__/session-search-help.test.js +25 -1
  92. package/dist/cli/__tests__/session-search-help.test.js.map +1 -1
  93. package/dist/cli/__tests__/session-search.test.js.map +1 -1
  94. package/dist/cli/__tests__/setup-agents-overwrite.test.js +79 -0
  95. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  96. package/dist/cli/__tests__/setup-gh-star.test.js.map +1 -1
  97. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +77 -3
  98. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  99. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts +2 -0
  100. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.d.ts.map +1 -0
  101. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js +285 -0
  102. package/dist/cli/__tests__/setup-hooks-trust-e2e.test.js.map +1 -0
  103. package/dist/cli/__tests__/setup-install-mode.test.js +2579 -294
  104. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  105. package/dist/cli/__tests__/setup-preferences.test.d.ts +2 -0
  106. package/dist/cli/__tests__/setup-preferences.test.d.ts.map +1 -0
  107. package/dist/cli/__tests__/setup-preferences.test.js +79 -0
  108. package/dist/cli/__tests__/setup-preferences.test.js.map +1 -0
  109. package/dist/cli/__tests__/setup-refresh.test.js +33 -2
  110. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  111. package/dist/cli/__tests__/setup-scope.test.js +46 -3
  112. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  113. package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
  114. package/dist/cli/__tests__/state.test.js +37 -0
  115. package/dist/cli/__tests__/state.test.js.map +1 -1
  116. package/dist/cli/__tests__/team.test.js +105 -7
  117. package/dist/cli/__tests__/team.test.js.map +1 -1
  118. package/dist/cli/__tests__/ultragoal.test.js +61 -1
  119. package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
  120. package/dist/cli/__tests__/uninstall.test.js +1817 -24
  121. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  122. package/dist/cli/__tests__/update.test.js +58 -0
  123. package/dist/cli/__tests__/update.test.js.map +1 -1
  124. package/dist/cli/__tests__/url.test.js.map +1 -1
  125. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +2 -1
  126. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  127. package/dist/cli/adapt.js.map +1 -1
  128. package/dist/cli/agents-init.js.map +1 -1
  129. package/dist/cli/agents.js.map +1 -1
  130. package/dist/cli/api.js.map +1 -1
  131. package/dist/cli/ask.d.ts +1 -1
  132. package/dist/cli/ask.d.ts.map +1 -1
  133. package/dist/cli/ask.js.map +1 -1
  134. package/dist/cli/auth.js.map +1 -1
  135. package/dist/cli/autoresearch-guided.js.map +1 -1
  136. package/dist/cli/autoresearch-intake.js.map +1 -1
  137. package/dist/cli/capabilities.js.map +1 -1
  138. package/dist/cli/cleanup.js.map +1 -1
  139. package/dist/cli/codex-feature-probe.js.map +1 -1
  140. package/dist/cli/codex-home.js.map +1 -1
  141. package/dist/cli/doctor.d.ts +21 -0
  142. package/dist/cli/doctor.d.ts.map +1 -1
  143. package/dist/cli/doctor.js +607 -130
  144. package/dist/cli/doctor.js.map +1 -1
  145. package/dist/cli/explore.js.map +1 -1
  146. package/dist/cli/index.d.ts +14 -3
  147. package/dist/cli/index.d.ts.map +1 -1
  148. package/dist/cli/index.js +245 -57
  149. package/dist/cli/index.js.map +1 -1
  150. package/dist/cli/mcp-parity.js +2 -2
  151. package/dist/cli/mcp-parity.js.map +1 -1
  152. package/dist/cli/mcp-serve.js.map +1 -1
  153. package/dist/cli/mission.js.map +1 -1
  154. package/dist/cli/native-assets.js.map +1 -1
  155. package/dist/cli/native-hook-claim-journal.d.ts +29 -0
  156. package/dist/cli/native-hook-claim-journal.d.ts.map +1 -0
  157. package/dist/cli/native-hook-claim-journal.js +252 -0
  158. package/dist/cli/native-hook-claim-journal.js.map +1 -0
  159. package/dist/cli/omx.js.map +1 -1
  160. package/dist/cli/plugin-marketplace.js.map +1 -1
  161. package/dist/cli/project-runtime-codex-homes.js.map +1 -1
  162. package/dist/cli/question.js.map +1 -1
  163. package/dist/cli/ralph.d.ts.map +1 -1
  164. package/dist/cli/ralph.js +3 -2
  165. package/dist/cli/ralph.js.map +1 -1
  166. package/dist/cli/ralplan.d.ts +10 -0
  167. package/dist/cli/ralplan.d.ts.map +1 -0
  168. package/dist/cli/ralplan.js +110 -0
  169. package/dist/cli/ralplan.js.map +1 -0
  170. package/dist/cli/session-search.d.ts +5 -0
  171. package/dist/cli/session-search.d.ts.map +1 -1
  172. package/dist/cli/session-search.js +87 -0
  173. package/dist/cli/session-search.js.map +1 -1
  174. package/dist/cli/setup-preferences.d.ts +11 -0
  175. package/dist/cli/setup-preferences.d.ts.map +1 -1
  176. package/dist/cli/setup-preferences.js +32 -0
  177. package/dist/cli/setup-preferences.js.map +1 -1
  178. package/dist/cli/setup.d.ts +58 -0
  179. package/dist/cli/setup.d.ts.map +1 -1
  180. package/dist/cli/setup.js +1450 -398
  181. package/dist/cli/setup.js.map +1 -1
  182. package/dist/cli/sparkshell.js.map +1 -1
  183. package/dist/cli/star-prompt.js.map +1 -1
  184. package/dist/cli/state.d.ts.map +1 -1
  185. package/dist/cli/state.js +2 -1
  186. package/dist/cli/state.js.map +1 -1
  187. package/dist/cli/team.js.map +1 -1
  188. package/dist/cli/ultragoal.d.ts +5 -1
  189. package/dist/cli/ultragoal.d.ts.map +1 -1
  190. package/dist/cli/ultragoal.js +34 -2
  191. package/dist/cli/ultragoal.js.map +1 -1
  192. package/dist/cli/uninstall.d.ts +17 -0
  193. package/dist/cli/uninstall.d.ts.map +1 -1
  194. package/dist/cli/uninstall.js +986 -95
  195. package/dist/cli/uninstall.js.map +1 -1
  196. package/dist/cli/update.d.ts.map +1 -1
  197. package/dist/cli/update.js +8 -1
  198. package/dist/cli/update.js.map +1 -1
  199. package/dist/cli/url.js.map +1 -1
  200. package/dist/compat/__tests__/doctor-contract.test.js +5 -1
  201. package/dist/compat/__tests__/doctor-contract.test.js.map +1 -1
  202. package/dist/compat/__tests__/rust-runtime-compat.test.js +1 -1
  203. package/dist/compat/__tests__/rust-runtime-compat.test.js.map +1 -1
  204. package/dist/config/__tests__/codex-hooks.test.js +1287 -11
  205. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  206. package/dist/config/__tests__/generator-idempotent.test.js +1179 -32
  207. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  208. package/dist/config/__tests__/generator-notify.test.js +6 -1
  209. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  210. package/dist/config/__tests__/models.test.js +67 -3
  211. package/dist/config/__tests__/models.test.js.map +1 -1
  212. package/dist/config/codex-feature-flags.d.ts.map +1 -1
  213. package/dist/config/codex-feature-flags.js.map +1 -1
  214. package/dist/config/codex-hooks.d.ts +124 -1
  215. package/dist/config/codex-hooks.d.ts.map +1 -1
  216. package/dist/config/codex-hooks.js +2142 -265
  217. package/dist/config/codex-hooks.js.map +1 -1
  218. package/dist/config/commit-lore-guard.js.map +1 -1
  219. package/dist/config/generator.d.ts +23 -34
  220. package/dist/config/generator.d.ts.map +1 -1
  221. package/dist/config/generator.js +1480 -246
  222. package/dist/config/generator.js.map +1 -1
  223. package/dist/config/mcp-registry.js.map +1 -1
  224. package/dist/config/models.d.ts +14 -4
  225. package/dist/config/models.d.ts.map +1 -1
  226. package/dist/config/models.js +15 -1
  227. package/dist/config/models.js.map +1 -1
  228. package/dist/config/omx-first-party-mcp.js.map +1 -1
  229. package/dist/config/team-mode.d.ts.map +1 -1
  230. package/dist/document-refresh/__tests__/enforcer.test.js.map +1 -1
  231. package/dist/document-refresh/enforcer.js.map +1 -1
  232. package/dist/exec/followup.js.map +1 -1
  233. package/dist/goal-workflows/artifacts.js.map +1 -1
  234. package/dist/hooks/__tests__/agents-overlay.test.js +7 -4
  235. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  236. package/dist/hooks/__tests__/analyze-routing-contract.test.js.map +1 -1
  237. package/dist/hooks/__tests__/analyze-skill-contract.test.js.map +1 -1
  238. package/dist/hooks/__tests__/anti-slop-workflow.test.js.map +1 -1
  239. package/dist/hooks/__tests__/autopilot-skill-contract.test.js +9 -3
  240. package/dist/hooks/__tests__/autopilot-skill-contract.test.js.map +1 -1
  241. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  242. package/dist/hooks/__tests__/consensus-execution-handoff.test.js.map +1 -1
  243. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  244. package/dist/hooks/__tests__/design-skill.test.js.map +1 -1
  245. package/dist/hooks/__tests__/keyword-detector.test.js +1376 -6
  246. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  247. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +252 -543
  248. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  249. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +38 -4
  250. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  251. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +765 -10
  252. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  253. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js +2 -0
  254. package/dist/hooks/__tests__/notify-hook-cross-worktree-heartbeat.test.js.map +1 -1
  255. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +142 -1
  256. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -1
  257. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  258. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js +48 -2
  259. package/dist/hooks/__tests__/notify-hook-non-omx-guard.test.js.map +1 -1
  260. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +61 -44
  261. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  262. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  263. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +3 -2
  264. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -1
  265. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +243 -4
  266. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  267. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +531 -12
  268. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  269. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +163 -24
  270. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  271. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +482 -32
  272. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  273. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +68 -1
  274. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  275. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +7 -1
  276. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  277. package/dist/hooks/__tests__/notify-hook-visual-verdict.test.js.map +1 -1
  278. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +43 -4
  279. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  280. package/dist/hooks/__tests__/openclaw-setup-contract.test.js.map +1 -1
  281. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  282. package/dist/hooks/__tests__/prometheus-strict-contract.test.js.map +1 -1
  283. package/dist/hooks/__tests__/prompt-guidance-fragments.test.js.map +1 -1
  284. package/dist/hooks/__tests__/prompt-guidance-test-helpers.js.map +1 -1
  285. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts +2 -0
  286. package/dist/hooks/__tests__/prompt-session-provenance.test.d.ts.map +1 -0
  287. package/dist/hooks/__tests__/prompt-session-provenance.test.js +82 -0
  288. package/dist/hooks/__tests__/prompt-session-provenance.test.js.map +1 -0
  289. package/dist/hooks/__tests__/session.test.js +878 -11
  290. package/dist/hooks/__tests__/session.test.js.map +1 -1
  291. package/dist/hooks/__tests__/skill-catalog-hygiene.test.js.map +1 -1
  292. package/dist/hooks/__tests__/team-runtime-gating-docs-contract.test.js.map +1 -1
  293. package/dist/hooks/__tests__/visual-ralph-skill.test.js.map +1 -1
  294. package/dist/hooks/__tests__/visual-verdict-loop.test.js.map +1 -1
  295. package/dist/hooks/agents-overlay.js +5 -5
  296. package/dist/hooks/agents-overlay.js.map +1 -1
  297. package/dist/hooks/code-simplifier/index.js.map +1 -1
  298. package/dist/hooks/explore-routing.js.map +1 -1
  299. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  300. package/dist/hooks/extensibility/events.js.map +1 -1
  301. package/dist/hooks/extensibility/loader.js.map +1 -1
  302. package/dist/hooks/extensibility/plugin-runner-stdin.js.map +1 -1
  303. package/dist/hooks/extensibility/sdk/logging.js.map +1 -1
  304. package/dist/hooks/keyword-detector.d.ts +33 -7
  305. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  306. package/dist/hooks/keyword-detector.js +2228 -119
  307. package/dist/hooks/keyword-detector.js.map +1 -1
  308. package/dist/hooks/keyword-registry.d.ts +12 -0
  309. package/dist/hooks/keyword-registry.d.ts.map +1 -1
  310. package/dist/hooks/keyword-registry.js +41 -0
  311. package/dist/hooks/keyword-registry.js.map +1 -1
  312. package/dist/hooks/prompt-session-provenance.d.ts +87 -0
  313. package/dist/hooks/prompt-session-provenance.d.ts.map +1 -0
  314. package/dist/hooks/prompt-session-provenance.js +180 -0
  315. package/dist/hooks/prompt-session-provenance.js.map +1 -0
  316. package/dist/hooks/session.d.ts +163 -34
  317. package/dist/hooks/session.d.ts.map +1 -1
  318. package/dist/hooks/session.js +1395 -317
  319. package/dist/hooks/session.js.map +1 -1
  320. package/dist/hooks/task-size-detector.js.map +1 -1
  321. package/dist/hud/__tests__/state.test.js.map +1 -1
  322. package/dist/hud/authority.js.map +1 -1
  323. package/dist/hud/constants.js.map +1 -1
  324. package/dist/hud/index.js.map +1 -1
  325. package/dist/hud/reconcile.js.map +1 -1
  326. package/dist/hud/render.js.map +1 -1
  327. package/dist/hud/state.js.map +1 -1
  328. package/dist/hud/tmux.js.map +1 -1
  329. package/dist/leader/__tests__/contract.test.js +153 -1
  330. package/dist/leader/__tests__/contract.test.js.map +1 -1
  331. package/dist/leader/contract.d.ts +42 -5
  332. package/dist/leader/contract.d.ts.map +1 -1
  333. package/dist/leader/contract.js +184 -3
  334. package/dist/leader/contract.js.map +1 -1
  335. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  336. package/dist/mcp/__tests__/code-intel-server.test.js +31 -3
  337. package/dist/mcp/__tests__/code-intel-server.test.js.map +1 -1
  338. package/dist/mcp/__tests__/server-lifecycle.test.js +15 -2
  339. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  340. package/dist/mcp/__tests__/state-paths.test.js +98 -1
  341. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  342. package/dist/mcp/__tests__/wiki-server.test.js +18 -0
  343. package/dist/mcp/__tests__/wiki-server.test.js.map +1 -1
  344. package/dist/mcp/bootstrap.js.map +1 -1
  345. package/dist/mcp/code-intel-server.d.ts +58 -58
  346. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  347. package/dist/mcp/code-intel-server.js +13 -3
  348. package/dist/mcp/code-intel-server.js.map +1 -1
  349. package/dist/mcp/hermes-bridge.js.map +1 -1
  350. package/dist/mcp/hermes-server.d.ts +70 -70
  351. package/dist/mcp/hermes-server.d.ts.map +1 -1
  352. package/dist/mcp/lifecycle-telemetry.js.map +1 -1
  353. package/dist/mcp/memory-server.d.ts +34 -34
  354. package/dist/mcp/memory-server.d.ts.map +1 -1
  355. package/dist/mcp/memory-server.js.map +1 -1
  356. package/dist/mcp/state-paths.d.ts +14 -0
  357. package/dist/mcp/state-paths.d.ts.map +1 -1
  358. package/dist/mcp/state-paths.js +96 -20
  359. package/dist/mcp/state-paths.js.map +1 -1
  360. package/dist/mcp/state-server.d.ts +47 -47
  361. package/dist/mcp/state-server.d.ts.map +1 -1
  362. package/dist/mcp/trace-server.d.ts +4 -4
  363. package/dist/mcp/trace-server.d.ts.map +1 -1
  364. package/dist/mcp/trace-server.js.map +1 -1
  365. package/dist/mcp/wiki-server.d.ts +34 -23
  366. package/dist/mcp/wiki-server.d.ts.map +1 -1
  367. package/dist/mcp/wiki-server.js +9 -2
  368. package/dist/mcp/wiki-server.js.map +1 -1
  369. package/dist/modes/__tests__/base-session-scope.test.js +53 -1
  370. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  371. package/dist/modes/__tests__/base-tmux-pane.test.js +4 -4
  372. package/dist/modes/__tests__/base-tmux-pane.test.js.map +1 -1
  373. package/dist/modes/base.d.ts.map +1 -1
  374. package/dist/modes/base.js +5 -5
  375. package/dist/modes/base.js.map +1 -1
  376. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  377. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  378. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js +177 -85
  379. package/dist/notifications/__tests__/session-idle-tail-dedupe.test.js.map +1 -1
  380. package/dist/notifications/__tests__/session-registry.test.js.map +1 -1
  381. package/dist/notifications/__tests__/template-engine.test.js.map +1 -1
  382. package/dist/notifications/__tests__/verbosity.test.js.map +1 -1
  383. package/dist/notifications/http-client.js.map +1 -1
  384. package/dist/notifications/index.d.ts +4 -1
  385. package/dist/notifications/index.d.ts.map +1 -1
  386. package/dist/notifications/index.js +3 -9
  387. package/dist/notifications/index.js.map +1 -1
  388. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  389. package/dist/notifications/reply-listener.d.ts.map +1 -1
  390. package/dist/notifications/reply-listener.js.map +1 -1
  391. package/dist/notifications/session-registry.js.map +1 -1
  392. package/dist/notifications/session-status.js.map +1 -1
  393. package/dist/notifications/temp-contract.d.ts.map +1 -1
  394. package/dist/notifications/temp-contract.js +4 -0
  395. package/dist/notifications/temp-contract.js.map +1 -1
  396. package/dist/notifications/tmux-detector.js.map +1 -1
  397. package/dist/notifications/tmux.js.map +1 -1
  398. package/dist/openclaw/config.js.map +1 -1
  399. package/dist/openclaw/dispatcher.js.map +1 -1
  400. package/dist/pipeline/__tests__/orchestrator.test.js +4 -4
  401. package/dist/pipeline/__tests__/orchestrator.test.js.map +1 -1
  402. package/dist/pipeline/__tests__/stages.test.js +16 -13
  403. package/dist/pipeline/__tests__/stages.test.js.map +1 -1
  404. package/dist/pipeline/stages/code-review.js.map +1 -1
  405. package/dist/pipeline/stages/ralph-verify.js.map +1 -1
  406. package/dist/pipeline/stages/ralplan.js.map +1 -1
  407. package/dist/pipeline/stages/team-exec.js.map +1 -1
  408. package/dist/pipeline/stages/ultragoal.js.map +1 -1
  409. package/dist/pipeline/stages/ultraqa.js.map +1 -1
  410. package/dist/planning/__tests__/approved-execution-lifecycle-matrix.test.js.map +1 -1
  411. package/dist/planning/artifact-names.js.map +1 -1
  412. package/dist/planning/artifacts.js.map +1 -1
  413. package/dist/question/__tests__/state.test.js.map +1 -1
  414. package/dist/question/__tests__/ui.test.js.map +1 -1
  415. package/dist/question/autopilot-wait.js.map +1 -1
  416. package/dist/question/client.d.ts.map +1 -1
  417. package/dist/question/client.js.map +1 -1
  418. package/dist/question/deep-interview.js.map +1 -1
  419. package/dist/question/events.js.map +1 -1
  420. package/dist/question/renderer.js.map +1 -1
  421. package/dist/question/state.d.ts.map +1 -1
  422. package/dist/question/state.js.map +1 -1
  423. package/dist/question/ui.js.map +1 -1
  424. package/dist/ralph/contract.d.ts +1 -1
  425. package/dist/ralph/contract.d.ts.map +1 -1
  426. package/dist/ralplan/__tests__/consensus-gate.test.js +527 -8
  427. package/dist/ralplan/__tests__/consensus-gate.test.js.map +1 -1
  428. package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts +2 -0
  429. package/dist/ralplan/__tests__/documented-leader-preflight.test.d.ts.map +1 -0
  430. package/dist/ralplan/__tests__/documented-leader-preflight.test.js +46 -0
  431. package/dist/ralplan/__tests__/documented-leader-preflight.test.js.map +1 -0
  432. package/dist/ralplan/__tests__/runtime.test.js +116 -14
  433. package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
  434. package/dist/ralplan/consensus-gate.d.ts +3 -3
  435. package/dist/ralplan/consensus-gate.d.ts.map +1 -1
  436. package/dist/ralplan/consensus-gate.js +173 -70
  437. package/dist/ralplan/consensus-gate.js.map +1 -1
  438. package/dist/ralplan/documented-leader-preflight.d.ts +34 -0
  439. package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -0
  440. package/dist/ralplan/documented-leader-preflight.js +58 -0
  441. package/dist/ralplan/documented-leader-preflight.js.map +1 -0
  442. package/dist/ralplan/runtime.d.ts +3 -2
  443. package/dist/ralplan/runtime.d.ts.map +1 -1
  444. package/dist/ralplan/runtime.js +12 -9
  445. package/dist/ralplan/runtime.js.map +1 -1
  446. package/dist/runtime/__tests__/bridge.test.js +42 -1
  447. package/dist/runtime/__tests__/bridge.test.js.map +1 -1
  448. package/dist/runtime/bridge.d.ts +12 -0
  449. package/dist/runtime/bridge.d.ts.map +1 -1
  450. package/dist/runtime/bridge.js +52 -1
  451. package/dist/runtime/bridge.js.map +1 -1
  452. package/dist/runtime/process-tree.js.map +1 -1
  453. package/dist/runtime/run-loop.js.map +1 -1
  454. package/dist/runtime/run-outcome.d.ts +3 -3
  455. package/dist/runtime/run-outcome.d.ts.map +1 -1
  456. package/dist/runtime/run-outcome.js.map +1 -1
  457. package/dist/runtime/run-state.js.map +1 -1
  458. package/dist/scripts/__tests__/codex-native-hook.test.js +6657 -995
  459. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  460. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts +2 -0
  461. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.d.ts.map +1 -0
  462. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js +634 -0
  463. package/dist/scripts/__tests__/fixtures/issue-3138-installed-smoke.js.map +1 -0
  464. package/dist/scripts/__tests__/generate-release-body.test.js.map +1 -1
  465. package/dist/scripts/__tests__/hook-derived-watcher.test.js.map +1 -1
  466. package/dist/scripts/__tests__/notify-dispatcher.test.js.map +1 -1
  467. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts +2 -0
  468. package/dist/scripts/__tests__/notify-fallback-delivery.test.d.ts.map +1 -0
  469. package/dist/scripts/__tests__/notify-fallback-delivery.test.js +174 -0
  470. package/dist/scripts/__tests__/notify-fallback-delivery.test.js.map +1 -0
  471. package/dist/scripts/__tests__/notify-state-io.test.js +57 -1
  472. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  473. package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts +2 -0
  474. package/dist/scripts/__tests__/reasoning-artifact-contract.test.d.ts.map +1 -0
  475. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js +108 -0
  476. package/dist/scripts/__tests__/reasoning-artifact-contract.test.js.map +1 -0
  477. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts +2 -0
  478. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.d.ts.map +1 -0
  479. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js +78 -0
  480. package/dist/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.js.map +1 -0
  481. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts +2 -0
  482. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.d.ts.map +1 -0
  483. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js +55 -0
  484. package/dist/scripts/__tests__/role-intent-durable-recovery-3181.test.js.map +1 -0
  485. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  486. package/dist/scripts/__tests__/smoke-packed-install.test.js +1100 -4
  487. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  488. package/dist/scripts/__tests__/test-reply-listener-live.test.js.map +1 -1
  489. package/dist/scripts/build-api.js.map +1 -1
  490. package/dist/scripts/build-explore-harness.js.map +1 -1
  491. package/dist/scripts/build-sparkshell.js.map +1 -1
  492. package/dist/scripts/cleanup-explore-harness.js.map +1 -1
  493. package/dist/scripts/codex-execution-surface.js.map +1 -1
  494. package/dist/scripts/codex-native-hook.d.ts +4 -1
  495. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  496. package/dist/scripts/codex-native-hook.js +11577 -1184
  497. package/dist/scripts/codex-native-hook.js.map +1 -1
  498. package/dist/scripts/generate-native-release-manifest.js.map +1 -1
  499. package/dist/scripts/generate-release-body.js.map +1 -1
  500. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  501. package/dist/scripts/hook-payload-guard.js.map +1 -1
  502. package/dist/scripts/notify-fallback-delivery.d.ts +48 -0
  503. package/dist/scripts/notify-fallback-delivery.d.ts.map +1 -0
  504. package/dist/scripts/notify-fallback-delivery.js +419 -0
  505. package/dist/scripts/notify-fallback-delivery.js.map +1 -0
  506. package/dist/scripts/notify-fallback-watcher.js +218 -138
  507. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  508. package/dist/scripts/notify-hook/auto-nudge.d.ts +18 -9
  509. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  510. package/dist/scripts/notify-hook/auto-nudge.js +112 -15
  511. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  512. package/dist/scripts/notify-hook/managed-tmux.d.ts +23 -0
  513. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  514. package/dist/scripts/notify-hook/managed-tmux.js +122 -12
  515. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  516. package/dist/scripts/notify-hook/orchestration-intent.d.ts +7 -7
  517. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -1
  518. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +5 -2
  519. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  520. package/dist/scripts/notify-hook/ralph-session-resume.js +33 -37
  521. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  522. package/dist/scripts/notify-hook/state-io.d.ts +17 -0
  523. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  524. package/dist/scripts/notify-hook/state-io.js +73 -11
  525. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  526. package/dist/scripts/notify-hook/team-dispatch.d.ts +10 -1
  527. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  528. package/dist/scripts/notify-hook/team-dispatch.js +234 -79
  529. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  530. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -2
  531. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  532. package/dist/scripts/notify-hook/team-leader-nudge.js +39 -13
  533. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  534. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +16 -8
  535. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  536. package/dist/scripts/notify-hook/team-tmux-guard.js +365 -44
  537. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  538. package/dist/scripts/notify-hook/team-worker-posttooluse.js.map +1 -1
  539. package/dist/scripts/notify-hook/team-worker-stop.d.ts +1 -1
  540. package/dist/scripts/notify-hook/team-worker-stop.d.ts.map +1 -1
  541. package/dist/scripts/notify-hook/team-worker-stop.js +19 -18
  542. package/dist/scripts/notify-hook/team-worker-stop.js.map +1 -1
  543. package/dist/scripts/notify-hook/team-worker.d.ts +5 -2
  544. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  545. package/dist/scripts/notify-hook/team-worker.js +37 -28
  546. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  547. package/dist/scripts/notify-hook/tmux-injection.d.ts +8 -1
  548. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  549. package/dist/scripts/notify-hook/tmux-injection.js +53 -18
  550. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  551. package/dist/scripts/notify-hook/visual-verdict.d.ts +1 -1
  552. package/dist/scripts/notify-hook/visual-verdict.d.ts.map +1 -1
  553. package/dist/scripts/notify-hook/visual-verdict.js +15 -13
  554. package/dist/scripts/notify-hook/visual-verdict.js.map +1 -1
  555. package/dist/scripts/notify-hook.d.ts +24 -1
  556. package/dist/scripts/notify-hook.d.ts.map +1 -1
  557. package/dist/scripts/notify-hook.js +267 -90
  558. package/dist/scripts/notify-hook.js.map +1 -1
  559. package/dist/scripts/postinstall.js.map +1 -1
  560. package/dist/scripts/prompt-inventory.js.map +1 -1
  561. package/dist/scripts/smoke-packed-install.d.ts +927 -5
  562. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  563. package/dist/scripts/smoke-packed-install.js +4317 -13
  564. package/dist/scripts/smoke-packed-install.js.map +1 -1
  565. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  566. package/dist/scripts/test-reply-listener-live.js.map +1 -1
  567. package/dist/scripts/test-sparkshell.js.map +1 -1
  568. package/dist/scripts/verify-native-agents.js.map +1 -1
  569. package/dist/session-history/friction.js.map +1 -1
  570. package/dist/session-history/search.js.map +1 -1
  571. package/dist/sidecar/__tests__/boundary.test.js.map +1 -1
  572. package/dist/sidecar/collector.js.map +1 -1
  573. package/dist/sidecar/index.js.map +1 -1
  574. package/dist/sidecar/render.js.map +1 -1
  575. package/dist/sidecar/tmux.js.map +1 -1
  576. package/dist/state/__tests__/mode-state-context.test.js +54 -0
  577. package/dist/state/__tests__/mode-state-context.test.js.map +1 -1
  578. package/dist/state/__tests__/operations.test.js +66 -15
  579. package/dist/state/__tests__/operations.test.js.map +1 -1
  580. package/dist/state/__tests__/skill-active.test.js +19 -1
  581. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  582. package/dist/state/__tests__/workflow-transition.test.js +61 -0
  583. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  584. package/dist/state/mode-state-context.d.ts +5 -0
  585. package/dist/state/mode-state-context.d.ts.map +1 -1
  586. package/dist/state/mode-state-context.js +59 -0
  587. package/dist/state/mode-state-context.js.map +1 -1
  588. package/dist/state/operations.d.ts +1 -1
  589. package/dist/state/operations.d.ts.map +1 -1
  590. package/dist/state/operations.js +49 -55
  591. package/dist/state/operations.js.map +1 -1
  592. package/dist/state/paths.d.ts +1 -1
  593. package/dist/state/paths.d.ts.map +1 -1
  594. package/dist/state/paths.js +1 -1
  595. package/dist/state/paths.js.map +1 -1
  596. package/dist/state/skill-active.d.ts +13 -1
  597. package/dist/state/skill-active.d.ts.map +1 -1
  598. package/dist/state/skill-active.js +43 -4
  599. package/dist/state/skill-active.js.map +1 -1
  600. package/dist/state/terminal-normalization.js.map +1 -1
  601. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  602. package/dist/state/workflow-transition-reconcile.js +20 -4
  603. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  604. package/dist/state/workflow-transition.d.ts +1 -1
  605. package/dist/state/workflow-transition.d.ts.map +1 -1
  606. package/dist/state/workflow-transition.js.map +1 -1
  607. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts +2 -0
  608. package/dist/subagents/__tests__/leader-bootstrap-3181.test.d.ts.map +1 -0
  609. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js +52 -0
  610. package/dist/subagents/__tests__/leader-bootstrap-3181.test.js.map +1 -0
  611. package/dist/subagents/__tests__/tracker.test.js +1550 -10
  612. package/dist/subagents/__tests__/tracker.test.js.map +1 -1
  613. package/dist/subagents/role-routing-marker.d.ts +10 -0
  614. package/dist/subagents/role-routing-marker.d.ts.map +1 -0
  615. package/dist/subagents/role-routing-marker.js +136 -0
  616. package/dist/subagents/role-routing-marker.js.map +1 -0
  617. package/dist/subagents/tracker.d.ts +91 -0
  618. package/dist/subagents/tracker.d.ts.map +1 -1
  619. package/dist/subagents/tracker.js +806 -62
  620. package/dist/subagents/tracker.js.map +1 -1
  621. package/dist/team/__tests__/api-interop.test.js +91 -1
  622. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  623. package/dist/team/__tests__/approved-execution.test.js.map +1 -1
  624. package/dist/team/__tests__/delivery-e2e-smoke.test.js +52 -10
  625. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  626. package/dist/team/__tests__/idle-nudge.test.js +45 -15
  627. package/dist/team/__tests__/idle-nudge.test.js.map +1 -1
  628. package/dist/team/__tests__/mcp-comm.test.js +93 -27
  629. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  630. package/dist/team/__tests__/model-contract.test.js +341 -4
  631. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  632. package/dist/team/__tests__/role-router.test.js.map +1 -1
  633. package/dist/team/__tests__/runtime-cli.test.js +9 -0
  634. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  635. package/dist/team/__tests__/runtime.test.js +3624 -355
  636. package/dist/team/__tests__/runtime.test.js.map +1 -1
  637. package/dist/team/__tests__/scaling.test.js +2516 -118
  638. package/dist/team/__tests__/scaling.test.js.map +1 -1
  639. package/dist/team/__tests__/state-root.test.js.map +1 -1
  640. package/dist/team/__tests__/state.test.js +804 -36
  641. package/dist/team/__tests__/state.test.js.map +1 -1
  642. package/dist/team/__tests__/team-ops-contract.test.js +6 -0
  643. package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
  644. package/dist/team/__tests__/tmux-session.test.js +3664 -969
  645. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  646. package/dist/team/__tests__/tmux-test-fixture.js.map +1 -1
  647. package/dist/team/__tests__/worker-runtime-identity.test.js +38 -2
  648. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  649. package/dist/team/__tests__/worktree.test.js +6 -0
  650. package/dist/team/__tests__/worktree.test.js.map +1 -1
  651. package/dist/team/api-interop.d.ts +2 -2
  652. package/dist/team/api-interop.d.ts.map +1 -1
  653. package/dist/team/api-interop.js +15 -9
  654. package/dist/team/api-interop.js.map +1 -1
  655. package/dist/team/commit-hygiene.js.map +1 -1
  656. package/dist/team/contracts.d.ts +5 -5
  657. package/dist/team/contracts.d.ts.map +1 -1
  658. package/dist/team/delivery-log.js.map +1 -1
  659. package/dist/team/exact-pane.d.ts +23 -0
  660. package/dist/team/exact-pane.d.ts.map +1 -0
  661. package/dist/team/exact-pane.js +133 -0
  662. package/dist/team/exact-pane.js.map +1 -0
  663. package/dist/team/followup-planner.js.map +1 -1
  664. package/dist/team/goal-workflow.js.map +1 -1
  665. package/dist/team/idle-nudge.d.ts +12 -1
  666. package/dist/team/idle-nudge.d.ts.map +1 -1
  667. package/dist/team/idle-nudge.js +41 -14
  668. package/dist/team/idle-nudge.js.map +1 -1
  669. package/dist/team/model-contract.d.ts +18 -2
  670. package/dist/team/model-contract.d.ts.map +1 -1
  671. package/dist/team/model-contract.js +314 -35
  672. package/dist/team/model-contract.js.map +1 -1
  673. package/dist/team/orchestrator.js.map +1 -1
  674. package/dist/team/pane-status.js.map +1 -1
  675. package/dist/team/phase-controller.js.map +1 -1
  676. package/dist/team/reminder-intents.d.ts +1 -1
  677. package/dist/team/reminder-intents.d.ts.map +1 -1
  678. package/dist/team/reminder-intents.js.map +1 -1
  679. package/dist/team/runtime-cli.js.map +1 -1
  680. package/dist/team/runtime.d.ts +1 -0
  681. package/dist/team/runtime.d.ts.map +1 -1
  682. package/dist/team/runtime.js +1326 -231
  683. package/dist/team/runtime.js.map +1 -1
  684. package/dist/team/scaling.d.ts +22 -8
  685. package/dist/team/scaling.d.ts.map +1 -1
  686. package/dist/team/scaling.js +1463 -376
  687. package/dist/team/scaling.js.map +1 -1
  688. package/dist/team/state/dispatch-lock.js.map +1 -1
  689. package/dist/team/state/dispatch.d.ts.map +1 -1
  690. package/dist/team/state/dispatch.js +26 -8
  691. package/dist/team/state/dispatch.js.map +1 -1
  692. package/dist/team/state/events.js.map +1 -1
  693. package/dist/team/state/locks.d.ts.map +1 -1
  694. package/dist/team/state/locks.js +61 -13
  695. package/dist/team/state/locks.js.map +1 -1
  696. package/dist/team/state/tasks.d.ts.map +1 -1
  697. package/dist/team/state/tasks.js +4 -3
  698. package/dist/team/state/tasks.js.map +1 -1
  699. package/dist/team/state-root.js.map +1 -1
  700. package/dist/team/state.d.ts +67 -1
  701. package/dist/team/state.d.ts.map +1 -1
  702. package/dist/team/state.js +636 -129
  703. package/dist/team/state.js.map +1 -1
  704. package/dist/team/team-identity.d.ts.map +1 -1
  705. package/dist/team/team-identity.js.map +1 -1
  706. package/dist/team/team-ops.d.ts +5 -0
  707. package/dist/team/team-ops.d.ts.map +1 -1
  708. package/dist/team/team-ops.js +4 -0
  709. package/dist/team/team-ops.js.map +1 -1
  710. package/dist/team/tmux-session.d.ts +125 -23
  711. package/dist/team/tmux-session.d.ts.map +1 -1
  712. package/dist/team/tmux-session.js +1340 -333
  713. package/dist/team/tmux-session.js.map +1 -1
  714. package/dist/team/worker-bootstrap.js.map +1 -1
  715. package/dist/team/worktree.d.ts.map +1 -1
  716. package/dist/team/worktree.js +4 -0
  717. package/dist/team/worktree.js.map +1 -1
  718. package/dist/ultragoal/__tests__/artifacts.test.js.map +1 -1
  719. package/dist/ultragoal/__tests__/docs-contract.test.js.map +1 -1
  720. package/dist/ultragoal/artifacts.d.ts.map +1 -1
  721. package/dist/ultragoal/artifacts.js +4 -1
  722. package/dist/ultragoal/artifacts.js.map +1 -1
  723. package/dist/url-reader/index.js.map +1 -1
  724. package/dist/utils/__tests__/file-durability.test.d.ts +2 -0
  725. package/dist/utils/__tests__/file-durability.test.d.ts.map +1 -0
  726. package/dist/utils/__tests__/file-durability.test.js +57 -0
  727. package/dist/utils/__tests__/file-durability.test.js.map +1 -0
  728. package/dist/utils/agents-model-table.js.map +1 -1
  729. package/dist/utils/file-durability.d.ts +15 -0
  730. package/dist/utils/file-durability.d.ts.map +1 -0
  731. package/dist/utils/file-durability.js +37 -0
  732. package/dist/utils/file-durability.js.map +1 -0
  733. package/dist/utils/package.js.map +1 -1
  734. package/dist/utils/paths.js.map +1 -1
  735. package/dist/utils/platform-command.js.map +1 -1
  736. package/dist/utils/repo-deps.js.map +1 -1
  737. package/dist/utils/version.js.map +1 -1
  738. package/dist/verification/__tests__/ci-rust-gates.test.js +1 -1
  739. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js +3 -0
  740. package/dist/verification/__tests__/dev-merge-issue-close-workflow.test.js.map +1 -1
  741. package/dist/verification/verifier.js.map +1 -1
  742. package/dist/visual/constants.d.ts +1 -1
  743. package/dist/visual/constants.d.ts.map +1 -1
  744. package/dist/vscode/__tests__/index.test.js.map +1 -1
  745. package/dist/vscode/index.js.map +1 -1
  746. package/dist/wiki/__tests__/cjk-tokenize.test.js.map +1 -1
  747. package/dist/wiki/__tests__/lint.test.js.map +1 -1
  748. package/dist/wiki/__tests__/query.test.js.map +1 -1
  749. package/dist/wiki/__tests__/storage.test.js.map +1 -1
  750. package/dist/wiki/__tests__/test-helpers.d.ts.map +1 -1
  751. package/dist/wiki/lint.d.ts +4 -1
  752. package/dist/wiki/lint.d.ts.map +1 -1
  753. package/dist/wiki/lint.js +3 -2
  754. package/dist/wiki/lint.js.map +1 -1
  755. package/dist/wiki/query.js.map +1 -1
  756. package/dist/wiki/storage.js.map +1 -1
  757. package/package.json +2 -2
  758. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  759. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +24 -0
  760. package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +10 -5
  761. package/plugins/oh-my-codex/skills/ralph/SKILL.md +6 -2
  762. package/plugins/oh-my-codex/skills/ralplan/SKILL.md +6 -2
  763. package/plugins/oh-my-codex/skills/team/SKILL.md +13 -9
  764. package/skills/omx-setup/SKILL.md +10 -5
  765. package/skills/ralph/SKILL.md +6 -2
  766. package/skills/ralplan/SKILL.md +6 -2
  767. package/skills/team/SKILL.md +13 -9
  768. package/src/scripts/__tests__/codex-native-hook.test.ts +20048 -10827
  769. package/src/scripts/__tests__/fixtures/issue-3138-installed-smoke.ts +734 -0
  770. package/src/scripts/__tests__/notify-fallback-delivery.test.ts +177 -0
  771. package/src/scripts/__tests__/notify-state-io.test.ts +73 -0
  772. package/src/scripts/__tests__/reasoning-artifact-contract.test.ts +129 -0
  773. package/src/scripts/__tests__/role-intent-bootstrap-e2e-3181.test.ts +78 -0
  774. package/src/scripts/__tests__/role-intent-durable-recovery-3181.test.ts +60 -0
  775. package/src/scripts/__tests__/smoke-packed-install.test.ts +1309 -4
  776. package/src/scripts/codex-native-hook.ts +11429 -1163
  777. package/src/scripts/notify-fallback-delivery.ts +416 -0
  778. package/src/scripts/notify-fallback-watcher.ts +216 -155
  779. package/src/scripts/notify-hook/auto-nudge.ts +113 -14
  780. package/src/scripts/notify-hook/managed-tmux.ts +149 -12
  781. package/src/scripts/notify-hook/ralph-session-resume.ts +37 -36
  782. package/src/scripts/notify-hook/state-io.ts +100 -13
  783. package/src/scripts/notify-hook/team-dispatch.ts +242 -75
  784. package/src/scripts/notify-hook/team-leader-nudge.ts +40 -18
  785. package/src/scripts/notify-hook/team-tmux-guard.ts +366 -39
  786. package/src/scripts/notify-hook/team-worker-stop.ts +20 -16
  787. package/src/scripts/notify-hook/team-worker.ts +39 -26
  788. package/src/scripts/notify-hook/tmux-injection.ts +66 -23
  789. package/src/scripts/notify-hook/visual-verdict.ts +22 -12
  790. package/src/scripts/notify-hook.ts +336 -90
  791. package/src/scripts/smoke-packed-install.ts +5154 -149
@@ -7,7 +7,7 @@ import { join } from 'node:path';
7
7
  import { spawn, spawnSync } from 'node:child_process';
8
8
  import { randomUUID } from 'node:crypto';
9
9
  import { initTeamState, enqueueDispatchRequest, readDispatchRequest } from '../../team/state.js';
10
- import { buildTmuxSessionName, buildWindowsMsysBackgroundHelperBootstrapScript } from '../../cli/index.js';
10
+ import { buildWindowsMsysBackgroundHelperBootstrapScript } from '../../cli/index.js';
11
11
  import { writeSessionStart } from '../session.js';
12
12
  const DEFAULT_AUTO_NUDGE_RESPONSE = 'continue with the current task only if it is already authorized';
13
13
  const INHERITED_OMX_ENV_KEYS = [
@@ -104,13 +104,15 @@ async function writeCanonicalWatcherTeamFixture(wd, { teamName = 'dispatch-team'
104
104
  },
105
105
  tmux_session: `${teamName}:0`,
106
106
  leader_pane_id: '%42',
107
+ leader_pane_pid: 4242,
108
+ tmux_pane_owner_id: `team:${teamName}`,
107
109
  hud_pane_id: null,
108
110
  resize_hook_name: null,
109
111
  resize_hook_target: null,
110
112
  worker_count: 1,
111
113
  next_task_id: 1,
112
114
  workers: [
113
- { name: 'worker-1', index: 1, pane_id: '%42', role: 'executor' },
115
+ { name: 'worker-1', index: 1, pane_id: '%42', pid: 4242, role: 'executor' },
114
116
  ],
115
117
  created_at: nowIso,
116
118
  };
@@ -119,8 +121,12 @@ async function writeCanonicalWatcherTeamFixture(wd, { teamName = 'dispatch-team'
119
121
  name: teamName,
120
122
  tmux_session: `${teamName}:0`,
121
123
  leader_pane_id: '%42',
124
+ leader_pane_pid: 4242,
125
+ tmux_pane_owner_id: `team:${teamName}`,
126
+ hud_pane_id: null,
127
+ hud_pane_pid: null,
122
128
  workers: [
123
- { name: 'worker-1', pane_id: '%42' },
129
+ { name: 'worker-1', pane_id: '%42', pid: 4242 },
124
130
  ],
125
131
  }, null, 2));
126
132
  await writeFile(join(teamDir, 'phase.json'), JSON.stringify({
@@ -129,6 +135,22 @@ async function writeCanonicalWatcherTeamFixture(wd, { teamName = 'dispatch-team'
129
135
  transitions: terminal ? [{ from: 'team-exec', to: 'complete', at: nowIso }] : [],
130
136
  }, null, 2));
131
137
  }
138
+ async function bindCanonicalDispatchWorkerFixture(wd, { teamName = 'dispatch-team', paneId = '%42', panePid = 4242, ownerId = `team:${teamName}`, hudPaneId = null, } = {}) {
139
+ for (const fileName of ['config.json', 'manifest.v2.json']) {
140
+ const statePath = join(wd, '.omx', 'state', 'team', teamName, fileName);
141
+ const state = JSON.parse(await readFile(statePath, 'utf8'));
142
+ const workers = Array.isArray(state.workers) ? state.workers : [];
143
+ state.tmux_pane_owner_id = ownerId;
144
+ state.leader_pane_id = paneId;
145
+ state.leader_pane_pid = panePid;
146
+ state.hud_pane_id = hudPaneId;
147
+ state.hud_pane_pid = null;
148
+ state.workers = workers.map((worker, index) => (index === 0 && worker && typeof worker === 'object'
149
+ ? { ...worker, pane_id: paneId, pid: panePid }
150
+ : worker));
151
+ await writeFile(statePath, JSON.stringify(state, null, 2));
152
+ }
153
+ }
132
154
  async function sleep(ms) {
133
155
  await new Promise(resolve => setTimeout(resolve, ms));
134
156
  }
@@ -171,6 +193,19 @@ set -eu
171
193
  echo "$@" >> "${tmuxLogPath}"
172
194
  cmd="$1"
173
195
  shift || true
196
+ if [[ "$cmd" == "list-panes" && "$#" -eq 3 && "$1" == "-a" && "$2" == "-F" && "$3" == "#{pane_id}\t#{pane_dead}\t#{pane_pid}" ]]; then
197
+ printf '%%42\t0\t${options.panePid ?? 4242}\n'
198
+ ${options.ralphPane ? `printf '${options.ralphPane.paneId.replace('%', '%%')}\\t0\\t${options.ralphPane.panePid}\\n'` : ''}
199
+ exit 0
200
+ fi
201
+ if [[ "$cmd" == "show-option" && "\${@: -1}" == "@omx_ralph_pane_owner_id" ]]; then
202
+ printf '%s\n' '${options.ralphPane?.paneOwner ?? options.paneOwner ?? 'ralph:owner'}'
203
+ exit 0
204
+ fi
205
+ if [[ "$cmd" == "show-option" && "\${@: -1}" == "@omx_team_pane_owner_id" ]]; then
206
+ printf '%s\n' '${options.paneOwner ?? 'team:dispatch-team'}'
207
+ exit 0
208
+ fi
174
209
  if [[ "$cmd" == "capture-pane" ]]; then
175
210
  if [[ -n "\${OMX_TEST_CAPTURE_SEQUENCE_FILE:-}" && -f "\${OMX_TEST_CAPTURE_SEQUENCE_FILE}" ]]; then
176
211
  counterFile="\${OMX_TEST_CAPTURE_COUNTER_FILE:-\${OMX_TEST_CAPTURE_SEQUENCE_FILE}.idx}"
@@ -222,7 +257,7 @@ if [[ "$cmd" == "display-message" ]]; then
222
257
  exit 0
223
258
  fi
224
259
  if [[ "$fmt" == "#S" ]]; then
225
- echo "\${OMX_TEST_TMUX_SESSION_NAME:-session-test}"
260
+ echo "${options.paneSession ?? '${OMX_TEST_TMUX_SESSION_NAME:-session-test}'}"
226
261
  exit 0
227
262
  fi
228
263
  exit 0
@@ -285,126 +320,6 @@ fi
285
320
  exit 0
286
321
  `;
287
322
  }
288
- function buildManagedRalphTmux(tmuxLogPath, options) {
289
- const { cwd, managedSessionName, anchorPane, livePane, codexPanes, missingAnchor = false } = options;
290
- const panes = (codexPanes && codexPanes.length > 0)
291
- ? codexPanes
292
- : [{ paneId: livePane, active: true, currentCommand: 'codex', startCommand: 'codex' }];
293
- const listPaneOutput = panes
294
- .map((pane) => {
295
- const paneId = pane.paneId;
296
- const active = pane.active ? '1' : '0';
297
- const currentCommand = pane.currentCommand || 'codex';
298
- const startCommand = pane.startCommand || 'codex';
299
- return `${paneId}\t${active}\t${currentCommand}\t${startCommand}`;
300
- })
301
- .join('\n');
302
- const paneCommandBranches = panes
303
- .map((pane) => {
304
- const currentCommand = (pane.currentCommand || 'codex').replace(/"/g, '\\"');
305
- const startCommand = (pane.startCommand || 'codex').replace(/"/g, '\\"');
306
- return ` if [[ "$format" == "#{pane_current_command}" && "$target" == "${pane.paneId}" ]]; then
307
- echo "${currentCommand}"
308
- exit 0
309
- fi
310
- if [[ "$format" == "#{pane_start_command}" && "$target" == "${pane.paneId}" ]]; then
311
- echo "${startCommand}"
312
- exit 0
313
- fi`;
314
- })
315
- .join('\n');
316
- return `#!/usr/bin/env bash
317
- set -eu
318
- echo "$@" >> "${tmuxLogPath}"
319
- cmd="$1"
320
- shift || true
321
- if [[ "$cmd" == "display-message" ]]; then
322
- target=""
323
- format=""
324
- while [[ "$#" -gt 0 ]]; do
325
- case "$1" in
326
- -p) shift ;;
327
- -t) target="$2"; shift 2 ;;
328
- *) format="$1"; shift ;;
329
- esac
330
- done
331
- if [[ "$target" == "${anchorPane}" && "${missingAnchor ? '1' : '0'}" == "1" ]]; then
332
- echo "pane missing" >&2
333
- exit 1
334
- fi
335
- if [[ "$format" == "#{pane_in_mode}" ]]; then
336
- echo "0"
337
- exit 0
338
- fi
339
- if [[ "$format" == "#{pane_id}" ]]; then
340
- echo "$target"
341
- exit 0
342
- fi
343
- if [[ "$format" == "#{pane_current_path}" ]]; then
344
- echo "${cwd}"
345
- exit 0
346
- fi
347
- ${paneCommandBranches}
348
- if [[ "$format" == "#S" ]]; then
349
- if [[ "$target" == "${anchorPane}" || "$target" == "${livePane}" ]]; then
350
- echo "${managedSessionName}"
351
- exit 0
352
- fi
353
- echo "unknown target" >&2
354
- exit 1
355
- fi
356
- exit 0
357
- fi
358
- if [[ "$cmd" == "list-panes" ]]; then
359
- target=""
360
- while [[ "$#" -gt 0 ]]; do
361
- case "$1" in
362
- -F) shift 2 ;;
363
- -t) shift; target="$1" ;;
364
- esac
365
- shift || true
366
- done
367
- if [[ "$target" == "${managedSessionName}" ]]; then
368
- printf '%s\n' "${listPaneOutput}"
369
- exit 0
370
- fi
371
- echo "can't find session" >&2
372
- exit 1
373
- fi
374
- if [[ "$cmd" == "capture-pane" ]]; then
375
- exit 0
376
- fi
377
- if [[ "$cmd" == "set-buffer" ]]; then
378
- printf '%s' "\${@: -1}" > "${tmuxLogPath}.buffer"
379
- exit 0
380
- fi
381
- if [[ "$cmd" == "show-buffer" ]]; then
382
- if [[ -f "${tmuxLogPath}.buffer" ]]; then cat "${tmuxLogPath}.buffer"; fi
383
- exit 0
384
- fi
385
- if [[ "$cmd" == "paste-buffer" ]]; then
386
- target=""
387
- while [[ "$#" -gt 0 ]]; do
388
- case "$1" in
389
- -t) target="$2"; shift 2 ;;
390
- *) shift ;;
391
- esac
392
- done
393
- if [[ -f "${tmuxLogPath}.buffer" ]]; then
394
- echo "send-keys -t \${target} -l $(cat "${tmuxLogPath}.buffer")" >> "${tmuxLogPath}"
395
- fi
396
- exit 0
397
- fi
398
- if [[ "$cmd" == "delete-buffer" ]]; then
399
- rm -f "${tmuxLogPath}.buffer"
400
- exit 0
401
- fi
402
- if [[ "$cmd" == "send-keys" ]]; then
403
- exit 0
404
- fi
405
- exit 0
406
- `;
407
- }
408
323
  function buildCleanNotifyEnv(overrides = {}) {
409
324
  return {
410
325
  ...process.env,
@@ -1011,6 +926,9 @@ describe('notify-fallback watcher', () => {
1011
926
  active: true,
1012
927
  current_phase: 'executing',
1013
928
  tmux_pane_id: '%42',
929
+ tmux_pane_pid: 4242,
930
+ tmux_session_name: 'session-test',
931
+ tmux_pane_owner_id: 'ralph:owner',
1014
932
  }, null, 2));
1015
933
  await writeFile(join(wd, '.omx', 'state', 'team-state.json'), JSON.stringify({
1016
934
  active: true,
@@ -1074,6 +992,9 @@ describe('notify-fallback watcher', () => {
1074
992
  active: true,
1075
993
  current_phase: 'executing',
1076
994
  tmux_pane_id: '%42',
995
+ tmux_pane_pid: 4242,
996
+ tmux_session_name: 'session-test',
997
+ tmux_pane_owner_id: 'ralph:owner',
1077
998
  }, null, 2));
1078
999
  await writeFile(join(wd, '.omx', 'state', 'team-state.json'), JSON.stringify({
1079
1000
  active: true,
@@ -1129,6 +1050,8 @@ describe('notify-fallback watcher', () => {
1129
1050
  name: 'dispatch-team',
1130
1051
  tmux_session: 'omx-team-dispatch-team',
1131
1052
  leader_pane_id: '%42',
1053
+ leader_pane_pid: 4242,
1054
+ tmux_pane_owner_id: 'team:dispatch-team',
1132
1055
  }, null, 2));
1133
1056
  const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
1134
1057
  const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
@@ -1296,6 +1219,45 @@ describe('notify-fallback watcher', () => {
1296
1219
  await rm(wd, { recursive: true, force: true });
1297
1220
  }
1298
1221
  });
1222
+ it('does not keep leader mailbox nudges alive after the team becomes terminal', async () => {
1223
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-terminal-mailbox-'));
1224
+ const fakeBinDir = join(wd, 'fake-bin');
1225
+ const tmuxLogPath = join(wd, 'tmux.log');
1226
+ try {
1227
+ await writeCanonicalWatcherTeamFixture(wd, { coarseState: 'inactive', terminal: true });
1228
+ await mkdir(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'mailbox'), { recursive: true });
1229
+ await mkdir(fakeBinDir, { recursive: true });
1230
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
1231
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
1232
+ await writeFile(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'mailbox', 'leader-fixed.json'), JSON.stringify({
1233
+ worker: 'leader-fixed',
1234
+ messages: [{
1235
+ message_id: 'terminal-msg',
1236
+ from_worker: 'worker-1',
1237
+ to_worker: 'leader-fixed',
1238
+ body: 'must not wake a terminal team',
1239
+ created_at: new Date().toISOString(),
1240
+ }],
1241
+ }, null, 2));
1242
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
1243
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
1244
+ const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook], {
1245
+ encoding: 'utf-8',
1246
+ env: buildCleanNotifyEnv({
1247
+ PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
1248
+ OMX_SESSION_ID: 'sess-current',
1249
+ }),
1250
+ });
1251
+ assert.equal(result.status, 0, result.stderr || result.stdout);
1252
+ assert.equal(await readFile(tmuxLogPath, 'utf8').catch(() => ''), '');
1253
+ const logPath = join(wd, '.omx', 'logs', `notify-fallback-${new Date().toISOString().split('T')[0]}.jsonl`);
1254
+ const logEntries = await readJsonLines(logPath);
1255
+ assert.equal(logEntries.some((entry) => entry.type === 'leader_nudge_tick'), false);
1256
+ }
1257
+ finally {
1258
+ await rm(wd, { recursive: true, force: true });
1259
+ }
1260
+ });
1299
1261
  it('does not run stalled-worker leader nudges from the fallback watcher when the leader is not stale', async () => {
1300
1262
  const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-worker-stall-nudge-'));
1301
1263
  const fakeBinDir = join(wd, 'fake-bin');
@@ -1390,7 +1352,7 @@ if [[ "$cmd" == "list-panes" ]]; then
1390
1352
  printf "%%42 12345\n%%10 12346\n%%11 12347\n"
1391
1353
  exit 0
1392
1354
  fi
1393
- echo "%42 1"
1355
+ printf "%%42\t0\t12345\n%%10\t0\t12346\n%%11\t0\t12347\n"
1394
1356
  exit 0
1395
1357
  fi
1396
1358
  exit 0
@@ -1411,9 +1373,10 @@ exit 0
1411
1373
  name: 'dispatch-team',
1412
1374
  tmux_session: 'omx-team-dispatch-team',
1413
1375
  leader_pane_id: '%42',
1376
+ leader_pane_pid: 12345,
1414
1377
  workers: [
1415
- { name: 'worker-1', index: 1, pane_id: '%10' },
1416
- { name: 'worker-2', index: 2, pane_id: '%11' },
1378
+ { name: 'worker-1', index: 1, pane_id: '%10', pid: 12346 },
1379
+ { name: 'worker-2', index: 2, pane_id: '%11', pid: 12347 },
1417
1380
  ],
1418
1381
  }, null, 2));
1419
1382
  await writeFile(join(wd, '.omx', 'state', 'team', 'dispatch-team', 'tasks', 'task-1.json'), JSON.stringify({
@@ -1480,7 +1443,7 @@ exit 0
1480
1443
  }),
1481
1444
  });
1482
1445
  assert.equal(result.status, 0, result.stderr || result.stdout);
1483
- const tmuxLog = await readFile(tmuxLogPath, 'utf8');
1446
+ const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
1484
1447
  assert.doesNotMatch(tmuxLog, /worker panes stalled/);
1485
1448
  assert.doesNotMatch(tmuxLog, /no progress 3m/);
1486
1449
  assert.doesNotMatch(tmuxLog, /leader stale/);
@@ -1862,6 +1825,7 @@ exit 0
1862
1825
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
1863
1826
  await writeFile(captureFile, '... ping ...');
1864
1827
  await initTeamState('dispatch-team', 'task', 'executor', 1, wd);
1828
+ await bindCanonicalDispatchWorkerFixture(wd);
1865
1829
  const queued = await enqueueDispatchRequest('dispatch-team', {
1866
1830
  kind: 'inbox',
1867
1831
  to_worker: 'worker-1',
@@ -1909,12 +1873,15 @@ exit 0
1909
1873
  try {
1910
1874
  await mkdir(stateDir, { recursive: true });
1911
1875
  await mkdir(fakeBinDir, { recursive: true });
1912
- await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
1876
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, { paneOwner: 'ralph:owner' }));
1913
1877
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
1914
1878
  await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
1915
1879
  active: true,
1916
1880
  current_phase: 'executing',
1917
1881
  tmux_pane_id: '%42',
1882
+ tmux_pane_pid: 4242,
1883
+ tmux_session_name: 'session-test',
1884
+ tmux_pane_owner_id: 'ralph:owner',
1918
1885
  }, null, 2));
1919
1886
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
1920
1887
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -1956,6 +1923,41 @@ exit 0
1956
1923
  await rm(wd, { recursive: true, force: true });
1957
1924
  }
1958
1925
  });
1926
+ it('fails closed for recycled, incomplete, HUD, and taken-over Ralph pane bindings', async () => {
1927
+ const cases = [
1928
+ [{ tmux_pane_pid: 4242, tmux_session_name: 'session-test', tmux_pane_owner_id: 'ralph:owner' }, { paneOwner: 'ralph:owner', panePid: 4243 }],
1929
+ [{}, { paneOwner: 'ralph:owner' }],
1930
+ [{ tmux_pane_pid: 4242, tmux_session_name: 'session-test', tmux_pane_owner_id: 'team:dispatch-team' }, { paneOwner: 'team:dispatch-team' }],
1931
+ [{ tmux_pane_pid: 4242, tmux_session_name: 'session-test', tmux_pane_owner_id: 'ralph:owner' }, { paneOwner: 'ralph:owner', paneSession: 'other-session' }],
1932
+ [{ tmux_pane_pid: 4242, tmux_session_name: 'session-test', tmux_pane_owner_id: 'ralph:owner' }, { paneOwner: 'ralph:other' }],
1933
+ [{ tmux_pane_pid: 4242.9, tmux_session_name: 'session-test', tmux_pane_owner_id: 'ralph:owner' }, { paneOwner: 'ralph:owner' }],
1934
+ [{ tmux_pane_pid: '4.242e3', tmux_session_name: 'session-test', tmux_pane_owner_id: 'ralph:owner' }, { paneOwner: 'ralph:owner' }],
1935
+ [{ tmux_pane_pid: '9007199254740992', tmux_session_name: 'session-test', tmux_pane_owner_id: 'ralph:owner' }, { paneOwner: 'ralph:owner' }],
1936
+ ];
1937
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
1938
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
1939
+ for (const [stateBinding, tmux] of cases) {
1940
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-binding-'));
1941
+ const stateDir = join(wd, '.omx', 'state');
1942
+ const fakeBinDir = join(wd, 'fake-bin');
1943
+ const tmuxLogPath = join(wd, 'tmux.log');
1944
+ try {
1945
+ await mkdir(stateDir, { recursive: true });
1946
+ await mkdir(fakeBinDir, { recursive: true });
1947
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, tmux));
1948
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
1949
+ await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({ active: true, current_phase: 'executing', tmux_pane_id: '%42', ...stateBinding }));
1950
+ await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({ last_progress_at: new Date(Date.now() - 61_000).toISOString() }));
1951
+ await writeFile(join(stateDir, 'notify-fallback-state.json'), JSON.stringify({ ralph_continue_steer: { last_sent_at: new Date(Date.now() - 61_000).toISOString() } }));
1952
+ const result = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook], { encoding: 'utf-8', env: { ...buildCleanNotifyEnv(), PATH: `${fakeBinDir}:${process.env.PATH || ''}` } });
1953
+ assert.equal(result.status, 0, result.stderr || result.stdout);
1954
+ assert.doesNotMatch(await readFile(tmuxLogPath, 'utf-8').catch(() => ''), /send-keys -t %42/);
1955
+ }
1956
+ finally {
1957
+ await rm(wd, { recursive: true, force: true });
1958
+ }
1959
+ }
1960
+ });
1959
1961
  it('suppresses Ralph continue steer when hud progress is still fresh after cooldown', async () => {
1960
1962
  const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-progress-fresh-'));
1961
1963
  const fakeBinDir = join(wd, 'fake-bin');
@@ -1971,6 +1973,9 @@ exit 0
1971
1973
  active: true,
1972
1974
  current_phase: 'executing',
1973
1975
  tmux_pane_id: '%42',
1976
+ tmux_pane_pid: 4242,
1977
+ tmux_session_name: 'session-test',
1978
+ tmux_pane_owner_id: 'ralph:owner',
1974
1979
  }, null, 2));
1975
1980
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
1976
1981
  last_progress_at: new Date(Date.now() - 5_000).toISOString(),
@@ -2007,12 +2012,15 @@ exit 0
2007
2012
  try {
2008
2013
  await mkdir(stateDir, { recursive: true });
2009
2014
  await mkdir(fakeBinDir, { recursive: true });
2010
- await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2015
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, { paneOwner: 'ralph:owner' }));
2011
2016
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
2012
2017
  await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
2013
2018
  active: true,
2014
2019
  current_phase: 'executing',
2015
2020
  tmux_pane_id: '%42',
2021
+ tmux_pane_pid: 4242,
2022
+ tmux_session_name: 'session-test',
2023
+ tmux_pane_owner_id: 'ralph:owner',
2016
2024
  }, null, 2));
2017
2025
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2018
2026
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -2059,6 +2067,9 @@ exit 0
2059
2067
  current_phase: 'starting',
2060
2068
  started_at: new Date(Date.now() - 180_000).toISOString(),
2061
2069
  tmux_pane_id: '%42',
2070
+ tmux_pane_pid: 4242,
2071
+ tmux_session_name: 'session-test',
2072
+ tmux_pane_owner_id: 'ralph:owner',
2062
2073
  }, null, 2));
2063
2074
  await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
2064
2075
  last_progress_at: new Date(Date.now() - 180_000).toISOString(),
@@ -2105,6 +2116,9 @@ exit 0
2105
2116
  active: true,
2106
2117
  current_phase: 'executing',
2107
2118
  tmux_pane_id: '%42',
2119
+ tmux_pane_pid: 4242,
2120
+ tmux_session_name: 'session-test',
2121
+ tmux_pane_owner_id: 'ralph:owner',
2108
2122
  owner_omx_session_id: omxSessionId,
2109
2123
  owner_codex_session_id: codexSessionId,
2110
2124
  }, null, 2));
@@ -2179,6 +2193,9 @@ exit 0
2179
2193
  active: true,
2180
2194
  current_phase: 'executing',
2181
2195
  tmux_pane_id: '%42',
2196
+ tmux_pane_pid: 4242,
2197
+ tmux_session_name: 'session-test',
2198
+ tmux_pane_owner_id: 'ralph:owner',
2182
2199
  }, null, 2));
2183
2200
  await writeFile(statePath, JSON.stringify({
2184
2201
  ralph_continue_steer: {
@@ -2245,7 +2262,7 @@ exit 0
2245
2262
  const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], { encoding: 'utf-8', env });
2246
2263
  assert.equal(run.status, 0, run.stderr || run.stdout);
2247
2264
  const watcherState = JSON.parse(await readFile(statePath, 'utf-8'));
2248
- assert.equal(watcherState.ralph_continue_steer?.last_reason, 'pane_missing');
2265
+ assert.equal(watcherState.ralph_continue_steer?.last_reason, 'pane_binding_missing');
2249
2266
  assert.equal(watcherState.ralph_continue_steer?.pane_id, '');
2250
2267
  const tmuxLog = await readFile(tmuxLogPath, 'utf8').catch(() => '');
2251
2268
  assert.equal(/send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/.test(tmuxLog), false);
@@ -2255,412 +2272,48 @@ exit 0
2255
2272
  await rm(wd, { recursive: true, force: true });
2256
2273
  }
2257
2274
  });
2258
- it('rebinds a stale-but-present session-scoped Ralph shell pane to the live pane before continue steer', async () => {
2259
- const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-rebind-stale-anchor-'));
2260
- const fakeBinDir = join(wd, 'fake-bin');
2261
- const stateDir = join(wd, '.omx', 'state');
2262
- const tmuxLogPath = join(wd, 'tmux.log');
2263
- const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
2264
- const sessionId = 'sess-ralph-rebind';
2265
- const sessionStateDir = join(stateDir, 'sessions', sessionId);
2266
- const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
2267
- const anchorPane = '%99';
2268
- const livePane = '%42';
2269
- try {
2270
- await mkdir(sessionStateDir, { recursive: true });
2271
- await mkdir(fakeBinDir, { recursive: true });
2272
- await writeSessionStart(wd, sessionId);
2273
- const managedSessionName = buildTmuxSessionName(wd, sessionId);
2274
- await writeFile(join(fakeBinDir, 'tmux'), buildManagedRalphTmux(tmuxLogPath, {
2275
- cwd: wd,
2276
- managedSessionName,
2277
- anchorPane,
2278
- livePane,
2279
- codexPanes: [
2280
- { paneId: anchorPane, active: false, currentCommand: 'sh', startCommand: 'bash' },
2281
- { paneId: '%41', active: false, currentCommand: 'codex', startCommand: 'codex' },
2282
- { paneId: livePane, active: true, currentCommand: 'codex', startCommand: 'codex' },
2283
- ],
2284
- }));
2285
- await chmod(join(fakeBinDir, 'tmux'), 0o755);
2286
- await writeFile(ralphStatePath, JSON.stringify({
2287
- active: true,
2288
- current_phase: 'executing',
2289
- tmux_pane_id: anchorPane,
2290
- }, null, 2));
2291
- await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
2292
- last_progress_at: new Date(Date.now() - 61_000).toISOString(),
2293
- }, null, 2));
2294
- await writeFile(watcherStatePath, JSON.stringify({
2295
- ralph_continue_steer: {
2296
- last_sent_at: new Date(Date.now() - 61_000).toISOString(),
2297
- },
2298
- }, null, 2));
2299
- const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2300
- const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2301
- const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
2302
- encoding: 'utf-8',
2303
- env: buildCleanNotifyEnv({
2304
- PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
2305
- }),
2306
- });
2307
- assert.equal(run.status, 0, run.stderr || run.stdout);
2308
- const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
2309
- assert.equal(persistedRalph.tmux_pane_id, livePane);
2310
- assert.match(persistedRalph.tmux_pane_set_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
2311
- const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
2312
- assert.equal(watcherState.ralph_continue_steer?.last_reason, 'sent');
2313
- assert.equal(watcherState.ralph_continue_steer?.pane_id, livePane);
2314
- const tmuxLog = await readFile(tmuxLogPath, 'utf8');
2315
- assert.match(tmuxLog, /send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
2316
- assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
2317
- }
2318
- finally {
2319
- await rm(wd, { recursive: true, force: true });
2320
- }
2321
- });
2322
- it('preserves newer Ralph state fields when a pane rebound happens after the state file advances', async () => {
2323
- const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-rebind-state-merge-'));
2324
- const fakeBinDir = join(wd, 'fake-bin');
2325
- const stateDir = join(wd, '.omx', 'state');
2326
- const tmuxLogPath = join(wd, 'tmux.log');
2327
- const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
2328
- const sessionId = 'sess-ralph-rebind-merge';
2329
- const sessionStateDir = join(stateDir, 'sessions', sessionId);
2330
- const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
2331
- const anchorPane = '%99';
2332
- const livePane = '%42';
2333
- try {
2334
- await mkdir(sessionStateDir, { recursive: true });
2335
- await mkdir(fakeBinDir, { recursive: true });
2336
- await writeSessionStart(wd, sessionId);
2337
- const managedSessionName = buildTmuxSessionName(wd, sessionId);
2338
- const fakeTmux = `#!/usr/bin/env bash
2339
- set -eu
2340
- echo "$@" >> "${tmuxLogPath}"
2341
- cmd="$1"
2342
- shift || true
2343
- if [[ "$cmd" == "display-message" ]]; then
2344
- target=""
2345
- format=""
2346
- while [[ "$#" -gt 0 ]]; do
2347
- case "$1" in
2348
- -p) shift ;;
2349
- -t) target="$2"; shift 2 ;;
2350
- *) format="$1"; shift ;;
2351
- esac
2352
- done
2353
- if [[ "$format" == "#{pane_in_mode}" ]]; then
2354
- echo "0"
2355
- exit 0
2356
- fi
2357
- if [[ "$format" == "#{pane_id}" ]]; then
2358
- echo "$target"
2359
- exit 0
2360
- fi
2361
- if [[ "$format" == "#{pane_current_path}" ]]; then
2362
- echo "${wd}"
2363
- exit 0
2364
- fi
2365
- if [[ "$format" == "#{pane_current_command}" && "$target" == "${anchorPane}" ]]; then
2366
- echo "sh"
2367
- exit 0
2368
- fi
2369
- if [[ "$format" == "#{pane_start_command}" && "$target" == "${anchorPane}" ]]; then
2370
- echo "bash"
2371
- exit 0
2372
- fi
2373
- if [[ "$format" == "#S" && "$target" == "${anchorPane}" ]]; then
2374
- echo "${managedSessionName}"
2375
- exit 0
2376
- fi
2377
- exit 0
2378
- fi
2379
- if [[ "$cmd" == "list-panes" ]]; then
2380
- target=""
2381
- while [[ "$#" -gt 0 ]]; do
2382
- case "$1" in
2383
- -F) shift 2 ;;
2384
- -t) shift; target="$1" ;;
2385
- esac
2386
- shift || true
2387
- done
2388
- if [[ "$target" == "${managedSessionName}" ]]; then
2389
- cat > "${ralphStatePath}" <<'JSON'
2390
- {
2391
- "active": true,
2392
- "current_phase": "reviewing",
2393
- "iteration": 11,
2394
- "owner_codex_session_id": "codex-updated-owner",
2395
- "tmux_pane_id": "%99"
2396
- }
2397
- JSON
2398
- printf "%%99\t0\tsh\tbash\n%%42\t1\tcodex\tcodex\n"
2399
- exit 0
2400
- fi
2401
- echo "can't find session" >&2
2402
- exit 1
2403
- fi
2404
- if [[ "$cmd" == "capture-pane" ]]; then
2405
- exit 0
2406
- fi
2407
- if [[ "$cmd" == "set-buffer" ]]; then
2408
- printf '%s' "\${@: -1}" > "${tmuxLogPath}.buffer"
2409
- exit 0
2410
- fi
2411
- if [[ "$cmd" == "show-buffer" ]]; then
2412
- if [[ -f "${tmuxLogPath}.buffer" ]]; then cat "${tmuxLogPath}.buffer"; fi
2413
- exit 0
2414
- fi
2415
- if [[ "$cmd" == "paste-buffer" ]]; then
2416
- target=""
2417
- while [[ "$#" -gt 0 ]]; do
2418
- case "$1" in
2419
- -t) target="$2"; shift 2 ;;
2420
- *) shift ;;
2421
- esac
2422
- done
2423
- if [[ -f "${tmuxLogPath}.buffer" ]]; then
2424
- echo "send-keys -t \${target} -l $(cat "${tmuxLogPath}.buffer")" >> "${tmuxLogPath}"
2425
- fi
2426
- exit 0
2427
- fi
2428
- if [[ "$cmd" == "delete-buffer" ]]; then
2429
- rm -f "${tmuxLogPath}.buffer"
2430
- exit 0
2431
- fi
2432
- if [[ "$cmd" == "send-keys" ]]; then
2433
- exit 0
2434
- fi
2435
- exit 0
2436
- `;
2437
- await writeFile(join(fakeBinDir, 'tmux'), fakeTmux);
2438
- await chmod(join(fakeBinDir, 'tmux'), 0o755);
2439
- await writeFile(ralphStatePath, JSON.stringify({
2440
- active: true,
2441
- current_phase: 'executing',
2442
- iteration: 1,
2443
- owner_codex_session_id: 'codex-stale-owner',
2444
- tmux_pane_id: anchorPane,
2445
- }, null, 2));
2446
- await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
2447
- last_progress_at: new Date(Date.now() - 61_000).toISOString(),
2448
- }, null, 2));
2449
- await writeFile(watcherStatePath, JSON.stringify({
2450
- ralph_continue_steer: {
2451
- last_sent_at: new Date(Date.now() - 61_000).toISOString(),
2452
- },
2453
- }, null, 2));
2454
- const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2455
- const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2456
- const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
2457
- encoding: 'utf-8',
2458
- env: buildCleanNotifyEnv({
2459
- PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
2460
- }),
2461
- });
2462
- assert.equal(run.status, 0, run.stderr || run.stdout);
2463
- const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
2464
- assert.equal(persistedRalph.tmux_pane_id, livePane);
2465
- assert.equal(persistedRalph.current_phase, 'reviewing');
2466
- assert.equal(persistedRalph.iteration, 11);
2467
- assert.equal(persistedRalph.owner_codex_session_id, 'codex-updated-owner');
2468
- assert.match(persistedRalph.tmux_pane_set_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
2469
- }
2470
- finally {
2471
- await rm(wd, { recursive: true, force: true });
2472
- }
2473
- });
2474
- it('keeps the verified Ralph anchor pane when another codex pane is focused in the same managed session', async () => {
2475
- const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-keep-anchor-pane-'));
2476
- const fakeBinDir = join(wd, 'fake-bin');
2477
- const stateDir = join(wd, '.omx', 'state');
2478
- const tmuxLogPath = join(wd, 'tmux.log');
2479
- const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
2480
- const sessionId = 'sess-ralph-keep-anchor';
2481
- const sessionStateDir = join(stateDir, 'sessions', sessionId);
2482
- const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
2483
- const anchorPane = '%99';
2484
- const livePane = '%42';
2485
- try {
2486
- await mkdir(sessionStateDir, { recursive: true });
2487
- await mkdir(fakeBinDir, { recursive: true });
2488
- await writeSessionStart(wd, sessionId);
2489
- const managedSessionName = buildTmuxSessionName(wd, sessionId);
2490
- await writeFile(join(fakeBinDir, 'tmux'), buildManagedRalphTmux(tmuxLogPath, {
2491
- cwd: wd,
2492
- managedSessionName,
2493
- anchorPane,
2494
- livePane,
2495
- codexPanes: [
2496
- { paneId: anchorPane, active: false, currentCommand: 'codex', startCommand: 'codex' },
2497
- { paneId: livePane, active: true, currentCommand: 'codex', startCommand: 'codex' },
2498
- ],
2499
- }));
2500
- await chmod(join(fakeBinDir, 'tmux'), 0o755);
2501
- await writeFile(ralphStatePath, JSON.stringify({
2502
- active: true,
2503
- current_phase: 'executing',
2504
- tmux_pane_id: anchorPane,
2505
- }, null, 2));
2506
- await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
2507
- last_progress_at: new Date(Date.now() - 61_000).toISOString(),
2508
- }, null, 2));
2509
- await writeFile(watcherStatePath, JSON.stringify({
2510
- ralph_continue_steer: {
2511
- last_sent_at: new Date(Date.now() - 61_000).toISOString(),
2512
- },
2513
- }, null, 2));
2514
- const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2515
- const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2516
- const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
2517
- encoding: 'utf-8',
2518
- env: buildCleanNotifyEnv({
2519
- PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
2520
- }),
2521
- });
2522
- assert.equal(run.status, 0, run.stderr || run.stdout);
2523
- const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
2524
- assert.equal(persistedRalph.tmux_pane_id, anchorPane);
2525
- assert.equal(typeof persistedRalph.tmux_pane_set_at, 'undefined');
2526
- const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
2527
- assert.equal(watcherState.ralph_continue_steer?.last_reason, 'sent');
2528
- assert.equal(watcherState.ralph_continue_steer?.pane_id, anchorPane);
2529
- const tmuxLog = await readFile(tmuxLogPath, 'utf8');
2530
- assert.match(tmuxLog, /send-keys -t %99 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
2531
- assert.doesNotMatch(tmuxLog, /send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
2532
- }
2533
- finally {
2534
- await rm(wd, { recursive: true, force: true });
2535
- }
2536
- });
2537
- it('rebinds a shell-degraded codex anchor to the live pane before continue steer', async () => {
2538
- const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-rebind-degraded-codex-anchor-'));
2539
- const fakeBinDir = join(wd, 'fake-bin');
2540
- const stateDir = join(wd, '.omx', 'state');
2541
- const tmuxLogPath = join(wd, 'tmux.log');
2542
- const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
2543
- const sessionId = 'sess-ralph-degraded-codex-anchor';
2544
- const sessionStateDir = join(stateDir, 'sessions', sessionId);
2545
- const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
2546
- const anchorPane = '%99';
2547
- const livePane = '%42';
2548
- try {
2549
- await mkdir(sessionStateDir, { recursive: true });
2550
- await mkdir(fakeBinDir, { recursive: true });
2551
- await writeSessionStart(wd, sessionId);
2552
- const managedSessionName = buildTmuxSessionName(wd, sessionId);
2553
- await writeFile(join(fakeBinDir, 'tmux'), buildManagedRalphTmux(tmuxLogPath, {
2554
- cwd: wd,
2555
- managedSessionName,
2556
- anchorPane,
2557
- livePane,
2558
- codexPanes: [
2559
- { paneId: anchorPane, active: true, currentCommand: 'bash', startCommand: 'codex --model gpt-5' },
2560
- { paneId: livePane, active: false, currentCommand: 'codex', startCommand: 'codex' },
2561
- ],
2562
- }));
2563
- await chmod(join(fakeBinDir, 'tmux'), 0o755);
2564
- await writeFile(ralphStatePath, JSON.stringify({
2565
- active: true,
2566
- current_phase: 'executing',
2567
- tmux_pane_id: anchorPane,
2568
- }, null, 2));
2569
- await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
2570
- last_progress_at: new Date(Date.now() - 61_000).toISOString(),
2571
- }, null, 2));
2572
- await writeFile(watcherStatePath, JSON.stringify({
2573
- ralph_continue_steer: {
2574
- last_sent_at: new Date(Date.now() - 61_000).toISOString(),
2575
- },
2576
- }, null, 2));
2577
- const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2578
- const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2579
- const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
2580
- encoding: 'utf-8',
2581
- env: buildCleanNotifyEnv({
2582
- PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
2583
- }),
2584
- });
2585
- assert.equal(run.status, 0, run.stderr || run.stdout);
2586
- const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
2587
- assert.equal(persistedRalph.tmux_pane_id, livePane);
2588
- assert.match(persistedRalph.tmux_pane_set_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
2589
- const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
2590
- assert.equal(watcherState.ralph_continue_steer?.last_reason, 'sent');
2591
- assert.equal(watcherState.ralph_continue_steer?.pane_id, livePane);
2592
- const tmuxLog = await readFile(tmuxLogPath, 'utf8');
2593
- assert.match(tmuxLog, /send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
2594
- assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
2595
- }
2596
- finally {
2597
- await rm(wd, { recursive: true, force: true });
2598
- }
2599
- });
2600
- it('falls back to the current managed session pane when the stored Ralph pane anchor is dead', async () => {
2601
- const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-rebind-dead-anchor-'));
2602
- const fakeBinDir = join(wd, 'fake-bin');
2603
- const stateDir = join(wd, '.omx', 'state');
2604
- const tmuxLogPath = join(wd, 'tmux.log');
2605
- const watcherStatePath = join(stateDir, 'notify-fallback-state.json');
2606
- const sessionId = 'sess-ralph-dead-anchor';
2607
- const sessionStateDir = join(stateDir, 'sessions', sessionId);
2608
- const ralphStatePath = join(sessionStateDir, 'ralph-state.json');
2609
- const anchorPane = '%99';
2610
- const livePane = '%42';
2611
- try {
2612
- await mkdir(sessionStateDir, { recursive: true });
2613
- await mkdir(fakeBinDir, { recursive: true });
2614
- await writeSessionStart(wd, sessionId);
2615
- const managedSessionName = buildTmuxSessionName(wd, sessionId);
2616
- await writeFile(join(fakeBinDir, 'tmux'), buildManagedRalphTmux(tmuxLogPath, {
2617
- cwd: wd,
2618
- managedSessionName,
2619
- anchorPane,
2620
- livePane,
2621
- codexPanes: [
2622
- { paneId: '%41', active: false, currentCommand: 'codex', startCommand: 'codex' },
2623
- { paneId: livePane, active: true, currentCommand: 'codex', startCommand: 'codex' },
2624
- ],
2625
- missingAnchor: true,
2626
- }));
2627
- await chmod(join(fakeBinDir, 'tmux'), 0o755);
2628
- await writeFile(ralphStatePath, JSON.stringify({
2629
- active: true,
2630
- current_phase: 'executing',
2631
- tmux_pane_id: anchorPane,
2632
- }, null, 2));
2633
- await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
2634
- last_progress_at: new Date(Date.now() - 61_000).toISOString(),
2635
- }, null, 2));
2636
- await writeFile(watcherStatePath, JSON.stringify({
2637
- ralph_continue_steer: {
2638
- last_sent_at: new Date(Date.now() - 61_000).toISOString(),
2639
- },
2640
- }, null, 2));
2641
- const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2642
- const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2643
- const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], {
2644
- encoding: 'utf-8',
2645
- env: buildCleanNotifyEnv({
2646
- PATH: `${fakeBinDir}:${process.env.PATH || ''}`,
2647
- }),
2648
- });
2649
- assert.equal(run.status, 0, run.stderr || run.stdout);
2650
- const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
2651
- assert.equal(persistedRalph.tmux_pane_id, livePane);
2652
- assert.match(persistedRalph.tmux_pane_set_at ?? '', /^\d{4}-\d{2}-\d{2}T/);
2653
- const watcherState = JSON.parse(await readFile(watcherStatePath, 'utf-8'));
2654
- assert.equal(watcherState.ralph_continue_steer?.last_reason, 'sent');
2655
- assert.equal(watcherState.ralph_continue_steer?.pane_id, livePane);
2656
- const tmuxLog = await readFile(tmuxLogPath, 'utf8');
2657
- assert.match(tmuxLog, /display-message -p -t %99 #S/);
2658
- assert.match(tmuxLog, /list-panes -s -t .*sess-ralph-dead-anchor/);
2659
- assert.match(tmuxLog, /send-keys -t %42 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
2660
- assert.doesNotMatch(tmuxLog, /send-keys -t %99 -l Ralph loop active continue \[OMX_TMUX_INJECT\]/);
2661
- }
2662
- finally {
2663
- await rm(wd, { recursive: true, force: true });
2275
+ it('fails closed without rebinding stale, degraded, or missing Ralph pane anchors', async () => {
2276
+ const watcherScript = new URL('../../../dist/scripts/notify-fallback-watcher.js', import.meta.url).pathname;
2277
+ const notifyHook = new URL('../../../dist/scripts/notify-hook.js', import.meta.url).pathname;
2278
+ for (const scenario of ['stale shell anchor', 'degraded codex anchor', 'missing anchor']) {
2279
+ const wd = await mkdtemp(join(tmpdir(), 'omx-fallback-ralph-no-rebind-'));
2280
+ const fakeBinDir = join(wd, 'fake-bin');
2281
+ const stateDir = join(wd, '.omx', 'state');
2282
+ const tmuxLogPath = join(wd, 'tmux.log');
2283
+ const ralphStatePath = join(stateDir, 'ralph-state.json');
2284
+ try {
2285
+ await mkdir(stateDir, { recursive: true });
2286
+ await mkdir(fakeBinDir, { recursive: true });
2287
+ await writeFile(tmuxLogPath, '');
2288
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, { paneOwner: 'ralph:owner' }));
2289
+ await chmod(join(fakeBinDir, 'tmux'), 0o755);
2290
+ await writeFile(ralphStatePath, JSON.stringify({
2291
+ active: true,
2292
+ current_phase: 'executing',
2293
+ tmux_pane_id: '%99',
2294
+ tmux_pane_pid: 4242,
2295
+ tmux_session_name: 'session-test',
2296
+ tmux_pane_owner_id: 'ralph:owner',
2297
+ scenario,
2298
+ }, null, 2));
2299
+ await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({ last_progress_at: new Date(Date.now() - 61_000).toISOString() }));
2300
+ await writeFile(join(stateDir, 'notify-fallback-state.json'), JSON.stringify({ ralph_continue_steer: { last_sent_at: new Date(Date.now() - 61_000).toISOString() } }));
2301
+ const run = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook], {
2302
+ encoding: 'utf-8',
2303
+ env: buildCleanNotifyEnv({ PATH: `${fakeBinDir}:${process.env.PATH || ''}` }),
2304
+ });
2305
+ assert.equal(run.status, 0, run.stderr || run.stdout);
2306
+ const persistedRalph = JSON.parse(await readFile(ralphStatePath, 'utf-8'));
2307
+ assert.equal(persistedRalph.tmux_pane_id, '%99', `${scenario} must retain its stored pane identity`);
2308
+ const watcherState = JSON.parse(await readFile(join(stateDir, 'notify-fallback-state.json'), 'utf-8'));
2309
+ assert.equal(watcherState.ralph_continue_steer?.last_reason, 'pane_binding_changed');
2310
+ const tmuxLog = await readFile(tmuxLogPath, 'utf8');
2311
+ assert.doesNotMatch(tmuxLog, /send-keys -t %(42|99) -l Ralph loop active continue/);
2312
+ assert.doesNotMatch(tmuxLog, /list-panes -s -t/, 'watcher must not discover a replacement pane');
2313
+ }
2314
+ finally {
2315
+ await rm(wd, { recursive: true, force: true });
2316
+ }
2664
2317
  }
2665
2318
  });
2666
2319
  it('sends the first Ralph continue steer immediately when persisted steer state is empty', async () => {
@@ -2672,12 +2325,15 @@ exit 0
2672
2325
  try {
2673
2326
  await mkdir(stateDir, { recursive: true });
2674
2327
  await mkdir(fakeBinDir, { recursive: true });
2675
- await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2328
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, { paneOwner: 'ralph:owner' }));
2676
2329
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
2677
2330
  await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
2678
2331
  active: true,
2679
2332
  current_phase: 'executing',
2680
2333
  tmux_pane_id: '%42',
2334
+ tmux_pane_pid: 4242,
2335
+ tmux_session_name: 'session-test',
2336
+ tmux_pane_owner_id: 'ralph:owner',
2681
2337
  }, null, 2));
2682
2338
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2683
2339
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -2716,12 +2372,15 @@ exit 0
2716
2372
  try {
2717
2373
  await mkdir(stateDir, { recursive: true });
2718
2374
  await mkdir(fakeBinDir, { recursive: true });
2719
- await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2375
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, { paneOwner: 'ralph:owner' }));
2720
2376
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
2721
2377
  await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
2722
2378
  active: true,
2723
2379
  current_phase: 'executing',
2724
2380
  tmux_pane_id: '%42',
2381
+ tmux_pane_pid: 4242,
2382
+ tmux_session_name: 'session-test',
2383
+ tmux_pane_owner_id: 'ralph:owner',
2725
2384
  }, null, 2));
2726
2385
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2727
2386
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -2767,6 +2426,9 @@ exit 0
2767
2426
  current_phase: 'blocked_on_user',
2768
2427
  completed_at: new Date().toISOString(),
2769
2428
  tmux_pane_id: '%42',
2429
+ tmux_pane_pid: 4242,
2430
+ tmux_session_name: 'session-test',
2431
+ tmux_pane_owner_id: 'ralph:owner',
2770
2432
  }, null, 2));
2771
2433
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2772
2434
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -2806,12 +2468,15 @@ exit 0
2806
2468
  try {
2807
2469
  await mkdir(stateDir, { recursive: true });
2808
2470
  await mkdir(fakeBinDir, { recursive: true });
2809
- await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2471
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, { paneOwner: 'ralph:owner' }));
2810
2472
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
2811
2473
  await writeFile(ralphStatePath, JSON.stringify({
2812
2474
  active: true,
2813
2475
  current_phase: 'executing',
2814
2476
  tmux_pane_id: '%42',
2477
+ tmux_pane_pid: 4242,
2478
+ tmux_session_name: 'session-test',
2479
+ tmux_pane_owner_id: 'ralph:owner',
2815
2480
  }, null, 2));
2816
2481
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2817
2482
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -2840,6 +2505,9 @@ exit 0
2840
2505
  current_phase: 'complete',
2841
2506
  completed_at: new Date().toISOString(),
2842
2507
  tmux_pane_id: '%42',
2508
+ tmux_pane_pid: 4242,
2509
+ tmux_session_name: 'session-test',
2510
+ tmux_pane_owner_id: 'ralph:owner',
2843
2511
  }, null, 2));
2844
2512
  const terminalRun = spawnSync(process.execPath, [watcherScript, '--once', '--cwd', wd, '--notify-script', notifyHook, '--poll-ms', '50'], { encoding: 'utf-8', env });
2845
2513
  assert.equal(terminalRun.status, 0, terminalRun.stderr || terminalRun.stdout);
@@ -2871,6 +2539,9 @@ exit 0
2871
2539
  current_phase: 'starting',
2872
2540
  started_at: staleStartedAt,
2873
2541
  tmux_pane_id: '%42',
2542
+ tmux_pane_pid: 4242,
2543
+ tmux_session_name: 'session-test',
2544
+ tmux_pane_owner_id: 'ralph:owner',
2874
2545
  }, null, 2));
2875
2546
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2876
2547
  last_progress_at: new Date(Date.now() - 5 * 60_000).toISOString(),
@@ -2916,6 +2587,9 @@ exit 0
2916
2587
  current_phase: 'executing',
2917
2588
  run_outcome: 'blocked_on_user',
2918
2589
  tmux_pane_id: '%42',
2590
+ tmux_pane_pid: 4242,
2591
+ tmux_session_name: 'session-test',
2592
+ tmux_pane_owner_id: 'ralph:owner',
2919
2593
  }, null, 2));
2920
2594
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2921
2595
  last_progress_at: new Date(Date.now() - 5 * 60_000).toISOString(),
@@ -2954,12 +2628,15 @@ exit 0
2954
2628
  try {
2955
2629
  await mkdir(stateDir, { recursive: true });
2956
2630
  await mkdir(fakeBinDir, { recursive: true });
2957
- await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
2631
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, { paneOwner: 'ralph:owner' }));
2958
2632
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
2959
2633
  await writeFile(join(stateDir, 'ralph-state.json'), JSON.stringify({
2960
2634
  active: true,
2961
2635
  current_phase: 'executing',
2962
2636
  tmux_pane_id: '%42',
2637
+ tmux_pane_pid: 4242,
2638
+ tmux_session_name: 'session-test',
2639
+ tmux_pane_owner_id: 'ralph:owner',
2963
2640
  }, null, 2));
2964
2641
  await writeFile(join(stateDir, 'hud-state.json'), JSON.stringify({
2965
2642
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -3005,9 +2682,11 @@ exit 0
3005
2682
  await mkdir(fakeBinDir, { recursive: true });
3006
2683
  await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, {
3007
2684
  failSendKeysMatch: 'Ralph loop active continue',
2685
+ ralphPane: { paneId: '%43', panePid: 4343, paneOwner: 'ralph:owner' },
3008
2686
  }));
3009
2687
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
3010
2688
  await initTeamState('dispatch-team', 'task', 'executor', 1, wd);
2689
+ await bindCanonicalDispatchWorkerFixture(wd);
3011
2690
  const queued = await enqueueDispatchRequest('dispatch-team', {
3012
2691
  kind: 'inbox',
3013
2692
  to_worker: 'worker-1',
@@ -3017,7 +2696,10 @@ exit 0
3017
2696
  await writeFile(join(wd, '.omx', 'state', 'ralph-state.json'), JSON.stringify({
3018
2697
  active: true,
3019
2698
  current_phase: 'executing',
3020
- tmux_pane_id: '%42',
2699
+ tmux_pane_id: '%43',
2700
+ tmux_pane_pid: 4343,
2701
+ tmux_session_name: 'session-test',
2702
+ tmux_pane_owner_id: 'ralph:owner',
3021
2703
  }, null, 2));
3022
2704
  await writeFile(join(wd, '.omx', 'state', 'hud-state.json'), JSON.stringify({
3023
2705
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -3084,6 +2766,7 @@ exit 0
3084
2766
  'ready', 'ready', 'ping', 'ping', 'ping', 'ping', 'ping', 'ping',
3085
2767
  ].join('\n'));
3086
2768
  await initTeamState('dispatch-team', 'task', 'executor', 1, wd);
2769
+ await bindCanonicalDispatchWorkerFixture(wd);
3087
2770
  const queued = await enqueueDispatchRequest('dispatch-team', {
3088
2771
  kind: 'inbox',
3089
2772
  to_worker: 'worker-1',
@@ -3249,6 +2932,9 @@ exit 0
3249
2932
  active: true,
3250
2933
  current_phase: 'executing',
3251
2934
  tmux_pane_id: '%42',
2935
+ tmux_pane_pid: 4242,
2936
+ tmux_session_name: 'session-test',
2937
+ tmux_pane_owner_id: 'ralph:owner',
3252
2938
  }, null, 2));
3253
2939
  await writeFile(watcherStatePath, JSON.stringify({
3254
2940
  ralph_continue_steer: {
@@ -3292,6 +2978,9 @@ exit 0
3292
2978
  active: true,
3293
2979
  current_phase: 'executing',
3294
2980
  tmux_pane_id: '%42',
2981
+ tmux_pane_pid: 4242,
2982
+ tmux_session_name: 'session-test',
2983
+ tmux_pane_owner_id: 'ralph:owner',
3295
2984
  }, null, 2));
3296
2985
  await writeFile(watcherStatePath, JSON.stringify({
3297
2986
  ralph_continue_steer: {
@@ -3339,6 +3028,9 @@ exit 0
3339
3028
  active: true,
3340
3029
  current_phase: 'executing',
3341
3030
  tmux_pane_id: '%42',
3031
+ tmux_pane_pid: 4242,
3032
+ tmux_session_name: 'session-test',
3033
+ tmux_pane_owner_id: 'ralph:owner',
3342
3034
  }, null, 2));
3343
3035
  await writeFile(join(sessionStateDir, 'hud-state.json'), JSON.stringify({
3344
3036
  last_progress_at: new Date(Date.now() - 61_000).toISOString(),
@@ -3348,7 +3040,7 @@ exit 0
3348
3040
  last_sent_at: new Date(Date.now() - 61_000).toISOString(),
3349
3041
  },
3350
3042
  }, null, 2));
3351
- await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath));
3043
+ await writeFile(join(fakeBinDir, 'tmux'), buildFakeTmux(tmuxLogPath, { paneOwner: 'ralph:owner' }));
3352
3044
  await chmod(join(fakeBinDir, 'tmux'), 0o755);
3353
3045
  const shortLivedParent = spawn(process.execPath, ['-e', 'setTimeout(() => process.exit(0), 10)'], {
3354
3046
  stdio: 'ignore',
@@ -3383,6 +3075,9 @@ exit 0
3383
3075
  current_phase: 'complete',
3384
3076
  completed_at: new Date().toISOString(),
3385
3077
  tmux_pane_id: '%42',
3078
+ tmux_pane_pid: 4242,
3079
+ tmux_session_name: 'session-test',
3080
+ tmux_pane_owner_id: 'ralph:owner',
3386
3081
  }, null, 2));
3387
3082
  await waitForExit(child, 4000);
3388
3083
  assert.equal(child.exitCode, 0);
@@ -4053,4 +3748,18 @@ setInterval(() => {}, 1000);
4053
3748
  }
4054
3749
  });
4055
3750
  });
3751
+ describe('notify fallback delivery protocol wiring', () => {
3752
+ it('routes rollout completions through durable delivery authority before spawning', async () => {
3753
+ const source = await readFile(new URL('../../scripts/notify-fallback-watcher.js', import.meta.url), 'utf-8');
3754
+ assert.match(source, /deliverNotifyFallback\(/);
3755
+ assert.match(source, /fallback_notify_claim/);
3756
+ assert.match(source, /authorityDeadlineAtMs/);
3757
+ assert.match(source, /setTimeout\(\(\) => \{ void stopForTimeout\(false\); \}, 10_000\)/);
3758
+ assert.match(source, /compactNotifyFallbackDeliveries\(stateDir\)/);
3759
+ assert.doesNotMatch(source, /spawnSync\(process\.execPath, \[notifyScript/);
3760
+ assert.match(source, /spawn\(process\.execPath, \[notifyScript/);
3761
+ assert.match(source, /SIGTERM/);
3762
+ assert.match(source, /SIGKILL/);
3763
+ });
3764
+ });
4056
3765
  //# sourceMappingURL=notify-fallback-watcher.test.js.map