@overdeck/core 0.50.3 → 0.50.4

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 (208) hide show
  1. package/dist/agents-DNMRPC8n.js.map +1 -1
  2. package/dist/{build-info-BJuEVWOh.js → build-info-ioRdOmqT.js} +3 -3
  3. package/dist/{build-info-BJuEVWOh.js.map → build-info-ioRdOmqT.js.map} +1 -1
  4. package/dist/build-info-kMMitDOP.js +2 -0
  5. package/dist/cli/index.js +4 -4
  6. package/dist/conversation-compaction-GFfO5UZL.js.map +1 -1
  7. package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js → advancing-selfheal-Dp5_Gai3.js} +3 -3
  8. package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js.map → advancing-selfheal-Dp5_Gai3.js.map} +1 -1
  9. package/dist/dashboard/{agent-gc-Coz8zQt_.js → agent-gc-JzoSs5Pi.js} +3 -3
  10. package/dist/dashboard/{agent-gc-Coz8zQt_.js.map → agent-gc-JzoSs5Pi.js.map} +1 -1
  11. package/dist/dashboard/{agent-idle-CAwB-K6M.js → agent-idle-D-Qy5BN_.js} +2 -2
  12. package/dist/dashboard/{agent-idle-CAwB-K6M.js.map → agent-idle-D-Qy5BN_.js.map} +1 -1
  13. package/dist/dashboard/{agent-state-Ci6PhjOE.js → agent-state-B196fj1h.js} +38 -16
  14. package/dist/dashboard/agent-state-B196fj1h.js.map +1 -0
  15. package/dist/dashboard/{agent-state-CbUEwUEQ.js → agent-state-IpWnw8lv.js} +1 -1
  16. package/dist/dashboard/{agent-state-service-D6IVrX3B.js → agent-state-service-CCo5-9DR.js} +3 -3
  17. package/dist/dashboard/{agent-state-service-D6IVrX3B.js.map → agent-state-service-CCo5-9DR.js.map} +1 -1
  18. package/dist/dashboard/{agent-state-service-DcN1j6eK.js → agent-state-service-D_F3VZ23.js} +1 -1
  19. package/dist/dashboard/{agents-BVvL4o_4.js → agents-CkGhnAmu.js} +1 -1
  20. package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js → backlog-auto-trigger-B5Z2zBtC.js} +2 -2
  21. package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js.map → backlog-auto-trigger-B5Z2zBtC.js.map} +1 -1
  22. package/dist/dashboard/build-info-BTjOB0Xr.js +2 -0
  23. package/dist/dashboard/{build-info-Cj3Y0K87.js → build-info-D0EJNj6C.js} +3 -3
  24. package/dist/dashboard/{build-info-Cj3Y0K87.js.map → build-info-D0EJNj6C.js.map} +1 -1
  25. package/dist/dashboard/cli-CIznFrcE.js +2 -0
  26. package/dist/dashboard/{cli-DIhQieif.js → cli-DCbVrSsg.js} +2 -2
  27. package/dist/dashboard/{cli-DIhQieif.js.map → cli-DCbVrSsg.js.map} +1 -1
  28. package/dist/dashboard/{close-issue-CRlmrE5g.js → close-issue-Bmu9aOIx.js} +2 -2
  29. package/dist/dashboard/{close-issue-CRlmrE5g.js.map → close-issue-Bmu9aOIx.js.map} +1 -1
  30. package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js → closed-issue-reaper-COxfTtLz.js} +5 -5
  31. package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js.map → closed-issue-reaper-COxfTtLz.js.map} +1 -1
  32. package/dist/dashboard/{closed-issue-reaper-DWlw2EtL.js → closed-issue-reaper-DwMwG_Pz.js} +1 -1
  33. package/dist/dashboard/{compaction-continuation-DhYgsXmF.js → compaction-continuation-Bqc212Zm.js} +2 -2
  34. package/dist/dashboard/{compaction-continuation-DhYgsXmF.js.map → compaction-continuation-Bqc212Zm.js.map} +1 -1
  35. package/dist/dashboard/{conflict-gate-vczgdVTa.js → conflict-gate-9mPVg7-5.js} +1 -1
  36. package/dist/dashboard/{conflict-gate-Bt-lLgYf.js → conflict-gate-CGQTcR0I.js} +3 -3
  37. package/dist/dashboard/{conflict-gate-Bt-lLgYf.js.map → conflict-gate-CGQTcR0I.js.map} +1 -1
  38. package/dist/dashboard/{conversation-compaction-CsdCbcQB.js → conversation-compaction-C7P6V8Iw.js} +5 -5
  39. package/dist/dashboard/{conversation-compaction-CsdCbcQB.js.map → conversation-compaction-C7P6V8Iw.js.map} +1 -1
  40. package/dist/dashboard/{conversation-compaction-D0zgWM1f.js → conversation-compaction-Cck5oi-V.js} +1 -1
  41. package/dist/dashboard/{conversation-service-6LaymUV-.js → conversation-service-CsOpwI9Y.js} +37 -5
  42. package/dist/dashboard/{conversation-service-6LaymUV-.js.map → conversation-service-CsOpwI9Y.js.map} +1 -1
  43. package/dist/dashboard/conversation-service-plOFkoMo.js +3 -0
  44. package/dist/dashboard/dashboard-db-worker.js +1 -1
  45. package/dist/dashboard/{deacon-CU47dU7U.js → deacon-B8oMMIgN.js} +62 -62
  46. package/dist/dashboard/{deacon-CU47dU7U.js.map → deacon-B8oMMIgN.js.map} +1 -1
  47. package/dist/dashboard/{deacon-DYsOa_ss.js → deacon-b7J57Svt.js} +2 -2
  48. package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js → deacon-pending-lifecycle-3NIJ7ew5.js} +2 -2
  49. package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js.map → deacon-pending-lifecycle-3NIJ7ew5.js.map} +1 -1
  50. package/dist/dashboard/deacon.js +5 -5
  51. package/dist/dashboard/{delivery-DMYxIE0J.js → delivery-lnI-gQKo.js} +1 -1
  52. package/dist/dashboard/{deploy-patrol-C4eXco1S.js → deploy-patrol-u3v2krg9.js} +2 -2
  53. package/dist/dashboard/{deploy-patrol-C4eXco1S.js.map → deploy-patrol-u3v2krg9.js.map} +1 -1
  54. package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js → feedback-stuck-retirement-CoFNqjCc.js} +2 -2
  55. package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js.map → feedback-stuck-retirement-CoFNqjCc.js.map} +1 -1
  56. package/dist/dashboard/{feedback-target-BzdJ2wIR.js → feedback-target-BBCh3RRq.js} +1 -1
  57. package/dist/dashboard/{feedback-target-BcsA_GRV.js → feedback-target-CeCq6EhW.js} +5 -5
  58. package/dist/dashboard/{feedback-target-BcsA_GRV.js.map → feedback-target-CeCq6EhW.js.map} +1 -1
  59. package/dist/dashboard/{flywheel-Is3w2lcT.js → flywheel-DKldI63J.js} +7 -7
  60. package/dist/dashboard/{flywheel-Is3w2lcT.js.map → flywheel-DKldI63J.js.map} +1 -1
  61. package/dist/dashboard/{flywheel-D0guNqkh.js → flywheel-DpRPLErh.js} +7 -7
  62. package/dist/dashboard/{flywheel-D0guNqkh.js.map → flywheel-DpRPLErh.js.map} +1 -1
  63. package/dist/dashboard/{flywheel-B34cdfbd.js → flywheel-jeZFYvm-.js} +1 -1
  64. package/dist/dashboard/{flywheel-merge-order-6HiPR2WG.js → flywheel-merge-order-0OnoosaO.js} +1 -1
  65. package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js → flywheel-merge-order-PG5op4j7.js} +2 -2
  66. package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js.map → flywheel-merge-order-PG5op4j7.js.map} +1 -1
  67. package/dist/dashboard/{harness-resolve-CzTFVZ5l.js → harness-resolve-C0HWj3H9.js} +2 -2
  68. package/dist/dashboard/{harness-resolve-CzTFVZ5l.js.map → harness-resolve-C0HWj3H9.js.map} +1 -1
  69. package/dist/dashboard/{identity-Dc7rQLln.js → identity-Cot1Hg4G.js} +2 -2
  70. package/dist/dashboard/{identity-Dc7rQLln.js.map → identity-Cot1Hg4G.js.map} +1 -1
  71. package/dist/dashboard/{inspect-agent-CP7Bu47O.js → inspect-agent-BDmLWbGx.js} +3 -3
  72. package/dist/dashboard/{inspect-agent-CP7Bu47O.js.map → inspect-agent-BDmLWbGx.js.map} +1 -1
  73. package/dist/dashboard/inspect-agent-BcOkA-hC.js +2 -0
  74. package/dist/dashboard/{issue-lifecycle-BPbczKyy.js → issue-lifecycle-1B_v8Rf5.js} +6 -6
  75. package/dist/dashboard/{issue-lifecycle-BPbczKyy.js.map → issue-lifecycle-1B_v8Rf5.js.map} +1 -1
  76. package/dist/dashboard/{issue-lifecycle-FaugTi39.js → issue-lifecycle-CIrfbIbj.js} +1 -1
  77. package/dist/dashboard/{issue-service-singleton-Dsad9NTt.js → issue-service-singleton-BVwPzjwF.js} +1 -1
  78. package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js → issue-service-singleton-KcRrOiFD.js} +2 -2
  79. package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js.map → issue-service-singleton-KcRrOiFD.js.map} +1 -1
  80. package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js → jsonl-resolver-jxbif7MD.js} +3 -3
  81. package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js.map → jsonl-resolver-jxbif7MD.js.map} +1 -1
  82. package/dist/dashboard/{kimi-code-Cx5YUJpS.js → kimi-code-BZh3NgcN.js} +1 -1
  83. package/dist/dashboard/{lifecycle-DZFHmpGI.js → lifecycle-CvcZ4_fA.js} +4 -4
  84. package/dist/dashboard/{lifecycle-DZFHmpGI.js.map → lifecycle-CvcZ4_fA.js.map} +1 -1
  85. package/dist/dashboard/{linear-mcp-auth-DylPJABZ.js → linear-mcp-auth-CZoj_-d7.js} +1 -1
  86. package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js → linear-mcp-auth-Dx74yCAY.js} +2 -2
  87. package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js.map → linear-mcp-auth-Dx74yCAY.js.map} +1 -1
  88. package/dist/dashboard/{memory-governor-B7uDWRo9.js → memory-governor-Bv3LAqk3.js} +5 -5
  89. package/dist/dashboard/{memory-governor-B7uDWRo9.js.map → memory-governor-Bv3LAqk3.js.map} +1 -1
  90. package/dist/dashboard/{merge-agent-ddEcZDZD.js → merge-agent-1zVLl6k_.js} +1 -1
  91. package/dist/dashboard/{merge-agent-ttRY053V.js → merge-agent-8zrQu48t.js} +8 -8
  92. package/dist/dashboard/{merge-agent-ttRY053V.js.map → merge-agent-8zrQu48t.js.map} +1 -1
  93. package/dist/dashboard/merge-ops-CFJaMPLu.js +2 -0
  94. package/dist/dashboard/{merge-train-f_un38UJ.js → merge-train-BYH_cF7G.js} +2 -2
  95. package/dist/dashboard/{merge-train-f_un38UJ.js.map → merge-train-BYH_cF7G.js.map} +1 -1
  96. package/dist/dashboard/{merge-train-deps-GPlHOjK6.js → merge-train-deps-BTfXM5AR.js} +2 -2
  97. package/dist/dashboard/{merge-train-deps-GPlHOjK6.js.map → merge-train-deps-BTfXM5AR.js.map} +1 -1
  98. package/dist/dashboard/{messaging-BasbftDu.js → messaging-Bk6vGsMw.js} +1 -1
  99. package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js → orphan-proposed-reconciler-B898fk26.js} +6 -6
  100. package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js.map → orphan-proposed-reconciler-B898fk26.js.map} +1 -1
  101. package/dist/dashboard/{orphan-proposed-reconciler-DzAxrM99.js → orphan-proposed-reconciler-Bx_aWS4B.js} +1 -1
  102. package/dist/dashboard/{parked-residue-DMtbxJml.js → parked-residue-q2kzrYSm.js} +9 -9
  103. package/dist/dashboard/{parked-residue-DMtbxJml.js.map → parked-residue-q2kzrYSm.js.map} +1 -1
  104. package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js → post-merge-lifecycle-worker-Cl7-uf4g.js} +2 -2
  105. package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js.map → post-merge-lifecycle-worker-Cl7-uf4g.js.map} +1 -1
  106. package/dist/dashboard/{project-resource-refresh-queue-cqOeJ0fB.js → project-resource-refresh-queue-O_dRfPHn.js} +1 -1
  107. package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js → project-resource-refresh-queue-se3T9IYP.js} +4 -4
  108. package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js.map → project-resource-refresh-queue-se3T9IYP.js.map} +1 -1
  109. package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js → reconstruct-cache-XpWC9L0F.js} +2 -2
  110. package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js.map → reconstruct-cache-XpWC9L0F.js.map} +1 -1
  111. package/dist/dashboard/{release-engine-78iCajIc.js → release-engine-BuzxOamf.js} +2 -2
  112. package/dist/dashboard/{release-engine-78iCajIc.js.map → release-engine-BuzxOamf.js.map} +1 -1
  113. package/dist/dashboard/{remote-completion-D5wzZCte.js → remote-completion-BCZgeKPS.js} +3 -3
  114. package/dist/dashboard/{remote-completion-D5wzZCte.js.map → remote-completion-BCZgeKPS.js.map} +1 -1
  115. package/dist/dashboard/{resolver-BGZh3lXA.js → resolver-BeNrqNou.js} +5 -5
  116. package/dist/dashboard/{resolver-BGZh3lXA.js.map → resolver-BeNrqNou.js.map} +1 -1
  117. package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js → resource-pressure-patrol-Ccg_aCgO.js} +3 -3
  118. package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js.map → resource-pressure-patrol-Ccg_aCgO.js.map} +1 -1
  119. package/dist/dashboard/{resume-BKyB4hA1.js → resume-KGCrn0DV.js} +1 -1
  120. package/dist/dashboard/{review-agent-ZdP-HmwY.js → review-agent-BmM6YMZW.js} +1 -1
  121. package/dist/dashboard/{review-agent-DHs-JngN.js → review-agent-DRWVU8fP.js} +13 -13
  122. package/dist/dashboard/{review-agent-DHs-JngN.js.map → review-agent-DRWVU8fP.js.map} +1 -1
  123. package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js → review-convoy-liveness-EvCDv8Q_.js} +3 -3
  124. package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js.map → review-convoy-liveness-EvCDv8Q_.js.map} +1 -1
  125. package/dist/dashboard/{review-run-recovery-BYalQzbh.js → review-run-recovery-Ctm48Snt.js} +1 -1
  126. package/dist/dashboard/{review-run-recovery-BvVG3oUr.js → review-run-recovery-DNDI58cM.js} +2 -2
  127. package/dist/dashboard/{review-run-recovery-BvVG3oUr.js.map → review-run-recovery-DNDI58cM.js.map} +1 -1
  128. package/dist/dashboard/{review-status-DXTFzCmR.js → review-status-FnlvQuPI.js} +1 -1
  129. package/dist/dashboard/{review-status-emit-C9YW6SQL.js → review-status-emit-CeMS31DX.js} +2 -2
  130. package/dist/dashboard/{review-status-emit-C9YW6SQL.js.map → review-status-emit-CeMS31DX.js.map} +1 -1
  131. package/dist/dashboard/{review-status-BegAr86G.js → review-status-yY5qt7Rl.js} +5 -5
  132. package/dist/dashboard/{review-status-BegAr86G.js.map → review-status-yY5qt7Rl.js.map} +1 -1
  133. package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js → review-verdict-feedback-BNgRj1e3.js} +4 -4
  134. package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js.map → review-verdict-feedback-BNgRj1e3.js.map} +1 -1
  135. package/dist/dashboard/{review-verdict-feedback-Dp139Lmv.js → review-verdict-feedback-D39cuSV1.js} +1 -1
  136. package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js → review-verdict-writer-DvFfKAn-.js} +2 -2
  137. package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js.map → review-verdict-writer-DvFfKAn-.js.map} +1 -1
  138. package/dist/dashboard/{review-verdict-writer-BVco3WoI.js → review-verdict-writer-PcGBkvJ4.js} +1 -1
  139. package/dist/dashboard/{sequence-io-iljWlslY.js → sequence-io-DZyschsf.js} +2 -2
  140. package/dist/dashboard/{sequence-io-iljWlslY.js.map → sequence-io-DZyschsf.js.map} +1 -1
  141. package/dist/dashboard/{sequencer-agent-BGGVKxtw.js → sequencer-agent-BXDw5cNx.js} +5 -5
  142. package/dist/dashboard/{sequencer-agent-BGGVKxtw.js.map → sequencer-agent-BXDw5cNx.js.map} +1 -1
  143. package/dist/dashboard/server.js +79 -72
  144. package/dist/dashboard/server.js.map +1 -1
  145. package/dist/dashboard/{service-kzhN0DLg.js → service-DP50Omvn.js} +33 -33
  146. package/dist/dashboard/{service-kzhN0DLg.js.map → service-DP50Omvn.js.map} +1 -1
  147. package/dist/dashboard/{smart-compaction-owKHJDbL.js → smart-compaction-944GL8PN.js} +1 -1
  148. package/dist/dashboard/{smart-compaction-C96F2aFQ.js → smart-compaction-BzDfF6yb.js} +2 -2
  149. package/dist/dashboard/{smart-compaction-C96F2aFQ.js.map → smart-compaction-BzDfF6yb.js.map} +1 -1
  150. package/dist/dashboard/{spawn-planning-session-CiJdOHTD.js → spawn-planning-session-BcVnHJjm.js} +1 -1
  151. package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js → spawn-planning-session-CH8IYli7.js} +3 -3
  152. package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js.map → spawn-planning-session-CH8IYli7.js.map} +1 -1
  153. package/dist/dashboard/{specialist-context-_ootmzn2.js → specialist-context-DhDTXVht.js} +3 -3
  154. package/dist/dashboard/{specialist-context-_ootmzn2.js.map → specialist-context-DhDTXVht.js.map} +1 -1
  155. package/dist/dashboard/{specialist-logs-BbAX221-.js → specialist-logs-C1bcnZ-G.js} +2 -2
  156. package/dist/dashboard/{specialist-logs-BbAX221-.js.map → specialist-logs-C1bcnZ-G.js.map} +1 -1
  157. package/dist/dashboard/{specialists-COZoYaiz.js → specialists-BKmReEWT.js} +53 -53
  158. package/dist/dashboard/{specialists-COZoYaiz.js.map → specialists-BKmReEWT.js.map} +1 -1
  159. package/dist/dashboard/{specialists-RPLJnYc1.js → specialists-Bvrvt0br.js} +12 -12
  160. package/dist/dashboard/{specialists-RPLJnYc1.js.map → specialists-Bvrvt0br.js.map} +1 -1
  161. package/dist/dashboard/{specialists-DTAOVZpU.js → specialists-CD6GEKXk.js} +1 -1
  162. package/dist/dashboard/{specialists-D8sYZnPb.js → specialists-eFWpJRBc.js} +1 -1
  163. package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js → stall-sweeper-D3fZvg7O.js} +3 -3
  164. package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js.map → stall-sweeper-D3fZvg7O.js.map} +1 -1
  165. package/dist/dashboard/{state-migrate-J3tFJnbd.js → state-migrate-KZ2goscQ.js} +2 -2
  166. package/dist/dashboard/{state-migrate-J3tFJnbd.js.map → state-migrate-KZ2goscQ.js.map} +1 -1
  167. package/dist/dashboard/{stuck-remediation-BvTK_NYN.js → stuck-remediation-Cb2V1roj.js} +1 -1
  168. package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js → stuck-remediation-CnoV0kcv.js} +5 -5
  169. package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js.map → stuck-remediation-CnoV0kcv.js.map} +1 -1
  170. package/dist/dashboard/{summary-fork-PLcckTRE.js → summary-fork-CxuNBM0F.js} +2 -2
  171. package/dist/dashboard/{summary-fork-D-cylknJ.js → summary-fork-lC2vMXly.js} +4 -4
  172. package/dist/dashboard/{summary-fork-D-cylknJ.js.map → summary-fork-lC2vMXly.js.map} +1 -1
  173. package/dist/dashboard/{teardown-workspace-CTL16rnG.js → teardown-workspace-C-9murBw.js} +4 -4
  174. package/dist/dashboard/{teardown-workspace-CTL16rnG.js.map → teardown-workspace-C-9murBw.js.map} +1 -1
  175. package/dist/dashboard/{test-agent-queue-CMgYZiR_.js → test-agent-queue-YCBRzaAB.js} +3 -3
  176. package/dist/dashboard/{test-agent-queue-CMgYZiR_.js.map → test-agent-queue-YCBRzaAB.js.map} +1 -1
  177. package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js → uat-failure-feedback-B-1mL8Zp.js} +3 -3
  178. package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js.map → uat-failure-feedback-B-1mL8Zp.js.map} +1 -1
  179. package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js → uat-promote-verification-BEBPcpPg.js} +2 -2
  180. package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js.map → uat-promote-verification-BEBPcpPg.js.map} +1 -1
  181. package/dist/dashboard/{uat-train-BoA9r6_I.js → uat-train-BFo3OQUB.js} +1 -1
  182. package/dist/dashboard/{uat-train-Cd8CJeNe.js → uat-train-COgoJomz.js} +8 -8
  183. package/dist/dashboard/{uat-train-Cd8CJeNe.js.map → uat-train-COgoJomz.js.map} +1 -1
  184. package/dist/dashboard/{validate-BdmvTIym.js → validate-069_N1e2.js} +2 -2
  185. package/dist/dashboard/{validate-BdmvTIym.js.map → validate-069_N1e2.js.map} +1 -1
  186. package/dist/dashboard/{verification-runner-BO0K3xNA.js → verification-runner-CQnZyC5A.js} +1 -1
  187. package/dist/dashboard/{verification-runner-fEY1Qj-l.js → verification-runner-fy3wNrWn.js} +4 -4
  188. package/dist/dashboard/{verification-runner-fEY1Qj-l.js.map → verification-runner-fy3wNrWn.js.map} +1 -1
  189. package/dist/dashboard/workflows-Bk6Kt0ZQ.js +2 -0
  190. package/dist/dashboard/{workflows-CxVnygYU.js → workflows-N4QMkDzN.js} +10 -10
  191. package/dist/dashboard/{workflows-CxVnygYU.js.map → workflows-N4QMkDzN.js.map} +1 -1
  192. package/dist/dashboard/{workspaces-Cpz0xJWQ.js → workspaces-DmoyfHoU.js} +33 -33
  193. package/dist/dashboard/{workspaces-Cpz0xJWQ.js.map → workspaces-DmoyfHoU.js.map} +1 -1
  194. package/dist/{lifecycle-C6k0HWEO.js → lifecycle-DCFcjg7y.js} +1 -1
  195. package/dist/{lifecycle-D5oRpvvB.js → lifecycle-GkaHQJNK.js} +2 -2
  196. package/dist/{lifecycle-D5oRpvvB.js.map → lifecycle-GkaHQJNK.js.map} +1 -1
  197. package/dist/{merge-agent-Drufc7tU.js → merge-agent-CogNFasY.js} +2 -2
  198. package/dist/{merge-agent-Drufc7tU.js.map → merge-agent-CogNFasY.js.map} +1 -1
  199. package/dist/{merge-agent-CBOVY_eC.js → merge-agent-eteKQ9Rb.js} +1 -1
  200. package/package.json +1 -1
  201. package/dist/build-info-BgrwLUy6.js +0 -2
  202. package/dist/dashboard/agent-state-Ci6PhjOE.js.map +0 -1
  203. package/dist/dashboard/build-info-CYlxvZSQ.js +0 -2
  204. package/dist/dashboard/cli-ZAS502uC.js +0 -2
  205. package/dist/dashboard/conversation-service-Dlks_hSS.js +0 -3
  206. package/dist/dashboard/inspect-agent-DTuSg95i.js +0 -2
  207. package/dist/dashboard/merge-ops-Bgc-EScO.js +0 -2
  208. package/dist/dashboard/workflows-CgGNyl1z.js +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"deacon-CU47dU7U.js","names":["defaultDeps","RSS_SETTLE_MS","join","execFileAsync","execFileAsync","defaultDeps","promisify","exec","join","listAllAgents","join","basename","join","dirname","execAsync","join","listDurableSlotAssignments","countCommitsAhead","execAsync","existsSync","execAsync","defaultDeps","loadYamlConfig","existsSync","execFileAsync","defaultDeps","defaultDeps","execAsync","defaultDeps","listTmuxSessionNames","existsSync","listDurableSlotAssignments","existsSync","existsSync","readFileSync","join","handleAgentHeartbeatDeadEvent","cleanupOrphanedPlanningSessions","handleAgentStoppedEvent","reconcileAgentLiveness","listAllAgents","applyBootReconciliationDecision","handleAgentStoppedEvent","normalizeIssueId","join","defaultDeps","execFileAsync","REAP_GRACE_MS","loadConfig","execFileAsync","execAsync","promisify","exec","join","promisify","exec","execFile","join","handleAgentHeartbeatDeadEventWithDeps","cleanupOrphanedPlanningSessionsWithDeps","handleAgentStoppedEventWithDeps","applyBootReconciliationDecisionWithDeps","reconcileAgentLivenessWithDeps","checkReadyForMergeStuckWithDeps","readdir","base","dirname"],"sources":["../../src/lib/cloister/agent-grace.ts","../../src/lib/cloister/deacon-inspect.ts","../../src/lib/cloister/modal-detector.ts","../../src/lib/cloister/orchestrated-compaction.ts","../../src/lib/cloister/deacon-api-recovery.ts","../../src/lib/cloister/preemption.ts","../../src/lib/cloister/deacon-post-review-commits.ts","../../src/lib/agents/supervisor-liveness.ts","../../src/lib/cloister/deacon-strike-landing.ts","../../src/lib/cloister/orphan-restore-verdict.ts","../../src/lib/cloister/deacon-review-status.ts","../../src/lib/cloister/deacon-review-unsignaled.ts","../../src/lib/cloister/deacon-review-signals.ts","../../src/lib/agents/slot-merge.ts","../../src/lib/pan-dir/task-door.ts","../../src/lib/swarm-policy.ts","../../src/lib/cloister/deacon-workspaces.ts","../../src/lib/cloister/deacon-swarm-orphan-gc.ts","../../src/lib/cloister/deacon-swarm-record.ts","../../src/lib/cloister/deacon-swarm-completion.ts","../../src/lib/cloister/deacon-swarm-gc.ts","../../src/lib/cloister/swarm-tiered-hooks.ts","../../src/lib/cloister/redrive-gate.ts","../../src/lib/cloister/swarm-failed-slot.ts","../../src/lib/cloister/swarm-blocked-slot.ts","../../src/lib/cloister/swarm-foreman.ts","../../src/lib/cloister/swarm-foreman-liveness.ts","../../src/lib/cloister/deacon-swarm.ts","../../src/lib/cloister/boot-reconciliation-predicates.ts","../../src/lib/cloister/boot-reconciliation.ts","../../src/lib/agents/pinned-launch.ts","../../src/lib/agents/placeholder-reconciliation.ts","../../src/lib/cloister/confirmed-session-query.ts","../../src/lib/cloister/idle-nudge-inspection.ts","../../src/lib/cloister/deacon-auto-resume.ts","../../src/lib/cloister/boot-reconciliation-outcomes.ts","../../src/lib/cloister/boot-reconciliation-apply.ts","../../src/lib/agent-directory-cleanup.ts","../../src/lib/cloister/transcript-retention.ts","../../src/lib/cloister/state-plane-patrol.ts","../../src/lib/cloister/state-recreation-patrol.ts","../../src/lib/cloister/deacon-main-divergence.ts","../../src/lib/cloister/patrol-cadence.ts","../../src/lib/cloister/pending-promotion-reconciler.ts","../../src/lib/cloister/test-status-green-ci-reconciler.ts","../../src/lib/cloister/orphan-dashboard-server-reaper.ts","../../src/lib/cloister/playwright-mcp-reaper.ts","../../src/lib/cloister/strike-workspace-reaper.ts","../../src/lib/cloister/inspect-session-reaper.ts","../../src/lib/cloister/deacon-verdict-fallback-sweep.ts","../../src/lib/cloister/deacon.ts"],"sourcesContent":["export const WORK_LAUNCHER_GRACE_MS = 120_000;\n\nexport function isStartingWithinGrace(\n state: { status?: string; startedAt?: string },\n now = Date.now(),\n graceMs = WORK_LAUNCHER_GRACE_MS,\n): boolean {\n if (state.status !== 'starting') return false;\n const startedMs = Date.parse(state.startedAt ?? '');\n return Number.isFinite(startedMs) && now - startedMs < graceMs;\n}\n","import { Effect } from 'effect';\n\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { loadReviewStatuses, setReviewStatusSync } from '../review-status.js';\nimport { killSession, sessionExists } from '../tmux.js';\n\n/**\n * Inspect prompts state a 10-minute budget. Deacon gives the agent a small\n * grace window beyond that, then fails loud so the parent work agent never waits\n * forever for a verdict that will not arrive.\n */\nexport const INSPECT_TIMEOUT_MS = 12 * 60_000;\n\nfunction inspectSessionName(issueId: string, itemId: string): string {\n const issueLower = issueId.toLowerCase();\n const beadSlug = itemId.replace(/[^a-z0-9-]/gi, '-').toLowerCase().slice(0, 24);\n return `inspect-${issueLower}-${beadSlug}`;\n}\n\nfunction formatInspectElapsed(elapsedMs: number): string {\n return `${Math.max(0, Math.round(elapsedMs / 60_000))}m`;\n}\n\nexport async function checkInspectAgentTimeouts(now = new Date()): Promise<string[]> {\n const actions: string[] = [];\n const statuses = loadReviewStatuses();\n const nowMs = now.getTime();\n\n for (const [rawIssueId, status] of Object.entries(statuses)) {\n if (status.inspectStatus !== 'inspecting') continue;\n\n const issueId = rawIssueId.toUpperCase();\n const itemId = status.inspectBeadId;\n const startedMs = status.inspectStartedAt ? Date.parse(status.inspectStartedAt) : NaN;\n const hasStartedAt = Number.isFinite(startedMs);\n const elapsedMs = hasStartedAt ? nowMs - startedMs : Number.POSITIVE_INFINITY;\n const timedOut = elapsedMs > INSPECT_TIMEOUT_MS;\n // New inspections persist their actual owner. Derive the dedicated-session\n // name only for rows created before that metadata existed.\n const sessionName = status.inspectOwnerSession\n ?? (itemId ? inspectSessionName(issueId, itemId) : undefined);\n const sessionAlive = sessionName\n ? await Effect.runPromise(sessionExists(sessionName)).catch(() => false)\n : false;\n const crashed = !!sessionName && !sessionAlive;\n\n if (!timedOut && !crashed) continue;\n\n const reason = !hasStartedAt\n ? 'missing inspectStartedAt metadata'\n : timedOut\n ? `timed out after ${formatInspectElapsed(elapsedMs)} (limit ${formatInspectElapsed(INSPECT_TIMEOUT_MS)})`\n : `tmux session ${sessionName} exited before producing a verdict`;\n const effectiveBeadId = itemId ?? 'unknown';\n const notes = `Inspection error for bead ${effectiveBeadId}: ${reason}. No verdict was produced.`;\n const verdict = `INSPECTION ERROR for bead ${effectiveBeadId}: inspection could not complete (${reason}) — no verdict was produced. Treat as infrastructure failure: do not silently proceed.`;\n\n // Mark terminal first so the next patrol cycle skips this inspection even if\n // kill or delivery fails; this is the idempotency guard.\n setReviewStatusSync(issueId, {\n inspectStatus: 'error',\n inspectNotes: notes,\n });\n\n if (sessionName && sessionAlive) {\n await Effect.runPromise(killSession(sessionName)).catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[deacon] Failed to kill inspect session ${sessionName}: ${msg}`);\n });\n }\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(`agent-${issueId.toLowerCase()}`, verdict, 'deacon:inspect-watchdog');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[deacon] Failed to deliver inspect error verdict for ${issueId}: ${msg}`);\n }\n\n const action = `Inspection watchdog tripped for ${issueId} bead ${effectiveBeadId}: ${reason}`;\n actions.push(action);\n logDeaconEventSync(`checkInspectAgentTimeouts: ${action}`);\n }\n\n return actions;\n}\n","import { Effect } from 'effect';\nimport { capturePane, sendKeysAsync } from '../tmux.js';\n\nexport interface KnownModal {\n id: string;\n matches: (pane: string) => boolean;\n keys: readonly string[];\n}\n\nexport const KNOWN_AGENT_MODALS: readonly KnownModal[] = [{\n id: 'codex-rate-limit-model-nudge',\n matches: pane => /rate limit/i.test(pane) && /keep current model/i.test(pane),\n keys: ['Down', 'Enter'],\n}];\n\n// Claude Code boot-blocking TUI screens: first-run onboarding (theme picker,\n// login-method picker, OAuth paste prompt) and the folder-trust dialog. A\n// session parked on one never starts its JSONL transcript, so the conversation\n// chat view has nothing to render and its composer cannot answer the screen —\n// only the terminal can. These must never be auto-answered: the login picker\n// starts an OAuth flow, and completing a spurious onboarding can rewrite the\n// real ~/.claude.json (conv-20260714-4261: a transient config read race at\n// spawn made a fully-configured machine boot into fresh-install onboarding).\nconst CLAUDE_BOOT_SCREEN_PATTERNS: readonly RegExp[] = [\n /Choose the text style that looks best with your terminal/i,\n /Select login method:/i,\n /Do you trust the files in this (?:folder|directory)\\?/i,\n /Paste code here if prompted/i,\n];\n\nexport function paneShowsClaudeBootBlockingScreen(pane: string): boolean {\n return CLAUDE_BOOT_SCREEN_PATTERNS.some(pattern => pattern.test(pane));\n}\n\n/** Rollout JSONL is launch/turn attribution only until a captured switch proves otherwise. */\n// A \"switched/switching to <token>\" phrase only counts as a model switch when\n// the phrase names a model or the token is plausibly a model id. Without this\n// guard, git's \"Switched to branch 'feature/x'\" (printed by every checkout)\n// captured \"branch\" and halted the agent as model_divergence — this killed\n// PAN-1491's review convoy twice.\nconst MODEL_SHAPED_TOKEN = /^(?:gpt|o\\d|claude|kimi|glm|gemini|deepseek|qwen|minimax|grok)[\\w.-]*$/i;\n\nexport function paneShowsModelSwitch(pane: string, launchModel: string): boolean {\n const modelPhrase = pane.match(/model\\s+(?:switched|switching)\\s+to\\s+([\\w.-]+)|(?:switched|switching)\\s+model\\s+to\\s+([\\w.-]+)/i);\n let target = modelPhrase?.[1] ?? modelPhrase?.[2];\n if (!target) {\n const generic = pane.match(/(?:switched|switching)\\s+to\\s+([\\w.-]+)/i)?.[1];\n if (generic && MODEL_SHAPED_TOKEN.test(generic)) target = generic;\n }\n return Boolean(target && target.toLowerCase() !== launchModel.toLowerCase());\n}\n\nexport interface ModalHandlerDeps {\n capturePane: (agentId: string) => Promise<string>;\n sendKey: (agentId: string, key: string) => Promise<void>;\n settle: () => Promise<void>;\n}\n\nconst defaultDeps: ModalHandlerDeps = {\n capturePane: agentId => Effect.runPromise(capturePane(agentId, 100)),\n sendKey: (agentId, key) => sendKeysAsync(agentId, key, 'known-modal-handler'),\n settle: () => new Promise(resolve => setTimeout(resolve, 300)),\n};\n\nexport async function handleKnownAgentModal(agentId: string, deps: ModalHandlerDeps = defaultDeps, initialPane?: string): Promise<'none' | 'handled' | 'needs-you'> {\n const pane = initialPane ?? await deps.capturePane(agentId);\n const modal = KNOWN_AGENT_MODALS.find(candidate => candidate.matches(pane));\n if (!modal) return 'none';\n for (const key of modal.keys) await deps.sendKey(agentId, key);\n await deps.settle();\n return modal.matches(await deps.capturePane(agentId)) ? 'needs-you' : 'handled';\n}\n","import { isContextOverflowTail } from '../context-overflow.js';\n\nexport const COMPACTION_CONTINUE_MIN_SETTLE_MS = 30_000;\nexport const COMPACTION_CONTINUE_MAX_WAIT_MS = 150_000;\n\nexport const ORCHESTRATED_COMPACTION_CONTINUE_MESSAGE =\n 'Compaction complete. Continue from the compacted summary now. ' +\n 'Re-read your current xBRIEF item and latest feedback file if needed, then resume work; do not wait for further input.';\n\ninterface PendingCompactionContinuation {\n requestedAt: number;\n observedBusy: boolean;\n}\n\nexport const orchestratedCompactionContinuations = new Map<string, PendingCompactionContinuation>();\n\nexport function scheduleOrchestratedCompactionContinuation(\n sessionName: string,\n requestedAt = Date.now(),\n): void {\n orchestratedCompactionContinuations.set(sessionName, {\n requestedAt,\n observedBusy: false,\n });\n}\n\nexport function cancelOrchestratedCompactionContinuation(sessionName: string): void {\n orchestratedCompactionContinuations.delete(sessionName);\n}\n\nexport function hasOrchestratedCompactionContinuation(sessionName: string): boolean {\n return orchestratedCompactionContinuations.has(sessionName);\n}\n\nexport async function deliverOrchestratedCompact(\n sessionName: string,\n deliver: () => Promise<void>,\n): Promise<void> {\n scheduleOrchestratedCompactionContinuation(sessionName);\n try {\n await deliver();\n } catch (error) {\n cancelOrchestratedCompactionContinuation(sessionName);\n throw error;\n }\n}\n\nexport async function maybeContinueOrchestratedCompaction(args: {\n sessionName: string;\n tmuxOutput: string;\n now?: number;\n send: (sessionName: string, message: string) => Promise<void>;\n}): Promise<boolean> {\n const pending = orchestratedCompactionContinuations.get(args.sessionName);\n if (!pending) return false;\n\n const now = args.now ?? Date.now();\n const elapsed = now - pending.requestedAt;\n const hasPrompt = args.tmuxOutput.includes('❯');\n if (!hasPrompt) {\n pending.observedBusy = true;\n if (elapsed >= COMPACTION_CONTINUE_MAX_WAIT_MS) {\n orchestratedCompactionContinuations.delete(args.sessionName);\n }\n return false;\n }\n if (isContextOverflowTail(args.tmuxOutput)) {\n if (elapsed >= COMPACTION_CONTINUE_MAX_WAIT_MS) {\n orchestratedCompactionContinuations.delete(args.sessionName);\n }\n return false;\n }\n\n if (!pending.observedBusy && elapsed < COMPACTION_CONTINUE_MIN_SETTLE_MS) {\n return false;\n }\n\n await args.send(args.sessionName, ORCHESTRATED_COMPACTION_CONTINUE_MESSAGE);\n orchestratedCompactionContinuations.delete(args.sessionName);\n return true;\n}\n","import { Effect } from 'effect';\nimport { isContextOverflowTail } from '../context-overflow.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getAgentRuntimeStateSync, getAgentStateSync, saveAgentRuntimeState, saveAgentStateSync, setAgentPausedSync } from '../agents.js';\nimport { applyCodexAuthBurnFlag, isCodexAuthRouted, paneShowsCodexAuthBurn } from '../codex-auth.js';\nimport { markWorkspaceStuck } from '../overdeck/review-status-sync.js';\nimport { sessionFilePath } from '../paths.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { capturePane, listSessionNames, sendKeys } from '../tmux.js';\nimport { handleKnownAgentModal, paneShowsModelSwitch } from './modal-detector.js';\nimport {\n deliverOrchestratedCompact,\n hasOrchestratedCompactionContinuation,\n maybeContinueOrchestratedCompaction,\n} from './orchestrated-compaction.js';\n\n// ============================================================================\n// API Error Recovery\n// ============================================================================\n\n/**\n * API error patterns that indicate transient server failures.\n * When an agent stops with one of these in its tmux output, it should\n * be nudged to retry rather than left idle.\n */\nconst API_ERROR_PATTERNS = [\n 'API Error: The server had an error while processing your request',\n 'API Error: Overloaded',\n 'API Error: Rate limit',\n 'API Error: Request was aborted',\n 'API Error: Timed out',\n '529 Overloaded',\n '502 Bad Gateway',\n '503 Service Unavailable',\n];\n\n/**\n * Cooldown between API-error recovery nudges per agent.\n * Prevents spamming agents that are hitting persistent errors.\n */\nconst API_ERROR_RECOVERY_COOLDOWN_MS = 5 * 60_000; // 5 minutes\n\n/**\n * Track API-error recovery attempts per agent.\n */\nconst apiErrorRecoveryState: Map<string, { lastAttempt: number }> = new Map();\n\n/**\n * Context-window overflow is NOT a transient error. It surfaces as e.g.\n * \"API Error: 400 Your input exceeds the context window of this model.\" and\n * nudging \"continue\" only re-sends the same oversized context for the same\n * 400. Recovery shrinks the context: agent-* sessions are summarized\n * out-of-band and respawned fresh with the summary as their opening prompt\n * (PAN-1781); specialist/planning sessions get the harness /compact plus a\n * continue nudge once it settles.\n *\n * This matters most for non-Anthropic models routed through CLIProxy\n * (e.g. gpt-5.5): Claude Code's native auto-compact is keyed to a context\n * window it can't determine for the proxied model, so it never fires and the\n * backend's own limit produces a hard 400 instead.\n */\n/** Continuation nudge sent after a successful harness /compact (specialist/planning sessions). */\nconst CONTEXT_OVERFLOW_CONTINUE_MSG =\n 'Your context was compacted to recover from a context-window overflow. ' +\n 'Continue from where you left off using the compacted summary and your ' +\n 'xBRIEF items / continue.json — do NOT start over.';\n\n/**\n * Let a recovery attempt finish before judging the result. MUST exceed the\n * patrol interval (60s): at exactly one interval the guard expires on the very\n * next patrol regardless of timer drift, which is how a still-settling\n * respawn used to get re-judged (and escalated) one tick after its compaction.\n * Compact respawns also spend ~30-60s generating the summary before the fresh\n * session even launches.\n */\nconst CONTEXT_COMPACT_SETTLE_MS = 150_000;\n\nexport const CONTEXT_PROACTIVE_COMPACT_HIGH_WATER_PERCENT = 85;\n\n/** Bounded compact-respawn attempts per overflow incident before marking stuck. */\nconst MAX_CONTEXT_COMPACT_ATTEMPTS = 2;\n\ntype ContextOverflowRecovery = {\n lastAttempt: number;\n compactAttempts: number;\n /**\n * How the last attempt recovered: 'respawn' = PAN-1781 fresh-seeded respawn\n * (agent-* sessions; the seed is the opening prompt, no follow-up nudge\n * needed); 'harness-compact' = /compact keystroke (specialist/planning\n * sessions; needs a continue nudge once the compaction settles).\n */\n mechanism: 'respawn' | 'harness-compact';\n};\n\n/**\n * Per-session context-overflow recovery state. Present only while a recovery\n * tier is in flight; deleted once the overflow clears (so a later overflow is\n * a fresh incident) or on escalation. Kept separate from apiErrorRecoveryState\n * so the transient-error path is untouched.\n */\nexport const contextOverflowRecoveryState: Map<string, ContextOverflowRecovery> = new Map();\n\n/**\n * PAN-3334: Overdeck no longer proactively force-compacts agents at a context\n * high-water mark. Routine compaction is owned by the harness — every launcher\n * exports CLAUDE_CODE_AUTO_COMPACT_WINDOW per model (PAN-2441), so the session\n * compacts itself at the right threshold, and the PAN-3057 transcript-based\n * continuation net re-drives the agent afterwards. The forced path only raced\n * itself (no shared cooldown across injection sites), orphaned its in-memory\n * continuation on dashboard restart, and compacted sessions that had no\n * business being compacted (handed-off agents the continuation net then\n * correctly refused to re-drive). What remains below is crash recovery for\n * sessions already wedged at overflow.\n */\n\n/**\n * PAN-1675 (A2): bounded native-compaction recovery for agents already flagged\n * `stuck` with reason `context_overflow`. These got stuck under the OLD\n * /compact+/clear ladder (which predates Overdeck-side compaction), so they\n * never received a native-compaction attempt — and the `overflowBlocked` gate\n * would otherwise skip their recovery forever. We give them a small, bounded\n * number of out-of-band compaction attempts; if the agent keeps overflowing\n * after that, it stays stuck for a human.\n */\nexport const stuckOverflowNativeRecoveryState: Map<string, { attempts: number; lastAttempt: number }> = new Map();\nconst MAX_STUCK_NATIVE_RECOVERY = 2;\nconst STUCK_NATIVE_RECOVERY_COOLDOWN_MS = 10 * 60 * 1000;\n\n/**\n * Check for agents (work agents, specialists, planning) that stopped due\n * to transient API errors.\n *\n * Unlike stuck-thinking agents (which are actively processing), API-error\n * agents have stopped with the prompt showing. The tmux output contains\n * an error message from the provider. Recovery: send a \"continue\" nudge.\n */\nexport async function checkApiErrorAgents(): Promise<string[]> {\n const actions: string[] = [];\n const now = Date.now();\n\n // Check all tmux sessions — not just listRunningAgents() — because\n // specialist sessions aren't always in the agents registry.\n let sessionNames: readonly string[];\n try {\n sessionNames = await Effect.runPromise(listSessionNames());\n } catch {\n return actions;\n }\n\n const agentSessions = sessionNames.filter(\n name => name.startsWith('agent-') || name.startsWith('specialist-') || name.startsWith('planning-'),\n );\n // PAN-1818: convoy reviewer sub-role sessions (agent-<issue>-review-<subRole>)\n // are owned exclusively by monitorReviewConvoySignals(). checkApiErrorAgents\n // derives a garbage issueId from these names and would apply work-agent\n // compact-respawn, racing the monitor. Skip them here.\n const sessionsToInspect = agentSessions.filter(\n name => !/^agent-.*-review-(?:security|correctness|performance|requirements)$/.test(name)\n || hasOrchestratedCompactionContinuation(name),\n );\n\n for (const sessionName of sessionsToInspect) {\n const compactionPendingAtStart = hasOrchestratedCompactionContinuation(sessionName);\n const recovery = apiErrorRecoveryState.get(sessionName);\n if (!compactionPendingAtStart && recovery && (now - recovery.lastAttempt) < API_ERROR_RECOVERY_COOLDOWN_MS) {\n continue;\n }\n\n let tmuxOutput: string;\n try {\n tmuxOutput = await Effect.runPromise(capturePane(sessionName, 100));\n } catch {\n continue;\n }\n\n if (!tmuxOutput.trim()) continue;\n\n const agentState = getAgentStateSync(sessionName);\n // PAN-2285: a revoked refresh token surfaces ONLY in the live pane (never\n // in the rollout JSONL) and leaves the agent looking \"running\" while it\n // 401-loops. Trip the troubled gate so it stops silently sitting, and\n // persist the burn flag in agent state — this patrol runs in the deacon\n // CHILD process, so the flag must cross to the dashboard server (banner\n // endpoint + spawn gate) through the shared agents table, not module\n // memory. Do NOT auto-kill or auto-respawn — the shared token family is\n // dead until re-auth.\n //\n // PAN-3528: this check used to live inside the `harness === 'codex'` branch\n // below, so an openai-provider model routed through CLIProxy under\n // claude-code — the standing GPT routing on machines that set\n // `openai.harness: claude-code` — was never scanned, even though it burns on\n // the very same credential.\n if (agentState && isCodexAuthRouted(agentState.harness, agentState.model)) {\n if (paneShowsCodexAuthBurn(tmuxOutput)) {\n const newlyFlagged = applyCodexAuthBurnFlag(agentState);\n if (newlyFlagged) {\n saveAgentStateSync(agentState);\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n message: `${sessionName} marked troubled: Codex refresh token revoked (401 token_invalidated). Re-authenticate Codex — the dashboard banner has a Re-authenticate button, or run \\`codex login\\` on the host.`,\n issueId: agentState.issueId,\n });\n actions.push(`Codex-auth-burned halt: ${sessionName} marked troubled — needs re-authentication`);\n }\n continue;\n }\n }\n if (agentState?.harness === 'codex') {\n if (paneShowsModelSwitch(tmuxOutput, agentState.model)) {\n const reason = `needs-you: live Codex pane indicates a model switch from launch model ${agentState.model}`;\n setAgentPausedSync(sessionName, reason, true);\n markWorkspaceStuck(agentState.issueId, 'model_divergence', { reason });\n actions.push(`Model-divergence halt: ${sessionName} — needs-you`);\n continue;\n }\n const modal = await handleKnownAgentModal(sessionName, undefined, tmuxOutput);\n if (modal !== 'none') {\n const issueId = agentState.issueId;\n const reason = modal === 'handled'\n ? 'usage limit: Codex rate-limit reminder dismissed without changing model'\n : 'needs-you: Codex rate-limit dialog remained after Keep current model selection';\n setAgentPausedSync(sessionName, reason, true);\n if (issueId) markWorkspaceStuck(issueId, 'usage_limit', { reason });\n actions.push(`Usage-limit halt: ${sessionName} kept its configured model${modal === 'needs-you' ? ' — needs-you' : ''}`);\n continue;\n }\n }\n\n const compactionPending = compactionPendingAtStart\n || hasOrchestratedCompactionContinuation(sessionName);\n if (compactionPending) {\n try {\n const continuedAfterCompaction = await maybeContinueOrchestratedCompaction({\n sessionName,\n tmuxOutput,\n now,\n send: (target, message) => Effect.runPromise(sendKeys(target, message)),\n });\n if (continuedAfterCompaction) {\n contextOverflowRecoveryState.delete(sessionName);\n actions.push(`Context compaction recovery: resumed ${sessionName} after compaction`);\n continue;\n }\n } catch (err) {\n console.error(`[deacon] Failed to resume ${sessionName} after orchestrated compaction:`, err);\n }\n if (hasOrchestratedCompactionContinuation(sessionName)) {\n continue;\n }\n }\n\n const hasPrompt = tmuxOutput.includes('❯');\n if (!hasPrompt) continue;\n\n // ── Compacted-and-idle recovery (PAN-3057) ──────────────────────────────\n // The tier above only knows about compactions Overdeck itself sent. The\n // harness compacts on its own — notably when resuming a session whose turn\n // was interrupted — and a manual `/compact` ends the turn, so the agent goes\n // idle and no resume path considers it (it never stopped). Read the fact\n // from the transcript instead of from who pressed the button. This runs\n // BEFORE the overflow/high-water tiers so a just-compacted agent gets a\n // nudge rather than a second compaction.\n try {\n const { maybeContinueCompactedAgent } = await import('./compaction-continuation.js');\n const { findLastCompactBoundary } = await import(\n '../../dashboard/server/services/conversation-service.js'\n );\n const { deliverAgentMessage } = await import('../agents/delivery.js');\n const continued = await maybeContinueCompactedAgent({\n agentId: sessionName,\n tmuxOutput,\n now,\n send: (target, message) => deliverAgentMessage(target, message, 'deacon:compaction-continuation'),\n findBoundary: findLastCompactBoundary,\n });\n if (continued) {\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} stopped after a context compaction — continuing it`,\n issueId: getAgentStateSync(sessionName)?.issueId,\n });\n actions.push(continued);\n continue;\n }\n } catch (err) {\n console.error(`[deacon] Compaction-continuation check failed for ${sessionName}:`, err);\n }\n\n // ── Context-window overflow recovery (distinct from transient errors) ──\n // A 400 \"input exceeds the context window\" cannot be retried by continuing.\n // Recover by compacting; once the compaction has settled and the overflow\n // is gone, nudge the agent to resume. A loop guard escalates to stuck if\n // /compact never clears the overflow.\n {\n const issueId = sessionName.startsWith('agent-')\n ? sessionName.replace('agent-', '').toUpperCase()\n : null;\n const overflowBlocked = (() => {\n if (!issueId) return false;\n const st = getReviewStatusSync(issueId);\n return Boolean(st?.stuck || st?.deaconIgnored);\n })();\n const ov = contextOverflowRecoveryState.get(sessionName);\n // Judge overflow from only the recent tail: the error sits adjacent to the\n // idle prompt when an agent stops, and after a /compact redraw the old\n // error scrolls past this window — so a settled /compact that cleared the\n // overflow won't be misread as \"still overflowing\" from stale scrollback.\n const hasOverflow = isContextOverflowTail(tmuxOutput);\n const runtimeState = getAgentRuntimeStateSync(sessionName);\n if (hasOverflow) {\n if (!runtimeState?.contextSaturatedAt) {\n await saveAgentRuntimeState(sessionName, { contextSaturatedAt: new Date(now).toISOString() });\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} marked wedged: context-window overflow detected`,\n issueId: issueId ?? undefined,\n });\n }\n } else if (runtimeState?.contextSaturatedAt) {\n await saveAgentRuntimeState(sessionName, { contextSaturatedAt: undefined });\n }\n\n // PAN-1675 (A2): rescue agents already flagged stuck=context_overflow.\n // The old /compact+/clear ladder set `stuck` and the `overflowBlocked`\n // gate below then skips their recovery permanently — but those agents\n // never got a Overdeck-side (out-of-band) compaction, which can recover\n // an overflow the harness /compact could not. Give them a bounded number\n // of native-compaction attempts BEFORE the overflowBlocked gate. A\n // successful resumeAgent({compact:true}) clears the stuck flag (in\n // resumeAgent), so a recovered agent re-enters the normal flow. deacon-\n // ignored issues are still left alone.\n {\n const stuckStatus = issueId ? getReviewStatusSync(issueId) : null;\n const isStuckOverflow = Boolean(\n stuckStatus?.stuck && stuckStatus.stuckReason === 'context_overflow' && !stuckStatus.deaconIgnored,\n );\n if (isStuckOverflow) {\n if (!hasOverflow) {\n // The tail no longer shows the overflow error — but that is a WEAK\n // signal: the 400 line can scroll out of the captured window while\n // the agent is still pinned near 100% context. Only clear the stuck\n // flag on a POSITIVE recovery signal — the agent's actual JSONL\n // context usage is back below the proactive high-water mark.\n // Otherwise leave it stuck: a genuinely-full agent must not be\n // returned to the pipeline on a tail-string miss only to re-overflow\n // on its next turn (the false-recovery flap).\n let recoveredPct: number | null = null;\n try {\n const st = getAgentStateSync(sessionName);\n const sid = st?.sessionId ?? runtimeState?.claudeSessionId;\n if (st?.workspace && sid && st.model) {\n const { computeContextUsage } = await import('../../dashboard/server/services/conversation-service.js');\n const usage = await computeContextUsage(sessionFilePath(st.workspace, sid), st.model);\n if (usage && usage.percentUsed < CONTEXT_PROACTIVE_COMPACT_HIGH_WATER_PERCENT) {\n recoveredPct = usage.percentUsed;\n }\n }\n } catch { /* treat as not-yet-recovered — leave it stuck */ }\n if (recoveredPct !== null) {\n const { clearWorkspaceStuck } = await import('../overdeck/review-status-sync.js');\n clearWorkspaceStuck(issueId!);\n stuckOverflowNativeRecoveryState.delete(sessionName);\n actions.push(`Context overflow recovery: cleared stuck flag for ${sessionName} (context back to ${Math.round(recoveredPct)}%)`);\n }\n continue;\n }\n const rec = stuckOverflowNativeRecoveryState.get(sessionName) ?? { attempts: 0, lastAttempt: 0 };\n if (rec.attempts >= MAX_STUCK_NATIVE_RECOVERY) {\n // Native compaction tried its budget and the agent keeps\n // overflowing — genuinely needs a human; leave it stuck.\n continue;\n }\n if (rec.lastAttempt && (now - rec.lastAttempt) < STUCK_NATIVE_RECOVERY_COOLDOWN_MS) {\n continue;\n }\n rec.attempts += 1;\n rec.lastAttempt = now;\n stuckOverflowNativeRecoveryState.set(sessionName, rec);\n const { resumeAgent } = await import('../agents.js');\n const recovered = await resumeAgent(sessionName, undefined, { compact: true });\n if (recovered.success) {\n stuckOverflowNativeRecoveryState.delete(sessionName);\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} recovered from a stuck context-overflow via Overdeck-side compaction (attempt ${rec.attempts})`,\n issueId: issueId ?? undefined,\n });\n console.log(`[deacon] Agent ${sessionName} recovered from stuck context-overflow via native compaction (attempt ${rec.attempts})`);\n actions.push(`Context overflow recovery: native-compacted previously-stuck ${sessionName} (attempt ${rec.attempts})`);\n continue;\n }\n // Respawn failed — a spawn-level error (seed generation never fails:\n // it degrades to a reseed-only seed inside resumeAgent). PAN-1781\n // removed the /clear keystroke tier; the bounded retry budget +\n // cooldown above covers transient spawn failures, and an exhausted\n // budget correctly leaves the agent stuck for a human.\n console.warn(`[deacon] Compact respawn failed for stuck ${sessionName} (${recovered.error ?? 'unknown'}; attempt ${rec.attempts}/${MAX_STUCK_NATIVE_RECOVERY})`);\n actions.push(`Context overflow recovery: compact respawn failed for stuck ${sessionName} (attempt ${rec.attempts})`);\n continue;\n }\n }\n\n if (!overflowBlocked) {\n if (ov && (now - ov.lastAttempt) < CONTEXT_COMPACT_SETTLE_MS) {\n // A recovery tier is in flight — give it time to finish before judging.\n continue;\n }\n\n if (ov && !hasOverflow) {\n // The previous tier cleared the overflow. A harness /compact leaves\n // the agent idle at the compacted summary, so nudge it to resume. A\n // PAN-1781 fresh-seeded respawn needs no nudge — its seed (summary +\n // reseed instructions) IS the opening prompt.\n if (ov.mechanism === 'harness-compact') {\n try {\n await Effect.runPromise(sendKeys(sessionName, CONTEXT_OVERFLOW_CONTINUE_MSG));\n console.log(`[deacon] Agent ${sessionName} resumed after context-overflow compaction`);\n actions.push(`Context overflow recovery: resumed ${sessionName} after compaction`);\n } catch (err) {\n console.error(`[deacon] Failed to resume ${sessionName} after compaction:`, err);\n }\n } else {\n actions.push(`Context overflow recovery: ${sessionName} recovered after compact respawn`);\n }\n contextOverflowRecoveryState.delete(sessionName);\n continue;\n }\n\n if (hasOverflow) {\n // Loop guard: a bounded number of compact attempts per incident, then\n // escalate to stuck for a human. PAN-1781 removed the /clear keystroke\n // tier — a respawn that still overflows means something is deeply\n // wrong (e.g. summarization producing oversized seeds), and blowing\n // away the context with /clear just hides it.\n if (ov && ov.compactAttempts >= MAX_CONTEXT_COMPACT_ATTEMPTS) {\n if (issueId) {\n markWorkspaceStuck(issueId, 'context_overflow', {\n compactAttempts: ov.compactAttempts,\n });\n }\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n message: `${sessionName} stuck: context-window overflow persisted after ${ov.compactAttempts} compact-recovery attempts`,\n issueId: issueId ?? undefined,\n });\n console.error(`[deacon] Agent ${sessionName} stuck after ${ov.compactAttempts} compact-recovery attempts — escalating`);\n contextOverflowRecoveryState.delete(sessionName);\n continue;\n }\n\n const compactAttempts = (ov?.compactAttempts ?? 0) + 1;\n\n // PAN-1781: agent-* sessions recover via summarize + fresh-seeded\n // respawn (resumeAgent({compact:true})): the wedged session is\n // summarized out-of-band and a FRESH session is spawned with that\n // summary as its opening prompt. Never the harness `/compact` (which\n // deadlocks past the ceiling) and never an in-place JSONL boundary +\n // --resume (which the harness's resume leaf selection bypassed ~half\n // the time, silently rebuilding the full overflowed context — the\n // root cause behind every \"compaction didn't work → /clear\" incident\n // up to PAN-1775). Attempts are counted on failure too, so a\n // persistently failing respawn exhausts the budget and escalates\n // instead of retrying forever.\n if (issueId !== null) {\n const { resumeAgent } = await import('../agents.js');\n const resumeResult = await resumeAgent(sessionName, undefined, { compact: true });\n contextOverflowRecoveryState.set(sessionName, {\n lastAttempt: now,\n compactAttempts,\n mechanism: 'respawn',\n });\n if (resumeResult.success) {\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} hit context-window overflow — respawned fresh with a compact-summary seed (attempt ${compactAttempts})`,\n issueId: issueId ?? undefined,\n });\n console.log(`[deacon] Agent ${sessionName} hit context-window overflow — compact-respawned (attempt ${compactAttempts})`);\n actions.push(`Context overflow recovery: compact-respawned ${sessionName} (attempt ${compactAttempts})`);\n } else {\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} compact respawn failed (${resumeResult.error ?? 'unknown'}) — will retry after settle (attempt ${compactAttempts}/${MAX_CONTEXT_COMPACT_ATTEMPTS})`,\n issueId: issueId ?? undefined,\n });\n console.warn(`[deacon] Compact respawn failed for ${sessionName} (${resumeResult.error ?? 'unknown'}; attempt ${compactAttempts}/${MAX_CONTEXT_COMPACT_ATTEMPTS})`);\n actions.push(`Context overflow recovery: compact respawn failed for ${sessionName} (attempt ${compactAttempts})`);\n }\n continue;\n }\n\n // Non-agent (specialist/planning) sessions are not registered agents,\n // so the respawn path doesn't apply — keep the harness /compact tier.\n try {\n await deliverOrchestratedCompact(\n sessionName,\n () => Effect.runPromise(sendKeys(sessionName, '/compact')),\n );\n contextOverflowRecoveryState.set(sessionName, {\n lastAttempt: now,\n compactAttempts,\n mechanism: 'harness-compact',\n });\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} hit context-window overflow — compacting to recover (attempt ${compactAttempts})`,\n issueId: issueId ?? undefined,\n });\n console.log(`[deacon] Agent ${sessionName} hit context-window overflow — sent /compact (attempt ${compactAttempts})`);\n actions.push(`Context overflow recovery: compacting ${sessionName} (attempt ${compactAttempts})`);\n } catch (err) {\n console.error(`[deacon] Failed to send /compact to ${sessionName}:`, err);\n }\n continue;\n }\n }\n }\n\n const hasApiError = API_ERROR_PATTERNS.some(pattern => tmuxOutput.includes(pattern));\n if (!hasApiError) continue;\n\n // For work agents, respect stuck/deacon-ignored flags\n if (sessionName.startsWith('agent-')) {\n const agentIssueId = (sessionName.replace('agent-', '')).toUpperCase();\n const agentReviewStatus = getReviewStatusSync(agentIssueId);\n if (agentReviewStatus?.stuck || agentReviewStatus?.deaconIgnored) {\n continue;\n }\n }\n\n console.log(`[deacon] Agent ${sessionName} stopped with API error — nudging retry`);\n\n try {\n const continueMsg = 'You stopped due to a transient API error. This is a temporary server issue, not a problem with your work. Continue from where you left off. Do NOT start over — pick up exactly where you stopped.';\n await Effect.runPromise(sendKeys(sessionName, continueMsg));\n apiErrorRecoveryState.set(sessionName, { lastAttempt: now });\n actions.push(`API error recovery: nudged ${sessionName} to retry`);\n } catch (err) {\n console.error(`[deacon] Failed to nudge ${sessionName} for API error retry:`, err);\n }\n }\n\n return actions;\n}\n","/**\n * PAN-2507: preemptive pipeline scheduler.\n *\n * When a review/test/merge (\"advancing\") dispatch cannot reserve capacity, the\n * deacon normally defers to the next patrol — indefinitely if idle work agents\n * hold the box. This module is the single owner of the YIELD mechanic: it pauses\n * an idle work agent (resumable — session killed, state preserved) to free the\n * slot/memory, so the advancing dispatch can proceed, and resumes yielded agents\n * oldest-first once capacity returns.\n *\n * Priority is fixed (higher preempts lower, never the reverse):\n * merge/ship > test > review > work-rework > work-new.\n * Preemption only flows DOWN this list — an advancing dispatch may yield a work\n * agent; new work never preempts anything; advancing roles never preempt each\n * other.\n *\n * Opt-in: gated on `[concurrency] preemption = true` in cloister.toml. Default\n * off ⇒ every entry point is a no-op and the dispatch sites keep their existing\n * defer-until-attrition behavior (PAN-2507 AC-1).\n *\n * All tmux interaction here goes through async primitives (NFR-1): the yield stop\n * uses the async `stopAgent` Effect, and attach-detection reads the async\n * `listSessions()`.\n */\n\nimport { Effect } from 'effect';\n\nimport {\n clearYieldForResumeSync,\n listAgentStates,\n listRunningAgentsSync,\n resumeAgent,\n setAgentYieldedSync,\n stopAgent,\n type AgentState,\n} from '../agents.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { listSessions } from '../tmux.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { loadCloisterConfigSync } from './config.js';\nimport { isAgentIdleForNudge } from './agent-idle.js';\nimport { assessMemoryPressure } from './memory-governor.js';\nimport { tryReserveAdvancingSlot } from './concurrency.js';\n\n/** RSS settle window after a resume before the next memory re-assessment (mirrors deacon-auto-resume). */\nconst RSS_SETTLE_MS = 2000;\n\nexport type AdvancingRole = 'review' | 'test' | 'ship';\n\nexport interface YieldOutcome {\n yielded: boolean;\n victimId?: string;\n reason?: string;\n}\n\n/**\n * A running work agent considered for yielding. Carries the precomputed\n * eligibility signals so `selectYieldVictim` is pure and unit-testable.\n */\nexport interface YieldCandidate {\n id: string;\n issueId: string;\n /** `isAgentIdleForNudge` — only idle agents may be yielded (never preempt active work). */\n idle: boolean;\n /** An operator is attached to the tmux session — never yield out from under a human. */\n attached: boolean;\n /** Already paused (operator pause or a prior yield) — not a fresh victim. */\n paused: boolean;\n /**\n * The agent's own issue is blocked on the pipeline (reviewStatus pending or\n * reviewing) — it is waiting anyway, so prefer it as a victim (FR-2a).\n */\n reviewBlocked: boolean;\n /** `lastActivity` epoch ms for longest-idle ordering (ascending). Null ⇒ treated as oldest. */\n lastActivityMs: number | null;\n /** `lastYieldResumeAt` epoch ms for the re-yield cooldown. Null ⇒ never yielded. */\n lastYieldResumeMs: number | null;\n}\n\n/**\n * FR-2 predicate + ordering, pure. Returns the best victim or null.\n *\n * Excludes any agent that is not idle, is operator-attached, is already paused,\n * or is inside its post-resume re-yield cooldown. Among the eligible, prefers\n * (a) an agent blocked on its own review, then (b) the longest-idle\n * (`lastActivity` ascending).\n */\nexport function selectYieldVictim(\n candidates: readonly YieldCandidate[],\n nowMs: number,\n cooldownSecs: number,\n): YieldCandidate | null {\n const cooldownMs = cooldownSecs * 1000;\n const eligible = candidates.filter((c) => {\n if (!c.idle) return false;\n if (c.attached) return false;\n if (c.paused) return false;\n if (c.lastYieldResumeMs !== null && nowMs - c.lastYieldResumeMs < cooldownMs) return false;\n return true;\n });\n if (eligible.length === 0) return null;\n\n const ordered = [...eligible].sort((a, b) => {\n // (a) prefer pipeline-blocked agents\n if (a.reviewBlocked !== b.reviewBlocked) return a.reviewBlocked ? -1 : 1;\n // (b) then longest-idle first (oldest lastActivity)\n return (a.lastActivityMs ?? 0) - (b.lastActivityMs ?? 0);\n });\n return ordered[0];\n}\n\nfunction reviewBlockedFor(issueId: string): boolean {\n const status = getReviewStatusSync(issueId)?.reviewStatus;\n // PAN-2507 (FR-2a): the enum has no `in_progress`; the faithful \"waiting on\n // its own review\" states are `pending` (queued) and `reviewing` (running).\n return status === 'pending' || status === 'reviewing';\n}\n\nfunction parseMs(iso: string | undefined): number | null {\n if (!iso) return null;\n const ms = Date.parse(iso);\n return Number.isFinite(ms) ? ms : null;\n}\n\nasync function buildCandidates(): Promise<YieldCandidate[]> {\n const sessions = await Effect.runPromise(listSessions());\n const attached = new Set(sessions.filter((s) => s.attached).map((s) => s.name));\n\n return listRunningAgentsSync()\n .filter((s) => s.role === 'work' && s.status === 'running')\n .map((s) => ({\n id: s.id,\n issueId: s.issueId,\n idle: isAgentIdleForNudge(s.id),\n attached: attached.has(s.id),\n paused: s.paused === true,\n reviewBlocked: reviewBlockedFor(s.issueId),\n lastActivityMs: parseMs(s.lastActivity),\n lastYieldResumeMs: parseMs(s.lastYieldResumeAt),\n }));\n}\n\nfunction countYielded(): number {\n return listAgentStates().filter((s: AgentState) => s.yieldedByScheduler === true).length;\n}\n\n/**\n * FR-1 flow: try to yield an idle work agent to free capacity for an advancing\n * dispatch of `role` for `issueId`. Pauses the victim with scheduler attribution\n * and stops its session (async). The caller re-attempts its reservation after a\n * true outcome; on FR-6c (retry still fails) the caller is responsible for\n * resuming the victim immediately — see the wire-site pattern.\n *\n * No-op (returns `{ yielded: false }`) when preemption is disabled, the\n * `max_yielded` cap is reached, or no eligible victim exists.\n */\nexport async function yieldWorkAgentFor(role: AdvancingRole, issueId: string): Promise<YieldOutcome> {\n const concurrency = loadCloisterConfigSync().concurrency;\n if (concurrency?.preemption !== true) return { yielded: false, reason: 'preemption disabled' };\n\n const maxYielded = concurrency.max_yielded ?? 3;\n const cooldownSecs = concurrency.yield_cooldown_secs ?? 600;\n\n const alreadyYielded = countYielded();\n if (alreadyYielded >= maxYielded) {\n return { yielded: false, reason: `max_yielded reached (${alreadyYielded}/${maxYielded})` };\n }\n\n const nowMs = Date.now();\n const candidates = await buildCandidates();\n const victim = selectYieldVictim(candidates, nowMs, cooldownSecs);\n if (!victim) return { yielded: false, reason: 'no eligible idle work agent to yield' };\n\n const reason = `yield: making room for ${role} of ${issueId}`;\n if (!setAgentYieldedSync(victim.id, reason)) {\n return { yielded: false, reason: `victim ${victim.id} state vanished before yield` };\n }\n await Effect.runPromise(stopAgent(victim.id));\n\n const idleMinutes = victim.lastActivityMs !== null ? Math.round((nowMs - victim.lastActivityMs) / 60000) : null;\n const idleDesc = idleMinutes !== null ? ` (idle ${idleMinutes}m)` : '';\n const message = `Yielded ${victim.id}${idleDesc} to run ${role} for ${issueId}`;\n logDeaconEventSync(`[preemption] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message, issueId });\n\n return { yielded: true, victimId: victim.id, reason: message };\n}\n\n/**\n * Dispatch-site entry point: a blocked advancing dispatch of `role` for\n * `issueId` tries to yield an idle work agent and re-confirm capacity. Returns\n * true iff, after the yield, `tryReserveAdvancingSlot()` succeeds — i.e. a slot\n * is now reserved for the caller to dispatch. On failure it resumes the victim\n * immediately (FR-6c) and returns false, so the caller defers exactly as before.\n *\n * Two capacity paths after a yield:\n * - count-gated: `stopAgent` already awaited `killSession`, so the freed slot\n * is visible to `countRunningAgents()` on the immediate retry;\n * - memory-gated: let the freed RSS settle, refresh the cached memory verdict\n * (`assessMemoryPressure` publishes it), then retry once.\n */\nexport async function tryYieldForAdvancingDispatch(role: AdvancingRole, issueId: string): Promise<boolean> {\n const outcome = await yieldWorkAgentFor(role, issueId);\n if (!outcome.yielded) return false;\n\n // Count-gated: the killed session already dropped the running count.\n if (tryReserveAdvancingSlot()) return true;\n\n // Memory-gated: settle freed RSS, refresh the cached verdict, retry once.\n await new Promise((r) => setTimeout(r, RSS_SETTLE_MS));\n await assessMemoryPressure();\n if (tryReserveAdvancingSlot()) return true;\n\n // FR-6c: the yield freed no usable capacity — put the victim back immediately.\n if (outcome.victimId) await resumeYieldedVictim(outcome.victimId);\n return false;\n}\n\n/**\n * Immediately resume a victim after a failed reservation retry (FR-6c): the\n * yield freed capacity but the advancing dispatch still could not reserve it, so\n * put the work agent back rather than leaving it stranded.\n */\nexport async function resumeYieldedVictim(agentId: string): Promise<void> {\n clearYieldForResumeSync(agentId);\n const result = await resumeAgent(agentId);\n const suffix = result.success ? 'resumed' : `resume failed: ${result.error ?? 'unknown'}`;\n logDeaconEventSync(`[preemption] Yield retry still blocked — ${agentId} ${suffix}`);\n}\n\n/**\n * FR-4: resume yielded work agents oldest-first, up to `maxToResume`, while the\n * memory gate allows. Clears the pause + yield attribution and stamps the\n * re-yield cooldown before each resume. Returns the ids actually resumed.\n *\n * Deliberately NOT gated on `preemption` — a previously-yielded agent must be\n * resumable even if the operator has since turned preemption off, so it is never\n * stranded paused.\n */\nexport async function resumeYieldedAgents(maxToResume: number): Promise<string[]> {\n if (maxToResume <= 0) return [];\n\n const yielded = listAgentStates({ status: 'stopped', role: 'work' })\n .filter((s) => s.yieldedByScheduler === true)\n .sort((a, b) => (parseMs(a.yieldedAt) ?? 0) - (parseMs(b.yieldedAt) ?? 0));\n if (yielded.length === 0) return [];\n\n const resumed: string[] = [];\n for (const agent of yielded) {\n if (resumed.length >= maxToResume) break;\n\n const memVerdict = await assessMemoryPressure();\n if (memVerdict.band !== 'ok') {\n logDeaconEventSync(\n `[preemption] resumeYieldedAgents: memory gate (${memVerdict.band}), availMB=${Math.round(memVerdict.availableBytes / 1048576)}`\n + `${memVerdict.loadPerCore == null ? '' : `, load/core=${memVerdict.loadPerCore.toFixed(2)}`}; deferring remaining yielded agents`,\n );\n break;\n }\n\n // Let the previous resume's RSS register before the next memory check.\n if (resumed.length > 0) {\n await new Promise((r) => setTimeout(r, RSS_SETTLE_MS));\n }\n\n clearYieldForResumeSync(agent.id);\n const result = await resumeAgent(agent.id);\n if (result.success) {\n resumed.push(agent.id);\n const message = `Resumed yielded ${agent.id} for ${agent.issueId} — capacity returned`;\n logDeaconEventSync(`[preemption] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message, issueId: agent.issueId });\n } else {\n // The pause is already cleared, so the normal auto-resume path will retry\n // this agent on a later patrol like any other stopped work agent.\n logDeaconEventSync(`[preemption] resumeYieldedAgents: resume failed for ${agent.id}: ${result.error ?? 'unknown'}`);\n }\n }\n return resumed;\n}\n","import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { getReviewStatusSync, loadReviewStatuses, setReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { isIssueClosed } from './issue-closed.js';\nimport {\n describeRunningAgents,\n releaseAdvancingSlot,\n tryReserveAdvancingSlot,\n} from './concurrency.js';\nimport { ciRetryMap } from './deacon-merge.js';\nimport { tryYieldForAdvancingDispatch } from './preemption.js';\n\nconst BLOCKED_REVIEW_MISSING_ANCHOR_LOG_INTERVAL_MS = 60 * 60 * 1000;\nconst blockedReviewMissingAnchorLogs = new Map<string, number>();\nconst blockedReviewDriftObservations = new Map<\n string,\n { reviewedAnchor: string; currentAnchor: string }\n>();\n// PAN-3254: the current anchor recorded at each issue's most recent reset. A\n// drift verdict against the SAME anchor a second time means the workspace has\n// not moved since we already reset for it — a repeating producer artifact,\n// not a new push (MIN-901: 425 resets, one distinct current anchor).\nconst lastResetAnchors = new Map<string, string>();\nconst resetLoopEscalated = new Set<string>();\n\n/**\n * Detect issues where the agent pushed new commits after a review verdict.\n *\n * Passed reviews are invalidated immediately when the reviewed tree changes.\n * Blocked reviews use the same drift evaluator, but require two consecutive\n * patrols at the same new HEAD before re-dispatching so a work agent that is\n * still pushing per-item commits does not start review mid-rework.\n */\nexport async function checkPostReviewCommits(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const statuses = loadReviewStatuses();\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n\n for (const [issueId, status] of Object.entries(statuses)) {\n const isBlocked = status.reviewStatus === 'blocked';\n if (!isBlocked) blockedReviewDriftObservations.delete(issueId);\n\n if (status.mergeStatus === 'merged') {\n blockedReviewDriftObservations.delete(issueId);\n lastResetAnchors.delete(issueId);\n resetLoopEscalated.delete(issueId);\n continue;\n }\n if (!isBlocked && status.reviewStatus !== 'passed' && !status.readyForMerge) continue;\n\n const reviewedAnchor = status.reviewedAtCommit;\n if (!reviewedAnchor) {\n blockedReviewDriftObservations.delete(issueId);\n if (isBlocked) {\n const now = Date.now();\n const lastLoggedAt = blockedReviewMissingAnchorLogs.get(issueId) ?? 0;\n if (now - lastLoggedAt >= BLOCKED_REVIEW_MISSING_ANCHOR_LOG_INTERVAL_MS) {\n blockedReviewMissingAnchorLogs.set(issueId, now);\n logDeaconEventSync(\n `checkPostReviewCommits: ${issueId} blocked without anchor — cannot detect drift`,\n );\n }\n }\n continue;\n }\n if (await isIssueClosed(issueId)) {\n blockedReviewDriftObservations.delete(issueId);\n console.log(`[deacon] ${issueId}: skipping review re-dispatch — issue is closed`);\n continue;\n }\n\n const project = resolveProjectFromIssueSync(issueId);\n if (!project) continue;\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`,\n );\n if (!existsSync(workspacePath)) continue;\n\n const { formatAnchorShort, rehydrateHeadAnchor } = await import('../git-utils.js');\n const { evaluateWorkspaceAnchorDrift } = await import('../workspace-anchor-drift.js');\n const verdict = await evaluateWorkspaceAnchorDrift(\n issueId,\n workspacePath,\n rehydrateHeadAnchor(reviewedAnchor),\n );\n if (verdict.kind === 'unreadable' || verdict.kind === 'current') {\n blockedReviewDriftObservations.delete(issueId);\n continue;\n }\n\n if (verdict.kind === 'benign') {\n blockedReviewDriftObservations.delete(issueId);\n setReviewStatusSync(issueId, { reviewedAtCommit: verdict.currentAnchor });\n console.log(`[deacon] Benign post-review HEAD move for ${issueId}: ${formatAnchorShort(reviewedAnchor)} → ${formatAnchorShort(verdict.currentAnchor)} — review/test preserved`);\n continue;\n }\n\n const currentHead = verdict.currentAnchor;\n\n // PAN-3254: bound repeat resets. If this exact current anchor already\n // triggered a reset, the branch has not moved since — re-resetting\n // produces an identical \"unchanged\" review cycle every patrol (~100 s)\n // and never converges. Escalate once instead of looping silently.\n if (lastResetAnchors.get(issueId) === currentHead) {\n if (!resetLoopEscalated.has(issueId)) {\n resetLoopEscalated.add(issueId);\n const message = `${issueId} — review reset suppressed: drift against ${formatAnchorShort(currentHead)} already caused a reset and the workspace has not moved since. This indicates an anchor-producer disagreement, not new commits; review will not re-dispatch until the workspace head actually changes.`;\n logDeaconEventSync(`checkPostReviewCommits: ${message}`);\n const { recordDeadEndNeedsYou } = await import('./dead-end-trip.js');\n await recordDeadEndNeedsYou(issueId, 'review-reset-loop', currentHead, message);\n }\n continue;\n }\n\n if (isBlocked) {\n const priorObservation = blockedReviewDriftObservations.get(issueId);\n if (\n !priorObservation\n || priorObservation.reviewedAnchor !== reviewedAnchor\n || priorObservation.currentAnchor !== currentHead\n ) {\n blockedReviewDriftObservations.set(issueId, {\n reviewedAnchor,\n currentAnchor: currentHead,\n });\n continue;\n }\n blockedReviewDriftObservations.delete(issueId);\n }\n\n console.log(\n `[deacon] Post-review commit detected for ${issueId}: ` +\n `was ${formatAnchorShort(reviewedAnchor)}, now ${formatAnchorShort(currentHead)} — resetting review`,\n );\n lastResetAnchors.set(issueId, currentHead);\n resetLoopEscalated.delete(issueId);\n if (isBlocked) {\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n readyForMerge: false,\n reviewedAtCommit: undefined,\n reviewRetryCount: 0,\n recoveryStartedAt: undefined,\n });\n } else {\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n readyForMerge: false,\n reviewedAtCommit: undefined,\n reviewNotes: undefined,\n testNotes: undefined,\n // Reset merge retry counter so checkFailedMergeRetry can retry again after\n // the work agent pushes a fix (e.g. to address a CI check failure).\n mergeRetryCount: 0,\n // PAN-794: new commits open a fresh recovery cycle — stale infra\n // failures from the previous cycle must not poison the breaker budget.\n reviewRetryCount: 0,\n recoveryStartedAt: undefined,\n });\n }\n // Also clear the CI transient retry counter so the next merge attempt\n // starts fresh. Without this, ciRetryMap retains count=6 from the previous\n // CI failure cycle, permanently blocking transient retries for this issue.\n ciRetryMap.delete(issueId);\n if (!isBlocked) {\n actions.push(`Reset review for ${issueId}: new commits after review passed (${formatAnchorShort(reviewedAnchor)} → ${formatAnchorShort(currentHead)})`);\n }\n\n // Redispatch a fresh review convoy. Re-read status to guard against races\n // with other dispatch paths (HTTP request-review, manual CLI) that may have\n // already picked up the work between the reset above and now.\n const freshStatus = getReviewStatusSync(issueId);\n // PAN-2507: also a blocked advancing (review) dispatch — try to yield an\n // idle work agent before deferring. (This 7th site was not in the PRD's\n // six-site enumeration but is the same `!tryReserveAdvancingSlot()` shape.)\n if (freshStatus?.reviewStatus === 'pending' && !tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n // PAN-1665: at the ceiling — status is already reset to pending above, so\n // the orphan-review path will re-dispatch on a later patrol once a slot frees.\n actions.push(`Deferred post-review re-dispatch for ${issueId} — advancing-role concurrency ceiling reached`);\n logDeaconEventSync(`checkPostReviewCommits: deferred review for ${issueId} — advancing ceiling reached (PAN-1665) — ${describeRunningAgents()}`);\n } else if (freshStatus?.reviewStatus === 'pending') {\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n const branch = `feature/${issueId.toLowerCase()}`;\n const dispatchResult = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePath,\n branch,\n force: true,\n }));\n if (dispatchResult.gated) {\n releaseAdvancingSlot();\n actions.push(`Deferred post-review re-dispatch for ${issueId} — ${dispatchResult.message}`);\n console.log(`[deacon] Deferred post-review re-dispatch for ${issueId}: ${dispatchResult.message}`);\n } else if (dispatchResult.success) {\n const action = isBlocked\n ? `Re-dispatched review for ${issueId}: rework commit after BLOCKED verdict (${formatAnchorShort(reviewedAnchor)} → ${formatAnchorShort(currentHead)})`\n : `Re-dispatched review for ${issueId}`;\n actions.push(action);\n console.log(`[deacon] ${action}`);\n } else {\n actions.push(`Failed to re-dispatch review for ${issueId}: ${dispatchResult.error || dispatchResult.message}`);\n console.error(`[deacon] Failed to re-dispatch review for ${issueId}:`, dispatchResult.error || dispatchResult.message);\n }\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error in checkPostReviewCommits:', msg);\n }\n\n return actions;\n}\n","/**\n * PAN-3002: supervisor-delivered agents (pty-supervisor, deliveryMethod:\n * supervisor) keep working after their tmux session dies — the worker holds\n * the pty and detaches. A tmux-only liveness check then declares a healthy\n * agent dead and marks it stopped, splitting state from reality: MIN-882 ran\n * 36h untracked ($166, 35MB transcript) while the pipeline showed \"Planned\n * idle\", and PAN-2997 was killed 41s after a healthy resume.\n *\n * A live pty-supervisor process carrying `--name <agentId>` is positive proof\n * of life regardless of tmux. The deacon's liveness arbiter consults this\n * before declaring an agent orphaned.\n */\nimport { execFile, execFileSync } from 'node:child_process';\nimport { promisify } from 'node:util';\n\ntype Pgrep = (pattern: string) => boolean;\ntype AsyncPgrep = (pattern: string) => Promise<boolean>;\n\nconst execFileAsync = promisify(execFile);\n\nconst defaultPgrep: Pgrep = (pattern) => {\n try {\n execFileSync('pgrep', ['-f', pattern], { stdio: ['ignore', 'ignore', 'ignore'] });\n return true;\n } catch {\n return false;\n }\n};\n\nconst defaultAsyncPgrep: AsyncPgrep = async (pattern) => {\n try {\n await execFileAsync('pgrep', ['-f', pattern], { encoding: 'utf8' });\n return true;\n } catch (error: unknown) {\n if (typeof error === 'object' && error !== null && 'code' in error && error.code === 1) return false;\n throw error;\n }\n};\n\n/**\n * True when a pty-supervisor process for this agent is alive. `--name` is\n * boundary-anchored so `agent-min-882` does not match `agent-min-882-review`.\n * Agent ids are `[a-z0-9-]` by construction, so the pattern needs no escaping.\n */\nexport function supervisorProcessAliveSync(agentId: string, pgrep: Pgrep = defaultPgrep): boolean {\n if (!agentId) return false;\n return pgrep(`pty-supervisor\\\\.js.*--name ${agentId}(\\\\s|$)`);\n}\n\n/** Async equivalent for server-reachable liveness checks. */\nexport async function supervisorProcessAlive(agentId: string, pgrep: AsyncPgrep = defaultAsyncPgrep): Promise<boolean> {\n if (!agentId) return false;\n return pgrep(`pty-supervisor\\\\.js.*--name ${agentId}(\\\\s|$)`);\n}\n","import { join } from 'node:path';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\n\nimport { loadReviewStatuses, getReviewStatusSync, setReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { listProjectsAsync, resolveProjectFromIssueSync } from '../projects.js';\nimport { getAgentState } from '../agents/agent-state.js';\nimport { hasAgentRuntimeInSubtree } from '../agents/runtime-command.js';\nimport { supervisorProcessAlive } from '../agents/supervisor-liveness.js';\nimport { listPaneValues } from '../tmux.js';\nimport { messageAgent, type MessageDeliveryOutcome } from '../agents/messaging.js';\nimport { writeFeedbackFile } from './feedback-writer.js';\nimport { surfaceIssueFeedbackNeedsYou } from './feedback-target.js';\nimport type { StrikeLandingAttempt } from '../strike-landing.js';\nimport { ensureInternalTokenSync, INTERNAL_TOKEN_HEADER } from '../internal-token.js';\nconst execFileAsync = promisify(execFile);\nexport interface StrikeMergeRequest {\n kind: 'strike'; markerHead: string; workspacePath: string; branchName: string; recoveryTarget: string;\n}\n\nexport interface StrikeMergeResult { success: boolean; mergeStatus?: string; error?: string; transport?: boolean }\ntype StrikeMergeTrigger = (issueId: string, request: StrikeMergeRequest) => Promise<StrikeMergeResult>;\n\nfunction internalDashboardUrl(): string {\n const port = Number.parseInt(process.env.API_PORT ?? process.env.PORT ?? '3011', 10);\n return process.env.OVERDECK_INTERNAL_DASHBOARD_URL ?? `http://127.0.0.1:${port}`;\n}\n\nexport async function requestStrikeMerge(\n issueId: string,\n request: StrikeMergeRequest,\n options: { dashboardUrl?: string; token?: string; fetchImpl?: typeof fetch } = {},\n): Promise<StrikeMergeResult> {\n const dashboardUrl = options.dashboardUrl ?? internalDashboardUrl();\n const token = options.token ?? ensureInternalTokenSync();\n const fetchImpl = options.fetchImpl ?? fetch;\n try {\n const response = await fetchImpl(new URL(`/api/internal/strikes/${issueId}/merge`, dashboardUrl), {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n origin: dashboardUrl,\n [INTERNAL_TOKEN_HEADER]: token,\n },\n body: JSON.stringify(request),\n });\n const body = await response.json() as StrikeMergeResult;\n return typeof body.success === 'boolean'\n ? body\n : { success: false, error: `Strike merge endpoint returned HTTP ${response.status} without a structured result` };\n } catch (error) {\n return {\n success: false,\n transport: true,\n error: `Strike merge request failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n\nexport interface StrikeLandingDeps {\n loadStatuses: () => Record<string, ReviewStatus>;\n getStatus: (issueId: string) => ReviewStatus | null;\n setStatus: typeof setReviewStatusSync;\n resolveProject: typeof resolveProjectFromIssueSync;\n mergeIssue: StrikeMergeTrigger;\n getMainHead: (projectPath: string) => Promise<string>;\n deliverRecovery: (agentId: string, message: string, dedupKey: string) => Promise<MessageDeliveryOutcome>;\n writeFeedback: (issueId: string, workspacePath: string, markdownBody: string) => Promise<boolean>;\n needsYou: (issueId: string, reason: string, details: Record<string, unknown>) => Promise<void>;\n now: () => string;\n schedule: (key: string, work: () => Promise<void>) => void;\n isScheduled: (key: string) => boolean;\n isPersistentlyOwned: (issueId: string) => boolean;\n listProjects: typeof listProjectsAsync;\n git: (args: string[], cwd: string) => Promise<string>;\n isStrikeAgentAlive: (agentId: string) => Promise<boolean>;\n}\n\nexport class StrikeLandingSupervisor {\n private readonly pending: Array<{ key: string; work: () => Promise<void> }> = [];\n private readonly owned = new Set<string>();\n private active = 0;\n constructor(private readonly concurrency = 2) {}\n has(key: string): boolean { return this.owned.has(key); }\n enqueue(key: string, work: () => Promise<void>): void {\n if (this.owned.has(key)) return;\n this.owned.add(key); this.pending.push({ key, work }); this.drain();\n }\n private drain(): void {\n while (this.active < this.concurrency && this.pending.length > 0) {\n const { key, work } = this.pending.shift()!;\n this.active += 1;\n void work().catch(error => console.error('[strike-landing] supervised work failed:', error)).finally(() => { this.active -= 1; this.owned.delete(key); this.drain(); });\n }\n }\n}\nconst strikeLandingSupervisor = new StrikeLandingSupervisor();\n\n/**\n * A keep-alive tmux shell survives after its harness exits, while a supervisor\n * can keep a harness alive after tmux disappears. Both probes must report no\n * runtime before the Deacon can safely take over a dead strike's branch.\n */\nasync function defaultIsStrikeAgentAlive(agentId: string): Promise<boolean> {\n try {\n const state = await Effect.runPromise(getAgentState(agentId));\n if (!state) return true;\n\n const [supervisorAlive, panePids] = await Promise.all([\n supervisorProcessAlive(agentId),\n Effect.runPromise(listPaneValues(agentId, '#{pane_pid}')),\n ]);\n if (supervisorAlive) return true;\n\n return (await Promise.all(\n panePids.map((panePid) => hasAgentRuntimeInSubtree(panePid, state.harness ?? 'claude-code')),\n )).some(Boolean);\n } catch {\n // A failed liveness probe is not proof that the agent stopped. Fail closed\n // rather than racing an agent whose process tree could not be inspected.\n return true;\n }\n}\n\nfunction defaultDeps(): StrikeLandingDeps {\n return {\n loadStatuses: loadReviewStatuses,\n getStatus: getReviewStatusSync,\n setStatus: setReviewStatusSync,\n resolveProject: resolveProjectFromIssueSync,\n mergeIssue: requestStrikeMerge,\n getMainHead: async (projectPath) => (await execFileAsync('git', ['rev-parse', 'origin/main'], { cwd: projectPath, encoding: 'utf8' })).stdout.trim(),\n // A recovery must arrive through the live delivery door. A keyed message\n // bypasses the monitor mail tier, which only becomes visible if the idle\n // session takes another turn, and makes a repeated patrol safe to retry.\n deliverRecovery: (agentId, message, dedupKey) => messageAgent(agentId, message, 'deacon-strike-landing', { owesRework: true, dedupKey }),\n writeFeedback: async (issueId, workspacePath, markdownBody) => (await Effect.runPromise(writeFeedbackFile({ issueId, workspacePath, specialist: 'merge-agent', outcome: 'needs-you', summary: 'Strike landing needs operator attention', markdownBody }))).success,\n needsYou: surfaceIssueFeedbackNeedsYou,\n now: () => new Date().toISOString(),\n schedule: (key, work) => strikeLandingSupervisor.enqueue(key, work),\n isScheduled: key => strikeLandingSupervisor.has(key),\n isPersistentlyOwned: () => false,\n listProjects: listProjectsAsync,\n git: async (args, cwd) => (await execFileAsync('git', args, { cwd, encoding: 'utf8' })).stdout.trim(),\n isStrikeAgentAlive: defaultIsStrikeAgentAlive,\n };\n}\n\nconst TRANSPORT_BACKOFF_BASE_MS = 60_000;\nconst TRANSPORT_BACKOFF_CAP_MS = 1_800_000;\nconst MAX_TRANSPORT_RETRIES = 10;\nconst NON_ACTIONABLE = /permission|merge guard|configured project|integration|infrastructure|unavailable|not registered|workspace does not exist|fetch failed|ECONNREFUSED|ECONNRESET|socket hang up|ETIMEDOUT|EAI_AGAIN/i;\nfunction attemptHistory(attempts: StrikeLandingAttempt[]): string {\n return attempts.map((attempt, index) => `${index + 1}. strike ${attempt.strikeHead}; main ${attempt.mainHead}; ${attempt.outcome}: ${attempt.detail}`).join('\\n');\n}\n\nasync function handleFailure(issueId: string, head: string, detail: string, projectPath: string, workspacePath: string, status: ReviewStatus, deps: StrikeLandingDeps): Promise<string> {\n let mainHead = 'unknown';\n try { mainHead = await deps.getMainHead(projectPath); } catch (error) { detail += `; main HEAD unavailable: ${error instanceof Error ? error.message : String(error)}`; }\n const attempts = [...(status.strikeLandingAttempts ?? []), { timestamp: deps.now(), strikeHead: head, mainHead, outcome: 'failed', detail }];\n const recoveryCount = (status.strikeRecoveryCount ?? 0) + 1;\n const recoveryMessage = `Strike landing failed for ${issueId} at ${head}.\\n\\nCurrent main: ${mainHead}\\nFailure: ${detail}\\n\\nRun pan sync-main ${issueId}, resolve every conflict, rerun the configured gates, push only strike/${issueId.toLowerCase()}, then run pan strike-ready ${issueId}. A fresh pushed HEAD is required before another landing attempt.`;\n if (!NON_ACTIONABLE.test(detail) && recoveryCount < 3) {\n try {\n const outcome = await deps.deliverRecovery(\n `strike-${issueId.toLowerCase()}`,\n recoveryMessage,\n `strike-landing:${issueId}:${head}:${recoveryCount}`,\n );\n if (outcome.delivered) {\n deps.setStatus(issueId, { strikeLandingState: 'recovering', strikeRecoveryCount: recoveryCount, strikeLandingAttempts: attempts, mergeNotes: detail });\n return `[strike-landing] ${issueId} at ${head} recovering (${recoveryCount}/3)`;\n }\n detail += `; recovery not delivered: ${outcome.reason ?? 'queued to mail only'}`;\n attempts[attempts.length - 1] = { ...attempts[attempts.length - 1], detail };\n } catch (error) { detail += `; recovery delivery failed: ${error instanceof Error ? error.message : String(error)}`; attempts[attempts.length - 1] = { ...attempts[attempts.length - 1], detail }; }\n }\n const reason = `Strike landing for ${issueId} needs operator attention after ${recoveryCount} cycle(s).\\n${attemptHistory(attempts)}`;\n deps.setStatus(issueId, { strikeLandingState: 'needs_you', strikeRecoveryCount: recoveryCount, strikeLandingAttempts: attempts, mergeNotes: detail });\n await deps.writeFeedback(issueId, workspacePath, `## Strike landing needs operator attention\\n\\n${reason}`);\n await deps.needsYou(issueId, reason, { attempts });\n return `[strike-landing] ${issueId} at ${head} needs-you`;\n}\n\nasync function handleTransportFailure(issueId: string, head: string, detail: string, status: ReviewStatus, deps: StrikeLandingDeps): Promise<string> {\n const timestamp = deps.now();\n const project = deps.resolveProject(issueId);\n const projectPath = project?.projectPath ?? '';\n const workspacePath = project ? join(projectPath, 'workspaces', `feature-${issueId.toLowerCase()}-strike`) : '';\n let mainHead = 'unknown';\n try { if (projectPath) mainHead = await deps.getMainHead(projectPath); }\n catch (error) { detail += `; main HEAD unavailable: ${error instanceof Error ? error.message : String(error)}`; }\n const attempts: StrikeLandingAttempt[] = [\n ...(status.strikeLandingAttempts ?? []),\n { timestamp, strikeHead: head, mainHead, outcome: 'transport-failed', detail },\n ];\n const retryCount = (status.strikeTransportRetryCount ?? 0) + 1;\n\n if (retryCount < MAX_TRANSPORT_RETRIES) {\n const delayMs = Math.min(TRANSPORT_BACKOFF_CAP_MS, TRANSPORT_BACKOFF_BASE_MS * (2 ** (retryCount - 1)));\n const nextAttemptAt = new Date(Date.parse(timestamp) + delayMs).toISOString();\n deps.setStatus(issueId, {\n strikeLandingState: 'ready',\n strikeReadyHead: head,\n strikeTransportRetryCount: retryCount,\n strikeNextAttemptAt: nextAttemptAt,\n strikeLandingAttempts: attempts,\n mergeNotes: detail,\n });\n return `[strike-landing] ${issueId} at ${head} transport retry ${retryCount}/${MAX_TRANSPORT_RETRIES} after ${nextAttemptAt}`;\n }\n\n const reason = `Strike landing for ${issueId} needs operator attention after ${retryCount} transport attempt(s).\\n${attemptHistory(attempts)}`;\n deps.setStatus(issueId, {\n strikeLandingState: 'needs_you',\n strikeTransportRetryCount: retryCount,\n strikeNextAttemptAt: undefined,\n strikeLandingAttempts: attempts,\n mergeNotes: detail,\n });\n await deps.writeFeedback(issueId, workspacePath, `## Strike landing needs operator attention\\n\\n${reason}`);\n await deps.needsYou(issueId, reason, { attempts });\n return `[strike-landing] ${issueId} at ${head} needs-you`;\n}\n\ninterface StrandedStrikeCandidate {\n issueId: string;\n branchName: string;\n workspacePath: string;\n head: string;\n}\n\nfunction parseStrikeWorktrees(porcelain: string): Array<{ branchName: string; workspacePath: string }> {\n const worktrees: Array<{ branchName: string; workspacePath: string }> = [];\n let workspacePath: string | undefined;\n for (const line of porcelain.split('\\n')) {\n if (line.startsWith('worktree ')) {\n workspacePath = line.slice('worktree '.length).trim();\n } else if (workspacePath && line.startsWith('branch refs/heads/strike/')) {\n worktrees.push({\n workspacePath,\n branchName: line.slice('branch refs/heads/'.length).trim(),\n });\n } else if (line.length === 0) {\n workspacePath = undefined;\n }\n }\n return worktrees;\n}\n\nasync function findStrandedStrikeCandidates(deps: StrikeLandingDeps): Promise<StrandedStrikeCandidate[]> {\n const candidates: StrandedStrikeCandidate[] = [];\n let projects: Awaited<ReturnType<typeof deps.listProjects>>;\n try {\n projects = await deps.listProjects();\n } catch {\n return candidates;\n }\n\n for (const { config } of projects) {\n let worktrees: Array<{ branchName: string; workspacePath: string }>;\n try {\n worktrees = parseStrikeWorktrees(await deps.git(['worktree', 'list', '--porcelain'], config.path));\n } catch {\n continue;\n }\n\n for (const { branchName, workspacePath } of worktrees) {\n const issueId = branchName.slice('strike/'.length).toUpperCase();\n if (!/^[A-Z][A-Z0-9]*-\\d+$/.test(issueId)) continue;\n if (await deps.isStrikeAgentAlive(`strike-${issueId.toLowerCase()}`)) continue;\n\n try {\n const [dirty, ahead, head] = await Promise.all([\n deps.git(['status', '--porcelain'], workspacePath),\n deps.git(['rev-list', '--count', 'origin/main..HEAD'], workspacePath),\n deps.git(['rev-parse', 'HEAD'], workspacePath),\n ]);\n if (dirty || Number(ahead) <= 0 || !/^[0-9a-f]{40}$/i.test(head)) continue;\n candidates.push({ issueId, branchName, workspacePath, head });\n } catch {\n // Cannot prove that this worktree is clean and ahead of main. Leave it\n // untouched for a later patrol rather than guessing at agent intent.\n }\n }\n }\n return candidates;\n}\n\n/**\n * Push and mark ready a completed strike whose harness has exited before it\n * could finish the commit → push → strike-ready handoff. The status update is\n * durable issue evidence and the returned action is recorded in the ship log.\n */\nexport async function salvageStrandedStrikeBranches(deps: StrikeLandingDeps): Promise<string[]> {\n const actions: string[] = [];\n for (const candidate of await findStrandedStrikeCandidates(deps)) {\n const current = deps.getStatus(candidate.issueId);\n if (current?.strikeReadyHead === candidate.head) continue;\n\n // Recheck immediately before the push to narrow the liveness race with an\n // agent that may have resumed after the initial branch scan.\n if (await deps.isStrikeAgentAlive(`strike-${candidate.issueId.toLowerCase()}`)) continue;\n\n try {\n const [dirty, head] = await Promise.all([\n deps.git(['status', '--porcelain'], candidate.workspacePath),\n deps.git(['rev-parse', 'HEAD'], candidate.workspacePath),\n ]);\n if (dirty || head !== candidate.head) continue;\n\n await deps.git(['push', 'origin', candidate.branchName], candidate.workspacePath);\n deps.setStatus(candidate.issueId, {\n strikeReadyHead: candidate.head,\n strikeReadyAt: deps.now(),\n strikeLandingState: 'ready',\n strikeRecoveryCount: 0,\n strikeTransportRetryCount: undefined,\n strikeNextAttemptAt: undefined,\n strikeLandingAttempts: current?.strikeLandingAttempts ?? [],\n mergeNotes: `Automatically salvaged completed strike branch ${candidate.branchName} after its harness exited before push.`,\n });\n actions.push(`[strike-salvage] pushed ${candidate.issueId} at ${candidate.head}`);\n } catch (error) {\n console.warn(`[strike-salvage] could not push ${candidate.issueId} at ${candidate.head}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n return actions;\n}\n\nexport async function patrolStrikeLandings(overrides: Partial<StrikeLandingDeps> = {}): Promise<string[]> {\n const deps = { ...defaultDeps(), ...overrides };\n const actions = await salvageStrandedStrikeBranches(deps);\n for (const [key, candidate] of Object.entries(deps.loadStatuses())) {\n const issueId = (candidate.issueId || key).toUpperCase();\n const head = candidate.strikeReadyHead;\n if (!head || (candidate.strikeLandingState !== 'ready' && candidate.strikeLandingState !== 'landing')) continue;\n if (candidate.strikeNextAttemptAt && candidate.strikeNextAttemptAt > deps.now()) continue;\n if (candidate.deaconIgnored || candidate.stuck || candidate.mergeStatus === 'merged') continue;\n\n const current = deps.getStatus(issueId);\n if (current?.strikeReadyHead !== head || (current.strikeLandingState !== 'ready' && current.strikeLandingState !== 'landing')) continue;\n const leaseKey = `${issueId}:${head}`;\n if (current.strikeLandingState === 'landing' && deps.isScheduled(leaseKey)) continue;\n if (current.strikeLandingState === 'landing' && deps.isPersistentlyOwned(issueId)) continue;\n const claimed = current.strikeLandingState === 'ready' ? deps.setStatus(issueId, { strikeLandingState: 'landing' }) : current;\n if (claimed.strikeReadyHead !== head || claimed.strikeLandingState !== 'landing') continue;\n\n deps.schedule(leaseKey, async () => {\n try { await executeStrikeLanding(issueId, head, claimed, deps); }\n catch (error) {\n const detail = `Unexpected supervised strike landing failure: ${error instanceof Error ? error.message : String(error)}`;\n try {\n const project = deps.resolveProject(issueId);\n await handleFailure(issueId, head, detail, project?.projectPath ?? '', project ? join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}-strike`) : '', deps.getStatus(issueId) ?? claimed, deps);\n } catch (recoveryError) {\n deps.setStatus(issueId, { strikeLandingState: 'needs_you', mergeNotes: `${detail}; durable recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}` });\n }\n }\n });\n actions.push(`[strike-landing] ${candidate.strikeLandingState === 'landing' ? 'reclaimed' : 'claimed'} ${issueId} at ${head}`);\n }\n return actions;\n}\n\nasync function executeStrikeLanding(issueId: string, head: string, claimed: ReviewStatus, deps: StrikeLandingDeps): Promise<void> {\n const project = deps.resolveProject(issueId);\n if (!project) {\n await handleFailure(issueId, head, `Strike landing could not resolve a configured project for ${issueId}`, '', '', claimed, deps);\n return;\n }\n const request: StrikeMergeRequest = {\n kind: 'strike', markerHead: head,\n workspacePath: join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}-strike`),\n branchName: `strike/${issueId.toLowerCase()}`,\n recoveryTarget: `strike-${issueId.toLowerCase()}`,\n };\n const result = await deps.mergeIssue(issueId, request);\n if (result.mergeStatus === 'merged') {\n deps.setStatus(issueId, {\n strikeLandingState: 'landed',\n strikeReadyHead: undefined,\n strikeReadyAt: undefined,\n strikeTransportRetryCount: undefined,\n strikeNextAttemptAt: undefined,\n });\n } else if (result.success || result.mergeStatus === 'queued' || result.mergeStatus === 'merging' || result.mergeStatus === 'merged') {\n return;\n } else if (result.transport) {\n const current = deps.getStatus(issueId) ?? claimed;\n if (current.mergeStatus === 'merged' || current.strikeLandingState === 'landed') {\n deps.setStatus(issueId, {\n strikeLandingState: 'landed',\n strikeReadyHead: undefined,\n strikeReadyAt: undefined,\n strikeTransportRetryCount: undefined,\n strikeNextAttemptAt: undefined,\n });\n return;\n }\n await handleTransportFailure(issueId, head, result.error ?? 'Strike landing transport failed', current, deps);\n } else {\n await handleFailure(issueId, head, result.error ?? 'Strike landing failed', project.projectPath, request.workspacePath, claimed, deps);\n }\n}\n","/**\n * PAN-3512 — orphan-restore terminal verdicts go through the verdict write door.\n *\n * The two orphan-restore paths in deacon-review-status.ts rebuild a terminal\n * review verdict from history (a coordinator that exited after a completed\n * review, and a 'reviewing' row whose agent is gone). Both used to write the row\n * directly, so a restore whose evidence head disagreed with the row anchor was\n * dropped by the old guard with no event and no route to an honest next step.\n *\n * This adapter keeps the restore call sites flat: the caller keeps building the\n * update object it already built, and everything other than the verdict, its\n * notes, and the evidence anchor rides along in `extra` so stuck-clearing and\n * retry-counter fields land in the same atomic write. Dropping them would\n * deadlock the issue at passed+stuck.\n */\nimport { recordReviewVerdict, type ReviewVerdict, type VerdictOutcome } from './review-verdict-writer.js';\nimport type { HeadAnchor } from '../git-utils.js';\n\nexport async function recordOrphanRestoreVerdict(\n issueId: string,\n update: Record<string, unknown>,\n): Promise<VerdictOutcome> {\n const { reviewStatus, reviewNotes, reviewedAtCommit, ...extra } = update;\n return recordReviewVerdict(issueId, {\n verdict: reviewStatus as ReviewVerdict,\n notes: reviewNotes as string | undefined,\n ...(reviewedAtCommit ? { evidenceHead: reviewedAtCommit as HeadAnchor } : {}),\n extra,\n writer: 'orphan-restore',\n });\n}\n","import { exec } from 'child_process';\nimport { existsSync, rmSync } from 'fs';\nimport { join } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getAgentRuntimeStateSync, getAgentStateSync, listRunningAgents } from '../agents.js';\nimport { listAllAgentsSync as listAllAgents } from '../overdeck/agents.js';\nimport { markWorkspaceStuck } from '../overdeck/review-status-sync.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { getReviewStatusSync, loadReviewStatuses, setReviewStatusSync, type ReviewStatus, type ReviewStatusUpdate } from '../review-status.js';\nimport { observeActiveReviewArtifact } from './verdict-restore.js';\nimport { recordOrphanRestoreVerdict } from './orphan-restore-verdict.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { recordDeaconNudge } from './deacon-nudge-log.js';\nimport { REVIEW_SUB_ROLES } from './review-monitor.js';\nimport { getAllProjectSpecialistStatuses, getTmuxSessionName } from './specialists.js';\nimport { isPaneDead, sessionExistsSync } from '../tmux.js';\nimport { describeRunningAgents, releaseAdvancingSlot, tryReserveAdvancingSlot } from './concurrency.js';\nimport { tryYieldForAdvancingDispatch } from './preemption.js';\nimport { findWorkspacePath, inferBranchFromWorkspace } from '../lifecycle/archive-planning.js';\nimport { isIssueClosed } from './issue-closed.js';\nimport { shouldSkipDispatchAsMerged } from './merge-verification.js';\nimport { getAutoCloseOutCanonicalState } from './deacon-canonical-state.js';\nimport { evaluateReviewConvoyLiveness } from './review-convoy-liveness.js';\n\nconst execAsync = promisify(exec);\n\n// ============================================================================\n// Orphaned Review Status Detection\n// ============================================================================\n\n/**\n * Check for orphaned review/test statuses (PAN-88 follow-up)\n *\n * Detects when an issue has reviewStatus='reviewing' or testStatus='testing'\n * but the corresponding specialist isn't actually running. This can happen if:\n * - The specialist crashed mid-review\n * - The specialist was killed\n * - The wake failed but status wasn't rolled back\n *\n * Resets orphaned statuses to 'pending' so the work can be retried.\n */\n/**\n * PAN-794: Circuit-breaker threshold for consecutive parallel-review re-dispatches\n * within a recovery cycle. After this many resets of reviewing → pending by the\n * orphan sweep, the workspace is flagged stuck so it stops burning agent cycles.\n * A clean review outcome (pass/blocked/fail), new commits, or manual unstick\n * resets the counter (see review-agent.ts and checkPostReviewCommits below).\n */\nconst REVIEW_INFRA_BREAKER_THRESHOLD = 3;\n\n/**\n * Orphan-test self-heal state. A test role cannot spawn while the workspace\n * docker stack is unhealthy — `assertWorkspaceStackHealthyForSpawn` throws and\n * the orphan-test patrol re-fails the identical dispatch every cycle forever\n * (observed: PAN-1190 looped `dispatch_failed` for ~18h after its server/dev\n * containers exited). The deacon now rebuilds the stack before re-dispatch,\n * bounded by a cooldown + attempt cap so a stack that genuinely cannot be\n * rebuilt escalates to a human instead of looping `docker compose` forever.\n */\nconst testStackRebuildState: Map<string, { lastAttempt: number; attempts: number; escalated: boolean }> =\n new Map();\nconst TEST_STACK_REBUILD_COOLDOWN_MS = 15 * 60 * 1000;\nconst TEST_STACK_REBUILD_MAX_ATTEMPTS = 3;\n\nexport const stalledReviewConvoyRecoveryState: Map<string, { lastAttempt: number; attempts: number; escalated: boolean }> =\n new Map();\nconst STALLED_REVIEW_CONVOY_RECOVERY_COOLDOWN_MS = 15 * 60 * 1000;\nconst STALLED_REVIEW_CONVOY_RECOVERY_MAX_ATTEMPTS = 3;\n\nexport interface ReviewConvoyLiveness {\n anyLive: boolean;\n anyGated: boolean;\n agentIds: string[];\n}\n\nexport function reviewConvoyLiveness(issueId: string): ReviewConvoyLiveness {\n const normalizedIssueId = issueId.toLowerCase();\n const agentIds = [\n `agent-${normalizedIssueId}`,\n `agent-${normalizedIssueId}-review`,\n ...REVIEW_SUB_ROLES.map((subRole) => `agent-${normalizedIssueId}-review-${subRole}`),\n ];\n\n let anyLive = false;\n let anyGated = false;\n\n for (const agentId of agentIds) {\n const agentState = getAgentStateSync(agentId);\n if (!agentState) {\n continue;\n }\n anyLive ||= agentState.status === 'running' || agentState.status === 'starting';\n anyGated ||= agentState.paused === true || agentState.troubled === true;\n }\n\n return { anyLive, anyGated, agentIds };\n}\n\n/**\n * Outcome of an orphan-test stack-health recovery attempt:\n * - `healthy` — stack is already fine; dispatch can proceed.\n * - `rebuilt` — stack was unhealthy, a rebuild ran successfully; dispatch can proceed.\n * - `cooldown` — stack unhealthy, a rebuild was attempted recently; wait it out.\n * - `exhausted` — stack unhealthy, the rebuild cap is reached; escalate, stop retrying.\n */\ntype TestStackRecovery = 'healthy' | 'rebuilt' | 'cooldown' | 'exhausted';\n\n/**\n * Ensure the workspace docker stack for a test re-dispatch is healthy,\n * rebuilding it once per cooldown window if not. Bounded by\n * TEST_STACK_REBUILD_MAX_ATTEMPTS so an unrebuildable stack escalates cleanly.\n */\nasync function recoverUnhealthyTestStack(\n issueId: string,\n workspacePath: string,\n): Promise<TestStackRecovery> {\n const key = issueId.toUpperCase();\n const { getWorkspaceStackHealth } = await import('../workspace/stack-health.js');\n\n // PAN-1249: getWorkspaceStackHealth now returns an Effect — run it at this\n // Promise boundary. The Effect never fails (error channel: never), so\n // Effect.runPromise is safe and the result is the plain WorkspaceStackHealth.\n const health = await Effect.runPromise(getWorkspaceStackHealth(issueId, { workspacePath }));\n if (health.healthy) {\n testStackRebuildState.delete(key);\n return 'healthy';\n }\n\n const record = testStackRebuildState.get(key) ?? { lastAttempt: 0, attempts: 0, escalated: false };\n const now = Date.now();\n\n if (record.attempts >= TEST_STACK_REBUILD_MAX_ATTEMPTS) {\n if (!record.escalated) {\n record.escalated = true;\n testStackRebuildState.set(key, record);\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n issueId: key,\n message: `test-stack-rebuild-exhausted: ${key}`,\n details: `Workspace docker stack still unhealthy after ${record.attempts} rebuild attempts: ${health.reasons.join('; ')}. Manual 'pan workspace rebuild ${key}' or 'pan workspace reap' needed.`,\n });\n console.warn(\n `[deacon] Test stack for ${key} unhealthy after ${record.attempts} rebuilds — escalated; ` +\n `stop re-dispatching until a human intervenes`,\n );\n }\n return 'exhausted';\n }\n\n if (now - record.lastAttempt < TEST_STACK_REBUILD_COOLDOWN_MS) {\n return 'cooldown';\n }\n\n record.lastAttempt = now;\n record.attempts += 1;\n testStackRebuildState.set(key, record);\n console.log(\n `[deacon] Test stack for ${key} unhealthy (${health.reasons.join('; ')}) — rebuilding ` +\n `(attempt ${record.attempts}/${TEST_STACK_REBUILD_MAX_ATTEMPTS})`,\n );\n\n const { rebuildWorkspaceStack } = await import('../workspace/rebuild-stack.js');\n // PAN-1249: rebuildWorkspaceStack returns Effect<RebuildWorkspaceStackResult>\n // with error channel `never` — the Effect captures any failure into\n // result.error. Run at this Promise boundary so the deacon's recovery loop\n // keeps its current Promise-based shape.\n const result = await Effect.runPromise(\n rebuildWorkspaceStack(issueId, {\n onProgress: (m) => console.log(`[deacon] ${key} stack rebuild: ${m}`),\n }),\n );\n if (!result.success) {\n console.warn(`[deacon] Test stack rebuild failed for ${key}: ${result.error}`);\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n issueId: key,\n message: `test-stack-rebuild-failed: ${key}`,\n details: result.error ?? 'unknown error',\n });\n return 'cooldown';\n }\n\n console.log(`[deacon] Test stack for ${key} rebuilt — proceeding with test dispatch`);\n return 'rebuilt';\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PAN-1908: reactive review-status handlers (replace directory scans)\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface ReviewStatusLike {\n updatedAt?: string;\n reviewSpawnedAt?: string | number;\n reviewStatus?: string;\n verificationStatus?: string;\n testStatus?: string;\n mergeStatus?: string;\n readyForMerge?: boolean;\n prUrl?: string | null;\n stuck?: boolean;\n deaconIgnored?: boolean;\n stuckReason?: string;\n reviewRetryCount?: number;\n recoveryStartedAt?: string;\n history?: Array<{ type: string; status: string; notes?: string }>;\n reviewNotes?: string;\n lastVerifiedCommit?: string;\n reviewedAtCommit?: string;\n stuckAt?: string;\n stuckDetails?: string;\n}\n\ninterface OrphanRecoveryOptions {\n readWorkspaceHead?: (workspace: string) => Promise<string>;\n}\n\nfunction latestHistoryEntry(\n history: Array<{ type: string; status: string; notes?: string }> | undefined,\n type: 'review' | 'test',\n terminalStatuses: readonly string[],\n): { status: string; notes?: string } | null {\n if (!history || history.length === 0) return null;\n for (let i = history.length - 1; i >= 0; i--) {\n const entry = history[i];\n if (entry.type === type && terminalStatuses.includes(entry.status)) {\n return { status: entry.status, notes: entry.notes };\n }\n }\n return null;\n}\n\nfunction latestHistoryByType(\n history: Array<{ type: string; status: string; notes?: string }> | undefined,\n type: 'review' | 'test',\n): string | undefined {\n if (!history || history.length === 0) return undefined;\n for (let i = history.length - 1; i >= 0; i--) {\n if (history[i].type === type) return history[i].status;\n }\n return undefined;\n}\n\nexport function completedReviewSnapshotAfterCoordinatorExit(\n status: Pick<ReviewStatus, 'history'>,\n): ReviewStatusUpdate | null {\n const review = latestHistoryEntry(status.history, 'review', ['passed', 'failed', 'blocked']);\n if (review?.status !== 'passed') return null;\n const test = latestHistoryEntry(status.history, 'test', ['passed', 'failed', 'skipped']);\n return {\n reviewStatus: 'passed',\n reviewNotes: review.notes,\n ...(test ? { testStatus: test.status as ReviewStatus['testStatus'], testNotes: test.notes } : {}),\n reviewRetryCount: 0,\n recoveryStartedAt: undefined,\n };\n}\n\nasync function isReviewAgentActiveForIssue(\n issueId: string,\n status: ReviewStatusLike,\n): Promise<{ active: boolean; reason: string }> {\n // PAN-1048 R5: role-primitive review/test runs (agent-<id>-review, agent-<id>-test).\n try {\n const agents = await Effect.runPromise(listRunningAgents());\n return evaluateReviewConvoyLiveness(issueId, status, agents);\n } catch {\n // fall through\n }\n\n // Global specialists\n for (const type of ['review-agent'] as const) {\n const session = getTmuxSessionName(type);\n if (sessionExistsSync(session)) {\n const rState = getAgentRuntimeStateSync(session);\n if (rState?.state === 'active' && rState.currentIssue?.toUpperCase() === issueId.toUpperCase()) {\n return { active: true, reason: `active global ${type} specialist` };\n }\n }\n }\n\n // Per-project ephemeral specialists\n try {\n const projectStatuses = await getAllProjectSpecialistStatuses();\n for (const projSpec of projectStatuses) {\n if (!projSpec.isRunning || projSpec.specialistType !== 'review-agent') continue;\n const rState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n if (rState?.state === 'active' && rState.currentIssue?.toUpperCase() === issueId.toUpperCase()) {\n return { active: true, reason: `active project review specialist ${projSpec.tmuxSession}` };\n }\n }\n } catch {\n // fall through\n }\n\n return { active: false, reason: 'no active review session' };\n}\n\nasync function isTestAgentActiveForIssue(issueId: string): Promise<boolean> {\n try {\n const agents = await Effect.runPromise(listRunningAgents());\n for (const agent of agents) {\n if (agent.status === 'stopped' || agent.status === 'error') continue;\n const id = (agent.issueId ?? '').trim().toUpperCase();\n if (!id || id !== issueId.toUpperCase()) continue;\n const role = agent.role ?? (agent.id.endsWith('-review') ? 'review' : agent.id.endsWith('-test') ? 'test' : null);\n if (role === 'test') return true;\n }\n } catch {\n // fall through\n }\n\n for (const type of ['test-agent'] as const) {\n const session = getTmuxSessionName(type);\n if (sessionExistsSync(session)) {\n const rState = getAgentRuntimeStateSync(session);\n if (rState?.state === 'active' && rState.currentIssue?.toUpperCase() === issueId.toUpperCase()) {\n return true;\n }\n }\n }\n\n try {\n const projectStatuses = await getAllProjectSpecialistStatuses();\n for (const projSpec of projectStatuses) {\n if (!projSpec.isRunning || projSpec.specialistType !== 'test-agent') continue;\n const rState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n if (rState?.state === 'active' && rState.currentIssue?.toUpperCase() === issueId.toUpperCase()) {\n return true;\n }\n }\n } catch {\n // fall through\n }\n\n return false;\n}\n\n// Reads workspace evidence asynchronously for diagnostics; artifacts never write review status.\nasync function observeActiveReviewArtifactForRecovery(\n issueId: string,\n status: Pick<ReviewStatus, 'lastVerifiedCommit'>,\n caller: string,\n) {\n const state = getAgentStateSync(`agent-${issueId.toLowerCase()}-review`);\n return observeActiveReviewArtifact(issueId, {\n runId: state?.reviewRunId,\n workspacePath: state?.workspace,\n rowHead: status.lastVerifiedCommit,\n caller,\n });\n}\n\nasync function recordArtifactObservationAtBreaker(issueId: string, status: Pick<ReviewStatus, 'lastVerifiedCommit'>, actions: string[]): Promise<void> {\n try {\n const result = await observeActiveReviewArtifactForRecovery(\n issueId,\n status,\n 'review-infrastructure-breaker',\n );\n if (result.outcome !== 'no-artifact') {\n actions.push(\n `Preserved ${issueId}'s active-run ${result.artifact.verdict} artifact for diagnosis; continuing bounded review-infrastructure recovery`,\n );\n }\n } catch (err) {\n console.warn(`[deacon] Artifact observation failed for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n/**\n * PAN-1908: react to review.coordinator.died by resetting the issue to a\n * pending review state and re-dispatching the review role. No review-status\n * DB scan — operates on the single issue ID from the event.\n */\nexport async function handleReviewCoordinatorDied(\n issueId: string,\n _sessionName: string,\n _reason: string,\n): Promise<string[]> {\n const actions: string[] = [];\n const status = getReviewStatusSync(issueId);\n\n if (!status) {\n logDeaconEventSync(`handleReviewCoordinatorDied: ${issueId} skipped — no review-status row`);\n return actions;\n }\n\n if (status.stuck || status.deaconIgnored) {\n logDeaconEventSync(`handleReviewCoordinatorDied: ${issueId} skipped — stuck/deaconIgnored`);\n return actions;\n }\n\n if (await isIssueClosed(issueId)) {\n logDeaconEventSync(`handleReviewCoordinatorDied: ${issueId} skipped — issue closed`);\n return actions;\n }\n\n const completedSnapshot = completedReviewSnapshotAfterCoordinatorExit(status);\n if (completedSnapshot) {\n const restored = await recordOrphanRestoreVerdict(issueId, completedSnapshot);\n actions.push(restored.landed ? `Restored completed review for ${issueId} after coordinator exit; no re-dispatch` : `Completed-review restore for ${issueId} not recorded (${restored.reason})`);\n return actions;\n }\n\n if ((status.reviewRetryCount ?? 0) >= REVIEW_INFRA_BREAKER_THRESHOLD) {\n await recordArtifactObservationAtBreaker(issueId, status, actions);\n markWorkspaceStuck(issueId, 'review_infrastructure_failure', {\n reviewRetryCount: status.reviewRetryCount ?? 0,\n recoveryStartedAt: status.recoveryStartedAt,\n });\n actions.push(`Review recovery stopped for ${issueId} after ${status.reviewRetryCount} retries`);\n return actions;\n }\n\n // Only reset from active/reviewing states; terminal/failed states should not\n // be overwritten by a coordinator death event.\n if (status.reviewStatus !== 'reviewing' && status.reviewStatus !== 'pending') {\n logDeaconEventSync(`handleReviewCoordinatorDied: ${issueId} skipped — reviewStatus=${status.reviewStatus}`);\n return actions;\n }\n\n const nextRetry = (status.reviewRetryCount ?? 0) + 1;\n const recoveryStart = status.recoveryStartedAt ?? new Date().toISOString();\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n reviewRetryCount: nextRetry,\n recoveryStartedAt: recoveryStart,\n });\n actions.push(`Reset review for ${issueId} after coordinator died (retry ${nextRetry})`);\n\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const workspace = findWorkspacePath(resolved?.projectPath ?? '', issueLower);\n\n if (!resolved || !workspace) {\n actions.push(`Skipped review re-dispatch for ${issueId}: workspace unavailable`);\n return actions;\n }\n\n if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n actions.push(`Deferred review re-dispatch for ${issueId} — advancing-role concurrency ceiling reached`);\n return actions;\n }\n\n try {\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n const dispatchResult = await Effect.runPromise(\n spawnReviewRoleForIssue({ issueId, workspace, branch: inferBranchFromWorkspace(workspace, issueLower) }),\n );\n if (dispatchResult.gated) {\n releaseAdvancingSlot();\n actions.push(`Deferred review re-dispatch for ${issueId} — ${dispatchResult.message}`);\n } else if (dispatchResult.success) {\n actions.push(`Re-dispatched review for ${issueId} after coordinator died`);\n } else {\n actions.push(`Failed to re-dispatch review for ${issueId}: ${dispatchResult.error || dispatchResult.message}`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n actions.push(`Failed to re-dispatch review for ${issueId}: ${msg}`);\n }\n\n return actions;\n}\n\n/**\n * PAN-1908: react to work.completed by creating a review-status row if one is\n * missing. The reactive scheduler's issue-state change already dispatches the\n * review role; this handler ensures the row exists so downstream reads don't\n * fail.\n */\nexport async function handleWorkCompleted(issueId: string): Promise<string[]> {\n const actions: string[] = [];\n const status = getReviewStatusSync(issueId);\n if (status) {\n logDeaconEventSync(`handleWorkCompleted: ${issueId} already has review-status row`);\n return actions;\n }\n\n if (await isIssueClosed(issueId)) {\n logDeaconEventSync(`handleWorkCompleted: ${issueId} skipped — issue closed`);\n return actions;\n }\n\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n updatedAt: new Date().toISOString(),\n });\n actions.push(`Created missing review-status row for ${issueId} (work completed)`);\n return actions;\n}\n\n/**\n * PAN-1908: per-issue orphan reconciler for a single review-status row. Used by\n * the legacy checkOrphanedReviewStatuses safety net and by reactive handlers.\n */\nasync function reconcileReviewStatusOrphan(\n issueId: string,\n rawStatus: ReviewStatusLike,\n options: OrphanRecoveryOptions = {},\n): Promise<string[]> {\n const actions: string[] = [];\n\n const status = getReviewStatusSync(issueId) ?? rawStatus;\n\n if (status.stuck && !(status.stuckReason === 'verification_stuck' && status.reviewStatus === 'reviewing')) return actions;\n if (status.deaconIgnored) return actions;\n if (await isIssueClosed(issueId)) return actions;\n\n const hasPassedReview = latestHistoryByType(status.history, 'review') === 'passed';\n const hasPassedTest = latestHistoryByType(status.history, 'test') === 'passed';\n const latestTerminalReview = latestHistoryEntry(status.history, 'review', ['passed', 'failed', 'blocked']);\n const latestTerminalTest = latestHistoryEntry(status.history, 'test', ['passed', 'failed', 'skipped']);\n\n const reviewLiveness = await isReviewAgentActiveForIssue(issueId, status);\n const reviewAgentActive = reviewLiveness.active;\n if (status.reviewStatus === 'reviewing') {\n console.log(`[deacon] Evaluated reviewing status for ${issueId}: ${reviewLiveness.reason}`);\n }\n\n // A supervised verification worker can finish after the dashboard process\n // that requested it has died. The worker records the pass, but the dead HTTP\n // fiber never clears the prior review failure or dispatches review. Recover\n // only when the verified commit is still current; a moved HEAD needs a fresh\n // verification instead of carrying the old pass forward.\n if (\n status.verificationStatus === 'passed' &&\n status.lastVerifiedCommit &&\n !['pending', 'reviewing', 'passed'].includes(status.reviewStatus ?? '') &&\n !reviewAgentActive\n ) {\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const agentState = getAgentStateSync(`agent-${issueLower}`);\n const workspace = agentState?.workspace || (resolved ? findWorkspacePath(resolved.projectPath, issueLower) : null);\n\n if (workspace) {\n try {\n // PAN-2948: polyrepo-aware — lastVerifiedCommit is a composite\n // sub-repo snapshot, so compare against the same snapshot form.\n const currentHead = options.readWorkspaceHead\n ? await options.readWorkspaceHead(workspace)\n : await (await import('../git-utils.js')).snapshotWorkspaceHeadsPromise(issueId, workspace);\n if (currentHead === status.lastVerifiedCommit) {\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n reviewNotes: undefined,\n });\n status.reviewStatus = 'pending';\n status.reviewNotes = undefined;\n actions.push(`Recovered review continuation for ${issueId} after verification completed during restart`);\n }\n } catch {\n // A missing/unreadable workspace cannot prove the pass is current.\n }\n }\n }\n\n // Orphaned reviewing status\n if (status.reviewStatus === 'reviewing' && !reviewAgentActive) {\n if (latestTerminalReview && latestTerminalReview.status === 'passed') {\n const reviewUpdate: Record<string, unknown> = {\n reviewStatus: latestTerminalReview.status,\n reviewNotes: latestTerminalReview.notes,\n };\n try {\n const project = resolveProjectFromIssueSync(issueId);\n if (project) {\n const workspacePath = join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (existsSync(workspacePath)) {\n // PAN-2948: polyrepo-aware — snapshots sub-repo heads, not the wrapper.\n const { snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n reviewUpdate['reviewedAtCommit'] = await snapshotWorkspaceHeadsPromise(issueId, workspacePath);\n }\n }\n } catch { /* non-fatal */ }\n if (status.stuckReason === 'review_infrastructure_failure') {\n reviewUpdate['stuck'] = false;\n reviewUpdate['stuckReason'] = undefined;\n reviewUpdate['stuckAt'] = undefined;\n reviewUpdate['stuckDetails'] = undefined;\n }\n if (latestTerminalTest) {\n reviewUpdate['testStatus'] = latestTerminalTest.status;\n reviewUpdate['testNotes'] = latestTerminalTest.notes;\n }\n if (status.mergeStatus === 'failed') {\n const isCiFailure = typeof status.reviewNotes === 'string' && status.reviewNotes.includes('failing required checks');\n if (!isCiFailure) {\n reviewUpdate['mergeStatus'] = 'pending';\n }\n }\n const restored = await recordOrphanRestoreVerdict(issueId, reviewUpdate);\n actions.push(\n (restored.landed ? `Restored orphaned review snapshot for ${issueId} to ${latestTerminalReview.status}` : `Orphaned review snapshot for ${issueId} not recorded (${restored.reason})`) +\n (latestTerminalTest ? ` / test ${latestTerminalTest.status}` : ''),\n );\n return actions;\n }\n if (!hasPassedReview) {\n // RACE GUARD (PAN-1577): preserve active-run evidence for diagnosis, but\n // a workspace-writable artifact cannot complete the review by itself.\n try {\n const observation = await observeActiveReviewArtifactForRecovery(\n issueId,\n status,\n 'orphan-review-recovery',\n );\n if (observation.outcome !== 'no-artifact') {\n actions.push(\n `Preserved orphaned ${observation.artifact.verdict} evidence for ${issueId} from active run ${observation.artifact.runId}; continuing bounded recovery`,\n );\n }\n } catch (err) {\n console.warn(`[deacon] Artifact observation failed for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n const nextRetry = (status.reviewRetryCount ?? 0) + 1;\n const recoveryStart = status.recoveryStartedAt ?? new Date().toISOString();\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n reviewRetryCount: nextRetry,\n recoveryStartedAt: recoveryStart,\n });\n actions.push(\n `Reset orphaned review for ${issueId} (no review-agent active; retry ${nextRetry}/${REVIEW_INFRA_BREAKER_THRESHOLD})`,\n );\n }\n }\n\n // Re-dispatch pending reviews\n const reviewQueuedOrActive = reviewAgentActive;\n if (\n status.reviewStatus === 'pending' &&\n !reviewQueuedOrActive &&\n !hasPassedReview &&\n status.prUrl\n ) {\n if ((status.reviewRetryCount ?? 0) >= REVIEW_INFRA_BREAKER_THRESHOLD) {\n try {\n await recordArtifactObservationAtBreaker(issueId, status, actions);\n markWorkspaceStuck(issueId, 'review_infrastructure_failure', {\n reviewRetryCount: status.reviewRetryCount ?? 0,\n recoveryStartedAt: status.recoveryStartedAt,\n lastReviewNotes: status.reviewNotes,\n });\n actions.push(\n `Tripped review-infra breaker for ${issueId} after ${status.reviewRetryCount} retries — marked stuck`,\n );\n } catch (err) {\n console.error(`[deacon] Failed to mark ${issueId} stuck after breaker trip:`, err);\n }\n return actions;\n }\n\n const agentIdForCheck = `agent-${issueId.toLowerCase()}`;\n const completedProcessedFile = join(AGENTS_DIR, agentIdForCheck, 'completed.processed');\n if (!existsSync(completedProcessedFile)) return actions;\n\n const agentState = getAgentStateSync(agentIdForCheck);\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const workspace = agentState?.workspace || (resolved ? findWorkspacePath(resolved.projectPath, issueLower) : null);\n\n if (workspace && resolved && !tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n actions.push(`Deferred review re-dispatch for ${issueId} — advancing-role concurrency ceiling reached`);\n } else if (workspace && resolved) {\n try {\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n const dispatchResult = await Effect.runPromise(\n spawnReviewRoleForIssue({ issueId, workspace, branch: inferBranchFromWorkspace(workspace, issueLower) }),\n );\n if (dispatchResult.gated) {\n releaseAdvancingSlot();\n actions.push(`Deferred review re-dispatch for ${issueId} — ${dispatchResult.message}`);\n } else if (dispatchResult.success) {\n actions.push(`Re-dispatched pending review for ${issueId} (deacon-orphan-recovery)`);\n } else {\n actions.push(`Failed to re-dispatch pending review for ${issueId}: ${dispatchResult.error || dispatchResult.message}`);\n }\n } catch (err) {\n actions.push(`Failed to re-dispatch pending review for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else if (!resolved) {\n actions.push(`Skipped pending review re-dispatch for ${issueId}: no project configured`);\n } else {\n actions.push(`Skipped pending review re-dispatch for ${issueId}: workspace unavailable`);\n }\n }\n\n // Orphaned testing status\n const testAgentActive = await isTestAgentActiveForIssue(issueId);\n if (\n (status.testStatus === 'testing' || status.testStatus === 'dispatch_failed') &&\n !testAgentActive &&\n !hasPassedTest &&\n !status.readyForMerge\n ) {\n const agentId = `agent-${issueId.toLowerCase()}`;\n const agentState = getAgentStateSync(agentId);\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const workspace = agentState?.workspace || (resolved ? findWorkspacePath(resolved.projectPath, issueLower) : null);\n\n if (workspace && resolved) {\n const branch = `feature/${issueLower}`;\n const { spawnRun } = await import('../agents.js');\n const { buildTestRolePrompt } = await import('./test-agent-queue.js');\n\n const stackRecovery = await recoverUnhealthyTestStack(issueId, workspace);\n if (stackRecovery === 'cooldown' || stackRecovery === 'exhausted') {\n setReviewStatusSync(issueId, { testStatus: 'dispatch_failed' });\n actions.push(\n stackRecovery === 'exhausted'\n ? `Orphaned test for ${issueId}: workspace docker stack unhealthy, rebuild cap reached — escalated to human`\n : `Orphaned test for ${issueId}: workspace docker stack rebuilding — deferring re-dispatch`,\n );\n } else if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('test', issueId))) {\n actions.push(`Deferred test re-dispatch for ${issueId} — advancing-role concurrency ceiling reached`);\n } else {\n const mergedGuard = await shouldSkipDispatchAsMerged(issueId);\n if (mergedGuard.skip) {\n actions.push(`Orphaned test for ${issueId}: ${mergedGuard.reason}; skipping re-dispatch`);\n } else {\n try {\n const run = await spawnRun(issueId, 'test', {\n workspace,\n prompt: buildTestRolePrompt({ issueId, workspace, branch }),\n startedBy: 'deacon:orphan-test-recovery',\n });\n testStackRebuildState.delete(issueId.toUpperCase());\n setReviewStatusSync(issueId, { testStatus: 'testing' });\n actions.push(`Re-dispatched orphaned test for ${issueId} via test role ${run.id} (deacon-orphan-recovery)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('already running')) {\n setReviewStatusSync(issueId, { testStatus: 'testing' });\n actions.push(`Orphaned test for ${issueId}: test role already running`);\n } else {\n setReviewStatusSync(issueId, { testStatus: 'dispatch_failed' });\n actions.push(`Orphaned test role dispatch failed for ${issueId}: ${msg}`);\n }\n }\n }\n }\n } else {\n setReviewStatusSync(issueId, { testStatus: 'pending' });\n actions.push(\n !resolved\n ? `Reset orphaned test for ${issueId}: no project configured`\n : `Reset orphaned test for ${issueId}: workspace unavailable`,\n );\n }\n }\n\n return actions;\n}\n\nexport async function checkOrphanedReviewStatuses(options: OrphanRecoveryOptions = {}): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n // PAN-1908: the primary orphan recovery path is now reactive\n // (review.coordinator.died / work.completed events). This function is kept\n // as a thin SQLite-only safety net for dropped events.\n const statuses = loadReviewStatuses();\n for (const [issueId, status] of Object.entries(statuses)) {\n const result = await reconcileReviewStatusOrphan(issueId, status as ReviewStatusLike, options);\n actions.push(...result);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking orphaned review statuses:', msg);\n }\n\n return actions;\n}\n\nexport async function recoverStalledReviewConvoys(\n getCanonicalState: (issueId: string) => Promise<string | null> = getAutoCloseOutCanonicalState,\n): Promise<string[]> {\n const actions: string[] = [];\n\n let statuses: Record<string, ReviewStatus>;\n try {\n statuses = loadReviewStatuses();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error loading review statuses for stalled convoy recovery:', message);\n return actions;\n }\n\n for (const [issueId, rawStatus] of Object.entries(statuses)) {\n try {\n const status = getReviewStatusSync(issueId) ?? rawStatus;\n if (status.reviewStatus !== 'reviewing' && status.reviewStatus !== 'pending') continue;\n if (status.stuck || status.deaconIgnored) continue;\n\n const canonicalState = await getCanonicalState(issueId);\n if (canonicalState !== 'in_review') continue;\n\n const liveness = reviewConvoyLiveness(issueId);\n if (liveness.anyLive || liveness.anyGated) continue;\n\n const issueLower = issueId.toLowerCase();\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) {\n actions.push(`Skipped stalled review convoy recovery for ${issueId}: no project configured`);\n continue;\n }\n\n const workspace = findWorkspacePath(resolved.projectPath, issueLower);\n if (!workspace) {\n actions.push(`Skipped stalled review convoy recovery for ${issueId}: workspace unavailable`);\n continue;\n }\n\n const key = issueId.toUpperCase();\n let record = stalledReviewConvoyRecoveryState.get(key) ?? { lastAttempt: 0, attempts: 0, escalated: false };\n const now = Date.now();\n\n // If a human un-stuck the issue, grant a fresh recovery budget.\n if (record.escalated && !status.stuck) {\n stalledReviewConvoyRecoveryState.delete(key);\n record = { lastAttempt: 0, attempts: 0, escalated: false };\n }\n\n if (record.attempts >= STALLED_REVIEW_CONVOY_RECOVERY_MAX_ATTEMPTS) {\n if (!record.escalated) {\n record.escalated = true;\n stalledReviewConvoyRecoveryState.set(key, record);\n const stuckDetails = JSON.stringify({\n attempts: record.attempts,\n agentIds: liveness.agentIds,\n canonicalState,\n });\n setReviewStatusSync(issueId, {\n stuck: true,\n stuckReason: 'review_convoy_unrecoverable',\n stuckAt: new Date(now).toISOString(),\n stuckDetails,\n });\n status.stuck = true;\n status.stuckReason = 'review_convoy_unrecoverable';\n status.stuckAt = new Date(now).toISOString();\n status.stuckDetails = stuckDetails;\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n issueId: key,\n message: `stalled-review-convoy-unrecoverable: ${key}`,\n details: `Review convoy fully stopped after ${record.attempts} recovery attempts; marked stuck for human intervention. Agents: ${liveness.agentIds.join(', ')}`,\n });\n actions.push(\n `Stalled review convoy for ${issueId}: recovery cap reached after ${record.attempts} attempts — marked stuck`,\n );\n } else {\n actions.push(\n `Stalled review convoy for ${issueId}: recovery cap already escalated after ${record.attempts} attempts`,\n );\n }\n continue;\n }\n\n if (now - record.lastAttempt < STALLED_REVIEW_CONVOY_RECOVERY_COOLDOWN_MS) {\n actions.push(\n `Stalled review convoy for ${issueId}: deferring — cooldown active after attempt ${record.attempts}/${STALLED_REVIEW_CONVOY_RECOVERY_MAX_ATTEMPTS}`,\n );\n continue;\n }\n\n // PAN-1665: honor advancing-role concurrency budget before dispatch.\n if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n actions.push(\n `Stalled review convoy for ${issueId}: deferring — advancing-role concurrency ceiling reached`,\n );\n continue;\n }\n\n record.lastAttempt = now;\n record.attempts += 1;\n stalledReviewConvoyRecoveryState.set(key, record);\n\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n try {\n const result = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace,\n branch: inferBranchFromWorkspace(workspace, issueLower),\n force: true,\n }));\n if (!result.success) {\n throw new Error(result.error ?? result.message);\n }\n stalledReviewConvoyRecoveryState.delete(key);\n status.reviewStatus = 'reviewing';\n actions.push(\n `Re-dispatched stalled review convoy for ${issueId} (attempt ${record.attempts}/${STALLED_REVIEW_CONVOY_RECOVERY_MAX_ATTEMPTS})`,\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n actions.push(`Failed to re-dispatch stalled review convoy for ${issueId}: ${message}`);\n console.error(`[deacon] Failed to re-dispatch stalled review convoy for ${issueId}:`, message);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n actions.push(`Failed stalled review convoy recovery for ${issueId}: ${message}`);\n console.error(`[deacon] Failed stalled review convoy recovery for ${issueId}:`, message);\n }\n }\n\n return actions;\n}\n\n// ============================================================================\n// PAN-699: Missing review status detection\n// ============================================================================\n\n/**\n * Check for completed work agents that have no review status entry at all.\n *\n * This catches the gap where `pan done` wrote the completion marker but the\n * HTTP trigger to the dashboard never arrived (dashboard down, network failure,\n * etc). Deacon scans the agent directories and auto-triggers review dispatch.\n */\nexport async function checkMissingReviewStatuses(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n // PAN-1908: primary missing-status creation is now reactive (work.completed\n // event). This function is kept as a thin safety net that queries the\n // agents table instead of scanning directories.\n const statuses = loadReviewStatuses();\n const agents = listAllAgents();\n\n for (const agent of agents) {\n if (!agent.id.startsWith('agent-')) continue;\n const issueId = agent.id.replace('agent-', '').toUpperCase();\n if (statuses[issueId]) continue;\n\n const completedFile = join(AGENTS_DIR, agent.id, 'completed');\n const processedFile = join(AGENTS_DIR, agent.id, 'completed.processed');\n if (!existsSync(completedFile) && !existsSync(processedFile)) continue;\n\n const rowCreated = await handleWorkCompleted(issueId);\n actions.push(...rowCreated);\n\n // PAN-1496: if the issue is closed, reap the stale markers.\n try {\n if (await isIssueClosed(issueId)) {\n try { if (existsSync(completedFile)) rmSync(completedFile); } catch { /* best-effort; PAN-3357: not a dir removal */ }\n try { if (existsSync(processedFile)) rmSync(processedFile); } catch { /* best-effort; PAN-3357: not a dir removal */ }\n actions.push(`Reaped stale completion markers for CLOSED ${issueId} (no review re-dispatch)`);\n continue;\n }\n } catch (closedErr) {\n console.warn(`[deacon] checkMissingReviewStatuses closed-check failed for ${issueId}:`, closedErr);\n }\n\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const workspace = findWorkspacePath(resolved?.projectPath ?? '', issueLower);\n if (!resolved || !workspace) {\n actions.push(`Skipped missing-status review for ${issueId}: ${!resolved ? 'no project configured' : 'workspace unavailable'}`);\n continue;\n }\n\n if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n actions.push(`Deferred missing-status review for ${issueId} — advancing-role concurrency ceiling reached`);\n continue;\n }\n\n try {\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace,\n branch: inferBranchFromWorkspace(workspace, issueLower),\n }));\n recordDeaconNudge({\n patrol: 'checkMissingReviewStatuses',\n issueId,\n action: 'auto-triggered review (missing status entry)',\n reason: 'work agent has a completion marker but no review was dispatched — the reactive work→review handoff (work.completed → in_review) never created/dispatched review',\n });\n actions.push(`Auto-triggered review for ${issueId} (missing status entry)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n actions.push(`Failed to auto-trigger review for ${issueId}: ${msg}`);\n console.error(`[deacon] Failed to auto-trigger review for ${issueId}:`, msg);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking missing review statuses:', msg);\n }\n\n return actions;\n}\n","import { existsSync, readdirSync, readFileSync, statSync } from 'fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { basename, join } from 'path';\nimport { Effect } from 'effect';\nimport { getAgentRuntimeStateSync, getAgentStateSync, listRunningAgents } from '../agents.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport type { HeadAnchor } from '../git-utils.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { loadReviewStatuses, setReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { getAllProjectSpecialistStatuses, getTmuxSessionName } from './specialists.js';\nimport { isPaneDead, sessionExistsSync } from '../tmux.js';\nimport { findWorkspacePath } from '../lifecycle/archive-planning.js';\nimport { evaluateReviewConvoyLiveness, reviewTimestampMs } from './review-convoy-liveness.js';\nimport { convergeRowFromVerdictOfRecord } from './verdict-restore.js';\nimport { recordReviewVerdict } from './review-verdict-writer.js';\nimport { deliverReviewVerdictFeedback } from './review-verdict-feedback.js';\nimport { findVerdictReport, findVerdictReportAsync, parseVerdictReport } from './review-verdict-report.js';\n\n// ============================================================================\n// Stuck review detection (PAN-733)\n// ============================================================================\n\n/**\n * Detect issues stuck in `reviewing` status with no active review session.\n *\n * When `spawnReviewRoleForIssue` sets `reviewing` + `reviewSpawnedAt` but the\n * spawn crashes or the review agent exits without updating status, the issue\n * can remain in `reviewing` forever. This check uses `reviewSpawnedAt` as a\n * heartbeat: if it's >30 minutes old and no review session is active, reset\n * to `pending` so deacon can retry dispatch on the next patrol.\n *\n * Guards:\n * - Only fires when reviewStatus === 'reviewing' AND reviewSpawnedAt is set\n * - Only resets if no active review session exists for the issue\n * - 30-minute threshold avoids resetting legitimate long-running reviews\n */\nexport async function checkStuckReviewing(): Promise<string[]> {\n const actions: string[] = [];\n const REVIEW_STUCK_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes\n\n try {\n const { loadReviewStatuses, setReviewStatusSync } = await import('../review-status.js');\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n // Build set of issues with active review sessions\n const activeReviewIssues = new Set<string>();\n const projectStatuses = await getAllProjectSpecialistStatuses();\n for (const projSpec of projectStatuses) {\n if (!projSpec.isRunning) continue;\n const rState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n if (rState?.state === 'active' && rState.currentIssue && projSpec.specialistType === 'review-agent') {\n activeReviewIssues.add(rState.currentIssue.toUpperCase());\n }\n }\n // Also check global review-agent\n const globalReviewSession = getTmuxSessionName('review-agent');\n if (sessionExistsSync(globalReviewSession)) {\n const rState = getAgentRuntimeStateSync(globalReviewSession);\n if (rState?.state === 'active' && rState.currentIssue) {\n activeReviewIssues.add(rState.currentIssue.toUpperCase());\n }\n }\n // Detect active review runs through the same liveness oracle used by\n // orphan reconciliation. A row latched to `running` is not sufficient.\n let reviewAgents: Parameters<typeof evaluateReviewConvoyLiveness>[2] = [];\n try {\n reviewAgents = await Effect.runPromise(listRunningAgents());\n } catch {\n // Non-fatal: fall back to specialist-only detection\n }\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'reviewing') continue;\n if (!status.reviewSpawnedAt) continue;\n const convoy = evaluateReviewConvoyLiveness(issueId, status, reviewAgents, now);\n if (activeReviewIssues.has(issueId.toUpperCase()) || convoy.active) continue;\n\n const spawnedAt = reviewTimestampMs(status.reviewSpawnedAt);\n if (!Number.isFinite(spawnedAt)) continue;\n if (now - spawnedAt < REVIEW_STUCK_THRESHOLD_MS) continue;\n\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n reviewNotes: `Review reset by deacon: no active review session after ${Math.round((now - spawnedAt) / 60000)}min`,\n });\n const msg = `Reset stuck reviewing status for ${issueId} (no active session for ${Math.round((now - spawnedAt) / 60000)}min)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking stuck reviewing statuses:', msg);\n }\n\n return actions;\n}\n\n// ============================================================================\n// Completed-but-unsignaled review detection\n// ============================================================================\n\n/**\n * Detect review specialists that wrote synthesis.md but never called\n * `pan specialists done review`. The review role prompt instructs the agent\n * to signal completion after writing the synthesis, but agents occasionally\n * forget (idle at prompt with reports already on disk). This leaves the\n * issue stuck in `reviewing` status forever.\n *\n * Recovery: read the synthesis verdict and nudge the review agent to signal\n * completion. If the agent session is dead, we auto-complete by updating the\n * review status directly so the pipeline isn't permanently blocked.\n *\n * Guards:\n * - Only fires when reviewStatus === 'reviewing'\n * - synthesis.md must exist and be >5 min old (gives the agent time to signal)\n * - Only nudges once per review cycle (tracked by runId in the review dir)\n */\nconst unsignaledReviewNudges = new Map<string, number>();\nconst pendingVerdictNudges = new Map<string, number>();\n\ntype ReviewRunContext = {\n generatedAt?: string;\n headSha?: string;\n repos?: Array<{\n repoKey?: string;\n headSha?: string;\n }>;\n};\n\nfunction reviewAnchorFromContext(context: ReviewRunContext): string | undefined {\n if (context.repos) {\n if (context.repos.length === 0) return undefined;\n const heads: string[] = [];\n for (const repo of context.repos) {\n if (!repo.repoKey?.trim() || !repo.headSha?.trim()) return undefined;\n heads.push(`${repo.repoKey.trim()}@${repo.headSha.trim()}`);\n }\n return heads.join(' ');\n }\n return context.headSha?.trim() || undefined;\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nexport function buildReviewCompletionCommand(\n issueId: string,\n verdict: 'passed' | 'blocked' | 'failed',\n notes: string,\n runId: string,\n): string {\n let command = `pan admin specialists done review ${shellQuote(issueId)} --status ${shellQuote(verdict)}`;\n if (verdict === 'blocked' || verdict === 'failed') {\n command += ` --notes ${shellQuote(notes)}`;\n }\n return `${command} --run-id ${shellQuote(runId)}`;\n}\n\nfunction isReviewContextForActiveRun(\n context: ReviewRunContext,\n status: Pick<ReviewStatus, 'reviewSpawnedAt' | 'lastVerifiedCommit'>,\n reportMtimeMs: number,\n): boolean {\n if (!status.reviewSpawnedAt) return true;\n\n const spawnedAtMs = reviewTimestampMs(status.reviewSpawnedAt);\n if (!Number.isFinite(spawnedAtMs)) return true;\n if (reportMtimeMs < spawnedAtMs) return false;\n\n if (context.generatedAt) {\n const generatedAtMs = Date.parse(context.generatedAt);\n if (Number.isFinite(generatedAtMs) && generatedAtMs < spawnedAtMs) return false;\n }\n\n const reviewedAnchor = reviewAnchorFromContext(context);\n return !status.lastVerifiedCommit\n || !reviewedAnchor\n || reviewedAnchor === status.lastVerifiedCommit;\n}\n\nexport function isSynthesisForActiveReviewRun(\n dirPath: string,\n status: Pick<ReviewStatus, 'reviewSpawnedAt' | 'lastVerifiedCommit'>,\n synthesisMtimeMs: number,\n): boolean {\n if (!status.reviewSpawnedAt) return true;\n const contextPath = join(dirPath, 'context.json');\n if (!existsSync(contextPath)) return false;\n\n try {\n const context = JSON.parse(readFileSync(contextPath, 'utf8')) as ReviewRunContext;\n return isReviewContextForActiveRun(context, status, synthesisMtimeMs);\n } catch {\n return false;\n }\n}\n\n/**\n * Apply settled review verdicts that reached disk after their runtime status\n * was reset to pending. The live review parent gets one chance to signal the\n * verdict itself; a dead parent, or one still unsignaled after 30 minutes, is\n * reconciled directly through the review-status and feedback write doors.\n */\nexport async function reconcileUnappliedReviewVerdicts(): Promise<string[]> {\n const actions: string[] = [];\n const VERDICT_SETTLE_MS = 5 * 60 * 1000;\n const NUDGE_GRACE_MS = 30 * 60 * 1000;\n\n try {\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if ((status.reviewStatus !== 'pending' && status.reviewStatus !== 'reviewing') || !status.reviewSpawnedAt) continue;\n\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const wsPath = findWorkspacePath(resolved.projectPath, issueId.toLowerCase());\n if (!wsPath) continue;\n\n const reviewBaseDir = join(wsPath, '.pan', 'review');\n let reviewEntries: string[];\n try {\n reviewEntries = await readdir(reviewBaseDir);\n } catch {\n continue;\n }\n\n let latestDir: string | null = null;\n let latestReport: Awaited<ReturnType<typeof findVerdictReportAsync>> = null;\n let latestContext: ReviewRunContext | null = null;\n let latestMtime = 0;\n for (const entry of reviewEntries) {\n if (!entry.startsWith(`agent-${issueId.toLowerCase()}-review`)) continue;\n const dirPath = join(reviewBaseDir, entry);\n try {\n const report = await findVerdictReportAsync(dirPath);\n if (!report) continue;\n const [reportStat, contextContent] = await Promise.all([\n stat(report.path),\n readFile(join(dirPath, 'context.json'), 'utf8'),\n ]);\n const context = JSON.parse(contextContent) as ReviewRunContext;\n if (!isReviewContextForActiveRun(context, status, reportStat.mtimeMs)) continue;\n if (reportStat.mtimeMs > latestMtime) {\n latestDir = dirPath;\n latestReport = report;\n latestContext = context;\n latestMtime = reportStat.mtimeMs;\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Error reading review run ${dirPath}:`, msg);\n }\n }\n if (!latestDir || !latestReport || !latestContext) continue;\n if (now - latestMtime < VERDICT_SETTLE_MS) continue;\n\n const requestedAtMs = status.reviewRequestedAt ? Date.parse(status.reviewRequestedAt) : Number.NaN;\n if (Number.isFinite(requestedAtMs) && requestedAtMs > latestMtime) continue;\n\n let currentHead: HeadAnchor | undefined;\n let reviewedHead: HeadAnchor | undefined;\n try {\n const { rehydrateHeadAnchor, snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n currentHead = await snapshotWorkspaceHeadsPromise(issueId, wsPath);\n const persistedAnchor = reviewAnchorFromContext(latestContext);\n reviewedHead = persistedAnchor ? rehydrateHeadAnchor(persistedAnchor) : undefined;\n } catch {\n continue;\n }\n if (!reviewedHead || currentHead !== reviewedHead) continue;\n\n let parsed: ReturnType<typeof parseVerdictReport>;\n try {\n parsed = parseVerdictReport(await readFile(latestReport.path, 'utf8'));\n } catch {\n continue;\n }\n if (!parsed) continue;\n\n const reviewSession = `agent-${issueId.toLowerCase()}-review`;\n const sessionAlive = sessionExistsSync(reviewSession);\n const paneDead = sessionAlive ? await Effect.runPromise(isPaneDead(reviewSession)).catch(() => true) : true;\n if (sessionAlive && !paneDead) {\n const lastNudged = pendingVerdictNudges.get(latestDir);\n if (!lastNudged) {\n const notes = parsed.topBlocker || `See ${latestReport.filename}`;\n const cmd = buildReviewCompletionCommand(\n issueId,\n parsed.verdict,\n notes,\n basename(latestDir),\n );\n const nudge = `Your review verdict is already written on disk but review status is still pending. Your ONLY remaining task is to execute this Bash command immediately — do not analyze, do not summarize, do not ask questions, just run it:\\n\\n${cmd}\\n\\nRun this command NOW. Do not write any other response before executing it.`;\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(reviewSession, nudge);\n pendingVerdictNudges.set(latestDir, now);\n const action = `Nudged ${reviewSession} to apply pending ${parsed.verdict} verdict from ${latestReport.filename}`;\n actions.push(action);\n console.log(`[deacon] ${action}`);\n } catch (err: unknown) {\n console.error(`[deacon] Failed to nudge ${reviewSession}:`, err instanceof Error ? err.message : String(err));\n }\n continue;\n }\n if (now - lastNudged < NUDGE_GRACE_MS) continue;\n }\n\n const attribution = `applied by deacon sweep from on-disk ${latestReport.filename}`;\n const reviewNotes = parsed.topBlocker\n ? `${parsed.topBlocker} — ${attribution}`\n : `Review ${parsed.verdict} ${attribution}`;\n const convergence = await convergeRowFromVerdictOfRecord(issueId, {\n runId: basename(latestDir),\n workspacePath: wsPath,\n writer: 'unsignaled-recovery',\n notes: reviewNotes,\n });\n if (!convergence.converged) continue;\n\n if (parsed.verdict === 'blocked') {\n try {\n await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId,\n verdict: 'blocked',\n notes: parsed.topBlocker || reviewNotes,\n workspacePath: wsPath,\n prUrl: status.prUrl,\n runId: basename(latestDir),\n }));\n } catch (err: unknown) {\n console.error(`[deacon] Failed to deliver reconciled review verdict for ${issueId}:`, err instanceof Error ? err.message : String(err));\n }\n }\n\n pendingVerdictNudges.delete(latestDir);\n const action = `reconcileUnappliedReviewVerdicts deacon sweep applied ${parsed.verdict} for ${issueId} from ${latestReport.filename}`;\n actions.push(action);\n emitActivityEntrySync({ source: 'cloister', level: 'warn', message: action, issueId });\n console.log(`[deacon] ${action}`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Error reconciling unapplied review verdict for ${issueId}:`, msg);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error reconciling unapplied review verdicts:', msg);\n }\n\n return actions;\n}\n\nexport async function checkCompletedButUnsignaledReviews(): Promise<string[]> {\n const actions: string[] = [];\n const SYNTHESIS_SETTLE_MS = 5 * 60 * 1000; // 5 minutes\n\n try {\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'reviewing') continue;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const wsPath = findWorkspacePath(resolved.projectPath, issueId.toLowerCase());\n if (!wsPath) continue;\n\n const reviewBaseDir = join(wsPath, '.pan', 'review');\n if (!existsSync(reviewBaseDir)) continue;\n\n // Find the most recently modified review run directory\n let latestDir: string | null = null;\n let latestReport: ReturnType<typeof findVerdictReport> = null;\n let latestMtime = 0;\n for (const entry of readdirSync(reviewBaseDir)) {\n if (!entry.startsWith(`agent-${issueId.toLowerCase()}-review`)) continue;\n const dirPath = join(reviewBaseDir, entry);\n const report = findVerdictReport(dirPath);\n if (!report) continue;\n const mtime = statSync(report.path).mtimeMs;\n if (!isSynthesisForActiveReviewRun(dirPath, status, mtime)) continue;\n if (mtime > latestMtime) {\n latestMtime = mtime;\n latestDir = dirPath;\n latestReport = report;\n }\n }\n if (!latestDir || !latestReport) continue;\n\n // Wait for synthesis to settle before intervening\n if (now - latestMtime < SYNTHESIS_SETTLE_MS) continue;\n\n // Deduplicate: only nudge once per directory (one review cycle)\n const lastNudged = unsignaledReviewNudges.get(latestDir);\n if (lastNudged && now - lastNudged < 30 * 60 * 1000) continue;\n\n const reviewSession = `agent-${issueId.toLowerCase()}-review`;\n const sessionAlive = sessionExistsSync(reviewSession);\n const paneDead = sessionAlive ? await Effect.runPromise(isPaneDead(reviewSession)).catch(() => true) : true;\n const activeReviewState = sessionAlive && !paneDead ? getAgentStateSync(reviewSession) : null;\n if (activeReviewState?.reviewRunId && latestDir !== join(reviewBaseDir, activeReviewState.reviewRunId)) {\n continue;\n }\n\n let parsed: ReturnType<typeof parseVerdictReport>;\n try {\n parsed = parseVerdictReport(readFileSync(latestReport.path, 'utf8'));\n } catch {\n continue;\n }\n if (!parsed) continue;\n const { verdict, topBlocker } = parsed;\n\n if (sessionAlive && !paneDead) {\n // If we already nudged once and 30+ min have passed with no signal,\n // the agent is unresponsive — auto-complete so the pipeline isn't blocked.\n if (lastNudged) {\n const notes = topBlocker || `Review auto-completed by deacon: ${verdict} (agent alive but unresponsive after nudge, ${latestReport.filename} exists)`;\n const outcome = await recordReviewVerdict(issueId, { verdict, notes, writer: 'unsignaled-recovery' });\n if (!outcome.landed) { actions.push(`Auto-complete for ${issueId} not recorded (${outcome.reason})`); continue; }\n actions.push(`Auto-completed review for ${issueId}: ${verdict} (alive but unresponsive after nudge, ${latestReport.filename} written ${Math.round((now - latestMtime) / 60000)}min ago)`);\n console.log(`[deacon] Auto-completed review for ${issueId}: ${verdict} (alive but unresponsive after nudge)`);\n continue;\n }\n\n // Agent is alive but idle — nudge it to signal completion\n const cmd = buildReviewCompletionCommand(\n issueId,\n verdict,\n topBlocker || `See ${latestReport.filename}`,\n basename(latestDir),\n );\n const nudge = `Your review verdict in ${latestReport.filename} is already written and saved. Your ONLY remaining task is to execute this Bash command immediately — do not analyze, do not summarize, do not ask questions, just run it:\\n\\n${cmd}\\n\\nRun this command NOW. Do not write any other response before executing it.`;\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(reviewSession, nudge);\n unsignaledReviewNudges.set(latestDir, now);\n actions.push(`Nudged ${reviewSession} to signal ${verdict} (${latestReport.filename} written ${Math.round((now - latestMtime) / 60000)}min ago)`);\n console.log(`[deacon] Nudged ${reviewSession} to signal ${verdict}`);\n } catch (err: unknown) {\n console.error(`[deacon] Failed to nudge ${reviewSession}:`, err instanceof Error ? err.message : String(err));\n }\n } else {\n // Session is dead — auto-complete so the pipeline isn't blocked\n const notes = topBlocker || `Review auto-completed by deacon: ${verdict} (agent dead, ${latestReport.filename} exists)`;\n const outcome = await recordReviewVerdict(issueId, { verdict, notes, writer: 'unsignaled-recovery' });\n if (!outcome.landed) { actions.push(`Auto-complete for ${issueId} not recorded (${outcome.reason})`); continue; }\n actions.push(`Auto-completed review for ${issueId}: ${verdict} (dead agent, ${latestReport.filename} written ${Math.round((now - latestMtime) / 60000)}min ago)`);\n console.log(`[deacon] Auto-completed review for ${issueId}: ${verdict} (dead agent)`);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking completed-but-unsignaled reviews:', msg);\n }\n\n return actions;\n}\n","import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { Effect } from 'effect';\nimport { isContextOverflowTail } from '../context-overflow.js';\nimport { getAgentRuntimeStateSync, getAgentState, getAgentStateSync, saveAgentStateSync, type AgentState } from '../agents.js';\nimport { deliverReviewVerdictFeedback } from './review-verdict-feedback.js';\nimport { findVerdictReport } from './review-verdict-report.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport type { HeadAnchor } from '../git-utils.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { REVIEW_SUB_ROLES, type ReviewSubRole } from './review-monitor.js';\nimport { capturePane, isPaneDead, killSession, listSessionNames, listSessions, sessionExists, sessionExistsSync } from '../tmux.js';\nimport { applyCodexAuthBurnFlag, isCodexAuthRouted, paneShowsCodexAuthBurn } from '../codex-auth.js';\nimport { extractMarkdownSection, findBlockingFindings } from '../review-findings.js';\nimport { getAgentEffectiveLastActivityMs } from './agent-idle.js';\n\nconst REVIEWER_IDLE_FAILURE_MS = 3 * 60 * 1000;\nconst REVIEW_REPORTS_PRESENT_NUDGE_COOLDOWN_MS = 60 * 1000;\nconst reviewReportsPresentNudges = new Map<string, number>();\n\ntype DeaconReviewSynthesis = {\n verdict: 'passed' | 'blocked';\n topBlocker: string;\n body: string;\n};\n\nexport function synthesizeReviewFromReports(opts: {\n issueId: string;\n reviewDir: string;\n reports: ReadonlyArray<{ subRole: ReviewSubRole; path: string; body: string }>;\n}): DeaconReviewSynthesis {\n const blockers = opts.reports.flatMap(report =>\n findBlockingFindings(report.body).map(title => ({\n subRole: report.subRole,\n title,\n path: report.path,\n })),\n );\n const verdict: 'passed' | 'blocked' = blockers.length > 0 ? 'blocked' : 'passed';\n const topBlocker = blockers[0] ? `[${blockers[0].subRole}] ${blockers[0].title}` : '';\n const blockerSection = blockers.length > 0\n ? blockers.map(blocker => `### [${blocker.subRole}] ${blocker.title}\\nSource: ${blocker.path}`).join('\\n\\n')\n : 'None';\n const cleanSubRoles = opts.reports\n .filter(report => !blockers.some(blocker => blocker.subRole === report.subRole))\n .map(report => `- ${report.subRole}`)\n .join('\\n') || 'None';\n const convoyRows = opts.reports.map(report => {\n const blockingCount = findBlockingFindings(report.body).length;\n return `| ${report.subRole} | ready | ${report.path} | ${blockingCount} |`;\n }).join('\\n');\n\n const body = [\n `# Review Synthesis — ${opts.issueId} — ${new Date().toISOString()}`,\n '',\n `## Verdict: ${verdict === 'passed' ? 'APPROVED' : `CHANGES REQUESTED — ${topBlocker}`}`,\n '',\n '## Context',\n '- Generated by Deacon fallback from completed on-disk reviewer reports.',\n `- Review directory: ${opts.reviewDir}`,\n '',\n '## Convoy Status',\n '| Sub-role | Signal | Output | Blocking findings |',\n '| --- | --- | --- | --- |',\n convoyRows,\n '',\n '## Blocking Findings',\n blockerSection,\n '',\n '## Non-blocking Findings',\n 'See individual reviewer reports.',\n '',\n '## Clean Sub-roles',\n cleanSubRoles,\n '',\n ].join('\\n');\n\n return { verdict, topBlocker, body };\n}\n\n/**\n * Retry a convoy reviewer that has stopped making progress without writing output.\n *\n * The retry covers terminal API errors, warm-resume kickoffs that never start a\n * turn, and elapsed reviewer deadlines. It preserves the same run/output wiring\n * and persists one shared attempt count so every trigger is bounded together.\n */\nasync function retryReviewer(state: AgentState, agentId: string, trigger: string): Promise<boolean> {\n const outputPath = state.reviewOutputPath;\n const attempt = (state.reviewRetryAttempt ?? 0) + 1;\n logDeaconEventSync(`monitorReviewConvoySignals: ${agentId} ${trigger} — retrying reviewer (attempt ${attempt})`);\n\n try {\n await Effect.runPromise(killSession(agentId));\n } catch (err) {\n logDeaconEventSync(`monitorReviewConvoySignals: failed to kill ${agentId} before respawn: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n if (!outputPath || !state.reviewSubRole || !state.reviewRunId) {\n logDeaconEventSync(`monitorReviewConvoySignals: cannot respawn ${agentId} — missing output/run/subRole`);\n return false;\n }\n\n try {\n const { spawnReviewSubRoleForIssue } = await import('./review-agent.js');\n const contextManifestPath = join(dirname(outputPath), 'context.json');\n const result = await Effect.runPromise(\n spawnReviewSubRoleForIssue({\n issueId: state.issueId,\n workspace: state.workspace,\n subRole: state.reviewSubRole as ReviewSubRole,\n runId: state.reviewRunId,\n outputPath,\n contextManifestPath,\n synthesisAgentId: state.reviewSynthesisAgentId,\n model: state.model,\n harness: state.harness,\n allowHost: state.hostOverride ?? false,\n }),\n );\n\n if (!result.success) {\n logDeaconEventSync(`monitorReviewConvoySignals: respawn of ${agentId} failed: ${result.error ?? result.message}`);\n return false;\n }\n\n const freshState = getAgentStateSync(agentId);\n if (freshState) {\n freshState.reviewRetryAttempt = attempt;\n saveAgentStateSync(freshState);\n }\n\n logDeaconEventSync(`monitorReviewConvoySignals: respawned ${agentId} as ${result.sessionId}`);\n return true;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`monitorReviewConvoySignals: respawn of ${agentId} threw: ${errMsg}`);\n return false;\n }\n}\n\nexport async function nudgeSynthesisForCompleteReviewerReports(states: readonly AgentState[]): Promise<string[]> {\n const actions: string[] = [];\n const now = Date.now();\n const synthesisStates = states.filter(state =>\n state.role === 'review'\n && !state.reviewSubRole\n && state.reviewRunId\n && state.workspace\n && state.issueId\n );\n\n for (const state of synthesisStates) {\n const status = getReviewStatusSync(state.issueId);\n if (!status || status.reviewStatus !== 'reviewing') continue;\n\n const reviewDir = join(state.workspace, '.pan', 'review', state.reviewRunId!);\n // A synthesis.md on disk does NOT mean the verdict landed: the parent can\n // die between writing the file and the verdict write door, wedging the issue\n // in 'reviewing' forever (observed 3× on 2026-07-13). Since reviewStatus\n // is still 'reviewing' here (guard above), fall through and land the\n // verdict deterministically instead of skipping.\n const synthesisAlreadyWritten = findVerdictReport(reviewDir) !== null;\n\n const startedMs = Date.parse(state.startedAt);\n const readyLines: string[] = [];\n const reports: Array<{ subRole: ReviewSubRole; path: string; body: string }> = [];\n let allReportsPresent = true;\n for (const subRole of REVIEW_SUB_ROLES) {\n const outputPath = join(reviewDir, `${subRole}.md`);\n if (!existsSync(outputPath)) {\n allReportsPresent = false;\n break;\n }\n try {\n const outputMtimeMs = statSync(outputPath).mtimeMs;\n if (Number.isFinite(startedMs) && outputMtimeMs < startedMs) {\n allReportsPresent = false;\n break;\n }\n } catch {\n allReportsPresent = false;\n break;\n }\n try {\n reports.push({ subRole, path: outputPath, body: readFileSync(outputPath, 'utf8') });\n } catch {\n allReportsPresent = false;\n break;\n }\n readyLines.push(`REVIEWER_READY ${subRole} ${outputPath}`);\n }\n if (!allReportsPresent) continue;\n\n const nudgeKey = `${state.id}:${state.reviewRunId}`;\n const lastNudge = reviewReportsPresentNudges.get(nudgeKey);\n if (lastNudge && now - lastNudge < REVIEW_REPORTS_PRESENT_NUDGE_COOLDOWN_MS) continue;\n\n const sessionAlive = await Effect.runPromise(sessionExists(state.id)).catch(() => false);\n const paneDead = sessionAlive ? await Effect.runPromise(isPaneDead(state.id)).catch(() => true) : true;\n if (synthesisAlreadyWritten || lastNudge || !sessionAlive || paneDead) {\n const synthesis = synthesizeReviewFromReports({\n issueId: state.issueId,\n reviewDir,\n reports,\n });\n writeFileSync(join(reviewDir, 'synthesis.md'), synthesis.body);\n let fallbackHead: HeadAnchor | undefined;\n try {\n // PAN-2948: polyrepo-aware — snapshots sub-repo heads, not the wrapper.\n const { snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n fallbackHead = await snapshotWorkspaceHeadsPromise(state.issueId, state.workspace);\n } catch { /* non-fatal — the verdict write door accepts missing evidence. */ }\n const notes = synthesis.topBlocker || 'Review approved by Deacon fallback from completed reviewer reports';\n const { recordReviewVerdict } = await import('./review-verdict-writer.js');\n const outcome = await recordReviewVerdict(state.issueId, {\n verdict: synthesis.verdict,\n notes,\n evidenceHead: fallbackHead,\n ...(synthesis.verdict === 'blocked' && fallbackHead ? { extra: { reviewedAtCommit: fallbackHead } } : {}),\n runId: state.reviewRunId,\n writer: 'fallback',\n });\n if (!outcome.landed) {\n // The verdict of record stays on disk in synthesis.md and the parent stays\n // alive for the PAN-2584 liveness deadline — killing it here would strand a\n // rejected verdict with no session left to re-signal it.\n const rejected = `Deacon fallback verdict for ${state.issueId} was not recorded (${outcome.reason}); left parent ${state.id} alive`;\n actions.push(rejected);\n logDeaconEventSync(`monitorReviewConvoySignals: ${rejected}`);\n continue;\n }\n if (synthesis.verdict === 'blocked') {\n await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId: state.issueId,\n verdict: 'blocked',\n notes,\n workspacePath: state.workspace,\n prUrl: status.prUrl,\n runId: state.reviewRunId,\n }));\n }\n if (sessionAlive) {\n await Effect.runPromise(killSession(state.id)).catch((err) => {\n const errMsg = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`monitorReviewConvoySignals: failed to kill synthesized parent ${state.id}: ${errMsg}`);\n });\n }\n const action = `Synthesized review for ${state.issueId} from ${REVIEW_SUB_ROLES.length} reviewer reports: ${synthesis.verdict}`;\n actions.push(action);\n logDeaconEventSync(`monitorReviewConvoySignals: ${action}`);\n continue;\n }\n\n const message = [\n 'Deacon fallback: all reviewer report files for this review run are present on disk, but synthesis has not been written yet.',\n 'If you have not already synthesized this run, treat these as the missing terminal reviewer signals and proceed now:',\n '',\n ...readyLines,\n '',\n 'Read the four reports, write synthesis.md, then signal review status exactly as roles/review.md instructs.',\n ].join('\\n');\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(state.id, message);\n reviewReportsPresentNudges.set(nudgeKey, now);\n const action = `Nudged ${state.id} to synthesize from ${REVIEW_SUB_ROLES.length} reviewer reports`;\n actions.push(action);\n logDeaconEventSync(`monitorReviewConvoySignals: ${action}`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`monitorReviewConvoySignals: failed to nudge ${state.id} for complete reviewer reports: ${errMsg}`);\n }\n }\n\n return actions;\n}\n\nexport async function monitorReviewConvoySignals(): Promise<string[]> {\n const actions: string[] = [];\n if (!existsSync(AGENTS_DIR)) return actions;\n\n let agentDirs: string[];\n try {\n agentDirs = readdirSync(AGENTS_DIR).filter(d => d.startsWith('agent-'));\n } catch {\n return actions;\n }\n\n // PAN-3549: session-created index for the stale-deadline guard below. One\n // probe up front; if it fails the guard stays off (legacy behavior).\n let sessionCreatedMsByName = new Map<string, number>();\n try {\n const sessions = await Effect.runPromise(listSessions());\n sessionCreatedMsByName = new Map(sessions.map((s) => [s.name, s.created.getTime()]));\n } catch { /* liveness probe failure — guard disabled this sweep */ }\n\n const reviewStates: AgentState[] = [];\n\n for (const agentId of agentDirs) {\n // PAN-1908: use the agents table; fall back to state.json only for legacy dirs.\n const state = await Effect.runPromise(\n getAgentState(agentId).pipe(Effect.catch(() => Effect.succeed(null))),\n ).then((dbState) => dbState ?? getAgentStateSync(agentId));\n if (!state) continue;\n if (state.role !== 'review') continue;\n reviewStates.push(state);\n if (!state.reviewSubRole || !state.reviewSynthesisAgentId) continue;\n if (state.reviewMonitorSignaled) continue;\n\n const startedMs = Date.parse(state.startedAt);\n\n // PAN-977: the review sub-role launcher now owns the convoy signal — it\n // signals synthesis on process exit (REVIEWER_READY/FAILED/TIMEOUT) and\n // touches `reviewer-signaled`. When that marker is newer than this run's\n // start, the launcher already signaled and Deacon must not double-signal.\n // Deacon stays the rare backup for the case where the launcher's bash\n // process was SIGKILLed before it could signal.\n const signalMarker = join(AGENTS_DIR, agentId, 'reviewer-signaled');\n if (existsSync(signalMarker)) {\n try {\n if (Number.isFinite(startedMs) && statSync(signalMarker).mtimeMs >= startedMs) continue;\n } catch { /* unreadable marker — fall through to backup signaling */ }\n }\n\n const outputPath = state.reviewOutputPath;\n let outputWrittenForThisRun = false;\n if (outputPath && existsSync(outputPath)) {\n try {\n const outputMtimeMs = statSync(outputPath).mtimeMs;\n outputWrittenForThisRun = Number.isFinite(startedMs) && outputMtimeMs >= startedMs;\n } catch {\n outputWrittenForThisRun = false;\n }\n }\n const deadlineMs = state.reviewDeadlineAt ? Date.parse(state.reviewDeadlineAt) : Number.NaN;\n\n // The synthesis agent must be alive to receive any signal.\n const synthesisAlive = await Effect.runPromise(sessionExists(state.reviewSynthesisAgentId));\n if (!synthesisAlive) continue;\n\n // PAN-1557: convoy reviewers are interactive now — there is no headless\n // launcher process / pid file. Liveness is the reviewer's own tmux session:\n // while it's alive the reviewer is still working (or idling attachably after\n // writing its report); once it's gone the reviewer has crashed or been\n // reaped. The Stop-hook is the primary signal (touches reviewer-signaled +\n // delivers REVIEWER_READY); Deacon is the backup for when that didn't fire.\n const reviewerSessionAlive = (await Effect.runPromise(sessionExists(agentId)).catch(() => false))\n && !(await Effect.runPromise(isPaneDead(agentId)).catch(() => true));\n\n // PAN-3549: a deadline older than the lane's CURRENT tmux session belongs\n // to a dead attempt — run ids are content-derived (head8), so a re-dispatch\n // on the same HEAD inherits the previous attempt's monitor state, and\n // timing out on it kills the fresh lane mid-resume (retryReviewer kills the\n // live session first) while the stale signal replays into the resumed\n // parent as a phantom \"infrastructure failure\" verdict (PAN-3511,\n // 2026-08-04). The live session is the oracle: clear the stale monitor\n // state through the state door and let the new attempt run.\n if (reviewerSessionAlive && Number.isFinite(deadlineMs)) {\n const sessionCreatedMs = sessionCreatedMsByName.get(agentId);\n if (sessionCreatedMs !== undefined && sessionCreatedMs > deadlineMs) {\n const staleDeadline = state.reviewDeadlineAt;\n delete state.reviewMonitorSignaled;\n delete state.reviewRetryAttempt;\n delete state.reviewDeadlineAt;\n try {\n saveAgentStateSync(state);\n } catch { /* best-effort — the next sweep re-evaluates */ }\n logDeaconEventSync(`monitorReviewConvoySignals: ${agentId} deadline ${staleDeadline} predates its live session — cleared stale monitor state (PAN-3549)`);\n continue;\n }\n }\n\n // Startup grace: give the session time to come up before \"gone\" reads as death.\n const REVIEWER_STARTUP_GRACE_MS = 90_000;\n const withinStartupGrace = Number.isFinite(startedMs) && (Date.now() - startedMs) < REVIEWER_STARTUP_GRACE_MS;\n\n // PAN-1806/PAN-3375: detect both terminal API errors that return to an idle\n // prompt and warm-resume kickoffs that never start a turn. resumeAgent marks\n // the runtime active after delivery, so a swallowed kickoff otherwise leaves\n // a stale active mirror forever. Any real reviewer work refreshes lastActivity;\n // retry once when either mirror is stale and no report exists.\n const runtimeState = getAgentRuntimeStateSync(agentId);\n const runtimeActivityMs = getAgentEffectiveLastActivityMs(agentId)\n ?? (runtimeState?.lastActivity ? Date.parse(runtimeState.lastActivity) : Number.NaN);\n const runtimeActivityAgeMs = Number.isFinite(runtimeActivityMs)\n ? Date.now() - runtimeActivityMs\n : 0;\n const idleAndNoOutput = reviewerSessionAlive\n && !outputWrittenForThisRun\n && runtimeState?.state === 'idle'\n && runtimeActivityAgeMs > REVIEWER_IDLE_FAILURE_MS;\n const staleActiveAndNoOutput = reviewerSessionAlive\n && !outputWrittenForThisRun\n && runtimeState?.state === 'active'\n && runtimeActivityAgeMs > REVIEWER_IDLE_FAILURE_MS;\n const stalledAndNoOutput = idleAndNoOutput || staleActiveAndNoOutput;\n\n // PAN-1818: context-window overflow is deterministic on the same diff/manifest.\n // Capture the pane tail and fast-fail BEFORE the retry branch so we never\n // burn another cycle on a reviewer that will re-overflow. Check an explicit\n // idle mirror or a stale active mirror whose activity evidence stopped moving.\n let contextOverflowDetected = false;\n if (!outputWrittenForThisRun && (runtimeState?.state === 'idle' || staleActiveAndNoOutput)) {\n const tail = await Effect.runPromise(capturePane(agentId, 100)).catch(() => '');\n contextOverflowDetected = isContextOverflowTail(tail);\n // PAN-2285: a convoy reviewer wedged on a revoked Codex refresh token shows\n // the burn markers in its pane. The idle-no-output path below already fails\n // this reviewer; additionally mark it troubled and persist the burn flag in\n // its agent state (same shape as checkApiErrorAgents) — this monitor runs\n // in the deacon CHILD process, so the flag must reach the dashboard\n // server's banner endpoint and spawn gate through the shared agents table,\n // not module memory.\n //\n // PAN-3528 widened the markers to cover the CLIProxy route, whose 503\n // wrapper text is generic enough to appear in a pane that is not\n // codex-authenticated at all — so only reviewers actually routed through\n // the codex credential family are eligible for the flag.\n const codexRouted = isCodexAuthRouted(state.harness, state.model);\n if (codexRouted && paneShowsCodexAuthBurn(tail) && applyCodexAuthBurnFlag(state)) {\n saveAgentStateSync(state);\n logDeaconEventSync(`review-monitor: ${agentId} marked troubled — Codex refresh token revoked (codex-auth-burned)`);\n }\n }\n\n let signal: 'ready' | 'failed' | 'timeout' | null = null;\n let reason = '';\n if (outputWrittenForThisRun) {\n // Report exists for this run but the Stop-hook didn't signal (no fresh\n // marker) — back it up with READY. Safe: the report is on disk.\n signal = 'ready';\n } else if (contextOverflowDetected) {\n signal = 'failed';\n reason = 'context-window overflow (no retry — deterministic)';\n } else if (stalledAndNoOutput) {\n const attempt = state.reviewRetryAttempt ?? 0;\n const trigger = staleActiveAndNoOutput\n ? 'warm-resumed but produced no activity or output'\n : 'idle with no output';\n if (attempt < 1 && (await retryReviewer(state, agentId, trigger))) {\n continue;\n }\n signal = 'failed';\n reason = staleActiveAndNoOutput\n ? `reviewer warm-resumed but produced no activity or output${attempt >= 1 ? ' (retry exhausted)' : ' (retry failed)'}`\n : `reviewer idle with no output after terminal API error${attempt >= 1 ? ' (retry exhausted)' : ' (retry failed)'}`;\n } else if (reviewerSessionAlive) {\n // Still working or idling attachably. Only intervene if well past the\n // deadline (genuinely wedged). The first elapsed deadline restarts only\n // this lane; the second produces a terminal timeout for synthesis.\n if (Number.isFinite(deadlineMs) && Date.now() >= deadlineMs + REVIEWER_STARTUP_GRACE_MS) {\n const attempt = state.reviewRetryAttempt ?? 0;\n if (attempt < 1 && (await retryReviewer(state, agentId, `still running past deadline ${state.reviewDeadlineAt}`))) {\n continue;\n }\n signal = 'timeout';\n reason = `reviewer still running past deadline ${state.reviewDeadlineAt}${attempt >= 1 ? ' (retry exhausted)' : ' (retry failed)'}`;\n } else {\n continue;\n }\n } else if (withinStartupGrace) {\n // Session not up yet — too early to call it dead.\n continue;\n } else if (Number.isFinite(deadlineMs) && Date.now() >= deadlineMs) {\n const attempt = state.reviewRetryAttempt ?? 0;\n if (attempt < 1 && (await retryReviewer(state, agentId, `exceeded deadline ${state.reviewDeadlineAt}`))) {\n continue;\n }\n signal = 'timeout';\n reason = `reviewer exceeded deadline ${state.reviewDeadlineAt}${attempt >= 1 ? ' (retry exhausted)' : ' (retry failed)'}`;\n } else {\n // Session gone, no report, before deadline → reviewer crashed or exited\n // before writing a report.\n signal = 'failed';\n reason = 'reviewer session ended before writing a report';\n }\n\n if (!signal) continue;\n\n const message = signal === 'ready'\n ? `REVIEWER_READY ${state.reviewSubRole} ${outputPath}`\n : signal === 'timeout'\n ? `REVIEWER_TIMEOUT ${state.reviewSubRole} ${reason}`\n : `REVIEWER_FAILED ${state.reviewSubRole} ${reason}`;\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(state.reviewSynthesisAgentId, message);\n state.reviewMonitorSignaled = signal;\n saveAgentStateSync(state);\n const action = `Signaled ${message} to ${state.reviewSynthesisAgentId}`;\n actions.push(action);\n logDeaconEventSync(`monitorReviewConvoySignals: ${action}`);\n if (signal === 'ready') {\n try {\n const { notifyPipelineSync } = await import('../pipeline-notifier.js');\n notifyPipelineSync({ type: 'reviewer_completed', issueId: state.issueId, role: state.reviewSubRole });\n } catch { /* non-fatal */ }\n } else if (signal === 'timeout') {\n try {\n const { notifyPipelineSync } = await import('../pipeline-notifier.js');\n notifyPipelineSync({\n type: 'reviewer_timed_out',\n issueId: state.issueId,\n role: state.reviewSubRole,\n sessionName: agentId,\n attempt: 1,\n maxRetries: 1,\n willRetry: false,\n });\n } catch { /* non-fatal */ }\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`monitorReviewConvoySignals: failed to signal ${state.reviewSynthesisAgentId} for ${agentId}: ${errMsg}`);\n }\n }\n\n actions.push(...await nudgeSynthesisForCompleteReviewerReports(reviewStates));\n\n return actions;\n}\n\n/**\n * Kill orphaned review sessions whose work agent is no longer running.\n *\n * Covers three naming patterns:\n * - PAN-1059 convoy: `agent-<issueId>-review-<subRole>` (current)\n * - Legacy specialist: `specialist-*-review-*` (retired)\n * - Legacy batch: `review-<issueId>-<timestamp>-<role>` (retired)\n *\n * A session is \"orphaned\" when the corresponding work-agent session\n * `agent-<issueLower>` does not exist.\n */\nexport async function cleanupOrphanedReviewSessions(): Promise<string[]> {\n const actions: string[] = [];\n let reviewSessions: string[];\n try {\n const allSessions = await Effect.runPromise(listSessionNames());\n const convoyReviewSessions = allSessions.filter(s => /^agent-.*-review-(?:security|correctness|performance|requirements)$/.test(s));\n const legacyReviewSessions = allSessions.filter(s => /^review-/.test(s));\n const canonicalReviewSessions = allSessions.filter(s => /^specialist-.*-review-/.test(s));\n reviewSessions = [...new Set([...convoyReviewSessions, ...legacyReviewSessions, ...canonicalReviewSessions])];\n } catch {\n return actions;\n }\n\n logDeaconEventSync(`cleanupOrphanedReviewSessions started: found ${reviewSessions.length} review session(s)`);\n\n for (const reviewSession of reviewSessions) {\n let issueId: string | null = null;\n\n // PAN-1059 convoy pattern\n const convoyMatch = reviewSession.match(/^agent-([a-z0-9]+-\\d+)-review-(?:security|correctness|performance|requirements)$/);\n if (convoyMatch) {\n issueId = convoyMatch[1].toUpperCase();\n } else {\n // Legacy batch pattern: review-<issueId>-<timestamp>\n const legacyMatch = reviewSession.match(/^review-([A-Za-z0-9]+-\\d+)-\\d+/);\n if (legacyMatch) {\n issueId = legacyMatch[1].toUpperCase();\n } else {\n // Legacy specialist pattern: specialist-<project>-<issueId>-review-<role>\n const canonicalMatch = reviewSession.match(/^specialist-(.+)-([A-Za-z0-9]+-\\d+)-review-[a-z-]+$/);\n if (canonicalMatch) {\n issueId = canonicalMatch[2].toUpperCase();\n }\n }\n }\n\n if (!issueId) {\n logDeaconEventSync(`cleanupOrphanedReviewSessions: ${reviewSession} skipped — unparseable session name`);\n continue;\n }\n\n // PAN-1059 convoy sub-reviewers are owned by the synthesis agent\n // (agent-<id>-review), not the work agent. Check synthesis first,\n // then fall back to work agent for legacy review sessions.\n const synthesisAgentSession = `agent-${issueId.toLowerCase()}-review`;\n const workAgentSession = `agent-${issueId.toLowerCase()}`;\n if (sessionExistsSync(synthesisAgentSession)) {\n logDeaconEventSync(`cleanupOrphanedReviewSessions: ${reviewSession} kept — synthesis agent ${synthesisAgentSession} exists`);\n continue;\n }\n if (sessionExistsSync(workAgentSession)) {\n logDeaconEventSync(`cleanupOrphanedReviewSessions: ${reviewSession} kept — work agent ${workAgentSession} exists`);\n continue;\n }\n\n try {\n await Effect.runPromise(killSession(reviewSession)).catch(() => {});\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`cleanupOrphanedReviewSessions: error killing ${reviewSession}: ${reason}`);\n }\n\n const msg = `Killed orphaned ${reviewSession} (synthesis ${synthesisAgentSession} and work ${workAgentSession} not running)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`cleanupOrphanedReviewSessions: ${msg}`);\n }\n if (actions.length > 0) {\n logDeaconEventSync(`cleanupOrphanedReviewSessions completed: killed ${actions.length} orphaned session(s)`);\n } else {\n logDeaconEventSync(`cleanupOrphanedReviewSessions completed: no orphaned sessions found`);\n }\n\n return actions;\n}\n\n\n/**\n * PAN-2584: liveness WATCH for the review PARENT (synthesis/self-review).\n *\n * ─── OBSERVABILITY ONLY — OPERATOR DIRECTIVE (2026-08-05) ───────────────────\n * This check must NEVER kill, stop, or respawn a parent again. On 2026-08-05\n * it killed the PAN-3511 parent two hours after that parent had recorded a\n * real verdict, because its \"no terminal verdict\" read used only the runtime\n * row — the plane that was losing verdicts that night. A past-deadline parent\n * now produces a needs-you escalation with the evidence, and the operator (or\n * a deliberately invoked door) decides whether a respawn is warranted.\n * ───────────────────────────────────────────────────────────────────────────\n *\n * The verdict check reads the verdict OF RECORD first (the synthesis artifact\n * via findVerdictReport — PAN-3511's contract), then the runtime row; a\n * terminal verdict in either plane means the parent is warm-idle by design\n * and is skipped. The deadline is explicit (`reviewDeadlineAt`, armed at\n * spawn/resume) with an implicit fallback from the last (re)start for parents\n * predating the arming code.\n */\nexport async function checkStalledReviewParents(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { listAgentStates } = await import('../agents.js');\n const { PARENT_REVIEW_TIMEOUT_MS } = await import('./review-agent.js');\n const states = listAgentStates();\n const now = Date.now();\n for (const state of states) {\n if (state.role !== 'review' || state.reviewSubRole || !state.issueId) continue;\n if (state.status !== 'running') continue;\n const armedMs = state.reviewDeadlineAt ? Date.parse(state.reviewDeadlineAt) : Number.NaN;\n const startedMs = Date.parse(state.lastResumeAt ?? state.startedAt);\n const deadlineMs = Number.isFinite(armedMs)\n ? armedMs\n : Number.isFinite(startedMs)\n ? startedMs + PARENT_REVIEW_TIMEOUT_MS\n : Number.NaN;\n if (!Number.isFinite(deadlineMs) || now < deadlineMs) continue;\n\n // Verdict-of-record first (PAN-3511's contract): the synthesis artifact\n // cannot lose a verdict; the runtime row can (and did, 2026-08-05). A\n // terminal verdict in EITHER plane means warm-idle by design — skip.\n if (hasTerminalVerdictOfRecord(state)) continue;\n const reviewStatus = getReviewStatusSync(state.issueId)?.reviewStatus;\n if (reviewStatus === 'passed' || reviewStatus === 'blocked' || reviewStatus === 'failed') continue;\n\n // Escalate once per deadline epoch — patrols repeat, the operator does\n // not need the same alarm every 60 seconds.\n const escalationKey = `${state.id}:${deadlineMs}`;\n if (stalledReviewParentEscalations.has(escalationKey)) continue;\n stalledReviewParentEscalations.add(escalationKey);\n\n const paneTail = await Effect.runPromise(capturePane(state.id, 30)).catch(() => '');\n const evidence = {\n deadline: new Date(deadlineMs).toISOString(),\n reviewStatus: reviewStatus ?? 'none',\n verdictArtifact: 'none found',\n paneTail: paneTail.split('\\n').filter(Boolean).slice(-5).join(' | ').slice(0, 400),\n };\n try {\n const { markWorkspaceStuck } = await import('../review-status.js');\n markWorkspaceStuck(state.issueId, 'review_parent_stalled_needs_you', {\n reason: `Review parent ${state.id} is past its review deadline with no verdict on record`,\n ...evidence,\n });\n } catch (stuckErr) {\n console.warn(`[deacon] checkStalledReviewParents: could not mark ${state.issueId} needs-you:`, stuckErr);\n }\n const message = `checkStalledReviewParents: ${state.id} past review deadline with no verdict for ${state.issueId} — escalated to operator (no kill; observability-only)`;\n actions.push(message);\n logDeaconEventSync(message);\n try {\n const { emitActivityEntrySync } = await import('../activity-logger.js');\n emitActivityEntrySync({\n source: 'review',\n level: 'warn',\n message: `Review parent for ${state.issueId} exceeded its deadline with no verdict — needs operator attention (respawn is YOUR call; the sweeper no longer kills)`,\n issueId: state.issueId,\n });\n } catch { /* activity logging is advisory */ }\n }\n } catch (error: unknown) {\n console.error('[deacon] Error checking stalled review parents:', error instanceof Error ? error.message : String(error));\n }\n return actions;\n}\n\n/** Escalation dedup for checkStalledReviewParents: `${agentId}:${deadlineMs}` keys. */\nconst stalledReviewParentEscalations = new Set<string>();\n\n/**\n * PAN-3551: the verdict of record for a review run — the synthesis artifact in\n * the run's review directory (synthesis.md or review.md, via the shared\n * findVerdictReport door). Presence means the parent completed its synthesis,\n * independent of what any status plane currently claims.\n */\nfunction hasTerminalVerdictOfRecord(state: AgentState): boolean {\n if (!state.workspace || !state.reviewRunId) return false;\n try {\n const reviewDir = join(state.workspace, '.pan', 'review', state.reviewRunId);\n return findVerdictReport(reviewDir) !== null;\n } catch {\n return false;\n }\n}\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { defaultRunWorkspace } from './spawn-prep.js';\nimport { resolveWorkspaceRepoRootsSync, type WorkspaceRepoRoot } from '../project-repos.js';\nimport type { XBriefItem } from '../xbrief/types.js';\n\nconst execAsync = promisify(exec);\n\nexport interface SlotMergeIssue {\n issueId: string;\n featureWorkspace?: string;\n slotBranch?: string;\n slotWorkspace?: string;\n}\n\nexport interface SlotMergeEvidence {\n verifyCommands: string[];\n expectedOutputs: string[];\n commandOutputs: Array<{ command: string; stdout: string; stderr: string }>;\n mergeOutput?: { stdout: string; stderr: string };\n /** Per-repo merge outputs for polyrepo slots (one entry per nested repo merged). */\n repoMergeOutputs?: Array<{ repoKey: string; stdout: string; stderr: string }>;\n}\n\nexport interface SlotMergeResult {\n verified: boolean;\n merged: boolean;\n conflicts: boolean;\n evidence: SlotMergeEvidence;\n failure?: string;\n}\n\nexport interface SlotMergeDeps {\n run: (command: string, cwd: string) => Promise<{ stdout: string; stderr: string }>;\n /** Polyrepo-aware repo roots for a workspace path. Defaults to the canonical resolver. */\n resolveRepoRoots: (issueId: string, workspacePath: string) => WorkspaceRepoRoot[];\n}\n\nexport interface SlotMergeOptions {\n deps?: Partial<SlotMergeDeps>;\n}\n\nexport async function verifyAndMergeSlot(\n issue: string | SlotMergeIssue,\n slotIndex: number,\n item: XBriefItem,\n options: SlotMergeOptions = {},\n): Promise<SlotMergeResult> {\n const issueId = typeof issue === 'string' ? issue : issue.issueId;\n const featureWorkspace = typeof issue === 'string'\n ? defaultRunWorkspace(issue)\n : issue.featureWorkspace ?? defaultRunWorkspace(issue.issueId);\n const slotWorkspace = typeof issue === 'string' ? `${featureWorkspace}-slot-${slotIndex}` : issue.slotWorkspace ?? `${featureWorkspace}-slot-${slotIndex}`;\n const slotBranch = typeof issue === 'string' ? `feature/${issueId.toLowerCase()}-slot-${slotIndex}` : issue.slotBranch ?? `feature/${issueId.toLowerCase()}-slot-${slotIndex}`;\n const deps: SlotMergeDeps = {\n run: async (command, cwd) => execAsync(command, { cwd }),\n resolveRepoRoots: resolveWorkspaceRepoRootsSync,\n ...options.deps,\n };\n const verifyCommands = item.metadata?.verify_commands ?? [];\n const expectedOutputs = item.metadata?.expected_outputs ?? [];\n const evidence: SlotMergeEvidence = {\n verifyCommands,\n expectedOutputs,\n commandOutputs: [],\n };\n\n if (!Number.isInteger(slotIndex) || slotIndex < 1) {\n return { verified: false, merged: false, conflicts: false, evidence, failure: `Invalid slot index: ${slotIndex}` };\n }\n if (verifyCommands.length === 0) {\n return { verified: false, merged: false, conflicts: false, evidence, failure: `Item ${item.id} has no verify_commands` };\n }\n if (expectedOutputs.length === 0) {\n return { verified: false, merged: false, conflicts: false, evidence, failure: `Item ${item.id} has no expected_outputs` };\n }\n\n for (const command of verifyCommands) {\n try {\n const output = await deps.run(command, slotWorkspace);\n evidence.commandOutputs.push({ command, stdout: output.stdout, stderr: output.stderr });\n } catch (error) {\n const failed = commandFailure(error);\n evidence.commandOutputs.push({ command, stdout: failed.stdout, stderr: failed.stderr });\n return {\n verified: false,\n merged: false,\n conflicts: false,\n evidence,\n failure: `Verify command failed for ${item.id}: ${command}`,\n };\n }\n }\n\n // PAN-3691: never report a merge when the slot branch carries no unmerged\n // current-item work. Count base..slot commits per real repo root (the\n // polyrepo wrapper is excluded by the resolver, so outer bookkeeping/setup\n // commits cannot satisfy the gate). A zero total means either a fresh slot\n // whose agent died before committing, or a slot branch reused after its\n // earlier work already merged — both must surface, not complete silently.\n const slotRoots = deps.resolveRepoRoots(issueId, slotWorkspace);\n if (slotRoots.some(root => root.degradedPolyrepo)) {\n return {\n verified: true,\n merged: false,\n conflicts: false,\n evidence,\n failure: `Could not resolve polyrepo roots for slot ${slotIndex} — refusing to infer a merge`,\n };\n }\n const aheadByRepo = new Map<string, number>();\n for (const root of slotRoots) {\n aheadByRepo.set(root.repoKey, await aheadCount(deps.run, root.dir, root.sourceBranch, slotBranch));\n }\n const rootsWithWork = slotRoots.filter(root => (aheadByRepo.get(root.repoKey) ?? 0) > 0);\n if (rootsWithWork.length === 0) {\n return {\n verified: true,\n merged: false,\n conflicts: false,\n evidence,\n failure: `Slot branch ${slotBranch} has no unmerged current-item changes in any repo — refusing to mark item ${item.id} merged`,\n };\n }\n\n const isPolyrepo = slotRoots.some(root => root.isPolyrepo);\n if (!isPolyrepo) {\n try {\n const mergeOutput = await deps.run(`git merge --no-ff ${JSON.stringify(slotBranch)}`, featureWorkspace);\n evidence.mergeOutput = mergeOutput;\n return { verified: true, merged: true, conflicts: false, evidence };\n } catch (error) {\n const failed = commandFailure(error);\n evidence.mergeOutput = { stdout: failed.stdout, stderr: failed.stderr };\n await deps.run('git merge --abort', featureWorkspace).catch(() => {});\n return {\n verified: true,\n merged: false,\n conflicts: true,\n evidence,\n failure: `Slot branch ${slotBranch} did not merge cleanly`,\n };\n }\n }\n\n // Polyrepo: the wrapper holds no slot branch. Merge each nested slot branch\n // into its feature-branch checkout inside the base feature workspace.\n const baseRoots = new Map(\n deps.resolveRepoRoots(issueId, featureWorkspace).map(root => [root.repoKey, root]),\n );\n const repoMergeOutputs: Array<{ repoKey: string; stdout: string; stderr: string }> = [];\n for (const root of rootsWithWork) {\n const baseRoot = baseRoots.get(root.repoKey);\n if (!baseRoot) {\n return {\n verified: true,\n merged: false,\n conflicts: false,\n evidence: { ...evidence, repoMergeOutputs },\n failure: `No base feature-workspace checkout for repo ${root.repoKey} — cannot integrate slot ${slotIndex}`,\n };\n }\n try {\n const output = await deps.run(`git merge --no-ff ${JSON.stringify(slotBranch)}`, baseRoot.dir);\n repoMergeOutputs.push({ repoKey: root.repoKey, stdout: output.stdout, stderr: output.stderr });\n } catch (error) {\n const failed = commandFailure(error);\n repoMergeOutputs.push({ repoKey: root.repoKey, stdout: failed.stdout, stderr: failed.stderr });\n await deps.run('git merge --abort', baseRoot.dir).catch(() => {});\n return {\n verified: true,\n merged: false,\n conflicts: true,\n evidence: { ...evidence, repoMergeOutputs },\n failure: `Slot branch ${slotBranch} did not merge cleanly into ${root.repoKey}`,\n };\n }\n }\n return { verified: true, merged: true, conflicts: false, evidence: { ...evidence, repoMergeOutputs } };\n}\n\n/** Count base..branch commits in one repo; a missing branch or repo means zero. */\nasync function aheadCount(\n run: SlotMergeDeps['run'],\n cwd: string,\n baseBranch: string,\n branch: string,\n): Promise<number> {\n try {\n const { stdout } = await run(\n `git rev-list --count ${JSON.stringify(baseBranch)}..${JSON.stringify(branch)}`,\n cwd,\n );\n const count = Number(stdout.trim());\n return Number.isFinite(count) && count > 0 ? count : 0;\n } catch {\n return 0;\n }\n}\n\nfunction commandFailure(error: unknown): { stdout: string; stderr: string } {\n const partial = error as { stdout?: unknown; stderr?: unknown; message?: unknown };\n return {\n stdout: typeof partial.stdout === 'string' ? partial.stdout : '',\n stderr: typeof partial.stderr === 'string'\n ? partial.stderr\n : typeof partial.message === 'string'\n ? partial.message\n : String(error),\n };\n}\n","/** Single task-mutation write door over the immutable xBRIEF specification. */\nimport { hostname } from 'node:os';\nimport { join } from 'node:path';\n\nimport type { ProjectConfig } from '../projects.js';\nimport type { TaskOperationType } from '../xbrief/dag.js';\nimport { applyStatusOverrides, findPlanSync, readPlanSync } from '../xbrief/io.js';\nimport { subItemsOf, type XBriefItemStatus } from '../xbrief/types.js';\nimport {\n type PanIssueRecord,\n type TaskClaim,\n type TaskClaimHistoryEntry,\n} from './record.js';\nimport { updateIssueRecord } from './record-update.js';\n\nexport interface TaskStatusChange {\n type: TaskOperationType;\n itemId: string;\n subItemIds?: string[];\n reason?: string;\n expectedSequence?: number;\n force?: boolean;\n writerId?: string;\n}\n\nexport interface TaskStatusChangeResult {\n issueId: string;\n itemId: string;\n status: XBriefItemStatus;\n sequence: number;\n claim?: TaskClaim;\n idempotent?: boolean;\n}\n\nexport class TaskStatusChangeError extends Error {\n constructor(\n message: string,\n public readonly details: {\n issueId: string;\n itemId: string;\n operation: TaskOperationType;\n currentStatus?: string;\n owner?: string;\n currentSequence?: number;\n },\n ) {\n super(message);\n this.name = 'TaskStatusChangeError';\n }\n}\n\nconst TERMINAL = new Set<XBriefItemStatus>(['completed', 'cancelled']);\n\nfunction statusFor(type: TaskOperationType): XBriefItemStatus {\n if (type === 'claim') return 'running';\n if (type === 'done') return 'completed';\n if (type === 'block') return 'blocked';\n if (type === 'unblock') return 'pending';\n if (type === 'reopen') return 'pending';\n return 'cancelled';\n}\n\nfunction subItemOverrideKey(itemId: string, subItemId: string): string {\n return subItemId.includes('.') ? subItemId : `${itemId}.${subItemId}`;\n}\n\nfunction allowedFrom(type: TaskOperationType, status: XBriefItemStatus): boolean {\n if (type === 'claim') return status === 'pending';\n if (type === 'done') return status === 'running';\n if (type === 'block') return status === 'pending' || status === 'running';\n if (type === 'unblock') return status === 'blocked';\n // PAN-3691: reopen is the canonical recovery for a falsely completed task.\n if (type === 'reopen') return status === 'completed';\n return status === 'pending' || status === 'running' || status === 'blocked';\n}\n\nfunction taskError(\n issueId: string,\n itemId: string,\n operation: TaskOperationType,\n currentStatus: string,\n owner?: string,\n extra?: string,\n): TaskStatusChangeError {\n const ownerText = owner ? ` The current claim owner is ${owner}.` : '';\n const recovery = `Run \\`pan task show ${issueId} ${itemId}\\` to inspect the current task state before retrying.`;\n return new TaskStatusChangeError(\n `Task ${itemId} on ${issueId} is ${currentStatus}, so ${operation} is not allowed.${ownerText}${extra ? ` ${extra}` : ''} ${recovery}`,\n { issueId, itemId, operation, currentStatus, owner },\n );\n}\n\nfunction archiveClaim(\n record: PanIssueRecord,\n itemId: string,\n outcome: TaskClaimHistoryEntry['outcome'],\n reason: string | undefined,\n forced: boolean,\n now: string,\n): void {\n const claim = record.tasks?.claims[itemId];\n if (!claim || !record.tasks) return;\n const entry: TaskClaimHistoryEntry = { ...claim, itemId, outcome, reason, forced: forced || undefined, releasedAt: now };\n record.tasks.claimHistory = [...(record.tasks.claimHistory ?? []), entry].slice(-50);\n delete record.tasks.claims[itemId];\n}\n\nexport async function applyTaskStatusChange(\n project: ProjectConfig,\n issueId: string,\n operation: TaskStatusChange,\n): Promise<TaskStatusChangeResult> {\n const normalizedIssueId = issueId.toUpperCase();\n const writerId = operation.writerId ?? process.env.OVERDECK_AGENT_ID ?? `cli-${process.pid}@${hostname()}`;\n let result: TaskStatusChangeResult | undefined;\n\n await updateIssueRecord(project, normalizedIssueId, (record) => {\n const workspacePath = join(project.path, 'workspaces', `feature-${normalizedIssueId.toLowerCase()}`);\n const planPath = findPlanSync(workspacePath);\n if (!planPath) throw new Error(`The xBRIEF for ${normalizedIssueId} is missing or unreadable. Return the issue to planning before changing task state.`);\n const doc = applyStatusOverrides(readPlanSync(planPath), record.statusOverrides ?? {});\n const item = doc.plan.items.find(({ id }) => id === operation.itemId);\n if (!item) throw new Error(`Task ${operation.itemId} does not exist in the immutable xBRIEF for ${normalizedIssueId}. Return the issue to planning to change scope.`);\n\n const currentStatus = item.status;\n const currentSequence = record.tasks?.sequence ?? 0;\n const existingClaim = record.tasks?.claims[operation.itemId];\n const nextStatus = statusFor(operation.type);\n const subItems = subItemsOf(item);\n const subIds = operation.subItemIds?.length\n ? new Set(operation.subItemIds)\n : new Set(\n operation.type === 'done' || operation.type === 'cancel' || operation.type === 'reopen'\n ? subItems.map(({ id }) => id)\n : [],\n );\n const repeatedCancellation = operation.type === 'cancel' && currentStatus === 'cancelled';\n if (operation.expectedSequence !== undefined && operation.expectedSequence !== currentSequence) {\n throw new TaskStatusChangeError(\n `Task sequence conflict for ${normalizedIssueId}: expected ${operation.expectedSequence}, but the current sequence is ${currentSequence}. Run \\`pan task next ${normalizedIssueId}\\` and retry with the new sequence.`,\n { issueId: normalizedIssueId, itemId: operation.itemId, operation: operation.type, currentStatus, currentSequence },\n );\n }\n if (TERMINAL.has(currentStatus) && !repeatedCancellation && operation.type !== 'reopen') {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId);\n }\n if (operation.force && !operation.reason) {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId, 'A forced transition requires --reason.');\n }\n if ((operation.type === 'block' || operation.type === 'cancel' || operation.type === 'reopen') && !operation.reason) {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId, 'This transition requires --reason.');\n }\n if (repeatedCancellation && subItems.every((subItem) => subItem.status === 'cancelled')) {\n result = { issueId: normalizedIssueId, itemId: operation.itemId, status: 'cancelled', sequence: currentSequence, idempotent: true };\n return record;\n }\n if (operation.type === 'claim' && currentStatus === 'running' && existingClaim?.writerId === writerId) {\n result = { issueId: normalizedIssueId, itemId: operation.itemId, status: 'running', sequence: currentSequence, claim: existingClaim, idempotent: true };\n return record;\n }\n const ownerRestricted = (operation.type === 'done' || operation.type === 'block' || operation.type === 'cancel') && currentStatus === 'running';\n if (!operation.force && ownerRestricted && existingClaim?.writerId !== writerId) {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId ?? 'missing claim owner');\n }\n if (!operation.force && !repeatedCancellation && !allowedFrom(operation.type, currentStatus)) {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId);\n }\n\n const now = new Date().toISOString();\n record.tasks ??= { sequence: 0, claims: {} };\n record.statusOverrides = { ...(record.statusOverrides ?? {}), [operation.itemId]: nextStatus };\n for (const subId of subIds) record.statusOverrides[subItemOverrideKey(operation.itemId, subId)] = nextStatus;\n\n if (operation.type === 'claim') {\n record.tasks.claims[operation.itemId] = {\n writerId,\n agentId: process.env.OVERDECK_AGENT_ID ?? null,\n pid: process.pid,\n host: hostname(),\n claimedAt: now,\n };\n } else if (existingClaim) {\n archiveClaim(record, operation.itemId, nextStatus === 'completed' ? 'completed' : nextStatus === 'blocked' ? 'blocked' : 'cancelled', operation.reason, operation.force ?? false, now);\n }\n if (operation.reason) {\n record.tasks.statusReasons = {\n ...(record.tasks.statusReasons ?? {}),\n [operation.itemId]: { reason: operation.reason, updatedAt: now, forced: operation.force || undefined },\n };\n }\n record.tasks.sequence = currentSequence + 1;\n result = {\n issueId: normalizedIssueId,\n itemId: operation.itemId,\n status: nextStatus,\n sequence: record.tasks.sequence,\n claim: record.tasks.claims[operation.itemId],\n };\n return record;\n }, { writerId });\n\n if (!result) throw new Error(`Task mutation for ${normalizedIssueId}/${operation.itemId} produced no result.`);\n return result;\n}\n","import { loadConfigSync } from './config-yaml.js';\nimport { getProjectSync, resolveProjectFromIssueSync } from './projects.js';\nimport { readIssueRecordSync } from './pan-dir/record.js';\n\nexport type SwarmMode = 'off' | 'auto' | 'always';\nexport interface SwarmPolicyLayer { mode?: SwarmMode; maxSlots?: number; autoAdvance?: boolean }\nexport interface ResolvedSwarmPolicy {\n mode: SwarmMode;\n maxSlots: number;\n autoAdvance: boolean;\n source: { mode: string; maxSlots: string; autoAdvance: string };\n}\n\nexport function resolveSwarmPolicyLayers(global: SwarmPolicyLayer = {}, project: SwarmPolicyLayer = {}, issue: SwarmPolicyLayer = {}, cli: SwarmPolicyLayer = {}): ResolvedSwarmPolicy {\n const resolve = <K extends keyof SwarmPolicyLayer>(key: K, fallback: NonNullable<SwarmPolicyLayer[K]>) => {\n for (const [source, layer] of [['cli', cli], ['issue', issue], ['project', project], ['global', global]] as const) {\n if (layer[key] !== undefined) return { value: layer[key] as NonNullable<SwarmPolicyLayer[K]>, source };\n }\n return { value: fallback, source: 'default' };\n };\n const mode = resolve('mode', 'off');\n const maxSlots = resolve('maxSlots', 3);\n const autoAdvance = resolve('autoAdvance', true);\n return { mode: mode.value, maxSlots: maxSlots.value, autoAdvance: autoAdvance.value, source: { mode: mode.source, maxSlots: maxSlots.source, autoAdvance: autoAdvance.source } };\n}\n\nexport function resolveSwarmPolicy(issueId?: string, cli: SwarmPolicyLayer = {}): ResolvedSwarmPolicy {\n const global = loadConfigSync().config.swarm;\n if (!issueId) return resolveSwarmPolicyLayers(global, {}, {}, cli);\n const resolved = resolveProjectFromIssueSync(issueId);\n const project = resolved ? getProjectSync(resolved.projectKey) : undefined;\n const issue = project ? readIssueRecordSync(project, issueId)?.swarm?.policy : undefined;\n return resolveSwarmPolicyLayers(global, project?.swarm, issue, cli);\n}\n\nexport function resolveAutomaticSwarmPolicy(issueId: string, swarmEligible: boolean, manual = false) {\n const policy = resolveSwarmPolicy(issueId);\n const spawnForeman = manual || (policy.mode !== 'off' && swarmEligible);\n return {\n policy,\n spawnForeman,\n requireSwarmReadiness: policy.mode === 'always',\n advanceWavesWithoutConfirmation: policy.autoAdvance,\n reason: manual ? 'manual' : policy.mode === 'off' ? 'mode-off' : swarmEligible ? 'eligible' : 'not-ready',\n } as const;\n}\n\nexport function resolveSwarmMaxSlots(issueId: string, configured: number): number {\n return Math.min(Math.max(1, Math.floor(configured)), Math.max(1, Math.floor(resolveSwarmPolicy(issueId).maxSlots)));\n}\n","import { existsSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { listProjectsSync } from '../projects.js';\n\nexport interface FeatureWorkspace {\n issueId: string;\n workspacePath: string;\n projectPath: string;\n}\n\nexport interface ListFeatureWorkspacesOptions {\n includeSlotWorkspaces?: boolean;\n}\n\nexport function listFeatureWorkspaces(options: ListFeatureWorkspacesOptions = {}): FeatureWorkspace[] {\n const includeSlotWorkspaces = options.includeSlotWorkspaces ?? true;\n const projects = listProjectsSync();\n const workspaces: FeatureWorkspace[] = [];\n\n for (const { config: projectConfig } of projects) {\n const workspacesRoot = join(projectConfig.path, 'workspaces');\n if (!existsSync(workspacesRoot)) continue;\n\n let entries: string[];\n try {\n entries = readdirSync(workspacesRoot, { withFileTypes: true })\n .filter(e => e.isDirectory() && e.name.startsWith('feature-'))\n .filter(e => includeSlotWorkspaces || !/-slot-\\d+$/.test(e.name))\n .map(e => e.name);\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n workspaces.push({\n issueId: entry.replace(/^feature-/, '').toUpperCase(),\n workspacePath: join(workspacesRoot, entry),\n projectPath: projectConfig.path,\n });\n }\n }\n\n return workspaces;\n}\n","import { listSlotAssignments as listDurableSlotAssignments, type SlotReconcileResult } from '../agents/slot-reconcile.js';\nimport type { CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\n\ntype OrphanGcDeps = Pick<CoordinateSwarmSlotsDeps, 'runGitCommand' | 'listSessionNames' | 'listSlotAssignments'>;\n\n/**\n * Reconcile GC for truly-orphaned slots (PAN-2214, completes PAN-2213(c)).\n *\n * An orphan is a slot index with an on-disk worktree or a local slot branch but\n * no slotAssignments entry and no live agent tmux session. Zero-commit-ahead\n * orphans are removed; orphans with unmerged commits are only reported so the\n * operator can push them via `pan swarm reset` — never deleted.\n */\nexport async function gcOrphanedSlots(\n issueId: string,\n workspacePath: string,\n reconciled: SlotReconcileResult,\n deps: OrphanGcDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n const issueLower = issueId.toLowerCase();\n\n const worktreeSlotIndexes = await listSlotWorktreeIndexes(workspacePath, deps.runGitCommand);\n const branchesBySlot = new Map(reconciled.branches.map(branch => [branch.slotIndex, branch.branch]));\n const candidateSlotIndexes = [...new Set([...worktreeSlotIndexes, ...branchesBySlot.keys()])].sort((a, b) => a - b);\n if (candidateSlotIndexes.length === 0) return actions;\n\n const ownedSlotIndexes = new Set([\n ...(deps.listSlotAssignments ?? listDurableSlotAssignments)(issueId, workspacePath).map(assignment => assignment.slotIndex),\n ...reconciled.merged.map(slot => slot.slotIndex),\n ...reconciled.inFlight.map(slot => slot.slotIndex),\n ]);\n const sessionNames = new Set(await deps.listSessionNames());\n\n for (const slotIndex of candidateSlotIndexes) {\n if (ownedSlotIndexes.has(slotIndex)) continue;\n if (sessionNames.has(`agent-${issueLower}-slot-${slotIndex}`)) continue;\n\n const branch = branchesBySlot.get(slotIndex);\n const aheadCount = branch ? await countCommitsAhead(workspacePath, branch, deps.runGitCommand) : 0;\n if (aheadCount === null || aheadCount > 0) {\n actions.push(\n `[swarm] orphan slot ${slotIndex} for ${issueId} preserved: ${branch} has `\n + `${aheadCount ?? 'an unknown number of'} unmerged commit(s) — run \\`pan swarm reset ${issueId}\\` `\n + 'to push it to origin before cleanup',\n );\n continue;\n }\n\n if (worktreeSlotIndexes.has(slotIndex)) {\n await deps.runGitCommand(`git worktree remove --force ${JSON.stringify(`${workspacePath}-slot-${slotIndex}`)}`, workspacePath);\n }\n if (branch) {\n await deps.runGitCommand(`git branch -D ${JSON.stringify(branch)}`, workspacePath);\n }\n actions.push(`[swarm] gc-orphan slot ${slotIndex} for ${issueId}`);\n }\n\n return actions;\n}\n\nasync function listSlotWorktreeIndexes(\n workspacePath: string,\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n): Promise<Set<number>> {\n const indexes = new Set<number>();\n try {\n const result = await runGitCommand('git worktree list --porcelain', workspacePath) as { stdout?: unknown };\n const pattern = new RegExp(`^worktree ${escapeRegExp(workspacePath)}-slot-(\\\\d+)$`);\n for (const line of String(result?.stdout ?? '').split('\\n')) {\n const match = pattern.exec(line.trim());\n if (match) indexes.add(Number(match[1]));\n }\n } catch {\n // Worktree enumeration failed — fall back to branch-derived candidates only.\n }\n return indexes;\n}\n\nasync function countCommitsAhead(\n workspacePath: string,\n branch: string,\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n): Promise<number | null> {\n try {\n const result = await runGitCommand(`git rev-list --count HEAD..${JSON.stringify(branch)}`, workspacePath) as { stdout?: unknown };\n const count = Number(String(result?.stdout ?? '').trim());\n return Number.isFinite(count) ? count : null;\n } catch {\n return null;\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import {\n readIssueRecordForWorkspaceSync,\n type PanIssueRecord,\n type PanIssueSwarmRecord,\n type PanIssueSwarmSlotCompletion,\n} from '../pan-dir/record.js';\nimport { updateIssueRecordForWorkspace } from '../pan-dir/record-update.js';\n\nexport function createMinimalIssueRecord(issueId: string): PanIssueRecord {\n const now = new Date().toISOString();\n return {\n issueId,\n schemaVersion: 2,\n created: now,\n updated: now,\n feedback: [],\n pipeline: {\n issueId,\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n readyForMerge: false,\n updatedAt: now,\n },\n closeOut: {\n usage: {\n byStage: {},\n totals: {},\n },\n merges: [],\n ranOn: '',\n },\n };\n}\n\nexport async function writeSwarmFinalizedAt(workspacePath: string, issueId: string, finalizedAt: string): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n finalizedAt,\n },\n }));\n}\n\nexport function readSwarmHold(workspacePath: string, issueId: string): PanIssueSwarmRecord['hold'] {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())?.swarm?.hold;\n}\n\nexport async function writeSwarmHold(\n workspacePath: string,\n issueId: string,\n hold: NonNullable<PanIssueSwarmRecord['hold']>,\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), (record) => ({\n ...record,\n swarm: { ...(record.swarm ?? {}), hold },\n }));\n}\n\nexport async function clearSwarmHold(workspacePath: string, issueId: string): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), (record) => {\n if (!record.swarm?.hold) return record;\n const swarm = { ...record.swarm };\n delete swarm.hold;\n return { ...record, swarm };\n });\n}\n\nexport function readSwarmInterventionCount(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n failureClass: string,\n): number {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())\n ?.swarm?.interventions?.[String(slotIndex)]?.[failureClass] ?? 0;\n}\n\nexport function readSwarmInterventions(\n workspacePath: string,\n issueId: string,\n): NonNullable<PanIssueSwarmRecord['interventions']> {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())?.swarm?.interventions ?? {};\n}\n\nexport function readSwarmCompletionObservation(\n workspacePath: string,\n issueId: string,\n progressKey: string,\n): NonNullable<PanIssueSwarmRecord['completionObservations']>[string] | undefined {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())\n ?.swarm?.completionObservations?.[progressKey];\n}\n\nexport async function writeSwarmCompletionObservation(\n workspacePath: string,\n issueId: string,\n progressKey: string,\n observation: NonNullable<PanIssueSwarmRecord['completionObservations']>[string],\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), record => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n completionObservations: {\n ...(record.swarm?.completionObservations ?? {}),\n [progressKey]: observation,\n },\n },\n }));\n}\n\nexport async function clearSwarmCompletionObservationRecord(\n workspacePath: string,\n issueId: string,\n progressKey: string,\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), record => {\n const existing = record.swarm?.completionObservations;\n if (!existing?.[progressKey]) return record;\n const completionObservations = { ...existing };\n delete completionObservations[progressKey];\n return { ...record, swarm: { ...(record.swarm ?? {}), completionObservations } };\n });\n}\n\nexport async function writeSwarmForemanTakeover(\n workspacePath: string,\n issueId: string,\n itemId: string,\n slotIndex: number,\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), record => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n reclaimedItems: {\n ...(record.swarm?.reclaimedItems ?? {}),\n [itemId]: { slotIndex, reclaimedAt: new Date().toISOString() },\n },\n },\n }));\n}\n\nexport async function writeSwarmIntervention(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n failureClass: string,\n options: { operator?: boolean } = {},\n): Promise<number | null> {\n let count: number | null = null;\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), (record) => {\n const slotKey = String(slotIndex);\n const interventions = record.swarm?.interventions ?? {};\n const slotInterventions = interventions[slotKey] ?? {};\n const current = slotInterventions[failureClass] ?? 0;\n if (current >= 3 && !options.operator) return record;\n count = current + 1;\n return {\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n interventions: {\n ...interventions,\n [slotKey]: { ...slotInterventions, [failureClass]: count as number },\n },\n },\n };\n });\n return count;\n}\n\n/**\n * PAN-3459: persist the issue-level swarm policy mode. An explicit\n * `pan swarm <id>` start opts the issue into ongoing Deacon coordination;\n * without a persisted issue-level mode, a global `swarm.mode: off` (the\n * default) makes every subsequent patrol skip the issue entirely — wave 1\n * dispatches and the swarm orphans: completed slot branches never merge and\n * remaining items never dispatch.\n */\nexport async function writeSwarmPolicyMode(\n workspacePath: string,\n issueId: string,\n mode: 'off' | 'auto' | 'always',\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n policy: {\n ...(record.swarm?.policy ?? {}),\n mode,\n },\n },\n }));\n}\n\n/**\n * PAN-2372 WI-3 / FR-4: persist a durable per-slot completion marker keyed by\n * `String(slotIndex)`. Read-modify-write preserves every other record field\n * (statusOverrides, slotAssignments, etc.) — see the byte-identical\n * preservation test in deacon-swarm-slot-completion.test.ts.\n */\nexport async function writeSwarmSlotCompletion(\n workspacePath: string,\n issueId: string,\n completion: PanIssueSwarmSlotCompletion,\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n slotCompletions: {\n ...(record.swarm?.slotCompletions ?? {}),\n [String(completion.slotIndex)]: completion,\n },\n },\n }));\n}\n\n/**\n * PAN-2372 WI-3 / FR-6: clear a slot's completion marker once the coordinator\n * (WI-4) has consumed it (merge/requeue). No-op when no marker exists for the\n * slot, so callers can clear unconditionally on the terminal transition.\n */\nexport async function clearSwarmSlotCompletion(workspacePath: string, issueId: string, slotIndex: number): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n const current = readIssueRecordForWorkspaceSync(workspacePath, normalizedIssueId);\n if (!current?.swarm?.slotCompletions || !(String(slotIndex) in current.swarm.slotCompletions)) return;\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => {\n const existing = record.swarm?.slotCompletions;\n if (!existing || !(String(slotIndex) in existing)) return record;\n const next: Record<string, PanIssueSwarmSlotCompletion> = { ...existing };\n delete next[String(slotIndex)];\n return { ...record, swarm: { ...record.swarm, slotCompletions: next } };\n });\n}\n\n/**\n * Consume all durable ownership for a slot in one record update. Merged-slot\n * GC uses this after it removes the worktree and branch so a completion marker\n * cannot outlive the assignment and keep the freed index occupied.\n */\nexport async function clearSwarmSlotOwnership(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n _itemId?: string,\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => {\n const swarm = record.swarm ?? {};\n const slotAssignments = (swarm.slotAssignments ?? []).filter(\n assignment => assignment.slotIndex !== slotIndex,\n );\n const slotCompletions = { ...(swarm.slotCompletions ?? {}) };\n delete slotCompletions[String(slotIndex)];\n return { ...record, swarm: { ...swarm, slotAssignments, slotCompletions } };\n });\n}\n\nexport async function releaseBlockedSwarmSlot(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n itemId: string,\n branch?: string,\n archived?: { archivedBranch: string; archivedWorktree: string; replacementBranch: string; releasedAt: string },\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => {\n const swarm = record.swarm ?? {};\n const slotAssignments = (swarm.slotAssignments ?? []).filter(\n assignment => assignment.slotIndex !== slotIndex,\n );\n const slotCompletions = { ...(swarm.slotCompletions ?? {}) };\n delete slotCompletions[String(slotIndex)];\n return {\n ...record,\n swarm: {\n ...swarm,\n slotAssignments,\n slotCompletions,\n releasedBlockedSlots: {\n ...(swarm.releasedBlockedSlots ?? {}),\n [String(slotIndex)]: {\n slotIndex,\n itemId,\n branch,\n ...archived,\n releasedAt: archived?.releasedAt ?? new Date().toISOString(),\n },\n },\n },\n };\n });\n}\n\nexport async function clearReleasedBlockedSwarmSlot(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), record => {\n const releasedBlockedSlots = { ...(record.swarm?.releasedBlockedSlots ?? {}) };\n delete releasedBlockedSlots[String(slotIndex)];\n return { ...record, swarm: { ...(record.swarm ?? {}), releasedBlockedSlots } };\n });\n}\n\n/**\n * Drop every superseded-attempt record (PAN-3694). A work-preserving swarm\n * reset removes all slot worktrees and branches, so the slot indexes the\n * superseded attempts occupied are genuinely free — but\n * `applySupersededSlotHighWater` kept reserving indexes 1..high-water,\n * leaving a fresh swarm able to dispatch only high-water+1. The archived\n * branches remain on origin; only the index-blocking record is cleared.\n */\nexport async function clearSupersededSwarmAttempts(workspacePath: string, issueId: string): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n supersededAttempts: [],\n },\n }));\n}\n\n/**\n * PAN-2372 WI-3 / FR-4, FR-5: write the durable slot-completion marker and read\n * it straight back to confirm it persisted. Returns true only when the keyed\n * marker exists on disk with a matching agentId. The slot `pan done` caller MUST\n * refuse to mark the slot done when this returns false — that is the whole point\n * of the issue: a slot used to finish without durably recording completion, so\n * the coordinator could not observe it. Keeping the write+verify here (not inline\n * in the CLI command) keeps the god-file done.ts from growing and co-locates the\n * swarm-record mechanics with the other slot-completion door functions.\n */\nexport async function persistAndVerifySwarmSlotCompletion(\n workspacePath: string,\n issueId: string,\n completion: PanIssueSwarmSlotCompletion,\n): Promise<boolean> {\n await writeSwarmSlotCompletion(workspacePath, issueId, completion);\n const reread = readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase());\n const persisted = reread?.swarm?.slotCompletions?.[String(completion.slotIndex)];\n return Boolean(persisted && persisted.agentId === completion.agentId);\n}\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { messageAgent } from '../agents/messaging.js';\nimport type { ReconciledSlotItem } from '../agents/slot-reconcile.js';\nimport { isStatePlaneOnlyStatus } from '../state-plane.js';\nimport { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\nimport { loadCloisterConfigSync, type SwarmInferCompletionMode } from './config.js';\nimport type { ClassifiedSwarmSlot, ClassifyInFlightSlotsOptions, CoordinateSwarmSlotsDeps } from './deacon-swarm.js';\nimport {\n clearSwarmCompletionObservationRecord,\n readSwarmCompletionObservation,\n writeSwarmCompletionObservation,\n} from './deacon-swarm-record.js';\n\nconst execAsync = promisify(exec);\ninterface SlotCompletionObservation {\n signature: string;\n nudged: boolean;\n consecutiveDoneCount: number;\n}\n\nexport interface DoneWithoutSignalObservation {\n commitTime: number | null;\n outputDigest: string;\n progressKey: string;\n stalledForMs: number;\n}\n\nexport async function classifyDoneWithoutSignal(\n slot: ReconciledSlotItem,\n deps: Partial<Pick<CoordinateSwarmSlotsDeps,\n | 'getSlotBranchAheadCount'\n | 'isSlotWorktreeClean'\n | 'sendCompletionNudge'\n | 'readCompletionObservation'\n | 'writeCompletionObservation'\n | 'clearCompletionObservation'>>,\n options: ClassifyInFlightSlotsOptions,\n observation: DoneWithoutSignalObservation,\n): Promise<ClassifiedSwarmSlot | null> {\n const mode = options.inferCompletion ?? 'off';\n if (mode === 'off' || !slot.agentId || !slot.branch || !options.workspacePath || !options.issueId) {\n if (options.workspacePath && options.issueId) {\n await (deps.clearCompletionObservation ?? clearSwarmCompletionObservationRecord)(\n options.workspacePath,\n options.issueId,\n observation.progressKey,\n );\n }\n return null;\n }\n\n const aheadCount = await (deps.getSlotBranchAheadCount ?? defaultGetSlotBranchAheadCount)(\n options.workspacePath,\n options.issueId,\n slot.branch,\n ).catch(() => 0);\n const clean = await (deps.isSlotWorktreeClean ?? defaultIsSlotWorktreeClean)(\n `${options.workspacePath}-slot-${slot.slotIndex}`,\n ).catch(() => false);\n if (aheadCount < 1 || !clean) {\n await (deps.clearCompletionObservation ?? clearSwarmCompletionObservationRecord)(\n options.workspacePath,\n options.issueId,\n observation.progressKey,\n );\n return null;\n }\n\n const signature = `${observation.commitTime ?? 'none'}:${observation.outputDigest}:${aheadCount}:clean`;\n const previous = (deps.readCompletionObservation ?? readSwarmCompletionObservation)(\n options.workspacePath,\n options.issueId,\n observation.progressKey,\n );\n const current: SlotCompletionObservation = {\n signature,\n nudged: previous?.signature === signature ? previous.nudged : false,\n consecutiveDoneCount: previous?.signature === signature ? previous.consecutiveDoneCount + 1 : 1,\n };\n const actions: string[] = [];\n const normalizedIssueId = options.issueId.toUpperCase();\n\n if (!current.nudged) {\n if (deps.sendCompletionNudge) await deps.sendCompletionNudge(slot.agentId, normalizedIssueId);\n current.nudged = true;\n actions.push(`[swarm] nudged slot ${slot.slotIndex} (item ${slot.itemId}) for ${normalizedIssueId}: run pan done ${normalizedIssueId}`);\n }\n\n await (deps.writeCompletionObservation ?? writeSwarmCompletionObservation)(\n options.workspacePath,\n options.issueId,\n observation.progressKey,\n current,\n );\n return {\n ...slot,\n lifecycle: 'awaiting-completion-signal',\n reason: 'no-progress-timeout',\n stalledForMs: observation.stalledForMs,\n signal: 'completion-nudge',\n actions,\n };\n}\n\nexport async function classifyDurableReadySlot(\n slot: ReconciledSlotItem,\n deps: Partial<Pick<CoordinateSwarmSlotsDeps, 'getSlotBranchAheadCount' | 'isSlotWorktreeClean'>>,\n options: ClassifyInFlightSlotsOptions,\n): Promise<ClassifiedSwarmSlot | null> {\n if (!slot.branch || !options.workspacePath || !options.issueId) return null;\n\n const aheadCount = await (deps.getSlotBranchAheadCount ?? defaultGetSlotBranchAheadCount)(\n options.workspacePath,\n options.issueId,\n slot.branch,\n ).catch(() => 0);\n if (aheadCount < 1) return null;\n\n const clean = await (deps.isSlotWorktreeClean ?? defaultIsSlotWorktreeClean)(\n `${options.workspacePath}-slot-${slot.slotIndex}`,\n ).catch(() => false);\n if (!clean) return null;\n\n return { ...slot, lifecycle: 'ready-to-merge', exitStatus: 0, signal: 'inferred' };\n}\n\nexport function resetSwarmCompletionInferenceForTests(): void {\n // Completion inference is record-backed; there is no process-local state to reset.\n}\n\nexport async function clearSwarmCompletionObservation(\n workspacePath: string,\n issueId: string,\n progressKey: string,\n deps: Partial<Pick<CoordinateSwarmSlotsDeps, 'clearCompletionObservation'>>,\n): Promise<void> {\n await (deps.clearCompletionObservation ?? clearSwarmCompletionObservationRecord)(workspacePath, issueId, progressKey);\n}\n\nexport function swarmInferCompletionMode(): SwarmInferCompletionMode {\n const raw = process.env.PAN_SWARM_INFER_COMPLETION ?? loadCloisterConfigSync().swarm?.infer_completion;\n // PAN-2372 WI-5 / FR-8: default to 'auto' (nudge once per stable observation).\n // PAN-3691: 'auto' no longer infers ready-to-merge for a LIVE agent from\n // clean/ahead branch state alone — a live slot converges only through its\n // durable completion signal. Explicit 'nudge' / 'off' in config.yaml or\n // PAN_SWARM_INFER_COMPLETION still parse; 'nudge' and 'auto' now behave the\n // same for live agents.\n return raw === 'off' || raw === 'nudge' || raw === 'auto' ? raw : 'auto';\n}\n\nexport async function defaultGetSlotBranchAheadCount(\n workspacePath: string,\n issueId: string,\n branch: string,\n): Promise<number> {\n const baseBranch = `feature/${issueId.toLowerCase()}`;\n const slotWorkspace = branch.match(/-slot-(\\d+)(?:-attempt-\\d+)?$/)\n ? `${workspacePath}-slot-${branch.match(/-slot-(\\d+)(?:-attempt-\\d+)?$/)![1]}`\n : workspacePath;\n const roots = resolveWorkspaceRepoRootsSync(issueId, slotWorkspace);\n let total = 0;\n for (const root of roots) {\n if (root.degradedPolyrepo) return 0;\n const { stdout } = await execAsync(\n `git rev-list --count ${JSON.stringify(baseBranch)}..${JSON.stringify(branch)}`,\n { cwd: root.dir },\n );\n if (!/^\\d+$/.test(stdout.trim())) return 0;\n total += Number(stdout.trim());\n }\n return total;\n}\n\nexport async function defaultIsSlotWorktreeClean(slotWorkspacePath: string): Promise<boolean> {\n const match = /feature-([a-z]+-\\d+)-slot-\\d+$/.exec(slotWorkspacePath);\n const roots = match ? resolveWorkspaceRepoRootsSync(match[1].toUpperCase(), slotWorkspacePath) : [];\n if (roots.some(root => root.degradedPolyrepo)) return false;\n const statuses = roots.length > 0\n ? await Promise.all(roots.map(root => execAsync('git status --porcelain', { cwd: root.dir }).then(result => result.stdout)))\n : [(await execAsync('git status --porcelain', { cwd: slotWorkspacePath })).stdout];\n // PAN-2372 WI-6 / FR-9: treat state-plane-only dirt (.pan/continue.json, .pan/records/...,\n // the workspace record door) as clean. The swarm writes durable state to those paths on the\n // permanent plane, so their presence must not block a slot from being inferred complete.\n // isStatePlaneOnlyStatus already returns true for empty porcelain (vacuous every()), so one\n // shared classifier covers both cases — no local path list here. See docs/STATE-PLANE-COMMIT-POLICY.md.\n return statuses.every(isStatePlaneOnlyStatus);\n}\n\nexport async function defaultSendCompletionNudge(agentId: string, issueId: string): Promise<void> {\n await messageAgent(\n agentId,\n `You appear to have committed clean slot work but have not signaled completion. If the slot is complete, run exactly:\\n\\npan done ${issueId}`,\n 'deacon:swarm-completion-inference',\n );\n}\n","import { existsSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { Effect } from 'effect';\nimport type { ReconciledSlotItem } from '../agents/slot-reconcile.js';\nimport { getAgentStateSync } from '../agents/agent-state.js';\nimport { stopAgent } from '../agents/termination.js';\nimport {\n resolveSlotWorkspaceWorktreesSync,\n resolveWorkspaceRepoRootsSync,\n type NestedSlotWorktree,\n type SlotWorkspaceWorktrees,\n type WorkspaceRepoRoot,\n} from '../project-repos.js';\nimport type { CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\n\nconst MERGED_LIVE_SLOT_IDLE_MS = 30 * 60 * 1000;\n\nexport async function reapMergedSlotAgent(\n issueId: string,\n slot: Pick<ReconciledSlotItem, 'slotIndex' | 'agentId'>,\n stopSlotAgent: (agentId: string) => Promise<void> = id => Effect.runPromise(stopAgent(id)),\n): Promise<string> {\n const agentId = slot.agentId ?? `agent-${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n try {\n await stopSlotAgent(agentId);\n return `[swarm] reaped merged agent ${agentId}`;\n } catch (err) {\n return `[swarm] could not reap merged agent ${agentId}: ${err instanceof Error ? err.message : String(err)}`;\n }\n}\n\nexport interface MergedSlotGcResult {\n actions: string[];\n /**\n * Merged-status slots that were NOT fully cleaned up this pass (PAN-3695).\n * Their items read as completed in the plan overlay, but the nested work is\n * not yet integrated and pushed — downstream dispatch and finalization must\n * be gated on this set.\n */\n uncleared: ReconciledSlotItem[];\n}\n\nexport async function gcMergedSlots(\n issueId: string,\n workspacePath: string,\n slots: ReconciledSlotItem[],\n deps: Parameters<typeof gcMergedSlotsWithStatus>[3],\n): Promise<string[]> {\n return (await gcMergedSlotsWithStatus(issueId, workspacePath, slots, deps)).actions;\n}\n\nexport async function gcMergedSlotsWithStatus(\n issueId: string,\n workspacePath: string,\n slots: ReconciledSlotItem[],\n deps: Pick<CoordinateSwarmSlotsDeps, 'runGitCommand' | 'clearSlotAssignment' | 'listSessionNames'> & {\n slotWorktreeExists?: (path: string) => boolean;\n getAgentLastActivity?: (agentId: string) => string | undefined;\n stopSlotAgent?: (agentId: string) => Promise<void>;\n listSlotWorkspaceWorktrees?: (issueId: string, slotWorkspace: string) => SlotWorkspaceWorktrees;\n listFeatureWorkspaceRepoRoots?: (issueId: string, workspacePath: string) => WorkspaceRepoRoot[];\n removeDirectory?: (path: string) => Promise<void>;\n },\n): Promise<MergedSlotGcResult> {\n const actions: string[] = [];\n const uncleared: ReconciledSlotItem[] = [];\n // A freshly dispatched slot branch points at the feature branch HEAD, so\n // `--merged HEAD` classifies it as merged before the agent's first commit.\n // Without a liveness guard, gc destroys the worktree/branch/assignment under\n // the live agent and the item redispatches at the next index.\n const sessionNames = new Set(await deps.listSessionNames());\n const worktreeExists = deps.slotWorktreeExists ?? existsSync;\n\n for (const slot of slots) {\n if (slot.status !== 'merged') continue;\n\n const agentId = slot.agentId ?? `agent-${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n // PAN-3695: decide liveness up front, but reap the agent only AFTER every\n // nested branch is merged, ancestry-verified, and the workspace removed.\n // Reaping first stranded unmerged nested work with no agent left to answer\n // for it (MIN-888 slot 4).\n let reapAction: string | null = null;\n if (sessionNames.has(agentId)) {\n const completionProven = slot.mergedVia === 'completed-status';\n const lastActivity = (deps.getAgentLastActivity ?? (id => getAgentStateSync(id)?.lastActivity))(agentId);\n const idleFor = lastActivity ? Date.now() - Date.parse(lastActivity) : 0;\n if (!completionProven && (!Number.isFinite(idleFor) || idleFor < MERGED_LIVE_SLOT_IDLE_MS)) {\n actions.push(`[swarm] gc skipped slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: agent session alive`);\n uncleared.push(slot);\n continue;\n }\n reapAction = completionProven\n ? `[swarm] gc reaped merged agent ${agentId}`\n : `[swarm] gc reaped idle merged agent ${agentId}`;\n }\n\n const slotWorkspace = `${workspacePath}-slot-${slot.slotIndex}`;\n const slotBranch = slot.branch ?? `feature/${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n if (worktreeExists(slotWorkspace)) {\n const removed = await removeSlotWorkspace(issueId, workspacePath, slotWorkspace, slotBranch, slot, deps, actions);\n if (!removed) {\n uncleared.push(slot);\n continue;\n }\n }\n // Polyrepo wrapper repos hold no local slot branch (PAN-3695): an absent\n // outer branch is an idempotent success once every nested branch has\n // verified ancestry, not a delete failure that wedges the slot forever.\n const branchExists = await slotBranchExists(deps.runGitCommand, workspacePath, slotBranch);\n if (branchExists === null) {\n actions.push(`[swarm] gc deferred slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: branch state of ${slotBranch} could not be determined`);\n uncleared.push(slot);\n continue;\n }\n if (branchExists) {\n try {\n await deps.runGitCommand(`git branch -D ${JSON.stringify(slotBranch)}`, workspacePath);\n } catch (error) {\n actions.push(`[swarm] gc deferred slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: branch delete failed: ${error instanceof Error ? error.message : String(error)}`);\n uncleared.push(slot);\n continue;\n }\n }\n await deps.clearSlotAssignment(workspacePath, issueId, slot.slotIndex, slot.itemId);\n if (reapAction) {\n try {\n await (deps.stopSlotAgent ?? (id => Effect.runPromise(stopAgent(id))))(agentId);\n actions.push(reapAction);\n } catch (error) {\n actions.push(`[swarm] could not reap merged agent ${agentId}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n actions.push(`[swarm] gc slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}`);\n }\n\n return { actions, uncleared };\n}\n\n/**\n * Whether the outer workspace has a local `slotBranch`, or null when that\n * cannot be determined. `git branch --list` exits 0 with empty output for an\n * absent branch, so a throw is always an indeterminate state, never \"absent\".\n */\nasync function slotBranchExists(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n workspacePath: string,\n slotBranch: string,\n): Promise<boolean | null> {\n try {\n const result = await runGitCommand(`git branch --list ${JSON.stringify(slotBranch)}`, workspacePath) as { stdout?: unknown };\n return String(result?.stdout ?? '').trim().length > 0;\n } catch {\n return null;\n }\n}\n\ntype SlotRemovalDeps = Pick<CoordinateSwarmSlotsDeps, 'runGitCommand'> & {\n listSlotWorkspaceWorktrees?: (issueId: string, slotWorkspace: string) => SlotWorkspaceWorktrees;\n listFeatureWorkspaceRepoRoots?: (issueId: string, workspacePath: string) => WorkspaceRepoRoot[];\n removeDirectory?: (path: string) => Promise<void>;\n};\n\n/**\n * Remove a merged slot's workspace (PAN-3686). A polyrepo slot workspace is an\n * aggregate directory whose registered git worktrees are the nested sub-repo\n * checkouts (<slot>/fe, <slot>/api, …), each owned by a different parent\n * repository. Git cannot remove the aggregate root while nested worktrees\n * live inside it (\"Directory not empty\"), so every nested worktree is\n * detached through the workspace abstraction first, in its owning parent\n * repo.\n *\n * Cleanup is atomic in intent: a read-only preflight proves every nested repo\n * safe AND removable, and decides the aggregate root's removal route, before\n * the first mutation. Discovering an unsafe or unremovable nested checkout\n * after an earlier one was already detached is the Tier-1 partial-cleanup\n * defect the preflight exists to prevent (PAN-3686 post-deploy review). Any\n * preflight failure defers the whole slot with zero removals.\n *\n * Never discards unmerged or locally-modified nested work. PAN-3695: a slot\n * that reached merged status with unmerged nested branches is INTEGRATED here,\n * not just deferred — each unmerged nested slot branch is merged through the\n * canonical nested merge path (`git merge --no-ff` into the per-repo base\n * feature-workspace checkout, same as verifyAndMergeSlot) and ancestry is\n * re-verified repo by repo before any removal. A repo whose merge is pending\n * or failed defers the whole slot with an actionable repo-specific reason;\n * successful per-repo merges are preserved and the next pass retries the rest.\n * Uncommitted nested changes still defer the slot untouched.\n */\nasync function removeSlotWorkspace(\n issueId: string,\n workspacePath: string,\n slotWorkspace: string,\n slotBranch: string,\n slot: Pick<ReconciledSlotItem, 'slotIndex' | 'itemId'>,\n deps: SlotRemovalDeps,\n actions: string[],\n): Promise<boolean> {\n const defer = (reason: string): false => {\n actions.push(`[swarm] gc deferred slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: ${reason}`);\n return false;\n };\n\n const { isPolyrepo, nested } = (deps.listSlotWorkspaceWorktrees ?? resolveSlotWorkspaceWorktreesSync)(issueId, slotWorkspace);\n\n // ── Integration (PAN-3695): merge unmerged nested slot branches through the\n // canonical nested merge path before any removal. A merged-status slot whose\n // nested work was never merged must be merged by the coordinator here —\n // mergeReadySlots only consumes ready-to-merge slots, so a GC-only deferral\n // is terminal and strands the work (MIN-888 slot 4). These merges mutate the\n // per-repo BASE feature checkouts, never the slot workspace being removed.\n if (nested.length > 0) {\n const mergeFailures = await mergeNestedSlotBranches(issueId, workspacePath, slotBranch, nested, deps);\n if (mergeFailures.length > 0) {\n return defer(`nested merge incomplete: ${mergeFailures.join('; ')}`);\n }\n }\n\n // ── Preflight (read-only): no mutations below this line until every check\n // for every nested repo and the aggregate root has passed. ──\n\n for (const worktree of nested) {\n const blocked = await nestedWorktreeDirtyReason(deps.runGitCommand, worktree);\n if (blocked) return defer(`preserving nested work: ${blocked}`);\n }\n\n // Each nested checkout must be a registered worktree of its owning parent\n // repo. The registration is what `git worktree remove` needs to succeed;\n // checking it up front keeps a mid-loop failure from leaving the slot half\n // detached. Nested gitdirs live in their owning parent repos, never under\n // the aggregate, so this lookup does not depend on aggregate metadata that\n // an earlier partial removal may have invalidated.\n for (const worktree of nested) {\n const registered = await isRegisteredWorktree(deps.runGitCommand, worktree.parentRepo, worktree.dir);\n if (registered === null) {\n return defer(`${worktree.repoKey}: worktree registration in parent repo could not be determined`);\n }\n if (!registered) {\n return defer(`${worktree.repoKey}: nested worktree is not registered in its parent repo (an earlier partial cleanup may have detached it); run pan swarm reset ${issueId}`);\n }\n }\n\n // Decide the aggregate root's removal route up front: a registered working\n // tree is removed through git; an unregistered plain directory (polyrepo\n // layouts that register only the nested paths, or a registration lost to an\n // earlier partial cleanup) is removed as a directory after nested detach.\n const aggregateRegistered = await isRegisteredWorktree(deps.runGitCommand, workspacePath, slotWorkspace);\n if (aggregateRegistered === null) {\n return defer('aggregate worktree registration could not be determined');\n }\n\n // ── Mutation: preflight proved every nested worktree safe and removable. ──\n\n const detached: string[] = [];\n for (const worktree of nested) {\n try {\n await deps.runGitCommand(`git worktree remove --force ${JSON.stringify(worktree.dir)}`, worktree.parentRepo);\n detached.push(worktree.repoKey);\n } catch (error) {\n // A failure here escaped preflight (I/O race, lock); report which nested\n // worktrees were already detached so the partial state is on record.\n const partial = detached.length > 0 ? ` (already detached: ${detached.join(', ')})` : '';\n return defer(`nested worktree remove failed in ${worktree.repoKey}${partial}: ${error instanceof Error ? error.message : String(error)}`);\n }\n try {\n await deps.runGitCommand(`git branch -D ${JSON.stringify(slotBranch)}`, worktree.parentRepo);\n } catch (error) {\n // The worktree is already detached; a leftover merged branch is cosmetic.\n actions.push(`[swarm] gc note slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: nested branch ${slotBranch} delete failed in ${worktree.repoKey}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n if (!aggregateRegistered && isPolyrepo) {\n try {\n await (deps.removeDirectory ?? (path => rm(path, { recursive: true, force: true })))(slotWorkspace);\n return true;\n } catch (rmError) {\n return defer(`aggregate directory remove failed: ${rmError instanceof Error ? rmError.message : String(rmError)}`);\n }\n }\n\n try {\n await deps.runGitCommand(`git worktree remove --force ${JSON.stringify(slotWorkspace)}`, workspacePath);\n return true;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // The registration vanished between preflight and removal (or preflight\n // was bypassed): the aggregate root is a plain directory git rightfully\n // refuses to remove.\n if (isPolyrepo && /is not a working tree/.test(message)) {\n try {\n await (deps.removeDirectory ?? (path => rm(path, { recursive: true, force: true })))(slotWorkspace);\n return true;\n } catch (rmError) {\n return defer(`aggregate directory remove failed: ${rmError instanceof Error ? rmError.message : String(rmError)}`);\n }\n }\n return defer(`worktree remove failed: ${message}`);\n }\n}\n\n/**\n * Whether `worktreeDir` is a registered worktree of the repository at\n * `repoDir`, or null when the registration state cannot be determined. The\n * porcelain list always names at least the repository's own main worktree, so\n * a response with no `worktree` lines is an anomaly, not proof of \"absent\".\n * Shared with `pan swarm reset` (PAN-3713), which must distinguish an\n * already-unregistered nested worktree (the desired postcondition) from a\n * genuine removal failure.\n */\nexport async function isRegisteredWorktree(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n repoDir: string,\n worktreeDir: string,\n): Promise<boolean | null> {\n try {\n const result = await runGitCommand('git worktree list --porcelain', repoDir) as { stdout?: unknown };\n const paths = String(result?.stdout ?? '')\n .split('\\n')\n .filter(line => line.startsWith('worktree '))\n .map(line => line.slice('worktree '.length).trim());\n if (paths.length === 0) return null;\n return paths.includes(worktreeDir);\n } catch {\n return null;\n }\n}\n\n/**\n * Merge every nested slot branch that still has commits its per-repo feature\n * branch lacks, through the canonical nested merge path (PAN-3695): the merge\n * runs in the repo's base feature-workspace checkout, then ancestry is\n * re-verified by re-counting `featureBranch..slotBranch` — a merge command\n * exiting 0 is not proof the slot head landed — and the parent feature branch\n * is pushed to origin so the work actually leaves the machine. A dirty base\n * checkout refuses the merge, and a missing or degraded base checkout fails\n * the repo outright — without it, remote durability cannot be verified, even\n * when the local feature branch already contains the slot head. Every repo is\n * attempted even after a sibling fails, so successful merges are preserved and\n * only the failed repos are retried on the next pass. Returns the per-repo\n * failure reasons; an empty list means every nested repo's slot branch is an\n * ancestor of its pushed feature branch.\n */\nasync function mergeNestedSlotBranches(\n issueId: string,\n workspacePath: string,\n slotBranch: string,\n nested: NestedSlotWorktree[],\n deps: SlotRemovalDeps,\n): Promise<string[]> {\n const failures: string[] = [];\n const baseRoots = new Map(\n (deps.listFeatureWorkspaceRepoRoots ?? resolveWorkspaceRepoRootsSync)(issueId, workspacePath)\n .map(root => [root.repoKey, root]),\n );\n for (const worktree of nested) {\n const ahead = await countCommitsAhead(deps.runGitCommand, worktree.parentRepo, worktree.featureBranch, slotBranch);\n if (ahead === null) {\n failures.push(`${worktree.repoKey}: merge state of ${slotBranch} against ${worktree.featureBranch} could not be determined`);\n continue;\n }\n const baseRoot = baseRoots.get(worktree.repoKey);\n // Remote durability must be verified for every nested repo before cleanup —\n // a missing or degraded base checkout makes that impossible, even when the\n // local feature branch already contains the slot head (PAN-3695 review).\n if (!baseRoot || baseRoot.degradedPolyrepo) {\n failures.push(ahead > 0\n ? `${worktree.repoKey}: no base feature-workspace checkout for ${worktree.featureBranch} — cannot merge ${slotBranch} (${ahead} unmerged commit(s))`\n : `${worktree.repoKey}: no base feature-workspace checkout for ${worktree.featureBranch} — cannot verify and push remote durability`);\n continue;\n }\n if (ahead > 0) {\n // Never merge into a dirty base checkout: tracked local changes there are\n // someone's uncommitted work on the parent feature branch.\n const baseDirty = await baseCheckoutDirtyReason(deps.runGitCommand, worktree, baseRoot, slotBranch);\n if (baseDirty) {\n failures.push(baseDirty);\n continue;\n }\n try {\n await deps.runGitCommand(`git merge --no-ff ${JSON.stringify(slotBranch)}`, baseRoot.dir);\n } catch (error) {\n await deps.runGitCommand('git merge --abort', baseRoot.dir).catch(() => {});\n failures.push(`${worktree.repoKey}: ${slotBranch} did not merge cleanly into ${worktree.featureBranch}: ${error instanceof Error ? error.message : String(error)}`);\n continue;\n }\n const remaining = await countCommitsAhead(deps.runGitCommand, worktree.parentRepo, worktree.featureBranch, slotBranch);\n if (remaining !== 0) {\n failures.push(`${worktree.repoKey}: ancestry verification failed — ${slotBranch} still has ${remaining ?? 'an unknown number of'} commit(s) not in ${worktree.featureBranch} after merge`);\n continue;\n }\n }\n // Push the parent feature branch so the merged nested work actually lands\n // on the remote — a local-only merge is invisible to the outer pipeline\n // (MIN-888 needed manual pushes). This also retries a push that failed on\n // an earlier pass: the local merge is already an ancestor, but the\n // origin..feature count still shows the unpushed commits.\n const unpushed = await countCommitsAhead(deps.runGitCommand, baseRoot.dir, `origin/${worktree.featureBranch}`, worktree.featureBranch);\n if (ahead > 0 || unpushed === null || unpushed > 0) {\n try {\n await deps.runGitCommand(`git push origin ${JSON.stringify(worktree.featureBranch)}`, baseRoot.dir);\n } catch (error) {\n failures.push(`${worktree.repoKey}: merged ${slotBranch} into ${worktree.featureBranch} but push to origin failed: ${error instanceof Error ? error.message : String(error)}`);\n continue;\n }\n }\n }\n return failures;\n}\n\n/**\n * Why a base feature-workspace checkout must not receive a merge right now, or\n * null when it is clean. Tracked local modifications block the merge (git would\n * refuse or, worse, entangle someone's uncommitted work); untracked files do\n * not, so they are ignored here.\n */\nasync function baseCheckoutDirtyReason(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n worktree: NestedSlotWorktree,\n baseRoot: WorkspaceRepoRoot,\n slotBranch: string,\n): Promise<string | null> {\n try {\n const status = await runGitCommand('git status --porcelain --untracked-files=no', baseRoot.dir) as { stdout?: unknown };\n if (String(status?.stdout ?? '').trim().length > 0) {\n return `${worktree.repoKey}: base feature checkout of ${worktree.featureBranch} has uncommitted changes — refusing to merge ${slotBranch}`;\n }\n } catch {\n return `${worktree.repoKey}: base feature checkout state of ${worktree.featureBranch} could not be determined — refusing to merge ${slotBranch}`;\n }\n return null;\n}\n\n/**\n * Why a nested slot worktree must be preserved, or null when it is safe to\n * remove. Any tracked change or non-ignored untracked file blocks removal\n * (ignored build artifacts do not). Merge state is handled separately by\n * mergeNestedSlotBranches before this runs.\n */\nasync function nestedWorktreeDirtyReason(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n worktree: NestedSlotWorktree,\n): Promise<string | null> {\n try {\n const status = await runGitCommand('git status --porcelain --untracked-files=all', worktree.dir) as { stdout?: unknown };\n if (String(status?.stdout ?? '').trim().length > 0) {\n return `${worktree.repoKey}: worktree has uncommitted changes`;\n }\n } catch {\n return `${worktree.repoKey}: worktree status could not be determined`;\n }\n return null;\n}\n\nasync function countCommitsAhead(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n parentRepo: string,\n featureBranch: string,\n slotBranch: string,\n): Promise<number | null> {\n try {\n const result = await runGitCommand(\n `git rev-list --count ${JSON.stringify(featureBranch)}..${JSON.stringify(slotBranch)}`,\n parentRepo,\n ) as { stdout?: unknown };\n const stdout = String(result?.stdout ?? '').trim();\n // An empty or non-numeric stdout is not evidence of \"zero commits ahead\" —\n // Number('') === 0 would misread it as safely merged. Only a digits-only\n // payload proves the count.\n if (!/^\\d+$/.test(stdout)) return null;\n return Number(stdout);\n } catch {\n return null;\n }\n}\n","/**\n * PAN-2385 — tiered-execution commit hooks for the swarm dispatch path.\n *\n * The tier-feed and tier-supervisor libraries shipped with PAN-1791/2222 but\n * had no production caller: commits landed and neither the standing crew nor\n * the subscribe-policy supervisor ever heard about them (found live during\n * the first Standing Crew run, PAN-2383). This module is the ignition.\n *\n * Fired from mergeReadySlots after a slot's bead commits merge into the\n * feature workspace:\n * 1. broadcastCommit — every live slot agent hears the rendered diff\n * (ingestion-only; everyone-hears-everything per docs/TIERED-EXECUTION.md).\n * 2. shouldSupervise(item, subscribe) → deliverCommitForReview — the\n * standing supervisor reviews per its subscribe policy. This is\n * independent of supervisor.owns_inspection, which only routes\n * `pan inspect`.\n *\n * Both hooks are best-effort: the commit has already landed, so a delivery\n * failure logs loudly and never blocks the merge loop.\n */\n\nimport { Effect } from 'effect';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { sessionExists } from '../tmux.js';\nimport { loadConfigSync as loadYamlConfig } from '../config-yaml.js';\nimport { resolveTieredExecutionEnabled, type ValidatedTieredExecutionConfig } from '../agents/tier-table.js';\nimport { resolveTier } from '../agents/resolve-tier.js';\nimport { broadcastCommit } from '../agents/tier-feed.js';\nimport {\n deliverCommitForReview,\n loadPrdDraft,\n shouldSupervise,\n spawnTierSupervisor,\n supervisorAgentId,\n} from '../agents/tier-supervisor.js';\nimport { listSlotAssignments } from '../agents/slot-reconcile.js';\nimport type { XBriefDocument, XBriefItem } from '../xbrief/types.js';\n\nconst execAsync = promisify(exec);\n\nexport interface FireTieredCommitHooksOptions {\n issueId: string;\n /** Feature workspace the slot branch just merged into (its HEAD is the commit). */\n workspacePath: string;\n /** The bead the merged commits implement. */\n item: XBriefItem;\n /** The merged plan view — plan.metadata carries the per-issue tiered override. */\n doc: XBriefDocument;\n}\n\nexport interface FireTieredCommitHooksDeps {\n loadConfig: typeof loadYamlConfig;\n getHeadSha: (workspacePath: string) => Promise<string>;\n listAssignments: typeof listSlotAssignments;\n isSessionAlive: (sessionName: string) => Promise<boolean>;\n broadcast: typeof broadcastCommit;\n ensureSupervisor: typeof spawnTierSupervisor;\n deliverReview: typeof deliverCommitForReview;\n loadPrd: typeof loadPrdDraft;\n}\n\nconst defaultDeps: FireTieredCommitHooksDeps = {\n loadConfig: loadYamlConfig,\n getHeadSha: async (workspacePath) =>\n (await execAsync('git rev-parse HEAD', { cwd: workspacePath })).stdout.trim(),\n listAssignments: listSlotAssignments,\n isSessionAlive: (sessionName) => Effect.runPromise(sessionExists(sessionName)),\n broadcast: broadcastCommit,\n ensureSupervisor: spawnTierSupervisor,\n deliverReview: deliverCommitForReview,\n loadPrd: loadPrdDraft,\n};\n\n/** Resolve the tier name a bead belongs to, for the feed header. Falls back to\n * the slot label when the tier table cannot resolve the item (fail-open here is\n * correct: the feed is informational and the commit has already landed). */\nfunction tierNameForItem(\n item: XBriefItem | undefined,\n tiered: ValidatedTieredExecutionConfig,\n slotIndex: number,\n): string {\n if (item) {\n try {\n return resolveTier(item, tiered).tierName;\n } catch {\n // fall through to the slot label\n }\n }\n return `slot-${slotIndex}`;\n}\n\n/**\n * Fire the commit feed + supervisor review for a bead whose commits just\n * merged into the feature workspace. Returns deacon action-log lines.\n */\nexport async function fireTieredCommitHooks(\n options: FireTieredCommitHooksOptions,\n deps: FireTieredCommitHooksDeps = defaultDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n const { issueId, workspacePath, item, doc } = options;\n\n let tiered: ValidatedTieredExecutionConfig | undefined;\n try {\n tiered = deps.loadConfig().config.tieredExecution;\n } catch (err) {\n actions.push(`[tiered] skipped commit hooks for ${issueId}: config load failed (${err instanceof Error ? err.message : String(err)})`);\n return actions;\n }\n if (!tiered || !resolveTieredExecutionEnabled(tiered, doc.plan.metadata)) return actions;\n\n let sha: string;\n try {\n sha = await deps.getHeadSha(workspacePath);\n } catch (err) {\n actions.push(`[tiered] commit hooks for ${issueId} item ${item.id} could not resolve HEAD: ${err instanceof Error ? err.message : String(err)}`);\n return actions;\n }\n\n // ── 1. Broadcast to every live standing slot agent ────────────────────────\n try {\n const itemsById = new Map(doc.plan.items.map((planItem) => [planItem.id, planItem]));\n const assignments = deps.listAssignments(issueId, workspacePath);\n const listeners: Array<{ tierName: string; agentId: string }> = [];\n for (const assignment of assignments) {\n if (!assignment.agentId) continue;\n if (!(await deps.isSessionAlive(assignment.agentId))) continue;\n listeners.push({\n tierName: tierNameForItem(itemsById.get(assignment.itemId), tiered, assignment.slotIndex),\n agentId: assignment.agentId,\n });\n }\n if (listeners.length > 0) {\n const deliveries = await deps.broadcast({\n workspace: workspacePath,\n issueId,\n sha,\n itemTitle: item.title,\n itemId: item.id,\n tiers: listeners,\n feedConfig: tiered.feed,\n });\n const delivered = deliveries.filter((delivery) => delivery.result.ok).length;\n actions.push(`[tiered] broadcast commit ${sha.slice(0, 10)} (item ${item.id}) for ${issueId} to ${delivered}/${listeners.length} standing agents`);\n }\n } catch (err) {\n actions.push(`[tiered] feed broadcast FAILED for ${issueId} item ${item.id}: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n // ── 2. Supervisor review per subscribe policy ─────────────────────────────\n try {\n const supervisor = tiered.supervisor;\n if (supervisor && shouldSupervise(item, supervisor.subscribe)) {\n const agentId = supervisorAgentId(issueId);\n if (!(await deps.isSessionAlive(agentId))) {\n await deps.ensureSupervisor(issueId, supervisor, { workspace: workspacePath });\n actions.push(`[tiered] spawned standing supervisor ${agentId} for ${issueId}`);\n }\n const prdMarkdown = await deps.loadPrd(workspacePath, issueId).catch(() => undefined);\n await deps.deliverReview({\n supervisorAgentId: agentId,\n workspacePath,\n issueId,\n item,\n sha,\n itemId: item.id,\n prdMarkdown,\n });\n actions.push(`[tiered] supervisor review dispatched for ${issueId} item ${item.id} (subscribe=${supervisor.subscribe})`);\n }\n } catch (err) {\n actions.push(`[tiered] supervisor review FAILED for ${issueId} item ${item.id}: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n return actions;\n}\n","/**\n * PAN-2543 shared autonomous re-drive gate.\n *\n * Recovery entry points classify agent gates, apply the intent policy, and\n * consult the cached memory verdict through this one function before acting.\n * Callers still reserve their role-specific concurrency slot after admission.\n */\nimport { existsSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport {\n decideResumeGate,\n getAgentResumeGateBlockReason,\n type AgentState,\n type ResumeGateContext,\n type ResumeGateDecision,\n} from '../agents/agent-state.js';\nimport {\n detectPendingOperatorDecision,\n type PendingOperatorDecision,\n} from '../agents/pending-decision-gate.js';\nimport { getCachedMemoryVerdict } from './memory-verdict-cache.js';\n\nexport const REDRIVE_ENTRY_POINTS = ['dead-session-rework', 'swarm-slot-requeue'] as const;\n\nexport type RedriveGateDecision =\n | { decision: 'proceed'; gateDecision: Extract<ResumeGateDecision, { decision: 'proceed' }> }\n | { decision: 'defer'; reason: string; needsYou?: true };\n\nexport function decideAutonomousRedrive(\n state: Pick<AgentState, 'paused' | 'pausedReason' | 'yieldedByScheduler' | 'troubled' | 'troubledAt' | 'stoppedByUser' | 'consecutiveFailures'>,\n context: ResumeGateContext = {},\n): RedriveGateDecision {\n const gateDecision = decideResumeGate(getAgentResumeGateBlockReason(state), 'autonomous', context);\n if (gateDecision.decision !== 'proceed') {\n return {\n decision: 'defer',\n reason: gateDecision.reason,\n ...(gateDecision.decision === 'block' && gateDecision.needsYou ? { needsYou: true } : {}),\n };\n }\n\n const memoryVerdict = getCachedMemoryVerdict();\n if (memoryVerdict && memoryVerdict.band !== 'ok') {\n return { decision: 'defer', reason: `memory pressure is ${memoryVerdict.band}` };\n }\n return { decision: 'proceed', gateDecision };\n}\n\nexport interface AgentAutonomousRedriveDeps {\n detectPendingOperatorDecision?: (agentId: string) => Promise<PendingOperatorDecision | null>;\n}\n\nexport async function decideAgentAutonomousRedrive(\n state: Pick<AgentState, 'paused' | 'pausedReason' | 'yieldedByScheduler' | 'troubled' | 'troubledAt' | 'stoppedByUser' | 'consecutiveFailures'>,\n agentDir: string,\n owesRework: boolean,\n deps: AgentAutonomousRedriveDeps = {},\n): Promise<RedriveGateDecision> {\n const hasCompletedHandoff = existsSync(join(agentDir, 'completed'))\n || existsSync(join(agentDir, 'completed.processed'));\n const admission = decideAutonomousRedrive(state, { hasCompletedHandoff, owesRework });\n if (admission.decision === 'defer') return admission;\n\n const agentId = basename(agentDir);\n const detectPendingDecision = deps.detectPendingOperatorDecision ?? detectPendingOperatorDecision;\n const pendingDecision = await detectPendingDecision(agentId);\n if (pendingDecision) {\n return {\n decision: 'defer',\n reason: `agent is waiting on an operator decision (${pendingDecision.reason})`,\n needsYou: true,\n };\n }\n\n return admission;\n}\n","import { existsSync } from 'node:fs';\nimport type { ReconciledSlotItem, SlotReconcileResult } from '../agents/slot-reconcile.js';\nimport {\n readIssueRecordForWorkspaceSync,\n type PanIssueRecord,\n type PanIssueSwarmSupersededAttempt,\n} from '../pan-dir/record.js';\nimport { updateIssueRecordForWorkspace } from '../pan-dir/record-update.js';\nimport { createMinimalIssueRecord, clearSwarmSlotCompletion } from './deacon-swarm-record.js';\nimport type { PersistedTaskOperation } from '../xbrief/dag.js';\nimport type { XBriefDocument } from '../xbrief/types.js';\nimport { acknowledgeRecoveryTrip, recordRecoveryFailure } from './recovery-trip.js';\nimport { getAgentDir, getAgentStateSync } from '../agents/agent-state.js';\nimport { decideAgentAutonomousRedrive, decideAutonomousRedrive } from './redrive-gate.js';\n\n/** Patrol GC never removes forensic attempts; configured issue close-out owns teardown. */\nexport const SWARM_SUPERSEDED_RETENTION = 'issue-close-out' as const;\n\nexport interface FailedSlotArchiveDeps {\n runGitCommand: (command: string, cwd: string) => Promise<unknown>;\n clearSlotAssignment: (workspacePath: string, issueId: string, slotIndex: number, itemId?: string) => Promise<void>;\n}\n\ninterface FailedSlotRequeueDeps extends FailedSlotArchiveDeps {\n applyTaskOperationToPlanFile: (\n issueId: string,\n operation: PersistedTaskOperation,\n workspacePath?: string,\n ) => Promise<unknown>;\n decideAgentAutonomousRedrive?: typeof decideAgentAutonomousRedrive;\n decideAutonomousRedrive?: typeof decideAutonomousRedrive;\n recordRecoveryFailure?: typeof recordRecoveryFailure;\n acknowledgeRecoveryTrip?: typeof acknowledgeRecoveryTrip;\n}\ninterface ClassifiedSlot extends ReconciledSlotItem { lifecycle: string; reason?: string }\n\nexport function nextSwarmSlotIndex(record: PanIssueRecord | undefined, reconciled: SlotReconcileResult): number {\n return Math.max(0,\n ...reconciled.branches.map(value => value.slotIndex),\n ...reconciled.agents.map(value => value.slotIndex),\n ...reconciled.inFlight.map(value => value.slotIndex),\n ...(record?.swarm?.slotAssignments ?? []).map(value => value.slotIndex),\n ...(record?.swarm?.supersededAttempts ?? []).map(value => value.slotIndex),\n ) + 1;\n}\n\nexport async function archiveFailedSwarmSlot(\n issueId: string,\n workspacePath: string,\n slot: ReconciledSlotItem & { reason?: string },\n deps: FailedSlotArchiveDeps,\n now = new Date(),\n): Promise<PanIssueSwarmSupersededAttempt> {\n const normalized = issueId.toUpperCase();\n const suffix = now.toISOString().replace(/[-:.TZ]/g, '');\n const archivedStem = `slot-${slot.slotIndex}-failed-${suffix}`;\n const oldWorktree = `${workspacePath}-slot-${slot.slotIndex}`;\n const archivedWorktree = `${workspacePath}-${archivedStem}`;\n const oldBranch = slot.branch ?? `feature/${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n const archivedBranch = `feature/${issueId.toLowerCase()}-${archivedStem}`;\n\n if (existsSync(oldWorktree)) {\n await deps.runGitCommand(`git worktree move ${JSON.stringify(oldWorktree)} ${JSON.stringify(archivedWorktree)}`, workspacePath);\n }\n await deps.runGitCommand(`git branch -m ${JSON.stringify(oldBranch)} ${JSON.stringify(archivedBranch)}`, workspacePath);\n\n const attempt: PanIssueSwarmSupersededAttempt = {\n slotIndex: slot.slotIndex,\n itemId: slot.itemId,\n agentId: slot.agentId,\n branch: oldBranch,\n archivedBranch,\n ...(existsSync(archivedWorktree) ? { archivedWorktree } : {}),\n reason: slot.reason ?? 'failed swarm slot',\n supersededAt: now.toISOString(),\n };\n await updateIssueRecordForWorkspace(workspacePath, normalized, existing => ({\n ...existing,\n swarm: {\n ...(existing.swarm ?? {}),\n supersededAttempts: [...(existing.swarm?.supersededAttempts ?? []), attempt],\n },\n }));\n // PAN-2372 WI-4 / FR-6: this slot is being superseded/requeued — clear its\n // durable completion marker so a fresh attempt on the same slotIndex is not\n // falsely observed as already-completed by classifyInFlightSlots.\n await clearSwarmSlotCompletion(workspacePath, normalized, slot.slotIndex);\n await deps.clearSlotAssignment(workspacePath, normalized, slot.slotIndex, slot.itemId);\n return attempt;\n}\n\nexport async function requeueFailedSwarmSlots(\n issueId: string,\n workspacePath: string,\n classified: ClassifiedSlot[],\n doc: XBriefDocument,\n reconciled: SlotReconcileResult,\n deps: FailedSlotRequeueDeps,\n blockedSlotIndexes: Set<number> = new Set(),\n): Promise<{ doc: XBriefDocument; actions: string[] }> {\n let nextDoc = doc;\n const actions: string[] = [];\n const decideAgentRedrive = deps.decideAgentAutonomousRedrive ?? decideAgentAutonomousRedrive;\n const decideUnownedRedrive = deps.decideAutonomousRedrive ?? decideAutonomousRedrive;\n const recordFailure = deps.recordRecoveryFailure ?? recordRecoveryFailure;\n const acknowledgeTrip = deps.acknowledgeRecoveryTrip ?? acknowledgeRecoveryTrip;\n for (const slot of classified.filter(candidate => candidate.lifecycle === 'failed')) {\n if (blockedSlotIndexes.has(slot.slotIndex)) {\n actions.push(`[swarm] skipped requeue slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: failed-merge block — awaiting operator recovery`);\n continue;\n }\n const pendingDecisionRecoveryPath = 'swarm-slot-pending-operator-decision';\n const pendingDecisionGeneration = `${slot.itemId}:${slot.agentId ?? `slot-${slot.slotIndex}`}`;\n const admission = slot.agentId\n ? await decideAgentRedrive(\n getAgentStateSync(slot.agentId) ?? {},\n getAgentDir(slot.agentId),\n true,\n )\n : decideUnownedRedrive({}, { owesRework: true });\n if (admission.decision === 'defer') {\n if (admission.needsYou) {\n const failure = await recordFailure(\n workspacePath,\n issueId,\n pendingDecisionRecoveryPath,\n pendingDecisionGeneration,\n 1,\n );\n if (failure.emitNeedsYou) {\n actions.push(`[swarm] needs-you ${issueId}: failed slot ${slot.slotIndex} ${admission.reason}`);\n }\n }\n actions.push(`[swarm] deferred failed slot ${slot.slotIndex}: ${admission.reason}`);\n continue;\n }\n await acknowledgeTrip(\n workspacePath,\n issueId,\n pendingDecisionRecoveryPath,\n pendingDecisionGeneration,\n );\n const attempt = await archiveFailedSwarmSlot(issueId, workspacePath, slot, deps);\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'unblock', itemId: slot.itemId, writerId: 'deacon-swarm', reason: `Redispatch after ${slot.reason ?? 'slot failure'}`,\n }, workspacePath);\n reconciled.inFlight = reconciled.inFlight.filter(candidate => candidate.itemId !== slot.itemId);\n reconciled.superseded = [...(reconciled.superseded ?? []), attempt];\n nextDoc = { ...nextDoc, plan: { ...nextDoc.plan, items: nextDoc.plan.items.map(item => item.id === slot.itemId ? { ...item, status: 'pending' as const } : item) } };\n actions.push(`[swarm] archived failed slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}`);\n const failure = await recordFailure(workspacePath, issueId, 'swarm-slot-requeue', slot.itemId);\n if (failure.emitNeedsYou) actions.push(`[swarm] needs-you ${issueId}: slot item ${slot.itemId} failed ${failure.trip.tripCount} times`);\n }\n return { doc: nextDoc, actions };\n}\n\nexport function applySupersededSlotHighWater(\n occupied: Set<number>,\n reconciled: SlotReconcileResult,\n configuredMax: number,\n): number {\n const highWater = Math.max(0, ...(reconciled.superseded ?? []).map(attempt => attempt.slotIndex));\n if (highWater > 0) for (let index = 1; index <= highWater; index++) occupied.add(index);\n return Math.max(configuredMax, highWater + 1);\n}\n","import { execFile } from 'node:child_process';\nimport { rename } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\nimport { ensureRegisteredSlotWorktree } from '../agents/registered-slot-spawn.js';\nimport type { SlotReconcileResult } from '../agents/slot-reconcile.js';\nimport type { XBriefDocument } from '../xbrief/types.js';\nimport { releaseBlockedSwarmSlot } from './deacon-swarm-record.js';\nimport type { ArchivedBlockedSlot, CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport async function prepareReleasedSwarmSlot(\n issueId: string,\n workspacePath: string,\n slotIndex: number,\n itemId: string,\n branch: string,\n): Promise<void> {\n await ensureRegisteredSlotWorktree(issueId, workspacePath, {\n agentId: `agent-${issueId.toLowerCase()}-slot-${slotIndex}`,\n branch,\n workspace: `${workspacePath}-slot-${slotIndex}`,\n slotIndex,\n slotItemId: itemId,\n });\n}\n\nexport async function defaultIsSlotBranchPushed(\n workspacePath: string,\n issueId: string,\n branch: string,\n): Promise<boolean> {\n const slotIndex = branch.match(/-slot-(\\d+)(?:-attempt-\\d+)?$/)?.[1];\n if (!slotIndex) return false;\n const roots = resolveWorkspaceRepoRootsSync(issueId, `${workspacePath}-slot-${slotIndex}`);\n if (roots.length === 0 || roots.some(root => root.degradedPolyrepo)) return false;\n try {\n const durable = await Promise.all(roots.map(async root => {\n await refreshDurableSlotRef(root.dir, branch);\n return true;\n }));\n return durable.every(Boolean);\n } catch {\n return false;\n }\n}\n\nexport async function releaseBlockedSlots(\n issueId: string,\n workspacePath: string,\n doc: XBriefDocument,\n reconciled: SlotReconcileResult,\n deps: Pick<CoordinateSwarmSlotsDeps, 'listSessionNames' | 'isPaneDead' | 'isSlotWorktreeClean'>\n & Partial<Pick<CoordinateSwarmSlotsDeps, 'isSlotBranchPushed' | 'archiveBlockedSlot' | 'prepareReleasedSlot'>>,\n): Promise<string[]> {\n const actions: string[] = [];\n const blockedItemIds = new Set(doc.plan.items.filter(item => item.status === 'blocked').map(item => item.id));\n if (blockedItemIds.size === 0) return actions;\n\n const sessionNames = new Set(await deps.listSessionNames());\n for (const slot of reconciled.inFlight.filter(candidate => blockedItemIds.has(candidate.itemId))) {\n const agentId = slot.agentId ?? `agent-${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n if (sessionNames.has(agentId) && !(await deps.isPaneDead(agentId))) continue;\n if (!slot.branch) {\n actions.push(`[swarm] needs-you ${issueId}: blocked slot ${slot.slotIndex} has no branch; preserving assignment`);\n continue;\n }\n const clean = await deps.isSlotWorktreeClean(`${workspacePath}-slot-${slot.slotIndex}`).catch(() => false);\n const pushed = await (deps.isSlotBranchPushed ?? defaultIsSlotBranchPushed)(\n workspacePath,\n issueId,\n slot.branch,\n ).catch(() => false);\n if (!clean || !pushed) {\n const reason = !clean ? 'uncommitted work' : 'unpushed or unverifiable work';\n actions.push(`[swarm] needs-you ${issueId}: blocked slot ${slot.slotIndex} has ${reason}; preserving assignment`);\n continue;\n }\n const archived = await (deps.archiveBlockedSlot ?? archiveBlockedSwarmSlot)(\n issueId,\n workspacePath,\n slot.slotIndex,\n slot.branch,\n );\n await releaseBlockedSwarmSlot(workspacePath, issueId, slot.slotIndex, slot.itemId, slot.branch, archived);\n await (deps.prepareReleasedSlot ?? prepareReleasedSwarmSlot)(\n issueId,\n workspacePath,\n slot.slotIndex,\n slot.itemId,\n archived.replacementBranch,\n );\n actions.push(`[swarm] released blocked slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: archived as ${archived.archivedBranch} and prepared ${archived.replacementBranch}`);\n }\n reconciled.inFlight = reconciled.inFlight.filter(slot => !blockedItemIds.has(slot.itemId));\n return actions;\n}\n\n/** Preserve a blocked attempt and provision a unique branch identity for slot reuse. */\nexport async function archiveBlockedSwarmSlot(\n issueId: string,\n workspacePath: string,\n slotIndex: number,\n branch: string,\n now = new Date(),\n): Promise<ArchivedBlockedSlot> {\n const releasedAt = now.toISOString();\n const suffix = releasedAt.replace(/[-:.TZ]/g, '');\n const slotWorkspace = `${workspacePath}-slot-${slotIndex}`;\n const archivedWorktree = `${slotWorkspace}-blocked-${suffix}`;\n const archivedBranch = `${branch}-blocked-${suffix}`;\n const replacementBranch = `feature/${issueId.toLowerCase()}-slot-${slotIndex}-attempt-${suffix}`;\n const slotRoots = resolveWorkspaceRepoRootsSync(issueId, slotWorkspace);\n const baseRoots = new Map(resolveWorkspaceRepoRootsSync(issueId, workspacePath)\n .map(root => [root.repoKey, root]));\n\n if (slotRoots.length === 0 || slotRoots.some(root => root.degradedPolyrepo)) {\n throw new Error(`Cannot archive blocked slot ${slotIndex}: repository roots are unavailable.`);\n }\n\n // Copy the freshly queried remote tip to an attempt-specific archive. The\n // original local and remote refs remain untouched; replacement work uses a\n // new branch identity.\n for (const root of slotRoots) {\n const remoteRef = await refreshDurableSlotRef(root.dir, branch);\n await execFileAsync('git', [\n 'push',\n 'origin',\n `${remoteRef}:refs/heads/${archivedBranch}`,\n ], { cwd: root.dir });\n }\n\n await rename(slotWorkspace, archivedWorktree);\n for (const root of slotRoots) {\n const baseRoot = baseRoots.get(root.repoKey);\n if (!baseRoot) throw new Error(`Cannot archive blocked slot ${slotIndex}: missing base repository ${root.repoKey}.`);\n const nestedPath = relative(slotWorkspace, root.dir);\n const archivedRoot = nestedPath ? resolve(archivedWorktree, nestedPath) : archivedWorktree;\n await execFileAsync('git', ['worktree', 'repair', archivedRoot], { cwd: baseRoot.dir });\n await execFileAsync('git', ['branch', archivedBranch, branch], { cwd: baseRoot.dir });\n await execFileAsync('git', ['switch', archivedBranch], { cwd: archivedRoot });\n }\n\n return { archivedBranch, archivedWorktree, replacementBranch, releasedAt };\n}\n\nasync function refreshDurableSlotRef(workspace: string, branch: string): Promise<string> {\n const remoteRef = `refs/remotes/origin/${branch}`;\n await execFileAsync('git', [\n 'fetch',\n '--no-tags',\n 'origin',\n `+refs/heads/${branch}:${remoteRef}`,\n ], { cwd: workspace });\n await execFileAsync('git', [\n 'merge-base',\n '--is-ancestor',\n `refs/heads/${branch}`,\n remoteRef,\n ], { cwd: workspace });\n return remoteRef;\n}\n","import { messageAgent } from '../agents/messaging.js';\nimport { spawnRun } from '../agents/spawn.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { listSessionNamesSync } from '../tmux.js';\nimport { buildWorkAgentPrompt } from './work-agent-prompt.js';\n\nexport interface EnsureSwarmForemanDeps {\n listSessionNamesSync: () => string[];\n messageAgent: typeof messageAgent;\n buildWorkAgentPrompt: typeof buildWorkAgentPrompt;\n spawnRun: typeof spawnRun;\n resolveProjectFromIssueSync: typeof resolveProjectFromIssueSync;\n}\n\nconst defaultDeps: EnsureSwarmForemanDeps = {\n // Keep the binding lazy so importing Deacon does not force unrelated tests\n // with focused tmux mocks to provide this foreman-only dependency.\n listSessionNamesSync: () => listSessionNamesSync(),\n messageAgent,\n buildWorkAgentPrompt,\n spawnRun,\n resolveProjectFromIssueSync,\n};\n\nexport async function ensureSwarmForeman(\n issueId: string,\n workspacePath: string,\n options: { startedBy: string; prompt?: string },\n deps: EnsureSwarmForemanDeps = defaultDeps,\n): Promise<string[]> {\n const issue = issueId.toUpperCase();\n const agentId = `agent-${issue.toLowerCase()}`;\n if (deps.listSessionNamesSync().includes(agentId)) {\n await deps.messageAgent(agentId, options.prompt ?? `Continue managing ${issue} as its swarm foreman. Run pan swarm status ${issue} --json before acting.`, 'pan-swarm');\n return [`[swarm] attached to live foreman ${agentId} for ${issue}`];\n }\n const basePrompt = await deps.buildWorkAgentPrompt({\n issueId: issue,\n env: 'LOCAL',\n workspacePath,\n projectRoot: deps.resolveProjectFromIssueSync(issue)?.projectPath,\n });\n const state = await deps.spawnRun(issue, 'work', {\n workspace: workspacePath,\n prompt: options.prompt ? `${basePrompt}\\n\\n${options.prompt}` : basePrompt,\n foreman: true,\n startedBy: options.startedBy,\n });\n return [`[swarm] spawned foreman ${state.id} for ${issue}`];\n}\n","import { emitActivityEntrySync } from '../activity-logger.js';\nimport { listSlotAssignments, type SlotReconcileResult } from '../agents/slot-reconcile.js';\nimport { readSwarmHold, writeSwarmHold } from './deacon-swarm-record.js';\nimport { workResumeSlotsAvailable } from './concurrency.js';\nimport { ensureSwarmForeman } from './swarm-foreman.js';\n\nexport interface SwarmForemanLivenessDeps {\n listSlotAssignments: (issueId: string, workspacePath: string) => Array<{ slotIndex: number }>;\n readSwarmHold: typeof readSwarmHold;\n workResumeSlotsAvailable: typeof workResumeSlotsAvailable;\n ensureSwarmForeman: typeof ensureSwarmForeman;\n writeSwarmHold: typeof writeSwarmHold;\n emitActivityEntry: typeof emitActivityEntrySync;\n}\n\nconst defaultDeps: SwarmForemanLivenessDeps = {\n listSlotAssignments,\n readSwarmHold,\n workResumeSlotsAvailable,\n ensureSwarmForeman,\n writeSwarmHold,\n emitActivityEntry: emitActivityEntrySync,\n};\n\nconst respawnFailures = new Map<string, number>();\n\nexport async function maintainSwarmForeman(\n issueId: string,\n workspacePath: string,\n reconciled: SlotReconcileResult,\n sessions: readonly string[],\n overrides: Partial<SwarmForemanLivenessDeps> = {},\n allowSpawn = true,\n bootstrap = false,\n): Promise<string[]> {\n const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, value]) => value !== undefined));\n const deps = { ...defaultDeps, ...definedOverrides } as SwarmForemanLivenessDeps;\n const issueLower = issueId.toLowerCase();\n if (!allowSpawn) return [];\n const mergedSlotIndexes = new Set(reconciled.merged.map(slot => slot.slotIndex));\n const active = bootstrap || sessions.some(name => name.startsWith(`agent-${issueLower}-slot-`))\n || reconciled.branches.some(branch => !mergedSlotIndexes.has(branch.slotIndex))\n || deps.listSlotAssignments(issueId, workspacePath).length > 0;\n if (!active) {\n respawnFailures.delete(issueId);\n return [];\n }\n if (sessions.includes(`agent-${issueLower}`) || deps.readSwarmHold(workspacePath, issueId)) return [];\n if (deps.workResumeSlotsAvailable() <= 0) {\n return [`[swarm-janitor] deferred foreman respawn for ${issueId}: resource governor`];\n }\n\n const failures = respawnFailures.get(issueId) ?? 0;\n if (failures >= 3) return [];\n try {\n const prompt = `Resume ${issueId} swarm foreman duties: read .pan/continue.json, run pan swarm status ${issueId} --json, reconcile current state, then continue the wave loop.`;\n const actions = await deps.ensureSwarmForeman(issueId, workspacePath, { startedBy: 'deacon:swarm-janitor', prompt });\n respawnFailures.delete(issueId);\n return actions;\n } catch (error) {\n const count = failures + 1;\n respawnFailures.set(issueId, count);\n const actions = [`[swarm-janitor] foreman respawn failed for ${issueId} (${count}/3): ${error instanceof Error ? error.message : String(error)}`];\n if (count < 3) return actions;\n\n const message = `${issueId} swarm halted after 3 consecutive foreman respawn failures; operator action is required.`;\n await deps.writeSwarmHold(workspacePath, issueId, { reason: message, setBy: 'deacon:swarm-janitor', at: new Date().toISOString() });\n deps.emitActivityEntry({ source: 'cloister', level: 'error', issueId, message });\n actions.push(`[swarm-janitor] ${message}`);\n return actions;\n }\n}\n\nexport function resetForemanRespawnFailuresForTests(): void {\n respawnFailures.clear();\n}\n","import { exec } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { join } from 'path';\nimport { getAgentRuntimeSnapshot } from '../agent-runtime.js';\nimport { messageAgent } from '../agents/messaging.js';\nimport { spawnRun } from '../agents/spawn.js';\nimport { verifyAndMergeSlot } from '../agents/slot-merge.js';\nimport {\n listSlotAssignments as listDurableSlotAssignments,\n reconcileSlotState,\n type ReconciledSlotItem,\n type SlotReconcileResult,\n} from '../agents/slot-reconcile.js';\nimport {\n readIssueRecordForWorkspaceSync,\n getIssueRecordPathForWorkspace,\n type PanIssueRecord,\n type PanIssueSwarmFailedMergeBlock,\n type PanIssueSwarmSlotCompletion,\n} from '../pan-dir/record.js';\nimport { updateIssueRecordForWorkspace } from '../pan-dir/record-update.js';\nimport { findSpecByIssue } from '../pan-dir/specs.js';\nimport { capturePane, isPaneDead, listPaneValues, listSessionNames as listTmuxSessionNames } from '../tmux.js';\nimport {\n blockingParentCount,\n createActiveSlice,\n getDispatchableItems,\n} from '../xbrief/dag.js';\nimport { applyTaskStatusChange } from '../pan-dir/task-door.js';\nimport { getProjectConfigFromWorkspacePath, resolveProjectForIssue } from '../pan-dir/record.js';\nimport { applyStatusOverrides } from '../xbrief/io.js';\nimport { analyzeSwarmReadiness, type SwarmReadinessVerdict } from '../xbrief/swarm-readiness.js';\nimport type { XBriefDocument, XBriefItem } from '../xbrief/types.js';\nimport { getReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { isDeaconGloballyPausedSync } from '../overdeck/control-settings.js';\nimport { resolveAutomaticSwarmPolicy, resolveSwarmMaxSlots } from '../swarm-policy.js';\nimport type { SwarmInferCompletionMode } from './config.js';\nimport {\n countRunningSwarmSlotsForIssue,\n getConcurrencyLimits,\n releaseSwarmSlot,\n tryReserveSwarmSlot,\n type ConcurrencyLimits,\n} from './concurrency.js';\nimport { listFeatureWorkspaces } from './deacon-workspaces.js';\nimport { gcOrphanedSlots } from './deacon-swarm-orphan-gc.js';\nimport {\n classifyDoneWithoutSignal,\n classifyDurableReadySlot,\n clearSwarmCompletionObservation,\n defaultGetSlotBranchAheadCount,\n defaultIsSlotWorktreeClean,\n defaultSendCompletionNudge,\n resetSwarmCompletionInferenceForTests,\n swarmInferCompletionMode,\n} from './deacon-swarm-completion.js';\nimport type { CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\nexport type { CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\nimport { gcMergedSlots, reapMergedSlotAgent } from './deacon-swarm-gc.js';\nimport { gcMergedSlotsAndAdvance } from './deacon-swarm-advance.js';\nimport { clearReleasedBlockedSwarmSlot, clearSwarmSlotCompletion, clearSwarmSlotOwnership, createMinimalIssueRecord, readSwarmHold, writeSwarmForemanTakeover } from './deacon-swarm-record.js';\nimport { fireTieredCommitHooks } from './swarm-tiered-hooks.js';\nimport { applySupersededSlotHighWater, archiveFailedSwarmSlot, requeueFailedSwarmSlots } from './swarm-failed-slot.js';\nimport { archiveBlockedSwarmSlot, defaultIsSlotBranchPushed, prepareReleasedSwarmSlot, releaseBlockedSlots } from './swarm-blocked-slot.js';\nimport { ensureSwarmForeman } from './swarm-foreman.js';\nimport { maintainSwarmForeman, resetForemanRespawnFailuresForTests, type SwarmForemanLivenessDeps } from './swarm-foreman-liveness.js';\n\nexport { gcOrphanedSlots } from './deacon-swarm-orphan-gc.js';\nexport { gcMergedSlots } from './deacon-swarm-gc.js';\nexport { releaseBlockedSlots } from './swarm-blocked-slot.js';\nconst execAsync = promisify(exec);\nconst SLOT_MERGE_REFIRE_COOLDOWN_MS = 5_000;\nconst SWARM_ADVANCE_FAILURE_THRESHOLD = 3;\nconst SWARM_ADVANCE_FAILURE_COOLDOWN_MS = 60_000;\nconst DEFAULT_SWARM_STALL_THRESHOLD_MS = 30 * 60 * 1000;\n\nconst recentSlotMergeFires = new Map<string, number>();\nconst issueAdvanceFailures = new Map<string, { count: number; cooldownUntil: number }>();\nconst failedMergeBlocks = new Map<string, FailedMergeBlock>();\nconst slotProgressObservations = new Map<string, SlotProgressObservation>();\nexport type SwarmRecoveryAction = 'retry' | 'drop' | 'handoff' | 'reclaim';\n\nexport interface FailedMergeBlock {\n issueId: string;\n itemId: string;\n slotIndex: number;\n branch?: string;\n note: string;\n}\n\nexport interface CoordinateSwarmSlotsOptions {\n issueId?: string;\n /** Explicit `pan swarm` bypasses the automatic-selection policy. */\n manual?: boolean;\n}\n\nconst defaultDeps: CoordinateSwarmSlotsDeps = {\n findSpecByIssue,\n listFeatureWorkspaces: () => listFeatureWorkspaces({ includeSlotWorkspaces: false }),\n reconcileSlotState,\n listSessionNames: () => Effect.runPromise(listTmuxSessionNames()),\n isPaneDead: (sessionName) => Effect.runPromise(isPaneDead(sessionName)),\n getPaneExitStatus: async (sessionName) => {\n const values = await Effect.runPromise(listPaneValues(sessionName, '#{pane_dead_status}'));\n const raw = values[0]?.trim();\n if (!raw) return null;\n const status = Number(raw);\n return Number.isFinite(status) ? status : null;\n },\n getAgentRuntimeState: (agentId) => Effect.runPromise(getAgentRuntimeSnapshot(agentId)),\n getPaneOutputDigest: async (sessionName) => Effect.runPromise(capturePane(sessionName, 200)),\n getBranchTipCommitTime: async (workspacePath, branch) => {\n try {\n const { stdout } = await execAsync(`git log -1 --format=%ct ${JSON.stringify(branch)}`, { cwd: workspacePath });\n const seconds = Number(stdout.trim());\n return Number.isFinite(seconds) ? seconds * 1000 : null;\n } catch {\n return null;\n }\n },\n getSlotBranchAheadCount: defaultGetSlotBranchAheadCount,\n isSlotWorktreeClean: defaultIsSlotWorktreeClean,\n isSlotBranchPushed: defaultIsSlotBranchPushed,\n archiveBlockedSlot: archiveBlockedSwarmSlot,\n prepareReleasedSlot: prepareReleasedSwarmSlot,\n sendCompletionNudge: defaultSendCompletionNudge,\n slotWorktreeExists: existsSync,\n verifyAndMergeSlot,\n applyTaskOperationToPlanFile: (issueId, operation, workspacePath = '') => {\n const project = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);\n return applyTaskStatusChange(project, issueId, operation);\n },\n fireTieredCommitHooks,\n recordSlotAssignment,\n clearSlotAssignment,\n runGitCommand: (command, cwd) => execAsync(command, { cwd }),\n registeredSlotCapacityAvailable: (issueId, selectedCount) => registeredSlotCapacityAvailable(issueId, selectedCount),\n tryReserveSwarmSlot,\n releaseSwarmSlot,\n spawnRun,\n getIssueHold: defaultGetIssueHold,\n shouldDispatch: defaultShouldDispatch,\n readSwarmHold,\n getMaxSlotIndex: defaultGetMaxSlotIndex,\n listSlotAssignments: listDurableSlotAssignments,\n listReleasedSlotIndexes: (issueId, workspacePath) => Object.keys(\n readIssueRecordForWorkspaceSync(workspacePath, issueId)?.swarm?.releasedBlockedSlots ?? {},\n ).map(Number),\n getReleasedSlotBranch: (issueId, workspacePath, slotIndex) => readIssueRecordForWorkspaceSync(\n workspacePath,\n issueId,\n )?.swarm?.releasedBlockedSlots?.[String(slotIndex)]?.replacementBranch,\n clearReleasedSlot: clearReleasedBlockedSwarmSlot,\n readStatusOverrides: defaultReadStatusOverrides,\n readSlotCompletion: defaultReadSlotCompletion,\n clearSlotCompletion: clearSwarmSlotCompletion,\n recordForemanTakeover: writeSwarmForemanTakeover,\n ensureSwarmForeman,\n sendStallEvent: (agentId, message) => messageAgent(agentId, message, 'deacon:swarm-stall'),\n resolveAutomaticSwarmPolicy,\n};\n\nfunction defaultGetMaxSlotIndex(): number {\n return Math.max(1, getConcurrencyLimits().reservedSwarmSlots);\n}\n\nfunction defaultGetIssueHold(issueId: string): Pick<ReviewStatus, 'stuck' | 'deaconIgnored' | 'stuckReason'> | null {\n try {\n return getReviewStatusSync(issueId);\n } catch {\n return null;\n }\n}\n\nfunction defaultReadStatusOverrides(workspacePath: string, issueId: string): Record<string, string> | undefined {\n // PAN-2372 WI-4 / FR-7: distinguish an absent record (silent undefined — a\n // brand-new issue that simply has no overrides yet) from an UNREADABLE record\n // (the file exists but won't parse). readIssueRecordForWorkspaceSync returns\n // null for both, so the existsSync check is what separates them. A corrupt\n // record is preserved as a .corrupt-<ts> sidecar by the atomic writer (WI-1);\n // warn so a broken sidecar never silently masks a slot's done-ness.\n const normalized = issueId.toUpperCase();\n const record = readIssueRecordForWorkspaceSync(workspacePath, normalized);\n if (record === null && existsSync(getIssueRecordPathForWorkspace(workspacePath, normalized))) {\n console.warn(`[swarm] record unreadable for ${normalized} — treating as no overrides; see .corrupt sidecar`);\n return undefined;\n }\n return record?.statusOverrides;\n}\n\n/**\n * PAN-2372 WI-4 / FR-6: read a slot's durable completion marker from the record\n * door. Returns undefined when no record or no marker exists. Used by\n * classifyInFlightSlots to recognize a slot whose `pan done` durably recorded\n * completion even when the runtime plane (agent state, tmux session) is gone.\n */\nfunction defaultReadSlotCompletion(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n): PanIssueSwarmSlotCompletion | undefined {\n try {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())?.swarm?.slotCompletions?.[String(slotIndex)];\n } catch {\n return undefined;\n }\n}\n\nfunction defaultShouldDispatch(issueId: string): boolean {\n if (isDeaconGloballyPausedSync()) return false;\n const hold = defaultGetIssueHold(issueId);\n return !(hold?.stuck || hold?.deaconIgnored);\n}\n\nexport type SwarmSlotLifecycle = 'running' | 'ready-to-merge' | 'failed' | 'stalled' | 'awaiting-completion-signal' | 'failed-merge-blocked';\n\nexport interface ClassifyInFlightSlotsOptions {\n workspacePath?: string;\n issueId?: string;\n inferCompletion?: SwarmInferCompletionMode;\n stallThresholdMs?: number;\n now?: number;\n}\n\nexport interface ClassifiedSwarmSlot extends ReconciledSlotItem {\n lifecycle: SwarmSlotLifecycle;\n exitStatus?: number | null;\n reason?: 'missing-agent' | 'vanished-session' | 'pane-exit-nonzero' | 'pane-exit-unknown' | 'no-progress-timeout';\n stalledForMs?: number;\n signal?: 'inferred' | 'completion-nudge' | 'durable-completion' | 'stall-event';\n actions?: string[];\n}\n\ninterface SlotProgressObservation {\n commitTime: number | null;\n outputDigest: string;\n lastProgressAt: number;\n stallNotified?: boolean;\n}\n\ninterface SlotAssignment {\n slotIndex: number;\n itemId: string;\n agentId?: string;\n branch?: string;\n}\n\ntype SlotAssignments = NonNullable<NonNullable<PanIssueRecord['swarm']>['slotAssignments']>;\n\nexport async function coordinateSwarmSlots(\n opts: CoordinateSwarmSlotsOptions = {},\n deps: CoordinateSwarmSlotsDeps = defaultDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n const filterIssueId = opts.issueId?.toUpperCase();\n\n for (const workspace of deps.listFeatureWorkspaces()) {\n const issueId = workspace.issueId.toUpperCase();\n if (filterIssueId && issueId !== filterIssueId) continue;\n const hold = (deps.getIssueHold ?? defaultGetIssueHold)(issueId);\n if (hold?.deaconIgnored) {\n actions.push(`[swarm] skipped ${issueId}: deacon-ignored — operator hold`);\n continue;\n }\n // PAN-2469: `stuck` is a SYSTEM-set failure marker (delivery/verification\n // trouble), not an operator hold — skipping coordination on it froze the\n // whole swarm forever: PAN-2388's slots sat ready-to-merge for hours while\n // the coordinator skipped the issue because the verification-gate deadlock\n // (PAN-2461) had marked it stuck. Coordination (merge/gc/endgame) continues\n // through system-stuck; only operator deacon-ignore fully halts it.\n if (hold?.stuck) {\n actions.push(`[swarm] ${issueId} is system-stuck (${hold.stuckReason ?? 'unknown'}) — coordinating anyway (stuck no longer halts assembly, PAN-2469)`);\n }\n if (isSwarmAdvanceCoolingDown(issueId)) {\n actions.push(`[swarm] deferred ${issueId}: advance backoff active`);\n continue;\n }\n const failedMergeBlocks = getFailedMergeBlocks(issueId, workspace.workspacePath);\n const blockedSlotIndexes = new Set(failedMergeBlocks.map(block => block.slotIndex));\n const blockedItemIds = new Set(failedMergeBlocks.map(block => block.itemId));\n if (failedMergeBlocks.length > 0) {\n const slots = failedMergeBlocks\n .map(block => `slot ${block.slotIndex} (item ${block.itemId})`)\n .join(', ');\n actions.push(`[swarm] blocked slots for ${issueId}: ${slots} — other slots continue; run \\`pan swarm recover ${issueId} <slot>\\``);\n }\n try {\n const spec = await Effect.runPromise(findSpecByIssue(workspace.projectPath, issueId));\n if (!spec) continue;\n const planStatus = spec.document.plan.status;\n if (planStatus === 'completed' || planStatus === 'cancelled') continue;\n const overrides = (deps.readStatusOverrides ?? defaultReadStatusOverrides)(workspace.workspacePath, issueId);\n const doc = overrides && Object.keys(overrides).length > 0\n ? applyStatusOverrides(spec.document, overrides)\n : spec.document;\n const readiness = analyzeSwarmReadiness(doc);\n const slotEligibleCount = readiness.items.filter(item => item.slotEligible).length;\n const dispatchEligible = opts.manual === true && readiness.swarmEligible && slotEligibleCount >= 2;\n if (dispatchEligible) {\n actions.push(`[swarm] considered ${issueId}: swarm eligible`);\n }\n const reconciled = await deps.reconcileSlotState(issueId, workspace.workspacePath, doc);\n if (!dispatchEligible) {\n const hasSlotState = reconciled.merged.length > 0 || reconciled.inFlight.length > 0\n || reconciled.branches.length > 0 || reconciled.agents.length > 0;\n if (!hasSlotState) continue;\n actions.push(`[swarm] considered ${issueId}: endgame (merge/cleanup only)`);\n }\n actions.push(...await releaseBlockedSlots(issueId, workspace.workspacePath, doc, reconciled, deps));\n const classified = await classifyInFlightSlots(reconciled.inFlight, deps, {\n workspacePath: workspace.workspacePath,\n issueId,\n inferCompletion: swarmInferCompletionMode(),\n });\n for (const slot of classified) {\n actions.push(`[swarm] ${issueId} slot ${slot.slotIndex} ${slot.lifecycle}${slot.signal ? ` signal: ${slot.signal}` : ''}`);\n if (slot.actions) actions.push(...slot.actions);\n }\n const requeue = await requeueFailedSwarmSlots(issueId, workspace.workspacePath, classified, doc, reconciled, deps, blockedSlotIndexes);\n actions.push(...requeue.actions);\n actions.push(...await mergeReadySlots(issueId, workspace.workspacePath, doc, classified, deps, blockedSlotIndexes));\n actions.push(...await gcMergedSlotsAndAdvance(issueId, workspace.workspacePath, reconciled, deps, async () => {\n if (!dispatchEligible) return [];\n return dispatchNextWave(issueId, workspace.workspacePath, requeue.doc, reconciled, analyzeSwarmReadiness(requeue.doc), deps, blockedSlotIndexes, blockedItemIds);\n }));\n recordSwarmAdvanceSuccess(issueId);\n } catch (err) {\n recordSwarmAdvanceFailure(issueId);\n console.warn(`[deacon] Error coordinating swarm ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n return actions;\n}\n\nexport async function swarmJanitorPass(deps: CoordinateSwarmSlotsDeps = defaultDeps): Promise<string[]> {\n const actions: string[] = [];\n const sessions = await deps.listSessionNames();\n for (const workspace of deps.listFeatureWorkspaces()) {\n const issueId = workspace.issueId.toUpperCase();\n const spec = await Effect.runPromise((deps.findSpecByIssue ?? findSpecByIssue)(workspace.projectPath, issueId));\n if (!spec) continue;\n const reconciled = await deps.reconcileSlotState(issueId, workspace.workspacePath, spec.document);\n actions.push(`[swarm-janitor] enumerated ${issueId}`);\n actions.push(...await gcMergedSlots(issueId, workspace.workspacePath, reconciled.merged, deps));\n actions.push(...await gcOrphanedSlots(issueId, workspace.workspacePath, reconciled, deps));\n const automatic = (deps.resolveAutomaticSwarmPolicy ?? resolveAutomaticSwarmPolicy)(issueId, analyzeSwarmReadiness(spec.document).swarmEligible);\n actions.push(...await maintainSwarmForeman(issueId, workspace.workspacePath, reconciled, sessions, deps, automatic.policy.mode !== 'off', automatic.spawnForeman));\n const classified = await classifyInFlightSlots(reconciled.inFlight, { ...deps, listSessionNames: async () => sessions }, { issueId, workspacePath: workspace.workspacePath });\n for (const slot of classified.filter(candidate => candidate.signal === 'stall-event')) {\n await deps.sendStallEvent?.(`agent-${issueId.toLowerCase()}`, `[swarm-event] slot ${slot.slotIndex} stalled (no progress ${Math.floor((slot.stalledForMs ?? 0) / 60_000)}m)`);\n actions.push(`[swarm-janitor] notified ${issueId} foreman that slot ${slot.slotIndex} stalled`);\n }\n }\n return actions;\n}\nexport { resetForemanRespawnFailuresForTests };\nexport async function classifyInFlightSlots(\n slots: ReconciledSlotItem[],\n deps: Pick<CoordinateSwarmSlotsDeps, 'listSessionNames' | 'isPaneDead' | 'getPaneExitStatus'>\n & Partial<Pick<\n CoordinateSwarmSlotsDeps,\n | 'getPaneOutputDigest'\n | 'getBranchTipCommitTime'\n | 'getSlotBranchAheadCount'\n | 'isSlotWorktreeClean'\n | 'sendCompletionNudge'\n | 'readCompletionObservation' | 'writeCompletionObservation' | 'clearCompletionObservation'\n | 'readSlotCompletion'\n | 'clearSlotCompletion'\n >> = defaultDeps,\n options: ClassifyInFlightSlotsOptions = {},\n): Promise<ClassifiedSwarmSlot[]> {\n const sessionNames = new Set(await deps.listSessionNames());\n const classified: ClassifiedSwarmSlot[] = [];\n const now = options.now ?? Date.now();\n const stallThresholdMs = options.stallThresholdMs ?? swarmStallThresholdMs();\n\n for (const slot of slots) {\n if (options.workspacePath && options.issueId) {\n const completion = await (deps.readSlotCompletion ?? defaultReadSlotCompletion)(\n options.workspacePath,\n options.issueId,\n slot.slotIndex,\n );\n if (completion) {\n if (completion.itemId === slot.itemId) {\n classified.push({ ...slot, lifecycle: 'ready-to-merge', exitStatus: 0, signal: 'durable-completion' });\n continue;\n }\n await (deps.clearSlotCompletion ?? clearSwarmSlotCompletion)(\n options.workspacePath,\n options.issueId,\n slot.slotIndex,\n );\n }\n }\n\n // PAN-3691: only a DEAD slot may be salvaged from durable git state; a LIVE agent never infers completion from branch state.\n const durableReady = await classifyDurableReadySlot(slot, deps, options);\n if (!slot.agentId) {\n if (durableReady) {\n classified.push(durableReady); continue;\n }\n classified.push({ ...slot, lifecycle: 'failed', reason: 'missing-agent' });\n continue;\n }\n\n // PAN-3720: runtime resolution is NEVER merge authority — not for a live\n // session, and not for a vanished one. Static slot ids cross assignment\n // generations (the Deacon reassigns e.g. agent-min-888-slot-1 to the next\n // work item), so a terminal done|completed snapshot may belong to the\n // PREVIOUS assignment: unsafe while the fresh session is alive, and still\n // unsafe after that session dies with partial commits. Normal completion\n // requires the durable, itemId-guarded slotCompletion marker (checked\n // above). A vanished session recovers only through clean committed branch\n // state (classifyDurableReadySlot); a live session through a zero\n // pane-exit below.\n if (!sessionNames.has(slot.agentId)) {\n if (durableReady) {\n classified.push(durableReady);\n continue;\n }\n classified.push({ ...slot, lifecycle: 'failed', reason: 'vanished-session' });\n continue;\n }\n\n const dead = await deps.isPaneDead(slot.agentId);\n if (!dead) {\n const outputDigest = deps.getPaneOutputDigest\n ? await deps.getPaneOutputDigest(slot.agentId).catch(() => '')\n : '';\n const commitTime = slot.branch && options.workspacePath && deps.getBranchTipCommitTime\n ? await deps.getBranchTipCommitTime(options.workspacePath, slot.branch).catch(() => null)\n : null;\n const progressKey = slotProgressKey(slot);\n const previous = slotProgressObservations.get(progressKey);\n if (\n !previous\n || previous.commitTime !== commitTime\n || previous.outputDigest !== outputDigest\n ) {\n slotProgressObservations.set(progressKey, { commitTime, outputDigest, lastProgressAt: now });\n if (options.workspacePath && options.issueId) {\n await clearSwarmCompletionObservation(options.workspacePath, options.issueId, progressKey, deps);\n }\n classified.push({ ...slot, lifecycle: 'running' });\n continue;\n }\n\n const stalledForMs = now - previous.lastProgressAt;\n if (stalledForMs > stallThresholdMs) {\n const awaitingSignal = await classifyDoneWithoutSignal(slot, deps, options, {\n commitTime,\n outputDigest,\n progressKey,\n stalledForMs,\n });\n if (awaitingSignal) {\n classified.push(awaitingSignal);\n continue;\n }\n classified.push({\n ...slot,\n lifecycle: 'stalled',\n reason: 'no-progress-timeout',\n stalledForMs,\n ...(!previous.stallNotified ? { signal: 'stall-event' as const } : {}),\n });\n previous.stallNotified = true;\n continue;\n }\n classified.push({ ...slot, lifecycle: 'running' });\n continue;\n }\n\n const exitStatus = await deps.getPaneExitStatus(slot.agentId);\n if (exitStatus === 0) {\n classified.push({ ...slot, lifecycle: 'ready-to-merge', exitStatus });\n continue;\n }\n\n classified.push({\n ...slot,\n lifecycle: 'failed',\n exitStatus,\n reason: exitStatus === null ? 'pane-exit-unknown' : 'pane-exit-nonzero',\n });\n }\n\n return classified;\n}\n\nfunction slotProgressKey(slot: ReconciledSlotItem): string {\n return slot.agentId ?? slot.branch ?? `${slot.itemId}:${slot.slotIndex}`;\n}\n\nfunction swarmStallThresholdMs(): number {\n const raw = process.env.PAN_SWARM_STALL_THRESHOLD_MS;\n if (!raw) return DEFAULT_SWARM_STALL_THRESHOLD_MS;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_SWARM_STALL_THRESHOLD_MS;\n}\n\nexport async function mergeReadySlots(\n issueId: string,\n workspacePath: string,\n doc: XBriefDocument,\n slots: ClassifiedSwarmSlot[],\n deps: Pick<CoordinateSwarmSlotsDeps, 'verifyAndMergeSlot' | 'applyTaskOperationToPlanFile' | 'fireTieredCommitHooks'> & { stopSlotAgent?: (agentId: string) => Promise<void> } = defaultDeps,\n blockedSlotIndexes: Set<number> = new Set(),\n): Promise<string[]> {\n const actions: string[] = [];\n const itemsById = new Map(doc.plan.items.map(item => [item.id, item]));\n const planPath = join(workspacePath, '.pan', 'spec.vbrief.json');\n\n for (const slot of slots) {\n if (slot.lifecycle !== 'ready-to-merge') continue;\n\n if (blockedSlotIndexes.has(slot.slotIndex)) {\n actions.push(`[swarm] skipped merge slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: failed-merge block — awaiting \\`pan swarm recover\\``);\n continue;\n }\n\n const item = itemsById.get(slot.itemId);\n if (!item) {\n // PAN-2469: this skip was silent — a slot whose itemId drifted from the\n // current spec (e.g. after a re-plan) sat ready-to-merge forever with no\n // trace. Say so every pass.\n actions.push(`[swarm] cannot merge slot ${slot.slotIndex} for ${issueId}: itemId \"${slot.itemId}\" not in the current plan (spec drift?) — needs operator attention`);\n continue;\n }\n\n const branchKey = slot.branch ?? `feature/${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n if (isSlotMergeCoolingDown(branchKey)) {\n actions.push(`[swarm] skipped merge slot ${slot.slotIndex} (item ${item.id}) for ${issueId}: refire cooldown`);\n continue;\n }\n\n recordSlotMergeFire(branchKey);\n const result = await deps.verifyAndMergeSlot({\n issueId,\n featureWorkspace: workspacePath,\n slotBranch: slot.branch,\n slotWorkspace: `${workspacePath}-slot-${slot.slotIndex}`,\n }, slot.slotIndex, item);\n if (result.merged) {\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'done',\n itemId: item.id,\n writerId: 'deacon-swarm',\n }, workspacePath);\n // PAN-2372 WI-4 / FR-6: the durable marker has done its job — the slot is\n // merged. Clear it so the same slotIndex can be re-dispatched later without\n // a stale \"completed\" marker falsely surfacing as ready-to-merge.\n await clearSwarmSlotCompletion(workspacePath, issueId, slot.slotIndex);\n actions.push(await reapMergedSlotAgent(issueId, slot, deps.stopSlotAgent), `[swarm] merged slot ${slot.slotIndex} (item ${item.id}) for ${issueId}`);\n // PAN-2385: commits just landed — fire the tiered feed + supervisor review (best-effort).\n try {\n actions.push(...await deps.fireTieredCommitHooks({ issueId, workspacePath, item, doc }));\n } catch (err) {\n actions.push(`[tiered] commit hooks threw for ${issueId} item ${item.id}: ${err instanceof Error ? err.message : String(err)}`);\n }\n continue;\n }\n\n if (result.conflicts) {\n await recordFailedMergeBlock({\n issueId,\n itemId: item.id,\n slotIndex: slot.slotIndex,\n branch: slot.branch,\n note: `Slot branch ${slot.branch ?? slot.slotIndex} did not merge cleanly`,\n }, workspacePath);\n actions.push(`[swarm] failed-merge slot ${slot.slotIndex} (item ${item.id}) for ${issueId}`);\n continue;\n }\n\n // Verification failure (merged result fails typecheck/tests) previously\n // fell through with no action at all — the slot silently re-verified on\n // every pass with nobody told why. Surface the failure so the operator\n // or recovery machinery can act on it.\n actions.push(`[swarm] verify-failed slot ${slot.slotIndex} (item ${item.id}) for ${issueId}: ${result.failure ?? 'verification failed'}`);\n }\n\n return actions;\n}\n\nexport function resetSwarmLoopSafetyForTests(): void {\n recentSlotMergeFires.clear();\n issueAdvanceFailures.clear();\n failedMergeBlocks.clear();\n slotProgressObservations.clear();\n resetSwarmCompletionInferenceForTests();\n}\n\nexport function recordSwarmAdvanceFailure(issueId: string, now = Date.now()): void {\n const normalized = issueId.toUpperCase();\n const previous = issueAdvanceFailures.get(normalized);\n const count = (previous?.count ?? 0) + 1;\n issueAdvanceFailures.set(normalized, {\n count,\n cooldownUntil: count >= SWARM_ADVANCE_FAILURE_THRESHOLD\n ? now + SWARM_ADVANCE_FAILURE_COOLDOWN_MS\n : previous?.cooldownUntil ?? 0,\n });\n}\n\nexport function recordSwarmAdvanceSuccess(issueId: string): void {\n issueAdvanceFailures.delete(issueId.toUpperCase());\n}\n\nexport function isSwarmAdvanceCoolingDown(issueId: string, now = Date.now()): boolean {\n const record = issueAdvanceFailures.get(issueId.toUpperCase());\n return record !== undefined && record.cooldownUntil > now;\n}\n\nexport function getFailedMergeBlock(\n issueId: string,\n slotIndex: number,\n workspacePath?: string,\n): FailedMergeBlock | undefined {\n const normalized = issueId.toUpperCase();\n if (workspacePath) {\n const swarm = readIssueRecordForWorkspaceSync(workspacePath, normalized)?.swarm;\n const keyed = swarm?.failedMergeBlocks?.[String(slotIndex)];\n if (keyed) return { ...keyed, issueId: keyed.issueId.toUpperCase() };\n const legacy = swarm?.failedMergeBlock;\n if (legacy && legacy.slotIndex === slotIndex) {\n return { ...legacy, issueId: legacy.issueId.toUpperCase() };\n }\n }\n return failedMergeBlocks.get(`${normalized}:${slotIndex}`);\n}\n\nexport function getFailedMergeBlocks(issueId: string, workspacePath?: string): FailedMergeBlock[] {\n const normalized = issueId.toUpperCase();\n const bySlot = new Map<number, FailedMergeBlock>();\n\n if (workspacePath) {\n const swarm = readIssueRecordForWorkspaceSync(workspacePath, normalized)?.swarm;\n if (swarm?.failedMergeBlock) {\n const legacy = swarm.failedMergeBlock;\n bySlot.set(legacy.slotIndex, { ...legacy, issueId: legacy.issueId.toUpperCase() });\n }\n if (swarm?.failedMergeBlocks) {\n for (const [key, block] of Object.entries(swarm.failedMergeBlocks)) {\n bySlot.set(Number(key), { ...block, issueId: block.issueId.toUpperCase() });\n }\n }\n }\n\n for (const [key, block] of failedMergeBlocks.entries()) {\n if (key.startsWith(`${normalized}:`) && !bySlot.has(block.slotIndex)) {\n bySlot.set(block.slotIndex, { ...block });\n }\n }\n\n return [...bySlot.values()].sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nexport async function recordFailedMergeBlock(block: FailedMergeBlock, workspacePath?: string): Promise<void> {\n const normalizedBlock = { ...block, issueId: block.issueId.toUpperCase() };\n failedMergeBlocks.set(`${normalizedBlock.issueId}:${normalizedBlock.slotIndex}`, normalizedBlock);\n if (workspacePath) {\n await writeSwarmFailedMergeBlock(workspacePath, normalizedBlock.issueId, normalizedBlock.slotIndex, normalizedBlock);\n }\n}\n\nexport async function clearFailedMergeBlock(issueId: string, slotIndex: number, workspacePath?: string): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n failedMergeBlocks.delete(`${normalizedIssueId}:${slotIndex}`);\n if (workspacePath) {\n await writeSwarmFailedMergeBlock(workspacePath, normalizedIssueId, slotIndex, undefined);\n }\n}\n\nexport async function recoverFailedMergeSlot(\n issueId: string,\n workspacePath: string,\n slotIndex: number,\n doc: XBriefDocument,\n action: SwarmRecoveryAction,\n deps: Pick<\n CoordinateSwarmSlotsDeps,\n 'applyTaskOperationToPlanFile'\n | 'clearSlotAssignment'\n | 'recordSlotAssignment'\n | 'registeredSlotCapacityAvailable'\n | 'tryReserveSwarmSlot'\n | 'releaseSwarmSlot'\n | 'spawnRun'\n | 'shouldDispatch'\n | 'getMaxSlotIndex'\n | 'listSlotAssignments'\n | 'runGitCommand'\n | 'recordForemanTakeover'\n > = defaultDeps,\n): Promise<string[]> {\n const normalizedIssueId = issueId.toUpperCase();\n const block = getFailedMergeBlock(normalizedIssueId, slotIndex, workspacePath);\n if (!block) {\n const otherBlocks = getFailedMergeBlocks(normalizedIssueId, workspacePath);\n if (otherBlocks.length === 0) {\n return [`[swarm] no failed-merge slot for ${normalizedIssueId}`];\n }\n const lines = otherBlocks\n .map(b => ` slot ${b.slotIndex} (item ${b.itemId}): ${b.note}`)\n .join('\\n');\n return [\n `[swarm] no failed-merge block for ${normalizedIssueId} slot ${slotIndex}. Currently blocked slots:\\n${lines}`,\n ];\n }\n\n if (action === 'handoff') {\n block.note = `Operator handoff required for slot ${block.slotIndex} (item ${block.itemId})`;\n await recordFailedMergeBlock(block, workspacePath);\n return [`[swarm] handoff paused ${normalizedIssueId} slot ${block.slotIndex} (item ${block.itemId})`];\n }\n\n if (action === 'drop') {\n await deps.applyTaskOperationToPlanFile(normalizedIssueId, {\n type: 'done',\n itemId: block.itemId,\n writerId: 'deacon-swarm',\n reason: 'Dropped failed swarm slot after operator recovery',\n }, workspacePath);\n await clearFailedMergeBlock(normalizedIssueId, block.slotIndex, workspacePath);\n await deps.clearSlotAssignment(workspacePath, normalizedIssueId, block.slotIndex, block.itemId);\n return [`[swarm] dropped failed-merge slot ${block.slotIndex} (item ${block.itemId}) for ${normalizedIssueId}`];\n }\n\n // retry: archive the conflicted attempt so it cannot re-assert, then unblock\n // and dispatch a fresh attempt.\n await archiveFailedSwarmSlot(normalizedIssueId, workspacePath, {\n itemId: block.itemId, slotIndex: block.slotIndex, status: 'in_flight', branch: block.branch,\n agentId: block.branch ? `agent-${normalizedIssueId.toLowerCase()}-slot-${block.slotIndex}` : undefined,\n },\n { runGitCommand: deps.runGitCommand, clearSlotAssignment: deps.clearSlotAssignment },\n );\n await deps.applyTaskOperationToPlanFile(normalizedIssueId, {\n type: 'unblock',\n itemId: block.itemId,\n writerId: 'deacon-swarm',\n reason: 'Retrying failed swarm slot after merge conflict',\n }, workspacePath);\n await clearFailedMergeBlock(normalizedIssueId, block.slotIndex, workspacePath);\n if (action === 'reclaim') {\n await (deps.recordForemanTakeover ?? writeSwarmForemanTakeover)(workspacePath, normalizedIssueId, block.itemId, block.slotIndex);\n return [`[swarm] reclaimed slot ${block.slotIndex} (item ${block.itemId}) for foreman implementation in ${normalizedIssueId}`];\n }\n const remainingBlocks = getFailedMergeBlocks(normalizedIssueId, workspacePath);\n const blockedSlotIndexes = new Set(remainingBlocks.map(b => b.slotIndex));\n const blockedItemIds = new Set(remainingBlocks.map(b => b.itemId));\n const retryDoc = {\n ...doc,\n plan: {\n ...doc.plan,\n items: doc.plan.items.map(item =>\n item.id === block.itemId ? { ...item, status: 'pending' as const } : item\n ),\n },\n };\n return [\n `[swarm] retrying failed-merge slot ${block.slotIndex} (item ${block.itemId}) for ${normalizedIssueId}`,\n ...await dispatchNextWave(normalizedIssueId, workspacePath, retryDoc, {\n issueId: normalizedIssueId,\n merged: [],\n inFlight: [],\n pending: [],\n branches: [],\n agents: [],\n }, analyzeSwarmReadiness(retryDoc), deps, blockedSlotIndexes, blockedItemIds),\n ];\n}\n\nfunction writeSwarmFailedMergeBlock(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n block: FailedMergeBlock | undefined,\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n return updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, record => {\n const existingSwarm = record.swarm ?? {};\n const foldedBlocks: Record<string, PanIssueSwarmFailedMergeBlock> = { ...(existingSwarm.failedMergeBlocks ?? {}) };\n if (existingSwarm.failedMergeBlock) foldedBlocks[String(existingSwarm.failedMergeBlock.slotIndex)] = existingSwarm.failedMergeBlock;\n if (block) foldedBlocks[String(slotIndex)] = block;\n else delete foldedBlocks[String(slotIndex)];\n return { ...record, swarm: { ...existingSwarm, failedMergeBlocks: foldedBlocks, failedMergeBlock: undefined } };\n }).then(() => undefined);\n}\n\nfunction recordSlotMergeFire(branchKey: string, now = Date.now()): void {\n recentSlotMergeFires.set(branchKey, now + SLOT_MERGE_REFIRE_COOLDOWN_MS);\n}\n\nfunction isSlotMergeCoolingDown(branchKey: string, now = Date.now()): boolean {\n const until = recentSlotMergeFires.get(branchKey);\n if (until === undefined) return false;\n if (until > now) return true;\n recentSlotMergeFires.delete(branchKey);\n return false;\n}\n\nexport async function dispatchNextWave(\n issueId: string,\n workspacePath: string,\n doc: XBriefDocument,\n reconciled: SlotReconcileResult,\n readiness: SwarmReadinessVerdict,\n deps: Pick<\n CoordinateSwarmSlotsDeps,\n 'registeredSlotCapacityAvailable'\n | 'tryReserveSwarmSlot'\n | 'releaseSwarmSlot'\n | 'applyTaskOperationToPlanFile'\n | 'recordSlotAssignment'\n | 'clearSlotAssignment'\n | 'spawnRun'\n | 'shouldDispatch'\n | 'readSwarmHold'\n | 'getMaxSlotIndex'\n | 'listSlotAssignments'\n | 'listReleasedSlotIndexes'\n | 'getReleasedSlotBranch'\n | 'clearReleasedSlot'\n > & Partial<Pick<CoordinateSwarmSlotsDeps, 'listSessionNames' | 'slotWorktreeExists'>> = defaultDeps,\n blockedSlotIndexes: Set<number> = new Set(),\n blockedItemIds: Set<string> = new Set(),\n): Promise<string[]> {\n const actions: string[] = [];\n const mergedItemIds = new Set(reconciled.merged.map(slot => slot.itemId));\n const slotEligibleIds = new Set(readiness.items.filter(item => item.slotEligible).map(item => item.id));\n const configuredMaxSlotIndex = resolveSwarmMaxSlots(issueId, (deps.getMaxSlotIndex ?? defaultGetMaxSlotIndex)());\n const releasedSlotIndexes = new Set((deps.listReleasedSlotIndexes ?? defaultDeps.listReleasedSlotIndexes)?.(issueId, workspacePath) ?? []);\n const occupiedSlotIndexes = new Set([\n ...blockedSlotIndexes, // PAN-2364: blocked slots count as occupied so other slots cannot collide with them\n ...reconciled.inFlight.map(slot => slot.slotIndex),\n // Active merged slots are covered by their assignment, agent, or worktree (PAN-3689).\n ...reconciled.branches.filter(branch => !branch.merged && !releasedSlotIndexes.has(branch.slotIndex)).map(branch => branch.slotIndex),\n ...reconciled.agents.filter(agent => agent.status !== 'stopped').map(agent => agent.slotIndex),\n ...(deps.listSlotAssignments ?? listDurableSlotAssignments)(issueId, workspacePath).map(assignment => assignment.slotIndex),\n ...(reconciled.superseded ?? []).map(attempt => attempt.slotIndex),\n ]);\n const maxSlotIndex = applySupersededSlotHighWater(occupiedSlotIndexes, reconciled, configuredMaxSlotIndex);\n // Orphaned on-disk worktrees still occupy their index (PAN-2213).\n if (deps.slotWorktreeExists) {\n for (let index = 1; index <= maxSlotIndex; index++) {\n if (!releasedSlotIndexes.has(index) && !occupiedSlotIndexes.has(index) && deps.slotWorktreeExists(`${workspacePath}-slot-${index}`)) {\n occupiedSlotIndexes.add(index);\n }\n }\n }\n const sessionNames = deps.listSessionNames ? await deps.listSessionNames() : [];\n const inFlightItemIds = new Set(reconciled.inFlight.map(slot => slot.itemId));\n const selectedItemIds: string[] = [];\n for (const item of getDispatchableItems(doc, mergedItemIds)) {\n if (blockedItemIds.has(item.id)) continue;\n if (!slotEligibleIds.has(item.id)) continue;\n if (inFlightItemIds.has(item.id)) continue;\n\n const overlapItemId = firstOverlappingItemId(item.id, [...inFlightItemIds, ...selectedItemIds], readiness);\n if (overlapItemId) {\n actions.push(`[swarm] deferred ${item.id} for ${issueId}: files_scope overlaps ${overlapItemId}`);\n continue;\n }\n\n if (!deps.registeredSlotCapacityAvailable(issueId, selectedItemIds.length)) {\n actions.push(`[swarm] deferred ${item.id} for ${issueId}: registered slot cap reached`);\n continue;\n }\n\n let slotIndex: number | null = null;\n for (;;) {\n const candidate = allocateSlotIndex(occupiedSlotIndexes, maxSlotIndex);\n if (candidate === null) break;\n const conflict = slotIndexConflictReason(issueId, workspacePath, candidate, sessionNames, reconciled, deps, releasedSlotIndexes);\n if (!conflict) {\n slotIndex = candidate;\n break;\n }\n // An index-level conflict the registry missed (live session, unmerged\n // branch, orphaned worktree) means the index is occupied — advance to\n // the next free index instead of refusing the item (PAN-2213).\n occupiedSlotIndexes.add(candidate);\n actions.push(`[swarm] slot ${candidate} occupied for ${issueId}: ${conflict} — advancing`);\n }\n if (slotIndex === null) {\n const occupying = [...occupiedSlotIndexes].filter(index => index >= 1 && index <= maxSlotIndex).sort((a, b) => a - b);\n actions.push(\n `[swarm] deferred ${item.id} for ${issueId}: all slot indexes 1..${maxSlotIndex} are occupied (slots ${occupying.join(', ')})`\n + ` — run \\`pan swarm reset ${issueId}\\` if these slots are orphans`,\n );\n break;\n }\n\n if (!deps.tryReserveSwarmSlot()) {\n actions.push(`[swarm] deferred ${item.id} for ${issueId}: swarm dispatch budget exhausted`);\n continue;\n }\n try {\n const releasedBranch = releasedSlotIndexes.has(slotIndex)\n ? (deps.getReleasedSlotBranch ?? defaultDeps.getReleasedSlotBranch)?.(issueId, workspacePath, slotIndex)\n : undefined;\n const slotBranch = releasedBranch ?? `feature/${issueId.toLowerCase()}-slot-${slotIndex}`;\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'claim',\n itemId: item.id,\n writerId: 'deacon-swarm',\n }, workspacePath);\n await deps.recordSlotAssignment(workspacePath, issueId, {\n slotIndex,\n itemId: item.id,\n agentId: `agent-${issueId.toLowerCase()}-slot-${slotIndex}`,\n branch: slotBranch,\n });\n // Freeze/hold can activate mid-wave; the cycle-start gate has already passed\n // by then, so re-check before every spawn (PAN-2214 slot-20 regression).\n const swarmHold = deps.readSwarmHold?.(workspacePath, issueId);\n if (swarmHold || !(deps.shouldDispatch ?? defaultShouldDispatch)(issueId)) {\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'unblock',\n itemId: item.id,\n writerId: 'deacon-swarm',\n reason: 'dispatch halted: freeze/hold active',\n }, workspacePath).catch(() => undefined);\n await deps.clearSlotAssignment(workspacePath, issueId, slotIndex, item.id);\n deps.releaseSwarmSlot();\n actions.push(`[swarm] dispatch-halted ${item.id}: freeze/hold active`);\n continue;\n }\n await deps.spawnRun(issueId, 'work', {\n workspace: workspacePath,\n slotIndex,\n slotItemId: item.id,\n ...(releasedBranch ? { slotBranch: releasedBranch } : {}),\n prompt: promptForDispatchItem(issueId, doc, item),\n startedBy: 'deacon:swarm-slot',\n });\n if (releasedSlotIndexes.has(slotIndex)) {\n await (deps.clearReleasedSlot ?? clearReleasedBlockedSwarmSlot)(workspacePath, issueId, slotIndex)\n .catch(error => actions.push(\n `[swarm] needs-you ${issueId}: replacement slot ${slotIndex} started but its release marker could not be cleared: ${error instanceof Error ? error.message : String(error)}`,\n ));\n }\n occupiedSlotIndexes.add(slotIndex);\n selectedItemIds.push(item.id);\n actions.push(`[swarm] dispatched ${dispatchPhaseForItem(doc, item)} slot ${slotIndex} (item ${item.id}) for ${issueId}`);\n } catch (error) {\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'unblock',\n itemId: item.id,\n writerId: 'deacon-swarm',\n reason: `slot dispatch failed: ${error instanceof Error ? error.message : String(error)}`,\n }, workspacePath).catch(() => undefined);\n await deps.clearSlotAssignment(workspacePath, issueId, slotIndex, item.id);\n deps.releaseSwarmSlot();\n actions.push(`[swarm] failed-dispatch ${item.id} for ${issueId}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n return actions;\n}\n\nexport function recordSlotAssignment(workspacePath: string, issueId: string, assignment: SlotAssignment): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n return updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, record => {\n const slotAssignments = [\n ...(record.swarm?.slotAssignments ?? []).filter(\n slot => slot.slotIndex !== assignment.slotIndex && slot.itemId !== assignment.itemId,\n ),\n { ...assignment, assignedAt: new Date().toISOString() },\n ].sort((a, b) => a.slotIndex - b.slotIndex);\n const slotCompletions = { ...(record.swarm?.slotCompletions ?? {}) };\n delete slotCompletions[String(assignment.slotIndex)];\n return { ...record, swarm: { ...(record.swarm ?? {}), slotAssignments, slotCompletions } };\n }).then(() => undefined);\n}\n\n/** Drop every recorded slot assignment and completion marker atomically. */\nexport function clearAllSlotAssignments(workspacePath: string, issueId: string): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n return updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, record => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n slotAssignments: [],\n slotCompletions: {},\n },\n })).then(() => undefined);\n}\n\nfunction clearSlotAssignment(workspacePath: string, issueId: string, slotIndex: number, itemId?: string): Promise<void> {\n return clearSwarmSlotOwnership(workspacePath, issueId, slotIndex, itemId);\n}\n\nfunction writeSwarmSlotAssignments(\n workspacePath: string,\n issueId: string,\n update: (existing: SlotAssignments) => SlotAssignments,\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n return updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, record => {\n const slotAssignments = update(record.swarm?.slotAssignments ?? [])\n .filter(assignment => Number.isInteger(assignment.slotIndex) && assignment.slotIndex > 0 && assignment.itemId.trim().length > 0)\n .sort((a, b) => a.slotIndex - b.slotIndex);\n return { ...record, swarm: { ...(record.swarm ?? {}), slotAssignments } };\n }).then(() => undefined);\n}\n\nfunction slotIndexConflictReason(\n issueId: string,\n workspacePath: string,\n slotIndex: number,\n sessionNames: readonly string[],\n reconciled: SlotReconcileResult,\n deps: Partial<Pick<CoordinateSwarmSlotsDeps, 'slotWorktreeExists'>>,\n releasedSlotIndexes: ReadonlySet<number> = new Set(),\n): string | undefined {\n const issueLower = issueId.toLowerCase();\n const agentId = `agent-${issueLower}-slot-${slotIndex}`;\n const branch = `feature/${issueLower}-slot-${slotIndex}`;\n if (sessionNames.includes(agentId)) {\n return `live ${agentId} session already exists`;\n }\n\n const unmergedBranch = !releasedSlotIndexes.has(slotIndex) && reconciled.branches.find(slotBranch =>\n slotBranch.slotIndex === slotIndex && slotBranch.branch === branch && !slotBranch.merged\n );\n if (unmergedBranch) {\n return `unmerged ${branch} branch already exists`;\n }\n\n const slotWorkspacePath = `${workspacePath}-slot-${slotIndex}`;\n if (!releasedSlotIndexes.has(slotIndex) && deps.slotWorktreeExists?.(slotWorkspacePath)) {\n return `slot worktree already exists at ${slotWorkspacePath}`;\n }\n\n return undefined;\n}\n\nfunction dispatchPhaseForItem(doc: XBriefDocument, item: XBriefItem): 'implementation' | 'synthesis' {\n return itemRequiresSynthesis(doc, item) && !synthesisContextForItem(item) ? 'synthesis' : 'implementation';\n}\n\nfunction promptForDispatchItem(issueId: string, doc: XBriefDocument, item: XBriefItem): string | undefined {\n if (!itemRequiresSynthesis(doc, item)) return undefined;\n\n const synthesisContext = synthesisContextForItem(item);\n if (synthesisContext) {\n return createActiveSlice(doc, {\n issueId,\n itemId: item.id,\n currentItemIds: [item.id],\n synthesisOutputs: { [item.id]: { contextUpdate: synthesisContext } },\n }).prompt;\n }\n\n const parentIds = doc.plan.edges\n .filter(edge => edge.type === 'blocks' && edge.to === item.id)\n .map(edge => edge.from);\n\n return [\n `SYNTHESIS PHASE for ${item.id}`,\n '',\n 'Do not implement this item yet. Summarize the blocking parent outputs into a concise context update for the implementation slot.',\n `Blocking parents: ${parentIds.join(', ') || '(none)'}`,\n '',\n 'Persist the synthesis context on the xBRIEF item metadata as synthesisContext, commit it, then stop.',\n ].join('\\n');\n}\n\nfunction itemRequiresSynthesis(doc: XBriefDocument, item: XBriefItem): boolean {\n return item.metadata?.requiresSynthesis === true || blockingParentCount(doc, item.id) > 1;\n}\n\nfunction synthesisContextForItem(item: XBriefItem): string | undefined {\n const raw = item.metadata?.synthesisContext;\n return typeof raw === 'string' && raw.trim().length > 0 ? raw : undefined;\n}\n\n/**\n * Lowest free slot index within the bound, derived purely from the occupied\n * set. Returns null — never an index above the bound — when indexes 1..bound\n * are all occupied (PAN-2214: unbounded allocation climbed slot-5..slot-20).\n */\nexport function allocateSlotIndex(occupiedSlotIndexes: ReadonlySet<number>, bound: number): number | null {\n const maxIndex = Math.max(1, Math.floor(bound));\n for (let slotIndex = 1; slotIndex <= maxIndex; slotIndex++) {\n if (!occupiedSlotIndexes.has(slotIndex)) return slotIndex;\n }\n return null;\n}\n\nfunction firstOverlappingItemId(\n itemId: string,\n activeItemIds: string[],\n readiness: SwarmReadinessVerdict,\n): string | undefined {\n for (const activeItemId of activeItemIds) {\n if ((readiness.overlapMatrix[itemId]?.[activeItemId]?.length ?? 0) > 0) return activeItemId;\n }\n return undefined;\n}\n\n/**\n * Whether the issue may register another slot. Counts only tmux-ALIVE slot\n * sessions (stale agents-table rows blocked all dispatch at zero live slots\n * after a reset), capped by the same swarm reserve tryReserveSwarmSlot\n * enforces — never maxWorkAgents (PAN-2214).\n */\nexport function registeredSlotCapacityAvailable(\n issueId: string,\n selectedCount: number,\n liveSlotCount: number = countRunningSwarmSlotsForIssue(issueId),\n limits: Pick<ConcurrencyLimits, 'reservedSwarmSlots'> = getConcurrencyLimits(),\n): boolean {\n return liveSlotCount + selectedCount < limits.reservedSwarmSlots;\n}\n","import { existsSync } from 'fs';\nimport { join } from 'node:path';\nimport { getAgentDir } from '../agents/agent-state.js';\nimport { getReviewStatusSync } from '../review-status.js';\n\nexport type BootReconciliationSkipReason = 'workspace_missing' | 'merged' | 'completed';\n\ninterface BootReconciliationAgent {\n id: string;\n issueId: string;\n workspace?: string | null;\n merged?: boolean;\n}\n\nexport function bootReconciliationSkipReason(agent: BootReconciliationAgent): BootReconciliationSkipReason | null {\n if (!agent.workspace || !existsSync(agent.workspace)) return 'workspace_missing';\n\n const review = getReviewStatusSync(agent.issueId);\n if (\n review?.mergeStatus === 'merged' ||\n agent.merged === true ||\n (review?.readyForMerge === true && review.reviewStatus === 'passed' && review.testStatus === 'passed')\n ) {\n return 'merged';\n }\n\n // Must mirror handleAgentStoppedEvent's mid-flight gate in deacon-auto-resume.ts:\n // a handed-off agent is resumable only when review or test found something to fix.\n // Any weaker rule here makes the boot dialog offer candidates the executor then\n // refuses, so the operator is asked a question whose answer is already decided.\n const completedMarkerExists =\n existsSync(join(getAgentDir(agent.id), 'completed')) ||\n existsSync(join(getAgentDir(agent.id), 'completed.processed'));\n if (completedMarkerExists) {\n const needsFix =\n review?.reviewStatus === 'blocked' ||\n review?.reviewStatus === 'failed' ||\n review?.testStatus === 'failed';\n if (!needsFix) return 'completed';\n }\n\n return null;\n}\n","import { existsSync } from 'node:fs';\nimport {\n type BootReconciliationDecision,\n getBootReconciliationState,\n getLastCleanShutdownAt,\n setBootReconciliationDecision,\n setBootReconciliationGrace,\n stampBootReconciliation,\n} from '../overdeck/control-settings.js';\nimport {\n getIssueStageSync,\n isTerminalIssueStage,\n listAllAgentsSync,\n} from '../overdeck/agents.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { loadCloisterConfigSync } from './config.js';\nimport { isExplicitNoResumeRequest } from './no-resume-mode.js';\nimport { bootReconciliationSkipReason } from './boot-reconciliation-predicates.js';\n\nexport const DEFAULT_BOOT_RECONCILIATION_GRACE_SECS = 120;\nexport const CLEAN_SHUTDOWN_FRESHNESS_MS = 30 * 60 * 1000;\n\n/**\n * How many times the grace window may be pushed out because the operator is\n * answering a different blocking question (an AskUserQuestion, a plan approval,\n * a permission prompt). The dialog is time-boxed on purpose — expiry commits\n * `hold_all`, which parks every candidate — so an operator who cannot even see\n * the countdown must not lose the window. The cap keeps that bounded: a dialog\n * nobody ever dismisses still resolves after grace * (1 + MAX_EXTENSIONS).\n */\nexport const MAX_BOOT_RECONCILIATION_GRACE_EXTENSIONS = 3;\n\nexport interface BootReconciliationExtendResult {\n extended: boolean;\n graceDeadline: string | null;\n graceExtensions: number;\n maxGraceExtensions: number;\n reason: 'extended' | 'not-pending' | 'cap-reached' | 'no-deadline';\n}\n\ntype ReconciliationAgent = ReturnType<typeof listAllAgentsSync>[number];\n\nexport interface BootReconciliationStartupResult {\n bootId: string;\n graceDeadline: string;\n candidateIds: string[];\n decision: BootReconciliationDecision;\n timerArmed: boolean;\n}\n\nexport interface StartBootReconciliationOptions {\n bootId?: string;\n now?: Date;\n onGraceExpired?: () => void | Promise<void>;\n}\n\nlet graceTimer: ReturnType<typeof setTimeout> | null = null;\n/**\n * The expiry hook the live timer was armed with. Held so `extendBootReconciliationGrace`\n * can re-arm against the new deadline without the HTTP route having to know how\n * the boot path wires the apply step.\n */\nlet graceExpiryHook: (() => void | Promise<void>) | null = null;\n\nfunction newestAgentTimestampMs(agent: ReconciliationAgent): number | null {\n const timestamps = [agent.lastActivity, agent.stoppedAt, agent.startedAt]\n .map((value) => value == null ? NaN : Date.parse(value))\n .filter(Number.isFinite);\n return timestamps.length > 0 ? Math.max(...timestamps) : null;\n}\n\nfunction isRecentBootCandidate(agent: ReconciliationAgent): boolean {\n const bootStartedAt = getBootReconciliationState().bootStartedAt;\n const bootStartedAtMs = bootStartedAt == null ? NaN : Date.parse(bootStartedAt);\n const newestTimestampMs = newestAgentTimestampMs(agent);\n if (!Number.isFinite(bootStartedAtMs) || newestTimestampMs == null) return false;\n const maxAgeMs = getBootReconciliationMaxCandidateAgeSeconds() * 1000;\n return newestTimestampMs >= bootStartedAtMs - maxAgeMs;\n}\n\nexport function isCleanShutdownBoot(): boolean {\n // Classify against boot start so grace extensions cannot age a clean marker into a crash.\n const state = getBootReconciliationState();\n const bootStartedAtMs = state.bootStartedAt == null ? NaN : Date.parse(state.bootStartedAt);\n const marker = getLastCleanShutdownAt();\n const markerMs = marker == null ? NaN : Date.parse(marker);\n return Number.isFinite(bootStartedAtMs)\n && Number.isFinite(markerMs)\n && bootStartedAtMs - markerMs <= CLEAN_SHUTDOWN_FRESHNESS_MS;\n}\n\nexport function getBootReconciliationGraceSeconds(): number {\n const value = loadCloisterConfigSync().startup.reconciliation_grace_secs;\n return Number.isFinite(value) && value > 0\n ? value\n : DEFAULT_BOOT_RECONCILIATION_GRACE_SECS;\n}\n\nexport function getBootReconciliationMaxCandidateAgeSeconds(): number {\n const config = loadCloisterConfigSync().startup;\n const value = config.reconciliation_max_candidate_age_secs;\n return Number.isFinite(value) && value > 0\n ? value\n : getBootReconciliationGraceSeconds() * 2;\n}\n\nexport function isAutoResumableRole(role: ReconciliationAgent['role']): boolean {\n return role === 'work' || role === 'strike';\n}\n\nexport function isBootReconciliationCandidate(agent: ReconciliationAgent): boolean {\n if (!isAutoResumableRole(agent.role) || agent.status !== 'stopped') return false;\n if (agent.paused === true || agent.troubled === true) return false;\n if (!isRecentBootCandidate(agent)) return false;\n if (bootReconciliationSkipReason(agent) !== null) return false;\n if (!agent.workspace || !existsSync(agent.workspace)) return false;\n if (isTerminalIssueStage(getIssueStageSync(agent.issueId))) return false;\n return true;\n}\n\nexport function listBootReconciliationCandidateIds(): string[] {\n return listBootReconciliationCandidates().map((agent) => agent.id);\n}\n\nexport function listBootReconciliationCandidates(): ReconciliationAgent[] {\n return listAllAgentsSync()\n .filter(isBootReconciliationCandidate);\n}\n\nexport function getBootReconciliationPendingHoldSet(): Set<string> {\n const state = getBootReconciliationState();\n if (state.decision !== 'pending') return new Set();\n return new Set(listBootReconciliationCandidateIds());\n}\n\nexport function getBootReconciliationHeldResumeSet(): Set<string> {\n const state = getBootReconciliationState();\n if (state.decision !== 'pending' && state.decision !== 'hold_all' && state.decision !== 'per_agent') {\n return new Set();\n }\n\n const heldCandidates = listBootReconciliationCandidates()\n .filter((agent) => state.decision !== 'per_agent' || state.perAgent[agent.issueId] !== 'resume')\n .map((agent) => agent.id);\n return new Set(heldCandidates);\n}\n\nexport function clearBootReconciliationGraceTimer(): void {\n graceExpiryHook = null;\n if (!graceTimer) return;\n clearTimeout(graceTimer);\n graceTimer = null;\n}\n\n/**\n * Push the grace deadline out by one full grace period. Only legal while the\n * decision is still `pending`, and only up to MAX_BOOT_RECONCILIATION_GRACE_EXTENSIONS\n * times per boot. The caller (the extend route) is expected to fire this while\n * another blocking dialog is covering the boot modal.\n */\nexport function extendBootReconciliationGrace(now: Date = new Date()): BootReconciliationExtendResult {\n const state = getBootReconciliationState();\n const maxGraceExtensions = MAX_BOOT_RECONCILIATION_GRACE_EXTENSIONS;\n\n if (state.decision !== 'pending') {\n return {\n extended: false,\n graceDeadline: state.graceDeadline,\n graceExtensions: state.graceExtensions,\n maxGraceExtensions,\n reason: 'not-pending',\n };\n }\n\n if (state.graceExtensions >= maxGraceExtensions) {\n return {\n extended: false,\n graceDeadline: state.graceDeadline,\n graceExtensions: state.graceExtensions,\n maxGraceExtensions,\n reason: 'cap-reached',\n };\n }\n\n const currentDeadlineMs = state.graceDeadline == null ? NaN : Date.parse(state.graceDeadline);\n if (!Number.isFinite(currentDeadlineMs)) {\n return {\n extended: false,\n graceDeadline: state.graceDeadline,\n graceExtensions: state.graceExtensions,\n maxGraceExtensions,\n reason: 'no-deadline',\n };\n }\n\n // Extend from whichever is later: the standing deadline, or now. A deadline\n // that already slipped past (server was busy) must still yield a full window.\n const graceMs = getBootReconciliationGraceSeconds() * 1000;\n const base = Math.max(currentDeadlineMs, now.getTime());\n const graceDeadline = new Date(base + graceMs).toISOString();\n const graceExtensions = state.graceExtensions + 1;\n\n setBootReconciliationGrace(graceDeadline, graceExtensions);\n armBootReconciliationGraceTimer(graceDeadline, graceExpiryHook ?? undefined);\n logDeaconEventSync(\n `boot reconciliation grace extended to ${graceDeadline} (${graceExtensions}/${maxGraceExtensions}) — operator has another blocking dialog open`,\n );\n\n return { extended: true, graceDeadline, graceExtensions, maxGraceExtensions, reason: 'extended' };\n}\n\nexport function armBootReconciliationGraceTimer(\n graceDeadline: string,\n onGraceExpired: () => void | Promise<void> = () => undefined,\n): boolean {\n clearBootReconciliationGraceTimer();\n const deadlineMs = Date.parse(graceDeadline);\n if (!Number.isFinite(deadlineMs)) return false;\n const delayMs = Math.max(0, deadlineMs - Date.now());\n // Retained so an extend can re-arm the same apply step against a later deadline.\n graceExpiryHook = onGraceExpired;\n\n graceTimer = setTimeout(() => {\n graceTimer = null;\n if (getBootReconciliationState().decision !== 'pending') return;\n if (listBootReconciliationCandidateIds().length === 0) {\n setBootReconciliationDecision('resume_all');\n logDeaconEventSync('boot reconciliation grace expired — vacuous hold auto-released (0 candidates)');\n void Promise.resolve(onGraceExpired()).catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`boot reconciliation grace expiry apply hook failed: ${message}`);\n });\n return;\n }\n const decision = isCleanShutdownBoot() ? 'resume_all' : 'hold_all';\n setBootReconciliationDecision(decision);\n logDeaconEventSync(decision === 'resume_all'\n ? 'boot reconciliation grace expired — clean shutdown within 30m, decision set to resume_all'\n : 'boot reconciliation grace expired — no clean shutdown marker (crash boot), decision set to hold_all');\n void Promise.resolve(onGraceExpired()).catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`boot reconciliation grace expiry apply hook failed: ${message}`);\n });\n }, delayMs);\n graceTimer.unref?.();\n return true;\n}\n\nexport function startBootReconciliation(\n options: StartBootReconciliationOptions = {},\n): BootReconciliationStartupResult {\n const now = options.now ?? new Date();\n const bootId = options.bootId ?? process.env.OVERDECK_BOOT_ID ?? `boot-${now.toISOString()}`;\n const graceDeadline = new Date(now.getTime() + getBootReconciliationGraceSeconds() * 1000).toISOString();\n\n // PAN-3547: a read/UI peer (OVERDECK_DISABLE_DEACON=1 — workspace container\n // or host-mode dev dashboard) must never assert boot ownership in shared\n // state: no stamp, no pending decision, no grace timer. On 2026-08-04 a\n // peer boot stamped a record holding MIN-839/864/874 and the primary\n // surfaced the peer's pending record to the operator with a live\n // resume-on-timeout countdown. Boot state is written by the primary only.\n if (process.env.OVERDECK_DISABLE_DEACON === '1') {\n logDeaconEventSync(`boot reconciliation skipped for ${bootId} — peer dashboard (OVERDECK_DISABLE_DEACON=1) does not write boot records (PAN-3547)`);\n return { bootId, graceDeadline, candidateIds: [], decision: 'hold_all', timerArmed: false };\n }\n\n const existing = getBootReconciliationState();\n const bootStartedAt = existing.bootId === bootId && existing.bootStartedAt\n ? existing.bootStartedAt\n : now.toISOString();\n const stampedGraceDeadline = existing.bootId === bootId && existing.graceDeadline\n ? existing.graceDeadline\n : graceDeadline;\n\n if (existing.bootId !== bootId || !existing.bootStartedAt) {\n stampBootReconciliation(bootId, stampedGraceDeadline, bootStartedAt);\n }\n\n const candidateIds = listBootReconciliationCandidateIds();\n\n if (existing.bootId === bootId && existing.decision) {\n const existingGraceDeadline = existing.graceDeadline ?? graceDeadline;\n if (existing.decision === 'pending') {\n const timerArmed = armBootReconciliationGraceTimer(existingGraceDeadline, options.onGraceExpired);\n logDeaconEventSync(`boot reconciliation preserved ${bootId}: pending until ${existingGraceDeadline}`);\n return { bootId, graceDeadline: existingGraceDeadline, candidateIds, decision: 'pending', timerArmed };\n }\n\n clearBootReconciliationGraceTimer();\n logDeaconEventSync(`boot reconciliation preserved ${bootId}: decision=${existing.decision}`);\n return {\n bootId,\n graceDeadline: existingGraceDeadline,\n candidateIds,\n decision: existing.decision,\n timerArmed: false,\n };\n }\n\n if (isExplicitNoResumeRequest()) {\n clearBootReconciliationGraceTimer();\n setBootReconciliationDecision('hold_all');\n logDeaconEventSync(`boot reconciliation stamped ${bootId}: explicit OVERDECK_NO_RESUME requested hold_all`);\n return { bootId, graceDeadline, candidateIds, decision: 'hold_all', timerArmed: false };\n }\n\n // PAN-2510: an empty candidate list at stamp time does NOT mean \"genuinely\n // nothing to reconcile\". startBootReconciliation() runs in the dashboard\n // process, but agent liveness is reconciled asynchronously in the separately\n // spawned deacon child (reconcileAgentLiveness marks crashed agents `stopped`\n // ~1s later). On a full box reboot (OOM crash, power cycle) the agents are\n // still marked `healthy`/`running` in the table when the dashboard stamps, so\n // listBootReconciliationCandidateIds() returns 0 — then the crashed agents\n // materialize as candidates a second later. Terminally committing `resume_all`\n // here (the pre-PAN-2510 fast path) permanently skipped the operator dialog\n // AND left the held-resume set empty, so those late-arriving candidates\n // auto-resumed ungated — the exact post-reboot flood we must avoid.\n //\n // Instead we always open the grace window in `pending`. The candidate set is\n // recomputed live (frontend poll + held-resume/pending-hold sets), so late\n // arrivals from the deacon-child reconciliation are discovered and held, and\n // the dialog renders. If the window expires with still-zero candidates\n // (genuinely clean boot / dashboard-only restart with nothing stopped), the\n // grace timer resolves `pending` → `hold_all`, the safe timeout default.\n setBootReconciliationDecision('pending');\n const timerArmed = armBootReconciliationGraceTimer(graceDeadline, options.onGraceExpired);\n const heldSummary = candidateIds.length === 0\n ? 'awaiting agent-table reconciliation (0 candidates at stamp time)'\n : `holding ${candidateIds.length} candidate(s)`;\n logDeaconEventSync(`boot reconciliation stamped ${bootId}: ${heldSummary} until ${graceDeadline}`);\n return { bootId, graceDeadline, candidateIds, decision: 'pending', timerArmed };\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getAgentDir } from './agent-state.js';\n\nexport interface PinnedAgentLaunch {\n sessionId: string;\n model: string;\n harness: RuntimeName;\n}\n\nfunction parseFlagValue(content: string, flag: '--model' | '-m'): string | null {\n const escapedFlag = flag.replace('-', '\\\\-');\n const quoted = content.match(new RegExp(`${escapedFlag}\\\\s+'([^']+)'`))\n ?? content.match(new RegExp(`${escapedFlag}\\\\s+\"([^\"]+)\"`));\n if (quoted?.[1]) return quoted[1];\n return content.match(new RegExp(`${escapedFlag}\\\\s+([^\\\\s'\"\\\\\\\\]+)`))?.[1] ?? null;\n}\n\nexport function parsePinnedAgentLaunch(\n launcher: string,\n sessionId: string,\n): PinnedAgentLaunch | null {\n const pinnedSessionId = sessionId.trim();\n if (!pinnedSessionId) return null;\n\n const harness: RuntimeName | null = launcher.includes('codex-app-server-host.js')\n || /(?:^|\\s)(?:exec\\s+)?codex(?:\\s|$)/m.test(launcher)\n ? 'codex'\n : /(?:^|\\s)(?:exec\\s+)?omp\\s+--mode\\s+rpc(?:\\s|$)/m.test(launcher)\n ? 'ohmypi'\n : /(?:^|\\s)claude(?:\\s|$)/m.test(launcher)\n ? 'claude-code'\n : null;\n const model = parseFlagValue(launcher, '--model') ?? parseFlagValue(launcher, '-m');\n if (!harness || !model || model.startsWith('pending-')) return null;\n\n return { sessionId: pinnedSessionId, model, harness };\n}\n\nexport function readPinnedAgentLaunchSync(agentId: string): PinnedAgentLaunch | null {\n const agentDir = getAgentDir(agentId);\n const launcherPath = join(agentDir, 'launcher.sh');\n const sessionIdPath = join(agentDir, 'session.id');\n if (!existsSync(launcherPath) || !existsSync(sessionIdPath)) return null;\n\n try {\n return parsePinnedAgentLaunch(\n readFileSync(launcherPath, 'utf8'),\n readFileSync(sessionIdPath, 'utf8'),\n );\n } catch {\n return null;\n }\n}\n","import { Effect } from 'effect';\n\nimport { logAgentLifecycleSync, logDeaconEventSync } from '../persistent-logger.js';\nimport type { AgentState } from './agent-state.js';\nimport { markAgentRunningState, saveAgentState } from './agent-state.js';\nimport { readPinnedAgentLaunchSync, type PinnedAgentLaunch } from './pinned-launch.js';\n\ninterface PlaceholderReconciliationDeps {\n readPinnedLaunch: (agentId: string) => PinnedAgentLaunch | null;\n markRunning: (state: AgentState) => AgentState;\n saveState: (state: AgentState) => Promise<void>;\n logDeacon: (message: string) => void;\n logAgent: (agentId: string, message: string) => void;\n}\n\nfunction defaultPlaceholderReconciliationDeps(): PlaceholderReconciliationDeps {\n return {\n readPinnedLaunch: readPinnedAgentLaunchSync,\n markRunning: markAgentRunningState,\n saveState: (state) => Effect.runPromise(saveAgentState(state)),\n logDeacon: logDeaconEventSync,\n logAgent: logAgentLifecycleSync,\n };\n}\n\nexport async function reconcileLiveWorkSpawnPlaceholder(\n state: AgentState,\n notifyStatusChanged: (\n state: AgentState,\n previousStatus?: AgentState['status'],\n hasLiveTmuxSession?: boolean,\n ) => void,\n deps?: PlaceholderReconciliationDeps,\n): Promise<string | null> {\n if (state.model !== 'pending-work-spawn') return null;\n const resolvedDeps = deps ?? defaultPlaceholderReconciliationDeps();\n const pinnedLaunch = resolvedDeps.readPinnedLaunch(state.id);\n if (!pinnedLaunch) return null;\n\n const previousStatus = state.status;\n state.model = pinnedLaunch.model;\n state.harness = pinnedLaunch.harness;\n state.sessionId = pinnedLaunch.sessionId;\n resolvedDeps.markRunning(state);\n await resolvedDeps.saveState(state);\n notifyStatusChanged(state, previousStatus, true);\n\n const message = `Reconciled ${state.id} placeholder to running (${pinnedLaunch.harness}/${pinnedLaunch.model})`;\n resolvedDeps.logDeacon(`handleAgentHeartbeatDeadEvent: ${message}`);\n resolvedDeps.logAgent(state.id, message);\n return message;\n}\n","import { querySessionSync, type SessionQueryResult } from '../tmux.js';\nimport { supervisorProcessAliveSync } from '../agents/supervisor-liveness.js';\n\nconst consecutiveMisses = new Map<string, number>();\n\nexport function queryConfirmedSession(agentId: string): [SessionQueryResult, string?] {\n const query = querySessionSync(agentId);\n if (query.status === 'error') {\n consecutiveMisses.delete(agentId);\n return [query, `skipped — tmux query failed (${query.detail})`];\n }\n if (query.status === 'exists') {\n consecutiveMisses.delete(agentId);\n return [query];\n }\n // PAN-3002: a missing tmux session is NOT proof of death for supervisor-\n // delivered agents — the pty-supervisor worker survives its tmux session and\n // keeps running untracked (MIN-882: 36h lost; PAN-2997: killed 41s after a\n // healthy resume). A live worker process retains the agent.\n if (supervisorProcessAliveSync(agentId)) {\n consecutiveMisses.delete(agentId);\n return [query, `retained — live pty-supervisor process despite missing tmux session (PAN-3002)`];\n }\n const missCount = (consecutiveMisses.get(agentId) ?? 0) + 1;\n consecutiveMisses.set(agentId, missCount);\n return missCount < 2\n ? [query, `retained after first confirmed tmux miss (${query.detail})`]\n : [query];\n}\n\nexport function clearConfirmedSessionMiss(agentId: string): void {\n consecutiveMisses.delete(agentId);\n}\n\nexport function consumeConfirmedSessionDetail(agentId: string, query: SessionQueryResult): string {\n clearConfirmedSessionMiss(agentId);\n return query.status === 'missing' ? query.detail : 'pane confirmed dead';\n}\n","import { getReviewStatusSync } from '../review-status.js';\nimport type { XBriefDocument, XBriefItem } from '../xbrief/types.js';\n\nconst BLOCKING_INSPECTION_STATUSES = new Set(['failed', 'blocked', 'error', 'inspecting']);\n\nexport interface BlockingMandatoryInspection {\n item: XBriefItem;\n status: string;\n notes?: string;\n}\n\nexport function getBlockingMandatoryInspection(\n plan: XBriefDocument,\n issueId: string,\n): BlockingMandatoryInspection | null {\n const inspection = getReviewStatusSync(issueId);\n const status = inspection?.inspectStatus as string | undefined;\n if (!status || !BLOCKING_INSPECTION_STATUSES.has(status) || !inspection?.inspectBeadId) return null;\n\n const item = plan.plan.items.find(candidate => candidate.id === inspection.inspectBeadId);\n if (item?.status !== 'completed' || item.metadata?.requiresInspection !== true) return null;\n\n return { item, status, notes: inspection.inspectNotes };\n}\n\nexport function buildInspectionBlockedNudge(\n issueId: string,\n inspection: BlockingMandatoryInspection,\n): string {\n const lines = [\n `Deacon idle-nudge: mandatory inspection blocks further task advancement for ${issueId}.`,\n '',\n `Inspection item: ${inspection.item.id} ${inspection.item.title}`,\n `Inspection status: ${inspection.status}`,\n `Actionable inspection notes: ${inspection.notes?.trim() || 'No inspection notes were recorded.'}`,\n '',\n ];\n\n if (inspection.status === 'failed' || inspection.status === 'blocked') {\n lines.push(\n `You must fix ${inspection.item.id}, commit and push the correction, then re-run \\`pan inspect ${issueId} --item ${inspection.item.id}\\`. Do not claim or implement another task until inspection passes.`,\n );\n } else if (inspection.status === 'error') {\n lines.push(\n `Resolve or report the inspection infrastructure failure, then re-run \\`pan inspect ${issueId} --item ${inspection.item.id}\\`; do not advance to another task until inspection passes.`,\n );\n } else {\n lines.push(\n `Wait for the inspection verdict for ${inspection.item.id}; do not advance to another task until inspection passes.`,\n );\n }\n\n return lines.join('\\n');\n}\n","import { existsSync, readdirSync, readFileSync, writeFileSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { cpus, loadavg } from 'os';\nimport { Effect } from 'effect';\nimport { isStartingWithinGrace } from './agent-grace.js';\nimport { isAgentIdleForNudge } from './agent-idle.js';\nimport { getConcurrencyLimits, countRunningAgents, workResumeSlotsAvailable } from './concurrency.js';\nimport { assessMemoryPressure } from './memory-governor.js';\nimport { resumeYieldedAgents } from './preemption.js';\nimport {\n getBootReconciliationHeldResumeSet,\n getBootReconciliationPendingHoldSet,\n isAutoResumableRole,\n} from './boot-reconciliation.js';\nimport { bootReconciliationSkipReason } from './boot-reconciliation-predicates.js';\nimport { isIssueClosed } from './issue-closed.js';\nimport { listAllAgentsSync as listAllAgents, RETAINED_TRANSCRIPTS_PHASE } from '../overdeck/agents.js';\nimport { emitActivityEntrySync, emitActivityTtsSync } from '../activity-logger.js';\nimport { logDeaconEventSync, logAgentLifecycleSync } from '../persistent-logger.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { getBootReconciliationState } from '../overdeck/control-settings.js';\nimport { captureTranscriptUserRecordSnapshot } from '../transcript-landing.js';\nimport {\n buildResumeContinueMessage,\n getAgentDir,\n getAgentRuntimeStateSync,\n getAgentState,\n getAgentStateSync,\n listAgentStates,\n markAgentRunningState,\n recordAgentFailure,\n resetAgentFailureCount,\n resumeAgent,\n stopAgent,\n saveAgentState,\n saveAgentStateSync,\n deliverInitialPromptWithRetry,\n type AgentState,\n} from '../agents.js';\nimport {\n killSession,\n listPaneValues,\n listSessionNames,\n sessionExists,\n sessionExistsSync,\n} from '../tmux.js';\nimport { readWorkspacePlanSync } from '../xbrief/io.js';\nimport { getDispatchableItems } from '../xbrief/dag.js';\nimport type { XBriefItem } from '../xbrief/types.js';\nimport { reconcileLiveWorkSpawnPlaceholder } from '../agents/placeholder-reconciliation.js';\nimport { consumeConfirmedSessionDetail, queryConfirmedSession } from './confirmed-session-query.js';\nimport { isTerminalSwarmSlotAgent } from './swarm-slot-lifecycle.js';\nimport { buildInspectionBlockedNudge, getBlockingMandatoryInspection } from './idle-nudge-inspection.js';\nexport interface AutoResumeNotifierDeps {\n notifyAgentStopped: (agentId: string) => void;\n notifyAgentStatusChanged: (state: AgentState, previousStatus?: AgentState['status'], hasLiveTmuxSession?: boolean) => void;\n}\n\nconst orphanFailureRecordedForAutoResume = new Set<string>();\n\nfunction isVerifyPausedAgentState(state: Pick<AgentState, 'issueId' | 'paused'>): boolean {\n if (state.paused !== true || !state.issueId) return false;\n return getReviewStatusSync(state.issueId)?.mergeStatus === 'merged';\n}\n\nfunction reviewArtifactExistsForRun(path: string | undefined, startedAt: string | undefined): boolean {\n if (!path || !existsSync(path)) return false;\n const startedMs = Date.parse(startedAt ?? '');\n if (!Number.isFinite(startedMs)) return true;\n try {\n return statSync(path).mtimeMs >= startedMs;\n } catch {\n return false;\n }\n}\n\nfunction hasCompletedReviewArtifact(state: AgentState): boolean {\n if (state.role !== 'review') return false;\n if (state.reviewSubRole) {\n return reviewArtifactExistsForRun(state.reviewOutputPath, state.startedAt);\n }\n if (!state.workspace || !state.reviewRunId) return false;\n return reviewArtifactExistsForRun(join(state.workspace, '.pan', 'review', state.reviewRunId, 'synthesis.md'), state.startedAt);\n}\n\nlet recoverOrphanedAgentsInFlight: Promise<string[]> | null = null;\nconst RAPID_POST_RESUME_DEATH_MS = 120_000;\n\nexport function isRapidPostResumeDeath(state: AgentState): boolean {\n const lastResumeMs = Date.parse(state.lastResumeAt ?? '');\n return Number.isFinite(lastResumeMs) && Date.now() - lastResumeMs <= RAPID_POST_RESUME_DEATH_MS;\n}\n\n/**\n * PAN-1718: a work agent that reached a tmux session but lost it before ever\n * delivering its kickoff never started doing real work — this is a launch\n * crash, not a healthy run that later died. Work agents set\n * kickoffDelivered=false at spawn and =true once the kickoff lands.\n *\n * Why this exists alongside isRapidPostResumeDeath: that guard keys off\n * lastResumeAt, so it only catches rapid death after a *resume*. A\n * fundamentally-broken work agent (crashing harness, dead model) is\n * re-dispatched by the orphan-proposed reconciler as a *fresh start* each\n * time, which has no lastResumeAt — and its startedAt→orphan span can exceed\n * the rapid window purely because the spawn itself is slow (e.g. ~2 min to\n * reach `running`). Without this, the orphan path resets the failure counter\n * every cycle, so consecutiveFailures oscillates 1→0→1 and never reaches\n * maxConsecutiveFailures — the troubled gate never trips and the agent\n * crash-loops forever. Treating a pre-kickoff orphan as an accumulating\n * failure lets it trip `troubled` after maxConsecutiveFailures, which the\n * reconciler then honors (it skips troubled agents) and the loop stops.\n */\nexport function isPreKickoffLaunchDeath(state: AgentState): boolean {\n return state.role === 'work' && state.kickoffDelivered === false;\n}\n\n/**\n * PAN-1908: event-driven orphan recovery. A single agent has been declared\n * heartbeat-dead (tmux session gone). Mark it stopped, record a failure for\n * auto-resume tracking, and notify subscribers. Does NOT enumerate directories —\n * it operates on the agent ID passed by the event/reconcile caller.\n */\nexport async function handleAgentHeartbeatDeadEvent(\n agentId: string,\n context: string | undefined,\n deps: AutoResumeNotifierDeps,\n): Promise<string[]> {\n const state = getAgentStateSync(agentId);\n if (!state) {\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} skipped — no state`);\n return [];\n }\n if (state.status !== 'running' && state.status !== 'starting') {\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} skipped — status=${state.status} (not running/starting)`);\n return [];\n }\n const verifyPaused = isVerifyPausedAgentState(state);\n let [sessionQuery, retainReason] = queryConfirmedSession(agentId);\n // A reconcile pass must settle stale registry rows without waiting for the\n // next stretched patrol, while preserving the two independent tmux misses\n // required to reject a transient session lookup failure.\n if (context === 'reconcile' && retainReason?.startsWith('retained after first confirmed tmux miss')) {\n [sessionQuery, retainReason] = queryConfirmedSession(agentId);\n }\n if (retainReason) { logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} ${retainReason}`); return []; }\n\n // PAN-1557: convoy reviewers are interactive — they own a tmux session\n // (remain-on-exit on) like other specialists, so liveness is the session's\n // pane, not a launcher pid. While the pane is alive the reviewer is working\n // or idling attachably; a dead pane (Claude exited) or a missing session\n // past the startup grace means it's done — fall through to mark stopped.\n if (state.reviewSubRole) {\n if (sessionQuery.status === 'exists') {\n try {\n const dead = ((await Effect.runPromise(listPaneValues(agentId, '#{pane_dead}')))[0]?.trim() ?? '') === '1';\n if (!dead) return []; // pane alive — still working / idling attachably\n try { await Effect.runPromise(killSession(agentId)); } catch { /* ignore */ }\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: killed dead reviewer pane ${agentId}`);\n } catch {\n return []; // can't check — assume alive\n }\n } else {\n // No session yet — startup grace keyed off startedAt before orphaning.\n const startedMs = Date.parse(state.startedAt ?? '');\n const REVIEWER_STARTUP_GRACE_MS = 90_000;\n if (Number.isFinite(startedMs) && Date.now() - startedMs < REVIEWER_STARTUP_GRACE_MS) {\n return [];\n }\n }\n // Session gone (or dead pane past grace) — fall through to mark stopped.\n } else if (sessionQuery.status === 'exists') {\n // Planning sessions use remain-on-exit, so the tmux session persists after\n // Claude exits. Check if the pane's process is actually dead.\n if (agentId.startsWith('planning-')) {\n try {\n const result = (await Effect.runPromise(listPaneValues(agentId, '#{pane_dead}')))[0]?.trim() ?? '';\n if (result !== '1') return []; // pane is alive — truly still running\n // Pane is dead — kill the zombie tmux session and fall through to recovery\n try { await Effect.runPromise(killSession(agentId)); } catch { /* ignore */ }\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: killed dead planning pane ${agentId}`);\n } catch {\n return []; // can't check — assume alive\n }\n } else {\n const action = await reconcileLiveWorkSpawnPlaceholder(state, deps.notifyAgentStatusChanged);\n return action ? [action] : [];\n }\n } else if (state.status === 'starting') {\n // PAN-1256: work agents in `starting` status need a startup grace\n // window before being declared orphaned.\n if (isStartingWithinGrace(state)) {\n return [];\n }\n // Past the grace window with no tmux session — true orphan, fall through.\n }\n\n // Orphaned — crashed agent with no tmux session\n const oldStatus = state.status;\n const missingSessionDetail = consumeConfirmedSessionDetail(agentId, sessionQuery);\n state.status = 'stopped';\n state.stoppedAt = new Date().toISOString();\n await Effect.runPromise(saveAgentState(state));\n // PAN-1530: only record failure markers for agents the auto-resume gate\n // will actually retry. Planning agents are one-shot by design.\n const isResumableRole = !agentId.startsWith('planning-');\n const completedReviewArtifact = hasCompletedReviewArtifact(state);\n if (state.stoppedByUser !== true && isResumableRole && !completedReviewArtifact && !verifyPaused) {\n const rapidPostResumeDeath = isRapidPostResumeDeath(state);\n const preKickoffLaunchDeath = isPreKickoffLaunchDeath(state);\n // PAN-1718: preserve (accumulate) the failure counter for deaths that prove\n // the agent never came up healthy — a rapid post-resume death, or a death\n // before the kickoff was ever delivered. Resetting on these lets a\n // fundamentally-broken agent that the reconciler re-dispatches as a fresh\n // start zero its counter every cycle and crash-loop forever without ever\n // tripping the troubled gate.\n const accumulatingDeath = rapidPostResumeDeath || preKickoffLaunchDeath;\n if (!accumulatingDeath) {\n resetAgentFailureCount(agentId);\n }\n const failureReason = rapidPostResumeDeath\n ? `rapid post-resume death: tmux session missing within ${RAPID_POST_RESUME_DEATH_MS / 1000}s (${context ?? 'event'})`\n : preKickoffLaunchDeath\n ? `launch crash: tmux session lost before kickoff delivery (${context ?? 'event'})`\n : `orphaned: tmux session missing (${context ?? 'event'})`;\n const failedState = await Effect.runPromise(recordAgentFailure(agentId, failureReason));\n if (failedState) {\n deps.notifyAgentStatusChanged(failedState, oldStatus, false);\n orphanFailureRecordedForAutoResume.add(agentId);\n }\n } else if (completedReviewArtifact) {\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} stopped after completed review artifact; not recording orphan failure`);\n } else if (verifyPaused) {\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} stopped after verify pause; not recording orphan failure`);\n }\n const msg = `Recovered orphaned agent ${agentId} (${oldStatus}→stopped)`;\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${msg} — tmux session missing (${missingSessionDetail}), state.json reset`);\n logAgentLifecycleSync(agentId, `status changed: ${oldStatus} → stopped (orphaned: tmux session missing)`);\n // Notify server layer so the read model and frontend update\n deps.notifyAgentStopped(agentId);\n return [msg];\n}\n\n/**\n * On startup, detect agents whose state.json claims 'running' or 'starting' but have\n * no live tmux session — this happens after a system crash where tmux was killed but\n * state.json was never updated. Reset them to 'stopped' so resume/re-plan works correctly.\n */\nexport async function recoverOrphanedAgents(context: string | undefined, deps: AutoResumeNotifierDeps): Promise<string[]> {\n if (recoverOrphanedAgentsInFlight) {\n logDeaconEventSync(`recoverOrphanedAgents coalesced${context ? ` (${context})` : ''}: scan already in flight`);\n return recoverOrphanedAgentsInFlight;\n }\n\n const scan = recoverOrphanedAgentsOnce(context, deps);\n recoverOrphanedAgentsInFlight = scan;\n try {\n return await scan;\n } finally {\n if (recoverOrphanedAgentsInFlight === scan) {\n recoverOrphanedAgentsInFlight = null;\n }\n }\n}\n\nasync function recoverOrphanedAgentsOnce(context: string | undefined, deps: AutoResumeNotifierDeps): Promise<string[]> {\n // PAN-1908: authoritative registry is the agents table; no directory scan.\n const candidates = listAllAgents()\n .filter((agent) => agent.status === 'running' || agent.status === 'starting')\n .map((agent) => agent.id);\n\n logDeaconEventSync(`recoverOrphanedAgents started${context ? ` (${context})` : ''}: ${candidates.length} candidate(s) from agents table`);\n const actions: string[] = [];\n for (const agentId of candidates) {\n try {\n const result = await handleAgentHeartbeatDeadEvent(agentId, context ?? 'patrol', deps);\n actions.push(...result);\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`recoverOrphanedAgents: error processing ${agentId}: ${reason}`);\n }\n }\n if (actions.length > 0 && context) {\n console.log(`[deacon] ${context}: ${actions.length} orphaned agent(s) reset to stopped`);\n logDeaconEventSync(`recoverOrphanedAgents completed (${context}): ${actions.length} orphaned agent(s) reset to stopped`);\n } else {\n logDeaconEventSync(`recoverOrphanedAgents completed: no orphaned agents found`);\n }\n return actions;\n}\n\n/**\n * Kill `planning-*` tmux sessions whose corresponding work agent (`agent-*`) is\n * already alive — that's definitive evidence planning is over. Handles the PAN-682\n * pattern where a planning session survives after `complete-planning` fails to\n * kill it (skipKill=true path, or complete-planning never invoked because the\n * work agent was started via a different code path).\n */\nexport async function cleanupOrphanedPlanningSessions(deps: AutoResumeNotifierDeps): Promise<string[]> {\n const actions: string[] = [];\n let planningSessions: string[];\n try {\n planningSessions = (await Effect.runPromise(listSessionNames()))\n .filter(s => s.startsWith('planning-'));\n } catch {\n return actions;\n }\n\n logDeaconEventSync(`cleanupOrphanedPlanningSessions started: found ${planningSessions.length} planning session(s)`);\n\n for (const planningSession of planningSessions) {\n // planning-pan-596 → agent-pan-596\n const workAgentSession = planningSession.replace(/^planning-/, 'agent-');\n if (!sessionExistsSync(workAgentSession)) {\n logDeaconEventSync(`cleanupOrphanedPlanningSessions: ${planningSession} kept — work agent ${workAgentSession} not running`);\n continue;\n }\n\n try {\n await Effect.runPromise(killSession(planningSession)).catch(() => {});\n } catch { /* non-fatal */ }\n\n // Mark planning agent state as stopped so the UI doesn't show a \"running\" pill.\n try {\n const agentState = getAgentStateSync(planningSession);\n if (agentState && (agentState.status === 'running' || agentState.status === 'starting')) {\n const oldStatus = agentState.status;\n saveAgentStateSync({ ...agentState, status: 'stopped', stoppedAt: new Date().toISOString() });\n deps.notifyAgentStopped(planningSession);\n logAgentLifecycleSync(planningSession, `status changed: ${oldStatus} → stopped (orphaned planning session killed)`);\n }\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`cleanupOrphanedPlanningSessions: error updating state for ${planningSession}: ${reason}`);\n }\n\n const msg = `Killed orphaned ${planningSession} (work agent ${workAgentSession} is running)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`cleanupOrphanedPlanningSessions: ${msg}`);\n }\n if (actions.length > 0) {\n logDeaconEventSync(`cleanupOrphanedPlanningSessions completed: killed ${actions.length} orphaned session(s)`);\n } else {\n logDeaconEventSync(`cleanupOrphanedPlanningSessions completed: no orphaned sessions found`);\n }\n\n return actions;\n}\n\n/**\n * Nudge work agents that are alive-but-idle with open beads remaining.\n *\n * Detects the gap that autoResumeStoppedWorkAgents misses: agents whose tmux\n * session is alive and `state.status === 'running'`, but whose Stop hook has\n * fired (state='idle' in the runtime mirror) and have NOT advanced. The\n * existing recovery paths only fire on `status='stopped'` (process killed)\n * or on a downstream review failure — neither matches this case.\n *\n * Triggered when ALL of the following hold:\n * - state.status === 'running' (process still alive)\n * - phase === 'implementation' or 'review-response'\n * - tmux session exists (not orphaned)\n * - isAgentIdleForNudge() returns true (Stop hook authoritative)\n * - pan task next -l <issueLabel> has ≥1 ready bead (work remaining)\n * - last nudge older than NUDGE_COOLDOWN_MS (don't spam)\n *\n * Action: send `pan tell` with a concrete imperative pointing at the next\n * ready bead. Updates `<agentDir>/.last-bead-nudge` for cooldown.\n *\n * Returns a list of action descriptions for runPatrol to log.\n */\nconst BEAD_NUDGE_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes\nconst STALLED_RESUME_NUDGE_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes\nexport const KICKOFF_REDELIVERY_THRESHOLD_MS = 3 * 60 * 1000; // 3 minutes\nconst KICKOFF_REDELIVERY_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes\n\nfunction hasLandedUserRecordSinceResume(state: AgentState, snapshot: Awaited<ReturnType<typeof captureTranscriptUserRecordSnapshot>>): boolean {\n const resumeAt = state.lastResumeAt ? Date.parse(state.lastResumeAt) : NaN;\n if (!Number.isFinite(resumeAt)) return true;\n const lastUserAt = snapshot.lastUserRecord?.timestamp ? Date.parse(snapshot.lastUserRecord.timestamp) : NaN;\n if (!Number.isFinite(lastUserAt)) return snapshot.userRecordCount > 0;\n return lastUserAt >= resumeAt;\n}\n\nfunction cooldownActive(agentId: string, filename: string, cooldownMs: number): boolean {\n const cooldownFile = join(getAgentDir(agentId), filename);\n if (!existsSync(cooldownFile)) return false;\n try {\n const last = parseInt(readFileSync(cooldownFile, 'utf-8').trim(), 10);\n return !Number.isNaN(last) && Date.now() - last < cooldownMs;\n } catch {\n return false;\n }\n}\n\nfunction recordCooldown(agentId: string, filename: string): void {\n writeFileSync(join(getAgentDir(agentId), filename), String(Date.now()), 'utf-8');\n}\n\nfunction buildStalledResumePrompt(state: AgentState): string | null {\n if (state.kickoffDelivered === false) {\n const promptPath = join(getAgentDir(state.id), 'initial-prompt.md');\n try {\n return readFileSync(promptPath, 'utf-8');\n } catch (err) {\n logDeaconEventSync(`nudgeStalledResumeWorkAgents: ${state.id} skipped — kickoffDelivered=false but ${promptPath} is unreadable: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n }\n return buildResumeContinueMessage(state);\n}\n\nfunction readyTaskLine(item: XBriefItem): string {\n return `${item.id} ${item.title}`.trim();\n}\n\nexport async function nudgeStalledResumeWorkAgents(): Promise<string[]> {\n const actions: string[] = [];\n\n const states = listAgentStates({ status: 'running', role: 'work' });\n\n for (const state of states) {\n const agentId = state.id;\n if (state.paused || state.troubled) continue;\n if (!state.lastResumeAt) continue;\n if (await isIssueClosed(state.issueId)) continue;\n if (!await Effect.runPromise(sessionExists(agentId))) continue;\n if (!isAgentIdleForNudge(agentId)) continue;\n if (cooldownActive(agentId, '.last-stalled-resume-nudge', STALLED_RESUME_NUDGE_COOLDOWN_MS)) continue;\n\n const sessionId = state.sessionId;\n if (!sessionId) continue;\n const snapshot = await captureTranscriptUserRecordSnapshot(state.workspace, sessionId);\n if (hasLandedUserRecordSinceResume(state, snapshot)) continue;\n\n const message = buildStalledResumePrompt(state);\n if (!message) continue;\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(agentId, message);\n recordCooldown(agentId, '.last-stalled-resume-nudge');\n const action = `Re-sent stalled resume prompt to ${agentId} (${state.issueId})`;\n actions.push(action);\n logDeaconEventSync(`nudgeStalledResumeWorkAgents: ${action}`);\n } catch (err: unknown) {\n logDeaconEventSync(`nudgeStalledResumeWorkAgents: ${agentId} messageAgent failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return actions;\n}\n\nexport async function redeliverUndeliveredKickoffs(): Promise<string[]> {\n const actions: string[] = [];\n const states = Array.from(new Map([\n ...listAgentStates({ status: 'running', role: 'work' }),\n ...listAgentStates({ status: 'running', role: 'flywheel' }),\n ].map((state) => [state.id, state])).values());\n\n for (const state of states) {\n const agentId = state.id;\n if (state.kickoffDelivered !== false) continue;\n if (state.paused || state.troubled) continue;\n if (await isIssueClosed(state.issueId)) continue;\n if (!await Effect.runPromise(sessionExists(agentId))) continue;\n\n const startedMs = Date.parse(state.startedAt ?? '');\n if (!Number.isFinite(startedMs) || Date.now() - startedMs <= KICKOFF_REDELIVERY_THRESHOLD_MS) continue;\n if (cooldownActive(agentId, '.last-kickoff-redelivery', KICKOFF_REDELIVERY_COOLDOWN_MS)) continue;\n\n const promptPath = join(getAgentDir(agentId), 'initial-prompt.md');\n let prompt: string;\n try {\n prompt = readFileSync(promptPath, 'utf-8');\n } catch (err) {\n logDeaconEventSync(`redeliverUndeliveredKickoffs: ${agentId} skipped — ${promptPath} unreadable: ${err instanceof Error ? err.message : String(err)}`);\n continue;\n }\n\n recordCooldown(agentId, '.last-kickoff-redelivery');\n const delivery = await deliverInitialPromptWithRetry(agentId, prompt, 'deacon:redeliver-undelivered-kickoff', state.deliveryMethod);\n\n if (delivery.ok) {\n const updated = { ...state, kickoffDelivered: true };\n await Effect.runPromise(saveAgentState(updated));\n const action = `Re-delivered undelivered kickoff to ${agentId} (${state.issueId})`;\n actions.push(action);\n logDeaconEventSync(`redeliverUndeliveredKickoffs: ${action}`);\n continue;\n }\n\n const reason = `kickoff re-delivery failed for ${agentId}: ${delivery.failure ?? 'unknown error'}`;\n const failedState = await Effect.runPromise(recordAgentFailure(agentId, reason));\n logDeaconEventSync(`redeliverUndeliveredKickoffs: ${reason}`);\n if (failedState?.troubled === true) {\n const message = `${agentId} is troubled after repeated kickoff re-delivery failures; operator intervention is required.`;\n emitActivityEntrySync({ source: 'cloister', level: 'error', message, issueId: state.issueId });\n emitActivityTtsSync({ utterance: message, priority: 0, issueId: state.issueId, source: 'cloister', eventType: 'kickoff_redelivery_failed' });\n actions.push(message);\n }\n }\n\n return actions;\n}\n\nexport async function nudgeIdleWorkAgentsWithOpenBeads(): Promise<string[]> {\n const actions: string[] = [];\n\n const states = listAgentStates({ status: 'running', role: 'work' });\n const eligible: AgentState[] = [];\n\n for (const state of states) {\n const agentId = state.id;\n if (await isIssueClosed(state.issueId)) {\n logDeaconEventSync(`nudgeIdleWorkAgentsWithOpenBeads: ${agentId} skipped — issue ${state.issueId} is closed`);\n continue;\n }\n\n // Tmux must be alive; orphans are handled by recoverOrphanedAgents.\n if (!await Effect.runPromise(sessionExists(agentId))) continue;\n\n // Authoritative idle signal — Stop hook fired and runtime mirror is idle.\n // Skips agents currently mid-thought (state='active') and ones we already\n // know are stopped/suspended.\n if (!isAgentIdleForNudge(agentId)) continue;\n\n // Cooldown — don't nudge the same agent more than once per BEAD_NUDGE_COOLDOWN_MS.\n const cooldownFile = join(getAgentDir(agentId), '.last-bead-nudge');\n if (existsSync(cooldownFile)) {\n try {\n const last = parseInt(readFileSync(cooldownFile, 'utf-8').trim(), 10);\n if (!Number.isNaN(last) && Date.now() - last < BEAD_NUDGE_COOLDOWN_MS) continue;\n } catch { /* fall through and nudge */ }\n }\n\n eligible.push(state);\n }\n\n for (const state of eligible) {\n const agentId = state.id;\n const plan = readWorkspacePlanSync(state.workspace);\n if (!plan) continue;\n const openTasks = getDispatchableItems(plan, new Set());\n const blockedInspection = getBlockingMandatoryInspection(plan, state.issueId);\n if (openTasks.length === 0 && !blockedInspection) continue;\n\n // Build the nudge: tell the agent what's next, do not just ping.\n const firstTask = openTasks[0] ? readyTaskLine(openTasks[0]).slice(0, 200) : '';\n // PAN-2102: startup kickoff delivery can silently fail on large briefs (the\n // ~50KB initial prompt trips the PTY supervisor's echo-confirm), leaving the\n // agent running with NO original context — only this nudge. Point it at the\n // brief on disk so it can self-recover the full plan/role/decisions/hazards\n // instead of guessing from the bead title alone.\n const briefPath = join(getAgentDir(agentId), 'initial-prompt.md');\n const message = blockedInspection ? buildInspectionBlockedNudge(state.issueId, blockedInspection) : [\n `Deacon idle-nudge: your tmux is alive but the agent is idle and you have ${openTasks.length} ready task(s) for ${state.issueId}.`,\n ``,\n `Next ready task: ${firstTask}`,\n ``,\n `If you don't already have your full brief for ${state.issueId} in context (work-agent role instructions, the xBRIEF plan, recorded decisions & hazards), re-read it now — it is on disk at ${briefPath}, plus .pan/continue.json and .pan/spec.vbrief.json in your workspace. Startup kickoff delivery can silently fail on large briefs, so do not assume you received it.`,\n ``,\n `Continue the per-item workflow without asking — claim it (\\`pan task claim <item-id>\\`), implement, commit, push, then complete it through \\`pan task done <item-id>\\`. ` +\n `Inspection is conditional on metadata.requiresInspection (default false; check the plan item before deciding to call \\`pan inspect\\`). ` +\n `Do NOT end your turn with a multi-paragraph summary; just advance to the next bead.`,\n ].join('\\n');\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(agentId, message);\n writeFileSync(join(getAgentDir(agentId), '.last-bead-nudge'), String(Date.now()), 'utf-8');\n const action = blockedInspection\n ? `Nudged idle ${agentId} (${state.issueId}) — mandatory inspection ${blockedInspection.status} for ${blockedInspection.item.id}`\n : `Nudged idle ${agentId} (${state.issueId}) — ${openTasks.length} ready task(s)`;\n actions.push(action);\n logDeaconEventSync(`nudgeIdleWorkAgentsWithOpenBeads: ${action}`);\n } catch (err: unknown) {\n logDeaconEventSync(`nudgeIdleWorkAgentsWithOpenBeads: ${agentId} messageAgent failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return actions;\n}\n\n/**\n * Auto-resume work agents that were stopped by a system crash/reboot\n * but still have incomplete work. Scans all agent state directories for\n * stopped work-role agents and resumes them.\n *\n * Resumption rules:\n * - Agents with pending review feedback (blocked/failed/verification-failed)\n * are ALWAYS resumed — the specialist pipeline needs them to fix issues.\n * - Agents without pending feedback are skipped if stoppedByUser=true (the\n * user deliberately killed them via pan kill / pan done).\n * - Orphaned agents (tmux session missing, no stoppedByUser flag) are resumed.\n *\n * Called by runPatrol() on every patrol cycle AND during deacon startup.\n *\n * PAN-1665: bounded by the concurrency governor. An unfreeze used to mass-resume\n * every stopped work agent back-to-back, marching the box toward dozens of heavy\n * `claude` processes (load spiked 5→52). We now resume only up to the number of\n * free work slots (`max_work_agents − runningWork`); at or over the cap we resume\n * nothing and let attrition drain. This is a gate on *starting* work — it never\n * kills a running agent. The load gate and stagger below are secondary safety\n * valves. Remaining candidates are re-evaluated next patrol, so nothing is\n * dropped — only spread out and bounded.\n */\n// Skip the rest of this cycle once 1-minute load exceeds cores * this factor.\nexport const RESUME_LOAD_FACTOR = 1.5;\n// Pause between consecutive resume spawns so the herd is spread across the cycle.\nexport const RESUME_STAGGER_MS = 150;\n// PAN-2500 memory-paced-boot: after a boot-reconciliation resume, wait for the\n// newly-spawned process's RSS to become visible in /proc before re-assessing\n// memory pressure for the next candidate — otherwise the next check reads\n// stale pre-resume MemAvailable and over-admits before the OS catches up.\nexport const RSS_SETTLE_MS = 2000;\n\nfunction shouldRetryUndeliveredKickoff(state: AgentState): boolean {\n return state.role === 'work' && state.kickoffDelivered === false;\n}\n\ninterface HandleAgentStoppedOptions {\n /** When true, the caller is managing global concurrency/load gates. */\n skipGlobalGates?: boolean;\n /** Descriptive source for log messages. */\n context?: string;\n /** Explicit operator resume_all may override a historical manual stop. */\n overrideStoppedByUser?: boolean;\n}\n\n/**\n * PAN-1908: event-driven resume decision for a stopped agent. Called by the\n * reactive scheduler on `agent.stopped` and by the thin safety-net reconcile.\n * Does not enumerate directories — it evaluates the single agent ID it was given.\n */\nexport async function handleAgentStoppedEvent(\n agentId: string,\n opts: HandleAgentStoppedOptions = {},\n deps: AutoResumeNotifierDeps,\n): Promise<string | null> {\n const { skipGlobalGates = false, context = 'event', overrideStoppedByUser = false } = opts;\n const state = getAgentStateSync(agentId);\n if (!state) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — no state`);\n return null;\n }\n if (state.status !== 'stopped') {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — status=${state.status} (not stopped)`);\n return null;\n }\n if (!isAutoResumableRole(state.role)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — role=${state.role} (not auto-resumable)`);\n return null;\n }\n if (state.foreman === true) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — swarm janitor owns foreman liveness`);\n return null;\n }\n // PAN-3479: tombstoned rows keep their session_id for transcript linkage —\n // the phase, not a nulled session, is what makes them non-resumable.\n if (state.phase === RETAINED_TRANSCRIPTS_PHASE) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — retired (transcripts retained)`);\n return null;\n }\n\n if (isTerminalSwarmSlotAgent(state)) {\n if (await Effect.runPromise(sessionExists(agentId))) {\n try {\n await Effect.runPromise(stopAgent(agentId));\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} reaped — assigned swarm item is terminal`);\n } catch (err) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} reap failed — ${err instanceof Error ? err.message : String(err)}`);\n }\n } else {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — assigned swarm item is terminal`);\n }\n return null;\n }\n\n if (getBootReconciliationHeldResumeSet().has(agentId)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — held by boot reconciliation decision`);\n return null;\n }\n\n // PAN-2974 (root cause A): the event path used to resume ungated during the\n // grace window whenever the agent fell OUTSIDE the pending-hold set (e.g.\n // recency-excluded completions — 2026-07-21 MIN-882). While the decision is\n // pending (or hold_all), hold every stopped work agent on this path and log\n // it as a late-arriving candidate for the operator dialog — the batch path\n // (autoResumeStoppedWorkAgents) owns the evaluation, mirroring its\n // getBootReconciliationPendingHoldSet() gate. skipGlobalGates stays\n // authoritative for the batch's own calls.\n const bootDecision = getBootReconciliationState().decision;\n if (!skipGlobalGates && (bootDecision === 'pending' || bootDecision === 'hold_all')) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} held — boot reconciliation ${bootDecision} (late-arriving candidate, evaluated with the batch)`);\n return null;\n }\n\n if (state.paused === true) {\n const pauseKind = isVerifyPausedAgentState(state) ? 'verify-paused' : 'manually-paused';\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — ${pauseKind} (${state.pausedReason ?? 'no reason'})`);\n return null;\n }\n\n if (state.troubled === true) {\n const failureCount = state.consecutiveFailures ?? 0;\n const since = state.firstFailureInRunAt ?? state.troubledAt ?? 'unknown';\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — troubled (${failureCount} consecutive failures since ${since})`);\n return null;\n }\n\n const terminalSkipReason = bootReconciliationSkipReason(state);\n if (terminalSkipReason === 'workspace_missing') {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — workspace missing (${state.workspace || 'undefined'})`);\n return null;\n }\n\n const hasLiveTmuxSession = await Effect.runPromise(sessionExists(agentId));\n if (hasLiveTmuxSession) {\n const previousStatus = state.status;\n markAgentRunningState(state);\n await Effect.runPromise(saveAgentState(state));\n deps.notifyAgentStatusChanged(state, previousStatus, true);\n const msg = `Reconciled ${agentId} (${previousStatus}→running; tmux session alive)`;\n logDeaconEventSync(`handleAgentStoppedEvent: ${msg}`);\n return null;\n }\n\n if (terminalSkipReason !== null) {\n const suffix = terminalSkipReason === 'merged'\n ? 'already merged or merge-ready'\n : 'completed marker exists and review/test passed';\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — ${suffix}`);\n return null;\n }\n\n if (state.lastFailureNextRetryAt !== undefined) {\n const nextRetryMs = Date.parse(state.lastFailureNextRetryAt);\n if (Number.isFinite(nextRetryMs) && nextRetryMs > Date.now()) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — backoff active (next retry at ${state.lastFailureNextRetryAt})`);\n return null;\n }\n }\n\n // Skip if the agent has a completed marker (or processed completion) — unless\n // review or test found issues that need fixing (blocked / failed).\n const completedFile = join(getAgentDir(agentId), 'completed');\n const processedFile = join(getAgentDir(agentId), 'completed.processed');\n const handedOffViaDone = existsSync(completedFile) || existsSync(processedFile);\n let review = getReviewStatusSync(state.issueId);\n if (handedOffViaDone) {\n const needsFix =\n review?.reviewStatus === 'blocked' ||\n review?.reviewStatus === 'failed' ||\n review?.testStatus === 'failed';\n if (needsFix) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} resuming despite completed marker — review/test needs fixing (review=${review?.reviewStatus}, test=${review?.testStatus})`);\n } else {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — pipeline mid-flight (review=${review?.reviewStatus ?? 'none'}, test=${review?.testStatus ?? 'none'})`);\n return null;\n }\n }\n\n // Refresh review status if we haven't loaded it yet.\n review ??= getReviewStatusSync(state.issueId);\n\n if (await isIssueClosed(state.issueId)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — issue ${state.issueId} is closed`);\n return null;\n }\n\n const hasPendingReviewFeedback =\n review?.reviewStatus === 'blocked' ||\n review?.reviewStatus === 'failed' ||\n review?.testStatus === 'failed' ||\n review?.verificationStatus === 'failed';\n\n const deliberatelyStopped = state.stoppedByUser === true;\n if (deliberatelyStopped && overrideStoppedByUser) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} resuming despite stoppedByUser — explicit operator resume_all override`);\n } else if (deliberatelyStopped && !(handedOffViaDone && hasPendingReviewFeedback)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — deliberately stopped by user (stoppedByUser=true)`);\n return null;\n }\n\n if (hasPendingReviewFeedback) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} resuming — review feedback pending (review=${review?.reviewStatus}, test=${review?.testStatus}, verification=${review?.verificationStatus})`);\n } else {\n const runtimeState = getAgentRuntimeStateSync(agentId);\n if (runtimeState?.state === 'idle' && !shouldRetryUndeliveredKickoff(state)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — idle (runtime.state=idle, no review feedback)`);\n return null;\n }\n }\n\n // Global gates (skipped when the batch reconcile is driving the loop).\n if (!skipGlobalGates) {\n const concurrencyLimits = getConcurrencyLimits();\n const runningBefore = countRunningAgents();\n const workSlots = workResumeSlotsAvailable(runningBefore, concurrencyLimits);\n if (workSlots <= 0) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} deferred — work concurrency cap reached (running=${runningBefore.work}, max=${concurrencyLimits.maxWorkAgents}, slots=${workSlots})`);\n return null;\n }\n const cores = cpus().length || 1;\n const loadCeiling = cores * RESUME_LOAD_FACTOR;\n const load1 = loadavg()[0];\n if (load1 > loadCeiling) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} deferred — load gate tripped (load1=${load1.toFixed(2)} > ${loadCeiling.toFixed(2)})`);\n return null;\n }\n const memVerdict = await assessMemoryPressure();\n if (memVerdict.band !== 'ok') {\n const cpuDetail = memVerdict.loadPerCore == null ? '' : `, load/core=${memVerdict.loadPerCore.toFixed(2)}`;\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} deferred — memory gate (${memVerdict.band}), availMB=${Math.round(memVerdict.availableBytes / 1048576)}${cpuDetail}`);\n return null;\n }\n }\n\n const runtimeStateForLog = getAgentRuntimeStateSync(agentId);\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} candidate — calling resumeAgent (issueId=${state.issueId}, runtime.state=${runtimeStateForLog?.state || 'null'})`);\n try {\n const result = await resumeAgent(agentId, undefined, { startedBy: 'deacon:auto-resume' });\n if (result.success) {\n const resumedState = await Effect.runPromise(getAgentState(agentId));\n if (resumedState) {\n deps.notifyAgentStatusChanged(resumedState, state.status, true);\n }\n const msg = `Auto-resumed ${agentId} (was orphaned by system event)`;\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`handleAgentStoppedEvent: ${msg}`);\n logAgentLifecycleSync(agentId, `resumed by deacon auto-recovery (session restored after system event)`);\n const issueId = state.issueId;\n emitActivityEntrySync({\n source: 'cloister',\n level: 'info',\n message: issueId\n ? `Deacon auto-resumed ${issueId} work agent`\n : `Deacon auto-resumed agent ${agentId}`,\n issueId,\n });\n emitActivityTtsSync({\n utterance: issueId\n ? `Deacon auto resumed ${issueId} work agent`\n : `Deacon auto resumed agent ${agentId}`,\n priority: 1,\n issueId,\n source: 'cloister',\n eventType: 'agent.autoResumed',\n });\n return agentId;\n }\n const msg = `Failed to auto-resume ${agentId}: ${result.error}`;\n if (!orphanFailureRecordedForAutoResume.has(agentId)) {\n const failedState = await Effect.runPromise(recordAgentFailure(agentId, msg));\n if (failedState) {\n deps.notifyAgentStatusChanged(failedState, state.status, false);\n }\n }\n console.warn(`[deacon] ${msg}`);\n logDeaconEventSync(`handleAgentStoppedEvent: ${msg}`);\n logAgentLifecycleSync(agentId, `auto-resume FAILED: ${result.error}`);\n return null;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!orphanFailureRecordedForAutoResume.has(agentId)) {\n const failedState = await Effect.runPromise(recordAgentFailure(agentId, `Auto-resume error for ${agentId}: ${msg}`));\n if (failedState) {\n deps.notifyAgentStatusChanged(failedState, state.status, false);\n }\n }\n console.warn(`[deacon] Auto-resume error for ${agentId}: ${msg}`);\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} auto-resume threw: ${msg}`);\n logAgentLifecycleSync(agentId, `auto-resume threw exception: ${msg}`);\n return null;\n }\n}\n\nexport async function autoResumeStoppedWorkAgents(deps: AutoResumeNotifierDeps): Promise<string[]> {\n const resumed: string[] = [];\n // PAN-1665: count spawn attempts (not just successes) — a failed resume still\n // forks a `claude` process, so the budget must bound attempts to curb the herd.\n let resumeAttempts = 0;\n // Free work slots this patrol = max_work_agents − running work agents. Zero when\n // already at/over the cap, in which case we resume nothing (never kill). Computed\n // once: newly-resumed sessions take time to register as tmux-alive, so we count\n // attempts against this fixed budget rather than re-polling mid-loop.\n const concurrencyLimits = getConcurrencyLimits();\n const runningBefore = countRunningAgents();\n const workSlots = workResumeSlotsAvailable(runningBefore, concurrencyLimits);\n const cores = cpus().length || 1;\n const loadCeiling = cores * RESUME_LOAD_FACTOR;\n\n // PAN-2507: resume scheduler-yielded work agents oldest-first BEFORE any other\n // stopped candidate, drawing from the same slot budget and honoring the same\n // per-iteration memory gate. Each resumed agent consumes one work slot.\n const resumedYielded = await resumeYieldedAgents(workSlots);\n if (resumedYielded.length > 0) {\n resumed.push(...resumedYielded);\n resumeAttempts += resumedYielded.length;\n }\n\n // PAN-1908: authoritative registry is the agents table; no directory scan.\n const bootReconciliationHoldSet = getBootReconciliationPendingHoldSet();\n const candidates = listAllAgents()\n .filter((agent) => agent.status === 'stopped' && agent.role === 'work')\n // PAN-3479: retired rows exist only for transcript linkage — never resume.\n .filter((agent) => agent.phase !== RETAINED_TRANSCRIPTS_PHASE)\n .filter((agent) => !bootReconciliationHoldSet.has(agent.id))\n .map((agent) => agent.id);\n\n if (bootReconciliationHoldSet.size > 0) {\n logDeaconEventSync(`autoResumeStoppedWorkAgents: boot reconciliation pending — holding ${bootReconciliationHoldSet.size} candidate(s)`);\n }\n logDeaconEventSync(`autoResumeStoppedWorkAgents started: ${candidates.length} candidate(s) from agents table`);\n\n for (const agentId of candidates) {\n // PAN-1665 concurrency gate: resume only up to the free work slots, and bail\n // when load is high. At/over the cap workSlots is 0 → we resume nothing and let\n // attrition drain (never kill). Deferred candidates are re-evaluated next patrol.\n if (resumeAttempts >= workSlots) {\n logDeaconEventSync(`autoResumeStoppedWorkAgents: work concurrency cap reached (running=${runningBefore.work}, max=${concurrencyLimits.maxWorkAgents}, slots=${workSlots}); deferring remaining candidates to next patrol`);\n break;\n }\n const load1 = loadavg()[0];\n if (load1 > loadCeiling) {\n logDeaconEventSync(`autoResumeStoppedWorkAgents: load gate tripped (load1=${load1.toFixed(2)} > ${loadCeiling.toFixed(2)} = ${cores} cores * ${RESUME_LOAD_FACTOR}); deferring remaining candidates to next patrol`);\n break;\n }\n const memVerdict = await assessMemoryPressure();\n if (memVerdict.band !== 'ok') {\n const cpuDetail = memVerdict.loadPerCore == null ? '' : `, load/core=${memVerdict.loadPerCore.toFixed(2)}`;\n logDeaconEventSync(`autoResumeStoppedWorkAgents: memory gate (${memVerdict.band}), availMB=${Math.round(memVerdict.availableBytes / 1048576)}${cpuDetail}; deferring remaining candidates to next patrol`);\n break;\n }\n // Stagger spawns so the scheduler can absorb each `claude` before the next.\n if (resumeAttempts > 0) {\n await new Promise(r => setTimeout(r, RESUME_STAGGER_MS));\n }\n\n const result = await handleAgentStoppedEvent(agentId, { skipGlobalGates: true, context: 'patrol' }, deps);\n if (result) {\n resumed.push(result);\n resumeAttempts++;\n }\n }\n if (resumed.length > 0) {\n console.log(`[deacon] Auto-resumed ${resumed.length} work agent(s): ${resumed.join(', ')}`);\n logDeaconEventSync(`autoResumeStoppedWorkAgents completed: resumed ${resumed.length} agent(s): ${resumed.join(', ')}`);\n } else {\n logDeaconEventSync(`autoResumeStoppedWorkAgents completed: no agents resumed`);\n }\n orphanFailureRecordedForAutoResume.clear();\n return resumed;\n}\n\n/**\n * PAN-1908: thin safety-net reconcile for dropped lifecycle events. Queries the\n * authoritative agents table (no directory scan) and re-runs the event handlers\n * for any row that is inconsistent with live tmux state or should have resumed.\n * The primary path is reactive (agent.stopped / agent.heartbeat_dead events);\n * this is only a fallback.\n */\nexport async function reconcileAgentLiveness(deps: AutoResumeNotifierDeps): Promise<string[]> {\n const actions: string[] = [];\n const agents = listAllAgents();\n\n const orphanCandidates = agents.filter((agent) => agent.status === 'running' || agent.status === 'starting').map((agent) => agent.id);\n for (const agentId of orphanCandidates) {\n const result = await handleAgentHeartbeatDeadEvent(agentId, 'reconcile', deps);\n actions.push(...result);\n }\n\n // Stopped work agents that may have missed an agent.stopped event.\n const stoppedWorkCandidates = agents\n .filter((agent) => agent.status === 'stopped' && agent.role === 'work')\n .map((agent) => agent.id);\n\n for (const agentId of stoppedWorkCandidates) {\n const resumed = await handleAgentStoppedEvent(agentId, { context: 'reconcile' }, deps);\n if (resumed) actions.push(`Auto-resumed ${agentId} via reconcile`);\n }\n\n if (actions.length > 0) {\n logDeaconEventSync(`reconcileAgentLiveness completed: ${actions.length} action(s)`);\n } else {\n logDeaconEventSync('reconcileAgentLiveness completed: no actions needed');\n }\n return actions;\n}\n","export type BootReconciliationOutcomeReason =\n | 'resumed'\n /**\n * The agent was already running by the time the batch reached it — the\n * reactive stopped-event path beat the batch to it. That is a success, not a\n * skip, and must not be reported as `no-resumable-session` (PAN-3052).\n */\n | 'already-running'\n | 'no-resumable-session'\n | 'deferred-concurrency'\n | 'deferred-load'\n | 'deferred-memory';\n\nexport interface BootReconciliationOutcome {\n id: string;\n issueId: string;\n outcome: 'resumed' | 'skipped';\n reason: BootReconciliationOutcomeReason;\n}\n\nexport type BootReconciliationDecisionSkipBreakdown = {\n workspace_missing: number;\n merged: number;\n completed: number;\n other: number;\n};\n\nexport interface BootReconciliationApplyResult {\n resumed: string[];\n outcomes: BootReconciliationOutcome[];\n skipped: BootReconciliationDecisionSkipBreakdown;\n deferred: number;\n}\n\ninterface BootReconciliationOutcomeAgent {\n id: string;\n issueId: string;\n}\n\nexport function resumedBootReconciliationOutcome(agent: BootReconciliationOutcomeAgent): BootReconciliationOutcome {\n return { id: agent.id, issueId: agent.issueId, outcome: 'resumed', reason: 'resumed' };\n}\n\n/** Already running when the batch reached it — counted as resumed, not skipped. */\nexport function alreadyRunningBootReconciliationOutcome(\n agent: BootReconciliationOutcomeAgent,\n): BootReconciliationOutcome {\n return { id: agent.id, issueId: agent.issueId, outcome: 'resumed', reason: 'already-running' };\n}\n\nexport function skippedBootReconciliationOutcome(\n agent: BootReconciliationOutcomeAgent,\n reason: Exclude<BootReconciliationOutcomeReason, 'resumed'>,\n): BootReconciliationOutcome {\n return { id: agent.id, issueId: agent.issueId, outcome: 'skipped', reason };\n}\n\nexport function skippedBootReconciliationOutcomes(\n agents: ReadonlyArray<BootReconciliationOutcomeAgent>,\n reason: Exclude<BootReconciliationOutcomeReason, 'resumed'>,\n): BootReconciliationOutcome[] {\n return agents.map((agent) => skippedBootReconciliationOutcome(agent, reason));\n}\n","/**\n * Applies the operator's boot-reconciliation decision without changing the\n * auto-resume policy that evaluates each selected agent (PAN-3052).\n */\nimport { cpus, loadavg } from 'os';\nimport { Effect } from 'effect';\nimport {\n alreadyRunningBootReconciliationOutcome,\n resumedBootReconciliationOutcome,\n skippedBootReconciliationOutcome,\n skippedBootReconciliationOutcomes,\n type BootReconciliationApplyResult,\n type BootReconciliationOutcome,\n} from './boot-reconciliation-outcomes.js';\nimport { getConcurrencyLimits, countRunningAgents, workResumeSlotsAvailable } from './concurrency.js';\nimport { assessMemoryPressure } from './memory-governor.js';\nimport { listBootReconciliationCandidates } from './boot-reconciliation.js';\nimport { bootReconciliationSkipReason, type BootReconciliationSkipReason } from './boot-reconciliation-predicates.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { getBootReconciliationState } from '../overdeck/control-settings.js';\nimport { sessionExists } from '../tmux.js';\nimport {\n handleAgentStoppedEvent,\n RESUME_LOAD_FACTOR,\n RESUME_STAGGER_MS,\n RSS_SETTLE_MS,\n type AutoResumeNotifierDeps,\n} from './deacon-auto-resume.js';\n\nexport type { BootReconciliationApplyResult, BootReconciliationOutcome, BootReconciliationOutcomeReason } from './boot-reconciliation-outcomes.js';\n\nexport interface BootReconciliationApplyOptions {\n origin?: 'operator' | 'auto';\n}\n\nconst appliedBootReconciliationDecisions = new Set<string>();\nconst emptyBootReconciliationApplyResult = (): BootReconciliationApplyResult => ({ resumed: [], outcomes: [], skipped: { workspace_missing: 0, merged: 0, completed: 0, other: 0 }, deferred: 0 });\n\nfunction bootReconciliationDecisionKey(): string | null {\n const state = getBootReconciliationState();\n if (!state.decision || state.decision === 'pending') return null;\n return `${state.bootId ?? 'boot'}:${state.decision}:${JSON.stringify(state.perAgent)}`;\n}\n\nexport async function applyBootReconciliationDecision(\n deps: AutoResumeNotifierDeps,\n opts: BootReconciliationApplyOptions = {},\n): Promise<BootReconciliationApplyResult> {\n const origin = opts.origin ?? 'auto';\n // PAN-3547: only the primary may act on a boot decision. A read/UI peer\n // (OVERDECK_DISABLE_DEACON=1) that applies a decision would resume agents\n // against the primary's drain posture — the decision itself is already in\n // shared state and the primary's own machinery applies it.\n if (process.env.OVERDECK_DISABLE_DEACON === '1') {\n logDeaconEventSync('applyBootReconciliationDecision: skipped — peer dashboard does not apply boot decisions (PAN-3547)');\n return emptyBootReconciliationApplyResult();\n }\n const decisionKey = bootReconciliationDecisionKey();\n if (!decisionKey) return emptyBootReconciliationApplyResult();\n if (appliedBootReconciliationDecisions.has(decisionKey)) {\n logDeaconEventSync('applyBootReconciliationDecision: decision already applied');\n return emptyBootReconciliationApplyResult();\n }\n\n const state = getBootReconciliationState();\n if (state.decision === 'hold_all') {\n appliedBootReconciliationDecisions.add(decisionKey);\n logDeaconEventSync('applyBootReconciliationDecision: hold_all — no agents resumed');\n return emptyBootReconciliationApplyResult();\n }\n\n let candidates = listBootReconciliationCandidates();\n if (origin === 'auto') {\n const candidateCount = candidates.length;\n candidates = candidates.filter((agent) => agent.stoppedByUser !== true);\n const filteredCount = candidateCount - candidates.length;\n if (filteredCount > 0) {\n logDeaconEventSync(`applyBootReconciliationDecision: auto origin filtered ${filteredCount} stoppedByUser candidate(s)`);\n }\n }\n if (state.decision === 'per_agent') {\n candidates = candidates.filter((agent) => state.perAgent[agent.issueId] === 'resume');\n }\n\n const resumed: string[] = [];\n const outcomes: BootReconciliationOutcome[] = [];\n const skipped: Record<BootReconciliationSkipReason | 'other', number> = { workspace_missing: 0, merged: 0, completed: 0, other: 0 };\n let deferred = 0;\n let resumeAttempts = 0;\n const concurrencyLimits = getConcurrencyLimits();\n const runningBefore = countRunningAgents();\n const workSlots = workResumeSlotsAvailable(runningBefore, concurrencyLimits);\n const cores = cpus().length || 1;\n const loadCeiling = cores * RESUME_LOAD_FACTOR;\n\n for (let index = 0; index < candidates.length; index++) {\n const agent = candidates[index];\n if (resumeAttempts >= workSlots) {\n logDeaconEventSync(`applyBootReconciliationDecision: work concurrency cap reached (running=${runningBefore.work}, max=${concurrencyLimits.maxWorkAgents}, slots=${workSlots}); deferring remaining candidates`);\n const deferredAgents = candidates.slice(index);\n deferred += deferredAgents.length;\n outcomes.push(...skippedBootReconciliationOutcomes(deferredAgents, 'deferred-concurrency'));\n break;\n }\n const load1 = loadavg()[0];\n if (load1 > loadCeiling) {\n logDeaconEventSync(`applyBootReconciliationDecision: load gate tripped (load1=${load1.toFixed(2)} > ${loadCeiling.toFixed(2)} = ${cores} cores * ${RESUME_LOAD_FACTOR}); deferring remaining candidates`);\n const deferredAgents = candidates.slice(index);\n deferred += deferredAgents.length;\n outcomes.push(...skippedBootReconciliationOutcomes(deferredAgents, 'deferred-load'));\n break;\n }\n const memVerdict = await assessMemoryPressure();\n if (memVerdict.band !== 'ok') {\n const cpuDetail = memVerdict.loadPerCore == null ? '' : `, load/core=${memVerdict.loadPerCore.toFixed(2)}`;\n logDeaconEventSync(`applyBootReconciliationDecision: memory gate (${memVerdict.band}), availMB=${Math.round(memVerdict.availableBytes / 1048576)}${cpuDetail}; deferring remaining candidates`);\n const deferredAgents = candidates.slice(index);\n deferred += deferredAgents.length;\n outcomes.push(...skippedBootReconciliationOutcomes(deferredAgents, 'deferred-memory'));\n break;\n }\n if (resumeAttempts > 0) {\n await new Promise(r => setTimeout(r, RESUME_STAGGER_MS));\n }\n\n const result = await handleAgentStoppedEvent(\n agent.id,\n {\n skipGlobalGates: true,\n context: 'boot-reconciliation',\n ...(origin === 'operator' ? { overrideStoppedByUser: true } : {}),\n },\n deps,\n );\n if (result) {\n resumed.push(result);\n outcomes.push(resumedBootReconciliationOutcome(agent));\n resumeAttempts++;\n // PAN-2500 memory-paced-boot: let RSS settle before the next candidate's\n // memory check (top of the next loop iteration) re-assesses.\n await new Promise(r => setTimeout(r, RSS_SETTLE_MS));\n } else if (await Effect.runPromise(sessionExists(agent.id))) {\n // PAN-3052: already live — the reactive stopped-event path won the race in\n // the seconds between the operator saving the decision and this batch. It\n // is counted in runningBefore, so it takes no slot; reporting it as a skip\n // told the operator an agent was \"not resumable\" while it was running.\n resumed.push(agent.id);\n outcomes.push(alreadyRunningBootReconciliationOutcome(agent));\n } else {\n const skipReason = bootReconciliationSkipReason(agent) ?? 'other';\n skipped[skipReason]++;\n outcomes.push(skippedBootReconciliationOutcome(agent, 'no-resumable-session'));\n }\n }\n\n appliedBootReconciliationDecisions.add(decisionKey);\n logDeaconEventSync(`applyBootReconciliationDecision: decision=${state.decision} resumed ${resumed.length} agent(s)`);\n return { resumed, outcomes, skipped, deferred };\n}\n","/**\n * Agent directory cleanup — identifies and removes orphaned legacy agent directories.\n *\n * Valid directories (preserved):\n * - agent-<issueId> — work agents (always preserved)\n * - planning-<issueId> — planning agents (only preserved while tmux session is running)\n * - conv-* — conversation directories (ALWAYS preserved: ohmypi\n * conversations write their only transcript to\n * conv-<name>/sessions/*.jsonl via --session-dir, and\n * codex conversations keep rollout JSONLs under\n * conv-<name>/codex-home/sessions/. Deleting these\n * destroys conversation history permanently — 2026-07-05\n * incident: a boot-time `pan sync` wiped every ended\n * pi/codex conversation transcript.)\n *\n * Legacy directories (eligible for cleanup when no tmux session is running):\n * - work-<issueId>, review-<issueId>, test-<issueId>, merge-<issueId>\n * - agent-<number>, agent-agent-*, agent-* with uppercase prefix\n * - specialist-*\n * - Any other non-standard name\n */\n\nimport { existsSync, readdirSync } from 'fs';\nimport { access, readdir } from 'node:fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from './paths.js';\nimport { listSessionNames } from './tmux.js';\nimport { parseIssueIdSync } from './issue-id.js';\nimport { FsError } from './errors.js';\nimport { getAgentStateSync } from './agents.js';\nimport { removeAgentStateDir } from './agents/state-dir-removal.js';\n\nexport const CLOSED_ISSUE_AGENT_DIR_GRACE_MS = 7 * 24 * 60 * 60 * 1000;\n\n/**\n * Valid agent directory naming patterns.\n *\n * Standard issue IDs: PREFIX-NUMBER → normalized to lowercase with dash\n * Rally issue IDs: PREFIX_NUMBER → normalized to lowercase, no dash\n *\n * Examples of valid names:\n * agent-pan-801, agent-min-215, agent-f29698\n * planning-pan-801, planning-min-215\n *\n * Examples of legacy names (no longer created):\n * agent-108, agent-MIN-791, agent-agent-pan-699, agent-pan-test-1\n * work-pan-208, review-pan-646, test-pan-646, merge-pan-646\n * conv-20260411-1125, specialist-overdeck-test-agent\n */\n\n/**\n * Check whether a directory name matches a valid work-agent directory pattern.\n *\n * Work-agent directories (agent-<issueId>) are always preserved, and so are\n * strike directories (strike-<issueId>) — strike sessions are registered in\n * the agents table and their state dir holds session.id/mail like any other\n * agent. Reaping them orphans the DB row and drops the strike node from the\n * issue tree.\n * Planning-agent directories are handled separately — they are only valid\n * while their tmux session is running.\n */\nexport function isValidAgentDirectoryName(name: string): boolean {\n const match = name.match(/^(?:agent|strike)-(.+)$/);\n if (!match) return false;\n\n const suffix = match[1]!;\n // Current code always lowercases issue IDs before creating directories\n if (suffix !== suffix.toLowerCase()) return false;\n\n // Direct agent-<issueId> directories (work agents, role orchestrators without suffix)\n if (parseIssueIdSync(suffix) !== null) return true;\n\n // Specialist directories: agent-<issueId>-<role> or agent-<issueId>-<role>-<subRole>\n // e.g. agent-pan-457-review-correctness, agent-pan-457-test, agent-pan-457-ship\n // Also work agents with slots: agent-pan-457-1\n const parts = suffix.split('-');\n for (let i = 2; i <= parts.length; i++) {\n const candidate = parts.slice(0, i).join('-');\n if (parseIssueIdSync(candidate) !== null) {\n const remainder = parts.slice(i).join('-');\n if (remainder && /^[a-z0-9-]+$/.test(remainder)) return true;\n }\n }\n\n return false;\n}\n\n/**\n * Check whether a directory name is a conversation (conv-*) directory.\n *\n * Conversation launcher/state files live in ~/.overdeck/conversations/, but the\n * conv-* directory under ~/.overdeck/agents/ is the canonical transcript home\n * for non-claude harnesses: ohmypi writes sessions/*.jsonl there (--session-dir)\n * and codex keeps rollout JSONLs under codex-home/sessions/. These directories\n * must NEVER be auto-removed — doing so permanently destroys conversation\n * history.\n */\nexport function isConversationDirectory(name: string): boolean {\n return name.startsWith('conv-');\n}\n\n/**\n * Extract the issue ID from a planning-* directory name.\n * Returns null if the name is not a planning directory or the issue ID is invalid.\n */\nexport function getPlanningIssueId(name: string): string | null {\n const match = name.match(/^planning-(.+)$/);\n if (!match) return null;\n\n const issueId = match[1]!;\n if (issueId !== issueId.toLowerCase()) return null;\n if (parseIssueIdSync(issueId) === null) return null;\n\n return issueId;\n}\n\nexport function getAgentDirectoryIssueId(name: string): string | null {\n const match = name.match(/^(?:agent|planning)-(.+)$/);\n if (!match) return null;\n\n const suffix = match[1]!;\n if (suffix !== suffix.toLowerCase()) return null;\n\n const direct = parseIssueIdSync(suffix);\n if (direct) return direct.raw.toUpperCase();\n\n const parts = suffix.split('-');\n for (let i = 1; i <= parts.length; i++) {\n const candidate = parts.slice(0, i).join('-');\n const parsed = parseIssueIdSync(candidate);\n if (parsed) return parsed.raw.toUpperCase();\n }\n\n return null;\n}\n\nexport interface OrphanedAgentDir {\n name: string;\n path: string;\n hasRunningSession: boolean;\n}\n\nasync function findOrphanedAgentDirsPromise(\n agentsDir: string = AGENTS_DIR,\n): Promise<OrphanedAgentDir[]> {\n if (!existsSync(agentsDir)) {\n return [];\n }\n\n const entries = readdirSync(agentsDir, { withFileTypes: true });\n const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);\n\n const sessionNames = await Effect.runPromise(listSessionNames());\n const sessionSet = new Set(sessionNames);\n\n const orphaned: OrphanedAgentDir[] = [];\n for (const name of dirs) {\n // Work-agent directories are always valid\n if (isValidAgentDirectoryName(name)) continue;\n\n // Conversation directories hold the only transcript for ohmypi/codex\n // conversations (sessions/*.jsonl, codex-home/sessions/). Never touch them.\n if (isConversationDirectory(name)) continue;\n\n // Planning directories are valid only while their tmux session is running\n const planningIssueId = getPlanningIssueId(name);\n if (planningIssueId !== null) {\n if (sessionSet.has(name)) continue; // running session — preserve\n // No running session — orphaned\n }\n\n const dirPath = join(agentsDir, name);\n orphaned.push({\n name,\n path: dirPath,\n hasRunningSession: sessionSet.has(name),\n });\n }\n\n return orphaned;\n}\n\nexport interface CleanupResult {\n /** Directories that were removed */\n removed: string[];\n /** Directories skipped because a tmux session is still running */\n protected: string[];\n /** Directories that would be removed (dry run) */\n wouldRemove: string[];\n /** Total orphaned directories found */\n totalOrphaned: number;\n}\n\nasync function cleanupAgentDirectoriesPromise(options: {\n dryRun?: boolean;\n force?: boolean;\n agentsDir?: string;\n} = {}): Promise<CleanupResult> {\n const { dryRun = false, force = false, agentsDir = AGENTS_DIR } = options;\n\n const orphaned = await Effect.runPromise(findOrphanedAgentDirs(agentsDir));\n const protectedDirs = orphaned.filter((d) => d.hasRunningSession);\n const removable = orphaned.filter((d) => !d.hasRunningSession);\n\n const result: CleanupResult = {\n removed: [],\n protected: protectedDirs.map((d) => d.name),\n wouldRemove: [],\n totalOrphaned: orphaned.length,\n };\n\n if (removable.length === 0) {\n return result;\n }\n\n if (dryRun) {\n result.wouldRemove = removable.map((d) => d.name);\n return result;\n }\n\n if (!force) {\n // Prompt for confirmation is handled by the CLI caller; this function\n // focuses on the actual cleanup logic.\n }\n\n for (const dir of removable) {\n try {\n await removeAgentStateDir(dir.path, agentsDir);\n result.removed.push(dir.name);\n } catch {\n // Non-fatal — directory may have already been removed or permissions changed.\n }\n }\n\n return result;\n}\n\ntype IssueReadSourceState = {\n identifier?: unknown;\n id?: unknown;\n status?: unknown;\n state?: unknown;\n canonicalStatus?: unknown;\n rawTrackerState?: unknown;\n completedAt?: unknown;\n closedAt?: unknown;\n};\n\nexport interface ClosedIssueAgentDir {\n name: string;\n path: string;\n issueId: string;\n closedAt: string;\n ageMs: number;\n hasRunningSession: boolean;\n hasStateFile: boolean;\n containsJsonl: boolean;\n}\n\nexport interface ClosedIssueAgentCleanupResult {\n removed: string[];\n protected: string[];\n wouldRemove: string[];\n totalCandidates: number;\n}\n\nfunction normalizeIssueId(value: unknown): string | null {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n const parsed = parseIssueIdSync(trimmed);\n return (parsed?.raw ?? trimmed).toUpperCase();\n}\n\nfunction isClosedIssueState(issue: IssueReadSourceState): boolean {\n const state = String(issue.state ?? '').toLowerCase();\n const status = String(issue.status ?? '').toLowerCase();\n const canonicalStatus = String(issue.canonicalStatus ?? '').toLowerCase();\n const rawTrackerState = String(issue.rawTrackerState ?? '').toLowerCase();\n return Boolean(\n issue.completedAt ||\n issue.closedAt ||\n state === 'closed' ||\n status === 'done' ||\n status === 'closed' ||\n status === 'cancelled' ||\n status === 'canceled' ||\n status === 'completed' ||\n canonicalStatus === 'done' ||\n canonicalStatus === 'closed' ||\n canonicalStatus === 'cancelled' ||\n canonicalStatus === 'canceled' ||\n canonicalStatus === 'completed' ||\n rawTrackerState === 'closed' ||\n rawTrackerState === 'done' ||\n rawTrackerState === 'completed',\n );\n}\n\nfunction getClosedIssueTimes(issues: unknown[]): Map<string, number> {\n const closed = new Map<string, number>();\n for (const issue of issues) {\n if (!issue || typeof issue !== 'object') continue;\n const item = issue as IssueReadSourceState;\n const issueId = normalizeIssueId(item.identifier) ?? normalizeIssueId(item.id);\n if (!issueId || !isClosedIssueState(item)) continue;\n\n const closedTimestamp = item.completedAt ?? item.closedAt;\n if (typeof closedTimestamp !== 'string') continue;\n\n const closedAtMs = Date.parse(closedTimestamp);\n if (!Number.isFinite(closedAtMs)) continue;\n closed.set(issueId, closedAtMs);\n }\n return closed;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readAgentStateIssueId(dirPath: string): Promise<string | null> {\n try {\n const agentId = dirPath.split('/').pop() ?? '';\n const issueId = getAgentStateSync(agentId)?.issueId;\n return issueId ? normalizeIssueId(issueId) : null;\n } catch {\n return null;\n }\n}\n\nasync function directoryContainsJsonl(dirPath: string): Promise<boolean> {\n let entries;\n try {\n entries = await readdir(dirPath, { withFileTypes: true });\n } catch {\n return false;\n }\n\n for (const entry of entries) {\n const entryPath = join(dirPath, entry.name);\n if (entry.isFile() && entry.name.endsWith('.jsonl')) return true;\n if (entry.isDirectory() && await directoryContainsJsonl(entryPath)) return true;\n }\n\n return false;\n}\n\nasync function findClosedIssueAgentDirsPromise(options: {\n issues: unknown[];\n agentsDir?: string;\n nowMs?: number;\n graceMs?: number;\n}): Promise<ClosedIssueAgentDir[]> {\n const agentsDir = options.agentsDir ?? AGENTS_DIR;\n const nowMs = options.nowMs ?? Date.now();\n const graceMs = options.graceMs ?? CLOSED_ISSUE_AGENT_DIR_GRACE_MS;\n const closedIssueTimes = getClosedIssueTimes(options.issues);\n if (closedIssueTimes.size === 0 || !await pathExists(agentsDir)) return [];\n\n const sessionNames = await Effect.runPromise(listSessionNames());\n const sessionSet = new Set(sessionNames);\n const entries = await readdir(agentsDir, { withFileTypes: true });\n const candidates: ClosedIssueAgentDir[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const dirPath = join(agentsDir, entry.name);\n const stateIssueId = await readAgentStateIssueId(dirPath);\n const issueId = stateIssueId ?? getAgentDirectoryIssueId(entry.name);\n if (!issueId) continue;\n\n const closedAtMs = closedIssueTimes.get(issueId);\n if (closedAtMs === undefined) continue;\n\n const ageMs = nowMs - closedAtMs;\n if (ageMs <= graceMs) continue;\n\n candidates.push({\n name: entry.name,\n path: dirPath,\n issueId,\n closedAt: new Date(closedAtMs).toISOString(),\n ageMs,\n hasRunningSession: sessionSet.has(entry.name),\n hasStateFile: getAgentStateSync(entry.name) !== null,\n containsJsonl: await directoryContainsJsonl(dirPath),\n });\n }\n\n return candidates;\n}\n\nasync function cleanupClosedIssueAgentDirectoriesPromise(options: {\n issues: unknown[];\n dryRun?: boolean;\n force?: boolean;\n agentsDir?: string;\n nowMs?: number;\n graceMs?: number;\n}): Promise<ClosedIssueAgentCleanupResult> {\n const agentsDir = options.agentsDir ?? AGENTS_DIR;\n const candidates = await findClosedIssueAgentDirsPromise(options);\n const protectedDirs = candidates.filter((dir) => dir.hasRunningSession || dir.containsJsonl);\n const removable = candidates.filter((dir) => !dir.hasRunningSession && !dir.containsJsonl);\n const result: ClosedIssueAgentCleanupResult = {\n removed: [],\n protected: protectedDirs.map((dir) => dir.name),\n wouldRemove: [],\n totalCandidates: candidates.length,\n };\n\n if (options.dryRun) {\n result.wouldRemove = removable.map((dir) => dir.name);\n return result;\n }\n\n if (!options.force) {\n // Startup owns the non-interactive cleanup path; CLI callers should use dry-run.\n }\n\n for (const dir of removable) {\n try {\n await removeAgentStateDir(dir.path, agentsDir);\n result.removed.push(dir.name);\n } catch {\n // Non-fatal — directory may have already been removed or permissions changed.\n }\n }\n\n return result;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect-native variant of findOrphanedAgentDirs. Fails with FsError if the\n * agents directory listing fails. The tmux listing is wrapped so listing\n * failures bubble up as FsError too (treats tmux as part of the filesystem\n * for purposes of this check).\n */\nexport const findOrphanedAgentDirs = (\n agentsDir: string = AGENTS_DIR,\n): Effect.Effect<readonly OrphanedAgentDir[], FsError> =>\n Effect.tryPromise({\n try: () => findOrphanedAgentDirsPromise(agentsDir),\n catch: (cause) =>\n new FsError({ path: agentsDir, operation: 'findOrphanedAgentDirs', cause }),\n });\n\n/**\n * Effect-native variant of cleanupAgentDirectories. Fails with FsError if the\n * orphan scan fails. Individual removal failures are still swallowed internally\n * so a partial cleanup is the worst case (matches the Promise contract).\n */\nexport const cleanupAgentDirectories = (options: {\n dryRun?: boolean;\n force?: boolean;\n agentsDir?: string;\n} = {}): Effect.Effect<CleanupResult, FsError> =>\n Effect.tryPromise({\n try: () => cleanupAgentDirectoriesPromise(options),\n catch: (cause) =>\n new FsError({\n path: options.agentsDir ?? AGENTS_DIR,\n operation: 'cleanupAgentDirectories',\n cause,\n }),\n });\n\nexport const findClosedIssueAgentDirs = (options: {\n issues: unknown[];\n agentsDir?: string;\n nowMs?: number;\n graceMs?: number;\n}): Effect.Effect<readonly ClosedIssueAgentDir[], FsError> =>\n Effect.tryPromise({\n try: () => findClosedIssueAgentDirsPromise(options),\n catch: (cause) =>\n new FsError({\n path: options.agentsDir ?? AGENTS_DIR,\n operation: 'findClosedIssueAgentDirs',\n cause,\n }),\n });\n\nexport const cleanupClosedIssueAgentDirectories = (options: {\n issues: unknown[];\n dryRun?: boolean;\n force?: boolean;\n agentsDir?: string;\n nowMs?: number;\n graceMs?: number;\n}): Effect.Effect<ClosedIssueAgentCleanupResult, FsError> =>\n Effect.tryPromise({\n try: () => cleanupClosedIssueAgentDirectoriesPromise(options),\n catch: (cause) =>\n new FsError({\n path: options.agentsDir ?? AGENTS_DIR,\n operation: 'cleanupClosedIssueAgentDirectories',\n cause,\n }),\n });\n","import type { Dirent, Stats } from 'node:fs';\nimport { readdir, rm, rmdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\n\nimport { isConversationDirectory } from '../agent-directory-cleanup.js';\nimport { RETAINED_TRANSCRIPTS_MARKER } from '../agents/state-dir-removal.js';\nimport { listAllAgentsSync, removeAgentRecordSync } from '../overdeck/agents.js';\nimport { listArchivedConversations, listConversations } from '../overdeck/conversations.js';\nimport { readIssueRecordForWorkspaceSync } from '../pan-dir/record.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport { listSessionNames } from '../tmux.js';\n\ninterface TranscriptRetentionConversation {\n name: string;\n status: 'active' | 'ended';\n archivedAt: string | null;\n}\n\nexport interface TranscriptRetentionAgent {\n id: string;\n issueId: string;\n status: string;\n workspace?: string | null;\n paused?: boolean | null;\n troubled?: boolean | null;\n stoppedByUser?: boolean | null;\n}\n\ntype ReadClosedOutRecord = (\n workspace: string,\n issueId: string,\n) => { pipeline?: { closedOut?: boolean } } | null;\n\nexport function isTranscriptRetentionTerminalAgent(\n agent: TranscriptRetentionAgent,\n readRecord: ReadClosedOutRecord = readIssueRecordForWorkspaceSync,\n): boolean {\n if (agent.status !== 'stopped' || !agent.workspace) return false;\n return readRecord(agent.workspace, agent.issueId)?.pipeline?.closedOut === true;\n}\n\nexport interface TranscriptRetentionDeps {\n readDir(path: string): Promise<Dirent[]>;\n stat(path: string): Promise<Stats>;\n removeFile(path: string): Promise<void>;\n removeDir(path: string): Promise<void>;\n removeAgentRecord(agentId: string): void;\n listSessionNames(): Promise<readonly string[]>;\n listAgents(): readonly TranscriptRetentionAgent[];\n isTerminalAgent(agent: TranscriptRetentionAgent): boolean;\n listConversations(): readonly TranscriptRetentionConversation[];\n listArchivedConversations(): readonly TranscriptRetentionConversation[];\n now(): number;\n log(message: string): void;\n}\n\nexport interface TranscriptRetentionOptions {\n transcriptDays?: number;\n agentsDir?: string;\n deps?: Partial<TranscriptRetentionDeps>;\n}\n\nconst defaultDeps: TranscriptRetentionDeps = {\n readDir: (path) => readdir(path, { withFileTypes: true }),\n stat,\n removeFile: async (path) => { await rm(path, { force: true }); },\n removeDir: rmdir,\n removeAgentRecord: removeAgentRecordSync,\n listSessionNames: () => Effect.runPromise(listSessionNames()),\n listAgents: listAllAgentsSync,\n isTerminalAgent: isTranscriptRetentionTerminalAgent,\n listConversations,\n listArchivedConversations,\n now: () => Date.now(),\n log: (message) => console.log(`[deacon] ${message}`),\n};\n\nfunction hasErrorCode(error: unknown, code: string): boolean {\n return (error as NodeJS.ErrnoException).code === code;\n}\n\nfunction conversationEligibility(deps: TranscriptRetentionDeps): Map<string, boolean> | null {\n try {\n const eligible = new Map<string, boolean>();\n for (const conversation of deps.listConversations()) {\n if (conversation.status === 'active') eligible.set(conversation.name, false);\n else if (!eligible.has(conversation.name)) eligible.set(conversation.name, true);\n }\n for (const conversation of deps.listArchivedConversations()) {\n if (!eligible.has(conversation.name)) eligible.set(conversation.name, true);\n }\n return eligible;\n } catch {\n return null;\n }\n}\n\nfunction agentEligibility(deps: TranscriptRetentionDeps): Map<string, boolean> | null {\n try {\n const eligible = new Map<string, boolean>();\n const terminalByIssue = new Map<string, boolean>();\n for (const agent of deps.listAgents()) {\n if (agent.status !== 'stopped' || !agent.workspace) {\n eligible.set(agent.id, false);\n continue;\n }\n const issueKey = `${agent.workspace}\\0${agent.issueId}`;\n let terminal = terminalByIssue.get(issueKey);\n if (terminal === undefined) {\n terminal = deps.isTerminalAgent(agent);\n terminalByIssue.set(issueKey, terminal);\n }\n eligible.set(agent.id, terminal);\n }\n return eligible;\n } catch {\n return null;\n }\n}\n\nasync function pruneTranscriptFiles(\n dirPath: string,\n cutoffMs: number,\n deps: TranscriptRetentionDeps,\n): Promise<{ deletedFiles: number; prunedDirs: number; remainingTranscripts: number; removedDir: boolean }> {\n let entries: Dirent[];\n try {\n entries = await deps.readDir(dirPath);\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) {\n return { deletedFiles: 0, prunedDirs: 0, remainingTranscripts: 0, removedDir: true };\n }\n throw error;\n }\n\n let deletedFiles = 0;\n let prunedDirs = 0;\n let remainingTranscripts = 0;\n for (const entry of entries) {\n const entryPath = join(dirPath, entry.name);\n if (entry.isDirectory()) {\n const nested = await pruneTranscriptFiles(entryPath, cutoffMs, deps);\n deletedFiles += nested.deletedFiles;\n prunedDirs += nested.prunedDirs;\n remainingTranscripts += nested.remainingTranscripts;\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;\n\n let fileStat: Stats;\n try {\n fileStat = await deps.stat(entryPath);\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) continue;\n throw error;\n }\n if (fileStat.mtimeMs >= cutoffMs) {\n remainingTranscripts++;\n continue;\n }\n\n await deps.removeFile(entryPath);\n deletedFiles++;\n }\n\n let removedDir = false;\n try {\n await deps.removeDir(dirPath);\n prunedDirs++;\n removedDir = true;\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) removedDir = true;\n else if (!hasErrorCode(error, 'ENOTEMPTY') && !hasErrorCode(error, 'EEXIST')) throw error;\n }\n\n return { deletedFiles, prunedDirs, remainingTranscripts, removedDir };\n}\n\n/**\n * Delete explicitly expired transcript artifacts from ended agent state dirs.\n * Unset, non-finite, zero, or negative retention never traverses the filesystem.\n */\nexport async function sweepTranscriptRetention(\n options: TranscriptRetentionOptions,\n): Promise<string[]> {\n const transcriptDays = options.transcriptDays;\n if (transcriptDays === undefined || !Number.isFinite(transcriptDays) || transcriptDays <= 0) {\n return [];\n }\n\n const deps = { ...defaultDeps, ...options.deps };\n let liveSessions: Set<string>;\n try {\n liveSessions = new Set(await deps.listSessionNames());\n } catch {\n const action = 'Transcript retention sweep skipped: tmux liveness census unavailable';\n deps.log(action);\n return [action];\n }\n\n const agentsDir = options.agentsDir ?? AGENTS_DIR;\n let agentEntries: Dirent[];\n try {\n agentEntries = await deps.readDir(agentsDir);\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) agentEntries = [];\n else throw error;\n }\n\n const cutoffMs = deps.now() - transcriptDays * 24 * 60 * 60 * 1000;\n let conversationEligibilityMap: Map<string, boolean> | null = null;\n let conversationEligibilityLoaded = false;\n let agentEligibilityMap: Map<string, boolean> | null = null;\n let agentEligibilityLoaded = false;\n let eligibleDirs = 0;\n let deletedFiles = 0;\n let prunedDirs = 0;\n\n for (const entry of agentEntries) {\n if (!entry.isDirectory() || liveSessions.has(entry.name)) continue;\n\n const conversation = isConversationDirectory(entry.name);\n if (conversation) {\n if (!conversationEligibilityLoaded) {\n conversationEligibilityMap = conversationEligibility(deps);\n conversationEligibilityLoaded = true;\n }\n if (conversationEligibilityMap === null) continue;\n const conversationName = entry.name.slice('conv-'.length);\n if (conversationEligibilityMap.get(conversationName) !== true) continue;\n } else {\n if (!agentEligibilityLoaded) {\n agentEligibilityMap = agentEligibility(deps);\n agentEligibilityLoaded = true;\n }\n if (agentEligibilityMap === null || agentEligibilityMap.get(entry.name) !== true) continue;\n }\n\n eligibleDirs++;\n const agentDir = join(agentsDir, entry.name);\n const result = await pruneTranscriptFiles(agentDir, cutoffMs, deps);\n deletedFiles += result.deletedFiles;\n prunedDirs += result.prunedDirs;\n\n if (!conversation && result.remainingTranscripts === 0) {\n let removedDir = result.removedDir;\n if (!removedDir) {\n await deps.removeFile(join(agentDir, RETAINED_TRANSCRIPTS_MARKER));\n try {\n await deps.removeDir(agentDir);\n prunedDirs++;\n removedDir = true;\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) removedDir = true;\n else if (!hasErrorCode(error, 'ENOTEMPTY') && !hasErrorCode(error, 'EEXIST')) throw error;\n }\n }\n if (removedDir) deps.removeAgentRecord(entry.name);\n }\n }\n\n const fileLabel = `transcript file${deletedFiles === 1 ? '' : 's'}`;\n const dirLabel = `ended agent state dir${eligibleDirs === 1 ? '' : 's'}`;\n const prunedLabel = `empty director${prunedDirs === 1 ? 'y' : 'ies'}`;\n const action = `Transcript retention sweep: deleted ${deletedFiles} ${fileLabel} older than ${transcriptDays} day${transcriptDays === 1 ? '' : 's'} from ${eligibleDirs} ${dirLabel}; pruned ${prunedDirs} ${prunedLabel}`;\n deps.log(action);\n return [action];\n}\n","import { Effect } from 'effect';\n\nimport type { ProjectConfig } from '../projects.js';\nimport { reconcileStatePlaneDrift } from '../pan-dir/auto-commit.js';\nimport type { PanIssueRecord } from '../pan-dir/record.js';\nimport { listIssueRecords } from '../pan-dir/record-list.js';\nimport { clearRecordPipelineClosedOut } from '../pan-dir/record-update.js';\nimport { readLiveTrackerIssueState, type LiveTrackerIssueState } from './issue-closed.js';\n\nexport interface StatePlaneReconcileAction {\n message: string;\n level: 'action' | 'warn';\n}\n\nexport function statePlaneReconcileEveryCycles(patrolIntervalMs: number): number {\n return Math.max(1, Math.ceil((60 * 60 * 1000) / patrolIntervalMs));\n}\n\nexport interface StatePlanePatrolDeps {\n listRecords: (project: ProjectConfig) => Promise<PanIssueRecord[]>;\n readTrackerState: (issueId: string) => Promise<LiveTrackerIssueState>;\n clearClosedOut: (\n project: ProjectConfig,\n issueId: string,\n reopenedAt: string,\n ) => Promise<boolean>;\n reconcileDrift: (projectPath: string) => Promise<{ committed: boolean; pushed?: boolean }>;\n now: () => string;\n}\n\nfunction defaultDeps(): StatePlanePatrolDeps {\n return {\n listRecords: listIssueRecords,\n readTrackerState: readLiveTrackerIssueState,\n clearClosedOut: (project, issueId, reopenedAt) => clearRecordPipelineClosedOut(\n project,\n issueId,\n { reopenedAt, autoCommit: false },\n ),\n reconcileDrift: (projectPath) => Effect.runPromise(reconcileStatePlaneDrift(projectPath)),\n now: () => new Date().toISOString(),\n };\n}\n\nexport async function reconcileProjectStatePlanes(\n projects: Array<{ config: ProjectConfig }>,\n deps: StatePlanePatrolDeps = defaultDeps(),\n): Promise<StatePlaneReconcileAction[]> {\n const actions: StatePlaneReconcileAction[] = [];\n const trackerStates = new Map<string, Promise<LiveTrackerIssueState>>();\n\n for (const { config } of projects) {\n if (!config.path) continue;\n const records = await deps.listRecords(config);\n const closedOutRecords = records.filter((record) => record.pipeline.closedOut === true);\n\n for (const record of closedOutRecords) {\n const issueId = record.issueId.toUpperCase();\n try {\n let trackerState = trackerStates.get(issueId);\n if (!trackerState) {\n trackerState = deps.readTrackerState(issueId);\n trackerStates.set(issueId, trackerState);\n }\n if (await trackerState === 'closed') continue;\n\n const reopenedAt = deps.now();\n if (await deps.clearClosedOut(config, issueId, reopenedAt)) {\n actions.push({\n message: `Cleared stale closedOut state for reopened ${issueId} at ${reopenedAt}`,\n level: 'action',\n });\n }\n } catch (error) {\n actions.push({\n message: `Preserved closedOut state for ${issueId}: live tracker reconciliation failed: ${error instanceof Error ? error.message : String(error)}`,\n level: 'warn',\n });\n }\n }\n\n const write = await deps.reconcileDrift(config.path);\n if (!write.committed) continue;\n actions.push({\n message: `Reconciled pending spec/record state for ${config.name ?? config.path}`,\n level: write.pushed === false ? 'warn' : 'action',\n });\n }\n return actions;\n}\n","import type { ProjectConfig } from '../projects.js';\nimport { inspectLegacyStatePaths, type LegacyStatePathInspection } from '../state-home.js';\n\nexport interface StateRecreationWarning {\n level: 'warn' | 'error';\n message: string;\n}\n\ntype LegacyStateInspector = (project: ProjectConfig) => Promise<LegacyStatePathInspection>;\n\nfunction cleanupCommand(projectPath: string, directories: string[]): string {\n const domains = directories.map(path => path.slice(`${projectPath}/.pan/`.length));\n return `rm -rf ${projectPath}/.pan/{${domains.join(',')}}`;\n}\n\nexport function createRecreatedStateWarningReporter(\n inspect: LegacyStateInspector = inspectLegacyStatePaths,\n): (projects: Array<{ config: ProjectConfig }>) => Promise<StateRecreationWarning[]> {\n const reportedProjects = new Set<string>();\n return async (projects) => {\n const warnings: StateRecreationWarning[] = [];\n for (const { config } of projects) {\n if (!config.path || reportedProjects.has(config.path)) continue;\n const inspection = await inspect(config);\n if (\n inspection.postMigrationWrites.length === 0\n && inspection.inertDirectories.length === 0\n && inspection.staleFiles.length === 0\n ) continue;\n\n reportedProjects.add(config.path);\n const details = [\n inspection.postMigrationWrites.length > 0\n ? `Post-migration legacy state writes (stray writer): ${inspection.postMigrationWrites.join(', ')}. Stop the writer and move the data through the state write door.`\n : null,\n inspection.inertDirectories.length > 0\n ? `Inert legacy state directories (not a stray writer): ${inspection.inertDirectories.join(', ')}. After confirming they are empty, remove them with ${cleanupCommand(config.path, inspection.inertDirectories)}.`\n : null,\n inspection.staleFiles.length > 0\n ? `Unmigrated legacy state content (not a stray writer): ${inspection.staleFiles.join(', ')}. Compare it with overdeck-state before deletion.`\n : null,\n ].filter(Boolean).join(' ');\n warnings.push({\n level: inspection.postMigrationWrites.length > 0 ? 'error' : 'warn',\n message: `Migrated checkout legacy state: ${details}`,\n });\n }\n return warnings;\n };\n}\n\nexport const recreatedStateWarnings = createRecreatedStateWarningReporter();\nexport { reconcileProjectStatePlanes, statePlaneReconcileEveryCycles } from './state-plane-patrol.js';\n","import { listProjectsSync } from '../projects.js';\nimport { getMainDivergence, type MainDivergence } from '../state-plane.js';\n\nexport interface ProjectMainDivergence extends MainDivergence {\n projectKey: string;\n projectPath: string;\n checkedAt: string;\n}\n\ninterface MainDivergenceState {\n mainDivergence?: ProjectMainDivergence[];\n}\n\ntype ProjectConfigEntry = ReturnType<typeof listProjectsSync>[number];\n\nexport async function recordMainDivergenceHealth(\n state: MainDivergenceState,\n projects: ProjectConfigEntry[] = listProjectsSync(),\n measure: (repoPath: string) => Promise<MainDivergence> = getMainDivergence,\n): Promise<string[]> {\n const checkedAt = new Date().toISOString();\n const records: ProjectMainDivergence[] = [];\n const warnings: string[] = [];\n\n for (const project of projects) {\n const projectPath = project.config.path;\n if (!projectPath) continue;\n\n let divergence: MainDivergence;\n try {\n divergence = await measure(projectPath);\n } catch {\n divergence = { ahead: 0, behind: 0 };\n }\n const projectKey = project.key ?? project.config.name ?? projectPath;\n const record: ProjectMainDivergence = {\n projectKey,\n projectPath,\n checkedAt,\n ...divergence,\n };\n records.push(record);\n\n if (record.ahead > 1 || record.behind > 0) {\n warnings.push(\n `Main divergence for ${projectKey}: local main ahead ${record.ahead}, behind ${record.behind} relative to origin/main`,\n );\n }\n }\n\n state.mainDivergence = records;\n return warnings;\n}\n","export type RecoveryJanitor = 'agent-state' | 'feedback' | 'orphan-reviewer';\n\nexport function shouldRunRecoveryJanitor(janitor: RecoveryJanitor, patrolCycle: number): boolean {\n return patrolCycle % (janitor === 'feedback' ? 5 : 60) === 0;\n}\n","import { mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { Effect } from 'effect';\n\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getInternalTokenSync, INTERNAL_TOKEN_HEADER } from '../internal-token.js';\nimport { PENDING_PROMOTION_FILENAME, WORKSPACE_RUNTIME_DIRNAME } from '../pan-dir/types.js';\nimport { findSpecByIssue } from '../pan-dir/specs.js';\nimport { listProjects, type ProjectConfig } from '../projects.js';\nimport { findWorkspaceDraftPlanSync, readPlanSync } from '../xbrief/io.js';\nimport type { XBriefDocument } from '../xbrief/types.js';\nimport { recordDeadEndNeedsYou } from './dead-end-trip.js';\nimport { isIssueClosed } from './issue-closed.js';\n\nconst MARKER_GRACE_MS = 120_000;\nconst MARKERLESS_SPEC_GRACE_MS = 5 * 60_000;\nconst NEEDS_YOU_ATTEMPTS = 5;\n\ntype ProjectEntry = { key: string; config: Pick<ProjectConfig, 'name' | 'path'> };\n\nexport type PendingPromotionMarker = {\n version: '1';\n issueId: string;\n canonicalFilename: string;\n noPrd: boolean;\n autoSpawnRequested: boolean;\n finalizedAt: string;\n lastError: string;\n lastAttemptAt: string;\n patrolAttempts: number;\n};\n\nexport interface PendingPromotionMarkerIo {\n read(path: string, issueId: string): Promise<PendingPromotionMarker | null>;\n write(path: string, marker: PendingPromotionMarker): Promise<void>;\n remove(path: string): Promise<void>;\n}\n\nexport interface PendingPromotionCandidate {\n projectKey: string;\n projectName: string;\n projectPath: string;\n workspacePath: string;\n issueId: string;\n specPath: string;\n markerPath: string;\n marker: PendingPromotionMarker | null;\n canonicalFilename: string;\n noPrd: boolean;\n autoSpawnRequested: boolean;\n finalizedAt: string;\n patrolAttempts: number;\n}\n\nexport interface PendingPromotionReconcilerOptions {\n projects?: ProjectEntry[];\n clock?: () => Date;\n fetch?: typeof fetch;\n dashboardOrigin?: string;\n getInternalToken?: () => string | null;\n findPromotedSpec?: (projectPath: string, issueId: string) => Promise<unknown | null>;\n isClosed?: (issueId: string) => Promise<boolean>;\n recordNeedsYou?: typeof recordDeadEndNeedsYou;\n emitActivity?: typeof emitActivityEntrySync;\n markerIo?: PendingPromotionMarkerIo;\n}\n\nfunction normalizeIssueId(value: unknown): string | null {\n return typeof value === 'string' && /^[A-Za-z]+-\\d+$/.test(value.trim())\n ? value.trim().toUpperCase()\n : null;\n}\n\nfunction issueIdFromWorkspaceName(name: string): string | null {\n const match = name.match(/^feature-([a-z]+-\\d+)$/i);\n return match ? match[1]!.toUpperCase() : null;\n}\n\nfunction parseDate(value: unknown): number | null {\n if (typeof value !== 'string') return null;\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction parseMarker(value: unknown, expectedIssueId: string): PendingPromotionMarker | null {\n if (!value || typeof value !== 'object') return null;\n const marker = value as Record<string, unknown>;\n const issueId = normalizeIssueId(marker['issueId']);\n if (marker['version'] !== '1' || issueId !== expectedIssueId) return null;\n if (typeof marker['canonicalFilename'] !== 'string' || marker['canonicalFilename'].length === 0) return null;\n if (parseDate(marker['finalizedAt']) === null) return null;\n\n return {\n version: '1',\n issueId,\n canonicalFilename: marker['canonicalFilename'],\n noPrd: marker['noPrd'] === true,\n autoSpawnRequested: marker['autoSpawnRequested'] === true,\n finalizedAt: marker['finalizedAt'] as string,\n lastError: typeof marker['lastError'] === 'string' ? marker['lastError'] : 'complete-planning failed',\n lastAttemptAt: typeof marker['lastAttemptAt'] === 'string' ? marker['lastAttemptAt'] : marker['finalizedAt'] as string,\n patrolAttempts: Number.isInteger(marker['patrolAttempts']) && Number(marker['patrolAttempts']) >= 0\n ? Number(marker['patrolAttempts'])\n : 0,\n };\n}\n\nasync function readMarker(path: string, issueId: string): Promise<PendingPromotionMarker | null> {\n try {\n return parseMarker(JSON.parse(await readFile(path, 'utf-8')), issueId);\n } catch {\n return null;\n }\n}\n\nasync function loadProjects(projects?: ProjectEntry[]): Promise<ProjectEntry[]> {\n if (projects) return projects;\n return Effect.runPromise(listProjects().pipe(Effect.catch(() => Effect.succeed([]))));\n}\n\nasync function defaultFindPromotedSpec(projectPath: string, issueId: string): Promise<unknown | null> {\n return Effect.runPromise(\n findSpecByIssue(projectPath, issueId).pipe(Effect.catch(() => Effect.succeed(null))),\n );\n}\n\nfunction internalDashboardOrigin(): string {\n const port = Number.parseInt(process.env['API_PORT'] ?? process.env['PORT'] ?? '3011', 10);\n return process.env['OVERDECK_INTERNAL_DASHBOARD_URL'] ?? `http://127.0.0.1:${port}`;\n}\n\nfunction logDiagnostic(kind: string, info: Record<string, unknown> = {}): void {\n console.debug(`[pending-promotion-reconciler] ${kind}`, info);\n}\n\nfunction markerPathForWorkspace(workspacePath: string): string {\n return join(workspacePath, WORKSPACE_RUNTIME_DIRNAME, PENDING_PROMOTION_FILENAME);\n}\n\nfunction canonicalFilenameFromSpec(doc: XBriefDocument): string | null {\n const filename = doc.plan.metadata?.canonicalFilename;\n return typeof filename === 'string' && filename.length > 0 ? filename : null;\n}\n\nasync function writeMarker(path: string, marker: PendingPromotionMarker): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const tmp = `${path}.tmp`;\n await writeFile(tmp, JSON.stringify(marker, null, 2) + '\\n', 'utf-8');\n await rename(tmp, path);\n}\n\nconst defaultMarkerIo: PendingPromotionMarkerIo = {\n read: readMarker,\n write: writeMarker,\n remove: (path) => rm(path, { force: true }),\n};\n\nexport async function findPendingPromotionCandidates(\n options: PendingPromotionReconcilerOptions = {},\n): Promise<PendingPromotionCandidate[]> {\n const projects = await loadProjects(options.projects);\n const now = (options.clock ?? (() => new Date()))().getTime();\n const findPromotedSpec = options.findPromotedSpec ?? defaultFindPromotedSpec;\n const markerIo = options.markerIo ?? defaultMarkerIo;\n const candidates: PendingPromotionCandidate[] = [];\n\n for (const { key, config } of projects) {\n const workspacesDir = join(config.path, 'workspaces');\n const workspaces = await readdir(workspacesDir, { withFileTypes: true }).catch(() => []);\n for (const workspace of workspaces) {\n if (!workspace.isDirectory() || workspace.name.endsWith('-strike')) continue;\n const issueId = issueIdFromWorkspaceName(workspace.name);\n if (!issueId) continue;\n\n const workspacePath = join(workspacesDir, workspace.name);\n const markerPath = markerPathForWorkspace(workspacePath);\n const marker = await markerIo.read(markerPath, issueId);\n const specPath = findWorkspaceDraftPlanSync(workspacePath);\n if (!specPath) continue;\n\n let spec: XBriefDocument;\n try {\n spec = readPlanSync(specPath);\n } catch {\n logDiagnostic('candidate-skipped', { issueId, reason: 'spec-unreadable' });\n continue;\n }\n if (spec.plan.status !== 'proposed' || normalizeIssueId(spec.plan.id) !== issueId) continue;\n\n if (marker) {\n candidates.push({\n projectKey: key,\n projectName: config.name,\n projectPath: config.path,\n workspacePath,\n issueId,\n specPath,\n markerPath,\n marker,\n canonicalFilename: marker.canonicalFilename,\n noPrd: marker.noPrd,\n autoSpawnRequested: marker.autoSpawnRequested,\n finalizedAt: marker.finalizedAt,\n patrolAttempts: marker.patrolAttempts,\n });\n continue;\n }\n\n if (spec.plan.metadata?.promotionIntent === 'manual') continue;\n const specInfo = await stat(specPath).catch(() => null);\n if (!specInfo || now - specInfo.mtimeMs < MARKERLESS_SPEC_GRACE_MS) continue;\n if (await findPromotedSpec(config.path, issueId)) continue;\n const canonicalFilename = canonicalFilenameFromSpec(spec);\n if (!canonicalFilename) {\n logDiagnostic('candidate-skipped', { issueId, reason: 'canonical-filename-missing' });\n continue;\n }\n\n candidates.push({\n projectKey: key,\n projectName: config.name,\n projectPath: config.path,\n workspacePath,\n issueId,\n specPath,\n markerPath,\n marker: null,\n canonicalFilename,\n noPrd: false,\n autoSpawnRequested: false,\n finalizedAt: specInfo.mtime.toISOString(),\n patrolAttempts: 0,\n });\n }\n }\n\n return candidates;\n}\n\nfunction parseResponseBody(text: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(text);\n return parsed && typeof parsed === 'object' ? parsed as Record<string, unknown> : {};\n } catch {\n return {};\n }\n}\n\nfunction responseError(status: number, body: Record<string, unknown>, text: string): string {\n if (typeof body['error'] === 'string') return body['error'];\n if (typeof body['message'] === 'string') return body['message'];\n return text.slice(0, 200) || `complete-planning returned HTTP ${status}`;\n}\n\nfunction responseStillPending(status: number, body: Record<string, unknown>): boolean {\n return status === 202 || body['inFlight'] === true || typeof body['skipped'] === 'string';\n}\n\nexport async function reconcilePendingPromotions(\n options: PendingPromotionReconcilerOptions = {},\n): Promise<string[]> {\n const now = (options.clock ?? (() => new Date()))();\n const nowMs = now.getTime();\n const fetchImpl = options.fetch ?? fetch;\n const dashboardOrigin = options.dashboardOrigin ?? internalDashboardOrigin();\n const internalToken = (options.getInternalToken ?? getInternalTokenSync)();\n const findPromotedSpec = options.findPromotedSpec ?? defaultFindPromotedSpec;\n const isClosed = options.isClosed ?? ((issueId: string) => isIssueClosed(issueId));\n const recordNeedsYou = options.recordNeedsYou ?? recordDeadEndNeedsYou;\n const emitActivity = options.emitActivity ?? emitActivityEntrySync;\n const markerIo = options.markerIo ?? defaultMarkerIo;\n const candidates = await findPendingPromotionCandidates({ ...options, clock: () => now, findPromotedSpec, markerIo });\n const actions: string[] = [];\n\n for (const candidate of candidates) {\n if (await isClosed(candidate.issueId)) {\n logDiagnostic('candidate-skipped', { issueId: candidate.issueId, reason: 'closed' });\n continue;\n }\n\n if (await findPromotedSpec(candidate.projectPath, candidate.issueId)) {\n await markerIo.remove(candidate.markerPath);\n actions.push(`Cleared resolved pending-promotion marker for ${candidate.issueId}`);\n continue;\n }\n\n const finalizedAtMs = parseDate(candidate.finalizedAt);\n if (finalizedAtMs === null || nowMs - finalizedAtMs < MARKER_GRACE_MS) {\n logDiagnostic('candidate-skipped', { issueId: candidate.issueId, reason: 'grace-window' });\n continue;\n }\n\n let response: Response | null = null;\n let body: Record<string, unknown> = {};\n let error: string | null = null;\n try {\n response = await fetchImpl(\n new URL(`/api/issues/${encodeURIComponent(candidate.issueId)}/complete-planning`, dashboardOrigin),\n {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n origin: dashboardOrigin,\n ...(internalToken ? { [INTERNAL_TOKEN_HEADER]: internalToken } : {}),\n },\n body: JSON.stringify({\n noPrd: candidate.noPrd,\n startedBy: 'pending-promotion-reconciler',\n }),\n },\n );\n const text = await response.text();\n body = parseResponseBody(text);\n if (responseStillPending(response.status, body)) {\n logDiagnostic('promotion-deferred', { issueId: candidate.issueId, status: response.status, body });\n continue;\n }\n if (response.status !== 200 || !response.ok || body['success'] === false || body['ok'] === false) {\n error = responseError(response.status, body, text);\n }\n } catch (cause) {\n error = cause instanceof Error ? cause.message : String(cause);\n }\n\n if (!error && response?.status === 200) {\n await markerIo.remove(candidate.markerPath);\n const action = `Recovered pending planning promotion for ${candidate.issueId}`;\n actions.push(action);\n emitActivity({\n source: 'cloister',\n level: 'success',\n issueId: candidate.issueId,\n message: `${candidate.issueId} planning promotion recovered automatically`,\n details: JSON.stringify({\n issueId: candidate.issueId,\n workspacePath: candidate.workspacePath,\n reconciler: 'pending-promotion',\n timestamp: now.toISOString(),\n }),\n });\n continue;\n }\n\n const nextAttempts = candidate.patrolAttempts + 1;\n const lastError = error ?? 'complete-planning did not converge';\n await markerIo.write(candidate.markerPath, {\n version: '1',\n issueId: candidate.issueId,\n canonicalFilename: candidate.canonicalFilename,\n noPrd: candidate.noPrd,\n autoSpawnRequested: candidate.autoSpawnRequested,\n finalizedAt: candidate.finalizedAt,\n lastError,\n lastAttemptAt: now.toISOString(),\n patrolAttempts: nextAttempts,\n });\n logDiagnostic('promotion-failed', { issueId: candidate.issueId, patrolAttempts: nextAttempts, error: lastError });\n actions.push(`Pending planning promotion for ${candidate.issueId} failed attempt ${nextAttempts}: ${lastError}`);\n\n if (nextAttempts >= NEEDS_YOU_ATTEMPTS) {\n await recordNeedsYou(\n candidate.issueId,\n 'pending-promotion',\n candidate.finalizedAt,\n `Planning promotion for ${candidate.issueId} has failed ${nextAttempts} patrol attempts. The finalized plan remains unpromoted. Run \\`pan plan done ${candidate.issueId}\\` to retry manually, then inspect the complete-planning error if it still fails.`,\n );\n }\n }\n\n return actions;\n}\n","import { Effect } from 'effect';\n\nexport interface TestStatusGreenCiReviewStatus {\n reviewStatus?: string | null;\n testStatus?: string | null;\n mergeStatus?: string | null;\n prUrl?: string | null;\n}\n\nexport interface TestStatusGreenCiPullRequestState {\n state: 'OPEN' | 'CLOSED';\n merged: boolean;\n headSha: string;\n}\n\nexport interface TestStatusGreenCiCheckRunSummary {\n name: string;\n htmlUrl?: string;\n}\n\nexport interface TestStatusGreenCiCheckRunsState {\n verdict: 'green' | 'pending' | 'red';\n successCount: number;\n successfulRuns: TestStatusGreenCiCheckRunSummary[];\n}\n\nexport interface TestStatusGreenCiDeps {\n isGitHubAppConfigured(): boolean;\n loadReviewStatuses(): Record<string, TestStatusGreenCiReviewStatus>;\n getPullRequestHeadState(\n owner: string,\n repo: string,\n number: number,\n ): Effect.Effect<TestStatusGreenCiPullRequestState, unknown>;\n getCiCheckRunsState(\n owner: string,\n repo: string,\n sha: string,\n ): Effect.Effect<TestStatusGreenCiCheckRunsState, unknown>;\n setReviewStatusSync(issueId: string, update: { testStatus: 'passed'; testNotes: string }): void;\n cooldowns: Map<string, number>;\n cooldownMs: number;\n now(): number;\n log(message: string): void;\n warn(message: string): void;\n}\n\nconst terminalMergeStatuses = new Set(['merged', 'merging', 'queued', 'verifying']);\n\nexport async function reconcileTestStatusFromGreenCiWithDeps(\n deps: TestStatusGreenCiDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n try {\n if (!deps.isGitHubAppConfigured()) return actions;\n\n const statuses = deps.loadReviewStatuses();\n const now = deps.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'passed') continue;\n if (status.testStatus !== 'pending') continue;\n if (terminalMergeStatuses.has(status.mergeStatus || '')) continue;\n if (!status.prUrl) continue;\n\n const cooledUntil = deps.cooldowns.get(issueId);\n if (cooledUntil && now < cooledUntil) continue;\n\n const match = status.prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!match) continue;\n const [, owner, repo, numberStr] = match;\n const prNumber = parseInt(numberStr, 10);\n if (!Number.isFinite(prNumber)) continue;\n\n try {\n const prState = await Effect.runPromise(deps.getPullRequestHeadState(owner, repo, prNumber));\n if (prState.state !== 'OPEN' || prState.merged || !prState.headSha) continue;\n\n const ciState = await Effect.runPromise(deps.getCiCheckRunsState(owner, repo, prState.headSha));\n if (ciState.verdict !== 'green') {\n deps.cooldowns.set(issueId, now + deps.cooldownMs);\n continue;\n }\n\n const firstRun = ciState.successfulRuns[0];\n const runLabel = firstRun?.htmlUrl\n ? `${firstRun.name} (${firstRun.htmlUrl})`\n : (firstRun?.name || `${ciState.successCount} successful CI check-run(s)`);\n const shortSha = prState.headSha.slice(0, 8);\n deps.setReviewStatusSync(issueId, {\n testStatus: 'passed',\n testNotes: `Reconciled from green GitHub Actions CI on ${shortSha}: ${runLabel}`,\n });\n deps.cooldowns.delete(issueId);\n const msg = `Reconciled testStatus=pending → passed for ${issueId} from green CI on PR #${prNumber} @ ${shortSha}`;\n actions.push(msg);\n deps.log(msg);\n } catch (prErr: any) {\n deps.cooldowns.set(issueId, now + deps.cooldownMs);\n deps.warn(`reconcileTestStatusFromGreenCi: ${issueId} PR/CI lookup failed: ${prErr.message}`);\n }\n }\n } catch (err: any) {\n deps.warn(`Error in reconcileTestStatusFromGreenCi: ${err.message}`);\n }\n return actions;\n}\n","/**\n * Deacon janitor (PAN-1625): reap orphaned `dist/dashboard/server.js` processes.\n *\n * A failed `pan dev` / `pan up` restart can leave a second dashboard server\n * running that already lost the port. It is not harmless — it can keep serving\n * stale data and, worse, run a SECOND Deacon racing the live one (single-deacon\n * invariant; PAN-821/PAN-698).\n *\n * THIS RUNS INSIDE THE LIVE SERVER PROCESS. The Deacon is in-process with\n * `dist/dashboard/server.js`, so `process.pid` IS the live server. The hard\n * safety rule: never kill `process.pid`, never kill the current port owner,\n * never kill a just-spawned server (could be a legitimate restart handoff), and\n * never kill a process living inside a container (a workspace-container server\n * is legitimate — only host-level orphans are reaped). Everything is built on a\n * pure, unit-tested selection function so the kill set is auditable.\n */\nimport { execFile } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { isDeaconGloballyPaused } from '../overdeck/control-settings.js';\nimport { loadConfig } from '../config.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst DEFAULT_MIN_AGE_SECONDS = 120;\nconst REAP_GRACE_MS = 5000;\nconst SERVER_SCRIPT = 'dist/dashboard/server.js';\n\nexport interface DashboardServerProc {\n pid: number;\n /** elapsed seconds since the process started (ps etimes). */\n ageSeconds: number;\n}\n\nexport interface SelectOrphanOptions {\n servers: DashboardServerProc[];\n /** The live server's own pid — NEVER reaped. */\n selfPid: number;\n /** The pid currently listening on the dashboard API port — NEVER reaped. */\n portOwnerPid: number | null;\n /** Don't reap a server younger than this (avoids racing a restart handoff). */\n minAgeSeconds?: number;\n}\n\n/**\n * Pure selection of which dashboard-server pids are safe to reap. Excludes the\n * deacon's own process, the live port owner, and any just-spawned server. Fully\n * deterministic and side-effect-free so the kill set can be unit-tested.\n */\nexport function selectOrphanServerPids(opts: SelectOrphanOptions): number[] {\n const minAge = opts.minAgeSeconds ?? DEFAULT_MIN_AGE_SECONDS;\n return opts.servers\n .filter((s) => s.pid !== opts.selfPid)\n .filter((s) => opts.portOwnerPid == null || s.pid !== opts.portOwnerPid)\n .filter((s) => s.ageSeconds >= minAge)\n .map((s) => s.pid);\n}\n\n/** Parse `ps -eo pid=,etimes=,args=` output into dashboard-server processes.\n * Exported for testing — only true `node … dist/dashboard/server.js`\n * invocations match; shell/grep/ps lines that merely mention the path do not. */\nexport function parseDashboardServerProcs(psStdout: string): DashboardServerProc[] {\n const procs: DashboardServerProc[] = [];\n for (const rawLine of psStdout.split('\\n')) {\n const line = rawLine.trim();\n const m = line.match(/^(\\d+)\\s+(\\d+)\\s+(.*)$/);\n if (!m) continue;\n const [, pidStr, ageStr, args] = m;\n if (!args.includes(SERVER_SCRIPT)) continue;\n // The executable (first token) must be a node binary — excludes\n // `/bin/bash -c '… dist/dashboard/server.js …'`, grep, ps, etc.\n const exe = args.split(/\\s+/)[0] ?? '';\n if (!/(^|\\/)node(js)?$/.test(exe)) continue;\n procs.push({ pid: Number(pidStr), ageSeconds: Number(ageStr) });\n }\n return procs;\n}\n\nasync function listDashboardServers(): Promise<DashboardServerProc[]> {\n try {\n const { stdout } = await execFileAsync('ps', ['-eo', 'pid=,etimes=,args='], {\n encoding: 'utf-8',\n maxBuffer: 8 * 1024 * 1024,\n });\n return parseDashboardServerProcs(stdout);\n } catch {\n return [];\n }\n}\n\n/** Best-effort: which pid listens on the dashboard API port. Returns null when\n * it can't be determined — `selfPid` remains the primary guard. */\nasync function resolvePortOwner(port: number): Promise<number | null> {\n try {\n const { stdout } = await execFileAsync('lsof', ['-ti', `tcp:${port}`, '-sTCP:LISTEN'], { encoding: 'utf-8' });\n const pid = Number(stdout.trim().split('\\n')[0]);\n return Number.isFinite(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\n/** True if the pid lives inside a container (docker/containerd/kube). A\n * workspace-container dashboard server is legitimate and must never be reaped\n * by the host deacon. On platforms without /proc (macOS) container processes\n * aren't host-visible, so this is a no-op (returns false). */\nasync function isContainerProcess(pid: number): Promise<boolean> {\n try {\n const cgroup = await readFile(`/proc/${pid}/cgroup`, 'utf-8');\n return /docker|containerd|kubepods|libpod/.test(cgroup);\n } catch {\n return false;\n }\n}\n\nexport interface ReapDeps {\n selfPid?: number;\n apiPort?: number;\n listServers?: () => Promise<DashboardServerProc[]>;\n resolvePortOwner?: (port: number) => Promise<number | null>;\n isContainerProcess?: (pid: number) => Promise<boolean>;\n kill?: (pid: number, signal: NodeJS.Signals | 0) => void;\n minAgeSeconds?: number;\n graceMs?: number;\n}\n\n/**\n * Find and reap orphaned host-level dashboard servers. Returns a list of\n * human-readable action strings (one per reap) for the patrol log. Honors the\n * global deacon pause and emits a `cloister` activity event per reap so it\n * surfaces in the Deacon activity view.\n */\nexport async function reapOrphanedDashboardServers(deps: ReapDeps = {}): Promise<string[]> {\n if (isDeaconGloballyPaused()) return [];\n\n const selfPid = deps.selfPid ?? process.pid;\n const kill = deps.kill ?? ((pid, signal) => process.kill(pid, signal));\n const graceMs = deps.graceMs ?? REAP_GRACE_MS;\n const apiPort = deps.apiPort ?? await Effect.runPromise(\n loadConfig().pipe(\n Effect.map((c) => c.dashboard.api_port ?? 3011),\n Effect.catch(() => Effect.succeed(3011)),\n ),\n );\n\n const listServers = deps.listServers ?? listDashboardServers;\n const portOwner = deps.resolvePortOwner ?? resolvePortOwner;\n const inContainer = deps.isContainerProcess ?? isContainerProcess;\n\n const [servers, portOwnerPid] = await Promise.all([listServers(), portOwner(apiPort)]);\n let candidates = selectOrphanServerPids({\n servers,\n selfPid,\n portOwnerPid,\n minAgeSeconds: deps.minAgeSeconds,\n });\n\n // Drop any candidate that lives inside a container (legitimate workspace server).\n const containerFlags = await Promise.all(candidates.map((pid) => inContainer(pid)));\n candidates = candidates.filter((_, i) => !containerFlags[i]);\n\n if (candidates.length === 0) return [];\n\n // SIGTERM first, then escalate survivors to SIGKILL after a grace window.\n for (const pid of candidates) {\n try { kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n await new Promise((resolve) => setTimeout(resolve, graceMs));\n\n const actions: string[] = [];\n for (const pid of candidates) {\n let alive = false;\n try { kill(pid, 0); alive = true; } catch { alive = false; }\n if (alive) {\n try { kill(pid, 'SIGKILL'); } catch { /* race: gone between check and kill */ }\n }\n const message = `Reaped orphan dashboard server pid ${pid}`;\n logDeaconEventSync(`[orphan-server-reaper] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'warn', message: `Deacon ${message.toLowerCase()}` });\n actions.push(message);\n }\n return actions;\n}\n","/**\n * Deacon janitor (PAN-1706): reap leftover `playwright-mcp` servers and their\n * headless Chromium trees.\n *\n * Agent sessions spawn `npx @playwright/mcp --isolated` for browser UAT and\n * routinely leave the browser open on the dashboard when they finish. Each\n * leftover headless page runs the full React app with all its pollers, so a\n * handful of ghosts multiplies dashboard load several-fold (observed 4×\n * during the PAN-1705 queueing collapse).\n *\n * Two independent rules, both built on a pure selection function:\n *\n * 1. ORPHANED — the mcp server's ancestor chain no longer contains a harness\n * process (claude / pi / codex / pty-supervisor): the owning session is\n * gone, the whole tree (mcp server + browser) is reaped. A minimum age\n * avoids racing a session that is still starting up.\n * 2. STALE BROWSER — the owner is still alive but the browser has been up\n * longer than any legitimate UAT run. Only the Chromium descendants are\n * killed; playwright-mcp launches a fresh browser on the session's next\n * tool call, so a live session loses nothing.\n */\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { isDeaconGloballyPaused } from '../overdeck/control-settings.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst DEFAULT_ORPHAN_MIN_AGE_SECONDS = 10 * 60;\nconst DEFAULT_STALE_BROWSER_AGE_SECONDS = 2 * 60 * 60;\nconst REAP_GRACE_MS = 5000;\n\n/** A harness process anywhere in the ancestor chain marks the tree as owned. */\nconst HARNESS_ARGS_RE = /(^|[/\\s])(claude|pi|codex)([\\s]|$)|pty-supervisor/;\nconst PLAYWRIGHT_MCP_RE = /playwright-mcp|@playwright\\/mcp/;\nconst BROWSER_ARGS_RE = /(^|\\/)(chrome|chromium|headless_shell)([\\s]|$)|--type=|--headless/;\n\nexport interface ProcEntry {\n pid: number;\n ppid: number;\n /** elapsed seconds since the process started (ps etimes). */\n ageSeconds: number;\n args: string;\n}\n\nexport interface PlaywrightReapTargets {\n /** Whole orphaned trees: mcp server pids + all their descendants. */\n orphanTreePids: number[];\n /** Stale browser processes under still-owned mcp servers. */\n staleBrowserPids: number[];\n}\n\nexport interface SelectPlaywrightReapOptions {\n procs: ProcEntry[];\n orphanMinAgeSeconds?: number;\n staleBrowserAgeSeconds?: number;\n}\n\n/** Parse `ps -eo pid=,ppid=,etimes=,args=` output. Exported for testing. */\nexport function parseProcTable(psStdout: string): ProcEntry[] {\n const procs: ProcEntry[] = [];\n for (const rawLine of psStdout.split('\\n')) {\n const m = rawLine.trim().match(/^(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(.*)$/);\n if (!m) continue;\n procs.push({\n pid: Number(m[1]),\n ppid: Number(m[2]),\n ageSeconds: Number(m[3]),\n args: m[4],\n });\n }\n return procs;\n}\n\nfunction collectDescendants(rootPid: number, childrenByPpid: Map<number, ProcEntry[]>): ProcEntry[] {\n const result: ProcEntry[] = [];\n const queue = [rootPid];\n while (queue.length > 0) {\n const pid = queue.shift()!;\n for (const child of childrenByPpid.get(pid) ?? []) {\n result.push(child);\n queue.push(child.pid);\n }\n }\n return result;\n}\n\n/** Walk ancestors of `pid`; true if any of them looks like a harness process. */\nfunction hasHarnessAncestor(pid: number, byPid: Map<number, ProcEntry>): boolean {\n let current = byPid.get(pid);\n const seen = new Set<number>();\n while (current && current.ppid > 1 && !seen.has(current.pid)) {\n seen.add(current.pid);\n const parent = byPid.get(current.ppid);\n if (!parent) break;\n if (HARNESS_ARGS_RE.test(parent.args)) return true;\n current = parent;\n }\n return false;\n}\n\n/**\n * Pure selection of which pids to reap. Deterministic and side-effect-free so\n * the kill set can be unit-tested.\n */\nexport function selectPlaywrightReapTargets(opts: SelectPlaywrightReapOptions): PlaywrightReapTargets {\n const orphanMinAge = opts.orphanMinAgeSeconds ?? DEFAULT_ORPHAN_MIN_AGE_SECONDS;\n const staleBrowserAge = opts.staleBrowserAgeSeconds ?? DEFAULT_STALE_BROWSER_AGE_SECONDS;\n\n const byPid = new Map(opts.procs.map((p) => [p.pid, p]));\n const childrenByPpid = new Map<number, ProcEntry[]>();\n for (const p of opts.procs) {\n const list = childrenByPpid.get(p.ppid);\n if (list) list.push(p);\n else childrenByPpid.set(p.ppid, [p]);\n }\n\n // mcp \"servers\" are the node processes actually running playwright-mcp —\n // not sh/npm wrappers (those die with the tree anyway) and not browsers.\n const mcpServers = opts.procs.filter(\n (p) => PLAYWRIGHT_MCP_RE.test(p.args) && !BROWSER_ARGS_RE.test(p.args) && /(^|\\/)node(js)?\\s/.test(p.args),\n );\n\n const orphanTreePids = new Set<number>();\n const staleBrowserPids = new Set<number>();\n\n for (const server of mcpServers) {\n const descendants = collectDescendants(server.pid, childrenByPpid);\n if (!hasHarnessAncestor(server.pid, byPid)) {\n if (server.ageSeconds >= orphanMinAge) {\n orphanTreePids.add(server.pid);\n for (const d of descendants) orphanTreePids.add(d.pid);\n }\n continue;\n }\n for (const d of descendants) {\n if (BROWSER_ARGS_RE.test(d.args) && d.ageSeconds >= staleBrowserAge) {\n staleBrowserPids.add(d.pid);\n }\n }\n }\n\n return {\n orphanTreePids: [...orphanTreePids],\n staleBrowserPids: [...staleBrowserPids].filter((pid) => !orphanTreePids.has(pid)),\n };\n}\n\nasync function listProcs(): Promise<ProcEntry[]> {\n try {\n const { stdout } = await execFileAsync('ps', ['-eo', 'pid=,ppid=,etimes=,args='], {\n encoding: 'utf-8',\n maxBuffer: 8 * 1024 * 1024,\n });\n return parseProcTable(stdout);\n } catch {\n return [];\n }\n}\n\nexport interface PlaywrightReapDeps {\n listProcs?: () => Promise<ProcEntry[]>;\n kill?: (pid: number, signal: NodeJS.Signals | 0) => void;\n orphanMinAgeSeconds?: number;\n staleBrowserAgeSeconds?: number;\n graceMs?: number;\n}\n\n/**\n * Find and reap leftover playwright-mcp trees / stale browsers. Returns\n * human-readable action strings for the patrol log. Honors the global deacon\n * pause and emits a `cloister` activity event per reap.\n */\nexport async function reapLeftoverPlaywrightBrowsers(deps: PlaywrightReapDeps = {}): Promise<string[]> {\n if (isDeaconGloballyPaused()) return [];\n\n const kill = deps.kill ?? ((pid, signal) => process.kill(pid, signal));\n const graceMs = deps.graceMs ?? REAP_GRACE_MS;\n const procs = await (deps.listProcs ?? listProcs)();\n const targets = selectPlaywrightReapTargets({\n procs,\n orphanMinAgeSeconds: deps.orphanMinAgeSeconds,\n staleBrowserAgeSeconds: deps.staleBrowserAgeSeconds,\n });\n\n const labelled: Array<{ pid: number; label: string }> = [\n ...targets.orphanTreePids.map((pid) => ({ pid, label: 'orphaned playwright-mcp tree' })),\n ...targets.staleBrowserPids.map((pid) => ({ pid, label: 'stale playwright browser' })),\n ];\n if (labelled.length === 0) return [];\n\n for (const { pid } of labelled) {\n try { kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n await new Promise((resolve) => setTimeout(resolve, graceMs));\n\n const actions: string[] = [];\n for (const { pid, label } of labelled) {\n let alive = false;\n try { kill(pid, 0); alive = true; } catch { alive = false; }\n if (alive) {\n try { kill(pid, 'SIGKILL'); } catch { /* race: gone between check and kill */ }\n }\n const message = `Reaped ${label} pid ${pid}`;\n logDeaconEventSync(`[playwright-reaper] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'warn', message: `Deacon ${message.toLowerCase()}` });\n actions.push(message);\n }\n return actions;\n}\n","/**\n * Strike workspace reaper (PAN-1882, PAN-2359).\n *\n * Strikes bypass the normal pipeline — and its close-out workspace teardown —\n * so their `feature-<id>-strike` git worktrees + `strike/<id>` branches pile up\n * forever after the strike merges (27 / ~16GB observed 2026-06-14). The strike\n * role ends at \"report the result\" with no cleanup, and a strike can't easily\n * remove the worktree it is running in. This deacon reaper does it: each patrol\n * it removes a `strike/<id>` worktree when its branch is fully merged into\n * `origin/main` AND no live `strike-<id>` session exists.\n *\n * Safety: only `strike/*` worktrees are eligible (never `feature/*`, the active\n * pipeline); a worktree is reaped only when its branch AUTHORED commits that are\n * now all in origin/main (walk-reflogs > 1 AND 0 commits ahead) and no live\n * strike session is using it. A freshly-created strike branch sits at\n * origin/main's HEAD with a single reflog entry, so the extra reflog guard\n * prevents reaping a workspace before the agent has committed any work.\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { sessionExistsSync } from '../tmux.js';\n\nconst execAsync = promisify(exec);\n\ninterface WorktreeRecord {\n path: string;\n branch: string;\n}\n\n/** Parse `git worktree list --porcelain` into {path, branch} records. */\nfunction parseWorktreePorcelain(porcelain: string): WorktreeRecord[] {\n const records: WorktreeRecord[] = [];\n let currentPath = '';\n for (const line of porcelain.split('\\n')) {\n if (line.startsWith('worktree ')) {\n currentPath = line.slice('worktree '.length).trim();\n } else if (line.startsWith('branch ')) {\n const branch = line.slice('branch '.length).trim().replace(/^refs\\/heads\\//, '');\n if (currentPath) records.push({ path: currentPath, branch });\n currentPath = '';\n } else if (line.trim() === '') {\n currentPath = '';\n }\n }\n return records;\n}\n\nexport async function reapMergedStrikeWorkspaces(projectRoot: string = process.cwd()): Promise<string[]> {\n const actions: string[] = [];\n\n let porcelain: string;\n try {\n const { stdout } = await execAsync('git worktree list --porcelain', { cwd: projectRoot, encoding: 'utf-8' });\n porcelain = stdout;\n } catch {\n return actions; // not a git repo / no worktrees — nothing to do\n }\n\n for (const { path, branch } of parseWorktreePorcelain(porcelain)) {\n if (!branch.startsWith('strike/')) continue; // strike worktrees only — never feature/*\n const issueId = branch.slice('strike/'.length); // e.g. \"pan-1864\"\n if (!issueId) continue;\n\n // A live strike is using this worktree — leave it alone.\n if (sessionExistsSync(`strike-${issueId}`)) continue;\n\n // Reap only when the branch is fully merged (0 commits ahead of origin/main),\n // so unmerged strike work is never lost.\n let ahead: string;\n try {\n const { stdout } = await execAsync(\n `git rev-list --count origin/main..${branch}`,\n { cwd: projectRoot, encoding: 'utf-8' },\n );\n ahead = stdout.trim();\n } catch {\n continue; // can't determine merge state — leave it for a future patrol\n }\n if (ahead !== '0') continue; // has unmerged commits — never reap\n\n // A fresh strike branch is created at origin/main's HEAD and is also\n // 0 commits ahead, but it has not authored any commits yet. Only reap\n // branches that actually authored commits and are now fully merged.\n let reflogCount: number;\n try {\n const { stdout } = await execAsync(\n `git rev-list --walk-reflogs --count ${JSON.stringify(branch)}`,\n { cwd: projectRoot, encoding: 'utf-8' },\n );\n reflogCount = Number(stdout.trim());\n if (Number.isNaN(reflogCount)) continue;\n } catch {\n continue; // can't determine authorship — leave it for a future patrol\n }\n if (reflogCount <= 1) continue; // never-committed branch (fresh strike) — don't reap\n\n try {\n await execAsync(`git worktree remove ${JSON.stringify(path)} --force`, { cwd: projectRoot });\n // Branch is an ancestor of origin/main (verified above) — safe to delete.\n await execAsync(`git branch -D ${JSON.stringify(branch)}`, { cwd: projectRoot }).catch(() => {});\n const action = `Reaped merged strike workspace ${path} (branch ${branch})`;\n actions.push(action);\n console.log(`[deacon] ${action}`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message: `[deacon] ${action}` });\n } catch {\n // worktree busy / already gone — retry next patrol\n }\n }\n\n return actions;\n}\n","import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from '../paths.js';\nimport { isPaneDead, killSession, listSessionNames } from '../tmux.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\n\nfunction describeReason(hasStateFile: boolean, paneDead: boolean): string {\n if (!hasStateFile && paneDead) return 'missing state.json and pane is dead';\n if (!hasStateFile) return 'missing state.json';\n return 'pane is dead';\n}\n\n/**\n * PAN-1559: inspect sessions are sub-role tmux sessions named `inspect-*`.\n * Any live inspect pane without agent state is untracked compute, and a dead\n * inspect pane is a zombie. Kill either shape so Deacon does not rely on the\n * stale agent-state janitor to eventually notice the directory.\n */\nexport async function cleanupOrphanedInspectSessions(): Promise<string[]> {\n const actions: string[] = [];\n let inspectSessions: readonly string[];\n\n try {\n inspectSessions = (await Effect.runPromise(listSessionNames()))\n .filter(session => session.startsWith('inspect-'));\n } catch {\n return actions;\n }\n\n for (const session of inspectSessions) {\n const statePath = join(AGENTS_DIR, session, 'state.json');\n const hasStateFile = existsSync(statePath);\n const paneDead = await Effect.runPromise(isPaneDead(session)).catch(() => false);\n\n if (hasStateFile && !paneDead) continue;\n\n try {\n await Effect.runPromise(killSession(session)).catch(() => {});\n } catch {\n // best-effort; keep the patrol moving\n }\n\n const reason = describeReason(hasStateFile, paneDead);\n const msg = `Killed orphaned inspect session ${session} (${reason})`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`cleanupOrphanedInspectSessions: ${msg}`);\n }\n\n if (actions.length > 0) {\n logDeaconEventSync(`cleanupOrphanedInspectSessions completed: killed ${actions.length} session(s)`);\n } else {\n logDeaconEventSync('cleanupOrphanedInspectSessions completed: no orphaned sessions found');\n }\n\n return actions;\n}\n","/**\n * PAN-3092: sweep stranded workspace verdict fallbacks.\n *\n * The fallback drain is scheduled by the process that wrote the fallback, on\n * unref'd timers. In a short-lived CLI (`pan admin specialists done` exits in\n * under a second) those timers never fire, so a verdict that failed its journal\n * write under lock contention can sit in `<workspace>/.overdeck/pipeline-verdict.json`\n * with nothing left alive to fold it. This patrol is the external retry the CLI\n * cannot provide.\n *\n * It also surfaces the case the retry cannot fix: a fallback still undrained ten\n * minutes on means the record lock has been held that long, and the operator\n * should hear about it once — MIN-902 spent an hour in exactly that state while\n * every surface reported the reviewer healthy.\n */\n\nimport { loadReviewStatuses } from '../review-status.js';\nimport {\n drainWorkspaceVerdictFallback,\n findWorkspaceVerdictConflicts,\n readWorkspaceVerdictFallback,\n} from '../overdeck/review-status-record-sync.js';\nimport type { VerdictConflict } from '../pan-dir/pipeline-verdict-merge.js';\nimport { resolveProjectFromIssueSync, getProjectSync } from '../projects.js';\nimport { readOwner, recordLockPath } from '../pan-dir/fs-lock.js';\nimport { findWorkspacePath } from '../lifecycle/archive-planning.js';\nimport { findRecoveryTrip, recordRecoveryFailure } from './recovery-trip.js';\nimport { emitActivityEntryOnce, type ActivityEmitOutcome } from '../activity-logger.js';\n\n/** How long a fallback may stay undrained before the operator hears about it. */\nexport const VERDICT_CONTENTION_SURFACE_MS = 10 * 60 * 1000;\n\n/**\n * Total time this sweep may spend waiting on warning delivery, across ALL issues.\n *\n * Bounding each request alone is not enough: the sweep visits issues serially,\n * so N stranded fallbacks against a stalled dashboard would cost N × the\n * per-request deadline — six would consume a whole 60s patrol interval and dozens\n * would hold it for many minutes, starving every later issue and later patrol\n * phase while the patrol heartbeat still reported healthy. The systemic\n * degradation that strands several verdicts at once is exactly the state that\n * makes every loopback request hit its deadline, so the budget is shared.\n */\nexport const SWEEP_WARNING_BUDGET_MS = 30 * 1000;\n\n/**\n * Resolve `attempt`, or give up at `deadlineMs` and report `failed`.\n *\n * Abandoning a warning is always safe: nothing is marked warned on `failed`, and\n * a later retry carries the same idempotency key, so a call that lands after we\n * stopped waiting returns `duplicate` rather than emitting a second warning.\n */\nasync function withWarningDeadline(\n attempt: Promise<ActivityEmitOutcome>,\n deadlineMs: number,\n): Promise<ActivityEmitOutcome> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n attempt,\n new Promise<ActivityEmitOutcome>((resolve) => {\n timer = setTimeout(() => { resolve('failed'); }, deadlineMs);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/**\n * Episodes already warned about in THIS process, keyed by recovery path + issue\n * + the fallback's own `updatedAt`.\n *\n * Only an optimisation, never the dedupe of record: it saves re-querying the\n * event log every patrol. Correctness across restarts comes from the two\n * durable planes — the open recovery trip, and `emitActivityEntryOnce`'s\n * event-log check on the episode id. An episode is added here only after the\n * warning is confirmed durable, so a failed append is retried rather than\n * suppressed, and entries are dropped once the trip lands.\n */\nconst warnedEpisodes = new Set<string>();\n\n/**\n * Describe a verdict conflict and the one action that reliably clears it.\n *\n * Deliberately does NOT hand out a `pan admin specialists done` command. That\n * command is not a general conflict resolver: `merge` takes passed|failed and\n * maps them to merged|failed (so the displayed `merged` is rejected), `inspect`\n * requires an `--item` this pipeline-level conflict cannot identify, `review`\n * does not accept `skipped`, verification has no specialist at all, and\n * re-signalling the value already in the journal writes no replacement fallback\n * — so the conflicting one survives and every later drain withholds it again.\n * A conflict can also span several gates, which one command cannot settle.\n *\n * A strictly newer review cycle is the only instruction correct for every gate\n * and every choice, so that is what the operator is told, along with what it\n * costs. A dedicated resolve-this-generation operation through the verdict write\n * door would let an operator adopt a specific written verdict instead; until\n * that exists, promising it here would be a lie.\n */\nfunction conflictMessage(issueId: string, conflicts: VerdictConflict[], ageMinutes: number): string {\n const detail = conflicts\n .map((c) => `${c.gate} (the record holds \"${c.journalValue}\", the workspace fallback holds \"${c.fallbackValue}\")`)\n .join('; ');\n const gateCount = conflicts.length === 1 ? 'one gate' : `${conflicts.length} gates`;\n return (\n `${issueId} — two different verdicts have been written for ${gateCount} and Overdeck cannot tell which is `\n + `newer, so it refuses to choose: ${detail}. This has been unresolved for ${ageMinutes}min and will NOT `\n + `clear on its own: the fold is withheld on every patrol, by design, so that neither written verdict is `\n + `destroyed. An operator must decide. The one action that reliably clears it for every gate is to dispatch `\n + `a strictly newer review cycle for ${issueId} — that supersedes the stranded fallback and re-runs the `\n + `gates, rather than adopting either of the verdicts already written. Overdeck has no command today that `\n + `adopts one specific written verdict, so do not expect a re-signal of an existing value to clear this.`\n );\n}\n\nasync function lockOwnerDescription(issueId: string): Promise<string> {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return 'unknown writer';\n const project = getProjectSync(resolved.projectKey);\n if (!project) return 'unknown writer';\n return (await readOwner(recordLockPath(project, issueId))).description;\n } catch {\n return 'unknown writer';\n }\n}\n\n/**\n * Drain every pending workspace verdict fallback, and surface the ones that stay\n * stuck. Returns one action line per drain or escalation, for the patrol log.\n */\ninterface WarningCandidate {\n issueId: string;\n workspacePath: string;\n recoveryPath: string;\n generation: string;\n episode: string;\n message: string;\n summary: string;\n}\n\n/**\n * Drain every pending workspace verdict fallback, and surface the ones that stay\n * stuck. Returns one action line per drain or escalation, for the patrol log.\n *\n * `budgetMs` bounds the total time spent waiting on warning delivery. The deacon\n * derives it from the resolved patrol interval so a shortened interval cannot be\n * overrun by the sweep.\n */\nexport async function sweepStrandedVerdictFallbacks(\n now = Date.now(),\n budgetMs = SWEEP_WARNING_BUDGET_MS,\n): Promise<string[]> {\n const actions: string[] = [];\n let statuses: Record<string, { mergeStatus?: string; closedOut?: boolean; stuck?: boolean; deaconIgnored?: boolean }>;\n try {\n statuses = loadReviewStatuses();\n } catch {\n return actions;\n }\n\n // ── Phase 1: drain everything, and collect who needs a warning ──────────────\n const candidates: WarningCandidate[] = [];\n\n for (const [issueId, status] of Object.entries(statuses)) {\n // A merged or closed-out issue's fallback is history, not a pending verdict.\n if (status.mergeStatus === 'merged' || status.closedOut) continue;\n\n try {\n const fallback = await readWorkspaceVerdictFallback(issueId);\n if (!fallback) continue;\n\n if (await drainWorkspaceVerdictFallback(issueId)) {\n actions.push(`Drained stranded verdict fallback for ${issueId}`);\n continue;\n }\n\n const ageMs = now - Date.parse(fallback.updatedAt);\n if (!Number.isFinite(ageMs) || ageMs < VERDICT_CONTENTION_SURFACE_MS) continue;\n // A stuck or deacon-ignored issue is already an operator's problem; drain\n // it, but do not add a second voice on top of the one they already have.\n if (status.stuck || status.deaconIgnored) continue;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n const workspacePath = resolved\n ? findWorkspacePath(resolved.projectPath, issueId.toLowerCase())\n : null;\n if (!workspacePath) continue;\n\n const ageMinutes = Math.round(ageMs / 60_000);\n // Two different failures land here. Contention clears on its own; a\n // verdict conflict never does, because every later drain withholds the\n // same fold. Telling an operator to wait for the second one would be\n // false remediation, so name which it is before saying anything.\n const conflicts = await findWorkspaceVerdictConflicts(issueId, fallback);\n const conflicted = conflicts.length > 0;\n const recoveryPath = conflicted ? 'verdict-fallback-conflict' : 'verdict-fallback-contention';\n const episode = `${recoveryPath}:${issueId}:${fallback.updatedAt}`;\n\n let message: string;\n let summary: string;\n if (conflicted) {\n message = conflictMessage(issueId, conflicts, ageMinutes);\n summary = `${issueId}: verdict conflict on ${conflicts.map((c) => c.gate).join(', ')} — needs operator adjudication`;\n } else {\n const owner = await lockOwnerDescription(issueId);\n message =\n `${issueId} — verdict fallback undrained for ${ageMinutes}min; the per-issue record lock is `\n + `contended (current owner: ${owner}). The verdict is durable in the workspace fallback and `\n + `folds automatically once the lock frees.`;\n summary = `${issueId}: verdict contended ${ageMinutes}min (lock owner: ${owner})`;\n }\n\n const openTrip = await findRecoveryTrip(issueId, recoveryPath, fallback.updatedAt);\n if (openTrip?.open === true) {\n // Already durably surfaced. Do NOT call recordRecoveryFailure: its\n // mutator returns the record unchanged for an open trip, but the write\n // door still takes the per-issue lock and runs the commit/flush path.\n // A retained verdict conflict is permanent by design, so that would be\n // one no-op record write per patrol forever — manufacturing contention\n // on the very lock family this issue exists to relieve.\n warnedEpisodes.delete(episode);\n continue;\n }\n\n candidates.push({\n issueId, workspacePath, recoveryPath, generation: fallback.updatedAt, episode, message, summary,\n });\n } catch {\n // A single unreadable workspace must never stop the sweep.\n }\n }\n\n // ── Phase 2: warn, all candidates concurrently under ONE deadline ───────────\n //\n // Concurrent rather than serial, and started together rather than in turn.\n // Spending a shared budget in issue order let the first hanging episode\n // consume all of it every patrol — iteration order is stable, so the same\n // episode won recursively and every later one was silently dropped without\n // ever attempting its warning. That is the exact hard case this feature\n // targets (a stalled dashboard plus a contended lock), so the unfairness bit\n // hardest when it mattered most. Fanning out gives every episode an attempt\n // and keeps total wall-clock at one budget regardless of how many stranded.\n // Each request is idempotent by episode key, so concurrency cannot duplicate\n // a warning, and an attempt abandoned at the deadline is retried next patrol.\n const pending = candidates.filter((c) => !warnedEpisodes.has(c.episode));\n if (pending.length > 0) {\n const outcomes = await Promise.all(pending.map((candidate) =>\n withWarningDeadline(\n emitActivityEntryOnce({\n id: `verdict-fallback:${candidate.episode}`,\n source: 'cloister',\n level: 'warn',\n issueId: candidate.issueId,\n message: candidate.message,\n }),\n budgetMs,\n ).catch((): ActivityEmitOutcome => 'failed')));\n\n outcomes.forEach((outcome, index) => {\n const candidate = pending[index]!;\n if (outcome === 'appended' || outcome === 'duplicate') {\n warnedEpisodes.add(candidate.episode);\n if (outcome === 'appended') actions.push(candidate.summary);\n } else {\n // `failed` wrote nothing; `unconfirmed` means the wired store offers no\n // settled path, so delivery is unknown. Neither is evidence the operator\n // was told, and marking the episode warned would suppress the retry.\n actions.push(`${candidate.issueId}: verdict-fallback warning could not be confirmed — retrying next patrol`);\n }\n });\n }\n\n // ── Phase 3: the durable needs-you trips ───────────────────────────────────\n //\n // After the warnings, never before: these go through the very record lock\n // whose contention is being reported, so a slow or failing trip write must\n // not delay or suppress the operator-visible warning.\n for (const candidate of candidates) {\n try {\n const { emitNeedsYou } = await recordRecoveryFailure(\n candidate.workspacePath, candidate.issueId, candidate.recoveryPath, candidate.generation, 1,\n );\n // The trip landed, so the durable plane owns dedupe from here — drop the\n // in-process key regardless of `emitNeedsYou`, which controls whether this\n // was the notifying trip, not whether the write succeeded.\n warnedEpisodes.delete(candidate.episode);\n if (emitNeedsYou) actions.push(`needs-you ${candidate.summary}`);\n } catch {\n actions.push(\n `${candidate.issueId}: ${candidate.recoveryPath} needs-you trip could not be recorded (record lock contended) — retrying next patrol`,\n );\n }\n }\n\n return actions;\n}\n","/**\n * Cloister Deacon - Health Monitor for Specialist Agents\n *\n * The Deacon is a health-check system that:\n * - Actively pings specialists to verify they're responsive\n * - Tracks consecutive failures per specialist\n * - Force-kills stuck specialists after threshold failures\n * - Enforces cooldown periods after force-kills\n * - Detects mass death events (infrastructure issues)\n *\n * Inspired by gastown's deacon pattern.\n */\n\nimport { appendFileSync, readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, rmSync } from 'fs';\n// PAN-1249: readFile / writeFile / unlink are consumed by the additive Effect\n// helpers below (`readFileOp`, `writeFileOp`, `unlinkPath`).\nimport { readdir, readFile, writeFile, unlink } from 'fs/promises';\nimport { dirname, join } from 'path';\nimport { exec, execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { homedir, loadavg, cpus } from 'os';\nimport { Effect } from 'effect';\nimport {\n FsError,\n GitError,\n ProcessSpawnError,\n ProcessTimeoutError,\n} from '../errors.js';\nimport { pidsWithCwdUnder } from '../process-cwd.js';\nimport { isStartingWithinGrace } from './agent-grace.js';\nimport { recordDeaconNudge } from './deacon-nudge-log.js';\nimport { checkInspectAgentTimeouts } from './deacon-inspect.js';\nimport { checkApiErrorAgents } from './deacon-api-recovery.js';\nimport { checkPostReviewCommits } from './deacon-post-review-commits.js';\nimport { patrolStrikeLandings } from './deacon-strike-landing.js';\nimport { checkOrphanedReviewStatuses, recoverStalledReviewConvoys, checkMissingReviewStatuses, checkStuckReviewing, checkCompletedButUnsignaledReviews, reconcileUnappliedReviewVerdicts, monitorReviewConvoySignals, cleanupOrphanedReviewSessions, checkStalledReviewParents } from './deacon-review.js';\nimport { getAutoCloseOutCanonicalState } from './deacon-canonical-state.js';\nimport { checkReadyForMergeStuck as checkReadyForMergeStuckWithDeps, reconcileStaleMergeStatus, reconcileStuckMergingStates, reconcileFalseMerged, reconcileClosedPrReadyForMerge, reconcileAutoMergeRows, reconcileStaleMergeBlockers, reconcileStuckReadyForMerge, reconcileMergedButReviewing, checkFailedMergeRetry, autoCloseOut, checkFirstCompletionAgents, ciRetryMap, FAILED_MERGE_MAX_RETRIES } from './deacon-merge.js';\nimport { reconcileTraefikNetworks } from '../workspace/traefik-connect.js';\nimport { swarmJanitorPass } from './deacon-swarm.js';\nimport { recoverOrphanedAgents as recoverOrphanedAgentsWithDeps, handleAgentHeartbeatDeadEvent as handleAgentHeartbeatDeadEventWithDeps, handleAgentStoppedEvent as handleAgentStoppedEventWithDeps, autoResumeStoppedWorkAgents as autoResumeStoppedWorkAgentsWithDeps, reconcileAgentLiveness as reconcileAgentLivenessWithDeps, nudgeStalledResumeWorkAgents, redeliverUndeliveredKickoffs, nudgeIdleWorkAgentsWithOpenBeads, cleanupOrphanedPlanningSessions as cleanupOrphanedPlanningSessionsWithDeps } from './deacon-auto-resume.js';\nimport { applyBootReconciliationDecision as applyBootReconciliationDecisionWithDeps, type BootReconciliationApplyOptions, type BootReconciliationApplyResult } from './boot-reconciliation-apply.js';\nimport { listFeatureWorkspaces } from './deacon-workspaces.js';\nimport { isConversationDirectory } from '../agent-directory-cleanup.js';\nimport { removeAgentStateDir } from '../agents/state-dir-removal.js';\nimport { sweepTranscriptRetention } from './transcript-retention.js';\nimport { createInFlightGuard } from './in-flight-guard.js';\nimport { listAllAgentsSync as listAllAgents } from '../overdeck/agents.js';\nimport { isContextOverflowTail } from '../context-overflow.js';\nimport { REVIEW_SUB_ROLES, type ReviewSubRole } from './review-monitor.js';\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n// ---------------------------------------------------------------------------\n// PAN-1249 Effect helpers (additive — internal use only)\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap an `execAsync` call in Effect with a typed `ProcessSpawnError`. Used\n * internally so callers can keep their Promise-returning signatures while the\n * implementation drifts towards Effect.\n */\nconst execCommand = (\n command: string,\n options?: Parameters<typeof execAsync>[1],\n): Effect.Effect<{ stdout: string; stderr: string }, ProcessSpawnError> =>\n Effect.tryPromise({\n try: async () => {\n const result = await execAsync(command, options);\n return {\n stdout: typeof result.stdout === 'string' ? result.stdout : result.stdout.toString('utf-8'),\n stderr: typeof result.stderr === 'string' ? result.stderr : result.stderr.toString('utf-8'),\n };\n },\n catch: (cause) =>\n new ProcessSpawnError({\n command,\n args: [],\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n\n/** Wrap `execFileAsync` in Effect with a typed error. */\nconst execFileCommand = (\n command: string,\n args: readonly string[],\n options?: Parameters<typeof execFileAsync>[2],\n): Effect.Effect<{ stdout: string; stderr: string }, ProcessSpawnError> =>\n Effect.tryPromise({\n try: async () => {\n const result = await execFileAsync(command, args as string[], options);\n return {\n stdout: typeof result.stdout === 'string' ? result.stdout : result.stdout.toString('utf-8'),\n stderr: typeof result.stderr === 'string' ? result.stderr : result.stderr.toString('utf-8'),\n };\n },\n catch: (cause) =>\n new ProcessSpawnError({\n command,\n args,\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n\n/** Wrap a fs/promises read in Effect with a typed FsError. */\nconst readFileOp = (path: string): Effect.Effect<string, FsError> =>\n Effect.tryPromise({\n try: () => readFile(path, 'utf-8'),\n catch: (cause) =>\n new FsError({\n path,\n operation: 'readFile',\n cause,\n }),\n });\n\n/** Wrap a fs/promises write in Effect with a typed FsError. */\nconst writeFileOp = (path: string, data: string): Effect.Effect<void, FsError> =>\n Effect.tryPromise({\n try: () => writeFile(path, data, 'utf-8'),\n catch: (cause) =>\n new FsError({\n path,\n operation: 'writeFile',\n cause,\n }),\n });\n\n/** Wrap a fs/promises unlink in Effect with a typed FsError. */\nconst unlinkPath = (path: string): Effect.Effect<void, FsError> =>\n Effect.tryPromise({\n try: () => unlink(path),\n catch: (cause) =>\n new FsError({\n path,\n operation: 'unlink',\n cause,\n }),\n });\n\n/** Re-exported for symmetry with the additive pattern in the rest of src/lib. */\nexport { GitError, ProcessTimeoutError };\nexport { checkInspectAgentTimeouts, INSPECT_TIMEOUT_MS } from './deacon-inspect.js';\nexport { reconcileStaleMergeStatus, reconcileStuckMergingStates, reconcileFalseMerged, reconcileClosedPrReadyForMerge, reconcileAutoMergeRows, reconcileStaleMergeBlockers, reconcileStuckReadyForMerge, reconcileMergedButReviewing, checkFailedMergeRetry, autoCloseOut, checkFirstCompletionAgents, ciRetryMap, FAILED_MERGE_MAX_RETRIES } from './deacon-merge.js';\nexport { coordinateSwarmSlots } from './deacon-swarm.js';\nexport { nudgeStalledResumeWorkAgents, redeliverUndeliveredKickoffs, nudgeIdleWorkAgentsWithOpenBeads, isRapidPostResumeDeath, isPreKickoffLaunchDeath } from './deacon-auto-resume.js';\n\nimport { OVERDECK_HOME, AGENTS_DIR, sessionFilePath } from '../paths.js';\nimport { loadCloisterConfigSync, loadCloisterConfig } from './config.js';\nimport { workResumeSlotsAvailable, getConcurrencyLimits, countRunningAgents, resetPatrolDispatchBudget, tryReserveAdvancingSlot, describeRunningAgents } from './concurrency.js';\nimport { tryYieldForAdvancingDispatch } from './preemption.js';\nimport { setReviewStatusSync, loadReviewStatuses, getReviewStatusSync } from '../review-status.js';\nimport { needsReviewDispatch } from '../review-dispatch-decision.js';\nimport { readIssueRecordSync } from '../pan-dir/record.js';\nimport { updateIssueRecord } from '../pan-dir/record-update.js';\nimport { readWorkspacePlanSync } from '../xbrief/io.js';\nimport { markWorkspaceStuck } from '../overdeck/review-status-sync.js';\nimport { isDeaconGloballyPaused } from '../overdeck/control-settings.js';\nimport { findWorkspacePath } from '../lifecycle/archive-planning.js';\nimport { resolveProjectFromIssueSync, listProjectsSync, getProjectSync } from '../projects.js';\nimport { recreatedStateWarnings, reconcileProjectStatePlanes, statePlaneReconcileEveryCycles } from './state-recreation-patrol.js';\nimport { reconcileTerminalIssueResidue } from './parked-residue.js';\nimport { withIssueRecordLock } from '../pan-dir/record-lock.js';\nimport { recordMainDivergenceHealth, type ProjectMainDivergence } from './deacon-main-divergence.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\nimport { mapGitHubStateToCanonical } from '../../core/state-mapping.js';\nimport { logDeaconEventSync, logAgentLifecycleSync } from '../persistent-logger.js';\nimport { emitActivityTtsSync } from '../activity-logger.js';\nimport { getShadowState } from '../shadow-state.js';\nimport type { TrackerConfig } from '../tracker/factory.js';\n\n// Review status file location (same as dashboard server)\n\nimport {\n SpecialistAgentName,\n getTmuxSessionName,\n isRunning,\n getAllProjectSpecialistStatuses,\n parseReviewerSessionName,\n} from './specialists.js';\nimport { getAgentRuntimeStateSync, saveAgentRuntimeState, saveSessionId, listRunningAgentsSync, listRunningAgents, listAgentStates, getAgentDir, getAgentStateSync, getAgentState, saveAgentStateSync, saveAgentState, resumeAgent, recordAgentFailure, resetAgentFailureCount, markAgentRunningState, buildDefaultResumeContinueMessage, clearAgentTroubledSync, type AgentState } from '../agents.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { buildTmuxCommandString, capturePane, createSession, getManagedTmuxSocketName, isPaneDead, killSessionSync, killSession, listPaneValuesSync, listPaneValues, listSessionNames, sessionExistsSync, sessionExists, sendKeys } from '../tmux.js';\nimport { withConcurrencyLimit } from '../concurrency.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { isAgentIdleForNudge } from './agent-idle.js';\nimport { checkStuckAgentRemediation } from './stuck-remediation.js';\nimport { decideAgentAutonomousRedrive } from './redrive-gate.js';\nimport { captureTranscriptUserRecordSnapshot } from '../transcript-landing.js';\nimport { reconcileClosedIssueAgents } from './closed-issue-reaper.js';\nimport { reconcilePipelineLabelsPatrol } from './label-reconciler.js';\nimport { pruneTerminalStoppedAgents } from './agent-gc.js';\nimport { shouldRunRecoveryJanitor } from './patrol-cadence.js';\nimport { recordDeadEndNeedsYou } from './dead-end-trip.js';\nimport { reconcileOrphanProposedSpecs, spawnWorkAgentThroughAgentsEndpoint, triggerRebuildAndStart } from './orphan-proposed-reconciler.js';\nimport { reconcilePendingPromotions } from './pending-promotion-reconciler.js';\nimport { reconcileTestStatusFromGreenCiWithDeps } from './test-status-green-ci-reconciler.js';\nimport { reapOrphanedDashboardServers } from './orphan-dashboard-server-reaper.js';\nimport { reconcileIdleWorkspaceStacks } from './idle-stack-reaper.js';\nimport { reapLeftoverPlaywrightBrowsers } from './playwright-mcp-reaper.js';\nimport { reapMergedStrikeWorkspaces } from './strike-workspace-reaper.js';\nimport { cleanupOrphanedInspectSessions } from './inspect-session-reaper.js';\nimport { isIssueClosed } from './issue-closed.js';\nimport { decideUnsignaledTestAction, readTestVerdictArtifact, recordUnsignaledTestEscalation } from './test-verdict.js';\nimport { sweepStrandedVerdictFallbacks } from './deacon-verdict-fallback-sweep.js';\nimport { deliverReviewVerdictFeedback } from './review-verdict-feedback.js';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Default parameters for stuck-session detection.\n * Per gastown: \"Let agents decide thresholds. 'Stuck' is a judgment call.\"\n */\nconst DEFAULT_CONFIG: DeaconConfig = {\n pingTimeoutMs: 30_000, // How long to wait for response\n consecutiveFailures: 3, // Failures before force-kill\n cooldownMs: 5 * 60_000, // 5 minutes between force-kills\n patrolIntervalMs: 60_000, // Safety net — immediate processing happens via pipeline events\n massDeathThreshold: 2, // Deaths within window triggers alert\n massDeathWindowMs: 60_000, // 1 minute window for mass death detection\n};\n\nexport interface DeaconConfig {\n pingTimeoutMs: number;\n consecutiveFailures: number;\n cooldownMs: number;\n patrolIntervalMs: number;\n massDeathThreshold: number;\n massDeathWindowMs: number;\n}\n\n// ============================================================================\n// Health State Types\n// ============================================================================\n\n/**\n * Health check state for a single specialist\n */\nexport interface SpecialistHealthState {\n specialistName: SpecialistAgentName;\n lastPingTime?: string; // ISO 8601\n lastResponseTime?: string; // ISO 8601\n consecutiveFailures: number;\n lastForceKillTime?: string; // ISO 8601\n forceKillCount: number;\n}\n\n/**\n * PAN-464: Tracks restart history for a workspace container.\n */\nexport interface ContainerRestartRecord {\n count: number; // Total restart attempts\n firstRestart: string; // ISO 8601 — when the first restart in the current burst happened\n lastRestart: string; // ISO 8601 — when the most recent restart happened\n gaveUp?: boolean; // True when max restarts exceeded — skip future auto-restarts\n}\n\n/**\n * Complete health check state for all specialists\n */\nexport interface DeaconState {\n specialists: Record<SpecialistAgentName, SpecialistHealthState>;\n lastPatrol?: string; // ISO 8601\n lastPatrolResult?: PatrolResult;\n patrolCycle: number;\n recentDeaths: string[]; // ISO timestamps of recent deaths\n lastMassDeathAlert?: string; // ISO 8601\n mergeStuckAttempts?: Record<string, number>; // circuit-breaker attempt counts (PAN-344)\n containerRestarts?: Record<string, ContainerRestartRecord>; // PAN-464: restart backoff tracking\n mainDivergence?: ProjectMainDivergence[];\n}\n\nexport type DeaconPatrolHealth = 'running' | 'starting' | 'stale' | 'stopped';\n\nexport interface DeaconPatrolFreshness {\n status: DeaconPatrolHealth;\n lastPatrol: string | null;\n secondsSinceLastPatrol: number | null;\n staleAfterSeconds: number;\n}\n\n/**\n * Result of a health check\n */\nexport interface HealthCheckResult {\n specialistName: SpecialistAgentName;\n isResponsive: boolean;\n responseTimeMs?: number;\n consecutiveFailures: number;\n shouldForceKill: boolean;\n inCooldown: boolean;\n cooldownRemainingMs?: number;\n wasRunning: boolean;\n error?: string;\n}\n\n// ============================================================================\n// State Management\n// ============================================================================\n\nconst DEACON_DIR = join(OVERDECK_HOME, 'deacon');\nconst STATE_FILE = join(DEACON_DIR, 'health-state.json');\nconst CONFIG_FILE = join(DEACON_DIR, 'config.json');\nconst DEACON_LOG_FILE = join(OVERDECK_HOME, 'logs', 'deacon.log');\n\nlet deaconInterval: NodeJS.Timeout | null = null;\nconst deaconPatrolGuard = createInFlightGuard();\nlet memoryPatrolInterval: NodeJS.Timeout | null = null;\nlet config: DeaconConfig = { ...DEFAULT_CONFIG };\n\n/** PAN-3550: the memory watch samples far more often than the 60s patrol so rising\n * pressure reaches the activity feed before the kernel acts. */\nexport const MEMORY_PATROL_INTERVAL_MS = 15_000;\n\n/**\n * Load deacon configuration\n */\nexport function loadConfig(): DeaconConfig {\n config = { ...DEFAULT_CONFIG };\n\n try {\n if (existsSync(CONFIG_FILE)) {\n const content = readFileSync(CONFIG_FILE, 'utf-8');\n const loaded = JSON.parse(content);\n config = { ...config, ...loaded };\n }\n } catch (error) {\n console.error('[deacon] Failed to load config:', error);\n }\n\n return config;\n}\n\n/**\n * Save deacon configuration\n */\nexport function saveConfig(newConfig: Partial<DeaconConfig>): void {\n ensureDeaconDir();\n config = { ...config, ...newConfig };\n writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n\n/**\n * Ensure deacon directory exists\n */\nfunction ensureDeaconDir(): void {\n if (!existsSync(DEACON_DIR)) {\n mkdirSync(DEACON_DIR, { recursive: true });\n }\n}\n\n/**\n * Load health check state from disk\n */\nexport function loadState(): DeaconState {\n ensureDeaconDir();\n\n try {\n if (existsSync(STATE_FILE)) {\n const content = readFileSync(STATE_FILE, 'utf-8');\n return JSON.parse(content);\n }\n } catch (error) {\n console.error('[deacon] Failed to load state:', error);\n }\n\n // Return empty state\n return {\n specialists: {} as Record<SpecialistAgentName, SpecialistHealthState>,\n patrolCycle: 0,\n recentDeaths: [],\n };\n}\n\nfunction newestPersistedPatrolHeartbeat(incomingLastPatrol: string | undefined): string | undefined {\n if (!incomingLastPatrol || !existsSync(STATE_FILE)) return incomingLastPatrol;\n\n try {\n const content = readFileSync(STATE_FILE, 'utf-8');\n const persisted = JSON.parse(content) as { lastPatrol?: unknown };\n if (typeof persisted.lastPatrol !== 'string') return incomingLastPatrol;\n\n const incomingMs = Date.parse(incomingLastPatrol);\n const persistedMs = Date.parse(persisted.lastPatrol);\n if (!Number.isFinite(persistedMs)) return incomingLastPatrol;\n if (!Number.isFinite(incomingMs) || persistedMs > incomingMs) return persisted.lastPatrol;\n } catch {\n // Fall through to the incoming state; saveState's write error handling remains below.\n }\n\n return incomingLastPatrol;\n}\n\n/**\n * Save health check state to disk\n */\nexport function saveState(state: DeaconState): void {\n ensureDeaconDir();\n\n try {\n const newestLastPatrol = newestPersistedPatrolHeartbeat(state.lastPatrol);\n if (newestLastPatrol) {\n state.lastPatrol = newestLastPatrol;\n } else {\n delete state.lastPatrol;\n }\n writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf-8');\n } catch (error) {\n console.error('[deacon] Failed to save state:', error);\n }\n}\n\nfunction saveLastPatrolResult(state: DeaconState, result: PatrolResult): void {\n state.lastPatrolResult = result;\n saveState(state);\n}\n\nfunction refreshPatrolHeartbeat(state: DeaconState): void { state.lastPatrol = new Date().toISOString(); saveState(state); }\n\nfunction startPatrolHeartbeatTicker(state: DeaconState): NodeJS.Timeout {\n const ticker = setInterval(() => refreshPatrolHeartbeat(state), Math.max(10_000, Math.floor(config.patrolIntervalMs / 2)));\n ticker.unref?.(); return ticker;\n}\n\n/**\n * Clear the persisted patrol heartbeat at dashboard startup so the supervisor\n * watchdog gives the first deacon patrol its normal startup grace window.\n */\nexport function resetPatrolHeartbeatForStartup(): void {\n try {\n if (!existsSync(STATE_FILE)) return;\n\n const state = loadState();\n delete state.lastPatrol;\n saveState(state);\n } catch {\n // Non-fatal: worst case the watchdog still sees the stale heartbeat.\n }\n}\n\n/**\n * Get health state for a specialist, creating if needed\n */\nfunction getSpecialistState(\n state: DeaconState,\n name: SpecialistAgentName\n): SpecialistHealthState {\n if (!state.specialists[name]) {\n state.specialists[name] = {\n specialistName: name,\n consecutiveFailures: 0,\n forceKillCount: 0,\n };\n }\n return state.specialists[name];\n}\n\n// ============================================================================\n// Health Check Logic\n// ============================================================================\n\n/**\n * Check if a specialist is in cooldown period\n */\nfunction isInCooldown(healthState: SpecialistHealthState): boolean {\n if (!healthState.lastForceKillTime) {\n return false;\n }\n\n const lastKill = new Date(healthState.lastForceKillTime).getTime();\n const cooldownEnd = lastKill + config.cooldownMs;\n return Date.now() < cooldownEnd;\n}\n\n/**\n * Get remaining cooldown time in ms\n */\nfunction getCooldownRemaining(healthState: SpecialistHealthState): number {\n if (!healthState.lastForceKillTime) {\n return 0;\n }\n\n const lastKill = new Date(healthState.lastForceKillTime).getTime();\n const cooldownEnd = lastKill + config.cooldownMs;\n const remaining = cooldownEnd - Date.now();\n return Math.max(0, remaining);\n}\n\n/**\n * Check if a specialist is responsive by reading their heartbeat\n */\nfunction checkHeartbeat(name: SpecialistAgentName): {\n isResponsive: boolean;\n lastActivity?: number;\n responseTimeMs?: number;\n} {\n const tmuxSession = getTmuxSessionName(name);\n const heartbeatFile = join(OVERDECK_HOME, 'heartbeats', `${tmuxSession}.json`);\n\n try {\n if (!existsSync(heartbeatFile)) {\n return { isResponsive: false };\n }\n\n const content = readFileSync(heartbeatFile, 'utf-8');\n const heartbeat = JSON.parse(content);\n const lastActivity = new Date(heartbeat.timestamp).getTime();\n const age = Date.now() - lastActivity;\n\n // If heartbeat is less than pingTimeout old, specialist is responsive\n const isResponsive = age < config.pingTimeoutMs;\n\n return {\n isResponsive,\n lastActivity,\n responseTimeMs: age,\n };\n } catch {\n return { isResponsive: false };\n }\n}\n\n/**\n * Perform a health check on a specialist\n *\n * When called from runPatrol, pass the shared state object to avoid\n * independent load/save cycles that clobber each other (the original\n * bug that prevented consecutiveFailures from ever accumulating).\n *\n * When called standalone (no sharedState), loads and saves state itself.\n */\nexport async function checkSpecialistHealth(\n name: SpecialistAgentName,\n sharedState?: DeaconState,\n): Promise<HealthCheckResult> {\n const state = sharedState ?? loadState();\n const healthState = getSpecialistState(state, name);\n const wasRunning = await isRunning(name);\n\n // Update ping time\n healthState.lastPingTime = new Date().toISOString();\n\n // If not running, it's not responsive\n if (!wasRunning) {\n if (!sharedState) saveState(state);\n return {\n specialistName: name,\n isResponsive: false,\n wasRunning: false,\n consecutiveFailures: healthState.consecutiveFailures,\n shouldForceKill: false, // Can't force-kill what's not running\n inCooldown: isInCooldown(healthState),\n cooldownRemainingMs: getCooldownRemaining(healthState),\n error: 'Specialist is not running',\n };\n }\n\n // Check heartbeat\n const heartbeatResult = checkHeartbeat(name);\n\n if (heartbeatResult.isResponsive) {\n // Reset failure counter on successful response\n healthState.consecutiveFailures = 0;\n healthState.lastResponseTime = new Date().toISOString();\n if (!sharedState) saveState(state);\n\n return {\n specialistName: name,\n isResponsive: true,\n responseTimeMs: heartbeatResult.responseTimeMs,\n wasRunning: true,\n consecutiveFailures: 0,\n shouldForceKill: false,\n inCooldown: isInCooldown(healthState),\n };\n }\n\n // Stale heartbeat — but an idle specialist is EXPECTED to have a stale heartbeat\n // (no tool calls = no hook-based heartbeat updates). Don't count idle specialists\n // as failures — only escalate when the specialist should be actively working.\n const tmuxSession = getTmuxSessionName(name);\n const runtimeState = getAgentRuntimeStateSync(tmuxSession);\n const isIdle = !runtimeState || runtimeState.state === 'idle';\n\n if (isIdle) {\n // Idle specialist with stale heartbeat is normal — treat as responsive\n if (!sharedState) saveState(state);\n return {\n specialistName: name,\n isResponsive: false, // heartbeat IS stale\n wasRunning: true,\n consecutiveFailures: healthState.consecutiveFailures, // don't increment\n shouldForceKill: false, // never force-kill an idle specialist\n inCooldown: isInCooldown(healthState),\n };\n }\n\n // Active specialist with stale heartbeat — genuinely unresponsive\n healthState.consecutiveFailures++;\n if (!sharedState) saveState(state);\n\n const shouldForceKill =\n healthState.consecutiveFailures >= config.consecutiveFailures &&\n !isInCooldown(healthState);\n\n return {\n specialistName: name,\n isResponsive: false,\n wasRunning: true,\n consecutiveFailures: healthState.consecutiveFailures,\n shouldForceKill,\n inCooldown: isInCooldown(healthState),\n cooldownRemainingMs: getCooldownRemaining(healthState),\n };\n}\n\n/**\n * Force-kill a stuck specialist\n *\n * When called from runPatrol, pass the shared state object.\n * When called standalone, loads and saves state itself.\n */\nexport async function forceKillSpecialist(\n name: SpecialistAgentName,\n sharedState?: DeaconState,\n): Promise<{\n success: boolean;\n message: string;\n}> {\n const tmuxSession = getTmuxSessionName(name);\n const state = sharedState ?? loadState();\n const healthState = getSpecialistState(state, name);\n\n // Check cooldown\n if (isInCooldown(healthState)) {\n const remaining = getCooldownRemaining(healthState);\n return {\n success: false,\n message: `Specialist ${name} is in cooldown. ${Math.ceil(remaining / 1000)}s remaining.`,\n };\n }\n\n try {\n // Kill the tmux session (non-blocking)\n await Effect.runPromise(killSession(tmuxSession));\n\n // Update state\n healthState.lastForceKillTime = new Date().toISOString();\n healthState.forceKillCount++;\n healthState.consecutiveFailures = 0;\n\n // Record death for mass death detection\n state.recentDeaths.push(new Date().toISOString());\n // Prune old deaths outside the window\n const windowStart = Date.now() - config.massDeathWindowMs;\n state.recentDeaths = state.recentDeaths.filter(\n (d) => new Date(d).getTime() > windowStart\n );\n\n if (!sharedState) saveState(state);\n\n console.log(`[deacon] Force-killed specialist ${name}`);\n\n return {\n success: true,\n message: `Specialist ${name} force-killed after ${healthState.forceKillCount} total kills`,\n };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n success: false,\n message: `Failed to kill specialist ${name}: ${msg}`,\n };\n }\n}\n\n/**\n * Check for mass death condition\n *\n * When called from runPatrol, pass the shared state object.\n * When called standalone, loads and saves state itself.\n */\nexport function checkMassDeath(sharedState?: DeaconState): {\n isMassDeath: boolean;\n deathCount: number;\n message?: string;\n} {\n const state = sharedState ?? loadState();\n\n // Prune old deaths\n const windowStart = Date.now() - config.massDeathWindowMs;\n state.recentDeaths = state.recentDeaths.filter(\n (d) => new Date(d).getTime() > windowStart\n );\n\n const deathCount = state.recentDeaths.length;\n\n if (deathCount >= config.massDeathThreshold) {\n // Check if we already alerted recently\n if (state.lastMassDeathAlert) {\n const lastAlert = new Date(state.lastMassDeathAlert).getTime();\n const alertCooldown = 5 * 60_000; // 5 minutes between alerts\n if (Date.now() - lastAlert < alertCooldown) {\n if (!sharedState) saveState(state);\n return {\n isMassDeath: true,\n deathCount,\n message: 'Mass death detected (already alerted)',\n };\n }\n }\n\n // Record alert\n state.lastMassDeathAlert = new Date().toISOString();\n if (!sharedState) saveState(state);\n\n return {\n isMassDeath: true,\n deathCount,\n message: `ALERT: ${deathCount} specialist deaths in ${config.massDeathWindowMs / 1000}s - possible infrastructure issue`,\n };\n }\n\n if (!sharedState) saveState(state);\n\n return {\n isMassDeath: false,\n deathCount,\n };\n}\n\n// ============================================================================\n// Patrol Loop\n// ============================================================================\n\n/**\n * Patrol result for a single cycle\n */\nexport interface PatrolResult {\n cycle: number;\n timestamp: string;\n specialists: HealthCheckResult[];\n actionsToken: string[];\n massDeathDetected: boolean;\n mainDivergence?: ProjectMainDivergence[];\n}\n\n/**\n * Check and auto-suspend idle agents (PAN-80)\n *\n * Specialists: 5 minute idle timeout\n * Work agents: NEVER auto-suspend after completion (stay available for merge)\n */\n// checkAndSuspendIdleAgents deleted in PAN-800 Phase 5.\n// The old implementation read runtime.json + wrote 'suspended' state +\n// tmux-regex-checked via isAgentActiveInTmux — all paths the new\n// AgentStateService supersedes. Active intervention (auto-suspend) is\n// out of scope for PAN-800 and tracked under PAN-188.\nexport async function checkAndSuspendIdleAgents(): Promise<string[]> {\n return [];\n}\n\n// ============================================================================\n// Agent State Cleanup\n// ============================================================================\n//\n// Lazy-agent detection (LAZY_PATTERNS regex catalog + checkLazyAgent +\n// sendAntiLazyMessage + checkAndCorrectLazyAgents) was deleted in PAN-800\n// Phase 5. It had already been disabled in the patrol because false positives\n// burned API credits (PAN-133). The replacement predicate is typed:\n// activity === \"idle\" && !waiting && now - lastActivity > threshold\n// Intervention itself is out of scope for PAN-800; PAN-188 tracks rebuilding\n// it on top of the AgentRuntimeSnapshot stream.\n// ============================================================================\n\n// ACTIVE_STATUS_PATTERNS + isAgentActiveInTmux deleted in PAN-800 Phase 5.\n// They pane-scraped Claude Code's status line to guess \"is this agent active\",\n// including a parseThinkingDuration stuck heuristic. The hook-driven runtime\n// mirror (getAgentRuntimeStateSync → activity 'working'/'thinking'/'idle') is\n// the single source of truth now; capture-pane activity detection is gone.\n// isAgentActiveInTmux had no remaining callers at deletion time.\n\n// ============================================================================\n// Stuck Work Agent Detection\n// ============================================================================\n\n/**\n * checkStuckWorkAgents gutted in PAN-800 Phase 5.\n *\n * The old implementation `capturePane`d every work agent and ran a\n * `parseThinkingDuration` regex over the tmux status line to decide an agent\n * was \"stuck thinking\", then escalated Escape → Ctrl-C → kill/respawn. That is\n * exactly the capture-pane scraping PAN-798/PAN-800 eliminate, and it was\n * brittle: it only recognised the \"Thinking\"/\"Fermenting\" spinner words (not\n * \"Quantumizing\" et al.) and could not parse hour-scale durations.\n *\n * Stuck detection is now hook-based and lives in `checkStuckAgentRemediation`\n * (stuck-remediation.ts), which reads the runtime mirror via\n * `isAgentIdleForNudge` + `getAgentRuntimeStateSync` and escalates\n * nudge → resume → troubled. PAN-1586 made `isAgentIdleForNudge` treat a stale\n * 'active' mirror (Stop hook never fired) as idle, so a genuinely-stalled agent\n * — regardless of spinner word or duration — is now caught there.\n *\n * The \"exclude-from-context\" dialog auto-dismiss that also lived here was a\n * pane-scrape active intervention; like auto-suspend and lazy-detection it is\n * out of scope for PAN-800 and tracked under PAN-188. The notification hook\n * already surfaces that dialog as `waiting-on-human`.\n *\n * Retained as a no-op stub so the patrol-cycle wiring stays stable.\n */\nexport async function checkStuckWorkAgents(): Promise<string[]> {\n return [];\n}\n\nexport {\n checkApiErrorAgents,\n CONTEXT_PROACTIVE_COMPACT_HIGH_WATER_PERCENT,\n contextOverflowRecoveryState,\n stuckOverflowNativeRecoveryState,\n} from './deacon-api-recovery.js';\n\n/**\n * Clean up stale agent state directories (PAN-154)\n *\n * Scans ~/.overdeck/agents/ for directories that:\n * - Have no active tmux session\n * - Are older than the configured retention threshold (default: 30 days)\n * - Don't have a recently processed completion marker\n *\n * Runs at low frequency (~once per day) via random trigger in patrol cycle.\n */\nexport async function cleanupStaleAgentState(): Promise<string[]> {\n const actions: string[] = [];\n const cloisterConfig = loadCloisterConfigSync();\n // Default retention for work/planning runtime residue. Fresh-session wipes\n // and close-out cleanup use removeAgentStateDir at their lifecycle events;\n // this purge is the safety net, and the shared door preserves all JSONL.\n const retentionDays = cloisterConfig.retention?.agent_state_days ?? 7;\n const retentionMs = retentionDays * 24 * 60 * 60 * 1000;\n // Reviewer runtime residue has a shorter debugging window. Review restart\n // and close-out paths use the same deletion door, while this 1-day purge\n // catches sessions whose event-driven cleanup never ran and preserves JSONL.\n const reviewerRetentionDays = cloisterConfig.retention?.reviewer_state_days ?? 1;\n const reviewerRetentionMs = reviewerRetentionDays * 24 * 60 * 60 * 1000;\n const now = Date.now();\n\n if (!existsSync(AGENTS_DIR)) {\n return actions;\n }\n\n try {\n const dirs = readdirSync(AGENTS_DIR, { withFileTypes: true })\n .filter(d => d.isDirectory());\n\n for (const dir of dirs) {\n // conv-* dirs are the canonical transcript home for non-claude harnesses\n // (ohmypi sessions/*.jsonl, codex codex-home/sessions/ rollouts). Purging\n // them permanently destroys conversation history — same invariant as\n // findOrphanedAgentDirs (2026-07-05 transcript-loss incident).\n if (isConversationDirectory(dir.name)) continue;\n const agentDir = join(AGENTS_DIR, dir.name);\n const isReviewer = dir.name.startsWith('review-');\n const effectiveRetentionMs = isReviewer ? reviewerRetentionMs : retentionMs;\n\n try {\n // Check if tmux session is active — never clean up running agents\n try {\n const exists = await Effect.runPromise(sessionExists(dir.name));\n if (exists) {\n continue; // Session exists, skip\n }\n } catch {\n // No session — candidate for cleanup\n }\n\n // PAN-1908: for canonical agent-*/strike-* directories, prefer the\n // SQLite agents table as the source of truth. If an entry exists, keep\n // the directory even if state.json is old — the registry (not the\n // filesystem) owns retention. Reviewers remain filesystem-ephemeral.\n if (dir.name.startsWith('agent-') || dir.name.startsWith('strike-')) {\n const agent = await Effect.runPromise(\n getAgentState(dir.name).pipe(Effect.catch(() => Effect.succeed(null))),\n );\n if (agent != null) continue;\n }\n\n // Check directory age via directory mtime.\n const mtime = statSync(agentDir).mtimeMs;\n\n const ageMs = now - mtime;\n if (ageMs < effectiveRetentionMs) {\n continue; // Not old enough, skip\n }\n\n // Reviewers don't have a `completed` marker and don't warrant the\n // 7-day grace period — skip the completion check for them.\n if (!isReviewer) {\n const completedFile = join(agentDir, 'completed');\n if (existsSync(completedFile)) {\n const completedAge = now - statSync(completedFile).mtimeMs;\n // Keep completed work agents for at least 7 days regardless of retention\n if (completedAge < 7 * 24 * 60 * 60 * 1000) {\n continue;\n }\n }\n }\n\n // Safe to remove\n const ageDays = Math.round(ageMs / (24 * 60 * 60 * 1000));\n const tag = isReviewer ? 'reviewer' : 'agent';\n const removal = await removeAgentStateDir(agentDir);\n const transcriptLabel = `transcript file${removal.preservedTranscripts === 1 ? '' : 's'} preserved`;\n actions.push(`Purged stale ${tag} state: ${dir.name} (${ageDays} days old; ${removal.preservedTranscripts} ${transcriptLabel})`);\n console.log(`[deacon] Purged stale ${tag} state: ${dir.name} (${ageDays} days old; ${removal.preservedTranscripts} ${transcriptLabel})`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Error cleaning up agent ${dir.name}:`, msg);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error during agent state cleanup:', msg);\n }\n\n if (actions.length > 0) {\n console.log(`[deacon] Cleanup complete: purged ${actions.length} stale agent directories`);\n }\n\n return actions;\n}\n\n/**\n * Clean up abandoned feedback directories.\n *\n * Event-driven cleanup handles the happy path (new review cycle → clear on\n * dispatch; merge → close-out removes workspace). This sweep is the safety net\n * for workspaces where those events never fired: work agent is no longer\n * running AND no review is in flight AND feedback files are still sitting in\n * `.pan/feedback/`.\n *\n * The feedback is useless once consumed, so we always delete — no archive, no\n * retention. See docs/REVIEW-AGENT-ARCHITECTURE.md.\n */\nexport async function cleanupAbandonedFeedback(): Promise<string[]> {\n const actions: string[] = [];\n\n const { getReviewStatusSync } = await import('../review-status.js');\n const { clearFeedbackFiles } = await import('./feedback-writer.js');\n\n for (const { issueId, workspacePath } of listFeatureWorkspaces()) {\n const panFeedbackDir = join(workspacePath, '.pan', 'feedback');\n if (!existsSync(panFeedbackDir)) continue;\n\n const issueLower = issueId.toLowerCase();\n\n // Gate 1: work agent tmux session active? If yes, feedback may be current.\n const agentSession = `agent-${issueLower}`;\n try {\n if (await Effect.runPromise(sessionExists(agentSession))) continue;\n } catch {\n // Treat lookup error as \"session might exist\" — skip out of caution.\n continue;\n }\n\n // Gate 2: review in flight? If yes, feedback is about to be consumed.\n try {\n const status = getReviewStatusSync(issueId);\n if (status?.reviewStatus === 'reviewing') continue;\n } catch {\n // No status entry → safe to clean.\n }\n\n // Both gates passed — feedback is abandoned, safe to delete.\n try {\n const countFeedbackFiles = (dir: string) => existsSync(dir)\n ? readdirSync(dir).filter(f => /^\\d{3}-/.test(f) && f.endsWith('.md')).length\n : 0;\n const before = countFeedbackFiles(panFeedbackDir);\n if (before === 0) continue;\n await Effect.runPromise(clearFeedbackFiles(workspacePath));\n actions.push(\n `Cleared ${before} abandoned feedback file(s) in feature-${issueLower} (agent stopped, no in-flight review)`,\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] cleanupAbandonedFeedback failed for feature-${issueLower}:`, msg);\n }\n }\n\n if (actions.length > 0) {\n console.log(`[deacon] Feedback cleanup: ${actions.length} workspace(s) cleared`);\n }\n\n return actions;\n}\n\nconst ORPHAN_REVIEWER_AGE_MS = 60 * 60 * 1000; // 1 hour\n\nasync function loadTmuxSessionsWithCreationTimes(): Promise<{ sessions: string[]; creationTimes: Map<string, number> } | null> {\n try {\n const { stdout } = await execAsync(\n `tmux -L ${getManagedTmuxSocketName()} -f ${join(OVERDECK_HOME, 'tmux', 'overdeck.tmux.conf')} list-sessions -F '#{session_name} #{session_created}'`,\n { encoding: 'utf-8' },\n );\n const lines = stdout.split('\\n').filter(l => l.trim());\n const sessions: string[] = [];\n const creationTimes = new Map<string, number>();\n for (const line of lines) {\n const parts = line.split(' ');\n if (parts.length >= 2) {\n const name = parts.slice(0, -1).join(' ');\n const created = parseInt(parts[parts.length - 1], 10);\n if (!Number.isFinite(created)) continue;\n if (name) {\n sessions.push(name);\n creationTimes.set(name, created * 1000); // tmux returns seconds\n }\n }\n }\n return { sessions, creationTimes };\n } catch {\n // tmux server may not be running — nothing to clean\n return null;\n }\n}\n\nfunction issueIdFromReviewerSessionName(sessionName: string): string | null {\n // PAN-1059 convoy pattern: agent-<issueId>-review-<subRole>\n const convoyMatch = sessionName.match(/^agent-([a-z0-9]+-\\d+)-review-(?:security|correctness|performance|requirements)$/);\n if (convoyMatch) {\n return convoyMatch[1].toUpperCase();\n }\n\n // Legacy specialist pattern: specialist-<project>-<issueId>-review-<role>\n const parsedReviewer = parseReviewerSessionName(sessionName);\n if (parsedReviewer) {\n return parsedReviewer.issueId.toUpperCase();\n }\n\n // Generic fallback\n const match = sessionName.match(/([A-Z0-9]+-\\d+)/i);\n return match ? match[1].toUpperCase() : null;\n}\n\nasync function handleOrphanReviewerSession(\n sessionName: string,\n sessions: string[],\n creationTimes: Map<string, number>,\n now: number,\n): Promise<string | null> {\n // PAN-1059: convoy sub-role sessions are agent-<id>-review-<subRole>.\n // Legacy specialist sessions matched specialist-*-review-*. Both contain -review-.\n if (!sessionName.includes('-review-')) return null;\n\n const createdMs = creationTimes.get(sessionName);\n if (!createdMs || now - createdMs < ORPHAN_REVIEWER_AGE_MS) return null;\n\n const issueId = issueIdFromReviewerSessionName(sessionName);\n if (!issueId) return null;\n\n // Gate 1: work agent running?\n const agentSession = `agent-${issueId.toLowerCase()}`;\n if (sessions.includes(agentSession)) return null;\n\n // Gate 2: review in flight for this issue?\n const { getReviewStatusSync } = await import('../review-status.js');\n try {\n const status = getReviewStatusSync(issueId);\n if (status?.reviewStatus === 'reviewing') return null;\n } catch {\n // No status entry → safe to clean\n }\n\n try {\n await Effect.runPromise(killSession(sessionName));\n const ageMin = Math.round((now - createdMs) / 60000);\n const msg = `Killed orphan reviewer session ${sessionName} (${ageMin}m old)`;\n console.log(`[deacon] ${msg}`);\n return msg;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] Failed to kill orphan session ${sessionName}:`, msg);\n return null;\n }\n}\n\n/**\n * PAN-1908: reactive orphan reviewer-session cleanup. When a work agent stops,\n * check whether any reviewer sessions for the same issue have outlived it and\n * kill them if they are old enough and no review is in flight.\n */\nexport async function handleAgentStoppedForOrphanReviewerSessions(agentId: string): Promise<string[]> {\n const match = agentId.match(/^agent-([a-z0-9]+-\\d+)$/i);\n if (!match) return [];\n const issueId = match[1].toUpperCase();\n\n const loaded = await loadTmuxSessionsWithCreationTimes();\n if (!loaded) return [];\n const { sessions, creationTimes } = loaded;\n\n const actions: string[] = [];\n const now = Date.now();\n for (const sessionName of sessions) {\n const sessionIssueId = issueIdFromReviewerSessionName(sessionName);\n if (sessionIssueId !== issueId) continue;\n\n const result = await handleOrphanReviewerSession(sessionName, sessions, creationTimes, now);\n if (result) actions.push(result);\n }\n\n if (actions.length > 0) {\n console.log(`[deacon] Orphan session cleanup: killed ${actions.length} session(s)`);\n }\n return actions;\n}\n\n/**\n * Clean up orphan reviewer and specialist tmux sessions (PAN-846).\n *\n * PAN-1908: this is now a thin dropped-event safety net. The primary cleanup\n * path is reactive via handleAgentStoppedForOrphanReviewerSessions.\n */\nexport async function cleanupOrphanReviewerSessions(): Promise<string[]> {\n const loaded = await loadTmuxSessionsWithCreationTimes();\n if (!loaded) return [];\n const { sessions, creationTimes } = loaded;\n\n const actions: string[] = [];\n const now = Date.now();\n for (const sessionName of sessions) {\n const result = await handleOrphanReviewerSession(sessionName, sessions, creationTimes, now);\n if (result) actions.push(result);\n }\n\n if (actions.length > 0) {\n console.log(`[deacon] Orphan session cleanup: killed ${actions.length} session(s)`);\n }\n return actions;\n}\n\nexport {\n reviewConvoyLiveness,\n handleReviewCoordinatorDied,\n handleWorkCompleted,\n checkOrphanedReviewStatuses,\n recoverStalledReviewConvoys,\n checkMissingReviewStatuses,\n checkStuckReviewing,\n checkCompletedButUnsignaledReviews,\n reconcileUnappliedReviewVerdicts,\n monitorReviewConvoySignals,\n cleanupOrphanedReviewSessions,\n synthesizeReviewFromReports,\n isSynthesisForActiveReviewRun,\n stalledReviewConvoyRecoveryState,\n} from './deacon-review.js';\nexport type { ReviewConvoyLiveness } from './deacon-review.js';\n\n// ============================================================================\n// PAN-2207: Recover completions whose PR is open but review was never dispatched\n// ============================================================================\n\n/**\n * Detect issues where `pan done` failed mid-flight: PR is open, all beads are\n * closed, reviewStatus is pending, and reviewRequestedAt was never written.\n * Verify all beads are closed before re-dispatching review; mark the record\n * with a tombstone so the patrol does not loop.\n */\nexport async function checkOrphanedCompletions(): Promise<string[]> {\n const actions: string[] = [];\n const now = new Date().toISOString();\n\n try {\n const statuses = loadReviewStatuses();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n try {\n if (status.reviewStatus !== 'pending') continue;\n if (status.mergeStatus === 'merged') continue;\n if (needsReviewDispatch(status)) continue;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const project = getProjectSync(resolved.projectKey);\n if (!project) continue;\n\n const record = readIssueRecordSync(project, issueId);\n if (record?.pipeline?.panDoneRecoveredAt) continue;\n\n const issueLower = issueId.toLowerCase();\n const workspacePath = findWorkspacePath(resolved.projectPath, issueLower);\n if (!workspacePath || !existsSync(workspacePath)) continue;\n\n const plan = readWorkspacePlanSync(workspacePath);\n if (!plan || plan.plan.items.length === 0) continue;\n if (plan.plan.items.some((item) => !['completed', 'cancelled'].includes(item.status))) continue;\n\n const { stdout } = await execAsync(\n `gh pr list --head feature/${issueLower} --state open --json url --jq '.[0].url'`,\n { encoding: 'utf-8', timeout: 15000 }\n );\n const prUrl = stdout.trim();\n if (!prUrl) continue;\n\n setReviewStatusSync(issueId, { reviewRequestedAt: now, prUrl });\n // Serialize with setReviewStatusSync's fire-and-forget journal rebuild so\n // the panDoneRecoveredAt tombstone is written after (and on top of) it.\n await updateIssueRecord(project, issueId, (nextRecord) => {\n Object.assign(nextRecord.pipeline, { reviewRequestedAt: now, prUrl, panDoneRecoveredAt: now });\n });\n\n const action = `checkOrphanedCompletions: recovered ${issueId} (PR open but review never dispatched)`;\n logDeaconEventSync(action);\n actions.push(action);\n console.log(`[deacon] ${action}`);\n } catch (issueErr: any) {\n console.warn(`[deacon] checkOrphanedCompletions: error for ${issueId}: ${issueErr?.message ?? issueErr}`);\n }\n }\n } catch (err: any) {\n console.warn(`[deacon] checkOrphanedCompletions: patrol error: ${err?.message ?? err}`);\n }\n\n return actions;\n}\n\n// ============================================================================\n// PAN-699: Pending test dispatch retry\n// ============================================================================\n\n/**\n * Retry test-agent dispatch for issues where review passed but the test-agent\n * never started or failed to dispatch.\n *\n * Conditions:\n * - reviewStatus === 'passed' AND (testStatus === 'pending' for >5min OR testStatus === 'dispatch_failed')\n * - Retries up to 3 times with exponential backoff tracked per-issue.\n */\nexport async function checkPendingTestDispatch(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const { loadReviewStatuses, setReviewStatusSync } = await import('../review-status.js');\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'passed') continue;\n if (status.testStatus !== 'pending' && status.testStatus !== 'dispatch_failed') continue;\n\n // PAN-1681: a live test session (idle or working) is the failsafe's job,\n // not a re-dispatch target. Re-dispatching a fresh agent here is what burnt\n // the 3-retry budget on idle agents that simply never signaled. Skip\n // entirely — checkCompletedButUnsignaledTests (run just before this) nudges\n // /auto-completes the existing agent. Only re-dispatch when NO live session.\n const testSession = `agent-${issueId.toLowerCase()}-test`;\n if (sessionExistsSync(testSession)) {\n const testPaneDead = await Effect.runPromise(isPaneDead(testSession)).catch(() => true);\n if (!testPaneDead) continue;\n }\n\n const retryCount = status.testRetryCount ?? 0;\n if (retryCount >= 3) {\n // PAN-1681: stop silently capping. With no live session to recover from\n // (we'd have continued above) and the verdict failsafe unable to recover,\n // the issue is genuinely stranded. Surface a one-time visible stuck marker\n // so the operator can see it instead of it sitting at test=pending forever.\n // Set only once — don't re-stamp every patrol.\n if (status.stuckReason !== 'test_signal_strand') {\n setReviewStatusSync(issueId, {\n stuck: true,\n stuckReason: 'test_signal_strand',\n stuckAt: new Date().toISOString(),\n testNotes: `Test stranded at ${status.testStatus} after ${retryCount} re-dispatches — the test agent never signaled and no verdict artifact was recovered. Inspect agent-${issueId.toLowerCase()}-test, or run: pan admin specialists done test ${issueId} --status passed|failed.`,\n });\n const msg = `Surfaced test strand for ${issueId}: stuck after ${retryCount} re-dispatches (test_signal_strand)`;\n actions.push(msg);\n console.warn(`[deacon] ${msg}`);\n }\n continue;\n }\n\n // For pending, only retry if it's been >5 minutes since review passed\n if (status.testStatus === 'pending') {\n const reviewPassedAt = status.history\n ?.filter(h => h.type === 'review' && h.status === 'passed')\n .pop()?.timestamp;\n if (reviewPassedAt && now - new Date(reviewPassedAt).getTime() < 5 * 60 * 1000) {\n continue;\n }\n }\n\n // PAN-1496/PAN-1613: never re-dispatch test for an issue closed on the\n // tracker or shadow-state. The check is reached only by the small set of\n // issues that pass all status filters above, and the shared helper is\n // TTL-cached — so this cannot storm the API.\n if (await isIssueClosed(issueId)) {\n console.log(`[deacon] ${issueId}: skipping test re-dispatch — issue is closed`);\n continue;\n }\n\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n\n const issueLower = issueId.toLowerCase();\n const workAgentId = `agent-${issueLower}`;\n const agentState = getAgentStateSync(workAgentId);\n const workspace = agentState?.workspace || findWorkspacePath(resolved.projectPath, issueLower);\n const branch = `feature/${issueLower}`;\n\n if (!workspace) {\n actions.push(`Skipped test retry for ${issueId}: workspace unavailable`);\n continue;\n }\n\n // PAN-1665: defer at the advancing-role concurrency ceiling; status stays\n // pending/dispatch_failed so a later patrol retries once a slot frees.\n // PAN-2507: first try to yield an idle work agent to free the slot.\n if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('test', issueId))) {\n actions.push(`Deferred test retry for ${issueId} — advancing-role concurrency ceiling reached`);\n logDeaconEventSync(`checkPendingTestDispatch: deferred test for ${issueId} — advancing ceiling reached (PAN-1665) — ${describeRunningAgents()}`);\n continue;\n }\n const { spawnRun } = await import('../agents.js');\n const { buildTestRolePrompt } = await import('./test-agent-queue.js');\n try {\n const run = await spawnRun(issueId, 'test', {\n workspace,\n prompt: buildTestRolePrompt({ issueId, workspace, branch }),\n startedBy: 'deacon:test-retry',\n });\n setReviewStatusSync(issueId, { testStatus: 'testing', testRetryCount: retryCount + 1 });\n recordDeaconNudge({\n patrol: 'checkPendingTestDispatch',\n issueId,\n action: `dispatched test role ${run.id} (retry ${retryCount + 1})`,\n reason: 'review=passed + test=pending, but the reactive review→test handoff (review.approved → testing) never fired — the deacon had to dispatch test',\n state: {\n reviewStatus: status.reviewStatus,\n testStatus: status.testStatus,\n retryCount,\n reviewedAtCommit: status.reviewedAtCommit,\n lastVerifiedCommit: status.lastVerifiedCommit,\n },\n });\n actions.push(`Dispatched test role ${run.id} for ${issueId} (retry ${retryCount + 1})`);\n console.log(`[deacon] Dispatched test role for ${issueId} (retry ${retryCount + 1})`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('already running')) {\n setReviewStatusSync(issueId, { testStatus: 'testing', testRetryCount: retryCount + 1 });\n actions.push(`Test role already running for ${issueId} (retry ${retryCount + 1})`);\n } else {\n setReviewStatusSync(issueId, {\n testStatus: 'dispatch_failed',\n testNotes: msg,\n testRetryCount: retryCount + 1,\n });\n actions.push(`Test role dispatch error for ${issueId}: ${msg}`);\n console.error(`[deacon] Test role dispatch error for ${issueId}:`, msg);\n }\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking pending test dispatch:', msg);\n }\n\n return actions;\n}\n\n// ============================================================================\n// Completed-but-unsignaled test detection (PAN-1681)\n// ============================================================================\n\n/**\n * Detect test agents that finished but never persisted their verdict — the test\n * twin of {@link checkCompletedButUnsignaledReviews}. The test role narrates\n * \"tests pass\" but the agent (often Haiku 4.5) sometimes never POSTs testStatus,\n * stranding the issue at test=pending/testing with a live-but-idle session.\n *\n * Recovery is symmetric with the review failsafe: the test role writes a\n * deterministic verdict artifact (.pan/test/result.json) BEFORE it POSTs, and\n * this patrol reads it back to recover the verdict. It NEVER guesses pass/fail\n * (continue.json D6) — auto-complete fires only from a written artifact; with no\n * artifact it nudges once to prompt write+POST, then defers to the\n * strand-surfacing path in checkPendingTestDispatch.\n *\n * Guards (hazard H4):\n * - Only fires when reviewStatus === 'passed' && testStatus ∈ {testing,pending}\n * - Skips closed issues (isIssueClosed) and stuck/ignored issues\n * - Gates a live session on isAgentIdleForNudge + a 5-min settle window\n * - Nudges at most once per test cycle (deduped by session) before completing\n * - Only honors an artifact newer than the current test dispatch (H3)\n */\nconst unsignaledTestNudges = new Map<string, number>();\n/** PAN-3092: test dispatch generations already escalated — never nudged again. */\nconst unsignaledTestEscalations = new Set<string>();\n\nexport async function checkCompletedButUnsignaledTests(): Promise<string[]> {\n const actions: string[] = [];\n const TEST_SETTLE_MS = 5 * 60 * 1000; // 5 minutes — mirror SYNTHESIS_SETTLE_MS\n const NUDGE_DEDUP_MS = 30 * 60 * 1000;\n\n try {\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'passed') continue;\n if (status.testStatus !== 'testing' && status.testStatus !== 'pending') continue;\n if (status.stuck || status.deaconIgnored) continue;\n if (await isIssueClosed(issueId)) continue;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const issueLower = issueId.toLowerCase();\n const wsPath = findWorkspacePath(resolved.projectPath, issueLower);\n if (!wsPath) continue;\n\n const testSession = `agent-${issueLower}-test`;\n const sessionAlive = sessionExistsSync(testSession);\n const paneDead = sessionAlive ? await Effect.runPromise(isPaneDead(testSession)).catch(() => true) : true;\n const sessionLive = sessionAlive && !paneDead;\n const idle = sessionLive ? isAgentIdleForNudge(testSession, TEST_SETTLE_MS, now) : false;\n\n // Only honor an artifact newer than the current test dispatch so a previous\n // cycle's verdict is never read after a re-dispatch (H3). The latest\n // test→'testing' history entry is the dispatch time.\n const lastDispatchAt = status.history\n ?.filter(h => h.type === 'test' && h.status === 'testing')\n .pop()?.timestamp;\n\n const lastNudged = unsignaledTestNudges.get(testSession);\n // PAN-3092: once a dispatch generation has been escalated to the operator,\n // it stays escalated. Without this the 30-minute nudge window reopens and\n // the patrol keeps waking an agent the operator has already been told about.\n const escalationKey = `${testSession}:${lastDispatchAt ?? 'unknown'}`;\n const alreadyNudged = unsignaledTestEscalations.has(escalationKey)\n || !!(lastNudged && now - lastNudged < NUDGE_DEDUP_MS);\n const minMtimeMs = lastDispatchAt ? new Date(lastDispatchAt).getTime() : undefined;\n const artifact = readTestVerdictArtifact(wsPath, minMtimeMs);\n\n const decision = decideUnsignaledTestAction({ sessionLive, idle, alreadyNudged, artifact });\n\n switch (decision.action) {\n case 'auto-complete': {\n const fallbackNote = `Test auto-completed by deacon: ${decision.status} (verdict artifact present, agent ${sessionLive ? 'alive but unresponsive after nudge' : 'dead'})`;\n setReviewStatusSync(issueId, {\n testStatus: decision.status,\n testNotes: decision.notes || fallbackNote, ...(decision.uatStatus ? { uatStatus: decision.uatStatus, uatNotes: decision.uatNotes || decision.notes } : {}),\n });\n const msg = `Auto-completed test for ${issueId}: ${decision.status} (${sessionLive ? 'alive but unresponsive after nudge' : 'dead agent'}, verdict artifact)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n break;\n }\n case 'nudge-verdict': {\n const noteArg =\n decision.status === 'failed'\n ? ` --notes \"${(decision.notes || 'See .pan/test/result.json').replace(/\"/g, \"'\").slice(0, 120)}\"`\n : '';\n const cmd = `pan admin specialists done test ${issueId} --status ${decision.status}${noteArg}${decision.uatStatus ? ` --uat-status ${decision.uatStatus}${decision.uatNotes ? ` --uat-notes \"${decision.uatNotes.replace(/\"/g, \"'\").slice(0, 120)}\"` : ''}` : ''}`;\n const nudge = `Your test verdict (${decision.status}) is already written to .pan/test/result.json. Your ONLY remaining task is to execute this Bash command immediately — do not analyze, do not summarize, do not ask questions, just run it:\\n\\n${cmd}\\n\\nRun this command NOW. Do not write any other response before executing it.`;\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(testSession, nudge);\n unsignaledTestNudges.set(testSession, now);\n const msg = `Nudged ${testSession} to signal ${decision.status} (verdict artifact present)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n } catch (err: any) {\n console.error(`[deacon] Failed to nudge ${testSession}:`, err.message);\n }\n break;\n }\n case 'nudge-write': {\n const nudge = `Your test run for ${issueId} looks finished but no verdict was recorded. Decide the automated-gate verdict and, when UAT was required, its separate verdict. Then do BOTH now: (1) write .pan/test/result.json as {\"status\":\"passed\"|\"failed\",\"notes\":\"<gate evidence>\",\"uatStatus\":\"passed\"|\"failed\",\"uatNotes\":\"<UAT evidence>\"} (omit UAT fields only when UAT was not required), and (2) run: pan admin specialists done test ${issueId} --status passed|failed [--uat-status passed|failed]. Do this immediately — do not summarize or ask questions, just write the file and run the command.`;\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(testSession, nudge);\n unsignaledTestNudges.set(testSession, now);\n const msg = `Nudged ${testSession} to write+signal a test verdict (no artifact yet)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n } catch (err: any) {\n console.error(`[deacon] Failed to nudge ${testSession}:`, err.message);\n }\n break;\n }\n case 'escalate': {\n // PAN-3092 (MIN-858): alive, idle, already nudged, no artifact — the\n // verdict exists only in the pane, if at all, and nothing automatic can\n // reach it. Tell a human once per dispatch generation rather than going\n // quiet for six hours while every surface reports the agent healthy.\n unsignaledTestEscalations.add(escalationKey);\n const msg = await recordUnsignaledTestEscalation(\n wsPath, issueId, testSession, lastDispatchAt ?? 'unknown',\n );\n if (msg) { actions.push(msg); console.warn(`[deacon] ${msg}`); }\n break;\n }\n case 'wait':\n case 'none':\n default:\n break;\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking completed-but-unsignaled tests:', msg);\n }\n\n return actions;\n}\n\n// ============================================================================\n// Verification/review contradiction (PAN-796)\n// ============================================================================\n\nexport async function checkVerificationReviewContradiction(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { loadReviewStatuses } = await import('../review-status.js');\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const statuses = loadReviewStatuses();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (\n status.verificationStatus === 'passed' &&\n status.reviewStatus === 'reviewing' &&\n status.stuckReason === 'review_infrastructure_failure'\n ) {\n // Snapshot the workspace HEAD so reviewedAtCommit is populated — without\n // it, checkPostReviewCommits can never confirm the review is current and\n // the canSkipTests fast-path in setReviewStatus never fires, leaving the\n // issue jammed at passed-but-stuck forever (PAN-977).\n let reviewedAtCommit;\n try {\n const project = resolveProjectFromIssueSync(issueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`,\n );\n if (existsSync(workspacePath)) {\n const { snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n reviewedAtCommit = await snapshotWorkspaceHeadsPromise(issueId, workspacePath);\n }\n }\n } catch { /* non-fatal — leave reviewedAtCommit undefined */ }\n\n // Clearing the stuck marker is mandatory: the bypass *resolves* the\n // review-infra failure, so the issue must no longer be skipped by the\n // deacon's stuck-issue guards or it deadlocks at passed+stuck.\n // PAN-3512: this carries a LIVE head, so dispatch-not-drop lands and re-gates it rather than silently rejecting it against a stale row anchor — intended.\n const { recordReviewVerdict } = await import('./review-verdict-writer.js');\n const bypass = await recordReviewVerdict(issueId, {\n verdict: 'passed',\n notes: 'Review bypassed: verification passed but review infrastructure repeatedly failed.',\n ...(reviewedAtCommit ? { evidenceHead: reviewedAtCommit } : {}),\n extra: { stuck: false, stuckReason: undefined, stuckAt: undefined, stuckDetails: undefined },\n writer: 'infra-bypass',\n });\n const msg = bypass.landed ? `Bypassed review for ${issueId}: verification passed, review infra failed` : `Review bypass for ${issueId} not recorded (${bypass.reason})`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n }\n } catch (error: unknown) {\n console.error('[deacon] Error checking verification/review contradiction:', error);\n }\n return actions;\n}\n\n// ============================================================================\n// Post-review commit detection\n// ============================================================================\n\nexport { checkPostReviewCommits };\n\n// Callback set by the server layer to emit domain events when agents are stopped.\n// Deacon is a library module and does not own the event store directly.\nlet agentStoppedNotifier: ((agentId: string) => void) | null = null;\nlet agentStatusChangedNotifier: ((state: AgentState, previousStatus?: AgentState['status'], hasLiveTmuxSession?: boolean) => void) | null = null;\nconst orphanFailureRecordedForAutoResume = new Set<string>();\n\n/**\n * Register a callback that deacon will call when it detects an orphaned agent\n * and resets it to stopped. The server layer uses this to emit an agent.stopped\n * domain event so the read model and frontend update in real-time.\n */\nexport function setAgentStoppedNotifier(fn: (agentId: string) => void): void {\n agentStoppedNotifier = fn;\n}\n\n/** Register a callback for Deacon-owned AgentState changes that must reach live clients. */\nexport function setAgentStatusChangedNotifier(fn: (state: AgentState, previousStatus?: AgentState['status'], hasLiveTmuxSession?: boolean) => void): void {\n agentStatusChangedNotifier = fn;\n}\n\nfunction notifyAgentStatusChanged(state: AgentState, previousStatus?: AgentState['status'], hasLiveTmuxSession?: boolean): void {\n if (!agentStatusChangedNotifier) return;\n try { agentStatusChangedNotifier(state, previousStatus, hasLiveTmuxSession); } catch { /* non-fatal */ }\n}\n\nfunction notifyAgentStopped(agentId: string): void {\n if (!agentStoppedNotifier) return;\n try { agentStoppedNotifier(agentId); } catch { /* non-fatal */ }\n}\n\nfunction autoResumeNotifierDeps() {\n return {\n notifyAgentStopped,\n notifyAgentStatusChanged,\n };\n}\n\nexport async function handleAgentHeartbeatDeadEvent(agentId: string, context?: string): Promise<string[]> {\n return handleAgentHeartbeatDeadEventWithDeps(agentId, context, autoResumeNotifierDeps());\n}\n\nexport async function recoverOrphanedAgents(context?: string): Promise<string[]> {\n return recoverOrphanedAgentsWithDeps(context, autoResumeNotifierDeps());\n}\n\nexport async function cleanupOrphanedPlanningSessions(): Promise<string[]> {\n return cleanupOrphanedPlanningSessionsWithDeps(autoResumeNotifierDeps());\n}\n\nexport async function handleAgentStoppedEvent(agentId: string, opts = {}): Promise<string | null> {\n return handleAgentStoppedEventWithDeps(agentId, opts, autoResumeNotifierDeps());\n}\n\nexport async function autoResumeStoppedWorkAgents(): Promise<string[]> {\n return autoResumeStoppedWorkAgentsWithDeps(autoResumeNotifierDeps());\n}\n\nexport async function applyBootReconciliationDecision(opts: BootReconciliationApplyOptions = {}): Promise<BootReconciliationApplyResult> {\n return applyBootReconciliationDecisionWithDeps(autoResumeNotifierDeps(), opts);\n}\n\nexport async function reconcileAgentLiveness(): Promise<string[]> {\n if (isDeaconGloballyPaused()) { logDeaconEventSync('reconcileAgentLiveness skipped — deacon globally paused'); return []; }\n return reconcileAgentLivenessWithDeps(autoResumeNotifierDeps());\n}\n// Callback set by the server layer to emit Socket.io merge:ready notifications.\n// Deacon is a library module and does not own the Socket.io instance directly.\nlet mergeReadyNotifier: ((issueId: string) => void) | null = null;\n\n/**\n * Register a callback that deacon will call when it detects an issue stuck in\n * readyForMerge state. The server layer uses this to emit a Socket.io event\n * so the dashboard can alert the user to click MERGE.\n */\nexport function setMergeReadyNotifier(fn: (issueId: string) => void): void {\n mergeReadyNotifier = fn;\n}\n\nexport async function checkReadyForMergeStuck(): Promise<string[]> {\n return checkReadyForMergeStuckWithDeps({\n loadState,\n saveState,\n hasMergeReadyNotifier: () => mergeReadyNotifier !== null,\n notifyMergeReady: (issueId) => {\n if (mergeReadyNotifier) mergeReadyNotifier(issueId);\n },\n });\n}\n\n\n\n\n\n\n\n\nconst testStatusGreenCiReconcileCooldowns = new Map<string, number>();\nconst TEST_STATUS_GREEN_CI_RECONCILE_COOLDOWN_MS = 5 * 60 * 1000;\n\n\n\nexport async function reconcileTestStatusFromGreenCi(): Promise<string[]> {\n const { getCiCheckRunsState, getPullRequestHeadState, isGitHubAppConfigured } = await import('../github-app.js');\n return reconcileTestStatusFromGreenCiWithDeps({\n isGitHubAppConfigured,\n loadReviewStatuses,\n getPullRequestHeadState,\n getCiCheckRunsState,\n setReviewStatusSync,\n cooldowns: testStatusGreenCiReconcileCooldowns,\n cooldownMs: TEST_STATUS_GREEN_CI_RECONCILE_COOLDOWN_MS,\n now: () => Date.now(),\n log: (message) => console.log(`[deacon] ${message}`),\n warn: (message) => console.warn(`[deacon] ${message}`),\n });\n}\n\n\n\n\n// ============================================================================\n// Stale feedback cleanup (PAN-705 flywheel fix)\n// ============================================================================\n\n/**\n * Remove stale CI-failure feedback files from a workspace's .pan/feedback/ dir.\n * These accumulate when the merge-agent retries CI-blocked merges, and cause\n * the work agent to incorrectly believe CI is still failing on resume.\n */\nasync function clearStaleCiFeedback(issueId: string): Promise<void> {\n const { readdir, rm } = await import('fs/promises');\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const projectConfig = resolveProjectFromIssueSync(issueId);\n if (!projectConfig) return;\n\n const repoDir = projectConfig.projectPath;\n if (!repoDir) return;\n\n // Find the workspace directory: workspaces/feature-<issueLower> under the repo\n const issueLower = issueId.toLowerCase();\n const feedbackDir = join(repoDir, 'workspaces', `feature-${issueLower}`, '.pan', 'feedback');\n\n try {\n if (!existsSync(feedbackDir)) return;\n const files = await readdir(feedbackDir);\n for (const file of files) {\n if (file.includes('merge-agent') && file.includes('ci-failure') && file.endsWith('.md')) {\n await rm(join(feedbackDir, file));\n console.log(`[deacon] Cleared stale CI feedback: ${file} for ${issueId}`);\n }\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n console.log(`[deacon] Could not clear stale CI feedback for ${issueId}: ${message}`);\n }\n}\n\n// Track per-issue cooldowns for dead-end recovery to avoid spamming\nconst deadEndCooldowns = new Map<string, number>();\n\n// Minimum time (ms) after status update before dead-end detection intervenes\nconst DEAD_END_STALENESS_MS = 5 * 60 * 1000; // 5 minutes\n// Cooldown between successive dead-end recovery attempts for the same issue\nconst DEAD_END_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes\n\n/**\n * Detect dead-end agents: review blocked, tests failed, or merge failed (CI)\n * but work agent is idle.\n *\n * This happens when:\n * - Review feedback was delivered with a wrong URL (now fixed, but old feedback persists)\n * - Agent forgot how to resubmit (context compaction lost instructions)\n * - Feedback delivery to tmux failed silently\n * - Merge failed due to CI checks (now handled in checkFailedMergeRetry by routing\n * to the work agent with feedback; dead-end catches cases where the agent is idle)\n *\n * Recovery: re-queue the review via the request-review API endpoint and\n * send the agent a nudge message with the correct resubmit command.\n * For CI-blocked merges: clear the stale merge failure and feedback files.\n */\nexport interface CheckDeadEndAgentsDeps {\n loadReviewStatuses?: typeof loadReviewStatuses;\n sessionExistsSync?: typeof sessionExistsSync;\n getAgentStateSync?: typeof getAgentStateSync;\n getAgentDir?: typeof getAgentDir;\n decideAgentAutonomousRedrive?: typeof decideAgentAutonomousRedrive;\n recordDeadEndNeedsYou?: typeof recordDeadEndNeedsYou;\n spawnWorkAgentThroughAgentsEndpoint?: typeof spawnWorkAgentThroughAgentsEndpoint;\n setReviewStatusSync?: typeof setReviewStatusSync;\n clearAgentTroubledSync?: typeof clearAgentTroubledSync;\n saveAgentStateSync?: typeof saveAgentStateSync;\n now?: () => number;\n}\n\nexport async function checkDeadEndAgents(deps: CheckDeadEndAgentsDeps = {}): Promise<string[]> {\n const actions: string[] = [];\n const readReviewStatuses = deps.loadReviewStatuses ?? loadReviewStatuses;\n const hasSession = deps.sessionExistsSync ?? sessionExistsSync;\n const readAgentState = deps.getAgentStateSync ?? getAgentStateSync;\n const resolveAgentDir = deps.getAgentDir ?? getAgentDir;\n const decideRedrive = deps.decideAgentAutonomousRedrive ?? decideAgentAutonomousRedrive;\n const recordNeedsYou = deps.recordDeadEndNeedsYou ?? recordDeadEndNeedsYou;\n const spawnWorkAgent = deps.spawnWorkAgentThroughAgentsEndpoint ?? spawnWorkAgentThroughAgentsEndpoint;\n const updateReviewStatus = deps.setReviewStatusSync ?? setReviewStatusSync;\n const clearAgentTroubled = deps.clearAgentTroubledSync ?? clearAgentTroubledSync;\n const saveAgentState = deps.saveAgentStateSync ?? saveAgentStateSync;\n\n try {\n const statuses = readReviewStatuses();\n\n const now = deps.now?.() ?? Date.now();\n\n for (const [key, status] of Object.entries(statuses)) {\n // Only act on blocked/failed reviews, failed tests, or CI-blocked merges.\n // 'failed' covers verification gate errors (e.g. JSON parse error in `.pan/spec.vbrief.json`)\n // that prevent the review specialist from running at all.\n const isReviewBlocked = status.reviewStatus === 'blocked' || status.reviewStatus === 'failed';\n const isVerificationFailed = status.verificationStatus === 'failed';\n const isTestFailed = status.testStatus === 'failed';\n const isMergeCiFailed = status.mergeStatus === 'failed' &&\n typeof status.mergeNotes === 'string' &&\n status.mergeNotes.includes('failing required checks');\n if (!isReviewBlocked && !isVerificationFailed && !isTestFailed && !isMergeCiFailed) continue;\n\n // Skip merged/completed issues\n if (status.mergeStatus === 'merged' || status.readyForMerge) continue;\n\n // Check staleness: status must have been set at least 5 min ago\n if (status.updatedAt) {\n const statusAge = now - new Date(status.updatedAt).getTime();\n if (statusAge < DEAD_END_STALENESS_MS) continue;\n }\n\n // Check per-issue cooldown\n const lastRecovery = deadEndCooldowns.get(key);\n if (lastRecovery && (now - lastRecovery) < DEAD_END_COOLDOWN_MS) continue;\n\n // Circuit breaker: don't intervene if already at max requeues\n const autoRequeueCount = status.autoRequeueCount || 0;\n if (autoRequeueCount >= 25) {\n console.log(`[deacon] Dead-end detected for ${key} but circuit breaker active (${autoRequeueCount}/25 requeues used)`);\n const trip = await recordDeadEndNeedsYou(key, 'dead-end-rebuild', status.updatedAt ?? 'rework', `Dead-end recovery needs-you: ${key} exhausted 25 requeues`);\n if (trip) actions.push(trip);\n continue;\n }\n\n // CI-blocked merges have their own retry circuit breaker in checkFailedMergeRetry().\n // Once that counter is saturated, only a new commit should reset the merge path.\n if (isMergeCiFailed && (status.mergeRetryCount || 0) >= FAILED_MERGE_MAX_RETRIES) {\n console.log(`[deacon] Dead-end detected for ${key} but merge retry ceiling is saturated (${status.mergeRetryCount}/${FAILED_MERGE_MAX_RETRIES})`);\n continue;\n }\n\n // The review-status map key is the authoritative issue identifier.\n // Persisted payloads can carry stale/mismatched status.issueId values after\n // retries or manual edits; using the key keeps recovery actions targeted to\n // the actual status entry being processed.\n const issueId = key;\n const agentSessionName = `agent-${issueId.toLowerCase()}`;\n\n // Human-readable status type — computed up front so both the\n // respawn-on-dead-agent path (PAN-2209) and the nudge-on-idle path can use it.\n let statusType: string;\n if (isReviewBlocked) {\n statusType = status.reviewStatus === 'failed' ? 'review failed' : 'review blocked';\n } else if (isVerificationFailed) {\n statusType = status.reviewStatus === 'pending'\n ? 'verification failed while review pending'\n : 'verification failed';\n } else if (isTestFailed) {\n statusType = 'tests failed';\n } else {\n statusType = 'merge CI blocked';\n }\n\n if (!hasSession(agentSessionName)) {\n // PAN-2209: respawn a dead work agent whose pending rework kickoff drains\n // `.pan/feedback` so it addresses the review. Merge-CI dead-ends need no\n // work session and keep their prior no-session behavior (they fall through\n // to `continue`; the merge-CI branch below requires a live session).\n if (isReviewBlocked || isVerificationFailed || isTestFailed) {\n const agentState = readAgentState(agentSessionName);\n const gateDecision = await decideRedrive(agentState ?? {}, resolveAgentDir(agentSessionName), true);\n if (gateDecision.decision === 'defer') {\n console.log(`[deacon] PAN-2209: ${issueId} (${statusType}) re-drive deferred — ${gateDecision.reason}`);\n const pendingOperatorDecision = gateDecision.reason.startsWith('agent is waiting on an operator decision');\n const recoveryPath = pendingOperatorDecision ? 'pending-operator-decision' : 'operator-stopped-rework';\n const needsYouMessage = pendingOperatorDecision\n ? `Dead-end recovery needs-you: ${issueId} ${gateDecision.reason}. Answer it with 'pan answer ${issueId}'.`\n : `Dead-end recovery needs-you: ${issueId} was explicitly stopped before handoff`;\n const trip = gateDecision.needsYou && await recordNeedsYou(\n issueId,\n recoveryPath,\n status.updatedAt ?? statusType,\n needsYouMessage,\n );\n if (trip) actions.push(trip);\n } else {\n if (gateDecision.gateDecision.clearStoppedByUser && agentState) { delete agentState.stoppedByUser; saveAgentState(agentState); }\n deadEndCooldowns.set(key, now); // Engage the shared circuit breaker so a chronically-dying agent\n // cannot be respawned forever (>=25 requeues halts, checked above).\n updateReviewStatus(issueId, { autoRequeueCount: autoRequeueCount + 1 });\n // Clear only the system-set `troubled` gate (resume-failure counter\n // after completion) — the false state that strands the rework, per\n // PAN-2209 evidence. Paused agents were already excluded above.\n clearAgentTroubled(agentSessionName);\n try {\n const spawn = await spawnWorkAgent(issueId);\n if (spawn.spawned) {\n actions.push(`Dead-end recovery (PAN-2209): respawned dead work agent for ${issueId} (${statusType}) to address feedback`);\n console.log(`[deacon] PAN-2209: respawned dead work agent for ${issueId} (${statusType})`);\n } else if (spawn.skippedReason === 'stack-unhealthy') {\n // PAN-2520: the respawn can't proceed because the workspace Docker\n // stack is down. Deferring here loops forever — nothing else rebuilds\n // the stack. Trigger the same rebuild-and-start recovery the dashboard\n // button uses (rebuild stack → `pan start`). The per-issue dead-end\n // cooldown and autoRequeueCount breaker (both set above) bound the\n // retries, so a stack that can't be rebuilt still escalates instead\n // of hammering.\n try {\n const rebuild = await triggerRebuildAndStart(issueId);\n if (rebuild.triggered) {\n actions.push(`Dead-end recovery (PAN-2520): stack-unhealthy for ${issueId} (${statusType}) — triggered rebuild-and-start`);\n console.log(`[deacon] PAN-2520: triggered rebuild-and-start for ${issueId} (stack-unhealthy)`);\n } else {\n actions.push(`Dead-end recovery (PAN-2520): rebuild-and-start for ${issueId} failed — ${rebuild.error}`);\n console.log(`[deacon] PAN-2520: rebuild-and-start for ${issueId} failed: ${rebuild.error}`);\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] PAN-2520: rebuild-and-start failed for ${issueId}: ${msg}`);\n actions.push(`Dead-end recovery (PAN-2520): rebuild-and-start failed for ${issueId}: ${msg}`);\n }\n } else {\n actions.push(`Dead-end recovery (PAN-2209): respawn of ${issueId} deferred — ${spawn.skippedReason ?? spawn.error}`);\n console.log(`[deacon] PAN-2209: respawn of ${issueId} deferred: ${spawn.skippedReason ?? spawn.error}`);\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] PAN-2209: respawn failed for ${issueId}: ${msg}`);\n actions.push(`Dead-end recovery (PAN-2209): respawn failed for ${issueId}: ${msg}`);\n }\n }\n }\n // No live session to nudge — done with this issue this cycle.\n continue;\n }\n\n // Check if agent is idle via Stop hook state (authoritative idle signal)\n if (!isAgentIdleForNudge(agentSessionName)) {\n // Agent is still working or has no hook state — let it finish\n continue;\n }\n\n // Agent is idle with a blocked/failed status — this is a dead end\n console.log(`[deacon] Dead-end detected: ${key} (${statusType}) with idle agent ${agentSessionName}`);\n\n // Record cooldown before taking action\n deadEndCooldowns.set(key, now);\n\n // For merge CI-blocked: clear the stale merge failure, clean up stale\n // CI-failure feedback files, and set readyForMerge so the merge flow can re-enter.\n if (isMergeCiFailed) {\n setReviewStatusSync(issueId, {\n mergeStatus: 'pending',\n readyForMerge: true,\n });\n // Reset CI retry counter so the next CI failure re-enters at attempt 1/5\n // instead of silently dead-ending due to the exhausted retry count.\n ciRetryMap.delete(issueId);\n // Clean up accumulated stale feedback so the work agent doesn't read them\n await clearStaleCiFeedback(issueId).catch(() => {});\n console.log(`[deacon] Cleared stale CI-blocked merge for ${issueId} — reset to readyForMerge`);\n actions.push(`Dead-end recovery: cleared CI-blocked merge for ${issueId} (${statusType}, idle for ${Math.round((now - new Date(status.updatedAt || '').getTime()) / 60000)}m)`);\n continue;\n }\n\n // Resolve latest feedback file for targeted nudge\n let latestFeedbackPath: string | undefined;\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n const wsPath = findWorkspacePath(resolved.projectPath, issueId.toLowerCase());\n if (wsPath) {\n const feedbackDir = join(wsPath, '.pan', 'feedback');\n if (existsSync(feedbackDir)) {\n const files = (await readdir(feedbackDir)).filter(f => f.endsWith('.md')).sort();\n if (files.length > 0) {\n latestFeedbackPath = join(feedbackDir, files[files.length - 1]);\n }\n }\n }\n }\n } catch {\n // ignore resolution errors\n }\n\n // Send the agent a nudge message with the correct resubmit command\n try {\n const feedbackPart = latestFeedbackPath\n ? `\\n\\nMUST READ: ${latestFeedbackPath}\\n\\nUse your Read tool to open this file, read every line, then fix the issues.`\n : '';\n const nudgeMessage = status.reviewStatus === 'failed'\n ? `Review verification failed for ${issueId}.${feedbackPart}\\n\\nCommon cause: merge conflict markers in .pan/spec.vbrief.json — fix by resolving conflicts in that file, then run: pan review request ${issueId} -m \"Fixed verification error\"`\n : isReviewBlocked\n ? `The review agent found issues in your code.${feedbackPart}\\n\\nFix every issue listed, commit all changes, then run: pan review request ${issueId} -m \"Fixed review issues\". If the exec yields, poll the same background terminal until it exits. Require exit code 0 and confirm pan show ${issueId} or pan review pending shows re-entry before declaring success.`\n : isVerificationFailed\n ? `Verification failed for ${issueId} while review is pending.${feedbackPart}\\n\\nFix the failing verification check, commit every change, push your branch, then request a new review with: pan review request ${issueId} -m \"Fixed verification failure\". If the exec yields, poll the same background terminal until it exits. Require exit code 0 and confirm pan show ${issueId} or pan review pending shows re-entry before declaring success.`\n : `Tests failed for your changes.${feedbackPart}\\n\\nFix the failures, commit, then run: pan review request ${issueId} -m \"Fixed test failures\". If the exec yields, poll the same background terminal until it exits. Require exit code 0 and confirm pan show ${issueId} or pan review pending shows re-entry before declaring success.`;\n\n await Effect.runPromise(sendKeys(agentSessionName, nudgeMessage));\n actions.push(`Dead-end recovery: nudged ${agentSessionName} (${statusType}, idle for ${Math.round((now - new Date(status.updatedAt || '').getTime()) / 60000)}m)`);\n console.log(`[deacon] Sent dead-end recovery nudge to ${agentSessionName}`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Failed to send dead-end nudge to ${agentSessionName}:`, msg);\n actions.push(`Dead-end recovery failed for ${agentSessionName}: ${msg}`);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error in dead-end detection:', msg);\n }\n\n return actions;\n}\n\nasync function reconcileAndCheckIfMerged(\n issueId: string,\n cycleCache?: Map<string, boolean>,\n): Promise<boolean> {\n const cacheKey = issueId.toUpperCase();\n const cached = cycleCache?.get(cacheKey);\n if (cached !== undefined) return cached;\n const remember = (result: boolean): boolean => {\n cycleCache?.set(cacheKey, result);\n return result;\n };\n\n const reviewStatus = getReviewStatusSync(issueId);\n if (reviewStatus?.mergeStatus === 'merged') {\n return remember(true);\n }\n\n if (reviewStatus?.prUrl) {\n const prRef = reviewStatus.prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (prRef) {\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../github-app.js');\n if (isGitHubAppConfigured()) {\n const prState = await Effect.runPromise(getPullRequestState(prRef[1], prRef[2], Number.parseInt(prRef[3], 10)));\n if (prState.merged) {\n setReviewStatusSync(issueId, { mergeStatus: 'merged', readyForMerge: false, mergeNotes: undefined });\n // PAN-1027: also run the post-merge handoff so labels get cleaned and the\n // work-agent tmux session is killed. Without this, GitHub-detected merges\n // leave stale \"in-progress\" / \"in-review\" labels and leaked tmux sessions.\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n const branch = `feature/${issueId.toLowerCase()}`;\n const { postMergeLifecycle } = await import('./merge-agent.js');\n postMergeLifecycle(issueId, resolved.projectPath, branch, { skipDeploy: true }).catch(err =>\n console.warn(`[deacon] postMergeLifecycle (gh-reconcile) failed for ${issueId}: ${err}`)\n );\n }\n } catch (err) {\n console.warn(`[deacon] Could not run post-merge handoff for ${issueId}: ${err}`);\n }\n return remember(true);\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[deacon] Failed GitHub merge reconciliation for ${issueId}: ${message}`);\n }\n }\n }\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) {\n return remember(false);\n }\n\n const project = getProjectSync(resolved.projectKey);\n if (!project?.tracker) {\n return remember(false);\n }\n\n try {\n const { createTracker } = await import('../tracker/factory.js');\n const overdeckConfig = await import('../config.js');\n const globalTrackerConfig = overdeckConfig.loadConfigSync().trackers;\n\n let trackerConfig: TrackerConfig | null = null;\n if (project.tracker === 'github' && project.github_repo) {\n const [owner, repo] = project.github_repo.split('/');\n if (!owner || !repo) {\n console.warn(`[deacon] Cannot reconcile ${issueId}: invalid GitHub repo config for ${resolved.projectKey}`);\n return remember(false);\n }\n trackerConfig = {\n type: 'github',\n owner,\n repo,\n tokenEnv: globalTrackerConfig.github?.token_env,\n };\n } else if (project.tracker === 'gitlab' && project.gitlab_repo) {\n trackerConfig = {\n type: 'gitlab',\n projectId: project.gitlab_repo,\n tokenEnv: globalTrackerConfig.gitlab?.token_env,\n };\n } else if (project.tracker === 'linear') {\n trackerConfig = {\n type: 'linear',\n apiKeyEnv: globalTrackerConfig.linear?.api_key_env,\n team: resolved.linearTeam,\n };\n } else if (project.tracker === 'rally') {\n trackerConfig = {\n type: 'rally',\n apiKeyEnv: globalTrackerConfig.rally?.api_key_env,\n server: globalTrackerConfig.rally?.server,\n workspace: globalTrackerConfig.rally?.workspace,\n project: project.rally_project ?? globalTrackerConfig.rally?.project,\n };\n }\n\n if (!trackerConfig) {\n console.warn(`[deacon] Cannot reconcile ${issueId}: incomplete tracker config for ${resolved.projectKey}`);\n return remember(false);\n }\n\n if (project.tracker === 'github') {\n return remember(false);\n }\n\n const tracker = createTracker(trackerConfig);\n await Effect.runPromise(tracker.getIssue(issueId));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[deacon] Failed tracker merge reconciliation for ${issueId}: ${message}`);\n }\n\n return remember(false);\n}\n\n\n\n\n\n\n\n\n// PAN-650: Bounded poking for stuck agents.\n// Without these limits, the patrol fires every 60s and re-sends the same poke\n// forever, eating tokens overnight. Cap pokes and require cooldown between them;\n// after the cap, transition resolution → 'abandoned' so the agent falls out of\n// the patrol filter and surfaces in the dashboard for human attention.\nconst STUCK_POKE_COOLDOWN_MS = 15 * 60 * 1000; // 15 minutes between pokes\nconst STUCK_POKE_MAX = 3; // Max pokes before abandoning\nconst stuckPokeState: Map<string, { lastPoke: number; pokes: number }> = new Map();\n\n/**\n * Patrol work agent resolution fields (PAN-309).\n *\n * For each running work agent:\n * - resolution === 'done' && count >= 2: auto-complete via pan done\n * - resolution === 'stuck' && count >= 3: send a poke (rate-limited, capped — PAN-650)\n */\nexport async function patrolWorkAgentResolutions(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const agents = listRunningAgentsSync();\n // Specialist sessions (global or per-project) use the specialist tmux prefix.\n const isSpecialistSession = (id: string) => id.startsWith('specialist-');\n\n for (const agent of agents) {\n if (!agent.id.startsWith('agent-') || isSpecialistSession(agent.id)) continue;\n\n const runtimeState = getAgentRuntimeStateSync(agent.id);\n if (!runtimeState?.resolution || runtimeState.resolution === 'working' || runtimeState.resolution === 'completed' || runtimeState.resolution === 'abandoned') continue;\n\n const resolution = runtimeState.resolution;\n const count = runtimeState.resolutionCount || 0;\n const slotAgentMatch = /^agent-(.+)-slot-\\d+$/.exec(agent.id);\n const issueId = (agent.issueId || slotAgentMatch?.[1] || agent.id.replace('agent-', '')).toUpperCase();\n\n // PAN-653: Skip workspaces marked stuck — Deacon must not poke/respawn them.\n // Keyed by issueId (not agentId) so respawned agents with new IDs still match.\n const resolutionReviewStatus = getReviewStatusSync(issueId);\n if (resolutionReviewStatus?.stuck) {\n console.log(`[deacon] Skipping stuck workspace ${issueId} in patrolWorkAgentResolutions`);\n continue;\n }\n if (resolutionReviewStatus?.deaconIgnored) {\n console.log(`[deacon] Skipping deacon-ignored workspace ${issueId} in patrolWorkAgentResolutions`);\n continue;\n }\n\n if (resolution === 'done' && count >= 1) {\n if (slotAgentMatch) {\n console.log(`[deacon] Slot ${agent.id} (${issueId}) reported done: notifying its foreman`);\n const { messageAgent } = await import('../agents/messaging.js');\n await messageAgent(`agent-${issueId.toLowerCase()}`, `[swarm-event] ${agent.id} reported done; run pan swarm status ${issueId} --json`, 'deacon:swarm-event');\n saveAgentRuntimeState(agent.id, {\n resolution: 'completed',\n resolutionCount: count + 1,\n resolutionUpdatedAt: new Date().toISOString(),\n });\n actions.push(`Deacon notified foreman of completed slot ${agent.id} (${issueId})`);\n addLog('action', `Notified foreman of completed slot ${agent.id} (${issueId})`, undefined);\n continue;\n }\n\n // PAN-534: lowered from >= 2 to >= 1. A single done signal is sufficient\n // evidence. The old threshold deadlocked when review failed and reset to\n // pending — the agent never re-signaled done, so count stayed at 1 forever.\n console.log(`[deacon] Auto-completing ${agent.id} (${issueId}): resolution=done, count=${count}`);\n\n try {\n // Find pan binary\n const panBin = join(OVERDECK_HOME, 'bin', 'pan');\n const binExists = existsSync(panBin);\n const bin = binExists ? panBin : 'pan';\n\n await execFileAsync(bin, ['work', 'done', issueId, '-c', 'Auto-completed by Deacon: evidence showed work complete after 2 nudges'], {\n timeout: 30000,\n });\n\n // Mark as completed in runtime.json\n saveAgentRuntimeState(agent.id, {\n resolution: 'completed',\n resolutionCount: count + 1,\n resolutionUpdatedAt: new Date().toISOString(),\n });\n\n actions.push(`Deacon auto-completed ${issueId} (${agent.id}) after ${count} failed nudges`);\n addLog('action', `Auto-completed ${issueId}: evidence-complete, ${count} nudges exhausted`, undefined);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] Failed to auto-complete ${agent.id}:`, msg);\n actions.push(`Deacon auto-complete failed for ${agent.id}: ${msg}`);\n }\n\n } else if (resolution === 'stuck' && count >= 3) {\n // Agent is stuck — send a poke to unstick it.\n // Rate-limit to STUCK_POKE_COOLDOWN_MS and cap at STUCK_POKE_MAX (PAN-650).\n const now = Date.now();\n const pokeState = stuckPokeState.get(agent.id) ?? { lastPoke: 0, pokes: 0 };\n\n if (now - pokeState.lastPoke < STUCK_POKE_COOLDOWN_MS) continue;\n\n if (pokeState.pokes >= STUCK_POKE_MAX) {\n // Exhausted poke budget — abandon the agent so it stops being patrolled\n // and surfaces in the dashboard for human intervention.\n console.log(`[deacon] Abandoning stuck agent ${agent.id} (${issueId}) after ${pokeState.pokes} pokes`);\n saveAgentRuntimeState(agent.id, {\n resolution: 'abandoned',\n resolutionCount: count,\n resolutionUpdatedAt: new Date().toISOString(),\n });\n stuckPokeState.delete(agent.id);\n actions.push(`Deacon abandoned stuck agent ${agent.id} (${issueId}) after ${STUCK_POKE_MAX} pokes`);\n addLog('warn', `Abandoned stuck agent ${issueId} after ${STUCK_POKE_MAX} pokes — needs human attention`, undefined);\n continue;\n }\n\n console.log(`[deacon] Poking stuck agent ${agent.id} (${issueId}): poke ${pokeState.pokes + 1}/${STUCK_POKE_MAX}`);\n\n try {\n const pokeMsg = `Deacon health check (${pokeState.pokes + 1}/${STUCK_POKE_MAX}): you appear stuck. Please check your current task status, review any errors, and continue working. If work is complete, run: pan done ${issueId} -c \"Implementation complete\"`;\n await Effect.runPromise(sendKeys(agent.id, pokeMsg));\n stuckPokeState.set(agent.id, { lastPoke: now, pokes: pokeState.pokes + 1 });\n actions.push(`Deacon poked stuck agent ${agent.id} (${issueId}) [${pokeState.pokes + 1}/${STUCK_POKE_MAX}]`);\n addLog('action', `Poked stuck agent ${issueId} (poke ${pokeState.pokes + 1}/${STUCK_POKE_MAX})`, undefined);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] Failed to poke ${agent.id}:`, msg);\n }\n }\n }\n } catch (error: unknown) {\n console.error('[deacon] Error in patrolWorkAgentResolutions:', error);\n }\n\n return actions;\n}\n\n// PAN-464: Container restart backoff configuration\nconst CONTAINER_RESTART_BACKOFF_MS = 60_000; // Minimum 60s between restart attempts\nconst CONTAINER_RESTART_MAX_COUNT = 5; // Give up after 5 restarts\nconst CONTAINER_RESTART_WINDOW_MS = 30 * 60_000; // Reset burst count after 30 min of quiet\n\n/**\n * PAN-464: Compute exponential backoff delay for a container given its restart history.\n * Returns delay in ms. Delay doubles each attempt: 60s, 120s, 240s, 480s, max 5 min.\n */\nexport function containerRestartBackoffMs(count: number): number {\n const base = CONTAINER_RESTART_BACKOFF_MS;\n const max = 5 * 60_000; // 5 minutes cap\n return Math.min(base * Math.pow(2, count - 1), max);\n}\n\n/**\n * PAN-464: Kill orphaned host processes (e.g., Vite, node) for a workspace path.\n * Orphaned Vite watchers exhaust inotify handles, causing ENOSPC in containers.\n * Runs before restarting the container so the root cause is cleared.\n *\n * CRITICAL: Must not kill the active work agent's process tree. The agent's tmux\n * pane runs bash+claude with cwd=workspace, so lsof +D returns it. We collect the\n * tmux pane PIDs for agent and planning sessions matching this workspace and exclude\n * them + all descendants from the kill list.\n */\nasync function killOrphanedWorkspaceProcesses(workspacePath: string): Promise<void> {\n try {\n // 1. Collect tmux pane PIDs for agent sessions in this workspace\n const protectedPids = new Set<string>([String(process.pid)]);\n try {\n const sessions = await Effect.runPromise(listSessionNames());\n const agentSessions = sessions.filter(s => s.startsWith('agent-') || s.startsWith('planning-'));\n for (const session of agentSessions) {\n try {\n const pid = (await Effect.runPromise(listPaneValues(session, '#{pane_pid}')))[0]?.trim();\n if (pid && /^\\d+$/.test(pid)) {\n // Add the pane PID and all its descendants to protected list\n protectedPids.add(pid);\n try {\n const { stdout: descendants } = await execAsync(\n `pgrep -P ${pid} 2>/dev/null; ps -o pid= --ppid ${pid} 2>/dev/null | xargs -I{} pgrep -P {} 2>/dev/null`,\n { encoding: 'utf-8', timeout: 3000 },\n );\n for (const d of descendants.trim().split(/\\s+/)) {\n if (d && /^\\d+$/.test(d)) protectedPids.add(d);\n }\n // Also walk the full descendant tree\n const { stdout: allDesc } = await execAsync(\n `pstree -p ${pid} 2>/dev/null | grep -oE '\\\\([0-9]+\\\\)' | tr -d '()' || true`,\n { encoding: 'utf-8', timeout: 3000 },\n );\n for (const d of allDesc.trim().split('\\n')) {\n if (d && /^\\d+$/.test(d.trim())) protectedPids.add(d.trim());\n }\n } catch { /* non-fatal */ }\n }\n } catch { /* non-fatal */ }\n }\n } catch { /* non-fatal */ }\n\n // 2. Find processes with cwd inside the workspace. NEVER select by open\n // files (`lsof +D`): Bun's hardlinked node_modules make the dashboard and\n // every PTY supervisor appear to hold files \"in\" any workspace via mmap'd\n // native addons — that kill list downs the platform (src/lib/process-cwd.ts).\n const pids = await pidsWithCwdUnder(workspacePath);\n\n // 3. Filter out protected PIDs (agent tmux panes and descendants)\n // AND Docker container processes — they have files open via volume mounts\n // but are legitimate; killing them causes container exit → restart loop.\n const { readFile } = await import('fs/promises');\n const isDockerContainerProcess = async (pid: string): Promise<boolean> => {\n try {\n const cgroup = await readFile(`/proc/${pid}/cgroup`, 'utf-8');\n return cgroup.includes('/docker-') || cgroup.includes('/docker/');\n } catch {\n return false;\n }\n };\n const safePids: string[] = [];\n for (const pid of pids) {\n if (protectedPids.has(pid)) continue;\n if (await isDockerContainerProcess(pid)) continue;\n safePids.push(pid);\n }\n\n if (safePids.length > 0) {\n await execAsync(`kill ${safePids.join(' ')} 2>/dev/null || true`, { encoding: 'utf-8', timeout: 5000 });\n console.log(`[deacon] Killed ${safePids.length} orphaned process(es) in ${workspacePath} before container restart (protected ${protectedPids.size - 1} agent PIDs)`);\n }\n } catch {\n // Non-fatal — proceed with restart even if cleanup fails\n }\n}\n\n/**\n * PAN-464: Check Docker container health for active workspaces.\n * Crashed containers (e.g., Vite ENOSPC) break the UAT environment.\n * Auto-restarts them with exponential backoff (60s → 120s → 240s → 5 min cap).\n * Gives up after 5 restarts within 30 minutes to avoid restart loops.\n * Kills orphaned host processes before restarting to fix the inotify root cause.\n */\nexport async function checkWorkspaceContainerHealth(sharedState?: DeaconState): Promise<string[]> {\n const actions: string[] = [];\n try {\n // Find all workspace-related containers that are exited (crashed)\n const { stdout } = await execAsync(\n 'docker ps -a --filter \"status=exited\" --filter \"name=feature-\" --format \"{{.Names}}|{{.Status}}\" 2>/dev/null || true',\n { encoding: 'utf-8', timeout: 10000 },\n );\n const crashed = stdout.trim().split('\\n').filter(Boolean);\n if (crashed.length === 0) return actions;\n\n const state = sharedState ?? loadState();\n if (!state.containerRestarts) state.containerRestarts = {};\n let stateDirty = false;\n\n const now = Date.now();\n\n for (const line of crashed) {\n const [name, status] = line.split('|');\n if (!name) continue;\n\n // Init containers are one-shot by design — they run setup, exit, and stay exited.\n // Restarting them is meaningless and floods agents with bogus \"container crashed\" alerts.\n // Match only the final Compose feature-<issue>-<service>-<index> suffix; an earlier feature segment may be a decoy.\n const match = name.match(/feature-((?:[a-z]+-\\d+)|(?:(?:f|us|de|ta|tc)\\d+))-(frontend|server)-\\d+$/i);\n if (!match) continue;\n\n // Skip clean shutdowns (exit code 0). Status format: \"Exited (N) X minutes ago\".\n // A service container exiting 0 is intentional (e.g., post-merge teardown), not a crash.\n const exitMatch = status?.match(/Exited \\((\\d+)\\)/);\n if (exitMatch && exitMatch[1] === '0') continue;\n\n const issueLower = match[1];\n const containerType = match[2];\n\n // Init containers are one-shot setup jobs — a clean exit (code 0) is not a crash.\n // Restarting them causes an infinite loop: they complete, exit 0, get restarted, repeat.\n if (containerType === 'init') continue;\n\n const agentId = `agent-${issueLower}`;\n\n // Only restart if the agent is active (has a tmux session)\n const agentRunning = await Effect.runPromise(sessionExists(agentId));\n if (!agentRunning) {\n // Agent not running — skip restart\n continue;\n }\n\n // PAN-464: Backoff / give-up logic\n const record = state.containerRestarts[name];\n if (record) {\n const windowStart = now - CONTAINER_RESTART_WINDOW_MS;\n const firstRestartMs = new Date(record.firstRestart).getTime();\n\n // Reset burst counter if the last restart was > 30 min ago (container ran stably for a while)\n if (firstRestartMs < windowStart) {\n delete state.containerRestarts[name];\n stateDirty = true;\n } else {\n // Still within the burst window\n if (record.gaveUp) {\n console.log(`[deacon] Container ${name} exceeded max restarts — skipping (gave up)`);\n continue;\n }\n // Check max count BEFORE backoff — if we've hit the limit, give up regardless of timing\n if (record.count >= CONTAINER_RESTART_MAX_COUNT) {\n record.gaveUp = true;\n stateDirty = true;\n const msg = `[deacon] Container ${name} exceeded max restarts (${CONTAINER_RESTART_MAX_COUNT}) — giving up`;\n console.warn(msg);\n actions.push(msg);\n // PAN-464: Alert agent that the container gave up — manual intervention required\n try {\n await Effect.runPromise(sendKeys(\n agentId,\n `⚠️ Deacon alert: container \"${name}\" has crashed ${CONTAINER_RESTART_MAX_COUNT} times and auto-restart gave up. The UAT environment at feature-${issueLower}.overdeck.localhost may be broken. Manual intervention required — check docker logs or re-containerize.`,\n 'deacon:container-gave-up',\n ));\n } catch {\n // Agent may not be interactive (e.g., waiting for input) — non-fatal\n }\n continue;\n }\n const backoffMs = containerRestartBackoffMs(record.count);\n const msSinceLast = now - new Date(record.lastRestart).getTime();\n if (msSinceLast < backoffMs) {\n console.log(`[deacon] Container ${name} in backoff (${Math.round((backoffMs - msSinceLast) / 1000)}s remaining)`);\n continue;\n }\n }\n }\n\n // Kill orphaned host processes (Vite, node) before restarting to fix inotify root cause\n try {\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const issueUpper = issueLower.toUpperCase();\n const resolved = resolveProjectFromIssueSync(issueUpper);\n if (resolved) {\n const workspacePath = `${resolved.projectPath}/workspaces/feature-${issueLower}`;\n await killOrphanedWorkspaceProcesses(workspacePath);\n }\n } catch {\n // Project not resolvable — skip orphan cleanup, still attempt restart\n }\n\n // Restart the container\n try {\n await execAsync(`docker restart ${name}`, { encoding: 'utf-8', timeout: 30000 });\n const existing = state.containerRestarts[name];\n state.containerRestarts[name] = {\n count: (existing?.count ?? 0) + 1,\n firstRestart: existing?.firstRestart ?? new Date().toISOString(),\n lastRestart: new Date().toISOString(),\n };\n stateDirty = true;\n const count = state.containerRestarts[name].count;\n const msg = `[deacon] Auto-restarted crashed container ${name} (attempt ${count}/${CONTAINER_RESTART_MAX_COUNT})`;\n console.log(msg);\n actions.push(msg);\n // PAN-464: Alert agent that its container crashed and was restarted\n try {\n await Effect.runPromise(sendKeys(\n agentId,\n `ℹ️ Deacon: container \"${name}\" crashed and was auto-restarted (attempt ${count}/${CONTAINER_RESTART_MAX_COUNT}). The UAT environment should recover in ~30s. No action needed unless this keeps happening.`,\n 'deacon:container-restarted',\n ));\n } catch {\n // Agent may not be interactive — non-fatal\n }\n } catch (restartErr: any) {\n console.warn(`[deacon] Failed to restart ${name}: ${restartErr.message}`);\n // PAN-464: Alert agent that restart failed\n try {\n await Effect.runPromise(sendKeys(\n agentId,\n `⚠️ Deacon alert: container \"${name}\" crashed and restart failed (${(restartErr as Error).message}). The UAT environment at feature-${issueLower}.overdeck.localhost is likely broken.`,\n 'deacon:container-restart-failed',\n ));\n } catch {\n // Non-fatal\n }\n }\n }\n\n // When called with sharedState, the caller is responsible for persisting.\n // Saving here would race with runPatrol's later saveState() and clobber records.\n if (stateDirty && !sharedState) saveState(state);\n } catch {\n // Docker not available or other error — skip silently\n }\n return actions;\n}\n\n/**\n * PAN-1726: Defense-in-depth reaper for the WORK session of a merged issue.\n *\n * `postMergeLifecycle` pauses + kills `agent-<id>` at merge time, but a server\n * restart mid-lifecycle (the PAN-1723 deploy re-runs the lifecycle from\n * pending-post-merge.json) or a deacon read-modify-write race on state.json can\n * leave the work session alive with `paused: null`. An idle merged work agent\n * sits at its prompt yet still counts against the PAN-1665 work ceiling,\n * throttling test/work dispatch for every live issue — the work-role sibling of\n * the PAN-1716 advancing reaper. We re-pause (so auto-resume can't resurrect it)\n * THEN kill the session; state.json is preserved so reopen still works.\n */\nexport async function checkMergedWorkSessions(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { selectMergedWorkSessions } = await import('./reap-terminal-sessions.js');\n const { setAgentPaused } = await import('../agents.js');\n const statuses = loadReviewStatuses();\n const aliveSessions = await Effect.runPromise(listSessionNames());\n const toKill = selectMergedWorkSessions(statuses, [...aliveSessions]);\n for (const session of toKill) {\n try {\n // The work session name is the agent id (`agent-<id>`). Re-assert the\n // pause gate before killing so the next patrol's auto-resume won't bring\n // it back; killing alone would just churn it stopped→running forever.\n await Effect.runPromise(setAgentPaused(session, 'merged — awaiting close-out (verify on main)', true));\n await Effect.runPromise(killSession(session));\n actions.push(`Reaped merged work session ${session} (issue merged, session idle)`);\n console.log(`[deacon] Reaped merged work session ${session} (PAN-1726)`);\n logDeaconEventSync(`checkMergedWorkSessions: paused + reaped ${session} — issue merged but work session alive (PAN-1726)`);\n } catch (err) {\n console.warn(`[deacon] Failed to reap merged work session ${session}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error reaping merged work sessions:', msg);\n }\n return actions;\n}\n\n/** Pane must be idle at least this long before the awaiting-test reaper kills it. */\nconst AWAITING_TEST_IDLE_REAP_MS = 10 * 60 * 1000;\n\n/**\n * PAN-1730: Reap the WORK session of an issue that's idle awaiting its test\n * verdict (review passed, test pending).\n *\n * Such a work agent has handed off via `pan done` and sits idle at its prompt,\n * yet `countRunningAgents()` keeps counting it against the PAN-1665 work\n * ceiling. When the work pool alone meets the total ceiling (work=7/9 observed)\n * `tryReserveAdvancingSlot()` can never admit the test that would release these\n * agents — a livelock that zeroed pipeline throughput for hours. Killing the\n * idle work session frees the work slot (and RAM).\n *\n * Unlike the PAN-1726 merged reaper this does NOT pause the agent: if the test\n * later FAILS, the deacon's auto-resume `needsFix` gate must be free to bring it\n * back to fix the feedback. While test stays `pending` the auto-resume\n * \"pipeline mid-flight\" gate already prevents churn, so killing-without-pausing\n * neither resurrects a still-pending agent nor strands a failed one. Runs before\n * the dispatchers so the freed slot benefits this same cycle's test dispatch.\n */\nexport async function checkAwaitingTestWorkSessions(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { selectAwaitingTestWorkSessions } = await import('./reap-terminal-sessions.js');\n const statuses = loadReviewStatuses();\n const aliveSessions = await Effect.runPromise(listSessionNames());\n const candidates = selectAwaitingTestWorkSessions(statuses, [...aliveSessions]);\n const now = Date.now();\n for (const session of candidates) {\n // Only reap a genuinely idle pane — a work agent still finalizing right\n // after handoff must be left alone. The work session name IS the agent id.\n const runtime = getAgentRuntimeStateSync(session);\n if (runtime?.state !== 'idle') continue;\n const idleSince = Date.parse(runtime.lastActivity ?? '');\n if (!Number.isFinite(idleSince) || now - idleSince < AWAITING_TEST_IDLE_REAP_MS) continue;\n try {\n // No pause: auto-resume's mid-flight gate keeps it down while test is\n // pending, and its needsFix gate must stay free to resume it on failure.\n await Effect.runPromise(killSession(session));\n actions.push(`Reaped idle awaiting-test work session ${session} (review passed, test pending, idle ≥10m)`);\n console.log(`[deacon] Reaped idle awaiting-test work session ${session} (PAN-1730)`);\n logDeaconEventSync(`checkAwaitingTestWorkSessions: reaped ${session} — review passed, test pending, idle ≥10m (PAN-1730)`);\n } catch (err) {\n console.warn(`[deacon] Failed to reap awaiting-test work session ${session}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error reaping awaiting-test work sessions:', msg);\n }\n return actions;\n}\n\nexport async function runPatrol(): Promise<PatrolResult> {\n const state = loadState();\n state.patrolCycle++;\n refreshPatrolHeartbeat(state);\n\n // PAN-378: Global specialists removed. All work done by per-project ephemeral specialists.\n const results: HealthCheckResult[] = [];\n const actions: string[] = [];\n\n // Global pause: skip the entire cycle when the operator has frozen Deacon.\n // Persisted in app_settings; survives restarts. Per-cycle check so flipping\n // the toggle at runtime takes effect on the next tick without restarting.\n if (isDeaconGloballyPaused()) {\n refreshPatrolHeartbeat(state);\n if (!hasLoggedGlobalPauseSkip) {\n console.log(`[deacon] Patrol cycle ${state.patrolCycle} SKIPPED — globally paused`);\n addLog('info', `Patrol cycle ${state.patrolCycle} skipped — deacon globally paused`, state.patrolCycle);\n hasLoggedGlobalPauseSkip = true;\n }\n const skipped: PatrolResult = {\n cycle: state.patrolCycle,\n timestamp: state.lastPatrol!,\n specialists: [],\n actionsToken: ['skipped: globally_paused'],\n massDeathDetected: false,\n };\n lastPatrolResult = skipped;\n saveLastPatrolResult(state, skipped);\n return skipped;\n }\n\n // PAN-1665: reset the per-patrol advancing-dispatch budget. Every review/test/\n // ship re-dispatch below reserves a slot via tryReserveAdvancingSlot() so the\n // patrol's combined spawns stay under the concurrency ceiling.\n resetPatrolDispatchBudget();\n\n const patrolHeartbeatTicker = startPatrolHeartbeatTicker(state);\n try {\n hasLoggedGlobalPauseSkip = false;\n addLog('info', `Patrol cycle ${state.patrolCycle} — checking per-project specialists`, state.patrolCycle);\n console.log(`[deacon] Patrol cycle ${state.patrolCycle} - checking per-project specialists`);\n\n for (const a of await (await import('./stale-task-claims.js')).patrolStaleTaskClaims()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n const stuckRemediationActions = await checkStuckAgentRemediation();\n actions.push(...stuckRemediationActions);\n for (const a of stuckRemediationActions) addLog('action', a, state.patrolCycle);\n\n // PAN-3485: the stall sweeper observes the parked population and records\n // recommendations. It never un-parks work; operator gates remain untouched.\n try {\n const sweepRecommendations = await (await import('./stall-sweeper.js')).runStallSweeperPatrol();\n actions.push(...sweepRecommendations);\n for (const recommendation of sweepRecommendations) addLog('info', recommendation, state.patrolCycle);\n } catch (err: any) {\n console.warn(`[deacon] stall sweeper patrol failed: ${err?.message ?? err}`);\n addLog('warn', `Stall sweeper patrol failed: ${err?.message ?? err}`, state.patrolCycle);\n }\n\n const { reconcileInFlightJournals } = await import('./advancing-selfheal.js');\n const reconciledJournalActions = await reconcileInFlightJournals();\n actions.push(...reconciledJournalActions);\n for (const a of reconciledJournalActions) addLog('action', a, state.patrolCycle);\n for (const a of (await import('./feedback-stuck-retirement.js')).retireResolvedFeedbackDeliveryStuckFlags()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n // Process any pending post-merge lifecycle that wasn't consumed on startup (PAN-626).\n // In dev mode, the deploy script may fail to restart cleanly, leaving the pending file.\n try {\n const pendingFile = join(OVERDECK_HOME, 'pending-post-merge.json');\n const { processPendingLifecycleForPatrol } = await import('./deacon-pending-lifecycle.js');\n const action = await processPendingLifecycleForPatrol(pendingFile);\n if (action) actions.push(action);\n } catch (err: any) {\n console.warn(`[deacon] Failed to process pending lifecycle: ${err.message}`);\n }\n if (state.patrolCycle % 5 === 0) await (await import('./deploy-patrol.js')).runScheduledDeployPatrol();\n /* PAN-378: Global specialist patrol removed. All specialist work now goes through\n * per-project ephemeral specialists via spawnEphemeralSpecialist(). The global\n * merge-agent, review-agent, and test-agent singletons are no longer used.\n * The patrol below handles per-project ephemeral specialist cleanup. */\n\n // PAN-378: Global specialist patrol removed. All specialist work is handled by\n // per-project ephemeral specialists via spawnEphemeralSpecialist().\n // Per-project ephemeral specialist patrol is below (dead session + stuck detection).\n\n // PAN-1908: primary liveness recovery is now reactive (agent.stopped /\n // agent.heartbeat_dead events handled by the Cloister domain-event scheduler).\n // Keep a thin table-query safety net on the patrol for dropped events.\n const livenessActions = await reconcileAgentLiveness();\n actions.push(...livenessActions);\n for (const a of livenessActions) addLog('action', a, state.patrolCycle);\n\n for (const a of await reconcileOrphanProposedSpecs()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n for (const a of await reconcilePendingPromotions()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n\n const closedIssueAgentActions = await reconcileClosedIssueAgents();\n actions.push(...closedIssueAgentActions);\n for (const a of closedIssueAgentActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1882: strikes bypass close-out, so their merged `strike/<id>` worktrees\n // + branches pile up forever. Reap any whose branch is fully merged into\n // origin/main with no live strike session (never touches feature/* or unmerged work).\n const strikeWorkspaceActions = await reapMergedStrikeWorkspaces();\n actions.push(...strikeWorkspaceActions);\n for (const a of strikeWorkspaceActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1817: stop the server+frontend UI containers of workspaces whose agent\n // has been idle (no agent, no tmux) past the grace window. Light-touch and\n // reversible — never touches the agent (host tmux), worktree, or branch.\n const idleStackActions = await reconcileIdleWorkspaceStacks();\n actions.push(...idleStackActions);\n for (const a of idleStackActions) addLog('action', a, state.patrolCycle);\n\n // PAN-3053: read-only Docker bridge-pool pressure signal; emits on transitions only.\n for (const a of await (await import('./bridge-pool-patrol.js')).patrolDockerBridgePool()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n\n // Re-send the resume continue prompt when a work agent is alive and idle after\n // resume but no user record landed in the JSONL transcript.\n const stalledResumeActions = await nudgeStalledResumeWorkAgents();\n actions.push(...stalledResumeActions);\n for (const a of stalledResumeActions) addLog('action', a, state.patrolCycle);\n\n const kickoffRedeliveryActions = await redeliverUndeliveredKickoffs();\n actions.push(...kickoffRedeliveryActions);\n for (const a of kickoffRedeliveryActions) addLog('action', a, state.patrolCycle);\n\n // Nudge work agents that are alive-but-idle with open beads remaining.\n // Catches the gap autoResume misses: tmux alive, status='running', Stop\n // hook fired (idle), but no advance to the next bead (gpt-5.5 checkpoint\n // pattern, prompt mis-interpretation, etc.).\n const beadNudgeActions = await nudgeIdleWorkAgentsWithOpenBeads();\n actions.push(...beadNudgeActions);\n for (const a of beadNudgeActions) addLog('action', a, state.patrolCycle);\n\n // Detect \"Invalid signature in thinking block\" in active agent output (PAN-612).\n // Context compaction corrupts thinking-block signatures; the session cannot be\n // resumed. Must run before idle-suspend so we recover corrupted agents first.\n const signatureCorruptionActions = await checkThinkingSignatureCorruption();\n actions.push(...signatureCorruptionActions);\n for (const a of signatureCorruptionActions) addLog('action', a, state.patrolCycle);\n\n // Check and auto-suspend idle agents (PAN-80, fixed in PAN-154)\n const suspendActions = await checkAndSuspendIdleAgents();\n actions.push(...suspendActions);\n for (const a of suspendActions) addLog('action', a, state.patrolCycle);\n\n // Clear readyForMerge for issues whose workspace no longer exists.\n // Prevents MERGE button showing for issues that can't actually merge.\n try {\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const allStatuses = loadReviewStatuses();\n for (const [issueId, status] of Object.entries(allStatuses)) {\n if (!status.readyForMerge || status.mergeStatus === 'merged') continue;\n const project = resolveProjectFromIssueSync(issueId);\n if (!project) continue;\n const wsPath = join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(wsPath)) {\n setReviewStatusSync(issueId, { readyForMerge: false, mergeStatus: 'failed', mergeNotes: 'Workspace does not exist' });\n const msg = `Cleared readyForMerge for ${issueId} (workspace deleted)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n }\n } catch (err: any) {\n console.warn(`[deacon] Failed to check workspace existence: ${err.message}`);\n }\n\n // PAN-2579 (warm-by-default lifecycle): the PAN-1716 terminal-advancing reaper\n // and the PAN-2341 idle-terminal reaper are GONE from this patrol. A completed\n // review/test/ship session stays warm so the next cycle resumes it with context\n // intact; it no longer counts against the advancing ceiling (countRunningAgents\n // excludes warm-idle sessions) and the memory governor sheds it first under\n // HARD pressure. Only sessions of MERGED issues (past close-out) are reaped.\n const { checkMergedAdvancingSessions } = await import('./advancing-selfheal.js');\n\n // PAN-1726: Reap the work session of an already-merged issue. An idle merged\n // work agent holds a PAN-1665 work slot; re-pause + kill it so the slot frees\n // for live work. (Merged = past close-out, so the warm policy does not apply.)\n const reapedWorkActions = await checkMergedWorkSessions();\n actions.push(...reapedWorkActions);\n for (const a of reapedWorkActions) addLog('action', a, state.patrolCycle);\n\n const reapedMergedAdvancingActions = await checkMergedAdvancingSessions();\n actions.push(...reapedMergedAdvancingActions);\n for (const a of reapedMergedAdvancingActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1778: proactively refresh Claude credentials on active remote agents\n // when the host credentials file changes, with a 15 min fallback cadence.\n // Runs before the reactive 401 heal/reap path so long remote runs get fresh\n // OAuth tokens before they stall. Zero active remote agents is a local state\n // scan only — no Fly provider construction and no Fly API calls.\n try {\n const { refreshClaudeCredentialsForActiveRemoteAgents } = await import('../remote/remote-completion.js');\n const remoteCredentialActions = await refreshClaudeCredentialsForActiveRemoteAgents();\n actions.push(...remoteCredentialActions);\n for (const a of remoteCredentialActions) addLog(a.includes('failed') ? 'warn' : 'action', a, state.patrolCycle);\n } catch (err: any) {\n addLog('warn', `Remote credential refresh patrol failed: ${err.message}`, state.patrolCycle);\n }\n\n // PAN-1845: keep ephemeral-tier remote VMs from running forever when the host\n // (or deacon) goes away. The patrol writes a freshness file; a VM-side\n // watchdog self-stops the machine when the heartbeat goes stale.\n try {\n const { refreshHostHeartbeatForEphemeralVms } = await import('../remote/remote-agents.js');\n const heartbeatActions = await refreshHostHeartbeatForEphemeralVms();\n actions.push(...heartbeatActions);\n for (const a of heartbeatActions) addLog(a.includes('failed') ? 'warn' : 'action', a, state.patrolCycle);\n } catch (err: any) {\n addLog('warn', `Remote heartbeat refresh patrol failed: ${err.message}`, state.patrolCycle);\n }\n\n // PAN-1676: hand completed remote (fly.io) agents to the review pipeline.\n // Cheap no-op when no remote-state.json is active (local file scan only —\n // no fly API calls); otherwise checks each VM for the REMOTE_DONE sentinel,\n // materializes the local worktree, creates review artifacts + the completed\n // marker, and destroys the machine.\n try {\n const { reapCompletedRemoteAgents } = await import('../remote/remote-completion.js');\n const remoteReap = await reapCompletedRemoteAgents();\n for (const r of remoteReap) {\n if (r.status === 'handed-off' || r.status === 'error') {\n const msg = `Remote reap ${r.issueId}: ${r.status} (${r.details.join('; ')})`;\n actions.push(msg);\n addLog(r.status === 'error' ? 'warn' : 'action', msg, state.patrolCycle);\n }\n }\n } catch (err: any) {\n addLog('warn', `Remote reap patrol failed: ${err.message}`, state.patrolCycle);\n }\n\n // PAN-1730: Reap the work session of an issue idle awaiting its test verdict\n // (review passed, test pending, pane idle ≥10m). When the work pool alone\n // meets the PAN-1665 total ceiling, these idle agents livelock test dispatch —\n // freeing the slot here lets this same cycle's checkPendingTestDispatch admit\n // the test that releases them. Kill-without-pause; see the function comment.\n const reapedAwaitingTestActions = await checkAwaitingTestWorkSessions();\n actions.push(...reapedAwaitingTestActions);\n for (const a of reapedAwaitingTestActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1908: primary review-status orphan recovery is now reactive\n // (review.coordinator.died / work.completed events). The patrol keeps a thin\n // SQLite-only safety net for dropped events.\n const orphanActions = await checkOrphanedReviewStatuses();\n actions.push(...orphanActions);\n for (const a of orphanActions) addLog('action', a, state.patrolCycle);\n\n // Bound per-bead inspect sessions so work agents never wait forever for a verdict.\n const inspectTimeoutActions = await checkInspectAgentTimeouts();\n actions.push(...inspectTimeoutActions);\n for (const a of inspectTimeoutActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1559: reap untracked inspect tmux sessions. Inspect agents now write\n // state.json at spawn, but this safety net kills older leaked sessions and\n // any future dead inspect panes before they burn compute indefinitely.\n const inspectReaperActions = await cleanupOrphanedInspectSessions();\n actions.push(...inspectReaperActions);\n for (const a of inspectReaperActions) addLog('action', a, state.patrolCycle);\n\n // Detect new commits pushed after review passed before any test/merge path can\n // act on stale review approval.\n const postReviewActions = await checkPostReviewCommits();\n actions.push(...postReviewActions);\n for (const a of postReviewActions) addLog('action', a, state.patrolCycle);\n\n const stalledConvoyActions = await recoverStalledReviewConvoys();\n actions.push(...stalledConvoyActions);\n for (const a of stalledConvoyActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1908: primary missing-status creation is now reactive (work.completed\n // event). The patrol keeps a thin agents-table safety net for dropped events.\n const missingStatusActions = await checkMissingReviewStatuses();\n actions.push(...missingStatusActions);\n for (const a of missingStatusActions) addLog('action', a, state.patrolCycle);\n\n // PAN-2207: recover issues where `pan done` failed mid-flight (PR open + review\n // never dispatched). Runs after missing-status so we don't race reactive status\n // creation, but before test/merge recovery so the review pipeline can proceed.\n const orphanCompletionActions = await checkOrphanedCompletions();\n actions.push(...orphanCompletionActions);\n for (const a of orphanCompletionActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1681: Recover test agents that wrote .pan/test/result.json but never\n // POSTed testStatus (nudge once → auto-complete). Runs BEFORE the dispatcher\n // below so a recoverable verdict is honored before any re-dispatch or stuck\n // marker — the dispatcher's stuck path skips issues this already resolved.\n const unsignaledTestActions = await checkCompletedButUnsignaledTests();\n actions.push(...unsignaledTestActions);\n for (const a of unsignaledTestActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1658: after a rebase, reviewStatus may already be passed while testStatus\n // remains pending. Run before the dispatcher so green GitHub Actions CI on the\n // current PR HEAD can clear stale pending state instead of spawning a new test.\n const greenCiTestStatusActions = await reconcileTestStatusFromGreenCi();\n actions.push(...greenCiTestStatusActions);\n for (const a of greenCiTestStatusActions) addLog('action', a, state.patrolCycle);\n\n // Retry test-agent dispatch for issues where review passed but test never started (PAN-699)\n const pendingTestActions = await checkPendingTestDispatch();\n actions.push(...pendingTestActions);\n for (const a of pendingTestActions) addLog('action', a, state.patrolCycle);\n\n // Reset issues stuck in 'reviewing' with no active review session (PAN-733)\n const stuckReviewActions = await checkStuckReviewing();\n actions.push(...stuckReviewActions);\n for (const a of stuckReviewActions) addLog('action', a, state.patrolCycle);\n\n // Detect review specialists that wrote synthesis.md but never signaled completion\n const unsignaledReviewActions = await checkCompletedButUnsignaledReviews();\n actions.push(...unsignaledReviewActions);\n for (const a of unsignaledReviewActions) addLog('action', a, state.patrolCycle);\n\n // Repair verdicts that reached disk after their review status was reset to pending.\n const unappliedReviewActions = await reconcileUnappliedReviewVerdicts();\n actions.push(...unappliedReviewActions);\n for (const a of unappliedReviewActions) addLog('action', a, state.patrolCycle);\n\n // PAN-3092: fold verdict fallbacks whose writing process died before its own\n // drain timer fired, and surface the ones the record lock keeps stuck.\n // Budget derived from the RESOLVED patrol interval, not a hardcoded 60s\n // assumption: a shorter configured interval would otherwise let the sweep\n // overrun it and start overlapping patrols.\n const verdictFallbackActions = await sweepStrandedVerdictFallbacks(\n Date.now(),\n Math.max(1_000, Math.floor(config.patrolIntervalMs / 2)),\n );\n actions.push(...verdictFallbackActions);\n for (const a of verdictFallbackActions) addLog('action', a, state.patrolCycle);\n\n // PAN-796: Bypass review for issues where verification passed but review infra keeps failing\n const verifContradictionActions = await checkVerificationReviewContradiction();\n actions.push(...verifContradictionActions);\n for (const a of verifContradictionActions) addLog('action', a, state.patrolCycle);\n\n // Kill orphaned planning sessions whose issue has already progressed past planning.\n // PAN-682 pattern: `planning-pan-<id>` tmux session survives hours after `complete-planning`\n // because either (a) `skipKill=true` was set or (b) complete-planning was never invoked\n // (work agent was started via a different path). If the corresponding work agent session\n // `agent-pan-<id>` is alive, planning is definitively over — kill the planning session.\n const planningCleanupActions = await cleanupOrphanedPlanningSessions();\n actions.push(...planningCleanupActions);\n for (const a of planningCleanupActions) addLog('action', a, state.patrolCycle);\n\n // The review-parent patrol is observability-only and checks the synthesis artifact\n // before the runtime row, so a completed parent is never mistaken for a stalled one.\n const stalledParentActions = await checkStalledReviewParents();\n actions.push(...stalledParentActions);\n for (const a of stalledParentActions) addLog('action', a, state.patrolCycle);\n\n const reviewerMonitorActions = await monitorReviewConvoySignals();\n actions.push(...reviewerMonitorActions);\n for (const a of reviewerMonitorActions) addLog('action', a, state.patrolCycle);\n\n // Kill orphaned review sessions whose work agent is no longer running.\n // Review sessions are named review-<issueId>-<timestamp>-<role> and are\n // never killed by teardown because the exact-match pattern doesn't catch them.\n const reviewCleanupActions = await cleanupOrphanedReviewSessions();\n actions.push(...reviewCleanupActions);\n for (const a of reviewCleanupActions) addLog('action', a, state.patrolCycle);\n\n // PAN-464: Check workspace Docker container health and auto-restart crashed containers\n const containerActions = await checkWorkspaceContainerHealth(state);\n actions.push(...containerActions);\n for (const a of containerActions) addLog('action', a, state.patrolCycle);\n\n // Dead-end and first-completion nudges DISABLED — too flaky, risk of\n // draining AI token credits by sending unnecessary prompts to agents.\n // If an agent is stuck, the human operator can nudge it manually via the\n // dashboard's Tell action.\n\n // Safety-net: trigger merge for issues stuck in readyForMerge state (PAN-344)\n const mergeStuckActions = await checkReadyForMergeStuck();\n actions.push(...mergeStuckActions);\n for (const a of mergeStuckActions) addLog('action', a, state.patrolCycle);\n\n // Auto-retry merges that failed due to transient post-rebase verification failures\n const failedMergeRetryActions = await checkFailedMergeRetry();\n actions.push(...failedMergeRetryActions);\n for (const a of failedMergeRetryActions) addLog('action', a, state.patrolCycle);\n for (const a of await patrolStrikeLandings()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n // PAN-2203: deterministic swarm coordination. This pass derives active\n // swarms from workspaces + xBRIEF readiness; later beads fill in merge,\n // dispatch, recovery, and cooldown behavior.\n const swarmActions = await swarmJanitorPass();\n actions.push(...swarmActions);\n for (const a of swarmActions) addLog('action', a, state.patrolCycle);\n\n // Reconcile stale merges and bound merging/verifying states that never completed.\n const staleMergeActions = await reconcileStaleMergeStatus();\n staleMergeActions.push(...await reconcileStuckMergingStates());\n actions.push(...staleMergeActions); for (const a of staleMergeActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1027 reverse: detect mergeStatus=merged issues whose GitHub PR is not merged\n // (closed-without-merge, reopened after revert, or false positive from squash detection).\n // Without this, those issues get stuck because mergeStatus blocks all pipeline gates.\n const falseMergedActions = await reconcileFalseMerged();\n actions.push(...falseMergedActions);\n for (const a of falseMergedActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1028: detect merge_status=merged but review_status non-terminal (coordinator\n // crashed mid-run, dashboard missed the transition). Reconcile to review_status=passed\n // so the dashboard stops showing \"running reviewers with no data.\"\n const mergedReviewingActions = await reconcileMergedButReviewing();\n actions.push(...mergedReviewingActions);\n for (const a of mergedReviewingActions) addLog('action', a, state.patrolCycle);\n\n const autoCloseOutActions = await autoCloseOut();\n actions.push(...autoCloseOutActions);\n for (const a of autoCloseOutActions) addLog('action', a, state.patrolCycle);\n\n // Closed-PR readyForMerge reconciler: stops the \"Awaiting Merge\" view from\n // listing issues whose PR was closed without merging (PAN-1111-style stale\n // state). Best-effort against the forge; 10-min per-issue cooldown.\n const closedPrReadyActions = await reconcileClosedPrReadyForMerge();\n actions.push(...closedPrReadyActions);\n for (const a of closedPrReadyActions) addLog('action', a, state.patrolCycle); for (const a of await reconcileAutoMergeRows()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n\n // PAN-2143: re-evaluate stale merge-blockers. resolveConflictGate clears a\n // stale merge_conflict/not_mergeable blocker once the branch is mergeable\n // again, but only runs on-demand from review dispatch — so a blocked issue\n // that falls out of the review flow stays stuck after review forever. This\n // patrol re-runs it for every in-pipeline issue still carrying a merge-blocker\n // so resolved conflicts clear and readyForMerge recomputes.\n const staleMergeBlockerActions = await reconcileStaleMergeBlockers();\n actions.push(...staleMergeBlockerActions);\n for (const a of staleMergeBlockerActions) addLog('action', a, state.patrolCycle);\n\n // PAN-2198: re-derive readyForMerge for the no-blocker \"stuck after review\" strand\n // (review+test+verify passed, no blocker, but readyForMerge stuck false) so it\n // converges on the deacon tick instead of only on the server-restart repair sweep.\n const stuckReadyActions = await reconcileStuckReadyForMerge();\n actions.push(...stuckReadyActions);\n for (const a of stuckReadyActions) addLog('action', a, state.patrolCycle);\n\n // Dead-end agent recovery: nudge agents stuck with reviewStatus=blocked/failed after\n // fixing review issues but not re-requesting review. Has 10-min per-issue cooldown and\n // 7-requeue circuit breaker to avoid runaway API credit consumption.\n const deadEndActions = await checkDeadEndAgents();\n actions.push(...deadEndActions);\n for (const a of deadEndActions) addLog('action', a, state.patrolCycle);\n\n // First-completion gap detection: nudge work agents that finished implementation\n // but never called pan done. Only fires for agents idle >10min with commits and\n // no completion marker or review status entry. Has 15-min cooldown per agent.\n const firstCompletionActions = await checkFirstCompletionAgents();\n actions.push(...firstCompletionActions);\n for (const a of firstCompletionActions) addLog('action', a, state.patrolCycle);\n\n // PAN-2428: workspace stacks can come up outside Overdeck (e.g. a project's own\n // dev script), leaving traefik disconnected from their network — every route to\n // that stack 504s until someone runs `docker network connect` by hand. Reconcile\n // idempotently on the patrol tick.\n const traefikActions = await reconcileTraefikNetworks();\n actions.push(...traefikActions);\n for (const a of traefikActions) addLog('action', a, state.patrolCycle);\n\n // Resolution patrol DISABLED — auto-completing and poking agents consumes\n // API credits and is unreliable. Human operator can take action via dashboard.\n\n // Lazy agent correction DISABLED — sends messages to agents which costs\n // API credits. Human operator can check lazy behavior via dashboard.\n\n // Stuck work agent recovery still runs — it only intervenes after 10 minutes\n // of no tool use, escalating to Escape/Ctrl-C/respawn (not paid messages).\n const stuckActions = await checkStuckWorkAgents();\n actions.push(...stuckActions);\n for (const a of stuckActions) addLog('action', a, state.patrolCycle);\n\n const apiErrorActions = await checkApiErrorAgents();\n actions.push(...apiErrorActions);\n for (const a of apiErrorActions) addLog('action', a, state.patrolCycle);\n if (state.patrolCycle % 10 === 0) for (const action of await reconcilePipelineLabelsPatrol()) { actions.push(action); addLog('action', action, state.patrolCycle); }\n // PAN-1625: reap orphaned dashboard-server processes. Low cadence (~10 min).\n // Never touches the live server, port owner, or workspace-container server.\n const serverReaperEveryCycles = Math.max(1, Math.round((10 * 60 * 1000) / config.patrolIntervalMs));\n if (state.patrolCycle % serverReaperEveryCycles === 0) {\n const reaperActions = await reapOrphanedDashboardServers();\n actions.push(...reaperActions);\n for (const a of reaperActions) addLog('action', a, state.patrolCycle);\n }\n\n // PAN-1706: reap leftover playwright-mcp trees / stale headless browsers.\n // Each ghost browser keeps a full dashboard page polling at full rate,\n // multiplying server load. Same ~10 min cadence as the server reaper.\n if (state.patrolCycle % serverReaperEveryCycles === 0) {\n const playwrightActions = await reapLeftoverPlaywrightBrowsers();\n actions.push(...playwrightActions);\n for (const a of playwrightActions) addLog('action', a, state.patrolCycle);\n }\n const projectConfigs = listProjectsSync();\n if (state.patrolCycle % statePlaneReconcileEveryCycles(config.patrolIntervalMs) === 0) for (const result of await reconcileProjectStatePlanes(projectConfigs)) { actions.push(result.message); addLog(result.level, result.message, state.patrolCycle); }\n // PAN-3727: sweep terminal-issue records for parked-population residue (open\n // recovery trips, operator-gate flags) left over from before close-out\n // started acknowledging it. Same cadence as the state-plane patrol above.\n if (state.patrolCycle % statePlaneReconcileEveryCycles(config.patrolIntervalMs) === 0) for (const result of await reconcileTerminalIssueResidue(projectConfigs)) { actions.push(result.message); addLog(result.level, result.message, state.patrolCycle); }\n for (const warning of await recreatedStateWarnings(projectConfigs)) addLog(warning.level, warning.message, state.patrolCycle);\n const divergenceWarnings = await recordMainDivergenceHealth(state, projectConfigs);\n for (const warning of divergenceWarnings) addLog('warn', warning, state.patrolCycle);\n if (shouldRunRecoveryJanitor('agent-state', state.patrolCycle)) {\n const cleanupActions = await cleanupStaleAgentState();\n actions.push(...cleanupActions);\n for (const a of cleanupActions) addLog('action', a, state.patrolCycle);\n\n const transcriptDays = loadCloisterConfigSync().retention?.transcript_days;\n if (typeof transcriptDays === 'number' && Number.isFinite(transcriptDays) && transcriptDays > 0) {\n const transcriptActions = await sweepTranscriptRetention({ transcriptDays });\n actions.push(...transcriptActions);\n for (const a of transcriptActions) addLog('action', a, state.patrolCycle);\n }\n }\n // Retention needs the canonical registry row to prove terminal state before\n // agent GC removes that evidence. Both run on the same 60-cycle cadence.\n if (state.patrolCycle % 60 === 0) for (const id of (await pruneTerminalStoppedAgents()).removed) actions.push(`[agents-gc] pruned ${id}`);\n\n // Periodic abandoned-feedback sweep — safety net for workspaces where the\n // event-driven cleanup (new review cycle / merge / close-out) never fired.\n // See docs/REVIEW-AGENT-ARCHITECTURE.md.\n if (shouldRunRecoveryJanitor('feedback', state.patrolCycle)) {\n const feedbackActions = await cleanupAbandonedFeedback();\n actions.push(...feedbackActions);\n for (const a of feedbackActions) addLog('action', a, state.patrolCycle);\n }\n\n // PAN-1908: primary orphan reviewer-session cleanup is reactive\n // (agent.stopped for the owning work agent). This is a thin safety-net\n // sweep for dropped events (PAN-846).\n if (shouldRunRecoveryJanitor('orphan-reviewer', state.patrolCycle)) {\n const orphanActions = await cleanupOrphanReviewerSessions();\n actions.push(...orphanActions);\n for (const a of orphanActions) addLog('action', a, state.patrolCycle);\n }\n\n // Check for mass death (uses shared state)\n const massDeathCheck = checkMassDeath(state);\n if (massDeathCheck.isMassDeath && massDeathCheck.message) {\n console.error(`[deacon] ${massDeathCheck.message}`);\n actions.push(massDeathCheck.message);\n addLog('error', massDeathCheck.message, state.patrolCycle);\n }\n\n // Patrol per-project ephemeral specialists (PAN-300)\n // Ephemeral specialists are spawned on-demand and are not auto-restarted by the deacon.\n // Patrol detects stuck sessions, dead sessions, and auto-completes successful merges (PAN-375).\n try {\n const projectSpecialists = await getAllProjectSpecialistStatuses();\n for (const projSpec of projectSpecialists) {\n if (!projSpec.isRunning) {\n // Session is dead — reset any stale active runtime state so the next\n // merge request is not blocked by a phantom busy signal.\n const runtimeState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n if (runtimeState?.state === 'active') {\n saveAgentRuntimeState(projSpec.tmuxSession, { state: 'idle', lastActivity: new Date().toISOString() });\n const msg = `Dead-session reset: per-project ${projSpec.specialistType} (${projSpec.projectKey}) was active but session is gone`;\n actions.push(msg);\n addLog('action', msg, state.patrolCycle);\n console.log(`[deacon] ${msg}`);\n\n // PAN-375: If merge specialist died while merging, check if merge actually succeeded\n if (projSpec.specialistType === 'merge-agent' && runtimeState.currentIssue) {\n const issueId = runtimeState.currentIssue;\n try {\n const currentStatus = getReviewStatusSync(issueId);\n if (currentStatus?.mergeStatus === 'merging') {\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n const branch = `feature/${issueId.toLowerCase()}`;\n const { stdout } = await execAsync(\n `git -C \"${resolved.projectPath}\" log --oneline origin/main --grep=\"Merge branch '${branch}'\" 2>/dev/null | head -1`,\n { encoding: 'utf-8', timeout: 15_000 }\n );\n if (stdout.trim()) {\n console.log(`[deacon] PAN-375: merge specialist died but ${issueId} IS merged (${stdout.trim()}). Auto-completing.`);\n setReviewStatusSync(issueId, { mergeStatus: 'merged', readyForMerge: false });\n const { postMergeLifecycle } = await import('./merge-agent.js');\n postMergeLifecycle(issueId, resolved.projectPath).catch(err =>\n console.warn(`[deacon] postMergeLifecycle failed for ${issueId}: ${err}`)\n );\n actions.push(`Auto-completed stale merge for ${issueId}`);\n } else {\n console.log(`[deacon] Merge specialist died and ${issueId} NOT merged. Resetting to readyForMerge.`);\n setReviewStatusSync(issueId, { mergeStatus: 'pending' });\n }\n }\n }\n } catch (err) {\n console.warn(`[deacon] PAN-375 check failed for ${issueId}: ${err}`);\n }\n }\n }\n continue;\n }\n\n const runtimeState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n // A running ephemeral specialist with no runtime state, or active for more than\n // the max specialist timeout (ephemeral specialist spawn uses 15 min), is considered stuck.\n const isStuck = runtimeState?.state === 'active' && runtimeState.lastActivity\n ? (Date.now() - new Date(runtimeState.lastActivity).getTime()) > 15 * 60 * 1000\n : false;\n\n if (isStuck) {\n addLog('warn', `Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing`, state.patrolCycle);\n console.log(`[deacon] Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing ${projSpec.tmuxSession}`);\n try {\n await Effect.runPromise(killSession(projSpec.tmuxSession));\n // Preserve Claude JSONL/session artifacts; only reset Overdeck runtime state.\n saveAgentRuntimeState(projSpec.tmuxSession, { state: 'idle', lastActivity: new Date().toISOString() });\n actions.push(`Force-killed stuck per-project ${projSpec.specialistType} (${projSpec.projectKey})`);\n } catch {\n // Non-fatal — session may have already exited\n }\n }\n\n // PAN-919: Idle-but-alive specialist — task completed but session lingers\n // (e.g. Claude Code sitting at \"Press Ctrl-D again to exit\" after one-shot task).\n // Ephemeral specialists have no reason to stay alive once idle.\n const IDLE_LINGER_MS = 5 * 60 * 1000; // 5 minutes\n if (\n !isStuck &&\n (!runtimeState || runtimeState.state === 'idle') &&\n runtimeState?.lastActivity &&\n Date.now() - new Date(runtimeState.lastActivity).getTime() > IDLE_LINGER_MS\n ) {\n const ageMin = Math.round((Date.now() - new Date(runtimeState.lastActivity).getTime()) / 60000);\n const msg = `Killed lingering idle specialist ${projSpec.specialistType} (${projSpec.projectKey}) — idle ${ageMin}min`;\n console.log(`[deacon] ${msg}`);\n try {\n await Effect.runPromise(killSession(projSpec.tmuxSession));\n actions.push(msg);\n addLog('action', msg, state.patrolCycle);\n } catch {\n // Non-fatal\n }\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error during per-project specialist patrol:', msg);\n }\n\n refreshPatrolHeartbeat(state);\n\n const result: PatrolResult = {\n cycle: state.patrolCycle,\n timestamp: state.lastPatrol!,\n specialists: results,\n actionsToken: actions,\n massDeathDetected: massDeathCheck.isMassDeath,\n mainDivergence: state.mainDivergence,\n };\n\n lastPatrolResult = result;\n saveLastPatrolResult(state, result);\n return result;\n } finally {\n clearInterval(patrolHeartbeatTicker);\n }\n}\n\n// Store the most recent patrol result for API access\nlet lastPatrolResult: PatrolResult | null = null;\nlet hasLoggedGlobalPauseSkip = false;\n\nfunction runScheduledPatrol(source: 'startup' | 'interval'): void {\n const started = deaconPatrolGuard.run(\n 'deacon-patrol',\n async () => {\n await runPatrol();\n },\n (err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[deacon] Patrol error:', err);\n logDeaconEventSync(`patrol error: ${msg}`);\n },\n );\n\n if (!started) {\n const msg = `patrol ${source} skipped — previous patrol still in flight`;\n console.warn(`[deacon] ${msg}`);\n logDeaconEventSync(`startDeacon: ${msg}`);\n }\n}\n\n// ============================================================================\n// Deacon Log Buffer\n// ============================================================================\n\nexport interface DeaconLogEntry {\n timestamp: string;\n level: 'info' | 'warn' | 'action' | 'error';\n message: string;\n cycle?: number;\n}\n\nconst MAX_LOG_ENTRIES = 200;\nconst deaconLogs: DeaconLogEntry[] = [];\n\nfunction addLog(level: DeaconLogEntry['level'], message: string, cycle?: number): void {\n const entry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n cycle,\n };\n deaconLogs.push(entry);\n try {\n mkdirSync(dirname(DEACON_LOG_FILE), { recursive: true });\n appendFileSync(DEACON_LOG_FILE, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // Non-fatal: the in-memory ring still serves the current process.\n }\n // Trim to max size\n if (deaconLogs.length > MAX_LOG_ENTRIES) {\n deaconLogs.splice(0, deaconLogs.length - MAX_LOG_ENTRIES);\n }\n}\n\n/**\n * Get recent deacon log entries.\n * Returns the most recent `limit` entries (default 100).\n */\nexport function getDeaconLogs(limit = 100): DeaconLogEntry[] {\n try {\n if (existsSync(DEACON_LOG_FILE)) {\n return readFileSync(DEACON_LOG_FILE, 'utf-8')\n .trim()\n .split('\\n')\n .filter(Boolean)\n .slice(-limit)\n .map((line) => JSON.parse(line) as DeaconLogEntry);\n }\n } catch {\n // Fall back to the in-memory ring if the durable log is malformed.\n }\n return deaconLogs.slice(-limit);\n}\n\n/**\n * Get the result of the most recent patrol cycle.\n * Used by the dashboard API to show recent Deacon actions.\n */\nexport function getLastPatrolResult(): PatrolResult | null {\n if (lastPatrolResult) return lastPatrolResult;\n const state = loadState();\n return state.lastPatrolResult ?? null;\n}\n\n/**\n * Detect agents whose tmux output contains \"Invalid signature in thinking block\" —\n * a symptom of context compaction corrupting thinking-block cryptographic signatures\n * (PAN-612). The corrupted session cannot be resumed; the agent must start fresh.\n *\n * Recovery: kill the tmux session, mark agent stopped, delete session.id so\n * autoResumeStoppedWorkAgents won't try --resume with the corrupted session.\n * The JSONL file is NEVER deleted (sacred).\n */\nasync function checkThinkingSignatureCorruption(): Promise<string[]> {\n const actions: string[] = [];\n if (!existsSync(AGENTS_DIR)) return actions;\n\n let agentDirs: string[];\n try {\n agentDirs = readdirSync(AGENTS_DIR).filter(d => d.startsWith('agent-'));\n } catch {\n return actions;\n }\n\n for (const agentId of agentDirs) {\n // Only check agents that claim to be running\n const state = getAgentStateSync(agentId);\n if (!state || state.status !== 'running') continue;\n\n // Check if the tmux session is alive\n if (!sessionExistsSync(agentId)) continue;\n\n // Capture recent output and scan for signature corruption\n let output: string;\n try {\n output = await Effect.runPromise(capturePane(agentId, 100));\n } catch {\n continue;\n }\n\n if (!output.includes('Invalid signature in thinking block')) continue;\n\n // Corruption detected — recover the agent\n const issueId = state.issueId ?? agentId;\n console.error(`[deacon] SIGNATURE CORRUPTION detected in ${agentId} (${issueId}) — recovering`);\n logDeaconEventSync(`checkThinkingSignatureCorruption: corruption detected in ${agentId} (${issueId})`);\n logAgentLifecycleSync(agentId, 'signature corruption detected — recovering: killed session, cleared session.id');\n\n // Kill the tmux session\n try {\n await Effect.runPromise(killSession(agentId));\n } catch { /* non-fatal */ }\n\n // Delete session.id so resumeAgent won't --resume the corrupted session\n const sessionFile = join(AGENTS_DIR, agentId, 'session.id');\n if (existsSync(sessionFile)) {\n try { rmSync(sessionFile); } catch { /* non-fatal; PAN-3357: not a dir removal */ }\n }\n\n // Mark agent as stopped\n try {\n saveAgentStateSync({ ...state, status: 'stopped', stoppedAt: new Date().toISOString() });\n } catch { /* non-fatal */ }\n\n // Notify server layer so the read model and frontend update\n if (agentStoppedNotifier) {\n try { agentStoppedNotifier(agentId); } catch { /* non-fatal */ }\n }\n\n const msg = `Recovered ${agentId} (${issueId}) from thinking-block signature corruption — session killed, will start fresh on next resume`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`checkThinkingSignatureCorruption: ${msg}`);\n }\n\n return actions;\n}\n\n/**\n * Start the deacon patrol loop\n */\n\n\nexport function startDeacon(): void {\n if (deaconInterval) {\n console.log('[deacon] Already running');\n return;\n }\n\n config = loadConfig();\n console.log(`[deacon] Starting health monitor (patrol every ${config.patrolIntervalMs / 1000}s)`);\n logDeaconEventSync(`startDeacon: health monitor starting (patrol every ${config.patrolIntervalMs / 1000}s)`);\n\n // Recover agents whose tmux sessions were killed by a system crash before the\n // first patrol. PAN-1908: use the thin table-query reconcile instead of directory scans.\n void (async () => {\n await reconcileAgentLiveness();\n const { runBootAdvancingSelfHeal } = await import('./advancing-selfheal.js');\n await runBootAdvancingSelfHeal();\n runScheduledPatrol('startup');\n })().catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[deacon] Startup recovery/patrol error:', err);\n logDeaconEventSync(`startDeacon: startup recovery/patrol error: ${msg}`);\n });\n\n // Schedule regular patrols\n deaconInterval = setInterval(() => {\n runScheduledPatrol('interval');\n }, config.patrolIntervalMs);\n\n // Resource-pressure patrol on dedicated 15s timer.\n memoryPatrolInterval = setInterval(() => {\n void (async () => {\n const { patrolResourcePressure } = await import('./resource-pressure-patrol.js');\n for (const action of await patrolResourcePressure()) {\n logDeaconEventSync(`resource-pressure-patrol: ${action}`);\n }\n })().catch((err) => {\n logDeaconEventSync(`resource-pressure-patrol: error: ${err instanceof Error ? err.message : String(err)}`);\n });\n }, MEMORY_PATROL_INTERVAL_MS);\n memoryPatrolInterval.unref?.();\n\n logDeaconEventSync('startDeacon: health monitor started');\n}\n\n/**\n * Stop the deacon patrol loop\n */\nexport function stopDeacon(): void {\n if (deaconInterval) {\n clearInterval(deaconInterval);\n deaconInterval = null;\n console.log('[deacon] Stopped health monitor');\n }\n if (memoryPatrolInterval) {\n clearInterval(memoryPatrolInterval);\n memoryPatrolInterval = null;\n }\n}\n\n/**\n * Check if deacon is running\n */\nexport function isDeaconRunning(): boolean {\n return deaconInterval !== null;\n}\n\n/**\n * Get current deacon status\n */\nexport function getDeaconStatus(): {\n isRunning: boolean;\n config: DeaconConfig;\n state: DeaconState;\n} {\n return {\n isRunning: isDeaconRunning(),\n config: loadConfig(),\n state: loadState(),\n };\n}\n\nexport function assessDeaconPatrolFreshness(\n params: {\n isRunning: boolean;\n lastPatrol?: string;\n patrolIntervalMs: number;\n nowMs?: number;\n }\n): DeaconPatrolFreshness {\n const staleAfterSeconds = Math.ceil((params.patrolIntervalMs * 3) / 1000);\n if (!params.isRunning) {\n return {\n status: 'stopped',\n lastPatrol: params.lastPatrol ?? null,\n secondsSinceLastPatrol: null,\n staleAfterSeconds,\n };\n }\n\n if (!params.lastPatrol) {\n return {\n status: 'starting',\n lastPatrol: null,\n secondsSinceLastPatrol: null,\n staleAfterSeconds,\n };\n }\n\n const nowMs = params.nowMs ?? Date.now();\n const lastPatrolMs = Date.parse(params.lastPatrol);\n if (!Number.isFinite(lastPatrolMs)) {\n return {\n status: 'stale',\n lastPatrol: params.lastPatrol,\n secondsSinceLastPatrol: null,\n staleAfterSeconds,\n };\n }\n\n const secondsSinceLastPatrol = Math.max(0, Math.floor((nowMs - lastPatrolMs) / 1000));\n return {\n status: secondsSinceLastPatrol > staleAfterSeconds ? 'stale' : 'running',\n lastPatrol: params.lastPatrol,\n secondsSinceLastPatrol,\n staleAfterSeconds,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"deacon-B8oMMIgN.js","names":["defaultDeps","RSS_SETTLE_MS","join","execFileAsync","execFileAsync","defaultDeps","promisify","exec","join","listAllAgents","join","basename","join","dirname","execAsync","join","listDurableSlotAssignments","countCommitsAhead","execAsync","existsSync","execAsync","defaultDeps","loadYamlConfig","existsSync","execFileAsync","defaultDeps","defaultDeps","execAsync","defaultDeps","listTmuxSessionNames","existsSync","listDurableSlotAssignments","existsSync","existsSync","readFileSync","join","handleAgentHeartbeatDeadEvent","cleanupOrphanedPlanningSessions","handleAgentStoppedEvent","reconcileAgentLiveness","listAllAgents","applyBootReconciliationDecision","handleAgentStoppedEvent","normalizeIssueId","join","defaultDeps","execFileAsync","REAP_GRACE_MS","loadConfig","execFileAsync","execAsync","promisify","exec","join","promisify","exec","execFile","join","handleAgentHeartbeatDeadEventWithDeps","cleanupOrphanedPlanningSessionsWithDeps","handleAgentStoppedEventWithDeps","applyBootReconciliationDecisionWithDeps","reconcileAgentLivenessWithDeps","checkReadyForMergeStuckWithDeps","readdir","base","dirname"],"sources":["../../src/lib/cloister/agent-grace.ts","../../src/lib/cloister/deacon-inspect.ts","../../src/lib/cloister/modal-detector.ts","../../src/lib/cloister/orchestrated-compaction.ts","../../src/lib/cloister/deacon-api-recovery.ts","../../src/lib/cloister/preemption.ts","../../src/lib/cloister/deacon-post-review-commits.ts","../../src/lib/agents/supervisor-liveness.ts","../../src/lib/cloister/deacon-strike-landing.ts","../../src/lib/cloister/orphan-restore-verdict.ts","../../src/lib/cloister/deacon-review-status.ts","../../src/lib/cloister/deacon-review-unsignaled.ts","../../src/lib/cloister/deacon-review-signals.ts","../../src/lib/agents/slot-merge.ts","../../src/lib/pan-dir/task-door.ts","../../src/lib/swarm-policy.ts","../../src/lib/cloister/deacon-workspaces.ts","../../src/lib/cloister/deacon-swarm-orphan-gc.ts","../../src/lib/cloister/deacon-swarm-record.ts","../../src/lib/cloister/deacon-swarm-completion.ts","../../src/lib/cloister/deacon-swarm-gc.ts","../../src/lib/cloister/swarm-tiered-hooks.ts","../../src/lib/cloister/redrive-gate.ts","../../src/lib/cloister/swarm-failed-slot.ts","../../src/lib/cloister/swarm-blocked-slot.ts","../../src/lib/cloister/swarm-foreman.ts","../../src/lib/cloister/swarm-foreman-liveness.ts","../../src/lib/cloister/deacon-swarm.ts","../../src/lib/cloister/boot-reconciliation-predicates.ts","../../src/lib/cloister/boot-reconciliation.ts","../../src/lib/agents/pinned-launch.ts","../../src/lib/agents/placeholder-reconciliation.ts","../../src/lib/cloister/confirmed-session-query.ts","../../src/lib/cloister/idle-nudge-inspection.ts","../../src/lib/cloister/deacon-auto-resume.ts","../../src/lib/cloister/boot-reconciliation-outcomes.ts","../../src/lib/cloister/boot-reconciliation-apply.ts","../../src/lib/agent-directory-cleanup.ts","../../src/lib/cloister/transcript-retention.ts","../../src/lib/cloister/state-plane-patrol.ts","../../src/lib/cloister/state-recreation-patrol.ts","../../src/lib/cloister/deacon-main-divergence.ts","../../src/lib/cloister/patrol-cadence.ts","../../src/lib/cloister/pending-promotion-reconciler.ts","../../src/lib/cloister/test-status-green-ci-reconciler.ts","../../src/lib/cloister/orphan-dashboard-server-reaper.ts","../../src/lib/cloister/playwright-mcp-reaper.ts","../../src/lib/cloister/strike-workspace-reaper.ts","../../src/lib/cloister/inspect-session-reaper.ts","../../src/lib/cloister/deacon-verdict-fallback-sweep.ts","../../src/lib/cloister/deacon.ts"],"sourcesContent":["export const WORK_LAUNCHER_GRACE_MS = 120_000;\n\nexport function isStartingWithinGrace(\n state: { status?: string; startedAt?: string },\n now = Date.now(),\n graceMs = WORK_LAUNCHER_GRACE_MS,\n): boolean {\n if (state.status !== 'starting') return false;\n const startedMs = Date.parse(state.startedAt ?? '');\n return Number.isFinite(startedMs) && now - startedMs < graceMs;\n}\n","import { Effect } from 'effect';\n\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { loadReviewStatuses, setReviewStatusSync } from '../review-status.js';\nimport { killSession, sessionExists } from '../tmux.js';\n\n/**\n * Inspect prompts state a 10-minute budget. Deacon gives the agent a small\n * grace window beyond that, then fails loud so the parent work agent never waits\n * forever for a verdict that will not arrive.\n */\nexport const INSPECT_TIMEOUT_MS = 12 * 60_000;\n\nfunction inspectSessionName(issueId: string, itemId: string): string {\n const issueLower = issueId.toLowerCase();\n const beadSlug = itemId.replace(/[^a-z0-9-]/gi, '-').toLowerCase().slice(0, 24);\n return `inspect-${issueLower}-${beadSlug}`;\n}\n\nfunction formatInspectElapsed(elapsedMs: number): string {\n return `${Math.max(0, Math.round(elapsedMs / 60_000))}m`;\n}\n\nexport async function checkInspectAgentTimeouts(now = new Date()): Promise<string[]> {\n const actions: string[] = [];\n const statuses = loadReviewStatuses();\n const nowMs = now.getTime();\n\n for (const [rawIssueId, status] of Object.entries(statuses)) {\n if (status.inspectStatus !== 'inspecting') continue;\n\n const issueId = rawIssueId.toUpperCase();\n const itemId = status.inspectBeadId;\n const startedMs = status.inspectStartedAt ? Date.parse(status.inspectStartedAt) : NaN;\n const hasStartedAt = Number.isFinite(startedMs);\n const elapsedMs = hasStartedAt ? nowMs - startedMs : Number.POSITIVE_INFINITY;\n const timedOut = elapsedMs > INSPECT_TIMEOUT_MS;\n // New inspections persist their actual owner. Derive the dedicated-session\n // name only for rows created before that metadata existed.\n const sessionName = status.inspectOwnerSession\n ?? (itemId ? inspectSessionName(issueId, itemId) : undefined);\n const sessionAlive = sessionName\n ? await Effect.runPromise(sessionExists(sessionName)).catch(() => false)\n : false;\n const crashed = !!sessionName && !sessionAlive;\n\n if (!timedOut && !crashed) continue;\n\n const reason = !hasStartedAt\n ? 'missing inspectStartedAt metadata'\n : timedOut\n ? `timed out after ${formatInspectElapsed(elapsedMs)} (limit ${formatInspectElapsed(INSPECT_TIMEOUT_MS)})`\n : `tmux session ${sessionName} exited before producing a verdict`;\n const effectiveBeadId = itemId ?? 'unknown';\n const notes = `Inspection error for bead ${effectiveBeadId}: ${reason}. No verdict was produced.`;\n const verdict = `INSPECTION ERROR for bead ${effectiveBeadId}: inspection could not complete (${reason}) — no verdict was produced. Treat as infrastructure failure: do not silently proceed.`;\n\n // Mark terminal first so the next patrol cycle skips this inspection even if\n // kill or delivery fails; this is the idempotency guard.\n setReviewStatusSync(issueId, {\n inspectStatus: 'error',\n inspectNotes: notes,\n });\n\n if (sessionName && sessionAlive) {\n await Effect.runPromise(killSession(sessionName)).catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[deacon] Failed to kill inspect session ${sessionName}: ${msg}`);\n });\n }\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(`agent-${issueId.toLowerCase()}`, verdict, 'deacon:inspect-watchdog');\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[deacon] Failed to deliver inspect error verdict for ${issueId}: ${msg}`);\n }\n\n const action = `Inspection watchdog tripped for ${issueId} bead ${effectiveBeadId}: ${reason}`;\n actions.push(action);\n logDeaconEventSync(`checkInspectAgentTimeouts: ${action}`);\n }\n\n return actions;\n}\n","import { Effect } from 'effect';\nimport { capturePane, sendKeysAsync } from '../tmux.js';\n\nexport interface KnownModal {\n id: string;\n matches: (pane: string) => boolean;\n keys: readonly string[];\n}\n\nexport const KNOWN_AGENT_MODALS: readonly KnownModal[] = [{\n id: 'codex-rate-limit-model-nudge',\n matches: pane => /rate limit/i.test(pane) && /keep current model/i.test(pane),\n keys: ['Down', 'Enter'],\n}];\n\n// Claude Code boot-blocking TUI screens: first-run onboarding (theme picker,\n// login-method picker, OAuth paste prompt) and the folder-trust dialog. A\n// session parked on one never starts its JSONL transcript, so the conversation\n// chat view has nothing to render and its composer cannot answer the screen —\n// only the terminal can. These must never be auto-answered: the login picker\n// starts an OAuth flow, and completing a spurious onboarding can rewrite the\n// real ~/.claude.json (conv-20260714-4261: a transient config read race at\n// spawn made a fully-configured machine boot into fresh-install onboarding).\nconst CLAUDE_BOOT_SCREEN_PATTERNS: readonly RegExp[] = [\n /Choose the text style that looks best with your terminal/i,\n /Select login method:/i,\n /Do you trust the files in this (?:folder|directory)\\?/i,\n /Paste code here if prompted/i,\n];\n\nexport function paneShowsClaudeBootBlockingScreen(pane: string): boolean {\n return CLAUDE_BOOT_SCREEN_PATTERNS.some(pattern => pattern.test(pane));\n}\n\n/** Rollout JSONL is launch/turn attribution only until a captured switch proves otherwise. */\n// A \"switched/switching to <token>\" phrase only counts as a model switch when\n// the phrase names a model or the token is plausibly a model id. Without this\n// guard, git's \"Switched to branch 'feature/x'\" (printed by every checkout)\n// captured \"branch\" and halted the agent as model_divergence — this killed\n// PAN-1491's review convoy twice.\nconst MODEL_SHAPED_TOKEN = /^(?:gpt|o\\d|claude|kimi|glm|gemini|deepseek|qwen|minimax|grok)[\\w.-]*$/i;\n\nexport function paneShowsModelSwitch(pane: string, launchModel: string): boolean {\n const modelPhrase = pane.match(/model\\s+(?:switched|switching)\\s+to\\s+([\\w.-]+)|(?:switched|switching)\\s+model\\s+to\\s+([\\w.-]+)/i);\n let target = modelPhrase?.[1] ?? modelPhrase?.[2];\n if (!target) {\n const generic = pane.match(/(?:switched|switching)\\s+to\\s+([\\w.-]+)/i)?.[1];\n if (generic && MODEL_SHAPED_TOKEN.test(generic)) target = generic;\n }\n return Boolean(target && target.toLowerCase() !== launchModel.toLowerCase());\n}\n\nexport interface ModalHandlerDeps {\n capturePane: (agentId: string) => Promise<string>;\n sendKey: (agentId: string, key: string) => Promise<void>;\n settle: () => Promise<void>;\n}\n\nconst defaultDeps: ModalHandlerDeps = {\n capturePane: agentId => Effect.runPromise(capturePane(agentId, 100)),\n sendKey: (agentId, key) => sendKeysAsync(agentId, key, 'known-modal-handler'),\n settle: () => new Promise(resolve => setTimeout(resolve, 300)),\n};\n\nexport async function handleKnownAgentModal(agentId: string, deps: ModalHandlerDeps = defaultDeps, initialPane?: string): Promise<'none' | 'handled' | 'needs-you'> {\n const pane = initialPane ?? await deps.capturePane(agentId);\n const modal = KNOWN_AGENT_MODALS.find(candidate => candidate.matches(pane));\n if (!modal) return 'none';\n for (const key of modal.keys) await deps.sendKey(agentId, key);\n await deps.settle();\n return modal.matches(await deps.capturePane(agentId)) ? 'needs-you' : 'handled';\n}\n","import { isContextOverflowTail } from '../context-overflow.js';\n\nexport const COMPACTION_CONTINUE_MIN_SETTLE_MS = 30_000;\nexport const COMPACTION_CONTINUE_MAX_WAIT_MS = 150_000;\n\nexport const ORCHESTRATED_COMPACTION_CONTINUE_MESSAGE =\n 'Compaction complete. Continue from the compacted summary now. ' +\n 'Re-read your current xBRIEF item and latest feedback file if needed, then resume work; do not wait for further input.';\n\ninterface PendingCompactionContinuation {\n requestedAt: number;\n observedBusy: boolean;\n}\n\nexport const orchestratedCompactionContinuations = new Map<string, PendingCompactionContinuation>();\n\nexport function scheduleOrchestratedCompactionContinuation(\n sessionName: string,\n requestedAt = Date.now(),\n): void {\n orchestratedCompactionContinuations.set(sessionName, {\n requestedAt,\n observedBusy: false,\n });\n}\n\nexport function cancelOrchestratedCompactionContinuation(sessionName: string): void {\n orchestratedCompactionContinuations.delete(sessionName);\n}\n\nexport function hasOrchestratedCompactionContinuation(sessionName: string): boolean {\n return orchestratedCompactionContinuations.has(sessionName);\n}\n\nexport async function deliverOrchestratedCompact(\n sessionName: string,\n deliver: () => Promise<void>,\n): Promise<void> {\n scheduleOrchestratedCompactionContinuation(sessionName);\n try {\n await deliver();\n } catch (error) {\n cancelOrchestratedCompactionContinuation(sessionName);\n throw error;\n }\n}\n\nexport async function maybeContinueOrchestratedCompaction(args: {\n sessionName: string;\n tmuxOutput: string;\n now?: number;\n send: (sessionName: string, message: string) => Promise<void>;\n}): Promise<boolean> {\n const pending = orchestratedCompactionContinuations.get(args.sessionName);\n if (!pending) return false;\n\n const now = args.now ?? Date.now();\n const elapsed = now - pending.requestedAt;\n const hasPrompt = args.tmuxOutput.includes('❯');\n if (!hasPrompt) {\n pending.observedBusy = true;\n if (elapsed >= COMPACTION_CONTINUE_MAX_WAIT_MS) {\n orchestratedCompactionContinuations.delete(args.sessionName);\n }\n return false;\n }\n if (isContextOverflowTail(args.tmuxOutput)) {\n if (elapsed >= COMPACTION_CONTINUE_MAX_WAIT_MS) {\n orchestratedCompactionContinuations.delete(args.sessionName);\n }\n return false;\n }\n\n if (!pending.observedBusy && elapsed < COMPACTION_CONTINUE_MIN_SETTLE_MS) {\n return false;\n }\n\n await args.send(args.sessionName, ORCHESTRATED_COMPACTION_CONTINUE_MESSAGE);\n orchestratedCompactionContinuations.delete(args.sessionName);\n return true;\n}\n","import { Effect } from 'effect';\nimport { isContextOverflowTail } from '../context-overflow.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getAgentRuntimeStateSync, getAgentStateSync, saveAgentRuntimeState, saveAgentStateSync, setAgentPausedSync } from '../agents.js';\nimport { applyCodexAuthBurnFlag, isCodexAuthRouted, paneShowsCodexAuthBurn } from '../codex-auth.js';\nimport { markWorkspaceStuck } from '../overdeck/review-status-sync.js';\nimport { sessionFilePath } from '../paths.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { capturePane, listSessionNames, sendKeys } from '../tmux.js';\nimport { handleKnownAgentModal, paneShowsModelSwitch } from './modal-detector.js';\nimport {\n deliverOrchestratedCompact,\n hasOrchestratedCompactionContinuation,\n maybeContinueOrchestratedCompaction,\n} from './orchestrated-compaction.js';\n\n// ============================================================================\n// API Error Recovery\n// ============================================================================\n\n/**\n * API error patterns that indicate transient server failures.\n * When an agent stops with one of these in its tmux output, it should\n * be nudged to retry rather than left idle.\n */\nconst API_ERROR_PATTERNS = [\n 'API Error: The server had an error while processing your request',\n 'API Error: Overloaded',\n 'API Error: Rate limit',\n 'API Error: Request was aborted',\n 'API Error: Timed out',\n '529 Overloaded',\n '502 Bad Gateway',\n '503 Service Unavailable',\n];\n\n/**\n * Cooldown between API-error recovery nudges per agent.\n * Prevents spamming agents that are hitting persistent errors.\n */\nconst API_ERROR_RECOVERY_COOLDOWN_MS = 5 * 60_000; // 5 minutes\n\n/**\n * Track API-error recovery attempts per agent.\n */\nconst apiErrorRecoveryState: Map<string, { lastAttempt: number }> = new Map();\n\n/**\n * Context-window overflow is NOT a transient error. It surfaces as e.g.\n * \"API Error: 400 Your input exceeds the context window of this model.\" and\n * nudging \"continue\" only re-sends the same oversized context for the same\n * 400. Recovery shrinks the context: agent-* sessions are summarized\n * out-of-band and respawned fresh with the summary as their opening prompt\n * (PAN-1781); specialist/planning sessions get the harness /compact plus a\n * continue nudge once it settles.\n *\n * This matters most for non-Anthropic models routed through CLIProxy\n * (e.g. gpt-5.5): Claude Code's native auto-compact is keyed to a context\n * window it can't determine for the proxied model, so it never fires and the\n * backend's own limit produces a hard 400 instead.\n */\n/** Continuation nudge sent after a successful harness /compact (specialist/planning sessions). */\nconst CONTEXT_OVERFLOW_CONTINUE_MSG =\n 'Your context was compacted to recover from a context-window overflow. ' +\n 'Continue from where you left off using the compacted summary and your ' +\n 'xBRIEF items / continue.json — do NOT start over.';\n\n/**\n * Let a recovery attempt finish before judging the result. MUST exceed the\n * patrol interval (60s): at exactly one interval the guard expires on the very\n * next patrol regardless of timer drift, which is how a still-settling\n * respawn used to get re-judged (and escalated) one tick after its compaction.\n * Compact respawns also spend ~30-60s generating the summary before the fresh\n * session even launches.\n */\nconst CONTEXT_COMPACT_SETTLE_MS = 150_000;\n\nexport const CONTEXT_PROACTIVE_COMPACT_HIGH_WATER_PERCENT = 85;\n\n/** Bounded compact-respawn attempts per overflow incident before marking stuck. */\nconst MAX_CONTEXT_COMPACT_ATTEMPTS = 2;\n\ntype ContextOverflowRecovery = {\n lastAttempt: number;\n compactAttempts: number;\n /**\n * How the last attempt recovered: 'respawn' = PAN-1781 fresh-seeded respawn\n * (agent-* sessions; the seed is the opening prompt, no follow-up nudge\n * needed); 'harness-compact' = /compact keystroke (specialist/planning\n * sessions; needs a continue nudge once the compaction settles).\n */\n mechanism: 'respawn' | 'harness-compact';\n};\n\n/**\n * Per-session context-overflow recovery state. Present only while a recovery\n * tier is in flight; deleted once the overflow clears (so a later overflow is\n * a fresh incident) or on escalation. Kept separate from apiErrorRecoveryState\n * so the transient-error path is untouched.\n */\nexport const contextOverflowRecoveryState: Map<string, ContextOverflowRecovery> = new Map();\n\n/**\n * PAN-3334: Overdeck no longer proactively force-compacts agents at a context\n * high-water mark. Routine compaction is owned by the harness — every launcher\n * exports CLAUDE_CODE_AUTO_COMPACT_WINDOW per model (PAN-2441), so the session\n * compacts itself at the right threshold, and the PAN-3057 transcript-based\n * continuation net re-drives the agent afterwards. The forced path only raced\n * itself (no shared cooldown across injection sites), orphaned its in-memory\n * continuation on dashboard restart, and compacted sessions that had no\n * business being compacted (handed-off agents the continuation net then\n * correctly refused to re-drive). What remains below is crash recovery for\n * sessions already wedged at overflow.\n */\n\n/**\n * PAN-1675 (A2): bounded native-compaction recovery for agents already flagged\n * `stuck` with reason `context_overflow`. These got stuck under the OLD\n * /compact+/clear ladder (which predates Overdeck-side compaction), so they\n * never received a native-compaction attempt — and the `overflowBlocked` gate\n * would otherwise skip their recovery forever. We give them a small, bounded\n * number of out-of-band compaction attempts; if the agent keeps overflowing\n * after that, it stays stuck for a human.\n */\nexport const stuckOverflowNativeRecoveryState: Map<string, { attempts: number; lastAttempt: number }> = new Map();\nconst MAX_STUCK_NATIVE_RECOVERY = 2;\nconst STUCK_NATIVE_RECOVERY_COOLDOWN_MS = 10 * 60 * 1000;\n\n/**\n * Check for agents (work agents, specialists, planning) that stopped due\n * to transient API errors.\n *\n * Unlike stuck-thinking agents (which are actively processing), API-error\n * agents have stopped with the prompt showing. The tmux output contains\n * an error message from the provider. Recovery: send a \"continue\" nudge.\n */\nexport async function checkApiErrorAgents(): Promise<string[]> {\n const actions: string[] = [];\n const now = Date.now();\n\n // Check all tmux sessions — not just listRunningAgents() — because\n // specialist sessions aren't always in the agents registry.\n let sessionNames: readonly string[];\n try {\n sessionNames = await Effect.runPromise(listSessionNames());\n } catch {\n return actions;\n }\n\n const agentSessions = sessionNames.filter(\n name => name.startsWith('agent-') || name.startsWith('specialist-') || name.startsWith('planning-'),\n );\n // PAN-1818: convoy reviewer sub-role sessions (agent-<issue>-review-<subRole>)\n // are owned exclusively by monitorReviewConvoySignals(). checkApiErrorAgents\n // derives a garbage issueId from these names and would apply work-agent\n // compact-respawn, racing the monitor. Skip them here.\n const sessionsToInspect = agentSessions.filter(\n name => !/^agent-.*-review-(?:security|correctness|performance|requirements)$/.test(name)\n || hasOrchestratedCompactionContinuation(name),\n );\n\n for (const sessionName of sessionsToInspect) {\n const compactionPendingAtStart = hasOrchestratedCompactionContinuation(sessionName);\n const recovery = apiErrorRecoveryState.get(sessionName);\n if (!compactionPendingAtStart && recovery && (now - recovery.lastAttempt) < API_ERROR_RECOVERY_COOLDOWN_MS) {\n continue;\n }\n\n let tmuxOutput: string;\n try {\n tmuxOutput = await Effect.runPromise(capturePane(sessionName, 100));\n } catch {\n continue;\n }\n\n if (!tmuxOutput.trim()) continue;\n\n const agentState = getAgentStateSync(sessionName);\n // PAN-2285: a revoked refresh token surfaces ONLY in the live pane (never\n // in the rollout JSONL) and leaves the agent looking \"running\" while it\n // 401-loops. Trip the troubled gate so it stops silently sitting, and\n // persist the burn flag in agent state — this patrol runs in the deacon\n // CHILD process, so the flag must cross to the dashboard server (banner\n // endpoint + spawn gate) through the shared agents table, not module\n // memory. Do NOT auto-kill or auto-respawn — the shared token family is\n // dead until re-auth.\n //\n // PAN-3528: this check used to live inside the `harness === 'codex'` branch\n // below, so an openai-provider model routed through CLIProxy under\n // claude-code — the standing GPT routing on machines that set\n // `openai.harness: claude-code` — was never scanned, even though it burns on\n // the very same credential.\n if (agentState && isCodexAuthRouted(agentState.harness, agentState.model)) {\n if (paneShowsCodexAuthBurn(tmuxOutput)) {\n const newlyFlagged = applyCodexAuthBurnFlag(agentState);\n if (newlyFlagged) {\n saveAgentStateSync(agentState);\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n message: `${sessionName} marked troubled: Codex refresh token revoked (401 token_invalidated). Re-authenticate Codex — the dashboard banner has a Re-authenticate button, or run \\`codex login\\` on the host.`,\n issueId: agentState.issueId,\n });\n actions.push(`Codex-auth-burned halt: ${sessionName} marked troubled — needs re-authentication`);\n }\n continue;\n }\n }\n if (agentState?.harness === 'codex') {\n if (paneShowsModelSwitch(tmuxOutput, agentState.model)) {\n const reason = `needs-you: live Codex pane indicates a model switch from launch model ${agentState.model}`;\n setAgentPausedSync(sessionName, reason, true);\n markWorkspaceStuck(agentState.issueId, 'model_divergence', { reason });\n actions.push(`Model-divergence halt: ${sessionName} — needs-you`);\n continue;\n }\n const modal = await handleKnownAgentModal(sessionName, undefined, tmuxOutput);\n if (modal !== 'none') {\n const issueId = agentState.issueId;\n const reason = modal === 'handled'\n ? 'usage limit: Codex rate-limit reminder dismissed without changing model'\n : 'needs-you: Codex rate-limit dialog remained after Keep current model selection';\n setAgentPausedSync(sessionName, reason, true);\n if (issueId) markWorkspaceStuck(issueId, 'usage_limit', { reason });\n actions.push(`Usage-limit halt: ${sessionName} kept its configured model${modal === 'needs-you' ? ' — needs-you' : ''}`);\n continue;\n }\n }\n\n const compactionPending = compactionPendingAtStart\n || hasOrchestratedCompactionContinuation(sessionName);\n if (compactionPending) {\n try {\n const continuedAfterCompaction = await maybeContinueOrchestratedCompaction({\n sessionName,\n tmuxOutput,\n now,\n send: (target, message) => Effect.runPromise(sendKeys(target, message)),\n });\n if (continuedAfterCompaction) {\n contextOverflowRecoveryState.delete(sessionName);\n actions.push(`Context compaction recovery: resumed ${sessionName} after compaction`);\n continue;\n }\n } catch (err) {\n console.error(`[deacon] Failed to resume ${sessionName} after orchestrated compaction:`, err);\n }\n if (hasOrchestratedCompactionContinuation(sessionName)) {\n continue;\n }\n }\n\n const hasPrompt = tmuxOutput.includes('❯');\n if (!hasPrompt) continue;\n\n // ── Compacted-and-idle recovery (PAN-3057) ──────────────────────────────\n // The tier above only knows about compactions Overdeck itself sent. The\n // harness compacts on its own — notably when resuming a session whose turn\n // was interrupted — and a manual `/compact` ends the turn, so the agent goes\n // idle and no resume path considers it (it never stopped). Read the fact\n // from the transcript instead of from who pressed the button. This runs\n // BEFORE the overflow/high-water tiers so a just-compacted agent gets a\n // nudge rather than a second compaction.\n try {\n const { maybeContinueCompactedAgent } = await import('./compaction-continuation.js');\n const { findLastCompactBoundary } = await import(\n '../../dashboard/server/services/conversation-service.js'\n );\n const { deliverAgentMessage } = await import('../agents/delivery.js');\n const continued = await maybeContinueCompactedAgent({\n agentId: sessionName,\n tmuxOutput,\n now,\n send: (target, message) => deliverAgentMessage(target, message, 'deacon:compaction-continuation'),\n findBoundary: findLastCompactBoundary,\n });\n if (continued) {\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} stopped after a context compaction — continuing it`,\n issueId: getAgentStateSync(sessionName)?.issueId,\n });\n actions.push(continued);\n continue;\n }\n } catch (err) {\n console.error(`[deacon] Compaction-continuation check failed for ${sessionName}:`, err);\n }\n\n // ── Context-window overflow recovery (distinct from transient errors) ──\n // A 400 \"input exceeds the context window\" cannot be retried by continuing.\n // Recover by compacting; once the compaction has settled and the overflow\n // is gone, nudge the agent to resume. A loop guard escalates to stuck if\n // /compact never clears the overflow.\n {\n const issueId = sessionName.startsWith('agent-')\n ? sessionName.replace('agent-', '').toUpperCase()\n : null;\n const overflowBlocked = (() => {\n if (!issueId) return false;\n const st = getReviewStatusSync(issueId);\n return Boolean(st?.stuck || st?.deaconIgnored);\n })();\n const ov = contextOverflowRecoveryState.get(sessionName);\n // Judge overflow from only the recent tail: the error sits adjacent to the\n // idle prompt when an agent stops, and after a /compact redraw the old\n // error scrolls past this window — so a settled /compact that cleared the\n // overflow won't be misread as \"still overflowing\" from stale scrollback.\n const hasOverflow = isContextOverflowTail(tmuxOutput);\n const runtimeState = getAgentRuntimeStateSync(sessionName);\n if (hasOverflow) {\n if (!runtimeState?.contextSaturatedAt) {\n await saveAgentRuntimeState(sessionName, { contextSaturatedAt: new Date(now).toISOString() });\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} marked wedged: context-window overflow detected`,\n issueId: issueId ?? undefined,\n });\n }\n } else if (runtimeState?.contextSaturatedAt) {\n await saveAgentRuntimeState(sessionName, { contextSaturatedAt: undefined });\n }\n\n // PAN-1675 (A2): rescue agents already flagged stuck=context_overflow.\n // The old /compact+/clear ladder set `stuck` and the `overflowBlocked`\n // gate below then skips their recovery permanently — but those agents\n // never got a Overdeck-side (out-of-band) compaction, which can recover\n // an overflow the harness /compact could not. Give them a bounded number\n // of native-compaction attempts BEFORE the overflowBlocked gate. A\n // successful resumeAgent({compact:true}) clears the stuck flag (in\n // resumeAgent), so a recovered agent re-enters the normal flow. deacon-\n // ignored issues are still left alone.\n {\n const stuckStatus = issueId ? getReviewStatusSync(issueId) : null;\n const isStuckOverflow = Boolean(\n stuckStatus?.stuck && stuckStatus.stuckReason === 'context_overflow' && !stuckStatus.deaconIgnored,\n );\n if (isStuckOverflow) {\n if (!hasOverflow) {\n // The tail no longer shows the overflow error — but that is a WEAK\n // signal: the 400 line can scroll out of the captured window while\n // the agent is still pinned near 100% context. Only clear the stuck\n // flag on a POSITIVE recovery signal — the agent's actual JSONL\n // context usage is back below the proactive high-water mark.\n // Otherwise leave it stuck: a genuinely-full agent must not be\n // returned to the pipeline on a tail-string miss only to re-overflow\n // on its next turn (the false-recovery flap).\n let recoveredPct: number | null = null;\n try {\n const st = getAgentStateSync(sessionName);\n const sid = st?.sessionId ?? runtimeState?.claudeSessionId;\n if (st?.workspace && sid && st.model) {\n const { computeContextUsage } = await import('../../dashboard/server/services/conversation-service.js');\n const usage = await computeContextUsage(sessionFilePath(st.workspace, sid), st.model);\n if (usage && usage.percentUsed < CONTEXT_PROACTIVE_COMPACT_HIGH_WATER_PERCENT) {\n recoveredPct = usage.percentUsed;\n }\n }\n } catch { /* treat as not-yet-recovered — leave it stuck */ }\n if (recoveredPct !== null) {\n const { clearWorkspaceStuck } = await import('../overdeck/review-status-sync.js');\n clearWorkspaceStuck(issueId!);\n stuckOverflowNativeRecoveryState.delete(sessionName);\n actions.push(`Context overflow recovery: cleared stuck flag for ${sessionName} (context back to ${Math.round(recoveredPct)}%)`);\n }\n continue;\n }\n const rec = stuckOverflowNativeRecoveryState.get(sessionName) ?? { attempts: 0, lastAttempt: 0 };\n if (rec.attempts >= MAX_STUCK_NATIVE_RECOVERY) {\n // Native compaction tried its budget and the agent keeps\n // overflowing — genuinely needs a human; leave it stuck.\n continue;\n }\n if (rec.lastAttempt && (now - rec.lastAttempt) < STUCK_NATIVE_RECOVERY_COOLDOWN_MS) {\n continue;\n }\n rec.attempts += 1;\n rec.lastAttempt = now;\n stuckOverflowNativeRecoveryState.set(sessionName, rec);\n const { resumeAgent } = await import('../agents.js');\n const recovered = await resumeAgent(sessionName, undefined, { compact: true });\n if (recovered.success) {\n stuckOverflowNativeRecoveryState.delete(sessionName);\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} recovered from a stuck context-overflow via Overdeck-side compaction (attempt ${rec.attempts})`,\n issueId: issueId ?? undefined,\n });\n console.log(`[deacon] Agent ${sessionName} recovered from stuck context-overflow via native compaction (attempt ${rec.attempts})`);\n actions.push(`Context overflow recovery: native-compacted previously-stuck ${sessionName} (attempt ${rec.attempts})`);\n continue;\n }\n // Respawn failed — a spawn-level error (seed generation never fails:\n // it degrades to a reseed-only seed inside resumeAgent). PAN-1781\n // removed the /clear keystroke tier; the bounded retry budget +\n // cooldown above covers transient spawn failures, and an exhausted\n // budget correctly leaves the agent stuck for a human.\n console.warn(`[deacon] Compact respawn failed for stuck ${sessionName} (${recovered.error ?? 'unknown'}; attempt ${rec.attempts}/${MAX_STUCK_NATIVE_RECOVERY})`);\n actions.push(`Context overflow recovery: compact respawn failed for stuck ${sessionName} (attempt ${rec.attempts})`);\n continue;\n }\n }\n\n if (!overflowBlocked) {\n if (ov && (now - ov.lastAttempt) < CONTEXT_COMPACT_SETTLE_MS) {\n // A recovery tier is in flight — give it time to finish before judging.\n continue;\n }\n\n if (ov && !hasOverflow) {\n // The previous tier cleared the overflow. A harness /compact leaves\n // the agent idle at the compacted summary, so nudge it to resume. A\n // PAN-1781 fresh-seeded respawn needs no nudge — its seed (summary +\n // reseed instructions) IS the opening prompt.\n if (ov.mechanism === 'harness-compact') {\n try {\n await Effect.runPromise(sendKeys(sessionName, CONTEXT_OVERFLOW_CONTINUE_MSG));\n console.log(`[deacon] Agent ${sessionName} resumed after context-overflow compaction`);\n actions.push(`Context overflow recovery: resumed ${sessionName} after compaction`);\n } catch (err) {\n console.error(`[deacon] Failed to resume ${sessionName} after compaction:`, err);\n }\n } else {\n actions.push(`Context overflow recovery: ${sessionName} recovered after compact respawn`);\n }\n contextOverflowRecoveryState.delete(sessionName);\n continue;\n }\n\n if (hasOverflow) {\n // Loop guard: a bounded number of compact attempts per incident, then\n // escalate to stuck for a human. PAN-1781 removed the /clear keystroke\n // tier — a respawn that still overflows means something is deeply\n // wrong (e.g. summarization producing oversized seeds), and blowing\n // away the context with /clear just hides it.\n if (ov && ov.compactAttempts >= MAX_CONTEXT_COMPACT_ATTEMPTS) {\n if (issueId) {\n markWorkspaceStuck(issueId, 'context_overflow', {\n compactAttempts: ov.compactAttempts,\n });\n }\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n message: `${sessionName} stuck: context-window overflow persisted after ${ov.compactAttempts} compact-recovery attempts`,\n issueId: issueId ?? undefined,\n });\n console.error(`[deacon] Agent ${sessionName} stuck after ${ov.compactAttempts} compact-recovery attempts — escalating`);\n contextOverflowRecoveryState.delete(sessionName);\n continue;\n }\n\n const compactAttempts = (ov?.compactAttempts ?? 0) + 1;\n\n // PAN-1781: agent-* sessions recover via summarize + fresh-seeded\n // respawn (resumeAgent({compact:true})): the wedged session is\n // summarized out-of-band and a FRESH session is spawned with that\n // summary as its opening prompt. Never the harness `/compact` (which\n // deadlocks past the ceiling) and never an in-place JSONL boundary +\n // --resume (which the harness's resume leaf selection bypassed ~half\n // the time, silently rebuilding the full overflowed context — the\n // root cause behind every \"compaction didn't work → /clear\" incident\n // up to PAN-1775). Attempts are counted on failure too, so a\n // persistently failing respawn exhausts the budget and escalates\n // instead of retrying forever.\n if (issueId !== null) {\n const { resumeAgent } = await import('../agents.js');\n const resumeResult = await resumeAgent(sessionName, undefined, { compact: true });\n contextOverflowRecoveryState.set(sessionName, {\n lastAttempt: now,\n compactAttempts,\n mechanism: 'respawn',\n });\n if (resumeResult.success) {\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} hit context-window overflow — respawned fresh with a compact-summary seed (attempt ${compactAttempts})`,\n issueId: issueId ?? undefined,\n });\n console.log(`[deacon] Agent ${sessionName} hit context-window overflow — compact-respawned (attempt ${compactAttempts})`);\n actions.push(`Context overflow recovery: compact-respawned ${sessionName} (attempt ${compactAttempts})`);\n } else {\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} compact respawn failed (${resumeResult.error ?? 'unknown'}) — will retry after settle (attempt ${compactAttempts}/${MAX_CONTEXT_COMPACT_ATTEMPTS})`,\n issueId: issueId ?? undefined,\n });\n console.warn(`[deacon] Compact respawn failed for ${sessionName} (${resumeResult.error ?? 'unknown'}; attempt ${compactAttempts}/${MAX_CONTEXT_COMPACT_ATTEMPTS})`);\n actions.push(`Context overflow recovery: compact respawn failed for ${sessionName} (attempt ${compactAttempts})`);\n }\n continue;\n }\n\n // Non-agent (specialist/planning) sessions are not registered agents,\n // so the respawn path doesn't apply — keep the harness /compact tier.\n try {\n await deliverOrchestratedCompact(\n sessionName,\n () => Effect.runPromise(sendKeys(sessionName, '/compact')),\n );\n contextOverflowRecoveryState.set(sessionName, {\n lastAttempt: now,\n compactAttempts,\n mechanism: 'harness-compact',\n });\n emitActivityEntrySync({\n source: 'cloister',\n level: 'warn',\n message: `${sessionName} hit context-window overflow — compacting to recover (attempt ${compactAttempts})`,\n issueId: issueId ?? undefined,\n });\n console.log(`[deacon] Agent ${sessionName} hit context-window overflow — sent /compact (attempt ${compactAttempts})`);\n actions.push(`Context overflow recovery: compacting ${sessionName} (attempt ${compactAttempts})`);\n } catch (err) {\n console.error(`[deacon] Failed to send /compact to ${sessionName}:`, err);\n }\n continue;\n }\n }\n }\n\n const hasApiError = API_ERROR_PATTERNS.some(pattern => tmuxOutput.includes(pattern));\n if (!hasApiError) continue;\n\n // For work agents, respect stuck/deacon-ignored flags\n if (sessionName.startsWith('agent-')) {\n const agentIssueId = (sessionName.replace('agent-', '')).toUpperCase();\n const agentReviewStatus = getReviewStatusSync(agentIssueId);\n if (agentReviewStatus?.stuck || agentReviewStatus?.deaconIgnored) {\n continue;\n }\n }\n\n console.log(`[deacon] Agent ${sessionName} stopped with API error — nudging retry`);\n\n try {\n const continueMsg = 'You stopped due to a transient API error. This is a temporary server issue, not a problem with your work. Continue from where you left off. Do NOT start over — pick up exactly where you stopped.';\n await Effect.runPromise(sendKeys(sessionName, continueMsg));\n apiErrorRecoveryState.set(sessionName, { lastAttempt: now });\n actions.push(`API error recovery: nudged ${sessionName} to retry`);\n } catch (err) {\n console.error(`[deacon] Failed to nudge ${sessionName} for API error retry:`, err);\n }\n }\n\n return actions;\n}\n","/**\n * PAN-2507: preemptive pipeline scheduler.\n *\n * When a review/test/merge (\"advancing\") dispatch cannot reserve capacity, the\n * deacon normally defers to the next patrol — indefinitely if idle work agents\n * hold the box. This module is the single owner of the YIELD mechanic: it pauses\n * an idle work agent (resumable — session killed, state preserved) to free the\n * slot/memory, so the advancing dispatch can proceed, and resumes yielded agents\n * oldest-first once capacity returns.\n *\n * Priority is fixed (higher preempts lower, never the reverse):\n * merge/ship > test > review > work-rework > work-new.\n * Preemption only flows DOWN this list — an advancing dispatch may yield a work\n * agent; new work never preempts anything; advancing roles never preempt each\n * other.\n *\n * Opt-in: gated on `[concurrency] preemption = true` in cloister.toml. Default\n * off ⇒ every entry point is a no-op and the dispatch sites keep their existing\n * defer-until-attrition behavior (PAN-2507 AC-1).\n *\n * All tmux interaction here goes through async primitives (NFR-1): the yield stop\n * uses the async `stopAgent` Effect, and attach-detection reads the async\n * `listSessions()`.\n */\n\nimport { Effect } from 'effect';\n\nimport {\n clearYieldForResumeSync,\n listAgentStates,\n listRunningAgentsSync,\n resumeAgent,\n setAgentYieldedSync,\n stopAgent,\n type AgentState,\n} from '../agents.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { listSessions } from '../tmux.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { loadCloisterConfigSync } from './config.js';\nimport { isAgentIdleForNudge } from './agent-idle.js';\nimport { assessMemoryPressure } from './memory-governor.js';\nimport { tryReserveAdvancingSlot } from './concurrency.js';\n\n/** RSS settle window after a resume before the next memory re-assessment (mirrors deacon-auto-resume). */\nconst RSS_SETTLE_MS = 2000;\n\nexport type AdvancingRole = 'review' | 'test' | 'ship';\n\nexport interface YieldOutcome {\n yielded: boolean;\n victimId?: string;\n reason?: string;\n}\n\n/**\n * A running work agent considered for yielding. Carries the precomputed\n * eligibility signals so `selectYieldVictim` is pure and unit-testable.\n */\nexport interface YieldCandidate {\n id: string;\n issueId: string;\n /** `isAgentIdleForNudge` — only idle agents may be yielded (never preempt active work). */\n idle: boolean;\n /** An operator is attached to the tmux session — never yield out from under a human. */\n attached: boolean;\n /** Already paused (operator pause or a prior yield) — not a fresh victim. */\n paused: boolean;\n /**\n * The agent's own issue is blocked on the pipeline (reviewStatus pending or\n * reviewing) — it is waiting anyway, so prefer it as a victim (FR-2a).\n */\n reviewBlocked: boolean;\n /** `lastActivity` epoch ms for longest-idle ordering (ascending). Null ⇒ treated as oldest. */\n lastActivityMs: number | null;\n /** `lastYieldResumeAt` epoch ms for the re-yield cooldown. Null ⇒ never yielded. */\n lastYieldResumeMs: number | null;\n}\n\n/**\n * FR-2 predicate + ordering, pure. Returns the best victim or null.\n *\n * Excludes any agent that is not idle, is operator-attached, is already paused,\n * or is inside its post-resume re-yield cooldown. Among the eligible, prefers\n * (a) an agent blocked on its own review, then (b) the longest-idle\n * (`lastActivity` ascending).\n */\nexport function selectYieldVictim(\n candidates: readonly YieldCandidate[],\n nowMs: number,\n cooldownSecs: number,\n): YieldCandidate | null {\n const cooldownMs = cooldownSecs * 1000;\n const eligible = candidates.filter((c) => {\n if (!c.idle) return false;\n if (c.attached) return false;\n if (c.paused) return false;\n if (c.lastYieldResumeMs !== null && nowMs - c.lastYieldResumeMs < cooldownMs) return false;\n return true;\n });\n if (eligible.length === 0) return null;\n\n const ordered = [...eligible].sort((a, b) => {\n // (a) prefer pipeline-blocked agents\n if (a.reviewBlocked !== b.reviewBlocked) return a.reviewBlocked ? -1 : 1;\n // (b) then longest-idle first (oldest lastActivity)\n return (a.lastActivityMs ?? 0) - (b.lastActivityMs ?? 0);\n });\n return ordered[0];\n}\n\nfunction reviewBlockedFor(issueId: string): boolean {\n const status = getReviewStatusSync(issueId)?.reviewStatus;\n // PAN-2507 (FR-2a): the enum has no `in_progress`; the faithful \"waiting on\n // its own review\" states are `pending` (queued) and `reviewing` (running).\n return status === 'pending' || status === 'reviewing';\n}\n\nfunction parseMs(iso: string | undefined): number | null {\n if (!iso) return null;\n const ms = Date.parse(iso);\n return Number.isFinite(ms) ? ms : null;\n}\n\nasync function buildCandidates(): Promise<YieldCandidate[]> {\n const sessions = await Effect.runPromise(listSessions());\n const attached = new Set(sessions.filter((s) => s.attached).map((s) => s.name));\n\n return listRunningAgentsSync()\n .filter((s) => s.role === 'work' && s.status === 'running')\n .map((s) => ({\n id: s.id,\n issueId: s.issueId,\n idle: isAgentIdleForNudge(s.id),\n attached: attached.has(s.id),\n paused: s.paused === true,\n reviewBlocked: reviewBlockedFor(s.issueId),\n lastActivityMs: parseMs(s.lastActivity),\n lastYieldResumeMs: parseMs(s.lastYieldResumeAt),\n }));\n}\n\nfunction countYielded(): number {\n return listAgentStates().filter((s: AgentState) => s.yieldedByScheduler === true).length;\n}\n\n/**\n * FR-1 flow: try to yield an idle work agent to free capacity for an advancing\n * dispatch of `role` for `issueId`. Pauses the victim with scheduler attribution\n * and stops its session (async). The caller re-attempts its reservation after a\n * true outcome; on FR-6c (retry still fails) the caller is responsible for\n * resuming the victim immediately — see the wire-site pattern.\n *\n * No-op (returns `{ yielded: false }`) when preemption is disabled, the\n * `max_yielded` cap is reached, or no eligible victim exists.\n */\nexport async function yieldWorkAgentFor(role: AdvancingRole, issueId: string): Promise<YieldOutcome> {\n const concurrency = loadCloisterConfigSync().concurrency;\n if (concurrency?.preemption !== true) return { yielded: false, reason: 'preemption disabled' };\n\n const maxYielded = concurrency.max_yielded ?? 3;\n const cooldownSecs = concurrency.yield_cooldown_secs ?? 600;\n\n const alreadyYielded = countYielded();\n if (alreadyYielded >= maxYielded) {\n return { yielded: false, reason: `max_yielded reached (${alreadyYielded}/${maxYielded})` };\n }\n\n const nowMs = Date.now();\n const candidates = await buildCandidates();\n const victim = selectYieldVictim(candidates, nowMs, cooldownSecs);\n if (!victim) return { yielded: false, reason: 'no eligible idle work agent to yield' };\n\n const reason = `yield: making room for ${role} of ${issueId}`;\n if (!setAgentYieldedSync(victim.id, reason)) {\n return { yielded: false, reason: `victim ${victim.id} state vanished before yield` };\n }\n await Effect.runPromise(stopAgent(victim.id));\n\n const idleMinutes = victim.lastActivityMs !== null ? Math.round((nowMs - victim.lastActivityMs) / 60000) : null;\n const idleDesc = idleMinutes !== null ? ` (idle ${idleMinutes}m)` : '';\n const message = `Yielded ${victim.id}${idleDesc} to run ${role} for ${issueId}`;\n logDeaconEventSync(`[preemption] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message, issueId });\n\n return { yielded: true, victimId: victim.id, reason: message };\n}\n\n/**\n * Dispatch-site entry point: a blocked advancing dispatch of `role` for\n * `issueId` tries to yield an idle work agent and re-confirm capacity. Returns\n * true iff, after the yield, `tryReserveAdvancingSlot()` succeeds — i.e. a slot\n * is now reserved for the caller to dispatch. On failure it resumes the victim\n * immediately (FR-6c) and returns false, so the caller defers exactly as before.\n *\n * Two capacity paths after a yield:\n * - count-gated: `stopAgent` already awaited `killSession`, so the freed slot\n * is visible to `countRunningAgents()` on the immediate retry;\n * - memory-gated: let the freed RSS settle, refresh the cached memory verdict\n * (`assessMemoryPressure` publishes it), then retry once.\n */\nexport async function tryYieldForAdvancingDispatch(role: AdvancingRole, issueId: string): Promise<boolean> {\n const outcome = await yieldWorkAgentFor(role, issueId);\n if (!outcome.yielded) return false;\n\n // Count-gated: the killed session already dropped the running count.\n if (tryReserveAdvancingSlot()) return true;\n\n // Memory-gated: settle freed RSS, refresh the cached verdict, retry once.\n await new Promise((r) => setTimeout(r, RSS_SETTLE_MS));\n await assessMemoryPressure();\n if (tryReserveAdvancingSlot()) return true;\n\n // FR-6c: the yield freed no usable capacity — put the victim back immediately.\n if (outcome.victimId) await resumeYieldedVictim(outcome.victimId);\n return false;\n}\n\n/**\n * Immediately resume a victim after a failed reservation retry (FR-6c): the\n * yield freed capacity but the advancing dispatch still could not reserve it, so\n * put the work agent back rather than leaving it stranded.\n */\nexport async function resumeYieldedVictim(agentId: string): Promise<void> {\n clearYieldForResumeSync(agentId);\n const result = await resumeAgent(agentId);\n const suffix = result.success ? 'resumed' : `resume failed: ${result.error ?? 'unknown'}`;\n logDeaconEventSync(`[preemption] Yield retry still blocked — ${agentId} ${suffix}`);\n}\n\n/**\n * FR-4: resume yielded work agents oldest-first, up to `maxToResume`, while the\n * memory gate allows. Clears the pause + yield attribution and stamps the\n * re-yield cooldown before each resume. Returns the ids actually resumed.\n *\n * Deliberately NOT gated on `preemption` — a previously-yielded agent must be\n * resumable even if the operator has since turned preemption off, so it is never\n * stranded paused.\n */\nexport async function resumeYieldedAgents(maxToResume: number): Promise<string[]> {\n if (maxToResume <= 0) return [];\n\n const yielded = listAgentStates({ status: 'stopped', role: 'work' })\n .filter((s) => s.yieldedByScheduler === true)\n .sort((a, b) => (parseMs(a.yieldedAt) ?? 0) - (parseMs(b.yieldedAt) ?? 0));\n if (yielded.length === 0) return [];\n\n const resumed: string[] = [];\n for (const agent of yielded) {\n if (resumed.length >= maxToResume) break;\n\n const memVerdict = await assessMemoryPressure();\n if (memVerdict.band !== 'ok') {\n logDeaconEventSync(\n `[preemption] resumeYieldedAgents: memory gate (${memVerdict.band}), availMB=${Math.round(memVerdict.availableBytes / 1048576)}`\n + `${memVerdict.loadPerCore == null ? '' : `, load/core=${memVerdict.loadPerCore.toFixed(2)}`}; deferring remaining yielded agents`,\n );\n break;\n }\n\n // Let the previous resume's RSS register before the next memory check.\n if (resumed.length > 0) {\n await new Promise((r) => setTimeout(r, RSS_SETTLE_MS));\n }\n\n clearYieldForResumeSync(agent.id);\n const result = await resumeAgent(agent.id);\n if (result.success) {\n resumed.push(agent.id);\n const message = `Resumed yielded ${agent.id} for ${agent.issueId} — capacity returned`;\n logDeaconEventSync(`[preemption] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message, issueId: agent.issueId });\n } else {\n // The pause is already cleared, so the normal auto-resume path will retry\n // this agent on a later patrol like any other stopped work agent.\n logDeaconEventSync(`[preemption] resumeYieldedAgents: resume failed for ${agent.id}: ${result.error ?? 'unknown'}`);\n }\n }\n return resumed;\n}\n","import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { getReviewStatusSync, loadReviewStatuses, setReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { isIssueClosed } from './issue-closed.js';\nimport {\n describeRunningAgents,\n releaseAdvancingSlot,\n tryReserveAdvancingSlot,\n} from './concurrency.js';\nimport { ciRetryMap } from './deacon-merge.js';\nimport { tryYieldForAdvancingDispatch } from './preemption.js';\n\nconst BLOCKED_REVIEW_MISSING_ANCHOR_LOG_INTERVAL_MS = 60 * 60 * 1000;\nconst blockedReviewMissingAnchorLogs = new Map<string, number>();\nconst blockedReviewDriftObservations = new Map<\n string,\n { reviewedAnchor: string; currentAnchor: string }\n>();\n// PAN-3254: the current anchor recorded at each issue's most recent reset. A\n// drift verdict against the SAME anchor a second time means the workspace has\n// not moved since we already reset for it — a repeating producer artifact,\n// not a new push (MIN-901: 425 resets, one distinct current anchor).\nconst lastResetAnchors = new Map<string, string>();\nconst resetLoopEscalated = new Set<string>();\n\n/**\n * Detect issues where the agent pushed new commits after a review verdict.\n *\n * Passed reviews are invalidated immediately when the reviewed tree changes.\n * Blocked reviews use the same drift evaluator, but require two consecutive\n * patrols at the same new HEAD before re-dispatching so a work agent that is\n * still pushing per-item commits does not start review mid-rework.\n */\nexport async function checkPostReviewCommits(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const statuses = loadReviewStatuses();\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n\n for (const [issueId, status] of Object.entries(statuses)) {\n const isBlocked = status.reviewStatus === 'blocked';\n if (!isBlocked) blockedReviewDriftObservations.delete(issueId);\n\n if (status.mergeStatus === 'merged') {\n blockedReviewDriftObservations.delete(issueId);\n lastResetAnchors.delete(issueId);\n resetLoopEscalated.delete(issueId);\n continue;\n }\n if (!isBlocked && status.reviewStatus !== 'passed' && !status.readyForMerge) continue;\n\n const reviewedAnchor = status.reviewedAtCommit;\n if (!reviewedAnchor) {\n blockedReviewDriftObservations.delete(issueId);\n if (isBlocked) {\n const now = Date.now();\n const lastLoggedAt = blockedReviewMissingAnchorLogs.get(issueId) ?? 0;\n if (now - lastLoggedAt >= BLOCKED_REVIEW_MISSING_ANCHOR_LOG_INTERVAL_MS) {\n blockedReviewMissingAnchorLogs.set(issueId, now);\n logDeaconEventSync(\n `checkPostReviewCommits: ${issueId} blocked without anchor — cannot detect drift`,\n );\n }\n }\n continue;\n }\n if (await isIssueClosed(issueId)) {\n blockedReviewDriftObservations.delete(issueId);\n console.log(`[deacon] ${issueId}: skipping review re-dispatch — issue is closed`);\n continue;\n }\n\n const project = resolveProjectFromIssueSync(issueId);\n if (!project) continue;\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`,\n );\n if (!existsSync(workspacePath)) continue;\n\n const { formatAnchorShort, rehydrateHeadAnchor } = await import('../git-utils.js');\n const { evaluateWorkspaceAnchorDrift } = await import('../workspace-anchor-drift.js');\n const verdict = await evaluateWorkspaceAnchorDrift(\n issueId,\n workspacePath,\n rehydrateHeadAnchor(reviewedAnchor),\n );\n if (verdict.kind === 'unreadable' || verdict.kind === 'current') {\n blockedReviewDriftObservations.delete(issueId);\n continue;\n }\n\n if (verdict.kind === 'benign') {\n blockedReviewDriftObservations.delete(issueId);\n setReviewStatusSync(issueId, { reviewedAtCommit: verdict.currentAnchor });\n console.log(`[deacon] Benign post-review HEAD move for ${issueId}: ${formatAnchorShort(reviewedAnchor)} → ${formatAnchorShort(verdict.currentAnchor)} — review/test preserved`);\n continue;\n }\n\n const currentHead = verdict.currentAnchor;\n\n // PAN-3254: bound repeat resets. If this exact current anchor already\n // triggered a reset, the branch has not moved since — re-resetting\n // produces an identical \"unchanged\" review cycle every patrol (~100 s)\n // and never converges. Escalate once instead of looping silently.\n if (lastResetAnchors.get(issueId) === currentHead) {\n if (!resetLoopEscalated.has(issueId)) {\n resetLoopEscalated.add(issueId);\n const message = `${issueId} — review reset suppressed: drift against ${formatAnchorShort(currentHead)} already caused a reset and the workspace has not moved since. This indicates an anchor-producer disagreement, not new commits; review will not re-dispatch until the workspace head actually changes.`;\n logDeaconEventSync(`checkPostReviewCommits: ${message}`);\n const { recordDeadEndNeedsYou } = await import('./dead-end-trip.js');\n await recordDeadEndNeedsYou(issueId, 'review-reset-loop', currentHead, message);\n }\n continue;\n }\n\n if (isBlocked) {\n const priorObservation = blockedReviewDriftObservations.get(issueId);\n if (\n !priorObservation\n || priorObservation.reviewedAnchor !== reviewedAnchor\n || priorObservation.currentAnchor !== currentHead\n ) {\n blockedReviewDriftObservations.set(issueId, {\n reviewedAnchor,\n currentAnchor: currentHead,\n });\n continue;\n }\n blockedReviewDriftObservations.delete(issueId);\n }\n\n console.log(\n `[deacon] Post-review commit detected for ${issueId}: ` +\n `was ${formatAnchorShort(reviewedAnchor)}, now ${formatAnchorShort(currentHead)} — resetting review`,\n );\n lastResetAnchors.set(issueId, currentHead);\n resetLoopEscalated.delete(issueId);\n if (isBlocked) {\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n readyForMerge: false,\n reviewedAtCommit: undefined,\n reviewRetryCount: 0,\n recoveryStartedAt: undefined,\n });\n } else {\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n readyForMerge: false,\n reviewedAtCommit: undefined,\n reviewNotes: undefined,\n testNotes: undefined,\n // Reset merge retry counter so checkFailedMergeRetry can retry again after\n // the work agent pushes a fix (e.g. to address a CI check failure).\n mergeRetryCount: 0,\n // PAN-794: new commits open a fresh recovery cycle — stale infra\n // failures from the previous cycle must not poison the breaker budget.\n reviewRetryCount: 0,\n recoveryStartedAt: undefined,\n });\n }\n // Also clear the CI transient retry counter so the next merge attempt\n // starts fresh. Without this, ciRetryMap retains count=6 from the previous\n // CI failure cycle, permanently blocking transient retries for this issue.\n ciRetryMap.delete(issueId);\n if (!isBlocked) {\n actions.push(`Reset review for ${issueId}: new commits after review passed (${formatAnchorShort(reviewedAnchor)} → ${formatAnchorShort(currentHead)})`);\n }\n\n // Redispatch a fresh review convoy. Re-read status to guard against races\n // with other dispatch paths (HTTP request-review, manual CLI) that may have\n // already picked up the work between the reset above and now.\n const freshStatus = getReviewStatusSync(issueId);\n // PAN-2507: also a blocked advancing (review) dispatch — try to yield an\n // idle work agent before deferring. (This 7th site was not in the PRD's\n // six-site enumeration but is the same `!tryReserveAdvancingSlot()` shape.)\n if (freshStatus?.reviewStatus === 'pending' && !tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n // PAN-1665: at the ceiling — status is already reset to pending above, so\n // the orphan-review path will re-dispatch on a later patrol once a slot frees.\n actions.push(`Deferred post-review re-dispatch for ${issueId} — advancing-role concurrency ceiling reached`);\n logDeaconEventSync(`checkPostReviewCommits: deferred review for ${issueId} — advancing ceiling reached (PAN-1665) — ${describeRunningAgents()}`);\n } else if (freshStatus?.reviewStatus === 'pending') {\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n const branch = `feature/${issueId.toLowerCase()}`;\n const dispatchResult = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePath,\n branch,\n force: true,\n }));\n if (dispatchResult.gated) {\n releaseAdvancingSlot();\n actions.push(`Deferred post-review re-dispatch for ${issueId} — ${dispatchResult.message}`);\n console.log(`[deacon] Deferred post-review re-dispatch for ${issueId}: ${dispatchResult.message}`);\n } else if (dispatchResult.success) {\n const action = isBlocked\n ? `Re-dispatched review for ${issueId}: rework commit after BLOCKED verdict (${formatAnchorShort(reviewedAnchor)} → ${formatAnchorShort(currentHead)})`\n : `Re-dispatched review for ${issueId}`;\n actions.push(action);\n console.log(`[deacon] ${action}`);\n } else {\n actions.push(`Failed to re-dispatch review for ${issueId}: ${dispatchResult.error || dispatchResult.message}`);\n console.error(`[deacon] Failed to re-dispatch review for ${issueId}:`, dispatchResult.error || dispatchResult.message);\n }\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error in checkPostReviewCommits:', msg);\n }\n\n return actions;\n}\n","/**\n * PAN-3002: supervisor-delivered agents (pty-supervisor, deliveryMethod:\n * supervisor) keep working after their tmux session dies — the worker holds\n * the pty and detaches. A tmux-only liveness check then declares a healthy\n * agent dead and marks it stopped, splitting state from reality: MIN-882 ran\n * 36h untracked ($166, 35MB transcript) while the pipeline showed \"Planned\n * idle\", and PAN-2997 was killed 41s after a healthy resume.\n *\n * A live pty-supervisor process carrying `--name <agentId>` is positive proof\n * of life regardless of tmux. The deacon's liveness arbiter consults this\n * before declaring an agent orphaned.\n */\nimport { execFile, execFileSync } from 'node:child_process';\nimport { promisify } from 'node:util';\n\ntype Pgrep = (pattern: string) => boolean;\ntype AsyncPgrep = (pattern: string) => Promise<boolean>;\n\nconst execFileAsync = promisify(execFile);\n\nconst defaultPgrep: Pgrep = (pattern) => {\n try {\n execFileSync('pgrep', ['-f', pattern], { stdio: ['ignore', 'ignore', 'ignore'] });\n return true;\n } catch {\n return false;\n }\n};\n\nconst defaultAsyncPgrep: AsyncPgrep = async (pattern) => {\n try {\n await execFileAsync('pgrep', ['-f', pattern], { encoding: 'utf8' });\n return true;\n } catch (error: unknown) {\n if (typeof error === 'object' && error !== null && 'code' in error && error.code === 1) return false;\n throw error;\n }\n};\n\n/**\n * True when a pty-supervisor process for this agent is alive. `--name` is\n * boundary-anchored so `agent-min-882` does not match `agent-min-882-review`.\n * Agent ids are `[a-z0-9-]` by construction, so the pattern needs no escaping.\n */\nexport function supervisorProcessAliveSync(agentId: string, pgrep: Pgrep = defaultPgrep): boolean {\n if (!agentId) return false;\n return pgrep(`pty-supervisor\\\\.js.*--name ${agentId}(\\\\s|$)`);\n}\n\n/** Async equivalent for server-reachable liveness checks. */\nexport async function supervisorProcessAlive(agentId: string, pgrep: AsyncPgrep = defaultAsyncPgrep): Promise<boolean> {\n if (!agentId) return false;\n return pgrep(`pty-supervisor\\\\.js.*--name ${agentId}(\\\\s|$)`);\n}\n","import { join } from 'node:path';\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\n\nimport { loadReviewStatuses, getReviewStatusSync, setReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { listProjectsAsync, resolveProjectFromIssueSync } from '../projects.js';\nimport { getAgentState } from '../agents/agent-state.js';\nimport { hasAgentRuntimeInSubtree } from '../agents/runtime-command.js';\nimport { supervisorProcessAlive } from '../agents/supervisor-liveness.js';\nimport { listPaneValues } from '../tmux.js';\nimport { messageAgent, type MessageDeliveryOutcome } from '../agents/messaging.js';\nimport { writeFeedbackFile } from './feedback-writer.js';\nimport { surfaceIssueFeedbackNeedsYou } from './feedback-target.js';\nimport type { StrikeLandingAttempt } from '../strike-landing.js';\nimport { ensureInternalTokenSync, INTERNAL_TOKEN_HEADER } from '../internal-token.js';\nconst execFileAsync = promisify(execFile);\nexport interface StrikeMergeRequest {\n kind: 'strike'; markerHead: string; workspacePath: string; branchName: string; recoveryTarget: string;\n}\n\nexport interface StrikeMergeResult { success: boolean; mergeStatus?: string; error?: string; transport?: boolean }\ntype StrikeMergeTrigger = (issueId: string, request: StrikeMergeRequest) => Promise<StrikeMergeResult>;\n\nfunction internalDashboardUrl(): string {\n const port = Number.parseInt(process.env.API_PORT ?? process.env.PORT ?? '3011', 10);\n return process.env.OVERDECK_INTERNAL_DASHBOARD_URL ?? `http://127.0.0.1:${port}`;\n}\n\nexport async function requestStrikeMerge(\n issueId: string,\n request: StrikeMergeRequest,\n options: { dashboardUrl?: string; token?: string; fetchImpl?: typeof fetch } = {},\n): Promise<StrikeMergeResult> {\n const dashboardUrl = options.dashboardUrl ?? internalDashboardUrl();\n const token = options.token ?? ensureInternalTokenSync();\n const fetchImpl = options.fetchImpl ?? fetch;\n try {\n const response = await fetchImpl(new URL(`/api/internal/strikes/${issueId}/merge`, dashboardUrl), {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n origin: dashboardUrl,\n [INTERNAL_TOKEN_HEADER]: token,\n },\n body: JSON.stringify(request),\n });\n const body = await response.json() as StrikeMergeResult;\n return typeof body.success === 'boolean'\n ? body\n : { success: false, error: `Strike merge endpoint returned HTTP ${response.status} without a structured result` };\n } catch (error) {\n return {\n success: false,\n transport: true,\n error: `Strike merge request failed: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n\nexport interface StrikeLandingDeps {\n loadStatuses: () => Record<string, ReviewStatus>;\n getStatus: (issueId: string) => ReviewStatus | null;\n setStatus: typeof setReviewStatusSync;\n resolveProject: typeof resolveProjectFromIssueSync;\n mergeIssue: StrikeMergeTrigger;\n getMainHead: (projectPath: string) => Promise<string>;\n deliverRecovery: (agentId: string, message: string, dedupKey: string) => Promise<MessageDeliveryOutcome>;\n writeFeedback: (issueId: string, workspacePath: string, markdownBody: string) => Promise<boolean>;\n needsYou: (issueId: string, reason: string, details: Record<string, unknown>) => Promise<void>;\n now: () => string;\n schedule: (key: string, work: () => Promise<void>) => void;\n isScheduled: (key: string) => boolean;\n isPersistentlyOwned: (issueId: string) => boolean;\n listProjects: typeof listProjectsAsync;\n git: (args: string[], cwd: string) => Promise<string>;\n isStrikeAgentAlive: (agentId: string) => Promise<boolean>;\n}\n\nexport class StrikeLandingSupervisor {\n private readonly pending: Array<{ key: string; work: () => Promise<void> }> = [];\n private readonly owned = new Set<string>();\n private active = 0;\n constructor(private readonly concurrency = 2) {}\n has(key: string): boolean { return this.owned.has(key); }\n enqueue(key: string, work: () => Promise<void>): void {\n if (this.owned.has(key)) return;\n this.owned.add(key); this.pending.push({ key, work }); this.drain();\n }\n private drain(): void {\n while (this.active < this.concurrency && this.pending.length > 0) {\n const { key, work } = this.pending.shift()!;\n this.active += 1;\n void work().catch(error => console.error('[strike-landing] supervised work failed:', error)).finally(() => { this.active -= 1; this.owned.delete(key); this.drain(); });\n }\n }\n}\nconst strikeLandingSupervisor = new StrikeLandingSupervisor();\n\n/**\n * A keep-alive tmux shell survives after its harness exits, while a supervisor\n * can keep a harness alive after tmux disappears. Both probes must report no\n * runtime before the Deacon can safely take over a dead strike's branch.\n */\nasync function defaultIsStrikeAgentAlive(agentId: string): Promise<boolean> {\n try {\n const state = await Effect.runPromise(getAgentState(agentId));\n if (!state) return true;\n\n const [supervisorAlive, panePids] = await Promise.all([\n supervisorProcessAlive(agentId),\n Effect.runPromise(listPaneValues(agentId, '#{pane_pid}')),\n ]);\n if (supervisorAlive) return true;\n\n return (await Promise.all(\n panePids.map((panePid) => hasAgentRuntimeInSubtree(panePid, state.harness ?? 'claude-code')),\n )).some(Boolean);\n } catch {\n // A failed liveness probe is not proof that the agent stopped. Fail closed\n // rather than racing an agent whose process tree could not be inspected.\n return true;\n }\n}\n\nfunction defaultDeps(): StrikeLandingDeps {\n return {\n loadStatuses: loadReviewStatuses,\n getStatus: getReviewStatusSync,\n setStatus: setReviewStatusSync,\n resolveProject: resolveProjectFromIssueSync,\n mergeIssue: requestStrikeMerge,\n getMainHead: async (projectPath) => (await execFileAsync('git', ['rev-parse', 'origin/main'], { cwd: projectPath, encoding: 'utf8' })).stdout.trim(),\n // A recovery must arrive through the live delivery door. A keyed message\n // bypasses the monitor mail tier, which only becomes visible if the idle\n // session takes another turn, and makes a repeated patrol safe to retry.\n deliverRecovery: (agentId, message, dedupKey) => messageAgent(agentId, message, 'deacon-strike-landing', { owesRework: true, dedupKey }),\n writeFeedback: async (issueId, workspacePath, markdownBody) => (await Effect.runPromise(writeFeedbackFile({ issueId, workspacePath, specialist: 'merge-agent', outcome: 'needs-you', summary: 'Strike landing needs operator attention', markdownBody }))).success,\n needsYou: surfaceIssueFeedbackNeedsYou,\n now: () => new Date().toISOString(),\n schedule: (key, work) => strikeLandingSupervisor.enqueue(key, work),\n isScheduled: key => strikeLandingSupervisor.has(key),\n isPersistentlyOwned: () => false,\n listProjects: listProjectsAsync,\n git: async (args, cwd) => (await execFileAsync('git', args, { cwd, encoding: 'utf8' })).stdout.trim(),\n isStrikeAgentAlive: defaultIsStrikeAgentAlive,\n };\n}\n\nconst TRANSPORT_BACKOFF_BASE_MS = 60_000;\nconst TRANSPORT_BACKOFF_CAP_MS = 1_800_000;\nconst MAX_TRANSPORT_RETRIES = 10;\nconst NON_ACTIONABLE = /permission|merge guard|configured project|integration|infrastructure|unavailable|not registered|workspace does not exist|fetch failed|ECONNREFUSED|ECONNRESET|socket hang up|ETIMEDOUT|EAI_AGAIN/i;\nfunction attemptHistory(attempts: StrikeLandingAttempt[]): string {\n return attempts.map((attempt, index) => `${index + 1}. strike ${attempt.strikeHead}; main ${attempt.mainHead}; ${attempt.outcome}: ${attempt.detail}`).join('\\n');\n}\n\nasync function handleFailure(issueId: string, head: string, detail: string, projectPath: string, workspacePath: string, status: ReviewStatus, deps: StrikeLandingDeps): Promise<string> {\n let mainHead = 'unknown';\n try { mainHead = await deps.getMainHead(projectPath); } catch (error) { detail += `; main HEAD unavailable: ${error instanceof Error ? error.message : String(error)}`; }\n const attempts = [...(status.strikeLandingAttempts ?? []), { timestamp: deps.now(), strikeHead: head, mainHead, outcome: 'failed', detail }];\n const recoveryCount = (status.strikeRecoveryCount ?? 0) + 1;\n const recoveryMessage = `Strike landing failed for ${issueId} at ${head}.\\n\\nCurrent main: ${mainHead}\\nFailure: ${detail}\\n\\nRun pan sync-main ${issueId}, resolve every conflict, rerun the configured gates, push only strike/${issueId.toLowerCase()}, then run pan strike-ready ${issueId}. A fresh pushed HEAD is required before another landing attempt.`;\n if (!NON_ACTIONABLE.test(detail) && recoveryCount < 3) {\n try {\n const outcome = await deps.deliverRecovery(\n `strike-${issueId.toLowerCase()}`,\n recoveryMessage,\n `strike-landing:${issueId}:${head}:${recoveryCount}`,\n );\n if (outcome.delivered) {\n deps.setStatus(issueId, { strikeLandingState: 'recovering', strikeRecoveryCount: recoveryCount, strikeLandingAttempts: attempts, mergeNotes: detail });\n return `[strike-landing] ${issueId} at ${head} recovering (${recoveryCount}/3)`;\n }\n detail += `; recovery not delivered: ${outcome.reason ?? 'queued to mail only'}`;\n attempts[attempts.length - 1] = { ...attempts[attempts.length - 1], detail };\n } catch (error) { detail += `; recovery delivery failed: ${error instanceof Error ? error.message : String(error)}`; attempts[attempts.length - 1] = { ...attempts[attempts.length - 1], detail }; }\n }\n const reason = `Strike landing for ${issueId} needs operator attention after ${recoveryCount} cycle(s).\\n${attemptHistory(attempts)}`;\n deps.setStatus(issueId, { strikeLandingState: 'needs_you', strikeRecoveryCount: recoveryCount, strikeLandingAttempts: attempts, mergeNotes: detail });\n await deps.writeFeedback(issueId, workspacePath, `## Strike landing needs operator attention\\n\\n${reason}`);\n await deps.needsYou(issueId, reason, { attempts });\n return `[strike-landing] ${issueId} at ${head} needs-you`;\n}\n\nasync function handleTransportFailure(issueId: string, head: string, detail: string, status: ReviewStatus, deps: StrikeLandingDeps): Promise<string> {\n const timestamp = deps.now();\n const project = deps.resolveProject(issueId);\n const projectPath = project?.projectPath ?? '';\n const workspacePath = project ? join(projectPath, 'workspaces', `feature-${issueId.toLowerCase()}-strike`) : '';\n let mainHead = 'unknown';\n try { if (projectPath) mainHead = await deps.getMainHead(projectPath); }\n catch (error) { detail += `; main HEAD unavailable: ${error instanceof Error ? error.message : String(error)}`; }\n const attempts: StrikeLandingAttempt[] = [\n ...(status.strikeLandingAttempts ?? []),\n { timestamp, strikeHead: head, mainHead, outcome: 'transport-failed', detail },\n ];\n const retryCount = (status.strikeTransportRetryCount ?? 0) + 1;\n\n if (retryCount < MAX_TRANSPORT_RETRIES) {\n const delayMs = Math.min(TRANSPORT_BACKOFF_CAP_MS, TRANSPORT_BACKOFF_BASE_MS * (2 ** (retryCount - 1)));\n const nextAttemptAt = new Date(Date.parse(timestamp) + delayMs).toISOString();\n deps.setStatus(issueId, {\n strikeLandingState: 'ready',\n strikeReadyHead: head,\n strikeTransportRetryCount: retryCount,\n strikeNextAttemptAt: nextAttemptAt,\n strikeLandingAttempts: attempts,\n mergeNotes: detail,\n });\n return `[strike-landing] ${issueId} at ${head} transport retry ${retryCount}/${MAX_TRANSPORT_RETRIES} after ${nextAttemptAt}`;\n }\n\n const reason = `Strike landing for ${issueId} needs operator attention after ${retryCount} transport attempt(s).\\n${attemptHistory(attempts)}`;\n deps.setStatus(issueId, {\n strikeLandingState: 'needs_you',\n strikeTransportRetryCount: retryCount,\n strikeNextAttemptAt: undefined,\n strikeLandingAttempts: attempts,\n mergeNotes: detail,\n });\n await deps.writeFeedback(issueId, workspacePath, `## Strike landing needs operator attention\\n\\n${reason}`);\n await deps.needsYou(issueId, reason, { attempts });\n return `[strike-landing] ${issueId} at ${head} needs-you`;\n}\n\ninterface StrandedStrikeCandidate {\n issueId: string;\n branchName: string;\n workspacePath: string;\n head: string;\n}\n\nfunction parseStrikeWorktrees(porcelain: string): Array<{ branchName: string; workspacePath: string }> {\n const worktrees: Array<{ branchName: string; workspacePath: string }> = [];\n let workspacePath: string | undefined;\n for (const line of porcelain.split('\\n')) {\n if (line.startsWith('worktree ')) {\n workspacePath = line.slice('worktree '.length).trim();\n } else if (workspacePath && line.startsWith('branch refs/heads/strike/')) {\n worktrees.push({\n workspacePath,\n branchName: line.slice('branch refs/heads/'.length).trim(),\n });\n } else if (line.length === 0) {\n workspacePath = undefined;\n }\n }\n return worktrees;\n}\n\nasync function findStrandedStrikeCandidates(deps: StrikeLandingDeps): Promise<StrandedStrikeCandidate[]> {\n const candidates: StrandedStrikeCandidate[] = [];\n let projects: Awaited<ReturnType<typeof deps.listProjects>>;\n try {\n projects = await deps.listProjects();\n } catch {\n return candidates;\n }\n\n for (const { config } of projects) {\n let worktrees: Array<{ branchName: string; workspacePath: string }>;\n try {\n worktrees = parseStrikeWorktrees(await deps.git(['worktree', 'list', '--porcelain'], config.path));\n } catch {\n continue;\n }\n\n for (const { branchName, workspacePath } of worktrees) {\n const issueId = branchName.slice('strike/'.length).toUpperCase();\n if (!/^[A-Z][A-Z0-9]*-\\d+$/.test(issueId)) continue;\n if (await deps.isStrikeAgentAlive(`strike-${issueId.toLowerCase()}`)) continue;\n\n try {\n const [dirty, ahead, head] = await Promise.all([\n deps.git(['status', '--porcelain'], workspacePath),\n deps.git(['rev-list', '--count', 'origin/main..HEAD'], workspacePath),\n deps.git(['rev-parse', 'HEAD'], workspacePath),\n ]);\n if (dirty || Number(ahead) <= 0 || !/^[0-9a-f]{40}$/i.test(head)) continue;\n candidates.push({ issueId, branchName, workspacePath, head });\n } catch {\n // Cannot prove that this worktree is clean and ahead of main. Leave it\n // untouched for a later patrol rather than guessing at agent intent.\n }\n }\n }\n return candidates;\n}\n\n/**\n * Push and mark ready a completed strike whose harness has exited before it\n * could finish the commit → push → strike-ready handoff. The status update is\n * durable issue evidence and the returned action is recorded in the ship log.\n */\nexport async function salvageStrandedStrikeBranches(deps: StrikeLandingDeps): Promise<string[]> {\n const actions: string[] = [];\n for (const candidate of await findStrandedStrikeCandidates(deps)) {\n const current = deps.getStatus(candidate.issueId);\n if (current?.strikeReadyHead === candidate.head) continue;\n\n // Recheck immediately before the push to narrow the liveness race with an\n // agent that may have resumed after the initial branch scan.\n if (await deps.isStrikeAgentAlive(`strike-${candidate.issueId.toLowerCase()}`)) continue;\n\n try {\n const [dirty, head] = await Promise.all([\n deps.git(['status', '--porcelain'], candidate.workspacePath),\n deps.git(['rev-parse', 'HEAD'], candidate.workspacePath),\n ]);\n if (dirty || head !== candidate.head) continue;\n\n await deps.git(['push', 'origin', candidate.branchName], candidate.workspacePath);\n deps.setStatus(candidate.issueId, {\n strikeReadyHead: candidate.head,\n strikeReadyAt: deps.now(),\n strikeLandingState: 'ready',\n strikeRecoveryCount: 0,\n strikeTransportRetryCount: undefined,\n strikeNextAttemptAt: undefined,\n strikeLandingAttempts: current?.strikeLandingAttempts ?? [],\n mergeNotes: `Automatically salvaged completed strike branch ${candidate.branchName} after its harness exited before push.`,\n });\n actions.push(`[strike-salvage] pushed ${candidate.issueId} at ${candidate.head}`);\n } catch (error) {\n console.warn(`[strike-salvage] could not push ${candidate.issueId} at ${candidate.head}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n return actions;\n}\n\nexport async function patrolStrikeLandings(overrides: Partial<StrikeLandingDeps> = {}): Promise<string[]> {\n const deps = { ...defaultDeps(), ...overrides };\n const actions = await salvageStrandedStrikeBranches(deps);\n for (const [key, candidate] of Object.entries(deps.loadStatuses())) {\n const issueId = (candidate.issueId || key).toUpperCase();\n const head = candidate.strikeReadyHead;\n if (!head || (candidate.strikeLandingState !== 'ready' && candidate.strikeLandingState !== 'landing')) continue;\n if (candidate.strikeNextAttemptAt && candidate.strikeNextAttemptAt > deps.now()) continue;\n if (candidate.deaconIgnored || candidate.stuck || candidate.mergeStatus === 'merged') continue;\n\n const current = deps.getStatus(issueId);\n if (current?.strikeReadyHead !== head || (current.strikeLandingState !== 'ready' && current.strikeLandingState !== 'landing')) continue;\n const leaseKey = `${issueId}:${head}`;\n if (current.strikeLandingState === 'landing' && deps.isScheduled(leaseKey)) continue;\n if (current.strikeLandingState === 'landing' && deps.isPersistentlyOwned(issueId)) continue;\n const claimed = current.strikeLandingState === 'ready' ? deps.setStatus(issueId, { strikeLandingState: 'landing' }) : current;\n if (claimed.strikeReadyHead !== head || claimed.strikeLandingState !== 'landing') continue;\n\n deps.schedule(leaseKey, async () => {\n try { await executeStrikeLanding(issueId, head, claimed, deps); }\n catch (error) {\n const detail = `Unexpected supervised strike landing failure: ${error instanceof Error ? error.message : String(error)}`;\n try {\n const project = deps.resolveProject(issueId);\n await handleFailure(issueId, head, detail, project?.projectPath ?? '', project ? join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}-strike`) : '', deps.getStatus(issueId) ?? claimed, deps);\n } catch (recoveryError) {\n deps.setStatus(issueId, { strikeLandingState: 'needs_you', mergeNotes: `${detail}; durable recovery failed: ${recoveryError instanceof Error ? recoveryError.message : String(recoveryError)}` });\n }\n }\n });\n actions.push(`[strike-landing] ${candidate.strikeLandingState === 'landing' ? 'reclaimed' : 'claimed'} ${issueId} at ${head}`);\n }\n return actions;\n}\n\nasync function executeStrikeLanding(issueId: string, head: string, claimed: ReviewStatus, deps: StrikeLandingDeps): Promise<void> {\n const project = deps.resolveProject(issueId);\n if (!project) {\n await handleFailure(issueId, head, `Strike landing could not resolve a configured project for ${issueId}`, '', '', claimed, deps);\n return;\n }\n const request: StrikeMergeRequest = {\n kind: 'strike', markerHead: head,\n workspacePath: join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}-strike`),\n branchName: `strike/${issueId.toLowerCase()}`,\n recoveryTarget: `strike-${issueId.toLowerCase()}`,\n };\n const result = await deps.mergeIssue(issueId, request);\n if (result.mergeStatus === 'merged') {\n deps.setStatus(issueId, {\n strikeLandingState: 'landed',\n strikeReadyHead: undefined,\n strikeReadyAt: undefined,\n strikeTransportRetryCount: undefined,\n strikeNextAttemptAt: undefined,\n });\n } else if (result.success || result.mergeStatus === 'queued' || result.mergeStatus === 'merging' || result.mergeStatus === 'merged') {\n return;\n } else if (result.transport) {\n const current = deps.getStatus(issueId) ?? claimed;\n if (current.mergeStatus === 'merged' || current.strikeLandingState === 'landed') {\n deps.setStatus(issueId, {\n strikeLandingState: 'landed',\n strikeReadyHead: undefined,\n strikeReadyAt: undefined,\n strikeTransportRetryCount: undefined,\n strikeNextAttemptAt: undefined,\n });\n return;\n }\n await handleTransportFailure(issueId, head, result.error ?? 'Strike landing transport failed', current, deps);\n } else {\n await handleFailure(issueId, head, result.error ?? 'Strike landing failed', project.projectPath, request.workspacePath, claimed, deps);\n }\n}\n","/**\n * PAN-3512 — orphan-restore terminal verdicts go through the verdict write door.\n *\n * The two orphan-restore paths in deacon-review-status.ts rebuild a terminal\n * review verdict from history (a coordinator that exited after a completed\n * review, and a 'reviewing' row whose agent is gone). Both used to write the row\n * directly, so a restore whose evidence head disagreed with the row anchor was\n * dropped by the old guard with no event and no route to an honest next step.\n *\n * This adapter keeps the restore call sites flat: the caller keeps building the\n * update object it already built, and everything other than the verdict, its\n * notes, and the evidence anchor rides along in `extra` so stuck-clearing and\n * retry-counter fields land in the same atomic write. Dropping them would\n * deadlock the issue at passed+stuck.\n */\nimport { recordReviewVerdict, type ReviewVerdict, type VerdictOutcome } from './review-verdict-writer.js';\nimport type { HeadAnchor } from '../git-utils.js';\n\nexport async function recordOrphanRestoreVerdict(\n issueId: string,\n update: Record<string, unknown>,\n): Promise<VerdictOutcome> {\n const { reviewStatus, reviewNotes, reviewedAtCommit, ...extra } = update;\n return recordReviewVerdict(issueId, {\n verdict: reviewStatus as ReviewVerdict,\n notes: reviewNotes as string | undefined,\n ...(reviewedAtCommit ? { evidenceHead: reviewedAtCommit as HeadAnchor } : {}),\n extra,\n writer: 'orphan-restore',\n });\n}\n","import { exec } from 'child_process';\nimport { existsSync, rmSync } from 'fs';\nimport { join } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getAgentRuntimeStateSync, getAgentStateSync, listRunningAgents } from '../agents.js';\nimport { listAllAgentsSync as listAllAgents } from '../overdeck/agents.js';\nimport { markWorkspaceStuck } from '../overdeck/review-status-sync.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { getReviewStatusSync, loadReviewStatuses, setReviewStatusSync, type ReviewStatus, type ReviewStatusUpdate } from '../review-status.js';\nimport { observeActiveReviewArtifact } from './verdict-restore.js';\nimport { recordOrphanRestoreVerdict } from './orphan-restore-verdict.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { recordDeaconNudge } from './deacon-nudge-log.js';\nimport { REVIEW_SUB_ROLES } from './review-monitor.js';\nimport { getAllProjectSpecialistStatuses, getTmuxSessionName } from './specialists.js';\nimport { isPaneDead, sessionExistsSync } from '../tmux.js';\nimport { describeRunningAgents, releaseAdvancingSlot, tryReserveAdvancingSlot } from './concurrency.js';\nimport { tryYieldForAdvancingDispatch } from './preemption.js';\nimport { findWorkspacePath, inferBranchFromWorkspace } from '../lifecycle/archive-planning.js';\nimport { isIssueClosed } from './issue-closed.js';\nimport { shouldSkipDispatchAsMerged } from './merge-verification.js';\nimport { getAutoCloseOutCanonicalState } from './deacon-canonical-state.js';\nimport { evaluateReviewConvoyLiveness } from './review-convoy-liveness.js';\n\nconst execAsync = promisify(exec);\n\n// ============================================================================\n// Orphaned Review Status Detection\n// ============================================================================\n\n/**\n * Check for orphaned review/test statuses (PAN-88 follow-up)\n *\n * Detects when an issue has reviewStatus='reviewing' or testStatus='testing'\n * but the corresponding specialist isn't actually running. This can happen if:\n * - The specialist crashed mid-review\n * - The specialist was killed\n * - The wake failed but status wasn't rolled back\n *\n * Resets orphaned statuses to 'pending' so the work can be retried.\n */\n/**\n * PAN-794: Circuit-breaker threshold for consecutive parallel-review re-dispatches\n * within a recovery cycle. After this many resets of reviewing → pending by the\n * orphan sweep, the workspace is flagged stuck so it stops burning agent cycles.\n * A clean review outcome (pass/blocked/fail), new commits, or manual unstick\n * resets the counter (see review-agent.ts and checkPostReviewCommits below).\n */\nconst REVIEW_INFRA_BREAKER_THRESHOLD = 3;\n\n/**\n * Orphan-test self-heal state. A test role cannot spawn while the workspace\n * docker stack is unhealthy — `assertWorkspaceStackHealthyForSpawn` throws and\n * the orphan-test patrol re-fails the identical dispatch every cycle forever\n * (observed: PAN-1190 looped `dispatch_failed` for ~18h after its server/dev\n * containers exited). The deacon now rebuilds the stack before re-dispatch,\n * bounded by a cooldown + attempt cap so a stack that genuinely cannot be\n * rebuilt escalates to a human instead of looping `docker compose` forever.\n */\nconst testStackRebuildState: Map<string, { lastAttempt: number; attempts: number; escalated: boolean }> =\n new Map();\nconst TEST_STACK_REBUILD_COOLDOWN_MS = 15 * 60 * 1000;\nconst TEST_STACK_REBUILD_MAX_ATTEMPTS = 3;\n\nexport const stalledReviewConvoyRecoveryState: Map<string, { lastAttempt: number; attempts: number; escalated: boolean }> =\n new Map();\nconst STALLED_REVIEW_CONVOY_RECOVERY_COOLDOWN_MS = 15 * 60 * 1000;\nconst STALLED_REVIEW_CONVOY_RECOVERY_MAX_ATTEMPTS = 3;\n\nexport interface ReviewConvoyLiveness {\n anyLive: boolean;\n anyGated: boolean;\n agentIds: string[];\n}\n\nexport function reviewConvoyLiveness(issueId: string): ReviewConvoyLiveness {\n const normalizedIssueId = issueId.toLowerCase();\n const agentIds = [\n `agent-${normalizedIssueId}`,\n `agent-${normalizedIssueId}-review`,\n ...REVIEW_SUB_ROLES.map((subRole) => `agent-${normalizedIssueId}-review-${subRole}`),\n ];\n\n let anyLive = false;\n let anyGated = false;\n\n for (const agentId of agentIds) {\n const agentState = getAgentStateSync(agentId);\n if (!agentState) {\n continue;\n }\n anyLive ||= agentState.status === 'running' || agentState.status === 'starting';\n anyGated ||= agentState.paused === true || agentState.troubled === true;\n }\n\n return { anyLive, anyGated, agentIds };\n}\n\n/**\n * Outcome of an orphan-test stack-health recovery attempt:\n * - `healthy` — stack is already fine; dispatch can proceed.\n * - `rebuilt` — stack was unhealthy, a rebuild ran successfully; dispatch can proceed.\n * - `cooldown` — stack unhealthy, a rebuild was attempted recently; wait it out.\n * - `exhausted` — stack unhealthy, the rebuild cap is reached; escalate, stop retrying.\n */\ntype TestStackRecovery = 'healthy' | 'rebuilt' | 'cooldown' | 'exhausted';\n\n/**\n * Ensure the workspace docker stack for a test re-dispatch is healthy,\n * rebuilding it once per cooldown window if not. Bounded by\n * TEST_STACK_REBUILD_MAX_ATTEMPTS so an unrebuildable stack escalates cleanly.\n */\nasync function recoverUnhealthyTestStack(\n issueId: string,\n workspacePath: string,\n): Promise<TestStackRecovery> {\n const key = issueId.toUpperCase();\n const { getWorkspaceStackHealth } = await import('../workspace/stack-health.js');\n\n // PAN-1249: getWorkspaceStackHealth now returns an Effect — run it at this\n // Promise boundary. The Effect never fails (error channel: never), so\n // Effect.runPromise is safe and the result is the plain WorkspaceStackHealth.\n const health = await Effect.runPromise(getWorkspaceStackHealth(issueId, { workspacePath }));\n if (health.healthy) {\n testStackRebuildState.delete(key);\n return 'healthy';\n }\n\n const record = testStackRebuildState.get(key) ?? { lastAttempt: 0, attempts: 0, escalated: false };\n const now = Date.now();\n\n if (record.attempts >= TEST_STACK_REBUILD_MAX_ATTEMPTS) {\n if (!record.escalated) {\n record.escalated = true;\n testStackRebuildState.set(key, record);\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n issueId: key,\n message: `test-stack-rebuild-exhausted: ${key}`,\n details: `Workspace docker stack still unhealthy after ${record.attempts} rebuild attempts: ${health.reasons.join('; ')}. Manual 'pan workspace rebuild ${key}' or 'pan workspace reap' needed.`,\n });\n console.warn(\n `[deacon] Test stack for ${key} unhealthy after ${record.attempts} rebuilds — escalated; ` +\n `stop re-dispatching until a human intervenes`,\n );\n }\n return 'exhausted';\n }\n\n if (now - record.lastAttempt < TEST_STACK_REBUILD_COOLDOWN_MS) {\n return 'cooldown';\n }\n\n record.lastAttempt = now;\n record.attempts += 1;\n testStackRebuildState.set(key, record);\n console.log(\n `[deacon] Test stack for ${key} unhealthy (${health.reasons.join('; ')}) — rebuilding ` +\n `(attempt ${record.attempts}/${TEST_STACK_REBUILD_MAX_ATTEMPTS})`,\n );\n\n const { rebuildWorkspaceStack } = await import('../workspace/rebuild-stack.js');\n // PAN-1249: rebuildWorkspaceStack returns Effect<RebuildWorkspaceStackResult>\n // with error channel `never` — the Effect captures any failure into\n // result.error. Run at this Promise boundary so the deacon's recovery loop\n // keeps its current Promise-based shape.\n const result = await Effect.runPromise(\n rebuildWorkspaceStack(issueId, {\n onProgress: (m) => console.log(`[deacon] ${key} stack rebuild: ${m}`),\n }),\n );\n if (!result.success) {\n console.warn(`[deacon] Test stack rebuild failed for ${key}: ${result.error}`);\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n issueId: key,\n message: `test-stack-rebuild-failed: ${key}`,\n details: result.error ?? 'unknown error',\n });\n return 'cooldown';\n }\n\n console.log(`[deacon] Test stack for ${key} rebuilt — proceeding with test dispatch`);\n return 'rebuilt';\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// PAN-1908: reactive review-status handlers (replace directory scans)\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface ReviewStatusLike {\n updatedAt?: string;\n reviewSpawnedAt?: string | number;\n reviewStatus?: string;\n verificationStatus?: string;\n testStatus?: string;\n mergeStatus?: string;\n readyForMerge?: boolean;\n prUrl?: string | null;\n stuck?: boolean;\n deaconIgnored?: boolean;\n stuckReason?: string;\n reviewRetryCount?: number;\n recoveryStartedAt?: string;\n history?: Array<{ type: string; status: string; notes?: string }>;\n reviewNotes?: string;\n lastVerifiedCommit?: string;\n reviewedAtCommit?: string;\n stuckAt?: string;\n stuckDetails?: string;\n}\n\ninterface OrphanRecoveryOptions {\n readWorkspaceHead?: (workspace: string) => Promise<string>;\n}\n\nfunction latestHistoryEntry(\n history: Array<{ type: string; status: string; notes?: string }> | undefined,\n type: 'review' | 'test',\n terminalStatuses: readonly string[],\n): { status: string; notes?: string } | null {\n if (!history || history.length === 0) return null;\n for (let i = history.length - 1; i >= 0; i--) {\n const entry = history[i];\n if (entry.type === type && terminalStatuses.includes(entry.status)) {\n return { status: entry.status, notes: entry.notes };\n }\n }\n return null;\n}\n\nfunction latestHistoryByType(\n history: Array<{ type: string; status: string; notes?: string }> | undefined,\n type: 'review' | 'test',\n): string | undefined {\n if (!history || history.length === 0) return undefined;\n for (let i = history.length - 1; i >= 0; i--) {\n if (history[i].type === type) return history[i].status;\n }\n return undefined;\n}\n\nexport function completedReviewSnapshotAfterCoordinatorExit(\n status: Pick<ReviewStatus, 'history'>,\n): ReviewStatusUpdate | null {\n const review = latestHistoryEntry(status.history, 'review', ['passed', 'failed', 'blocked']);\n if (review?.status !== 'passed') return null;\n const test = latestHistoryEntry(status.history, 'test', ['passed', 'failed', 'skipped']);\n return {\n reviewStatus: 'passed',\n reviewNotes: review.notes,\n ...(test ? { testStatus: test.status as ReviewStatus['testStatus'], testNotes: test.notes } : {}),\n reviewRetryCount: 0,\n recoveryStartedAt: undefined,\n };\n}\n\nasync function isReviewAgentActiveForIssue(\n issueId: string,\n status: ReviewStatusLike,\n): Promise<{ active: boolean; reason: string }> {\n // PAN-1048 R5: role-primitive review/test runs (agent-<id>-review, agent-<id>-test).\n try {\n const agents = await Effect.runPromise(listRunningAgents());\n return evaluateReviewConvoyLiveness(issueId, status, agents);\n } catch {\n // fall through\n }\n\n // Global specialists\n for (const type of ['review-agent'] as const) {\n const session = getTmuxSessionName(type);\n if (sessionExistsSync(session)) {\n const rState = getAgentRuntimeStateSync(session);\n if (rState?.state === 'active' && rState.currentIssue?.toUpperCase() === issueId.toUpperCase()) {\n return { active: true, reason: `active global ${type} specialist` };\n }\n }\n }\n\n // Per-project ephemeral specialists\n try {\n const projectStatuses = await getAllProjectSpecialistStatuses();\n for (const projSpec of projectStatuses) {\n if (!projSpec.isRunning || projSpec.specialistType !== 'review-agent') continue;\n const rState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n if (rState?.state === 'active' && rState.currentIssue?.toUpperCase() === issueId.toUpperCase()) {\n return { active: true, reason: `active project review specialist ${projSpec.tmuxSession}` };\n }\n }\n } catch {\n // fall through\n }\n\n return { active: false, reason: 'no active review session' };\n}\n\nasync function isTestAgentActiveForIssue(issueId: string): Promise<boolean> {\n try {\n const agents = await Effect.runPromise(listRunningAgents());\n for (const agent of agents) {\n if (agent.status === 'stopped' || agent.status === 'error') continue;\n const id = (agent.issueId ?? '').trim().toUpperCase();\n if (!id || id !== issueId.toUpperCase()) continue;\n const role = agent.role ?? (agent.id.endsWith('-review') ? 'review' : agent.id.endsWith('-test') ? 'test' : null);\n if (role === 'test') return true;\n }\n } catch {\n // fall through\n }\n\n for (const type of ['test-agent'] as const) {\n const session = getTmuxSessionName(type);\n if (sessionExistsSync(session)) {\n const rState = getAgentRuntimeStateSync(session);\n if (rState?.state === 'active' && rState.currentIssue?.toUpperCase() === issueId.toUpperCase()) {\n return true;\n }\n }\n }\n\n try {\n const projectStatuses = await getAllProjectSpecialistStatuses();\n for (const projSpec of projectStatuses) {\n if (!projSpec.isRunning || projSpec.specialistType !== 'test-agent') continue;\n const rState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n if (rState?.state === 'active' && rState.currentIssue?.toUpperCase() === issueId.toUpperCase()) {\n return true;\n }\n }\n } catch {\n // fall through\n }\n\n return false;\n}\n\n// Reads workspace evidence asynchronously for diagnostics; artifacts never write review status.\nasync function observeActiveReviewArtifactForRecovery(\n issueId: string,\n status: Pick<ReviewStatus, 'lastVerifiedCommit'>,\n caller: string,\n) {\n const state = getAgentStateSync(`agent-${issueId.toLowerCase()}-review`);\n return observeActiveReviewArtifact(issueId, {\n runId: state?.reviewRunId,\n workspacePath: state?.workspace,\n rowHead: status.lastVerifiedCommit,\n caller,\n });\n}\n\nasync function recordArtifactObservationAtBreaker(issueId: string, status: Pick<ReviewStatus, 'lastVerifiedCommit'>, actions: string[]): Promise<void> {\n try {\n const result = await observeActiveReviewArtifactForRecovery(\n issueId,\n status,\n 'review-infrastructure-breaker',\n );\n if (result.outcome !== 'no-artifact') {\n actions.push(\n `Preserved ${issueId}'s active-run ${result.artifact.verdict} artifact for diagnosis; continuing bounded review-infrastructure recovery`,\n );\n }\n } catch (err) {\n console.warn(`[deacon] Artifact observation failed for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n/**\n * PAN-1908: react to review.coordinator.died by resetting the issue to a\n * pending review state and re-dispatching the review role. No review-status\n * DB scan — operates on the single issue ID from the event.\n */\nexport async function handleReviewCoordinatorDied(\n issueId: string,\n _sessionName: string,\n _reason: string,\n): Promise<string[]> {\n const actions: string[] = [];\n const status = getReviewStatusSync(issueId);\n\n if (!status) {\n logDeaconEventSync(`handleReviewCoordinatorDied: ${issueId} skipped — no review-status row`);\n return actions;\n }\n\n if (status.stuck || status.deaconIgnored) {\n logDeaconEventSync(`handleReviewCoordinatorDied: ${issueId} skipped — stuck/deaconIgnored`);\n return actions;\n }\n\n if (await isIssueClosed(issueId)) {\n logDeaconEventSync(`handleReviewCoordinatorDied: ${issueId} skipped — issue closed`);\n return actions;\n }\n\n const completedSnapshot = completedReviewSnapshotAfterCoordinatorExit(status);\n if (completedSnapshot) {\n const restored = await recordOrphanRestoreVerdict(issueId, completedSnapshot);\n actions.push(restored.landed ? `Restored completed review for ${issueId} after coordinator exit; no re-dispatch` : `Completed-review restore for ${issueId} not recorded (${restored.reason})`);\n return actions;\n }\n\n if ((status.reviewRetryCount ?? 0) >= REVIEW_INFRA_BREAKER_THRESHOLD) {\n await recordArtifactObservationAtBreaker(issueId, status, actions);\n markWorkspaceStuck(issueId, 'review_infrastructure_failure', {\n reviewRetryCount: status.reviewRetryCount ?? 0,\n recoveryStartedAt: status.recoveryStartedAt,\n });\n actions.push(`Review recovery stopped for ${issueId} after ${status.reviewRetryCount} retries`);\n return actions;\n }\n\n // Only reset from active/reviewing states; terminal/failed states should not\n // be overwritten by a coordinator death event.\n if (status.reviewStatus !== 'reviewing' && status.reviewStatus !== 'pending') {\n logDeaconEventSync(`handleReviewCoordinatorDied: ${issueId} skipped — reviewStatus=${status.reviewStatus}`);\n return actions;\n }\n\n const nextRetry = (status.reviewRetryCount ?? 0) + 1;\n const recoveryStart = status.recoveryStartedAt ?? new Date().toISOString();\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n reviewRetryCount: nextRetry,\n recoveryStartedAt: recoveryStart,\n });\n actions.push(`Reset review for ${issueId} after coordinator died (retry ${nextRetry})`);\n\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const workspace = findWorkspacePath(resolved?.projectPath ?? '', issueLower);\n\n if (!resolved || !workspace) {\n actions.push(`Skipped review re-dispatch for ${issueId}: workspace unavailable`);\n return actions;\n }\n\n if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n actions.push(`Deferred review re-dispatch for ${issueId} — advancing-role concurrency ceiling reached`);\n return actions;\n }\n\n try {\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n const dispatchResult = await Effect.runPromise(\n spawnReviewRoleForIssue({ issueId, workspace, branch: inferBranchFromWorkspace(workspace, issueLower) }),\n );\n if (dispatchResult.gated) {\n releaseAdvancingSlot();\n actions.push(`Deferred review re-dispatch for ${issueId} — ${dispatchResult.message}`);\n } else if (dispatchResult.success) {\n actions.push(`Re-dispatched review for ${issueId} after coordinator died`);\n } else {\n actions.push(`Failed to re-dispatch review for ${issueId}: ${dispatchResult.error || dispatchResult.message}`);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n actions.push(`Failed to re-dispatch review for ${issueId}: ${msg}`);\n }\n\n return actions;\n}\n\n/**\n * PAN-1908: react to work.completed by creating a review-status row if one is\n * missing. The reactive scheduler's issue-state change already dispatches the\n * review role; this handler ensures the row exists so downstream reads don't\n * fail.\n */\nexport async function handleWorkCompleted(issueId: string): Promise<string[]> {\n const actions: string[] = [];\n const status = getReviewStatusSync(issueId);\n if (status) {\n logDeaconEventSync(`handleWorkCompleted: ${issueId} already has review-status row`);\n return actions;\n }\n\n if (await isIssueClosed(issueId)) {\n logDeaconEventSync(`handleWorkCompleted: ${issueId} skipped — issue closed`);\n return actions;\n }\n\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n testStatus: 'pending',\n updatedAt: new Date().toISOString(),\n });\n actions.push(`Created missing review-status row for ${issueId} (work completed)`);\n return actions;\n}\n\n/**\n * PAN-1908: per-issue orphan reconciler for a single review-status row. Used by\n * the legacy checkOrphanedReviewStatuses safety net and by reactive handlers.\n */\nasync function reconcileReviewStatusOrphan(\n issueId: string,\n rawStatus: ReviewStatusLike,\n options: OrphanRecoveryOptions = {},\n): Promise<string[]> {\n const actions: string[] = [];\n\n const status = getReviewStatusSync(issueId) ?? rawStatus;\n\n if (status.stuck && !(status.stuckReason === 'verification_stuck' && status.reviewStatus === 'reviewing')) return actions;\n if (status.deaconIgnored) return actions;\n if (await isIssueClosed(issueId)) return actions;\n\n const hasPassedReview = latestHistoryByType(status.history, 'review') === 'passed';\n const hasPassedTest = latestHistoryByType(status.history, 'test') === 'passed';\n const latestTerminalReview = latestHistoryEntry(status.history, 'review', ['passed', 'failed', 'blocked']);\n const latestTerminalTest = latestHistoryEntry(status.history, 'test', ['passed', 'failed', 'skipped']);\n\n const reviewLiveness = await isReviewAgentActiveForIssue(issueId, status);\n const reviewAgentActive = reviewLiveness.active;\n if (status.reviewStatus === 'reviewing') {\n console.log(`[deacon] Evaluated reviewing status for ${issueId}: ${reviewLiveness.reason}`);\n }\n\n // A supervised verification worker can finish after the dashboard process\n // that requested it has died. The worker records the pass, but the dead HTTP\n // fiber never clears the prior review failure or dispatches review. Recover\n // only when the verified commit is still current; a moved HEAD needs a fresh\n // verification instead of carrying the old pass forward.\n if (\n status.verificationStatus === 'passed' &&\n status.lastVerifiedCommit &&\n !['pending', 'reviewing', 'passed'].includes(status.reviewStatus ?? '') &&\n !reviewAgentActive\n ) {\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const agentState = getAgentStateSync(`agent-${issueLower}`);\n const workspace = agentState?.workspace || (resolved ? findWorkspacePath(resolved.projectPath, issueLower) : null);\n\n if (workspace) {\n try {\n // PAN-2948: polyrepo-aware — lastVerifiedCommit is a composite\n // sub-repo snapshot, so compare against the same snapshot form.\n const currentHead = options.readWorkspaceHead\n ? await options.readWorkspaceHead(workspace)\n : await (await import('../git-utils.js')).snapshotWorkspaceHeadsPromise(issueId, workspace);\n if (currentHead === status.lastVerifiedCommit) {\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n reviewNotes: undefined,\n });\n status.reviewStatus = 'pending';\n status.reviewNotes = undefined;\n actions.push(`Recovered review continuation for ${issueId} after verification completed during restart`);\n }\n } catch {\n // A missing/unreadable workspace cannot prove the pass is current.\n }\n }\n }\n\n // Orphaned reviewing status\n if (status.reviewStatus === 'reviewing' && !reviewAgentActive) {\n if (latestTerminalReview && latestTerminalReview.status === 'passed') {\n const reviewUpdate: Record<string, unknown> = {\n reviewStatus: latestTerminalReview.status,\n reviewNotes: latestTerminalReview.notes,\n };\n try {\n const project = resolveProjectFromIssueSync(issueId);\n if (project) {\n const workspacePath = join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (existsSync(workspacePath)) {\n // PAN-2948: polyrepo-aware — snapshots sub-repo heads, not the wrapper.\n const { snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n reviewUpdate['reviewedAtCommit'] = await snapshotWorkspaceHeadsPromise(issueId, workspacePath);\n }\n }\n } catch { /* non-fatal */ }\n if (status.stuckReason === 'review_infrastructure_failure') {\n reviewUpdate['stuck'] = false;\n reviewUpdate['stuckReason'] = undefined;\n reviewUpdate['stuckAt'] = undefined;\n reviewUpdate['stuckDetails'] = undefined;\n }\n if (latestTerminalTest) {\n reviewUpdate['testStatus'] = latestTerminalTest.status;\n reviewUpdate['testNotes'] = latestTerminalTest.notes;\n }\n if (status.mergeStatus === 'failed') {\n const isCiFailure = typeof status.reviewNotes === 'string' && status.reviewNotes.includes('failing required checks');\n if (!isCiFailure) {\n reviewUpdate['mergeStatus'] = 'pending';\n }\n }\n const restored = await recordOrphanRestoreVerdict(issueId, reviewUpdate);\n actions.push(\n (restored.landed ? `Restored orphaned review snapshot for ${issueId} to ${latestTerminalReview.status}` : `Orphaned review snapshot for ${issueId} not recorded (${restored.reason})`) +\n (latestTerminalTest ? ` / test ${latestTerminalTest.status}` : ''),\n );\n return actions;\n }\n if (!hasPassedReview) {\n // RACE GUARD (PAN-1577): preserve active-run evidence for diagnosis, but\n // a workspace-writable artifact cannot complete the review by itself.\n try {\n const observation = await observeActiveReviewArtifactForRecovery(\n issueId,\n status,\n 'orphan-review-recovery',\n );\n if (observation.outcome !== 'no-artifact') {\n actions.push(\n `Preserved orphaned ${observation.artifact.verdict} evidence for ${issueId} from active run ${observation.artifact.runId}; continuing bounded recovery`,\n );\n }\n } catch (err) {\n console.warn(`[deacon] Artifact observation failed for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n const nextRetry = (status.reviewRetryCount ?? 0) + 1;\n const recoveryStart = status.recoveryStartedAt ?? new Date().toISOString();\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n reviewRetryCount: nextRetry,\n recoveryStartedAt: recoveryStart,\n });\n actions.push(\n `Reset orphaned review for ${issueId} (no review-agent active; retry ${nextRetry}/${REVIEW_INFRA_BREAKER_THRESHOLD})`,\n );\n }\n }\n\n // Re-dispatch pending reviews\n const reviewQueuedOrActive = reviewAgentActive;\n if (\n status.reviewStatus === 'pending' &&\n !reviewQueuedOrActive &&\n !hasPassedReview &&\n status.prUrl\n ) {\n if ((status.reviewRetryCount ?? 0) >= REVIEW_INFRA_BREAKER_THRESHOLD) {\n try {\n await recordArtifactObservationAtBreaker(issueId, status, actions);\n markWorkspaceStuck(issueId, 'review_infrastructure_failure', {\n reviewRetryCount: status.reviewRetryCount ?? 0,\n recoveryStartedAt: status.recoveryStartedAt,\n lastReviewNotes: status.reviewNotes,\n });\n actions.push(\n `Tripped review-infra breaker for ${issueId} after ${status.reviewRetryCount} retries — marked stuck`,\n );\n } catch (err) {\n console.error(`[deacon] Failed to mark ${issueId} stuck after breaker trip:`, err);\n }\n return actions;\n }\n\n const agentIdForCheck = `agent-${issueId.toLowerCase()}`;\n const completedProcessedFile = join(AGENTS_DIR, agentIdForCheck, 'completed.processed');\n if (!existsSync(completedProcessedFile)) return actions;\n\n const agentState = getAgentStateSync(agentIdForCheck);\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const workspace = agentState?.workspace || (resolved ? findWorkspacePath(resolved.projectPath, issueLower) : null);\n\n if (workspace && resolved && !tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n actions.push(`Deferred review re-dispatch for ${issueId} — advancing-role concurrency ceiling reached`);\n } else if (workspace && resolved) {\n try {\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n const dispatchResult = await Effect.runPromise(\n spawnReviewRoleForIssue({ issueId, workspace, branch: inferBranchFromWorkspace(workspace, issueLower) }),\n );\n if (dispatchResult.gated) {\n releaseAdvancingSlot();\n actions.push(`Deferred review re-dispatch for ${issueId} — ${dispatchResult.message}`);\n } else if (dispatchResult.success) {\n actions.push(`Re-dispatched pending review for ${issueId} (deacon-orphan-recovery)`);\n } else {\n actions.push(`Failed to re-dispatch pending review for ${issueId}: ${dispatchResult.error || dispatchResult.message}`);\n }\n } catch (err) {\n actions.push(`Failed to re-dispatch pending review for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n } else if (!resolved) {\n actions.push(`Skipped pending review re-dispatch for ${issueId}: no project configured`);\n } else {\n actions.push(`Skipped pending review re-dispatch for ${issueId}: workspace unavailable`);\n }\n }\n\n // Orphaned testing status\n const testAgentActive = await isTestAgentActiveForIssue(issueId);\n if (\n (status.testStatus === 'testing' || status.testStatus === 'dispatch_failed') &&\n !testAgentActive &&\n !hasPassedTest &&\n !status.readyForMerge\n ) {\n const agentId = `agent-${issueId.toLowerCase()}`;\n const agentState = getAgentStateSync(agentId);\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const workspace = agentState?.workspace || (resolved ? findWorkspacePath(resolved.projectPath, issueLower) : null);\n\n if (workspace && resolved) {\n const branch = `feature/${issueLower}`;\n const { spawnRun } = await import('../agents.js');\n const { buildTestRolePrompt } = await import('./test-agent-queue.js');\n\n const stackRecovery = await recoverUnhealthyTestStack(issueId, workspace);\n if (stackRecovery === 'cooldown' || stackRecovery === 'exhausted') {\n setReviewStatusSync(issueId, { testStatus: 'dispatch_failed' });\n actions.push(\n stackRecovery === 'exhausted'\n ? `Orphaned test for ${issueId}: workspace docker stack unhealthy, rebuild cap reached — escalated to human`\n : `Orphaned test for ${issueId}: workspace docker stack rebuilding — deferring re-dispatch`,\n );\n } else if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('test', issueId))) {\n actions.push(`Deferred test re-dispatch for ${issueId} — advancing-role concurrency ceiling reached`);\n } else {\n const mergedGuard = await shouldSkipDispatchAsMerged(issueId);\n if (mergedGuard.skip) {\n actions.push(`Orphaned test for ${issueId}: ${mergedGuard.reason}; skipping re-dispatch`);\n } else {\n try {\n const run = await spawnRun(issueId, 'test', {\n workspace,\n prompt: buildTestRolePrompt({ issueId, workspace, branch }),\n startedBy: 'deacon:orphan-test-recovery',\n });\n testStackRebuildState.delete(issueId.toUpperCase());\n setReviewStatusSync(issueId, { testStatus: 'testing' });\n actions.push(`Re-dispatched orphaned test for ${issueId} via test role ${run.id} (deacon-orphan-recovery)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('already running')) {\n setReviewStatusSync(issueId, { testStatus: 'testing' });\n actions.push(`Orphaned test for ${issueId}: test role already running`);\n } else {\n setReviewStatusSync(issueId, { testStatus: 'dispatch_failed' });\n actions.push(`Orphaned test role dispatch failed for ${issueId}: ${msg}`);\n }\n }\n }\n }\n } else {\n setReviewStatusSync(issueId, { testStatus: 'pending' });\n actions.push(\n !resolved\n ? `Reset orphaned test for ${issueId}: no project configured`\n : `Reset orphaned test for ${issueId}: workspace unavailable`,\n );\n }\n }\n\n return actions;\n}\n\nexport async function checkOrphanedReviewStatuses(options: OrphanRecoveryOptions = {}): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n // PAN-1908: the primary orphan recovery path is now reactive\n // (review.coordinator.died / work.completed events). This function is kept\n // as a thin SQLite-only safety net for dropped events.\n const statuses = loadReviewStatuses();\n for (const [issueId, status] of Object.entries(statuses)) {\n const result = await reconcileReviewStatusOrphan(issueId, status as ReviewStatusLike, options);\n actions.push(...result);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking orphaned review statuses:', msg);\n }\n\n return actions;\n}\n\nexport async function recoverStalledReviewConvoys(\n getCanonicalState: (issueId: string) => Promise<string | null> = getAutoCloseOutCanonicalState,\n): Promise<string[]> {\n const actions: string[] = [];\n\n let statuses: Record<string, ReviewStatus>;\n try {\n statuses = loadReviewStatuses();\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error loading review statuses for stalled convoy recovery:', message);\n return actions;\n }\n\n for (const [issueId, rawStatus] of Object.entries(statuses)) {\n try {\n const status = getReviewStatusSync(issueId) ?? rawStatus;\n if (status.reviewStatus !== 'reviewing' && status.reviewStatus !== 'pending') continue;\n if (status.stuck || status.deaconIgnored) continue;\n\n const canonicalState = await getCanonicalState(issueId);\n if (canonicalState !== 'in_review') continue;\n\n const liveness = reviewConvoyLiveness(issueId);\n if (liveness.anyLive || liveness.anyGated) continue;\n\n const issueLower = issueId.toLowerCase();\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) {\n actions.push(`Skipped stalled review convoy recovery for ${issueId}: no project configured`);\n continue;\n }\n\n const workspace = findWorkspacePath(resolved.projectPath, issueLower);\n if (!workspace) {\n actions.push(`Skipped stalled review convoy recovery for ${issueId}: workspace unavailable`);\n continue;\n }\n\n const key = issueId.toUpperCase();\n let record = stalledReviewConvoyRecoveryState.get(key) ?? { lastAttempt: 0, attempts: 0, escalated: false };\n const now = Date.now();\n\n // If a human un-stuck the issue, grant a fresh recovery budget.\n if (record.escalated && !status.stuck) {\n stalledReviewConvoyRecoveryState.delete(key);\n record = { lastAttempt: 0, attempts: 0, escalated: false };\n }\n\n if (record.attempts >= STALLED_REVIEW_CONVOY_RECOVERY_MAX_ATTEMPTS) {\n if (!record.escalated) {\n record.escalated = true;\n stalledReviewConvoyRecoveryState.set(key, record);\n const stuckDetails = JSON.stringify({\n attempts: record.attempts,\n agentIds: liveness.agentIds,\n canonicalState,\n });\n setReviewStatusSync(issueId, {\n stuck: true,\n stuckReason: 'review_convoy_unrecoverable',\n stuckAt: new Date(now).toISOString(),\n stuckDetails,\n });\n status.stuck = true;\n status.stuckReason = 'review_convoy_unrecoverable';\n status.stuckAt = new Date(now).toISOString();\n status.stuckDetails = stuckDetails;\n emitActivityEntrySync({\n source: 'cloister',\n level: 'error',\n issueId: key,\n message: `stalled-review-convoy-unrecoverable: ${key}`,\n details: `Review convoy fully stopped after ${record.attempts} recovery attempts; marked stuck for human intervention. Agents: ${liveness.agentIds.join(', ')}`,\n });\n actions.push(\n `Stalled review convoy for ${issueId}: recovery cap reached after ${record.attempts} attempts — marked stuck`,\n );\n } else {\n actions.push(\n `Stalled review convoy for ${issueId}: recovery cap already escalated after ${record.attempts} attempts`,\n );\n }\n continue;\n }\n\n if (now - record.lastAttempt < STALLED_REVIEW_CONVOY_RECOVERY_COOLDOWN_MS) {\n actions.push(\n `Stalled review convoy for ${issueId}: deferring — cooldown active after attempt ${record.attempts}/${STALLED_REVIEW_CONVOY_RECOVERY_MAX_ATTEMPTS}`,\n );\n continue;\n }\n\n // PAN-1665: honor advancing-role concurrency budget before dispatch.\n if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n actions.push(\n `Stalled review convoy for ${issueId}: deferring — advancing-role concurrency ceiling reached`,\n );\n continue;\n }\n\n record.lastAttempt = now;\n record.attempts += 1;\n stalledReviewConvoyRecoveryState.set(key, record);\n\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n try {\n const result = await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace,\n branch: inferBranchFromWorkspace(workspace, issueLower),\n force: true,\n }));\n if (!result.success) {\n throw new Error(result.error ?? result.message);\n }\n stalledReviewConvoyRecoveryState.delete(key);\n status.reviewStatus = 'reviewing';\n actions.push(\n `Re-dispatched stalled review convoy for ${issueId} (attempt ${record.attempts}/${STALLED_REVIEW_CONVOY_RECOVERY_MAX_ATTEMPTS})`,\n );\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n actions.push(`Failed to re-dispatch stalled review convoy for ${issueId}: ${message}`);\n console.error(`[deacon] Failed to re-dispatch stalled review convoy for ${issueId}:`, message);\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n actions.push(`Failed stalled review convoy recovery for ${issueId}: ${message}`);\n console.error(`[deacon] Failed stalled review convoy recovery for ${issueId}:`, message);\n }\n }\n\n return actions;\n}\n\n// ============================================================================\n// PAN-699: Missing review status detection\n// ============================================================================\n\n/**\n * Check for completed work agents that have no review status entry at all.\n *\n * This catches the gap where `pan done` wrote the completion marker but the\n * HTTP trigger to the dashboard never arrived (dashboard down, network failure,\n * etc). Deacon scans the agent directories and auto-triggers review dispatch.\n */\nexport async function checkMissingReviewStatuses(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n // PAN-1908: primary missing-status creation is now reactive (work.completed\n // event). This function is kept as a thin safety net that queries the\n // agents table instead of scanning directories.\n const statuses = loadReviewStatuses();\n const agents = listAllAgents();\n\n for (const agent of agents) {\n if (!agent.id.startsWith('agent-')) continue;\n const issueId = agent.id.replace('agent-', '').toUpperCase();\n if (statuses[issueId]) continue;\n\n const completedFile = join(AGENTS_DIR, agent.id, 'completed');\n const processedFile = join(AGENTS_DIR, agent.id, 'completed.processed');\n if (!existsSync(completedFile) && !existsSync(processedFile)) continue;\n\n const rowCreated = await handleWorkCompleted(issueId);\n actions.push(...rowCreated);\n\n // PAN-1496: if the issue is closed, reap the stale markers.\n try {\n if (await isIssueClosed(issueId)) {\n try { if (existsSync(completedFile)) rmSync(completedFile); } catch { /* best-effort; PAN-3357: not a dir removal */ }\n try { if (existsSync(processedFile)) rmSync(processedFile); } catch { /* best-effort; PAN-3357: not a dir removal */ }\n actions.push(`Reaped stale completion markers for CLOSED ${issueId} (no review re-dispatch)`);\n continue;\n }\n } catch (closedErr) {\n console.warn(`[deacon] checkMissingReviewStatuses closed-check failed for ${issueId}:`, closedErr);\n }\n\n const resolved = resolveProjectFromIssueSync(issueId);\n const issueLower = issueId.toLowerCase();\n const workspace = findWorkspacePath(resolved?.projectPath ?? '', issueLower);\n if (!resolved || !workspace) {\n actions.push(`Skipped missing-status review for ${issueId}: ${!resolved ? 'no project configured' : 'workspace unavailable'}`);\n continue;\n }\n\n if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('review', issueId))) {\n actions.push(`Deferred missing-status review for ${issueId} — advancing-role concurrency ceiling reached`);\n continue;\n }\n\n try {\n const { spawnReviewRoleForIssue } = await import('./review-agent.js');\n await Effect.runPromise(spawnReviewRoleForIssue({\n issueId,\n workspace,\n branch: inferBranchFromWorkspace(workspace, issueLower),\n }));\n recordDeaconNudge({\n patrol: 'checkMissingReviewStatuses',\n issueId,\n action: 'auto-triggered review (missing status entry)',\n reason: 'work agent has a completion marker but no review was dispatched — the reactive work→review handoff (work.completed → in_review) never created/dispatched review',\n });\n actions.push(`Auto-triggered review for ${issueId} (missing status entry)`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n actions.push(`Failed to auto-trigger review for ${issueId}: ${msg}`);\n console.error(`[deacon] Failed to auto-trigger review for ${issueId}:`, msg);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking missing review statuses:', msg);\n }\n\n return actions;\n}\n","import { existsSync, readdirSync, readFileSync, statSync } from 'fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport { basename, join } from 'path';\nimport { Effect } from 'effect';\nimport { getAgentRuntimeStateSync, getAgentStateSync, listRunningAgents } from '../agents.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport type { HeadAnchor } from '../git-utils.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { loadReviewStatuses, setReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { getAllProjectSpecialistStatuses, getTmuxSessionName } from './specialists.js';\nimport { isPaneDead, sessionExistsSync } from '../tmux.js';\nimport { findWorkspacePath } from '../lifecycle/archive-planning.js';\nimport { evaluateReviewConvoyLiveness, reviewTimestampMs } from './review-convoy-liveness.js';\nimport { convergeRowFromVerdictOfRecord } from './verdict-restore.js';\nimport { recordReviewVerdict } from './review-verdict-writer.js';\nimport { deliverReviewVerdictFeedback } from './review-verdict-feedback.js';\nimport { findVerdictReport, findVerdictReportAsync, parseVerdictReport } from './review-verdict-report.js';\n\n// ============================================================================\n// Stuck review detection (PAN-733)\n// ============================================================================\n\n/**\n * Detect issues stuck in `reviewing` status with no active review session.\n *\n * When `spawnReviewRoleForIssue` sets `reviewing` + `reviewSpawnedAt` but the\n * spawn crashes or the review agent exits without updating status, the issue\n * can remain in `reviewing` forever. This check uses `reviewSpawnedAt` as a\n * heartbeat: if it's >30 minutes old and no review session is active, reset\n * to `pending` so deacon can retry dispatch on the next patrol.\n *\n * Guards:\n * - Only fires when reviewStatus === 'reviewing' AND reviewSpawnedAt is set\n * - Only resets if no active review session exists for the issue\n * - 30-minute threshold avoids resetting legitimate long-running reviews\n */\nexport async function checkStuckReviewing(): Promise<string[]> {\n const actions: string[] = [];\n const REVIEW_STUCK_THRESHOLD_MS = 30 * 60 * 1000; // 30 minutes\n\n try {\n const { loadReviewStatuses, setReviewStatusSync } = await import('../review-status.js');\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n // Build set of issues with active review sessions\n const activeReviewIssues = new Set<string>();\n const projectStatuses = await getAllProjectSpecialistStatuses();\n for (const projSpec of projectStatuses) {\n if (!projSpec.isRunning) continue;\n const rState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n if (rState?.state === 'active' && rState.currentIssue && projSpec.specialistType === 'review-agent') {\n activeReviewIssues.add(rState.currentIssue.toUpperCase());\n }\n }\n // Also check global review-agent\n const globalReviewSession = getTmuxSessionName('review-agent');\n if (sessionExistsSync(globalReviewSession)) {\n const rState = getAgentRuntimeStateSync(globalReviewSession);\n if (rState?.state === 'active' && rState.currentIssue) {\n activeReviewIssues.add(rState.currentIssue.toUpperCase());\n }\n }\n // Detect active review runs through the same liveness oracle used by\n // orphan reconciliation. A row latched to `running` is not sufficient.\n let reviewAgents: Parameters<typeof evaluateReviewConvoyLiveness>[2] = [];\n try {\n reviewAgents = await Effect.runPromise(listRunningAgents());\n } catch {\n // Non-fatal: fall back to specialist-only detection\n }\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'reviewing') continue;\n if (!status.reviewSpawnedAt) continue;\n const convoy = evaluateReviewConvoyLiveness(issueId, status, reviewAgents, now);\n if (activeReviewIssues.has(issueId.toUpperCase()) || convoy.active) continue;\n\n const spawnedAt = reviewTimestampMs(status.reviewSpawnedAt);\n if (!Number.isFinite(spawnedAt)) continue;\n if (now - spawnedAt < REVIEW_STUCK_THRESHOLD_MS) continue;\n\n setReviewStatusSync(issueId, {\n reviewStatus: 'pending',\n reviewNotes: `Review reset by deacon: no active review session after ${Math.round((now - spawnedAt) / 60000)}min`,\n });\n const msg = `Reset stuck reviewing status for ${issueId} (no active session for ${Math.round((now - spawnedAt) / 60000)}min)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking stuck reviewing statuses:', msg);\n }\n\n return actions;\n}\n\n// ============================================================================\n// Completed-but-unsignaled review detection\n// ============================================================================\n\n/**\n * Detect review specialists that wrote synthesis.md but never called\n * `pan specialists done review`. The review role prompt instructs the agent\n * to signal completion after writing the synthesis, but agents occasionally\n * forget (idle at prompt with reports already on disk). This leaves the\n * issue stuck in `reviewing` status forever.\n *\n * Recovery: read the synthesis verdict and nudge the review agent to signal\n * completion. If the agent session is dead, we auto-complete by updating the\n * review status directly so the pipeline isn't permanently blocked.\n *\n * Guards:\n * - Only fires when reviewStatus === 'reviewing'\n * - synthesis.md must exist and be >5 min old (gives the agent time to signal)\n * - Only nudges once per review cycle (tracked by runId in the review dir)\n */\nconst unsignaledReviewNudges = new Map<string, number>();\nconst pendingVerdictNudges = new Map<string, number>();\n\ntype ReviewRunContext = {\n generatedAt?: string;\n headSha?: string;\n repos?: Array<{\n repoKey?: string;\n headSha?: string;\n }>;\n};\n\nfunction reviewAnchorFromContext(context: ReviewRunContext): string | undefined {\n if (context.repos) {\n if (context.repos.length === 0) return undefined;\n const heads: string[] = [];\n for (const repo of context.repos) {\n if (!repo.repoKey?.trim() || !repo.headSha?.trim()) return undefined;\n heads.push(`${repo.repoKey.trim()}@${repo.headSha.trim()}`);\n }\n return heads.join(' ');\n }\n return context.headSha?.trim() || undefined;\n}\n\nfunction shellQuote(value: string): string {\n return `'${value.replaceAll(\"'\", \"'\\\\''\")}'`;\n}\n\nexport function buildReviewCompletionCommand(\n issueId: string,\n verdict: 'passed' | 'blocked' | 'failed',\n notes: string,\n runId: string,\n): string {\n let command = `pan admin specialists done review ${shellQuote(issueId)} --status ${shellQuote(verdict)}`;\n if (verdict === 'blocked' || verdict === 'failed') {\n command += ` --notes ${shellQuote(notes)}`;\n }\n return `${command} --run-id ${shellQuote(runId)}`;\n}\n\nfunction isReviewContextForActiveRun(\n context: ReviewRunContext,\n status: Pick<ReviewStatus, 'reviewSpawnedAt' | 'lastVerifiedCommit'>,\n reportMtimeMs: number,\n): boolean {\n if (!status.reviewSpawnedAt) return true;\n\n const spawnedAtMs = reviewTimestampMs(status.reviewSpawnedAt);\n if (!Number.isFinite(spawnedAtMs)) return true;\n if (reportMtimeMs < spawnedAtMs) return false;\n\n if (context.generatedAt) {\n const generatedAtMs = Date.parse(context.generatedAt);\n if (Number.isFinite(generatedAtMs) && generatedAtMs < spawnedAtMs) return false;\n }\n\n const reviewedAnchor = reviewAnchorFromContext(context);\n return !status.lastVerifiedCommit\n || !reviewedAnchor\n || reviewedAnchor === status.lastVerifiedCommit;\n}\n\nexport function isSynthesisForActiveReviewRun(\n dirPath: string,\n status: Pick<ReviewStatus, 'reviewSpawnedAt' | 'lastVerifiedCommit'>,\n synthesisMtimeMs: number,\n): boolean {\n if (!status.reviewSpawnedAt) return true;\n const contextPath = join(dirPath, 'context.json');\n if (!existsSync(contextPath)) return false;\n\n try {\n const context = JSON.parse(readFileSync(contextPath, 'utf8')) as ReviewRunContext;\n return isReviewContextForActiveRun(context, status, synthesisMtimeMs);\n } catch {\n return false;\n }\n}\n\n/**\n * Apply settled review verdicts that reached disk after their runtime status\n * was reset to pending. The live review parent gets one chance to signal the\n * verdict itself; a dead parent, or one still unsignaled after 30 minutes, is\n * reconciled directly through the review-status and feedback write doors.\n */\nexport async function reconcileUnappliedReviewVerdicts(): Promise<string[]> {\n const actions: string[] = [];\n const VERDICT_SETTLE_MS = 5 * 60 * 1000;\n const NUDGE_GRACE_MS = 30 * 60 * 1000;\n\n try {\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if ((status.reviewStatus !== 'pending' && status.reviewStatus !== 'reviewing') || !status.reviewSpawnedAt) continue;\n\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const wsPath = findWorkspacePath(resolved.projectPath, issueId.toLowerCase());\n if (!wsPath) continue;\n\n const reviewBaseDir = join(wsPath, '.pan', 'review');\n let reviewEntries: string[];\n try {\n reviewEntries = await readdir(reviewBaseDir);\n } catch {\n continue;\n }\n\n let latestDir: string | null = null;\n let latestReport: Awaited<ReturnType<typeof findVerdictReportAsync>> = null;\n let latestContext: ReviewRunContext | null = null;\n let latestMtime = 0;\n for (const entry of reviewEntries) {\n if (!entry.startsWith(`agent-${issueId.toLowerCase()}-review`)) continue;\n const dirPath = join(reviewBaseDir, entry);\n try {\n const report = await findVerdictReportAsync(dirPath);\n if (!report) continue;\n const [reportStat, contextContent] = await Promise.all([\n stat(report.path),\n readFile(join(dirPath, 'context.json'), 'utf8'),\n ]);\n const context = JSON.parse(contextContent) as ReviewRunContext;\n if (!isReviewContextForActiveRun(context, status, reportStat.mtimeMs)) continue;\n if (reportStat.mtimeMs > latestMtime) {\n latestDir = dirPath;\n latestReport = report;\n latestContext = context;\n latestMtime = reportStat.mtimeMs;\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Error reading review run ${dirPath}:`, msg);\n }\n }\n if (!latestDir || !latestReport || !latestContext) continue;\n if (now - latestMtime < VERDICT_SETTLE_MS) continue;\n\n const requestedAtMs = status.reviewRequestedAt ? Date.parse(status.reviewRequestedAt) : Number.NaN;\n if (Number.isFinite(requestedAtMs) && requestedAtMs > latestMtime) continue;\n\n let currentHead: HeadAnchor | undefined;\n let reviewedHead: HeadAnchor | undefined;\n try {\n const { rehydrateHeadAnchor, snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n currentHead = await snapshotWorkspaceHeadsPromise(issueId, wsPath);\n const persistedAnchor = reviewAnchorFromContext(latestContext);\n reviewedHead = persistedAnchor ? rehydrateHeadAnchor(persistedAnchor) : undefined;\n } catch {\n continue;\n }\n if (!reviewedHead || currentHead !== reviewedHead) continue;\n\n let parsed: ReturnType<typeof parseVerdictReport>;\n try {\n parsed = parseVerdictReport(await readFile(latestReport.path, 'utf8'));\n } catch {\n continue;\n }\n if (!parsed) continue;\n\n const reviewSession = `agent-${issueId.toLowerCase()}-review`;\n const sessionAlive = sessionExistsSync(reviewSession);\n const paneDead = sessionAlive ? await Effect.runPromise(isPaneDead(reviewSession)).catch(() => true) : true;\n if (sessionAlive && !paneDead) {\n const lastNudged = pendingVerdictNudges.get(latestDir);\n if (!lastNudged) {\n const notes = parsed.topBlocker || `See ${latestReport.filename}`;\n const cmd = buildReviewCompletionCommand(\n issueId,\n parsed.verdict,\n notes,\n basename(latestDir),\n );\n const nudge = `Your review verdict is already written on disk but review status is still pending. Your ONLY remaining task is to execute this Bash command immediately — do not analyze, do not summarize, do not ask questions, just run it:\\n\\n${cmd}\\n\\nRun this command NOW. Do not write any other response before executing it.`;\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(reviewSession, nudge);\n pendingVerdictNudges.set(latestDir, now);\n const action = `Nudged ${reviewSession} to apply pending ${parsed.verdict} verdict from ${latestReport.filename}`;\n actions.push(action);\n console.log(`[deacon] ${action}`);\n } catch (err: unknown) {\n console.error(`[deacon] Failed to nudge ${reviewSession}:`, err instanceof Error ? err.message : String(err));\n }\n continue;\n }\n if (now - lastNudged < NUDGE_GRACE_MS) continue;\n }\n\n const attribution = `applied by deacon sweep from on-disk ${latestReport.filename}`;\n const reviewNotes = parsed.topBlocker\n ? `${parsed.topBlocker} — ${attribution}`\n : `Review ${parsed.verdict} ${attribution}`;\n const convergence = await convergeRowFromVerdictOfRecord(issueId, {\n runId: basename(latestDir),\n workspacePath: wsPath,\n writer: 'unsignaled-recovery',\n notes: reviewNotes,\n });\n if (!convergence.converged) continue;\n\n if (parsed.verdict === 'blocked') {\n try {\n await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId,\n verdict: 'blocked',\n notes: parsed.topBlocker || reviewNotes,\n workspacePath: wsPath,\n prUrl: status.prUrl,\n runId: basename(latestDir),\n }));\n } catch (err: unknown) {\n console.error(`[deacon] Failed to deliver reconciled review verdict for ${issueId}:`, err instanceof Error ? err.message : String(err));\n }\n }\n\n pendingVerdictNudges.delete(latestDir);\n const action = `reconcileUnappliedReviewVerdicts deacon sweep applied ${parsed.verdict} for ${issueId} from ${latestReport.filename}`;\n actions.push(action);\n emitActivityEntrySync({ source: 'cloister', level: 'warn', message: action, issueId });\n console.log(`[deacon] ${action}`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Error reconciling unapplied review verdict for ${issueId}:`, msg);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error reconciling unapplied review verdicts:', msg);\n }\n\n return actions;\n}\n\nexport async function checkCompletedButUnsignaledReviews(): Promise<string[]> {\n const actions: string[] = [];\n const SYNTHESIS_SETTLE_MS = 5 * 60 * 1000; // 5 minutes\n\n try {\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'reviewing') continue;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const wsPath = findWorkspacePath(resolved.projectPath, issueId.toLowerCase());\n if (!wsPath) continue;\n\n const reviewBaseDir = join(wsPath, '.pan', 'review');\n if (!existsSync(reviewBaseDir)) continue;\n\n // Find the most recently modified review run directory\n let latestDir: string | null = null;\n let latestReport: ReturnType<typeof findVerdictReport> = null;\n let latestMtime = 0;\n for (const entry of readdirSync(reviewBaseDir)) {\n if (!entry.startsWith(`agent-${issueId.toLowerCase()}-review`)) continue;\n const dirPath = join(reviewBaseDir, entry);\n const report = findVerdictReport(dirPath);\n if (!report) continue;\n const mtime = statSync(report.path).mtimeMs;\n if (!isSynthesisForActiveReviewRun(dirPath, status, mtime)) continue;\n if (mtime > latestMtime) {\n latestMtime = mtime;\n latestDir = dirPath;\n latestReport = report;\n }\n }\n if (!latestDir || !latestReport) continue;\n\n // Wait for synthesis to settle before intervening\n if (now - latestMtime < SYNTHESIS_SETTLE_MS) continue;\n\n // Deduplicate: only nudge once per directory (one review cycle)\n const lastNudged = unsignaledReviewNudges.get(latestDir);\n if (lastNudged && now - lastNudged < 30 * 60 * 1000) continue;\n\n const reviewSession = `agent-${issueId.toLowerCase()}-review`;\n const sessionAlive = sessionExistsSync(reviewSession);\n const paneDead = sessionAlive ? await Effect.runPromise(isPaneDead(reviewSession)).catch(() => true) : true;\n const activeReviewState = sessionAlive && !paneDead ? getAgentStateSync(reviewSession) : null;\n if (activeReviewState?.reviewRunId && latestDir !== join(reviewBaseDir, activeReviewState.reviewRunId)) {\n continue;\n }\n\n let parsed: ReturnType<typeof parseVerdictReport>;\n try {\n parsed = parseVerdictReport(readFileSync(latestReport.path, 'utf8'));\n } catch {\n continue;\n }\n if (!parsed) continue;\n const { verdict, topBlocker } = parsed;\n\n if (sessionAlive && !paneDead) {\n // If we already nudged once and 30+ min have passed with no signal,\n // the agent is unresponsive — auto-complete so the pipeline isn't blocked.\n if (lastNudged) {\n const notes = topBlocker || `Review auto-completed by deacon: ${verdict} (agent alive but unresponsive after nudge, ${latestReport.filename} exists)`;\n const outcome = await recordReviewVerdict(issueId, { verdict, notes, writer: 'unsignaled-recovery' });\n if (!outcome.landed) { actions.push(`Auto-complete for ${issueId} not recorded (${outcome.reason})`); continue; }\n actions.push(`Auto-completed review for ${issueId}: ${verdict} (alive but unresponsive after nudge, ${latestReport.filename} written ${Math.round((now - latestMtime) / 60000)}min ago)`);\n console.log(`[deacon] Auto-completed review for ${issueId}: ${verdict} (alive but unresponsive after nudge)`);\n continue;\n }\n\n // Agent is alive but idle — nudge it to signal completion\n const cmd = buildReviewCompletionCommand(\n issueId,\n verdict,\n topBlocker || `See ${latestReport.filename}`,\n basename(latestDir),\n );\n const nudge = `Your review verdict in ${latestReport.filename} is already written and saved. Your ONLY remaining task is to execute this Bash command immediately — do not analyze, do not summarize, do not ask questions, just run it:\\n\\n${cmd}\\n\\nRun this command NOW. Do not write any other response before executing it.`;\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(reviewSession, nudge);\n unsignaledReviewNudges.set(latestDir, now);\n actions.push(`Nudged ${reviewSession} to signal ${verdict} (${latestReport.filename} written ${Math.round((now - latestMtime) / 60000)}min ago)`);\n console.log(`[deacon] Nudged ${reviewSession} to signal ${verdict}`);\n } catch (err: unknown) {\n console.error(`[deacon] Failed to nudge ${reviewSession}:`, err instanceof Error ? err.message : String(err));\n }\n } else {\n // Session is dead — auto-complete so the pipeline isn't blocked\n const notes = topBlocker || `Review auto-completed by deacon: ${verdict} (agent dead, ${latestReport.filename} exists)`;\n const outcome = await recordReviewVerdict(issueId, { verdict, notes, writer: 'unsignaled-recovery' });\n if (!outcome.landed) { actions.push(`Auto-complete for ${issueId} not recorded (${outcome.reason})`); continue; }\n actions.push(`Auto-completed review for ${issueId}: ${verdict} (dead agent, ${latestReport.filename} written ${Math.round((now - latestMtime) / 60000)}min ago)`);\n console.log(`[deacon] Auto-completed review for ${issueId}: ${verdict} (dead agent)`);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking completed-but-unsignaled reviews:', msg);\n }\n\n return actions;\n}\n","import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'fs';\nimport { dirname, join } from 'path';\nimport { Effect } from 'effect';\nimport { isContextOverflowTail } from '../context-overflow.js';\nimport { getAgentRuntimeStateSync, getAgentState, getAgentStateSync, saveAgentStateSync, type AgentState } from '../agents.js';\nimport { deliverReviewVerdictFeedback } from './review-verdict-feedback.js';\nimport { findVerdictReport } from './review-verdict-report.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport type { HeadAnchor } from '../git-utils.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { REVIEW_SUB_ROLES, type ReviewSubRole } from './review-monitor.js';\nimport { capturePane, isPaneDead, killSession, listSessionNames, listSessions, sessionExists, sessionExistsSync } from '../tmux.js';\nimport { applyCodexAuthBurnFlag, isCodexAuthRouted, paneShowsCodexAuthBurn } from '../codex-auth.js';\nimport { extractMarkdownSection, findBlockingFindings } from '../review-findings.js';\nimport { getAgentEffectiveLastActivityMs } from './agent-idle.js';\n\nconst REVIEWER_IDLE_FAILURE_MS = 3 * 60 * 1000;\nconst REVIEW_REPORTS_PRESENT_NUDGE_COOLDOWN_MS = 60 * 1000;\nconst reviewReportsPresentNudges = new Map<string, number>();\n\ntype DeaconReviewSynthesis = {\n verdict: 'passed' | 'blocked';\n topBlocker: string;\n body: string;\n};\n\nexport function synthesizeReviewFromReports(opts: {\n issueId: string;\n reviewDir: string;\n reports: ReadonlyArray<{ subRole: ReviewSubRole; path: string; body: string }>;\n}): DeaconReviewSynthesis {\n const blockers = opts.reports.flatMap(report =>\n findBlockingFindings(report.body).map(title => ({\n subRole: report.subRole,\n title,\n path: report.path,\n })),\n );\n const verdict: 'passed' | 'blocked' = blockers.length > 0 ? 'blocked' : 'passed';\n const topBlocker = blockers[0] ? `[${blockers[0].subRole}] ${blockers[0].title}` : '';\n const blockerSection = blockers.length > 0\n ? blockers.map(blocker => `### [${blocker.subRole}] ${blocker.title}\\nSource: ${blocker.path}`).join('\\n\\n')\n : 'None';\n const cleanSubRoles = opts.reports\n .filter(report => !blockers.some(blocker => blocker.subRole === report.subRole))\n .map(report => `- ${report.subRole}`)\n .join('\\n') || 'None';\n const convoyRows = opts.reports.map(report => {\n const blockingCount = findBlockingFindings(report.body).length;\n return `| ${report.subRole} | ready | ${report.path} | ${blockingCount} |`;\n }).join('\\n');\n\n const body = [\n `# Review Synthesis — ${opts.issueId} — ${new Date().toISOString()}`,\n '',\n `## Verdict: ${verdict === 'passed' ? 'APPROVED' : `CHANGES REQUESTED — ${topBlocker}`}`,\n '',\n '## Context',\n '- Generated by Deacon fallback from completed on-disk reviewer reports.',\n `- Review directory: ${opts.reviewDir}`,\n '',\n '## Convoy Status',\n '| Sub-role | Signal | Output | Blocking findings |',\n '| --- | --- | --- | --- |',\n convoyRows,\n '',\n '## Blocking Findings',\n blockerSection,\n '',\n '## Non-blocking Findings',\n 'See individual reviewer reports.',\n '',\n '## Clean Sub-roles',\n cleanSubRoles,\n '',\n ].join('\\n');\n\n return { verdict, topBlocker, body };\n}\n\n/**\n * Retry a convoy reviewer that has stopped making progress without writing output.\n *\n * The retry covers terminal API errors, warm-resume kickoffs that never start a\n * turn, and elapsed reviewer deadlines. It preserves the same run/output wiring\n * and persists one shared attempt count so every trigger is bounded together.\n */\nasync function retryReviewer(state: AgentState, agentId: string, trigger: string): Promise<boolean> {\n const outputPath = state.reviewOutputPath;\n const attempt = (state.reviewRetryAttempt ?? 0) + 1;\n logDeaconEventSync(`monitorReviewConvoySignals: ${agentId} ${trigger} — retrying reviewer (attempt ${attempt})`);\n\n try {\n await Effect.runPromise(killSession(agentId));\n } catch (err) {\n logDeaconEventSync(`monitorReviewConvoySignals: failed to kill ${agentId} before respawn: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n if (!outputPath || !state.reviewSubRole || !state.reviewRunId) {\n logDeaconEventSync(`monitorReviewConvoySignals: cannot respawn ${agentId} — missing output/run/subRole`);\n return false;\n }\n\n try {\n const { spawnReviewSubRoleForIssue } = await import('./review-agent.js');\n const contextManifestPath = join(dirname(outputPath), 'context.json');\n const result = await Effect.runPromise(\n spawnReviewSubRoleForIssue({\n issueId: state.issueId,\n workspace: state.workspace,\n subRole: state.reviewSubRole as ReviewSubRole,\n runId: state.reviewRunId,\n outputPath,\n contextManifestPath,\n synthesisAgentId: state.reviewSynthesisAgentId,\n model: state.model,\n harness: state.harness,\n allowHost: state.hostOverride ?? false,\n }),\n );\n\n if (!result.success) {\n logDeaconEventSync(`monitorReviewConvoySignals: respawn of ${agentId} failed: ${result.error ?? result.message}`);\n return false;\n }\n\n const freshState = getAgentStateSync(agentId);\n if (freshState) {\n freshState.reviewRetryAttempt = attempt;\n saveAgentStateSync(freshState);\n }\n\n logDeaconEventSync(`monitorReviewConvoySignals: respawned ${agentId} as ${result.sessionId}`);\n return true;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`monitorReviewConvoySignals: respawn of ${agentId} threw: ${errMsg}`);\n return false;\n }\n}\n\nexport async function nudgeSynthesisForCompleteReviewerReports(states: readonly AgentState[]): Promise<string[]> {\n const actions: string[] = [];\n const now = Date.now();\n const synthesisStates = states.filter(state =>\n state.role === 'review'\n && !state.reviewSubRole\n && state.reviewRunId\n && state.workspace\n && state.issueId\n );\n\n for (const state of synthesisStates) {\n const status = getReviewStatusSync(state.issueId);\n if (!status || status.reviewStatus !== 'reviewing') continue;\n\n const reviewDir = join(state.workspace, '.pan', 'review', state.reviewRunId!);\n // A synthesis.md on disk does NOT mean the verdict landed: the parent can\n // die between writing the file and the verdict write door, wedging the issue\n // in 'reviewing' forever (observed 3× on 2026-07-13). Since reviewStatus\n // is still 'reviewing' here (guard above), fall through and land the\n // verdict deterministically instead of skipping.\n const synthesisAlreadyWritten = findVerdictReport(reviewDir) !== null;\n\n const startedMs = Date.parse(state.startedAt);\n const readyLines: string[] = [];\n const reports: Array<{ subRole: ReviewSubRole; path: string; body: string }> = [];\n let allReportsPresent = true;\n for (const subRole of REVIEW_SUB_ROLES) {\n const outputPath = join(reviewDir, `${subRole}.md`);\n if (!existsSync(outputPath)) {\n allReportsPresent = false;\n break;\n }\n try {\n const outputMtimeMs = statSync(outputPath).mtimeMs;\n if (Number.isFinite(startedMs) && outputMtimeMs < startedMs) {\n allReportsPresent = false;\n break;\n }\n } catch {\n allReportsPresent = false;\n break;\n }\n try {\n reports.push({ subRole, path: outputPath, body: readFileSync(outputPath, 'utf8') });\n } catch {\n allReportsPresent = false;\n break;\n }\n readyLines.push(`REVIEWER_READY ${subRole} ${outputPath}`);\n }\n if (!allReportsPresent) continue;\n\n const nudgeKey = `${state.id}:${state.reviewRunId}`;\n const lastNudge = reviewReportsPresentNudges.get(nudgeKey);\n if (lastNudge && now - lastNudge < REVIEW_REPORTS_PRESENT_NUDGE_COOLDOWN_MS) continue;\n\n const sessionAlive = await Effect.runPromise(sessionExists(state.id)).catch(() => false);\n const paneDead = sessionAlive ? await Effect.runPromise(isPaneDead(state.id)).catch(() => true) : true;\n if (synthesisAlreadyWritten || lastNudge || !sessionAlive || paneDead) {\n const synthesis = synthesizeReviewFromReports({\n issueId: state.issueId,\n reviewDir,\n reports,\n });\n writeFileSync(join(reviewDir, 'synthesis.md'), synthesis.body);\n let fallbackHead: HeadAnchor | undefined;\n try {\n // PAN-2948: polyrepo-aware — snapshots sub-repo heads, not the wrapper.\n const { snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n fallbackHead = await snapshotWorkspaceHeadsPromise(state.issueId, state.workspace);\n } catch { /* non-fatal — the verdict write door accepts missing evidence. */ }\n const notes = synthesis.topBlocker || 'Review approved by Deacon fallback from completed reviewer reports';\n const { recordReviewVerdict } = await import('./review-verdict-writer.js');\n const outcome = await recordReviewVerdict(state.issueId, {\n verdict: synthesis.verdict,\n notes,\n evidenceHead: fallbackHead,\n ...(synthesis.verdict === 'blocked' && fallbackHead ? { extra: { reviewedAtCommit: fallbackHead } } : {}),\n runId: state.reviewRunId,\n writer: 'fallback',\n });\n if (!outcome.landed) {\n // The verdict of record stays on disk in synthesis.md and the parent stays\n // alive for the PAN-2584 liveness deadline — killing it here would strand a\n // rejected verdict with no session left to re-signal it.\n const rejected = `Deacon fallback verdict for ${state.issueId} was not recorded (${outcome.reason}); left parent ${state.id} alive`;\n actions.push(rejected);\n logDeaconEventSync(`monitorReviewConvoySignals: ${rejected}`);\n continue;\n }\n if (synthesis.verdict === 'blocked') {\n await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId: state.issueId,\n verdict: 'blocked',\n notes,\n workspacePath: state.workspace,\n prUrl: status.prUrl,\n runId: state.reviewRunId,\n }));\n }\n if (sessionAlive) {\n await Effect.runPromise(killSession(state.id)).catch((err) => {\n const errMsg = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`monitorReviewConvoySignals: failed to kill synthesized parent ${state.id}: ${errMsg}`);\n });\n }\n const action = `Synthesized review for ${state.issueId} from ${REVIEW_SUB_ROLES.length} reviewer reports: ${synthesis.verdict}`;\n actions.push(action);\n logDeaconEventSync(`monitorReviewConvoySignals: ${action}`);\n continue;\n }\n\n const message = [\n 'Deacon fallback: all reviewer report files for this review run are present on disk, but synthesis has not been written yet.',\n 'If you have not already synthesized this run, treat these as the missing terminal reviewer signals and proceed now:',\n '',\n ...readyLines,\n '',\n 'Read the four reports, write synthesis.md, then signal review status exactly as roles/review.md instructs.',\n ].join('\\n');\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(state.id, message);\n reviewReportsPresentNudges.set(nudgeKey, now);\n const action = `Nudged ${state.id} to synthesize from ${REVIEW_SUB_ROLES.length} reviewer reports`;\n actions.push(action);\n logDeaconEventSync(`monitorReviewConvoySignals: ${action}`);\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`monitorReviewConvoySignals: failed to nudge ${state.id} for complete reviewer reports: ${errMsg}`);\n }\n }\n\n return actions;\n}\n\nexport async function monitorReviewConvoySignals(): Promise<string[]> {\n const actions: string[] = [];\n if (!existsSync(AGENTS_DIR)) return actions;\n\n let agentDirs: string[];\n try {\n agentDirs = readdirSync(AGENTS_DIR).filter(d => d.startsWith('agent-'));\n } catch {\n return actions;\n }\n\n // PAN-3549: session-created index for the stale-deadline guard below. One\n // probe up front; if it fails the guard stays off (legacy behavior).\n let sessionCreatedMsByName = new Map<string, number>();\n try {\n const sessions = await Effect.runPromise(listSessions());\n sessionCreatedMsByName = new Map(sessions.map((s) => [s.name, s.created.getTime()]));\n } catch { /* liveness probe failure — guard disabled this sweep */ }\n\n const reviewStates: AgentState[] = [];\n\n for (const agentId of agentDirs) {\n // PAN-1908: use the agents table; fall back to state.json only for legacy dirs.\n const state = await Effect.runPromise(\n getAgentState(agentId).pipe(Effect.catch(() => Effect.succeed(null))),\n ).then((dbState) => dbState ?? getAgentStateSync(agentId));\n if (!state) continue;\n if (state.role !== 'review') continue;\n reviewStates.push(state);\n if (!state.reviewSubRole || !state.reviewSynthesisAgentId) continue;\n if (state.reviewMonitorSignaled) continue;\n\n const startedMs = Date.parse(state.startedAt);\n\n // PAN-977: the review sub-role launcher now owns the convoy signal — it\n // signals synthesis on process exit (REVIEWER_READY/FAILED/TIMEOUT) and\n // touches `reviewer-signaled`. When that marker is newer than this run's\n // start, the launcher already signaled and Deacon must not double-signal.\n // Deacon stays the rare backup for the case where the launcher's bash\n // process was SIGKILLed before it could signal.\n const signalMarker = join(AGENTS_DIR, agentId, 'reviewer-signaled');\n if (existsSync(signalMarker)) {\n try {\n if (Number.isFinite(startedMs) && statSync(signalMarker).mtimeMs >= startedMs) continue;\n } catch { /* unreadable marker — fall through to backup signaling */ }\n }\n\n const outputPath = state.reviewOutputPath;\n let outputWrittenForThisRun = false;\n if (outputPath && existsSync(outputPath)) {\n try {\n const outputMtimeMs = statSync(outputPath).mtimeMs;\n outputWrittenForThisRun = Number.isFinite(startedMs) && outputMtimeMs >= startedMs;\n } catch {\n outputWrittenForThisRun = false;\n }\n }\n const deadlineMs = state.reviewDeadlineAt ? Date.parse(state.reviewDeadlineAt) : Number.NaN;\n\n // The synthesis agent must be alive to receive any signal.\n const synthesisAlive = await Effect.runPromise(sessionExists(state.reviewSynthesisAgentId));\n if (!synthesisAlive) continue;\n\n // PAN-1557: convoy reviewers are interactive now — there is no headless\n // launcher process / pid file. Liveness is the reviewer's own tmux session:\n // while it's alive the reviewer is still working (or idling attachably after\n // writing its report); once it's gone the reviewer has crashed or been\n // reaped. The Stop-hook is the primary signal (touches reviewer-signaled +\n // delivers REVIEWER_READY); Deacon is the backup for when that didn't fire.\n const reviewerSessionAlive = (await Effect.runPromise(sessionExists(agentId)).catch(() => false))\n && !(await Effect.runPromise(isPaneDead(agentId)).catch(() => true));\n\n // PAN-3549: a deadline older than the lane's CURRENT tmux session belongs\n // to a dead attempt — run ids are content-derived (head8), so a re-dispatch\n // on the same HEAD inherits the previous attempt's monitor state, and\n // timing out on it kills the fresh lane mid-resume (retryReviewer kills the\n // live session first) while the stale signal replays into the resumed\n // parent as a phantom \"infrastructure failure\" verdict (PAN-3511,\n // 2026-08-04). The live session is the oracle: clear the stale monitor\n // state through the state door and let the new attempt run.\n if (reviewerSessionAlive && Number.isFinite(deadlineMs)) {\n const sessionCreatedMs = sessionCreatedMsByName.get(agentId);\n if (sessionCreatedMs !== undefined && sessionCreatedMs > deadlineMs) {\n const staleDeadline = state.reviewDeadlineAt;\n delete state.reviewMonitorSignaled;\n delete state.reviewRetryAttempt;\n delete state.reviewDeadlineAt;\n try {\n saveAgentStateSync(state);\n } catch { /* best-effort — the next sweep re-evaluates */ }\n logDeaconEventSync(`monitorReviewConvoySignals: ${agentId} deadline ${staleDeadline} predates its live session — cleared stale monitor state (PAN-3549)`);\n continue;\n }\n }\n\n // Startup grace: give the session time to come up before \"gone\" reads as death.\n const REVIEWER_STARTUP_GRACE_MS = 90_000;\n const withinStartupGrace = Number.isFinite(startedMs) && (Date.now() - startedMs) < REVIEWER_STARTUP_GRACE_MS;\n\n // PAN-1806/PAN-3375: detect both terminal API errors that return to an idle\n // prompt and warm-resume kickoffs that never start a turn. resumeAgent marks\n // the runtime active after delivery, so a swallowed kickoff otherwise leaves\n // a stale active mirror forever. Any real reviewer work refreshes lastActivity;\n // retry once when either mirror is stale and no report exists.\n const runtimeState = getAgentRuntimeStateSync(agentId);\n const runtimeActivityMs = getAgentEffectiveLastActivityMs(agentId)\n ?? (runtimeState?.lastActivity ? Date.parse(runtimeState.lastActivity) : Number.NaN);\n const runtimeActivityAgeMs = Number.isFinite(runtimeActivityMs)\n ? Date.now() - runtimeActivityMs\n : 0;\n const idleAndNoOutput = reviewerSessionAlive\n && !outputWrittenForThisRun\n && runtimeState?.state === 'idle'\n && runtimeActivityAgeMs > REVIEWER_IDLE_FAILURE_MS;\n const staleActiveAndNoOutput = reviewerSessionAlive\n && !outputWrittenForThisRun\n && runtimeState?.state === 'active'\n && runtimeActivityAgeMs > REVIEWER_IDLE_FAILURE_MS;\n const stalledAndNoOutput = idleAndNoOutput || staleActiveAndNoOutput;\n\n // PAN-1818: context-window overflow is deterministic on the same diff/manifest.\n // Capture the pane tail and fast-fail BEFORE the retry branch so we never\n // burn another cycle on a reviewer that will re-overflow. Check an explicit\n // idle mirror or a stale active mirror whose activity evidence stopped moving.\n let contextOverflowDetected = false;\n if (!outputWrittenForThisRun && (runtimeState?.state === 'idle' || staleActiveAndNoOutput)) {\n const tail = await Effect.runPromise(capturePane(agentId, 100)).catch(() => '');\n contextOverflowDetected = isContextOverflowTail(tail);\n // PAN-2285: a convoy reviewer wedged on a revoked Codex refresh token shows\n // the burn markers in its pane. The idle-no-output path below already fails\n // this reviewer; additionally mark it troubled and persist the burn flag in\n // its agent state (same shape as checkApiErrorAgents) — this monitor runs\n // in the deacon CHILD process, so the flag must reach the dashboard\n // server's banner endpoint and spawn gate through the shared agents table,\n // not module memory.\n //\n // PAN-3528 widened the markers to cover the CLIProxy route, whose 503\n // wrapper text is generic enough to appear in a pane that is not\n // codex-authenticated at all — so only reviewers actually routed through\n // the codex credential family are eligible for the flag.\n const codexRouted = isCodexAuthRouted(state.harness, state.model);\n if (codexRouted && paneShowsCodexAuthBurn(tail) && applyCodexAuthBurnFlag(state)) {\n saveAgentStateSync(state);\n logDeaconEventSync(`review-monitor: ${agentId} marked troubled — Codex refresh token revoked (codex-auth-burned)`);\n }\n }\n\n let signal: 'ready' | 'failed' | 'timeout' | null = null;\n let reason = '';\n if (outputWrittenForThisRun) {\n // Report exists for this run but the Stop-hook didn't signal (no fresh\n // marker) — back it up with READY. Safe: the report is on disk.\n signal = 'ready';\n } else if (contextOverflowDetected) {\n signal = 'failed';\n reason = 'context-window overflow (no retry — deterministic)';\n } else if (stalledAndNoOutput) {\n const attempt = state.reviewRetryAttempt ?? 0;\n const trigger = staleActiveAndNoOutput\n ? 'warm-resumed but produced no activity or output'\n : 'idle with no output';\n if (attempt < 1 && (await retryReviewer(state, agentId, trigger))) {\n continue;\n }\n signal = 'failed';\n reason = staleActiveAndNoOutput\n ? `reviewer warm-resumed but produced no activity or output${attempt >= 1 ? ' (retry exhausted)' : ' (retry failed)'}`\n : `reviewer idle with no output after terminal API error${attempt >= 1 ? ' (retry exhausted)' : ' (retry failed)'}`;\n } else if (reviewerSessionAlive) {\n // Still working or idling attachably. Only intervene if well past the\n // deadline (genuinely wedged). The first elapsed deadline restarts only\n // this lane; the second produces a terminal timeout for synthesis.\n if (Number.isFinite(deadlineMs) && Date.now() >= deadlineMs + REVIEWER_STARTUP_GRACE_MS) {\n const attempt = state.reviewRetryAttempt ?? 0;\n if (attempt < 1 && (await retryReviewer(state, agentId, `still running past deadline ${state.reviewDeadlineAt}`))) {\n continue;\n }\n signal = 'timeout';\n reason = `reviewer still running past deadline ${state.reviewDeadlineAt}${attempt >= 1 ? ' (retry exhausted)' : ' (retry failed)'}`;\n } else {\n continue;\n }\n } else if (withinStartupGrace) {\n // Session not up yet — too early to call it dead.\n continue;\n } else if (Number.isFinite(deadlineMs) && Date.now() >= deadlineMs) {\n const attempt = state.reviewRetryAttempt ?? 0;\n if (attempt < 1 && (await retryReviewer(state, agentId, `exceeded deadline ${state.reviewDeadlineAt}`))) {\n continue;\n }\n signal = 'timeout';\n reason = `reviewer exceeded deadline ${state.reviewDeadlineAt}${attempt >= 1 ? ' (retry exhausted)' : ' (retry failed)'}`;\n } else {\n // Session gone, no report, before deadline → reviewer crashed or exited\n // before writing a report.\n signal = 'failed';\n reason = 'reviewer session ended before writing a report';\n }\n\n if (!signal) continue;\n\n const message = signal === 'ready'\n ? `REVIEWER_READY ${state.reviewSubRole} ${outputPath}`\n : signal === 'timeout'\n ? `REVIEWER_TIMEOUT ${state.reviewSubRole} ${reason}`\n : `REVIEWER_FAILED ${state.reviewSubRole} ${reason}`;\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(state.reviewSynthesisAgentId, message);\n state.reviewMonitorSignaled = signal;\n saveAgentStateSync(state);\n const action = `Signaled ${message} to ${state.reviewSynthesisAgentId}`;\n actions.push(action);\n logDeaconEventSync(`monitorReviewConvoySignals: ${action}`);\n if (signal === 'ready') {\n try {\n const { notifyPipelineSync } = await import('../pipeline-notifier.js');\n notifyPipelineSync({ type: 'reviewer_completed', issueId: state.issueId, role: state.reviewSubRole });\n } catch { /* non-fatal */ }\n } else if (signal === 'timeout') {\n try {\n const { notifyPipelineSync } = await import('../pipeline-notifier.js');\n notifyPipelineSync({\n type: 'reviewer_timed_out',\n issueId: state.issueId,\n role: state.reviewSubRole,\n sessionName: agentId,\n attempt: 1,\n maxRetries: 1,\n willRetry: false,\n });\n } catch { /* non-fatal */ }\n }\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`monitorReviewConvoySignals: failed to signal ${state.reviewSynthesisAgentId} for ${agentId}: ${errMsg}`);\n }\n }\n\n actions.push(...await nudgeSynthesisForCompleteReviewerReports(reviewStates));\n\n return actions;\n}\n\n/**\n * Kill orphaned review sessions whose work agent is no longer running.\n *\n * Covers three naming patterns:\n * - PAN-1059 convoy: `agent-<issueId>-review-<subRole>` (current)\n * - Legacy specialist: `specialist-*-review-*` (retired)\n * - Legacy batch: `review-<issueId>-<timestamp>-<role>` (retired)\n *\n * A session is \"orphaned\" when the corresponding work-agent session\n * `agent-<issueLower>` does not exist.\n */\nexport async function cleanupOrphanedReviewSessions(): Promise<string[]> {\n const actions: string[] = [];\n let reviewSessions: string[];\n try {\n const allSessions = await Effect.runPromise(listSessionNames());\n const convoyReviewSessions = allSessions.filter(s => /^agent-.*-review-(?:security|correctness|performance|requirements)$/.test(s));\n const legacyReviewSessions = allSessions.filter(s => /^review-/.test(s));\n const canonicalReviewSessions = allSessions.filter(s => /^specialist-.*-review-/.test(s));\n reviewSessions = [...new Set([...convoyReviewSessions, ...legacyReviewSessions, ...canonicalReviewSessions])];\n } catch {\n return actions;\n }\n\n logDeaconEventSync(`cleanupOrphanedReviewSessions started: found ${reviewSessions.length} review session(s)`);\n\n for (const reviewSession of reviewSessions) {\n let issueId: string | null = null;\n\n // PAN-1059 convoy pattern\n const convoyMatch = reviewSession.match(/^agent-([a-z0-9]+-\\d+)-review-(?:security|correctness|performance|requirements)$/);\n if (convoyMatch) {\n issueId = convoyMatch[1].toUpperCase();\n } else {\n // Legacy batch pattern: review-<issueId>-<timestamp>\n const legacyMatch = reviewSession.match(/^review-([A-Za-z0-9]+-\\d+)-\\d+/);\n if (legacyMatch) {\n issueId = legacyMatch[1].toUpperCase();\n } else {\n // Legacy specialist pattern: specialist-<project>-<issueId>-review-<role>\n const canonicalMatch = reviewSession.match(/^specialist-(.+)-([A-Za-z0-9]+-\\d+)-review-[a-z-]+$/);\n if (canonicalMatch) {\n issueId = canonicalMatch[2].toUpperCase();\n }\n }\n }\n\n if (!issueId) {\n logDeaconEventSync(`cleanupOrphanedReviewSessions: ${reviewSession} skipped — unparseable session name`);\n continue;\n }\n\n // PAN-1059 convoy sub-reviewers are owned by the synthesis agent\n // (agent-<id>-review), not the work agent. Check synthesis first,\n // then fall back to work agent for legacy review sessions.\n const synthesisAgentSession = `agent-${issueId.toLowerCase()}-review`;\n const workAgentSession = `agent-${issueId.toLowerCase()}`;\n if (sessionExistsSync(synthesisAgentSession)) {\n logDeaconEventSync(`cleanupOrphanedReviewSessions: ${reviewSession} kept — synthesis agent ${synthesisAgentSession} exists`);\n continue;\n }\n if (sessionExistsSync(workAgentSession)) {\n logDeaconEventSync(`cleanupOrphanedReviewSessions: ${reviewSession} kept — work agent ${workAgentSession} exists`);\n continue;\n }\n\n try {\n await Effect.runPromise(killSession(reviewSession)).catch(() => {});\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`cleanupOrphanedReviewSessions: error killing ${reviewSession}: ${reason}`);\n }\n\n const msg = `Killed orphaned ${reviewSession} (synthesis ${synthesisAgentSession} and work ${workAgentSession} not running)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`cleanupOrphanedReviewSessions: ${msg}`);\n }\n if (actions.length > 0) {\n logDeaconEventSync(`cleanupOrphanedReviewSessions completed: killed ${actions.length} orphaned session(s)`);\n } else {\n logDeaconEventSync(`cleanupOrphanedReviewSessions completed: no orphaned sessions found`);\n }\n\n return actions;\n}\n\n\n/**\n * PAN-2584: liveness WATCH for the review PARENT (synthesis/self-review).\n *\n * ─── OBSERVABILITY ONLY — OPERATOR DIRECTIVE (2026-08-05) ───────────────────\n * This check must NEVER kill, stop, or respawn a parent again. On 2026-08-05\n * it killed the PAN-3511 parent two hours after that parent had recorded a\n * real verdict, because its \"no terminal verdict\" read used only the runtime\n * row — the plane that was losing verdicts that night. A past-deadline parent\n * now produces a needs-you escalation with the evidence, and the operator (or\n * a deliberately invoked door) decides whether a respawn is warranted.\n * ───────────────────────────────────────────────────────────────────────────\n *\n * The verdict check reads the verdict OF RECORD first (the synthesis artifact\n * via findVerdictReport — PAN-3511's contract), then the runtime row; a\n * terminal verdict in either plane means the parent is warm-idle by design\n * and is skipped. The deadline is explicit (`reviewDeadlineAt`, armed at\n * spawn/resume) with an implicit fallback from the last (re)start for parents\n * predating the arming code.\n */\nexport async function checkStalledReviewParents(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { listAgentStates } = await import('../agents.js');\n const { PARENT_REVIEW_TIMEOUT_MS } = await import('./review-agent.js');\n const states = listAgentStates();\n const now = Date.now();\n for (const state of states) {\n if (state.role !== 'review' || state.reviewSubRole || !state.issueId) continue;\n if (state.status !== 'running') continue;\n const armedMs = state.reviewDeadlineAt ? Date.parse(state.reviewDeadlineAt) : Number.NaN;\n const startedMs = Date.parse(state.lastResumeAt ?? state.startedAt);\n const deadlineMs = Number.isFinite(armedMs)\n ? armedMs\n : Number.isFinite(startedMs)\n ? startedMs + PARENT_REVIEW_TIMEOUT_MS\n : Number.NaN;\n if (!Number.isFinite(deadlineMs) || now < deadlineMs) continue;\n\n // Verdict-of-record first (PAN-3511's contract): the synthesis artifact\n // cannot lose a verdict; the runtime row can (and did, 2026-08-05). A\n // terminal verdict in EITHER plane means warm-idle by design — skip.\n if (hasTerminalVerdictOfRecord(state)) continue;\n const reviewStatus = getReviewStatusSync(state.issueId)?.reviewStatus;\n if (reviewStatus === 'passed' || reviewStatus === 'blocked' || reviewStatus === 'failed') continue;\n\n // Escalate once per deadline epoch — patrols repeat, the operator does\n // not need the same alarm every 60 seconds.\n const escalationKey = `${state.id}:${deadlineMs}`;\n if (stalledReviewParentEscalations.has(escalationKey)) continue;\n stalledReviewParentEscalations.add(escalationKey);\n\n const paneTail = await Effect.runPromise(capturePane(state.id, 30)).catch(() => '');\n const evidence = {\n deadline: new Date(deadlineMs).toISOString(),\n reviewStatus: reviewStatus ?? 'none',\n verdictArtifact: 'none found',\n paneTail: paneTail.split('\\n').filter(Boolean).slice(-5).join(' | ').slice(0, 400),\n };\n try {\n const { markWorkspaceStuck } = await import('../review-status.js');\n markWorkspaceStuck(state.issueId, 'review_parent_stalled_needs_you', {\n reason: `Review parent ${state.id} is past its review deadline with no verdict on record`,\n ...evidence,\n });\n } catch (stuckErr) {\n console.warn(`[deacon] checkStalledReviewParents: could not mark ${state.issueId} needs-you:`, stuckErr);\n }\n const message = `checkStalledReviewParents: ${state.id} past review deadline with no verdict for ${state.issueId} — escalated to operator (no kill; observability-only)`;\n actions.push(message);\n logDeaconEventSync(message);\n try {\n const { emitActivityEntrySync } = await import('../activity-logger.js');\n emitActivityEntrySync({\n source: 'review',\n level: 'warn',\n message: `Review parent for ${state.issueId} exceeded its deadline with no verdict — needs operator attention (respawn is YOUR call; the sweeper no longer kills)`,\n issueId: state.issueId,\n });\n } catch { /* activity logging is advisory */ }\n }\n } catch (error: unknown) {\n console.error('[deacon] Error checking stalled review parents:', error instanceof Error ? error.message : String(error));\n }\n return actions;\n}\n\n/** Escalation dedup for checkStalledReviewParents: `${agentId}:${deadlineMs}` keys. */\nconst stalledReviewParentEscalations = new Set<string>();\n\n/**\n * PAN-3551: the verdict of record for a review run — the synthesis artifact in\n * the run's review directory (synthesis.md or review.md, via the shared\n * findVerdictReport door). Presence means the parent completed its synthesis,\n * independent of what any status plane currently claims.\n */\nfunction hasTerminalVerdictOfRecord(state: AgentState): boolean {\n if (!state.workspace || !state.reviewRunId) return false;\n try {\n const reviewDir = join(state.workspace, '.pan', 'review', state.reviewRunId);\n return findVerdictReport(reviewDir) !== null;\n } catch {\n return false;\n }\n}\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { defaultRunWorkspace } from './spawn-prep.js';\nimport { resolveWorkspaceRepoRootsSync, type WorkspaceRepoRoot } from '../project-repos.js';\nimport type { XBriefItem } from '../xbrief/types.js';\n\nconst execAsync = promisify(exec);\n\nexport interface SlotMergeIssue {\n issueId: string;\n featureWorkspace?: string;\n slotBranch?: string;\n slotWorkspace?: string;\n}\n\nexport interface SlotMergeEvidence {\n verifyCommands: string[];\n expectedOutputs: string[];\n commandOutputs: Array<{ command: string; stdout: string; stderr: string }>;\n mergeOutput?: { stdout: string; stderr: string };\n /** Per-repo merge outputs for polyrepo slots (one entry per nested repo merged). */\n repoMergeOutputs?: Array<{ repoKey: string; stdout: string; stderr: string }>;\n}\n\nexport interface SlotMergeResult {\n verified: boolean;\n merged: boolean;\n conflicts: boolean;\n evidence: SlotMergeEvidence;\n failure?: string;\n}\n\nexport interface SlotMergeDeps {\n run: (command: string, cwd: string) => Promise<{ stdout: string; stderr: string }>;\n /** Polyrepo-aware repo roots for a workspace path. Defaults to the canonical resolver. */\n resolveRepoRoots: (issueId: string, workspacePath: string) => WorkspaceRepoRoot[];\n}\n\nexport interface SlotMergeOptions {\n deps?: Partial<SlotMergeDeps>;\n}\n\nexport async function verifyAndMergeSlot(\n issue: string | SlotMergeIssue,\n slotIndex: number,\n item: XBriefItem,\n options: SlotMergeOptions = {},\n): Promise<SlotMergeResult> {\n const issueId = typeof issue === 'string' ? issue : issue.issueId;\n const featureWorkspace = typeof issue === 'string'\n ? defaultRunWorkspace(issue)\n : issue.featureWorkspace ?? defaultRunWorkspace(issue.issueId);\n const slotWorkspace = typeof issue === 'string' ? `${featureWorkspace}-slot-${slotIndex}` : issue.slotWorkspace ?? `${featureWorkspace}-slot-${slotIndex}`;\n const slotBranch = typeof issue === 'string' ? `feature/${issueId.toLowerCase()}-slot-${slotIndex}` : issue.slotBranch ?? `feature/${issueId.toLowerCase()}-slot-${slotIndex}`;\n const deps: SlotMergeDeps = {\n run: async (command, cwd) => execAsync(command, { cwd }),\n resolveRepoRoots: resolveWorkspaceRepoRootsSync,\n ...options.deps,\n };\n const verifyCommands = item.metadata?.verify_commands ?? [];\n const expectedOutputs = item.metadata?.expected_outputs ?? [];\n const evidence: SlotMergeEvidence = {\n verifyCommands,\n expectedOutputs,\n commandOutputs: [],\n };\n\n if (!Number.isInteger(slotIndex) || slotIndex < 1) {\n return { verified: false, merged: false, conflicts: false, evidence, failure: `Invalid slot index: ${slotIndex}` };\n }\n if (verifyCommands.length === 0) {\n return { verified: false, merged: false, conflicts: false, evidence, failure: `Item ${item.id} has no verify_commands` };\n }\n if (expectedOutputs.length === 0) {\n return { verified: false, merged: false, conflicts: false, evidence, failure: `Item ${item.id} has no expected_outputs` };\n }\n\n for (const command of verifyCommands) {\n try {\n const output = await deps.run(command, slotWorkspace);\n evidence.commandOutputs.push({ command, stdout: output.stdout, stderr: output.stderr });\n } catch (error) {\n const failed = commandFailure(error);\n evidence.commandOutputs.push({ command, stdout: failed.stdout, stderr: failed.stderr });\n return {\n verified: false,\n merged: false,\n conflicts: false,\n evidence,\n failure: `Verify command failed for ${item.id}: ${command}`,\n };\n }\n }\n\n // PAN-3691: never report a merge when the slot branch carries no unmerged\n // current-item work. Count base..slot commits per real repo root (the\n // polyrepo wrapper is excluded by the resolver, so outer bookkeeping/setup\n // commits cannot satisfy the gate). A zero total means either a fresh slot\n // whose agent died before committing, or a slot branch reused after its\n // earlier work already merged — both must surface, not complete silently.\n const slotRoots = deps.resolveRepoRoots(issueId, slotWorkspace);\n if (slotRoots.some(root => root.degradedPolyrepo)) {\n return {\n verified: true,\n merged: false,\n conflicts: false,\n evidence,\n failure: `Could not resolve polyrepo roots for slot ${slotIndex} — refusing to infer a merge`,\n };\n }\n const aheadByRepo = new Map<string, number>();\n for (const root of slotRoots) {\n aheadByRepo.set(root.repoKey, await aheadCount(deps.run, root.dir, root.sourceBranch, slotBranch));\n }\n const rootsWithWork = slotRoots.filter(root => (aheadByRepo.get(root.repoKey) ?? 0) > 0);\n if (rootsWithWork.length === 0) {\n return {\n verified: true,\n merged: false,\n conflicts: false,\n evidence,\n failure: `Slot branch ${slotBranch} has no unmerged current-item changes in any repo — refusing to mark item ${item.id} merged`,\n };\n }\n\n const isPolyrepo = slotRoots.some(root => root.isPolyrepo);\n if (!isPolyrepo) {\n try {\n const mergeOutput = await deps.run(`git merge --no-ff ${JSON.stringify(slotBranch)}`, featureWorkspace);\n evidence.mergeOutput = mergeOutput;\n return { verified: true, merged: true, conflicts: false, evidence };\n } catch (error) {\n const failed = commandFailure(error);\n evidence.mergeOutput = { stdout: failed.stdout, stderr: failed.stderr };\n await deps.run('git merge --abort', featureWorkspace).catch(() => {});\n return {\n verified: true,\n merged: false,\n conflicts: true,\n evidence,\n failure: `Slot branch ${slotBranch} did not merge cleanly`,\n };\n }\n }\n\n // Polyrepo: the wrapper holds no slot branch. Merge each nested slot branch\n // into its feature-branch checkout inside the base feature workspace.\n const baseRoots = new Map(\n deps.resolveRepoRoots(issueId, featureWorkspace).map(root => [root.repoKey, root]),\n );\n const repoMergeOutputs: Array<{ repoKey: string; stdout: string; stderr: string }> = [];\n for (const root of rootsWithWork) {\n const baseRoot = baseRoots.get(root.repoKey);\n if (!baseRoot) {\n return {\n verified: true,\n merged: false,\n conflicts: false,\n evidence: { ...evidence, repoMergeOutputs },\n failure: `No base feature-workspace checkout for repo ${root.repoKey} — cannot integrate slot ${slotIndex}`,\n };\n }\n try {\n const output = await deps.run(`git merge --no-ff ${JSON.stringify(slotBranch)}`, baseRoot.dir);\n repoMergeOutputs.push({ repoKey: root.repoKey, stdout: output.stdout, stderr: output.stderr });\n } catch (error) {\n const failed = commandFailure(error);\n repoMergeOutputs.push({ repoKey: root.repoKey, stdout: failed.stdout, stderr: failed.stderr });\n await deps.run('git merge --abort', baseRoot.dir).catch(() => {});\n return {\n verified: true,\n merged: false,\n conflicts: true,\n evidence: { ...evidence, repoMergeOutputs },\n failure: `Slot branch ${slotBranch} did not merge cleanly into ${root.repoKey}`,\n };\n }\n }\n return { verified: true, merged: true, conflicts: false, evidence: { ...evidence, repoMergeOutputs } };\n}\n\n/** Count base..branch commits in one repo; a missing branch or repo means zero. */\nasync function aheadCount(\n run: SlotMergeDeps['run'],\n cwd: string,\n baseBranch: string,\n branch: string,\n): Promise<number> {\n try {\n const { stdout } = await run(\n `git rev-list --count ${JSON.stringify(baseBranch)}..${JSON.stringify(branch)}`,\n cwd,\n );\n const count = Number(stdout.trim());\n return Number.isFinite(count) && count > 0 ? count : 0;\n } catch {\n return 0;\n }\n}\n\nfunction commandFailure(error: unknown): { stdout: string; stderr: string } {\n const partial = error as { stdout?: unknown; stderr?: unknown; message?: unknown };\n return {\n stdout: typeof partial.stdout === 'string' ? partial.stdout : '',\n stderr: typeof partial.stderr === 'string'\n ? partial.stderr\n : typeof partial.message === 'string'\n ? partial.message\n : String(error),\n };\n}\n","/** Single task-mutation write door over the immutable xBRIEF specification. */\nimport { hostname } from 'node:os';\nimport { join } from 'node:path';\n\nimport type { ProjectConfig } from '../projects.js';\nimport type { TaskOperationType } from '../xbrief/dag.js';\nimport { applyStatusOverrides, findPlanSync, readPlanSync } from '../xbrief/io.js';\nimport { subItemsOf, type XBriefItemStatus } from '../xbrief/types.js';\nimport {\n type PanIssueRecord,\n type TaskClaim,\n type TaskClaimHistoryEntry,\n} from './record.js';\nimport { updateIssueRecord } from './record-update.js';\n\nexport interface TaskStatusChange {\n type: TaskOperationType;\n itemId: string;\n subItemIds?: string[];\n reason?: string;\n expectedSequence?: number;\n force?: boolean;\n writerId?: string;\n}\n\nexport interface TaskStatusChangeResult {\n issueId: string;\n itemId: string;\n status: XBriefItemStatus;\n sequence: number;\n claim?: TaskClaim;\n idempotent?: boolean;\n}\n\nexport class TaskStatusChangeError extends Error {\n constructor(\n message: string,\n public readonly details: {\n issueId: string;\n itemId: string;\n operation: TaskOperationType;\n currentStatus?: string;\n owner?: string;\n currentSequence?: number;\n },\n ) {\n super(message);\n this.name = 'TaskStatusChangeError';\n }\n}\n\nconst TERMINAL = new Set<XBriefItemStatus>(['completed', 'cancelled']);\n\nfunction statusFor(type: TaskOperationType): XBriefItemStatus {\n if (type === 'claim') return 'running';\n if (type === 'done') return 'completed';\n if (type === 'block') return 'blocked';\n if (type === 'unblock') return 'pending';\n if (type === 'reopen') return 'pending';\n return 'cancelled';\n}\n\nfunction subItemOverrideKey(itemId: string, subItemId: string): string {\n return subItemId.includes('.') ? subItemId : `${itemId}.${subItemId}`;\n}\n\nfunction allowedFrom(type: TaskOperationType, status: XBriefItemStatus): boolean {\n if (type === 'claim') return status === 'pending';\n if (type === 'done') return status === 'running';\n if (type === 'block') return status === 'pending' || status === 'running';\n if (type === 'unblock') return status === 'blocked';\n // PAN-3691: reopen is the canonical recovery for a falsely completed task.\n if (type === 'reopen') return status === 'completed';\n return status === 'pending' || status === 'running' || status === 'blocked';\n}\n\nfunction taskError(\n issueId: string,\n itemId: string,\n operation: TaskOperationType,\n currentStatus: string,\n owner?: string,\n extra?: string,\n): TaskStatusChangeError {\n const ownerText = owner ? ` The current claim owner is ${owner}.` : '';\n const recovery = `Run \\`pan task show ${issueId} ${itemId}\\` to inspect the current task state before retrying.`;\n return new TaskStatusChangeError(\n `Task ${itemId} on ${issueId} is ${currentStatus}, so ${operation} is not allowed.${ownerText}${extra ? ` ${extra}` : ''} ${recovery}`,\n { issueId, itemId, operation, currentStatus, owner },\n );\n}\n\nfunction archiveClaim(\n record: PanIssueRecord,\n itemId: string,\n outcome: TaskClaimHistoryEntry['outcome'],\n reason: string | undefined,\n forced: boolean,\n now: string,\n): void {\n const claim = record.tasks?.claims[itemId];\n if (!claim || !record.tasks) return;\n const entry: TaskClaimHistoryEntry = { ...claim, itemId, outcome, reason, forced: forced || undefined, releasedAt: now };\n record.tasks.claimHistory = [...(record.tasks.claimHistory ?? []), entry].slice(-50);\n delete record.tasks.claims[itemId];\n}\n\nexport async function applyTaskStatusChange(\n project: ProjectConfig,\n issueId: string,\n operation: TaskStatusChange,\n): Promise<TaskStatusChangeResult> {\n const normalizedIssueId = issueId.toUpperCase();\n const writerId = operation.writerId ?? process.env.OVERDECK_AGENT_ID ?? `cli-${process.pid}@${hostname()}`;\n let result: TaskStatusChangeResult | undefined;\n\n await updateIssueRecord(project, normalizedIssueId, (record) => {\n const workspacePath = join(project.path, 'workspaces', `feature-${normalizedIssueId.toLowerCase()}`);\n const planPath = findPlanSync(workspacePath);\n if (!planPath) throw new Error(`The xBRIEF for ${normalizedIssueId} is missing or unreadable. Return the issue to planning before changing task state.`);\n const doc = applyStatusOverrides(readPlanSync(planPath), record.statusOverrides ?? {});\n const item = doc.plan.items.find(({ id }) => id === operation.itemId);\n if (!item) throw new Error(`Task ${operation.itemId} does not exist in the immutable xBRIEF for ${normalizedIssueId}. Return the issue to planning to change scope.`);\n\n const currentStatus = item.status;\n const currentSequence = record.tasks?.sequence ?? 0;\n const existingClaim = record.tasks?.claims[operation.itemId];\n const nextStatus = statusFor(operation.type);\n const subItems = subItemsOf(item);\n const subIds = operation.subItemIds?.length\n ? new Set(operation.subItemIds)\n : new Set(\n operation.type === 'done' || operation.type === 'cancel' || operation.type === 'reopen'\n ? subItems.map(({ id }) => id)\n : [],\n );\n const repeatedCancellation = operation.type === 'cancel' && currentStatus === 'cancelled';\n if (operation.expectedSequence !== undefined && operation.expectedSequence !== currentSequence) {\n throw new TaskStatusChangeError(\n `Task sequence conflict for ${normalizedIssueId}: expected ${operation.expectedSequence}, but the current sequence is ${currentSequence}. Run \\`pan task next ${normalizedIssueId}\\` and retry with the new sequence.`,\n { issueId: normalizedIssueId, itemId: operation.itemId, operation: operation.type, currentStatus, currentSequence },\n );\n }\n if (TERMINAL.has(currentStatus) && !repeatedCancellation && operation.type !== 'reopen') {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId);\n }\n if (operation.force && !operation.reason) {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId, 'A forced transition requires --reason.');\n }\n if ((operation.type === 'block' || operation.type === 'cancel' || operation.type === 'reopen') && !operation.reason) {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId, 'This transition requires --reason.');\n }\n if (repeatedCancellation && subItems.every((subItem) => subItem.status === 'cancelled')) {\n result = { issueId: normalizedIssueId, itemId: operation.itemId, status: 'cancelled', sequence: currentSequence, idempotent: true };\n return record;\n }\n if (operation.type === 'claim' && currentStatus === 'running' && existingClaim?.writerId === writerId) {\n result = { issueId: normalizedIssueId, itemId: operation.itemId, status: 'running', sequence: currentSequence, claim: existingClaim, idempotent: true };\n return record;\n }\n const ownerRestricted = (operation.type === 'done' || operation.type === 'block' || operation.type === 'cancel') && currentStatus === 'running';\n if (!operation.force && ownerRestricted && existingClaim?.writerId !== writerId) {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId ?? 'missing claim owner');\n }\n if (!operation.force && !repeatedCancellation && !allowedFrom(operation.type, currentStatus)) {\n throw taskError(normalizedIssueId, operation.itemId, operation.type, currentStatus, existingClaim?.writerId);\n }\n\n const now = new Date().toISOString();\n record.tasks ??= { sequence: 0, claims: {} };\n record.statusOverrides = { ...(record.statusOverrides ?? {}), [operation.itemId]: nextStatus };\n for (const subId of subIds) record.statusOverrides[subItemOverrideKey(operation.itemId, subId)] = nextStatus;\n\n if (operation.type === 'claim') {\n record.tasks.claims[operation.itemId] = {\n writerId,\n agentId: process.env.OVERDECK_AGENT_ID ?? null,\n pid: process.pid,\n host: hostname(),\n claimedAt: now,\n };\n } else if (existingClaim) {\n archiveClaim(record, operation.itemId, nextStatus === 'completed' ? 'completed' : nextStatus === 'blocked' ? 'blocked' : 'cancelled', operation.reason, operation.force ?? false, now);\n }\n if (operation.reason) {\n record.tasks.statusReasons = {\n ...(record.tasks.statusReasons ?? {}),\n [operation.itemId]: { reason: operation.reason, updatedAt: now, forced: operation.force || undefined },\n };\n }\n record.tasks.sequence = currentSequence + 1;\n result = {\n issueId: normalizedIssueId,\n itemId: operation.itemId,\n status: nextStatus,\n sequence: record.tasks.sequence,\n claim: record.tasks.claims[operation.itemId],\n };\n return record;\n }, { writerId });\n\n if (!result) throw new Error(`Task mutation for ${normalizedIssueId}/${operation.itemId} produced no result.`);\n return result;\n}\n","import { loadConfigSync } from './config-yaml.js';\nimport { getProjectSync, resolveProjectFromIssueSync } from './projects.js';\nimport { readIssueRecordSync } from './pan-dir/record.js';\n\nexport type SwarmMode = 'off' | 'auto' | 'always';\nexport interface SwarmPolicyLayer { mode?: SwarmMode; maxSlots?: number; autoAdvance?: boolean }\nexport interface ResolvedSwarmPolicy {\n mode: SwarmMode;\n maxSlots: number;\n autoAdvance: boolean;\n source: { mode: string; maxSlots: string; autoAdvance: string };\n}\n\nexport function resolveSwarmPolicyLayers(global: SwarmPolicyLayer = {}, project: SwarmPolicyLayer = {}, issue: SwarmPolicyLayer = {}, cli: SwarmPolicyLayer = {}): ResolvedSwarmPolicy {\n const resolve = <K extends keyof SwarmPolicyLayer>(key: K, fallback: NonNullable<SwarmPolicyLayer[K]>) => {\n for (const [source, layer] of [['cli', cli], ['issue', issue], ['project', project], ['global', global]] as const) {\n if (layer[key] !== undefined) return { value: layer[key] as NonNullable<SwarmPolicyLayer[K]>, source };\n }\n return { value: fallback, source: 'default' };\n };\n const mode = resolve('mode', 'off');\n const maxSlots = resolve('maxSlots', 3);\n const autoAdvance = resolve('autoAdvance', true);\n return { mode: mode.value, maxSlots: maxSlots.value, autoAdvance: autoAdvance.value, source: { mode: mode.source, maxSlots: maxSlots.source, autoAdvance: autoAdvance.source } };\n}\n\nexport function resolveSwarmPolicy(issueId?: string, cli: SwarmPolicyLayer = {}): ResolvedSwarmPolicy {\n const global = loadConfigSync().config.swarm;\n if (!issueId) return resolveSwarmPolicyLayers(global, {}, {}, cli);\n const resolved = resolveProjectFromIssueSync(issueId);\n const project = resolved ? getProjectSync(resolved.projectKey) : undefined;\n const issue = project ? readIssueRecordSync(project, issueId)?.swarm?.policy : undefined;\n return resolveSwarmPolicyLayers(global, project?.swarm, issue, cli);\n}\n\nexport function resolveAutomaticSwarmPolicy(issueId: string, swarmEligible: boolean, manual = false) {\n const policy = resolveSwarmPolicy(issueId);\n const spawnForeman = manual || (policy.mode !== 'off' && swarmEligible);\n return {\n policy,\n spawnForeman,\n requireSwarmReadiness: policy.mode === 'always',\n advanceWavesWithoutConfirmation: policy.autoAdvance,\n reason: manual ? 'manual' : policy.mode === 'off' ? 'mode-off' : swarmEligible ? 'eligible' : 'not-ready',\n } as const;\n}\n\nexport function resolveSwarmMaxSlots(issueId: string, configured: number): number {\n return Math.min(Math.max(1, Math.floor(configured)), Math.max(1, Math.floor(resolveSwarmPolicy(issueId).maxSlots)));\n}\n","import { existsSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { listProjectsSync } from '../projects.js';\n\nexport interface FeatureWorkspace {\n issueId: string;\n workspacePath: string;\n projectPath: string;\n}\n\nexport interface ListFeatureWorkspacesOptions {\n includeSlotWorkspaces?: boolean;\n}\n\nexport function listFeatureWorkspaces(options: ListFeatureWorkspacesOptions = {}): FeatureWorkspace[] {\n const includeSlotWorkspaces = options.includeSlotWorkspaces ?? true;\n const projects = listProjectsSync();\n const workspaces: FeatureWorkspace[] = [];\n\n for (const { config: projectConfig } of projects) {\n const workspacesRoot = join(projectConfig.path, 'workspaces');\n if (!existsSync(workspacesRoot)) continue;\n\n let entries: string[];\n try {\n entries = readdirSync(workspacesRoot, { withFileTypes: true })\n .filter(e => e.isDirectory() && e.name.startsWith('feature-'))\n .filter(e => includeSlotWorkspaces || !/-slot-\\d+$/.test(e.name))\n .map(e => e.name);\n } catch {\n continue;\n }\n\n for (const entry of entries) {\n workspaces.push({\n issueId: entry.replace(/^feature-/, '').toUpperCase(),\n workspacePath: join(workspacesRoot, entry),\n projectPath: projectConfig.path,\n });\n }\n }\n\n return workspaces;\n}\n","import { listSlotAssignments as listDurableSlotAssignments, type SlotReconcileResult } from '../agents/slot-reconcile.js';\nimport type { CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\n\ntype OrphanGcDeps = Pick<CoordinateSwarmSlotsDeps, 'runGitCommand' | 'listSessionNames' | 'listSlotAssignments'>;\n\n/**\n * Reconcile GC for truly-orphaned slots (PAN-2214, completes PAN-2213(c)).\n *\n * An orphan is a slot index with an on-disk worktree or a local slot branch but\n * no slotAssignments entry and no live agent tmux session. Zero-commit-ahead\n * orphans are removed; orphans with unmerged commits are only reported so the\n * operator can push them via `pan swarm reset` — never deleted.\n */\nexport async function gcOrphanedSlots(\n issueId: string,\n workspacePath: string,\n reconciled: SlotReconcileResult,\n deps: OrphanGcDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n const issueLower = issueId.toLowerCase();\n\n const worktreeSlotIndexes = await listSlotWorktreeIndexes(workspacePath, deps.runGitCommand);\n const branchesBySlot = new Map(reconciled.branches.map(branch => [branch.slotIndex, branch.branch]));\n const candidateSlotIndexes = [...new Set([...worktreeSlotIndexes, ...branchesBySlot.keys()])].sort((a, b) => a - b);\n if (candidateSlotIndexes.length === 0) return actions;\n\n const ownedSlotIndexes = new Set([\n ...(deps.listSlotAssignments ?? listDurableSlotAssignments)(issueId, workspacePath).map(assignment => assignment.slotIndex),\n ...reconciled.merged.map(slot => slot.slotIndex),\n ...reconciled.inFlight.map(slot => slot.slotIndex),\n ]);\n const sessionNames = new Set(await deps.listSessionNames());\n\n for (const slotIndex of candidateSlotIndexes) {\n if (ownedSlotIndexes.has(slotIndex)) continue;\n if (sessionNames.has(`agent-${issueLower}-slot-${slotIndex}`)) continue;\n\n const branch = branchesBySlot.get(slotIndex);\n const aheadCount = branch ? await countCommitsAhead(workspacePath, branch, deps.runGitCommand) : 0;\n if (aheadCount === null || aheadCount > 0) {\n actions.push(\n `[swarm] orphan slot ${slotIndex} for ${issueId} preserved: ${branch} has `\n + `${aheadCount ?? 'an unknown number of'} unmerged commit(s) — run \\`pan swarm reset ${issueId}\\` `\n + 'to push it to origin before cleanup',\n );\n continue;\n }\n\n if (worktreeSlotIndexes.has(slotIndex)) {\n await deps.runGitCommand(`git worktree remove --force ${JSON.stringify(`${workspacePath}-slot-${slotIndex}`)}`, workspacePath);\n }\n if (branch) {\n await deps.runGitCommand(`git branch -D ${JSON.stringify(branch)}`, workspacePath);\n }\n actions.push(`[swarm] gc-orphan slot ${slotIndex} for ${issueId}`);\n }\n\n return actions;\n}\n\nasync function listSlotWorktreeIndexes(\n workspacePath: string,\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n): Promise<Set<number>> {\n const indexes = new Set<number>();\n try {\n const result = await runGitCommand('git worktree list --porcelain', workspacePath) as { stdout?: unknown };\n const pattern = new RegExp(`^worktree ${escapeRegExp(workspacePath)}-slot-(\\\\d+)$`);\n for (const line of String(result?.stdout ?? '').split('\\n')) {\n const match = pattern.exec(line.trim());\n if (match) indexes.add(Number(match[1]));\n }\n } catch {\n // Worktree enumeration failed — fall back to branch-derived candidates only.\n }\n return indexes;\n}\n\nasync function countCommitsAhead(\n workspacePath: string,\n branch: string,\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n): Promise<number | null> {\n try {\n const result = await runGitCommand(`git rev-list --count HEAD..${JSON.stringify(branch)}`, workspacePath) as { stdout?: unknown };\n const count = Number(String(result?.stdout ?? '').trim());\n return Number.isFinite(count) ? count : null;\n } catch {\n return null;\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","import {\n readIssueRecordForWorkspaceSync,\n type PanIssueRecord,\n type PanIssueSwarmRecord,\n type PanIssueSwarmSlotCompletion,\n} from '../pan-dir/record.js';\nimport { updateIssueRecordForWorkspace } from '../pan-dir/record-update.js';\n\nexport function createMinimalIssueRecord(issueId: string): PanIssueRecord {\n const now = new Date().toISOString();\n return {\n issueId,\n schemaVersion: 2,\n created: now,\n updated: now,\n feedback: [],\n pipeline: {\n issueId,\n reviewStatus: 'pending',\n testStatus: 'pending',\n mergeStatus: 'pending',\n readyForMerge: false,\n updatedAt: now,\n },\n closeOut: {\n usage: {\n byStage: {},\n totals: {},\n },\n merges: [],\n ranOn: '',\n },\n };\n}\n\nexport async function writeSwarmFinalizedAt(workspacePath: string, issueId: string, finalizedAt: string): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n finalizedAt,\n },\n }));\n}\n\nexport function readSwarmHold(workspacePath: string, issueId: string): PanIssueSwarmRecord['hold'] {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())?.swarm?.hold;\n}\n\nexport async function writeSwarmHold(\n workspacePath: string,\n issueId: string,\n hold: NonNullable<PanIssueSwarmRecord['hold']>,\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), (record) => ({\n ...record,\n swarm: { ...(record.swarm ?? {}), hold },\n }));\n}\n\nexport async function clearSwarmHold(workspacePath: string, issueId: string): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), (record) => {\n if (!record.swarm?.hold) return record;\n const swarm = { ...record.swarm };\n delete swarm.hold;\n return { ...record, swarm };\n });\n}\n\nexport function readSwarmInterventionCount(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n failureClass: string,\n): number {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())\n ?.swarm?.interventions?.[String(slotIndex)]?.[failureClass] ?? 0;\n}\n\nexport function readSwarmInterventions(\n workspacePath: string,\n issueId: string,\n): NonNullable<PanIssueSwarmRecord['interventions']> {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())?.swarm?.interventions ?? {};\n}\n\nexport function readSwarmCompletionObservation(\n workspacePath: string,\n issueId: string,\n progressKey: string,\n): NonNullable<PanIssueSwarmRecord['completionObservations']>[string] | undefined {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())\n ?.swarm?.completionObservations?.[progressKey];\n}\n\nexport async function writeSwarmCompletionObservation(\n workspacePath: string,\n issueId: string,\n progressKey: string,\n observation: NonNullable<PanIssueSwarmRecord['completionObservations']>[string],\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), record => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n completionObservations: {\n ...(record.swarm?.completionObservations ?? {}),\n [progressKey]: observation,\n },\n },\n }));\n}\n\nexport async function clearSwarmCompletionObservationRecord(\n workspacePath: string,\n issueId: string,\n progressKey: string,\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), record => {\n const existing = record.swarm?.completionObservations;\n if (!existing?.[progressKey]) return record;\n const completionObservations = { ...existing };\n delete completionObservations[progressKey];\n return { ...record, swarm: { ...(record.swarm ?? {}), completionObservations } };\n });\n}\n\nexport async function writeSwarmForemanTakeover(\n workspacePath: string,\n issueId: string,\n itemId: string,\n slotIndex: number,\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), record => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n reclaimedItems: {\n ...(record.swarm?.reclaimedItems ?? {}),\n [itemId]: { slotIndex, reclaimedAt: new Date().toISOString() },\n },\n },\n }));\n}\n\nexport async function writeSwarmIntervention(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n failureClass: string,\n options: { operator?: boolean } = {},\n): Promise<number | null> {\n let count: number | null = null;\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), (record) => {\n const slotKey = String(slotIndex);\n const interventions = record.swarm?.interventions ?? {};\n const slotInterventions = interventions[slotKey] ?? {};\n const current = slotInterventions[failureClass] ?? 0;\n if (current >= 3 && !options.operator) return record;\n count = current + 1;\n return {\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n interventions: {\n ...interventions,\n [slotKey]: { ...slotInterventions, [failureClass]: count as number },\n },\n },\n };\n });\n return count;\n}\n\n/**\n * PAN-3459: persist the issue-level swarm policy mode. An explicit\n * `pan swarm <id>` start opts the issue into ongoing Deacon coordination;\n * without a persisted issue-level mode, a global `swarm.mode: off` (the\n * default) makes every subsequent patrol skip the issue entirely — wave 1\n * dispatches and the swarm orphans: completed slot branches never merge and\n * remaining items never dispatch.\n */\nexport async function writeSwarmPolicyMode(\n workspacePath: string,\n issueId: string,\n mode: 'off' | 'auto' | 'always',\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n policy: {\n ...(record.swarm?.policy ?? {}),\n mode,\n },\n },\n }));\n}\n\n/**\n * PAN-2372 WI-3 / FR-4: persist a durable per-slot completion marker keyed by\n * `String(slotIndex)`. Read-modify-write preserves every other record field\n * (statusOverrides, slotAssignments, etc.) — see the byte-identical\n * preservation test in deacon-swarm-slot-completion.test.ts.\n */\nexport async function writeSwarmSlotCompletion(\n workspacePath: string,\n issueId: string,\n completion: PanIssueSwarmSlotCompletion,\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n slotCompletions: {\n ...(record.swarm?.slotCompletions ?? {}),\n [String(completion.slotIndex)]: completion,\n },\n },\n }));\n}\n\n/**\n * PAN-2372 WI-3 / FR-6: clear a slot's completion marker once the coordinator\n * (WI-4) has consumed it (merge/requeue). No-op when no marker exists for the\n * slot, so callers can clear unconditionally on the terminal transition.\n */\nexport async function clearSwarmSlotCompletion(workspacePath: string, issueId: string, slotIndex: number): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n const current = readIssueRecordForWorkspaceSync(workspacePath, normalizedIssueId);\n if (!current?.swarm?.slotCompletions || !(String(slotIndex) in current.swarm.slotCompletions)) return;\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => {\n const existing = record.swarm?.slotCompletions;\n if (!existing || !(String(slotIndex) in existing)) return record;\n const next: Record<string, PanIssueSwarmSlotCompletion> = { ...existing };\n delete next[String(slotIndex)];\n return { ...record, swarm: { ...record.swarm, slotCompletions: next } };\n });\n}\n\n/**\n * Consume all durable ownership for a slot in one record update. Merged-slot\n * GC uses this after it removes the worktree and branch so a completion marker\n * cannot outlive the assignment and keep the freed index occupied.\n */\nexport async function clearSwarmSlotOwnership(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n _itemId?: string,\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => {\n const swarm = record.swarm ?? {};\n const slotAssignments = (swarm.slotAssignments ?? []).filter(\n assignment => assignment.slotIndex !== slotIndex,\n );\n const slotCompletions = { ...(swarm.slotCompletions ?? {}) };\n delete slotCompletions[String(slotIndex)];\n return { ...record, swarm: { ...swarm, slotAssignments, slotCompletions } };\n });\n}\n\nexport async function releaseBlockedSwarmSlot(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n itemId: string,\n branch?: string,\n archived?: { archivedBranch: string; archivedWorktree: string; replacementBranch: string; releasedAt: string },\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => {\n const swarm = record.swarm ?? {};\n const slotAssignments = (swarm.slotAssignments ?? []).filter(\n assignment => assignment.slotIndex !== slotIndex,\n );\n const slotCompletions = { ...(swarm.slotCompletions ?? {}) };\n delete slotCompletions[String(slotIndex)];\n return {\n ...record,\n swarm: {\n ...swarm,\n slotAssignments,\n slotCompletions,\n releasedBlockedSlots: {\n ...(swarm.releasedBlockedSlots ?? {}),\n [String(slotIndex)]: {\n slotIndex,\n itemId,\n branch,\n ...archived,\n releasedAt: archived?.releasedAt ?? new Date().toISOString(),\n },\n },\n },\n };\n });\n}\n\nexport async function clearReleasedBlockedSwarmSlot(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n): Promise<void> {\n await updateIssueRecordForWorkspace(workspacePath, issueId.toUpperCase(), record => {\n const releasedBlockedSlots = { ...(record.swarm?.releasedBlockedSlots ?? {}) };\n delete releasedBlockedSlots[String(slotIndex)];\n return { ...record, swarm: { ...(record.swarm ?? {}), releasedBlockedSlots } };\n });\n}\n\n/**\n * Drop every superseded-attempt record (PAN-3694). A work-preserving swarm\n * reset removes all slot worktrees and branches, so the slot indexes the\n * superseded attempts occupied are genuinely free — but\n * `applySupersededSlotHighWater` kept reserving indexes 1..high-water,\n * leaving a fresh swarm able to dispatch only high-water+1. The archived\n * branches remain on origin; only the index-blocking record is cleared.\n */\nexport async function clearSupersededSwarmAttempts(workspacePath: string, issueId: string): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n await updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, (record) => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n supersededAttempts: [],\n },\n }));\n}\n\n/**\n * PAN-2372 WI-3 / FR-4, FR-5: write the durable slot-completion marker and read\n * it straight back to confirm it persisted. Returns true only when the keyed\n * marker exists on disk with a matching agentId. The slot `pan done` caller MUST\n * refuse to mark the slot done when this returns false — that is the whole point\n * of the issue: a slot used to finish without durably recording completion, so\n * the coordinator could not observe it. Keeping the write+verify here (not inline\n * in the CLI command) keeps the god-file done.ts from growing and co-locates the\n * swarm-record mechanics with the other slot-completion door functions.\n */\nexport async function persistAndVerifySwarmSlotCompletion(\n workspacePath: string,\n issueId: string,\n completion: PanIssueSwarmSlotCompletion,\n): Promise<boolean> {\n await writeSwarmSlotCompletion(workspacePath, issueId, completion);\n const reread = readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase());\n const persisted = reread?.swarm?.slotCompletions?.[String(completion.slotIndex)];\n return Boolean(persisted && persisted.agentId === completion.agentId);\n}\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { messageAgent } from '../agents/messaging.js';\nimport type { ReconciledSlotItem } from '../agents/slot-reconcile.js';\nimport { isStatePlaneOnlyStatus } from '../state-plane.js';\nimport { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\nimport { loadCloisterConfigSync, type SwarmInferCompletionMode } from './config.js';\nimport type { ClassifiedSwarmSlot, ClassifyInFlightSlotsOptions, CoordinateSwarmSlotsDeps } from './deacon-swarm.js';\nimport {\n clearSwarmCompletionObservationRecord,\n readSwarmCompletionObservation,\n writeSwarmCompletionObservation,\n} from './deacon-swarm-record.js';\n\nconst execAsync = promisify(exec);\ninterface SlotCompletionObservation {\n signature: string;\n nudged: boolean;\n consecutiveDoneCount: number;\n}\n\nexport interface DoneWithoutSignalObservation {\n commitTime: number | null;\n outputDigest: string;\n progressKey: string;\n stalledForMs: number;\n}\n\nexport async function classifyDoneWithoutSignal(\n slot: ReconciledSlotItem,\n deps: Partial<Pick<CoordinateSwarmSlotsDeps,\n | 'getSlotBranchAheadCount'\n | 'isSlotWorktreeClean'\n | 'sendCompletionNudge'\n | 'readCompletionObservation'\n | 'writeCompletionObservation'\n | 'clearCompletionObservation'>>,\n options: ClassifyInFlightSlotsOptions,\n observation: DoneWithoutSignalObservation,\n): Promise<ClassifiedSwarmSlot | null> {\n const mode = options.inferCompletion ?? 'off';\n if (mode === 'off' || !slot.agentId || !slot.branch || !options.workspacePath || !options.issueId) {\n if (options.workspacePath && options.issueId) {\n await (deps.clearCompletionObservation ?? clearSwarmCompletionObservationRecord)(\n options.workspacePath,\n options.issueId,\n observation.progressKey,\n );\n }\n return null;\n }\n\n const aheadCount = await (deps.getSlotBranchAheadCount ?? defaultGetSlotBranchAheadCount)(\n options.workspacePath,\n options.issueId,\n slot.branch,\n ).catch(() => 0);\n const clean = await (deps.isSlotWorktreeClean ?? defaultIsSlotWorktreeClean)(\n `${options.workspacePath}-slot-${slot.slotIndex}`,\n ).catch(() => false);\n if (aheadCount < 1 || !clean) {\n await (deps.clearCompletionObservation ?? clearSwarmCompletionObservationRecord)(\n options.workspacePath,\n options.issueId,\n observation.progressKey,\n );\n return null;\n }\n\n const signature = `${observation.commitTime ?? 'none'}:${observation.outputDigest}:${aheadCount}:clean`;\n const previous = (deps.readCompletionObservation ?? readSwarmCompletionObservation)(\n options.workspacePath,\n options.issueId,\n observation.progressKey,\n );\n const current: SlotCompletionObservation = {\n signature,\n nudged: previous?.signature === signature ? previous.nudged : false,\n consecutiveDoneCount: previous?.signature === signature ? previous.consecutiveDoneCount + 1 : 1,\n };\n const actions: string[] = [];\n const normalizedIssueId = options.issueId.toUpperCase();\n\n if (!current.nudged) {\n if (deps.sendCompletionNudge) await deps.sendCompletionNudge(slot.agentId, normalizedIssueId);\n current.nudged = true;\n actions.push(`[swarm] nudged slot ${slot.slotIndex} (item ${slot.itemId}) for ${normalizedIssueId}: run pan done ${normalizedIssueId}`);\n }\n\n await (deps.writeCompletionObservation ?? writeSwarmCompletionObservation)(\n options.workspacePath,\n options.issueId,\n observation.progressKey,\n current,\n );\n return {\n ...slot,\n lifecycle: 'awaiting-completion-signal',\n reason: 'no-progress-timeout',\n stalledForMs: observation.stalledForMs,\n signal: 'completion-nudge',\n actions,\n };\n}\n\nexport async function classifyDurableReadySlot(\n slot: ReconciledSlotItem,\n deps: Partial<Pick<CoordinateSwarmSlotsDeps, 'getSlotBranchAheadCount' | 'isSlotWorktreeClean'>>,\n options: ClassifyInFlightSlotsOptions,\n): Promise<ClassifiedSwarmSlot | null> {\n if (!slot.branch || !options.workspacePath || !options.issueId) return null;\n\n const aheadCount = await (deps.getSlotBranchAheadCount ?? defaultGetSlotBranchAheadCount)(\n options.workspacePath,\n options.issueId,\n slot.branch,\n ).catch(() => 0);\n if (aheadCount < 1) return null;\n\n const clean = await (deps.isSlotWorktreeClean ?? defaultIsSlotWorktreeClean)(\n `${options.workspacePath}-slot-${slot.slotIndex}`,\n ).catch(() => false);\n if (!clean) return null;\n\n return { ...slot, lifecycle: 'ready-to-merge', exitStatus: 0, signal: 'inferred' };\n}\n\nexport function resetSwarmCompletionInferenceForTests(): void {\n // Completion inference is record-backed; there is no process-local state to reset.\n}\n\nexport async function clearSwarmCompletionObservation(\n workspacePath: string,\n issueId: string,\n progressKey: string,\n deps: Partial<Pick<CoordinateSwarmSlotsDeps, 'clearCompletionObservation'>>,\n): Promise<void> {\n await (deps.clearCompletionObservation ?? clearSwarmCompletionObservationRecord)(workspacePath, issueId, progressKey);\n}\n\nexport function swarmInferCompletionMode(): SwarmInferCompletionMode {\n const raw = process.env.PAN_SWARM_INFER_COMPLETION ?? loadCloisterConfigSync().swarm?.infer_completion;\n // PAN-2372 WI-5 / FR-8: default to 'auto' (nudge once per stable observation).\n // PAN-3691: 'auto' no longer infers ready-to-merge for a LIVE agent from\n // clean/ahead branch state alone — a live slot converges only through its\n // durable completion signal. Explicit 'nudge' / 'off' in config.yaml or\n // PAN_SWARM_INFER_COMPLETION still parse; 'nudge' and 'auto' now behave the\n // same for live agents.\n return raw === 'off' || raw === 'nudge' || raw === 'auto' ? raw : 'auto';\n}\n\nexport async function defaultGetSlotBranchAheadCount(\n workspacePath: string,\n issueId: string,\n branch: string,\n): Promise<number> {\n const baseBranch = `feature/${issueId.toLowerCase()}`;\n const slotWorkspace = branch.match(/-slot-(\\d+)(?:-attempt-\\d+)?$/)\n ? `${workspacePath}-slot-${branch.match(/-slot-(\\d+)(?:-attempt-\\d+)?$/)![1]}`\n : workspacePath;\n const roots = resolveWorkspaceRepoRootsSync(issueId, slotWorkspace);\n let total = 0;\n for (const root of roots) {\n if (root.degradedPolyrepo) return 0;\n const { stdout } = await execAsync(\n `git rev-list --count ${JSON.stringify(baseBranch)}..${JSON.stringify(branch)}`,\n { cwd: root.dir },\n );\n if (!/^\\d+$/.test(stdout.trim())) return 0;\n total += Number(stdout.trim());\n }\n return total;\n}\n\nexport async function defaultIsSlotWorktreeClean(slotWorkspacePath: string): Promise<boolean> {\n const match = /feature-([a-z]+-\\d+)-slot-\\d+$/.exec(slotWorkspacePath);\n const roots = match ? resolveWorkspaceRepoRootsSync(match[1].toUpperCase(), slotWorkspacePath) : [];\n if (roots.some(root => root.degradedPolyrepo)) return false;\n const statuses = roots.length > 0\n ? await Promise.all(roots.map(root => execAsync('git status --porcelain', { cwd: root.dir }).then(result => result.stdout)))\n : [(await execAsync('git status --porcelain', { cwd: slotWorkspacePath })).stdout];\n // PAN-2372 WI-6 / FR-9: treat state-plane-only dirt (.pan/continue.json, .pan/records/...,\n // the workspace record door) as clean. The swarm writes durable state to those paths on the\n // permanent plane, so their presence must not block a slot from being inferred complete.\n // isStatePlaneOnlyStatus already returns true for empty porcelain (vacuous every()), so one\n // shared classifier covers both cases — no local path list here. See docs/STATE-PLANE-COMMIT-POLICY.md.\n return statuses.every(isStatePlaneOnlyStatus);\n}\n\nexport async function defaultSendCompletionNudge(agentId: string, issueId: string): Promise<void> {\n await messageAgent(\n agentId,\n `You appear to have committed clean slot work but have not signaled completion. If the slot is complete, run exactly:\\n\\npan done ${issueId}`,\n 'deacon:swarm-completion-inference',\n );\n}\n","import { existsSync } from 'node:fs';\nimport { rm } from 'node:fs/promises';\nimport { Effect } from 'effect';\nimport type { ReconciledSlotItem } from '../agents/slot-reconcile.js';\nimport { getAgentStateSync } from '../agents/agent-state.js';\nimport { stopAgent } from '../agents/termination.js';\nimport {\n resolveSlotWorkspaceWorktreesSync,\n resolveWorkspaceRepoRootsSync,\n type NestedSlotWorktree,\n type SlotWorkspaceWorktrees,\n type WorkspaceRepoRoot,\n} from '../project-repos.js';\nimport type { CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\n\nconst MERGED_LIVE_SLOT_IDLE_MS = 30 * 60 * 1000;\n\nexport async function reapMergedSlotAgent(\n issueId: string,\n slot: Pick<ReconciledSlotItem, 'slotIndex' | 'agentId'>,\n stopSlotAgent: (agentId: string) => Promise<void> = id => Effect.runPromise(stopAgent(id)),\n): Promise<string> {\n const agentId = slot.agentId ?? `agent-${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n try {\n await stopSlotAgent(agentId);\n return `[swarm] reaped merged agent ${agentId}`;\n } catch (err) {\n return `[swarm] could not reap merged agent ${agentId}: ${err instanceof Error ? err.message : String(err)}`;\n }\n}\n\nexport interface MergedSlotGcResult {\n actions: string[];\n /**\n * Merged-status slots that were NOT fully cleaned up this pass (PAN-3695).\n * Their items read as completed in the plan overlay, but the nested work is\n * not yet integrated and pushed — downstream dispatch and finalization must\n * be gated on this set.\n */\n uncleared: ReconciledSlotItem[];\n}\n\nexport async function gcMergedSlots(\n issueId: string,\n workspacePath: string,\n slots: ReconciledSlotItem[],\n deps: Parameters<typeof gcMergedSlotsWithStatus>[3],\n): Promise<string[]> {\n return (await gcMergedSlotsWithStatus(issueId, workspacePath, slots, deps)).actions;\n}\n\nexport async function gcMergedSlotsWithStatus(\n issueId: string,\n workspacePath: string,\n slots: ReconciledSlotItem[],\n deps: Pick<CoordinateSwarmSlotsDeps, 'runGitCommand' | 'clearSlotAssignment' | 'listSessionNames'> & {\n slotWorktreeExists?: (path: string) => boolean;\n getAgentLastActivity?: (agentId: string) => string | undefined;\n stopSlotAgent?: (agentId: string) => Promise<void>;\n listSlotWorkspaceWorktrees?: (issueId: string, slotWorkspace: string) => SlotWorkspaceWorktrees;\n listFeatureWorkspaceRepoRoots?: (issueId: string, workspacePath: string) => WorkspaceRepoRoot[];\n removeDirectory?: (path: string) => Promise<void>;\n },\n): Promise<MergedSlotGcResult> {\n const actions: string[] = [];\n const uncleared: ReconciledSlotItem[] = [];\n // A freshly dispatched slot branch points at the feature branch HEAD, so\n // `--merged HEAD` classifies it as merged before the agent's first commit.\n // Without a liveness guard, gc destroys the worktree/branch/assignment under\n // the live agent and the item redispatches at the next index.\n const sessionNames = new Set(await deps.listSessionNames());\n const worktreeExists = deps.slotWorktreeExists ?? existsSync;\n\n for (const slot of slots) {\n if (slot.status !== 'merged') continue;\n\n const agentId = slot.agentId ?? `agent-${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n // PAN-3695: decide liveness up front, but reap the agent only AFTER every\n // nested branch is merged, ancestry-verified, and the workspace removed.\n // Reaping first stranded unmerged nested work with no agent left to answer\n // for it (MIN-888 slot 4).\n let reapAction: string | null = null;\n if (sessionNames.has(agentId)) {\n const completionProven = slot.mergedVia === 'completed-status';\n const lastActivity = (deps.getAgentLastActivity ?? (id => getAgentStateSync(id)?.lastActivity))(agentId);\n const idleFor = lastActivity ? Date.now() - Date.parse(lastActivity) : 0;\n if (!completionProven && (!Number.isFinite(idleFor) || idleFor < MERGED_LIVE_SLOT_IDLE_MS)) {\n actions.push(`[swarm] gc skipped slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: agent session alive`);\n uncleared.push(slot);\n continue;\n }\n reapAction = completionProven\n ? `[swarm] gc reaped merged agent ${agentId}`\n : `[swarm] gc reaped idle merged agent ${agentId}`;\n }\n\n const slotWorkspace = `${workspacePath}-slot-${slot.slotIndex}`;\n const slotBranch = slot.branch ?? `feature/${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n if (worktreeExists(slotWorkspace)) {\n const removed = await removeSlotWorkspace(issueId, workspacePath, slotWorkspace, slotBranch, slot, deps, actions);\n if (!removed) {\n uncleared.push(slot);\n continue;\n }\n }\n // Polyrepo wrapper repos hold no local slot branch (PAN-3695): an absent\n // outer branch is an idempotent success once every nested branch has\n // verified ancestry, not a delete failure that wedges the slot forever.\n const branchExists = await slotBranchExists(deps.runGitCommand, workspacePath, slotBranch);\n if (branchExists === null) {\n actions.push(`[swarm] gc deferred slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: branch state of ${slotBranch} could not be determined`);\n uncleared.push(slot);\n continue;\n }\n if (branchExists) {\n try {\n await deps.runGitCommand(`git branch -D ${JSON.stringify(slotBranch)}`, workspacePath);\n } catch (error) {\n actions.push(`[swarm] gc deferred slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: branch delete failed: ${error instanceof Error ? error.message : String(error)}`);\n uncleared.push(slot);\n continue;\n }\n }\n await deps.clearSlotAssignment(workspacePath, issueId, slot.slotIndex, slot.itemId);\n if (reapAction) {\n try {\n await (deps.stopSlotAgent ?? (id => Effect.runPromise(stopAgent(id))))(agentId);\n actions.push(reapAction);\n } catch (error) {\n actions.push(`[swarm] could not reap merged agent ${agentId}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n actions.push(`[swarm] gc slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}`);\n }\n\n return { actions, uncleared };\n}\n\n/**\n * Whether the outer workspace has a local `slotBranch`, or null when that\n * cannot be determined. `git branch --list` exits 0 with empty output for an\n * absent branch, so a throw is always an indeterminate state, never \"absent\".\n */\nasync function slotBranchExists(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n workspacePath: string,\n slotBranch: string,\n): Promise<boolean | null> {\n try {\n const result = await runGitCommand(`git branch --list ${JSON.stringify(slotBranch)}`, workspacePath) as { stdout?: unknown };\n return String(result?.stdout ?? '').trim().length > 0;\n } catch {\n return null;\n }\n}\n\ntype SlotRemovalDeps = Pick<CoordinateSwarmSlotsDeps, 'runGitCommand'> & {\n listSlotWorkspaceWorktrees?: (issueId: string, slotWorkspace: string) => SlotWorkspaceWorktrees;\n listFeatureWorkspaceRepoRoots?: (issueId: string, workspacePath: string) => WorkspaceRepoRoot[];\n removeDirectory?: (path: string) => Promise<void>;\n};\n\n/**\n * Remove a merged slot's workspace (PAN-3686). A polyrepo slot workspace is an\n * aggregate directory whose registered git worktrees are the nested sub-repo\n * checkouts (<slot>/fe, <slot>/api, …), each owned by a different parent\n * repository. Git cannot remove the aggregate root while nested worktrees\n * live inside it (\"Directory not empty\"), so every nested worktree is\n * detached through the workspace abstraction first, in its owning parent\n * repo.\n *\n * Cleanup is atomic in intent: a read-only preflight proves every nested repo\n * safe AND removable, and decides the aggregate root's removal route, before\n * the first mutation. Discovering an unsafe or unremovable nested checkout\n * after an earlier one was already detached is the Tier-1 partial-cleanup\n * defect the preflight exists to prevent (PAN-3686 post-deploy review). Any\n * preflight failure defers the whole slot with zero removals.\n *\n * Never discards unmerged or locally-modified nested work. PAN-3695: a slot\n * that reached merged status with unmerged nested branches is INTEGRATED here,\n * not just deferred — each unmerged nested slot branch is merged through the\n * canonical nested merge path (`git merge --no-ff` into the per-repo base\n * feature-workspace checkout, same as verifyAndMergeSlot) and ancestry is\n * re-verified repo by repo before any removal. A repo whose merge is pending\n * or failed defers the whole slot with an actionable repo-specific reason;\n * successful per-repo merges are preserved and the next pass retries the rest.\n * Uncommitted nested changes still defer the slot untouched.\n */\nasync function removeSlotWorkspace(\n issueId: string,\n workspacePath: string,\n slotWorkspace: string,\n slotBranch: string,\n slot: Pick<ReconciledSlotItem, 'slotIndex' | 'itemId'>,\n deps: SlotRemovalDeps,\n actions: string[],\n): Promise<boolean> {\n const defer = (reason: string): false => {\n actions.push(`[swarm] gc deferred slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: ${reason}`);\n return false;\n };\n\n const { isPolyrepo, nested } = (deps.listSlotWorkspaceWorktrees ?? resolveSlotWorkspaceWorktreesSync)(issueId, slotWorkspace);\n\n // ── Integration (PAN-3695): merge unmerged nested slot branches through the\n // canonical nested merge path before any removal. A merged-status slot whose\n // nested work was never merged must be merged by the coordinator here —\n // mergeReadySlots only consumes ready-to-merge slots, so a GC-only deferral\n // is terminal and strands the work (MIN-888 slot 4). These merges mutate the\n // per-repo BASE feature checkouts, never the slot workspace being removed.\n if (nested.length > 0) {\n const mergeFailures = await mergeNestedSlotBranches(issueId, workspacePath, slotBranch, nested, deps);\n if (mergeFailures.length > 0) {\n return defer(`nested merge incomplete: ${mergeFailures.join('; ')}`);\n }\n }\n\n // ── Preflight (read-only): no mutations below this line until every check\n // for every nested repo and the aggregate root has passed. ──\n\n for (const worktree of nested) {\n const blocked = await nestedWorktreeDirtyReason(deps.runGitCommand, worktree);\n if (blocked) return defer(`preserving nested work: ${blocked}`);\n }\n\n // Each nested checkout must be a registered worktree of its owning parent\n // repo. The registration is what `git worktree remove` needs to succeed;\n // checking it up front keeps a mid-loop failure from leaving the slot half\n // detached. Nested gitdirs live in their owning parent repos, never under\n // the aggregate, so this lookup does not depend on aggregate metadata that\n // an earlier partial removal may have invalidated.\n for (const worktree of nested) {\n const registered = await isRegisteredWorktree(deps.runGitCommand, worktree.parentRepo, worktree.dir);\n if (registered === null) {\n return defer(`${worktree.repoKey}: worktree registration in parent repo could not be determined`);\n }\n if (!registered) {\n return defer(`${worktree.repoKey}: nested worktree is not registered in its parent repo (an earlier partial cleanup may have detached it); run pan swarm reset ${issueId}`);\n }\n }\n\n // Decide the aggregate root's removal route up front: a registered working\n // tree is removed through git; an unregistered plain directory (polyrepo\n // layouts that register only the nested paths, or a registration lost to an\n // earlier partial cleanup) is removed as a directory after nested detach.\n const aggregateRegistered = await isRegisteredWorktree(deps.runGitCommand, workspacePath, slotWorkspace);\n if (aggregateRegistered === null) {\n return defer('aggregate worktree registration could not be determined');\n }\n\n // ── Mutation: preflight proved every nested worktree safe and removable. ──\n\n const detached: string[] = [];\n for (const worktree of nested) {\n try {\n await deps.runGitCommand(`git worktree remove --force ${JSON.stringify(worktree.dir)}`, worktree.parentRepo);\n detached.push(worktree.repoKey);\n } catch (error) {\n // A failure here escaped preflight (I/O race, lock); report which nested\n // worktrees were already detached so the partial state is on record.\n const partial = detached.length > 0 ? ` (already detached: ${detached.join(', ')})` : '';\n return defer(`nested worktree remove failed in ${worktree.repoKey}${partial}: ${error instanceof Error ? error.message : String(error)}`);\n }\n try {\n await deps.runGitCommand(`git branch -D ${JSON.stringify(slotBranch)}`, worktree.parentRepo);\n } catch (error) {\n // The worktree is already detached; a leftover merged branch is cosmetic.\n actions.push(`[swarm] gc note slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: nested branch ${slotBranch} delete failed in ${worktree.repoKey}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n if (!aggregateRegistered && isPolyrepo) {\n try {\n await (deps.removeDirectory ?? (path => rm(path, { recursive: true, force: true })))(slotWorkspace);\n return true;\n } catch (rmError) {\n return defer(`aggregate directory remove failed: ${rmError instanceof Error ? rmError.message : String(rmError)}`);\n }\n }\n\n try {\n await deps.runGitCommand(`git worktree remove --force ${JSON.stringify(slotWorkspace)}`, workspacePath);\n return true;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n // The registration vanished between preflight and removal (or preflight\n // was bypassed): the aggregate root is a plain directory git rightfully\n // refuses to remove.\n if (isPolyrepo && /is not a working tree/.test(message)) {\n try {\n await (deps.removeDirectory ?? (path => rm(path, { recursive: true, force: true })))(slotWorkspace);\n return true;\n } catch (rmError) {\n return defer(`aggregate directory remove failed: ${rmError instanceof Error ? rmError.message : String(rmError)}`);\n }\n }\n return defer(`worktree remove failed: ${message}`);\n }\n}\n\n/**\n * Whether `worktreeDir` is a registered worktree of the repository at\n * `repoDir`, or null when the registration state cannot be determined. The\n * porcelain list always names at least the repository's own main worktree, so\n * a response with no `worktree` lines is an anomaly, not proof of \"absent\".\n * Shared with `pan swarm reset` (PAN-3713), which must distinguish an\n * already-unregistered nested worktree (the desired postcondition) from a\n * genuine removal failure.\n */\nexport async function isRegisteredWorktree(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n repoDir: string,\n worktreeDir: string,\n): Promise<boolean | null> {\n try {\n const result = await runGitCommand('git worktree list --porcelain', repoDir) as { stdout?: unknown };\n const paths = String(result?.stdout ?? '')\n .split('\\n')\n .filter(line => line.startsWith('worktree '))\n .map(line => line.slice('worktree '.length).trim());\n if (paths.length === 0) return null;\n return paths.includes(worktreeDir);\n } catch {\n return null;\n }\n}\n\n/**\n * Merge every nested slot branch that still has commits its per-repo feature\n * branch lacks, through the canonical nested merge path (PAN-3695): the merge\n * runs in the repo's base feature-workspace checkout, then ancestry is\n * re-verified by re-counting `featureBranch..slotBranch` — a merge command\n * exiting 0 is not proof the slot head landed — and the parent feature branch\n * is pushed to origin so the work actually leaves the machine. A dirty base\n * checkout refuses the merge, and a missing or degraded base checkout fails\n * the repo outright — without it, remote durability cannot be verified, even\n * when the local feature branch already contains the slot head. Every repo is\n * attempted even after a sibling fails, so successful merges are preserved and\n * only the failed repos are retried on the next pass. Returns the per-repo\n * failure reasons; an empty list means every nested repo's slot branch is an\n * ancestor of its pushed feature branch.\n */\nasync function mergeNestedSlotBranches(\n issueId: string,\n workspacePath: string,\n slotBranch: string,\n nested: NestedSlotWorktree[],\n deps: SlotRemovalDeps,\n): Promise<string[]> {\n const failures: string[] = [];\n const baseRoots = new Map(\n (deps.listFeatureWorkspaceRepoRoots ?? resolveWorkspaceRepoRootsSync)(issueId, workspacePath)\n .map(root => [root.repoKey, root]),\n );\n for (const worktree of nested) {\n const ahead = await countCommitsAhead(deps.runGitCommand, worktree.parentRepo, worktree.featureBranch, slotBranch);\n if (ahead === null) {\n failures.push(`${worktree.repoKey}: merge state of ${slotBranch} against ${worktree.featureBranch} could not be determined`);\n continue;\n }\n const baseRoot = baseRoots.get(worktree.repoKey);\n // Remote durability must be verified for every nested repo before cleanup —\n // a missing or degraded base checkout makes that impossible, even when the\n // local feature branch already contains the slot head (PAN-3695 review).\n if (!baseRoot || baseRoot.degradedPolyrepo) {\n failures.push(ahead > 0\n ? `${worktree.repoKey}: no base feature-workspace checkout for ${worktree.featureBranch} — cannot merge ${slotBranch} (${ahead} unmerged commit(s))`\n : `${worktree.repoKey}: no base feature-workspace checkout for ${worktree.featureBranch} — cannot verify and push remote durability`);\n continue;\n }\n if (ahead > 0) {\n // Never merge into a dirty base checkout: tracked local changes there are\n // someone's uncommitted work on the parent feature branch.\n const baseDirty = await baseCheckoutDirtyReason(deps.runGitCommand, worktree, baseRoot, slotBranch);\n if (baseDirty) {\n failures.push(baseDirty);\n continue;\n }\n try {\n await deps.runGitCommand(`git merge --no-ff ${JSON.stringify(slotBranch)}`, baseRoot.dir);\n } catch (error) {\n await deps.runGitCommand('git merge --abort', baseRoot.dir).catch(() => {});\n failures.push(`${worktree.repoKey}: ${slotBranch} did not merge cleanly into ${worktree.featureBranch}: ${error instanceof Error ? error.message : String(error)}`);\n continue;\n }\n const remaining = await countCommitsAhead(deps.runGitCommand, worktree.parentRepo, worktree.featureBranch, slotBranch);\n if (remaining !== 0) {\n failures.push(`${worktree.repoKey}: ancestry verification failed — ${slotBranch} still has ${remaining ?? 'an unknown number of'} commit(s) not in ${worktree.featureBranch} after merge`);\n continue;\n }\n }\n // Push the parent feature branch so the merged nested work actually lands\n // on the remote — a local-only merge is invisible to the outer pipeline\n // (MIN-888 needed manual pushes). This also retries a push that failed on\n // an earlier pass: the local merge is already an ancestor, but the\n // origin..feature count still shows the unpushed commits.\n const unpushed = await countCommitsAhead(deps.runGitCommand, baseRoot.dir, `origin/${worktree.featureBranch}`, worktree.featureBranch);\n if (ahead > 0 || unpushed === null || unpushed > 0) {\n try {\n await deps.runGitCommand(`git push origin ${JSON.stringify(worktree.featureBranch)}`, baseRoot.dir);\n } catch (error) {\n failures.push(`${worktree.repoKey}: merged ${slotBranch} into ${worktree.featureBranch} but push to origin failed: ${error instanceof Error ? error.message : String(error)}`);\n continue;\n }\n }\n }\n return failures;\n}\n\n/**\n * Why a base feature-workspace checkout must not receive a merge right now, or\n * null when it is clean. Tracked local modifications block the merge (git would\n * refuse or, worse, entangle someone's uncommitted work); untracked files do\n * not, so they are ignored here.\n */\nasync function baseCheckoutDirtyReason(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n worktree: NestedSlotWorktree,\n baseRoot: WorkspaceRepoRoot,\n slotBranch: string,\n): Promise<string | null> {\n try {\n const status = await runGitCommand('git status --porcelain --untracked-files=no', baseRoot.dir) as { stdout?: unknown };\n if (String(status?.stdout ?? '').trim().length > 0) {\n return `${worktree.repoKey}: base feature checkout of ${worktree.featureBranch} has uncommitted changes — refusing to merge ${slotBranch}`;\n }\n } catch {\n return `${worktree.repoKey}: base feature checkout state of ${worktree.featureBranch} could not be determined — refusing to merge ${slotBranch}`;\n }\n return null;\n}\n\n/**\n * Why a nested slot worktree must be preserved, or null when it is safe to\n * remove. Any tracked change or non-ignored untracked file blocks removal\n * (ignored build artifacts do not). Merge state is handled separately by\n * mergeNestedSlotBranches before this runs.\n */\nasync function nestedWorktreeDirtyReason(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n worktree: NestedSlotWorktree,\n): Promise<string | null> {\n try {\n const status = await runGitCommand('git status --porcelain --untracked-files=all', worktree.dir) as { stdout?: unknown };\n if (String(status?.stdout ?? '').trim().length > 0) {\n return `${worktree.repoKey}: worktree has uncommitted changes`;\n }\n } catch {\n return `${worktree.repoKey}: worktree status could not be determined`;\n }\n return null;\n}\n\nasync function countCommitsAhead(\n runGitCommand: CoordinateSwarmSlotsDeps['runGitCommand'],\n parentRepo: string,\n featureBranch: string,\n slotBranch: string,\n): Promise<number | null> {\n try {\n const result = await runGitCommand(\n `git rev-list --count ${JSON.stringify(featureBranch)}..${JSON.stringify(slotBranch)}`,\n parentRepo,\n ) as { stdout?: unknown };\n const stdout = String(result?.stdout ?? '').trim();\n // An empty or non-numeric stdout is not evidence of \"zero commits ahead\" —\n // Number('') === 0 would misread it as safely merged. Only a digits-only\n // payload proves the count.\n if (!/^\\d+$/.test(stdout)) return null;\n return Number(stdout);\n } catch {\n return null;\n }\n}\n","/**\n * PAN-2385 — tiered-execution commit hooks for the swarm dispatch path.\n *\n * The tier-feed and tier-supervisor libraries shipped with PAN-1791/2222 but\n * had no production caller: commits landed and neither the standing crew nor\n * the subscribe-policy supervisor ever heard about them (found live during\n * the first Standing Crew run, PAN-2383). This module is the ignition.\n *\n * Fired from mergeReadySlots after a slot's bead commits merge into the\n * feature workspace:\n * 1. broadcastCommit — every live slot agent hears the rendered diff\n * (ingestion-only; everyone-hears-everything per docs/TIERED-EXECUTION.md).\n * 2. shouldSupervise(item, subscribe) → deliverCommitForReview — the\n * standing supervisor reviews per its subscribe policy. This is\n * independent of supervisor.owns_inspection, which only routes\n * `pan inspect`.\n *\n * Both hooks are best-effort: the commit has already landed, so a delivery\n * failure logs loudly and never blocks the merge loop.\n */\n\nimport { Effect } from 'effect';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { sessionExists } from '../tmux.js';\nimport { loadConfigSync as loadYamlConfig } from '../config-yaml.js';\nimport { resolveTieredExecutionEnabled, type ValidatedTieredExecutionConfig } from '../agents/tier-table.js';\nimport { resolveTier } from '../agents/resolve-tier.js';\nimport { broadcastCommit } from '../agents/tier-feed.js';\nimport {\n deliverCommitForReview,\n loadPrdDraft,\n shouldSupervise,\n spawnTierSupervisor,\n supervisorAgentId,\n} from '../agents/tier-supervisor.js';\nimport { listSlotAssignments } from '../agents/slot-reconcile.js';\nimport type { XBriefDocument, XBriefItem } from '../xbrief/types.js';\n\nconst execAsync = promisify(exec);\n\nexport interface FireTieredCommitHooksOptions {\n issueId: string;\n /** Feature workspace the slot branch just merged into (its HEAD is the commit). */\n workspacePath: string;\n /** The bead the merged commits implement. */\n item: XBriefItem;\n /** The merged plan view — plan.metadata carries the per-issue tiered override. */\n doc: XBriefDocument;\n}\n\nexport interface FireTieredCommitHooksDeps {\n loadConfig: typeof loadYamlConfig;\n getHeadSha: (workspacePath: string) => Promise<string>;\n listAssignments: typeof listSlotAssignments;\n isSessionAlive: (sessionName: string) => Promise<boolean>;\n broadcast: typeof broadcastCommit;\n ensureSupervisor: typeof spawnTierSupervisor;\n deliverReview: typeof deliverCommitForReview;\n loadPrd: typeof loadPrdDraft;\n}\n\nconst defaultDeps: FireTieredCommitHooksDeps = {\n loadConfig: loadYamlConfig,\n getHeadSha: async (workspacePath) =>\n (await execAsync('git rev-parse HEAD', { cwd: workspacePath })).stdout.trim(),\n listAssignments: listSlotAssignments,\n isSessionAlive: (sessionName) => Effect.runPromise(sessionExists(sessionName)),\n broadcast: broadcastCommit,\n ensureSupervisor: spawnTierSupervisor,\n deliverReview: deliverCommitForReview,\n loadPrd: loadPrdDraft,\n};\n\n/** Resolve the tier name a bead belongs to, for the feed header. Falls back to\n * the slot label when the tier table cannot resolve the item (fail-open here is\n * correct: the feed is informational and the commit has already landed). */\nfunction tierNameForItem(\n item: XBriefItem | undefined,\n tiered: ValidatedTieredExecutionConfig,\n slotIndex: number,\n): string {\n if (item) {\n try {\n return resolveTier(item, tiered).tierName;\n } catch {\n // fall through to the slot label\n }\n }\n return `slot-${slotIndex}`;\n}\n\n/**\n * Fire the commit feed + supervisor review for a bead whose commits just\n * merged into the feature workspace. Returns deacon action-log lines.\n */\nexport async function fireTieredCommitHooks(\n options: FireTieredCommitHooksOptions,\n deps: FireTieredCommitHooksDeps = defaultDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n const { issueId, workspacePath, item, doc } = options;\n\n let tiered: ValidatedTieredExecutionConfig | undefined;\n try {\n tiered = deps.loadConfig().config.tieredExecution;\n } catch (err) {\n actions.push(`[tiered] skipped commit hooks for ${issueId}: config load failed (${err instanceof Error ? err.message : String(err)})`);\n return actions;\n }\n if (!tiered || !resolveTieredExecutionEnabled(tiered, doc.plan.metadata)) return actions;\n\n let sha: string;\n try {\n sha = await deps.getHeadSha(workspacePath);\n } catch (err) {\n actions.push(`[tiered] commit hooks for ${issueId} item ${item.id} could not resolve HEAD: ${err instanceof Error ? err.message : String(err)}`);\n return actions;\n }\n\n // ── 1. Broadcast to every live standing slot agent ────────────────────────\n try {\n const itemsById = new Map(doc.plan.items.map((planItem) => [planItem.id, planItem]));\n const assignments = deps.listAssignments(issueId, workspacePath);\n const listeners: Array<{ tierName: string; agentId: string }> = [];\n for (const assignment of assignments) {\n if (!assignment.agentId) continue;\n if (!(await deps.isSessionAlive(assignment.agentId))) continue;\n listeners.push({\n tierName: tierNameForItem(itemsById.get(assignment.itemId), tiered, assignment.slotIndex),\n agentId: assignment.agentId,\n });\n }\n if (listeners.length > 0) {\n const deliveries = await deps.broadcast({\n workspace: workspacePath,\n issueId,\n sha,\n itemTitle: item.title,\n itemId: item.id,\n tiers: listeners,\n feedConfig: tiered.feed,\n });\n const delivered = deliveries.filter((delivery) => delivery.result.ok).length;\n actions.push(`[tiered] broadcast commit ${sha.slice(0, 10)} (item ${item.id}) for ${issueId} to ${delivered}/${listeners.length} standing agents`);\n }\n } catch (err) {\n actions.push(`[tiered] feed broadcast FAILED for ${issueId} item ${item.id}: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n // ── 2. Supervisor review per subscribe policy ─────────────────────────────\n try {\n const supervisor = tiered.supervisor;\n if (supervisor && shouldSupervise(item, supervisor.subscribe)) {\n const agentId = supervisorAgentId(issueId);\n if (!(await deps.isSessionAlive(agentId))) {\n await deps.ensureSupervisor(issueId, supervisor, { workspace: workspacePath });\n actions.push(`[tiered] spawned standing supervisor ${agentId} for ${issueId}`);\n }\n const prdMarkdown = await deps.loadPrd(workspacePath, issueId).catch(() => undefined);\n await deps.deliverReview({\n supervisorAgentId: agentId,\n workspacePath,\n issueId,\n item,\n sha,\n itemId: item.id,\n prdMarkdown,\n });\n actions.push(`[tiered] supervisor review dispatched for ${issueId} item ${item.id} (subscribe=${supervisor.subscribe})`);\n }\n } catch (err) {\n actions.push(`[tiered] supervisor review FAILED for ${issueId} item ${item.id}: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n return actions;\n}\n","/**\n * PAN-2543 shared autonomous re-drive gate.\n *\n * Recovery entry points classify agent gates, apply the intent policy, and\n * consult the cached memory verdict through this one function before acting.\n * Callers still reserve their role-specific concurrency slot after admission.\n */\nimport { existsSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport {\n decideResumeGate,\n getAgentResumeGateBlockReason,\n type AgentState,\n type ResumeGateContext,\n type ResumeGateDecision,\n} from '../agents/agent-state.js';\nimport {\n detectPendingOperatorDecision,\n type PendingOperatorDecision,\n} from '../agents/pending-decision-gate.js';\nimport { getCachedMemoryVerdict } from './memory-verdict-cache.js';\n\nexport const REDRIVE_ENTRY_POINTS = ['dead-session-rework', 'swarm-slot-requeue'] as const;\n\nexport type RedriveGateDecision =\n | { decision: 'proceed'; gateDecision: Extract<ResumeGateDecision, { decision: 'proceed' }> }\n | { decision: 'defer'; reason: string; needsYou?: true };\n\nexport function decideAutonomousRedrive(\n state: Pick<AgentState, 'paused' | 'pausedReason' | 'yieldedByScheduler' | 'troubled' | 'troubledAt' | 'stoppedByUser' | 'consecutiveFailures'>,\n context: ResumeGateContext = {},\n): RedriveGateDecision {\n const gateDecision = decideResumeGate(getAgentResumeGateBlockReason(state), 'autonomous', context);\n if (gateDecision.decision !== 'proceed') {\n return {\n decision: 'defer',\n reason: gateDecision.reason,\n ...(gateDecision.decision === 'block' && gateDecision.needsYou ? { needsYou: true } : {}),\n };\n }\n\n const memoryVerdict = getCachedMemoryVerdict();\n if (memoryVerdict && memoryVerdict.band !== 'ok') {\n return { decision: 'defer', reason: `memory pressure is ${memoryVerdict.band}` };\n }\n return { decision: 'proceed', gateDecision };\n}\n\nexport interface AgentAutonomousRedriveDeps {\n detectPendingOperatorDecision?: (agentId: string) => Promise<PendingOperatorDecision | null>;\n}\n\nexport async function decideAgentAutonomousRedrive(\n state: Pick<AgentState, 'paused' | 'pausedReason' | 'yieldedByScheduler' | 'troubled' | 'troubledAt' | 'stoppedByUser' | 'consecutiveFailures'>,\n agentDir: string,\n owesRework: boolean,\n deps: AgentAutonomousRedriveDeps = {},\n): Promise<RedriveGateDecision> {\n const hasCompletedHandoff = existsSync(join(agentDir, 'completed'))\n || existsSync(join(agentDir, 'completed.processed'));\n const admission = decideAutonomousRedrive(state, { hasCompletedHandoff, owesRework });\n if (admission.decision === 'defer') return admission;\n\n const agentId = basename(agentDir);\n const detectPendingDecision = deps.detectPendingOperatorDecision ?? detectPendingOperatorDecision;\n const pendingDecision = await detectPendingDecision(agentId);\n if (pendingDecision) {\n return {\n decision: 'defer',\n reason: `agent is waiting on an operator decision (${pendingDecision.reason})`,\n needsYou: true,\n };\n }\n\n return admission;\n}\n","import { existsSync } from 'node:fs';\nimport type { ReconciledSlotItem, SlotReconcileResult } from '../agents/slot-reconcile.js';\nimport {\n readIssueRecordForWorkspaceSync,\n type PanIssueRecord,\n type PanIssueSwarmSupersededAttempt,\n} from '../pan-dir/record.js';\nimport { updateIssueRecordForWorkspace } from '../pan-dir/record-update.js';\nimport { createMinimalIssueRecord, clearSwarmSlotCompletion } from './deacon-swarm-record.js';\nimport type { PersistedTaskOperation } from '../xbrief/dag.js';\nimport type { XBriefDocument } from '../xbrief/types.js';\nimport { acknowledgeRecoveryTrip, recordRecoveryFailure } from './recovery-trip.js';\nimport { getAgentDir, getAgentStateSync } from '../agents/agent-state.js';\nimport { decideAgentAutonomousRedrive, decideAutonomousRedrive } from './redrive-gate.js';\n\n/** Patrol GC never removes forensic attempts; configured issue close-out owns teardown. */\nexport const SWARM_SUPERSEDED_RETENTION = 'issue-close-out' as const;\n\nexport interface FailedSlotArchiveDeps {\n runGitCommand: (command: string, cwd: string) => Promise<unknown>;\n clearSlotAssignment: (workspacePath: string, issueId: string, slotIndex: number, itemId?: string) => Promise<void>;\n}\n\ninterface FailedSlotRequeueDeps extends FailedSlotArchiveDeps {\n applyTaskOperationToPlanFile: (\n issueId: string,\n operation: PersistedTaskOperation,\n workspacePath?: string,\n ) => Promise<unknown>;\n decideAgentAutonomousRedrive?: typeof decideAgentAutonomousRedrive;\n decideAutonomousRedrive?: typeof decideAutonomousRedrive;\n recordRecoveryFailure?: typeof recordRecoveryFailure;\n acknowledgeRecoveryTrip?: typeof acknowledgeRecoveryTrip;\n}\ninterface ClassifiedSlot extends ReconciledSlotItem { lifecycle: string; reason?: string }\n\nexport function nextSwarmSlotIndex(record: PanIssueRecord | undefined, reconciled: SlotReconcileResult): number {\n return Math.max(0,\n ...reconciled.branches.map(value => value.slotIndex),\n ...reconciled.agents.map(value => value.slotIndex),\n ...reconciled.inFlight.map(value => value.slotIndex),\n ...(record?.swarm?.slotAssignments ?? []).map(value => value.slotIndex),\n ...(record?.swarm?.supersededAttempts ?? []).map(value => value.slotIndex),\n ) + 1;\n}\n\nexport async function archiveFailedSwarmSlot(\n issueId: string,\n workspacePath: string,\n slot: ReconciledSlotItem & { reason?: string },\n deps: FailedSlotArchiveDeps,\n now = new Date(),\n): Promise<PanIssueSwarmSupersededAttempt> {\n const normalized = issueId.toUpperCase();\n const suffix = now.toISOString().replace(/[-:.TZ]/g, '');\n const archivedStem = `slot-${slot.slotIndex}-failed-${suffix}`;\n const oldWorktree = `${workspacePath}-slot-${slot.slotIndex}`;\n const archivedWorktree = `${workspacePath}-${archivedStem}`;\n const oldBranch = slot.branch ?? `feature/${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n const archivedBranch = `feature/${issueId.toLowerCase()}-${archivedStem}`;\n\n if (existsSync(oldWorktree)) {\n await deps.runGitCommand(`git worktree move ${JSON.stringify(oldWorktree)} ${JSON.stringify(archivedWorktree)}`, workspacePath);\n }\n await deps.runGitCommand(`git branch -m ${JSON.stringify(oldBranch)} ${JSON.stringify(archivedBranch)}`, workspacePath);\n\n const attempt: PanIssueSwarmSupersededAttempt = {\n slotIndex: slot.slotIndex,\n itemId: slot.itemId,\n agentId: slot.agentId,\n branch: oldBranch,\n archivedBranch,\n ...(existsSync(archivedWorktree) ? { archivedWorktree } : {}),\n reason: slot.reason ?? 'failed swarm slot',\n supersededAt: now.toISOString(),\n };\n await updateIssueRecordForWorkspace(workspacePath, normalized, existing => ({\n ...existing,\n swarm: {\n ...(existing.swarm ?? {}),\n supersededAttempts: [...(existing.swarm?.supersededAttempts ?? []), attempt],\n },\n }));\n // PAN-2372 WI-4 / FR-6: this slot is being superseded/requeued — clear its\n // durable completion marker so a fresh attempt on the same slotIndex is not\n // falsely observed as already-completed by classifyInFlightSlots.\n await clearSwarmSlotCompletion(workspacePath, normalized, slot.slotIndex);\n await deps.clearSlotAssignment(workspacePath, normalized, slot.slotIndex, slot.itemId);\n return attempt;\n}\n\nexport async function requeueFailedSwarmSlots(\n issueId: string,\n workspacePath: string,\n classified: ClassifiedSlot[],\n doc: XBriefDocument,\n reconciled: SlotReconcileResult,\n deps: FailedSlotRequeueDeps,\n blockedSlotIndexes: Set<number> = new Set(),\n): Promise<{ doc: XBriefDocument; actions: string[] }> {\n let nextDoc = doc;\n const actions: string[] = [];\n const decideAgentRedrive = deps.decideAgentAutonomousRedrive ?? decideAgentAutonomousRedrive;\n const decideUnownedRedrive = deps.decideAutonomousRedrive ?? decideAutonomousRedrive;\n const recordFailure = deps.recordRecoveryFailure ?? recordRecoveryFailure;\n const acknowledgeTrip = deps.acknowledgeRecoveryTrip ?? acknowledgeRecoveryTrip;\n for (const slot of classified.filter(candidate => candidate.lifecycle === 'failed')) {\n if (blockedSlotIndexes.has(slot.slotIndex)) {\n actions.push(`[swarm] skipped requeue slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: failed-merge block — awaiting operator recovery`);\n continue;\n }\n const pendingDecisionRecoveryPath = 'swarm-slot-pending-operator-decision';\n const pendingDecisionGeneration = `${slot.itemId}:${slot.agentId ?? `slot-${slot.slotIndex}`}`;\n const admission = slot.agentId\n ? await decideAgentRedrive(\n getAgentStateSync(slot.agentId) ?? {},\n getAgentDir(slot.agentId),\n true,\n )\n : decideUnownedRedrive({}, { owesRework: true });\n if (admission.decision === 'defer') {\n if (admission.needsYou) {\n const failure = await recordFailure(\n workspacePath,\n issueId,\n pendingDecisionRecoveryPath,\n pendingDecisionGeneration,\n 1,\n );\n if (failure.emitNeedsYou) {\n actions.push(`[swarm] needs-you ${issueId}: failed slot ${slot.slotIndex} ${admission.reason}`);\n }\n }\n actions.push(`[swarm] deferred failed slot ${slot.slotIndex}: ${admission.reason}`);\n continue;\n }\n await acknowledgeTrip(\n workspacePath,\n issueId,\n pendingDecisionRecoveryPath,\n pendingDecisionGeneration,\n );\n const attempt = await archiveFailedSwarmSlot(issueId, workspacePath, slot, deps);\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'unblock', itemId: slot.itemId, writerId: 'deacon-swarm', reason: `Redispatch after ${slot.reason ?? 'slot failure'}`,\n }, workspacePath);\n reconciled.inFlight = reconciled.inFlight.filter(candidate => candidate.itemId !== slot.itemId);\n reconciled.superseded = [...(reconciled.superseded ?? []), attempt];\n nextDoc = { ...nextDoc, plan: { ...nextDoc.plan, items: nextDoc.plan.items.map(item => item.id === slot.itemId ? { ...item, status: 'pending' as const } : item) } };\n actions.push(`[swarm] archived failed slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}`);\n const failure = await recordFailure(workspacePath, issueId, 'swarm-slot-requeue', slot.itemId);\n if (failure.emitNeedsYou) actions.push(`[swarm] needs-you ${issueId}: slot item ${slot.itemId} failed ${failure.trip.tripCount} times`);\n }\n return { doc: nextDoc, actions };\n}\n\nexport function applySupersededSlotHighWater(\n occupied: Set<number>,\n reconciled: SlotReconcileResult,\n configuredMax: number,\n): number {\n const highWater = Math.max(0, ...(reconciled.superseded ?? []).map(attempt => attempt.slotIndex));\n if (highWater > 0) for (let index = 1; index <= highWater; index++) occupied.add(index);\n return Math.max(configuredMax, highWater + 1);\n}\n","import { execFile } from 'node:child_process';\nimport { rename } from 'node:fs/promises';\nimport { relative, resolve } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { resolveWorkspaceRepoRootsSync } from '../project-repos.js';\nimport { ensureRegisteredSlotWorktree } from '../agents/registered-slot-spawn.js';\nimport type { SlotReconcileResult } from '../agents/slot-reconcile.js';\nimport type { XBriefDocument } from '../xbrief/types.js';\nimport { releaseBlockedSwarmSlot } from './deacon-swarm-record.js';\nimport type { ArchivedBlockedSlot, CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport async function prepareReleasedSwarmSlot(\n issueId: string,\n workspacePath: string,\n slotIndex: number,\n itemId: string,\n branch: string,\n): Promise<void> {\n await ensureRegisteredSlotWorktree(issueId, workspacePath, {\n agentId: `agent-${issueId.toLowerCase()}-slot-${slotIndex}`,\n branch,\n workspace: `${workspacePath}-slot-${slotIndex}`,\n slotIndex,\n slotItemId: itemId,\n });\n}\n\nexport async function defaultIsSlotBranchPushed(\n workspacePath: string,\n issueId: string,\n branch: string,\n): Promise<boolean> {\n const slotIndex = branch.match(/-slot-(\\d+)(?:-attempt-\\d+)?$/)?.[1];\n if (!slotIndex) return false;\n const roots = resolveWorkspaceRepoRootsSync(issueId, `${workspacePath}-slot-${slotIndex}`);\n if (roots.length === 0 || roots.some(root => root.degradedPolyrepo)) return false;\n try {\n const durable = await Promise.all(roots.map(async root => {\n await refreshDurableSlotRef(root.dir, branch);\n return true;\n }));\n return durable.every(Boolean);\n } catch {\n return false;\n }\n}\n\nexport async function releaseBlockedSlots(\n issueId: string,\n workspacePath: string,\n doc: XBriefDocument,\n reconciled: SlotReconcileResult,\n deps: Pick<CoordinateSwarmSlotsDeps, 'listSessionNames' | 'isPaneDead' | 'isSlotWorktreeClean'>\n & Partial<Pick<CoordinateSwarmSlotsDeps, 'isSlotBranchPushed' | 'archiveBlockedSlot' | 'prepareReleasedSlot'>>,\n): Promise<string[]> {\n const actions: string[] = [];\n const blockedItemIds = new Set(doc.plan.items.filter(item => item.status === 'blocked').map(item => item.id));\n if (blockedItemIds.size === 0) return actions;\n\n const sessionNames = new Set(await deps.listSessionNames());\n for (const slot of reconciled.inFlight.filter(candidate => blockedItemIds.has(candidate.itemId))) {\n const agentId = slot.agentId ?? `agent-${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n if (sessionNames.has(agentId) && !(await deps.isPaneDead(agentId))) continue;\n if (!slot.branch) {\n actions.push(`[swarm] needs-you ${issueId}: blocked slot ${slot.slotIndex} has no branch; preserving assignment`);\n continue;\n }\n const clean = await deps.isSlotWorktreeClean(`${workspacePath}-slot-${slot.slotIndex}`).catch(() => false);\n const pushed = await (deps.isSlotBranchPushed ?? defaultIsSlotBranchPushed)(\n workspacePath,\n issueId,\n slot.branch,\n ).catch(() => false);\n if (!clean || !pushed) {\n const reason = !clean ? 'uncommitted work' : 'unpushed or unverifiable work';\n actions.push(`[swarm] needs-you ${issueId}: blocked slot ${slot.slotIndex} has ${reason}; preserving assignment`);\n continue;\n }\n const archived = await (deps.archiveBlockedSlot ?? archiveBlockedSwarmSlot)(\n issueId,\n workspacePath,\n slot.slotIndex,\n slot.branch,\n );\n await releaseBlockedSwarmSlot(workspacePath, issueId, slot.slotIndex, slot.itemId, slot.branch, archived);\n await (deps.prepareReleasedSlot ?? prepareReleasedSwarmSlot)(\n issueId,\n workspacePath,\n slot.slotIndex,\n slot.itemId,\n archived.replacementBranch,\n );\n actions.push(`[swarm] released blocked slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: archived as ${archived.archivedBranch} and prepared ${archived.replacementBranch}`);\n }\n reconciled.inFlight = reconciled.inFlight.filter(slot => !blockedItemIds.has(slot.itemId));\n return actions;\n}\n\n/** Preserve a blocked attempt and provision a unique branch identity for slot reuse. */\nexport async function archiveBlockedSwarmSlot(\n issueId: string,\n workspacePath: string,\n slotIndex: number,\n branch: string,\n now = new Date(),\n): Promise<ArchivedBlockedSlot> {\n const releasedAt = now.toISOString();\n const suffix = releasedAt.replace(/[-:.TZ]/g, '');\n const slotWorkspace = `${workspacePath}-slot-${slotIndex}`;\n const archivedWorktree = `${slotWorkspace}-blocked-${suffix}`;\n const archivedBranch = `${branch}-blocked-${suffix}`;\n const replacementBranch = `feature/${issueId.toLowerCase()}-slot-${slotIndex}-attempt-${suffix}`;\n const slotRoots = resolveWorkspaceRepoRootsSync(issueId, slotWorkspace);\n const baseRoots = new Map(resolveWorkspaceRepoRootsSync(issueId, workspacePath)\n .map(root => [root.repoKey, root]));\n\n if (slotRoots.length === 0 || slotRoots.some(root => root.degradedPolyrepo)) {\n throw new Error(`Cannot archive blocked slot ${slotIndex}: repository roots are unavailable.`);\n }\n\n // Copy the freshly queried remote tip to an attempt-specific archive. The\n // original local and remote refs remain untouched; replacement work uses a\n // new branch identity.\n for (const root of slotRoots) {\n const remoteRef = await refreshDurableSlotRef(root.dir, branch);\n await execFileAsync('git', [\n 'push',\n 'origin',\n `${remoteRef}:refs/heads/${archivedBranch}`,\n ], { cwd: root.dir });\n }\n\n await rename(slotWorkspace, archivedWorktree);\n for (const root of slotRoots) {\n const baseRoot = baseRoots.get(root.repoKey);\n if (!baseRoot) throw new Error(`Cannot archive blocked slot ${slotIndex}: missing base repository ${root.repoKey}.`);\n const nestedPath = relative(slotWorkspace, root.dir);\n const archivedRoot = nestedPath ? resolve(archivedWorktree, nestedPath) : archivedWorktree;\n await execFileAsync('git', ['worktree', 'repair', archivedRoot], { cwd: baseRoot.dir });\n await execFileAsync('git', ['branch', archivedBranch, branch], { cwd: baseRoot.dir });\n await execFileAsync('git', ['switch', archivedBranch], { cwd: archivedRoot });\n }\n\n return { archivedBranch, archivedWorktree, replacementBranch, releasedAt };\n}\n\nasync function refreshDurableSlotRef(workspace: string, branch: string): Promise<string> {\n const remoteRef = `refs/remotes/origin/${branch}`;\n await execFileAsync('git', [\n 'fetch',\n '--no-tags',\n 'origin',\n `+refs/heads/${branch}:${remoteRef}`,\n ], { cwd: workspace });\n await execFileAsync('git', [\n 'merge-base',\n '--is-ancestor',\n `refs/heads/${branch}`,\n remoteRef,\n ], { cwd: workspace });\n return remoteRef;\n}\n","import { messageAgent } from '../agents/messaging.js';\nimport { spawnRun } from '../agents/spawn.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { listSessionNamesSync } from '../tmux.js';\nimport { buildWorkAgentPrompt } from './work-agent-prompt.js';\n\nexport interface EnsureSwarmForemanDeps {\n listSessionNamesSync: () => string[];\n messageAgent: typeof messageAgent;\n buildWorkAgentPrompt: typeof buildWorkAgentPrompt;\n spawnRun: typeof spawnRun;\n resolveProjectFromIssueSync: typeof resolveProjectFromIssueSync;\n}\n\nconst defaultDeps: EnsureSwarmForemanDeps = {\n // Keep the binding lazy so importing Deacon does not force unrelated tests\n // with focused tmux mocks to provide this foreman-only dependency.\n listSessionNamesSync: () => listSessionNamesSync(),\n messageAgent,\n buildWorkAgentPrompt,\n spawnRun,\n resolveProjectFromIssueSync,\n};\n\nexport async function ensureSwarmForeman(\n issueId: string,\n workspacePath: string,\n options: { startedBy: string; prompt?: string },\n deps: EnsureSwarmForemanDeps = defaultDeps,\n): Promise<string[]> {\n const issue = issueId.toUpperCase();\n const agentId = `agent-${issue.toLowerCase()}`;\n if (deps.listSessionNamesSync().includes(agentId)) {\n await deps.messageAgent(agentId, options.prompt ?? `Continue managing ${issue} as its swarm foreman. Run pan swarm status ${issue} --json before acting.`, 'pan-swarm');\n return [`[swarm] attached to live foreman ${agentId} for ${issue}`];\n }\n const basePrompt = await deps.buildWorkAgentPrompt({\n issueId: issue,\n env: 'LOCAL',\n workspacePath,\n projectRoot: deps.resolveProjectFromIssueSync(issue)?.projectPath,\n });\n const state = await deps.spawnRun(issue, 'work', {\n workspace: workspacePath,\n prompt: options.prompt ? `${basePrompt}\\n\\n${options.prompt}` : basePrompt,\n foreman: true,\n startedBy: options.startedBy,\n });\n return [`[swarm] spawned foreman ${state.id} for ${issue}`];\n}\n","import { emitActivityEntrySync } from '../activity-logger.js';\nimport { listSlotAssignments, type SlotReconcileResult } from '../agents/slot-reconcile.js';\nimport { readSwarmHold, writeSwarmHold } from './deacon-swarm-record.js';\nimport { workResumeSlotsAvailable } from './concurrency.js';\nimport { ensureSwarmForeman } from './swarm-foreman.js';\n\nexport interface SwarmForemanLivenessDeps {\n listSlotAssignments: (issueId: string, workspacePath: string) => Array<{ slotIndex: number }>;\n readSwarmHold: typeof readSwarmHold;\n workResumeSlotsAvailable: typeof workResumeSlotsAvailable;\n ensureSwarmForeman: typeof ensureSwarmForeman;\n writeSwarmHold: typeof writeSwarmHold;\n emitActivityEntry: typeof emitActivityEntrySync;\n}\n\nconst defaultDeps: SwarmForemanLivenessDeps = {\n listSlotAssignments,\n readSwarmHold,\n workResumeSlotsAvailable,\n ensureSwarmForeman,\n writeSwarmHold,\n emitActivityEntry: emitActivityEntrySync,\n};\n\nconst respawnFailures = new Map<string, number>();\n\nexport async function maintainSwarmForeman(\n issueId: string,\n workspacePath: string,\n reconciled: SlotReconcileResult,\n sessions: readonly string[],\n overrides: Partial<SwarmForemanLivenessDeps> = {},\n allowSpawn = true,\n bootstrap = false,\n): Promise<string[]> {\n const definedOverrides = Object.fromEntries(Object.entries(overrides).filter(([, value]) => value !== undefined));\n const deps = { ...defaultDeps, ...definedOverrides } as SwarmForemanLivenessDeps;\n const issueLower = issueId.toLowerCase();\n if (!allowSpawn) return [];\n const mergedSlotIndexes = new Set(reconciled.merged.map(slot => slot.slotIndex));\n const active = bootstrap || sessions.some(name => name.startsWith(`agent-${issueLower}-slot-`))\n || reconciled.branches.some(branch => !mergedSlotIndexes.has(branch.slotIndex))\n || deps.listSlotAssignments(issueId, workspacePath).length > 0;\n if (!active) {\n respawnFailures.delete(issueId);\n return [];\n }\n if (sessions.includes(`agent-${issueLower}`) || deps.readSwarmHold(workspacePath, issueId)) return [];\n if (deps.workResumeSlotsAvailable() <= 0) {\n return [`[swarm-janitor] deferred foreman respawn for ${issueId}: resource governor`];\n }\n\n const failures = respawnFailures.get(issueId) ?? 0;\n if (failures >= 3) return [];\n try {\n const prompt = `Resume ${issueId} swarm foreman duties: read .pan/continue.json, run pan swarm status ${issueId} --json, reconcile current state, then continue the wave loop.`;\n const actions = await deps.ensureSwarmForeman(issueId, workspacePath, { startedBy: 'deacon:swarm-janitor', prompt });\n respawnFailures.delete(issueId);\n return actions;\n } catch (error) {\n const count = failures + 1;\n respawnFailures.set(issueId, count);\n const actions = [`[swarm-janitor] foreman respawn failed for ${issueId} (${count}/3): ${error instanceof Error ? error.message : String(error)}`];\n if (count < 3) return actions;\n\n const message = `${issueId} swarm halted after 3 consecutive foreman respawn failures; operator action is required.`;\n await deps.writeSwarmHold(workspacePath, issueId, { reason: message, setBy: 'deacon:swarm-janitor', at: new Date().toISOString() });\n deps.emitActivityEntry({ source: 'cloister', level: 'error', issueId, message });\n actions.push(`[swarm-janitor] ${message}`);\n return actions;\n }\n}\n\nexport function resetForemanRespawnFailuresForTests(): void {\n respawnFailures.clear();\n}\n","import { exec } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { join } from 'path';\nimport { getAgentRuntimeSnapshot } from '../agent-runtime.js';\nimport { messageAgent } from '../agents/messaging.js';\nimport { spawnRun } from '../agents/spawn.js';\nimport { verifyAndMergeSlot } from '../agents/slot-merge.js';\nimport {\n listSlotAssignments as listDurableSlotAssignments,\n reconcileSlotState,\n type ReconciledSlotItem,\n type SlotReconcileResult,\n} from '../agents/slot-reconcile.js';\nimport {\n readIssueRecordForWorkspaceSync,\n getIssueRecordPathForWorkspace,\n type PanIssueRecord,\n type PanIssueSwarmFailedMergeBlock,\n type PanIssueSwarmSlotCompletion,\n} from '../pan-dir/record.js';\nimport { updateIssueRecordForWorkspace } from '../pan-dir/record-update.js';\nimport { findSpecByIssue } from '../pan-dir/specs.js';\nimport { capturePane, isPaneDead, listPaneValues, listSessionNames as listTmuxSessionNames } from '../tmux.js';\nimport {\n blockingParentCount,\n createActiveSlice,\n getDispatchableItems,\n} from '../xbrief/dag.js';\nimport { applyTaskStatusChange } from '../pan-dir/task-door.js';\nimport { getProjectConfigFromWorkspacePath, resolveProjectForIssue } from '../pan-dir/record.js';\nimport { applyStatusOverrides } from '../xbrief/io.js';\nimport { analyzeSwarmReadiness, type SwarmReadinessVerdict } from '../xbrief/swarm-readiness.js';\nimport type { XBriefDocument, XBriefItem } from '../xbrief/types.js';\nimport { getReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { isDeaconGloballyPausedSync } from '../overdeck/control-settings.js';\nimport { resolveAutomaticSwarmPolicy, resolveSwarmMaxSlots } from '../swarm-policy.js';\nimport type { SwarmInferCompletionMode } from './config.js';\nimport {\n countRunningSwarmSlotsForIssue,\n getConcurrencyLimits,\n releaseSwarmSlot,\n tryReserveSwarmSlot,\n type ConcurrencyLimits,\n} from './concurrency.js';\nimport { listFeatureWorkspaces } from './deacon-workspaces.js';\nimport { gcOrphanedSlots } from './deacon-swarm-orphan-gc.js';\nimport {\n classifyDoneWithoutSignal,\n classifyDurableReadySlot,\n clearSwarmCompletionObservation,\n defaultGetSlotBranchAheadCount,\n defaultIsSlotWorktreeClean,\n defaultSendCompletionNudge,\n resetSwarmCompletionInferenceForTests,\n swarmInferCompletionMode,\n} from './deacon-swarm-completion.js';\nimport type { CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\nexport type { CoordinateSwarmSlotsDeps } from './deacon-swarm-types.js';\nimport { gcMergedSlots, reapMergedSlotAgent } from './deacon-swarm-gc.js';\nimport { gcMergedSlotsAndAdvance } from './deacon-swarm-advance.js';\nimport { clearReleasedBlockedSwarmSlot, clearSwarmSlotCompletion, clearSwarmSlotOwnership, createMinimalIssueRecord, readSwarmHold, writeSwarmForemanTakeover } from './deacon-swarm-record.js';\nimport { fireTieredCommitHooks } from './swarm-tiered-hooks.js';\nimport { applySupersededSlotHighWater, archiveFailedSwarmSlot, requeueFailedSwarmSlots } from './swarm-failed-slot.js';\nimport { archiveBlockedSwarmSlot, defaultIsSlotBranchPushed, prepareReleasedSwarmSlot, releaseBlockedSlots } from './swarm-blocked-slot.js';\nimport { ensureSwarmForeman } from './swarm-foreman.js';\nimport { maintainSwarmForeman, resetForemanRespawnFailuresForTests, type SwarmForemanLivenessDeps } from './swarm-foreman-liveness.js';\n\nexport { gcOrphanedSlots } from './deacon-swarm-orphan-gc.js';\nexport { gcMergedSlots } from './deacon-swarm-gc.js';\nexport { releaseBlockedSlots } from './swarm-blocked-slot.js';\nconst execAsync = promisify(exec);\nconst SLOT_MERGE_REFIRE_COOLDOWN_MS = 5_000;\nconst SWARM_ADVANCE_FAILURE_THRESHOLD = 3;\nconst SWARM_ADVANCE_FAILURE_COOLDOWN_MS = 60_000;\nconst DEFAULT_SWARM_STALL_THRESHOLD_MS = 30 * 60 * 1000;\n\nconst recentSlotMergeFires = new Map<string, number>();\nconst issueAdvanceFailures = new Map<string, { count: number; cooldownUntil: number }>();\nconst failedMergeBlocks = new Map<string, FailedMergeBlock>();\nconst slotProgressObservations = new Map<string, SlotProgressObservation>();\nexport type SwarmRecoveryAction = 'retry' | 'drop' | 'handoff' | 'reclaim';\n\nexport interface FailedMergeBlock {\n issueId: string;\n itemId: string;\n slotIndex: number;\n branch?: string;\n note: string;\n}\n\nexport interface CoordinateSwarmSlotsOptions {\n issueId?: string;\n /** Explicit `pan swarm` bypasses the automatic-selection policy. */\n manual?: boolean;\n}\n\nconst defaultDeps: CoordinateSwarmSlotsDeps = {\n findSpecByIssue,\n listFeatureWorkspaces: () => listFeatureWorkspaces({ includeSlotWorkspaces: false }),\n reconcileSlotState,\n listSessionNames: () => Effect.runPromise(listTmuxSessionNames()),\n isPaneDead: (sessionName) => Effect.runPromise(isPaneDead(sessionName)),\n getPaneExitStatus: async (sessionName) => {\n const values = await Effect.runPromise(listPaneValues(sessionName, '#{pane_dead_status}'));\n const raw = values[0]?.trim();\n if (!raw) return null;\n const status = Number(raw);\n return Number.isFinite(status) ? status : null;\n },\n getAgentRuntimeState: (agentId) => Effect.runPromise(getAgentRuntimeSnapshot(agentId)),\n getPaneOutputDigest: async (sessionName) => Effect.runPromise(capturePane(sessionName, 200)),\n getBranchTipCommitTime: async (workspacePath, branch) => {\n try {\n const { stdout } = await execAsync(`git log -1 --format=%ct ${JSON.stringify(branch)}`, { cwd: workspacePath });\n const seconds = Number(stdout.trim());\n return Number.isFinite(seconds) ? seconds * 1000 : null;\n } catch {\n return null;\n }\n },\n getSlotBranchAheadCount: defaultGetSlotBranchAheadCount,\n isSlotWorktreeClean: defaultIsSlotWorktreeClean,\n isSlotBranchPushed: defaultIsSlotBranchPushed,\n archiveBlockedSlot: archiveBlockedSwarmSlot,\n prepareReleasedSlot: prepareReleasedSwarmSlot,\n sendCompletionNudge: defaultSendCompletionNudge,\n slotWorktreeExists: existsSync,\n verifyAndMergeSlot,\n applyTaskOperationToPlanFile: (issueId, operation, workspacePath = '') => {\n const project = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);\n return applyTaskStatusChange(project, issueId, operation);\n },\n fireTieredCommitHooks,\n recordSlotAssignment,\n clearSlotAssignment,\n runGitCommand: (command, cwd) => execAsync(command, { cwd }),\n registeredSlotCapacityAvailable: (issueId, selectedCount) => registeredSlotCapacityAvailable(issueId, selectedCount),\n tryReserveSwarmSlot,\n releaseSwarmSlot,\n spawnRun,\n getIssueHold: defaultGetIssueHold,\n shouldDispatch: defaultShouldDispatch,\n readSwarmHold,\n getMaxSlotIndex: defaultGetMaxSlotIndex,\n listSlotAssignments: listDurableSlotAssignments,\n listReleasedSlotIndexes: (issueId, workspacePath) => Object.keys(\n readIssueRecordForWorkspaceSync(workspacePath, issueId)?.swarm?.releasedBlockedSlots ?? {},\n ).map(Number),\n getReleasedSlotBranch: (issueId, workspacePath, slotIndex) => readIssueRecordForWorkspaceSync(\n workspacePath,\n issueId,\n )?.swarm?.releasedBlockedSlots?.[String(slotIndex)]?.replacementBranch,\n clearReleasedSlot: clearReleasedBlockedSwarmSlot,\n readStatusOverrides: defaultReadStatusOverrides,\n readSlotCompletion: defaultReadSlotCompletion,\n clearSlotCompletion: clearSwarmSlotCompletion,\n recordForemanTakeover: writeSwarmForemanTakeover,\n ensureSwarmForeman,\n sendStallEvent: (agentId, message) => messageAgent(agentId, message, 'deacon:swarm-stall'),\n resolveAutomaticSwarmPolicy,\n};\n\nfunction defaultGetMaxSlotIndex(): number {\n return Math.max(1, getConcurrencyLimits().reservedSwarmSlots);\n}\n\nfunction defaultGetIssueHold(issueId: string): Pick<ReviewStatus, 'stuck' | 'deaconIgnored' | 'stuckReason'> | null {\n try {\n return getReviewStatusSync(issueId);\n } catch {\n return null;\n }\n}\n\nfunction defaultReadStatusOverrides(workspacePath: string, issueId: string): Record<string, string> | undefined {\n // PAN-2372 WI-4 / FR-7: distinguish an absent record (silent undefined — a\n // brand-new issue that simply has no overrides yet) from an UNREADABLE record\n // (the file exists but won't parse). readIssueRecordForWorkspaceSync returns\n // null for both, so the existsSync check is what separates them. A corrupt\n // record is preserved as a .corrupt-<ts> sidecar by the atomic writer (WI-1);\n // warn so a broken sidecar never silently masks a slot's done-ness.\n const normalized = issueId.toUpperCase();\n const record = readIssueRecordForWorkspaceSync(workspacePath, normalized);\n if (record === null && existsSync(getIssueRecordPathForWorkspace(workspacePath, normalized))) {\n console.warn(`[swarm] record unreadable for ${normalized} — treating as no overrides; see .corrupt sidecar`);\n return undefined;\n }\n return record?.statusOverrides;\n}\n\n/**\n * PAN-2372 WI-4 / FR-6: read a slot's durable completion marker from the record\n * door. Returns undefined when no record or no marker exists. Used by\n * classifyInFlightSlots to recognize a slot whose `pan done` durably recorded\n * completion even when the runtime plane (agent state, tmux session) is gone.\n */\nfunction defaultReadSlotCompletion(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n): PanIssueSwarmSlotCompletion | undefined {\n try {\n return readIssueRecordForWorkspaceSync(workspacePath, issueId.toUpperCase())?.swarm?.slotCompletions?.[String(slotIndex)];\n } catch {\n return undefined;\n }\n}\n\nfunction defaultShouldDispatch(issueId: string): boolean {\n if (isDeaconGloballyPausedSync()) return false;\n const hold = defaultGetIssueHold(issueId);\n return !(hold?.stuck || hold?.deaconIgnored);\n}\n\nexport type SwarmSlotLifecycle = 'running' | 'ready-to-merge' | 'failed' | 'stalled' | 'awaiting-completion-signal' | 'failed-merge-blocked';\n\nexport interface ClassifyInFlightSlotsOptions {\n workspacePath?: string;\n issueId?: string;\n inferCompletion?: SwarmInferCompletionMode;\n stallThresholdMs?: number;\n now?: number;\n}\n\nexport interface ClassifiedSwarmSlot extends ReconciledSlotItem {\n lifecycle: SwarmSlotLifecycle;\n exitStatus?: number | null;\n reason?: 'missing-agent' | 'vanished-session' | 'pane-exit-nonzero' | 'pane-exit-unknown' | 'no-progress-timeout';\n stalledForMs?: number;\n signal?: 'inferred' | 'completion-nudge' | 'durable-completion' | 'stall-event';\n actions?: string[];\n}\n\ninterface SlotProgressObservation {\n commitTime: number | null;\n outputDigest: string;\n lastProgressAt: number;\n stallNotified?: boolean;\n}\n\ninterface SlotAssignment {\n slotIndex: number;\n itemId: string;\n agentId?: string;\n branch?: string;\n}\n\ntype SlotAssignments = NonNullable<NonNullable<PanIssueRecord['swarm']>['slotAssignments']>;\n\nexport async function coordinateSwarmSlots(\n opts: CoordinateSwarmSlotsOptions = {},\n deps: CoordinateSwarmSlotsDeps = defaultDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n const filterIssueId = opts.issueId?.toUpperCase();\n\n for (const workspace of deps.listFeatureWorkspaces()) {\n const issueId = workspace.issueId.toUpperCase();\n if (filterIssueId && issueId !== filterIssueId) continue;\n const hold = (deps.getIssueHold ?? defaultGetIssueHold)(issueId);\n if (hold?.deaconIgnored) {\n actions.push(`[swarm] skipped ${issueId}: deacon-ignored — operator hold`);\n continue;\n }\n // PAN-2469: `stuck` is a SYSTEM-set failure marker (delivery/verification\n // trouble), not an operator hold — skipping coordination on it froze the\n // whole swarm forever: PAN-2388's slots sat ready-to-merge for hours while\n // the coordinator skipped the issue because the verification-gate deadlock\n // (PAN-2461) had marked it stuck. Coordination (merge/gc/endgame) continues\n // through system-stuck; only operator deacon-ignore fully halts it.\n if (hold?.stuck) {\n actions.push(`[swarm] ${issueId} is system-stuck (${hold.stuckReason ?? 'unknown'}) — coordinating anyway (stuck no longer halts assembly, PAN-2469)`);\n }\n if (isSwarmAdvanceCoolingDown(issueId)) {\n actions.push(`[swarm] deferred ${issueId}: advance backoff active`);\n continue;\n }\n const failedMergeBlocks = getFailedMergeBlocks(issueId, workspace.workspacePath);\n const blockedSlotIndexes = new Set(failedMergeBlocks.map(block => block.slotIndex));\n const blockedItemIds = new Set(failedMergeBlocks.map(block => block.itemId));\n if (failedMergeBlocks.length > 0) {\n const slots = failedMergeBlocks\n .map(block => `slot ${block.slotIndex} (item ${block.itemId})`)\n .join(', ');\n actions.push(`[swarm] blocked slots for ${issueId}: ${slots} — other slots continue; run \\`pan swarm recover ${issueId} <slot>\\``);\n }\n try {\n const spec = await Effect.runPromise(findSpecByIssue(workspace.projectPath, issueId));\n if (!spec) continue;\n const planStatus = spec.document.plan.status;\n if (planStatus === 'completed' || planStatus === 'cancelled') continue;\n const overrides = (deps.readStatusOverrides ?? defaultReadStatusOverrides)(workspace.workspacePath, issueId);\n const doc = overrides && Object.keys(overrides).length > 0\n ? applyStatusOverrides(spec.document, overrides)\n : spec.document;\n const readiness = analyzeSwarmReadiness(doc);\n const slotEligibleCount = readiness.items.filter(item => item.slotEligible).length;\n const dispatchEligible = opts.manual === true && readiness.swarmEligible && slotEligibleCount >= 2;\n if (dispatchEligible) {\n actions.push(`[swarm] considered ${issueId}: swarm eligible`);\n }\n const reconciled = await deps.reconcileSlotState(issueId, workspace.workspacePath, doc);\n if (!dispatchEligible) {\n const hasSlotState = reconciled.merged.length > 0 || reconciled.inFlight.length > 0\n || reconciled.branches.length > 0 || reconciled.agents.length > 0;\n if (!hasSlotState) continue;\n actions.push(`[swarm] considered ${issueId}: endgame (merge/cleanup only)`);\n }\n actions.push(...await releaseBlockedSlots(issueId, workspace.workspacePath, doc, reconciled, deps));\n const classified = await classifyInFlightSlots(reconciled.inFlight, deps, {\n workspacePath: workspace.workspacePath,\n issueId,\n inferCompletion: swarmInferCompletionMode(),\n });\n for (const slot of classified) {\n actions.push(`[swarm] ${issueId} slot ${slot.slotIndex} ${slot.lifecycle}${slot.signal ? ` signal: ${slot.signal}` : ''}`);\n if (slot.actions) actions.push(...slot.actions);\n }\n const requeue = await requeueFailedSwarmSlots(issueId, workspace.workspacePath, classified, doc, reconciled, deps, blockedSlotIndexes);\n actions.push(...requeue.actions);\n actions.push(...await mergeReadySlots(issueId, workspace.workspacePath, doc, classified, deps, blockedSlotIndexes));\n actions.push(...await gcMergedSlotsAndAdvance(issueId, workspace.workspacePath, reconciled, deps, async () => {\n if (!dispatchEligible) return [];\n return dispatchNextWave(issueId, workspace.workspacePath, requeue.doc, reconciled, analyzeSwarmReadiness(requeue.doc), deps, blockedSlotIndexes, blockedItemIds);\n }));\n recordSwarmAdvanceSuccess(issueId);\n } catch (err) {\n recordSwarmAdvanceFailure(issueId);\n console.warn(`[deacon] Error coordinating swarm ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n return actions;\n}\n\nexport async function swarmJanitorPass(deps: CoordinateSwarmSlotsDeps = defaultDeps): Promise<string[]> {\n const actions: string[] = [];\n const sessions = await deps.listSessionNames();\n for (const workspace of deps.listFeatureWorkspaces()) {\n const issueId = workspace.issueId.toUpperCase();\n const spec = await Effect.runPromise((deps.findSpecByIssue ?? findSpecByIssue)(workspace.projectPath, issueId));\n if (!spec) continue;\n const reconciled = await deps.reconcileSlotState(issueId, workspace.workspacePath, spec.document);\n actions.push(`[swarm-janitor] enumerated ${issueId}`);\n actions.push(...await gcMergedSlots(issueId, workspace.workspacePath, reconciled.merged, deps));\n actions.push(...await gcOrphanedSlots(issueId, workspace.workspacePath, reconciled, deps));\n const automatic = (deps.resolveAutomaticSwarmPolicy ?? resolveAutomaticSwarmPolicy)(issueId, analyzeSwarmReadiness(spec.document).swarmEligible);\n actions.push(...await maintainSwarmForeman(issueId, workspace.workspacePath, reconciled, sessions, deps, automatic.policy.mode !== 'off', automatic.spawnForeman));\n const classified = await classifyInFlightSlots(reconciled.inFlight, { ...deps, listSessionNames: async () => sessions }, { issueId, workspacePath: workspace.workspacePath });\n for (const slot of classified.filter(candidate => candidate.signal === 'stall-event')) {\n await deps.sendStallEvent?.(`agent-${issueId.toLowerCase()}`, `[swarm-event] slot ${slot.slotIndex} stalled (no progress ${Math.floor((slot.stalledForMs ?? 0) / 60_000)}m)`);\n actions.push(`[swarm-janitor] notified ${issueId} foreman that slot ${slot.slotIndex} stalled`);\n }\n }\n return actions;\n}\nexport { resetForemanRespawnFailuresForTests };\nexport async function classifyInFlightSlots(\n slots: ReconciledSlotItem[],\n deps: Pick<CoordinateSwarmSlotsDeps, 'listSessionNames' | 'isPaneDead' | 'getPaneExitStatus'>\n & Partial<Pick<\n CoordinateSwarmSlotsDeps,\n | 'getPaneOutputDigest'\n | 'getBranchTipCommitTime'\n | 'getSlotBranchAheadCount'\n | 'isSlotWorktreeClean'\n | 'sendCompletionNudge'\n | 'readCompletionObservation' | 'writeCompletionObservation' | 'clearCompletionObservation'\n | 'readSlotCompletion'\n | 'clearSlotCompletion'\n >> = defaultDeps,\n options: ClassifyInFlightSlotsOptions = {},\n): Promise<ClassifiedSwarmSlot[]> {\n const sessionNames = new Set(await deps.listSessionNames());\n const classified: ClassifiedSwarmSlot[] = [];\n const now = options.now ?? Date.now();\n const stallThresholdMs = options.stallThresholdMs ?? swarmStallThresholdMs();\n\n for (const slot of slots) {\n if (options.workspacePath && options.issueId) {\n const completion = await (deps.readSlotCompletion ?? defaultReadSlotCompletion)(\n options.workspacePath,\n options.issueId,\n slot.slotIndex,\n );\n if (completion) {\n if (completion.itemId === slot.itemId) {\n classified.push({ ...slot, lifecycle: 'ready-to-merge', exitStatus: 0, signal: 'durable-completion' });\n continue;\n }\n await (deps.clearSlotCompletion ?? clearSwarmSlotCompletion)(\n options.workspacePath,\n options.issueId,\n slot.slotIndex,\n );\n }\n }\n\n // PAN-3691: only a DEAD slot may be salvaged from durable git state; a LIVE agent never infers completion from branch state.\n const durableReady = await classifyDurableReadySlot(slot, deps, options);\n if (!slot.agentId) {\n if (durableReady) {\n classified.push(durableReady); continue;\n }\n classified.push({ ...slot, lifecycle: 'failed', reason: 'missing-agent' });\n continue;\n }\n\n // PAN-3720: runtime resolution is NEVER merge authority — not for a live\n // session, and not for a vanished one. Static slot ids cross assignment\n // generations (the Deacon reassigns e.g. agent-min-888-slot-1 to the next\n // work item), so a terminal done|completed snapshot may belong to the\n // PREVIOUS assignment: unsafe while the fresh session is alive, and still\n // unsafe after that session dies with partial commits. Normal completion\n // requires the durable, itemId-guarded slotCompletion marker (checked\n // above). A vanished session recovers only through clean committed branch\n // state (classifyDurableReadySlot); a live session through a zero\n // pane-exit below.\n if (!sessionNames.has(slot.agentId)) {\n if (durableReady) {\n classified.push(durableReady);\n continue;\n }\n classified.push({ ...slot, lifecycle: 'failed', reason: 'vanished-session' });\n continue;\n }\n\n const dead = await deps.isPaneDead(slot.agentId);\n if (!dead) {\n const outputDigest = deps.getPaneOutputDigest\n ? await deps.getPaneOutputDigest(slot.agentId).catch(() => '')\n : '';\n const commitTime = slot.branch && options.workspacePath && deps.getBranchTipCommitTime\n ? await deps.getBranchTipCommitTime(options.workspacePath, slot.branch).catch(() => null)\n : null;\n const progressKey = slotProgressKey(slot);\n const previous = slotProgressObservations.get(progressKey);\n if (\n !previous\n || previous.commitTime !== commitTime\n || previous.outputDigest !== outputDigest\n ) {\n slotProgressObservations.set(progressKey, { commitTime, outputDigest, lastProgressAt: now });\n if (options.workspacePath && options.issueId) {\n await clearSwarmCompletionObservation(options.workspacePath, options.issueId, progressKey, deps);\n }\n classified.push({ ...slot, lifecycle: 'running' });\n continue;\n }\n\n const stalledForMs = now - previous.lastProgressAt;\n if (stalledForMs > stallThresholdMs) {\n const awaitingSignal = await classifyDoneWithoutSignal(slot, deps, options, {\n commitTime,\n outputDigest,\n progressKey,\n stalledForMs,\n });\n if (awaitingSignal) {\n classified.push(awaitingSignal);\n continue;\n }\n classified.push({\n ...slot,\n lifecycle: 'stalled',\n reason: 'no-progress-timeout',\n stalledForMs,\n ...(!previous.stallNotified ? { signal: 'stall-event' as const } : {}),\n });\n previous.stallNotified = true;\n continue;\n }\n classified.push({ ...slot, lifecycle: 'running' });\n continue;\n }\n\n const exitStatus = await deps.getPaneExitStatus(slot.agentId);\n if (exitStatus === 0) {\n classified.push({ ...slot, lifecycle: 'ready-to-merge', exitStatus });\n continue;\n }\n\n classified.push({\n ...slot,\n lifecycle: 'failed',\n exitStatus,\n reason: exitStatus === null ? 'pane-exit-unknown' : 'pane-exit-nonzero',\n });\n }\n\n return classified;\n}\n\nfunction slotProgressKey(slot: ReconciledSlotItem): string {\n return slot.agentId ?? slot.branch ?? `${slot.itemId}:${slot.slotIndex}`;\n}\n\nfunction swarmStallThresholdMs(): number {\n const raw = process.env.PAN_SWARM_STALL_THRESHOLD_MS;\n if (!raw) return DEFAULT_SWARM_STALL_THRESHOLD_MS;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_SWARM_STALL_THRESHOLD_MS;\n}\n\nexport async function mergeReadySlots(\n issueId: string,\n workspacePath: string,\n doc: XBriefDocument,\n slots: ClassifiedSwarmSlot[],\n deps: Pick<CoordinateSwarmSlotsDeps, 'verifyAndMergeSlot' | 'applyTaskOperationToPlanFile' | 'fireTieredCommitHooks'> & { stopSlotAgent?: (agentId: string) => Promise<void> } = defaultDeps,\n blockedSlotIndexes: Set<number> = new Set(),\n): Promise<string[]> {\n const actions: string[] = [];\n const itemsById = new Map(doc.plan.items.map(item => [item.id, item]));\n const planPath = join(workspacePath, '.pan', 'spec.vbrief.json');\n\n for (const slot of slots) {\n if (slot.lifecycle !== 'ready-to-merge') continue;\n\n if (blockedSlotIndexes.has(slot.slotIndex)) {\n actions.push(`[swarm] skipped merge slot ${slot.slotIndex} (item ${slot.itemId}) for ${issueId}: failed-merge block — awaiting \\`pan swarm recover\\``);\n continue;\n }\n\n const item = itemsById.get(slot.itemId);\n if (!item) {\n // PAN-2469: this skip was silent — a slot whose itemId drifted from the\n // current spec (e.g. after a re-plan) sat ready-to-merge forever with no\n // trace. Say so every pass.\n actions.push(`[swarm] cannot merge slot ${slot.slotIndex} for ${issueId}: itemId \"${slot.itemId}\" not in the current plan (spec drift?) — needs operator attention`);\n continue;\n }\n\n const branchKey = slot.branch ?? `feature/${issueId.toLowerCase()}-slot-${slot.slotIndex}`;\n if (isSlotMergeCoolingDown(branchKey)) {\n actions.push(`[swarm] skipped merge slot ${slot.slotIndex} (item ${item.id}) for ${issueId}: refire cooldown`);\n continue;\n }\n\n recordSlotMergeFire(branchKey);\n const result = await deps.verifyAndMergeSlot({\n issueId,\n featureWorkspace: workspacePath,\n slotBranch: slot.branch,\n slotWorkspace: `${workspacePath}-slot-${slot.slotIndex}`,\n }, slot.slotIndex, item);\n if (result.merged) {\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'done',\n itemId: item.id,\n writerId: 'deacon-swarm',\n }, workspacePath);\n // PAN-2372 WI-4 / FR-6: the durable marker has done its job — the slot is\n // merged. Clear it so the same slotIndex can be re-dispatched later without\n // a stale \"completed\" marker falsely surfacing as ready-to-merge.\n await clearSwarmSlotCompletion(workspacePath, issueId, slot.slotIndex);\n actions.push(await reapMergedSlotAgent(issueId, slot, deps.stopSlotAgent), `[swarm] merged slot ${slot.slotIndex} (item ${item.id}) for ${issueId}`);\n // PAN-2385: commits just landed — fire the tiered feed + supervisor review (best-effort).\n try {\n actions.push(...await deps.fireTieredCommitHooks({ issueId, workspacePath, item, doc }));\n } catch (err) {\n actions.push(`[tiered] commit hooks threw for ${issueId} item ${item.id}: ${err instanceof Error ? err.message : String(err)}`);\n }\n continue;\n }\n\n if (result.conflicts) {\n await recordFailedMergeBlock({\n issueId,\n itemId: item.id,\n slotIndex: slot.slotIndex,\n branch: slot.branch,\n note: `Slot branch ${slot.branch ?? slot.slotIndex} did not merge cleanly`,\n }, workspacePath);\n actions.push(`[swarm] failed-merge slot ${slot.slotIndex} (item ${item.id}) for ${issueId}`);\n continue;\n }\n\n // Verification failure (merged result fails typecheck/tests) previously\n // fell through with no action at all — the slot silently re-verified on\n // every pass with nobody told why. Surface the failure so the operator\n // or recovery machinery can act on it.\n actions.push(`[swarm] verify-failed slot ${slot.slotIndex} (item ${item.id}) for ${issueId}: ${result.failure ?? 'verification failed'}`);\n }\n\n return actions;\n}\n\nexport function resetSwarmLoopSafetyForTests(): void {\n recentSlotMergeFires.clear();\n issueAdvanceFailures.clear();\n failedMergeBlocks.clear();\n slotProgressObservations.clear();\n resetSwarmCompletionInferenceForTests();\n}\n\nexport function recordSwarmAdvanceFailure(issueId: string, now = Date.now()): void {\n const normalized = issueId.toUpperCase();\n const previous = issueAdvanceFailures.get(normalized);\n const count = (previous?.count ?? 0) + 1;\n issueAdvanceFailures.set(normalized, {\n count,\n cooldownUntil: count >= SWARM_ADVANCE_FAILURE_THRESHOLD\n ? now + SWARM_ADVANCE_FAILURE_COOLDOWN_MS\n : previous?.cooldownUntil ?? 0,\n });\n}\n\nexport function recordSwarmAdvanceSuccess(issueId: string): void {\n issueAdvanceFailures.delete(issueId.toUpperCase());\n}\n\nexport function isSwarmAdvanceCoolingDown(issueId: string, now = Date.now()): boolean {\n const record = issueAdvanceFailures.get(issueId.toUpperCase());\n return record !== undefined && record.cooldownUntil > now;\n}\n\nexport function getFailedMergeBlock(\n issueId: string,\n slotIndex: number,\n workspacePath?: string,\n): FailedMergeBlock | undefined {\n const normalized = issueId.toUpperCase();\n if (workspacePath) {\n const swarm = readIssueRecordForWorkspaceSync(workspacePath, normalized)?.swarm;\n const keyed = swarm?.failedMergeBlocks?.[String(slotIndex)];\n if (keyed) return { ...keyed, issueId: keyed.issueId.toUpperCase() };\n const legacy = swarm?.failedMergeBlock;\n if (legacy && legacy.slotIndex === slotIndex) {\n return { ...legacy, issueId: legacy.issueId.toUpperCase() };\n }\n }\n return failedMergeBlocks.get(`${normalized}:${slotIndex}`);\n}\n\nexport function getFailedMergeBlocks(issueId: string, workspacePath?: string): FailedMergeBlock[] {\n const normalized = issueId.toUpperCase();\n const bySlot = new Map<number, FailedMergeBlock>();\n\n if (workspacePath) {\n const swarm = readIssueRecordForWorkspaceSync(workspacePath, normalized)?.swarm;\n if (swarm?.failedMergeBlock) {\n const legacy = swarm.failedMergeBlock;\n bySlot.set(legacy.slotIndex, { ...legacy, issueId: legacy.issueId.toUpperCase() });\n }\n if (swarm?.failedMergeBlocks) {\n for (const [key, block] of Object.entries(swarm.failedMergeBlocks)) {\n bySlot.set(Number(key), { ...block, issueId: block.issueId.toUpperCase() });\n }\n }\n }\n\n for (const [key, block] of failedMergeBlocks.entries()) {\n if (key.startsWith(`${normalized}:`) && !bySlot.has(block.slotIndex)) {\n bySlot.set(block.slotIndex, { ...block });\n }\n }\n\n return [...bySlot.values()].sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nexport async function recordFailedMergeBlock(block: FailedMergeBlock, workspacePath?: string): Promise<void> {\n const normalizedBlock = { ...block, issueId: block.issueId.toUpperCase() };\n failedMergeBlocks.set(`${normalizedBlock.issueId}:${normalizedBlock.slotIndex}`, normalizedBlock);\n if (workspacePath) {\n await writeSwarmFailedMergeBlock(workspacePath, normalizedBlock.issueId, normalizedBlock.slotIndex, normalizedBlock);\n }\n}\n\nexport async function clearFailedMergeBlock(issueId: string, slotIndex: number, workspacePath?: string): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n failedMergeBlocks.delete(`${normalizedIssueId}:${slotIndex}`);\n if (workspacePath) {\n await writeSwarmFailedMergeBlock(workspacePath, normalizedIssueId, slotIndex, undefined);\n }\n}\n\nexport async function recoverFailedMergeSlot(\n issueId: string,\n workspacePath: string,\n slotIndex: number,\n doc: XBriefDocument,\n action: SwarmRecoveryAction,\n deps: Pick<\n CoordinateSwarmSlotsDeps,\n 'applyTaskOperationToPlanFile'\n | 'clearSlotAssignment'\n | 'recordSlotAssignment'\n | 'registeredSlotCapacityAvailable'\n | 'tryReserveSwarmSlot'\n | 'releaseSwarmSlot'\n | 'spawnRun'\n | 'shouldDispatch'\n | 'getMaxSlotIndex'\n | 'listSlotAssignments'\n | 'runGitCommand'\n | 'recordForemanTakeover'\n > = defaultDeps,\n): Promise<string[]> {\n const normalizedIssueId = issueId.toUpperCase();\n const block = getFailedMergeBlock(normalizedIssueId, slotIndex, workspacePath);\n if (!block) {\n const otherBlocks = getFailedMergeBlocks(normalizedIssueId, workspacePath);\n if (otherBlocks.length === 0) {\n return [`[swarm] no failed-merge slot for ${normalizedIssueId}`];\n }\n const lines = otherBlocks\n .map(b => ` slot ${b.slotIndex} (item ${b.itemId}): ${b.note}`)\n .join('\\n');\n return [\n `[swarm] no failed-merge block for ${normalizedIssueId} slot ${slotIndex}. Currently blocked slots:\\n${lines}`,\n ];\n }\n\n if (action === 'handoff') {\n block.note = `Operator handoff required for slot ${block.slotIndex} (item ${block.itemId})`;\n await recordFailedMergeBlock(block, workspacePath);\n return [`[swarm] handoff paused ${normalizedIssueId} slot ${block.slotIndex} (item ${block.itemId})`];\n }\n\n if (action === 'drop') {\n await deps.applyTaskOperationToPlanFile(normalizedIssueId, {\n type: 'done',\n itemId: block.itemId,\n writerId: 'deacon-swarm',\n reason: 'Dropped failed swarm slot after operator recovery',\n }, workspacePath);\n await clearFailedMergeBlock(normalizedIssueId, block.slotIndex, workspacePath);\n await deps.clearSlotAssignment(workspacePath, normalizedIssueId, block.slotIndex, block.itemId);\n return [`[swarm] dropped failed-merge slot ${block.slotIndex} (item ${block.itemId}) for ${normalizedIssueId}`];\n }\n\n // retry: archive the conflicted attempt so it cannot re-assert, then unblock\n // and dispatch a fresh attempt.\n await archiveFailedSwarmSlot(normalizedIssueId, workspacePath, {\n itemId: block.itemId, slotIndex: block.slotIndex, status: 'in_flight', branch: block.branch,\n agentId: block.branch ? `agent-${normalizedIssueId.toLowerCase()}-slot-${block.slotIndex}` : undefined,\n },\n { runGitCommand: deps.runGitCommand, clearSlotAssignment: deps.clearSlotAssignment },\n );\n await deps.applyTaskOperationToPlanFile(normalizedIssueId, {\n type: 'unblock',\n itemId: block.itemId,\n writerId: 'deacon-swarm',\n reason: 'Retrying failed swarm slot after merge conflict',\n }, workspacePath);\n await clearFailedMergeBlock(normalizedIssueId, block.slotIndex, workspacePath);\n if (action === 'reclaim') {\n await (deps.recordForemanTakeover ?? writeSwarmForemanTakeover)(workspacePath, normalizedIssueId, block.itemId, block.slotIndex);\n return [`[swarm] reclaimed slot ${block.slotIndex} (item ${block.itemId}) for foreman implementation in ${normalizedIssueId}`];\n }\n const remainingBlocks = getFailedMergeBlocks(normalizedIssueId, workspacePath);\n const blockedSlotIndexes = new Set(remainingBlocks.map(b => b.slotIndex));\n const blockedItemIds = new Set(remainingBlocks.map(b => b.itemId));\n const retryDoc = {\n ...doc,\n plan: {\n ...doc.plan,\n items: doc.plan.items.map(item =>\n item.id === block.itemId ? { ...item, status: 'pending' as const } : item\n ),\n },\n };\n return [\n `[swarm] retrying failed-merge slot ${block.slotIndex} (item ${block.itemId}) for ${normalizedIssueId}`,\n ...await dispatchNextWave(normalizedIssueId, workspacePath, retryDoc, {\n issueId: normalizedIssueId,\n merged: [],\n inFlight: [],\n pending: [],\n branches: [],\n agents: [],\n }, analyzeSwarmReadiness(retryDoc), deps, blockedSlotIndexes, blockedItemIds),\n ];\n}\n\nfunction writeSwarmFailedMergeBlock(\n workspacePath: string,\n issueId: string,\n slotIndex: number,\n block: FailedMergeBlock | undefined,\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n return updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, record => {\n const existingSwarm = record.swarm ?? {};\n const foldedBlocks: Record<string, PanIssueSwarmFailedMergeBlock> = { ...(existingSwarm.failedMergeBlocks ?? {}) };\n if (existingSwarm.failedMergeBlock) foldedBlocks[String(existingSwarm.failedMergeBlock.slotIndex)] = existingSwarm.failedMergeBlock;\n if (block) foldedBlocks[String(slotIndex)] = block;\n else delete foldedBlocks[String(slotIndex)];\n return { ...record, swarm: { ...existingSwarm, failedMergeBlocks: foldedBlocks, failedMergeBlock: undefined } };\n }).then(() => undefined);\n}\n\nfunction recordSlotMergeFire(branchKey: string, now = Date.now()): void {\n recentSlotMergeFires.set(branchKey, now + SLOT_MERGE_REFIRE_COOLDOWN_MS);\n}\n\nfunction isSlotMergeCoolingDown(branchKey: string, now = Date.now()): boolean {\n const until = recentSlotMergeFires.get(branchKey);\n if (until === undefined) return false;\n if (until > now) return true;\n recentSlotMergeFires.delete(branchKey);\n return false;\n}\n\nexport async function dispatchNextWave(\n issueId: string,\n workspacePath: string,\n doc: XBriefDocument,\n reconciled: SlotReconcileResult,\n readiness: SwarmReadinessVerdict,\n deps: Pick<\n CoordinateSwarmSlotsDeps,\n 'registeredSlotCapacityAvailable'\n | 'tryReserveSwarmSlot'\n | 'releaseSwarmSlot'\n | 'applyTaskOperationToPlanFile'\n | 'recordSlotAssignment'\n | 'clearSlotAssignment'\n | 'spawnRun'\n | 'shouldDispatch'\n | 'readSwarmHold'\n | 'getMaxSlotIndex'\n | 'listSlotAssignments'\n | 'listReleasedSlotIndexes'\n | 'getReleasedSlotBranch'\n | 'clearReleasedSlot'\n > & Partial<Pick<CoordinateSwarmSlotsDeps, 'listSessionNames' | 'slotWorktreeExists'>> = defaultDeps,\n blockedSlotIndexes: Set<number> = new Set(),\n blockedItemIds: Set<string> = new Set(),\n): Promise<string[]> {\n const actions: string[] = [];\n const mergedItemIds = new Set(reconciled.merged.map(slot => slot.itemId));\n const slotEligibleIds = new Set(readiness.items.filter(item => item.slotEligible).map(item => item.id));\n const configuredMaxSlotIndex = resolveSwarmMaxSlots(issueId, (deps.getMaxSlotIndex ?? defaultGetMaxSlotIndex)());\n const releasedSlotIndexes = new Set((deps.listReleasedSlotIndexes ?? defaultDeps.listReleasedSlotIndexes)?.(issueId, workspacePath) ?? []);\n const occupiedSlotIndexes = new Set([\n ...blockedSlotIndexes, // PAN-2364: blocked slots count as occupied so other slots cannot collide with them\n ...reconciled.inFlight.map(slot => slot.slotIndex),\n // Active merged slots are covered by their assignment, agent, or worktree (PAN-3689).\n ...reconciled.branches.filter(branch => !branch.merged && !releasedSlotIndexes.has(branch.slotIndex)).map(branch => branch.slotIndex),\n ...reconciled.agents.filter(agent => agent.status !== 'stopped').map(agent => agent.slotIndex),\n ...(deps.listSlotAssignments ?? listDurableSlotAssignments)(issueId, workspacePath).map(assignment => assignment.slotIndex),\n ...(reconciled.superseded ?? []).map(attempt => attempt.slotIndex),\n ]);\n const maxSlotIndex = applySupersededSlotHighWater(occupiedSlotIndexes, reconciled, configuredMaxSlotIndex);\n // Orphaned on-disk worktrees still occupy their index (PAN-2213).\n if (deps.slotWorktreeExists) {\n for (let index = 1; index <= maxSlotIndex; index++) {\n if (!releasedSlotIndexes.has(index) && !occupiedSlotIndexes.has(index) && deps.slotWorktreeExists(`${workspacePath}-slot-${index}`)) {\n occupiedSlotIndexes.add(index);\n }\n }\n }\n const sessionNames = deps.listSessionNames ? await deps.listSessionNames() : [];\n const inFlightItemIds = new Set(reconciled.inFlight.map(slot => slot.itemId));\n const selectedItemIds: string[] = [];\n for (const item of getDispatchableItems(doc, mergedItemIds)) {\n if (blockedItemIds.has(item.id)) continue;\n if (!slotEligibleIds.has(item.id)) continue;\n if (inFlightItemIds.has(item.id)) continue;\n\n const overlapItemId = firstOverlappingItemId(item.id, [...inFlightItemIds, ...selectedItemIds], readiness);\n if (overlapItemId) {\n actions.push(`[swarm] deferred ${item.id} for ${issueId}: files_scope overlaps ${overlapItemId}`);\n continue;\n }\n\n if (!deps.registeredSlotCapacityAvailable(issueId, selectedItemIds.length)) {\n actions.push(`[swarm] deferred ${item.id} for ${issueId}: registered slot cap reached`);\n continue;\n }\n\n let slotIndex: number | null = null;\n for (;;) {\n const candidate = allocateSlotIndex(occupiedSlotIndexes, maxSlotIndex);\n if (candidate === null) break;\n const conflict = slotIndexConflictReason(issueId, workspacePath, candidate, sessionNames, reconciled, deps, releasedSlotIndexes);\n if (!conflict) {\n slotIndex = candidate;\n break;\n }\n // An index-level conflict the registry missed (live session, unmerged\n // branch, orphaned worktree) means the index is occupied — advance to\n // the next free index instead of refusing the item (PAN-2213).\n occupiedSlotIndexes.add(candidate);\n actions.push(`[swarm] slot ${candidate} occupied for ${issueId}: ${conflict} — advancing`);\n }\n if (slotIndex === null) {\n const occupying = [...occupiedSlotIndexes].filter(index => index >= 1 && index <= maxSlotIndex).sort((a, b) => a - b);\n actions.push(\n `[swarm] deferred ${item.id} for ${issueId}: all slot indexes 1..${maxSlotIndex} are occupied (slots ${occupying.join(', ')})`\n + ` — run \\`pan swarm reset ${issueId}\\` if these slots are orphans`,\n );\n break;\n }\n\n if (!deps.tryReserveSwarmSlot()) {\n actions.push(`[swarm] deferred ${item.id} for ${issueId}: swarm dispatch budget exhausted`);\n continue;\n }\n try {\n const releasedBranch = releasedSlotIndexes.has(slotIndex)\n ? (deps.getReleasedSlotBranch ?? defaultDeps.getReleasedSlotBranch)?.(issueId, workspacePath, slotIndex)\n : undefined;\n const slotBranch = releasedBranch ?? `feature/${issueId.toLowerCase()}-slot-${slotIndex}`;\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'claim',\n itemId: item.id,\n writerId: 'deacon-swarm',\n }, workspacePath);\n await deps.recordSlotAssignment(workspacePath, issueId, {\n slotIndex,\n itemId: item.id,\n agentId: `agent-${issueId.toLowerCase()}-slot-${slotIndex}`,\n branch: slotBranch,\n });\n // Freeze/hold can activate mid-wave; the cycle-start gate has already passed\n // by then, so re-check before every spawn (PAN-2214 slot-20 regression).\n const swarmHold = deps.readSwarmHold?.(workspacePath, issueId);\n if (swarmHold || !(deps.shouldDispatch ?? defaultShouldDispatch)(issueId)) {\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'unblock',\n itemId: item.id,\n writerId: 'deacon-swarm',\n reason: 'dispatch halted: freeze/hold active',\n }, workspacePath).catch(() => undefined);\n await deps.clearSlotAssignment(workspacePath, issueId, slotIndex, item.id);\n deps.releaseSwarmSlot();\n actions.push(`[swarm] dispatch-halted ${item.id}: freeze/hold active`);\n continue;\n }\n await deps.spawnRun(issueId, 'work', {\n workspace: workspacePath,\n slotIndex,\n slotItemId: item.id,\n ...(releasedBranch ? { slotBranch: releasedBranch } : {}),\n prompt: promptForDispatchItem(issueId, doc, item),\n startedBy: 'deacon:swarm-slot',\n });\n if (releasedSlotIndexes.has(slotIndex)) {\n await (deps.clearReleasedSlot ?? clearReleasedBlockedSwarmSlot)(workspacePath, issueId, slotIndex)\n .catch(error => actions.push(\n `[swarm] needs-you ${issueId}: replacement slot ${slotIndex} started but its release marker could not be cleared: ${error instanceof Error ? error.message : String(error)}`,\n ));\n }\n occupiedSlotIndexes.add(slotIndex);\n selectedItemIds.push(item.id);\n actions.push(`[swarm] dispatched ${dispatchPhaseForItem(doc, item)} slot ${slotIndex} (item ${item.id}) for ${issueId}`);\n } catch (error) {\n await deps.applyTaskOperationToPlanFile(issueId, {\n type: 'unblock',\n itemId: item.id,\n writerId: 'deacon-swarm',\n reason: `slot dispatch failed: ${error instanceof Error ? error.message : String(error)}`,\n }, workspacePath).catch(() => undefined);\n await deps.clearSlotAssignment(workspacePath, issueId, slotIndex, item.id);\n deps.releaseSwarmSlot();\n actions.push(`[swarm] failed-dispatch ${item.id} for ${issueId}: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n return actions;\n}\n\nexport function recordSlotAssignment(workspacePath: string, issueId: string, assignment: SlotAssignment): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n return updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, record => {\n const slotAssignments = [\n ...(record.swarm?.slotAssignments ?? []).filter(\n slot => slot.slotIndex !== assignment.slotIndex && slot.itemId !== assignment.itemId,\n ),\n { ...assignment, assignedAt: new Date().toISOString() },\n ].sort((a, b) => a.slotIndex - b.slotIndex);\n const slotCompletions = { ...(record.swarm?.slotCompletions ?? {}) };\n delete slotCompletions[String(assignment.slotIndex)];\n return { ...record, swarm: { ...(record.swarm ?? {}), slotAssignments, slotCompletions } };\n }).then(() => undefined);\n}\n\n/** Drop every recorded slot assignment and completion marker atomically. */\nexport function clearAllSlotAssignments(workspacePath: string, issueId: string): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n return updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, record => ({\n ...record,\n swarm: {\n ...(record.swarm ?? {}),\n slotAssignments: [],\n slotCompletions: {},\n },\n })).then(() => undefined);\n}\n\nfunction clearSlotAssignment(workspacePath: string, issueId: string, slotIndex: number, itemId?: string): Promise<void> {\n return clearSwarmSlotOwnership(workspacePath, issueId, slotIndex, itemId);\n}\n\nfunction writeSwarmSlotAssignments(\n workspacePath: string,\n issueId: string,\n update: (existing: SlotAssignments) => SlotAssignments,\n): Promise<void> {\n const normalizedIssueId = issueId.toUpperCase();\n return updateIssueRecordForWorkspace(workspacePath, normalizedIssueId, record => {\n const slotAssignments = update(record.swarm?.slotAssignments ?? [])\n .filter(assignment => Number.isInteger(assignment.slotIndex) && assignment.slotIndex > 0 && assignment.itemId.trim().length > 0)\n .sort((a, b) => a.slotIndex - b.slotIndex);\n return { ...record, swarm: { ...(record.swarm ?? {}), slotAssignments } };\n }).then(() => undefined);\n}\n\nfunction slotIndexConflictReason(\n issueId: string,\n workspacePath: string,\n slotIndex: number,\n sessionNames: readonly string[],\n reconciled: SlotReconcileResult,\n deps: Partial<Pick<CoordinateSwarmSlotsDeps, 'slotWorktreeExists'>>,\n releasedSlotIndexes: ReadonlySet<number> = new Set(),\n): string | undefined {\n const issueLower = issueId.toLowerCase();\n const agentId = `agent-${issueLower}-slot-${slotIndex}`;\n const branch = `feature/${issueLower}-slot-${slotIndex}`;\n if (sessionNames.includes(agentId)) {\n return `live ${agentId} session already exists`;\n }\n\n const unmergedBranch = !releasedSlotIndexes.has(slotIndex) && reconciled.branches.find(slotBranch =>\n slotBranch.slotIndex === slotIndex && slotBranch.branch === branch && !slotBranch.merged\n );\n if (unmergedBranch) {\n return `unmerged ${branch} branch already exists`;\n }\n\n const slotWorkspacePath = `${workspacePath}-slot-${slotIndex}`;\n if (!releasedSlotIndexes.has(slotIndex) && deps.slotWorktreeExists?.(slotWorkspacePath)) {\n return `slot worktree already exists at ${slotWorkspacePath}`;\n }\n\n return undefined;\n}\n\nfunction dispatchPhaseForItem(doc: XBriefDocument, item: XBriefItem): 'implementation' | 'synthesis' {\n return itemRequiresSynthesis(doc, item) && !synthesisContextForItem(item) ? 'synthesis' : 'implementation';\n}\n\nfunction promptForDispatchItem(issueId: string, doc: XBriefDocument, item: XBriefItem): string | undefined {\n if (!itemRequiresSynthesis(doc, item)) return undefined;\n\n const synthesisContext = synthesisContextForItem(item);\n if (synthesisContext) {\n return createActiveSlice(doc, {\n issueId,\n itemId: item.id,\n currentItemIds: [item.id],\n synthesisOutputs: { [item.id]: { contextUpdate: synthesisContext } },\n }).prompt;\n }\n\n const parentIds = doc.plan.edges\n .filter(edge => edge.type === 'blocks' && edge.to === item.id)\n .map(edge => edge.from);\n\n return [\n `SYNTHESIS PHASE for ${item.id}`,\n '',\n 'Do not implement this item yet. Summarize the blocking parent outputs into a concise context update for the implementation slot.',\n `Blocking parents: ${parentIds.join(', ') || '(none)'}`,\n '',\n 'Persist the synthesis context on the xBRIEF item metadata as synthesisContext, commit it, then stop.',\n ].join('\\n');\n}\n\nfunction itemRequiresSynthesis(doc: XBriefDocument, item: XBriefItem): boolean {\n return item.metadata?.requiresSynthesis === true || blockingParentCount(doc, item.id) > 1;\n}\n\nfunction synthesisContextForItem(item: XBriefItem): string | undefined {\n const raw = item.metadata?.synthesisContext;\n return typeof raw === 'string' && raw.trim().length > 0 ? raw : undefined;\n}\n\n/**\n * Lowest free slot index within the bound, derived purely from the occupied\n * set. Returns null — never an index above the bound — when indexes 1..bound\n * are all occupied (PAN-2214: unbounded allocation climbed slot-5..slot-20).\n */\nexport function allocateSlotIndex(occupiedSlotIndexes: ReadonlySet<number>, bound: number): number | null {\n const maxIndex = Math.max(1, Math.floor(bound));\n for (let slotIndex = 1; slotIndex <= maxIndex; slotIndex++) {\n if (!occupiedSlotIndexes.has(slotIndex)) return slotIndex;\n }\n return null;\n}\n\nfunction firstOverlappingItemId(\n itemId: string,\n activeItemIds: string[],\n readiness: SwarmReadinessVerdict,\n): string | undefined {\n for (const activeItemId of activeItemIds) {\n if ((readiness.overlapMatrix[itemId]?.[activeItemId]?.length ?? 0) > 0) return activeItemId;\n }\n return undefined;\n}\n\n/**\n * Whether the issue may register another slot. Counts only tmux-ALIVE slot\n * sessions (stale agents-table rows blocked all dispatch at zero live slots\n * after a reset), capped by the same swarm reserve tryReserveSwarmSlot\n * enforces — never maxWorkAgents (PAN-2214).\n */\nexport function registeredSlotCapacityAvailable(\n issueId: string,\n selectedCount: number,\n liveSlotCount: number = countRunningSwarmSlotsForIssue(issueId),\n limits: Pick<ConcurrencyLimits, 'reservedSwarmSlots'> = getConcurrencyLimits(),\n): boolean {\n return liveSlotCount + selectedCount < limits.reservedSwarmSlots;\n}\n","import { existsSync } from 'fs';\nimport { join } from 'node:path';\nimport { getAgentDir } from '../agents/agent-state.js';\nimport { getReviewStatusSync } from '../review-status.js';\n\nexport type BootReconciliationSkipReason = 'workspace_missing' | 'merged' | 'completed';\n\ninterface BootReconciliationAgent {\n id: string;\n issueId: string;\n workspace?: string | null;\n merged?: boolean;\n}\n\nexport function bootReconciliationSkipReason(agent: BootReconciliationAgent): BootReconciliationSkipReason | null {\n if (!agent.workspace || !existsSync(agent.workspace)) return 'workspace_missing';\n\n const review = getReviewStatusSync(agent.issueId);\n if (\n review?.mergeStatus === 'merged' ||\n agent.merged === true ||\n (review?.readyForMerge === true && review.reviewStatus === 'passed' && review.testStatus === 'passed')\n ) {\n return 'merged';\n }\n\n // Must mirror handleAgentStoppedEvent's mid-flight gate in deacon-auto-resume.ts:\n // a handed-off agent is resumable only when review or test found something to fix.\n // Any weaker rule here makes the boot dialog offer candidates the executor then\n // refuses, so the operator is asked a question whose answer is already decided.\n const completedMarkerExists =\n existsSync(join(getAgentDir(agent.id), 'completed')) ||\n existsSync(join(getAgentDir(agent.id), 'completed.processed'));\n if (completedMarkerExists) {\n const needsFix =\n review?.reviewStatus === 'blocked' ||\n review?.reviewStatus === 'failed' ||\n review?.testStatus === 'failed';\n if (!needsFix) return 'completed';\n }\n\n return null;\n}\n","import { existsSync } from 'node:fs';\nimport {\n type BootReconciliationDecision,\n getBootReconciliationState,\n getLastCleanShutdownAt,\n setBootReconciliationDecision,\n setBootReconciliationGrace,\n stampBootReconciliation,\n} from '../overdeck/control-settings.js';\nimport {\n getIssueStageSync,\n isTerminalIssueStage,\n listAllAgentsSync,\n} from '../overdeck/agents.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { loadCloisterConfigSync } from './config.js';\nimport { isExplicitNoResumeRequest } from './no-resume-mode.js';\nimport { bootReconciliationSkipReason } from './boot-reconciliation-predicates.js';\n\nexport const DEFAULT_BOOT_RECONCILIATION_GRACE_SECS = 120;\nexport const CLEAN_SHUTDOWN_FRESHNESS_MS = 30 * 60 * 1000;\n\n/**\n * How many times the grace window may be pushed out because the operator is\n * answering a different blocking question (an AskUserQuestion, a plan approval,\n * a permission prompt). The dialog is time-boxed on purpose — expiry commits\n * `hold_all`, which parks every candidate — so an operator who cannot even see\n * the countdown must not lose the window. The cap keeps that bounded: a dialog\n * nobody ever dismisses still resolves after grace * (1 + MAX_EXTENSIONS).\n */\nexport const MAX_BOOT_RECONCILIATION_GRACE_EXTENSIONS = 3;\n\nexport interface BootReconciliationExtendResult {\n extended: boolean;\n graceDeadline: string | null;\n graceExtensions: number;\n maxGraceExtensions: number;\n reason: 'extended' | 'not-pending' | 'cap-reached' | 'no-deadline';\n}\n\ntype ReconciliationAgent = ReturnType<typeof listAllAgentsSync>[number];\n\nexport interface BootReconciliationStartupResult {\n bootId: string;\n graceDeadline: string;\n candidateIds: string[];\n decision: BootReconciliationDecision;\n timerArmed: boolean;\n}\n\nexport interface StartBootReconciliationOptions {\n bootId?: string;\n now?: Date;\n onGraceExpired?: () => void | Promise<void>;\n}\n\nlet graceTimer: ReturnType<typeof setTimeout> | null = null;\n/**\n * The expiry hook the live timer was armed with. Held so `extendBootReconciliationGrace`\n * can re-arm against the new deadline without the HTTP route having to know how\n * the boot path wires the apply step.\n */\nlet graceExpiryHook: (() => void | Promise<void>) | null = null;\n\nfunction newestAgentTimestampMs(agent: ReconciliationAgent): number | null {\n const timestamps = [agent.lastActivity, agent.stoppedAt, agent.startedAt]\n .map((value) => value == null ? NaN : Date.parse(value))\n .filter(Number.isFinite);\n return timestamps.length > 0 ? Math.max(...timestamps) : null;\n}\n\nfunction isRecentBootCandidate(agent: ReconciliationAgent): boolean {\n const bootStartedAt = getBootReconciliationState().bootStartedAt;\n const bootStartedAtMs = bootStartedAt == null ? NaN : Date.parse(bootStartedAt);\n const newestTimestampMs = newestAgentTimestampMs(agent);\n if (!Number.isFinite(bootStartedAtMs) || newestTimestampMs == null) return false;\n const maxAgeMs = getBootReconciliationMaxCandidateAgeSeconds() * 1000;\n return newestTimestampMs >= bootStartedAtMs - maxAgeMs;\n}\n\nexport function isCleanShutdownBoot(): boolean {\n // Classify against boot start so grace extensions cannot age a clean marker into a crash.\n const state = getBootReconciliationState();\n const bootStartedAtMs = state.bootStartedAt == null ? NaN : Date.parse(state.bootStartedAt);\n const marker = getLastCleanShutdownAt();\n const markerMs = marker == null ? NaN : Date.parse(marker);\n return Number.isFinite(bootStartedAtMs)\n && Number.isFinite(markerMs)\n && bootStartedAtMs - markerMs <= CLEAN_SHUTDOWN_FRESHNESS_MS;\n}\n\nexport function getBootReconciliationGraceSeconds(): number {\n const value = loadCloisterConfigSync().startup.reconciliation_grace_secs;\n return Number.isFinite(value) && value > 0\n ? value\n : DEFAULT_BOOT_RECONCILIATION_GRACE_SECS;\n}\n\nexport function getBootReconciliationMaxCandidateAgeSeconds(): number {\n const config = loadCloisterConfigSync().startup;\n const value = config.reconciliation_max_candidate_age_secs;\n return Number.isFinite(value) && value > 0\n ? value\n : getBootReconciliationGraceSeconds() * 2;\n}\n\nexport function isAutoResumableRole(role: ReconciliationAgent['role']): boolean {\n return role === 'work' || role === 'strike';\n}\n\nexport function isBootReconciliationCandidate(agent: ReconciliationAgent): boolean {\n if (!isAutoResumableRole(agent.role) || agent.status !== 'stopped') return false;\n if (agent.paused === true || agent.troubled === true) return false;\n if (!isRecentBootCandidate(agent)) return false;\n if (bootReconciliationSkipReason(agent) !== null) return false;\n if (!agent.workspace || !existsSync(agent.workspace)) return false;\n if (isTerminalIssueStage(getIssueStageSync(agent.issueId))) return false;\n return true;\n}\n\nexport function listBootReconciliationCandidateIds(): string[] {\n return listBootReconciliationCandidates().map((agent) => agent.id);\n}\n\nexport function listBootReconciliationCandidates(): ReconciliationAgent[] {\n return listAllAgentsSync()\n .filter(isBootReconciliationCandidate);\n}\n\nexport function getBootReconciliationPendingHoldSet(): Set<string> {\n const state = getBootReconciliationState();\n if (state.decision !== 'pending') return new Set();\n return new Set(listBootReconciliationCandidateIds());\n}\n\nexport function getBootReconciliationHeldResumeSet(): Set<string> {\n const state = getBootReconciliationState();\n if (state.decision !== 'pending' && state.decision !== 'hold_all' && state.decision !== 'per_agent') {\n return new Set();\n }\n\n const heldCandidates = listBootReconciliationCandidates()\n .filter((agent) => state.decision !== 'per_agent' || state.perAgent[agent.issueId] !== 'resume')\n .map((agent) => agent.id);\n return new Set(heldCandidates);\n}\n\nexport function clearBootReconciliationGraceTimer(): void {\n graceExpiryHook = null;\n if (!graceTimer) return;\n clearTimeout(graceTimer);\n graceTimer = null;\n}\n\n/**\n * Push the grace deadline out by one full grace period. Only legal while the\n * decision is still `pending`, and only up to MAX_BOOT_RECONCILIATION_GRACE_EXTENSIONS\n * times per boot. The caller (the extend route) is expected to fire this while\n * another blocking dialog is covering the boot modal.\n */\nexport function extendBootReconciliationGrace(now: Date = new Date()): BootReconciliationExtendResult {\n const state = getBootReconciliationState();\n const maxGraceExtensions = MAX_BOOT_RECONCILIATION_GRACE_EXTENSIONS;\n\n if (state.decision !== 'pending') {\n return {\n extended: false,\n graceDeadline: state.graceDeadline,\n graceExtensions: state.graceExtensions,\n maxGraceExtensions,\n reason: 'not-pending',\n };\n }\n\n if (state.graceExtensions >= maxGraceExtensions) {\n return {\n extended: false,\n graceDeadline: state.graceDeadline,\n graceExtensions: state.graceExtensions,\n maxGraceExtensions,\n reason: 'cap-reached',\n };\n }\n\n const currentDeadlineMs = state.graceDeadline == null ? NaN : Date.parse(state.graceDeadline);\n if (!Number.isFinite(currentDeadlineMs)) {\n return {\n extended: false,\n graceDeadline: state.graceDeadline,\n graceExtensions: state.graceExtensions,\n maxGraceExtensions,\n reason: 'no-deadline',\n };\n }\n\n // Extend from whichever is later: the standing deadline, or now. A deadline\n // that already slipped past (server was busy) must still yield a full window.\n const graceMs = getBootReconciliationGraceSeconds() * 1000;\n const base = Math.max(currentDeadlineMs, now.getTime());\n const graceDeadline = new Date(base + graceMs).toISOString();\n const graceExtensions = state.graceExtensions + 1;\n\n setBootReconciliationGrace(graceDeadline, graceExtensions);\n armBootReconciliationGraceTimer(graceDeadline, graceExpiryHook ?? undefined);\n logDeaconEventSync(\n `boot reconciliation grace extended to ${graceDeadline} (${graceExtensions}/${maxGraceExtensions}) — operator has another blocking dialog open`,\n );\n\n return { extended: true, graceDeadline, graceExtensions, maxGraceExtensions, reason: 'extended' };\n}\n\nexport function armBootReconciliationGraceTimer(\n graceDeadline: string,\n onGraceExpired: () => void | Promise<void> = () => undefined,\n): boolean {\n clearBootReconciliationGraceTimer();\n const deadlineMs = Date.parse(graceDeadline);\n if (!Number.isFinite(deadlineMs)) return false;\n const delayMs = Math.max(0, deadlineMs - Date.now());\n // Retained so an extend can re-arm the same apply step against a later deadline.\n graceExpiryHook = onGraceExpired;\n\n graceTimer = setTimeout(() => {\n graceTimer = null;\n if (getBootReconciliationState().decision !== 'pending') return;\n if (listBootReconciliationCandidateIds().length === 0) {\n setBootReconciliationDecision('resume_all');\n logDeaconEventSync('boot reconciliation grace expired — vacuous hold auto-released (0 candidates)');\n void Promise.resolve(onGraceExpired()).catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`boot reconciliation grace expiry apply hook failed: ${message}`);\n });\n return;\n }\n const decision = isCleanShutdownBoot() ? 'resume_all' : 'hold_all';\n setBootReconciliationDecision(decision);\n logDeaconEventSync(decision === 'resume_all'\n ? 'boot reconciliation grace expired — clean shutdown within 30m, decision set to resume_all'\n : 'boot reconciliation grace expired — no clean shutdown marker (crash boot), decision set to hold_all');\n void Promise.resolve(onGraceExpired()).catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`boot reconciliation grace expiry apply hook failed: ${message}`);\n });\n }, delayMs);\n graceTimer.unref?.();\n return true;\n}\n\nexport function startBootReconciliation(\n options: StartBootReconciliationOptions = {},\n): BootReconciliationStartupResult {\n const now = options.now ?? new Date();\n const bootId = options.bootId ?? process.env.OVERDECK_BOOT_ID ?? `boot-${now.toISOString()}`;\n const graceDeadline = new Date(now.getTime() + getBootReconciliationGraceSeconds() * 1000).toISOString();\n\n // PAN-3547: a read/UI peer (OVERDECK_DISABLE_DEACON=1 — workspace container\n // or host-mode dev dashboard) must never assert boot ownership in shared\n // state: no stamp, no pending decision, no grace timer. On 2026-08-04 a\n // peer boot stamped a record holding MIN-839/864/874 and the primary\n // surfaced the peer's pending record to the operator with a live\n // resume-on-timeout countdown. Boot state is written by the primary only.\n if (process.env.OVERDECK_DISABLE_DEACON === '1') {\n logDeaconEventSync(`boot reconciliation skipped for ${bootId} — peer dashboard (OVERDECK_DISABLE_DEACON=1) does not write boot records (PAN-3547)`);\n return { bootId, graceDeadline, candidateIds: [], decision: 'hold_all', timerArmed: false };\n }\n\n const existing = getBootReconciliationState();\n const bootStartedAt = existing.bootId === bootId && existing.bootStartedAt\n ? existing.bootStartedAt\n : now.toISOString();\n const stampedGraceDeadline = existing.bootId === bootId && existing.graceDeadline\n ? existing.graceDeadline\n : graceDeadline;\n\n if (existing.bootId !== bootId || !existing.bootStartedAt) {\n stampBootReconciliation(bootId, stampedGraceDeadline, bootStartedAt);\n }\n\n const candidateIds = listBootReconciliationCandidateIds();\n\n if (existing.bootId === bootId && existing.decision) {\n const existingGraceDeadline = existing.graceDeadline ?? graceDeadline;\n if (existing.decision === 'pending') {\n const timerArmed = armBootReconciliationGraceTimer(existingGraceDeadline, options.onGraceExpired);\n logDeaconEventSync(`boot reconciliation preserved ${bootId}: pending until ${existingGraceDeadline}`);\n return { bootId, graceDeadline: existingGraceDeadline, candidateIds, decision: 'pending', timerArmed };\n }\n\n clearBootReconciliationGraceTimer();\n logDeaconEventSync(`boot reconciliation preserved ${bootId}: decision=${existing.decision}`);\n return {\n bootId,\n graceDeadline: existingGraceDeadline,\n candidateIds,\n decision: existing.decision,\n timerArmed: false,\n };\n }\n\n if (isExplicitNoResumeRequest()) {\n clearBootReconciliationGraceTimer();\n setBootReconciliationDecision('hold_all');\n logDeaconEventSync(`boot reconciliation stamped ${bootId}: explicit OVERDECK_NO_RESUME requested hold_all`);\n return { bootId, graceDeadline, candidateIds, decision: 'hold_all', timerArmed: false };\n }\n\n // PAN-2510: an empty candidate list at stamp time does NOT mean \"genuinely\n // nothing to reconcile\". startBootReconciliation() runs in the dashboard\n // process, but agent liveness is reconciled asynchronously in the separately\n // spawned deacon child (reconcileAgentLiveness marks crashed agents `stopped`\n // ~1s later). On a full box reboot (OOM crash, power cycle) the agents are\n // still marked `healthy`/`running` in the table when the dashboard stamps, so\n // listBootReconciliationCandidateIds() returns 0 — then the crashed agents\n // materialize as candidates a second later. Terminally committing `resume_all`\n // here (the pre-PAN-2510 fast path) permanently skipped the operator dialog\n // AND left the held-resume set empty, so those late-arriving candidates\n // auto-resumed ungated — the exact post-reboot flood we must avoid.\n //\n // Instead we always open the grace window in `pending`. The candidate set is\n // recomputed live (frontend poll + held-resume/pending-hold sets), so late\n // arrivals from the deacon-child reconciliation are discovered and held, and\n // the dialog renders. If the window expires with still-zero candidates\n // (genuinely clean boot / dashboard-only restart with nothing stopped), the\n // grace timer resolves `pending` → `hold_all`, the safe timeout default.\n setBootReconciliationDecision('pending');\n const timerArmed = armBootReconciliationGraceTimer(graceDeadline, options.onGraceExpired);\n const heldSummary = candidateIds.length === 0\n ? 'awaiting agent-table reconciliation (0 candidates at stamp time)'\n : `holding ${candidateIds.length} candidate(s)`;\n logDeaconEventSync(`boot reconciliation stamped ${bootId}: ${heldSummary} until ${graceDeadline}`);\n return { bootId, graceDeadline, candidateIds, decision: 'pending', timerArmed };\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getAgentDir } from './agent-state.js';\n\nexport interface PinnedAgentLaunch {\n sessionId: string;\n model: string;\n harness: RuntimeName;\n}\n\nfunction parseFlagValue(content: string, flag: '--model' | '-m'): string | null {\n const escapedFlag = flag.replace('-', '\\\\-');\n const quoted = content.match(new RegExp(`${escapedFlag}\\\\s+'([^']+)'`))\n ?? content.match(new RegExp(`${escapedFlag}\\\\s+\"([^\"]+)\"`));\n if (quoted?.[1]) return quoted[1];\n return content.match(new RegExp(`${escapedFlag}\\\\s+([^\\\\s'\"\\\\\\\\]+)`))?.[1] ?? null;\n}\n\nexport function parsePinnedAgentLaunch(\n launcher: string,\n sessionId: string,\n): PinnedAgentLaunch | null {\n const pinnedSessionId = sessionId.trim();\n if (!pinnedSessionId) return null;\n\n const harness: RuntimeName | null = launcher.includes('codex-app-server-host.js')\n || /(?:^|\\s)(?:exec\\s+)?codex(?:\\s|$)/m.test(launcher)\n ? 'codex'\n : /(?:^|\\s)(?:exec\\s+)?omp\\s+--mode\\s+rpc(?:\\s|$)/m.test(launcher)\n ? 'ohmypi'\n : /(?:^|\\s)claude(?:\\s|$)/m.test(launcher)\n ? 'claude-code'\n : null;\n const model = parseFlagValue(launcher, '--model') ?? parseFlagValue(launcher, '-m');\n if (!harness || !model || model.startsWith('pending-')) return null;\n\n return { sessionId: pinnedSessionId, model, harness };\n}\n\nexport function readPinnedAgentLaunchSync(agentId: string): PinnedAgentLaunch | null {\n const agentDir = getAgentDir(agentId);\n const launcherPath = join(agentDir, 'launcher.sh');\n const sessionIdPath = join(agentDir, 'session.id');\n if (!existsSync(launcherPath) || !existsSync(sessionIdPath)) return null;\n\n try {\n return parsePinnedAgentLaunch(\n readFileSync(launcherPath, 'utf8'),\n readFileSync(sessionIdPath, 'utf8'),\n );\n } catch {\n return null;\n }\n}\n","import { Effect } from 'effect';\n\nimport { logAgentLifecycleSync, logDeaconEventSync } from '../persistent-logger.js';\nimport type { AgentState } from './agent-state.js';\nimport { markAgentRunningState, saveAgentState } from './agent-state.js';\nimport { readPinnedAgentLaunchSync, type PinnedAgentLaunch } from './pinned-launch.js';\n\ninterface PlaceholderReconciliationDeps {\n readPinnedLaunch: (agentId: string) => PinnedAgentLaunch | null;\n markRunning: (state: AgentState) => AgentState;\n saveState: (state: AgentState) => Promise<void>;\n logDeacon: (message: string) => void;\n logAgent: (agentId: string, message: string) => void;\n}\n\nfunction defaultPlaceholderReconciliationDeps(): PlaceholderReconciliationDeps {\n return {\n readPinnedLaunch: readPinnedAgentLaunchSync,\n markRunning: markAgentRunningState,\n saveState: (state) => Effect.runPromise(saveAgentState(state)),\n logDeacon: logDeaconEventSync,\n logAgent: logAgentLifecycleSync,\n };\n}\n\nexport async function reconcileLiveWorkSpawnPlaceholder(\n state: AgentState,\n notifyStatusChanged: (\n state: AgentState,\n previousStatus?: AgentState['status'],\n hasLiveTmuxSession?: boolean,\n ) => void,\n deps?: PlaceholderReconciliationDeps,\n): Promise<string | null> {\n if (state.model !== 'pending-work-spawn') return null;\n const resolvedDeps = deps ?? defaultPlaceholderReconciliationDeps();\n const pinnedLaunch = resolvedDeps.readPinnedLaunch(state.id);\n if (!pinnedLaunch) return null;\n\n const previousStatus = state.status;\n state.model = pinnedLaunch.model;\n state.harness = pinnedLaunch.harness;\n state.sessionId = pinnedLaunch.sessionId;\n resolvedDeps.markRunning(state);\n await resolvedDeps.saveState(state);\n notifyStatusChanged(state, previousStatus, true);\n\n const message = `Reconciled ${state.id} placeholder to running (${pinnedLaunch.harness}/${pinnedLaunch.model})`;\n resolvedDeps.logDeacon(`handleAgentHeartbeatDeadEvent: ${message}`);\n resolvedDeps.logAgent(state.id, message);\n return message;\n}\n","import { querySessionSync, type SessionQueryResult } from '../tmux.js';\nimport { supervisorProcessAliveSync } from '../agents/supervisor-liveness.js';\n\nconst consecutiveMisses = new Map<string, number>();\n\nexport function queryConfirmedSession(agentId: string): [SessionQueryResult, string?] {\n const query = querySessionSync(agentId);\n if (query.status === 'error') {\n consecutiveMisses.delete(agentId);\n return [query, `skipped — tmux query failed (${query.detail})`];\n }\n if (query.status === 'exists') {\n consecutiveMisses.delete(agentId);\n return [query];\n }\n // PAN-3002: a missing tmux session is NOT proof of death for supervisor-\n // delivered agents — the pty-supervisor worker survives its tmux session and\n // keeps running untracked (MIN-882: 36h lost; PAN-2997: killed 41s after a\n // healthy resume). A live worker process retains the agent.\n if (supervisorProcessAliveSync(agentId)) {\n consecutiveMisses.delete(agentId);\n return [query, `retained — live pty-supervisor process despite missing tmux session (PAN-3002)`];\n }\n const missCount = (consecutiveMisses.get(agentId) ?? 0) + 1;\n consecutiveMisses.set(agentId, missCount);\n return missCount < 2\n ? [query, `retained after first confirmed tmux miss (${query.detail})`]\n : [query];\n}\n\nexport function clearConfirmedSessionMiss(agentId: string): void {\n consecutiveMisses.delete(agentId);\n}\n\nexport function consumeConfirmedSessionDetail(agentId: string, query: SessionQueryResult): string {\n clearConfirmedSessionMiss(agentId);\n return query.status === 'missing' ? query.detail : 'pane confirmed dead';\n}\n","import { getReviewStatusSync } from '../review-status.js';\nimport type { XBriefDocument, XBriefItem } from '../xbrief/types.js';\n\nconst BLOCKING_INSPECTION_STATUSES = new Set(['failed', 'blocked', 'error', 'inspecting']);\n\nexport interface BlockingMandatoryInspection {\n item: XBriefItem;\n status: string;\n notes?: string;\n}\n\nexport function getBlockingMandatoryInspection(\n plan: XBriefDocument,\n issueId: string,\n): BlockingMandatoryInspection | null {\n const inspection = getReviewStatusSync(issueId);\n const status = inspection?.inspectStatus as string | undefined;\n if (!status || !BLOCKING_INSPECTION_STATUSES.has(status) || !inspection?.inspectBeadId) return null;\n\n const item = plan.plan.items.find(candidate => candidate.id === inspection.inspectBeadId);\n if (item?.status !== 'completed' || item.metadata?.requiresInspection !== true) return null;\n\n return { item, status, notes: inspection.inspectNotes };\n}\n\nexport function buildInspectionBlockedNudge(\n issueId: string,\n inspection: BlockingMandatoryInspection,\n): string {\n const lines = [\n `Deacon idle-nudge: mandatory inspection blocks further task advancement for ${issueId}.`,\n '',\n `Inspection item: ${inspection.item.id} ${inspection.item.title}`,\n `Inspection status: ${inspection.status}`,\n `Actionable inspection notes: ${inspection.notes?.trim() || 'No inspection notes were recorded.'}`,\n '',\n ];\n\n if (inspection.status === 'failed' || inspection.status === 'blocked') {\n lines.push(\n `You must fix ${inspection.item.id}, commit and push the correction, then re-run \\`pan inspect ${issueId} --item ${inspection.item.id}\\`. Do not claim or implement another task until inspection passes.`,\n );\n } else if (inspection.status === 'error') {\n lines.push(\n `Resolve or report the inspection infrastructure failure, then re-run \\`pan inspect ${issueId} --item ${inspection.item.id}\\`; do not advance to another task until inspection passes.`,\n );\n } else {\n lines.push(\n `Wait for the inspection verdict for ${inspection.item.id}; do not advance to another task until inspection passes.`,\n );\n }\n\n return lines.join('\\n');\n}\n","import { existsSync, readdirSync, readFileSync, writeFileSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { cpus, loadavg } from 'os';\nimport { Effect } from 'effect';\nimport { isStartingWithinGrace } from './agent-grace.js';\nimport { isAgentIdleForNudge } from './agent-idle.js';\nimport { getConcurrencyLimits, countRunningAgents, workResumeSlotsAvailable } from './concurrency.js';\nimport { assessMemoryPressure } from './memory-governor.js';\nimport { resumeYieldedAgents } from './preemption.js';\nimport {\n getBootReconciliationHeldResumeSet,\n getBootReconciliationPendingHoldSet,\n isAutoResumableRole,\n} from './boot-reconciliation.js';\nimport { bootReconciliationSkipReason } from './boot-reconciliation-predicates.js';\nimport { isIssueClosed } from './issue-closed.js';\nimport { listAllAgentsSync as listAllAgents, RETAINED_TRANSCRIPTS_PHASE } from '../overdeck/agents.js';\nimport { emitActivityEntrySync, emitActivityTtsSync } from '../activity-logger.js';\nimport { logDeaconEventSync, logAgentLifecycleSync } from '../persistent-logger.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { getBootReconciliationState } from '../overdeck/control-settings.js';\nimport { captureTranscriptUserRecordSnapshot } from '../transcript-landing.js';\nimport {\n buildResumeContinueMessage,\n getAgentDir,\n getAgentRuntimeStateSync,\n getAgentState,\n getAgentStateSync,\n listAgentStates,\n markAgentRunningState,\n recordAgentFailure,\n resetAgentFailureCount,\n resumeAgent,\n stopAgent,\n saveAgentState,\n saveAgentStateSync,\n deliverInitialPromptWithRetry,\n type AgentState,\n} from '../agents.js';\nimport {\n killSession,\n listPaneValues,\n listSessionNames,\n sessionExists,\n sessionExistsSync,\n} from '../tmux.js';\nimport { readWorkspacePlanSync } from '../xbrief/io.js';\nimport { getDispatchableItems } from '../xbrief/dag.js';\nimport type { XBriefItem } from '../xbrief/types.js';\nimport { reconcileLiveWorkSpawnPlaceholder } from '../agents/placeholder-reconciliation.js';\nimport { consumeConfirmedSessionDetail, queryConfirmedSession } from './confirmed-session-query.js';\nimport { isTerminalSwarmSlotAgent } from './swarm-slot-lifecycle.js';\nimport { buildInspectionBlockedNudge, getBlockingMandatoryInspection } from './idle-nudge-inspection.js';\nexport interface AutoResumeNotifierDeps {\n notifyAgentStopped: (agentId: string) => void;\n notifyAgentStatusChanged: (state: AgentState, previousStatus?: AgentState['status'], hasLiveTmuxSession?: boolean) => void;\n}\n\nconst orphanFailureRecordedForAutoResume = new Set<string>();\n\nfunction isVerifyPausedAgentState(state: Pick<AgentState, 'issueId' | 'paused'>): boolean {\n if (state.paused !== true || !state.issueId) return false;\n return getReviewStatusSync(state.issueId)?.mergeStatus === 'merged';\n}\n\nfunction reviewArtifactExistsForRun(path: string | undefined, startedAt: string | undefined): boolean {\n if (!path || !existsSync(path)) return false;\n const startedMs = Date.parse(startedAt ?? '');\n if (!Number.isFinite(startedMs)) return true;\n try {\n return statSync(path).mtimeMs >= startedMs;\n } catch {\n return false;\n }\n}\n\nfunction hasCompletedReviewArtifact(state: AgentState): boolean {\n if (state.role !== 'review') return false;\n if (state.reviewSubRole) {\n return reviewArtifactExistsForRun(state.reviewOutputPath, state.startedAt);\n }\n if (!state.workspace || !state.reviewRunId) return false;\n return reviewArtifactExistsForRun(join(state.workspace, '.pan', 'review', state.reviewRunId, 'synthesis.md'), state.startedAt);\n}\n\nlet recoverOrphanedAgentsInFlight: Promise<string[]> | null = null;\nconst RAPID_POST_RESUME_DEATH_MS = 120_000;\n\nexport function isRapidPostResumeDeath(state: AgentState): boolean {\n const lastResumeMs = Date.parse(state.lastResumeAt ?? '');\n return Number.isFinite(lastResumeMs) && Date.now() - lastResumeMs <= RAPID_POST_RESUME_DEATH_MS;\n}\n\n/**\n * PAN-1718: a work agent that reached a tmux session but lost it before ever\n * delivering its kickoff never started doing real work — this is a launch\n * crash, not a healthy run that later died. Work agents set\n * kickoffDelivered=false at spawn and =true once the kickoff lands.\n *\n * Why this exists alongside isRapidPostResumeDeath: that guard keys off\n * lastResumeAt, so it only catches rapid death after a *resume*. A\n * fundamentally-broken work agent (crashing harness, dead model) is\n * re-dispatched by the orphan-proposed reconciler as a *fresh start* each\n * time, which has no lastResumeAt — and its startedAt→orphan span can exceed\n * the rapid window purely because the spawn itself is slow (e.g. ~2 min to\n * reach `running`). Without this, the orphan path resets the failure counter\n * every cycle, so consecutiveFailures oscillates 1→0→1 and never reaches\n * maxConsecutiveFailures — the troubled gate never trips and the agent\n * crash-loops forever. Treating a pre-kickoff orphan as an accumulating\n * failure lets it trip `troubled` after maxConsecutiveFailures, which the\n * reconciler then honors (it skips troubled agents) and the loop stops.\n */\nexport function isPreKickoffLaunchDeath(state: AgentState): boolean {\n return state.role === 'work' && state.kickoffDelivered === false;\n}\n\n/**\n * PAN-1908: event-driven orphan recovery. A single agent has been declared\n * heartbeat-dead (tmux session gone). Mark it stopped, record a failure for\n * auto-resume tracking, and notify subscribers. Does NOT enumerate directories —\n * it operates on the agent ID passed by the event/reconcile caller.\n */\nexport async function handleAgentHeartbeatDeadEvent(\n agentId: string,\n context: string | undefined,\n deps: AutoResumeNotifierDeps,\n): Promise<string[]> {\n const state = getAgentStateSync(agentId);\n if (!state) {\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} skipped — no state`);\n return [];\n }\n if (state.status !== 'running' && state.status !== 'starting') {\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} skipped — status=${state.status} (not running/starting)`);\n return [];\n }\n const verifyPaused = isVerifyPausedAgentState(state);\n let [sessionQuery, retainReason] = queryConfirmedSession(agentId);\n // A reconcile pass must settle stale registry rows without waiting for the\n // next stretched patrol, while preserving the two independent tmux misses\n // required to reject a transient session lookup failure.\n if (context === 'reconcile' && retainReason?.startsWith('retained after first confirmed tmux miss')) {\n [sessionQuery, retainReason] = queryConfirmedSession(agentId);\n }\n if (retainReason) { logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} ${retainReason}`); return []; }\n\n // PAN-1557: convoy reviewers are interactive — they own a tmux session\n // (remain-on-exit on) like other specialists, so liveness is the session's\n // pane, not a launcher pid. While the pane is alive the reviewer is working\n // or idling attachably; a dead pane (Claude exited) or a missing session\n // past the startup grace means it's done — fall through to mark stopped.\n if (state.reviewSubRole) {\n if (sessionQuery.status === 'exists') {\n try {\n const dead = ((await Effect.runPromise(listPaneValues(agentId, '#{pane_dead}')))[0]?.trim() ?? '') === '1';\n if (!dead) return []; // pane alive — still working / idling attachably\n try { await Effect.runPromise(killSession(agentId)); } catch { /* ignore */ }\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: killed dead reviewer pane ${agentId}`);\n } catch {\n return []; // can't check — assume alive\n }\n } else {\n // No session yet — startup grace keyed off startedAt before orphaning.\n const startedMs = Date.parse(state.startedAt ?? '');\n const REVIEWER_STARTUP_GRACE_MS = 90_000;\n if (Number.isFinite(startedMs) && Date.now() - startedMs < REVIEWER_STARTUP_GRACE_MS) {\n return [];\n }\n }\n // Session gone (or dead pane past grace) — fall through to mark stopped.\n } else if (sessionQuery.status === 'exists') {\n // Planning sessions use remain-on-exit, so the tmux session persists after\n // Claude exits. Check if the pane's process is actually dead.\n if (agentId.startsWith('planning-')) {\n try {\n const result = (await Effect.runPromise(listPaneValues(agentId, '#{pane_dead}')))[0]?.trim() ?? '';\n if (result !== '1') return []; // pane is alive — truly still running\n // Pane is dead — kill the zombie tmux session and fall through to recovery\n try { await Effect.runPromise(killSession(agentId)); } catch { /* ignore */ }\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: killed dead planning pane ${agentId}`);\n } catch {\n return []; // can't check — assume alive\n }\n } else {\n const action = await reconcileLiveWorkSpawnPlaceholder(state, deps.notifyAgentStatusChanged);\n return action ? [action] : [];\n }\n } else if (state.status === 'starting') {\n // PAN-1256: work agents in `starting` status need a startup grace\n // window before being declared orphaned.\n if (isStartingWithinGrace(state)) {\n return [];\n }\n // Past the grace window with no tmux session — true orphan, fall through.\n }\n\n // Orphaned — crashed agent with no tmux session\n const oldStatus = state.status;\n const missingSessionDetail = consumeConfirmedSessionDetail(agentId, sessionQuery);\n state.status = 'stopped';\n state.stoppedAt = new Date().toISOString();\n await Effect.runPromise(saveAgentState(state));\n // PAN-1530: only record failure markers for agents the auto-resume gate\n // will actually retry. Planning agents are one-shot by design.\n const isResumableRole = !agentId.startsWith('planning-');\n const completedReviewArtifact = hasCompletedReviewArtifact(state);\n if (state.stoppedByUser !== true && isResumableRole && !completedReviewArtifact && !verifyPaused) {\n const rapidPostResumeDeath = isRapidPostResumeDeath(state);\n const preKickoffLaunchDeath = isPreKickoffLaunchDeath(state);\n // PAN-1718: preserve (accumulate) the failure counter for deaths that prove\n // the agent never came up healthy — a rapid post-resume death, or a death\n // before the kickoff was ever delivered. Resetting on these lets a\n // fundamentally-broken agent that the reconciler re-dispatches as a fresh\n // start zero its counter every cycle and crash-loop forever without ever\n // tripping the troubled gate.\n const accumulatingDeath = rapidPostResumeDeath || preKickoffLaunchDeath;\n if (!accumulatingDeath) {\n resetAgentFailureCount(agentId);\n }\n const failureReason = rapidPostResumeDeath\n ? `rapid post-resume death: tmux session missing within ${RAPID_POST_RESUME_DEATH_MS / 1000}s (${context ?? 'event'})`\n : preKickoffLaunchDeath\n ? `launch crash: tmux session lost before kickoff delivery (${context ?? 'event'})`\n : `orphaned: tmux session missing (${context ?? 'event'})`;\n const failedState = await Effect.runPromise(recordAgentFailure(agentId, failureReason));\n if (failedState) {\n deps.notifyAgentStatusChanged(failedState, oldStatus, false);\n orphanFailureRecordedForAutoResume.add(agentId);\n }\n } else if (completedReviewArtifact) {\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} stopped after completed review artifact; not recording orphan failure`);\n } else if (verifyPaused) {\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${agentId} stopped after verify pause; not recording orphan failure`);\n }\n const msg = `Recovered orphaned agent ${agentId} (${oldStatus}→stopped)`;\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`handleAgentHeartbeatDeadEvent: ${msg} — tmux session missing (${missingSessionDetail}), state.json reset`);\n logAgentLifecycleSync(agentId, `status changed: ${oldStatus} → stopped (orphaned: tmux session missing)`);\n // Notify server layer so the read model and frontend update\n deps.notifyAgentStopped(agentId);\n return [msg];\n}\n\n/**\n * On startup, detect agents whose state.json claims 'running' or 'starting' but have\n * no live tmux session — this happens after a system crash where tmux was killed but\n * state.json was never updated. Reset them to 'stopped' so resume/re-plan works correctly.\n */\nexport async function recoverOrphanedAgents(context: string | undefined, deps: AutoResumeNotifierDeps): Promise<string[]> {\n if (recoverOrphanedAgentsInFlight) {\n logDeaconEventSync(`recoverOrphanedAgents coalesced${context ? ` (${context})` : ''}: scan already in flight`);\n return recoverOrphanedAgentsInFlight;\n }\n\n const scan = recoverOrphanedAgentsOnce(context, deps);\n recoverOrphanedAgentsInFlight = scan;\n try {\n return await scan;\n } finally {\n if (recoverOrphanedAgentsInFlight === scan) {\n recoverOrphanedAgentsInFlight = null;\n }\n }\n}\n\nasync function recoverOrphanedAgentsOnce(context: string | undefined, deps: AutoResumeNotifierDeps): Promise<string[]> {\n // PAN-1908: authoritative registry is the agents table; no directory scan.\n const candidates = listAllAgents()\n .filter((agent) => agent.status === 'running' || agent.status === 'starting')\n .map((agent) => agent.id);\n\n logDeaconEventSync(`recoverOrphanedAgents started${context ? ` (${context})` : ''}: ${candidates.length} candidate(s) from agents table`);\n const actions: string[] = [];\n for (const agentId of candidates) {\n try {\n const result = await handleAgentHeartbeatDeadEvent(agentId, context ?? 'patrol', deps);\n actions.push(...result);\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`recoverOrphanedAgents: error processing ${agentId}: ${reason}`);\n }\n }\n if (actions.length > 0 && context) {\n console.log(`[deacon] ${context}: ${actions.length} orphaned agent(s) reset to stopped`);\n logDeaconEventSync(`recoverOrphanedAgents completed (${context}): ${actions.length} orphaned agent(s) reset to stopped`);\n } else {\n logDeaconEventSync(`recoverOrphanedAgents completed: no orphaned agents found`);\n }\n return actions;\n}\n\n/**\n * Kill `planning-*` tmux sessions whose corresponding work agent (`agent-*`) is\n * already alive — that's definitive evidence planning is over. Handles the PAN-682\n * pattern where a planning session survives after `complete-planning` fails to\n * kill it (skipKill=true path, or complete-planning never invoked because the\n * work agent was started via a different code path).\n */\nexport async function cleanupOrphanedPlanningSessions(deps: AutoResumeNotifierDeps): Promise<string[]> {\n const actions: string[] = [];\n let planningSessions: string[];\n try {\n planningSessions = (await Effect.runPromise(listSessionNames()))\n .filter(s => s.startsWith('planning-'));\n } catch {\n return actions;\n }\n\n logDeaconEventSync(`cleanupOrphanedPlanningSessions started: found ${planningSessions.length} planning session(s)`);\n\n for (const planningSession of planningSessions) {\n // planning-pan-596 → agent-pan-596\n const workAgentSession = planningSession.replace(/^planning-/, 'agent-');\n if (!sessionExistsSync(workAgentSession)) {\n logDeaconEventSync(`cleanupOrphanedPlanningSessions: ${planningSession} kept — work agent ${workAgentSession} not running`);\n continue;\n }\n\n try {\n await Effect.runPromise(killSession(planningSession)).catch(() => {});\n } catch { /* non-fatal */ }\n\n // Mark planning agent state as stopped so the UI doesn't show a \"running\" pill.\n try {\n const agentState = getAgentStateSync(planningSession);\n if (agentState && (agentState.status === 'running' || agentState.status === 'starting')) {\n const oldStatus = agentState.status;\n saveAgentStateSync({ ...agentState, status: 'stopped', stoppedAt: new Date().toISOString() });\n deps.notifyAgentStopped(planningSession);\n logAgentLifecycleSync(planningSession, `status changed: ${oldStatus} → stopped (orphaned planning session killed)`);\n }\n } catch (err: unknown) {\n const reason = err instanceof Error ? err.message : String(err);\n logDeaconEventSync(`cleanupOrphanedPlanningSessions: error updating state for ${planningSession}: ${reason}`);\n }\n\n const msg = `Killed orphaned ${planningSession} (work agent ${workAgentSession} is running)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`cleanupOrphanedPlanningSessions: ${msg}`);\n }\n if (actions.length > 0) {\n logDeaconEventSync(`cleanupOrphanedPlanningSessions completed: killed ${actions.length} orphaned session(s)`);\n } else {\n logDeaconEventSync(`cleanupOrphanedPlanningSessions completed: no orphaned sessions found`);\n }\n\n return actions;\n}\n\n/**\n * Nudge work agents that are alive-but-idle with open beads remaining.\n *\n * Detects the gap that autoResumeStoppedWorkAgents misses: agents whose tmux\n * session is alive and `state.status === 'running'`, but whose Stop hook has\n * fired (state='idle' in the runtime mirror) and have NOT advanced. The\n * existing recovery paths only fire on `status='stopped'` (process killed)\n * or on a downstream review failure — neither matches this case.\n *\n * Triggered when ALL of the following hold:\n * - state.status === 'running' (process still alive)\n * - phase === 'implementation' or 'review-response'\n * - tmux session exists (not orphaned)\n * - isAgentIdleForNudge() returns true (Stop hook authoritative)\n * - pan task next -l <issueLabel> has ≥1 ready bead (work remaining)\n * - last nudge older than NUDGE_COOLDOWN_MS (don't spam)\n *\n * Action: send `pan tell` with a concrete imperative pointing at the next\n * ready bead. Updates `<agentDir>/.last-bead-nudge` for cooldown.\n *\n * Returns a list of action descriptions for runPatrol to log.\n */\nconst BEAD_NUDGE_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes\nconst STALLED_RESUME_NUDGE_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes\nexport const KICKOFF_REDELIVERY_THRESHOLD_MS = 3 * 60 * 1000; // 3 minutes\nconst KICKOFF_REDELIVERY_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes\n\nfunction hasLandedUserRecordSinceResume(state: AgentState, snapshot: Awaited<ReturnType<typeof captureTranscriptUserRecordSnapshot>>): boolean {\n const resumeAt = state.lastResumeAt ? Date.parse(state.lastResumeAt) : NaN;\n if (!Number.isFinite(resumeAt)) return true;\n const lastUserAt = snapshot.lastUserRecord?.timestamp ? Date.parse(snapshot.lastUserRecord.timestamp) : NaN;\n if (!Number.isFinite(lastUserAt)) return snapshot.userRecordCount > 0;\n return lastUserAt >= resumeAt;\n}\n\nfunction cooldownActive(agentId: string, filename: string, cooldownMs: number): boolean {\n const cooldownFile = join(getAgentDir(agentId), filename);\n if (!existsSync(cooldownFile)) return false;\n try {\n const last = parseInt(readFileSync(cooldownFile, 'utf-8').trim(), 10);\n return !Number.isNaN(last) && Date.now() - last < cooldownMs;\n } catch {\n return false;\n }\n}\n\nfunction recordCooldown(agentId: string, filename: string): void {\n writeFileSync(join(getAgentDir(agentId), filename), String(Date.now()), 'utf-8');\n}\n\nfunction buildStalledResumePrompt(state: AgentState): string | null {\n if (state.kickoffDelivered === false) {\n const promptPath = join(getAgentDir(state.id), 'initial-prompt.md');\n try {\n return readFileSync(promptPath, 'utf-8');\n } catch (err) {\n logDeaconEventSync(`nudgeStalledResumeWorkAgents: ${state.id} skipped — kickoffDelivered=false but ${promptPath} is unreadable: ${err instanceof Error ? err.message : String(err)}`);\n return null;\n }\n }\n return buildResumeContinueMessage(state);\n}\n\nfunction readyTaskLine(item: XBriefItem): string {\n return `${item.id} ${item.title}`.trim();\n}\n\nexport async function nudgeStalledResumeWorkAgents(): Promise<string[]> {\n const actions: string[] = [];\n\n const states = listAgentStates({ status: 'running', role: 'work' });\n\n for (const state of states) {\n const agentId = state.id;\n if (state.paused || state.troubled) continue;\n if (!state.lastResumeAt) continue;\n if (await isIssueClosed(state.issueId)) continue;\n if (!await Effect.runPromise(sessionExists(agentId))) continue;\n if (!isAgentIdleForNudge(agentId)) continue;\n if (cooldownActive(agentId, '.last-stalled-resume-nudge', STALLED_RESUME_NUDGE_COOLDOWN_MS)) continue;\n\n const sessionId = state.sessionId;\n if (!sessionId) continue;\n const snapshot = await captureTranscriptUserRecordSnapshot(state.workspace, sessionId);\n if (hasLandedUserRecordSinceResume(state, snapshot)) continue;\n\n const message = buildStalledResumePrompt(state);\n if (!message) continue;\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(agentId, message);\n recordCooldown(agentId, '.last-stalled-resume-nudge');\n const action = `Re-sent stalled resume prompt to ${agentId} (${state.issueId})`;\n actions.push(action);\n logDeaconEventSync(`nudgeStalledResumeWorkAgents: ${action}`);\n } catch (err: unknown) {\n logDeaconEventSync(`nudgeStalledResumeWorkAgents: ${agentId} messageAgent failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return actions;\n}\n\nexport async function redeliverUndeliveredKickoffs(): Promise<string[]> {\n const actions: string[] = [];\n const states = Array.from(new Map([\n ...listAgentStates({ status: 'running', role: 'work' }),\n ...listAgentStates({ status: 'running', role: 'flywheel' }),\n ].map((state) => [state.id, state])).values());\n\n for (const state of states) {\n const agentId = state.id;\n if (state.kickoffDelivered !== false) continue;\n if (state.paused || state.troubled) continue;\n if (await isIssueClosed(state.issueId)) continue;\n if (!await Effect.runPromise(sessionExists(agentId))) continue;\n\n const startedMs = Date.parse(state.startedAt ?? '');\n if (!Number.isFinite(startedMs) || Date.now() - startedMs <= KICKOFF_REDELIVERY_THRESHOLD_MS) continue;\n if (cooldownActive(agentId, '.last-kickoff-redelivery', KICKOFF_REDELIVERY_COOLDOWN_MS)) continue;\n\n const promptPath = join(getAgentDir(agentId), 'initial-prompt.md');\n let prompt: string;\n try {\n prompt = readFileSync(promptPath, 'utf-8');\n } catch (err) {\n logDeaconEventSync(`redeliverUndeliveredKickoffs: ${agentId} skipped — ${promptPath} unreadable: ${err instanceof Error ? err.message : String(err)}`);\n continue;\n }\n\n recordCooldown(agentId, '.last-kickoff-redelivery');\n const delivery = await deliverInitialPromptWithRetry(agentId, prompt, 'deacon:redeliver-undelivered-kickoff', state.deliveryMethod);\n\n if (delivery.ok) {\n const updated = { ...state, kickoffDelivered: true };\n await Effect.runPromise(saveAgentState(updated));\n const action = `Re-delivered undelivered kickoff to ${agentId} (${state.issueId})`;\n actions.push(action);\n logDeaconEventSync(`redeliverUndeliveredKickoffs: ${action}`);\n continue;\n }\n\n const reason = `kickoff re-delivery failed for ${agentId}: ${delivery.failure ?? 'unknown error'}`;\n const failedState = await Effect.runPromise(recordAgentFailure(agentId, reason));\n logDeaconEventSync(`redeliverUndeliveredKickoffs: ${reason}`);\n if (failedState?.troubled === true) {\n const message = `${agentId} is troubled after repeated kickoff re-delivery failures; operator intervention is required.`;\n emitActivityEntrySync({ source: 'cloister', level: 'error', message, issueId: state.issueId });\n emitActivityTtsSync({ utterance: message, priority: 0, issueId: state.issueId, source: 'cloister', eventType: 'kickoff_redelivery_failed' });\n actions.push(message);\n }\n }\n\n return actions;\n}\n\nexport async function nudgeIdleWorkAgentsWithOpenBeads(): Promise<string[]> {\n const actions: string[] = [];\n\n const states = listAgentStates({ status: 'running', role: 'work' });\n const eligible: AgentState[] = [];\n\n for (const state of states) {\n const agentId = state.id;\n if (await isIssueClosed(state.issueId)) {\n logDeaconEventSync(`nudgeIdleWorkAgentsWithOpenBeads: ${agentId} skipped — issue ${state.issueId} is closed`);\n continue;\n }\n\n // Tmux must be alive; orphans are handled by recoverOrphanedAgents.\n if (!await Effect.runPromise(sessionExists(agentId))) continue;\n\n // Authoritative idle signal — Stop hook fired and runtime mirror is idle.\n // Skips agents currently mid-thought (state='active') and ones we already\n // know are stopped/suspended.\n if (!isAgentIdleForNudge(agentId)) continue;\n\n // Cooldown — don't nudge the same agent more than once per BEAD_NUDGE_COOLDOWN_MS.\n const cooldownFile = join(getAgentDir(agentId), '.last-bead-nudge');\n if (existsSync(cooldownFile)) {\n try {\n const last = parseInt(readFileSync(cooldownFile, 'utf-8').trim(), 10);\n if (!Number.isNaN(last) && Date.now() - last < BEAD_NUDGE_COOLDOWN_MS) continue;\n } catch { /* fall through and nudge */ }\n }\n\n eligible.push(state);\n }\n\n for (const state of eligible) {\n const agentId = state.id;\n const plan = readWorkspacePlanSync(state.workspace);\n if (!plan) continue;\n const openTasks = getDispatchableItems(plan, new Set());\n const blockedInspection = getBlockingMandatoryInspection(plan, state.issueId);\n if (openTasks.length === 0 && !blockedInspection) continue;\n\n // Build the nudge: tell the agent what's next, do not just ping.\n const firstTask = openTasks[0] ? readyTaskLine(openTasks[0]).slice(0, 200) : '';\n // PAN-2102: startup kickoff delivery can silently fail on large briefs (the\n // ~50KB initial prompt trips the PTY supervisor's echo-confirm), leaving the\n // agent running with NO original context — only this nudge. Point it at the\n // brief on disk so it can self-recover the full plan/role/decisions/hazards\n // instead of guessing from the bead title alone.\n const briefPath = join(getAgentDir(agentId), 'initial-prompt.md');\n const message = blockedInspection ? buildInspectionBlockedNudge(state.issueId, blockedInspection) : [\n `Deacon idle-nudge: your tmux is alive but the agent is idle and you have ${openTasks.length} ready task(s) for ${state.issueId}.`,\n ``,\n `Next ready task: ${firstTask}`,\n ``,\n `If you don't already have your full brief for ${state.issueId} in context (work-agent role instructions, the xBRIEF plan, recorded decisions & hazards), re-read it now — it is on disk at ${briefPath}, plus .pan/continue.json and .pan/spec.vbrief.json in your workspace. Startup kickoff delivery can silently fail on large briefs, so do not assume you received it.`,\n ``,\n `Continue the per-item workflow without asking — claim it (\\`pan task claim <item-id>\\`), implement, commit, push, then complete it through \\`pan task done <item-id>\\`. ` +\n `Inspection is conditional on metadata.requiresInspection (default false; check the plan item before deciding to call \\`pan inspect\\`). ` +\n `Do NOT end your turn with a multi-paragraph summary; just advance to the next bead.`,\n ].join('\\n');\n\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(agentId, message);\n writeFileSync(join(getAgentDir(agentId), '.last-bead-nudge'), String(Date.now()), 'utf-8');\n const action = blockedInspection\n ? `Nudged idle ${agentId} (${state.issueId}) — mandatory inspection ${blockedInspection.status} for ${blockedInspection.item.id}`\n : `Nudged idle ${agentId} (${state.issueId}) — ${openTasks.length} ready task(s)`;\n actions.push(action);\n logDeaconEventSync(`nudgeIdleWorkAgentsWithOpenBeads: ${action}`);\n } catch (err: unknown) {\n logDeaconEventSync(`nudgeIdleWorkAgentsWithOpenBeads: ${agentId} messageAgent failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return actions;\n}\n\n/**\n * Auto-resume work agents that were stopped by a system crash/reboot\n * but still have incomplete work. Scans all agent state directories for\n * stopped work-role agents and resumes them.\n *\n * Resumption rules:\n * - Agents with pending review feedback (blocked/failed/verification-failed)\n * are ALWAYS resumed — the specialist pipeline needs them to fix issues.\n * - Agents without pending feedback are skipped if stoppedByUser=true (the\n * user deliberately killed them via pan kill / pan done).\n * - Orphaned agents (tmux session missing, no stoppedByUser flag) are resumed.\n *\n * Called by runPatrol() on every patrol cycle AND during deacon startup.\n *\n * PAN-1665: bounded by the concurrency governor. An unfreeze used to mass-resume\n * every stopped work agent back-to-back, marching the box toward dozens of heavy\n * `claude` processes (load spiked 5→52). We now resume only up to the number of\n * free work slots (`max_work_agents − runningWork`); at or over the cap we resume\n * nothing and let attrition drain. This is a gate on *starting* work — it never\n * kills a running agent. The load gate and stagger below are secondary safety\n * valves. Remaining candidates are re-evaluated next patrol, so nothing is\n * dropped — only spread out and bounded.\n */\n// Skip the rest of this cycle once 1-minute load exceeds cores * this factor.\nexport const RESUME_LOAD_FACTOR = 1.5;\n// Pause between consecutive resume spawns so the herd is spread across the cycle.\nexport const RESUME_STAGGER_MS = 150;\n// PAN-2500 memory-paced-boot: after a boot-reconciliation resume, wait for the\n// newly-spawned process's RSS to become visible in /proc before re-assessing\n// memory pressure for the next candidate — otherwise the next check reads\n// stale pre-resume MemAvailable and over-admits before the OS catches up.\nexport const RSS_SETTLE_MS = 2000;\n\nfunction shouldRetryUndeliveredKickoff(state: AgentState): boolean {\n return state.role === 'work' && state.kickoffDelivered === false;\n}\n\ninterface HandleAgentStoppedOptions {\n /** When true, the caller is managing global concurrency/load gates. */\n skipGlobalGates?: boolean;\n /** Descriptive source for log messages. */\n context?: string;\n /** Explicit operator resume_all may override a historical manual stop. */\n overrideStoppedByUser?: boolean;\n}\n\n/**\n * PAN-1908: event-driven resume decision for a stopped agent. Called by the\n * reactive scheduler on `agent.stopped` and by the thin safety-net reconcile.\n * Does not enumerate directories — it evaluates the single agent ID it was given.\n */\nexport async function handleAgentStoppedEvent(\n agentId: string,\n opts: HandleAgentStoppedOptions = {},\n deps: AutoResumeNotifierDeps,\n): Promise<string | null> {\n const { skipGlobalGates = false, context = 'event', overrideStoppedByUser = false } = opts;\n const state = getAgentStateSync(agentId);\n if (!state) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — no state`);\n return null;\n }\n if (state.status !== 'stopped') {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — status=${state.status} (not stopped)`);\n return null;\n }\n if (!isAutoResumableRole(state.role)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — role=${state.role} (not auto-resumable)`);\n return null;\n }\n if (state.foreman === true) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — swarm janitor owns foreman liveness`);\n return null;\n }\n // PAN-3479: tombstoned rows keep their session_id for transcript linkage —\n // the phase, not a nulled session, is what makes them non-resumable.\n if (state.phase === RETAINED_TRANSCRIPTS_PHASE) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — retired (transcripts retained)`);\n return null;\n }\n\n if (isTerminalSwarmSlotAgent(state)) {\n if (await Effect.runPromise(sessionExists(agentId))) {\n try {\n await Effect.runPromise(stopAgent(agentId));\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} reaped — assigned swarm item is terminal`);\n } catch (err) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} reap failed — ${err instanceof Error ? err.message : String(err)}`);\n }\n } else {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — assigned swarm item is terminal`);\n }\n return null;\n }\n\n if (getBootReconciliationHeldResumeSet().has(agentId)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — held by boot reconciliation decision`);\n return null;\n }\n\n // PAN-2974 (root cause A): the event path used to resume ungated during the\n // grace window whenever the agent fell OUTSIDE the pending-hold set (e.g.\n // recency-excluded completions — 2026-07-21 MIN-882). While the decision is\n // pending (or hold_all), hold every stopped work agent on this path and log\n // it as a late-arriving candidate for the operator dialog — the batch path\n // (autoResumeStoppedWorkAgents) owns the evaluation, mirroring its\n // getBootReconciliationPendingHoldSet() gate. skipGlobalGates stays\n // authoritative for the batch's own calls.\n const bootDecision = getBootReconciliationState().decision;\n if (!skipGlobalGates && (bootDecision === 'pending' || bootDecision === 'hold_all')) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} held — boot reconciliation ${bootDecision} (late-arriving candidate, evaluated with the batch)`);\n return null;\n }\n\n if (state.paused === true) {\n const pauseKind = isVerifyPausedAgentState(state) ? 'verify-paused' : 'manually-paused';\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — ${pauseKind} (${state.pausedReason ?? 'no reason'})`);\n return null;\n }\n\n if (state.troubled === true) {\n const failureCount = state.consecutiveFailures ?? 0;\n const since = state.firstFailureInRunAt ?? state.troubledAt ?? 'unknown';\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — troubled (${failureCount} consecutive failures since ${since})`);\n return null;\n }\n\n const terminalSkipReason = bootReconciliationSkipReason(state);\n if (terminalSkipReason === 'workspace_missing') {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — workspace missing (${state.workspace || 'undefined'})`);\n return null;\n }\n\n const hasLiveTmuxSession = await Effect.runPromise(sessionExists(agentId));\n if (hasLiveTmuxSession) {\n const previousStatus = state.status;\n markAgentRunningState(state);\n await Effect.runPromise(saveAgentState(state));\n deps.notifyAgentStatusChanged(state, previousStatus, true);\n const msg = `Reconciled ${agentId} (${previousStatus}→running; tmux session alive)`;\n logDeaconEventSync(`handleAgentStoppedEvent: ${msg}`);\n return null;\n }\n\n if (terminalSkipReason !== null) {\n const suffix = terminalSkipReason === 'merged'\n ? 'already merged or merge-ready'\n : 'completed marker exists and review/test passed';\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — ${suffix}`);\n return null;\n }\n\n if (state.lastFailureNextRetryAt !== undefined) {\n const nextRetryMs = Date.parse(state.lastFailureNextRetryAt);\n if (Number.isFinite(nextRetryMs) && nextRetryMs > Date.now()) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — backoff active (next retry at ${state.lastFailureNextRetryAt})`);\n return null;\n }\n }\n\n // Skip if the agent has a completed marker (or processed completion) — unless\n // review or test found issues that need fixing (blocked / failed).\n const completedFile = join(getAgentDir(agentId), 'completed');\n const processedFile = join(getAgentDir(agentId), 'completed.processed');\n const handedOffViaDone = existsSync(completedFile) || existsSync(processedFile);\n let review = getReviewStatusSync(state.issueId);\n if (handedOffViaDone) {\n const needsFix =\n review?.reviewStatus === 'blocked' ||\n review?.reviewStatus === 'failed' ||\n review?.testStatus === 'failed';\n if (needsFix) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} resuming despite completed marker — review/test needs fixing (review=${review?.reviewStatus}, test=${review?.testStatus})`);\n } else {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — pipeline mid-flight (review=${review?.reviewStatus ?? 'none'}, test=${review?.testStatus ?? 'none'})`);\n return null;\n }\n }\n\n // Refresh review status if we haven't loaded it yet.\n review ??= getReviewStatusSync(state.issueId);\n\n if (await isIssueClosed(state.issueId)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — issue ${state.issueId} is closed`);\n return null;\n }\n\n const hasPendingReviewFeedback =\n review?.reviewStatus === 'blocked' ||\n review?.reviewStatus === 'failed' ||\n review?.testStatus === 'failed' ||\n review?.verificationStatus === 'failed';\n\n const deliberatelyStopped = state.stoppedByUser === true;\n if (deliberatelyStopped && overrideStoppedByUser) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} resuming despite stoppedByUser — explicit operator resume_all override`);\n } else if (deliberatelyStopped && !(handedOffViaDone && hasPendingReviewFeedback)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — deliberately stopped by user (stoppedByUser=true)`);\n return null;\n }\n\n if (hasPendingReviewFeedback) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} resuming — review feedback pending (review=${review?.reviewStatus}, test=${review?.testStatus}, verification=${review?.verificationStatus})`);\n } else {\n const runtimeState = getAgentRuntimeStateSync(agentId);\n if (runtimeState?.state === 'idle' && !shouldRetryUndeliveredKickoff(state)) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} skipped — idle (runtime.state=idle, no review feedback)`);\n return null;\n }\n }\n\n // Global gates (skipped when the batch reconcile is driving the loop).\n if (!skipGlobalGates) {\n const concurrencyLimits = getConcurrencyLimits();\n const runningBefore = countRunningAgents();\n const workSlots = workResumeSlotsAvailable(runningBefore, concurrencyLimits);\n if (workSlots <= 0) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} deferred — work concurrency cap reached (running=${runningBefore.work}, max=${concurrencyLimits.maxWorkAgents}, slots=${workSlots})`);\n return null;\n }\n const cores = cpus().length || 1;\n const loadCeiling = cores * RESUME_LOAD_FACTOR;\n const load1 = loadavg()[0];\n if (load1 > loadCeiling) {\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} deferred — load gate tripped (load1=${load1.toFixed(2)} > ${loadCeiling.toFixed(2)})`);\n return null;\n }\n const memVerdict = await assessMemoryPressure();\n if (memVerdict.band !== 'ok') {\n const cpuDetail = memVerdict.loadPerCore == null ? '' : `, load/core=${memVerdict.loadPerCore.toFixed(2)}`;\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} deferred — memory gate (${memVerdict.band}), availMB=${Math.round(memVerdict.availableBytes / 1048576)}${cpuDetail}`);\n return null;\n }\n }\n\n const runtimeStateForLog = getAgentRuntimeStateSync(agentId);\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} candidate — calling resumeAgent (issueId=${state.issueId}, runtime.state=${runtimeStateForLog?.state || 'null'})`);\n try {\n const result = await resumeAgent(agentId, undefined, { startedBy: 'deacon:auto-resume' });\n if (result.success) {\n const resumedState = await Effect.runPromise(getAgentState(agentId));\n if (resumedState) {\n deps.notifyAgentStatusChanged(resumedState, state.status, true);\n }\n const msg = `Auto-resumed ${agentId} (was orphaned by system event)`;\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`handleAgentStoppedEvent: ${msg}`);\n logAgentLifecycleSync(agentId, `resumed by deacon auto-recovery (session restored after system event)`);\n const issueId = state.issueId;\n emitActivityEntrySync({\n source: 'cloister',\n level: 'info',\n message: issueId\n ? `Deacon auto-resumed ${issueId} work agent`\n : `Deacon auto-resumed agent ${agentId}`,\n issueId,\n });\n emitActivityTtsSync({\n utterance: issueId\n ? `Deacon auto resumed ${issueId} work agent`\n : `Deacon auto resumed agent ${agentId}`,\n priority: 1,\n issueId,\n source: 'cloister',\n eventType: 'agent.autoResumed',\n });\n return agentId;\n }\n const msg = `Failed to auto-resume ${agentId}: ${result.error}`;\n if (!orphanFailureRecordedForAutoResume.has(agentId)) {\n const failedState = await Effect.runPromise(recordAgentFailure(agentId, msg));\n if (failedState) {\n deps.notifyAgentStatusChanged(failedState, state.status, false);\n }\n }\n console.warn(`[deacon] ${msg}`);\n logDeaconEventSync(`handleAgentStoppedEvent: ${msg}`);\n logAgentLifecycleSync(agentId, `auto-resume FAILED: ${result.error}`);\n return null;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n if (!orphanFailureRecordedForAutoResume.has(agentId)) {\n const failedState = await Effect.runPromise(recordAgentFailure(agentId, `Auto-resume error for ${agentId}: ${msg}`));\n if (failedState) {\n deps.notifyAgentStatusChanged(failedState, state.status, false);\n }\n }\n console.warn(`[deacon] Auto-resume error for ${agentId}: ${msg}`);\n logDeaconEventSync(`handleAgentStoppedEvent: ${agentId} auto-resume threw: ${msg}`);\n logAgentLifecycleSync(agentId, `auto-resume threw exception: ${msg}`);\n return null;\n }\n}\n\nexport async function autoResumeStoppedWorkAgents(deps: AutoResumeNotifierDeps): Promise<string[]> {\n const resumed: string[] = [];\n // PAN-1665: count spawn attempts (not just successes) — a failed resume still\n // forks a `claude` process, so the budget must bound attempts to curb the herd.\n let resumeAttempts = 0;\n // Free work slots this patrol = max_work_agents − running work agents. Zero when\n // already at/over the cap, in which case we resume nothing (never kill). Computed\n // once: newly-resumed sessions take time to register as tmux-alive, so we count\n // attempts against this fixed budget rather than re-polling mid-loop.\n const concurrencyLimits = getConcurrencyLimits();\n const runningBefore = countRunningAgents();\n const workSlots = workResumeSlotsAvailable(runningBefore, concurrencyLimits);\n const cores = cpus().length || 1;\n const loadCeiling = cores * RESUME_LOAD_FACTOR;\n\n // PAN-2507: resume scheduler-yielded work agents oldest-first BEFORE any other\n // stopped candidate, drawing from the same slot budget and honoring the same\n // per-iteration memory gate. Each resumed agent consumes one work slot.\n const resumedYielded = await resumeYieldedAgents(workSlots);\n if (resumedYielded.length > 0) {\n resumed.push(...resumedYielded);\n resumeAttempts += resumedYielded.length;\n }\n\n // PAN-1908: authoritative registry is the agents table; no directory scan.\n const bootReconciliationHoldSet = getBootReconciliationPendingHoldSet();\n const candidates = listAllAgents()\n .filter((agent) => agent.status === 'stopped' && agent.role === 'work')\n // PAN-3479: retired rows exist only for transcript linkage — never resume.\n .filter((agent) => agent.phase !== RETAINED_TRANSCRIPTS_PHASE)\n .filter((agent) => !bootReconciliationHoldSet.has(agent.id))\n .map((agent) => agent.id);\n\n if (bootReconciliationHoldSet.size > 0) {\n logDeaconEventSync(`autoResumeStoppedWorkAgents: boot reconciliation pending — holding ${bootReconciliationHoldSet.size} candidate(s)`);\n }\n logDeaconEventSync(`autoResumeStoppedWorkAgents started: ${candidates.length} candidate(s) from agents table`);\n\n for (const agentId of candidates) {\n // PAN-1665 concurrency gate: resume only up to the free work slots, and bail\n // when load is high. At/over the cap workSlots is 0 → we resume nothing and let\n // attrition drain (never kill). Deferred candidates are re-evaluated next patrol.\n if (resumeAttempts >= workSlots) {\n logDeaconEventSync(`autoResumeStoppedWorkAgents: work concurrency cap reached (running=${runningBefore.work}, max=${concurrencyLimits.maxWorkAgents}, slots=${workSlots}); deferring remaining candidates to next patrol`);\n break;\n }\n const load1 = loadavg()[0];\n if (load1 > loadCeiling) {\n logDeaconEventSync(`autoResumeStoppedWorkAgents: load gate tripped (load1=${load1.toFixed(2)} > ${loadCeiling.toFixed(2)} = ${cores} cores * ${RESUME_LOAD_FACTOR}); deferring remaining candidates to next patrol`);\n break;\n }\n const memVerdict = await assessMemoryPressure();\n if (memVerdict.band !== 'ok') {\n const cpuDetail = memVerdict.loadPerCore == null ? '' : `, load/core=${memVerdict.loadPerCore.toFixed(2)}`;\n logDeaconEventSync(`autoResumeStoppedWorkAgents: memory gate (${memVerdict.band}), availMB=${Math.round(memVerdict.availableBytes / 1048576)}${cpuDetail}; deferring remaining candidates to next patrol`);\n break;\n }\n // Stagger spawns so the scheduler can absorb each `claude` before the next.\n if (resumeAttempts > 0) {\n await new Promise(r => setTimeout(r, RESUME_STAGGER_MS));\n }\n\n const result = await handleAgentStoppedEvent(agentId, { skipGlobalGates: true, context: 'patrol' }, deps);\n if (result) {\n resumed.push(result);\n resumeAttempts++;\n }\n }\n if (resumed.length > 0) {\n console.log(`[deacon] Auto-resumed ${resumed.length} work agent(s): ${resumed.join(', ')}`);\n logDeaconEventSync(`autoResumeStoppedWorkAgents completed: resumed ${resumed.length} agent(s): ${resumed.join(', ')}`);\n } else {\n logDeaconEventSync(`autoResumeStoppedWorkAgents completed: no agents resumed`);\n }\n orphanFailureRecordedForAutoResume.clear();\n return resumed;\n}\n\n/**\n * PAN-1908: thin safety-net reconcile for dropped lifecycle events. Queries the\n * authoritative agents table (no directory scan) and re-runs the event handlers\n * for any row that is inconsistent with live tmux state or should have resumed.\n * The primary path is reactive (agent.stopped / agent.heartbeat_dead events);\n * this is only a fallback.\n */\nexport async function reconcileAgentLiveness(deps: AutoResumeNotifierDeps): Promise<string[]> {\n const actions: string[] = [];\n const agents = listAllAgents();\n\n const orphanCandidates = agents.filter((agent) => agent.status === 'running' || agent.status === 'starting').map((agent) => agent.id);\n for (const agentId of orphanCandidates) {\n const result = await handleAgentHeartbeatDeadEvent(agentId, 'reconcile', deps);\n actions.push(...result);\n }\n\n // Stopped work agents that may have missed an agent.stopped event.\n const stoppedWorkCandidates = agents\n .filter((agent) => agent.status === 'stopped' && agent.role === 'work')\n .map((agent) => agent.id);\n\n for (const agentId of stoppedWorkCandidates) {\n const resumed = await handleAgentStoppedEvent(agentId, { context: 'reconcile' }, deps);\n if (resumed) actions.push(`Auto-resumed ${agentId} via reconcile`);\n }\n\n if (actions.length > 0) {\n logDeaconEventSync(`reconcileAgentLiveness completed: ${actions.length} action(s)`);\n } else {\n logDeaconEventSync('reconcileAgentLiveness completed: no actions needed');\n }\n return actions;\n}\n","export type BootReconciliationOutcomeReason =\n | 'resumed'\n /**\n * The agent was already running by the time the batch reached it — the\n * reactive stopped-event path beat the batch to it. That is a success, not a\n * skip, and must not be reported as `no-resumable-session` (PAN-3052).\n */\n | 'already-running'\n | 'no-resumable-session'\n | 'deferred-concurrency'\n | 'deferred-load'\n | 'deferred-memory';\n\nexport interface BootReconciliationOutcome {\n id: string;\n issueId: string;\n outcome: 'resumed' | 'skipped';\n reason: BootReconciliationOutcomeReason;\n}\n\nexport type BootReconciliationDecisionSkipBreakdown = {\n workspace_missing: number;\n merged: number;\n completed: number;\n other: number;\n};\n\nexport interface BootReconciliationApplyResult {\n resumed: string[];\n outcomes: BootReconciliationOutcome[];\n skipped: BootReconciliationDecisionSkipBreakdown;\n deferred: number;\n}\n\ninterface BootReconciliationOutcomeAgent {\n id: string;\n issueId: string;\n}\n\nexport function resumedBootReconciliationOutcome(agent: BootReconciliationOutcomeAgent): BootReconciliationOutcome {\n return { id: agent.id, issueId: agent.issueId, outcome: 'resumed', reason: 'resumed' };\n}\n\n/** Already running when the batch reached it — counted as resumed, not skipped. */\nexport function alreadyRunningBootReconciliationOutcome(\n agent: BootReconciliationOutcomeAgent,\n): BootReconciliationOutcome {\n return { id: agent.id, issueId: agent.issueId, outcome: 'resumed', reason: 'already-running' };\n}\n\nexport function skippedBootReconciliationOutcome(\n agent: BootReconciliationOutcomeAgent,\n reason: Exclude<BootReconciliationOutcomeReason, 'resumed'>,\n): BootReconciliationOutcome {\n return { id: agent.id, issueId: agent.issueId, outcome: 'skipped', reason };\n}\n\nexport function skippedBootReconciliationOutcomes(\n agents: ReadonlyArray<BootReconciliationOutcomeAgent>,\n reason: Exclude<BootReconciliationOutcomeReason, 'resumed'>,\n): BootReconciliationOutcome[] {\n return agents.map((agent) => skippedBootReconciliationOutcome(agent, reason));\n}\n","/**\n * Applies the operator's boot-reconciliation decision without changing the\n * auto-resume policy that evaluates each selected agent (PAN-3052).\n */\nimport { cpus, loadavg } from 'os';\nimport { Effect } from 'effect';\nimport {\n alreadyRunningBootReconciliationOutcome,\n resumedBootReconciliationOutcome,\n skippedBootReconciliationOutcome,\n skippedBootReconciliationOutcomes,\n type BootReconciliationApplyResult,\n type BootReconciliationOutcome,\n} from './boot-reconciliation-outcomes.js';\nimport { getConcurrencyLimits, countRunningAgents, workResumeSlotsAvailable } from './concurrency.js';\nimport { assessMemoryPressure } from './memory-governor.js';\nimport { listBootReconciliationCandidates } from './boot-reconciliation.js';\nimport { bootReconciliationSkipReason, type BootReconciliationSkipReason } from './boot-reconciliation-predicates.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { getBootReconciliationState } from '../overdeck/control-settings.js';\nimport { sessionExists } from '../tmux.js';\nimport {\n handleAgentStoppedEvent,\n RESUME_LOAD_FACTOR,\n RESUME_STAGGER_MS,\n RSS_SETTLE_MS,\n type AutoResumeNotifierDeps,\n} from './deacon-auto-resume.js';\n\nexport type { BootReconciliationApplyResult, BootReconciliationOutcome, BootReconciliationOutcomeReason } from './boot-reconciliation-outcomes.js';\n\nexport interface BootReconciliationApplyOptions {\n origin?: 'operator' | 'auto';\n}\n\nconst appliedBootReconciliationDecisions = new Set<string>();\nconst emptyBootReconciliationApplyResult = (): BootReconciliationApplyResult => ({ resumed: [], outcomes: [], skipped: { workspace_missing: 0, merged: 0, completed: 0, other: 0 }, deferred: 0 });\n\nfunction bootReconciliationDecisionKey(): string | null {\n const state = getBootReconciliationState();\n if (!state.decision || state.decision === 'pending') return null;\n return `${state.bootId ?? 'boot'}:${state.decision}:${JSON.stringify(state.perAgent)}`;\n}\n\nexport async function applyBootReconciliationDecision(\n deps: AutoResumeNotifierDeps,\n opts: BootReconciliationApplyOptions = {},\n): Promise<BootReconciliationApplyResult> {\n const origin = opts.origin ?? 'auto';\n // PAN-3547: only the primary may act on a boot decision. A read/UI peer\n // (OVERDECK_DISABLE_DEACON=1) that applies a decision would resume agents\n // against the primary's drain posture — the decision itself is already in\n // shared state and the primary's own machinery applies it.\n if (process.env.OVERDECK_DISABLE_DEACON === '1') {\n logDeaconEventSync('applyBootReconciliationDecision: skipped — peer dashboard does not apply boot decisions (PAN-3547)');\n return emptyBootReconciliationApplyResult();\n }\n const decisionKey = bootReconciliationDecisionKey();\n if (!decisionKey) return emptyBootReconciliationApplyResult();\n if (appliedBootReconciliationDecisions.has(decisionKey)) {\n logDeaconEventSync('applyBootReconciliationDecision: decision already applied');\n return emptyBootReconciliationApplyResult();\n }\n\n const state = getBootReconciliationState();\n if (state.decision === 'hold_all') {\n appliedBootReconciliationDecisions.add(decisionKey);\n logDeaconEventSync('applyBootReconciliationDecision: hold_all — no agents resumed');\n return emptyBootReconciliationApplyResult();\n }\n\n let candidates = listBootReconciliationCandidates();\n if (origin === 'auto') {\n const candidateCount = candidates.length;\n candidates = candidates.filter((agent) => agent.stoppedByUser !== true);\n const filteredCount = candidateCount - candidates.length;\n if (filteredCount > 0) {\n logDeaconEventSync(`applyBootReconciliationDecision: auto origin filtered ${filteredCount} stoppedByUser candidate(s)`);\n }\n }\n if (state.decision === 'per_agent') {\n candidates = candidates.filter((agent) => state.perAgent[agent.issueId] === 'resume');\n }\n\n const resumed: string[] = [];\n const outcomes: BootReconciliationOutcome[] = [];\n const skipped: Record<BootReconciliationSkipReason | 'other', number> = { workspace_missing: 0, merged: 0, completed: 0, other: 0 };\n let deferred = 0;\n let resumeAttempts = 0;\n const concurrencyLimits = getConcurrencyLimits();\n const runningBefore = countRunningAgents();\n const workSlots = workResumeSlotsAvailable(runningBefore, concurrencyLimits);\n const cores = cpus().length || 1;\n const loadCeiling = cores * RESUME_LOAD_FACTOR;\n\n for (let index = 0; index < candidates.length; index++) {\n const agent = candidates[index];\n if (resumeAttempts >= workSlots) {\n logDeaconEventSync(`applyBootReconciliationDecision: work concurrency cap reached (running=${runningBefore.work}, max=${concurrencyLimits.maxWorkAgents}, slots=${workSlots}); deferring remaining candidates`);\n const deferredAgents = candidates.slice(index);\n deferred += deferredAgents.length;\n outcomes.push(...skippedBootReconciliationOutcomes(deferredAgents, 'deferred-concurrency'));\n break;\n }\n const load1 = loadavg()[0];\n if (load1 > loadCeiling) {\n logDeaconEventSync(`applyBootReconciliationDecision: load gate tripped (load1=${load1.toFixed(2)} > ${loadCeiling.toFixed(2)} = ${cores} cores * ${RESUME_LOAD_FACTOR}); deferring remaining candidates`);\n const deferredAgents = candidates.slice(index);\n deferred += deferredAgents.length;\n outcomes.push(...skippedBootReconciliationOutcomes(deferredAgents, 'deferred-load'));\n break;\n }\n const memVerdict = await assessMemoryPressure();\n if (memVerdict.band !== 'ok') {\n const cpuDetail = memVerdict.loadPerCore == null ? '' : `, load/core=${memVerdict.loadPerCore.toFixed(2)}`;\n logDeaconEventSync(`applyBootReconciliationDecision: memory gate (${memVerdict.band}), availMB=${Math.round(memVerdict.availableBytes / 1048576)}${cpuDetail}; deferring remaining candidates`);\n const deferredAgents = candidates.slice(index);\n deferred += deferredAgents.length;\n outcomes.push(...skippedBootReconciliationOutcomes(deferredAgents, 'deferred-memory'));\n break;\n }\n if (resumeAttempts > 0) {\n await new Promise(r => setTimeout(r, RESUME_STAGGER_MS));\n }\n\n const result = await handleAgentStoppedEvent(\n agent.id,\n {\n skipGlobalGates: true,\n context: 'boot-reconciliation',\n ...(origin === 'operator' ? { overrideStoppedByUser: true } : {}),\n },\n deps,\n );\n if (result) {\n resumed.push(result);\n outcomes.push(resumedBootReconciliationOutcome(agent));\n resumeAttempts++;\n // PAN-2500 memory-paced-boot: let RSS settle before the next candidate's\n // memory check (top of the next loop iteration) re-assesses.\n await new Promise(r => setTimeout(r, RSS_SETTLE_MS));\n } else if (await Effect.runPromise(sessionExists(agent.id))) {\n // PAN-3052: already live — the reactive stopped-event path won the race in\n // the seconds between the operator saving the decision and this batch. It\n // is counted in runningBefore, so it takes no slot; reporting it as a skip\n // told the operator an agent was \"not resumable\" while it was running.\n resumed.push(agent.id);\n outcomes.push(alreadyRunningBootReconciliationOutcome(agent));\n } else {\n const skipReason = bootReconciliationSkipReason(agent) ?? 'other';\n skipped[skipReason]++;\n outcomes.push(skippedBootReconciliationOutcome(agent, 'no-resumable-session'));\n }\n }\n\n appliedBootReconciliationDecisions.add(decisionKey);\n logDeaconEventSync(`applyBootReconciliationDecision: decision=${state.decision} resumed ${resumed.length} agent(s)`);\n return { resumed, outcomes, skipped, deferred };\n}\n","/**\n * Agent directory cleanup — identifies and removes orphaned legacy agent directories.\n *\n * Valid directories (preserved):\n * - agent-<issueId> — work agents (always preserved)\n * - planning-<issueId> — planning agents (only preserved while tmux session is running)\n * - conv-* — conversation directories (ALWAYS preserved: ohmypi\n * conversations write their only transcript to\n * conv-<name>/sessions/*.jsonl via --session-dir, and\n * codex conversations keep rollout JSONLs under\n * conv-<name>/codex-home/sessions/. Deleting these\n * destroys conversation history permanently — 2026-07-05\n * incident: a boot-time `pan sync` wiped every ended\n * pi/codex conversation transcript.)\n *\n * Legacy directories (eligible for cleanup when no tmux session is running):\n * - work-<issueId>, review-<issueId>, test-<issueId>, merge-<issueId>\n * - agent-<number>, agent-agent-*, agent-* with uppercase prefix\n * - specialist-*\n * - Any other non-standard name\n */\n\nimport { existsSync, readdirSync } from 'fs';\nimport { access, readdir } from 'node:fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from './paths.js';\nimport { listSessionNames } from './tmux.js';\nimport { parseIssueIdSync } from './issue-id.js';\nimport { FsError } from './errors.js';\nimport { getAgentStateSync } from './agents.js';\nimport { removeAgentStateDir } from './agents/state-dir-removal.js';\n\nexport const CLOSED_ISSUE_AGENT_DIR_GRACE_MS = 7 * 24 * 60 * 60 * 1000;\n\n/**\n * Valid agent directory naming patterns.\n *\n * Standard issue IDs: PREFIX-NUMBER → normalized to lowercase with dash\n * Rally issue IDs: PREFIX_NUMBER → normalized to lowercase, no dash\n *\n * Examples of valid names:\n * agent-pan-801, agent-min-215, agent-f29698\n * planning-pan-801, planning-min-215\n *\n * Examples of legacy names (no longer created):\n * agent-108, agent-MIN-791, agent-agent-pan-699, agent-pan-test-1\n * work-pan-208, review-pan-646, test-pan-646, merge-pan-646\n * conv-20260411-1125, specialist-overdeck-test-agent\n */\n\n/**\n * Check whether a directory name matches a valid work-agent directory pattern.\n *\n * Work-agent directories (agent-<issueId>) are always preserved, and so are\n * strike directories (strike-<issueId>) — strike sessions are registered in\n * the agents table and their state dir holds session.id/mail like any other\n * agent. Reaping them orphans the DB row and drops the strike node from the\n * issue tree.\n * Planning-agent directories are handled separately — they are only valid\n * while their tmux session is running.\n */\nexport function isValidAgentDirectoryName(name: string): boolean {\n const match = name.match(/^(?:agent|strike)-(.+)$/);\n if (!match) return false;\n\n const suffix = match[1]!;\n // Current code always lowercases issue IDs before creating directories\n if (suffix !== suffix.toLowerCase()) return false;\n\n // Direct agent-<issueId> directories (work agents, role orchestrators without suffix)\n if (parseIssueIdSync(suffix) !== null) return true;\n\n // Specialist directories: agent-<issueId>-<role> or agent-<issueId>-<role>-<subRole>\n // e.g. agent-pan-457-review-correctness, agent-pan-457-test, agent-pan-457-ship\n // Also work agents with slots: agent-pan-457-1\n const parts = suffix.split('-');\n for (let i = 2; i <= parts.length; i++) {\n const candidate = parts.slice(0, i).join('-');\n if (parseIssueIdSync(candidate) !== null) {\n const remainder = parts.slice(i).join('-');\n if (remainder && /^[a-z0-9-]+$/.test(remainder)) return true;\n }\n }\n\n return false;\n}\n\n/**\n * Check whether a directory name is a conversation (conv-*) directory.\n *\n * Conversation launcher/state files live in ~/.overdeck/conversations/, but the\n * conv-* directory under ~/.overdeck/agents/ is the canonical transcript home\n * for non-claude harnesses: ohmypi writes sessions/*.jsonl there (--session-dir)\n * and codex keeps rollout JSONLs under codex-home/sessions/. These directories\n * must NEVER be auto-removed — doing so permanently destroys conversation\n * history.\n */\nexport function isConversationDirectory(name: string): boolean {\n return name.startsWith('conv-');\n}\n\n/**\n * Extract the issue ID from a planning-* directory name.\n * Returns null if the name is not a planning directory or the issue ID is invalid.\n */\nexport function getPlanningIssueId(name: string): string | null {\n const match = name.match(/^planning-(.+)$/);\n if (!match) return null;\n\n const issueId = match[1]!;\n if (issueId !== issueId.toLowerCase()) return null;\n if (parseIssueIdSync(issueId) === null) return null;\n\n return issueId;\n}\n\nexport function getAgentDirectoryIssueId(name: string): string | null {\n const match = name.match(/^(?:agent|planning)-(.+)$/);\n if (!match) return null;\n\n const suffix = match[1]!;\n if (suffix !== suffix.toLowerCase()) return null;\n\n const direct = parseIssueIdSync(suffix);\n if (direct) return direct.raw.toUpperCase();\n\n const parts = suffix.split('-');\n for (let i = 1; i <= parts.length; i++) {\n const candidate = parts.slice(0, i).join('-');\n const parsed = parseIssueIdSync(candidate);\n if (parsed) return parsed.raw.toUpperCase();\n }\n\n return null;\n}\n\nexport interface OrphanedAgentDir {\n name: string;\n path: string;\n hasRunningSession: boolean;\n}\n\nasync function findOrphanedAgentDirsPromise(\n agentsDir: string = AGENTS_DIR,\n): Promise<OrphanedAgentDir[]> {\n if (!existsSync(agentsDir)) {\n return [];\n }\n\n const entries = readdirSync(agentsDir, { withFileTypes: true });\n const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);\n\n const sessionNames = await Effect.runPromise(listSessionNames());\n const sessionSet = new Set(sessionNames);\n\n const orphaned: OrphanedAgentDir[] = [];\n for (const name of dirs) {\n // Work-agent directories are always valid\n if (isValidAgentDirectoryName(name)) continue;\n\n // Conversation directories hold the only transcript for ohmypi/codex\n // conversations (sessions/*.jsonl, codex-home/sessions/). Never touch them.\n if (isConversationDirectory(name)) continue;\n\n // Planning directories are valid only while their tmux session is running\n const planningIssueId = getPlanningIssueId(name);\n if (planningIssueId !== null) {\n if (sessionSet.has(name)) continue; // running session — preserve\n // No running session — orphaned\n }\n\n const dirPath = join(agentsDir, name);\n orphaned.push({\n name,\n path: dirPath,\n hasRunningSession: sessionSet.has(name),\n });\n }\n\n return orphaned;\n}\n\nexport interface CleanupResult {\n /** Directories that were removed */\n removed: string[];\n /** Directories skipped because a tmux session is still running */\n protected: string[];\n /** Directories that would be removed (dry run) */\n wouldRemove: string[];\n /** Total orphaned directories found */\n totalOrphaned: number;\n}\n\nasync function cleanupAgentDirectoriesPromise(options: {\n dryRun?: boolean;\n force?: boolean;\n agentsDir?: string;\n} = {}): Promise<CleanupResult> {\n const { dryRun = false, force = false, agentsDir = AGENTS_DIR } = options;\n\n const orphaned = await Effect.runPromise(findOrphanedAgentDirs(agentsDir));\n const protectedDirs = orphaned.filter((d) => d.hasRunningSession);\n const removable = orphaned.filter((d) => !d.hasRunningSession);\n\n const result: CleanupResult = {\n removed: [],\n protected: protectedDirs.map((d) => d.name),\n wouldRemove: [],\n totalOrphaned: orphaned.length,\n };\n\n if (removable.length === 0) {\n return result;\n }\n\n if (dryRun) {\n result.wouldRemove = removable.map((d) => d.name);\n return result;\n }\n\n if (!force) {\n // Prompt for confirmation is handled by the CLI caller; this function\n // focuses on the actual cleanup logic.\n }\n\n for (const dir of removable) {\n try {\n await removeAgentStateDir(dir.path, agentsDir);\n result.removed.push(dir.name);\n } catch {\n // Non-fatal — directory may have already been removed or permissions changed.\n }\n }\n\n return result;\n}\n\ntype IssueReadSourceState = {\n identifier?: unknown;\n id?: unknown;\n status?: unknown;\n state?: unknown;\n canonicalStatus?: unknown;\n rawTrackerState?: unknown;\n completedAt?: unknown;\n closedAt?: unknown;\n};\n\nexport interface ClosedIssueAgentDir {\n name: string;\n path: string;\n issueId: string;\n closedAt: string;\n ageMs: number;\n hasRunningSession: boolean;\n hasStateFile: boolean;\n containsJsonl: boolean;\n}\n\nexport interface ClosedIssueAgentCleanupResult {\n removed: string[];\n protected: string[];\n wouldRemove: string[];\n totalCandidates: number;\n}\n\nfunction normalizeIssueId(value: unknown): string | null {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n if (!trimmed) return null;\n const parsed = parseIssueIdSync(trimmed);\n return (parsed?.raw ?? trimmed).toUpperCase();\n}\n\nfunction isClosedIssueState(issue: IssueReadSourceState): boolean {\n const state = String(issue.state ?? '').toLowerCase();\n const status = String(issue.status ?? '').toLowerCase();\n const canonicalStatus = String(issue.canonicalStatus ?? '').toLowerCase();\n const rawTrackerState = String(issue.rawTrackerState ?? '').toLowerCase();\n return Boolean(\n issue.completedAt ||\n issue.closedAt ||\n state === 'closed' ||\n status === 'done' ||\n status === 'closed' ||\n status === 'cancelled' ||\n status === 'canceled' ||\n status === 'completed' ||\n canonicalStatus === 'done' ||\n canonicalStatus === 'closed' ||\n canonicalStatus === 'cancelled' ||\n canonicalStatus === 'canceled' ||\n canonicalStatus === 'completed' ||\n rawTrackerState === 'closed' ||\n rawTrackerState === 'done' ||\n rawTrackerState === 'completed',\n );\n}\n\nfunction getClosedIssueTimes(issues: unknown[]): Map<string, number> {\n const closed = new Map<string, number>();\n for (const issue of issues) {\n if (!issue || typeof issue !== 'object') continue;\n const item = issue as IssueReadSourceState;\n const issueId = normalizeIssueId(item.identifier) ?? normalizeIssueId(item.id);\n if (!issueId || !isClosedIssueState(item)) continue;\n\n const closedTimestamp = item.completedAt ?? item.closedAt;\n if (typeof closedTimestamp !== 'string') continue;\n\n const closedAtMs = Date.parse(closedTimestamp);\n if (!Number.isFinite(closedAtMs)) continue;\n closed.set(issueId, closedAtMs);\n }\n return closed;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function readAgentStateIssueId(dirPath: string): Promise<string | null> {\n try {\n const agentId = dirPath.split('/').pop() ?? '';\n const issueId = getAgentStateSync(agentId)?.issueId;\n return issueId ? normalizeIssueId(issueId) : null;\n } catch {\n return null;\n }\n}\n\nasync function directoryContainsJsonl(dirPath: string): Promise<boolean> {\n let entries;\n try {\n entries = await readdir(dirPath, { withFileTypes: true });\n } catch {\n return false;\n }\n\n for (const entry of entries) {\n const entryPath = join(dirPath, entry.name);\n if (entry.isFile() && entry.name.endsWith('.jsonl')) return true;\n if (entry.isDirectory() && await directoryContainsJsonl(entryPath)) return true;\n }\n\n return false;\n}\n\nasync function findClosedIssueAgentDirsPromise(options: {\n issues: unknown[];\n agentsDir?: string;\n nowMs?: number;\n graceMs?: number;\n}): Promise<ClosedIssueAgentDir[]> {\n const agentsDir = options.agentsDir ?? AGENTS_DIR;\n const nowMs = options.nowMs ?? Date.now();\n const graceMs = options.graceMs ?? CLOSED_ISSUE_AGENT_DIR_GRACE_MS;\n const closedIssueTimes = getClosedIssueTimes(options.issues);\n if (closedIssueTimes.size === 0 || !await pathExists(agentsDir)) return [];\n\n const sessionNames = await Effect.runPromise(listSessionNames());\n const sessionSet = new Set(sessionNames);\n const entries = await readdir(agentsDir, { withFileTypes: true });\n const candidates: ClosedIssueAgentDir[] = [];\n\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n\n const dirPath = join(agentsDir, entry.name);\n const stateIssueId = await readAgentStateIssueId(dirPath);\n const issueId = stateIssueId ?? getAgentDirectoryIssueId(entry.name);\n if (!issueId) continue;\n\n const closedAtMs = closedIssueTimes.get(issueId);\n if (closedAtMs === undefined) continue;\n\n const ageMs = nowMs - closedAtMs;\n if (ageMs <= graceMs) continue;\n\n candidates.push({\n name: entry.name,\n path: dirPath,\n issueId,\n closedAt: new Date(closedAtMs).toISOString(),\n ageMs,\n hasRunningSession: sessionSet.has(entry.name),\n hasStateFile: getAgentStateSync(entry.name) !== null,\n containsJsonl: await directoryContainsJsonl(dirPath),\n });\n }\n\n return candidates;\n}\n\nasync function cleanupClosedIssueAgentDirectoriesPromise(options: {\n issues: unknown[];\n dryRun?: boolean;\n force?: boolean;\n agentsDir?: string;\n nowMs?: number;\n graceMs?: number;\n}): Promise<ClosedIssueAgentCleanupResult> {\n const agentsDir = options.agentsDir ?? AGENTS_DIR;\n const candidates = await findClosedIssueAgentDirsPromise(options);\n const protectedDirs = candidates.filter((dir) => dir.hasRunningSession || dir.containsJsonl);\n const removable = candidates.filter((dir) => !dir.hasRunningSession && !dir.containsJsonl);\n const result: ClosedIssueAgentCleanupResult = {\n removed: [],\n protected: protectedDirs.map((dir) => dir.name),\n wouldRemove: [],\n totalCandidates: candidates.length,\n };\n\n if (options.dryRun) {\n result.wouldRemove = removable.map((dir) => dir.name);\n return result;\n }\n\n if (!options.force) {\n // Startup owns the non-interactive cleanup path; CLI callers should use dry-run.\n }\n\n for (const dir of removable) {\n try {\n await removeAgentStateDir(dir.path, agentsDir);\n result.removed.push(dir.name);\n } catch {\n // Non-fatal — directory may have already been removed or permissions changed.\n }\n }\n\n return result;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect-native variant of findOrphanedAgentDirs. Fails with FsError if the\n * agents directory listing fails. The tmux listing is wrapped so listing\n * failures bubble up as FsError too (treats tmux as part of the filesystem\n * for purposes of this check).\n */\nexport const findOrphanedAgentDirs = (\n agentsDir: string = AGENTS_DIR,\n): Effect.Effect<readonly OrphanedAgentDir[], FsError> =>\n Effect.tryPromise({\n try: () => findOrphanedAgentDirsPromise(agentsDir),\n catch: (cause) =>\n new FsError({ path: agentsDir, operation: 'findOrphanedAgentDirs', cause }),\n });\n\n/**\n * Effect-native variant of cleanupAgentDirectories. Fails with FsError if the\n * orphan scan fails. Individual removal failures are still swallowed internally\n * so a partial cleanup is the worst case (matches the Promise contract).\n */\nexport const cleanupAgentDirectories = (options: {\n dryRun?: boolean;\n force?: boolean;\n agentsDir?: string;\n} = {}): Effect.Effect<CleanupResult, FsError> =>\n Effect.tryPromise({\n try: () => cleanupAgentDirectoriesPromise(options),\n catch: (cause) =>\n new FsError({\n path: options.agentsDir ?? AGENTS_DIR,\n operation: 'cleanupAgentDirectories',\n cause,\n }),\n });\n\nexport const findClosedIssueAgentDirs = (options: {\n issues: unknown[];\n agentsDir?: string;\n nowMs?: number;\n graceMs?: number;\n}): Effect.Effect<readonly ClosedIssueAgentDir[], FsError> =>\n Effect.tryPromise({\n try: () => findClosedIssueAgentDirsPromise(options),\n catch: (cause) =>\n new FsError({\n path: options.agentsDir ?? AGENTS_DIR,\n operation: 'findClosedIssueAgentDirs',\n cause,\n }),\n });\n\nexport const cleanupClosedIssueAgentDirectories = (options: {\n issues: unknown[];\n dryRun?: boolean;\n force?: boolean;\n agentsDir?: string;\n nowMs?: number;\n graceMs?: number;\n}): Effect.Effect<ClosedIssueAgentCleanupResult, FsError> =>\n Effect.tryPromise({\n try: () => cleanupClosedIssueAgentDirectoriesPromise(options),\n catch: (cause) =>\n new FsError({\n path: options.agentsDir ?? AGENTS_DIR,\n operation: 'cleanupClosedIssueAgentDirectories',\n cause,\n }),\n });\n","import type { Dirent, Stats } from 'node:fs';\nimport { readdir, rm, rmdir, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\n\nimport { isConversationDirectory } from '../agent-directory-cleanup.js';\nimport { RETAINED_TRANSCRIPTS_MARKER } from '../agents/state-dir-removal.js';\nimport { listAllAgentsSync, removeAgentRecordSync } from '../overdeck/agents.js';\nimport { listArchivedConversations, listConversations } from '../overdeck/conversations.js';\nimport { readIssueRecordForWorkspaceSync } from '../pan-dir/record.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport { listSessionNames } from '../tmux.js';\n\ninterface TranscriptRetentionConversation {\n name: string;\n status: 'active' | 'ended';\n archivedAt: string | null;\n}\n\nexport interface TranscriptRetentionAgent {\n id: string;\n issueId: string;\n status: string;\n workspace?: string | null;\n paused?: boolean | null;\n troubled?: boolean | null;\n stoppedByUser?: boolean | null;\n}\n\ntype ReadClosedOutRecord = (\n workspace: string,\n issueId: string,\n) => { pipeline?: { closedOut?: boolean } } | null;\n\nexport function isTranscriptRetentionTerminalAgent(\n agent: TranscriptRetentionAgent,\n readRecord: ReadClosedOutRecord = readIssueRecordForWorkspaceSync,\n): boolean {\n if (agent.status !== 'stopped' || !agent.workspace) return false;\n return readRecord(agent.workspace, agent.issueId)?.pipeline?.closedOut === true;\n}\n\nexport interface TranscriptRetentionDeps {\n readDir(path: string): Promise<Dirent[]>;\n stat(path: string): Promise<Stats>;\n removeFile(path: string): Promise<void>;\n removeDir(path: string): Promise<void>;\n removeAgentRecord(agentId: string): void;\n listSessionNames(): Promise<readonly string[]>;\n listAgents(): readonly TranscriptRetentionAgent[];\n isTerminalAgent(agent: TranscriptRetentionAgent): boolean;\n listConversations(): readonly TranscriptRetentionConversation[];\n listArchivedConversations(): readonly TranscriptRetentionConversation[];\n now(): number;\n log(message: string): void;\n}\n\nexport interface TranscriptRetentionOptions {\n transcriptDays?: number;\n agentsDir?: string;\n deps?: Partial<TranscriptRetentionDeps>;\n}\n\nconst defaultDeps: TranscriptRetentionDeps = {\n readDir: (path) => readdir(path, { withFileTypes: true }),\n stat,\n removeFile: async (path) => { await rm(path, { force: true }); },\n removeDir: rmdir,\n removeAgentRecord: removeAgentRecordSync,\n listSessionNames: () => Effect.runPromise(listSessionNames()),\n listAgents: listAllAgentsSync,\n isTerminalAgent: isTranscriptRetentionTerminalAgent,\n listConversations,\n listArchivedConversations,\n now: () => Date.now(),\n log: (message) => console.log(`[deacon] ${message}`),\n};\n\nfunction hasErrorCode(error: unknown, code: string): boolean {\n return (error as NodeJS.ErrnoException).code === code;\n}\n\nfunction conversationEligibility(deps: TranscriptRetentionDeps): Map<string, boolean> | null {\n try {\n const eligible = new Map<string, boolean>();\n for (const conversation of deps.listConversations()) {\n if (conversation.status === 'active') eligible.set(conversation.name, false);\n else if (!eligible.has(conversation.name)) eligible.set(conversation.name, true);\n }\n for (const conversation of deps.listArchivedConversations()) {\n if (!eligible.has(conversation.name)) eligible.set(conversation.name, true);\n }\n return eligible;\n } catch {\n return null;\n }\n}\n\nfunction agentEligibility(deps: TranscriptRetentionDeps): Map<string, boolean> | null {\n try {\n const eligible = new Map<string, boolean>();\n const terminalByIssue = new Map<string, boolean>();\n for (const agent of deps.listAgents()) {\n if (agent.status !== 'stopped' || !agent.workspace) {\n eligible.set(agent.id, false);\n continue;\n }\n const issueKey = `${agent.workspace}\\0${agent.issueId}`;\n let terminal = terminalByIssue.get(issueKey);\n if (terminal === undefined) {\n terminal = deps.isTerminalAgent(agent);\n terminalByIssue.set(issueKey, terminal);\n }\n eligible.set(agent.id, terminal);\n }\n return eligible;\n } catch {\n return null;\n }\n}\n\nasync function pruneTranscriptFiles(\n dirPath: string,\n cutoffMs: number,\n deps: TranscriptRetentionDeps,\n): Promise<{ deletedFiles: number; prunedDirs: number; remainingTranscripts: number; removedDir: boolean }> {\n let entries: Dirent[];\n try {\n entries = await deps.readDir(dirPath);\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) {\n return { deletedFiles: 0, prunedDirs: 0, remainingTranscripts: 0, removedDir: true };\n }\n throw error;\n }\n\n let deletedFiles = 0;\n let prunedDirs = 0;\n let remainingTranscripts = 0;\n for (const entry of entries) {\n const entryPath = join(dirPath, entry.name);\n if (entry.isDirectory()) {\n const nested = await pruneTranscriptFiles(entryPath, cutoffMs, deps);\n deletedFiles += nested.deletedFiles;\n prunedDirs += nested.prunedDirs;\n remainingTranscripts += nested.remainingTranscripts;\n continue;\n }\n if (!entry.isFile() || !entry.name.endsWith('.jsonl')) continue;\n\n let fileStat: Stats;\n try {\n fileStat = await deps.stat(entryPath);\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) continue;\n throw error;\n }\n if (fileStat.mtimeMs >= cutoffMs) {\n remainingTranscripts++;\n continue;\n }\n\n await deps.removeFile(entryPath);\n deletedFiles++;\n }\n\n let removedDir = false;\n try {\n await deps.removeDir(dirPath);\n prunedDirs++;\n removedDir = true;\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) removedDir = true;\n else if (!hasErrorCode(error, 'ENOTEMPTY') && !hasErrorCode(error, 'EEXIST')) throw error;\n }\n\n return { deletedFiles, prunedDirs, remainingTranscripts, removedDir };\n}\n\n/**\n * Delete explicitly expired transcript artifacts from ended agent state dirs.\n * Unset, non-finite, zero, or negative retention never traverses the filesystem.\n */\nexport async function sweepTranscriptRetention(\n options: TranscriptRetentionOptions,\n): Promise<string[]> {\n const transcriptDays = options.transcriptDays;\n if (transcriptDays === undefined || !Number.isFinite(transcriptDays) || transcriptDays <= 0) {\n return [];\n }\n\n const deps = { ...defaultDeps, ...options.deps };\n let liveSessions: Set<string>;\n try {\n liveSessions = new Set(await deps.listSessionNames());\n } catch {\n const action = 'Transcript retention sweep skipped: tmux liveness census unavailable';\n deps.log(action);\n return [action];\n }\n\n const agentsDir = options.agentsDir ?? AGENTS_DIR;\n let agentEntries: Dirent[];\n try {\n agentEntries = await deps.readDir(agentsDir);\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) agentEntries = [];\n else throw error;\n }\n\n const cutoffMs = deps.now() - transcriptDays * 24 * 60 * 60 * 1000;\n let conversationEligibilityMap: Map<string, boolean> | null = null;\n let conversationEligibilityLoaded = false;\n let agentEligibilityMap: Map<string, boolean> | null = null;\n let agentEligibilityLoaded = false;\n let eligibleDirs = 0;\n let deletedFiles = 0;\n let prunedDirs = 0;\n\n for (const entry of agentEntries) {\n if (!entry.isDirectory() || liveSessions.has(entry.name)) continue;\n\n const conversation = isConversationDirectory(entry.name);\n if (conversation) {\n if (!conversationEligibilityLoaded) {\n conversationEligibilityMap = conversationEligibility(deps);\n conversationEligibilityLoaded = true;\n }\n if (conversationEligibilityMap === null) continue;\n const conversationName = entry.name.slice('conv-'.length);\n if (conversationEligibilityMap.get(conversationName) !== true) continue;\n } else {\n if (!agentEligibilityLoaded) {\n agentEligibilityMap = agentEligibility(deps);\n agentEligibilityLoaded = true;\n }\n if (agentEligibilityMap === null || agentEligibilityMap.get(entry.name) !== true) continue;\n }\n\n eligibleDirs++;\n const agentDir = join(agentsDir, entry.name);\n const result = await pruneTranscriptFiles(agentDir, cutoffMs, deps);\n deletedFiles += result.deletedFiles;\n prunedDirs += result.prunedDirs;\n\n if (!conversation && result.remainingTranscripts === 0) {\n let removedDir = result.removedDir;\n if (!removedDir) {\n await deps.removeFile(join(agentDir, RETAINED_TRANSCRIPTS_MARKER));\n try {\n await deps.removeDir(agentDir);\n prunedDirs++;\n removedDir = true;\n } catch (error) {\n if (hasErrorCode(error, 'ENOENT')) removedDir = true;\n else if (!hasErrorCode(error, 'ENOTEMPTY') && !hasErrorCode(error, 'EEXIST')) throw error;\n }\n }\n if (removedDir) deps.removeAgentRecord(entry.name);\n }\n }\n\n const fileLabel = `transcript file${deletedFiles === 1 ? '' : 's'}`;\n const dirLabel = `ended agent state dir${eligibleDirs === 1 ? '' : 's'}`;\n const prunedLabel = `empty director${prunedDirs === 1 ? 'y' : 'ies'}`;\n const action = `Transcript retention sweep: deleted ${deletedFiles} ${fileLabel} older than ${transcriptDays} day${transcriptDays === 1 ? '' : 's'} from ${eligibleDirs} ${dirLabel}; pruned ${prunedDirs} ${prunedLabel}`;\n deps.log(action);\n return [action];\n}\n","import { Effect } from 'effect';\n\nimport type { ProjectConfig } from '../projects.js';\nimport { reconcileStatePlaneDrift } from '../pan-dir/auto-commit.js';\nimport type { PanIssueRecord } from '../pan-dir/record.js';\nimport { listIssueRecords } from '../pan-dir/record-list.js';\nimport { clearRecordPipelineClosedOut } from '../pan-dir/record-update.js';\nimport { readLiveTrackerIssueState, type LiveTrackerIssueState } from './issue-closed.js';\n\nexport interface StatePlaneReconcileAction {\n message: string;\n level: 'action' | 'warn';\n}\n\nexport function statePlaneReconcileEveryCycles(patrolIntervalMs: number): number {\n return Math.max(1, Math.ceil((60 * 60 * 1000) / patrolIntervalMs));\n}\n\nexport interface StatePlanePatrolDeps {\n listRecords: (project: ProjectConfig) => Promise<PanIssueRecord[]>;\n readTrackerState: (issueId: string) => Promise<LiveTrackerIssueState>;\n clearClosedOut: (\n project: ProjectConfig,\n issueId: string,\n reopenedAt: string,\n ) => Promise<boolean>;\n reconcileDrift: (projectPath: string) => Promise<{ committed: boolean; pushed?: boolean }>;\n now: () => string;\n}\n\nfunction defaultDeps(): StatePlanePatrolDeps {\n return {\n listRecords: listIssueRecords,\n readTrackerState: readLiveTrackerIssueState,\n clearClosedOut: (project, issueId, reopenedAt) => clearRecordPipelineClosedOut(\n project,\n issueId,\n { reopenedAt, autoCommit: false },\n ),\n reconcileDrift: (projectPath) => Effect.runPromise(reconcileStatePlaneDrift(projectPath)),\n now: () => new Date().toISOString(),\n };\n}\n\nexport async function reconcileProjectStatePlanes(\n projects: Array<{ config: ProjectConfig }>,\n deps: StatePlanePatrolDeps = defaultDeps(),\n): Promise<StatePlaneReconcileAction[]> {\n const actions: StatePlaneReconcileAction[] = [];\n const trackerStates = new Map<string, Promise<LiveTrackerIssueState>>();\n\n for (const { config } of projects) {\n if (!config.path) continue;\n const records = await deps.listRecords(config);\n const closedOutRecords = records.filter((record) => record.pipeline.closedOut === true);\n\n for (const record of closedOutRecords) {\n const issueId = record.issueId.toUpperCase();\n try {\n let trackerState = trackerStates.get(issueId);\n if (!trackerState) {\n trackerState = deps.readTrackerState(issueId);\n trackerStates.set(issueId, trackerState);\n }\n if (await trackerState === 'closed') continue;\n\n const reopenedAt = deps.now();\n if (await deps.clearClosedOut(config, issueId, reopenedAt)) {\n actions.push({\n message: `Cleared stale closedOut state for reopened ${issueId} at ${reopenedAt}`,\n level: 'action',\n });\n }\n } catch (error) {\n actions.push({\n message: `Preserved closedOut state for ${issueId}: live tracker reconciliation failed: ${error instanceof Error ? error.message : String(error)}`,\n level: 'warn',\n });\n }\n }\n\n const write = await deps.reconcileDrift(config.path);\n if (!write.committed) continue;\n actions.push({\n message: `Reconciled pending spec/record state for ${config.name ?? config.path}`,\n level: write.pushed === false ? 'warn' : 'action',\n });\n }\n return actions;\n}\n","import type { ProjectConfig } from '../projects.js';\nimport { inspectLegacyStatePaths, type LegacyStatePathInspection } from '../state-home.js';\n\nexport interface StateRecreationWarning {\n level: 'warn' | 'error';\n message: string;\n}\n\ntype LegacyStateInspector = (project: ProjectConfig) => Promise<LegacyStatePathInspection>;\n\nfunction cleanupCommand(projectPath: string, directories: string[]): string {\n const domains = directories.map(path => path.slice(`${projectPath}/.pan/`.length));\n return `rm -rf ${projectPath}/.pan/{${domains.join(',')}}`;\n}\n\nexport function createRecreatedStateWarningReporter(\n inspect: LegacyStateInspector = inspectLegacyStatePaths,\n): (projects: Array<{ config: ProjectConfig }>) => Promise<StateRecreationWarning[]> {\n const reportedProjects = new Set<string>();\n return async (projects) => {\n const warnings: StateRecreationWarning[] = [];\n for (const { config } of projects) {\n if (!config.path || reportedProjects.has(config.path)) continue;\n const inspection = await inspect(config);\n if (\n inspection.postMigrationWrites.length === 0\n && inspection.inertDirectories.length === 0\n && inspection.staleFiles.length === 0\n ) continue;\n\n reportedProjects.add(config.path);\n const details = [\n inspection.postMigrationWrites.length > 0\n ? `Post-migration legacy state writes (stray writer): ${inspection.postMigrationWrites.join(', ')}. Stop the writer and move the data through the state write door.`\n : null,\n inspection.inertDirectories.length > 0\n ? `Inert legacy state directories (not a stray writer): ${inspection.inertDirectories.join(', ')}. After confirming they are empty, remove them with ${cleanupCommand(config.path, inspection.inertDirectories)}.`\n : null,\n inspection.staleFiles.length > 0\n ? `Unmigrated legacy state content (not a stray writer): ${inspection.staleFiles.join(', ')}. Compare it with overdeck-state before deletion.`\n : null,\n ].filter(Boolean).join(' ');\n warnings.push({\n level: inspection.postMigrationWrites.length > 0 ? 'error' : 'warn',\n message: `Migrated checkout legacy state: ${details}`,\n });\n }\n return warnings;\n };\n}\n\nexport const recreatedStateWarnings = createRecreatedStateWarningReporter();\nexport { reconcileProjectStatePlanes, statePlaneReconcileEveryCycles } from './state-plane-patrol.js';\n","import { listProjectsSync } from '../projects.js';\nimport { getMainDivergence, type MainDivergence } from '../state-plane.js';\n\nexport interface ProjectMainDivergence extends MainDivergence {\n projectKey: string;\n projectPath: string;\n checkedAt: string;\n}\n\ninterface MainDivergenceState {\n mainDivergence?: ProjectMainDivergence[];\n}\n\ntype ProjectConfigEntry = ReturnType<typeof listProjectsSync>[number];\n\nexport async function recordMainDivergenceHealth(\n state: MainDivergenceState,\n projects: ProjectConfigEntry[] = listProjectsSync(),\n measure: (repoPath: string) => Promise<MainDivergence> = getMainDivergence,\n): Promise<string[]> {\n const checkedAt = new Date().toISOString();\n const records: ProjectMainDivergence[] = [];\n const warnings: string[] = [];\n\n for (const project of projects) {\n const projectPath = project.config.path;\n if (!projectPath) continue;\n\n let divergence: MainDivergence;\n try {\n divergence = await measure(projectPath);\n } catch {\n divergence = { ahead: 0, behind: 0 };\n }\n const projectKey = project.key ?? project.config.name ?? projectPath;\n const record: ProjectMainDivergence = {\n projectKey,\n projectPath,\n checkedAt,\n ...divergence,\n };\n records.push(record);\n\n if (record.ahead > 1 || record.behind > 0) {\n warnings.push(\n `Main divergence for ${projectKey}: local main ahead ${record.ahead}, behind ${record.behind} relative to origin/main`,\n );\n }\n }\n\n state.mainDivergence = records;\n return warnings;\n}\n","export type RecoveryJanitor = 'agent-state' | 'feedback' | 'orphan-reviewer';\n\nexport function shouldRunRecoveryJanitor(janitor: RecoveryJanitor, patrolCycle: number): boolean {\n return patrolCycle % (janitor === 'feedback' ? 5 : 60) === 0;\n}\n","import { mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'node:fs/promises';\nimport { basename, dirname, join } from 'node:path';\nimport { Effect } from 'effect';\n\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getInternalTokenSync, INTERNAL_TOKEN_HEADER } from '../internal-token.js';\nimport { PENDING_PROMOTION_FILENAME, WORKSPACE_RUNTIME_DIRNAME } from '../pan-dir/types.js';\nimport { findSpecByIssue } from '../pan-dir/specs.js';\nimport { listProjects, type ProjectConfig } from '../projects.js';\nimport { findWorkspaceDraftPlanSync, readPlanSync } from '../xbrief/io.js';\nimport type { XBriefDocument } from '../xbrief/types.js';\nimport { recordDeadEndNeedsYou } from './dead-end-trip.js';\nimport { isIssueClosed } from './issue-closed.js';\n\nconst MARKER_GRACE_MS = 120_000;\nconst MARKERLESS_SPEC_GRACE_MS = 5 * 60_000;\nconst NEEDS_YOU_ATTEMPTS = 5;\n\ntype ProjectEntry = { key: string; config: Pick<ProjectConfig, 'name' | 'path'> };\n\nexport type PendingPromotionMarker = {\n version: '1';\n issueId: string;\n canonicalFilename: string;\n noPrd: boolean;\n autoSpawnRequested: boolean;\n finalizedAt: string;\n lastError: string;\n lastAttemptAt: string;\n patrolAttempts: number;\n};\n\nexport interface PendingPromotionMarkerIo {\n read(path: string, issueId: string): Promise<PendingPromotionMarker | null>;\n write(path: string, marker: PendingPromotionMarker): Promise<void>;\n remove(path: string): Promise<void>;\n}\n\nexport interface PendingPromotionCandidate {\n projectKey: string;\n projectName: string;\n projectPath: string;\n workspacePath: string;\n issueId: string;\n specPath: string;\n markerPath: string;\n marker: PendingPromotionMarker | null;\n canonicalFilename: string;\n noPrd: boolean;\n autoSpawnRequested: boolean;\n finalizedAt: string;\n patrolAttempts: number;\n}\n\nexport interface PendingPromotionReconcilerOptions {\n projects?: ProjectEntry[];\n clock?: () => Date;\n fetch?: typeof fetch;\n dashboardOrigin?: string;\n getInternalToken?: () => string | null;\n findPromotedSpec?: (projectPath: string, issueId: string) => Promise<unknown | null>;\n isClosed?: (issueId: string) => Promise<boolean>;\n recordNeedsYou?: typeof recordDeadEndNeedsYou;\n emitActivity?: typeof emitActivityEntrySync;\n markerIo?: PendingPromotionMarkerIo;\n}\n\nfunction normalizeIssueId(value: unknown): string | null {\n return typeof value === 'string' && /^[A-Za-z]+-\\d+$/.test(value.trim())\n ? value.trim().toUpperCase()\n : null;\n}\n\nfunction issueIdFromWorkspaceName(name: string): string | null {\n const match = name.match(/^feature-([a-z]+-\\d+)$/i);\n return match ? match[1]!.toUpperCase() : null;\n}\n\nfunction parseDate(value: unknown): number | null {\n if (typeof value !== 'string') return null;\n const parsed = Date.parse(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\nfunction parseMarker(value: unknown, expectedIssueId: string): PendingPromotionMarker | null {\n if (!value || typeof value !== 'object') return null;\n const marker = value as Record<string, unknown>;\n const issueId = normalizeIssueId(marker['issueId']);\n if (marker['version'] !== '1' || issueId !== expectedIssueId) return null;\n if (typeof marker['canonicalFilename'] !== 'string' || marker['canonicalFilename'].length === 0) return null;\n if (parseDate(marker['finalizedAt']) === null) return null;\n\n return {\n version: '1',\n issueId,\n canonicalFilename: marker['canonicalFilename'],\n noPrd: marker['noPrd'] === true,\n autoSpawnRequested: marker['autoSpawnRequested'] === true,\n finalizedAt: marker['finalizedAt'] as string,\n lastError: typeof marker['lastError'] === 'string' ? marker['lastError'] : 'complete-planning failed',\n lastAttemptAt: typeof marker['lastAttemptAt'] === 'string' ? marker['lastAttemptAt'] : marker['finalizedAt'] as string,\n patrolAttempts: Number.isInteger(marker['patrolAttempts']) && Number(marker['patrolAttempts']) >= 0\n ? Number(marker['patrolAttempts'])\n : 0,\n };\n}\n\nasync function readMarker(path: string, issueId: string): Promise<PendingPromotionMarker | null> {\n try {\n return parseMarker(JSON.parse(await readFile(path, 'utf-8')), issueId);\n } catch {\n return null;\n }\n}\n\nasync function loadProjects(projects?: ProjectEntry[]): Promise<ProjectEntry[]> {\n if (projects) return projects;\n return Effect.runPromise(listProjects().pipe(Effect.catch(() => Effect.succeed([]))));\n}\n\nasync function defaultFindPromotedSpec(projectPath: string, issueId: string): Promise<unknown | null> {\n return Effect.runPromise(\n findSpecByIssue(projectPath, issueId).pipe(Effect.catch(() => Effect.succeed(null))),\n );\n}\n\nfunction internalDashboardOrigin(): string {\n const port = Number.parseInt(process.env['API_PORT'] ?? process.env['PORT'] ?? '3011', 10);\n return process.env['OVERDECK_INTERNAL_DASHBOARD_URL'] ?? `http://127.0.0.1:${port}`;\n}\n\nfunction logDiagnostic(kind: string, info: Record<string, unknown> = {}): void {\n console.debug(`[pending-promotion-reconciler] ${kind}`, info);\n}\n\nfunction markerPathForWorkspace(workspacePath: string): string {\n return join(workspacePath, WORKSPACE_RUNTIME_DIRNAME, PENDING_PROMOTION_FILENAME);\n}\n\nfunction canonicalFilenameFromSpec(doc: XBriefDocument): string | null {\n const filename = doc.plan.metadata?.canonicalFilename;\n return typeof filename === 'string' && filename.length > 0 ? filename : null;\n}\n\nasync function writeMarker(path: string, marker: PendingPromotionMarker): Promise<void> {\n await mkdir(dirname(path), { recursive: true });\n const tmp = `${path}.tmp`;\n await writeFile(tmp, JSON.stringify(marker, null, 2) + '\\n', 'utf-8');\n await rename(tmp, path);\n}\n\nconst defaultMarkerIo: PendingPromotionMarkerIo = {\n read: readMarker,\n write: writeMarker,\n remove: (path) => rm(path, { force: true }),\n};\n\nexport async function findPendingPromotionCandidates(\n options: PendingPromotionReconcilerOptions = {},\n): Promise<PendingPromotionCandidate[]> {\n const projects = await loadProjects(options.projects);\n const now = (options.clock ?? (() => new Date()))().getTime();\n const findPromotedSpec = options.findPromotedSpec ?? defaultFindPromotedSpec;\n const markerIo = options.markerIo ?? defaultMarkerIo;\n const candidates: PendingPromotionCandidate[] = [];\n\n for (const { key, config } of projects) {\n const workspacesDir = join(config.path, 'workspaces');\n const workspaces = await readdir(workspacesDir, { withFileTypes: true }).catch(() => []);\n for (const workspace of workspaces) {\n if (!workspace.isDirectory() || workspace.name.endsWith('-strike')) continue;\n const issueId = issueIdFromWorkspaceName(workspace.name);\n if (!issueId) continue;\n\n const workspacePath = join(workspacesDir, workspace.name);\n const markerPath = markerPathForWorkspace(workspacePath);\n const marker = await markerIo.read(markerPath, issueId);\n const specPath = findWorkspaceDraftPlanSync(workspacePath);\n if (!specPath) continue;\n\n let spec: XBriefDocument;\n try {\n spec = readPlanSync(specPath);\n } catch {\n logDiagnostic('candidate-skipped', { issueId, reason: 'spec-unreadable' });\n continue;\n }\n if (spec.plan.status !== 'proposed' || normalizeIssueId(spec.plan.id) !== issueId) continue;\n\n if (marker) {\n candidates.push({\n projectKey: key,\n projectName: config.name,\n projectPath: config.path,\n workspacePath,\n issueId,\n specPath,\n markerPath,\n marker,\n canonicalFilename: marker.canonicalFilename,\n noPrd: marker.noPrd,\n autoSpawnRequested: marker.autoSpawnRequested,\n finalizedAt: marker.finalizedAt,\n patrolAttempts: marker.patrolAttempts,\n });\n continue;\n }\n\n if (spec.plan.metadata?.promotionIntent === 'manual') continue;\n const specInfo = await stat(specPath).catch(() => null);\n if (!specInfo || now - specInfo.mtimeMs < MARKERLESS_SPEC_GRACE_MS) continue;\n if (await findPromotedSpec(config.path, issueId)) continue;\n const canonicalFilename = canonicalFilenameFromSpec(spec);\n if (!canonicalFilename) {\n logDiagnostic('candidate-skipped', { issueId, reason: 'canonical-filename-missing' });\n continue;\n }\n\n candidates.push({\n projectKey: key,\n projectName: config.name,\n projectPath: config.path,\n workspacePath,\n issueId,\n specPath,\n markerPath,\n marker: null,\n canonicalFilename,\n noPrd: false,\n autoSpawnRequested: false,\n finalizedAt: specInfo.mtime.toISOString(),\n patrolAttempts: 0,\n });\n }\n }\n\n return candidates;\n}\n\nfunction parseResponseBody(text: string): Record<string, unknown> {\n try {\n const parsed = JSON.parse(text);\n return parsed && typeof parsed === 'object' ? parsed as Record<string, unknown> : {};\n } catch {\n return {};\n }\n}\n\nfunction responseError(status: number, body: Record<string, unknown>, text: string): string {\n if (typeof body['error'] === 'string') return body['error'];\n if (typeof body['message'] === 'string') return body['message'];\n return text.slice(0, 200) || `complete-planning returned HTTP ${status}`;\n}\n\nfunction responseStillPending(status: number, body: Record<string, unknown>): boolean {\n return status === 202 || body['inFlight'] === true || typeof body['skipped'] === 'string';\n}\n\nexport async function reconcilePendingPromotions(\n options: PendingPromotionReconcilerOptions = {},\n): Promise<string[]> {\n const now = (options.clock ?? (() => new Date()))();\n const nowMs = now.getTime();\n const fetchImpl = options.fetch ?? fetch;\n const dashboardOrigin = options.dashboardOrigin ?? internalDashboardOrigin();\n const internalToken = (options.getInternalToken ?? getInternalTokenSync)();\n const findPromotedSpec = options.findPromotedSpec ?? defaultFindPromotedSpec;\n const isClosed = options.isClosed ?? ((issueId: string) => isIssueClosed(issueId));\n const recordNeedsYou = options.recordNeedsYou ?? recordDeadEndNeedsYou;\n const emitActivity = options.emitActivity ?? emitActivityEntrySync;\n const markerIo = options.markerIo ?? defaultMarkerIo;\n const candidates = await findPendingPromotionCandidates({ ...options, clock: () => now, findPromotedSpec, markerIo });\n const actions: string[] = [];\n\n for (const candidate of candidates) {\n if (await isClosed(candidate.issueId)) {\n logDiagnostic('candidate-skipped', { issueId: candidate.issueId, reason: 'closed' });\n continue;\n }\n\n if (await findPromotedSpec(candidate.projectPath, candidate.issueId)) {\n await markerIo.remove(candidate.markerPath);\n actions.push(`Cleared resolved pending-promotion marker for ${candidate.issueId}`);\n continue;\n }\n\n const finalizedAtMs = parseDate(candidate.finalizedAt);\n if (finalizedAtMs === null || nowMs - finalizedAtMs < MARKER_GRACE_MS) {\n logDiagnostic('candidate-skipped', { issueId: candidate.issueId, reason: 'grace-window' });\n continue;\n }\n\n let response: Response | null = null;\n let body: Record<string, unknown> = {};\n let error: string | null = null;\n try {\n response = await fetchImpl(\n new URL(`/api/issues/${encodeURIComponent(candidate.issueId)}/complete-planning`, dashboardOrigin),\n {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n origin: dashboardOrigin,\n ...(internalToken ? { [INTERNAL_TOKEN_HEADER]: internalToken } : {}),\n },\n body: JSON.stringify({\n noPrd: candidate.noPrd,\n startedBy: 'pending-promotion-reconciler',\n }),\n },\n );\n const text = await response.text();\n body = parseResponseBody(text);\n if (responseStillPending(response.status, body)) {\n logDiagnostic('promotion-deferred', { issueId: candidate.issueId, status: response.status, body });\n continue;\n }\n if (response.status !== 200 || !response.ok || body['success'] === false || body['ok'] === false) {\n error = responseError(response.status, body, text);\n }\n } catch (cause) {\n error = cause instanceof Error ? cause.message : String(cause);\n }\n\n if (!error && response?.status === 200) {\n await markerIo.remove(candidate.markerPath);\n const action = `Recovered pending planning promotion for ${candidate.issueId}`;\n actions.push(action);\n emitActivity({\n source: 'cloister',\n level: 'success',\n issueId: candidate.issueId,\n message: `${candidate.issueId} planning promotion recovered automatically`,\n details: JSON.stringify({\n issueId: candidate.issueId,\n workspacePath: candidate.workspacePath,\n reconciler: 'pending-promotion',\n timestamp: now.toISOString(),\n }),\n });\n continue;\n }\n\n const nextAttempts = candidate.patrolAttempts + 1;\n const lastError = error ?? 'complete-planning did not converge';\n await markerIo.write(candidate.markerPath, {\n version: '1',\n issueId: candidate.issueId,\n canonicalFilename: candidate.canonicalFilename,\n noPrd: candidate.noPrd,\n autoSpawnRequested: candidate.autoSpawnRequested,\n finalizedAt: candidate.finalizedAt,\n lastError,\n lastAttemptAt: now.toISOString(),\n patrolAttempts: nextAttempts,\n });\n logDiagnostic('promotion-failed', { issueId: candidate.issueId, patrolAttempts: nextAttempts, error: lastError });\n actions.push(`Pending planning promotion for ${candidate.issueId} failed attempt ${nextAttempts}: ${lastError}`);\n\n if (nextAttempts >= NEEDS_YOU_ATTEMPTS) {\n await recordNeedsYou(\n candidate.issueId,\n 'pending-promotion',\n candidate.finalizedAt,\n `Planning promotion for ${candidate.issueId} has failed ${nextAttempts} patrol attempts. The finalized plan remains unpromoted. Run \\`pan plan done ${candidate.issueId}\\` to retry manually, then inspect the complete-planning error if it still fails.`,\n );\n }\n }\n\n return actions;\n}\n","import { Effect } from 'effect';\n\nexport interface TestStatusGreenCiReviewStatus {\n reviewStatus?: string | null;\n testStatus?: string | null;\n mergeStatus?: string | null;\n prUrl?: string | null;\n}\n\nexport interface TestStatusGreenCiPullRequestState {\n state: 'OPEN' | 'CLOSED';\n merged: boolean;\n headSha: string;\n}\n\nexport interface TestStatusGreenCiCheckRunSummary {\n name: string;\n htmlUrl?: string;\n}\n\nexport interface TestStatusGreenCiCheckRunsState {\n verdict: 'green' | 'pending' | 'red';\n successCount: number;\n successfulRuns: TestStatusGreenCiCheckRunSummary[];\n}\n\nexport interface TestStatusGreenCiDeps {\n isGitHubAppConfigured(): boolean;\n loadReviewStatuses(): Record<string, TestStatusGreenCiReviewStatus>;\n getPullRequestHeadState(\n owner: string,\n repo: string,\n number: number,\n ): Effect.Effect<TestStatusGreenCiPullRequestState, unknown>;\n getCiCheckRunsState(\n owner: string,\n repo: string,\n sha: string,\n ): Effect.Effect<TestStatusGreenCiCheckRunsState, unknown>;\n setReviewStatusSync(issueId: string, update: { testStatus: 'passed'; testNotes: string }): void;\n cooldowns: Map<string, number>;\n cooldownMs: number;\n now(): number;\n log(message: string): void;\n warn(message: string): void;\n}\n\nconst terminalMergeStatuses = new Set(['merged', 'merging', 'queued', 'verifying']);\n\nexport async function reconcileTestStatusFromGreenCiWithDeps(\n deps: TestStatusGreenCiDeps,\n): Promise<string[]> {\n const actions: string[] = [];\n try {\n if (!deps.isGitHubAppConfigured()) return actions;\n\n const statuses = deps.loadReviewStatuses();\n const now = deps.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'passed') continue;\n if (status.testStatus !== 'pending') continue;\n if (terminalMergeStatuses.has(status.mergeStatus || '')) continue;\n if (!status.prUrl) continue;\n\n const cooledUntil = deps.cooldowns.get(issueId);\n if (cooledUntil && now < cooledUntil) continue;\n\n const match = status.prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (!match) continue;\n const [, owner, repo, numberStr] = match;\n const prNumber = parseInt(numberStr, 10);\n if (!Number.isFinite(prNumber)) continue;\n\n try {\n const prState = await Effect.runPromise(deps.getPullRequestHeadState(owner, repo, prNumber));\n if (prState.state !== 'OPEN' || prState.merged || !prState.headSha) continue;\n\n const ciState = await Effect.runPromise(deps.getCiCheckRunsState(owner, repo, prState.headSha));\n if (ciState.verdict !== 'green') {\n deps.cooldowns.set(issueId, now + deps.cooldownMs);\n continue;\n }\n\n const firstRun = ciState.successfulRuns[0];\n const runLabel = firstRun?.htmlUrl\n ? `${firstRun.name} (${firstRun.htmlUrl})`\n : (firstRun?.name || `${ciState.successCount} successful CI check-run(s)`);\n const shortSha = prState.headSha.slice(0, 8);\n deps.setReviewStatusSync(issueId, {\n testStatus: 'passed',\n testNotes: `Reconciled from green GitHub Actions CI on ${shortSha}: ${runLabel}`,\n });\n deps.cooldowns.delete(issueId);\n const msg = `Reconciled testStatus=pending → passed for ${issueId} from green CI on PR #${prNumber} @ ${shortSha}`;\n actions.push(msg);\n deps.log(msg);\n } catch (prErr: any) {\n deps.cooldowns.set(issueId, now + deps.cooldownMs);\n deps.warn(`reconcileTestStatusFromGreenCi: ${issueId} PR/CI lookup failed: ${prErr.message}`);\n }\n }\n } catch (err: any) {\n deps.warn(`Error in reconcileTestStatusFromGreenCi: ${err.message}`);\n }\n return actions;\n}\n","/**\n * Deacon janitor (PAN-1625): reap orphaned `dist/dashboard/server.js` processes.\n *\n * A failed `pan dev` / `pan up` restart can leave a second dashboard server\n * running that already lost the port. It is not harmless — it can keep serving\n * stale data and, worse, run a SECOND Deacon racing the live one (single-deacon\n * invariant; PAN-821/PAN-698).\n *\n * THIS RUNS INSIDE THE LIVE SERVER PROCESS. The Deacon is in-process with\n * `dist/dashboard/server.js`, so `process.pid` IS the live server. The hard\n * safety rule: never kill `process.pid`, never kill the current port owner,\n * never kill a just-spawned server (could be a legitimate restart handoff), and\n * never kill a process living inside a container (a workspace-container server\n * is legitimate — only host-level orphans are reaped). Everything is built on a\n * pure, unit-tested selection function so the kill set is auditable.\n */\nimport { execFile } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { isDeaconGloballyPaused } from '../overdeck/control-settings.js';\nimport { loadConfig } from '../config.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst DEFAULT_MIN_AGE_SECONDS = 120;\nconst REAP_GRACE_MS = 5000;\nconst SERVER_SCRIPT = 'dist/dashboard/server.js';\n\nexport interface DashboardServerProc {\n pid: number;\n /** elapsed seconds since the process started (ps etimes). */\n ageSeconds: number;\n}\n\nexport interface SelectOrphanOptions {\n servers: DashboardServerProc[];\n /** The live server's own pid — NEVER reaped. */\n selfPid: number;\n /** The pid currently listening on the dashboard API port — NEVER reaped. */\n portOwnerPid: number | null;\n /** Don't reap a server younger than this (avoids racing a restart handoff). */\n minAgeSeconds?: number;\n}\n\n/**\n * Pure selection of which dashboard-server pids are safe to reap. Excludes the\n * deacon's own process, the live port owner, and any just-spawned server. Fully\n * deterministic and side-effect-free so the kill set can be unit-tested.\n */\nexport function selectOrphanServerPids(opts: SelectOrphanOptions): number[] {\n const minAge = opts.minAgeSeconds ?? DEFAULT_MIN_AGE_SECONDS;\n return opts.servers\n .filter((s) => s.pid !== opts.selfPid)\n .filter((s) => opts.portOwnerPid == null || s.pid !== opts.portOwnerPid)\n .filter((s) => s.ageSeconds >= minAge)\n .map((s) => s.pid);\n}\n\n/** Parse `ps -eo pid=,etimes=,args=` output into dashboard-server processes.\n * Exported for testing — only true `node … dist/dashboard/server.js`\n * invocations match; shell/grep/ps lines that merely mention the path do not. */\nexport function parseDashboardServerProcs(psStdout: string): DashboardServerProc[] {\n const procs: DashboardServerProc[] = [];\n for (const rawLine of psStdout.split('\\n')) {\n const line = rawLine.trim();\n const m = line.match(/^(\\d+)\\s+(\\d+)\\s+(.*)$/);\n if (!m) continue;\n const [, pidStr, ageStr, args] = m;\n if (!args.includes(SERVER_SCRIPT)) continue;\n // The executable (first token) must be a node binary — excludes\n // `/bin/bash -c '… dist/dashboard/server.js …'`, grep, ps, etc.\n const exe = args.split(/\\s+/)[0] ?? '';\n if (!/(^|\\/)node(js)?$/.test(exe)) continue;\n procs.push({ pid: Number(pidStr), ageSeconds: Number(ageStr) });\n }\n return procs;\n}\n\nasync function listDashboardServers(): Promise<DashboardServerProc[]> {\n try {\n const { stdout } = await execFileAsync('ps', ['-eo', 'pid=,etimes=,args='], {\n encoding: 'utf-8',\n maxBuffer: 8 * 1024 * 1024,\n });\n return parseDashboardServerProcs(stdout);\n } catch {\n return [];\n }\n}\n\n/** Best-effort: which pid listens on the dashboard API port. Returns null when\n * it can't be determined — `selfPid` remains the primary guard. */\nasync function resolvePortOwner(port: number): Promise<number | null> {\n try {\n const { stdout } = await execFileAsync('lsof', ['-ti', `tcp:${port}`, '-sTCP:LISTEN'], { encoding: 'utf-8' });\n const pid = Number(stdout.trim().split('\\n')[0]);\n return Number.isFinite(pid) && pid > 0 ? pid : null;\n } catch {\n return null;\n }\n}\n\n/** True if the pid lives inside a container (docker/containerd/kube). A\n * workspace-container dashboard server is legitimate and must never be reaped\n * by the host deacon. On platforms without /proc (macOS) container processes\n * aren't host-visible, so this is a no-op (returns false). */\nasync function isContainerProcess(pid: number): Promise<boolean> {\n try {\n const cgroup = await readFile(`/proc/${pid}/cgroup`, 'utf-8');\n return /docker|containerd|kubepods|libpod/.test(cgroup);\n } catch {\n return false;\n }\n}\n\nexport interface ReapDeps {\n selfPid?: number;\n apiPort?: number;\n listServers?: () => Promise<DashboardServerProc[]>;\n resolvePortOwner?: (port: number) => Promise<number | null>;\n isContainerProcess?: (pid: number) => Promise<boolean>;\n kill?: (pid: number, signal: NodeJS.Signals | 0) => void;\n minAgeSeconds?: number;\n graceMs?: number;\n}\n\n/**\n * Find and reap orphaned host-level dashboard servers. Returns a list of\n * human-readable action strings (one per reap) for the patrol log. Honors the\n * global deacon pause and emits a `cloister` activity event per reap so it\n * surfaces in the Deacon activity view.\n */\nexport async function reapOrphanedDashboardServers(deps: ReapDeps = {}): Promise<string[]> {\n if (isDeaconGloballyPaused()) return [];\n\n const selfPid = deps.selfPid ?? process.pid;\n const kill = deps.kill ?? ((pid, signal) => process.kill(pid, signal));\n const graceMs = deps.graceMs ?? REAP_GRACE_MS;\n const apiPort = deps.apiPort ?? await Effect.runPromise(\n loadConfig().pipe(\n Effect.map((c) => c.dashboard.api_port ?? 3011),\n Effect.catch(() => Effect.succeed(3011)),\n ),\n );\n\n const listServers = deps.listServers ?? listDashboardServers;\n const portOwner = deps.resolvePortOwner ?? resolvePortOwner;\n const inContainer = deps.isContainerProcess ?? isContainerProcess;\n\n const [servers, portOwnerPid] = await Promise.all([listServers(), portOwner(apiPort)]);\n let candidates = selectOrphanServerPids({\n servers,\n selfPid,\n portOwnerPid,\n minAgeSeconds: deps.minAgeSeconds,\n });\n\n // Drop any candidate that lives inside a container (legitimate workspace server).\n const containerFlags = await Promise.all(candidates.map((pid) => inContainer(pid)));\n candidates = candidates.filter((_, i) => !containerFlags[i]);\n\n if (candidates.length === 0) return [];\n\n // SIGTERM first, then escalate survivors to SIGKILL after a grace window.\n for (const pid of candidates) {\n try { kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n await new Promise((resolve) => setTimeout(resolve, graceMs));\n\n const actions: string[] = [];\n for (const pid of candidates) {\n let alive = false;\n try { kill(pid, 0); alive = true; } catch { alive = false; }\n if (alive) {\n try { kill(pid, 'SIGKILL'); } catch { /* race: gone between check and kill */ }\n }\n const message = `Reaped orphan dashboard server pid ${pid}`;\n logDeaconEventSync(`[orphan-server-reaper] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'warn', message: `Deacon ${message.toLowerCase()}` });\n actions.push(message);\n }\n return actions;\n}\n","/**\n * Deacon janitor (PAN-1706): reap leftover `playwright-mcp` servers and their\n * headless Chromium trees.\n *\n * Agent sessions spawn `npx @playwright/mcp --isolated` for browser UAT and\n * routinely leave the browser open on the dashboard when they finish. Each\n * leftover headless page runs the full React app with all its pollers, so a\n * handful of ghosts multiplies dashboard load several-fold (observed 4×\n * during the PAN-1705 queueing collapse).\n *\n * Two independent rules, both built on a pure selection function:\n *\n * 1. ORPHANED — the mcp server's ancestor chain no longer contains a harness\n * process (claude / pi / codex / pty-supervisor): the owning session is\n * gone, the whole tree (mcp server + browser) is reaped. A minimum age\n * avoids racing a session that is still starting up.\n * 2. STALE BROWSER — the owner is still alive but the browser has been up\n * longer than any legitimate UAT run. Only the Chromium descendants are\n * killed; playwright-mcp launches a fresh browser on the session's next\n * tool call, so a live session loses nothing.\n */\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\nimport { isDeaconGloballyPaused } from '../overdeck/control-settings.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst DEFAULT_ORPHAN_MIN_AGE_SECONDS = 10 * 60;\nconst DEFAULT_STALE_BROWSER_AGE_SECONDS = 2 * 60 * 60;\nconst REAP_GRACE_MS = 5000;\n\n/** A harness process anywhere in the ancestor chain marks the tree as owned. */\nconst HARNESS_ARGS_RE = /(^|[/\\s])(claude|pi|codex)([\\s]|$)|pty-supervisor/;\nconst PLAYWRIGHT_MCP_RE = /playwright-mcp|@playwright\\/mcp/;\nconst BROWSER_ARGS_RE = /(^|\\/)(chrome|chromium|headless_shell)([\\s]|$)|--type=|--headless/;\n\nexport interface ProcEntry {\n pid: number;\n ppid: number;\n /** elapsed seconds since the process started (ps etimes). */\n ageSeconds: number;\n args: string;\n}\n\nexport interface PlaywrightReapTargets {\n /** Whole orphaned trees: mcp server pids + all their descendants. */\n orphanTreePids: number[];\n /** Stale browser processes under still-owned mcp servers. */\n staleBrowserPids: number[];\n}\n\nexport interface SelectPlaywrightReapOptions {\n procs: ProcEntry[];\n orphanMinAgeSeconds?: number;\n staleBrowserAgeSeconds?: number;\n}\n\n/** Parse `ps -eo pid=,ppid=,etimes=,args=` output. Exported for testing. */\nexport function parseProcTable(psStdout: string): ProcEntry[] {\n const procs: ProcEntry[] = [];\n for (const rawLine of psStdout.split('\\n')) {\n const m = rawLine.trim().match(/^(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(.*)$/);\n if (!m) continue;\n procs.push({\n pid: Number(m[1]),\n ppid: Number(m[2]),\n ageSeconds: Number(m[3]),\n args: m[4],\n });\n }\n return procs;\n}\n\nfunction collectDescendants(rootPid: number, childrenByPpid: Map<number, ProcEntry[]>): ProcEntry[] {\n const result: ProcEntry[] = [];\n const queue = [rootPid];\n while (queue.length > 0) {\n const pid = queue.shift()!;\n for (const child of childrenByPpid.get(pid) ?? []) {\n result.push(child);\n queue.push(child.pid);\n }\n }\n return result;\n}\n\n/** Walk ancestors of `pid`; true if any of them looks like a harness process. */\nfunction hasHarnessAncestor(pid: number, byPid: Map<number, ProcEntry>): boolean {\n let current = byPid.get(pid);\n const seen = new Set<number>();\n while (current && current.ppid > 1 && !seen.has(current.pid)) {\n seen.add(current.pid);\n const parent = byPid.get(current.ppid);\n if (!parent) break;\n if (HARNESS_ARGS_RE.test(parent.args)) return true;\n current = parent;\n }\n return false;\n}\n\n/**\n * Pure selection of which pids to reap. Deterministic and side-effect-free so\n * the kill set can be unit-tested.\n */\nexport function selectPlaywrightReapTargets(opts: SelectPlaywrightReapOptions): PlaywrightReapTargets {\n const orphanMinAge = opts.orphanMinAgeSeconds ?? DEFAULT_ORPHAN_MIN_AGE_SECONDS;\n const staleBrowserAge = opts.staleBrowserAgeSeconds ?? DEFAULT_STALE_BROWSER_AGE_SECONDS;\n\n const byPid = new Map(opts.procs.map((p) => [p.pid, p]));\n const childrenByPpid = new Map<number, ProcEntry[]>();\n for (const p of opts.procs) {\n const list = childrenByPpid.get(p.ppid);\n if (list) list.push(p);\n else childrenByPpid.set(p.ppid, [p]);\n }\n\n // mcp \"servers\" are the node processes actually running playwright-mcp —\n // not sh/npm wrappers (those die with the tree anyway) and not browsers.\n const mcpServers = opts.procs.filter(\n (p) => PLAYWRIGHT_MCP_RE.test(p.args) && !BROWSER_ARGS_RE.test(p.args) && /(^|\\/)node(js)?\\s/.test(p.args),\n );\n\n const orphanTreePids = new Set<number>();\n const staleBrowserPids = new Set<number>();\n\n for (const server of mcpServers) {\n const descendants = collectDescendants(server.pid, childrenByPpid);\n if (!hasHarnessAncestor(server.pid, byPid)) {\n if (server.ageSeconds >= orphanMinAge) {\n orphanTreePids.add(server.pid);\n for (const d of descendants) orphanTreePids.add(d.pid);\n }\n continue;\n }\n for (const d of descendants) {\n if (BROWSER_ARGS_RE.test(d.args) && d.ageSeconds >= staleBrowserAge) {\n staleBrowserPids.add(d.pid);\n }\n }\n }\n\n return {\n orphanTreePids: [...orphanTreePids],\n staleBrowserPids: [...staleBrowserPids].filter((pid) => !orphanTreePids.has(pid)),\n };\n}\n\nasync function listProcs(): Promise<ProcEntry[]> {\n try {\n const { stdout } = await execFileAsync('ps', ['-eo', 'pid=,ppid=,etimes=,args='], {\n encoding: 'utf-8',\n maxBuffer: 8 * 1024 * 1024,\n });\n return parseProcTable(stdout);\n } catch {\n return [];\n }\n}\n\nexport interface PlaywrightReapDeps {\n listProcs?: () => Promise<ProcEntry[]>;\n kill?: (pid: number, signal: NodeJS.Signals | 0) => void;\n orphanMinAgeSeconds?: number;\n staleBrowserAgeSeconds?: number;\n graceMs?: number;\n}\n\n/**\n * Find and reap leftover playwright-mcp trees / stale browsers. Returns\n * human-readable action strings for the patrol log. Honors the global deacon\n * pause and emits a `cloister` activity event per reap.\n */\nexport async function reapLeftoverPlaywrightBrowsers(deps: PlaywrightReapDeps = {}): Promise<string[]> {\n if (isDeaconGloballyPaused()) return [];\n\n const kill = deps.kill ?? ((pid, signal) => process.kill(pid, signal));\n const graceMs = deps.graceMs ?? REAP_GRACE_MS;\n const procs = await (deps.listProcs ?? listProcs)();\n const targets = selectPlaywrightReapTargets({\n procs,\n orphanMinAgeSeconds: deps.orphanMinAgeSeconds,\n staleBrowserAgeSeconds: deps.staleBrowserAgeSeconds,\n });\n\n const labelled: Array<{ pid: number; label: string }> = [\n ...targets.orphanTreePids.map((pid) => ({ pid, label: 'orphaned playwright-mcp tree' })),\n ...targets.staleBrowserPids.map((pid) => ({ pid, label: 'stale playwright browser' })),\n ];\n if (labelled.length === 0) return [];\n\n for (const { pid } of labelled) {\n try { kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n await new Promise((resolve) => setTimeout(resolve, graceMs));\n\n const actions: string[] = [];\n for (const { pid, label } of labelled) {\n let alive = false;\n try { kill(pid, 0); alive = true; } catch { alive = false; }\n if (alive) {\n try { kill(pid, 'SIGKILL'); } catch { /* race: gone between check and kill */ }\n }\n const message = `Reaped ${label} pid ${pid}`;\n logDeaconEventSync(`[playwright-reaper] ${message}`);\n emitActivityEntrySync({ source: 'cloister', level: 'warn', message: `Deacon ${message.toLowerCase()}` });\n actions.push(message);\n }\n return actions;\n}\n","/**\n * Strike workspace reaper (PAN-1882, PAN-2359).\n *\n * Strikes bypass the normal pipeline — and its close-out workspace teardown —\n * so their `feature-<id>-strike` git worktrees + `strike/<id>` branches pile up\n * forever after the strike merges (27 / ~16GB observed 2026-06-14). The strike\n * role ends at \"report the result\" with no cleanup, and a strike can't easily\n * remove the worktree it is running in. This deacon reaper does it: each patrol\n * it removes a `strike/<id>` worktree when its branch is fully merged into\n * `origin/main` AND no live `strike-<id>` session exists.\n *\n * Safety: only `strike/*` worktrees are eligible (never `feature/*`, the active\n * pipeline); a worktree is reaped only when its branch AUTHORED commits that are\n * now all in origin/main (walk-reflogs > 1 AND 0 commits ahead) and no live\n * strike session is using it. A freshly-created strike branch sits at\n * origin/main's HEAD with a single reflog entry, so the extra reflog guard\n * prevents reaping a workspace before the agent has committed any work.\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { sessionExistsSync } from '../tmux.js';\n\nconst execAsync = promisify(exec);\n\ninterface WorktreeRecord {\n path: string;\n branch: string;\n}\n\n/** Parse `git worktree list --porcelain` into {path, branch} records. */\nfunction parseWorktreePorcelain(porcelain: string): WorktreeRecord[] {\n const records: WorktreeRecord[] = [];\n let currentPath = '';\n for (const line of porcelain.split('\\n')) {\n if (line.startsWith('worktree ')) {\n currentPath = line.slice('worktree '.length).trim();\n } else if (line.startsWith('branch ')) {\n const branch = line.slice('branch '.length).trim().replace(/^refs\\/heads\\//, '');\n if (currentPath) records.push({ path: currentPath, branch });\n currentPath = '';\n } else if (line.trim() === '') {\n currentPath = '';\n }\n }\n return records;\n}\n\nexport async function reapMergedStrikeWorkspaces(projectRoot: string = process.cwd()): Promise<string[]> {\n const actions: string[] = [];\n\n let porcelain: string;\n try {\n const { stdout } = await execAsync('git worktree list --porcelain', { cwd: projectRoot, encoding: 'utf-8' });\n porcelain = stdout;\n } catch {\n return actions; // not a git repo / no worktrees — nothing to do\n }\n\n for (const { path, branch } of parseWorktreePorcelain(porcelain)) {\n if (!branch.startsWith('strike/')) continue; // strike worktrees only — never feature/*\n const issueId = branch.slice('strike/'.length); // e.g. \"pan-1864\"\n if (!issueId) continue;\n\n // A live strike is using this worktree — leave it alone.\n if (sessionExistsSync(`strike-${issueId}`)) continue;\n\n // Reap only when the branch is fully merged (0 commits ahead of origin/main),\n // so unmerged strike work is never lost.\n let ahead: string;\n try {\n const { stdout } = await execAsync(\n `git rev-list --count origin/main..${branch}`,\n { cwd: projectRoot, encoding: 'utf-8' },\n );\n ahead = stdout.trim();\n } catch {\n continue; // can't determine merge state — leave it for a future patrol\n }\n if (ahead !== '0') continue; // has unmerged commits — never reap\n\n // A fresh strike branch is created at origin/main's HEAD and is also\n // 0 commits ahead, but it has not authored any commits yet. Only reap\n // branches that actually authored commits and are now fully merged.\n let reflogCount: number;\n try {\n const { stdout } = await execAsync(\n `git rev-list --walk-reflogs --count ${JSON.stringify(branch)}`,\n { cwd: projectRoot, encoding: 'utf-8' },\n );\n reflogCount = Number(stdout.trim());\n if (Number.isNaN(reflogCount)) continue;\n } catch {\n continue; // can't determine authorship — leave it for a future patrol\n }\n if (reflogCount <= 1) continue; // never-committed branch (fresh strike) — don't reap\n\n try {\n await execAsync(`git worktree remove ${JSON.stringify(path)} --force`, { cwd: projectRoot });\n // Branch is an ancestor of origin/main (verified above) — safe to delete.\n await execAsync(`git branch -D ${JSON.stringify(branch)}`, { cwd: projectRoot }).catch(() => {});\n const action = `Reaped merged strike workspace ${path} (branch ${branch})`;\n actions.push(action);\n console.log(`[deacon] ${action}`);\n emitActivityEntrySync({ source: 'cloister', level: 'info', message: `[deacon] ${action}` });\n } catch {\n // worktree busy / already gone — retry next patrol\n }\n }\n\n return actions;\n}\n","import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from '../paths.js';\nimport { isPaneDead, killSession, listSessionNames } from '../tmux.js';\nimport { logDeaconEventSync } from '../persistent-logger.js';\n\nfunction describeReason(hasStateFile: boolean, paneDead: boolean): string {\n if (!hasStateFile && paneDead) return 'missing state.json and pane is dead';\n if (!hasStateFile) return 'missing state.json';\n return 'pane is dead';\n}\n\n/**\n * PAN-1559: inspect sessions are sub-role tmux sessions named `inspect-*`.\n * Any live inspect pane without agent state is untracked compute, and a dead\n * inspect pane is a zombie. Kill either shape so Deacon does not rely on the\n * stale agent-state janitor to eventually notice the directory.\n */\nexport async function cleanupOrphanedInspectSessions(): Promise<string[]> {\n const actions: string[] = [];\n let inspectSessions: readonly string[];\n\n try {\n inspectSessions = (await Effect.runPromise(listSessionNames()))\n .filter(session => session.startsWith('inspect-'));\n } catch {\n return actions;\n }\n\n for (const session of inspectSessions) {\n const statePath = join(AGENTS_DIR, session, 'state.json');\n const hasStateFile = existsSync(statePath);\n const paneDead = await Effect.runPromise(isPaneDead(session)).catch(() => false);\n\n if (hasStateFile && !paneDead) continue;\n\n try {\n await Effect.runPromise(killSession(session)).catch(() => {});\n } catch {\n // best-effort; keep the patrol moving\n }\n\n const reason = describeReason(hasStateFile, paneDead);\n const msg = `Killed orphaned inspect session ${session} (${reason})`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`cleanupOrphanedInspectSessions: ${msg}`);\n }\n\n if (actions.length > 0) {\n logDeaconEventSync(`cleanupOrphanedInspectSessions completed: killed ${actions.length} session(s)`);\n } else {\n logDeaconEventSync('cleanupOrphanedInspectSessions completed: no orphaned sessions found');\n }\n\n return actions;\n}\n","/**\n * PAN-3092: sweep stranded workspace verdict fallbacks.\n *\n * The fallback drain is scheduled by the process that wrote the fallback, on\n * unref'd timers. In a short-lived CLI (`pan admin specialists done` exits in\n * under a second) those timers never fire, so a verdict that failed its journal\n * write under lock contention can sit in `<workspace>/.overdeck/pipeline-verdict.json`\n * with nothing left alive to fold it. This patrol is the external retry the CLI\n * cannot provide.\n *\n * It also surfaces the case the retry cannot fix: a fallback still undrained ten\n * minutes on means the record lock has been held that long, and the operator\n * should hear about it once — MIN-902 spent an hour in exactly that state while\n * every surface reported the reviewer healthy.\n */\n\nimport { loadReviewStatuses } from '../review-status.js';\nimport {\n drainWorkspaceVerdictFallback,\n findWorkspaceVerdictConflicts,\n readWorkspaceVerdictFallback,\n} from '../overdeck/review-status-record-sync.js';\nimport type { VerdictConflict } from '../pan-dir/pipeline-verdict-merge.js';\nimport { resolveProjectFromIssueSync, getProjectSync } from '../projects.js';\nimport { readOwner, recordLockPath } from '../pan-dir/fs-lock.js';\nimport { findWorkspacePath } from '../lifecycle/archive-planning.js';\nimport { findRecoveryTrip, recordRecoveryFailure } from './recovery-trip.js';\nimport { emitActivityEntryOnce, type ActivityEmitOutcome } from '../activity-logger.js';\n\n/** How long a fallback may stay undrained before the operator hears about it. */\nexport const VERDICT_CONTENTION_SURFACE_MS = 10 * 60 * 1000;\n\n/**\n * Total time this sweep may spend waiting on warning delivery, across ALL issues.\n *\n * Bounding each request alone is not enough: the sweep visits issues serially,\n * so N stranded fallbacks against a stalled dashboard would cost N × the\n * per-request deadline — six would consume a whole 60s patrol interval and dozens\n * would hold it for many minutes, starving every later issue and later patrol\n * phase while the patrol heartbeat still reported healthy. The systemic\n * degradation that strands several verdicts at once is exactly the state that\n * makes every loopback request hit its deadline, so the budget is shared.\n */\nexport const SWEEP_WARNING_BUDGET_MS = 30 * 1000;\n\n/**\n * Resolve `attempt`, or give up at `deadlineMs` and report `failed`.\n *\n * Abandoning a warning is always safe: nothing is marked warned on `failed`, and\n * a later retry carries the same idempotency key, so a call that lands after we\n * stopped waiting returns `duplicate` rather than emitting a second warning.\n */\nasync function withWarningDeadline(\n attempt: Promise<ActivityEmitOutcome>,\n deadlineMs: number,\n): Promise<ActivityEmitOutcome> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n return await Promise.race([\n attempt,\n new Promise<ActivityEmitOutcome>((resolve) => {\n timer = setTimeout(() => { resolve('failed'); }, deadlineMs);\n }),\n ]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n}\n\n/**\n * Episodes already warned about in THIS process, keyed by recovery path + issue\n * + the fallback's own `updatedAt`.\n *\n * Only an optimisation, never the dedupe of record: it saves re-querying the\n * event log every patrol. Correctness across restarts comes from the two\n * durable planes — the open recovery trip, and `emitActivityEntryOnce`'s\n * event-log check on the episode id. An episode is added here only after the\n * warning is confirmed durable, so a failed append is retried rather than\n * suppressed, and entries are dropped once the trip lands.\n */\nconst warnedEpisodes = new Set<string>();\n\n/**\n * Describe a verdict conflict and the one action that reliably clears it.\n *\n * Deliberately does NOT hand out a `pan admin specialists done` command. That\n * command is not a general conflict resolver: `merge` takes passed|failed and\n * maps them to merged|failed (so the displayed `merged` is rejected), `inspect`\n * requires an `--item` this pipeline-level conflict cannot identify, `review`\n * does not accept `skipped`, verification has no specialist at all, and\n * re-signalling the value already in the journal writes no replacement fallback\n * — so the conflicting one survives and every later drain withholds it again.\n * A conflict can also span several gates, which one command cannot settle.\n *\n * A strictly newer review cycle is the only instruction correct for every gate\n * and every choice, so that is what the operator is told, along with what it\n * costs. A dedicated resolve-this-generation operation through the verdict write\n * door would let an operator adopt a specific written verdict instead; until\n * that exists, promising it here would be a lie.\n */\nfunction conflictMessage(issueId: string, conflicts: VerdictConflict[], ageMinutes: number): string {\n const detail = conflicts\n .map((c) => `${c.gate} (the record holds \"${c.journalValue}\", the workspace fallback holds \"${c.fallbackValue}\")`)\n .join('; ');\n const gateCount = conflicts.length === 1 ? 'one gate' : `${conflicts.length} gates`;\n return (\n `${issueId} — two different verdicts have been written for ${gateCount} and Overdeck cannot tell which is `\n + `newer, so it refuses to choose: ${detail}. This has been unresolved for ${ageMinutes}min and will NOT `\n + `clear on its own: the fold is withheld on every patrol, by design, so that neither written verdict is `\n + `destroyed. An operator must decide. The one action that reliably clears it for every gate is to dispatch `\n + `a strictly newer review cycle for ${issueId} — that supersedes the stranded fallback and re-runs the `\n + `gates, rather than adopting either of the verdicts already written. Overdeck has no command today that `\n + `adopts one specific written verdict, so do not expect a re-signal of an existing value to clear this.`\n );\n}\n\nasync function lockOwnerDescription(issueId: string): Promise<string> {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return 'unknown writer';\n const project = getProjectSync(resolved.projectKey);\n if (!project) return 'unknown writer';\n return (await readOwner(recordLockPath(project, issueId))).description;\n } catch {\n return 'unknown writer';\n }\n}\n\n/**\n * Drain every pending workspace verdict fallback, and surface the ones that stay\n * stuck. Returns one action line per drain or escalation, for the patrol log.\n */\ninterface WarningCandidate {\n issueId: string;\n workspacePath: string;\n recoveryPath: string;\n generation: string;\n episode: string;\n message: string;\n summary: string;\n}\n\n/**\n * Drain every pending workspace verdict fallback, and surface the ones that stay\n * stuck. Returns one action line per drain or escalation, for the patrol log.\n *\n * `budgetMs` bounds the total time spent waiting on warning delivery. The deacon\n * derives it from the resolved patrol interval so a shortened interval cannot be\n * overrun by the sweep.\n */\nexport async function sweepStrandedVerdictFallbacks(\n now = Date.now(),\n budgetMs = SWEEP_WARNING_BUDGET_MS,\n): Promise<string[]> {\n const actions: string[] = [];\n let statuses: Record<string, { mergeStatus?: string; closedOut?: boolean; stuck?: boolean; deaconIgnored?: boolean }>;\n try {\n statuses = loadReviewStatuses();\n } catch {\n return actions;\n }\n\n // ── Phase 1: drain everything, and collect who needs a warning ──────────────\n const candidates: WarningCandidate[] = [];\n\n for (const [issueId, status] of Object.entries(statuses)) {\n // A merged or closed-out issue's fallback is history, not a pending verdict.\n if (status.mergeStatus === 'merged' || status.closedOut) continue;\n\n try {\n const fallback = await readWorkspaceVerdictFallback(issueId);\n if (!fallback) continue;\n\n if (await drainWorkspaceVerdictFallback(issueId)) {\n actions.push(`Drained stranded verdict fallback for ${issueId}`);\n continue;\n }\n\n const ageMs = now - Date.parse(fallback.updatedAt);\n if (!Number.isFinite(ageMs) || ageMs < VERDICT_CONTENTION_SURFACE_MS) continue;\n // A stuck or deacon-ignored issue is already an operator's problem; drain\n // it, but do not add a second voice on top of the one they already have.\n if (status.stuck || status.deaconIgnored) continue;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n const workspacePath = resolved\n ? findWorkspacePath(resolved.projectPath, issueId.toLowerCase())\n : null;\n if (!workspacePath) continue;\n\n const ageMinutes = Math.round(ageMs / 60_000);\n // Two different failures land here. Contention clears on its own; a\n // verdict conflict never does, because every later drain withholds the\n // same fold. Telling an operator to wait for the second one would be\n // false remediation, so name which it is before saying anything.\n const conflicts = await findWorkspaceVerdictConflicts(issueId, fallback);\n const conflicted = conflicts.length > 0;\n const recoveryPath = conflicted ? 'verdict-fallback-conflict' : 'verdict-fallback-contention';\n const episode = `${recoveryPath}:${issueId}:${fallback.updatedAt}`;\n\n let message: string;\n let summary: string;\n if (conflicted) {\n message = conflictMessage(issueId, conflicts, ageMinutes);\n summary = `${issueId}: verdict conflict on ${conflicts.map((c) => c.gate).join(', ')} — needs operator adjudication`;\n } else {\n const owner = await lockOwnerDescription(issueId);\n message =\n `${issueId} — verdict fallback undrained for ${ageMinutes}min; the per-issue record lock is `\n + `contended (current owner: ${owner}). The verdict is durable in the workspace fallback and `\n + `folds automatically once the lock frees.`;\n summary = `${issueId}: verdict contended ${ageMinutes}min (lock owner: ${owner})`;\n }\n\n const openTrip = await findRecoveryTrip(issueId, recoveryPath, fallback.updatedAt);\n if (openTrip?.open === true) {\n // Already durably surfaced. Do NOT call recordRecoveryFailure: its\n // mutator returns the record unchanged for an open trip, but the write\n // door still takes the per-issue lock and runs the commit/flush path.\n // A retained verdict conflict is permanent by design, so that would be\n // one no-op record write per patrol forever — manufacturing contention\n // on the very lock family this issue exists to relieve.\n warnedEpisodes.delete(episode);\n continue;\n }\n\n candidates.push({\n issueId, workspacePath, recoveryPath, generation: fallback.updatedAt, episode, message, summary,\n });\n } catch {\n // A single unreadable workspace must never stop the sweep.\n }\n }\n\n // ── Phase 2: warn, all candidates concurrently under ONE deadline ───────────\n //\n // Concurrent rather than serial, and started together rather than in turn.\n // Spending a shared budget in issue order let the first hanging episode\n // consume all of it every patrol — iteration order is stable, so the same\n // episode won recursively and every later one was silently dropped without\n // ever attempting its warning. That is the exact hard case this feature\n // targets (a stalled dashboard plus a contended lock), so the unfairness bit\n // hardest when it mattered most. Fanning out gives every episode an attempt\n // and keeps total wall-clock at one budget regardless of how many stranded.\n // Each request is idempotent by episode key, so concurrency cannot duplicate\n // a warning, and an attempt abandoned at the deadline is retried next patrol.\n const pending = candidates.filter((c) => !warnedEpisodes.has(c.episode));\n if (pending.length > 0) {\n const outcomes = await Promise.all(pending.map((candidate) =>\n withWarningDeadline(\n emitActivityEntryOnce({\n id: `verdict-fallback:${candidate.episode}`,\n source: 'cloister',\n level: 'warn',\n issueId: candidate.issueId,\n message: candidate.message,\n }),\n budgetMs,\n ).catch((): ActivityEmitOutcome => 'failed')));\n\n outcomes.forEach((outcome, index) => {\n const candidate = pending[index]!;\n if (outcome === 'appended' || outcome === 'duplicate') {\n warnedEpisodes.add(candidate.episode);\n if (outcome === 'appended') actions.push(candidate.summary);\n } else {\n // `failed` wrote nothing; `unconfirmed` means the wired store offers no\n // settled path, so delivery is unknown. Neither is evidence the operator\n // was told, and marking the episode warned would suppress the retry.\n actions.push(`${candidate.issueId}: verdict-fallback warning could not be confirmed — retrying next patrol`);\n }\n });\n }\n\n // ── Phase 3: the durable needs-you trips ───────────────────────────────────\n //\n // After the warnings, never before: these go through the very record lock\n // whose contention is being reported, so a slow or failing trip write must\n // not delay or suppress the operator-visible warning.\n for (const candidate of candidates) {\n try {\n const { emitNeedsYou } = await recordRecoveryFailure(\n candidate.workspacePath, candidate.issueId, candidate.recoveryPath, candidate.generation, 1,\n );\n // The trip landed, so the durable plane owns dedupe from here — drop the\n // in-process key regardless of `emitNeedsYou`, which controls whether this\n // was the notifying trip, not whether the write succeeded.\n warnedEpisodes.delete(candidate.episode);\n if (emitNeedsYou) actions.push(`needs-you ${candidate.summary}`);\n } catch {\n actions.push(\n `${candidate.issueId}: ${candidate.recoveryPath} needs-you trip could not be recorded (record lock contended) — retrying next patrol`,\n );\n }\n }\n\n return actions;\n}\n","/**\n * Cloister Deacon - Health Monitor for Specialist Agents\n *\n * The Deacon is a health-check system that:\n * - Actively pings specialists to verify they're responsive\n * - Tracks consecutive failures per specialist\n * - Force-kills stuck specialists after threshold failures\n * - Enforces cooldown periods after force-kills\n * - Detects mass death events (infrastructure issues)\n *\n * Inspired by gastown's deacon pattern.\n */\n\nimport { appendFileSync, readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync, rmSync } from 'fs';\n// PAN-1249: readFile / writeFile / unlink are consumed by the additive Effect\n// helpers below (`readFileOp`, `writeFileOp`, `unlinkPath`).\nimport { readdir, readFile, writeFile, unlink } from 'fs/promises';\nimport { dirname, join } from 'path';\nimport { exec, execFile } from 'child_process';\nimport { promisify } from 'util';\nimport { homedir, loadavg, cpus } from 'os';\nimport { Effect } from 'effect';\nimport {\n FsError,\n GitError,\n ProcessSpawnError,\n ProcessTimeoutError,\n} from '../errors.js';\nimport { pidsWithCwdUnder } from '../process-cwd.js';\nimport { isStartingWithinGrace } from './agent-grace.js';\nimport { recordDeaconNudge } from './deacon-nudge-log.js';\nimport { checkInspectAgentTimeouts } from './deacon-inspect.js';\nimport { checkApiErrorAgents } from './deacon-api-recovery.js';\nimport { checkPostReviewCommits } from './deacon-post-review-commits.js';\nimport { patrolStrikeLandings } from './deacon-strike-landing.js';\nimport { checkOrphanedReviewStatuses, recoverStalledReviewConvoys, checkMissingReviewStatuses, checkStuckReviewing, checkCompletedButUnsignaledReviews, reconcileUnappliedReviewVerdicts, monitorReviewConvoySignals, cleanupOrphanedReviewSessions, checkStalledReviewParents } from './deacon-review.js';\nimport { getAutoCloseOutCanonicalState } from './deacon-canonical-state.js';\nimport { checkReadyForMergeStuck as checkReadyForMergeStuckWithDeps, reconcileStaleMergeStatus, reconcileStuckMergingStates, reconcileFalseMerged, reconcileClosedPrReadyForMerge, reconcileAutoMergeRows, reconcileStaleMergeBlockers, reconcileStuckReadyForMerge, reconcileMergedButReviewing, checkFailedMergeRetry, autoCloseOut, checkFirstCompletionAgents, ciRetryMap, FAILED_MERGE_MAX_RETRIES } from './deacon-merge.js';\nimport { reconcileTraefikNetworks } from '../workspace/traefik-connect.js';\nimport { swarmJanitorPass } from './deacon-swarm.js';\nimport { recoverOrphanedAgents as recoverOrphanedAgentsWithDeps, handleAgentHeartbeatDeadEvent as handleAgentHeartbeatDeadEventWithDeps, handleAgentStoppedEvent as handleAgentStoppedEventWithDeps, autoResumeStoppedWorkAgents as autoResumeStoppedWorkAgentsWithDeps, reconcileAgentLiveness as reconcileAgentLivenessWithDeps, nudgeStalledResumeWorkAgents, redeliverUndeliveredKickoffs, nudgeIdleWorkAgentsWithOpenBeads, cleanupOrphanedPlanningSessions as cleanupOrphanedPlanningSessionsWithDeps } from './deacon-auto-resume.js';\nimport { applyBootReconciliationDecision as applyBootReconciliationDecisionWithDeps, type BootReconciliationApplyOptions, type BootReconciliationApplyResult } from './boot-reconciliation-apply.js';\nimport { listFeatureWorkspaces } from './deacon-workspaces.js';\nimport { isConversationDirectory } from '../agent-directory-cleanup.js';\nimport { removeAgentStateDir } from '../agents/state-dir-removal.js';\nimport { sweepTranscriptRetention } from './transcript-retention.js';\nimport { createInFlightGuard } from './in-flight-guard.js';\nimport { listAllAgentsSync as listAllAgents } from '../overdeck/agents.js';\nimport { isContextOverflowTail } from '../context-overflow.js';\nimport { REVIEW_SUB_ROLES, type ReviewSubRole } from './review-monitor.js';\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n// ---------------------------------------------------------------------------\n// PAN-1249 Effect helpers (additive — internal use only)\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap an `execAsync` call in Effect with a typed `ProcessSpawnError`. Used\n * internally so callers can keep their Promise-returning signatures while the\n * implementation drifts towards Effect.\n */\nconst execCommand = (\n command: string,\n options?: Parameters<typeof execAsync>[1],\n): Effect.Effect<{ stdout: string; stderr: string }, ProcessSpawnError> =>\n Effect.tryPromise({\n try: async () => {\n const result = await execAsync(command, options);\n return {\n stdout: typeof result.stdout === 'string' ? result.stdout : result.stdout.toString('utf-8'),\n stderr: typeof result.stderr === 'string' ? result.stderr : result.stderr.toString('utf-8'),\n };\n },\n catch: (cause) =>\n new ProcessSpawnError({\n command,\n args: [],\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n\n/** Wrap `execFileAsync` in Effect with a typed error. */\nconst execFileCommand = (\n command: string,\n args: readonly string[],\n options?: Parameters<typeof execFileAsync>[2],\n): Effect.Effect<{ stdout: string; stderr: string }, ProcessSpawnError> =>\n Effect.tryPromise({\n try: async () => {\n const result = await execFileAsync(command, args as string[], options);\n return {\n stdout: typeof result.stdout === 'string' ? result.stdout : result.stdout.toString('utf-8'),\n stderr: typeof result.stderr === 'string' ? result.stderr : result.stderr.toString('utf-8'),\n };\n },\n catch: (cause) =>\n new ProcessSpawnError({\n command,\n args,\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n\n/** Wrap a fs/promises read in Effect with a typed FsError. */\nconst readFileOp = (path: string): Effect.Effect<string, FsError> =>\n Effect.tryPromise({\n try: () => readFile(path, 'utf-8'),\n catch: (cause) =>\n new FsError({\n path,\n operation: 'readFile',\n cause,\n }),\n });\n\n/** Wrap a fs/promises write in Effect with a typed FsError. */\nconst writeFileOp = (path: string, data: string): Effect.Effect<void, FsError> =>\n Effect.tryPromise({\n try: () => writeFile(path, data, 'utf-8'),\n catch: (cause) =>\n new FsError({\n path,\n operation: 'writeFile',\n cause,\n }),\n });\n\n/** Wrap a fs/promises unlink in Effect with a typed FsError. */\nconst unlinkPath = (path: string): Effect.Effect<void, FsError> =>\n Effect.tryPromise({\n try: () => unlink(path),\n catch: (cause) =>\n new FsError({\n path,\n operation: 'unlink',\n cause,\n }),\n });\n\n/** Re-exported for symmetry with the additive pattern in the rest of src/lib. */\nexport { GitError, ProcessTimeoutError };\nexport { checkInspectAgentTimeouts, INSPECT_TIMEOUT_MS } from './deacon-inspect.js';\nexport { reconcileStaleMergeStatus, reconcileStuckMergingStates, reconcileFalseMerged, reconcileClosedPrReadyForMerge, reconcileAutoMergeRows, reconcileStaleMergeBlockers, reconcileStuckReadyForMerge, reconcileMergedButReviewing, checkFailedMergeRetry, autoCloseOut, checkFirstCompletionAgents, ciRetryMap, FAILED_MERGE_MAX_RETRIES } from './deacon-merge.js';\nexport { coordinateSwarmSlots } from './deacon-swarm.js';\nexport { nudgeStalledResumeWorkAgents, redeliverUndeliveredKickoffs, nudgeIdleWorkAgentsWithOpenBeads, isRapidPostResumeDeath, isPreKickoffLaunchDeath } from './deacon-auto-resume.js';\n\nimport { OVERDECK_HOME, AGENTS_DIR, sessionFilePath } from '../paths.js';\nimport { loadCloisterConfigSync, loadCloisterConfig } from './config.js';\nimport { workResumeSlotsAvailable, getConcurrencyLimits, countRunningAgents, resetPatrolDispatchBudget, tryReserveAdvancingSlot, describeRunningAgents } from './concurrency.js';\nimport { tryYieldForAdvancingDispatch } from './preemption.js';\nimport { setReviewStatusSync, loadReviewStatuses, getReviewStatusSync } from '../review-status.js';\nimport { needsReviewDispatch } from '../review-dispatch-decision.js';\nimport { readIssueRecordSync } from '../pan-dir/record.js';\nimport { updateIssueRecord } from '../pan-dir/record-update.js';\nimport { readWorkspacePlanSync } from '../xbrief/io.js';\nimport { markWorkspaceStuck } from '../overdeck/review-status-sync.js';\nimport { isDeaconGloballyPaused } from '../overdeck/control-settings.js';\nimport { findWorkspacePath } from '../lifecycle/archive-planning.js';\nimport { resolveProjectFromIssueSync, listProjectsSync, getProjectSync } from '../projects.js';\nimport { recreatedStateWarnings, reconcileProjectStatePlanes, statePlaneReconcileEveryCycles } from './state-recreation-patrol.js';\nimport { reconcileTerminalIssueResidue } from './parked-residue.js';\nimport { withIssueRecordLock } from '../pan-dir/record-lock.js';\nimport { recordMainDivergenceHealth, type ProjectMainDivergence } from './deacon-main-divergence.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\nimport { mapGitHubStateToCanonical } from '../../core/state-mapping.js';\nimport { logDeaconEventSync, logAgentLifecycleSync } from '../persistent-logger.js';\nimport { emitActivityTtsSync } from '../activity-logger.js';\nimport { getShadowState } from '../shadow-state.js';\nimport type { TrackerConfig } from '../tracker/factory.js';\n\n// Review status file location (same as dashboard server)\n\nimport {\n SpecialistAgentName,\n getTmuxSessionName,\n isRunning,\n getAllProjectSpecialistStatuses,\n parseReviewerSessionName,\n} from './specialists.js';\nimport { getAgentRuntimeStateSync, saveAgentRuntimeState, saveSessionId, listRunningAgentsSync, listRunningAgents, listAgentStates, getAgentDir, getAgentStateSync, getAgentState, saveAgentStateSync, saveAgentState, resumeAgent, recordAgentFailure, resetAgentFailureCount, markAgentRunningState, buildDefaultResumeContinueMessage, clearAgentTroubledSync, type AgentState } from '../agents.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { buildTmuxCommandString, capturePane, createSession, getManagedTmuxSocketName, isPaneDead, killSessionSync, killSession, listPaneValuesSync, listPaneValues, listSessionNames, sessionExistsSync, sessionExists, sendKeys } from '../tmux.js';\nimport { withConcurrencyLimit } from '../concurrency.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { isAgentIdleForNudge } from './agent-idle.js';\nimport { checkStuckAgentRemediation } from './stuck-remediation.js';\nimport { decideAgentAutonomousRedrive } from './redrive-gate.js';\nimport { captureTranscriptUserRecordSnapshot } from '../transcript-landing.js';\nimport { reconcileClosedIssueAgents } from './closed-issue-reaper.js';\nimport { reconcilePipelineLabelsPatrol } from './label-reconciler.js';\nimport { pruneTerminalStoppedAgents } from './agent-gc.js';\nimport { shouldRunRecoveryJanitor } from './patrol-cadence.js';\nimport { recordDeadEndNeedsYou } from './dead-end-trip.js';\nimport { reconcileOrphanProposedSpecs, spawnWorkAgentThroughAgentsEndpoint, triggerRebuildAndStart } from './orphan-proposed-reconciler.js';\nimport { reconcilePendingPromotions } from './pending-promotion-reconciler.js';\nimport { reconcileTestStatusFromGreenCiWithDeps } from './test-status-green-ci-reconciler.js';\nimport { reapOrphanedDashboardServers } from './orphan-dashboard-server-reaper.js';\nimport { reconcileIdleWorkspaceStacks } from './idle-stack-reaper.js';\nimport { reapLeftoverPlaywrightBrowsers } from './playwright-mcp-reaper.js';\nimport { reapMergedStrikeWorkspaces } from './strike-workspace-reaper.js';\nimport { cleanupOrphanedInspectSessions } from './inspect-session-reaper.js';\nimport { isIssueClosed } from './issue-closed.js';\nimport { decideUnsignaledTestAction, readTestVerdictArtifact, recordUnsignaledTestEscalation } from './test-verdict.js';\nimport { sweepStrandedVerdictFallbacks } from './deacon-verdict-fallback-sweep.js';\nimport { deliverReviewVerdictFeedback } from './review-verdict-feedback.js';\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\n/**\n * Default parameters for stuck-session detection.\n * Per gastown: \"Let agents decide thresholds. 'Stuck' is a judgment call.\"\n */\nconst DEFAULT_CONFIG: DeaconConfig = {\n pingTimeoutMs: 30_000, // How long to wait for response\n consecutiveFailures: 3, // Failures before force-kill\n cooldownMs: 5 * 60_000, // 5 minutes between force-kills\n patrolIntervalMs: 60_000, // Safety net — immediate processing happens via pipeline events\n massDeathThreshold: 2, // Deaths within window triggers alert\n massDeathWindowMs: 60_000, // 1 minute window for mass death detection\n};\n\nexport interface DeaconConfig {\n pingTimeoutMs: number;\n consecutiveFailures: number;\n cooldownMs: number;\n patrolIntervalMs: number;\n massDeathThreshold: number;\n massDeathWindowMs: number;\n}\n\n// ============================================================================\n// Health State Types\n// ============================================================================\n\n/**\n * Health check state for a single specialist\n */\nexport interface SpecialistHealthState {\n specialistName: SpecialistAgentName;\n lastPingTime?: string; // ISO 8601\n lastResponseTime?: string; // ISO 8601\n consecutiveFailures: number;\n lastForceKillTime?: string; // ISO 8601\n forceKillCount: number;\n}\n\n/**\n * PAN-464: Tracks restart history for a workspace container.\n */\nexport interface ContainerRestartRecord {\n count: number; // Total restart attempts\n firstRestart: string; // ISO 8601 — when the first restart in the current burst happened\n lastRestart: string; // ISO 8601 — when the most recent restart happened\n gaveUp?: boolean; // True when max restarts exceeded — skip future auto-restarts\n}\n\n/**\n * Complete health check state for all specialists\n */\nexport interface DeaconState {\n specialists: Record<SpecialistAgentName, SpecialistHealthState>;\n lastPatrol?: string; // ISO 8601\n lastPatrolResult?: PatrolResult;\n patrolCycle: number;\n recentDeaths: string[]; // ISO timestamps of recent deaths\n lastMassDeathAlert?: string; // ISO 8601\n mergeStuckAttempts?: Record<string, number>; // circuit-breaker attempt counts (PAN-344)\n containerRestarts?: Record<string, ContainerRestartRecord>; // PAN-464: restart backoff tracking\n mainDivergence?: ProjectMainDivergence[];\n}\n\nexport type DeaconPatrolHealth = 'running' | 'starting' | 'stale' | 'stopped';\n\nexport interface DeaconPatrolFreshness {\n status: DeaconPatrolHealth;\n lastPatrol: string | null;\n secondsSinceLastPatrol: number | null;\n staleAfterSeconds: number;\n}\n\n/**\n * Result of a health check\n */\nexport interface HealthCheckResult {\n specialistName: SpecialistAgentName;\n isResponsive: boolean;\n responseTimeMs?: number;\n consecutiveFailures: number;\n shouldForceKill: boolean;\n inCooldown: boolean;\n cooldownRemainingMs?: number;\n wasRunning: boolean;\n error?: string;\n}\n\n// ============================================================================\n// State Management\n// ============================================================================\n\nconst DEACON_DIR = join(OVERDECK_HOME, 'deacon');\nconst STATE_FILE = join(DEACON_DIR, 'health-state.json');\nconst CONFIG_FILE = join(DEACON_DIR, 'config.json');\nconst DEACON_LOG_FILE = join(OVERDECK_HOME, 'logs', 'deacon.log');\n\nlet deaconInterval: NodeJS.Timeout | null = null;\nconst deaconPatrolGuard = createInFlightGuard();\nlet memoryPatrolInterval: NodeJS.Timeout | null = null;\nlet config: DeaconConfig = { ...DEFAULT_CONFIG };\n\n/** PAN-3550: the memory watch samples far more often than the 60s patrol so rising\n * pressure reaches the activity feed before the kernel acts. */\nexport const MEMORY_PATROL_INTERVAL_MS = 15_000;\n\n/**\n * Load deacon configuration\n */\nexport function loadConfig(): DeaconConfig {\n config = { ...DEFAULT_CONFIG };\n\n try {\n if (existsSync(CONFIG_FILE)) {\n const content = readFileSync(CONFIG_FILE, 'utf-8');\n const loaded = JSON.parse(content);\n config = { ...config, ...loaded };\n }\n } catch (error) {\n console.error('[deacon] Failed to load config:', error);\n }\n\n return config;\n}\n\n/**\n * Save deacon configuration\n */\nexport function saveConfig(newConfig: Partial<DeaconConfig>): void {\n ensureDeaconDir();\n config = { ...config, ...newConfig };\n writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8');\n}\n\n/**\n * Ensure deacon directory exists\n */\nfunction ensureDeaconDir(): void {\n if (!existsSync(DEACON_DIR)) {\n mkdirSync(DEACON_DIR, { recursive: true });\n }\n}\n\n/**\n * Load health check state from disk\n */\nexport function loadState(): DeaconState {\n ensureDeaconDir();\n\n try {\n if (existsSync(STATE_FILE)) {\n const content = readFileSync(STATE_FILE, 'utf-8');\n return JSON.parse(content);\n }\n } catch (error) {\n console.error('[deacon] Failed to load state:', error);\n }\n\n // Return empty state\n return {\n specialists: {} as Record<SpecialistAgentName, SpecialistHealthState>,\n patrolCycle: 0,\n recentDeaths: [],\n };\n}\n\nfunction newestPersistedPatrolHeartbeat(incomingLastPatrol: string | undefined): string | undefined {\n if (!incomingLastPatrol || !existsSync(STATE_FILE)) return incomingLastPatrol;\n\n try {\n const content = readFileSync(STATE_FILE, 'utf-8');\n const persisted = JSON.parse(content) as { lastPatrol?: unknown };\n if (typeof persisted.lastPatrol !== 'string') return incomingLastPatrol;\n\n const incomingMs = Date.parse(incomingLastPatrol);\n const persistedMs = Date.parse(persisted.lastPatrol);\n if (!Number.isFinite(persistedMs)) return incomingLastPatrol;\n if (!Number.isFinite(incomingMs) || persistedMs > incomingMs) return persisted.lastPatrol;\n } catch {\n // Fall through to the incoming state; saveState's write error handling remains below.\n }\n\n return incomingLastPatrol;\n}\n\n/**\n * Save health check state to disk\n */\nexport function saveState(state: DeaconState): void {\n ensureDeaconDir();\n\n try {\n const newestLastPatrol = newestPersistedPatrolHeartbeat(state.lastPatrol);\n if (newestLastPatrol) {\n state.lastPatrol = newestLastPatrol;\n } else {\n delete state.lastPatrol;\n }\n writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), 'utf-8');\n } catch (error) {\n console.error('[deacon] Failed to save state:', error);\n }\n}\n\nfunction saveLastPatrolResult(state: DeaconState, result: PatrolResult): void {\n state.lastPatrolResult = result;\n saveState(state);\n}\n\nfunction refreshPatrolHeartbeat(state: DeaconState): void { state.lastPatrol = new Date().toISOString(); saveState(state); }\n\nfunction startPatrolHeartbeatTicker(state: DeaconState): NodeJS.Timeout {\n const ticker = setInterval(() => refreshPatrolHeartbeat(state), Math.max(10_000, Math.floor(config.patrolIntervalMs / 2)));\n ticker.unref?.(); return ticker;\n}\n\n/**\n * Clear the persisted patrol heartbeat at dashboard startup so the supervisor\n * watchdog gives the first deacon patrol its normal startup grace window.\n */\nexport function resetPatrolHeartbeatForStartup(): void {\n try {\n if (!existsSync(STATE_FILE)) return;\n\n const state = loadState();\n delete state.lastPatrol;\n saveState(state);\n } catch {\n // Non-fatal: worst case the watchdog still sees the stale heartbeat.\n }\n}\n\n/**\n * Get health state for a specialist, creating if needed\n */\nfunction getSpecialistState(\n state: DeaconState,\n name: SpecialistAgentName\n): SpecialistHealthState {\n if (!state.specialists[name]) {\n state.specialists[name] = {\n specialistName: name,\n consecutiveFailures: 0,\n forceKillCount: 0,\n };\n }\n return state.specialists[name];\n}\n\n// ============================================================================\n// Health Check Logic\n// ============================================================================\n\n/**\n * Check if a specialist is in cooldown period\n */\nfunction isInCooldown(healthState: SpecialistHealthState): boolean {\n if (!healthState.lastForceKillTime) {\n return false;\n }\n\n const lastKill = new Date(healthState.lastForceKillTime).getTime();\n const cooldownEnd = lastKill + config.cooldownMs;\n return Date.now() < cooldownEnd;\n}\n\n/**\n * Get remaining cooldown time in ms\n */\nfunction getCooldownRemaining(healthState: SpecialistHealthState): number {\n if (!healthState.lastForceKillTime) {\n return 0;\n }\n\n const lastKill = new Date(healthState.lastForceKillTime).getTime();\n const cooldownEnd = lastKill + config.cooldownMs;\n const remaining = cooldownEnd - Date.now();\n return Math.max(0, remaining);\n}\n\n/**\n * Check if a specialist is responsive by reading their heartbeat\n */\nfunction checkHeartbeat(name: SpecialistAgentName): {\n isResponsive: boolean;\n lastActivity?: number;\n responseTimeMs?: number;\n} {\n const tmuxSession = getTmuxSessionName(name);\n const heartbeatFile = join(OVERDECK_HOME, 'heartbeats', `${tmuxSession}.json`);\n\n try {\n if (!existsSync(heartbeatFile)) {\n return { isResponsive: false };\n }\n\n const content = readFileSync(heartbeatFile, 'utf-8');\n const heartbeat = JSON.parse(content);\n const lastActivity = new Date(heartbeat.timestamp).getTime();\n const age = Date.now() - lastActivity;\n\n // If heartbeat is less than pingTimeout old, specialist is responsive\n const isResponsive = age < config.pingTimeoutMs;\n\n return {\n isResponsive,\n lastActivity,\n responseTimeMs: age,\n };\n } catch {\n return { isResponsive: false };\n }\n}\n\n/**\n * Perform a health check on a specialist\n *\n * When called from runPatrol, pass the shared state object to avoid\n * independent load/save cycles that clobber each other (the original\n * bug that prevented consecutiveFailures from ever accumulating).\n *\n * When called standalone (no sharedState), loads and saves state itself.\n */\nexport async function checkSpecialistHealth(\n name: SpecialistAgentName,\n sharedState?: DeaconState,\n): Promise<HealthCheckResult> {\n const state = sharedState ?? loadState();\n const healthState = getSpecialistState(state, name);\n const wasRunning = await isRunning(name);\n\n // Update ping time\n healthState.lastPingTime = new Date().toISOString();\n\n // If not running, it's not responsive\n if (!wasRunning) {\n if (!sharedState) saveState(state);\n return {\n specialistName: name,\n isResponsive: false,\n wasRunning: false,\n consecutiveFailures: healthState.consecutiveFailures,\n shouldForceKill: false, // Can't force-kill what's not running\n inCooldown: isInCooldown(healthState),\n cooldownRemainingMs: getCooldownRemaining(healthState),\n error: 'Specialist is not running',\n };\n }\n\n // Check heartbeat\n const heartbeatResult = checkHeartbeat(name);\n\n if (heartbeatResult.isResponsive) {\n // Reset failure counter on successful response\n healthState.consecutiveFailures = 0;\n healthState.lastResponseTime = new Date().toISOString();\n if (!sharedState) saveState(state);\n\n return {\n specialistName: name,\n isResponsive: true,\n responseTimeMs: heartbeatResult.responseTimeMs,\n wasRunning: true,\n consecutiveFailures: 0,\n shouldForceKill: false,\n inCooldown: isInCooldown(healthState),\n };\n }\n\n // Stale heartbeat — but an idle specialist is EXPECTED to have a stale heartbeat\n // (no tool calls = no hook-based heartbeat updates). Don't count idle specialists\n // as failures — only escalate when the specialist should be actively working.\n const tmuxSession = getTmuxSessionName(name);\n const runtimeState = getAgentRuntimeStateSync(tmuxSession);\n const isIdle = !runtimeState || runtimeState.state === 'idle';\n\n if (isIdle) {\n // Idle specialist with stale heartbeat is normal — treat as responsive\n if (!sharedState) saveState(state);\n return {\n specialistName: name,\n isResponsive: false, // heartbeat IS stale\n wasRunning: true,\n consecutiveFailures: healthState.consecutiveFailures, // don't increment\n shouldForceKill: false, // never force-kill an idle specialist\n inCooldown: isInCooldown(healthState),\n };\n }\n\n // Active specialist with stale heartbeat — genuinely unresponsive\n healthState.consecutiveFailures++;\n if (!sharedState) saveState(state);\n\n const shouldForceKill =\n healthState.consecutiveFailures >= config.consecutiveFailures &&\n !isInCooldown(healthState);\n\n return {\n specialistName: name,\n isResponsive: false,\n wasRunning: true,\n consecutiveFailures: healthState.consecutiveFailures,\n shouldForceKill,\n inCooldown: isInCooldown(healthState),\n cooldownRemainingMs: getCooldownRemaining(healthState),\n };\n}\n\n/**\n * Force-kill a stuck specialist\n *\n * When called from runPatrol, pass the shared state object.\n * When called standalone, loads and saves state itself.\n */\nexport async function forceKillSpecialist(\n name: SpecialistAgentName,\n sharedState?: DeaconState,\n): Promise<{\n success: boolean;\n message: string;\n}> {\n const tmuxSession = getTmuxSessionName(name);\n const state = sharedState ?? loadState();\n const healthState = getSpecialistState(state, name);\n\n // Check cooldown\n if (isInCooldown(healthState)) {\n const remaining = getCooldownRemaining(healthState);\n return {\n success: false,\n message: `Specialist ${name} is in cooldown. ${Math.ceil(remaining / 1000)}s remaining.`,\n };\n }\n\n try {\n // Kill the tmux session (non-blocking)\n await Effect.runPromise(killSession(tmuxSession));\n\n // Update state\n healthState.lastForceKillTime = new Date().toISOString();\n healthState.forceKillCount++;\n healthState.consecutiveFailures = 0;\n\n // Record death for mass death detection\n state.recentDeaths.push(new Date().toISOString());\n // Prune old deaths outside the window\n const windowStart = Date.now() - config.massDeathWindowMs;\n state.recentDeaths = state.recentDeaths.filter(\n (d) => new Date(d).getTime() > windowStart\n );\n\n if (!sharedState) saveState(state);\n\n console.log(`[deacon] Force-killed specialist ${name}`);\n\n return {\n success: true,\n message: `Specialist ${name} force-killed after ${healthState.forceKillCount} total kills`,\n };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n success: false,\n message: `Failed to kill specialist ${name}: ${msg}`,\n };\n }\n}\n\n/**\n * Check for mass death condition\n *\n * When called from runPatrol, pass the shared state object.\n * When called standalone, loads and saves state itself.\n */\nexport function checkMassDeath(sharedState?: DeaconState): {\n isMassDeath: boolean;\n deathCount: number;\n message?: string;\n} {\n const state = sharedState ?? loadState();\n\n // Prune old deaths\n const windowStart = Date.now() - config.massDeathWindowMs;\n state.recentDeaths = state.recentDeaths.filter(\n (d) => new Date(d).getTime() > windowStart\n );\n\n const deathCount = state.recentDeaths.length;\n\n if (deathCount >= config.massDeathThreshold) {\n // Check if we already alerted recently\n if (state.lastMassDeathAlert) {\n const lastAlert = new Date(state.lastMassDeathAlert).getTime();\n const alertCooldown = 5 * 60_000; // 5 minutes between alerts\n if (Date.now() - lastAlert < alertCooldown) {\n if (!sharedState) saveState(state);\n return {\n isMassDeath: true,\n deathCount,\n message: 'Mass death detected (already alerted)',\n };\n }\n }\n\n // Record alert\n state.lastMassDeathAlert = new Date().toISOString();\n if (!sharedState) saveState(state);\n\n return {\n isMassDeath: true,\n deathCount,\n message: `ALERT: ${deathCount} specialist deaths in ${config.massDeathWindowMs / 1000}s - possible infrastructure issue`,\n };\n }\n\n if (!sharedState) saveState(state);\n\n return {\n isMassDeath: false,\n deathCount,\n };\n}\n\n// ============================================================================\n// Patrol Loop\n// ============================================================================\n\n/**\n * Patrol result for a single cycle\n */\nexport interface PatrolResult {\n cycle: number;\n timestamp: string;\n specialists: HealthCheckResult[];\n actionsToken: string[];\n massDeathDetected: boolean;\n mainDivergence?: ProjectMainDivergence[];\n}\n\n/**\n * Check and auto-suspend idle agents (PAN-80)\n *\n * Specialists: 5 minute idle timeout\n * Work agents: NEVER auto-suspend after completion (stay available for merge)\n */\n// checkAndSuspendIdleAgents deleted in PAN-800 Phase 5.\n// The old implementation read runtime.json + wrote 'suspended' state +\n// tmux-regex-checked via isAgentActiveInTmux — all paths the new\n// AgentStateService supersedes. Active intervention (auto-suspend) is\n// out of scope for PAN-800 and tracked under PAN-188.\nexport async function checkAndSuspendIdleAgents(): Promise<string[]> {\n return [];\n}\n\n// ============================================================================\n// Agent State Cleanup\n// ============================================================================\n//\n// Lazy-agent detection (LAZY_PATTERNS regex catalog + checkLazyAgent +\n// sendAntiLazyMessage + checkAndCorrectLazyAgents) was deleted in PAN-800\n// Phase 5. It had already been disabled in the patrol because false positives\n// burned API credits (PAN-133). The replacement predicate is typed:\n// activity === \"idle\" && !waiting && now - lastActivity > threshold\n// Intervention itself is out of scope for PAN-800; PAN-188 tracks rebuilding\n// it on top of the AgentRuntimeSnapshot stream.\n// ============================================================================\n\n// ACTIVE_STATUS_PATTERNS + isAgentActiveInTmux deleted in PAN-800 Phase 5.\n// They pane-scraped Claude Code's status line to guess \"is this agent active\",\n// including a parseThinkingDuration stuck heuristic. The hook-driven runtime\n// mirror (getAgentRuntimeStateSync → activity 'working'/'thinking'/'idle') is\n// the single source of truth now; capture-pane activity detection is gone.\n// isAgentActiveInTmux had no remaining callers at deletion time.\n\n// ============================================================================\n// Stuck Work Agent Detection\n// ============================================================================\n\n/**\n * checkStuckWorkAgents gutted in PAN-800 Phase 5.\n *\n * The old implementation `capturePane`d every work agent and ran a\n * `parseThinkingDuration` regex over the tmux status line to decide an agent\n * was \"stuck thinking\", then escalated Escape → Ctrl-C → kill/respawn. That is\n * exactly the capture-pane scraping PAN-798/PAN-800 eliminate, and it was\n * brittle: it only recognised the \"Thinking\"/\"Fermenting\" spinner words (not\n * \"Quantumizing\" et al.) and could not parse hour-scale durations.\n *\n * Stuck detection is now hook-based and lives in `checkStuckAgentRemediation`\n * (stuck-remediation.ts), which reads the runtime mirror via\n * `isAgentIdleForNudge` + `getAgentRuntimeStateSync` and escalates\n * nudge → resume → troubled. PAN-1586 made `isAgentIdleForNudge` treat a stale\n * 'active' mirror (Stop hook never fired) as idle, so a genuinely-stalled agent\n * — regardless of spinner word or duration — is now caught there.\n *\n * The \"exclude-from-context\" dialog auto-dismiss that also lived here was a\n * pane-scrape active intervention; like auto-suspend and lazy-detection it is\n * out of scope for PAN-800 and tracked under PAN-188. The notification hook\n * already surfaces that dialog as `waiting-on-human`.\n *\n * Retained as a no-op stub so the patrol-cycle wiring stays stable.\n */\nexport async function checkStuckWorkAgents(): Promise<string[]> {\n return [];\n}\n\nexport {\n checkApiErrorAgents,\n CONTEXT_PROACTIVE_COMPACT_HIGH_WATER_PERCENT,\n contextOverflowRecoveryState,\n stuckOverflowNativeRecoveryState,\n} from './deacon-api-recovery.js';\n\n/**\n * Clean up stale agent state directories (PAN-154)\n *\n * Scans ~/.overdeck/agents/ for directories that:\n * - Have no active tmux session\n * - Are older than the configured retention threshold (default: 30 days)\n * - Don't have a recently processed completion marker\n *\n * Runs at low frequency (~once per day) via random trigger in patrol cycle.\n */\nexport async function cleanupStaleAgentState(): Promise<string[]> {\n const actions: string[] = [];\n const cloisterConfig = loadCloisterConfigSync();\n // Default retention for work/planning runtime residue. Fresh-session wipes\n // and close-out cleanup use removeAgentStateDir at their lifecycle events;\n // this purge is the safety net, and the shared door preserves all JSONL.\n const retentionDays = cloisterConfig.retention?.agent_state_days ?? 7;\n const retentionMs = retentionDays * 24 * 60 * 60 * 1000;\n // Reviewer runtime residue has a shorter debugging window. Review restart\n // and close-out paths use the same deletion door, while this 1-day purge\n // catches sessions whose event-driven cleanup never ran and preserves JSONL.\n const reviewerRetentionDays = cloisterConfig.retention?.reviewer_state_days ?? 1;\n const reviewerRetentionMs = reviewerRetentionDays * 24 * 60 * 60 * 1000;\n const now = Date.now();\n\n if (!existsSync(AGENTS_DIR)) {\n return actions;\n }\n\n try {\n const dirs = readdirSync(AGENTS_DIR, { withFileTypes: true })\n .filter(d => d.isDirectory());\n\n for (const dir of dirs) {\n // conv-* dirs are the canonical transcript home for non-claude harnesses\n // (ohmypi sessions/*.jsonl, codex codex-home/sessions/ rollouts). Purging\n // them permanently destroys conversation history — same invariant as\n // findOrphanedAgentDirs (2026-07-05 transcript-loss incident).\n if (isConversationDirectory(dir.name)) continue;\n const agentDir = join(AGENTS_DIR, dir.name);\n const isReviewer = dir.name.startsWith('review-');\n const effectiveRetentionMs = isReviewer ? reviewerRetentionMs : retentionMs;\n\n try {\n // Check if tmux session is active — never clean up running agents\n try {\n const exists = await Effect.runPromise(sessionExists(dir.name));\n if (exists) {\n continue; // Session exists, skip\n }\n } catch {\n // No session — candidate for cleanup\n }\n\n // PAN-1908: for canonical agent-*/strike-* directories, prefer the\n // SQLite agents table as the source of truth. If an entry exists, keep\n // the directory even if state.json is old — the registry (not the\n // filesystem) owns retention. Reviewers remain filesystem-ephemeral.\n if (dir.name.startsWith('agent-') || dir.name.startsWith('strike-')) {\n const agent = await Effect.runPromise(\n getAgentState(dir.name).pipe(Effect.catch(() => Effect.succeed(null))),\n );\n if (agent != null) continue;\n }\n\n // Check directory age via directory mtime.\n const mtime = statSync(agentDir).mtimeMs;\n\n const ageMs = now - mtime;\n if (ageMs < effectiveRetentionMs) {\n continue; // Not old enough, skip\n }\n\n // Reviewers don't have a `completed` marker and don't warrant the\n // 7-day grace period — skip the completion check for them.\n if (!isReviewer) {\n const completedFile = join(agentDir, 'completed');\n if (existsSync(completedFile)) {\n const completedAge = now - statSync(completedFile).mtimeMs;\n // Keep completed work agents for at least 7 days regardless of retention\n if (completedAge < 7 * 24 * 60 * 60 * 1000) {\n continue;\n }\n }\n }\n\n // Safe to remove\n const ageDays = Math.round(ageMs / (24 * 60 * 60 * 1000));\n const tag = isReviewer ? 'reviewer' : 'agent';\n const removal = await removeAgentStateDir(agentDir);\n const transcriptLabel = `transcript file${removal.preservedTranscripts === 1 ? '' : 's'} preserved`;\n actions.push(`Purged stale ${tag} state: ${dir.name} (${ageDays} days old; ${removal.preservedTranscripts} ${transcriptLabel})`);\n console.log(`[deacon] Purged stale ${tag} state: ${dir.name} (${ageDays} days old; ${removal.preservedTranscripts} ${transcriptLabel})`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Error cleaning up agent ${dir.name}:`, msg);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error during agent state cleanup:', msg);\n }\n\n if (actions.length > 0) {\n console.log(`[deacon] Cleanup complete: purged ${actions.length} stale agent directories`);\n }\n\n return actions;\n}\n\n/**\n * Clean up abandoned feedback directories.\n *\n * Event-driven cleanup handles the happy path (new review cycle → clear on\n * dispatch; merge → close-out removes workspace). This sweep is the safety net\n * for workspaces where those events never fired: work agent is no longer\n * running AND no review is in flight AND feedback files are still sitting in\n * `.pan/feedback/`.\n *\n * The feedback is useless once consumed, so we always delete — no archive, no\n * retention. See docs/REVIEW-AGENT-ARCHITECTURE.md.\n */\nexport async function cleanupAbandonedFeedback(): Promise<string[]> {\n const actions: string[] = [];\n\n const { getReviewStatusSync } = await import('../review-status.js');\n const { clearFeedbackFiles } = await import('./feedback-writer.js');\n\n for (const { issueId, workspacePath } of listFeatureWorkspaces()) {\n const panFeedbackDir = join(workspacePath, '.pan', 'feedback');\n if (!existsSync(panFeedbackDir)) continue;\n\n const issueLower = issueId.toLowerCase();\n\n // Gate 1: work agent tmux session active? If yes, feedback may be current.\n const agentSession = `agent-${issueLower}`;\n try {\n if (await Effect.runPromise(sessionExists(agentSession))) continue;\n } catch {\n // Treat lookup error as \"session might exist\" — skip out of caution.\n continue;\n }\n\n // Gate 2: review in flight? If yes, feedback is about to be consumed.\n try {\n const status = getReviewStatusSync(issueId);\n if (status?.reviewStatus === 'reviewing') continue;\n } catch {\n // No status entry → safe to clean.\n }\n\n // Both gates passed — feedback is abandoned, safe to delete.\n try {\n const countFeedbackFiles = (dir: string) => existsSync(dir)\n ? readdirSync(dir).filter(f => /^\\d{3}-/.test(f) && f.endsWith('.md')).length\n : 0;\n const before = countFeedbackFiles(panFeedbackDir);\n if (before === 0) continue;\n await Effect.runPromise(clearFeedbackFiles(workspacePath));\n actions.push(\n `Cleared ${before} abandoned feedback file(s) in feature-${issueLower} (agent stopped, no in-flight review)`,\n );\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] cleanupAbandonedFeedback failed for feature-${issueLower}:`, msg);\n }\n }\n\n if (actions.length > 0) {\n console.log(`[deacon] Feedback cleanup: ${actions.length} workspace(s) cleared`);\n }\n\n return actions;\n}\n\nconst ORPHAN_REVIEWER_AGE_MS = 60 * 60 * 1000; // 1 hour\n\nasync function loadTmuxSessionsWithCreationTimes(): Promise<{ sessions: string[]; creationTimes: Map<string, number> } | null> {\n try {\n const { stdout } = await execAsync(\n `tmux -L ${getManagedTmuxSocketName()} -f ${join(OVERDECK_HOME, 'tmux', 'overdeck.tmux.conf')} list-sessions -F '#{session_name} #{session_created}'`,\n { encoding: 'utf-8' },\n );\n const lines = stdout.split('\\n').filter(l => l.trim());\n const sessions: string[] = [];\n const creationTimes = new Map<string, number>();\n for (const line of lines) {\n const parts = line.split(' ');\n if (parts.length >= 2) {\n const name = parts.slice(0, -1).join(' ');\n const created = parseInt(parts[parts.length - 1], 10);\n if (!Number.isFinite(created)) continue;\n if (name) {\n sessions.push(name);\n creationTimes.set(name, created * 1000); // tmux returns seconds\n }\n }\n }\n return { sessions, creationTimes };\n } catch {\n // tmux server may not be running — nothing to clean\n return null;\n }\n}\n\nfunction issueIdFromReviewerSessionName(sessionName: string): string | null {\n // PAN-1059 convoy pattern: agent-<issueId>-review-<subRole>\n const convoyMatch = sessionName.match(/^agent-([a-z0-9]+-\\d+)-review-(?:security|correctness|performance|requirements)$/);\n if (convoyMatch) {\n return convoyMatch[1].toUpperCase();\n }\n\n // Legacy specialist pattern: specialist-<project>-<issueId>-review-<role>\n const parsedReviewer = parseReviewerSessionName(sessionName);\n if (parsedReviewer) {\n return parsedReviewer.issueId.toUpperCase();\n }\n\n // Generic fallback\n const match = sessionName.match(/([A-Z0-9]+-\\d+)/i);\n return match ? match[1].toUpperCase() : null;\n}\n\nasync function handleOrphanReviewerSession(\n sessionName: string,\n sessions: string[],\n creationTimes: Map<string, number>,\n now: number,\n): Promise<string | null> {\n // PAN-1059: convoy sub-role sessions are agent-<id>-review-<subRole>.\n // Legacy specialist sessions matched specialist-*-review-*. Both contain -review-.\n if (!sessionName.includes('-review-')) return null;\n\n const createdMs = creationTimes.get(sessionName);\n if (!createdMs || now - createdMs < ORPHAN_REVIEWER_AGE_MS) return null;\n\n const issueId = issueIdFromReviewerSessionName(sessionName);\n if (!issueId) return null;\n\n // Gate 1: work agent running?\n const agentSession = `agent-${issueId.toLowerCase()}`;\n if (sessions.includes(agentSession)) return null;\n\n // Gate 2: review in flight for this issue?\n const { getReviewStatusSync } = await import('../review-status.js');\n try {\n const status = getReviewStatusSync(issueId);\n if (status?.reviewStatus === 'reviewing') return null;\n } catch {\n // No status entry → safe to clean\n }\n\n try {\n await Effect.runPromise(killSession(sessionName));\n const ageMin = Math.round((now - createdMs) / 60000);\n const msg = `Killed orphan reviewer session ${sessionName} (${ageMin}m old)`;\n console.log(`[deacon] ${msg}`);\n return msg;\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] Failed to kill orphan session ${sessionName}:`, msg);\n return null;\n }\n}\n\n/**\n * PAN-1908: reactive orphan reviewer-session cleanup. When a work agent stops,\n * check whether any reviewer sessions for the same issue have outlived it and\n * kill them if they are old enough and no review is in flight.\n */\nexport async function handleAgentStoppedForOrphanReviewerSessions(agentId: string): Promise<string[]> {\n const match = agentId.match(/^agent-([a-z0-9]+-\\d+)$/i);\n if (!match) return [];\n const issueId = match[1].toUpperCase();\n\n const loaded = await loadTmuxSessionsWithCreationTimes();\n if (!loaded) return [];\n const { sessions, creationTimes } = loaded;\n\n const actions: string[] = [];\n const now = Date.now();\n for (const sessionName of sessions) {\n const sessionIssueId = issueIdFromReviewerSessionName(sessionName);\n if (sessionIssueId !== issueId) continue;\n\n const result = await handleOrphanReviewerSession(sessionName, sessions, creationTimes, now);\n if (result) actions.push(result);\n }\n\n if (actions.length > 0) {\n console.log(`[deacon] Orphan session cleanup: killed ${actions.length} session(s)`);\n }\n return actions;\n}\n\n/**\n * Clean up orphan reviewer and specialist tmux sessions (PAN-846).\n *\n * PAN-1908: this is now a thin dropped-event safety net. The primary cleanup\n * path is reactive via handleAgentStoppedForOrphanReviewerSessions.\n */\nexport async function cleanupOrphanReviewerSessions(): Promise<string[]> {\n const loaded = await loadTmuxSessionsWithCreationTimes();\n if (!loaded) return [];\n const { sessions, creationTimes } = loaded;\n\n const actions: string[] = [];\n const now = Date.now();\n for (const sessionName of sessions) {\n const result = await handleOrphanReviewerSession(sessionName, sessions, creationTimes, now);\n if (result) actions.push(result);\n }\n\n if (actions.length > 0) {\n console.log(`[deacon] Orphan session cleanup: killed ${actions.length} session(s)`);\n }\n return actions;\n}\n\nexport {\n reviewConvoyLiveness,\n handleReviewCoordinatorDied,\n handleWorkCompleted,\n checkOrphanedReviewStatuses,\n recoverStalledReviewConvoys,\n checkMissingReviewStatuses,\n checkStuckReviewing,\n checkCompletedButUnsignaledReviews,\n reconcileUnappliedReviewVerdicts,\n monitorReviewConvoySignals,\n cleanupOrphanedReviewSessions,\n synthesizeReviewFromReports,\n isSynthesisForActiveReviewRun,\n stalledReviewConvoyRecoveryState,\n} from './deacon-review.js';\nexport type { ReviewConvoyLiveness } from './deacon-review.js';\n\n// ============================================================================\n// PAN-2207: Recover completions whose PR is open but review was never dispatched\n// ============================================================================\n\n/**\n * Detect issues where `pan done` failed mid-flight: PR is open, all beads are\n * closed, reviewStatus is pending, and reviewRequestedAt was never written.\n * Verify all beads are closed before re-dispatching review; mark the record\n * with a tombstone so the patrol does not loop.\n */\nexport async function checkOrphanedCompletions(): Promise<string[]> {\n const actions: string[] = [];\n const now = new Date().toISOString();\n\n try {\n const statuses = loadReviewStatuses();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n try {\n if (status.reviewStatus !== 'pending') continue;\n if (status.mergeStatus === 'merged') continue;\n if (needsReviewDispatch(status)) continue;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const project = getProjectSync(resolved.projectKey);\n if (!project) continue;\n\n const record = readIssueRecordSync(project, issueId);\n if (record?.pipeline?.panDoneRecoveredAt) continue;\n\n const issueLower = issueId.toLowerCase();\n const workspacePath = findWorkspacePath(resolved.projectPath, issueLower);\n if (!workspacePath || !existsSync(workspacePath)) continue;\n\n const plan = readWorkspacePlanSync(workspacePath);\n if (!plan || plan.plan.items.length === 0) continue;\n if (plan.plan.items.some((item) => !['completed', 'cancelled'].includes(item.status))) continue;\n\n const { stdout } = await execAsync(\n `gh pr list --head feature/${issueLower} --state open --json url --jq '.[0].url'`,\n { encoding: 'utf-8', timeout: 15000 }\n );\n const prUrl = stdout.trim();\n if (!prUrl) continue;\n\n setReviewStatusSync(issueId, { reviewRequestedAt: now, prUrl });\n // Serialize with setReviewStatusSync's fire-and-forget journal rebuild so\n // the panDoneRecoveredAt tombstone is written after (and on top of) it.\n await updateIssueRecord(project, issueId, (nextRecord) => {\n Object.assign(nextRecord.pipeline, { reviewRequestedAt: now, prUrl, panDoneRecoveredAt: now });\n });\n\n const action = `checkOrphanedCompletions: recovered ${issueId} (PR open but review never dispatched)`;\n logDeaconEventSync(action);\n actions.push(action);\n console.log(`[deacon] ${action}`);\n } catch (issueErr: any) {\n console.warn(`[deacon] checkOrphanedCompletions: error for ${issueId}: ${issueErr?.message ?? issueErr}`);\n }\n }\n } catch (err: any) {\n console.warn(`[deacon] checkOrphanedCompletions: patrol error: ${err?.message ?? err}`);\n }\n\n return actions;\n}\n\n// ============================================================================\n// PAN-699: Pending test dispatch retry\n// ============================================================================\n\n/**\n * Retry test-agent dispatch for issues where review passed but the test-agent\n * never started or failed to dispatch.\n *\n * Conditions:\n * - reviewStatus === 'passed' AND (testStatus === 'pending' for >5min OR testStatus === 'dispatch_failed')\n * - Retries up to 3 times with exponential backoff tracked per-issue.\n */\nexport async function checkPendingTestDispatch(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const { loadReviewStatuses, setReviewStatusSync } = await import('../review-status.js');\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'passed') continue;\n if (status.testStatus !== 'pending' && status.testStatus !== 'dispatch_failed') continue;\n\n // PAN-1681: a live test session (idle or working) is the failsafe's job,\n // not a re-dispatch target. Re-dispatching a fresh agent here is what burnt\n // the 3-retry budget on idle agents that simply never signaled. Skip\n // entirely — checkCompletedButUnsignaledTests (run just before this) nudges\n // /auto-completes the existing agent. Only re-dispatch when NO live session.\n const testSession = `agent-${issueId.toLowerCase()}-test`;\n if (sessionExistsSync(testSession)) {\n const testPaneDead = await Effect.runPromise(isPaneDead(testSession)).catch(() => true);\n if (!testPaneDead) continue;\n }\n\n const retryCount = status.testRetryCount ?? 0;\n if (retryCount >= 3) {\n // PAN-1681: stop silently capping. With no live session to recover from\n // (we'd have continued above) and the verdict failsafe unable to recover,\n // the issue is genuinely stranded. Surface a one-time visible stuck marker\n // so the operator can see it instead of it sitting at test=pending forever.\n // Set only once — don't re-stamp every patrol.\n if (status.stuckReason !== 'test_signal_strand') {\n setReviewStatusSync(issueId, {\n stuck: true,\n stuckReason: 'test_signal_strand',\n stuckAt: new Date().toISOString(),\n testNotes: `Test stranded at ${status.testStatus} after ${retryCount} re-dispatches — the test agent never signaled and no verdict artifact was recovered. Inspect agent-${issueId.toLowerCase()}-test, or run: pan admin specialists done test ${issueId} --status passed|failed.`,\n });\n const msg = `Surfaced test strand for ${issueId}: stuck after ${retryCount} re-dispatches (test_signal_strand)`;\n actions.push(msg);\n console.warn(`[deacon] ${msg}`);\n }\n continue;\n }\n\n // For pending, only retry if it's been >5 minutes since review passed\n if (status.testStatus === 'pending') {\n const reviewPassedAt = status.history\n ?.filter(h => h.type === 'review' && h.status === 'passed')\n .pop()?.timestamp;\n if (reviewPassedAt && now - new Date(reviewPassedAt).getTime() < 5 * 60 * 1000) {\n continue;\n }\n }\n\n // PAN-1496/PAN-1613: never re-dispatch test for an issue closed on the\n // tracker or shadow-state. The check is reached only by the small set of\n // issues that pass all status filters above, and the shared helper is\n // TTL-cached — so this cannot storm the API.\n if (await isIssueClosed(issueId)) {\n console.log(`[deacon] ${issueId}: skipping test re-dispatch — issue is closed`);\n continue;\n }\n\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n\n const issueLower = issueId.toLowerCase();\n const workAgentId = `agent-${issueLower}`;\n const agentState = getAgentStateSync(workAgentId);\n const workspace = agentState?.workspace || findWorkspacePath(resolved.projectPath, issueLower);\n const branch = `feature/${issueLower}`;\n\n if (!workspace) {\n actions.push(`Skipped test retry for ${issueId}: workspace unavailable`);\n continue;\n }\n\n // PAN-1665: defer at the advancing-role concurrency ceiling; status stays\n // pending/dispatch_failed so a later patrol retries once a slot frees.\n // PAN-2507: first try to yield an idle work agent to free the slot.\n if (!tryReserveAdvancingSlot() && !(await tryYieldForAdvancingDispatch('test', issueId))) {\n actions.push(`Deferred test retry for ${issueId} — advancing-role concurrency ceiling reached`);\n logDeaconEventSync(`checkPendingTestDispatch: deferred test for ${issueId} — advancing ceiling reached (PAN-1665) — ${describeRunningAgents()}`);\n continue;\n }\n const { spawnRun } = await import('../agents.js');\n const { buildTestRolePrompt } = await import('./test-agent-queue.js');\n try {\n const run = await spawnRun(issueId, 'test', {\n workspace,\n prompt: buildTestRolePrompt({ issueId, workspace, branch }),\n startedBy: 'deacon:test-retry',\n });\n setReviewStatusSync(issueId, { testStatus: 'testing', testRetryCount: retryCount + 1 });\n recordDeaconNudge({\n patrol: 'checkPendingTestDispatch',\n issueId,\n action: `dispatched test role ${run.id} (retry ${retryCount + 1})`,\n reason: 'review=passed + test=pending, but the reactive review→test handoff (review.approved → testing) never fired — the deacon had to dispatch test',\n state: {\n reviewStatus: status.reviewStatus,\n testStatus: status.testStatus,\n retryCount,\n reviewedAtCommit: status.reviewedAtCommit,\n lastVerifiedCommit: status.lastVerifiedCommit,\n },\n });\n actions.push(`Dispatched test role ${run.id} for ${issueId} (retry ${retryCount + 1})`);\n console.log(`[deacon] Dispatched test role for ${issueId} (retry ${retryCount + 1})`);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes('already running')) {\n setReviewStatusSync(issueId, { testStatus: 'testing', testRetryCount: retryCount + 1 });\n actions.push(`Test role already running for ${issueId} (retry ${retryCount + 1})`);\n } else {\n setReviewStatusSync(issueId, {\n testStatus: 'dispatch_failed',\n testNotes: msg,\n testRetryCount: retryCount + 1,\n });\n actions.push(`Test role dispatch error for ${issueId}: ${msg}`);\n console.error(`[deacon] Test role dispatch error for ${issueId}:`, msg);\n }\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking pending test dispatch:', msg);\n }\n\n return actions;\n}\n\n// ============================================================================\n// Completed-but-unsignaled test detection (PAN-1681)\n// ============================================================================\n\n/**\n * Detect test agents that finished but never persisted their verdict — the test\n * twin of {@link checkCompletedButUnsignaledReviews}. The test role narrates\n * \"tests pass\" but the agent (often Haiku 4.5) sometimes never POSTs testStatus,\n * stranding the issue at test=pending/testing with a live-but-idle session.\n *\n * Recovery is symmetric with the review failsafe: the test role writes a\n * deterministic verdict artifact (.pan/test/result.json) BEFORE it POSTs, and\n * this patrol reads it back to recover the verdict. It NEVER guesses pass/fail\n * (continue.json D6) — auto-complete fires only from a written artifact; with no\n * artifact it nudges once to prompt write+POST, then defers to the\n * strand-surfacing path in checkPendingTestDispatch.\n *\n * Guards (hazard H4):\n * - Only fires when reviewStatus === 'passed' && testStatus ∈ {testing,pending}\n * - Skips closed issues (isIssueClosed) and stuck/ignored issues\n * - Gates a live session on isAgentIdleForNudge + a 5-min settle window\n * - Nudges at most once per test cycle (deduped by session) before completing\n * - Only honors an artifact newer than the current test dispatch (H3)\n */\nconst unsignaledTestNudges = new Map<string, number>();\n/** PAN-3092: test dispatch generations already escalated — never nudged again. */\nconst unsignaledTestEscalations = new Set<string>();\n\nexport async function checkCompletedButUnsignaledTests(): Promise<string[]> {\n const actions: string[] = [];\n const TEST_SETTLE_MS = 5 * 60 * 1000; // 5 minutes — mirror SYNTHESIS_SETTLE_MS\n const NUDGE_DEDUP_MS = 30 * 60 * 1000;\n\n try {\n const statuses = loadReviewStatuses();\n const now = Date.now();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (status.reviewStatus !== 'passed') continue;\n if (status.testStatus !== 'testing' && status.testStatus !== 'pending') continue;\n if (status.stuck || status.deaconIgnored) continue;\n if (await isIssueClosed(issueId)) continue;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) continue;\n const issueLower = issueId.toLowerCase();\n const wsPath = findWorkspacePath(resolved.projectPath, issueLower);\n if (!wsPath) continue;\n\n const testSession = `agent-${issueLower}-test`;\n const sessionAlive = sessionExistsSync(testSession);\n const paneDead = sessionAlive ? await Effect.runPromise(isPaneDead(testSession)).catch(() => true) : true;\n const sessionLive = sessionAlive && !paneDead;\n const idle = sessionLive ? isAgentIdleForNudge(testSession, TEST_SETTLE_MS, now) : false;\n\n // Only honor an artifact newer than the current test dispatch so a previous\n // cycle's verdict is never read after a re-dispatch (H3). The latest\n // test→'testing' history entry is the dispatch time.\n const lastDispatchAt = status.history\n ?.filter(h => h.type === 'test' && h.status === 'testing')\n .pop()?.timestamp;\n\n const lastNudged = unsignaledTestNudges.get(testSession);\n // PAN-3092: once a dispatch generation has been escalated to the operator,\n // it stays escalated. Without this the 30-minute nudge window reopens and\n // the patrol keeps waking an agent the operator has already been told about.\n const escalationKey = `${testSession}:${lastDispatchAt ?? 'unknown'}`;\n const alreadyNudged = unsignaledTestEscalations.has(escalationKey)\n || !!(lastNudged && now - lastNudged < NUDGE_DEDUP_MS);\n const minMtimeMs = lastDispatchAt ? new Date(lastDispatchAt).getTime() : undefined;\n const artifact = readTestVerdictArtifact(wsPath, minMtimeMs);\n\n const decision = decideUnsignaledTestAction({ sessionLive, idle, alreadyNudged, artifact });\n\n switch (decision.action) {\n case 'auto-complete': {\n const fallbackNote = `Test auto-completed by deacon: ${decision.status} (verdict artifact present, agent ${sessionLive ? 'alive but unresponsive after nudge' : 'dead'})`;\n setReviewStatusSync(issueId, {\n testStatus: decision.status,\n testNotes: decision.notes || fallbackNote, ...(decision.uatStatus ? { uatStatus: decision.uatStatus, uatNotes: decision.uatNotes || decision.notes } : {}),\n });\n const msg = `Auto-completed test for ${issueId}: ${decision.status} (${sessionLive ? 'alive but unresponsive after nudge' : 'dead agent'}, verdict artifact)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n break;\n }\n case 'nudge-verdict': {\n const noteArg =\n decision.status === 'failed'\n ? ` --notes \"${(decision.notes || 'See .pan/test/result.json').replace(/\"/g, \"'\").slice(0, 120)}\"`\n : '';\n const cmd = `pan admin specialists done test ${issueId} --status ${decision.status}${noteArg}${decision.uatStatus ? ` --uat-status ${decision.uatStatus}${decision.uatNotes ? ` --uat-notes \"${decision.uatNotes.replace(/\"/g, \"'\").slice(0, 120)}\"` : ''}` : ''}`;\n const nudge = `Your test verdict (${decision.status}) is already written to .pan/test/result.json. Your ONLY remaining task is to execute this Bash command immediately — do not analyze, do not summarize, do not ask questions, just run it:\\n\\n${cmd}\\n\\nRun this command NOW. Do not write any other response before executing it.`;\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(testSession, nudge);\n unsignaledTestNudges.set(testSession, now);\n const msg = `Nudged ${testSession} to signal ${decision.status} (verdict artifact present)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n } catch (err: any) {\n console.error(`[deacon] Failed to nudge ${testSession}:`, err.message);\n }\n break;\n }\n case 'nudge-write': {\n const nudge = `Your test run for ${issueId} looks finished but no verdict was recorded. Decide the automated-gate verdict and, when UAT was required, its separate verdict. Then do BOTH now: (1) write .pan/test/result.json as {\"status\":\"passed\"|\"failed\",\"notes\":\"<gate evidence>\",\"uatStatus\":\"passed\"|\"failed\",\"uatNotes\":\"<UAT evidence>\"} (omit UAT fields only when UAT was not required), and (2) run: pan admin specialists done test ${issueId} --status passed|failed [--uat-status passed|failed]. Do this immediately — do not summarize or ask questions, just write the file and run the command.`;\n try {\n const { messageAgent } = await import('../agents.js');\n await messageAgent(testSession, nudge);\n unsignaledTestNudges.set(testSession, now);\n const msg = `Nudged ${testSession} to write+signal a test verdict (no artifact yet)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n } catch (err: any) {\n console.error(`[deacon] Failed to nudge ${testSession}:`, err.message);\n }\n break;\n }\n case 'escalate': {\n // PAN-3092 (MIN-858): alive, idle, already nudged, no artifact — the\n // verdict exists only in the pane, if at all, and nothing automatic can\n // reach it. Tell a human once per dispatch generation rather than going\n // quiet for six hours while every surface reports the agent healthy.\n unsignaledTestEscalations.add(escalationKey);\n const msg = await recordUnsignaledTestEscalation(\n wsPath, issueId, testSession, lastDispatchAt ?? 'unknown',\n );\n if (msg) { actions.push(msg); console.warn(`[deacon] ${msg}`); }\n break;\n }\n case 'wait':\n case 'none':\n default:\n break;\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error checking completed-but-unsignaled tests:', msg);\n }\n\n return actions;\n}\n\n// ============================================================================\n// Verification/review contradiction (PAN-796)\n// ============================================================================\n\nexport async function checkVerificationReviewContradiction(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { loadReviewStatuses } = await import('../review-status.js');\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const statuses = loadReviewStatuses();\n\n for (const [issueId, status] of Object.entries(statuses)) {\n if (\n status.verificationStatus === 'passed' &&\n status.reviewStatus === 'reviewing' &&\n status.stuckReason === 'review_infrastructure_failure'\n ) {\n // Snapshot the workspace HEAD so reviewedAtCommit is populated — without\n // it, checkPostReviewCommits can never confirm the review is current and\n // the canSkipTests fast-path in setReviewStatus never fires, leaving the\n // issue jammed at passed-but-stuck forever (PAN-977).\n let reviewedAtCommit;\n try {\n const project = resolveProjectFromIssueSync(issueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`,\n );\n if (existsSync(workspacePath)) {\n const { snapshotWorkspaceHeadsPromise } = await import('../git-utils.js');\n reviewedAtCommit = await snapshotWorkspaceHeadsPromise(issueId, workspacePath);\n }\n }\n } catch { /* non-fatal — leave reviewedAtCommit undefined */ }\n\n // Clearing the stuck marker is mandatory: the bypass *resolves* the\n // review-infra failure, so the issue must no longer be skipped by the\n // deacon's stuck-issue guards or it deadlocks at passed+stuck.\n // PAN-3512: this carries a LIVE head, so dispatch-not-drop lands and re-gates it rather than silently rejecting it against a stale row anchor — intended.\n const { recordReviewVerdict } = await import('./review-verdict-writer.js');\n const bypass = await recordReviewVerdict(issueId, {\n verdict: 'passed',\n notes: 'Review bypassed: verification passed but review infrastructure repeatedly failed.',\n ...(reviewedAtCommit ? { evidenceHead: reviewedAtCommit } : {}),\n extra: { stuck: false, stuckReason: undefined, stuckAt: undefined, stuckDetails: undefined },\n writer: 'infra-bypass',\n });\n const msg = bypass.landed ? `Bypassed review for ${issueId}: verification passed, review infra failed` : `Review bypass for ${issueId} not recorded (${bypass.reason})`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n }\n } catch (error: unknown) {\n console.error('[deacon] Error checking verification/review contradiction:', error);\n }\n return actions;\n}\n\n// ============================================================================\n// Post-review commit detection\n// ============================================================================\n\nexport { checkPostReviewCommits };\n\n// Callback set by the server layer to emit domain events when agents are stopped.\n// Deacon is a library module and does not own the event store directly.\nlet agentStoppedNotifier: ((agentId: string) => void) | null = null;\nlet agentStatusChangedNotifier: ((state: AgentState, previousStatus?: AgentState['status'], hasLiveTmuxSession?: boolean) => void) | null = null;\nconst orphanFailureRecordedForAutoResume = new Set<string>();\n\n/**\n * Register a callback that deacon will call when it detects an orphaned agent\n * and resets it to stopped. The server layer uses this to emit an agent.stopped\n * domain event so the read model and frontend update in real-time.\n */\nexport function setAgentStoppedNotifier(fn: (agentId: string) => void): void {\n agentStoppedNotifier = fn;\n}\n\n/** Register a callback for Deacon-owned AgentState changes that must reach live clients. */\nexport function setAgentStatusChangedNotifier(fn: (state: AgentState, previousStatus?: AgentState['status'], hasLiveTmuxSession?: boolean) => void): void {\n agentStatusChangedNotifier = fn;\n}\n\nfunction notifyAgentStatusChanged(state: AgentState, previousStatus?: AgentState['status'], hasLiveTmuxSession?: boolean): void {\n if (!agentStatusChangedNotifier) return;\n try { agentStatusChangedNotifier(state, previousStatus, hasLiveTmuxSession); } catch { /* non-fatal */ }\n}\n\nfunction notifyAgentStopped(agentId: string): void {\n if (!agentStoppedNotifier) return;\n try { agentStoppedNotifier(agentId); } catch { /* non-fatal */ }\n}\n\nfunction autoResumeNotifierDeps() {\n return {\n notifyAgentStopped,\n notifyAgentStatusChanged,\n };\n}\n\nexport async function handleAgentHeartbeatDeadEvent(agentId: string, context?: string): Promise<string[]> {\n return handleAgentHeartbeatDeadEventWithDeps(agentId, context, autoResumeNotifierDeps());\n}\n\nexport async function recoverOrphanedAgents(context?: string): Promise<string[]> {\n return recoverOrphanedAgentsWithDeps(context, autoResumeNotifierDeps());\n}\n\nexport async function cleanupOrphanedPlanningSessions(): Promise<string[]> {\n return cleanupOrphanedPlanningSessionsWithDeps(autoResumeNotifierDeps());\n}\n\nexport async function handleAgentStoppedEvent(agentId: string, opts = {}): Promise<string | null> {\n return handleAgentStoppedEventWithDeps(agentId, opts, autoResumeNotifierDeps());\n}\n\nexport async function autoResumeStoppedWorkAgents(): Promise<string[]> {\n return autoResumeStoppedWorkAgentsWithDeps(autoResumeNotifierDeps());\n}\n\nexport async function applyBootReconciliationDecision(opts: BootReconciliationApplyOptions = {}): Promise<BootReconciliationApplyResult> {\n return applyBootReconciliationDecisionWithDeps(autoResumeNotifierDeps(), opts);\n}\n\nexport async function reconcileAgentLiveness(): Promise<string[]> {\n if (isDeaconGloballyPaused()) { logDeaconEventSync('reconcileAgentLiveness skipped — deacon globally paused'); return []; }\n return reconcileAgentLivenessWithDeps(autoResumeNotifierDeps());\n}\n// Callback set by the server layer to emit Socket.io merge:ready notifications.\n// Deacon is a library module and does not own the Socket.io instance directly.\nlet mergeReadyNotifier: ((issueId: string) => void) | null = null;\n\n/**\n * Register a callback that deacon will call when it detects an issue stuck in\n * readyForMerge state. The server layer uses this to emit a Socket.io event\n * so the dashboard can alert the user to click MERGE.\n */\nexport function setMergeReadyNotifier(fn: (issueId: string) => void): void {\n mergeReadyNotifier = fn;\n}\n\nexport async function checkReadyForMergeStuck(): Promise<string[]> {\n return checkReadyForMergeStuckWithDeps({\n loadState,\n saveState,\n hasMergeReadyNotifier: () => mergeReadyNotifier !== null,\n notifyMergeReady: (issueId) => {\n if (mergeReadyNotifier) mergeReadyNotifier(issueId);\n },\n });\n}\n\n\n\n\n\n\n\n\nconst testStatusGreenCiReconcileCooldowns = new Map<string, number>();\nconst TEST_STATUS_GREEN_CI_RECONCILE_COOLDOWN_MS = 5 * 60 * 1000;\n\n\n\nexport async function reconcileTestStatusFromGreenCi(): Promise<string[]> {\n const { getCiCheckRunsState, getPullRequestHeadState, isGitHubAppConfigured } = await import('../github-app.js');\n return reconcileTestStatusFromGreenCiWithDeps({\n isGitHubAppConfigured,\n loadReviewStatuses,\n getPullRequestHeadState,\n getCiCheckRunsState,\n setReviewStatusSync,\n cooldowns: testStatusGreenCiReconcileCooldowns,\n cooldownMs: TEST_STATUS_GREEN_CI_RECONCILE_COOLDOWN_MS,\n now: () => Date.now(),\n log: (message) => console.log(`[deacon] ${message}`),\n warn: (message) => console.warn(`[deacon] ${message}`),\n });\n}\n\n\n\n\n// ============================================================================\n// Stale feedback cleanup (PAN-705 flywheel fix)\n// ============================================================================\n\n/**\n * Remove stale CI-failure feedback files from a workspace's .pan/feedback/ dir.\n * These accumulate when the merge-agent retries CI-blocked merges, and cause\n * the work agent to incorrectly believe CI is still failing on resume.\n */\nasync function clearStaleCiFeedback(issueId: string): Promise<void> {\n const { readdir, rm } = await import('fs/promises');\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const projectConfig = resolveProjectFromIssueSync(issueId);\n if (!projectConfig) return;\n\n const repoDir = projectConfig.projectPath;\n if (!repoDir) return;\n\n // Find the workspace directory: workspaces/feature-<issueLower> under the repo\n const issueLower = issueId.toLowerCase();\n const feedbackDir = join(repoDir, 'workspaces', `feature-${issueLower}`, '.pan', 'feedback');\n\n try {\n if (!existsSync(feedbackDir)) return;\n const files = await readdir(feedbackDir);\n for (const file of files) {\n if (file.includes('merge-agent') && file.includes('ci-failure') && file.endsWith('.md')) {\n await rm(join(feedbackDir, file));\n console.log(`[deacon] Cleared stale CI feedback: ${file} for ${issueId}`);\n }\n }\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n console.log(`[deacon] Could not clear stale CI feedback for ${issueId}: ${message}`);\n }\n}\n\n// Track per-issue cooldowns for dead-end recovery to avoid spamming\nconst deadEndCooldowns = new Map<string, number>();\n\n// Minimum time (ms) after status update before dead-end detection intervenes\nconst DEAD_END_STALENESS_MS = 5 * 60 * 1000; // 5 minutes\n// Cooldown between successive dead-end recovery attempts for the same issue\nconst DEAD_END_COOLDOWN_MS = 10 * 60 * 1000; // 10 minutes\n\n/**\n * Detect dead-end agents: review blocked, tests failed, or merge failed (CI)\n * but work agent is idle.\n *\n * This happens when:\n * - Review feedback was delivered with a wrong URL (now fixed, but old feedback persists)\n * - Agent forgot how to resubmit (context compaction lost instructions)\n * - Feedback delivery to tmux failed silently\n * - Merge failed due to CI checks (now handled in checkFailedMergeRetry by routing\n * to the work agent with feedback; dead-end catches cases where the agent is idle)\n *\n * Recovery: re-queue the review via the request-review API endpoint and\n * send the agent a nudge message with the correct resubmit command.\n * For CI-blocked merges: clear the stale merge failure and feedback files.\n */\nexport interface CheckDeadEndAgentsDeps {\n loadReviewStatuses?: typeof loadReviewStatuses;\n sessionExistsSync?: typeof sessionExistsSync;\n getAgentStateSync?: typeof getAgentStateSync;\n getAgentDir?: typeof getAgentDir;\n decideAgentAutonomousRedrive?: typeof decideAgentAutonomousRedrive;\n recordDeadEndNeedsYou?: typeof recordDeadEndNeedsYou;\n spawnWorkAgentThroughAgentsEndpoint?: typeof spawnWorkAgentThroughAgentsEndpoint;\n setReviewStatusSync?: typeof setReviewStatusSync;\n clearAgentTroubledSync?: typeof clearAgentTroubledSync;\n saveAgentStateSync?: typeof saveAgentStateSync;\n now?: () => number;\n}\n\nexport async function checkDeadEndAgents(deps: CheckDeadEndAgentsDeps = {}): Promise<string[]> {\n const actions: string[] = [];\n const readReviewStatuses = deps.loadReviewStatuses ?? loadReviewStatuses;\n const hasSession = deps.sessionExistsSync ?? sessionExistsSync;\n const readAgentState = deps.getAgentStateSync ?? getAgentStateSync;\n const resolveAgentDir = deps.getAgentDir ?? getAgentDir;\n const decideRedrive = deps.decideAgentAutonomousRedrive ?? decideAgentAutonomousRedrive;\n const recordNeedsYou = deps.recordDeadEndNeedsYou ?? recordDeadEndNeedsYou;\n const spawnWorkAgent = deps.spawnWorkAgentThroughAgentsEndpoint ?? spawnWorkAgentThroughAgentsEndpoint;\n const updateReviewStatus = deps.setReviewStatusSync ?? setReviewStatusSync;\n const clearAgentTroubled = deps.clearAgentTroubledSync ?? clearAgentTroubledSync;\n const saveAgentState = deps.saveAgentStateSync ?? saveAgentStateSync;\n\n try {\n const statuses = readReviewStatuses();\n\n const now = deps.now?.() ?? Date.now();\n\n for (const [key, status] of Object.entries(statuses)) {\n // Only act on blocked/failed reviews, failed tests, or CI-blocked merges.\n // 'failed' covers verification gate errors (e.g. JSON parse error in `.pan/spec.vbrief.json`)\n // that prevent the review specialist from running at all.\n const isReviewBlocked = status.reviewStatus === 'blocked' || status.reviewStatus === 'failed';\n const isVerificationFailed = status.verificationStatus === 'failed';\n const isTestFailed = status.testStatus === 'failed';\n const isMergeCiFailed = status.mergeStatus === 'failed' &&\n typeof status.mergeNotes === 'string' &&\n status.mergeNotes.includes('failing required checks');\n if (!isReviewBlocked && !isVerificationFailed && !isTestFailed && !isMergeCiFailed) continue;\n\n // Skip merged/completed issues\n if (status.mergeStatus === 'merged' || status.readyForMerge) continue;\n\n // Check staleness: status must have been set at least 5 min ago\n if (status.updatedAt) {\n const statusAge = now - new Date(status.updatedAt).getTime();\n if (statusAge < DEAD_END_STALENESS_MS) continue;\n }\n\n // Check per-issue cooldown\n const lastRecovery = deadEndCooldowns.get(key);\n if (lastRecovery && (now - lastRecovery) < DEAD_END_COOLDOWN_MS) continue;\n\n // Circuit breaker: don't intervene if already at max requeues\n const autoRequeueCount = status.autoRequeueCount || 0;\n if (autoRequeueCount >= 25) {\n console.log(`[deacon] Dead-end detected for ${key} but circuit breaker active (${autoRequeueCount}/25 requeues used)`);\n const trip = await recordDeadEndNeedsYou(key, 'dead-end-rebuild', status.updatedAt ?? 'rework', `Dead-end recovery needs-you: ${key} exhausted 25 requeues`);\n if (trip) actions.push(trip);\n continue;\n }\n\n // CI-blocked merges have their own retry circuit breaker in checkFailedMergeRetry().\n // Once that counter is saturated, only a new commit should reset the merge path.\n if (isMergeCiFailed && (status.mergeRetryCount || 0) >= FAILED_MERGE_MAX_RETRIES) {\n console.log(`[deacon] Dead-end detected for ${key} but merge retry ceiling is saturated (${status.mergeRetryCount}/${FAILED_MERGE_MAX_RETRIES})`);\n continue;\n }\n\n // The review-status map key is the authoritative issue identifier.\n // Persisted payloads can carry stale/mismatched status.issueId values after\n // retries or manual edits; using the key keeps recovery actions targeted to\n // the actual status entry being processed.\n const issueId = key;\n const agentSessionName = `agent-${issueId.toLowerCase()}`;\n\n // Human-readable status type — computed up front so both the\n // respawn-on-dead-agent path (PAN-2209) and the nudge-on-idle path can use it.\n let statusType: string;\n if (isReviewBlocked) {\n statusType = status.reviewStatus === 'failed' ? 'review failed' : 'review blocked';\n } else if (isVerificationFailed) {\n statusType = status.reviewStatus === 'pending'\n ? 'verification failed while review pending'\n : 'verification failed';\n } else if (isTestFailed) {\n statusType = 'tests failed';\n } else {\n statusType = 'merge CI blocked';\n }\n\n if (!hasSession(agentSessionName)) {\n // PAN-2209: respawn a dead work agent whose pending rework kickoff drains\n // `.pan/feedback` so it addresses the review. Merge-CI dead-ends need no\n // work session and keep their prior no-session behavior (they fall through\n // to `continue`; the merge-CI branch below requires a live session).\n if (isReviewBlocked || isVerificationFailed || isTestFailed) {\n const agentState = readAgentState(agentSessionName);\n const gateDecision = await decideRedrive(agentState ?? {}, resolveAgentDir(agentSessionName), true);\n if (gateDecision.decision === 'defer') {\n console.log(`[deacon] PAN-2209: ${issueId} (${statusType}) re-drive deferred — ${gateDecision.reason}`);\n const pendingOperatorDecision = gateDecision.reason.startsWith('agent is waiting on an operator decision');\n const recoveryPath = pendingOperatorDecision ? 'pending-operator-decision' : 'operator-stopped-rework';\n const needsYouMessage = pendingOperatorDecision\n ? `Dead-end recovery needs-you: ${issueId} ${gateDecision.reason}. Answer it with 'pan answer ${issueId}'.`\n : `Dead-end recovery needs-you: ${issueId} was explicitly stopped before handoff`;\n const trip = gateDecision.needsYou && await recordNeedsYou(\n issueId,\n recoveryPath,\n status.updatedAt ?? statusType,\n needsYouMessage,\n );\n if (trip) actions.push(trip);\n } else {\n if (gateDecision.gateDecision.clearStoppedByUser && agentState) { delete agentState.stoppedByUser; saveAgentState(agentState); }\n deadEndCooldowns.set(key, now); // Engage the shared circuit breaker so a chronically-dying agent\n // cannot be respawned forever (>=25 requeues halts, checked above).\n updateReviewStatus(issueId, { autoRequeueCount: autoRequeueCount + 1 });\n // Clear only the system-set `troubled` gate (resume-failure counter\n // after completion) — the false state that strands the rework, per\n // PAN-2209 evidence. Paused agents were already excluded above.\n clearAgentTroubled(agentSessionName);\n try {\n const spawn = await spawnWorkAgent(issueId);\n if (spawn.spawned) {\n actions.push(`Dead-end recovery (PAN-2209): respawned dead work agent for ${issueId} (${statusType}) to address feedback`);\n console.log(`[deacon] PAN-2209: respawned dead work agent for ${issueId} (${statusType})`);\n } else if (spawn.skippedReason === 'stack-unhealthy') {\n // PAN-2520: the respawn can't proceed because the workspace Docker\n // stack is down. Deferring here loops forever — nothing else rebuilds\n // the stack. Trigger the same rebuild-and-start recovery the dashboard\n // button uses (rebuild stack → `pan start`). The per-issue dead-end\n // cooldown and autoRequeueCount breaker (both set above) bound the\n // retries, so a stack that can't be rebuilt still escalates instead\n // of hammering.\n try {\n const rebuild = await triggerRebuildAndStart(issueId);\n if (rebuild.triggered) {\n actions.push(`Dead-end recovery (PAN-2520): stack-unhealthy for ${issueId} (${statusType}) — triggered rebuild-and-start`);\n console.log(`[deacon] PAN-2520: triggered rebuild-and-start for ${issueId} (stack-unhealthy)`);\n } else {\n actions.push(`Dead-end recovery (PAN-2520): rebuild-and-start for ${issueId} failed — ${rebuild.error}`);\n console.log(`[deacon] PAN-2520: rebuild-and-start for ${issueId} failed: ${rebuild.error}`);\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] PAN-2520: rebuild-and-start failed for ${issueId}: ${msg}`);\n actions.push(`Dead-end recovery (PAN-2520): rebuild-and-start failed for ${issueId}: ${msg}`);\n }\n } else {\n actions.push(`Dead-end recovery (PAN-2209): respawn of ${issueId} deferred — ${spawn.skippedReason ?? spawn.error}`);\n console.log(`[deacon] PAN-2209: respawn of ${issueId} deferred: ${spawn.skippedReason ?? spawn.error}`);\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] PAN-2209: respawn failed for ${issueId}: ${msg}`);\n actions.push(`Dead-end recovery (PAN-2209): respawn failed for ${issueId}: ${msg}`);\n }\n }\n }\n // No live session to nudge — done with this issue this cycle.\n continue;\n }\n\n // Check if agent is idle via Stop hook state (authoritative idle signal)\n if (!isAgentIdleForNudge(agentSessionName)) {\n // Agent is still working or has no hook state — let it finish\n continue;\n }\n\n // Agent is idle with a blocked/failed status — this is a dead end\n console.log(`[deacon] Dead-end detected: ${key} (${statusType}) with idle agent ${agentSessionName}`);\n\n // Record cooldown before taking action\n deadEndCooldowns.set(key, now);\n\n // For merge CI-blocked: clear the stale merge failure, clean up stale\n // CI-failure feedback files, and set readyForMerge so the merge flow can re-enter.\n if (isMergeCiFailed) {\n setReviewStatusSync(issueId, {\n mergeStatus: 'pending',\n readyForMerge: true,\n });\n // Reset CI retry counter so the next CI failure re-enters at attempt 1/5\n // instead of silently dead-ending due to the exhausted retry count.\n ciRetryMap.delete(issueId);\n // Clean up accumulated stale feedback so the work agent doesn't read them\n await clearStaleCiFeedback(issueId).catch(() => {});\n console.log(`[deacon] Cleared stale CI-blocked merge for ${issueId} — reset to readyForMerge`);\n actions.push(`Dead-end recovery: cleared CI-blocked merge for ${issueId} (${statusType}, idle for ${Math.round((now - new Date(status.updatedAt || '').getTime()) / 60000)}m)`);\n continue;\n }\n\n // Resolve latest feedback file for targeted nudge\n let latestFeedbackPath: string | undefined;\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n const wsPath = findWorkspacePath(resolved.projectPath, issueId.toLowerCase());\n if (wsPath) {\n const feedbackDir = join(wsPath, '.pan', 'feedback');\n if (existsSync(feedbackDir)) {\n const files = (await readdir(feedbackDir)).filter(f => f.endsWith('.md')).sort();\n if (files.length > 0) {\n latestFeedbackPath = join(feedbackDir, files[files.length - 1]);\n }\n }\n }\n }\n } catch {\n // ignore resolution errors\n }\n\n // Send the agent a nudge message with the correct resubmit command\n try {\n const feedbackPart = latestFeedbackPath\n ? `\\n\\nMUST READ: ${latestFeedbackPath}\\n\\nUse your Read tool to open this file, read every line, then fix the issues.`\n : '';\n const nudgeMessage = status.reviewStatus === 'failed'\n ? `Review verification failed for ${issueId}.${feedbackPart}\\n\\nCommon cause: merge conflict markers in .pan/spec.vbrief.json — fix by resolving conflicts in that file, then run: pan review request ${issueId} -m \"Fixed verification error\"`\n : isReviewBlocked\n ? `The review agent found issues in your code.${feedbackPart}\\n\\nFix every issue listed, commit all changes, then run: pan review request ${issueId} -m \"Fixed review issues\". If the exec yields, poll the same background terminal until it exits. Require exit code 0 and confirm pan show ${issueId} or pan review pending shows re-entry before declaring success.`\n : isVerificationFailed\n ? `Verification failed for ${issueId} while review is pending.${feedbackPart}\\n\\nFix the failing verification check, commit every change, push your branch, then request a new review with: pan review request ${issueId} -m \"Fixed verification failure\". If the exec yields, poll the same background terminal until it exits. Require exit code 0 and confirm pan show ${issueId} or pan review pending shows re-entry before declaring success.`\n : `Tests failed for your changes.${feedbackPart}\\n\\nFix the failures, commit, then run: pan review request ${issueId} -m \"Fixed test failures\". If the exec yields, poll the same background terminal until it exits. Require exit code 0 and confirm pan show ${issueId} or pan review pending shows re-entry before declaring success.`;\n\n await Effect.runPromise(sendKeys(agentSessionName, nudgeMessage));\n actions.push(`Dead-end recovery: nudged ${agentSessionName} (${statusType}, idle for ${Math.round((now - new Date(status.updatedAt || '').getTime()) / 60000)}m)`);\n console.log(`[deacon] Sent dead-end recovery nudge to ${agentSessionName}`);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error(`[deacon] Failed to send dead-end nudge to ${agentSessionName}:`, msg);\n actions.push(`Dead-end recovery failed for ${agentSessionName}: ${msg}`);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error in dead-end detection:', msg);\n }\n\n return actions;\n}\n\nasync function reconcileAndCheckIfMerged(\n issueId: string,\n cycleCache?: Map<string, boolean>,\n): Promise<boolean> {\n const cacheKey = issueId.toUpperCase();\n const cached = cycleCache?.get(cacheKey);\n if (cached !== undefined) return cached;\n const remember = (result: boolean): boolean => {\n cycleCache?.set(cacheKey, result);\n return result;\n };\n\n const reviewStatus = getReviewStatusSync(issueId);\n if (reviewStatus?.mergeStatus === 'merged') {\n return remember(true);\n }\n\n if (reviewStatus?.prUrl) {\n const prRef = reviewStatus.prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (prRef) {\n try {\n const { getPullRequestState, isGitHubAppConfigured } = await import('../github-app.js');\n if (isGitHubAppConfigured()) {\n const prState = await Effect.runPromise(getPullRequestState(prRef[1], prRef[2], Number.parseInt(prRef[3], 10)));\n if (prState.merged) {\n setReviewStatusSync(issueId, { mergeStatus: 'merged', readyForMerge: false, mergeNotes: undefined });\n // PAN-1027: also run the post-merge handoff so labels get cleaned and the\n // work-agent tmux session is killed. Without this, GitHub-detected merges\n // leave stale \"in-progress\" / \"in-review\" labels and leaked tmux sessions.\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n const branch = `feature/${issueId.toLowerCase()}`;\n const { postMergeLifecycle } = await import('./merge-agent.js');\n postMergeLifecycle(issueId, resolved.projectPath, branch, { skipDeploy: true }).catch(err =>\n console.warn(`[deacon] postMergeLifecycle (gh-reconcile) failed for ${issueId}: ${err}`)\n );\n }\n } catch (err) {\n console.warn(`[deacon] Could not run post-merge handoff for ${issueId}: ${err}`);\n }\n return remember(true);\n }\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[deacon] Failed GitHub merge reconciliation for ${issueId}: ${message}`);\n }\n }\n }\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) {\n return remember(false);\n }\n\n const project = getProjectSync(resolved.projectKey);\n if (!project?.tracker) {\n return remember(false);\n }\n\n try {\n const { createTracker } = await import('../tracker/factory.js');\n const overdeckConfig = await import('../config.js');\n const globalTrackerConfig = overdeckConfig.loadConfigSync().trackers;\n\n let trackerConfig: TrackerConfig | null = null;\n if (project.tracker === 'github' && project.github_repo) {\n const [owner, repo] = project.github_repo.split('/');\n if (!owner || !repo) {\n console.warn(`[deacon] Cannot reconcile ${issueId}: invalid GitHub repo config for ${resolved.projectKey}`);\n return remember(false);\n }\n trackerConfig = {\n type: 'github',\n owner,\n repo,\n tokenEnv: globalTrackerConfig.github?.token_env,\n };\n } else if (project.tracker === 'gitlab' && project.gitlab_repo) {\n trackerConfig = {\n type: 'gitlab',\n projectId: project.gitlab_repo,\n tokenEnv: globalTrackerConfig.gitlab?.token_env,\n };\n } else if (project.tracker === 'linear') {\n trackerConfig = {\n type: 'linear',\n apiKeyEnv: globalTrackerConfig.linear?.api_key_env,\n team: resolved.linearTeam,\n };\n } else if (project.tracker === 'rally') {\n trackerConfig = {\n type: 'rally',\n apiKeyEnv: globalTrackerConfig.rally?.api_key_env,\n server: globalTrackerConfig.rally?.server,\n workspace: globalTrackerConfig.rally?.workspace,\n project: project.rally_project ?? globalTrackerConfig.rally?.project,\n };\n }\n\n if (!trackerConfig) {\n console.warn(`[deacon] Cannot reconcile ${issueId}: incomplete tracker config for ${resolved.projectKey}`);\n return remember(false);\n }\n\n if (project.tracker === 'github') {\n return remember(false);\n }\n\n const tracker = createTracker(trackerConfig);\n await Effect.runPromise(tracker.getIssue(issueId));\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[deacon] Failed tracker merge reconciliation for ${issueId}: ${message}`);\n }\n\n return remember(false);\n}\n\n\n\n\n\n\n\n\n// PAN-650: Bounded poking for stuck agents.\n// Without these limits, the patrol fires every 60s and re-sends the same poke\n// forever, eating tokens overnight. Cap pokes and require cooldown between them;\n// after the cap, transition resolution → 'abandoned' so the agent falls out of\n// the patrol filter and surfaces in the dashboard for human attention.\nconst STUCK_POKE_COOLDOWN_MS = 15 * 60 * 1000; // 15 minutes between pokes\nconst STUCK_POKE_MAX = 3; // Max pokes before abandoning\nconst stuckPokeState: Map<string, { lastPoke: number; pokes: number }> = new Map();\n\n/**\n * Patrol work agent resolution fields (PAN-309).\n *\n * For each running work agent:\n * - resolution === 'done' && count >= 2: auto-complete via pan done\n * - resolution === 'stuck' && count >= 3: send a poke (rate-limited, capped — PAN-650)\n */\nexport async function patrolWorkAgentResolutions(): Promise<string[]> {\n const actions: string[] = [];\n\n try {\n const agents = listRunningAgentsSync();\n // Specialist sessions (global or per-project) use the specialist tmux prefix.\n const isSpecialistSession = (id: string) => id.startsWith('specialist-');\n\n for (const agent of agents) {\n if (!agent.id.startsWith('agent-') || isSpecialistSession(agent.id)) continue;\n\n const runtimeState = getAgentRuntimeStateSync(agent.id);\n if (!runtimeState?.resolution || runtimeState.resolution === 'working' || runtimeState.resolution === 'completed' || runtimeState.resolution === 'abandoned') continue;\n\n const resolution = runtimeState.resolution;\n const count = runtimeState.resolutionCount || 0;\n const slotAgentMatch = /^agent-(.+)-slot-\\d+$/.exec(agent.id);\n const issueId = (agent.issueId || slotAgentMatch?.[1] || agent.id.replace('agent-', '')).toUpperCase();\n\n // PAN-653: Skip workspaces marked stuck — Deacon must not poke/respawn them.\n // Keyed by issueId (not agentId) so respawned agents with new IDs still match.\n const resolutionReviewStatus = getReviewStatusSync(issueId);\n if (resolutionReviewStatus?.stuck) {\n console.log(`[deacon] Skipping stuck workspace ${issueId} in patrolWorkAgentResolutions`);\n continue;\n }\n if (resolutionReviewStatus?.deaconIgnored) {\n console.log(`[deacon] Skipping deacon-ignored workspace ${issueId} in patrolWorkAgentResolutions`);\n continue;\n }\n\n if (resolution === 'done' && count >= 1) {\n if (slotAgentMatch) {\n console.log(`[deacon] Slot ${agent.id} (${issueId}) reported done: notifying its foreman`);\n const { messageAgent } = await import('../agents/messaging.js');\n await messageAgent(`agent-${issueId.toLowerCase()}`, `[swarm-event] ${agent.id} reported done; run pan swarm status ${issueId} --json`, 'deacon:swarm-event');\n saveAgentRuntimeState(agent.id, {\n resolution: 'completed',\n resolutionCount: count + 1,\n resolutionUpdatedAt: new Date().toISOString(),\n });\n actions.push(`Deacon notified foreman of completed slot ${agent.id} (${issueId})`);\n addLog('action', `Notified foreman of completed slot ${agent.id} (${issueId})`, undefined);\n continue;\n }\n\n // PAN-534: lowered from >= 2 to >= 1. A single done signal is sufficient\n // evidence. The old threshold deadlocked when review failed and reset to\n // pending — the agent never re-signaled done, so count stayed at 1 forever.\n console.log(`[deacon] Auto-completing ${agent.id} (${issueId}): resolution=done, count=${count}`);\n\n try {\n // Find pan binary\n const panBin = join(OVERDECK_HOME, 'bin', 'pan');\n const binExists = existsSync(panBin);\n const bin = binExists ? panBin : 'pan';\n\n await execFileAsync(bin, ['work', 'done', issueId, '-c', 'Auto-completed by Deacon: evidence showed work complete after 2 nudges'], {\n timeout: 30000,\n });\n\n // Mark as completed in runtime.json\n saveAgentRuntimeState(agent.id, {\n resolution: 'completed',\n resolutionCount: count + 1,\n resolutionUpdatedAt: new Date().toISOString(),\n });\n\n actions.push(`Deacon auto-completed ${issueId} (${agent.id}) after ${count} failed nudges`);\n addLog('action', `Auto-completed ${issueId}: evidence-complete, ${count} nudges exhausted`, undefined);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] Failed to auto-complete ${agent.id}:`, msg);\n actions.push(`Deacon auto-complete failed for ${agent.id}: ${msg}`);\n }\n\n } else if (resolution === 'stuck' && count >= 3) {\n // Agent is stuck — send a poke to unstick it.\n // Rate-limit to STUCK_POKE_COOLDOWN_MS and cap at STUCK_POKE_MAX (PAN-650).\n const now = Date.now();\n const pokeState = stuckPokeState.get(agent.id) ?? { lastPoke: 0, pokes: 0 };\n\n if (now - pokeState.lastPoke < STUCK_POKE_COOLDOWN_MS) continue;\n\n if (pokeState.pokes >= STUCK_POKE_MAX) {\n // Exhausted poke budget — abandon the agent so it stops being patrolled\n // and surfaces in the dashboard for human intervention.\n console.log(`[deacon] Abandoning stuck agent ${agent.id} (${issueId}) after ${pokeState.pokes} pokes`);\n saveAgentRuntimeState(agent.id, {\n resolution: 'abandoned',\n resolutionCount: count,\n resolutionUpdatedAt: new Date().toISOString(),\n });\n stuckPokeState.delete(agent.id);\n actions.push(`Deacon abandoned stuck agent ${agent.id} (${issueId}) after ${STUCK_POKE_MAX} pokes`);\n addLog('warn', `Abandoned stuck agent ${issueId} after ${STUCK_POKE_MAX} pokes — needs human attention`, undefined);\n continue;\n }\n\n console.log(`[deacon] Poking stuck agent ${agent.id} (${issueId}): poke ${pokeState.pokes + 1}/${STUCK_POKE_MAX}`);\n\n try {\n const pokeMsg = `Deacon health check (${pokeState.pokes + 1}/${STUCK_POKE_MAX}): you appear stuck. Please check your current task status, review any errors, and continue working. If work is complete, run: pan done ${issueId} -c \"Implementation complete\"`;\n await Effect.runPromise(sendKeys(agent.id, pokeMsg));\n stuckPokeState.set(agent.id, { lastPoke: now, pokes: pokeState.pokes + 1 });\n actions.push(`Deacon poked stuck agent ${agent.id} (${issueId}) [${pokeState.pokes + 1}/${STUCK_POKE_MAX}]`);\n addLog('action', `Poked stuck agent ${issueId} (poke ${pokeState.pokes + 1}/${STUCK_POKE_MAX})`, undefined);\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[deacon] Failed to poke ${agent.id}:`, msg);\n }\n }\n }\n } catch (error: unknown) {\n console.error('[deacon] Error in patrolWorkAgentResolutions:', error);\n }\n\n return actions;\n}\n\n// PAN-464: Container restart backoff configuration\nconst CONTAINER_RESTART_BACKOFF_MS = 60_000; // Minimum 60s between restart attempts\nconst CONTAINER_RESTART_MAX_COUNT = 5; // Give up after 5 restarts\nconst CONTAINER_RESTART_WINDOW_MS = 30 * 60_000; // Reset burst count after 30 min of quiet\n\n/**\n * PAN-464: Compute exponential backoff delay for a container given its restart history.\n * Returns delay in ms. Delay doubles each attempt: 60s, 120s, 240s, 480s, max 5 min.\n */\nexport function containerRestartBackoffMs(count: number): number {\n const base = CONTAINER_RESTART_BACKOFF_MS;\n const max = 5 * 60_000; // 5 minutes cap\n return Math.min(base * Math.pow(2, count - 1), max);\n}\n\n/**\n * PAN-464: Kill orphaned host processes (e.g., Vite, node) for a workspace path.\n * Orphaned Vite watchers exhaust inotify handles, causing ENOSPC in containers.\n * Runs before restarting the container so the root cause is cleared.\n *\n * CRITICAL: Must not kill the active work agent's process tree. The agent's tmux\n * pane runs bash+claude with cwd=workspace, so lsof +D returns it. We collect the\n * tmux pane PIDs for agent and planning sessions matching this workspace and exclude\n * them + all descendants from the kill list.\n */\nasync function killOrphanedWorkspaceProcesses(workspacePath: string): Promise<void> {\n try {\n // 1. Collect tmux pane PIDs for agent sessions in this workspace\n const protectedPids = new Set<string>([String(process.pid)]);\n try {\n const sessions = await Effect.runPromise(listSessionNames());\n const agentSessions = sessions.filter(s => s.startsWith('agent-') || s.startsWith('planning-'));\n for (const session of agentSessions) {\n try {\n const pid = (await Effect.runPromise(listPaneValues(session, '#{pane_pid}')))[0]?.trim();\n if (pid && /^\\d+$/.test(pid)) {\n // Add the pane PID and all its descendants to protected list\n protectedPids.add(pid);\n try {\n const { stdout: descendants } = await execAsync(\n `pgrep -P ${pid} 2>/dev/null; ps -o pid= --ppid ${pid} 2>/dev/null | xargs -I{} pgrep -P {} 2>/dev/null`,\n { encoding: 'utf-8', timeout: 3000 },\n );\n for (const d of descendants.trim().split(/\\s+/)) {\n if (d && /^\\d+$/.test(d)) protectedPids.add(d);\n }\n // Also walk the full descendant tree\n const { stdout: allDesc } = await execAsync(\n `pstree -p ${pid} 2>/dev/null | grep -oE '\\\\([0-9]+\\\\)' | tr -d '()' || true`,\n { encoding: 'utf-8', timeout: 3000 },\n );\n for (const d of allDesc.trim().split('\\n')) {\n if (d && /^\\d+$/.test(d.trim())) protectedPids.add(d.trim());\n }\n } catch { /* non-fatal */ }\n }\n } catch { /* non-fatal */ }\n }\n } catch { /* non-fatal */ }\n\n // 2. Find processes with cwd inside the workspace. NEVER select by open\n // files (`lsof +D`): Bun's hardlinked node_modules make the dashboard and\n // every PTY supervisor appear to hold files \"in\" any workspace via mmap'd\n // native addons — that kill list downs the platform (src/lib/process-cwd.ts).\n const pids = await pidsWithCwdUnder(workspacePath);\n\n // 3. Filter out protected PIDs (agent tmux panes and descendants)\n // AND Docker container processes — they have files open via volume mounts\n // but are legitimate; killing them causes container exit → restart loop.\n const { readFile } = await import('fs/promises');\n const isDockerContainerProcess = async (pid: string): Promise<boolean> => {\n try {\n const cgroup = await readFile(`/proc/${pid}/cgroup`, 'utf-8');\n return cgroup.includes('/docker-') || cgroup.includes('/docker/');\n } catch {\n return false;\n }\n };\n const safePids: string[] = [];\n for (const pid of pids) {\n if (protectedPids.has(pid)) continue;\n if (await isDockerContainerProcess(pid)) continue;\n safePids.push(pid);\n }\n\n if (safePids.length > 0) {\n await execAsync(`kill ${safePids.join(' ')} 2>/dev/null || true`, { encoding: 'utf-8', timeout: 5000 });\n console.log(`[deacon] Killed ${safePids.length} orphaned process(es) in ${workspacePath} before container restart (protected ${protectedPids.size - 1} agent PIDs)`);\n }\n } catch {\n // Non-fatal — proceed with restart even if cleanup fails\n }\n}\n\n/**\n * PAN-464: Check Docker container health for active workspaces.\n * Crashed containers (e.g., Vite ENOSPC) break the UAT environment.\n * Auto-restarts them with exponential backoff (60s → 120s → 240s → 5 min cap).\n * Gives up after 5 restarts within 30 minutes to avoid restart loops.\n * Kills orphaned host processes before restarting to fix the inotify root cause.\n */\nexport async function checkWorkspaceContainerHealth(sharedState?: DeaconState): Promise<string[]> {\n const actions: string[] = [];\n try {\n // Find all workspace-related containers that are exited (crashed)\n const { stdout } = await execAsync(\n 'docker ps -a --filter \"status=exited\" --filter \"name=feature-\" --format \"{{.Names}}|{{.Status}}\" 2>/dev/null || true',\n { encoding: 'utf-8', timeout: 10000 },\n );\n const crashed = stdout.trim().split('\\n').filter(Boolean);\n if (crashed.length === 0) return actions;\n\n const state = sharedState ?? loadState();\n if (!state.containerRestarts) state.containerRestarts = {};\n let stateDirty = false;\n\n const now = Date.now();\n\n for (const line of crashed) {\n const [name, status] = line.split('|');\n if (!name) continue;\n\n // Init containers are one-shot by design — they run setup, exit, and stay exited.\n // Restarting them is meaningless and floods agents with bogus \"container crashed\" alerts.\n // Match only the final Compose feature-<issue>-<service>-<index> suffix; an earlier feature segment may be a decoy.\n const match = name.match(/feature-((?:[a-z]+-\\d+)|(?:(?:f|us|de|ta|tc)\\d+))-(frontend|server)-\\d+$/i);\n if (!match) continue;\n\n // Skip clean shutdowns (exit code 0). Status format: \"Exited (N) X minutes ago\".\n // A service container exiting 0 is intentional (e.g., post-merge teardown), not a crash.\n const exitMatch = status?.match(/Exited \\((\\d+)\\)/);\n if (exitMatch && exitMatch[1] === '0') continue;\n\n const issueLower = match[1];\n const containerType = match[2];\n\n // Init containers are one-shot setup jobs — a clean exit (code 0) is not a crash.\n // Restarting them causes an infinite loop: they complete, exit 0, get restarted, repeat.\n if (containerType === 'init') continue;\n\n const agentId = `agent-${issueLower}`;\n\n // Only restart if the agent is active (has a tmux session)\n const agentRunning = await Effect.runPromise(sessionExists(agentId));\n if (!agentRunning) {\n // Agent not running — skip restart\n continue;\n }\n\n // PAN-464: Backoff / give-up logic\n const record = state.containerRestarts[name];\n if (record) {\n const windowStart = now - CONTAINER_RESTART_WINDOW_MS;\n const firstRestartMs = new Date(record.firstRestart).getTime();\n\n // Reset burst counter if the last restart was > 30 min ago (container ran stably for a while)\n if (firstRestartMs < windowStart) {\n delete state.containerRestarts[name];\n stateDirty = true;\n } else {\n // Still within the burst window\n if (record.gaveUp) {\n console.log(`[deacon] Container ${name} exceeded max restarts — skipping (gave up)`);\n continue;\n }\n // Check max count BEFORE backoff — if we've hit the limit, give up regardless of timing\n if (record.count >= CONTAINER_RESTART_MAX_COUNT) {\n record.gaveUp = true;\n stateDirty = true;\n const msg = `[deacon] Container ${name} exceeded max restarts (${CONTAINER_RESTART_MAX_COUNT}) — giving up`;\n console.warn(msg);\n actions.push(msg);\n // PAN-464: Alert agent that the container gave up — manual intervention required\n try {\n await Effect.runPromise(sendKeys(\n agentId,\n `⚠️ Deacon alert: container \"${name}\" has crashed ${CONTAINER_RESTART_MAX_COUNT} times and auto-restart gave up. The UAT environment at feature-${issueLower}.overdeck.localhost may be broken. Manual intervention required — check docker logs or re-containerize.`,\n 'deacon:container-gave-up',\n ));\n } catch {\n // Agent may not be interactive (e.g., waiting for input) — non-fatal\n }\n continue;\n }\n const backoffMs = containerRestartBackoffMs(record.count);\n const msSinceLast = now - new Date(record.lastRestart).getTime();\n if (msSinceLast < backoffMs) {\n console.log(`[deacon] Container ${name} in backoff (${Math.round((backoffMs - msSinceLast) / 1000)}s remaining)`);\n continue;\n }\n }\n }\n\n // Kill orphaned host processes (Vite, node) before restarting to fix inotify root cause\n try {\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const issueUpper = issueLower.toUpperCase();\n const resolved = resolveProjectFromIssueSync(issueUpper);\n if (resolved) {\n const workspacePath = `${resolved.projectPath}/workspaces/feature-${issueLower}`;\n await killOrphanedWorkspaceProcesses(workspacePath);\n }\n } catch {\n // Project not resolvable — skip orphan cleanup, still attempt restart\n }\n\n // Restart the container\n try {\n await execAsync(`docker restart ${name}`, { encoding: 'utf-8', timeout: 30000 });\n const existing = state.containerRestarts[name];\n state.containerRestarts[name] = {\n count: (existing?.count ?? 0) + 1,\n firstRestart: existing?.firstRestart ?? new Date().toISOString(),\n lastRestart: new Date().toISOString(),\n };\n stateDirty = true;\n const count = state.containerRestarts[name].count;\n const msg = `[deacon] Auto-restarted crashed container ${name} (attempt ${count}/${CONTAINER_RESTART_MAX_COUNT})`;\n console.log(msg);\n actions.push(msg);\n // PAN-464: Alert agent that its container crashed and was restarted\n try {\n await Effect.runPromise(sendKeys(\n agentId,\n `ℹ️ Deacon: container \"${name}\" crashed and was auto-restarted (attempt ${count}/${CONTAINER_RESTART_MAX_COUNT}). The UAT environment should recover in ~30s. No action needed unless this keeps happening.`,\n 'deacon:container-restarted',\n ));\n } catch {\n // Agent may not be interactive — non-fatal\n }\n } catch (restartErr: any) {\n console.warn(`[deacon] Failed to restart ${name}: ${restartErr.message}`);\n // PAN-464: Alert agent that restart failed\n try {\n await Effect.runPromise(sendKeys(\n agentId,\n `⚠️ Deacon alert: container \"${name}\" crashed and restart failed (${(restartErr as Error).message}). The UAT environment at feature-${issueLower}.overdeck.localhost is likely broken.`,\n 'deacon:container-restart-failed',\n ));\n } catch {\n // Non-fatal\n }\n }\n }\n\n // When called with sharedState, the caller is responsible for persisting.\n // Saving here would race with runPatrol's later saveState() and clobber records.\n if (stateDirty && !sharedState) saveState(state);\n } catch {\n // Docker not available or other error — skip silently\n }\n return actions;\n}\n\n/**\n * PAN-1726: Defense-in-depth reaper for the WORK session of a merged issue.\n *\n * `postMergeLifecycle` pauses + kills `agent-<id>` at merge time, but a server\n * restart mid-lifecycle (the PAN-1723 deploy re-runs the lifecycle from\n * pending-post-merge.json) or a deacon read-modify-write race on state.json can\n * leave the work session alive with `paused: null`. An idle merged work agent\n * sits at its prompt yet still counts against the PAN-1665 work ceiling,\n * throttling test/work dispatch for every live issue — the work-role sibling of\n * the PAN-1716 advancing reaper. We re-pause (so auto-resume can't resurrect it)\n * THEN kill the session; state.json is preserved so reopen still works.\n */\nexport async function checkMergedWorkSessions(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { selectMergedWorkSessions } = await import('./reap-terminal-sessions.js');\n const { setAgentPaused } = await import('../agents.js');\n const statuses = loadReviewStatuses();\n const aliveSessions = await Effect.runPromise(listSessionNames());\n const toKill = selectMergedWorkSessions(statuses, [...aliveSessions]);\n for (const session of toKill) {\n try {\n // The work session name is the agent id (`agent-<id>`). Re-assert the\n // pause gate before killing so the next patrol's auto-resume won't bring\n // it back; killing alone would just churn it stopped→running forever.\n await Effect.runPromise(setAgentPaused(session, 'merged — awaiting close-out (verify on main)', true));\n await Effect.runPromise(killSession(session));\n actions.push(`Reaped merged work session ${session} (issue merged, session idle)`);\n console.log(`[deacon] Reaped merged work session ${session} (PAN-1726)`);\n logDeaconEventSync(`checkMergedWorkSessions: paused + reaped ${session} — issue merged but work session alive (PAN-1726)`);\n } catch (err) {\n console.warn(`[deacon] Failed to reap merged work session ${session}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error reaping merged work sessions:', msg);\n }\n return actions;\n}\n\n/** Pane must be idle at least this long before the awaiting-test reaper kills it. */\nconst AWAITING_TEST_IDLE_REAP_MS = 10 * 60 * 1000;\n\n/**\n * PAN-1730: Reap the WORK session of an issue that's idle awaiting its test\n * verdict (review passed, test pending).\n *\n * Such a work agent has handed off via `pan done` and sits idle at its prompt,\n * yet `countRunningAgents()` keeps counting it against the PAN-1665 work\n * ceiling. When the work pool alone meets the total ceiling (work=7/9 observed)\n * `tryReserveAdvancingSlot()` can never admit the test that would release these\n * agents — a livelock that zeroed pipeline throughput for hours. Killing the\n * idle work session frees the work slot (and RAM).\n *\n * Unlike the PAN-1726 merged reaper this does NOT pause the agent: if the test\n * later FAILS, the deacon's auto-resume `needsFix` gate must be free to bring it\n * back to fix the feedback. While test stays `pending` the auto-resume\n * \"pipeline mid-flight\" gate already prevents churn, so killing-without-pausing\n * neither resurrects a still-pending agent nor strands a failed one. Runs before\n * the dispatchers so the freed slot benefits this same cycle's test dispatch.\n */\nexport async function checkAwaitingTestWorkSessions(): Promise<string[]> {\n const actions: string[] = [];\n try {\n const { selectAwaitingTestWorkSessions } = await import('./reap-terminal-sessions.js');\n const statuses = loadReviewStatuses();\n const aliveSessions = await Effect.runPromise(listSessionNames());\n const candidates = selectAwaitingTestWorkSessions(statuses, [...aliveSessions]);\n const now = Date.now();\n for (const session of candidates) {\n // Only reap a genuinely idle pane — a work agent still finalizing right\n // after handoff must be left alone. The work session name IS the agent id.\n const runtime = getAgentRuntimeStateSync(session);\n if (runtime?.state !== 'idle') continue;\n const idleSince = Date.parse(runtime.lastActivity ?? '');\n if (!Number.isFinite(idleSince) || now - idleSince < AWAITING_TEST_IDLE_REAP_MS) continue;\n try {\n // No pause: auto-resume's mid-flight gate keeps it down while test is\n // pending, and its needsFix gate must stay free to resume it on failure.\n await Effect.runPromise(killSession(session));\n actions.push(`Reaped idle awaiting-test work session ${session} (review passed, test pending, idle ≥10m)`);\n console.log(`[deacon] Reaped idle awaiting-test work session ${session} (PAN-1730)`);\n logDeaconEventSync(`checkAwaitingTestWorkSessions: reaped ${session} — review passed, test pending, idle ≥10m (PAN-1730)`);\n } catch (err) {\n console.warn(`[deacon] Failed to reap awaiting-test work session ${session}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error reaping awaiting-test work sessions:', msg);\n }\n return actions;\n}\n\nexport async function runPatrol(): Promise<PatrolResult> {\n const state = loadState();\n state.patrolCycle++;\n refreshPatrolHeartbeat(state);\n\n // PAN-378: Global specialists removed. All work done by per-project ephemeral specialists.\n const results: HealthCheckResult[] = [];\n const actions: string[] = [];\n\n // Global pause: skip the entire cycle when the operator has frozen Deacon.\n // Persisted in app_settings; survives restarts. Per-cycle check so flipping\n // the toggle at runtime takes effect on the next tick without restarting.\n if (isDeaconGloballyPaused()) {\n refreshPatrolHeartbeat(state);\n if (!hasLoggedGlobalPauseSkip) {\n console.log(`[deacon] Patrol cycle ${state.patrolCycle} SKIPPED — globally paused`);\n addLog('info', `Patrol cycle ${state.patrolCycle} skipped — deacon globally paused`, state.patrolCycle);\n hasLoggedGlobalPauseSkip = true;\n }\n const skipped: PatrolResult = {\n cycle: state.patrolCycle,\n timestamp: state.lastPatrol!,\n specialists: [],\n actionsToken: ['skipped: globally_paused'],\n massDeathDetected: false,\n };\n lastPatrolResult = skipped;\n saveLastPatrolResult(state, skipped);\n return skipped;\n }\n\n // PAN-1665: reset the per-patrol advancing-dispatch budget. Every review/test/\n // ship re-dispatch below reserves a slot via tryReserveAdvancingSlot() so the\n // patrol's combined spawns stay under the concurrency ceiling.\n resetPatrolDispatchBudget();\n\n const patrolHeartbeatTicker = startPatrolHeartbeatTicker(state);\n try {\n hasLoggedGlobalPauseSkip = false;\n addLog('info', `Patrol cycle ${state.patrolCycle} — checking per-project specialists`, state.patrolCycle);\n console.log(`[deacon] Patrol cycle ${state.patrolCycle} - checking per-project specialists`);\n\n for (const a of await (await import('./stale-task-claims.js')).patrolStaleTaskClaims()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n const stuckRemediationActions = await checkStuckAgentRemediation();\n actions.push(...stuckRemediationActions);\n for (const a of stuckRemediationActions) addLog('action', a, state.patrolCycle);\n\n // PAN-3485: the stall sweeper observes the parked population and records\n // recommendations. It never un-parks work; operator gates remain untouched.\n try {\n const sweepRecommendations = await (await import('./stall-sweeper.js')).runStallSweeperPatrol();\n actions.push(...sweepRecommendations);\n for (const recommendation of sweepRecommendations) addLog('info', recommendation, state.patrolCycle);\n } catch (err: any) {\n console.warn(`[deacon] stall sweeper patrol failed: ${err?.message ?? err}`);\n addLog('warn', `Stall sweeper patrol failed: ${err?.message ?? err}`, state.patrolCycle);\n }\n\n const { reconcileInFlightJournals } = await import('./advancing-selfheal.js');\n const reconciledJournalActions = await reconcileInFlightJournals();\n actions.push(...reconciledJournalActions);\n for (const a of reconciledJournalActions) addLog('action', a, state.patrolCycle);\n for (const a of (await import('./feedback-stuck-retirement.js')).retireResolvedFeedbackDeliveryStuckFlags()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n // Process any pending post-merge lifecycle that wasn't consumed on startup (PAN-626).\n // In dev mode, the deploy script may fail to restart cleanly, leaving the pending file.\n try {\n const pendingFile = join(OVERDECK_HOME, 'pending-post-merge.json');\n const { processPendingLifecycleForPatrol } = await import('./deacon-pending-lifecycle.js');\n const action = await processPendingLifecycleForPatrol(pendingFile);\n if (action) actions.push(action);\n } catch (err: any) {\n console.warn(`[deacon] Failed to process pending lifecycle: ${err.message}`);\n }\n if (state.patrolCycle % 5 === 0) await (await import('./deploy-patrol.js')).runScheduledDeployPatrol();\n /* PAN-378: Global specialist patrol removed. All specialist work now goes through\n * per-project ephemeral specialists via spawnEphemeralSpecialist(). The global\n * merge-agent, review-agent, and test-agent singletons are no longer used.\n * The patrol below handles per-project ephemeral specialist cleanup. */\n\n // PAN-378: Global specialist patrol removed. All specialist work is handled by\n // per-project ephemeral specialists via spawnEphemeralSpecialist().\n // Per-project ephemeral specialist patrol is below (dead session + stuck detection).\n\n // PAN-1908: primary liveness recovery is now reactive (agent.stopped /\n // agent.heartbeat_dead events handled by the Cloister domain-event scheduler).\n // Keep a thin table-query safety net on the patrol for dropped events.\n const livenessActions = await reconcileAgentLiveness();\n actions.push(...livenessActions);\n for (const a of livenessActions) addLog('action', a, state.patrolCycle);\n\n for (const a of await reconcileOrphanProposedSpecs()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n for (const a of await reconcilePendingPromotions()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n\n const closedIssueAgentActions = await reconcileClosedIssueAgents();\n actions.push(...closedIssueAgentActions);\n for (const a of closedIssueAgentActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1882: strikes bypass close-out, so their merged `strike/<id>` worktrees\n // + branches pile up forever. Reap any whose branch is fully merged into\n // origin/main with no live strike session (never touches feature/* or unmerged work).\n const strikeWorkspaceActions = await reapMergedStrikeWorkspaces();\n actions.push(...strikeWorkspaceActions);\n for (const a of strikeWorkspaceActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1817: stop the server+frontend UI containers of workspaces whose agent\n // has been idle (no agent, no tmux) past the grace window. Light-touch and\n // reversible — never touches the agent (host tmux), worktree, or branch.\n const idleStackActions = await reconcileIdleWorkspaceStacks();\n actions.push(...idleStackActions);\n for (const a of idleStackActions) addLog('action', a, state.patrolCycle);\n\n // PAN-3053: read-only Docker bridge-pool pressure signal; emits on transitions only.\n for (const a of await (await import('./bridge-pool-patrol.js')).patrolDockerBridgePool()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n\n // Re-send the resume continue prompt when a work agent is alive and idle after\n // resume but no user record landed in the JSONL transcript.\n const stalledResumeActions = await nudgeStalledResumeWorkAgents();\n actions.push(...stalledResumeActions);\n for (const a of stalledResumeActions) addLog('action', a, state.patrolCycle);\n\n const kickoffRedeliveryActions = await redeliverUndeliveredKickoffs();\n actions.push(...kickoffRedeliveryActions);\n for (const a of kickoffRedeliveryActions) addLog('action', a, state.patrolCycle);\n\n // Nudge work agents that are alive-but-idle with open beads remaining.\n // Catches the gap autoResume misses: tmux alive, status='running', Stop\n // hook fired (idle), but no advance to the next bead (gpt-5.5 checkpoint\n // pattern, prompt mis-interpretation, etc.).\n const beadNudgeActions = await nudgeIdleWorkAgentsWithOpenBeads();\n actions.push(...beadNudgeActions);\n for (const a of beadNudgeActions) addLog('action', a, state.patrolCycle);\n\n // Detect \"Invalid signature in thinking block\" in active agent output (PAN-612).\n // Context compaction corrupts thinking-block signatures; the session cannot be\n // resumed. Must run before idle-suspend so we recover corrupted agents first.\n const signatureCorruptionActions = await checkThinkingSignatureCorruption();\n actions.push(...signatureCorruptionActions);\n for (const a of signatureCorruptionActions) addLog('action', a, state.patrolCycle);\n\n // Check and auto-suspend idle agents (PAN-80, fixed in PAN-154)\n const suspendActions = await checkAndSuspendIdleAgents();\n actions.push(...suspendActions);\n for (const a of suspendActions) addLog('action', a, state.patrolCycle);\n\n // Clear readyForMerge for issues whose workspace no longer exists.\n // Prevents MERGE button showing for issues that can't actually merge.\n try {\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const allStatuses = loadReviewStatuses();\n for (const [issueId, status] of Object.entries(allStatuses)) {\n if (!status.readyForMerge || status.mergeStatus === 'merged') continue;\n const project = resolveProjectFromIssueSync(issueId);\n if (!project) continue;\n const wsPath = join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(wsPath)) {\n setReviewStatusSync(issueId, { readyForMerge: false, mergeStatus: 'failed', mergeNotes: 'Workspace does not exist' });\n const msg = `Cleared readyForMerge for ${issueId} (workspace deleted)`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n }\n }\n } catch (err: any) {\n console.warn(`[deacon] Failed to check workspace existence: ${err.message}`);\n }\n\n // PAN-2579 (warm-by-default lifecycle): the PAN-1716 terminal-advancing reaper\n // and the PAN-2341 idle-terminal reaper are GONE from this patrol. A completed\n // review/test/ship session stays warm so the next cycle resumes it with context\n // intact; it no longer counts against the advancing ceiling (countRunningAgents\n // excludes warm-idle sessions) and the memory governor sheds it first under\n // HARD pressure. Only sessions of MERGED issues (past close-out) are reaped.\n const { checkMergedAdvancingSessions } = await import('./advancing-selfheal.js');\n\n // PAN-1726: Reap the work session of an already-merged issue. An idle merged\n // work agent holds a PAN-1665 work slot; re-pause + kill it so the slot frees\n // for live work. (Merged = past close-out, so the warm policy does not apply.)\n const reapedWorkActions = await checkMergedWorkSessions();\n actions.push(...reapedWorkActions);\n for (const a of reapedWorkActions) addLog('action', a, state.patrolCycle);\n\n const reapedMergedAdvancingActions = await checkMergedAdvancingSessions();\n actions.push(...reapedMergedAdvancingActions);\n for (const a of reapedMergedAdvancingActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1778: proactively refresh Claude credentials on active remote agents\n // when the host credentials file changes, with a 15 min fallback cadence.\n // Runs before the reactive 401 heal/reap path so long remote runs get fresh\n // OAuth tokens before they stall. Zero active remote agents is a local state\n // scan only — no Fly provider construction and no Fly API calls.\n try {\n const { refreshClaudeCredentialsForActiveRemoteAgents } = await import('../remote/remote-completion.js');\n const remoteCredentialActions = await refreshClaudeCredentialsForActiveRemoteAgents();\n actions.push(...remoteCredentialActions);\n for (const a of remoteCredentialActions) addLog(a.includes('failed') ? 'warn' : 'action', a, state.patrolCycle);\n } catch (err: any) {\n addLog('warn', `Remote credential refresh patrol failed: ${err.message}`, state.patrolCycle);\n }\n\n // PAN-1845: keep ephemeral-tier remote VMs from running forever when the host\n // (or deacon) goes away. The patrol writes a freshness file; a VM-side\n // watchdog self-stops the machine when the heartbeat goes stale.\n try {\n const { refreshHostHeartbeatForEphemeralVms } = await import('../remote/remote-agents.js');\n const heartbeatActions = await refreshHostHeartbeatForEphemeralVms();\n actions.push(...heartbeatActions);\n for (const a of heartbeatActions) addLog(a.includes('failed') ? 'warn' : 'action', a, state.patrolCycle);\n } catch (err: any) {\n addLog('warn', `Remote heartbeat refresh patrol failed: ${err.message}`, state.patrolCycle);\n }\n\n // PAN-1676: hand completed remote (fly.io) agents to the review pipeline.\n // Cheap no-op when no remote-state.json is active (local file scan only —\n // no fly API calls); otherwise checks each VM for the REMOTE_DONE sentinel,\n // materializes the local worktree, creates review artifacts + the completed\n // marker, and destroys the machine.\n try {\n const { reapCompletedRemoteAgents } = await import('../remote/remote-completion.js');\n const remoteReap = await reapCompletedRemoteAgents();\n for (const r of remoteReap) {\n if (r.status === 'handed-off' || r.status === 'error') {\n const msg = `Remote reap ${r.issueId}: ${r.status} (${r.details.join('; ')})`;\n actions.push(msg);\n addLog(r.status === 'error' ? 'warn' : 'action', msg, state.patrolCycle);\n }\n }\n } catch (err: any) {\n addLog('warn', `Remote reap patrol failed: ${err.message}`, state.patrolCycle);\n }\n\n // PAN-1730: Reap the work session of an issue idle awaiting its test verdict\n // (review passed, test pending, pane idle ≥10m). When the work pool alone\n // meets the PAN-1665 total ceiling, these idle agents livelock test dispatch —\n // freeing the slot here lets this same cycle's checkPendingTestDispatch admit\n // the test that releases them. Kill-without-pause; see the function comment.\n const reapedAwaitingTestActions = await checkAwaitingTestWorkSessions();\n actions.push(...reapedAwaitingTestActions);\n for (const a of reapedAwaitingTestActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1908: primary review-status orphan recovery is now reactive\n // (review.coordinator.died / work.completed events). The patrol keeps a thin\n // SQLite-only safety net for dropped events.\n const orphanActions = await checkOrphanedReviewStatuses();\n actions.push(...orphanActions);\n for (const a of orphanActions) addLog('action', a, state.patrolCycle);\n\n // Bound per-bead inspect sessions so work agents never wait forever for a verdict.\n const inspectTimeoutActions = await checkInspectAgentTimeouts();\n actions.push(...inspectTimeoutActions);\n for (const a of inspectTimeoutActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1559: reap untracked inspect tmux sessions. Inspect agents now write\n // state.json at spawn, but this safety net kills older leaked sessions and\n // any future dead inspect panes before they burn compute indefinitely.\n const inspectReaperActions = await cleanupOrphanedInspectSessions();\n actions.push(...inspectReaperActions);\n for (const a of inspectReaperActions) addLog('action', a, state.patrolCycle);\n\n // Detect new commits pushed after review passed before any test/merge path can\n // act on stale review approval.\n const postReviewActions = await checkPostReviewCommits();\n actions.push(...postReviewActions);\n for (const a of postReviewActions) addLog('action', a, state.patrolCycle);\n\n const stalledConvoyActions = await recoverStalledReviewConvoys();\n actions.push(...stalledConvoyActions);\n for (const a of stalledConvoyActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1908: primary missing-status creation is now reactive (work.completed\n // event). The patrol keeps a thin agents-table safety net for dropped events.\n const missingStatusActions = await checkMissingReviewStatuses();\n actions.push(...missingStatusActions);\n for (const a of missingStatusActions) addLog('action', a, state.patrolCycle);\n\n // PAN-2207: recover issues where `pan done` failed mid-flight (PR open + review\n // never dispatched). Runs after missing-status so we don't race reactive status\n // creation, but before test/merge recovery so the review pipeline can proceed.\n const orphanCompletionActions = await checkOrphanedCompletions();\n actions.push(...orphanCompletionActions);\n for (const a of orphanCompletionActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1681: Recover test agents that wrote .pan/test/result.json but never\n // POSTed testStatus (nudge once → auto-complete). Runs BEFORE the dispatcher\n // below so a recoverable verdict is honored before any re-dispatch or stuck\n // marker — the dispatcher's stuck path skips issues this already resolved.\n const unsignaledTestActions = await checkCompletedButUnsignaledTests();\n actions.push(...unsignaledTestActions);\n for (const a of unsignaledTestActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1658: after a rebase, reviewStatus may already be passed while testStatus\n // remains pending. Run before the dispatcher so green GitHub Actions CI on the\n // current PR HEAD can clear stale pending state instead of spawning a new test.\n const greenCiTestStatusActions = await reconcileTestStatusFromGreenCi();\n actions.push(...greenCiTestStatusActions);\n for (const a of greenCiTestStatusActions) addLog('action', a, state.patrolCycle);\n\n // Retry test-agent dispatch for issues where review passed but test never started (PAN-699)\n const pendingTestActions = await checkPendingTestDispatch();\n actions.push(...pendingTestActions);\n for (const a of pendingTestActions) addLog('action', a, state.patrolCycle);\n\n // Reset issues stuck in 'reviewing' with no active review session (PAN-733)\n const stuckReviewActions = await checkStuckReviewing();\n actions.push(...stuckReviewActions);\n for (const a of stuckReviewActions) addLog('action', a, state.patrolCycle);\n\n // Detect review specialists that wrote synthesis.md but never signaled completion\n const unsignaledReviewActions = await checkCompletedButUnsignaledReviews();\n actions.push(...unsignaledReviewActions);\n for (const a of unsignaledReviewActions) addLog('action', a, state.patrolCycle);\n\n // Repair verdicts that reached disk after their review status was reset to pending.\n const unappliedReviewActions = await reconcileUnappliedReviewVerdicts();\n actions.push(...unappliedReviewActions);\n for (const a of unappliedReviewActions) addLog('action', a, state.patrolCycle);\n\n // PAN-3092: fold verdict fallbacks whose writing process died before its own\n // drain timer fired, and surface the ones the record lock keeps stuck.\n // Budget derived from the RESOLVED patrol interval, not a hardcoded 60s\n // assumption: a shorter configured interval would otherwise let the sweep\n // overrun it and start overlapping patrols.\n const verdictFallbackActions = await sweepStrandedVerdictFallbacks(\n Date.now(),\n Math.max(1_000, Math.floor(config.patrolIntervalMs / 2)),\n );\n actions.push(...verdictFallbackActions);\n for (const a of verdictFallbackActions) addLog('action', a, state.patrolCycle);\n\n // PAN-796: Bypass review for issues where verification passed but review infra keeps failing\n const verifContradictionActions = await checkVerificationReviewContradiction();\n actions.push(...verifContradictionActions);\n for (const a of verifContradictionActions) addLog('action', a, state.patrolCycle);\n\n // Kill orphaned planning sessions whose issue has already progressed past planning.\n // PAN-682 pattern: `planning-pan-<id>` tmux session survives hours after `complete-planning`\n // because either (a) `skipKill=true` was set or (b) complete-planning was never invoked\n // (work agent was started via a different path). If the corresponding work agent session\n // `agent-pan-<id>` is alive, planning is definitively over — kill the planning session.\n const planningCleanupActions = await cleanupOrphanedPlanningSessions();\n actions.push(...planningCleanupActions);\n for (const a of planningCleanupActions) addLog('action', a, state.patrolCycle);\n\n // The review-parent patrol is observability-only and checks the synthesis artifact\n // before the runtime row, so a completed parent is never mistaken for a stalled one.\n const stalledParentActions = await checkStalledReviewParents();\n actions.push(...stalledParentActions);\n for (const a of stalledParentActions) addLog('action', a, state.patrolCycle);\n\n const reviewerMonitorActions = await monitorReviewConvoySignals();\n actions.push(...reviewerMonitorActions);\n for (const a of reviewerMonitorActions) addLog('action', a, state.patrolCycle);\n\n // Kill orphaned review sessions whose work agent is no longer running.\n // Review sessions are named review-<issueId>-<timestamp>-<role> and are\n // never killed by teardown because the exact-match pattern doesn't catch them.\n const reviewCleanupActions = await cleanupOrphanedReviewSessions();\n actions.push(...reviewCleanupActions);\n for (const a of reviewCleanupActions) addLog('action', a, state.patrolCycle);\n\n // PAN-464: Check workspace Docker container health and auto-restart crashed containers\n const containerActions = await checkWorkspaceContainerHealth(state);\n actions.push(...containerActions);\n for (const a of containerActions) addLog('action', a, state.patrolCycle);\n\n // Dead-end and first-completion nudges DISABLED — too flaky, risk of\n // draining AI token credits by sending unnecessary prompts to agents.\n // If an agent is stuck, the human operator can nudge it manually via the\n // dashboard's Tell action.\n\n // Safety-net: trigger merge for issues stuck in readyForMerge state (PAN-344)\n const mergeStuckActions = await checkReadyForMergeStuck();\n actions.push(...mergeStuckActions);\n for (const a of mergeStuckActions) addLog('action', a, state.patrolCycle);\n\n // Auto-retry merges that failed due to transient post-rebase verification failures\n const failedMergeRetryActions = await checkFailedMergeRetry();\n actions.push(...failedMergeRetryActions);\n for (const a of failedMergeRetryActions) addLog('action', a, state.patrolCycle);\n for (const a of await patrolStrikeLandings()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n // PAN-2203: deterministic swarm coordination. This pass derives active\n // swarms from workspaces + xBRIEF readiness; later beads fill in merge,\n // dispatch, recovery, and cooldown behavior.\n const swarmActions = await swarmJanitorPass();\n actions.push(...swarmActions);\n for (const a of swarmActions) addLog('action', a, state.patrolCycle);\n\n // Reconcile stale merges and bound merging/verifying states that never completed.\n const staleMergeActions = await reconcileStaleMergeStatus();\n staleMergeActions.push(...await reconcileStuckMergingStates());\n actions.push(...staleMergeActions); for (const a of staleMergeActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1027 reverse: detect mergeStatus=merged issues whose GitHub PR is not merged\n // (closed-without-merge, reopened after revert, or false positive from squash detection).\n // Without this, those issues get stuck because mergeStatus blocks all pipeline gates.\n const falseMergedActions = await reconcileFalseMerged();\n actions.push(...falseMergedActions);\n for (const a of falseMergedActions) addLog('action', a, state.patrolCycle);\n\n // PAN-1028: detect merge_status=merged but review_status non-terminal (coordinator\n // crashed mid-run, dashboard missed the transition). Reconcile to review_status=passed\n // so the dashboard stops showing \"running reviewers with no data.\"\n const mergedReviewingActions = await reconcileMergedButReviewing();\n actions.push(...mergedReviewingActions);\n for (const a of mergedReviewingActions) addLog('action', a, state.patrolCycle);\n\n const autoCloseOutActions = await autoCloseOut();\n actions.push(...autoCloseOutActions);\n for (const a of autoCloseOutActions) addLog('action', a, state.patrolCycle);\n\n // Closed-PR readyForMerge reconciler: stops the \"Awaiting Merge\" view from\n // listing issues whose PR was closed without merging (PAN-1111-style stale\n // state). Best-effort against the forge; 10-min per-issue cooldown.\n const closedPrReadyActions = await reconcileClosedPrReadyForMerge();\n actions.push(...closedPrReadyActions);\n for (const a of closedPrReadyActions) addLog('action', a, state.patrolCycle); for (const a of await reconcileAutoMergeRows()) { actions.push(a); addLog('action', a, state.patrolCycle); }\n\n // PAN-2143: re-evaluate stale merge-blockers. resolveConflictGate clears a\n // stale merge_conflict/not_mergeable blocker once the branch is mergeable\n // again, but only runs on-demand from review dispatch — so a blocked issue\n // that falls out of the review flow stays stuck after review forever. This\n // patrol re-runs it for every in-pipeline issue still carrying a merge-blocker\n // so resolved conflicts clear and readyForMerge recomputes.\n const staleMergeBlockerActions = await reconcileStaleMergeBlockers();\n actions.push(...staleMergeBlockerActions);\n for (const a of staleMergeBlockerActions) addLog('action', a, state.patrolCycle);\n\n // PAN-2198: re-derive readyForMerge for the no-blocker \"stuck after review\" strand\n // (review+test+verify passed, no blocker, but readyForMerge stuck false) so it\n // converges on the deacon tick instead of only on the server-restart repair sweep.\n const stuckReadyActions = await reconcileStuckReadyForMerge();\n actions.push(...stuckReadyActions);\n for (const a of stuckReadyActions) addLog('action', a, state.patrolCycle);\n\n // Dead-end agent recovery: nudge agents stuck with reviewStatus=blocked/failed after\n // fixing review issues but not re-requesting review. Has 10-min per-issue cooldown and\n // 7-requeue circuit breaker to avoid runaway API credit consumption.\n const deadEndActions = await checkDeadEndAgents();\n actions.push(...deadEndActions);\n for (const a of deadEndActions) addLog('action', a, state.patrolCycle);\n\n // First-completion gap detection: nudge work agents that finished implementation\n // but never called pan done. Only fires for agents idle >10min with commits and\n // no completion marker or review status entry. Has 15-min cooldown per agent.\n const firstCompletionActions = await checkFirstCompletionAgents();\n actions.push(...firstCompletionActions);\n for (const a of firstCompletionActions) addLog('action', a, state.patrolCycle);\n\n // PAN-2428: workspace stacks can come up outside Overdeck (e.g. a project's own\n // dev script), leaving traefik disconnected from their network — every route to\n // that stack 504s until someone runs `docker network connect` by hand. Reconcile\n // idempotently on the patrol tick.\n const traefikActions = await reconcileTraefikNetworks();\n actions.push(...traefikActions);\n for (const a of traefikActions) addLog('action', a, state.patrolCycle);\n\n // Resolution patrol DISABLED — auto-completing and poking agents consumes\n // API credits and is unreliable. Human operator can take action via dashboard.\n\n // Lazy agent correction DISABLED — sends messages to agents which costs\n // API credits. Human operator can check lazy behavior via dashboard.\n\n // Stuck work agent recovery still runs — it only intervenes after 10 minutes\n // of no tool use, escalating to Escape/Ctrl-C/respawn (not paid messages).\n const stuckActions = await checkStuckWorkAgents();\n actions.push(...stuckActions);\n for (const a of stuckActions) addLog('action', a, state.patrolCycle);\n\n const apiErrorActions = await checkApiErrorAgents();\n actions.push(...apiErrorActions);\n for (const a of apiErrorActions) addLog('action', a, state.patrolCycle);\n if (state.patrolCycle % 10 === 0) for (const action of await reconcilePipelineLabelsPatrol()) { actions.push(action); addLog('action', action, state.patrolCycle); }\n // PAN-1625: reap orphaned dashboard-server processes. Low cadence (~10 min).\n // Never touches the live server, port owner, or workspace-container server.\n const serverReaperEveryCycles = Math.max(1, Math.round((10 * 60 * 1000) / config.patrolIntervalMs));\n if (state.patrolCycle % serverReaperEveryCycles === 0) {\n const reaperActions = await reapOrphanedDashboardServers();\n actions.push(...reaperActions);\n for (const a of reaperActions) addLog('action', a, state.patrolCycle);\n }\n\n // PAN-1706: reap leftover playwright-mcp trees / stale headless browsers.\n // Each ghost browser keeps a full dashboard page polling at full rate,\n // multiplying server load. Same ~10 min cadence as the server reaper.\n if (state.patrolCycle % serverReaperEveryCycles === 0) {\n const playwrightActions = await reapLeftoverPlaywrightBrowsers();\n actions.push(...playwrightActions);\n for (const a of playwrightActions) addLog('action', a, state.patrolCycle);\n }\n const projectConfigs = listProjectsSync();\n if (state.patrolCycle % statePlaneReconcileEveryCycles(config.patrolIntervalMs) === 0) for (const result of await reconcileProjectStatePlanes(projectConfigs)) { actions.push(result.message); addLog(result.level, result.message, state.patrolCycle); }\n // PAN-3727: sweep terminal-issue records for parked-population residue (open\n // recovery trips, operator-gate flags) left over from before close-out\n // started acknowledging it. Same cadence as the state-plane patrol above.\n if (state.patrolCycle % statePlaneReconcileEveryCycles(config.patrolIntervalMs) === 0) for (const result of await reconcileTerminalIssueResidue(projectConfigs)) { actions.push(result.message); addLog(result.level, result.message, state.patrolCycle); }\n for (const warning of await recreatedStateWarnings(projectConfigs)) addLog(warning.level, warning.message, state.patrolCycle);\n const divergenceWarnings = await recordMainDivergenceHealth(state, projectConfigs);\n for (const warning of divergenceWarnings) addLog('warn', warning, state.patrolCycle);\n if (shouldRunRecoveryJanitor('agent-state', state.patrolCycle)) {\n const cleanupActions = await cleanupStaleAgentState();\n actions.push(...cleanupActions);\n for (const a of cleanupActions) addLog('action', a, state.patrolCycle);\n\n const transcriptDays = loadCloisterConfigSync().retention?.transcript_days;\n if (typeof transcriptDays === 'number' && Number.isFinite(transcriptDays) && transcriptDays > 0) {\n const transcriptActions = await sweepTranscriptRetention({ transcriptDays });\n actions.push(...transcriptActions);\n for (const a of transcriptActions) addLog('action', a, state.patrolCycle);\n }\n }\n // Retention needs the canonical registry row to prove terminal state before\n // agent GC removes that evidence. Both run on the same 60-cycle cadence.\n if (state.patrolCycle % 60 === 0) for (const id of (await pruneTerminalStoppedAgents()).removed) actions.push(`[agents-gc] pruned ${id}`);\n\n // Periodic abandoned-feedback sweep — safety net for workspaces where the\n // event-driven cleanup (new review cycle / merge / close-out) never fired.\n // See docs/REVIEW-AGENT-ARCHITECTURE.md.\n if (shouldRunRecoveryJanitor('feedback', state.patrolCycle)) {\n const feedbackActions = await cleanupAbandonedFeedback();\n actions.push(...feedbackActions);\n for (const a of feedbackActions) addLog('action', a, state.patrolCycle);\n }\n\n // PAN-1908: primary orphan reviewer-session cleanup is reactive\n // (agent.stopped for the owning work agent). This is a thin safety-net\n // sweep for dropped events (PAN-846).\n if (shouldRunRecoveryJanitor('orphan-reviewer', state.patrolCycle)) {\n const orphanActions = await cleanupOrphanReviewerSessions();\n actions.push(...orphanActions);\n for (const a of orphanActions) addLog('action', a, state.patrolCycle);\n }\n\n // Check for mass death (uses shared state)\n const massDeathCheck = checkMassDeath(state);\n if (massDeathCheck.isMassDeath && massDeathCheck.message) {\n console.error(`[deacon] ${massDeathCheck.message}`);\n actions.push(massDeathCheck.message);\n addLog('error', massDeathCheck.message, state.patrolCycle);\n }\n\n // Patrol per-project ephemeral specialists (PAN-300)\n // Ephemeral specialists are spawned on-demand and are not auto-restarted by the deacon.\n // Patrol detects stuck sessions, dead sessions, and auto-completes successful merges (PAN-375).\n try {\n const projectSpecialists = await getAllProjectSpecialistStatuses();\n for (const projSpec of projectSpecialists) {\n if (!projSpec.isRunning) {\n // Session is dead — reset any stale active runtime state so the next\n // merge request is not blocked by a phantom busy signal.\n const runtimeState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n if (runtimeState?.state === 'active') {\n saveAgentRuntimeState(projSpec.tmuxSession, { state: 'idle', lastActivity: new Date().toISOString() });\n const msg = `Dead-session reset: per-project ${projSpec.specialistType} (${projSpec.projectKey}) was active but session is gone`;\n actions.push(msg);\n addLog('action', msg, state.patrolCycle);\n console.log(`[deacon] ${msg}`);\n\n // PAN-375: If merge specialist died while merging, check if merge actually succeeded\n if (projSpec.specialistType === 'merge-agent' && runtimeState.currentIssue) {\n const issueId = runtimeState.currentIssue;\n try {\n const currentStatus = getReviewStatusSync(issueId);\n if (currentStatus?.mergeStatus === 'merging') {\n const { resolveProjectFromIssueSync } = await import('../projects.js');\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n const branch = `feature/${issueId.toLowerCase()}`;\n const { stdout } = await execAsync(\n `git -C \"${resolved.projectPath}\" log --oneline origin/main --grep=\"Merge branch '${branch}'\" 2>/dev/null | head -1`,\n { encoding: 'utf-8', timeout: 15_000 }\n );\n if (stdout.trim()) {\n console.log(`[deacon] PAN-375: merge specialist died but ${issueId} IS merged (${stdout.trim()}). Auto-completing.`);\n setReviewStatusSync(issueId, { mergeStatus: 'merged', readyForMerge: false });\n const { postMergeLifecycle } = await import('./merge-agent.js');\n postMergeLifecycle(issueId, resolved.projectPath).catch(err =>\n console.warn(`[deacon] postMergeLifecycle failed for ${issueId}: ${err}`)\n );\n actions.push(`Auto-completed stale merge for ${issueId}`);\n } else {\n console.log(`[deacon] Merge specialist died and ${issueId} NOT merged. Resetting to readyForMerge.`);\n setReviewStatusSync(issueId, { mergeStatus: 'pending' });\n }\n }\n }\n } catch (err) {\n console.warn(`[deacon] PAN-375 check failed for ${issueId}: ${err}`);\n }\n }\n }\n continue;\n }\n\n const runtimeState = getAgentRuntimeStateSync(projSpec.tmuxSession);\n // A running ephemeral specialist with no runtime state, or active for more than\n // the max specialist timeout (ephemeral specialist spawn uses 15 min), is considered stuck.\n const isStuck = runtimeState?.state === 'active' && runtimeState.lastActivity\n ? (Date.now() - new Date(runtimeState.lastActivity).getTime()) > 15 * 60 * 1000\n : false;\n\n if (isStuck) {\n addLog('warn', `Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing`, state.patrolCycle);\n console.log(`[deacon] Per-project ${projSpec.specialistType} (${projSpec.projectKey}) stuck, force-killing ${projSpec.tmuxSession}`);\n try {\n await Effect.runPromise(killSession(projSpec.tmuxSession));\n // Preserve Claude JSONL/session artifacts; only reset Overdeck runtime state.\n saveAgentRuntimeState(projSpec.tmuxSession, { state: 'idle', lastActivity: new Date().toISOString() });\n actions.push(`Force-killed stuck per-project ${projSpec.specialistType} (${projSpec.projectKey})`);\n } catch {\n // Non-fatal — session may have already exited\n }\n }\n\n // PAN-919: Idle-but-alive specialist — task completed but session lingers\n // (e.g. Claude Code sitting at \"Press Ctrl-D again to exit\" after one-shot task).\n // Ephemeral specialists have no reason to stay alive once idle.\n const IDLE_LINGER_MS = 5 * 60 * 1000; // 5 minutes\n if (\n !isStuck &&\n (!runtimeState || runtimeState.state === 'idle') &&\n runtimeState?.lastActivity &&\n Date.now() - new Date(runtimeState.lastActivity).getTime() > IDLE_LINGER_MS\n ) {\n const ageMin = Math.round((Date.now() - new Date(runtimeState.lastActivity).getTime()) / 60000);\n const msg = `Killed lingering idle specialist ${projSpec.specialistType} (${projSpec.projectKey}) — idle ${ageMin}min`;\n console.log(`[deacon] ${msg}`);\n try {\n await Effect.runPromise(killSession(projSpec.tmuxSession));\n actions.push(msg);\n addLog('action', msg, state.patrolCycle);\n } catch {\n // Non-fatal\n }\n }\n }\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n console.error('[deacon] Error during per-project specialist patrol:', msg);\n }\n\n refreshPatrolHeartbeat(state);\n\n const result: PatrolResult = {\n cycle: state.patrolCycle,\n timestamp: state.lastPatrol!,\n specialists: results,\n actionsToken: actions,\n massDeathDetected: massDeathCheck.isMassDeath,\n mainDivergence: state.mainDivergence,\n };\n\n lastPatrolResult = result;\n saveLastPatrolResult(state, result);\n return result;\n } finally {\n clearInterval(patrolHeartbeatTicker);\n }\n}\n\n// Store the most recent patrol result for API access\nlet lastPatrolResult: PatrolResult | null = null;\nlet hasLoggedGlobalPauseSkip = false;\n\nfunction runScheduledPatrol(source: 'startup' | 'interval'): void {\n const started = deaconPatrolGuard.run(\n 'deacon-patrol',\n async () => {\n await runPatrol();\n },\n (err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[deacon] Patrol error:', err);\n logDeaconEventSync(`patrol error: ${msg}`);\n },\n );\n\n if (!started) {\n const msg = `patrol ${source} skipped — previous patrol still in flight`;\n console.warn(`[deacon] ${msg}`);\n logDeaconEventSync(`startDeacon: ${msg}`);\n }\n}\n\n// ============================================================================\n// Deacon Log Buffer\n// ============================================================================\n\nexport interface DeaconLogEntry {\n timestamp: string;\n level: 'info' | 'warn' | 'action' | 'error';\n message: string;\n cycle?: number;\n}\n\nconst MAX_LOG_ENTRIES = 200;\nconst deaconLogs: DeaconLogEntry[] = [];\n\nfunction addLog(level: DeaconLogEntry['level'], message: string, cycle?: number): void {\n const entry = {\n timestamp: new Date().toISOString(),\n level,\n message,\n cycle,\n };\n deaconLogs.push(entry);\n try {\n mkdirSync(dirname(DEACON_LOG_FILE), { recursive: true });\n appendFileSync(DEACON_LOG_FILE, JSON.stringify(entry) + '\\n', 'utf-8');\n } catch {\n // Non-fatal: the in-memory ring still serves the current process.\n }\n // Trim to max size\n if (deaconLogs.length > MAX_LOG_ENTRIES) {\n deaconLogs.splice(0, deaconLogs.length - MAX_LOG_ENTRIES);\n }\n}\n\n/**\n * Get recent deacon log entries.\n * Returns the most recent `limit` entries (default 100).\n */\nexport function getDeaconLogs(limit = 100): DeaconLogEntry[] {\n try {\n if (existsSync(DEACON_LOG_FILE)) {\n return readFileSync(DEACON_LOG_FILE, 'utf-8')\n .trim()\n .split('\\n')\n .filter(Boolean)\n .slice(-limit)\n .map((line) => JSON.parse(line) as DeaconLogEntry);\n }\n } catch {\n // Fall back to the in-memory ring if the durable log is malformed.\n }\n return deaconLogs.slice(-limit);\n}\n\n/**\n * Get the result of the most recent patrol cycle.\n * Used by the dashboard API to show recent Deacon actions.\n */\nexport function getLastPatrolResult(): PatrolResult | null {\n if (lastPatrolResult) return lastPatrolResult;\n const state = loadState();\n return state.lastPatrolResult ?? null;\n}\n\n/**\n * Detect agents whose tmux output contains \"Invalid signature in thinking block\" —\n * a symptom of context compaction corrupting thinking-block cryptographic signatures\n * (PAN-612). The corrupted session cannot be resumed; the agent must start fresh.\n *\n * Recovery: kill the tmux session, mark agent stopped, delete session.id so\n * autoResumeStoppedWorkAgents won't try --resume with the corrupted session.\n * The JSONL file is NEVER deleted (sacred).\n */\nasync function checkThinkingSignatureCorruption(): Promise<string[]> {\n const actions: string[] = [];\n if (!existsSync(AGENTS_DIR)) return actions;\n\n let agentDirs: string[];\n try {\n agentDirs = readdirSync(AGENTS_DIR).filter(d => d.startsWith('agent-'));\n } catch {\n return actions;\n }\n\n for (const agentId of agentDirs) {\n // Only check agents that claim to be running\n const state = getAgentStateSync(agentId);\n if (!state || state.status !== 'running') continue;\n\n // Check if the tmux session is alive\n if (!sessionExistsSync(agentId)) continue;\n\n // Capture recent output and scan for signature corruption\n let output: string;\n try {\n output = await Effect.runPromise(capturePane(agentId, 100));\n } catch {\n continue;\n }\n\n if (!output.includes('Invalid signature in thinking block')) continue;\n\n // Corruption detected — recover the agent\n const issueId = state.issueId ?? agentId;\n console.error(`[deacon] SIGNATURE CORRUPTION detected in ${agentId} (${issueId}) — recovering`);\n logDeaconEventSync(`checkThinkingSignatureCorruption: corruption detected in ${agentId} (${issueId})`);\n logAgentLifecycleSync(agentId, 'signature corruption detected — recovering: killed session, cleared session.id');\n\n // Kill the tmux session\n try {\n await Effect.runPromise(killSession(agentId));\n } catch { /* non-fatal */ }\n\n // Delete session.id so resumeAgent won't --resume the corrupted session\n const sessionFile = join(AGENTS_DIR, agentId, 'session.id');\n if (existsSync(sessionFile)) {\n try { rmSync(sessionFile); } catch { /* non-fatal; PAN-3357: not a dir removal */ }\n }\n\n // Mark agent as stopped\n try {\n saveAgentStateSync({ ...state, status: 'stopped', stoppedAt: new Date().toISOString() });\n } catch { /* non-fatal */ }\n\n // Notify server layer so the read model and frontend update\n if (agentStoppedNotifier) {\n try { agentStoppedNotifier(agentId); } catch { /* non-fatal */ }\n }\n\n const msg = `Recovered ${agentId} (${issueId}) from thinking-block signature corruption — session killed, will start fresh on next resume`;\n actions.push(msg);\n console.log(`[deacon] ${msg}`);\n logDeaconEventSync(`checkThinkingSignatureCorruption: ${msg}`);\n }\n\n return actions;\n}\n\n/**\n * Start the deacon patrol loop\n */\n\n\nexport function startDeacon(): void {\n if (deaconInterval) {\n console.log('[deacon] Already running');\n return;\n }\n\n config = loadConfig();\n console.log(`[deacon] Starting health monitor (patrol every ${config.patrolIntervalMs / 1000}s)`);\n logDeaconEventSync(`startDeacon: health monitor starting (patrol every ${config.patrolIntervalMs / 1000}s)`);\n\n // Recover agents whose tmux sessions were killed by a system crash before the\n // first patrol. PAN-1908: use the thin table-query reconcile instead of directory scans.\n void (async () => {\n await reconcileAgentLiveness();\n const { runBootAdvancingSelfHeal } = await import('./advancing-selfheal.js');\n await runBootAdvancingSelfHeal();\n runScheduledPatrol('startup');\n })().catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n console.error('[deacon] Startup recovery/patrol error:', err);\n logDeaconEventSync(`startDeacon: startup recovery/patrol error: ${msg}`);\n });\n\n // Schedule regular patrols\n deaconInterval = setInterval(() => {\n runScheduledPatrol('interval');\n }, config.patrolIntervalMs);\n\n // Resource-pressure patrol on dedicated 15s timer.\n memoryPatrolInterval = setInterval(() => {\n void (async () => {\n const { patrolResourcePressure } = await import('./resource-pressure-patrol.js');\n for (const action of await patrolResourcePressure()) {\n logDeaconEventSync(`resource-pressure-patrol: ${action}`);\n }\n })().catch((err) => {\n logDeaconEventSync(`resource-pressure-patrol: error: ${err instanceof Error ? err.message : String(err)}`);\n });\n }, MEMORY_PATROL_INTERVAL_MS);\n memoryPatrolInterval.unref?.();\n\n logDeaconEventSync('startDeacon: health monitor started');\n}\n\n/**\n * Stop the deacon patrol loop\n */\nexport function stopDeacon(): void {\n if (deaconInterval) {\n clearInterval(deaconInterval);\n deaconInterval = null;\n console.log('[deacon] Stopped health monitor');\n }\n if (memoryPatrolInterval) {\n clearInterval(memoryPatrolInterval);\n memoryPatrolInterval = null;\n }\n}\n\n/**\n * Check if deacon is running\n */\nexport function isDeaconRunning(): boolean {\n return deaconInterval !== null;\n}\n\n/**\n * Get current deacon status\n */\nexport function getDeaconStatus(): {\n isRunning: boolean;\n config: DeaconConfig;\n state: DeaconState;\n} {\n return {\n isRunning: isDeaconRunning(),\n config: loadConfig(),\n state: loadState(),\n };\n}\n\nexport function assessDeaconPatrolFreshness(\n params: {\n isRunning: boolean;\n lastPatrol?: string;\n patrolIntervalMs: number;\n nowMs?: number;\n }\n): DeaconPatrolFreshness {\n const staleAfterSeconds = Math.ceil((params.patrolIntervalMs * 3) / 1000);\n if (!params.isRunning) {\n return {\n status: 'stopped',\n lastPatrol: params.lastPatrol ?? null,\n secondsSinceLastPatrol: null,\n staleAfterSeconds,\n };\n }\n\n if (!params.lastPatrol) {\n return {\n status: 'starting',\n lastPatrol: null,\n secondsSinceLastPatrol: null,\n staleAfterSeconds,\n };\n }\n\n const nowMs = params.nowMs ?? Date.now();\n const lastPatrolMs = Date.parse(params.lastPatrol);\n if (!Number.isFinite(lastPatrolMs)) {\n return {\n status: 'stale',\n lastPatrol: params.lastPatrol,\n secondsSinceLastPatrol: null,\n staleAfterSeconds,\n };\n }\n\n const secondsSinceLastPatrol = Math.max(0, Math.floor((nowMs - lastPatrolMs) / 1000));\n return {\n status: secondsSinceLastPatrol > staleAfterSeconds ? 'stale' : 'running',\n lastPatrol: params.lastPatrol,\n secondsSinceLastPatrol,\n staleAfterSeconds,\n };\n}\n"],"mappings":"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"}