oh-my-codex 0.11.13 → 0.12.0

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 (369) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +27 -17
  4. package/crates/omx-runtime/src/main.rs +6 -2
  5. package/dist/agents/native-config.js +1 -1
  6. package/dist/agents/native-config.js.map +1 -1
  7. package/dist/cli/__tests__/autoresearch-guided.test.js +74 -2
  8. package/dist/cli/__tests__/autoresearch-guided.test.js.map +1 -1
  9. package/dist/cli/__tests__/cleanup.test.js +22 -30
  10. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  11. package/dist/cli/__tests__/error-handling-warnings.test.js +3 -1
  12. package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
  13. package/dist/cli/__tests__/index.test.js +217 -4
  14. package/dist/cli/__tests__/index.test.js.map +1 -1
  15. package/dist/cli/__tests__/setup-refresh.test.js +49 -9
  16. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  17. package/dist/cli/__tests__/setup-scope.test.js +9 -0
  18. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  19. package/dist/cli/__tests__/team.test.js +136 -11
  20. package/dist/cli/__tests__/team.test.js.map +1 -1
  21. package/dist/cli/__tests__/uninstall.test.js +10 -0
  22. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  23. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +1 -0
  24. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
  25. package/dist/cli/autoresearch-guided.d.ts.map +1 -1
  26. package/dist/cli/autoresearch-guided.js +2 -1
  27. package/dist/cli/autoresearch-guided.js.map +1 -1
  28. package/dist/cli/autoresearch.d.ts.map +1 -1
  29. package/dist/cli/autoresearch.js +2 -1
  30. package/dist/cli/autoresearch.js.map +1 -1
  31. package/dist/cli/cleanup.d.ts.map +1 -1
  32. package/dist/cli/cleanup.js +4 -2
  33. package/dist/cli/cleanup.js.map +1 -1
  34. package/dist/cli/index.d.ts +12 -0
  35. package/dist/cli/index.d.ts.map +1 -1
  36. package/dist/cli/index.js +238 -30
  37. package/dist/cli/index.js.map +1 -1
  38. package/dist/cli/omx.js +2 -0
  39. package/dist/cli/omx.js.map +1 -1
  40. package/dist/cli/setup.d.ts +1 -0
  41. package/dist/cli/setup.d.ts.map +1 -1
  42. package/dist/cli/setup.js +41 -7
  43. package/dist/cli/setup.js.map +1 -1
  44. package/dist/cli/team.d.ts.map +1 -1
  45. package/dist/cli/team.js +16 -557
  46. package/dist/cli/team.js.map +1 -1
  47. package/dist/cli/uninstall.d.ts.map +1 -1
  48. package/dist/cli/uninstall.js +34 -9
  49. package/dist/cli/uninstall.js.map +1 -1
  50. package/dist/config/__tests__/generator-idempotent.test.js +79 -2
  51. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  52. package/dist/config/__tests__/generator-notify.test.js +2 -0
  53. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  54. package/dist/config/codex-hooks.d.ts +11 -0
  55. package/dist/config/codex-hooks.d.ts.map +1 -0
  56. package/dist/config/codex-hooks.js +50 -0
  57. package/dist/config/codex-hooks.js.map +1 -0
  58. package/dist/config/generator.d.ts +5 -3
  59. package/dist/config/generator.d.ts.map +1 -1
  60. package/dist/config/generator.js +24 -14
  61. package/dist/config/generator.js.map +1 -1
  62. package/dist/hooks/__tests__/debugger-log-recency-contract.test.d.ts +2 -0
  63. package/dist/hooks/__tests__/debugger-log-recency-contract.test.d.ts.map +1 -0
  64. package/dist/hooks/__tests__/debugger-log-recency-contract.test.js +20 -0
  65. package/dist/hooks/__tests__/debugger-log-recency-contract.test.js.map +1 -0
  66. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +236 -2
  67. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  68. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js +86 -0
  69. package/dist/hooks/__tests__/notify-hook-all-workers-idle.test.js.map +1 -1
  70. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +40 -0
  71. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  72. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.d.ts +2 -0
  73. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.d.ts.map +1 -0
  74. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js +54 -0
  75. package/dist/hooks/__tests__/notify-hook-managed-tmux.test.js.map +1 -0
  76. package/dist/hooks/__tests__/notify-hook-modules.test.js +31 -0
  77. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  78. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +51 -0
  79. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -1
  80. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.d.ts +2 -0
  81. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.d.ts.map +1 -0
  82. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js +136 -0
  83. package/dist/hooks/__tests__/notify-hook-session-idle-dedupe.test.js.map +1 -0
  84. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +120 -0
  85. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  86. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +145 -20
  87. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  88. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js +116 -0
  89. package/dist/hooks/__tests__/notify-hook-team-tmux-guard.test.js.map +1 -1
  90. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +86 -0
  91. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  92. package/dist/hooks/__tests__/pre-context-gate-skills.test.js +1 -0
  93. package/dist/hooks/__tests__/pre-context-gate-skills.test.js.map +1 -1
  94. package/dist/hooks/extensibility/__tests__/runtime.test.js +49 -0
  95. package/dist/hooks/extensibility/__tests__/runtime.test.js.map +1 -1
  96. package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
  97. package/dist/hooks/extensibility/runtime.js +10 -0
  98. package/dist/hooks/extensibility/runtime.js.map +1 -1
  99. package/dist/hooks/extensibility/types.d.ts +1 -1
  100. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  101. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  102. package/dist/hooks/prompt-guidance-contract.js +12 -8
  103. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  104. package/dist/hooks/session.d.ts +5 -1
  105. package/dist/hooks/session.d.ts.map +1 -1
  106. package/dist/hooks/session.js +10 -6
  107. package/dist/hooks/session.js.map +1 -1
  108. package/dist/hud/index.d.ts.map +1 -1
  109. package/dist/hud/index.js +6 -1
  110. package/dist/hud/index.js.map +1 -1
  111. package/dist/mcp/__tests__/bootstrap.test.js +0 -3
  112. package/dist/mcp/__tests__/bootstrap.test.js.map +1 -1
  113. package/dist/mcp/__tests__/code-intel-server.test.js +27 -1
  114. package/dist/mcp/__tests__/code-intel-server.test.js.map +1 -1
  115. package/dist/mcp/__tests__/server-lifecycle.test.js +0 -5
  116. package/dist/mcp/__tests__/server-lifecycle.test.js.map +1 -1
  117. package/dist/mcp/bootstrap.d.ts +1 -1
  118. package/dist/mcp/bootstrap.d.ts.map +1 -1
  119. package/dist/mcp/bootstrap.js +0 -1
  120. package/dist/mcp/bootstrap.js.map +1 -1
  121. package/dist/mcp/code-intel-server.d.ts +20 -0
  122. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  123. package/dist/mcp/code-intel-server.js +6 -5
  124. package/dist/mcp/code-intel-server.js.map +1 -1
  125. package/dist/notifications/__tests__/idle-cooldown.test.js +24 -1
  126. package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
  127. package/dist/notifications/__tests__/reply-listener.test.js +20 -1
  128. package/dist/notifications/__tests__/reply-listener.test.js.map +1 -1
  129. package/dist/notifications/__tests__/tmux.test.js +41 -0
  130. package/dist/notifications/__tests__/tmux.test.js.map +1 -1
  131. package/dist/notifications/idle-cooldown.d.ts +13 -0
  132. package/dist/notifications/idle-cooldown.d.ts.map +1 -1
  133. package/dist/notifications/idle-cooldown.js +50 -16
  134. package/dist/notifications/idle-cooldown.js.map +1 -1
  135. package/dist/notifications/reply-listener.d.ts.map +1 -1
  136. package/dist/notifications/reply-listener.js +2 -0
  137. package/dist/notifications/reply-listener.js.map +1 -1
  138. package/dist/notifications/tmux.d.ts.map +1 -1
  139. package/dist/notifications/tmux.js +4 -0
  140. package/dist/notifications/tmux.js.map +1 -1
  141. package/dist/scripts/__tests__/codex-native-hook.test.d.ts +2 -0
  142. package/dist/scripts/__tests__/codex-native-hook.test.d.ts.map +1 -0
  143. package/dist/scripts/__tests__/codex-native-hook.test.js +720 -0
  144. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -0
  145. package/dist/scripts/codex-native-hook.d.ts +22 -0
  146. package/dist/scripts/codex-native-hook.d.ts.map +1 -0
  147. package/dist/scripts/codex-native-hook.js +594 -0
  148. package/dist/scripts/codex-native-hook.js.map +1 -0
  149. package/dist/scripts/codex-native-pre-post.d.ts +26 -0
  150. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -0
  151. package/dist/scripts/codex-native-pre-post.js +118 -0
  152. package/dist/scripts/codex-native-pre-post.js.map +1 -0
  153. package/dist/scripts/notify-fallback-watcher.js +262 -18
  154. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  155. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  156. package/dist/scripts/notify-hook/auto-nudge.js +5 -6
  157. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  158. package/dist/scripts/notify-hook/log.d.ts +2 -2
  159. package/dist/scripts/notify-hook/log.d.ts.map +1 -1
  160. package/dist/scripts/notify-hook/log.js +10 -2
  161. package/dist/scripts/notify-hook/log.js.map +1 -1
  162. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -1
  163. package/dist/scripts/notify-hook/managed-tmux.js +2 -0
  164. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -1
  165. package/dist/scripts/notify-hook/orchestration-intent.d.ts +18 -0
  166. package/dist/scripts/notify-hook/orchestration-intent.d.ts.map +1 -0
  167. package/dist/scripts/notify-hook/orchestration-intent.js +72 -0
  168. package/dist/scripts/notify-hook/orchestration-intent.js.map +1 -0
  169. package/dist/scripts/notify-hook/process-runner.js.map +1 -1
  170. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  171. package/dist/scripts/notify-hook/ralph-session-resume.js +7 -0
  172. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  173. package/dist/scripts/notify-hook/team-dispatch.d.ts +15 -6
  174. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  175. package/dist/scripts/notify-hook/team-dispatch.js +125 -6
  176. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  177. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +3 -2
  178. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  179. package/dist/scripts/notify-hook/team-leader-nudge.js +165 -37
  180. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  181. package/dist/scripts/notify-hook/team-tmux-guard.d.ts +4 -1
  182. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  183. package/dist/scripts/notify-hook/team-tmux-guard.js +33 -44
  184. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  185. package/dist/scripts/notify-hook/team-worker.d.ts.map +1 -1
  186. package/dist/scripts/notify-hook/team-worker.js +68 -5
  187. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  188. package/dist/scripts/notify-hook/utils.d.ts +1 -1
  189. package/dist/scripts/notify-hook/utils.d.ts.map +1 -1
  190. package/dist/scripts/notify-hook/utils.js.map +1 -1
  191. package/dist/scripts/notify-hook.js +55 -32
  192. package/dist/scripts/notify-hook.js.map +1 -1
  193. package/dist/team/__tests__/api-interop.test.js +344 -18
  194. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  195. package/dist/team/__tests__/delivery-e2e-smoke.test.d.ts +2 -0
  196. package/dist/team/__tests__/delivery-e2e-smoke.test.d.ts.map +1 -0
  197. package/dist/team/__tests__/delivery-e2e-smoke.test.js +671 -0
  198. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -0
  199. package/dist/team/__tests__/mcp-comm.test.js +5 -0
  200. package/dist/team/__tests__/mcp-comm.test.js.map +1 -1
  201. package/dist/team/__tests__/runtime.test.js +422 -12
  202. package/dist/team/__tests__/runtime.test.js.map +1 -1
  203. package/dist/team/__tests__/state.test.js +126 -8
  204. package/dist/team/__tests__/state.test.js.map +1 -1
  205. package/dist/team/__tests__/team-ops-contract.test.js +4 -0
  206. package/dist/team/__tests__/team-ops-contract.test.js.map +1 -1
  207. package/dist/team/__tests__/tmux-session.test.js +160 -0
  208. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  209. package/dist/team/__tests__/worker-bootstrap.test.js +19 -1
  210. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  211. package/dist/team/api-interop.d.ts.map +1 -1
  212. package/dist/team/api-interop.js +95 -23
  213. package/dist/team/api-interop.js.map +1 -1
  214. package/dist/team/contracts.d.ts +11 -1
  215. package/dist/team/contracts.d.ts.map +1 -1
  216. package/dist/team/contracts.js +29 -0
  217. package/dist/team/contracts.js.map +1 -1
  218. package/dist/team/delivery-log.d.ts +14 -0
  219. package/dist/team/delivery-log.d.ts.map +1 -0
  220. package/dist/team/delivery-log.js +35 -0
  221. package/dist/team/delivery-log.js.map +1 -0
  222. package/dist/team/idle-nudge.d.ts +2 -2
  223. package/dist/team/idle-nudge.js +2 -2
  224. package/dist/team/mcp-comm.d.ts +4 -0
  225. package/dist/team/mcp-comm.d.ts.map +1 -1
  226. package/dist/team/mcp-comm.js +84 -1
  227. package/dist/team/mcp-comm.js.map +1 -1
  228. package/dist/team/pane-status.d.ts +149 -0
  229. package/dist/team/pane-status.d.ts.map +1 -0
  230. package/dist/team/pane-status.js +558 -0
  231. package/dist/team/pane-status.js.map +1 -0
  232. package/dist/team/reminder-intents.d.ts +11 -0
  233. package/dist/team/reminder-intents.d.ts.map +1 -0
  234. package/dist/team/reminder-intents.js +40 -0
  235. package/dist/team/reminder-intents.js.map +1 -0
  236. package/dist/team/runtime-cli.d.ts +1 -1
  237. package/dist/team/runtime-cli.js +2 -2
  238. package/dist/team/runtime-cli.js.map +1 -1
  239. package/dist/team/runtime.d.ts +2 -1
  240. package/dist/team/runtime.d.ts.map +1 -1
  241. package/dist/team/runtime.js +407 -190
  242. package/dist/team/runtime.js.map +1 -1
  243. package/dist/team/scaling.d.ts.map +1 -1
  244. package/dist/team/scaling.js +6 -5
  245. package/dist/team/scaling.js.map +1 -1
  246. package/dist/team/state/dispatch.d.ts +4 -1
  247. package/dist/team/state/dispatch.d.ts.map +1 -1
  248. package/dist/team/state/dispatch.js +59 -18
  249. package/dist/team/state/dispatch.js.map +1 -1
  250. package/dist/team/state/mailbox.d.ts.map +1 -1
  251. package/dist/team/state/mailbox.js +39 -2
  252. package/dist/team/state/mailbox.js.map +1 -1
  253. package/dist/team/state/monitor.d.ts +2 -1
  254. package/dist/team/state/monitor.d.ts.map +1 -1
  255. package/dist/team/state/monitor.js +30 -1
  256. package/dist/team/state/monitor.js.map +1 -1
  257. package/dist/team/state/types.d.ts +5 -2
  258. package/dist/team/state/types.d.ts.map +1 -1
  259. package/dist/team/state/types.js.map +1 -1
  260. package/dist/team/state.d.ts +30 -3
  261. package/dist/team/state.d.ts.map +1 -1
  262. package/dist/team/state.js +170 -2
  263. package/dist/team/state.js.map +1 -1
  264. package/dist/team/team-ops.d.ts +5 -1
  265. package/dist/team/team-ops.d.ts.map +1 -1
  266. package/dist/team/team-ops.js +4 -0
  267. package/dist/team/team-ops.js.map +1 -1
  268. package/dist/team/tmux-session.d.ts +2 -0
  269. package/dist/team/tmux-session.d.ts.map +1 -1
  270. package/dist/team/tmux-session.js +19 -3
  271. package/dist/team/tmux-session.js.map +1 -1
  272. package/dist/team/worker-bootstrap.d.ts +4 -0
  273. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  274. package/dist/team/worker-bootstrap.js +33 -6
  275. package/dist/team/worker-bootstrap.js.map +1 -1
  276. package/dist/utils/__tests__/paths.test.js +63 -1
  277. package/dist/utils/__tests__/paths.test.js.map +1 -1
  278. package/dist/utils/__tests__/platform-command.test.js +50 -4
  279. package/dist/utils/__tests__/platform-command.test.js.map +1 -1
  280. package/dist/utils/paths.d.ts +12 -0
  281. package/dist/utils/paths.d.ts.map +1 -1
  282. package/dist/utils/paths.js +44 -2
  283. package/dist/utils/paths.js.map +1 -1
  284. package/dist/utils/platform-command.d.ts.map +1 -1
  285. package/dist/utils/platform-command.js +13 -5
  286. package/dist/utils/platform-command.js.map +1 -1
  287. package/dist/utils/sleep.d.ts.map +1 -1
  288. package/dist/utils/sleep.js +10 -1
  289. package/dist/utils/sleep.js.map +1 -1
  290. package/package.json +1 -1
  291. package/prompts/analyst.md +2 -2
  292. package/prompts/api-reviewer.md +2 -2
  293. package/prompts/architect.md +2 -2
  294. package/prompts/build-fixer.md +2 -2
  295. package/prompts/code-reviewer.md +2 -2
  296. package/prompts/code-simplifier.md +1 -1
  297. package/prompts/critic.md +2 -2
  298. package/prompts/debugger.md +3 -2
  299. package/prompts/dependency-expert.md +2 -2
  300. package/prompts/designer.md +2 -2
  301. package/prompts/executor.md +3 -2
  302. package/prompts/explore.md +2 -2
  303. package/prompts/git-master.md +2 -2
  304. package/prompts/information-architect.md +2 -2
  305. package/prompts/performance-reviewer.md +2 -2
  306. package/prompts/planner.md +3 -2
  307. package/prompts/product-analyst.md +2 -2
  308. package/prompts/product-manager.md +2 -2
  309. package/prompts/qa-tester.md +2 -2
  310. package/prompts/quality-reviewer.md +2 -2
  311. package/prompts/quality-strategist.md +2 -2
  312. package/prompts/researcher.md +2 -2
  313. package/prompts/security-reviewer.md +2 -2
  314. package/prompts/sisyphus-lite.md +2 -2
  315. package/prompts/style-reviewer.md +2 -2
  316. package/prompts/team-executor.md +2 -2
  317. package/prompts/test-engineer.md +2 -2
  318. package/prompts/ux-researcher.md +2 -2
  319. package/prompts/verifier.md +3 -2
  320. package/prompts/vision.md +2 -2
  321. package/prompts/writer.md +2 -2
  322. package/skills/team/SKILL.md +18 -33
  323. package/src/scripts/__tests__/codex-native-hook.test.ts +931 -0
  324. package/src/scripts/codex-native-hook.ts +721 -0
  325. package/src/scripts/codex-native-pre-post.ts +161 -0
  326. package/src/scripts/notify-fallback-watcher.ts +318 -26
  327. package/src/scripts/notify-hook/auto-nudge.ts +5 -10
  328. package/src/scripts/notify-hook/log.ts +18 -4
  329. package/src/scripts/notify-hook/managed-tmux.ts +1 -0
  330. package/src/scripts/notify-hook/orchestration-intent.ts +82 -0
  331. package/src/scripts/notify-hook/process-runner.ts +4 -4
  332. package/src/scripts/notify-hook/ralph-session-resume.ts +9 -0
  333. package/src/scripts/notify-hook/team-dispatch.ts +134 -6
  334. package/src/scripts/notify-hook/team-leader-nudge.ts +183 -37
  335. package/src/scripts/notify-hook/team-tmux-guard.ts +35 -43
  336. package/src/scripts/notify-hook/team-worker.ts +73 -4
  337. package/src/scripts/notify-hook/utils.ts +1 -1
  338. package/src/scripts/notify-hook.ts +64 -32
  339. package/templates/AGENTS.md +21 -11
  340. package/README.de.md +0 -263
  341. package/README.el.md +0 -223
  342. package/README.es.md +0 -263
  343. package/README.fr.md +0 -263
  344. package/README.it.md +0 -263
  345. package/README.ja.md +0 -264
  346. package/README.ko.md +0 -264
  347. package/README.pl.md +0 -216
  348. package/README.pt.md +0 -263
  349. package/README.ru.md +0 -263
  350. package/README.tr.md +0 -263
  351. package/README.vi.md +0 -223
  352. package/README.zh-TW.md +0 -293
  353. package/README.zh.md +0 -264
  354. package/dist/mcp/__tests__/team-server-cleanup.test.d.ts +0 -2
  355. package/dist/mcp/__tests__/team-server-cleanup.test.d.ts.map +0 -1
  356. package/dist/mcp/__tests__/team-server-cleanup.test.js +0 -219
  357. package/dist/mcp/__tests__/team-server-cleanup.test.js.map +0 -1
  358. package/dist/mcp/__tests__/team-server-runtime-deps.test.d.ts +0 -2
  359. package/dist/mcp/__tests__/team-server-runtime-deps.test.d.ts.map +0 -1
  360. package/dist/mcp/__tests__/team-server-runtime-deps.test.js +0 -13
  361. package/dist/mcp/__tests__/team-server-runtime-deps.test.js.map +0 -1
  362. package/dist/mcp/__tests__/team-server-wait.test.d.ts +0 -2
  363. package/dist/mcp/__tests__/team-server-wait.test.d.ts.map +0 -1
  364. package/dist/mcp/__tests__/team-server-wait.test.js +0 -155
  365. package/dist/mcp/__tests__/team-server-wait.test.js.map +0 -1
  366. package/dist/mcp/team-server.d.ts +0 -24
  367. package/dist/mcp/team-server.d.ts.map +0 -1
  368. package/dist/mcp/team-server.js +0 -482
  369. package/dist/mcp/team-server.js.map +0 -1
@@ -3,10 +3,11 @@ import { existsSync, appendFileSync, mkdirSync } from 'fs';
3
3
  import { mkdir, readdir, readFile, writeFile } from 'fs/promises';
4
4
  import { performance } from 'perf_hooks';
5
5
  import { spawn, spawnSync } from 'child_process';
6
- import { sanitizeTeamName, isTmuxAvailable, createTeamSession, buildWorkerProcessLaunchSpec, resolveTeamWorkerCli, resolveTeamWorkerCliPlan, resolveTeamWorkerLaunchMode, waitForWorkerReady, dismissTrustPromptIfPresent, sleepFractionalSeconds, sendToWorker, sendToWorkerStdin, isWorkerAlive, getWorkerPanePid, killWorkerByPaneIdAsync, restoreStandaloneHudPane, teardownWorkerPanes, unregisterResizeHook, destroyTeamSession, listTeamSessions, } from './tmux-session.js';
6
+ import { sanitizeTeamName, isTmuxAvailable, hasCurrentTmuxClientContext, createTeamSession, buildWorkerProcessLaunchSpec, resolveTeamWorkerCli, resolveTeamWorkerCliPlan, resolveTeamWorkerLaunchMode, waitForWorkerReady, dismissTrustPromptIfPresent, sleepFractionalSeconds, sendToWorker, sendToWorkerStdin, isWorkerAlive, getWorkerPanePid, killWorkerByPaneIdAsync, restoreStandaloneHudPane, teardownWorkerPanes, unregisterResizeHook, destroyTeamSession, listPaneIds, listTeamSessions, } from './tmux-session.js';
7
7
  import { teamInit as initTeamState, DEFAULT_MAX_WORKERS, teamReadConfig as readTeamConfig, teamWriteWorkerIdentity as writeWorkerIdentity, teamReadWorkerHeartbeat as readWorkerHeartbeat, teamReadWorkerStatus as readWorkerStatus, teamWriteWorkerInbox as writeWorkerInbox, teamCreateTask as createStateTask, teamReadTask as readTask, teamListTasks as listTasks, teamReadManifest as readTeamManifestV2, teamNormalizeGovernance as normalizeTeamGovernance, teamNormalizePolicy as normalizeTeamPolicy, teamClaimTask as claimTask, teamReleaseTaskClaim as releaseTaskClaim, teamReclaimExpiredTaskClaim as reclaimExpiredTaskClaim, teamAppendEvent as appendTeamEvent, teamReadTaskApproval as readTaskApproval, teamListMailbox as listMailboxMessages, teamMarkMessageDelivered as markMessageDelivered, teamMarkMessageNotified as markMessageNotified, teamEnqueueDispatchRequest as enqueueDispatchRequest, teamMarkDispatchRequestNotified as markDispatchRequestNotified, teamTransitionDispatchRequest as transitionDispatchRequest, teamReadDispatchRequest as readDispatchRequest, teamCleanup as cleanupTeamState, teamSaveConfig as saveTeamConfig, teamWriteShutdownRequest as writeShutdownRequest, teamReadShutdownAck as readShutdownAck, teamReadMonitorSnapshot as readMonitorSnapshot, teamWriteMonitorSnapshot as writeMonitorSnapshot, teamReadPhase as readTeamPhaseState, teamWritePhase as writeTeamPhaseState, } from './team-ops.js';
8
8
  import { queueInboxInstruction, queueDirectMailboxMessage, queueBroadcastMailboxMessage, waitForDispatchReceipt, } from './mcp-comm.js';
9
- import { generateWorkerOverlay, writeTeamWorkerInstructionsFile, removeTeamWorkerInstructionsFile, writeWorkerWorktreeRootAgentsFile, removeWorkerWorktreeRootAgentsFile, generateInitialInbox, generateTaskAssignmentInbox, generateShutdownInbox, generateTriggerMessage, generateMailboxTriggerMessage, generateLeaderMailboxTriggerMessage, writeWorkerRoleInstructionsFile, } from './worker-bootstrap.js';
9
+ import { appendTeamDeliveryLogForCwd } from './delivery-log.js';
10
+ import { generateWorkerOverlay, writeTeamWorkerInstructionsFile, removeTeamWorkerInstructionsFile, writeWorkerWorktreeRootAgentsFile, removeWorkerWorktreeRootAgentsFile, generateInitialInbox, generateTaskAssignmentInbox, generateShutdownInbox, buildTriggerDirective, buildMailboxTriggerDirective, buildLeaderMailboxTriggerDirective, writeWorkerRoleInstructionsFile, } from './worker-bootstrap.js';
10
11
  import { loadRolePrompt } from './role-router.js';
11
12
  import { composeRoleInstructionsForRole } from '../agents/native-config.js';
12
13
  import { codexPromptsDir } from '../utils/paths.js';
@@ -49,6 +50,58 @@ async function syncRootTeamModeStateOnTerminalPhase(teamName, phase, cwd) {
49
50
  // Best-effort compatibility sync only.
50
51
  }
51
52
  }
53
+ export function applyCreatedInteractiveSessionToConfig(config, createdSession, workerPaneIds) {
54
+ config.tmux_session = createdSession.name;
55
+ config.leader_pane_id = createdSession.leaderPaneId;
56
+ config.hud_pane_id = createdSession.hudPaneId;
57
+ config.resize_hook_name = createdSession.resizeHookName;
58
+ config.resize_hook_target = createdSession.resizeHookTarget;
59
+ for (let i = 0; i < createdSession.workerPaneIds.length; i++) {
60
+ const paneId = createdSession.workerPaneIds[i];
61
+ workerPaneIds[i] = paneId;
62
+ if (config.workers[i]) {
63
+ config.workers[i].pane_id = paneId;
64
+ }
65
+ }
66
+ }
67
+ function collectShutdownPaneIds(params) {
68
+ const { config, livePaneIds = [], restoredStandaloneHudPaneId = null } = params;
69
+ const excludedPaneIds = new Set([
70
+ config.leader_pane_id,
71
+ config.hud_pane_id,
72
+ restoredStandaloneHudPaneId,
73
+ ].filter((paneId) => typeof paneId === 'string' && paneId.trim().startsWith('%')));
74
+ const paneIds = new Set();
75
+ for (const paneId of [
76
+ ...config.workers.map((worker) => worker.pane_id),
77
+ ...livePaneIds,
78
+ ]) {
79
+ if (typeof paneId !== 'string')
80
+ continue;
81
+ const normalized = paneId.trim();
82
+ if (!normalized.startsWith('%'))
83
+ continue;
84
+ if (excludedPaneIds.has(normalized))
85
+ continue;
86
+ paneIds.add(normalized);
87
+ }
88
+ return [...paneIds];
89
+ }
90
+ async function logRuntimeDispatchOutcome(params) {
91
+ const { cwd, teamName, workerName, requestId, messageId, intent, outcome, source = 'team.runtime' } = params;
92
+ await appendTeamDeliveryLogForCwd(cwd, {
93
+ event: 'dispatch_result',
94
+ source,
95
+ team: teamName,
96
+ request_id: requestId,
97
+ message_id: messageId,
98
+ to_worker: workerName,
99
+ intent,
100
+ transport: outcome.transport,
101
+ result: outcome.ok ? 'confirmed' : 'failed',
102
+ reason: outcome.reason,
103
+ });
104
+ }
52
105
  function collectProvisionedShutdownWorktrees(config) {
53
106
  const seenWorktreePaths = new Set();
54
107
  const worktrees = [];
@@ -147,6 +200,36 @@ async function appendIntegrationEvent(teamName, type, worker, metadata, cwd) {
147
200
  async function sendIntegrationMessageToLeader(teamName, worker, body, cwd) {
148
201
  await sendWorkerMessage(teamName, worker.name, 'leader-fixed', body, cwd).catch(() => { });
149
202
  }
203
+ function leaderHeadAdvanced(before, after) {
204
+ return typeof after === 'string' && after.length > 0 && after !== before;
205
+ }
206
+ async function recordIntegrationFailure(teamName, worker, state, details, cwd) {
207
+ const leaderHeadAfter = details.leaderHeadAfter ?? details.leaderHeadBefore;
208
+ const sourceShort = details.sourceCommit.slice(0, 12);
209
+ const leaderShort = details.leaderHeadBefore.slice(0, 12);
210
+ state.last_leader_head = leaderHeadAfter;
211
+ state.status = 'integration_failed';
212
+ state.conflict_commit = details.sourceCommit;
213
+ state.conflict_files = undefined;
214
+ state.updated_at = new Date().toISOString();
215
+ await appendIntegrationEvent(teamName, 'worker_integration_failed', worker, {
216
+ worker_name: worker.name,
217
+ operation: details.operation,
218
+ source_commit: details.sourceCommit,
219
+ leader_head_before: details.leaderHeadBefore,
220
+ leader_head_after: leaderHeadAfter,
221
+ worktree_path: details.worktreePath,
222
+ summary: `${details.operation} for ${worker.name} reported success but leader HEAD did not advance`,
223
+ }, cwd);
224
+ appendIntegrationReport(teamName, {
225
+ workerName: worker.name,
226
+ operation: details.operation,
227
+ strategy: details.strategy,
228
+ files: [],
229
+ detail: `${details.operation} reported success for ${sourceShort}, but leader HEAD did not advance from ${leaderShort}; not marking worker as integrated.`,
230
+ }, cwd);
231
+ await sendIntegrationMessageToLeader(teamName, worker, `INTEGRATION FAILED: ${details.operation} for ${worker.name} reported success, but leader HEAD stayed at ${leaderShort}. Not emitting INTEGRATED; retry or inspect leader branch state before continuing.`, cwd);
232
+ }
150
233
  function autoCommitDirtyWorktree(worker) {
151
234
  const worktreePath = resolve(worker.worktree_path);
152
235
  const repoRoot = resolve(worker.worktree_repo_root);
@@ -247,8 +330,17 @@ async function integrateWorkerCommitsIntoLeader(params) {
247
330
  if (merge.ok) {
248
331
  const newLeaderHead = resolveLeaderHead(repoRoot, cwd) ?? leaderHead;
249
332
  const workerIntegrated = leaderContainsCommit(repoRoot, cwd, workerHead);
250
- const leaderAdvanced = newLeaderHead !== leaderHead;
251
- if (workerIntegrated && leaderAdvanced) {
333
+ if (!leaderHeadAdvanced(leaderHead, newLeaderHead)) {
334
+ await recordIntegrationFailure(teamName, worker, state, {
335
+ operation: 'merge',
336
+ sourceCommit: workerHead,
337
+ leaderHeadBefore: leaderHead,
338
+ leaderHeadAfter: newLeaderHead,
339
+ worktreePath,
340
+ strategy: '-X theirs',
341
+ }, cwd);
342
+ }
343
+ else if (workerIntegrated) {
252
344
  state.last_integrated_head = workerHead;
253
345
  state.last_leader_head = newLeaderHead;
254
346
  state.status = 'integrated';
@@ -383,6 +475,18 @@ async function integrateWorkerCommitsIntoLeader(params) {
383
475
  break;
384
476
  }
385
477
  const newLeaderHead = resolveLeaderHead(repoRoot, cwd) ?? leaderHead;
478
+ if (!leaderHeadAdvanced(leaderHead, newLeaderHead)) {
479
+ await recordIntegrationFailure(teamName, worker, state, {
480
+ operation: 'cherry-pick',
481
+ sourceCommit: commit,
482
+ leaderHeadBefore: leaderHead,
483
+ leaderHeadAfter: newLeaderHead,
484
+ worktreePath,
485
+ strategy: '-X theirs',
486
+ }, cwd);
487
+ allPicked = false;
488
+ break;
489
+ }
386
490
  state.last_integrated_head = commit;
387
491
  state.last_leader_head = newLeaderHead;
388
492
  state.status = 'integrated';
@@ -1172,7 +1276,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
1172
1276
  if (!isTmuxAvailable()) {
1173
1277
  throw new Error('Team mode requires tmux. Install with: apt install tmux / brew install tmux');
1174
1278
  }
1175
- if (!process.env.TMUX) {
1279
+ if (!hasCurrentTmuxClientContext()) {
1176
1280
  throw new Error('Team mode requires running inside tmux current leader pane');
1177
1281
  }
1178
1282
  }
@@ -1305,7 +1409,8 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
1305
1409
  rolePromptContent: rawRolePromptContent ?? undefined,
1306
1410
  worktreeRootAgentsCanonical: Boolean(workerWorkspace.worktreePath),
1307
1411
  });
1308
- const trigger = generateTriggerMessage(workerName, sanitized, resolveInstructionStateRoot(workerWorkspace.worktreePath));
1412
+ const triggerDirective = buildTriggerDirective(workerName, sanitized, resolveInstructionStateRoot(workerWorkspace.worktreePath));
1413
+ const trigger = triggerDirective.text;
1309
1414
  const initialPrompt = workerCliPlan[i - 1] === 'gemini' ? trigger : undefined;
1310
1415
  if (initialPrompt) {
1311
1416
  await writeWorkerInbox(sanitized, workerName, inbox, leaderCwd);
@@ -1319,6 +1424,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
1319
1424
  instructionsFilePath,
1320
1425
  inbox,
1321
1426
  trigger,
1427
+ triggerIntent: triggerDirective.intent,
1322
1428
  initialPrompt,
1323
1429
  workerLaunchArgs,
1324
1430
  workerCli: workerCliPlan[i - 1],
@@ -1355,14 +1461,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
1355
1461
  sessionCreated = true;
1356
1462
  createdWorkerPaneIds.push(...createdSession.workerPaneIds);
1357
1463
  createdLeaderPaneId = createdSession.leaderPaneId;
1358
- config.tmux_session = sessionName;
1359
- config.leader_pane_id = createdSession.leaderPaneId;
1360
- config.hud_pane_id = createdSession.hudPaneId;
1361
- config.resize_hook_name = createdSession.resizeHookName;
1362
- config.resize_hook_target = createdSession.resizeHookTarget;
1363
- for (let i = 0; i < createdSession.workerPaneIds.length; i++) {
1364
- workerPaneIds[i] = createdSession.workerPaneIds[i];
1365
- }
1464
+ applyCreatedInteractiveSessionToConfig(config, createdSession, workerPaneIds);
1366
1465
  }
1367
1466
  else {
1368
1467
  config.tmux_session = `prompt-${sanitized}`;
@@ -1388,13 +1487,14 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
1388
1487
  if (!bootstrapPlan) {
1389
1488
  throw new Error(`missing bootstrap plan for worker-${i}`);
1390
1489
  }
1391
- const { workerName, paneId, workerTasks, workerRole, inbox, trigger, initialPrompt } = {
1490
+ const { workerName, paneId, workerTasks, workerRole, inbox, trigger, triggerIntent, initialPrompt } = {
1392
1491
  workerName: bootstrapPlan.workerName,
1393
1492
  paneId: workerPaneIds[i - 1],
1394
1493
  workerTasks: bootstrapPlan.workerTasks,
1395
1494
  workerRole: bootstrapPlan.workerRole,
1396
1495
  inbox: bootstrapPlan.inbox,
1397
1496
  trigger: bootstrapPlan.trigger,
1497
+ triggerIntent: bootstrapPlan.triggerIntent,
1398
1498
  initialPrompt: bootstrapPlan.initialPrompt,
1399
1499
  };
1400
1500
  const workerWorkspace = bootstrapPlan.workerWorkspace;
@@ -1466,6 +1566,7 @@ export async function startTeam(teamName, task, agentType, workerCount, tasks, c
1466
1566
  workerCli: workerCliPlan[i - 1],
1467
1567
  inbox,
1468
1568
  triggerMessage: trigger,
1569
+ intent: triggerIntent,
1469
1570
  cwd: leaderCwd,
1470
1571
  dispatchPolicy,
1471
1572
  inboxCorrelationKey: `startup:${workerName}`,
@@ -1874,6 +1975,7 @@ export async function assignTask(teamName, workerName, taskId, cwd) {
1874
1975
  const maxAssignRetries = 2;
1875
1976
  const assignRetryDelayS = 2;
1876
1977
  let outcome = { ok: false, transport: 'none', reason: 'not_attempted' };
1978
+ const triggerDirective = buildTriggerDirective(workerName, sanitized, resolveInstructionStateRoot(workerInfo.worktree_path));
1877
1979
  for (let attempt = 1; attempt <= maxAssignRetries; attempt++) {
1878
1980
  outcome = await dispatchCriticalInboxInstruction({
1879
1981
  teamName: sanitized,
@@ -1882,7 +1984,8 @@ export async function assignTask(teamName, workerName, taskId, cwd) {
1882
1984
  workerIndex: workerInfo.index,
1883
1985
  paneId: workerInfo.pane_id,
1884
1986
  inbox,
1885
- triggerMessage: generateTriggerMessage(workerName, sanitized, resolveInstructionStateRoot(workerInfo.worktree_path)),
1987
+ triggerMessage: triggerDirective.text,
1988
+ intent: triggerDirective.intent,
1886
1989
  cwd,
1887
1990
  dispatchPolicy,
1888
1991
  inboxCorrelationKey: `assign:${taskId}:${workerName}`,
@@ -1988,6 +2091,7 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
1988
2091
  const requestedAt = new Date().toISOString();
1989
2092
  await writeShutdownRequest(sanitized, w.name, 'leader-fixed', cwd);
1990
2093
  shutdownRequestTimes.set(w.name, requestedAt);
2094
+ const triggerDirective = buildTriggerDirective(w.name, sanitized, resolveInstructionStateRoot(w.worktree_path));
1991
2095
  await dispatchCriticalInboxInstruction({
1992
2096
  teamName: sanitized,
1993
2097
  config,
@@ -1995,7 +2099,8 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
1995
2099
  workerIndex: w.index,
1996
2100
  paneId: w.pane_id,
1997
2101
  inbox: generateShutdownInbox(sanitized, w.name),
1998
- triggerMessage: generateTriggerMessage(w.name, sanitized, resolveInstructionStateRoot(w.worktree_path)),
2102
+ triggerMessage: triggerDirective.text,
2103
+ intent: triggerDirective.intent,
1999
2104
  cwd,
2000
2105
  dispatchPolicy,
2001
2106
  inboxCorrelationKey: `shutdown:${w.name}`,
@@ -2049,8 +2154,10 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
2049
2154
  const leaderPaneId = config.leader_pane_id;
2050
2155
  const hudPaneId = config.hud_pane_id;
2051
2156
  if (config.worker_launch_mode === 'interactive') {
2052
- const workerPanePids = config.workers
2053
- .map((w) => getWorkerPanePid(sessionName, w.index, w.pane_id))
2157
+ const livePaneIds = listPaneIds(sessionName);
2158
+ let shutdownPaneIds = collectShutdownPaneIds({ config, livePaneIds });
2159
+ const workerPanePids = shutdownPaneIds
2160
+ .map((paneId) => getWorkerPanePid(sessionName, 1, paneId))
2054
2161
  .filter((pid) => typeof pid === 'number' && Number.isFinite(pid) && pid > 0);
2055
2162
  for (const panePid of workerPanePids) {
2056
2163
  await terminateTrackedProcessTree(panePid);
@@ -2073,22 +2180,25 @@ export async function shutdownTeam(teamName, cwd, options = {}) {
2073
2180
  if (resizeHookWarning) {
2074
2181
  console.warn(`[team shutdown] ${sanitized}: ${resizeHookWarning}; continuing teardown`);
2075
2182
  }
2076
- const workerPaneIds = config.workers
2077
- .map((w) => w.pane_id)
2078
- .filter((paneId) => typeof paneId === 'string' && paneId.trim().length > 0);
2079
- await teardownWorkerPanes(workerPaneIds, {
2080
- leaderPaneId,
2081
- hudPaneId,
2082
- });
2183
+ let restoredHudPaneId = null;
2083
2184
  if (hudPaneId) {
2084
2185
  await killWorkerByPaneIdAsync(hudPaneId, leaderPaneId ?? undefined);
2085
2186
  if (sessionName.includes(':')) {
2086
- const restoredHudPaneId = restoreStandaloneHudPane(leaderPaneId, cwd);
2187
+ restoredHudPaneId = restoreStandaloneHudPane(leaderPaneId, cwd);
2087
2188
  if (!restoredHudPaneId) {
2088
2189
  console.warn(`[team shutdown] ${sanitized}: failed to restore standalone HUD pane`);
2089
2190
  }
2090
2191
  }
2091
2192
  }
2193
+ shutdownPaneIds = collectShutdownPaneIds({
2194
+ config,
2195
+ livePaneIds: listPaneIds(sessionName),
2196
+ restoredStandaloneHudPaneId: restoredHudPaneId,
2197
+ });
2198
+ await teardownWorkerPanes(shutdownPaneIds, {
2199
+ leaderPaneId,
2200
+ hudPaneId: restoredHudPaneId ?? hudPaneId,
2201
+ });
2092
2202
  // 4. Destroy tmux session
2093
2203
  if (!sessionName.includes(':')) {
2094
2204
  try {
@@ -2416,7 +2526,7 @@ async function markDispatchRequestLeaderPaneMissingDeferred(params) {
2416
2526
  }, cwd).catch(() => { });
2417
2527
  }
2418
2528
  async function dispatchCriticalInboxInstruction(params) {
2419
- const { teamName, config, workerName, workerIndex, paneId, workerCli, inbox, triggerMessage, cwd, dispatchPolicy, inboxCorrelationKey, requireWorkerStartupEvidence, } = params;
2529
+ const { teamName, config, workerName, workerIndex, paneId, workerCli, inbox, triggerMessage, intent, cwd, dispatchPolicy, inboxCorrelationKey, requireWorkerStartupEvidence, } = params;
2420
2530
  if (config.worker_launch_mode === 'prompt') {
2421
2531
  return await queueInboxInstruction({
2422
2532
  teamName,
@@ -2425,6 +2535,7 @@ async function dispatchCriticalInboxInstruction(params) {
2425
2535
  paneId,
2426
2536
  inbox,
2427
2537
  triggerMessage,
2538
+ intent,
2428
2539
  cwd,
2429
2540
  transportPreference: 'prompt_stdin',
2430
2541
  fallbackAllowed: false,
@@ -2440,6 +2551,7 @@ async function dispatchCriticalInboxInstruction(params) {
2440
2551
  paneId,
2441
2552
  inbox,
2442
2553
  triggerMessage,
2554
+ intent,
2443
2555
  cwd,
2444
2556
  transportPreference: 'transport_direct',
2445
2557
  fallbackAllowed: false,
@@ -2454,6 +2566,7 @@ async function dispatchCriticalInboxInstruction(params) {
2454
2566
  paneId,
2455
2567
  inbox,
2456
2568
  triggerMessage,
2569
+ intent,
2457
2570
  cwd,
2458
2571
  transportPreference: 'hook_preferred_with_fallback',
2459
2572
  fallbackAllowed: true,
@@ -2494,7 +2607,7 @@ async function dispatchCriticalInboxInstruction(params) {
2494
2607
  if (receipt?.status === 'failed') {
2495
2608
  const fallback = await notifyWorkerOutcome(config, workerIndex, triggerMessage, paneId);
2496
2609
  if (fallback.ok) {
2497
- await markDispatchRequestNotified(teamName, queued.request_id, { last_reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}`, failed_at: undefined }, cwd).catch(() => null);
2610
+ await transitionDispatchRequest(teamName, queued.request_id, 'failed', 'failed', { last_reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}` }, cwd).catch(() => { });
2498
2611
  return {
2499
2612
  ok: true,
2500
2613
  transport: fallback.transport,
@@ -2543,14 +2656,16 @@ async function dispatchCriticalInboxInstruction(params) {
2543
2656
  };
2544
2657
  }
2545
2658
  async function finalizeHookPreferredMailboxDispatch(params) {
2546
- const { teamName, requestId, workerName, workerIndex, paneId, messageId, triggerMessage, config, dispatchPolicy, cwd, fallbackNotify, } = params;
2659
+ const { teamName, requestId, workerName, workerIndex, paneId, messageId, triggerMessage, intent, config, dispatchPolicy, cwd, fallbackNotify, } = params;
2547
2660
  const receipt = await waitForDispatchReceipt(teamName, requestId, cwd, {
2548
2661
  timeoutMs: dispatchPolicy.dispatch_ack_timeout_ms,
2549
2662
  pollMs: 50,
2550
2663
  });
2551
2664
  if (receipt && (receipt.status === 'notified' || receipt.status === 'delivered')) {
2552
2665
  await markMessageNotified(teamName, workerName, messageId, cwd).catch(() => false);
2553
- return { ok: true, transport: 'hook', reason: `hook_receipt_${receipt.status}`, request_id: requestId, message_id: messageId };
2666
+ const outcome = { ok: true, transport: 'hook', reason: `hook_receipt_${receipt.status}`, request_id: requestId, message_id: messageId };
2667
+ await logRuntimeDispatchOutcome({ cwd, teamName, workerName, requestId, messageId, intent, outcome });
2668
+ return outcome;
2554
2669
  }
2555
2670
  const fallback = fallbackNotify
2556
2671
  ? await fallbackNotify()
@@ -2560,23 +2675,27 @@ async function finalizeHookPreferredMailboxDispatch(params) {
2560
2675
  if (receipt?.status === 'failed') {
2561
2676
  if (fallback.ok) {
2562
2677
  await markMessageNotified(teamName, workerName, messageId, cwd).catch(() => false);
2563
- await markDispatchRequestNotified(teamName, requestId, { message_id: messageId, last_reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}`, failed_at: undefined }, cwd).catch(() => null);
2564
- return {
2678
+ await transitionDispatchRequest(teamName, requestId, 'failed', 'failed', { message_id: messageId, last_reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}` }, cwd).catch(() => { });
2679
+ const outcome = {
2565
2680
  ok: true,
2566
2681
  transport: fallback.transport,
2567
2682
  reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}`,
2568
2683
  request_id: requestId,
2569
2684
  message_id: messageId,
2570
2685
  };
2686
+ await logRuntimeDispatchOutcome({ cwd, teamName, workerName, requestId, messageId, intent, outcome });
2687
+ return outcome;
2571
2688
  }
2572
2689
  await transitionDispatchRequest(teamName, requestId, 'failed', 'failed', { message_id: messageId, last_reason: `fallback_attempted_but_unconfirmed:${fallback.reason}` }, cwd).catch(() => { });
2573
- return {
2690
+ const outcome = {
2574
2691
  ok: false,
2575
2692
  transport: fallback.transport,
2576
2693
  reason: `fallback_attempted_but_unconfirmed:${fallback.reason}`,
2577
2694
  request_id: requestId,
2578
2695
  message_id: messageId,
2579
2696
  };
2697
+ await logRuntimeDispatchOutcome({ cwd, teamName, workerName, requestId, messageId, intent, outcome });
2698
+ return outcome;
2580
2699
  }
2581
2700
  if (fallback.ok) {
2582
2701
  if (isLeaderPaneMissingMailboxPersistedOutcome({ workerName, paneId, outcome: fallback })) {
@@ -2586,38 +2705,44 @@ async function finalizeHookPreferredMailboxDispatch(params) {
2586
2705
  messageId,
2587
2706
  cwd,
2588
2707
  });
2589
- return {
2708
+ const outcome = {
2590
2709
  ok: true,
2591
2710
  transport: fallback.transport,
2592
2711
  reason: 'leader_pane_missing_mailbox_persisted',
2593
2712
  request_id: requestId,
2594
2713
  message_id: messageId,
2595
2714
  };
2715
+ await logRuntimeDispatchOutcome({ cwd, teamName, workerName, requestId, messageId, intent, outcome });
2716
+ return outcome;
2596
2717
  }
2597
2718
  await markMessageNotified(teamName, workerName, messageId, cwd).catch(() => false);
2598
2719
  const marked = await markDispatchRequestNotified(teamName, requestId, { message_id: messageId, last_reason: `fallback_confirmed:${fallback.reason}` }, cwd);
2599
2720
  if (!marked) {
2600
2721
  await transitionDispatchRequest(teamName, requestId, 'failed', 'failed', { message_id: messageId, last_reason: `fallback_confirmed_after_failed_receipt:${fallback.reason}` }, cwd).catch(() => { });
2601
2722
  }
2602
- return {
2723
+ const outcome = {
2603
2724
  ok: true,
2604
2725
  transport: fallback.transport,
2605
2726
  reason: `hook_timeout_fallback_confirmed:${fallback.reason}`,
2606
2727
  request_id: requestId,
2607
2728
  message_id: messageId,
2608
2729
  };
2730
+ await logRuntimeDispatchOutcome({ cwd, teamName, workerName, requestId, messageId, intent, outcome });
2731
+ return outcome;
2609
2732
  }
2610
2733
  const current = await readDispatchRequest(teamName, requestId, cwd);
2611
2734
  if (current) {
2612
2735
  await transitionDispatchRequest(teamName, requestId, current.status, 'failed', { message_id: messageId, last_reason: `fallback_attempted_but_unconfirmed:${fallback.reason}` }, cwd).catch(() => { });
2613
2736
  }
2614
- return {
2737
+ const outcome = {
2615
2738
  ok: false,
2616
2739
  transport: fallback.transport,
2617
2740
  reason: `fallback_attempted_but_unconfirmed:${fallback.reason}`,
2618
2741
  request_id: requestId,
2619
2742
  message_id: messageId,
2620
2743
  };
2744
+ await logRuntimeDispatchOutcome({ cwd, teamName, workerName, requestId, messageId, intent, outcome });
2745
+ return outcome;
2621
2746
  }
2622
2747
  async function notifyLeaderAsync(config, message, cwd) {
2623
2748
  // Canonical leader delivery is durable mailbox persistence plus HUD-owned
@@ -2663,44 +2788,15 @@ async function deliverPendingMailboxMessages(teamName, config, workers, previous
2663
2788
  if (!worker.alive)
2664
2789
  continue;
2665
2790
  for (const msg of unnotified) {
2666
- const triggerMessage = generateMailboxTriggerMessage(worker.name, teamName, 1, resolveInstructionStateRoot(workerInfo.worktree_path));
2667
- const transportPreference = config.worker_launch_mode === 'prompt'
2668
- ? 'prompt_stdin'
2669
- : (dispatchPolicy.dispatch_mode === 'transport_direct' ? 'transport_direct' : 'hook_preferred_with_fallback');
2670
- const fallbackAllowed = transportPreference === 'hook_preferred_with_fallback';
2671
- const queued = await enqueueDispatchRequest(teamName, {
2672
- kind: 'mailbox',
2673
- to_worker: worker.name,
2674
- worker_index: workerInfo.index,
2675
- pane_id: workerInfo.pane_id,
2676
- trigger_message: triggerMessage,
2677
- message_id: msg.message_id,
2678
- transport_preference: transportPreference,
2679
- fallback_allowed: fallbackAllowed,
2680
- }, cwd);
2681
- let outcome;
2682
- if (transportPreference === 'hook_preferred_with_fallback') {
2683
- outcome = await finalizeHookPreferredMailboxDispatch({
2684
- teamName,
2685
- requestId: queued.request.request_id,
2686
- workerName: worker.name,
2687
- workerIndex: workerInfo.index,
2688
- paneId: workerInfo.pane_id,
2689
- messageId: msg.message_id,
2690
- triggerMessage,
2691
- config,
2692
- dispatchPolicy,
2693
- cwd,
2694
- });
2695
- }
2696
- else {
2697
- const direct = await notifyWorkerOutcome(config, workerInfo.index, triggerMessage, workerInfo.pane_id);
2698
- outcome = { ...direct, request_id: queued.request.request_id, message_id: msg.message_id };
2699
- if (outcome.ok) {
2700
- await markMessageNotified(teamName, worker.name, msg.message_id, cwd).catch(() => false);
2701
- await markDispatchRequestNotified(teamName, queued.request.request_id, { message_id: msg.message_id, last_reason: outcome.reason }, cwd).catch(() => null);
2702
- }
2703
- }
2791
+ const outcome = await dispatchPendingMailboxMessage({
2792
+ teamName,
2793
+ workerName: worker.name,
2794
+ workerInfo,
2795
+ messageId: msg.message_id,
2796
+ config,
2797
+ dispatchPolicy,
2798
+ cwd,
2799
+ });
2704
2800
  if (outcome.ok) {
2705
2801
  nextNotifications[msg.message_id] = new Date().toISOString();
2706
2802
  }
@@ -2713,87 +2809,175 @@ async function deliverPendingMailboxMessages(teamName, config, workers, previous
2713
2809
  }
2714
2810
  return pruned;
2715
2811
  }
2716
- export async function sendWorkerMessage(teamName, fromWorker, toWorker, body, cwd) {
2717
- const sanitized = sanitizeTeamName(teamName);
2718
- const config = await readTeamConfig(sanitized, cwd);
2719
- if (!config)
2720
- throw new Error(`Team ${sanitized} not found`);
2721
- const manifest = await readTeamManifestV2(sanitized, cwd);
2722
- const dispatchPolicy = resolveDispatchPolicy(manifest?.policy, config.worker_launch_mode);
2723
- if (toWorker === 'leader-fixed') {
2724
- const leaderTriggerMessage = generateLeaderMailboxTriggerMessage(sanitized, fromWorker);
2725
- const leaderTransportPreference = dispatchPolicy.dispatch_mode === 'transport_direct'
2726
- ? 'transport_direct'
2727
- : 'hook_preferred_with_fallback';
2728
- const outcome = await queueDirectMailboxMessage({
2729
- teamName: sanitized,
2730
- fromWorker,
2731
- toWorker,
2732
- toPaneId: config.leader_pane_id ?? undefined,
2733
- body,
2734
- triggerMessage: leaderTriggerMessage,
2812
+ function resolveWorkerMailboxTransportPreference(config, dispatchPolicy) {
2813
+ return config.worker_launch_mode === 'prompt'
2814
+ ? 'prompt_stdin'
2815
+ : (dispatchPolicy.dispatch_mode === 'transport_direct' ? 'transport_direct' : 'hook_preferred_with_fallback');
2816
+ }
2817
+ function resolveLeaderMailboxTransportPreference(dispatchPolicy) {
2818
+ return dispatchPolicy.dispatch_mode === 'transport_direct' ? 'transport_direct' : 'hook_preferred_with_fallback';
2819
+ }
2820
+ function isExistingMailboxNotificationOutcome(outcome) {
2821
+ return outcome.ok && outcome.reason === 'existing_message_already_notified';
2822
+ }
2823
+ async function dispatchPendingMailboxMessage(params) {
2824
+ const { teamName, workerName, workerInfo, messageId, config, dispatchPolicy, cwd } = params;
2825
+ const triggerDirective = buildMailboxTriggerDirective(workerName, teamName, 1, resolveInstructionStateRoot(workerInfo.worktree_path));
2826
+ const transportPreference = resolveWorkerMailboxTransportPreference(config, dispatchPolicy);
2827
+ const queued = await enqueueDispatchRequest(teamName, {
2828
+ kind: 'mailbox',
2829
+ to_worker: workerName,
2830
+ worker_index: workerInfo.index,
2831
+ pane_id: workerInfo.pane_id,
2832
+ trigger_message: triggerDirective.text,
2833
+ intent: triggerDirective.intent,
2834
+ message_id: messageId,
2835
+ transport_preference: transportPreference,
2836
+ fallback_allowed: transportPreference === 'hook_preferred_with_fallback',
2837
+ }, cwd);
2838
+ if (transportPreference === 'hook_preferred_with_fallback') {
2839
+ return await finalizeQueuedMailboxDispatch({
2840
+ queuedOutcome: {
2841
+ ok: true,
2842
+ transport: 'hook',
2843
+ reason: 'queued_for_hook_dispatch',
2844
+ request_id: queued.request.request_id,
2845
+ message_id: messageId,
2846
+ },
2847
+ transportPreference,
2848
+ teamName,
2849
+ workerName,
2850
+ workerIndex: workerInfo.index,
2851
+ paneId: workerInfo.pane_id,
2852
+ messageId,
2853
+ triggerMessage: triggerDirective.text,
2854
+ intent: triggerDirective.intent,
2855
+ config,
2856
+ dispatchPolicy,
2735
2857
  cwd,
2736
- transportPreference: leaderTransportPreference,
2737
- fallbackAllowed: leaderTransportPreference === 'hook_preferred_with_fallback',
2738
- notify: async (_target, message) => (leaderTransportPreference === 'hook_preferred_with_fallback'
2739
- ? { ok: true, transport: 'hook', reason: 'queued_for_hook_dispatch' }
2740
- : await notifyLeaderAsync(config, message, cwd)),
2741
2858
  });
2742
- let finalOutcome = outcome;
2743
- const mailboxAlreadyNotified = outcome.ok && outcome.reason === 'existing_message_already_notified';
2744
- if (!mailboxAlreadyNotified && leaderTransportPreference === 'hook_preferred_with_fallback' && !config.leader_pane_id) {
2745
- if (outcome.request_id) {
2746
- await markDispatchRequestLeaderPaneMissingDeferred({
2747
- teamName: sanitized,
2748
- requestId: outcome.request_id,
2749
- messageId: outcome.message_id,
2750
- cwd,
2751
- });
2752
- }
2753
- finalOutcome = {
2754
- ...outcome,
2755
- ok: true,
2756
- transport: 'mailbox',
2757
- reason: 'leader_pane_missing_mailbox_persisted',
2758
- };
2759
- }
2760
- const canLeaderFallbackDirectly = Boolean(config.leader_pane_id) && isTmuxAvailable();
2761
- if (!mailboxAlreadyNotified && leaderTransportPreference === 'hook_preferred_with_fallback' && canLeaderFallbackDirectly) {
2762
- if (!outcome.request_id || !outcome.message_id) {
2763
- throw new Error('mailbox_notify_failed:dispatch_request_missing_id');
2764
- }
2765
- finalOutcome = await finalizeHookPreferredMailboxDispatch({
2766
- teamName: sanitized,
2767
- requestId: outcome.request_id,
2768
- workerName: 'leader-fixed',
2769
- paneId: config.leader_pane_id ?? undefined,
2770
- messageId: outcome.message_id,
2771
- triggerMessage: leaderTriggerMessage,
2772
- config,
2773
- dispatchPolicy,
2859
+ }
2860
+ const direct = await notifyWorkerOutcome(config, workerInfo.index, triggerDirective.text, workerInfo.pane_id);
2861
+ const outcome = { ...direct, request_id: queued.request.request_id, message_id: messageId };
2862
+ if (outcome.ok) {
2863
+ await markMessageNotified(teamName, workerName, messageId, cwd).catch(() => false);
2864
+ await markDispatchRequestNotified(teamName, queued.request.request_id, { message_id: messageId, last_reason: outcome.reason }, cwd).catch(() => null);
2865
+ }
2866
+ await logRuntimeDispatchOutcome({
2867
+ cwd,
2868
+ teamName,
2869
+ workerName,
2870
+ requestId: queued.request.request_id,
2871
+ messageId,
2872
+ outcome,
2873
+ });
2874
+ return outcome;
2875
+ }
2876
+ async function finalizeQueuedMailboxDispatch(params) {
2877
+ const { queuedOutcome, transportPreference, teamName, workerName, workerIndex, paneId, messageId, triggerMessage, intent, config, dispatchPolicy, cwd, fallbackNotify, } = params;
2878
+ if (transportPreference !== 'hook_preferred_with_fallback') {
2879
+ return queuedOutcome;
2880
+ }
2881
+ if (isExistingMailboxNotificationOutcome(queuedOutcome)) {
2882
+ return queuedOutcome;
2883
+ }
2884
+ if (!queuedOutcome.request_id || !messageId) {
2885
+ return { ...queuedOutcome, ok: false, reason: 'dispatch_request_missing_id' };
2886
+ }
2887
+ return await finalizeHookPreferredMailboxDispatch({
2888
+ teamName,
2889
+ requestId: queuedOutcome.request_id,
2890
+ workerName,
2891
+ workerIndex,
2892
+ paneId,
2893
+ messageId,
2894
+ triggerMessage,
2895
+ intent,
2896
+ config,
2897
+ dispatchPolicy,
2898
+ cwd,
2899
+ fallbackNotify,
2900
+ });
2901
+ }
2902
+ async function sendLeaderMailboxMessage(params) {
2903
+ const { teamName, fromWorker, body, config, dispatchPolicy, cwd } = params;
2904
+ const triggerDirective = buildLeaderMailboxTriggerDirective(teamName, fromWorker);
2905
+ const transportPreference = resolveLeaderMailboxTransportPreference(dispatchPolicy);
2906
+ const queuedOutcome = await queueDirectMailboxMessage({
2907
+ teamName,
2908
+ fromWorker,
2909
+ toWorker: 'leader-fixed',
2910
+ toPaneId: config.leader_pane_id ?? undefined,
2911
+ body,
2912
+ triggerMessage: triggerDirective.text,
2913
+ intent: triggerDirective.intent,
2914
+ cwd,
2915
+ transportPreference,
2916
+ fallbackAllowed: transportPreference === 'hook_preferred_with_fallback',
2917
+ notify: async (_target, message) => (transportPreference === 'hook_preferred_with_fallback'
2918
+ ? { ok: true, transport: 'hook', reason: 'queued_for_hook_dispatch' }
2919
+ : await notifyLeaderAsync(config, message, cwd)),
2920
+ });
2921
+ if (!isExistingMailboxNotificationOutcome(queuedOutcome)
2922
+ && transportPreference === 'hook_preferred_with_fallback'
2923
+ && !config.leader_pane_id) {
2924
+ if (queuedOutcome.request_id) {
2925
+ await markDispatchRequestLeaderPaneMissingDeferred({
2926
+ teamName,
2927
+ requestId: queuedOutcome.request_id,
2928
+ messageId: queuedOutcome.message_id,
2774
2929
  cwd,
2775
- fallbackNotify: async () => await notifyLeaderAsync(config, leaderTriggerMessage, cwd),
2776
2930
  });
2777
2931
  }
2778
- if (!finalOutcome.ok)
2779
- throw new Error(`mailbox_notify_failed:${finalOutcome.reason}`);
2780
- return finalOutcome;
2932
+ const deferredOutcome = {
2933
+ ...queuedOutcome,
2934
+ ok: true,
2935
+ transport: 'mailbox',
2936
+ reason: 'leader_pane_missing_mailbox_persisted',
2937
+ };
2938
+ await logRuntimeDispatchOutcome({
2939
+ cwd,
2940
+ teamName,
2941
+ workerName: 'leader-fixed',
2942
+ requestId: deferredOutcome.request_id,
2943
+ messageId: deferredOutcome.message_id,
2944
+ intent: triggerDirective.intent,
2945
+ outcome: deferredOutcome,
2946
+ });
2947
+ return deferredOutcome;
2781
2948
  }
2782
- const recipient = config.workers.find((w) => w.name === toWorker);
2949
+ const canLeaderFallbackDirectly = Boolean(config.leader_pane_id) && isTmuxAvailable();
2950
+ return await finalizeQueuedMailboxDispatch({
2951
+ queuedOutcome,
2952
+ transportPreference: canLeaderFallbackDirectly ? transportPreference : 'transport_direct',
2953
+ teamName,
2954
+ workerName: 'leader-fixed',
2955
+ paneId: config.leader_pane_id ?? undefined,
2956
+ messageId: queuedOutcome.message_id,
2957
+ triggerMessage: triggerDirective.text,
2958
+ intent: triggerDirective.intent,
2959
+ config,
2960
+ dispatchPolicy,
2961
+ cwd,
2962
+ fallbackNotify: async () => await notifyLeaderAsync(config, triggerDirective.text, cwd),
2963
+ });
2964
+ }
2965
+ async function sendRecipientMailboxMessage(params) {
2966
+ const { teamName, fromWorker, toWorker, body, config, dispatchPolicy, cwd } = params;
2967
+ const recipient = config.workers.find((worker) => worker.name === toWorker);
2783
2968
  if (!recipient)
2784
2969
  throw new Error(`Worker ${toWorker} not found in team`);
2785
- const triggerMessage = generateMailboxTriggerMessage(toWorker, sanitized, 1, resolveInstructionStateRoot(recipient.worktree_path));
2786
- const transportPreference = config.worker_launch_mode === 'prompt'
2787
- ? 'prompt_stdin'
2788
- : (dispatchPolicy.dispatch_mode === 'transport_direct' ? 'transport_direct' : 'hook_preferred_with_fallback');
2789
- const outcome = await queueDirectMailboxMessage({
2790
- teamName: sanitized,
2970
+ const triggerDirective = buildMailboxTriggerDirective(toWorker, teamName, 1, resolveInstructionStateRoot(recipient.worktree_path));
2971
+ const transportPreference = resolveWorkerMailboxTransportPreference(config, dispatchPolicy);
2972
+ const queuedOutcome = await queueDirectMailboxMessage({
2973
+ teamName,
2791
2974
  fromWorker,
2792
2975
  toWorker,
2793
2976
  toWorkerIndex: recipient.index,
2794
2977
  toPaneId: recipient.pane_id,
2795
2978
  body,
2796
- triggerMessage,
2979
+ triggerMessage: triggerDirective.text,
2980
+ intent: triggerDirective.intent,
2797
2981
  cwd,
2798
2982
  transportPreference,
2799
2983
  fallbackAllowed: transportPreference === 'hook_preferred_with_fallback',
@@ -2801,25 +2985,82 @@ export async function sendWorkerMessage(teamName, fromWorker, toWorker, body, cw
2801
2985
  ? { ok: true, transport: 'hook', reason: 'queued_for_hook_dispatch' }
2802
2986
  : await notifyWorkerOutcome(config, recipient.index, message, recipient.pane_id)),
2803
2987
  });
2804
- let finalOutcome = outcome;
2805
- const mailboxAlreadyNotified = outcome.ok && outcome.reason === 'existing_message_already_notified';
2806
- if (!mailboxAlreadyNotified && transportPreference === 'hook_preferred_with_fallback') {
2807
- if (!outcome.request_id || !outcome.message_id) {
2808
- throw new Error('mailbox_notify_failed:dispatch_request_missing_id');
2988
+ return await finalizeQueuedMailboxDispatch({
2989
+ queuedOutcome,
2990
+ transportPreference,
2991
+ teamName,
2992
+ workerName: recipient.name,
2993
+ workerIndex: recipient.index,
2994
+ paneId: recipient.pane_id,
2995
+ messageId: queuedOutcome.message_id,
2996
+ triggerMessage: triggerDirective.text,
2997
+ intent: triggerDirective.intent,
2998
+ config,
2999
+ dispatchPolicy,
3000
+ cwd,
3001
+ });
3002
+ }
3003
+ async function finalizeBroadcastMailboxOutcomes(params) {
3004
+ const { teamName, outcomes, transportPreference, config, dispatchPolicy, cwd } = params;
3005
+ if (transportPreference !== 'hook_preferred_with_fallback') {
3006
+ return outcomes;
3007
+ }
3008
+ const finalizedOutcomes = [];
3009
+ for (const outcome of outcomes) {
3010
+ const target = outcome.to_worker
3011
+ ? (config.workers.find((worker) => worker.name === outcome.to_worker) ?? null)
3012
+ : null;
3013
+ if (!target) {
3014
+ finalizedOutcomes.push({ ...outcome, ok: false, reason: 'missing_worker_index' });
3015
+ continue;
2809
3016
  }
2810
- finalOutcome = await finalizeHookPreferredMailboxDispatch({
2811
- teamName: sanitized,
2812
- requestId: outcome.request_id,
2813
- workerName: recipient.name,
2814
- workerIndex: recipient.index,
2815
- paneId: recipient.pane_id,
3017
+ const triggerDirective = buildMailboxTriggerDirective(target.name, teamName, 1, resolveInstructionStateRoot(target.worktree_path));
3018
+ finalizedOutcomes.push(await finalizeQueuedMailboxDispatch({
3019
+ queuedOutcome: outcome,
3020
+ transportPreference,
3021
+ teamName,
3022
+ workerName: target.name,
3023
+ workerIndex: target.index,
3024
+ paneId: target.pane_id,
2816
3025
  messageId: outcome.message_id,
2817
- triggerMessage,
3026
+ triggerMessage: triggerDirective.text,
3027
+ intent: triggerDirective.intent,
3028
+ config,
3029
+ dispatchPolicy,
3030
+ cwd,
3031
+ }));
3032
+ }
3033
+ return finalizedOutcomes;
3034
+ }
3035
+ export async function sendWorkerMessage(teamName, fromWorker, toWorker, body, cwd) {
3036
+ const sanitized = sanitizeTeamName(teamName);
3037
+ const config = await readTeamConfig(sanitized, cwd);
3038
+ if (!config)
3039
+ throw new Error(`Team ${sanitized} not found`);
3040
+ const manifest = await readTeamManifestV2(sanitized, cwd);
3041
+ const dispatchPolicy = resolveDispatchPolicy(manifest?.policy, config.worker_launch_mode);
3042
+ if (toWorker === 'leader-fixed') {
3043
+ const finalOutcome = await sendLeaderMailboxMessage({
3044
+ teamName: sanitized,
3045
+ fromWorker,
3046
+ body,
2818
3047
  config,
2819
3048
  dispatchPolicy,
2820
3049
  cwd,
2821
3050
  });
3051
+ if (!finalOutcome.ok)
3052
+ throw new Error(`mailbox_notify_failed:${finalOutcome.reason}`);
3053
+ return finalOutcome;
2822
3054
  }
3055
+ const finalOutcome = await sendRecipientMailboxMessage({
3056
+ teamName: sanitized,
3057
+ fromWorker,
3058
+ toWorker,
3059
+ body,
3060
+ config,
3061
+ dispatchPolicy,
3062
+ cwd,
3063
+ });
2823
3064
  if (!finalOutcome.ok)
2824
3065
  throw new Error(`mailbox_notify_failed:${finalOutcome.reason}`);
2825
3066
  return finalOutcome;
@@ -2831,16 +3072,15 @@ export async function broadcastWorkerMessage(teamName, fromWorker, body, cwd) {
2831
3072
  throw new Error(`Team ${sanitized} not found`);
2832
3073
  const manifest = await readTeamManifestV2(sanitized, cwd);
2833
3074
  const dispatchPolicy = resolveDispatchPolicy(manifest?.policy, config.worker_launch_mode);
2834
- const transportPreference = config.worker_launch_mode === 'prompt'
2835
- ? 'prompt_stdin'
2836
- : (dispatchPolicy.dispatch_mode === 'transport_direct' ? 'transport_direct' : 'hook_preferred_with_fallback');
3075
+ const transportPreference = resolveWorkerMailboxTransportPreference(config, dispatchPolicy);
2837
3076
  const outcomes = await queueBroadcastMailboxMessage({
2838
3077
  teamName: sanitized,
2839
3078
  fromWorker,
2840
3079
  recipients: config.workers.map((w) => ({ workerName: w.name, workerIndex: w.index, paneId: w.pane_id })),
2841
3080
  body,
2842
3081
  cwd,
2843
- triggerFor: (workerName) => generateMailboxTriggerMessage(workerName, sanitized, 1, resolveInstructionStateRoot(config.workers.find((worker) => worker.name === workerName)?.worktree_path)),
3082
+ triggerFor: (workerName) => buildMailboxTriggerDirective(workerName, sanitized, 1, resolveInstructionStateRoot(config.workers.find((worker) => worker.name === workerName)?.worktree_path)).text,
3083
+ intentFor: () => 'pending-mailbox-review',
2844
3084
  transportPreference,
2845
3085
  fallbackAllowed: transportPreference === 'hook_preferred_with_fallback',
2846
3086
  notify: async (target, message) => transportPreference === 'hook_preferred_with_fallback'
@@ -2849,37 +3089,14 @@ export async function broadcastWorkerMessage(teamName, fromWorker, body, cwd) {
2849
3089
  ? await notifyWorkerOutcome(config, target.workerIndex, message, target.paneId)
2850
3090
  : { ok: false, transport: 'none', reason: 'missing_worker_index' }),
2851
3091
  });
2852
- const finalizedOutcomes = [];
2853
- for (const outcome of outcomes) {
2854
- if (transportPreference !== 'hook_preferred_with_fallback') {
2855
- finalizedOutcomes.push(outcome);
2856
- continue;
2857
- }
2858
- if (!outcome.request_id || !outcome.message_id) {
2859
- finalizedOutcomes.push({ ...outcome, ok: false, reason: 'dispatch_request_missing_id' });
2860
- continue;
2861
- }
2862
- const target = outcome.to_worker
2863
- ? (config.workers.find((w) => w.name === outcome.to_worker) ?? null)
2864
- : null;
2865
- if (!target) {
2866
- finalizedOutcomes.push({ ...outcome, ok: false, reason: 'missing_worker_index' });
2867
- continue;
2868
- }
2869
- finalizedOutcomes.push(await finalizeHookPreferredMailboxDispatch({
2870
- teamName: sanitized,
2871
- requestId: outcome.request_id,
2872
- workerName: target.name,
2873
- workerIndex: target.index,
2874
- paneId: target.pane_id,
2875
- messageId: outcome.message_id,
2876
- triggerMessage: generateMailboxTriggerMessage(target.name, sanitized, 1, resolveInstructionStateRoot(target.worktree_path)),
2877
- config,
2878
- dispatchPolicy,
2879
- cwd,
2880
- }));
2881
- }
2882
- const results = transportPreference === 'hook_preferred_with_fallback' ? finalizedOutcomes : outcomes;
3092
+ const results = await finalizeBroadcastMailboxOutcomes({
3093
+ teamName: sanitized,
3094
+ outcomes,
3095
+ transportPreference,
3096
+ config,
3097
+ dispatchPolicy,
3098
+ cwd,
3099
+ });
2883
3100
  if (results.some((result) => !result.ok)) {
2884
3101
  const firstFailure = results.find((result) => !result.ok);
2885
3102
  throw new Error(`mailbox_notify_failed:${firstFailure?.reason ?? 'unknown'}`);