@overdeck/core 0.45.10 → 0.45.11
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.
- package/dist/{agent-state-KVbJEG5f.js → agent-state-RLhS7ckO.js} +1 -1
- package/dist/{agents-pp8FO7Uj.js → agents-By3u_fQ8.js} +1 -1
- package/dist/{agents-CQHK0CSA.js → agents-CgVr77Qq.js} +12 -12
- package/dist/{agents-CQHK0CSA.js.map → agents-CgVr77Qq.js.map} +1 -1
- package/dist/{backlog-auto-trigger-CyuoS0nl.js → backlog-auto-trigger-B_-1tk4e.js} +5 -5
- package/dist/{backlog-auto-trigger-CyuoS0nl.js.map → backlog-auto-trigger-B_-1tk4e.js.map} +1 -1
- package/dist/cli/index.js +45 -45
- package/dist/{cli-Cewlu3yU.js → cli-DGeSW85B.js} +2 -2
- package/dist/{cli-Cewlu3yU.js.map → cli-DGeSW85B.js.map} +1 -1
- package/dist/cli-MTB9IOoR.js +3 -0
- package/dist/{close-issue-BJGX9_z7.js → close-issue-Djajb6Sv.js} +2 -2
- package/dist/{close-issue-BJGX9_z7.js.map → close-issue-Djajb6Sv.js.map} +1 -1
- package/dist/{concurrency-Ca-fC7tA.js → concurrency-4rzjHAQA.js} +1 -1
- package/dist/{conversation-compaction-B_6s9EvM.js → conversation-compaction-BQvZh09I.js} +4 -4
- package/dist/{conversation-compaction-B_6s9EvM.js.map → conversation-compaction-BQvZh09I.js.map} +1 -1
- package/dist/dashboard/{advancing-selfheal-CMlGznKv.js → advancing-selfheal-BezNfM3P.js} +3 -3
- package/dist/dashboard/{advancing-selfheal-CMlGznKv.js.map → advancing-selfheal-BezNfM3P.js.map} +1 -1
- package/dist/dashboard/{agent-state-xk_G8t90.js → agent-state-BUANcaA7.js} +1 -1
- package/dist/dashboard/{agent-state-service-C1iUF9Ft.js → agent-state-service-DmFpIMbP.js} +1 -1
- package/dist/dashboard/{agent-state-service-5ge9xxhx.js → agent-state-service-tXUdp6UF.js} +2 -2
- package/dist/dashboard/{agent-state-service-5ge9xxhx.js.map → agent-state-service-tXUdp6UF.js.map} +1 -1
- package/dist/dashboard/{agents-BIggLvi_.js → agents-C-vqmKDi.js} +1 -1
- package/dist/dashboard/{agents-DHvsjsuP.js → agents-C7V4odQ6.js} +11 -11
- package/dist/dashboard/{agents-DHvsjsuP.js.map → agents-C7V4odQ6.js.map} +1 -1
- package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js → backlog-auto-trigger-Cb8PR-5w.js} +2 -2
- package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js.map → backlog-auto-trigger-Cb8PR-5w.js.map} +1 -1
- package/dist/dashboard/{cli-CvK9xuSS.js → cli-BgzbB6h3.js} +2 -2
- package/dist/dashboard/{cli-CvK9xuSS.js.map → cli-BgzbB6h3.js.map} +1 -1
- package/dist/dashboard/cli-DCqy_wFa.js +3 -0
- package/dist/dashboard/{close-issue-BnpErnD1.js → close-issue-BWf3pxn4.js} +2 -2
- package/dist/dashboard/{close-issue-BnpErnD1.js.map → close-issue-BWf3pxn4.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js → closed-issue-reaper-D7XPq5pY.js} +4 -4
- package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js.map → closed-issue-reaper-D7XPq5pY.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-BLCQwTpF.js → closed-issue-reaper-VSTSTROK.js} +1 -1
- package/dist/dashboard/{conflict-gate-JdFuhKuF.js → conflict-gate-DwOslTij.js} +1 -1
- package/dist/dashboard/{conflict-gate-BhKvrPxn.js → conflict-gate-FFEvTDVA.js} +3 -3
- package/dist/dashboard/{conflict-gate-BhKvrPxn.js.map → conflict-gate-FFEvTDVA.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-C5o_eKEm.js → conversation-compaction-CCqN4hWk.js} +4 -4
- package/dist/dashboard/{conversation-compaction-C5o_eKEm.js.map → conversation-compaction-CCqN4hWk.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-Ba5aZdrb.js → conversation-compaction-CL5Ti4j4.js} +1 -1
- package/dist/dashboard/{deacon-j3qSZ7--.js → deacon-CGHl90eC.js} +107 -50
- package/dist/dashboard/{deacon-j3qSZ7--.js.map → deacon-CGHl90eC.js.map} +1 -1
- package/dist/dashboard/{deacon-Cv2RMfHs.js → deacon-DMPh40Im.js} +2 -2
- package/dist/dashboard/deacon-swarm-CVTiNL0N.js +2 -0
- package/dist/dashboard/{deacon-swarm-DadWNi5J.js → deacon-swarm-aMMJTq6t.js} +4 -4
- package/dist/dashboard/{deacon-swarm-DadWNi5J.js.map → deacon-swarm-aMMJTq6t.js.map} +1 -1
- package/dist/dashboard/deacon.js +4 -4
- package/dist/dashboard/{feedback-target-oE4-Esxl.js → feedback-target-dJjFfITP.js} +4 -4
- package/dist/dashboard/{feedback-target-oE4-Esxl.js.map → feedback-target-dJjFfITP.js.map} +1 -1
- package/dist/dashboard/{feedback-target-C4DaDSZ8.js → feedback-target-nQSM6ygL.js} +1 -1
- package/dist/dashboard/{flywheel-ClMjGW_u.js → flywheel-BLoVNtJV.js} +6 -6
- package/dist/dashboard/{flywheel-ClMjGW_u.js.map → flywheel-BLoVNtJV.js.map} +1 -1
- package/dist/dashboard/{flywheel-DefHRzY4.js → flywheel-Cqf8vu7N.js} +6 -6
- package/dist/dashboard/{flywheel-DefHRzY4.js.map → flywheel-Cqf8vu7N.js.map} +1 -1
- package/dist/dashboard/{flywheel-BqDdGvrQ.js → flywheel-PJsbrXNY.js} +1 -1
- package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js → flywheel-merge-order-B7BH8mZ3.js} +2 -2
- package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js.map → flywheel-merge-order-B7BH8mZ3.js.map} +1 -1
- package/dist/dashboard/{flywheel-merge-order-B6YDSElh.js → flywheel-merge-order-CCwF60b-.js} +1 -1
- package/dist/dashboard/{harness-resolve-DKeSVsJL.js → harness-resolve-B3f9G1Kp.js} +2 -2
- package/dist/dashboard/{harness-resolve-DKeSVsJL.js.map → harness-resolve-B3f9G1Kp.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-BKKc0XnB.js → inspect-agent-CKF5-SJr.js} +3 -3
- package/dist/dashboard/{inspect-agent-BKKc0XnB.js.map → inspect-agent-CKF5-SJr.js.map} +1 -1
- package/dist/dashboard/inspect-agent-CqpEWGWy.js +2 -0
- package/dist/dashboard/{issue-lifecycle-DnhQgddB.js → issue-lifecycle-5LbOcATP.js} +7 -7
- package/dist/dashboard/{issue-lifecycle-DnhQgddB.js.map → issue-lifecycle-5LbOcATP.js.map} +1 -1
- package/dist/dashboard/{issue-lifecycle-nY4esZn8.js → issue-lifecycle-CjYqQ669.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-Bnqv1uaa.js → issue-service-singleton-B8iu_TKV.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-_AquSBTl.js → issue-service-singleton-CDGt__oQ.js} +2 -2
- package/dist/dashboard/{issue-service-singleton-_AquSBTl.js.map → issue-service-singleton-CDGt__oQ.js.map} +1 -1
- package/dist/dashboard/{lifecycle-CGKm7doE.js → lifecycle-CEg4sHZH.js} +4 -4
- package/dist/dashboard/{lifecycle-CGKm7doE.js.map → lifecycle-CEg4sHZH.js.map} +1 -1
- package/dist/dashboard/{merge-agent-Dh8Y9PDO.js → merge-agent-7nBiCytp.js} +1 -1
- package/dist/dashboard/{merge-agent-BcgAYStl.js → merge-agent-C81xPIGd.js} +6 -6
- package/dist/dashboard/{merge-agent-BcgAYStl.js.map → merge-agent-C81xPIGd.js.map} +1 -1
- package/dist/dashboard/merge-ops-DE_6CnmZ.js +2 -0
- package/dist/dashboard/{merge-train-DmxlYbEo.js → merge-train-D3qzRccL.js} +2 -2
- package/dist/dashboard/{merge-train-DmxlYbEo.js.map → merge-train-D3qzRccL.js.map} +1 -1
- package/dist/dashboard/{merge-train-deps-DBHsOWt9.js → merge-train-deps-Be5ElC_P.js} +2 -2
- package/dist/dashboard/{merge-train-deps-DBHsOWt9.js.map → merge-train-deps-Be5ElC_P.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js → orphan-proposed-reconciler-Boq_XaRe.js} +3 -3
- package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js.map → orphan-proposed-reconciler-Boq_XaRe.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-mXV--tcB.js → orphan-proposed-reconciler-C0sPnDq_.js} +1 -1
- package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js → reconstruct-cache-C2pCltOq.js} +3 -3
- package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js.map → reconstruct-cache-C2pCltOq.js.map} +1 -1
- package/dist/dashboard/{remote-completion-Bklh6loJ.js → remote-completion-BmxD5UMQ.js} +3 -3
- package/dist/dashboard/{remote-completion-Bklh6loJ.js.map → remote-completion-BmxD5UMQ.js.map} +1 -1
- package/dist/dashboard/resume-C-nxu9Y4.js +3 -0
- package/dist/dashboard/{review-agent-phzqNqIs.js → review-agent-D6P6d3pz.js} +26 -17
- package/dist/dashboard/review-agent-D6P6d3pz.js.map +1 -0
- package/dist/dashboard/review-agent-DTINEffB.js +3 -0
- package/dist/dashboard/{review-context-CCq56O_Y.js → review-context-Dyo0tsBa.js} +2 -2
- package/dist/dashboard/{review-context-CCq56O_Y.js.map → review-context-Dyo0tsBa.js.map} +1 -1
- package/dist/dashboard/{review-status-BQjqQXZG.js → review-status-B5ZASOo4.js} +18 -5
- package/dist/dashboard/review-status-B5ZASOo4.js.map +1 -0
- package/dist/dashboard/{review-status-bBkqUj5i.js → review-status-h7BXIA4z.js} +1 -1
- package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js → review-verdict-feedback-B70VbfRL.js} +4 -4
- package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js.map → review-verdict-feedback-B70VbfRL.js.map} +1 -1
- package/dist/dashboard/{review-verdict-feedback-cZh9Wi9G.js → review-verdict-feedback-CorgH49f.js} +1 -1
- package/dist/dashboard/{sequence-io-B2GmilSI.js → sequence-io-6YP8fVpH.js} +2 -2
- package/dist/dashboard/{sequence-io-B2GmilSI.js.map → sequence-io-6YP8fVpH.js.map} +1 -1
- package/dist/dashboard/{sequencer-agent-D8pW2aU_.js → sequencer-agent-ja-nkEc1.js} +5 -5
- package/dist/dashboard/{sequencer-agent-D8pW2aU_.js.map → sequencer-agent-ja-nkEc1.js.map} +1 -1
- package/dist/dashboard/server.js +66 -55
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{service-CILd-uOt.js → service-HKDrIy0h.js} +24 -24
- package/dist/dashboard/{service-CILd-uOt.js.map → service-HKDrIy0h.js.map} +1 -1
- package/dist/dashboard/{smart-compaction-Bgp_gwcA.js → smart-compaction-L0LSnF0_.js} +1 -1
- package/dist/dashboard/{smart-compaction-CrP90OFE.js → smart-compaction-uke1npUz.js} +2 -2
- package/dist/dashboard/{smart-compaction-CrP90OFE.js.map → smart-compaction-uke1npUz.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-D15KbPOy.js → spawn-planning-session-CSMWco8B.js} +1 -1
- package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js → spawn-planning-session-CXc0NkwV.js} +3 -3
- package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js.map → spawn-planning-session-CXc0NkwV.js.map} +1 -1
- package/dist/dashboard/{specialist-context-CwKkcXL9.js → specialist-context-BGCJ24LH.js} +3 -3
- package/dist/dashboard/{specialist-context-CwKkcXL9.js.map → specialist-context-BGCJ24LH.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-CvDxquV_.js → specialist-logs--Uz-r8ia.js} +2 -2
- package/dist/dashboard/{specialist-logs-CvDxquV_.js.map → specialist-logs--Uz-r8ia.js.map} +1 -1
- package/dist/dashboard/{specialists-BCjcG4uE.js → specialists-Ca9XSBCS.js} +1 -1
- package/dist/dashboard/{specialists-D07ybnam.js → specialists-DEfrAbWs.js} +36 -36
- package/dist/dashboard/{specialists-D07ybnam.js.map → specialists-DEfrAbWs.js.map} +1 -1
- package/dist/dashboard/{specialists-F2uMO4QS.js → specialists-DeZx9xom.js} +1 -1
- package/dist/dashboard/{specialists-BYi0k8CZ.js → specialists-nZ0mRwpZ.js} +12 -12
- package/dist/dashboard/{specialists-BYi0k8CZ.js.map → specialists-nZ0mRwpZ.js.map} +1 -1
- package/dist/dashboard/{summary-fork-DedqguZb.js → summary-fork-2a1L2DIU.js} +2 -2
- package/dist/dashboard/{summary-fork-EjvOkEPl.js → summary-fork-Xn0QUNmb.js} +3 -3
- package/dist/dashboard/{summary-fork-EjvOkEPl.js.map → summary-fork-Xn0QUNmb.js.map} +1 -1
- package/dist/dashboard/{teardown-workspace-5LCVbjfq.js → teardown-workspace-DhA6oAIK.js} +2 -2
- package/dist/dashboard/{teardown-workspace-5LCVbjfq.js.map → teardown-workspace-DhA6oAIK.js.map} +1 -1
- package/dist/dashboard/{test-agent-queue-qfsq6uaC.js → test-agent-queue-B_ukPyIK.js} +3 -3
- package/dist/dashboard/{test-agent-queue-qfsq6uaC.js.map → test-agent-queue-B_ukPyIK.js.map} +1 -1
- package/dist/dashboard/{uat-train-CtNGgXxj.js → uat-train-BLBAbJ8Y.js} +10 -10
- package/dist/dashboard/{uat-train-CtNGgXxj.js.map → uat-train-BLBAbJ8Y.js.map} +1 -1
- package/dist/dashboard/{uat-train-C3hWXtip.js → uat-train-DPfGR9_I.js} +1 -1
- package/dist/dashboard/{verification-runner-DuZRHjPU.js → verification-runner-KTGTNugM.js} +1 -1
- package/dist/dashboard/{verification-runner-BPAhPWR7.js → verification-runner-R2-4XgMO.js} +4 -4
- package/dist/dashboard/{verification-runner-BPAhPWR7.js.map → verification-runner-R2-4XgMO.js.map} +1 -1
- package/dist/dashboard/{workflows-H-COJ84X.js → workflows-CSKX5MhC.js} +7 -7
- package/dist/dashboard/{workflows-H-COJ84X.js.map → workflows-CSKX5MhC.js.map} +1 -1
- package/dist/dashboard/workflows-DVwXauef.js +2 -0
- package/dist/dashboard/{workspaces-CRU9_vRn.js → workspaces-DXLDgXo2.js} +32 -32
- package/dist/dashboard/{workspaces-CRU9_vRn.js.map → workspaces-DXLDgXo2.js.map} +1 -1
- package/dist/docs-index.sqlite +0 -0
- package/dist/{feedback-target-DTGAhFkM.js → feedback-target-BsKN4Tr_.js} +4 -4
- package/dist/{feedback-target-DTGAhFkM.js.map → feedback-target-BsKN4Tr_.js.map} +1 -1
- package/dist/{feedback-target-DutF088V.js → feedback-target-DM1WzOKy.js} +1 -1
- package/dist/{harness-resolve-Bz1prk6B.js → harness-resolve-B-YDNwNW.js} +2 -2
- package/dist/{harness-resolve-Bz1prk6B.js.map → harness-resolve-B-YDNwNW.js.map} +1 -1
- package/dist/{issue-lifecycle-fVqvCdKM.js → issue-lifecycle-Bc1l3U9f.js} +7 -7
- package/dist/{issue-lifecycle-fVqvCdKM.js.map → issue-lifecycle-Bc1l3U9f.js.map} +1 -1
- package/dist/{issue-service-singleton-CZm8p6Rj.js → issue-service-singleton-D-8Q6tdT.js} +1 -1
- package/dist/{issue-service-singleton-Cr73JAaR.js → issue-service-singleton-D1433I7n.js} +2 -2
- package/dist/{issue-service-singleton-Cr73JAaR.js.map → issue-service-singleton-D1433I7n.js.map} +1 -1
- package/dist/{lifecycle-CR6tZfl1.js → lifecycle-SmrqysDf.js} +7 -7
- package/dist/{lifecycle-CR6tZfl1.js.map → lifecycle-SmrqysDf.js.map} +1 -1
- package/dist/{lifecycle-D_EWiUjk.js → lifecycle-mRMyiIXs.js} +2 -2
- package/dist/{merge-agent-LM4avOtA.js → merge-agent-B9hIZ6Z0.js} +6 -6
- package/dist/{merge-agent-LM4avOtA.js.map → merge-agent-B9hIZ6Z0.js.map} +1 -1
- package/dist/{merge-agent-Bj046HE1.js → merge-agent-DUj22cXL.js} +1 -1
- package/dist/{merge-fulfillment-DrnC1Kmg.js → merge-fulfillment-__P4WE-T.js} +3 -3
- package/dist/{merge-fulfillment-DrnC1Kmg.js.map → merge-fulfillment-__P4WE-T.js.map} +1 -1
- package/dist/{reconstruct-cache-NPqoAUg-.js → reconstruct-cache-9SWp82SQ.js} +3 -3
- package/dist/{reconstruct-cache-NPqoAUg-.js.map → reconstruct-cache-9SWp82SQ.js.map} +1 -1
- package/dist/{records-BIkN1LQ4.js → records-CTmvuRzz.js} +14 -1
- package/dist/{records-BIkN1LQ4.js.map → records-CTmvuRzz.js.map} +1 -1
- package/dist/records-backfill-BQcmTmwF.js +2 -0
- package/dist/{records-backfill-HM0Pln6N.js → records-backfill-BrNsv_zT.js} +2 -2
- package/dist/{records-backfill-HM0Pln6N.js.map → records-backfill-BrNsv_zT.js.map} +1 -1
- package/dist/resume-Bn4kDk9x.js +3 -0
- package/dist/{review-agent-DFosQzCG.js → review-agent-PhqOhpx3.js} +24 -16
- package/dist/{review-agent-DFosQzCG.js.map → review-agent-PhqOhpx3.js.map} +1 -1
- package/dist/review-agent-hLbRRxZF.js +3 -0
- package/dist/{review-status-iJwfEoNX.js → review-status-B_XXomV0.js} +6 -6
- package/dist/{review-status-iJwfEoNX.js.map → review-status-B_XXomV0.js.map} +1 -1
- package/dist/{review-status-BbNVlXh1.js → review-status-Bo-EFVKh.js} +1 -1
- package/dist/{review-verdict-feedback-BGPw3z1X.js → review-verdict-feedback-CXHkoiKZ.js} +1 -1
- package/dist/{review-verdict-feedback-CCZ05atV.js → review-verdict-feedback-DGV13LDb.js} +4 -4
- package/dist/{review-verdict-feedback-CCZ05atV.js.map → review-verdict-feedback-DGV13LDb.js.map} +1 -1
- package/dist/{sequence-io-CcxZNHcD.js → sequence-io-Cg4NHAco.js} +1 -1
- package/dist/{sequence-io-Cx1usxG3.js → sequence-io-CyxvNHfA.js} +2 -2
- package/dist/{sequence-io-Cx1usxG3.js.map → sequence-io-CyxvNHfA.js.map} +1 -1
- package/dist/{smart-compaction-ByUEgQkF.js → smart-compaction-ByKXTQNy.js} +2 -2
- package/dist/{smart-compaction-ByUEgQkF.js.map → smart-compaction-ByKXTQNy.js.map} +1 -1
- package/dist/{smart-compaction-DfLe_CtH.js → smart-compaction-U6e_uB1E.js} +1 -1
- package/dist/{specialist-context-McuGkaeR.js → specialist-context-BUTadO-U.js} +3 -3
- package/dist/{specialist-context-McuGkaeR.js.map → specialist-context-BUTadO-U.js.map} +1 -1
- package/dist/{specialist-logs-CUUJ4eiB.js → specialist-logs-pEMCTtGZ.js} +2 -2
- package/dist/{specialist-logs-CUUJ4eiB.js.map → specialist-logs-pEMCTtGZ.js.map} +1 -1
- package/dist/{specialists-BSrylD1S.js → specialists-BGFnrQ8n.js} +1 -1
- package/dist/{specialists-B0elgInc.js → specialists-DeTkkp8b.js} +12 -12
- package/dist/{specialists-B0elgInc.js.map → specialists-DeTkkp8b.js.map} +1 -1
- package/dist/{summary-fork-DskMRnyb.js → summary-fork-CgzThE2-.js} +2 -2
- package/dist/{summary-fork-CPUTKcAT.js → summary-fork-Dx1RMuvU.js} +3 -3
- package/dist/{summary-fork-CPUTKcAT.js.map → summary-fork-Dx1RMuvU.js.map} +1 -1
- package/dist/{sync-BiNVQHBf.js → sync-Bsww9tuj.js} +2 -2
- package/dist/{sync-BiNVQHBf.js.map → sync-Bsww9tuj.js.map} +1 -1
- package/dist/sync-CylE_SjB.js +2 -0
- package/dist/{verdict-restore-DQiV7_fe.js → verdict-restore-DGpX0ZOJ.js} +5 -5
- package/dist/{verdict-restore-DQiV7_fe.js.map → verdict-restore-DGpX0ZOJ.js.map} +1 -1
- package/dist/{webhook-handlers-DHBSEDEm.js → webhook-handlers-BjsfIL7u.js} +3 -3
- package/dist/{webhook-handlers-DHBSEDEm.js.map → webhook-handlers-BjsfIL7u.js.map} +1 -1
- package/dist/{workspace-migrate-duUsWNpd.js → workspace-migrate-BBQ8nAnP.js} +2 -2
- package/dist/{workspace-migrate-duUsWNpd.js.map → workspace-migrate-BBQ8nAnP.js.map} +1 -1
- package/dist/{workspace-reap-DLCIoJmn.js → workspace-reap-12O9354k.js} +2 -2
- package/dist/{workspace-reap-DLCIoJmn.js.map → workspace-reap-12O9354k.js.map} +1 -1
- package/package.json +1 -1
- package/scripts/file-size-baseline.txt +1 -1
- package/dist/cli-DBWG12XK.js +0 -3
- package/dist/dashboard/cli-CJDWXE_Y.js +0 -3
- package/dist/dashboard/deacon-swarm-D1iAUX-P.js +0 -2
- package/dist/dashboard/inspect-agent-vm88mLYr.js +0 -2
- package/dist/dashboard/merge-ops-QdBiQ8XB.js +0 -2
- package/dist/dashboard/resume-DnIsmIgC.js +0 -3
- package/dist/dashboard/review-agent-DgySDb-G.js +0 -3
- package/dist/dashboard/review-agent-phzqNqIs.js.map +0 -1
- package/dist/dashboard/review-status-BQjqQXZG.js.map +0 -1
- package/dist/dashboard/workflows-Cz8OtiB4.js +0 -2
- package/dist/records-backfill-DEgtRyKL.js +0 -2
- package/dist/resume-DGymuXZM.js +0 -3
- package/dist/review-agent-DjVadURa.js +0 -3
- package/dist/sync-4jqukkxz.js +0 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Gn as resolveProjectFromIssueSync, In as listProjects, Nn as init_projects } from "./config-yaml-DPAieL_G.js";
|
|
2
2
|
import { a as init_activity_logger, n as emitActivityEntrySync } from "./activity-logger-pcbf-e30.js";
|
|
3
3
|
import { O as listSessionNames, x as init_tmux } from "./tmux-ePNhMIMU.js";
|
|
4
|
-
import { c as init_review_status, s as getReviewStatusSync } from "./review-status-
|
|
5
|
-
import { Mn as getAgentState, _t as init_config, t as init_agents, vt as loadCloisterConfig } from "./agents-
|
|
4
|
+
import { c as init_review_status, s as getReviewStatusSync } from "./review-status-B5ZASOo4.js";
|
|
5
|
+
import { Mn as getAgentState, _t as init_config, t as init_agents, vt as loadCloisterConfig } from "./agents-C7V4odQ6.js";
|
|
6
6
|
import { i as resolveGitHubIssueSync, n as init_tracker_utils } from "./tracker-utils-bpWs7Xyx.js";
|
|
7
7
|
import { d as readPlanSync, i as findPlanSync, o as init_io } from "./io-Wy8YpFeP.js";
|
|
8
8
|
import { _ as listPullRequestsForHead, f as init_github_app, p as isGitHubAppConfigured } from "./github-app-FMXgo3vK.js";
|
|
@@ -506,4 +506,4 @@ async function reconcileOrphanProposedSpecs(options = {}) {
|
|
|
506
506
|
//#endregion
|
|
507
507
|
export { spawnWorkAgentThroughAgentsEndpoint as a, reconcileOrphanProposedSpecs as i, handleOrphanProposedSpec as n, triggerRebuildAndStart as o, hasReviewPipelinePresence as r, findOrphanProposedSpecsForReconciler as t };
|
|
508
508
|
|
|
509
|
-
//# sourceMappingURL=orphan-proposed-reconciler-
|
|
509
|
+
//# sourceMappingURL=orphan-proposed-reconciler-Boq_XaRe.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"orphan-proposed-reconciler-Db8tUhce.js","names":[],"sources":["../../src/lib/cloister/orphan-proposed-reconciler.ts"],"sourcesContent":["import { exec } from 'child_process';\nimport { existsSync } from 'fs';\nimport { readdir, readFile } from 'fs/promises';\nimport { readFileSync, readdirSync } from 'fs';\nimport { isAbsolute, join, resolve } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\n\nimport { getAgentState, type AgentState } from '../agents.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { listProjects, resolveProjectFromIssueSync, type ProjectConfig } from '../projects.js';\nimport { getReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { listSessionNames } from '../tmux.js';\nimport { findPlanSync, readPlanSync } from '../vbrief/io.js';\nimport type { VBriefDocument } from '../vbrief/types.js';\nimport { isGitHubAppConfigured, listPullRequestsForHead } from '../github-app.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\nimport { loadCloisterConfig } from './config.js';\nimport { clearIssueClosedCache, isIssueClosed } from './issue-closed.js';\n\nconst DEFAULT_ATTEMPT_INTERVAL_MS = 5 * 60 * 1000;\nconst execAsync = promisify(exec);\nconst attemptCooldowns = new Map<string, number>();\nconst terminalClosedIssueIds = new Set<string>();\nlet scanInFlight = false;\n\nexport interface OrphanProposedReconcilerConfig {\n enabled?: boolean;\n minAttemptIntervalMs?: number;\n}\n\ntype ProjectEntry = { key: string; config: Pick<ProjectConfig, 'name' | 'path'> };\n\ntype ProposedSpecState = {\n plan?: {\n id?: unknown;\n status?: unknown;\n items?: unknown;\n };\n};\n\nexport type ReviewPipelinePresenceStatus = Pick<ReviewStatus,\n 'reviewStatus' | 'testStatus' | 'mergeStatus' | 'readyForMerge' | 'prNumber' | 'prUrl'\n>;\n\nexport function hasReviewPipelinePresence(status: ReviewPipelinePresenceStatus | null): boolean {\n if (!status) return false;\n if (status.reviewStatus !== 'pending') return true;\n if (status.testStatus !== 'pending') return true;\n if (status.mergeStatus && status.mergeStatus !== 'pending') return true;\n if (status.readyForMerge === true) return true;\n if (status.prNumber != null) return true;\n if (typeof status.prUrl === 'string' && status.prUrl.trim().length > 0) return true;\n return false;\n}\n\nexport interface OrphanProposedCandidate {\n projectKey: string;\n projectName: string;\n projectPath: string;\n issueId: string;\n specPath: string;\n beadCount: number;\n planItemCount: number;\n}\n\nexport interface FindOrphanProposedOptions {\n projects?: ProjectEntry[];\n tmuxSessionNames?: readonly string[];\n getAgentStateForIssue?: (agentId: string) => Promise<Pick<AgentState, 'status' | 'paused' | 'troubled'> | null>;\n getReviewStatusForIssue?: (issueId: string) => ReviewPipelinePresenceStatus | null;\n closedIssueIds?: Set<string>;\n}\n\nexport interface SpawnWorkAgentResult {\n spawned: boolean;\n agentId?: string;\n skippedReason?: string;\n error?: string;\n}\n\nexport interface ReconcileOrphanProposedOptions extends FindOrphanProposedOptions {\n now?: Date;\n config?: OrphanProposedReconcilerConfig;\n spawnWorkAgent?: (issueId: string) => Promise<SpawnWorkAgentResult>;\n hasOpenPrForBranch?: (projectPath: string, issueId: string) => Promise<boolean>;\n dashboardOrigin?: string;\n}\n\nexport interface HandleOrphanProposedSpecOptions {\n spawnWorkAgent?: (issueId: string) => Promise<SpawnWorkAgentResult>;\n hasOpenPrForBranch?: (projectPath: string, issueId: string) => Promise<boolean>;\n dashboardOrigin?: string;\n config?: OrphanProposedReconcilerConfig;\n /** Project override for tests / callers that already resolved the issue. */\n project?: { projectKey: string; projectPath: string };\n /** Spec override so the safety net does not re-read the file. */\n spec?: { path: string; doc: VBriefDocument };\n}\n\nasync function findSpecPathForIssue(projectPath: string, issueId: string): Promise<string | null> {\n const specsDir = join(projectPath, '.pan', 'specs');\n if (!existsSync(specsDir)) return null;\n let filenames: string[];\n try {\n filenames = readdirSync(specsDir);\n } catch {\n return null;\n }\n for (const filename of filenames) {\n if (!filename.endsWith('.vbrief.json')) continue;\n const specPath = join(specsDir, filename);\n try {\n const doc = readPlanSync(specPath);\n if (normalizeIssueId(doc.plan?.id) === issueId.toUpperCase()) {\n return specPath;\n }\n } catch {\n // ignore unreadable specs\n }\n }\n return null;\n}\n\nfunction normalizeIssueId(value: unknown): string | null {\n return typeof value === 'string' && /^[A-Za-z]+-\\d+$/.test(value.trim())\n ? value.trim().toUpperCase()\n : null;\n}\n\nasync function readJsonFile<T>(path: string): Promise<T | null> {\n try {\n return JSON.parse(await readFile(path, 'utf-8')) as T;\n } catch {\n return null;\n }\n}\n\nasync function resolveBeadsIssuesPath(projectPath: string, issueId: string): Promise<string | null> {\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n const beadsDir = join(workspacePath, '.beads');\n const localIssuesPath = join(beadsDir, 'issues.jsonl');\n if (existsSync(localIssuesPath)) return localIssuesPath;\n\n const redirectPath = join(beadsDir, 'redirect');\n if (!existsSync(redirectPath)) return null;\n\n try {\n const redirected = (await readFile(redirectPath, 'utf-8')).trim();\n if (!redirected) return null;\n return join(isAbsolute(redirected) ? redirected : resolve(workspacePath, redirected), 'issues.jsonl');\n } catch {\n return null;\n }\n}\n\nasync function countBeadsForIssue(projectPath: string, issueId: string): Promise<number> {\n const beadsPath = await resolveBeadsIssuesPath(projectPath, issueId);\n if (!beadsPath || !existsSync(beadsPath)) return 0;\n\n try {\n const raw = await readFile(beadsPath, 'utf-8');\n return raw.split('\\n').filter(Boolean).filter((line) => {\n try {\n const record = JSON.parse(line) as { _type?: unknown; labels?: unknown };\n return record._type === 'issue'\n && Array.isArray(record.labels)\n && record.labels.some((label) => typeof label === 'string' && label.toLowerCase() === issueId.toLowerCase());\n } catch {\n return false;\n }\n }).length;\n } catch {\n return 0;\n }\n}\n\nasync function defaultGetAgentState(agentId: string): Promise<Pick<AgentState, 'status' | 'paused' | 'troubled'> | null> {\n return Effect.runPromise(getAgentState(agentId).pipe(Effect.catch(() => Effect.succeed(null))));\n}\n\nasync function loadProjectsForScan(projects?: ProjectEntry[]): Promise<ProjectEntry[]> {\n if (projects) return projects;\n return Effect.runPromise(listProjects().pipe(Effect.catch(() => Effect.succeed([]))));\n}\n\nasync function loadTmuxSessionNames(tmuxSessionNames?: readonly string[]): Promise<readonly string[]> {\n if (tmuxSessionNames) return tmuxSessionNames;\n return Effect.runPromise(listSessionNames().pipe(Effect.catch(() => Effect.succeed([]))));\n}\n\nfunction hasIssueScopedSession(tmuxSessionNames: readonly string[], agentId: string): boolean {\n return tmuxSessionNames.some((sessionName) => sessionName === agentId || sessionName.startsWith(`${agentId}-`));\n}\n\nasync function defaultHasOpenPrForBranch(projectPath: string, issueId: string): Promise<boolean> {\n const branch = `feature/${issueId.toLowerCase()}`;\n\n try {\n const resolved = resolveGitHubIssueSync(issueId);\n if (isGitHubAppConfigured() && resolved.isGitHub) {\n return (await Effect.runPromise(listPullRequestsForHead(resolved.owner, resolved.repo, branch, 'open'))).length > 0;\n }\n\n const result = await execAsync(`gh pr list --head ${branch} --state open --json number --limit 1`, { cwd: projectPath }) as { stdout?: string } | string;\n const stdout = typeof result === 'string' ? result : result.stdout;\n const prs = JSON.parse(stdout ?? '[]') as unknown;\n return Array.isArray(prs) && prs.length > 0;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logReconcilerDiagnostic('open-pr-check-failed', { projectPath, issueId, branch, error: message });\n return false;\n }\n}\n\nexport async function findOrphanProposedSpecsForReconciler(options: FindOrphanProposedOptions = {}): Promise<OrphanProposedCandidate[]> {\n const projects = await loadProjectsForScan(options.projects);\n const tmuxSessionNames = await loadTmuxSessionNames(options.tmuxSessionNames);\n const getState = options.getAgentStateForIssue ?? defaultGetAgentState;\n const getReviewStatus = options.getReviewStatusForIssue ?? getReviewStatusSync;\n const candidates: OrphanProposedCandidate[] = [];\n\n for (const { key, config } of projects) {\n const specsDir = join(config.path, '.pan', 'specs');\n if (!existsSync(specsDir)) continue;\n\n const entries = await readdir(specsDir, { withFileTypes: true }).catch(() => []);\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.vbrief.json')) continue;\n\n const specPath = join(specsDir, entry.name);\n const spec = await readJsonFile<ProposedSpecState>(specPath);\n if (spec?.plan?.status !== 'proposed') continue;\n\n const issueId = normalizeIssueId(spec.plan?.id);\n if (!issueId) continue;\n\n const agentId = `agent-${issueId.toLowerCase()}`;\n const state = await getState(agentId);\n if (hasIssueScopedSession(tmuxSessionNames, agentId)) continue;\n if (state?.status === 'starting' || state?.status === 'running') continue;\n if (state?.paused === true || state?.troubled === true) continue;\n if (hasReviewPipelinePresence(getReviewStatus(issueId))) {\n logReconcilerDiagnostic('candidate-excluded', { issueId, reason: 'review-pipeline-presence' });\n continue;\n }\n if (terminalClosedIssueIds.has(issueId)) continue;\n if (await isIssueClosed(issueId, options.closedIssueIds)) continue;\n\n const planItems = Array.isArray(spec.plan.items) ? spec.plan.items : [];\n const planItemCount = planItems.length;\n const beadCount = await countBeadsForIssue(config.path, issueId);\n if (planItemCount === 0 || beadCount !== planItemCount) continue;\n\n candidates.push({\n projectKey: key,\n projectName: config.name,\n projectPath: config.path,\n issueId,\n specPath,\n beadCount,\n planItemCount,\n });\n }\n }\n\n return candidates;\n}\n\nfunction internalDashboardOrigin(): string {\n const port = Number.parseInt(process.env['API_PORT'] ?? process.env['PORT'] ?? '3011', 10);\n return process.env['OVERDECK_INTERNAL_DASHBOARD_URL'] ?? `http://127.0.0.1:${port}`;\n}\n\nfunction classifySpawnSkip(status: number, body: Record<string, unknown>): string {\n const error = typeof body['error'] === 'string' ? body['error'] : '';\n if (body['stackHealth'] || /workspace docker stack/i.test(error)) return 'stack-unhealthy';\n if (body['paused'] === true) return 'paused';\n if (body['troubled'] === true) return 'troubled';\n if (body['guardrails'] || body['requiresAcknowledgement'] === true || status === 409) return 'guardrails';\n if (body['providerHealth']) return 'provider-down';\n if (status === 422 && /already closed|closed issue|cannot start an agent for a closed issue/i.test(error)) return 'closed-issue';\n return 'spawn-failed';\n}\n\nexport async function spawnWorkAgentThroughAgentsEndpoint(issueId: string, dashboardOrigin = internalDashboardOrigin()): Promise<SpawnWorkAgentResult> {\n const response = await fetch(new URL('/api/agents', dashboardOrigin), {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n origin: dashboardOrigin,\n },\n body: JSON.stringify({ issueId, role: 'work' }),\n });\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n const agentId = typeof body['agentId'] === 'string' ? body['agentId'] : `agent-${issueId.toLowerCase()}`;\n\n if (response.ok && body['success'] !== false) {\n return { spawned: true, agentId };\n }\n\n return {\n spawned: false,\n skippedReason: classifySpawnSkip(response.status, body),\n error: typeof body['error'] === 'string'\n ? body['error']\n : typeof body['message'] === 'string'\n ? body['message']\n : `Work agent spawn returned HTTP ${response.status}`,\n };\n}\n\nexport interface RebuildAndStartResult {\n triggered: boolean;\n error?: string;\n}\n\n/**\n * PAN-2520: trigger the `rebuild-and-start` recovery for a workspace whose Docker\n * stack is unhealthy — the same path the dashboard \"Rebuild & start\" button uses\n * (`POST /api/workspaces/:issueId/rebuild-and-start`, workspaces.ts). It rebuilds\n * the stack, then `pan start`s the work agent once the stack is healthy. Used by\n * the deacon dead-end recovery when a PAN-2209 respawn defers on `stack-unhealthy`\n * (which would otherwise loop forever, since nothing else rebuilds the stack).\n */\nexport async function triggerRebuildAndStart(\n issueId: string,\n dashboardOrigin = internalDashboardOrigin(),\n): Promise<RebuildAndStartResult> {\n const response = await fetch(\n new URL(`/api/workspaces/${encodeURIComponent(issueId)}/rebuild-and-start`, dashboardOrigin),\n {\n method: 'POST',\n headers: { 'content-type': 'application/json', origin: dashboardOrigin },\n },\n );\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n if (response.ok && body['success'] !== false) {\n return { triggered: true };\n }\n return {\n triggered: false,\n error: typeof body['error'] === 'string'\n ? body['error']\n : `rebuild-and-start returned HTTP ${response.status}`,\n };\n}\n\n/**\n * Surface a real, actioned reconciler outcome in the dashboard activity feed.\n * Use this ONLY when something actually happened that a human should see — a\n * work agent was started, or a start genuinely failed. `message` must read as a\n * plain sentence (it is what the feed shows), not a machine event name.\n */\nfunction emitReconcilerActivity(\n level: 'info' | 'warn' | 'error' | 'success',\n message: string,\n details: Record<string, unknown> = {},\n issueId = 'ALL',\n): void {\n const eventIssueId = issueId.toUpperCase();\n emitActivityEntrySync({\n source: 'cloister',\n level,\n issueId: eventIssueId,\n message,\n details: JSON.stringify({\n ...details,\n issueId: eventIssueId,\n timestamp: new Date().toISOString(),\n }),\n });\n}\n\n/**\n * Per-cycle diagnostic trail — console only, never the user activity feed.\n * The reconciler scans on every patrol (~60s); scan-start / orphan-detected /\n * cooldown-skip chatter was flooding the feed and reading as cryptic machine\n * codes (PAN-1626). Those belong in logs, not in front of the operator.\n */\nfunction logReconcilerDiagnostic(kind: string, info: Record<string, unknown> = {}): void {\n console.debug(`[orphan-proposed-reconciler] ${kind}`, info);\n}\n\n/**\n * PAN-1908: reactive orphan-proposed handler. When a spec reaches `proposed`\n * and no work agent is running/starting, spawn a work agent for that issue\n * without scanning all project specs.\n */\nexport async function handleOrphanProposedSpec(\n issueId: string,\n options: HandleOrphanProposedSpecOptions = {},\n): Promise<string[]> {\n const upperIssueId = issueId.trim().toUpperCase();\n if (!upperIssueId) return [];\n\n const loadedConfig = options.config ?? await Effect.runPromise(\n loadCloisterConfig().pipe(\n Effect.map(config => config.orphanProposedReconciler ?? { enabled: true, minAttemptIntervalMs: DEFAULT_ATTEMPT_INTERVAL_MS }),\n Effect.catch(() => Effect.succeed({ enabled: true, minAttemptIntervalMs: DEFAULT_ATTEMPT_INTERVAL_MS })),\n ),\n );\n if (loadedConfig.enabled === false) return [];\n\n const now = Date.now();\n const minAttemptIntervalMs = Math.max(loadedConfig.minAttemptIntervalMs ?? DEFAULT_ATTEMPT_INTERVAL_MS, DEFAULT_ATTEMPT_INTERVAL_MS);\n const lastAttempt = attemptCooldowns.get(upperIssueId);\n if (lastAttempt !== undefined && now - lastAttempt < minAttemptIntervalMs) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'cooldown' });\n return [];\n }\n\n const resolved = options.project ?? resolveProjectFromIssueSync(upperIssueId);\n if (!resolved) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'no-project' });\n return [];\n }\n const projectPath = resolved.projectPath;\n const projectKey = resolved.projectKey;\n\n const issueLower = upperIssueId.toLowerCase();\n\n let planDoc: VBriefDocument;\n let planPath: string | null;\n if (options.spec) {\n planPath = options.spec.path;\n planDoc = options.spec.doc;\n } else {\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n planPath = findPlanSync(workspacePath) ?? await findSpecPathForIssue(projectPath, upperIssueId);\n if (!planPath) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'no-spec' });\n return [];\n }\n\n try {\n planDoc = readPlanSync(planPath);\n } catch {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'spec-unreadable' });\n return [];\n }\n }\n\n if (planDoc.plan?.status !== 'proposed') {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'not-proposed', status: planDoc.plan?.status });\n return [];\n }\n\n const agentId = `agent-${issueLower}`;\n\n // PAN-1908: prefer agents-table state (fast) but fall back to state.json.\n const agentState = await Effect.runPromise(\n getAgentState(agentId).pipe(Effect.catch(() => Effect.succeed(null))),\n );\n if (agentState?.status === 'starting' || agentState?.status === 'running') {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'agent-active' });\n return [];\n }\n if (agentState?.paused === true || agentState?.troubled === true) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: agentState.paused ? 'paused' : 'troubled' });\n return [];\n }\n\n // Any tmux session for the issue means the pipeline is active here.\n const sessions = await Effect.runPromise(listSessionNames().pipe(Effect.catch(() => Effect.succeed([]))));\n if (hasIssueScopedSession(sessions, agentId)) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'issue-scoped-session' });\n return [];\n }\n\n if (hasReviewPipelinePresence(getReviewStatusSync(upperIssueId))) {\n logReconcilerDiagnostic('candidate-excluded', { issueId: upperIssueId, reason: 'review-pipeline-presence' });\n return [];\n }\n\n if (terminalClosedIssueIds.has(upperIssueId)) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'terminal-closed' });\n return [];\n }\n if (await isIssueClosed(upperIssueId)) {\n terminalClosedIssueIds.add(upperIssueId);\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'closed' });\n return [];\n }\n\n const planItems = Array.isArray(planDoc.plan?.items) ? planDoc.plan.items : [];\n const planItemCount = planItems.length;\n const beadCount = await countBeadsForIssue(resolved.projectPath, upperIssueId);\n if (planItemCount === 0 || beadCount !== planItemCount) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'bead-mismatch', planItemCount, beadCount });\n return [];\n }\n\n try {\n if (await (options.hasOpenPrForBranch ?? defaultHasOpenPrForBranch)(projectPath, upperIssueId)) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'open-pr' });\n return [`Skipped orphan proposed spec ${upperIssueId}: open-pr`];\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logReconcilerDiagnostic('open-pr-check-failed', { issueId: upperIssueId, error: message });\n }\n\n attemptCooldowns.set(upperIssueId, now);\n\n try {\n const spawn = await (options.spawnWorkAgent ?? ((id) => spawnWorkAgentThroughAgentsEndpoint(id, options.dashboardOrigin)))(upperIssueId);\n if (spawn.spawned) {\n emitReconcilerActivity(\n 'success',\n `Started work agent for ${upperIssueId} — proposed spec had tasks but no running agent`,\n { issueId: upperIssueId, agentId: spawn.agentId, projectKey, projectPath },\n upperIssueId,\n );\n return [`Spawned work agent for orphan proposed spec ${upperIssueId}`];\n }\n\n const reason = spawn.skippedReason ?? 'spawn-failed';\n if (reason === 'closed-issue') {\n terminalClosedIssueIds.add(upperIssueId);\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason, error: spawn.error });\n return [`Skipped orphan proposed spec ${upperIssueId}: ${reason}`];\n }\n emitReconcilerActivity(\n 'warn',\n `Couldn't start work agent for ${upperIssueId}: ${reason}`,\n { issueId: upperIssueId, error: spawn.error, projectKey, projectPath },\n upperIssueId,\n );\n return [`Skipped orphan proposed spec ${upperIssueId}: ${reason}`];\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n emitReconcilerActivity(\n 'error',\n `Couldn't start work agent for ${upperIssueId}: ${message}`,\n { issueId: upperIssueId, projectKey, projectPath },\n upperIssueId,\n );\n return [`Skipped orphan proposed spec ${upperIssueId}: spawn-failed`];\n }\n}\n\n/**\n * PAN-1908: thin dropped-event safety net for orphan proposed specs. The\n * primary path is reactive via handleOrphanProposedSpec on\n * issue.statusChanged(todo). This net finds candidate issueIds and delegates\n * to the same handler so the eligibility logic lives in one place.\n */\nexport async function reconcileOrphanProposedSpecs(options: ReconcileOrphanProposedOptions = {}): Promise<string[]> {\n const loadedConfig = options.config ?? await Effect.runPromise(\n loadCloisterConfig().pipe(\n Effect.map(config => config.orphanProposedReconciler ?? { enabled: true, minAttemptIntervalMs: DEFAULT_ATTEMPT_INTERVAL_MS }),\n Effect.catch(() => Effect.succeed({ enabled: true, minAttemptIntervalMs: DEFAULT_ATTEMPT_INTERVAL_MS })),\n ),\n );\n if (loadedConfig.enabled === false) return [];\n if (scanInFlight) {\n logReconcilerDiagnostic('scan-skipped', { reason: 'previous scan still running' });\n return [];\n }\n\n scanInFlight = true;\n try {\n const actions: string[] = [];\n const candidates = await findOrphanProposedSpecsForReconciler(options);\n\n for (const candidate of candidates) {\n let planDoc: VBriefDocument;\n try {\n planDoc = readPlanSync(candidate.specPath);\n } catch {\n logReconcilerDiagnostic('spawn-skipped', { issueId: candidate.issueId, reason: 'spec-unreadable' });\n continue;\n }\n\n const result = await handleOrphanProposedSpec(candidate.issueId, {\n spawnWorkAgent: options.spawnWorkAgent,\n hasOpenPrForBranch: options.hasOpenPrForBranch,\n dashboardOrigin: options.dashboardOrigin,\n config: { enabled: true, minAttemptIntervalMs: loadedConfig.minAttemptIntervalMs },\n project: { projectKey: candidate.projectKey, projectPath: candidate.projectPath },\n spec: { path: candidate.specPath, doc: planDoc },\n });\n actions.push(...result);\n }\n\n return actions;\n } finally {\n scanInFlight = false;\n }\n}\n\nexport function clearOrphanProposedAttemptCooldowns(): void {\n attemptCooldowns.clear();\n terminalClosedIssueIds.clear();\n clearIssueClosedCache();\n scanInFlight = false;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"orphan-proposed-reconciler-Boq_XaRe.js","names":[],"sources":["../../src/lib/cloister/orphan-proposed-reconciler.ts"],"sourcesContent":["import { exec } from 'child_process';\nimport { existsSync } from 'fs';\nimport { readdir, readFile } from 'fs/promises';\nimport { readFileSync, readdirSync } from 'fs';\nimport { isAbsolute, join, resolve } from 'path';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\n\nimport { getAgentState, type AgentState } from '../agents.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { listProjects, resolveProjectFromIssueSync, type ProjectConfig } from '../projects.js';\nimport { getReviewStatusSync, type ReviewStatus } from '../review-status.js';\nimport { listSessionNames } from '../tmux.js';\nimport { findPlanSync, readPlanSync } from '../vbrief/io.js';\nimport type { VBriefDocument } from '../vbrief/types.js';\nimport { isGitHubAppConfigured, listPullRequestsForHead } from '../github-app.js';\nimport { resolveGitHubIssueSync } from '../tracker-utils.js';\nimport { loadCloisterConfig } from './config.js';\nimport { clearIssueClosedCache, isIssueClosed } from './issue-closed.js';\n\nconst DEFAULT_ATTEMPT_INTERVAL_MS = 5 * 60 * 1000;\nconst execAsync = promisify(exec);\nconst attemptCooldowns = new Map<string, number>();\nconst terminalClosedIssueIds = new Set<string>();\nlet scanInFlight = false;\n\nexport interface OrphanProposedReconcilerConfig {\n enabled?: boolean;\n minAttemptIntervalMs?: number;\n}\n\ntype ProjectEntry = { key: string; config: Pick<ProjectConfig, 'name' | 'path'> };\n\ntype ProposedSpecState = {\n plan?: {\n id?: unknown;\n status?: unknown;\n items?: unknown;\n };\n};\n\nexport type ReviewPipelinePresenceStatus = Pick<ReviewStatus,\n 'reviewStatus' | 'testStatus' | 'mergeStatus' | 'readyForMerge' | 'prNumber' | 'prUrl'\n>;\n\nexport function hasReviewPipelinePresence(status: ReviewPipelinePresenceStatus | null): boolean {\n if (!status) return false;\n if (status.reviewStatus !== 'pending') return true;\n if (status.testStatus !== 'pending') return true;\n if (status.mergeStatus && status.mergeStatus !== 'pending') return true;\n if (status.readyForMerge === true) return true;\n if (status.prNumber != null) return true;\n if (typeof status.prUrl === 'string' && status.prUrl.trim().length > 0) return true;\n return false;\n}\n\nexport interface OrphanProposedCandidate {\n projectKey: string;\n projectName: string;\n projectPath: string;\n issueId: string;\n specPath: string;\n beadCount: number;\n planItemCount: number;\n}\n\nexport interface FindOrphanProposedOptions {\n projects?: ProjectEntry[];\n tmuxSessionNames?: readonly string[];\n getAgentStateForIssue?: (agentId: string) => Promise<Pick<AgentState, 'status' | 'paused' | 'troubled'> | null>;\n getReviewStatusForIssue?: (issueId: string) => ReviewPipelinePresenceStatus | null;\n closedIssueIds?: Set<string>;\n}\n\nexport interface SpawnWorkAgentResult {\n spawned: boolean;\n agentId?: string;\n skippedReason?: string;\n error?: string;\n}\n\nexport interface ReconcileOrphanProposedOptions extends FindOrphanProposedOptions {\n now?: Date;\n config?: OrphanProposedReconcilerConfig;\n spawnWorkAgent?: (issueId: string) => Promise<SpawnWorkAgentResult>;\n hasOpenPrForBranch?: (projectPath: string, issueId: string) => Promise<boolean>;\n dashboardOrigin?: string;\n}\n\nexport interface HandleOrphanProposedSpecOptions {\n spawnWorkAgent?: (issueId: string) => Promise<SpawnWorkAgentResult>;\n hasOpenPrForBranch?: (projectPath: string, issueId: string) => Promise<boolean>;\n dashboardOrigin?: string;\n config?: OrphanProposedReconcilerConfig;\n /** Project override for tests / callers that already resolved the issue. */\n project?: { projectKey: string; projectPath: string };\n /** Spec override so the safety net does not re-read the file. */\n spec?: { path: string; doc: VBriefDocument };\n}\n\nasync function findSpecPathForIssue(projectPath: string, issueId: string): Promise<string | null> {\n const specsDir = join(projectPath, '.pan', 'specs');\n if (!existsSync(specsDir)) return null;\n let filenames: string[];\n try {\n filenames = readdirSync(specsDir);\n } catch {\n return null;\n }\n for (const filename of filenames) {\n if (!filename.endsWith('.vbrief.json')) continue;\n const specPath = join(specsDir, filename);\n try {\n const doc = readPlanSync(specPath);\n if (normalizeIssueId(doc.plan?.id) === issueId.toUpperCase()) {\n return specPath;\n }\n } catch {\n // ignore unreadable specs\n }\n }\n return null;\n}\n\nfunction normalizeIssueId(value: unknown): string | null {\n return typeof value === 'string' && /^[A-Za-z]+-\\d+$/.test(value.trim())\n ? value.trim().toUpperCase()\n : null;\n}\n\nasync function readJsonFile<T>(path: string): Promise<T | null> {\n try {\n return JSON.parse(await readFile(path, 'utf-8')) as T;\n } catch {\n return null;\n }\n}\n\nasync function resolveBeadsIssuesPath(projectPath: string, issueId: string): Promise<string | null> {\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n const beadsDir = join(workspacePath, '.beads');\n const localIssuesPath = join(beadsDir, 'issues.jsonl');\n if (existsSync(localIssuesPath)) return localIssuesPath;\n\n const redirectPath = join(beadsDir, 'redirect');\n if (!existsSync(redirectPath)) return null;\n\n try {\n const redirected = (await readFile(redirectPath, 'utf-8')).trim();\n if (!redirected) return null;\n return join(isAbsolute(redirected) ? redirected : resolve(workspacePath, redirected), 'issues.jsonl');\n } catch {\n return null;\n }\n}\n\nasync function countBeadsForIssue(projectPath: string, issueId: string): Promise<number> {\n const beadsPath = await resolveBeadsIssuesPath(projectPath, issueId);\n if (!beadsPath || !existsSync(beadsPath)) return 0;\n\n try {\n const raw = await readFile(beadsPath, 'utf-8');\n return raw.split('\\n').filter(Boolean).filter((line) => {\n try {\n const record = JSON.parse(line) as { _type?: unknown; labels?: unknown };\n return record._type === 'issue'\n && Array.isArray(record.labels)\n && record.labels.some((label) => typeof label === 'string' && label.toLowerCase() === issueId.toLowerCase());\n } catch {\n return false;\n }\n }).length;\n } catch {\n return 0;\n }\n}\n\nasync function defaultGetAgentState(agentId: string): Promise<Pick<AgentState, 'status' | 'paused' | 'troubled'> | null> {\n return Effect.runPromise(getAgentState(agentId).pipe(Effect.catch(() => Effect.succeed(null))));\n}\n\nasync function loadProjectsForScan(projects?: ProjectEntry[]): Promise<ProjectEntry[]> {\n if (projects) return projects;\n return Effect.runPromise(listProjects().pipe(Effect.catch(() => Effect.succeed([]))));\n}\n\nasync function loadTmuxSessionNames(tmuxSessionNames?: readonly string[]): Promise<readonly string[]> {\n if (tmuxSessionNames) return tmuxSessionNames;\n return Effect.runPromise(listSessionNames().pipe(Effect.catch(() => Effect.succeed([]))));\n}\n\nfunction hasIssueScopedSession(tmuxSessionNames: readonly string[], agentId: string): boolean {\n return tmuxSessionNames.some((sessionName) => sessionName === agentId || sessionName.startsWith(`${agentId}-`));\n}\n\nasync function defaultHasOpenPrForBranch(projectPath: string, issueId: string): Promise<boolean> {\n const branch = `feature/${issueId.toLowerCase()}`;\n\n try {\n const resolved = resolveGitHubIssueSync(issueId);\n if (isGitHubAppConfigured() && resolved.isGitHub) {\n return (await Effect.runPromise(listPullRequestsForHead(resolved.owner, resolved.repo, branch, 'open'))).length > 0;\n }\n\n const result = await execAsync(`gh pr list --head ${branch} --state open --json number --limit 1`, { cwd: projectPath }) as { stdout?: string } | string;\n const stdout = typeof result === 'string' ? result : result.stdout;\n const prs = JSON.parse(stdout ?? '[]') as unknown;\n return Array.isArray(prs) && prs.length > 0;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logReconcilerDiagnostic('open-pr-check-failed', { projectPath, issueId, branch, error: message });\n return false;\n }\n}\n\nexport async function findOrphanProposedSpecsForReconciler(options: FindOrphanProposedOptions = {}): Promise<OrphanProposedCandidate[]> {\n const projects = await loadProjectsForScan(options.projects);\n const tmuxSessionNames = await loadTmuxSessionNames(options.tmuxSessionNames);\n const getState = options.getAgentStateForIssue ?? defaultGetAgentState;\n const getReviewStatus = options.getReviewStatusForIssue ?? getReviewStatusSync;\n const candidates: OrphanProposedCandidate[] = [];\n\n for (const { key, config } of projects) {\n const specsDir = join(config.path, '.pan', 'specs');\n if (!existsSync(specsDir)) continue;\n\n const entries = await readdir(specsDir, { withFileTypes: true }).catch(() => []);\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.vbrief.json')) continue;\n\n const specPath = join(specsDir, entry.name);\n const spec = await readJsonFile<ProposedSpecState>(specPath);\n if (spec?.plan?.status !== 'proposed') continue;\n\n const issueId = normalizeIssueId(spec.plan?.id);\n if (!issueId) continue;\n\n const agentId = `agent-${issueId.toLowerCase()}`;\n const state = await getState(agentId);\n if (hasIssueScopedSession(tmuxSessionNames, agentId)) continue;\n if (state?.status === 'starting' || state?.status === 'running') continue;\n if (state?.paused === true || state?.troubled === true) continue;\n if (hasReviewPipelinePresence(getReviewStatus(issueId))) {\n logReconcilerDiagnostic('candidate-excluded', { issueId, reason: 'review-pipeline-presence' });\n continue;\n }\n if (terminalClosedIssueIds.has(issueId)) continue;\n if (await isIssueClosed(issueId, options.closedIssueIds)) continue;\n\n const planItems = Array.isArray(spec.plan.items) ? spec.plan.items : [];\n const planItemCount = planItems.length;\n const beadCount = await countBeadsForIssue(config.path, issueId);\n if (planItemCount === 0 || beadCount !== planItemCount) continue;\n\n candidates.push({\n projectKey: key,\n projectName: config.name,\n projectPath: config.path,\n issueId,\n specPath,\n beadCount,\n planItemCount,\n });\n }\n }\n\n return candidates;\n}\n\nfunction internalDashboardOrigin(): string {\n const port = Number.parseInt(process.env['API_PORT'] ?? process.env['PORT'] ?? '3011', 10);\n return process.env['OVERDECK_INTERNAL_DASHBOARD_URL'] ?? `http://127.0.0.1:${port}`;\n}\n\nfunction classifySpawnSkip(status: number, body: Record<string, unknown>): string {\n const error = typeof body['error'] === 'string' ? body['error'] : '';\n if (body['stackHealth'] || /workspace docker stack/i.test(error)) return 'stack-unhealthy';\n if (body['paused'] === true) return 'paused';\n if (body['troubled'] === true) return 'troubled';\n if (body['guardrails'] || body['requiresAcknowledgement'] === true || status === 409) return 'guardrails';\n if (body['providerHealth']) return 'provider-down';\n if (status === 422 && /already closed|closed issue|cannot start an agent for a closed issue/i.test(error)) return 'closed-issue';\n return 'spawn-failed';\n}\n\nexport async function spawnWorkAgentThroughAgentsEndpoint(issueId: string, dashboardOrigin = internalDashboardOrigin()): Promise<SpawnWorkAgentResult> {\n const response = await fetch(new URL('/api/agents', dashboardOrigin), {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n origin: dashboardOrigin,\n },\n body: JSON.stringify({ issueId, role: 'work' }),\n });\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n const agentId = typeof body['agentId'] === 'string' ? body['agentId'] : `agent-${issueId.toLowerCase()}`;\n\n if (response.ok && body['success'] !== false) {\n return { spawned: true, agentId };\n }\n\n return {\n spawned: false,\n skippedReason: classifySpawnSkip(response.status, body),\n error: typeof body['error'] === 'string'\n ? body['error']\n : typeof body['message'] === 'string'\n ? body['message']\n : `Work agent spawn returned HTTP ${response.status}`,\n };\n}\n\nexport interface RebuildAndStartResult {\n triggered: boolean;\n error?: string;\n}\n\n/**\n * PAN-2520: trigger the `rebuild-and-start` recovery for a workspace whose Docker\n * stack is unhealthy — the same path the dashboard \"Rebuild & start\" button uses\n * (`POST /api/workspaces/:issueId/rebuild-and-start`, workspaces.ts). It rebuilds\n * the stack, then `pan start`s the work agent once the stack is healthy. Used by\n * the deacon dead-end recovery when a PAN-2209 respawn defers on `stack-unhealthy`\n * (which would otherwise loop forever, since nothing else rebuilds the stack).\n */\nexport async function triggerRebuildAndStart(\n issueId: string,\n dashboardOrigin = internalDashboardOrigin(),\n): Promise<RebuildAndStartResult> {\n const response = await fetch(\n new URL(`/api/workspaces/${encodeURIComponent(issueId)}/rebuild-and-start`, dashboardOrigin),\n {\n method: 'POST',\n headers: { 'content-type': 'application/json', origin: dashboardOrigin },\n },\n );\n const body = await response.json().catch(() => ({})) as Record<string, unknown>;\n if (response.ok && body['success'] !== false) {\n return { triggered: true };\n }\n return {\n triggered: false,\n error: typeof body['error'] === 'string'\n ? body['error']\n : `rebuild-and-start returned HTTP ${response.status}`,\n };\n}\n\n/**\n * Surface a real, actioned reconciler outcome in the dashboard activity feed.\n * Use this ONLY when something actually happened that a human should see — a\n * work agent was started, or a start genuinely failed. `message` must read as a\n * plain sentence (it is what the feed shows), not a machine event name.\n */\nfunction emitReconcilerActivity(\n level: 'info' | 'warn' | 'error' | 'success',\n message: string,\n details: Record<string, unknown> = {},\n issueId = 'ALL',\n): void {\n const eventIssueId = issueId.toUpperCase();\n emitActivityEntrySync({\n source: 'cloister',\n level,\n issueId: eventIssueId,\n message,\n details: JSON.stringify({\n ...details,\n issueId: eventIssueId,\n timestamp: new Date().toISOString(),\n }),\n });\n}\n\n/**\n * Per-cycle diagnostic trail — console only, never the user activity feed.\n * The reconciler scans on every patrol (~60s); scan-start / orphan-detected /\n * cooldown-skip chatter was flooding the feed and reading as cryptic machine\n * codes (PAN-1626). Those belong in logs, not in front of the operator.\n */\nfunction logReconcilerDiagnostic(kind: string, info: Record<string, unknown> = {}): void {\n console.debug(`[orphan-proposed-reconciler] ${kind}`, info);\n}\n\n/**\n * PAN-1908: reactive orphan-proposed handler. When a spec reaches `proposed`\n * and no work agent is running/starting, spawn a work agent for that issue\n * without scanning all project specs.\n */\nexport async function handleOrphanProposedSpec(\n issueId: string,\n options: HandleOrphanProposedSpecOptions = {},\n): Promise<string[]> {\n const upperIssueId = issueId.trim().toUpperCase();\n if (!upperIssueId) return [];\n\n const loadedConfig = options.config ?? await Effect.runPromise(\n loadCloisterConfig().pipe(\n Effect.map(config => config.orphanProposedReconciler ?? { enabled: true, minAttemptIntervalMs: DEFAULT_ATTEMPT_INTERVAL_MS }),\n Effect.catch(() => Effect.succeed({ enabled: true, minAttemptIntervalMs: DEFAULT_ATTEMPT_INTERVAL_MS })),\n ),\n );\n if (loadedConfig.enabled === false) return [];\n\n const now = Date.now();\n const minAttemptIntervalMs = Math.max(loadedConfig.minAttemptIntervalMs ?? DEFAULT_ATTEMPT_INTERVAL_MS, DEFAULT_ATTEMPT_INTERVAL_MS);\n const lastAttempt = attemptCooldowns.get(upperIssueId);\n if (lastAttempt !== undefined && now - lastAttempt < minAttemptIntervalMs) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'cooldown' });\n return [];\n }\n\n const resolved = options.project ?? resolveProjectFromIssueSync(upperIssueId);\n if (!resolved) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'no-project' });\n return [];\n }\n const projectPath = resolved.projectPath;\n const projectKey = resolved.projectKey;\n\n const issueLower = upperIssueId.toLowerCase();\n\n let planDoc: VBriefDocument;\n let planPath: string | null;\n if (options.spec) {\n planPath = options.spec.path;\n planDoc = options.spec.doc;\n } else {\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n planPath = findPlanSync(workspacePath) ?? await findSpecPathForIssue(projectPath, upperIssueId);\n if (!planPath) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'no-spec' });\n return [];\n }\n\n try {\n planDoc = readPlanSync(planPath);\n } catch {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'spec-unreadable' });\n return [];\n }\n }\n\n if (planDoc.plan?.status !== 'proposed') {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'not-proposed', status: planDoc.plan?.status });\n return [];\n }\n\n const agentId = `agent-${issueLower}`;\n\n // PAN-1908: prefer agents-table state (fast) but fall back to state.json.\n const agentState = await Effect.runPromise(\n getAgentState(agentId).pipe(Effect.catch(() => Effect.succeed(null))),\n );\n if (agentState?.status === 'starting' || agentState?.status === 'running') {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'agent-active' });\n return [];\n }\n if (agentState?.paused === true || agentState?.troubled === true) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: agentState.paused ? 'paused' : 'troubled' });\n return [];\n }\n\n // Any tmux session for the issue means the pipeline is active here.\n const sessions = await Effect.runPromise(listSessionNames().pipe(Effect.catch(() => Effect.succeed([]))));\n if (hasIssueScopedSession(sessions, agentId)) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'issue-scoped-session' });\n return [];\n }\n\n if (hasReviewPipelinePresence(getReviewStatusSync(upperIssueId))) {\n logReconcilerDiagnostic('candidate-excluded', { issueId: upperIssueId, reason: 'review-pipeline-presence' });\n return [];\n }\n\n if (terminalClosedIssueIds.has(upperIssueId)) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'terminal-closed' });\n return [];\n }\n if (await isIssueClosed(upperIssueId)) {\n terminalClosedIssueIds.add(upperIssueId);\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'closed' });\n return [];\n }\n\n const planItems = Array.isArray(planDoc.plan?.items) ? planDoc.plan.items : [];\n const planItemCount = planItems.length;\n const beadCount = await countBeadsForIssue(resolved.projectPath, upperIssueId);\n if (planItemCount === 0 || beadCount !== planItemCount) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'bead-mismatch', planItemCount, beadCount });\n return [];\n }\n\n try {\n if (await (options.hasOpenPrForBranch ?? defaultHasOpenPrForBranch)(projectPath, upperIssueId)) {\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason: 'open-pr' });\n return [`Skipped orphan proposed spec ${upperIssueId}: open-pr`];\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n logReconcilerDiagnostic('open-pr-check-failed', { issueId: upperIssueId, error: message });\n }\n\n attemptCooldowns.set(upperIssueId, now);\n\n try {\n const spawn = await (options.spawnWorkAgent ?? ((id) => spawnWorkAgentThroughAgentsEndpoint(id, options.dashboardOrigin)))(upperIssueId);\n if (spawn.spawned) {\n emitReconcilerActivity(\n 'success',\n `Started work agent for ${upperIssueId} — proposed spec had tasks but no running agent`,\n { issueId: upperIssueId, agentId: spawn.agentId, projectKey, projectPath },\n upperIssueId,\n );\n return [`Spawned work agent for orphan proposed spec ${upperIssueId}`];\n }\n\n const reason = spawn.skippedReason ?? 'spawn-failed';\n if (reason === 'closed-issue') {\n terminalClosedIssueIds.add(upperIssueId);\n logReconcilerDiagnostic('spawn-skipped', { issueId: upperIssueId, reason, error: spawn.error });\n return [`Skipped orphan proposed spec ${upperIssueId}: ${reason}`];\n }\n emitReconcilerActivity(\n 'warn',\n `Couldn't start work agent for ${upperIssueId}: ${reason}`,\n { issueId: upperIssueId, error: spawn.error, projectKey, projectPath },\n upperIssueId,\n );\n return [`Skipped orphan proposed spec ${upperIssueId}: ${reason}`];\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n emitReconcilerActivity(\n 'error',\n `Couldn't start work agent for ${upperIssueId}: ${message}`,\n { issueId: upperIssueId, projectKey, projectPath },\n upperIssueId,\n );\n return [`Skipped orphan proposed spec ${upperIssueId}: spawn-failed`];\n }\n}\n\n/**\n * PAN-1908: thin dropped-event safety net for orphan proposed specs. The\n * primary path is reactive via handleOrphanProposedSpec on\n * issue.statusChanged(todo). This net finds candidate issueIds and delegates\n * to the same handler so the eligibility logic lives in one place.\n */\nexport async function reconcileOrphanProposedSpecs(options: ReconcileOrphanProposedOptions = {}): Promise<string[]> {\n const loadedConfig = options.config ?? await Effect.runPromise(\n loadCloisterConfig().pipe(\n Effect.map(config => config.orphanProposedReconciler ?? { enabled: true, minAttemptIntervalMs: DEFAULT_ATTEMPT_INTERVAL_MS }),\n Effect.catch(() => Effect.succeed({ enabled: true, minAttemptIntervalMs: DEFAULT_ATTEMPT_INTERVAL_MS })),\n ),\n );\n if (loadedConfig.enabled === false) return [];\n if (scanInFlight) {\n logReconcilerDiagnostic('scan-skipped', { reason: 'previous scan still running' });\n return [];\n }\n\n scanInFlight = true;\n try {\n const actions: string[] = [];\n const candidates = await findOrphanProposedSpecsForReconciler(options);\n\n for (const candidate of candidates) {\n let planDoc: VBriefDocument;\n try {\n planDoc = readPlanSync(candidate.specPath);\n } catch {\n logReconcilerDiagnostic('spawn-skipped', { issueId: candidate.issueId, reason: 'spec-unreadable' });\n continue;\n }\n\n const result = await handleOrphanProposedSpec(candidate.issueId, {\n spawnWorkAgent: options.spawnWorkAgent,\n hasOpenPrForBranch: options.hasOpenPrForBranch,\n dashboardOrigin: options.dashboardOrigin,\n config: { enabled: true, minAttemptIntervalMs: loadedConfig.minAttemptIntervalMs },\n project: { projectKey: candidate.projectKey, projectPath: candidate.projectPath },\n spec: { path: candidate.specPath, doc: planDoc },\n });\n actions.push(...result);\n }\n\n return actions;\n } finally {\n scanInFlight = false;\n }\n}\n\nexport function clearOrphanProposedAttemptCooldowns(): void {\n attemptCooldowns.clear();\n terminalClosedIssueIds.clear();\n clearIssueClosedCache();\n scanInFlight = false;\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as handleOrphanProposedSpec } from "./orphan-proposed-reconciler-
|
|
1
|
+
import { n as handleOrphanProposedSpec } from "./orphan-proposed-reconciler-Boq_XaRe.js";
|
|
2
2
|
export { handleOrphanProposedSpec };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as __esmMin } from "./rolldown-runtime-DNCzzKnI.js";
|
|
2
2
|
import { Ft as resolveProjectForIssue, Ln as listProjectsSync, Nn as init_projects, Ot as init_record, jt as readIssueRecord } from "./config-yaml-DPAieL_G.js";
|
|
3
3
|
import { c as backfillAgentsSync, d as init_agents, p as listAllAgentsSync } from "./briefing-freshness-CXhZdnB8.js";
|
|
4
|
-
import { Z as listRunningAgents, t as init_agents$1 } from "./agents-
|
|
4
|
+
import { Z as listRunningAgents, t as init_agents$1 } from "./agents-C7V4odQ6.js";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
import { join } from "node:path";
|
|
7
7
|
import { readdir } from "node:fs/promises";
|
|
@@ -194,7 +194,7 @@ function isIssueClosed(issue) {
|
|
|
194
194
|
return false;
|
|
195
195
|
}
|
|
196
196
|
async function loadInFlightIssueIds(projects) {
|
|
197
|
-
const { getSharedIssueService, startSharedIssueService } = await import("./issue-service-singleton-
|
|
197
|
+
const { getSharedIssueService, startSharedIssueService } = await import("./issue-service-singleton-B8iu_TKV.js");
|
|
198
198
|
const issueService = getSharedIssueService();
|
|
199
199
|
await startSharedIssueService({ skipPolling: true });
|
|
200
200
|
const issues = issueService.getIssues({ includeCompleted: true });
|
|
@@ -340,4 +340,4 @@ __esmMin((() => {
|
|
|
340
340
|
}))();
|
|
341
341
|
export { reconstructCache, reconstructCacheAuto };
|
|
342
342
|
|
|
343
|
-
//# sourceMappingURL=reconstruct-cache-
|
|
343
|
+
//# sourceMappingURL=reconstruct-cache-C2pCltOq.js.map
|
package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js.map → reconstruct-cache-C2pCltOq.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reconstruct-cache-_tDZ2D72.js","names":[],"sources":["../../src/lib/reconstruct/enumerate-in-flight.ts","../../src/lib/reconstruct/derive-phase.ts","../../src/lib/reconstruct/reconstruct-cache.ts"],"sourcesContent":["/**\n * PAN-1920: source-of-truth enumerator for in-flight issues.\n *\n * In-flight = OPEN GitHub issue ∩ has a `feature/<id-lowercase>` workspace on disk.\n * Reads only git worktrees / the workspaces directory; never the SQLite cache.\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ProjectConfig } from '../projects.js';\n\nconst execAsync = promisify(exec);\n\nconst FEATURE_DIR_RE = /^feature-([a-z0-9-]+)$/;\n\n/**\n * List the issue IDs that are both OPEN on GitHub and have a local\n * `feature/<id-lowercase>` workspace directory.\n */\nexport async function enumerateInFlightIssuesFromSources(\n projects: ProjectConfig[],\n openIssueIds: Set<string>,\n): Promise<Set<string>> {\n const inFlight = new Set<string>();\n const seen = new Set<string>();\n\n for (const project of projects) {\n const workspaceConfig = project.workspace || {};\n const workspacesDir = join(project.path, workspaceConfig.workspaces_dir || 'workspaces');\n\n // Primary source: scan the workspaces directory for feature-* folders.\n // This works for monorepos and polyrepos alike.\n try {\n const entries = await readdir(workspacesDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const match = FEATURE_DIR_RE.exec(entry.name);\n if (!match) continue;\n const issueId = match[1]!.toUpperCase();\n if (seen.has(issueId)) continue;\n seen.add(issueId);\n if (openIssueIds.has(issueId)) {\n inFlight.add(issueId);\n }\n }\n } catch {\n // workspaces directory may not exist for this project\n }\n\n // Secondary source: git worktree list (useful when the workspace directory\n // is a git worktree but lives outside the configured workspaces_dir).\n try {\n const { stdout } = await execAsync('git worktree list --porcelain', {\n cwd: project.path,\n timeout: 10_000,\n });\n for (const line of stdout.split('\\n')) {\n const worktreeMatch = /^worktree\\s+(.+)$/.exec(line);\n if (!worktreeMatch) continue;\n const worktreePath = worktreeMatch[1]!;\n const baseName = worktreePath.split('/').pop() ?? worktreePath;\n const dirMatch = FEATURE_DIR_RE.exec(baseName);\n if (!dirMatch) continue;\n const issueId = dirMatch[1]!.toUpperCase();\n if (seen.has(issueId)) continue;\n seen.add(issueId);\n if (openIssueIds.has(issueId)) {\n inFlight.add(issueId);\n }\n }\n } catch {\n // project root may not be a git repo, or git is unavailable\n }\n }\n\n return inFlight;\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 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 const { getSharedIssueService, startSharedIssueService } = await import(\n '../../dashboard/server/services/issue-service-singleton.js'\n );\n const issueService = getSharedIssueService();\n await startSharedIssueService({ skipPolling: true });\n\n const issues = issueService.getIssues({ includeCompleted: true });\n const openIssueIds = new Set<string>();\n for (const issue of issues) {\n if (!issue || typeof issue !== 'object') continue;\n const item = issue as { identifier?: unknown; id?: unknown };\n const issueId = normalizeIssueId(item.identifier) ?? normalizeIssueId(item.id);\n if (!issueId) continue;\n if (!isIssueClosed(issue as any)) {\n openIssueIds.add(issueId);\n }\n }\n\n return enumerateInFlightIssuesFromSources(projects, openIssueIds);\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 { processed: agentsRebuilt } = 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 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-C2pCltOq.js","names":[],"sources":["../../src/lib/reconstruct/enumerate-in-flight.ts","../../src/lib/reconstruct/derive-phase.ts","../../src/lib/reconstruct/reconstruct-cache.ts"],"sourcesContent":["/**\n * PAN-1920: source-of-truth enumerator for in-flight issues.\n *\n * In-flight = OPEN GitHub issue ∩ has a `feature/<id-lowercase>` workspace on disk.\n * Reads only git worktrees / the workspaces directory; never the SQLite cache.\n */\n\nimport { readdir } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ProjectConfig } from '../projects.js';\n\nconst execAsync = promisify(exec);\n\nconst FEATURE_DIR_RE = /^feature-([a-z0-9-]+)$/;\n\n/**\n * List the issue IDs that are both OPEN on GitHub and have a local\n * `feature/<id-lowercase>` workspace directory.\n */\nexport async function enumerateInFlightIssuesFromSources(\n projects: ProjectConfig[],\n openIssueIds: Set<string>,\n): Promise<Set<string>> {\n const inFlight = new Set<string>();\n const seen = new Set<string>();\n\n for (const project of projects) {\n const workspaceConfig = project.workspace || {};\n const workspacesDir = join(project.path, workspaceConfig.workspaces_dir || 'workspaces');\n\n // Primary source: scan the workspaces directory for feature-* folders.\n // This works for monorepos and polyrepos alike.\n try {\n const entries = await readdir(workspacesDir, { withFileTypes: true });\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const match = FEATURE_DIR_RE.exec(entry.name);\n if (!match) continue;\n const issueId = match[1]!.toUpperCase();\n if (seen.has(issueId)) continue;\n seen.add(issueId);\n if (openIssueIds.has(issueId)) {\n inFlight.add(issueId);\n }\n }\n } catch {\n // workspaces directory may not exist for this project\n }\n\n // Secondary source: git worktree list (useful when the workspace directory\n // is a git worktree but lives outside the configured workspaces_dir).\n try {\n const { stdout } = await execAsync('git worktree list --porcelain', {\n cwd: project.path,\n timeout: 10_000,\n });\n for (const line of stdout.split('\\n')) {\n const worktreeMatch = /^worktree\\s+(.+)$/.exec(line);\n if (!worktreeMatch) continue;\n const worktreePath = worktreeMatch[1]!;\n const baseName = worktreePath.split('/').pop() ?? worktreePath;\n const dirMatch = FEATURE_DIR_RE.exec(baseName);\n if (!dirMatch) continue;\n const issueId = dirMatch[1]!.toUpperCase();\n if (seen.has(issueId)) continue;\n seen.add(issueId);\n if (openIssueIds.has(issueId)) {\n inFlight.add(issueId);\n }\n }\n } catch {\n // project root may not be a git repo, or git is unavailable\n }\n }\n\n return inFlight;\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 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 const { getSharedIssueService, startSharedIssueService } = await import(\n '../../dashboard/server/services/issue-service-singleton.js'\n );\n const issueService = getSharedIssueService();\n await startSharedIssueService({ skipPolling: true });\n\n const issues = issueService.getIssues({ includeCompleted: true });\n const openIssueIds = new Set<string>();\n for (const issue of issues) {\n if (!issue || typeof issue !== 'object') continue;\n const item = issue as { identifier?: unknown; id?: unknown };\n const issueId = normalizeIssueId(item.identifier) ?? normalizeIssueId(item.id);\n if (!issueId) continue;\n if (!isIssueClosed(issue as any)) {\n openIssueIds.add(issueId);\n }\n }\n\n return enumerateInFlightIssuesFromSources(projects, openIssueIds);\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 { processed: agentsRebuilt } = 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 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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"}
|
|
@@ -309,7 +309,7 @@ async function reapCompletedRemoteAgents(opts = {}) {
|
|
|
309
309
|
} catch {
|
|
310
310
|
details.push("No continue.json retrieved from VM (skipped)");
|
|
311
311
|
}
|
|
312
|
-
const { getAgentStateSync, saveAgentStateSync } = await import("./agents-
|
|
312
|
+
const { getAgentStateSync, saveAgentStateSync } = await import("./agents-C-vqmKDi.js");
|
|
313
313
|
const existing = getAgentStateSync(agentId);
|
|
314
314
|
if (existing) {
|
|
315
315
|
existing.workspace = existing.workspace || workspacePath;
|
|
@@ -328,7 +328,7 @@ async function reapCompletedRemoteAgents(opts = {}) {
|
|
|
328
328
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
329
329
|
});
|
|
330
330
|
const { createReviewArtifactsForIssue } = await import("./review-artifacts-BGc69epg.js");
|
|
331
|
-
const { setReviewStatusSync } = await import("./review-status-
|
|
331
|
+
const { setReviewStatusSync } = await import("./review-status-h7BXIA4z.js");
|
|
332
332
|
const primaryArtifact = (await Effect.runPromise(createReviewArtifactsForIssue(issueId, workspacePath))).mergeSet?.repos.find((repo) => !!repo.artifactUrl);
|
|
333
333
|
if (primaryArtifact?.artifactUrl) {
|
|
334
334
|
setReviewStatusSync(issueId, { prUrl: primaryArtifact.artifactUrl });
|
|
@@ -386,4 +386,4 @@ async function reapCompletedRemoteAgents(opts = {}) {
|
|
|
386
386
|
//#endregion
|
|
387
387
|
export { reapCompletedRemoteAgents, refreshClaudeCredentialsForActiveRemoteAgents };
|
|
388
388
|
|
|
389
|
-
//# sourceMappingURL=remote-completion-
|
|
389
|
+
//# sourceMappingURL=remote-completion-BmxD5UMQ.js.map
|
package/dist/dashboard/{remote-completion-Bklh6loJ.js.map → remote-completion-BmxD5UMQ.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-completion-Bklh6loJ.js","names":[],"sources":["../../src/lib/remote/remote-completion.ts"],"sourcesContent":["/**\n * Remote Agent Completion Handoff\n *\n * Remote (fly.io) work agents cannot run `pan done` — the host pipeline has\n * no workspace registry entry for them. Instead they finish by pushing their\n * feature branch and creating the `/workspace/.pan/REMOTE_DONE` sentinel\n * file (see the REMOTE gate in cloister/prompts/work.md).\n *\n * `reapCompletedRemoteAgents()` is the host-side half of that contract:\n * for every remote agent that has created the sentinel (or exited after\n * pushing its branch), it\n *\n * 1. verifies the feature branch exists on origin,\n * 2. materializes the standard local worktree for the branch,\n * 3. copies the remote .pan/continue.json back (best effort),\n * 4. writes a minimal local agent state.json (downstream flows resolve\n * the workspace through it),\n * 5. creates the review artifact set + review status (same entry the\n * `pan done` flow uses), writes the `completed` marker that the\n * cloister's checkCompletionMarkers() patrol consumes,\n * 6. stops the fly machine and marks the remote agent state stopped.\n *\n * Invoked via `pan admin remote reap` (and suitable for a deacon patrol).\n */\n\nimport { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { basename, join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { loadConfigSync } from '../config.js';\nimport { createFlyProviderFromConfig } from './index.js';\nimport {\n loadRemoteAgentState,\n saveRemoteAgentState,\n isRemoteAgentRunning,\n listActiveRemoteAgentStates,\n getRemoteAgentOutput,\n sendToRemoteAgent,\n} from './remote-agents.js';\nimport { resolveProjectFromIssueSync, extractTeamPrefix, findProjectByTeamSync } from '../projects.js';\nimport { createWorkspace } from '../workspace-manager.js';\nimport { PAN_DIRNAME, PAN_CONTINUE_FILENAME } from '../pan-dir/index.js';\n\nconst execAsync = promisify(exec);\nconst AGENTS_DIR = join(homedir(), '.overdeck', 'agents');\nconst REMOTE_CLAUDE_CREDENTIAL_REFRESH_INTERVAL_MS = 15 * 60 * 1000;\n\ninterface PerVmCredentialRefreshState {\n fingerprint: string | null;\n refreshedAtMs: number;\n}\n\nconst perVmCredentialRefreshState = new Map<string, PerVmCredentialRefreshState>();\n\nexport interface RemoteReapResult {\n agentId: string;\n issueId: string;\n status: 'handed-off' | 'still-running' | 'stale' | 'error';\n details: string[];\n}\n\ninterface RemoteClaudeCredentialRefreshDeps {\n listActiveRemoteAgentStates?: typeof listActiveRemoteAgentStates;\n nowMs?: () => number;\n credentialFingerprint?: () => string | null;\n loadConfig?: typeof loadConfigSync;\n createFlyProvider?: typeof createFlyProviderFromConfig;\n}\n\nfunction getHostClaudeCredentialFingerprint(): string | null {\n const credFile = join(homedir(), '.claude', '.credentials.json');\n if (!existsSync(credFile)) return null;\n try {\n const stat = statSync(credFile);\n return `${stat.mtimeMs}:${stat.size}`;\n } catch {\n return null;\n }\n}\n\nexport function resetRemoteClaudeCredentialRefreshForTests(): void {\n perVmCredentialRefreshState.clear();\n}\n\n/**\n * Proactively copy fresh host Claude credentials to active remote agents.\n *\n * Host OAuth refresh rotates refresh tokens, so a long-running Fly VM can be\n * left with an orphaned copy. The host credentials file mtime is the precise\n * trigger on Linux; platforms without that file fall back to a bounded 15 min\n * refresh cadence. The active-agent scan runs first so the common zero-remote\n * case never constructs a Fly provider or makes Fly API calls.\n *\n * Refresh state is tracked per VM (keyed by vmName), not globally. A failed\n * or newly started VM must be retried on the next patrol regardless of whether\n * another VM already succeeded this cycle.\n */\nexport async function refreshClaudeCredentialsForActiveRemoteAgents(\n deps: RemoteClaudeCredentialRefreshDeps = {},\n): Promise<string[]> {\n const activeStates = (deps.listActiveRemoteAgentStates ?? listActiveRemoteAgentStates)();\n const activeVmNames = new Set(activeStates.map((state) => state.vmName));\n\n // Drop state for VMs that are no longer active so a future machine reusing\n // the same vmName starts with a clean watermark. Do this before the empty\n // short-circuit so the Map is pruned even when no agents are currently active.\n for (const vmName of perVmCredentialRefreshState.keys()) {\n if (!activeVmNames.has(vmName)) {\n perVmCredentialRefreshState.delete(vmName);\n }\n }\n\n if (activeStates.length === 0) return [];\n\n const nowMs = (deps.nowMs ?? Date.now)();\n const currentFingerprint = (deps.credentialFingerprint ?? getHostClaudeCredentialFingerprint)();\n const config = (deps.loadConfig ?? loadConfigSync)();\n const fly = (deps.createFlyProvider ?? createFlyProviderFromConfig)(config.remote);\n const actions: string[] = [];\n\n for (const state of activeStates) {\n const last = perVmCredentialRefreshState.get(state.vmName);\n const credentialChanged = currentFingerprint !== null && currentFingerprint !== last?.fingerprint;\n const refreshDue = !last ||\n nowMs - last.refreshedAtMs >= REMOTE_CLAUDE_CREDENTIAL_REFRESH_INTERVAL_MS;\n if (!credentialChanged && !refreshDue) continue;\n\n try {\n const synced = await fly.syncClaudeCredentials(state.vmName);\n if (synced) {\n // Only advance this VM's watermark on a successful sync. A failure\n // leaves its last watermark intact so the next patrol retries it.\n perVmCredentialRefreshState.set(state.vmName, {\n fingerprint: currentFingerprint,\n refreshedAtMs: nowMs,\n });\n }\n actions.push(\n synced\n ? `Remote credentials refreshed for ${state.issueId.toUpperCase()} on ${state.vmName}`\n : `Remote credentials refresh skipped for ${state.issueId.toUpperCase()} on ${state.vmName} (no host credentials)`,\n );\n } catch (err: any) {\n actions.push(`Remote credentials refresh failed for ${state.issueId.toUpperCase()} on ${state.vmName}: ${err.message}`);\n }\n }\n\n return actions;\n}\n\n/** List agent IDs that have a remote-state.json in running/starting state. */\nfunction listActiveRemoteAgents(): string[] {\n return listActiveRemoteAgentStates().map((state) => state.id);\n}\n\nasync function branchExistsOnOrigin(projectRoot: string, branch: string): Promise<boolean> {\n try {\n const { stdout } = await execAsync(`git ls-remote --heads origin ${branch}`, { cwd: projectRoot });\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Preserve the remote session's observable state into the local agent dir\n * BEFORE the machine goes away: the final tmux pane (remote-output.log) and\n * the Claude Code session JSONLs (remote-sessions/). The JSONLs are the only\n * record of the remote conversation — destroying a machine without them\n * loses the transcript irrecoverably (learned on the first migrated issue).\n */\nasync function captureRemoteForensics(agentId: string, vmName: string, details: string[]): Promise<{ paneCaptured: boolean }> {\n const agentDir = join(AGENTS_DIR, agentId);\n mkdirSync(agentDir, { recursive: true });\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n\n let paneCaptured = false;\n try {\n const output = await getRemoteAgentOutput(agentId, vmName, 2000);\n if (output.trim()) {\n writeFileSync(join(agentDir, 'remote-output.log'), output);\n details.push('Captured final remote pane to remote-output.log');\n paneCaptured = true;\n }\n } catch {\n details.push('Could not capture remote pane');\n }\n\n // The Machines exec API runs with HOME=/, so Claude's session JSONLs live\n // under /.claude/projects — but the launcher may export HOME=/root, so\n // check both. Surface failures: an empty copy means the only transcript of\n // the agent's work is about to be lost with the rootfs.\n try {\n const list = await Effect.runPromise(\n fly.ssh(vmName, 'ls /.claude/projects/*/*.jsonl /root/.claude/projects/*/*.jsonl 2>/dev/null')\n );\n const files = list.stdout.trim().split('\\n').filter(Boolean);\n if (files.length > 0) {\n const destDir = join(agentDir, 'remote-sessions');\n mkdirSync(destDir, { recursive: true });\n let copied = 0;\n for (const remotePath of files) {\n try {\n await fly.copyFromVm(vmName, remotePath, join(destDir, basename(remotePath)));\n copied++;\n } catch {\n details.push(`Failed to copy ${remotePath}`);\n }\n }\n details.push(`Copied ${copied}/${files.length} session JSONL(s) to remote-sessions/`);\n } else {\n details.push('No session JSONLs found under /.claude or /root/.claude');\n }\n } catch (err: any) {\n details.push(`Could not copy session JSONLs: ${err.message}`);\n }\n return { paneCaptured };\n}\n\n/**\n * Commit and push whatever work is sitting in /workspace before the machine\n * is stopped. Fly machine rootfs is ephemeral — a stop is only \"preserved\n * for inspection\" until the next start, which resets the filesystem from the\n * image. Unpushed work on a stopped machine is one restart away from gone\n * (lost live on PAN-1762: ~2h of agent work).\n */\nasync function salvageRemoteWork(vmName: string, branch: string, details: string[]): Promise<void> {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n try {\n const result = await Effect.runPromise(fly.ssh(\n vmName,\n `cd /workspace && git add -A && (git diff --cached --quiet || git commit -m \"wip(remote): salvage checkpoint before machine stop\") && git push origin ${branch} 2>&1 | tail -2`\n ));\n if (result.exitCode === 0) {\n details.push(`Salvaged workspace state to origin/${branch}`);\n } else {\n details.push(`Salvage push failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);\n }\n } catch (err: any) {\n details.push(`Salvage attempt failed: ${err.message}`);\n }\n}\n\n/**\n * Long remote runs outlive the synced OAuth token: when the host's Claude\n * refreshes its token, the VM's copy is orphaned (refresh tokens rotate),\n * and the agent stalls on `API Error: 401 · Please run /login` until\n * credentials are re-synced (observed live on PAN-1762 — ~1h stalled, then\n * mistaken for a crash). Detect the stall from the pane tail, re-sync\n * credentials from the host, and nudge the session so it retries.\n */\nasync function healStaleRemoteAuth(agentId: string, vmName: string, details: string[]): Promise<void> {\n try {\n const tail = await getRemoteAgentOutput(agentId, vmName, 40);\n if (!/Invalid authentication credentials|Please run \\/login/i.test(tail)) return;\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n const synced = await fly.syncClaudeCredentials(vmName);\n if (!synced) {\n details.push('401 stall detected but credential re-sync failed');\n return;\n }\n await sendToRemoteAgent(agentId, vmName, 'Your API credentials were stale and have been re-synced. Please continue from where you left off.');\n details.push('401 stall detected — re-synced Claude credentials and nudged the session');\n } catch (err: any) {\n details.push(`401 heal attempt failed: ${err.message}`);\n }\n}\n\n/**\n * Reap completed remote agents into the review pipeline.\n * Pass issueId to target one agent; otherwise scans all active remote agents.\n */\nexport async function reapCompletedRemoteAgents(opts: { issueId?: string; dryRun?: boolean } = {}): Promise<RemoteReapResult[]> {\n const agentIds = opts.issueId\n ? [`agent-${opts.issueId.toLowerCase()}`]\n : listActiveRemoteAgents();\n\n const results: RemoteReapResult[] = [];\n\n for (const agentId of agentIds) {\n const details: string[] = [];\n const remoteState = loadRemoteAgentState(agentId);\n if (!remoteState || remoteState.location !== 'remote') {\n results.push({ agentId, issueId: opts.issueId ?? 'unknown', status: 'stale', details: ['No remote-state.json'] });\n continue;\n }\n const issueId = remoteState.issueId.toUpperCase();\n const vmName = remoteState.vmName;\n\n try {\n // 1. Completion detection: the /workspace/.pan/REMOTE_DONE sentinel\n // file (created by the agent per the REMOTE prompt contract), or the\n // session having exited. Do NOT grep session output for the marker —\n // the prompt's own instruction text contains the literal string, so a\n // mid-work agent's pane matches it (false positive observed live on\n // the first migrated issue).\n let sessionAlive = false;\n let sentinelSeen = false;\n try {\n // A single has-session over the Machines exec API false-negatives on\n // transient exec failures/429s — one flake here stopped a healthy\n // mid-work machine live (PAN-1762). Any success across retries means\n // alive; only consistent failure proceeds to the crash path.\n for (let attempt = 0; attempt < 3 && !sessionAlive; attempt++) {\n if (attempt > 0) await new Promise((r) => setTimeout(r, 5000));\n sessionAlive = await isRemoteAgentRunning(agentId, vmName);\n }\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n const check = await Effect.runPromise(\n fly.ssh(vmName, '[ -f /workspace/.pan/REMOTE_DONE ] && echo present')\n );\n sentinelSeen = check.stdout.trim() === 'present';\n } catch (err: any) {\n details.push(`VM unreachable (${err.message}) — falling back to branch check`);\n }\n\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n const resolved = resolveProjectFromIssueSync(issueId, []);\n const projectRoot = projectConfig?.path ?? resolved?.projectPath;\n if (!projectRoot) {\n throw new Error(`Cannot resolve project for ${issueId}`);\n }\n\n const branch = `feature/${issueId.toLowerCase()}`;\n const pushed = await branchExistsOnOrigin(projectRoot, branch);\n\n if (sessionAlive && !sentinelSeen) {\n await healStaleRemoteAuth(agentId, vmName, details);\n results.push({ agentId, issueId, status: 'still-running', details });\n continue;\n }\n\n // Only the sentinel hands off. A dead session WITHOUT the sentinel is a\n // crash, not a completion — migrated issues always have a pushed branch,\n // so \"session ended + branch pushed\" would false-positive every crash\n // straight into review (observed live on the first migrated issue).\n // Park as error with forensics; the machine stays (stopped) for\n // inspection and manual recovery via `pan admin remote reap --issue`.\n if (!sentinelSeen || !pushed) {\n const reason = !sentinelSeen\n ? 'session ended without the REMOTE_DONE sentinel — treating as crash, not completion'\n : `sentinel present but ${branch} is not on origin`;\n details.push(reason);\n const { paneCaptured } = await captureRemoteForensics(agentId, vmName, details);\n if (!sessionAlive && paneCaptured) {\n // Contradiction: has-session said dead, but capture-pane just read\n // the live pane through the same tmux server. The session is\n // reachable — the alive check flaked. Do NOT stop the machine.\n details.push('Pane capture succeeded despite dead has-session — alive-check flake, leaving agent running');\n results.push({ agentId, issueId, status: 'still-running', details });\n continue;\n }\n // Salvage before stopping: rootfs does not survive a stop→start\n // cycle, so this is the last chance to preserve unpushed work.\n await salvageRemoteWork(vmName, branch, details);\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n await Effect.runPromise(fly.stopVm(vmName));\n details.push(`Stopped machine ${vmName} (rootfs resets on next start — salvage above is the durable copy)`);\n } catch (err: any) {\n details.push(`Warning: could not stop machine: ${err.message}`);\n }\n saveRemoteAgentState({ ...remoteState, status: 'error', lastActivity: new Date().toISOString() });\n results.push({ agentId, issueId, status: 'error', details });\n continue;\n }\n details.push(`REMOTE_DONE sentinel present for ${issueId}`);\n\n if (opts.dryRun) {\n details.push('Dry run — would hand off to review');\n results.push({ agentId, issueId, status: 'handed-off', details });\n continue;\n }\n\n // 2. Materialize the standard local worktree (tracks the pushed branch).\n const workspacePath = join(projectRoot, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspacePath)) {\n if (!projectConfig) throw new Error(`No project config for ${issueId}; cannot create workspace`);\n const wsResult = await Effect.runPromise(createWorkspace({\n projectConfig,\n featureName: issueId.toLowerCase(),\n startDocker: false,\n }));\n if (!wsResult.success) {\n throw new Error(`Failed to create local worktree: ${wsResult.errors.join('; ')}`);\n }\n details.push(`Materialized local worktree at ${workspacePath}`);\n } else {\n // Worktree exists (e.g. migrated issue with --keep) — fast-forward it.\n await execAsync(`git fetch origin ${branch}`, { cwd: workspacePath });\n await execAsync(`git merge --ff-only origin/${branch}`, { cwd: workspacePath }).catch(() => {\n details.push('Local worktree diverged from origin — left as-is');\n });\n }\n\n // 3. Copy continue.json back from the VM (best effort — session record).\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n mkdirSync(join(workspacePath, PAN_DIRNAME), { recursive: true });\n await fly.copyFromVm(\n vmName,\n `/workspace/${PAN_DIRNAME}/${PAN_CONTINUE_FILENAME}`,\n join(workspacePath, PAN_DIRNAME, PAN_CONTINUE_FILENAME),\n );\n details.push('Copied continue.json back from VM');\n } catch {\n details.push('No continue.json retrieved from VM (skipped)');\n }\n\n // 4. Minimal local agent state so downstream flows resolve the workspace.\n const { getAgentStateSync, saveAgentStateSync } = await import('../agents.js');\n const existing = getAgentStateSync(agentId);\n if (existing) {\n existing.workspace = existing.workspace || workspacePath;\n existing.status = 'stopped';\n existing.lastActivity = new Date().toISOString();\n saveAgentStateSync(existing);\n } else {\n saveAgentStateSync({\n id: agentId,\n issueId,\n workspace: workspacePath,\n role: 'work',\n harness: 'claude-code',\n model: remoteState.model,\n status: 'stopped',\n startedAt: remoteState.startedAt,\n lastActivity: new Date().toISOString(),\n });\n }\n\n // 5. Review artifacts + completed marker — same entries pan done uses.\n const { createReviewArtifactsForIssue } = await import('../review-artifacts.js');\n const { setReviewStatusSync } = await import('../review-status.js');\n const artifactResult = await Effect.runPromise(createReviewArtifactsForIssue(issueId, workspacePath));\n const primaryArtifact = artifactResult.mergeSet?.repos.find((repo) => !!repo.artifactUrl);\n if (primaryArtifact?.artifactUrl) {\n setReviewStatusSync(issueId, { prUrl: primaryArtifact.artifactUrl });\n details.push(`Review artifact: ${primaryArtifact.artifactUrl}`);\n }\n\n mkdirSync(join(AGENTS_DIR, agentId), { recursive: true });\n const processedMarker = join(AGENTS_DIR, agentId, 'completed.processed');\n if (existsSync(processedMarker)) {\n try { unlinkSync(processedMarker); } catch { /* best effort */ }\n }\n writeFileSync(join(AGENTS_DIR, agentId, 'completed'), JSON.stringify({\n timestamp: new Date().toISOString(),\n trackerUpdated: false,\n comment: `Remote agent completed on ${vmName} (reaped)`,\n }));\n details.push('Wrote completion marker for cloister pickup');\n\n // 5.5. Preserve the transcript and final pane before the machine goes.\n await captureRemoteForensics(agentId, vmName, details);\n\n // 6. Retire the remote workspace entirely: from review onward the\n // pipeline runs against the local worktree, and lingering remote\n // metadata makes getWorkspaceInfoForIssue() route review operations\n // at the (now-idle) VM. Destroying the machine also closes the\n // run-forever cost leak.\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n if (sessionAlive) {\n const { killRemoteAgent } = await import('./remote-agents.js');\n await killRemoteAgent(agentId, vmName).catch(() => undefined);\n }\n await Effect.runPromise(fly.deleteVm(vmName));\n details.push(`Destroyed machine ${vmName}`);\n } catch (err: any) {\n details.push(`Warning: could not destroy machine: ${err.message}`);\n }\n const { deleteWorkspaceMetadataSync } = await import('./workspace-metadata.js');\n deleteWorkspaceMetadataSync(issueId);\n details.push('Removed remote workspace metadata (pipeline is local from here)');\n saveRemoteAgentState({ ...remoteState, status: 'stopped', lastActivity: new Date().toISOString() });\n\n results.push({ agentId, issueId, status: 'handed-off', details });\n } catch (err: any) {\n details.push(err.message);\n results.push({ agentId, issueId, status: 'error', details });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"remote-completion-BmxD5UMQ.js","names":[],"sources":["../../src/lib/remote/remote-completion.ts"],"sourcesContent":["/**\n * Remote Agent Completion Handoff\n *\n * Remote (fly.io) work agents cannot run `pan done` — the host pipeline has\n * no workspace registry entry for them. Instead they finish by pushing their\n * feature branch and creating the `/workspace/.pan/REMOTE_DONE` sentinel\n * file (see the REMOTE gate in cloister/prompts/work.md).\n *\n * `reapCompletedRemoteAgents()` is the host-side half of that contract:\n * for every remote agent that has created the sentinel (or exited after\n * pushing its branch), it\n *\n * 1. verifies the feature branch exists on origin,\n * 2. materializes the standard local worktree for the branch,\n * 3. copies the remote .pan/continue.json back (best effort),\n * 4. writes a minimal local agent state.json (downstream flows resolve\n * the workspace through it),\n * 5. creates the review artifact set + review status (same entry the\n * `pan done` flow uses), writes the `completed` marker that the\n * cloister's checkCompletionMarkers() patrol consumes,\n * 6. stops the fly machine and marks the remote agent state stopped.\n *\n * Invoked via `pan admin remote reap` (and suitable for a deacon patrol).\n */\n\nimport { existsSync, mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from 'fs';\nimport { homedir } from 'os';\nimport { basename, join } from 'path';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { loadConfigSync } from '../config.js';\nimport { createFlyProviderFromConfig } from './index.js';\nimport {\n loadRemoteAgentState,\n saveRemoteAgentState,\n isRemoteAgentRunning,\n listActiveRemoteAgentStates,\n getRemoteAgentOutput,\n sendToRemoteAgent,\n} from './remote-agents.js';\nimport { resolveProjectFromIssueSync, extractTeamPrefix, findProjectByTeamSync } from '../projects.js';\nimport { createWorkspace } from '../workspace-manager.js';\nimport { PAN_DIRNAME, PAN_CONTINUE_FILENAME } from '../pan-dir/index.js';\n\nconst execAsync = promisify(exec);\nconst AGENTS_DIR = join(homedir(), '.overdeck', 'agents');\nconst REMOTE_CLAUDE_CREDENTIAL_REFRESH_INTERVAL_MS = 15 * 60 * 1000;\n\ninterface PerVmCredentialRefreshState {\n fingerprint: string | null;\n refreshedAtMs: number;\n}\n\nconst perVmCredentialRefreshState = new Map<string, PerVmCredentialRefreshState>();\n\nexport interface RemoteReapResult {\n agentId: string;\n issueId: string;\n status: 'handed-off' | 'still-running' | 'stale' | 'error';\n details: string[];\n}\n\ninterface RemoteClaudeCredentialRefreshDeps {\n listActiveRemoteAgentStates?: typeof listActiveRemoteAgentStates;\n nowMs?: () => number;\n credentialFingerprint?: () => string | null;\n loadConfig?: typeof loadConfigSync;\n createFlyProvider?: typeof createFlyProviderFromConfig;\n}\n\nfunction getHostClaudeCredentialFingerprint(): string | null {\n const credFile = join(homedir(), '.claude', '.credentials.json');\n if (!existsSync(credFile)) return null;\n try {\n const stat = statSync(credFile);\n return `${stat.mtimeMs}:${stat.size}`;\n } catch {\n return null;\n }\n}\n\nexport function resetRemoteClaudeCredentialRefreshForTests(): void {\n perVmCredentialRefreshState.clear();\n}\n\n/**\n * Proactively copy fresh host Claude credentials to active remote agents.\n *\n * Host OAuth refresh rotates refresh tokens, so a long-running Fly VM can be\n * left with an orphaned copy. The host credentials file mtime is the precise\n * trigger on Linux; platforms without that file fall back to a bounded 15 min\n * refresh cadence. The active-agent scan runs first so the common zero-remote\n * case never constructs a Fly provider or makes Fly API calls.\n *\n * Refresh state is tracked per VM (keyed by vmName), not globally. A failed\n * or newly started VM must be retried on the next patrol regardless of whether\n * another VM already succeeded this cycle.\n */\nexport async function refreshClaudeCredentialsForActiveRemoteAgents(\n deps: RemoteClaudeCredentialRefreshDeps = {},\n): Promise<string[]> {\n const activeStates = (deps.listActiveRemoteAgentStates ?? listActiveRemoteAgentStates)();\n const activeVmNames = new Set(activeStates.map((state) => state.vmName));\n\n // Drop state for VMs that are no longer active so a future machine reusing\n // the same vmName starts with a clean watermark. Do this before the empty\n // short-circuit so the Map is pruned even when no agents are currently active.\n for (const vmName of perVmCredentialRefreshState.keys()) {\n if (!activeVmNames.has(vmName)) {\n perVmCredentialRefreshState.delete(vmName);\n }\n }\n\n if (activeStates.length === 0) return [];\n\n const nowMs = (deps.nowMs ?? Date.now)();\n const currentFingerprint = (deps.credentialFingerprint ?? getHostClaudeCredentialFingerprint)();\n const config = (deps.loadConfig ?? loadConfigSync)();\n const fly = (deps.createFlyProvider ?? createFlyProviderFromConfig)(config.remote);\n const actions: string[] = [];\n\n for (const state of activeStates) {\n const last = perVmCredentialRefreshState.get(state.vmName);\n const credentialChanged = currentFingerprint !== null && currentFingerprint !== last?.fingerprint;\n const refreshDue = !last ||\n nowMs - last.refreshedAtMs >= REMOTE_CLAUDE_CREDENTIAL_REFRESH_INTERVAL_MS;\n if (!credentialChanged && !refreshDue) continue;\n\n try {\n const synced = await fly.syncClaudeCredentials(state.vmName);\n if (synced) {\n // Only advance this VM's watermark on a successful sync. A failure\n // leaves its last watermark intact so the next patrol retries it.\n perVmCredentialRefreshState.set(state.vmName, {\n fingerprint: currentFingerprint,\n refreshedAtMs: nowMs,\n });\n }\n actions.push(\n synced\n ? `Remote credentials refreshed for ${state.issueId.toUpperCase()} on ${state.vmName}`\n : `Remote credentials refresh skipped for ${state.issueId.toUpperCase()} on ${state.vmName} (no host credentials)`,\n );\n } catch (err: any) {\n actions.push(`Remote credentials refresh failed for ${state.issueId.toUpperCase()} on ${state.vmName}: ${err.message}`);\n }\n }\n\n return actions;\n}\n\n/** List agent IDs that have a remote-state.json in running/starting state. */\nfunction listActiveRemoteAgents(): string[] {\n return listActiveRemoteAgentStates().map((state) => state.id);\n}\n\nasync function branchExistsOnOrigin(projectRoot: string, branch: string): Promise<boolean> {\n try {\n const { stdout } = await execAsync(`git ls-remote --heads origin ${branch}`, { cwd: projectRoot });\n return stdout.trim().length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Preserve the remote session's observable state into the local agent dir\n * BEFORE the machine goes away: the final tmux pane (remote-output.log) and\n * the Claude Code session JSONLs (remote-sessions/). The JSONLs are the only\n * record of the remote conversation — destroying a machine without them\n * loses the transcript irrecoverably (learned on the first migrated issue).\n */\nasync function captureRemoteForensics(agentId: string, vmName: string, details: string[]): Promise<{ paneCaptured: boolean }> {\n const agentDir = join(AGENTS_DIR, agentId);\n mkdirSync(agentDir, { recursive: true });\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n\n let paneCaptured = false;\n try {\n const output = await getRemoteAgentOutput(agentId, vmName, 2000);\n if (output.trim()) {\n writeFileSync(join(agentDir, 'remote-output.log'), output);\n details.push('Captured final remote pane to remote-output.log');\n paneCaptured = true;\n }\n } catch {\n details.push('Could not capture remote pane');\n }\n\n // The Machines exec API runs with HOME=/, so Claude's session JSONLs live\n // under /.claude/projects — but the launcher may export HOME=/root, so\n // check both. Surface failures: an empty copy means the only transcript of\n // the agent's work is about to be lost with the rootfs.\n try {\n const list = await Effect.runPromise(\n fly.ssh(vmName, 'ls /.claude/projects/*/*.jsonl /root/.claude/projects/*/*.jsonl 2>/dev/null')\n );\n const files = list.stdout.trim().split('\\n').filter(Boolean);\n if (files.length > 0) {\n const destDir = join(agentDir, 'remote-sessions');\n mkdirSync(destDir, { recursive: true });\n let copied = 0;\n for (const remotePath of files) {\n try {\n await fly.copyFromVm(vmName, remotePath, join(destDir, basename(remotePath)));\n copied++;\n } catch {\n details.push(`Failed to copy ${remotePath}`);\n }\n }\n details.push(`Copied ${copied}/${files.length} session JSONL(s) to remote-sessions/`);\n } else {\n details.push('No session JSONLs found under /.claude or /root/.claude');\n }\n } catch (err: any) {\n details.push(`Could not copy session JSONLs: ${err.message}`);\n }\n return { paneCaptured };\n}\n\n/**\n * Commit and push whatever work is sitting in /workspace before the machine\n * is stopped. Fly machine rootfs is ephemeral — a stop is only \"preserved\n * for inspection\" until the next start, which resets the filesystem from the\n * image. Unpushed work on a stopped machine is one restart away from gone\n * (lost live on PAN-1762: ~2h of agent work).\n */\nasync function salvageRemoteWork(vmName: string, branch: string, details: string[]): Promise<void> {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n try {\n const result = await Effect.runPromise(fly.ssh(\n vmName,\n `cd /workspace && git add -A && (git diff --cached --quiet || git commit -m \"wip(remote): salvage checkpoint before machine stop\") && git push origin ${branch} 2>&1 | tail -2`\n ));\n if (result.exitCode === 0) {\n details.push(`Salvaged workspace state to origin/${branch}`);\n } else {\n details.push(`Salvage push failed (exit ${result.exitCode}): ${result.stderr || result.stdout}`);\n }\n } catch (err: any) {\n details.push(`Salvage attempt failed: ${err.message}`);\n }\n}\n\n/**\n * Long remote runs outlive the synced OAuth token: when the host's Claude\n * refreshes its token, the VM's copy is orphaned (refresh tokens rotate),\n * and the agent stalls on `API Error: 401 · Please run /login` until\n * credentials are re-synced (observed live on PAN-1762 — ~1h stalled, then\n * mistaken for a crash). Detect the stall from the pane tail, re-sync\n * credentials from the host, and nudge the session so it retries.\n */\nasync function healStaleRemoteAuth(agentId: string, vmName: string, details: string[]): Promise<void> {\n try {\n const tail = await getRemoteAgentOutput(agentId, vmName, 40);\n if (!/Invalid authentication credentials|Please run \\/login/i.test(tail)) return;\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n const synced = await fly.syncClaudeCredentials(vmName);\n if (!synced) {\n details.push('401 stall detected but credential re-sync failed');\n return;\n }\n await sendToRemoteAgent(agentId, vmName, 'Your API credentials were stale and have been re-synced. Please continue from where you left off.');\n details.push('401 stall detected — re-synced Claude credentials and nudged the session');\n } catch (err: any) {\n details.push(`401 heal attempt failed: ${err.message}`);\n }\n}\n\n/**\n * Reap completed remote agents into the review pipeline.\n * Pass issueId to target one agent; otherwise scans all active remote agents.\n */\nexport async function reapCompletedRemoteAgents(opts: { issueId?: string; dryRun?: boolean } = {}): Promise<RemoteReapResult[]> {\n const agentIds = opts.issueId\n ? [`agent-${opts.issueId.toLowerCase()}`]\n : listActiveRemoteAgents();\n\n const results: RemoteReapResult[] = [];\n\n for (const agentId of agentIds) {\n const details: string[] = [];\n const remoteState = loadRemoteAgentState(agentId);\n if (!remoteState || remoteState.location !== 'remote') {\n results.push({ agentId, issueId: opts.issueId ?? 'unknown', status: 'stale', details: ['No remote-state.json'] });\n continue;\n }\n const issueId = remoteState.issueId.toUpperCase();\n const vmName = remoteState.vmName;\n\n try {\n // 1. Completion detection: the /workspace/.pan/REMOTE_DONE sentinel\n // file (created by the agent per the REMOTE prompt contract), or the\n // session having exited. Do NOT grep session output for the marker —\n // the prompt's own instruction text contains the literal string, so a\n // mid-work agent's pane matches it (false positive observed live on\n // the first migrated issue).\n let sessionAlive = false;\n let sentinelSeen = false;\n try {\n // A single has-session over the Machines exec API false-negatives on\n // transient exec failures/429s — one flake here stopped a healthy\n // mid-work machine live (PAN-1762). Any success across retries means\n // alive; only consistent failure proceeds to the crash path.\n for (let attempt = 0; attempt < 3 && !sessionAlive; attempt++) {\n if (attempt > 0) await new Promise((r) => setTimeout(r, 5000));\n sessionAlive = await isRemoteAgentRunning(agentId, vmName);\n }\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n const check = await Effect.runPromise(\n fly.ssh(vmName, '[ -f /workspace/.pan/REMOTE_DONE ] && echo present')\n );\n sentinelSeen = check.stdout.trim() === 'present';\n } catch (err: any) {\n details.push(`VM unreachable (${err.message}) — falling back to branch check`);\n }\n\n const teamPrefix = extractTeamPrefix(issueId);\n const projectConfig = teamPrefix ? findProjectByTeamSync(teamPrefix) : null;\n const resolved = resolveProjectFromIssueSync(issueId, []);\n const projectRoot = projectConfig?.path ?? resolved?.projectPath;\n if (!projectRoot) {\n throw new Error(`Cannot resolve project for ${issueId}`);\n }\n\n const branch = `feature/${issueId.toLowerCase()}`;\n const pushed = await branchExistsOnOrigin(projectRoot, branch);\n\n if (sessionAlive && !sentinelSeen) {\n await healStaleRemoteAuth(agentId, vmName, details);\n results.push({ agentId, issueId, status: 'still-running', details });\n continue;\n }\n\n // Only the sentinel hands off. A dead session WITHOUT the sentinel is a\n // crash, not a completion — migrated issues always have a pushed branch,\n // so \"session ended + branch pushed\" would false-positive every crash\n // straight into review (observed live on the first migrated issue).\n // Park as error with forensics; the machine stays (stopped) for\n // inspection and manual recovery via `pan admin remote reap --issue`.\n if (!sentinelSeen || !pushed) {\n const reason = !sentinelSeen\n ? 'session ended without the REMOTE_DONE sentinel — treating as crash, not completion'\n : `sentinel present but ${branch} is not on origin`;\n details.push(reason);\n const { paneCaptured } = await captureRemoteForensics(agentId, vmName, details);\n if (!sessionAlive && paneCaptured) {\n // Contradiction: has-session said dead, but capture-pane just read\n // the live pane through the same tmux server. The session is\n // reachable — the alive check flaked. Do NOT stop the machine.\n details.push('Pane capture succeeded despite dead has-session — alive-check flake, leaving agent running');\n results.push({ agentId, issueId, status: 'still-running', details });\n continue;\n }\n // Salvage before stopping: rootfs does not survive a stop→start\n // cycle, so this is the last chance to preserve unpushed work.\n await salvageRemoteWork(vmName, branch, details);\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n await Effect.runPromise(fly.stopVm(vmName));\n details.push(`Stopped machine ${vmName} (rootfs resets on next start — salvage above is the durable copy)`);\n } catch (err: any) {\n details.push(`Warning: could not stop machine: ${err.message}`);\n }\n saveRemoteAgentState({ ...remoteState, status: 'error', lastActivity: new Date().toISOString() });\n results.push({ agentId, issueId, status: 'error', details });\n continue;\n }\n details.push(`REMOTE_DONE sentinel present for ${issueId}`);\n\n if (opts.dryRun) {\n details.push('Dry run — would hand off to review');\n results.push({ agentId, issueId, status: 'handed-off', details });\n continue;\n }\n\n // 2. Materialize the standard local worktree (tracks the pushed branch).\n const workspacePath = join(projectRoot, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspacePath)) {\n if (!projectConfig) throw new Error(`No project config for ${issueId}; cannot create workspace`);\n const wsResult = await Effect.runPromise(createWorkspace({\n projectConfig,\n featureName: issueId.toLowerCase(),\n startDocker: false,\n }));\n if (!wsResult.success) {\n throw new Error(`Failed to create local worktree: ${wsResult.errors.join('; ')}`);\n }\n details.push(`Materialized local worktree at ${workspacePath}`);\n } else {\n // Worktree exists (e.g. migrated issue with --keep) — fast-forward it.\n await execAsync(`git fetch origin ${branch}`, { cwd: workspacePath });\n await execAsync(`git merge --ff-only origin/${branch}`, { cwd: workspacePath }).catch(() => {\n details.push('Local worktree diverged from origin — left as-is');\n });\n }\n\n // 3. Copy continue.json back from the VM (best effort — session record).\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n mkdirSync(join(workspacePath, PAN_DIRNAME), { recursive: true });\n await fly.copyFromVm(\n vmName,\n `/workspace/${PAN_DIRNAME}/${PAN_CONTINUE_FILENAME}`,\n join(workspacePath, PAN_DIRNAME, PAN_CONTINUE_FILENAME),\n );\n details.push('Copied continue.json back from VM');\n } catch {\n details.push('No continue.json retrieved from VM (skipped)');\n }\n\n // 4. Minimal local agent state so downstream flows resolve the workspace.\n const { getAgentStateSync, saveAgentStateSync } = await import('../agents.js');\n const existing = getAgentStateSync(agentId);\n if (existing) {\n existing.workspace = existing.workspace || workspacePath;\n existing.status = 'stopped';\n existing.lastActivity = new Date().toISOString();\n saveAgentStateSync(existing);\n } else {\n saveAgentStateSync({\n id: agentId,\n issueId,\n workspace: workspacePath,\n role: 'work',\n harness: 'claude-code',\n model: remoteState.model,\n status: 'stopped',\n startedAt: remoteState.startedAt,\n lastActivity: new Date().toISOString(),\n });\n }\n\n // 5. Review artifacts + completed marker — same entries pan done uses.\n const { createReviewArtifactsForIssue } = await import('../review-artifacts.js');\n const { setReviewStatusSync } = await import('../review-status.js');\n const artifactResult = await Effect.runPromise(createReviewArtifactsForIssue(issueId, workspacePath));\n const primaryArtifact = artifactResult.mergeSet?.repos.find((repo) => !!repo.artifactUrl);\n if (primaryArtifact?.artifactUrl) {\n setReviewStatusSync(issueId, { prUrl: primaryArtifact.artifactUrl });\n details.push(`Review artifact: ${primaryArtifact.artifactUrl}`);\n }\n\n mkdirSync(join(AGENTS_DIR, agentId), { recursive: true });\n const processedMarker = join(AGENTS_DIR, agentId, 'completed.processed');\n if (existsSync(processedMarker)) {\n try { unlinkSync(processedMarker); } catch { /* best effort */ }\n }\n writeFileSync(join(AGENTS_DIR, agentId, 'completed'), JSON.stringify({\n timestamp: new Date().toISOString(),\n trackerUpdated: false,\n comment: `Remote agent completed on ${vmName} (reaped)`,\n }));\n details.push('Wrote completion marker for cloister pickup');\n\n // 5.5. Preserve the transcript and final pane before the machine goes.\n await captureRemoteForensics(agentId, vmName, details);\n\n // 6. Retire the remote workspace entirely: from review onward the\n // pipeline runs against the local worktree, and lingering remote\n // metadata makes getWorkspaceInfoForIssue() route review operations\n // at the (now-idle) VM. Destroying the machine also closes the\n // run-forever cost leak.\n try {\n const config = loadConfigSync();\n const fly = createFlyProviderFromConfig(config.remote);\n if (sessionAlive) {\n const { killRemoteAgent } = await import('./remote-agents.js');\n await killRemoteAgent(agentId, vmName).catch(() => undefined);\n }\n await Effect.runPromise(fly.deleteVm(vmName));\n details.push(`Destroyed machine ${vmName}`);\n } catch (err: any) {\n details.push(`Warning: could not destroy machine: ${err.message}`);\n }\n const { deleteWorkspaceMetadataSync } = await import('./workspace-metadata.js');\n deleteWorkspaceMetadataSync(issueId);\n details.push('Removed remote workspace metadata (pipeline is local from here)');\n saveRemoteAgentState({ ...remoteState, status: 'stopped', lastActivity: new Date().toISOString() });\n\n results.push({ agentId, issueId, status: 'handed-off', details });\n } catch (err: any) {\n details.push(err.message);\n results.push({ agentId, issueId, status: 'error', details });\n }\n }\n\n return results;\n}\n"],"mappings":"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"}
|