@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":"project-resource-refresh-queue-DebwjPPA.js","names":["execFileAsync"],"sources":["../../src/dashboard/server/services/resource-discovery-shared.ts","../../src/dashboard/server/review-status.ts","../../src/dashboard/server/services/pipeline-membership.ts","../../src/dashboard/server/services/resource-discovery-signals.ts","../../src/dashboard/server/services/resource-discovery.ts","../../src/dashboard/server/services/project-resource-refresh-queue.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nimport { listConversations, type LegacyConversation as Conversation } from '../../../lib/overdeck/conversations.js';\nimport { getRuntimeCensus } from '../../../lib/runtime-census.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport interface TrackerIssueRecord {\n identifier?: string;\n title?: string;\n state?: string;\n status?: string;\n rawTrackerState?: string;\n source?: string;\n totalChildCount?: number;\n completedChildCount?: number;\n inProgressChildCount?: number;\n}\n\ninterface ActiveRemoteAgentState {\n issueId: string;\n vmName: string;\n status: string;\n model: string;\n startedAt: string;\n}\n\nexport interface SharedResourceSignals {\n trackerIssues: Map<string, TrackerIssueRecord>;\n tmuxSessions: string[];\n dockerContainers: string[];\n conversations: Conversation[];\n remoteAgentStates: ActiveRemoteAgentState[];\n}\n\nasync function loadTrackerIssues(): Promise<Map<string, TrackerIssueRecord>> {\n const map = new Map<string, TrackerIssueRecord>();\n try {\n const { getSharedIssueService } = await import('./issue-service-singleton.js');\n const service = await getSharedIssueService();\n const issues = service.getIssues() as TrackerIssueRecord[];\n for (const issue of issues) {\n if (issue.identifier) map.set(issue.identifier.toUpperCase(), issue);\n }\n } catch {\n // A cold issue service contributes no tracker signals to this snapshot.\n }\n return map;\n}\n\nasync function loadDockerContainers(): Promise<string[]> {\n try {\n const { stdout } = await execFileAsync('docker', ['ps', '--format', '{{.Names}}'], {\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.split('\\n').map((line) => line.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\nasync function loadActiveRemoteAgentStates(): Promise<ActiveRemoteAgentState[]> {\n try {\n const { listActiveRemoteAgentStates } = await import('../../../lib/remote/remote-agents.js');\n return listActiveRemoteAgentStates();\n } catch {\n return [];\n }\n}\n\nexport async function captureSharedResourceSignals(): Promise<SharedResourceSignals> {\n const [trackerIssues, runtimeCensus, dockerContainers, remoteAgentStates] = await Promise.all([\n loadTrackerIssues(),\n getRuntimeCensus(),\n loadDockerContainers(),\n loadActiveRemoteAgentStates(),\n ]);\n const tmuxSessions = [...runtimeCensus.sessionNames];\n let conversations: Conversation[] = [];\n try {\n conversations = listConversations();\n } catch {\n // Conversation state is optional for resource discovery.\n }\n return { trackerIssues, tmuxSessions, dockerContainers, conversations, remoteAgentStates };\n}\n","/**\n * Re-export from canonical location at src/lib/review-status.ts\n * This file exists only for backward compatibility during the PAN-262 migration.\n */\nexport {\n loadReviewStatuses,\n loadReadyForMergeFlags,\n saveReviewStatuses,\n setReviewStatusSync,\n getReviewStatusSync,\n clearReviewStatus,\n} from '../../lib/review-status.js';\n\nexport type { ReviewStatus, StatusHistoryEntry } from '../../lib/review-status.js';\n","import type { IssuePipelineMembership, MembershipUnavailableReason } from '@overdeck/contracts';\nimport { createPromiseConcurrencyLimiter, createSettledTtlPromiseCache } from '../../../lib/concurrency.js';\nimport {\n gatherProjectLensSignals,\n mapPipelineProjects,\n PipelineMembershipUnavailableError,\n PIPELINE_PROJECT_CONCURRENCY,\n} from '../../../lib/pipeline-membership-gather.js';\nimport type { ProjectConfig } from '../../../lib/projects.js';\nimport { resolvePipelineMembership, type IssueLensSignals, type PipelineMembership } from '../../../lib/pipeline-membership.js';\n\nexport const PIPELINE_MEMBERSHIP_TTL_MS = 30_000;\nexport const PIPELINE_MEMBERSHIP_SNAPSHOT_TTL_MS = 5 * 60_000;\n\ninterface PipelineMembershipServiceDeps {\n gather(project: ProjectConfig): Promise<IssueLensSignals[]>;\n now(): number;\n}\n\nexport interface PipelineMembershipService {\n (project: ProjectConfig): Promise<PipelineMembership[]>;\n /** Drop the settled per-project TTL entry so the next call re-gathers (PAN-2893 event-driven refresh). */\n invalidate(projectPath?: string): void;\n}\n\nexport function createPipelineMembershipService(\n deps: PipelineMembershipServiceDeps = { gather: gatherProjectLensSignals, now: () => Date.now() },\n): PipelineMembershipService {\n const cachedGather = createSettledTtlPromiseCache<string, PipelineMembership[]>(PIPELINE_MEMBERSHIP_TTL_MS, deps.now);\n const service = async (project: ProjectConfig): Promise<PipelineMembership[]> =>\n cachedGather(project.path, () =>\n deps.gather(project).then((signals) => signals.map(resolvePipelineMembership)));\n service.invalidate = (projectPath?: string): void => cachedGather.invalidate(projectPath);\n return service;\n}\n\nexport const getProjectPipelineMembership = createPipelineMembershipService();\n\nexport async function getPipelineMembershipForProjects(\n projects: ProjectConfig[],\n getMembership = getProjectPipelineMembership,\n): Promise<PipelineMembership[]> {\n const results = await getPipelineMembershipResultsForProjects(projects, getMembership);\n const failed = results.find((result) => result.error);\n if (failed) throw failed.error;\n return results.flatMap((result) => result.memberships ?? []);\n}\n\nexport interface ProjectPipelineMembershipResult {\n project: ProjectConfig;\n memberships?: PipelineMembership[];\n error?: unknown;\n unavailableReason?: MembershipUnavailableReason;\n}\n\nexport async function getPipelineMembershipResultsForProjects(\n projects: ProjectConfig[],\n getMembership = getProjectPipelineMembership,\n): Promise<ProjectPipelineMembershipResult[]> {\n const gathered = await mapPipelineProjects(projects, getMembership);\n return gathered.map(({ project, value, error }) => {\n if (error) return { project, error };\n return { project, memberships: value ?? [] };\n });\n}\n\ntype MembershipLookup = (project: ProjectConfig) => Promise<PipelineMembership[]>;\n\ninterface MembershipSnapshot {\n value: PipelineMembership[];\n refreshedAt: number;\n refresh?: Promise<void>;\n lastError?: string;\n lastErrorReason?: MembershipUnavailableReason;\n lastErrorAt?: number;\n}\n\nconst membershipSnapshots = new Map<string, MembershipSnapshot>();\nconst scheduleMembershipRefresh = createPromiseConcurrencyLimiter(PIPELINE_PROJECT_CONCURRENCY);\n\nfunction refreshMembershipSnapshot(\n project: ProjectConfig,\n snapshot: MembershipSnapshot,\n getMembership: MembershipLookup,\n now: () => number,\n): Promise<void> {\n if (snapshot.refresh) return snapshot.refresh;\n snapshot.refresh = scheduleMembershipRefresh(() => getMembership(project)).then((value) => {\n snapshot.value = value;\n snapshot.refreshedAt = now();\n snapshot.lastError = undefined;\n snapshot.lastErrorReason = undefined;\n snapshot.lastErrorAt = undefined;\n }).catch((error: unknown) => {\n // Keep the last successful snapshot; a cold caller remains unavailable.\n // Record and log the failure — a swallowed boot-warm failure previously\n // left a cold project with zero diagnostic trace (PAN-2972).\n snapshot.lastError = error instanceof Error ? error.message : String(error);\n snapshot.lastErrorReason = error instanceof PipelineMembershipUnavailableError\n ? error.reason\n : 'gather_failed';\n snapshot.lastErrorAt = now();\n console.warn(\n `[pipeline-membership] refresh failed for ${project.name ?? project.path}; keeping last-good snapshot:`,\n snapshot.lastError,\n );\n }).finally(() => {\n snapshot.refresh = undefined;\n });\n return snapshot.refresh;\n}\n\n/** Read the latest successful snapshots without scheduling tracker or git work. */\nexport function readPipelineMembershipSnapshotsForProjects(\n projects: ProjectConfig[],\n): ProjectPipelineMembershipResult[] {\n return projects.map((project) => {\n const snapshot = membershipSnapshots.get(project.path);\n if (snapshot?.refreshedAt) return { project, memberships: snapshot.value };\n // Cold cache: surface the recorded failure when one exists so the operator\n // sees the real cause instead of a permanent \"loading\" (PAN-2972).\n return {\n project,\n error: new Error(snapshot?.lastError\n ? `Pipeline membership refresh failed: ${snapshot.lastError}`\n : 'Pipeline membership snapshot is loading'),\n unavailableReason: snapshot?.lastErrorReason,\n };\n });\n}\n\n/** Return the latest successful snapshot immediately and refresh stale/missing projects in the background. */\nexport function getPipelineMembershipSnapshotsForProjects(\n projects: ProjectConfig[],\n getMembership: MembershipLookup = getProjectPipelineMembership,\n now = () => Date.now(),\n): ProjectPipelineMembershipResult[] {\n const results = readPipelineMembershipSnapshotsForProjects(projects);\n for (const project of projects) {\n const snapshot = membershipSnapshots.get(project.path);\n if (!snapshot?.refresh && (!snapshot || now() - snapshot.refreshedAt >= PIPELINE_MEMBERSHIP_SNAPSHOT_TTL_MS)) {\n const current = snapshot ?? { value: [], refreshedAt: 0 };\n membershipSnapshots.set(project.path, current);\n void refreshMembershipSnapshot(project, current, getMembership, now);\n }\n }\n return results;\n}\n\n/** Await only the first snapshot; later resource refreshes consume stale data while revalidating. */\nexport async function getPipelineMembershipSnapshotsForResourceDiscovery(\n projects: ProjectConfig[],\n getMembership: MembershipLookup = getProjectPipelineMembership,\n now = () => Date.now(),\n): Promise<ProjectPipelineMembershipResult[]> {\n return Promise.all(projects.map(async (project) => {\n const existing = membershipSnapshots.get(project.path);\n if (existing?.refreshedAt) {\n if (now() - existing.refreshedAt >= PIPELINE_MEMBERSHIP_SNAPSHOT_TTL_MS) {\n void refreshMembershipSnapshot(project, existing, getMembership, now);\n }\n return { project, memberships: existing.value };\n }\n const snapshot = existing ?? { value: [], refreshedAt: 0 };\n membershipSnapshots.set(project.path, snapshot);\n await refreshMembershipSnapshot(project, snapshot, getMembership, now);\n return snapshot.refreshedAt\n ? { project, memberships: snapshot.value }\n : {\n project,\n error: new Error('Pipeline membership snapshot failed to load'),\n unavailableReason: snapshot.lastErrorReason,\n };\n }));\n}\n\n/**\n * PAN-2893 — event-driven refresh. Invalidate the per-project TTL cache and\n * force a snapshot re-gather NOW for the given projects, so agent lifecycle\n * changes reach membership consumers without waiting out the 5-minute TTL.\n * Refresh failures keep the last good snapshot (same policy as TTL refreshes).\n */\nexport async function refreshMembershipSnapshotsForProjects(\n projects: ProjectConfig[],\n getMembership = getProjectPipelineMembership,\n now = () => Date.now(),\n): Promise<void> {\n await Promise.all(projects.map(async (project) => {\n getMembership.invalidate(project.path);\n const snapshot = membershipSnapshots.get(project.path) ?? { value: [], refreshedAt: 0 };\n membershipSnapshots.set(project.path, snapshot);\n // A refresh that started BEFORE the triggering event may resolve with\n // pre-event data; chain a fresh gather behind it instead of reusing it.\n const inFlight = snapshot.refresh;\n if (inFlight) {\n await inFlight;\n getMembership.invalidate(project.path);\n }\n await refreshMembershipSnapshot(project, snapshot, getMembership, now);\n }));\n}\n\n/**\n * PAN-2893 — last successful membership result for a project, or null when no\n * gather has ever succeeded. Lets the membership route serve stale data through\n * a transient tracker failure (e.g. a Linear 503) instead of erroring the pane.\n */\nexport function getLastGoodMembershipSnapshot(projectPath: string): PipelineMembership[] | null {\n const snapshot = membershipSnapshots.get(projectPath);\n return snapshot?.refreshedAt ? snapshot.value : null;\n}\n\nexport function summarizePipelineMembership(membership: PipelineMembership): IssuePipelineMembership {\n return {\n available: true,\n inPipeline: membership.inPipeline,\n bucket: membership.bucket,\n labelDrift: membership.labelDrift,\n };\n}\n\nexport function unavailablePipelineMembership(): IssuePipelineMembership {\n return { available: false, inPipeline: false, bucket: 'clean_terminal', labelDrift: null };\n}\n","export const RECENCY_DAYS = 14;\nconst RECENCY_MS = RECENCY_DAYS * 24 * 60 * 60 * 1000;\n\nexport interface TaskTotals {\n total: number;\n closed: number;\n inProgress: number;\n lastUpdated: string | null;\n}\n\nexport function isWithinRecencyMs(timestamp: number): boolean {\n return Number.isFinite(timestamp) && (Date.now() - timestamp) < RECENCY_MS;\n}\n\nexport function isWithinRecencyDate(dateString: string | null): boolean {\n if (!dateString) return false;\n const parsed = Date.parse(dateString);\n return Number.isFinite(parsed) && (Date.now() - parsed) < RECENCY_MS;\n}\n","import { execFile } from 'node:child_process';\nimport { readdir, stat } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { compareIssueIds } from '@overdeck/contracts';\n\nimport type { TaskTotals } from './resource-discovery-signals.js';\nimport { isWithinRecencyDate, isWithinRecencyMs } from './resource-discovery-signals.js';\nimport {\n captureSharedResourceSignals,\n type SharedResourceSignals,\n type TrackerIssueRecord,\n} from './resource-discovery-shared.js';\n\nimport { getAgentRuntimeState } from '../../../lib/agents.js';\nimport {\n PAN_CONTINUE_FILENAME,\n PAN_DIRNAME,\n} from '../../../lib/pan-dir/index.js';\nimport { findSpecByIssue } from '../../../lib/pan-dir/specs.js';\nimport { findDraftPrd } from '../../../lib/prd-locations.js';\nimport { listProjectsSync, resolveProjectFromIssueSync, type ProjectConfig, type ResolvedProject } from '../../../lib/projects.js';\nimport { listWorkspaces } from '../../../lib/workspaces/resolver.js';\nimport {\n PLANNED_BACKLOG_SPEC_ONLY_REASON,\n type PipelineBucket,\n type PipelineMembership,\n} from '../../../lib/pipeline-membership.js';\nimport { listOpenPullRequestsSnapshot } from '../../../lib/pipeline-membership-gather.js';\nimport { loadReadyForMergeFlags } from '../review-status.js';\nimport { resolveAgentGitInfo } from './git-info.js';\nimport { resolveMissingIssueTitles } from './issue-title-fallback.js';\nimport { parseIssueIdFromTextSync } from '../../../lib/resource-utils.js';\nimport {\n readPipelineMembershipSnapshotsForProjects,\n refreshMembershipSnapshotsForProjects,\n} from './pipeline-membership.js';\n\nconst execFileAsync = promisify(execFile);\nconst RECENT_ACTIVITY_WINDOW_MS = 5_000;\n\nexport { RECENCY_DAYS } from './resource-discovery-signals.js';\nexport type { TaskTotals } from './resource-discovery-signals.js';\n\nexport type ResourceSource = 'tracker' | 'tmux' | 'workspace' | 'branch' | 'pr' | 'prd' | 'vbrief' | 'tasks' | 'docker' | 'remote-agent' | 'conversation';\n\nexport interface ResourcePullRequest {\n number: number;\n title: string;\n url?: string;\n state: string;\n isDraft: boolean;\n}\n\nexport interface ResourceDetails {\n hasWorkspace: boolean;\n localBranchCount: number;\n remoteBranchCount: number;\n tmuxSessionCount: number;\n prs: ResourcePullRequest[];\n hasXbrief: boolean;\n hasTasks: boolean;\n hasPrd: boolean;\n dockerContainerCount: number;\n /** Current HEAD of the agent's workspace, or null when no workspace exists. */\n actualBranch: string | null;\n /** True when the workspace HEAD differs from the expected feature/<id> branch. */\n branchDrifted: boolean;\n /** True when a feature/* or bypass/* branch for the issue has unmerged commits not on main. */\n branchAheadOfMain: boolean;\n /** True when the workspace path is configured but missing on disk. */\n workspaceMissing: boolean;\n /** Remote (fly.io) work agent for this issue, when one is active (PAN-1676). */\n remoteAgent: { vmName: string; status: string; model: string; startedAt: string } | null;\n /** Non-archived conversations explicitly linked to this issue. */\n conversations: Array<{ id: number; name: string; title: string | null; status: string }>;\n}\n\nexport interface ResourceDetailIdentifiers {\n workspacePaths: string[];\n localBranchNames: string[];\n remoteBranchNames: string[];\n tmuxSessionNames: string[];\n prs: Array<{\n number: number;\n title: string;\n state: string;\n isDraft: boolean;\n }>;\n dockerContainerNames: string[];\n}\n\nexport interface ResourceAllocatedIssue {\n issueId: string;\n title: string;\n projectName: string;\n branch: string;\n status: string;\n stateLabel: string;\n agentStatus: string | null;\n hasPlanning: boolean;\n hasPrd: boolean;\n hasState: boolean;\n isShadow: boolean;\n isRally: boolean;\n childCount?: number;\n completedCount?: number;\n inProgressCount?: number;\n readyForMerge: boolean;\n rawTrackerState?: string;\n resourceSources: ResourceSource[];\n resourceDetails: ResourceDetails;\n taskTotals: TaskTotals | null;\n pipelineBucket?: PipelineBucket;\n /** PAN-2822: planned_backlog caused solely by the L6 spec lens — display surfaces may hide these. */\n specOnlyPlanned?: boolean;\n /** Live resource residue attached to a resolver-rejected terminal issue. */\n resourceDrift?: boolean;\n}\n\ninterface InternalResourceDetails {\n tmuxSessions: string[];\n workspacePath: string | null;\n localBranches: string[];\n remoteBranches: string[];\n prs: GhPullRequest[];\n xbriefPath: string | null;\n xbriefMtime: number | null;\n tasksPath: string | null;\n prdPath: string | null;\n dockerContainers: string[];\n actualBranch: string | null;\n branchDrifted: boolean;\n branchAheadOfMain: boolean;\n workspaceMissing: boolean;\n remoteAgent: { vmName: string; status: string; model: string; startedAt: string } | null;\n conversations: Array<{ id: number; name: string; title: string | null; status: string; tmuxSession: string | null }>;\n}\n\ninterface MutableResourceIssue {\n issueId: string;\n title: string;\n projectName: string;\n branch: string;\n trackerState: string | null;\n rawTrackerState?: string;\n isRally: boolean;\n hasPlanning: boolean;\n hasPrd: boolean;\n hasState: boolean;\n isShadow: boolean;\n agentStatus: string | null;\n readyForMerge: boolean;\n lastActivity: number | null;\n resourceSources: Set<ResourceSource>;\n resourceDetails: InternalResourceDetails;\n taskTotals: TaskTotals | null;\n}\n\ninterface InternalDiscoveredIssue extends Omit<ResourceAllocatedIssue, 'resourceSources' | 'resourceDetails'> {\n resourceSources: Set<ResourceSource>;\n resourceDetails: InternalResourceDetails;\n}\n\ninterface GhPullRequest {\n number: number;\n title: string;\n url: string;\n state: string;\n isDraft: boolean;\n headRefName: string;\n baseRefName: string;\n}\n\ninterface ProjectRef {\n key: string;\n config: ProjectConfig;\n}\n\ninterface ResourceDiscoveryCacheEntry {\n value: ResourceAllocatedIssue[];\n computedAt: number;\n}\n\ninterface ProjectResourceSnapshot {\n detailed: InternalDiscoveredIssue[];\n public: ResourceAllocatedIssue[];\n computedAt: number;\n}\n\nconst projectResourceSnapshots = new Map<string, ProjectResourceSnapshot>();\nlet cachedResourceIssues: ResourceDiscoveryCacheEntry | null = null;\nlet cachedDetailedResourceIssues: InternalDiscoveredIssue[] | null = null;\nlet resourceIssuesRefreshPromise: Promise<ResourceAllocatedIssue[]> | null = null;\n\nfunction projectPrefixes(project: ProjectRef): string[] {\n const prefixes = new Set<string>();\n if (project.config.issue_prefix) prefixes.add(project.config.issue_prefix.toUpperCase());\n for (const prefix of project.config.issue_prefixes ?? []) prefixes.add(prefix.toUpperCase());\n if (prefixes.size === 0) prefixes.add(project.key.toUpperCase().replace(/-/g, ''));\n return [...prefixes];\n}\n\nfunction deriveStateLabel(\n issue: MutableResourceIssue,\n hasTmux: boolean,\n hasFreshHeartbeat: boolean,\n membership?: PipelineMembership,\n): string {\n const trackerState = issue.trackerState ?? '';\n if (membership?.bucket === 'post_merge_limbo') return 'Merged — Needs Close-Out';\n if (membership?.lenses.L3_issueOpen === false) {\n return hasTmux ? 'Closed' : 'Done';\n }\n if (issue.readyForMerge) return 'In Review';\n if (trackerState === 'done' || trackerState === 'closed' || trackerState === 'canceled') {\n return hasTmux ? 'Closed' : 'Done';\n }\n if (trackerState === 'in_review') return 'In Review';\n if (trackerState === 'in_progress') return 'In Progress';\n if (hasTmux && (issue.agentStatus === 'active' || hasFreshHeartbeat)) return 'In Progress';\n if (issue.agentStatus === 'suspended') return 'Suspended';\n if (issue.hasPrd && !issue.hasState) return 'Planning';\n if (issue.hasState) return 'Has Context';\n if (issue.resourceSources.has('workspace') || issue.resourceSources.has('branch')) return 'Allocated';\n return 'Idle';\n}\n\nfunction sortPullRequests(prs: GhPullRequest[]): GhPullRequest[] {\n return [...prs].sort((a, b) => {\n if (a.isDraft !== b.isDraft) return a.isDraft ? 1 : -1;\n return a.number - b.number;\n });\n}\n\nfunction summarizeResourceDetails(details: InternalResourceDetails): ResourceDetails {\n return {\n hasWorkspace: details.workspacePath !== null,\n localBranchCount: details.localBranches.length,\n remoteBranchCount: details.remoteBranches.length,\n tmuxSessionCount: details.tmuxSessions.length,\n prs: sortPullRequests(details.prs).map((pr) => ({\n number: pr.number,\n title: pr.title,\n url: pr.url,\n state: pr.state,\n isDraft: pr.isDraft,\n })),\n hasXbrief: details.xbriefPath !== null,\n hasTasks: details.tasksPath !== null,\n hasPrd: details.prdPath !== null,\n dockerContainerCount: details.dockerContainers.length,\n actualBranch: details.actualBranch,\n branchDrifted: details.branchDrifted,\n branchAheadOfMain: details.branchAheadOfMain,\n workspaceMissing: details.workspaceMissing,\n remoteAgent: details.remoteAgent,\n conversations: details.conversations.map((conv) => ({ id: conv.id, name: conv.name, title: conv.title, status: conv.status })),\n };\n}\n\nfunction summarizeResourceDetailIdentifiers(details: InternalResourceDetails): ResourceDetailIdentifiers {\n return {\n workspacePaths: details.workspacePath ? [details.workspacePath] : [],\n localBranchNames: [...details.localBranches].sort(),\n remoteBranchNames: [...details.remoteBranches].sort(),\n tmuxSessionNames: [...details.tmuxSessions].sort(),\n prs: sortPullRequests(details.prs).map((pr) => ({\n number: pr.number,\n title: pr.title,\n state: pr.state,\n isDraft: pr.isDraft,\n })),\n dockerContainerNames: [...details.dockerContainers].sort(),\n };\n}\n\nfunction hasRecentActivity(lastActivity: number | null): boolean {\n return lastActivity !== null && Number.isFinite(lastActivity) && (Date.now() - lastActivity) < RECENT_ACTIVITY_WINDOW_MS;\n}\n\nfunction isLiveResource(issue: MutableResourceIssue): boolean {\n return issue.resourceDetails.remoteAgent !== null\n || issue.resourceDetails.tmuxSessions.length > 0\n || issue.resourceDetails.dockerContainers.length > 0\n || hasOpenPr(issue);\n}\n\nfunction isActiveTrackerState(state: string | null): boolean {\n return state === 'in_progress' || state === 'in_review' || state === 'started';\n}\n\nfunction isTerminalTrackerState(state: string | null): boolean {\n return state === 'closed' || state === 'done' || state === 'canceled' || state === 'completed';\n}\n\nfunction hasOpenPr(issue: MutableResourceIssue): boolean {\n return issue.resourceDetails.prs.some((pr) => pr.state === 'OPEN' || pr.state === 'open');\n}\n\nfunction shouldLoadReviewStatus(issue: MutableResourceIssue): boolean {\n return issue.resourceSources.size > 0\n && (!isTerminalTrackerState(issue.trackerState) || hasOpenPr(issue))\n && (isLiveResource(issue) || (isActiveTrackerState(issue.trackerState) && issue.branch != null));\n}\n\n/**\n * tmux session-name prefixes that map a session to its issue in the Command Deck\n * tree. `strike-` MUST be included (PAN-1682): strike sessions are named\n * `strike-<issue>` and a strike issue is typically `todo` (not an active tracker\n * state), so without registering the tmux resource the issue is filtered out of\n * the tree entirely. Keep this list in sync with the agent-session prefixes the\n * dashboard recognizes elsewhere (e.g. routes/agents.ts).\n */\nconst DISCOVERABLE_SESSION_PREFIXES = ['agent-', 'planning-', 'specialist-', 'review-', 'strike-'] as const;\n\nexport function isDiscoverableAgentSession(sessionName: string): boolean {\n return DISCOVERABLE_SESSION_PREFIXES.some((prefix) => sessionName.startsWith(prefix));\n}\n\nasync function loadOpenPullRequests(projects: ProjectRef[]): Promise<Map<string, GhPullRequest[]>> {\n const pullRequests = new Map<string, GhPullRequest[]>();\n await Promise.all(projects.map(async (project) => {\n if (!project.config.github_repo) return;\n const [owner, repo] = project.config.github_repo.split('/');\n if (!owner || !repo) return;\n try {\n const prs = await listOpenPullRequestsSnapshot(owner, repo);\n for (const row of prs) {\n if (row.number === undefined || row.title === undefined || row.url === undefined\n || row.state === undefined || row.isDraft === undefined || row.baseRefName === undefined) continue;\n const pr: GhPullRequest = {\n number: row.number, title: row.title, url: row.url, state: row.state,\n isDraft: row.isDraft, headRefName: row.headRefName, baseRefName: row.baseRefName,\n };\n const issueId = parseIssueIdFromTextSync(pr.headRefName);\n if (!issueId) continue;\n const existing = pullRequests.get(issueId) ?? [];\n existing.push(pr);\n pullRequests.set(issueId, existing);\n }\n } catch {\n // ignore repo-specific failures\n }\n }));\n\n for (const [issueId, prs] of pullRequests) {\n pullRequests.set(issueId, sortPullRequests(prs));\n }\n\n return pullRequests;\n}\n\nconst PROJECT_BRANCH_PATTERNS = [\n 'refs/heads/feature/*',\n 'refs/remotes/origin/feature/*',\n 'refs/heads/bypass/*',\n 'refs/remotes/origin/bypass/*',\n];\n\nasync function loadProjectBranchSnapshot(projectPath: string): Promise<{\n local: string[];\n remote: string[];\n ahead: Set<string>;\n}> {\n const run = (extraArgs: string[] = []) => execFileAsync('git', [\n 'for-each-ref',\n '--format=%(refname:short)',\n ...extraArgs,\n ...PROJECT_BRANCH_PATTERNS,\n ], {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: 10000,\n }).catch(() => ({ stdout: '' }));\n\n const [allRefs, unmergedRefs] = await Promise.all([\n run(),\n run(['--no-merged=main']),\n ]);\n const branches = allRefs.stdout.split('\\n').map((line) => line.trim()).filter(Boolean);\n const ahead = new Set(unmergedRefs.stdout.split('\\n').map((line) => line.trim()).filter(Boolean));\n\n return {\n local: branches.filter((branch) => !branch.startsWith('origin/')),\n remote: branches.filter((branch) => branch.startsWith('origin/')),\n ahead,\n };\n}\n\ninterface WorkspaceScanResult {\n workspacePath: string;\n hasPlanning: boolean;\n hasPrd: boolean;\n hasState: boolean;\n hasXbrief: boolean;\n xbriefPath: string | null;\n xbriefMtime: number | null;\n hasTasks: boolean;\n}\n\nasync function scanWorkspace(\n workspacesDir: string,\n workspaceName: string,\n): Promise<WorkspaceScanResult> {\n const workspacePath = join(workspacesDir, workspaceName);\n const projectRoot = join(workspacesDir, '..');\n const workspaceEntries = new Set(await readdir(workspacePath).catch(() => [] as string[]));\n const panEntries = workspaceEntries.has(PAN_DIRNAME)\n ? new Set(await readdir(join(workspacePath, PAN_DIRNAME)).catch(() => [] as string[]))\n : new Set<string>();\n const issueMatch = workspaceName.match(/^feature-([a-z]+-\\d+)$/i);\n const issueId = issueMatch ? issueMatch[1].toUpperCase() : null;\n const specEntry = issueId\n ? await Effect.runPromise(findSpecByIssue(projectRoot, issueId)).catch(() => null)\n : null;\n const xbriefPath = specEntry ? specEntry.path : null;\n let xbriefMtime: number | null = null;\n if (xbriefPath) {\n try {\n const stats = await stat(xbriefPath);\n xbriefMtime = stats.mtimeMs;\n } catch {\n xbriefMtime = null;\n }\n }\n\n return {\n workspacePath,\n hasPlanning: workspaceEntries.has(PAN_DIRNAME),\n hasPrd: panEntries.has('prd.md'),\n hasState: panEntries.has(PAN_CONTINUE_FILENAME),\n hasXbrief: xbriefPath !== null,\n xbriefPath,\n xbriefMtime,\n hasTasks: xbriefPath !== null,\n };\n}\n\nasync function computeResourceAllocatedIssues(\n projectConfigs: ProjectConfig[],\n sharedSignals?: SharedResourceSignals,\n): Promise<InternalDiscoveredIssue[]> {\n const registeredProjects = listProjectsSync() as ProjectRef[];\n const projects = projectConfigs.map((config) =>\n registeredProjects.find((entry) => entry.config.path === config.path)\n ?? { key: config.name ?? config.path, config });\n const {\n trackerIssues,\n tmuxSessions,\n dockerContainers,\n conversations,\n remoteAgentStates,\n } = sharedSignals ?? await captureSharedResourceSignals();\n const pullRequests = await loadOpenPullRequests(projects);\n\n const issueMap = new Map<string, MutableResourceIssue>();\n const projectByPrefix = new Map<string, ProjectRef>();\n for (const project of projects) {\n for (const prefix of projectPrefixes(project)) {\n projectByPrefix.set(prefix, project);\n }\n }\n\n const projectRefFromResolved = (resolvedProject: ResolvedProject): ProjectRef | null =>\n projects.find((project) =>\n project.key === resolvedProject.projectKey\n || project.config.path === resolvedProject.projectPath) ?? null;\n\n const resolveProjectRef = (issueId: string, preferredProject?: ProjectRef): ProjectRef | null => {\n if (preferredProject) return preferredProject;\n const resolvedProject = resolveProjectFromIssueSync(issueId);\n if (resolvedProject) {\n const project = projectRefFromResolved(resolvedProject);\n if (project) return project;\n }\n const prefix = issueId.toUpperCase().match(/^([A-Z]+)-\\d+$/)?.[1] ?? '';\n return projectByPrefix.get(prefix) ?? null;\n };\n\n const ensureIssue = (issueId: string, preferredProject?: ProjectRef): MutableResourceIssue | null => {\n const upper = issueId.toUpperCase();\n const existing = issueMap.get(upper);\n if (existing) return existing;\n\n const resolved = resolveProjectRef(upper, preferredProject);\n if (!resolved) return null;\n\n const tracker = trackerIssues.get(upper);\n const created: MutableResourceIssue = {\n issueId: upper,\n title: tracker?.title?.trim() || upper,\n projectName: resolved.config.name ?? resolved.key,\n branch: `feature/${upper.toLowerCase()}`,\n trackerState: typeof tracker?.state === 'string' ? tracker.state : null,\n rawTrackerState: tracker?.rawTrackerState,\n isRally: tracker?.source === 'rally',\n hasPlanning: false,\n hasPrd: false,\n hasState: false,\n isShadow: false,\n agentStatus: null,\n readyForMerge: false,\n lastActivity: null,\n resourceSources: new Set<ResourceSource>(),\n resourceDetails: {\n tmuxSessions: [],\n workspacePath: null,\n localBranches: [],\n remoteBranches: [],\n prs: [],\n xbriefPath: null,\n xbriefMtime: null,\n tasksPath: null,\n prdPath: null,\n dockerContainers: [],\n actualBranch: null,\n branchDrifted: false,\n branchAheadOfMain: false,\n workspaceMissing: false,\n remoteAgent: null,\n conversations: [],\n },\n taskTotals: null,\n };\n issueMap.set(upper, created);\n return created;\n };\n\n for (const [issueId, tracker] of trackerIssues) {\n const issue = ensureIssue(issueId);\n if (!issue) continue;\n issue.title = tracker.title?.trim() || issue.title;\n issue.trackerState = typeof tracker.state === 'string' ? tracker.state : issue.trackerState;\n issue.rawTrackerState = tracker.rawTrackerState ?? issue.rawTrackerState;\n issue.isRally = tracker.source === 'rally';\n issue.resourceSources.add('tracker');\n }\n\n for (const sessionName of tmuxSessions) {\n if (!isDiscoverableAgentSession(sessionName)) {\n continue;\n }\n const issueId = parseIssueIdFromTextSync(sessionName);\n if (!issueId) continue;\n const issue = ensureIssue(issueId);\n if (!issue) continue;\n issue.resourceSources.add('tmux');\n if (!issue.resourceDetails.tmuxSessions.includes(sessionName)) {\n issue.resourceDetails.tmuxSessions.push(sessionName);\n }\n }\n\n // Non-archived conversations explicitly linked to an issue are a distinct\n // resource signal from tmux/agent sessions (PAN-2602). They indicate active\n // operator attention even when no agent session is running.\n for (const conv of conversations) {\n if (!conv.issueId || conv.archivedAt) continue;\n const issue = ensureIssue(conv.issueId);\n if (!issue) continue;\n issue.resourceSources.add('conversation');\n issue.resourceDetails.conversations.push({\n id: conv.id,\n name: conv.name,\n title: conv.title,\n status: conv.status,\n tmuxSession: conv.tmuxSession,\n });\n }\n\n // Remote (fly.io) work agents have no local tmux session — surface them\n // from their remote-state.json so the tree shows the issue as actively\n // worked (PAN-1676).\n for (const remoteState of remoteAgentStates) {\n const issue = ensureIssue(remoteState.issueId);\n if (!issue) continue;\n issue.resourceSources.add('remote-agent');\n issue.resourceDetails.remoteAgent = {\n vmName: remoteState.vmName,\n status: remoteState.status,\n model: remoteState.model,\n startedAt: remoteState.startedAt,\n };\n if (!issue.agentStatus) issue.agentStatus = 'active';\n }\n\n for (const containerName of dockerContainers) {\n const issueId = parseIssueIdFromTextSync(containerName.replace(/feature\\//g, 'feature-'));\n if (!issueId) continue;\n const issue = ensureIssue(issueId);\n if (!issue) continue;\n issue.resourceSources.add('docker');\n issue.resourceDetails.dockerContainers.push(containerName);\n }\n\n await Promise.all(projects.map(async (project) => {\n const projectPath = project.config.path;\n const workspacesDir = join(projectPath, 'workspaces');\n // ONE bulk tasks read per project per refresh, rather than a per-workspace\n // read each 30s TTL — the bulk shape keeps refreshes cheap as the workspace\n // count grows.\n // PAN-1990: prefer the workspaces resolver (kind='issue' rows for this\n // project); fall back to the directory scan only when the table hasn't\n // been seeded yet for this project (pre-first-boot / not yet backfilled).\n const resolverRows = listWorkspaces({ projectId: project.key, kind: 'issue' });\n const [workspaceCandidates, branches] = await Promise.all([\n resolverRows.length > 0\n ? Promise.resolve(resolverRows.map((row) => ({\n name: basename(row.path),\n issueId: (row.issueId ?? basename(row.path).replace(/^feature-/, '')).toUpperCase(),\n })))\n : readdir(workspacesDir, { withFileTypes: true })\n .then((entries) => entries\n .filter((entry) => entry.isDirectory() && entry.name.startsWith('feature-'))\n .map((entry) => ({ name: entry.name, issueId: entry.name.replace(/^feature-/, '').toUpperCase() })))\n .catch(() => []),\n loadProjectBranchSnapshot(projectPath),\n ]);\n\n await Promise.all(workspaceCandidates.map(async ({ name, issueId }) => {\n const issue = ensureIssue(issueId, project);\n if (!issue) return;\n const workspace = await scanWorkspace(workspacesDir, name);\n issue.resourceSources.add('workspace');\n issue.resourceDetails.workspacePath = workspace.workspacePath;\n issue.hasPlanning = workspace.hasPlanning;\n issue.hasPrd = workspace.hasPrd;\n issue.hasState = workspace.hasState;\n if (workspace.xbriefPath) {\n issue.resourceSources.add('vbrief');\n issue.resourceDetails.xbriefPath = workspace.xbriefPath;\n issue.resourceDetails.xbriefMtime = workspace.xbriefMtime;\n }\n if (workspace.hasTasks) {\n issue.resourceSources.add('tasks');\n issue.resourceDetails.tasksPath = join(workspace.workspacePath, '.tasks');\n }\n const gitInfo = await resolveAgentGitInfo(workspace.workspacePath, issue.branch);\n issue.resourceDetails.actualBranch = gitInfo.actualBranch;\n issue.resourceDetails.branchDrifted = gitInfo.branchDrifted;\n issue.resourceDetails.workspaceMissing = gitInfo.workspaceMissing;\n }));\n\n for (const [branch, key] of [\n ...branches.local.map((b) => [b, 'localBranches'] as const),\n ...branches.remote.map((b) => [b, 'remoteBranches'] as const),\n ]) {\n const issueId = parseIssueIdFromTextSync(branch);\n if (!issueId) continue;\n const issue = ensureIssue(issueId, project);\n if (!issue) continue;\n issue.resourceSources.add('branch');\n if (!issue.resourceDetails[key].includes(branch)) {\n issue.resourceDetails[key].push(branch);\n }\n if (branches.ahead.has(branch)) {\n issue.resourceDetails.branchAheadOfMain = true;\n }\n }\n }));\n\n for (const [issueId, prs] of pullRequests) {\n const issue = ensureIssue(issueId);\n if (!issue) continue;\n issue.resourceSources.add('pr');\n issue.resourceDetails.prs = prs;\n const bestTitle = prs.find((pr) => !pr.isDraft)?.title ?? prs[0]?.title;\n issue.title = issue.title === issue.issueId && bestTitle ? bestTitle : issue.title;\n }\n\n await Promise.all([...issueMap.values()].map(async (issue) => {\n if (issue.resourceDetails.tmuxSessions.length === 0) return;\n\n // Runtime-state ids equal session names (agent-<issue>, strike-<issue>,\n // planning-<issue>, ...). Probe every discovered session — not just\n // agent-<issue> — so strike/planning sessions surface as live agents.\n // PAN-1682 made these sessions discoverable but left attribution\n // agent-only, which rendered a running strike as a lifeless node.\n const states = (await Promise.all(\n issue.resourceDetails.tmuxSessions.map((sessionName) =>\n Effect.runPromise(getAgentRuntimeState(sessionName)).catch(() => null),\n ),\n )).filter((state): state is NonNullable<typeof state> => state !== null);\n if (states.length === 0) return;\n\n const best = states.find((state) => state.state === 'active')\n ?? states.reduce((a, b) => (Date.parse(a.lastActivity) >= Date.parse(b.lastActivity) ? a : b));\n issue.agentStatus = best.state;\n\n const lastActivity = Math.max(\n ...states.map((state) => Date.parse(state.lastActivity)).filter(Number.isFinite),\n );\n issue.lastActivity = Number.isFinite(lastActivity) ? lastActivity : null;\n }));\n\n const reviewStatusIssueIds = [...issueMap.values()]\n .filter(shouldLoadReviewStatus)\n .map((issue) => issue.issueId);\n if (reviewStatusIssueIds.length > 0) {\n const readyForMergeFlags = loadReadyForMergeFlags(reviewStatusIssueIds);\n for (const issue of issueMap.values()) {\n issue.readyForMerge = readyForMergeFlags.get(issue.issueId) ?? false;\n }\n }\n\n const memberships = new Map<string, PipelineMembership>();\n const unavailableProjects = new Set<string>();\n const projectMemberships = readPipelineMembershipSnapshotsForProjects(projects.map((project) => project.config));\n for (const result of projectMemberships) {\n if (result.error) unavailableProjects.add(result.project.name);\n for (const membership of result.memberships ?? []) {\n ensureIssue(membership.issueId);\n memberships.set(membership.issueId, membership);\n }\n }\n\n await Promise.all([...issueMap.values()].map(async (issue) => {\n const project = resolveProjectRef(issue.issueId);\n if (!project) return;\n const draft = await Effect.runPromise(findDraftPrd(project.config.path, issue.issueId)).catch(() => null);\n if (!draft) return;\n issue.resourceSources.add('prd');\n issue.resourceDetails.prdPath = draft.path;\n issue.hasPrd = true;\n }));\n\n // Issues that aged out of the tracker sync window (closed long ago) still\n // surface here through their surviving local resources, but carry no tracker\n // row — their title fell back to the bare identifier. Resolve those titles\n // through the tracker's per-issue door, memoized across passes (PAN-3337).\n const untitledIssueIds = [...issueMap.values()]\n .filter((issue) => issue.resourceSources.size > 0 && issue.title === issue.issueId)\n .map((issue) => issue.issueId);\n if (untitledIssueIds.length > 0) {\n const fallbackTitles = await resolveMissingIssueTitles(untitledIssueIds).catch(() => new Map<string, string>());\n for (const issue of issueMap.values()) {\n const title = fallbackTitles.get(issue.issueId);\n if (title && issue.title === issue.issueId) issue.title = title;\n }\n }\n\n // The canonical resolver owns pipeline inclusion whenever it returns a verdict.\n // While a project's membership is unavailable, retain only rows backed by a live\n // resource so a transient tracker failure cannot erase running work.\n const discoveredIssues = [...issueMap.values()]\n .filter((issue) => issue.resourceSources.size > 0)\n .filter((issue) => {\n const membership = memberships.get(issue.issueId);\n return membership?.inPipeline === true\n || (membership?.bucket === 'clean_terminal' && isLiveResource(issue))\n || (!membership && unavailableProjects.has(issue.projectName) && isLiveResource(issue));\n })\n .map((issue) => {\n const membership = memberships.get(issue.issueId);\n const hasTmux = issue.resourceDetails.tmuxSessions.length > 0;\n const hasRecentHeartbeat = hasRecentActivity(issue.lastActivity);\n const stateLabel = deriveStateLabel(issue, hasTmux, hasRecentHeartbeat, membership);\n const isRemoteRunning = issue.resourceDetails.remoteAgent?.status === 'running'\n || issue.resourceDetails.remoteAgent?.status === 'starting';\n const status = (hasTmux && (issue.agentStatus === 'active' || hasRecentHeartbeat)) || isRemoteRunning\n ? 'running'\n : issue.hasState\n ? 'has_state'\n : 'idle';\n\n return {\n issueId: issue.issueId,\n title: issue.title,\n projectName: issue.projectName,\n branch: issue.branch,\n status,\n stateLabel,\n agentStatus: issue.agentStatus,\n hasPlanning: issue.hasPlanning,\n hasPrd: issue.hasPrd,\n hasState: issue.hasState,\n isShadow: issue.isShadow,\n isRally: issue.isRally,\n childCount: trackerIssues.get(issue.issueId)?.totalChildCount,\n completedCount: trackerIssues.get(issue.issueId)?.completedChildCount,\n inProgressCount: trackerIssues.get(issue.issueId)?.inProgressChildCount,\n readyForMerge: issue.readyForMerge,\n rawTrackerState: issue.rawTrackerState,\n resourceSources: new Set([...issue.resourceSources].sort()),\n resourceDetails: issue.resourceDetails,\n taskTotals: issue.taskTotals,\n pipelineBucket: membership?.bucket,\n specOnlyPlanned: membership\n ? membership.bucket === 'planned_backlog'\n && membership.reasons.includes(PLANNED_BACKLOG_SPEC_ONLY_REASON)\n : undefined,\n resourceDrift: membership ? !membership.inPipeline : undefined,\n } satisfies InternalDiscoveredIssue;\n });\n\n return discoveredIssues.sort((a, b) => compareIssueIds(a.issueId, b.issueId));\n}\n\nfunction toPublicResourceIssue(issue: InternalDiscoveredIssue): ResourceAllocatedIssue {\n return {\n ...issue,\n resourceSources: [...issue.resourceSources],\n resourceDetails: summarizeResourceDetails(issue.resourceDetails),\n };\n}\n\nfunction rebuildCombinedResourceSnapshot(): ResourceAllocatedIssue[] {\n const detailed = [...projectResourceSnapshots.values()]\n .flatMap((snapshot) => snapshot.detailed)\n .sort((a, b) => compareIssueIds(a.issueId, b.issueId));\n const publicIssues = detailed.map(toPublicResourceIssue);\n cachedDetailedResourceIssues = detailed;\n cachedResourceIssues = {\n value: publicIssues,\n computedAt: Math.max(0, ...[...projectResourceSnapshots.values()].map((snapshot) => snapshot.computedAt)),\n };\n return publicIssues;\n}\n\nfunction publishProjectResourceSnapshot(project: ProjectConfig, issues: InternalDiscoveredIssue[]): void {\n projectResourceSnapshots.set(project.path, {\n detailed: issues,\n public: issues.map(toPublicResourceIssue),\n computedAt: Date.now(),\n });\n rebuildCombinedResourceSnapshot();\n}\n\nexport interface RefreshResourceAllocatedProjectsOptions {\n refreshMembership?: boolean;\n}\n\n/** Refresh only the requested projects, capturing fleet-wide signals once for the batch. */\nexport async function refreshResourceAllocatedProjects(\n projects: ProjectConfig[],\n options: RefreshResourceAllocatedProjectsOptions = {},\n): Promise<ResourceAllocatedIssue[]> {\n if (projects.length === 0) return cachedResourceIssues?.value ?? [];\n const sharedSignals = await captureSharedResourceSignals();\n const failures: Error[] = [];\n for (const project of projects) {\n try {\n if (options.refreshMembership !== false) {\n await refreshMembershipSnapshotsForProjects([project]);\n }\n const issues = await computeResourceAllocatedIssues([project], sharedSignals);\n publishProjectResourceSnapshot(project, issues);\n } catch (error) {\n const failure = error instanceof Error ? error : new Error(String(error));\n failures.push(failure);\n console.warn(\n `[resource-discovery] refresh failed for ${project.name ?? project.path}; keeping last-good snapshot:`,\n failure.message,\n );\n }\n }\n if (failures.length > 0) {\n throw new AggregateError(failures, `${failures.length} project resource refresh(es) failed`);\n }\n return cachedResourceIssues?.value ?? [];\n}\n\nexport async function getCachedResourceAllocatedIssues(): Promise<ResourceAllocatedIssue[]> {\n return cachedResourceIssues?.value ?? [];\n}\n\nexport async function discoverResourceAllocatedIssues(): Promise<ResourceAllocatedIssue[]> {\n return triggerResourceDiscoveryRefresh();\n}\n\n/** Explicit all-project refresh for tests and diagnostics; runtime callers use the project queue. */\nexport function triggerResourceDiscoveryRefresh(): Promise<ResourceAllocatedIssue[]> {\n if (resourceIssuesRefreshPromise) return resourceIssuesRefreshPromise;\n resourceIssuesRefreshPromise = refreshResourceAllocatedProjects(\n listProjectsSync().map((entry) => entry.config),\n ).finally(() => {\n resourceIssuesRefreshPromise = null;\n });\n return resourceIssuesRefreshPromise;\n}\n\nexport async function discoverResourceAllocatedIssuesFresh(): Promise<ResourceAllocatedIssue[]> {\n const projects = listProjectsSync().map((entry) => entry.config);\n await refreshMembershipSnapshotsForProjects(projects);\n return (await computeResourceAllocatedIssues(projects)).map(toPublicResourceIssue);\n}\n\nexport function sanitizeResourceAllocatedIssues(issues: ResourceAllocatedIssue[]): ResourceAllocatedIssue[] {\n return issues.map((issue) => ({\n ...issue,\n resourceDetails: {\n hasWorkspace: issue.resourceDetails.hasWorkspace,\n localBranchCount: issue.resourceDetails.localBranchCount,\n remoteBranchCount: issue.resourceDetails.remoteBranchCount,\n tmuxSessionCount: issue.resourceDetails.tmuxSessionCount,\n prs: issue.resourceDetails.prs.map((pr) => ({\n number: pr.number,\n title: pr.title,\n state: pr.state,\n isDraft: pr.isDraft,\n })),\n hasXbrief: issue.resourceDetails.hasXbrief,\n hasTasks: issue.resourceDetails.hasTasks,\n hasPrd: issue.resourceDetails.hasPrd,\n dockerContainerCount: issue.resourceDetails.dockerContainerCount,\n actualBranch: issue.resourceDetails.actualBranch,\n branchDrifted: issue.resourceDetails.branchDrifted,\n branchAheadOfMain: issue.resourceDetails.branchAheadOfMain,\n workspaceMissing: issue.resourceDetails.workspaceMissing,\n remoteAgent: issue.resourceDetails.remoteAgent ?? null,\n conversations: issue.resourceDetails.conversations.map((conv) => ({ id: conv.id, name: conv.name, title: conv.title, status: conv.status })),\n },\n }));\n}\n\nexport function toPublicResourceDetailIdentifiers(details: InternalResourceDetails): ResourceDetailIdentifiers {\n return summarizeResourceDetailIdentifiers(details);\n}\n\nexport async function getResourceDetailIdentifiers(issueId: string): Promise<ResourceDetailIdentifiers | null> {\n const normalizedIssueId = issueId.toUpperCase();\n const cachedMatch = cachedDetailedResourceIssues?.find((entry) => entry.issueId === normalizedIssueId);\n return cachedMatch ? toPublicResourceDetailIdentifiers(cachedMatch.resourceDetails) : null;\n}\n\nexport function groupResourceAllocatedIssuesByProject(issues: ResourceAllocatedIssue[]): Array<{\n name: string;\n path: string;\n features: ResourceAllocatedIssue[];\n}> {\n const projectTree = new Map<string, { name: string; path: string; features: ResourceAllocatedIssue[] }>();\n\n for (const issue of issues) {\n const existing = projectTree.get(issue.projectName);\n if (existing) {\n existing.features.push(issue);\n continue;\n }\n projectTree.set(issue.projectName, {\n name: issue.projectName,\n path: issue.projectName,\n features: [issue],\n });\n }\n\n return [...projectTree.values()]\n .map((project) => ({\n ...project,\n features: project.features.sort((a, b) => compareIssueIds(a.issueId, b.issueId)),\n }))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\nexport function resetResourceAllocatedIssuesCacheForTests(): void {\n projectResourceSnapshots.clear();\n cachedResourceIssues = null;\n cachedDetailedResourceIssues = null;\n resourceIssuesRefreshPromise = null;\n}\n","import type { ProjectConfig } from '../../../lib/projects.js';\nimport { listProjectsSync } from '../../../lib/projects.js';\nimport { refreshResourceAllocatedProjects } from './resource-discovery.js';\n\nexport const PROJECT_RESOURCE_REFRESH_DEBOUNCE_MS = 1_000;\nexport const PROJECT_RESOURCE_CONVERGENCE_INTERVAL_MS = 5 * 60_000;\n\nexport interface ProjectResourceRefreshQueueState {\n running: boolean;\n activeProjectPaths: string[];\n pendingProjectPaths: string[];\n lastStartedAt: number | null;\n lastCompletedAt: number | null;\n lastError: string | null;\n}\n\nexport interface ProjectResourceRefreshContext {\n reasonsByProjectPath: ReadonlyMap<string, ReadonlySet<string>>;\n}\n\nexport interface ProjectResourceRefreshQueueDeps {\n refreshProjects(projects: ProjectConfig[], context: ProjectResourceRefreshContext): Promise<unknown>;\n debounceMs?: number;\n now?: () => number;\n}\n\nexport interface ProjectResourceRefreshQueue {\n enqueueProject(project: ProjectConfig, reason: string): void;\n enqueueProjects(projects: ProjectConfig[], reason: string): void;\n whenIdle(): Promise<void>;\n getState(): ProjectResourceRefreshQueueState;\n stop(): void;\n}\n\nexport function createProjectResourceRefreshQueue(\n deps: ProjectResourceRefreshQueueDeps,\n): ProjectResourceRefreshQueue {\n const debounceMs = deps.debounceMs ?? PROJECT_RESOURCE_REFRESH_DEBOUNCE_MS;\n const now = deps.now ?? Date.now;\n const pending = new Map<string, ProjectConfig>();\n const reasons = new Map<string, Set<string>>();\n const idleWaiters = new Set<() => void>();\n let activeProjectPaths: string[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n let running = false;\n let stopped = false;\n let lastStartedAt: number | null = null;\n let lastCompletedAt: number | null = null;\n let lastError: string | null = null;\n\n const isIdle = () => !running && timer === null && pending.size === 0;\n\n const resolveIdleWaiters = () => {\n if (!isIdle()) return;\n for (const resolve of idleWaiters) resolve();\n idleWaiters.clear();\n };\n\n const drain = async (): Promise<void> => {\n timer = null;\n if (running || stopped) return;\n running = true;\n try {\n while (!stopped && pending.size > 0) {\n const projects = [...pending.values()];\n const reasonsByProjectPath = new Map<string, ReadonlySet<string>>();\n for (const project of projects) {\n reasonsByProjectPath.set(project.path, new Set(reasons.get(project.path) ?? []));\n reasons.delete(project.path);\n }\n pending.clear();\n activeProjectPaths = projects.map((project) => project.path);\n lastStartedAt = now();\n try {\n await deps.refreshProjects(projects, { reasonsByProjectPath });\n lastError = null;\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error);\n console.warn('[project-resource-refresh] batch failed:', lastError);\n } finally {\n activeProjectPaths = [];\n lastCompletedAt = now();\n }\n }\n } finally {\n running = false;\n resolveIdleWaiters();\n }\n };\n\n const schedule = () => {\n if (stopped || running || timer) return;\n timer = setTimeout(() => void drain(), debounceMs);\n timer.unref?.();\n };\n\n const enqueueProject = (project: ProjectConfig, reason: string) => {\n if (stopped) return;\n pending.set(project.path, project);\n const projectReasons = reasons.get(project.path) ?? new Set<string>();\n projectReasons.add(reason);\n reasons.set(project.path, projectReasons);\n schedule();\n };\n\n return {\n enqueueProject,\n enqueueProjects(projects, reason) {\n for (const project of projects) enqueueProject(project, reason);\n },\n whenIdle() {\n if (isIdle()) return Promise.resolve();\n return new Promise<void>((resolve) => idleWaiters.add(resolve));\n },\n getState() {\n return {\n running,\n activeProjectPaths: [...activeProjectPaths],\n pendingProjectPaths: [...pending.keys()],\n lastStartedAt,\n lastCompletedAt,\n lastError,\n };\n },\n stop() {\n stopped = true;\n if (timer) clearTimeout(timer);\n timer = null;\n pending.clear();\n reasons.clear();\n resolveIdleWaiters();\n },\n };\n}\n\nconst MEMBERSHIP_REFRESH_REASONS = new Set([\n 'boot-warm',\n 'periodic-convergence',\n 'issue.statusChanged:closed-out',\n 'pull_request:opened',\n 'pull_request:closed',\n 'pull_request:reopened',\n]);\n\nexport function shouldRefreshMembershipForResourceRefresh(\n context: ProjectResourceRefreshContext,\n): boolean {\n return [...context.reasonsByProjectPath.values()]\n .some((projectReasons) => [...projectReasons].some((reason) => MEMBERSHIP_REFRESH_REASONS.has(reason)));\n}\n\nconst projectResourceRefreshQueue = createProjectResourceRefreshQueue({\n refreshProjects: (projects, context) => refreshResourceAllocatedProjects(projects, {\n refreshMembership: shouldRefreshMembershipForResourceRefresh(context),\n }),\n});\n\nexport function enqueueProjectResourceRefresh(project: ProjectConfig, reason: string): void {\n projectResourceRefreshQueue.enqueueProject(project, reason);\n}\n\nexport function enqueueProjectsResourceRefresh(projects: ProjectConfig[], reason: string): void {\n projectResourceRefreshQueue.enqueueProjects(projects, reason);\n}\n\nexport function whenProjectResourceRefreshIdle(): Promise<void> {\n return projectResourceRefreshQueue.whenIdle();\n}\n\nexport function getProjectResourceRefreshQueueState(): ProjectResourceRefreshQueueState {\n return projectResourceRefreshQueue.getState();\n}\n\nexport function stopProjectResourceRefreshQueue(): void {\n projectResourceRefreshQueue.stop();\n}\n\nexport function startProjectResourceConvergence(): () => void {\n const timer = setInterval(() => {\n enqueueProjectsResourceRefresh(\n listProjectsSync().map((entry) => entry.config),\n 'periodic-convergence',\n );\n }, PROJECT_RESOURCE_CONVERGENCE_INTERVAL_MS);\n timer.unref?.();\n return () => clearInterval(timer);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"project-resource-refresh-queue-se3T9IYP.js","names":["execFileAsync"],"sources":["../../src/dashboard/server/services/resource-discovery-shared.ts","../../src/dashboard/server/review-status.ts","../../src/dashboard/server/services/pipeline-membership.ts","../../src/dashboard/server/services/resource-discovery-signals.ts","../../src/dashboard/server/services/resource-discovery.ts","../../src/dashboard/server/services/project-resource-refresh-queue.ts"],"sourcesContent":["import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nimport { listConversations, type LegacyConversation as Conversation } from '../../../lib/overdeck/conversations.js';\nimport { getRuntimeCensus } from '../../../lib/runtime-census.js';\n\nconst execFileAsync = promisify(execFile);\n\nexport interface TrackerIssueRecord {\n identifier?: string;\n title?: string;\n state?: string;\n status?: string;\n rawTrackerState?: string;\n source?: string;\n totalChildCount?: number;\n completedChildCount?: number;\n inProgressChildCount?: number;\n}\n\ninterface ActiveRemoteAgentState {\n issueId: string;\n vmName: string;\n status: string;\n model: string;\n startedAt: string;\n}\n\nexport interface SharedResourceSignals {\n trackerIssues: Map<string, TrackerIssueRecord>;\n tmuxSessions: string[];\n dockerContainers: string[];\n conversations: Conversation[];\n remoteAgentStates: ActiveRemoteAgentState[];\n}\n\nasync function loadTrackerIssues(): Promise<Map<string, TrackerIssueRecord>> {\n const map = new Map<string, TrackerIssueRecord>();\n try {\n const { getSharedIssueService } = await import('./issue-service-singleton.js');\n const service = await getSharedIssueService();\n const issues = service.getIssues() as TrackerIssueRecord[];\n for (const issue of issues) {\n if (issue.identifier) map.set(issue.identifier.toUpperCase(), issue);\n }\n } catch {\n // A cold issue service contributes no tracker signals to this snapshot.\n }\n return map;\n}\n\nasync function loadDockerContainers(): Promise<string[]> {\n try {\n const { stdout } = await execFileAsync('docker', ['ps', '--format', '{{.Names}}'], {\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.split('\\n').map((line) => line.trim()).filter(Boolean);\n } catch {\n return [];\n }\n}\n\nasync function loadActiveRemoteAgentStates(): Promise<ActiveRemoteAgentState[]> {\n try {\n const { listActiveRemoteAgentStates } = await import('../../../lib/remote/remote-agents.js');\n return listActiveRemoteAgentStates();\n } catch {\n return [];\n }\n}\n\nexport async function captureSharedResourceSignals(): Promise<SharedResourceSignals> {\n const [trackerIssues, runtimeCensus, dockerContainers, remoteAgentStates] = await Promise.all([\n loadTrackerIssues(),\n getRuntimeCensus(),\n loadDockerContainers(),\n loadActiveRemoteAgentStates(),\n ]);\n const tmuxSessions = [...runtimeCensus.sessionNames];\n let conversations: Conversation[] = [];\n try {\n conversations = listConversations();\n } catch {\n // Conversation state is optional for resource discovery.\n }\n return { trackerIssues, tmuxSessions, dockerContainers, conversations, remoteAgentStates };\n}\n","/**\n * Re-export from canonical location at src/lib/review-status.ts\n * This file exists only for backward compatibility during the PAN-262 migration.\n */\nexport {\n loadReviewStatuses,\n loadReadyForMergeFlags,\n saveReviewStatuses,\n setReviewStatusSync,\n getReviewStatusSync,\n clearReviewStatus,\n} from '../../lib/review-status.js';\n\nexport type { ReviewStatus, StatusHistoryEntry } from '../../lib/review-status.js';\n","import type { IssuePipelineMembership, MembershipUnavailableReason } from '@overdeck/contracts';\nimport { createPromiseConcurrencyLimiter, createSettledTtlPromiseCache } from '../../../lib/concurrency.js';\nimport {\n gatherProjectLensSignals,\n mapPipelineProjects,\n PipelineMembershipUnavailableError,\n PIPELINE_PROJECT_CONCURRENCY,\n} from '../../../lib/pipeline-membership-gather.js';\nimport type { ProjectConfig } from '../../../lib/projects.js';\nimport { resolvePipelineMembership, type IssueLensSignals, type PipelineMembership } from '../../../lib/pipeline-membership.js';\n\nexport const PIPELINE_MEMBERSHIP_TTL_MS = 30_000;\nexport const PIPELINE_MEMBERSHIP_SNAPSHOT_TTL_MS = 5 * 60_000;\n\ninterface PipelineMembershipServiceDeps {\n gather(project: ProjectConfig): Promise<IssueLensSignals[]>;\n now(): number;\n}\n\nexport interface PipelineMembershipService {\n (project: ProjectConfig): Promise<PipelineMembership[]>;\n /** Drop the settled per-project TTL entry so the next call re-gathers (PAN-2893 event-driven refresh). */\n invalidate(projectPath?: string): void;\n}\n\nexport function createPipelineMembershipService(\n deps: PipelineMembershipServiceDeps = { gather: gatherProjectLensSignals, now: () => Date.now() },\n): PipelineMembershipService {\n const cachedGather = createSettledTtlPromiseCache<string, PipelineMembership[]>(PIPELINE_MEMBERSHIP_TTL_MS, deps.now);\n const service = async (project: ProjectConfig): Promise<PipelineMembership[]> =>\n cachedGather(project.path, () =>\n deps.gather(project).then((signals) => signals.map(resolvePipelineMembership)));\n service.invalidate = (projectPath?: string): void => cachedGather.invalidate(projectPath);\n return service;\n}\n\nexport const getProjectPipelineMembership = createPipelineMembershipService();\n\nexport async function getPipelineMembershipForProjects(\n projects: ProjectConfig[],\n getMembership = getProjectPipelineMembership,\n): Promise<PipelineMembership[]> {\n const results = await getPipelineMembershipResultsForProjects(projects, getMembership);\n const failed = results.find((result) => result.error);\n if (failed) throw failed.error;\n return results.flatMap((result) => result.memberships ?? []);\n}\n\nexport interface ProjectPipelineMembershipResult {\n project: ProjectConfig;\n memberships?: PipelineMembership[];\n error?: unknown;\n unavailableReason?: MembershipUnavailableReason;\n}\n\nexport async function getPipelineMembershipResultsForProjects(\n projects: ProjectConfig[],\n getMembership = getProjectPipelineMembership,\n): Promise<ProjectPipelineMembershipResult[]> {\n const gathered = await mapPipelineProjects(projects, getMembership);\n return gathered.map(({ project, value, error }) => {\n if (error) return { project, error };\n return { project, memberships: value ?? [] };\n });\n}\n\ntype MembershipLookup = (project: ProjectConfig) => Promise<PipelineMembership[]>;\n\ninterface MembershipSnapshot {\n value: PipelineMembership[];\n refreshedAt: number;\n refresh?: Promise<void>;\n lastError?: string;\n lastErrorReason?: MembershipUnavailableReason;\n lastErrorAt?: number;\n}\n\nconst membershipSnapshots = new Map<string, MembershipSnapshot>();\nconst scheduleMembershipRefresh = createPromiseConcurrencyLimiter(PIPELINE_PROJECT_CONCURRENCY);\n\nfunction refreshMembershipSnapshot(\n project: ProjectConfig,\n snapshot: MembershipSnapshot,\n getMembership: MembershipLookup,\n now: () => number,\n): Promise<void> {\n if (snapshot.refresh) return snapshot.refresh;\n snapshot.refresh = scheduleMembershipRefresh(() => getMembership(project)).then((value) => {\n snapshot.value = value;\n snapshot.refreshedAt = now();\n snapshot.lastError = undefined;\n snapshot.lastErrorReason = undefined;\n snapshot.lastErrorAt = undefined;\n }).catch((error: unknown) => {\n // Keep the last successful snapshot; a cold caller remains unavailable.\n // Record and log the failure — a swallowed boot-warm failure previously\n // left a cold project with zero diagnostic trace (PAN-2972).\n snapshot.lastError = error instanceof Error ? error.message : String(error);\n snapshot.lastErrorReason = error instanceof PipelineMembershipUnavailableError\n ? error.reason\n : 'gather_failed';\n snapshot.lastErrorAt = now();\n console.warn(\n `[pipeline-membership] refresh failed for ${project.name ?? project.path}; keeping last-good snapshot:`,\n snapshot.lastError,\n );\n }).finally(() => {\n snapshot.refresh = undefined;\n });\n return snapshot.refresh;\n}\n\n/** Read the latest successful snapshots without scheduling tracker or git work. */\nexport function readPipelineMembershipSnapshotsForProjects(\n projects: ProjectConfig[],\n): ProjectPipelineMembershipResult[] {\n return projects.map((project) => {\n const snapshot = membershipSnapshots.get(project.path);\n if (snapshot?.refreshedAt) return { project, memberships: snapshot.value };\n // Cold cache: surface the recorded failure when one exists so the operator\n // sees the real cause instead of a permanent \"loading\" (PAN-2972).\n return {\n project,\n error: new Error(snapshot?.lastError\n ? `Pipeline membership refresh failed: ${snapshot.lastError}`\n : 'Pipeline membership snapshot is loading'),\n unavailableReason: snapshot?.lastErrorReason,\n };\n });\n}\n\n/** Return the latest successful snapshot immediately and refresh stale/missing projects in the background. */\nexport function getPipelineMembershipSnapshotsForProjects(\n projects: ProjectConfig[],\n getMembership: MembershipLookup = getProjectPipelineMembership,\n now = () => Date.now(),\n): ProjectPipelineMembershipResult[] {\n const results = readPipelineMembershipSnapshotsForProjects(projects);\n for (const project of projects) {\n const snapshot = membershipSnapshots.get(project.path);\n if (!snapshot?.refresh && (!snapshot || now() - snapshot.refreshedAt >= PIPELINE_MEMBERSHIP_SNAPSHOT_TTL_MS)) {\n const current = snapshot ?? { value: [], refreshedAt: 0 };\n membershipSnapshots.set(project.path, current);\n void refreshMembershipSnapshot(project, current, getMembership, now);\n }\n }\n return results;\n}\n\n/** Await only the first snapshot; later resource refreshes consume stale data while revalidating. */\nexport async function getPipelineMembershipSnapshotsForResourceDiscovery(\n projects: ProjectConfig[],\n getMembership: MembershipLookup = getProjectPipelineMembership,\n now = () => Date.now(),\n): Promise<ProjectPipelineMembershipResult[]> {\n return Promise.all(projects.map(async (project) => {\n const existing = membershipSnapshots.get(project.path);\n if (existing?.refreshedAt) {\n if (now() - existing.refreshedAt >= PIPELINE_MEMBERSHIP_SNAPSHOT_TTL_MS) {\n void refreshMembershipSnapshot(project, existing, getMembership, now);\n }\n return { project, memberships: existing.value };\n }\n const snapshot = existing ?? { value: [], refreshedAt: 0 };\n membershipSnapshots.set(project.path, snapshot);\n await refreshMembershipSnapshot(project, snapshot, getMembership, now);\n return snapshot.refreshedAt\n ? { project, memberships: snapshot.value }\n : {\n project,\n error: new Error('Pipeline membership snapshot failed to load'),\n unavailableReason: snapshot.lastErrorReason,\n };\n }));\n}\n\n/**\n * PAN-2893 — event-driven refresh. Invalidate the per-project TTL cache and\n * force a snapshot re-gather NOW for the given projects, so agent lifecycle\n * changes reach membership consumers without waiting out the 5-minute TTL.\n * Refresh failures keep the last good snapshot (same policy as TTL refreshes).\n */\nexport async function refreshMembershipSnapshotsForProjects(\n projects: ProjectConfig[],\n getMembership = getProjectPipelineMembership,\n now = () => Date.now(),\n): Promise<void> {\n await Promise.all(projects.map(async (project) => {\n getMembership.invalidate(project.path);\n const snapshot = membershipSnapshots.get(project.path) ?? { value: [], refreshedAt: 0 };\n membershipSnapshots.set(project.path, snapshot);\n // A refresh that started BEFORE the triggering event may resolve with\n // pre-event data; chain a fresh gather behind it instead of reusing it.\n const inFlight = snapshot.refresh;\n if (inFlight) {\n await inFlight;\n getMembership.invalidate(project.path);\n }\n await refreshMembershipSnapshot(project, snapshot, getMembership, now);\n }));\n}\n\n/**\n * PAN-2893 — last successful membership result for a project, or null when no\n * gather has ever succeeded. Lets the membership route serve stale data through\n * a transient tracker failure (e.g. a Linear 503) instead of erroring the pane.\n */\nexport function getLastGoodMembershipSnapshot(projectPath: string): PipelineMembership[] | null {\n const snapshot = membershipSnapshots.get(projectPath);\n return snapshot?.refreshedAt ? snapshot.value : null;\n}\n\nexport function summarizePipelineMembership(membership: PipelineMembership): IssuePipelineMembership {\n return {\n available: true,\n inPipeline: membership.inPipeline,\n bucket: membership.bucket,\n labelDrift: membership.labelDrift,\n };\n}\n\nexport function unavailablePipelineMembership(): IssuePipelineMembership {\n return { available: false, inPipeline: false, bucket: 'clean_terminal', labelDrift: null };\n}\n","export const RECENCY_DAYS = 14;\nconst RECENCY_MS = RECENCY_DAYS * 24 * 60 * 60 * 1000;\n\nexport interface TaskTotals {\n total: number;\n closed: number;\n inProgress: number;\n lastUpdated: string | null;\n}\n\nexport function isWithinRecencyMs(timestamp: number): boolean {\n return Number.isFinite(timestamp) && (Date.now() - timestamp) < RECENCY_MS;\n}\n\nexport function isWithinRecencyDate(dateString: string | null): boolean {\n if (!dateString) return false;\n const parsed = Date.parse(dateString);\n return Number.isFinite(parsed) && (Date.now() - parsed) < RECENCY_MS;\n}\n","import { execFile } from 'node:child_process';\nimport { readdir, stat } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { promisify } from 'node:util';\nimport { Effect } from 'effect';\nimport { compareIssueIds } from '@overdeck/contracts';\n\nimport type { TaskTotals } from './resource-discovery-signals.js';\nimport { isWithinRecencyDate, isWithinRecencyMs } from './resource-discovery-signals.js';\nimport {\n captureSharedResourceSignals,\n type SharedResourceSignals,\n type TrackerIssueRecord,\n} from './resource-discovery-shared.js';\n\nimport { getAgentRuntimeState } from '../../../lib/agents.js';\nimport {\n PAN_CONTINUE_FILENAME,\n PAN_DIRNAME,\n} from '../../../lib/pan-dir/index.js';\nimport { findSpecByIssue } from '../../../lib/pan-dir/specs.js';\nimport { findDraftPrd } from '../../../lib/prd-locations.js';\nimport { listProjectsSync, resolveProjectFromIssueSync, type ProjectConfig, type ResolvedProject } from '../../../lib/projects.js';\nimport { listWorkspaces } from '../../../lib/workspaces/resolver.js';\nimport {\n PLANNED_BACKLOG_SPEC_ONLY_REASON,\n type PipelineBucket,\n type PipelineMembership,\n} from '../../../lib/pipeline-membership.js';\nimport { listOpenPullRequestsSnapshot } from '../../../lib/pipeline-membership-gather.js';\nimport { loadReadyForMergeFlags } from '../review-status.js';\nimport { resolveAgentGitInfo } from './git-info.js';\nimport { resolveMissingIssueTitles } from './issue-title-fallback.js';\nimport { parseIssueIdFromTextSync } from '../../../lib/resource-utils.js';\nimport {\n readPipelineMembershipSnapshotsForProjects,\n refreshMembershipSnapshotsForProjects,\n} from './pipeline-membership.js';\n\nconst execFileAsync = promisify(execFile);\nconst RECENT_ACTIVITY_WINDOW_MS = 5_000;\n\nexport { RECENCY_DAYS } from './resource-discovery-signals.js';\nexport type { TaskTotals } from './resource-discovery-signals.js';\n\nexport type ResourceSource = 'tracker' | 'tmux' | 'workspace' | 'branch' | 'pr' | 'prd' | 'vbrief' | 'tasks' | 'docker' | 'remote-agent' | 'conversation';\n\nexport interface ResourcePullRequest {\n number: number;\n title: string;\n url?: string;\n state: string;\n isDraft: boolean;\n}\n\nexport interface ResourceDetails {\n hasWorkspace: boolean;\n localBranchCount: number;\n remoteBranchCount: number;\n tmuxSessionCount: number;\n prs: ResourcePullRequest[];\n hasXbrief: boolean;\n hasTasks: boolean;\n hasPrd: boolean;\n dockerContainerCount: number;\n /** Current HEAD of the agent's workspace, or null when no workspace exists. */\n actualBranch: string | null;\n /** True when the workspace HEAD differs from the expected feature/<id> branch. */\n branchDrifted: boolean;\n /** True when a feature/* or bypass/* branch for the issue has unmerged commits not on main. */\n branchAheadOfMain: boolean;\n /** True when the workspace path is configured but missing on disk. */\n workspaceMissing: boolean;\n /** Remote (fly.io) work agent for this issue, when one is active (PAN-1676). */\n remoteAgent: { vmName: string; status: string; model: string; startedAt: string } | null;\n /** Non-archived conversations explicitly linked to this issue. */\n conversations: Array<{ id: number; name: string; title: string | null; status: string }>;\n}\n\nexport interface ResourceDetailIdentifiers {\n workspacePaths: string[];\n localBranchNames: string[];\n remoteBranchNames: string[];\n tmuxSessionNames: string[];\n prs: Array<{\n number: number;\n title: string;\n state: string;\n isDraft: boolean;\n }>;\n dockerContainerNames: string[];\n}\n\nexport interface ResourceAllocatedIssue {\n issueId: string;\n title: string;\n projectName: string;\n branch: string;\n status: string;\n stateLabel: string;\n agentStatus: string | null;\n hasPlanning: boolean;\n hasPrd: boolean;\n hasState: boolean;\n isShadow: boolean;\n isRally: boolean;\n childCount?: number;\n completedCount?: number;\n inProgressCount?: number;\n readyForMerge: boolean;\n rawTrackerState?: string;\n resourceSources: ResourceSource[];\n resourceDetails: ResourceDetails;\n taskTotals: TaskTotals | null;\n pipelineBucket?: PipelineBucket;\n /** PAN-2822: planned_backlog caused solely by the L6 spec lens — display surfaces may hide these. */\n specOnlyPlanned?: boolean;\n /** Live resource residue attached to a resolver-rejected terminal issue. */\n resourceDrift?: boolean;\n}\n\ninterface InternalResourceDetails {\n tmuxSessions: string[];\n workspacePath: string | null;\n localBranches: string[];\n remoteBranches: string[];\n prs: GhPullRequest[];\n xbriefPath: string | null;\n xbriefMtime: number | null;\n tasksPath: string | null;\n prdPath: string | null;\n dockerContainers: string[];\n actualBranch: string | null;\n branchDrifted: boolean;\n branchAheadOfMain: boolean;\n workspaceMissing: boolean;\n remoteAgent: { vmName: string; status: string; model: string; startedAt: string } | null;\n conversations: Array<{ id: number; name: string; title: string | null; status: string; tmuxSession: string | null }>;\n}\n\ninterface MutableResourceIssue {\n issueId: string;\n title: string;\n projectName: string;\n branch: string;\n trackerState: string | null;\n rawTrackerState?: string;\n isRally: boolean;\n hasPlanning: boolean;\n hasPrd: boolean;\n hasState: boolean;\n isShadow: boolean;\n agentStatus: string | null;\n readyForMerge: boolean;\n lastActivity: number | null;\n resourceSources: Set<ResourceSource>;\n resourceDetails: InternalResourceDetails;\n taskTotals: TaskTotals | null;\n}\n\ninterface InternalDiscoveredIssue extends Omit<ResourceAllocatedIssue, 'resourceSources' | 'resourceDetails'> {\n resourceSources: Set<ResourceSource>;\n resourceDetails: InternalResourceDetails;\n}\n\ninterface GhPullRequest {\n number: number;\n title: string;\n url: string;\n state: string;\n isDraft: boolean;\n headRefName: string;\n baseRefName: string;\n}\n\ninterface ProjectRef {\n key: string;\n config: ProjectConfig;\n}\n\ninterface ResourceDiscoveryCacheEntry {\n value: ResourceAllocatedIssue[];\n computedAt: number;\n}\n\ninterface ProjectResourceSnapshot {\n detailed: InternalDiscoveredIssue[];\n public: ResourceAllocatedIssue[];\n computedAt: number;\n}\n\nconst projectResourceSnapshots = new Map<string, ProjectResourceSnapshot>();\nlet cachedResourceIssues: ResourceDiscoveryCacheEntry | null = null;\nlet cachedDetailedResourceIssues: InternalDiscoveredIssue[] | null = null;\nlet resourceIssuesRefreshPromise: Promise<ResourceAllocatedIssue[]> | null = null;\n\nfunction projectPrefixes(project: ProjectRef): string[] {\n const prefixes = new Set<string>();\n if (project.config.issue_prefix) prefixes.add(project.config.issue_prefix.toUpperCase());\n for (const prefix of project.config.issue_prefixes ?? []) prefixes.add(prefix.toUpperCase());\n if (prefixes.size === 0) prefixes.add(project.key.toUpperCase().replace(/-/g, ''));\n return [...prefixes];\n}\n\nfunction deriveStateLabel(\n issue: MutableResourceIssue,\n hasTmux: boolean,\n hasFreshHeartbeat: boolean,\n membership?: PipelineMembership,\n): string {\n const trackerState = issue.trackerState ?? '';\n if (membership?.bucket === 'post_merge_limbo') return 'Merged — Needs Close-Out';\n if (membership?.lenses.L3_issueOpen === false) {\n return hasTmux ? 'Closed' : 'Done';\n }\n if (issue.readyForMerge) return 'In Review';\n if (trackerState === 'done' || trackerState === 'closed' || trackerState === 'canceled') {\n return hasTmux ? 'Closed' : 'Done';\n }\n if (trackerState === 'in_review') return 'In Review';\n if (trackerState === 'in_progress') return 'In Progress';\n if (hasTmux && (issue.agentStatus === 'active' || hasFreshHeartbeat)) return 'In Progress';\n if (issue.agentStatus === 'suspended') return 'Suspended';\n if (issue.hasPrd && !issue.hasState) return 'Planning';\n if (issue.hasState) return 'Has Context';\n if (issue.resourceSources.has('workspace') || issue.resourceSources.has('branch')) return 'Allocated';\n return 'Idle';\n}\n\nfunction sortPullRequests(prs: GhPullRequest[]): GhPullRequest[] {\n return [...prs].sort((a, b) => {\n if (a.isDraft !== b.isDraft) return a.isDraft ? 1 : -1;\n return a.number - b.number;\n });\n}\n\nfunction summarizeResourceDetails(details: InternalResourceDetails): ResourceDetails {\n return {\n hasWorkspace: details.workspacePath !== null,\n localBranchCount: details.localBranches.length,\n remoteBranchCount: details.remoteBranches.length,\n tmuxSessionCount: details.tmuxSessions.length,\n prs: sortPullRequests(details.prs).map((pr) => ({\n number: pr.number,\n title: pr.title,\n url: pr.url,\n state: pr.state,\n isDraft: pr.isDraft,\n })),\n hasXbrief: details.xbriefPath !== null,\n hasTasks: details.tasksPath !== null,\n hasPrd: details.prdPath !== null,\n dockerContainerCount: details.dockerContainers.length,\n actualBranch: details.actualBranch,\n branchDrifted: details.branchDrifted,\n branchAheadOfMain: details.branchAheadOfMain,\n workspaceMissing: details.workspaceMissing,\n remoteAgent: details.remoteAgent,\n conversations: details.conversations.map((conv) => ({ id: conv.id, name: conv.name, title: conv.title, status: conv.status })),\n };\n}\n\nfunction summarizeResourceDetailIdentifiers(details: InternalResourceDetails): ResourceDetailIdentifiers {\n return {\n workspacePaths: details.workspacePath ? [details.workspacePath] : [],\n localBranchNames: [...details.localBranches].sort(),\n remoteBranchNames: [...details.remoteBranches].sort(),\n tmuxSessionNames: [...details.tmuxSessions].sort(),\n prs: sortPullRequests(details.prs).map((pr) => ({\n number: pr.number,\n title: pr.title,\n state: pr.state,\n isDraft: pr.isDraft,\n })),\n dockerContainerNames: [...details.dockerContainers].sort(),\n };\n}\n\nfunction hasRecentActivity(lastActivity: number | null): boolean {\n return lastActivity !== null && Number.isFinite(lastActivity) && (Date.now() - lastActivity) < RECENT_ACTIVITY_WINDOW_MS;\n}\n\nfunction isLiveResource(issue: MutableResourceIssue): boolean {\n return issue.resourceDetails.remoteAgent !== null\n || issue.resourceDetails.tmuxSessions.length > 0\n || issue.resourceDetails.dockerContainers.length > 0\n || hasOpenPr(issue);\n}\n\nfunction isActiveTrackerState(state: string | null): boolean {\n return state === 'in_progress' || state === 'in_review' || state === 'started';\n}\n\nfunction isTerminalTrackerState(state: string | null): boolean {\n return state === 'closed' || state === 'done' || state === 'canceled' || state === 'completed';\n}\n\nfunction hasOpenPr(issue: MutableResourceIssue): boolean {\n return issue.resourceDetails.prs.some((pr) => pr.state === 'OPEN' || pr.state === 'open');\n}\n\nfunction shouldLoadReviewStatus(issue: MutableResourceIssue): boolean {\n return issue.resourceSources.size > 0\n && (!isTerminalTrackerState(issue.trackerState) || hasOpenPr(issue))\n && (isLiveResource(issue) || (isActiveTrackerState(issue.trackerState) && issue.branch != null));\n}\n\n/**\n * tmux session-name prefixes that map a session to its issue in the Command Deck\n * tree. `strike-` MUST be included (PAN-1682): strike sessions are named\n * `strike-<issue>` and a strike issue is typically `todo` (not an active tracker\n * state), so without registering the tmux resource the issue is filtered out of\n * the tree entirely. Keep this list in sync with the agent-session prefixes the\n * dashboard recognizes elsewhere (e.g. routes/agents.ts).\n */\nconst DISCOVERABLE_SESSION_PREFIXES = ['agent-', 'planning-', 'specialist-', 'review-', 'strike-'] as const;\n\nexport function isDiscoverableAgentSession(sessionName: string): boolean {\n return DISCOVERABLE_SESSION_PREFIXES.some((prefix) => sessionName.startsWith(prefix));\n}\n\nasync function loadOpenPullRequests(projects: ProjectRef[]): Promise<Map<string, GhPullRequest[]>> {\n const pullRequests = new Map<string, GhPullRequest[]>();\n await Promise.all(projects.map(async (project) => {\n if (!project.config.github_repo) return;\n const [owner, repo] = project.config.github_repo.split('/');\n if (!owner || !repo) return;\n try {\n const prs = await listOpenPullRequestsSnapshot(owner, repo);\n for (const row of prs) {\n if (row.number === undefined || row.title === undefined || row.url === undefined\n || row.state === undefined || row.isDraft === undefined || row.baseRefName === undefined) continue;\n const pr: GhPullRequest = {\n number: row.number, title: row.title, url: row.url, state: row.state,\n isDraft: row.isDraft, headRefName: row.headRefName, baseRefName: row.baseRefName,\n };\n const issueId = parseIssueIdFromTextSync(pr.headRefName);\n if (!issueId) continue;\n const existing = pullRequests.get(issueId) ?? [];\n existing.push(pr);\n pullRequests.set(issueId, existing);\n }\n } catch {\n // ignore repo-specific failures\n }\n }));\n\n for (const [issueId, prs] of pullRequests) {\n pullRequests.set(issueId, sortPullRequests(prs));\n }\n\n return pullRequests;\n}\n\nconst PROJECT_BRANCH_PATTERNS = [\n 'refs/heads/feature/*',\n 'refs/remotes/origin/feature/*',\n 'refs/heads/bypass/*',\n 'refs/remotes/origin/bypass/*',\n];\n\nasync function loadProjectBranchSnapshot(projectPath: string): Promise<{\n local: string[];\n remote: string[];\n ahead: Set<string>;\n}> {\n const run = (extraArgs: string[] = []) => execFileAsync('git', [\n 'for-each-ref',\n '--format=%(refname:short)',\n ...extraArgs,\n ...PROJECT_BRANCH_PATTERNS,\n ], {\n cwd: projectPath,\n encoding: 'utf-8',\n timeout: 10000,\n }).catch(() => ({ stdout: '' }));\n\n const [allRefs, unmergedRefs] = await Promise.all([\n run(),\n run(['--no-merged=main']),\n ]);\n const branches = allRefs.stdout.split('\\n').map((line) => line.trim()).filter(Boolean);\n const ahead = new Set(unmergedRefs.stdout.split('\\n').map((line) => line.trim()).filter(Boolean));\n\n return {\n local: branches.filter((branch) => !branch.startsWith('origin/')),\n remote: branches.filter((branch) => branch.startsWith('origin/')),\n ahead,\n };\n}\n\ninterface WorkspaceScanResult {\n workspacePath: string;\n hasPlanning: boolean;\n hasPrd: boolean;\n hasState: boolean;\n hasXbrief: boolean;\n xbriefPath: string | null;\n xbriefMtime: number | null;\n hasTasks: boolean;\n}\n\nasync function scanWorkspace(\n workspacesDir: string,\n workspaceName: string,\n): Promise<WorkspaceScanResult> {\n const workspacePath = join(workspacesDir, workspaceName);\n const projectRoot = join(workspacesDir, '..');\n const workspaceEntries = new Set(await readdir(workspacePath).catch(() => [] as string[]));\n const panEntries = workspaceEntries.has(PAN_DIRNAME)\n ? new Set(await readdir(join(workspacePath, PAN_DIRNAME)).catch(() => [] as string[]))\n : new Set<string>();\n const issueMatch = workspaceName.match(/^feature-([a-z]+-\\d+)$/i);\n const issueId = issueMatch ? issueMatch[1].toUpperCase() : null;\n const specEntry = issueId\n ? await Effect.runPromise(findSpecByIssue(projectRoot, issueId)).catch(() => null)\n : null;\n const xbriefPath = specEntry ? specEntry.path : null;\n let xbriefMtime: number | null = null;\n if (xbriefPath) {\n try {\n const stats = await stat(xbriefPath);\n xbriefMtime = stats.mtimeMs;\n } catch {\n xbriefMtime = null;\n }\n }\n\n return {\n workspacePath,\n hasPlanning: workspaceEntries.has(PAN_DIRNAME),\n hasPrd: panEntries.has('prd.md'),\n hasState: panEntries.has(PAN_CONTINUE_FILENAME),\n hasXbrief: xbriefPath !== null,\n xbriefPath,\n xbriefMtime,\n hasTasks: xbriefPath !== null,\n };\n}\n\nasync function computeResourceAllocatedIssues(\n projectConfigs: ProjectConfig[],\n sharedSignals?: SharedResourceSignals,\n): Promise<InternalDiscoveredIssue[]> {\n const registeredProjects = listProjectsSync() as ProjectRef[];\n const projects = projectConfigs.map((config) =>\n registeredProjects.find((entry) => entry.config.path === config.path)\n ?? { key: config.name ?? config.path, config });\n const {\n trackerIssues,\n tmuxSessions,\n dockerContainers,\n conversations,\n remoteAgentStates,\n } = sharedSignals ?? await captureSharedResourceSignals();\n const pullRequests = await loadOpenPullRequests(projects);\n\n const issueMap = new Map<string, MutableResourceIssue>();\n const projectByPrefix = new Map<string, ProjectRef>();\n for (const project of projects) {\n for (const prefix of projectPrefixes(project)) {\n projectByPrefix.set(prefix, project);\n }\n }\n\n const projectRefFromResolved = (resolvedProject: ResolvedProject): ProjectRef | null =>\n projects.find((project) =>\n project.key === resolvedProject.projectKey\n || project.config.path === resolvedProject.projectPath) ?? null;\n\n const resolveProjectRef = (issueId: string, preferredProject?: ProjectRef): ProjectRef | null => {\n if (preferredProject) return preferredProject;\n const resolvedProject = resolveProjectFromIssueSync(issueId);\n if (resolvedProject) {\n const project = projectRefFromResolved(resolvedProject);\n if (project) return project;\n }\n const prefix = issueId.toUpperCase().match(/^([A-Z]+)-\\d+$/)?.[1] ?? '';\n return projectByPrefix.get(prefix) ?? null;\n };\n\n const ensureIssue = (issueId: string, preferredProject?: ProjectRef): MutableResourceIssue | null => {\n const upper = issueId.toUpperCase();\n const existing = issueMap.get(upper);\n if (existing) return existing;\n\n const resolved = resolveProjectRef(upper, preferredProject);\n if (!resolved) return null;\n\n const tracker = trackerIssues.get(upper);\n const created: MutableResourceIssue = {\n issueId: upper,\n title: tracker?.title?.trim() || upper,\n projectName: resolved.config.name ?? resolved.key,\n branch: `feature/${upper.toLowerCase()}`,\n trackerState: typeof tracker?.state === 'string' ? tracker.state : null,\n rawTrackerState: tracker?.rawTrackerState,\n isRally: tracker?.source === 'rally',\n hasPlanning: false,\n hasPrd: false,\n hasState: false,\n isShadow: false,\n agentStatus: null,\n readyForMerge: false,\n lastActivity: null,\n resourceSources: new Set<ResourceSource>(),\n resourceDetails: {\n tmuxSessions: [],\n workspacePath: null,\n localBranches: [],\n remoteBranches: [],\n prs: [],\n xbriefPath: null,\n xbriefMtime: null,\n tasksPath: null,\n prdPath: null,\n dockerContainers: [],\n actualBranch: null,\n branchDrifted: false,\n branchAheadOfMain: false,\n workspaceMissing: false,\n remoteAgent: null,\n conversations: [],\n },\n taskTotals: null,\n };\n issueMap.set(upper, created);\n return created;\n };\n\n for (const [issueId, tracker] of trackerIssues) {\n const issue = ensureIssue(issueId);\n if (!issue) continue;\n issue.title = tracker.title?.trim() || issue.title;\n issue.trackerState = typeof tracker.state === 'string' ? tracker.state : issue.trackerState;\n issue.rawTrackerState = tracker.rawTrackerState ?? issue.rawTrackerState;\n issue.isRally = tracker.source === 'rally';\n issue.resourceSources.add('tracker');\n }\n\n for (const sessionName of tmuxSessions) {\n if (!isDiscoverableAgentSession(sessionName)) {\n continue;\n }\n const issueId = parseIssueIdFromTextSync(sessionName);\n if (!issueId) continue;\n const issue = ensureIssue(issueId);\n if (!issue) continue;\n issue.resourceSources.add('tmux');\n if (!issue.resourceDetails.tmuxSessions.includes(sessionName)) {\n issue.resourceDetails.tmuxSessions.push(sessionName);\n }\n }\n\n // Non-archived conversations explicitly linked to an issue are a distinct\n // resource signal from tmux/agent sessions (PAN-2602). They indicate active\n // operator attention even when no agent session is running.\n for (const conv of conversations) {\n if (!conv.issueId || conv.archivedAt) continue;\n const issue = ensureIssue(conv.issueId);\n if (!issue) continue;\n issue.resourceSources.add('conversation');\n issue.resourceDetails.conversations.push({\n id: conv.id,\n name: conv.name,\n title: conv.title,\n status: conv.status,\n tmuxSession: conv.tmuxSession,\n });\n }\n\n // Remote (fly.io) work agents have no local tmux session — surface them\n // from their remote-state.json so the tree shows the issue as actively\n // worked (PAN-1676).\n for (const remoteState of remoteAgentStates) {\n const issue = ensureIssue(remoteState.issueId);\n if (!issue) continue;\n issue.resourceSources.add('remote-agent');\n issue.resourceDetails.remoteAgent = {\n vmName: remoteState.vmName,\n status: remoteState.status,\n model: remoteState.model,\n startedAt: remoteState.startedAt,\n };\n if (!issue.agentStatus) issue.agentStatus = 'active';\n }\n\n for (const containerName of dockerContainers) {\n const issueId = parseIssueIdFromTextSync(containerName.replace(/feature\\//g, 'feature-'));\n if (!issueId) continue;\n const issue = ensureIssue(issueId);\n if (!issue) continue;\n issue.resourceSources.add('docker');\n issue.resourceDetails.dockerContainers.push(containerName);\n }\n\n await Promise.all(projects.map(async (project) => {\n const projectPath = project.config.path;\n const workspacesDir = join(projectPath, 'workspaces');\n // ONE bulk tasks read per project per refresh, rather than a per-workspace\n // read each 30s TTL — the bulk shape keeps refreshes cheap as the workspace\n // count grows.\n // PAN-1990: prefer the workspaces resolver (kind='issue' rows for this\n // project); fall back to the directory scan only when the table hasn't\n // been seeded yet for this project (pre-first-boot / not yet backfilled).\n const resolverRows = listWorkspaces({ projectId: project.key, kind: 'issue' });\n const [workspaceCandidates, branches] = await Promise.all([\n resolverRows.length > 0\n ? Promise.resolve(resolverRows.map((row) => ({\n name: basename(row.path),\n issueId: (row.issueId ?? basename(row.path).replace(/^feature-/, '')).toUpperCase(),\n })))\n : readdir(workspacesDir, { withFileTypes: true })\n .then((entries) => entries\n .filter((entry) => entry.isDirectory() && entry.name.startsWith('feature-'))\n .map((entry) => ({ name: entry.name, issueId: entry.name.replace(/^feature-/, '').toUpperCase() })))\n .catch(() => []),\n loadProjectBranchSnapshot(projectPath),\n ]);\n\n await Promise.all(workspaceCandidates.map(async ({ name, issueId }) => {\n const issue = ensureIssue(issueId, project);\n if (!issue) return;\n const workspace = await scanWorkspace(workspacesDir, name);\n issue.resourceSources.add('workspace');\n issue.resourceDetails.workspacePath = workspace.workspacePath;\n issue.hasPlanning = workspace.hasPlanning;\n issue.hasPrd = workspace.hasPrd;\n issue.hasState = workspace.hasState;\n if (workspace.xbriefPath) {\n issue.resourceSources.add('vbrief');\n issue.resourceDetails.xbriefPath = workspace.xbriefPath;\n issue.resourceDetails.xbriefMtime = workspace.xbriefMtime;\n }\n if (workspace.hasTasks) {\n issue.resourceSources.add('tasks');\n issue.resourceDetails.tasksPath = join(workspace.workspacePath, '.tasks');\n }\n const gitInfo = await resolveAgentGitInfo(workspace.workspacePath, issue.branch);\n issue.resourceDetails.actualBranch = gitInfo.actualBranch;\n issue.resourceDetails.branchDrifted = gitInfo.branchDrifted;\n issue.resourceDetails.workspaceMissing = gitInfo.workspaceMissing;\n }));\n\n for (const [branch, key] of [\n ...branches.local.map((b) => [b, 'localBranches'] as const),\n ...branches.remote.map((b) => [b, 'remoteBranches'] as const),\n ]) {\n const issueId = parseIssueIdFromTextSync(branch);\n if (!issueId) continue;\n const issue = ensureIssue(issueId, project);\n if (!issue) continue;\n issue.resourceSources.add('branch');\n if (!issue.resourceDetails[key].includes(branch)) {\n issue.resourceDetails[key].push(branch);\n }\n if (branches.ahead.has(branch)) {\n issue.resourceDetails.branchAheadOfMain = true;\n }\n }\n }));\n\n for (const [issueId, prs] of pullRequests) {\n const issue = ensureIssue(issueId);\n if (!issue) continue;\n issue.resourceSources.add('pr');\n issue.resourceDetails.prs = prs;\n const bestTitle = prs.find((pr) => !pr.isDraft)?.title ?? prs[0]?.title;\n issue.title = issue.title === issue.issueId && bestTitle ? bestTitle : issue.title;\n }\n\n await Promise.all([...issueMap.values()].map(async (issue) => {\n if (issue.resourceDetails.tmuxSessions.length === 0) return;\n\n // Runtime-state ids equal session names (agent-<issue>, strike-<issue>,\n // planning-<issue>, ...). Probe every discovered session — not just\n // agent-<issue> — so strike/planning sessions surface as live agents.\n // PAN-1682 made these sessions discoverable but left attribution\n // agent-only, which rendered a running strike as a lifeless node.\n const states = (await Promise.all(\n issue.resourceDetails.tmuxSessions.map((sessionName) =>\n Effect.runPromise(getAgentRuntimeState(sessionName)).catch(() => null),\n ),\n )).filter((state): state is NonNullable<typeof state> => state !== null);\n if (states.length === 0) return;\n\n const best = states.find((state) => state.state === 'active')\n ?? states.reduce((a, b) => (Date.parse(a.lastActivity) >= Date.parse(b.lastActivity) ? a : b));\n issue.agentStatus = best.state;\n\n const lastActivity = Math.max(\n ...states.map((state) => Date.parse(state.lastActivity)).filter(Number.isFinite),\n );\n issue.lastActivity = Number.isFinite(lastActivity) ? lastActivity : null;\n }));\n\n const reviewStatusIssueIds = [...issueMap.values()]\n .filter(shouldLoadReviewStatus)\n .map((issue) => issue.issueId);\n if (reviewStatusIssueIds.length > 0) {\n const readyForMergeFlags = loadReadyForMergeFlags(reviewStatusIssueIds);\n for (const issue of issueMap.values()) {\n issue.readyForMerge = readyForMergeFlags.get(issue.issueId) ?? false;\n }\n }\n\n const memberships = new Map<string, PipelineMembership>();\n const unavailableProjects = new Set<string>();\n const projectMemberships = readPipelineMembershipSnapshotsForProjects(projects.map((project) => project.config));\n for (const result of projectMemberships) {\n if (result.error) unavailableProjects.add(result.project.name);\n for (const membership of result.memberships ?? []) {\n ensureIssue(membership.issueId);\n memberships.set(membership.issueId, membership);\n }\n }\n\n await Promise.all([...issueMap.values()].map(async (issue) => {\n const project = resolveProjectRef(issue.issueId);\n if (!project) return;\n const draft = await Effect.runPromise(findDraftPrd(project.config.path, issue.issueId)).catch(() => null);\n if (!draft) return;\n issue.resourceSources.add('prd');\n issue.resourceDetails.prdPath = draft.path;\n issue.hasPrd = true;\n }));\n\n // Issues that aged out of the tracker sync window (closed long ago) still\n // surface here through their surviving local resources, but carry no tracker\n // row — their title fell back to the bare identifier. Resolve those titles\n // through the tracker's per-issue door, memoized across passes (PAN-3337).\n const untitledIssueIds = [...issueMap.values()]\n .filter((issue) => issue.resourceSources.size > 0 && issue.title === issue.issueId)\n .map((issue) => issue.issueId);\n if (untitledIssueIds.length > 0) {\n const fallbackTitles = await resolveMissingIssueTitles(untitledIssueIds).catch(() => new Map<string, string>());\n for (const issue of issueMap.values()) {\n const title = fallbackTitles.get(issue.issueId);\n if (title && issue.title === issue.issueId) issue.title = title;\n }\n }\n\n // The canonical resolver owns pipeline inclusion whenever it returns a verdict.\n // While a project's membership is unavailable, retain only rows backed by a live\n // resource so a transient tracker failure cannot erase running work.\n const discoveredIssues = [...issueMap.values()]\n .filter((issue) => issue.resourceSources.size > 0)\n .filter((issue) => {\n const membership = memberships.get(issue.issueId);\n return membership?.inPipeline === true\n || (membership?.bucket === 'clean_terminal' && isLiveResource(issue))\n || (!membership && unavailableProjects.has(issue.projectName) && isLiveResource(issue));\n })\n .map((issue) => {\n const membership = memberships.get(issue.issueId);\n const hasTmux = issue.resourceDetails.tmuxSessions.length > 0;\n const hasRecentHeartbeat = hasRecentActivity(issue.lastActivity);\n const stateLabel = deriveStateLabel(issue, hasTmux, hasRecentHeartbeat, membership);\n const isRemoteRunning = issue.resourceDetails.remoteAgent?.status === 'running'\n || issue.resourceDetails.remoteAgent?.status === 'starting';\n const status = (hasTmux && (issue.agentStatus === 'active' || hasRecentHeartbeat)) || isRemoteRunning\n ? 'running'\n : issue.hasState\n ? 'has_state'\n : 'idle';\n\n return {\n issueId: issue.issueId,\n title: issue.title,\n projectName: issue.projectName,\n branch: issue.branch,\n status,\n stateLabel,\n agentStatus: issue.agentStatus,\n hasPlanning: issue.hasPlanning,\n hasPrd: issue.hasPrd,\n hasState: issue.hasState,\n isShadow: issue.isShadow,\n isRally: issue.isRally,\n childCount: trackerIssues.get(issue.issueId)?.totalChildCount,\n completedCount: trackerIssues.get(issue.issueId)?.completedChildCount,\n inProgressCount: trackerIssues.get(issue.issueId)?.inProgressChildCount,\n readyForMerge: issue.readyForMerge,\n rawTrackerState: issue.rawTrackerState,\n resourceSources: new Set([...issue.resourceSources].sort()),\n resourceDetails: issue.resourceDetails,\n taskTotals: issue.taskTotals,\n pipelineBucket: membership?.bucket,\n specOnlyPlanned: membership\n ? membership.bucket === 'planned_backlog'\n && membership.reasons.includes(PLANNED_BACKLOG_SPEC_ONLY_REASON)\n : undefined,\n resourceDrift: membership ? !membership.inPipeline : undefined,\n } satisfies InternalDiscoveredIssue;\n });\n\n return discoveredIssues.sort((a, b) => compareIssueIds(a.issueId, b.issueId));\n}\n\nfunction toPublicResourceIssue(issue: InternalDiscoveredIssue): ResourceAllocatedIssue {\n return {\n ...issue,\n resourceSources: [...issue.resourceSources],\n resourceDetails: summarizeResourceDetails(issue.resourceDetails),\n };\n}\n\nfunction rebuildCombinedResourceSnapshot(): ResourceAllocatedIssue[] {\n const detailed = [...projectResourceSnapshots.values()]\n .flatMap((snapshot) => snapshot.detailed)\n .sort((a, b) => compareIssueIds(a.issueId, b.issueId));\n const publicIssues = detailed.map(toPublicResourceIssue);\n cachedDetailedResourceIssues = detailed;\n cachedResourceIssues = {\n value: publicIssues,\n computedAt: Math.max(0, ...[...projectResourceSnapshots.values()].map((snapshot) => snapshot.computedAt)),\n };\n return publicIssues;\n}\n\nfunction publishProjectResourceSnapshot(project: ProjectConfig, issues: InternalDiscoveredIssue[]): void {\n projectResourceSnapshots.set(project.path, {\n detailed: issues,\n public: issues.map(toPublicResourceIssue),\n computedAt: Date.now(),\n });\n rebuildCombinedResourceSnapshot();\n}\n\nexport interface RefreshResourceAllocatedProjectsOptions {\n refreshMembership?: boolean;\n}\n\n/** Refresh only the requested projects, capturing fleet-wide signals once for the batch. */\nexport async function refreshResourceAllocatedProjects(\n projects: ProjectConfig[],\n options: RefreshResourceAllocatedProjectsOptions = {},\n): Promise<ResourceAllocatedIssue[]> {\n if (projects.length === 0) return cachedResourceIssues?.value ?? [];\n const sharedSignals = await captureSharedResourceSignals();\n const failures: Error[] = [];\n for (const project of projects) {\n try {\n if (options.refreshMembership !== false) {\n await refreshMembershipSnapshotsForProjects([project]);\n }\n const issues = await computeResourceAllocatedIssues([project], sharedSignals);\n publishProjectResourceSnapshot(project, issues);\n } catch (error) {\n const failure = error instanceof Error ? error : new Error(String(error));\n failures.push(failure);\n console.warn(\n `[resource-discovery] refresh failed for ${project.name ?? project.path}; keeping last-good snapshot:`,\n failure.message,\n );\n }\n }\n if (failures.length > 0) {\n throw new AggregateError(failures, `${failures.length} project resource refresh(es) failed`);\n }\n return cachedResourceIssues?.value ?? [];\n}\n\nexport async function getCachedResourceAllocatedIssues(): Promise<ResourceAllocatedIssue[]> {\n return cachedResourceIssues?.value ?? [];\n}\n\nexport async function discoverResourceAllocatedIssues(): Promise<ResourceAllocatedIssue[]> {\n return triggerResourceDiscoveryRefresh();\n}\n\n/** Explicit all-project refresh for tests and diagnostics; runtime callers use the project queue. */\nexport function triggerResourceDiscoveryRefresh(): Promise<ResourceAllocatedIssue[]> {\n if (resourceIssuesRefreshPromise) return resourceIssuesRefreshPromise;\n resourceIssuesRefreshPromise = refreshResourceAllocatedProjects(\n listProjectsSync().map((entry) => entry.config),\n ).finally(() => {\n resourceIssuesRefreshPromise = null;\n });\n return resourceIssuesRefreshPromise;\n}\n\nexport async function discoverResourceAllocatedIssuesFresh(): Promise<ResourceAllocatedIssue[]> {\n const projects = listProjectsSync().map((entry) => entry.config);\n await refreshMembershipSnapshotsForProjects(projects);\n return (await computeResourceAllocatedIssues(projects)).map(toPublicResourceIssue);\n}\n\nexport function sanitizeResourceAllocatedIssues(issues: ResourceAllocatedIssue[]): ResourceAllocatedIssue[] {\n return issues.map((issue) => ({\n ...issue,\n resourceDetails: {\n hasWorkspace: issue.resourceDetails.hasWorkspace,\n localBranchCount: issue.resourceDetails.localBranchCount,\n remoteBranchCount: issue.resourceDetails.remoteBranchCount,\n tmuxSessionCount: issue.resourceDetails.tmuxSessionCount,\n prs: issue.resourceDetails.prs.map((pr) => ({\n number: pr.number,\n title: pr.title,\n state: pr.state,\n isDraft: pr.isDraft,\n })),\n hasXbrief: issue.resourceDetails.hasXbrief,\n hasTasks: issue.resourceDetails.hasTasks,\n hasPrd: issue.resourceDetails.hasPrd,\n dockerContainerCount: issue.resourceDetails.dockerContainerCount,\n actualBranch: issue.resourceDetails.actualBranch,\n branchDrifted: issue.resourceDetails.branchDrifted,\n branchAheadOfMain: issue.resourceDetails.branchAheadOfMain,\n workspaceMissing: issue.resourceDetails.workspaceMissing,\n remoteAgent: issue.resourceDetails.remoteAgent ?? null,\n conversations: issue.resourceDetails.conversations.map((conv) => ({ id: conv.id, name: conv.name, title: conv.title, status: conv.status })),\n },\n }));\n}\n\nexport function toPublicResourceDetailIdentifiers(details: InternalResourceDetails): ResourceDetailIdentifiers {\n return summarizeResourceDetailIdentifiers(details);\n}\n\nexport async function getResourceDetailIdentifiers(issueId: string): Promise<ResourceDetailIdentifiers | null> {\n const normalizedIssueId = issueId.toUpperCase();\n const cachedMatch = cachedDetailedResourceIssues?.find((entry) => entry.issueId === normalizedIssueId);\n return cachedMatch ? toPublicResourceDetailIdentifiers(cachedMatch.resourceDetails) : null;\n}\n\nexport function groupResourceAllocatedIssuesByProject(issues: ResourceAllocatedIssue[]): Array<{\n name: string;\n path: string;\n features: ResourceAllocatedIssue[];\n}> {\n const projectTree = new Map<string, { name: string; path: string; features: ResourceAllocatedIssue[] }>();\n\n for (const issue of issues) {\n const existing = projectTree.get(issue.projectName);\n if (existing) {\n existing.features.push(issue);\n continue;\n }\n projectTree.set(issue.projectName, {\n name: issue.projectName,\n path: issue.projectName,\n features: [issue],\n });\n }\n\n return [...projectTree.values()]\n .map((project) => ({\n ...project,\n features: project.features.sort((a, b) => compareIssueIds(a.issueId, b.issueId)),\n }))\n .sort((a, b) => a.name.localeCompare(b.name));\n}\n\nexport function resetResourceAllocatedIssuesCacheForTests(): void {\n projectResourceSnapshots.clear();\n cachedResourceIssues = null;\n cachedDetailedResourceIssues = null;\n resourceIssuesRefreshPromise = null;\n}\n","import type { ProjectConfig } from '../../../lib/projects.js';\nimport { listProjectsSync } from '../../../lib/projects.js';\nimport { refreshResourceAllocatedProjects } from './resource-discovery.js';\n\nexport const PROJECT_RESOURCE_REFRESH_DEBOUNCE_MS = 1_000;\nexport const PROJECT_RESOURCE_CONVERGENCE_INTERVAL_MS = 5 * 60_000;\n\nexport interface ProjectResourceRefreshQueueState {\n running: boolean;\n activeProjectPaths: string[];\n pendingProjectPaths: string[];\n lastStartedAt: number | null;\n lastCompletedAt: number | null;\n lastError: string | null;\n}\n\nexport interface ProjectResourceRefreshContext {\n reasonsByProjectPath: ReadonlyMap<string, ReadonlySet<string>>;\n}\n\nexport interface ProjectResourceRefreshQueueDeps {\n refreshProjects(projects: ProjectConfig[], context: ProjectResourceRefreshContext): Promise<unknown>;\n debounceMs?: number;\n now?: () => number;\n}\n\nexport interface ProjectResourceRefreshQueue {\n enqueueProject(project: ProjectConfig, reason: string): void;\n enqueueProjects(projects: ProjectConfig[], reason: string): void;\n whenIdle(): Promise<void>;\n getState(): ProjectResourceRefreshQueueState;\n stop(): void;\n}\n\nexport function createProjectResourceRefreshQueue(\n deps: ProjectResourceRefreshQueueDeps,\n): ProjectResourceRefreshQueue {\n const debounceMs = deps.debounceMs ?? PROJECT_RESOURCE_REFRESH_DEBOUNCE_MS;\n const now = deps.now ?? Date.now;\n const pending = new Map<string, ProjectConfig>();\n const reasons = new Map<string, Set<string>>();\n const idleWaiters = new Set<() => void>();\n let activeProjectPaths: string[] = [];\n let timer: ReturnType<typeof setTimeout> | null = null;\n let running = false;\n let stopped = false;\n let lastStartedAt: number | null = null;\n let lastCompletedAt: number | null = null;\n let lastError: string | null = null;\n\n const isIdle = () => !running && timer === null && pending.size === 0;\n\n const resolveIdleWaiters = () => {\n if (!isIdle()) return;\n for (const resolve of idleWaiters) resolve();\n idleWaiters.clear();\n };\n\n const drain = async (): Promise<void> => {\n timer = null;\n if (running || stopped) return;\n running = true;\n try {\n while (!stopped && pending.size > 0) {\n const projects = [...pending.values()];\n const reasonsByProjectPath = new Map<string, ReadonlySet<string>>();\n for (const project of projects) {\n reasonsByProjectPath.set(project.path, new Set(reasons.get(project.path) ?? []));\n reasons.delete(project.path);\n }\n pending.clear();\n activeProjectPaths = projects.map((project) => project.path);\n lastStartedAt = now();\n try {\n await deps.refreshProjects(projects, { reasonsByProjectPath });\n lastError = null;\n } catch (error) {\n lastError = error instanceof Error ? error.message : String(error);\n console.warn('[project-resource-refresh] batch failed:', lastError);\n } finally {\n activeProjectPaths = [];\n lastCompletedAt = now();\n }\n }\n } finally {\n running = false;\n resolveIdleWaiters();\n }\n };\n\n const schedule = () => {\n if (stopped || running || timer) return;\n timer = setTimeout(() => void drain(), debounceMs);\n timer.unref?.();\n };\n\n const enqueueProject = (project: ProjectConfig, reason: string) => {\n if (stopped) return;\n pending.set(project.path, project);\n const projectReasons = reasons.get(project.path) ?? new Set<string>();\n projectReasons.add(reason);\n reasons.set(project.path, projectReasons);\n schedule();\n };\n\n return {\n enqueueProject,\n enqueueProjects(projects, reason) {\n for (const project of projects) enqueueProject(project, reason);\n },\n whenIdle() {\n if (isIdle()) return Promise.resolve();\n return new Promise<void>((resolve) => idleWaiters.add(resolve));\n },\n getState() {\n return {\n running,\n activeProjectPaths: [...activeProjectPaths],\n pendingProjectPaths: [...pending.keys()],\n lastStartedAt,\n lastCompletedAt,\n lastError,\n };\n },\n stop() {\n stopped = true;\n if (timer) clearTimeout(timer);\n timer = null;\n pending.clear();\n reasons.clear();\n resolveIdleWaiters();\n },\n };\n}\n\nconst MEMBERSHIP_REFRESH_REASONS = new Set([\n 'boot-warm',\n 'periodic-convergence',\n 'issue.statusChanged:closed-out',\n 'pull_request:opened',\n 'pull_request:closed',\n 'pull_request:reopened',\n]);\n\nexport function shouldRefreshMembershipForResourceRefresh(\n context: ProjectResourceRefreshContext,\n): boolean {\n return [...context.reasonsByProjectPath.values()]\n .some((projectReasons) => [...projectReasons].some((reason) => MEMBERSHIP_REFRESH_REASONS.has(reason)));\n}\n\nconst projectResourceRefreshQueue = createProjectResourceRefreshQueue({\n refreshProjects: (projects, context) => refreshResourceAllocatedProjects(projects, {\n refreshMembership: shouldRefreshMembershipForResourceRefresh(context),\n }),\n});\n\nexport function enqueueProjectResourceRefresh(project: ProjectConfig, reason: string): void {\n projectResourceRefreshQueue.enqueueProject(project, reason);\n}\n\nexport function enqueueProjectsResourceRefresh(projects: ProjectConfig[], reason: string): void {\n projectResourceRefreshQueue.enqueueProjects(projects, reason);\n}\n\nexport function whenProjectResourceRefreshIdle(): Promise<void> {\n return projectResourceRefreshQueue.whenIdle();\n}\n\nexport function getProjectResourceRefreshQueueState(): ProjectResourceRefreshQueueState {\n return projectResourceRefreshQueue.getState();\n}\n\nexport function stopProjectResourceRefreshQueue(): void {\n projectResourceRefreshQueue.stop();\n}\n\nexport function startProjectResourceConvergence(): () => void {\n const timer = setInterval(() => {\n enqueueProjectsResourceRefresh(\n listProjectsSync().map((entry) => entry.config),\n 'periodic-convergence',\n );\n }, PROJECT_RESOURCE_CONVERGENCE_INTERVAL_MS);\n timer.unref?.();\n return () => clearInterval(timer);\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { $ as readIssueRecord, Y as init_record, fn as init_projects, rt as resolveProjectForIssue, vn as listProjectsSync } from "./config-yaml-Dce9VUd6.js";
2
2
  import { c as init_agents, i as backfillAgentsSync, u as listAllAgentsSync } from "./agents-CkngXhQr.js";
3
- import { Ln as listRunningAgents, q as init_agents$1 } from "./agent-state-Ci6PhjOE.js";
3
+ import { Ln as listRunningAgents, q as init_agents$1 } from "./agent-state-B196fj1h.js";
4
4
  import { a as gatherProjectLensSignals, o as gatherProjectLensSignalsForProjects, s as init_pipeline_membership_gather } from "./pipeline-membership-gather-QYMqf_Wf.js";
5
5
  import { n as init_pipeline_membership, r as resolvePipelineMembership } from "./pipeline-membership-DxJOJOjn.js";
6
6
  import { Effect } from "effect";
@@ -263,4 +263,4 @@ function reconstructCacheAuto(opts) {
263
263
  //#endregion
264
264
  export { reconstructCache, reconstructCacheAuto };
265
265
 
266
- //# sourceMappingURL=reconstruct-cache-Bb2Jg2DZ.js.map
266
+ //# sourceMappingURL=reconstruct-cache-XpWC9L0F.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"reconstruct-cache-Bb2Jg2DZ.js","names":[],"sources":["../../src/lib/reconstruct/enumerate-in-flight.ts","../../src/lib/reconstruct/derive-phase.ts","../../src/lib/reconstruct/reconstruct-cache.ts"],"sourcesContent":["/** Resolver-backed source-of-truth enumerator for pipeline members (PAN-1920/PAN-1966). */\n\nimport { gatherProjectLensSignals, gatherProjectLensSignalsForProjects } from '../pipeline-membership-gather.js';\nimport { resolvePipelineMembership, type IssueLensSignals } from '../pipeline-membership.js';\nimport type { ProjectConfig } from '../projects.js';\n\nexport async function enumerateInFlightIssuesFromSources(\n projects: ProjectConfig[],\n gather: (project: ProjectConfig) => Promise<IssueLensSignals[]> = gatherProjectLensSignals,\n): Promise<Set<string>> {\n const results = await gatherProjectLensSignalsForProjects(projects, gather);\n // Degrade per project, never throw: this runs during dashboard boot\n // (reconstruct-cache), and one project's unreadable lens — a GitHub App\n // token without access, a phantom issue id in a spec — must not take the\n // whole server down (PAN-2820: boot died before the HTTP listen). A failed\n // project's issues are simply not enumerated this pass; reconstruct keeps\n // its other sources and the next pass retries.\n for (const result of results) {\n if (result.error) {\n const message = result.error instanceof Error ? result.error.message : String(result.error);\n console.error(`[enumerate-in-flight] Skipping project ${result.project.name}: lens gather failed — ${message.slice(0, 300)}`);\n }\n }\n const signals = results.flatMap((result) => result.signals ?? []);\n return new Set(\n signals\n .map(resolvePipelineMembership)\n .filter((membership) => membership.inPipeline)\n .map((membership) => membership.issueId),\n );\n}\n","/**\n * PAN-1920: derive the dashboard pipeline phase from sources of truth only.\n *\n * The phase is a function of GitHub issue state, the per-issue git-tracked\n * record, and the GitHub PR reviewDecision. It never reads the SQLite cache.\n */\n\nimport type { PanIssueRecord } from '../pan-dir/record.js';\n\nexport type PipelinePhase = 'work' | 'review' | 'merge' | 'done';\n\nexport interface DerivePhaseArgs {\n /** True when the tracker issue itself is closed/done. */\n issueClosed: boolean;\n /** True when a PR exists for the feature branch. */\n hasPr: boolean;\n /** Per-issue git-tracked record (may be null while PAN-1919 is rolling out). */\n record: PanIssueRecord | null;\n /** GitHub `reviewDecision` from `gh pr view --json reviewDecision`. */\n reviewDecision: string | null;\n}\n\n/**\n * Derive the pipeline phase from sources.\n *\n * Rules (D3/D4):\n * - issue closed on tracker → done\n * - no PR → work\n * - approved (record readyForMerge / reviewStatus passed / GitHub APPROVED) → merge\n * - PR open but not approved → review\n */\nexport function derivePipelinePhase(args: DerivePhaseArgs): PipelinePhase {\n if (args.issueClosed) return 'done';\n if (!args.hasPr) return 'work';\n\n const approved =\n args.record?.pipeline?.readyForMerge === true ||\n args.record?.pipeline?.reviewStatus === 'passed' ||\n args.reviewDecision === 'APPROVED';\n\n return approved ? 'merge' : 'review';\n}\n","/**\n * PAN-1920: reconstruct the dashboard cache from sources of truth.\n *\n * Rebuilds the agents table from state.json + tmux, enumerates in-flight\n * issues from GitHub + workspaces, derives pipeline phases from the per-issue\n * record + GitHub PR state, and produces the AgentSnapshot / AgentRuntimeSnapshot\n * maps the dashboard bootstrap paths need.\n *\n * Reads NO SQLite cache tables: no events, no projection cache, no review_status.\n */\n\nimport { Effect } from 'effect';\nimport type {\n AgentRuntimeSnapshot,\n AgentSnapshot,\n ReviewStatusSnapshot,\n} from '@overdeck/contracts';\nimport { backfillAgentsSync, listAllAgentsSync } from '../overdeck/agents.js';\nimport { listRunningAgents, type AgentState } from '../agents.js';\nimport { listProjectsSync, type ProjectConfig } from '../projects.js';\nimport {\n readIssueRecord,\n resolveProjectForIssue,\n type PanIssueRecord,\n} from '../pan-dir/record.js';\nimport { enumerateInFlightIssuesFromSources } from './enumerate-in-flight.js';\nimport { derivePipelinePhase, type PipelinePhase } from './derive-phase.js';\n\nexport interface ReconstructOptions {\n verbose?: boolean;\n /** Override tmux session discovery for tests / headless environments. */\n listLiveSessions?: () => Set<string>;\n}\n\nexport interface ReconstructResult {\n issuesEnumerated: number;\n agentsRebuilt: number;\n markedStoppedIds: Array<{ id: string; previousStatus: string }>;\n phaseCounts: Record<PipelinePhase, number>;\n agentRuntimeById: Record<string, AgentRuntimeSnapshot>;\n agentsById: Record<string, AgentSnapshot>;\n reviewStatusByIssueId: Record<string, ReviewStatusSnapshot>;\n phaseByIssueId: Record<string, PipelinePhase>;\n}\n\nfunction toAgentStatus(status: string): AgentSnapshot['status'] {\n if (\n status === 'starting' ||\n status === 'running' ||\n status === 'stopped' ||\n status === 'error'\n ) {\n return status;\n }\n return 'unknown';\n}\n\nfunction toAgentRole(role: string): AgentSnapshot['role'] | undefined {\n if (\n role === 'plan' ||\n role === 'work' ||\n role === 'review' ||\n role === 'test' ||\n role === 'ship' ||\n role === 'flywheel' ||\n role === 'strike'\n ) {\n return role;\n }\n return undefined;\n}\n\nfunction toAgentSnapshot(state: AgentState & { tmuxActive?: boolean }): AgentSnapshot {\n return {\n id: state.id,\n issueId: state.issueId,\n workspace: state.workspace || undefined,\n runtime: state.harness || undefined,\n model: state.model || undefined,\n status: toAgentStatus(state.status),\n startedAt: state.startedAt || undefined,\n lastActivity: state.lastActivity || undefined,\n branch: state.branch || undefined,\n costSoFar: state.costSoFar,\n sessionId: state.sessionId || undefined,\n role: toAgentRole(state.role),\n phase: state.phase || undefined,\n workType: state.workType || undefined,\n roleRunHead: state.roleRunHead || undefined,\n hasLiveTmuxSession: state.tmuxActive,\n stoppedByUser: state.stoppedByUser,\n paused: state.paused,\n pausedReason: state.pausedReason,\n pausedAt: state.pausedAt,\n troubled: state.troubled,\n troubledAt: state.troubledAt,\n consecutiveFailures: state.consecutiveFailures,\n firstFailureInRunAt: state.firstFailureInRunAt,\n lastFailureAt: state.lastFailureAt,\n lastFailureReason: state.lastFailureReason,\n lastFailureNextRetryAt: state.lastFailureNextRetryAt,\n };\n}\n\nfunction toAgentRuntimeSnapshot(state: AgentState): AgentRuntimeSnapshot {\n const now = new Date().toISOString();\n const activity: AgentRuntimeSnapshot['activity'] =\n state.status === 'running' || state.status === 'starting'\n ? 'working'\n : state.status === 'stopped'\n ? 'stopped'\n : 'idle';\n\n return {\n id: state.id,\n activity,\n lastActivity: state.lastActivity || now,\n model: state.model || undefined,\n currentIssue: state.issueId,\n updatedAtSequence: 0,\n };\n}\n\nfunction toReviewStatusValue(\n value: string | undefined,\n): ReviewStatusSnapshot['reviewStatus'] {\n if (!value) return undefined;\n if (\n value === 'pending' ||\n value === 'reviewing' ||\n value === 'passed' ||\n value === 'failed' ||\n value === 'blocked'\n ) {\n return value;\n }\n return undefined;\n}\n\nfunction toTestStatusValue(\n value: string | undefined,\n): ReviewStatusSnapshot['testStatus'] {\n if (!value) return undefined;\n if (\n value === 'pending' ||\n value === 'testing' ||\n value === 'passed' ||\n value === 'failed' ||\n value === 'skipped' ||\n value === 'dispatch_failed'\n ) {\n return value;\n }\n return undefined;\n}\n\nfunction toMergeStatusValue(\n value: string | undefined,\n): ReviewStatusSnapshot['mergeStatus'] {\n if (!value) return undefined;\n if (\n value === 'pending' ||\n value === 'queued' ||\n value === 'merging' ||\n value === 'verifying' ||\n value === 'merged' ||\n value === 'failed'\n ) {\n return value;\n }\n return undefined;\n}\n\nfunction toVerificationStatusValue(\n value: string | undefined,\n): ReviewStatusSnapshot['verificationStatus'] {\n if (!value) return undefined;\n if (\n value === 'pending' ||\n value === 'running' ||\n value === 'passed' ||\n value === 'failed' ||\n value === 'skipped'\n ) {\n return value;\n }\n return undefined;\n}\n\nfunction recordToReviewStatusSnapshot(record: PanIssueRecord): ReviewStatusSnapshot {\n const pipeline = record.pipeline;\n return {\n issueId: record.issueId,\n reviewStatus: toReviewStatusValue(pipeline.reviewStatus),\n testStatus: toTestStatusValue(pipeline.testStatus),\n mergeStatus: toMergeStatusValue(pipeline.mergeStatus),\n verificationStatus: toVerificationStatusValue(pipeline.verificationStatus),\n readyForMerge: pipeline.readyForMerge ?? false,\n updatedAt: pipeline.updatedAt,\n prUrl: pipeline.prUrl,\n reviewedAtCommit: pipeline.reviewedAtCommit,\n lastVerifiedCommit: pipeline.lastVerifiedCommit,\n autoMerge: pipeline.autoMerge,\n blockerReasons: pipeline.blockerReasons as ReviewStatusSnapshot['blockerReasons'],\n mergeNotes: pipeline.mergeNotes,\n reviewRetryCount: undefined,\n testRetryCount: undefined,\n mergeRetryCount: undefined,\n };\n}\n\nfunction normalizeIssueId(value: unknown): string | null {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n return trimmed ? trimmed.toUpperCase() : null;\n}\n\nfunction isIssueClosed(issue: {\n completedAt?: unknown;\n state?: unknown;\n canonicalStatus?: unknown;\n status?: unknown;\n}): boolean {\n const state = String(issue.state ?? '').toLowerCase();\n const status = String(issue.status ?? '').toLowerCase();\n const canonicalStatus = String(issue.canonicalStatus ?? '').toLowerCase();\n if (issue.completedAt) return true;\n if (\n state === 'closed' ||\n state === 'done' ||\n state === 'completed'\n ) {\n return true;\n }\n if (\n status === 'done' ||\n status === 'closed' ||\n status === 'completed' ||\n status === 'cancelled' ||\n status === 'canceled'\n ) {\n return true;\n }\n if (\n canonicalStatus === 'done' ||\n canonicalStatus === 'closed' ||\n canonicalStatus === 'completed' ||\n canonicalStatus === 'cancelled' ||\n canonicalStatus === 'canceled'\n ) {\n return true;\n }\n return false;\n}\n\nasync function loadInFlightIssueIds(\n projects: ProjectConfig[],\n): Promise<Set<string>> {\n return enumerateInFlightIssuesFromSources(projects);\n}\n\nasync function fetchPrState(\n issueId: string,\n): Promise<{ hasPr: boolean; reviewDecision: string | null }> {\n const { fetchIssuePullRequest } = await import(\n '../overdeck/pull-requests.js'\n );\n const result = await fetchIssuePullRequest(issueId);\n if (!result.pr) return { hasPr: false, reviewDecision: null };\n return { hasPr: true, reviewDecision: result.pr.reviewDecision };\n}\n\n/**\n * Reconstruct the dashboard cache from durable sources only.\n *\n * The `_db` parameter is accepted for backward compatibility but is no longer\n * used — the cache now reads from the overdeck layer directly.\n */\nexport async function reconstructCache(\n _db?: unknown,\n opts?: ReconstructOptions,\n): Promise<ReconstructResult> {\n const verbose = opts?.verbose ?? false;\n\n // 1. Rebuild the agents table from state.json + tmux (sources-only).\n const {\n processed: agentsRebuilt,\n markedStoppedIds = [],\n } = backfillAgentsSync({\n verbose,\n listLiveSessions: opts?.listLiveSessions,\n });\n\n // 2. Build AgentSnapshot / AgentRuntimeSnapshot maps from the rebuilt table.\n let runningAgents: (AgentState & { tmuxActive: boolean })[] = [];\n try {\n runningAgents = await Effect.runPromise(listRunningAgents());\n } catch (err) {\n console.warn(\n '[reconstruct-cache] listRunningAgents failed, falling back to agents table:',\n (err as Error).message,\n );\n runningAgents = listAllAgentsSync().map((agent) => {\n const state = {\n id: agent.id,\n issueId: agent.issueId,\n workspace: agent.workspace ?? '',\n role: agent.role as AgentState['role'],\n model: agent.model ?? '',\n status: agent.status as AgentState['status'],\n startedAt: agent.startedAt ?? new Date().toISOString(),\n harness: agent.harness ? (agent.harness as AgentState['harness']) : undefined,\n lastActivity: agent.lastActivity ?? undefined,\n lastResumeAt: agent.lastResumeAt ?? undefined,\n stoppedAt: agent.stoppedAt ?? undefined,\n stoppedByUser: agent.stoppedByUser ?? undefined,\n stoppedByPause: agent.stoppedByPause ?? undefined,\n kickoffDelivered: agent.kickoffDelivered ?? undefined,\n paused: agent.paused ?? undefined,\n pausedReason: agent.pausedReason ?? undefined,\n pausedAt: agent.pausedAt ?? undefined,\n troubled: agent.troubled ?? undefined,\n troubledAt: agent.troubledAt ?? undefined,\n consecutiveFailures: agent.consecutiveFailures ?? undefined,\n firstFailureInRunAt: agent.firstFailureInRunAt ?? undefined,\n lastFailureAt: agent.lastFailureAt ?? undefined,\n lastFailureReason: agent.lastFailureReason ?? undefined,\n lastFailureNextRetryAt: agent.lastFailureNextRetryAt ?? undefined,\n branch: agent.branch ?? undefined,\n costSoFar: agent.costSoFar ?? undefined,\n sessionId: agent.sessionId ?? undefined,\n phase: agent.phase ? (agent.phase as AgentState['phase']) : undefined,\n workType: agent.workType ?? undefined,\n roleRunHead: agent.roleRunHead ?? undefined,\n channelsEnabled: agent.channelsEnabled ?? undefined,\n supervisorEnabled: agent.supervisorEnabled ?? undefined,\n deliveryMethod: agent.deliveryMethod\n ? (agent.deliveryMethod as AgentState['deliveryMethod'])\n : undefined,\n flywheelRunId: agent.flywheelRunId ?? undefined,\n reviewSubRole: agent.reviewSubRole ?? undefined,\n reviewRunId: agent.reviewRunId ?? undefined,\n reviewOutputPath: agent.reviewOutputPath ?? undefined,\n reviewSynthesisAgentId: agent.reviewSynthesisAgentId ?? undefined,\n reviewDeadlineAt: agent.reviewDeadlineAt ?? undefined,\n reviewMonitorSignaled: agent.reviewMonitorSignaled\n ? (agent.reviewMonitorSignaled as AgentState['reviewMonitorSignaled'])\n : undefined,\n reviewRetryAttempt: agent.reviewRetryAttempt ?? undefined,\n hostOverride: agent.hostOverride ?? undefined,\n inspectSubRole: agent.inspectSubRole ?? undefined,\n } satisfies AgentState;\n return { ...state, tmuxActive: false };\n });\n }\n\n const agentsById: Record<string, AgentSnapshot> = {};\n const agentRuntimeById: Record<string, AgentRuntimeSnapshot> = {};\n for (const a of runningAgents) {\n agentsById[a.id] = toAgentSnapshot(a);\n agentRuntimeById[a.id] = toAgentRuntimeSnapshot(a);\n }\n\n // 3. Enumerate in-flight issues from GitHub + workspaces.\n const projects = listProjectsSync().map(({ config }) => config);\n const inFlight = await loadInFlightIssueIds(projects);\n\n // 4. Derive phases and review-status snapshots from sources.\n const phaseCounts: Record<PipelinePhase, number> = {\n work: 0,\n review: 0,\n merge: 0,\n done: 0,\n };\n const phaseByIssueId: Record<string, PipelinePhase> = {};\n const reviewStatusByIssueId: Record<string, ReviewStatusSnapshot> = {};\n\n for (const issueId of inFlight) {\n const project = resolveProjectForIssue(issueId);\n const record = project ? await readIssueRecord(project, issueId) : null;\n const { hasPr, reviewDecision } = await fetchPrState(issueId);\n const phase = derivePipelinePhase({\n issueClosed: false,\n hasPr,\n record,\n reviewDecision,\n });\n phaseCounts[phase]++;\n phaseByIssueId[issueId] = phase;\n\n if (record) {\n reviewStatusByIssueId[issueId] = recordToReviewStatusSnapshot(record);\n }\n\n if (verbose) {\n console.log(`[reconstruct-cache] ${issueId} → ${phase}`);\n }\n }\n\n return {\n issuesEnumerated: inFlight.size,\n agentsRebuilt,\n markedStoppedIds,\n phaseCounts,\n agentRuntimeById,\n agentsById,\n reviewStatusByIssueId,\n phaseByIssueId,\n };\n}\n\nexport function reconstructCacheAuto(opts?: ReconstructOptions): Promise<ReconstructResult> {\n return reconstructCache(undefined, opts);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reconstruct-cache-XpWC9L0F.js","names":[],"sources":["../../src/lib/reconstruct/enumerate-in-flight.ts","../../src/lib/reconstruct/derive-phase.ts","../../src/lib/reconstruct/reconstruct-cache.ts"],"sourcesContent":["/** Resolver-backed source-of-truth enumerator for pipeline members (PAN-1920/PAN-1966). */\n\nimport { gatherProjectLensSignals, gatherProjectLensSignalsForProjects } from '../pipeline-membership-gather.js';\nimport { resolvePipelineMembership, type IssueLensSignals } from '../pipeline-membership.js';\nimport type { ProjectConfig } from '../projects.js';\n\nexport async function enumerateInFlightIssuesFromSources(\n projects: ProjectConfig[],\n gather: (project: ProjectConfig) => Promise<IssueLensSignals[]> = gatherProjectLensSignals,\n): Promise<Set<string>> {\n const results = await gatherProjectLensSignalsForProjects(projects, gather);\n // Degrade per project, never throw: this runs during dashboard boot\n // (reconstruct-cache), and one project's unreadable lens — a GitHub App\n // token without access, a phantom issue id in a spec — must not take the\n // whole server down (PAN-2820: boot died before the HTTP listen). A failed\n // project's issues are simply not enumerated this pass; reconstruct keeps\n // its other sources and the next pass retries.\n for (const result of results) {\n if (result.error) {\n const message = result.error instanceof Error ? result.error.message : String(result.error);\n console.error(`[enumerate-in-flight] Skipping project ${result.project.name}: lens gather failed — ${message.slice(0, 300)}`);\n }\n }\n const signals = results.flatMap((result) => result.signals ?? []);\n return new Set(\n signals\n .map(resolvePipelineMembership)\n .filter((membership) => membership.inPipeline)\n .map((membership) => membership.issueId),\n );\n}\n","/**\n * PAN-1920: derive the dashboard pipeline phase from sources of truth only.\n *\n * The phase is a function of GitHub issue state, the per-issue git-tracked\n * record, and the GitHub PR reviewDecision. It never reads the SQLite cache.\n */\n\nimport type { PanIssueRecord } from '../pan-dir/record.js';\n\nexport type PipelinePhase = 'work' | 'review' | 'merge' | 'done';\n\nexport interface DerivePhaseArgs {\n /** True when the tracker issue itself is closed/done. */\n issueClosed: boolean;\n /** True when a PR exists for the feature branch. */\n hasPr: boolean;\n /** Per-issue git-tracked record (may be null while PAN-1919 is rolling out). */\n record: PanIssueRecord | null;\n /** GitHub `reviewDecision` from `gh pr view --json reviewDecision`. */\n reviewDecision: string | null;\n}\n\n/**\n * Derive the pipeline phase from sources.\n *\n * Rules (D3/D4):\n * - issue closed on tracker → done\n * - no PR → work\n * - approved (record readyForMerge / reviewStatus passed / GitHub APPROVED) → merge\n * - PR open but not approved → review\n */\nexport function derivePipelinePhase(args: DerivePhaseArgs): PipelinePhase {\n if (args.issueClosed) return 'done';\n if (!args.hasPr) return 'work';\n\n const approved =\n args.record?.pipeline?.readyForMerge === true ||\n args.record?.pipeline?.reviewStatus === 'passed' ||\n args.reviewDecision === 'APPROVED';\n\n return approved ? 'merge' : 'review';\n}\n","/**\n * PAN-1920: reconstruct the dashboard cache from sources of truth.\n *\n * Rebuilds the agents table from state.json + tmux, enumerates in-flight\n * issues from GitHub + workspaces, derives pipeline phases from the per-issue\n * record + GitHub PR state, and produces the AgentSnapshot / AgentRuntimeSnapshot\n * maps the dashboard bootstrap paths need.\n *\n * Reads NO SQLite cache tables: no events, no projection cache, no review_status.\n */\n\nimport { Effect } from 'effect';\nimport type {\n AgentRuntimeSnapshot,\n AgentSnapshot,\n ReviewStatusSnapshot,\n} from '@overdeck/contracts';\nimport { backfillAgentsSync, listAllAgentsSync } from '../overdeck/agents.js';\nimport { listRunningAgents, type AgentState } from '../agents.js';\nimport { listProjectsSync, type ProjectConfig } from '../projects.js';\nimport {\n readIssueRecord,\n resolveProjectForIssue,\n type PanIssueRecord,\n} from '../pan-dir/record.js';\nimport { enumerateInFlightIssuesFromSources } from './enumerate-in-flight.js';\nimport { derivePipelinePhase, type PipelinePhase } from './derive-phase.js';\n\nexport interface ReconstructOptions {\n verbose?: boolean;\n /** Override tmux session discovery for tests / headless environments. */\n listLiveSessions?: () => Set<string>;\n}\n\nexport interface ReconstructResult {\n issuesEnumerated: number;\n agentsRebuilt: number;\n markedStoppedIds: Array<{ id: string; previousStatus: string }>;\n phaseCounts: Record<PipelinePhase, number>;\n agentRuntimeById: Record<string, AgentRuntimeSnapshot>;\n agentsById: Record<string, AgentSnapshot>;\n reviewStatusByIssueId: Record<string, ReviewStatusSnapshot>;\n phaseByIssueId: Record<string, PipelinePhase>;\n}\n\nfunction toAgentStatus(status: string): AgentSnapshot['status'] {\n if (\n status === 'starting' ||\n status === 'running' ||\n status === 'stopped' ||\n status === 'error'\n ) {\n return status;\n }\n return 'unknown';\n}\n\nfunction toAgentRole(role: string): AgentSnapshot['role'] | undefined {\n if (\n role === 'plan' ||\n role === 'work' ||\n role === 'review' ||\n role === 'test' ||\n role === 'ship' ||\n role === 'flywheel' ||\n role === 'strike'\n ) {\n return role;\n }\n return undefined;\n}\n\nfunction toAgentSnapshot(state: AgentState & { tmuxActive?: boolean }): AgentSnapshot {\n return {\n id: state.id,\n issueId: state.issueId,\n workspace: state.workspace || undefined,\n runtime: state.harness || undefined,\n model: state.model || undefined,\n status: toAgentStatus(state.status),\n startedAt: state.startedAt || undefined,\n lastActivity: state.lastActivity || undefined,\n branch: state.branch || undefined,\n costSoFar: state.costSoFar,\n sessionId: state.sessionId || undefined,\n role: toAgentRole(state.role),\n phase: state.phase || undefined,\n workType: state.workType || undefined,\n roleRunHead: state.roleRunHead || undefined,\n hasLiveTmuxSession: state.tmuxActive,\n stoppedByUser: state.stoppedByUser,\n paused: state.paused,\n pausedReason: state.pausedReason,\n pausedAt: state.pausedAt,\n troubled: state.troubled,\n troubledAt: state.troubledAt,\n consecutiveFailures: state.consecutiveFailures,\n firstFailureInRunAt: state.firstFailureInRunAt,\n lastFailureAt: state.lastFailureAt,\n lastFailureReason: state.lastFailureReason,\n lastFailureNextRetryAt: state.lastFailureNextRetryAt,\n };\n}\n\nfunction toAgentRuntimeSnapshot(state: AgentState): AgentRuntimeSnapshot {\n const now = new Date().toISOString();\n const activity: AgentRuntimeSnapshot['activity'] =\n state.status === 'running' || state.status === 'starting'\n ? 'working'\n : state.status === 'stopped'\n ? 'stopped'\n : 'idle';\n\n return {\n id: state.id,\n activity,\n lastActivity: state.lastActivity || now,\n model: state.model || undefined,\n currentIssue: state.issueId,\n updatedAtSequence: 0,\n };\n}\n\nfunction toReviewStatusValue(\n value: string | undefined,\n): ReviewStatusSnapshot['reviewStatus'] {\n if (!value) return undefined;\n if (\n value === 'pending' ||\n value === 'reviewing' ||\n value === 'passed' ||\n value === 'failed' ||\n value === 'blocked'\n ) {\n return value;\n }\n return undefined;\n}\n\nfunction toTestStatusValue(\n value: string | undefined,\n): ReviewStatusSnapshot['testStatus'] {\n if (!value) return undefined;\n if (\n value === 'pending' ||\n value === 'testing' ||\n value === 'passed' ||\n value === 'failed' ||\n value === 'skipped' ||\n value === 'dispatch_failed'\n ) {\n return value;\n }\n return undefined;\n}\n\nfunction toMergeStatusValue(\n value: string | undefined,\n): ReviewStatusSnapshot['mergeStatus'] {\n if (!value) return undefined;\n if (\n value === 'pending' ||\n value === 'queued' ||\n value === 'merging' ||\n value === 'verifying' ||\n value === 'merged' ||\n value === 'failed'\n ) {\n return value;\n }\n return undefined;\n}\n\nfunction toVerificationStatusValue(\n value: string | undefined,\n): ReviewStatusSnapshot['verificationStatus'] {\n if (!value) return undefined;\n if (\n value === 'pending' ||\n value === 'running' ||\n value === 'passed' ||\n value === 'failed' ||\n value === 'skipped'\n ) {\n return value;\n }\n return undefined;\n}\n\nfunction recordToReviewStatusSnapshot(record: PanIssueRecord): ReviewStatusSnapshot {\n const pipeline = record.pipeline;\n return {\n issueId: record.issueId,\n reviewStatus: toReviewStatusValue(pipeline.reviewStatus),\n testStatus: toTestStatusValue(pipeline.testStatus),\n mergeStatus: toMergeStatusValue(pipeline.mergeStatus),\n verificationStatus: toVerificationStatusValue(pipeline.verificationStatus),\n readyForMerge: pipeline.readyForMerge ?? false,\n updatedAt: pipeline.updatedAt,\n prUrl: pipeline.prUrl,\n reviewedAtCommit: pipeline.reviewedAtCommit,\n lastVerifiedCommit: pipeline.lastVerifiedCommit,\n autoMerge: pipeline.autoMerge,\n blockerReasons: pipeline.blockerReasons as ReviewStatusSnapshot['blockerReasons'],\n mergeNotes: pipeline.mergeNotes,\n reviewRetryCount: undefined,\n testRetryCount: undefined,\n mergeRetryCount: undefined,\n };\n}\n\nfunction normalizeIssueId(value: unknown): string | null {\n if (typeof value !== 'string') return null;\n const trimmed = value.trim();\n return trimmed ? trimmed.toUpperCase() : null;\n}\n\nfunction isIssueClosed(issue: {\n completedAt?: unknown;\n state?: unknown;\n canonicalStatus?: unknown;\n status?: unknown;\n}): boolean {\n const state = String(issue.state ?? '').toLowerCase();\n const status = String(issue.status ?? '').toLowerCase();\n const canonicalStatus = String(issue.canonicalStatus ?? '').toLowerCase();\n if (issue.completedAt) return true;\n if (\n state === 'closed' ||\n state === 'done' ||\n state === 'completed'\n ) {\n return true;\n }\n if (\n status === 'done' ||\n status === 'closed' ||\n status === 'completed' ||\n status === 'cancelled' ||\n status === 'canceled'\n ) {\n return true;\n }\n if (\n canonicalStatus === 'done' ||\n canonicalStatus === 'closed' ||\n canonicalStatus === 'completed' ||\n canonicalStatus === 'cancelled' ||\n canonicalStatus === 'canceled'\n ) {\n return true;\n }\n return false;\n}\n\nasync function loadInFlightIssueIds(\n projects: ProjectConfig[],\n): Promise<Set<string>> {\n return enumerateInFlightIssuesFromSources(projects);\n}\n\nasync function fetchPrState(\n issueId: string,\n): Promise<{ hasPr: boolean; reviewDecision: string | null }> {\n const { fetchIssuePullRequest } = await import(\n '../overdeck/pull-requests.js'\n );\n const result = await fetchIssuePullRequest(issueId);\n if (!result.pr) return { hasPr: false, reviewDecision: null };\n return { hasPr: true, reviewDecision: result.pr.reviewDecision };\n}\n\n/**\n * Reconstruct the dashboard cache from durable sources only.\n *\n * The `_db` parameter is accepted for backward compatibility but is no longer\n * used — the cache now reads from the overdeck layer directly.\n */\nexport async function reconstructCache(\n _db?: unknown,\n opts?: ReconstructOptions,\n): Promise<ReconstructResult> {\n const verbose = opts?.verbose ?? false;\n\n // 1. Rebuild the agents table from state.json + tmux (sources-only).\n const {\n processed: agentsRebuilt,\n markedStoppedIds = [],\n } = backfillAgentsSync({\n verbose,\n listLiveSessions: opts?.listLiveSessions,\n });\n\n // 2. Build AgentSnapshot / AgentRuntimeSnapshot maps from the rebuilt table.\n let runningAgents: (AgentState & { tmuxActive: boolean })[] = [];\n try {\n runningAgents = await Effect.runPromise(listRunningAgents());\n } catch (err) {\n console.warn(\n '[reconstruct-cache] listRunningAgents failed, falling back to agents table:',\n (err as Error).message,\n );\n runningAgents = listAllAgentsSync().map((agent) => {\n const state = {\n id: agent.id,\n issueId: agent.issueId,\n workspace: agent.workspace ?? '',\n role: agent.role as AgentState['role'],\n model: agent.model ?? '',\n status: agent.status as AgentState['status'],\n startedAt: agent.startedAt ?? new Date().toISOString(),\n harness: agent.harness ? (agent.harness as AgentState['harness']) : undefined,\n lastActivity: agent.lastActivity ?? undefined,\n lastResumeAt: agent.lastResumeAt ?? undefined,\n stoppedAt: agent.stoppedAt ?? undefined,\n stoppedByUser: agent.stoppedByUser ?? undefined,\n stoppedByPause: agent.stoppedByPause ?? undefined,\n kickoffDelivered: agent.kickoffDelivered ?? undefined,\n paused: agent.paused ?? undefined,\n pausedReason: agent.pausedReason ?? undefined,\n pausedAt: agent.pausedAt ?? undefined,\n troubled: agent.troubled ?? undefined,\n troubledAt: agent.troubledAt ?? undefined,\n consecutiveFailures: agent.consecutiveFailures ?? undefined,\n firstFailureInRunAt: agent.firstFailureInRunAt ?? undefined,\n lastFailureAt: agent.lastFailureAt ?? undefined,\n lastFailureReason: agent.lastFailureReason ?? undefined,\n lastFailureNextRetryAt: agent.lastFailureNextRetryAt ?? undefined,\n branch: agent.branch ?? undefined,\n costSoFar: agent.costSoFar ?? undefined,\n sessionId: agent.sessionId ?? undefined,\n phase: agent.phase ? (agent.phase as AgentState['phase']) : undefined,\n workType: agent.workType ?? undefined,\n roleRunHead: agent.roleRunHead ?? undefined,\n channelsEnabled: agent.channelsEnabled ?? undefined,\n supervisorEnabled: agent.supervisorEnabled ?? undefined,\n deliveryMethod: agent.deliveryMethod\n ? (agent.deliveryMethod as AgentState['deliveryMethod'])\n : undefined,\n flywheelRunId: agent.flywheelRunId ?? undefined,\n reviewSubRole: agent.reviewSubRole ?? undefined,\n reviewRunId: agent.reviewRunId ?? undefined,\n reviewOutputPath: agent.reviewOutputPath ?? undefined,\n reviewSynthesisAgentId: agent.reviewSynthesisAgentId ?? undefined,\n reviewDeadlineAt: agent.reviewDeadlineAt ?? undefined,\n reviewMonitorSignaled: agent.reviewMonitorSignaled\n ? (agent.reviewMonitorSignaled as AgentState['reviewMonitorSignaled'])\n : undefined,\n reviewRetryAttempt: agent.reviewRetryAttempt ?? undefined,\n hostOverride: agent.hostOverride ?? undefined,\n inspectSubRole: agent.inspectSubRole ?? undefined,\n } satisfies AgentState;\n return { ...state, tmuxActive: false };\n });\n }\n\n const agentsById: Record<string, AgentSnapshot> = {};\n const agentRuntimeById: Record<string, AgentRuntimeSnapshot> = {};\n for (const a of runningAgents) {\n agentsById[a.id] = toAgentSnapshot(a);\n agentRuntimeById[a.id] = toAgentRuntimeSnapshot(a);\n }\n\n // 3. Enumerate in-flight issues from GitHub + workspaces.\n const projects = listProjectsSync().map(({ config }) => config);\n const inFlight = await loadInFlightIssueIds(projects);\n\n // 4. Derive phases and review-status snapshots from sources.\n const phaseCounts: Record<PipelinePhase, number> = {\n work: 0,\n review: 0,\n merge: 0,\n done: 0,\n };\n const phaseByIssueId: Record<string, PipelinePhase> = {};\n const reviewStatusByIssueId: Record<string, ReviewStatusSnapshot> = {};\n\n for (const issueId of inFlight) {\n const project = resolveProjectForIssue(issueId);\n const record = project ? await readIssueRecord(project, issueId) : null;\n const { hasPr, reviewDecision } = await fetchPrState(issueId);\n const phase = derivePipelinePhase({\n issueClosed: false,\n hasPr,\n record,\n reviewDecision,\n });\n phaseCounts[phase]++;\n phaseByIssueId[issueId] = phase;\n\n if (record) {\n reviewStatusByIssueId[issueId] = recordToReviewStatusSnapshot(record);\n }\n\n if (verbose) {\n console.log(`[reconstruct-cache] ${issueId} → ${phase}`);\n }\n }\n\n return {\n issuesEnumerated: inFlight.size,\n agentsRebuilt,\n markedStoppedIds,\n phaseCounts,\n agentRuntimeById,\n agentsById,\n reviewStatusByIssueId,\n phaseByIssueId,\n };\n}\n\nexport function reconstructCacheAuto(opts?: ReconstructOptions): Promise<ReconstructResult> {\n return reconstructCache(undefined, opts);\n}\n"],"mappings":";;;;;;;;;;;;;AAMA,eAAA,mCAAA,UAAA,SAAA,0BAAA;;AAWE,MAAA,MAAA,UAAA,QAAA,KAAA,OAAA,OAAA;;AAGI,UAAA,MAAA,0CAAA,OAAA,QAAA,KAAA,yBAAA,QAAA,MAAA,GAAA,IAAA,GAAA;;;AAIJ,QAAA,IAAA,IAAA,QAAA,IAAA,0BAAA,CAAA,QAAA,eAAA,WAAA,WAAA,CAAA,KAAA,eAAA,WAAA,QAAA,CAAA;;;;;;;;;;;;;ACOF,SAAgB,oBAAoB,MAAsC;AACxE,KAAI,KAAK,YAAa,QAAO;AAC7B,KAAI,CAAC,KAAK,MAAO,QAAO;AAOxB,QAJE,KAAK,QAAQ,UAAU,kBAAkB,QACzC,KAAK,QAAQ,UAAU,iBAAiB,YACxC,KAAK,mBAAmB,aAER,UAAU;;;;;;;;;;;;;;ACK9B,SAAS,cAAc,QAAyC;AAC9D,KACE,WAAW,cACX,WAAW,aACX,WAAW,aACX,WAAW,QAEX,QAAO;AAET,QAAO;;AAGT,SAAS,YAAY,MAAiD;AACpE,KACE,SAAS,UACT,SAAS,UACT,SAAS,YACT,SAAS,UACT,SAAS,UACT,SAAS,cACT,SAAS,SAET,QAAO;;AAKX,SAAS,gBAAgB,OAA6D;AACpF,QAAO;EACL,IAAI,MAAM;EACV,SAAS,MAAM;EACf,WAAW,MAAM,aAAa,KAAA;EAC9B,SAAS,MAAM,WAAW,KAAA;EAC1B,OAAO,MAAM,SAAS,KAAA;EACtB,QAAQ,cAAc,MAAM,OAAO;EACnC,WAAW,MAAM,aAAa,KAAA;EAC9B,cAAc,MAAM,gBAAgB,KAAA;EACpC,QAAQ,MAAM,UAAU,KAAA;EACxB,WAAW,MAAM;EACjB,WAAW,MAAM,aAAa,KAAA;EAC9B,MAAM,YAAY,MAAM,KAAK;EAC7B,OAAO,MAAM,SAAS,KAAA;EACtB,UAAU,MAAM,YAAY,KAAA;EAC5B,aAAa,MAAM,eAAe,KAAA;EAClC,oBAAoB,MAAM;EAC1B,eAAe,MAAM;EACrB,QAAQ,MAAM;EACd,cAAc,MAAM;EACpB,UAAU,MAAM;EAChB,UAAU,MAAM;EAChB,YAAY,MAAM;EAClB,qBAAqB,MAAM;EAC3B,qBAAqB,MAAM;EAC3B,eAAe,MAAM;EACrB,mBAAmB,MAAM;EACzB,wBAAwB,MAAM;EAC/B;;AAGH,SAAS,uBAAuB,OAAyC;CACvE,MAAM,uBAAM,IAAI,MAAM,EAAC,aAAa;CACpC,MAAM,WACJ,MAAM,WAAW,aAAa,MAAM,WAAW,aAC3C,YACA,MAAM,WAAW,YACf,YACA;AAER,QAAO;EACL,IAAI,MAAM;EACV;EACA,cAAc,MAAM,gBAAgB;EACpC,OAAO,MAAM,SAAS,KAAA;EACtB,cAAc,MAAM;EACpB,mBAAmB;EACpB;;AAGH,SAAS,oBACP,OACsC;AACtC,KAAI,CAAC,MAAO,QAAO,KAAA;AACnB,KACE,UAAU,aACV,UAAU,eACV,UAAU,YACV,UAAU,YACV,UAAU,UAEV,QAAO;;AAKX,SAAS,kBACP,OACoC;AACpC,KAAI,CAAC,MAAO,QAAO,KAAA;AACnB,KACE,UAAU,aACV,UAAU,aACV,UAAU,YACV,UAAU,YACV,UAAU,aACV,UAAU,kBAEV,QAAO;;AAKX,SAAS,mBACP,OACqC;AACrC,KAAI,CAAC,MAAO,QAAO,KAAA;AACnB,KACE,UAAU,aACV,UAAU,YACV,UAAU,aACV,UAAU,eACV,UAAU,YACV,UAAU,SAEV,QAAO;;AAKX,SAAS,0BACP,OAC4C;AAC5C,KAAI,CAAC,MAAO,QAAO,KAAA;AACnB,KACE,UAAU,aACV,UAAU,aACV,UAAU,YACV,UAAU,YACV,UAAU,UAEV,QAAO;;AAKX,SAAS,6BAA6B,QAA8C;CAClF,MAAM,WAAW,OAAO;AACxB,QAAO;EACL,SAAS,OAAO;EAChB,cAAc,oBAAoB,SAAS,aAAa;EACxD,YAAY,kBAAkB,SAAS,WAAW;EAClD,aAAa,mBAAmB,SAAS,YAAY;EACrD,oBAAoB,0BAA0B,SAAS,mBAAmB;EAC1E,eAAe,SAAS,iBAAiB;EACzC,WAAW,SAAS;EACpB,OAAO,SAAS;EAChB,kBAAkB,SAAS;EAC3B,oBAAoB,SAAS;EAC7B,WAAW,SAAS;EACpB,gBAAgB,SAAS;EACzB,YAAY,SAAS;EACrB,kBAAkB,KAAA;EAClB,gBAAgB,KAAA;EAChB,iBAAiB,KAAA;EAClB;;AA+CH,eAAe,qBACb,UACsB;AACtB,QAAO,mCAAmC,SAAS;;AAGrD,eAAe,aACb,SAC4D;CAC5D,MAAM,EAAE,0BAA0B,MAAM,OACtC;CAEF,MAAM,SAAS,MAAM,sBAAsB,QAAQ;AACnD,KAAI,CAAC,OAAO,GAAI,QAAO;EAAE,OAAO;EAAO,gBAAgB;EAAM;AAC7D,QAAO;EAAE,OAAO;EAAM,gBAAgB,OAAO,GAAG;EAAgB;;;;;;;;AASlE,eAAsB,iBACpB,KACA,MAC4B;CAC5B,MAAM,UAAU,MAAM,WAAW;CAGjC,MAAM,EACJ,WAAW,eACX,mBAAmB,EAAE,KACnB,mBAAmB;EACrB;EACA,kBAAkB,MAAM;EACzB,CAAC;CAGF,IAAI,gBAA0D,EAAE;AAChE,KAAI;AACF,kBAAgB,MAAM,OAAO,WAAW,mBAAmB,CAAC;UACrD,KAAK;AACZ,UAAQ,KACN,+EACC,IAAc,QAChB;AACD,kBAAgB,mBAAmB,CAAC,KAAK,UAAU;AAkDjD,UAAO;IAhDL,IAAI,MAAM;IACV,SAAS,MAAM;IACf,WAAW,MAAM,aAAa;IAC9B,MAAM,MAAM;IACZ,OAAO,MAAM,SAAS;IACtB,QAAQ,MAAM;IACd,WAAW,MAAM,8BAAa,IAAI,MAAM,EAAC,aAAa;IACtD,SAAS,MAAM,UAAW,MAAM,UAAoC,KAAA;IACpE,cAAc,MAAM,gBAAgB,KAAA;IACpC,cAAc,MAAM,gBAAgB,KAAA;IACpC,WAAW,MAAM,aAAa,KAAA;IAC9B,eAAe,MAAM,iBAAiB,KAAA;IACtC,gBAAgB,MAAM,kBAAkB,KAAA;IACxC,kBAAkB,MAAM,oBAAoB,KAAA;IAC5C,QAAQ,MAAM,UAAU,KAAA;IACxB,cAAc,MAAM,gBAAgB,KAAA;IACpC,UAAU,MAAM,YAAY,KAAA;IAC5B,UAAU,MAAM,YAAY,KAAA;IAC5B,YAAY,MAAM,cAAc,KAAA;IAChC,qBAAqB,MAAM,uBAAuB,KAAA;IAClD,qBAAqB,MAAM,uBAAuB,KAAA;IAClD,eAAe,MAAM,iBAAiB,KAAA;IACtC,mBAAmB,MAAM,qBAAqB,KAAA;IAC9C,wBAAwB,MAAM,0BAA0B,KAAA;IACxD,QAAQ,MAAM,UAAU,KAAA;IACxB,WAAW,MAAM,aAAa,KAAA;IAC9B,WAAW,MAAM,aAAa,KAAA;IAC9B,OAAO,MAAM,QAAS,MAAM,QAAgC,KAAA;IAC5D,UAAU,MAAM,YAAY,KAAA;IAC5B,aAAa,MAAM,eAAe,KAAA;IAClC,iBAAiB,MAAM,mBAAmB,KAAA;IAC1C,mBAAmB,MAAM,qBAAqB,KAAA;IAC9C,gBAAgB,MAAM,iBACjB,MAAM,iBACP,KAAA;IACJ,eAAe,MAAM,iBAAiB,KAAA;IACtC,eAAe,MAAM,iBAAiB,KAAA;IACtC,aAAa,MAAM,eAAe,KAAA;IAClC,kBAAkB,MAAM,oBAAoB,KAAA;IAC5C,wBAAwB,MAAM,0BAA0B,KAAA;IACxD,kBAAkB,MAAM,oBAAoB,KAAA;IAC5C,uBAAuB,MAAM,wBACxB,MAAM,wBACP,KAAA;IACJ,oBAAoB,MAAM,sBAAsB,KAAA;IAChD,cAAc,MAAM,gBAAgB,KAAA;IACpC,gBAAgB,MAAM,kBAAkB,KAAA;IAEvB,YAAY;IAAO;IACtC;;CAGJ,MAAM,aAA4C,EAAE;CACpD,MAAM,mBAAyD,EAAE;AACjE,MAAK,MAAM,KAAK,eAAe;AAC7B,aAAW,EAAE,MAAM,gBAAgB,EAAE;AACrC,mBAAiB,EAAE,MAAM,uBAAuB,EAAE;;CAKpD,MAAM,WAAW,MAAM,qBADN,kBAAkB,CAAC,KAAK,EAAE,aAAa,OACJ,CAAC;CAGrD,MAAM,cAA6C;EACjD,MAAM;EACN,QAAQ;EACR,OAAO;EACP,MAAM;EACP;CACD,MAAM,iBAAgD,EAAE;CACxD,MAAM,wBAA8D,EAAE;AAEtE,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,UAAU,uBAAuB,QAAQ;EAC/C,MAAM,SAAS,UAAU,MAAM,gBAAgB,SAAS,QAAQ,GAAG;EACnE,MAAM,EAAE,OAAO,mBAAmB,MAAM,aAAa,QAAQ;EAC7D,MAAM,QAAQ,oBAAoB;GAChC,aAAa;GACb;GACA;GACA;GACD,CAAC;AACF,cAAY;AACZ,iBAAe,WAAW;AAE1B,MAAI,OACF,uBAAsB,WAAW,6BAA6B,OAAO;AAGvE,MAAI,QACF,SAAQ,IAAI,uBAAuB,QAAQ,KAAK,QAAQ;;AAI5D,QAAO;EACL,kBAAkB,SAAS;EAC3B;EACA;EACA;EACA;EACA;EACA;EACA;EACD;;AAGH,SAAgB,qBAAqB,MAAuD;AAC1F,QAAO,iBAAiB,KAAA,GAAW,KAAK"}
@@ -1,6 +1,6 @@
1
1
  import { $t as findProjectByPathSync, fn as init_projects, on as getProjectSync } from "./config-yaml-Dce9VUd6.js";
2
2
  import { i as init_merge_set, r as getMergeSetSync } from "./merge-set-DRPWriK9.js";
3
- import { p as init_review_status, w as setReviewStatusSync } from "./review-status-BegAr86G.js";
3
+ import { p as init_review_status, w as setReviewStatusSync } from "./review-status-yY5qt7Rl.js";
4
4
  import { n as upsertReleaseSetSync, r as withComponentStateSync } from "./release-set-B_sH_Mud.js";
5
5
  import { exec } from "node:child_process";
6
6
  import { promisify } from "node:util";
@@ -296,4 +296,4 @@ function appendNote(existing, note) {
296
296
  //#endregion
297
297
  export { runRelease };
298
298
 
299
- //# sourceMappingURL=release-engine-78iCajIc.js.map
299
+ //# sourceMappingURL=release-engine-BuzxOamf.js.map