oh-my-copilot 4.13.1 → 4.13.21

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 (507) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/README.md +3 -3
  4. package/bridge/cli.cjs +4709 -1961
  5. package/bridge/mcp-server.cjs +112 -11
  6. package/bridge/runtime-cli.cjs +4917 -2645
  7. package/bridge/team-bridge.cjs +6 -4
  8. package/bridge/team-mcp.cjs +19607 -0
  9. package/bridge/team.js +4895 -2215
  10. package/dist/__tests__/auto-slash-aliases.test.js +91 -0
  11. package/dist/__tests__/auto-slash-aliases.test.js.map +1 -1
  12. package/dist/__tests__/hooks/learner/bridge.test.js +25 -0
  13. package/dist/__tests__/hooks/learner/bridge.test.js.map +1 -1
  14. package/dist/__tests__/hooks/learner/parser.test.js +30 -0
  15. package/dist/__tests__/hooks/learner/parser.test.js.map +1 -1
  16. package/dist/__tests__/hud/extra-usage.test.js +42 -0
  17. package/dist/__tests__/hud/extra-usage.test.js.map +1 -1
  18. package/dist/__tests__/hud/labels.test.d.ts +2 -0
  19. package/dist/__tests__/hud/labels.test.d.ts.map +1 -0
  20. package/dist/__tests__/hud/labels.test.js +158 -0
  21. package/dist/__tests__/hud/labels.test.js.map +1 -0
  22. package/dist/__tests__/hud/render-enterprise.test.d.ts +5 -0
  23. package/dist/__tests__/hud/render-enterprise.test.d.ts.map +1 -0
  24. package/dist/__tests__/hud/render-enterprise.test.js +232 -0
  25. package/dist/__tests__/hud/render-enterprise.test.js.map +1 -0
  26. package/dist/__tests__/hud/render-rate-limits-priority.test.js +185 -0
  27. package/dist/__tests__/hud/render-rate-limits-priority.test.js.map +1 -1
  28. package/dist/__tests__/hud/stdin-session-scope.test.d.ts +2 -0
  29. package/dist/__tests__/hud/stdin-session-scope.test.d.ts.map +1 -0
  30. package/dist/__tests__/hud/stdin-session-scope.test.js +169 -0
  31. package/dist/__tests__/hud/stdin-session-scope.test.js.map +1 -0
  32. package/dist/__tests__/hud/usage-api.test.js +111 -1
  33. package/dist/__tests__/hud/usage-api.test.js.map +1 -1
  34. package/dist/__tests__/hud-marketplace-resolution.test.js +34 -0
  35. package/dist/__tests__/hud-marketplace-resolution.test.js.map +1 -1
  36. package/dist/__tests__/installer-hud-skip.test.js +6 -0
  37. package/dist/__tests__/installer-hud-skip.test.js.map +1 -1
  38. package/dist/__tests__/installer-omc-reference.test.js +1 -1
  39. package/dist/__tests__/installer-omc-reference.test.js.map +1 -1
  40. package/dist/__tests__/keyword-detector-echo-guard.test.d.ts +2 -0
  41. package/dist/__tests__/keyword-detector-echo-guard.test.d.ts.map +1 -0
  42. package/dist/__tests__/keyword-detector-echo-guard.test.js +218 -0
  43. package/dist/__tests__/keyword-detector-echo-guard.test.js.map +1 -0
  44. package/dist/__tests__/mnemosyne/detector.test.js +2 -0
  45. package/dist/__tests__/mnemosyne/detector.test.js.map +1 -1
  46. package/dist/__tests__/notepad.test.js +2 -2
  47. package/dist/__tests__/notepad.test.js.map +1 -1
  48. package/dist/__tests__/pre-tool-enforcer.test.js +84 -1
  49. package/dist/__tests__/pre-tool-enforcer.test.js.map +1 -1
  50. package/dist/__tests__/preemptive-compaction-hook.test.js +63 -0
  51. package/dist/__tests__/preemptive-compaction-hook.test.js.map +1 -1
  52. package/dist/__tests__/ralph-prd-mandatory.test.js +11 -1
  53. package/dist/__tests__/ralph-prd-mandatory.test.js.map +1 -1
  54. package/dist/__tests__/ralph-prd.test.js +40 -2
  55. package/dist/__tests__/ralph-prd.test.js.map +1 -1
  56. package/dist/__tests__/skills.test.js +363 -4
  57. package/dist/__tests__/skills.test.js.map +1 -1
  58. package/dist/__tests__/state-root-resolution.test.js +120 -6
  59. package/dist/__tests__/state-root-resolution.test.js.map +1 -1
  60. package/dist/autoresearch/__tests__/runtime.test.js +225 -28
  61. package/dist/autoresearch/__tests__/runtime.test.js.map +1 -1
  62. package/dist/autoresearch/runtime.d.ts.map +1 -1
  63. package/dist/autoresearch/runtime.js +9 -5
  64. package/dist/autoresearch/runtime.js.map +1 -1
  65. package/dist/cli/__tests__/team-status-worktree.test.d.ts +2 -0
  66. package/dist/cli/__tests__/team-status-worktree.test.d.ts.map +1 -0
  67. package/dist/cli/__tests__/team-status-worktree.test.js +96 -0
  68. package/dist/cli/__tests__/team-status-worktree.test.js.map +1 -0
  69. package/dist/cli/__tests__/team.test.js +164 -0
  70. package/dist/cli/__tests__/team.test.js.map +1 -1
  71. package/dist/cli/autoresearch.d.ts +1 -1
  72. package/dist/cli/autoresearch.d.ts.map +1 -1
  73. package/dist/cli/autoresearch.js +4 -4
  74. package/dist/cli/commands/team.d.ts +1 -0
  75. package/dist/cli/commands/team.d.ts.map +1 -1
  76. package/dist/cli/commands/team.js +15 -3
  77. package/dist/cli/commands/team.js.map +1 -1
  78. package/dist/cli/index.js +3 -0
  79. package/dist/cli/index.js.map +1 -1
  80. package/dist/cli/team.d.ts +6 -0
  81. package/dist/cli/team.d.ts.map +1 -1
  82. package/dist/cli/team.js +126 -5
  83. package/dist/cli/team.js.map +1 -1
  84. package/dist/cli/utils/formatting.d.ts +0 -10
  85. package/dist/cli/utils/formatting.d.ts.map +1 -1
  86. package/dist/cli/utils/formatting.js +0 -52
  87. package/dist/cli/utils/formatting.js.map +1 -1
  88. package/dist/config/__tests__/loader.test.js.map +1 -1
  89. package/dist/features/boulder-state/constants.d.ts +1 -1
  90. package/dist/features/boulder-state/constants.d.ts.map +1 -1
  91. package/dist/features/builtin-skills/skills.d.ts.map +1 -1
  92. package/dist/features/builtin-skills/skills.js +11 -3
  93. package/dist/features/builtin-skills/skills.js.map +1 -1
  94. package/dist/features/builtin-skills/types.d.ts +7 -0
  95. package/dist/features/builtin-skills/types.d.ts.map +1 -1
  96. package/dist/features/delegation-enforcer.d.ts +2 -0
  97. package/dist/features/delegation-enforcer.d.ts.map +1 -1
  98. package/dist/features/delegation-enforcer.js +10 -0
  99. package/dist/features/delegation-enforcer.js.map +1 -1
  100. package/dist/features/magic-keywords.d.ts.map +1 -1
  101. package/dist/features/magic-keywords.js +30 -1
  102. package/dist/features/magic-keywords.js.map +1 -1
  103. package/dist/features/rate-limit-wait/tmux-detector.d.ts +4 -4
  104. package/dist/features/rate-limit-wait/tmux-detector.js +9 -9
  105. package/dist/features/rate-limit-wait/tmux-detector.js.map +1 -1
  106. package/dist/hooks/__tests__/bridge-routing.test.js +156 -0
  107. package/dist/hooks/__tests__/bridge-routing.test.js.map +1 -1
  108. package/dist/hooks/auto-slash-command/constants.d.ts.map +1 -1
  109. package/dist/hooks/auto-slash-command/constants.js +1 -0
  110. package/dist/hooks/auto-slash-command/constants.js.map +1 -1
  111. package/dist/hooks/auto-slash-command/executor.d.ts.map +1 -1
  112. package/dist/hooks/auto-slash-command/executor.js +29 -0
  113. package/dist/hooks/auto-slash-command/executor.js.map +1 -1
  114. package/dist/hooks/bridge.d.ts.map +1 -1
  115. package/dist/hooks/bridge.js +184 -1
  116. package/dist/hooks/bridge.js.map +1 -1
  117. package/dist/hooks/learner/bridge.d.ts.map +1 -1
  118. package/dist/hooks/learner/bridge.js +4 -109
  119. package/dist/hooks/learner/bridge.js.map +1 -1
  120. package/dist/hooks/learner/detector.js +1 -1
  121. package/dist/hooks/learner/parser.d.ts +9 -0
  122. package/dist/hooks/learner/parser.d.ts.map +1 -1
  123. package/dist/hooks/learner/parser.js +18 -5
  124. package/dist/hooks/learner/parser.js.map +1 -1
  125. package/dist/hooks/learner/types.d.ts +6 -0
  126. package/dist/hooks/learner/types.d.ts.map +1 -1
  127. package/dist/hooks/mode-registry/__tests__/session-isolation.test.js +16 -0
  128. package/dist/hooks/mode-registry/__tests__/session-isolation.test.js.map +1 -1
  129. package/dist/hooks/notepad/index.d.ts +4 -1
  130. package/dist/hooks/notepad/index.d.ts.map +1 -1
  131. package/dist/hooks/notepad/index.js +7 -4
  132. package/dist/hooks/notepad/index.js.map +1 -1
  133. package/dist/hooks/persistent-mode/__tests__/ralph-verification-flow.test.js +70 -3
  134. package/dist/hooks/persistent-mode/__tests__/ralph-verification-flow.test.js.map +1 -1
  135. package/dist/hooks/persistent-mode/index.d.ts.map +1 -1
  136. package/dist/hooks/persistent-mode/index.js +17 -13
  137. package/dist/hooks/persistent-mode/index.js.map +1 -1
  138. package/dist/hooks/persistent-mode/stop-hook-blocking.test.js +175 -3
  139. package/dist/hooks/persistent-mode/stop-hook-blocking.test.js.map +1 -1
  140. package/dist/hooks/project-memory/__tests__/integration.test.js +117 -3
  141. package/dist/hooks/project-memory/__tests__/integration.test.js.map +1 -1
  142. package/dist/hooks/project-memory/__tests__/storage.test.js +11 -11
  143. package/dist/hooks/project-memory/__tests__/storage.test.js.map +1 -1
  144. package/dist/hooks/project-memory/index.d.ts.map +1 -1
  145. package/dist/hooks/project-memory/index.js +24 -12
  146. package/dist/hooks/project-memory/index.js.map +1 -1
  147. package/dist/hooks/ralph/index.d.ts +1 -1
  148. package/dist/hooks/ralph/index.d.ts.map +1 -1
  149. package/dist/hooks/ralph/index.js +1 -1
  150. package/dist/hooks/ralph/index.js.map +1 -1
  151. package/dist/hooks/ralph/loop.d.ts +6 -6
  152. package/dist/hooks/ralph/loop.d.ts.map +1 -1
  153. package/dist/hooks/ralph/loop.js +18 -18
  154. package/dist/hooks/ralph/loop.js.map +1 -1
  155. package/dist/hooks/ralph/prd.d.ts +23 -12
  156. package/dist/hooks/ralph/prd.d.ts.map +1 -1
  157. package/dist/hooks/ralph/prd.js +88 -42
  158. package/dist/hooks/ralph/prd.js.map +1 -1
  159. package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js +20 -0
  160. package/dist/hooks/session-end/__tests__/mode-state-cleanup.test.js.map +1 -1
  161. package/dist/hooks/session-end/index.d.ts +9 -0
  162. package/dist/hooks/session-end/index.d.ts.map +1 -1
  163. package/dist/hooks/session-end/index.js +110 -0
  164. package/dist/hooks/session-end/index.js.map +1 -1
  165. package/dist/hooks/skill-bridge.cjs +114 -92
  166. package/dist/hooks/skill-state/index.d.ts.map +1 -1
  167. package/dist/hooks/skill-state/index.js +1 -0
  168. package/dist/hooks/skill-state/index.js.map +1 -1
  169. package/dist/hud/__tests__/enterprise-cost.test.d.ts +5 -0
  170. package/dist/hud/__tests__/enterprise-cost.test.d.ts.map +1 -0
  171. package/dist/hud/__tests__/enterprise-cost.test.js +151 -0
  172. package/dist/hud/__tests__/enterprise-cost.test.js.map +1 -0
  173. package/dist/hud/__tests__/git-status.test.js +18 -0
  174. package/dist/hud/__tests__/git-status.test.js.map +1 -1
  175. package/dist/hud/__tests__/usage-api-enterprise.test.d.ts +5 -0
  176. package/dist/hud/__tests__/usage-api-enterprise.test.d.ts.map +1 -0
  177. package/dist/hud/__tests__/usage-api-enterprise.test.js +249 -0
  178. package/dist/hud/__tests__/usage-api-enterprise.test.js.map +1 -0
  179. package/dist/hud/elements/background.d.ts +3 -3
  180. package/dist/hud/elements/background.d.ts.map +1 -1
  181. package/dist/hud/elements/background.js +5 -4
  182. package/dist/hud/elements/background.js.map +1 -1
  183. package/dist/hud/elements/call-counts.d.ts +2 -2
  184. package/dist/hud/elements/call-counts.d.ts.map +1 -1
  185. package/dist/hud/elements/call-counts.js +7 -6
  186. package/dist/hud/elements/call-counts.js.map +1 -1
  187. package/dist/hud/elements/context.d.ts +3 -3
  188. package/dist/hud/elements/context.d.ts.map +1 -1
  189. package/dist/hud/elements/context.js +5 -4
  190. package/dist/hud/elements/context.js.map +1 -1
  191. package/dist/hud/elements/enterprise-cost.d.ts +16 -0
  192. package/dist/hud/elements/enterprise-cost.d.ts.map +1 -0
  193. package/dist/hud/elements/enterprise-cost.js +63 -0
  194. package/dist/hud/elements/enterprise-cost.js.map +1 -0
  195. package/dist/hud/elements/git.d.ts +2 -1
  196. package/dist/hud/elements/git.d.ts.map +1 -1
  197. package/dist/hud/elements/git.js +7 -6
  198. package/dist/hud/elements/git.js.map +1 -1
  199. package/dist/hud/elements/permission.d.ts.map +1 -1
  200. package/dist/hud/elements/permission.js +2 -5
  201. package/dist/hud/elements/permission.js.map +1 -1
  202. package/dist/hud/elements/ralph.d.ts +2 -2
  203. package/dist/hud/elements/ralph.d.ts.map +1 -1
  204. package/dist/hud/elements/ralph.js +3 -2
  205. package/dist/hud/elements/ralph.js.map +1 -1
  206. package/dist/hud/elements/session.d.ts.map +1 -1
  207. package/dist/hud/elements/session.js +5 -9
  208. package/dist/hud/elements/session.js.map +1 -1
  209. package/dist/hud/elements/thinking.d.ts +2 -2
  210. package/dist/hud/elements/thinking.d.ts.map +1 -1
  211. package/dist/hud/elements/thinking.js +3 -2
  212. package/dist/hud/elements/thinking.js.map +1 -1
  213. package/dist/hud/elements/token-usage.d.ts +2 -2
  214. package/dist/hud/elements/token-usage.d.ts.map +1 -1
  215. package/dist/hud/elements/token-usage.js +3 -2
  216. package/dist/hud/elements/token-usage.js.map +1 -1
  217. package/dist/hud/index.d.ts.map +1 -1
  218. package/dist/hud/index.js +5 -1
  219. package/dist/hud/index.js.map +1 -1
  220. package/dist/hud/render.d.ts.map +1 -1
  221. package/dist/hud/render.js +43 -10
  222. package/dist/hud/render.js.map +1 -1
  223. package/dist/hud/state.d.ts.map +1 -1
  224. package/dist/hud/state.js +18 -1
  225. package/dist/hud/state.js.map +1 -1
  226. package/dist/hud/stdin.d.ts.map +1 -1
  227. package/dist/hud/stdin.js +49 -5
  228. package/dist/hud/stdin.js.map +1 -1
  229. package/dist/hud/types.d.ts +42 -0
  230. package/dist/hud/types.d.ts.map +1 -1
  231. package/dist/hud/types.js +59 -1
  232. package/dist/hud/types.js.map +1 -1
  233. package/dist/hud/usage-api.d.ts +19 -1
  234. package/dist/hud/usage-api.d.ts.map +1 -1
  235. package/dist/hud/usage-api.js +101 -18
  236. package/dist/hud/usage-api.js.map +1 -1
  237. package/dist/installer/__tests__/hud-cache-wrapper.test.d.ts +2 -0
  238. package/dist/installer/__tests__/hud-cache-wrapper.test.d.ts.map +1 -0
  239. package/dist/installer/__tests__/hud-cache-wrapper.test.js +235 -0
  240. package/dist/installer/__tests__/hud-cache-wrapper.test.js.map +1 -0
  241. package/dist/installer/__tests__/standalone-hook-reconcile.test.js +2 -0
  242. package/dist/installer/__tests__/standalone-hook-reconcile.test.js.map +1 -1
  243. package/dist/installer/index.d.ts +1 -0
  244. package/dist/installer/index.d.ts.map +1 -1
  245. package/dist/installer/index.js +83 -31
  246. package/dist/installer/index.js.map +1 -1
  247. package/dist/lib/__tests__/worktree-paths.test.js +162 -39
  248. package/dist/lib/__tests__/worktree-paths.test.js.map +1 -1
  249. package/dist/lib/worktree-paths.d.ts +87 -9
  250. package/dist/lib/worktree-paths.d.ts.map +1 -1
  251. package/dist/lib/worktree-paths.js +209 -27
  252. package/dist/lib/worktree-paths.js.map +1 -1
  253. package/dist/mcp/__tests__/team-server-artifact-convergence.test.js +156 -4
  254. package/dist/mcp/__tests__/team-server-artifact-convergence.test.js.map +1 -1
  255. package/dist/mcp/omc-tools-server.d.ts +11 -17
  256. package/dist/mcp/omc-tools-server.d.ts.map +1 -1
  257. package/dist/mcp/omc-tools-server.js +22 -32
  258. package/dist/mcp/omc-tools-server.js.map +1 -1
  259. package/dist/mcp/team-job-convergence.d.ts +9 -2
  260. package/dist/mcp/team-job-convergence.d.ts.map +1 -1
  261. package/dist/mcp/team-job-convergence.js +35 -14
  262. package/dist/mcp/team-job-convergence.js.map +1 -1
  263. package/dist/mcp/team-server.d.ts.map +1 -1
  264. package/dist/mcp/team-server.js +93 -13
  265. package/dist/mcp/team-server.js.map +1 -1
  266. package/dist/ralphthon/prd.d.ts.map +1 -1
  267. package/dist/ralphthon/prd.js +13 -17
  268. package/dist/ralphthon/prd.js.map +1 -1
  269. package/dist/shared/types.d.ts +2 -0
  270. package/dist/shared/types.d.ts.map +1 -1
  271. package/dist/team/__tests__/api-interop.cleanup.test.d.ts +2 -0
  272. package/dist/team/__tests__/api-interop.cleanup.test.d.ts.map +1 -0
  273. package/dist/team/__tests__/api-interop.cleanup.test.js +234 -0
  274. package/dist/team/__tests__/api-interop.cleanup.test.js.map +1 -0
  275. package/dist/team/__tests__/api-interop.dispatch.test.js +2 -1
  276. package/dist/team/__tests__/api-interop.dispatch.test.js.map +1 -1
  277. package/dist/team/__tests__/auto-merge-restart.test.d.ts +2 -0
  278. package/dist/team/__tests__/auto-merge-restart.test.d.ts.map +1 -0
  279. package/dist/team/__tests__/auto-merge-restart.test.js +128 -0
  280. package/dist/team/__tests__/auto-merge-restart.test.js.map +1 -0
  281. package/dist/team/__tests__/auto-merge.perf.test.d.ts +2 -0
  282. package/dist/team/__tests__/auto-merge.perf.test.d.ts.map +1 -0
  283. package/dist/team/__tests__/auto-merge.perf.test.js +85 -0
  284. package/dist/team/__tests__/auto-merge.perf.test.js.map +1 -0
  285. package/dist/team/__tests__/conflict-mailbox.test.d.ts +2 -0
  286. package/dist/team/__tests__/conflict-mailbox.test.d.ts.map +1 -0
  287. package/dist/team/__tests__/conflict-mailbox.test.js +238 -0
  288. package/dist/team/__tests__/conflict-mailbox.test.js.map +1 -0
  289. package/dist/team/__tests__/git-worktree.test.js +164 -3
  290. package/dist/team/__tests__/git-worktree.test.js.map +1 -1
  291. package/dist/team/__tests__/helpers/git-fixture.d.ts +72 -0
  292. package/dist/team/__tests__/helpers/git-fixture.d.ts.map +1 -0
  293. package/dist/team/__tests__/helpers/git-fixture.js +252 -0
  294. package/dist/team/__tests__/helpers/git-fixture.js.map +1 -0
  295. package/dist/team/__tests__/leader-inbox.test.d.ts +2 -0
  296. package/dist/team/__tests__/leader-inbox.test.d.ts.map +1 -0
  297. package/dist/team/__tests__/leader-inbox.test.js +152 -0
  298. package/dist/team/__tests__/leader-inbox.test.js.map +1 -0
  299. package/dist/team/__tests__/merge-orchestrator.test.d.ts +2 -0
  300. package/dist/team/__tests__/merge-orchestrator.test.d.ts.map +1 -0
  301. package/dist/team/__tests__/merge-orchestrator.test.js +779 -0
  302. package/dist/team/__tests__/merge-orchestrator.test.js.map +1 -0
  303. package/dist/team/__tests__/model-contract.test.js +35 -21
  304. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  305. package/dist/team/__tests__/rebase-smoke.test.d.ts +2 -0
  306. package/dist/team/__tests__/rebase-smoke.test.d.ts.map +1 -0
  307. package/dist/team/__tests__/rebase-smoke.test.js +272 -0
  308. package/dist/team/__tests__/rebase-smoke.test.js.map +1 -0
  309. package/dist/team/__tests__/runtime-cli.test.js +11 -3
  310. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  311. package/dist/team/__tests__/runtime-prompt-mode.test.js +14 -16
  312. package/dist/team/__tests__/runtime-prompt-mode.test.js.map +1 -1
  313. package/dist/team/__tests__/runtime-v2-role-routing.test.js +7 -1
  314. package/dist/team/__tests__/runtime-v2-role-routing.test.js.map +1 -1
  315. package/dist/team/__tests__/runtime-v2.dispatch.test.js +293 -15
  316. package/dist/team/__tests__/runtime-v2.dispatch.test.js.map +1 -1
  317. package/dist/team/__tests__/runtime-v2.monitor.test.d.ts +2 -0
  318. package/dist/team/__tests__/runtime-v2.monitor.test.d.ts.map +1 -0
  319. package/dist/team/__tests__/runtime-v2.monitor.test.js +219 -0
  320. package/dist/team/__tests__/runtime-v2.monitor.test.js.map +1 -0
  321. package/dist/team/__tests__/runtime-v2.shutdown.test.d.ts +2 -0
  322. package/dist/team/__tests__/runtime-v2.shutdown.test.d.ts.map +1 -0
  323. package/dist/team/__tests__/runtime-v2.shutdown.test.js +292 -0
  324. package/dist/team/__tests__/runtime-v2.shutdown.test.js.map +1 -0
  325. package/dist/team/__tests__/scaling-launch-config.test.js +203 -3
  326. package/dist/team/__tests__/scaling-launch-config.test.js.map +1 -1
  327. package/dist/team/__tests__/team-summary-worktree.test.d.ts +2 -0
  328. package/dist/team/__tests__/team-summary-worktree.test.d.ts.map +1 -0
  329. package/dist/team/__tests__/team-summary-worktree.test.js +85 -0
  330. package/dist/team/__tests__/team-summary-worktree.test.js.map +1 -0
  331. package/dist/team/__tests__/teardown-invariant.test.d.ts +2 -0
  332. package/dist/team/__tests__/teardown-invariant.test.d.ts.map +1 -0
  333. package/dist/team/__tests__/teardown-invariant.test.js +176 -0
  334. package/dist/team/__tests__/teardown-invariant.test.js.map +1 -0
  335. package/dist/team/__tests__/tmux-session-liveness.test.d.ts +2 -0
  336. package/dist/team/__tests__/tmux-session-liveness.test.d.ts.map +1 -0
  337. package/dist/team/__tests__/tmux-session-liveness.test.js +37 -0
  338. package/dist/team/__tests__/tmux-session-liveness.test.js.map +1 -0
  339. package/dist/team/__tests__/worker-bootstrap.test.js +47 -1
  340. package/dist/team/__tests__/worker-bootstrap.test.js.map +1 -1
  341. package/dist/team/__tests__/worker-commit-cadence.test.d.ts +2 -0
  342. package/dist/team/__tests__/worker-commit-cadence.test.d.ts.map +1 -0
  343. package/dist/team/__tests__/worker-commit-cadence.test.js +315 -0
  344. package/dist/team/__tests__/worker-commit-cadence.test.js.map +1 -0
  345. package/dist/team/__tests__/worktree-contract-fields.test.d.ts +2 -0
  346. package/dist/team/__tests__/worktree-contract-fields.test.d.ts.map +1 -0
  347. package/dist/team/__tests__/worktree-contract-fields.test.js +157 -0
  348. package/dist/team/__tests__/worktree-contract-fields.test.js.map +1 -0
  349. package/dist/team/__tests__/worktree-runtime-e2e.test.d.ts +2 -0
  350. package/dist/team/__tests__/worktree-runtime-e2e.test.d.ts.map +1 -0
  351. package/dist/team/__tests__/worktree-runtime-e2e.test.js +182 -0
  352. package/dist/team/__tests__/worktree-runtime-e2e.test.js.map +1 -0
  353. package/dist/team/api-interop.d.ts +2 -1
  354. package/dist/team/api-interop.d.ts.map +1 -1
  355. package/dist/team/api-interop.js +121 -14
  356. package/dist/team/api-interop.js.map +1 -1
  357. package/dist/team/capabilities.d.ts.map +1 -1
  358. package/dist/team/capabilities.js +2 -0
  359. package/dist/team/capabilities.js.map +1 -1
  360. package/dist/team/cli-worker-contract.d.ts +7 -6
  361. package/dist/team/cli-worker-contract.d.ts.map +1 -1
  362. package/dist/team/cli-worker-contract.js +7 -6
  363. package/dist/team/cli-worker-contract.js.map +1 -1
  364. package/dist/team/conflict-mailbox.d.ts +49 -0
  365. package/dist/team/conflict-mailbox.d.ts.map +1 -0
  366. package/dist/team/conflict-mailbox.js +98 -0
  367. package/dist/team/conflict-mailbox.js.map +1 -0
  368. package/dist/team/events.d.ts +3 -0
  369. package/dist/team/events.d.ts.map +1 -1
  370. package/dist/team/events.js +2 -1
  371. package/dist/team/events.js.map +1 -1
  372. package/dist/team/git-worktree.d.ts +78 -10
  373. package/dist/team/git-worktree.d.ts.map +1 -1
  374. package/dist/team/git-worktree.js +453 -97
  375. package/dist/team/git-worktree.js.map +1 -1
  376. package/dist/team/governance.d.ts +3 -2
  377. package/dist/team/governance.d.ts.map +1 -1
  378. package/dist/team/governance.js.map +1 -1
  379. package/dist/team/leader-inbox.d.ts +28 -0
  380. package/dist/team/leader-inbox.d.ts.map +1 -0
  381. package/dist/team/leader-inbox.js +68 -0
  382. package/dist/team/leader-inbox.js.map +1 -0
  383. package/dist/team/merge-coordinator.d.ts +6 -0
  384. package/dist/team/merge-coordinator.d.ts.map +1 -1
  385. package/dist/team/merge-coordinator.js +6 -2
  386. package/dist/team/merge-coordinator.js.map +1 -1
  387. package/dist/team/merge-orchestrator.d.ts +41 -0
  388. package/dist/team/merge-orchestrator.d.ts.map +1 -0
  389. package/dist/team/merge-orchestrator.js +736 -0
  390. package/dist/team/merge-orchestrator.js.map +1 -0
  391. package/dist/team/model-contract.d.ts +18 -2
  392. package/dist/team/model-contract.d.ts.map +1 -1
  393. package/dist/team/model-contract.js +55 -28
  394. package/dist/team/model-contract.js.map +1 -1
  395. package/dist/team/monitor.d.ts.map +1 -1
  396. package/dist/team/monitor.js +47 -3
  397. package/dist/team/monitor.js.map +1 -1
  398. package/dist/team/runtime-cli.d.ts +1 -0
  399. package/dist/team/runtime-cli.d.ts.map +1 -1
  400. package/dist/team/runtime-cli.js +15 -2
  401. package/dist/team/runtime-cli.js.map +1 -1
  402. package/dist/team/runtime-flags.d.ts +5 -0
  403. package/dist/team/runtime-flags.d.ts.map +1 -0
  404. package/dist/team/runtime-flags.js +12 -0
  405. package/dist/team/runtime-flags.js.map +1 -0
  406. package/dist/team/runtime-v2.d.ts +16 -1
  407. package/dist/team/runtime-v2.d.ts.map +1 -1
  408. package/dist/team/runtime-v2.js +531 -127
  409. package/dist/team/runtime-v2.js.map +1 -1
  410. package/dist/team/runtime.d.ts.map +1 -1
  411. package/dist/team/runtime.js +4 -1
  412. package/dist/team/runtime.js.map +1 -1
  413. package/dist/team/scaling.d.ts.map +1 -1
  414. package/dist/team/scaling.js +110 -13
  415. package/dist/team/scaling.js.map +1 -1
  416. package/dist/team/state/tasks.d.ts.map +1 -1
  417. package/dist/team/state/tasks.js +1 -0
  418. package/dist/team/state/tasks.js.map +1 -1
  419. package/dist/team/team-ops.d.ts.map +1 -1
  420. package/dist/team/team-ops.js +112 -16
  421. package/dist/team/team-ops.js.map +1 -1
  422. package/dist/team/tmux-session.d.ts +17 -29
  423. package/dist/team/tmux-session.d.ts.map +1 -1
  424. package/dist/team/tmux-session.js +117 -108
  425. package/dist/team/tmux-session.js.map +1 -1
  426. package/dist/team/types.d.ts +31 -17
  427. package/dist/team/types.d.ts.map +1 -1
  428. package/dist/team/types.js.map +1 -1
  429. package/dist/team/worker-bootstrap.d.ts +11 -0
  430. package/dist/team/worker-bootstrap.d.ts.map +1 -1
  431. package/dist/team/worker-bootstrap.js +37 -14
  432. package/dist/team/worker-bootstrap.js.map +1 -1
  433. package/dist/team/worker-canonicalization.d.ts.map +1 -1
  434. package/dist/team/worker-canonicalization.js +2 -0
  435. package/dist/team/worker-canonicalization.js.map +1 -1
  436. package/dist/team/worker-commit-cadence.d.ts +70 -0
  437. package/dist/team/worker-commit-cadence.d.ts.map +1 -0
  438. package/dist/team/worker-commit-cadence.js +271 -0
  439. package/dist/team/worker-commit-cadence.js.map +1 -0
  440. package/dist/tools/__tests__/memory-tools.test.js +4 -4
  441. package/dist/tools/__tests__/memory-tools.test.js.map +1 -1
  442. package/dist/tools/__tests__/state-tools.test.js +13 -1
  443. package/dist/tools/__tests__/state-tools.test.js.map +1 -1
  444. package/dist/utils/frontmatter.d.ts.map +1 -1
  445. package/dist/utils/frontmatter.js +1 -16
  446. package/dist/utils/frontmatter.js.map +1 -1
  447. package/dist/utils/resolve-node.d.ts.map +1 -1
  448. package/dist/utils/resolve-node.js +22 -28
  449. package/dist/utils/resolve-node.js.map +1 -1
  450. package/docs/GETTING-STARTED.md +415 -0
  451. package/docs/MIGRATION.md +1003 -0
  452. package/docs/REFERENCE.md +21 -4
  453. package/docs/TEAM-WORKTREE-MODE.md +86 -0
  454. package/docs/company-context-interface.md +5 -16
  455. package/docs/copilot-instructions.md +1 -1
  456. package/package.json +1 -1
  457. package/scripts/keyword-detector.mjs +168 -20
  458. package/scripts/lib/hud-cache-wrapper.sh +122 -0
  459. package/scripts/persistent-mode.cjs +28 -2
  460. package/scripts/persistent-mode.mjs +28 -2
  461. package/scripts/plugin-setup.mjs +10 -1
  462. package/scripts/post-tool-verifier.mjs +153 -3
  463. package/scripts/pre-tool-enforcer.mjs +54 -6
  464. package/scripts/session-start.mjs +194 -2
  465. package/skills/AGENTS.md +22 -91
  466. package/skills/ai-slop-cleaner/SKILL.md +118 -120
  467. package/skills/ask/SKILL.md +6 -8
  468. package/skills/autopilot/SKILL.md +1 -28
  469. package/skills/autoresearch/SKILL.md +5 -5
  470. package/skills/cancel/SKILL.md +13 -13
  471. package/skills/cccg/SKILL.md +1 -0
  472. package/skills/configure-notifications/SKILL.md +80 -215
  473. package/skills/debug/SKILL.md +3 -2
  474. package/skills/deep-dive/SKILL.md +20 -9
  475. package/skills/deep-interview/SKILL.md +32 -4
  476. package/skills/deepinit/SKILL.md +1 -0
  477. package/skills/external-context/SKILL.md +2 -1
  478. package/skills/hud/SKILL.md +22 -52
  479. package/skills/learner/SKILL.md +90 -57
  480. package/skills/mcp-setup/SKILL.md +45 -60
  481. package/skills/omc-doctor/SKILL.md +1 -1
  482. package/skills/omc-reference/SKILL.md +6 -6
  483. package/skills/omc-setup/SKILL.md +30 -74
  484. package/skills/omc-setup/phases/01-install-claude-md.md +97 -0
  485. package/skills/omc-setup/phases/02-configure.md +25 -40
  486. package/skills/omc-setup/phases/03-integrations.md +100 -69
  487. package/skills/omc-setup/phases/04-welcome.md +102 -44
  488. package/skills/omc-teams/SKILL.md +63 -26
  489. package/skills/plan/SKILL.md +11 -9
  490. package/skills/ralph/SKILL.md +42 -41
  491. package/skills/ralplan/SKILL.md +3 -10
  492. package/skills/release/SKILL.md +168 -57
  493. package/skills/remember/SKILL.md +2 -1
  494. package/skills/sciomc/SKILL.md +12 -11
  495. package/skills/self-improve/SKILL.md +4 -4
  496. package/skills/setup/SKILL.md +1 -0
  497. package/skills/skill/SKILL.md +21 -20
  498. package/skills/skillify/SKILL.md +34 -3
  499. package/skills/team/SKILL.md +62 -44
  500. package/skills/ultraqa/SKILL.md +1 -2
  501. package/skills/ultrawork/SKILL.md +28 -21
  502. package/skills/verify/SKILL.md +2 -1
  503. package/skills/writer-memory/SKILL.md +2 -1
  504. package/templates/hooks/keyword-detector.mjs +129 -11
  505. package/templates/hooks/persistent-mode.mjs +28 -2
  506. package/templates/hooks/pre-tool-use.mjs +144 -45
  507. package/templates/hooks/session-start.mjs +25 -1
@@ -15,10 +15,10 @@
15
15
  * Architecture mirrors runtime.ts: startTeam, monitorTeam, shutdownTeam,
16
16
  * assignTask, resumeTeam as discrete operations driven by the caller.
17
17
  */
18
- import { join, resolve } from 'path';
19
18
  import { tmuxExecAsync } from '../cli/tmux-utils.js';
19
+ import { join, resolve } from 'path';
20
20
  import { existsSync } from 'fs';
21
- import { mkdir, readdir, readFile, writeFile } from 'fs/promises';
21
+ import { mkdir, readdir, readFile, rm, writeFile } from 'fs/promises';
22
22
  import { performance } from 'perf_hooks';
23
23
  import { TeamPaths, absPath, teamStateRoot } from './state-paths.js';
24
24
  import { allocateTasksToWorkers } from './allocation-policy.js';
@@ -28,12 +28,11 @@ import { DEFAULT_TEAM_GOVERNANCE, DEFAULT_TEAM_TRANSPORT_POLICY, getConfigGovern
28
28
  import { inferPhase } from './phase-controller.js';
29
29
  import { validateTeamName } from './team-name.js';
30
30
  import { buildWorkerArgv, getContract, resolveValidatedBinaryPath, getWorkerEnv as getModelWorkerEnv, isPromptModeAgent, getPromptModeArgs, resolveClaudeWorkerModel, } from './model-contract.js';
31
- import { createTeamSession, spawnWorkerInPane, sendToWorker, waitForPaneReady, paneHasActiveTask, paneLooksReady, applyMainVerticalLayout, } from './tmux-session.js';
31
+ import { createTeamSession, spawnWorkerInPane, sendToWorker, killTeamSession, waitForPaneReady, paneHasActiveTask, paneLooksReady, applyMainVerticalLayout, getWorkerLiveness, } from './tmux-session.js';
32
32
  import { composeInitialInbox, ensureWorkerStateDir, writeWorkerOverlay, generateTriggerMessage, generatePromptModeStartupPrompt, } from './worker-bootstrap.js';
33
33
  import { queueInboxInstruction } from './mcp-comm.js';
34
- import { cleanupTeamWorktrees } from './git-worktree.js';
34
+ import { cleanupTeamWorktrees, inspectTeamWorktreeCleanupSafety, ensureWorkerWorktree, installWorktreeRootAgents, normalizeTeamWorktreeMode, } from './git-worktree.js';
35
35
  import { formatOmcCliInvocation } from '../utils/omc-cli-rendering.js';
36
- import { getHostCliType } from '../utils/host-detection.js';
37
36
  import { createSwallowedErrorLogger } from '../lib/swallowed-error.js';
38
37
  import { CANONICAL_TEAM_ROLES } from '../shared/types.js';
39
38
  import { loadConfig } from '../config/loader.js';
@@ -41,16 +40,70 @@ import { buildResolvedRoutingSnapshot, getRoleRoutingSpec } from './stage-router
41
40
  import { routeTaskToRole } from './role-router.js';
42
41
  import { normalizeDelegationRole } from '../features/delegation-routing/types.js';
43
42
  import { cliWorkerOutputFilePath, parseCliWorkerVerdict, renderCliWorkerOutputContract, shouldInjectContract, } from './cli-worker-contract.js';
43
+ import { startMergeOrchestrator, recoverFromRestart, } from './merge-orchestrator.js';
44
+ import { ensureLeaderInbox, extendLeaderBootstrapPrompt, appendToLeaderInbox } from './leader-inbox.js';
45
+ import { execFileSync } from 'node:child_process';
46
+ import { isRuntimeV2Enabled } from './runtime-flags.js';
47
+ import { installCommitCadence, startFallbackPoller, uninstallCommitCadence, } from './worker-commit-cadence.js';
44
48
  // ---------------------------------------------------------------------------
45
- // Feature flag
49
+ // In-process orchestrator registry (per-team handle for the lifetime of the
50
+ // runtime-cli process). Lives at module scope so shutdownTeamV2 can find it.
46
51
  // ---------------------------------------------------------------------------
47
- export function isRuntimeV2Enabled(env = process.env) {
48
- const raw = env.OMC_RUNTIME_V2;
49
- if (!raw)
50
- return true;
51
- const normalized = raw.trim().toLowerCase();
52
- return !['0', 'false', 'no', 'off'].includes(normalized);
52
+ const orchestratorByTeam = new Map();
53
+ const cadenceByTeam = new Map();
54
+ function registerTeamOrchestrator(teamName, handle) {
55
+ orchestratorByTeam.set(teamName, handle);
56
+ }
57
+ function getTeamOrchestrator(teamName) {
58
+ return orchestratorByTeam.get(teamName);
59
+ }
60
+ function unregisterTeamOrchestrator(teamName) {
61
+ orchestratorByTeam.delete(teamName);
62
+ }
63
+ function registerTeamCadence(teamName, context, poller) {
64
+ const entry = cadenceByTeam.get(teamName) ?? { pollers: [], contexts: [] };
65
+ entry.contexts.push(context);
66
+ if (poller)
67
+ entry.pollers.push(poller);
68
+ cadenceByTeam.set(teamName, entry);
69
+ }
70
+ async function stopTeamCadence(teamName) {
71
+ const entry = cadenceByTeam.get(teamName);
72
+ if (!entry)
73
+ return;
74
+ cadenceByTeam.delete(teamName);
75
+ for (const poller of entry.pollers) {
76
+ try {
77
+ poller.stop();
78
+ }
79
+ catch { /* best-effort cleanup */ }
80
+ }
81
+ for (const context of entry.contexts) {
82
+ try {
83
+ await uninstallCommitCadence(context);
84
+ }
85
+ catch { /* best-effort cleanup */ }
86
+ }
87
+ }
88
+ /**
89
+ * Resolve the leader's current branch via `git branch --show-current` from cwd.
90
+ * Throws if not a git repo or HEAD is detached.
91
+ */
92
+ function resolveLeaderBranch(cwd) {
93
+ const out = execFileSync('git', ['branch', '--show-current'], {
94
+ cwd,
95
+ encoding: 'utf-8',
96
+ stdio: ['pipe', 'pipe', 'pipe'],
97
+ }).trim();
98
+ if (!out) {
99
+ throw new Error('auto-merge requires a non-detached leader branch (git branch --show-current returned empty)');
100
+ }
101
+ return out;
53
102
  }
103
+ // ---------------------------------------------------------------------------
104
+ // Feature flag
105
+ // ---------------------------------------------------------------------------
106
+ export { isRuntimeV2Enabled } from './runtime-flags.js';
54
107
  const MONITOR_SIGNAL_STALE_MS = 30_000;
55
108
  // ---------------------------------------------------------------------------
56
109
  // Helper: sanitize team name
@@ -125,23 +178,17 @@ function resolvePreflightBinaryPath(agentType) {
125
178
  // ---------------------------------------------------------------------------
126
179
  // Helper: check worker liveness via tmux pane
127
180
  // ---------------------------------------------------------------------------
128
- async function isWorkerPaneAlive(paneId) {
181
+ async function getWorkerPaneLiveness(paneId) {
129
182
  if (!paneId)
130
- return false;
131
- try {
132
- const { isWorkerAlive } = await import('./tmux-session.js');
133
- return await isWorkerAlive(paneId);
134
- }
135
- catch {
136
- return false;
137
- }
183
+ return 'dead';
184
+ return getWorkerLiveness(paneId);
138
185
  }
139
186
  async function captureWorkerPane(paneId) {
140
187
  if (!paneId)
141
188
  return '';
142
189
  try {
143
- const { stdout } = await tmuxExecAsync(['capture-pane', '-t', paneId, '-p', '-S', '-80']);
144
- return stdout;
190
+ const result = await tmuxExecAsync(['capture-pane', '-t', paneId, '-p', '-S', '-80']);
191
+ return result.stdout ?? '';
145
192
  }
146
193
  catch {
147
194
  return '';
@@ -167,12 +214,18 @@ function findOutstandingWorkerTask(worker, taskById, inProgressByOwner) {
167
214
  const owned = inProgressByOwner.get(worker.name) ?? [];
168
215
  return owned[0] ?? null;
169
216
  }
217
+ function getTaskDependencyIds(task) {
218
+ return task.depends_on ?? task.blocked_by ?? [];
219
+ }
220
+ function getMissingDependencyIds(task, taskById) {
221
+ return getTaskDependencyIds(task).filter((dependencyId) => !taskById.has(dependencyId));
222
+ }
170
223
  // ---------------------------------------------------------------------------
171
224
  // V2 task instruction builder — CLI API lifecycle, NO done.json
172
225
  // ---------------------------------------------------------------------------
173
226
  /**
174
227
  * Build the initial task instruction for v2 workers.
175
- * Workers use `omcp team api` CLI commands for all lifecycle transitions.
228
+ * Workers use `omc team api` CLI commands for all lifecycle transitions.
176
229
  */
177
230
  function buildV2TaskInstruction(teamName, workerName, task, taskId, cliOutputContract) {
178
231
  const claimTaskCommand = formatOmcCliInvocation(`team api claim-task --input '${JSON.stringify({ team_name: teamName, task_id: taskId, worker: workerName })}' --json`, {});
@@ -269,7 +322,7 @@ async function spawnV2Worker(opts) {
269
322
  const splitResult = await tmuxExecAsync([
270
323
  'split-window', splitType, '-t', splitTarget,
271
324
  '-d', '-P', '-F', '#{pane_id}',
272
- '-c', opts.cwd,
325
+ '-c', opts.workerCwd ?? opts.cwd,
273
326
  ]);
274
327
  const paneId = splitResult.stdout.split('\n')[0]?.trim();
275
328
  if (!paneId) {
@@ -288,8 +341,9 @@ async function spawnV2Worker(opts) {
288
341
  : undefined;
289
342
  // Build v2 task instruction (CLI API, NO done.json)
290
343
  const instruction = buildV2TaskInstruction(opts.teamName, opts.workerName, opts.task, opts.taskId, cliOutputContract);
291
- const inboxTriggerMessage = generateTriggerMessage(opts.teamName, opts.workerName);
292
- const promptModeStartupPrompt = generatePromptModeStartupPrompt(opts.teamName, opts.workerName, undefined, cliOutputContract);
344
+ const instructionStateRoot = opts.worktreePath ? '$OMC_TEAM_STATE_ROOT' : undefined;
345
+ const inboxTriggerMessage = generateTriggerMessage(opts.teamName, opts.workerName, instructionStateRoot);
346
+ const promptModeStartupPrompt = generatePromptModeStartupPrompt(opts.teamName, opts.workerName, instructionStateRoot, cliOutputContract);
293
347
  if (usePromptMode) {
294
348
  await composeInitialInbox(opts.teamName, opts.workerName, instruction, opts.cwd, cliOutputContract);
295
349
  }
@@ -298,10 +352,14 @@ async function spawnV2Worker(opts) {
298
352
  ...getModelWorkerEnv(opts.teamName, opts.workerName, opts.agentType),
299
353
  OMC_TEAM_STATE_ROOT: teamStateRoot(opts.cwd, opts.teamName),
300
354
  OMC_TEAM_LEADER_CWD: opts.cwd,
355
+ ...(opts.worktreePath ? { OMC_TEAM_WORKTREE_PATH: opts.worktreePath } : {}),
356
+ ...(opts.workerCwd ? { OMC_TEAM_WORKER_CWD: opts.workerCwd } : {}),
301
357
  };
302
358
  const resolvedBinaryPath = opts.resolvedBinaryPaths[opts.agentType]
303
359
  ?? resolveValidatedBinaryPath(opts.agentType);
304
360
  // Resolve model from environment variables.
361
+ // For Claude agents on Bedrock/Vertex, resolve the provider-specific model
362
+ // so workers don't fall back to invalid Anthropic API model names. (#1695)
305
363
  // Snapshot-provided model (from resolved_routing) takes precedence so
306
364
  // per-role routing (codex/gemini/claude-tier) is honored at spawn time.
307
365
  const modelForAgent = opts.model ?? (() => {
@@ -315,18 +373,35 @@ async function spawnV2Worker(opts) {
315
373
  || process.env.OMC_GEMINI_DEFAULT_MODEL
316
374
  || undefined;
317
375
  }
376
+ // Claude agents: resolve Bedrock/Vertex model when on those providers
318
377
  return resolveClaudeWorkerModel();
319
378
  })();
320
379
  const [launchBinary, ...launchArgs] = buildWorkerArgv(opts.agentType, {
321
380
  teamName: opts.teamName,
322
381
  workerName: opts.workerName,
323
- cwd: opts.cwd,
382
+ cwd: opts.workerCwd ?? opts.cwd,
324
383
  resolvedBinaryPath,
325
384
  model: modelForAgent,
326
385
  });
327
- // For prompt-mode agents (codex, gemini), pass instruction via CLI flag
386
+ // For prompt-mode agents (currently gemini), keep the full instruction in
387
+ // inbox.md and pass only a short file-pointer prompt via CLI args. This
388
+ // avoids echoing reviewer/seed prompt text into tmux scrollback.
328
389
  if (usePromptMode) {
329
- launchArgs.push(...getPromptModeArgs(opts.agentType, instruction));
390
+ launchArgs.push(...getPromptModeArgs(opts.agentType, promptModeStartupPrompt));
391
+ }
392
+ if (opts.autoMerge && opts.worktreePath) {
393
+ const cadenceContext = {
394
+ teamName: opts.teamName,
395
+ workerName: opts.workerName,
396
+ worktreePath: opts.worktreePath,
397
+ agentType: opts.agentType,
398
+ enabled: true,
399
+ };
400
+ const cadence = await installCommitCadence(cadenceContext);
401
+ const poller = cadence.method === 'fallback-poll'
402
+ ? startFallbackPoller(opts.worktreePath, opts.workerName)
403
+ : undefined;
404
+ registerTeamCadence(opts.teamName, cadenceContext, poller);
330
405
  }
331
406
  const paneConfig = {
332
407
  teamName: opts.teamName,
@@ -334,7 +409,7 @@ async function spawnV2Worker(opts) {
334
409
  envVars,
335
410
  launchBinary,
336
411
  launchArgs,
337
- cwd: opts.cwd,
412
+ cwd: opts.workerCwd ?? opts.cwd,
338
413
  };
339
414
  await spawnWorkerInPane(opts.sessionName, paneId, paneConfig);
340
415
  // Apply layout
@@ -385,7 +460,7 @@ async function spawnV2Worker(opts) {
385
460
  startupFailureReason: dispatchOutcome.reason,
386
461
  };
387
462
  }
388
- if (opts.agentType === 'claude' || opts.agentType === 'copilot') {
463
+ if (opts.agentType === 'claude') {
389
464
  const settled = await waitForWorkerStartupEvidence(opts.teamName, opts.workerName, opts.taskId, opts.cwd, 6);
390
465
  if (!settled) {
391
466
  return {
@@ -411,6 +486,46 @@ async function spawnV2Worker(opts) {
411
486
  ...(outputFile ? { outputFile } : {}),
412
487
  };
413
488
  }
489
+ async function rollbackUnpersistedNativeWorktreeStartup(teamName, cwd, cause) {
490
+ const safety = inspectTeamWorktreeCleanupSafety(teamName, cwd);
491
+ if (!safety.hasEvidence)
492
+ return;
493
+ const teamRoot = absPath(cwd, TeamPaths.root(teamName));
494
+ const errorMessage = cause instanceof Error ? cause.message : String(cause);
495
+ try {
496
+ const cleanup = cleanupTeamWorktrees(teamName, cwd);
497
+ if (cleanup.preserved.length === 0) {
498
+ await rm(teamRoot, { recursive: true, force: true });
499
+ return;
500
+ }
501
+ await mkdir(teamRoot, { recursive: true });
502
+ await writeFile(join(teamRoot, 'startup-failure.json'), JSON.stringify({
503
+ reason: 'startup_failed_before_config_persisted',
504
+ error: errorMessage,
505
+ preserved: cleanup.preserved,
506
+ recorded_at: new Date().toISOString(),
507
+ }, null, 2), 'utf-8');
508
+ }
509
+ catch (rollbackError) {
510
+ await mkdir(teamRoot, { recursive: true });
511
+ await writeFile(join(teamRoot, 'startup-failure.json'), JSON.stringify({
512
+ reason: 'startup_failed_before_config_persisted',
513
+ error: errorMessage,
514
+ rollback_error: rollbackError instanceof Error ? rollbackError.message : String(rollbackError),
515
+ recorded_at: new Date().toISOString(),
516
+ }, null, 2), 'utf-8');
517
+ }
518
+ }
519
+ async function rollbackStartedNativeWorktreeStartup(args) {
520
+ try {
521
+ await killTeamSession(args.sessionName, args.workerPaneIds, args.leaderPaneId ?? undefined, { sessionMode: args.sessionMode });
522
+ }
523
+ catch (killError) {
524
+ process.stderr.write(`[team/runtime-v2] startup rollback tmux cleanup failed: ${killError instanceof Error ? killError.message : String(killError)}
525
+ `);
526
+ }
527
+ await rollbackUnpersistedNativeWorktreeStartup(args.teamName, args.cwd, args.cause);
528
+ }
414
529
  // ---------------------------------------------------------------------------
415
530
  // startTeamV2 — direct tmux creation, CLI API inbox, NO watchdog
416
531
  // ---------------------------------------------------------------------------
@@ -430,6 +545,25 @@ export async function startTeamV2(config) {
430
545
  // do NOT change routing — user must recreate the team to pick up changes.
431
546
  const pluginCfg = config.pluginConfig ?? loadConfig();
432
547
  const resolvedRouting = buildResolvedRoutingSnapshot(pluginCfg);
548
+ let worktreeMode = normalizeTeamWorktreeMode(process.env.OMC_TEAM_WORKTREE_MODE ?? pluginCfg.team?.ops?.worktreeMode);
549
+ // Auto-merge gate (M5 + M3 hardening). Forces worktreeMode='named' so each
550
+ // worker has a real branch the orchestrator can merge from.
551
+ let autoMergeLeaderBranch;
552
+ if (config.autoMerge) {
553
+ if (!isRuntimeV2Enabled()) {
554
+ throw new Error('auto-merge requires OMC_RUNTIME_V2=1 (this feature is v2-only).');
555
+ }
556
+ autoMergeLeaderBranch = resolveLeaderBranch(leaderCwd);
557
+ const stripped = autoMergeLeaderBranch.replace(/^refs\/heads\//i, '').toLowerCase();
558
+ if (stripped === 'main' || stripped === 'master') {
559
+ throw new Error('auto-merge refuses main/master leader branch — use a feature branch');
560
+ }
561
+ if (worktreeMode !== 'named') {
562
+ // Force named-branch worktree mode so workers get a real branch.
563
+ worktreeMode = 'named';
564
+ }
565
+ }
566
+ const workspaceMode = worktreeMode === 'disabled' ? 'single' : 'worktree';
433
567
  // Validate CLIs and pin absolute binary paths for user-declared agentTypes.
434
568
  // AC-8: missing/untrusted binaries fall back to the snapshot's Claude tuple at
435
569
  // spawn time; emit a loud warning naming the binary so operators can fix it.
@@ -507,6 +641,23 @@ export async function startTeamV2(config) {
507
641
  }
508
642
  // Build allocation inputs for the new role-aware allocator
509
643
  const workerNames = Array.from({ length: config.workerCount }, (_, index) => `worker-${index + 1}`);
644
+ const workerWorktrees = new Map();
645
+ try {
646
+ if (worktreeMode !== 'disabled') {
647
+ for (const workerName of workerNames) {
648
+ const worktree = ensureWorkerWorktree(sanitized, workerName, leaderCwd, {
649
+ mode: worktreeMode,
650
+ requireCleanLeader: true,
651
+ });
652
+ if (worktree)
653
+ workerWorktrees.set(workerName, worktree);
654
+ }
655
+ }
656
+ }
657
+ catch (error) {
658
+ await rollbackUnpersistedNativeWorktreeStartup(sanitized, leaderCwd, error);
659
+ throw error;
660
+ }
510
661
  const workerNameSet = new Set(workerNames);
511
662
  // Respect explicit owner fields first, then allocate remaining tasks
512
663
  const startupAllocations = [];
@@ -529,7 +680,7 @@ export async function startTeamV2(config) {
529
680
  const allocationWorkers = workerNames.map((name, i) => ({
530
681
  name,
531
682
  role: config.workerRoles?.[i]
532
- ?? (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? getHostCliType()),
683
+ ?? (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? 'claude'),
533
684
  currentLoad: 0,
534
685
  }));
535
686
  for (const r of allocateTasksToWorkers(allocationTasks, allocationWorkers)) {
@@ -537,41 +688,71 @@ export async function startTeamV2(config) {
537
688
  }
538
689
  }
539
690
  // Set up worker state dirs and overlays (with v2 CLI API instructions)
540
- for (let i = 0; i < workerNames.length; i++) {
541
- const wName = workerNames[i];
542
- const agentType = (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? getHostCliType());
543
- await ensureWorkerStateDir(sanitized, wName, leaderCwd);
544
- await writeWorkerOverlay({
545
- teamName: sanitized, workerName: wName, agentType,
546
- tasks: config.tasks.map((t, idx) => ({
547
- id: String(idx + 1), subject: t.subject, description: t.description,
548
- })),
549
- cwd: leaderCwd,
550
- ...(config.rolePrompt ? { bootstrapInstructions: config.rolePrompt } : {}),
551
- });
691
+ try {
692
+ for (let i = 0; i < workerNames.length; i++) {
693
+ const wName = workerNames[i];
694
+ const agentType = (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? 'claude');
695
+ await ensureWorkerStateDir(sanitized, wName, leaderCwd);
696
+ const overlayPath = await writeWorkerOverlay({
697
+ teamName: sanitized, workerName: wName, agentType,
698
+ tasks: config.tasks.map((t, idx) => ({
699
+ id: String(idx + 1), subject: t.subject, description: t.description,
700
+ })),
701
+ cwd: leaderCwd,
702
+ ...(config.rolePrompt ? { bootstrapInstructions: config.rolePrompt } : {}),
703
+ ...(workerWorktrees.has(wName) ? { instructionStateRoot: '$OMC_TEAM_STATE_ROOT' } : {}),
704
+ });
705
+ const worktree = workerWorktrees.get(wName);
706
+ if (worktree) {
707
+ const overlayContent = await readFile(overlayPath, 'utf-8');
708
+ installWorktreeRootAgents(sanitized, wName, leaderCwd, worktree.path, overlayContent);
709
+ }
710
+ }
711
+ }
712
+ catch (error) {
713
+ await rollbackUnpersistedNativeWorktreeStartup(sanitized, leaderCwd, error);
714
+ throw error;
552
715
  }
553
716
  // Create tmux session (leader only — workers spawned below)
554
- const session = await createTeamSession(sanitized, 0, leaderCwd, {
555
- newWindow: Boolean(config.newWindow),
556
- });
717
+ let session;
718
+ try {
719
+ session = await createTeamSession(sanitized, 0, leaderCwd, {
720
+ newWindow: Boolean(config.newWindow),
721
+ });
722
+ }
723
+ catch (error) {
724
+ await rollbackUnpersistedNativeWorktreeStartup(sanitized, leaderCwd, error);
725
+ throw error;
726
+ }
557
727
  const sessionName = session.sessionName;
558
728
  const leaderPaneId = session.leaderPaneId;
559
729
  const ownsWindow = session.sessionMode !== 'split-pane';
560
730
  const workerPaneIds = [];
561
731
  // Build workers info for config
562
- const workersInfo = workerNames.map((wName, i) => ({
563
- name: wName,
564
- index: i + 1,
565
- role: config.workerRoles?.[i]
566
- ?? (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? getHostCliType()),
567
- assigned_tasks: [],
568
- working_dir: leaderCwd,
569
- }));
732
+ const workersInfo = workerNames.map((wName, i) => {
733
+ const worktree = workerWorktrees.get(wName);
734
+ return {
735
+ name: wName,
736
+ index: i + 1,
737
+ role: config.workerRoles?.[i]
738
+ ?? (agentTypes[i % agentTypes.length] ?? agentTypes[0] ?? 'claude'),
739
+ assigned_tasks: [],
740
+ working_dir: worktree?.path ?? leaderCwd,
741
+ team_state_root: teamStateRoot(leaderCwd, sanitized),
742
+ ...(worktree ? {
743
+ worktree_repo_root: leaderCwd,
744
+ worktree_path: worktree.path,
745
+ worktree_branch: worktree.branch,
746
+ worktree_detached: worktree.detached,
747
+ worktree_created: worktree.created,
748
+ } : {}),
749
+ };
750
+ });
570
751
  // Write initial v2 config
571
752
  const teamConfig = {
572
753
  name: sanitized,
573
754
  task: config.tasks.map(t => t.subject).join('; '),
574
- agent_type: agentTypes[0] || getHostCliType(),
755
+ agent_type: agentTypes[0] || 'claude',
575
756
  worker_launch_mode: 'interactive',
576
757
  policy: DEFAULT_TEAM_TRANSPORT_POLICY,
577
758
  governance: DEFAULT_TEAM_GOVERNANCE,
@@ -589,9 +770,24 @@ export async function startTeamV2(config) {
589
770
  resize_hook_name: null,
590
771
  resize_hook_target: null,
591
772
  resolved_routing: resolvedRouting,
592
- ...(ownsWindow ? { workspace_mode: 'single' } : {}),
773
+ workspace_mode: workspaceMode,
774
+ worktree_mode: worktreeMode,
593
775
  };
594
- await saveTeamConfig(teamConfig, leaderCwd);
776
+ try {
777
+ await saveTeamConfig(teamConfig, leaderCwd);
778
+ }
779
+ catch (error) {
780
+ await rollbackStartedNativeWorktreeStartup({
781
+ teamName: sanitized,
782
+ cwd: leaderCwd,
783
+ cause: error,
784
+ sessionName,
785
+ leaderPaneId,
786
+ workerPaneIds,
787
+ sessionMode: session.sessionMode,
788
+ });
789
+ throw error;
790
+ }
595
791
  const permissionsSnapshot = {
596
792
  approval_mode: process.env.OMC_APPROVAL_MODE || 'default',
597
793
  sandbox_mode: process.env.OMC_SANDBOX_MODE || 'default',
@@ -606,7 +802,7 @@ export async function startTeamV2(config) {
606
802
  worker_id: 'leader-fixed',
607
803
  role: 'leader',
608
804
  },
609
- policy: { ...DEFAULT_TEAM_TRANSPORT_POLICY, ...DEFAULT_TEAM_GOVERNANCE },
805
+ policy: DEFAULT_TEAM_TRANSPORT_POLICY,
610
806
  governance: DEFAULT_TEAM_GOVERNANCE,
611
807
  permissions_snapshot: permissionsSnapshot,
612
808
  tmux_session: sessionName,
@@ -617,13 +813,28 @@ export async function startTeamV2(config) {
617
813
  leader_cwd: leaderCwd,
618
814
  team_state_root: teamConfig.team_state_root,
619
815
  workspace_mode: teamConfig.workspace_mode,
816
+ worktree_mode: teamConfig.worktree_mode,
620
817
  leader_pane_id: leaderPaneId,
621
818
  hud_pane_id: null,
622
819
  resize_hook_name: null,
623
820
  resize_hook_target: null,
624
821
  next_worker_index: teamConfig.next_worker_index,
625
822
  };
626
- await writeFile(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), 'utf-8');
823
+ try {
824
+ await writeFile(absPath(leaderCwd, TeamPaths.manifest(sanitized)), JSON.stringify(teamManifest, null, 2), 'utf-8');
825
+ }
826
+ catch (error) {
827
+ await rollbackStartedNativeWorktreeStartup({
828
+ teamName: sanitized,
829
+ cwd: leaderCwd,
830
+ cause: error,
831
+ sessionName,
832
+ leaderPaneId,
833
+ workerPaneIds,
834
+ sessionMode: session.sessionMode,
835
+ });
836
+ throw error;
837
+ }
627
838
  // Spawn workers for initial tasks (at most one startup task per worker)
628
839
  const initialStartupAllocations = [];
629
840
  const seenStartupWorkers = new Set();
@@ -635,62 +846,146 @@ export async function startTeamV2(config) {
635
846
  if (initialStartupAllocations.length >= config.workerCount)
636
847
  break;
637
848
  }
638
- for (const decision of initialStartupAllocations) {
639
- const wName = decision.workerName;
640
- const workerIndex = Number.parseInt(wName.replace('worker-', ''), 10) - 1;
641
- const taskId = String(decision.taskIndex + 1);
642
- const task = config.tasks[decision.taskIndex];
643
- if (!task || workerIndex < 0)
644
- continue;
645
- // Route the task through the team's immutable snapshot (Option E).
646
- // Falls back to the round-robin agentType when the inferred role is
647
- // outside the canonical vocabulary (preserves pre-patch behavior).
648
- const fallbackAgent = (agentTypes[workerIndex % agentTypes.length] ?? agentTypes[0] ?? 'claude');
649
- const assignment = resolveTaskAssignment(task, resolvedRouting, pluginCfg.team?.roleRouting, resolvedBinaryPaths, fallbackAgent);
650
- const workerLaunch = await spawnV2Worker({
651
- sessionName,
652
- leaderPaneId,
653
- existingWorkerPaneIds: workerPaneIds,
654
- teamName: sanitized,
655
- workerName: wName,
656
- workerIndex,
657
- agentType: assignment.agentType,
658
- task,
659
- taskId,
660
- cwd: leaderCwd,
661
- resolvedBinaryPaths,
662
- ...(assignment.model ? { model: assignment.model } : {}),
663
- ...(assignment.role ? { role: assignment.role } : {}),
664
- });
665
- if (workerLaunch.paneId) {
666
- workerPaneIds.push(workerLaunch.paneId);
667
- const workerInfo = workersInfo[workerIndex];
668
- if (workerInfo) {
669
- workerInfo.pane_id = workerLaunch.paneId;
670
- workerInfo.assigned_tasks = workerLaunch.startupAssigned ? [taskId] : [];
671
- workerInfo.worker_cli = (assignment.agentType === 'copilot' ? 'claude' : assignment.agentType);
672
- if (workerLaunch.outputFile) {
673
- workerInfo.output_file = workerLaunch.outputFile;
849
+ try {
850
+ for (const decision of initialStartupAllocations) {
851
+ const wName = decision.workerName;
852
+ const workerIndex = Number.parseInt(wName.replace('worker-', ''), 10) - 1;
853
+ const taskId = String(decision.taskIndex + 1);
854
+ const task = config.tasks[decision.taskIndex];
855
+ if (!task || workerIndex < 0)
856
+ continue;
857
+ // Route the task through the team's immutable snapshot (Option E).
858
+ // Falls back to the round-robin agentType when the inferred role is
859
+ // outside the canonical vocabulary (preserves pre-patch behavior).
860
+ const fallbackAgent = (agentTypes[workerIndex % agentTypes.length] ?? agentTypes[0] ?? 'claude');
861
+ const assignment = resolveTaskAssignment(task, resolvedRouting, pluginCfg.team?.roleRouting, resolvedBinaryPaths, fallbackAgent);
862
+ const workerLaunch = await spawnV2Worker({
863
+ sessionName,
864
+ leaderPaneId,
865
+ existingWorkerPaneIds: workerPaneIds,
866
+ teamName: sanitized,
867
+ workerName: wName,
868
+ workerIndex,
869
+ agentType: assignment.agentType,
870
+ task,
871
+ taskId,
872
+ cwd: leaderCwd,
873
+ workerCwd: workersInfo[workerIndex]?.working_dir ?? leaderCwd,
874
+ worktreePath: workersInfo[workerIndex]?.worktree_path,
875
+ autoMerge: Boolean(config.autoMerge),
876
+ resolvedBinaryPaths,
877
+ ...(assignment.model ? { model: assignment.model } : {}),
878
+ ...(assignment.role ? { role: assignment.role } : {}),
879
+ });
880
+ if (workerLaunch.paneId) {
881
+ workerPaneIds.push(workerLaunch.paneId);
882
+ const workerInfo = workersInfo[workerIndex];
883
+ if (workerInfo) {
884
+ workerInfo.pane_id = workerLaunch.paneId;
885
+ workerInfo.assigned_tasks = workerLaunch.startupAssigned ? [taskId] : [];
886
+ workerInfo.worker_cli = assignment.agentType;
887
+ if (workerLaunch.outputFile) {
888
+ workerInfo.output_file = workerLaunch.outputFile;
889
+ }
674
890
  }
675
891
  }
676
- }
677
- if (workerLaunch.startupFailureReason) {
678
- await appendTeamEvent(sanitized, {
679
- type: 'team_leader_nudge',
680
- worker: 'leader-fixed',
681
- reason: `startup_manual_intervention_required:${wName}:${workerLaunch.startupFailureReason}`,
682
- }, leaderCwd);
892
+ if (workerLaunch.startupFailureReason) {
893
+ const logEventFailure = createSwallowedErrorLogger('team.runtime-v2.startTeamV2 appendTeamEvent failed');
894
+ appendTeamEvent(sanitized, {
895
+ type: 'team_leader_nudge',
896
+ worker: 'leader-fixed',
897
+ reason: `startup_manual_intervention_required:${wName}:${workerLaunch.startupFailureReason}`,
898
+ }, leaderCwd).catch(logEventFailure);
899
+ }
683
900
  }
684
901
  }
902
+ catch (error) {
903
+ await rollbackStartedNativeWorktreeStartup({
904
+ teamName: sanitized,
905
+ cwd: leaderCwd,
906
+ cause: error,
907
+ sessionName,
908
+ leaderPaneId,
909
+ workerPaneIds,
910
+ sessionMode: session.sessionMode,
911
+ });
912
+ throw error;
913
+ }
685
914
  // Persist config with pane IDs
686
915
  teamConfig.workers = workersInfo;
687
- await saveTeamConfig(teamConfig, leaderCwd);
916
+ try {
917
+ await saveTeamConfig(teamConfig, leaderCwd);
918
+ }
919
+ catch (error) {
920
+ await rollbackStartedNativeWorktreeStartup({
921
+ teamName: sanitized,
922
+ cwd: leaderCwd,
923
+ cause: error,
924
+ sessionName,
925
+ leaderPaneId,
926
+ workerPaneIds,
927
+ sessionMode: session.sessionMode,
928
+ });
929
+ throw error;
930
+ }
931
+ const logEventFailure = createSwallowedErrorLogger('team.runtime-v2.startTeamV2 appendTeamEvent failed');
688
932
  // Emit start event — NO watchdog, leader drives via monitorTeamV2()
689
- await appendTeamEvent(sanitized, {
933
+ appendTeamEvent(sanitized, {
690
934
  type: 'team_leader_nudge',
691
935
  worker: 'leader-fixed',
692
936
  reason: `start_team_v2: workers=${config.workerCount} tasks=${config.tasks.length} panes=${workerPaneIds.length}`,
693
- }, leaderCwd);
937
+ }, leaderCwd).catch(logEventFailure);
938
+ // Auto-merge orchestrator startup. Because --auto-merge is an explicit
939
+ // safety opt-in, startup/registration failures are fatal: continuing would
940
+ // leave users believing worker edits are being merged when they are not.
941
+ if (config.autoMerge && autoMergeLeaderBranch) {
942
+ try {
943
+ await ensureLeaderInbox(sanitized, leaderCwd);
944
+ // Seed an introductory leader-inbox note so the leader knows the inbox
945
+ // exists and where to read it. This mirrors the worker bootstrap pattern.
946
+ await appendToLeaderInbox(sanitized, extendLeaderBootstrapPrompt(sanitized), leaderCwd);
947
+ // M6: try to recover from a previous run before starting fresh.
948
+ try {
949
+ await recoverFromRestart({
950
+ teamName: sanitized,
951
+ repoRoot: leaderCwd,
952
+ leaderBranch: autoMergeLeaderBranch,
953
+ cwd: leaderCwd,
954
+ });
955
+ }
956
+ catch (recErr) {
957
+ process.stderr.write(`[team/runtime-v2] auto-merge recover-from-restart failed: ${recErr}\n`);
958
+ }
959
+ const orchestrator = await startMergeOrchestrator({
960
+ teamName: sanitized,
961
+ repoRoot: leaderCwd,
962
+ leaderBranch: autoMergeLeaderBranch,
963
+ cwd: leaderCwd,
964
+ });
965
+ registerTeamOrchestrator(sanitized, orchestrator);
966
+ // Register every spawned worker (named worktree mode is enforced above
967
+ // when autoMerge is on, so worker branches exist). A single failed
968
+ // registration makes the auto-merge contract unsafe, so fail loudly.
969
+ for (const w of workersInfo) {
970
+ await orchestrator.registerWorker(w.name);
971
+ }
972
+ }
973
+ catch (orchErr) {
974
+ await stopTeamCadence(sanitized);
975
+ unregisterTeamOrchestrator(sanitized);
976
+ await rollbackStartedNativeWorktreeStartup({
977
+ teamName: sanitized,
978
+ cwd: leaderCwd,
979
+ cause: orchErr,
980
+ sessionName,
981
+ leaderPaneId,
982
+ workerPaneIds,
983
+ sessionMode: session.sessionMode,
984
+ });
985
+ const reason = orchErr instanceof Error ? orchErr.message : String(orchErr);
986
+ throw new Error(`auto-merge startup failed: ${reason}`);
987
+ }
988
+ }
694
989
  return {
695
990
  teamName: sanitized,
696
991
  sanitizedName: sanitized,
@@ -837,8 +1132,8 @@ export async function processCliWorkerVerdicts(teamName, cwd) {
837
1132
  const outputFile = worker.output_file;
838
1133
  if (!outputFile)
839
1134
  continue;
840
- const alive = await isWorkerPaneAlive(worker.pane_id);
841
- if (alive)
1135
+ const liveness = await getWorkerPaneLiveness(worker.pane_id);
1136
+ if (liveness !== 'dead')
842
1137
  continue;
843
1138
  if (!fsExistsSync(outputFile)) {
844
1139
  results.push({ workerName: worker.name, taskId: null, status: 'file_missing' });
@@ -1002,16 +1297,17 @@ export async function monitorTeamV2(teamName, cwd) {
1002
1297
  const recommendations = [];
1003
1298
  const workerScanStartMs = performance.now();
1004
1299
  const workerSignals = await Promise.all(config.workers.map(async (worker) => {
1005
- const alive = await isWorkerPaneAlive(worker.pane_id);
1300
+ const liveness = await getWorkerPaneLiveness(worker.pane_id);
1301
+ const alive = liveness === 'alive';
1006
1302
  const [status, heartbeat, paneCapture] = await Promise.all([
1007
1303
  readWorkerStatus(sanitized, worker.name, cwd),
1008
1304
  readWorkerHeartbeat(sanitized, worker.name, cwd),
1009
1305
  alive ? captureWorkerPane(worker.pane_id) : Promise.resolve(''),
1010
1306
  ]);
1011
- return { worker, alive, status, heartbeat, paneCapture };
1307
+ return { worker, alive, liveness, status, heartbeat, paneCapture };
1012
1308
  }));
1013
1309
  const workerScanMs = performance.now() - workerScanStartMs;
1014
- for (const { worker: w, alive, status, heartbeat, paneCapture } of workerSignals) {
1310
+ for (const { worker: w, alive, liveness, status, heartbeat, paneCapture } of workerSignals) {
1015
1311
  const currentTask = status.current_task_id ? taskById.get(status.current_task_id) ?? null : null;
1016
1312
  const outstandingTask = currentTask ?? findOutstandingWorkerTask(w, taskById, inProgressByOwner);
1017
1313
  const expectedTaskId = status.current_task_id ?? outstandingTask?.id ?? w.assigned_tasks[0] ?? '';
@@ -1030,12 +1326,20 @@ export async function monitorTeamV2(teamName, cwd) {
1030
1326
  workers.push({
1031
1327
  name: w.name,
1032
1328
  alive,
1329
+ liveness,
1033
1330
  status,
1034
1331
  heartbeat,
1035
1332
  assignedTasks: w.assigned_tasks,
1333
+ working_dir: w.working_dir,
1334
+ worktree_repo_root: w.worktree_repo_root,
1335
+ worktree_path: w.worktree_path,
1336
+ worktree_branch: w.worktree_branch,
1337
+ worktree_detached: w.worktree_detached,
1338
+ worktree_created: w.worktree_created,
1339
+ team_state_root: w.team_state_root,
1036
1340
  turnsWithoutProgress,
1037
1341
  });
1038
- if (!alive) {
1342
+ if (liveness === 'dead') {
1039
1343
  deadWorkers.push(w.name);
1040
1344
  const deadWorkerTasks = inProgressByOwner.get(w.name) || [];
1041
1345
  for (const t of deadWorkerTasks) {
@@ -1046,8 +1350,14 @@ export async function monitorTeamV2(teamName, cwd) {
1046
1350
  const statusFresh = isFreshTimestamp(status.updated_at);
1047
1351
  const heartbeatFresh = isFreshTimestamp(heartbeat?.last_turn_at);
1048
1352
  const hasWorkStartEvidence = expectedTaskId !== '' && hasWorkerStatusProgress(status, expectedTaskId);
1353
+ const missingDependencyIds = outstandingTask
1354
+ ? getMissingDependencyIds(outstandingTask, taskById)
1355
+ : [];
1049
1356
  let stallReason = null;
1050
- if (paneSuggestsIdle && expectedTaskId !== '' && !hasWorkStartEvidence) {
1357
+ if (paneSuggestsIdle && missingDependencyIds.length > 0) {
1358
+ stallReason = 'missing_dependency';
1359
+ }
1360
+ else if (paneSuggestsIdle && expectedTaskId !== '' && !hasWorkStartEvidence) {
1051
1361
  stallReason = 'no_work_start_evidence';
1052
1362
  }
1053
1363
  else if (paneSuggestsIdle && expectedTaskId !== '' && (!statusFresh || !heartbeatFresh)) {
@@ -1058,7 +1368,10 @@ export async function monitorTeamV2(teamName, cwd) {
1058
1368
  }
1059
1369
  if (stallReason) {
1060
1370
  nonReportingWorkers.push(w.name);
1061
- if (stallReason === 'no_work_start_evidence') {
1371
+ if (stallReason === 'missing_dependency') {
1372
+ recommendations.push(`Investigate ${w.name}: task-${outstandingTask?.id ?? expectedTaskId} is blocked by missing task ids [${missingDependencyIds.join(', ')}]; pane is idle at prompt`);
1373
+ }
1374
+ else if (stallReason === 'no_work_start_evidence') {
1062
1375
  recommendations.push(`Investigate ${w.name}: assigned work but no work-start evidence; pane is idle at prompt`);
1063
1376
  }
1064
1377
  else if (stallReason === 'stale_or_missing_worker_reports') {
@@ -1079,19 +1392,27 @@ export async function monitorTeamV2(teamName, cwd) {
1079
1392
  failed: allTasks.filter((t) => t.status === 'failed').length,
1080
1393
  };
1081
1394
  const allTasksTerminal = taskCounts.pending === 0 && taskCounts.blocked === 0 && taskCounts.in_progress === 0;
1395
+ for (const task of allTasks) {
1396
+ const missingDependencyIds = getMissingDependencyIds(task, taskById);
1397
+ if (missingDependencyIds.length === 0) {
1398
+ continue;
1399
+ }
1400
+ recommendations.push(`Investigate task-${task.id}: depends on missing task ids [${missingDependencyIds.join(', ')}]`);
1401
+ }
1082
1402
  // Infer phase from task distribution
1083
1403
  const phase = inferPhase(allTasks.map((t) => ({
1084
1404
  status: t.status,
1085
1405
  metadata: undefined,
1086
1406
  })));
1087
1407
  // Emit monitor-derived events (task completions, worker state changes)
1088
- await emitMonitorDerivedEvents(sanitized, allTasks, workers.map((w) => ({ name: w.name, alive: w.alive, status: w.status })), previousSnapshot, cwd);
1408
+ await emitMonitorDerivedEvents(sanitized, allTasks, workers.map((w) => ({ name: w.name, alive: w.alive, liveness: w.liveness, status: w.status })), previousSnapshot, cwd);
1089
1409
  // Persist snapshot for next cycle
1090
1410
  const updatedAt = new Date().toISOString();
1091
1411
  const totalMs = performance.now() - monitorStartMs;
1092
1412
  await writeMonitorSnapshot(sanitized, {
1093
1413
  taskStatusById: Object.fromEntries(allTasks.map((t) => [t.id, t.status])),
1094
1414
  workerAliveByName: Object.fromEntries(workers.map((w) => [w.name, w.alive])),
1415
+ workerLivenessByName: Object.fromEntries(workers.map((w) => [w.name, w.liveness])),
1095
1416
  workerStateByName: Object.fromEntries(workers.map((w) => [w.name, w.status.state])),
1096
1417
  workerTurnCountByName: Object.fromEntries(workers.map((w) => [w.name, w.heartbeat?.turn_count ?? 0])),
1097
1418
  workerTaskIdByName: Object.fromEntries(workers.map((w) => [w.name, w.status.current_task_id ?? ''])),
@@ -1143,9 +1464,48 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
1143
1464
  const timeoutMs = options.timeoutMs ?? 15_000;
1144
1465
  const sanitized = sanitizeTeamName(teamName);
1145
1466
  const config = await readTeamConfig(sanitized, cwd);
1467
+ const finalizeAutoMerge = async () => {
1468
+ const orchestrator = getTeamOrchestrator(sanitized);
1469
+ if (orchestrator) {
1470
+ try {
1471
+ const drainResult = await orchestrator.drainAndStop();
1472
+ if (drainResult.unmerged.length > 0) {
1473
+ await appendTeamEvent(sanitized, {
1474
+ type: 'team_leader_nudge',
1475
+ worker: 'leader-fixed',
1476
+ reason: `auto_merge_drain_unmerged:${drainResult.unmerged.map((u) => `${u.workerName}:${u.reason}`).join(',')}`,
1477
+ }, cwd).catch(logEventFailure);
1478
+ }
1479
+ for (const w of config?.workers ?? []) {
1480
+ try {
1481
+ await orchestrator.unregisterWorker(w.name);
1482
+ }
1483
+ catch (err) {
1484
+ process.stderr.write(`[team/runtime-v2] orchestrator.unregisterWorker(${w.name}) failed: ${err}\n`);
1485
+ }
1486
+ }
1487
+ }
1488
+ catch (err) {
1489
+ process.stderr.write(`[team/runtime-v2] orchestrator drainAndStop: ${err}\n`);
1490
+ }
1491
+ finally {
1492
+ await stopTeamCadence(sanitized);
1493
+ unregisterTeamOrchestrator(sanitized);
1494
+ }
1495
+ }
1496
+ else {
1497
+ await stopTeamCadence(sanitized);
1498
+ }
1499
+ };
1146
1500
  if (!config) {
1147
- // No config available; only clean state. We intentionally avoid guessing
1148
- // a tmux session name here to prevent accidental self-session termination.
1501
+ // No config means worker liveness cannot be proven. Worktree metadata and
1502
+ // root AGENTS backups live under the scoped state tree, so use non-mutating
1503
+ // inspection and preserve state whenever any worktree recovery evidence exists.
1504
+ const cleanupSafety = inspectTeamWorktreeCleanupSafety(sanitized, cwd);
1505
+ if (cleanupSafety.hasEvidence) {
1506
+ process.stderr.write('[team/runtime-v2] preserving team state because config is missing and worktree cleanup evidence remains\n');
1507
+ return;
1508
+ }
1149
1509
  await cleanupTeamState(sanitized, cwd);
1150
1510
  return;
1151
1511
  }
@@ -1205,7 +1565,10 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
1205
1565
  await writeShutdownRequest(sanitized, w.name, 'leader-fixed', cwd);
1206
1566
  shutdownRequestTimes.set(w.name, requestedAt);
1207
1567
  // Write shutdown inbox
1208
- const shutdownInbox = `# Shutdown Request\n\nAll tasks are complete. Please wrap up and respond with a shutdown acknowledgement.\n\nWrite your ack to: ${TeamPaths.shutdownAck(sanitized, w.name)}\nFormat: {"status":"accept","reason":"ok","updated_at":"<iso>"}\n\nThen exit your session.\n`;
1568
+ const shutdownAckPath = w.worktree_path
1569
+ ? `$OMC_TEAM_STATE_ROOT/workers/${w.name}/shutdown-ack.json`
1570
+ : TeamPaths.shutdownAck(sanitized, w.name);
1571
+ const shutdownInbox = `# Shutdown Request\n\nAll tasks are complete. Please wrap up and respond with a shutdown acknowledgement.\n\nWrite your ack to: ${shutdownAckPath}\nFormat: {"status":"accept","reason":"ok","updated_at":"<iso>"}\n\nThen exit your session.\n`;
1209
1572
  await writeWorkerInbox(sanitized, w.name, shutdownInbox, cwd);
1210
1573
  }
1211
1574
  catch (err) {
@@ -1244,11 +1607,11 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
1244
1607
  await new Promise((r) => setTimeout(r, 2_000));
1245
1608
  }
1246
1609
  // 4. Force kill remaining tmux panes
1610
+ const recordedWorkerPaneIds = config.workers
1611
+ .map((w) => w.pane_id)
1612
+ .filter((p) => typeof p === 'string' && p.trim().length > 0);
1247
1613
  try {
1248
- const { killWorkerPanes, killTeamSession, resolveSplitPaneWorkerPaneIds } = await import('./tmux-session.js');
1249
- const recordedWorkerPaneIds = config.workers
1250
- .map((w) => w.pane_id)
1251
- .filter((p) => typeof p === 'string' && p.trim().length > 0);
1614
+ const { killWorkerPanes, killTeamSession, resolveSplitPaneWorkerPaneIds, getWorkerLiveness } = await import('./tmux-session.js');
1252
1615
  const ownsWindow = config.tmux_window_owned === true;
1253
1616
  const workerPaneIds = ownsWindow
1254
1617
  ? recordedWorkerPaneIds
@@ -1265,9 +1628,36 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
1265
1628
  : 'detached-session';
1266
1629
  await killTeamSession(config.tmux_session, workerPaneIds, config.leader_pane_id ?? undefined, { sessionMode });
1267
1630
  }
1631
+ const paneById = new Map(config.workers
1632
+ .filter((w) => typeof w.pane_id === 'string' && w.pane_id.trim().length > 0)
1633
+ .map((w) => [w.pane_id, w.name]));
1634
+ const liveness = await Promise.all(workerPaneIds.map(async (paneId) => [paneId, await getWorkerLiveness(paneId)]));
1635
+ const aliveWorkers = liveness
1636
+ .filter(([, state]) => state === 'alive')
1637
+ .map(([paneId]) => paneById.get(paneId) ?? paneId);
1638
+ if (aliveWorkers.length > 0) {
1639
+ process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane(s) are still alive: ${aliveWorkers.join(', ')}
1640
+ `);
1641
+ await finalizeAutoMerge();
1642
+ return;
1643
+ }
1644
+ const unknownWorkers = liveness
1645
+ .filter(([, state]) => state === 'unknown')
1646
+ .map(([paneId]) => paneById.get(paneId) ?? paneId);
1647
+ if (unknownWorkers.length > 0) {
1648
+ process.stderr.write(`[team/runtime-v2] preserving worktrees/state because worker pane liveness is unknown: ${unknownWorkers.join(', ')}
1649
+ `);
1650
+ await finalizeAutoMerge();
1651
+ return;
1652
+ }
1268
1653
  }
1269
1654
  catch (err) {
1270
1655
  process.stderr.write(`[team/runtime-v2] tmux cleanup: ${err}\n`);
1656
+ if (recordedWorkerPaneIds.length > 0) {
1657
+ process.stderr.write('[team/runtime-v2] preserving worktrees/state because tmux cleanup did not prove worker panes exited\n');
1658
+ await finalizeAutoMerge();
1659
+ return;
1660
+ }
1271
1661
  }
1272
1662
  // 5. Ralph completion logging
1273
1663
  if (ralph) {
@@ -1281,14 +1671,28 @@ export async function shutdownTeamV2(teamName, cwd, options = {}) {
1281
1671
  reason: `ralph_cleanup_summary: total=${finalTasks.length} completed=${completed} failed=${failed} pending=${pending} force=${force}`,
1282
1672
  }, cwd).catch(logEventFailure);
1283
1673
  }
1284
- // 6. Clean up state
1674
+ // 6a. Drain the merge orchestrator (if attached). Final merge sweep before
1675
+ // cleanupTeamWorktrees touches per-worker worktrees. Also used by preserve-state
1676
+ // exits above so auto-merge shutdown is not skipped when pane liveness is unknown.
1677
+ await finalizeAutoMerge();
1678
+ // 6. Clean up state. If worktree cleanup preserved dirty worktrees, keep the
1679
+ // team state directory too; it contains the metadata and root AGENTS.md backups
1680
+ // needed for a later safe cleanup attempt.
1681
+ let preservedWorktrees = 0;
1285
1682
  try {
1286
- cleanupTeamWorktrees(sanitized, cwd);
1683
+ const worktreeCleanup = cleanupTeamWorktrees(sanitized, cwd);
1684
+ preservedWorktrees = worktreeCleanup.preserved.length;
1287
1685
  }
1288
1686
  catch (err) {
1687
+ preservedWorktrees = 1;
1289
1688
  process.stderr.write(`[team/runtime-v2] worktree cleanup: ${err}\n`);
1290
1689
  }
1291
- await cleanupTeamState(sanitized, cwd);
1690
+ if (preservedWorktrees === 0) {
1691
+ await cleanupTeamState(sanitized, cwd);
1692
+ }
1693
+ else {
1694
+ process.stderr.write(`[team/runtime-v2] preserved ${preservedWorktrees} worktree(s); keeping team state for follow-up cleanup\n`);
1695
+ }
1292
1696
  }
1293
1697
  // ---------------------------------------------------------------------------
1294
1698
  // resumeTeam — reconstruct runtime from persisted state
@@ -1300,7 +1704,7 @@ export async function resumeTeamV2(teamName, cwd) {
1300
1704
  return null;
1301
1705
  // Verify tmux session is alive
1302
1706
  try {
1303
- const sessionName = config.tmux_session || `omcp-team-${sanitized}`;
1707
+ const sessionName = config.tmux_session || `omc-team-${sanitized}`;
1304
1708
  await tmuxExecAsync(['has-session', '-t', sessionName.split(':')[0]]);
1305
1709
  return {
1306
1710
  teamName: sanitized,