@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,7 +1,7 @@
|
|
|
1
1
|
import { t as __esmMin } from "./chunk-ruWRV7i3.js";
|
|
2
2
|
import { i as queueAutoCommit, r as init_auto_commit } from "./auto-commit-DrRvPia9.js";
|
|
3
3
|
import { _ as getProjectPanPaths, v as init_specs } from "./io-BfPe-7jt.js";
|
|
4
|
-
import { a as init_review_status, i as getReviewStatusSync } from "./review-status-
|
|
4
|
+
import { a as init_review_status, i as getReviewStatusSync } from "./review-status-B_XXomV0.js";
|
|
5
5
|
import { n as parseSequenceJson, t as init_types } from "./types-BNmsgkQG.js";
|
|
6
6
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
@@ -135,4 +135,4 @@ var init_sequence_io = __esmMin((() => {
|
|
|
135
135
|
//#endregion
|
|
136
136
|
export { parseSequenceMd as n, writeSequenceMd as r, init_sequence_io as t };
|
|
137
137
|
|
|
138
|
-
//# sourceMappingURL=sequence-io-
|
|
138
|
+
//# sourceMappingURL=sequence-io-CyxvNHfA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sequence-io-
|
|
1
|
+
{"version":3,"file":"sequence-io-CyxvNHfA.js","names":[],"sources":["../src/lib/backlog/sequence-io.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { parseSequenceJson } from './types.js';\nimport type { SequenceDoc, SequenceEdge, SequenceNode, SequenceParseError } from './types.js';\nimport { queueAutoCommit } from '../pan-dir/auto-commit.js';\nimport { getReviewStatusSync } from '../review-status.js';\nimport { getProjectPanPaths } from '../pan-dir/specs.js';\n\nconst MACHINE_MARKER = '<!-- machine-readable; do not hand-edit below this line -->';\nconst TOP_TIER_SIZE = 80;\nfunction sequencePath(projectRoot: string): string {\n return join(getProjectPanPaths(projectRoot).panDir, 'backlog', 'sequence.md');\n}\n\nexport type ParseSequenceMdResult = { ok: true; doc: SequenceDoc } | SequenceParseError;\n\nexport function parseSequenceMd(markdown: string): ParseSequenceMdResult {\n const markerIdx = markdown.indexOf(MACHINE_MARKER);\n const searchText = markerIdx >= 0 ? markdown.slice(markerIdx) : markdown;\n\n const fenceMatch = searchText.match(/```json\\s*\\n([\\s\\S]*?)```/);\n if (!fenceMatch) {\n return { ok: false, error: 'No fenced JSON block found in sequence.md' };\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(fenceMatch[1]);\n } catch (e) {\n return { ok: false, error: `JSON parse failed: ${e instanceof Error ? e.message : String(e)}` };\n }\n\n return parseSequenceJson(parsed);\n}\n\nfunction renderTable(nodes: SequenceNode[]): string {\n const header = '| rank | issue | size | importance | condition | epic | depends-on | why |';\n const sep = '|------|-------|------|------------|-----------|------|------------|-----|';\n const rows = nodes.map((n) =>\n `| ${n.rank} | ${n.issue} | ${n.size} | ${n.importance} | ${n.condition} | ${n.isEpic ? '✓' : ''} | ${n.dependsOn.join(', ')} | ${n.why} |`\n );\n return [header, sep, ...rows].join('\\n');\n}\n\nfunction renderRationaleSection(nodes: SequenceNode[]): string {\n const topTier = nodes.slice(0, TOP_TIER_SIZE).filter((n) => n.rationale);\n if (topTier.length === 0) return '';\n const lines = ['## Rationale detail', ''];\n for (const n of topTier) {\n lines.push(`### ${n.issue} (rank ${n.rank})`, '', n.rationale!, '');\n }\n return lines.join('\\n');\n}\n\nexport interface WriteSequenceMdOpts {\n /**\n * Set to true when the caller is an operator-facing route that has already\n * applied the intended field value to the doc. Skips the prior-file\n * preservation step so operators can reset gate/planning back to 'auto'.\n *\n * When false/absent (AI resequence path), prior non-'auto' operator fields\n * and in-pipeline ranks are carried forward from the on-disk file (FR-13,\n * FR-15, FR-16, FR-17).\n */\n operatorEdit?: boolean;\n}\n\n/**\n * Write sequence.md, merging operator-owned state from the prior file.\n *\n * Merge rules for AI resequence (operatorEdit absent/false):\n * - gate !== 'auto' in prior → carry forward (operator-set)\n * - planning !== 'auto' in prior → carry forward (operator-set)\n * - in-pipeline issues (live workspace or non-pending review) → carry forward rank/why/rationale\n * - edges with source === 'operator' in prior → always preserved verbatim\n *\n * For explicit operator edits (operatorEdit: true), the doc already contains\n * the correct operator-intended value; prior-preservation is skipped entirely.\n */\nexport function writeSequenceMd(projectRoot: string, doc: SequenceDoc, opts?: WriteSequenceMdOpts): void {\n const outPath = sequencePath(projectRoot);\n\n // Load prior sequence for merge-preservation (AI resequence path only)\n let priorNodeMap = new Map<string, SequenceNode>();\n let priorOperatorEdges: SequenceEdge[] = [];\n if (!opts?.operatorEdit && existsSync(outPath)) {\n const priorText = readFileSync(outPath, 'utf-8');\n const prior = parseSequenceMd(priorText);\n if (prior.ok) {\n priorNodeMap = new Map(prior.doc.nodes.map((n) => [n.issue, n]));\n priorOperatorEdges = prior.doc.edges.filter((e) => e.source === 'operator');\n }\n }\n\n // Detect in-pipeline (pinned) issues: live workspace dir OR non-pending review status\n const workspacesDir = join(projectRoot, 'workspaces');\n const isPinned = (issueId: string): boolean => {\n const rs = getReviewStatusSync(issueId.toUpperCase());\n if (rs && rs.reviewStatus !== 'pending') return true;\n return existsSync(join(workspacesDir, `feature-${issueId.toLowerCase()}`));\n };\n\n // Merge operator-owned fields into each node\n const mergedNodes: SequenceNode[] = doc.nodes.map((node) => {\n const prior = priorNodeMap.get(node.issue);\n if (!prior) return node;\n // Preserve operator-set gate and planning\n const gate = prior.gate !== 'auto' ? prior.gate : node.gate;\n const planning = prior.planning !== 'auto' ? prior.planning : node.planning;\n // Preserve rank/why/rationale for pinned (in-pipeline) issues\n if (isPinned(node.issue)) {\n return { ...node, gate, planning, rank: prior.rank, why: prior.why, rationale: prior.rationale };\n }\n return { ...node, gate, planning };\n });\n\n // Merge operator edges: union of prior operator edges and current doc's operator edges\n // (prior ones are kept even if the sequencer dropped them; new ones from doc are added).\n const currentOperatorEdges = doc.edges.filter((e) => e.source === 'operator');\n const currentNonOperatorEdges = doc.edges.filter((e) => e.source !== 'operator');\n const operatorEdgeKeys = new Set<string>();\n const mergedOperatorEdges: SequenceEdge[] = [];\n for (const edge of [...priorOperatorEdges, ...currentOperatorEdges]) {\n const key = `${edge.from}→${edge.to}→${edge.type}`;\n if (!operatorEdgeKeys.has(key)) {\n operatorEdgeKeys.add(key);\n mergedOperatorEdges.push(edge);\n }\n }\n const mergedEdges: SequenceEdge[] = [...mergedOperatorEdges, ...currentNonOperatorEdges];\n\n const mergedDoc: SequenceDoc = { ...doc, nodes: mergedNodes, edges: mergedEdges };\n const sortedNodes = [...mergedDoc.nodes].sort((a, b) => a.rank - b.rank);\n const ts = mergedDoc.generatedAt;\n const header = `# Backlog Sequence\\n\\n_Last sequenced: ${ts} · model: ${mergedDoc.model} · open: ${mergedDoc.openCount}_\\n`;\n const table = renderTable(sortedNodes);\n const rationale = renderRationaleSection(sortedNodes);\n const machineBlock = `${MACHINE_MARKER}\\n\\n\\`\\`\\`json\\n${JSON.stringify(mergedDoc, null, 2)}\\n\\`\\`\\`\\n`;\n\n const parts = [header, table];\n if (rationale) parts.push(rationale);\n parts.push(machineBlock);\n\n const content = parts.join('\\n\\n');\n mkdirSync(dirname(outPath), { recursive: true });\n writeFileSync(outPath, content, 'utf-8');\n\n queueAutoCommit({\n projectRoot,\n paths: [outPath],\n subject: `chore(state): update backlog sequence (${doc.project})`,\n });\n}\n"],"mappings":";;;;;;;;AAUA,SAAS,aAAa,aAA6B;AACjD,QAAO,KAAK,mBAAmB,YAAY,CAAC,QAAQ,WAAW,cAAc;;AAK/E,SAAgB,gBAAgB,UAAyC;CACvE,MAAM,YAAY,SAAS,QAAQ,eAAe;CAGlD,MAAM,cAFa,aAAa,IAAI,SAAS,MAAM,UAAU,GAAG,UAElC,MAAM,4BAA4B;AAChE,KAAI,CAAC,WACH,QAAO;EAAE,IAAI;EAAO,OAAO;EAA6C;CAG1E,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,WAAW,GAAG;UAC3B,GAAG;AACV,SAAO;GAAE,IAAI;GAAO,OAAO,sBAAsB,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;GAAI;;AAGjG,QAAO,kBAAkB,OAAO;;AAGlC,SAAS,YAAY,OAA+B;AAMlD,QAAO;EAAC;EAAQ;EAAK,GAHR,MAAM,KAAK,MACtB,KAAK,EAAE,KAAK,KAAK,EAAE,MAAM,KAAK,EAAE,KAAK,KAAK,EAAE,WAAW,KAAK,EAAE,UAAU,KAAK,EAAE,SAAS,MAAM,GAAG,KAAK,EAAE,UAAU,KAAK,KAAK,CAAC,KAAK,EAAE,IAAI,IAE9G;EAAC,CAAC,KAAK,KAAK;;AAG1C,SAAS,uBAAuB,OAA+B;CAC7D,MAAM,UAAU,MAAM,MAAM,GAAG,cAAc,CAAC,QAAQ,MAAM,EAAE,UAAU;AACxE,KAAI,QAAQ,WAAW,EAAG,QAAO;CACjC,MAAM,QAAQ,CAAC,uBAAuB,GAAG;AACzC,MAAK,MAAM,KAAK,QACd,OAAM,KAAK,OAAO,EAAE,MAAM,SAAS,EAAE,KAAK,IAAI,IAAI,EAAE,WAAY,GAAG;AAErE,QAAO,MAAM,KAAK,KAAK;;;;;;;;;;;;;;AA4BzB,SAAgB,gBAAgB,aAAqB,KAAkB,MAAkC;CACvG,MAAM,UAAU,aAAa,YAAY;CAGzC,IAAI,+BAAe,IAAI,KAA2B;CAClD,IAAI,qBAAqC,EAAE;AAC3C,KAAI,CAAC,MAAM,gBAAgB,WAAW,QAAQ,EAAE;EAE9C,MAAM,QAAQ,gBADI,aAAa,SAAS,QACD,CAAC;AACxC,MAAI,MAAM,IAAI;AACZ,kBAAe,IAAI,IAAI,MAAM,IAAI,MAAM,KAAK,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;AAChE,wBAAqB,MAAM,IAAI,MAAM,QAAQ,MAAM,EAAE,WAAW,WAAW;;;CAK/E,MAAM,gBAAgB,KAAK,aAAa,aAAa;CACrD,MAAM,YAAY,YAA6B;EAC7C,MAAM,KAAK,oBAAoB,QAAQ,aAAa,CAAC;AACrD,MAAI,MAAM,GAAG,iBAAiB,UAAW,QAAO;AAChD,SAAO,WAAW,KAAK,eAAe,WAAW,QAAQ,aAAa,GAAG,CAAC;;CAI5E,MAAM,cAA8B,IAAI,MAAM,KAAK,SAAS;EAC1D,MAAM,QAAQ,aAAa,IAAI,KAAK,MAAM;AAC1C,MAAI,CAAC,MAAO,QAAO;EAEnB,MAAM,OAAO,MAAM,SAAS,SAAS,MAAM,OAAO,KAAK;EACvD,MAAM,WAAW,MAAM,aAAa,SAAS,MAAM,WAAW,KAAK;AAEnE,MAAI,SAAS,KAAK,MAAM,CACtB,QAAO;GAAE,GAAG;GAAM;GAAM;GAAU,MAAM,MAAM;GAAM,KAAK,MAAM;GAAK,WAAW,MAAM;GAAW;AAElG,SAAO;GAAE,GAAG;GAAM;GAAM;GAAU;GAClC;CAIF,MAAM,uBAAuB,IAAI,MAAM,QAAQ,MAAM,EAAE,WAAW,WAAW;CAC7E,MAAM,0BAA0B,IAAI,MAAM,QAAQ,MAAM,EAAE,WAAW,WAAW;CAChF,MAAM,mCAAmB,IAAI,KAAa;CAC1C,MAAM,sBAAsC,EAAE;AAC9C,MAAK,MAAM,QAAQ,CAAC,GAAG,oBAAoB,GAAG,qBAAqB,EAAE;EACnE,MAAM,MAAM,GAAG,KAAK,KAAK,GAAG,KAAK,GAAG,GAAG,KAAK;AAC5C,MAAI,CAAC,iBAAiB,IAAI,IAAI,EAAE;AAC9B,oBAAiB,IAAI,IAAI;AACzB,uBAAoB,KAAK,KAAK;;;CAGlC,MAAM,cAA8B,CAAC,GAAG,qBAAqB,GAAG,wBAAwB;CAExF,MAAM,YAAyB;EAAE,GAAG;EAAK,OAAO;EAAa,OAAO;EAAa;CACjF,MAAM,cAAc,CAAC,GAAG,UAAU,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,KAAK;CAExE,MAAM,SAAS,0CADJ,UAAU,YACuC,YAAY,UAAU,MAAM,WAAW,UAAU,UAAU;CACvH,MAAM,QAAQ,YAAY,YAAY;CACtC,MAAM,YAAY,uBAAuB,YAAY;CACrD,MAAM,eAAe,GAAG,eAAe,kBAAkB,KAAK,UAAU,WAAW,MAAM,EAAE,CAAC;CAE5F,MAAM,QAAQ,CAAC,QAAQ,MAAM;AAC7B,KAAI,UAAW,OAAM,KAAK,UAAU;AACpC,OAAM,KAAK,aAAa;CAExB,MAAM,UAAU,MAAM,KAAK,OAAO;AAClC,WAAU,QAAQ,QAAQ,EAAE,EAAE,WAAW,MAAM,CAAC;AAChD,eAAc,SAAS,SAAS,QAAQ;AAExC,iBAAgB;EACd;EACA,OAAO,CAAC,QAAQ;EAChB,SAAS,0CAA0C,IAAI,QAAQ;EAChE,CAAC;;;;aArJ2C;mBAEa;qBACF;aACD;AAEnD,kBAAiB;AACjB,iBAAgB"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { t as __esmMin } from "./chunk-ruWRV7i3.js";
|
|
2
2
|
import { a as FsError, d as ProcessSpawnError, m as init_errors } from "./errors-DyEDJ7xB.js";
|
|
3
|
-
import { C as init_context_overflow, Nt as getProviderEnvForModel, jt as buildSpawnEnvForModel, t as init_agents, w as isContextOverflowError } from "./agents-
|
|
3
|
+
import { C as init_context_overflow, Nt as getProviderEnvForModel, jt as buildSpawnEnvForModel, t as init_agents, w as isContextOverflowError } from "./agents-CgVr77Qq.js";
|
|
4
4
|
import { a as getHarnessBehavior } from "./src-U_5LlV2C.js";
|
|
5
5
|
import { d as init_behavior } from "./codex-Czbp-N_y.js";
|
|
6
6
|
import { i as init_claude_permissions, r as getClaudePermissionFlagsSync } from "./claude-permissions-CVUlJPl1.js";
|
|
@@ -850,4 +850,4 @@ Be thorough. Preserve exact file paths, function names, error messages, and code
|
|
|
850
850
|
//#endregion
|
|
851
851
|
export { runModelSummary as a, truncateHeadTail as c, parseEntries as i, generateSummaryFromPrompt as n, serializeConversation as o, init_smart_compaction as r, summarizeSerializedText as s, generateSmartSummary as t };
|
|
852
852
|
|
|
853
|
-
//# sourceMappingURL=smart-compaction-
|
|
853
|
+
//# sourceMappingURL=smart-compaction-ByKXTQNy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smart-compaction-ByUEgQkF.js","names":["self.runModelSummary"],"sources":["../src/lib/conversations/smart-compaction.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\nimport * as self from './smart-compaction.js';\nimport { buildSpawnEnvForModel, getProviderEnvForModel } from '../agents.js';\nimport { getClaudePermissionFlagsSync } from '../claude-permissions.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { FsError, ProcessSpawnError } from '../errors.js';\nimport { recordBackgroundAiCost } from '../background-ai/cost.js';\nimport type { AIProvider } from '../cost.js';\nimport { isContextOverflowError } from '../context-overflow.js';\n\n/** Record summary-fork/compaction spend from a `claude -p --output-format json`\n * envelope. Best-effort; never throws into the caller (PAN-1589). */\nfunction recordSummaryForkCost(model: string, envelope: Record<string, unknown>): void {\n const usage = (envelope['usage'] ?? {}) as Record<string, unknown>;\n const num = (v: unknown): number => (typeof v === 'number' ? v : 0);\n const m = model.toLowerCase();\n const provider: AIProvider = m.includes('gpt') ? 'openai' : m.includes('gemini') ? 'google' : 'anthropic';\n recordBackgroundAiCost({\n feature: 'summaryFork',\n provider,\n model,\n usage: {\n inputTokens: num(usage['input_tokens']),\n outputTokens: num(usage['output_tokens']),\n cacheReadTokens: num(usage['cache_read_input_tokens']),\n cacheWriteTokens: num(usage['cache_creation_input_tokens']),\n },\n costUsd: typeof envelope['total_cost_usd'] === 'number' ? (envelope['total_cost_usd'] as number) : undefined,\n });\n}\n\nconst SUMMARY_TIMEOUT_MS = 60_000;\nconst FORK_SUMMARY_TIMEOUT_MS = 300_000;\nconst DEFAULT_SUMMARY_MODEL = 'claude-haiku-4-5-20251001';\n\nexport interface CompactionOptions {\n jsonlPath: string;\n model?: string;\n keepRecentTokens?: number;\n reserveTokens?: number;\n richMode?: boolean;\n /** 'compact' = native compaction (returns stub if nothing to summarize). 'fork' = always produce a real summary for conversation forks. */\n mode?: 'compact' | 'fork';\n /** When true, include thinking block content in the serialized conversation sent to the summary model. Default: true. */\n includeThinkingInSummary?: boolean;\n /** Runtime harness used for model-backed summary generation. */\n harness?: RuntimeName;\n}\n\nexport interface CompactionResult {\n summary: string;\n tokensBefore: number;\n firstKeptEntryIndex: number;\n summaryModel: string | null;\n readFiles: string[];\n modifiedFiles: string[];\n}\n\ninterface FileOperations {\n read: Set<string>;\n written: Set<string>;\n edited: Set<string>;\n}\n\ninterface CutPointResult {\n firstKeptEntryIndex: number;\n turnStartIndex: number;\n isSplitTurn: boolean;\n}\n\n// ============================================================================\n// Entry parsing\n// ============================================================================\n\nexport async function parseEntries(jsonlPath: string): Promise<any[]> {\n const content = await readFile(jsonlPath, 'utf-8');\n const entries: any[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n entries.push(JSON.parse(line));\n } catch {\n // skip malformed\n }\n }\n return entries;\n}\n\n// ============================================================================\n// Token estimation (chars/4 heuristic, conservative)\n// ============================================================================\n\nfunction estimateTokens(entry: any): number {\n if (!entry) return 0;\n\n // Compact boundary / system entries\n if (entry.type === 'system' || entry.subtype === 'compact_boundary') {\n const text = entry.content || '';\n return Math.ceil(text.length / 4);\n }\n\n // User entries (including compact summaries and tool results)\n if (entry.type === 'user' && entry.message) {\n const content = entry.message.content;\n if (typeof content === 'string') {\n return Math.ceil(content.length / 4);\n }\n if (Array.isArray(content)) {\n let chars = 0;\n for (const block of content) {\n if (block.type === 'text' && block.text) {\n chars += block.text.length;\n } else if (block.type === 'tool_result') {\n // Estimate tool result content\n if (typeof block.content === 'string') {\n chars += block.content.length;\n } else if (Array.isArray(block.content)) {\n for (const b of block.content) {\n if (b.text) chars += b.text.length;\n }\n }\n } else if (block.type === 'image') {\n chars += 4800; // ~1200 tokens\n }\n }\n return Math.ceil(chars / 4);\n }\n }\n\n // Assistant entries\n if (entry.type === 'assistant' && entry.message) {\n const content = entry.message.content;\n if (Array.isArray(content)) {\n let chars = 0;\n for (const block of content) {\n if (block.type === 'text' && block.text) {\n chars += block.text.length;\n } else if (block.type === 'thinking' && block.thinking) {\n chars += block.thinking.length;\n } else if (block.type === 'tool_use') {\n chars += block.name?.length || 0;\n chars += JSON.stringify(block.input || {}).length;\n }\n }\n return Math.ceil(chars / 4);\n }\n if (typeof content === 'string') {\n return Math.ceil(content.length / 4);\n }\n }\n\n return 0;\n}\n\nfunction estimateContextTokens(entries: any[]): number {\n // Use the last assistant usage if available, otherwise heuristic\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n if (entry.type === 'assistant' && entry.message?.usage) {\n const u = entry.message.usage;\n if (typeof u.input_tokens === 'number') {\n let total = u.input_tokens || 0;\n if (typeof u.output_tokens === 'number') total += u.output_tokens;\n if (typeof u.cache_creation_input_tokens === 'number') total += u.cache_creation_input_tokens;\n if (typeof u.cache_read_input_tokens === 'number') total += u.cache_read_input_tokens;\n if (total <= 0) continue;\n // Add trailing entries after this assistant\n for (let j = i + 1; j < entries.length; j++) {\n total += estimateTokens(entries[j]);\n }\n return total;\n }\n if (typeof u.inputTokens === 'number') {\n let total = u.inputTokens || 0;\n if (typeof u.outputTokens === 'number') total += u.outputTokens;\n if (typeof u.cacheCreationInputTokens === 'number') total += u.cacheCreationInputTokens;\n if (typeof u.cacheReadInputTokens === 'number') total += u.cacheReadInputTokens;\n if (total <= 0) continue;\n for (let j = i + 1; j < entries.length; j++) {\n total += estimateTokens(entries[j]);\n }\n return total;\n }\n }\n }\n\n // Fallback: sum all heuristics\n return entries.reduce((sum, e) => sum + estimateTokens(e), 0);\n}\n\n// ============================================================================\n// File operation tracking\n// ============================================================================\n\nfunction createFileOps(): FileOperations {\n return { read: new Set(), written: new Set(), edited: new Set() };\n}\n\nfunction extractFileOpsFromEntries(entries: any[]): FileOperations {\n const fileOps = createFileOps();\n for (const entry of entries) {\n if (entry.type !== 'assistant' || !entry.message?.content) continue;\n const content = entry.message.content;\n if (!Array.isArray(content)) continue;\n for (const block of content) {\n if (block.type !== 'tool_use' || !block.input) continue;\n const path = block.input.file_path || block.input.path;\n if (!path) continue;\n switch (block.name) {\n case 'Read':\n case 'read':\n fileOps.read.add(path);\n break;\n case 'Write':\n case 'write':\n fileOps.written.add(path);\n break;\n case 'Edit':\n case 'edit':\n fileOps.edited.add(path);\n break;\n }\n }\n }\n return fileOps;\n}\n\nfunction computeFileLists(fileOps: FileOperations): { readFiles: string[]; modifiedFiles: string[] } {\n const modified = new Set([...fileOps.edited, ...fileOps.written]);\n const readOnly = [...fileOps.read].filter(f => !modified.has(f)).sort();\n return { readFiles: readOnly, modifiedFiles: [...modified].sort() };\n}\n\nfunction formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {\n const sections: string[] = [];\n if (readFiles.length > 0) {\n sections.push(`<read-files>\\n${readFiles.join('\\n')}\\n</read-files>`);\n }\n if (modifiedFiles.length > 0) {\n sections.push(`<modified-files>\\n${modifiedFiles.join('\\n')}\\n</modified-files>`);\n }\n if (sections.length === 0) return '';\n return '\\n\\n' + sections.join('\\n\\n');\n}\n\n// ============================================================================\n// Previous compact boundary detection\n// ============================================================================\n\nfunction findPreviousCompactBoundary(entries: any[]): { index: number; summary: string } | null {\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n // Overdeck native compaction writes compact_boundary then a user summary entry\n if (entry.type === 'system' && entry.subtype === 'compact_boundary') {\n // Look immediately after for the summary user message\n if (i + 1 < entries.length) {\n const next = entries[i + 1];\n if (next.type === 'user' && next.isCompactSummary && next.message?.content) {\n return { index: i, summary: next.message.content };\n }\n }\n }\n // Also detect older compact summary entries\n if (entry.type === 'user' && entry.isCompactSummary && entry.message?.content) {\n return { index: i, summary: entry.message.content };\n }\n }\n return null;\n}\n\n// ============================================================================\n// Cut point detection (inspired by Pi agent)\n// ============================================================================\n\nfunction isUserMessageEntry(entry: any): boolean {\n if (entry.type !== 'user' || !entry.message) return false;\n const content = entry.message.content;\n // A pure tool_result user entry is not a turn-starting user message\n if (Array.isArray(content)) {\n const hasText = content.some((b: any) => b.type === 'text' && b.text?.trim());\n return hasText;\n }\n if (typeof content === 'string') {\n return content.trim().length > 0;\n }\n return false;\n}\n\nfunction isAssistantEntry(entry: any): boolean {\n return entry.type === 'assistant' && !!entry.message;\n}\n\nfunction findValidCutPoints(entries: any[], startIndex: number, endIndex: number): number[] {\n const cutPoints: number[] = [];\n for (let i = startIndex; i < endIndex; i++) {\n if (isUserMessageEntry(entries[i]) || isAssistantEntry(entries[i])) {\n cutPoints.push(i);\n }\n }\n return cutPoints;\n}\n\nfunction findTurnStartIndex(entries: any[], entryIndex: number, startIndex: number): number {\n for (let i = entryIndex; i >= startIndex; i--) {\n if (isUserMessageEntry(entries[i])) {\n return i;\n }\n }\n return -1;\n}\n\nfunction findCutPoint(\n entries: any[],\n startIndex: number,\n endIndex: number,\n keepRecentTokens: number,\n): CutPointResult {\n const cutPoints = findValidCutPoints(entries, startIndex, endIndex);\n if (cutPoints.length === 0) {\n return { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };\n }\n\n let accumulatedTokens = 0;\n let cutIndex = cutPoints[0];\n\n for (let i = endIndex - 1; i >= startIndex; i--) {\n const tokens = estimateTokens(entries[i]);\n accumulatedTokens += tokens;\n\n if (accumulatedTokens >= keepRecentTokens) {\n for (let c = 0; c < cutPoints.length; c++) {\n if (cutPoints[c] >= i) {\n cutIndex = cutPoints[c];\n break;\n }\n }\n break;\n }\n }\n\n // Scan backwards to include non-message metadata entries after a boundary.\n // Never scan past the previous valid cut point (or the original cut point\n // itself if it's the first valid one).\n const originalCutIndex = cutIndex;\n const cutPointArrayIndex = cutPoints.findIndex((cp) => cp === originalCutIndex);\n const minCutIndex = cutPointArrayIndex > 0 ? cutPoints[cutPointArrayIndex - 1]! + 1 : originalCutIndex;\n\n while (cutIndex > startIndex && cutIndex > minCutIndex) {\n const prev = entries[cutIndex - 1];\n if (prev.type === 'system' && prev.subtype === 'compact_boundary') break;\n if (isUserMessageEntry(prev) || isAssistantEntry(prev)) break;\n cutIndex--;\n }\n\n const isUser = isUserMessageEntry(entries[cutIndex]);\n const turnStartIndex = isUser ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);\n\n return {\n firstKeptEntryIndex: cutIndex,\n turnStartIndex,\n isSplitTurn: !isUser && turnStartIndex !== -1,\n };\n}\n\n// ============================================================================\n// Conversation serialization\n// ============================================================================\n\nconst TOOL_RESULT_MAX_CHARS = 2000;\n\nfunction truncateForSummary(text: string, maxChars: number): string {\n if (text.length <= maxChars) return text;\n return `${text.slice(0, maxChars)}\\n\\n[... ${text.length - maxChars} more characters truncated]`;\n}\n\nfunction serializeEntry(entry: any, includeThinking: boolean = true): string | undefined {\n if (entry.type === 'user' && entry.message) {\n const content = entry.message.content;\n let text = '';\n if (typeof content === 'string') {\n text = content;\n } else if (Array.isArray(content)) {\n const parts: string[] = [];\n for (const block of content) {\n if (block.type === 'text' && block.text) {\n parts.push(block.text);\n } else if (block.type === 'tool_result') {\n let resultText = '';\n if (typeof block.content === 'string') {\n resultText = block.content;\n } else if (Array.isArray(block.content)) {\n resultText = block.content\n .filter((b: any) => b.type === 'text' && b.text)\n .map((b: any) => b.text)\n .join('\\n');\n }\n if (resultText) {\n parts.push(`[Tool result]: ${truncateForSummary(resultText, TOOL_RESULT_MAX_CHARS)}`);\n }\n }\n }\n text = parts.join('\\n');\n }\n if (text.trim()) {\n return `[User]: ${text.trim()}`;\n }\n }\n\n if (entry.type === 'assistant' && entry.message) {\n const content = entry.message.content;\n const parts: string[] = [];\n if (Array.isArray(content)) {\n const textParts: string[] = [];\n const toolCalls: string[] = [];\n for (const block of content) {\n if (block.type === 'text' && block.text) {\n textParts.push(block.text);\n } else if (block.type === 'thinking' && block.thinking && includeThinking) {\n textParts.push(`[thinking]: ${block.thinking}`);\n } else if (block.type === 'tool_use') {\n const args = block.input || {};\n const argsStr = Object.entries(args)\n .map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n .join(', ');\n toolCalls.push(`${block.name}(${argsStr})`);\n }\n }\n if (textParts.length) parts.push(`[Assistant]: ${textParts.join('\\n')}`);\n if (toolCalls.length) parts.push(`[Assistant tool calls]: ${toolCalls.join('; ')}`);\n } else if (typeof content === 'string') {\n parts.push(`[Assistant]: ${content}`);\n }\n if (parts.length) {\n return parts.join('\\n\\n');\n }\n }\n\n if (entry.type === 'system' && entry.subtype === 'compact_boundary' && entry.content) {\n return `[Context checkpoint]: ${entry.content}`;\n }\n\n return undefined;\n}\n\nexport function serializeConversation(entries: any[], includeThinking: boolean = true): string {\n const parts: string[] = [];\n for (const entry of entries) {\n const serialized = serializeEntry(entry, includeThinking);\n if (serialized) parts.push(serialized);\n }\n return parts.join('\\n\\n');\n}\n\n// ============================================================================\n// Summarization prompts\n// ============================================================================\n\nconst SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`;\n\nconst SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.\n\nUse this EXACT format:\n\n## Goal\n[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned by user]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Ordered list of what should happen next]\n\n## Critical Context\n- [Any data, examples, or references needed to continue]\n- [Or \"(none)\" if not applicable]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\nconst UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, and context from the new messages\n- UPDATE the Progress section: move items from \"In Progress\" to \"Done\" when completed\n- UPDATE \"Next Steps\" based on what was accomplished\n- PRESERVE exact file paths, function names, and error messages\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Goal\n[Preserve existing goals, add new ones if the task expanded]\n\n## Constraints & Preferences\n- [Preserve existing, add new ones discovered]\n\n## Progress\n### Done\n- [x] [Include previously done items AND newly completed items]\n\n### In Progress\n- [ ] [Current work - update based on progress]\n\n### Blocked\n- [Current blockers - remove if resolved]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale] (preserve all previous, add new)\n\n## Next Steps\n1. [Update based on current state]\n\n## Critical Context\n- [Preserve important context, add new if needed]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\nconst TURN_PREFIX_PROMPT = `This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.`;\n\n// ============================================================================\n// Rich summarization prompts (9-section format, more verbose)\n// ============================================================================\n\nconst RICH_SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a comprehensive continuation summary that another LLM will use to continue the work without losing context.\n\nUse this EXACT format:\n\n## Primary Request and Intent\n[What is the user trying to accomplish? Include explicit asks, constraints, and intended outcome.]\n\n## Key Technical Concepts\n- [Major technical topics, tools, frameworks, libraries, patterns, and architectural ideas]\n- [Or \"(none)\" if none were mentioned]\n\n## Files and Code Sections\n- [File name]: [why it matters, what was examined or changed, include important code snippets in full when applicable]\n- [Or \"(none)\" if not applicable]\n\n## Errors and fixes\n- [What went wrong and how it was fixed or addressed]\n- [Or \"(none)\" if none occurred]\n\n## Problem Solving\n[Problems resolved and any ongoing troubleshooting]\n\n## All user messages\n- [Every user message in the conversation that was not a tool result. Do not omit any.]\n- [Or \"(none)\" if not applicable]\n\n## Pending Tasks\n- [All unfinished work the user has explicitly asked for]\n- [Or \"(none)\" if not applicable]\n\n## Current Work\n[Exactly what was being worked on immediately before this summarization request. Focus on the latest messages.]\n\n## Optional Next Step\n[The next action that should be taken, but only if it directly follows from the user's most recent request. Include exact quoted lines where useful.]\n\nBe thorough, specific, and technically accurate. Preserve exact file paths, function names, error messages, and code snippets.`;\n\nconst RICH_UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, user messages, and context from the new messages\n- UPDATE the \"Current Work\" section to reflect the latest state\n- UPDATE \"Pending Tasks\" based on what was completed or newly requested\n- PRESERVE exact file paths, function names, error messages, and code snippets\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Primary Request and Intent\n[Preserve existing intent, add new ones if the task expanded]\n\n## Key Technical Concepts\n- [Preserve existing, add new ones discovered]\n\n## Files and Code Sections\n- [Preserve existing files, add new ones, update changed ones with full code snippets]\n\n## Errors and fixes\n- [Preserve existing, add new ones, mark resolved if appropriate]\n\n## Problem Solving\n[Update with new resolutions and ongoing troubleshooting]\n\n## All user messages\n- [Preserve existing user messages, add new ones from the recent conversation]\n\n## Pending Tasks\n- [Update based on what was completed or newly requested]\n\n## Current Work\n[Update to reflect the latest state immediately before this request]\n\n## Optional Next Step\n[Update based on current state]\n\nBe thorough. Preserve exact file paths, function names, error messages, and code snippets.`;\n\n// ============================================================================\n// LLM call\n// ============================================================================\n\nasync function runPiModelSummary(prompt: string, model: string, timeoutMs?: number): Promise<string> {\n const sessionDir = await mkdtemp(join(tmpdir(), 'overdeck-pi-summary-'));\n const effectiveTimeout = timeoutMs ?? SUMMARY_TIMEOUT_MS;\n const spawnEnv = await buildSpawnEnvForModel(model);\n\n const child = spawn('omp', [\n '--mode', 'rpc',\n '--model', model,\n '--session-dir', sessionDir,\n '--no-context-files',\n ], {\n env: spawnEnv,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n child.stdout.setEncoding('utf-8');\n child.stderr.setEncoding('utf-8');\n child.stdout.on('data', chunk => { stdout += chunk; });\n child.stderr.on('data', chunk => { stderr += chunk; });\n\n try {\n return await new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n child.kill('SIGTERM');\n reject(new Error(`Pi summary generation timed out after ${effectiveTimeout}ms`));\n }, effectiveTimeout);\n\n child.on('error', err => {\n clearTimeout(timeout);\n reject(err);\n });\n child.on('close', code => {\n clearTimeout(timeout);\n if (code !== 0 && !stdout.trim()) {\n const detail = stderr.trim() || `exit code ${code}`;\n reject(new Error(`Pi summary generation failed: ${detail}`));\n return;\n }\n const summary = stdout.trim();\n if (!summary) {\n reject(new Error('Pi summary generation returned empty output'));\n return;\n }\n resolve(summary);\n });\n\n child.stdin.end(`${JSON.stringify({ id: randomUUID(), type: 'prompt', message: prompt })}\\n`);\n });\n } finally {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n }\n}async function runModelSummaryPromise(prompt: string, model?: string, timeoutMs?: number, harness: RuntimeName = 'claude-code', allowedTools?: string[]): Promise<string> {\n const useModel = model || DEFAULT_SUMMARY_MODEL;\n console.log(`[claude-invoke] purpose=smart-summary | model=${useModel} | harness=${harness} | source=smart-compaction.ts:runModelSummary | promptChars=${prompt.length} | timeoutMs=${timeoutMs ?? SUMMARY_TIMEOUT_MS}`);\n\n if (getHarnessBehavior(harness).deliveryKind === 'rpc-fifo') {\n // Pi/omp runs in rpc mode and auto-executes tools, so it needs no allowlist.\n const summary = await runPiModelSummary(prompt, useModel, timeoutMs);\n console.log(`[claude-invoke] SUCCESS purpose=smart-summary | model=${useModel} | harness=${harness} | outputChars=${summary.length}`);\n return summary;\n }\n\n const args = [\n '-p',\n '--output-format', 'json',\n '--model', useModel,\n ...getClaudePermissionFlagsSync(),\n // Plain summary generation uses no tools; callers that instruct the model\n // to write a file (e.g. external handoff authoring) pass an allowlist so\n // the tool is auto-approved instead of stalling on a permission prompt\n // that headless `-p` mode can never answer.\n ...(allowedTools && allowedTools.length > 0 ? ['--allowedTools', ...allowedTools] : []),\n ];\n\n // Sanitize parent provider env (strip ANTHROPIC_BASE_URL etc.) and inject the\n // correct provider env for `useModel`. If provider env lookup fails (e.g.\n // missing API key), let it throw — the caller (compactConversationNative)\n // falls back to a heuristic summary.\n const spawnEnv = await buildSpawnEnvForModel(useModel);\n const injectedKeys = Object.keys(await getProviderEnvForModel(useModel));\n const command = 'claude';\n if (injectedKeys.length > 0) {\n console.log(`[smart-compaction] Spawning ${command} for summary with model ${useModel}, injecting provider env: ${injectedKeys.join(', ')}`);\n } else {\n console.log(`[smart-compaction] Spawning ${command} for summary with model ${useModel} (anthropic, parent provider env stripped)`);\n }\n\n const child = spawn(command, args, {\n env: spawnEnv,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n const effectiveTimeout = timeoutMs ?? SUMMARY_TIMEOUT_MS;\n\n return await new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n child.kill('SIGTERM');\n reject(new Error(`Summary generation timed out after ${effectiveTimeout}ms`));\n }, effectiveTimeout);\n\n child.stdin.end(prompt);\n child.stdout.setEncoding('utf-8');\n child.stderr.setEncoding('utf-8');\n child.stdout.on('data', chunk => { stdout += chunk; });\n child.stderr.on('data', chunk => { stderr += chunk; });\n child.on('error', err => {\n clearTimeout(timeout);\n reject(err);\n });\n child.on('close', code => {\n clearTimeout(timeout);\n if (code !== 0) {\n const detail = stderr.trim() || stdout.trim() || `exit code ${code}`;\n console.error(`[claude-invoke] FAILED purpose=smart-summary | model=${useModel} | error=\"exit code ${code}: ${detail.slice(0, 200)}\"`);\n reject(new Error(`Summary generation failed: ${detail}`));\n return;\n }\n const raw = stdout.trim();\n if (!raw) {\n console.error(`[claude-invoke] FAILED purpose=smart-summary | model=${useModel} | error=\"empty output\"`);\n reject(new Error(`Summary generation returned empty output`));\n return;\n }\n // `--output-format json` returns { result, usage, total_cost_usd, ... }.\n // Extract the summary text and record spend; fall back to raw stdout if\n // the output isn't the expected JSON envelope (defensive — PAN-1589).\n let summary = raw;\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n if (parsed && typeof parsed['result'] === 'string') {\n summary = (parsed['result'] as string).trim();\n recordSummaryForkCost(useModel, parsed);\n }\n } catch {\n // Not JSON — treat the raw stdout as the summary (older CLI / unexpected).\n }\n if (!summary) {\n console.error(`[claude-invoke] FAILED purpose=smart-summary | model=${useModel} | error=\"empty result\"`);\n reject(new Error(`Summary generation returned empty output`));\n return;\n }\n console.log(`[claude-invoke] SUCCESS purpose=smart-summary | model=${useModel} | outputChars=${summary.length}`);\n resolve(summary);\n });\n });\n}\n\nexport function truncateHeadTail(input: string, ceilingChars: number): string {\n if (input.length <= ceilingChars) return input;\n const truncatedCount = input.length - ceilingChars;\n const marker = `[... ${truncatedCount} characters truncated ...]`;\n const headLength = Math.ceil(ceilingChars / 2);\n const tailLength = Math.floor(ceilingChars / 2);\n return `${input.slice(0, headLength)}${marker}${input.slice(input.length - tailLength)}`;\n}\n\nexport async function generateSummaryFromPrompt(\n serialized: string,\n previousSummary: string | undefined,\n model: string | undefined,\n richMode: boolean,\n timeoutMs?: number,\n harness: RuntimeName = 'claude-code',\n): Promise<string> {\n const conversationText = `<conversation>\\n${serialized}\\n</conversation>`;\n let promptText = `${conversationText}\\n\\n`;\n\n const updatePrompt = richMode ? RICH_UPDATE_SUMMARIZATION_PROMPT : UPDATE_SUMMARIZATION_PROMPT;\n const initPrompt = richMode ? RICH_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;\n\n if (previousSummary) {\n promptText += `<previous-summary>\\n${previousSummary}\\n</previous-summary>\\n\\n${updatePrompt}`;\n } else {\n promptText += initPrompt;\n }\n\n const messages = [\n {\n role: 'user' as const,\n content: [{ type: 'text' as const, text: promptText }],\n },\n ];\n\n // Wrap in system prompt via claude -p\n const fullPrompt = `${SUMMARIZATION_SYSTEM_PROMPT}\\n\\n${messages[0].content[0].text}`;\n\n try {\n return (await Effect.runPromise(self.runModelSummary(fullPrompt, model, timeoutMs, harness)));\n } catch (err) {\n if (!isContextOverflowError(err)) throw err;\n\n const ceiling = getChunkBudgetChars(model);\n const truncatedSerialized = truncateHeadTail(serialized, ceiling);\n const truncatedPrevious = previousSummary ? truncateHeadTail(previousSummary, ceiling) : undefined;\n\n const truncatedConversationText = `<conversation>\\n${truncatedSerialized}\\n</conversation>`;\n let retryPromptText = `${truncatedConversationText}\\n\\n`;\n if (truncatedPrevious) {\n retryPromptText += `<previous-summary>\\n${truncatedPrevious}\\n</previous-summary>\\n\\n${updatePrompt}`;\n } else {\n retryPromptText += initPrompt;\n }\n const retryFullPrompt = `${SUMMARIZATION_SYSTEM_PROMPT}\\n\\n${retryPromptText}`;\n return (await Effect.runPromise(self.runModelSummary(retryFullPrompt, model, timeoutMs, harness)));\n }\n}\n\nasync function generateTurnPrefixSummary(\n serialized: string,\n model: string | undefined,\n timeoutMs?: number,\n harness: RuntimeName = 'claude-code',\n): Promise<string> {\n const promptText = `<conversation>\\n${serialized}\\n</conversation>\\n\\n${TURN_PREFIX_PROMPT}`;\n const fullPrompt = `${SUMMARIZATION_SYSTEM_PROMPT}\\n\\n${promptText}`;\n return (await Effect.runPromise(self.runModelSummary(fullPrompt, model, timeoutMs, harness)));\n}\n\n// ============================================================================\n// Chunked incremental summarization\n// ============================================================================\n\n// Per-chunk serialized-content budget (in characters). Leaves room in the\n// model's context window for the prompt template, the carried-forward\n// <previous-summary>, and the generated output. Intentionally conservative\n// — code-heavy content tokenizes denser than the 4-chars/token heuristic.\nconst CHUNK_BUDGET_CHARS_BY_MODEL: Record<string, number> = {\n 'claude-haiku-4-5-20251001': 300_000, // ~75k tokens content, 200k window\n 'claude-sonnet-5': 1_200_000, // ~300k tokens content, 1M window\n 'claude-sonnet-4-6': 1_200_000, // ~300k tokens content, 1M window\n 'claude-fable-5': 1_200_000, // ~300k tokens content, large window\n 'claude-opus-4-8': 1_200_000, // ~300k tokens content, 1M window\n 'claude-opus-4-7': 1_200_000, // ~300k tokens content, 1M window\n 'claude-opus-4-6': 1_200_000, // ~300k tokens content, 1M window\n};\nconst DEFAULT_CHUNK_BUDGET_CHARS = 300_000;\n\nfunction getChunkBudgetChars(model: string | undefined): number {\n if (!model) return DEFAULT_CHUNK_BUDGET_CHARS;\n return CHUNK_BUDGET_CHARS_BY_MODEL[model] ?? DEFAULT_CHUNK_BUDGET_CHARS;\n}\n\nfunction chunkEntriesByBudget(entries: any[], budgetChars: number): any[][] {\n const chunks: any[][] = [];\n let current: any[] = [];\n let currentChars = 0;\n for (const entry of entries) {\n const serialized = serializeEntry(entry);\n const size = serialized ? serialized.length + 2 : 0;\n if (currentChars + size > budgetChars && current.length > 0) {\n chunks.push(current);\n current = [];\n currentChars = 0;\n }\n current.push(entry);\n currentChars += size;\n }\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nasync function generateChunkedSummary(\n entries: any[],\n initialPreviousSummary: string | undefined,\n model: string | undefined,\n richMode: boolean,\n timeoutMs: number,\n includeThinking: boolean = true,\n harness: RuntimeName = 'claude-code',\n): Promise<string> {\n const budget = getChunkBudgetChars(model);\n const chunks = chunkEntriesByBudget(entries, budget);\n if (chunks.length === 0) {\n return initialPreviousSummary ?? '';\n }\n\n let running: string | undefined = initialPreviousSummary;\n for (let i = 0; i < chunks.length; i++) {\n const serialized = serializeConversation(chunks[i], includeThinking);\n if (!serialized.trim()) continue;\n console.log(\n `[smart-compaction] Summarizing chunk ${i + 1}/${chunks.length} ` +\n `(${serialized.length} chars, ${chunks[i].length} entries) with ${model ?? DEFAULT_SUMMARY_MODEL}`,\n );\n running = await generateSummaryFromPrompt(serialized, running, model, richMode, timeoutMs, harness);\n }\n\n return running ?? '';\n}\n\n/**\n * Split already-serialized transcript text into chunks that fit the summarizer\n * model's context budget. Splits on turn boundaries (the canonical serialized\n * form separates turns with a blank line) so a chunk never cuts mid-turn unless\n * a single turn is itself larger than the budget.\n */\nfunction chunkSerializedTextByBudget(text: string, budgetChars: number): string[] {\n if (text.length <= budgetChars) return text.trim() ? [text] : [];\n const turns = text.split('\\n\\n');\n const chunks: string[] = [];\n let current = '';\n for (const turn of turns) {\n const candidate = current ? `${current}\\n\\n${turn}` : turn;\n if (candidate.length > budgetChars && current) {\n chunks.push(current);\n current = turn;\n } else {\n current = candidate;\n }\n }\n if (current.trim()) chunks.push(current);\n return chunks;\n}\n\n/**\n * Harness-agnostic chunk-and-merge summarizer. Takes transcript text already\n * serialized by a ConversationTranscriptAdapter (so it works for any harness)\n * and runs the same incremental summary prompt the Claude-Code entry path uses,\n * carrying a running <previous-summary> forward across chunks. Returns the\n * final structured summary string.\n *\n * `harness` selects the summarizer LLM backend (claude-code or pi) and is\n * independent of which harness produced the transcript.\n */\nexport async function summarizeSerializedText(\n serialized: string,\n options: {\n model?: string;\n richMode?: boolean;\n timeoutMs?: number;\n harness?: RuntimeName;\n } = {},\n): Promise<string> {\n const model = options.model;\n const richMode = options.richMode ?? false;\n const timeoutMs = options.timeoutMs ?? FORK_SUMMARY_TIMEOUT_MS;\n const harness = options.harness ?? 'claude-code';\n\n const budget = getChunkBudgetChars(model);\n const chunks = chunkSerializedTextByBudget(serialized, budget);\n if (chunks.length === 0) return '';\n\n let running: string | undefined;\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i]!;\n if (!chunk.trim()) continue;\n console.log(\n `[smart-compaction] Summarizing serialized chunk ${i + 1}/${chunks.length} ` +\n `(${chunk.length} chars) with ${model ?? DEFAULT_SUMMARY_MODEL}`,\n );\n running = await generateSummaryFromPrompt(chunk, running, model, richMode, timeoutMs, harness);\n }\n return running ?? '';\n}\n\nasync function generateSmartSummaryPromise(options: CompactionOptions): Promise<CompactionResult> {\n const entries = await parseEntries(options.jsonlPath);\n if (entries.length === 0) {\n throw new Error(`Session file is empty: ${options.jsonlPath}`);\n }\n\n const isFork = options.mode === 'fork';\n // Forks summarize the entire conversation (no \"kept\" recent portion);\n // compaction keeps recent entries verbatim and only summarizes older history.\n const keepRecentTokens = options.keepRecentTokens ?? (isFork ? Number.MAX_SAFE_INTEGER : 20000);\n const model = options.model || DEFAULT_SUMMARY_MODEL;\n const richMode = options.richMode ?? false;\n const includeThinking = options.includeThinkingInSummary ?? true;\n const harness = options.harness ?? 'claude-code';\n const tokensBefore = estimateContextTokens(entries);\n\n // Detect previous compact boundary for incremental summarization\n const prevBoundary = findPreviousCompactBoundary(entries);\n let boundaryStart = 0;\n let previousSummary: string | undefined;\n if (prevBoundary) {\n boundaryStart = prevBoundary.index;\n previousSummary = prevBoundary.summary;\n }\n\n const boundaryEnd = entries.length;\n const cutPoint = findCutPoint(entries, boundaryStart, boundaryEnd, keepRecentTokens);\n\n // Determine which entries to summarize\n const historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptEntryIndex;\n\n const messagesToSummarize: any[] = [];\n for (let i = boundaryStart; i < historyEnd; i++) {\n messagesToSummarize.push(entries[i]);\n }\n\n const turnPrefixMessages: any[] = [];\n if (cutPoint.isSplitTurn) {\n for (let i = cutPoint.turnStartIndex; i < cutPoint.firstKeptEntryIndex; i++) {\n turnPrefixMessages.push(entries[i]);\n }\n }\n\n // Extract file ops from all relevant entries (summary + prefix + kept boundary)\n const fileOps = extractFileOpsFromEntries(messagesToSummarize);\n if (cutPoint.isSplitTurn) {\n const prefixOps = extractFileOpsFromEntries(turnPrefixMessages);\n prefixOps.read.forEach(f => fileOps.read.add(f));\n prefixOps.written.forEach(f => fileOps.written.add(f));\n prefixOps.edited.forEach(f => fileOps.edited.add(f));\n }\n // Also include file ops from entries AFTER the cut point up to the end\n // so the summary captures files touched in the \"kept\" recent work too\n const recentOps = extractFileOpsFromEntries(entries.slice(cutPoint.firstKeptEntryIndex));\n recentOps.read.forEach(f => fileOps.read.add(f));\n recentOps.written.forEach(f => fileOps.written.add(f));\n recentOps.edited.forEach(f => fileOps.edited.add(f));\n\n // Generate summaries\n const llmTimeoutMs = isFork ? FORK_SUMMARY_TIMEOUT_MS : undefined;\n let summary: string;\n let serializedHistory = serializeConversation(messagesToSummarize, includeThinking);\n let hasHistory = serializedHistory.trim().length > 0;\n\n // For forks, always produce a real summary. If the selected history slice is\n // empty (e.g. only metadata entries before the first valid cut point), fall\n // back to serializing the entire conversation.\n if (isFork && !hasHistory && !cutPoint.isSplitTurn) {\n const allMessages = entries.slice(boundaryStart, cutPoint.firstKeptEntryIndex);\n if (allMessages.length > 0) {\n serializedHistory = serializeConversation(allMessages, includeThinking);\n hasHistory = serializedHistory.trim().length > 0;\n }\n }\n\n const hasPrefix = cutPoint.isSplitTurn && turnPrefixMessages.length > 0;\n\n if (!hasHistory && !hasPrefix) {\n // Nothing to summarize — return previous summary or a minimal stub.\n // For forks, summarize the entire conversation via chunked incremental\n // passes (carrying <previous-summary> forward) so arbitrarily large\n // JSONL files fit the selected model's context window.\n if (isFork) {\n const forkTimeoutMs = llmTimeoutMs ?? FORK_SUMMARY_TIMEOUT_MS;\n // Summarize from the previous compact boundary (if any) forward — the\n // previousSummary already covers everything before boundaryStart.\n const forkEntries = boundaryStart > 0 ? entries.slice(boundaryStart) : entries;\n const anyContent = forkEntries.some((e) => serializeEntry(e, includeThinking));\n if (anyContent) {\n try {\n summary = await generateChunkedSummary(forkEntries, previousSummary, model, richMode, forkTimeoutMs, includeThinking, harness);\n } catch (error) {\n throw new Error(`Smart summary generation failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n if (!summary.trim()) {\n throw new Error('Smart summary generation failed: chunked summarization produced empty output');\n }\n } else {\n summary = previousSummary || '## Goal\\nContinue the current task.\\n\\n## Constraints & Preferences\\n- (none)\\n\\n## Progress\\n### Done\\n- (none)\\n\\n### In Progress\\n- [ ] Awaiting next instruction\\n\\n### Blocked\\n- (none)\\n\\n## Key Decisions\\n- (none)\\n\\n## Next Steps\\n1. Await user instruction\\n\\n## Critical Context\\n- (none)';\n }\n } else {\n const minimalStub = richMode\n ? '## Primary Request and Intent\\nContinue the current task.\\n\\n## Key Technical Concepts\\n- (none)\\n\\n## Files and Code Sections\\n- (none)\\n\\n## Errors and fixes\\n- (none)\\n\\n## Problem Solving\\nNo issues encountered.\\n\\n## All user messages\\n- (none)\\n\\n## Pending Tasks\\n- (none)\\n\\n## Current Work\\nAwaiting next instruction.\\n\\n## Optional Next Step\\nAwait user instruction.'\n : '## Goal\\nContinue the current task.\\n\\n## Constraints & Preferences\\n- (none)\\n\\n## Progress\\n### Done\\n- (none)\\n\\n### In Progress\\n- [ ] Awaiting next instruction\\n\\n### Blocked\\n- (none)\\n\\n## Key Decisions\\n- (none)\\n\\n## Next Steps\\n1. Await user instruction\\n\\n## Critical Context\\n- (none)';\n summary = previousSummary || minimalStub;\n }\n } else {\n try {\n if (hasPrefix) {\n const [historyResult, turnPrefixResult] = await Promise.all([\n hasHistory\n ? generateSummaryFromPrompt(serializedHistory, previousSummary, model, richMode, llmTimeoutMs, harness)\n : Promise.resolve('No prior history.'),\n generateTurnPrefixSummary(serializeConversation(turnPrefixMessages, includeThinking), model, llmTimeoutMs, harness),\n ]);\n summary = `${historyResult}\\n\\n---\\n\\n**Turn Context (split turn):**\\n\\n${turnPrefixResult}`;\n } else {\n summary = await generateSummaryFromPrompt(\n serializedHistory,\n previousSummary,\n model,\n richMode,\n llmTimeoutMs,\n harness,\n );\n }\n } catch (error) {\n throw new Error(`Smart summary generation failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Append file operations\n const { readFiles, modifiedFiles } = computeFileLists(fileOps);\n summary += formatFileOperations(readFiles, modifiedFiles);\n\n return {\n summary,\n tokensBefore,\n firstKeptEntryIndex: cutPoint.firstKeptEntryIndex,\n summaryModel: model,\n readFiles,\n modifiedFiles,\n };\n}\n\n// ─── Effect variants (PAN-1249, additive) ────────────────────────────────────\n//\n// Additive Effect surface for smart-compaction. The underlying Promise\n// functions remain canonical; these wrappers map failures to typed errors\n// (ProcessSpawnError for spawn / generation failures, FsError for IO).\n\n/** Effect variant of runModelSummary. */\nexport function runModelSummary(\n prompt: string,\n model?: string,\n timeoutMs?: number,\n harness: RuntimeName = 'claude-code',\n allowedTools?: string[],\n): Effect.Effect<string, ProcessSpawnError> {\n return Effect.tryPromise({\n try: () => runModelSummaryPromise(prompt, model, timeoutMs, harness, allowedTools),\n catch: (cause) =>\n new ProcessSpawnError({\n command: getHarnessBehavior(harness).deliveryKind === 'rpc-fifo' ? getHarnessBehavior(harness).executableName : 'claude',\n args: ['-p', model ?? 'default'],\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n}\n\n/** Effect variant of generateSmartSummary. */\nexport function generateSmartSummary(\n options: CompactionOptions,\n): Effect.Effect<CompactionResult, FsError | ProcessSpawnError> {\n return Effect.tryPromise({\n try: () => generateSmartSummaryPromise(options),\n catch: (cause) => {\n const msg = cause instanceof Error ? cause.message : String(cause);\n // Distinguish between fs failures (read jsonl) and spawn failures (LLM).\n // The underlying impl throws plain Error in both cases; we map to\n // FsError when the message hints at file IO, otherwise spawn.\n if (msg.toLowerCase().includes('enoent') || msg.toLowerCase().includes('no such file')) {\n return new FsError({ path: options.jsonlPath, operation: 'smart-summary-read', cause });\n }\n return new ProcessSpawnError({\n command: 'claude',\n args: ['-p', options.model ?? 'default'],\n message: msg,\n cause,\n });\n },\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"smart-compaction-ByKXTQNy.js","names":["self.runModelSummary"],"sources":["../src/lib/conversations/smart-compaction.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\nimport { randomUUID } from 'node:crypto';\nimport { mkdtemp, readFile, rm } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\nimport * as self from './smart-compaction.js';\nimport { buildSpawnEnvForModel, getProviderEnvForModel } from '../agents.js';\nimport { getClaudePermissionFlagsSync } from '../claude-permissions.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { FsError, ProcessSpawnError } from '../errors.js';\nimport { recordBackgroundAiCost } from '../background-ai/cost.js';\nimport type { AIProvider } from '../cost.js';\nimport { isContextOverflowError } from '../context-overflow.js';\n\n/** Record summary-fork/compaction spend from a `claude -p --output-format json`\n * envelope. Best-effort; never throws into the caller (PAN-1589). */\nfunction recordSummaryForkCost(model: string, envelope: Record<string, unknown>): void {\n const usage = (envelope['usage'] ?? {}) as Record<string, unknown>;\n const num = (v: unknown): number => (typeof v === 'number' ? v : 0);\n const m = model.toLowerCase();\n const provider: AIProvider = m.includes('gpt') ? 'openai' : m.includes('gemini') ? 'google' : 'anthropic';\n recordBackgroundAiCost({\n feature: 'summaryFork',\n provider,\n model,\n usage: {\n inputTokens: num(usage['input_tokens']),\n outputTokens: num(usage['output_tokens']),\n cacheReadTokens: num(usage['cache_read_input_tokens']),\n cacheWriteTokens: num(usage['cache_creation_input_tokens']),\n },\n costUsd: typeof envelope['total_cost_usd'] === 'number' ? (envelope['total_cost_usd'] as number) : undefined,\n });\n}\n\nconst SUMMARY_TIMEOUT_MS = 60_000;\nconst FORK_SUMMARY_TIMEOUT_MS = 300_000;\nconst DEFAULT_SUMMARY_MODEL = 'claude-haiku-4-5-20251001';\n\nexport interface CompactionOptions {\n jsonlPath: string;\n model?: string;\n keepRecentTokens?: number;\n reserveTokens?: number;\n richMode?: boolean;\n /** 'compact' = native compaction (returns stub if nothing to summarize). 'fork' = always produce a real summary for conversation forks. */\n mode?: 'compact' | 'fork';\n /** When true, include thinking block content in the serialized conversation sent to the summary model. Default: true. */\n includeThinkingInSummary?: boolean;\n /** Runtime harness used for model-backed summary generation. */\n harness?: RuntimeName;\n}\n\nexport interface CompactionResult {\n summary: string;\n tokensBefore: number;\n firstKeptEntryIndex: number;\n summaryModel: string | null;\n readFiles: string[];\n modifiedFiles: string[];\n}\n\ninterface FileOperations {\n read: Set<string>;\n written: Set<string>;\n edited: Set<string>;\n}\n\ninterface CutPointResult {\n firstKeptEntryIndex: number;\n turnStartIndex: number;\n isSplitTurn: boolean;\n}\n\n// ============================================================================\n// Entry parsing\n// ============================================================================\n\nexport async function parseEntries(jsonlPath: string): Promise<any[]> {\n const content = await readFile(jsonlPath, 'utf-8');\n const entries: any[] = [];\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n entries.push(JSON.parse(line));\n } catch {\n // skip malformed\n }\n }\n return entries;\n}\n\n// ============================================================================\n// Token estimation (chars/4 heuristic, conservative)\n// ============================================================================\n\nfunction estimateTokens(entry: any): number {\n if (!entry) return 0;\n\n // Compact boundary / system entries\n if (entry.type === 'system' || entry.subtype === 'compact_boundary') {\n const text = entry.content || '';\n return Math.ceil(text.length / 4);\n }\n\n // User entries (including compact summaries and tool results)\n if (entry.type === 'user' && entry.message) {\n const content = entry.message.content;\n if (typeof content === 'string') {\n return Math.ceil(content.length / 4);\n }\n if (Array.isArray(content)) {\n let chars = 0;\n for (const block of content) {\n if (block.type === 'text' && block.text) {\n chars += block.text.length;\n } else if (block.type === 'tool_result') {\n // Estimate tool result content\n if (typeof block.content === 'string') {\n chars += block.content.length;\n } else if (Array.isArray(block.content)) {\n for (const b of block.content) {\n if (b.text) chars += b.text.length;\n }\n }\n } else if (block.type === 'image') {\n chars += 4800; // ~1200 tokens\n }\n }\n return Math.ceil(chars / 4);\n }\n }\n\n // Assistant entries\n if (entry.type === 'assistant' && entry.message) {\n const content = entry.message.content;\n if (Array.isArray(content)) {\n let chars = 0;\n for (const block of content) {\n if (block.type === 'text' && block.text) {\n chars += block.text.length;\n } else if (block.type === 'thinking' && block.thinking) {\n chars += block.thinking.length;\n } else if (block.type === 'tool_use') {\n chars += block.name?.length || 0;\n chars += JSON.stringify(block.input || {}).length;\n }\n }\n return Math.ceil(chars / 4);\n }\n if (typeof content === 'string') {\n return Math.ceil(content.length / 4);\n }\n }\n\n return 0;\n}\n\nfunction estimateContextTokens(entries: any[]): number {\n // Use the last assistant usage if available, otherwise heuristic\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n if (entry.type === 'assistant' && entry.message?.usage) {\n const u = entry.message.usage;\n if (typeof u.input_tokens === 'number') {\n let total = u.input_tokens || 0;\n if (typeof u.output_tokens === 'number') total += u.output_tokens;\n if (typeof u.cache_creation_input_tokens === 'number') total += u.cache_creation_input_tokens;\n if (typeof u.cache_read_input_tokens === 'number') total += u.cache_read_input_tokens;\n if (total <= 0) continue;\n // Add trailing entries after this assistant\n for (let j = i + 1; j < entries.length; j++) {\n total += estimateTokens(entries[j]);\n }\n return total;\n }\n if (typeof u.inputTokens === 'number') {\n let total = u.inputTokens || 0;\n if (typeof u.outputTokens === 'number') total += u.outputTokens;\n if (typeof u.cacheCreationInputTokens === 'number') total += u.cacheCreationInputTokens;\n if (typeof u.cacheReadInputTokens === 'number') total += u.cacheReadInputTokens;\n if (total <= 0) continue;\n for (let j = i + 1; j < entries.length; j++) {\n total += estimateTokens(entries[j]);\n }\n return total;\n }\n }\n }\n\n // Fallback: sum all heuristics\n return entries.reduce((sum, e) => sum + estimateTokens(e), 0);\n}\n\n// ============================================================================\n// File operation tracking\n// ============================================================================\n\nfunction createFileOps(): FileOperations {\n return { read: new Set(), written: new Set(), edited: new Set() };\n}\n\nfunction extractFileOpsFromEntries(entries: any[]): FileOperations {\n const fileOps = createFileOps();\n for (const entry of entries) {\n if (entry.type !== 'assistant' || !entry.message?.content) continue;\n const content = entry.message.content;\n if (!Array.isArray(content)) continue;\n for (const block of content) {\n if (block.type !== 'tool_use' || !block.input) continue;\n const path = block.input.file_path || block.input.path;\n if (!path) continue;\n switch (block.name) {\n case 'Read':\n case 'read':\n fileOps.read.add(path);\n break;\n case 'Write':\n case 'write':\n fileOps.written.add(path);\n break;\n case 'Edit':\n case 'edit':\n fileOps.edited.add(path);\n break;\n }\n }\n }\n return fileOps;\n}\n\nfunction computeFileLists(fileOps: FileOperations): { readFiles: string[]; modifiedFiles: string[] } {\n const modified = new Set([...fileOps.edited, ...fileOps.written]);\n const readOnly = [...fileOps.read].filter(f => !modified.has(f)).sort();\n return { readFiles: readOnly, modifiedFiles: [...modified].sort() };\n}\n\nfunction formatFileOperations(readFiles: string[], modifiedFiles: string[]): string {\n const sections: string[] = [];\n if (readFiles.length > 0) {\n sections.push(`<read-files>\\n${readFiles.join('\\n')}\\n</read-files>`);\n }\n if (modifiedFiles.length > 0) {\n sections.push(`<modified-files>\\n${modifiedFiles.join('\\n')}\\n</modified-files>`);\n }\n if (sections.length === 0) return '';\n return '\\n\\n' + sections.join('\\n\\n');\n}\n\n// ============================================================================\n// Previous compact boundary detection\n// ============================================================================\n\nfunction findPreviousCompactBoundary(entries: any[]): { index: number; summary: string } | null {\n for (let i = entries.length - 1; i >= 0; i--) {\n const entry = entries[i];\n // Overdeck native compaction writes compact_boundary then a user summary entry\n if (entry.type === 'system' && entry.subtype === 'compact_boundary') {\n // Look immediately after for the summary user message\n if (i + 1 < entries.length) {\n const next = entries[i + 1];\n if (next.type === 'user' && next.isCompactSummary && next.message?.content) {\n return { index: i, summary: next.message.content };\n }\n }\n }\n // Also detect older compact summary entries\n if (entry.type === 'user' && entry.isCompactSummary && entry.message?.content) {\n return { index: i, summary: entry.message.content };\n }\n }\n return null;\n}\n\n// ============================================================================\n// Cut point detection (inspired by Pi agent)\n// ============================================================================\n\nfunction isUserMessageEntry(entry: any): boolean {\n if (entry.type !== 'user' || !entry.message) return false;\n const content = entry.message.content;\n // A pure tool_result user entry is not a turn-starting user message\n if (Array.isArray(content)) {\n const hasText = content.some((b: any) => b.type === 'text' && b.text?.trim());\n return hasText;\n }\n if (typeof content === 'string') {\n return content.trim().length > 0;\n }\n return false;\n}\n\nfunction isAssistantEntry(entry: any): boolean {\n return entry.type === 'assistant' && !!entry.message;\n}\n\nfunction findValidCutPoints(entries: any[], startIndex: number, endIndex: number): number[] {\n const cutPoints: number[] = [];\n for (let i = startIndex; i < endIndex; i++) {\n if (isUserMessageEntry(entries[i]) || isAssistantEntry(entries[i])) {\n cutPoints.push(i);\n }\n }\n return cutPoints;\n}\n\nfunction findTurnStartIndex(entries: any[], entryIndex: number, startIndex: number): number {\n for (let i = entryIndex; i >= startIndex; i--) {\n if (isUserMessageEntry(entries[i])) {\n return i;\n }\n }\n return -1;\n}\n\nfunction findCutPoint(\n entries: any[],\n startIndex: number,\n endIndex: number,\n keepRecentTokens: number,\n): CutPointResult {\n const cutPoints = findValidCutPoints(entries, startIndex, endIndex);\n if (cutPoints.length === 0) {\n return { firstKeptEntryIndex: startIndex, turnStartIndex: -1, isSplitTurn: false };\n }\n\n let accumulatedTokens = 0;\n let cutIndex = cutPoints[0];\n\n for (let i = endIndex - 1; i >= startIndex; i--) {\n const tokens = estimateTokens(entries[i]);\n accumulatedTokens += tokens;\n\n if (accumulatedTokens >= keepRecentTokens) {\n for (let c = 0; c < cutPoints.length; c++) {\n if (cutPoints[c] >= i) {\n cutIndex = cutPoints[c];\n break;\n }\n }\n break;\n }\n }\n\n // Scan backwards to include non-message metadata entries after a boundary.\n // Never scan past the previous valid cut point (or the original cut point\n // itself if it's the first valid one).\n const originalCutIndex = cutIndex;\n const cutPointArrayIndex = cutPoints.findIndex((cp) => cp === originalCutIndex);\n const minCutIndex = cutPointArrayIndex > 0 ? cutPoints[cutPointArrayIndex - 1]! + 1 : originalCutIndex;\n\n while (cutIndex > startIndex && cutIndex > minCutIndex) {\n const prev = entries[cutIndex - 1];\n if (prev.type === 'system' && prev.subtype === 'compact_boundary') break;\n if (isUserMessageEntry(prev) || isAssistantEntry(prev)) break;\n cutIndex--;\n }\n\n const isUser = isUserMessageEntry(entries[cutIndex]);\n const turnStartIndex = isUser ? -1 : findTurnStartIndex(entries, cutIndex, startIndex);\n\n return {\n firstKeptEntryIndex: cutIndex,\n turnStartIndex,\n isSplitTurn: !isUser && turnStartIndex !== -1,\n };\n}\n\n// ============================================================================\n// Conversation serialization\n// ============================================================================\n\nconst TOOL_RESULT_MAX_CHARS = 2000;\n\nfunction truncateForSummary(text: string, maxChars: number): string {\n if (text.length <= maxChars) return text;\n return `${text.slice(0, maxChars)}\\n\\n[... ${text.length - maxChars} more characters truncated]`;\n}\n\nfunction serializeEntry(entry: any, includeThinking: boolean = true): string | undefined {\n if (entry.type === 'user' && entry.message) {\n const content = entry.message.content;\n let text = '';\n if (typeof content === 'string') {\n text = content;\n } else if (Array.isArray(content)) {\n const parts: string[] = [];\n for (const block of content) {\n if (block.type === 'text' && block.text) {\n parts.push(block.text);\n } else if (block.type === 'tool_result') {\n let resultText = '';\n if (typeof block.content === 'string') {\n resultText = block.content;\n } else if (Array.isArray(block.content)) {\n resultText = block.content\n .filter((b: any) => b.type === 'text' && b.text)\n .map((b: any) => b.text)\n .join('\\n');\n }\n if (resultText) {\n parts.push(`[Tool result]: ${truncateForSummary(resultText, TOOL_RESULT_MAX_CHARS)}`);\n }\n }\n }\n text = parts.join('\\n');\n }\n if (text.trim()) {\n return `[User]: ${text.trim()}`;\n }\n }\n\n if (entry.type === 'assistant' && entry.message) {\n const content = entry.message.content;\n const parts: string[] = [];\n if (Array.isArray(content)) {\n const textParts: string[] = [];\n const toolCalls: string[] = [];\n for (const block of content) {\n if (block.type === 'text' && block.text) {\n textParts.push(block.text);\n } else if (block.type === 'thinking' && block.thinking && includeThinking) {\n textParts.push(`[thinking]: ${block.thinking}`);\n } else if (block.type === 'tool_use') {\n const args = block.input || {};\n const argsStr = Object.entries(args)\n .map(([k, v]) => `${k}=${JSON.stringify(v)}`)\n .join(', ');\n toolCalls.push(`${block.name}(${argsStr})`);\n }\n }\n if (textParts.length) parts.push(`[Assistant]: ${textParts.join('\\n')}`);\n if (toolCalls.length) parts.push(`[Assistant tool calls]: ${toolCalls.join('; ')}`);\n } else if (typeof content === 'string') {\n parts.push(`[Assistant]: ${content}`);\n }\n if (parts.length) {\n return parts.join('\\n\\n');\n }\n }\n\n if (entry.type === 'system' && entry.subtype === 'compact_boundary' && entry.content) {\n return `[Context checkpoint]: ${entry.content}`;\n }\n\n return undefined;\n}\n\nexport function serializeConversation(entries: any[], includeThinking: boolean = true): string {\n const parts: string[] = [];\n for (const entry of entries) {\n const serialized = serializeEntry(entry, includeThinking);\n if (serialized) parts.push(serialized);\n }\n return parts.join('\\n\\n');\n}\n\n// ============================================================================\n// Summarization prompts\n// ============================================================================\n\nconst SUMMARIZATION_SYSTEM_PROMPT = `You are a context summarization assistant. Your task is to read a conversation between a user and an AI coding assistant, then produce a structured summary following the exact format specified.\n\nDo NOT continue the conversation. Do NOT respond to any questions in the conversation. ONLY output the structured summary.`;\n\nconst SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a structured context checkpoint summary that another LLM will use to continue the work.\n\nUse this EXACT format:\n\n## Goal\n[What is the user trying to accomplish? Can be multiple items if the session covers different tasks.]\n\n## Constraints & Preferences\n- [Any constraints, preferences, or requirements mentioned by user]\n- [Or \"(none)\" if none were mentioned]\n\n## Progress\n### Done\n- [x] [Completed tasks/changes]\n\n### In Progress\n- [ ] [Current work]\n\n### Blocked\n- [Issues preventing progress, if any]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale]\n\n## Next Steps\n1. [Ordered list of what should happen next]\n\n## Critical Context\n- [Any data, examples, or references needed to continue]\n- [Or \"(none)\" if not applicable]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\nconst UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, and context from the new messages\n- UPDATE the Progress section: move items from \"In Progress\" to \"Done\" when completed\n- UPDATE \"Next Steps\" based on what was accomplished\n- PRESERVE exact file paths, function names, and error messages\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Goal\n[Preserve existing goals, add new ones if the task expanded]\n\n## Constraints & Preferences\n- [Preserve existing, add new ones discovered]\n\n## Progress\n### Done\n- [x] [Include previously done items AND newly completed items]\n\n### In Progress\n- [ ] [Current work - update based on progress]\n\n### Blocked\n- [Current blockers - remove if resolved]\n\n## Key Decisions\n- **[Decision]**: [Brief rationale] (preserve all previous, add new)\n\n## Next Steps\n1. [Update based on current state]\n\n## Critical Context\n- [Preserve important context, add new if needed]\n\nKeep each section concise. Preserve exact file paths, function names, and error messages.`;\n\nconst TURN_PREFIX_PROMPT = `This is the PREFIX of a turn that was too large to keep. The SUFFIX (recent work) is retained.\n\nSummarize the prefix to provide context for the retained suffix:\n\n## Original Request\n[What did the user ask for in this turn?]\n\n## Early Progress\n- [Key decisions and work done in the prefix]\n\n## Context for Suffix\n- [Information needed to understand the retained recent work]\n\nBe concise. Focus on what's needed to understand the kept suffix.`;\n\n// ============================================================================\n// Rich summarization prompts (9-section format, more verbose)\n// ============================================================================\n\nconst RICH_SUMMARIZATION_PROMPT = `The messages above are a conversation to summarize. Create a comprehensive continuation summary that another LLM will use to continue the work without losing context.\n\nUse this EXACT format:\n\n## Primary Request and Intent\n[What is the user trying to accomplish? Include explicit asks, constraints, and intended outcome.]\n\n## Key Technical Concepts\n- [Major technical topics, tools, frameworks, libraries, patterns, and architectural ideas]\n- [Or \"(none)\" if none were mentioned]\n\n## Files and Code Sections\n- [File name]: [why it matters, what was examined or changed, include important code snippets in full when applicable]\n- [Or \"(none)\" if not applicable]\n\n## Errors and fixes\n- [What went wrong and how it was fixed or addressed]\n- [Or \"(none)\" if none occurred]\n\n## Problem Solving\n[Problems resolved and any ongoing troubleshooting]\n\n## All user messages\n- [Every user message in the conversation that was not a tool result. Do not omit any.]\n- [Or \"(none)\" if not applicable]\n\n## Pending Tasks\n- [All unfinished work the user has explicitly asked for]\n- [Or \"(none)\" if not applicable]\n\n## Current Work\n[Exactly what was being worked on immediately before this summarization request. Focus on the latest messages.]\n\n## Optional Next Step\n[The next action that should be taken, but only if it directly follows from the user's most recent request. Include exact quoted lines where useful.]\n\nBe thorough, specific, and technically accurate. Preserve exact file paths, function names, error messages, and code snippets.`;\n\nconst RICH_UPDATE_SUMMARIZATION_PROMPT = `The messages above are NEW conversation messages to incorporate into the existing summary provided in <previous-summary> tags.\n\nUpdate the existing structured summary with new information. RULES:\n- PRESERVE all existing information from the previous summary\n- ADD new progress, decisions, user messages, and context from the new messages\n- UPDATE the \"Current Work\" section to reflect the latest state\n- UPDATE \"Pending Tasks\" based on what was completed or newly requested\n- PRESERVE exact file paths, function names, error messages, and code snippets\n- If something is no longer relevant, you may remove it\n\nUse this EXACT format:\n\n## Primary Request and Intent\n[Preserve existing intent, add new ones if the task expanded]\n\n## Key Technical Concepts\n- [Preserve existing, add new ones discovered]\n\n## Files and Code Sections\n- [Preserve existing files, add new ones, update changed ones with full code snippets]\n\n## Errors and fixes\n- [Preserve existing, add new ones, mark resolved if appropriate]\n\n## Problem Solving\n[Update with new resolutions and ongoing troubleshooting]\n\n## All user messages\n- [Preserve existing user messages, add new ones from the recent conversation]\n\n## Pending Tasks\n- [Update based on what was completed or newly requested]\n\n## Current Work\n[Update to reflect the latest state immediately before this request]\n\n## Optional Next Step\n[Update based on current state]\n\nBe thorough. Preserve exact file paths, function names, error messages, and code snippets.`;\n\n// ============================================================================\n// LLM call\n// ============================================================================\n\nasync function runPiModelSummary(prompt: string, model: string, timeoutMs?: number): Promise<string> {\n const sessionDir = await mkdtemp(join(tmpdir(), 'overdeck-pi-summary-'));\n const effectiveTimeout = timeoutMs ?? SUMMARY_TIMEOUT_MS;\n const spawnEnv = await buildSpawnEnvForModel(model);\n\n const child = spawn('omp', [\n '--mode', 'rpc',\n '--model', model,\n '--session-dir', sessionDir,\n '--no-context-files',\n ], {\n env: spawnEnv,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n child.stdout.setEncoding('utf-8');\n child.stderr.setEncoding('utf-8');\n child.stdout.on('data', chunk => { stdout += chunk; });\n child.stderr.on('data', chunk => { stderr += chunk; });\n\n try {\n return await new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n child.kill('SIGTERM');\n reject(new Error(`Pi summary generation timed out after ${effectiveTimeout}ms`));\n }, effectiveTimeout);\n\n child.on('error', err => {\n clearTimeout(timeout);\n reject(err);\n });\n child.on('close', code => {\n clearTimeout(timeout);\n if (code !== 0 && !stdout.trim()) {\n const detail = stderr.trim() || `exit code ${code}`;\n reject(new Error(`Pi summary generation failed: ${detail}`));\n return;\n }\n const summary = stdout.trim();\n if (!summary) {\n reject(new Error('Pi summary generation returned empty output'));\n return;\n }\n resolve(summary);\n });\n\n child.stdin.end(`${JSON.stringify({ id: randomUUID(), type: 'prompt', message: prompt })}\\n`);\n });\n } finally {\n await rm(sessionDir, { recursive: true, force: true }).catch(() => undefined);\n }\n}async function runModelSummaryPromise(prompt: string, model?: string, timeoutMs?: number, harness: RuntimeName = 'claude-code', allowedTools?: string[]): Promise<string> {\n const useModel = model || DEFAULT_SUMMARY_MODEL;\n console.log(`[claude-invoke] purpose=smart-summary | model=${useModel} | harness=${harness} | source=smart-compaction.ts:runModelSummary | promptChars=${prompt.length} | timeoutMs=${timeoutMs ?? SUMMARY_TIMEOUT_MS}`);\n\n if (getHarnessBehavior(harness).deliveryKind === 'rpc-fifo') {\n // Pi/omp runs in rpc mode and auto-executes tools, so it needs no allowlist.\n const summary = await runPiModelSummary(prompt, useModel, timeoutMs);\n console.log(`[claude-invoke] SUCCESS purpose=smart-summary | model=${useModel} | harness=${harness} | outputChars=${summary.length}`);\n return summary;\n }\n\n const args = [\n '-p',\n '--output-format', 'json',\n '--model', useModel,\n ...getClaudePermissionFlagsSync(),\n // Plain summary generation uses no tools; callers that instruct the model\n // to write a file (e.g. external handoff authoring) pass an allowlist so\n // the tool is auto-approved instead of stalling on a permission prompt\n // that headless `-p` mode can never answer.\n ...(allowedTools && allowedTools.length > 0 ? ['--allowedTools', ...allowedTools] : []),\n ];\n\n // Sanitize parent provider env (strip ANTHROPIC_BASE_URL etc.) and inject the\n // correct provider env for `useModel`. If provider env lookup fails (e.g.\n // missing API key), let it throw — the caller (compactConversationNative)\n // falls back to a heuristic summary.\n const spawnEnv = await buildSpawnEnvForModel(useModel);\n const injectedKeys = Object.keys(await getProviderEnvForModel(useModel));\n const command = 'claude';\n if (injectedKeys.length > 0) {\n console.log(`[smart-compaction] Spawning ${command} for summary with model ${useModel}, injecting provider env: ${injectedKeys.join(', ')}`);\n } else {\n console.log(`[smart-compaction] Spawning ${command} for summary with model ${useModel} (anthropic, parent provider env stripped)`);\n }\n\n const child = spawn(command, args, {\n env: spawnEnv,\n stdio: ['pipe', 'pipe', 'pipe'],\n });\n\n let stdout = '';\n let stderr = '';\n const effectiveTimeout = timeoutMs ?? SUMMARY_TIMEOUT_MS;\n\n return await new Promise<string>((resolve, reject) => {\n const timeout = setTimeout(() => {\n child.kill('SIGTERM');\n reject(new Error(`Summary generation timed out after ${effectiveTimeout}ms`));\n }, effectiveTimeout);\n\n child.stdin.end(prompt);\n child.stdout.setEncoding('utf-8');\n child.stderr.setEncoding('utf-8');\n child.stdout.on('data', chunk => { stdout += chunk; });\n child.stderr.on('data', chunk => { stderr += chunk; });\n child.on('error', err => {\n clearTimeout(timeout);\n reject(err);\n });\n child.on('close', code => {\n clearTimeout(timeout);\n if (code !== 0) {\n const detail = stderr.trim() || stdout.trim() || `exit code ${code}`;\n console.error(`[claude-invoke] FAILED purpose=smart-summary | model=${useModel} | error=\"exit code ${code}: ${detail.slice(0, 200)}\"`);\n reject(new Error(`Summary generation failed: ${detail}`));\n return;\n }\n const raw = stdout.trim();\n if (!raw) {\n console.error(`[claude-invoke] FAILED purpose=smart-summary | model=${useModel} | error=\"empty output\"`);\n reject(new Error(`Summary generation returned empty output`));\n return;\n }\n // `--output-format json` returns { result, usage, total_cost_usd, ... }.\n // Extract the summary text and record spend; fall back to raw stdout if\n // the output isn't the expected JSON envelope (defensive — PAN-1589).\n let summary = raw;\n try {\n const parsed = JSON.parse(raw) as Record<string, unknown>;\n if (parsed && typeof parsed['result'] === 'string') {\n summary = (parsed['result'] as string).trim();\n recordSummaryForkCost(useModel, parsed);\n }\n } catch {\n // Not JSON — treat the raw stdout as the summary (older CLI / unexpected).\n }\n if (!summary) {\n console.error(`[claude-invoke] FAILED purpose=smart-summary | model=${useModel} | error=\"empty result\"`);\n reject(new Error(`Summary generation returned empty output`));\n return;\n }\n console.log(`[claude-invoke] SUCCESS purpose=smart-summary | model=${useModel} | outputChars=${summary.length}`);\n resolve(summary);\n });\n });\n}\n\nexport function truncateHeadTail(input: string, ceilingChars: number): string {\n if (input.length <= ceilingChars) return input;\n const truncatedCount = input.length - ceilingChars;\n const marker = `[... ${truncatedCount} characters truncated ...]`;\n const headLength = Math.ceil(ceilingChars / 2);\n const tailLength = Math.floor(ceilingChars / 2);\n return `${input.slice(0, headLength)}${marker}${input.slice(input.length - tailLength)}`;\n}\n\nexport async function generateSummaryFromPrompt(\n serialized: string,\n previousSummary: string | undefined,\n model: string | undefined,\n richMode: boolean,\n timeoutMs?: number,\n harness: RuntimeName = 'claude-code',\n): Promise<string> {\n const conversationText = `<conversation>\\n${serialized}\\n</conversation>`;\n let promptText = `${conversationText}\\n\\n`;\n\n const updatePrompt = richMode ? RICH_UPDATE_SUMMARIZATION_PROMPT : UPDATE_SUMMARIZATION_PROMPT;\n const initPrompt = richMode ? RICH_SUMMARIZATION_PROMPT : SUMMARIZATION_PROMPT;\n\n if (previousSummary) {\n promptText += `<previous-summary>\\n${previousSummary}\\n</previous-summary>\\n\\n${updatePrompt}`;\n } else {\n promptText += initPrompt;\n }\n\n const messages = [\n {\n role: 'user' as const,\n content: [{ type: 'text' as const, text: promptText }],\n },\n ];\n\n // Wrap in system prompt via claude -p\n const fullPrompt = `${SUMMARIZATION_SYSTEM_PROMPT}\\n\\n${messages[0].content[0].text}`;\n\n try {\n return (await Effect.runPromise(self.runModelSummary(fullPrompt, model, timeoutMs, harness)));\n } catch (err) {\n if (!isContextOverflowError(err)) throw err;\n\n const ceiling = getChunkBudgetChars(model);\n const truncatedSerialized = truncateHeadTail(serialized, ceiling);\n const truncatedPrevious = previousSummary ? truncateHeadTail(previousSummary, ceiling) : undefined;\n\n const truncatedConversationText = `<conversation>\\n${truncatedSerialized}\\n</conversation>`;\n let retryPromptText = `${truncatedConversationText}\\n\\n`;\n if (truncatedPrevious) {\n retryPromptText += `<previous-summary>\\n${truncatedPrevious}\\n</previous-summary>\\n\\n${updatePrompt}`;\n } else {\n retryPromptText += initPrompt;\n }\n const retryFullPrompt = `${SUMMARIZATION_SYSTEM_PROMPT}\\n\\n${retryPromptText}`;\n return (await Effect.runPromise(self.runModelSummary(retryFullPrompt, model, timeoutMs, harness)));\n }\n}\n\nasync function generateTurnPrefixSummary(\n serialized: string,\n model: string | undefined,\n timeoutMs?: number,\n harness: RuntimeName = 'claude-code',\n): Promise<string> {\n const promptText = `<conversation>\\n${serialized}\\n</conversation>\\n\\n${TURN_PREFIX_PROMPT}`;\n const fullPrompt = `${SUMMARIZATION_SYSTEM_PROMPT}\\n\\n${promptText}`;\n return (await Effect.runPromise(self.runModelSummary(fullPrompt, model, timeoutMs, harness)));\n}\n\n// ============================================================================\n// Chunked incremental summarization\n// ============================================================================\n\n// Per-chunk serialized-content budget (in characters). Leaves room in the\n// model's context window for the prompt template, the carried-forward\n// <previous-summary>, and the generated output. Intentionally conservative\n// — code-heavy content tokenizes denser than the 4-chars/token heuristic.\nconst CHUNK_BUDGET_CHARS_BY_MODEL: Record<string, number> = {\n 'claude-haiku-4-5-20251001': 300_000, // ~75k tokens content, 200k window\n 'claude-sonnet-5': 1_200_000, // ~300k tokens content, 1M window\n 'claude-sonnet-4-6': 1_200_000, // ~300k tokens content, 1M window\n 'claude-fable-5': 1_200_000, // ~300k tokens content, large window\n 'claude-opus-4-8': 1_200_000, // ~300k tokens content, 1M window\n 'claude-opus-4-7': 1_200_000, // ~300k tokens content, 1M window\n 'claude-opus-4-6': 1_200_000, // ~300k tokens content, 1M window\n};\nconst DEFAULT_CHUNK_BUDGET_CHARS = 300_000;\n\nfunction getChunkBudgetChars(model: string | undefined): number {\n if (!model) return DEFAULT_CHUNK_BUDGET_CHARS;\n return CHUNK_BUDGET_CHARS_BY_MODEL[model] ?? DEFAULT_CHUNK_BUDGET_CHARS;\n}\n\nfunction chunkEntriesByBudget(entries: any[], budgetChars: number): any[][] {\n const chunks: any[][] = [];\n let current: any[] = [];\n let currentChars = 0;\n for (const entry of entries) {\n const serialized = serializeEntry(entry);\n const size = serialized ? serialized.length + 2 : 0;\n if (currentChars + size > budgetChars && current.length > 0) {\n chunks.push(current);\n current = [];\n currentChars = 0;\n }\n current.push(entry);\n currentChars += size;\n }\n if (current.length > 0) chunks.push(current);\n return chunks;\n}\n\nasync function generateChunkedSummary(\n entries: any[],\n initialPreviousSummary: string | undefined,\n model: string | undefined,\n richMode: boolean,\n timeoutMs: number,\n includeThinking: boolean = true,\n harness: RuntimeName = 'claude-code',\n): Promise<string> {\n const budget = getChunkBudgetChars(model);\n const chunks = chunkEntriesByBudget(entries, budget);\n if (chunks.length === 0) {\n return initialPreviousSummary ?? '';\n }\n\n let running: string | undefined = initialPreviousSummary;\n for (let i = 0; i < chunks.length; i++) {\n const serialized = serializeConversation(chunks[i], includeThinking);\n if (!serialized.trim()) continue;\n console.log(\n `[smart-compaction] Summarizing chunk ${i + 1}/${chunks.length} ` +\n `(${serialized.length} chars, ${chunks[i].length} entries) with ${model ?? DEFAULT_SUMMARY_MODEL}`,\n );\n running = await generateSummaryFromPrompt(serialized, running, model, richMode, timeoutMs, harness);\n }\n\n return running ?? '';\n}\n\n/**\n * Split already-serialized transcript text into chunks that fit the summarizer\n * model's context budget. Splits on turn boundaries (the canonical serialized\n * form separates turns with a blank line) so a chunk never cuts mid-turn unless\n * a single turn is itself larger than the budget.\n */\nfunction chunkSerializedTextByBudget(text: string, budgetChars: number): string[] {\n if (text.length <= budgetChars) return text.trim() ? [text] : [];\n const turns = text.split('\\n\\n');\n const chunks: string[] = [];\n let current = '';\n for (const turn of turns) {\n const candidate = current ? `${current}\\n\\n${turn}` : turn;\n if (candidate.length > budgetChars && current) {\n chunks.push(current);\n current = turn;\n } else {\n current = candidate;\n }\n }\n if (current.trim()) chunks.push(current);\n return chunks;\n}\n\n/**\n * Harness-agnostic chunk-and-merge summarizer. Takes transcript text already\n * serialized by a ConversationTranscriptAdapter (so it works for any harness)\n * and runs the same incremental summary prompt the Claude-Code entry path uses,\n * carrying a running <previous-summary> forward across chunks. Returns the\n * final structured summary string.\n *\n * `harness` selects the summarizer LLM backend (claude-code or pi) and is\n * independent of which harness produced the transcript.\n */\nexport async function summarizeSerializedText(\n serialized: string,\n options: {\n model?: string;\n richMode?: boolean;\n timeoutMs?: number;\n harness?: RuntimeName;\n } = {},\n): Promise<string> {\n const model = options.model;\n const richMode = options.richMode ?? false;\n const timeoutMs = options.timeoutMs ?? FORK_SUMMARY_TIMEOUT_MS;\n const harness = options.harness ?? 'claude-code';\n\n const budget = getChunkBudgetChars(model);\n const chunks = chunkSerializedTextByBudget(serialized, budget);\n if (chunks.length === 0) return '';\n\n let running: string | undefined;\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i]!;\n if (!chunk.trim()) continue;\n console.log(\n `[smart-compaction] Summarizing serialized chunk ${i + 1}/${chunks.length} ` +\n `(${chunk.length} chars) with ${model ?? DEFAULT_SUMMARY_MODEL}`,\n );\n running = await generateSummaryFromPrompt(chunk, running, model, richMode, timeoutMs, harness);\n }\n return running ?? '';\n}\n\nasync function generateSmartSummaryPromise(options: CompactionOptions): Promise<CompactionResult> {\n const entries = await parseEntries(options.jsonlPath);\n if (entries.length === 0) {\n throw new Error(`Session file is empty: ${options.jsonlPath}`);\n }\n\n const isFork = options.mode === 'fork';\n // Forks summarize the entire conversation (no \"kept\" recent portion);\n // compaction keeps recent entries verbatim and only summarizes older history.\n const keepRecentTokens = options.keepRecentTokens ?? (isFork ? Number.MAX_SAFE_INTEGER : 20000);\n const model = options.model || DEFAULT_SUMMARY_MODEL;\n const richMode = options.richMode ?? false;\n const includeThinking = options.includeThinkingInSummary ?? true;\n const harness = options.harness ?? 'claude-code';\n const tokensBefore = estimateContextTokens(entries);\n\n // Detect previous compact boundary for incremental summarization\n const prevBoundary = findPreviousCompactBoundary(entries);\n let boundaryStart = 0;\n let previousSummary: string | undefined;\n if (prevBoundary) {\n boundaryStart = prevBoundary.index;\n previousSummary = prevBoundary.summary;\n }\n\n const boundaryEnd = entries.length;\n const cutPoint = findCutPoint(entries, boundaryStart, boundaryEnd, keepRecentTokens);\n\n // Determine which entries to summarize\n const historyEnd = cutPoint.isSplitTurn ? cutPoint.turnStartIndex : cutPoint.firstKeptEntryIndex;\n\n const messagesToSummarize: any[] = [];\n for (let i = boundaryStart; i < historyEnd; i++) {\n messagesToSummarize.push(entries[i]);\n }\n\n const turnPrefixMessages: any[] = [];\n if (cutPoint.isSplitTurn) {\n for (let i = cutPoint.turnStartIndex; i < cutPoint.firstKeptEntryIndex; i++) {\n turnPrefixMessages.push(entries[i]);\n }\n }\n\n // Extract file ops from all relevant entries (summary + prefix + kept boundary)\n const fileOps = extractFileOpsFromEntries(messagesToSummarize);\n if (cutPoint.isSplitTurn) {\n const prefixOps = extractFileOpsFromEntries(turnPrefixMessages);\n prefixOps.read.forEach(f => fileOps.read.add(f));\n prefixOps.written.forEach(f => fileOps.written.add(f));\n prefixOps.edited.forEach(f => fileOps.edited.add(f));\n }\n // Also include file ops from entries AFTER the cut point up to the end\n // so the summary captures files touched in the \"kept\" recent work too\n const recentOps = extractFileOpsFromEntries(entries.slice(cutPoint.firstKeptEntryIndex));\n recentOps.read.forEach(f => fileOps.read.add(f));\n recentOps.written.forEach(f => fileOps.written.add(f));\n recentOps.edited.forEach(f => fileOps.edited.add(f));\n\n // Generate summaries\n const llmTimeoutMs = isFork ? FORK_SUMMARY_TIMEOUT_MS : undefined;\n let summary: string;\n let serializedHistory = serializeConversation(messagesToSummarize, includeThinking);\n let hasHistory = serializedHistory.trim().length > 0;\n\n // For forks, always produce a real summary. If the selected history slice is\n // empty (e.g. only metadata entries before the first valid cut point), fall\n // back to serializing the entire conversation.\n if (isFork && !hasHistory && !cutPoint.isSplitTurn) {\n const allMessages = entries.slice(boundaryStart, cutPoint.firstKeptEntryIndex);\n if (allMessages.length > 0) {\n serializedHistory = serializeConversation(allMessages, includeThinking);\n hasHistory = serializedHistory.trim().length > 0;\n }\n }\n\n const hasPrefix = cutPoint.isSplitTurn && turnPrefixMessages.length > 0;\n\n if (!hasHistory && !hasPrefix) {\n // Nothing to summarize — return previous summary or a minimal stub.\n // For forks, summarize the entire conversation via chunked incremental\n // passes (carrying <previous-summary> forward) so arbitrarily large\n // JSONL files fit the selected model's context window.\n if (isFork) {\n const forkTimeoutMs = llmTimeoutMs ?? FORK_SUMMARY_TIMEOUT_MS;\n // Summarize from the previous compact boundary (if any) forward — the\n // previousSummary already covers everything before boundaryStart.\n const forkEntries = boundaryStart > 0 ? entries.slice(boundaryStart) : entries;\n const anyContent = forkEntries.some((e) => serializeEntry(e, includeThinking));\n if (anyContent) {\n try {\n summary = await generateChunkedSummary(forkEntries, previousSummary, model, richMode, forkTimeoutMs, includeThinking, harness);\n } catch (error) {\n throw new Error(`Smart summary generation failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n if (!summary.trim()) {\n throw new Error('Smart summary generation failed: chunked summarization produced empty output');\n }\n } else {\n summary = previousSummary || '## Goal\\nContinue the current task.\\n\\n## Constraints & Preferences\\n- (none)\\n\\n## Progress\\n### Done\\n- (none)\\n\\n### In Progress\\n- [ ] Awaiting next instruction\\n\\n### Blocked\\n- (none)\\n\\n## Key Decisions\\n- (none)\\n\\n## Next Steps\\n1. Await user instruction\\n\\n## Critical Context\\n- (none)';\n }\n } else {\n const minimalStub = richMode\n ? '## Primary Request and Intent\\nContinue the current task.\\n\\n## Key Technical Concepts\\n- (none)\\n\\n## Files and Code Sections\\n- (none)\\n\\n## Errors and fixes\\n- (none)\\n\\n## Problem Solving\\nNo issues encountered.\\n\\n## All user messages\\n- (none)\\n\\n## Pending Tasks\\n- (none)\\n\\n## Current Work\\nAwaiting next instruction.\\n\\n## Optional Next Step\\nAwait user instruction.'\n : '## Goal\\nContinue the current task.\\n\\n## Constraints & Preferences\\n- (none)\\n\\n## Progress\\n### Done\\n- (none)\\n\\n### In Progress\\n- [ ] Awaiting next instruction\\n\\n### Blocked\\n- (none)\\n\\n## Key Decisions\\n- (none)\\n\\n## Next Steps\\n1. Await user instruction\\n\\n## Critical Context\\n- (none)';\n summary = previousSummary || minimalStub;\n }\n } else {\n try {\n if (hasPrefix) {\n const [historyResult, turnPrefixResult] = await Promise.all([\n hasHistory\n ? generateSummaryFromPrompt(serializedHistory, previousSummary, model, richMode, llmTimeoutMs, harness)\n : Promise.resolve('No prior history.'),\n generateTurnPrefixSummary(serializeConversation(turnPrefixMessages, includeThinking), model, llmTimeoutMs, harness),\n ]);\n summary = `${historyResult}\\n\\n---\\n\\n**Turn Context (split turn):**\\n\\n${turnPrefixResult}`;\n } else {\n summary = await generateSummaryFromPrompt(\n serializedHistory,\n previousSummary,\n model,\n richMode,\n llmTimeoutMs,\n harness,\n );\n }\n } catch (error) {\n throw new Error(`Smart summary generation failed: ${error instanceof Error ? error.message : String(error)}`);\n }\n }\n\n // Append file operations\n const { readFiles, modifiedFiles } = computeFileLists(fileOps);\n summary += formatFileOperations(readFiles, modifiedFiles);\n\n return {\n summary,\n tokensBefore,\n firstKeptEntryIndex: cutPoint.firstKeptEntryIndex,\n summaryModel: model,\n readFiles,\n modifiedFiles,\n };\n}\n\n// ─── Effect variants (PAN-1249, additive) ────────────────────────────────────\n//\n// Additive Effect surface for smart-compaction. The underlying Promise\n// functions remain canonical; these wrappers map failures to typed errors\n// (ProcessSpawnError for spawn / generation failures, FsError for IO).\n\n/** Effect variant of runModelSummary. */\nexport function runModelSummary(\n prompt: string,\n model?: string,\n timeoutMs?: number,\n harness: RuntimeName = 'claude-code',\n allowedTools?: string[],\n): Effect.Effect<string, ProcessSpawnError> {\n return Effect.tryPromise({\n try: () => runModelSummaryPromise(prompt, model, timeoutMs, harness, allowedTools),\n catch: (cause) =>\n new ProcessSpawnError({\n command: getHarnessBehavior(harness).deliveryKind === 'rpc-fifo' ? getHarnessBehavior(harness).executableName : 'claude',\n args: ['-p', model ?? 'default'],\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n}\n\n/** Effect variant of generateSmartSummary. */\nexport function generateSmartSummary(\n options: CompactionOptions,\n): Effect.Effect<CompactionResult, FsError | ProcessSpawnError> {\n return Effect.tryPromise({\n try: () => generateSmartSummaryPromise(options),\n catch: (cause) => {\n const msg = cause instanceof Error ? cause.message : String(cause);\n // Distinguish between fs failures (read jsonl) and spawn failures (LLM).\n // The underlying impl throws plain Error in both cases; we map to\n // FsError when the message hints at file IO, otherwise spawn.\n if (msg.toLowerCase().includes('enoent') || msg.toLowerCase().includes('no such file')) {\n return new FsError({ path: options.jsonlPath, operation: 'smart-summary-read', cause });\n }\n return new ProcessSpawnError({\n command: 'claude',\n args: ['-p', options.model ?? 'default'],\n message: msg,\n cause,\n });\n },\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as runModelSummary, c as truncateHeadTail, i as parseEntries, n as generateSummaryFromPrompt, o as serializeConversation, r as init_smart_compaction, s as summarizeSerializedText, t as generateSmartSummary } from "./smart-compaction-
|
|
1
|
+
import { a as runModelSummary, c as truncateHeadTail, i as parseEntries, n as generateSummaryFromPrompt, o as serializeConversation, r as init_smart_compaction, s as summarizeSerializedText, t as generateSmartSummary } from "./smart-compaction-ByKXTQNy.js";
|
|
2
2
|
init_smart_compaction();
|
|
3
3
|
export { generateSmartSummary, generateSummaryFromPrompt, parseEntries, runModelSummary, serializeConversation, summarizeSerializedText, truncateHeadTail };
|
|
@@ -99,7 +99,7 @@ function getDigestModel(projectKey, specialistType) {
|
|
|
99
99
|
async function generateContextDigestPromise(projectKey, specialistType, options = {}) {
|
|
100
100
|
ensureContextDirectory(projectKey, specialistType);
|
|
101
101
|
const runCount = options.runCount ?? getContextRunsCount(projectKey);
|
|
102
|
-
const { getRecentRunLogs } = await import("./specialist-logs-
|
|
102
|
+
const { getRecentRunLogs } = await import("./specialist-logs-pEMCTtGZ.js");
|
|
103
103
|
const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);
|
|
104
104
|
if (recentRuns.length === 0 && !options.force) {
|
|
105
105
|
console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);
|
|
@@ -113,7 +113,7 @@ async function generateContextDigestPromise(projectKey, specialistType, options
|
|
|
113
113
|
if (!existsSync(tempDir)) mkdirSync(tempDir, { recursive: true });
|
|
114
114
|
const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);
|
|
115
115
|
writeFileSync(promptFile, prompt, "utf-8");
|
|
116
|
-
const { getProviderEnvForModel } = await import("./agents-
|
|
116
|
+
const { getProviderEnvForModel } = await import("./agents-By3u_fQ8.js");
|
|
117
117
|
const providerEnv = await getProviderEnvForModel(model);
|
|
118
118
|
const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}="${v}"`).join(" ");
|
|
119
119
|
const permissionFlags = getClaudePermissionFlagsStringSync();
|
|
@@ -234,4 +234,4 @@ __esmMin((() => {
|
|
|
234
234
|
}))();
|
|
235
235
|
export { scheduleDigestGeneration };
|
|
236
236
|
|
|
237
|
-
//# sourceMappingURL=specialist-context-
|
|
237
|
+
//# sourceMappingURL=specialist-context-BUTadO-U.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"specialist-context-McuGkaeR.js","names":["loadYamlConfig"],"sources":["../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.overdeck/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { Effect } from 'effect';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { getOverdeckHome } from '../paths.js';\nimport { getClaudePermissionFlagsStringSync } from '../claude-permissions.js';\nimport type { RunLogEntry } from './specialist-logs.js';\nimport { getProjectSync } from '../projects.js';\nimport { loadConfigSync as loadYamlConfig, resolveModel } from '../config-yaml.js';\n\nfunction execAsync(command: string, options: { encoding: 'utf-8'; maxBuffer: number; timeout: number }): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n exec(command, options, (error, stdout = '', stderr = '') => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve({ stdout, stderr });\n });\n });\n}\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getOverdeckHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProjectSync(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction roleForSpecialist(specialistType: string): { role: 'plan' | 'work' | 'review' | 'test' | 'ship'; subRole?: string } {\n const normalized = specialistType.replace(/-agent$/, '');\n if (normalized === 'inspect') return { role: 'work', subRole: 'inspect' };\n if (normalized === 'review') return { role: 'review' };\n if (normalized === 'test' || normalized === 'uat') return { role: 'test' };\n if (normalized === 'merge' || normalized === 'ship') return { role: 'ship' };\n if (normalized === 'planning' || normalized === 'plan') return { role: 'plan' };\n return { role: 'work' };\n}\n\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProjectSync(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n try {\n const { role, subRole } = roleForSpecialist(specialistType);\n return resolveModel(role, subRole, loadYamlConfig().config, subRole ? undefined : `${role}:${projectKey}`);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}async function generateContextDigestPromise(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const { getRecentRunLogs } = await import('./specialist-logs.js');\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[claude-invoke] purpose=specialist-digest | model=${model} | source=specialist-context.ts:generateContextDigest | project=${projectKey} | specialist=${specialistType} | promptChars=${prompt.length}`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getOverdeckHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt (include provider env vars for non-Anthropic models)\n const { getProviderEnvForModel } = await import('../agents.js');\n const providerEnv = await getProviderEnvForModel(model);\n const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}=\"${v}\"`).join(' ');\n const permissionFlags = getClaudePermissionFlagsStringSync();\n const { stdout, stderr } = await execAsync(\n `${envPrefix ? envPrefix + ' ' : ''}claude ${permissionFlags} --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[claude-invoke] STDERR purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | stderr=\"${stderr.slice(0, 200)}\"`);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[claude-invoke] FAILED purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | error=\"empty output\"`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[claude-invoke] SUCCESS purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | outputChars=${digest.length}`);\n return digest;\n } catch (error: any) {\n console.error(`[claude-invoke] FAILED purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | error=\"${error.message}\"`);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProjectSync(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}async function regenerateContextDigestPromise(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return (await Effect.runPromise(generateContextDigest(projectKey, specialistType, { force: true })));\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n Effect.runPromise(generateContextDigest(projectKey, specialistType)).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n\n// ─── Effect variants (PAN-1249) ──────────────────────────────────────────────\n\n/**\n * Effect variant of {@link generateContextDigest}. The Promise version already\n * resolves to `null` on every failure mode (claude unavailable, empty runs,\n * write error), so the Effect form mirrors that contract via `Effect.promise`.\n */\nexport const generateContextDigest = (\n projectKey: string,\n specialistType: string,\n options: { runCount?: number; model?: string; force?: boolean } = {},\n): Effect.Effect<string | null> =>\n Effect.promise(() => generateContextDigestPromise(projectKey, specialistType, options));\n\n/**\n * Effect variant of {@link regenerateContextDigest}. Same swallowed-failure\n * semantics as the Promise version.\n */\nexport const regenerateContextDigest = (\n projectKey: string,\n specialistType: string,\n): Effect.Effect<string | null> =>\n Effect.promise(() => regenerateContextDigestPromise(projectKey, specialistType));\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"specialist-context-BUTadO-U.js","names":["loadYamlConfig"],"sources":["../src/lib/cloister/specialist-context.ts"],"sourcesContent":["/**\n * Specialist Context Management\n *\n * Generates and manages AI-powered context digests from recent specialist runs.\n * These digests seed new specialist sessions with learned patterns and expertise.\n *\n * Directory structure:\n * ~/.overdeck/specialists/{projectKey}/{specialistType}/context/latest-digest.md\n */\n\nimport { Effect } from 'effect';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { exec } from 'child_process';\nimport { getOverdeckHome } from '../paths.js';\nimport { getClaudePermissionFlagsStringSync } from '../claude-permissions.js';\nimport type { RunLogEntry } from './specialist-logs.js';\nimport { getProjectSync } from '../projects.js';\nimport { loadConfigSync as loadYamlConfig, resolveModel } from '../config-yaml.js';\n\nfunction execAsync(command: string, options: { encoding: 'utf-8'; maxBuffer: number; timeout: number }): Promise<{ stdout: string; stderr: string }> {\n return new Promise((resolve, reject) => {\n exec(command, options, (error, stdout = '', stderr = '') => {\n if (error) {\n reject(error);\n return;\n }\n\n resolve({ stdout, stderr });\n });\n });\n}\n\n/** Get specialists directory (lazy to support test env overrides) */\nfunction getSpecialistsDir(): string {\n return join(getOverdeckHome(), 'specialists');\n}\n\n/**\n * Get the context directory for a project's specialist\n */\nexport function getContextDirectory(projectKey: string, specialistType: string): string {\n return join(getSpecialistsDir(), projectKey, specialistType, 'context');\n}\n\n/**\n * Get the path to the latest context digest file\n */\nexport function getContextDigestPath(projectKey: string, specialistType: string): string {\n const contextDir = getContextDirectory(projectKey, specialistType);\n return join(contextDir, 'latest-digest.md');\n}\n\n/**\n * Ensure context directory exists for a project's specialist\n */\nfunction ensureContextDirectory(projectKey: string, specialistType: string): void {\n const contextDir = getContextDirectory(projectKey, specialistType);\n if (!existsSync(contextDir)) {\n mkdirSync(contextDir, { recursive: true });\n }\n}\n\n/**\n * Load the context digest for a specialist\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Context digest content or null if not found\n */\nexport function loadContextDigest(projectKey: string, specialistType: string): string | null {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return null;\n }\n\n try {\n return readFileSync(digestPath, 'utf-8');\n } catch (error) {\n console.error(`[specialist-context] Failed to load digest for ${projectKey}/${specialistType}:`, error);\n return null;\n }\n}\n\n/**\n * Get the number of recent runs to include in context\n *\n * Reads from project config or uses default.\n *\n * @param projectKey - Project identifier\n * @returns Number of runs to include (default: 5)\n */\nfunction getContextRunsCount(projectKey: string): number {\n const project = getProjectSync(projectKey);\n return project?.specialists?.context_runs ?? 5;\n}\n\n/**\n * Get the model to use for digest generation\n *\n * Reads from project config or uses the same model as the specialist.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns Model ID to use\n */\nfunction roleForSpecialist(specialistType: string): { role: 'plan' | 'work' | 'review' | 'test' | 'ship'; subRole?: string } {\n const normalized = specialistType.replace(/-agent$/, '');\n if (normalized === 'inspect') return { role: 'work', subRole: 'inspect' };\n if (normalized === 'review') return { role: 'review' };\n if (normalized === 'test' || normalized === 'uat') return { role: 'test' };\n if (normalized === 'merge' || normalized === 'ship') return { role: 'ship' };\n if (normalized === 'planning' || normalized === 'plan') return { role: 'plan' };\n return { role: 'work' };\n}\n\nfunction getDigestModel(projectKey: string, specialistType: string): string {\n const project = getProjectSync(projectKey);\n\n // Check for explicit digest model in project config\n if (project?.specialists?.digest_model) {\n return project.specialists.digest_model;\n }\n\n try {\n const { role, subRole } = roleForSpecialist(specialistType);\n return resolveModel(role, subRole, loadYamlConfig().config, subRole ? undefined : `${role}:${projectKey}`);\n } catch (error) {\n // Default to Sonnet if can't resolve\n return 'claude-sonnet-4-6';\n }\n}async function generateContextDigestPromise(\n projectKey: string,\n specialistType: string,\n options: {\n runCount?: number;\n model?: string;\n force?: boolean; // Generate even if no recent runs\n } = {}\n): Promise<string | null> {\n ensureContextDirectory(projectKey, specialistType);\n\n // Get recent runs\n const runCount = options.runCount ?? getContextRunsCount(projectKey);\n const { getRecentRunLogs } = await import('./specialist-logs.js');\n const recentRuns = getRecentRunLogs(projectKey, specialistType, runCount);\n\n if (recentRuns.length === 0 && !options.force) {\n console.log(`[specialist-context] No recent runs for ${projectKey}/${specialistType}, skipping digest generation`);\n return null;\n }\n\n // Build prompt for digest generation\n const prompt = buildDigestPrompt(projectKey, specialistType, recentRuns);\n const model = options.model ?? getDigestModel(projectKey, specialistType);\n\n try {\n console.log(`[claude-invoke] purpose=specialist-digest | model=${model} | source=specialist-context.ts:generateContextDigest | project=${projectKey} | specialist=${specialistType} | promptChars=${prompt.length}`);\n\n // Use Claude Code CLI to generate digest\n // Write prompt to temp file to avoid shell escaping issues\n const tempDir = join(getOverdeckHome(), 'tmp');\n if (!existsSync(tempDir)) {\n mkdirSync(tempDir, { recursive: true });\n }\n\n const promptFile = join(tempDir, `digest-prompt-${Date.now()}.md`);\n writeFileSync(promptFile, prompt, 'utf-8');\n\n // Run Claude Code with the prompt (include provider env vars for non-Anthropic models)\n const { getProviderEnvForModel } = await import('../agents.js');\n const providerEnv = await getProviderEnvForModel(model);\n const envPrefix = Object.entries(providerEnv).map(([k, v]) => `${k}=\"${v}\"`).join(' ');\n const permissionFlags = getClaudePermissionFlagsStringSync();\n const { stdout, stderr } = await execAsync(\n `${envPrefix ? envPrefix + ' ' : ''}claude ${permissionFlags} --model ${model} \"$(cat '${promptFile}')\"`,\n {\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024, // 10MB buffer\n timeout: 60000, // 60 second timeout\n }\n );\n\n // Clean up temp file\n try {\n unlinkSync(promptFile);\n } catch {\n // Ignore cleanup errors\n }\n\n if (stderr && !stderr.includes('warning')) {\n console.error(`[claude-invoke] STDERR purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | stderr=\"${stderr.slice(0, 200)}\"`);\n }\n\n const digest = stdout.trim();\n\n if (!digest) {\n console.error(`[claude-invoke] FAILED purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | error=\"empty output\"`);\n return null;\n }\n\n // Save digest\n const digestPath = getContextDigestPath(projectKey, specialistType);\n writeFileSync(digestPath, digest, 'utf-8');\n\n console.log(`[claude-invoke] SUCCESS purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | outputChars=${digest.length}`);\n return digest;\n } catch (error: any) {\n console.error(`[claude-invoke] FAILED purpose=specialist-digest | model=${model} | project=${projectKey} | specialist=${specialistType} | error=\"${error.message}\"`);\n // Degrade gracefully - return null so specialist can continue without context\n return null;\n }\n}\n\n/**\n * Build the prompt for digest generation\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @param recentRuns - Recent run logs\n * @returns Prompt for Claude\n */\nfunction buildDigestPrompt(\n projectKey: string,\n specialistType: string,\n recentRuns: RunLogEntry[]\n): string {\n const project = getProjectSync(projectKey);\n const projectName = project?.name || projectKey;\n\n let prompt = `You are analyzing the recent history of a ${specialistType} specialist for the ${projectName} project.\n\nYour task is to generate a concise context digest that will be provided to the specialist at the start of their next run. This digest should help them understand:\n- Common patterns and practices observed in recent runs\n- Recurring issues or failure modes\n- Successful approaches and best practices\n- Any project-specific context that would be helpful\n\nGenerate a digest in markdown format. Keep it focused and actionable - aim for 200-400 words total.\n\n## Recent Runs\n\n`;\n\n if (recentRuns.length === 0) {\n prompt += `No recent runs available yet. This is the specialist's first run.\\n\\n`;\n prompt += `Generate a brief introduction for the specialist explaining their role and what to expect.\\n`;\n } else {\n recentRuns.forEach((run, index) => {\n prompt += `### Run ${index + 1}: ${run.metadata.issueId} (${run.metadata.status || 'unknown'})\\n`;\n prompt += `Started: ${run.metadata.startedAt}\\n`;\n if (run.metadata.finishedAt) {\n prompt += `Finished: ${run.metadata.finishedAt}\\n`;\n }\n if (run.metadata.duration) {\n const durationSec = Math.floor(run.metadata.duration / 1000);\n const minutes = Math.floor(durationSec / 60);\n const seconds = durationSec % 60;\n prompt += `Duration: ${minutes}m ${seconds}s\\n`;\n }\n if (run.metadata.notes) {\n prompt += `Notes: ${run.metadata.notes}\\n`;\n }\n\n // Include snippets from the log if available\n try {\n const logContent = readFileSync(run.filePath, 'utf-8');\n // Extract key sections (limit to avoid overwhelming the prompt)\n const maxChars = 500;\n const transcriptMatch = logContent.match(/## Session Transcript\\n([\\s\\S]+?)(?=\\n## |$)/);\n if (transcriptMatch) {\n let transcript = transcriptMatch[1].trim();\n if (transcript.length > maxChars) {\n transcript = transcript.substring(0, maxChars) + '... [truncated]';\n }\n prompt += `\\nTranscript excerpt:\\n${transcript}\\n`;\n }\n } catch (error) {\n // If we can't read the log, skip the excerpt\n }\n\n prompt += `\\n`;\n });\n }\n\n prompt += `\\n## Your Task\n\nGenerate a context digest that summarizes the key insights from these runs. Format it as:\n\n# Recent ${specialistType} History for ${projectName}\n\n## Summary\n[2-3 sentence overview of patterns and trends]\n\n## Common Patterns\n[Bulleted list of observed patterns]\n\n## Recent Notable Runs\n[Brief highlights of 2-3 most interesting runs]\n\n## Recommendations\n[Specific guidance for the next run based on this history]\n\nKeep it concise, actionable, and focused on helping the specialist be more effective.`;\n\n return prompt;\n}async function regenerateContextDigestPromise(\n projectKey: string,\n specialistType: string\n): Promise<string | null> {\n return (await Effect.runPromise(generateContextDigest(projectKey, specialistType, { force: true })));\n}\n\n/**\n * Generate digest after a run completes (async, fire-and-forget)\n *\n * This is called after a specialist finishes a run to update the context\n * for the next run. It runs asynchronously and failures are logged but not thrown.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n */\nexport function scheduleDigestGeneration(projectKey: string, specialistType: string): void {\n // Run async without awaiting\n Effect.runPromise(generateContextDigest(projectKey, specialistType)).catch((error) => {\n console.error(\n `[specialist-context] Background digest generation failed for ${projectKey}/${specialistType}:`,\n error\n );\n });\n}\n\n/**\n * Check if a context digest exists\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest file exists\n */\nexport function hasContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n return existsSync(digestPath);\n}\n\n/**\n * Delete the context digest\n *\n * Useful for forcing a fresh start or clearing stale context.\n *\n * @param projectKey - Project identifier\n * @param specialistType - Specialist type\n * @returns True if digest was deleted, false if it didn't exist\n */\nexport function deleteContextDigest(projectKey: string, specialistType: string): boolean {\n const digestPath = getContextDigestPath(projectKey, specialistType);\n\n if (!existsSync(digestPath)) {\n return false;\n }\n\n try {\n unlinkSync(digestPath);\n return true;\n } catch (error) {\n console.error(`[specialist-context] Failed to delete digest:`, error);\n return false;\n }\n}\n\n// ─── Effect variants (PAN-1249) ──────────────────────────────────────────────\n\n/**\n * Effect variant of {@link generateContextDigest}. The Promise version already\n * resolves to `null` on every failure mode (claude unavailable, empty runs,\n * write error), so the Effect form mirrors that contract via `Effect.promise`.\n */\nexport const generateContextDigest = (\n projectKey: string,\n specialistType: string,\n options: { runCount?: number; model?: string; force?: boolean } = {},\n): Effect.Effect<string | null> =>\n Effect.promise(() => generateContextDigestPromise(projectKey, specialistType, options));\n\n/**\n * Effect variant of {@link regenerateContextDigest}. Same swallowed-failure\n * semantics as the Promise version.\n */\nexport const regenerateContextDigest = (\n projectKey: string,\n specialistType: string,\n): Effect.Effect<string | null> =>\n Effect.promise(() => regenerateContextDigestPromise(projectKey, specialistType));\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { i as __toCommonJS, t as __esmMin } from "./chunk-ruWRV7i3.js";
|
|
2
2
|
import { $ as getOverdeckHome, et as init_paths } from "./paths-3R6b989k.js";
|
|
3
3
|
import { T as projects_exports, v as init_projects } from "./projects-D2nMJ9C4.js";
|
|
4
|
-
import { J as specialists_exports, O as init_specialists } from "./specialists-
|
|
4
|
+
import { J as specialists_exports, O as init_specialists } from "./specialists-DeTkkp8b.js";
|
|
5
5
|
import { Data } from "effect";
|
|
6
6
|
import { appendFileSync, existsSync, readFileSync, readdirSync, statSync, unlinkSync } from "fs";
|
|
7
7
|
import { basename, join } from "path";
|
|
@@ -239,4 +239,4 @@ __esmMin((() => {
|
|
|
239
239
|
}))();
|
|
240
240
|
export { cleanupAllLogsSync, cleanupOldLogsSync, finalizeRunLogSync, getRecentRunLogs, getRunLogPath, getRunLogSync, listRunLogsSync, parseLogMetadata };
|
|
241
241
|
|
|
242
|
-
//# sourceMappingURL=specialist-logs-
|
|
242
|
+
//# sourceMappingURL=specialist-logs-pEMCTtGZ.js.map
|