@overdeck/core 0.50.3 → 0.50.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents-DNMRPC8n.js.map +1 -1
- package/dist/{build-info-BJuEVWOh.js → build-info-ioRdOmqT.js} +3 -3
- package/dist/{build-info-BJuEVWOh.js.map → build-info-ioRdOmqT.js.map} +1 -1
- package/dist/build-info-kMMitDOP.js +2 -0
- package/dist/cli/index.js +4 -4
- package/dist/conversation-compaction-GFfO5UZL.js.map +1 -1
- package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js → advancing-selfheal-Dp5_Gai3.js} +3 -3
- package/dist/dashboard/{advancing-selfheal-Bq8V0QUa.js.map → advancing-selfheal-Dp5_Gai3.js.map} +1 -1
- package/dist/dashboard/{agent-gc-Coz8zQt_.js → agent-gc-JzoSs5Pi.js} +3 -3
- package/dist/dashboard/{agent-gc-Coz8zQt_.js.map → agent-gc-JzoSs5Pi.js.map} +1 -1
- package/dist/dashboard/{agent-idle-CAwB-K6M.js → agent-idle-D-Qy5BN_.js} +2 -2
- package/dist/dashboard/{agent-idle-CAwB-K6M.js.map → agent-idle-D-Qy5BN_.js.map} +1 -1
- package/dist/dashboard/{agent-state-Ci6PhjOE.js → agent-state-B196fj1h.js} +38 -16
- package/dist/dashboard/agent-state-B196fj1h.js.map +1 -0
- package/dist/dashboard/{agent-state-CbUEwUEQ.js → agent-state-IpWnw8lv.js} +1 -1
- package/dist/dashboard/{agent-state-service-D6IVrX3B.js → agent-state-service-CCo5-9DR.js} +3 -3
- package/dist/dashboard/{agent-state-service-D6IVrX3B.js.map → agent-state-service-CCo5-9DR.js.map} +1 -1
- package/dist/dashboard/{agent-state-service-DcN1j6eK.js → agent-state-service-D_F3VZ23.js} +1 -1
- package/dist/dashboard/{agents-BVvL4o_4.js → agents-CkGhnAmu.js} +1 -1
- package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js → backlog-auto-trigger-B5Z2zBtC.js} +2 -2
- package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js.map → backlog-auto-trigger-B5Z2zBtC.js.map} +1 -1
- package/dist/dashboard/build-info-BTjOB0Xr.js +2 -0
- package/dist/dashboard/{build-info-Cj3Y0K87.js → build-info-D0EJNj6C.js} +3 -3
- package/dist/dashboard/{build-info-Cj3Y0K87.js.map → build-info-D0EJNj6C.js.map} +1 -1
- package/dist/dashboard/cli-CIznFrcE.js +2 -0
- package/dist/dashboard/{cli-DIhQieif.js → cli-DCbVrSsg.js} +2 -2
- package/dist/dashboard/{cli-DIhQieif.js.map → cli-DCbVrSsg.js.map} +1 -1
- package/dist/dashboard/{close-issue-CRlmrE5g.js → close-issue-Bmu9aOIx.js} +2 -2
- package/dist/dashboard/{close-issue-CRlmrE5g.js.map → close-issue-Bmu9aOIx.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js → closed-issue-reaper-COxfTtLz.js} +5 -5
- package/dist/dashboard/{closed-issue-reaper-DrHyYgJR.js.map → closed-issue-reaper-COxfTtLz.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-DWlw2EtL.js → closed-issue-reaper-DwMwG_Pz.js} +1 -1
- package/dist/dashboard/{compaction-continuation-DhYgsXmF.js → compaction-continuation-Bqc212Zm.js} +2 -2
- package/dist/dashboard/{compaction-continuation-DhYgsXmF.js.map → compaction-continuation-Bqc212Zm.js.map} +1 -1
- package/dist/dashboard/{conflict-gate-vczgdVTa.js → conflict-gate-9mPVg7-5.js} +1 -1
- package/dist/dashboard/{conflict-gate-Bt-lLgYf.js → conflict-gate-CGQTcR0I.js} +3 -3
- package/dist/dashboard/{conflict-gate-Bt-lLgYf.js.map → conflict-gate-CGQTcR0I.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-CsdCbcQB.js → conversation-compaction-C7P6V8Iw.js} +5 -5
- package/dist/dashboard/{conversation-compaction-CsdCbcQB.js.map → conversation-compaction-C7P6V8Iw.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-D0zgWM1f.js → conversation-compaction-Cck5oi-V.js} +1 -1
- package/dist/dashboard/{conversation-service-6LaymUV-.js → conversation-service-CsOpwI9Y.js} +37 -5
- package/dist/dashboard/{conversation-service-6LaymUV-.js.map → conversation-service-CsOpwI9Y.js.map} +1 -1
- package/dist/dashboard/conversation-service-plOFkoMo.js +3 -0
- package/dist/dashboard/dashboard-db-worker.js +1 -1
- package/dist/dashboard/{deacon-CU47dU7U.js → deacon-B8oMMIgN.js} +62 -62
- package/dist/dashboard/{deacon-CU47dU7U.js.map → deacon-B8oMMIgN.js.map} +1 -1
- package/dist/dashboard/{deacon-DYsOa_ss.js → deacon-b7J57Svt.js} +2 -2
- package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js → deacon-pending-lifecycle-3NIJ7ew5.js} +2 -2
- package/dist/dashboard/{deacon-pending-lifecycle-ijGX8wLb.js.map → deacon-pending-lifecycle-3NIJ7ew5.js.map} +1 -1
- package/dist/dashboard/deacon.js +5 -5
- package/dist/dashboard/{delivery-DMYxIE0J.js → delivery-lnI-gQKo.js} +1 -1
- package/dist/dashboard/{deploy-patrol-C4eXco1S.js → deploy-patrol-u3v2krg9.js} +2 -2
- package/dist/dashboard/{deploy-patrol-C4eXco1S.js.map → deploy-patrol-u3v2krg9.js.map} +1 -1
- package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js → feedback-stuck-retirement-CoFNqjCc.js} +2 -2
- package/dist/dashboard/{feedback-stuck-retirement-Dd5XNrMr.js.map → feedback-stuck-retirement-CoFNqjCc.js.map} +1 -1
- package/dist/dashboard/{feedback-target-BzdJ2wIR.js → feedback-target-BBCh3RRq.js} +1 -1
- package/dist/dashboard/{feedback-target-BcsA_GRV.js → feedback-target-CeCq6EhW.js} +5 -5
- package/dist/dashboard/{feedback-target-BcsA_GRV.js.map → feedback-target-CeCq6EhW.js.map} +1 -1
- package/dist/dashboard/{flywheel-Is3w2lcT.js → flywheel-DKldI63J.js} +7 -7
- package/dist/dashboard/{flywheel-Is3w2lcT.js.map → flywheel-DKldI63J.js.map} +1 -1
- package/dist/dashboard/{flywheel-D0guNqkh.js → flywheel-DpRPLErh.js} +7 -7
- package/dist/dashboard/{flywheel-D0guNqkh.js.map → flywheel-DpRPLErh.js.map} +1 -1
- package/dist/dashboard/{flywheel-B34cdfbd.js → flywheel-jeZFYvm-.js} +1 -1
- package/dist/dashboard/{flywheel-merge-order-6HiPR2WG.js → flywheel-merge-order-0OnoosaO.js} +1 -1
- package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js → flywheel-merge-order-PG5op4j7.js} +2 -2
- package/dist/dashboard/{flywheel-merge-order-BsNPMu0S.js.map → flywheel-merge-order-PG5op4j7.js.map} +1 -1
- package/dist/dashboard/{harness-resolve-CzTFVZ5l.js → harness-resolve-C0HWj3H9.js} +2 -2
- package/dist/dashboard/{harness-resolve-CzTFVZ5l.js.map → harness-resolve-C0HWj3H9.js.map} +1 -1
- package/dist/dashboard/{identity-Dc7rQLln.js → identity-Cot1Hg4G.js} +2 -2
- package/dist/dashboard/{identity-Dc7rQLln.js.map → identity-Cot1Hg4G.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-CP7Bu47O.js → inspect-agent-BDmLWbGx.js} +3 -3
- package/dist/dashboard/{inspect-agent-CP7Bu47O.js.map → inspect-agent-BDmLWbGx.js.map} +1 -1
- package/dist/dashboard/inspect-agent-BcOkA-hC.js +2 -0
- package/dist/dashboard/{issue-lifecycle-BPbczKyy.js → issue-lifecycle-1B_v8Rf5.js} +6 -6
- package/dist/dashboard/{issue-lifecycle-BPbczKyy.js.map → issue-lifecycle-1B_v8Rf5.js.map} +1 -1
- package/dist/dashboard/{issue-lifecycle-FaugTi39.js → issue-lifecycle-CIrfbIbj.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-Dsad9NTt.js → issue-service-singleton-BVwPzjwF.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js → issue-service-singleton-KcRrOiFD.js} +2 -2
- package/dist/dashboard/{issue-service-singleton-BrZQJLRI.js.map → issue-service-singleton-KcRrOiFD.js.map} +1 -1
- package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js → jsonl-resolver-jxbif7MD.js} +3 -3
- package/dist/dashboard/{jsonl-resolver-DQEe0DHM.js.map → jsonl-resolver-jxbif7MD.js.map} +1 -1
- package/dist/dashboard/{kimi-code-Cx5YUJpS.js → kimi-code-BZh3NgcN.js} +1 -1
- package/dist/dashboard/{lifecycle-DZFHmpGI.js → lifecycle-CvcZ4_fA.js} +4 -4
- package/dist/dashboard/{lifecycle-DZFHmpGI.js.map → lifecycle-CvcZ4_fA.js.map} +1 -1
- package/dist/dashboard/{linear-mcp-auth-DylPJABZ.js → linear-mcp-auth-CZoj_-d7.js} +1 -1
- package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js → linear-mcp-auth-Dx74yCAY.js} +2 -2
- package/dist/dashboard/{linear-mcp-auth-BFlpMvWh.js.map → linear-mcp-auth-Dx74yCAY.js.map} +1 -1
- package/dist/dashboard/{memory-governor-B7uDWRo9.js → memory-governor-Bv3LAqk3.js} +5 -5
- package/dist/dashboard/{memory-governor-B7uDWRo9.js.map → memory-governor-Bv3LAqk3.js.map} +1 -1
- package/dist/dashboard/{merge-agent-ddEcZDZD.js → merge-agent-1zVLl6k_.js} +1 -1
- package/dist/dashboard/{merge-agent-ttRY053V.js → merge-agent-8zrQu48t.js} +8 -8
- package/dist/dashboard/{merge-agent-ttRY053V.js.map → merge-agent-8zrQu48t.js.map} +1 -1
- package/dist/dashboard/merge-ops-CFJaMPLu.js +2 -0
- package/dist/dashboard/{merge-train-f_un38UJ.js → merge-train-BYH_cF7G.js} +2 -2
- package/dist/dashboard/{merge-train-f_un38UJ.js.map → merge-train-BYH_cF7G.js.map} +1 -1
- package/dist/dashboard/{merge-train-deps-GPlHOjK6.js → merge-train-deps-BTfXM5AR.js} +2 -2
- package/dist/dashboard/{merge-train-deps-GPlHOjK6.js.map → merge-train-deps-BTfXM5AR.js.map} +1 -1
- package/dist/dashboard/{messaging-BasbftDu.js → messaging-Bk6vGsMw.js} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js → orphan-proposed-reconciler-B898fk26.js} +6 -6
- package/dist/dashboard/{orphan-proposed-reconciler-DFHdQSEg.js.map → orphan-proposed-reconciler-B898fk26.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-DzAxrM99.js → orphan-proposed-reconciler-Bx_aWS4B.js} +1 -1
- package/dist/dashboard/{parked-residue-DMtbxJml.js → parked-residue-q2kzrYSm.js} +9 -9
- package/dist/dashboard/{parked-residue-DMtbxJml.js.map → parked-residue-q2kzrYSm.js.map} +1 -1
- package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js → post-merge-lifecycle-worker-Cl7-uf4g.js} +2 -2
- package/dist/dashboard/{post-merge-lifecycle-worker-jzN2g_EY.js.map → post-merge-lifecycle-worker-Cl7-uf4g.js.map} +1 -1
- package/dist/dashboard/{project-resource-refresh-queue-cqOeJ0fB.js → project-resource-refresh-queue-O_dRfPHn.js} +1 -1
- package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js → project-resource-refresh-queue-se3T9IYP.js} +4 -4
- package/dist/dashboard/{project-resource-refresh-queue-DebwjPPA.js.map → project-resource-refresh-queue-se3T9IYP.js.map} +1 -1
- package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js → reconstruct-cache-XpWC9L0F.js} +2 -2
- package/dist/dashboard/{reconstruct-cache-Bb2Jg2DZ.js.map → reconstruct-cache-XpWC9L0F.js.map} +1 -1
- package/dist/dashboard/{release-engine-78iCajIc.js → release-engine-BuzxOamf.js} +2 -2
- package/dist/dashboard/{release-engine-78iCajIc.js.map → release-engine-BuzxOamf.js.map} +1 -1
- package/dist/dashboard/{remote-completion-D5wzZCte.js → remote-completion-BCZgeKPS.js} +3 -3
- package/dist/dashboard/{remote-completion-D5wzZCte.js.map → remote-completion-BCZgeKPS.js.map} +1 -1
- package/dist/dashboard/{resolver-BGZh3lXA.js → resolver-BeNrqNou.js} +5 -5
- package/dist/dashboard/{resolver-BGZh3lXA.js.map → resolver-BeNrqNou.js.map} +1 -1
- package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js → resource-pressure-patrol-Ccg_aCgO.js} +3 -3
- package/dist/dashboard/{resource-pressure-patrol-Bg23rPV-.js.map → resource-pressure-patrol-Ccg_aCgO.js.map} +1 -1
- package/dist/dashboard/{resume-BKyB4hA1.js → resume-KGCrn0DV.js} +1 -1
- package/dist/dashboard/{review-agent-ZdP-HmwY.js → review-agent-BmM6YMZW.js} +1 -1
- package/dist/dashboard/{review-agent-DHs-JngN.js → review-agent-DRWVU8fP.js} +13 -13
- package/dist/dashboard/{review-agent-DHs-JngN.js.map → review-agent-DRWVU8fP.js.map} +1 -1
- package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js → review-convoy-liveness-EvCDv8Q_.js} +3 -3
- package/dist/dashboard/{review-convoy-liveness-BTMvU4CA.js.map → review-convoy-liveness-EvCDv8Q_.js.map} +1 -1
- package/dist/dashboard/{review-run-recovery-BYalQzbh.js → review-run-recovery-Ctm48Snt.js} +1 -1
- package/dist/dashboard/{review-run-recovery-BvVG3oUr.js → review-run-recovery-DNDI58cM.js} +2 -2
- package/dist/dashboard/{review-run-recovery-BvVG3oUr.js.map → review-run-recovery-DNDI58cM.js.map} +1 -1
- package/dist/dashboard/{review-status-DXTFzCmR.js → review-status-FnlvQuPI.js} +1 -1
- package/dist/dashboard/{review-status-emit-C9YW6SQL.js → review-status-emit-CeMS31DX.js} +2 -2
- package/dist/dashboard/{review-status-emit-C9YW6SQL.js.map → review-status-emit-CeMS31DX.js.map} +1 -1
- package/dist/dashboard/{review-status-BegAr86G.js → review-status-yY5qt7Rl.js} +5 -5
- package/dist/dashboard/{review-status-BegAr86G.js.map → review-status-yY5qt7Rl.js.map} +1 -1
- package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js → review-verdict-feedback-BNgRj1e3.js} +4 -4
- package/dist/dashboard/{review-verdict-feedback-qlVBi19-.js.map → review-verdict-feedback-BNgRj1e3.js.map} +1 -1
- package/dist/dashboard/{review-verdict-feedback-Dp139Lmv.js → review-verdict-feedback-D39cuSV1.js} +1 -1
- package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js → review-verdict-writer-DvFfKAn-.js} +2 -2
- package/dist/dashboard/{review-verdict-writer-CJr_Ob0Y.js.map → review-verdict-writer-DvFfKAn-.js.map} +1 -1
- package/dist/dashboard/{review-verdict-writer-BVco3WoI.js → review-verdict-writer-PcGBkvJ4.js} +1 -1
- package/dist/dashboard/{sequence-io-iljWlslY.js → sequence-io-DZyschsf.js} +2 -2
- package/dist/dashboard/{sequence-io-iljWlslY.js.map → sequence-io-DZyschsf.js.map} +1 -1
- package/dist/dashboard/{sequencer-agent-BGGVKxtw.js → sequencer-agent-BXDw5cNx.js} +5 -5
- package/dist/dashboard/{sequencer-agent-BGGVKxtw.js.map → sequencer-agent-BXDw5cNx.js.map} +1 -1
- package/dist/dashboard/server.js +79 -72
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{service-kzhN0DLg.js → service-DP50Omvn.js} +33 -33
- package/dist/dashboard/{service-kzhN0DLg.js.map → service-DP50Omvn.js.map} +1 -1
- package/dist/dashboard/{smart-compaction-owKHJDbL.js → smart-compaction-944GL8PN.js} +1 -1
- package/dist/dashboard/{smart-compaction-C96F2aFQ.js → smart-compaction-BzDfF6yb.js} +2 -2
- package/dist/dashboard/{smart-compaction-C96F2aFQ.js.map → smart-compaction-BzDfF6yb.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-CiJdOHTD.js → spawn-planning-session-BcVnHJjm.js} +1 -1
- package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js → spawn-planning-session-CH8IYli7.js} +3 -3
- package/dist/dashboard/{spawn-planning-session-FmJa6PCS.js.map → spawn-planning-session-CH8IYli7.js.map} +1 -1
- package/dist/dashboard/{specialist-context-_ootmzn2.js → specialist-context-DhDTXVht.js} +3 -3
- package/dist/dashboard/{specialist-context-_ootmzn2.js.map → specialist-context-DhDTXVht.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-BbAX221-.js → specialist-logs-C1bcnZ-G.js} +2 -2
- package/dist/dashboard/{specialist-logs-BbAX221-.js.map → specialist-logs-C1bcnZ-G.js.map} +1 -1
- package/dist/dashboard/{specialists-COZoYaiz.js → specialists-BKmReEWT.js} +53 -53
- package/dist/dashboard/{specialists-COZoYaiz.js.map → specialists-BKmReEWT.js.map} +1 -1
- package/dist/dashboard/{specialists-RPLJnYc1.js → specialists-Bvrvt0br.js} +12 -12
- package/dist/dashboard/{specialists-RPLJnYc1.js.map → specialists-Bvrvt0br.js.map} +1 -1
- package/dist/dashboard/{specialists-DTAOVZpU.js → specialists-CD6GEKXk.js} +1 -1
- package/dist/dashboard/{specialists-D8sYZnPb.js → specialists-eFWpJRBc.js} +1 -1
- package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js → stall-sweeper-D3fZvg7O.js} +3 -3
- package/dist/dashboard/{stall-sweeper-9bRBZhrQ.js.map → stall-sweeper-D3fZvg7O.js.map} +1 -1
- package/dist/dashboard/{state-migrate-J3tFJnbd.js → state-migrate-KZ2goscQ.js} +2 -2
- package/dist/dashboard/{state-migrate-J3tFJnbd.js.map → state-migrate-KZ2goscQ.js.map} +1 -1
- package/dist/dashboard/{stuck-remediation-BvTK_NYN.js → stuck-remediation-Cb2V1roj.js} +1 -1
- package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js → stuck-remediation-CnoV0kcv.js} +5 -5
- package/dist/dashboard/{stuck-remediation-4Rf_xMEb.js.map → stuck-remediation-CnoV0kcv.js.map} +1 -1
- package/dist/dashboard/{summary-fork-PLcckTRE.js → summary-fork-CxuNBM0F.js} +2 -2
- package/dist/dashboard/{summary-fork-D-cylknJ.js → summary-fork-lC2vMXly.js} +4 -4
- package/dist/dashboard/{summary-fork-D-cylknJ.js.map → summary-fork-lC2vMXly.js.map} +1 -1
- package/dist/dashboard/{teardown-workspace-CTL16rnG.js → teardown-workspace-C-9murBw.js} +4 -4
- package/dist/dashboard/{teardown-workspace-CTL16rnG.js.map → teardown-workspace-C-9murBw.js.map} +1 -1
- package/dist/dashboard/{test-agent-queue-CMgYZiR_.js → test-agent-queue-YCBRzaAB.js} +3 -3
- package/dist/dashboard/{test-agent-queue-CMgYZiR_.js.map → test-agent-queue-YCBRzaAB.js.map} +1 -1
- package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js → uat-failure-feedback-B-1mL8Zp.js} +3 -3
- package/dist/dashboard/{uat-failure-feedback-DS6W9X3I.js.map → uat-failure-feedback-B-1mL8Zp.js.map} +1 -1
- package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js → uat-promote-verification-BEBPcpPg.js} +2 -2
- package/dist/dashboard/{uat-promote-verification-CRsIHE_q.js.map → uat-promote-verification-BEBPcpPg.js.map} +1 -1
- package/dist/dashboard/{uat-train-BoA9r6_I.js → uat-train-BFo3OQUB.js} +1 -1
- package/dist/dashboard/{uat-train-Cd8CJeNe.js → uat-train-COgoJomz.js} +8 -8
- package/dist/dashboard/{uat-train-Cd8CJeNe.js.map → uat-train-COgoJomz.js.map} +1 -1
- package/dist/dashboard/{validate-BdmvTIym.js → validate-069_N1e2.js} +2 -2
- package/dist/dashboard/{validate-BdmvTIym.js.map → validate-069_N1e2.js.map} +1 -1
- package/dist/dashboard/{verification-runner-BO0K3xNA.js → verification-runner-CQnZyC5A.js} +1 -1
- package/dist/dashboard/{verification-runner-fEY1Qj-l.js → verification-runner-fy3wNrWn.js} +4 -4
- package/dist/dashboard/{verification-runner-fEY1Qj-l.js.map → verification-runner-fy3wNrWn.js.map} +1 -1
- package/dist/dashboard/workflows-Bk6Kt0ZQ.js +2 -0
- package/dist/dashboard/{workflows-CxVnygYU.js → workflows-N4QMkDzN.js} +10 -10
- package/dist/dashboard/{workflows-CxVnygYU.js.map → workflows-N4QMkDzN.js.map} +1 -1
- package/dist/dashboard/{workspaces-Cpz0xJWQ.js → workspaces-DmoyfHoU.js} +33 -33
- package/dist/dashboard/{workspaces-Cpz0xJWQ.js.map → workspaces-DmoyfHoU.js.map} +1 -1
- package/dist/{lifecycle-C6k0HWEO.js → lifecycle-DCFcjg7y.js} +1 -1
- package/dist/{lifecycle-D5oRpvvB.js → lifecycle-GkaHQJNK.js} +2 -2
- package/dist/{lifecycle-D5oRpvvB.js.map → lifecycle-GkaHQJNK.js.map} +1 -1
- package/dist/{merge-agent-Drufc7tU.js → merge-agent-CogNFasY.js} +2 -2
- package/dist/{merge-agent-Drufc7tU.js.map → merge-agent-CogNFasY.js.map} +1 -1
- package/dist/{merge-agent-CBOVY_eC.js → merge-agent-eteKQ9Rb.js} +1 -1
- package/package.json +1 -1
- package/dist/build-info-BgrwLUy6.js +0 -2
- package/dist/dashboard/agent-state-Ci6PhjOE.js.map +0 -1
- package/dist/dashboard/build-info-CYlxvZSQ.js +0 -2
- package/dist/dashboard/cli-ZAS502uC.js +0 -2
- package/dist/dashboard/conversation-service-Dlks_hSS.js +0 -3
- package/dist/dashboard/inspect-agent-DTuSg95i.js +0 -2
- package/dist/dashboard/merge-ops-Bgc-EScO.js +0 -2
- package/dist/dashboard/workflows-CgGNyl1z.js +0 -2
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { A as saveAgentStateSync, C as markAgentTroubled, D as recordStartupSessionExit, E as recordAgentFailureSync, F as writeAgentStateJsonSync, I as isRole, M as setAgentPausedSync, N as setAgentYieldedSync, O as resetAgentFailureCount, P as wipeAgentStateDirs, S as markAgentStoppedState, T as recordAgentFailure, _ as init_agent_state, a as clearAgentOperatorGatesForIssuesSync, b as markAgentRunning, c as clearAgentTroubled, d as decideResumeGate, f as getAgentDir, g as getAgentStateSync, h as getAgentStateFilePath, i as clearAgentOperatorGatesForIssueSync, j as setAgentPaused, k as saveAgentState, l as clearAgentTroubledSync, m as getAgentState, n as SESSION_EXITED_BEFORE_KICKOFF, o as clearAgentPaused, p as getAgentResumeGateBlockReason, r as __testInternals, s as clearAgentPausedSync, t as GOVERNOR_SLOT_PAUSE_REASON_PREFIX, u as clearYieldForResumeSync, v as isAgentPaused, w as recordAgentActivitySync, x as markAgentRunningState, y as isAgentTroubled } from "./agent-state-
|
|
1
|
+
import { A as saveAgentStateSync, C as markAgentTroubled, D as recordStartupSessionExit, E as recordAgentFailureSync, F as writeAgentStateJsonSync, I as isRole, M as setAgentPausedSync, N as setAgentYieldedSync, O as resetAgentFailureCount, P as wipeAgentStateDirs, S as markAgentStoppedState, T as recordAgentFailure, _ as init_agent_state, a as clearAgentOperatorGatesForIssuesSync, b as markAgentRunning, c as clearAgentTroubled, d as decideResumeGate, f as getAgentDir, g as getAgentStateSync, h as getAgentStateFilePath, i as clearAgentOperatorGatesForIssueSync, j as setAgentPaused, k as saveAgentState, l as clearAgentTroubledSync, m as getAgentState, n as SESSION_EXITED_BEFORE_KICKOFF, o as clearAgentPaused, p as getAgentResumeGateBlockReason, r as __testInternals, s as clearAgentPausedSync, t as GOVERNOR_SLOT_PAUSE_REASON_PREFIX, u as clearYieldForResumeSync, v as isAgentPaused, w as recordAgentActivitySync, x as markAgentRunningState, y as isAgentTroubled } from "./agent-state-B196fj1h.js";
|
|
2
2
|
init_agent_state();
|
|
3
3
|
export { GOVERNOR_SLOT_PAUSE_REASON_PREFIX, SESSION_EXITED_BEFORE_KICKOFF, __testInternals, clearAgentOperatorGatesForIssueSync, clearAgentOperatorGatesForIssuesSync, clearAgentPaused, clearAgentPausedSync, clearAgentTroubled, clearAgentTroubledSync, clearYieldForResumeSync, decideResumeGate, getAgentDir, getAgentResumeGateBlockReason, getAgentState, getAgentStateFilePath, getAgentStateSync, isAgentPaused, isAgentTroubled, isRole, markAgentRunning, markAgentRunningState, markAgentStoppedState, markAgentTroubled, recordAgentActivitySync, recordAgentFailure, recordAgentFailureSync, recordStartupSessionExit, resetAgentFailureCount, saveAgentState, saveAgentStateSync, setAgentPaused, setAgentPausedSync, setAgentYieldedSync, wipeAgentStateDirs, writeAgentStateJsonSync };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { a as init_event_store, i as initEventStore } from "./event-store-BYOh686E.js";
|
|
2
2
|
import { h as applyEvent, m as INITIAL_READ_MODEL_STATE, t as init_src } from "./src-tnFSfiXX.js";
|
|
3
3
|
import { t as emitBootReconciledStopEvents } from "./boot-reconciled-stop-events-fbtwLQEZ.js";
|
|
4
|
-
import { An as init_session_history,
|
|
4
|
+
import { An as init_session_history, Da as markAgentStateServiceInProcess, Ea as init_agent_runtime_mirror, Oa as setAgentRuntimeMirror, Sa as init_agents, Ta as getRuntimeSnapshot$1, _ as init_agent_state, ba as appendAgentPlaneSession, g as getAgentStateSync, kn as appendSessionIdToHistory } from "./agent-state-B196fj1h.js";
|
|
5
5
|
import { Context, Effect, Layer, SubscriptionRef } from "effect";
|
|
6
6
|
//#region services/agent-state-service.ts
|
|
7
7
|
/**
|
|
@@ -61,7 +61,7 @@ const AgentStateServiceLive = Layer.effect(AgentStateService, Effect.gen(functio
|
|
|
61
61
|
const store = yield* Effect.promise(() => initEventStore());
|
|
62
62
|
const ref = yield* SubscriptionRef.make({});
|
|
63
63
|
const seedFromSources = Effect.gen(function* () {
|
|
64
|
-
const { reconstructCacheAuto } = yield* Effect.promise(() => import("./reconstruct-cache-
|
|
64
|
+
const { reconstructCacheAuto } = yield* Effect.promise(() => import("./reconstruct-cache-XpWC9L0F.js"));
|
|
65
65
|
const result = yield* Effect.promise(() => reconstructCacheAuto());
|
|
66
66
|
const seeded = result.agentRuntimeById;
|
|
67
67
|
yield* Effect.promise(() => emitBootReconciledStopEvents(store, result.markedStoppedIds, seeded, "[AgentStateService] Failed to emit boot-reconciled stop event:"));
|
|
@@ -125,4 +125,4 @@ function applyEventToRef(ref, ev) {
|
|
|
125
125
|
//#endregion
|
|
126
126
|
export { mergeRuntimeBySequence as i, AgentStateServiceLive as n, getRuntimeSnapshot as r, AgentStateService as t };
|
|
127
127
|
|
|
128
|
-
//# sourceMappingURL=agent-state-service-
|
|
128
|
+
//# sourceMappingURL=agent-state-service-CCo5-9DR.js.map
|
package/dist/dashboard/{agent-state-service-D6IVrX3B.js.map → agent-state-service-CCo5-9DR.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-state-service-D6IVrX3B.js","names":["getMirrorSnapshot","applyReducerEvent"],"sources":["../../src/dashboard/server/services/agent-state-service.ts"],"sourcesContent":["/**\n * AgentStateService — canonical in-memory runtime state for all agents (PAN-800)\n *\n * Source of truth: SubscriptionRef<Record<AgentId, AgentRuntimeSnapshot>> derived\n * from folding agent.* runtime events out of the append-only event store.\n *\n * writers canonical state readers\n * ------- --------------- -------\n * hooks → POST /api/agents/:id/heartbeat\n * specialists → emit() EventStore.appendAsync .get(id) / .changes\n * ↓ subscribe → route handlers\n * SubscriptionRef deacon, UI\n *\n * Invariants:\n * - The Record inside the ref is only ever mutated via the shared reducer\n * (packages/contracts/src/event-reducers.ts). Never set directly.\n * - `emit()` MUST route through `appendAsync`, never `emitOnly`. emitOnly\n * assigns sequence=-1 which would regress `updatedAtSequence` and break the\n * Math.max(state.sequence, event.sequence) invariant in the shared reducer.\n * - `readFileSync` is forbidden — bootstrap uses `fs.promises.readFile` for\n * the one-time runtime.json migration fallback.\n */\n\nimport { Effect, Layer, Context, Stream, SubscriptionRef } from 'effect';\nimport {\n applyEvent as applyReducerEvent,\n INITIAL_READ_MODEL_STATE,\n} from '@overdeck/contracts';\nimport type {\n AgentRuntimeSnapshot,\n AgentSnapshot,\n DomainEvent,\n} from '@overdeck/contracts';\nimport { initEventStore } from '../event-store.js';\nimport type { StoredEvent } from '../event-store.js';\nimport { setAgentRuntimeMirror, getRuntimeSnapshot as getMirrorSnapshot, markAgentStateServiceInProcess } from '../../../lib/agent-runtime-mirror.js';\nimport { getAgentStateSync } from '../../../lib/agents/agent-state.js';\nimport { appendAgentPlaneSession } from '../../../lib/pan-dir/agents.js';\nimport { appendSessionIdToHistory } from '../../../lib/session-history.js';\nimport { emitBootReconciledStopEvents } from './boot-reconciled-stop-events.js';\n\n// ─── Event filtering ──────────────────────────────────────────────────────────\n\n/**\n * Event types that affect AgentRuntimeSnapshot. Keep in sync with\n * `packages/contracts/src/event-reducers.ts` — every case that writes\n * `agentRuntimeById` must appear here.\n */\nconst RUNTIME_EVENT_TYPES: ReadonlySet<string> = new Set([\n 'agent.activity_changed',\n 'agent.thinking_started',\n 'agent.thinking_stopped',\n 'agent.waiting_started',\n 'agent.waiting_cleared',\n 'agent.message_received',\n 'agent.model_set',\n 'agent.current_issue_set',\n 'agent.resolution_changed',\n 'agent.context_saturation_changed',\n 'agent.state_restored',\n // Lifecycle event: pan kill bypasses the Stop hook, so the reducer folds\n // agent.stopped into the runtime snapshot to prevent \"idle forever\" ghosts.\n 'agent.stopped',\n]);\n\nfunction isRuntimeEvent(e: { type: string }): boolean {\n return RUNTIME_EVENT_TYPES.has(e.type);\n}\n\n// ─── Service interface ────────────────────────────────────────────────────────\n\nexport interface AgentStateServiceShape {\n /** Latest snapshot for a single agent, or undefined if unknown. */\n readonly get: (id: string) => Effect.Effect<AgentRuntimeSnapshot | undefined>;\n /** Full map of agent → snapshot. */\n readonly getAll: Effect.Effect<Record<string, AgentRuntimeSnapshot>>;\n /** Stream of every new snapshot map. Emits whenever any agent updates. */\n readonly changes: Stream.Stream<Record<string, AgentRuntimeSnapshot>>;\n /**\n * Emit a runtime event. Routes through EventStore.appendAsync — the event\n * becomes durable before the returned Effect completes. Never blocks the\n * event loop; hooks that POST through this path stay non-blocking.\n */\n readonly emit: (\n event: Omit<DomainEvent, 'sequence'>,\n ) => Effect.Effect<void>;\n}\n\nexport class AgentStateService extends Context.Service<\n AgentStateService,\n AgentStateServiceShape\n>()('overdeck/dashboard/AgentStateService') {}\n\n// ─── Live implementation ──────────────────────────────────────────────────────\n\n// Re-export the cross-process-safe mirror accessor.\nexport const getRuntimeSnapshot = getMirrorSnapshot;\n\nexport const AgentStateServiceLive = Layer.effect(\n AgentStateService,\n Effect.gen(function* () {\n // Flag lib-side adapters to prefer the in-process mirror over HTTP.\n // Without this, agent-enrichment / ReadModel bootstrap would fetch() our\n // own HTTP server before it finished listening — a circular deadlock.\n yield* markAgentStateServiceInProcess();\n const store = yield* Effect.promise(() => initEventStore());\n const ref = yield* SubscriptionRef.make<Record<string, AgentRuntimeSnapshot>>({});\n\n // ── Bootstrap from sources (PAN-1920) ───────────────────────────────────\n // Reconstruct runtime snapshots from state.json + tmux in a background fork\n // so the dashboard port binds fast. The merge keeps any live events that\n // arrived during the fork (they have a higher sequence than reconstruction).\n const seedFromSources = Effect.gen(function* () {\n const { reconstructCacheAuto } = yield* Effect.promise(() =>\n import('../../../lib/reconstruct/reconstruct-cache.js'),\n );\n const result = yield* Effect.promise(() => reconstructCacheAuto());\n const seeded = result.agentRuntimeById;\n yield* Effect.promise(() => emitBootReconciledStopEvents(store, result.markedStoppedIds, seeded, '[AgentStateService] Failed to emit boot-reconciled stop event:'));\n if (Object.keys(seeded).length > 0) {\n yield* SubscriptionRef.update(ref, (current) =>\n mergeRuntimeBySequence(current, seeded, result.agentsById),\n );\n yield* setAgentRuntimeMirror(yield* SubscriptionRef.get(ref));\n console.log(\n `[AgentStateService] Bootstrapped ${Object.keys(seeded).length} runtime snapshot(s) from sources`,\n );\n }\n });\n yield* Effect.forkDetach(seedFromSources);\n\n // ── Subscribe forward ────────────────────────────────────────────────────\n // No unsubscribe — the service lives for the whole dashboard process.\n store.subscribe((ev) => {\n if (!isRuntimeEvent(ev)) return;\n // PAN-1989: durably mirror a newly-learned Claude session id to the\n // agent's sessions.json. This is the single convergence point — every\n // model_set, hook-emitted or server-emitted, flows through here — so the\n // resume pointer is persisted to disk the instant it is known, instead of\n // living only in the in-memory snapshot that a restart/reboot discards.\n if (ev.type === 'agent.model_set') {\n const payload = (ev as { payload?: { agentId?: string; claudeSessionId?: string } }).payload;\n if (payload?.agentId && payload.claudeSessionId) {\n appendSessionIdToHistory(payload.agentId, payload.claudeSessionId);\n const state = getAgentStateSync(payload.agentId);\n if (!state) {\n console.warn(\n `[AgentStateService] Could not append durable session ${payload.claudeSessionId} `\n + `for ${payload.agentId}: agent state is missing`,\n );\n } else {\n void appendAgentPlaneSession(state, {\n id: payload.claudeSessionId,\n startedAt: new Date().toISOString(),\n reason: 'rotation',\n }).catch((error) => {\n console.warn(\n `[AgentStateService] Could not append durable session ${payload.claudeSessionId} `\n + `for ${payload.agentId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n });\n }\n }\n }\n Effect.runFork(applyEventToRef(ref, ev));\n });\n\n return {\n get: (id) =>\n SubscriptionRef.get(ref).pipe(Effect.map((m) => m[id])),\n getAll: SubscriptionRef.get(ref),\n changes: SubscriptionRef.changes(ref),\n emit: (event) =>\n Effect.promise(() =>\n store.appendAsync(event as Omit<DomainEvent, 'sequence'>),\n ).pipe(Effect.asVoid),\n };\n }),\n);\n\n// ─── Internals ────────────────────────────────────────────────────────────────\n\nexport function mergeRuntimeBySequence(\n current: Record<string, AgentRuntimeSnapshot>,\n reconstructed: Record<string, AgentRuntimeSnapshot>,\n reconstructedAgents: Record<string, AgentSnapshot>,\n): Record<string, AgentRuntimeSnapshot> {\n const merged: Record<string, AgentRuntimeSnapshot> = { ...reconstructed };\n for (const [id, snap] of Object.entries(current)) {\n const recon = reconstructed[id];\n if (!recon) {\n merged[id] = snap;\n continue;\n }\n const sourceAgent = reconstructedAgents[id];\n if (recon.activity === 'stopped' && sourceAgent?.hasLiveTmuxSession === false) {\n continue;\n }\n const currentSeq = snap.updatedAtSequence ?? -1;\n const reconSeq = recon.updatedAtSequence ?? 0;\n if (currentSeq >= reconSeq) {\n merged[id] = snap;\n }\n }\n return merged;\n}\n\nfunction applyEventToRef(\n ref: SubscriptionRef.SubscriptionRef<Record<string, AgentRuntimeSnapshot>>,\n ev: StoredEvent,\n): Effect.Effect<void> {\n return SubscriptionRef.update(ref, (current) => {\n const fakeState = {\n ...INITIAL_READ_MODEL_STATE,\n agentRuntimeById: current,\n };\n const nextState = applyReducerEvent(fakeState, ev as unknown as DomainEvent);\n const next = nextState.agentRuntimeById;\n\n Effect.runSync(setAgentRuntimeMirror(next));\n return next;\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agent-state-service-CCo5-9DR.js","names":["getMirrorSnapshot","applyReducerEvent"],"sources":["../../src/dashboard/server/services/agent-state-service.ts"],"sourcesContent":["/**\n * AgentStateService — canonical in-memory runtime state for all agents (PAN-800)\n *\n * Source of truth: SubscriptionRef<Record<AgentId, AgentRuntimeSnapshot>> derived\n * from folding agent.* runtime events out of the append-only event store.\n *\n * writers canonical state readers\n * ------- --------------- -------\n * hooks → POST /api/agents/:id/heartbeat\n * specialists → emit() EventStore.appendAsync .get(id) / .changes\n * ↓ subscribe → route handlers\n * SubscriptionRef deacon, UI\n *\n * Invariants:\n * - The Record inside the ref is only ever mutated via the shared reducer\n * (packages/contracts/src/event-reducers.ts). Never set directly.\n * - `emit()` MUST route through `appendAsync`, never `emitOnly`. emitOnly\n * assigns sequence=-1 which would regress `updatedAtSequence` and break the\n * Math.max(state.sequence, event.sequence) invariant in the shared reducer.\n * - `readFileSync` is forbidden — bootstrap uses `fs.promises.readFile` for\n * the one-time runtime.json migration fallback.\n */\n\nimport { Effect, Layer, Context, Stream, SubscriptionRef } from 'effect';\nimport {\n applyEvent as applyReducerEvent,\n INITIAL_READ_MODEL_STATE,\n} from '@overdeck/contracts';\nimport type {\n AgentRuntimeSnapshot,\n AgentSnapshot,\n DomainEvent,\n} from '@overdeck/contracts';\nimport { initEventStore } from '../event-store.js';\nimport type { StoredEvent } from '../event-store.js';\nimport { setAgentRuntimeMirror, getRuntimeSnapshot as getMirrorSnapshot, markAgentStateServiceInProcess } from '../../../lib/agent-runtime-mirror.js';\nimport { getAgentStateSync } from '../../../lib/agents/agent-state.js';\nimport { appendAgentPlaneSession } from '../../../lib/pan-dir/agents.js';\nimport { appendSessionIdToHistory } from '../../../lib/session-history.js';\nimport { emitBootReconciledStopEvents } from './boot-reconciled-stop-events.js';\n\n// ─── Event filtering ──────────────────────────────────────────────────────────\n\n/**\n * Event types that affect AgentRuntimeSnapshot. Keep in sync with\n * `packages/contracts/src/event-reducers.ts` — every case that writes\n * `agentRuntimeById` must appear here.\n */\nconst RUNTIME_EVENT_TYPES: ReadonlySet<string> = new Set([\n 'agent.activity_changed',\n 'agent.thinking_started',\n 'agent.thinking_stopped',\n 'agent.waiting_started',\n 'agent.waiting_cleared',\n 'agent.message_received',\n 'agent.model_set',\n 'agent.current_issue_set',\n 'agent.resolution_changed',\n 'agent.context_saturation_changed',\n 'agent.state_restored',\n // Lifecycle event: pan kill bypasses the Stop hook, so the reducer folds\n // agent.stopped into the runtime snapshot to prevent \"idle forever\" ghosts.\n 'agent.stopped',\n]);\n\nfunction isRuntimeEvent(e: { type: string }): boolean {\n return RUNTIME_EVENT_TYPES.has(e.type);\n}\n\n// ─── Service interface ────────────────────────────────────────────────────────\n\nexport interface AgentStateServiceShape {\n /** Latest snapshot for a single agent, or undefined if unknown. */\n readonly get: (id: string) => Effect.Effect<AgentRuntimeSnapshot | undefined>;\n /** Full map of agent → snapshot. */\n readonly getAll: Effect.Effect<Record<string, AgentRuntimeSnapshot>>;\n /** Stream of every new snapshot map. Emits whenever any agent updates. */\n readonly changes: Stream.Stream<Record<string, AgentRuntimeSnapshot>>;\n /**\n * Emit a runtime event. Routes through EventStore.appendAsync — the event\n * becomes durable before the returned Effect completes. Never blocks the\n * event loop; hooks that POST through this path stay non-blocking.\n */\n readonly emit: (\n event: Omit<DomainEvent, 'sequence'>,\n ) => Effect.Effect<void>;\n}\n\nexport class AgentStateService extends Context.Service<\n AgentStateService,\n AgentStateServiceShape\n>()('overdeck/dashboard/AgentStateService') {}\n\n// ─── Live implementation ──────────────────────────────────────────────────────\n\n// Re-export the cross-process-safe mirror accessor.\nexport const getRuntimeSnapshot = getMirrorSnapshot;\n\nexport const AgentStateServiceLive = Layer.effect(\n AgentStateService,\n Effect.gen(function* () {\n // Flag lib-side adapters to prefer the in-process mirror over HTTP.\n // Without this, agent-enrichment / ReadModel bootstrap would fetch() our\n // own HTTP server before it finished listening — a circular deadlock.\n yield* markAgentStateServiceInProcess();\n const store = yield* Effect.promise(() => initEventStore());\n const ref = yield* SubscriptionRef.make<Record<string, AgentRuntimeSnapshot>>({});\n\n // ── Bootstrap from sources (PAN-1920) ───────────────────────────────────\n // Reconstruct runtime snapshots from state.json + tmux in a background fork\n // so the dashboard port binds fast. The merge keeps any live events that\n // arrived during the fork (they have a higher sequence than reconstruction).\n const seedFromSources = Effect.gen(function* () {\n const { reconstructCacheAuto } = yield* Effect.promise(() =>\n import('../../../lib/reconstruct/reconstruct-cache.js'),\n );\n const result = yield* Effect.promise(() => reconstructCacheAuto());\n const seeded = result.agentRuntimeById;\n yield* Effect.promise(() => emitBootReconciledStopEvents(store, result.markedStoppedIds, seeded, '[AgentStateService] Failed to emit boot-reconciled stop event:'));\n if (Object.keys(seeded).length > 0) {\n yield* SubscriptionRef.update(ref, (current) =>\n mergeRuntimeBySequence(current, seeded, result.agentsById),\n );\n yield* setAgentRuntimeMirror(yield* SubscriptionRef.get(ref));\n console.log(\n `[AgentStateService] Bootstrapped ${Object.keys(seeded).length} runtime snapshot(s) from sources`,\n );\n }\n });\n yield* Effect.forkDetach(seedFromSources);\n\n // ── Subscribe forward ────────────────────────────────────────────────────\n // No unsubscribe — the service lives for the whole dashboard process.\n store.subscribe((ev) => {\n if (!isRuntimeEvent(ev)) return;\n // PAN-1989: durably mirror a newly-learned Claude session id to the\n // agent's sessions.json. This is the single convergence point — every\n // model_set, hook-emitted or server-emitted, flows through here — so the\n // resume pointer is persisted to disk the instant it is known, instead of\n // living only in the in-memory snapshot that a restart/reboot discards.\n if (ev.type === 'agent.model_set') {\n const payload = (ev as { payload?: { agentId?: string; claudeSessionId?: string } }).payload;\n if (payload?.agentId && payload.claudeSessionId) {\n appendSessionIdToHistory(payload.agentId, payload.claudeSessionId);\n const state = getAgentStateSync(payload.agentId);\n if (!state) {\n console.warn(\n `[AgentStateService] Could not append durable session ${payload.claudeSessionId} `\n + `for ${payload.agentId}: agent state is missing`,\n );\n } else {\n void appendAgentPlaneSession(state, {\n id: payload.claudeSessionId,\n startedAt: new Date().toISOString(),\n reason: 'rotation',\n }).catch((error) => {\n console.warn(\n `[AgentStateService] Could not append durable session ${payload.claudeSessionId} `\n + `for ${payload.agentId}: ${error instanceof Error ? error.message : String(error)}`,\n );\n });\n }\n }\n }\n Effect.runFork(applyEventToRef(ref, ev));\n });\n\n return {\n get: (id) =>\n SubscriptionRef.get(ref).pipe(Effect.map((m) => m[id])),\n getAll: SubscriptionRef.get(ref),\n changes: SubscriptionRef.changes(ref),\n emit: (event) =>\n Effect.promise(() =>\n store.appendAsync(event as Omit<DomainEvent, 'sequence'>),\n ).pipe(Effect.asVoid),\n };\n }),\n);\n\n// ─── Internals ────────────────────────────────────────────────────────────────\n\nexport function mergeRuntimeBySequence(\n current: Record<string, AgentRuntimeSnapshot>,\n reconstructed: Record<string, AgentRuntimeSnapshot>,\n reconstructedAgents: Record<string, AgentSnapshot>,\n): Record<string, AgentRuntimeSnapshot> {\n const merged: Record<string, AgentRuntimeSnapshot> = { ...reconstructed };\n for (const [id, snap] of Object.entries(current)) {\n const recon = reconstructed[id];\n if (!recon) {\n merged[id] = snap;\n continue;\n }\n const sourceAgent = reconstructedAgents[id];\n if (recon.activity === 'stopped' && sourceAgent?.hasLiveTmuxSession === false) {\n continue;\n }\n const currentSeq = snap.updatedAtSequence ?? -1;\n const reconSeq = recon.updatedAtSequence ?? 0;\n if (currentSeq >= reconSeq) {\n merged[id] = snap;\n }\n }\n return merged;\n}\n\nfunction applyEventToRef(\n ref: SubscriptionRef.SubscriptionRef<Record<string, AgentRuntimeSnapshot>>,\n ev: StoredEvent,\n): Effect.Effect<void> {\n return SubscriptionRef.update(ref, (current) => {\n const fakeState = {\n ...INITIAL_READ_MODEL_STATE,\n agentRuntimeById: current,\n };\n const nextState = applyReducerEvent(fakeState, ev as unknown as DomainEvent);\n const next = nextState.agentRuntimeById;\n\n Effect.runSync(setAgentRuntimeMirror(next));\n return next;\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as mergeRuntimeBySequence, n as AgentStateServiceLive, r as getRuntimeSnapshot, t as AgentStateService } from "./agent-state-service-
|
|
1
|
+
import { i as mergeRuntimeBySequence, n as AgentStateServiceLive, r as getRuntimeSnapshot, t as AgentStateService } from "./agent-state-service-CCo5-9DR.js";
|
|
2
2
|
export { AgentStateService, AgentStateServiceLive, getRuntimeSnapshot, mergeRuntimeBySequence };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { $t as getLatestSessionIdSync, A as saveAgentStateSync, B as resolveAgentTargetSync, C as markAgentTroubled, Dr as deliverResumeMessageWithTranscriptConfirmation, E as recordAgentFailureSync, Er as deliverInitialPromptWithRetry, F as writeAgentStateJsonSync, Ft as resumeAgent,
|
|
1
|
+
import { $t as getLatestSessionIdSync, A as saveAgentStateSync, B as resolveAgentTargetSync, C as markAgentTroubled, Dr as deliverResumeMessageWithTranscriptConfirmation, E as recordAgentFailureSync, Er as deliverInitialPromptWithRetry, F as writeAgentStateJsonSync, Ft as resumeAgent, Gr as assertWorkspaceStackHealthyForSpawn, Gt as messageAgent, H as waitForReadySignal, I as isRole, In as listAgentStates, J as compactAtTierRunBoundary, Ji as buildSpawnEnvForModel, K as saveAgentRuntimeState, Kr as buildAgentLaunchConfig, L as clearReadySignal, Ln as listRunningAgents, M as setAgentPausedSync, Mn as stopAgent, N as setAgentYieldedSync, Nn as stopAgentSync, Nt as buildCompactRecoverySeed, O as resetAgentFailureCount, P as wipeAgentStateDirs, Pn as dropLegacyAgentStatesMissingRoleAsync, Q as shouldReplayCompactAtTierRunBoundary, Qi as getProviderTmuxFlags, Qt as getLatestSessionId, R as isQualifiedAgentId, Rn as listRunningAgentsSync, S as markAgentStoppedState, T as recordAgentFailure, Tr as deliverAgentPermissionDecision, U as getAgentRuntimeState, V as waitForAgentIdle, W as getAgentRuntimeStateSync, X as replayCrashedStandingAgent, Xi as getProviderEnvForModel, Xr as transitionIssueToInProgress, Xt as appendActivity, Y as contextWindowForModel, Yi as determineModel, Yr as retrieveSpawnTimeMemoryContext, Z as replayStandingAgent, Zi as getProviderExportsForModel, Zr as transitionIssueToInReview, Zt as getActivity, _i as injectPiConversationMemory, aa as decideChannelsForWorkAgent, bi as waitForPromptReady, c as clearAgentTroubled, cn as spawnAgent, en as getSessionId, f as getAgentDir, ft as autoRecoverAgents, g as getAgentStateSync, h as getAgentStateFilePath, ht as restartAgent, ia as buildResumeContinueMessage, j as setAgentPaused, jr as setAgentDeliveryMethod, k as saveAgentState, l as clearAgentTroubledSync, li as getAgentRuntimeBaseCommand, ln as spawnRun, m as getAgentState, mi as getRoleRuntimeBaseCommand, mt as recoverAgent, n as SESSION_EXITED_BEFORE_KICKOFF, o as clearAgentPaused, oa as decideSupervisorForWorkAgent, oi as OHMYPI_AGENT_READY_TIMEOUT_SECONDS, pi as getProviderAuthMode, pt as detectCrashedAgents, q as init_agents, qi as buildCavemanExports, r as __testInternals, ra as buildDefaultResumeContinueMessage, rn as saveSessionId, s as clearAgentPausedSync, sa as dismissDevChannelsDialog, si as describeOhmypiSpawnFailure, t as GOVERNOR_SLOT_PAUSE_REASON_PREFIX, u as clearYieldForResumeSync, ua as writeChannelsBridgeMcpConfig, v as isAgentPaused, vi as roleAgentDefinitionPath, wr as deliverAgentMessage, x as markAgentRunningState, y as isAgentTroubled, z as normalizeAgentId, zn as warnOnBareNumericIssueIds } from "./agent-state-B196fj1h.js";
|
|
2
2
|
init_agents();
|
|
3
3
|
export { GOVERNOR_SLOT_PAUSE_REASON_PREFIX, OHMYPI_AGENT_READY_TIMEOUT_SECONDS, SESSION_EXITED_BEFORE_KICKOFF, __testInternals, appendActivity, assertWorkspaceStackHealthyForSpawn, autoRecoverAgents, buildAgentLaunchConfig, buildCavemanExports, buildCompactRecoverySeed, buildDefaultResumeContinueMessage, buildResumeContinueMessage, buildSpawnEnvForModel, clearAgentPaused, clearAgentPausedSync, clearAgentTroubled, clearAgentTroubledSync, clearReadySignal, clearYieldForResumeSync, compactAtTierRunBoundary, contextWindowForModel, decideChannelsForWorkAgent, decideSupervisorForWorkAgent, deliverAgentMessage, deliverAgentPermissionDecision, deliverInitialPromptWithRetry, deliverResumeMessageWithTranscriptConfirmation, describeOhmypiSpawnFailure, detectCrashedAgents, determineModel, dismissDevChannelsDialog, dropLegacyAgentStatesMissingRoleAsync, getActivity, getAgentDir, getAgentRuntimeBaseCommand, getAgentRuntimeState, getAgentRuntimeStateSync, getAgentState, getAgentStateFilePath, getAgentStateSync, getLatestSessionId, getLatestSessionIdSync, getProviderAuthMode, getProviderEnvForModel, getProviderExportsForModel, getProviderTmuxFlags, getRoleRuntimeBaseCommand, getSessionId, injectPiConversationMemory, isAgentPaused, isAgentTroubled, isQualifiedAgentId, isRole, listAgentStates, listRunningAgents, listRunningAgentsSync, markAgentRunningState, markAgentStoppedState, markAgentTroubled, messageAgent, normalizeAgentId, recordAgentFailure, recordAgentFailureSync, recoverAgent, replayCrashedStandingAgent, replayStandingAgent, resetAgentFailureCount, resolveAgentTargetSync, restartAgent, resumeAgent, retrieveSpawnTimeMemoryContext, roleAgentDefinitionPath, saveAgentRuntimeState, saveAgentState, saveAgentStateSync, saveSessionId, setAgentDeliveryMethod, setAgentPaused, setAgentPausedSync, setAgentYieldedSync, shouldReplayCompactAtTierRunBoundary, spawnAgent, spawnRun, stopAgent, stopAgentSync, transitionIssueToInProgress, transitionIssueToInReview, waitForAgentIdle, waitForPromptReady, waitForReadySignal, warnOnBareNumericIssueIds, wipeAgentStateDirs, writeAgentStateJsonSync, writeChannelsBridgeMcpConfig };
|
package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js → backlog-auto-trigger-B5Z2zBtC.js}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as isNoResumeValueEnabled } from "./no-resume-mode-BsvYzqbB.js";
|
|
2
|
-
import { i as spawnSequencerAgent } from "./sequencer-agent-
|
|
2
|
+
import { i as spawnSequencerAgent } from "./sequencer-agent-BXDw5cNx.js";
|
|
3
3
|
//#region ../../lib/backlog/backlog-auto-trigger.ts
|
|
4
4
|
/**
|
|
5
5
|
* PAN-2396: --no-resume is the operator's full-quiescence escape hatch —
|
|
@@ -31,4 +31,4 @@ function triggerDebouncedIncrementalPass(projectRoot) {
|
|
|
31
31
|
//#endregion
|
|
32
32
|
export { triggerDebouncedIncrementalPass };
|
|
33
33
|
|
|
34
|
-
//# sourceMappingURL=backlog-auto-trigger-
|
|
34
|
+
//# sourceMappingURL=backlog-auto-trigger-B5Z2zBtC.js.map
|
package/dist/dashboard/{backlog-auto-trigger-BuSBqG2V.js.map → backlog-auto-trigger-B5Z2zBtC.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backlog-auto-trigger-
|
|
1
|
+
{"version":3,"file":"backlog-auto-trigger-B5Z2zBtC.js","names":[],"sources":["../../src/lib/backlog/backlog-auto-trigger.ts"],"sourcesContent":["import { isNoResumeValueEnabled } from '../cloister/no-resume-mode.js';\nimport { spawnSequencerAgent } from './sequencer-agent.js';\n\n/**\n * PAN-2396: --no-resume is the operator's full-quiescence escape hatch —\n * \"human-driven only\". When the boot carries OVERDECK_NO_RESUME, no\n * autonomous sequencer pass may spawn; only explicit operator actions\n * (the dashboard's start-sequencer button, `pan` commands) remain allowed.\n * Checked at FIRE time so an already-scheduled timer respects the gate too.\n */\nfunction autonomousSpawnSuppressed(mode: 'incremental' | 'review'): boolean {\n if (!isNoResumeValueEnabled(process.env.OVERDECK_NO_RESUME)) return false;\n console.warn(`[backlog-auto-trigger] suppressed ${mode} sequencer pass: boot is --no-resume (human-driven only)`);\n return true;\n}\n\nlet _incrementalTimer: ReturnType<typeof setTimeout> | null = null;\nlet _reviewTimer: ReturnType<typeof setInterval> | null = null;\n\nconst DEBOUNCE_MS = 30_000;\n\n/**\n * PAN-1866: Call on every backlog delta (issue opened/closed/edited).\n * Debounces: multiple changes within DEBOUNCE_MS collapse into a single\n * incremental sequencer pass.\n */\nexport function triggerDebouncedIncrementalPass(projectRoot: string): void {\n if (_incrementalTimer !== null) clearTimeout(_incrementalTimer);\n _incrementalTimer = setTimeout(() => {\n _incrementalTimer = null;\n if (autonomousSpawnSuppressed('incremental')) return;\n spawnSequencerAgent('incremental', { projectRoot }).catch((err: unknown) =>\n console.warn('[backlog-auto-trigger] incremental pass failed:', err),\n );\n }, DEBOUNCE_MS);\n}\n\n/**\n * PAN-1866: Start a periodic review-pass cadence. Safe to call multiple times\n * (clears the previous interval first). Pass 0 to disable.\n */\nexport function startPeriodicReviewPass(projectRoot: string, intervalMs: number): void {\n if (_reviewTimer !== null) {\n clearInterval(_reviewTimer);\n _reviewTimer = null;\n }\n if (intervalMs <= 0) return;\n _reviewTimer = setInterval(() => {\n if (autonomousSpawnSuppressed('review')) return;\n spawnSequencerAgent('review', { projectRoot }).catch((err: unknown) =>\n console.warn('[backlog-auto-trigger] periodic review pass failed:', err),\n );\n }, intervalMs);\n}\n\nexport function stopPeriodicReviewPass(): void {\n if (_reviewTimer !== null) {\n clearInterval(_reviewTimer);\n _reviewTimer = null;\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,SAAS,0BAA0B,MAAyC;AAC1E,KAAI,CAAC,uBAAuB,QAAQ,IAAI,mBAAmB,CAAE,QAAO;AACpE,SAAQ,KAAK,qCAAqC,KAAK,0DAA0D;AACjH,QAAO;;AAGT,IAAI,oBAA0D;AAG9D,MAAM,cAAc;;;;;;AAOpB,SAAgB,gCAAgC,aAA2B;AACzE,KAAI,sBAAsB,KAAM,cAAa,kBAAkB;AAC/D,qBAAoB,iBAAiB;AACnC,sBAAoB;AACpB,MAAI,0BAA0B,cAAc,CAAE;AAC9C,sBAAoB,eAAe,EAAE,aAAa,CAAC,CAAC,OAAO,QACzD,QAAQ,KAAK,mDAAmD,IAAI,CACrE;IACA,YAAY"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
//#region ../../lib/deploy/build-info.ts
|
|
2
2
|
function getBuildInfo() {
|
|
3
3
|
return {
|
|
4
|
-
buildCommit: "
|
|
5
|
-
builtAt: "2026-09-
|
|
4
|
+
buildCommit: "a44e091b1b68616931485a222f16d53bf9ea449b",
|
|
5
|
+
builtAt: "2026-09-11T07:11:08.125Z",
|
|
6
6
|
buildDirty: false,
|
|
7
7
|
buildBranch: null
|
|
8
8
|
};
|
|
@@ -10,4 +10,4 @@ function getBuildInfo() {
|
|
|
10
10
|
//#endregion
|
|
11
11
|
export { getBuildInfo as t };
|
|
12
12
|
|
|
13
|
-
//# sourceMappingURL=build-info-
|
|
13
|
+
//# sourceMappingURL=build-info-D0EJNj6C.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-info-
|
|
1
|
+
{"version":3,"file":"build-info-D0EJNj6C.js","names":[],"sources":["../../src/lib/deploy/build-info.ts"],"sourcesContent":["declare const __OVERDECK_BUILD_COMMIT__: string;\ndeclare const __OVERDECK_BUILD_TIME__: string;\ndeclare const __OVERDECK_BUILD_DIRTY__: boolean;\ndeclare const __OVERDECK_BUILD_BRANCH__: string | null;\n\nexport interface BuildInfo {\n readonly buildCommit: string | null;\n readonly builtAt: string | null;\n readonly buildDirty: boolean | null;\n readonly buildBranch: string | null;\n}\n\nexport function getBuildInfo(): BuildInfo {\n return {\n buildCommit:\n typeof __OVERDECK_BUILD_COMMIT__ === 'string' ? __OVERDECK_BUILD_COMMIT__ : null,\n builtAt: typeof __OVERDECK_BUILD_TIME__ === 'string' ? __OVERDECK_BUILD_TIME__ : null,\n buildDirty:\n typeof __OVERDECK_BUILD_DIRTY__ === 'boolean' ? __OVERDECK_BUILD_DIRTY__ : null,\n buildBranch:\n typeof __OVERDECK_BUILD_BRANCH__ === 'string' ? __OVERDECK_BUILD_BRANCH__ : null,\n };\n}\n"],"mappings":";AAYA,SAAgB,eAA0B;AACxC,QAAO;EACL,aAAA;EAEA,SAAA;EACA,YAAA;EAEA,aAC8E;EAC/E"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Ri as init_paths } from "./config-yaml-Dce9VUd6.js";
|
|
2
2
|
import { a as getWorkspaceForIssue, f as resolveWorkspaceForCwd, o as init_resolver, p as resolveWorkspaceRef } from "./resolver-ChLZFIjB.js";
|
|
3
3
|
import { c as resolveMemoryRoot, i as init_paths$1, l as resolveObservationsFile, p as resolveSummariesDir, r as ensureParentDir } from "./paths-eTg2jV4C.js";
|
|
4
|
-
import { q as init_agents } from "./agent-state-
|
|
4
|
+
import { q as init_agents } from "./agent-state-B196fj1h.js";
|
|
5
5
|
import { i as init_settings } from "./settings-BpM0Ergn.js";
|
|
6
6
|
import { r as init_health, t as init_providers } from "./providers-DQogCKG0.js";
|
|
7
7
|
import { a as init_fts_db, c as withMemoryFtsDatabase, n as init_search, s as runMemoryFtsTransaction } from "./search-BtgzCB2W.js";
|
|
@@ -273,4 +273,4 @@ function isEnoent(error) {
|
|
|
273
273
|
//#endregion
|
|
274
274
|
export { generateDailySummary as n, resolveMemoryWorkspaceTarget as r, createResetMarker as t };
|
|
275
275
|
|
|
276
|
-
//# sourceMappingURL=cli-
|
|
276
|
+
//# sourceMappingURL=cli-DCbVrSsg.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-DIhQieif.js","names":[],"sources":["../../src/lib/memory/cli.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { readdir, readFile, rename, writeFile } from 'fs/promises';\nimport { dirname, isAbsolute, join } from 'path';\nimport type { MemoryObservation, MemoryStatus, ResetMarker } from '@overdeck/contracts';\nimport { getOverdeckHome } from '../paths.js';\nimport { runMemoryFtsTransaction, withMemoryFtsDatabase } from './fts-db.js';\nimport { resolveExtractionProviderSelection } from './providers/index.js';\nimport { getMemoryRollupPendingThreshold, loadMemorySettings } from './settings.js';\nimport {\n ensureParentDir,\n resolveMemoryRoot,\n resolveObservationsFile,\n resolvePendingDir,\n resolveSummariesDir,\n resolveWorkspaceMemoryRoot,\n} from './paths.js';\nimport { readVerifiedPinFile, resolveContainedPinPath } from './pin-path.js';\nimport { getMemoryHealthPath, type MemoryHealthSnapshot } from './health.js';\nimport { mirrorDailySummary } from './state-mirror.js';\nimport { readArchivedStatusEntries, readCurrentStatus, readObservationsSince, type ArchivedStatusEntry } from './rollup.js';\nimport { getAgentStateSync } from '../agents.js';\nimport {\n getWorkspaceForIssue,\n listProjects,\n listWorkspaces,\n listWorkspacesForPath,\n resolveWorkspaceForCwd,\n resolveWorkspaceRef,\n} from '../workspaces/resolver.js';\nimport { searchMemory as searchMemoryFts, type MemorySearchHit } from './search.js';\n\nconst DEFAULT_PROJECT_ID = 'overdeck';\nconst MIN_DAILY_SUMMARY_OBSERVATIONS = 3;\nconst DAILY_SUMMARY_REGENERATION_OBSERVATIONS = 20;\n/** Upper bound on `pan memory status --history <n>` (PAN-3286 D-8). */\nexport const MEMORY_STATUS_HISTORY_LIMIT = 50;\n\nconst ADDRESSING_MODES_HINT =\n 'Address a workspace with --workspace <id|name>, with an issue positional, or by running the command from inside the workspace directory.';\n\nexport interface MemorySearchOptions {\n project?: string;\n workspace?: string;\n issue?: string;\n tag?: string;\n sibling?: boolean;\n global?: boolean;\n limit?: number;\n includeArchived?: boolean;\n /** All workspaces targeting this directory (PAN-3286 FR-4) — resolved by the caller via listWorkspacesForPath. */\n targetPath?: string;\n}\n\nexport interface MemorySearchResult {\n observation: MemoryObservation;\n score: number;\n}\n\nexport type DailySummaryStatus = 'generated' | 'insufficient-data' | 'up-to-date';\n\nexport interface DailySummaryResult {\n status: DailySummaryStatus;\n path: string;\n markdown: string;\n observationCount: number;\n previousObservationCount: number | null;\n}\n\nexport interface MemoryDoctorOptions {\n project?: string;\n now?: Date;\n}\n\nexport interface MemoryDoctorResult {\n exitCode: number;\n provider: Awaited<ReturnType<typeof resolveExtractionProviderSelection>>;\n rollupPendingThreshold: number;\n issues: Array<{\n projectId: string;\n issueId: string;\n health: MemoryHealthSnapshot;\n pendingCount: number;\n lastObservation: string | null;\n }>;\n staleActiveAgents: Array<{ agentId: string; issueId: string; lastSuccess: string | null }>;\n}\n\n/**\n * PAN-1990 FR-9: search runs against the existing memory_fts index (every\n * observation write indexes into it — see observations.ts's indexObservation)\n * instead of reading every daily JSONL file for every workspace into memory\n * and sorting the full corpus. Runtime and memory scale with the FTS-bounded\n * result set (limit), not with total history. Reset-marker filtering also\n * moves into the FTS query itself (search.ts already does this in SQL).\n *\n * `--workspace <id|name>` (D-3) resolves either a workspace UUID or a\n * workspace name, then uses ITS OWNING project before searching, so a\n * workspace registered under a project other than `overdeck` is actually\n * searched instead of silently querying the default project and finding\n * nothing. A name that matches more than one workspace across projects is\n * reported as an ambiguity error rather than guessing.\n */\n/** Search one project (optionally scoped to one workspace/issue within it) and resolve hits back to observations. */\nasync function searchProjectScope(input: {\n query: string;\n projectId: string;\n workspaceId?: string;\n issueId?: string;\n sibling?: boolean;\n includeArchived?: boolean;\n tags?: string[];\n limit: number;\n}): Promise<MemorySearchResult[]> {\n const hits = await searchMemoryFts({\n query: input.query,\n projectId: input.projectId,\n workspaceId: input.workspaceId,\n issueId: input.issueId,\n sibling: input.sibling,\n includeArchived: input.includeArchived,\n tags: input.tags,\n // The shared FTS index also carries daily-summary hits (doc_type\n // 'summary'), which this command can't resolve back to a\n // MemoryObservation — restrict to observations so a summary ranked\n // above matching observations doesn't consume the limit and vanish.\n docType: 'observation',\n limit: input.limit,\n });\n\n const fileCache = new Map<string, MemoryObservation[]>();\n const pairs = await Promise.all(hits.map(async (hit) => ({\n hit,\n observation: await resolveObservationFromHit(input.projectId, hit, fileCache),\n })));\n return pairs\n .filter((pair): pair is { hit: MemorySearchHit; observation: MemoryObservation } => pair.observation !== null)\n .map((pair) => ({ observation: pair.observation, score: pair.hit.rankScore }));\n}\n\n/** Merge per-scope result sets by rank score (ties broken by recency), capped at limit. */\nfunction mergeRankedResults(perScope: MemorySearchResult[][], limit: number): MemorySearchResult[] {\n return perScope.flat()\n .sort((a, b) => b.score - a.score || b.observation.timestamp.localeCompare(a.observation.timestamp))\n .slice(0, limit);\n}\n\nexport async function searchMemory(query: string, options: MemorySearchOptions = {}): Promise<MemorySearchResult[]> {\n const limit = options.limit ?? 20;\n\n // --target fans out per matched workspace (which may span several\n // projects, each with its own FTS shard) instead of per project — the\n // caller has already resolved targetPath via listWorkspacesForPath, so a\n // zero-match \"no workspaces target <dir>\" note can be printed without\n // ever reaching the FTS index.\n if (options.targetPath) {\n const workspaces = listWorkspacesForPath(options.targetPath);\n const perWorkspace = await Promise.all(workspaces.map((workspace) => searchProjectScope({\n query,\n projectId: workspace.projectId,\n workspaceId: workspace.id,\n sibling: options.sibling,\n includeArchived: options.includeArchived,\n tags: options.tag ? [options.tag] : undefined,\n limit,\n })));\n return mergeRankedResults(perWorkspace, limit);\n }\n\n let resolvedWorkspaceId: string | undefined;\n let resolvedWorkspaceProjectId: string | undefined;\n if (options.workspace) {\n const resolution = resolveWorkspaceRef(options.workspace);\n if (resolution.ambiguous) {\n throw new Error(`Multiple workspaces named '${options.workspace}' found (projects: ${resolution.matches.map((w) => w.projectId).join(', ')}); use the workspace id instead.`);\n }\n if (resolution.workspace) {\n resolvedWorkspaceId = resolution.workspace.id;\n resolvedWorkspaceProjectId = resolution.workspace.projectId;\n } else {\n // Matches neither an id nor a name — keep filtering by the literal ref\n // (which then matches no FTS rows) rather than throwing or silently\n // searching the whole project unfiltered.\n resolvedWorkspaceId = options.workspace;\n }\n }\n\n const projectIds = options.global\n ? listProjects().map((project) => project.id)\n : [resolvedWorkspaceProjectId ?? options.project ?? DEFAULT_PROJECT_ID];\n // sibling mode needs a workspaceId to exclude even when the caller only\n // passed --issue (the CLI's usual sibling invocation) — resolve it the same\n // way the pre-FTS implementation did.\n const workspaceIdForIssue = options.issue ? getWorkspaceForIssue(options.issue)?.id : undefined;\n\n const perProject = await Promise.all(projectIds.map((projectId) => searchProjectScope({\n query,\n projectId,\n workspaceId: resolvedWorkspaceId ?? workspaceIdForIssue,\n issueId: options.issue,\n sibling: options.sibling,\n includeArchived: options.includeArchived,\n tags: options.tag ? [options.tag] : undefined,\n limit,\n })));\n\n return mergeRankedResults(perProject, limit);\n}\n\n/** Resolve an FTS hit back to its full MemoryObservation by reading only the one day-file it points at. */\nasync function resolveObservationFromHit(\n projectId: string,\n hit: MemorySearchHit,\n fileCache: Map<string, MemoryObservation[]>,\n): Promise<MemoryObservation | null> {\n const filePath = resolveObservationsFile(projectId, hit.workspaceId, hit.entryDate);\n let observations = fileCache.get(filePath);\n if (!observations) {\n observations = await readObservationsFile(filePath);\n fileCache.set(filePath, observations);\n }\n return observations.find((observation) => observation.id === hit.source) ?? null;\n}\n\n/**\n * Issue-shaped facade over `getMemoryStatusForWorkspace`, retained as the\n * pre-PAN-3286 public entry point (the CLI now resolves a workspace first).\n */\nexport async function getMemoryStatus(projectId: string, issueId: string): Promise<MemoryStatus | undefined> {\n const workspaceId = getWorkspaceForIssue(issueId)?.id;\n if (!workspaceId) return undefined;\n return getMemoryStatusForWorkspace(projectId, workspaceId);\n}\n\nexport interface MemoryWorkspaceTargetInput {\n /**\n * `--project`, used only by the issue-positional arm so its memory-root\n * resolution stays byte-identical to `getMemoryStatus` (PAN-3286 FR-5).\n */\n projectId?: string;\n issueId?: string;\n workspaceRef?: string;\n cwd?: string;\n}\n\nexport interface MemoryWorkspaceTarget {\n projectId: string;\n workspaceId: string;\n workspaceName: string;\n issueId: string | null;\n /** What to print in messages and summary titles: the issue id, else the workspace name. */\n label: string;\n}\n\n/**\n * The three memory addressing modes, in precedence order: `--workspace <ref>`,\n * an issue positional, then the workspace owning the cwd (PAN-3286 FR-5).\n * Throws with an actionable message naming all three when nothing resolves.\n */\nexport function resolveMemoryWorkspaceTarget(input: MemoryWorkspaceTargetInput): MemoryWorkspaceTarget {\n if (input.workspaceRef) {\n const resolution = resolveWorkspaceRef(input.workspaceRef);\n if (resolution.ambiguous) {\n throw new Error(\n `Multiple workspaces named '${input.workspaceRef}' found (projects: ${resolution.matches.map((w) => w.projectId).join(', ')}); use the workspace id instead.`,\n );\n }\n if (!resolution.workspace) {\n throw new Error(`No workspace found with id or name '${input.workspaceRef}'`);\n }\n const workspace = resolution.workspace;\n return {\n projectId: workspace.projectId,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n issueId: workspace.issueId,\n label: workspace.issueId ?? workspace.name,\n };\n }\n\n if (input.issueId) {\n const workspace = getWorkspaceForIssue(input.issueId);\n // Message deliberately unchanged from the pre-PAN-3286 issue-only path.\n if (!workspace) throw new Error(`No workspace found for issue ${input.issueId}`);\n return {\n projectId: input.projectId ?? DEFAULT_PROJECT_ID,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n issueId: input.issueId,\n label: input.issueId,\n };\n }\n\n const cwd = input.cwd ?? process.cwd();\n const workspace = resolveWorkspaceForCwd(cwd);\n if (!workspace) {\n throw new Error(`No workspace is registered for ${cwd}. ${ADDRESSING_MODES_HINT}`);\n }\n return {\n projectId: workspace.projectId,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n issueId: workspace.issueId,\n label: workspace.issueId ?? workspace.name,\n };\n}\n\nexport interface MemoryTimelineOptions {\n /** Most recent N calendar days, counting today as day 1. */\n days?: number;\n limit?: number;\n now?: Date;\n}\n\n/** Default day window and row cap for `pan memory timeline` (PAN-3286 FR-8). */\nexport const MEMORY_TIMELINE_DEFAULT_DAYS = 7;\nexport const MEMORY_TIMELINE_DEFAULT_LIMIT = 50;\n\n/**\n * Chronological observations for a resolved workspace, oldest-first\n * (PAN-3286 FR-8). The window and cap default to the last 7 days and 50 rows.\n */\nexport async function getMemoryTimeline(\n projectId: string,\n workspaceId: string,\n options: MemoryTimelineOptions = {},\n): Promise<MemoryObservation[]> {\n return readObservationsSince(projectId, workspaceId, {\n days: options.days ?? MEMORY_TIMELINE_DEFAULT_DAYS,\n limit: options.limit ?? MEMORY_TIMELINE_DEFAULT_LIMIT,\n now: options.now,\n });\n}\n\nexport type MemoryReadResult =\n | { status: 'ok'; relativePath: string; content: string }\n | { status: 'absolute-path' }\n | { status: 'escapes-home' }\n | { status: 'unreadable' };\n\nexport interface MemoryReadOptions {\n /** 1-based first line to print. */\n from?: number;\n /** Maximum number of lines to print, counting from `from`. */\n lines?: number;\n}\n\n/**\n * Read a file from a workspace's memory home, refusing anything that is not a\n * regular file genuinely inside it (PAN-3286 FR-9, D-9, NFR-4).\n *\n * Containment is delegated to `readVerifiedPinFile`, which realpath-resolves\n * both the home and the target so an in-home symlink pointing outside is\n * rejected, and opens with `O_NOFOLLOW` so a final-component swap fails closed.\n * Absolute inputs are refused outright before that, so no absolute path can\n * ever be read even if it happens to land inside the home. Claude Code JSONL\n * session files under `~/.claude` are therefore unreachable by construction —\n * they live outside every memory home.\n */\nexport async function readMemoryFile(\n projectId: string,\n workspaceId: string,\n requestedPath: string,\n options: MemoryReadOptions = {},\n): Promise<MemoryReadResult> {\n if (isAbsolute(requestedPath)) return { status: 'absolute-path' };\n\n const memoryHome = resolveWorkspaceMemoryRoot(projectId, workspaceId);\n const relativePath = resolveContainedPinPath(memoryHome, requestedPath);\n if (relativePath === null) return { status: 'escapes-home' };\n\n const content = await readVerifiedPinFile(memoryHome, relativePath);\n if (content === null) return { status: 'unreadable' };\n\n return { status: 'ok', relativePath, content: sliceLines(content, options) };\n}\n\nfunction sliceLines(content: string, options: MemoryReadOptions): string {\n const from = Math.max(1, Math.floor(options.from ?? 1));\n if (from === 1 && options.lines === undefined) return content;\n\n const endedWithNewline = content.endsWith('\\n');\n const lines = content.split('\\n');\n if (endedWithNewline) lines.pop();\n\n const start = from - 1;\n const selected = options.lines === undefined\n ? lines.slice(start)\n : lines.slice(start, start + Math.max(0, Math.floor(options.lines)));\n return selected.length === 0 ? '' : `${selected.join('\\n')}\\n`;\n}\n\n/** Current status for an already-resolved workspace (PAN-3286 FR-5). */\nexport async function getMemoryStatusForWorkspace(projectId: string, workspaceId: string): Promise<MemoryStatus | undefined> {\n return readCurrentStatus(projectId, workspaceId);\n}\n\n/**\n * Archived statuses newest-first, capped at `MEMORY_STATUS_HISTORY_LIMIT`\n * (PAN-3286 FR-6). The rollup writer prunes the on-disk archive to its three\n * most recent entries, so a larger request returns whatever is still retained.\n */\nexport async function getMemoryStatusHistory(\n projectId: string,\n workspaceId: string,\n limit: number,\n): Promise<ArchivedStatusEntry[]> {\n const bounded = Math.min(Math.max(1, Math.floor(limit)), MEMORY_STATUS_HISTORY_LIMIT);\n return readArchivedStatusEntries(projectId, workspaceId, bounded);\n}\n\nexport async function createResetMarker(input: {\n projectId?: string;\n scope: ResetMarker['scope'];\n scopeId: string;\n reason: string;\n fromTimestamp?: string;\n id?: string;\n createdAt?: string;\n emitResetMarkerCreated?: (marker: ResetMarker, timestamp: string) => void | Promise<void>;\n}): Promise<ResetMarker> {\n const projectId = input.projectId ?? DEFAULT_PROJECT_ID;\n const marker: ResetMarker = {\n id: input.id ?? randomUUID(),\n scope: input.scope,\n scopeId: input.scopeId,\n fromTimestamp: input.fromTimestamp ?? new Date().toISOString(),\n reason: input.reason,\n createdAt: input.createdAt ?? new Date().toISOString(),\n };\n const path = join(resolveMemoryRoot(projectId), 'reset-markers.json');\n const markers = await readJsonFile<ResetMarker[]>(path, []);\n await writeJsonAtomically(path, [...markers, marker]);\n await writeResetMarkerToFtsDb(projectId, marker);\n await (input.emitResetMarkerCreated ?? emitResetMarkerCreated)(marker, marker.createdAt);\n return marker;\n}\n\nexport async function generateDailySummary(input: {\n projectId?: string;\n issueId?: string;\n /** `--workspace <id|name>`; when omitted the issue positional or cwd resolves the target. */\n workspaceRef?: string;\n cwd?: string;\n date?: string;\n}): Promise<DailySummaryResult> {\n const target = resolveMemoryWorkspaceTarget({\n projectId: input.projectId,\n issueId: input.issueId,\n workspaceRef: input.workspaceRef,\n cwd: input.cwd,\n });\n const projectId = target.projectId;\n const date = input.date ?? new Date().toISOString().slice(0, 10);\n const workspaceId = target.workspaceId;\n const path = join(resolveSummariesDir(projectId, workspaceId), `${date}.md`);\n const observations = await readObservationsFile(resolveObservationsFile(projectId, workspaceId, date));\n const existingMarkdown = await readTextFile(path);\n const previousObservationCount = existingMarkdown ? parseSummaryObservationCount(existingMarkdown) : null;\n\n if (observations.length < MIN_DAILY_SUMMARY_OBSERVATIONS) {\n return { status: 'insufficient-data', path, markdown: existingMarkdown ?? '', observationCount: observations.length, previousObservationCount };\n }\n\n if (existingMarkdown && previousObservationCount !== null && observations.length - previousObservationCount < DAILY_SUMMARY_REGENERATION_OBSERVATIONS) {\n return { status: 'up-to-date', path, markdown: existingMarkdown, observationCount: observations.length, previousObservationCount };\n }\n\n const markdown = buildDailySummaryMarkdown(target.label, date, observations);\n await ensureParentDir(path);\n await writeFile(path, markdown, 'utf8');\n await indexDailySummary(projectId, target, date, observations, markdown);\n await mirrorDailySummary(projectId, target.workspaceName, date, markdown);\n return { status: 'generated', path, markdown, observationCount: observations.length, previousObservationCount };\n}\n\nexport async function runMemoryDoctor(options: MemoryDoctorOptions = {}): Promise<MemoryDoctorResult> {\n const projectId = options.project ?? DEFAULT_PROJECT_ID;\n const now = options.now ?? new Date();\n const provider = await resolveExtractionProviderSelection();\n const rollupPendingThreshold = await getMemoryRollupPendingThreshold();\n const issues = await readIssueDoctorSnapshots(projectId);\n const activeAgents = await readActiveAgents();\n const staleActiveAgents = activeAgents\n .map((agent) => {\n const issue = issues.find((candidate) => candidate.issueId === agent.issueId);\n return { agentId: agent.id, issueId: agent.issueId, lastSuccess: issue?.health.last_success ?? null };\n })\n .filter((agent) => isStaleExtraction(agent.lastSuccess, now));\n\n return {\n exitCode: staleActiveAgents.length > 0 ? 1 : 0,\n provider,\n rollupPendingThreshold,\n issues,\n staleActiveAgents,\n };\n}\n\nexport async function readMemorySettingsSummary(): Promise<{\n rollupPendingThreshold: number;\n provider: Awaited<ReturnType<typeof resolveExtractionProviderSelection>>;\n}> {\n return {\n rollupPendingThreshold: (await loadMemorySettings()).rollupPendingThreshold,\n provider: await resolveExtractionProviderSelection(),\n };\n}\n\nasync function readWorkspaceObservations(projectId: string, workspaceId: string): Promise<MemoryObservation[]> {\n const observationsDir = dirname(resolveObservationsFile(projectId, workspaceId, new Date()));\n const files = (await readdir(observationsDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n })).filter((file) => file.endsWith('.jsonl')).sort();\n const nested = await Promise.all(files.map((file) => readObservationsFile(join(observationsDir, file))));\n return nested.flat();\n}\n\nasync function readObservationsFile(path: string): Promise<MemoryObservation[]> {\n let raw: string;\n try {\n raw = await readFile(path, 'utf8');\n } catch (error) {\n if (isEnoent(error)) return [];\n throw error;\n }\n return raw.split('\\n')\n .filter((line) => line.trim().length > 0)\n .map((line) => JSON.parse(line) as MemoryObservation);\n}\n\nasync function readTextFile(path: string): Promise<string | null> {\n try {\n return await readFile(path, 'utf8');\n } catch (error) {\n if (isEnoent(error)) return null;\n throw error;\n }\n}\n\nfunction buildDailySummaryMarkdown(issueId: string, date: string, observations: MemoryObservation[]): string {\n const actions = observations.filter((observation) => observation.actionStatus !== null);\n return [\n `# ${issueId} memory summary — ${date}`,\n '',\n `Observations: ${observations.length}`,\n `Action updates: ${actions.length}`,\n '',\n ...observations.map((observation) => [\n `## ${observation.timestamp}`,\n '',\n observation.actionStatus ? `**Action:** ${observation.actionStatus}` : '**Action:** none',\n '',\n observation.summary,\n '',\n observation.files.length > 0 ? `Files: ${observation.files.join(', ')}` : 'Files: none',\n observation.tags.length > 0 ? `Tags: ${observation.tags.join(', ')}` : 'Tags: none',\n '',\n ].join('\\n')),\n ].join('\\n');\n}\n\nfunction parseSummaryObservationCount(markdown: string): number | null {\n const match = markdown.match(/^Observations: (\\d+)$/m);\n return match ? Number(match[1]) : null;\n}\n\nasync function indexDailySummary(projectId: string, target: MemoryWorkspaceTarget, date: string, observations: MemoryObservation[], markdown: string): Promise<void> {\n const latest = observations.at(-1);\n const files = [...new Set(observations.flatMap((observation) => observation.files))].join(',');\n const tags = [...new Set(['memory', 'summary', ...observations.flatMap((observation) => observation.tags)])].join(',');\n const entryTime = latest?.timestamp.slice(11) ?? '00:00:00.000Z';\n // Issue-addressed summaries keep the pre-PAN-3286 issue-keyed identity;\n // workspace-addressed ones (no issue) key on workspace_id instead so their\n // reset-marker scope and per-day dedupe both resolve. Both column names are\n // literals from a closed set, never user input.\n const dedupeColumn = target.issueId ? 'issue_id' : 'workspace_id';\n const dedupeValue = target.issueId ?? target.workspaceId;\n await runMemoryFtsTransaction(projectId, [\n {\n method: 'run',\n sql: `\n DELETE FROM memory_fts\n WHERE project_id = ?\n AND ${dedupeColumn} = ?\n AND doc_type = 'summary'\n AND entry_date = ?\n `,\n params: [projectId, dedupeValue, date],\n },\n {\n method: 'run',\n sql: `\n INSERT INTO memory_fts (\n content,\n display_content,\n source,\n branch,\n entry_date,\n entry_time,\n entry_type,\n files,\n tags,\n doc_type,\n scope,\n project_id,\n workspace_id,\n issue_id,\n run_id,\n session_id,\n agent_role,\n agent_harness\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n params: [\n markdown,\n markdown,\n 'summary',\n latest?.gitBranch ?? '',\n date,\n entryTime,\n 'memory-summary',\n files,\n tags,\n 'summary',\n target.issueId ? 'issue' : 'workspace',\n projectId,\n target.workspaceId,\n target.issueId ?? '',\n latest?.runId ?? '',\n latest?.sessionId ?? '',\n latest?.agentRole ?? '',\n latest?.agentHarness ?? '',\n ],\n },\n ]);\n}\n\nasync function readIssueDoctorSnapshots(projectId: string): Promise<MemoryDoctorResult['issues']> {\n const issueWorkspaces = listWorkspaces({ projectId, kind: 'issue' }).filter((ws): ws is typeof ws & { issueId: string } => ws.issueId !== null);\n return Promise.all(issueWorkspaces.map(async (workspace) => {\n const health = await readJsonFile<MemoryHealthSnapshot>(getMemoryHealthPath({ projectId, workspaceId: workspace.id }), emptyHealth());\n const pendingCount = await countJsonFiles(resolvePendingDir(projectId, workspace.id));\n const observations = await readWorkspaceObservations(projectId, workspace.id);\n return {\n projectId,\n issueId: workspace.issueId,\n health,\n pendingCount,\n lastObservation: observations.at(-1)?.timestamp ?? null,\n };\n }));\n}\n\nasync function readActiveAgents(): Promise<Array<{ id: string; issueId: string }>> {\n const agentsDir = join(getOverdeckHome(), 'agents');\n const entries = await readdir(agentsDir, { withFileTypes: true }).catch((error: unknown) => {\n if (isEnoent(error)) return [];\n throw error;\n });\n const agents: Array<{ id: string; issueId: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const state = getAgentStateSync(entry.name);\n if ((state?.status === 'running' || state?.status === 'starting') && state?.issueId) {\n agents.push({ id: entry.name, issueId: state.issueId });\n }\n }\n return agents;\n}\n\nasync function countJsonFiles(path: string): Promise<number> {\n const entries = await readdir(path).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n });\n return entries.filter((entry) => entry.endsWith('.json') && !entry.startsWith('.')).length;\n}\n\nasync function readJsonFile<T>(path: string, fallback: T): Promise<T> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as T;\n } catch (error) {\n if (isEnoent(error)) return fallback;\n throw error;\n }\n}\n\nasync function writeJsonAtomically(path: string, value: unknown): Promise<void> {\n await ensureParentDir(path);\n const tempPath = `${dirname(path)}/.${randomUUID()}.tmp`;\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tempPath, path);\n}\n\nasync function writeResetMarkerToFtsDb(projectId: string, marker: ResetMarker): Promise<void> {\n await withMemoryFtsDatabase(projectId, async (db) => {\n await db.prepare(`\n INSERT INTO reset_markers (scope, scope_id, from_timestamp, reason, created_at)\n VALUES (?, ?, ?, ?, ?)\n `).run(marker.scope, marker.scopeId, marker.fromTimestamp, marker.reason, marker.createdAt);\n });\n}\n\nasync function emitResetMarkerCreated(marker: ResetMarker, timestamp: string): Promise<void> {\n const { initEventStore } = await import('../../dashboard/server/event-store.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'memory.reset_marker_created',\n timestamp,\n payload: { marker },\n });\n}\n\nfunction isStaleExtraction(lastSuccess: string | null, now: Date): boolean {\n if (!lastSuccess) return true;\n const timestamp = Date.parse(lastSuccess);\n return Number.isNaN(timestamp) || now.getTime() - timestamp > 60 * 60 * 1000;\n}\n\nfunction emptyHealth(): MemoryHealthSnapshot {\n return {\n status: 'healthy',\n last_success: null,\n last_failure: null,\n extractions_attempted: 0,\n extractions_succeeded: 0,\n failed_by_reason: {},\n };\n}\n\nfunction isEnoent(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli-DCbVrSsg.js","names":[],"sources":["../../src/lib/memory/cli.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport { readdir, readFile, rename, writeFile } from 'fs/promises';\nimport { dirname, isAbsolute, join } from 'path';\nimport type { MemoryObservation, MemoryStatus, ResetMarker } from '@overdeck/contracts';\nimport { getOverdeckHome } from '../paths.js';\nimport { runMemoryFtsTransaction, withMemoryFtsDatabase } from './fts-db.js';\nimport { resolveExtractionProviderSelection } from './providers/index.js';\nimport { getMemoryRollupPendingThreshold, loadMemorySettings } from './settings.js';\nimport {\n ensureParentDir,\n resolveMemoryRoot,\n resolveObservationsFile,\n resolvePendingDir,\n resolveSummariesDir,\n resolveWorkspaceMemoryRoot,\n} from './paths.js';\nimport { readVerifiedPinFile, resolveContainedPinPath } from './pin-path.js';\nimport { getMemoryHealthPath, type MemoryHealthSnapshot } from './health.js';\nimport { mirrorDailySummary } from './state-mirror.js';\nimport { readArchivedStatusEntries, readCurrentStatus, readObservationsSince, type ArchivedStatusEntry } from './rollup.js';\nimport { getAgentStateSync } from '../agents.js';\nimport {\n getWorkspaceForIssue,\n listProjects,\n listWorkspaces,\n listWorkspacesForPath,\n resolveWorkspaceForCwd,\n resolveWorkspaceRef,\n} from '../workspaces/resolver.js';\nimport { searchMemory as searchMemoryFts, type MemorySearchHit } from './search.js';\n\nconst DEFAULT_PROJECT_ID = 'overdeck';\nconst MIN_DAILY_SUMMARY_OBSERVATIONS = 3;\nconst DAILY_SUMMARY_REGENERATION_OBSERVATIONS = 20;\n/** Upper bound on `pan memory status --history <n>` (PAN-3286 D-8). */\nexport const MEMORY_STATUS_HISTORY_LIMIT = 50;\n\nconst ADDRESSING_MODES_HINT =\n 'Address a workspace with --workspace <id|name>, with an issue positional, or by running the command from inside the workspace directory.';\n\nexport interface MemorySearchOptions {\n project?: string;\n workspace?: string;\n issue?: string;\n tag?: string;\n sibling?: boolean;\n global?: boolean;\n limit?: number;\n includeArchived?: boolean;\n /** All workspaces targeting this directory (PAN-3286 FR-4) — resolved by the caller via listWorkspacesForPath. */\n targetPath?: string;\n}\n\nexport interface MemorySearchResult {\n observation: MemoryObservation;\n score: number;\n}\n\nexport type DailySummaryStatus = 'generated' | 'insufficient-data' | 'up-to-date';\n\nexport interface DailySummaryResult {\n status: DailySummaryStatus;\n path: string;\n markdown: string;\n observationCount: number;\n previousObservationCount: number | null;\n}\n\nexport interface MemoryDoctorOptions {\n project?: string;\n now?: Date;\n}\n\nexport interface MemoryDoctorResult {\n exitCode: number;\n provider: Awaited<ReturnType<typeof resolveExtractionProviderSelection>>;\n rollupPendingThreshold: number;\n issues: Array<{\n projectId: string;\n issueId: string;\n health: MemoryHealthSnapshot;\n pendingCount: number;\n lastObservation: string | null;\n }>;\n staleActiveAgents: Array<{ agentId: string; issueId: string; lastSuccess: string | null }>;\n}\n\n/**\n * PAN-1990 FR-9: search runs against the existing memory_fts index (every\n * observation write indexes into it — see observations.ts's indexObservation)\n * instead of reading every daily JSONL file for every workspace into memory\n * and sorting the full corpus. Runtime and memory scale with the FTS-bounded\n * result set (limit), not with total history. Reset-marker filtering also\n * moves into the FTS query itself (search.ts already does this in SQL).\n *\n * `--workspace <id|name>` (D-3) resolves either a workspace UUID or a\n * workspace name, then uses ITS OWNING project before searching, so a\n * workspace registered under a project other than `overdeck` is actually\n * searched instead of silently querying the default project and finding\n * nothing. A name that matches more than one workspace across projects is\n * reported as an ambiguity error rather than guessing.\n */\n/** Search one project (optionally scoped to one workspace/issue within it) and resolve hits back to observations. */\nasync function searchProjectScope(input: {\n query: string;\n projectId: string;\n workspaceId?: string;\n issueId?: string;\n sibling?: boolean;\n includeArchived?: boolean;\n tags?: string[];\n limit: number;\n}): Promise<MemorySearchResult[]> {\n const hits = await searchMemoryFts({\n query: input.query,\n projectId: input.projectId,\n workspaceId: input.workspaceId,\n issueId: input.issueId,\n sibling: input.sibling,\n includeArchived: input.includeArchived,\n tags: input.tags,\n // The shared FTS index also carries daily-summary hits (doc_type\n // 'summary'), which this command can't resolve back to a\n // MemoryObservation — restrict to observations so a summary ranked\n // above matching observations doesn't consume the limit and vanish.\n docType: 'observation',\n limit: input.limit,\n });\n\n const fileCache = new Map<string, MemoryObservation[]>();\n const pairs = await Promise.all(hits.map(async (hit) => ({\n hit,\n observation: await resolveObservationFromHit(input.projectId, hit, fileCache),\n })));\n return pairs\n .filter((pair): pair is { hit: MemorySearchHit; observation: MemoryObservation } => pair.observation !== null)\n .map((pair) => ({ observation: pair.observation, score: pair.hit.rankScore }));\n}\n\n/** Merge per-scope result sets by rank score (ties broken by recency), capped at limit. */\nfunction mergeRankedResults(perScope: MemorySearchResult[][], limit: number): MemorySearchResult[] {\n return perScope.flat()\n .sort((a, b) => b.score - a.score || b.observation.timestamp.localeCompare(a.observation.timestamp))\n .slice(0, limit);\n}\n\nexport async function searchMemory(query: string, options: MemorySearchOptions = {}): Promise<MemorySearchResult[]> {\n const limit = options.limit ?? 20;\n\n // --target fans out per matched workspace (which may span several\n // projects, each with its own FTS shard) instead of per project — the\n // caller has already resolved targetPath via listWorkspacesForPath, so a\n // zero-match \"no workspaces target <dir>\" note can be printed without\n // ever reaching the FTS index.\n if (options.targetPath) {\n const workspaces = listWorkspacesForPath(options.targetPath);\n const perWorkspace = await Promise.all(workspaces.map((workspace) => searchProjectScope({\n query,\n projectId: workspace.projectId,\n workspaceId: workspace.id,\n sibling: options.sibling,\n includeArchived: options.includeArchived,\n tags: options.tag ? [options.tag] : undefined,\n limit,\n })));\n return mergeRankedResults(perWorkspace, limit);\n }\n\n let resolvedWorkspaceId: string | undefined;\n let resolvedWorkspaceProjectId: string | undefined;\n if (options.workspace) {\n const resolution = resolveWorkspaceRef(options.workspace);\n if (resolution.ambiguous) {\n throw new Error(`Multiple workspaces named '${options.workspace}' found (projects: ${resolution.matches.map((w) => w.projectId).join(', ')}); use the workspace id instead.`);\n }\n if (resolution.workspace) {\n resolvedWorkspaceId = resolution.workspace.id;\n resolvedWorkspaceProjectId = resolution.workspace.projectId;\n } else {\n // Matches neither an id nor a name — keep filtering by the literal ref\n // (which then matches no FTS rows) rather than throwing or silently\n // searching the whole project unfiltered.\n resolvedWorkspaceId = options.workspace;\n }\n }\n\n const projectIds = options.global\n ? listProjects().map((project) => project.id)\n : [resolvedWorkspaceProjectId ?? options.project ?? DEFAULT_PROJECT_ID];\n // sibling mode needs a workspaceId to exclude even when the caller only\n // passed --issue (the CLI's usual sibling invocation) — resolve it the same\n // way the pre-FTS implementation did.\n const workspaceIdForIssue = options.issue ? getWorkspaceForIssue(options.issue)?.id : undefined;\n\n const perProject = await Promise.all(projectIds.map((projectId) => searchProjectScope({\n query,\n projectId,\n workspaceId: resolvedWorkspaceId ?? workspaceIdForIssue,\n issueId: options.issue,\n sibling: options.sibling,\n includeArchived: options.includeArchived,\n tags: options.tag ? [options.tag] : undefined,\n limit,\n })));\n\n return mergeRankedResults(perProject, limit);\n}\n\n/** Resolve an FTS hit back to its full MemoryObservation by reading only the one day-file it points at. */\nasync function resolveObservationFromHit(\n projectId: string,\n hit: MemorySearchHit,\n fileCache: Map<string, MemoryObservation[]>,\n): Promise<MemoryObservation | null> {\n const filePath = resolveObservationsFile(projectId, hit.workspaceId, hit.entryDate);\n let observations = fileCache.get(filePath);\n if (!observations) {\n observations = await readObservationsFile(filePath);\n fileCache.set(filePath, observations);\n }\n return observations.find((observation) => observation.id === hit.source) ?? null;\n}\n\n/**\n * Issue-shaped facade over `getMemoryStatusForWorkspace`, retained as the\n * pre-PAN-3286 public entry point (the CLI now resolves a workspace first).\n */\nexport async function getMemoryStatus(projectId: string, issueId: string): Promise<MemoryStatus | undefined> {\n const workspaceId = getWorkspaceForIssue(issueId)?.id;\n if (!workspaceId) return undefined;\n return getMemoryStatusForWorkspace(projectId, workspaceId);\n}\n\nexport interface MemoryWorkspaceTargetInput {\n /**\n * `--project`, used only by the issue-positional arm so its memory-root\n * resolution stays byte-identical to `getMemoryStatus` (PAN-3286 FR-5).\n */\n projectId?: string;\n issueId?: string;\n workspaceRef?: string;\n cwd?: string;\n}\n\nexport interface MemoryWorkspaceTarget {\n projectId: string;\n workspaceId: string;\n workspaceName: string;\n issueId: string | null;\n /** What to print in messages and summary titles: the issue id, else the workspace name. */\n label: string;\n}\n\n/**\n * The three memory addressing modes, in precedence order: `--workspace <ref>`,\n * an issue positional, then the workspace owning the cwd (PAN-3286 FR-5).\n * Throws with an actionable message naming all three when nothing resolves.\n */\nexport function resolveMemoryWorkspaceTarget(input: MemoryWorkspaceTargetInput): MemoryWorkspaceTarget {\n if (input.workspaceRef) {\n const resolution = resolveWorkspaceRef(input.workspaceRef);\n if (resolution.ambiguous) {\n throw new Error(\n `Multiple workspaces named '${input.workspaceRef}' found (projects: ${resolution.matches.map((w) => w.projectId).join(', ')}); use the workspace id instead.`,\n );\n }\n if (!resolution.workspace) {\n throw new Error(`No workspace found with id or name '${input.workspaceRef}'`);\n }\n const workspace = resolution.workspace;\n return {\n projectId: workspace.projectId,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n issueId: workspace.issueId,\n label: workspace.issueId ?? workspace.name,\n };\n }\n\n if (input.issueId) {\n const workspace = getWorkspaceForIssue(input.issueId);\n // Message deliberately unchanged from the pre-PAN-3286 issue-only path.\n if (!workspace) throw new Error(`No workspace found for issue ${input.issueId}`);\n return {\n projectId: input.projectId ?? DEFAULT_PROJECT_ID,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n issueId: input.issueId,\n label: input.issueId,\n };\n }\n\n const cwd = input.cwd ?? process.cwd();\n const workspace = resolveWorkspaceForCwd(cwd);\n if (!workspace) {\n throw new Error(`No workspace is registered for ${cwd}. ${ADDRESSING_MODES_HINT}`);\n }\n return {\n projectId: workspace.projectId,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n issueId: workspace.issueId,\n label: workspace.issueId ?? workspace.name,\n };\n}\n\nexport interface MemoryTimelineOptions {\n /** Most recent N calendar days, counting today as day 1. */\n days?: number;\n limit?: number;\n now?: Date;\n}\n\n/** Default day window and row cap for `pan memory timeline` (PAN-3286 FR-8). */\nexport const MEMORY_TIMELINE_DEFAULT_DAYS = 7;\nexport const MEMORY_TIMELINE_DEFAULT_LIMIT = 50;\n\n/**\n * Chronological observations for a resolved workspace, oldest-first\n * (PAN-3286 FR-8). The window and cap default to the last 7 days and 50 rows.\n */\nexport async function getMemoryTimeline(\n projectId: string,\n workspaceId: string,\n options: MemoryTimelineOptions = {},\n): Promise<MemoryObservation[]> {\n return readObservationsSince(projectId, workspaceId, {\n days: options.days ?? MEMORY_TIMELINE_DEFAULT_DAYS,\n limit: options.limit ?? MEMORY_TIMELINE_DEFAULT_LIMIT,\n now: options.now,\n });\n}\n\nexport type MemoryReadResult =\n | { status: 'ok'; relativePath: string; content: string }\n | { status: 'absolute-path' }\n | { status: 'escapes-home' }\n | { status: 'unreadable' };\n\nexport interface MemoryReadOptions {\n /** 1-based first line to print. */\n from?: number;\n /** Maximum number of lines to print, counting from `from`. */\n lines?: number;\n}\n\n/**\n * Read a file from a workspace's memory home, refusing anything that is not a\n * regular file genuinely inside it (PAN-3286 FR-9, D-9, NFR-4).\n *\n * Containment is delegated to `readVerifiedPinFile`, which realpath-resolves\n * both the home and the target so an in-home symlink pointing outside is\n * rejected, and opens with `O_NOFOLLOW` so a final-component swap fails closed.\n * Absolute inputs are refused outright before that, so no absolute path can\n * ever be read even if it happens to land inside the home. Claude Code JSONL\n * session files under `~/.claude` are therefore unreachable by construction —\n * they live outside every memory home.\n */\nexport async function readMemoryFile(\n projectId: string,\n workspaceId: string,\n requestedPath: string,\n options: MemoryReadOptions = {},\n): Promise<MemoryReadResult> {\n if (isAbsolute(requestedPath)) return { status: 'absolute-path' };\n\n const memoryHome = resolveWorkspaceMemoryRoot(projectId, workspaceId);\n const relativePath = resolveContainedPinPath(memoryHome, requestedPath);\n if (relativePath === null) return { status: 'escapes-home' };\n\n const content = await readVerifiedPinFile(memoryHome, relativePath);\n if (content === null) return { status: 'unreadable' };\n\n return { status: 'ok', relativePath, content: sliceLines(content, options) };\n}\n\nfunction sliceLines(content: string, options: MemoryReadOptions): string {\n const from = Math.max(1, Math.floor(options.from ?? 1));\n if (from === 1 && options.lines === undefined) return content;\n\n const endedWithNewline = content.endsWith('\\n');\n const lines = content.split('\\n');\n if (endedWithNewline) lines.pop();\n\n const start = from - 1;\n const selected = options.lines === undefined\n ? lines.slice(start)\n : lines.slice(start, start + Math.max(0, Math.floor(options.lines)));\n return selected.length === 0 ? '' : `${selected.join('\\n')}\\n`;\n}\n\n/** Current status for an already-resolved workspace (PAN-3286 FR-5). */\nexport async function getMemoryStatusForWorkspace(projectId: string, workspaceId: string): Promise<MemoryStatus | undefined> {\n return readCurrentStatus(projectId, workspaceId);\n}\n\n/**\n * Archived statuses newest-first, capped at `MEMORY_STATUS_HISTORY_LIMIT`\n * (PAN-3286 FR-6). The rollup writer prunes the on-disk archive to its three\n * most recent entries, so a larger request returns whatever is still retained.\n */\nexport async function getMemoryStatusHistory(\n projectId: string,\n workspaceId: string,\n limit: number,\n): Promise<ArchivedStatusEntry[]> {\n const bounded = Math.min(Math.max(1, Math.floor(limit)), MEMORY_STATUS_HISTORY_LIMIT);\n return readArchivedStatusEntries(projectId, workspaceId, bounded);\n}\n\nexport async function createResetMarker(input: {\n projectId?: string;\n scope: ResetMarker['scope'];\n scopeId: string;\n reason: string;\n fromTimestamp?: string;\n id?: string;\n createdAt?: string;\n emitResetMarkerCreated?: (marker: ResetMarker, timestamp: string) => void | Promise<void>;\n}): Promise<ResetMarker> {\n const projectId = input.projectId ?? DEFAULT_PROJECT_ID;\n const marker: ResetMarker = {\n id: input.id ?? randomUUID(),\n scope: input.scope,\n scopeId: input.scopeId,\n fromTimestamp: input.fromTimestamp ?? new Date().toISOString(),\n reason: input.reason,\n createdAt: input.createdAt ?? new Date().toISOString(),\n };\n const path = join(resolveMemoryRoot(projectId), 'reset-markers.json');\n const markers = await readJsonFile<ResetMarker[]>(path, []);\n await writeJsonAtomically(path, [...markers, marker]);\n await writeResetMarkerToFtsDb(projectId, marker);\n await (input.emitResetMarkerCreated ?? emitResetMarkerCreated)(marker, marker.createdAt);\n return marker;\n}\n\nexport async function generateDailySummary(input: {\n projectId?: string;\n issueId?: string;\n /** `--workspace <id|name>`; when omitted the issue positional or cwd resolves the target. */\n workspaceRef?: string;\n cwd?: string;\n date?: string;\n}): Promise<DailySummaryResult> {\n const target = resolveMemoryWorkspaceTarget({\n projectId: input.projectId,\n issueId: input.issueId,\n workspaceRef: input.workspaceRef,\n cwd: input.cwd,\n });\n const projectId = target.projectId;\n const date = input.date ?? new Date().toISOString().slice(0, 10);\n const workspaceId = target.workspaceId;\n const path = join(resolveSummariesDir(projectId, workspaceId), `${date}.md`);\n const observations = await readObservationsFile(resolveObservationsFile(projectId, workspaceId, date));\n const existingMarkdown = await readTextFile(path);\n const previousObservationCount = existingMarkdown ? parseSummaryObservationCount(existingMarkdown) : null;\n\n if (observations.length < MIN_DAILY_SUMMARY_OBSERVATIONS) {\n return { status: 'insufficient-data', path, markdown: existingMarkdown ?? '', observationCount: observations.length, previousObservationCount };\n }\n\n if (existingMarkdown && previousObservationCount !== null && observations.length - previousObservationCount < DAILY_SUMMARY_REGENERATION_OBSERVATIONS) {\n return { status: 'up-to-date', path, markdown: existingMarkdown, observationCount: observations.length, previousObservationCount };\n }\n\n const markdown = buildDailySummaryMarkdown(target.label, date, observations);\n await ensureParentDir(path);\n await writeFile(path, markdown, 'utf8');\n await indexDailySummary(projectId, target, date, observations, markdown);\n await mirrorDailySummary(projectId, target.workspaceName, date, markdown);\n return { status: 'generated', path, markdown, observationCount: observations.length, previousObservationCount };\n}\n\nexport async function runMemoryDoctor(options: MemoryDoctorOptions = {}): Promise<MemoryDoctorResult> {\n const projectId = options.project ?? DEFAULT_PROJECT_ID;\n const now = options.now ?? new Date();\n const provider = await resolveExtractionProviderSelection();\n const rollupPendingThreshold = await getMemoryRollupPendingThreshold();\n const issues = await readIssueDoctorSnapshots(projectId);\n const activeAgents = await readActiveAgents();\n const staleActiveAgents = activeAgents\n .map((agent) => {\n const issue = issues.find((candidate) => candidate.issueId === agent.issueId);\n return { agentId: agent.id, issueId: agent.issueId, lastSuccess: issue?.health.last_success ?? null };\n })\n .filter((agent) => isStaleExtraction(agent.lastSuccess, now));\n\n return {\n exitCode: staleActiveAgents.length > 0 ? 1 : 0,\n provider,\n rollupPendingThreshold,\n issues,\n staleActiveAgents,\n };\n}\n\nexport async function readMemorySettingsSummary(): Promise<{\n rollupPendingThreshold: number;\n provider: Awaited<ReturnType<typeof resolveExtractionProviderSelection>>;\n}> {\n return {\n rollupPendingThreshold: (await loadMemorySettings()).rollupPendingThreshold,\n provider: await resolveExtractionProviderSelection(),\n };\n}\n\nasync function readWorkspaceObservations(projectId: string, workspaceId: string): Promise<MemoryObservation[]> {\n const observationsDir = dirname(resolveObservationsFile(projectId, workspaceId, new Date()));\n const files = (await readdir(observationsDir).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n })).filter((file) => file.endsWith('.jsonl')).sort();\n const nested = await Promise.all(files.map((file) => readObservationsFile(join(observationsDir, file))));\n return nested.flat();\n}\n\nasync function readObservationsFile(path: string): Promise<MemoryObservation[]> {\n let raw: string;\n try {\n raw = await readFile(path, 'utf8');\n } catch (error) {\n if (isEnoent(error)) return [];\n throw error;\n }\n return raw.split('\\n')\n .filter((line) => line.trim().length > 0)\n .map((line) => JSON.parse(line) as MemoryObservation);\n}\n\nasync function readTextFile(path: string): Promise<string | null> {\n try {\n return await readFile(path, 'utf8');\n } catch (error) {\n if (isEnoent(error)) return null;\n throw error;\n }\n}\n\nfunction buildDailySummaryMarkdown(issueId: string, date: string, observations: MemoryObservation[]): string {\n const actions = observations.filter((observation) => observation.actionStatus !== null);\n return [\n `# ${issueId} memory summary — ${date}`,\n '',\n `Observations: ${observations.length}`,\n `Action updates: ${actions.length}`,\n '',\n ...observations.map((observation) => [\n `## ${observation.timestamp}`,\n '',\n observation.actionStatus ? `**Action:** ${observation.actionStatus}` : '**Action:** none',\n '',\n observation.summary,\n '',\n observation.files.length > 0 ? `Files: ${observation.files.join(', ')}` : 'Files: none',\n observation.tags.length > 0 ? `Tags: ${observation.tags.join(', ')}` : 'Tags: none',\n '',\n ].join('\\n')),\n ].join('\\n');\n}\n\nfunction parseSummaryObservationCount(markdown: string): number | null {\n const match = markdown.match(/^Observations: (\\d+)$/m);\n return match ? Number(match[1]) : null;\n}\n\nasync function indexDailySummary(projectId: string, target: MemoryWorkspaceTarget, date: string, observations: MemoryObservation[], markdown: string): Promise<void> {\n const latest = observations.at(-1);\n const files = [...new Set(observations.flatMap((observation) => observation.files))].join(',');\n const tags = [...new Set(['memory', 'summary', ...observations.flatMap((observation) => observation.tags)])].join(',');\n const entryTime = latest?.timestamp.slice(11) ?? '00:00:00.000Z';\n // Issue-addressed summaries keep the pre-PAN-3286 issue-keyed identity;\n // workspace-addressed ones (no issue) key on workspace_id instead so their\n // reset-marker scope and per-day dedupe both resolve. Both column names are\n // literals from a closed set, never user input.\n const dedupeColumn = target.issueId ? 'issue_id' : 'workspace_id';\n const dedupeValue = target.issueId ?? target.workspaceId;\n await runMemoryFtsTransaction(projectId, [\n {\n method: 'run',\n sql: `\n DELETE FROM memory_fts\n WHERE project_id = ?\n AND ${dedupeColumn} = ?\n AND doc_type = 'summary'\n AND entry_date = ?\n `,\n params: [projectId, dedupeValue, date],\n },\n {\n method: 'run',\n sql: `\n INSERT INTO memory_fts (\n content,\n display_content,\n source,\n branch,\n entry_date,\n entry_time,\n entry_type,\n files,\n tags,\n doc_type,\n scope,\n project_id,\n workspace_id,\n issue_id,\n run_id,\n session_id,\n agent_role,\n agent_harness\n ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `,\n params: [\n markdown,\n markdown,\n 'summary',\n latest?.gitBranch ?? '',\n date,\n entryTime,\n 'memory-summary',\n files,\n tags,\n 'summary',\n target.issueId ? 'issue' : 'workspace',\n projectId,\n target.workspaceId,\n target.issueId ?? '',\n latest?.runId ?? '',\n latest?.sessionId ?? '',\n latest?.agentRole ?? '',\n latest?.agentHarness ?? '',\n ],\n },\n ]);\n}\n\nasync function readIssueDoctorSnapshots(projectId: string): Promise<MemoryDoctorResult['issues']> {\n const issueWorkspaces = listWorkspaces({ projectId, kind: 'issue' }).filter((ws): ws is typeof ws & { issueId: string } => ws.issueId !== null);\n return Promise.all(issueWorkspaces.map(async (workspace) => {\n const health = await readJsonFile<MemoryHealthSnapshot>(getMemoryHealthPath({ projectId, workspaceId: workspace.id }), emptyHealth());\n const pendingCount = await countJsonFiles(resolvePendingDir(projectId, workspace.id));\n const observations = await readWorkspaceObservations(projectId, workspace.id);\n return {\n projectId,\n issueId: workspace.issueId,\n health,\n pendingCount,\n lastObservation: observations.at(-1)?.timestamp ?? null,\n };\n }));\n}\n\nasync function readActiveAgents(): Promise<Array<{ id: string; issueId: string }>> {\n const agentsDir = join(getOverdeckHome(), 'agents');\n const entries = await readdir(agentsDir, { withFileTypes: true }).catch((error: unknown) => {\n if (isEnoent(error)) return [];\n throw error;\n });\n const agents: Array<{ id: string; issueId: string }> = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n const state = getAgentStateSync(entry.name);\n if ((state?.status === 'running' || state?.status === 'starting') && state?.issueId) {\n agents.push({ id: entry.name, issueId: state.issueId });\n }\n }\n return agents;\n}\n\nasync function countJsonFiles(path: string): Promise<number> {\n const entries = await readdir(path).catch((error: unknown) => {\n if (isEnoent(error)) return [] as string[];\n throw error;\n });\n return entries.filter((entry) => entry.endsWith('.json') && !entry.startsWith('.')).length;\n}\n\nasync function readJsonFile<T>(path: string, fallback: T): Promise<T> {\n try {\n return JSON.parse(await readFile(path, 'utf8')) as T;\n } catch (error) {\n if (isEnoent(error)) return fallback;\n throw error;\n }\n}\n\nasync function writeJsonAtomically(path: string, value: unknown): Promise<void> {\n await ensureParentDir(path);\n const tempPath = `${dirname(path)}/.${randomUUID()}.tmp`;\n await writeFile(tempPath, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n await rename(tempPath, path);\n}\n\nasync function writeResetMarkerToFtsDb(projectId: string, marker: ResetMarker): Promise<void> {\n await withMemoryFtsDatabase(projectId, async (db) => {\n await db.prepare(`\n INSERT INTO reset_markers (scope, scope_id, from_timestamp, reason, created_at)\n VALUES (?, ?, ?, ?, ?)\n `).run(marker.scope, marker.scopeId, marker.fromTimestamp, marker.reason, marker.createdAt);\n });\n}\n\nasync function emitResetMarkerCreated(marker: ResetMarker, timestamp: string): Promise<void> {\n const { initEventStore } = await import('../../dashboard/server/event-store.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'memory.reset_marker_created',\n timestamp,\n payload: { marker },\n });\n}\n\nfunction isStaleExtraction(lastSuccess: string | null, now: Date): boolean {\n if (!lastSuccess) return true;\n const timestamp = Date.parse(lastSuccess);\n return Number.isNaN(timestamp) || now.getTime() - timestamp > 60 * 60 * 1000;\n}\n\nfunction emptyHealth(): MemoryHealthSnapshot {\n return {\n status: 'healthy',\n last_success: null,\n last_failure: null,\n extractions_attempted: 0,\n extractions_succeeded: 0,\n failed_by_reason: {},\n };\n}\n\nfunction isEnoent(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Gn as extractNumberSync, Jn as normalizeIssueIdSync, Kn as extractPrefixSync, qn as init_issue_id } from "./config-yaml-Dce9VUd6.js";
|
|
2
|
-
import { S as markAgentStoppedState, k as saveAgentState, m as getAgentState, q as init_agents } from "./agent-state-
|
|
2
|
+
import { S as markAgentStoppedState, k as saveAgentState, m as getAgentState, q as init_agents } from "./agent-state-B196fj1h.js";
|
|
3
3
|
import { a as stepSkipped, i as stepOk, n as init_types, r as stepFailed, t as getLinearApiKey } from "./types-JR7ZOtpL.js";
|
|
4
4
|
import { Effect } from "effect";
|
|
5
5
|
import { exec } from "child_process";
|
|
@@ -327,4 +327,4 @@ async function applyLabelLinearImpl(ctx, apiKey) {
|
|
|
327
327
|
//#endregion
|
|
328
328
|
export { closeIssue as t };
|
|
329
329
|
|
|
330
|
-
//# sourceMappingURL=close-issue-
|
|
330
|
+
//# sourceMappingURL=close-issue-Bmu9aOIx.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"close-issue-CRlmrE5g.js","names":[],"sources":["../../src/lib/lifecycle/close-issue.ts"],"sourcesContent":["/**\n * close-issue — Transition issue to closed/done state + label management.\n *\n * Uses the IssueTracker abstraction when available, with fallback to\n * direct API calls for contexts where the tracker isn't set up (e.g.,\n * standalone CLI).\n *\n * Operations:\n * 1. Transition issue to closed state\n * 2. Add 'closed-out' label\n * 3. Remove all current-phase workflow labels (WORKFLOW_LABELS)\n * 4. Add completion comment\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport type { IssueTracker } from '../tracker/interface.js';\nimport type { LifecycleContext, StepResult } from './types.js';\nimport { stepOk, stepSkipped, stepFailed, getLinearApiKey } from './types.js';\nimport { extractNumberSync, extractPrefixSync, normalizeIssueIdSync } from '../issue-id.js';\nimport { getAgentState, markAgentStoppedState, saveAgentState } from '../agents.js';\n\nconst execAsync = promisify(exec);\n\nconst CLOSED_OUT_LABEL = 'closed-out';\nconst CLOSED_OUT_COLOR = '1d4ed8';\n/**\n * Canonical set of current-phase pipeline labels. A closed/done issue is in no\n * phase, so close paths strip ALL of these. Single source of truth — close-out\n * imports this rather than keeping its own (previously divergent, missing\n * `planned`) list, which had left ~82 closed issues falsely labelled in-review/\n * in-progress/planned.\n */\nexport const WORKFLOW_LABELS = [\n 'in-progress', 'in-review', 'planned', 'in-planning',\n 'needs-close-out', 'verifying-on-main', 'ready-for-merge',\n];\n\nexport const POST_MERGE_RESIDUE_LABELS = ['merged', 'ready'];\nconst CLOSE_OUT_LABELS_TO_REMOVE = [...WORKFLOW_LABELS, ...POST_MERGE_RESIDUE_LABELS];\n\n/** Options for close-issue */\nexport interface CloseIssueOptions {\n /** IssueTracker instance (preferred — uses abstraction layer) */\n tracker?: IssueTracker;\n /** Reason for closing */\n reason?: string;\n /** Comment to add when closing */\n comment?: string;\n /** Apply the closed-out label. Default: true */\n applyLabel?: boolean;\n /** Only apply label (skip state transition). Default: false */\n labelOnly?: boolean;\n}\n\n/**\n * Close an issue and manage labels.\n *\n * If a tracker is provided, uses the abstraction layer.\n * Otherwise, falls back to direct gh CLI (GitHub) or Linear SDK calls.\n */\nasync function markWorkAgentStoppedForIssue(issueId: string): Promise<void> {\n const agentId = `agent-${normalizeIssueIdSync(issueId)}`;\n const state = await Effect.runPromise(getAgentState(agentId));\n if (!state) return;\n markAgentStoppedState(state);\n await Effect.runPromise(saveAgentState(state));\n}\n\nexport function closeIssue(\n ctx: LifecycleContext,\n opts: CloseIssueOptions = {},\n): Effect.Effect<StepResult[]> {\n return Effect.gen(function* () {\n const results: StepResult[] = [];\n const { applyLabel = true, labelOnly = false, comment } = opts;\n\n // Step 1: Transition to closed (unless labelOnly)\n if (!labelOnly) {\n const closeResult = opts.tracker\n ? yield* closeViaTracker(ctx, opts.tracker, comment)\n : yield* closeViaDirect(ctx, comment);\n results.push(closeResult);\n\n // If close failed, don't bother with labels\n if (!closeResult.success && !closeResult.skipped) {\n return results;\n }\n }\n\n // Step 2: Close any open PR for the feature branch (GitHub only)\n if (ctx.github) {\n const prResult = yield* closeGitHubPr(ctx);\n results.push(prResult);\n }\n\n // Step 3: Apply closed-out label + remove workflow labels\n if (applyLabel) {\n const labelResult = yield* applyClosedOutLabel(ctx, opts.tracker);\n results.push(labelResult);\n }\n\n return results;\n });\n}\n\n/**\n * Close via IssueTracker abstraction.\n */\nfunction closeViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n comment?: string,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:transition';\n return Effect.gen(function* () {\n yield* tracker.transitionIssue(ctx.issueId, 'closed');\n yield* Effect.promise(() => markWorkAgentStoppedForIssue(ctx.issueId));\n if (comment) {\n // Best-effort comment — swallow errors\n yield* tracker.addComment(ctx.issueId, comment).pipe(\n Effect.catch(() => Effect.void),\n );\n }\n return stepOk(step, [`Closed ${ctx.issueId} via ${tracker.name} tracker`]);\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed(step, `Failed to close via tracker: ${(err as Error).message ?? String(err)}`)),\n ),\n );\n}\n\n/**\n * Close via direct API calls (fallback when no tracker configured).\n * Determines issue type from context and uses appropriate method.\n */\nfunction closeViaDirect(\n ctx: LifecycleContext,\n comment?: string,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:transition';\n\n if (ctx.github) {\n return closeGitHubDirect(ctx, comment);\n }\n\n // Rally issue\n if (ctx.rally) {\n return closeRallyDirect(ctx);\n }\n\n // Try Linear\n return Effect.gen(function* () {\n const linearApiKey = yield* Effect.promise(() => getLinearApiKey());\n if (linearApiKey) {\n return yield* closeLinearDirect(ctx, linearApiKey);\n }\n return stepFailed(step, 'No tracker available and cannot determine issue type');\n });\n}\n\n/**\n * Close a GitHub issue via gh CLI.\n */\nfunction closeGitHubDirect(ctx: LifecycleContext, comment?: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeGitHubDirectImpl(ctx, comment),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `gh issue close failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function closeGitHubDirectImpl(ctx: LifecycleContext, comment?: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.github) {\n return stepFailed(step, 'GitHub config not provided');\n }\n const { owner, repo, number } = ctx.github;\n\n // Refuse to close a GitHub issue when its feature branch still has an open,\n // unmerged PR. PAN-1030 closed the issue while PR #1046 was open with 1152\n // lines of real changes — the close-out path trusted the operator without\n // verifying the merge. Fail loudly so the operator notices and either\n // merges the PR first or tells us why the issue should still close.\n const branchName = `feature/${ctx.issueId.toLowerCase()}`;\n try {\n const { stdout: prRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number,mergedAt,mergeCommit --jq '.[0]'`,\n { encoding: 'utf-8' },\n );\n const trimmed = prRaw.trim();\n if (trimmed) {\n try {\n const pr = JSON.parse(trimmed) as { number?: number; mergedAt?: string | null; mergeCommit?: unknown };\n if (pr.number && !pr.mergedAt && !pr.mergeCommit) {\n return stepFailed(\n step,\n `Refusing to close issue #${number}: open PR #${pr.number} on ${branchName} has not been merged. Merge or close the PR first, or rename the branch if it is unrelated.`,\n );\n }\n } catch {\n // PR JSON unparseable — continue with the close (don't block on parser error).\n }\n }\n } catch {\n // gh query failed — don't block close-out on a flaky network call.\n }\n\n try {\n const commentArg = comment ? ` --comment \"${comment.replace(/\"/g, '\\\\\"')}\"` : '';\n await execAsync(\n `gh issue close ${number} --repo ${owner}/${repo}${commentArg}`,\n { encoding: 'utf-8' },\n );\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Closed GitHub issue #${number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepFailed(step, `gh issue close failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * Close any open GitHub PR for the feature branch.\n */\nfunction closeGitHubPr(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeGitHubPrImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:close-pr', [`PR close failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function closeGitHubPrImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:close-pr';\n if (!ctx.github) {\n return stepSkipped(step, ['Not a GitHub issue']);\n }\n const { owner, repo } = ctx.github;\n const issueLower = ctx.issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n\n try {\n // Pull number + merge status. Closing without checking can land us in the\n // PAN-1030 state: issue closed + PR left OPEN with real unmerged code,\n // or worse — close a not-yet-merged PR with a \"Merged via Overdeck\n // lifecycle\" comment that lies about what happened.\n const { stdout: prListRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number,mergedAt,mergeCommit --jq '.[0]'`,\n { encoding: 'utf-8' },\n );\n const prRaw = prListRaw.trim();\n if (!prRaw) {\n return stepSkipped(step, ['No open PR found for branch']);\n }\n let pr: { number?: number; mergedAt?: string | null; mergeCommit?: unknown };\n try {\n pr = JSON.parse(prRaw);\n } catch {\n return stepSkipped(step, [`PR JSON parse failed: ${prRaw.slice(0, 80)}`]);\n }\n if (!pr.number) {\n return stepSkipped(step, ['Open PR query returned no number']);\n }\n if (!pr.mergedAt && !pr.mergeCommit) {\n // PR is open but NOT merged — refuse to close. The close-out flow must\n // not pretend an unmerged PR was merged. Fail loudly so the operator\n // notices the inconsistency rather than the PR silently disappearing.\n return stepFailed(\n step,\n `Refusing to close PR #${pr.number} on ${owner}/${repo}: PR is not merged. Investigate why close-out fired without a merge.`,\n );\n }\n await execAsync(\n `gh pr close ${pr.number} --repo ${owner}/${repo} --comment \"Merged via Overdeck lifecycle\"`,\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Closed PR #${pr.number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepSkipped(step, [`PR close failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\n/**\n * Rate limit circuit breaker for Linear API.\n * After hitting a rate limit, stop all Linear API calls for COOLDOWN_MS.\n * This prevents the 24,626-call storm that exhausted Linear's 5000 req/hr limit (PAN-328).\n */\nlet _linearRateLimitUntil = 0;\nconst LINEAR_RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour (matches Linear's 5000/hr window)\n\n/**\n * Close a Linear issue via SDK (find by identifier, transition to Done).\n */\nfunction closeLinearDirect(ctx: LifecycleContext, apiKey: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeLinearDirectImpl(ctx, apiKey),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `Linear close failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function closeLinearDirectImpl(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n\n // Circuit breaker: if we recently hit a rate limit, fail fast without making API calls\n if (Date.now() < _linearRateLimitUntil) {\n const remainingMin = Math.ceil((_linearRateLimitUntil - Date.now()) / 60000);\n return stepFailed(step, `Linear rate limit cooldown active (${remainingMin}min remaining). Issue will be closed during close-out ceremony.`);\n }\n\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNumber = extractNumberSync(ctx.issueId);\n const issuePrefix = extractPrefixSync(ctx.issueId);\n if (issueNumber === null || issuePrefix === null) {\n return stepFailed(step, `Could not parse issue ID: ${ctx.issueId}`);\n }\n const results = await client.issues({\n filter: {\n number: { eq: issueNumber },\n team: { key: { eq: issuePrefix } },\n },\n first: 1,\n });\n\n if (results.nodes.length === 0) {\n return stepFailed(step, `Issue ${ctx.issueId} not found in Linear`);\n }\n\n const issue = results.nodes[0];\n const team = await issue.team;\n if (team) {\n const states = await team.states();\n const doneState = states.nodes.find(s => s.name === 'Done') ||\n states.nodes.find(s => s.type === 'completed');\n if (doneState) {\n await issue.update({ stateId: doneState.id });\n }\n }\n\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Moved Linear issue ${ctx.issueId} to Done`]);\n } catch (err) {\n const message = (err as Error).message;\n\n // Detect rate limit errors and activate circuit breaker\n if (message.includes('Rate limit') || message.includes('rate limit') || message.includes('429')) {\n _linearRateLimitUntil = Date.now() + LINEAR_RATE_LIMIT_COOLDOWN_MS;\n console.warn(`[close-issue] Linear rate limit hit — circuit breaker activated for 1 hour`);\n return stepFailed(step, `Linear rate limit exceeded. Circuit breaker activated — no Linear API calls for 1 hour. Issue will be closed during close-out ceremony.`);\n }\n\n return stepFailed(step, `Linear close failed: ${message}`);\n }\n}\n\n/**\n * Close a Rally issue via RallyTracker.\n */\nfunction closeRallyDirect(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeRallyDirectImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `Rally close failed: ${(err as Error).message ?? String(err)}`)),\n ),\n );\n}\n\nasync function closeRallyDirectImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.rally) {\n return stepFailed(step, 'Rally config not provided');\n }\n const { RallyTracker } = await import('../tracker/rally.js');\n const tracker = new RallyTracker({\n apiKey: ctx.rally.apiKey,\n server: ctx.rally.server,\n workspace: ctx.rally.workspace,\n project: ctx.rally.project,\n });\n // RallyTracker.transitionIssue returns Effect (migrated in PAN-1249).\n await Effect.runPromise(tracker.transitionIssue(ctx.issueId, 'closed'));\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Closed Rally issue ${ctx.issueId}`]);\n}\n\n/**\n * Apply 'closed-out' label and remove workflow labels.\n * Uses tracker if available, falls back to direct calls.\n */\nfunction applyClosedOutLabel(\n ctx: LifecycleContext,\n tracker?: IssueTracker,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:label';\n\n if (tracker) {\n return applyLabelViaTracker(ctx, tracker);\n }\n\n if (ctx.github) {\n return applyLabelGitHub(ctx);\n }\n\n return Effect.gen(function* () {\n const linearApiKey = yield* Effect.promise(() => getLinearApiKey());\n if (linearApiKey) {\n return yield* applyLabelLinear(ctx, linearApiKey);\n }\n return stepSkipped(step, ['No tracker available for label management']);\n });\n}\n\nfunction applyLabelViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:label';\n return Effect.gen(function* () {\n const issue = yield* tracker.getIssue(ctx.issueId);\n const newLabels = issue.labels.filter((l: string) => !CLOSE_OUT_LABELS_TO_REMOVE.includes(l));\n if (!newLabels.includes(CLOSED_OUT_LABEL)) {\n newLabels.push(CLOSED_OUT_LABEL);\n }\n yield* tracker.updateIssue(ctx.issueId, { labels: newLabels });\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label via ${tracker.name} tracker`]);\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message ?? String(err)}`])),\n ),\n );\n}\n\nfunction applyLabelGitHub(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => applyLabelGitHubImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:label', [`Label management failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function applyLabelGitHubImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:label';\n if (!ctx.github) return stepSkipped(step);\n const { owner, repo, number } = ctx.github;\n\n try {\n // Ensure label exists\n await execAsync(\n `gh label create \"${CLOSED_OUT_LABEL}\" --repo ${owner}/${repo} --color \"${CLOSED_OUT_COLOR}\" --description \"Verified and closed out\" --force 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n // Only remove labels actually on the issue — `gh issue edit` fails the\n // whole edit if any --remove-label names a label missing from the repo.\n const { stdout: labelsJson } = await execAsync(\n `gh issue view ${number} --repo ${owner}/${repo} --json labels --jq '[.labels[].name]'`,\n { encoding: 'utf-8' },\n );\n const currentLabels: string[] = JSON.parse(labelsJson.trim() || '[]');\n const removeLabelArgs = CLOSE_OUT_LABELS_TO_REMOVE\n .filter(label => currentLabels.includes(label))\n .map(label => `--remove-label \"${label}\"`)\n .join(' ');\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --add-label \"${CLOSED_OUT_LABEL}\" ${removeLabelArgs}`.trim(),\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on GitHub`]);\n } catch (err) {\n return stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\nfunction applyLabelLinear(ctx: LifecycleContext, apiKey: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => applyLabelLinearImpl(ctx, apiKey),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:label', [`Linear label management failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function applyLabelLinearImpl(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:label';\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNum = extractNumberSync(ctx.issueId);\n const teamKey = extractPrefixSync(ctx.issueId);\n if (issueNum === null || teamKey === null) {\n return stepFailed(step, `Could not parse issue ID: ${ctx.issueId}`);\n }\n const results = await client.issues({\n filter: {\n number: { eq: issueNum },\n team: { key: { eq: teamKey } },\n },\n first: 1,\n });\n if (results.nodes.length === 0) {\n return stepSkipped(step, ['Issue not found for label management']);\n }\n\n const issue = results.nodes[0];\n\n // Find or create closed-out label\n const labels = await client.issueLabels({ filter: { name: { eq: CLOSED_OUT_LABEL } } });\n let labelId: string;\n if (labels.nodes.length > 0) {\n labelId = labels.nodes[0].id;\n } else {\n const created = await client.createIssueLabel({ name: CLOSED_OUT_LABEL, color: `#${CLOSED_OUT_COLOR}` });\n const createdLabel = await created.issueLabel;\n labelId = createdLabel ? createdLabel.id : '';\n }\n\n if (labelId) {\n const existingLabels = await issue.labels();\n const labelIds = existingLabels.nodes\n .filter(l => !CLOSE_OUT_LABELS_TO_REMOVE.includes(l.name))\n .map(l => l.id);\n if (!labelIds.includes(labelId)) {\n labelIds.push(labelId);\n }\n await issue.update({ labelIds });\n }\n\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on Linear`]);\n } catch (err) {\n return stepSkipped(step, [`Linear label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"close-issue-Bmu9aOIx.js","names":[],"sources":["../../src/lib/lifecycle/close-issue.ts"],"sourcesContent":["/**\n * close-issue — Transition issue to closed/done state + label management.\n *\n * Uses the IssueTracker abstraction when available, with fallback to\n * direct API calls for contexts where the tracker isn't set up (e.g.,\n * standalone CLI).\n *\n * Operations:\n * 1. Transition issue to closed state\n * 2. Add 'closed-out' label\n * 3. Remove all current-phase workflow labels (WORKFLOW_LABELS)\n * 4. Add completion comment\n */\n\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport type { IssueTracker } from '../tracker/interface.js';\nimport type { LifecycleContext, StepResult } from './types.js';\nimport { stepOk, stepSkipped, stepFailed, getLinearApiKey } from './types.js';\nimport { extractNumberSync, extractPrefixSync, normalizeIssueIdSync } from '../issue-id.js';\nimport { getAgentState, markAgentStoppedState, saveAgentState } from '../agents.js';\n\nconst execAsync = promisify(exec);\n\nconst CLOSED_OUT_LABEL = 'closed-out';\nconst CLOSED_OUT_COLOR = '1d4ed8';\n/**\n * Canonical set of current-phase pipeline labels. A closed/done issue is in no\n * phase, so close paths strip ALL of these. Single source of truth — close-out\n * imports this rather than keeping its own (previously divergent, missing\n * `planned`) list, which had left ~82 closed issues falsely labelled in-review/\n * in-progress/planned.\n */\nexport const WORKFLOW_LABELS = [\n 'in-progress', 'in-review', 'planned', 'in-planning',\n 'needs-close-out', 'verifying-on-main', 'ready-for-merge',\n];\n\nexport const POST_MERGE_RESIDUE_LABELS = ['merged', 'ready'];\nconst CLOSE_OUT_LABELS_TO_REMOVE = [...WORKFLOW_LABELS, ...POST_MERGE_RESIDUE_LABELS];\n\n/** Options for close-issue */\nexport interface CloseIssueOptions {\n /** IssueTracker instance (preferred — uses abstraction layer) */\n tracker?: IssueTracker;\n /** Reason for closing */\n reason?: string;\n /** Comment to add when closing */\n comment?: string;\n /** Apply the closed-out label. Default: true */\n applyLabel?: boolean;\n /** Only apply label (skip state transition). Default: false */\n labelOnly?: boolean;\n}\n\n/**\n * Close an issue and manage labels.\n *\n * If a tracker is provided, uses the abstraction layer.\n * Otherwise, falls back to direct gh CLI (GitHub) or Linear SDK calls.\n */\nasync function markWorkAgentStoppedForIssue(issueId: string): Promise<void> {\n const agentId = `agent-${normalizeIssueIdSync(issueId)}`;\n const state = await Effect.runPromise(getAgentState(agentId));\n if (!state) return;\n markAgentStoppedState(state);\n await Effect.runPromise(saveAgentState(state));\n}\n\nexport function closeIssue(\n ctx: LifecycleContext,\n opts: CloseIssueOptions = {},\n): Effect.Effect<StepResult[]> {\n return Effect.gen(function* () {\n const results: StepResult[] = [];\n const { applyLabel = true, labelOnly = false, comment } = opts;\n\n // Step 1: Transition to closed (unless labelOnly)\n if (!labelOnly) {\n const closeResult = opts.tracker\n ? yield* closeViaTracker(ctx, opts.tracker, comment)\n : yield* closeViaDirect(ctx, comment);\n results.push(closeResult);\n\n // If close failed, don't bother with labels\n if (!closeResult.success && !closeResult.skipped) {\n return results;\n }\n }\n\n // Step 2: Close any open PR for the feature branch (GitHub only)\n if (ctx.github) {\n const prResult = yield* closeGitHubPr(ctx);\n results.push(prResult);\n }\n\n // Step 3: Apply closed-out label + remove workflow labels\n if (applyLabel) {\n const labelResult = yield* applyClosedOutLabel(ctx, opts.tracker);\n results.push(labelResult);\n }\n\n return results;\n });\n}\n\n/**\n * Close via IssueTracker abstraction.\n */\nfunction closeViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n comment?: string,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:transition';\n return Effect.gen(function* () {\n yield* tracker.transitionIssue(ctx.issueId, 'closed');\n yield* Effect.promise(() => markWorkAgentStoppedForIssue(ctx.issueId));\n if (comment) {\n // Best-effort comment — swallow errors\n yield* tracker.addComment(ctx.issueId, comment).pipe(\n Effect.catch(() => Effect.void),\n );\n }\n return stepOk(step, [`Closed ${ctx.issueId} via ${tracker.name} tracker`]);\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed(step, `Failed to close via tracker: ${(err as Error).message ?? String(err)}`)),\n ),\n );\n}\n\n/**\n * Close via direct API calls (fallback when no tracker configured).\n * Determines issue type from context and uses appropriate method.\n */\nfunction closeViaDirect(\n ctx: LifecycleContext,\n comment?: string,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:transition';\n\n if (ctx.github) {\n return closeGitHubDirect(ctx, comment);\n }\n\n // Rally issue\n if (ctx.rally) {\n return closeRallyDirect(ctx);\n }\n\n // Try Linear\n return Effect.gen(function* () {\n const linearApiKey = yield* Effect.promise(() => getLinearApiKey());\n if (linearApiKey) {\n return yield* closeLinearDirect(ctx, linearApiKey);\n }\n return stepFailed(step, 'No tracker available and cannot determine issue type');\n });\n}\n\n/**\n * Close a GitHub issue via gh CLI.\n */\nfunction closeGitHubDirect(ctx: LifecycleContext, comment?: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeGitHubDirectImpl(ctx, comment),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `gh issue close failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function closeGitHubDirectImpl(ctx: LifecycleContext, comment?: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.github) {\n return stepFailed(step, 'GitHub config not provided');\n }\n const { owner, repo, number } = ctx.github;\n\n // Refuse to close a GitHub issue when its feature branch still has an open,\n // unmerged PR. PAN-1030 closed the issue while PR #1046 was open with 1152\n // lines of real changes — the close-out path trusted the operator without\n // verifying the merge. Fail loudly so the operator notices and either\n // merges the PR first or tells us why the issue should still close.\n const branchName = `feature/${ctx.issueId.toLowerCase()}`;\n try {\n const { stdout: prRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number,mergedAt,mergeCommit --jq '.[0]'`,\n { encoding: 'utf-8' },\n );\n const trimmed = prRaw.trim();\n if (trimmed) {\n try {\n const pr = JSON.parse(trimmed) as { number?: number; mergedAt?: string | null; mergeCommit?: unknown };\n if (pr.number && !pr.mergedAt && !pr.mergeCommit) {\n return stepFailed(\n step,\n `Refusing to close issue #${number}: open PR #${pr.number} on ${branchName} has not been merged. Merge or close the PR first, or rename the branch if it is unrelated.`,\n );\n }\n } catch {\n // PR JSON unparseable — continue with the close (don't block on parser error).\n }\n }\n } catch {\n // gh query failed — don't block close-out on a flaky network call.\n }\n\n try {\n const commentArg = comment ? ` --comment \"${comment.replace(/\"/g, '\\\\\"')}\"` : '';\n await execAsync(\n `gh issue close ${number} --repo ${owner}/${repo}${commentArg}`,\n { encoding: 'utf-8' },\n );\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Closed GitHub issue #${number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepFailed(step, `gh issue close failed: ${(err as Error).message}`);\n }\n}\n\n/**\n * Close any open GitHub PR for the feature branch.\n */\nfunction closeGitHubPr(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeGitHubPrImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:close-pr', [`PR close failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function closeGitHubPrImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:close-pr';\n if (!ctx.github) {\n return stepSkipped(step, ['Not a GitHub issue']);\n }\n const { owner, repo } = ctx.github;\n const issueLower = ctx.issueId.toLowerCase();\n const branchName = `feature/${issueLower}`;\n\n try {\n // Pull number + merge status. Closing without checking can land us in the\n // PAN-1030 state: issue closed + PR left OPEN with real unmerged code,\n // or worse — close a not-yet-merged PR with a \"Merged via Overdeck\n // lifecycle\" comment that lies about what happened.\n const { stdout: prListRaw } = await execAsync(\n `gh pr list --repo ${owner}/${repo} --head \"${branchName}\" --state open --json number,mergedAt,mergeCommit --jq '.[0]'`,\n { encoding: 'utf-8' },\n );\n const prRaw = prListRaw.trim();\n if (!prRaw) {\n return stepSkipped(step, ['No open PR found for branch']);\n }\n let pr: { number?: number; mergedAt?: string | null; mergeCommit?: unknown };\n try {\n pr = JSON.parse(prRaw);\n } catch {\n return stepSkipped(step, [`PR JSON parse failed: ${prRaw.slice(0, 80)}`]);\n }\n if (!pr.number) {\n return stepSkipped(step, ['Open PR query returned no number']);\n }\n if (!pr.mergedAt && !pr.mergeCommit) {\n // PR is open but NOT merged — refuse to close. The close-out flow must\n // not pretend an unmerged PR was merged. Fail loudly so the operator\n // notices the inconsistency rather than the PR silently disappearing.\n return stepFailed(\n step,\n `Refusing to close PR #${pr.number} on ${owner}/${repo}: PR is not merged. Investigate why close-out fired without a merge.`,\n );\n }\n await execAsync(\n `gh pr close ${pr.number} --repo ${owner}/${repo} --comment \"Merged via Overdeck lifecycle\"`,\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Closed PR #${pr.number} on ${owner}/${repo}`]);\n } catch (err) {\n return stepSkipped(step, [`PR close failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\n/**\n * Rate limit circuit breaker for Linear API.\n * After hitting a rate limit, stop all Linear API calls for COOLDOWN_MS.\n * This prevents the 24,626-call storm that exhausted Linear's 5000 req/hr limit (PAN-328).\n */\nlet _linearRateLimitUntil = 0;\nconst LINEAR_RATE_LIMIT_COOLDOWN_MS = 60 * 60 * 1000; // 1 hour (matches Linear's 5000/hr window)\n\n/**\n * Close a Linear issue via SDK (find by identifier, transition to Done).\n */\nfunction closeLinearDirect(ctx: LifecycleContext, apiKey: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeLinearDirectImpl(ctx, apiKey),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `Linear close failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function closeLinearDirectImpl(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:transition';\n\n // Circuit breaker: if we recently hit a rate limit, fail fast without making API calls\n if (Date.now() < _linearRateLimitUntil) {\n const remainingMin = Math.ceil((_linearRateLimitUntil - Date.now()) / 60000);\n return stepFailed(step, `Linear rate limit cooldown active (${remainingMin}min remaining). Issue will be closed during close-out ceremony.`);\n }\n\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNumber = extractNumberSync(ctx.issueId);\n const issuePrefix = extractPrefixSync(ctx.issueId);\n if (issueNumber === null || issuePrefix === null) {\n return stepFailed(step, `Could not parse issue ID: ${ctx.issueId}`);\n }\n const results = await client.issues({\n filter: {\n number: { eq: issueNumber },\n team: { key: { eq: issuePrefix } },\n },\n first: 1,\n });\n\n if (results.nodes.length === 0) {\n return stepFailed(step, `Issue ${ctx.issueId} not found in Linear`);\n }\n\n const issue = results.nodes[0];\n const team = await issue.team;\n if (team) {\n const states = await team.states();\n const doneState = states.nodes.find(s => s.name === 'Done') ||\n states.nodes.find(s => s.type === 'completed');\n if (doneState) {\n await issue.update({ stateId: doneState.id });\n }\n }\n\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Moved Linear issue ${ctx.issueId} to Done`]);\n } catch (err) {\n const message = (err as Error).message;\n\n // Detect rate limit errors and activate circuit breaker\n if (message.includes('Rate limit') || message.includes('rate limit') || message.includes('429')) {\n _linearRateLimitUntil = Date.now() + LINEAR_RATE_LIMIT_COOLDOWN_MS;\n console.warn(`[close-issue] Linear rate limit hit — circuit breaker activated for 1 hour`);\n return stepFailed(step, `Linear rate limit exceeded. Circuit breaker activated — no Linear API calls for 1 hour. Issue will be closed during close-out ceremony.`);\n }\n\n return stepFailed(step, `Linear close failed: ${message}`);\n }\n}\n\n/**\n * Close a Rally issue via RallyTracker.\n */\nfunction closeRallyDirect(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => closeRallyDirectImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('close-issue:transition', `Rally close failed: ${(err as Error).message ?? String(err)}`)),\n ),\n );\n}\n\nasync function closeRallyDirectImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:transition';\n if (!ctx.rally) {\n return stepFailed(step, 'Rally config not provided');\n }\n const { RallyTracker } = await import('../tracker/rally.js');\n const tracker = new RallyTracker({\n apiKey: ctx.rally.apiKey,\n server: ctx.rally.server,\n workspace: ctx.rally.workspace,\n project: ctx.rally.project,\n });\n // RallyTracker.transitionIssue returns Effect (migrated in PAN-1249).\n await Effect.runPromise(tracker.transitionIssue(ctx.issueId, 'closed'));\n await markWorkAgentStoppedForIssue(ctx.issueId);\n return stepOk(step, [`Closed Rally issue ${ctx.issueId}`]);\n}\n\n/**\n * Apply 'closed-out' label and remove workflow labels.\n * Uses tracker if available, falls back to direct calls.\n */\nfunction applyClosedOutLabel(\n ctx: LifecycleContext,\n tracker?: IssueTracker,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:label';\n\n if (tracker) {\n return applyLabelViaTracker(ctx, tracker);\n }\n\n if (ctx.github) {\n return applyLabelGitHub(ctx);\n }\n\n return Effect.gen(function* () {\n const linearApiKey = yield* Effect.promise(() => getLinearApiKey());\n if (linearApiKey) {\n return yield* applyLabelLinear(ctx, linearApiKey);\n }\n return stepSkipped(step, ['No tracker available for label management']);\n });\n}\n\nfunction applyLabelViaTracker(\n ctx: LifecycleContext,\n tracker: IssueTracker,\n): Effect.Effect<StepResult> {\n const step = 'close-issue:label';\n return Effect.gen(function* () {\n const issue = yield* tracker.getIssue(ctx.issueId);\n const newLabels = issue.labels.filter((l: string) => !CLOSE_OUT_LABELS_TO_REMOVE.includes(l));\n if (!newLabels.includes(CLOSED_OUT_LABEL)) {\n newLabels.push(CLOSED_OUT_LABEL);\n }\n yield* tracker.updateIssue(ctx.issueId, { labels: newLabels });\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label via ${tracker.name} tracker`]);\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message ?? String(err)}`])),\n ),\n );\n}\n\nfunction applyLabelGitHub(ctx: LifecycleContext): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => applyLabelGitHubImpl(ctx),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:label', [`Label management failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function applyLabelGitHubImpl(ctx: LifecycleContext): Promise<StepResult> {\n const step = 'close-issue:label';\n if (!ctx.github) return stepSkipped(step);\n const { owner, repo, number } = ctx.github;\n\n try {\n // Ensure label exists\n await execAsync(\n `gh label create \"${CLOSED_OUT_LABEL}\" --repo ${owner}/${repo} --color \"${CLOSED_OUT_COLOR}\" --description \"Verified and closed out\" --force 2>/dev/null || true`,\n { encoding: 'utf-8' },\n );\n // Only remove labels actually on the issue — `gh issue edit` fails the\n // whole edit if any --remove-label names a label missing from the repo.\n const { stdout: labelsJson } = await execAsync(\n `gh issue view ${number} --repo ${owner}/${repo} --json labels --jq '[.labels[].name]'`,\n { encoding: 'utf-8' },\n );\n const currentLabels: string[] = JSON.parse(labelsJson.trim() || '[]');\n const removeLabelArgs = CLOSE_OUT_LABELS_TO_REMOVE\n .filter(label => currentLabels.includes(label))\n .map(label => `--remove-label \"${label}\"`)\n .join(' ');\n await execAsync(\n `gh issue edit ${number} --repo ${owner}/${repo} --add-label \"${CLOSED_OUT_LABEL}\" ${removeLabelArgs}`.trim(),\n { encoding: 'utf-8' },\n );\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on GitHub`]);\n } catch (err) {\n return stepSkipped(step, [`Label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n\nfunction applyLabelLinear(ctx: LifecycleContext, apiKey: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => applyLabelLinearImpl(ctx, apiKey),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('close-issue:label', [`Linear label management failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nasync function applyLabelLinearImpl(ctx: LifecycleContext, apiKey: string): Promise<StepResult> {\n const step = 'close-issue:label';\n try {\n const { LinearClient } = await import('@linear/sdk');\n const client = new LinearClient({ apiKey });\n\n const issueNum = extractNumberSync(ctx.issueId);\n const teamKey = extractPrefixSync(ctx.issueId);\n if (issueNum === null || teamKey === null) {\n return stepFailed(step, `Could not parse issue ID: ${ctx.issueId}`);\n }\n const results = await client.issues({\n filter: {\n number: { eq: issueNum },\n team: { key: { eq: teamKey } },\n },\n first: 1,\n });\n if (results.nodes.length === 0) {\n return stepSkipped(step, ['Issue not found for label management']);\n }\n\n const issue = results.nodes[0];\n\n // Find or create closed-out label\n const labels = await client.issueLabels({ filter: { name: { eq: CLOSED_OUT_LABEL } } });\n let labelId: string;\n if (labels.nodes.length > 0) {\n labelId = labels.nodes[0].id;\n } else {\n const created = await client.createIssueLabel({ name: CLOSED_OUT_LABEL, color: `#${CLOSED_OUT_COLOR}` });\n const createdLabel = await created.issueLabel;\n labelId = createdLabel ? createdLabel.id : '';\n }\n\n if (labelId) {\n const existingLabels = await issue.labels();\n const labelIds = existingLabels.nodes\n .filter(l => !CLOSE_OUT_LABELS_TO_REMOVE.includes(l.name))\n .map(l => l.id);\n if (!labelIds.includes(labelId)) {\n labelIds.push(labelId);\n }\n await issue.update({ labelIds });\n }\n\n return stepOk(step, [`Applied '${CLOSED_OUT_LABEL}' label on Linear`]);\n } catch (err) {\n return stepSkipped(step, [`Linear label management failed (non-fatal): ${(err as Error).message}`]);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { Ri as init_paths, Y as init_record, di as AGENTS_DIR, fn as init_projects, qn as init_issue_id, rt as resolveProjectForIssue, vn as listProjectsSync } from "./config-yaml-Dce9VUd6.js";
|
|
2
2
|
import { l as init_activity_logger, o as emitActivityEntrySync } from "./activity-logger-DNYDzAOU.js";
|
|
3
3
|
import { C as init_tmux, E as killSession, j as listSessionNames } from "./tmux-l-yvNWhg.js";
|
|
4
|
-
import { Gn as removeAgentStateDir, Hn as init_state_dir_removal, Ln as listRunningAgents, Mn as stopAgent, q as init_agents } from "./agent-state-
|
|
5
|
-
import { X as readJournalStatus, Y as init_review_status_record_sync, h as loadReviewStatuses, p as init_review_status, w as setReviewStatusSync } from "./review-status-
|
|
4
|
+
import { Gn as removeAgentStateDir, Hn as init_state_dir_removal, Ln as listRunningAgents, Mn as stopAgent, q as init_agents } from "./agent-state-B196fj1h.js";
|
|
5
|
+
import { X as readJournalStatus, Y as init_review_status_record_sync, h as loadReviewStatuses, p as init_review_status, w as setReviewStatusSync } from "./review-status-yY5qt7Rl.js";
|
|
6
6
|
import { i as teardownWorkspaceDockerByNamePromise, n as init_docker } from "./docker-fz7ELYZ8.js";
|
|
7
7
|
import { i as isTrackerIssueClosed, n as init_issue_closed, r as isIssueClosed } from "./issue-closed-D7aMUeMq.js";
|
|
8
8
|
import "./prd-locations-Chcvxzle.js";
|
|
9
9
|
import "./archive-planning-CtdBSD1-.js";
|
|
10
|
-
import "./close-issue-
|
|
10
|
+
import "./close-issue-Bmu9aOIx.js";
|
|
11
11
|
import { join } from "node:path";
|
|
12
12
|
import { Data, Effect } from "effect";
|
|
13
13
|
import "fs";
|
|
@@ -467,7 +467,7 @@ async function reconcileClosedIssueAgents() {
|
|
|
467
467
|
}
|
|
468
468
|
let mergedIssueIds = devnetIssueIds ? [] : null;
|
|
469
469
|
if (openDevnetIssueIds.length > 0) try {
|
|
470
|
-
const { getReviewStatusesSync } = await import("./review-status-
|
|
470
|
+
const { getReviewStatusesSync } = await import("./review-status-FnlvQuPI.js");
|
|
471
471
|
const statuses = getReviewStatusesSync(openDevnetIssueIds);
|
|
472
472
|
mergedIssueIds = openDevnetIssueIds.filter((issueId) => statuses[issueId]?.mergeStatus === "merged");
|
|
473
473
|
} catch (error) {
|
|
@@ -486,4 +486,4 @@ async function reconcileClosedIssueAgents() {
|
|
|
486
486
|
//#endregion
|
|
487
487
|
export { reapClosedIssueReviewRequests as n, reconcileClosedIssueAgents as r, handleIssueStatusChangedClosed as t };
|
|
488
488
|
|
|
489
|
-
//# sourceMappingURL=closed-issue-reaper-
|
|
489
|
+
//# sourceMappingURL=closed-issue-reaper-COxfTtLz.js.map
|