@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jsonl-resolver-DQEe0DHM.js","names":[],"sources":["../../src/dashboard/server/routes/jsonl-resolver.ts"],"sourcesContent":["import { resolveMuseSessionPath } from '../../../lib/runtimes/muse-session.js';\n/**\n * JSONL transcript resolver for the Command Deck (PAN-830).\n *\n * Maps an agent ID (e.g. `agent-pan-830`, `planning-pan-830`, or a canonical\n * specialist tmux session name) to the agent's JSONL transcript file on disk.\n *\n * Codex agents (PAN-1805) write rollout JSONLs under the per-agent\n * `codex-home/sessions/` tree — resolution dispatches on the harness recorded\n * in state.json (thread-id fast path, then latest-rollout fallback).\n *\n * For claude-code agents the JSONL filename is the *Claude session ID* (a UUID\n * written by Claude Code itself), NOT the agent/tmux name. Lookup order:\n * 1. session.id — UUID pinned before fresh work-agent launch and updated\n * by auto-suspend/resume flows\n * 2. sessions.json — array of UUIDs the agent has used; the heartbeat hook\n * APPENDS new IDs and dedupes, so once a session has been\n * seen its array index never moves. After a `pan resume`\n * brings an older session back as the live one, the array\n * order no longer reflects time-recency. We disambiguate\n * across all listed IDs by JSONL mtime.\n * 3. runtime state — in-process mirror, populated by hooks\n * 4. agents registry — the DB row's session_id recorded at spawn; survives\n * janitor removal of the agent directory\n *\n * Async-only (fs/promises) because this code path runs inside the dashboard\n * server's event loop.\n */\nimport { access, readFile, stat, readdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { getAgentRuntimeState, getAgentStateSync } from '../../../lib/agents.js';\nimport { encodeClaudeProjectDir, getOverdeckHome } from '../../../lib/paths.js';\nimport { getAgentWorkspace } from '../../../lib/agent-enrichment.js';\nimport { getHarnessBehavior } from '../../../lib/runtimes/behavior.js';\nimport type { HarnessName } from '../../../lib/runtimes/types.js';\nimport { logAgentLifecycleSync } from '../../../lib/persistent-logger.js';\n\nexport interface ResolveJsonlPathOptions {\n /** Override the ~/.overdeck/agents directory (test hook). */\n agentsDirOverride?: string;\n /** Override the ~/.claude/projects directory (test hook). */\n claudeProjectsDirOverride?: string;\n /** Override the ~/.kimi-code directory (test hook). */\n kimiHomeOverride?: string;\n /**\n * Explicit workspace path for resolveKimiWirePath. Conversation rows have\n * no AgentState (readRecordedState/getAgentStateSync return nothing), so\n * dashboard conversation callers must supply conv.cwd directly instead of\n * relying on the agent-state lookup (PAN-1837 review fix).\n */\n workspaceOverride?: string;\n /** Override the runtime-state lookup (test hook). */\n getRuntimeStateAsync?: (agentId: string) => Promise<{ claudeSessionId?: string } | null>;\n /** Override forensic logging (test hook). */\n logDiagnostic?: (agentId: string, message: string) => void;\n}\n\n// Command Deck polling can resolve the same session repeatedly. Keep forensic\n// logging useful by writing only when the resolution outcome changes.\nconst transcriptResolutionSignatures = new Map<string, string>();\n\nfunction logTranscriptResolution(\n agentId: string,\n signature: string,\n message: string,\n opts: ResolveJsonlPathOptions,\n): void {\n const signatureKey = `${agentId}:${opts.agentsDirOverride ?? 'live'}`;\n if (transcriptResolutionSignatures.get(signatureKey) === signature) return;\n transcriptResolutionSignatures.set(signatureKey, signature);\n const logger = opts.logDiagnostic\n ?? (opts.agentsDirOverride ? undefined : logAgentLifecycleSync);\n logger?.(agentId, `transcript resolution: ${message}`);\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n return access(p).then(() => true, () => false);\n}\n\nasync function readOptional(p: string): Promise<string | null> {\n return readFile(p, 'utf-8').catch(() => null);\n}\n\nfunction behaviorForHarness(harness: string | null | undefined) {\n return getHarnessBehavior(harness as HarnessName | null | undefined);\n}\n\n/**\n * Pick the candidate UUID whose JSONL transcript has the most recent mtime.\n *\n * The heartbeat hook appends to sessions.json and dedupes — so once an ID is\n * recorded, its array index never moves even if a later resume makes it the\n * current session again. Choosing by JSONL mtime is the only reliable signal\n * for \"which session is actually live right now.\"\n *\n * Returns null if no candidate has a corresponding JSONL on disk. Falls back\n * to scanning every project dir if the workspace path can't be resolved\n * (covers specialist sessions whose workspace lives elsewhere).\n */\nasync function pickFreshestSessionId(\n candidates: ReadonlyArray<string>,\n agentId: string,\n opts: ResolveJsonlPathOptions,\n): Promise<string | null> {\n if (candidates.length === 0) return null;\n if (candidates.length === 1) return candidates[0]!;\n\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n\n // Fast path: derive the agent's workspace and look only in the matching\n // project dir. Avoids fanning out across every Claude project.\n let candidatePaths: Array<{ id: string; path: string }> = [];\n try {\n const workspace = await Effect.runPromise(getAgentWorkspace(agentId));\n if (workspace) {\n const projectDir = join(projectsRoot, encodeClaudeProjectDir(workspace));\n candidatePaths = candidates.map((id) => ({ id, path: join(projectDir, `${id}.jsonl`) }));\n }\n } catch { /* non-fatal — fall back to project-dir scan */ }\n\n // Slow path: scan all project dirs (specialist agents, multi-workspace cases).\n if (candidatePaths.length === 0) {\n try {\n const dirs = await readdir(projectsRoot);\n const SAFE_DIR = /^[a-zA-Z0-9_.-]+$/;\n for (const id of candidates) {\n for (const dir of dirs) {\n if (!SAFE_DIR.test(dir)) continue;\n candidatePaths.push({ id, path: join(projectsRoot, dir, `${id}.jsonl`) });\n }\n }\n } catch { /* fall through to no-mtime path */ }\n }\n\n // Stat each candidate path; pick the (id, mtime) with the newest mtime.\n let best: { id: string; mtimeMs: number } | null = null;\n for (const { id, path } of candidatePaths) {\n try {\n const s = await stat(path);\n if (!best || s.mtimeMs > best.mtimeMs) {\n best = { id, mtimeMs: s.mtimeMs };\n }\n } catch { /* missing file — skip */ }\n }\n\n // If no candidate has a JSONL on disk, fall back to the historical \"last\n // appended\" heuristic — the previous behavior. Better than returning null\n // for callers that only need a session ID, not necessarily a live JSONL.\n return best ? best.id : (candidates[candidates.length - 1] ?? null);\n}\n\n/** Async equivalent of getLatestSessionId from lib/agents.ts. */\nexport async function resolveClaudeSessionId(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n\n // 1. session.id — UUID pinned before fresh work-agent launch and updated by\n // suspend/resume flows. Authoritative when present.\n const sessionIdRaw = await readOptional(join(agentDir, 'session.id'));\n const sessionIdTrimmed = sessionIdRaw?.trim();\n if (sessionIdTrimmed) return sessionIdTrimmed;\n\n // 2. sessions.json — array of UUIDs the agent has ever used. We can't trust\n // array order (see file-level docs), so disambiguate by JSONL mtime.\n const sessionsRaw = await readOptional(join(agentDir, 'sessions.json'));\n if (sessionsRaw) {\n try {\n const parsed: unknown = JSON.parse(sessionsRaw);\n if (Array.isArray(parsed)) {\n const valid = parsed.filter((v): v is string => typeof v === 'string' && v.trim().length > 0);\n const fresh = await pickFreshestSessionId(valid, agentId, opts);\n if (fresh) return fresh;\n }\n } catch { /* non-fatal */ }\n }\n\n // 3. runtime state claudeSessionId (in-process mirror)\n try {\n const lookup = opts.getRuntimeStateAsync ?? ((id: string) => Effect.runPromise(getAgentRuntimeState(id)));\n const runtimeState = await lookup(agentId);\n if (runtimeState?.claudeSessionId) return runtimeState.claudeSessionId;\n } catch { /* non-fatal */ }\n\n // 4. agents registry — the DB row records session_id at spawn (PAN-1908) and\n // survives janitors removing the agent directory, so an ended session's\n // transcript stays resolvable. Skipped under agentsDirOverride (test hook)\n // to keep resolver tests hermetic, mirroring readRecordedState.\n if (!opts.agentsDirOverride) {\n const registrySessionId = getAgentStateSync(agentId)?.sessionId?.trim();\n if (registrySessionId) return registrySessionId;\n }\n\n return null;\n}\n\n/**\n * Read the Claude session id PINNED into an agent/conversation launcher.sh.\n *\n * The launcher is what spawns the live tmux pane: it runs\n * `claude … --session-id <uuid>` (or `--resume <uuid>`), so the pinned id is the\n * EXACT session the Terminal tab attaches to — the only deterministic ground\n * truth for \"which session is live right now.\" Resolving the transcript panel\n * from this id makes the Conversation tab match the Terminal tab by construction,\n * instead of guessing via JSONL mtime (racy: an older session's file gets touched\n * by a compaction summary write-back or a transient relaunch, its mtime jumps\n * ahead of the live session's, and the panel renders the wrong transcript).\n *\n * Checks the conversation launcher dir first, then the agent launcher dir.\n * Returns the uuid, or null when no launcher exists or it pins no session id.\n */\nconst LAUNCHER_UUID =\n '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';\nconst LAUNCHER_SESSION_ID_RE = new RegExp(`--session-id\\\\s+'?(${LAUNCHER_UUID})'?`);\nconst LAUNCHER_RESUME_RE = new RegExp(`--resume\\\\s+'?(${LAUNCHER_UUID})'?`);\n\nexport async function readLauncherPinnedSessionId(\n tmuxSession: string,\n opts: { overdeckHomeOverride?: string } = {},\n): Promise<string | null> {\n const home = opts.overdeckHomeOverride ?? getOverdeckHome();\n const candidates = [\n join(home, 'conversations', tmuxSession, 'launcher.sh'),\n join(home, 'agents', tmuxSession, 'launcher.sh'),\n ];\n for (const path of candidates) {\n const text = await readOptional(path);\n if (!text) continue;\n const match = LAUNCHER_SESSION_ID_RE.exec(text) ?? LAUNCHER_RESUME_RE.exec(text);\n if (match) return match[1]!;\n }\n return null;\n}\n\n/**\n * Read the harness + workspace recorded for an agent, honoring the test\n * override dir. Used by resolveAgentHarness / agentHasClaudeTranscript so the\n * stale-harness self-correction runs identically in prod and under test.\n */\nasync function readRecordedState(\n agentId: string,\n opts: ResolveJsonlPathOptions,\n): Promise<{ harness: string | null; workspace?: string }> {\n if (opts.agentsDirOverride) {\n try {\n const raw = await readFile(join(opts.agentsDirOverride, agentId, 'state.json'), 'utf8');\n const s = JSON.parse(raw) as { harness?: unknown; workspace?: unknown };\n return {\n harness: typeof s.harness === 'string' ? s.harness : null,\n workspace: typeof s.workspace === 'string' ? s.workspace : undefined,\n };\n } catch {\n return { harness: null };\n }\n }\n const st = getAgentStateSync(agentId);\n return { harness: st?.harness ?? null, workspace: st?.workspace };\n}\n\n/**\n * Resolve the harness for transcript routing. Reads state.json, then\n * self-corrects when the recorded value is stale.\n *\n * Trust an explicit non-default harness (codex / pi / ohmypi / acp) — it was chosen\n * by resolveHarness at spawn time and is not the generic fallback. The\n * 'claude-code' default, however, goes stale in one observed case: a\n * wipe-and-respawn that changed the provider-default harness left state.json\n * carrying the pre-respawn 'claude-code' while the fresh launch was\n * codex/ohmypi. The resolver then took the claude-code branch, found no\n * claudeSessionId, and the dashboard showed \"No conversation data available\"\n * for a live agent whose real transcript sat untouched under codex-home.\n *\n * Self-correction (claude-code/null recorded only): if the agent has NO\n * claude-code transcript on disk but DOES have a codex rollout / pi session,\n * the on-disk runtime wins — those artifacts are written only by that runtime,\n * so this cannot surface a wrong transcript. When a claude-code transcript IS\n * present it always wins, so a past codex run's codex-home can't shadow a\n * current claude-code session.\n */\nexport async function resolveAgentHarness(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const recorded = (await readRecordedState(agentId, opts)).harness;\n if (recorded === 'codex' || recorded === 'pi' || recorded === 'ohmypi' || recorded === 'acp' || recorded === 'kimi-code' || recorded === 'opencode' || recorded === 'muse') {\n return recorded;\n }\n // 'claude-code' (or null) is the default that can go stale. Correct it from\n // on-disk artifacts only when no claude-code transcript exists for the agent.\n if (await agentHasClaudeTranscript(agentId, opts)) {\n return recorded ?? 'claude-code';\n }\n if (await resolveAcpTranscriptPath(agentId, opts)) return 'acp';\n if (await resolveCodexRolloutPath(agentId, opts)) return 'codex';\n if (await resolvePiSessionPath(agentId, opts)) return 'ohmypi';\n if (await resolveKimiWirePath(agentId, opts)) return 'kimi-code';\n return recorded;\n}\n\n/**\n * Whether the agent has a resolvable claude-code transcript on disk. Used by\n * resolveAgentHarness to decide if a claude-code recording is still live\n * (vs. a stale default that should be corrected to codex/ohmypi).\n */\nasync function agentHasClaudeTranscript(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<boolean> {\n const sessionId = await resolveClaudeSessionId(agentId, opts);\n if (!sessionId) return false;\n const workspace = (await readRecordedState(agentId, opts)).workspace;\n if (!workspace) return false;\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n return pathExists(join(projectsRoot, encodeClaudeProjectDir(workspace), `${sessionId}.jsonl`));\n}\n\n/**\n * Resolve the Codex rollout JSONL for a codex-harness agent (PAN-1805).\n *\n * Fast path: the persisted codex-thread-id maps directly to its rollout file.\n * Lazy fallback (same shape as the conversation panel's PAN-1690 fix): codex\n * writes the rollout only on the first turn, so a spawn-time thread-id capture\n * can miss it — the per-agent CODEX_HOME holds only this agent's rollouts, so\n * the newest one is its current thread.\n */\nexport async function resolveCodexRolloutPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n const codexHome = join(agentDir, 'codex-home');\n if (!(await pathExists(codexHome))) return null;\n\n const { findRolloutPath, findLatestRollout } = await import('../../../lib/runtimes/codex.js');\n\n const threadId = (await readOptional(join(agentDir, 'codex-thread-id')))?.trim();\n if (threadId) {\n const rollout = findRolloutPath(codexHome, threadId);\n if (rollout) return rollout;\n }\n return findLatestRollout(codexHome);\n}\n\n/** Resolve the normalized transcript written by the persistent ACP host. */\nexport async function resolveAcpTranscriptPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const transcriptPath = join(agentsRoot, agentId, 'acp-session.jsonl');\n return await pathExists(transcriptPath) ? transcriptPath : null;\n}\n\n/**\n * Resolve the native Kimi Code CLI wire.jsonl for a kimi-code-harness agent\n * (PAN-1837 wi8a). Fast path: the per-agent kimi-session-id (persisted by\n * wi5's spawnAgent) maps directly to\n * `<kimiHome>/sessions/<workDirKey>/<sessionId>/agents/main/wire.jsonl`.\n * Fallback (no captured id): the newest session directory by wire.jsonl mtime\n * under the workspace's workDirKey bucket — same shape as the codex/pi lazy\n * fallbacks above, since Kimi may not have finished writing its first turn\n * when the id was captured.\n */\nexport async function resolveKimiWirePath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const workspace = opts.workspaceOverride ?? (await readRecordedState(agentId, opts)).workspace;\n if (!workspace) return null;\n\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const sessionId = (await readOptional(join(agentsRoot, agentId, 'kimi-session-id')))?.trim() || null;\n const kimiHome = opts.kimiHomeOverride ?? join(homedir(), '.kimi-code');\n\n // PAN-1837 review fix (P2): use the async twin here — this resolver runs on\n // the dashboard event loop and Command Deck polling re-resolves the same\n // session repeatedly, so a sync readdirSync/statSync walk would block the\n // loop on every poll as a session's history grows.\n const { findKimiWirePathAsync } = await import('../../../lib/runtimes/kimi-code.js');\n return findKimiWirePathAsync(kimiHome, workspace, sessionId);\n}\n\n/**\n * Resolve the pi/kimi session JSONL (PAN-1908). Pi writes its session transcript\n * as `<iso-ts>_<session-id>.jsonl` either in the agent dir's `sessions/` subdir\n * (conversations) OR in the agent dir root (work agents) — so check both. The dir\n * also holds `cost-events.jsonl` / `activity.jsonl`, which are NOT transcripts.\n * Return the freshest transcript by mtime, or null if pi hasn't written one yet.\n */\nexport async function resolvePiSessionPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n const NON_TRANSCRIPT = new Set([\n 'acp-session.jsonl',\n 'cost-events.jsonl',\n 'activity.jsonl',\n 'pending-events.jsonl',\n ]);\n let best: { path: string; mtime: number } | null = null;\n for (const dir of [join(agentDir, 'sessions'), agentDir]) {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of entries) {\n if (!name.endsWith('.jsonl') || NON_TRANSCRIPT.has(name)) continue;\n const p = join(dir, name);\n try {\n const m = (await stat(p)).mtimeMs;\n if (!best || m > best.mtime) best = { path: p, mtime: m };\n } catch { /* unreadable entry — skip */ }\n }\n }\n return best?.path ?? null;\n}\n\n/**\n * Resolve the JSONL transcript for an agent.\n *\n * Codex agents resolve to their rollout JSONL (PAN-1805). For claude-code\n * agents, returns the absolute path if both the claudeSessionId is known AND\n * the corresponding JSONL file exists; otherwise null.\n */\nexport async function resolveJsonlPath(\n agentId: string,\n workspacePath: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n // Dispatch on the recorded harness so a stale session.id from an earlier\n // claude-code run of the same agent id can't shadow the codex transcript.\n const harness = await resolveAgentHarness(agentId, opts);\n const behavior = behaviorForHarness(harness);\n if (behavior.transcriptKind === 'codex-rollout-jsonl') {\n return resolveCodexRolloutPath(agentId, opts);\n }\n if (behavior.transcriptKind === 'ohmypi-jsonl') {\n return resolvePiSessionPath(agentId, opts);\n }\n if (behavior.transcriptKind === 'acp-jsonl') {\n return resolveAcpTranscriptPath(agentId, opts);\n }\n if (behavior.transcriptKind === 'muse-jsonl') return resolveMuseSessionPath(agentId, opts.agentsDirOverride);\n if (behavior.transcriptKind === 'kimi-wire-jsonl') {\n return resolveKimiWirePath(agentId, opts);\n }\n\n const claudeSessionId = await resolveClaudeSessionId(agentId, opts);\n if (!claudeSessionId) {\n logTranscriptResolution(\n agentId,\n `missing-session-id:${harness ?? 'unknown'}`,\n `failed harness=${harness ?? 'unknown'} reason=no-session-id `\n + 'checked=session.id,sessions.json,runtime-state',\n opts,\n );\n return null;\n }\n\n const recordedWorkspace = (await readRecordedState(agentId, opts)).workspace;\n const effectiveWorkspacePath = recordedWorkspace ?? workspacePath;\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n const encodedDir = encodeClaudeProjectDir(effectiveWorkspacePath);\n const jsonlPath = join(projectsRoot, encodedDir, `${claudeSessionId}.jsonl`);\n if (await pathExists(jsonlPath)) {\n logTranscriptResolution(\n agentId,\n `resolved:${jsonlPath}`,\n `resolved harness=${harness ?? 'claude-code'} sessionId=${claudeSessionId} path=${jsonlPath}`,\n opts,\n );\n return jsonlPath;\n }\n logTranscriptResolution(\n agentId,\n `missing-jsonl:${jsonlPath}`,\n `failed harness=${harness ?? 'claude-code'} reason=jsonl-missing sessionId=${claudeSessionId} `\n + `workspace=${effectiveWorkspacePath} expectedPath=${jsonlPath}`,\n opts,\n );\n return null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"jsonl-resolver-jxbif7MD.js","names":[],"sources":["../../src/dashboard/server/routes/jsonl-resolver.ts"],"sourcesContent":["import { resolveMuseSessionPath } from '../../../lib/runtimes/muse-session.js';\n/**\n * JSONL transcript resolver for the Command Deck (PAN-830).\n *\n * Maps an agent ID (e.g. `agent-pan-830`, `planning-pan-830`, or a canonical\n * specialist tmux session name) to the agent's JSONL transcript file on disk.\n *\n * Codex agents (PAN-1805) write rollout JSONLs under the per-agent\n * `codex-home/sessions/` tree — resolution dispatches on the harness recorded\n * in state.json (thread-id fast path, then latest-rollout fallback).\n *\n * For claude-code agents the JSONL filename is the *Claude session ID* (a UUID\n * written by Claude Code itself), NOT the agent/tmux name. Lookup order:\n * 1. session.id — UUID pinned before fresh work-agent launch and updated\n * by auto-suspend/resume flows\n * 2. sessions.json — array of UUIDs the agent has used; the heartbeat hook\n * APPENDS new IDs and dedupes, so once a session has been\n * seen its array index never moves. After a `pan resume`\n * brings an older session back as the live one, the array\n * order no longer reflects time-recency. We disambiguate\n * across all listed IDs by JSONL mtime.\n * 3. runtime state — in-process mirror, populated by hooks\n * 4. agents registry — the DB row's session_id recorded at spawn; survives\n * janitor removal of the agent directory\n *\n * Async-only (fs/promises) because this code path runs inside the dashboard\n * server's event loop.\n */\nimport { access, readFile, stat, readdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { getAgentRuntimeState, getAgentStateSync } from '../../../lib/agents.js';\nimport { encodeClaudeProjectDir, getOverdeckHome } from '../../../lib/paths.js';\nimport { getAgentWorkspace } from '../../../lib/agent-enrichment.js';\nimport { getHarnessBehavior } from '../../../lib/runtimes/behavior.js';\nimport type { HarnessName } from '../../../lib/runtimes/types.js';\nimport { logAgentLifecycleSync } from '../../../lib/persistent-logger.js';\n\nexport interface ResolveJsonlPathOptions {\n /** Override the ~/.overdeck/agents directory (test hook). */\n agentsDirOverride?: string;\n /** Override the ~/.claude/projects directory (test hook). */\n claudeProjectsDirOverride?: string;\n /** Override the ~/.kimi-code directory (test hook). */\n kimiHomeOverride?: string;\n /**\n * Explicit workspace path for resolveKimiWirePath. Conversation rows have\n * no AgentState (readRecordedState/getAgentStateSync return nothing), so\n * dashboard conversation callers must supply conv.cwd directly instead of\n * relying on the agent-state lookup (PAN-1837 review fix).\n */\n workspaceOverride?: string;\n /** Override the runtime-state lookup (test hook). */\n getRuntimeStateAsync?: (agentId: string) => Promise<{ claudeSessionId?: string } | null>;\n /** Override forensic logging (test hook). */\n logDiagnostic?: (agentId: string, message: string) => void;\n}\n\n// Command Deck polling can resolve the same session repeatedly. Keep forensic\n// logging useful by writing only when the resolution outcome changes.\nconst transcriptResolutionSignatures = new Map<string, string>();\n\nfunction logTranscriptResolution(\n agentId: string,\n signature: string,\n message: string,\n opts: ResolveJsonlPathOptions,\n): void {\n const signatureKey = `${agentId}:${opts.agentsDirOverride ?? 'live'}`;\n if (transcriptResolutionSignatures.get(signatureKey) === signature) return;\n transcriptResolutionSignatures.set(signatureKey, signature);\n const logger = opts.logDiagnostic\n ?? (opts.agentsDirOverride ? undefined : logAgentLifecycleSync);\n logger?.(agentId, `transcript resolution: ${message}`);\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n return access(p).then(() => true, () => false);\n}\n\nasync function readOptional(p: string): Promise<string | null> {\n return readFile(p, 'utf-8').catch(() => null);\n}\n\nfunction behaviorForHarness(harness: string | null | undefined) {\n return getHarnessBehavior(harness as HarnessName | null | undefined);\n}\n\n/**\n * Pick the candidate UUID whose JSONL transcript has the most recent mtime.\n *\n * The heartbeat hook appends to sessions.json and dedupes — so once an ID is\n * recorded, its array index never moves even if a later resume makes it the\n * current session again. Choosing by JSONL mtime is the only reliable signal\n * for \"which session is actually live right now.\"\n *\n * Returns null if no candidate has a corresponding JSONL on disk. Falls back\n * to scanning every project dir if the workspace path can't be resolved\n * (covers specialist sessions whose workspace lives elsewhere).\n */\nasync function pickFreshestSessionId(\n candidates: ReadonlyArray<string>,\n agentId: string,\n opts: ResolveJsonlPathOptions,\n): Promise<string | null> {\n if (candidates.length === 0) return null;\n if (candidates.length === 1) return candidates[0]!;\n\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n\n // Fast path: derive the agent's workspace and look only in the matching\n // project dir. Avoids fanning out across every Claude project.\n let candidatePaths: Array<{ id: string; path: string }> = [];\n try {\n const workspace = await Effect.runPromise(getAgentWorkspace(agentId));\n if (workspace) {\n const projectDir = join(projectsRoot, encodeClaudeProjectDir(workspace));\n candidatePaths = candidates.map((id) => ({ id, path: join(projectDir, `${id}.jsonl`) }));\n }\n } catch { /* non-fatal — fall back to project-dir scan */ }\n\n // Slow path: scan all project dirs (specialist agents, multi-workspace cases).\n if (candidatePaths.length === 0) {\n try {\n const dirs = await readdir(projectsRoot);\n const SAFE_DIR = /^[a-zA-Z0-9_.-]+$/;\n for (const id of candidates) {\n for (const dir of dirs) {\n if (!SAFE_DIR.test(dir)) continue;\n candidatePaths.push({ id, path: join(projectsRoot, dir, `${id}.jsonl`) });\n }\n }\n } catch { /* fall through to no-mtime path */ }\n }\n\n // Stat each candidate path; pick the (id, mtime) with the newest mtime.\n let best: { id: string; mtimeMs: number } | null = null;\n for (const { id, path } of candidatePaths) {\n try {\n const s = await stat(path);\n if (!best || s.mtimeMs > best.mtimeMs) {\n best = { id, mtimeMs: s.mtimeMs };\n }\n } catch { /* missing file — skip */ }\n }\n\n // If no candidate has a JSONL on disk, fall back to the historical \"last\n // appended\" heuristic — the previous behavior. Better than returning null\n // for callers that only need a session ID, not necessarily a live JSONL.\n return best ? best.id : (candidates[candidates.length - 1] ?? null);\n}\n\n/** Async equivalent of getLatestSessionId from lib/agents.ts. */\nexport async function resolveClaudeSessionId(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n\n // 1. session.id — UUID pinned before fresh work-agent launch and updated by\n // suspend/resume flows. Authoritative when present.\n const sessionIdRaw = await readOptional(join(agentDir, 'session.id'));\n const sessionIdTrimmed = sessionIdRaw?.trim();\n if (sessionIdTrimmed) return sessionIdTrimmed;\n\n // 2. sessions.json — array of UUIDs the agent has ever used. We can't trust\n // array order (see file-level docs), so disambiguate by JSONL mtime.\n const sessionsRaw = await readOptional(join(agentDir, 'sessions.json'));\n if (sessionsRaw) {\n try {\n const parsed: unknown = JSON.parse(sessionsRaw);\n if (Array.isArray(parsed)) {\n const valid = parsed.filter((v): v is string => typeof v === 'string' && v.trim().length > 0);\n const fresh = await pickFreshestSessionId(valid, agentId, opts);\n if (fresh) return fresh;\n }\n } catch { /* non-fatal */ }\n }\n\n // 3. runtime state claudeSessionId (in-process mirror)\n try {\n const lookup = opts.getRuntimeStateAsync ?? ((id: string) => Effect.runPromise(getAgentRuntimeState(id)));\n const runtimeState = await lookup(agentId);\n if (runtimeState?.claudeSessionId) return runtimeState.claudeSessionId;\n } catch { /* non-fatal */ }\n\n // 4. agents registry — the DB row records session_id at spawn (PAN-1908) and\n // survives janitors removing the agent directory, so an ended session's\n // transcript stays resolvable. Skipped under agentsDirOverride (test hook)\n // to keep resolver tests hermetic, mirroring readRecordedState.\n if (!opts.agentsDirOverride) {\n const registrySessionId = getAgentStateSync(agentId)?.sessionId?.trim();\n if (registrySessionId) return registrySessionId;\n }\n\n return null;\n}\n\n/**\n * Read the Claude session id PINNED into an agent/conversation launcher.sh.\n *\n * The launcher is what spawns the live tmux pane: it runs\n * `claude … --session-id <uuid>` (or `--resume <uuid>`), so the pinned id is the\n * EXACT session the Terminal tab attaches to — the only deterministic ground\n * truth for \"which session is live right now.\" Resolving the transcript panel\n * from this id makes the Conversation tab match the Terminal tab by construction,\n * instead of guessing via JSONL mtime (racy: an older session's file gets touched\n * by a compaction summary write-back or a transient relaunch, its mtime jumps\n * ahead of the live session's, and the panel renders the wrong transcript).\n *\n * Checks the conversation launcher dir first, then the agent launcher dir.\n * Returns the uuid, or null when no launcher exists or it pins no session id.\n */\nconst LAUNCHER_UUID =\n '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';\nconst LAUNCHER_SESSION_ID_RE = new RegExp(`--session-id\\\\s+'?(${LAUNCHER_UUID})'?`);\nconst LAUNCHER_RESUME_RE = new RegExp(`--resume\\\\s+'?(${LAUNCHER_UUID})'?`);\n\nexport async function readLauncherPinnedSessionId(\n tmuxSession: string,\n opts: { overdeckHomeOverride?: string } = {},\n): Promise<string | null> {\n const home = opts.overdeckHomeOverride ?? getOverdeckHome();\n const candidates = [\n join(home, 'conversations', tmuxSession, 'launcher.sh'),\n join(home, 'agents', tmuxSession, 'launcher.sh'),\n ];\n for (const path of candidates) {\n const text = await readOptional(path);\n if (!text) continue;\n const match = LAUNCHER_SESSION_ID_RE.exec(text) ?? LAUNCHER_RESUME_RE.exec(text);\n if (match) return match[1]!;\n }\n return null;\n}\n\n/**\n * Read the harness + workspace recorded for an agent, honoring the test\n * override dir. Used by resolveAgentHarness / agentHasClaudeTranscript so the\n * stale-harness self-correction runs identically in prod and under test.\n */\nasync function readRecordedState(\n agentId: string,\n opts: ResolveJsonlPathOptions,\n): Promise<{ harness: string | null; workspace?: string }> {\n if (opts.agentsDirOverride) {\n try {\n const raw = await readFile(join(opts.agentsDirOverride, agentId, 'state.json'), 'utf8');\n const s = JSON.parse(raw) as { harness?: unknown; workspace?: unknown };\n return {\n harness: typeof s.harness === 'string' ? s.harness : null,\n workspace: typeof s.workspace === 'string' ? s.workspace : undefined,\n };\n } catch {\n return { harness: null };\n }\n }\n const st = getAgentStateSync(agentId);\n return { harness: st?.harness ?? null, workspace: st?.workspace };\n}\n\n/**\n * Resolve the harness for transcript routing. Reads state.json, then\n * self-corrects when the recorded value is stale.\n *\n * Trust an explicit non-default harness (codex / pi / ohmypi / acp) — it was chosen\n * by resolveHarness at spawn time and is not the generic fallback. The\n * 'claude-code' default, however, goes stale in one observed case: a\n * wipe-and-respawn that changed the provider-default harness left state.json\n * carrying the pre-respawn 'claude-code' while the fresh launch was\n * codex/ohmypi. The resolver then took the claude-code branch, found no\n * claudeSessionId, and the dashboard showed \"No conversation data available\"\n * for a live agent whose real transcript sat untouched under codex-home.\n *\n * Self-correction (claude-code/null recorded only): if the agent has NO\n * claude-code transcript on disk but DOES have a codex rollout / pi session,\n * the on-disk runtime wins — those artifacts are written only by that runtime,\n * so this cannot surface a wrong transcript. When a claude-code transcript IS\n * present it always wins, so a past codex run's codex-home can't shadow a\n * current claude-code session.\n */\nexport async function resolveAgentHarness(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const recorded = (await readRecordedState(agentId, opts)).harness;\n if (recorded === 'codex' || recorded === 'pi' || recorded === 'ohmypi' || recorded === 'acp' || recorded === 'kimi-code' || recorded === 'opencode' || recorded === 'muse') {\n return recorded;\n }\n // 'claude-code' (or null) is the default that can go stale. Correct it from\n // on-disk artifacts only when no claude-code transcript exists for the agent.\n if (await agentHasClaudeTranscript(agentId, opts)) {\n return recorded ?? 'claude-code';\n }\n if (await resolveAcpTranscriptPath(agentId, opts)) return 'acp';\n if (await resolveCodexRolloutPath(agentId, opts)) return 'codex';\n if (await resolvePiSessionPath(agentId, opts)) return 'ohmypi';\n if (await resolveKimiWirePath(agentId, opts)) return 'kimi-code';\n return recorded;\n}\n\n/**\n * Whether the agent has a resolvable claude-code transcript on disk. Used by\n * resolveAgentHarness to decide if a claude-code recording is still live\n * (vs. a stale default that should be corrected to codex/ohmypi).\n */\nasync function agentHasClaudeTranscript(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<boolean> {\n const sessionId = await resolveClaudeSessionId(agentId, opts);\n if (!sessionId) return false;\n const workspace = (await readRecordedState(agentId, opts)).workspace;\n if (!workspace) return false;\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n return pathExists(join(projectsRoot, encodeClaudeProjectDir(workspace), `${sessionId}.jsonl`));\n}\n\n/**\n * Resolve the Codex rollout JSONL for a codex-harness agent (PAN-1805).\n *\n * Fast path: the persisted codex-thread-id maps directly to its rollout file.\n * Lazy fallback (same shape as the conversation panel's PAN-1690 fix): codex\n * writes the rollout only on the first turn, so a spawn-time thread-id capture\n * can miss it — the per-agent CODEX_HOME holds only this agent's rollouts, so\n * the newest one is its current thread.\n */\nexport async function resolveCodexRolloutPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n const codexHome = join(agentDir, 'codex-home');\n if (!(await pathExists(codexHome))) return null;\n\n const { findRolloutPath, findLatestRollout } = await import('../../../lib/runtimes/codex.js');\n\n const threadId = (await readOptional(join(agentDir, 'codex-thread-id')))?.trim();\n if (threadId) {\n const rollout = findRolloutPath(codexHome, threadId);\n if (rollout) return rollout;\n }\n return findLatestRollout(codexHome);\n}\n\n/** Resolve the normalized transcript written by the persistent ACP host. */\nexport async function resolveAcpTranscriptPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const transcriptPath = join(agentsRoot, agentId, 'acp-session.jsonl');\n return await pathExists(transcriptPath) ? transcriptPath : null;\n}\n\n/**\n * Resolve the native Kimi Code CLI wire.jsonl for a kimi-code-harness agent\n * (PAN-1837 wi8a). Fast path: the per-agent kimi-session-id (persisted by\n * wi5's spawnAgent) maps directly to\n * `<kimiHome>/sessions/<workDirKey>/<sessionId>/agents/main/wire.jsonl`.\n * Fallback (no captured id): the newest session directory by wire.jsonl mtime\n * under the workspace's workDirKey bucket — same shape as the codex/pi lazy\n * fallbacks above, since Kimi may not have finished writing its first turn\n * when the id was captured.\n */\nexport async function resolveKimiWirePath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const workspace = opts.workspaceOverride ?? (await readRecordedState(agentId, opts)).workspace;\n if (!workspace) return null;\n\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const sessionId = (await readOptional(join(agentsRoot, agentId, 'kimi-session-id')))?.trim() || null;\n const kimiHome = opts.kimiHomeOverride ?? join(homedir(), '.kimi-code');\n\n // PAN-1837 review fix (P2): use the async twin here — this resolver runs on\n // the dashboard event loop and Command Deck polling re-resolves the same\n // session repeatedly, so a sync readdirSync/statSync walk would block the\n // loop on every poll as a session's history grows.\n const { findKimiWirePathAsync } = await import('../../../lib/runtimes/kimi-code.js');\n return findKimiWirePathAsync(kimiHome, workspace, sessionId);\n}\n\n/**\n * Resolve the pi/kimi session JSONL (PAN-1908). Pi writes its session transcript\n * as `<iso-ts>_<session-id>.jsonl` either in the agent dir's `sessions/` subdir\n * (conversations) OR in the agent dir root (work agents) — so check both. The dir\n * also holds `cost-events.jsonl` / `activity.jsonl`, which are NOT transcripts.\n * Return the freshest transcript by mtime, or null if pi hasn't written one yet.\n */\nexport async function resolvePiSessionPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n const NON_TRANSCRIPT = new Set([\n 'acp-session.jsonl',\n 'cost-events.jsonl',\n 'activity.jsonl',\n 'pending-events.jsonl',\n ]);\n let best: { path: string; mtime: number } | null = null;\n for (const dir of [join(agentDir, 'sessions'), agentDir]) {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of entries) {\n if (!name.endsWith('.jsonl') || NON_TRANSCRIPT.has(name)) continue;\n const p = join(dir, name);\n try {\n const m = (await stat(p)).mtimeMs;\n if (!best || m > best.mtime) best = { path: p, mtime: m };\n } catch { /* unreadable entry — skip */ }\n }\n }\n return best?.path ?? null;\n}\n\n/**\n * Resolve the JSONL transcript for an agent.\n *\n * Codex agents resolve to their rollout JSONL (PAN-1805). For claude-code\n * agents, returns the absolute path if both the claudeSessionId is known AND\n * the corresponding JSONL file exists; otherwise null.\n */\nexport async function resolveJsonlPath(\n agentId: string,\n workspacePath: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n // Dispatch on the recorded harness so a stale session.id from an earlier\n // claude-code run of the same agent id can't shadow the codex transcript.\n const harness = await resolveAgentHarness(agentId, opts);\n const behavior = behaviorForHarness(harness);\n if (behavior.transcriptKind === 'codex-rollout-jsonl') {\n return resolveCodexRolloutPath(agentId, opts);\n }\n if (behavior.transcriptKind === 'ohmypi-jsonl') {\n return resolvePiSessionPath(agentId, opts);\n }\n if (behavior.transcriptKind === 'acp-jsonl') {\n return resolveAcpTranscriptPath(agentId, opts);\n }\n if (behavior.transcriptKind === 'muse-jsonl') return resolveMuseSessionPath(agentId, opts.agentsDirOverride);\n if (behavior.transcriptKind === 'kimi-wire-jsonl') {\n return resolveKimiWirePath(agentId, opts);\n }\n\n const claudeSessionId = await resolveClaudeSessionId(agentId, opts);\n if (!claudeSessionId) {\n logTranscriptResolution(\n agentId,\n `missing-session-id:${harness ?? 'unknown'}`,\n `failed harness=${harness ?? 'unknown'} reason=no-session-id `\n + 'checked=session.id,sessions.json,runtime-state',\n opts,\n );\n return null;\n }\n\n const recordedWorkspace = (await readRecordedState(agentId, opts)).workspace;\n const effectiveWorkspacePath = recordedWorkspace ?? workspacePath;\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n const encodedDir = encodeClaudeProjectDir(effectiveWorkspacePath);\n const jsonlPath = join(projectsRoot, encodedDir, `${claudeSessionId}.jsonl`);\n if (await pathExists(jsonlPath)) {\n logTranscriptResolution(\n agentId,\n `resolved:${jsonlPath}`,\n `resolved harness=${harness ?? 'claude-code'} sessionId=${claudeSessionId} path=${jsonlPath}`,\n opts,\n );\n return jsonlPath;\n }\n logTranscriptResolution(\n agentId,\n `missing-jsonl:${jsonlPath}`,\n `failed harness=${harness ?? 'claude-code'} reason=jsonl-missing sessionId=${claudeSessionId} `\n + `workspace=${effectiveWorkspacePath} expectedPath=${jsonlPath}`,\n opts,\n );\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { En as writeKimiSessionId, Tn as withKimiSessionCaptureLock, bn as kimiSessionsRoot, hn as findKimiWirePathAsync, vn as init_kimi_code, wn as waitForNewKimiSessionAsync } from "./agent-state-
|
|
1
|
+
import { En as writeKimiSessionId, Tn as withKimiSessionCaptureLock, bn as kimiSessionsRoot, hn as findKimiWirePathAsync, vn as init_kimi_code, wn as waitForNewKimiSessionAsync } from "./agent-state-B196fj1h.js";
|
|
2
2
|
init_kimi_code();
|
|
3
3
|
export { findKimiWirePathAsync, kimiSessionsRoot, waitForNewKimiSessionAsync, withKimiSessionCaptureLock, writeKimiSessionId };
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { a as stepSkipped, i as stepOk, n as init_types, r as stepFailed } from "./types-JR7ZOtpL.js";
|
|
2
2
|
import { t as archivePlanning } from "./archive-planning-CtdBSD1-.js";
|
|
3
|
-
import { t as closeIssue } from "./close-issue-
|
|
4
|
-
import { t as teardownWorkspace } from "./teardown-workspace-
|
|
5
|
-
import { a as deepWipe, i as closeOut, n as cancelIssueWorkflow, o as resetToTodo, r as close, t as approve } from "./workflows-
|
|
3
|
+
import { t as closeIssue } from "./close-issue-Bmu9aOIx.js";
|
|
4
|
+
import { t as teardownWorkspace } from "./teardown-workspace-C-9murBw.js";
|
|
5
|
+
import { a as deepWipe, i as closeOut, n as cancelIssueWorkflow, o as resetToTodo, r as close, t as approve } from "./workflows-N4QMkDzN.js";
|
|
6
6
|
//#region ../../lib/lifecycle/index.ts
|
|
7
7
|
init_types();
|
|
8
8
|
//#endregion
|
|
9
9
|
export { approve, archivePlanning, cancelIssueWorkflow, close, closeIssue, closeOut, deepWipe, resetToTodo, stepFailed, stepOk, stepSkipped, teardownWorkspace };
|
|
10
10
|
|
|
11
|
-
//# sourceMappingURL=lifecycle-
|
|
11
|
+
//# sourceMappingURL=lifecycle-CvcZ4_fA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle-
|
|
1
|
+
{"version":3,"file":"lifecycle-CvcZ4_fA.js","names":[],"sources":["../../src/lib/lifecycle/index.ts"],"sourcesContent":["/**\n * Lifecycle — Composable, idempotent post-merge operations.\n *\n * Atomic operations:\n * archive-planning — PRD active→completed + workspace .pan/ preservation\n * close-issue — IssueTracker transition + label management\n * teardown-workspace — Full workspace cleanup (agent, worktree, Docker, tmux)\n *\n * Workflows (compose the above):\n * approve() — merge + close + archive + teardown + compact\n * close() — close + teardown\n * closeOut() — verify-merged + archive + teardown + close + label + clear-status\n * deepWipe() — teardown(deleteBranches) + delete agent state + reset issue\n */\n\n// Types\nexport type {\n StepResult,\n WorkflowResult,\n LifecycleContext,\n TeardownOptions,\n ArchiveOptions,\n ApproveOptions,\n DeepWipeOptions,\n CloseOutOptions,\n} from './types.js';\n\nexport { stepOk, stepSkipped, stepFailed } from './types.js';\n\n// Atomic operations\nexport { archivePlanning } from './archive-planning.js';\nexport { closeIssue } from './close-issue.js';\nexport { teardownWorkspace } from './teardown-workspace.js';\n\n// Workflows\nexport { approve, close, closeOut, deepWipe, resetToTodo, cancelIssueWorkflow } from './workflows.js';\n"],"mappings":";;;;;;YA2B6D"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { l as processLinearMcpAuthWake } from "./linear-mcp-auth-
|
|
1
|
+
import { l as processLinearMcpAuthWake } from "./linear-mcp-auth-Dx74yCAY.js";
|
|
2
2
|
export { processLinearMcpAuthWake };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as init_event_store, i as initEventStore } from "./event-store-BYOh686E.js";
|
|
2
|
-
import { Kt as messageAgentWithOutcome, Or as init_delivery, Sr as AmbiguousKeyedDeliveryError, Wt as init_messaging, _ as init_agent_state, f as getAgentDir } from "./agent-state-
|
|
2
|
+
import { Kt as messageAgentWithOutcome, Or as init_delivery, Sr as AmbiguousKeyedDeliveryError, Wt as init_messaging, _ as init_agent_state, f as getAgentDir } from "./agent-state-B196fj1h.js";
|
|
3
3
|
import { _ as init_tmux_dedup, l as KeyedMarkerVerificationError, u as KeyedSubmitTargetDeadError, v as isKeyedSubmitBlockedMenuError } from "./auto-spawn-consent-CJeQM5e0.js";
|
|
4
4
|
import { n as createPromiseCoalescer } from "./in-flight-guard-HoToxzGt.js";
|
|
5
5
|
import { join } from "node:path";
|
|
@@ -276,4 +276,4 @@ function processLinearMcpAuthWake() {
|
|
|
276
276
|
//#endregion
|
|
277
277
|
export { appendLinearMcpAuthHealthyEvent as a, linearMcpAuthEvent as c, appendLinearMcpAuthEvent as i, processLinearMcpAuthWake as l, LINEAR_MCP_AUTH_WAKE_COPY as n, appendLinearMcpAuthNotifiedEvent as o, appendLinearMcpAuthCallbackRelayedEvent as r, computeLinearMcpAuthWakeSet as s, LINEAR_MCP_AUTH_URL_TTL_MS as t, resolveLinearMcpAuthIntervention as u };
|
|
278
278
|
|
|
279
|
-
//# sourceMappingURL=linear-mcp-auth-
|
|
279
|
+
//# sourceMappingURL=linear-mcp-auth-Dx74yCAY.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linear-mcp-auth-BFlpMvWh.js","names":[],"sources":["../../src/lib/linear-mcp-auth.ts"],"sourcesContent":["import type { DomainEvent } from '@overdeck/contracts';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n initEventStore,\n type StoredEvent,\n} from '../dashboard/server/event-store.js';\nimport { getAgentDir } from './agents/agent-state.js';\nimport { AmbiguousKeyedDeliveryError } from './agents/delivery.js';\nimport { messageAgentWithOutcome } from './agents/messaging.js';\nimport { isKeyedSubmitBlockedMenuError, KeyedMarkerVerificationError, KeyedSubmitTargetDeadError } from './tmux-dedup.js';\nimport { createPromiseCoalescer } from './cloister/in-flight-guard.js';\n\nexport const LINEAR_MCP_AUTH_URL_TTL_MS = 30 * 60 * 1000;\nexport const LINEAR_MCP_AUTH_WAKE_COPY = 'Linear MCP authentication has been restored — the operator completed OAuth. Re-check access now with one lightweight read (e.g. mcp__linear__list_issues). If it succeeds, resume your canonical task. If it still returns an authentication error, call mcp__linear__authenticate once and wait; do not retry in a loop.';\n\nconst linearMcpAuthWakeCoalescer = createPromiseCoalescer<void>();\n\nexport type LinearMcpAuthStatus = 'none' | 'active' | 'expired';\nexport type LinearMcpAuthNotificationOutcome = 'delivering' | 'delivered' | 'queued' | 'failed';\n\nexport interface LinearMcpAuthBlockedAgent {\n agentId: string;\n issueId: string | null;\n declaredAt: string;\n expiresAt: string;\n /** Set by a completion record (delivered/queued/failed). Terminal: the\n * agent never re-enters the wake set for this lifecycle. */\n notifiedAt: string | null;\n /** Projection-only enrichment attached by the GET route from the issues\n * resolver; never persisted in lifecycle events. */\n issueUrl?: string | null;\n /** Projection-only enrichment attached by the GET route from the\n * conversations read door: the canonical /conv/<rowid> dashboard URL for\n * conv-* agents; never persisted in lifecycle events. */\n conversationUrl?: string | null;\n}\n\nexport interface LinearMcpAuthIntervention {\n status: LinearMcpAuthStatus;\n authUrl: string | null;\n authUrlAgentId: string | null;\n authUrlExpiresAt: string | null;\n declaredAt: string | null;\n blockedAgents: LinearMcpAuthBlockedAgent[];\n}\n\ninterface LinearMcpAuthLifecycle {\n /** Durable correlation id: the sequence of the required event that opened\n * this lifecycle. `notified` events carry it so a delayed delivery record\n * can only be applied to the lifecycle whose wake attempt produced it. */\n id: string;\n declaredAt: string;\n authUrl: string | null;\n authUrlAgentId: string | null;\n authUrlExpiresAt: string | null;\n agents: Map<string, LinearMcpAuthBlockedAgent>;\n}\n\ninterface LinearMcpAuthFold {\n open: LinearMcpAuthLifecycle | null;\n lastCompleted: LinearMcpAuthLifecycle | null;\n}\n\nexport interface RequiredPayload {\n agentId: string;\n issueId: string | null;\n authUrl: string | null;\n expiresAt: string | null;\n}\n\nexport interface HealthyPayload {\n agentId: string;\n issueId: string | null;\n source: 'hook' | 'operator';\n}\n\nexport interface NotifiedPayload {\n agentId: string;\n issueId: string | null;\n outcome: LinearMcpAuthNotificationOutcome;\n /** Correlates this record to the lifecycle whose wake attempt produced it.\n * Absent only in events written before PAN-2997 review cycle 2. */\n lifecycleId?: string;\n}\n\nexport interface CallbackRelayedPayload {\n agentId: string;\n issueId: string | null;\n}\n\nexport type LinearMcpAuthEventInput =\n | { type: 'linear_mcp_auth.required'; timestamp?: string; payload: RequiredPayload }\n | { type: 'linear_mcp_auth.healthy'; timestamp?: string; payload: HealthyPayload }\n | { type: 'linear_mcp_auth.notified'; timestamp?: string; payload: NotifiedPayload }\n | { type: 'linear_mcp_auth.callback_relayed'; timestamp?: string; payload: CallbackRelayedPayload };\n\nconst LINEAR_MCP_AUTH_EVENT_TYPES = [\n 'linear_mcp_auth.required',\n 'linear_mcp_auth.healthy',\n 'linear_mcp_auth.notified',\n 'linear_mcp_auth.callback_relayed',\n] as const;\n\nfunction defaultExpiresAt(declaredAt: string): string {\n return new Date(Date.parse(declaredAt) + LINEAR_MCP_AUTH_URL_TTL_MS).toISOString();\n}\n\nfunction foldLinearMcpAuthEvents(\n events: StoredEvent[],\n initial: LinearMcpAuthFold = { open: null, lastCompleted: null },\n): LinearMcpAuthFold {\n let open = initial.open;\n let lastCompleted = initial.lastCompleted;\n\n for (const event of events) {\n switch (event.type) {\n case 'linear_mcp_auth.required': {\n const payload = event.payload as RequiredPayload;\n open ??= {\n id: `seq-${event.sequence}`,\n declaredAt: event.timestamp,\n authUrl: null,\n authUrlAgentId: null,\n authUrlExpiresAt: null,\n agents: new Map(),\n };\n const expiresAt = payload.expiresAt ?? defaultExpiresAt(event.timestamp);\n open.agents.set(payload.agentId, {\n agentId: payload.agentId,\n issueId: payload.issueId,\n declaredAt: event.timestamp,\n expiresAt,\n notifiedAt: null,\n });\n if (payload.authUrl !== null) {\n open.authUrl = payload.authUrl;\n open.authUrlAgentId = payload.agentId;\n open.authUrlExpiresAt = expiresAt;\n }\n break;\n }\n case 'linear_mcp_auth.healthy':\n if (open !== null) {\n lastCompleted = open;\n open = null;\n }\n break;\n case 'linear_mcp_auth.notified': {\n const payload = event.payload as NotifiedPayload;\n // A notification record belongs to exactly one lifecycle: the one\n // whose wake pass produced it. Without this correlation a delayed\n // delivery from lifecycle A could be stamped onto lifecycle B,\n // permanently suppressing B's wake.\n const lifecycle = payload.lifecycleId !== undefined\n ? (open?.id === payload.lifecycleId ? open\n : lastCompleted?.id === payload.lifecycleId ? lastCompleted\n : null)\n : (open ?? lastCompleted);\n const agent = lifecycle?.agents.get(payload.agentId);\n // 'delivering' rows from earlier iterations are not completions and\n // are ignored: interrupted deliveries are resumed through the keyed\n // outbox (see deliverWakeWithOutbox), not the event log.\n if (agent !== undefined && payload.outcome !== 'delivering') {\n lifecycle?.agents.set(payload.agentId, {\n ...agent,\n notifiedAt: event.timestamp,\n });\n }\n break;\n }\n case 'linear_mcp_auth.callback_relayed':\n break;\n }\n }\n\n return { open, lastCompleted };\n}\n\ninterface LinearMcpAuthProjectionCache {\n fold: LinearMcpAuthFold;\n coveredSequence: number;\n}\n\nlet projectionCache: LinearMcpAuthProjectionCache | null = null;\n\nexport function _resetLinearMcpAuthProjectionCacheForTests(): void {\n projectionCache = null;\n wakeFollowUpDelayMs = WAKE_FOLLOW_UP_MIN_DELAY_MS;\n}\n\n/**\n * Read the lifecycle fold from a maintained projection rather than rebuilding\n * it per poll. The projection advances incrementally: each read fetches only\n * auth-typed events newer than the covered sequence via an indexed SQL query\n * (`queryByTypesSince`), so unrelated retained history is never materialized\n * — a full `readFrom(0)` on the seven-day event table has blown the heap in\n * smoke testing, and the banner calls this path every 5–30 seconds per\n * connected client.\n */\nasync function readLinearMcpAuthFold(): Promise<LinearMcpAuthFold> {\n const store = await initEventStore();\n // The events table is a disposable cache — rebuilt, compacted, or purged\n // underneath us. If it now ends before the covered sequence, the cached\n // prefix is stale and the projection must restart from scratch.\n if (projectionCache !== null && store.getLatestSequence() < projectionCache.coveredSequence) {\n projectionCache = null;\n }\n const afterSequence = projectionCache?.coveredSequence ?? 0;\n const delta = store.queryByTypesSince([...LINEAR_MCP_AUTH_EVENT_TYPES], afterSequence);\n if (delta.length === 0 && projectionCache !== null) {\n return projectionCache.fold;\n }\n const fold = foldLinearMcpAuthEvents(delta, projectionCache?.fold);\n const lastEvent = delta[delta.length - 1];\n projectionCache = {\n fold,\n coveredSequence: lastEvent !== undefined ? lastEvent.sequence : afterSequence,\n };\n return fold;\n}\n\nfunction projectLifecycle(\n lifecycle: LinearMcpAuthLifecycle | null,\n nowIso: string,\n): LinearMcpAuthIntervention {\n if (lifecycle === null) {\n return {\n status: 'none',\n authUrl: null,\n authUrlAgentId: null,\n authUrlExpiresAt: null,\n declaredAt: null,\n blockedAgents: [],\n };\n }\n\n const status = lifecycle.authUrlExpiresAt !== null\n && lifecycle.authUrlExpiresAt < nowIso\n ? 'expired'\n : 'active';\n\n return {\n status,\n authUrl: lifecycle.authUrl,\n authUrlAgentId: lifecycle.authUrlAgentId,\n authUrlExpiresAt: lifecycle.authUrlExpiresAt,\n declaredAt: lifecycle.declaredAt,\n blockedAgents: [...lifecycle.agents.values()],\n };\n}\n\nexport function linearMcpAuthEvent(input: LinearMcpAuthEventInput): Omit<DomainEvent, 'sequence'> {\n return {\n type: input.type,\n timestamp: input.timestamp ?? new Date().toISOString(),\n payload: input.payload,\n } as Omit<DomainEvent, 'sequence'>;\n}\n\nexport async function appendLinearMcpAuthEvent(input: LinearMcpAuthEventInput): Promise<number> {\n const store = await initEventStore();\n return store.appendAsync(linearMcpAuthEvent(input));\n}\n\nexport function appendLinearMcpAuthRequiredEvent(\n payload: RequiredPayload,\n timestamp?: string,\n): Promise<number> {\n return appendLinearMcpAuthEvent({ type: 'linear_mcp_auth.required', payload, timestamp });\n}\n\nexport function appendLinearMcpAuthHealthyEvent(\n payload: HealthyPayload,\n timestamp?: string,\n): Promise<number> {\n return appendLinearMcpAuthEvent({ type: 'linear_mcp_auth.healthy', payload, timestamp });\n}\n\nexport function appendLinearMcpAuthNotifiedEvent(\n payload: NotifiedPayload,\n timestamp?: string,\n): Promise<number> {\n return appendLinearMcpAuthEvent({ type: 'linear_mcp_auth.notified', payload, timestamp });\n}\n\nexport function appendLinearMcpAuthCallbackRelayedEvent(\n payload: CallbackRelayedPayload,\n timestamp?: string,\n): Promise<number> {\n return appendLinearMcpAuthEvent({ type: 'linear_mcp_auth.callback_relayed', payload, timestamp });\n}\n\nexport async function resolveLinearMcpAuthIntervention(\n nowIso = new Date().toISOString(),\n): Promise<LinearMcpAuthIntervention> {\n const { open } = await readLinearMcpAuthFold();\n return projectLifecycle(open, nowIso);\n}\n\nexport interface LinearMcpAuthWakeSet {\n lifecycleId: string;\n agents: LinearMcpAuthBlockedAgent[];\n}\n\nexport async function computeLinearMcpAuthWakeSet(): Promise<LinearMcpAuthWakeSet | null> {\n const { lastCompleted } = await readLinearMcpAuthFold();\n if (lastCompleted === null) return null;\n const agents = [...lastCompleted.agents.values()].filter(agent => agent.notifiedAt === null);\n return { lifecycleId: lastCompleted.id, agents };\n}\n\nconst MAX_WAKE_PASSES = 10;\nconst WAKE_FOLLOW_UP_MIN_DELAY_MS = 1000;\nconst WAKE_FOLLOW_UP_MAX_DELAY_MS = 60_000;\n\nlet wakeFollowUpDelayMs = WAKE_FOLLOW_UP_MIN_DELAY_MS;\n\n// ── Keyed wake outbox ────────────────────────────────────────────────────────\n//\n// The durable delivery receipt for one (lifecycleId, agentId) wake. Unlike a\n// claim flag in the event log, the entry is lifecycle-keyed, carries the\n// intended message and the delivery state/outcome, and is updated by the\n// delivery wrapper itself on every path — so every acknowledged send has a\n// receipt, recovery resumes only unacknowledged entries, and no cross-\n// lifecycle content or timestamp matching is involved. Writes are temp-file +\n// rename (atomic on POSIX) and all I/O is async (NFR-1).\n\nexport interface LinearMcpWakeOutboxEntry {\n lifecycleId: string;\n agentId: string;\n message: string;\n state: 'pending' | 'acknowledged';\n outcome?: LinearMcpAuthNotificationOutcome;\n createdAt: string;\n acknowledgedAt?: string;\n}\n\nfunction wakeOutboxPath(agentId: string, lifecycleId: string): string {\n return join(getAgentDir(agentId), 'linear-mcp-wake', `${lifecycleId}.json`);\n}\n\nasync function readWakeOutbox(agentId: string, lifecycleId: string): Promise<LinearMcpWakeOutboxEntry | null> {\n try {\n const raw = await readFile(wakeOutboxPath(agentId, lifecycleId), 'utf-8');\n return JSON.parse(raw) as LinearMcpWakeOutboxEntry;\n } catch {\n return null;\n }\n}\n\nasync function writeWakeOutbox(entry: LinearMcpWakeOutboxEntry): Promise<void> {\n const path = wakeOutboxPath(entry.agentId, entry.lifecycleId);\n await mkdir(join(getAgentDir(entry.agentId), 'linear-mcp-wake'), { recursive: true });\n const tmpPath = `${path}.${process.pid}.tmp`;\n await writeFile(tmpPath, JSON.stringify(entry, null, 2), 'utf-8');\n await rename(tmpPath, path);\n}\n\ninterface WakeDelivery {\n /** True when this call actually invoked the delivery door. */\n sent: boolean;\n outcome: LinearMcpAuthNotificationOutcome;\n}\n\n/**\n * Deliver one lifecycle's wake through the keyed outbox. An acknowledged\n * entry suppresses the replay and replays only its recorded outcome — a\n * crash after the ack write never produces a second send, and the recorded\n * outcome stays faithful ('queued' is never upgraded to 'delivered'). A\n * pending or missing entry is (re)driven: a crash before the ack write\n * replays the send, which is correct because that send was never\n * acknowledged.\n */\nasync function deliverWakeWithOutbox(agentId: string, lifecycleId: string): Promise<WakeDelivery> {\n const existing = await readWakeOutbox(agentId, lifecycleId);\n if (existing?.state === 'acknowledged' && existing.outcome !== undefined) {\n return { sent: false, outcome: existing.outcome };\n }\n if (existing === null) {\n await writeWakeOutbox({\n lifecycleId,\n agentId,\n message: LINEAR_MCP_AUTH_WAKE_COPY,\n state: 'pending',\n createdAt: new Date().toISOString(),\n });\n }\n\n let outcome: LinearMcpAuthNotificationOutcome;\n try {\n // The key is threaded to the delivery door, where the crash-independent\n // component (supervisor key set / tmux session option) deduplicates the\n // side effect itself. A dashboard crash after a completed delivery can\n // still replay THIS call (the outbox ack may not have landed), but the\n // replayed delivery deduplicates at the door — the agent never sees the\n // same keyed wake twice.\n outcome = await messageAgentWithOutcome(\n agentId,\n LINEAR_MCP_AUTH_WAKE_COPY,\n 'linear-mcp-auth-wake',\n { dedupKey: `linear-mcp-auth-wake:${lifecycleId}` },\n );\n } catch (error) {\n // AMBIGUOUS keyed delivery (cycle 8): the supervisor may have completed\n // the injection but its answer never arrived. Do NOT record a terminal\n // 'failed' — leave the entry pending so the next wake pass (or boot\n // recovery) retries the SAME key at the SAME tier, where the supervisor's\n // in-flight reservation/delivered set deduplicates it. At-most-once is\n // preserved by the door; a terminal record would either lose a wake that\n // never landed or misreport one that did.\n //\n // DEAD TARGET (cycle 9): the tmux submit found the pane dead and sent NO\n // Enter, deliberately preserving the pending claim and leaving the key\n // non-terminal. Same handling: the entry stays pending so a later pass\n // re-drives the wake — by then messageAgent's zombie/stopped detection\n // resumes the agent and the keyed door of the new session delivers.\n //\n // BLOCKING MENU (PAN-3212): the harness swallowed the paste and Enter was\n // deliberately withheld. Keep the outbox pending so the next pass re-drives\n // the same key; the tmux recovery path proves payload presence and re-pastes\n // when the old pending claim contains no visible text.\n //\n // UNVERIFIED MARKERS (cycle 13): a safety-critical marker read failed\n // mid-repair/rollback, so the marker state is unproven and the poison\n // breadcrumb stays authoritative. Same handling: pending, retried later.\n if (\n error instanceof AmbiguousKeyedDeliveryError\n || error instanceof KeyedSubmitTargetDeadError\n || isKeyedSubmitBlockedMenuError(error)\n || error instanceof KeyedMarkerVerificationError\n ) {\n throw error;\n }\n outcome = 'failed';\n }\n await writeWakeOutbox({\n lifecycleId,\n agentId,\n message: LINEAR_MCP_AUTH_WAKE_COPY,\n state: 'acknowledged',\n outcome,\n createdAt: existing?.createdAt ?? new Date().toISOString(),\n acknowledgedAt: new Date().toISOString(),\n });\n return { sent: true, outcome };\n}\n\n/**\n * Record the completion of a delivery attempt in the lifecycle event log —\n * this, not the outbox receipt, is what removes the agent from the wake set.\n * If the append fails, the agent stays in the wake set but the acknowledged\n * outbox entry suppresses any replay: the next pass only retries this\n * completion record.\n */\nasync function recordWakeCompletion(\n blockedAgent: LinearMcpAuthBlockedAgent,\n lifecycleId: string,\n outcome: LinearMcpAuthNotificationOutcome,\n): Promise<void> {\n try {\n await appendLinearMcpAuthNotifiedEvent({\n agentId: blockedAgent.agentId,\n issueId: blockedAgent.issueId,\n outcome,\n lifecycleId,\n });\n } catch (completionError) {\n console.error(`[linear-mcp-auth] failed to record wake outcome for ${blockedAgent.agentId}:`, completionError);\n }\n}\n\nexport function processLinearMcpAuthWake(): Promise<void> {\n return linearMcpAuthWakeCoalescer.run('global', async () => {\n // Drain-until-stable: a healthy event that lands while this pass is\n // delivering wakes would otherwise be swallowed by the coalescer (its\n // trigger gets this in-flight promise back). Re-reading the fold after\n // each pass surfaces the newly completed lifecycle, so it gets its own\n // wake round inside the same coalesced run.\n let stabilized = false;\n for (let pass = 0; pass < MAX_WAKE_PASSES; pass++) {\n const wakeSet = await computeLinearMcpAuthWakeSet();\n if (wakeSet === null || wakeSet.agents.length === 0) {\n stabilized = true;\n wakeFollowUpDelayMs = WAKE_FOLLOW_UP_MIN_DELAY_MS;\n break;\n }\n for (const blockedAgent of wakeSet.agents) {\n try {\n const delivery = await deliverWakeWithOutbox(blockedAgent.agentId, wakeSet.lifecycleId);\n await recordWakeCompletion(blockedAgent, wakeSet.lifecycleId, delivery.outcome);\n } catch (deliveryError) {\n // The outbox itself is unreadable/unwritable — without a durable\n // receipt an interrupted attempt cannot be reconciled, so skip this\n // agent rather than risk an unreconciled replay.\n console.error(`[linear-mcp-auth] wake outbox failure for ${blockedAgent.agentId}; skipping this pass:`, deliveryError);\n }\n }\n }\n if (!stabilized) {\n // Every healthy trigger that arrived during this run received the same\n // coalesced promise, so no external retry is guaranteed. Schedule one\n // ourselves with bounded exponential backoff — a persistent outbox or\n // EventStore failure must not turn into a hot retry/log loop.\n console.error(`[linear-mcp-auth] wake pass did not stabilize after ${MAX_WAKE_PASSES} passes — scheduling a follow-up run in ${wakeFollowUpDelayMs}ms`);\n const timer = setTimeout(() => { void processLinearMcpAuthWake(); }, wakeFollowUpDelayMs);\n timer.unref();\n wakeFollowUpDelayMs = Math.min(wakeFollowUpDelayMs * 2, WAKE_FOLLOW_UP_MAX_DELAY_MS);\n }\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"linear-mcp-auth-Dx74yCAY.js","names":[],"sources":["../../src/lib/linear-mcp-auth.ts"],"sourcesContent":["import type { DomainEvent } from '@overdeck/contracts';\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport {\n initEventStore,\n type StoredEvent,\n} from '../dashboard/server/event-store.js';\nimport { getAgentDir } from './agents/agent-state.js';\nimport { AmbiguousKeyedDeliveryError } from './agents/delivery.js';\nimport { messageAgentWithOutcome } from './agents/messaging.js';\nimport { isKeyedSubmitBlockedMenuError, KeyedMarkerVerificationError, KeyedSubmitTargetDeadError } from './tmux-dedup.js';\nimport { createPromiseCoalescer } from './cloister/in-flight-guard.js';\n\nexport const LINEAR_MCP_AUTH_URL_TTL_MS = 30 * 60 * 1000;\nexport const LINEAR_MCP_AUTH_WAKE_COPY = 'Linear MCP authentication has been restored — the operator completed OAuth. Re-check access now with one lightweight read (e.g. mcp__linear__list_issues). If it succeeds, resume your canonical task. If it still returns an authentication error, call mcp__linear__authenticate once and wait; do not retry in a loop.';\n\nconst linearMcpAuthWakeCoalescer = createPromiseCoalescer<void>();\n\nexport type LinearMcpAuthStatus = 'none' | 'active' | 'expired';\nexport type LinearMcpAuthNotificationOutcome = 'delivering' | 'delivered' | 'queued' | 'failed';\n\nexport interface LinearMcpAuthBlockedAgent {\n agentId: string;\n issueId: string | null;\n declaredAt: string;\n expiresAt: string;\n /** Set by a completion record (delivered/queued/failed). Terminal: the\n * agent never re-enters the wake set for this lifecycle. */\n notifiedAt: string | null;\n /** Projection-only enrichment attached by the GET route from the issues\n * resolver; never persisted in lifecycle events. */\n issueUrl?: string | null;\n /** Projection-only enrichment attached by the GET route from the\n * conversations read door: the canonical /conv/<rowid> dashboard URL for\n * conv-* agents; never persisted in lifecycle events. */\n conversationUrl?: string | null;\n}\n\nexport interface LinearMcpAuthIntervention {\n status: LinearMcpAuthStatus;\n authUrl: string | null;\n authUrlAgentId: string | null;\n authUrlExpiresAt: string | null;\n declaredAt: string | null;\n blockedAgents: LinearMcpAuthBlockedAgent[];\n}\n\ninterface LinearMcpAuthLifecycle {\n /** Durable correlation id: the sequence of the required event that opened\n * this lifecycle. `notified` events carry it so a delayed delivery record\n * can only be applied to the lifecycle whose wake attempt produced it. */\n id: string;\n declaredAt: string;\n authUrl: string | null;\n authUrlAgentId: string | null;\n authUrlExpiresAt: string | null;\n agents: Map<string, LinearMcpAuthBlockedAgent>;\n}\n\ninterface LinearMcpAuthFold {\n open: LinearMcpAuthLifecycle | null;\n lastCompleted: LinearMcpAuthLifecycle | null;\n}\n\nexport interface RequiredPayload {\n agentId: string;\n issueId: string | null;\n authUrl: string | null;\n expiresAt: string | null;\n}\n\nexport interface HealthyPayload {\n agentId: string;\n issueId: string | null;\n source: 'hook' | 'operator';\n}\n\nexport interface NotifiedPayload {\n agentId: string;\n issueId: string | null;\n outcome: LinearMcpAuthNotificationOutcome;\n /** Correlates this record to the lifecycle whose wake attempt produced it.\n * Absent only in events written before PAN-2997 review cycle 2. */\n lifecycleId?: string;\n}\n\nexport interface CallbackRelayedPayload {\n agentId: string;\n issueId: string | null;\n}\n\nexport type LinearMcpAuthEventInput =\n | { type: 'linear_mcp_auth.required'; timestamp?: string; payload: RequiredPayload }\n | { type: 'linear_mcp_auth.healthy'; timestamp?: string; payload: HealthyPayload }\n | { type: 'linear_mcp_auth.notified'; timestamp?: string; payload: NotifiedPayload }\n | { type: 'linear_mcp_auth.callback_relayed'; timestamp?: string; payload: CallbackRelayedPayload };\n\nconst LINEAR_MCP_AUTH_EVENT_TYPES = [\n 'linear_mcp_auth.required',\n 'linear_mcp_auth.healthy',\n 'linear_mcp_auth.notified',\n 'linear_mcp_auth.callback_relayed',\n] as const;\n\nfunction defaultExpiresAt(declaredAt: string): string {\n return new Date(Date.parse(declaredAt) + LINEAR_MCP_AUTH_URL_TTL_MS).toISOString();\n}\n\nfunction foldLinearMcpAuthEvents(\n events: StoredEvent[],\n initial: LinearMcpAuthFold = { open: null, lastCompleted: null },\n): LinearMcpAuthFold {\n let open = initial.open;\n let lastCompleted = initial.lastCompleted;\n\n for (const event of events) {\n switch (event.type) {\n case 'linear_mcp_auth.required': {\n const payload = event.payload as RequiredPayload;\n open ??= {\n id: `seq-${event.sequence}`,\n declaredAt: event.timestamp,\n authUrl: null,\n authUrlAgentId: null,\n authUrlExpiresAt: null,\n agents: new Map(),\n };\n const expiresAt = payload.expiresAt ?? defaultExpiresAt(event.timestamp);\n open.agents.set(payload.agentId, {\n agentId: payload.agentId,\n issueId: payload.issueId,\n declaredAt: event.timestamp,\n expiresAt,\n notifiedAt: null,\n });\n if (payload.authUrl !== null) {\n open.authUrl = payload.authUrl;\n open.authUrlAgentId = payload.agentId;\n open.authUrlExpiresAt = expiresAt;\n }\n break;\n }\n case 'linear_mcp_auth.healthy':\n if (open !== null) {\n lastCompleted = open;\n open = null;\n }\n break;\n case 'linear_mcp_auth.notified': {\n const payload = event.payload as NotifiedPayload;\n // A notification record belongs to exactly one lifecycle: the one\n // whose wake pass produced it. Without this correlation a delayed\n // delivery from lifecycle A could be stamped onto lifecycle B,\n // permanently suppressing B's wake.\n const lifecycle = payload.lifecycleId !== undefined\n ? (open?.id === payload.lifecycleId ? open\n : lastCompleted?.id === payload.lifecycleId ? lastCompleted\n : null)\n : (open ?? lastCompleted);\n const agent = lifecycle?.agents.get(payload.agentId);\n // 'delivering' rows from earlier iterations are not completions and\n // are ignored: interrupted deliveries are resumed through the keyed\n // outbox (see deliverWakeWithOutbox), not the event log.\n if (agent !== undefined && payload.outcome !== 'delivering') {\n lifecycle?.agents.set(payload.agentId, {\n ...agent,\n notifiedAt: event.timestamp,\n });\n }\n break;\n }\n case 'linear_mcp_auth.callback_relayed':\n break;\n }\n }\n\n return { open, lastCompleted };\n}\n\ninterface LinearMcpAuthProjectionCache {\n fold: LinearMcpAuthFold;\n coveredSequence: number;\n}\n\nlet projectionCache: LinearMcpAuthProjectionCache | null = null;\n\nexport function _resetLinearMcpAuthProjectionCacheForTests(): void {\n projectionCache = null;\n wakeFollowUpDelayMs = WAKE_FOLLOW_UP_MIN_DELAY_MS;\n}\n\n/**\n * Read the lifecycle fold from a maintained projection rather than rebuilding\n * it per poll. The projection advances incrementally: each read fetches only\n * auth-typed events newer than the covered sequence via an indexed SQL query\n * (`queryByTypesSince`), so unrelated retained history is never materialized\n * — a full `readFrom(0)` on the seven-day event table has blown the heap in\n * smoke testing, and the banner calls this path every 5–30 seconds per\n * connected client.\n */\nasync function readLinearMcpAuthFold(): Promise<LinearMcpAuthFold> {\n const store = await initEventStore();\n // The events table is a disposable cache — rebuilt, compacted, or purged\n // underneath us. If it now ends before the covered sequence, the cached\n // prefix is stale and the projection must restart from scratch.\n if (projectionCache !== null && store.getLatestSequence() < projectionCache.coveredSequence) {\n projectionCache = null;\n }\n const afterSequence = projectionCache?.coveredSequence ?? 0;\n const delta = store.queryByTypesSince([...LINEAR_MCP_AUTH_EVENT_TYPES], afterSequence);\n if (delta.length === 0 && projectionCache !== null) {\n return projectionCache.fold;\n }\n const fold = foldLinearMcpAuthEvents(delta, projectionCache?.fold);\n const lastEvent = delta[delta.length - 1];\n projectionCache = {\n fold,\n coveredSequence: lastEvent !== undefined ? lastEvent.sequence : afterSequence,\n };\n return fold;\n}\n\nfunction projectLifecycle(\n lifecycle: LinearMcpAuthLifecycle | null,\n nowIso: string,\n): LinearMcpAuthIntervention {\n if (lifecycle === null) {\n return {\n status: 'none',\n authUrl: null,\n authUrlAgentId: null,\n authUrlExpiresAt: null,\n declaredAt: null,\n blockedAgents: [],\n };\n }\n\n const status = lifecycle.authUrlExpiresAt !== null\n && lifecycle.authUrlExpiresAt < nowIso\n ? 'expired'\n : 'active';\n\n return {\n status,\n authUrl: lifecycle.authUrl,\n authUrlAgentId: lifecycle.authUrlAgentId,\n authUrlExpiresAt: lifecycle.authUrlExpiresAt,\n declaredAt: lifecycle.declaredAt,\n blockedAgents: [...lifecycle.agents.values()],\n };\n}\n\nexport function linearMcpAuthEvent(input: LinearMcpAuthEventInput): Omit<DomainEvent, 'sequence'> {\n return {\n type: input.type,\n timestamp: input.timestamp ?? new Date().toISOString(),\n payload: input.payload,\n } as Omit<DomainEvent, 'sequence'>;\n}\n\nexport async function appendLinearMcpAuthEvent(input: LinearMcpAuthEventInput): Promise<number> {\n const store = await initEventStore();\n return store.appendAsync(linearMcpAuthEvent(input));\n}\n\nexport function appendLinearMcpAuthRequiredEvent(\n payload: RequiredPayload,\n timestamp?: string,\n): Promise<number> {\n return appendLinearMcpAuthEvent({ type: 'linear_mcp_auth.required', payload, timestamp });\n}\n\nexport function appendLinearMcpAuthHealthyEvent(\n payload: HealthyPayload,\n timestamp?: string,\n): Promise<number> {\n return appendLinearMcpAuthEvent({ type: 'linear_mcp_auth.healthy', payload, timestamp });\n}\n\nexport function appendLinearMcpAuthNotifiedEvent(\n payload: NotifiedPayload,\n timestamp?: string,\n): Promise<number> {\n return appendLinearMcpAuthEvent({ type: 'linear_mcp_auth.notified', payload, timestamp });\n}\n\nexport function appendLinearMcpAuthCallbackRelayedEvent(\n payload: CallbackRelayedPayload,\n timestamp?: string,\n): Promise<number> {\n return appendLinearMcpAuthEvent({ type: 'linear_mcp_auth.callback_relayed', payload, timestamp });\n}\n\nexport async function resolveLinearMcpAuthIntervention(\n nowIso = new Date().toISOString(),\n): Promise<LinearMcpAuthIntervention> {\n const { open } = await readLinearMcpAuthFold();\n return projectLifecycle(open, nowIso);\n}\n\nexport interface LinearMcpAuthWakeSet {\n lifecycleId: string;\n agents: LinearMcpAuthBlockedAgent[];\n}\n\nexport async function computeLinearMcpAuthWakeSet(): Promise<LinearMcpAuthWakeSet | null> {\n const { lastCompleted } = await readLinearMcpAuthFold();\n if (lastCompleted === null) return null;\n const agents = [...lastCompleted.agents.values()].filter(agent => agent.notifiedAt === null);\n return { lifecycleId: lastCompleted.id, agents };\n}\n\nconst MAX_WAKE_PASSES = 10;\nconst WAKE_FOLLOW_UP_MIN_DELAY_MS = 1000;\nconst WAKE_FOLLOW_UP_MAX_DELAY_MS = 60_000;\n\nlet wakeFollowUpDelayMs = WAKE_FOLLOW_UP_MIN_DELAY_MS;\n\n// ── Keyed wake outbox ────────────────────────────────────────────────────────\n//\n// The durable delivery receipt for one (lifecycleId, agentId) wake. Unlike a\n// claim flag in the event log, the entry is lifecycle-keyed, carries the\n// intended message and the delivery state/outcome, and is updated by the\n// delivery wrapper itself on every path — so every acknowledged send has a\n// receipt, recovery resumes only unacknowledged entries, and no cross-\n// lifecycle content or timestamp matching is involved. Writes are temp-file +\n// rename (atomic on POSIX) and all I/O is async (NFR-1).\n\nexport interface LinearMcpWakeOutboxEntry {\n lifecycleId: string;\n agentId: string;\n message: string;\n state: 'pending' | 'acknowledged';\n outcome?: LinearMcpAuthNotificationOutcome;\n createdAt: string;\n acknowledgedAt?: string;\n}\n\nfunction wakeOutboxPath(agentId: string, lifecycleId: string): string {\n return join(getAgentDir(agentId), 'linear-mcp-wake', `${lifecycleId}.json`);\n}\n\nasync function readWakeOutbox(agentId: string, lifecycleId: string): Promise<LinearMcpWakeOutboxEntry | null> {\n try {\n const raw = await readFile(wakeOutboxPath(agentId, lifecycleId), 'utf-8');\n return JSON.parse(raw) as LinearMcpWakeOutboxEntry;\n } catch {\n return null;\n }\n}\n\nasync function writeWakeOutbox(entry: LinearMcpWakeOutboxEntry): Promise<void> {\n const path = wakeOutboxPath(entry.agentId, entry.lifecycleId);\n await mkdir(join(getAgentDir(entry.agentId), 'linear-mcp-wake'), { recursive: true });\n const tmpPath = `${path}.${process.pid}.tmp`;\n await writeFile(tmpPath, JSON.stringify(entry, null, 2), 'utf-8');\n await rename(tmpPath, path);\n}\n\ninterface WakeDelivery {\n /** True when this call actually invoked the delivery door. */\n sent: boolean;\n outcome: LinearMcpAuthNotificationOutcome;\n}\n\n/**\n * Deliver one lifecycle's wake through the keyed outbox. An acknowledged\n * entry suppresses the replay and replays only its recorded outcome — a\n * crash after the ack write never produces a second send, and the recorded\n * outcome stays faithful ('queued' is never upgraded to 'delivered'). A\n * pending or missing entry is (re)driven: a crash before the ack write\n * replays the send, which is correct because that send was never\n * acknowledged.\n */\nasync function deliverWakeWithOutbox(agentId: string, lifecycleId: string): Promise<WakeDelivery> {\n const existing = await readWakeOutbox(agentId, lifecycleId);\n if (existing?.state === 'acknowledged' && existing.outcome !== undefined) {\n return { sent: false, outcome: existing.outcome };\n }\n if (existing === null) {\n await writeWakeOutbox({\n lifecycleId,\n agentId,\n message: LINEAR_MCP_AUTH_WAKE_COPY,\n state: 'pending',\n createdAt: new Date().toISOString(),\n });\n }\n\n let outcome: LinearMcpAuthNotificationOutcome;\n try {\n // The key is threaded to the delivery door, where the crash-independent\n // component (supervisor key set / tmux session option) deduplicates the\n // side effect itself. A dashboard crash after a completed delivery can\n // still replay THIS call (the outbox ack may not have landed), but the\n // replayed delivery deduplicates at the door — the agent never sees the\n // same keyed wake twice.\n outcome = await messageAgentWithOutcome(\n agentId,\n LINEAR_MCP_AUTH_WAKE_COPY,\n 'linear-mcp-auth-wake',\n { dedupKey: `linear-mcp-auth-wake:${lifecycleId}` },\n );\n } catch (error) {\n // AMBIGUOUS keyed delivery (cycle 8): the supervisor may have completed\n // the injection but its answer never arrived. Do NOT record a terminal\n // 'failed' — leave the entry pending so the next wake pass (or boot\n // recovery) retries the SAME key at the SAME tier, where the supervisor's\n // in-flight reservation/delivered set deduplicates it. At-most-once is\n // preserved by the door; a terminal record would either lose a wake that\n // never landed or misreport one that did.\n //\n // DEAD TARGET (cycle 9): the tmux submit found the pane dead and sent NO\n // Enter, deliberately preserving the pending claim and leaving the key\n // non-terminal. Same handling: the entry stays pending so a later pass\n // re-drives the wake — by then messageAgent's zombie/stopped detection\n // resumes the agent and the keyed door of the new session delivers.\n //\n // BLOCKING MENU (PAN-3212): the harness swallowed the paste and Enter was\n // deliberately withheld. Keep the outbox pending so the next pass re-drives\n // the same key; the tmux recovery path proves payload presence and re-pastes\n // when the old pending claim contains no visible text.\n //\n // UNVERIFIED MARKERS (cycle 13): a safety-critical marker read failed\n // mid-repair/rollback, so the marker state is unproven and the poison\n // breadcrumb stays authoritative. Same handling: pending, retried later.\n if (\n error instanceof AmbiguousKeyedDeliveryError\n || error instanceof KeyedSubmitTargetDeadError\n || isKeyedSubmitBlockedMenuError(error)\n || error instanceof KeyedMarkerVerificationError\n ) {\n throw error;\n }\n outcome = 'failed';\n }\n await writeWakeOutbox({\n lifecycleId,\n agentId,\n message: LINEAR_MCP_AUTH_WAKE_COPY,\n state: 'acknowledged',\n outcome,\n createdAt: existing?.createdAt ?? new Date().toISOString(),\n acknowledgedAt: new Date().toISOString(),\n });\n return { sent: true, outcome };\n}\n\n/**\n * Record the completion of a delivery attempt in the lifecycle event log —\n * this, not the outbox receipt, is what removes the agent from the wake set.\n * If the append fails, the agent stays in the wake set but the acknowledged\n * outbox entry suppresses any replay: the next pass only retries this\n * completion record.\n */\nasync function recordWakeCompletion(\n blockedAgent: LinearMcpAuthBlockedAgent,\n lifecycleId: string,\n outcome: LinearMcpAuthNotificationOutcome,\n): Promise<void> {\n try {\n await appendLinearMcpAuthNotifiedEvent({\n agentId: blockedAgent.agentId,\n issueId: blockedAgent.issueId,\n outcome,\n lifecycleId,\n });\n } catch (completionError) {\n console.error(`[linear-mcp-auth] failed to record wake outcome for ${blockedAgent.agentId}:`, completionError);\n }\n}\n\nexport function processLinearMcpAuthWake(): Promise<void> {\n return linearMcpAuthWakeCoalescer.run('global', async () => {\n // Drain-until-stable: a healthy event that lands while this pass is\n // delivering wakes would otherwise be swallowed by the coalescer (its\n // trigger gets this in-flight promise back). Re-reading the fold after\n // each pass surfaces the newly completed lifecycle, so it gets its own\n // wake round inside the same coalesced run.\n let stabilized = false;\n for (let pass = 0; pass < MAX_WAKE_PASSES; pass++) {\n const wakeSet = await computeLinearMcpAuthWakeSet();\n if (wakeSet === null || wakeSet.agents.length === 0) {\n stabilized = true;\n wakeFollowUpDelayMs = WAKE_FOLLOW_UP_MIN_DELAY_MS;\n break;\n }\n for (const blockedAgent of wakeSet.agents) {\n try {\n const delivery = await deliverWakeWithOutbox(blockedAgent.agentId, wakeSet.lifecycleId);\n await recordWakeCompletion(blockedAgent, wakeSet.lifecycleId, delivery.outcome);\n } catch (deliveryError) {\n // The outbox itself is unreadable/unwritable — without a durable\n // receipt an interrupted attempt cannot be reconciled, so skip this\n // agent rather than risk an unreconciled replay.\n console.error(`[linear-mcp-auth] wake outbox failure for ${blockedAgent.agentId}; skipping this pass:`, deliveryError);\n }\n }\n }\n if (!stabilized) {\n // Every healthy trigger that arrived during this run received the same\n // coalesced promise, so no external retry is guaranteed. Schedule one\n // ourselves with bounded exponential backoff — a persistent outbox or\n // EventStore failure must not turn into a hot retry/log loop.\n console.error(`[linear-mcp-auth] wake pass did not stabilize after ${MAX_WAKE_PASSES} passes — scheduling a follow-up run in ${wakeFollowUpDelayMs}ms`);\n const timer = setTimeout(() => { void processLinearMcpAuthWake(); }, wakeFollowUpDelayMs);\n timer.unref();\n wakeFollowUpDelayMs = Math.min(wakeFollowUpDelayMs * 2, WAKE_FOLLOW_UP_MAX_DELAY_MS);\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { i as __require, t as __commonJSMin } from "./rolldown-runtime-DNCzzKnI.js";
|
|
2
2
|
import { On as resolveProjectFromIssueSync, S as init_defaults, c as init_load, fn as init_projects, p as loadConfigSync, ui as init_errors, x as DEFAULT_CONFIG } from "./config-yaml-Dce9VUd6.js";
|
|
3
|
-
import { t as getBuildInfo } from "./build-info-
|
|
4
|
-
import { t as getDashboardIdentity } from "./identity-
|
|
3
|
+
import { t as getBuildInfo } from "./build-info-D0EJNj6C.js";
|
|
4
|
+
import { t as getDashboardIdentity } from "./identity-Cot1Hg4G.js";
|
|
5
5
|
import { h as loadConfigSync$1, p as init_config } from "./config-BUjU7GZQ.js";
|
|
6
6
|
import { a as init_event_store, i as initEventStore } from "./event-store-BYOh686E.js";
|
|
7
7
|
import { O as projectLegacySystemHealthSummary, t as init_src } from "./src-tnFSfiXX.js";
|
|
8
|
-
import { $n as createHostHealthCollector,
|
|
8
|
+
import { $n as createHostHealthCollector, Ea as init_agent_runtime_mirror, Fn as init_queries, G as init_runtime_state, Kn as descendantPidsForSession, Ln as listRunningAgents, Ta as getRuntimeSnapshot, U as getAgentRuntimeState, Yn as init_runtime_census, _ as init_agent_state, br as init_memory_verdict_cache, er as init_collector, ir as init_darwin, jn as init_termination, nr as init_linux, q as init_agents, qn as getRuntimeCensus, rr as DARWIN_MEMORY_PRESSURE_DEFAULTS, tr as LINUX_MEMORY_PRESSURE_DEFAULTS, xr as setCachedMemoryVerdict } from "./agent-state-B196fj1h.js";
|
|
9
9
|
import { f as DockerStatsCollector, p as init_docker_stats } from "./stack-health-Y8O3UuPe.js";
|
|
10
10
|
import { i as readReviewStatusMap, n as init_review_status_source, t as classifyAdvancingSessionLifecycle } from "./review-status-source-Jq0BXmn3.js";
|
|
11
11
|
import { r as init_config$1 } from "./config-B-MLcKSo.js";
|
|
12
|
-
import { d as getReviewStatusSync, p as init_review_status } from "./review-status-
|
|
12
|
+
import { d as getReviewStatusSync, p as init_review_status } from "./review-status-yY5qt7Rl.js";
|
|
13
13
|
import { n as __filename, r as init_esm_shims } from "./esm-shims-CVzRn1Sa.js";
|
|
14
14
|
import { r as computeBuildStaleness } from "./staleness-CyM7ACHv.js";
|
|
15
15
|
import { createRequire } from "node:module";
|
|
@@ -23180,4 +23180,4 @@ async function assessMemoryPressure() {
|
|
|
23180
23180
|
//#endregion
|
|
23181
23181
|
export { classifyAgentHealth as _, readGovernorWatchReserveBytes as a, getContainerHistory as c, getDeployStaleness as d, getResourceConfig as f, startSmeeProcessSync as g, isSmeeConfiguredSync as h, readGovernorReserves as i, getCurrentDockerStats as l, getSystemHealthSnapshot as m, classifyMemoryPressure as n, getResourceStacks as o, getSystemHealthEvidenceSnapshot as p, readGovernorPsiCalmConfig as r, formatUptime as s, assessMemoryPressure as t, getAcceptedSystemHealthSnapshot as u };
|
|
23182
23182
|
|
|
23183
|
-
//# sourceMappingURL=memory-governor-
|
|
23183
|
+
//# sourceMappingURL=memory-governor-Bv3LAqk3.js.map
|