@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":"release-engine-78iCajIc.js","names":[],"sources":["../../src/lib/release/release-plan.ts","../../src/lib/release/release-engine.ts"],"sourcesContent":["import type { ReleaseComponentConfig, ReleaseConfig } from '../projects.js';\n\nexport interface ReleaseComponentPlanEntry {\n component: string;\n config: ReleaseComponentConfig;\n dependsOn: string[];\n releaseOrder: number;\n notes: string[];\n}\n\nexport function resolveReleasePlan(release: ReleaseConfig): ReleaseComponentPlanEntry[] {\n const components = release.components ?? {};\n const activeComponentNames = Object.keys(components).filter((name) => components[name]?.trigger !== 'skip');\n const activeComponents = new Set(activeComponentNames);\n const dependencyNotes = new Map<string, string[]>();\n\n const activeDependencies = new Map<string, string[]>();\n for (const name of activeComponentNames) {\n const dependencies = components[name]?.depends_on ?? [];\n const active = dependencies.filter((dependency) => {\n if (activeComponents.has(dependency)) return true;\n const reason = components[dependency]?.trigger === 'skip' ? 'skipped' : 'absent';\n const notes = dependencyNotes.get(name) ?? [];\n notes.push(`Dependency \"${dependency}\" is ${reason}; treated as satisfied.`);\n dependencyNotes.set(name, notes);\n return false;\n });\n activeDependencies.set(name, active);\n }\n\n const sorted: string[] = [];\n const visitState = new Map<string, 'visiting' | 'visited'>();\n const stack: string[] = [];\n\n const visit = (name: string): void => {\n const state = visitState.get(name);\n if (state === 'visited') return;\n if (state === 'visiting') {\n const cycleStart = stack.indexOf(name);\n const cycle = [...stack.slice(cycleStart), name];\n throw new Error(`Release dependency cycle detected: ${cycle.join(' -> ')}`);\n }\n\n visitState.set(name, 'visiting');\n stack.push(name);\n for (const dependency of activeDependencies.get(name) ?? []) {\n visit(dependency);\n }\n stack.pop();\n visitState.set(name, 'visited');\n sorted.push(name);\n };\n\n for (const name of activeComponentNames) {\n visit(name);\n }\n\n return sorted.map((component, releaseOrder) => ({\n component,\n config: components[component],\n dependsOn: activeDependencies.get(component) ?? [],\n releaseOrder,\n notes: dependencyNotes.get(component) ?? [],\n }));\n}\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { getMergeSetSync } from '../merge-set.js';\nimport { findProjectByPathSync, getProjectSync, type ReleaseComponentConfig } from '../projects.js';\nimport { setReviewStatusSync } from '../review-status.js';\nimport {\n type ReleaseCheckStatus,\n type ReleaseComponentState,\n type ReleaseSet,\n type ReleaseSetStatus,\n upsertReleaseSetSync,\n withComponentStateSync,\n} from '../release-set.js';\nimport { resolveReleasePlan, type ReleaseComponentPlanEntry } from './release-plan.js';\n\nconst execAsync = promisify(exec);\n\nexport interface ReleaseEngineOptions {\n commandTimeoutMs?: number;\n commandCwd?: string;\n healthTimeoutMs?: number;\n healthPollIntervalMs?: number;\n runCommand?: (command: string, timeoutMs: number, cwd?: string) => Promise<void>;\n fetchHealth?: (url: string, timeoutMs: number) => Promise<{ ok: boolean; status?: number }>;\n sleep?: (ms: number) => Promise<void>;\n now?: () => Date;\n}\n\ninterface ComponentFailure {\n componentKey: string;\n rollbackRan: boolean;\n}\n\nconst DEFAULT_COMMAND_TIMEOUT_MS = 60_000;\nconst DEFAULT_HEALTH_TIMEOUT_MS = 120_000;\nconst DEFAULT_HEALTH_POLL_INTERVAL_MS = 2_000;\n\nexport async function runRelease(\n issueId: string,\n projectPath: string,\n options: ReleaseEngineOptions = {},\n): Promise<ReleaseSet | null> {\n const mergeSet = getMergeSetSync(issueId);\n if (!mergeSet) {\n throw new Error(`Cannot run release for ${issueId}: merge set not found`);\n }\n\n const project = getProjectSync(mergeSet.projectKey) ?? findProjectByPathSync(projectPath);\n if (!project?.release) {\n setReviewStatusSync(issueId, {\n releaseStatus: 'skipped',\n releaseNotes: 'No release config found for project.',\n });\n return null;\n }\n\n const now = options.now ?? (() => new Date());\n options.commandCwd = options.commandCwd ?? projectPath;\n const plan = resolveReleasePlan(project.release);\n let releaseSet = buildReleaseSet(issueId, mergeSet, plan, now().toISOString());\n upsertReleaseSetSync(releaseSet);\n setReviewStatusSync(issueId, { releaseStatus: 'releasing' });\n\n let failure: ComponentFailure | null = null;\n for (const entry of plan) {\n if (entry.config.trigger === 'manual') {\n releaseSet = persistComponentPatch(releaseSet, entry.component, {\n status: 'blocked',\n notes: 'Awaiting manual release.',\n });\n continue;\n }\n\n const unmetDependencies = entry.dependsOn.filter((dependencyKey) => {\n const dependency = releaseSet.components.find((component) => component.componentKey === dependencyKey);\n return !dependency || dependency.status !== 'passed';\n });\n if (unmetDependencies.length > 0) {\n const dependencyNames = unmetDependencies.join(', ');\n releaseSet = persistComponentPatch(releaseSet, entry.component, {\n status: 'blocked',\n notes: `Blocked: dependencies not passed: ${dependencyNames}.`,\n });\n continue;\n }\n\n releaseSet = persistComponentPatch(releaseSet, entry.component, {\n status: 'releasing',\n notes: entry.notes.join(' ') || undefined,\n });\n\n const result = await runAutoComponent(entry.component, entry.config, options);\n releaseSet = persistComponentPatch(releaseSet, entry.component, result.patch);\n\n if (!result.passed) {\n let rollbackRan = false;\n if (entry.config.rollback) {\n rollbackRan = await runConfiguredCommand(entry.config.rollback, options);\n releaseSet = persistComponentPatch(releaseSet, entry.component, {\n status: rollbackRan ? 'rolled_back' : 'failed',\n rollbackStatus: rollbackRan ? 'rolled_back' : 'failed',\n notes: appendNote(result.patch.notes, rollbackRan ? 'Rollback completed.' : 'Rollback failed.'),\n });\n }\n failure = { componentKey: entry.component, rollbackRan };\n break;\n }\n }\n\n if (failure) {\n releaseSet = haltRemainingComponents(releaseSet, failure.componentKey);\n const releaseStatus = finalFailureStatus(releaseSet, failure.rollbackRan);\n releaseSet = persistReleaseSetStatus(releaseSet, releaseStatus);\n setReviewStatusSync(issueId, {\n releaseStatus,\n releaseNotes: `Release halted at ${failure.componentKey}.`,\n });\n return releaseSet;\n }\n\n const blockedComponents = releaseSet.components.filter(\n (component) => component.status === 'blocked',\n );\n if (blockedComponents.length > 0) {\n const blockedKeys = blockedComponents.map((component) => component.componentKey).join(', ');\n releaseSet = persistReleaseSetStatus(releaseSet, 'partial');\n setReviewStatusSync(issueId, {\n releaseStatus: 'partial',\n releaseNotes: `Release awaiting manual step(s): ${blockedKeys}.`,\n });\n return releaseSet;\n }\n\n releaseSet = persistReleaseSetStatus(releaseSet, 'passed');\n setReviewStatusSync(issueId, { releaseStatus: 'passed' });\n return releaseSet;\n}\n\nfunction buildReleaseSet(\n issueId: string,\n mergeSet: NonNullable<ReturnType<typeof getMergeSetSync>>,\n plan: ReleaseComponentPlanEntry[],\n timestamp: string,\n): ReleaseSet {\n return {\n issueId,\n projectKey: mergeSet.projectKey,\n projectPath: mergeSet.projectPath,\n workspaceType: mergeSet.workspaceType,\n status: 'releasing',\n createdAt: timestamp,\n updatedAt: timestamp,\n components: plan.map((entry): ReleaseComponentState => ({\n componentKey: entry.component,\n provider: entry.config.provider,\n trigger: entry.config.trigger,\n releaseOrder: entry.releaseOrder,\n required: true,\n status: 'pending',\n healthStatus: entry.config.health_url ? 'pending' : 'skipped',\n versionStatus: entry.config.version_check ? 'pending' : 'skipped',\n smokeStatus: entry.config.smoke_test ? 'pending' : 'skipped',\n rollbackStatus: entry.config.rollback ? 'pending' : 'skipped',\n notes: entry.notes.join(' ') || undefined,\n })),\n };\n}\n\nasync function runAutoComponent(\n componentKey: string,\n config: ReleaseComponentConfig,\n options: ReleaseEngineOptions,\n): Promise<{ passed: boolean; patch: Partial<ReleaseComponentState> }> {\n if (config.health_url) {\n const healthy = await waitForHealth(config.health_url, options);\n if (!healthy) {\n return {\n passed: false,\n patch: {\n status: 'failed',\n healthStatus: 'failed',\n notes: `Health check failed for ${componentKey}.`,\n },\n };\n }\n }\n\n if (config.version_check) {\n const passed = await runConfiguredCommand(config.version_check, options);\n if (!passed) {\n return {\n passed: false,\n patch: {\n status: 'failed',\n healthStatus: config.health_url ? 'passed' : 'skipped',\n versionStatus: 'failed',\n notes: `Version check failed for ${componentKey}.`,\n },\n };\n }\n }\n\n if (config.smoke_test) {\n const passed = await runConfiguredCommand(config.smoke_test, options);\n if (!passed) {\n return {\n passed: false,\n patch: {\n status: 'failed',\n healthStatus: config.health_url ? 'passed' : 'skipped',\n versionStatus: config.version_check ? 'passed' : 'skipped',\n smokeStatus: 'failed',\n notes: `Smoke test failed for ${componentKey}.`,\n },\n };\n }\n }\n\n return {\n passed: true,\n patch: {\n status: 'passed',\n healthStatus: config.health_url ? 'passed' : 'skipped',\n versionStatus: config.version_check ? 'passed' : 'skipped',\n smokeStatus: config.smoke_test ? 'passed' : 'skipped',\n },\n };\n}\n\nasync function waitForHealth(url: string, options: ReleaseEngineOptions): Promise<boolean> {\n const timeoutMs = options.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS;\n const intervalMs = options.healthPollIntervalMs ?? DEFAULT_HEALTH_POLL_INTERVAL_MS;\n const fetchHealth = options.fetchHealth ?? defaultFetchHealth;\n const sleep = options.sleep ?? defaultSleep;\n const deadline = Date.now() + timeoutMs;\n\n while (true) {\n try {\n const response = await fetchHealth(url, options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n if (response.ok) return true;\n } catch {\n // Keep polling until the bounded health deadline expires.\n }\n\n const remaining = deadline - Date.now();\n if (remaining <= 0) return false;\n await sleep(Math.min(intervalMs, remaining));\n }\n}\n\nasync function runConfiguredCommand(command: string, options: ReleaseEngineOptions): Promise<boolean> {\n const timeoutMs = options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;\n const runCommand = options.runCommand ?? defaultRunCommand;\n try {\n await runCommand(command, timeoutMs, options.commandCwd);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function defaultRunCommand(command: string, timeoutMs: number, cwd?: string): Promise<void> {\n await execAsync(command, { timeout: timeoutMs, cwd });\n}\n\nasync function defaultFetchHealth(url: string, timeoutMs: number): Promise<{ ok: boolean; status?: number }> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(url, { signal: controller.signal });\n return { ok: response.ok, status: response.status };\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nfunction persistComponentPatch(\n releaseSet: ReleaseSet,\n componentKey: string,\n patch: Partial<ReleaseComponentState>,\n): ReleaseSet {\n const updated = withComponentStateSync(releaseSet, componentKey, patch);\n upsertReleaseSetSync(updated);\n return updated;\n}\n\nfunction persistReleaseSetStatus(releaseSet: ReleaseSet, status: ReleaseSetStatus): ReleaseSet {\n const updated = {\n ...releaseSet,\n status,\n updatedAt: new Date().toISOString(),\n };\n upsertReleaseSetSync(updated);\n return updated;\n}\n\nfunction haltRemainingComponents(releaseSet: ReleaseSet, failedComponentKey: string): ReleaseSet {\n const failed = releaseSet.components.find(component => component.componentKey === failedComponentKey);\n if (!failed) return releaseSet;\n\n let updated = releaseSet;\n for (const component of releaseSet.components) {\n if (component.releaseOrder <= failed.releaseOrder || component.status !== 'pending') continue;\n updated = withComponentStateSync(updated, component.componentKey, {\n status: 'skipped',\n notes: 'Release halted before this component ran.',\n });\n }\n upsertReleaseSetSync(updated);\n return updated;\n}\n\nfunction finalFailureStatus(releaseSet: ReleaseSet, rollbackRan: boolean): ReleaseSetStatus {\n if (rollbackRan) return 'rolled_back';\n return releaseSet.components.some(component => component.required && component.status === 'passed')\n ? 'partial'\n : 'failed';\n}\n\nfunction appendNote(existing: string | undefined, note: string): string {\n return existing ? `${existing} ${note}` : note;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"release-engine-BuzxOamf.js","names":[],"sources":["../../src/lib/release/release-plan.ts","../../src/lib/release/release-engine.ts"],"sourcesContent":["import type { ReleaseComponentConfig, ReleaseConfig } from '../projects.js';\n\nexport interface ReleaseComponentPlanEntry {\n component: string;\n config: ReleaseComponentConfig;\n dependsOn: string[];\n releaseOrder: number;\n notes: string[];\n}\n\nexport function resolveReleasePlan(release: ReleaseConfig): ReleaseComponentPlanEntry[] {\n const components = release.components ?? {};\n const activeComponentNames = Object.keys(components).filter((name) => components[name]?.trigger !== 'skip');\n const activeComponents = new Set(activeComponentNames);\n const dependencyNotes = new Map<string, string[]>();\n\n const activeDependencies = new Map<string, string[]>();\n for (const name of activeComponentNames) {\n const dependencies = components[name]?.depends_on ?? [];\n const active = dependencies.filter((dependency) => {\n if (activeComponents.has(dependency)) return true;\n const reason = components[dependency]?.trigger === 'skip' ? 'skipped' : 'absent';\n const notes = dependencyNotes.get(name) ?? [];\n notes.push(`Dependency \"${dependency}\" is ${reason}; treated as satisfied.`);\n dependencyNotes.set(name, notes);\n return false;\n });\n activeDependencies.set(name, active);\n }\n\n const sorted: string[] = [];\n const visitState = new Map<string, 'visiting' | 'visited'>();\n const stack: string[] = [];\n\n const visit = (name: string): void => {\n const state = visitState.get(name);\n if (state === 'visited') return;\n if (state === 'visiting') {\n const cycleStart = stack.indexOf(name);\n const cycle = [...stack.slice(cycleStart), name];\n throw new Error(`Release dependency cycle detected: ${cycle.join(' -> ')}`);\n }\n\n visitState.set(name, 'visiting');\n stack.push(name);\n for (const dependency of activeDependencies.get(name) ?? []) {\n visit(dependency);\n }\n stack.pop();\n visitState.set(name, 'visited');\n sorted.push(name);\n };\n\n for (const name of activeComponentNames) {\n visit(name);\n }\n\n return sorted.map((component, releaseOrder) => ({\n component,\n config: components[component],\n dependsOn: activeDependencies.get(component) ?? [],\n releaseOrder,\n notes: dependencyNotes.get(component) ?? [],\n }));\n}\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { getMergeSetSync } from '../merge-set.js';\nimport { findProjectByPathSync, getProjectSync, type ReleaseComponentConfig } from '../projects.js';\nimport { setReviewStatusSync } from '../review-status.js';\nimport {\n type ReleaseCheckStatus,\n type ReleaseComponentState,\n type ReleaseSet,\n type ReleaseSetStatus,\n upsertReleaseSetSync,\n withComponentStateSync,\n} from '../release-set.js';\nimport { resolveReleasePlan, type ReleaseComponentPlanEntry } from './release-plan.js';\n\nconst execAsync = promisify(exec);\n\nexport interface ReleaseEngineOptions {\n commandTimeoutMs?: number;\n commandCwd?: string;\n healthTimeoutMs?: number;\n healthPollIntervalMs?: number;\n runCommand?: (command: string, timeoutMs: number, cwd?: string) => Promise<void>;\n fetchHealth?: (url: string, timeoutMs: number) => Promise<{ ok: boolean; status?: number }>;\n sleep?: (ms: number) => Promise<void>;\n now?: () => Date;\n}\n\ninterface ComponentFailure {\n componentKey: string;\n rollbackRan: boolean;\n}\n\nconst DEFAULT_COMMAND_TIMEOUT_MS = 60_000;\nconst DEFAULT_HEALTH_TIMEOUT_MS = 120_000;\nconst DEFAULT_HEALTH_POLL_INTERVAL_MS = 2_000;\n\nexport async function runRelease(\n issueId: string,\n projectPath: string,\n options: ReleaseEngineOptions = {},\n): Promise<ReleaseSet | null> {\n const mergeSet = getMergeSetSync(issueId);\n if (!mergeSet) {\n throw new Error(`Cannot run release for ${issueId}: merge set not found`);\n }\n\n const project = getProjectSync(mergeSet.projectKey) ?? findProjectByPathSync(projectPath);\n if (!project?.release) {\n setReviewStatusSync(issueId, {\n releaseStatus: 'skipped',\n releaseNotes: 'No release config found for project.',\n });\n return null;\n }\n\n const now = options.now ?? (() => new Date());\n options.commandCwd = options.commandCwd ?? projectPath;\n const plan = resolveReleasePlan(project.release);\n let releaseSet = buildReleaseSet(issueId, mergeSet, plan, now().toISOString());\n upsertReleaseSetSync(releaseSet);\n setReviewStatusSync(issueId, { releaseStatus: 'releasing' });\n\n let failure: ComponentFailure | null = null;\n for (const entry of plan) {\n if (entry.config.trigger === 'manual') {\n releaseSet = persistComponentPatch(releaseSet, entry.component, {\n status: 'blocked',\n notes: 'Awaiting manual release.',\n });\n continue;\n }\n\n const unmetDependencies = entry.dependsOn.filter((dependencyKey) => {\n const dependency = releaseSet.components.find((component) => component.componentKey === dependencyKey);\n return !dependency || dependency.status !== 'passed';\n });\n if (unmetDependencies.length > 0) {\n const dependencyNames = unmetDependencies.join(', ');\n releaseSet = persistComponentPatch(releaseSet, entry.component, {\n status: 'blocked',\n notes: `Blocked: dependencies not passed: ${dependencyNames}.`,\n });\n continue;\n }\n\n releaseSet = persistComponentPatch(releaseSet, entry.component, {\n status: 'releasing',\n notes: entry.notes.join(' ') || undefined,\n });\n\n const result = await runAutoComponent(entry.component, entry.config, options);\n releaseSet = persistComponentPatch(releaseSet, entry.component, result.patch);\n\n if (!result.passed) {\n let rollbackRan = false;\n if (entry.config.rollback) {\n rollbackRan = await runConfiguredCommand(entry.config.rollback, options);\n releaseSet = persistComponentPatch(releaseSet, entry.component, {\n status: rollbackRan ? 'rolled_back' : 'failed',\n rollbackStatus: rollbackRan ? 'rolled_back' : 'failed',\n notes: appendNote(result.patch.notes, rollbackRan ? 'Rollback completed.' : 'Rollback failed.'),\n });\n }\n failure = { componentKey: entry.component, rollbackRan };\n break;\n }\n }\n\n if (failure) {\n releaseSet = haltRemainingComponents(releaseSet, failure.componentKey);\n const releaseStatus = finalFailureStatus(releaseSet, failure.rollbackRan);\n releaseSet = persistReleaseSetStatus(releaseSet, releaseStatus);\n setReviewStatusSync(issueId, {\n releaseStatus,\n releaseNotes: `Release halted at ${failure.componentKey}.`,\n });\n return releaseSet;\n }\n\n const blockedComponents = releaseSet.components.filter(\n (component) => component.status === 'blocked',\n );\n if (blockedComponents.length > 0) {\n const blockedKeys = blockedComponents.map((component) => component.componentKey).join(', ');\n releaseSet = persistReleaseSetStatus(releaseSet, 'partial');\n setReviewStatusSync(issueId, {\n releaseStatus: 'partial',\n releaseNotes: `Release awaiting manual step(s): ${blockedKeys}.`,\n });\n return releaseSet;\n }\n\n releaseSet = persistReleaseSetStatus(releaseSet, 'passed');\n setReviewStatusSync(issueId, { releaseStatus: 'passed' });\n return releaseSet;\n}\n\nfunction buildReleaseSet(\n issueId: string,\n mergeSet: NonNullable<ReturnType<typeof getMergeSetSync>>,\n plan: ReleaseComponentPlanEntry[],\n timestamp: string,\n): ReleaseSet {\n return {\n issueId,\n projectKey: mergeSet.projectKey,\n projectPath: mergeSet.projectPath,\n workspaceType: mergeSet.workspaceType,\n status: 'releasing',\n createdAt: timestamp,\n updatedAt: timestamp,\n components: plan.map((entry): ReleaseComponentState => ({\n componentKey: entry.component,\n provider: entry.config.provider,\n trigger: entry.config.trigger,\n releaseOrder: entry.releaseOrder,\n required: true,\n status: 'pending',\n healthStatus: entry.config.health_url ? 'pending' : 'skipped',\n versionStatus: entry.config.version_check ? 'pending' : 'skipped',\n smokeStatus: entry.config.smoke_test ? 'pending' : 'skipped',\n rollbackStatus: entry.config.rollback ? 'pending' : 'skipped',\n notes: entry.notes.join(' ') || undefined,\n })),\n };\n}\n\nasync function runAutoComponent(\n componentKey: string,\n config: ReleaseComponentConfig,\n options: ReleaseEngineOptions,\n): Promise<{ passed: boolean; patch: Partial<ReleaseComponentState> }> {\n if (config.health_url) {\n const healthy = await waitForHealth(config.health_url, options);\n if (!healthy) {\n return {\n passed: false,\n patch: {\n status: 'failed',\n healthStatus: 'failed',\n notes: `Health check failed for ${componentKey}.`,\n },\n };\n }\n }\n\n if (config.version_check) {\n const passed = await runConfiguredCommand(config.version_check, options);\n if (!passed) {\n return {\n passed: false,\n patch: {\n status: 'failed',\n healthStatus: config.health_url ? 'passed' : 'skipped',\n versionStatus: 'failed',\n notes: `Version check failed for ${componentKey}.`,\n },\n };\n }\n }\n\n if (config.smoke_test) {\n const passed = await runConfiguredCommand(config.smoke_test, options);\n if (!passed) {\n return {\n passed: false,\n patch: {\n status: 'failed',\n healthStatus: config.health_url ? 'passed' : 'skipped',\n versionStatus: config.version_check ? 'passed' : 'skipped',\n smokeStatus: 'failed',\n notes: `Smoke test failed for ${componentKey}.`,\n },\n };\n }\n }\n\n return {\n passed: true,\n patch: {\n status: 'passed',\n healthStatus: config.health_url ? 'passed' : 'skipped',\n versionStatus: config.version_check ? 'passed' : 'skipped',\n smokeStatus: config.smoke_test ? 'passed' : 'skipped',\n },\n };\n}\n\nasync function waitForHealth(url: string, options: ReleaseEngineOptions): Promise<boolean> {\n const timeoutMs = options.healthTimeoutMs ?? DEFAULT_HEALTH_TIMEOUT_MS;\n const intervalMs = options.healthPollIntervalMs ?? DEFAULT_HEALTH_POLL_INTERVAL_MS;\n const fetchHealth = options.fetchHealth ?? defaultFetchHealth;\n const sleep = options.sleep ?? defaultSleep;\n const deadline = Date.now() + timeoutMs;\n\n while (true) {\n try {\n const response = await fetchHealth(url, options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS);\n if (response.ok) return true;\n } catch {\n // Keep polling until the bounded health deadline expires.\n }\n\n const remaining = deadline - Date.now();\n if (remaining <= 0) return false;\n await sleep(Math.min(intervalMs, remaining));\n }\n}\n\nasync function runConfiguredCommand(command: string, options: ReleaseEngineOptions): Promise<boolean> {\n const timeoutMs = options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;\n const runCommand = options.runCommand ?? defaultRunCommand;\n try {\n await runCommand(command, timeoutMs, options.commandCwd);\n return true;\n } catch {\n return false;\n }\n}\n\nasync function defaultRunCommand(command: string, timeoutMs: number, cwd?: string): Promise<void> {\n await execAsync(command, { timeout: timeoutMs, cwd });\n}\n\nasync function defaultFetchHealth(url: string, timeoutMs: number): Promise<{ ok: boolean; status?: number }> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), timeoutMs);\n try {\n const response = await fetch(url, { signal: controller.signal });\n return { ok: response.ok, status: response.status };\n } finally {\n clearTimeout(timeout);\n }\n}\n\nfunction defaultSleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\nfunction persistComponentPatch(\n releaseSet: ReleaseSet,\n componentKey: string,\n patch: Partial<ReleaseComponentState>,\n): ReleaseSet {\n const updated = withComponentStateSync(releaseSet, componentKey, patch);\n upsertReleaseSetSync(updated);\n return updated;\n}\n\nfunction persistReleaseSetStatus(releaseSet: ReleaseSet, status: ReleaseSetStatus): ReleaseSet {\n const updated = {\n ...releaseSet,\n status,\n updatedAt: new Date().toISOString(),\n };\n upsertReleaseSetSync(updated);\n return updated;\n}\n\nfunction haltRemainingComponents(releaseSet: ReleaseSet, failedComponentKey: string): ReleaseSet {\n const failed = releaseSet.components.find(component => component.componentKey === failedComponentKey);\n if (!failed) return releaseSet;\n\n let updated = releaseSet;\n for (const component of releaseSet.components) {\n if (component.releaseOrder <= failed.releaseOrder || component.status !== 'pending') continue;\n updated = withComponentStateSync(updated, component.componentKey, {\n status: 'skipped',\n notes: 'Release halted before this component ran.',\n });\n }\n upsertReleaseSetSync(updated);\n return updated;\n}\n\nfunction finalFailureStatus(releaseSet: ReleaseSet, rollbackRan: boolean): ReleaseSetStatus {\n if (rollbackRan) return 'rolled_back';\n return releaseSet.components.some(component => component.required && component.status === 'passed')\n ? 'partial'\n : 'failed';\n}\n\nfunction appendNote(existing: string | undefined, note: string): string {\n return existing ? `${existing} ${note}` : note;\n}\n"],"mappings":"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"}
@@ -309,7 +309,7 @@ async function reapCompletedRemoteAgents(opts = {}) {
309
309
  } catch {
310
310
  details.push("No continue.json retrieved from VM (skipped)");
311
311
  }
312
- const { getAgentStateSync, saveAgentStateSync } = await import("./agents-BVvL4o_4.js");
312
+ const { getAgentStateSync, saveAgentStateSync } = await import("./agents-CkGhnAmu.js");
313
313
  const existing = getAgentStateSync(agentId);
314
314
  if (existing) {
315
315
  existing.workspace = existing.workspace || workspacePath;
@@ -328,7 +328,7 @@ async function reapCompletedRemoteAgents(opts = {}) {
328
328
  lastActivity: (/* @__PURE__ */ new Date()).toISOString()
329
329
  });
330
330
  const { createReviewArtifactsForIssue } = await import("./review-artifacts-DExSp5Lh.js");
331
- const { setReviewStatusSync } = await import("./review-status-DXTFzCmR.js");
331
+ const { setReviewStatusSync } = await import("./review-status-FnlvQuPI.js");
332
332
  const primaryArtifact = (await Effect.runPromise(createReviewArtifactsForIssue(issueId, workspacePath))).mergeSet?.repos.find((repo) => !!repo.artifactUrl);
333
333
  if (primaryArtifact?.artifactUrl) {
334
334
  setReviewStatusSync(issueId, { prUrl: primaryArtifact.artifactUrl });
@@ -386,4 +386,4 @@ async function reapCompletedRemoteAgents(opts = {}) {
386
386
  //#endregion
387
387
  export { reapCompletedRemoteAgents, refreshClaudeCredentialsForActiveRemoteAgents };
388
388
 
389
- //# sourceMappingURL=remote-completion-D5wzZCte.js.map
389
+ //# sourceMappingURL=remote-completion-BCZgeKPS.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"remote-completion-D5wzZCte.js","names":[],"sources":["../../src/lib/remote/remote-completion.ts"],"sourcesContent":["/**\n * Remote Agent Completion Handoff\n *\n * Remote (fly.io) work agents cannot run `pan done` — the host pipeline has\n * no workspace registry entry for them. Instead they finish by pushing their\n * feature branch and creating the `/workspace/.pan/REMOTE_DONE` sentinel\n * file (see the REMOTE gate in cloister/prompts/work.md).\n *\n * `reapCompletedRemoteAgents()` is the host-side half of that contract:\n * for every remote agent that has created the sentinel (or exited after\n * pushing its branch), it\n *\n * 1. verifies the feature branch exists on origin,\n * 2. materializes the standard local worktree for the branch,\n * 3. copies the remote .pan/continue.json back (best effort),\n * 4. writes a minimal local agent state.json (downstream flows resolve\n * the workspace through it),\n * 5. creates the review artifact set + review status (same entry the\n * `pan done` flow uses), writes the `completed` marker that the\n * cloister's checkCompletionMarkers() patrol consumes,\n * 6. stops the fly machine and marks the remote agent state stopped.\n *\n * Invoked via `pan admin remote reap` (and suitable for a deacon patrol).\n */\n\nimport { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { basename, join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { loadConfigSync } from '../config.js';\nimport { createFlyProviderFromConfig } from './index.js';\nimport {\n loadRemoteAgentState,\n saveRemoteAgentState,\n isRemoteAgentRunning,\n listActiveRemoteAgentStates,\n getRemoteAgentOutput,\n sendToRemoteAgent,\n} from './remote-agents.js';\nimport { resolveProjectFromIssueSync, extractTeamPrefix, findProjectByTeamSync } from '../projects.js';\nimport { createWorkspace } from '../workspace-manager.js';\nimport { PAN_DIRNAME, PAN_CONTINUE_FILENAME } from '../pan-dir/index.js';\n\nconst execAsync = promisify(exec);\nconst AGENTS_DIR = join(homedir(), '.overdeck', 'agents');\nconst REMOTE_CLAUDE_CREDENTIAL_REFRESH_INTERVAL_MS = 15 * 60 * 1000;\n\ninterface PerVmCredentialRefreshState {\n fingerprint: string | null;\n refreshedAtMs: number;\n}\n\nconst perVmCredentialRefreshState = new Map<string, PerVmCredentialRefreshState>();\n\nexport interface RemoteReapResult {\n agentId: string;\n issueId: string;\n status: 'handed-off' | 'still-running' | 'stale' | 'error';\n details: string[];\n}\n\ninterface RemoteClaudeCredentialRefreshDeps {\n listActiveRemoteAgentStates?: typeof listActiveRemoteAgentStates;\n nowMs?: () => number;\n credentialFingerprint?: () => string | null;\n loadConfig?: typeof loadConfigSync;\n createFlyProvider?: typeof createFlyProviderFromConfig;\n}\n\nfunction getHostClaudeCredentialFingerprint(): string | null {\n const credFile = join(homedir(), '.claude', '.credentials.json');\n if (!existsSync(credFile)) return null;\n try {\n const stat = statSync(credFile);\n return `${stat.mtimeMs}:${stat.size}`;\n } catch {\n return null;\n }\n}\n\nexport function resetRemoteClaudeCredentialRefreshForTests(): void {\n perVmCredentialRefreshState.clear();\n}\n\n/**\n * Proactively copy fresh host Claude credentials to active remote agents.\n *\n * Host OAuth refresh rotates refresh tokens, so a long-running Fly VM can be\n * left with an orphaned copy. The host credentials file mtime is the precise\n * trigger on Linux; platforms without that file fall back to a bounded 15 min\n * refresh cadence. The active-agent scan runs first so the common zero-remote\n * case never constructs a Fly provider or makes Fly API calls.\n *\n * Refresh state is tracked per VM (keyed by vmName), not globally. A failed\n * or newly started VM must be retried on the next patrol regardless of whether\n * another VM already succeeded this cycle.\n */\nexport async function refreshClaudeCredentialsForActiveRemoteAgents(\n deps: RemoteClaudeCredentialRefreshDeps = {},\n): Promise<string[]> {\n const activeStates = (deps.listActiveRemoteAgentStates ?? listActiveRemoteAgentStates)();\n const activeVmNames = new Set(activeStates.map((state) => state.vmName));\n\n // Drop state for VMs that are no longer active so a future machine reusing\n // the same vmName starts with a clean watermark. Do this before the empty\n // short-circuit so the Map is pruned even when no agents are currently active.\n for (const vmName of perVmCredentialRefreshState.keys()) {\n if (!activeVmNames.has(vmName)) {\n perVmCredentialRefreshState.delete(vmName);\n }\n }\n\n if (activeStates.length === 0) return [];\n\n const nowMs = (deps.nowMs ?? Date.now)();\n const currentFingerprint = (deps.credentialFingerprint ?? getHostClaudeCredentialFingerprint)();\n const config = (deps.loadConfig ?? loadConfigSync)();\n const fly = (deps.createFlyProvider ?? createFlyProviderFromConfig)(config.remote);\n const actions: string[] = [];\n\n for (const state of activeStates) {\n const last = perVmCredentialRefreshState.get(state.vmName);\n const credentialChanged = currentFingerprint !== null && currentFingerprint !== last?.fingerprint;\n const refreshDue = !last ||\n nowMs - last.refreshedAtMs >= REMOTE_CLAUDE_CREDENTIAL_REFRESH_INTERVAL_MS;\n if (!credentialChanged && !refreshDue) continue;\n\n try {\n const synced = await fly.syncClaudeCredentials(state.vmName);\n if (synced) {\n // Only advance this VM's watermark on a successful sync. A failure\n // leaves its last watermark intact so the next patrol retries it.\n perVmCredentialRefreshState.set(state.vmName, {\n fingerprint: currentFingerprint,\n refreshedAtMs: nowMs,\n });\n }\n actions.push(\n synced\n ? `Remote credentials refreshed for ${state.issueId.toUpperCase()} on ${state.vmName}`\n : `Remote credentials refresh skipped for ${state.issueId.toUpperCase()} on ${state.vmName} (no host credentials)`,\n );\n } catch (err: any) {\n actions.push(`Remote credentials refresh failed for ${state.issueId.toUpperCase()} on ${state.vmName}: ${err.message}`);\n }\n }\n\n return actions;\n}\n\n/** List agent IDs that have a remote-state.json in running/starting state. */\nfunction listActiveRemoteAgents(): string[] {\n return listActiveRemoteAgentStates().map((state) => state.id);\n}\n\nasync function branchExistsOnOrigin(projectRoot: string, branch: string): Promise<boolean> {\n try {\n const { stdout } = await execAsync(`git ls-remote --heads origin ${branch}`, { cwd: projectRoot });\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Preserve the remote session's observable state into the local agent dir\n * BEFORE the machine goes away: the final tmux pane (remote-output.log) and\n * the Claude Code session JSONLs (remote-sessions/). The JSONLs are the only\n * record of the remote conversation — destroying a machine without them\n * loses the transcript irrecoverably (learned on the first migrated issue).\n */\nasync function captureRemoteForensics(agentId: string, vmName: string, details: string[]): Promise<{ paneCaptured: boolean }> {\n const agentDir = join(AGENTS_DIR, agentId);\n mkdirSync(agentDir, { recursive: true });\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n\n let paneCaptured = false;\n try {\n const output = await getRemoteAgentOutput(agentId, vmName, 2000);\n if (output.trim()) {\n writeFileSync(join(agentDir, 'remote-output.log'), output);\n details.push('Captured final remote pane to remote-output.log');\n paneCaptured = true;\n }\n } catch {\n details.push('Could not capture remote pane');\n }\n\n // The Machines exec API runs with HOME=/, so Claude's session JSONLs live\n // under /.claude/projects — but the launcher may export HOME=/root, so\n // check both. Surface failures: an empty copy means the only transcript of\n // the agent's work is about to be lost with the rootfs.\n try {\n const list = await Effect.runPromise(\n fly.ssh(vmName, 'ls /.claude/projects/*/*.jsonl /root/.claude/projects/*/*.jsonl 2>/dev/null')\n );\n const files = list.stdout.trim().split('\\n').filter(Boolean);\n if (files.length > 0) {\n const destDir = join(agentDir, 'remote-sessions');\n mkdirSync(destDir, { recursive: true });\n let copied = 0;\n for (const remotePath of files) {\n try {\n await fly.copyFromVm(vmName, remotePath, join(destDir, basename(remotePath)));\n copied++;\n } catch {\n details.push(`Failed to copy ${remotePath}`);\n }\n }\n details.push(`Copied ${copied}/${files.length} session JSONL(s) to remote-sessions/`);\n } else {\n details.push('No session JSONLs found under /.claude or /root/.claude');\n }\n } catch (err: any) {\n details.push(`Could not copy session JSONLs: ${err.message}`);\n }\n return { paneCaptured };\n}\n\n/**\n * Commit and push whatever work is sitting in /workspace before the machine\n * is stopped. Fly machine rootfs is ephemeral — a stop is only \"preserved\n * for inspection\" until the next start, which resets the filesystem from the\n * image. Unpushed work on a stopped machine is one restart away from gone\n * (lost live on PAN-1762: ~2h of agent work).\n */\nasync function salvageRemoteWork(vmName: string, branch: string, details: string[]): Promise<void> {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n try {\n const result = await Effect.runPromise(fly.ssh(\n vmName,\n `cd /workspace && git add -A && (git diff --cached --quiet || git commit -m \"wip(remote): salvage checkpoint before machine stop\") && git push origin ${branch} 2>&1 | tail -2`\n ));\n if (result.exitCode === 0) {\n details.push(`Salvaged workspace state to origin/${branch}`);\n } else {\n details.push(`Salvage push failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);\n }\n } catch (err: any) {\n details.push(`Salvage attempt failed: ${err.message}`);\n }\n}\n\n/**\n * Long remote runs outlive the synced OAuth token: when the host's Claude\n * refreshes its token, the VM's copy is orphaned (refresh tokens rotate),\n * and the agent stalls on `API Error: 401 · Please run /login` until\n * credentials are re-synced (observed live on PAN-1762 — ~1h stalled, then\n * mistaken for a crash). Detect the stall from the pane tail, re-sync\n * credentials from the host, and nudge the session so it retries.\n */\nasync function healStaleRemoteAuth(agentId: string, vmName: string, details: string[]): Promise<void> {\n try {\n const tail = await getRemoteAgentOutput(agentId, vmName, 40);\n if (!/Invalid authentication credentials|Please run \\/login/i.test(tail)) return;\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n const synced = await fly.syncClaudeCredentials(vmName);\n if (!synced) {\n details.push('401 stall detected but credential re-sync failed');\n return;\n }\n await sendToRemoteAgent(agentId, vmName, 'Your API credentials were stale and have been re-synced. Please continue from where you left off.');\n details.push('401 stall detected — re-synced Claude credentials and nudged the session');\n } catch (err: any) {\n details.push(`401 heal attempt failed: ${err.message}`);\n }\n}\n\n/**\n * Reap completed remote agents into the review pipeline.\n * Pass issueId to target one agent; otherwise scans all active remote agents.\n */\nexport async function reapCompletedRemoteAgents(opts: { issueId?: string; dryRun?: boolean } = {}): Promise<RemoteReapResult[]> {\n const agentIds = opts.issueId\n ? [`agent-${opts.issueId.toLowerCase()}`]\n : listActiveRemoteAgents();\n\n const results: RemoteReapResult[] = [];\n\n for (const agentId of agentIds) {\n const details: string[] = [];\n const remoteState = loadRemoteAgentState(agentId);\n if (!remoteState || remoteState.location !== 'remote') {\n results.push({ agentId, issueId: opts.issueId ?? 'unknown', status: 'stale', details: ['No remote-state.json'] });\n continue;\n }\n const issueId = remoteState.issueId.toUpperCase();\n const vmName = remoteState.vmName;\n\n try {\n // 1. Completion detection: the /workspace/.pan/REMOTE_DONE sentinel\n // file (created by the agent per the REMOTE prompt contract), or the\n // session having exited. Do NOT grep session output for the marker —\n // the prompt's own instruction text contains the literal string, so a\n // mid-work agent's pane matches it (false positive observed live on\n // the first migrated issue).\n let sessionAlive = false;\n let sentinelSeen = false;\n try {\n // A single has-session over the Machines exec API false-negatives on\n // transient exec failures/429s — one flake here stopped a healthy\n // mid-work machine live (PAN-1762). Any success across retries means\n // alive; only consistent failure proceeds to the crash path.\n for (let attempt = 0; attempt < 3 && !sessionAlive; attempt++) {\n if (attempt > 0) await new Promise((r) => setTimeout(r, 5000));\n sessionAlive = await isRemoteAgentRunning(agentId, vmName);\n }\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n const check = await Effect.runPromise(\n fly.ssh(vmName, '[ -f /workspace/.pan/REMOTE_DONE ] && echo present')\n );\n sentinelSeen = check.stdout.trim() === 'present';\n } catch (err: any) {\n details.push(`VM unreachable (${err.message}) — falling back to branch check`);\n }\n\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n const resolved = resolveProjectFromIssueSync(issueId, []);\n const projectRoot = projectConfig?.path ?? resolved?.projectPath;\n if (!projectRoot) {\n throw new Error(`Cannot resolve project for ${issueId}`);\n }\n\n const branch = `feature/${issueId.toLowerCase()}`;\n const pushed = await branchExistsOnOrigin(projectRoot, branch);\n\n if (sessionAlive && !sentinelSeen) {\n await healStaleRemoteAuth(agentId, vmName, details);\n results.push({ agentId, issueId, status: 'still-running', details });\n continue;\n }\n\n // Only the sentinel hands off. A dead session WITHOUT the sentinel is a\n // crash, not a completion — migrated issues always have a pushed branch,\n // so \"session ended + branch pushed\" would false-positive every crash\n // straight into review (observed live on the first migrated issue).\n // Park as error with forensics; the machine stays (stopped) for\n // inspection and manual recovery via `pan admin remote reap --issue`.\n if (!sentinelSeen || !pushed) {\n const reason = !sentinelSeen\n ? 'session ended without the REMOTE_DONE sentinel — treating as crash, not completion'\n : `sentinel present but ${branch} is not on origin`;\n details.push(reason);\n const { paneCaptured } = await captureRemoteForensics(agentId, vmName, details);\n if (!sessionAlive && paneCaptured) {\n // Contradiction: has-session said dead, but capture-pane just read\n // the live pane through the same tmux server. The session is\n // reachable — the alive check flaked. Do NOT stop the machine.\n details.push('Pane capture succeeded despite dead has-session — alive-check flake, leaving agent running');\n results.push({ agentId, issueId, status: 'still-running', details });\n continue;\n }\n // Salvage before stopping: rootfs does not survive a stop→start\n // cycle, so this is the last chance to preserve unpushed work.\n await salvageRemoteWork(vmName, branch, details);\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n await Effect.runPromise(fly.stopVm(vmName));\n details.push(`Stopped machine ${vmName} (rootfs resets on next start — salvage above is the durable copy)`);\n } catch (err: any) {\n details.push(`Warning: could not stop machine: ${err.message}`);\n }\n saveRemoteAgentState({ ...remoteState, status: 'error', lastActivity: new Date().toISOString() });\n results.push({ agentId, issueId, status: 'error', details });\n continue;\n }\n details.push(`REMOTE_DONE sentinel present for ${issueId}`);\n\n if (opts.dryRun) {\n details.push('Dry run — would hand off to review');\n results.push({ agentId, issueId, status: 'handed-off', details });\n continue;\n }\n\n // 2. Materialize the standard local worktree (tracks the pushed branch).\n const workspacePath = join(projectRoot, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspacePath)) {\n if (!projectConfig) throw new Error(`No project config for ${issueId}; cannot create workspace`);\n const wsResult = await Effect.runPromise(createWorkspace({\n projectConfig,\n featureName: issueId.toLowerCase(),\n startDocker: false,\n }));\n if (!wsResult.success) {\n throw new Error(`Failed to create local worktree: ${wsResult.errors.join('; ')}`);\n }\n details.push(`Materialized local worktree at ${workspacePath}`);\n } else {\n // Worktree exists (e.g. migrated issue with --keep) — fast-forward it.\n await execAsync(`git fetch origin ${branch}`, { cwd: workspacePath });\n await execAsync(`git merge --ff-only origin/${branch}`, { cwd: workspacePath }).catch(() => {\n details.push('Local worktree diverged from origin — left as-is');\n });\n }\n\n // 3. Copy continue.json back from the VM (best effort — session record).\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n mkdirSync(join(workspacePath, PAN_DIRNAME), { recursive: true });\n await fly.copyFromVm(\n vmName,\n `/workspace/${PAN_DIRNAME}/${PAN_CONTINUE_FILENAME}`,\n join(workspacePath, PAN_DIRNAME, PAN_CONTINUE_FILENAME),\n );\n details.push('Copied continue.json back from VM');\n } catch {\n details.push('No continue.json retrieved from VM (skipped)');\n }\n\n // 4. Minimal local agent state so downstream flows resolve the workspace.\n const { getAgentStateSync, saveAgentStateSync } = await import('../agents.js');\n const existing = getAgentStateSync(agentId);\n if (existing) {\n existing.workspace = existing.workspace || workspacePath;\n existing.status = 'stopped';\n existing.lastActivity = new Date().toISOString();\n saveAgentStateSync(existing);\n } else {\n saveAgentStateSync({\n id: agentId,\n issueId,\n workspace: workspacePath,\n role: 'work',\n harness: 'claude-code',\n model: remoteState.model,\n status: 'stopped',\n startedAt: remoteState.startedAt,\n lastActivity: new Date().toISOString(),\n });\n }\n\n // 5. Review artifacts + completed marker — same entries pan done uses.\n const { createReviewArtifactsForIssue } = await import('../review-artifacts.js');\n const { setReviewStatusSync } = await import('../review-status.js');\n const artifactResult = await Effect.runPromise(createReviewArtifactsForIssue(issueId, workspacePath));\n const primaryArtifact = artifactResult.mergeSet?.repos.find((repo) => !!repo.artifactUrl);\n if (primaryArtifact?.artifactUrl) {\n setReviewStatusSync(issueId, { prUrl: primaryArtifact.artifactUrl });\n details.push(`Review artifact: ${primaryArtifact.artifactUrl}`);\n }\n\n mkdirSync(join(AGENTS_DIR, agentId), { recursive: true });\n const processedMarker = join(AGENTS_DIR, agentId, 'completed.processed');\n if (existsSync(processedMarker)) {\n try { unlinkSync(processedMarker); } catch { /* best effort */ }\n }\n writeFileSync(join(AGENTS_DIR, agentId, 'completed'), JSON.stringify({\n timestamp: new Date().toISOString(),\n trackerUpdated: false,\n comment: `Remote agent completed on ${vmName} (reaped)`,\n }));\n details.push('Wrote completion marker for cloister pickup');\n\n // 5.5. Preserve the transcript and final pane before the machine goes.\n await captureRemoteForensics(agentId, vmName, details);\n\n // 6. Retire the remote workspace entirely: from review onward the\n // pipeline runs against the local worktree, and lingering remote\n // metadata makes getWorkspaceInfoForIssue() route review operations\n // at the (now-idle) VM. Destroying the machine also closes the\n // run-forever cost leak.\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n if (sessionAlive) {\n const { killRemoteAgent } = await import('./remote-agents.js');\n await killRemoteAgent(agentId, vmName).catch(() => undefined);\n }\n await Effect.runPromise(fly.deleteVm(vmName));\n details.push(`Destroyed machine ${vmName}`);\n } catch (err: any) {\n details.push(`Warning: could not destroy machine: ${err.message}`);\n }\n const { deleteWorkspaceMetadataSync } = await import('./workspace-metadata.js');\n deleteWorkspaceMetadataSync(issueId);\n details.push('Removed remote workspace metadata (pipeline is local from here)');\n saveRemoteAgentState({ ...remoteState, status: 'stopped', lastActivity: new Date().toISOString() });\n\n results.push({ agentId, issueId, status: 'handed-off', details });\n } catch (err: any) {\n details.push(err.message);\n results.push({ agentId, issueId, status: 'error', details });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"remote-completion-BCZgeKPS.js","names":[],"sources":["../../src/lib/remote/remote-completion.ts"],"sourcesContent":["/**\n * Remote Agent Completion Handoff\n *\n * Remote (fly.io) work agents cannot run `pan done` — the host pipeline has\n * no workspace registry entry for them. Instead they finish by pushing their\n * feature branch and creating the `/workspace/.pan/REMOTE_DONE` sentinel\n * file (see the REMOTE gate in cloister/prompts/work.md).\n *\n * `reapCompletedRemoteAgents()` is the host-side half of that contract:\n * for every remote agent that has created the sentinel (or exited after\n * pushing its branch), it\n *\n * 1. verifies the feature branch exists on origin,\n * 2. materializes the standard local worktree for the branch,\n * 3. copies the remote .pan/continue.json back (best effort),\n * 4. writes a minimal local agent state.json (downstream flows resolve\n * the workspace through it),\n * 5. creates the review artifact set + review status (same entry the\n * `pan done` flow uses), writes the `completed` marker that the\n * cloister's checkCompletionMarkers() patrol consumes,\n * 6. stops the fly machine and marks the remote agent state stopped.\n *\n * Invoked via `pan admin remote reap` (and suitable for a deacon patrol).\n */\n\nimport { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { basename, join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { loadConfigSync } from '../config.js';\nimport { createFlyProviderFromConfig } from './index.js';\nimport {\n loadRemoteAgentState,\n saveRemoteAgentState,\n isRemoteAgentRunning,\n listActiveRemoteAgentStates,\n getRemoteAgentOutput,\n sendToRemoteAgent,\n} from './remote-agents.js';\nimport { resolveProjectFromIssueSync, extractTeamPrefix, findProjectByTeamSync } from '../projects.js';\nimport { createWorkspace } from '../workspace-manager.js';\nimport { PAN_DIRNAME, PAN_CONTINUE_FILENAME } from '../pan-dir/index.js';\n\nconst execAsync = promisify(exec);\nconst AGENTS_DIR = join(homedir(), '.overdeck', 'agents');\nconst REMOTE_CLAUDE_CREDENTIAL_REFRESH_INTERVAL_MS = 15 * 60 * 1000;\n\ninterface PerVmCredentialRefreshState {\n fingerprint: string | null;\n refreshedAtMs: number;\n}\n\nconst perVmCredentialRefreshState = new Map<string, PerVmCredentialRefreshState>();\n\nexport interface RemoteReapResult {\n agentId: string;\n issueId: string;\n status: 'handed-off' | 'still-running' | 'stale' | 'error';\n details: string[];\n}\n\ninterface RemoteClaudeCredentialRefreshDeps {\n listActiveRemoteAgentStates?: typeof listActiveRemoteAgentStates;\n nowMs?: () => number;\n credentialFingerprint?: () => string | null;\n loadConfig?: typeof loadConfigSync;\n createFlyProvider?: typeof createFlyProviderFromConfig;\n}\n\nfunction getHostClaudeCredentialFingerprint(): string | null {\n const credFile = join(homedir(), '.claude', '.credentials.json');\n if (!existsSync(credFile)) return null;\n try {\n const stat = statSync(credFile);\n return `${stat.mtimeMs}:${stat.size}`;\n } catch {\n return null;\n }\n}\n\nexport function resetRemoteClaudeCredentialRefreshForTests(): void {\n perVmCredentialRefreshState.clear();\n}\n\n/**\n * Proactively copy fresh host Claude credentials to active remote agents.\n *\n * Host OAuth refresh rotates refresh tokens, so a long-running Fly VM can be\n * left with an orphaned copy. The host credentials file mtime is the precise\n * trigger on Linux; platforms without that file fall back to a bounded 15 min\n * refresh cadence. The active-agent scan runs first so the common zero-remote\n * case never constructs a Fly provider or makes Fly API calls.\n *\n * Refresh state is tracked per VM (keyed by vmName), not globally. A failed\n * or newly started VM must be retried on the next patrol regardless of whether\n * another VM already succeeded this cycle.\n */\nexport async function refreshClaudeCredentialsForActiveRemoteAgents(\n deps: RemoteClaudeCredentialRefreshDeps = {},\n): Promise<string[]> {\n const activeStates = (deps.listActiveRemoteAgentStates ?? listActiveRemoteAgentStates)();\n const activeVmNames = new Set(activeStates.map((state) => state.vmName));\n\n // Drop state for VMs that are no longer active so a future machine reusing\n // the same vmName starts with a clean watermark. Do this before the empty\n // short-circuit so the Map is pruned even when no agents are currently active.\n for (const vmName of perVmCredentialRefreshState.keys()) {\n if (!activeVmNames.has(vmName)) {\n perVmCredentialRefreshState.delete(vmName);\n }\n }\n\n if (activeStates.length === 0) return [];\n\n const nowMs = (deps.nowMs ?? Date.now)();\n const currentFingerprint = (deps.credentialFingerprint ?? getHostClaudeCredentialFingerprint)();\n const config = (deps.loadConfig ?? loadConfigSync)();\n const fly = (deps.createFlyProvider ?? createFlyProviderFromConfig)(config.remote);\n const actions: string[] = [];\n\n for (const state of activeStates) {\n const last = perVmCredentialRefreshState.get(state.vmName);\n const credentialChanged = currentFingerprint !== null && currentFingerprint !== last?.fingerprint;\n const refreshDue = !last ||\n nowMs - last.refreshedAtMs >= REMOTE_CLAUDE_CREDENTIAL_REFRESH_INTERVAL_MS;\n if (!credentialChanged && !refreshDue) continue;\n\n try {\n const synced = await fly.syncClaudeCredentials(state.vmName);\n if (synced) {\n // Only advance this VM's watermark on a successful sync. A failure\n // leaves its last watermark intact so the next patrol retries it.\n perVmCredentialRefreshState.set(state.vmName, {\n fingerprint: currentFingerprint,\n refreshedAtMs: nowMs,\n });\n }\n actions.push(\n synced\n ? `Remote credentials refreshed for ${state.issueId.toUpperCase()} on ${state.vmName}`\n : `Remote credentials refresh skipped for ${state.issueId.toUpperCase()} on ${state.vmName} (no host credentials)`,\n );\n } catch (err: any) {\n actions.push(`Remote credentials refresh failed for ${state.issueId.toUpperCase()} on ${state.vmName}: ${err.message}`);\n }\n }\n\n return actions;\n}\n\n/** List agent IDs that have a remote-state.json in running/starting state. */\nfunction listActiveRemoteAgents(): string[] {\n return listActiveRemoteAgentStates().map((state) => state.id);\n}\n\nasync function branchExistsOnOrigin(projectRoot: string, branch: string): Promise<boolean> {\n try {\n const { stdout } = await execAsync(`git ls-remote --heads origin ${branch}`, { cwd: projectRoot });\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Preserve the remote session's observable state into the local agent dir\n * BEFORE the machine goes away: the final tmux pane (remote-output.log) and\n * the Claude Code session JSONLs (remote-sessions/). The JSONLs are the only\n * record of the remote conversation — destroying a machine without them\n * loses the transcript irrecoverably (learned on the first migrated issue).\n */\nasync function captureRemoteForensics(agentId: string, vmName: string, details: string[]): Promise<{ paneCaptured: boolean }> {\n const agentDir = join(AGENTS_DIR, agentId);\n mkdirSync(agentDir, { recursive: true });\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n\n let paneCaptured = false;\n try {\n const output = await getRemoteAgentOutput(agentId, vmName, 2000);\n if (output.trim()) {\n writeFileSync(join(agentDir, 'remote-output.log'), output);\n details.push('Captured final remote pane to remote-output.log');\n paneCaptured = true;\n }\n } catch {\n details.push('Could not capture remote pane');\n }\n\n // The Machines exec API runs with HOME=/, so Claude's session JSONLs live\n // under /.claude/projects — but the launcher may export HOME=/root, so\n // check both. Surface failures: an empty copy means the only transcript of\n // the agent's work is about to be lost with the rootfs.\n try {\n const list = await Effect.runPromise(\n fly.ssh(vmName, 'ls /.claude/projects/*/*.jsonl /root/.claude/projects/*/*.jsonl 2>/dev/null')\n );\n const files = list.stdout.trim().split('\\n').filter(Boolean);\n if (files.length > 0) {\n const destDir = join(agentDir, 'remote-sessions');\n mkdirSync(destDir, { recursive: true });\n let copied = 0;\n for (const remotePath of files) {\n try {\n await fly.copyFromVm(vmName, remotePath, join(destDir, basename(remotePath)));\n copied++;\n } catch {\n details.push(`Failed to copy ${remotePath}`);\n }\n }\n details.push(`Copied ${copied}/${files.length} session JSONL(s) to remote-sessions/`);\n } else {\n details.push('No session JSONLs found under /.claude or /root/.claude');\n }\n } catch (err: any) {\n details.push(`Could not copy session JSONLs: ${err.message}`);\n }\n return { paneCaptured };\n}\n\n/**\n * Commit and push whatever work is sitting in /workspace before the machine\n * is stopped. Fly machine rootfs is ephemeral — a stop is only \"preserved\n * for inspection\" until the next start, which resets the filesystem from the\n * image. Unpushed work on a stopped machine is one restart away from gone\n * (lost live on PAN-1762: ~2h of agent work).\n */\nasync function salvageRemoteWork(vmName: string, branch: string, details: string[]): Promise<void> {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n try {\n const result = await Effect.runPromise(fly.ssh(\n vmName,\n `cd /workspace && git add -A && (git diff --cached --quiet || git commit -m \"wip(remote): salvage checkpoint before machine stop\") && git push origin ${branch} 2>&1 | tail -2`\n ));\n if (result.exitCode === 0) {\n details.push(`Salvaged workspace state to origin/${branch}`);\n } else {\n details.push(`Salvage push failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);\n }\n } catch (err: any) {\n details.push(`Salvage attempt failed: ${err.message}`);\n }\n}\n\n/**\n * Long remote runs outlive the synced OAuth token: when the host's Claude\n * refreshes its token, the VM's copy is orphaned (refresh tokens rotate),\n * and the agent stalls on `API Error: 401 · Please run /login` until\n * credentials are re-synced (observed live on PAN-1762 — ~1h stalled, then\n * mistaken for a crash). Detect the stall from the pane tail, re-sync\n * credentials from the host, and nudge the session so it retries.\n */\nasync function healStaleRemoteAuth(agentId: string, vmName: string, details: string[]): Promise<void> {\n try {\n const tail = await getRemoteAgentOutput(agentId, vmName, 40);\n if (!/Invalid authentication credentials|Please run \\/login/i.test(tail)) return;\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n const synced = await fly.syncClaudeCredentials(vmName);\n if (!synced) {\n details.push('401 stall detected but credential re-sync failed');\n return;\n }\n await sendToRemoteAgent(agentId, vmName, 'Your API credentials were stale and have been re-synced. Please continue from where you left off.');\n details.push('401 stall detected — re-synced Claude credentials and nudged the session');\n } catch (err: any) {\n details.push(`401 heal attempt failed: ${err.message}`);\n }\n}\n\n/**\n * Reap completed remote agents into the review pipeline.\n * Pass issueId to target one agent; otherwise scans all active remote agents.\n */\nexport async function reapCompletedRemoteAgents(opts: { issueId?: string; dryRun?: boolean } = {}): Promise<RemoteReapResult[]> {\n const agentIds = opts.issueId\n ? [`agent-${opts.issueId.toLowerCase()}`]\n : listActiveRemoteAgents();\n\n const results: RemoteReapResult[] = [];\n\n for (const agentId of agentIds) {\n const details: string[] = [];\n const remoteState = loadRemoteAgentState(agentId);\n if (!remoteState || remoteState.location !== 'remote') {\n results.push({ agentId, issueId: opts.issueId ?? 'unknown', status: 'stale', details: ['No remote-state.json'] });\n continue;\n }\n const issueId = remoteState.issueId.toUpperCase();\n const vmName = remoteState.vmName;\n\n try {\n // 1. Completion detection: the /workspace/.pan/REMOTE_DONE sentinel\n // file (created by the agent per the REMOTE prompt contract), or the\n // session having exited. Do NOT grep session output for the marker —\n // the prompt's own instruction text contains the literal string, so a\n // mid-work agent's pane matches it (false positive observed live on\n // the first migrated issue).\n let sessionAlive = false;\n let sentinelSeen = false;\n try {\n // A single has-session over the Machines exec API false-negatives on\n // transient exec failures/429s — one flake here stopped a healthy\n // mid-work machine live (PAN-1762). Any success across retries means\n // alive; only consistent failure proceeds to the crash path.\n for (let attempt = 0; attempt < 3 && !sessionAlive; attempt++) {\n if (attempt > 0) await new Promise((r) => setTimeout(r, 5000));\n sessionAlive = await isRemoteAgentRunning(agentId, vmName);\n }\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n const check = await Effect.runPromise(\n fly.ssh(vmName, '[ -f /workspace/.pan/REMOTE_DONE ] && echo present')\n );\n sentinelSeen = check.stdout.trim() === 'present';\n } catch (err: any) {\n details.push(`VM unreachable (${err.message}) — falling back to branch check`);\n }\n\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n const resolved = resolveProjectFromIssueSync(issueId, []);\n const projectRoot = projectConfig?.path ?? resolved?.projectPath;\n if (!projectRoot) {\n throw new Error(`Cannot resolve project for ${issueId}`);\n }\n\n const branch = `feature/${issueId.toLowerCase()}`;\n const pushed = await branchExistsOnOrigin(projectRoot, branch);\n\n if (sessionAlive && !sentinelSeen) {\n await healStaleRemoteAuth(agentId, vmName, details);\n results.push({ agentId, issueId, status: 'still-running', details });\n continue;\n }\n\n // Only the sentinel hands off. A dead session WITHOUT the sentinel is a\n // crash, not a completion — migrated issues always have a pushed branch,\n // so \"session ended + branch pushed\" would false-positive every crash\n // straight into review (observed live on the first migrated issue).\n // Park as error with forensics; the machine stays (stopped) for\n // inspection and manual recovery via `pan admin remote reap --issue`.\n if (!sentinelSeen || !pushed) {\n const reason = !sentinelSeen\n ? 'session ended without the REMOTE_DONE sentinel — treating as crash, not completion'\n : `sentinel present but ${branch} is not on origin`;\n details.push(reason);\n const { paneCaptured } = await captureRemoteForensics(agentId, vmName, details);\n if (!sessionAlive && paneCaptured) {\n // Contradiction: has-session said dead, but capture-pane just read\n // the live pane through the same tmux server. The session is\n // reachable — the alive check flaked. Do NOT stop the machine.\n details.push('Pane capture succeeded despite dead has-session — alive-check flake, leaving agent running');\n results.push({ agentId, issueId, status: 'still-running', details });\n continue;\n }\n // Salvage before stopping: rootfs does not survive a stop→start\n // cycle, so this is the last chance to preserve unpushed work.\n await salvageRemoteWork(vmName, branch, details);\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n await Effect.runPromise(fly.stopVm(vmName));\n details.push(`Stopped machine ${vmName} (rootfs resets on next start — salvage above is the durable copy)`);\n } catch (err: any) {\n details.push(`Warning: could not stop machine: ${err.message}`);\n }\n saveRemoteAgentState({ ...remoteState, status: 'error', lastActivity: new Date().toISOString() });\n results.push({ agentId, issueId, status: 'error', details });\n continue;\n }\n details.push(`REMOTE_DONE sentinel present for ${issueId}`);\n\n if (opts.dryRun) {\n details.push('Dry run — would hand off to review');\n results.push({ agentId, issueId, status: 'handed-off', details });\n continue;\n }\n\n // 2. Materialize the standard local worktree (tracks the pushed branch).\n const workspacePath = join(projectRoot, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspacePath)) {\n if (!projectConfig) throw new Error(`No project config for ${issueId}; cannot create workspace`);\n const wsResult = await Effect.runPromise(createWorkspace({\n projectConfig,\n featureName: issueId.toLowerCase(),\n startDocker: false,\n }));\n if (!wsResult.success) {\n throw new Error(`Failed to create local worktree: ${wsResult.errors.join('; ')}`);\n }\n details.push(`Materialized local worktree at ${workspacePath}`);\n } else {\n // Worktree exists (e.g. migrated issue with --keep) — fast-forward it.\n await execAsync(`git fetch origin ${branch}`, { cwd: workspacePath });\n await execAsync(`git merge --ff-only origin/${branch}`, { cwd: workspacePath }).catch(() => {\n details.push('Local worktree diverged from origin — left as-is');\n });\n }\n\n // 3. Copy continue.json back from the VM (best effort — session record).\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n mkdirSync(join(workspacePath, PAN_DIRNAME), { recursive: true });\n await fly.copyFromVm(\n vmName,\n `/workspace/${PAN_DIRNAME}/${PAN_CONTINUE_FILENAME}`,\n join(workspacePath, PAN_DIRNAME, PAN_CONTINUE_FILENAME),\n );\n details.push('Copied continue.json back from VM');\n } catch {\n details.push('No continue.json retrieved from VM (skipped)');\n }\n\n // 4. Minimal local agent state so downstream flows resolve the workspace.\n const { getAgentStateSync, saveAgentStateSync } = await import('../agents.js');\n const existing = getAgentStateSync(agentId);\n if (existing) {\n existing.workspace = existing.workspace || workspacePath;\n existing.status = 'stopped';\n existing.lastActivity = new Date().toISOString();\n saveAgentStateSync(existing);\n } else {\n saveAgentStateSync({\n id: agentId,\n issueId,\n workspace: workspacePath,\n role: 'work',\n harness: 'claude-code',\n model: remoteState.model,\n status: 'stopped',\n startedAt: remoteState.startedAt,\n lastActivity: new Date().toISOString(),\n });\n }\n\n // 5. Review artifacts + completed marker — same entries pan done uses.\n const { createReviewArtifactsForIssue } = await import('../review-artifacts.js');\n const { setReviewStatusSync } = await import('../review-status.js');\n const artifactResult = await Effect.runPromise(createReviewArtifactsForIssue(issueId, workspacePath));\n const primaryArtifact = artifactResult.mergeSet?.repos.find((repo) => !!repo.artifactUrl);\n if (primaryArtifact?.artifactUrl) {\n setReviewStatusSync(issueId, { prUrl: primaryArtifact.artifactUrl });\n details.push(`Review artifact: ${primaryArtifact.artifactUrl}`);\n }\n\n mkdirSync(join(AGENTS_DIR, agentId), { recursive: true });\n const processedMarker = join(AGENTS_DIR, agentId, 'completed.processed');\n if (existsSync(processedMarker)) {\n try { unlinkSync(processedMarker); } catch { /* best effort */ }\n }\n writeFileSync(join(AGENTS_DIR, agentId, 'completed'), JSON.stringify({\n timestamp: new Date().toISOString(),\n trackerUpdated: false,\n comment: `Remote agent completed on ${vmName} (reaped)`,\n }));\n details.push('Wrote completion marker for cloister pickup');\n\n // 5.5. Preserve the transcript and final pane before the machine goes.\n await captureRemoteForensics(agentId, vmName, details);\n\n // 6. Retire the remote workspace entirely: from review onward the\n // pipeline runs against the local worktree, and lingering remote\n // metadata makes getWorkspaceInfoForIssue() route review operations\n // at the (now-idle) VM. Destroying the machine also closes the\n // run-forever cost leak.\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n if (sessionAlive) {\n const { killRemoteAgent } = await import('./remote-agents.js');\n await killRemoteAgent(agentId, vmName).catch(() => undefined);\n }\n await Effect.runPromise(fly.deleteVm(vmName));\n details.push(`Destroyed machine ${vmName}`);\n } catch (err: any) {\n details.push(`Warning: could not destroy machine: ${err.message}`);\n }\n const { deleteWorkspaceMetadataSync } = await import('./workspace-metadata.js');\n deleteWorkspaceMetadataSync(issueId);\n details.push('Removed remote workspace metadata (pipeline is local from here)');\n saveRemoteAgentState({ ...remoteState, status: 'stopped', lastActivity: new Date().toISOString() });\n\n results.push({ agentId, issueId, status: 'handed-off', details });\n } catch (err: any) {\n details.push(err.message);\n results.push({ agentId, issueId, status: 'error', details });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { $ as readIssueRecord, Li as getOverdeckHome, On as resolveProjectFromIssueSync, Ri as init_paths, Y as init_record, fn as init_projects, on as getProjectSync } from "./config-yaml-Dce9VUd6.js";
2
- import { Fn as init_queries, In as listAgentStates, Rn as listRunningAgentsSync } from "./agent-state-Ci6PhjOE.js";
3
- import { h as loadReviewStatuses, p as init_review_status } from "./review-status-BegAr86G.js";
2
+ import { Fn as init_queries, In as listAgentStates, Rn as listRunningAgentsSync } from "./agent-state-B196fj1h.js";
3
+ import { h as loadReviewStatuses, p as init_review_status } from "./review-status-yY5qt7Rl.js";
4
4
  import { n as init_issue_closed, r as isIssueClosed } from "./issue-closed-D7aMUeMq.js";
5
- import { t as isRecordPipelineTerminal } from "./parked-residue-DMtbxJml.js";
6
- import { s as shouldSkipReviewStatus } from "./stuck-remediation-4Rf_xMEb.js";
5
+ import { t as isRecordPipelineTerminal } from "./parked-residue-q2kzrYSm.js";
6
+ import { s as shouldSkipReviewStatus } from "./stuck-remediation-CnoV0kcv.js";
7
7
  import { existsSync } from "fs";
8
8
  import { join } from "path";
9
9
  //#region ../../lib/parked/resolver.ts
@@ -385,4 +385,4 @@ function summarizeParked(rows) {
385
385
  //#endregion
386
386
  export { resolveParkedPopulation as n, summarizeParked as r, PARKED_ORBIT_SEVERITY as t };
387
387
 
388
- //# sourceMappingURL=resolver-BGZh3lXA.js.map
388
+ //# sourceMappingURL=resolver-BeNrqNou.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolver-BGZh3lXA.js","names":[],"sources":["../../src/lib/parked/resolver.ts"],"sourcesContent":["/**\n * Single authoritative parked-population resolver (PAN-3485, epic phase 1).\n *\n * A \"parked orbit\" is any state an issue can sit in where no autonomous actor\n * will advance it within 24h without operator or flywheel intervention. Over\n * months of incident response, each failure mode grew its own safety valve —\n * stuck flags, needs-you trips, deacon-ignore, resume gates, UAT gates, merge\n * retry caps, and circuit breakers — and every one of them converts autonomous\n * motion into operator work. Nine of those valves exist today, in six subsystems,\n * and no surface could answer \"what is stalled, why, and what would release it.\"\n * This resolver is that answer: ONE read door that unions all nine orbits into\n * typed rows, so the CLI, the API, the dashboard, and the stall sweeper all agree\n * by construction.\n *\n * Modeled on `resolvePipelineMembership()` (src/lib/pipeline-membership.ts):\n * a pure classifier over gathered signals, with the gathering done here through\n * existing read doors only (review-status door, agents table, tmux liveness,\n * the per-issue record door for recovery trips). No surface may re-derive\n * parking independently.\n *\n * The nine orbits (see docs/PARKED-POPULATION.md):\n *\n * 1. stuck-flag review_status.stuck = 1 (any stuck_reason)\n * 2. needs-you an open recovery trip in the permanent record\n * 3. deacon-ignored review_status.deaconIgnored = true\n * 4. operator-gate paused (operator, not yield) / troubled / stoppedByUser\n * 5. uat-failed uatStatus failed with merge still pending\n * 6. merge-failed mergeStatus failed (retries saturated or abandoned)\n * 7. zombie-session live agent whose issue is merged/closed\n * 8. idle-running live agent, no pipeline owner, idle beyond threshold\n * 9. circuit-breaker autoRequeueCount >= 25 (dead-end recovery exhausted)\n */\n\nimport { loadReviewStatuses, type ReviewStatus } from '../review-status.js';\nimport { listAgentStates, listRunningAgentsSync } from '../agents/queries.js';\nimport type { AgentState } from '../agents.js';\nimport { FAILED_MERGE_MAX_RETRIES } from '../cloister/deacon-merge.js';\nimport { shouldSkipReviewStatus } from '../cloister/stuck-remediation.js';\nimport { isIssueClosed } from '../cloister/issue-closed.js';\nimport { readIssueRecord } from '../pan-dir/record.js';\nimport { getProjectSync, resolveProjectFromIssueSync } from '../projects.js';\nimport { isRecordPipelineTerminal } from '../cloister/parked-residue.js';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { getOverdeckHome } from '../paths.js';\n\nexport const PARKED_ORBITS = [\n 'stuck-flag',\n 'needs-you',\n 'deacon-ignored',\n 'operator-gate',\n 'uat-failed',\n 'merge-failed',\n 'zombie-session',\n 'idle-running',\n 'circuit-breaker',\n] as const;\n\nexport type ParkedOrbit = (typeof PARKED_ORBITS)[number];\n\n/**\n * Severity order — recommendations surface these orbits in this order, with\n * operator-gated rows last so a full report ends by surfacing what only a human\n * can release. The God View tints an orb\n * by its most severe orbit.\n */\nexport const PARKED_ORBIT_SEVERITY: readonly ParkedOrbit[] = [\n 'zombie-session',\n 'merge-failed',\n 'uat-failed',\n 'stuck-flag',\n 'circuit-breaker',\n 'idle-running',\n 'needs-you',\n 'deacon-ignored',\n 'operator-gate',\n];\n\nexport interface ParkedRow {\n issueId: string;\n orbit: ParkedOrbit;\n /** ISO timestamp the issue entered this orbit (best available evidence). */\n parkedAt: string;\n /** Operator-facing sentence: why the issue is parked. */\n parkReason: string;\n /** Operator-facing sentence: what would release it. */\n unparkCondition: string;\n /** Orbit-specific evidence (stuck reason, gate kind, idle minutes, …). */\n details?: Record<string, unknown>;\n}\n\n/** A human sentence per stuck_reason — the stuck flag carries the machine code, the resolver owns the copy.\n * GUARD-EXIT INVARIANT (PAN-3488): every stuck_reason written anywhere in src/ MUST have an entry\n * here — scripts/guard-park-exits.sh fails the lint on an undocumented flavor. */\nconst STUCK_REASON_COPY: Record<string, { park: string; unpark: string }> = {\n feedback_delivery_needs_you: {\n park: 'review/test feedback could not be delivered — the work agent is not running and nothing resumed it',\n unpark: 'resume the work agent with its pending feedback through the established work-resume door',\n },\n review_infrastructure_failure: {\n park: 'the review pipeline failed repeatedly for infrastructure reasons, not verdict reasons',\n unpark: 're-dispatch a fresh review through the review door once the infra cause is resolved',\n },\n review_parent_stalled_needs_you: {\n park: 'the review parent exceeded its deadline with no terminal verdict in the row or verdict artifact',\n unpark: 'inspect the parent pane and review artifacts, then pan unstick <id> and pan review restart <id> if a fresh review is required',\n },\n verification_stuck: {\n park: 'verification exhausted its cycles without passing',\n unpark: 're-drive the verification feedback to a resumed work agent',\n },\n 'dead-end-rebuild': {\n park: 'dead-end recovery exhausted 25 requeues',\n unpark: 'operator decision — decompose or pan unstick after the root cause is fixed',\n },\n main_diverged: {\n park: 'the PR branch diverged from main and cannot merge cleanly',\n unpark: 'sync-main and resolve conflicts, then re-drive through the normal pipeline',\n },\n model_divergence: {\n park: 'the agent hit a model/API divergence error and was parked for investigation',\n unpark: 'investigate the model error (pane + transcript), then pan unstick and resume',\n },\n usage_limit: {\n park: 'the agent hit a provider usage limit and stopped mid-flight',\n unpark: 'wait for the provider window to reset, then pan unstick and resume',\n },\n context_overflow: {\n park: 'the agent overflowed its context window and cannot continue in this session',\n unpark: 'pan unstick after compaction/fork — resume with a fresh session',\n },\n review_convoy_unrecoverable: {\n park: 'the review convoy died and could not be recovered in place',\n unpark: 're-dispatch a fresh review convoy through the review door after the root cause is resolved',\n },\n test_signal_strand: {\n park: 'a test verdict was written but never delivered to the pipeline',\n unpark: 're-drive the stranded verdict to a resumed work agent',\n },\n 'review-not-converging': {\n park: 'review cycles stopped converging (stall or reversal across ≥3 cycles) — rework is suppressed',\n unpark: 'decompose into sibling issues, or pan unstick to clear the gate and attempt rework',\n },\n state_derived_verification_hold: {\n park: 'verification is held by a derived-state rule (the recorded state forbids advancing)',\n unpark: 'fix the underlying state mismatch, then pan unstick to release the hold',\n },\n};\n\n/** Idle threshold for the idle-running orbit: below this a live idle agent is warm, not parked. */\nexport const IDLE_RUNNING_THRESHOLD_MS = 6 * 60 * 60_000;\n\n/**\n * Roles the idle-running orbit must NEVER touch: orchestrators and\n * conversations. Their real activity lives in the conversation/runtime plane,\n * not the agents-table lastActivity this orbit reads — the sweeper's first\n * night proved the trap when its idle nudge→stop killed the FLYWHEEL (its\n * agents-table stamp was a day stale while it ticked normally), halting all\n * new dispatch for 90 minutes. A \"stopped orchestrator\" is not a freed slot;\n * it is the pipeline going silent.\n */\nexport const IDLE_EXEMPT_ROLES: ReadonlySet<string> = new Set(['flywheel', 'sequencer', 'conversation', 'knowledge']);\n\n/** Per-issue gathered signals — the classifier's entire input. Gathered through read doors, never stores. */\nexport interface ParkedSignals {\n issueId: string;\n reviewStatus: ReviewStatus | null;\n /** All agents (any status) carrying this issue id. */\n agents: AgentState[];\n /** Live (running + tmux-active) agents for this issue. */\n liveAgents: (AgentState & { tmuxActive: boolean })[];\n /** Open recovery trips from the permanent record (needs-you orbit). */\n openRecoveryTrips: { recoveryPath: string; needsYouEmittedAt?: string }[];\n /** Tracker-closed (only resolved for live-agent candidates; null = unknown/not checked). */\n issueClosed: boolean | null;\n now: number;\n}\n\nfunction isoOr(ts: string | number | null | undefined, fallback: number): string {\n if (typeof ts === 'number' && Number.isFinite(ts)) return new Date(ts).toISOString();\n if (typeof ts === 'string' && ts) {\n const parsed = Date.parse(ts);\n if (Number.isFinite(parsed)) return new Date(parsed).toISOString();\n }\n return new Date(fallback).toISOString();\n}\n\n/**\n * `pan done` marks the work agent stoppedByUser as an auto-resume suppressor\n * (done.ts:439/:869 — finished, don't idle-revive; the PAN-2668 completed-\n * handoff exception is the sanctioned rework path). That is a NORMAL lifecycle\n * completion, not an operator park — reporting it as an operator gate makes\n * the sweeper escalate false gates every TTL (observed on PAN-3512 at\n * 00:02:26Z, self-cleared 80s later by the rework resume). A completed\n * handoff marker means finished, not parked.\n */\nexport function hasCompletedHandoffMarker(agentId: string): boolean {\n const dir = join(getOverdeckHome(), 'agents', agentId);\n return existsSync(join(dir, 'completed')) || existsSync(join(dir, 'completed.processed'));\n}\n\n/**\n * Agent rows occasionally carry a bare numeric issue id (\"2156\") where every\n * other surface keys \"PAN-2156\" — without normalization the issue loses its\n * review-status row and trip enrichment. Uppercase always; for bare numerics\n * that fail project resolution, retry with the PAN- prefix.\n */\nexport function normalizeParkedIssueId(raw: string): string {\n const upper = raw.trim().toUpperCase();\n if (!/^\\d+$/.test(upper)) return upper;\n if (resolveProjectFromIssueSync(upper)) return upper;\n return `PAN-${upper}`;\n}\n\nfunction stuckCopy(reason: string | undefined): { park: string; unpark: string } {\n if (reason && STUCK_REASON_COPY[reason]) return STUCK_REASON_COPY[reason];\n return {\n park: `stuck flag set${reason ? ` (${reason})` : ''} — nothing autonomous will advance this issue`,\n unpark: 'pan unstick after the underlying cause is fixed',\n };\n}\n\n/**\n * Classify one issue's parked orbits from gathered signals. Pure — unit-tested\n * with fixtures. Emits at most one row per orbit; an issue may legitimately\n * occupy several orbits at once (e.g. a stuck flag AND a dead operator-stopped\n * agent). idle-running is only emitted when no other orbit already explains\n * the stall — it is the orbit of last resort (\"live but leaderless\").\n */\nexport function classifyParked(s: ParkedSignals): ParkedRow[] {\n const rows: ParkedRow[] = [];\n const r = s.reviewStatus;\n const issueId = s.issueId;\n const push = (orbit: ParkedOrbit, parkedAt: string, parkReason: string, unparkCondition: string, details?: Record<string, unknown>) => {\n rows.push({ issueId, orbit, parkedAt, parkReason, unparkCondition, ...(details ? { details } : {}) });\n };\n\n // Terminal issues are never parked — they are residue. A closed issue can\n // only produce zombie-session rows (a live agent to reap); every other orbit\n // is moot once the issue is done. issueClosed === null means \"unknown\" — the\n // gather resolves it for row-producing issues and re-classifies (pass 2).\n const closed = s.issueClosed === true;\n\n // 1. stuck-flag\n if (!closed && r?.stuck) {\n const copy = stuckCopy(r.stuckReason);\n push('stuck-flag', isoOr(r.stuckAt ?? r.updatedAt, s.now), copy.park, copy.unpark, { stuckReason: r.stuckReason ?? null });\n }\n\n // 2. needs-you (open recovery trips from the permanent record)\n if (!closed) for (const trip of s.openRecoveryTrips) {\n push(\n 'needs-you',\n isoOr(trip.needsYouEmittedAt, s.now),\n `a durable needs-you escalation fired (${trip.recoveryPath}) and went silent — the operator never answered`,\n 'answer the escalation (pan answer / dashboard needs-you), or let the sweeper re-surface it on its TTL',\n { recoveryPath: trip.recoveryPath },\n );\n }\n\n // 3. deacon-ignored\n if (!closed && r?.deaconIgnored) {\n push(\n 'deacon-ignored',\n isoOr(r.deaconIgnoredAt ?? r.updatedAt, s.now),\n `deacon is ignoring this issue${r.deaconIgnoredReason ? ` — ${r.deaconIgnoredReason}` : ''}`,\n 'clear the ignore flag once the reason it was set is resolved',\n { reason: r.deaconIgnoredReason ?? null },\n );\n }\n\n // 4. operator-gate — operator-set resume gates on this issue's agents,\n // grouped one row per gate kind (an issue with three stopped agents has\n // ONE operator-stop park, not three). A scheduler YIELD reuses the paused\n // flag but is self-clearing (the preemptive scheduler resumes yielded\n // agents oldest-first) — NOT a park.\n if (!closed) {\n const gateRows = new Map<'paused' | 'troubled' | 'stopped-by-user', { parkedAt: string; agentIds: string[]; reason: string; unpark: string }>();\n for (const agent of s.agents) {\n if (agent.paused === true && agent.yieldedByScheduler !== true) {\n const row = gateRows.get('paused') ?? { parkedAt: isoOr(agent.pausedAt, s.now), agentIds: [], reason: '', unpark: '' };\n row.agentIds.push(agent.id);\n const at = isoOr(agent.pausedAt, s.now);\n if (at < row.parkedAt) row.parkedAt = at;\n if (agent.pausedReason && !row.reason) row.reason = agent.pausedReason;\n gateRows.set('paused', row);\n } else if (agent.troubled === true) {\n const row = gateRows.get('troubled') ?? { parkedAt: isoOr(agent.troubledAt, s.now), agentIds: [], reason: '', unpark: '' };\n row.agentIds.push(agent.id);\n const at = isoOr(agent.troubledAt, s.now);\n if (at < row.parkedAt) row.parkedAt = at;\n gateRows.set('troubled', row);\n } else if (agent.stoppedByUser === true && agent.status !== 'running' && !hasCompletedHandoffMarker(agent.id)) {\n const row = gateRows.get('stopped-by-user') ?? { parkedAt: isoOr(agent.stoppedAt, s.now), agentIds: [], reason: '', unpark: '' };\n row.agentIds.push(agent.id);\n const at = isoOr(agent.stoppedAt, s.now);\n if (at < row.parkedAt) row.parkedAt = at;\n gateRows.set('stopped-by-user', row);\n }\n }\n for (const [gate, row] of gateRows) {\n const names = row.agentIds.join(', ');\n if (gate === 'paused') {\n push('operator-gate', row.parkedAt, `${names} manually paused${row.reason ? ` — ${row.reason}` : ''}`, 'pan unpause <id> (operator-only; the sweeper re-surfaces on TTL, never overrides)', { gate, agentIds: row.agentIds });\n } else if (gate === 'troubled') {\n push('operator-gate', row.parkedAt, `${names} marked troubled after repeated resume/crash failures`, 'pan untroubled <id> after the crash cause is investigated', { gate, agentIds: row.agentIds });\n } else {\n push('operator-gate', row.parkedAt, `${names} explicitly stopped by the operator with no completed handoff to re-drive`, 'pan start <id> (operator-only; explicit start clears the stop gate)', { gate, agentIds: row.agentIds });\n }\n }\n }\n\n // 5. uat-failed — the feedback relay already owns rework while work is live.\n const hasLiveWorkAgent = s.liveAgents.some((agent) => agent.role === 'work');\n if (!closed && r?.uatStatus === 'failed' && r.mergeStatus !== 'merged' && !r.readyForMerge && !hasLiveWorkAgent) {\n push(\n 'uat-failed',\n isoOr(r.updatedAt, s.now),\n 'UAT failed and no work agent is live to rework it — the merge gate will not take the issue and the UAT-failure relay found no delivery target',\n 'pan start <id> to put a work agent on the UAT feedback; the relay redelivers on the next failed verdict',\n { uatNotes: r.uatNotes ?? null },\n );\n }\n\n // 6. merge-failed — a failed merge that nothing is retrying\n if (!closed && r?.mergeStatus === 'failed') {\n const retries = r.mergeRetryCount ?? 0;\n push(\n 'merge-failed',\n isoOr(r.updatedAt, s.now),\n retries >= FAILED_MERGE_MAX_RETRIES\n ? `merge failed and its ${FAILED_MERGE_MAX_RETRIES} automatic retries are exhausted`\n : 'merge failed and no retry is in flight',\n 'one fresh merge attempt once main CI is green and the branch is conflict-free (sweeper tries once per scan window)',\n { mergeRetryCount: retries, mergeNotes: r.mergeNotes ?? null },\n );\n }\n\n // 7. zombie-session — live agent whose issue is already merged/closed\n for (const agent of s.liveAgents) {\n const merged = r?.mergeStatus === 'merged' || s.issueClosed === true;\n if (!merged) continue;\n push(\n 'zombie-session',\n isoOr(agent.lastActivity ?? agent.startedAt, s.now),\n `${agent.id} is still running but the issue is ${r?.mergeStatus === 'merged' ? 'merged' : 'closed'} — it holds a session and a concurrency slot for nothing`,\n 'reap the session through the established merged-zombie teardown door',\n { agentId: agent.id, mergeStatus: r?.mergeStatus ?? null },\n );\n }\n\n // 9. circuit-breaker — dead-end recovery exhausted\n const requeues = r?.autoRequeueCount ?? 0;\n if (!closed && requeues >= 25) {\n push(\n 'circuit-breaker',\n isoOr(r?.updatedAt, s.now),\n `dead-end recovery used all ${requeues}/25 requeues — the circuit breaker is permanently open`,\n 'operator decision — decompose the change or pan unstick after the root cause is fixed',\n { autoRequeueCount: requeues },\n );\n }\n\n // 8. idle-running — live agent, no pipeline owner, idle beyond threshold, and\n // no other orbit already explains the stall (orbit of last resort).\n if (!closed && rows.length === 0) {\n for (const agent of s.liveAgents) {\n // Orchestrators and conversations are exempt (IDLE_EXEMPT_ROLES): their\n // activity does not live in the stamp this orbit reads, and stopping one\n // silences the pipeline instead of freeing a slot.\n if (IDLE_EXEMPT_ROLES.has(String(agent.role ?? ''))) continue;\n const lastMs = Date.parse(agent.lastActivity ?? agent.startedAt ?? '');\n if (!Number.isFinite(lastMs)) continue;\n const idleMs = s.now - lastMs;\n if (idleMs < IDLE_RUNNING_THRESHOLD_MS) continue;\n // Warm-idle on a pipeline-owned issue is the intended state (PAN-2579):\n // review/test/merge owns the next move, the agent is SUPPOSED to wait.\n if (shouldSkipReviewStatus(r)) continue;\n push(\n 'idle-running',\n new Date(lastMs).toISOString(),\n `${agent.id} is alive but has done nothing for ${Math.floor(idleMs / 60_000)} minutes and no pipeline stage owns the next move`,\n 'poke for progress; if none, stop or resume with a nudge through the established agent-control door',\n { agentId: agent.id, idleMinutes: Math.floor(idleMs / 60_000) },\n );\n }\n }\n\n return rows;\n}\n\n/** Options for the gather pass — injectable for tests. */\nexport interface ResolveParkedOptions {\n now?: number;\n /** Per-issue open recovery trips (defaults to the record door). */\n readOpenTrips?: (issueId: string) => Promise<{ recoveryPath: string; needsYouEmittedAt?: string }[]>;\n /** Tracker-closed check (defaults to isIssueClosed). Only called for live-agent candidates. */\n isClosed?: (issueId: string) => Promise<boolean>;\n /**\n * Cheap local terminality evidence from the per-issue record (defaults to\n * defaultReadRecordTerminal). Checked BEFORE any tracker call — a record\n * this resolver already reads for trips, so a tracker blip can never\n * resurrect a record-terminal issue into the parked population (PAN-3727).\n */\n readRecordTerminal?: (issueId: string) => Promise<boolean>;\n}\n\n/**\n * Record-level terminality via the shared isRecordPipelineTerminal predicate\n * (also used by the terminal-issue residue patrol, PAN-3727) — closedOut, or\n * mergeStatus='merged' with no reopenedAt. Any throw or missing record is\n * \"not terminal\" so this check can only suppress, never invent, a park.\n */\nexport async function defaultReadRecordTerminal(issueId: string): Promise<boolean> {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return false;\n const project = getProjectSync(resolved.projectKey);\n if (!project) return false;\n const record = await readIssueRecord(project, issueId);\n if (!record) return false;\n return isRecordPipelineTerminal(record);\n } catch {\n return false;\n }\n}\n\nasync function defaultReadOpenTrips(issueId: string): Promise<{ recoveryPath: string; needsYouEmittedAt?: string }[]> {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return [];\n const project = getProjectSync(resolved.projectKey);\n if (!project) return [];\n const record = await readIssueRecord(project, issueId);\n return (record?.recoveryTrips ?? [])\n .filter((trip) => trip.open === true)\n .map((trip) => ({ recoveryPath: trip.recoveryPath, ...(trip.needsYouEmittedAt ? { needsYouEmittedAt: trip.needsYouEmittedAt } : {}) }));\n } catch {\n // A record that cannot be read must never fail the whole resolve — the\n // issue simply loses its needs-you enrichment for this pass.\n return [];\n }\n}\n\n/**\n * Gather signals through the read doors and classify every candidate issue.\n * Candidates = every issue with a review_status row OR a registered agent —\n * the bounded in-flight universe (~dozens), never the 800-row backlog (an\n * untouched backlog issue is not parked; it was never started).\n *\n * Returns rows sorted oldest-first (the sweep order).\n */\nexport async function resolveParkedPopulation(options: ResolveParkedOptions = {}): Promise<ParkedRow[]> {\n const now = options.now ?? Date.now();\n const readTrips = options.readOpenTrips ?? defaultReadOpenTrips;\n const isClosed = options.isClosed ?? isIssueClosed;\n const readRecordTerminal = options.readRecordTerminal ?? defaultReadRecordTerminal;\n\n const statuses = loadReviewStatuses();\n const allAgents = listAgentStates();\n const liveAgents = listRunningAgentsSync().filter((a) => a.tmuxActive && (a.status === 'running' || a.status === 'starting'));\n\n const agentsByIssue = new Map<string, AgentState[]>();\n for (const agent of allAgents) {\n const issueId = normalizeParkedIssueId(agent.issueId ?? '');\n if (!issueId) continue;\n const list = agentsByIssue.get(issueId) ?? [];\n list.push(agent);\n agentsByIssue.set(issueId, list);\n }\n const liveByIssue = new Map<string, (AgentState & { tmuxActive: boolean })[]>();\n for (const agent of liveAgents) {\n const issueId = normalizeParkedIssueId(agent.issueId ?? '');\n if (!issueId) continue;\n const list = liveByIssue.get(issueId) ?? [];\n list.push(agent);\n liveByIssue.set(issueId, list);\n }\n\n const candidateIds = new Set<string>([...Object.keys(statuses).map((id) => id.toUpperCase()), ...agentsByIssue.keys(), ...liveByIssue.keys()]);\n\n // Pass 1: classify with closedness unknown (null) except where cheap local\n // evidence already decides (live-agent zombie checks resolve it below).\n // Pass 2: for every issue that PRODUCED a row, resolve tracker-closed\n // (TTL-cached, shadow-state-first) and re-classify — a closed issue keeps\n // only its zombie-session rows; every other orbit is moot residue.\n const closedByIssue = new Map<string, boolean>();\n const classifyOne = async (issueId: string): Promise<ParkedRow[]> => {\n const statusKey = Object.keys(statuses).find((key) => key.toUpperCase() === issueId) ?? issueId;\n const reviewStatus = statuses[statusKey] ?? null;\n const live = liveByIssue.get(issueId) ?? [];\n let issueClosed = closedByIssue.get(issueId) ?? null;\n if (issueClosed === null && await readRecordTerminal(issueId)) {\n // Cheap local terminality evidence decides before any tracker call — a\n // tracker blip (fail-open toward \"open\", negative-cached for minutes)\n // must never resurrect a record-terminal issue into the population.\n issueClosed = true;\n closedByIssue.set(issueId, true);\n } else if (issueClosed === null && live.length > 0 && reviewStatus?.mergeStatus !== 'merged') {\n // Zombie detection needs tracker-closed only when the cheap local signals\n // can't decide (strikes bypass the review pipeline, so mergeStatus never\n // records their merge). Bound the check to live-agent candidates.\n try { issueClosed = await isClosed(issueId); closedByIssue.set(issueId, issueClosed); } catch { issueClosed = null; }\n }\n const trips = issueClosed === true ? [] : await readTrips(issueId);\n return classifyParked({\n issueId,\n reviewStatus,\n agents: agentsByIssue.get(issueId) ?? [],\n liveAgents: live,\n openRecoveryTrips: trips,\n issueClosed,\n now,\n });\n };\n\n const firstPass = new Map<string, ParkedRow[]>();\n for (const issueId of candidateIds) {\n const rows = await classifyOne(issueId);\n if (rows.length > 0) firstPass.set(issueId, rows);\n }\n const rows: ParkedRow[] = [];\n for (const [issueId, produced] of firstPass) {\n if (!closedByIssue.has(issueId)) {\n try { closedByIssue.set(issueId, await isClosed(issueId)); } catch { /* unknown — keep the open-issue reading */ }\n }\n if (closedByIssue.get(issueId) === true) {\n // Closed: residue — keep only zombie-session rows (reap candidates).\n rows.push(...produced.filter((row) => row.orbit === 'zombie-session'));\n } else {\n rows.push(...produced);\n }\n }\n\n return rows.sort((a, b) => a.parkedAt.localeCompare(b.parkedAt) || a.issueId.localeCompare(b.issueId));\n}\n\n/** Rollup for dashboards: count by orbit plus the primary (most severe) orbit per issue. */\nexport function summarizeParked(rows: readonly ParkedRow[]): { total: number; byOrbit: Record<string, number>; primaryByIssue: Record<string, ParkedOrbit> } {\n const byOrbit: Record<string, number> = {};\n const primaryByIssue: Record<string, ParkedOrbit> = {};\n const issues = new Set<string>();\n for (const row of rows) {\n byOrbit[row.orbit] = (byOrbit[row.orbit] ?? 0) + 1;\n issues.add(row.issueId);\n const current = primaryByIssue[row.issueId];\n if (!current || PARKED_ORBIT_SEVERITY.indexOf(row.orbit) < PARKED_ORBIT_SEVERITY.indexOf(current)) {\n primaryByIssue[row.issueId] = row.orbit;\n }\n }\n return { total: issues.size, byOrbit, primaryByIssue };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"resolver-BeNrqNou.js","names":[],"sources":["../../src/lib/parked/resolver.ts"],"sourcesContent":["/**\n * Single authoritative parked-population resolver (PAN-3485, epic phase 1).\n *\n * A \"parked orbit\" is any state an issue can sit in where no autonomous actor\n * will advance it within 24h without operator or flywheel intervention. Over\n * months of incident response, each failure mode grew its own safety valve —\n * stuck flags, needs-you trips, deacon-ignore, resume gates, UAT gates, merge\n * retry caps, and circuit breakers — and every one of them converts autonomous\n * motion into operator work. Nine of those valves exist today, in six subsystems,\n * and no surface could answer \"what is stalled, why, and what would release it.\"\n * This resolver is that answer: ONE read door that unions all nine orbits into\n * typed rows, so the CLI, the API, the dashboard, and the stall sweeper all agree\n * by construction.\n *\n * Modeled on `resolvePipelineMembership()` (src/lib/pipeline-membership.ts):\n * a pure classifier over gathered signals, with the gathering done here through\n * existing read doors only (review-status door, agents table, tmux liveness,\n * the per-issue record door for recovery trips). No surface may re-derive\n * parking independently.\n *\n * The nine orbits (see docs/PARKED-POPULATION.md):\n *\n * 1. stuck-flag review_status.stuck = 1 (any stuck_reason)\n * 2. needs-you an open recovery trip in the permanent record\n * 3. deacon-ignored review_status.deaconIgnored = true\n * 4. operator-gate paused (operator, not yield) / troubled / stoppedByUser\n * 5. uat-failed uatStatus failed with merge still pending\n * 6. merge-failed mergeStatus failed (retries saturated or abandoned)\n * 7. zombie-session live agent whose issue is merged/closed\n * 8. idle-running live agent, no pipeline owner, idle beyond threshold\n * 9. circuit-breaker autoRequeueCount >= 25 (dead-end recovery exhausted)\n */\n\nimport { loadReviewStatuses, type ReviewStatus } from '../review-status.js';\nimport { listAgentStates, listRunningAgentsSync } from '../agents/queries.js';\nimport type { AgentState } from '../agents.js';\nimport { FAILED_MERGE_MAX_RETRIES } from '../cloister/deacon-merge.js';\nimport { shouldSkipReviewStatus } from '../cloister/stuck-remediation.js';\nimport { isIssueClosed } from '../cloister/issue-closed.js';\nimport { readIssueRecord } from '../pan-dir/record.js';\nimport { getProjectSync, resolveProjectFromIssueSync } from '../projects.js';\nimport { isRecordPipelineTerminal } from '../cloister/parked-residue.js';\nimport { existsSync } from 'fs';\nimport { join } from 'path';\nimport { getOverdeckHome } from '../paths.js';\n\nexport const PARKED_ORBITS = [\n 'stuck-flag',\n 'needs-you',\n 'deacon-ignored',\n 'operator-gate',\n 'uat-failed',\n 'merge-failed',\n 'zombie-session',\n 'idle-running',\n 'circuit-breaker',\n] as const;\n\nexport type ParkedOrbit = (typeof PARKED_ORBITS)[number];\n\n/**\n * Severity order — recommendations surface these orbits in this order, with\n * operator-gated rows last so a full report ends by surfacing what only a human\n * can release. The God View tints an orb\n * by its most severe orbit.\n */\nexport const PARKED_ORBIT_SEVERITY: readonly ParkedOrbit[] = [\n 'zombie-session',\n 'merge-failed',\n 'uat-failed',\n 'stuck-flag',\n 'circuit-breaker',\n 'idle-running',\n 'needs-you',\n 'deacon-ignored',\n 'operator-gate',\n];\n\nexport interface ParkedRow {\n issueId: string;\n orbit: ParkedOrbit;\n /** ISO timestamp the issue entered this orbit (best available evidence). */\n parkedAt: string;\n /** Operator-facing sentence: why the issue is parked. */\n parkReason: string;\n /** Operator-facing sentence: what would release it. */\n unparkCondition: string;\n /** Orbit-specific evidence (stuck reason, gate kind, idle minutes, …). */\n details?: Record<string, unknown>;\n}\n\n/** A human sentence per stuck_reason — the stuck flag carries the machine code, the resolver owns the copy.\n * GUARD-EXIT INVARIANT (PAN-3488): every stuck_reason written anywhere in src/ MUST have an entry\n * here — scripts/guard-park-exits.sh fails the lint on an undocumented flavor. */\nconst STUCK_REASON_COPY: Record<string, { park: string; unpark: string }> = {\n feedback_delivery_needs_you: {\n park: 'review/test feedback could not be delivered — the work agent is not running and nothing resumed it',\n unpark: 'resume the work agent with its pending feedback through the established work-resume door',\n },\n review_infrastructure_failure: {\n park: 'the review pipeline failed repeatedly for infrastructure reasons, not verdict reasons',\n unpark: 're-dispatch a fresh review through the review door once the infra cause is resolved',\n },\n review_parent_stalled_needs_you: {\n park: 'the review parent exceeded its deadline with no terminal verdict in the row or verdict artifact',\n unpark: 'inspect the parent pane and review artifacts, then pan unstick <id> and pan review restart <id> if a fresh review is required',\n },\n verification_stuck: {\n park: 'verification exhausted its cycles without passing',\n unpark: 're-drive the verification feedback to a resumed work agent',\n },\n 'dead-end-rebuild': {\n park: 'dead-end recovery exhausted 25 requeues',\n unpark: 'operator decision — decompose or pan unstick after the root cause is fixed',\n },\n main_diverged: {\n park: 'the PR branch diverged from main and cannot merge cleanly',\n unpark: 'sync-main and resolve conflicts, then re-drive through the normal pipeline',\n },\n model_divergence: {\n park: 'the agent hit a model/API divergence error and was parked for investigation',\n unpark: 'investigate the model error (pane + transcript), then pan unstick and resume',\n },\n usage_limit: {\n park: 'the agent hit a provider usage limit and stopped mid-flight',\n unpark: 'wait for the provider window to reset, then pan unstick and resume',\n },\n context_overflow: {\n park: 'the agent overflowed its context window and cannot continue in this session',\n unpark: 'pan unstick after compaction/fork — resume with a fresh session',\n },\n review_convoy_unrecoverable: {\n park: 'the review convoy died and could not be recovered in place',\n unpark: 're-dispatch a fresh review convoy through the review door after the root cause is resolved',\n },\n test_signal_strand: {\n park: 'a test verdict was written but never delivered to the pipeline',\n unpark: 're-drive the stranded verdict to a resumed work agent',\n },\n 'review-not-converging': {\n park: 'review cycles stopped converging (stall or reversal across ≥3 cycles) — rework is suppressed',\n unpark: 'decompose into sibling issues, or pan unstick to clear the gate and attempt rework',\n },\n state_derived_verification_hold: {\n park: 'verification is held by a derived-state rule (the recorded state forbids advancing)',\n unpark: 'fix the underlying state mismatch, then pan unstick to release the hold',\n },\n};\n\n/** Idle threshold for the idle-running orbit: below this a live idle agent is warm, not parked. */\nexport const IDLE_RUNNING_THRESHOLD_MS = 6 * 60 * 60_000;\n\n/**\n * Roles the idle-running orbit must NEVER touch: orchestrators and\n * conversations. Their real activity lives in the conversation/runtime plane,\n * not the agents-table lastActivity this orbit reads — the sweeper's first\n * night proved the trap when its idle nudge→stop killed the FLYWHEEL (its\n * agents-table stamp was a day stale while it ticked normally), halting all\n * new dispatch for 90 minutes. A \"stopped orchestrator\" is not a freed slot;\n * it is the pipeline going silent.\n */\nexport const IDLE_EXEMPT_ROLES: ReadonlySet<string> = new Set(['flywheel', 'sequencer', 'conversation', 'knowledge']);\n\n/** Per-issue gathered signals — the classifier's entire input. Gathered through read doors, never stores. */\nexport interface ParkedSignals {\n issueId: string;\n reviewStatus: ReviewStatus | null;\n /** All agents (any status) carrying this issue id. */\n agents: AgentState[];\n /** Live (running + tmux-active) agents for this issue. */\n liveAgents: (AgentState & { tmuxActive: boolean })[];\n /** Open recovery trips from the permanent record (needs-you orbit). */\n openRecoveryTrips: { recoveryPath: string; needsYouEmittedAt?: string }[];\n /** Tracker-closed (only resolved for live-agent candidates; null = unknown/not checked). */\n issueClosed: boolean | null;\n now: number;\n}\n\nfunction isoOr(ts: string | number | null | undefined, fallback: number): string {\n if (typeof ts === 'number' && Number.isFinite(ts)) return new Date(ts).toISOString();\n if (typeof ts === 'string' && ts) {\n const parsed = Date.parse(ts);\n if (Number.isFinite(parsed)) return new Date(parsed).toISOString();\n }\n return new Date(fallback).toISOString();\n}\n\n/**\n * `pan done` marks the work agent stoppedByUser as an auto-resume suppressor\n * (done.ts:439/:869 — finished, don't idle-revive; the PAN-2668 completed-\n * handoff exception is the sanctioned rework path). That is a NORMAL lifecycle\n * completion, not an operator park — reporting it as an operator gate makes\n * the sweeper escalate false gates every TTL (observed on PAN-3512 at\n * 00:02:26Z, self-cleared 80s later by the rework resume). A completed\n * handoff marker means finished, not parked.\n */\nexport function hasCompletedHandoffMarker(agentId: string): boolean {\n const dir = join(getOverdeckHome(), 'agents', agentId);\n return existsSync(join(dir, 'completed')) || existsSync(join(dir, 'completed.processed'));\n}\n\n/**\n * Agent rows occasionally carry a bare numeric issue id (\"2156\") where every\n * other surface keys \"PAN-2156\" — without normalization the issue loses its\n * review-status row and trip enrichment. Uppercase always; for bare numerics\n * that fail project resolution, retry with the PAN- prefix.\n */\nexport function normalizeParkedIssueId(raw: string): string {\n const upper = raw.trim().toUpperCase();\n if (!/^\\d+$/.test(upper)) return upper;\n if (resolveProjectFromIssueSync(upper)) return upper;\n return `PAN-${upper}`;\n}\n\nfunction stuckCopy(reason: string | undefined): { park: string; unpark: string } {\n if (reason && STUCK_REASON_COPY[reason]) return STUCK_REASON_COPY[reason];\n return {\n park: `stuck flag set${reason ? ` (${reason})` : ''} — nothing autonomous will advance this issue`,\n unpark: 'pan unstick after the underlying cause is fixed',\n };\n}\n\n/**\n * Classify one issue's parked orbits from gathered signals. Pure — unit-tested\n * with fixtures. Emits at most one row per orbit; an issue may legitimately\n * occupy several orbits at once (e.g. a stuck flag AND a dead operator-stopped\n * agent). idle-running is only emitted when no other orbit already explains\n * the stall — it is the orbit of last resort (\"live but leaderless\").\n */\nexport function classifyParked(s: ParkedSignals): ParkedRow[] {\n const rows: ParkedRow[] = [];\n const r = s.reviewStatus;\n const issueId = s.issueId;\n const push = (orbit: ParkedOrbit, parkedAt: string, parkReason: string, unparkCondition: string, details?: Record<string, unknown>) => {\n rows.push({ issueId, orbit, parkedAt, parkReason, unparkCondition, ...(details ? { details } : {}) });\n };\n\n // Terminal issues are never parked — they are residue. A closed issue can\n // only produce zombie-session rows (a live agent to reap); every other orbit\n // is moot once the issue is done. issueClosed === null means \"unknown\" — the\n // gather resolves it for row-producing issues and re-classifies (pass 2).\n const closed = s.issueClosed === true;\n\n // 1. stuck-flag\n if (!closed && r?.stuck) {\n const copy = stuckCopy(r.stuckReason);\n push('stuck-flag', isoOr(r.stuckAt ?? r.updatedAt, s.now), copy.park, copy.unpark, { stuckReason: r.stuckReason ?? null });\n }\n\n // 2. needs-you (open recovery trips from the permanent record)\n if (!closed) for (const trip of s.openRecoveryTrips) {\n push(\n 'needs-you',\n isoOr(trip.needsYouEmittedAt, s.now),\n `a durable needs-you escalation fired (${trip.recoveryPath}) and went silent — the operator never answered`,\n 'answer the escalation (pan answer / dashboard needs-you), or let the sweeper re-surface it on its TTL',\n { recoveryPath: trip.recoveryPath },\n );\n }\n\n // 3. deacon-ignored\n if (!closed && r?.deaconIgnored) {\n push(\n 'deacon-ignored',\n isoOr(r.deaconIgnoredAt ?? r.updatedAt, s.now),\n `deacon is ignoring this issue${r.deaconIgnoredReason ? ` — ${r.deaconIgnoredReason}` : ''}`,\n 'clear the ignore flag once the reason it was set is resolved',\n { reason: r.deaconIgnoredReason ?? null },\n );\n }\n\n // 4. operator-gate — operator-set resume gates on this issue's agents,\n // grouped one row per gate kind (an issue with three stopped agents has\n // ONE operator-stop park, not three). A scheduler YIELD reuses the paused\n // flag but is self-clearing (the preemptive scheduler resumes yielded\n // agents oldest-first) — NOT a park.\n if (!closed) {\n const gateRows = new Map<'paused' | 'troubled' | 'stopped-by-user', { parkedAt: string; agentIds: string[]; reason: string; unpark: string }>();\n for (const agent of s.agents) {\n if (agent.paused === true && agent.yieldedByScheduler !== true) {\n const row = gateRows.get('paused') ?? { parkedAt: isoOr(agent.pausedAt, s.now), agentIds: [], reason: '', unpark: '' };\n row.agentIds.push(agent.id);\n const at = isoOr(agent.pausedAt, s.now);\n if (at < row.parkedAt) row.parkedAt = at;\n if (agent.pausedReason && !row.reason) row.reason = agent.pausedReason;\n gateRows.set('paused', row);\n } else if (agent.troubled === true) {\n const row = gateRows.get('troubled') ?? { parkedAt: isoOr(agent.troubledAt, s.now), agentIds: [], reason: '', unpark: '' };\n row.agentIds.push(agent.id);\n const at = isoOr(agent.troubledAt, s.now);\n if (at < row.parkedAt) row.parkedAt = at;\n gateRows.set('troubled', row);\n } else if (agent.stoppedByUser === true && agent.status !== 'running' && !hasCompletedHandoffMarker(agent.id)) {\n const row = gateRows.get('stopped-by-user') ?? { parkedAt: isoOr(agent.stoppedAt, s.now), agentIds: [], reason: '', unpark: '' };\n row.agentIds.push(agent.id);\n const at = isoOr(agent.stoppedAt, s.now);\n if (at < row.parkedAt) row.parkedAt = at;\n gateRows.set('stopped-by-user', row);\n }\n }\n for (const [gate, row] of gateRows) {\n const names = row.agentIds.join(', ');\n if (gate === 'paused') {\n push('operator-gate', row.parkedAt, `${names} manually paused${row.reason ? ` — ${row.reason}` : ''}`, 'pan unpause <id> (operator-only; the sweeper re-surfaces on TTL, never overrides)', { gate, agentIds: row.agentIds });\n } else if (gate === 'troubled') {\n push('operator-gate', row.parkedAt, `${names} marked troubled after repeated resume/crash failures`, 'pan untroubled <id> after the crash cause is investigated', { gate, agentIds: row.agentIds });\n } else {\n push('operator-gate', row.parkedAt, `${names} explicitly stopped by the operator with no completed handoff to re-drive`, 'pan start <id> (operator-only; explicit start clears the stop gate)', { gate, agentIds: row.agentIds });\n }\n }\n }\n\n // 5. uat-failed — the feedback relay already owns rework while work is live.\n const hasLiveWorkAgent = s.liveAgents.some((agent) => agent.role === 'work');\n if (!closed && r?.uatStatus === 'failed' && r.mergeStatus !== 'merged' && !r.readyForMerge && !hasLiveWorkAgent) {\n push(\n 'uat-failed',\n isoOr(r.updatedAt, s.now),\n 'UAT failed and no work agent is live to rework it — the merge gate will not take the issue and the UAT-failure relay found no delivery target',\n 'pan start <id> to put a work agent on the UAT feedback; the relay redelivers on the next failed verdict',\n { uatNotes: r.uatNotes ?? null },\n );\n }\n\n // 6. merge-failed — a failed merge that nothing is retrying\n if (!closed && r?.mergeStatus === 'failed') {\n const retries = r.mergeRetryCount ?? 0;\n push(\n 'merge-failed',\n isoOr(r.updatedAt, s.now),\n retries >= FAILED_MERGE_MAX_RETRIES\n ? `merge failed and its ${FAILED_MERGE_MAX_RETRIES} automatic retries are exhausted`\n : 'merge failed and no retry is in flight',\n 'one fresh merge attempt once main CI is green and the branch is conflict-free (sweeper tries once per scan window)',\n { mergeRetryCount: retries, mergeNotes: r.mergeNotes ?? null },\n );\n }\n\n // 7. zombie-session — live agent whose issue is already merged/closed\n for (const agent of s.liveAgents) {\n const merged = r?.mergeStatus === 'merged' || s.issueClosed === true;\n if (!merged) continue;\n push(\n 'zombie-session',\n isoOr(agent.lastActivity ?? agent.startedAt, s.now),\n `${agent.id} is still running but the issue is ${r?.mergeStatus === 'merged' ? 'merged' : 'closed'} — it holds a session and a concurrency slot for nothing`,\n 'reap the session through the established merged-zombie teardown door',\n { agentId: agent.id, mergeStatus: r?.mergeStatus ?? null },\n );\n }\n\n // 9. circuit-breaker — dead-end recovery exhausted\n const requeues = r?.autoRequeueCount ?? 0;\n if (!closed && requeues >= 25) {\n push(\n 'circuit-breaker',\n isoOr(r?.updatedAt, s.now),\n `dead-end recovery used all ${requeues}/25 requeues — the circuit breaker is permanently open`,\n 'operator decision — decompose the change or pan unstick after the root cause is fixed',\n { autoRequeueCount: requeues },\n );\n }\n\n // 8. idle-running — live agent, no pipeline owner, idle beyond threshold, and\n // no other orbit already explains the stall (orbit of last resort).\n if (!closed && rows.length === 0) {\n for (const agent of s.liveAgents) {\n // Orchestrators and conversations are exempt (IDLE_EXEMPT_ROLES): their\n // activity does not live in the stamp this orbit reads, and stopping one\n // silences the pipeline instead of freeing a slot.\n if (IDLE_EXEMPT_ROLES.has(String(agent.role ?? ''))) continue;\n const lastMs = Date.parse(agent.lastActivity ?? agent.startedAt ?? '');\n if (!Number.isFinite(lastMs)) continue;\n const idleMs = s.now - lastMs;\n if (idleMs < IDLE_RUNNING_THRESHOLD_MS) continue;\n // Warm-idle on a pipeline-owned issue is the intended state (PAN-2579):\n // review/test/merge owns the next move, the agent is SUPPOSED to wait.\n if (shouldSkipReviewStatus(r)) continue;\n push(\n 'idle-running',\n new Date(lastMs).toISOString(),\n `${agent.id} is alive but has done nothing for ${Math.floor(idleMs / 60_000)} minutes and no pipeline stage owns the next move`,\n 'poke for progress; if none, stop or resume with a nudge through the established agent-control door',\n { agentId: agent.id, idleMinutes: Math.floor(idleMs / 60_000) },\n );\n }\n }\n\n return rows;\n}\n\n/** Options for the gather pass — injectable for tests. */\nexport interface ResolveParkedOptions {\n now?: number;\n /** Per-issue open recovery trips (defaults to the record door). */\n readOpenTrips?: (issueId: string) => Promise<{ recoveryPath: string; needsYouEmittedAt?: string }[]>;\n /** Tracker-closed check (defaults to isIssueClosed). Only called for live-agent candidates. */\n isClosed?: (issueId: string) => Promise<boolean>;\n /**\n * Cheap local terminality evidence from the per-issue record (defaults to\n * defaultReadRecordTerminal). Checked BEFORE any tracker call — a record\n * this resolver already reads for trips, so a tracker blip can never\n * resurrect a record-terminal issue into the parked population (PAN-3727).\n */\n readRecordTerminal?: (issueId: string) => Promise<boolean>;\n}\n\n/**\n * Record-level terminality via the shared isRecordPipelineTerminal predicate\n * (also used by the terminal-issue residue patrol, PAN-3727) — closedOut, or\n * mergeStatus='merged' with no reopenedAt. Any throw or missing record is\n * \"not terminal\" so this check can only suppress, never invent, a park.\n */\nexport async function defaultReadRecordTerminal(issueId: string): Promise<boolean> {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return false;\n const project = getProjectSync(resolved.projectKey);\n if (!project) return false;\n const record = await readIssueRecord(project, issueId);\n if (!record) return false;\n return isRecordPipelineTerminal(record);\n } catch {\n return false;\n }\n}\n\nasync function defaultReadOpenTrips(issueId: string): Promise<{ recoveryPath: string; needsYouEmittedAt?: string }[]> {\n try {\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return [];\n const project = getProjectSync(resolved.projectKey);\n if (!project) return [];\n const record = await readIssueRecord(project, issueId);\n return (record?.recoveryTrips ?? [])\n .filter((trip) => trip.open === true)\n .map((trip) => ({ recoveryPath: trip.recoveryPath, ...(trip.needsYouEmittedAt ? { needsYouEmittedAt: trip.needsYouEmittedAt } : {}) }));\n } catch {\n // A record that cannot be read must never fail the whole resolve — the\n // issue simply loses its needs-you enrichment for this pass.\n return [];\n }\n}\n\n/**\n * Gather signals through the read doors and classify every candidate issue.\n * Candidates = every issue with a review_status row OR a registered agent —\n * the bounded in-flight universe (~dozens), never the 800-row backlog (an\n * untouched backlog issue is not parked; it was never started).\n *\n * Returns rows sorted oldest-first (the sweep order).\n */\nexport async function resolveParkedPopulation(options: ResolveParkedOptions = {}): Promise<ParkedRow[]> {\n const now = options.now ?? Date.now();\n const readTrips = options.readOpenTrips ?? defaultReadOpenTrips;\n const isClosed = options.isClosed ?? isIssueClosed;\n const readRecordTerminal = options.readRecordTerminal ?? defaultReadRecordTerminal;\n\n const statuses = loadReviewStatuses();\n const allAgents = listAgentStates();\n const liveAgents = listRunningAgentsSync().filter((a) => a.tmuxActive && (a.status === 'running' || a.status === 'starting'));\n\n const agentsByIssue = new Map<string, AgentState[]>();\n for (const agent of allAgents) {\n const issueId = normalizeParkedIssueId(agent.issueId ?? '');\n if (!issueId) continue;\n const list = agentsByIssue.get(issueId) ?? [];\n list.push(agent);\n agentsByIssue.set(issueId, list);\n }\n const liveByIssue = new Map<string, (AgentState & { tmuxActive: boolean })[]>();\n for (const agent of liveAgents) {\n const issueId = normalizeParkedIssueId(agent.issueId ?? '');\n if (!issueId) continue;\n const list = liveByIssue.get(issueId) ?? [];\n list.push(agent);\n liveByIssue.set(issueId, list);\n }\n\n const candidateIds = new Set<string>([...Object.keys(statuses).map((id) => id.toUpperCase()), ...agentsByIssue.keys(), ...liveByIssue.keys()]);\n\n // Pass 1: classify with closedness unknown (null) except where cheap local\n // evidence already decides (live-agent zombie checks resolve it below).\n // Pass 2: for every issue that PRODUCED a row, resolve tracker-closed\n // (TTL-cached, shadow-state-first) and re-classify — a closed issue keeps\n // only its zombie-session rows; every other orbit is moot residue.\n const closedByIssue = new Map<string, boolean>();\n const classifyOne = async (issueId: string): Promise<ParkedRow[]> => {\n const statusKey = Object.keys(statuses).find((key) => key.toUpperCase() === issueId) ?? issueId;\n const reviewStatus = statuses[statusKey] ?? null;\n const live = liveByIssue.get(issueId) ?? [];\n let issueClosed = closedByIssue.get(issueId) ?? null;\n if (issueClosed === null && await readRecordTerminal(issueId)) {\n // Cheap local terminality evidence decides before any tracker call — a\n // tracker blip (fail-open toward \"open\", negative-cached for minutes)\n // must never resurrect a record-terminal issue into the population.\n issueClosed = true;\n closedByIssue.set(issueId, true);\n } else if (issueClosed === null && live.length > 0 && reviewStatus?.mergeStatus !== 'merged') {\n // Zombie detection needs tracker-closed only when the cheap local signals\n // can't decide (strikes bypass the review pipeline, so mergeStatus never\n // records their merge). Bound the check to live-agent candidates.\n try { issueClosed = await isClosed(issueId); closedByIssue.set(issueId, issueClosed); } catch { issueClosed = null; }\n }\n const trips = issueClosed === true ? [] : await readTrips(issueId);\n return classifyParked({\n issueId,\n reviewStatus,\n agents: agentsByIssue.get(issueId) ?? [],\n liveAgents: live,\n openRecoveryTrips: trips,\n issueClosed,\n now,\n });\n };\n\n const firstPass = new Map<string, ParkedRow[]>();\n for (const issueId of candidateIds) {\n const rows = await classifyOne(issueId);\n if (rows.length > 0) firstPass.set(issueId, rows);\n }\n const rows: ParkedRow[] = [];\n for (const [issueId, produced] of firstPass) {\n if (!closedByIssue.has(issueId)) {\n try { closedByIssue.set(issueId, await isClosed(issueId)); } catch { /* unknown — keep the open-issue reading */ }\n }\n if (closedByIssue.get(issueId) === true) {\n // Closed: residue — keep only zombie-session rows (reap candidates).\n rows.push(...produced.filter((row) => row.orbit === 'zombie-session'));\n } else {\n rows.push(...produced);\n }\n }\n\n return rows.sort((a, b) => a.parkedAt.localeCompare(b.parkedAt) || a.issueId.localeCompare(b.issueId));\n}\n\n/** Rollup for dashboards: count by orbit plus the primary (most severe) orbit per issue. */\nexport function summarizeParked(rows: readonly ParkedRow[]): { total: number; byOrbit: Record<string, number>; primaryByIssue: Record<string, ParkedOrbit> } {\n const byOrbit: Record<string, number> = {};\n const primaryByIssue: Record<string, ParkedOrbit> = {};\n const issues = new Set<string>();\n for (const row of rows) {\n byOrbit[row.orbit] = (byOrbit[row.orbit] ?? 0) + 1;\n issues.add(row.issueId);\n const current = primaryByIssue[row.issueId];\n if (!current || PARKED_ORBIT_SEVERITY.indexOf(row.orbit) < PARKED_ORBIT_SEVERITY.indexOf(current)) {\n primaryByIssue[row.issueId] = row.orbit;\n }\n }\n return { total: issues.size, byOrbit, primaryByIssue };\n}\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import { Li as getOverdeckHome, Ri as init_paths } from "./config-yaml-Dce9VUd6.js";
2
2
  import { l as init_activity_logger, o as emitActivityEntrySync } from "./activity-logger-DNYDzAOU.js";
3
3
  import { _ as logDeaconEventSync, h as init_persistent_logger } from "./agents-CkngXhQr.js";
4
- import { Yn as init_runtime_census, qn as getRuntimeCensus } from "./agent-state-Ci6PhjOE.js";
5
- import { a as readGovernorWatchReserveBytes, i as readGovernorReserves, r as readGovernorPsiCalmConfig, t as assessMemoryPressure } from "./memory-governor-B7uDWRo9.js";
4
+ import { Yn as init_runtime_census, qn as getRuntimeCensus } from "./agent-state-B196fj1h.js";
5
+ import { a as readGovernorWatchReserveBytes, i as readGovernorReserves, r as readGovernorPsiCalmConfig, t as assessMemoryPressure } from "./memory-governor-Bv3LAqk3.js";
6
6
  import { resolve } from "path";
7
7
  import { homedir } from "os";
8
8
  import { statfs } from "node:fs/promises";
@@ -396,4 +396,4 @@ async function patrolResourcePressure(deps = {}) {
396
396
  //#endregion
397
397
  export { patrolResourcePressure };
398
398
 
399
- //# sourceMappingURL=resource-pressure-patrol-Bg23rPV-.js.map
399
+ //# sourceMappingURL=resource-pressure-patrol-Ccg_aCgO.js.map