@overdeck/core 0.45.10 → 0.45.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-state-KVbJEG5f.js → agent-state-RLhS7ckO.js} +1 -1
- package/dist/{agents-pp8FO7Uj.js → agents-By3u_fQ8.js} +1 -1
- package/dist/{agents-CQHK0CSA.js → agents-CgVr77Qq.js} +12 -12
- package/dist/{agents-CQHK0CSA.js.map → agents-CgVr77Qq.js.map} +1 -1
- package/dist/{backlog-auto-trigger-CyuoS0nl.js → backlog-auto-trigger-B_-1tk4e.js} +5 -5
- package/dist/{backlog-auto-trigger-CyuoS0nl.js.map → backlog-auto-trigger-B_-1tk4e.js.map} +1 -1
- package/dist/cli/index.js +45 -45
- package/dist/{cli-Cewlu3yU.js → cli-DGeSW85B.js} +2 -2
- package/dist/{cli-Cewlu3yU.js.map → cli-DGeSW85B.js.map} +1 -1
- package/dist/cli-MTB9IOoR.js +3 -0
- package/dist/{close-issue-BJGX9_z7.js → close-issue-Djajb6Sv.js} +2 -2
- package/dist/{close-issue-BJGX9_z7.js.map → close-issue-Djajb6Sv.js.map} +1 -1
- package/dist/{concurrency-Ca-fC7tA.js → concurrency-4rzjHAQA.js} +1 -1
- package/dist/{conversation-compaction-B_6s9EvM.js → conversation-compaction-BQvZh09I.js} +4 -4
- package/dist/{conversation-compaction-B_6s9EvM.js.map → conversation-compaction-BQvZh09I.js.map} +1 -1
- package/dist/dashboard/{advancing-selfheal-CMlGznKv.js → advancing-selfheal-BezNfM3P.js} +3 -3
- package/dist/dashboard/{advancing-selfheal-CMlGznKv.js.map → advancing-selfheal-BezNfM3P.js.map} +1 -1
- package/dist/dashboard/{agent-state-xk_G8t90.js → agent-state-BUANcaA7.js} +1 -1
- package/dist/dashboard/{agent-state-service-C1iUF9Ft.js → agent-state-service-DmFpIMbP.js} +1 -1
- package/dist/dashboard/{agent-state-service-5ge9xxhx.js → agent-state-service-tXUdp6UF.js} +2 -2
- package/dist/dashboard/{agent-state-service-5ge9xxhx.js.map → agent-state-service-tXUdp6UF.js.map} +1 -1
- package/dist/dashboard/{agents-BIggLvi_.js → agents-C-vqmKDi.js} +1 -1
- package/dist/dashboard/{agents-DHvsjsuP.js → agents-C7V4odQ6.js} +11 -11
- package/dist/dashboard/{agents-DHvsjsuP.js.map → agents-C7V4odQ6.js.map} +1 -1
- package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js → backlog-auto-trigger-Cb8PR-5w.js} +2 -2
- package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js.map → backlog-auto-trigger-Cb8PR-5w.js.map} +1 -1
- package/dist/dashboard/{cli-CvK9xuSS.js → cli-BgzbB6h3.js} +2 -2
- package/dist/dashboard/{cli-CvK9xuSS.js.map → cli-BgzbB6h3.js.map} +1 -1
- package/dist/dashboard/cli-DCqy_wFa.js +3 -0
- package/dist/dashboard/{close-issue-BnpErnD1.js → close-issue-BWf3pxn4.js} +2 -2
- package/dist/dashboard/{close-issue-BnpErnD1.js.map → close-issue-BWf3pxn4.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js → closed-issue-reaper-D7XPq5pY.js} +4 -4
- package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js.map → closed-issue-reaper-D7XPq5pY.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-BLCQwTpF.js → closed-issue-reaper-VSTSTROK.js} +1 -1
- package/dist/dashboard/{conflict-gate-JdFuhKuF.js → conflict-gate-DwOslTij.js} +1 -1
- package/dist/dashboard/{conflict-gate-BhKvrPxn.js → conflict-gate-FFEvTDVA.js} +3 -3
- package/dist/dashboard/{conflict-gate-BhKvrPxn.js.map → conflict-gate-FFEvTDVA.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-C5o_eKEm.js → conversation-compaction-CCqN4hWk.js} +4 -4
- package/dist/dashboard/{conversation-compaction-C5o_eKEm.js.map → conversation-compaction-CCqN4hWk.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-Ba5aZdrb.js → conversation-compaction-CL5Ti4j4.js} +1 -1
- package/dist/dashboard/{deacon-j3qSZ7--.js → deacon-CGHl90eC.js} +107 -50
- package/dist/dashboard/{deacon-j3qSZ7--.js.map → deacon-CGHl90eC.js.map} +1 -1
- package/dist/dashboard/{deacon-Cv2RMfHs.js → deacon-DMPh40Im.js} +2 -2
- package/dist/dashboard/deacon-swarm-CVTiNL0N.js +2 -0
- package/dist/dashboard/{deacon-swarm-DadWNi5J.js → deacon-swarm-aMMJTq6t.js} +4 -4
- package/dist/dashboard/{deacon-swarm-DadWNi5J.js.map → deacon-swarm-aMMJTq6t.js.map} +1 -1
- package/dist/dashboard/deacon.js +4 -4
- package/dist/dashboard/{feedback-target-oE4-Esxl.js → feedback-target-dJjFfITP.js} +4 -4
- package/dist/dashboard/{feedback-target-oE4-Esxl.js.map → feedback-target-dJjFfITP.js.map} +1 -1
- package/dist/dashboard/{feedback-target-C4DaDSZ8.js → feedback-target-nQSM6ygL.js} +1 -1
- package/dist/dashboard/{flywheel-ClMjGW_u.js → flywheel-BLoVNtJV.js} +6 -6
- package/dist/dashboard/{flywheel-ClMjGW_u.js.map → flywheel-BLoVNtJV.js.map} +1 -1
- package/dist/dashboard/{flywheel-DefHRzY4.js → flywheel-Cqf8vu7N.js} +6 -6
- package/dist/dashboard/{flywheel-DefHRzY4.js.map → flywheel-Cqf8vu7N.js.map} +1 -1
- package/dist/dashboard/{flywheel-BqDdGvrQ.js → flywheel-PJsbrXNY.js} +1 -1
- package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js → flywheel-merge-order-B7BH8mZ3.js} +2 -2
- package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js.map → flywheel-merge-order-B7BH8mZ3.js.map} +1 -1
- package/dist/dashboard/{flywheel-merge-order-B6YDSElh.js → flywheel-merge-order-CCwF60b-.js} +1 -1
- package/dist/dashboard/{harness-resolve-DKeSVsJL.js → harness-resolve-B3f9G1Kp.js} +2 -2
- package/dist/dashboard/{harness-resolve-DKeSVsJL.js.map → harness-resolve-B3f9G1Kp.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-BKKc0XnB.js → inspect-agent-CKF5-SJr.js} +3 -3
- package/dist/dashboard/{inspect-agent-BKKc0XnB.js.map → inspect-agent-CKF5-SJr.js.map} +1 -1
- package/dist/dashboard/inspect-agent-CqpEWGWy.js +2 -0
- package/dist/dashboard/{issue-lifecycle-DnhQgddB.js → issue-lifecycle-5LbOcATP.js} +7 -7
- package/dist/dashboard/{issue-lifecycle-DnhQgddB.js.map → issue-lifecycle-5LbOcATP.js.map} +1 -1
- package/dist/dashboard/{issue-lifecycle-nY4esZn8.js → issue-lifecycle-CjYqQ669.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-Bnqv1uaa.js → issue-service-singleton-B8iu_TKV.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-_AquSBTl.js → issue-service-singleton-CDGt__oQ.js} +2 -2
- package/dist/dashboard/{issue-service-singleton-_AquSBTl.js.map → issue-service-singleton-CDGt__oQ.js.map} +1 -1
- package/dist/dashboard/{lifecycle-CGKm7doE.js → lifecycle-CEg4sHZH.js} +4 -4
- package/dist/dashboard/{lifecycle-CGKm7doE.js.map → lifecycle-CEg4sHZH.js.map} +1 -1
- package/dist/dashboard/{merge-agent-Dh8Y9PDO.js → merge-agent-7nBiCytp.js} +1 -1
- package/dist/dashboard/{merge-agent-BcgAYStl.js → merge-agent-C81xPIGd.js} +6 -6
- package/dist/dashboard/{merge-agent-BcgAYStl.js.map → merge-agent-C81xPIGd.js.map} +1 -1
- package/dist/dashboard/merge-ops-DE_6CnmZ.js +2 -0
- package/dist/dashboard/{merge-train-DmxlYbEo.js → merge-train-D3qzRccL.js} +2 -2
- package/dist/dashboard/{merge-train-DmxlYbEo.js.map → merge-train-D3qzRccL.js.map} +1 -1
- package/dist/dashboard/{merge-train-deps-DBHsOWt9.js → merge-train-deps-Be5ElC_P.js} +2 -2
- package/dist/dashboard/{merge-train-deps-DBHsOWt9.js.map → merge-train-deps-Be5ElC_P.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js → orphan-proposed-reconciler-Boq_XaRe.js} +3 -3
- package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js.map → orphan-proposed-reconciler-Boq_XaRe.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-mXV--tcB.js → orphan-proposed-reconciler-C0sPnDq_.js} +1 -1
- package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js → reconstruct-cache-C2pCltOq.js} +3 -3
- package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js.map → reconstruct-cache-C2pCltOq.js.map} +1 -1
- package/dist/dashboard/{remote-completion-Bklh6loJ.js → remote-completion-BmxD5UMQ.js} +3 -3
- package/dist/dashboard/{remote-completion-Bklh6loJ.js.map → remote-completion-BmxD5UMQ.js.map} +1 -1
- package/dist/dashboard/resume-C-nxu9Y4.js +3 -0
- package/dist/dashboard/{review-agent-phzqNqIs.js → review-agent-D6P6d3pz.js} +26 -17
- package/dist/dashboard/review-agent-D6P6d3pz.js.map +1 -0
- package/dist/dashboard/review-agent-DTINEffB.js +3 -0
- package/dist/dashboard/{review-context-CCq56O_Y.js → review-context-Dyo0tsBa.js} +2 -2
- package/dist/dashboard/{review-context-CCq56O_Y.js.map → review-context-Dyo0tsBa.js.map} +1 -1
- package/dist/dashboard/{review-status-BQjqQXZG.js → review-status-B5ZASOo4.js} +18 -5
- package/dist/dashboard/review-status-B5ZASOo4.js.map +1 -0
- package/dist/dashboard/{review-status-bBkqUj5i.js → review-status-h7BXIA4z.js} +1 -1
- package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js → review-verdict-feedback-B70VbfRL.js} +4 -4
- package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js.map → review-verdict-feedback-B70VbfRL.js.map} +1 -1
- package/dist/dashboard/{review-verdict-feedback-cZh9Wi9G.js → review-verdict-feedback-CorgH49f.js} +1 -1
- package/dist/dashboard/{sequence-io-B2GmilSI.js → sequence-io-6YP8fVpH.js} +2 -2
- package/dist/dashboard/{sequence-io-B2GmilSI.js.map → sequence-io-6YP8fVpH.js.map} +1 -1
- package/dist/dashboard/{sequencer-agent-D8pW2aU_.js → sequencer-agent-ja-nkEc1.js} +5 -5
- package/dist/dashboard/{sequencer-agent-D8pW2aU_.js.map → sequencer-agent-ja-nkEc1.js.map} +1 -1
- package/dist/dashboard/server.js +66 -55
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{service-CILd-uOt.js → service-HKDrIy0h.js} +24 -24
- package/dist/dashboard/{service-CILd-uOt.js.map → service-HKDrIy0h.js.map} +1 -1
- package/dist/dashboard/{smart-compaction-Bgp_gwcA.js → smart-compaction-L0LSnF0_.js} +1 -1
- package/dist/dashboard/{smart-compaction-CrP90OFE.js → smart-compaction-uke1npUz.js} +2 -2
- package/dist/dashboard/{smart-compaction-CrP90OFE.js.map → smart-compaction-uke1npUz.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-D15KbPOy.js → spawn-planning-session-CSMWco8B.js} +1 -1
- package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js → spawn-planning-session-CXc0NkwV.js} +3 -3
- package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js.map → spawn-planning-session-CXc0NkwV.js.map} +1 -1
- package/dist/dashboard/{specialist-context-CwKkcXL9.js → specialist-context-BGCJ24LH.js} +3 -3
- package/dist/dashboard/{specialist-context-CwKkcXL9.js.map → specialist-context-BGCJ24LH.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-CvDxquV_.js → specialist-logs--Uz-r8ia.js} +2 -2
- package/dist/dashboard/{specialist-logs-CvDxquV_.js.map → specialist-logs--Uz-r8ia.js.map} +1 -1
- package/dist/dashboard/{specialists-BCjcG4uE.js → specialists-Ca9XSBCS.js} +1 -1
- package/dist/dashboard/{specialists-D07ybnam.js → specialists-DEfrAbWs.js} +36 -36
- package/dist/dashboard/{specialists-D07ybnam.js.map → specialists-DEfrAbWs.js.map} +1 -1
- package/dist/dashboard/{specialists-F2uMO4QS.js → specialists-DeZx9xom.js} +1 -1
- package/dist/dashboard/{specialists-BYi0k8CZ.js → specialists-nZ0mRwpZ.js} +12 -12
- package/dist/dashboard/{specialists-BYi0k8CZ.js.map → specialists-nZ0mRwpZ.js.map} +1 -1
- package/dist/dashboard/{summary-fork-DedqguZb.js → summary-fork-2a1L2DIU.js} +2 -2
- package/dist/dashboard/{summary-fork-EjvOkEPl.js → summary-fork-Xn0QUNmb.js} +3 -3
- package/dist/dashboard/{summary-fork-EjvOkEPl.js.map → summary-fork-Xn0QUNmb.js.map} +1 -1
- package/dist/dashboard/{teardown-workspace-5LCVbjfq.js → teardown-workspace-DhA6oAIK.js} +2 -2
- package/dist/dashboard/{teardown-workspace-5LCVbjfq.js.map → teardown-workspace-DhA6oAIK.js.map} +1 -1
- package/dist/dashboard/{test-agent-queue-qfsq6uaC.js → test-agent-queue-B_ukPyIK.js} +3 -3
- package/dist/dashboard/{test-agent-queue-qfsq6uaC.js.map → test-agent-queue-B_ukPyIK.js.map} +1 -1
- package/dist/dashboard/{uat-train-CtNGgXxj.js → uat-train-BLBAbJ8Y.js} +10 -10
- package/dist/dashboard/{uat-train-CtNGgXxj.js.map → uat-train-BLBAbJ8Y.js.map} +1 -1
- package/dist/dashboard/{uat-train-C3hWXtip.js → uat-train-DPfGR9_I.js} +1 -1
- package/dist/dashboard/{verification-runner-DuZRHjPU.js → verification-runner-KTGTNugM.js} +1 -1
- package/dist/dashboard/{verification-runner-BPAhPWR7.js → verification-runner-R2-4XgMO.js} +4 -4
- package/dist/dashboard/{verification-runner-BPAhPWR7.js.map → verification-runner-R2-4XgMO.js.map} +1 -1
- package/dist/dashboard/{workflows-H-COJ84X.js → workflows-CSKX5MhC.js} +7 -7
- package/dist/dashboard/{workflows-H-COJ84X.js.map → workflows-CSKX5MhC.js.map} +1 -1
- package/dist/dashboard/workflows-DVwXauef.js +2 -0
- package/dist/dashboard/{workspaces-CRU9_vRn.js → workspaces-DXLDgXo2.js} +32 -32
- package/dist/dashboard/{workspaces-CRU9_vRn.js.map → workspaces-DXLDgXo2.js.map} +1 -1
- package/dist/docs-index.sqlite +0 -0
- package/dist/{feedback-target-DTGAhFkM.js → feedback-target-BsKN4Tr_.js} +4 -4
- package/dist/{feedback-target-DTGAhFkM.js.map → feedback-target-BsKN4Tr_.js.map} +1 -1
- package/dist/{feedback-target-DutF088V.js → feedback-target-DM1WzOKy.js} +1 -1
- package/dist/{harness-resolve-Bz1prk6B.js → harness-resolve-B-YDNwNW.js} +2 -2
- package/dist/{harness-resolve-Bz1prk6B.js.map → harness-resolve-B-YDNwNW.js.map} +1 -1
- package/dist/{issue-lifecycle-fVqvCdKM.js → issue-lifecycle-Bc1l3U9f.js} +7 -7
- package/dist/{issue-lifecycle-fVqvCdKM.js.map → issue-lifecycle-Bc1l3U9f.js.map} +1 -1
- package/dist/{issue-service-singleton-CZm8p6Rj.js → issue-service-singleton-D-8Q6tdT.js} +1 -1
- package/dist/{issue-service-singleton-Cr73JAaR.js → issue-service-singleton-D1433I7n.js} +2 -2
- package/dist/{issue-service-singleton-Cr73JAaR.js.map → issue-service-singleton-D1433I7n.js.map} +1 -1
- package/dist/{lifecycle-CR6tZfl1.js → lifecycle-SmrqysDf.js} +7 -7
- package/dist/{lifecycle-CR6tZfl1.js.map → lifecycle-SmrqysDf.js.map} +1 -1
- package/dist/{lifecycle-D_EWiUjk.js → lifecycle-mRMyiIXs.js} +2 -2
- package/dist/{merge-agent-LM4avOtA.js → merge-agent-B9hIZ6Z0.js} +6 -6
- package/dist/{merge-agent-LM4avOtA.js.map → merge-agent-B9hIZ6Z0.js.map} +1 -1
- package/dist/{merge-agent-Bj046HE1.js → merge-agent-DUj22cXL.js} +1 -1
- package/dist/{merge-fulfillment-DrnC1Kmg.js → merge-fulfillment-__P4WE-T.js} +3 -3
- package/dist/{merge-fulfillment-DrnC1Kmg.js.map → merge-fulfillment-__P4WE-T.js.map} +1 -1
- package/dist/{reconstruct-cache-NPqoAUg-.js → reconstruct-cache-9SWp82SQ.js} +3 -3
- package/dist/{reconstruct-cache-NPqoAUg-.js.map → reconstruct-cache-9SWp82SQ.js.map} +1 -1
- package/dist/{records-BIkN1LQ4.js → records-CTmvuRzz.js} +14 -1
- package/dist/{records-BIkN1LQ4.js.map → records-CTmvuRzz.js.map} +1 -1
- package/dist/records-backfill-BQcmTmwF.js +2 -0
- package/dist/{records-backfill-HM0Pln6N.js → records-backfill-BrNsv_zT.js} +2 -2
- package/dist/{records-backfill-HM0Pln6N.js.map → records-backfill-BrNsv_zT.js.map} +1 -1
- package/dist/resume-Bn4kDk9x.js +3 -0
- package/dist/{review-agent-DFosQzCG.js → review-agent-PhqOhpx3.js} +24 -16
- package/dist/{review-agent-DFosQzCG.js.map → review-agent-PhqOhpx3.js.map} +1 -1
- package/dist/review-agent-hLbRRxZF.js +3 -0
- package/dist/{review-status-iJwfEoNX.js → review-status-B_XXomV0.js} +6 -6
- package/dist/{review-status-iJwfEoNX.js.map → review-status-B_XXomV0.js.map} +1 -1
- package/dist/{review-status-BbNVlXh1.js → review-status-Bo-EFVKh.js} +1 -1
- package/dist/{review-verdict-feedback-BGPw3z1X.js → review-verdict-feedback-CXHkoiKZ.js} +1 -1
- package/dist/{review-verdict-feedback-CCZ05atV.js → review-verdict-feedback-DGV13LDb.js} +4 -4
- package/dist/{review-verdict-feedback-CCZ05atV.js.map → review-verdict-feedback-DGV13LDb.js.map} +1 -1
- package/dist/{sequence-io-CcxZNHcD.js → sequence-io-Cg4NHAco.js} +1 -1
- package/dist/{sequence-io-Cx1usxG3.js → sequence-io-CyxvNHfA.js} +2 -2
- package/dist/{sequence-io-Cx1usxG3.js.map → sequence-io-CyxvNHfA.js.map} +1 -1
- package/dist/{smart-compaction-ByUEgQkF.js → smart-compaction-ByKXTQNy.js} +2 -2
- package/dist/{smart-compaction-ByUEgQkF.js.map → smart-compaction-ByKXTQNy.js.map} +1 -1
- package/dist/{smart-compaction-DfLe_CtH.js → smart-compaction-U6e_uB1E.js} +1 -1
- package/dist/{specialist-context-McuGkaeR.js → specialist-context-BUTadO-U.js} +3 -3
- package/dist/{specialist-context-McuGkaeR.js.map → specialist-context-BUTadO-U.js.map} +1 -1
- package/dist/{specialist-logs-CUUJ4eiB.js → specialist-logs-pEMCTtGZ.js} +2 -2
- package/dist/{specialist-logs-CUUJ4eiB.js.map → specialist-logs-pEMCTtGZ.js.map} +1 -1
- package/dist/{specialists-BSrylD1S.js → specialists-BGFnrQ8n.js} +1 -1
- package/dist/{specialists-B0elgInc.js → specialists-DeTkkp8b.js} +12 -12
- package/dist/{specialists-B0elgInc.js.map → specialists-DeTkkp8b.js.map} +1 -1
- package/dist/{summary-fork-DskMRnyb.js → summary-fork-CgzThE2-.js} +2 -2
- package/dist/{summary-fork-CPUTKcAT.js → summary-fork-Dx1RMuvU.js} +3 -3
- package/dist/{summary-fork-CPUTKcAT.js.map → summary-fork-Dx1RMuvU.js.map} +1 -1
- package/dist/{sync-BiNVQHBf.js → sync-Bsww9tuj.js} +2 -2
- package/dist/{sync-BiNVQHBf.js.map → sync-Bsww9tuj.js.map} +1 -1
- package/dist/sync-CylE_SjB.js +2 -0
- package/dist/{verdict-restore-DQiV7_fe.js → verdict-restore-DGpX0ZOJ.js} +5 -5
- package/dist/{verdict-restore-DQiV7_fe.js.map → verdict-restore-DGpX0ZOJ.js.map} +1 -1
- package/dist/{webhook-handlers-DHBSEDEm.js → webhook-handlers-BjsfIL7u.js} +3 -3
- package/dist/{webhook-handlers-DHBSEDEm.js.map → webhook-handlers-BjsfIL7u.js.map} +1 -1
- package/dist/{workspace-migrate-duUsWNpd.js → workspace-migrate-BBQ8nAnP.js} +2 -2
- package/dist/{workspace-migrate-duUsWNpd.js.map → workspace-migrate-BBQ8nAnP.js.map} +1 -1
- package/dist/{workspace-reap-DLCIoJmn.js → workspace-reap-12O9354k.js} +2 -2
- package/dist/{workspace-reap-DLCIoJmn.js.map → workspace-reap-12O9354k.js.map} +1 -1
- package/package.json +1 -1
- package/scripts/file-size-baseline.txt +1 -1
- package/dist/cli-DBWG12XK.js +0 -3
- package/dist/dashboard/cli-CJDWXE_Y.js +0 -3
- package/dist/dashboard/deacon-swarm-D1iAUX-P.js +0 -2
- package/dist/dashboard/inspect-agent-vm88mLYr.js +0 -2
- package/dist/dashboard/merge-ops-QdBiQ8XB.js +0 -2
- package/dist/dashboard/resume-DnIsmIgC.js +0 -3
- package/dist/dashboard/review-agent-DgySDb-G.js +0 -3
- package/dist/dashboard/review-agent-phzqNqIs.js.map +0 -1
- package/dist/dashboard/review-status-BQjqQXZG.js.map +0 -1
- package/dist/dashboard/workflows-Cz8OtiB4.js +0 -2
- package/dist/records-backfill-DEgtRyKL.js +0 -2
- package/dist/resume-DGymuXZM.js +0 -3
- package/dist/review-agent-DjVadURa.js +0 -3
- package/dist/sync-4jqukkxz.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"summary-fork-EjvOkEPl.js","names":["join","serializeClaudeCodeConversation","parseClaudeCodeEntries","mkdir","join"],"sources":["../../src/lib/conversations/transcript-adapter.ts","../../src/lib/conversations/handoff-paths.ts","../../src/lib/conversations/summary-fork.ts"],"sourcesContent":["/**\n * Conversation transcript adapter.\n *\n * Each agent harness writes its session transcript in its own JSONL shape:\n *\n * - Claude Code records have top-level `type: 'user'|'assistant'|'system'`,\n * with `entry.message.content` as a block array whose blocks are\n * `text|tool_use|tool_result|thinking`.\n * - Pi records have top-level `type: 'message'|'session'|'model_change'|\n * 'thinking_level_change'|...`, with the user/assistant role nested in\n * `entry.message.role` and blocks of type `text|thinking|toolCall|toolResult`.\n * - Future harnesses (Codex, etc.) will have their own shapes.\n *\n * The handoff authoring pipeline doesn't care about any of that. It needs\n * one thing from each harness: a canonical \"<conversation>...</conversation>\"\n * text it can feed the authoring model.\n *\n * This module provides that abstraction. Adding a new harness is two short\n * functions (resolveSessionFile + serialize) plus a registry entry — the\n * fork pipeline never needs to learn about the new harness.\n */\nimport { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { Effect } from 'effect';\n\nimport type { LegacyConversation as Conversation } from '../overdeck/conversations.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { sessionFilePath } from '../paths.js';\nimport {\n parseEntries as parseClaudeCodeEntries,\n serializeConversation as serializeClaudeCodeConversation,\n generateSmartSummary,\n summarizeSerializedText,\n} from './smart-compaction.js';\n\nexport interface CompactSummaryOptions {\n /** Model for the summarizer LLM. */\n model?: string;\n /** Use the rich (Claude-native-style) summary prompt variant. */\n richMode?: boolean;\n /** Include thinking blocks in the serialized source. Default: true. */\n includeThinking?: boolean;\n /** Harness backend the summarizer LLM runs on. Default: 'claude-code'. */\n harness?: RuntimeName;\n /** Per-LLM-call timeout (ms). */\n timeoutMs?: number;\n}\n\nexport interface ConversationTranscriptAdapter {\n /** Display name for logging/errors. */\n readonly name: RuntimeName;\n\n /** Plain forks copy raw Claude JSONL and spawn Claude with `--resume` —\n * only Claude Code can be the source of a plain fork. */\n readonly supportsPlainForkAsSource: boolean;\n\n /** Source-authored handoff requires delivering a prompt to the live source\n * agent and waiting for it to write a sentinel file. Claude Code supports\n * this via deliverAgentMessage; Pi has no hook-equivalent signaling channel\n * for the .done sentinel (see PAN-1134), so source authoring is gated off\n * for Pi until that lands. */\n readonly supportsSourceAuthoredHandoff: boolean;\n\n /** Resolve the path to the source conversation's transcript file.\n * Returns null if the conversation has no transcript yet (e.g. session\n * just started and hasn't produced a JSONL line). */\n resolveSessionFile(conv: Conversation): Promise<string | null>;\n\n /** Serialize the transcript into a harness-agnostic canonical text that\n * the handoff prompt template can embed verbatim. Each turn is rendered\n * with a `[user]` / `[assistant]` header followed by text and tool-use\n * summaries; thinking blocks are optional (defaults to including them).\n */\n serializeTranscript(sessionFile: string, options?: { includeThinking?: boolean }): Promise<string>;\n\n /**\n * Produce a model-generated compact summary of the transcript, suitable for\n * seeding a summary fork or feeding the handoff authoring prompt.\n *\n * claude-code uses the entry-aware smart-compaction flow (rich, file-op\n * aware); other harnesses serialize the transcript and run a generic\n * chunk-and-merge summarizer over the text. The `harness` option selects the\n * summarizer LLM backend and is independent of which adapter (= source\n * harness) produced the transcript.\n */\n compactSummary(\n sessionFile: string,\n options?: CompactSummaryOptions,\n ): Promise<{ summary: string; summaryModel: string | null }>;\n}\n\n// ─── Claude Code ──────────────────────────────────────────────────────────\n\nconst claudeCodeAdapter: ConversationTranscriptAdapter = {\n name: 'claude-code',\n supportsPlainForkAsSource: true,\n supportsSourceAuthoredHandoff: true,\n\n async resolveSessionFile(conv) {\n if (!conv.claudeSessionId) return null;\n const path = sessionFilePath(conv.cwd, conv.claudeSessionId);\n return existsSync(path) ? path : null;\n },\n\n async serializeTranscript(sessionFile, options) {\n const entries = await parseClaudeCodeEntries(sessionFile);\n return serializeClaudeCodeConversation(entries, options?.includeThinking ?? true);\n },\n\n async compactSummary(sessionFile, options) {\n // Claude Code keeps the entry-aware smart-compaction flow: it parses the\n // JSONL into typed entries, finds compact boundaries, and carries file-op\n // detail into the summary. This is richer than text-only chunking.\n const result = await Effect.runPromise(\n generateSmartSummary({\n jsonlPath: sessionFile,\n model: options?.model,\n richMode: options?.richMode ?? false,\n mode: 'fork',\n includeThinkingInSummary: options?.includeThinking ?? true,\n harness: options?.harness ?? 'claude-code',\n }),\n );\n return { summary: result.summary, summaryModel: result.summaryModel };\n },\n};\n\n// ─── Pi ───────────────────────────────────────────────────────────────────\n\n/**\n * Pi sessions live at:\n * `~/.overdeck/agents/<tmuxSession>/sessions/<iso-timestamp>_<id>.jsonl`\n *\n * Pi may rotate session files (e.g. on resume), so we pick the\n * newest by filename — filenames sort lexicographically by their\n * ISO timestamp prefix so this is deterministic.\n */\nasync function resolvePiSessionFileFromTmux(tmuxSession: string): Promise<string | null> {\n const sessionDir = join(homedir(), '.overdeck', 'agents', tmuxSession, 'sessions');\n if (!existsSync(sessionDir)) return null;\n try {\n const entries = (await readdir(sessionDir)).filter((name) => name.endsWith('.jsonl'));\n if (entries.length === 0) return null;\n entries.sort();\n return join(sessionDir, entries[entries.length - 1]!);\n } catch {\n return null;\n }\n}\n\ninterface PiEntry {\n type?: string;\n message?: {\n role?: string;\n content?: Array<{\n type?: string;\n text?: string;\n thinking?: string;\n name?: string;\n arguments?: unknown;\n }>;\n };\n}\n\nfunction serializePiEntry(entry: PiEntry, includeThinking: boolean): string | undefined {\n if (entry.type !== 'message') return undefined;\n const role = entry.message?.role;\n if (role !== 'user' && role !== 'assistant') return undefined;\n\n const content = entry.message?.content;\n if (!Array.isArray(content)) return undefined;\n\n const lines: string[] = [`[${role}]`];\n for (const block of content) {\n if (block?.type === 'text' && typeof block.text === 'string' && block.text.trim()) {\n lines.push(block.text);\n } else if (block?.type === 'thinking' && includeThinking && typeof block.thinking === 'string' && block.thinking.trim()) {\n lines.push(`[thinking]\\n${block.thinking}`);\n } else if (block?.type === 'toolCall' && typeof block.name === 'string') {\n let args = '';\n try {\n args = JSON.stringify(block.arguments).slice(0, 500);\n } catch {\n args = '<unserializable>';\n }\n lines.push(`[tool_use: ${block.name}]\\n${args}`);\n }\n // toolResult, function-call envelopes, etc. are intentionally skipped — the\n // surface we care about for handoff is what the user said, what the agent\n // said, what tools it ran, and what it was thinking. Tool outputs balloon\n // the transcript without adding signal a handoff doc needs.\n }\n if (lines.length === 1) return undefined; // header only — no content\n return lines.join('\\n');\n}\n\nconst piAdapter: ConversationTranscriptAdapter = {\n name: 'ohmypi',\n supportsPlainForkAsSource: false,\n supportsSourceAuthoredHandoff: false,\n\n async resolveSessionFile(conv) {\n return resolvePiSessionFileFromTmux(conv.tmuxSession);\n },\n\n async serializeTranscript(sessionFile, options) {\n const includeThinking = options?.includeThinking ?? true;\n const content = await readFile(sessionFile, 'utf-8');\n const parts: string[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n let entry: PiEntry;\n try {\n entry = JSON.parse(line) as PiEntry;\n } catch {\n continue;\n }\n const serialized = serializePiEntry(entry, includeThinking);\n if (serialized) parts.push(serialized);\n }\n return parts.join('\\n\\n');\n },\n\n async compactSummary(sessionFile, options) {\n // Pi's JSONL shape is not what the Claude-Code entry parser understands, so\n // we serialize the transcript through this adapter first, then run the\n // generic text chunk-and-merge summarizer over it.\n const serialized = await piAdapter.serializeTranscript(sessionFile, {\n includeThinking: options?.includeThinking ?? true,\n });\n if (!serialized.trim()) {\n return { summary: '', summaryModel: null };\n }\n const summary = await summarizeSerializedText(serialized, {\n model: options?.model,\n richMode: options?.richMode ?? false,\n harness: options?.harness ?? 'claude-code',\n timeoutMs: options?.timeoutMs,\n });\n return { summary, summaryModel: options?.model ?? null };\n },\n};\n\n// ─── Registry ─────────────────────────────────────────────────────────────\n\nconst REGISTRY: Record<RuntimeName, ConversationTranscriptAdapter> = {\n 'claude-code': claudeCodeAdapter,\n 'ohmypi': piAdapter,\n 'codex': claudeCodeAdapter,\n};\n\n/**\n * Look up the transcript adapter for a harness. Unknown harnesses default to\n * the Claude Code adapter — that matches the conservative behavior of the rest\n * of the codebase (see getHarness() in @overdeck/contracts).\n */\nexport function getTranscriptAdapter(harness: RuntimeName | undefined): ConversationTranscriptAdapter {\n return REGISTRY[harness ?? 'claude-code'] ?? claudeCodeAdapter;\n}\n","import { mkdir } from 'fs/promises';\nimport { join } from 'path';\nimport { getHandoffsDir } from '../paths.js';\n\nconst SAFE_PATH_SEGMENT = /^[A-Za-z0-9._-]+$/;\n\nexport interface HandoffPaths {\n docPath: string;\n sentinelPath: string;\n}\n\nexport function createHandoffPaths(sourceConvId: string, isoTimestamp: string): HandoffPaths {\n const convId = sourceConvId.trim();\n const timestamp = isoTimestamp.trim();\n\n if (!SAFE_PATH_SEGMENT.test(convId)) {\n throw new Error(`Invalid handoff conversation id: ${sourceConvId}`);\n }\n if (timestamp.length === 0 || timestamp.includes('/') || timestamp.includes('\\\\')) {\n throw new Error(`Invalid handoff timestamp: ${isoTimestamp}`);\n }\n\n const docPath = join(getHandoffsDir(), `${convId}-${timestamp}.md`);\n return {\n docPath,\n sentinelPath: `${docPath}.done`,\n };\n}\n\nexport async function ensureHandoffsDir(): Promise<string> {\n const handoffsDir = getHandoffsDir();\n await mkdir(handoffsDir, { recursive: true });\n return handoffsDir;\n}\n","/**\n * Conversation fork pipeline.\n *\n * This module handles creating a new conversation from an existing one.\n * Two modes are supported:\n *\n * 1. Summary fork (default): The conversation history is serialized and sent\n * to an LLM summarizer (see smart-compaction.ts). The generated structured\n * summary is injected as the first user message in the new session.\n *\n * 2. Plain fork: Raw JSONL history is copied from the last compact_boundary\n * into a new session file. Thinking blocks are sanitized (converted to text)\n * to prevent signature validation errors on cross-model resumes.\n *\n * Entry point: createSummaryFork()\n * - Reserves a new session ID and file path\n * - Generates summary (LLM, heuristic fallback, or skips for plain fork)\n * - Creates a DB record for the new conversation\n * - Returns the new conversation + session metadata\n *\n * Dashboard API: runForkPipeline() in src/dashboard/server/routes/conversations.ts\n * wires the options through and handles tmux spawn + summary injection.\n */\nimport { randomUUID } from 'node:crypto';\nimport { access, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\n\nimport type { LegacyConversation as Conversation } from '../overdeck/conversations.js';\nimport {\n createConversation,\n getConversationByName,\n recordConversationHandoff,\n updateConversationForkFallbackReason,\n} from '../overdeck/conversations.js';\nimport { encodeClaudeProjectDir, packageRoot, sessionFilePath } from '../paths.js';\nimport { loadConfigSync } from '../config-yaml.js';\nimport { deliverAgentMessage } from '../agents.js';\nimport { runModelSummary } from './smart-compaction.js';\nimport { getTranscriptAdapter } from './transcript-adapter.js';\nimport { createHandoffPaths, ensureHandoffsDir, type HandoffPaths } from './handoff-paths.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { FsError } from '../errors.js';\nimport { getWorkspaceStackHealth } from '../workspace/stack-health.js';\n\nexport type SummaryForkMode = 'summary' | 'plain' | 'handoff';\nexport type HandoffAuthor = 'source' | 'external';\n\nexport interface SummaryForkOptions {\n model?: string;\n cwd?: string;\n harness?: RuntimeName;\n localSummaryOnly?: boolean;\n forkMode?: SummaryForkMode;\n focus?: string;\n handoffTimeoutMs?: number;\n handoffPollIntervalMs?: number;\n /** When true, include thinking block content in the serialized conversation sent to the summary model. Default: true. */\n includeThinkingInSummary?: boolean;\n /**\n * Who authors the handoff document when forkMode === 'handoff'.\n *\n * - 'source' (legacy): deliver the handoff prompt to the live source agent\n * via deliverAgentMessage and wait for it to write the doc + .done sentinel.\n * Requires the source conversation to be alive. Pollutes the source's\n * context. Uses whatever model/harness the source is running on.\n * - 'external' (default): spawn a separate authoring session with the chosen\n * model/harness that reads the source JSONL transcript and emits the doc.\n * Does not touch the source conversation. Works on ended sources.\n */\n handoffAuthor?: HandoffAuthor;\n /** Model for the external handoff authoring session. Ignored when handoffAuthor === 'source'. */\n handoffAuthorModel?: string;\n /** Harness for the external handoff authoring session. Ignored when handoffAuthor === 'source'. */\n handoffAuthorHarness?: RuntimeName;\n}\n\nexport interface SummaryForkResult {\n conversation: Conversation;\n sessionId: string;\n sessionFile: string;\n summary: string;\n summaryModel: string | null;\n forkMode: SummaryForkMode;\n handoffDocPath: string | null;\n forkFallbackReason: string | null;\n}\n\nconst FORK_WAIT_INSTRUCTION = `\\n---\\n\\n**Do not take any action.** This is context from a prior conversation fork. Acknowledge the summary and wait for the user's next instruction.`;\nconst DEFAULT_HANDOFF_TIMEOUT_MS = 300_000;\nconst DEFAULT_HANDOFF_POLL_INTERVAL_MS = 1_000;\nconst NO_HANDOFF_FOCUS = 'No specific focus was provided.';\n\nexport type HandoffDocValidation =\n | { ok: true }\n | { ok: false; reason: string };\n\nexport interface RequestHandoffOptions {\n timeoutMs?: number;\n pollIntervalMs?: number;\n now?: Date;\n}\n\nexport interface RequestHandoffResult {\n docPath: string;\n docText: string;\n}\n\nexport class HandoffStallError extends Error {\n constructor(\n public readonly docPath: string,\n public readonly sentinelPath: string,\n public readonly timeoutMs: number,\n ) {\n super(`Timed out waiting ${timeoutMs}ms for handoff document and sentinel: ${docPath}, ${sentinelPath}`);\n this.name = 'HandoffStallError';\n }\n}\n\nexport class HandoffValidationError extends Error {\n constructor(\n public readonly docPath: string,\n public readonly reason: string,\n ) {\n super(`Invalid handoff document ${docPath}: ${reason}`);\n this.name = 'HandoffValidationError';\n }\n}\n\n/**\n * Strip a wrapping ``` fenced code block from the doc body if the LLM\n * helpfully wrapped its Markdown output in a fence. Returns the inner\n * content if a fence was detected, otherwise returns the trimmed input.\n */\nexport function sanitizeHandoffDoc(text: string): string {\n const trimmed = text.trim();\n const fenceMatch = trimmed.match(/^```(?:markdown|md)?\\s*\\n([\\s\\S]*?)\\n```\\s*$/);\n return fenceMatch?.[1]?.trim() ?? trimmed;\n}\n\nexport function validateHandoffDoc(text: string): HandoffDocValidation {\n const sanitized = sanitizeHandoffDoc(text);\n if (sanitized.length < 200) {\n return { ok: false, reason: 'handoff document must be at least 200 characters' };\n }\n // Accept any heading depth (H1-H6), case-insensitive, with an optional\n // trailing colon. Real-world LLM outputs vary on heading conventions; the\n // failure mode of a too-strict validator is silent fallback to summary fork\n // with no surface to the user, which is the worst outcome.\n if (!/^#{1,6}\\s+suggested skills\\s*:?\\s*$/imu.test(sanitized)) {\n return { ok: false, reason: 'handoff document must contain a Suggested skills heading' };\n }\n return { ok: true };\n}\n\nfunction renderHandoffPrompt(template: string, focus: string | undefined, outputPath: string): string {\n const safeFocus = focus?.trim() || NO_HANDOFF_FOCUS;\n return template\n .split('{{focus}}').join(safeFocus)\n .split('{{outputPath}}').join(outputPath);\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function waitForHandoffDoc(paths: HandoffPaths, timeoutMs: number, pollIntervalMs: number): Promise<string> {\n const deadline = Date.now() + timeoutMs;\n\n while (true) {\n try {\n await access(paths.docPath);\n await access(paths.sentinelPath);\n return await readFile(paths.docPath, 'utf-8');\n } catch {\n const remainingMs = deadline - Date.now();\n if (remainingMs <= 0) {\n throw new HandoffStallError(paths.docPath, paths.sentinelPath, timeoutMs);\n }\n await delay(Math.min(pollIntervalMs, remainingMs));\n }\n }\n}\n\nconst DEFAULT_HANDOFF_AUTHOR_MODEL = 'claude-sonnet-4-6';\nconst HANDOFF_AUTHOR_TIMEOUT_MS = 300_000;\n// When the raw transcript exceeds this many characters, pre-compact it via\n// generateSmartSummary (which chunks internally) before sending to the\n// handoff authoring model. Threshold chosen to keep the final prompt\n// comfortably under Haiku's 200k-token window (~800k chars at 4 chars/token,\n// minus headroom for the template, focus, and a response budget). The exact\n// value isn't critical — anything that triggers compaction for \"long\"\n// transcripts is fine.\nconst HANDOFF_TRANSCRIPT_COMPACT_THRESHOLD = 100_000;\n\nfunction renderExternalHandoffPrompt(template: string, focus: string | undefined, transcript: string, outputPath: string): string {\n const safeFocus = focus?.trim() || NO_HANDOFF_FOCUS;\n return template\n .split('{{focus}}').join(safeFocus)\n .split('{{outputPath}}').join(outputPath)\n .split('{{transcript}}').join(transcript);\n}\n\n/**\n * Author a handoff document from an external authoring session.\n *\n * Reads the source JSONL transcript and asks a fresh, isolated model session\n * (with the chosen model + harness) to write the handoff Markdown. The source\n * conversation is never contacted — its context is not polluted.\n *\n * Returns the same { docPath, docText } shape as requestHandoffFromAgent so\n * callers can substitute one for the other.\n */\nexport async function authorHandoffExternal(\n sourceConv: Conversation,\n sourceSessionFile: string,\n focus: string | undefined,\n model: string | undefined,\n harness: RuntimeName | undefined,\n options: { now?: Date } = {},\n): Promise<RequestHandoffResult> {\n await ensureHandoffsDir();\n const timestamp = (options.now ?? new Date()).toISOString();\n const paths = createHandoffPaths(sourceConv.name, timestamp);\n\n const effectiveModel = model ?? DEFAULT_HANDOFF_AUTHOR_MODEL;\n const effectiveHarness: RuntimeName = harness ?? 'claude-code';\n\n // The authoring harness decides the prompt template: Claude Code's `Write`\n // tool vs Pi's lowercase `write` tool, with harness-specific phrasing about\n // how the model should call it. Both templates share the same H2 contract,\n // and the file-on-disk read below is harness-independent.\n const templateName = effectiveHarness === 'ohmypi' ? 'handoff-external-pi.md' : 'handoff-external.md';\n const template = await readFile(join(packageRoot, 'roles', templateName), 'utf-8');\n\n // The source's harness decides how the transcript is read/serialized; the\n // authoring harness (model + harness picked by the user) is independent.\n const sourceAdapter = getTranscriptAdapter(sourceConv.harness ?? undefined);\n // Skip thinking blocks — they're large and the structured output we want\n // doesn't need internal reasoning, only the user/assistant exchange.\n const transcript = await sourceAdapter.serializeTranscript(sourceSessionFile, { includeThinking: false });\n\n // If the raw transcript is small enough, feed it to the model verbatim —\n // that's the richest input. For long transcripts we'd overflow any model's\n // context window in a single-pass prompt, so first compact via the chunked\n // generateSmartSummary path (which already handles chunking + merging) and\n // feed the compact summary to the handoff authoring step. The handoff\n // template treats {{transcript}} as opaque context — a compact summary\n // works as well as the raw transcript, just with less fine detail.\n let promptInput: string;\n let inputLabel: 'raw-transcript' | 'compact-summary';\n // Precompaction goes through the source adapter's compactSummary, which is\n // harness-agnostic: claude-code uses the entry-aware smart-compaction flow,\n // Pi (and future harnesses) serialize their transcript and run the generic\n // chunk-and-merge summarizer. Either way a long source transcript is\n // compacted before it reaches the authoring model's context window.\n if (transcript.length > HANDOFF_TRANSCRIPT_COMPACT_THRESHOLD) {\n console.log(`[claude-invoke] purpose=handoff-author-external | model=${effectiveModel} | harness=${effectiveHarness} | source=${sourceConv.name} | transcriptChars=${transcript.length} | precompacting=true`);\n const { config } = loadConfigSync();\n const richMode = config.conversations.richCompaction;\n const compact = await sourceAdapter.compactSummary(sourceSessionFile, {\n model: effectiveModel,\n richMode,\n includeThinking: false,\n harness: effectiveHarness,\n });\n promptInput = compact.summary;\n inputLabel = 'compact-summary';\n console.log(`[claude-invoke] purpose=handoff-author-external precompact-result | model=${effectiveModel} | compactChars=${compact.summary.length}`);\n } else {\n promptInput = transcript;\n inputLabel = 'raw-transcript';\n }\n\n const prompt = renderExternalHandoffPrompt(template, focus, promptInput, paths.docPath);\n console.log(`[claude-invoke] purpose=handoff-author-external | model=${effectiveModel} | harness=${effectiveHarness} | source=${sourceConv.name} | inputType=${inputLabel} | promptChars=${prompt.length} | outputPath=${paths.docPath}`);\n\n // The prompt tells the model to use its Write tool to author the document\n // directly at paths.docPath. We capture stdout only as a diagnostic log —\n // the source of truth for the doc is the file on disk. Writing via tool\n // avoids stdout preamble leaks like \"Here is the handoff document:\" that\n // contaminated earlier attempts.\n //\n // The authoring session is headless (`claude -p`), so the Write tool must be\n // explicitly allowlisted — otherwise `--permission-mode auto` raises a\n // permission prompt the session can never answer, the model emits \"I need\n // permission to write…\" to stdout, no file is written, and the whole fork\n // falls back to a plain summary with reason `handoff-validation` (PAN-1582).\n // Pi runs in rpc mode and auto-executes tools, so the allowlist is a no-op there.\n const stdout = await Effect.runPromise(runModelSummary(prompt, effectiveModel, HANDOFF_AUTHOR_TIMEOUT_MS, effectiveHarness, ['Write']));\n console.log(`[claude-invoke] purpose=handoff-author-external acknowledgement | model=${effectiveModel} | stdoutChars=${stdout.length} | stdoutPreview=${JSON.stringify(stdout.slice(0, 120))}`);\n\n let docText: string;\n try {\n docText = await readFile(paths.docPath, 'utf-8');\n } catch (err) {\n // The model didn't use Write — it almost certainly produced the doc on\n // stdout instead. Persist whatever we got for diagnosis, then surface\n // the failure as a validation error so the pipeline falls back to a\n // summary fork rather than spinning forever.\n const rejectedPath = `${paths.docPath}.rejected.md`;\n await writeFile(rejectedPath, stdout, 'utf-8').catch((wErr) => {\n console.warn(`[handoff-author-external] failed to persist stdout to ${rejectedPath}: ${wErr?.message ?? wErr}`);\n });\n console.warn(`[handoff-author-external] model did not call Write — stdout (${stdout.length} chars) saved to ${rejectedPath}: ${(err as { message?: string })?.message ?? err}`);\n throw new HandoffValidationError(paths.docPath, 'authoring session did not call its Write tool to create the handoff doc');\n }\n\n const validation = validateHandoffDoc(docText);\n if (!validation.ok) {\n // The file exists but its content failed the contract check. Persist\n // the rejected file for diagnosis and surface the reason.\n const rejectedPath = `${paths.docPath}.rejected.md`;\n await writeFile(rejectedPath, docText, 'utf-8').catch((err) => {\n console.warn(`[handoff-author-external] failed to persist rejected doc to ${rejectedPath}: ${err?.message ?? err}`);\n });\n console.warn(`[handoff-author-external] validation rejected file content (${validation.reason}); copy saved to ${rejectedPath}`);\n throw new HandoffValidationError(paths.docPath, validation.reason);\n }\n\n // Sanitize as a safety net in case the model wrapped the file in fences\n // despite the prompt telling it not to. Overwrite the file with the\n // cleaned text so downstream consumers see the same thing the validator\n // approved.\n const sanitized = sanitizeHandoffDoc(docText);\n if (sanitized !== docText) {\n await writeFile(paths.docPath, sanitized, 'utf-8');\n }\n await writeFile(paths.sentinelPath, '', 'utf-8');\n\n return { docPath: paths.docPath, docText: sanitized };\n}\n\nexport async function requestHandoffFromAgent(\n sourceConv: Conversation,\n focus?: string,\n options: RequestHandoffOptions = {},\n): Promise<RequestHandoffResult> {\n await ensureHandoffsDir();\n const timestamp = (options.now ?? new Date()).toISOString();\n const paths = createHandoffPaths(sourceConv.name, timestamp);\n const template = await readFile(join(packageRoot, 'roles', 'handoff.md'), 'utf-8');\n const prompt = renderHandoffPrompt(template, focus, paths.docPath);\n\n await deliverAgentMessage(sourceConv.tmuxSession, prompt, 'handoff-request');\n\n const docText = await waitForHandoffDoc(\n paths,\n options.timeoutMs ?? DEFAULT_HANDOFF_TIMEOUT_MS,\n options.pollIntervalMs ?? DEFAULT_HANDOFF_POLL_INTERVAL_MS,\n );\n const validation = validateHandoffDoc(docText);\n if (!validation.ok) {\n // The source agent already wrote the file to docPath. Move it aside to\n // .rejected.md so the next handoff attempt doesn't reuse a stale invalid\n // doc and so the operator can inspect what the agent wrote.\n const rejectedPath = `${paths.docPath}.rejected.md`;\n await writeFile(rejectedPath, docText, 'utf-8').catch((err) => {\n console.warn(`[handoff-source] failed to persist rejected output to ${rejectedPath}: ${err?.message ?? err}`);\n });\n console.warn(`[handoff-source] validation rejected source-authored doc (${validation.reason}); raw output saved to ${rejectedPath}`);\n throw new HandoffValidationError(paths.docPath, validation.reason);\n }\n\n return { docPath: paths.docPath, docText: sanitizeHandoffDoc(docText) };\n}\n\nfunction workspaceSourceFromCwd(sourceConv: Conversation): { issueId: string; workspacePath: string } | null {\n const normalizedCwd = sourceConv.cwd.replace(/\\\\/g, '/');\n const match = normalizedCwd.match(/^((?:(?:.*)\\/)?workspaces\\/feature-([a-z]+-\\d+))(?:\\/.*)?$/i);\n if (!match?.[1] || !match[2]) return null;\n return {\n issueId: sourceConv.issueId ?? match[2].toUpperCase(),\n workspacePath: match[1],\n };\n}\n\nexport async function handoffPreconditionFallbackReason(sourceConv: Conversation): Promise<string | null> {\n if (sourceConv.status === 'ended') return 'source-ended';\n\n const workspaceSource = workspaceSourceFromCwd(sourceConv);\n if (!workspaceSource) return null;\n\n try {\n await access(join(workspaceSource.workspacePath, '.devcontainer'));\n } catch {\n return null;\n }\n\n const health = await Effect.runPromise(getWorkspaceStackHealth(workspaceSource.issueId, {\n workspacePath: workspaceSource.workspacePath,\n }));\n return health.healthy ? 'source-workspace-devcontainer' : null;\n}\n\nexport function handoffFailureReason(error: unknown): string {\n if (error instanceof HandoffStallError) return 'handoff-timeout';\n if (error instanceof HandoffValidationError) return 'handoff-validation';\n return 'handoff-request-failed';\n}\n\nexport function logHandoffFallback(sourceConv: Conversation, reason: string): void {\n console.warn(`[summary-fork] handoff-fallback source=${sourceConv.name} reason=${reason}`);\n}\n\n/**\n * When a handoff falls back to a summary fork, the user's focus text would\n * otherwise be silently dropped. Prepend a small notice to the summary so the\n * successor conversation still sees what was asked, and so the user gets a\n * visible breadcrumb that the intended handoff failed.\n */\nexport function prependFallbackFocus(summary: string, focus: string | undefined, fallbackReason: string): string {\n const trimmedFocus = focus?.trim();\n if (!trimmedFocus) return summary;\n const header = [\n `**Note from Overdeck:** the intended handoff fell back to a summary fork (\\`${fallbackReason}\\`). The focus you requested is preserved below; the summary that follows is auto-generated, not an authored handoff document.`,\n '',\n '**Requested focus:**',\n '',\n `> ${trimmedFocus.split('\\n').join('\\n> ')}`,\n '',\n '---',\n '',\n ].join('\\n');\n return header + summary;\n}\n\nasync function generateSummarySeed(\n sourceSessionFile: string,\n summaryModel: string | undefined,\n localSummaryOnly: boolean | undefined,\n includeThinkingInSummary: boolean | undefined,\n summaryHarness?: RuntimeName,\n sourceHarness?: RuntimeName,\n): Promise<{ summary: string; summaryModel: string | null }> {\n if (localSummaryOnly) {\n return {\n summary: await Effect.runPromise(generateFallbackSummary(sourceSessionFile)),\n summaryModel: null,\n };\n }\n return generateSummaryForFork(sourceSessionFile, summaryModel, includeThinkingInSummary, summaryHarness, sourceHarness);\n}\n\nasync function generateFallbackSummaryPromise(jsonlPath: string): Promise<string> {\n const { readFile } = await import('node:fs/promises');\n const lines = (await readFile(jsonlPath, 'utf-8'))\n .split('\\n')\n .filter((l) => l.trim());\n\n const userMessages: string[] = [];\n const filesModified = new Set<string>();\n const toolsUsed = new Set<string>();\n\n for (const line of lines) {\n let entry: any;\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n\n if (entry.type === 'user' && entry.message) {\n const content = entry.message.content;\n if (typeof content === 'string' && content.trim()) {\n if (!content.trim().startsWith('<local-command') && !content.trim().startsWith('<command-name')) {\n userMessages.push(content.trim());\n }\n } else if (Array.isArray(content)) {\n for (const block of content) {\n if (block.type === 'text' && block.text?.trim() && !block.text.trim().startsWith('<')) {\n userMessages.push(block.text.trim());\n }\n }\n }\n }\n\n if (entry.type === 'assistant' && entry.message?.content) {\n const content = entry.message.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (block.type === 'tool_use') {\n toolsUsed.add(block.name);\n if (block.name === 'Edit' || block.name === 'Write') {\n const fp = block.input?.file_path || block.input?.path;\n if (fp) filesModified.add(fp);\n }\n }\n }\n }\n }\n }\n\n let summary = `## Conversation Summary Fork\\n\\n`;\n summary += `This is a continuation of a previous conversation, seeded with a summary of the earlier work.\\n\\n`;\n\n if (userMessages.length > 0) {\n summary += `### User Messages:\\n`;\n for (const msg of userMessages.slice(0, 10)) {\n summary += `- ${msg.slice(0, 200)}${msg.length > 200 ? '...' : ''}\\n`;\n }\n summary += '\\n';\n }\n\n if (filesModified.size > 0) {\n summary += `### Files Modified:\\n`;\n for (const f of [...filesModified].sort()) {\n summary += `- \\`${f.replace(/.*\\/overdeck\\//, '')}\\`\\n`;\n }\n summary += '\\n';\n }\n\n if (toolsUsed.size > 0) {\n summary += `### Tools Used: ${[...toolsUsed].sort().join(', ')}\\n\\n`;\n }\n\n summary += FORK_WAIT_INSTRUCTION;\n\n return summary;\n}\n\nexport async function generateSummaryForFork(\n jsonlPath: string,\n summaryModel?: string,\n includeThinkingInSummary?: boolean,\n summaryHarness: RuntimeName = 'claude-code',\n sourceHarness?: RuntimeName,\n): Promise<{ summary: string; summaryModel: string | null }> {\n if (!summaryModel) {\n // Fork summaries serialize the entire conversation in one shot. Sonnet 4.6's\n // 1M-token context handles large sessions that would overflow Haiku's 200k.\n summaryModel = 'claude-sonnet-4-6';\n }\n\n console.log(`[claude-invoke] purpose=summary-fork | model=${summaryModel} | summaryHarness=${summaryHarness} | sourceHarness=${sourceHarness ?? 'claude-code'} | source=summary-fork.ts:generateSummaryForFork | jsonl=${jsonlPath}`);\n\n const { config } = loadConfigSync();\n const richMode = config.conversations.richCompaction;\n\n // The source harness selects which transcript adapter parses/serializes the\n // JSONL (Pi and Claude Code have different shapes); the summary harness\n // selects which CLI backend runs the summarizer LLM. They are independent —\n // e.g. a Pi source can be summarized by a Claude model.\n const adapter = getTranscriptAdapter(sourceHarness);\n\n try {\n const result = await adapter.compactSummary(jsonlPath, {\n model: summaryModel,\n richMode,\n includeThinking: includeThinkingInSummary,\n harness: summaryHarness,\n });\n console.log(`[claude-invoke] SUCCESS purpose=summary-fork | model=${summaryModel} | outputChars=${result.summary.length}`);\n return { summary: result.summary + FORK_WAIT_INSTRUCTION, summaryModel };\n } catch (err: any) {\n console.error(`[claude-invoke] FAILED purpose=summary-fork | model=${summaryModel} | error=\"${err.message}\"`);\n throw err;\n }\n}async function reserveSummaryForkSessionPromise(\n cwd: string,\n): Promise<{ sessionId: string; sessionFile: string }> {\n // PAN-1862 (NFR-3): delegates to the shared session-fork primitive so the\n // conversation panel and the review convoy fork through one code path.\n const { reserveForkSession } = await import('./session-fork.js');\n return reserveForkSession(cwd);\n}\n\nasync function copySessionFromCompactBoundaryPromise(\n sourcePath: string,\n destPath: string,\n): Promise<void> {\n const { copySessionForFork } = await import('./session-fork.js');\n return copySessionForFork(sourcePath, destPath, { fullHistory: false });\n}\n\nasync function createSummaryForkPromise(\n conv: Conversation,\n options: SummaryForkOptions = {},\n): Promise<SummaryForkResult> {\n const sourceSessionFile = conv.claudeSessionId\n ? sessionFilePath(conv.cwd, conv.claudeSessionId)\n : null;\n if (!sourceSessionFile) {\n throw new Error(`No session file found for conversation ${conv.name}`);\n }\n\n const cwd = options.cwd || conv.cwd || process.cwd();\n const launchModel = options.model || conv.model;\n const summaryModel = options.model || conv.model;\n const forkMode = options.forkMode ?? 'summary';\n console.log(`[summary-fork] Forking conv=${conv.name} launchModel=${launchModel || 'default'} summaryModel=${summaryModel || 'default'} localOnly=${options.localSummaryOnly || false} forkMode=${forkMode}`);\n\n const { sessionId, sessionFile } = await Effect.runPromise(reserveSummaryForkSession(cwd));\n\n let summary: string;\n let usedSummaryModel: string | null;\n let effectiveForkMode = forkMode;\n let handoffDocPath: string | null = null;\n let forkFallbackReason: string | null = null;\n\n if (forkMode === 'plain') {\n // Plain fork: copy raw JSONL from last compact boundary (or full history)\n // into the new session file so Claude Code can --resume it directly.\n await Effect.runPromise(copySessionFromCompactBoundary(sourceSessionFile, sessionFile));\n summary = '';\n usedSummaryModel = null;\n } else if (forkMode === 'handoff') {\n const handoffAuthor: HandoffAuthor = options.handoffAuthor ?? 'external';\n if (handoffAuthor === 'external') {\n // External authoring: read the source JSONL and have a fresh authoring\n // session write the handoff doc. Source conversation is never touched.\n // Works on ended source conversations and lets the user pick the\n // authoring model/harness independently of the source.\n try {\n const handoff = await authorHandoffExternal(\n conv,\n sourceSessionFile,\n options.focus,\n options.handoffAuthorModel,\n options.handoffAuthorHarness,\n );\n summary = handoff.docText;\n usedSummaryModel = options.handoffAuthorModel ?? DEFAULT_HANDOFF_AUTHOR_MODEL;\n handoffDocPath = handoff.docPath;\n } catch (error) {\n forkFallbackReason = handoffFailureReason(error);\n effectiveForkMode = 'summary';\n logHandoffFallback(conv, forkFallbackReason);\n const result = await generateSummarySeed(sourceSessionFile, summaryModel ?? undefined, options.localSummaryOnly, options.includeThinkingInSummary, undefined, conv.harness ?? undefined);\n summary = prependFallbackFocus(result.summary, options.focus, forkFallbackReason);\n usedSummaryModel = result.summaryModel;\n }\n } else {\n // Source authoring (legacy): deliver the handoff prompt to the live\n // source agent and wait for it to write the doc + .done sentinel.\n // Requires the source conversation to be alive and pollutes its\n // context with the handoff turn pair.\n const preconditionFallback = await handoffPreconditionFallbackReason(conv);\n if (preconditionFallback) {\n forkFallbackReason = preconditionFallback;\n effectiveForkMode = 'summary';\n logHandoffFallback(conv, preconditionFallback);\n const result = await generateSummarySeed(sourceSessionFile, summaryModel ?? undefined, options.localSummaryOnly, options.includeThinkingInSummary, undefined, conv.harness ?? undefined);\n summary = prependFallbackFocus(result.summary, options.focus, preconditionFallback);\n usedSummaryModel = result.summaryModel;\n } else {\n try {\n const handoff = await requestHandoffFromAgent(conv, options.focus, {\n timeoutMs: options.handoffTimeoutMs,\n pollIntervalMs: options.handoffPollIntervalMs,\n });\n summary = handoff.docText;\n usedSummaryModel = null;\n handoffDocPath = handoff.docPath;\n } catch (error) {\n forkFallbackReason = handoffFailureReason(error);\n effectiveForkMode = 'summary';\n logHandoffFallback(conv, forkFallbackReason);\n const result = await generateSummarySeed(sourceSessionFile, summaryModel ?? undefined, options.localSummaryOnly, options.includeThinkingInSummary, undefined, conv.harness ?? undefined);\n summary = prependFallbackFocus(result.summary, options.focus, forkFallbackReason);\n usedSummaryModel = result.summaryModel;\n }\n }\n }\n } else {\n const result = await generateSummarySeed(sourceSessionFile, summaryModel ?? undefined, options.localSummaryOnly, options.includeThinkingInSummary, undefined, conv.harness ?? undefined);\n summary = result.summary;\n usedSummaryModel = result.summaryModel;\n }\n\n const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');\n const suffix = randomUUID().slice(0, 4);\n const newName = `${timestamp}-${suffix}`;\n const newTmux = `conv-${newName}`;\n\n let newConv = createConversation({\n name: newName,\n tmuxSession: newTmux,\n cwd,\n issueId: conv.issueId ?? undefined,\n title: effectiveForkMode === 'plain'\n ? `Fork: ${conv.title || conv.name}`\n : effectiveForkMode === 'handoff'\n ? `Handoff: ${conv.title || conv.name}`\n : `Summary Fork: ${conv.title || conv.name}`,\n titleSource: 'manual',\n titleSeed: effectiveForkMode === 'plain'\n ? `Fork of ${conv.name}`\n : effectiveForkMode === 'handoff'\n ? `Handoff of ${conv.name}`\n : `Summary Fork of ${conv.name}`,\n claudeSessionId: sessionId,\n model: launchModel ?? undefined,\n effort: conv.effort ?? undefined,\n harness: options.harness ?? conv.harness ?? undefined,\n });\n if (handoffDocPath) {\n newConv = recordConversationHandoff(conv.name, newConv.name, handoffDocPath);\n }\n if (forkFallbackReason) {\n updateConversationForkFallbackReason(newConv.name, forkFallbackReason);\n newConv = getConversationByName(newConv.name) ?? newConv;\n }\n\n return {\n conversation: newConv,\n sessionId,\n sessionFile,\n summary,\n summaryModel: usedSummaryModel,\n forkMode: effectiveForkMode,\n handoffDocPath,\n forkFallbackReason,\n };\n}\n\n// Re-export runModelSummary for any callers that need it directly\nexport { runModelSummary };\n\n// ─── Effect variants (PAN-1249, additive) ────────────────────────────────────\n//\n// Additive Effect surface for fork helpers. Failures from the underlying\n// fs ops or LLM calls surface as FsError (filesystem failures) or Error\n// (LLM / generation failures). The existing Promise functions remain\n// canonical; these are wrappers for Effect-native callers.\n\n/** Effect variant of generateFallbackSummary. */\nexport function generateFallbackSummary(\n jsonlPath: string,\n): Effect.Effect<string, FsError> {\n return Effect.tryPromise({\n try: () => generateFallbackSummaryPromise(jsonlPath),\n catch: (cause) =>\n new FsError({ path: jsonlPath, operation: 'fallback-summary', cause }),\n });\n}\n\n/** Effect variant of reserveSummaryForkSession. */\nexport function reserveSummaryForkSession(\n cwd: string,\n): Effect.Effect<{ sessionId: string; sessionFile: string }, FsError> {\n return Effect.tryPromise({\n try: () => reserveSummaryForkSessionPromise(cwd),\n catch: (cause) =>\n new FsError({ path: cwd, operation: 'reserve-session', cause }),\n });\n}\n\n/** Effect variant of copySessionFromCompactBoundary. */\nexport function copySessionFromCompactBoundary(\n sourcePath: string,\n destPath: string,\n): Effect.Effect<void, FsError> {\n return Effect.tryPromise({\n try: () => copySessionFromCompactBoundaryPromise(sourcePath, destPath),\n catch: (cause) =>\n new FsError({ path: sourcePath, operation: 'copy-session', cause }),\n });\n}\n\n/** Effect variant of createSummaryFork. */\nexport function createSummaryFork(\n conv: Conversation,\n options: SummaryForkOptions = {},\n): Effect.Effect<SummaryForkResult, FsError> {\n return Effect.tryPromise({\n try: () => createSummaryForkPromise(conv, options),\n catch: (cause) =>\n new FsError({ path: conv.name, operation: 'create-summary-fork', cause }),\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2IA,eAAe,6BAA6B,aAA6C;CACvF,MAAM,aAAaA,OAAK,SAAS,EAAE,aAAa,UAAU,aAAa,WAAW;AAClF,KAAI,CAAC,WAAW,WAAW,CAAE,QAAO;AACpC,KAAI;EACF,MAAM,WAAW,MAAM,QAAQ,WAAW,EAAE,QAAQ,SAAS,KAAK,SAAS,SAAS,CAAC;AACrF,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,UAAQ,MAAM;AACd,SAAOA,OAAK,YAAY,QAAQ,QAAQ,SAAS,GAAI;SAC/C;AACN,SAAO;;;AAkBX,SAAS,iBAAiB,OAAgB,iBAA8C;AACtF,KAAI,MAAM,SAAS,UAAW,QAAO,KAAA;CACrC,MAAM,OAAO,MAAM,SAAS;AAC5B,KAAI,SAAS,UAAU,SAAS,YAAa,QAAO,KAAA;CAEpD,MAAM,UAAU,MAAM,SAAS;AAC/B,KAAI,CAAC,MAAM,QAAQ,QAAQ,CAAE,QAAO,KAAA;CAEpC,MAAM,QAAkB,CAAC,IAAI,KAAK,GAAG;AACrC,MAAK,MAAM,SAAS,QAClB,KAAI,OAAO,SAAS,UAAU,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,MAAM,CAC/E,OAAM,KAAK,MAAM,KAAK;UACb,OAAO,SAAS,cAAc,mBAAmB,OAAO,MAAM,aAAa,YAAY,MAAM,SAAS,MAAM,CACrH,OAAM,KAAK,eAAe,MAAM,WAAW;UAClC,OAAO,SAAS,cAAc,OAAO,MAAM,SAAS,UAAU;EACvE,IAAI,OAAO;AACX,MAAI;AACF,UAAO,KAAK,UAAU,MAAM,UAAU,CAAC,MAAM,GAAG,IAAI;UAC9C;AACN,UAAO;;AAET,QAAM,KAAK,cAAc,MAAM,KAAK,KAAK,OAAO;;AAOpD,KAAI,MAAM,WAAW,EAAG,QAAO,KAAA;AAC/B,QAAO,MAAM,KAAK,KAAK;;;;;;;AA+DzB,SAAgB,qBAAqB,SAAiE;AACpG,QAAO,SAAS,WAAW,kBAAkB;;;;aAtOD;wBAMf;AA4DzB,qBAAmD;EACvD,MAAM;EACN,2BAA2B;EAC3B,+BAA+B;EAE/B,MAAM,mBAAmB,MAAM;AAC7B,OAAI,CAAC,KAAK,gBAAiB,QAAO;GAClC,MAAM,OAAO,gBAAgB,KAAK,KAAK,KAAK,gBAAgB;AAC5D,UAAO,WAAW,KAAK,GAAG,OAAO;;EAGnC,MAAM,oBAAoB,aAAa,SAAS;AAE9C,UAAOC,sBAAgC,MADjBC,aAAuB,YAAY,EACT,SAAS,mBAAmB,KAAK;;EAGnF,MAAM,eAAe,aAAa,SAAS;GAIzC,MAAM,SAAS,MAAM,OAAO,WAC1B,qBAAqB;IACnB,WAAW;IACX,OAAO,SAAS;IAChB,UAAU,SAAS,YAAY;IAC/B,MAAM;IACN,0BAA0B,SAAS,mBAAmB;IACtD,SAAS,SAAS,WAAW;IAC9B,CAAC,CACH;AACD,UAAO;IAAE,SAAS,OAAO;IAAS,cAAc,OAAO;IAAc;;EAExE;AAuEK,aAA2C;EAC/C,MAAM;EACN,2BAA2B;EAC3B,+BAA+B;EAE/B,MAAM,mBAAmB,MAAM;AAC7B,UAAO,6BAA6B,KAAK,YAAY;;EAGvD,MAAM,oBAAoB,aAAa,SAAS;GAC9C,MAAM,kBAAkB,SAAS,mBAAmB;GACpD,MAAM,UAAU,MAAM,SAAS,aAAa,QAAQ;GACpD,MAAM,QAAkB,EAAE;AAC1B,QAAK,MAAM,QAAQ,QAAQ,MAAM,KAAK,EAAE;AACtC,QAAI,CAAC,KAAK,MAAM,CAAE;IAClB,IAAI;AACJ,QAAI;AACF,aAAQ,KAAK,MAAM,KAAK;YAClB;AACN;;IAEF,MAAM,aAAa,iBAAiB,OAAO,gBAAgB;AAC3D,QAAI,WAAY,OAAM,KAAK,WAAW;;AAExC,UAAO,MAAM,KAAK,OAAO;;EAG3B,MAAM,eAAe,aAAa,SAAS;GAIzC,MAAM,aAAa,MAAM,UAAU,oBAAoB,aAAa,EAClE,iBAAiB,SAAS,mBAAmB,MAC9C,CAAC;AACF,OAAI,CAAC,WAAW,MAAM,CACpB,QAAO;IAAE,SAAS;IAAI,cAAc;IAAM;AAQ5C,UAAO;IAAE,SAAA,MANa,wBAAwB,YAAY;KACxD,OAAO,SAAS;KAChB,UAAU,SAAS,YAAY;KAC/B,SAAS,SAAS,WAAW;KAC7B,WAAW,SAAS;KACrB,CAAC;IACgB,cAAc,SAAS,SAAS;IAAM;;EAE3D;AAIK,YAA+D;EACnE,eAAe;EACf,UAAU;EACV,SAAS;EACV;;;;AChPD,SAAgB,mBAAmB,cAAsB,cAAoC;CAC3F,MAAM,SAAS,aAAa,MAAM;CAClC,MAAM,YAAY,aAAa,MAAM;AAErC,KAAI,CAAC,kBAAkB,KAAK,OAAO,CACjC,OAAM,IAAI,MAAM,oCAAoC,eAAe;AAErE,KAAI,UAAU,WAAW,KAAK,UAAU,SAAS,IAAI,IAAI,UAAU,SAAS,KAAK,CAC/E,OAAM,IAAI,MAAM,8BAA8B,eAAe;CAG/D,MAAM,UAAU,KAAK,gBAAgB,EAAE,GAAG,OAAO,GAAG,UAAU,KAAK;AACnE,QAAO;EACL;EACA,cAAc,GAAG,QAAQ;EAC1B;;AAGH,eAAsB,oBAAqC;CACzD,MAAM,cAAc,gBAAgB;AACpC,OAAMC,QAAM,aAAa,EAAE,WAAW,MAAM,CAAC;AAC7C,QAAO;;;;aA9BoC;AAEvC,qBAAoB;;;;;;;;;ACkI1B,SAAgB,mBAAmB,MAAsB;CACvD,MAAM,UAAU,KAAK,MAAM;AAE3B,QADmB,QAAQ,MAAM,+CAChB,GAAG,IAAI,MAAM,IAAI;;AAGpC,SAAgB,mBAAmB,MAAoC;CACrE,MAAM,YAAY,mBAAmB,KAAK;AAC1C,KAAI,UAAU,SAAS,IACrB,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAoD;AAMlF,KAAI,CAAC,yCAAyC,KAAK,UAAU,CAC3D,QAAO;EAAE,IAAI;EAAO,QAAQ;EAA4D;AAE1F,QAAO,EAAE,IAAI,MAAM;;AAGrB,SAAS,oBAAoB,UAAkB,OAA2B,YAA4B;CACpG,MAAM,YAAY,OAAO,MAAM,IAAI;AACnC,QAAO,SACJ,MAAM,YAAY,CAAC,KAAK,UAAU,CAClC,MAAM,iBAAiB,CAAC,KAAK,WAAW;;AAG7C,SAAS,MAAM,IAA2B;AACxC,QAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;;AAG1D,eAAe,kBAAkB,OAAqB,WAAmB,gBAAyC;CAChH,MAAM,WAAW,KAAK,KAAK,GAAG;AAE9B,QAAO,KACL,KAAI;AACF,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,MAAM,aAAa;AAChC,SAAO,MAAM,SAAS,MAAM,SAAS,QAAQ;SACvC;EACN,MAAM,cAAc,WAAW,KAAK,KAAK;AACzC,MAAI,eAAe,EACjB,OAAM,IAAI,kBAAkB,MAAM,SAAS,MAAM,cAAc,UAAU;AAE3E,QAAM,MAAM,KAAK,IAAI,gBAAgB,YAAY,CAAC;;;AAgBxD,SAAS,4BAA4B,UAAkB,OAA2B,YAAoB,YAA4B;CAChI,MAAM,YAAY,OAAO,MAAM,IAAI;AACnC,QAAO,SACJ,MAAM,YAAY,CAAC,KAAK,UAAU,CAClC,MAAM,iBAAiB,CAAC,KAAK,WAAW,CACxC,MAAM,iBAAiB,CAAC,KAAK,WAAW;;;;;;;;;;;;AAa7C,eAAsB,sBACpB,YACA,mBACA,OACA,OACA,SACA,UAA0B,EAAE,EACG;AAC/B,OAAM,mBAAmB;CACzB,MAAM,aAAa,QAAQ,uBAAO,IAAI,MAAM,EAAE,aAAa;CAC3D,MAAM,QAAQ,mBAAmB,WAAW,MAAM,UAAU;CAE5D,MAAM,iBAAiB,SAAS;CAChC,MAAM,mBAAgC,WAAW;CAOjD,MAAM,WAAW,MAAM,SAASC,OAAK,aAAa,SAD7B,qBAAqB,WAAW,2BAA2B,sBACR,EAAE,QAAQ;CAIlF,MAAM,gBAAgB,qBAAqB,WAAW,WAAW,KAAA,EAAU;CAG3E,MAAM,aAAa,MAAM,cAAc,oBAAoB,mBAAmB,EAAE,iBAAiB,OAAO,CAAC;CASzG,IAAI;CACJ,IAAI;AAMJ,KAAI,WAAW,SAAS,sCAAsC;AAC5D,UAAQ,IAAI,2DAA2D,eAAe,aAAa,iBAAiB,YAAY,WAAW,KAAK,qBAAqB,WAAW,OAAO,uBAAuB;EAC9M,MAAM,EAAE,WAAW,gBAAgB;EACnC,MAAM,WAAW,OAAO,cAAc;EACtC,MAAM,UAAU,MAAM,cAAc,eAAe,mBAAmB;GACpE,OAAO;GACP;GACA,iBAAiB;GACjB,SAAS;GACV,CAAC;AACF,gBAAc,QAAQ;AACtB,eAAa;AACb,UAAQ,IAAI,6EAA6E,eAAe,kBAAkB,QAAQ,QAAQ,SAAS;QAC9I;AACL,gBAAc;AACd,eAAa;;CAGf,MAAM,SAAS,4BAA4B,UAAU,OAAO,aAAa,MAAM,QAAQ;AACvF,SAAQ,IAAI,2DAA2D,eAAe,aAAa,iBAAiB,YAAY,WAAW,KAAK,eAAe,WAAW,iBAAiB,OAAO,OAAO,gBAAgB,MAAM,UAAU;CAczO,MAAM,SAAS,MAAM,OAAO,WAAW,gBAAgB,QAAQ,gBAAgB,2BAA2B,kBAAkB,CAAC,QAAQ,CAAC,CAAC;AACvI,SAAQ,IAAI,2EAA2E,eAAe,iBAAiB,OAAO,OAAO,mBAAmB,KAAK,UAAU,OAAO,MAAM,GAAG,IAAI,CAAC,GAAG;CAE/L,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,SAAS,MAAM,SAAS,QAAQ;UACzC,KAAK;EAKZ,MAAM,eAAe,GAAG,MAAM,QAAQ;AACtC,QAAM,UAAU,cAAc,QAAQ,QAAQ,CAAC,OAAO,SAAS;AAC7D,WAAQ,KAAK,yDAAyD,aAAa,IAAI,MAAM,WAAW,OAAO;IAC/G;AACF,UAAQ,KAAK,gEAAgE,OAAO,OAAO,mBAAmB,aAAa,IAAK,KAA8B,WAAW,MAAM;AAC/K,QAAM,IAAI,uBAAuB,MAAM,SAAS,0EAA0E;;CAG5H,MAAM,aAAa,mBAAmB,QAAQ;AAC9C,KAAI,CAAC,WAAW,IAAI;EAGlB,MAAM,eAAe,GAAG,MAAM,QAAQ;AACtC,QAAM,UAAU,cAAc,SAAS,QAAQ,CAAC,OAAO,QAAQ;AAC7D,WAAQ,KAAK,+DAA+D,aAAa,IAAI,KAAK,WAAW,MAAM;IACnH;AACF,UAAQ,KAAK,+DAA+D,WAAW,OAAO,mBAAmB,eAAe;AAChI,QAAM,IAAI,uBAAuB,MAAM,SAAS,WAAW,OAAO;;CAOpE,MAAM,YAAY,mBAAmB,QAAQ;AAC7C,KAAI,cAAc,QAChB,OAAM,UAAU,MAAM,SAAS,WAAW,QAAQ;AAEpD,OAAM,UAAU,MAAM,cAAc,IAAI,QAAQ;AAEhD,QAAO;EAAE,SAAS,MAAM;EAAS,SAAS;EAAW;;AAGvD,eAAsB,wBACpB,YACA,OACA,UAAiC,EAAE,EACJ;AAC/B,OAAM,mBAAmB;CACzB,MAAM,aAAa,QAAQ,uBAAO,IAAI,MAAM,EAAE,aAAa;CAC3D,MAAM,QAAQ,mBAAmB,WAAW,MAAM,UAAU;CAE5D,MAAM,SAAS,oBAAoB,MADZ,SAASA,OAAK,aAAa,SAAS,aAAa,EAAE,QAAQ,EACrC,OAAO,MAAM,QAAQ;AAElE,OAAM,oBAAoB,WAAW,aAAa,QAAQ,kBAAkB;CAE5E,MAAM,UAAU,MAAM,kBACpB,OACA,QAAQ,aAAa,4BACrB,QAAQ,kBAAkB,iCAC3B;CACD,MAAM,aAAa,mBAAmB,QAAQ;AAC9C,KAAI,CAAC,WAAW,IAAI;EAIlB,MAAM,eAAe,GAAG,MAAM,QAAQ;AACtC,QAAM,UAAU,cAAc,SAAS,QAAQ,CAAC,OAAO,QAAQ;AAC7D,WAAQ,KAAK,yDAAyD,aAAa,IAAI,KAAK,WAAW,MAAM;IAC7G;AACF,UAAQ,KAAK,6DAA6D,WAAW,OAAO,yBAAyB,eAAe;AACpI,QAAM,IAAI,uBAAuB,MAAM,SAAS,WAAW,OAAO;;AAGpE,QAAO;EAAE,SAAS,MAAM;EAAS,SAAS,mBAAmB,QAAQ;EAAE;;AAGzE,SAAS,uBAAuB,YAA6E;CAE3G,MAAM,QADgB,WAAW,IAAI,QAAQ,OAAO,IACzB,CAAC,MAAM,8DAA8D;AAChG,KAAI,CAAC,QAAQ,MAAM,CAAC,MAAM,GAAI,QAAO;AACrC,QAAO;EACL,SAAS,WAAW,WAAW,MAAM,GAAG,aAAa;EACrD,eAAe,MAAM;EACtB;;AAGH,eAAsB,kCAAkC,YAAkD;AACxG,KAAI,WAAW,WAAW,QAAS,QAAO;CAE1C,MAAM,kBAAkB,uBAAuB,WAAW;AAC1D,KAAI,CAAC,gBAAiB,QAAO;AAE7B,KAAI;AACF,QAAM,OAAOA,OAAK,gBAAgB,eAAe,gBAAgB,CAAC;SAC5D;AACN,SAAO;;AAMT,SAAO,MAHc,OAAO,WAAW,wBAAwB,gBAAgB,SAAS,EACtF,eAAe,gBAAgB,eAChC,CAAC,CAAC,EACW,UAAU,kCAAkC;;AAG5D,SAAgB,qBAAqB,OAAwB;AAC3D,KAAI,iBAAiB,kBAAmB,QAAO;AAC/C,KAAI,iBAAiB,uBAAwB,QAAO;AACpD,QAAO;;AAGT,SAAgB,mBAAmB,YAA0B,QAAsB;AACjF,SAAQ,KAAK,0CAA0C,WAAW,KAAK,UAAU,SAAS;;;;;;;;AAS5F,SAAgB,qBAAqB,SAAiB,OAA2B,gBAAgC;CAC/G,MAAM,eAAe,OAAO,MAAM;AAClC,KAAI,CAAC,aAAc,QAAO;AAW1B,QAVe;EACb,+EAA+E,eAAe;EAC9F;EACA;EACA;EACA,KAAK,aAAa,MAAM,KAAK,CAAC,KAAK,OAAO;EAC1C;EACA;EACA;EACD,CAAC,KAAK,KACM,GAAG;;AAoBlB,eAAe,+BAA+B,WAAoC;CAChF,MAAM,EAAE,aAAa,MAAM,OAAO;CAClC,MAAM,SAAS,MAAM,SAAS,WAAW,QAAQ,EAC9C,MAAM,KAAK,CACX,QAAQ,MAAM,EAAE,MAAM,CAAC;CAE1B,MAAM,eAAyB,EAAE;CACjC,MAAM,gCAAgB,IAAI,KAAa;CACvC,MAAM,4BAAY,IAAI,KAAa;AAEnC,MAAK,MAAM,QAAQ,OAAO;EACxB,IAAI;AACJ,MAAI;AACF,WAAQ,KAAK,MAAM,KAAK;UAClB;AACN;;AAGF,MAAI,MAAM,SAAS,UAAU,MAAM,SAAS;GAC1C,MAAM,UAAU,MAAM,QAAQ;AAC9B,OAAI,OAAO,YAAY,YAAY,QAAQ,MAAM;QAC3C,CAAC,QAAQ,MAAM,CAAC,WAAW,iBAAiB,IAAI,CAAC,QAAQ,MAAM,CAAC,WAAW,gBAAgB,CAC7F,cAAa,KAAK,QAAQ,MAAM,CAAC;cAE1B,MAAM,QAAQ,QAAQ;SAC1B,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,UAAU,MAAM,MAAM,MAAM,IAAI,CAAC,MAAM,KAAK,MAAM,CAAC,WAAW,IAAI,CACnF,cAAa,KAAK,MAAM,KAAK,MAAM,CAAC;;;AAM5C,MAAI,MAAM,SAAS,eAAe,MAAM,SAAS,SAAS;GACxD,MAAM,UAAU,MAAM,QAAQ;AAC9B,OAAI,MAAM,QAAQ,QAAQ;SACnB,MAAM,SAAS,QAClB,KAAI,MAAM,SAAS,YAAY;AAC7B,eAAU,IAAI,MAAM,KAAK;AACzB,SAAI,MAAM,SAAS,UAAU,MAAM,SAAS,SAAS;MACnD,MAAM,KAAK,MAAM,OAAO,aAAa,MAAM,OAAO;AAClD,UAAI,GAAI,eAAc,IAAI,GAAG;;;;;;CAQzC,IAAI,UAAU;AACd,YAAW;AAEX,KAAI,aAAa,SAAS,GAAG;AAC3B,aAAW;AACX,OAAK,MAAM,OAAO,aAAa,MAAM,GAAG,GAAG,CACzC,YAAW,KAAK,IAAI,MAAM,GAAG,IAAI,GAAG,IAAI,SAAS,MAAM,QAAQ,GAAG;AAEpE,aAAW;;AAGb,KAAI,cAAc,OAAO,GAAG;AAC1B,aAAW;AACX,OAAK,MAAM,KAAK,CAAC,GAAG,cAAc,CAAC,MAAM,CACvC,YAAW,OAAO,EAAE,QAAQ,kBAAkB,GAAG,CAAC;AAEpD,aAAW;;AAGb,KAAI,UAAU,OAAO,EACnB,YAAW,mBAAmB,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC,KAAK,KAAK,CAAC;AAGjE,YAAW;AAEX,QAAO;;AAGT,eAAsB,uBACpB,WACA,cACA,0BACA,iBAA8B,eAC9B,eAC2D;AAC3D,KAAI,CAAC,aAGH,gBAAe;AAGjB,SAAQ,IAAI,gDAAgD,aAAa,oBAAoB,eAAe,mBAAmB,iBAAiB,cAAc,2DAA2D,YAAY;CAErO,MAAM,EAAE,WAAW,gBAAgB;CACnC,MAAM,WAAW,OAAO,cAAc;CAMtC,MAAM,UAAU,qBAAqB,cAAc;AAEnD,KAAI;EACF,MAAM,SAAS,MAAM,QAAQ,eAAe,WAAW;GACrD,OAAO;GACP;GACA,iBAAiB;GACjB,SAAS;GACV,CAAC;AACF,UAAQ,IAAI,wDAAwD,aAAa,iBAAiB,OAAO,QAAQ,SAAS;AAC1H,SAAO;GAAE,SAAS,OAAO,UAAU;GAAuB;GAAc;UACjE,KAAU;AACjB,UAAQ,MAAM,uDAAuD,aAAa,YAAY,IAAI,QAAQ,GAAG;AAC7G,QAAM;;;AAET,eAAe,iCACd,KACqD;CAGrD,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,QAAO,mBAAmB,IAAI;;AAGhC,eAAe,sCACb,YACA,UACe;CACf,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,QAAO,mBAAmB,YAAY,UAAU,EAAE,aAAa,OAAO,CAAC;;;AA2JzE,SAAgB,wBACd,WACgC;AAChC,QAAO,OAAO,WAAW;EACvB,WAAW,+BAA+B,UAAU;EACpD,QAAQ,UACN,IAAI,QAAQ;GAAE,MAAM;GAAW,WAAW;GAAoB;GAAO,CAAC;EACzE,CAAC;;;AAIJ,SAAgB,0BACd,KACoE;AACpE,QAAO,OAAO,WAAW;EACvB,WAAW,iCAAiC,IAAI;EAChD,QAAQ,UACN,IAAI,QAAQ;GAAE,MAAM;GAAK,WAAW;GAAmB;GAAO,CAAC;EAClE,CAAC;;;AAIJ,SAAgB,+BACd,YACA,UAC8B;AAC9B,QAAO,OAAO,WAAW;EACvB,WAAW,sCAAsC,YAAY,SAAS;EACtE,QAAQ,UACN,IAAI,QAAQ;GAAE,MAAM;GAAY,WAAW;GAAgB;GAAO,CAAC;EACtE,CAAC;;;;qBAntBkC;aAC6C;mBAChC;cACA;wBACK;0BACO;qBAC+B;cAEvD;oBACgC;AA6CjE,yBAAwB;AACxB,8BAA6B;AAC7B,oCAAmC;AACnC,oBAAmB;AAiBZ,qBAAb,cAAuC,MAAM;EAC3C,YACE,SACA,cACA,WACA;AACA,SAAM,qBAAqB,UAAU,wCAAwC,QAAQ,IAAI,eAAe;AAJxF,QAAA,UAAA;AACA,QAAA,eAAA;AACA,QAAA,YAAA;AAGhB,QAAK,OAAO;;;AAIH,0BAAb,cAA4C,MAAM;EAChD,YACE,SACA,QACA;AACA,SAAM,4BAA4B,QAAQ,IAAI,SAAS;AAHvC,QAAA,UAAA;AACA,QAAA,SAAA;AAGhB,QAAK,OAAO;;;AA2DV,gCAA+B;AAC/B,6BAA4B;AAQ5B,wCAAuC"}
|
|
1
|
+
{"version":3,"file":"summary-fork-Xn0QUNmb.js","names":["join","serializeClaudeCodeConversation","parseClaudeCodeEntries","mkdir","join"],"sources":["../../src/lib/conversations/transcript-adapter.ts","../../src/lib/conversations/handoff-paths.ts","../../src/lib/conversations/summary-fork.ts"],"sourcesContent":["/**\n * Conversation transcript adapter.\n *\n * Each agent harness writes its session transcript in its own JSONL shape:\n *\n * - Claude Code records have top-level `type: 'user'|'assistant'|'system'`,\n * with `entry.message.content` as a block array whose blocks are\n * `text|tool_use|tool_result|thinking`.\n * - Pi records have top-level `type: 'message'|'session'|'model_change'|\n * 'thinking_level_change'|...`, with the user/assistant role nested in\n * `entry.message.role` and blocks of type `text|thinking|toolCall|toolResult`.\n * - Future harnesses (Codex, etc.) will have their own shapes.\n *\n * The handoff authoring pipeline doesn't care about any of that. It needs\n * one thing from each harness: a canonical \"<conversation>...</conversation>\"\n * text it can feed the authoring model.\n *\n * This module provides that abstraction. Adding a new harness is two short\n * functions (resolveSessionFile + serialize) plus a registry entry — the\n * fork pipeline never needs to learn about the new harness.\n */\nimport { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { Effect } from 'effect';\n\nimport type { LegacyConversation as Conversation } from '../overdeck/conversations.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { sessionFilePath } from '../paths.js';\nimport {\n parseEntries as parseClaudeCodeEntries,\n serializeConversation as serializeClaudeCodeConversation,\n generateSmartSummary,\n summarizeSerializedText,\n} from './smart-compaction.js';\n\nexport interface CompactSummaryOptions {\n /** Model for the summarizer LLM. */\n model?: string;\n /** Use the rich (Claude-native-style) summary prompt variant. */\n richMode?: boolean;\n /** Include thinking blocks in the serialized source. Default: true. */\n includeThinking?: boolean;\n /** Harness backend the summarizer LLM runs on. Default: 'claude-code'. */\n harness?: RuntimeName;\n /** Per-LLM-call timeout (ms). */\n timeoutMs?: number;\n}\n\nexport interface ConversationTranscriptAdapter {\n /** Display name for logging/errors. */\n readonly name: RuntimeName;\n\n /** Plain forks copy raw Claude JSONL and spawn Claude with `--resume` —\n * only Claude Code can be the source of a plain fork. */\n readonly supportsPlainForkAsSource: boolean;\n\n /** Source-authored handoff requires delivering a prompt to the live source\n * agent and waiting for it to write a sentinel file. Claude Code supports\n * this via deliverAgentMessage; Pi has no hook-equivalent signaling channel\n * for the .done sentinel (see PAN-1134), so source authoring is gated off\n * for Pi until that lands. */\n readonly supportsSourceAuthoredHandoff: boolean;\n\n /** Resolve the path to the source conversation's transcript file.\n * Returns null if the conversation has no transcript yet (e.g. session\n * just started and hasn't produced a JSONL line). */\n resolveSessionFile(conv: Conversation): Promise<string | null>;\n\n /** Serialize the transcript into a harness-agnostic canonical text that\n * the handoff prompt template can embed verbatim. Each turn is rendered\n * with a `[user]` / `[assistant]` header followed by text and tool-use\n * summaries; thinking blocks are optional (defaults to including them).\n */\n serializeTranscript(sessionFile: string, options?: { includeThinking?: boolean }): Promise<string>;\n\n /**\n * Produce a model-generated compact summary of the transcript, suitable for\n * seeding a summary fork or feeding the handoff authoring prompt.\n *\n * claude-code uses the entry-aware smart-compaction flow (rich, file-op\n * aware); other harnesses serialize the transcript and run a generic\n * chunk-and-merge summarizer over the text. The `harness` option selects the\n * summarizer LLM backend and is independent of which adapter (= source\n * harness) produced the transcript.\n */\n compactSummary(\n sessionFile: string,\n options?: CompactSummaryOptions,\n ): Promise<{ summary: string; summaryModel: string | null }>;\n}\n\n// ─── Claude Code ──────────────────────────────────────────────────────────\n\nconst claudeCodeAdapter: ConversationTranscriptAdapter = {\n name: 'claude-code',\n supportsPlainForkAsSource: true,\n supportsSourceAuthoredHandoff: true,\n\n async resolveSessionFile(conv) {\n if (!conv.claudeSessionId) return null;\n const path = sessionFilePath(conv.cwd, conv.claudeSessionId);\n return existsSync(path) ? path : null;\n },\n\n async serializeTranscript(sessionFile, options) {\n const entries = await parseClaudeCodeEntries(sessionFile);\n return serializeClaudeCodeConversation(entries, options?.includeThinking ?? true);\n },\n\n async compactSummary(sessionFile, options) {\n // Claude Code keeps the entry-aware smart-compaction flow: it parses the\n // JSONL into typed entries, finds compact boundaries, and carries file-op\n // detail into the summary. This is richer than text-only chunking.\n const result = await Effect.runPromise(\n generateSmartSummary({\n jsonlPath: sessionFile,\n model: options?.model,\n richMode: options?.richMode ?? false,\n mode: 'fork',\n includeThinkingInSummary: options?.includeThinking ?? true,\n harness: options?.harness ?? 'claude-code',\n }),\n );\n return { summary: result.summary, summaryModel: result.summaryModel };\n },\n};\n\n// ─── Pi ───────────────────────────────────────────────────────────────────\n\n/**\n * Pi sessions live at:\n * `~/.overdeck/agents/<tmuxSession>/sessions/<iso-timestamp>_<id>.jsonl`\n *\n * Pi may rotate session files (e.g. on resume), so we pick the\n * newest by filename — filenames sort lexicographically by their\n * ISO timestamp prefix so this is deterministic.\n */\nasync function resolvePiSessionFileFromTmux(tmuxSession: string): Promise<string | null> {\n const sessionDir = join(homedir(), '.overdeck', 'agents', tmuxSession, 'sessions');\n if (!existsSync(sessionDir)) return null;\n try {\n const entries = (await readdir(sessionDir)).filter((name) => name.endsWith('.jsonl'));\n if (entries.length === 0) return null;\n entries.sort();\n return join(sessionDir, entries[entries.length - 1]!);\n } catch {\n return null;\n }\n}\n\ninterface PiEntry {\n type?: string;\n message?: {\n role?: string;\n content?: Array<{\n type?: string;\n text?: string;\n thinking?: string;\n name?: string;\n arguments?: unknown;\n }>;\n };\n}\n\nfunction serializePiEntry(entry: PiEntry, includeThinking: boolean): string | undefined {\n if (entry.type !== 'message') return undefined;\n const role = entry.message?.role;\n if (role !== 'user' && role !== 'assistant') return undefined;\n\n const content = entry.message?.content;\n if (!Array.isArray(content)) return undefined;\n\n const lines: string[] = [`[${role}]`];\n for (const block of content) {\n if (block?.type === 'text' && typeof block.text === 'string' && block.text.trim()) {\n lines.push(block.text);\n } else if (block?.type === 'thinking' && includeThinking && typeof block.thinking === 'string' && block.thinking.trim()) {\n lines.push(`[thinking]\\n${block.thinking}`);\n } else if (block?.type === 'toolCall' && typeof block.name === 'string') {\n let args = '';\n try {\n args = JSON.stringify(block.arguments).slice(0, 500);\n } catch {\n args = '<unserializable>';\n }\n lines.push(`[tool_use: ${block.name}]\\n${args}`);\n }\n // toolResult, function-call envelopes, etc. are intentionally skipped — the\n // surface we care about for handoff is what the user said, what the agent\n // said, what tools it ran, and what it was thinking. Tool outputs balloon\n // the transcript without adding signal a handoff doc needs.\n }\n if (lines.length === 1) return undefined; // header only — no content\n return lines.join('\\n');\n}\n\nconst piAdapter: ConversationTranscriptAdapter = {\n name: 'ohmypi',\n supportsPlainForkAsSource: false,\n supportsSourceAuthoredHandoff: false,\n\n async resolveSessionFile(conv) {\n return resolvePiSessionFileFromTmux(conv.tmuxSession);\n },\n\n async serializeTranscript(sessionFile, options) {\n const includeThinking = options?.includeThinking ?? true;\n const content = await readFile(sessionFile, 'utf-8');\n const parts: string[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n let entry: PiEntry;\n try {\n entry = JSON.parse(line) as PiEntry;\n } catch {\n continue;\n }\n const serialized = serializePiEntry(entry, includeThinking);\n if (serialized) parts.push(serialized);\n }\n return parts.join('\\n\\n');\n },\n\n async compactSummary(sessionFile, options) {\n // Pi's JSONL shape is not what the Claude-Code entry parser understands, so\n // we serialize the transcript through this adapter first, then run the\n // generic text chunk-and-merge summarizer over it.\n const serialized = await piAdapter.serializeTranscript(sessionFile, {\n includeThinking: options?.includeThinking ?? true,\n });\n if (!serialized.trim()) {\n return { summary: '', summaryModel: null };\n }\n const summary = await summarizeSerializedText(serialized, {\n model: options?.model,\n richMode: options?.richMode ?? false,\n harness: options?.harness ?? 'claude-code',\n timeoutMs: options?.timeoutMs,\n });\n return { summary, summaryModel: options?.model ?? null };\n },\n};\n\n// ─── Registry ─────────────────────────────────────────────────────────────\n\nconst REGISTRY: Record<RuntimeName, ConversationTranscriptAdapter> = {\n 'claude-code': claudeCodeAdapter,\n 'ohmypi': piAdapter,\n 'codex': claudeCodeAdapter,\n};\n\n/**\n * Look up the transcript adapter for a harness. Unknown harnesses default to\n * the Claude Code adapter — that matches the conservative behavior of the rest\n * of the codebase (see getHarness() in @overdeck/contracts).\n */\nexport function getTranscriptAdapter(harness: RuntimeName | undefined): ConversationTranscriptAdapter {\n return REGISTRY[harness ?? 'claude-code'] ?? claudeCodeAdapter;\n}\n","import { mkdir } from 'fs/promises';\nimport { join } from 'path';\nimport { getHandoffsDir } from '../paths.js';\n\nconst SAFE_PATH_SEGMENT = /^[A-Za-z0-9._-]+$/;\n\nexport interface HandoffPaths {\n docPath: string;\n sentinelPath: string;\n}\n\nexport function createHandoffPaths(sourceConvId: string, isoTimestamp: string): HandoffPaths {\n const convId = sourceConvId.trim();\n const timestamp = isoTimestamp.trim();\n\n if (!SAFE_PATH_SEGMENT.test(convId)) {\n throw new Error(`Invalid handoff conversation id: ${sourceConvId}`);\n }\n if (timestamp.length === 0 || timestamp.includes('/') || timestamp.includes('\\\\')) {\n throw new Error(`Invalid handoff timestamp: ${isoTimestamp}`);\n }\n\n const docPath = join(getHandoffsDir(), `${convId}-${timestamp}.md`);\n return {\n docPath,\n sentinelPath: `${docPath}.done`,\n };\n}\n\nexport async function ensureHandoffsDir(): Promise<string> {\n const handoffsDir = getHandoffsDir();\n await mkdir(handoffsDir, { recursive: true });\n return handoffsDir;\n}\n","/**\n * Conversation fork pipeline.\n *\n * This module handles creating a new conversation from an existing one.\n * Two modes are supported:\n *\n * 1. Summary fork (default): The conversation history is serialized and sent\n * to an LLM summarizer (see smart-compaction.ts). The generated structured\n * summary is injected as the first user message in the new session.\n *\n * 2. Plain fork: Raw JSONL history is copied from the last compact_boundary\n * into a new session file. Thinking blocks are sanitized (converted to text)\n * to prevent signature validation errors on cross-model resumes.\n *\n * Entry point: createSummaryFork()\n * - Reserves a new session ID and file path\n * - Generates summary (LLM, heuristic fallback, or skips for plain fork)\n * - Creates a DB record for the new conversation\n * - Returns the new conversation + session metadata\n *\n * Dashboard API: runForkPipeline() in src/dashboard/server/routes/conversations.ts\n * wires the options through and handles tmux spawn + summary injection.\n */\nimport { randomUUID } from 'node:crypto';\nimport { access, mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\n\nimport type { LegacyConversation as Conversation } from '../overdeck/conversations.js';\nimport {\n createConversation,\n getConversationByName,\n recordConversationHandoff,\n updateConversationForkFallbackReason,\n} from '../overdeck/conversations.js';\nimport { encodeClaudeProjectDir, packageRoot, sessionFilePath } from '../paths.js';\nimport { loadConfigSync } from '../config-yaml.js';\nimport { deliverAgentMessage } from '../agents.js';\nimport { runModelSummary } from './smart-compaction.js';\nimport { getTranscriptAdapter } from './transcript-adapter.js';\nimport { createHandoffPaths, ensureHandoffsDir, type HandoffPaths } from './handoff-paths.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { FsError } from '../errors.js';\nimport { getWorkspaceStackHealth } from '../workspace/stack-health.js';\n\nexport type SummaryForkMode = 'summary' | 'plain' | 'handoff';\nexport type HandoffAuthor = 'source' | 'external';\n\nexport interface SummaryForkOptions {\n model?: string;\n cwd?: string;\n harness?: RuntimeName;\n localSummaryOnly?: boolean;\n forkMode?: SummaryForkMode;\n focus?: string;\n handoffTimeoutMs?: number;\n handoffPollIntervalMs?: number;\n /** When true, include thinking block content in the serialized conversation sent to the summary model. Default: true. */\n includeThinkingInSummary?: boolean;\n /**\n * Who authors the handoff document when forkMode === 'handoff'.\n *\n * - 'source' (legacy): deliver the handoff prompt to the live source agent\n * via deliverAgentMessage and wait for it to write the doc + .done sentinel.\n * Requires the source conversation to be alive. Pollutes the source's\n * context. Uses whatever model/harness the source is running on.\n * - 'external' (default): spawn a separate authoring session with the chosen\n * model/harness that reads the source JSONL transcript and emits the doc.\n * Does not touch the source conversation. Works on ended sources.\n */\n handoffAuthor?: HandoffAuthor;\n /** Model for the external handoff authoring session. Ignored when handoffAuthor === 'source'. */\n handoffAuthorModel?: string;\n /** Harness for the external handoff authoring session. Ignored when handoffAuthor === 'source'. */\n handoffAuthorHarness?: RuntimeName;\n}\n\nexport interface SummaryForkResult {\n conversation: Conversation;\n sessionId: string;\n sessionFile: string;\n summary: string;\n summaryModel: string | null;\n forkMode: SummaryForkMode;\n handoffDocPath: string | null;\n forkFallbackReason: string | null;\n}\n\nconst FORK_WAIT_INSTRUCTION = `\\n---\\n\\n**Do not take any action.** This is context from a prior conversation fork. Acknowledge the summary and wait for the user's next instruction.`;\nconst DEFAULT_HANDOFF_TIMEOUT_MS = 300_000;\nconst DEFAULT_HANDOFF_POLL_INTERVAL_MS = 1_000;\nconst NO_HANDOFF_FOCUS = 'No specific focus was provided.';\n\nexport type HandoffDocValidation =\n | { ok: true }\n | { ok: false; reason: string };\n\nexport interface RequestHandoffOptions {\n timeoutMs?: number;\n pollIntervalMs?: number;\n now?: Date;\n}\n\nexport interface RequestHandoffResult {\n docPath: string;\n docText: string;\n}\n\nexport class HandoffStallError extends Error {\n constructor(\n public readonly docPath: string,\n public readonly sentinelPath: string,\n public readonly timeoutMs: number,\n ) {\n super(`Timed out waiting ${timeoutMs}ms for handoff document and sentinel: ${docPath}, ${sentinelPath}`);\n this.name = 'HandoffStallError';\n }\n}\n\nexport class HandoffValidationError extends Error {\n constructor(\n public readonly docPath: string,\n public readonly reason: string,\n ) {\n super(`Invalid handoff document ${docPath}: ${reason}`);\n this.name = 'HandoffValidationError';\n }\n}\n\n/**\n * Strip a wrapping ``` fenced code block from the doc body if the LLM\n * helpfully wrapped its Markdown output in a fence. Returns the inner\n * content if a fence was detected, otherwise returns the trimmed input.\n */\nexport function sanitizeHandoffDoc(text: string): string {\n const trimmed = text.trim();\n const fenceMatch = trimmed.match(/^```(?:markdown|md)?\\s*\\n([\\s\\S]*?)\\n```\\s*$/);\n return fenceMatch?.[1]?.trim() ?? trimmed;\n}\n\nexport function validateHandoffDoc(text: string): HandoffDocValidation {\n const sanitized = sanitizeHandoffDoc(text);\n if (sanitized.length < 200) {\n return { ok: false, reason: 'handoff document must be at least 200 characters' };\n }\n // Accept any heading depth (H1-H6), case-insensitive, with an optional\n // trailing colon. Real-world LLM outputs vary on heading conventions; the\n // failure mode of a too-strict validator is silent fallback to summary fork\n // with no surface to the user, which is the worst outcome.\n if (!/^#{1,6}\\s+suggested skills\\s*:?\\s*$/imu.test(sanitized)) {\n return { ok: false, reason: 'handoff document must contain a Suggested skills heading' };\n }\n return { ok: true };\n}\n\nfunction renderHandoffPrompt(template: string, focus: string | undefined, outputPath: string): string {\n const safeFocus = focus?.trim() || NO_HANDOFF_FOCUS;\n return template\n .split('{{focus}}').join(safeFocus)\n .split('{{outputPath}}').join(outputPath);\n}\n\nfunction delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nasync function waitForHandoffDoc(paths: HandoffPaths, timeoutMs: number, pollIntervalMs: number): Promise<string> {\n const deadline = Date.now() + timeoutMs;\n\n while (true) {\n try {\n await access(paths.docPath);\n await access(paths.sentinelPath);\n return await readFile(paths.docPath, 'utf-8');\n } catch {\n const remainingMs = deadline - Date.now();\n if (remainingMs <= 0) {\n throw new HandoffStallError(paths.docPath, paths.sentinelPath, timeoutMs);\n }\n await delay(Math.min(pollIntervalMs, remainingMs));\n }\n }\n}\n\nconst DEFAULT_HANDOFF_AUTHOR_MODEL = 'claude-sonnet-4-6';\nconst HANDOFF_AUTHOR_TIMEOUT_MS = 300_000;\n// When the raw transcript exceeds this many characters, pre-compact it via\n// generateSmartSummary (which chunks internally) before sending to the\n// handoff authoring model. Threshold chosen to keep the final prompt\n// comfortably under Haiku's 200k-token window (~800k chars at 4 chars/token,\n// minus headroom for the template, focus, and a response budget). The exact\n// value isn't critical — anything that triggers compaction for \"long\"\n// transcripts is fine.\nconst HANDOFF_TRANSCRIPT_COMPACT_THRESHOLD = 100_000;\n\nfunction renderExternalHandoffPrompt(template: string, focus: string | undefined, transcript: string, outputPath: string): string {\n const safeFocus = focus?.trim() || NO_HANDOFF_FOCUS;\n return template\n .split('{{focus}}').join(safeFocus)\n .split('{{outputPath}}').join(outputPath)\n .split('{{transcript}}').join(transcript);\n}\n\n/**\n * Author a handoff document from an external authoring session.\n *\n * Reads the source JSONL transcript and asks a fresh, isolated model session\n * (with the chosen model + harness) to write the handoff Markdown. The source\n * conversation is never contacted — its context is not polluted.\n *\n * Returns the same { docPath, docText } shape as requestHandoffFromAgent so\n * callers can substitute one for the other.\n */\nexport async function authorHandoffExternal(\n sourceConv: Conversation,\n sourceSessionFile: string,\n focus: string | undefined,\n model: string | undefined,\n harness: RuntimeName | undefined,\n options: { now?: Date } = {},\n): Promise<RequestHandoffResult> {\n await ensureHandoffsDir();\n const timestamp = (options.now ?? new Date()).toISOString();\n const paths = createHandoffPaths(sourceConv.name, timestamp);\n\n const effectiveModel = model ?? DEFAULT_HANDOFF_AUTHOR_MODEL;\n const effectiveHarness: RuntimeName = harness ?? 'claude-code';\n\n // The authoring harness decides the prompt template: Claude Code's `Write`\n // tool vs Pi's lowercase `write` tool, with harness-specific phrasing about\n // how the model should call it. Both templates share the same H2 contract,\n // and the file-on-disk read below is harness-independent.\n const templateName = effectiveHarness === 'ohmypi' ? 'handoff-external-pi.md' : 'handoff-external.md';\n const template = await readFile(join(packageRoot, 'roles', templateName), 'utf-8');\n\n // The source's harness decides how the transcript is read/serialized; the\n // authoring harness (model + harness picked by the user) is independent.\n const sourceAdapter = getTranscriptAdapter(sourceConv.harness ?? undefined);\n // Skip thinking blocks — they're large and the structured output we want\n // doesn't need internal reasoning, only the user/assistant exchange.\n const transcript = await sourceAdapter.serializeTranscript(sourceSessionFile, { includeThinking: false });\n\n // If the raw transcript is small enough, feed it to the model verbatim —\n // that's the richest input. For long transcripts we'd overflow any model's\n // context window in a single-pass prompt, so first compact via the chunked\n // generateSmartSummary path (which already handles chunking + merging) and\n // feed the compact summary to the handoff authoring step. The handoff\n // template treats {{transcript}} as opaque context — a compact summary\n // works as well as the raw transcript, just with less fine detail.\n let promptInput: string;\n let inputLabel: 'raw-transcript' | 'compact-summary';\n // Precompaction goes through the source adapter's compactSummary, which is\n // harness-agnostic: claude-code uses the entry-aware smart-compaction flow,\n // Pi (and future harnesses) serialize their transcript and run the generic\n // chunk-and-merge summarizer. Either way a long source transcript is\n // compacted before it reaches the authoring model's context window.\n if (transcript.length > HANDOFF_TRANSCRIPT_COMPACT_THRESHOLD) {\n console.log(`[claude-invoke] purpose=handoff-author-external | model=${effectiveModel} | harness=${effectiveHarness} | source=${sourceConv.name} | transcriptChars=${transcript.length} | precompacting=true`);\n const { config } = loadConfigSync();\n const richMode = config.conversations.richCompaction;\n const compact = await sourceAdapter.compactSummary(sourceSessionFile, {\n model: effectiveModel,\n richMode,\n includeThinking: false,\n harness: effectiveHarness,\n });\n promptInput = compact.summary;\n inputLabel = 'compact-summary';\n console.log(`[claude-invoke] purpose=handoff-author-external precompact-result | model=${effectiveModel} | compactChars=${compact.summary.length}`);\n } else {\n promptInput = transcript;\n inputLabel = 'raw-transcript';\n }\n\n const prompt = renderExternalHandoffPrompt(template, focus, promptInput, paths.docPath);\n console.log(`[claude-invoke] purpose=handoff-author-external | model=${effectiveModel} | harness=${effectiveHarness} | source=${sourceConv.name} | inputType=${inputLabel} | promptChars=${prompt.length} | outputPath=${paths.docPath}`);\n\n // The prompt tells the model to use its Write tool to author the document\n // directly at paths.docPath. We capture stdout only as a diagnostic log —\n // the source of truth for the doc is the file on disk. Writing via tool\n // avoids stdout preamble leaks like \"Here is the handoff document:\" that\n // contaminated earlier attempts.\n //\n // The authoring session is headless (`claude -p`), so the Write tool must be\n // explicitly allowlisted — otherwise `--permission-mode auto` raises a\n // permission prompt the session can never answer, the model emits \"I need\n // permission to write…\" to stdout, no file is written, and the whole fork\n // falls back to a plain summary with reason `handoff-validation` (PAN-1582).\n // Pi runs in rpc mode and auto-executes tools, so the allowlist is a no-op there.\n const stdout = await Effect.runPromise(runModelSummary(prompt, effectiveModel, HANDOFF_AUTHOR_TIMEOUT_MS, effectiveHarness, ['Write']));\n console.log(`[claude-invoke] purpose=handoff-author-external acknowledgement | model=${effectiveModel} | stdoutChars=${stdout.length} | stdoutPreview=${JSON.stringify(stdout.slice(0, 120))}`);\n\n let docText: string;\n try {\n docText = await readFile(paths.docPath, 'utf-8');\n } catch (err) {\n // The model didn't use Write — it almost certainly produced the doc on\n // stdout instead. Persist whatever we got for diagnosis, then surface\n // the failure as a validation error so the pipeline falls back to a\n // summary fork rather than spinning forever.\n const rejectedPath = `${paths.docPath}.rejected.md`;\n await writeFile(rejectedPath, stdout, 'utf-8').catch((wErr) => {\n console.warn(`[handoff-author-external] failed to persist stdout to ${rejectedPath}: ${wErr?.message ?? wErr}`);\n });\n console.warn(`[handoff-author-external] model did not call Write — stdout (${stdout.length} chars) saved to ${rejectedPath}: ${(err as { message?: string })?.message ?? err}`);\n throw new HandoffValidationError(paths.docPath, 'authoring session did not call its Write tool to create the handoff doc');\n }\n\n const validation = validateHandoffDoc(docText);\n if (!validation.ok) {\n // The file exists but its content failed the contract check. Persist\n // the rejected file for diagnosis and surface the reason.\n const rejectedPath = `${paths.docPath}.rejected.md`;\n await writeFile(rejectedPath, docText, 'utf-8').catch((err) => {\n console.warn(`[handoff-author-external] failed to persist rejected doc to ${rejectedPath}: ${err?.message ?? err}`);\n });\n console.warn(`[handoff-author-external] validation rejected file content (${validation.reason}); copy saved to ${rejectedPath}`);\n throw new HandoffValidationError(paths.docPath, validation.reason);\n }\n\n // Sanitize as a safety net in case the model wrapped the file in fences\n // despite the prompt telling it not to. Overwrite the file with the\n // cleaned text so downstream consumers see the same thing the validator\n // approved.\n const sanitized = sanitizeHandoffDoc(docText);\n if (sanitized !== docText) {\n await writeFile(paths.docPath, sanitized, 'utf-8');\n }\n await writeFile(paths.sentinelPath, '', 'utf-8');\n\n return { docPath: paths.docPath, docText: sanitized };\n}\n\nexport async function requestHandoffFromAgent(\n sourceConv: Conversation,\n focus?: string,\n options: RequestHandoffOptions = {},\n): Promise<RequestHandoffResult> {\n await ensureHandoffsDir();\n const timestamp = (options.now ?? new Date()).toISOString();\n const paths = createHandoffPaths(sourceConv.name, timestamp);\n const template = await readFile(join(packageRoot, 'roles', 'handoff.md'), 'utf-8');\n const prompt = renderHandoffPrompt(template, focus, paths.docPath);\n\n await deliverAgentMessage(sourceConv.tmuxSession, prompt, 'handoff-request');\n\n const docText = await waitForHandoffDoc(\n paths,\n options.timeoutMs ?? DEFAULT_HANDOFF_TIMEOUT_MS,\n options.pollIntervalMs ?? DEFAULT_HANDOFF_POLL_INTERVAL_MS,\n );\n const validation = validateHandoffDoc(docText);\n if (!validation.ok) {\n // The source agent already wrote the file to docPath. Move it aside to\n // .rejected.md so the next handoff attempt doesn't reuse a stale invalid\n // doc and so the operator can inspect what the agent wrote.\n const rejectedPath = `${paths.docPath}.rejected.md`;\n await writeFile(rejectedPath, docText, 'utf-8').catch((err) => {\n console.warn(`[handoff-source] failed to persist rejected output to ${rejectedPath}: ${err?.message ?? err}`);\n });\n console.warn(`[handoff-source] validation rejected source-authored doc (${validation.reason}); raw output saved to ${rejectedPath}`);\n throw new HandoffValidationError(paths.docPath, validation.reason);\n }\n\n return { docPath: paths.docPath, docText: sanitizeHandoffDoc(docText) };\n}\n\nfunction workspaceSourceFromCwd(sourceConv: Conversation): { issueId: string; workspacePath: string } | null {\n const normalizedCwd = sourceConv.cwd.replace(/\\\\/g, '/');\n const match = normalizedCwd.match(/^((?:(?:.*)\\/)?workspaces\\/feature-([a-z]+-\\d+))(?:\\/.*)?$/i);\n if (!match?.[1] || !match[2]) return null;\n return {\n issueId: sourceConv.issueId ?? match[2].toUpperCase(),\n workspacePath: match[1],\n };\n}\n\nexport async function handoffPreconditionFallbackReason(sourceConv: Conversation): Promise<string | null> {\n if (sourceConv.status === 'ended') return 'source-ended';\n\n const workspaceSource = workspaceSourceFromCwd(sourceConv);\n if (!workspaceSource) return null;\n\n try {\n await access(join(workspaceSource.workspacePath, '.devcontainer'));\n } catch {\n return null;\n }\n\n const health = await Effect.runPromise(getWorkspaceStackHealth(workspaceSource.issueId, {\n workspacePath: workspaceSource.workspacePath,\n }));\n return health.healthy ? 'source-workspace-devcontainer' : null;\n}\n\nexport function handoffFailureReason(error: unknown): string {\n if (error instanceof HandoffStallError) return 'handoff-timeout';\n if (error instanceof HandoffValidationError) return 'handoff-validation';\n return 'handoff-request-failed';\n}\n\nexport function logHandoffFallback(sourceConv: Conversation, reason: string): void {\n console.warn(`[summary-fork] handoff-fallback source=${sourceConv.name} reason=${reason}`);\n}\n\n/**\n * When a handoff falls back to a summary fork, the user's focus text would\n * otherwise be silently dropped. Prepend a small notice to the summary so the\n * successor conversation still sees what was asked, and so the user gets a\n * visible breadcrumb that the intended handoff failed.\n */\nexport function prependFallbackFocus(summary: string, focus: string | undefined, fallbackReason: string): string {\n const trimmedFocus = focus?.trim();\n if (!trimmedFocus) return summary;\n const header = [\n `**Note from Overdeck:** the intended handoff fell back to a summary fork (\\`${fallbackReason}\\`). The focus you requested is preserved below; the summary that follows is auto-generated, not an authored handoff document.`,\n '',\n '**Requested focus:**',\n '',\n `> ${trimmedFocus.split('\\n').join('\\n> ')}`,\n '',\n '---',\n '',\n ].join('\\n');\n return header + summary;\n}\n\nasync function generateSummarySeed(\n sourceSessionFile: string,\n summaryModel: string | undefined,\n localSummaryOnly: boolean | undefined,\n includeThinkingInSummary: boolean | undefined,\n summaryHarness?: RuntimeName,\n sourceHarness?: RuntimeName,\n): Promise<{ summary: string; summaryModel: string | null }> {\n if (localSummaryOnly) {\n return {\n summary: await Effect.runPromise(generateFallbackSummary(sourceSessionFile)),\n summaryModel: null,\n };\n }\n return generateSummaryForFork(sourceSessionFile, summaryModel, includeThinkingInSummary, summaryHarness, sourceHarness);\n}\n\nasync function generateFallbackSummaryPromise(jsonlPath: string): Promise<string> {\n const { readFile } = await import('node:fs/promises');\n const lines = (await readFile(jsonlPath, 'utf-8'))\n .split('\\n')\n .filter((l) => l.trim());\n\n const userMessages: string[] = [];\n const filesModified = new Set<string>();\n const toolsUsed = new Set<string>();\n\n for (const line of lines) {\n let entry: any;\n try {\n entry = JSON.parse(line);\n } catch {\n continue;\n }\n\n if (entry.type === 'user' && entry.message) {\n const content = entry.message.content;\n if (typeof content === 'string' && content.trim()) {\n if (!content.trim().startsWith('<local-command') && !content.trim().startsWith('<command-name')) {\n userMessages.push(content.trim());\n }\n } else if (Array.isArray(content)) {\n for (const block of content) {\n if (block.type === 'text' && block.text?.trim() && !block.text.trim().startsWith('<')) {\n userMessages.push(block.text.trim());\n }\n }\n }\n }\n\n if (entry.type === 'assistant' && entry.message?.content) {\n const content = entry.message.content;\n if (Array.isArray(content)) {\n for (const block of content) {\n if (block.type === 'tool_use') {\n toolsUsed.add(block.name);\n if (block.name === 'Edit' || block.name === 'Write') {\n const fp = block.input?.file_path || block.input?.path;\n if (fp) filesModified.add(fp);\n }\n }\n }\n }\n }\n }\n\n let summary = `## Conversation Summary Fork\\n\\n`;\n summary += `This is a continuation of a previous conversation, seeded with a summary of the earlier work.\\n\\n`;\n\n if (userMessages.length > 0) {\n summary += `### User Messages:\\n`;\n for (const msg of userMessages.slice(0, 10)) {\n summary += `- ${msg.slice(0, 200)}${msg.length > 200 ? '...' : ''}\\n`;\n }\n summary += '\\n';\n }\n\n if (filesModified.size > 0) {\n summary += `### Files Modified:\\n`;\n for (const f of [...filesModified].sort()) {\n summary += `- \\`${f.replace(/.*\\/overdeck\\//, '')}\\`\\n`;\n }\n summary += '\\n';\n }\n\n if (toolsUsed.size > 0) {\n summary += `### Tools Used: ${[...toolsUsed].sort().join(', ')}\\n\\n`;\n }\n\n summary += FORK_WAIT_INSTRUCTION;\n\n return summary;\n}\n\nexport async function generateSummaryForFork(\n jsonlPath: string,\n summaryModel?: string,\n includeThinkingInSummary?: boolean,\n summaryHarness: RuntimeName = 'claude-code',\n sourceHarness?: RuntimeName,\n): Promise<{ summary: string; summaryModel: string | null }> {\n if (!summaryModel) {\n // Fork summaries serialize the entire conversation in one shot. Sonnet 4.6's\n // 1M-token context handles large sessions that would overflow Haiku's 200k.\n summaryModel = 'claude-sonnet-4-6';\n }\n\n console.log(`[claude-invoke] purpose=summary-fork | model=${summaryModel} | summaryHarness=${summaryHarness} | sourceHarness=${sourceHarness ?? 'claude-code'} | source=summary-fork.ts:generateSummaryForFork | jsonl=${jsonlPath}`);\n\n const { config } = loadConfigSync();\n const richMode = config.conversations.richCompaction;\n\n // The source harness selects which transcript adapter parses/serializes the\n // JSONL (Pi and Claude Code have different shapes); the summary harness\n // selects which CLI backend runs the summarizer LLM. They are independent —\n // e.g. a Pi source can be summarized by a Claude model.\n const adapter = getTranscriptAdapter(sourceHarness);\n\n try {\n const result = await adapter.compactSummary(jsonlPath, {\n model: summaryModel,\n richMode,\n includeThinking: includeThinkingInSummary,\n harness: summaryHarness,\n });\n console.log(`[claude-invoke] SUCCESS purpose=summary-fork | model=${summaryModel} | outputChars=${result.summary.length}`);\n return { summary: result.summary + FORK_WAIT_INSTRUCTION, summaryModel };\n } catch (err: any) {\n console.error(`[claude-invoke] FAILED purpose=summary-fork | model=${summaryModel} | error=\"${err.message}\"`);\n throw err;\n }\n}async function reserveSummaryForkSessionPromise(\n cwd: string,\n): Promise<{ sessionId: string; sessionFile: string }> {\n // PAN-1862 (NFR-3): delegates to the shared session-fork primitive so the\n // conversation panel and the review convoy fork through one code path.\n const { reserveForkSession } = await import('./session-fork.js');\n return reserveForkSession(cwd);\n}\n\nasync function copySessionFromCompactBoundaryPromise(\n sourcePath: string,\n destPath: string,\n): Promise<void> {\n const { copySessionForFork } = await import('./session-fork.js');\n return copySessionForFork(sourcePath, destPath, { fullHistory: false });\n}\n\nasync function createSummaryForkPromise(\n conv: Conversation,\n options: SummaryForkOptions = {},\n): Promise<SummaryForkResult> {\n const sourceSessionFile = conv.claudeSessionId\n ? sessionFilePath(conv.cwd, conv.claudeSessionId)\n : null;\n if (!sourceSessionFile) {\n throw new Error(`No session file found for conversation ${conv.name}`);\n }\n\n const cwd = options.cwd || conv.cwd || process.cwd();\n const launchModel = options.model || conv.model;\n const summaryModel = options.model || conv.model;\n const forkMode = options.forkMode ?? 'summary';\n console.log(`[summary-fork] Forking conv=${conv.name} launchModel=${launchModel || 'default'} summaryModel=${summaryModel || 'default'} localOnly=${options.localSummaryOnly || false} forkMode=${forkMode}`);\n\n const { sessionId, sessionFile } = await Effect.runPromise(reserveSummaryForkSession(cwd));\n\n let summary: string;\n let usedSummaryModel: string | null;\n let effectiveForkMode = forkMode;\n let handoffDocPath: string | null = null;\n let forkFallbackReason: string | null = null;\n\n if (forkMode === 'plain') {\n // Plain fork: copy raw JSONL from last compact boundary (or full history)\n // into the new session file so Claude Code can --resume it directly.\n await Effect.runPromise(copySessionFromCompactBoundary(sourceSessionFile, sessionFile));\n summary = '';\n usedSummaryModel = null;\n } else if (forkMode === 'handoff') {\n const handoffAuthor: HandoffAuthor = options.handoffAuthor ?? 'external';\n if (handoffAuthor === 'external') {\n // External authoring: read the source JSONL and have a fresh authoring\n // session write the handoff doc. Source conversation is never touched.\n // Works on ended source conversations and lets the user pick the\n // authoring model/harness independently of the source.\n try {\n const handoff = await authorHandoffExternal(\n conv,\n sourceSessionFile,\n options.focus,\n options.handoffAuthorModel,\n options.handoffAuthorHarness,\n );\n summary = handoff.docText;\n usedSummaryModel = options.handoffAuthorModel ?? DEFAULT_HANDOFF_AUTHOR_MODEL;\n handoffDocPath = handoff.docPath;\n } catch (error) {\n forkFallbackReason = handoffFailureReason(error);\n effectiveForkMode = 'summary';\n logHandoffFallback(conv, forkFallbackReason);\n const result = await generateSummarySeed(sourceSessionFile, summaryModel ?? undefined, options.localSummaryOnly, options.includeThinkingInSummary, undefined, conv.harness ?? undefined);\n summary = prependFallbackFocus(result.summary, options.focus, forkFallbackReason);\n usedSummaryModel = result.summaryModel;\n }\n } else {\n // Source authoring (legacy): deliver the handoff prompt to the live\n // source agent and wait for it to write the doc + .done sentinel.\n // Requires the source conversation to be alive and pollutes its\n // context with the handoff turn pair.\n const preconditionFallback = await handoffPreconditionFallbackReason(conv);\n if (preconditionFallback) {\n forkFallbackReason = preconditionFallback;\n effectiveForkMode = 'summary';\n logHandoffFallback(conv, preconditionFallback);\n const result = await generateSummarySeed(sourceSessionFile, summaryModel ?? undefined, options.localSummaryOnly, options.includeThinkingInSummary, undefined, conv.harness ?? undefined);\n summary = prependFallbackFocus(result.summary, options.focus, preconditionFallback);\n usedSummaryModel = result.summaryModel;\n } else {\n try {\n const handoff = await requestHandoffFromAgent(conv, options.focus, {\n timeoutMs: options.handoffTimeoutMs,\n pollIntervalMs: options.handoffPollIntervalMs,\n });\n summary = handoff.docText;\n usedSummaryModel = null;\n handoffDocPath = handoff.docPath;\n } catch (error) {\n forkFallbackReason = handoffFailureReason(error);\n effectiveForkMode = 'summary';\n logHandoffFallback(conv, forkFallbackReason);\n const result = await generateSummarySeed(sourceSessionFile, summaryModel ?? undefined, options.localSummaryOnly, options.includeThinkingInSummary, undefined, conv.harness ?? undefined);\n summary = prependFallbackFocus(result.summary, options.focus, forkFallbackReason);\n usedSummaryModel = result.summaryModel;\n }\n }\n }\n } else {\n const result = await generateSummarySeed(sourceSessionFile, summaryModel ?? undefined, options.localSummaryOnly, options.includeThinkingInSummary, undefined, conv.harness ?? undefined);\n summary = result.summary;\n usedSummaryModel = result.summaryModel;\n }\n\n const timestamp = new Date().toISOString().slice(0, 10).replace(/-/g, '');\n const suffix = randomUUID().slice(0, 4);\n const newName = `${timestamp}-${suffix}`;\n const newTmux = `conv-${newName}`;\n\n let newConv = createConversation({\n name: newName,\n tmuxSession: newTmux,\n cwd,\n issueId: conv.issueId ?? undefined,\n title: effectiveForkMode === 'plain'\n ? `Fork: ${conv.title || conv.name}`\n : effectiveForkMode === 'handoff'\n ? `Handoff: ${conv.title || conv.name}`\n : `Summary Fork: ${conv.title || conv.name}`,\n titleSource: 'manual',\n titleSeed: effectiveForkMode === 'plain'\n ? `Fork of ${conv.name}`\n : effectiveForkMode === 'handoff'\n ? `Handoff of ${conv.name}`\n : `Summary Fork of ${conv.name}`,\n claudeSessionId: sessionId,\n model: launchModel ?? undefined,\n effort: conv.effort ?? undefined,\n harness: options.harness ?? conv.harness ?? undefined,\n });\n if (handoffDocPath) {\n newConv = recordConversationHandoff(conv.name, newConv.name, handoffDocPath);\n }\n if (forkFallbackReason) {\n updateConversationForkFallbackReason(newConv.name, forkFallbackReason);\n newConv = getConversationByName(newConv.name) ?? newConv;\n }\n\n return {\n conversation: newConv,\n sessionId,\n sessionFile,\n summary,\n summaryModel: usedSummaryModel,\n forkMode: effectiveForkMode,\n handoffDocPath,\n forkFallbackReason,\n };\n}\n\n// Re-export runModelSummary for any callers that need it directly\nexport { runModelSummary };\n\n// ─── Effect variants (PAN-1249, additive) ────────────────────────────────────\n//\n// Additive Effect surface for fork helpers. Failures from the underlying\n// fs ops or LLM calls surface as FsError (filesystem failures) or Error\n// (LLM / generation failures). The existing Promise functions remain\n// canonical; these are wrappers for Effect-native callers.\n\n/** Effect variant of generateFallbackSummary. */\nexport function generateFallbackSummary(\n jsonlPath: string,\n): Effect.Effect<string, FsError> {\n return Effect.tryPromise({\n try: () => generateFallbackSummaryPromise(jsonlPath),\n catch: (cause) =>\n new FsError({ path: jsonlPath, operation: 'fallback-summary', cause }),\n });\n}\n\n/** Effect variant of reserveSummaryForkSession. */\nexport function reserveSummaryForkSession(\n cwd: string,\n): Effect.Effect<{ sessionId: string; sessionFile: string }, FsError> {\n return Effect.tryPromise({\n try: () => reserveSummaryForkSessionPromise(cwd),\n catch: (cause) =>\n new FsError({ path: cwd, operation: 'reserve-session', cause }),\n });\n}\n\n/** Effect variant of copySessionFromCompactBoundary. */\nexport function copySessionFromCompactBoundary(\n sourcePath: string,\n destPath: string,\n): Effect.Effect<void, FsError> {\n return Effect.tryPromise({\n try: () => copySessionFromCompactBoundaryPromise(sourcePath, destPath),\n catch: (cause) =>\n new FsError({ path: sourcePath, operation: 'copy-session', cause }),\n });\n}\n\n/** Effect variant of createSummaryFork. */\nexport function createSummaryFork(\n conv: Conversation,\n options: SummaryForkOptions = {},\n): Effect.Effect<SummaryForkResult, FsError> {\n return Effect.tryPromise({\n try: () => createSummaryForkPromise(conv, options),\n catch: (cause) =>\n new FsError({ path: conv.name, operation: 'create-summary-fork', cause }),\n });\n}\n"],"mappings":"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"}
|
|
@@ -605,7 +605,7 @@ function pruneSpecialistRegistry(issueId) {
|
|
|
605
605
|
return Effect.tryPromise({
|
|
606
606
|
try: async () => {
|
|
607
607
|
const step = "teardown:specialist-registry";
|
|
608
|
-
const { pruneSpecialistRegistryEntriesForIssue } = await import("./specialists-
|
|
608
|
+
const { pruneSpecialistRegistryEntriesForIssue } = await import("./specialists-DeZx9xom.js");
|
|
609
609
|
const removed = pruneSpecialistRegistryEntriesForIssue(issueId);
|
|
610
610
|
return removed > 0 ? stepOk(step, [`Pruned ${removed} specialist registry entr${removed === 1 ? "y" : "ies"} for ${issueId}`]) : stepSkipped(step, [`No specialist registry entries for ${issueId}`]);
|
|
611
611
|
},
|
|
@@ -630,4 +630,4 @@ function pruneCheckpointRefs(projectPath, issueLower) {
|
|
|
630
630
|
//#endregion
|
|
631
631
|
export { teardownWorkspace as t };
|
|
632
632
|
|
|
633
|
-
//# sourceMappingURL=teardown-workspace-
|
|
633
|
+
//# sourceMappingURL=teardown-workspace-DhA6oAIK.js.map
|
package/dist/dashboard/{teardown-workspace-5LCVbjfq.js.map → teardown-workspace-DhA6oAIK.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"teardown-workspace-5LCVbjfq.js","names":[],"sources":["../../src/lib/lifecycle/teardown-workspace.ts"],"sourcesContent":["/**\n * teardown-workspace — Full workspace cleanup.\n *\n * Consolidates workspace teardown from close-out.ts and workspace-manager.ts.\n * Handles: tmux sessions, TLDR daemon, Docker containers, git worktrees,\n * agent state directories, and (optionally) git branches.\n *\n * The workspace-manager's removeWorkspace() handles additional project-specific\n * cleanup (DNS, tunnels, Hume, ports) that this module does not cover.\n * In Phase 2, removeWorkspace() will delegate to this module for the common steps.\n */\n\nimport { existsSync } from 'fs';\nimport { appendFile, readFile, rm, writeFile } from 'fs/promises';\nimport { join, basename, dirname } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from '../paths.js';\nimport { killSession, sessionExists, listSessionNames } from '../tmux.js';\nimport type { LifecycleContext, StepResult, TeardownOptions } from './types.js';\nimport { stepOk, stepSkipped, stepFailed } from './types.js';\nimport { findAllWorkspacePaths, findWorkspacePath } from './archive-planning.js';\nimport { getContainersReferencingWorkspacePath } from '../workspace-manager.js';\nimport { DEVCONTAINER_DIRNAME } from '../workspace/devcontainer-renderer.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * Kill tmux sessions associated with an issue.\n */\nfunction killTmuxSessions(issueLower: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => killTmuxSessionsImpl(issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:tmux-sessions', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function killTmuxSessionsImpl(issueLower: string): Promise<StepResult> {\n const step = 'teardown:tmux-sessions';\n let killed = 0;\n\n // Exact-match sessions (agent, test, merge, planning, strike).\n const exactPatterns = [\n `agent-${issueLower}`,\n `test-${issueLower}`,\n `merge-${issueLower}`,\n `planning-${issueLower}`,\n `strike-${issueLower}`,\n ];\n for (const session of exactPatterns) {\n if (await Effect.runPromise(sessionExists(session))) {\n try {\n await Effect.runPromise(killSession(session));\n killed++;\n } catch {\n // session may have died between check and kill\n }\n }\n }\n\n // Pattern-match sessions for review coordinators, review specialists, and\n // canonical specialists. Today's naming uses the UPPER issue ID (PAN-1024)\n // not the lower form, and includes session families that the prior\n // single-regex didn't cover. PAN-1024 close-out left\n // `review-coordinator-PAN-1024-...` and\n // `specialist-overdeck-PAN-1024-test-agent` alive (2026-05-09).\n //\n // Patterns we now match (case-insensitive on the issue ID):\n // - review-coordinator-<ISSUE>-<timestamp>\n // - review-<ISSUE>-<timestamp>-<role> (legacy)\n // - specialist-<projectKey>-<ISSUE>-<role> (canonical PAN-830/915)\n try {\n const allSessions = await Effect.runPromise(listSessionNames());\n const escapedLower = issueLower.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const escapedUpper = issueLower.toUpperCase().replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const issuePart = `(${escapedLower}|${escapedUpper})`;\n const patterns: RegExp[] = [\n new RegExp(`^review-coordinator-${issuePart}-\\\\d+`),\n new RegExp(`^review-${issuePart}-\\\\d+`),\n new RegExp(`^specialist-[^-]+(?:-[^-]+)*?-${issuePart}-`),\n ];\n const matchedSessions = allSessions.filter(s => patterns.some(p => p.test(s)));\n for (const session of matchedSessions) {\n try {\n await Effect.runPromise(killSession(session));\n killed++;\n } catch {\n // session may have died between check and kill\n }\n }\n } catch {\n // Session listing may fail if tmux server is not running\n }\n\n // NOTE: Per-project ephemeral specialists (specialist-{project}-{type}) are NOT killed here.\n // They belong to the project, not the issue, and accumulate context across issues via --resume.\n // Their grace period / idle timeout handles cleanup when no new work arrives.\n\n if (killed > 0) {\n return stepOk(step, [`Killed ${killed} tmux session(s)`]);\n }\n return stepSkipped(step, ['No tmux sessions found']);\n}\n\n/**\n * Stop TLDR daemon if workspace has a .venv.\n */\nfunction stopTldrDaemon(workspacePath: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => stopTldrDaemonImpl(workspacePath),\n catch: (err) => err,\n }).pipe(\n Effect.catch(() =>\n Effect.succeed(stepSkipped('teardown:tldr-daemon', ['TLDR daemon not running or failed to stop (non-fatal)'])),\n ),\n );\n}\n\nasync function stopTldrDaemonImpl(workspacePath: string): Promise<StepResult> {\n const step = 'teardown:tldr-daemon';\n const venvPath = join(workspacePath, '.venv');\n if (!existsSync(venvPath)) {\n return stepSkipped(step, ['No .venv found']);\n }\n try {\n const { getTldrDaemonServiceSync } = await import('../tldr-daemon.js');\n const tldrService = getTldrDaemonServiceSync(workspacePath, venvPath);\n await tldrService.stop();\n return stepOk(step, ['Stopped TLDR daemon']);\n } catch {\n return stepSkipped(step, ['TLDR daemon not running or failed to stop (non-fatal)']);\n }\n}\n\n/**\n * Stop Docker containers for the workspace.\n */\nfunction stopDocker(\n workspacePath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => stopDockerImpl(workspacePath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch(() =>\n Effect.succeed(stepSkipped('teardown:docker', ['Docker cleanup skipped (not running or failed)'])),\n ),\n );\n}\n\nasync function stopDockerImpl(\n workspacePath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:docker';\n try {\n const { stopWorkspaceDocker } = await import('../workspace-manager.js');\n await Effect.runPromise(stopWorkspaceDocker(workspacePath, issueLower));\n return stepOk(step, ['Stopped Docker containers']);\n } catch {\n return stepSkipped(step, ['Docker cleanup skipped (not running or failed)']);\n }\n}\n\n/**\n * Detect whether a PID belongs to a process running inside a Docker container.\n * Container processes appear in host lsof output when paths are bind-mounted.\n */\nasync function isDockerContainerProcess(pid: string): Promise<boolean> {\n try {\n const cgroup = await readFile(`/proc/${pid}/cgroup`, 'utf-8');\n return cgroup.includes('/docker-') || cgroup.includes('/docker/');\n } catch {\n return false;\n }\n}\n\n/**\n * Kill orphaned host processes for a workspace.\n */\nfunction killOrphanedProcesses(workspacePath: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => killOrphanedProcessesImpl(workspacePath),\n catch: (err) => err,\n }).pipe(\n Effect.catch(() =>\n Effect.succeed(stepSkipped('teardown:orphaned-processes', ['Orphaned process cleanup failed (non-fatal)'])),\n ),\n );\n}\n\nasync function killOrphanedProcessesImpl(workspacePath: string): Promise<StepResult> {\n const step = 'teardown:orphaned-processes';\n try {\n // Find PIDs with cwd matching the workspace path\n const { stdout } = await execAsync(\n `lsof +D \"${workspacePath}\" -t 2>/dev/null || true`,\n { encoding: 'utf-8', timeout: 10000 },\n );\n const pids = stdout.trim().split('\\n').filter(Boolean).map(p => p.trim()).filter(p => /^\\d+$/.test(p));\n\n if (pids.length === 0) {\n return stepSkipped(step, ['No orphaned processes found']);\n }\n\n // Don't kill our own process or the dashboard\n const myPid = String(process.pid);\n const safePids = pids.filter(p => p !== myPid);\n\n if (safePids.length === 0) {\n return stepSkipped(step, ['No orphaned processes to kill']);\n }\n\n // Filter out Docker container processes — they appear in host lsof due to bind mounts\n const hostPids: string[] = [];\n for (const pid of safePids) {\n if (!(await isDockerContainerProcess(pid))) {\n hostPids.push(pid);\n }\n }\n\n if (hostPids.length === 0) {\n return stepSkipped(step, ['No orphaned host processes to kill (all were container processes)']);\n }\n\n await execAsync(`kill ${hostPids.join(' ')} 2>/dev/null || true`, { encoding: 'utf-8', timeout: 5000 });\n return stepOk(step, [`Killed ${hostPids.length} orphaned process(es)`]);\n } catch {\n return stepSkipped(step, ['Orphaned process cleanup failed (non-fatal)']);\n }\n}\n\n/**\n * Sync workspace beads to the project-root beads database before workspace deletion.\n */\nfunction syncWorkspaceBeads(\n projectPath: string,\n workspacePath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => syncWorkspaceBeadsImpl(projectPath, workspacePath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:sync-beads', `Failed to sync workspace beads: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function syncWorkspaceBeadsImpl(\n projectPath: string,\n workspacePath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:sync-beads';\n const workspaceBeadsDir = join(workspacePath, '.beads');\n\n if (!existsSync(workspaceBeadsDir)) {\n return stepSkipped(step, ['No .beads directory in workspace']);\n }\n\n try {\n // Export workspace beads to JSONL\n // 120s: bd export cold-starts a dolt server per workspace .beads and can\n // block on the global bd lock; a timeout kill bypasses `|| true` and\n // aborts the whole close-out ceremony (observed at 15s, 14/32 closes).\n await execAsync(\n 'bd export --output .beads/issues-export.jsonl 2>&1 || true',\n { cwd: workspacePath, encoding: 'utf-8', timeout: 120000 }\n );\n\n const exportPath = join(workspacePath, '.beads', 'issues-export.jsonl');\n if (!existsSync(exportPath)) {\n await execAsync('bd export --output .beads/issues.jsonl 2>&1 || true', { cwd: workspacePath, encoding: 'utf-8', timeout: 120000 });\n }\n\n // Import workspace beads into project-root database\n // Use bd import if available, otherwise copy JSONL entries\n try {\n await execAsync(\n `bd import \"${join(workspacePath, '.beads', 'issues.jsonl')}\" 2>&1 || true`,\n { cwd: projectPath, encoding: 'utf-8', timeout: 120000 }\n );\n return stepOk(step, [`Synced workspace beads to project root for ${issueLower}`]);\n } catch {\n // bd import may not exist — try manual JSONL merge\n const wsJsonl = join(workspacePath, '.beads', 'issues.jsonl');\n const projJsonl = join(projectPath, '.beads', 'issues.jsonl');\n\n if (existsSync(wsJsonl) && existsSync(projJsonl)) {\n const wsContent = await readFile(wsJsonl, 'utf-8');\n const issuePattern = issueLower.replace('-', '[-_]');\n const relevantLines = wsContent.split('\\n').filter(\n line => line.trim() && new RegExp(issuePattern, 'i').test(line)\n );\n if (relevantLines.length > 0) {\n await appendFile(projJsonl, '\\n' + relevantLines.join('\\n'));\n return stepOk(step, [`Appended ${relevantLines.length} beads entries for ${issueLower} to project JSONL`]);\n }\n }\n return stepSkipped(step, ['No beads to sync or import not available']);\n }\n } catch (err) {\n return stepFailed(step, `Failed to sync workspace beads: ${(err as Error).message}`);\n }\n}\n\n/**\n * Clear beads for this issue from the project-root .beads/issues.jsonl.\n */\nfunction clearProjectBeads(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => clearProjectBeadsImpl(projectPath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:clear-beads', `Failed to clear beads: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function clearProjectBeadsImpl(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:clear-beads';\n const projJsonl = join(projectPath, '.beads', 'issues.jsonl');\n\n if (!existsSync(projJsonl)) {\n return stepSkipped(step, ['No .beads/issues.jsonl in project root']);\n }\n\n try {\n const content = await readFile(projJsonl, 'utf-8');\n const lines = content.split('\\n');\n const issueUpper = issueLower.toUpperCase();\n const before = lines.length;\n // Remove lines that reference this issue (by ID in the title or issue field)\n const filtered = lines.filter(line => {\n if (!line.trim()) return true; // keep blank lines\n try {\n const entry = JSON.parse(line);\n const title = (entry.title || '').toUpperCase();\n const issue = (entry.issue || '').toUpperCase();\n return !title.includes(issueUpper) && issue !== issueUpper;\n } catch {\n return true; // keep unparseable lines\n }\n });\n const removed = before - filtered.length;\n if (removed > 0) {\n await writeFile(projJsonl, filtered.join('\\n'));\n return stepOk(step, [`Removed ${removed} beads entries for ${issueLower} from project JSONL`]);\n }\n return stepSkipped(step, [`No beads entries found for ${issueLower}`]);\n } catch (err) {\n return stepFailed(step, `Failed to clear beads: ${(err as Error).message}`);\n }\n}\n\n/**\n * Remove git worktree for the workspace.\n */\nfunction removeWorktree(\n projectPath: string,\n workspacePath: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => removeWorktreeImpl(projectPath, workspacePath),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:worktree', `Failed to remove workspace: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function removeWorktreeImpl(\n projectPath: string,\n workspacePath: string,\n): Promise<StepResult> {\n const step = 'teardown:worktree';\n if (!existsSync(workspacePath)) {\n return stepSkipped(step, ['Workspace directory does not exist']);\n }\n\n // Guard: never delete workspace (and its `.devcontainer/`) while containers\n // still reference compose paths inside it.\n const orphanedContainers = await Effect.runPromise(getContainersReferencingWorkspacePath(workspacePath));\n if (orphanedContainers.length > 0) {\n return stepFailed(\n step,\n `Cannot remove workspace: ${orphanedContainers.length} Docker container(s) still reference compose paths in ${DEVCONTAINER_DIRNAME}/. ` +\n `Run workspace Docker cleanup first or stop the containers manually.`,\n );\n }\n\n try {\n await execAsync(`git worktree remove \"${workspacePath}\" --force`, { cwd: projectPath });\n return stepOk(step, ['Removed git worktree']);\n } catch {\n // worktree remove failed — try direct removal\n try {\n await rm(workspacePath, { recursive: true, force: true });\n return stepOk(step, ['Removed workspace directory after worktree removal failed']);\n } catch (err) {\n return stepFailed(step, `Failed to remove workspace: ${(err as Error).message}`);\n }\n }\n}\n\n/**\n * Remove every agent state directory tied to an issue.\n */\nfunction removeAgentState(issueLower: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => removeAgentStateImpl(issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:agent-state', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function removeAgentStateImpl(issueLower: string): Promise<StepResult> {\n const step = 'teardown:agent-state';\n const { readdir, rm } = await import('fs/promises');\n\n let entries: string[];\n try {\n entries = await readdir(AGENTS_DIR);\n } catch {\n return stepSkipped(step, ['Agents directory not present']);\n }\n\n const work = `agent-${issueLower}`;\n const planner = `planning-${issueLower}`;\n const strike = `strike-${issueLower}`;\n const specialistPrefix = `agent-${issueLower}-`;\n const targets = entries.filter(name =>\n name === work || name === planner || name === strike || name.startsWith(specialistPrefix),\n );\n\n let removed = 0;\n for (const name of targets) {\n try {\n await rm(join(AGENTS_DIR, name), { recursive: true, force: true });\n removed++;\n } catch { /* non-fatal */ }\n }\n\n if (removed > 0) {\n return stepOk(step, [`Removed ${removed} agent state director${removed === 1 ? 'y' : 'ies'}`]);\n }\n return stepSkipped(step, ['No agent state directories found']);\n}\n\n/**\n * Delete feature branches (local + remote).\n */\nfunction deleteBranches(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => deleteBranchesImpl(projectPath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:branches', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function deleteBranchesImpl(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:branches';\n const branchName = `feature/${issueLower}`;\n const details: string[] = [];\n const localSlots = await execAsync(`git for-each-ref --format=\"%(refname:short)\" \"refs/heads/feature/${issueLower}-slot-*\"`, { cwd: projectPath, encoding: 'utf-8' })\n .then(({ stdout }) => stdout.trim().split('\\n').filter(Boolean)).catch(() => [] as string[]);\n const remoteSlots = await execAsync(`git ls-remote --heads origin \"feature/${issueLower}-slot-*\"`, { cwd: projectPath, encoding: 'utf-8' })\n .then(({ stdout }) => stdout.trim().split('\\n').map(line => line.split('refs/heads/')[1]).filter((name): name is string => Boolean(name))).catch(() => [] as string[]);\n\n // Delete local branch\n try {\n await execAsync(`git branch -D \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted local branch ${branchName}`);\n } catch {\n details.push(`Local branch ${branchName} not found (already deleted)`);\n }\n\n for (const slotBranch of localSlots) {\n try {\n await execAsync(`git branch -D ${JSON.stringify(slotBranch)}`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted local branch ${slotBranch}`);\n } catch { details.push(`Failed to delete local branch ${slotBranch}`); }\n }\n\n // Delete remote branch\n try {\n await execAsync(`git push origin --delete \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted remote branch ${branchName}`);\n } catch {\n details.push(`Remote branch ${branchName} not found (already deleted)`);\n }\n\n for (const slotBranch of remoteSlots) {\n try {\n await execAsync(`git push origin --delete ${JSON.stringify(slotBranch)}`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted remote branch ${slotBranch}`);\n } catch { details.push(`Failed to delete remote branch ${slotBranch}`); }\n }\n\n return stepOk(step, details);\n}\n\n/**\n * Whether a local branch exists and is fully merged into main.\n */\nasync function localBranchMergedState(\n projectPath: string,\n branchName: string,\n): Promise<{ exists: boolean; merged: boolean }> {\n try {\n await execAsync(`git show-ref --verify --quiet \"refs/heads/${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n } catch {\n return { exists: false, merged: false };\n }\n try {\n await execAsync(`git merge-base --is-ancestor \"${branchName}\" main`, { cwd: projectPath, encoding: 'utf-8' });\n return { exists: true, merged: true };\n } catch {\n return { exists: true, merged: false };\n }\n}\n\n/**\n * Delete the strike branch (local + remote) once it is merged into main.\n *\n * Strike work (`pan strike`) lands on main directly, so a merged\n * `strike/<issue>` branch is pure residue — it is deleted on every teardown,\n * not just when `deleteBranches` is requested (PAN-1721). An UNMERGED strike\n * branch may hold the only copy of an aborted strike's work, so it is\n * preserved and reported instead.\n */\nfunction deleteStrikeBranch(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => deleteStrikeBranchImpl(projectPath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:strike-branch', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function deleteStrikeBranchImpl(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:strike-branch';\n const branchName = `strike/${issueLower}`;\n const details: string[] = [];\n\n // Local branch\n const local = await localBranchMergedState(projectPath, branchName);\n if (local.exists) {\n if (local.merged) {\n try {\n await execAsync(`git branch -D \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted local branch ${branchName}`);\n } catch (err) {\n details.push(`Failed to delete local branch ${branchName}: ${(err as Error).message}`);\n }\n } else {\n details.push(`Preserved local branch ${branchName} — not merged to main`);\n }\n }\n\n // Remote branch\n try {\n const { stdout: remoteRef } = await execAsync(\n `git ls-remote --heads origin \"${branchName}\" 2>/dev/null || true`,\n { cwd: projectPath, encoding: 'utf-8' },\n );\n if (remoteRef.trim()) {\n let remoteMerged = false;\n try {\n await execAsync(`git fetch origin \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n await execAsync('git merge-base --is-ancestor FETCH_HEAD main', { cwd: projectPath, encoding: 'utf-8' });\n remoteMerged = true;\n } catch {\n remoteMerged = false;\n }\n if (remoteMerged) {\n await execAsync(`git push origin --delete \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted remote branch ${branchName}`);\n } else {\n details.push(`Preserved remote branch ${branchName} — not merged to main`);\n }\n }\n } catch {\n // remote check/delete failed — non-fatal\n }\n\n if (details.length === 0) {\n return stepSkipped(step, [`No strike branch ${branchName} found`]);\n }\n return details.some(d => d.startsWith('Failed'))\n ? stepFailed(step, details.join('; '))\n : stepOk(step, details);\n}\n\n/**\n * Remove leftover strike worktrees (`workspaces/feature-<issue>-strike`).\n *\n * Runs when the regular worktree removal is gated off (remove_workspace=false\n * at close-out): a strike worktree has no post-close purpose once the strike\n * branch is merged or gone, and leaving it keeps the closed issue alive in\n * resource discovery (PAN-1721). Worktrees whose strike branch has unmerged\n * commits are preserved alongside the branch.\n */\nfunction removeStrikeWorktrees(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => removeStrikeWorktreesImpl(projectPath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:strike-worktree', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function removeStrikeWorktreesImpl(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:strike-worktree';\n const strikePaths = findAllWorkspacePaths(projectPath, issueLower).filter(p => p.endsWith('-strike'));\n if (strikePaths.length === 0) {\n return stepSkipped(step, ['No strike worktree found']);\n }\n\n const branch = await localBranchMergedState(projectPath, `strike/${issueLower}`);\n if (branch.exists && !branch.merged) {\n return stepSkipped(step, [`Preserved strike worktree — strike/${issueLower} not merged to main`]);\n }\n\n const details: string[] = [];\n for (const p of strikePaths) {\n const result = await removeWorktreeImpl(projectPath, p);\n details.push(...(result.details ?? [result.error ?? result.step]));\n if (!result.success && !result.skipped) {\n return stepFailed(step, `Failed to remove strike worktree ${p}: ${result.error ?? 'unknown error'}`);\n }\n }\n return stepOk(step, details);\n}\n\n/**\n * Clear shadow state for an issue.\n */\nfunction clearShadowState(issueId: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => clearShadowStateImpl(issueId),\n catch: (err) => err,\n }).pipe(\n Effect.catch(() =>\n Effect.succeed(stepSkipped('teardown:shadow-state', ['Shadow state cleanup skipped (non-fatal)'])),\n ),\n );\n}\n\nasync function clearShadowStateImpl(issueId: string): Promise<StepResult> {\n const step = 'teardown:shadow-state';\n try {\n const { removeShadowState } = await import('../shadow-state.js');\n const result = removeShadowState(issueId);\n if (result.success) {\n return stepOk(step, [`Cleared shadow state for ${issueId}`]);\n }\n return stepSkipped(step, ['No shadow state found']);\n } catch {\n return stepSkipped(step, ['Shadow state cleanup skipped (non-fatal)']);\n }\n}\n\n/**\n * Remove legacy .planning/<issue>/ directory from project root.\n */\nfunction clearLegacyPlanningDir(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const step = 'teardown:legacy-planning-dir';\n const legacyDir = join(projectPath, '.planning', issueLower);\n if (!existsSync(legacyDir)) {\n return stepSkipped(step, ['No legacy planning directory found']);\n }\n await rm(legacyDir, { recursive: true, force: true });\n return stepOk(step, [`Deleted legacy planning dir: ${legacyDir}`]);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:legacy-planning-dir', `Failed to delete legacy planning dir: ${(err as Error).message}`)),\n ),\n );\n}\n\n/**\n * Build template placeholders for project-specific cleanup (tunnel, Hume).\n */\nfunction buildPlaceholders(\n ctx: LifecycleContext,\n opts: TeardownOptions,\n workspacePath: string,\n) {\n const issueLower = ctx.issueId.toLowerCase();\n const featureFolder = `feature-${issueLower}`;\n const projName = opts.projectName || ctx.projectName || basename(ctx.projectPath);\n const domain = opts.workspaceConfig?.dns?.domain || 'localhost';\n return {\n FEATURE_NAME: issueLower,\n FEATURE_FOLDER: featureFolder,\n BRANCH_NAME: `feature/${issueLower}`,\n COMPOSE_PROJECT: `${projName}-${featureFolder}`,\n DOMAIN: domain,\n PROJECT_NAME: projName,\n PROJECT_PATH: ctx.projectPath,\n WORKSPACE_PATH: workspacePath,\n };\n}\n\n/**\n * Remove Cloudflare tunnel ingress for workspace.\n */\nfunction removeTunnelConfig(\n tunnelConfig: any,\n placeholders: Record<string, string>,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const { removeTunnelIngress } = await import('../tunnel.js');\n const result = await Effect.runPromise(removeTunnelIngress(tunnelConfig, placeholders as any));\n return stepOk('teardown:tunnel', result.steps || ['Removed tunnel ingress']);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('teardown:tunnel', [`Tunnel cleanup warning: ${(err as Error).message}`])),\n ),\n );\n}\n\n/**\n * Remove Hume EVI config for workspace.\n */\nfunction removeHumeEviConfig(\n humeConfig: any,\n placeholders: Record<string, string>,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const { deleteHumeConfig } = await import('../hume.js');\n const result = await Effect.runPromise(deleteHumeConfig(humeConfig, placeholders as any));\n return stepOk('teardown:hume', result.steps || ['Removed Hume EVI config']);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('teardown:hume', [`Hume cleanup warning: ${(err as Error).message}`])),\n ),\n );\n}\n\n/**\n * Full workspace teardown.\n */\nexport function teardownWorkspace(\n ctx: LifecycleContext,\n opts: TeardownOptions = {},\n): Effect.Effect<StepResult[]> {\n return Effect.gen(function* () {\n const issueLower = ctx.issueId.toLowerCase();\n const workspacePath = findWorkspacePath(ctx.projectPath, issueLower);\n const shouldDeleteWorkspace = opts.deleteWorkspace !== false; // default true\n const results: StepResult[] = [];\n\n // 1. Kill tmux sessions\n results.push(yield* killTmuxSessions(issueLower));\n\n // 2. Clear shadow state (always runs)\n results.push(yield* clearShadowState(ctx.issueId));\n\n // 3. Clear legacy planning directory (always runs)\n results.push(yield* clearLegacyPlanningDir(ctx.projectPath, issueLower));\n\n // 4-9: Workspace-specific cleanup\n if (workspacePath && existsSync(workspacePath)) {\n // 4. Stop TLDR daemon (only if deleting workspace)\n if (shouldDeleteWorkspace) {\n results.push(yield* stopTldrDaemon(workspacePath));\n }\n\n // 5. Stop Docker containers (only if deleting workspace)\n if (shouldDeleteWorkspace && !opts.skipDocker) {\n results.push(yield* stopDocker(workspacePath, issueLower));\n }\n\n // 5b. Kill orphaned host processes (Vite, node) that survive Docker teardown\n if (shouldDeleteWorkspace) {\n results.push(yield* killOrphanedProcesses(workspacePath));\n }\n\n // 6. Beads lifecycle: sync or clear depending on context (PAN-412)\n if (opts.clearBeads) {\n results.push(yield* clearProjectBeads(ctx.projectPath, issueLower));\n } else if (shouldDeleteWorkspace) {\n results.push(yield* syncWorkspaceBeads(ctx.projectPath, workspacePath, issueLower));\n }\n\n // 7-8: Project-specific cleanup (tunnel, Hume)\n if (shouldDeleteWorkspace && (opts.workspaceConfig?.tunnel || opts.workspaceConfig?.hume)) {\n const placeholders = buildPlaceholders(ctx, opts, workspacePath);\n\n if (opts.workspaceConfig.tunnel) {\n results.push(yield* removeTunnelConfig(opts.workspaceConfig.tunnel, placeholders));\n }\n if (opts.workspaceConfig.hume) {\n results.push(yield* removeHumeEviConfig(opts.workspaceConfig.hume, placeholders));\n }\n }\n\n // 9. Remove worktree + workspace directory (only if deleting workspace).\n if (shouldDeleteWorkspace) {\n const allPaths = findAllWorkspacePaths(ctx.projectPath, issueLower);\n for (const p of allPaths) {\n results.push(yield* removeWorktree(ctx.projectPath, p));\n }\n }\n } else {\n results.push(stepSkipped('teardown:workspace', ['No workspace found to clean up']));\n }\n\n // 9b. Strike worktrees are residue once the strike branch is merged —\n // remove them even when deleteWorkspace is gated off (PAN-1721). When\n // shouldDeleteWorkspace is true the loop above already removed them via\n // findAllWorkspacePaths.\n if (!shouldDeleteWorkspace) {\n results.push(yield* removeStrikeWorktrees(ctx.projectPath, issueLower));\n }\n\n // 10. Remove agent state\n results.push(yield* removeAgentState(issueLower));\n\n // 11. Delete branches. Feature branches only if explicitly requested;\n // the strike branch is always a candidate once merged to main (PAN-1721).\n results.push(yield* deleteStrikeBranch(ctx.projectPath, issueLower));\n if (opts.deleteBranches) {\n results.push(yield* deleteBranches(ctx.projectPath, issueLower));\n }\n\n // 12. Prune checkpoint refs for this issue's agents.\n results.push(yield* pruneCheckpointRefs(ctx.projectPath, issueLower));\n\n // 13. Prune specialist registry entries for this issue.\n results.push(yield* pruneSpecialistRegistry(ctx.issueId));\n\n return results;\n });\n}\n\nfunction pruneSpecialistRegistry(issueId: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const step = 'teardown:specialist-registry';\n const { pruneSpecialistRegistryEntriesForIssue } = await import('../cloister/specialists.js');\n const removed = pruneSpecialistRegistryEntriesForIssue(issueId);\n return removed > 0\n ? stepOk(step, [`Pruned ${removed} specialist registry entr${removed === 1 ? 'y' : 'ies'} for ${issueId}`])\n : stepSkipped(step, [`No specialist registry entries for ${issueId}`]);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('teardown:specialist-registry', [`Specialist registry prune failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nfunction pruneCheckpointRefs(projectPath: string, issueLower: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const step = 'teardown:checkpoint-refs';\n const { pruneCheckpointRefsForAgents } = await import('../checkpoint/checkpoint-manager.js');\n const agentIds = [`agent-${issueLower}`, `planning-${issueLower}`, `strike-${issueLower}`];\n const pruned = await Effect.runPromise(pruneCheckpointRefsForAgents(projectPath, agentIds));\n return stepOk(step, [`Pruned ${pruned} checkpoint ref(s) for ${agentIds.join(', ')}`]);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:checkpoint-refs', `Checkpoint prune failed: ${(err as Error).message}`)),\n ),\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"teardown-workspace-DhA6oAIK.js","names":[],"sources":["../../src/lib/lifecycle/teardown-workspace.ts"],"sourcesContent":["/**\n * teardown-workspace — Full workspace cleanup.\n *\n * Consolidates workspace teardown from close-out.ts and workspace-manager.ts.\n * Handles: tmux sessions, TLDR daemon, Docker containers, git worktrees,\n * agent state directories, and (optionally) git branches.\n *\n * The workspace-manager's removeWorkspace() handles additional project-specific\n * cleanup (DNS, tunnels, Hume, ports) that this module does not cover.\n * In Phase 2, removeWorkspace() will delegate to this module for the common steps.\n */\n\nimport { existsSync } from 'fs';\nimport { appendFile, readFile, rm, writeFile } from 'fs/promises';\nimport { join, basename, dirname } from 'path';\nimport { homedir } from 'os';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { Effect } from 'effect';\nimport { AGENTS_DIR } from '../paths.js';\nimport { killSession, sessionExists, listSessionNames } from '../tmux.js';\nimport type { LifecycleContext, StepResult, TeardownOptions } from './types.js';\nimport { stepOk, stepSkipped, stepFailed } from './types.js';\nimport { findAllWorkspacePaths, findWorkspacePath } from './archive-planning.js';\nimport { getContainersReferencingWorkspacePath } from '../workspace-manager.js';\nimport { DEVCONTAINER_DIRNAME } from '../workspace/devcontainer-renderer.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * Kill tmux sessions associated with an issue.\n */\nfunction killTmuxSessions(issueLower: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => killTmuxSessionsImpl(issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:tmux-sessions', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function killTmuxSessionsImpl(issueLower: string): Promise<StepResult> {\n const step = 'teardown:tmux-sessions';\n let killed = 0;\n\n // Exact-match sessions (agent, test, merge, planning, strike).\n const exactPatterns = [\n `agent-${issueLower}`,\n `test-${issueLower}`,\n `merge-${issueLower}`,\n `planning-${issueLower}`,\n `strike-${issueLower}`,\n ];\n for (const session of exactPatterns) {\n if (await Effect.runPromise(sessionExists(session))) {\n try {\n await Effect.runPromise(killSession(session));\n killed++;\n } catch {\n // session may have died between check and kill\n }\n }\n }\n\n // Pattern-match sessions for review coordinators, review specialists, and\n // canonical specialists. Today's naming uses the UPPER issue ID (PAN-1024)\n // not the lower form, and includes session families that the prior\n // single-regex didn't cover. PAN-1024 close-out left\n // `review-coordinator-PAN-1024-...` and\n // `specialist-overdeck-PAN-1024-test-agent` alive (2026-05-09).\n //\n // Patterns we now match (case-insensitive on the issue ID):\n // - review-coordinator-<ISSUE>-<timestamp>\n // - review-<ISSUE>-<timestamp>-<role> (legacy)\n // - specialist-<projectKey>-<ISSUE>-<role> (canonical PAN-830/915)\n try {\n const allSessions = await Effect.runPromise(listSessionNames());\n const escapedLower = issueLower.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const escapedUpper = issueLower.toUpperCase().replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const issuePart = `(${escapedLower}|${escapedUpper})`;\n const patterns: RegExp[] = [\n new RegExp(`^review-coordinator-${issuePart}-\\\\d+`),\n new RegExp(`^review-${issuePart}-\\\\d+`),\n new RegExp(`^specialist-[^-]+(?:-[^-]+)*?-${issuePart}-`),\n ];\n const matchedSessions = allSessions.filter(s => patterns.some(p => p.test(s)));\n for (const session of matchedSessions) {\n try {\n await Effect.runPromise(killSession(session));\n killed++;\n } catch {\n // session may have died between check and kill\n }\n }\n } catch {\n // Session listing may fail if tmux server is not running\n }\n\n // NOTE: Per-project ephemeral specialists (specialist-{project}-{type}) are NOT killed here.\n // They belong to the project, not the issue, and accumulate context across issues via --resume.\n // Their grace period / idle timeout handles cleanup when no new work arrives.\n\n if (killed > 0) {\n return stepOk(step, [`Killed ${killed} tmux session(s)`]);\n }\n return stepSkipped(step, ['No tmux sessions found']);\n}\n\n/**\n * Stop TLDR daemon if workspace has a .venv.\n */\nfunction stopTldrDaemon(workspacePath: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => stopTldrDaemonImpl(workspacePath),\n catch: (err) => err,\n }).pipe(\n Effect.catch(() =>\n Effect.succeed(stepSkipped('teardown:tldr-daemon', ['TLDR daemon not running or failed to stop (non-fatal)'])),\n ),\n );\n}\n\nasync function stopTldrDaemonImpl(workspacePath: string): Promise<StepResult> {\n const step = 'teardown:tldr-daemon';\n const venvPath = join(workspacePath, '.venv');\n if (!existsSync(venvPath)) {\n return stepSkipped(step, ['No .venv found']);\n }\n try {\n const { getTldrDaemonServiceSync } = await import('../tldr-daemon.js');\n const tldrService = getTldrDaemonServiceSync(workspacePath, venvPath);\n await tldrService.stop();\n return stepOk(step, ['Stopped TLDR daemon']);\n } catch {\n return stepSkipped(step, ['TLDR daemon not running or failed to stop (non-fatal)']);\n }\n}\n\n/**\n * Stop Docker containers for the workspace.\n */\nfunction stopDocker(\n workspacePath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => stopDockerImpl(workspacePath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch(() =>\n Effect.succeed(stepSkipped('teardown:docker', ['Docker cleanup skipped (not running or failed)'])),\n ),\n );\n}\n\nasync function stopDockerImpl(\n workspacePath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:docker';\n try {\n const { stopWorkspaceDocker } = await import('../workspace-manager.js');\n await Effect.runPromise(stopWorkspaceDocker(workspacePath, issueLower));\n return stepOk(step, ['Stopped Docker containers']);\n } catch {\n return stepSkipped(step, ['Docker cleanup skipped (not running or failed)']);\n }\n}\n\n/**\n * Detect whether a PID belongs to a process running inside a Docker container.\n * Container processes appear in host lsof output when paths are bind-mounted.\n */\nasync function isDockerContainerProcess(pid: string): Promise<boolean> {\n try {\n const cgroup = await readFile(`/proc/${pid}/cgroup`, 'utf-8');\n return cgroup.includes('/docker-') || cgroup.includes('/docker/');\n } catch {\n return false;\n }\n}\n\n/**\n * Kill orphaned host processes for a workspace.\n */\nfunction killOrphanedProcesses(workspacePath: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => killOrphanedProcessesImpl(workspacePath),\n catch: (err) => err,\n }).pipe(\n Effect.catch(() =>\n Effect.succeed(stepSkipped('teardown:orphaned-processes', ['Orphaned process cleanup failed (non-fatal)'])),\n ),\n );\n}\n\nasync function killOrphanedProcessesImpl(workspacePath: string): Promise<StepResult> {\n const step = 'teardown:orphaned-processes';\n try {\n // Find PIDs with cwd matching the workspace path\n const { stdout } = await execAsync(\n `lsof +D \"${workspacePath}\" -t 2>/dev/null || true`,\n { encoding: 'utf-8', timeout: 10000 },\n );\n const pids = stdout.trim().split('\\n').filter(Boolean).map(p => p.trim()).filter(p => /^\\d+$/.test(p));\n\n if (pids.length === 0) {\n return stepSkipped(step, ['No orphaned processes found']);\n }\n\n // Don't kill our own process or the dashboard\n const myPid = String(process.pid);\n const safePids = pids.filter(p => p !== myPid);\n\n if (safePids.length === 0) {\n return stepSkipped(step, ['No orphaned processes to kill']);\n }\n\n // Filter out Docker container processes — they appear in host lsof due to bind mounts\n const hostPids: string[] = [];\n for (const pid of safePids) {\n if (!(await isDockerContainerProcess(pid))) {\n hostPids.push(pid);\n }\n }\n\n if (hostPids.length === 0) {\n return stepSkipped(step, ['No orphaned host processes to kill (all were container processes)']);\n }\n\n await execAsync(`kill ${hostPids.join(' ')} 2>/dev/null || true`, { encoding: 'utf-8', timeout: 5000 });\n return stepOk(step, [`Killed ${hostPids.length} orphaned process(es)`]);\n } catch {\n return stepSkipped(step, ['Orphaned process cleanup failed (non-fatal)']);\n }\n}\n\n/**\n * Sync workspace beads to the project-root beads database before workspace deletion.\n */\nfunction syncWorkspaceBeads(\n projectPath: string,\n workspacePath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => syncWorkspaceBeadsImpl(projectPath, workspacePath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:sync-beads', `Failed to sync workspace beads: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function syncWorkspaceBeadsImpl(\n projectPath: string,\n workspacePath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:sync-beads';\n const workspaceBeadsDir = join(workspacePath, '.beads');\n\n if (!existsSync(workspaceBeadsDir)) {\n return stepSkipped(step, ['No .beads directory in workspace']);\n }\n\n try {\n // Export workspace beads to JSONL\n // 120s: bd export cold-starts a dolt server per workspace .beads and can\n // block on the global bd lock; a timeout kill bypasses `|| true` and\n // aborts the whole close-out ceremony (observed at 15s, 14/32 closes).\n await execAsync(\n 'bd export --output .beads/issues-export.jsonl 2>&1 || true',\n { cwd: workspacePath, encoding: 'utf-8', timeout: 120000 }\n );\n\n const exportPath = join(workspacePath, '.beads', 'issues-export.jsonl');\n if (!existsSync(exportPath)) {\n await execAsync('bd export --output .beads/issues.jsonl 2>&1 || true', { cwd: workspacePath, encoding: 'utf-8', timeout: 120000 });\n }\n\n // Import workspace beads into project-root database\n // Use bd import if available, otherwise copy JSONL entries\n try {\n await execAsync(\n `bd import \"${join(workspacePath, '.beads', 'issues.jsonl')}\" 2>&1 || true`,\n { cwd: projectPath, encoding: 'utf-8', timeout: 120000 }\n );\n return stepOk(step, [`Synced workspace beads to project root for ${issueLower}`]);\n } catch {\n // bd import may not exist — try manual JSONL merge\n const wsJsonl = join(workspacePath, '.beads', 'issues.jsonl');\n const projJsonl = join(projectPath, '.beads', 'issues.jsonl');\n\n if (existsSync(wsJsonl) && existsSync(projJsonl)) {\n const wsContent = await readFile(wsJsonl, 'utf-8');\n const issuePattern = issueLower.replace('-', '[-_]');\n const relevantLines = wsContent.split('\\n').filter(\n line => line.trim() && new RegExp(issuePattern, 'i').test(line)\n );\n if (relevantLines.length > 0) {\n await appendFile(projJsonl, '\\n' + relevantLines.join('\\n'));\n return stepOk(step, [`Appended ${relevantLines.length} beads entries for ${issueLower} to project JSONL`]);\n }\n }\n return stepSkipped(step, ['No beads to sync or import not available']);\n }\n } catch (err) {\n return stepFailed(step, `Failed to sync workspace beads: ${(err as Error).message}`);\n }\n}\n\n/**\n * Clear beads for this issue from the project-root .beads/issues.jsonl.\n */\nfunction clearProjectBeads(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => clearProjectBeadsImpl(projectPath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:clear-beads', `Failed to clear beads: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function clearProjectBeadsImpl(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:clear-beads';\n const projJsonl = join(projectPath, '.beads', 'issues.jsonl');\n\n if (!existsSync(projJsonl)) {\n return stepSkipped(step, ['No .beads/issues.jsonl in project root']);\n }\n\n try {\n const content = await readFile(projJsonl, 'utf-8');\n const lines = content.split('\\n');\n const issueUpper = issueLower.toUpperCase();\n const before = lines.length;\n // Remove lines that reference this issue (by ID in the title or issue field)\n const filtered = lines.filter(line => {\n if (!line.trim()) return true; // keep blank lines\n try {\n const entry = JSON.parse(line);\n const title = (entry.title || '').toUpperCase();\n const issue = (entry.issue || '').toUpperCase();\n return !title.includes(issueUpper) && issue !== issueUpper;\n } catch {\n return true; // keep unparseable lines\n }\n });\n const removed = before - filtered.length;\n if (removed > 0) {\n await writeFile(projJsonl, filtered.join('\\n'));\n return stepOk(step, [`Removed ${removed} beads entries for ${issueLower} from project JSONL`]);\n }\n return stepSkipped(step, [`No beads entries found for ${issueLower}`]);\n } catch (err) {\n return stepFailed(step, `Failed to clear beads: ${(err as Error).message}`);\n }\n}\n\n/**\n * Remove git worktree for the workspace.\n */\nfunction removeWorktree(\n projectPath: string,\n workspacePath: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => removeWorktreeImpl(projectPath, workspacePath),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:worktree', `Failed to remove workspace: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function removeWorktreeImpl(\n projectPath: string,\n workspacePath: string,\n): Promise<StepResult> {\n const step = 'teardown:worktree';\n if (!existsSync(workspacePath)) {\n return stepSkipped(step, ['Workspace directory does not exist']);\n }\n\n // Guard: never delete workspace (and its `.devcontainer/`) while containers\n // still reference compose paths inside it.\n const orphanedContainers = await Effect.runPromise(getContainersReferencingWorkspacePath(workspacePath));\n if (orphanedContainers.length > 0) {\n return stepFailed(\n step,\n `Cannot remove workspace: ${orphanedContainers.length} Docker container(s) still reference compose paths in ${DEVCONTAINER_DIRNAME}/. ` +\n `Run workspace Docker cleanup first or stop the containers manually.`,\n );\n }\n\n try {\n await execAsync(`git worktree remove \"${workspacePath}\" --force`, { cwd: projectPath });\n return stepOk(step, ['Removed git worktree']);\n } catch {\n // worktree remove failed — try direct removal\n try {\n await rm(workspacePath, { recursive: true, force: true });\n return stepOk(step, ['Removed workspace directory after worktree removal failed']);\n } catch (err) {\n return stepFailed(step, `Failed to remove workspace: ${(err as Error).message}`);\n }\n }\n}\n\n/**\n * Remove every agent state directory tied to an issue.\n */\nfunction removeAgentState(issueLower: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => removeAgentStateImpl(issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:agent-state', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function removeAgentStateImpl(issueLower: string): Promise<StepResult> {\n const step = 'teardown:agent-state';\n const { readdir, rm } = await import('fs/promises');\n\n let entries: string[];\n try {\n entries = await readdir(AGENTS_DIR);\n } catch {\n return stepSkipped(step, ['Agents directory not present']);\n }\n\n const work = `agent-${issueLower}`;\n const planner = `planning-${issueLower}`;\n const strike = `strike-${issueLower}`;\n const specialistPrefix = `agent-${issueLower}-`;\n const targets = entries.filter(name =>\n name === work || name === planner || name === strike || name.startsWith(specialistPrefix),\n );\n\n let removed = 0;\n for (const name of targets) {\n try {\n await rm(join(AGENTS_DIR, name), { recursive: true, force: true });\n removed++;\n } catch { /* non-fatal */ }\n }\n\n if (removed > 0) {\n return stepOk(step, [`Removed ${removed} agent state director${removed === 1 ? 'y' : 'ies'}`]);\n }\n return stepSkipped(step, ['No agent state directories found']);\n}\n\n/**\n * Delete feature branches (local + remote).\n */\nfunction deleteBranches(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => deleteBranchesImpl(projectPath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:branches', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function deleteBranchesImpl(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:branches';\n const branchName = `feature/${issueLower}`;\n const details: string[] = [];\n const localSlots = await execAsync(`git for-each-ref --format=\"%(refname:short)\" \"refs/heads/feature/${issueLower}-slot-*\"`, { cwd: projectPath, encoding: 'utf-8' })\n .then(({ stdout }) => stdout.trim().split('\\n').filter(Boolean)).catch(() => [] as string[]);\n const remoteSlots = await execAsync(`git ls-remote --heads origin \"feature/${issueLower}-slot-*\"`, { cwd: projectPath, encoding: 'utf-8' })\n .then(({ stdout }) => stdout.trim().split('\\n').map(line => line.split('refs/heads/')[1]).filter((name): name is string => Boolean(name))).catch(() => [] as string[]);\n\n // Delete local branch\n try {\n await execAsync(`git branch -D \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted local branch ${branchName}`);\n } catch {\n details.push(`Local branch ${branchName} not found (already deleted)`);\n }\n\n for (const slotBranch of localSlots) {\n try {\n await execAsync(`git branch -D ${JSON.stringify(slotBranch)}`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted local branch ${slotBranch}`);\n } catch { details.push(`Failed to delete local branch ${slotBranch}`); }\n }\n\n // Delete remote branch\n try {\n await execAsync(`git push origin --delete \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted remote branch ${branchName}`);\n } catch {\n details.push(`Remote branch ${branchName} not found (already deleted)`);\n }\n\n for (const slotBranch of remoteSlots) {\n try {\n await execAsync(`git push origin --delete ${JSON.stringify(slotBranch)}`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted remote branch ${slotBranch}`);\n } catch { details.push(`Failed to delete remote branch ${slotBranch}`); }\n }\n\n return stepOk(step, details);\n}\n\n/**\n * Whether a local branch exists and is fully merged into main.\n */\nasync function localBranchMergedState(\n projectPath: string,\n branchName: string,\n): Promise<{ exists: boolean; merged: boolean }> {\n try {\n await execAsync(`git show-ref --verify --quiet \"refs/heads/${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n } catch {\n return { exists: false, merged: false };\n }\n try {\n await execAsync(`git merge-base --is-ancestor \"${branchName}\" main`, { cwd: projectPath, encoding: 'utf-8' });\n return { exists: true, merged: true };\n } catch {\n return { exists: true, merged: false };\n }\n}\n\n/**\n * Delete the strike branch (local + remote) once it is merged into main.\n *\n * Strike work (`pan strike`) lands on main directly, so a merged\n * `strike/<issue>` branch is pure residue — it is deleted on every teardown,\n * not just when `deleteBranches` is requested (PAN-1721). An UNMERGED strike\n * branch may hold the only copy of an aborted strike's work, so it is\n * preserved and reported instead.\n */\nfunction deleteStrikeBranch(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => deleteStrikeBranchImpl(projectPath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:strike-branch', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function deleteStrikeBranchImpl(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:strike-branch';\n const branchName = `strike/${issueLower}`;\n const details: string[] = [];\n\n // Local branch\n const local = await localBranchMergedState(projectPath, branchName);\n if (local.exists) {\n if (local.merged) {\n try {\n await execAsync(`git branch -D \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted local branch ${branchName}`);\n } catch (err) {\n details.push(`Failed to delete local branch ${branchName}: ${(err as Error).message}`);\n }\n } else {\n details.push(`Preserved local branch ${branchName} — not merged to main`);\n }\n }\n\n // Remote branch\n try {\n const { stdout: remoteRef } = await execAsync(\n `git ls-remote --heads origin \"${branchName}\" 2>/dev/null || true`,\n { cwd: projectPath, encoding: 'utf-8' },\n );\n if (remoteRef.trim()) {\n let remoteMerged = false;\n try {\n await execAsync(`git fetch origin \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n await execAsync('git merge-base --is-ancestor FETCH_HEAD main', { cwd: projectPath, encoding: 'utf-8' });\n remoteMerged = true;\n } catch {\n remoteMerged = false;\n }\n if (remoteMerged) {\n await execAsync(`git push origin --delete \"${branchName}\"`, { cwd: projectPath, encoding: 'utf-8' });\n details.push(`Deleted remote branch ${branchName}`);\n } else {\n details.push(`Preserved remote branch ${branchName} — not merged to main`);\n }\n }\n } catch {\n // remote check/delete failed — non-fatal\n }\n\n if (details.length === 0) {\n return stepSkipped(step, [`No strike branch ${branchName} found`]);\n }\n return details.some(d => d.startsWith('Failed'))\n ? stepFailed(step, details.join('; '))\n : stepOk(step, details);\n}\n\n/**\n * Remove leftover strike worktrees (`workspaces/feature-<issue>-strike`).\n *\n * Runs when the regular worktree removal is gated off (remove_workspace=false\n * at close-out): a strike worktree has no post-close purpose once the strike\n * branch is merged or gone, and leaving it keeps the closed issue alive in\n * resource discovery (PAN-1721). Worktrees whose strike branch has unmerged\n * commits are preserved alongside the branch.\n */\nfunction removeStrikeWorktrees(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => removeStrikeWorktreesImpl(projectPath, issueLower),\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:strike-worktree', `Failed: ${(err as Error).message}`)),\n ),\n );\n}\n\nasync function removeStrikeWorktreesImpl(\n projectPath: string,\n issueLower: string,\n): Promise<StepResult> {\n const step = 'teardown:strike-worktree';\n const strikePaths = findAllWorkspacePaths(projectPath, issueLower).filter(p => p.endsWith('-strike'));\n if (strikePaths.length === 0) {\n return stepSkipped(step, ['No strike worktree found']);\n }\n\n const branch = await localBranchMergedState(projectPath, `strike/${issueLower}`);\n if (branch.exists && !branch.merged) {\n return stepSkipped(step, [`Preserved strike worktree — strike/${issueLower} not merged to main`]);\n }\n\n const details: string[] = [];\n for (const p of strikePaths) {\n const result = await removeWorktreeImpl(projectPath, p);\n details.push(...(result.details ?? [result.error ?? result.step]));\n if (!result.success && !result.skipped) {\n return stepFailed(step, `Failed to remove strike worktree ${p}: ${result.error ?? 'unknown error'}`);\n }\n }\n return stepOk(step, details);\n}\n\n/**\n * Clear shadow state for an issue.\n */\nfunction clearShadowState(issueId: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: () => clearShadowStateImpl(issueId),\n catch: (err) => err,\n }).pipe(\n Effect.catch(() =>\n Effect.succeed(stepSkipped('teardown:shadow-state', ['Shadow state cleanup skipped (non-fatal)'])),\n ),\n );\n}\n\nasync function clearShadowStateImpl(issueId: string): Promise<StepResult> {\n const step = 'teardown:shadow-state';\n try {\n const { removeShadowState } = await import('../shadow-state.js');\n const result = removeShadowState(issueId);\n if (result.success) {\n return stepOk(step, [`Cleared shadow state for ${issueId}`]);\n }\n return stepSkipped(step, ['No shadow state found']);\n } catch {\n return stepSkipped(step, ['Shadow state cleanup skipped (non-fatal)']);\n }\n}\n\n/**\n * Remove legacy .planning/<issue>/ directory from project root.\n */\nfunction clearLegacyPlanningDir(\n projectPath: string,\n issueLower: string,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const step = 'teardown:legacy-planning-dir';\n const legacyDir = join(projectPath, '.planning', issueLower);\n if (!existsSync(legacyDir)) {\n return stepSkipped(step, ['No legacy planning directory found']);\n }\n await rm(legacyDir, { recursive: true, force: true });\n return stepOk(step, [`Deleted legacy planning dir: ${legacyDir}`]);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:legacy-planning-dir', `Failed to delete legacy planning dir: ${(err as Error).message}`)),\n ),\n );\n}\n\n/**\n * Build template placeholders for project-specific cleanup (tunnel, Hume).\n */\nfunction buildPlaceholders(\n ctx: LifecycleContext,\n opts: TeardownOptions,\n workspacePath: string,\n) {\n const issueLower = ctx.issueId.toLowerCase();\n const featureFolder = `feature-${issueLower}`;\n const projName = opts.projectName || ctx.projectName || basename(ctx.projectPath);\n const domain = opts.workspaceConfig?.dns?.domain || 'localhost';\n return {\n FEATURE_NAME: issueLower,\n FEATURE_FOLDER: featureFolder,\n BRANCH_NAME: `feature/${issueLower}`,\n COMPOSE_PROJECT: `${projName}-${featureFolder}`,\n DOMAIN: domain,\n PROJECT_NAME: projName,\n PROJECT_PATH: ctx.projectPath,\n WORKSPACE_PATH: workspacePath,\n };\n}\n\n/**\n * Remove Cloudflare tunnel ingress for workspace.\n */\nfunction removeTunnelConfig(\n tunnelConfig: any,\n placeholders: Record<string, string>,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const { removeTunnelIngress } = await import('../tunnel.js');\n const result = await Effect.runPromise(removeTunnelIngress(tunnelConfig, placeholders as any));\n return stepOk('teardown:tunnel', result.steps || ['Removed tunnel ingress']);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('teardown:tunnel', [`Tunnel cleanup warning: ${(err as Error).message}`])),\n ),\n );\n}\n\n/**\n * Remove Hume EVI config for workspace.\n */\nfunction removeHumeEviConfig(\n humeConfig: any,\n placeholders: Record<string, string>,\n): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const { deleteHumeConfig } = await import('../hume.js');\n const result = await Effect.runPromise(deleteHumeConfig(humeConfig, placeholders as any));\n return stepOk('teardown:hume', result.steps || ['Removed Hume EVI config']);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('teardown:hume', [`Hume cleanup warning: ${(err as Error).message}`])),\n ),\n );\n}\n\n/**\n * Full workspace teardown.\n */\nexport function teardownWorkspace(\n ctx: LifecycleContext,\n opts: TeardownOptions = {},\n): Effect.Effect<StepResult[]> {\n return Effect.gen(function* () {\n const issueLower = ctx.issueId.toLowerCase();\n const workspacePath = findWorkspacePath(ctx.projectPath, issueLower);\n const shouldDeleteWorkspace = opts.deleteWorkspace !== false; // default true\n const results: StepResult[] = [];\n\n // 1. Kill tmux sessions\n results.push(yield* killTmuxSessions(issueLower));\n\n // 2. Clear shadow state (always runs)\n results.push(yield* clearShadowState(ctx.issueId));\n\n // 3. Clear legacy planning directory (always runs)\n results.push(yield* clearLegacyPlanningDir(ctx.projectPath, issueLower));\n\n // 4-9: Workspace-specific cleanup\n if (workspacePath && existsSync(workspacePath)) {\n // 4. Stop TLDR daemon (only if deleting workspace)\n if (shouldDeleteWorkspace) {\n results.push(yield* stopTldrDaemon(workspacePath));\n }\n\n // 5. Stop Docker containers (only if deleting workspace)\n if (shouldDeleteWorkspace && !opts.skipDocker) {\n results.push(yield* stopDocker(workspacePath, issueLower));\n }\n\n // 5b. Kill orphaned host processes (Vite, node) that survive Docker teardown\n if (shouldDeleteWorkspace) {\n results.push(yield* killOrphanedProcesses(workspacePath));\n }\n\n // 6. Beads lifecycle: sync or clear depending on context (PAN-412)\n if (opts.clearBeads) {\n results.push(yield* clearProjectBeads(ctx.projectPath, issueLower));\n } else if (shouldDeleteWorkspace) {\n results.push(yield* syncWorkspaceBeads(ctx.projectPath, workspacePath, issueLower));\n }\n\n // 7-8: Project-specific cleanup (tunnel, Hume)\n if (shouldDeleteWorkspace && (opts.workspaceConfig?.tunnel || opts.workspaceConfig?.hume)) {\n const placeholders = buildPlaceholders(ctx, opts, workspacePath);\n\n if (opts.workspaceConfig.tunnel) {\n results.push(yield* removeTunnelConfig(opts.workspaceConfig.tunnel, placeholders));\n }\n if (opts.workspaceConfig.hume) {\n results.push(yield* removeHumeEviConfig(opts.workspaceConfig.hume, placeholders));\n }\n }\n\n // 9. Remove worktree + workspace directory (only if deleting workspace).\n if (shouldDeleteWorkspace) {\n const allPaths = findAllWorkspacePaths(ctx.projectPath, issueLower);\n for (const p of allPaths) {\n results.push(yield* removeWorktree(ctx.projectPath, p));\n }\n }\n } else {\n results.push(stepSkipped('teardown:workspace', ['No workspace found to clean up']));\n }\n\n // 9b. Strike worktrees are residue once the strike branch is merged —\n // remove them even when deleteWorkspace is gated off (PAN-1721). When\n // shouldDeleteWorkspace is true the loop above already removed them via\n // findAllWorkspacePaths.\n if (!shouldDeleteWorkspace) {\n results.push(yield* removeStrikeWorktrees(ctx.projectPath, issueLower));\n }\n\n // 10. Remove agent state\n results.push(yield* removeAgentState(issueLower));\n\n // 11. Delete branches. Feature branches only if explicitly requested;\n // the strike branch is always a candidate once merged to main (PAN-1721).\n results.push(yield* deleteStrikeBranch(ctx.projectPath, issueLower));\n if (opts.deleteBranches) {\n results.push(yield* deleteBranches(ctx.projectPath, issueLower));\n }\n\n // 12. Prune checkpoint refs for this issue's agents.\n results.push(yield* pruneCheckpointRefs(ctx.projectPath, issueLower));\n\n // 13. Prune specialist registry entries for this issue.\n results.push(yield* pruneSpecialistRegistry(ctx.issueId));\n\n return results;\n });\n}\n\nfunction pruneSpecialistRegistry(issueId: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const step = 'teardown:specialist-registry';\n const { pruneSpecialistRegistryEntriesForIssue } = await import('../cloister/specialists.js');\n const removed = pruneSpecialistRegistryEntriesForIssue(issueId);\n return removed > 0\n ? stepOk(step, [`Pruned ${removed} specialist registry entr${removed === 1 ? 'y' : 'ies'} for ${issueId}`])\n : stepSkipped(step, [`No specialist registry entries for ${issueId}`]);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepSkipped('teardown:specialist-registry', [`Specialist registry prune failed (non-fatal): ${(err as Error).message}`])),\n ),\n );\n}\n\nfunction pruneCheckpointRefs(projectPath: string, issueLower: string): Effect.Effect<StepResult> {\n return Effect.tryPromise({\n try: async () => {\n const step = 'teardown:checkpoint-refs';\n const { pruneCheckpointRefsForAgents } = await import('../checkpoint/checkpoint-manager.js');\n const agentIds = [`agent-${issueLower}`, `planning-${issueLower}`, `strike-${issueLower}`];\n const pruned = await Effect.runPromise(pruneCheckpointRefsForAgents(projectPath, agentIds));\n return stepOk(step, [`Pruned ${pruned} checkpoint ref(s) for ${agentIds.join(', ')}`]);\n },\n catch: (err) => err,\n }).pipe(\n Effect.catch((err) =>\n Effect.succeed(stepFailed('teardown:checkpoint-refs', `Checkpoint prune failed: ${(err as Error).message}`)),\n ),\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Gn as resolveProjectFromIssueSync, Nn as init_projects } from "./config-yaml-DPAieL_G.js";
|
|
2
|
-
import { c as init_review_status, y as setReviewStatusSync } from "./review-status-
|
|
3
|
-
import { W as spawnRun, t as init_agents } from "./agents-
|
|
2
|
+
import { c as init_review_status, y as setReviewStatusSync } from "./review-status-B5ZASOo4.js";
|
|
3
|
+
import { W as spawnRun, t as init_agents } from "./agents-C7V4odQ6.js";
|
|
4
4
|
import { n as shouldSkipDispatchAsMerged, t as init_merge_verification } from "./merge-verification-DA9rvqsE.js";
|
|
5
5
|
import { r as init_test_verdict, t as clearTestVerdictArtifact } from "./test-verdict-D3Il7cBK.js";
|
|
6
6
|
import { Data, Effect } from "effect";
|
|
@@ -163,4 +163,4 @@ const dispatchTestAgentAndNotify = (issueId, workspace, branch, notifyAgent) =>
|
|
|
163
163
|
//#endregion
|
|
164
164
|
export { buildTestRolePrompt, dispatchTestAgentAndNotify };
|
|
165
165
|
|
|
166
|
-
//# sourceMappingURL=test-agent-queue-
|
|
166
|
+
//# sourceMappingURL=test-agent-queue-B_ukPyIK.js.map
|