@overdeck/core 0.45.10 → 0.45.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{agent-state-KVbJEG5f.js → agent-state-RLhS7ckO.js} +1 -1
- package/dist/{agents-pp8FO7Uj.js → agents-By3u_fQ8.js} +1 -1
- package/dist/{agents-CQHK0CSA.js → agents-CgVr77Qq.js} +12 -12
- package/dist/{agents-CQHK0CSA.js.map → agents-CgVr77Qq.js.map} +1 -1
- package/dist/{backlog-auto-trigger-CyuoS0nl.js → backlog-auto-trigger-B_-1tk4e.js} +5 -5
- package/dist/{backlog-auto-trigger-CyuoS0nl.js.map → backlog-auto-trigger-B_-1tk4e.js.map} +1 -1
- package/dist/cli/index.js +45 -45
- package/dist/{cli-Cewlu3yU.js → cli-DGeSW85B.js} +2 -2
- package/dist/{cli-Cewlu3yU.js.map → cli-DGeSW85B.js.map} +1 -1
- package/dist/cli-MTB9IOoR.js +3 -0
- package/dist/{close-issue-BJGX9_z7.js → close-issue-Djajb6Sv.js} +2 -2
- package/dist/{close-issue-BJGX9_z7.js.map → close-issue-Djajb6Sv.js.map} +1 -1
- package/dist/{concurrency-Ca-fC7tA.js → concurrency-4rzjHAQA.js} +1 -1
- package/dist/{conversation-compaction-B_6s9EvM.js → conversation-compaction-BQvZh09I.js} +4 -4
- package/dist/{conversation-compaction-B_6s9EvM.js.map → conversation-compaction-BQvZh09I.js.map} +1 -1
- package/dist/dashboard/{advancing-selfheal-CMlGznKv.js → advancing-selfheal-BezNfM3P.js} +3 -3
- package/dist/dashboard/{advancing-selfheal-CMlGznKv.js.map → advancing-selfheal-BezNfM3P.js.map} +1 -1
- package/dist/dashboard/{agent-state-xk_G8t90.js → agent-state-BUANcaA7.js} +1 -1
- package/dist/dashboard/{agent-state-service-C1iUF9Ft.js → agent-state-service-DmFpIMbP.js} +1 -1
- package/dist/dashboard/{agent-state-service-5ge9xxhx.js → agent-state-service-tXUdp6UF.js} +2 -2
- package/dist/dashboard/{agent-state-service-5ge9xxhx.js.map → agent-state-service-tXUdp6UF.js.map} +1 -1
- package/dist/dashboard/{agents-BIggLvi_.js → agents-C-vqmKDi.js} +1 -1
- package/dist/dashboard/{agents-DHvsjsuP.js → agents-C7V4odQ6.js} +11 -11
- package/dist/dashboard/{agents-DHvsjsuP.js.map → agents-C7V4odQ6.js.map} +1 -1
- package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js → backlog-auto-trigger-Cb8PR-5w.js} +2 -2
- package/dist/dashboard/{backlog-auto-trigger-COm_R96V.js.map → backlog-auto-trigger-Cb8PR-5w.js.map} +1 -1
- package/dist/dashboard/{cli-CvK9xuSS.js → cli-BgzbB6h3.js} +2 -2
- package/dist/dashboard/{cli-CvK9xuSS.js.map → cli-BgzbB6h3.js.map} +1 -1
- package/dist/dashboard/cli-DCqy_wFa.js +3 -0
- package/dist/dashboard/{close-issue-BnpErnD1.js → close-issue-BWf3pxn4.js} +2 -2
- package/dist/dashboard/{close-issue-BnpErnD1.js.map → close-issue-BWf3pxn4.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js → closed-issue-reaper-D7XPq5pY.js} +4 -4
- package/dist/dashboard/{closed-issue-reaper-DRqK5k39.js.map → closed-issue-reaper-D7XPq5pY.js.map} +1 -1
- package/dist/dashboard/{closed-issue-reaper-BLCQwTpF.js → closed-issue-reaper-VSTSTROK.js} +1 -1
- package/dist/dashboard/{conflict-gate-JdFuhKuF.js → conflict-gate-DwOslTij.js} +1 -1
- package/dist/dashboard/{conflict-gate-BhKvrPxn.js → conflict-gate-FFEvTDVA.js} +3 -3
- package/dist/dashboard/{conflict-gate-BhKvrPxn.js.map → conflict-gate-FFEvTDVA.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-C5o_eKEm.js → conversation-compaction-CCqN4hWk.js} +4 -4
- package/dist/dashboard/{conversation-compaction-C5o_eKEm.js.map → conversation-compaction-CCqN4hWk.js.map} +1 -1
- package/dist/dashboard/{conversation-compaction-Ba5aZdrb.js → conversation-compaction-CL5Ti4j4.js} +1 -1
- package/dist/dashboard/{deacon-j3qSZ7--.js → deacon-CGHl90eC.js} +107 -50
- package/dist/dashboard/{deacon-j3qSZ7--.js.map → deacon-CGHl90eC.js.map} +1 -1
- package/dist/dashboard/{deacon-Cv2RMfHs.js → deacon-DMPh40Im.js} +2 -2
- package/dist/dashboard/deacon-swarm-CVTiNL0N.js +2 -0
- package/dist/dashboard/{deacon-swarm-DadWNi5J.js → deacon-swarm-aMMJTq6t.js} +4 -4
- package/dist/dashboard/{deacon-swarm-DadWNi5J.js.map → deacon-swarm-aMMJTq6t.js.map} +1 -1
- package/dist/dashboard/deacon.js +4 -4
- package/dist/dashboard/{feedback-target-oE4-Esxl.js → feedback-target-dJjFfITP.js} +4 -4
- package/dist/dashboard/{feedback-target-oE4-Esxl.js.map → feedback-target-dJjFfITP.js.map} +1 -1
- package/dist/dashboard/{feedback-target-C4DaDSZ8.js → feedback-target-nQSM6ygL.js} +1 -1
- package/dist/dashboard/{flywheel-ClMjGW_u.js → flywheel-BLoVNtJV.js} +6 -6
- package/dist/dashboard/{flywheel-ClMjGW_u.js.map → flywheel-BLoVNtJV.js.map} +1 -1
- package/dist/dashboard/{flywheel-DefHRzY4.js → flywheel-Cqf8vu7N.js} +6 -6
- package/dist/dashboard/{flywheel-DefHRzY4.js.map → flywheel-Cqf8vu7N.js.map} +1 -1
- package/dist/dashboard/{flywheel-BqDdGvrQ.js → flywheel-PJsbrXNY.js} +1 -1
- package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js → flywheel-merge-order-B7BH8mZ3.js} +2 -2
- package/dist/dashboard/{flywheel-merge-order-CuDcqTRJ.js.map → flywheel-merge-order-B7BH8mZ3.js.map} +1 -1
- package/dist/dashboard/{flywheel-merge-order-B6YDSElh.js → flywheel-merge-order-CCwF60b-.js} +1 -1
- package/dist/dashboard/{harness-resolve-DKeSVsJL.js → harness-resolve-B3f9G1Kp.js} +2 -2
- package/dist/dashboard/{harness-resolve-DKeSVsJL.js.map → harness-resolve-B3f9G1Kp.js.map} +1 -1
- package/dist/dashboard/{inspect-agent-BKKc0XnB.js → inspect-agent-CKF5-SJr.js} +3 -3
- package/dist/dashboard/{inspect-agent-BKKc0XnB.js.map → inspect-agent-CKF5-SJr.js.map} +1 -1
- package/dist/dashboard/inspect-agent-CqpEWGWy.js +2 -0
- package/dist/dashboard/{issue-lifecycle-DnhQgddB.js → issue-lifecycle-5LbOcATP.js} +7 -7
- package/dist/dashboard/{issue-lifecycle-DnhQgddB.js.map → issue-lifecycle-5LbOcATP.js.map} +1 -1
- package/dist/dashboard/{issue-lifecycle-nY4esZn8.js → issue-lifecycle-CjYqQ669.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-Bnqv1uaa.js → issue-service-singleton-B8iu_TKV.js} +1 -1
- package/dist/dashboard/{issue-service-singleton-_AquSBTl.js → issue-service-singleton-CDGt__oQ.js} +2 -2
- package/dist/dashboard/{issue-service-singleton-_AquSBTl.js.map → issue-service-singleton-CDGt__oQ.js.map} +1 -1
- package/dist/dashboard/{lifecycle-CGKm7doE.js → lifecycle-CEg4sHZH.js} +4 -4
- package/dist/dashboard/{lifecycle-CGKm7doE.js.map → lifecycle-CEg4sHZH.js.map} +1 -1
- package/dist/dashboard/{merge-agent-Dh8Y9PDO.js → merge-agent-7nBiCytp.js} +1 -1
- package/dist/dashboard/{merge-agent-BcgAYStl.js → merge-agent-C81xPIGd.js} +6 -6
- package/dist/dashboard/{merge-agent-BcgAYStl.js.map → merge-agent-C81xPIGd.js.map} +1 -1
- package/dist/dashboard/merge-ops-DE_6CnmZ.js +2 -0
- package/dist/dashboard/{merge-train-DmxlYbEo.js → merge-train-D3qzRccL.js} +2 -2
- package/dist/dashboard/{merge-train-DmxlYbEo.js.map → merge-train-D3qzRccL.js.map} +1 -1
- package/dist/dashboard/{merge-train-deps-DBHsOWt9.js → merge-train-deps-Be5ElC_P.js} +2 -2
- package/dist/dashboard/{merge-train-deps-DBHsOWt9.js.map → merge-train-deps-Be5ElC_P.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js → orphan-proposed-reconciler-Boq_XaRe.js} +3 -3
- package/dist/dashboard/{orphan-proposed-reconciler-Db8tUhce.js.map → orphan-proposed-reconciler-Boq_XaRe.js.map} +1 -1
- package/dist/dashboard/{orphan-proposed-reconciler-mXV--tcB.js → orphan-proposed-reconciler-C0sPnDq_.js} +1 -1
- package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js → reconstruct-cache-C2pCltOq.js} +3 -3
- package/dist/dashboard/{reconstruct-cache-_tDZ2D72.js.map → reconstruct-cache-C2pCltOq.js.map} +1 -1
- package/dist/dashboard/{remote-completion-Bklh6loJ.js → remote-completion-BmxD5UMQ.js} +3 -3
- package/dist/dashboard/{remote-completion-Bklh6loJ.js.map → remote-completion-BmxD5UMQ.js.map} +1 -1
- package/dist/dashboard/resume-C-nxu9Y4.js +3 -0
- package/dist/dashboard/{review-agent-phzqNqIs.js → review-agent-D6P6d3pz.js} +26 -17
- package/dist/dashboard/review-agent-D6P6d3pz.js.map +1 -0
- package/dist/dashboard/review-agent-DTINEffB.js +3 -0
- package/dist/dashboard/{review-context-CCq56O_Y.js → review-context-Dyo0tsBa.js} +2 -2
- package/dist/dashboard/{review-context-CCq56O_Y.js.map → review-context-Dyo0tsBa.js.map} +1 -1
- package/dist/dashboard/{review-status-BQjqQXZG.js → review-status-B5ZASOo4.js} +18 -5
- package/dist/dashboard/review-status-B5ZASOo4.js.map +1 -0
- package/dist/dashboard/{review-status-bBkqUj5i.js → review-status-h7BXIA4z.js} +1 -1
- package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js → review-verdict-feedback-B70VbfRL.js} +4 -4
- package/dist/dashboard/{review-verdict-feedback-wqnikhMK.js.map → review-verdict-feedback-B70VbfRL.js.map} +1 -1
- package/dist/dashboard/{review-verdict-feedback-cZh9Wi9G.js → review-verdict-feedback-CorgH49f.js} +1 -1
- package/dist/dashboard/{sequence-io-B2GmilSI.js → sequence-io-6YP8fVpH.js} +2 -2
- package/dist/dashboard/{sequence-io-B2GmilSI.js.map → sequence-io-6YP8fVpH.js.map} +1 -1
- package/dist/dashboard/{sequencer-agent-D8pW2aU_.js → sequencer-agent-ja-nkEc1.js} +5 -5
- package/dist/dashboard/{sequencer-agent-D8pW2aU_.js.map → sequencer-agent-ja-nkEc1.js.map} +1 -1
- package/dist/dashboard/server.js +66 -55
- package/dist/dashboard/server.js.map +1 -1
- package/dist/dashboard/{service-CILd-uOt.js → service-HKDrIy0h.js} +24 -24
- package/dist/dashboard/{service-CILd-uOt.js.map → service-HKDrIy0h.js.map} +1 -1
- package/dist/dashboard/{smart-compaction-Bgp_gwcA.js → smart-compaction-L0LSnF0_.js} +1 -1
- package/dist/dashboard/{smart-compaction-CrP90OFE.js → smart-compaction-uke1npUz.js} +2 -2
- package/dist/dashboard/{smart-compaction-CrP90OFE.js.map → smart-compaction-uke1npUz.js.map} +1 -1
- package/dist/dashboard/{spawn-planning-session-D15KbPOy.js → spawn-planning-session-CSMWco8B.js} +1 -1
- package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js → spawn-planning-session-CXc0NkwV.js} +3 -3
- package/dist/dashboard/{spawn-planning-session-BmJWHSCu.js.map → spawn-planning-session-CXc0NkwV.js.map} +1 -1
- package/dist/dashboard/{specialist-context-CwKkcXL9.js → specialist-context-BGCJ24LH.js} +3 -3
- package/dist/dashboard/{specialist-context-CwKkcXL9.js.map → specialist-context-BGCJ24LH.js.map} +1 -1
- package/dist/dashboard/{specialist-logs-CvDxquV_.js → specialist-logs--Uz-r8ia.js} +2 -2
- package/dist/dashboard/{specialist-logs-CvDxquV_.js.map → specialist-logs--Uz-r8ia.js.map} +1 -1
- package/dist/dashboard/{specialists-BCjcG4uE.js → specialists-Ca9XSBCS.js} +1 -1
- package/dist/dashboard/{specialists-D07ybnam.js → specialists-DEfrAbWs.js} +36 -36
- package/dist/dashboard/{specialists-D07ybnam.js.map → specialists-DEfrAbWs.js.map} +1 -1
- package/dist/dashboard/{specialists-F2uMO4QS.js → specialists-DeZx9xom.js} +1 -1
- package/dist/dashboard/{specialists-BYi0k8CZ.js → specialists-nZ0mRwpZ.js} +12 -12
- package/dist/dashboard/{specialists-BYi0k8CZ.js.map → specialists-nZ0mRwpZ.js.map} +1 -1
- package/dist/dashboard/{summary-fork-DedqguZb.js → summary-fork-2a1L2DIU.js} +2 -2
- package/dist/dashboard/{summary-fork-EjvOkEPl.js → summary-fork-Xn0QUNmb.js} +3 -3
- package/dist/dashboard/{summary-fork-EjvOkEPl.js.map → summary-fork-Xn0QUNmb.js.map} +1 -1
- package/dist/dashboard/{teardown-workspace-5LCVbjfq.js → teardown-workspace-DhA6oAIK.js} +2 -2
- package/dist/dashboard/{teardown-workspace-5LCVbjfq.js.map → teardown-workspace-DhA6oAIK.js.map} +1 -1
- package/dist/dashboard/{test-agent-queue-qfsq6uaC.js → test-agent-queue-B_ukPyIK.js} +3 -3
- package/dist/dashboard/{test-agent-queue-qfsq6uaC.js.map → test-agent-queue-B_ukPyIK.js.map} +1 -1
- package/dist/dashboard/{uat-train-CtNGgXxj.js → uat-train-BLBAbJ8Y.js} +10 -10
- package/dist/dashboard/{uat-train-CtNGgXxj.js.map → uat-train-BLBAbJ8Y.js.map} +1 -1
- package/dist/dashboard/{uat-train-C3hWXtip.js → uat-train-DPfGR9_I.js} +1 -1
- package/dist/dashboard/{verification-runner-DuZRHjPU.js → verification-runner-KTGTNugM.js} +1 -1
- package/dist/dashboard/{verification-runner-BPAhPWR7.js → verification-runner-R2-4XgMO.js} +4 -4
- package/dist/dashboard/{verification-runner-BPAhPWR7.js.map → verification-runner-R2-4XgMO.js.map} +1 -1
- package/dist/dashboard/{workflows-H-COJ84X.js → workflows-CSKX5MhC.js} +7 -7
- package/dist/dashboard/{workflows-H-COJ84X.js.map → workflows-CSKX5MhC.js.map} +1 -1
- package/dist/dashboard/workflows-DVwXauef.js +2 -0
- package/dist/dashboard/{workspaces-CRU9_vRn.js → workspaces-DXLDgXo2.js} +32 -32
- package/dist/dashboard/{workspaces-CRU9_vRn.js.map → workspaces-DXLDgXo2.js.map} +1 -1
- package/dist/docs-index.sqlite +0 -0
- package/dist/{feedback-target-DTGAhFkM.js → feedback-target-BsKN4Tr_.js} +4 -4
- package/dist/{feedback-target-DTGAhFkM.js.map → feedback-target-BsKN4Tr_.js.map} +1 -1
- package/dist/{feedback-target-DutF088V.js → feedback-target-DM1WzOKy.js} +1 -1
- package/dist/{harness-resolve-Bz1prk6B.js → harness-resolve-B-YDNwNW.js} +2 -2
- package/dist/{harness-resolve-Bz1prk6B.js.map → harness-resolve-B-YDNwNW.js.map} +1 -1
- package/dist/{issue-lifecycle-fVqvCdKM.js → issue-lifecycle-Bc1l3U9f.js} +7 -7
- package/dist/{issue-lifecycle-fVqvCdKM.js.map → issue-lifecycle-Bc1l3U9f.js.map} +1 -1
- package/dist/{issue-service-singleton-CZm8p6Rj.js → issue-service-singleton-D-8Q6tdT.js} +1 -1
- package/dist/{issue-service-singleton-Cr73JAaR.js → issue-service-singleton-D1433I7n.js} +2 -2
- package/dist/{issue-service-singleton-Cr73JAaR.js.map → issue-service-singleton-D1433I7n.js.map} +1 -1
- package/dist/{lifecycle-CR6tZfl1.js → lifecycle-SmrqysDf.js} +7 -7
- package/dist/{lifecycle-CR6tZfl1.js.map → lifecycle-SmrqysDf.js.map} +1 -1
- package/dist/{lifecycle-D_EWiUjk.js → lifecycle-mRMyiIXs.js} +2 -2
- package/dist/{merge-agent-LM4avOtA.js → merge-agent-B9hIZ6Z0.js} +6 -6
- package/dist/{merge-agent-LM4avOtA.js.map → merge-agent-B9hIZ6Z0.js.map} +1 -1
- package/dist/{merge-agent-Bj046HE1.js → merge-agent-DUj22cXL.js} +1 -1
- package/dist/{merge-fulfillment-DrnC1Kmg.js → merge-fulfillment-__P4WE-T.js} +3 -3
- package/dist/{merge-fulfillment-DrnC1Kmg.js.map → merge-fulfillment-__P4WE-T.js.map} +1 -1
- package/dist/{reconstruct-cache-NPqoAUg-.js → reconstruct-cache-9SWp82SQ.js} +3 -3
- package/dist/{reconstruct-cache-NPqoAUg-.js.map → reconstruct-cache-9SWp82SQ.js.map} +1 -1
- package/dist/{records-BIkN1LQ4.js → records-CTmvuRzz.js} +14 -1
- package/dist/{records-BIkN1LQ4.js.map → records-CTmvuRzz.js.map} +1 -1
- package/dist/records-backfill-BQcmTmwF.js +2 -0
- package/dist/{records-backfill-HM0Pln6N.js → records-backfill-BrNsv_zT.js} +2 -2
- package/dist/{records-backfill-HM0Pln6N.js.map → records-backfill-BrNsv_zT.js.map} +1 -1
- package/dist/resume-Bn4kDk9x.js +3 -0
- package/dist/{review-agent-DFosQzCG.js → review-agent-PhqOhpx3.js} +24 -16
- package/dist/{review-agent-DFosQzCG.js.map → review-agent-PhqOhpx3.js.map} +1 -1
- package/dist/review-agent-hLbRRxZF.js +3 -0
- package/dist/{review-status-iJwfEoNX.js → review-status-B_XXomV0.js} +6 -6
- package/dist/{review-status-iJwfEoNX.js.map → review-status-B_XXomV0.js.map} +1 -1
- package/dist/{review-status-BbNVlXh1.js → review-status-Bo-EFVKh.js} +1 -1
- package/dist/{review-verdict-feedback-BGPw3z1X.js → review-verdict-feedback-CXHkoiKZ.js} +1 -1
- package/dist/{review-verdict-feedback-CCZ05atV.js → review-verdict-feedback-DGV13LDb.js} +4 -4
- package/dist/{review-verdict-feedback-CCZ05atV.js.map → review-verdict-feedback-DGV13LDb.js.map} +1 -1
- package/dist/{sequence-io-CcxZNHcD.js → sequence-io-Cg4NHAco.js} +1 -1
- package/dist/{sequence-io-Cx1usxG3.js → sequence-io-CyxvNHfA.js} +2 -2
- package/dist/{sequence-io-Cx1usxG3.js.map → sequence-io-CyxvNHfA.js.map} +1 -1
- package/dist/{smart-compaction-ByUEgQkF.js → smart-compaction-ByKXTQNy.js} +2 -2
- package/dist/{smart-compaction-ByUEgQkF.js.map → smart-compaction-ByKXTQNy.js.map} +1 -1
- package/dist/{smart-compaction-DfLe_CtH.js → smart-compaction-U6e_uB1E.js} +1 -1
- package/dist/{specialist-context-McuGkaeR.js → specialist-context-BUTadO-U.js} +3 -3
- package/dist/{specialist-context-McuGkaeR.js.map → specialist-context-BUTadO-U.js.map} +1 -1
- package/dist/{specialist-logs-CUUJ4eiB.js → specialist-logs-pEMCTtGZ.js} +2 -2
- package/dist/{specialist-logs-CUUJ4eiB.js.map → specialist-logs-pEMCTtGZ.js.map} +1 -1
- package/dist/{specialists-BSrylD1S.js → specialists-BGFnrQ8n.js} +1 -1
- package/dist/{specialists-B0elgInc.js → specialists-DeTkkp8b.js} +12 -12
- package/dist/{specialists-B0elgInc.js.map → specialists-DeTkkp8b.js.map} +1 -1
- package/dist/{summary-fork-DskMRnyb.js → summary-fork-CgzThE2-.js} +2 -2
- package/dist/{summary-fork-CPUTKcAT.js → summary-fork-Dx1RMuvU.js} +3 -3
- package/dist/{summary-fork-CPUTKcAT.js.map → summary-fork-Dx1RMuvU.js.map} +1 -1
- package/dist/{sync-BiNVQHBf.js → sync-Bsww9tuj.js} +2 -2
- package/dist/{sync-BiNVQHBf.js.map → sync-Bsww9tuj.js.map} +1 -1
- package/dist/sync-CylE_SjB.js +2 -0
- package/dist/{verdict-restore-DQiV7_fe.js → verdict-restore-DGpX0ZOJ.js} +5 -5
- package/dist/{verdict-restore-DQiV7_fe.js.map → verdict-restore-DGpX0ZOJ.js.map} +1 -1
- package/dist/{webhook-handlers-DHBSEDEm.js → webhook-handlers-BjsfIL7u.js} +3 -3
- package/dist/{webhook-handlers-DHBSEDEm.js.map → webhook-handlers-BjsfIL7u.js.map} +1 -1
- package/dist/{workspace-migrate-duUsWNpd.js → workspace-migrate-BBQ8nAnP.js} +2 -2
- package/dist/{workspace-migrate-duUsWNpd.js.map → workspace-migrate-BBQ8nAnP.js.map} +1 -1
- package/dist/{workspace-reap-DLCIoJmn.js → workspace-reap-12O9354k.js} +2 -2
- package/dist/{workspace-reap-DLCIoJmn.js.map → workspace-reap-12O9354k.js.map} +1 -1
- package/package.json +1 -1
- package/scripts/file-size-baseline.txt +1 -1
- package/dist/cli-DBWG12XK.js +0 -3
- package/dist/dashboard/cli-CJDWXE_Y.js +0 -3
- package/dist/dashboard/deacon-swarm-D1iAUX-P.js +0 -2
- package/dist/dashboard/inspect-agent-vm88mLYr.js +0 -2
- package/dist/dashboard/merge-ops-QdBiQ8XB.js +0 -2
- package/dist/dashboard/resume-DnIsmIgC.js +0 -3
- package/dist/dashboard/review-agent-DgySDb-G.js +0 -3
- package/dist/dashboard/review-agent-phzqNqIs.js.map +0 -1
- package/dist/dashboard/review-status-BQjqQXZG.js.map +0 -1
- package/dist/dashboard/workflows-Cz8OtiB4.js +0 -2
- package/dist/records-backfill-DEgtRyKL.js +0 -2
- package/dist/resume-DGymuXZM.js +0 -3
- package/dist/review-agent-DjVadURa.js +0 -3
- package/dist/sync-4jqukkxz.js +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents-CQHK0CSA.js","names":["join","existsSync","readFileSync","join","readFileSync","randomUUID","readFile","fetchAgentRuntimeSnapshot","mkdirAsync","writeFileAsync","loadYamlConfig","loadYamlConfig","NodeServicesLayer","platform","ChildProcessSpawner","join","existsSync","execAsync","openSync","fsConstants","exec","execAsync","exec","renameAsync","statAsync","execAsync","loadYamlConfig","parseYaml","promisify","loadYamlConfig","loadYamlConfig","loadConfigSync","join","existsSync","readFileSync","createReadStream","existsSync","stat","httpRequest","parse","execAsync","promisify","mkdirAsync","writeFileAsync","writeFileAsync","execAsync","escapeRegExp","promisify","join","existsSync","readdirSync","statSync","execAsync","exec","hasAgentRuntimeInSession","exec","execFileAsync","execFile","readFile","join","execFileAsync","execFile","execFile"],"sources":["../src/lib/overdeck/agent-rollback-state.ts","../src/lib/agent-runtime.ts","../src/lib/agent-runtime-mirror.ts","../src/lib/overdeck/conversation-cost-session.ts","../src/lib/overdeck/conversations.ts","../src/lib/agents/runtime-state.ts","../src/lib/agents/identity.ts","../src/lib/cloister/auto-resume-config.ts","../src/lib/overdeck/agent-record-sync.ts","../src/lib/persistent-logger.ts","../src/lib/agents/agent-state.ts","../src/lib/agents/supervisor-channels.ts","../src/lib/openai-auth.ts","../src/lib/provider-health.ts","../src/lib/agents/provider-env.ts","../src/lib/claude-auth.ts","../src/lib/runtimes/ohmypi-fifo.ts","../src/lib/runtimes/pi-fifo.ts","../src/lib/agents/runtime-command.ts","../src/lib/agents/resolve-tier.ts","../src/lib/agents/staffing.ts","../src/lib/agents/spawn-prep.ts","../src/lib/cv.ts","../src/lib/hooks.ts","../src/lib/bridge-token.ts","../src/lib/transcript-landing.ts","../src/lib/agents/delivery.ts","../src/lib/cloister/config.ts","../src/lib/cloister/memory-verdict-cache.ts","../src/lib/cloister/concurrency.ts","../src/lib/agents/queries.ts","../src/lib/agents/termination.ts","../src/lib/agents/spawn.ts","../src/lib/cost-parsers/ohmypi-parser.ts","../src/lib/runtimes/ohmypi.ts","../src/lib/agents/activity.ts","../src/lib/operator-interventions.ts","../src/lib/session-rotation.ts","../src/lib/agents/messaging.ts","../src/lib/context-overflow.ts","../src/lib/agents/resume.ts","../src/lib/graceful-restart.ts","../src/lib/agents/recovery.ts","../src/lib/vbrief/swarm-readiness.ts","../src/lib/agents/slot-reconcile.ts","../src/lib/agents/standing-tiers.ts","../src/lib/agents/tier-escalation.ts","../src/lib/agents/tier-feed.ts","../src/lib/agents/tier-supervisor.ts","../src/lib/agents/tier-replay.ts","../src/lib/agents.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { getOverdeckHome } from '../paths.js';\nimport type { AgentState } from '../agents.js';\n\nconst AGENT_STATE_ROLLBACK_FILE = 'state' + '.json';\n\nexport function getRollbackAgentStatePath(agentId: string): string {\n return join(getOverdeckHome(), 'agents', agentId, AGENT_STATE_ROLLBACK_FILE);\n}\n\nexport function readRollbackAgentStateSync<T>(\n agentId: string,\n parse: (content: string, normalizedId: string) => T | null,\n): T | null {\n const stateFile = getRollbackAgentStatePath(agentId);\n if (!existsSync(stateFile)) return null;\n return parse(readFileSync(stateFile, 'utf8'), agentId);\n}\n\nexport function writeRollbackAgentStateSync(\n state: AgentState,\n serialize: (state: AgentState) => string,\n): void {\n const stateFile = getRollbackAgentStatePath(state.id);\n mkdirSync(join(getOverdeckHome(), 'agents', state.id), { recursive: true });\n writeFileSync(stateFile, serialize(state));\n}\n\nexport async function readRollbackAgentHarnessFromDir(\n agentsDir: string,\n agentId: string,\n): Promise<string | null> {\n try {\n const raw = await readFile(join(agentsDir, agentId, AGENT_STATE_ROLLBACK_FILE), 'utf8');\n const state = JSON.parse(raw) as { harness?: unknown };\n return typeof state.harness === 'string' ? state.harness : null;\n } catch {\n return null;\n }\n}\n","/**\n * PAN-800 — out-of-process client for the canonical AgentRuntimeSnapshot.\n *\n * Every caller that previously read/wrote ~/.overdeck/agents/<id>/runtime.json\n * now goes through this module. There is no file fallback: the dashboard's\n * AgentStateService SubscriptionRef is the single source of truth for runtime\n * snapshots. CLI and lib modules that need runtime state hit the HTTP API.\n * Server-reachable code inside the dashboard process yields AgentStateService\n * directly (zero-roundtrip) — this module is for everything else.\n */\n\nimport { Data, Effect } from 'effect'\nimport type {\n AgentRuntimeSnapshot,\n Activity,\n AgentResolution,\n ChannelReplyArtifactRef,\n ChannelReplyKind,\n WaitingReason,\n} from '@overdeck/contracts'\nimport { ensureInternalTokenSync, INTERNAL_TOKEN_HEADER } from './internal-token.js'\n\n// Use 127.0.0.1 explicitly: when /etc/hosts resolves `localhost` to ::1\n// (IPv6 first), Node's undici-based fetch() connects to [::1]:3011 and\n// fails because the dashboard listens on the IPv4 wildcard 0.0.0.0.\n// curl falls back to IPv4; Node's fetch in this version does not.\nconst DASHBOARD_URL = process.env['OVERDECK_DASHBOARD_URL'] || 'http://127.0.0.1:3011'\nconst DEFAULT_TIMEOUT_MS = 1500\n\nfunction abortSignal(ms: number): AbortSignal {\n const controller = new AbortController()\n setTimeout(() => controller.abort(), ms).unref?.()\n return controller.signal\n}\n\nclass AgentRuntimeFetchError extends Data.TaggedError('AgentRuntimeFetchError')<{\n readonly url: string\n readonly cause?: unknown\n}> {}\n\nexport const getAgentRuntimeSnapshot = (\n agentId: string,\n): Effect.Effect<AgentRuntimeSnapshot | null> => {\n if (!agentId) return Effect.succeed(null)\n const url = `${DASHBOARD_URL}/api/agents/${encodeURIComponent(agentId)}/runtime`\n return Effect.gen(function* () {\n const res = yield* Effect.tryPromise({\n try: () => fetch(url, { signal: abortSignal(DEFAULT_TIMEOUT_MS) }),\n catch: (cause) => new AgentRuntimeFetchError({ url, cause }),\n })\n if (!res.ok) return null\n const body = yield* Effect.tryPromise({\n try: () => res.json() as Promise<{ success: boolean; snapshot?: AgentRuntimeSnapshot }>,\n catch: (cause) => new AgentRuntimeFetchError({ url, cause }),\n })\n return body.success && body.snapshot ? body.snapshot : null\n }).pipe(Effect.orElseSucceed(() => null))\n}\n\ntype HeartbeatBody =\n | { kind: 'activity'; activity: Activity; tool?: string }\n | { kind: 'thinking_start'; lastToolAt: string }\n | { kind: 'thinking_stop'; resolvedBy: 'tool' | 'waiting' | 'idle' | 'stopped' }\n | { kind: 'waiting_start'; reason: WaitingReason; message?: string }\n | { kind: 'waiting_clear'; clearedBy: 'user_response' | 'timeout' | 'stopped' | 'tool_resumed' }\n | { kind: 'message_received'; direction: 'to_agent' | 'from_agent'; source: 'user' | 'cloister' | 'specialist' | 'automated' }\n | { kind: 'channel_reply'; reply: { kind: ChannelReplyKind; summary: string; artifactRefs?: ChannelReplyArtifactRef[] } }\n | { kind: 'model_set'; model: string; claudeSessionId?: string; sessionModel?: string; sessionHarness?: string }\n | { kind: 'resolution_set'; resolution: AgentResolution; resolutionCount: number }\n | { kind: 'current_issue_set'; currentIssue?: string }\n | { kind: 'context_saturation_changed'; contextSaturatedAt?: string }\n\n/**\n * Emit a runtime event. Returns true if the dashboard accepted it, false on\n * any failure (timeout, network error, non-2xx). Callers get fire-and-forget\n * semantics — no retry, no buffering. The bash hooks have their own\n * pending-events.jsonl fallback; in-process callers just log and move on.\n */\nexport const emitAgentEvent = (\n agentId: string,\n body: HeartbeatBody,\n): Effect.Effect<boolean> => {\n if (!agentId) return Effect.succeed(false)\n const url = `${DASHBOARD_URL}/api/agents/${encodeURIComponent(agentId)}/heartbeat`\n return Effect.gen(function* () {\n // The heartbeat route requires the internal token\n // (validateAgentRuntimeEventAuth). Without it every event POSTed from a\n // CLI process was silently 403'd — a codex slot agent's `pan done`\n // resolution never reached AgentStateService, so the swarm coordinator\n // classified finished slots as still running, forever. The bash hooks\n // attach this same header; the lib emitter must too.\n const token = yield* Effect.try({\n try: (): string => ensureInternalTokenSync(),\n catch: (cause) => new AgentRuntimeFetchError({ url, cause }),\n }).pipe(Effect.orElseSucceed((): string | null => null))\n if (!token) return false\n const res = yield* Effect.tryPromise({\n try: () =>\n fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', [INTERNAL_TOKEN_HEADER]: token },\n body: JSON.stringify({ ...body, timestamp: new Date().toISOString() }),\n signal: abortSignal(DEFAULT_TIMEOUT_MS),\n }),\n catch: (cause) => new AgentRuntimeFetchError({ url, cause }),\n })\n return res.ok\n }).pipe(Effect.orElseSucceed(() => false))\n}\n\n// ─── Convenience wrappers mirroring the event taxonomy ────────────────────────\n\nexport const emitActivity = (\n agentId: string,\n activity: Activity,\n tool?: string,\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'activity', activity, tool })\n\nexport const emitWaitingStart = (\n agentId: string,\n reason: WaitingReason,\n message?: string,\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'waiting_start', reason, message })\n\nexport const emitWaitingClear = (\n agentId: string,\n clearedBy: 'user_response' | 'timeout' | 'stopped' | 'tool_resumed' = 'user_response',\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'waiting_clear', clearedBy })\n\nexport const emitModelSet = (\n agentId: string,\n model: string,\n claudeSessionId?: string,\n sessionOrigin?: { sessionModel?: string; sessionHarness?: string },\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'model_set', model, claudeSessionId, ...sessionOrigin })\n\nexport const emitMessageReceived = (\n agentId: string,\n direction: 'to_agent' | 'from_agent',\n source: 'user' | 'cloister' | 'specialist' | 'automated',\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'message_received', direction, source })\n\nexport const emitChannelReply = (\n agentId: string,\n reply: { kind: ChannelReplyKind; summary: string; artifactRefs?: ChannelReplyArtifactRef[] },\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'channel_reply', reply })\n\nexport const emitResolution = (\n agentId: string,\n resolution: AgentResolution,\n resolutionCount: number,\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'resolution_set', resolution, resolutionCount })\n\nexport const emitContextSaturationChanged = (\n agentId: string,\n contextSaturatedAt?: string,\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'context_saturation_changed', contextSaturatedAt })\n","/**\n * PAN-800 — in-process mirror of AgentStateService's canonical ref.\n *\n * The dashboard server writes into this mirror on every event fold so lib code\n * running in the same process can read the latest AgentRuntimeSnapshot.\n * CLI/out-of-process callers should prefer the async HTTP path in agent-runtime.ts.\n *\n * All exports are Effect-native (PAN-1249 wave-1 migration).\n */\n\nimport { Effect } from 'effect';\nimport type { AgentRuntimeSnapshot } from '@overdeck/contracts';\n\nlet mirror: Record<string, AgentRuntimeSnapshot> = {};\n\n/**\n * Set to true by AgentStateServiceLive's layer construction. When true, the\n * async HTTP adapter in agent-runtime.ts short-circuits to the sync mirror\n * instead of making a localhost HTTP call — we're running IN the dashboard,\n * so fetching our own HTTP server would deadlock if it's still bootstrapping.\n */\nlet inProcessService = false;\n\nexport function markAgentStateServiceInProcess(): Effect.Effect<void, never> {\n return Effect.sync(() => {\n inProcessService = true;\n });\n}\n\nexport function isAgentStateServiceInProcess(): Effect.Effect<boolean, never> {\n return Effect.sync(() => inProcessService);\n}\n\nexport function setAgentRuntimeMirror(next: Record<string, AgentRuntimeSnapshot>): Effect.Effect<void, never> {\n return Effect.sync(() => {\n mirror = next;\n });\n}\n\nexport function getRuntimeSnapshot(agentId: string): Effect.Effect<AgentRuntimeSnapshot | null, never> {\n return Effect.sync(() => mirror[agentId] ?? null);\n}\n","import { getOverdeckDatabaseSync } from './infra.js';\n\nexport function findConversationForCostSessionSync(input: {\n sessionId?: string | null;\n agentId?: string | null;\n}): { name: string } | null {\n const db = getOverdeckDatabaseSync();\n if (input.sessionId) {\n const row = db.prepare(\n `SELECT c.name\n FROM conversation_files cf\n JOIN conversations c ON c.id = cf.conversation_id\n WHERE cf.locator = ?\n ORDER BY cf.created_at DESC, cf.id DESC\n LIMIT 1`,\n ).get(input.sessionId) as { name: string } | undefined;\n if (row) return { name: row.name };\n }\n if (!input.agentId?.startsWith('conv-')) return null;\n const row = db.prepare(\n `SELECT c.name\n FROM conversations c\n WHERE c.tmux_session = ?\n ORDER BY c.created_at DESC\n LIMIT 1`,\n ).get(input.agentId) as { name: string } | undefined;\n return row ? { name: row.name } : null;\n}\n","/**\n * Conversations domain — ConversationsResolver (read door) + ConversationWriter (write door)\n * + TranscriptsResolver (shared read door over JSONL index) + TranscriptsWriter (cache-maint).\n *\n * Critical invariant: the backing session files (claude/pi/codex JSONL) are SACRED.\n * ConversationWriter touches only the DB and creates NEW backing files via forkNewFile;\n * it NEVER mutates, truncates, appends-to, or deletes an existing one.\n * TranscriptsResolver has no write methods at all — mechanically enforced by the surface.\n *\n * Writer durability diverges from Issues: conversations have NO git mirror.\n * The DB row IS the source of truth (schema 90-96). ConversationWriter has no Records dep.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nimport { Context, Effect, Layer, Schema, Stream } from 'effect';\nimport { and, eq, isNotNull, isNull } from 'drizzle-orm';\nimport { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core';\nimport { HttpApiEndpoint, HttpApiGroup } from 'effect/unstable/httpapi';\n\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getOverdeckHome } from '../paths.js';\nimport { Db, EventBus, getOverdeckDatabaseSync } from './infra.js';\nimport { getEventStore } from '../../dashboard/server/event-store.js';\nimport { ensureDiscoveredSessionsSchema } from './discovered-sessions.js';\n\n// ── Local Drizzle table definitions ──────────────────────────────────────────\n// Mirror locked schema (docs/overdeck-remodel/overdeck-schema.ts:97-163).\n// No FK or index annotations here — those live in the compiled migration only.\n\nconst conversationsTable = sqliteTable('conversations', {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n cwd: text('cwd').notNull(),\n issueId: text('issue_id'),\n harness: text('harness'),\n model: text('model'),\n effort: text('effort'),\n title: text('title'),\n titleSource: text('title_source'),\n createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),\n archivedAt: integer('archived_at', { mode: 'timestamp_ms' }),\n handoffDocPath: text('handoff_doc_path'),\n handoffTargetConvId: text('handoff_target_conv_id'),\n clearedToConvId: text('cleared_to_conv_id'),\n tmuxSession: text('tmux_session'),\n status: text('status').notNull().default('active'),\n endedAt: integer('ended_at', { mode: 'timestamp_ms' }),\n lastAttachedAt: integer('last_attached_at', { mode: 'timestamp_ms' }),\n sessionFile: text('session_file'),\n totalCost: real('total_cost').default(0),\n totalTokens: integer('total_tokens').default(0),\n forkStatus: text('fork_status'),\n forkError: text('fork_error'),\n forkRetryCount: integer('fork_retry_count').notNull().default(0),\n forkRequest: text('fork_request'),\n forkFallbackReason: text('fork_fallback_reason'),\n deliveryMethod: text('delivery_method'),\n spawnError: text('spawn_error'),\n});\n\nconst conversationFilesTable = sqliteTable('conversation_files', {\n id: integer('id').primaryKey({ autoIncrement: true }),\n conversationId: text('conversation_id').notNull(),\n harness: text('harness').notNull(),\n locator: text('locator').notNull(),\n createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),\n});\n\nconst favoritesTable = sqliteTable('favorites', {\n type: text('type').notNull(),\n itemId: text('item_id').notNull(),\n createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),\n});\n\nconst transcriptsTable = sqliteTable('transcripts', {\n backingFilePath: text('backing_file_path').primaryKey(),\n sessionId: text('session_id'),\n harness: text('harness'),\n workspacePath: text('workspace_path'),\n messageCount: integer('message_count'),\n models: text('models', { mode: 'json' }).$type<string[] | null>(),\n tokenInput: integer('token_input'),\n tokenOutput: integer('token_output'),\n firstTs: integer('first_ts', { mode: 'timestamp_ms' }),\n lastTs: integer('last_ts', { mode: 'timestamp_ms' }),\n panIssueId: text('pan_issue_id'),\n panAgentId: text('pan_agent_id'),\n});\n\n// ── Entity schemas ────────────────────────────────────────────────────────────\n\nexport const ConversationId = Schema.String.pipe(Schema.brand('ConversationId'));\nexport type ConversationId = typeof ConversationId.Type;\n\nexport const ConversationName = Schema.String.pipe(Schema.brand('ConversationName'));\nexport type ConversationName = typeof ConversationName.Type;\n\nexport const Harness = Schema.Literals(['claude-code', 'pi', 'codex', 'kimi']);\nexport type Harness = typeof Harness.Type;\n\nexport const TitleSource = Schema.Literals(['manual', 'auto', 'ai', 'default']);\nexport type TitleSource = typeof TitleSource.Type;\n\nexport const FavoriteType = Schema.Literals(['conversation', 'project']);\nexport type FavoriteType = typeof FavoriteType.Type;\n\nexport const BackingFile = Schema.Struct({\n harness: Harness,\n locator: Schema.String,\n createdAt: Schema.Date,\n});\nexport type BackingFile = typeof BackingFile.Type;\n\nexport const Conversation = Schema.Struct({\n id: ConversationId,\n name: ConversationName,\n cwd: Schema.String,\n issueId: Schema.NullOr(Schema.String),\n harness: Schema.NullOr(Harness),\n model: Schema.NullOr(Schema.String),\n effort: Schema.NullOr(Schema.String),\n title: Schema.NullOr(Schema.String),\n titleSource: Schema.NullOr(TitleSource),\n createdAt: Schema.Date,\n archivedAt: Schema.NullOr(Schema.Date),\n handoffDocPath: Schema.NullOr(Schema.String),\n handoffTargetConvId: Schema.NullOr(ConversationId),\n clearedToConvId: Schema.NullOr(ConversationId),\n files: Schema.Array(BackingFile),\n});\nexport type Conversation = typeof Conversation.Type;\n\nexport const ConversationFilter = Schema.Struct({\n archived: Schema.optional(Schema.Boolean),\n issueId: Schema.optional(Schema.String),\n});\nexport type ConversationFilter = typeof ConversationFilter.Type;\n\nexport const ParsedTranscript = Schema.Struct({\n messages: Schema.Array(Schema.Unknown),\n messageCount: Schema.Number,\n models: Schema.Array(Schema.String),\n firstTs: Schema.NullOr(Schema.Date),\n lastTs: Schema.NullOr(Schema.Date),\n});\nexport type ParsedTranscript = typeof ParsedTranscript.Type;\n\nexport const TranscriptSubject = Schema.Union([Conversation, ConversationName]);\nexport type TranscriptSubject = typeof TranscriptSubject.Type;\n\nexport const Transcript = Schema.Struct({\n backingFilePath: Schema.String,\n sessionId: Schema.NullOr(Schema.String),\n harness: Schema.NullOr(Harness),\n workspacePath: Schema.NullOr(Schema.String),\n messageCount: Schema.NullOr(Schema.Number),\n models: Schema.NullOr(Schema.Array(Schema.String)),\n tokenInput: Schema.NullOr(Schema.Number),\n tokenOutput: Schema.NullOr(Schema.Number),\n firstTs: Schema.NullOr(Schema.Date),\n lastTs: Schema.NullOr(Schema.Date),\n panIssueId: Schema.NullOr(Schema.String),\n panAgentId: Schema.NullOr(Schema.String),\n});\nexport type Transcript = typeof Transcript.Type;\n\n// ── Error types ───────────────────────────────────────────────────────────────\n\nexport class ConversationNotFound extends Schema.TaggedErrorClass<ConversationNotFound>()(\n 'ConversationNotFound', { name: ConversationName },\n) {}\n\nexport class AlreadyArchived extends Schema.TaggedErrorClass<AlreadyArchived>()(\n 'AlreadyArchived', { name: ConversationName },\n) {}\n\nexport class NotArchived extends Schema.TaggedErrorClass<NotArchived>()(\n 'NotArchived', { name: ConversationName },\n) {}\n\n// ── Internal sync decoders (known-shape DB rows) ──────────────────────────────\n\nconst decodeConversation = Schema.decodeUnknownSync(Conversation);\nconst decodeTranscript = Schema.decodeUnknownSync(Transcript);\nconst decodeBackingFile = Schema.decodeUnknownSync(BackingFile);\n\n// ── Row type aliases ───────────────────────────────────────────────────────────\n\ntype ConvRow = typeof conversationsTable.$inferSelect;\ntype FileRow = typeof conversationFilesTable.$inferSelect;\ntype TransRow = typeof transcriptsTable.$inferSelect;\n\n// ── Internal row mappers ───────────────────────────────────────────────────────\n\nfunction rowToBackingFile(r: FileRow): BackingFile {\n return decodeBackingFile({ harness: r.harness, locator: r.locator, createdAt: r.createdAt });\n}\n\nfunction rowToConversation(row: ConvRow, files: FileRow[]): Conversation {\n return decodeConversation({\n id: row.id,\n name: row.name,\n cwd: row.cwd,\n issueId: row.issueId ?? null,\n harness: row.harness ?? null,\n model: row.model ?? null,\n effort: row.effort ?? null,\n title: row.title ?? null,\n titleSource: row.titleSource ?? null,\n createdAt: row.createdAt,\n archivedAt: row.archivedAt ?? null,\n handoffDocPath: row.handoffDocPath ?? null,\n handoffTargetConvId: row.handoffTargetConvId ?? null,\n clearedToConvId: row.clearedToConvId ?? null,\n files: files.map(rowToBackingFile),\n });\n}\n\nfunction rowToTranscript(r: TransRow): Transcript {\n return decodeTranscript({\n backingFilePath: r.backingFilePath,\n sessionId: r.sessionId ?? null,\n harness: r.harness ?? null,\n workspacePath: r.workspacePath ?? null,\n messageCount: r.messageCount ?? null,\n models: r.models ?? null,\n tokenInput: r.tokenInput ?? null,\n tokenOutput: r.tokenOutput ?? null,\n firstTs: r.firstTs ?? null,\n lastTs: r.lastTs ?? null,\n panIssueId: r.panIssueId ?? null,\n panAgentId: r.panAgentId ?? null,\n });\n}\n\n// ── ConversationsResolver — read door ─────────────────────────────────────────\n\nexport class ConversationsResolver extends Context.Service<ConversationsResolver, {\n readonly get: (name: ConversationName) => Effect.Effect<Conversation, ConversationNotFound>;\n readonly list: (f: ConversationFilter) => Effect.Effect<ReadonlyArray<Conversation>>;\n readonly getCurrent: () => Effect.Effect<Conversation, ConversationNotFound>;\n readonly getHandoffDoc: (name: ConversationName) => Effect.Effect<string, ConversationNotFound>;\n}>()('overdeck/ConversationsResolver') {}\n\nexport const ConversationsResolverLive = Layer.effect(\n ConversationsResolver,\n Effect.gen(function* () {\n const db = yield* Db;\n\n const withFiles = (row: ConvRow): Effect.Effect<Conversation> =>\n Effect.gen(function* () {\n const files = yield* Effect.promise(() =>\n db.q.select().from(conversationFilesTable).where(eq(conversationFilesTable.conversationId, row.id)),\n );\n return rowToConversation(row, files);\n });\n\n const get = (name: ConversationName): Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.gen(function* () {\n const [row] = yield* Effect.promise(() =>\n db.q.select().from(conversationsTable).where(eq(conversationsTable.name, name)),\n );\n if (!row) return yield* Effect.fail(new ConversationNotFound({ name }));\n return yield* withFiles(row);\n });\n\n const list = (f: ConversationFilter): Effect.Effect<ReadonlyArray<Conversation>> =>\n Effect.gen(function* () {\n const rows = yield* Effect.promise(() => {\n const base = db.q.select().from(conversationsTable);\n const conds = [];\n if (f.archived === true) conds.push(isNotNull(conversationsTable.archivedAt));\n if (f.archived === false) conds.push(isNull(conversationsTable.archivedAt));\n if (f.issueId) conds.push(eq(conversationsTable.issueId, f.issueId));\n return conds.length > 0 ? base.where(and(...conds)) : base;\n });\n return yield* Effect.forEach(rows, withFiles);\n });\n\n const getCurrent = (): Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.fail(new ConversationNotFound({ name: '' as ConversationName }));\n\n const getHandoffDoc = (name: ConversationName): Effect.Effect<string, ConversationNotFound> =>\n Effect.gen(function* () {\n const conv = yield* get(name);\n if (!conv.handoffDocPath) return yield* Effect.fail(new ConversationNotFound({ name }));\n return yield* Effect.promise(() => readFile(conv.handoffDocPath!, 'utf-8'));\n });\n\n return ConversationsResolver.of({ get, list, getCurrent, getHandoffDoc });\n }),\n);\n\n// ── TranscriptsResolver — shared read door (JSONL index + sacred file reads) ──\n// Read-only: no method writes a backing file or the transcripts index.\n\nexport class TranscriptsResolver extends Context.Service<TranscriptsResolver, {\n readonly resolveFiles: (subject: TranscriptSubject) => Effect.Effect<ReadonlyArray<string>>;\n readonly parse: (subject: TranscriptSubject) => Effect.Effect<ParsedTranscript>;\n readonly serialize: (subject: TranscriptSubject) => Effect.Effect<string>;\n readonly watch: (subject: TranscriptSubject) => Stream.Stream<ParsedTranscript>;\n readonly get: (key: string) => Effect.Effect<Transcript>;\n readonly list: (f: ConversationFilter) => Effect.Effect<ReadonlyArray<Transcript>>;\n readonly stats: () => Effect.Effect<{ count: number; managed: number }>;\n readonly search: (query: string) => Effect.Effect<ReadonlyArray<Transcript>>;\n}>()('overdeck/TranscriptsResolver') {}\n\nexport const TranscriptsResolverLive = Layer.effect(\n TranscriptsResolver,\n Effect.gen(function* () {\n const db = yield* Db;\n\n const resolveFiles = (_subject: TranscriptSubject): Effect.Effect<ReadonlyArray<string>> =>\n Effect.succeed([]);\n\n const parse = (_subject: TranscriptSubject): Effect.Effect<ParsedTranscript> =>\n Effect.succeed({ messages: [], messageCount: 0, models: [], firstTs: null, lastTs: null });\n\n const serialize = (_subject: TranscriptSubject): Effect.Effect<string> =>\n Effect.succeed('');\n\n const watch = (_subject: TranscriptSubject): Stream.Stream<ParsedTranscript> =>\n Stream.empty;\n\n const get = (key: string): Effect.Effect<Transcript> =>\n Effect.gen(function* () {\n const [row] = yield* Effect.promise(() =>\n db.q.select().from(transcriptsTable).where(eq(transcriptsTable.backingFilePath, key)),\n );\n if (!row) return yield* Effect.die(`Transcript not found: ${key}`);\n return rowToTranscript(row);\n });\n\n const list = (_f: ConversationFilter): Effect.Effect<ReadonlyArray<Transcript>> =>\n Effect.gen(function* () {\n const rows = yield* Effect.promise(() => db.q.select().from(transcriptsTable));\n return rows.map(rowToTranscript);\n });\n\n const stats = (): Effect.Effect<{ count: number; managed: number }> =>\n Effect.gen(function* () {\n const rows = yield* Effect.promise(() => db.q.select().from(transcriptsTable));\n return {\n count: rows.length,\n managed: rows.filter(r => r.panAgentId != null).length,\n };\n });\n\n const search = (_query: string): Effect.Effect<ReadonlyArray<Transcript>> =>\n Effect.succeed([]);\n\n return TranscriptsResolver.of({ resolveFiles, parse, serialize, watch, get, list, stats, search });\n }),\n);\n\n// ── TranscriptsWriter — cache-maintenance write door ─────────────────────────\n// Mutates the `transcripts` index (rebuilt from JSONL); NEVER writes a sacred file.\n\nexport class TranscriptsWriter extends Context.Service<TranscriptsWriter, {\n readonly scan: (dirs?: ReadonlyArray<string>) => Effect.Effect<{ scanned: number }>;\n readonly rebuild: (dirs?: ReadonlyArray<string>) => Effect.Effect<{ scanned: number }>;\n readonly enrich: (ids?: ReadonlyArray<string>) => Effect.Effect<{ enriched: number }>;\n readonly embed: (ids?: ReadonlyArray<string>) => Effect.Effect<{ embedded: number }>;\n}>()('overdeck/TranscriptsWriter') {}\n\nexport const TranscriptsWriterLive = Layer.succeed(\n TranscriptsWriter,\n TranscriptsWriter.of({\n scan: (_dirs?) => Effect.succeed({ scanned: 0 }),\n rebuild: (_dirs?) => Effect.succeed({ scanned: 0 }),\n enrich: (_ids?) => Effect.succeed({ enriched: 0 }),\n embed: (_ids?) => Effect.succeed({ embedded: 0 }),\n }),\n);\n\n// ── ConversationWriter — write door ───────────────────────────────────────────\n// Writes ONLY the DB (conversations / favorites / conversation_files tables) and\n// creates NEW backing files via the fork primitive. NEVER mutates an existing file.\n// NO Records dependency — the DB row IS the source of truth (no git mirror).\n\nexport class ConversationWriter extends Context.Service<ConversationWriter, {\n readonly create: (opts: {\n name: ConversationName; cwd: string; model?: string; effort?: string;\n harness?: Harness; issueId?: string; title?: string;\n }) => Effect.Effect<Conversation>;\n readonly archive: (name: ConversationName) => Effect.Effect<Conversation, ConversationNotFound | AlreadyArchived>;\n readonly unarchive: (name: ConversationName) => Effect.Effect<Conversation, ConversationNotFound | NotArchived>;\n readonly setFavorite: (type: 'conversation' | 'project', itemId: string) => Effect.Effect<void>;\n readonly unsetFavorite: (type: 'conversation' | 'project', itemId: string) => Effect.Effect<void>;\n readonly retitle: (name: ConversationName, title: string, source: 'manual' | 'auto' | 'ai') =>\n Effect.Effect<Conversation, ConversationNotFound>;\n readonly setModel: (name: ConversationName, model: string) => Effect.Effect<Conversation, ConversationNotFound>;\n readonly setHarness: (name: ConversationName, harness: Harness) => Effect.Effect<Conversation, ConversationNotFound>;\n readonly handoff: (source: ConversationName, target: ConversationName, docPath: string) =>\n Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound>;\n readonly clear: (source: ConversationName) =>\n Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound>;\n readonly summaryFork: (source: ConversationName, opts: { mode: 'summary' | 'plain'; model?: string }) =>\n Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound>;\n readonly compact: (name: ConversationName) =>\n Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound>;\n}>()('overdeck/ConversationWriter') {}\n\nexport const ConversationWriterLive = Layer.effect(\n ConversationWriter,\n Effect.gen(function* () {\n const db = yield* Db;\n const bus = yield* EventBus;\n const resolver = yield* ConversationsResolver;\n const now = () => new Date();\n\n const create = (opts: {\n name: ConversationName; cwd: string; model?: string; effort?: string;\n harness?: Harness; issueId?: string; title?: string;\n }): Effect.Effect<Conversation> =>\n Effect.gen(function* () {\n const id = randomUUID() as unknown as ConversationId;\n const ts = now();\n yield* Effect.promise(() =>\n db.q.insert(conversationsTable).values({\n id,\n name: opts.name,\n cwd: opts.cwd,\n issueId: opts.issueId ?? null,\n harness: opts.harness ?? null,\n model: opts.model ?? null,\n effort: opts.effort ?? null,\n title: opts.title ?? null,\n titleSource: opts.title ? 'manual' : null,\n createdAt: ts,\n }).onConflictDoNothing(),\n );\n yield* bus.emit({ type: 'conversation.created', payload: { name: opts.name } });\n return decodeConversation({\n id, name: opts.name, cwd: opts.cwd,\n issueId: opts.issueId ?? null, harness: opts.harness ?? null,\n model: opts.model ?? null, effort: opts.effort ?? null,\n title: opts.title ?? null, titleSource: opts.title ? 'manual' : null,\n createdAt: ts, archivedAt: null,\n handoffDocPath: null, handoffTargetConvId: null, clearedToConvId: null,\n files: [],\n });\n });\n\n const archive = (name: ConversationName): Effect.Effect<Conversation, ConversationNotFound | AlreadyArchived> =>\n Effect.gen(function* () {\n const conv = yield* resolver.get(name);\n if (conv.archivedAt) return yield* Effect.fail(new AlreadyArchived({ name }));\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ archivedAt: now() }).where(eq(conversationsTable.name, name)),\n );\n yield* Effect.promise(() =>\n db.q.delete(favoritesTable).where(\n and(eq(favoritesTable.type, 'conversation'), eq(favoritesTable.itemId, name)),\n ),\n );\n yield* bus.emit({ type: 'conversation.archived', payload: { name } });\n return yield* resolver.get(name);\n });\n\n const unarchive = (name: ConversationName): Effect.Effect<Conversation, ConversationNotFound | NotArchived> =>\n Effect.gen(function* () {\n const conv = yield* resolver.get(name);\n if (!conv.archivedAt) return yield* Effect.fail(new NotArchived({ name }));\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ archivedAt: null }).where(eq(conversationsTable.name, name)),\n );\n yield* bus.emit({ type: 'conversation.unarchived', payload: { name } });\n return yield* resolver.get(name);\n });\n\n const setFavorite = (type: 'conversation' | 'project', itemId: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* Effect.promise(() =>\n db.q.insert(favoritesTable).values({ type, itemId, createdAt: now() }).onConflictDoNothing(),\n );\n yield* bus.emit({ type: 'conversation.favorited', payload: { type, itemId } });\n });\n\n const unsetFavorite = (type: 'conversation' | 'project', itemId: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* Effect.promise(() =>\n db.q.delete(favoritesTable).where(\n and(eq(favoritesTable.type, type), eq(favoritesTable.itemId, itemId)),\n ),\n );\n yield* bus.emit({ type: 'conversation.unfavorited', payload: { type, itemId } });\n });\n\n const retitle = (name: ConversationName, title: string, source: 'manual' | 'auto' | 'ai'):\n Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.gen(function* () {\n yield* resolver.get(name);\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ title, titleSource: source }).where(eq(conversationsTable.name, name)),\n );\n yield* bus.emit({ type: 'conversation.retitled', payload: { name, title, source } });\n return yield* resolver.get(name);\n });\n\n const setModel = (name: ConversationName, model: string):\n Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.gen(function* () {\n yield* resolver.get(name);\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ model }).where(eq(conversationsTable.name, name)),\n );\n yield* bus.emit({ type: 'conversation.modelChanged', payload: { name, model } });\n return yield* resolver.get(name);\n });\n\n const setHarness = (name: ConversationName, harness: Harness):\n Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.gen(function* () {\n yield* resolver.get(name);\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ harness }).where(eq(conversationsTable.name, name)),\n );\n yield* bus.emit({ type: 'conversation.harnessChanged', payload: { name, harness } });\n return yield* resolver.get(name);\n });\n\n // The fork primitive — the ONLY mechanism that creates a new backing file.\n // Creates a fresh UUID locator, registers a conversation_files pointer, records lineage.\n // NEVER opens an existing file for write.\n const forkNewFile = (\n source: ConversationName,\n kind: 'handoff' | 'clear' | 'summary' | 'plain' | 'harness-switch' | 'compaction',\n opts?: { docPath?: string },\n ): Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound> =>\n Effect.gen(function* () {\n const conv = yield* resolver.get(source);\n const newLocator = randomUUID();\n yield* Effect.promise(() =>\n db.q.insert(conversationFilesTable).values({\n conversationId: conv.id,\n harness: conv.harness ?? 'claude-code',\n locator: newLocator,\n createdAt: now(),\n }).onConflictDoNothing(),\n );\n if (kind === 'handoff') {\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({\n handoffDocPath: opts?.docPath ?? null,\n handoffTargetConvId: conv.id,\n }).where(eq(conversationsTable.name, source)),\n );\n } else if (kind === 'clear') {\n yield* Effect.promise(() =>\n db.q.update(conversationsTable)\n .set({ clearedToConvId: conv.id })\n .where(eq(conversationsTable.name, source)),\n );\n }\n yield* bus.emit({ type: 'conversation.forked', payload: { source, kind } });\n return { conversation: conv, backingFile: newLocator };\n });\n\n const handoff = (source: ConversationName, _target: ConversationName, docPath: string) =>\n forkNewFile(source, 'handoff', { docPath });\n\n const clear = (source: ConversationName) =>\n forkNewFile(source, 'clear');\n\n const summaryFork = (source: ConversationName, opts: { mode: 'summary' | 'plain'; model?: string }) =>\n forkNewFile(source, opts.mode === 'summary' ? 'summary' : 'plain');\n\n const compact = (name: ConversationName) =>\n forkNewFile(name, 'compaction');\n\n return ConversationWriter.of({\n create, archive, unarchive, setFavorite, unsetFavorite,\n retitle, setModel, setHarness,\n handoff, clear, summaryFork, compact,\n });\n }),\n);\n\n// ── ConversationsApi — HttpApiGroup (controller declarations) ─────────────────\n// Handlers wire in at bootstrap; R = ConversationsResolver | TranscriptsResolver |\n// ConversationWriter, never Db directly.\n\nexport const ConversationsApi = HttpApiGroup.make('conversations')\n .add(HttpApiEndpoint.get('list', '/conversations', {\n success: Schema.Array(Conversation),\n }))\n .add(HttpApiEndpoint.get('get', '/conversations/:name', {\n params: Schema.Struct({ name: ConversationName }),\n success: Conversation,\n error: ConversationNotFound,\n }))\n .add(HttpApiEndpoint.get('getHandoffDoc', '/conversations/:name/handoff-doc', {\n params: Schema.Struct({ name: ConversationName }),\n success: Schema.String,\n error: ConversationNotFound,\n }))\n .add(HttpApiEndpoint.post('create', '/conversations', {\n success: Conversation,\n }))\n .add(HttpApiEndpoint.post('archive', '/conversations/:name/archive', {\n params: Schema.Struct({ name: ConversationName }),\n success: Conversation,\n error: [ConversationNotFound, AlreadyArchived],\n }))\n .add(HttpApiEndpoint.post('unarchive', '/conversations/:name/unarchive', {\n params: Schema.Struct({ name: ConversationName }),\n success: Conversation,\n error: [ConversationNotFound, NotArchived],\n }));\n\n// ── Legacy-compatible sync door ──────────────────────────────────────────────\n//\n// These exports keep the current dashboard/CLI call sites synchronous while\n// moving the storage boundary to overdeck.db. They must not call the legacy\n// panopticon.db conversation helpers.\n\nexport type LegacyTitleSource = 'auto' | 'ai' | 'ai-refined' | 'ai-explicit' | 'manual' | 'default';\n\nexport interface ForkRequest {\n parentConversationName: string;\n sessionId: string;\n forkMode: 'summary' | 'plain' | 'handoff';\n summaryModel?: string;\n localSummaryOnly: boolean;\n includeThinkingInSummary?: boolean;\n summaryHarness?: RuntimeName;\n handoffFocus?: string;\n handoffAuthor: 'source' | 'external';\n handoffAuthorModel?: string;\n handoffAuthorHarness?: RuntimeName;\n}\n\nexport interface LegacyConversation {\n id: number;\n name: string;\n tmuxSession: string;\n status: 'active' | 'ended';\n cwd: string;\n issueId: string | null;\n createdAt: string;\n endedAt: string | null;\n lastAttachedAt: string | null;\n claudeSessionId: string | null;\n title: string | null;\n titleSource: LegacyTitleSource | null;\n titleSeed: string | null;\n totalCost: number;\n totalTokens: number;\n archivedAt: string | null;\n model: string | null;\n effort: string | null;\n forkStatus: string | null;\n forkError: string | null;\n harness: RuntimeName | null;\n deliveryMethod: 'auto' | 'channels' | 'tmux' | null;\n spawnError: string | null;\n handoffDocPath: string | null;\n handoffTargetConvId: number | null;\n forkFallbackReason: string | null;\n clearedToConvId: number | null;\n forkRequest: string | null;\n forkRetryCount: number;\n}\n\nexport interface ArchivedConversationWithEnrichment {\n id: number;\n name: string;\n cwd: string;\n issueId: string | null;\n createdAt: string;\n claudeSessionId: string | null;\n harness: RuntimeName | null;\n title: string | null;\n totalCost: number;\n archivedAt: string;\n model: string | null;\n discoveredJsonlPath: string | null;\n discoveredWorkspacePath: string | null;\n messageCount: number | null;\n firstTs: string | null;\n lastTs: string | null;\n primaryModel: string | null;\n tokenInput: number | null;\n tokenOutput: number | null;\n estimatedCost: number | null;\n toolsUsed: string | null;\n filesTouched: string | null;\n tags: string | null;\n summary: string | null;\n enrichmentLevel: number | null;\n enrichmentFailed: number | null;\n}\n\nexport interface ArchivedConversationListOptions {\n harness?: RuntimeName;\n workspacePath?: string;\n primaryModel?: string;\n unmanaged?: boolean;\n since?: string;\n before?: string;\n after?: string;\n minCost?: number;\n maxCost?: number;\n minMessages?: number;\n issueId?: string;\n enriched?: boolean;\n notEnriched?: boolean;\n enrichmentLevel?: number;\n enrichmentLevelLessThan?: number;\n tags?: string[];\n tools?: string[];\n files?: string[];\n limit?: number;\n offset?: number;\n}\n\nexport type LegacyFavoriteType = 'conversation';\n\ninterface LegacyConversationRow {\n legacy_id: number;\n id: string;\n name: string;\n cwd: string;\n issue_id: string | null;\n harness: string | null;\n model: string | null;\n effort: string | null;\n title: string | null;\n title_source: string | null;\n created_at: number | Date;\n archived_at: number | Date | null;\n handoff_doc_path: string | null;\n handoff_target_conv_id: string | null;\n cleared_to_conv_id: string | null;\n claude_session_id: string | null;\n tmux_session: string | null;\n status: string | null;\n ended_at: number | null;\n last_attached_at: number | null;\n session_file: string | null;\n total_cost: number | null;\n total_tokens: number | null;\n fork_status: string | null;\n fork_error: string | null;\n fork_retry_count: number | null;\n fork_request: string | null;\n fork_fallback_reason: string | null;\n delivery_method: string | null;\n spawn_error: string | null;\n}\n\nconst LEGACY_CONVERSATION_SELECT = `\n SELECT\n c.rowid AS legacy_id,\n c.id,\n c.name,\n c.cwd,\n c.issue_id,\n c.harness,\n c.model,\n c.effort,\n c.title,\n c.title_source,\n c.created_at,\n c.archived_at,\n c.handoff_doc_path,\n c.handoff_target_conv_id,\n c.cleared_to_conv_id,\n c.tmux_session,\n c.status,\n c.ended_at,\n c.last_attached_at,\n c.session_file,\n c.total_cost,\n c.total_tokens,\n c.fork_status,\n c.fork_error,\n c.fork_retry_count,\n c.fork_request,\n c.fork_fallback_reason,\n c.delivery_method,\n c.spawn_error,\n (\n SELECT cf.locator\n FROM conversation_files cf\n WHERE cf.conversation_id = c.id\n ORDER BY (cf.harness = 'claude-code') DESC, cf.created_at ASC, cf.id ASC\n LIMIT 1\n ) AS claude_session_id\n FROM conversations c\n`;\n\nconst AGENT_CONVERSATION_PREFIXES = ['agent-', 'planning-', 'specialist-'];\n\nexport function isAgentConversationName(name: string): boolean {\n return AGENT_CONVERSATION_PREFIXES.some((p) => name.startsWith(p));\n}\n\n/**\n * Live Claude session id for a conversation — consistent across every consumer (PAN-1866).\n *\n * Work-agent / specialist conversations rotate Claude sessions, but the DB only records the\n * FIRST session (the oldest conversation_files locator), so the stored id is stale by\n * construction. The agent folder's session.id is the authoritative live session — it is what\n * `claude --resume` actually runs. So for agent conversations resolve from the filesystem\n * (session.id, then sessions.json), falling back to the DB value; for human conversation-panel\n * sessions the DB value is canonical. Applied at the read door (rowToLegacyConversation) so the\n * CLI, panel, teardown, and frontend all observe the same id.\n *\n * Read inline (not via lib/agents.ts) to avoid the agents <-> conversations import cycle.\n */\nexport function resolveLiveSessionId(conv: {\n name: string;\n tmuxSession: string;\n claudeSessionId: string | null;\n}): string | null {\n if (!isAgentConversationName(conv.name)) return conv.claudeSessionId;\n const agentDir = join(getOverdeckHome(), 'agents', conv.tmuxSession);\n try {\n const sid = readFileSync(join(agentDir, 'session.id'), 'utf8').trim();\n if (sid) return sid;\n } catch { /* no session.id yet */ }\n try {\n const arr: unknown = JSON.parse(readFileSync(join(agentDir, 'sessions.json'), 'utf8'));\n if (Array.isArray(arr) && arr.length > 0) {\n const last = arr[arr.length - 1];\n if (typeof last === 'string' && last.trim()) return last.trim();\n }\n } catch { /* no/invalid sessions.json */ }\n return conv.claudeSessionId;\n}\n\nfunction overdeckDb() {\n return getOverdeckDatabaseSync();\n}\n\nfunction toIso(value: number | Date | null | undefined): string | null {\n if (value == null) return null;\n if (value instanceof Date) return value.toISOString();\n return new Date(value).toISOString();\n}\n\nfunction toMillis(value: Date | string | number = new Date()): number {\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'number') return value;\n return new Date(value).getTime();\n}\n\n/** Map a raw DB harness string to a canonical RuntimeName, normalizing legacy 'pi' to 'ohmypi' on read. */\nexport function normalizeHarness(harness: string | null): RuntimeName | null {\n if (harness === 'pi' || harness === 'ohmypi') return 'ohmypi';\n if (harness === 'claude-code' || harness === 'codex') return harness;\n return null;\n}\n\nfunction legacyRowIdForConversationId(id: string | null): number | null {\n if (!id) return null;\n const row = overdeckDb()\n .prepare(`SELECT rowid AS id FROM conversations WHERE id = ?`)\n .get(id) as { id: number } | undefined;\n return row?.id ?? null;\n}\n\nfunction conversationUuidForLegacyId(id: number): string | null {\n const row = overdeckDb()\n .prepare(`SELECT id FROM conversations WHERE rowid = ?`)\n .get(id) as { id: string } | undefined;\n return row?.id ?? null;\n}\n\nfunction rowToLegacyConversation(row: LegacyConversationRow): LegacyConversation {\n const archivedAt = toIso(row.archived_at);\n return {\n id: row.legacy_id,\n name: row.name,\n tmuxSession: row.tmux_session ?? `conv-${row.name}`,\n status: (row.status ?? (archivedAt ? 'ended' : 'active')) as 'active' | 'ended',\n cwd: row.cwd,\n issueId: row.issue_id ?? null,\n createdAt: toIso(row.created_at) ?? new Date(0).toISOString(),\n endedAt: toIso(row.ended_at) ?? archivedAt,\n lastAttachedAt: toIso(row.last_attached_at),\n claudeSessionId: resolveLiveSessionId({\n name: row.name,\n tmuxSession: row.tmux_session ?? `conv-${row.name}`,\n claudeSessionId: row.claude_session_id ?? null,\n }),\n title: row.title ?? null,\n titleSource: (row.title_source as LegacyTitleSource | null) ?? null,\n titleSeed: row.title ?? null,\n totalCost: row.total_cost ?? 0,\n totalTokens: row.total_tokens ?? 0,\n archivedAt,\n model: row.model ?? null,\n effort: row.effort ?? null,\n forkStatus: row.fork_status ?? null,\n forkError: row.fork_error ?? null,\n harness: normalizeHarness(row.harness),\n deliveryMethod: (row.delivery_method as 'auto' | 'channels' | 'tmux' | null) ?? null,\n spawnError: row.spawn_error ?? null,\n handoffDocPath: row.handoff_doc_path ?? null,\n handoffTargetConvId: legacyRowIdForConversationId(row.handoff_target_conv_id),\n forkFallbackReason: row.fork_fallback_reason ?? null,\n clearedToConvId: legacyRowIdForConversationId(row.cleared_to_conv_id),\n forkRequest: row.fork_request ?? null,\n forkRetryCount: row.fork_retry_count ?? 0,\n };\n}\n\nfunction getConversationByUuid(id: string): LegacyConversation | null {\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT} WHERE c.id = ?`)\n .get(id) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\n\nfunction getConversationUuidByName(name: string): string | null {\n const row = overdeckDb()\n .prepare(`SELECT id FROM conversations WHERE name = ?`)\n .get(name) as { id: string } | undefined;\n return row?.id ?? null;\n}\n\n/**\n * Aggregate each conversation's cost + token usage from the canonical `cost_events`\n * ledger, keyed by session id and joined through `conversation_files`. This is the\n * source of truth for conversation cost: `conversations.total_cost` is only a\n * denormalized cache written when a conversation is opened (via the /messages\n * route), so it reads stale/zero for any conversation not opened since the\n * overdeck.db cutover. The list reads from this ledger so it shows live costs\n * without requiring each conversation to be opened first.\n *\n * Returns a map of conversation id → { cost, tokens }. A conversation predating the\n * ledger has no rows and is simply absent — callers fall back to the cached\n * `total_cost` column for those.\n */\nexport function getConversationLedgerCosts(): Map<string, { cost: number; tokens: number }> {\n // Keyed by the conversation's rowid, because that is the public `id`\n // {@link LegacyConversation.id} carries (c.rowid AS legacy_id), not the uuid.\n // cost_events.session_id matches conversation_files.locator; a conversation may\n // have several locators (relaunch/clear), so we sum across all of them.\n const rows = overdeckDb()\n .prepare(\n `SELECT c.rowid AS cid,\n COALESCE(SUM(ce.cost), 0) AS cost,\n COALESCE(SUM(ce.input + ce.output + ce.cache_read + ce.cache_write), 0) AS tokens\n FROM cost_events ce\n JOIN conversation_files cf ON cf.locator = ce.session_id\n JOIN conversations c ON c.id = cf.conversation_id\n GROUP BY c.rowid`,\n )\n .all() as { cid: number; cost: number; tokens: number }[];\n const map = new Map<string, { cost: number; tokens: number }>();\n for (const r of rows) map.set(String(r.cid), { cost: r.cost ?? 0, tokens: r.tokens ?? 0 });\n return map;\n}\n\nexport function listConversations(options?: { limit?: number; offset?: number }): LegacyConversation[] {\n let sql = `${LEGACY_CONVERSATION_SELECT}\n WHERE c.archived_at IS NULL\n AND c.name NOT LIKE 'agent-%'\n AND c.name NOT LIKE 'planning-%'\n AND c.name NOT LIKE 'specialist-%'\n ORDER BY c.created_at DESC`;\n const params: number[] = [];\n if (options?.limit !== undefined) {\n sql += ' LIMIT ?';\n params.push(options.limit);\n }\n if (options?.offset !== undefined) {\n sql += ' OFFSET ?';\n params.push(options.offset);\n }\n const rows = overdeckDb().prepare(sql).all(...params) as LegacyConversationRow[];\n return rows.map(rowToLegacyConversation);\n}\n\nexport function getConversationByName(name: string): LegacyConversation | null {\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT} WHERE c.name = ?`)\n .get(name) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\n\nexport function getConversationById(id: number): LegacyConversation | null {\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT} WHERE c.rowid = ?`)\n .get(id) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\n\nexport function getConversationByClaudeSessionId(claudeSessionId: string): LegacyConversation | null {\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT}\n WHERE EXISTS (\n SELECT 1 FROM conversation_files cf\n WHERE cf.conversation_id = c.id AND cf.locator = ?\n )`)\n .get(claudeSessionId) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\nexport { findConversationForCostSessionSync } from './conversation-cost-session.js';\nexport function getConversationByTmuxSession(tmuxSession: string): LegacyConversation | null {\n const name = tmuxSession.startsWith('conv-') ? tmuxSession.slice(5) : tmuxSession;\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT}\n WHERE c.name = ? AND c.archived_at IS NULL\n ORDER BY c.created_at DESC\n LIMIT 1`)\n .get(name) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\n\nexport function listArchivedConversations(): LegacyConversation[] {\n const rows = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT}\n WHERE c.archived_at IS NOT NULL\n ORDER BY c.archived_at DESC, c.created_at DESC`)\n .all() as LegacyConversationRow[];\n return rows.map(rowToLegacyConversation);\n}\n\nexport function listArchivedConversationsWithEnrichment(options: ArchivedConversationListOptions = {}): ArchivedConversationWithEnrichment[] {\n ensureDiscoveredSessionsSchema();\n const db = overdeckDb();\n\n const conditions: string[] = ['c.archived_at IS NOT NULL'];\n const params: unknown[] = [];\n\n const lastTs = `COALESCE(ds.last_ts, c.archived_at)`;\n const firstTs = `COALESCE(ds.first_ts, c.created_at)`;\n const primaryModel = `COALESCE(ds.primary_model, c.model)`;\n const estimatedCost = `COALESCE(ds.estimated_cost, c.total_cost)`;\n const messageCount = `COALESCE(ds.message_count, 0)`;\n const enrichmentLevel = `COALESCE(ds.enrichment_level, 0)`;\n\n if (options.harness === 'claude-code') {\n conditions.push('(c.harness = ? OR c.harness IS NULL)');\n params.push(options.harness);\n } else if (options.harness === 'ohmypi') {\n conditions.push('(c.harness = ? OR c.harness = ?)');\n params.push(options.harness, 'pi');\n } else if (options.harness !== undefined) {\n conditions.push('c.harness = ?');\n params.push(options.harness);\n }\n if (options.workspacePath !== undefined) { conditions.push('c.cwd = ?'); params.push(options.workspacePath); }\n if (options.primaryModel !== undefined) { conditions.push(`${primaryModel} = ?`); params.push(options.primaryModel); }\n if (options.issueId !== undefined) { conditions.push('c.issue_id = ?'); params.push(options.issueId); }\n if (options.since !== undefined) { conditions.push(`${lastTs} >= ?`); params.push(toMillis(options.since)); }\n if (options.before !== undefined) { conditions.push(`${lastTs} < ?`); params.push(toMillis(options.before)); }\n if (options.after !== undefined) { conditions.push(`${firstTs} >= ?`); params.push(toMillis(options.after)); }\n if (options.minCost !== undefined) { conditions.push(`${estimatedCost} >= ?`); params.push(options.minCost); }\n if (options.maxCost !== undefined) { conditions.push(`${estimatedCost} <= ?`); params.push(options.maxCost); }\n if (options.minMessages !== undefined) { conditions.push(`${messageCount} >= ?`); params.push(options.minMessages); }\n if (options.unmanaged === true) { conditions.push('0 = 1'); }\n if (options.enriched === true) { conditions.push(`${enrichmentLevel} > 0`); }\n if (options.notEnriched === true) { conditions.push(`${enrichmentLevel} = 0`); }\n if (options.enrichmentLevel !== undefined) { conditions.push(`${enrichmentLevel} = ?`); params.push(options.enrichmentLevel); }\n if (options.enrichmentLevelLessThan !== undefined) { conditions.push(`${enrichmentLevel} < ?`); params.push(options.enrichmentLevelLessThan); }\n if (options.tags?.length) {\n for (const tag of options.tags) {\n conditions.push(`EXISTS (SELECT 1 FROM discovered_session_tags dst WHERE dst.session_id = ds.id AND dst.tag = ?)`);\n params.push(tag);\n }\n }\n if (options.tools?.length) {\n for (const tool of options.tools) {\n conditions.push(`EXISTS (SELECT 1 FROM discovered_session_tools dstool WHERE dstool.session_id = ds.id AND dstool.tool = ?)`);\n params.push(tool);\n }\n }\n if (options.files?.length) {\n for (const file of options.files) {\n conditions.push(`EXISTS (SELECT 1 FROM discovered_session_files dsfile WHERE dsfile.session_id = ds.id AND dsfile.file_path = ?)`);\n params.push(file);\n }\n }\n\n const safeLimit = Number.isFinite(options.limit) && options.limit! >= 0 ? options.limit! : undefined;\n const safeOffset = Number.isFinite(options.offset) && options.offset! >= 0 ? options.offset! : undefined;\n const limitClause = safeLimit !== undefined ? 'LIMIT ?' : safeOffset !== undefined ? 'LIMIT -1' : '';\n const offsetClause = safeOffset !== undefined ? 'OFFSET ?' : '';\n if (safeLimit !== undefined) params.push(safeLimit);\n if (safeOffset !== undefined) params.push(safeOffset);\n\n const where = `WHERE ${conditions.join(' AND ')}`;\n const sql = `\n SELECT\n c.rowid AS legacy_id,\n c.id AS uuid,\n c.name,\n c.cwd,\n c.issue_id,\n c.created_at,\n c.harness,\n (\n SELECT cf.locator FROM conversation_files cf\n WHERE cf.conversation_id = c.id\n ORDER BY (cf.harness = 'claude-code') DESC, cf.created_at ASC, cf.id ASC\n LIMIT 1\n ) AS claude_session_id,\n c.title,\n c.total_cost,\n c.archived_at,\n c.model,\n ds.jsonl_path AS discovered_jsonl_path,\n ds.workspace_path AS discovered_workspace_path,\n ds.message_count,\n ds.first_ts,\n ds.last_ts,\n ds.primary_model,\n ds.token_input,\n ds.token_output,\n ds.estimated_cost,\n ds.tools_used,\n ds.files_touched,\n ds.tags,\n ds.summary,\n ds.enrichment_level,\n ds.enrichment_failed\n FROM conversations c\n LEFT JOIN discovered_sessions ds ON ds.session_id = (\n SELECT cf.locator FROM conversation_files cf\n WHERE cf.conversation_id = c.id\n ORDER BY (cf.harness = 'claude-code') DESC, cf.created_at ASC, cf.id ASC\n LIMIT 1\n )\n ${where}\n ORDER BY c.archived_at DESC, c.created_at DESC\n ${limitClause} ${offsetClause}\n `;\n\n type RawRow = {\n legacy_id: number; uuid: string; name: string; cwd: string; issue_id: string | null;\n created_at: number; harness: string | null; claude_session_id: string | null; title: string | null;\n total_cost: number | null; archived_at: number | null; model: string | null;\n discovered_jsonl_path: string | null; discovered_workspace_path: string | null;\n message_count: number | null; first_ts: number | null; last_ts: number | null;\n primary_model: string | null; token_input: number | null; token_output: number | null;\n estimated_cost: number | null; tools_used: string | null; files_touched: string | null;\n tags: string | null; summary: string | null; enrichment_level: number | null;\n enrichment_failed: number | null;\n };\n\n const rawRows = db.prepare(sql).all(...params) as RawRow[];\n\n return rawRows.map((r): ArchivedConversationWithEnrichment => ({\n id: r.legacy_id,\n name: r.name,\n cwd: r.cwd,\n issueId: r.issue_id ?? null,\n createdAt: toIso(r.created_at) ?? new Date(0).toISOString(),\n claudeSessionId: r.claude_session_id ?? null,\n harness: normalizeHarness(r.harness),\n title: r.title ?? null,\n totalCost: r.total_cost ?? 0,\n archivedAt: toIso(r.archived_at) ?? toIso(r.created_at) ?? new Date(0).toISOString(),\n model: r.model ?? null,\n discoveredJsonlPath: r.discovered_jsonl_path ?? null,\n discoveredWorkspacePath: r.discovered_workspace_path ?? r.cwd,\n messageCount: r.message_count ?? null,\n firstTs: toIso(r.first_ts),\n lastTs: toIso(r.last_ts),\n primaryModel: r.primary_model ?? r.model ?? null,\n tokenInput: r.token_input ?? null,\n tokenOutput: r.token_output ?? null,\n estimatedCost: r.estimated_cost ?? null,\n toolsUsed: r.tools_used ?? null,\n filesTouched: r.files_touched ?? null,\n tags: r.tags ?? null,\n summary: r.summary ?? null,\n enrichmentLevel: r.enrichment_level ?? null,\n enrichmentFailed: r.enrichment_failed ?? null,\n }));\n}\n\nexport function listArchivedConversationNames(): string[] {\n return listArchivedConversations().map((conv) => conv.name);\n}\n\nexport function createConversation(opts: {\n name: string;\n tmuxSession: string;\n cwd: string;\n issueId?: string;\n claudeSessionId?: string;\n title?: string;\n titleSource?: LegacyTitleSource;\n titleSeed?: string;\n model?: string;\n effort?: string;\n forkStatus?: string;\n harness?: RuntimeName;\n deliveryMethod?: 'auto' | 'channels' | 'tmux';\n}): LegacyConversation {\n const db = overdeckDb();\n const id = randomUUID();\n const now = toMillis();\n\n db.transaction(() => {\n db.prepare(`DELETE FROM conversation_files WHERE conversation_id IN (SELECT id FROM conversations WHERE name = ?)`).run(opts.name);\n db.prepare(`DELETE FROM conversations WHERE name = ?`).run(opts.name);\n db.prepare(`\n INSERT INTO conversations\n (id, name, cwd, issue_id, harness, model, effort, title, title_source, created_at, archived_at,\n tmux_session, status, fork_status, fork_retry_count, delivery_method, spawn_error)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, 'active', ?, 0, ?, ?)\n `).run(\n id,\n opts.name,\n opts.cwd,\n opts.issueId ?? null,\n opts.harness ?? null,\n opts.model ?? null,\n opts.effort ?? null,\n opts.title ?? null,\n opts.titleSource ?? (opts.title ? 'auto' : null),\n now,\n opts.tmuxSession ?? null,\n opts.forkStatus ?? null,\n opts.deliveryMethod ?? null,\n null, // spawn_error starts null\n );\n if (opts.claudeSessionId) {\n db.prepare(`\n INSERT OR IGNORE INTO conversation_files (conversation_id, harness, locator, created_at)\n VALUES (?, ?, ?, ?)\n `).run(id, opts.harness ?? 'claude-code', opts.claudeSessionId, now);\n }\n })();\n\n const conv = getConversationByUuid(id);\n if (!conv) throw new Error(`Failed to create conversation ${opts.name}`);\n return conv;\n}\n\nexport function markConversationEnded(name: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET ended_at = ?, status = 'ended' WHERE name = ?`)\n .run(Date.now(), name);\n}\n\n// PAN-1972: resurrect a conversation the liveness poller previously latched to\n// 'ended'. tmux is the liveness oracle — when a session + harness are observed\n// alive, the row must read 'active'. The `status != 'active'` guard makes this a\n// true no-op (no needless write) when the row is already correct.\nexport function markConversationRunning(name: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET status = 'active', ended_at = NULL WHERE name = ? AND status != 'active'`)\n .run(name);\n}\n\nexport function markConversationActive(name: string): void {\n overdeckDb().prepare(`UPDATE conversations SET archived_at = NULL WHERE name = ?`).run(name);\n}\n\nexport function reactivateConversationForSpawn(opts: {\n name: string;\n tmuxSession: string;\n cwd: string;\n issueId?: string;\n claudeSessionId?: string;\n model?: string;\n harness?: RuntimeName;\n}): void {\n const db = overdeckDb();\n const now = toMillis();\n const id = getConversationUuidByName(opts.name);\n if (!id) return;\n db.prepare(`\n UPDATE conversations\n SET cwd = ?, issue_id = ?, model = ?, harness = ?, archived_at = NULL\n WHERE id = ?\n `).run(opts.cwd, opts.issueId ?? null, opts.model ?? null, opts.harness ?? null, id);\n if (opts.claudeSessionId) {\n db.prepare(`\n INSERT OR IGNORE INTO conversation_files (conversation_id, harness, locator, created_at)\n VALUES (?, ?, ?, ?)\n `).run(id, opts.harness ?? 'claude-code', opts.claudeSessionId, now);\n }\n}\n\nexport function updateLastAttached(name: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET last_attached_at = ? WHERE name = ?`)\n .run(Date.now(), name);\n}\n\nexport function markAllEndedOnStartup(): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET status = 'ended', ended_at = COALESCE(ended_at, ?) WHERE status = 'active'`)\n .run(Date.now());\n}\n\nexport function updateConversationTitle(name: string, title: string, titleSource?: LegacyTitleSource): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET title = ?, title_source = COALESCE(?, title_source) WHERE name = ?`)\n .run(title, titleSource ?? null, name);\n try {\n getEventStore().emitOnly({\n type: 'conversation.title_changed',\n timestamp: new Date().toISOString(),\n payload: { conversationName: name, title, titleSource: titleSource ?? '' },\n });\n } catch {\n // Event store is uninitialized in CLI/test contexts; title persistence is enough.\n }\n}\n\nexport function archiveConversation(name: string): void {\n const db = overdeckDb();\n db.prepare(`UPDATE conversations SET archived_at = ? WHERE name = ?`).run(toMillis(), name);\n db.prepare(`DELETE FROM favorites WHERE type = 'conversation' AND item_id = ?`).run(name);\n}\n\nexport function unarchiveConversation(name: string): void {\n overdeckDb().prepare(`UPDATE conversations SET archived_at = NULL WHERE name = ?`).run(name);\n}\n\nexport function updateConversationCost(name: string, totalCost: number, totalTokens?: number): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET total_cost = ?, total_tokens = COALESCE(?, total_tokens) WHERE name = ?`)\n .run(totalCost, totalTokens ?? null, name);\n}\n\nexport function setConversationModel(name: string, model: string): void {\n overdeckDb().prepare(`UPDATE conversations SET model = ? WHERE name = ?`).run(model, name);\n}\n\nexport function setConversationEffort(name: string, effort: string | null): void {\n overdeckDb().prepare(`UPDATE conversations SET effort = ? WHERE name = ?`).run(effort, name);\n}\n\nexport function setConversationHarness(name: string, harness: RuntimeName): void {\n overdeckDb().prepare(`UPDATE conversations SET harness = ? WHERE name = ?`).run(harness, name);\n}\n\nexport function setConversationClaudeSessionId(name: string, claudeSessionId: string): void {\n const db = overdeckDb();\n const id = getConversationUuidByName(name);\n if (!id) return;\n const harness = getConversationByName(name)?.harness ?? 'claude-code';\n db.prepare(`\n INSERT OR IGNORE INTO conversation_files (conversation_id, harness, locator, created_at)\n VALUES (?, ?, ?, ?)\n `).run(id, harness, claudeSessionId, toMillis());\n}\n\nexport function updateConversationDeliveryMethod(name: string, method: 'auto' | 'channels' | 'tmux' | null): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET delivery_method = ? WHERE name = ?`)\n .run(method, name);\n}\n\nexport function backfillConversationModel(name: string, model: string): void {\n overdeckDb().prepare(`UPDATE conversations SET model = ? WHERE name = ? AND model IS NULL`).run(model, name);\n}\n\nexport function updateForkStatus(name: string, status: string | null, error?: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET fork_status = ?, fork_error = ? WHERE name = ?`)\n .run(status, error ?? null, name);\n}\n\nexport function getStuckForks(): LegacyConversation[] {\n const rows = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT} WHERE c.fork_status IS NOT NULL AND c.fork_status != 'failed' ORDER BY c.created_at ASC`)\n .all() as LegacyConversationRow[];\n return rows.map(rowToLegacyConversation);\n}\n\nexport function setForkRequest(name: string, json: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET fork_request = ? WHERE name = ?`)\n .run(json, name);\n}\n\nexport function incrementForkRetryCount(name: string): number {\n const db = overdeckDb();\n db.prepare(`UPDATE conversations SET fork_retry_count = fork_retry_count + 1 WHERE name = ?`).run(name);\n const row = db.prepare(`SELECT fork_retry_count FROM conversations WHERE name = ?`).get(name) as\n | { fork_retry_count: number }\n | undefined;\n return row?.fork_retry_count ?? 0;\n}\n\nexport function updateConversationForkFallbackReason(name: string, reason: string | null): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET fork_fallback_reason = ? WHERE name = ?`)\n .run(reason, name);\n}\n\nexport function recordConversationHandoff(sourceName: string, targetName: string, docPath: string): LegacyConversation {\n const targetId = getConversationUuidByName(targetName);\n const target = targetId ? getConversationByUuid(targetId) : null;\n if (!targetId || !target) throw new Error(`Handoff target conversation ${targetName} not found`);\n const db = overdeckDb();\n db.prepare(`UPDATE conversations SET handoff_doc_path = ? WHERE id = ?`).run(docPath, targetId);\n db.prepare(`UPDATE conversations SET handoff_target_conv_id = ? WHERE name = ?`).run(targetId, sourceName);\n return getConversationByUuid(targetId) ?? target;\n}\n\nexport function setClearedToConvId(name: string, convId: number): void {\n const targetUuid = conversationUuidForLegacyId(convId);\n overdeckDb().prepare(`UPDATE conversations SET cleared_to_conv_id = ? WHERE name = ?`).run(targetUuid, name);\n}\n\nexport function hasOtherActiveConversationOnTmuxSession(_tmuxSession: string, _excludeName: string): boolean {\n return false;\n}\n\nexport function updateSpawnError(name: string, error: string | null): void {\n overdeckDb().prepare(`UPDATE conversations SET spawn_error = ? WHERE name = ?`).run(error, name);\n}\n\nexport function clearStuckForks(): number {\n return 0;\n}\n\nexport function canReplaceTitle(conv: LegacyConversation): boolean {\n if (conv.titleSource === 'manual') return false;\n return conv.titleSource === 'default' || conv.titleSource === 'auto';\n}\n\nexport function canRefineTitle(conv: LegacyConversation): boolean {\n return conv.titleSource === 'default'\n || conv.titleSource === 'auto'\n || conv.titleSource === 'ai'\n || conv.titleSource === 'ai-refined';\n}\n\nexport function listFavoritedIds(type: LegacyFavoriteType): string[] {\n const rows = overdeckDb()\n .prepare(`SELECT item_id FROM favorites WHERE type = ?`)\n .all(type) as Array<{ item_id: string }>;\n return rows.map((row) => row.item_id);\n}\n\nexport function setFavorite(type: LegacyFavoriteType, itemId: string): void {\n overdeckDb()\n .prepare(`INSERT OR IGNORE INTO favorites (type, item_id, created_at) VALUES (?, ?, ?)`)\n .run(type, itemId, toMillis());\n}\n\nexport function removeFavorite(type: LegacyFavoriteType, itemId: string): void {\n overdeckDb().prepare(`DELETE FROM favorites WHERE type = ? AND item_id = ?`).run(type, itemId);\n}\n\nexport interface ImportLegacyConversationMapped {\n name: string;\n tmuxSession: string | null;\n status: 'active' | 'ended';\n cwd: string;\n createdAt: number;\n endedAt: number | null;\n lastAttachedAt: number | null;\n sessionFile: string | null;\n claudeSessionId: string | null;\n title: string | null;\n titleSource: string | null;\n titleSeed: string | null;\n totalCost: number;\n totalTokens: number;\n archivedAt: number | null;\n model: string | null;\n effort: string | null;\n forkStatus: string | null;\n forkError: string | null;\n harness: string | null;\n deliveryMethod: string | null;\n spawnError: string | null;\n handoffDocPath: string | null;\n forkFallbackReason: string | null;\n forkRequest: string | null;\n forkRetryCount: number;\n}\n\nexport function importLegacyConversation(mapped: ImportLegacyConversationMapped): { uuid: string } {\n const db = overdeckDb();\n const uuid = randomUUID();\n db.transaction(() => {\n db.prepare(`\n INSERT OR IGNORE INTO conversations\n (id, name, cwd, harness, model, effort, title, title_source, created_at, archived_at,\n tmux_session, status, ended_at, last_attached_at, session_file, total_cost, total_tokens,\n fork_status, fork_error, fork_retry_count, fork_request, fork_fallback_reason,\n delivery_method, spawn_error, handoff_doc_path, handoff_target_conv_id, cleared_to_conv_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL)\n `).run(\n uuid,\n mapped.name,\n mapped.cwd,\n mapped.harness,\n mapped.model,\n mapped.effort,\n mapped.title,\n mapped.titleSource,\n mapped.createdAt,\n mapped.archivedAt,\n mapped.tmuxSession ?? `conv-${mapped.name}`,\n mapped.status,\n mapped.endedAt,\n mapped.lastAttachedAt,\n mapped.sessionFile,\n mapped.totalCost,\n mapped.totalTokens,\n mapped.forkStatus,\n mapped.forkError,\n mapped.forkRetryCount,\n mapped.forkRequest,\n mapped.forkFallbackReason,\n mapped.deliveryMethod,\n mapped.spawnError,\n mapped.handoffDocPath,\n );\n if (mapped.claudeSessionId) {\n db.prepare(`\n INSERT OR IGNORE INTO conversation_files (conversation_id, harness, locator, created_at)\n VALUES (?, ?, ?, ?)\n `).run(uuid, mapped.harness ?? 'claude-code', mapped.claudeSessionId, mapped.createdAt);\n }\n })();\n return { uuid };\n}\n\nexport function setImportedConversationLinks(\n uuid: string,\n links: { handoffTargetUuid: string | null; clearedToUuid: string | null },\n): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET handoff_target_conv_id = ?, cleared_to_conv_id = ? WHERE id = ?`)\n .run(links.handoffTargetUuid, links.clearedToUuid, uuid);\n}\n","import { readFile, mkdir, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport type { AgentRuntimeSnapshot } from '@overdeck/contracts';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport {\n getAgentRuntimeSnapshot as fetchAgentRuntimeSnapshot,\n emitAgentEvent,\n} from '../agent-runtime.js';\nimport { getRuntimeSnapshot, isAgentStateServiceInProcess } from '../agent-runtime-mirror.js';\nimport { normalizeHarness } from '../overdeck/conversations.js';\nimport { getAgentDir } from '../agents.js';\n\nexport type AgentResolution = 'working' | 'done' | 'needs_input' | 'stuck' | 'completed' | 'unclear' | 'abandoned';\n\n/** Callers consume this shape; data comes from AgentRuntimeSnapshot. */\nexport interface AgentRuntimeState {\n // 'suspended' retained for backward-compat with callers that still compare\n // against it defensively. The new event path never emits suspended — PAN-800\n // drops the auto-suspend feature; PAN-188 reintroduces it.\n state: 'active' | 'idle' | 'suspended' | 'stopped' | 'uninitialized' | 'waiting-on-human';\n lastActivity: string;\n currentTool?: string;\n claudeSessionId?: string;\n sessionModel?: string;\n sessionHarness?: RuntimeName;\n /**\n * For specialists: the issue currently being processed. Tracked per-agent in\n * the AgentStateService snapshot (see agent.current_issue_set event).\n */\n currentIssue?: string;\n resolution?: AgentResolution;\n resolutionCount?: number;\n resolutionUpdatedAt?: string;\n waitingReason?: string;\n waitingStartedAt?: string;\n waitingNotification?: string;\n contextSaturatedAt?: string;\n}\n\nexport function sessionResumeDriftReasons(\n runtimeState: AgentRuntimeState | null,\n model: string,\n harness: RuntimeName,\n): string[] {\n if (!runtimeState?.sessionModel || !runtimeState.sessionHarness) return [];\n const reasons: string[] = [];\n if (runtimeState.sessionModel !== model) {\n reasons.push(`model ${runtimeState.sessionModel}→${model}`);\n }\n if (runtimeState.sessionHarness !== harness) {\n reasons.push(`harness ${runtimeState.sessionHarness}→${harness}`);\n }\n return reasons;\n}\n\nfunction snapshotToRuntimeState(snap: AgentRuntimeSnapshot | null): AgentRuntimeState | null {\n if (!snap) return null;\n // Map Activity → legacy state. The legacy 'active' value collapses working\n // and thinking — neither consumer ever distinguished them.\n let state: AgentRuntimeState['state'];\n switch (snap.activity) {\n case 'working': state = 'active'; break;\n case 'thinking': state = 'active'; break;\n case 'idle': state = 'idle'; break;\n case 'stopped': state = 'stopped'; break;\n case 'waiting': state = 'waiting-on-human'; break;\n default: state = 'uninitialized';\n }\n return {\n state,\n lastActivity: snap.lastActivity,\n currentTool: snap.currentTool,\n claudeSessionId: snap.claudeSessionId,\n sessionModel: snap.sessionModel,\n sessionHarness: normalizeHarness(snap.sessionHarness ?? null) ?? undefined,\n currentIssue: snap.currentIssue,\n resolution: snap.resolution as AgentResolution | undefined,\n resolutionCount: snap.resolutionCount,\n resolutionUpdatedAt: snap.resolutionUpdatedAt,\n waitingReason: snap.waiting?.reason,\n waitingStartedAt: snap.waiting?.startedAt,\n waitingNotification: snap.waiting?.message,\n contextSaturatedAt: snap.contextSaturatedAt,\n };\n}\n\nexport function getAgentRuntimeStateSync(agentId: string): AgentRuntimeState | null {\n // Sync path: read from the in-process mirror (empty in fresh CLI processes,\n // populated inside the dashboard server). CLI commands should use\n // getAgentRuntimeStateProgram so they fall through to HTTP.\n return snapshotToRuntimeState(Effect.runSync(getRuntimeSnapshot(agentId)));\n}\n\nexport const getAgentRuntimeState = (agentId: string): Effect.Effect<AgentRuntimeState | null> =>\n Effect.gen(function* () {\n if (yield* isAgentStateServiceInProcess()) {\n return snapshotToRuntimeState(yield* getRuntimeSnapshot(agentId));\n }\n\n const snap = yield* fetchAgentRuntimeSnapshot(agentId);\n return snapshotToRuntimeState(snap);\n });\n\nasync function patchRuntimeJson(agentId: string, patch: Partial<AgentRuntimeState>): Promise<void> {\n const agentDir = getAgentDir(agentId);\n const runtimeFile = join(agentDir, 'runtime.json');\n let runtime: Record<string, unknown> = {};\n\n try {\n runtime = JSON.parse(await readFile(runtimeFile, 'utf-8')) as Record<string, unknown>;\n } catch {\n runtime = {};\n }\n\n if (Object.prototype.hasOwnProperty.call(patch, 'contextSaturatedAt')) {\n if (patch.contextSaturatedAt === undefined) {\n delete runtime.contextSaturatedAt;\n } else {\n runtime.contextSaturatedAt = patch.contextSaturatedAt;\n }\n }\n\n await mkdir(agentDir, { recursive: true });\n await writeFile(runtimeFile, JSON.stringify(runtime, null, 2));\n}\n\n/**\n * Emit events derived from a legacy-shape patch. Callers gradually migrate to\n * direct emitAgentEvent calls; this adapter keeps existing code working.\n */\nexport async function saveAgentRuntimeState(agentId: string, patch: Partial<AgentRuntimeState>): Promise<void> {\n if (patch.currentIssue !== undefined) {\n await Effect.runPromise(emitAgentEvent(agentId, {\n kind: 'current_issue_set',\n currentIssue: patch.currentIssue || undefined,\n }));\n }\n\n if (patch.resolution !== undefined && patch.resolutionCount !== undefined) {\n await Effect.runPromise(emitAgentEvent(agentId, {\n kind: 'resolution_set',\n resolution: patch.resolution,\n resolutionCount: patch.resolutionCount,\n }));\n }\n\n if (patch.state !== undefined) {\n if (patch.state === 'waiting-on-human') {\n await Effect.runPromise(emitAgentEvent(agentId, {\n kind: 'waiting_start',\n reason: (patch.waitingReason as 'tool_permission' | 'user_question' | 'disambiguation' | 'other') || 'other',\n message: patch.waitingNotification,\n }));\n } else if (patch.state === 'active') {\n await Effect.runPromise(emitAgentEvent(agentId, { kind: 'activity', activity: 'working', tool: patch.currentTool }));\n } else if (patch.state === 'idle') {\n await Effect.runPromise(emitAgentEvent(agentId, { kind: 'activity', activity: 'idle' }));\n } else if (patch.state === 'stopped') {\n await Effect.runPromise(emitAgentEvent(agentId, { kind: 'activity', activity: 'stopped' }));\n }\n } else if (patch.currentTool !== undefined) {\n await Effect.runPromise(emitAgentEvent(agentId, { kind: 'activity', activity: 'working', tool: patch.currentTool }));\n }\n\n if (patch.claudeSessionId || patch.sessionModel !== undefined || patch.sessionHarness !== undefined) {\n // model_set requires a model — use existing snapshot's model if present.\n const snap = getAgentRuntimeStateSync(agentId);\n if (snap || patch.claudeSessionId || patch.sessionModel !== undefined || patch.sessionHarness !== undefined) {\n const event: {\n kind: 'model_set';\n model: string;\n claudeSessionId?: string;\n sessionModel?: string;\n sessionHarness?: RuntimeName;\n } = {\n kind: 'model_set',\n model: 'unknown',\n };\n if (patch.claudeSessionId !== undefined) event.claudeSessionId = patch.claudeSessionId;\n if (patch.sessionModel !== undefined) event.sessionModel = patch.sessionModel;\n if (patch.sessionHarness !== undefined) event.sessionHarness = patch.sessionHarness;\n await Effect.runPromise(emitAgentEvent(agentId, {\n ...event,\n }));\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(patch, 'contextSaturatedAt')) {\n await patchRuntimeJson(agentId, patch);\n await Effect.runPromise(emitAgentEvent(agentId, {\n kind: 'context_saturation_changed',\n contextSaturatedAt: patch.contextSaturatedAt,\n }));\n }\n}\n","import { existsSync, readFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { resolveBareNumericIdSync } from '../issue-id.js';\nimport { getOverdeckHome } from '../paths.js';\nimport { getRollbackAgentStatePath } from '../overdeck/agent-rollback-state.js';\nimport { getOverdeckAgentStateSync, listOverdeckAgentStatesSync } from '../overdeck/agent-state-sync.js';\nimport { getAgentRuntimeStateSync } from './runtime-state.js';\n\n/** Known agent ID prefixes — IDs with these prefixes are already normalized */\nconst AGENT_PREFIXES = ['agent-', 'planning-', 'conv-', 'strike-', 'inspect-'];\n// Singleton runners spawn under their own bare ID (spawnRun creates the tmux\n// session and agent dir from the raw ID). They MUST be listed here so\n// normalizeAgentId is a no-op for them — otherwise message delivery and state\n// lookups would target `agent-<id>` and miss the real session (PAN-1866: the\n// sequencer spawned but its prompt was delivered to a nonexistent\n// `agent-sequencer-runner` pane, leaving the agent idle).\nconst SINGLETON_AGENT_IDS = new Set(['flywheel-orchestrator', 'sequencer-runner']);\n\n/** Normalize agent ID: preserve known prefixes, add 'agent-' for bare issue IDs */\nexport function normalizeAgentId(agentId: string): string {\n if (SINGLETON_AGENT_IDS.has(agentId)) return agentId;\n if (AGENT_PREFIXES.some(p => agentId.startsWith(p))) {\n return agentId;\n }\n return `agent-${agentId.toLowerCase()}`;\n}\n\n/** True when the input is already a fully-qualified agent ID (known prefix or singleton), not an issue ID. */\nexport function isQualifiedAgentId(input: string): boolean {\n const lower = input.toLowerCase();\n return SINGLETON_AGENT_IDS.has(lower) || AGENT_PREFIXES.some(p => lower.startsWith(p));\n}\n\nfunction agentStateExistsSync(agentId: string): boolean {\n return Boolean(getOverdeckAgentStateSync(agentId)) || existsSync(getRollbackAgentStatePath(agentId));\n}\n\n/**\n * Resolve a CLI-supplied agent target to an on-disk agent ID (PAN-1760).\n * Accepts bare numerics (\"1148\"), issue IDs (\"PAN-1148\"), and fully-qualified\n * agent IDs (\"agent-pan-1148-ship\", \"strike-pan-1723\", \"inspect-pan-1744-x\",\n * \"flywheel-orchestrator\"). For issue IDs, prefers the canonical work-agent\n * directory when present, then falls back to the single registered agent state\n * for that issue. If no single fallback exists, preserves the historical\n * canonical agent-* target.\n */\nexport function resolveAgentTargetSync(input: string): string | null {\n if (isQualifiedAgentId(input)) return input.toLowerCase();\n const issueId = resolveBareNumericIdSync(input);\n if (!issueId) return null;\n\n const canonicalAgentId = normalizeAgentId(issueId);\n if (agentStateExistsSync(canonicalAgentId)) return canonicalAgentId;\n\n try {\n const wantedIssueId = issueId.toUpperCase();\n const matches = listOverdeckAgentStatesSync()\n .filter((agent) => agent.issueId.toUpperCase() === wantedIssueId)\n .map((agent) => agent.id);\n if (matches.length === 1) return matches[0].toLowerCase();\n return canonicalAgentId;\n } catch {\n return canonicalAgentId;\n }\n}\n\n/**\n * Get path to agent's ready signal file (written by SessionStart hook)\n */\nfunction getReadySignalPath(agentId: string): string {\n return join(getOverdeckHome(), 'agents', agentId, 'ready.json');\n}\n\n/**\n * Clear ready signal before spawning (clean slate)\n */\nexport function clearReadySignal(agentId: string): void {\n const readyPath = getReadySignalPath(agentId);\n if (existsSync(readyPath)) {\n try {\n unlinkSync(readyPath);\n } catch {\n // Ignore errors - non-critical\n }\n }\n}\n\nfunction isReadySignalPresent(readyPath: string): boolean {\n if (!existsSync(readyPath)) return false;\n try {\n const signal = JSON.parse(readFileSync(readyPath, 'utf-8'));\n // Accept both the Claude hook shape ({ ready: true, ... }) and the Pi\n // extension shape ({ agentId, sessionId, ... } with no `ready` field).\n return Boolean(signal && typeof signal === 'object' && signal.ready !== false);\n } catch {\n // File exists but mid-write / invalid — keep waiting.\n return false;\n }\n}\n\nexport async function waitForReadySignal(agentId: string, timeoutSeconds = 30): Promise<boolean> {\n const readyPath = getReadySignalPath(agentId);\n\n for (let i = 0; i < timeoutSeconds; i++) {\n if (isReadySignalPresent(readyPath)) return true;\n await new Promise(resolve => setTimeout(resolve, 1000)); // Non-blocking sleep\n }\n\n return isReadySignalPresent(readyPath);\n}\n\n/**\n * Wait until a hook-instrumented agent reports it is idle at the prompt, via the\n * runtime mirror (Stop / SessionStart hook → activity 'idle'), or the timeout\n * elapses. Returns true if idle was observed.\n *\n * PAN-1594/1596: this is the hook-derived \"is the agent idle right now\" check.\n * It replaced the tmux pane-scrape `waitForClaudePrompt` (since removed). Works\n * for any hook-instrumented session — agents AND conversations (`conv-*`), which\n * feed the runtime mirror once their heartbeat POSTs authenticate (PAN-1596). No\n * dependency on tmux output or permission mode.\n *\n * Distinct from waitForReadySignal: that answers the one-time \"has this\n * (re)launched session reached the prompt\" (ready.json gate, used by the\n * conversation reattach/fork paths); this answers \"is the running agent idle at\n * the prompt right now\".\n */\nexport async function waitForAgentIdle(agentId: string, timeoutMs = 5000): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n do {\n if (getAgentRuntimeStateSync(agentId)?.state === 'idle') return true;\n await new Promise(r => setTimeout(r, 250));\n } while (Date.now() < deadline);\n return getAgentRuntimeStateSync(agentId)?.state === 'idle';\n}\n","import { getProjectSync, resolveProjectFromIssueSync } from '../projects.js';\n\nexport interface AutoResumeConfig {\n maxConsecutiveFailures: number;\n troubledWindowMs: number;\n failureBackoffSchedule: number[];\n}\n\nexport const DEFAULT_AUTO_RESUME_CONFIG: AutoResumeConfig = {\n maxConsecutiveFailures: 3,\n troubledWindowMs: 10 * 60 * 1000,\n failureBackoffSchedule: [5, 30, 120],\n};\n\nfunction positiveNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\nfunction validBackoffSchedule(value: unknown): value is number[] {\n return Array.isArray(value)\n && value.length > 0\n && value.every((entry) => typeof entry === 'number' && Number.isFinite(entry) && entry >= 0);\n}\n\nexport function normalizeAutoResumeConfig(\n override: Partial<AutoResumeConfig> | undefined,\n source: string,\n): AutoResumeConfig {\n if (override === undefined) return DEFAULT_AUTO_RESUME_CONFIG;\n\n const config: AutoResumeConfig = { ...DEFAULT_AUTO_RESUME_CONFIG };\n if (override.maxConsecutiveFailures !== undefined) {\n if (positiveNumber(override.maxConsecutiveFailures)) {\n config.maxConsecutiveFailures = override.maxConsecutiveFailures;\n } else {\n console.warn(`[auto-resume] Invalid ${source}.maxConsecutiveFailures; using default ${DEFAULT_AUTO_RESUME_CONFIG.maxConsecutiveFailures}`);\n }\n }\n\n if (override.troubledWindowMs !== undefined) {\n if (positiveNumber(override.troubledWindowMs)) {\n config.troubledWindowMs = override.troubledWindowMs;\n } else {\n console.warn(`[auto-resume] Invalid ${source}.troubledWindowMs; using default ${DEFAULT_AUTO_RESUME_CONFIG.troubledWindowMs}`);\n }\n }\n\n if (override.failureBackoffSchedule !== undefined) {\n if (validBackoffSchedule(override.failureBackoffSchedule)) {\n config.failureBackoffSchedule = override.failureBackoffSchedule;\n } else {\n console.warn(`[auto-resume] Invalid ${source}.failureBackoffSchedule; using default ${DEFAULT_AUTO_RESUME_CONFIG.failureBackoffSchedule.join(',')}`);\n }\n }\n\n return config;\n}\n\nexport function resolveAutoResumeConfigForIssue(issueId: string | undefined): AutoResumeConfig {\n if (!issueId) return DEFAULT_AUTO_RESUME_CONFIG;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return DEFAULT_AUTO_RESUME_CONFIG;\n\n const project = getProjectSync(resolved.projectKey);\n return normalizeAutoResumeConfig(project?.autoResume, `projects.${resolved.projectKey}.autoResume`);\n}\n","import type { RuntimeName } from '../runtimes/types.js';\nimport {\n readIssueRecordSync,\n resolveProjectForIssue,\n writeAgentHarnessModelSync,\n} from '../pan-dir/record.js';\n\nexport function readAgentHarnessModelRecordSync(issueId: string): {\n harness?: RuntimeName;\n model?: string;\n} | null {\n const project = resolveProjectForIssue(issueId);\n if (!project) return null;\n const record = readIssueRecordSync(project, issueId);\n return {\n harness: record?.harness,\n model: record?.model,\n };\n}\n\nexport function writeAgentHarnessModelRecordSync(\n issueId: string,\n harness: RuntimeName,\n model: string,\n): void {\n const project = resolveProjectForIssue(issueId);\n if (!project) return;\n writeAgentHarnessModelSync(project, issueId, harness, model);\n}\n\n","/**\n * Append-only persistent logger for forensic audit trails.\n *\n * Complements the SQLite event store (activity-logger.ts) with flat-file logs\n * that survive event-store resets and are greppable from the shell.\n *\n * Files:\n * ~/.overdeck/logs/deacon.log — deacon startup recovery actions\n * ~/.overdeck/agents/<id>/lifecycle.log — per-agent state transitions\n */\n\nimport { appendFileSync, mkdirSync } from 'fs';\nimport { appendFile, mkdir } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { getOverdeckHome } from './paths.js';\n\nfunction logsDir(): string {\n return join(getOverdeckHome(), 'logs');\n}\n\nfunction agentDir(agentId: string): string {\n return join(getOverdeckHome(), 'agents', agentId);\n}\n\nfunction ensureLogsDir(): void {\n try {\n mkdirSync(logsDir(), { recursive: true });\n } catch { /* non-fatal */ }\n}\n\nfunction ensureAgentDir(agentId: string): void {\n try {\n mkdirSync(agentDir(agentId), { recursive: true });\n } catch { /* non-fatal */ }\n}\n\nfunction timestamp(): string {\n return new Date().toISOString();\n}\n\n/** Append a line to ~/.overdeck/logs/deacon.log */\nexport function logDeaconEventSync(message: string): void {\n ensureLogsDir();\n try {\n appendFileSync(join(logsDir(), 'deacon.log'), `[${timestamp()}] ${message}\\n`);\n } catch {\n // Non-fatal — logging must never break recovery logic\n }\n}\n\n/** Append a line to ~/.overdeck/agents/<agentId>/lifecycle.log */\nexport function logAgentLifecycleSync(agentId: string, message: string): void {\n ensureAgentDir(agentId);\n try {\n appendFileSync(\n join(agentDir(agentId), 'lifecycle.log'),\n `[${timestamp()}] ${message}\\n`,\n );\n } catch {\n // Non-fatal\n }\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n//\n// Logging must NEVER break recovery logic, so both Effect variants swallow all\n// errors and return Effect<void, never> — they never fail the parent Effect.\n\n/** Effect variant of {@link logDeaconEventSync}. Failures are swallowed silently. */\nexport const logDeaconEvent = (message: string): Effect.Effect<void, never> =>\n Effect.promise(async () => {\n try {\n const dir = logsDir();\n await mkdir(dir, { recursive: true });\n await appendFile(join(dir, 'deacon.log'), `[${timestamp()}] ${message}\\n`);\n } catch {\n // Non-fatal\n }\n });\n\n/** Effect variant of {@link logAgentLifecycleSync}. Failures are swallowed silently. */\nexport const logAgentLifecycle = (agentId: string, message: string): Effect.Effect<void, never> =>\n Effect.promise(async () => {\n try {\n const dir = agentDir(agentId);\n await mkdir(dir, { recursive: true });\n await appendFile(\n join(dir, 'lifecycle.log'),\n `[${timestamp()}] ${message}\\n`,\n );\n } catch {\n // Non-fatal\n }\n });\n","import { readdir, rm, writeFile as writeFileAsync, mkdir as mkdirAsync } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { AGENTS_DIR, getOverdeckHome } from '../paths.js';\nimport { FsError } from '../errors.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { resolveAutoResumeConfigForIssue } from '../cloister/auto-resume-config.js';\nimport { getRollbackAgentStatePath, readRollbackAgentStateSync, writeRollbackAgentStateSync } from '../overdeck/agent-rollback-state.js';\nimport { getOverdeckAgentStateSync, saveOverdeckAgentStateSync } from '../overdeck/agent-state-sync.js';\nimport { readAgentHarnessModelRecordSync, writeAgentHarnessModelRecordSync } from '../overdeck/agent-record-sync.js';\nimport { logAgentLifecycleSync } from '../persistent-logger.js';\nimport { recordFeatureRegistryLifecycle } from '../registry/feature-registry-population.js';\nimport { normalizeAgentId } from './identity.js';\n\nexport type Role = 'plan' | 'work' | 'review' | 'test' | 'ship' | 'flywheel' | 'strike' | 'sequencer' | 'knowledge';\n\nexport const SESSION_EXITED_BEFORE_KICKOFF = 'session-exited-before-kickoff';\n\nexport interface AgentState {\n id: string;\n issueId: string;\n workspace: string;\n /** Coding-agent harness this agent runs under (PAN-636). */\n harness?: RuntimeName;\n /** Unified role primitive (PAN-1048). */\n role: Role;\n model: string;\n /**\n * The exact spawn key fed to the weighted-distribution model picker at spawn\n * (`${role}:${issueId}`), persisted so the dashboard MODEL inspector (PAN-2053)\n * can show the faithful FNV-1a derivation without re-guessing the key's form.\n * Undefined for scalar-role agents and for agents spawned before PAN-2053.\n */\n modelSpawnKey?: string;\n status: 'starting' | 'running' | 'stopped' | 'error';\n startedAt: string;\n lastActivity?: string;\n lastResumeAt?: string;\n /**\n * Tri-state kickoff delivery signal for work-agent lifecycle monitoring:\n * undefined = legacy/pre-feature agent or non-applicable role;\n * false = spawned but kickoff delivery not yet confirmed;\n * true = kickoff delivery confirmed.\n */\n kickoffDelivered?: boolean;\n stoppedAt?: string;\n /** True when markAgentStopped was called (user-initiated stop). Cleared on\n * resume. Read by deacon's autoResumeStoppedWorkAgents to distinguish a\n * deliberate stop from a crash/orphan. */\n stoppedByUser?: boolean;\n stoppedByPause?: boolean;\n paused?: boolean;\n pausedReason?: string;\n pausedAt?: string;\n /**\n * PAN-2507: true when this work agent was paused by the preemptive scheduler\n * (yielded to free capacity for an advancing dispatch), as distinct from an\n * operator pause. Reuses `paused: true` so every existing no-resume gate\n * protects the yielded agent; this flag lets the deacon resume yielded agents\n * oldest-first and lets `pan unpause` self-clear the yield attribution.\n */\n yieldedByScheduler?: boolean;\n /** PAN-2507: ISO timestamp of the yield (oldest-first resume ordering). */\n yieldedAt?: string;\n /**\n * PAN-2507: ISO timestamp of the most recent resume-from-yield. Enforces the\n * re-yield cooldown (an agent just resumed from a yield may not be re-yielded\n * until `yield_cooldown_secs` elapse). Survives unpause (it is a cooldown\n * tracker, not a pause field).\n */\n lastYieldResumeAt?: string;\n troubled?: boolean;\n troubledAt?: string;\n consecutiveFailures?: number;\n firstFailureInRunAt?: string;\n lastFailureAt?: string;\n lastFailureReason?: string;\n lastFailureNextRetryAt?: string;\n branch?: string; // Git branch name for this agent\n costSoFar?: number;\n sessionId?: string; // For resuming sessions after handoff\n\n // Work type system (PAN-118)\n phase?: 'exploration' | 'implementation' | 'testing' | 'documentation' | 'review-response' | 'planning' | 'synthesis';\n workType?: string; // Current work type ID\n\n /**\n * Whether this work agent was launched with the experimental Claude Code\n * Channels prompt-delivery path enabled. Set at launch time after the\n * eligibility check; never mutated after. Read by deliverAgentMessage to\n * decide whether to attempt the bridge socket before falling back to\n * sendKeysAsync. Absent or false means tmux-only delivery (current default).\n */\n channelsEnabled?: boolean;\n /** True when this work agent was launched through the PTY supervisor wrapper. */\n supervisorEnabled?: boolean;\n /**\n * Delivery method for agent messages. 'auto' tries supervisor, then channels,\n * then tmux; explicit socket methods are strict (throw on failure); 'tmux'\n * bypasses socket transports entirely.\n */\n deliveryMethod?: 'auto' | 'supervisor' | 'channels' | 'tmux';\n\n /**\n * Short HEAD sha (8 chars) of the workspace at the moment this role run was\n * spawned. Used by the reactive scheduler's activeRoleRunExists() to detect a\n * stale/zombie role session: if the workspace HEAD has advanced past this\n * marker, the existing session ran against old code and must not block a\n * fresh re-dispatch for the new HEAD. Set for non-work roles in spawnRun.\n */\n roleRunHead?: string;\n\n /** Flywheel run that spawned this agent, if any. Absent for operator-started agents (PAN-1812). */\n flywheelRunId?: string;\n\n /** True when a planning session was launched with `pan plan --auto`. */\n auto?: boolean;\n\n /** Review-convoy metadata for server-side reviewer lifecycle monitoring. */\n reviewSubRole?: string;\n reviewRunId?: string;\n reviewOutputPath?: string;\n reviewSynthesisAgentId?: string;\n reviewDeadlineAt?: string;\n reviewMonitorSignaled?: 'ready' | 'failed' | 'timeout';\n /** Number of times Deacon has respawned this convoy reviewer (PAN-1806). */\n reviewRetryAttempt?: number;\n /** PAN-1862 Phase A (synthesis parent): discovery→fork orchestration state. */\n reviewDiscoveryPending?: boolean;\n reviewContextManifestPath?: string;\n reviewDiscoveryReadyAt?: string;\n reviewConvoyForkedAt?: string;\n reviewForkCacheChecked?: boolean;\n /** PAN-1862 Phase A (convoy reviewer): session was forked from the parent's discovery session. */\n reviewForkedFromParent?: boolean;\n hostOverride?: boolean;\n\n /** Inspect sub-role for inspect-* agents (PAN-1834). */\n inspectSubRole?: string;\n\n /** Registered swarm slot index for per-item work agents. */\n slotIndex?: number;\n /** vBRIEF item id explicitly assigned to this registered swarm slot. */\n slotItemId?: string;\n}\n\nconst toAgentFsError = (operation: string, path: string, cause: unknown): FsError =>\n new FsError({ operation, path, cause });\n\nexport function getAgentDir(agentId: string): string {\n return join(getOverdeckHome(), 'agents', agentId);\n}\n\nexport function getAgentStateFilePath(agentId: string): string {\n return getRollbackAgentStatePath(agentId);\n}\n\n/**\n * PAN-1985: wipe agent state directories for an issue, optionally scoped to\n * a role prefix. Used by the restart-fresh and review-restart paths to clean\n * state before respawning — the new agent then reads `.pan/continue.json`,\n * the vBRIEF, the beads, and the branch to pick up where the prior run left\n * off. Refuses to run against an empty or unsafe id.\n *\n * - `rolePrefix` omitted: wipes only the work agent dir (`agent-<id>`).\n * Specialist dirs (review, test, ship, etc.) are left alone.\n * - `rolePrefix` set (e.g. 'review'): wipes the role parent dir\n * (`agent-<id>-<prefix>`) AND any sub-roles (`agent-<id>-<prefix>-<anything>`),\n * leaving the work agent dir alone.\n *\n * Refuses to operate on the root `AGENTS_DIR` itself or on paths that escape\n * it; the `validateAgentId` guard below enforces a conservative id grammar.\n */\nexport async function wipeAgentStateDirs(\n issueId: string,\n opts: { rolePrefix?: string } = {},\n): Promise<{ removed: string[]; path: string }> {\n if (!issueId || !/^[A-Za-z]+-\\d+$/.test(issueId)) {\n throw new Error(`wipeAgentStateDirs: invalid issueId \"${issueId}\"`);\n }\n if (opts.rolePrefix !== undefined && !/^[a-z][a-z0-9-]*$/.test(opts.rolePrefix)) {\n throw new Error(`wipeAgentStateDirs: invalid rolePrefix \"${opts.rolePrefix}\"`);\n }\n const issueLower = issueId.toLowerCase();\n const dirPath = join(AGENTS_DIR, `agent-${issueLower}${opts.rolePrefix ? `-${opts.rolePrefix}` : ''}`);\n let entries: string[];\n try {\n entries = await readdir(AGENTS_DIR);\n } catch {\n return { removed: [], path: dirPath };\n }\n let targets: string[];\n if (opts.rolePrefix) {\n const base = `agent-${issueLower}-${opts.rolePrefix}`;\n targets = entries.filter((name) => name === base || name.startsWith(`${base}-`));\n } else {\n const work = `agent-${issueLower}`;\n targets = entries.filter((name) => name === work);\n }\n for (const name of targets) {\n try {\n await rm(join(AGENTS_DIR, name), { recursive: true, force: true });\n } catch { /* non-fatal — best-effort wipe */ }\n }\n return { removed: targets, path: dirPath };\n}\n\nexport function isRole(value: unknown): value is Role {\n return value === 'plan' || value === 'work' || value === 'review' || value === 'test' || value === 'ship' || value === 'flywheel' || value === 'strike' || value === 'sequencer' || value === 'knowledge';\n}\n\nfunction cleanAgentState(raw: AgentState): AgentState {\n return {\n id: raw.id,\n issueId: raw.issueId,\n workspace: raw.workspace,\n harness: raw.harness,\n role: raw.role,\n model: raw.model,\n status: raw.status,\n startedAt: raw.startedAt,\n lastActivity: raw.lastActivity,\n lastResumeAt: raw.lastResumeAt,\n kickoffDelivered: raw.kickoffDelivered,\n stoppedAt: raw.stoppedAt,\n stoppedByUser: raw.stoppedByUser,\n stoppedByPause: raw.stoppedByPause,\n paused: raw.paused,\n pausedReason: raw.pausedReason,\n pausedAt: raw.pausedAt,\n yieldedByScheduler: raw.yieldedByScheduler,\n yieldedAt: raw.yieldedAt,\n lastYieldResumeAt: raw.lastYieldResumeAt,\n troubled: raw.troubled,\n troubledAt: raw.troubledAt,\n consecutiveFailures: raw.consecutiveFailures,\n firstFailureInRunAt: raw.firstFailureInRunAt,\n lastFailureAt: raw.lastFailureAt,\n lastFailureReason: raw.lastFailureReason,\n lastFailureNextRetryAt: raw.lastFailureNextRetryAt,\n branch: raw.branch,\n costSoFar: raw.costSoFar,\n sessionId: raw.sessionId,\n roleRunHead: raw.roleRunHead,\n channelsEnabled: raw.channelsEnabled,\n supervisorEnabled: raw.supervisorEnabled,\n deliveryMethod: raw.deliveryMethod,\n reviewSubRole: raw.reviewSubRole,\n reviewRunId: raw.reviewRunId,\n reviewOutputPath: raw.reviewOutputPath,\n reviewSynthesisAgentId: raw.reviewSynthesisAgentId,\n reviewDeadlineAt: raw.reviewDeadlineAt,\n reviewMonitorSignaled: raw.reviewMonitorSignaled,\n reviewRetryAttempt: raw.reviewRetryAttempt,\n reviewDiscoveryPending: raw.reviewDiscoveryPending,\n reviewContextManifestPath: raw.reviewContextManifestPath,\n reviewDiscoveryReadyAt: raw.reviewDiscoveryReadyAt,\n reviewConvoyForkedAt: raw.reviewConvoyForkedAt,\n reviewForkCacheChecked: raw.reviewForkCacheChecked,\n reviewForkedFromParent: raw.reviewForkedFromParent,\n hostOverride: raw.hostOverride,\n inspectSubRole: raw.inspectSubRole,\n slotIndex: raw.slotIndex,\n slotItemId: raw.slotItemId,\n };\n}\n\nfunction parseAgentState(content: string, normalizedId: string): AgentState | null {\n try {\n const state = JSON.parse(content) as Partial<AgentState>;\n if (!isRole(state.role)) {\n // Roleless states are invisible to getAgentState; cleanup is handled\n // by warnOnBareNumericIssueIds / dropLegacyAgentStatesMissingRoleAsync.\n return null;\n }\n if (!state.id) state.id = normalizedId;\n return cleanAgentState(state as AgentState);\n } catch {\n return null;\n }\n}\n\nexport function getAgentStateSync(agentId: string): AgentState | null {\n const normalizedId = normalizeAgentId(agentId);\n\n const overdeckState = getOverdeckAgentStateSync(normalizedId);\n if (overdeckState) return cleanAgentState(overdeckState);\n\n const state = readRollbackAgentStateSync(normalizedId, parseAgentState);\n if (!state) return null;\n\n // PAN-1919: harness/model are no longer sourced from state.json. Merge from\n // the per-issue git-tracked record so cross-machine pickup works.\n if (state.issueId) {\n const record = readAgentHarnessModelRecordSync(state.issueId);\n if (record?.harness) state.harness = record.harness;\n if (record?.model) state.model = record.model;\n }\n\n return state;\n}\n\nexport const getAgentState = (agentId: string): Effect.Effect<AgentState | null, FsError> => {\n return Effect.try({\n try: () => getAgentStateSync(agentId),\n catch: (cause) => toAgentFsError('read', `agents-db:${agentId}`, cause),\n });\n};\n\nfunction prepareAgentStateForSave(state: AgentState): AgentState {\n if (state.status === 'running' || state.status === 'starting') {\n delete state.stoppedAt;\n } else if (state.status === 'stopped' && !state.stoppedAt) {\n state.stoppedAt = new Date().toISOString();\n }\n return state;\n}\n\nexport function writeAgentStateJsonSync(state: AgentState): void {\n writeRollbackAgentStateSync(state, (clean) => JSON.stringify(cleanAgentState(clean), null, 2));\n}\n\nexport function saveAgentStateSync(state: AgentState): void {\n // Detect status transition for audit trail\n const oldState = getAgentStateSync(state.id);\n const oldStatus = oldState?.status;\n\n prepareAgentStateForSave(state);\n\n saveOverdeckAgentStateSync(state);\n writeAgentStateJsonSync(state);\n\n // PAN-1919: mirror harness/model into the per-issue git-tracked record so\n // they travel with the branch. Done synchronously at save time; auto-commit\n // is suppressed here because spawn paths explicitly queue the commit.\n if (state.issueId && state.harness && state.model) {\n try {\n writeAgentHarnessModelRecordSync(state.issueId, state.harness, state.model);\n } catch (err) {\n console.warn(`[agents] Failed to mirror harness/model to record for ${state.issueId}: ${(err as Error).message}`);\n }\n }\n\n if (oldStatus && oldStatus !== state.status) {\n logAgentLifecycleSync(state.id, `status changed: ${oldStatus} → ${state.status} (saveAgentState)`);\n }\n}\n\nexport const saveAgentState = (state: AgentState): Effect.Effect<void, FsError> => {\n const dir = getAgentDir(state.id);\n const stateFile = getRollbackAgentStatePath(state.id);\n\n return Effect.gen(function* () {\n yield* Effect.tryPromise({\n try: () => mkdirAsync(dir, { recursive: true }),\n catch: (cause) => toAgentFsError('mkdir', dir, cause),\n });\n\n const oldState = yield* getAgentState(state.id);\n const oldStatus = oldState?.status;\n\n if (state.status === 'running' || state.status === 'starting') {\n delete state.stoppedAt;\n } else if (state.status === 'stopped' && !state.stoppedAt) {\n state.stoppedAt = new Date().toISOString();\n }\n\n yield* Effect.try({\n try: () => saveOverdeckAgentStateSync(state),\n catch: (cause) => toAgentFsError('write', `agents-db:${state.id}`, cause),\n });\n\n yield* Effect.tryPromise({\n try: () => writeFileAsync(stateFile, JSON.stringify(cleanAgentState(state), null, 2)),\n catch: (cause) => toAgentFsError('write', stateFile, cause),\n });\n recordFeatureRegistryAgentState(state);\n\n // PAN-1919: mirror harness/model into the per-issue git-tracked record.\n if (state.harness && state.model) {\n yield* Effect.try({\n try: () => writeAgentHarnessModelRecordSync(state.issueId, state.harness!, state.model!),\n catch: (cause) => toAgentFsError('write', `record:${state.issueId}`, cause),\n });\n }\n\n if (oldStatus && oldStatus !== state.status) {\n logAgentLifecycleSync(state.id, `status changed: ${oldStatus} → ${state.status} (saveAgentStateProgram)`);\n }\n });\n};\n\nfunction recordFeatureRegistryAgentState(state: AgentState): void {\n const status = state.status === 'starting' || state.status === 'running' ? 'active' : 'deferred';\n void recordFeatureRegistryLifecycle({\n issueId: state.issueId,\n workspacePath: state.workspace,\n agentId: state.id,\n status,\n });\n}\n\nfunction clearFailureTrackingFields(state: AgentState): void {\n state.consecutiveFailures = 0;\n delete state.firstFailureInRunAt;\n delete state.lastFailureAt;\n delete state.lastFailureReason;\n delete state.lastFailureNextRetryAt;\n}\n\n/**\n * Marker prefix used by the flywheel orchestrator when pausing an agent solely\n * to free a governor work slot. Pauses for this reason must never leave the\n * agent troubled (PAN-1812).\n */\nexport const GOVERNOR_SLOT_PAUSE_REASON_PREFIX = '[governor-slot]';\n\nfunction isGovernorSlotPauseReason(reason: string | undefined): boolean {\n return reason !== undefined && reason.startsWith(GOVERNOR_SLOT_PAUSE_REASON_PREFIX);\n}\n\n/** Sets the persistent manual pause gate used before stopping or suppressing resume. */\nfunction applyAgentPaused(state: AgentState, reason?: string, stoppedByPause = false): void {\n if (!state.paused) {\n state.pausedAt = new Date().toISOString();\n }\n state.paused = true;\n if (stoppedByPause) {\n state.stoppedByPause = true;\n }\n if (reason === undefined) {\n delete state.pausedReason;\n } else {\n state.pausedReason = reason;\n }\n\n // PAN-1812: a governor slot pause is a resource-hygiene action, not a fault.\n // Clear any existing troubled gate so the agent remains resumable when a slot\n // frees.\n if (isGovernorSlotPauseReason(reason)) {\n delete state.troubled;\n delete state.troubledAt;\n }\n}\n\n/** Sets the persistent manual pause gate used before stopping or suppressing resume. */\nexport function setAgentPausedSync(agentId: string, reason?: string, stoppedByPause = false): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n\n applyAgentPaused(state, reason, stoppedByPause);\n saveAgentStateSync(state);\n return true;\n}\n\nexport const setAgentPaused = (\n agentId: string,\n reason?: string,\n stoppedByPause = false,\n): Effect.Effect<AgentState | null, FsError> =>\n Effect.gen(function* () {\n const state = yield* getAgentState(agentId);\n if (!state) return null;\n\n applyAgentPaused(state, reason, stoppedByPause);\n yield* saveAgentState(state);\n return state;\n });\n\n/**\n * PAN-2507: pause an agent as a scheduler YIELD (distinct from an operator\n * pause). Reuses the pause write path so every no-resume gate protects the\n * yielded agent, and stamps the yield attribution used for oldest-first resume.\n */\nfunction applyAgentYielded(state: AgentState, reason: string): void {\n applyAgentPaused(state, reason, true);\n state.yieldedByScheduler = true;\n state.yieldedAt = new Date().toISOString();\n}\n\n/** PAN-2507: set the yield gate (pause + scheduler attribution) in one write. */\nexport function setAgentYieldedSync(agentId: string, reason: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n applyAgentYielded(state, reason);\n saveAgentStateSync(state);\n return true;\n}\n\n/**\n * PAN-2507: clear a scheduler yield ahead of a resume — drops the pause + yield\n * attribution (so `resumeAgent`'s pause gate passes) and stamps\n * `lastYieldResumeAt` to arm the re-yield cooldown, in a single write.\n */\nexport function clearYieldForResumeSync(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n applyAgentUnpaused(state);\n state.lastYieldResumeAt = new Date().toISOString();\n saveAgentStateSync(state);\n return true;\n}\n\nfunction applyAgentUnpaused(state: AgentState): void {\n if (state.stoppedByPause === true) {\n delete state.stoppedByUser;\n }\n delete state.stoppedByPause;\n delete state.paused;\n delete state.pausedReason;\n delete state.pausedAt;\n // PAN-2507 (FR-5): clearing a pause also clears the scheduler-yield\n // attribution, so an operator `pan unpause` on a yielded agent self-clears\n // the yield. `lastYieldResumeAt` is deliberately preserved — it is a\n // re-yield cooldown tracker, not a pause field.\n delete state.yieldedByScheduler;\n delete state.yieldedAt;\n}\n\nfunction isAgentPauseClear(state: AgentState): boolean {\n return !state.paused && state.pausedReason === undefined && state.pausedAt === undefined\n && state.yieldedByScheduler === undefined && state.yieldedAt === undefined;\n}\n\n/** Clears the persistent manual pause gate without spawning the agent. */\nexport function clearAgentPausedSync(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n if (isAgentPauseClear(state)) return true;\n\n applyAgentUnpaused(state);\n saveAgentStateSync(state);\n return true;\n}\n\nexport const clearAgentPaused = (agentId: string): Effect.Effect<AgentState | null, FsError> =>\n Effect.gen(function* () {\n const state = yield* getAgentState(agentId);\n if (!state) return null;\n if (isAgentPauseClear(state)) return state;\n\n applyAgentUnpaused(state);\n yield* saveAgentState(state);\n return state;\n });\n\n/** Marks an agent as troubled after repeated resume failures. */\nexport function markAgentTroubled(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n\n if (!state.troubled) {\n state.troubledAt = new Date().toISOString();\n }\n state.troubled = true;\n saveAgentStateSync(state);\n return true;\n}\n\nfunction isAgentTroubledClear(state: AgentState): boolean {\n return !state.troubled && state.troubledAt === undefined && (state.consecutiveFailures ?? 0) === 0 && state.firstFailureInRunAt === undefined && state.lastFailureAt === undefined && state.lastFailureReason === undefined && state.lastFailureNextRetryAt === undefined;\n}\n\nfunction applyAgentUntroubled(state: AgentState): void {\n delete state.troubled;\n delete state.troubledAt;\n clearFailureTrackingFields(state);\n}\n\n/** Clears the troubled gate and its accumulated failure state. */\nexport function clearAgentTroubledSync(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n if (isAgentTroubledClear(state)) return true;\n\n applyAgentUntroubled(state);\n saveAgentStateSync(state);\n return true;\n}\n\nexport const clearAgentTroubled = (agentId: string): Effect.Effect<AgentState | null, FsError> =>\n Effect.gen(function* () {\n const state = yield* getAgentState(agentId);\n if (!state) return null;\n if (isAgentTroubledClear(state)) return state;\n\n applyAgentUntroubled(state);\n yield* saveAgentState(state);\n return state;\n });\n\nfunction applyAgentFailure(state: AgentState, reason: string): void {\n const config = resolveAutoResumeConfigForIssue(state.issueId);\n const nowMs = Date.now();\n const now = new Date(nowMs).toISOString();\n const firstFailureMs = Date.parse(state.firstFailureInRunAt ?? '');\n const hasValidFirstFailure = Number.isFinite(firstFailureMs);\n const windowElapsed = hasValidFirstFailure\n && nowMs - firstFailureMs > config.troubledWindowMs;\n\n if (windowElapsed || !hasValidFirstFailure) {\n state.consecutiveFailures = 1;\n state.firstFailureInRunAt = now;\n } else {\n state.consecutiveFailures = (state.consecutiveFailures ?? 0) + 1;\n }\n\n const backoffSeconds = config.failureBackoffSchedule[\n Math.min(state.consecutiveFailures - 1, config.failureBackoffSchedule.length - 1)\n ];\n state.lastFailureAt = now;\n state.lastFailureReason = reason;\n state.lastFailureNextRetryAt = new Date(nowMs + backoffSeconds * 1000).toISOString();\n\n const firstFailureInRunMs = Date.parse(state.firstFailureInRunAt ?? '');\n const shouldMarkTroubled = state.consecutiveFailures >= config.maxConsecutiveFailures\n && Number.isFinite(firstFailureInRunMs)\n && nowMs - firstFailureInRunMs <= config.troubledWindowMs;\n\n if (shouldMarkTroubled) {\n if (!state.troubled) {\n state.troubledAt = now;\n }\n state.troubled = true;\n }\n}\n\n/** Records one failed resume/crash observation for later backoff and troubled gating. */\nexport function recordAgentFailureSync(agentId: string, reason: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n\n applyAgentFailure(state, reason);\n saveAgentStateSync(state);\n return true;\n}\n\nexport const recordAgentFailure = (agentId: string, reason: string): Effect.Effect<AgentState | null, FsError> =>\n Effect.gen(function* () {\n const state = yield* getAgentState(agentId);\n if (!state) return null;\n\n applyAgentFailure(state, reason);\n yield* saveAgentState(state);\n return state;\n });\n\n/** Resets failure tracking after an agent reaches running state. */\nexport function resetAgentFailureCount(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n if ((state.consecutiveFailures ?? 0) === 0 && state.firstFailureInRunAt === undefined && state.lastFailureAt === undefined && state.lastFailureReason === undefined && state.lastFailureNextRetryAt === undefined) return true;\n\n clearFailureTrackingFields(state);\n saveAgentStateSync(state);\n return true;\n}\n\n/** Reports whether callers should block start, resume, auto-resume, or message delivery on the manual pause gate. */\nexport function isAgentPaused(agentId: string): boolean {\n return getAgentStateSync(agentId)?.paused === true;\n}\n\n/** Reports whether callers should block start, resume, auto-resume, or message delivery on the troubled gate. */\nexport function isAgentTroubled(agentId: string): boolean {\n return getAgentStateSync(agentId)?.troubled === true;\n}\n\nexport async function recordStartupSessionExit(state: AgentState, issueId: string, source: Role | 'work-agent'): Promise<never> {\n await Effect.runPromise(recordAgentFailure(state.id, SESSION_EXITED_BEFORE_KICKOFF));\n const failedState = await Effect.runPromise(getAgentState(state.id));\n if (failedState) {\n failedState.status = 'stopped';\n failedState.stoppedAt = new Date().toISOString();\n failedState.kickoffDelivered = false;\n failedState.lastFailureReason = SESSION_EXITED_BEFORE_KICKOFF;\n await Effect.runPromise(saveAgentState(failedState));\n }\n state.status = 'stopped';\n state.stoppedAt = new Date().toISOString();\n state.kickoffDelivered = false;\n state.lastFailureReason = SESSION_EXITED_BEFORE_KICKOFF;\n emitActivityEntrySync({\n source,\n level: 'error',\n message: `${state.id}: session exited before kickoff could be delivered`,\n issueId,\n });\n throw new Error(`Agent ${state.id} exited before kickoff could be delivered`);\n}\n\nexport type ResumeGateBlock =\n | { gate: 'paused'; reason: string; pausedReason?: string; yieldedByScheduler?: boolean }\n | { gate: 'troubled'; reason: string; troubledAt?: string }\n | { gate: 'stopped-by-user'; reason: string }\n | { gate: 'failure-backoff'; reason: string; consecutiveFailures: number };\n\nexport type ResumeIntent = 'autonomous' | 'operator-start' | 'message-delivery';\n\nexport interface ResumeGateContext {\n hasCompletedHandoff?: boolean;\n owesRework?: boolean;\n}\n\nexport type ResumeGateDecision =\n | { decision: 'proceed'; clearStoppedByUser?: true; overrideFailureBackoff?: true; warning?: string }\n | { decision: 'block'; reason: string; needsYou?: true }\n | { decision: 'queue-message'; reason: string };\n\n/** Pure classification only. Call decideResumeGate before acting on the classified gate. */\nexport function getAgentResumeGateBlockReason(\n state: Pick<AgentState, 'paused' | 'pausedReason' | 'yieldedByScheduler' | 'troubled' | 'troubledAt' | 'stoppedByUser' | 'consecutiveFailures'>,\n): ResumeGateBlock | undefined {\n if (state.paused === true) {\n return {\n gate: 'paused',\n reason: state.pausedReason ? `agent is paused (${state.pausedReason})` : 'agent is paused',\n pausedReason: state.pausedReason,\n yieldedByScheduler: state.yieldedByScheduler,\n };\n }\n if (state.troubled === true) {\n const failures = state.consecutiveFailures ?? 0;\n return {\n gate: 'troubled',\n reason: `agent is troubled (${failures} failure${failures === 1 ? '' : 's'})`,\n troubledAt: state.troubledAt,\n };\n }\n if (state.stoppedByUser === true) return { gate: 'stopped-by-user', reason: 'agent was stopped by the operator' };\n const failures = state.consecutiveFailures ?? 0;\n if (failures > 0) return {\n gate: 'failure-backoff',\n reason: `agent is in failure backoff (${failures} failure${failures === 1 ? '' : 's'})`,\n consecutiveFailures: failures,\n };\n return undefined;\n}\n\n/** Intent-aware policy over a classified gate. This function never mutates agent state. */\nexport function decideResumeGate(\n block: ResumeGateBlock | undefined,\n intent: ResumeIntent,\n context: ResumeGateContext = {},\n): ResumeGateDecision {\n if (!block) return { decision: 'proceed' };\n if (intent === 'message-delivery') return { decision: 'queue-message', reason: block.reason };\n\n if (intent === 'operator-start') {\n if (block.gate === 'paused') return { decision: 'block', reason: `${block.reason}; run pan unpause first` };\n if (block.gate === 'troubled') return { decision: 'block', reason: `${block.reason}; run pan untroubled first` };\n if (block.gate === 'stopped-by-user') return { decision: 'proceed', clearStoppedByUser: true };\n return {\n decision: 'proceed',\n overrideFailureBackoff: true,\n warning: `Operator start overrides ${block.reason}`,\n };\n }\n\n if (block.gate === 'stopped-by-user') {\n if (context.hasCompletedHandoff === true && context.owesRework === true) {\n return { decision: 'proceed', clearStoppedByUser: true };\n }\n return { decision: 'block', reason: block.reason, needsYou: true };\n }\n return { decision: 'block', reason: block.reason };\n}\n\nfunction assertAgentCanTransitionToRunning(state: AgentState): void {\n const decision = decideResumeGate(getAgentResumeGateBlockReason(state), 'operator-start');\n if (decision.decision === 'block') {\n throw new Error(`Cannot run ${state.id}: ${decision.reason}. Clear the gate before resuming.`);\n }\n}\n\nexport function markAgentRunning(state: AgentState, options?: { preserveFailureTracking?: boolean }): void {\n assertAgentCanTransitionToRunning(state);\n const oldStatus = state.status;\n state.status = 'running';\n state.lastActivity = new Date().toISOString();\n if (options?.preserveFailureTracking !== true) {\n clearFailureTrackingFields(state);\n }\n delete state.stoppedAt;\n // Clear user-stop intent so a later crash/orphan can be auto-resumed. Without\n // this the flag is sticky across the stop→resume→crash sequence and autoResume\n // would permanently skip the agent on any subsequent orphan recovery.\n delete state.stoppedByUser;\n logAgentLifecycleSync(state.id, `status changed: ${oldStatus} → running (markAgentRunning)`);\n}\n\nfunction markAgentStopped(state: AgentState): void {\n const oldStatus = state.status;\n state.status = 'stopped';\n state.stoppedAt = new Date().toISOString();\n state.stoppedByUser = true;\n logAgentLifecycleSync(state.id, `status changed: ${oldStatus} → stopped (markAgentStopped, user-initiated)`);\n}\n\nexport function markAgentStoppedState(state: AgentState): AgentState {\n if (!state.id) {\n state.id = normalizeAgentId(state.issueId);\n }\n markAgentStopped(state);\n return state;\n}\n\nexport function markAgentRunningState(state: AgentState): AgentState {\n if (!state.id) {\n state.id = normalizeAgentId(state.issueId);\n }\n markAgentRunning(state);\n return state;\n}\n\n/** Test-only internals. Do not import outside of test files. */\nexport const __testInternals = { markAgentRunning, markAgentStopped };\n","import { existsSync } from 'fs';\nimport { readFile } from 'fs/promises';\nimport { homedir } from 'os';\nimport { dirname, join } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { isClaudeCodeChannelsMcpEnabled } from '../config-yaml.js';\nimport type { ModelId } from '../settings.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport { packageRoot } from '../paths.js';\nimport { getProviderForModelSync } from '../providers.js';\nimport { writePtyToken } from '../pty-token.js';\nimport { capturePane, sendRawKeystroke } from '../tmux.js';\nimport {\n getAgentDir,\n getAgentState,\n recordAgentFailure,\n saveAgentState,\n saveAgentStateSync,\n type AgentState,\n type Role,\n} from './agent-state.js';\n\ninterface SupervisorChannelsSpawnOptions {\n issueId: string;\n workspace: string;\n role?: Role;\n model?: string;\n harness?: RuntimeName;\n allowHost?: boolean;\n}\n\nexport function buildDefaultResumeContinueMessage(issueId: string): string {\n return `You are resuming work on ${issueId}. Read .pan/continue.json for context and pick up where you left off — do not wait for further instructions.`;\n}\n\nexport async function buildResumeMessageForAgent(\n state: AgentState,\n fallbackMessage: string,\n callerMessage?: string,\n): Promise<{ message?: string; redeliveringKickoff: boolean; error?: string }> {\n if (state.role !== 'work' || state.kickoffDelivered !== false) {\n return { message: callerMessage ?? fallbackMessage, redeliveringKickoff: false };\n }\n\n const promptPath = join(getAgentDir(state.id), 'initial-prompt.md');\n try {\n const kickoffPrompt = await readFile(promptPath, 'utf-8');\n const suffix = callerMessage\n ? `\\n\\n---\\n\\nAdditional message delivered during resume:\\n\\n${callerMessage}`\n : '';\n return { message: `${kickoffPrompt}${suffix}`, redeliveringKickoff: true };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return {\n redeliveringKickoff: true,\n error: `kickoff prompt missing at ${promptPath}: ${reason}`,\n };\n }\n}\n\nexport function markKickoffRedelivered(state: AgentState): void {\n state.kickoffDelivered = true;\n saveAgentStateSync(state);\n}\n\nexport async function recordKickoffDeliveryFailure(state: AgentState, issueId: string, source: Role | 'work-agent'): Promise<void> {\n await Effect.runPromise(recordAgentFailure(state.id, 'kickoff delivery failed'));\n const failedState = await Effect.runPromise(getAgentState(state.id));\n if (failedState) {\n failedState.status = 'running';\n failedState.kickoffDelivered = false;\n await Effect.runPromise(saveAgentState(failedState));\n }\n state.status = 'running';\n state.kickoffDelivered = false;\n emitActivityEntrySync({\n source,\n level: 'error',\n message: `${state.id}: kickoff delivery failed`,\n issueId,\n });\n}\n\ninterface ChannelsDecision {\n eligible: boolean;\n reason?: string;\n}\n\ninterface SupervisorDecision {\n eligible: boolean;\n reason?: string;\n}\n\nexport function decideSupervisorForWorkAgent(\n agentId: string,\n options: SupervisorChannelsSpawnOptions,\n state: AgentState,\n): SupervisorDecision {\n void options;\n const log = (eligible: boolean, reason?: string): void => {\n const tag = eligible ? 'supervisor:eligible' : `supervisor:ineligible:${reason ?? 'unknown'}`;\n console.log(`[${agentId}] ${tag}`);\n };\n\n if (state.role !== 'work') {\n log(false, 'not-a-work-agent');\n return { eligible: false, reason: 'not-a-work-agent' };\n }\n\n if (process.env.OVERDECK_DOCKER_WORKSPACE === '1' || process.env.PAN_DOCKER === '1') {\n log(false, 'docker-not-supported-yet');\n return { eligible: false, reason: 'docker-not-supported-yet' };\n }\n\n const behavior = state.harness ? getHarnessBehavior(state.harness) : null;\n if (!behavior?.supportsPtySupervisor) {\n const reason = `harness-${state.harness ?? 'unknown'}`;\n log(false, reason);\n return { eligible: false, reason };\n }\n\n log(true);\n return { eligible: true };\n}\n\nexport async function prepareSupervisorForFreshLaunch(\n agentId: string,\n options: SupervisorChannelsSpawnOptions,\n state: AgentState,\n): Promise<{ useSupervisor: boolean; supervisorScriptPath?: string }> {\n const supervisorDecision = decideSupervisorForWorkAgent(agentId, options, state);\n if (!supervisorDecision.eligible) {\n delete state.supervisorEnabled;\n return { useSupervisor: false };\n }\n\n const supervisorScriptPath = resolvePtySupervisorScriptPath();\n if (!existsSync(supervisorScriptPath)) {\n throw new Error('pty-supervisor build artifact missing — run `npm run build`.');\n }\n await writePtyToken(agentId);\n state.supervisorEnabled = true;\n return { useSupervisor: true, supervisorScriptPath };\n}\n\nexport async function prepareSupervisorForRelaunch(\n agentId: string,\n state: AgentState,\n model: string,\n harness: RuntimeName,\n): Promise<{ useSupervisor: boolean; supervisorScriptPath?: string }> {\n if (state.supervisorEnabled !== true) {\n return { useSupervisor: false };\n }\n\n const relaunchState: AgentState = { ...state, model, harness };\n const supervisorDecision = decideSupervisorForWorkAgent(agentId, {\n issueId: state.issueId || agentId.replace(/^agent-/, '').toUpperCase(),\n workspace: state.workspace,\n role: 'work',\n model,\n harness,\n allowHost: state.hostOverride,\n }, relaunchState);\n if (!supervisorDecision.eligible) {\n delete state.supervisorEnabled;\n return { useSupervisor: false };\n }\n\n const supervisorScriptPath = resolvePtySupervisorScriptPath();\n if (!existsSync(supervisorScriptPath)) {\n throw new Error('pty-supervisor build artifact missing — run `npm run build`.');\n }\n await writePtyToken(agentId);\n state.supervisorEnabled = true;\n return { useSupervisor: true, supervisorScriptPath };\n}\n\nfunction resolvePtySupervisorScriptPath(): string {\n return join(packageRoot, 'dist', 'pty-supervisor.js');\n}\n\n/**\n * Decide whether to enable Claude Code Channels for a work-agent launch.\n *\n * Eligibility (all required):\n * - experimental.claudeCodeChannelsMcp is true in the merged config\n * - the agent is a work agent (specialists/conversations stay off MCP)\n * - the harness is Claude Code (not Pi or another runtime harness)\n * - auth provider is Anthropic-direct (excludes Bedrock/Vertex/Foundry)\n * - the workspace is not running inside a Docker container\n *\n * Logs the decision exactly once with a category prefix so users can see why\n * the bridge did or did not engage. The function is otherwise side-effect\n * free; the caller is responsible for writing the .mcp.json and mutating\n * state.channelsEnabled when eligible is true. This legacy MCP transport is now\n * opt-in for new spawns; the PTY supervisor is the default delivery transport.\n */\nexport function decideChannelsForWorkAgent(\n agentId: string,\n _options: SupervisorChannelsSpawnOptions,\n state: AgentState,\n): ChannelsDecision {\n const log = (eligible: boolean, reason?: string): void => {\n const tag = eligible ? 'channels:eligible' : `channels:ineligible:${reason ?? 'unknown'}`;\n console.log(`[${agentId}] ${tag}`);\n };\n\n if (!isClaudeCodeChannelsMcpEnabled()) {\n return { eligible: false, reason: 'mcp-default-off' };\n }\n\n if (state.role !== 'work') {\n log(false, 'not-a-work-agent');\n return { eligible: false, reason: 'not-a-work-agent' };\n }\n\n if (!state.harness || !getHarnessBehavior(state.harness).supportsChannelsBridge) {\n log(false, `harness-${state.harness ?? 'unknown'}`);\n return { eligible: false, reason: `harness-${state.harness ?? 'unknown'}` };\n }\n\n // Auth gate. The Channels capability is gated by Anthropic auth in the\n // compiled Claude Code binary; we only attempt the bridge when the model\n // routes to the anthropic provider.\n const provider = getProviderForModelSync(state.model as ModelId);\n if (provider.name !== 'anthropic') {\n log(false, `provider-${provider.name}`);\n return { eligible: false, reason: `provider-${provider.name}` };\n }\n\n if (\n process.env.CLAUDE_CODE_USE_BEDROCK === '1' ||\n process.env.CLAUDE_CODE_USE_VERTEX === '1' ||\n process.env.CLAUDE_CODE_USE_FOUNDRY === '1'\n ) {\n log(false, 'auth-bedrock-vertex-foundry');\n return { eligible: false, reason: 'auth-bedrock-vertex-foundry' };\n }\n\n // Docker workspace gate. We do not yet share a socket dir between host and\n // container; deferred to a follow-up issue (see hazards H10).\n if (\n process.env.OVERDECK_DOCKER_WORKSPACE === '1' ||\n process.env.PAN_DOCKER === '1'\n ) {\n log(false, 'docker-not-supported-yet');\n return { eligible: false, reason: 'docker-not-supported-yet' };\n }\n\n log(true);\n return { eligible: true };\n}\n\n/**\n * Write the per-agent MCP config that points claude at the overdeck-bridge\n * stdio server. The path is the workspace-local <workspace>/.pan/agent-mcp.json\n * — one config per agent, never shared, never reused.\n */\nexport async function writeChannelsBridgeMcpConfig(\n configPath: string,\n agentId: string,\n): Promise<void> {\n const fsp = await import('fs/promises');\n await fsp.mkdir(dirname(configPath), { recursive: true });\n // Resolve the bridge entrypoint from the project root. The source file\n // lives in src/lib/channels/ and is executed directly via `bun run`\n // (Bun runs TypeScript without pre-compilation). We must point at the\n // source, not a dist copy, because the build does not copy the bridge\n // script into the bundle output.\n const here = dirname(import.meta.url.replace('file://', ''));\n const projectRoot = join(here, '..', '..');\n const repoBridgePath = join(projectRoot, 'src', 'lib', 'channels', 'overdeck-bridge.ts');\n const mcpConfig = {\n mcpServers: {\n 'overdeck-bridge': {\n command: 'bun',\n args: ['run', repoBridgePath],\n env: {\n OVERDECK_AGENT_ID: agentId,\n OVERDECK_HOME: process.env.OVERDECK_HOME ?? join(homedir(), '.overdeck'),\n },\n },\n },\n };\n await fsp.writeFile(configPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\n}\n\n/**\n * Dismiss the dev-channels confirmation TUI dialog rendered by\n * `claude --dangerously-load-development-channels`. The dialog text\n * 'WARNING: Loading development channels' must be on screen before any prompt\n * is delivered, otherwise the channel listener never registers and every\n * early channel push silently falls back to tmux.\n *\n * Polling budget is 20s because cold-start claude with TLDR + Playwright MCP\n * servers attached commonly takes 8–15s to render the first frame; a tighter\n * budget false-negatives. If the dialog is not detected within the timeout,\n * we proceed — the dialog is suppressed in some auth states (e.g. when the\n * binary takes a non-interactive code path), and the launch must not block\n * forever.\n *\n * Uses sendRawKeystrokeAsync intentionally: sendKeysAsync's load-buffer +\n * paste-buffer machinery is for typing message bodies, not for a single\n * Enter on a TUI prompt where mistimed paste can fire before the dialog\n * accepts input.\n *\n * Once the dialog is detected we send Enter and KEEP checking — a single\n * keystroke can be dropped if the TUI is still mid-render, which left the\n * dialog on screen with the helper already returned. We re-send Enter every\n * RESEND_INTERVAL_MS until the needle is gone (bounded by DISMISS_BUDGET_MS).\n */\nexport async function dismissDevChannelsDialog(agentId: string): Promise<void> {\n const TIMEOUT_MS = 20_000;\n const POLL_INTERVAL_MS = 200;\n const RESEND_INTERVAL_MS = 150;\n const DISMISS_BUDGET_MS = 5_000;\n const NEEDLE = 'WARNING: Loading development channels';\n const start = Date.now();\n while (Date.now() - start < TIMEOUT_MS) {\n try {\n const pane = await Effect.runPromise(capturePane(agentId, 50));\n if (pane.includes(NEEDLE)) {\n // Dialog is up. Send Enter, then keep re-sending until the needle\n // clears — the first keystroke can land before the TUI is ready to\n // accept it, leaving the dialog stuck on screen.\n const dismissStart = Date.now();\n while (Date.now() - dismissStart < DISMISS_BUDGET_MS) {\n await Effect.runPromise(sendRawKeystroke(agentId, 'C-m', 'channels:dismiss-dev-dialog'));\n await new Promise((r) => setTimeout(r, RESEND_INTERVAL_MS));\n const after = await Effect.runPromise(\n capturePane(agentId, 50).pipe(Effect.catch(() => Effect.succeed(''))),\n );\n if (!after.includes(NEEDLE)) return;\n }\n console.log(`[${agentId}] channels:dismiss:dialog-still-present-after-budget`);\n return;\n }\n } catch {\n // Capture failures are transient (tmux session not yet visible to\n // the new pane); keep polling within the budget.\n }\n await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));\n }\n console.log(`[${agentId}] channels:dismiss:dialog-not-detected`);\n}\n","/**\n * openai-auth.ts\n *\n * Reads Codex/ChatGPT local auth state and keeps the CLIProxy subscription\n * credential store in sync. OpenAI models are supported only through\n * Codex/ChatGPT subscription auth routed via CLIProxyAPI; API-key fallback is\n * intentionally not used because api.openai.com does not expose an\n * Anthropic-compatible /v1/messages endpoint.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { readFile } from 'fs/promises';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { FsError } from './errors.js';\nimport { bridgeCodexAuthToCliproxySync } from './cliproxy.js';\n\nexport interface OpenAIAuthStatus {\n /** True if Codex auth storage exists locally. */\n installed: boolean;\n /** True if Codex/ChatGPT OAuth refresh credentials are available. */\n loggedIn: boolean;\n /** True if the access token is expired, even if refresh may still succeed. */\n expired: boolean;\n /** Auth mode persisted by Codex (usually \"chatgpt\" for subscription auth). */\n authMode: string | null;\n /** Codex account identifier, if present. */\n accountId: string | null;\n /** When Codex last refreshed auth state, if recorded. */\n lastRefresh: string | null;\n /** Access-token expiry timestamp in ms, if it can be derived from the JWT. */\n accessTokenExpiresAt: number | null;\n /** True when an OpenAI API key is present in auth.json or env. */\n hasOpenAIApiKey: boolean;\n /** True when Overdeck bridged ~/.codex/auth.json into CLIProxy config. */\n bridgedFromCodex: boolean;\n}\n\ninterface RawAuthFile {\n auth_mode?: unknown;\n last_refresh?: unknown;\n OPENAI_API_KEY?: unknown;\n tokens?: {\n id_token?: unknown;\n access_token?: unknown;\n refresh_token?: unknown;\n account_id?: unknown;\n };\n}\n\nfunction getCodexAuthPath(): string {\n return join(homedir(), '.codex', 'auth.json');\n}\n\nfunction decodeBase64Url(value: string): string | null {\n try {\n const normalized = value.replace(/-/g, '+').replace(/_/g, '/');\n const padding = normalized.length % 4 === 0 ? '' : '='.repeat(4 - (normalized.length % 4));\n return Buffer.from(normalized + padding, 'base64').toString('utf8');\n } catch {\n return null;\n }\n}\n\nfunction decodeJwtPayload(token: string | null): Record<string, unknown> | null {\n if (!token) return null;\n const parts = token.split('.');\n if (parts.length < 2) return null;\n\n const decoded = decodeBase64Url(parts[1]);\n if (!decoded) return null;\n\n try {\n return JSON.parse(decoded) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction getJwtExpiry(token: string | null): number | null {\n const payload = decodeJwtPayload(token);\n return typeof payload?.exp === 'number' ? payload.exp * 1000 : null;\n}\n\nasync function readCodexAuthAsync(authPath: string): Promise<RawAuthFile | null> {\n if (!existsSync(authPath)) return null;\n try {\n return JSON.parse(await readFile(authPath, 'utf8')) as RawAuthFile;\n } catch {\n return null;\n }\n}\n\nfunction readCodexAuthSync(authPath: string): RawAuthFile | null {\n if (!existsSync(authPath)) return null;\n try {\n return JSON.parse(readFileSync(authPath, 'utf8')) as RawAuthFile;\n } catch {\n return null;\n }\n}\n\nfunction hasApiKey(raw: RawAuthFile | null): boolean {\n return !!process.env.OPENAI_API_KEY\n || (typeof raw?.OPENAI_API_KEY === 'string' && raw.OPENAI_API_KEY.trim().length > 0);\n}\n\nfunction buildStatus(raw: RawAuthFile | null, installed: boolean, bridgedFromCodex: boolean): OpenAIAuthStatus {\n const authMode = typeof raw?.auth_mode === 'string' ? raw.auth_mode : null;\n const lastRefresh = typeof raw?.last_refresh === 'string' ? raw.last_refresh : null;\n const codexAccessToken = typeof raw?.tokens?.access_token === 'string' ? raw.tokens.access_token : null;\n const codexRefreshToken = typeof raw?.tokens?.refresh_token === 'string' ? raw.tokens.refresh_token : null;\n const codexIdToken = typeof raw?.tokens?.id_token === 'string' ? raw.tokens.id_token : null;\n const codexAccountId = typeof raw?.tokens?.account_id === 'string' ? raw.tokens.account_id : null;\n const accessTokenExpiresAt = getJwtExpiry(codexAccessToken) ?? getJwtExpiry(codexIdToken);\n\n return {\n installed,\n loggedIn: !!codexRefreshToken,\n expired: !!(accessTokenExpiresAt && accessTokenExpiresAt < Date.now()),\n authMode,\n accountId: codexAccountId,\n lastRefresh,\n accessTokenExpiresAt,\n hasOpenAIApiKey: hasApiKey(raw),\n bridgedFromCodex,\n };\n}async function getOpenAIAuthStatusPromise(): Promise<OpenAIAuthStatus> {\n const codexDir = join(homedir(), '.codex');\n const authPath = getCodexAuthPath();\n const installed = existsSync(codexDir) || existsSync(authPath);\n const raw = await readCodexAuthAsync(authPath);\n\n let bridgedFromCodex = false;\n try {\n if (bridgeCodexAuthToCliproxySync()) {\n bridgedFromCodex = true;\n }\n } catch { /* non-fatal — cliproxy bridge is best-effort */ }\n\n return buildStatus(raw, installed, bridgedFromCodex);\n}\n\n/** Effect variant of {@link getOpenAIAuthStatus}. */\nexport const getOpenAIAuthStatus = (): Effect.Effect<OpenAIAuthStatus, FsError> =>\n Effect.tryPromise({\n try: () => getOpenAIAuthStatusPromise(),\n catch: (cause) => new FsError({ path: getCodexAuthPath(), operation: 'getOpenAIAuthStatus', cause }),\n });\n\n/** Synchronous variant for CLI-side code. Dashboard server routes should use {@link getOpenAIAuthStatus}. */\nexport function getOpenAIAuthStatusSync(): OpenAIAuthStatus {\n const codexDir = join(homedir(), '.codex');\n const authPath = getCodexAuthPath();\n const installed = existsSync(codexDir) || existsSync(authPath);\n const raw = readCodexAuthSync(authPath);\n\n let bridgedFromCodex = false;\n try {\n if (bridgeCodexAuthToCliproxySync()) {\n bridgedFromCodex = true;\n }\n } catch { /* non-fatal */ }\n\n return buildStatus(raw, installed, bridgedFromCodex);\n}\n","/**\n * Provider Health Probing\n *\n * Pre-flight check for non-Anthropic direct providers before spawning agents.\n * Sends a minimal request to detect quota exhaustion, auth failures, and\n * network issues BEFORE the agent enters Claude Code's opaque retry loop.\n */\n\nimport { Effect } from 'effect';\nimport { getProviderEnvSync, getProviderForModelSync, type ProviderConfig } from './providers.js';\nimport { loadConfigSync as loadYamlConfig } from './config-yaml.js';\nimport { ensureOpenAICompatibleProxyRunning } from './openai-compatible-proxy.js';\nimport type { ModelId } from './settings.js';\n\nexport type ProbeResultKind = 'quota' | 'auth' | 'timeout' | 'network' | 'server' | 'unknown';\n\nexport type ProbeResult =\n | { ok: true }\n | { ok: false; kind: ProbeResultKind; status?: number; message: string };\n\ninterface CacheEntry {\n result: ProbeResult;\n expires: number;\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes — for durable results (ok, auth, quota)\nconst TRANSIENT_CACHE_TTL_MS = 15 * 1000; // timeout/network/server/unknown are often a one-off blip\nconst PROBE_TIMEOUT_MS = 8000;\nconst cache = new Map<string, CacheEntry>();\n\nfunction cacheKey(provider: string, apiKey: string): string {\n // Hash the key to avoid storing raw secrets in memory map keys\n const keyHash = apiKey.slice(0, 8) + '...' + apiKey.slice(-4);\n return `${provider}:${keyHash}`;\n}\n\nfunction classifyError(status: number, body: string): ProbeResult {\n const lower = body.toLowerCase();\n\n if (status === 429) {\n if (lower.includes('quota') || lower.includes('exhausted') || lower.includes('limit')) {\n return { ok: false, kind: 'quota', status, message: parseErrorMessage(body) || 'Quota exhausted' };\n }\n return { ok: false, kind: 'quota', status, message: parseErrorMessage(body) || 'Rate limited (429)' };\n }\n\n if (status === 401 || status === 403) {\n return { ok: false, kind: 'auth', status, message: parseErrorMessage(body) || 'Invalid API key' };\n }\n\n if (status >= 500) {\n return { ok: false, kind: 'server', status, message: parseErrorMessage(body) || `Server error (${status})` };\n }\n\n return { ok: false, kind: 'unknown', status, message: parseErrorMessage(body) || `Unexpected status ${status}` };\n}\n\nfunction parseErrorMessage(body: string): string {\n try {\n const parsed = JSON.parse(body);\n return parsed?.error?.message || parsed?.message || '';\n } catch {\n return body.slice(0, 200);\n }\n}\n\nexport function buildAnthropicMessagesUrl(baseUrl: string): string {\n const normalized = baseUrl.replace(/\\/+$/, '');\n return normalized.endsWith('/v1')\n ? `${normalized}/messages`\n : `${normalized}/v1/messages`;\n}async function probeProviderPromise(\n provider: ProviderConfig,\n apiKey: string,\n model: string,\n): Promise<ProbeResult> {\n const key = cacheKey(provider.name, apiKey);\n const cached = cache.get(key);\n if (cached && Date.now() < cached.expires) {\n return cached.result;\n }\n\n const result = await doProbe(provider, apiKey, model);\n\n // Auth/quota failures are durable (a bad key or exhausted quota won't\n // resolve itself in seconds) and are worth caching for the full TTL.\n // timeout/network/server/unknown are frequently a one-off blip — caching\n // those for 5 minutes would keep telling the user a healthy provider is\n // down for the full window, blocking every retry with a stale verdict.\n const isTransientFailure = !result.ok && (result.kind === 'timeout' || result.kind === 'network' || result.kind === 'server' || result.kind === 'unknown');\n const ttl = isTransientFailure ? TRANSIENT_CACHE_TTL_MS : CACHE_TTL_MS;\n cache.set(key, { result, expires: Date.now() + ttl });\n return result;\n}\n\n/**\n * Clear cached probe result for a provider (e.g. after key change).\n */\nexport function invalidateProbeCacheSync(provider?: string): void {\n if (provider) {\n for (const k of cache.keys()) {\n if (k.startsWith(`${provider}:`)) cache.delete(k);\n }\n } else {\n cache.clear();\n }\n}\n\nasync function doProbe(\n provider: ProviderConfig,\n apiKey: string,\n model: string,\n): Promise<ProbeResult> {\n // Nous Portal routes through the local OpenAI-compatible proxy sidecar at\n // 127.0.0.1:12436. Without this, the probe runs before the proxy is booted\n // on the first spawn after every dashboard restart and surfaces a spurious\n // \"Cannot reach Nous Portal: fetch failed\" toast.\n if (provider.name === 'nous') {\n await Effect.runPromise(ensureOpenAICompatibleProxyRunning());\n // Use GET /v1/models instead of POST /v1/messages. The only Nous model\n // (qwen/qwen3.6-plus) is a reasoning model that ignores max_tokens for\n // its reasoning phase and routinely takes >8s to answer a one-character\n // probe, blowing the timeout. /v1/models verifies network + auth without\n // burning reasoning tokens.\n return probeModelsEndpoint(provider, apiKey);\n }\n\n const providerEnv = getProviderEnvSync(provider, apiKey);\n const baseUrl = providerEnv.ANTHROPIC_BASE_URL ?? provider.baseUrl;\n if (!baseUrl) return { ok: true };\n\n const url = buildAnthropicMessagesUrl(baseUrl);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'content-type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model,\n max_tokens: 1,\n messages: [{ role: 'user', content: '.' }],\n }),\n });\n\n clearTimeout(timer);\n const body = await response.text();\n\n // 2xx = provider is working (even if it returned empty/short content)\n if (response.ok) {\n return { ok: true };\n }\n\n return classifyError(response.status, body);\n } catch (err: unknown) {\n clearTimeout(timer);\n return classifyFetchError(err, provider);\n }\n}\n\nasync function probeModelsEndpoint(\n provider: ProviderConfig,\n apiKey: string,\n): Promise<ProbeResult> {\n const baseUrl = provider.baseUrl;\n if (!baseUrl) return { ok: true };\n\n const normalized = baseUrl.replace(/\\/+$/, '');\n const url = normalized.endsWith('/v1') ? `${normalized}/models` : `${normalized}/v1/models`;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n signal: controller.signal,\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n accept: 'application/json',\n },\n });\n\n clearTimeout(timer);\n if (response.ok) return { ok: true };\n return classifyError(response.status, await response.text());\n } catch (err: unknown) {\n clearTimeout(timer);\n return classifyFetchError(err, provider);\n }\n}\n\nfunction classifyFetchError(err: unknown, provider: ProviderConfig): ProbeResult {\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return { ok: false, kind: 'timeout', message: `Provider did not respond within ${PROBE_TIMEOUT_MS / 1000}s` };\n }\n if (err.message.includes('ECONNREFUSED') || err.message.includes('ENOTFOUND') || err.message.includes('fetch failed')) {\n return { ok: false, kind: 'network', message: `Cannot reach ${provider.displayName}: ${err.message}` };\n }\n }\n return { ok: false, kind: 'unknown', message: `Probe failed: ${(err as Error).message ?? err}` };\n}\n\n/**\n * User-facing error message for a failed probe, suitable for dashboard display.\n */\nexport function formatProbeError(provider: ProviderConfig, model: string, result: ProbeResult & { ok: false }): string {\n const prefix = `${provider.displayName} (${model})`;\n\n switch (result.kind) {\n case 'quota':\n return `${prefix}: quota exhausted — top up credits or switch to a different model`;\n case 'auth':\n return `${prefix}: authentication failed — check your API key in Settings`;\n case 'timeout':\n return `${prefix}: endpoint unreachable (timeout) — the provider may be down`;\n case 'network':\n return `${prefix}: network error — ${result.message}`;\n case 'server':\n return `${prefix}: provider returned ${result.status} server error — try again later or switch models`;\n default:\n return `${prefix}: pre-flight check failed — ${result.message}`;\n }\n}async function validateProviderHealthPromise(\n model: string,\n apiKey?: string,\n): Promise<void> {\n const provider = getProviderForModelSync(model as ModelId);\n\n // Skip: Anthropic native and OpenAI subscription routing have their own checks.\n if (provider.name === 'anthropic' || provider.name === 'openai') {\n return;\n }\n\n const key = apiKey ?? resolveApiKey(provider);\n if (!key) return; // No key configured — let downstream handle the \"no key\" error\n\n const result = await Effect.runPromise(probeProvider(provider, key, model));\n if (!result.ok) {\n throw new ProviderHealthError(provider, model, result);\n }\n}\n\nfunction resolveApiKey(provider: ProviderConfig): string | undefined {\n const { config } = loadYamlConfig();\n return config.apiKeys[provider.name as keyof typeof config.apiKeys];\n}\n\nexport class ProviderHealthError extends Error {\n public readonly provider: ProviderConfig;\n public readonly model: string;\n public readonly probeResult: ProbeResult & { ok: false };\n\n constructor(provider: ProviderConfig, model: string, result: ProbeResult & { ok: false }) {\n super(formatProbeError(provider, model, result));\n this.name = 'ProviderHealthError';\n this.provider = provider;\n this.model = model;\n this.probeResult = result;\n }\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of {@link probeProvider}. Never fails — probe results\n * (including error classifications) are carried in the success channel as\n * `ProbeResult`.\n */\nexport const probeProvider = (\n provider: ProviderConfig,\n apiKey: string,\n model: string,\n): Effect.Effect<ProbeResult, never> =>\n Effect.promise(() => probeProviderPromise(provider, apiKey, model));\n\n/** Effect variant of {@link validateProviderHealth}. */\nexport const validateProviderHealth = (\n model: string,\n apiKey?: string,\n): Effect.Effect<void, ProviderHealthError> =>\n Effect.tryPromise({\n try: () => validateProviderHealthPromise(model, apiKey),\n catch: (cause) => {\n if (cause instanceof ProviderHealthError) return cause;\n // Should never happen — validateProviderHealth only throws ProviderHealthError.\n // Re-wrap defensively so the typed error channel stays narrow.\n const provider = getProviderForModelSync(model as ModelId);\n return new ProviderHealthError(provider, model, {\n ok: false,\n kind: 'unknown',\n message: cause instanceof Error ? cause.message : String(cause),\n });\n },\n });\n\n/** Effect variant of {@link invalidateProbeCacheSync}. Pure cache mutation; cannot fail. */\nexport const invalidateProbeCache = (provider?: string): Effect.Effect<void, never> =>\n Effect.sync(() => invalidateProbeCacheSync(provider));\n","import { Effect } from 'effect';\nimport type { NormalizedCavemanConfig } from '../config-yaml.js';\nimport { loadConfigSync as loadYamlConfig, resolveModel } from '../config-yaml.js';\nimport { readCavemanVariant } from '../caveman/workspace.js';\nimport { bridgeGeminiAuthToCliproxy, getCliproxyClientEnv } from '../cliproxy.js';\nimport { normalizeModelOverrideSync, requireModelOverrideSync } from '../model-validation.js';\nimport { getOpenAIAuthStatus } from '../openai-auth.js';\nimport { ensureOpenAICompatibleProxyRunning } from '../openai-compatible-proxy.js';\nimport { validateProviderHealth } from '../provider-health.js';\nimport { getProviderEnvSync, getProviderForModelSync } from '../providers.js';\nimport { hasModelCapabilitySync, getModelCapabilitySync, resolveModelIdSync } from '../model-capabilities.js';\nimport type { Role } from './agent-state.js';\n\n/** Map abstract/future model names to CLIProxy-supported names.\n * The CLIProxy registry has gpt-5.4 but not gpt-5.4-pro. */\nexport const CLI_PROXY_MODEL_ALIASES: Record<string, string> = {\n 'gpt-5.5-pro': 'gpt-5.5',\n 'gpt-5.4-pro': 'gpt-5.4',\n};\n\n/**\n * Get provider-specific env vars (BASE_URL, AUTH_TOKEN) for a model.\n * Reads the current API key from settings so resumed/recovered agents\n * always use the latest key.\n */\nexport async function getProviderEnvForModel(model: string): Promise<Record<string, string>> {\n const provider = getProviderForModelSync(model);\n if (provider.name === 'anthropic') return {};\n\n const { config } = loadYamlConfig();\n\n // OpenRouter API key is stored in config.yaml under providers.openrouter.api_key\n if (provider.name === 'openrouter') {\n const apiKey = config.apiKeys.openrouter;\n if (apiKey) {\n return getProviderEnvSync(provider, apiKey);\n }\n throw new Error(`OpenRouter API key not configured. Add your key in Settings → OpenRouter before using model \"${model}\".`);\n }\n\n const apiKey = config.apiKeys[provider.name as keyof typeof config.apiKeys];\n\n if (provider.name === 'google') {\n if (!apiKey) {\n throw new Error(`Google API key not configured. Add GOOGLE_API_KEY in Settings → Google or ~/.overdeck.env before using model \"${model}\".`);\n }\n\n if (!await Effect.runPromise(bridgeGeminiAuthToCliproxy(apiKey))) {\n throw new Error(`Failed to bridge Google API key into CLIProxy before using model \"${model}\".`);\n }\n\n return getCliproxyClientEnv();\n }\n\n if (provider.name === 'openai') {\n const authStatus = await Effect.runPromise(getOpenAIAuthStatus());\n if (authStatus.loggedIn) {\n // Route through the local CLIProxyAPI sidecar using the user's\n // ChatGPT subscription OAuth tokens. Claude Code sees a normal\n // Anthropic-compatible endpoint and never needs an API key.\n return getCliproxyClientEnv();\n }\n\n const configuredKey = apiKey || authStatus.hasOpenAIApiKey;\n throw new Error(\n configuredKey\n ? `OpenAI API-key routing is no longer supported for model \"${model}\" because api.openai.com does not expose an Anthropic-compatible /v1/messages endpoint. Sign in with a Codex/ChatGPT subscription via \\`pan admin specialists codex login\\` or Dashboard Settings → Codex Login.`\n : `Codex/ChatGPT subscription login required for OpenAI model \"${model}\". Sign in via \\`pan admin specialists codex login\\` or Dashboard Settings → Codex Login.`,\n );\n }\n\n if (apiKey) {\n if (provider.name === 'nous') {\n await Effect.runPromise(ensureOpenAICompatibleProxyRunning());\n }\n await Effect.runPromise(validateProviderHealth(model, apiKey));\n return getProviderEnvSync(provider, apiKey);\n }\n\n throw new Error(`No API key configured for ${provider.displayName}. Configure it in Settings before using model \"${model}\".`);\n}\n\n/**\n * Get bash export lines for provider env vars (for use in launcher scripts).\n * Returns empty string for Anthropic models.\n */\nconst PROVIDER_ENV_KEYS = [\n 'ANTHROPIC_API_KEY',\n 'ANTHROPIC_BASE_URL',\n 'ANTHROPIC_AUTH_TOKEN',\n 'ANTHROPIC_DEFAULT_HAIKU_MODEL',\n 'ANTHROPIC_DEFAULT_OPUS_MODEL',\n 'ANTHROPIC_DEFAULT_SONNET_MODEL',\n 'ANTHROPIC_SMALL_FAST_MODEL',\n 'CLAUDE_CODE_SUBAGENT_MODEL',\n 'CLAUDE_CODE_AUTO_COMPACT_WINDOW',\n 'OPENAI_API_KEY',\n 'GEMINI_API_KEY',\n 'API_TIMEOUT_MS',\n 'CLAUDE_CODE_API_KEY_HELPER_TTL_MS',\n // Pi-native provider env vars (bridged from Overdeck settings so Pi can auth)\n 'KIMI_API_KEY',\n 'MINIMAX_API_KEY',\n 'ZAI_API_KEY',\n 'MIMO_API_KEY',\n 'OPENROUTER_API_KEY',\n 'NOUS_API_KEY',\n 'DASHSCOPE_API_KEY',\n] as const;\n\nfunction getClaudeCodeAutoCompactWindowForModel(model: string): number | undefined {\n const provider = getProviderForModelSync(model);\n if (provider.name === 'anthropic') return undefined;\n\n const resolvedModel = resolveModelIdSync(model);\n if (!hasModelCapabilitySync(resolvedModel)) return undefined;\n return getModelCapabilitySync(resolvedModel).contextWindow;\n}\n\nexport async function getProviderExportsForModel(model: string): Promise<string> {\n const envVars = await getProviderEnvForModel(model);\n const unsetLines = PROVIDER_ENV_KEYS.map(key => `unset ${key}`);\n const autoCompactWindow = getClaudeCodeAutoCompactWindowForModel(model);\n const exportLines = Object.entries(envVars)\n .map(([k, v]) => `export ${k}=\"${v.replace(/\"/g, '\\\\\"')}\"`);\n if (autoCompactWindow !== undefined) {\n exportLines.push(`export CLAUDE_CODE_AUTO_COMPACT_WINDOW=\"${autoCompactWindow}\"`);\n }\n\n return [...unsetLines, ...exportLines].join('\\n') + '\\n';\n}\n\n/**\n * Build a sanitized env for programmatically spawning `claude`.\n *\n * The dashboard parent process may inherit provider env vars (e.g.\n * ANTHROPIC_BASE_URL pointing at the CLIProxy sidecar) that would mis-route\n * a child process targeting an Anthropic model. Launcher *scripts* strip\n * these via `unset` lines; programmatic spawns must do the same.\n *\n * Returns a copy of `baseEnv` (default: process.env) with all PROVIDER_ENV_KEYS\n * deleted, then overlaid with the correct provider env for `model`.\n */\nexport async function buildSpawnEnvForModel(\n model: string,\n baseEnv: NodeJS.ProcessEnv = process.env,\n): Promise<Record<string, string>> {\n const sanitized: Record<string, string> = {};\n for (const [k, v] of Object.entries(baseEnv)) {\n if (v === undefined) continue;\n if ((PROVIDER_ENV_KEYS as readonly string[]).includes(k)) continue;\n sanitized[k] = v;\n }\n const providerEnv = await getProviderEnvForModel(model);\n const autoCompactWindow = getClaudeCodeAutoCompactWindowForModel(model);\n return {\n ...sanitized,\n ...providerEnv,\n ...(autoCompactWindow !== undefined ? { CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(autoCompactWindow) } : {}),\n };\n}\n\n/**\n * Get tmux -e flags for provider env vars (for use in tmux new-session).\n * Returns empty string for Anthropic models.\n */\nexport async function getProviderTmuxFlags(model: string): Promise<string> {\n const envVars = await getProviderEnvForModel(model);\n let flags = '';\n for (const [key, value] of Object.entries(envVars)) {\n flags += ` -e ${key}=\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n return flags;\n}\n\n/**\n * Build shell export lines to inject into a work agent's launcher.sh.\n *\n * Sets CAVEMAN_DEFAULT_MODE and OVERDECK_CAVEMAN_VARIANT so the caveman\n * SessionStart hook activates at the right intensity level and cost events\n * carry the A/B test variant.\n *\n * @param workspacePath Absolute workspace path (to read stored variant)\n * @param config Normalized caveman config from YamlConfig\n * @param isPlanning True for planning agents — caveman always disabled there\n * @returns Shell export lines to prepend to the launcher script\n */\nexport async function buildCavemanExports(\n workspacePath: string,\n config: NormalizedCavemanConfig,\n isPlanning: boolean\n): Promise<string> {\n // Planning agents: never compress — output is user-facing\n if (isPlanning || !config.enabled) return '';\n\n const variant = await Effect.runPromise(readCavemanVariant(workspacePath));\n\n // If this workspace's A/B variant is 'disabled', set variant for tracking but no mode\n if (variant === 'off') return '';\n if (variant === 'disabled') {\n return `export OVERDECK_CAVEMAN_VARIANT=\"${variant}\"\\n`;\n }\n\n // Work agents use the 'work' intensity mode\n const mode = config.modes.work;\n if (mode === 'off' || mode === 'disabled') return '';\n\n return `export CAVEMAN_DEFAULT_MODE=\"${mode}\"\\nexport OVERDECK_CAVEMAN_VARIANT=\"${variant}\"\\n`;\n}\n\n/**\n * Determine which model to use for a role-based work agent.\n *\n * Priority:\n * 1. Explicitly provided model (options.model)\n * 2. Role routing via config.yaml roles/workhorses (defaults to work)\n *\n * Resolution failures propagate as spawn-time errors. Per PAN-1048 PRD:\n * invalid workhorse references and unresolved role configs must fail loudly\n * at config-load/spawn time, not silently fall back to a hidden default\n * model. Defaults are seeded into the config when entries are absent\n * (DEFAULT_WORKHORSES / DEFAULT_ROLES) — anything that still raises here\n * is a real configuration bug the user must see.\n */\n/**\n * Models that are known-broken for autonomous *work* agents and must never be\n * used to spawn one, even if a project pins them in config. The gate fails\n * loudly for the work role when the model wasn't an explicit per-spawn override.\n *\n * Empty as of PAN-1584: gpt-5.5 used to wedge at launch with CLIProxy \"System\n * messages are not allowed\", which was a stale CLIProxyAPI binary (6.9.45)\n * mis-translating Claude Code's request to the Codex backend. Upgrading the\n * pinned CLIProxyAPI to v7.1.39 (+ a version-aware installer) fixed it; gpt-5.5\n * work agents now launch clean under the claude-code harness. The mechanism is\n * retained for any future known-broken model. (Pi-harness gpt-5.5 was not\n * re-verified in this pass — re-add 'gpt-5.5' here if a Pi init hang resurfaces.)\n */\nconst WORK_AGENT_BROKEN_MODELS = new Set<string>([]);\n\nexport function determineModel(options: { model?: string; role?: Role; spawnKey?: string } = {}): string {\n const modelOverride = normalizeModelOverrideSync(options.model);\n const resolved = modelOverride\n ? modelOverride\n : requireModelOverrideSync(resolveModel(options.role ?? 'work', undefined, loadYamlConfig().config, options.spawnKey));\n\n // Work-agent safety net: a config pin (or smart-selection) must not spawn a\n // work agent on a model that is known to wedge for the work role. Fail loudly\n // rather than launch a dead agent or silently substitute another model. Only\n // applies to the work role and only when the model wasn't an explicit,\n // deliberate per-spawn override.\n const role = options.role ?? 'work';\n if (role === 'work' && !modelOverride && WORK_AGENT_BROKEN_MODELS.has(resolved)) {\n throw new Error(\n `Resolved work model \"${resolved}\" is known-broken for work agents. ` +\n 'Set roles.work.model to a working model in config.yaml, or pass an explicit --model override.',\n );\n }\n\n return resolved;\n}\n","/**\n * claude-auth.ts\n *\n * Reads Claude Code's credentials to determine whether the user is logged in\n * with a subscription (MAX / Pro / Team) or not.\n *\n * Credentials are checked in order:\n * 1. ~/.claude/.credentials.json (Linux, older Claude Code versions)\n * 2. macOS Keychain: \"Claude Code-credentials\" (macOS with newer Claude Code)\n *\n * The credential payload contains:\n * claudeAiOauth.subscriptionType — \"max\" | \"pro\" | \"team\" | null\n * claudeAiOauth.rateLimitTier — e.g. \"default_claude_max_20x\"\n * claudeAiOauth.accessToken — OAuth bearer token\n * claudeAiOauth.expiresAt — Unix timestamp (ms)\n */\n\nimport { existsSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { platform } from 'process';\nimport { Duration, Effect, FileSystem } from 'effect';\nimport { ChildProcessSpawner } from 'effect/unstable/process/ChildProcessSpawner';\nimport { ChildProcess } from 'effect/unstable/process';\nimport { layer as NodeServicesLayer } from '@effect/platform-node/NodeServices';\nimport { ClaudeCredentialParseError } from './errors.js';\n\nexport interface ClaudeAuthStatus {\n /** True if the ~/.claude directory exists (i.e. Claude Code has been installed). */\n installed: boolean;\n /** True if a non-expired OAuth token is present. */\n loggedIn: boolean;\n /** True if the token exists but has already expired. */\n expired: boolean;\n /** \"max\" | \"pro\" | \"team\" | null — from claudeAiOauth.subscriptionType */\n subscriptionType: string | null;\n /** e.g. \"default_claude_max_20x\" */\n rateLimitTier: string | null;\n /** Token expiry timestamp in ms, or null if unknown. */\n expiresAt: number | null;\n /** True when ANTHROPIC_API_KEY is set in the server process environment.\n * When present, Claude Code prefers this over subscription auth. */\n hasAnthropicApiKey: boolean;\n}\n\nexport function parseOAuthPayload(raw: string): Effect.Effect<\n {\n loggedIn: boolean;\n expired: boolean;\n subscriptionType: string | null;\n rateLimitTier: string | null;\n expiresAt: number | null;\n },\n ClaudeCredentialParseError\n> {\n return Effect.try({\n try: () => {\n const creds = JSON.parse(raw) as Record<string, unknown>;\n const oauth = (creds.claudeAiOauth ?? {}) as Record<string, unknown>;\n\n if (!oauth.accessToken) {\n return { loggedIn: false, expired: false, subscriptionType: null, rateLimitTier: null, expiresAt: null };\n }\n\n const expiresAt = typeof oauth.expiresAt === 'number' ? oauth.expiresAt : null;\n const expired = !!(expiresAt && expiresAt < Date.now());\n\n // Treat as logged in even if expired — Claude Code auto-refreshes tokens\n // transparently. Only mark as not-logged-in if there's no token at all.\n return {\n loggedIn: true,\n expired,\n subscriptionType: typeof oauth.subscriptionType === 'string' ? oauth.subscriptionType : null,\n rateLimitTier: typeof oauth.rateLimitTier === 'string' ? oauth.rateLimitTier : null,\n expiresAt,\n };\n },\n catch: (cause) => new ClaudeCredentialParseError({ message: 'Failed to parse credentials JSON', cause }),\n });\n}\n\n/**\n * Read credentials JSON from macOS Keychain.\n * Claude Code ≥2.x stores credentials under service \"Claude Code-credentials\".\n */\nconst readKeychainCredentials: Effect.Effect<string | null, never, ChildProcessSpawner> =\n Effect.gen(function* () {\n if (platform !== 'darwin') return null;\n const spawner = yield* ChildProcessSpawner;\n const cmd = ChildProcess.make('security', [\n 'find-generic-password', '-s', 'Claude Code-credentials', '-w',\n ]);\n return yield* spawner.string(cmd).pipe(\n Effect.timeout(Duration.seconds(3)),\n Effect.map((s) => s.trim() || null),\n Effect.orElseSucceed(() => null),\n );\n });\n\nconst getClaudeAuthStatusImpl: Effect.Effect<\n ClaudeAuthStatus,\n never,\n FileSystem.FileSystem | ChildProcessSpawner\n> = Effect.gen(function* () {\n const claudeDir = join(homedir(), '.claude');\n const credPath = join(claudeDir, '.credentials.json');\n\n const installed = existsSync(claudeDir);\n const hasAnthropicApiKey = !!process.env.ANTHROPIC_API_KEY;\n\n let loggedIn = false;\n let expired = false;\n let subscriptionType: string | null = null;\n let rateLimitTier: string | null = null;\n let expiresAt: number | null = null;\n\n // 1. Try flat credentials file (Linux, older Claude Code)\n if (existsSync(credPath)) {\n const fs = yield* FileSystem.FileSystem;\n const result = yield* fs.readFileString(credPath, 'utf-8').pipe(\n Effect.flatMap(parseOAuthPayload),\n Effect.orElseSucceed(() => null),\n );\n if (result) {\n loggedIn = result.loggedIn;\n expired = result.expired;\n subscriptionType = result.subscriptionType;\n rateLimitTier = result.rateLimitTier;\n expiresAt = result.expiresAt;\n }\n }\n\n // 2. Fall back to macOS Keychain (Claude Code ≥2.x on macOS)\n if (!loggedIn) {\n const keychainRaw = yield* readKeychainCredentials;\n if (keychainRaw) {\n const result = yield* parseOAuthPayload(keychainRaw).pipe(\n Effect.orElseSucceed(() => null),\n );\n if (result) {\n loggedIn = result.loggedIn;\n expired = result.expired;\n subscriptionType = result.subscriptionType;\n rateLimitTier = result.rateLimitTier;\n expiresAt = result.expiresAt;\n }\n }\n }\n\n return { installed, loggedIn, expired, subscriptionType, rateLimitTier, expiresAt, hasAnthropicApiKey };\n});\n\nexport function getClaudeAuthStatus(): Effect.Effect<ClaudeAuthStatus, never> {\n return getClaudeAuthStatusImpl.pipe(\n Effect.scoped,\n Effect.provide(NodeServicesLayer),\n );\n}\n","/**\n * oh-my-pi (omp) RPC named-pipe lifecycle (PAN-1989).\n *\n * omp runs in `--mode rpc` and reads JSONL commands from stdin. We feed omp by\n * redirecting its stdin from a per-agent fifo at:\n *\n * $OVERDECK_HOME/agents/<agentId>/rpc.in\n *\n * The launcher script creates the fifo, then `exec omp --mode rpc ... < <fifo>`.\n * The runtime adapter (OhmypiRuntime.sendMessage) writes JSONL lines to the fifo.\n *\n * Why a fifo and not stdin pipe + tmux paste-buffer:\n * - tmux paste-buffer is unreliable for JSONL (Enter timing, terminal\n * echo, line buffering). The fifo path bypasses the TTY entirely.\n * - omp remains visible inside its tmux pane for crash isolation and\n * visual debugging — only its stdin is redirected.\n *\n * Ordering invariant:\n * - The launcher creates the fifo BEFORE spawning omp.\n * - omp's first stdout event is `session_start` which the extension turns\n * into ready.json under $OVERDECK_HOME/agents/<agentId>/.\n * - Adapter MUST wait for ready.json before opening the writer side of\n * the fifo.\n *\n * IMPORTANT: this module performs blocking I/O (mkfifo + file open) inside\n * async helpers. NEVER call it from a dashboard server route handler — only\n * from spawnAgent and killAgent paths that run on dedicated workers.\n */\n\nimport { existsSync, mkdirSync, openSync, writeSync, closeSync, unlinkSync, constants as fsConstants } from 'node:fs'\nimport { join } from 'node:path'\nimport { exec } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { Data, Effect } from 'effect'\nimport { getOverdeckHome } from '../paths.js'\n\nconst execAsync = promisify(exec)\n\nexport interface OhmypiFifoPaths {\n agentDir: string\n readyPath: string\n fifoPath: string\n}\n\nexport class OhmypiNotReady extends Error {\n readonly code = 'OHMYPI_NOT_READY' as const\n constructor(message: string) {\n super(message)\n this.name = 'OhmypiNotReady'\n }\n}\n\nexport function ohmypiFifoPaths(agentId: string, home?: string): OhmypiFifoPaths {\n const overdeckHome = home === undefined ? getOverdeckHome() : join(home, '.overdeck')\n const agentDir = join(overdeckHome, 'agents', agentId)\n return {\n agentDir,\n readyPath: join(agentDir, 'ready.json'),\n fifoPath: join(agentDir, 'rpc.in'),\n }\n}\n\nasync function createOhmypiFifoPromise(agentId: string, home?: string): Promise<string> {\n const paths = ohmypiFifoPaths(agentId, home)\n mkdirSync(paths.agentDir, { recursive: true, mode: 0o700 })\n if (existsSync(paths.fifoPath)) {\n unlinkSync(paths.fifoPath)\n }\n await execAsync(`mkfifo -m 600 ${shellQuote(paths.fifoPath)}`)\n return paths.fifoPath\n}\n\n/**\n * Unlink the fifo. Safe to call when the fifo does not exist.\n */\nexport function destroyOhmypiFifoSync(agentId: string, home?: string): void {\n const paths = ohmypiFifoPaths(agentId, home)\n try {\n unlinkSync(paths.fifoPath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err\n }\n}\n\n/**\n * Write a single JSONL command to the agent's fifo.\n *\n * Throws OhmypiNotReady (without blocking) when ready.json is not yet present.\n * Throws OhmypiNotReady when the fifo exists but omp has no read fd open (ENXIO).\n */\nexport function writeOhmypiCommandSync(agentId: string, command: unknown, home?: string): void {\n const paths = ohmypiFifoPaths(agentId, home)\n if (!existsSync(paths.readyPath)) {\n throw new OhmypiNotReady(\n `omp agent ${agentId}: ready.json not present yet (waiting for session_start)`,\n )\n }\n if (!existsSync(paths.fifoPath)) {\n throw new OhmypiNotReady(`omp agent ${agentId}: rpc.in fifo missing`)\n }\n\n let fd: number\n try {\n fd = openSync(paths.fifoPath, fsConstants.O_WRONLY | fsConstants.O_NONBLOCK)\n } catch (err) {\n const errno = (err as NodeJS.ErrnoException).code\n if (errno === 'ENXIO') {\n throw new OhmypiNotReady(`omp agent ${agentId}: no reader on rpc.in fifo (omp exited?)`)\n }\n throw err\n }\n try {\n const line = `${JSON.stringify(command)}\\n`\n writeSync(fd, line)\n } finally {\n closeSync(fd)\n }\n}\n\n// Tiny shell-arg quoter. Acceptable inputs are absolute paths under HOME.\nfunction shellQuote(s: string): string {\n if (/^[A-Za-z0-9._/-]+$/.test(s)) return s\n return `'${s.replace(/'/g, `'\\\\''`)}'`\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Tagged error for ohmypi-fifo Effect variants. */\nexport class OhmypiFifoError extends Data.TaggedError('OhmypiFifoError')<{\n readonly agentId: string\n readonly stage: 'create' | 'write' | 'destroy'\n readonly message: string\n readonly cause?: unknown\n}> {}\n\n/** Effect variant of `createOhmypiFifo`. */\nexport const createOhmypiFifo = (\n agentId: string,\n home?: string,\n): Effect.Effect<string, OhmypiFifoError> =>\n Effect.tryPromise({\n try: () => createOhmypiFifoPromise(agentId, home),\n catch: (cause) =>\n new OhmypiFifoError({\n agentId,\n stage: 'create',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n\n/** Effect variant of `writeOhmypiCommand`. */\nexport const writeOhmypiCommand = (\n agentId: string,\n command: unknown,\n home?: string,\n): Effect.Effect<void, OhmypiFifoError> =>\n Effect.try({\n try: () => writeOhmypiCommandSync(agentId, command, home),\n catch: (cause) =>\n new OhmypiFifoError({\n agentId,\n stage: 'write',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n\n/** Effect variant of `destroyOhmypiFifo`. */\nexport const destroyOhmypiFifo = (\n agentId: string,\n home?: string,\n): Effect.Effect<void, OhmypiFifoError> =>\n Effect.try({\n try: () => destroyOhmypiFifoSync(agentId, home),\n catch: (cause) =>\n new OhmypiFifoError({\n agentId,\n stage: 'destroy',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n","/**\n * Pi RPC named-pipe lifecycle (PAN-636).\n *\n * Pi runs in `--mode rpc` and reads JSONL commands from stdin. We feed Pi by\n * redirecting its stdin from a per-agent fifo at:\n *\n * $OVERDECK_HOME/agents/<agentId>/rpc.in\n *\n * The launcher script creates the fifo, then `exec pi --mode rpc ... < <fifo>`.\n * The runtime adapter (PiRuntime.sendMessage) writes JSONL lines to the fifo.\n *\n * Why a fifo and not stdin pipe + tmux paste-buffer:\n * - tmux paste-buffer is unreliable for JSONL (Enter timing, terminal\n * echo, line buffering). The fifo path bypasses the TTY entirely.\n * - Pi remains visible inside its tmux pane for crash isolation and\n * visual debugging — only its stdin is redirected.\n *\n * Ordering invariant (hazard H1):\n * - The launcher creates the fifo BEFORE spawning Pi.\n * - Pi's first stdout event is `session_start` which the extension turns\n * into ready.json under $OVERDECK_HOME/agents/<agentId>/.\n * - Adapter MUST wait for ready.json before opening the writer side of\n * the fifo. Opening for write before a reader exists would block\n * indefinitely (or, with O_NONBLOCK, fail fast with ENXIO). We use\n * readiness-by-marker as the explicit synchronization point.\n *\n * IMPORTANT: this module performs blocking I/O (mkfifo + file open) inside\n * async helpers. NEVER call it from a dashboard server route handler — only\n * from spawnAgent and killAgent paths that run on dedicated workers.\n */\n\nimport { existsSync, mkdirSync, openSync, writeSync, closeSync, unlinkSync, constants as fsConstants } from 'node:fs'\nimport { join } from 'node:path'\nimport { exec } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { Data, Effect } from 'effect'\nimport { getOverdeckHome } from '../paths.js'\n\nconst execAsync = promisify(exec)\n\nexport interface PiFifoPaths {\n agentDir: string\n readyPath: string\n fifoPath: string\n}\n\nexport class PiNotReady extends Error {\n readonly code = 'PI_NOT_READY' as const\n constructor(message: string) {\n super(message)\n this.name = 'PiNotReady'\n }\n}\n\nexport function piFifoPaths(agentId: string, home?: string): PiFifoPaths {\n const overdeckHome = home === undefined ? getOverdeckHome() : join(home, '.overdeck')\n const agentDir = join(overdeckHome, 'agents', agentId)\n return {\n agentDir,\n readyPath: join(agentDir, 'ready.json'),\n fifoPath: join(agentDir, 'rpc.in'),\n }\n}async function createPiFifoPromise(agentId: string, home?: string): Promise<string> {\n const paths = piFifoPaths(agentId, home)\n mkdirSync(paths.agentDir, { recursive: true, mode: 0o700 })\n if (existsSync(paths.fifoPath)) {\n unlinkSync(paths.fifoPath)\n }\n // Node has no native mkfifo. Shell out to /usr/bin/mkfifo. -m 600 sets the\n // mode atomically. We single-quote the path to defang any unexpected shell\n // metacharacters even though agent ids are alphanumeric + dash by contract.\n await execAsync(`mkfifo -m 600 ${shellQuote(paths.fifoPath)}`)\n return paths.fifoPath\n}\n\n/**\n * Unlink the fifo. Safe to call when the fifo does not exist.\n */\nexport function destroyPiFifoSync(agentId: string, home?: string): void {\n const paths = piFifoPaths(agentId, home)\n try {\n unlinkSync(paths.fifoPath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err\n }\n}\n\n/**\n * Write a single JSONL command to the agent's fifo.\n *\n * Throws PiNotReady (without blocking) when ready.json is not yet present —\n * the adapter is expected to wait for the Pi extension to write ready.json\n * on session_start before issuing RPC writes (hazard H1).\n *\n * Throws PiNotReady (also without blocking) when the fifo exists but Pi has\n * no read fd open — opening O_WRONLY|O_NONBLOCK on a reader-less fifo fails\n * with ENXIO. This is the typed signal that Pi died and the adapter should\n * recycle the agent rather than retry.\n *\n * The command is JSON-stringified and a single trailing newline is added so\n * Pi's JSONL parser sees one record per line.\n */\nexport function writePiCommandSync(agentId: string, command: unknown, home?: string): void {\n const paths = piFifoPaths(agentId, home)\n if (!existsSync(paths.readyPath)) {\n throw new PiNotReady(\n `Pi agent ${agentId}: ready.json not present yet (waiting for session_start)`,\n )\n }\n if (!existsSync(paths.fifoPath)) {\n throw new PiNotReady(`Pi agent ${agentId}: rpc.in fifo missing`)\n }\n\n let fd: number\n try {\n fd = openSync(paths.fifoPath, fsConstants.O_WRONLY | fsConstants.O_NONBLOCK)\n } catch (err) {\n const errno = (err as NodeJS.ErrnoException).code\n if (errno === 'ENXIO') {\n throw new PiNotReady(`Pi agent ${agentId}: no reader on rpc.in fifo (Pi exited?)`)\n }\n throw err\n }\n try {\n const line = `${JSON.stringify(command)}\\n`\n writeSync(fd, line)\n } finally {\n closeSync(fd)\n }\n}\n\n// Tiny shell-arg quoter. Acceptable inputs are absolute paths under HOME.\nfunction shellQuote(s: string): string {\n if (/^[A-Za-z0-9._/-]+$/.test(s)) return s\n return `'${s.replace(/'/g, `'\\\\''`)}'`\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n//\n// Additive Effect-channel variants of the fifo helpers above. Sync/promise\n// variants are preserved so the existing PiRuntime adapter keeps working.\n\n/** Tagged error for pi-fifo Effect variants. */\nexport class PiFifoError extends Data.TaggedError('PiFifoError')<{\n readonly agentId: string\n readonly stage: 'create' | 'write' | 'destroy'\n readonly message: string\n readonly cause?: unknown\n}> {}\n\n/** Effect variant of `createPiFifo`. */\nexport const createPiFifo = (\n agentId: string,\n home?: string,\n): Effect.Effect<string, PiFifoError> =>\n Effect.tryPromise({\n try: () => createPiFifoPromise(agentId, home),\n catch: (cause) =>\n new PiFifoError({\n agentId,\n stage: 'create',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n\n/**\n * Effect variant of `writePiCommand`. Lifts the sync FD ops via `Effect.try`.\n * The PiNotReady signal is preserved in the cause field so callers can branch.\n */\nexport const writePiCommand = (\n agentId: string,\n command: unknown,\n home?: string,\n): Effect.Effect<void, PiFifoError> =>\n Effect.try({\n try: () => writePiCommandSync(agentId, command, home),\n catch: (cause) =>\n new PiFifoError({\n agentId,\n stage: 'write',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n\n/** Effect variant of `destroyPiFifo`. */\nexport const destroyPiFifo = (\n agentId: string,\n home?: string,\n): Effect.Effect<void, PiFifoError> =>\n Effect.try({\n try: () => destroyPiFifoSync(agentId, home),\n catch: (cause) =>\n new PiFifoError({\n agentId,\n stage: 'destroy',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n","import { existsSync, mkdirSync, readFileSync, statfsSync, statSync, writeFileSync } from 'fs';\nimport { mkdir, rename as renameAsync, stat as statAsync, writeFile } from 'fs/promises';\nimport { exec } from 'child_process';\nimport { randomUUID } from 'crypto';\nimport { homedir } from 'os';\nimport { basename, dirname, join, resolve } from 'path';\nimport { promisify } from 'util';\nimport { parse as parseYaml } from 'yaml';\nimport { Effect } from 'effect';\nimport type { MemoryIdentity } from '@overdeck/contracts';\nimport { getClaudePermissionFlagsStringSync } from '../claude-permissions.js';\nimport { loadConfigSync as loadYamlConfig } from '../config-yaml.js';\nimport type { RoleEffort } from '../config-yaml.js';\nimport { ensureSessionContextBriefingFile } from '../briefing-freshness.js';\nimport { getClaudeAuthStatus } from '../claude-auth.js';\nimport { workspaceContextFile } from '../context-layers/layers.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport { initCodexHome } from '../runtimes/codex.js';\nimport { createOhmypiFifo, ohmypiFifoPaths, OhmypiNotReady, writeOhmypiCommandSync } from '../runtimes/ohmypi-fifo.js';\nimport { createPiFifo, piFifoPaths, PiNotReady, writePiCommandSync } from '../runtimes/pi-fifo.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { requireModelOverrideSync, shellQuoteModelIdSync } from '../model-validation.js';\nimport { getOpenAIAuthStatus } from '../openai-auth.js';\nimport { getOverdeckHome, packageRoot, resolveOhmypiExtensionPath, resolvePiExtensionPath } from '../paths.js';\nimport { getProviderForModelSync } from '../providers.js';\nimport type { AuthMode } from '../subscription-types.js';\nimport { capturePane, sessionExists } from '../tmux.js';\nimport { getAgentDir, getAgentStateSync, type Role } from './agent-state.js';\nimport { waitForReadySignal } from './identity.js';\nimport { CLI_PROXY_MODEL_ALIASES } from './provider-env.js';\n\nconst execAsync = promisify(exec);\nconst missingRoleDefinitionWarnings = new Set<string>();\n\n/**\n * Write an agent launcher script atomically. Every agent shares a fixed\n * `launcher.sh` path inside its agent dir, and spawn/resume/restart paths can\n * overlap (e.g. a Deacon recovery racing a manual restart). Writing in place\n * lets one path read a half-written script; write to a unique temp file then\n * rename (atomic on the same filesystem).\n */\nexport async function writeLauncherScriptAtomic(launcherScript: string, content: string): Promise<void> {\n const tmp = `${launcherScript}.${randomUUID()}.tmp`;\n await writeFile(tmp, content, { mode: 0o755 });\n await renameAsync(tmp, launcherScript);\n}\n\nexport async function claudeSystemPromptFiles(workspace: string, harness: RuntimeName | undefined): Promise<string[]> {\n const files: string[] = [];\n const contextFile = workspaceContextFile(workspace);\n try {\n await statAsync(contextFile);\n files.push(contextFile);\n } catch (error) {\n if (!isNodeNotFound(error)) throw error;\n }\n files.push(await ensureSessionContextBriefingFile());\n\n // PAN-1566: ohmypi also receives the rendered global context layer.\n const behavior = getHarnessBehavior(harness);\n if (behavior.contextLayerKind === 'pi') {\n const { piGlobalContextFile } = await import('../context-layers/index.js');\n const globalFile = piGlobalContextFile();\n if (existsSync(globalFile)) {\n files.unshift(globalFile);\n }\n }\n\n // PAN-1574: Codex receives its rendered global context layer (codex-global.md).\n if (behavior.contextLayerKind === 'codex') {\n const { codexGlobalContextFile } = await import('../context-layers/index.js');\n const globalFile = codexGlobalContextFile();\n if (existsSync(globalFile)) {\n files.unshift(globalFile);\n }\n }\n\n return files;\n}\n\nfunction isNodeNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n\n/**\n * BFS-walk a process subtree rooted at `rootPid` looking for the active agent\n * runtime. Returns true if any process in the tree matches the expected harness,\n * false if the tree exists but no match, false on any error.\n *\n * Used by sendAgentMessage zombie detection. pane_pid is the tmux pane's root\n * process, which is bash for work-agent launchers (`bash launcher.sh`) but can\n * be the runtime directly for specialists (`exec claude ...` / `exec pi ...`).\n */\nexport async function hasAgentRuntimeInSubtree(rootPid: string, harness: RuntimeName = 'claude-code'): Promise<boolean> {\n const expectedProcessNames = new Set(getHarnessBehavior(harness).processNames);\n const queue: string[] = [rootPid];\n const seen = new Set<string>();\n while (queue.length > 0) {\n const pid = queue.shift()!;\n if (seen.has(pid) || !/^\\d+$/.test(pid)) continue;\n seen.add(pid);\n\n try {\n const { stdout: comm } = await execAsync(`ps -p ${pid} -o comm=`);\n const name = comm.trim();\n if (expectedProcessNames.has(name)) return true;\n } catch {\n continue;\n }\n\n try {\n const { stdout: kids } = await execAsync(`pgrep -P ${pid}`);\n for (const kid of kids.trim().split('\\n').filter(Boolean)) {\n queue.push(kid);\n }\n } catch {\n // pgrep exits non-zero when there are no children — not an error.\n }\n }\n return false;\n}\n\nexport async function getPiLauncherFields(agentId: string, model: string): Promise<{\n harness: 'ohmypi';\n piExtensionPath: string;\n piFifoPath: string;\n piSessionDir: string;\n model: string;\n}> {\n const paths = piFifoPaths(agentId);\n await mkdir(paths.agentDir, { recursive: true, mode: 0o700 });\n const piExtensionPath = resolvePiExtensionPath();\n if (!piExtensionPath) {\n throw new Error(\n `Pi extension not built. Run: npm run build\\n(looked for dist/extensions/pi.js and packages/pi-extension/dist/index.js under ${packageRoot})`\n );\n }\n // PAN-1048 review feedback 006 (S1): thread the resolved role/workhorse model\n // through to buildPiCommand. The Pi launcher branch ignores baseCommand and\n // rebuilds from scratch starting with the literal `pi`, so the only way to\n // surface --model is via the launcher config's `model` field. Without this,\n // a Pi-backed role silently fell back to Pi's default model and ignored the\n // configured workhorse model entirely.\n return {\n harness: 'ohmypi',\n piExtensionPath,\n piFifoPath: await Effect.runPromise(createPiFifo(agentId)),\n piSessionDir: paths.agentDir,\n model,\n };\n}\n\nexport async function getOhmypiLauncherFields(agentId: string, model: string): Promise<{\n harness: 'ohmypi';\n piExtensionPath: string;\n piFifoPath: string;\n piSessionDir: string;\n model: string;\n}> {\n const paths = ohmypiFifoPaths(agentId);\n await mkdir(paths.agentDir, { recursive: true, mode: 0o700 });\n const ohmypiExtensionPath = resolveOhmypiExtensionPath();\n if (!ohmypiExtensionPath) {\n throw new Error(\n `ohmypi extension not built. Run: npm run build\\n(looked for dist/extensions/ohmypi.js and packages/ohmypi-extension/dist/index.js under ${packageRoot})`\n );\n }\n return {\n harness: 'ohmypi',\n piExtensionPath: ohmypiExtensionPath,\n piFifoPath: await Effect.runPromise(createOhmypiFifo(agentId)),\n piSessionDir: paths.agentDir,\n model,\n };\n}\n\nexport function getCodexLauncherFields(agentId: string, model: string, workspacePath?: string): {\n harness: 'codex';\n codexMode: 'work-tui';\n codexHome: string;\n codexSessionDir: string;\n model: string;\n} {\n const codexHome = join(homedir(), '.overdeck', 'agents', agentId, 'codex-home');\n // PAN-1803: codex work agents must inherit the user's configured codex\n // permission level (Settings → Permissions → Codex) and pre-trust the\n // workspace, EXACTLY like the conversation path\n // (routes/conversations.ts). Without trustedDir, codex shows its first-run\n // folder-trust / \"load project-local config?\" wizard and blocks the pane.\n // Without the permission mapping, work agents ignore the Settings choice\n // and run hardcoded never+workspace-write.\n const codexPermMode = loadYamlConfig().config.codex?.permissionMode ?? 'workspace';\n const approvalPolicy = codexPermMode === 'full-access' ? 'never' : 'on-request';\n const sandboxMode =\n codexPermMode === 'full-access' ? 'danger-full-access'\n : codexPermMode === 'read-only' ? 'read-only'\n : 'workspace-write';\n const approvalsReviewer = codexPermMode === 'auto-review' ? 'auto_review' : undefined;\n initCodexHome(codexHome, {\n trustedDir: workspacePath,\n approvalPolicy,\n sandboxMode,\n approvalsReviewer,\n });\n return {\n harness: 'codex',\n codexMode: 'work-tui',\n codexHome,\n codexSessionDir: join(codexHome, 'sessions'),\n model,\n };\n}\n\n/**\n * Wait for the Pi work-agent ready marker (`ready.json`) to appear.\n * Pi does not produce the Claude SessionStart hook signal, so resume/restart\n * paths must use this instead of `waitForReadySignal()`.\n */\nasync function waitForPiAgentReady(agentId: string, timeoutSec = 30): Promise<boolean> {\n const { readyPath } = piFifoPaths(agentId);\n const deadline = Date.now() + timeoutSec * 1000;\n while (Date.now() < deadline) {\n if (existsSync(readyPath)) return true;\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\nexport const OHMYPI_AGENT_READY_TIMEOUT_SECONDS = 120;\n\nasync function waitForOhmypiAgentReady(agentId: string, timeoutSec = OHMYPI_AGENT_READY_TIMEOUT_SECONDS): Promise<boolean> {\n const { readyPath } = ohmypiFifoPaths(agentId);\n const deadline = Date.now() + timeoutSec * 1000;\n while (Date.now() < deadline) {\n if (existsSync(readyPath)) return true;\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\n/**\n * PAN-2100: build a diagnostic suffix for an ohmypi readiness failure.\n *\n * An ENOSPC (disk-full) omp crash surfaces as an opaque \"did not become ready\"\n * timeout — omp cannot write `ready.json` when the disk is full. That exact\n * condition crashed flywheel-orchestrator (RUN-20) and the only record of it sat\n * unread in output.log for hours, sending the flywheel down a wrong \"kimi→ohmypi\n * misroute\" diagnosis. Surface the real signals at throw time: free disk on the\n * Overdeck-home filesystem and the tail of the agent's output.log. Best-effort:\n * every probe is independently guarded so diagnostics never mask the real error.\n */\nexport function describeOhmypiSpawnFailure(agentId: string): string {\n const parts: string[] = [];\n try {\n const { bavail, bsize } = statfsSync(getOverdeckHome());\n const freeMb = Math.round((bavail * bsize) / (1024 * 1024));\n parts.push(`freeDisk=${freeMb}MiB`);\n if (freeMb < 512) parts.push('(DISK NEARLY FULL — likely ENOSPC crash)');\n } catch { /* statfs best-effort */ }\n try {\n const logPath = join(getAgentDir(agentId), 'output.log');\n if (existsSync(logPath)) {\n const tail = readFileSync(logPath, 'utf8').slice(-1500).trim().split('\\n').slice(-8).join('\\n');\n if (tail) parts.push(`output.log tail:\\n${tail}`);\n }\n } catch { /* best-effort */ }\n return parts.length ? ` [${parts.join(' ')}]` : '';\n}\n\nasync function waitForCodexTuiReady(agentId: string, timeoutSec = 30): Promise<boolean> {\n const deadline = Date.now() + timeoutSec * 1000;\n while (Date.now() < deadline) {\n try {\n if (!(await Effect.runPromise(sessionExists(agentId)))) return false;\n const pane = await Effect.runPromise(capturePane(agentId, 80));\n // The codex TUI is ready when its input prompt (a line starting with the\n // `›` glyph) AND its status line (`<model> ... · <cwd>`) are both on\n // screen. PAN-1803: the previous check keyed off the first-run\n // trust-wizard markers (\"press enter to continue\") — but pre-trusting the\n // workspace (correctly) skips that wizard, so those markers never appear\n // and the kickoff never fired. Detect the actual ready prompt instead.\n const hasInputPrompt = /^\\s*[›>]\\s/m.test(pane);\n const hasStatusLine = /·\\s+[~/]/.test(pane);\n if (hasInputPrompt && hasStatusLine) {\n return true;\n }\n // Fallback: if pre-trust ever fails and the wizard does appear, treat its\n // markers as ready (the kickoff paste will dismiss + drive it).\n if (/press enter to continue/i.test(pane) || /ctrl[+-][cj]/i.test(pane)) {\n return true;\n }\n } catch {\n // The pane may not exist yet immediately after tmux session creation.\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\nexport async function waitForPromptReady(agentId: string, harness: RuntimeName | undefined, timeoutSec = 30): Promise<boolean> {\n if (getHarnessBehavior(harness).readinessKind === 'codex-tui-prompt') return waitForCodexTuiReady(agentId, timeoutSec);\n return waitForReadySignal(agentId, timeoutSec);\n}\n\nexport function inferMemoryProjectId(workspacePath: string): string {\n const workspaceName = basename(workspacePath);\n if (workspaceName.startsWith('feature-')) return basename(dirname(dirname(workspacePath)));\n return workspaceName;\n}\n\n/**\n * Inject prompt-time memory context into a Pi prompt (PAN-1546).\n * Mirrors Claude Code's UserPromptSubmit hook behaviour: every follow-up\n * prompt gets relevant memory surfaced via RAG.\n */\nasync function injectPiPromptTimeMemory(agentId: string, prompt: string): Promise<string> {\n if (!prompt.trim()) return prompt;\n\n const agentState = getAgentStateSync(agentId);\n if (!agentState || !agentState.workspace || !agentState.issueId) {\n return prompt;\n }\n\n try {\n const identity: MemoryIdentity = {\n projectId: inferMemoryProjectId(agentState.workspace),\n workspaceId: basename(agentState.workspace),\n issueId: agentState.issueId,\n runId: agentId,\n sessionId: agentId,\n agentRole: agentState.role ?? 'work',\n agentHarness: agentState.harness ?? 'claude-code',\n };\n const { injectPromptTimeMemory } = await import('../memory/injection.js');\n const result = await injectPromptTimeMemory({ prompt, identity, surface: 'user-prompt' });\n if (result.context) {\n return `${result.context}\\n\\n---\\n\\n${prompt}`;\n }\n } catch (error) {\n console.warn(`[agents] Prompt-time memory injection failed for ${agentId}:`, error instanceof Error ? error.message : String(error));\n }\n return prompt;\n}\n\n/**\n * Prompt-time memory injection for a Pi CONVERSATION follow-up (PAN-1546).\n *\n * The rpc/work-agent path (injectPiPromptTimeMemory) reads agent-state.json,\n * which conversations do not have — they deliver follow-ups via tmux\n * paste-buffer, not the FIFO. This builds the memory identity from the\n * conversation's cwd + issueId instead. Only issue-linked conversations have\n * memory to surface; everything else (and any failure) passes through\n * unchanged so a memory hiccup can never block a message.\n */\nexport async function injectPiConversationMemory(\n opts: { cwd: string; issueId?: string | null; conversationName: string },\n prompt: string,\n): Promise<string> {\n if (!prompt.trim() || !opts.issueId || !opts.cwd) return prompt;\n try {\n const identity: MemoryIdentity = {\n projectId: inferMemoryProjectId(opts.cwd),\n workspaceId: basename(opts.cwd),\n issueId: opts.issueId,\n runId: opts.conversationName,\n sessionId: opts.conversationName,\n agentRole: 'work',\n agentHarness: 'ohmypi',\n };\n const { injectPromptTimeMemory } = await import('../memory/injection.js');\n const result = await injectPromptTimeMemory({ prompt, identity, surface: 'user-prompt' });\n if (result.context) {\n return `${result.context}\\n\\n---\\n\\n${prompt}`;\n }\n } catch (error) {\n console.warn(`[agents] Conversation memory injection failed for ${opts.conversationName}:`, error instanceof Error ? error.message : String(error));\n }\n return prompt;\n}\n\n/**\n * Deliver a prompt to a Pi work agent through the FIFO JSONL command protocol.\n * Pi never reads tmux input — pasting prompts there is a no-op as far as the\n * model is concerned. Throws if Pi never reached readiness within the timeout.\n */\nexport async function writePiAgentPrompt(agentId: string, prompt: string, timeoutSec = 30): Promise<void> {\n const augmentedPrompt = await injectPiPromptTimeMemory(agentId, prompt);\n const ready = await waitForPiAgentReady(agentId, timeoutSec);\n if (!ready) {\n throw new Error(`Pi agent ${agentId} did not become ready within ${timeoutSec}s`);\n }\n try {\n // steer: Pi delivers immediately when idle and queues mid-turn; a bare\n // prompt is rejected with AgentBusyError while a run is active.\n writePiCommandSync(agentId, { id: randomUUID(), type: 'prompt', message: augmentedPrompt, streamingBehavior: 'steer' });\n } catch (err) {\n if (err instanceof PiNotReady) {\n throw new Error(`Pi agent ${agentId} reader gone before prompt could be delivered: ${err.message}`);\n }\n throw err;\n }\n}\n\nexport async function writeOhmypiAgentPrompt(agentId: string, prompt: string, timeoutSec = OHMYPI_AGENT_READY_TIMEOUT_SECONDS): Promise<void> {\n const augmentedPrompt = await injectPiPromptTimeMemory(agentId, prompt);\n const ready = await waitForOhmypiAgentReady(agentId, timeoutSec);\n if (!ready) {\n throw new Error(`ohmypi agent ${agentId} did not become ready within ${timeoutSec}s${describeOhmypiSpawnFailure(agentId)}`);\n }\n try {\n // steer: omp delivers immediately when idle and queues mid-turn; a bare\n // prompt is rejected with AgentBusyError while a run is active.\n writeOhmypiCommandSync(agentId, { id: randomUUID(), type: 'prompt', message: augmentedPrompt, streamingBehavior: 'steer' });\n } catch (err) {\n if (err instanceof OhmypiNotReady) {\n throw new Error(`ohmypi agent ${agentId} reader gone before prompt could be delivered: ${err.message}`);\n }\n throw err;\n }\n}\n\nexport async function getProviderAuthMode(model: string): Promise<AuthMode | undefined> {\n const provider = getProviderForModelSync(model);\n if (provider.name === 'anthropic') {\n const authStatus = await Effect.runPromise(getClaudeAuthStatus());\n if (authStatus.hasAnthropicApiKey) return 'api-key';\n return authStatus.loggedIn ? 'subscription' : undefined;\n }\n\n if (provider.name === 'openai') {\n const { config } = loadYamlConfig();\n const authStatus = await Effect.runPromise(getOpenAIAuthStatus());\n return authStatus.loggedIn\n ? 'subscription'\n : (config.providerAuth?.openai ?? 'api-key');\n }\n\n if (provider.name === 'google') {\n const { config } = loadYamlConfig();\n return config.providerAuth?.google;\n }\n\n return undefined;\n}\n\n/**\n * Build the base command that the launcher will exec for an agent.\n *\n * The `harness` parameter (PAN-636) selects between Claude Code (default)\n * and ohmypi/Pi. When the harness uses the ohmypi RPC command, the function\n * short-circuits to a\n * `omp --mode rpc --model <model>` line; the launcher generator then layers\n * --session-dir, --extension, --no-context-files, and the stdin-from-fifo\n * redirect on top via generateLauncherScript. The `agentName` (PAN-982:\n * --name) and `agentDefinition` (PAN-982: --agent) parameters only apply to the\n * Claude Code path — ohmypi has no agent-definition system.\n */\nexport async function getAgentRuntimeBaseCommand(\n model: string,\n agentName?: string,\n agentDefinition?: string,\n harness: RuntimeName = 'claude-code',\n effort?: RoleEffort,\n): Promise<string> {\n const validatedModel = requireModelOverrideSync(model);\n const quotedModel = shellQuoteModelIdSync(validatedModel);\n const behavior = getHarnessBehavior(harness);\n if (behavior.launchCommandKind === 'ohmypi-rpc') {\n return `omp --mode rpc --model ${quotedModel}`;\n }\n if (behavior.launchCommandKind === 'codex-work-tui') {\n // buildCodexCommand in launcher-generator builds the full Codex command;\n // return a stub base command so the launcher generator can short-circuit.\n return `codex`;\n }\n\n // Integration tests can inject a harmless harness command so a leaked or\n // intentionally-real tmux session never runs the production `claude` binary.\n if (process.env.OVERDECK_TEST_HARNESS_COMMAND) {\n return process.env.OVERDECK_TEST_HARNESS_COMMAND;\n }\n\n const provider = getProviderForModelSync(validatedModel);\n // PAN-982: --name <agentId> creates a human-readable Claude session name discoverable via\n // `claude --resume`.\n const nameFlag = agentName ? ` --name ${agentName}` : '';\n\n // Classify agentDefinition. A registered agent NAME (e.g. pan-review-agent,\n // resolved from ~/.claude/agents/*.md) still launches via `--agent <name>` —\n // Claude Code 2.1.195 resolves names natively, and its frontmatter owns\n // permissionMode/tools/effort. A role definition FILE (roles/<role>.md) can no\n // longer be passed to --agent (PAN-2087: 2.1.195 dropped --agent file support),\n // so its frontmatter-stripped body is injected as an appended system prompt.\n const defIsRoleFile = !!agentDefinition\n && existsSync(resolve(agentDefinition))\n && statSync(resolve(agentDefinition)).isFile();\n\n if (agentDefinition && !defIsRoleFile) {\n // Registered agent NAME — unchanged. --model still wins over any frontmatter\n // default model; permissionMode/effort come from the named agent's frontmatter.\n const agentFlag = ` --agent ${agentDefinition}`;\n const effortFlag = effort ? ` --effort ${effort}` : '';\n if (provider.name === 'openai' && (await getProviderAuthMode(validatedModel)) === 'subscription') {\n const resolvedModel = CLI_PROXY_MODEL_ALIASES[validatedModel] ?? validatedModel;\n return `claude${agentFlag} --model ${shellQuoteModelIdSync(resolvedModel)}${effortFlag}${nameFlag}`;\n }\n return `claude${agentFlag} --model ${quotedModel}${effortFlag}${nameFlag}`;\n }\n\n // PAN-2087: role FILE (inject body + effort) or no definition. permissionMode\n // comes from the global permission flags; the role's hooks fire globally via\n // ~/.claude/settings.json. roleInject folds in effort when a role file is\n // present; otherwise --effort is passed directly.\n const permissionFlags = getClaudePermissionFlagsStringSync();\n const roleInject = defIsRoleFile ? roleSystemPromptInjectionSync(agentDefinition as string, effort) : '';\n const effortFlag = (!defIsRoleFile && effort) ? ` --effort ${effort}` : '';\n\n // OpenAI subscription → local CLIProxyAPI sidecar exposes an\n // Anthropic-compatible /v1/messages endpoint, so Claude Code can drive\n // gpt-* models directly via ANTHROPIC_BASE_URL (no wrapper process).\n // The provider env vars are injected separately by getProviderEnvForModel.\n if (provider.name === 'openai' && (await getProviderAuthMode(validatedModel)) === 'subscription') {\n // CLIProxy supports gpt-5.x but not the -pro variant; map aliases to real names.\n const resolvedModel = CLI_PROXY_MODEL_ALIASES[validatedModel] ?? validatedModel;\n return `claude ${permissionFlags}${roleInject} --model ${shellQuoteModelIdSync(resolvedModel)}${effortFlag}${nameFlag}`;\n }\n\n return `claude ${permissionFlags}${roleInject} --model ${quotedModel}${effortFlag}${nameFlag}`;\n}\n\n/**\n * Resolve the role's Claude-harness agent-definition path.\n *\n * Returns the file Claude Code's `--agent` flag should load to seed the run\n * with the role's frontmatter (permissions, tools, hooks, default model).\n * Returns `null` when the role does not have a Claude agent definition — for\n * example, the review convoy sub-roles, whose prompts are harness-agnostic\n * templates the orchestrator inlines into the spawn message (see\n * `buildConvoyPrompt` in `src/lib/cloister/review-agent.ts`). Sub-role\n * templates live in `roles/review-<subRole>.md`; they are deliberately not\n * loaded via `--agent` so the same content drives Claude Code, Pi, and\n * future harnesses uniformly and never auto-discovers as an ambient Claude\n * subagent inside a work agent's session.\n *\n * Without a sub-role the return is always the top-level role file; callers\n * can rely on the overload to avoid null-handling on that path.\n */\nexport function roleAgentDefinitionPath(role: Role): string;\nexport function roleAgentDefinitionPath(role: Role, subRole: string | undefined): string | null;\nexport function roleAgentDefinitionPath(role: Role, subRole?: string): string | null {\n if (role === 'review' && subRole) {\n return null;\n }\n return join(packageRoot, 'roles', `${role}.md`);\n}\n\n/**\n * PAN-2087: Claude Code 2.1.195 removed support for passing an agent DEFINITION\n * FILE to `--agent` — it now resolves only registered agent NAMES (from\n * `~/.claude/agents/*.md`). Role definitions live as files (`roles/<role>.md`),\n * so `claude --agent roles/<role>.md` started failing with \"agent not found\" and\n * the agent exited on launch. Instead of `--agent`, inject the role's BODY as an\n * appended system prompt and translate its `effort:` frontmatter to `--effort`.\n *\n * Everything the role frontmatter used to supply via `--agent` is reconstituted\n * as flags:\n * - body → `--append-system-prompt-file` (frontmatter stripped)\n * - `effort:` → `--effort`\n * - `mcpServers:` → a generated `--mcp-config` JSON (PAN-2090)\n * - `tools:` → `--allowedTools` (PAN-2090), with each declared MCP server\n * appended as `mcp__<name>` so the role can still call its own MCP tools —\n * otherwise the strict allow-list would block e.g. playwright for `test`.\n *\n * The role frontmatter's hooks are deliberately NOT reconstituted: they are\n * installed globally in `~/.claude/settings.json` and fire for every claude\n * session, and per the PAN-1402 note Claude Code never honored path-form\n * `--agent` frontmatter hooks anyway. permissionMode is covered by the\n * launcher's permission flags. Generated artifacts are cached under\n * `~/.overdeck/role-prompts/` and referenced by absolute path, so they do not\n * depend on `roles/` existing in the agent's cwd.\n *\n * Returns the flags (with a leading space) to splice in place of the old\n * `--agent <file>` flag, or '' when the definition file is missing.\n */\nexport function roleSystemPromptInjectionSync(definitionPath: string, explicitEffort?: RoleEffort): string {\n const abs = resolve(definitionPath);\n if (!existsSync(abs)) return '';\n const raw = readFileSync(abs, 'utf8');\n let body = raw;\n let frontmatter: Record<string, unknown> = {};\n const fm = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/);\n if (fm) {\n body = raw.slice(fm[0].length);\n try {\n const parsed = parseYaml(fm[1]);\n if (parsed && typeof parsed === 'object') frontmatter = parsed as Record<string, unknown>;\n } catch {\n // Malformed frontmatter — fall back to body-only injection.\n }\n }\n\n const dir = join(getOverdeckHome(), 'role-prompts');\n mkdirSync(dir, { recursive: true });\n const stem = basename(definitionPath).replace(/\\.md$/, '');\n const outPath = join(dir, `${stem}.md`);\n writeFileSync(outPath, body);\n\n const flags: string[] = [` --append-system-prompt-file '${outPath}'`];\n\n const effort = explicitEffort ?? (typeof frontmatter.effort === 'string' ? (frontmatter.effort as RoleEffort) : undefined);\n if (effort) flags.push(` --effort ${effort}`);\n\n // mcpServers: a YAML list of single-key maps ({ playwright: { … } }). Flatten\n // into one { mcpServers: { name: def } } config loaded via --mcp-config. It is\n // additive (the launcher's channels --mcp-config still applies; no\n // --strict-mcp-config), so the role keeps any project/global MCP servers too.\n const mcpNames: string[] = [];\n if (Array.isArray(frontmatter.mcpServers) && frontmatter.mcpServers.length > 0) {\n const servers: Record<string, unknown> = {};\n for (const entry of frontmatter.mcpServers) {\n if (entry && typeof entry === 'object') {\n for (const [name, def] of Object.entries(entry as Record<string, unknown>)) {\n servers[name] = def;\n mcpNames.push(name);\n }\n }\n }\n if (Object.keys(servers).length > 0) {\n const mcpPath = join(dir, `${stem}.mcp.json`);\n writeFileSync(mcpPath, JSON.stringify({ mcpServers: servers }, null, 2));\n flags.push(` --mcp-config '${mcpPath}'`);\n }\n }\n\n // tools: allow-list → --allowedTools (comma-joined single arg so the variadic\n // never swallows later flags). Append `mcp__<server>` for each declared MCP\n // server so its tools survive the strict allow-list.\n if (Array.isArray(frontmatter.tools)) {\n const toolNames = frontmatter.tools.filter((t): t is string => typeof t === 'string');\n if (toolNames.length > 0) {\n const allowed = [...toolNames, ...mcpNames.map((n) => `mcp__${n}`)];\n flags.push(` --allowedTools '${allowed.join(',')}'`);\n }\n }\n\n return flags.join('');\n}\n\n/** Build a Claude/ohmypi base command for role-based runs. */\nexport async function getRoleRuntimeBaseCommand(\n model: string,\n agentName: string,\n role: Role,\n harness: RuntimeName = 'claude-code',\n subRole?: string,\n effort?: RoleEffort,\n): Promise<string> {\n const validatedModel = requireModelOverrideSync(model);\n const quotedModel = shellQuoteModelIdSync(validatedModel);\n const behavior = getHarnessBehavior(harness);\n if (behavior.launchCommandKind === 'ohmypi-rpc') {\n return `omp --mode rpc --model ${quotedModel}`;\n }\n if (behavior.launchCommandKind === 'codex-work-tui') {\n return `codex`;\n }\n\n // Integration tests can inject a harmless harness command so a leaked or\n // intentionally-real tmux session never runs the production `claude` binary.\n if (process.env.OVERDECK_TEST_HARNESS_COMMAND) {\n return process.env.OVERDECK_TEST_HARNESS_COMMAND;\n }\n\n const provider = getProviderForModelSync(validatedModel);\n const requestedDefinitionPath = roleAgentDefinitionPath(role, subRole);\n const definitionPath = requestedDefinitionPath && existsSync(resolve(requestedDefinitionPath))\n ? requestedDefinitionPath\n : null;\n if (requestedDefinitionPath && !definitionPath && !missingRoleDefinitionWarnings.has(requestedDefinitionPath)) {\n missingRoleDefinitionWarnings.add(requestedDefinitionPath);\n console.warn(\n `[agents] Role definition ${resolve(requestedDefinitionPath)} does not exist; launching ${role} without its role system prompt`,\n );\n }\n // PAN-2087: inject the role body (+ effort frontmatter) as an appended system\n // prompt instead of `--agent <file>` (Claude Code dropped --agent file support).\n const roleInject = definitionPath ? roleSystemPromptInjectionSync(definitionPath, effort) : '';\n const nameFlag = ` --name ${agentName}`;\n const effortFlag = (!definitionPath && effort) ? ` --effort ${effort}` : '';\n // permissionMode now comes from the global permission flags for EVERY role\n // (the old --agent path relied on role frontmatter, which Claude Code no longer\n // applies). This honors the user's bypass/auto setting uniformly.\n const permissionFlags = ` ${getClaudePermissionFlagsStringSync()}`;\n\n // PAN-1557: convoy sub-reviewers now run as interactive, attachable sessions\n // (prompt delivered via tmux, completion signalled by the Stop-hook) instead\n // of headless `claude --print`. No role uses --print anymore.\n const printFlag = '';\n\n if (provider.name === 'openai' && (await getProviderAuthMode(validatedModel)) === 'subscription') {\n const resolvedModel = CLI_PROXY_MODEL_ALIASES[validatedModel] ?? validatedModel;\n return `claude${printFlag}${roleInject}${permissionFlags} --model ${shellQuoteModelIdSync(resolvedModel)}${effortFlag}${nameFlag}`;\n }\n\n return `claude${printFlag}${roleInject}${permissionFlags} --model ${quotedModel}${effortFlag}${nameFlag}`;\n}\n","import type { RuntimeName } from '../runtimes/types.js';\nimport type { VBriefDifficulty, VBriefItem, VBriefItemKind } from '../vbrief/types.js';\nimport type { TierDefinition } from './tier-table.js';\n\n/**\n * Model + harness resolution chain for tiered execution (PAN-1791, folding in\n * PAN-1196). Resolution order for each bead:\n *\n * 1. per-bead `metadata.model` override — wins for the model; the harness\n * (and dispatch lane) still comes from the rest of the chain\n * 2. `byKind[item.metadata.kind]` — subject-matter routing to a tier name\n * 3. `difficultyToTier[item.metadata.difficulty]` — the tier table\n * 4. `roleDefault` — the configured role default\n *\n * When nothing resolves and no default is configured, resolveTier throws a\n * named ResolveTierError. It NEVER falls back to a hardcoded model literal\n * (no-hardcoded-model-fallbacks rule).\n *\n * Pure function over already-validated config — no spawning, no I/O here.\n */\n\n/**\n * Reserved tierName values naming the resolution source when the result did\n * not come from a configured tier. Configured tier names must not use these.\n */\nexport const OVERRIDE_TIER_NAME = 'override';\nexport const ROLE_DEFAULT_TIER_NAME = 'role-default';\n\nexport interface ResolvedTier {\n tierName: string;\n model: string;\n harness: RuntimeName;\n}\n\nexport interface RoleDefaultTier {\n model: string;\n harness: RuntimeName;\n}\n\n/**\n * Structural subset of ValidatedTieredExecutionConfig (tiers +\n * difficultyToTier) plus the chain-specific inputs. Callers can spread a\n * validated tier-table config and attach byKind/roleDefault.\n */\nexport interface ResolveTierConfig {\n tiers: Record<string, TierDefinition>;\n difficultyToTier: Partial<Record<VBriefDifficulty, string>>;\n /** Subject-matter routing: item kind -> configured tier name. */\n byKind?: Partial<Record<VBriefItemKind, string>>;\n roleDefault?: RoleDefaultTier;\n}\n\nexport class ResolveTierError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResolveTierError';\n }\n}\n\nfunction itemLabel(item: Pick<VBriefItem, 'id' | 'title'>): string {\n return item.id || item.title || '<unknown item>';\n}\n\nfunction lookupTier(\n config: ResolveTierConfig,\n tierName: string,\n source: string,\n item: Pick<VBriefItem, 'id' | 'title'>,\n): ResolvedTier {\n const tier = config.tiers[tierName];\n if (!tier) {\n throw new ResolveTierError(\n `${source} routes item '${itemLabel(item)}' to tier '${tierName}', but no such tier is configured`,\n );\n }\n return { tierName, model: tier.model, harness: tier.harness };\n}\n\n/** The chain below the per-bead override: byKind -> byDifficulty -> role default. */\nfunction resolveBaseTier(\n item: Pick<VBriefItem, 'id' | 'title' | 'metadata'>,\n config: ResolveTierConfig,\n): ResolvedTier | undefined {\n const kind = item.metadata?.kind;\n const kindTierName = kind ? config.byKind?.[kind] : undefined;\n if (kindTierName) {\n return lookupTier(config, kindTierName, `byKind['${kind}']`, item);\n }\n\n const difficulty = item.metadata?.difficulty;\n const difficultyTierName = difficulty ? config.difficultyToTier[difficulty] : undefined;\n if (difficultyTierName) {\n return lookupTier(config, difficultyTierName, `difficultyToTier['${difficulty}']`, item);\n }\n\n if (config.roleDefault) {\n return {\n tierName: ROLE_DEFAULT_TIER_NAME,\n model: config.roleDefault.model,\n harness: config.roleDefault.harness,\n };\n }\n\n return undefined;\n}\n\nexport function resolveTier(\n item: Pick<VBriefItem, 'id' | 'title' | 'metadata'>,\n config: ResolveTierConfig,\n): ResolvedTier {\n const base = resolveBaseTier(item, config);\n\n const override = item.metadata?.model;\n if (typeof override === 'string' && override.length > 0) {\n if (!base) {\n throw new ResolveTierError(\n `item '${itemLabel(item)}' sets metadata.model '${override}' but no tier resolves its harness: `\n + 'no byKind match, no byDifficulty tier, and no role default configured',\n );\n }\n return { tierName: OVERRIDE_TIER_NAME, model: override, harness: base.harness };\n }\n\n if (!base) {\n throw new ResolveTierError(\n `no tier/model configured for item '${itemLabel(item)}': no metadata.model override, `\n + 'no byKind match, no byDifficulty tier, and no role default configured',\n );\n }\n\n return base;\n}\n","/**\n * PAN-2397 W1 — Always Tiered: the single staffing resolver.\n *\n * Every bead dispatch resolves WHO runs it through this one function. When an\n * explicit tier table is enabled (globally or via the plan's per-issue\n * override), staffing comes from the tier resolution chain. Otherwise staffing\n * comes from the IMPLICIT tier table: a degenerate one-tier world whose\n * `default` tier is exactly today's roles.work resolution (same resolveModel\n * call, same spawnKey, so Role-Models work distributions keep their\n * deterministic per-spawn behavior). \"Non-tiered execution\" is therefore not\n * a separate code path — it is this degenerate configuration.\n *\n * Fallthrough rule (D-explicit-gap): when the explicit table cannot place a\n * bead (no difficulty, no by_kind match, no per-bead override — the\n * ResolveTierError case), staffing falls through to the implicit tier. That\n * reproduces the historical behavior where such beads ran on the role-default\n * model, without callers special-casing it.\n */\n\nimport type { RuntimeName } from '../runtimes/types.js';\nimport type { VBriefItem } from '../vbrief/types.js';\nimport { loadConfigSync as loadYamlConfig } from '../config-yaml.js';\nimport type { NormalizedConfig } from '../config-yaml/schema.js';\nimport { resolveModel } from '../config-yaml/roles.js';\nimport { requireModelOverrideSync } from '../model-validation.js';\nimport { getBuiltInDefaultHarness, getProviderForModelSync } from '../providers.js';\nimport { fmix32, fnv1a32 } from '../config-yaml/percent.js';\nimport { resolveTier } from './resolve-tier.js';\nimport { resolveTieredExecutionEnabled, type TierDistributionEntry } from './tier-table.js';\n\nexport const IMPLICIT_TIER_NAME = 'default';\n\nexport interface Staffing {\n tierName: string;\n model: string;\n harness: RuntimeName;\n /** true when staffing came from the implicit roles.work-derived tier. */\n implicit: boolean;\n}\n\nexport interface ResolveStaffingOptions {\n /** plan.metadata — carries the per-issue tiered_execution override. */\n planMetadata?: { [key: string]: unknown };\n /**\n * Deterministic key for Role-Models percent distributions in the implicit\n * tier (spawn.ts uses `${role}:${issueId}`). Required for byte-identical\n * behavior with the historical determineModel path.\n */\n spawnKey?: string;\n /** Injectable config for tests; defaults to the loaded config. */\n config?: Pick<NormalizedConfig, 'roles' | 'workhorses' | 'tieredExecution' | 'providerHarnesses'>;\n}\n\n/** Provider-default harness for a model (PAN-1984: harness is derived from the\n * model's provider — per-provider setting else built-in default). */\nexport function providerDefaultHarnessSync(\n model: string,\n config: Pick<NormalizedConfig, 'providerHarnesses'>,\n): RuntimeName {\n const provider = getProviderForModelSync(model).name;\n return config.providerHarnesses?.[provider] ?? getBuiltInDefaultHarness(provider);\n}\n\n/** The implicit tier: roles.work resolution as a Staffing. Fails loudly when\n * the work role is unresolvable — never a hardcoded fallback. */\nexport function resolveImplicitStaffing(\n config: Pick<NormalizedConfig, 'roles' | 'workhorses' | 'providerHarnesses'>,\n spawnKey?: string,\n): Staffing {\n const model = requireModelOverrideSync(resolveModel('work', undefined, config, spawnKey));\n return {\n tierName: IMPLICIT_TIER_NAME,\n model,\n harness: providerDefaultHarnessSync(model, config),\n implicit: true,\n };\n}\n\n/**\n * THE staffing resolver (FR-1). Explicit tier table when enabled for the\n * issue; implicit roles.work tier otherwise — and as the fallthrough when the\n * explicit table cannot place the bead.\n */\nexport function resolveStaffing(\n item: Pick<VBriefItem, 'id' | 'title' | 'metadata'>,\n options: ResolveStaffingOptions = {},\n): Staffing {\n const config = options.config ?? loadYamlConfig().config;\n const tiered = config.tieredExecution;\n\n if (tiered && resolveTieredExecutionEnabled(tiered, options.planMetadata)) {\n try {\n const tier = resolveTier(item, tiered);\n // PAN-2391: a distribution tier spreads beads across weighted\n // model+harness entries. Selection is deterministic per bead so\n // replay/re-resolution always lands on the same entry. A per-bead\n // metadata.model override never reaches here with the tier's name\n // (resolveTier returns the override pseudo-tier), so overrides win.\n const distribution = tiered.tiers?.[tier.tierName]?.distribution;\n if (distribution && distribution.length > 0) {\n const entry = pickDistributionEntry(distribution, `${options.spawnKey ?? ''}:${item.id}`);\n return { tierName: tier.tierName, model: entry.model, harness: entry.harness, implicit: false };\n }\n return { tierName: tier.tierName, model: tier.model, harness: tier.harness, implicit: false };\n } catch {\n // Explicit table cannot place this bead — fall through to the implicit\n // tier (historical role-default behavior, now uniform).\n }\n }\n\n return resolveImplicitStaffing(config, options.spawnKey);\n}\n\n/** Deterministic weighted pick (D6): FNV-1a of the selection key → bucket in\n * [0, 100). Same key always selects the same entry. */\nexport function pickDistributionEntry(\n entries: readonly TierDistributionEntry[],\n selectionKey: string,\n): TierDistributionEntry {\n const total = entries.reduce((sum, entry) => sum + entry.weight, 0);\n const bucket = fmix32(fnv1a32(selectionKey)) % total;\n let cursor = 0;\n for (const entry of entries) {\n cursor += entry.weight;\n if (bucket < cursor) return entry;\n }\n return entries[entries.length - 1]!;\n}\n","import { existsSync } from 'fs';\nimport { basename, join } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getClaudePermissionFlagsStringSync } from '../claude-permissions.js';\nimport { loadConfigSync } from '../config.js';\nimport { loadConfigSync as loadYamlConfig } from '../config-yaml.js';\nimport type { RoleEffort } from '../config-yaml.js';\nimport { getFlywheelActiveRunIdSync } from '../overdeck/control-settings.js';\nimport { createTrackerFromConfig, createTracker } from '../tracker/factory.js';\nimport type { IssueState } from '../tracker/interface.js';\nimport { findProjectByPathSync, getIssuePrefix, resolveProjectFromIssueSync } from '../projects.js';\nimport { getWorkspaceStackHealth } from '../workspace/stack-health.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { getProviderForModelSync, setupCredentialFileAuthSync, clearCredentialFileAuthSync } from '../providers.js';\nimport type { ModelId, ComplexityLevel } from '../settings.js';\nimport { requireModelOverrideSync } from '../model-validation.js';\nimport type { MemoryIdentity } from '@overdeck/contracts';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { readWorkspacePlanSync } from '../vbrief/io.js';\nimport { getDispatchableItems } from '../vbrief/dag.js';\nimport { type Role } from './agent-state.js';\nimport type { TierAssignment } from './dispatch-tier.js';\nimport { resolveStaffing } from './staffing.js';\nimport { resolveTieredExecutionEnabled, resolveTieredExecutionEnabledForIssue } from './tier-table.js';\nimport {\n buildCavemanExports,\n getProviderEnvForModel,\n getProviderExportsForModel,\n} from './provider-env.js';\nimport {\n claudeSystemPromptFiles,\n getAgentRuntimeBaseCommand,\n getCodexLauncherFields,\n getOhmypiLauncherFields,\n inferMemoryProjectId,\n roleAgentDefinitionPath,\n roleSystemPromptInjectionSync,\n} from './runtime-command.js';\n\nexport type FlywheelSpawnEnv = {\n OVERDECK_FLYWHEEL_RUN_ID?: string;\n OVERDECK_FLYWHEEL_AGENT_ROLE?: Role;\n};\n\nexport function normalizeFlywheelRunId(runId: string | null | undefined): string | undefined {\n if (!runId) return undefined;\n const trimmed = runId.trim();\n return /^RUN-\\d+$/.test(trimmed) ? trimmed : undefined;\n}\n\nexport function resolveFlywheelSpawnEnv(role: Role, runIdOverride?: string | null): FlywheelSpawnEnv {\n const runId = normalizeFlywheelRunId(runIdOverride ?? getFlywheelActiveRunIdSync());\n return runId\n ? { OVERDECK_FLYWHEEL_RUN_ID: runId, OVERDECK_FLYWHEEL_AGENT_ROLE: role }\n : {};\n}\n\nexport function flywheelEnvExports(env: FlywheelSpawnEnv): string[] {\n return [\n env.OVERDECK_FLYWHEEL_RUN_ID ? `export OVERDECK_FLYWHEEL_RUN_ID=${env.OVERDECK_FLYWHEEL_RUN_ID}` : undefined,\n env.OVERDECK_FLYWHEEL_AGENT_ROLE ? `export OVERDECK_FLYWHEEL_AGENT_ROLE=${env.OVERDECK_FLYWHEEL_AGENT_ROLE}` : undefined,\n ].filter((value): value is string => value !== undefined);\n}\n\nexport interface SpawnOptions {\n issueId: string;\n workspace: string;\n /** Coding-agent harness (PAN-636). Defaults to 'claude-code' when omitted. */\n harness?: RuntimeName;\n model?: string;\n prompt?: string;\n /**\n * Spawn role. Defaults to 'work'. The 'strike' role is the bypass path that\n * skips plan/review/test/ship and lands directly on main — see roles/strike.md.\n * Strike sessions are named `strike-<issue-id>` instead of `agent-<issue-id>`.\n */\n role?: 'work' | 'strike' | 'knowledge';\n difficulty?: ComplexityLevel;\n agentType?: 'review-agent' | 'test-agent' | 'merge-agent' | 'work-agent';\n\n // Work type system (PAN-118)\n phase?: 'exploration' | 'implementation' | 'testing' | 'documentation' | 'review-response' | 'planning' | 'synthesis';\n workType?: string; // Explicit work type ID (overrides phase-based detection)\n\n /**\n * Optional registered slot agent id. Omitted for the default one-work-agent\n * path; set only by spawnRun when launching a per-item slot.\n */\n agentId?: string;\n /** Registered swarm slot index for per-item work-agent spawning. */\n slotIndex?: number;\n /** vBRIEF item id assigned to the registered swarm slot. */\n slotItemId?: string;\n allowHost?: boolean;\n flywheelRunId?: string;\n /** Claude Code `--effort` level for the spawned session (work/strike). */\n effort?: RoleEffort;\n}\n\nexport interface SpawnRunOptions {\n workspace?: string;\n harness?: RuntimeName;\n model?: string;\n prompt?: string;\n agentId?: string;\n /**\n * Sub-role within the review convoy (PAN-1059).\n * When set alongside role='review', each convoy reviewer gets its own\n * isolated tmux session using the code-review-<subRole> agent definition.\n * Values: 'security' | 'correctness' | 'performance' | 'requirements'\n */\n subRole?: string;\n /**\n * Review convoy wiring (PAN-977). When spawning a review sub-role, the\n * synthesis agent id and the reviewer's output path are passed in up front\n * so the generated launcher can own the REVIEWER_READY/FAILED/TIMEOUT signal\n * deterministically on process exit. Persisted onto AgentState too.\n */\n reviewSynthesisAgentId?: string;\n reviewOutputPath?: string;\n allowHost?: boolean;\n registerConversation?: boolean;\n effort?: RoleEffort;\n extraEnvExports?: string[];\n resumeSessionId?: string;\n flywheelRunId?: string;\n /** 1-based registered slot index for per-item work-agent spawning. */\n slotIndex?: number;\n /** vBRIEF item id assigned to this registered slot. Required with slotIndex. */\n slotItemId?: string;\n /** Optional per-spawn cap for registered work-agent slots. Defaults to the work-agent governor cap. */\n maxRegisteredSlots?: number;\n}\n\nexport interface RegisteredSlotSpawn {\n agentId: string;\n branch: string;\n workspace: string;\n slotIndex: number;\n slotItemId: string;\n}\n\n/**\n * Thread a tiered-execution tier assignment into spawn options (PAN-1791).\n * When the assignment resolved a tier, its model+harness replace the parent\n * defaults so the dispatched bead runs on the tier its difficulty selected.\n * With no assignment (tiering disabled), the options pass through unchanged.\n */\nexport function applyTierAssignment<T extends Pick<SpawnRunOptions, 'model' | 'harness'>>(\n options: T,\n assignment?: TierAssignment,\n): T {\n if (!assignment?.model) return options;\n return {\n ...options,\n model: assignment.model,\n harness: assignment.harness ?? options.harness,\n };\n}\n\nexport function resolveRegisteredSlotSpawn(\n issueId: string,\n baseWorkspace: string,\n options: Pick<SpawnRunOptions, 'slotIndex' | 'slotItemId'>,\n): RegisteredSlotSpawn | null {\n const { slotIndex, slotItemId: slotItemIdRaw } = options;\n if (slotIndex === undefined && slotItemIdRaw === undefined) return null;\n if (slotIndex === undefined || slotItemIdRaw === undefined) {\n throw new Error('Registered slot spawn requires both slotIndex and slotItemId.');\n }\n if (!Number.isInteger(slotIndex) || slotIndex < 1) {\n throw new Error(`Registered slot index must be a positive integer; got ${slotIndex}.`);\n }\n const slotItemId = slotItemIdRaw.trim();\n if (!slotItemId) {\n throw new Error('Registered slot spawn requires a non-empty slotItemId.');\n }\n\n const issueLower = issueId.toLowerCase();\n return {\n agentId: `agent-${issueLower}-slot-${slotIndex}`,\n branch: `feature/${issueLower}-slot-${slotIndex}`,\n workspace: `${baseWorkspace}-slot-${slotIndex}`,\n slotIndex,\n slotItemId,\n };\n}\n\nexport interface SlotTierSpawnParams {\n model?: string;\n harness?: RuntimeName;\n tierName?: string;\n /** PAN-2397: true when staffing came from the implicit roles.work tier.\n * Implicit staffing intentionally omits `harness` so the spawn keeps its\n * historical harness handling (provider-default derived from the model). */\n implicit?: boolean;\n}\n\n/**\n * Tiered-execution model resolution for a registered slot spawn (PAN-1791,\n * fixing PAN-1196's \"difficulty captured and ignored\"). When tiered execution\n * is enabled for the plan, resolve the slot item's tier through\n * assignDispatchTier and return its (model, harness) as spawn params so the\n * dispatched bead runs on the tier its difficulty selected.\n *\n * Returns {} — leaving the existing model resolution untouched — when:\n * - tiered execution is disabled (globally and per-plan), or\n * - an explicit per-spawn model override was passed (same precedence as\n * determineModel: an explicit override outranks routing), or\n * - the item carries no difficulty and no per-bead model override, in which\n * case the chain's role-default step IS the existing configured\n * role-default resolution in determineModel (nothing hardcoded here).\n */\nexport function resolveSlotTierSpawnParams(\n baseWorkspace: string,\n slotItemId: string,\n explicitModel?: string,\n spawnKey?: string,\n): SlotTierSpawnParams {\n // An explicit per-spawn model override outranks all staffing (same\n // precedence as determineModel).\n if (explicitModel) return {};\n const doc = readWorkspacePlanSync(baseWorkspace);\n if (!doc) return {};\n const planMetadata = doc?.plan?.metadata;\n\n // Extract issueId from workspace path: feature-<issueId>\n const workspaceName = basename(baseWorkspace);\n const issueIdMatch = workspaceName.match(/^feature-(.+)$/i);\n const issueId = issueIdMatch ? issueIdMatch[1].toUpperCase() : null;\n\n const config = loadYamlConfig().config;\n const tiered = config.tieredExecution;\n // Use issue-aware resolver to honor record override (PAN-2383)\n const explicitTableActive = Boolean(\n tiered && (issueId ? resolveTieredExecutionEnabledForIssue(tiered, issueId, planMetadata) : resolveTieredExecutionEnabled(tiered, planMetadata))\n );\n\n const item = doc.plan.items.find((candidate) => candidate.id === slotItemId);\n if (!item) {\n // With an explicit table active a missing item is a data-integrity signal;\n // otherwise fall back to the historical role-default spawn path.\n if (explicitTableActive) {\n throw new Error(\n `Tiered execution is enabled but item '${slotItemId}' was not found in the plan for ${baseWorkspace}.`,\n );\n }\n return {};\n }\n\n // PAN-2397 (Always Tiered): staffing ALWAYS resolves — explicit tier table\n // when enabled, else the implicit roles.work tier (same resolveModel +\n // spawnKey as determineModel, so distributions stay deterministic).\n const staffing = resolveStaffing(item, { planMetadata, spawnKey, config });\n return {\n model: staffing.model,\n harness: staffing.implicit ? undefined : staffing.harness,\n tierName: staffing.tierName,\n implicit: staffing.implicit,\n };\n}\n\n/**\n * Tiered-execution model resolution for the single work-agent path.\n * Auto-start and non-swarm issues still run one foreground work agent, but\n * the first dispatchable vBRIEF item is the same scheduling unit the agent is\n * about to execute. If tiered execution is enabled for the issue, route that\n * agent through the item's resolved tier; otherwise leave role-default model\n * resolution untouched.\n */\nexport function resolveSingleWorkTierSpawnParams(\n workspace: string,\n explicitModel?: string,\n spawnKey?: string,\n): SlotTierSpawnParams {\n if (explicitModel) return {};\n\n const doc = readWorkspacePlanSync(workspace);\n if (!doc) return {};\n const planMetadata = doc?.plan?.metadata;\n\n const item = getDispatchableItems(doc, new Set())[0];\n if (!item) return {};\n\n // Extract issueId from workspace path: feature-<issueId>\n const workspaceName = basename(workspace);\n const issueIdMatch = workspaceName.match(/^feature-(.+)$/i);\n const issueId = issueIdMatch ? issueIdMatch[1].toUpperCase() : null;\n\n // PAN-2397 (Always Tiered): the single-work path staffs through the same\n // resolver as slots — explicit table when enabled, implicit roles.work\n // tier otherwise. Use issue-aware resolver to honor record override (PAN-2383).\n const config = loadYamlConfig().config;\n const tiered = config.tieredExecution;\n const effectiveTieredEnabled = issueId\n ? resolveTieredExecutionEnabledForIssue(tiered, issueId, planMetadata)\n : resolveTieredExecutionEnabled(tiered, planMetadata);\n\n const staffing = resolveStaffing(item, {\n planMetadata,\n spawnKey,\n config: { ...config, tieredExecution: { ...tiered, enabled: effectiveTieredEnabled } },\n });\n return {\n model: staffing.model,\n harness: staffing.implicit ? undefined : staffing.harness,\n tierName: staffing.tierName,\n implicit: staffing.implicit,\n };\n}\n\n/**\n * Shared tracker resolution logic for issue state transitions.\n *\n * Resolution order (by project tracker type):\n * 1. github_repo → GitHub Issues (takes priority over issue_prefix, since projects\n * like overdeck use GitHub Issues with a prefix, not Linear)\n * 2. rally_project → Rally\n * 3. issue_prefix (no github_repo) → Linear (covers gitlab+linear and pure-linear projects)\n * 4. gitlab_repo only → warn and skip (GitLab doesn't support label-based state transitions)\n *\n * Precedence rationale: issue_prefix was renamed from linear_team but is now also set on\n * GitHub-hosted projects (e.g. issue_prefix: PAN for overdeck GitHub Issues).\n * github_repo must be checked first so GitHub projects don't misroute to Linear.\n */\nasync function transitionIssueState(issueId: string, state: IssueState, workspacePath?: string): Promise<void> {\n // Guard: bare numeric IDs (no alphabetic prefix, e.g. \"484\") must never reach\n // any tracker API. Linear's searchIssues(\"484\") would match MIN-484 in the wrong\n // team. Log a warning and skip — the workspace's project must use prefixed IDs.\n if (/^\\d+$/.test(issueId)) {\n console.warn(\n `[agents] Skipping ${state} transition for bare numeric ID \"${issueId}\" — ` +\n `issue IDs must include a project prefix (e.g. PAN-${issueId}). ` +\n `This workspace was likely created before the pan- prefix convention.`\n );\n return;\n }\n\n // Resolve the project from workspacePath — its configured tracker is authoritative.\n // Every issue MUST belong to a registered project with a tracker configured.\n const projectConfig = workspacePath ? findProjectByPathSync(workspacePath) : null;\n if (!projectConfig) {\n throw new Error(`Cannot transition ${issueId}: no project config found for workspace ${workspacePath || '(none)'}. Register the project in projects.yaml.`);\n }\n\n // Project has a GitHub repo — use GitHub Issues tracker.\n // Checked BEFORE issue_prefix because github_repo projects (e.g. overdeck)\n // set issue_prefix for their GitHub Issue prefix (PAN-), not for Linear.\n if (projectConfig.github_repo) {\n const [owner, repo] = projectConfig.github_repo.split('/');\n const tracker = createTracker({ type: 'github', owner, repo });\n await Effect.runPromise(tracker.transitionIssue(issueId, state));\n console.log(`[agents] Transitioned ${issueId} to ${state} via GitHub (${projectConfig.github_repo})`);\n return;\n }\n\n // Project has a Rally project — use Rally tracker\n if (projectConfig.rally_project) {\n const config = loadConfigSync();\n const trackersConfig = config.trackers;\n if (!trackersConfig?.rally) {\n throw new Error(`Project ${projectConfig.name} uses Rally (project: ${projectConfig.rally_project}) but no Rally tracker is configured in config.yaml`);\n }\n const tracker = createTrackerFromConfig(trackersConfig, 'rally');\n await Effect.runPromise(tracker.transitionIssue(issueId, state));\n console.log(`[agents] Transitioned ${issueId} to ${state} via Rally (project: ${projectConfig.rally_project})`);\n return;\n }\n\n // Project has a Linear team prefix (and no github_repo) — use Linear tracker.\n // This covers: pure-Linear projects and gitlab+Linear projects (e.g. mind-your-now).\n if (getIssuePrefix(projectConfig)) {\n const config = loadConfigSync();\n const trackersConfig = config.trackers;\n if (!trackersConfig?.linear) {\n throw new Error(`Project ${projectConfig.name} uses Linear (team: ${getIssuePrefix(projectConfig)}) but no Linear tracker is configured in config.yaml`);\n }\n const tracker = createTrackerFromConfig(trackersConfig, 'linear');\n await Effect.runPromise(tracker.transitionIssue(issueId, state));\n console.log(`[agents] Transitioned ${issueId} to ${state} via Linear (team: ${getIssuePrefix(projectConfig)})`);\n return;\n }\n\n if (projectConfig.gitlab_repo) {\n console.warn(`[agents] GitLab project detected (${projectConfig.gitlab_repo}) but GitLab does not support ${state} label transitions`);\n return;\n }\n\n throw new Error(`Project ${projectConfig.name} has no tracker configured (need issue_prefix, github_repo, or rally_project in projects.yaml)`);\n}\n\nexport async function transitionIssueToInProgress(issueId: string, workspacePath?: string): Promise<void> {\n return transitionIssueState(issueId, 'in_progress', workspacePath);\n}\n\n/**\n * Transitions an issue to \"in_review\" state in the configured issue tracker.\n * Fire-and-forget — logs warnings on failure but never blocks the pipeline.\n */\nexport async function transitionIssueToInReview(issueId: string, workspacePath?: string): Promise<void> {\n return transitionIssueState(issueId, 'in_review', workspacePath);\n}\n\nexport interface AgentLaunchConfig {\n launcherContent: string;\n providerEnv: Record<string, string>;\n}\n\nexport async function buildAgentLaunchConfig(opts: {\n agentId: string;\n model: string;\n workspace: string;\n role: Role;\n spawnMode?: 'resume';\n resumeSessionId?: string;\n isPlanning?: boolean;\n /** Per-agent .mcp.json path for the experimental Channels bridge. */\n channelsBridgeMcpConfig?: string;\n /** MCP server name to load as a Channel; defaults to 'overdeck-bridge'. */\n channelsBridgeServerName?: string;\n useSupervisor?: boolean;\n supervisorScriptPath?: string;\n /** Claude Code session id for fresh launches that need a known id before boot. */\n sessionId?: string;\n /**\n * Coding-agent harness (PAN-636). Defaults to 'claude-code' when omitted —\n * preserves bit-for-bit pre-PAN-636 behavior. When 'pi', the launcher is\n * built via the Pi command-line generator instead of the claude path; opts\n * like agentId-as-name and agent-frontmatter are ignored because Pi has\n * no agent-definition system.\n */\n harness?: RuntimeName;\n extraEnvExports?: string[];\n /** Claude Code `--effort` level threaded into the launcher command. */\n effort?: RoleEffort;\n /** Inline prompt to embed in launch commands that still support prompt arguments. */\n promptInline?: string;\n}): Promise<AgentLaunchConfig> {\n const model = requireModelOverrideSync(opts.model);\n\n // Substrate guard: inject permission deny rules for Overdeck infrastructure\n // paths (.claude/agents/, .claude/hooks/, ~/.overdeck/, JSONL session dirs)\n // into the workspace's .claude/settings.local.json. Idempotent. Without this\n // a vBRIEF action like \"delete the legacy pan-*-agent.md files\" can convince\n // an agent to brick its own runtime. PAN-1048 X1 incident, 2026-05-09.\n try {\n const { injectOverdeckInfraDeny } = await import('../claude-settings-overlay.js');\n await Effect.runPromise(injectOverdeckInfraDeny(opts.workspace));\n } catch (err) {\n console.warn(`[agents] injectOverdeckInfraDeny failed for ${opts.agentId} (non-fatal): ${err instanceof Error ? err.message : err}`);\n }\n\n const providerEnv = await getProviderEnvForModel(model);\n\n const provider = getProviderForModelSync(model as ModelId);\n if (provider.authType === 'credential-file') {\n setupCredentialFileAuthSync(provider, opts.workspace);\n } else {\n clearCredentialFileAuthSync(opts.workspace);\n }\n\n const providerExports = await getProviderExportsForModel(model);\n\n // PAN-1048: resume/restart launchers must respect the agent's role.\n // A resumed review/test/ship run loads the wrong frontmatter (and wrong\n // tool permissions) if it always points at roles/work.md.\n const launchRole: Role = opts.isPlanning ? 'plan' : opts.role;\n\n // PAN-1055: ohmypi harness needs --session-dir + fifo redirect threaded into\n // the launcher; getOhmypiLauncherFields() resolves them from the agent state\n // and they're spread into generateLauncherScript() below.\n // PAN-1574: codex harness needs its per-agent CODEX_HOME path.\n const behavior = getHarnessBehavior(opts.harness);\n const piLauncherFields = behavior.usesRpcFifo\n ? await getOhmypiLauncherFields(opts.agentId, model)\n : {};\n const codexLauncherFields = behavior.usesCodexHome\n ? getCodexLauncherFields(opts.agentId, model, opts.workspace)\n : {};\n\n if (opts.spawnMode === 'resume' && opts.resumeSessionId) {\n // Resume sessions adopt the role definition via --agent.\n // Permissions/model/tools/hooks come from roles/<role>.md frontmatter.\n // --name <agentId> gives the resumed Claude session a human-readable handle.\n //\n // The frontmatter's permissionMode: bypassPermissions only bypasses prompts\n // INSIDE cwd. Tools that touch siblings of cwd (e.g. bd reading\n // .beads/issues.jsonl through git subprocesses, pan reading\n // ~/.overdeck/...) still hit \"Do you want to proceed?\" without DSP.\n // Mid-Bash dialog dismissals (deacon nudge, paste-buffer write, sibling\n // hook output) cancel the in-flight tool call and surface as\n // `Interrupted · What should Claude do instead?` (PAN-1024 reproduced\n // this loop on every fresh resume of PAN-1044/PAN-934).\n //\n // Match the fresh-spawn path: when permissionMode resolves to 'bypass'\n // (PAN_YOLO=true OR claude.permissionMode=bypass in config), prepend\n // --dangerously-skip-permissions on resume too.\n // Use the shared helper so the only string literal for DSP lives in\n // claude-permissions.ts (see scripts/lint-permissions.sh allowlist).\n const launcherContent = generateLauncherScriptSync({\n role: launchRole,\n spawnMode: 'resume',\n workingDir: opts.workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports,\n // PAN-2087: claude-code resumes inject the role body (+ effort) as an\n // appended system prompt instead of `--agent <file>` (Claude Code 2.1.195\n // dropped --agent file support); permission flags come from the global\n // resolver. ohmypi/codex resumes route through getAgentRuntimeBaseCommand\n // which short-circuits to the omp/codex form.\n baseCommand: behavior.launchCommandKind !== 'claude-code'\n ? await getAgentRuntimeBaseCommand(model, opts.agentId, launchRole, opts.harness)\n : `claude ${getClaudePermissionFlagsStringSync()}${roleSystemPromptInjectionSync(roleAgentDefinitionPath(launchRole))}`,\n resumeSessionId: opts.resumeSessionId,\n model: behavior.launchCommandKind !== 'claude-code' || providerExports.includes('ANTHROPIC_BASE_URL') ? model : undefined,\n extraArgs: behavior.launchCommandKind !== 'claude-code' ? undefined : `--name ${opts.agentId}`,\n appendSystemPromptFiles: await claudeSystemPromptFiles(opts.workspace, opts.harness),\n extraEnvExports: opts.extraEnvExports,\n useSupervisor: opts.useSupervisor,\n supervisorScriptPath: opts.supervisorScriptPath,\n promptInline: opts.promptInline,\n ...piLauncherFields,\n ...codexLauncherFields,\n });\n return { launcherContent, providerEnv };\n }\n\n const yamlConfig = loadYamlConfig();\n const cavemanExports = await buildCavemanExports(\n opts.workspace,\n yamlConfig.config.caveman,\n opts.isPlanning ?? false,\n );\n\n // PAN-982: pass the role definition path + agentId through getAgentRuntimeBaseCommand so it\n // emits 'claude --agent roles/<role>.md --name <agentId>'.\n // PAN-636: when the harness uses the ohmypi RPC command, the helper\n // short-circuits to an omp --mode rpc line and the\n // agentName/agentDefinition arguments are ignored (Pi has no agent\n // definitions). The launcher generator's Pi branch then layers --session-dir\n // and the fifo redirect on top.\n const agentDefinition = roleAgentDefinitionPath(launchRole);\n const launcherContent = generateLauncherScriptSync({\n role: launchRole,\n workingDir: opts.workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports,\n cavemanExports,\n baseCommand: await getAgentRuntimeBaseCommand(model, opts.agentId, agentDefinition, opts.harness ?? 'claude-code', opts.effort),\n sessionId: behavior.sessionIdSource === 'launcher-session-id' ? opts.sessionId : undefined,\n appendSystemPromptFiles: await claudeSystemPromptFiles(opts.workspace, opts.harness),\n extraEnvExports: opts.extraEnvExports,\n useSupervisor: opts.useSupervisor,\n supervisorScriptPath: opts.supervisorScriptPath,\n promptInline: opts.promptInline,\n ...piLauncherFields,\n ...codexLauncherFields,\n ...(opts.channelsBridgeMcpConfig\n ? {\n channelsBridgeMcpConfig: opts.channelsBridgeMcpConfig,\n channelsBridgeServerName: opts.channelsBridgeServerName ?? 'overdeck-bridge',\n }\n : {}),\n });\n\n return { launcherContent, providerEnv };\n}\n\nexport function defaultRunWorkspace(issueId: string): string {\n const project = resolveProjectFromIssueSync(issueId);\n if (!project) {\n throw new Error(`Cannot spawn role run for ${issueId}: no project is configured for this issue prefix`);\n }\n return join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n}\n\nexport async function retrieveSpawnTimeMemoryContext(input: {\n prompt: string;\n issueId: string;\n workspace: string;\n agentId: string;\n role: Role;\n harness: RuntimeName;\n}): Promise<string> {\n if (!input.prompt.trim()) return '';\n\n try {\n const identity: MemoryIdentity = {\n projectId: inferMemoryProjectId(input.workspace),\n workspaceId: basename(input.workspace),\n issueId: input.issueId,\n runId: input.agentId,\n sessionId: input.agentId,\n agentRole: input.role,\n agentHarness: input.harness,\n };\n const { injectPromptTimeMemory } = await import('../memory/injection.js');\n return (await injectPromptTimeMemory({ prompt: input.prompt, identity, surface: 'spawn' })).context;\n } catch (error) {\n console.warn(`[agents] Spawn-time memory context unavailable for ${input.agentId}:`, error instanceof Error ? error.message : String(error));\n return '';\n }\n}\n\nexport async function withSpawnTimeMemoryContext(input: {\n prompt: string;\n issueId: string;\n workspace: string;\n agentId: string;\n role: Role;\n harness: RuntimeName;\n}): Promise<string> {\n const context = await retrieveSpawnTimeMemoryContext(input);\n return context ? `${context}\\n\\n---\\n\\n${input.prompt}` : input.prompt;\n}\n\nexport function runAgentId(issueId: string, role: Role, subRole?: string): string {\n const base = role === 'work'\n ? `agent-${issueId.toLowerCase()}`\n : `agent-${issueId.toLowerCase()}-${role}`;\n return subRole ? `${base}-${subRole}` : base;\n}\n\n/**\n * Spawn-time stack-rebuild self-heal state. PAN-1618: the work-spawn gate\n * (`assertWorkspaceStackHealthyForSpawn`) used to fail hard when the workspace\n * docker stack was down, with only manual recoveries (`pan workspace rebuild`\n * or interactive `--host`). Under autonomous operation a fully-planned\n * `proposed` item whose stack happened to be down could never auto-start its\n * work agent — it sat at the gate forever. This mirrors the PAN-1247\n * orphan-test self-heal one role earlier: rebuild the stack before failing,\n * bounded by a cooldown + attempt cap so a stack that genuinely cannot be\n * rebuilt escalates to a human instead of looping `docker compose` forever.\n */\nconst spawnStackRebuildState: Map<string, { lastAttempt: number; attempts: number; escalated: boolean; hostFallbackNoticed?: boolean }> =\n new Map();\nconst SPAWN_STACK_REBUILD_COOLDOWN_MS = 15 * 60 * 1000;\nconst SPAWN_STACK_REBUILD_MAX_ATTEMPTS = 3;\n\n/**\n * Spawn a role-based Overdeck run. Work delegates to the existing work-agent\n * path; review/test/ship use the role definition files under roles/.\n */\nexport async function assertWorkspaceStackHealthyForSpawn(\n issueId: string,\n role: Role,\n allowHost = false,\n workspacePath?: string,\n): Promise<void> {\n if (role === 'plan' || role === 'knowledge') return;\n\n // PAN-1872: guard against an undefined issueId so workspace health checks do\n // not crash with `Cannot read properties of undefined (reading 'toUpperCase')`\n // while pan start is recovering from a sync-main conflict.\n const normalizedIssue = (issueId ?? '').toUpperCase();\n\n // PAN-1746: absence of a workspace must be a HARDER failure than an unhealthy\n // one. The host-fallback path below lets advancing roles (review/test/ship)\n // proceed when the docker stack is merely unhealthy — but a workspace\n // directory that does not exist at all means the launcher would fall back to\n // its cwd ($HOME) and wedge Claude at the folder-trust prompt while it holds\n // an advancing slot against the PAN-1665 governor. Refuse the spawn outright\n // instead of degrading to host. (`work`'s resume path already guards this in\n // restartAgent; this closes the same gap on the role-run spawn path.)\n if (workspacePath && !existsSync(workspacePath)) {\n throw new Error(\n `Workspace for ${normalizedIssue} does not exist at ${workspacePath} — refusing to spawn ${role}. `\n + `A missing workspace would land the agent in $HOME at the folder-trust prompt. `\n + `Recreate the workspace ('pan workspace rebuild ${normalizedIssue}') before retrying.`,\n );\n }\n\n const health = await Effect.runPromise(getWorkspaceStackHealth(issueId, { workspacePath }));\n if (health.healthy) {\n spawnStackRebuildState.delete(normalizedIssue);\n return;\n }\n\n const details = health.reasons.join('; ');\n const message = `Workspace docker stack for ${normalizedIssue} is not healthy: ${details}. Run 'pan workspace rebuild ${normalizedIssue}' or retry with --host to override.`;\n\n if (allowHost) {\n // PAN-1556: host-override is a per-spawn detail, not user-facing activity —\n // it fired once per convoy member and buried real feed items (conversations).\n // Keep the console.warn for debugging; do not emit to the session feed.\n console.warn(`[agents] ${message}`);\n return;\n }\n\n // PAN-1645 + PAN-1618: an unhealthy stack must NEVER *block* the advancing\n // roles. review/test/ship all operate on the HOST workspace — ship\n // rebases/pushes against the host .git, review reads the committed diff, and\n // test runs the project's quality gates (host-run unless a gate explicitly\n // opts into a container) — so they do not need the workspace's docker\n // containers at all. The long-standing manual `--host` workaround (PAN-1645)\n // burned enormous effort just rediscovering that ship-on-broken-docker is a\n // false gate. For these roles we still attempt one bounded autonomous rebuild\n // (so a project whose test gates DO run in containers gets a healthy stack\n // when recoverable), but if it can't be made healthy we AUTO-FALL-BACK TO\n // HOST and proceed instead of throwing.\n //\n // `work` is different: a work agent may rely on the dev container's services,\n // so silently running it on the host could build/test against a missing\n // environment. work keeps the hard gate (rebuild → escalate to a human).\n const hostFallbackEligible = role !== 'work';\n\n const record = spawnStackRebuildState.get(normalizedIssue)\n ?? { lastAttempt: 0, attempts: 0, escalated: false };\n const now = Date.now();\n\n const fallbackToHost = (reason: string): void => {\n console.warn(`[agents] ${message} — auto-falling back to host for ${role} (${reason})`);\n // Emit the host-fallback notice once per issue. Use a SEPARATE latch from\n // the work-escalation latch (`escalated`): if review/test/ship trip the\n // host fallback first, a later `work` spawn for the same broken-stack issue\n // must still be able to emit its own (error-level) dead-end marker — the\n // operator's only signal that a work agent is blocked on docker.\n if (!record.hostFallbackNoticed) {\n record.hostFallbackNoticed = true;\n spawnStackRebuildState.set(normalizedIssue, record);\n emitActivityEntrySync({\n source: role,\n level: 'warn',\n issueId: normalizedIssue,\n message: `agent-spawn-host-fallback: ${normalizedIssue}`,\n details: `Workspace docker stack unhealthy (${details}); ${role} runs on the host (rebase/verify use host .git + host gates), so proceeding without containers. ${reason}`,\n });\n }\n };\n\n const blockWork = (markerMessage: string, errDetails: string): never => {\n if (!record.escalated) {\n record.escalated = true;\n spawnStackRebuildState.set(normalizedIssue, record);\n emitActivityEntrySync({\n source: role,\n level: 'error',\n issueId: normalizedIssue,\n message: markerMessage,\n details: errDetails,\n });\n }\n throw new Error(message);\n };\n\n if (record.attempts >= SPAWN_STACK_REBUILD_MAX_ATTEMPTS) {\n if (hostFallbackEligible) {\n fallbackToHost(`rebuild exhausted after ${record.attempts} attempts`);\n return;\n }\n blockWork(\n `agent-spawn-stack-rebuild-exhausted: ${normalizedIssue}`,\n `Workspace docker stack still unhealthy after ${record.attempts} rebuild attempts: ${details}. Manual 'pan workspace rebuild ${normalizedIssue}' or retry with --host needed.`,\n );\n }\n\n if (now - record.lastAttempt < SPAWN_STACK_REBUILD_COOLDOWN_MS) {\n // A rebuild was attempted recently and the stack is still unhealthy —\n // don't hammer `docker compose` every spawn.\n if (hostFallbackEligible) {\n fallbackToHost('rebuild on cooldown');\n return;\n }\n blockWork(`agent-spawn-blocked-stack-unhealthy: ${normalizedIssue}`, details);\n }\n\n record.lastAttempt = now;\n record.attempts += 1;\n spawnStackRebuildState.set(normalizedIssue, record);\n console.log(\n `[agents] Workspace stack for ${normalizedIssue} unhealthy (${details}) — rebuilding ` +\n `before spawn (attempt ${record.attempts}/${SPAWN_STACK_REBUILD_MAX_ATTEMPTS})`,\n );\n\n const { rebuildWorkspaceStack } = await import('../workspace/rebuild-stack.js');\n const result = await Effect.runPromise(\n rebuildWorkspaceStack(issueId, {\n onProgress: (m) => console.log(`[agents] ${normalizedIssue} stack rebuild: ${m}`),\n }),\n ).catch((err: unknown) => ({ success: false as const, error: err instanceof Error ? err.message : String(err) }));\n\n if (result.success) {\n spawnStackRebuildState.delete(normalizedIssue);\n console.log(`[agents] Workspace stack for ${normalizedIssue} rebuilt — proceeding with spawn`);\n return;\n }\n\n console.warn(`[agents] Workspace stack rebuild failed for ${normalizedIssue}: ${result.error}`);\n if (hostFallbackEligible) {\n fallbackToHost(`rebuild failed: ${result.error ?? 'unknown'}`);\n return;\n }\n blockWork(`agent-spawn-stack-rebuild-failed: ${normalizedIssue}`, result.error ?? details);\n}\n","/**\n * Agent CV (Work History) System\n *\n * Tracks agent performance over time to enable capability-based routing.\n */\n\nimport { Effect } from 'effect';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\nimport { FsError } from './errors.js';\n\nexport interface WorkEntry {\n issueId: string;\n startedAt: string;\n completedAt?: string;\n outcome: 'success' | 'failed' | 'abandoned' | 'in_progress';\n duration?: number; // minutes\n skills?: string[];\n failureReason?: string;\n commits?: number;\n linesChanged?: number;\n}\n\nexport interface AgentCV {\n agentId: string;\n createdAt: string;\n lastActive: string;\n runtime: string;\n model: string;\n stats: {\n totalIssues: number;\n successCount: number;\n failureCount: number;\n abandonedCount: number;\n avgDuration: number; // minutes\n successRate: number; // 0-1\n };\n skillsUsed: string[];\n recentWork: WorkEntry[];\n}\n\nfunction getCVFile(agentId: string): string {\n return join(AGENTS_DIR, agentId, 'cv.json');\n}\n\n/**\n * Read an agent's CV without creating one.\n */\nexport function readAgentCVSync(agentId: string): AgentCV | null {\n const cvFile = getCVFile(agentId);\n\n if (!existsSync(cvFile)) return null;\n\n try {\n return JSON.parse(readFileSync(cvFile, 'utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Get or create an agent's CV\n */\nexport function getAgentCVSync(agentId: string): AgentCV {\n const existing = readAgentCVSync(agentId);\n if (existing) return existing;\n\n // Create new CV\n const cv: AgentCV = {\n agentId,\n createdAt: new Date().toISOString(),\n lastActive: new Date().toISOString(),\n runtime: 'claude',\n model: 'sonnet',\n stats: {\n totalIssues: 0,\n successCount: 0,\n failureCount: 0,\n abandonedCount: 0,\n avgDuration: 0,\n successRate: 0,\n },\n skillsUsed: [],\n recentWork: [],\n };\n\n saveAgentCVSync(cv);\n return cv;\n}\n\n/**\n * Save an agent's CV\n */\nexport function saveAgentCVSync(cv: AgentCV): void {\n const dir = join(AGENTS_DIR, cv.agentId);\n mkdirSync(dir, { recursive: true });\n writeFileSync(getCVFile(cv.agentId), JSON.stringify(cv, null, 2));\n}\n\n/**\n * Start tracking work for an agent\n */\nexport function startWorkSync(agentId: string, issueId: string, skills?: string[]): void {\n const cv = getAgentCVSync(agentId);\n\n const entry: WorkEntry = {\n issueId,\n startedAt: new Date().toISOString(),\n outcome: 'in_progress',\n skills,\n };\n\n cv.recentWork.unshift(entry);\n cv.stats.totalIssues++;\n cv.lastActive = new Date().toISOString();\n\n // Track skills\n if (skills) {\n for (const skill of skills) {\n if (!cv.skillsUsed.includes(skill)) {\n cv.skillsUsed.push(skill);\n }\n }\n }\n\n // Keep only last 50 entries\n if (cv.recentWork.length > 50) {\n cv.recentWork = cv.recentWork.slice(0, 50);\n }\n\n saveAgentCVSync(cv);\n}\n\n/**\n * Complete work for an agent\n */\nexport function completeWorkSync(\n agentId: string,\n issueId: string,\n outcome: 'success' | 'failed' | 'abandoned',\n details?: { commits?: number; linesChanged?: number; failureReason?: string }\n): void {\n const cv = getAgentCVSync(agentId);\n\n // Find the work entry\n const entry = cv.recentWork.find(\n (w) => w.issueId === issueId && w.outcome === 'in_progress'\n );\n\n if (entry) {\n entry.outcome = outcome;\n entry.completedAt = new Date().toISOString();\n entry.duration = Math.round(\n (new Date().getTime() - new Date(entry.startedAt).getTime()) / (1000 * 60)\n );\n if (details?.commits) entry.commits = details.commits;\n if (details?.linesChanged) entry.linesChanged = details.linesChanged;\n if (details?.failureReason) entry.failureReason = details.failureReason;\n }\n\n // Update stats\n if (outcome === 'success') {\n cv.stats.successCount++;\n } else if (outcome === 'failed') {\n cv.stats.failureCount++;\n } else if (outcome === 'abandoned') {\n cv.stats.abandonedCount++;\n }\n\n // Calculate success rate\n const completed = cv.stats.successCount + cv.stats.failureCount + cv.stats.abandonedCount;\n cv.stats.successRate = completed > 0 ? cv.stats.successCount / completed : 0;\n\n // Calculate average duration (only from completed work)\n const completedEntries = cv.recentWork.filter(\n (w) => w.duration !== undefined && w.outcome !== 'in_progress'\n );\n if (completedEntries.length > 0) {\n const totalDuration = completedEntries.reduce((sum, w) => sum + (w.duration || 0), 0);\n cv.stats.avgDuration = Math.round(totalDuration / completedEntries.length);\n }\n\n cv.lastActive = new Date().toISOString();\n saveAgentCVSync(cv);\n}\n\n/**\n * Get agent rankings by success rate\n */\nexport function getAgentRankingsSync(): Array<{\n agentId: string;\n successRate: number;\n totalIssues: number;\n avgDuration: number;\n}> {\n const rankings: Array<{\n agentId: string;\n successRate: number;\n totalIssues: number;\n avgDuration: number;\n }> = [];\n\n if (!existsSync(AGENTS_DIR)) return rankings;\n\n const dirs = readdirSync(AGENTS_DIR, { withFileTypes: true }).filter(\n (d) => d.isDirectory()\n );\n\n for (const dir of dirs) {\n const cv = getAgentCVSync(dir.name);\n if (cv.stats.totalIssues > 0) {\n rankings.push({\n agentId: dir.name,\n successRate: cv.stats.successRate,\n totalIssues: cv.stats.totalIssues,\n avgDuration: cv.stats.avgDuration,\n });\n }\n }\n\n // Sort by success rate, then by total issues\n rankings.sort((a, b) => {\n if (b.successRate !== a.successRate) {\n return b.successRate - a.successRate;\n }\n return b.totalIssues - a.totalIssues;\n });\n\n return rankings;\n}\n\n/**\n * Format CV for display\n */\nexport function formatCVSync(cv: AgentCV): string {\n const lines: string[] = [\n `# Agent CV: ${cv.agentId}`,\n '',\n `Runtime: ${cv.runtime} (${cv.model})`,\n `Created: ${cv.createdAt}`,\n `Last Active: ${cv.lastActive}`,\n '',\n '## Statistics',\n '',\n `- Total Issues: ${cv.stats.totalIssues}`,\n `- Success Rate: ${(cv.stats.successRate * 100).toFixed(1)}%`,\n `- Successes: ${cv.stats.successCount}`,\n `- Failures: ${cv.stats.failureCount}`,\n `- Abandoned: ${cv.stats.abandonedCount}`,\n `- Avg Duration: ${cv.stats.avgDuration} minutes`,\n '',\n ];\n\n if (cv.skillsUsed.length > 0) {\n lines.push('## Skills Used');\n lines.push('');\n lines.push(cv.skillsUsed.join(', '));\n lines.push('');\n }\n\n if (cv.recentWork.length > 0) {\n lines.push('## Recent Work');\n lines.push('');\n\n for (const work of cv.recentWork.slice(0, 10)) {\n const statusIcon = {\n success: '✓',\n failed: '✗',\n abandoned: '⊘',\n in_progress: '●',\n }[work.outcome];\n\n const duration = work.duration ? ` (${work.duration}m)` : '';\n lines.push(`${statusIcon} ${work.issueId}${duration}`);\n\n if (work.failureReason) {\n lines.push(` Reason: ${work.failureReason}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n// CV file IO is sync by design. Read paths return Effect.sync; write paths\n// surface FsError.\n\n/** Load (or create) the CV for an agent. Pure-ish. */\nexport const getAgentCV = (agentId: string): Effect.Effect<AgentCV> =>\n Effect.sync(() => getAgentCVSync(agentId));\n\n/** Persist a CV to disk. */\nexport const saveAgentCV = (cv: AgentCV): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => saveAgentCVSync(cv),\n catch: (cause) =>\n new FsError({ path: cv.agentId, operation: 'save-agent-cv', cause }),\n });\n\n/** Mark the start of a work item on an agent's CV. */\nexport const startWork = (\n agentId: string,\n issueId: string,\n skills?: string[],\n): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => startWorkSync(agentId, issueId, skills),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'cv-start-work', cause }),\n });\n\n/** Mark completion (success / failure / abandoned). */\nexport const completeWork = (\n ...args: Parameters<typeof completeWorkSync>\n): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => completeWorkSync(...args),\n catch: (cause) =>\n new FsError({ path: args[0], operation: 'cv-complete-work', cause }),\n });\n\n/** Aggregate rankings across all agents. Pure-ish. */\nexport const getAgentRankings = (): Effect.Effect<\n ReturnType<typeof getAgentRankingsSync>\n> => Effect.sync(() => getAgentRankingsSync());\n\n/** Render a CV as text. Pure. */\nexport const formatCV = (cv: AgentCV): Effect.Effect<string> =>\n Effect.sync(() => formatCVSync(cv));\n","/**\n * FPP Hooks System - Fixed Point Principle\n *\n * \"Any runnable action is a fixed point and must resolve before the system can rest.\"\n *\n * Inspired by Doctor Who: a fixed point in time must occur — it cannot be avoided.\n *\n * Hooks are persistent work queues for agents. When an agent starts,\n * it checks its hook for pending work and executes immediately.\n */\n\nimport { Effect } from 'effect';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\nimport { FsError } from './errors.js';\n\nexport interface HookItem {\n id: string;\n type: 'task' | 'message' | 'notification';\n priority: 'urgent' | 'high' | 'normal' | 'low';\n source: string;\n payload: {\n issueId?: string;\n message?: string;\n action?: string;\n context?: Record<string, any>;\n };\n createdAt: string;\n expiresAt?: string;\n}\n\nexport interface Hook {\n agentId: string;\n items: HookItem[];\n lastChecked?: string;\n}\n\nfunction getHookDir(agentId: string): string {\n return join(AGENTS_DIR, agentId);\n}\n\nfunction getHookFile(agentId: string): string {\n return join(getHookDir(agentId), 'hook.json');\n}\n\nfunction getMailDir(agentId: string): string {\n return join(getHookDir(agentId), 'mail');\n}\n\n/**\n * Initialize hook structure for an agent\n */\nexport function initHookSync(agentId: string): void {\n const hookDir = getHookDir(agentId);\n const mailDir = getMailDir(agentId);\n\n mkdirSync(hookDir, { recursive: true });\n mkdirSync(mailDir, { recursive: true });\n\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n const hook: Hook = {\n agentId,\n items: [],\n };\n writeFileSync(hookFile, JSON.stringify(hook, null, 2));\n }\n}\n\n/**\n * Get the hook for an agent\n */\nexport function getHookSync(agentId: string): Hook | null {\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(hookFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * Add work to an agent's hook (FPP trigger)\n */\nexport function pushToHookSync(agentId: string, item: Omit<HookItem, 'id' | 'createdAt'>): HookItem {\n initHookSync(agentId);\n\n const hook = getHookSync(agentId) || { agentId, items: [] };\n\n const newItem: HookItem = {\n ...item,\n id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n createdAt: new Date().toISOString(),\n };\n\n hook.items.push(newItem);\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return newItem;\n}\n\n/**\n * Check if agent has pending work (FPP check)\n */\nexport function checkHookSync(agentId: string): { hasWork: boolean; urgentCount: number; items: HookItem[] } {\n const hook = getHookSync(agentId);\n\n if (!hook || hook.items.length === 0) {\n // Also check mail directory for incoming messages\n const mailDir = getMailDir(agentId);\n if (existsSync(mailDir)) {\n const mails = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n if (mails.length > 0) {\n // Convert mail to hook items\n const mailItems: HookItem[] = mails.map((file) => {\n try {\n const content = readFileSync(join(mailDir, file), 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n }).filter(Boolean) as HookItem[];\n\n return {\n hasWork: mailItems.length > 0,\n urgentCount: mailItems.filter((i) => i.priority === 'urgent').length,\n items: mailItems,\n };\n }\n }\n\n return { hasWork: false, urgentCount: 0, items: [] };\n }\n\n // Filter out expired items\n const now = new Date();\n const activeItems = hook.items.filter((item) => {\n if (item.expiresAt) {\n return new Date(item.expiresAt) > now;\n }\n return true;\n });\n\n // Sort by priority: urgent > high > normal > low\n const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };\n activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);\n\n return {\n hasWork: activeItems.length > 0,\n urgentCount: activeItems.filter((i) => i.priority === 'urgent').length,\n items: activeItems,\n };\n}\n\n/**\n * Pop the next work item from hook (after execution)\n */\nexport function popFromHookSync(agentId: string, itemId: string): boolean {\n const hook = getHookSync(agentId);\n if (!hook) return false;\n\n const index = hook.items.findIndex((i) => i.id === itemId);\n if (index === -1) return false;\n\n hook.items.splice(index, 1);\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Clear all items from hook\n */\nexport function clearHookSync(agentId: string): void {\n const hook = getHookSync(agentId);\n if (!hook) return;\n\n hook.items = [];\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n}\n\n/**\n * Reorder hook items by providing a new order of item IDs\n * Used for manual queue management from dashboard\n */\nexport function reorderHookItemsSync(agentId: string, orderedItemIds: string[]): boolean {\n const hook = getHookSync(agentId);\n if (!hook) return false;\n\n // Validate that all provided IDs exist in the hook\n const existingIds = new Set(hook.items.map((item) => item.id));\n const providedIds = new Set(orderedItemIds);\n\n // Check if all provided IDs exist\n for (const id of orderedItemIds) {\n if (!existingIds.has(id)) {\n console.error(`[hooks] Cannot reorder: item ${id} not found in hook`);\n return false;\n }\n }\n\n // Check if all existing IDs are provided\n if (existingIds.size !== providedIds.size) {\n console.error(`[hooks] Cannot reorder: mismatch in item count (existing: ${existingIds.size}, provided: ${providedIds.size})`);\n return false;\n }\n\n // Build a map for quick lookup\n const itemMap = new Map(hook.items.map((item) => [item.id, item]));\n\n // Reorder items based on provided IDs\n hook.items = orderedItemIds.map((id) => itemMap.get(id)!);\n\n // Write back to file\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Send a message to an agent's mailbox\n */\nexport function sendMailSync(\n toAgentId: string,\n from: string,\n message: string,\n priority: HookItem['priority'] = 'normal'\n): void {\n initHookSync(toAgentId);\n const mailDir = getMailDir(toAgentId);\n\n const mailItem: HookItem = {\n id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n type: 'message',\n priority,\n source: from,\n payload: { message },\n createdAt: new Date().toISOString(),\n };\n\n writeFileSync(\n join(mailDir, `${mailItem.id}.json`),\n JSON.stringify(mailItem, null, 2)\n );\n}\n\n/**\n * Get and clear mail for an agent\n */\nexport function collectMailSync(agentId: string): HookItem[] {\n const mailDir = getMailDir(agentId);\n if (!existsSync(mailDir)) return [];\n\n const mails: HookItem[] = [];\n const files = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n\n for (const file of files) {\n const filePath = join(mailDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n mails.push(JSON.parse(content));\n unlinkSync(filePath); // Remove after reading\n } catch {\n // Skip invalid mail\n }\n }\n\n return mails;\n}\n\n/**\n * Generate Fixed Point prompt for agent startup\n */\nexport function generateFixedPointPromptSync(agentId: string): string | null {\n const { hasWork, urgentCount, items } = checkHookSync(agentId);\n\n if (!hasWork) return null;\n\n const lines: string[] = [\n '# FPP: Work Found on Your Hook',\n '',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n if (urgentCount > 0) {\n lines.push(`⚠️ **${urgentCount} URGENT item(s) require immediate attention**`);\n lines.push('');\n }\n\n lines.push(`## Pending Work Items (${items.length})`);\n lines.push('');\n\n for (const item of items) {\n const priorityEmoji = {\n urgent: '🔴',\n high: '🟠',\n normal: '🟢',\n low: '⚪',\n }[item.priority];\n\n lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);\n lines.push(`- Source: ${item.source}`);\n lines.push(`- Created: ${item.createdAt}`);\n\n if (item.payload.issueId) {\n lines.push(`- Issue: ${item.payload.issueId}`);\n }\n if (item.payload.message) {\n lines.push(`- Message: ${item.payload.message}`);\n }\n if (item.payload.action) {\n lines.push(`- Action: ${item.payload.action}`);\n }\n lines.push('');\n }\n\n lines.push('---');\n lines.push('');\n lines.push('Execute these items in priority order. Use `bd hook pop <id>` after completing each item.');\n\n return lines.join('\\n');\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n// Hook (FPP work-queue) IO — sync FS by design. Read paths use Effect.sync;\n// mutating paths surface FsError on disk failure.\n\n/** Initialize an empty hook for an agent (no-op if it exists). */\nexport const initHook = (agentId: string): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => initHookSync(agentId),\n catch: (cause) => new FsError({ path: agentId, operation: 'init-hook', cause }),\n });\n\n/** Load an agent's hook (null when missing). Pure-ish. */\nexport const getHook = (agentId: string): Effect.Effect<Hook | null> =>\n Effect.sync(() => getHookSync(agentId));\n\n/** Push a new item onto the agent's hook. */\nexport const pushToHook = (\n agentId: string,\n item: Omit<HookItem, 'id' | 'createdAt'>,\n): Effect.Effect<HookItem, FsError> =>\n Effect.try({\n try: () => pushToHookSync(agentId, item),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'push-to-hook', cause }),\n });\n\n/** Inspect an agent's hook for pending work. Pure-ish. */\nexport const checkHook = (\n agentId: string,\n): Effect.Effect<ReturnType<typeof checkHookSync>> => Effect.sync(() => checkHookSync(agentId));\n\n/** Pop a specific item from the hook by id. */\nexport const popFromHook = (\n agentId: string,\n itemId: string,\n): Effect.Effect<boolean, FsError> =>\n Effect.try({\n try: () => popFromHookSync(agentId, itemId),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'pop-from-hook', cause }),\n });\n\n/** Clear all items from the hook. */\nexport const clearHook = (agentId: string): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => clearHookSync(agentId),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'clear-hook', cause }),\n });\n\n/** Reorder hook items by id (best-effort, only existing ids). */\nexport const reorderHookItems = (\n agentId: string,\n orderedItemIds: string[],\n): Effect.Effect<boolean, FsError> =>\n Effect.try({\n try: () => reorderHookItemsSync(agentId, orderedItemIds),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'reorder-hook', cause }),\n });\n\n/** Send a mail item to an agent's mail drop (becomes hook item on collect). */\nexport const sendMail = (\n ...args: Parameters<typeof sendMailSync>\n): Effect.Effect<ReturnType<typeof sendMailSync>, FsError> =>\n Effect.try({\n try: () => sendMailSync(...args),\n catch: (cause) =>\n new FsError({ path: args[0], operation: 'send-mail', cause }),\n });\n\n/** Drain an agent's mail drop into hook items. Pure-ish. */\nexport const collectMail = (agentId: string): Effect.Effect<HookItem[]> =>\n Effect.sync(() => collectMailSync(agentId));\n\n/** Build the FPP prompt text for an agent's pending work. Pure-ish. */\nexport const generateFixedPointPrompt = (\n agentId: string,\n): Effect.Effect<string | null> => Effect.sync(() => generateFixedPointPromptSync(agentId));\n","import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { randomBytes } from 'node:crypto';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\nimport { FsError } from './errors.js';\n\nimport { getOverdeckHome } from './paths.js';\n\nexport const BRIDGE_TOKEN_HEADER = 'x-overdeck-bridge-token';\n\nfunction bridgeTokensDir(): string {\n return join(getOverdeckHome(), 'bridge-tokens');\n}\n\nexport function getBridgeTokenPath(agentId: string): string {\n return join(bridgeTokensDir(), `${agentId}.token`);\n}\n\nexport function readBridgeTokenSync(agentId: string): string | null {\n const path = getBridgeTokenPath(agentId);\n if (!existsSync(path)) return null;\n try {\n const value = readFileSync(path, 'utf8').trim();\n return value.length > 0 ? value : null;\n } catch {\n return null;\n }\n}\n\nexport function writeBridgeTokenSync(agentId: string): string {\n const dir = bridgeTokensDir();\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n const token = randomBytes(32).toString('hex');\n const path = getBridgeTokenPath(agentId);\n writeFileSync(path, `${token}\\n`, { mode: 0o600 });\n try {\n chmodSync(path, 0o600);\n } catch {\n // best effort\n }\n return token;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Read the bridge token for an agent. Returns `null` if no token exists.\n * Effect-native variant — never fails (errors are swallowed to null like the Promise version).\n */\nexport const readBridgeToken = (agentId: string): Effect.Effect<string | null> =>\n Effect.sync(() => readBridgeTokenSync(agentId));\n\n/**\n * Generate and persist a new bridge token. Returns the new token.\n * Effect-native variant — fails with FsError if the write cannot be persisted.\n */\nexport const writeBridgeToken = (agentId: string): Effect.Effect<string, FsError> =>\n Effect.try({\n try: () => writeBridgeTokenSync(agentId),\n catch: (cause) =>\n new FsError({\n path: getBridgeTokenPath(agentId),\n operation: 'writeBridgeToken',\n cause,\n }),\n });\n","import { createReadStream, existsSync } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { sessionFilePath } from './paths.js';\n\nconst DEFAULT_TAIL_BYTES = 512 * 1024;\n\nexport interface TranscriptUserRecordSnapshot {\n readonly sessionFile: string;\n readonly userRecordCount: number;\n readonly fileSize?: number;\n /** Byte offset from which this snapshot was parsed. */\n readonly rangeStartByte?: number;\n /** Byte offset after the last complete JSONL line observed by this snapshot. */\n readonly readOffset?: number;\n readonly lastUserRecord?: {\n readonly lineNumber: number;\n readonly timestamp?: string;\n readonly uuid?: string;\n };\n}\n\nasync function readFileRange(filePath: string, start: number, endExclusive: number): Promise<string> {\n if (endExclusive <= start) return '';\n\n return await new Promise<string>((resolve, reject) => {\n const stream = createReadStream(filePath, { start, end: endExclusive - 1, encoding: 'utf8' });\n let data = '';\n stream.on('data', chunk => { data += chunk; });\n stream.on('end', () => resolve(data));\n stream.on('error', reject);\n });\n}\n\nfunction dropLeadingPartialLine(content: string): string {\n const firstNewline = content.indexOf('\\n');\n if (firstNewline === -1) return '';\n return content.slice(firstNewline + 1);\n}\n\nfunction nextCompleteLineOffset(rangeStartByte: number, rawContent: string, fileSize: number): number {\n if (!rawContent) return fileSize;\n if (rawContent.endsWith('\\n')) return fileSize;\n\n const lastNewline = rawContent.lastIndexOf('\\n');\n if (lastNewline === -1) return rangeStartByte;\n return rangeStartByte + Buffer.byteLength(rawContent.slice(0, lastNewline + 1), 'utf8');\n}\n\nfunction isLandedUserRecord(entry: unknown): boolean {\n if (!entry || typeof entry !== 'object') return false;\n const record = entry as {\n type?: unknown;\n message?: { role?: unknown; content?: unknown };\n };\n if (record.type !== 'user' || record.message?.role !== 'user') return false;\n\n const content = record.message.content;\n if (typeof content === 'string') return content.trim().length > 0;\n if (!Array.isArray(content)) return false;\n\n return content.some((item: unknown) => {\n if (!item || typeof item !== 'object') return false;\n return (item as { type?: unknown }).type !== 'tool_result';\n });\n}\n\nfunction parseLandedUserRecords(content: string): Pick<TranscriptUserRecordSnapshot, 'userRecordCount' | 'lastUserRecord'> {\n let userRecordCount = 0;\n let lastUserRecord: TranscriptUserRecordSnapshot['lastUserRecord'];\n\n const lines = content.split('\\n');\n for (const [index, line] of lines.entries()) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line) as { timestamp?: unknown; uuid?: unknown };\n if (!isLandedUserRecord(entry)) continue;\n userRecordCount += 1;\n lastUserRecord = {\n lineNumber: index + 1,\n ...(typeof entry.timestamp === 'string' ? { timestamp: entry.timestamp } : {}),\n ...(typeof entry.uuid === 'string' ? { uuid: entry.uuid } : {}),\n };\n } catch {\n // Claude may be appending the JSONL while we read it. Ignore malformed\n // partial lines; the next poll sees the completed record.\n }\n }\n\n return { userRecordCount, ...(lastUserRecord ? { lastUserRecord } : {}) };\n}\n\nexport async function captureTranscriptUserRecordSnapshot(\n workspace: string,\n sessionId: string,\n options: { tailBytes?: number; fromByteOffset?: number } = {},\n): Promise<TranscriptUserRecordSnapshot> {\n const sessionFile = sessionFilePath(workspace, sessionId);\n if (!existsSync(sessionFile)) {\n return { sessionFile, userRecordCount: 0, fileSize: 0, rangeStartByte: 0, readOffset: 0 };\n }\n\n try {\n const fileStat = await stat(sessionFile);\n const fileSize = fileStat.size;\n const rangeStartByte = options.fromByteOffset === undefined\n ? Math.max(0, fileSize - (options.tailBytes ?? DEFAULT_TAIL_BYTES))\n : Math.min(Math.max(0, options.fromByteOffset), fileSize);\n const rawContent = await readFileRange(sessionFile, rangeStartByte, fileSize);\n const content = options.fromByteOffset === undefined && rangeStartByte > 0\n ? dropLeadingPartialLine(rawContent)\n : rawContent;\n const parsed = parseLandedUserRecords(content);\n\n return {\n sessionFile,\n ...parsed,\n fileSize,\n rangeStartByte,\n readOffset: nextCompleteLineOffset(rangeStartByte, rawContent, fileSize),\n };\n } catch {\n return { sessionFile, userRecordCount: 0, fileSize: 0, rangeStartByte: 0, readOffset: 0 };\n }\n}\n\nexport function hasNewTranscriptUserRecord(\n before: TranscriptUserRecordSnapshot,\n after: TranscriptUserRecordSnapshot,\n): boolean {\n if (before.sessionFile !== after.sessionFile) return after.userRecordCount > 0;\n\n if (\n before.readOffset !== undefined &&\n after.rangeStartByte !== undefined &&\n after.rangeStartByte === before.readOffset\n ) {\n return after.userRecordCount > 0;\n }\n\n return after.userRecordCount > before.userRecordCount;\n}\n\nexport interface TranscriptWatchProbe {\n /** A landed user record whose content contains the watched message text. */\n matchedUserRecord: boolean;\n /** compact_boundary records observed at/after the probe's start offset. */\n compactBoundaryCount: number;\n}\n\nconst MATCH_PREFIX_CHARS = 120;\n\nfunction normalizeForContentMatch(value: string): string {\n return value.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Harness-meta user records the matcher must never treat as the user's own\n * message landing. Compaction itself writes user-type records (the\n * continuation summary, `<command-name>`/`<local-command-*>` entries), and a\n * continuation summary can even quote the watched message — counting any of\n * these as a landing would mask exactly the eaten-by-compaction case the\n * probe exists to detect (PAN-1635 / PAN-1769).\n */\nconst META_USER_CONTENT_PREFIXES = [\n 'This session is being continued',\n '<command-name>',\n '<local-command-',\n 'Caveat: The messages below',\n];\n\nfunction userRecordText(entry: unknown): string | null {\n if (!entry || typeof entry !== 'object') return null;\n const record = entry as { type?: unknown; message?: { role?: unknown; content?: unknown } };\n if (record.type !== 'user' || record.message?.role !== 'user') return null;\n const content = record.message.content;\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return null;\n return content\n .filter((item): item is { type?: unknown; text?: unknown } => !!item && typeof item === 'object')\n .filter((item) => item.type === 'text' && typeof item.text === 'string')\n .map((item) => item.text as string)\n .join('\\n');\n}\n\nfunction isCompactBoundaryRecord(entry: unknown): boolean {\n if (!entry || typeof entry !== 'object') return false;\n const record = entry as { type?: unknown; subtype?: unknown };\n return record.type === 'system' && record.subtype === 'compact_boundary';\n}\n\n/**\n * Scan the transcript from `fromByteOffset` for (a) a user record carrying\n * `messageText` and (b) compact boundaries. Backs the eaten-by-compaction\n * watcher: a boundary appearing without the message means Claude Code's\n * submit-time compaction dropped the just-delivered prompt.\n */\nexport async function probeTranscriptSince(\n workspace: string,\n sessionId: string,\n fromByteOffset: number,\n messageText: string,\n): Promise<TranscriptWatchProbe> {\n const needle = normalizeForContentMatch(messageText).slice(0, MATCH_PREFIX_CHARS);\n const sessionFile = sessionFilePath(workspace, sessionId);\n if (!needle || !existsSync(sessionFile)) {\n return { matchedUserRecord: false, compactBoundaryCount: 0 };\n }\n\n try {\n const fileStat = await stat(sessionFile);\n const start = Math.min(Math.max(0, fromByteOffset), fileStat.size);\n const content = await readFileRange(sessionFile, start, fileStat.size);\n let matchedUserRecord = false;\n let compactBoundaryCount = 0;\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line) as unknown;\n if (isCompactBoundaryRecord(entry)) {\n compactBoundaryCount += 1;\n continue;\n }\n const text = userRecordText(entry);\n if (text === null) continue;\n if (META_USER_CONTENT_PREFIXES.some((prefix) => text.startsWith(prefix))) continue;\n if (normalizeForContentMatch(text).includes(needle)) matchedUserRecord = true;\n } catch {\n // Partial trailing line mid-append; the next probe sees it complete.\n }\n }\n return { matchedUserRecord, compactBoundaryCount };\n } catch {\n return { matchedUserRecord: false, compactBoundaryCount: 0 };\n }\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { request as httpRequest } from 'node:http';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { AgentState } from '../agents.js';\nimport {\n normalizeAgentId,\n getAgentState,\n saveAgentState,\n getAgentDir,\n waitForPromptReady,\n SESSION_EXITED_BEFORE_KICKOFF,\n} from '../agents.js';\nimport { getAgentRuntimeState } from './runtime-state.js';\nimport { isPaneDead, sendKeys, sessionExists } from '../tmux.js';\nimport { BRIDGE_TOKEN_HEADER, readBridgeTokenSync } from '../bridge-token.js';\nimport { PTY_TOKEN_HEADER, readPtyToken } from '../pty-token.js';\nimport {\n captureTranscriptUserRecordSnapshot,\n hasNewTranscriptUserRecord,\n type TranscriptUserRecordSnapshot,\n} from '../transcript-landing.js';\n\nexport type DeliveryResult = {\n ok: boolean;\n path: 'supervisor' | 'channels' | 'tmux' | 'pi' | 'codex';\n failure?: string;\n};\n\n/**\n * PAN-1988: resume / feedback / continue delivery must be RESILIENT. When an agent is pinned to\n * the strict 'supervisor' transport — which throws with NO fallback when its echo-confirmation\n * fails (the recurring \"input echo confirmation failed\" that left review feedback undelivered to\n * the work agent every round) — deliver via 'auto' instead, so a supervisor failure falls back to\n * the proven tmux paste-buffer and the message still lands. Other explicit methods\n * ('tmux'/'channels'/'auto') are preserved. The strict 'supervisor' contract itself (PAN-1769) is\n * intentionally left intact in deliverAgentMessage for callers that opt into it directly.\n */\nexport function resilientDeliveryMethod(\n method: 'auto' | 'supervisor' | 'channels' | 'tmux' | undefined,\n): 'auto' | 'supervisor' | 'channels' | 'tmux' | undefined {\n return method === 'supervisor' ? 'auto' : method;\n}\n\n/**\n * Resolve OVERDECK_HOME — same fallback semantics as overdeck-bridge.\n */\nfunction overdeckHomeForSockets(): string {\n return process.env.OVERDECK_HOME ?? join(homedir(), '.overdeck');\n}\n\nfunction overdeckHomeForChannels(): string {\n return overdeckHomeForSockets();\n}\n\n/**\n * Append a delivery-event log line to the per-agent bridge log. Best-effort.\n */\nasync function appendChannelDeliveryLog(\n agentId: string,\n entry: {\n path: 'supervisor' | 'channel' | 'tmux';\n reason?: string;\n caller?: string;\n 'pty-supervisor'?: string;\n channels?: string;\n },\n): Promise<void> {\n try {\n const home = overdeckHomeForSockets();\n const dir = join(home, 'logs');\n await (await import('fs/promises')).mkdir(dir, { recursive: true });\n const line = JSON.stringify({\n ts: new Date().toISOString(),\n agentId,\n ...entry,\n });\n await (await import('fs/promises')).appendFile(\n join(dir, `bridge-${agentId}.log`),\n `${line}\\n`,\n 'utf-8',\n );\n } catch {\n // Non-critical\n }\n}\n\n/**\n * POST a JSON body to a Unix-domain socket using node:net + a hand-rolled\n * minimal HTTP/1.1 request. Resolves on a 200-class response, rejects on any\n * error including socket-not-found, connection refused, write timeout, or\n * non-2xx status. Kept tiny on purpose: this is a hot path, only one caller,\n * and the whole point of a fallback to tmux is that we do not need a robust\n * HTTP client here.\n */\nasync function postUnixSocketJson(\n socketPath: string,\n body: unknown,\n timeoutMs: number,\n token: string,\n tokenHeader: string = BRIDGE_TOKEN_HEADER,\n): Promise<{ status: number; body: string }> {\n const payload = JSON.stringify(body);\n\n return new Promise((resolveCall, reject) => {\n // Settle exactly once. Without this guard a late idle-timeout or\n // post-response socket error could reject after the response already\n // resolved the promise.\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const clearClientTimeout = () => {\n if (timeout) clearTimeout(timeout);\n timeout = undefined;\n };\n const finishOk = (value: { status: number; body: string }) => {\n if (settled) return;\n settled = true;\n clearClientTimeout();\n resolveCall(value);\n };\n const finishErr = (err: Error) => {\n if (settled) return;\n settled = true;\n clearClientTimeout();\n reject(err);\n };\n\n const req = httpRequest(\n {\n socketPath,\n path: '/',\n method: 'POST',\n agent: false,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(payload),\n [tokenHeader]: token,\n },\n },\n (res) => {\n let responseBody = '';\n res.setEncoding('utf8');\n res.on('data', (chunk) => {\n responseBody += chunk;\n });\n res.on('end', () => {\n const status = res.statusCode ?? 0;\n if (status >= 200 && status < 300) {\n finishOk({ status, body: responseBody });\n return;\n }\n finishErr(new Error(`socket POST: status ${status}: ${responseBody.slice(0, 100)}`));\n });\n },\n );\n\n timeout = setTimeout(() => {\n req.destroy(new Error('socket POST timeout'));\n }, timeoutMs);\n timeout.unref?.();\n req.on('error', (err: Error) => {\n finishErr(err);\n });\n req.write(payload);\n req.end();\n });\n}\n\n/**\n * Single delivery primitive for orchestrator-to-work-agent messages. Auto mode\n * tries the PTY supervisor socket, then legacy Channels MCP, then tmux. Explicit\n * socket methods are strict and throw instead of falling back.\n */\nexport async function deliverAgentMessage(\n agentId: string,\n message: string,\n caller: string = 'unknown',\n deliveryMethod?: 'auto' | 'supervisor' | 'channels' | 'tmux',\n): Promise<DeliveryResult> {\n const normalizedId = normalizeAgentId(agentId);\n\n let channelsEnabled = false;\n let resolvedMethod = deliveryMethod;\n try {\n const state = await Effect.runPromise(getAgentState(normalizedId));\n channelsEnabled = Boolean(state?.channelsEnabled);\n resolvedMethod ??= state?.deliveryMethod ?? 'auto';\n } catch {\n resolvedMethod ??= 'auto';\n }\n\n if (resolvedMethod === 'tmux') {\n await assertTmuxTargetCanReceive(normalizedId, caller);\n await Effect.runPromise(sendKeys(normalizedId, message));\n return { ok: true, path: 'tmux' };\n }\n\n let supervisorFailure: string | undefined;\n if (resolvedMethod === 'auto' || resolvedMethod === 'supervisor') {\n const supervisorSocketPath = join(overdeckHomeForSockets(), 'sockets', `pty-${normalizedId}.sock`);\n const ptyToken = await readPtyToken(normalizedId);\n if (!existsSync(supervisorSocketPath)) {\n supervisorFailure = 'socket-missing';\n } else if (!ptyToken) {\n supervisorFailure = 'pty-token-missing';\n } else {\n try {\n // Must exceed the supervisor's worst-case echo-confirmation path\n // (2 attempts × 2.5s + 2 purges × 150ms ≈ 5.3s, pty-supervisor.ts).\n // A shorter client timeout abandons the POST mid-retry and fires the\n // tmux fallback while the supervisor is still writing — re-creating\n // the duplicate-submit race PAN-1769 fixed.\n await postUnixSocketJson(\n supervisorSocketPath,\n { content: message, meta: { caller } },\n 8_000,\n ptyToken,\n PTY_TOKEN_HEADER,\n );\n await appendChannelDeliveryLog(normalizedId, { path: 'supervisor', caller });\n return { ok: true, path: 'supervisor' };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n supervisorFailure = `socket-post-failed: ${reason}`;\n }\n }\n\n if (resolvedMethod === 'supervisor') {\n throw new Error(`MessageDeliveryFailed: PTY supervisor delivery failed for ${normalizedId} (${caller}): ${supervisorFailure}`);\n }\n }\n\n if (resolvedMethod === 'auto' || resolvedMethod === 'channels') {\n let channelFailure: string | undefined;\n const socketPath = join(overdeckHomeForSockets(), 'sockets', `agent-${normalizedId}.sock`);\n if (!channelsEnabled) {\n channelFailure = 'channels-disabled';\n } else if (!existsSync(socketPath)) {\n channelFailure = 'socket-missing';\n } else {\n const bridgeToken = readBridgeTokenSync(normalizedId);\n if (!bridgeToken) {\n channelFailure = 'bridge-token-missing';\n } else {\n try {\n await postUnixSocketJson(\n socketPath,\n { content: message, meta: { caller } },\n 2000,\n bridgeToken,\n );\n await appendChannelDeliveryLog(normalizedId, {\n path: 'channel',\n caller,\n ...(supervisorFailure ? { 'pty-supervisor': supervisorFailure } : {}),\n });\n return { ok: true, path: 'channels' };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n channelFailure = `socket-post-failed: ${reason}`;\n }\n }\n }\n\n if (resolvedMethod === 'channels') {\n throw new Error(`MessageDeliveryFailed: Channels delivery failed for ${normalizedId} (${caller}): ${channelFailure}`);\n }\n\n await appendChannelDeliveryLog(normalizedId, {\n path: 'tmux',\n reason: channelFailure,\n caller,\n ...(supervisorFailure ? { 'pty-supervisor': supervisorFailure } : {}),\n ...(channelFailure ? { channels: channelFailure } : {}),\n });\n await assertTmuxTargetCanReceive(normalizedId, caller);\n await Effect.runPromise(sendKeys(normalizedId, message));\n return { ok: true, path: 'tmux', failure: channelFailure ?? supervisorFailure };\n }\n\n await assertTmuxTargetCanReceive(normalizedId, caller);\n await Effect.runPromise(sendKeys(normalizedId, message));\n return { ok: true, path: 'tmux' };\n}\n\n/**\n * PAN-2228: loud failure semantics for the tmux tier. `tmux send-keys` into a\n * session whose pane process has EXITED (a remain-on-exit corpse) succeeds at the\n * tmux level while the message lands nowhere — the zombie-kickoff failure mode\n * (PAN-2179): callers saw ok:true, treated feedback as delivered, and the issue\n * silently stalled. Verify a live pane BEFORE pasting, and throw the same\n * MessageDeliveryFailed shape the strict socket methods use so every caller's\n * existing failure path fires instead of a false success. (A fully MISSING\n * session needs no check here — tmux send-keys itself errors loudly on it.)\n */\nasync function assertTmuxTargetCanReceive(normalizedId: string, caller: string): Promise<void> {\n const alive = await Effect.runPromise(sessionExists(normalizedId));\n if (!alive) return; // send-keys will fail loudly on its own for a missing session\n const paneDead = await Effect.runPromise(isPaneDead(normalizedId));\n if (paneDead) {\n throw new Error(`MessageDeliveryFailed: tmux delivery failed for ${normalizedId} (${caller}): pane-dead (session alive but the harness process has exited)`);\n }\n}\n\nconst RESUME_TRANSCRIPT_CONFIRM_TIMEOUT_MS = 3_000;\nconst RESUME_TRANSCRIPT_CONFIRM_INTERVAL_MS = 100;\n\nasync function waitForTranscriptUserRecordLanding(\n workspace: string,\n sessionId: string,\n before: TranscriptUserRecordSnapshot,\n snapshot: typeof captureTranscriptUserRecordSnapshot,\n timeoutMs = RESUME_TRANSCRIPT_CONFIRM_TIMEOUT_MS,\n intervalMs = RESUME_TRANSCRIPT_CONFIRM_INTERVAL_MS,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n const fromByteOffset = before.readOffset ?? before.fileSize;\n do {\n const after = await snapshot(workspace, sessionId, { fromByteOffset });\n if (hasNewTranscriptUserRecord(before, after)) return true;\n await new Promise(resolve => setTimeout(resolve, intervalMs));\n } while (Date.now() < deadline);\n\n const after = await snapshot(workspace, sessionId, { fromByteOffset });\n return hasNewTranscriptUserRecord(before, after);\n}\n\nexport async function deliverResumeMessageWithTranscriptConfirmation(args: {\n agentId: string;\n workspace: string;\n sessionId: string;\n message: string;\n caller: string;\n deliveryMethod?: 'auto' | 'supervisor' | 'channels' | 'tmux';\n timeoutMs?: number;\n intervalMs?: number;\n deliver?: typeof deliverAgentMessage;\n snapshot?: typeof captureTranscriptUserRecordSnapshot;\n}): Promise<{ delivered: boolean; attempts: number; lastDelivery?: DeliveryResult }> {\n const snapshot = args.snapshot ?? captureTranscriptUserRecordSnapshot;\n const deliver = args.deliver ?? deliverAgentMessage;\n const before = await snapshot(args.workspace, args.sessionId);\n let lastDelivery: DeliveryResult | undefined;\n\n for (let attempt = 1; attempt <= 2; attempt += 1) {\n lastDelivery = await deliver(args.agentId, args.message, args.caller, args.deliveryMethod);\n if (lastDelivery.ok && await waitForTranscriptUserRecordLanding(\n args.workspace,\n args.sessionId,\n before,\n snapshot,\n args.timeoutMs,\n args.intervalMs,\n )) {\n return { delivered: true, attempts: attempt, lastDelivery };\n }\n if (attempt < 2) {\n console.warn(`[resumeAgent] Auto-continue prompt did not land in ${args.sessionId}; redelivering once.`);\n }\n }\n\n return { delivered: false, attempts: 2, ...(lastDelivery ? { lastDelivery } : {}) };\n}\n\nexport async function deliverInitialPromptWithRetry(\n agentId: string,\n prompt: string,\n caller: string,\n deliveryMethod?: 'auto' | 'supervisor' | 'channels' | 'tmux',\n options: {\n timeoutMs?: number;\n intervalMs?: number;\n settleDelayMs?: number;\n deliver?: typeof deliverAgentMessage;\n snapshot?: typeof captureTranscriptUserRecordSnapshot;\n getState?: (agentId: string) => Promise<AgentState | null>;\n waitForReady?: typeof waitForPromptReady;\n sessionExists?: (agentId: string) => Promise<boolean>;\n } = {},\n): Promise<DeliveryResult> {\n const normalizedId = normalizeAgentId(agentId);\n const deliver = options.deliver ?? deliverAgentMessage;\n const snapshot = options.snapshot ?? captureTranscriptUserRecordSnapshot;\n const getState = options.getState ?? (async (id: string) => {\n try {\n return await Effect.runPromise(getAgentState(normalizeAgentId(id)));\n } catch {\n return null;\n }\n });\n const waitForReady = options.waitForReady ?? waitForPromptReady;\n const sessionExistsForAgent = options.sessionExists ?? (async (id: string) => (\n Effect.runPromise(sessionExists(normalizeAgentId(id)))\n ));\n\n function promptReadyTimeoutSeconds(): number {\n const raw = process.env.OVERDECK_PROMPT_READY_TIMEOUT_SECONDS;\n if (!raw) return 30;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 30;\n }\n\n async function resolveTranscriptConfirmationTarget(\n state: AgentState | null,\n ): Promise<{ workspace: string; sessionId: string; before: TranscriptUserRecordSnapshot } | null> {\n if (!state?.workspace || getHarnessBehavior(state.harness).transcriptKind !== 'claude-jsonl') {\n return null;\n }\n\n let sessionId = state.sessionId;\n if (!sessionId) {\n try {\n sessionId = (await Effect.runPromise(getAgentRuntimeState(normalizedId)))?.claudeSessionId;\n } catch {\n sessionId = undefined;\n }\n }\n if (!sessionId) return null;\n\n return {\n workspace: state.workspace,\n sessionId,\n before: await snapshot(state.workspace, sessionId),\n };\n }\n\n // PAN-1803: the codex TUI mangles a large pasted kickoff prompt — a multi-\n // thousand-character paste garbles its input and trips its \"Create a plan?\"\n // mode hint, so the agent never executes. Write the full brief to a file and\n // deliver a SHORT pointer instead (robust regardless of transport — the same\n // pattern that makes file-backed handoffs reliable). Only codex needs this;\n // claude-code/pi line-based input handle the full prompt fine.\n //\n // PAN-2330: keep the workspace-level .pan/kickoff.md owned by the work/strike\n // agent. Review, test, planning, flywheel, and other role prompts can share\n // the same workspace; writing those prompts to .pan/kickoff.md overwrites the\n // work agent's brief and a later resume can make it read the wrong task.\n let deliveredPrompt = prompt;\n let state = await getState(agentId);\n try {\n if (state?.harness && getHarnessBehavior(state.harness).usesCodexHome) {\n const ownsWorkspaceKickoff = state.workspace && (state.role === 'work' || state.role === 'strike');\n const kickoffPath = ownsWorkspaceKickoff\n ? join(state.workspace, '.pan', 'kickoff.md')\n : join(getAgentDir(normalizedId), 'kickoff.md');\n const displayPath = ownsWorkspaceKickoff ? '`.pan/kickoff.md`' : `\\`${kickoffPath}\\``;\n mkdirSync(dirname(kickoffPath), { recursive: true });\n writeFileSync(kickoffPath, prompt, 'utf-8');\n deliveredPrompt =\n `Your complete task brief has been written to ${displayPath}. `\n + 'Read that file in full now and execute it exactly — it is your full set of work '\n + 'instructions. Begin immediately and keep working autonomously until done; do not '\n + 'wait for further input.';\n }\n } catch {\n // Non-fatal: fall back to delivering the full prompt inline.\n }\n\n let lastFailure = 'not-attempted';\n for (let attempt = 1; attempt <= 2; attempt += 1) {\n let harness: RuntimeName | undefined;\n try {\n state = await getState(agentId);\n harness = state?.harness;\n } catch {\n harness = undefined;\n }\n const readyTimeoutSeconds = promptReadyTimeoutSeconds();\n const ready = await waitForReady(agentId, harness, readyTimeoutSeconds);\n if (!ready) {\n const alive = await sessionExistsForAgent(agentId);\n lastFailure = alive ? 'ready-signal-timeout' : SESSION_EXITED_BEFORE_KICKOFF;\n const displayName = getHarnessBehavior(harness).displayName;\n console.error(`[${agentId}] ${displayName} did not become ready within ${readyTimeoutSeconds}s (kickoff attempt ${attempt}/2)`);\n if (!alive) break;\n continue;\n }\n\n const settleDelayMs = options.settleDelayMs ?? 500;\n if (settleDelayMs > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, settleDelayMs));\n }\n try {\n const confirmationTarget = await resolveTranscriptConfirmationTarget(state);\n const result = await deliver(agentId, deliveredPrompt, caller, deliveryMethod);\n if (result.ok) {\n if (!confirmationTarget) return result;\n const confirmed = await waitForTranscriptUserRecordLanding(\n confirmationTarget.workspace,\n confirmationTarget.sessionId,\n confirmationTarget.before,\n snapshot,\n options.timeoutMs,\n options.intervalMs,\n );\n if (confirmed) return result;\n lastFailure = 'kickoff-not-confirmed';\n } else {\n lastFailure = result.failure ?? `delivery returned ok=false via ${result.path}`;\n }\n } catch (err) {\n lastFailure = err instanceof Error ? err.message : String(err);\n }\n console.error(`[${agentId}] Kickoff delivery attempt ${attempt}/2 failed: ${lastFailure}`);\n }\n\n return { ok: false, path: 'tmux', failure: lastFailure };\n}\n\nexport async function deliverAgentPermissionDecision(\n agentId: string,\n requestId: string,\n behavior: 'allow' | 'deny',\n): Promise<void> {\n const normalizedId = normalizeAgentId(agentId);\n\n let state: AgentState | null = null;\n try {\n state = await Effect.runPromise(getAgentState(normalizedId));\n } catch {\n state = null;\n }\n\n if (!state?.channelsEnabled) {\n throw new Error(`agent ${normalizedId} is not using Claude channels`);\n }\n\n const socketPath = join(overdeckHomeForChannels(), 'sockets', `agent-${normalizedId}.sock`);\n if (!existsSync(socketPath)) {\n throw new Error(`bridge socket missing for ${normalizedId}`);\n }\n\n const bridgeToken = readBridgeTokenSync(normalizedId);\n if (!bridgeToken) {\n throw new Error(`bridge token missing for ${normalizedId}`);\n }\n\n await postUnixSocketJson(\n socketPath,\n {\n type: 'permission_response',\n requestId,\n behavior,\n },\n 2000,\n bridgeToken,\n );\n\n await appendChannelDeliveryLog(normalizedId, {\n path: 'channel',\n caller: `permission-response:${requestId}:${behavior}`,\n });\n}\n\n/** Update just the delivery method on an agent's state file. */\nexport async function setAgentDeliveryMethod(\n agentId: string,\n deliveryMethod: 'auto' | 'supervisor' | 'channels' | 'tmux',\n): Promise<void> {\n const state = await Effect.runPromise(getAgentState(agentId));\n if (!state) return;\n state.deliveryMethod = deliveryMethod;\n await Effect.runPromise(saveAgentState(state));\n}\n","/**\n * Cloister Configuration\n *\n * Loads and manages Cloister configuration from ~/.overdeck/cloister.toml\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { mkdir, readFile, writeFile } from 'fs/promises';\nimport { parse, stringify } from '@iarna/toml';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { ConfigError, FsError } from '../errors.js';\nimport { OVERDECK_HOME } from '../paths.js';\nimport type { TieredExecutionConfig } from '../agents/tier-table.js';\n\nconst CLOISTER_CONFIG_FILE = join(OVERDECK_HOME, 'cloister.toml');\n\n/**\n * Health threshold configuration (in minutes)\n */\nexport interface HealthThresholds {\n stale: number;\n warning: number;\n stuck: number;\n}\n\n/**\n * Automatic action configuration\n */\nexport interface AutoActions {\n poke_on_warning: boolean;\n poke_on_stuck: boolean; // Poke agents idle > stuck threshold (default: true)\n kill_on_stuck: boolean;\n restart_on_kill: boolean;\n /** Minimum ms between pokes for the same agent. Prevents spam on repeated health checks. Default: 30 min */\n poke_cooldown_ms: number;\n}\n\nexport interface StuckRemediationConfig {\n enabled: boolean;\n stage1_minutes: number;\n stage2_minutes: number;\n stage3_minutes: number;\n flywheel_stage1_minutes: number;\n flywheel_stage2_minutes: number;\n flywheel_stage3_minutes: number;\n}\n\n/**\n * Monitoring configuration\n */\nexport interface MonitoringConfig {\n check_interval: number; // seconds between health checks\n heartbeat_sources: ('jsonl_mtime' | 'tmux_activity' | 'git_activity' | 'active_heartbeat')[];\n}\n\n/**\n * Concurrency limits for deacon auto-resume / auto-dispatch (PAN-1665).\n *\n * These are *gates on starting new work* — the deacon never kills a running agent\n * to satisfy them. If the system is already over a limit (e.g. after a forced\n * `pan start`, or a backlog at unfreeze), the deacon simply stops resuming/\n * dispatching until natural attrition brings the count down. To forcibly trim an\n * over-limit system back to the cap, the operator uses the explicit emergency\n * brake — it is never automatic.\n */\nexport interface ConcurrencyConfig {\n /** Max concurrently-running *work* agents the deacon will resume/spawn up to. */\n max_work_agents: number;\n /**\n * Slots kept free above `max_work_agents` for advancing roles (review/test/ship)\n * so the pipeline can always drain even when work is at its cap. The overall\n * ceiling for any auto-dispatch is `max_work_agents + reserved_advancing_slots`.\n */\n reserved_advancing_slots: number;\n /**\n * Dedicated swarm-slot reserve, isolated from `max_work_agents` and\n * `reserved_advancing_slots` (PAN-2212). Swarm slot dispatch draws only from this\n * reserve, so a busy pipeline never starves the swarm — and swarm slots never\n * starve review/test in reverse. Default 3.\n */\n reserved_swarm_slots: number;\n /**\n * When true, operator-started work agents (no flywheelRunId) are exempt from\n * the emergency brake/governor reaping so the operator's deliberate spawns are\n * not trimmed to satisfy the cap. Defaults to true (PAN-1812).\n */\n exempt_operator_started?: boolean;\n /**\n * PAN-2504: memory-DRIVEN work-agent admission. When true, the work-agent\n * ceiling is derived from live free memory — admit until the next agent's\n * estimated footprint would breach the governor's SOFT reserve — instead of\n * the fixed `max_work_agents` count. This lets a big box fill toward its\n * memory budget rather than idling at a hardcoded 6. Defaults to false\n * (opt-in): unset installs keep the count-based cap. Requires the memory\n * governor to have published a cached verdict; falls back to the count cap\n * until then.\n */\n memory_driven?: boolean;\n /**\n * Safety upper bound on concurrent work agents when `memory_driven` is on —\n * caps the memory-derived ceiling so a mis-estimated footprint can't march the\n * box to hundreds of agents. Default 24.\n */\n memory_driven_max_work_agents?: number;\n /**\n * Per-work-agent RSS estimate (GB) used for the memory-driven budget:\n * additional slots = floor((availableBytes - softReserve) / work_footprint_gb).\n * Conservative by design (over-estimating admits fewer). Default 2, matching\n * `resources.governorFootprintDefaultWorkGb`.\n */\n work_footprint_gb?: number;\n /**\n * PAN-2507: preemptive pipeline scheduler. When true, a blocked advancing\n * (review/test/ship) dispatch may YIELD an idle work agent — pause it\n * (resumable, session preserved) to free the slot/memory, dispatch the\n * advancing role, and auto-resume the yielded agent oldest-first once\n * capacity returns. Defaults to false (opt-in): unset installs keep the\n * static defer-until-attrition behavior at every dispatch site.\n */\n preemption?: boolean;\n /**\n * PAN-2507: anti-thrash cap on how many work agents may be in the yielded\n * (scheduler-paused) state at once. Default 3.\n */\n max_yielded?: number;\n /**\n * PAN-2507: anti-thrash cooldown (seconds) — an agent resumed from a yield\n * may not be re-yielded until this many seconds have elapsed since its\n * resume. Default 600.\n */\n yield_cooldown_secs?: number;\n}\n\nexport type SwarmInferCompletionMode = 'off' | 'nudge' | 'auto';\n\nexport interface SwarmConfig {\n infer_completion: SwarmInferCompletionMode;\n}\n\n/**\n * Startup configuration\n */\nexport interface StartupConfig {\n auto_start: boolean; // Start Cloister when dashboard starts\n reconciliation_grace_secs: number; // Boot reconciliation grace window before default resume\n reconciliation_max_candidate_age_secs: number; // Max age for stopped agents to appear in boot reconciliation\n}\n\n/**\n * Notification configuration (future feature)\n */\nexport interface NotificationConfig {\n slack_webhook?: string;\n email?: string;\n}\n\n/**\n * Specialist agent configuration\n */\nexport interface SpecialistConfig {\n enabled: boolean;\n auto_wake: boolean;\n}\n\n/**\n * Test agent specific configuration\n */\nexport interface TestAgentConfig extends SpecialistConfig {\n test_command?: string; // Optional test command override (e.g., \"npm test\", \"pytest\", etc.)\n}\n\n/**\n * Configuration for a single reviewer agent in the parallel review flow.\n */\nexport interface ReviewAgentConfig {\n /** Unique name / role identifier (e.g. 'correctness', 'security', 'performance') */\n name: string;\n /** Optional model override (e.g. 'claude-opus-4-6'). Falls back to work-type routing. */\n model?: string;\n /** Focus areas for this reviewer (informational, passed as context) */\n focus?: string[];\n /** Set to false to skip this reviewer. Defaults to true. */\n enabled?: boolean;\n}\n\n/**\n * All specialist agents configuration\n */\nexport interface SpecialistsConfig {\n merge_agent?: SpecialistConfig;\n review_agent?: SpecialistConfig;\n test_agent?: TestAgentConfig;\n inspect_agent?: SpecialistConfig;\n uat_agent?: SpecialistConfig;\n /** User-configurable list of parallel reviewer agents. Absent ⇒ 3 built-in defaults. */\n review_agents?: ReviewAgentConfig[];\n}\n\n/**\n * Model selection configuration\n */\nexport interface ModelSelectionConfig {\n default_model: 'opus' | 'sonnet' | 'haiku';\n complexity_routing: {\n trivial: 'opus' | 'sonnet' | 'haiku';\n simple: 'opus' | 'sonnet' | 'haiku';\n medium: 'opus' | 'sonnet' | 'haiku';\n complex: 'opus' | 'sonnet' | 'haiku';\n expert: 'opus' | 'sonnet' | 'haiku';\n };\n specialist_models: {\n merge_agent?: 'opus' | 'sonnet' | 'haiku';\n review_agent?: 'opus' | 'sonnet' | 'haiku';\n test_agent?: 'opus' | 'sonnet' | 'haiku';\n inspect_agent?: 'opus' | 'sonnet' | 'haiku';\n uat_agent?: 'opus' | 'sonnet' | 'haiku';\n };\n /**\n * PAN-636 legacy coding-agent harness override. PAN-1787 treats this as a\n * deprecated alias for roles.<role>.harness; roles.<role>.harness wins when\n * both are set. Absent keys are normal for older config files.\n */\n specialist_harnesses?: {\n merge_agent?: 'claude-code' | 'pi';\n review_agent?: 'claude-code' | 'pi';\n test_agent?: 'claude-code' | 'pi';\n inspect_agent?: 'claude-code' | 'pi';\n uat_agent?: 'claude-code' | 'pi';\n };\n}\n\n/**\n * Handoff trigger configuration\n */\nexport interface HandoffTriggersConfig {\n stuck_escalation?: {\n enabled: boolean;\n haiku_to_sonnet_minutes: number;\n sonnet_to_opus_minutes: number;\n };\n test_failure?: {\n enabled: boolean;\n from_model: 'opus' | 'sonnet' | 'haiku';\n to_model: 'opus' | 'sonnet' | 'haiku';\n trigger_on: 'any_failure' | '2_consecutive';\n };\n implementation_complete?: {\n enabled: boolean;\n to_specialist: string; // e.g., 'test-agent'\n };\n}\n\n/**\n * Handoff configuration\n */\nexport interface HandoffConfig {\n auto_triggers: HandoffTriggersConfig;\n}\n\n/**\n * Cost tracking configuration\n */\nexport interface CostTrackingConfig {\n display_enabled: boolean;\n log_to_jsonl: boolean;\n}\n\n/**\n * Auto-restart configuration\n */\nexport interface AutoRestartConfig {\n enabled: boolean;\n max_retries: number;\n backoff_seconds: number[]; // Array of backoff delays (e.g., [30, 60, 120])\n}\n\n/**\n * Cost limits configuration\n */\nexport interface CostLimitsConfig {\n per_agent_usd: number;\n per_issue_usd: number;\n daily_total_usd: number;\n alert_threshold: number; // Fraction (0.0-1.0) at which to start alerting\n}\n\n/**\n * Retention policy configuration\n */\nexport interface RetentionConfig {\n /**\n * Days to keep work/planning agent state dirs after their session ends.\n * Default 7. Event-driven cleanup (postMergeLifecycle, executeCloseOut)\n * deletes state at the moment it becomes useless; this retention is only\n * a safety net for cases where those events didn't fire.\n */\n agent_state_days: number;\n /**\n * Days to keep reviewer state dirs (review-* prefix). Default 1.\n * runParallelReview Phase 6 deletes reviewer state immediately after the\n * review posts; this retention catches crashed-mid-cleanup edge cases.\n */\n reviewer_state_days?: number;\n health_staleness_hours: number; // Hours before hiding stale agents in health API (default: 24)\n}\n\nexport interface CloseOutConfig {\n remove_workspace: boolean;\n delete_feature_branch: boolean;\n auto: boolean;\n auto_delay_minutes: number;\n}\n\nexport interface OrphanProposedReconcilerConfig {\n enabled: boolean;\n minAttemptIntervalMs: number;\n}\n\n/**\n * Complete Cloister configuration\n */\nexport interface CloisterConfig {\n startup: StartupConfig;\n thresholds: HealthThresholds;\n auto_actions: AutoActions;\n stuck_remediation?: StuckRemediationConfig;\n monitoring: MonitoringConfig;\n concurrency?: ConcurrencyConfig;\n swarm?: SwarmConfig;\n notifications?: NotificationConfig;\n specialists?: SpecialistsConfig;\n model_selection?: ModelSelectionConfig;\n tiered_execution?: TieredExecutionConfig;\n handoffs?: HandoffConfig;\n cost_tracking?: CostTrackingConfig;\n auto_restart?: AutoRestartConfig;\n cost_limits?: CostLimitsConfig;\n retention?: RetentionConfig;\n close_out?: CloseOutConfig;\n orphanProposedReconciler?: OrphanProposedReconcilerConfig;\n}\n\n/**\n * Default Cloister configuration\n */\nexport const DEFAULT_CLOISTER_CONFIG: CloisterConfig = {\n startup: {\n auto_start: true,\n reconciliation_grace_secs: 120,\n reconciliation_max_candidate_age_secs: 240,\n },\n thresholds: {\n stale: 5,\n warning: 15,\n stuck: 30,\n },\n auto_actions: {\n poke_on_warning: true,\n poke_on_stuck: true, // Poke agents that have been idle > stuck threshold\n kill_on_stuck: false, // Manual by default for safety\n restart_on_kill: false,\n poke_cooldown_ms: 30 * 60 * 1000, // 30 min between pokes for the same agent\n },\n stuck_remediation: {\n enabled: true,\n stage1_minutes: 20,\n stage2_minutes: 45,\n stage3_minutes: 90,\n flywheel_stage1_minutes: 20,\n flywheel_stage2_minutes: 24,\n flywheel_stage3_minutes: 28,\n },\n monitoring: {\n check_interval: 60, // 1 minute\n heartbeat_sources: ['jsonl_mtime', 'tmux_activity', 'git_activity'],\n },\n concurrency: {\n max_work_agents: 6,\n reserved_advancing_slots: 3,\n reserved_swarm_slots: 3,\n exempt_operator_started: true,\n memory_driven: false,\n memory_driven_max_work_agents: 24,\n work_footprint_gb: 2,\n preemption: false,\n max_yielded: 3,\n yield_cooldown_secs: 600,\n },\n swarm: {\n infer_completion: 'auto',\n },\n notifications: {\n slack_webhook: undefined,\n email: undefined,\n },\n specialists: {\n merge_agent: {\n enabled: true,\n auto_wake: false, // Only wake on explicit \"Approve & Merge\" click\n },\n review_agent: {\n enabled: true,\n auto_wake: false, // Woken by the verification pipeline, not ad hoc polling\n },\n test_agent: {\n enabled: true,\n auto_wake: true,\n },\n inspect_agent: {\n enabled: true,\n auto_wake: false, // Triggered explicitly per bead via pan inspect\n },\n uat_agent: {\n enabled: true,\n auto_wake: true,\n },\n },\n model_selection: {\n default_model: 'sonnet',\n complexity_routing: {\n trivial: 'haiku',\n simple: 'haiku',\n medium: 'sonnet',\n complex: 'sonnet',\n expert: 'opus',\n },\n specialist_models: {\n // PAN-754: no hardcoded defaults. User config.yaml role settings are authoritative.\n // Resolution falls through to role model config, then to the global fallback model.\n },\n specialist_harnesses: {\n // PAN-1787/PAN-1842: removed. Harness resolution for all spawn paths\n // now flows through resolveHarness(), which consults explicit, role,\n // providerHarnesses, and built-in provider defaults in order.\n },\n },\n tiered_execution: {\n enabled: false,\n tiers: {},\n supervisor: undefined,\n replay_threshold: 0.5,\n },\n handoffs: {\n auto_triggers: {\n stuck_escalation: {\n enabled: true,\n haiku_to_sonnet_minutes: 10,\n sonnet_to_opus_minutes: 20,\n },\n test_failure: {\n enabled: true,\n from_model: 'haiku',\n to_model: 'sonnet',\n trigger_on: 'any_failure',\n },\n implementation_complete: {\n enabled: true, // Auto-handoff to test-agent when implementation done\n to_specialist: 'test-agent',\n },\n },\n },\n cost_tracking: {\n display_enabled: true,\n log_to_jsonl: true,\n },\n auto_restart: {\n enabled: true,\n max_retries: 3,\n backoff_seconds: [30, 60, 120], // 30s, 1m, 2m\n },\n cost_limits: {\n per_agent_usd: 10.0,\n per_issue_usd: 25.0,\n daily_total_usd: 100.0,\n alert_threshold: 0.8, // Alert at 80%\n },\n retention: {\n agent_state_days: 7,\n reviewer_state_days: 1,\n health_staleness_hours: 24,\n },\n close_out: {\n remove_workspace: false,\n delete_feature_branch: false,\n auto: false,\n auto_delay_minutes: 60,\n },\n orphanProposedReconciler: {\n enabled: true,\n minAttemptIntervalMs: 5 * 60 * 1000,\n },\n};\n\n/**\n * Deep merge utility that recursively merges objects.\n * - Recursively merges nested objects\n * - Arrays in overrides replace defaults (not concatenated)\n * - User values take precedence over defaults\n */\nfunction deepMerge<T extends object>(defaults: T, overrides: Partial<T>): T {\n const result = { ...defaults };\n\n for (const key of Object.keys(overrides) as (keyof T)[]) {\n const defaultVal = defaults[key];\n const overrideVal = overrides[key];\n\n // Skip undefined values in overrides\n if (overrideVal === undefined) continue;\n\n // Deep merge if both values are non-array objects\n if (\n typeof defaultVal === 'object' &&\n defaultVal !== null &&\n !Array.isArray(defaultVal) &&\n typeof overrideVal === 'object' &&\n overrideVal !== null &&\n !Array.isArray(overrideVal)\n ) {\n result[key] = deepMerge(defaultVal as any, overrideVal as any);\n } else {\n // Direct override for primitives and arrays\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Load Cloister configuration\n *\n * Reads from ~/.overdeck/cloister.toml and merges with defaults.\n * Creates default config file if it doesn't exist.\n */\nexport function loadCloisterConfigSync(): CloisterConfig {\n // Ensure overdeck home exists\n if (!existsSync(OVERDECK_HOME)) {\n mkdirSync(OVERDECK_HOME, { recursive: true });\n }\n\n let config = DEFAULT_CLOISTER_CONFIG;\n\n // If config file doesn't exist, create it with defaults\n if (!existsSync(CLOISTER_CONFIG_FILE)) {\n saveCloisterConfigSync(DEFAULT_CLOISTER_CONFIG);\n } else {\n try {\n const content = readFileSync(CLOISTER_CONFIG_FILE, 'utf-8');\n const parsed = parse(content) as unknown as Partial<CloisterConfig>;\n\n // Deep merge with defaults\n config = deepMerge(DEFAULT_CLOISTER_CONFIG, parsed);\n } catch (error) {\n console.error('Failed to load Cloister config:', error);\n console.error('Using default configuration');\n config = DEFAULT_CLOISTER_CONFIG;\n }\n }\n\n return config;\n}\n\n\n/**\n * Save Cloister configuration\n *\n * Writes configuration to ~/.overdeck/cloister.toml\n */\nexport function saveCloisterConfigSync(config: CloisterConfig): void {\n // Ensure overdeck home exists\n if (!existsSync(OVERDECK_HOME)) {\n mkdirSync(OVERDECK_HOME, { recursive: true });\n }\n\n try {\n const content = stringify(config as any);\n writeFileSync(CLOISTER_CONFIG_FILE, content, 'utf-8');\n } catch (error) {\n console.error('Failed to save Cloister config:', error);\n throw error;\n }\n}\n\n/**\n * Update Cloister configuration\n *\n * Merges partial config updates with existing config.\n */\nexport function updateCloisterConfigSync(updates: Partial<CloisterConfig>): CloisterConfig {\n const current = loadCloisterConfigSync();\n const updated = deepMerge(current, updates);\n saveCloisterConfigSync(updated);\n return updated;\n}\n\n/**\n * Get the path to the Cloister config file\n */\nexport function getCloisterConfigPath(): string {\n return CLOISTER_CONFIG_FILE;\n}\n\n/**\n * Check if Cloister should auto-start\n */\nexport function shouldAutoStart(): boolean {\n const config = loadCloisterConfigSync();\n return config.startup.auto_start;\n}\n\n/**\n * Get health thresholds in milliseconds\n */\nexport function getHealthThresholdsMs(): {\n stale: number;\n warning: number;\n stuck: number;\n} {\n const config = loadCloisterConfigSync();\n return {\n stale: config.thresholds.stale * 60 * 1000,\n warning: config.thresholds.warning * 60 * 1000,\n stuck: config.thresholds.stuck * 60 * 1000,\n };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n//\n// Additive Effect-channel variants of the config helpers above. The sync\n// variants are preserved so existing callers (CLI scripts, top-level module\n// initialization) do not have to migrate; new Effect-based callers can compose\n// these directly without `Effect.runSync` round-tripping.\n\n/** Effect variant of `loadCloisterConfig`. Falls back to defaults on read/parse failures. */\nexport const loadCloisterConfig = (): Effect.Effect<CloisterConfig, FsError | ConfigError> =>\n Effect.gen(function* () {\n yield* Effect.tryPromise({\n try: () => mkdir(OVERDECK_HOME, { recursive: true }),\n catch: (cause) => new FsError({ path: OVERDECK_HOME, operation: 'mkdir', cause }),\n });\n\n let config: CloisterConfig = DEFAULT_CLOISTER_CONFIG;\n\n if (!existsSync(CLOISTER_CONFIG_FILE)) {\n yield* saveCloisterConfig(DEFAULT_CLOISTER_CONFIG);\n } else {\n const content: string | null = yield* Effect.tryPromise({\n try: () => readFile(CLOISTER_CONFIG_FILE, 'utf-8'),\n catch: (cause) => new FsError({ path: CLOISTER_CONFIG_FILE, operation: 'readFile', cause }),\n }).pipe(\n Effect.catch((err: FsError) => {\n console.error('Failed to load Cloister config:', err);\n console.error('Using default configuration');\n return Effect.succeed<string | null>(null);\n }),\n );\n\n if (content !== null) {\n try {\n const parsed = parse(content) as unknown as Partial<CloisterConfig>;\n config = deepMerge(DEFAULT_CLOISTER_CONFIG, parsed);\n } catch (error) {\n console.error('Failed to parse Cloister config:', error);\n console.error('Using default configuration');\n config = DEFAULT_CLOISTER_CONFIG;\n }\n }\n }\n\n return config;\n });\n\n/** Effect variant of `saveCloisterConfig`. */\nexport const saveCloisterConfig = (config: CloisterConfig): Effect.Effect<void, FsError | ConfigError> =>\n Effect.gen(function* () {\n yield* Effect.tryPromise({\n try: () => mkdir(OVERDECK_HOME, { recursive: true }),\n catch: (cause) => new FsError({ path: OVERDECK_HOME, operation: 'mkdir', cause }),\n });\n\n const content = yield* Effect.try({\n try: () => stringify(config as any),\n catch: (cause) => new ConfigError({ message: 'Failed to serialize Cloister config', cause }),\n });\n\n yield* Effect.tryPromise({\n try: () => writeFile(CLOISTER_CONFIG_FILE, content, 'utf-8'),\n catch: (cause) => new FsError({ path: CLOISTER_CONFIG_FILE, operation: 'writeFile', cause }),\n });\n });\n\n/** Effect variant of `updateCloisterConfig`. */\nexport const updateCloisterConfig = (\n updates: Partial<CloisterConfig>,\n): Effect.Effect<CloisterConfig, FsError | ConfigError> =>\n Effect.gen(function* () {\n const current = yield* loadCloisterConfig();\n const updated = deepMerge(current, updates);\n yield* saveCloisterConfig(updated);\n return updated;\n });\n","/**\n * PAN-2500: the cached MemoryVerdict, split out from memory-governor.ts into\n * its own zero-dependency module. concurrency.ts (canDispatchAdvancing /\n * tryReserveAdvancingSlot) needs to read this synchronously without importing\n * memory-governor.ts directly — memory-governor.ts pulls in\n * dashboard/server/routes/resources/stacks.ts, which transitively reaches\n * concurrency.ts itself (stacks.ts -> review-status.ts -> agents.ts ->\n * agents/spawn.ts -> concurrency.ts), so a direct concurrency.ts ->\n * memory-governor.ts import closes a real cycle (caught by\n * scripts/lint-circular-deps.sh). This module has no imports of its own, so\n * it cannot be part of any cycle.\n */\n\nexport type MemoryPressureBand = 'ok' | 'soft' | 'hard';\n\nexport interface MemoryPressureThresholds {\n warningBytes: number;\n criticalBytes: number;\n}\n\nexport interface MemoryVerdict {\n band: MemoryPressureBand;\n availableBytes: number;\n thresholds: MemoryPressureThresholds;\n}\n\nlet cachedVerdict: MemoryVerdict | null = null;\n\n/**\n * The verdict from the most recent assessMemoryPressure() call, or null before\n * any patrol has run one. Synchronous — no I/O.\n */\nexport function getCachedMemoryVerdict(): MemoryVerdict | null {\n return cachedVerdict;\n}\n\nexport function setCachedMemoryVerdict(verdict: MemoryVerdict | null): void {\n cachedVerdict = verdict;\n}\n\n/** Test-only. */\nexport function setCachedMemoryVerdictForTests(verdict: MemoryVerdict | null): void {\n cachedVerdict = verdict;\n}\n","/**\n * Concurrency governor for deacon auto-resume / auto-dispatch (PAN-1665).\n *\n * The deacon's patrol re-spawns work from several sources (resume stopped work\n * agents, re-dispatch orphaned review/test specialists, …). With no ceiling, an\n * unfreeze marches the box toward dozens of concurrent heavy `claude` processes.\n *\n * This module is the single budget every patrol spawn path consults. Two rules:\n *\n * 1. It is a **gate on starting new work** — it NEVER kills a running agent to\n * get back under a limit. If the system is already over (a forced `pan start`,\n * a backlog at unfreeze), the deacon simply resumes/dispatches nothing until\n * natural attrition drains the count. Forcibly trimming an over-limit system\n * is the operator's explicit emergency brake, never automatic.\n * 2. Advancing roles (review/test/ship) get reserved headroom above the work cap\n * so the pipeline can always drain instead of deadlocking with work agents.\n */\n\nimport { loadCloisterConfigSync } from './config.js';\nimport {\n listRunningAgentsSync,\n stopAgentSync,\n getAgentStateSync,\n saveAgentStateSync,\n getAgentRuntimeStateSync,\n} from '../agents.js';\nimport { countAgentsByStatus } from '../overdeck/agents.js';\nimport { getCachedMemoryVerdict } from './memory-verdict-cache.js';\nimport { isRoleTerminal, type AdvancingRole } from './reap-terminal-sessions.js';\nimport { readReviewStatusMap } from './review-status-source.js';\n\nconst DEFAULT_MAX_WORK_AGENTS = 6;\nconst DEFAULT_RESERVED_ADVANCING_SLOTS = 3;\nconst DEFAULT_RESERVED_SWARM_SLOTS = 3;\n\n/** Roles that advance work through the pipeline and must keep reserved headroom. */\nconst ADVANCING_ROLES = new Set(['review', 'test', 'ship']);\n\n/**\n * Swarm slots (PAN-2212) are work-role sessions with an `agent-<issue>-slot-N`\n * id. They draw from a dedicated swarm reserve and are counted APART from `work`,\n * so a busy pipeline never starves the swarm — and running swarm slots never\n * starve review/test in reverse.\n */\nconst SWARM_SLOT_ID = /-slot-\\d+$/;\n\nexport interface ConcurrencyLimits {\n maxWorkAgents: number;\n reservedAdvancingSlots: number;\n /** Dedicated swarm-slot reserve, isolated from the work/advancing ceiling (PAN-2212). */\n reservedSwarmSlots: number;\n /** Overall ceiling for any auto-dispatch: work cap + reserved advancing slots. */\n totalCeiling: number;\n /** Whether operator-started agents are exempt from governor reaping (PAN-1812). */\n exemptOperatorStarted: boolean;\n}\n\nfunction normalizeCount(value: unknown, fallback: number, min: number): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;\n const floored = Math.floor(value);\n return floored < min ? min : floored;\n}\n\nexport function getConcurrencyLimits(): ConcurrencyLimits {\n const c = loadCloisterConfigSync().concurrency;\n const maxWorkAgents = normalizeCount(c?.max_work_agents, DEFAULT_MAX_WORK_AGENTS, 1);\n const reservedAdvancingSlots = normalizeCount(c?.reserved_advancing_slots, DEFAULT_RESERVED_ADVANCING_SLOTS, 0);\n const reservedSwarmSlots = normalizeCount(c?.reserved_swarm_slots, DEFAULT_RESERVED_SWARM_SLOTS, 0);\n return {\n maxWorkAgents,\n reservedAdvancingSlots,\n reservedSwarmSlots,\n totalCeiling: maxWorkAgents + reservedAdvancingSlots,\n exemptOperatorStarted: c?.exempt_operator_started ?? true,\n };\n}\n\nexport interface RunningCounts {\n /** Regular work agents, EXCLUDING swarm slots (PAN-2212). */\n work: number;\n advancing: number;\n /** Swarm-slot work agents, counted apart from `work` (PAN-2212). */\n swarm: number;\n /** work + advancing (EXCLUDES swarm) — the ceiling every non-swarm dispatch consults. */\n total: number;\n}\n\n/**\n * Human-readable breakdown of what currently counts against the ceiling, for\n * deferral diagnostics (PAN-1716). Lists the offending session ids so a\n * starvation is diagnosable from the patrol log alone — the original livelock\n * (completed review/test sessions that never get reaped) is invisible from a\n * bare \"ceiling reached\" line.\n */\nexport function describeRunningAgents(): string {\n const alive = listRunningAgentsSync().filter(a => a.tmuxActive);\n const swarm = alive.filter(a => a.role === 'work' && SWARM_SLOT_ID.test(a.id)).map(a => a.id);\n const work = alive.filter(a => a.role === 'work' && !SWARM_SLOT_ID.test(a.id)).map(a => a.id);\n const advancing = alive.filter(a => a.role && ADVANCING_ROLES.has(a.role)).map(a => a.id);\n const warmIdle = countWarmIdleAdvancingAgents(alive);\n const { totalCeiling } = getConcurrencyLimits();\n return `counts: work=${work.length} advancing=${advancing.length} (warm-idle=${warmIdle}, excluded) swarm=${swarm.length} total=${work.length + Math.max(0, advancing.length - warmIdle)}/${totalCeiling}`\n + ` | advancing=[${advancing.join(', ')}] work=[${work.join(', ')}] swarm=[${swarm.join(', ')}]`;\n}\n\n/** Count currently-running agents by role class.\n *\n * PAN-1908: the agents table is the authoritative runtime registry. Counts are\n * derived from status='running' rows grouped by role; the deacon's event-driven\n * updates keep status in sync with tmux liveness.\n */\n/** Count tmux-alive swarm-slot work agents (agent-<issue>-slot-N) — PAN-2212. */\nfunction countRunningSwarmSlots(): number {\n return listRunningAgentsSync().filter(\n a => a.tmuxActive && a.role === 'work' && SWARM_SLOT_ID.test(a.id),\n ).length;\n}\n\n/**\n * Count tmux-ALIVE swarm-slot work agents for one issue (agent-<issue>-slot-N).\n * Stale agents-table rows whose tmux session is dead do NOT count — counting\n * them blocked all dispatch at zero live slots after a reset (PAN-2214).\n */\nexport function countRunningSwarmSlotsForIssue(\n issueId: string,\n agents: ReturnType<typeof listRunningAgentsSync> = listRunningAgentsSync(),\n): number {\n const prefix = `agent-${issueId.toLowerCase()}-slot-`;\n return agents.filter(\n a => a.tmuxActive && a.role === 'work' && SWARM_SLOT_ID.test(a.id) && a.id.startsWith(prefix),\n ).length;\n}\n\n/**\n * PAN-2579: count running advancing-role agents whose issue's phase verdict is\n * already terminal — \"warm-idle\" sessions kept alive for fast re-review under\n * the warm-by-default lifecycle. They are free capacity, not load: excluding\n * them from the ceiling is what lets warm sessions persist without recreating\n * the PAN-1716 livelock (completed reviewers starving every new dispatch).\n * Best-effort: if the review-status source is unregistered or unreadable,\n * count nothing as warm-idle (the ceiling stays conservative). The status map\n * is read through the cycle-free review-status-source leaf, registered by\n * review-status.ts at module load.\n */\nexport function countWarmIdleAdvancingAgents(\n agents: ReturnType<typeof listRunningAgentsSync> = listRunningAgentsSync(),\n): number {\n const advancingRows = agents.filter(a => a.role && ADVANCING_ROLES.has(a.role) && a.issueId);\n if (advancingRows.length === 0) return 0;\n const statuses = readReviewStatusMap();\n if (!statuses) return 0;\n let warmIdle = 0;\n for (const row of advancingRows) {\n const status = statuses[row.issueId!.toUpperCase()];\n if (status && isRoleTerminal(row.role as AdvancingRole, status)) warmIdle++;\n }\n return warmIdle;\n}\n\nexport function countRunningAgents(): RunningCounts {\n const counts = countAgentsByStatus('running');\n const workTotal = counts['work'] ?? 0;\n let advancingTotal = 0;\n for (const role of ADVANCING_ROLES) {\n advancingTotal += counts[role] ?? 0;\n }\n // Swarm slots are work-role sessions but draw from the dedicated swarm reserve,\n // so subtract them from `work` (PAN-2212): the swarm neither starves nor is\n // starved by the work/advancing ceiling.\n const swarm = countRunningSwarmSlots();\n const work = Math.max(0, workTotal - swarm);\n // PAN-2579: warm-idle advancing sessions (verdict terminal, kept alive for the\n // next cycle) do not occupy the ceiling.\n const advancing = Math.max(0, advancingTotal - countWarmIdleAdvancingAgents());\n return { work, advancing, swarm, total: work + advancing };\n}\n\nconst GIB = 1024 ** 3;\n\n/**\n * PAN-2504: additional work-agent admissions permitted by the *memory* budget,\n * rather than the fixed `max_work_agents` count. This is what makes the box fill\n * toward its RAM instead of idling at a hardcoded 6.\n *\n * additional = floor((availableBytes - softReserve) / work_footprint)\n * capped by (memory_driven_max_work_agents - runningWork)\n *\n * The SOFT reserve is read from the cached memory verdict's\n * `thresholds.warningBytes` — the governor already publishes it there, so this\n * needs no import of memory-governor.ts (which would close a real dependency\n * cycle; see memory-verdict-cache.ts).\n *\n * Returns null — meaning \"use the count-based cap\" — when memory-driven mode is\n * off OR the governor has not published a verdict yet (fail safe to the fixed\n * cap rather than admitting blind). availableBytes already reflects the RSS of\n * currently-running agents, so `additional` is genuinely how many *more* fit;\n * the per-iteration `assessMemoryPressure()` brake in the deacon loops still\n * catches an under-estimated footprint mid-fill.\n */\nexport function memoryDrivenWorkSlots(runningWork: number): number | null {\n const c = loadCloisterConfigSync().concurrency;\n if (c?.memory_driven !== true) return null;\n const verdict = getCachedMemoryVerdict();\n if (!verdict) return null;\n const perAgentBytes = Math.max(0.25, c?.work_footprint_gb ?? 2) * GIB;\n const budgetBytes = verdict.availableBytes - verdict.thresholds.warningBytes;\n const fits = budgetBytes > 0 ? Math.floor(budgetBytes / perAgentBytes) : 0;\n const ceiling = normalizeCount(c?.memory_driven_max_work_agents, 24, 1);\n return Math.max(0, Math.min(fits, ceiling - runningWork));\n}\n\n/**\n * How many work agents the deacon may resume/spawn this patrol. Zero when already\n * at or over the cap — the deacon then resumes nothing and lets attrition drain.\n *\n * PAN-2504: when `concurrency.memory_driven` is on and the governor has a cached\n * verdict, the ceiling is the memory budget (`memoryDrivenWorkSlots`) instead of\n * the fixed count cap.\n */\nexport function workResumeSlotsAvailable(\n counts: RunningCounts = countRunningAgents(),\n limits: ConcurrencyLimits = getConcurrencyLimits(),\n): number {\n const memSlots = memoryDrivenWorkSlots(counts.work);\n if (memSlots !== null) return memSlots;\n return Math.max(0, limits.maxWorkAgents - counts.work);\n}\n\n/**\n * Whether an advancing-role (review/test/ship) dispatch is allowed. Gated on the\n * overall ceiling so review/test/ship can always claim their reserved headroom —\n * and, per PAN-2500 specialist-budget, on the memory governor: don't reserve GB\n * for a specialist under memory pressure. Count-slot semantics are UNCHANGED\n * when the cached band is 'ok' (or no patrol has assessed memory yet).\n */\nexport function canDispatchAdvancing(\n counts: RunningCounts = countRunningAgents(),\n limits: ConcurrencyLimits = getConcurrencyLimits(),\n): boolean {\n const verdict = getCachedMemoryVerdict();\n if (verdict && verdict.band !== 'ok') return false;\n return counts.total < limits.totalCeiling;\n}\n\n// ---------------------------------------------------------------------------\n// Per-patrol advancing-dispatch reservation\n//\n// countRunningAgents() only sees tmux-alive sessions. Agents dispatched earlier\n// in the SAME patrol haven't registered a session yet, so several dispatch\n// functions (checkOrphanedReviewStatuses, checkMissingReviewStatuses,\n// checkPendingTestDispatch, checkPostReviewCommits) running back-to-back would\n// each see the stale low count and blow past the ceiling. runPatrol() resets\n// this counter at the top of every cycle; each dispatch site reserves a slot.\n// ---------------------------------------------------------------------------\nlet advancingReservedThisPatrol = 0;\n/** Dedicated per-patrol swarm-dispatch budget, isolated from advancing (PAN-2212). */\nlet swarmReservedThisPatrol = 0;\n\n/** Reset the per-patrol dispatch budgets. Called once at patrol start. */\nexport function resetPatrolDispatchBudget(): void {\n advancingReservedThisPatrol = 0;\n swarmReservedThisPatrol = 0;\n}\n\n/**\n * Claim one advancing-role (review/test/ship) dispatch slot for this patrol.\n * Returns false when the total ceiling is reached, or (PAN-2500 specialist-budget)\n * when the memory governor's cached band is not 'ok' — the caller must DEFER\n * (leave status untouched so a later patrol retries), never fail. Counts both\n * tmux-alive agents and advancing dispatches already reserved this patrol.\n */\nexport function tryReserveAdvancingSlot(\n counts: RunningCounts = countRunningAgents(),\n limits: ConcurrencyLimits = getConcurrencyLimits(),\n): boolean {\n const verdict = getCachedMemoryVerdict();\n if (verdict && verdict.band !== 'ok') return false;\n if (counts.total + advancingReservedThisPatrol >= limits.totalCeiling) return false;\n advancingReservedThisPatrol++;\n return true;\n}\n\n/** Release a same-patrol advancing reservation when dispatch was calmly gated. */\nexport function releaseAdvancingSlot(): void {\n advancingReservedThisPatrol = Math.max(0, advancingReservedThisPatrol - 1);\n}\n\n/**\n * Claim one swarm-slot dispatch for this patrol (PAN-2212). Gated ONLY on the\n * dedicated swarm reserve — never the work/advancing ceiling — so a busy pipeline\n * never starves the swarm. Returns false when the reserve is full; the caller\n * DEFERS (leave the item unclaimed so a later patrol retries), never fails.\n */\nexport function tryReserveSwarmSlot(\n counts: RunningCounts = countRunningAgents(),\n limits: ConcurrencyLimits = getConcurrencyLimits(),\n): boolean {\n if (counts.swarm + swarmReservedThisPatrol >= limits.reservedSwarmSlots) return false;\n swarmReservedThisPatrol++;\n return true;\n}\n\n/** Release a same-patrol swarm reservation when dispatch was calmly gated. */\nexport function releaseSwarmSlot(): void {\n swarmReservedThisPatrol = Math.max(0, swarmReservedThisPatrol - 1);\n}\n\n// ---------------------------------------------------------------------------\n// Emergency brake\n//\n// The governor never auto-kills to satisfy a limit, so an over-cap system\n// (forced `pan start`s, an unfreeze backlog) stays over until attrition drains.\n// The emergency brake is the *explicit operator action* that forcibly trims the\n// excess. It is deliberately separate from the nuclear `emergencyStop` (which\n// kills ALL agents): the brake stops only work agents above the cap, idle ones\n// first, and clears the user-stop flag so the deacon re-admits them as slots free\n// (drain-at-cap, not retirement). Never called automatically.\n// ---------------------------------------------------------------------------\nexport interface BrakeResult {\n /** Running work agents before the brake. */\n before: number;\n /** The configured work-agent cap. */\n cap: number;\n /** Agent ids stopped by the brake. */\n stopped: string[];\n /** Running work agents after the brake. */\n remaining: number;\n}\n\nexport function emergencyBrake(): BrakeResult {\n const { maxWorkAgents, exemptOperatorStarted } = getConcurrencyLimits();\n const runningWork = listRunningAgentsSync().filter(a => a.tmuxActive && a.role === 'work');\n const excess = runningWork.length - maxWorkAgents;\n if (excess <= 0) {\n return { before: runningWork.length, cap: maxWorkAgents, stopped: [], remaining: runningWork.length };\n }\n\n // PAN-1812: operator-started work agents (no flywheelRunId) are exempt from\n // automatic governor reaping when the config flag is enabled.\n const candidates = exemptOperatorStarted\n ? runningWork.filter(a => a.flywheelRunId !== undefined && a.flywheelRunId !== null && a.flywheelRunId !== '')\n : runningWork;\n\n // Stop the least-productive first: idle agents ahead of active ones, and among\n // equals the stalest (oldest lastActivity) first.\n const ordered = [...candidates].sort((a, b) => {\n const aIdle = getAgentRuntimeStateSync(a.id)?.state === 'idle' ? 0 : 1;\n const bIdle = getAgentRuntimeStateSync(b.id)?.state === 'idle' ? 0 : 1;\n if (aIdle !== bIdle) return aIdle - bIdle;\n return (Date.parse(a.lastActivity ?? '') || 0) - (Date.parse(b.lastActivity ?? '') || 0);\n });\n\n const stopped: string[] = [];\n for (const agent of ordered.slice(0, excess)) {\n try {\n stopAgentSync(agent.id);\n // stopAgentSync stamps stoppedByUser=true (deliberate stop). Clear it so the\n // deacon re-admits this agent when a slot frees — the brake trims to the cap,\n // it does not retire the work.\n const state = getAgentStateSync(agent.id);\n if (state) {\n delete state.stoppedByUser;\n saveAgentStateSync(state);\n }\n stopped.push(agent.id);\n } catch {\n // best effort — skip agents that fail to stop cleanly\n }\n }\n\n return {\n before: runningWork.length,\n cap: maxWorkAgents,\n stopped,\n remaining: runningWork.length - stopped.length,\n };\n}\n","import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { FsError, TmuxError } from '../errors.js';\nimport type { AgentStatus } from '@overdeck/contracts';\nimport type { AgentState, Role } from '../agents.js';\nimport { getAgentState, isRole, normalizeAgentId } from '../agents.js';\nimport { killSession, listSessions, listSessionsSync } from '../tmux.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport { getRollbackAgentStatePath } from '../overdeck/agent-rollback-state.js';\nimport { listOverdeckAgentStatesSync } from '../overdeck/agent-state-sync.js';\n\nexport function listRunningAgentsSync(): (AgentState & { tmuxActive: boolean })[] {\n // Match liveness against ALL overdeck-socket sessions, not just `agent-*`.\n // Agent state dirs are named by role prefix (planning-/agent-/conv-/strike-);\n // getAgentSessions only returns `agent-*`, so planning/conv/strike sessions\n // would always read tmuxActive:false and get dropped by the enrichment poller.\n const tmuxSessions = listSessionsSync();\n const tmuxNames = new Set(tmuxSessions.map(s => s.name));\n\n return listOverdeckAgentStatesSync().map((state) => {\n const normalizedId = normalizeAgentId(state.id);\n return {\n ...state,\n id: normalizedId,\n tmuxActive: tmuxNames.has(normalizedId),\n };\n });\n}\n\n/**\n * PAN-1908: list all agents in the SQLite registry with optional filtering.\n * This is the replacement for enumerating ~/.overdeck/agents/ directories.\n */\nexport function listAgentStates(options?: { status?: AgentStatus; role?: Role }): AgentState[] {\n return listOverdeckAgentStatesSync()\n .filter((state) => {\n if (options?.status && state.status !== options.status) return false;\n if (options?.role && state.role !== options.role) return false;\n return true;\n });\n}\n\n\nexport const listRunningAgents = (): Effect.Effect<(AgentState & { tmuxActive: boolean })[], FsError | TmuxError> =>\n Effect.gen(function* () {\n // PAN-1908: authoritative registry is the SQLite agents table; no directory scan.\n //\n // TRAP — `tmuxActive` reflects whether THIS process can see the agent's tmux\n // session on the `overdeck` socket. Run this from a one-off `tsx -e`/CLI\n // process that lacks access to that socket and `listSessions()` returns\n // empty, so EVERY agent comes back `tmuxActive: false` — including ones that\n // are genuinely running. Do not conclude \"the agent isn't running\" / \"the\n // enrichment poller skips it\" from an out-of-server-process reading. Trust\n // the live dashboard server's view (it owns the socket) or check the tmux\n // session directly with `tmux -L overdeck list-sessions`.\n //\n // Use the UNFILTERED session list (not getAgentSessions, which is `agent-*`\n // only): agent state dirs carry role prefixes (planning-/agent-/conv-/strike-),\n // and planning/conv/strike sessions must read tmuxActive:true so the\n // enrichment poller scans them for AskUserQuestion / pending input (PAN-1395).\n const tmuxSessions = yield* listSessions();\n const tmuxNames = new Set(tmuxSessions.map(s => s.name));\n\n return listOverdeckAgentStatesSync().map((state) => {\n const normalizedId = normalizeAgentId(state.id);\n return {\n ...state,\n id: normalizedId,\n tmuxActive: tmuxNames.has(normalizedId),\n };\n });\n });\n\n/**\n * PAN-1048 P2: async startup migration.\n *\n * The previous synchronous version used readdirSync, readFileSync,\n * killSession (sync tmux subprocess), and rmSync — all on the Node\n * event loop. Called from warnOnBareNumericIssueIds() during dashboard\n * read-model bootstrap, this blocked all HTTP/WebSocket/PTY traffic on\n * server startup while it scanned every agent dir, killed stale tmux\n * sessions, and recursively deleted directories.\n *\n * This async variant does the same work using fs/promises and the\n * already-async killSessionAsync() so the bootstrap path no longer\n * stalls the event loop.\n */\nexport async function dropLegacyAgentStatesMissingRoleAsync(): Promise<number> {\n if (!existsSync(AGENTS_DIR)) return 0;\n\n const fsp = await import('fs/promises');\n let entries: string[];\n try {\n entries = await fsp.readdir(AGENTS_DIR);\n } catch {\n return 0;\n }\n\n let dropped = 0;\n await Promise.all(\n entries.map(async (entry) => {\n const dirPath = join(AGENTS_DIR, entry);\n let stat;\n try {\n stat = await fsp.stat(dirPath);\n } catch {\n return;\n }\n if (!stat.isDirectory()) return;\n\n const agentId = normalizeAgentId(entry);\n const stateFile = getRollbackAgentStatePath(agentId);\n let raw: { role?: unknown };\n try {\n const contents = await fsp.readFile(stateFile, 'utf8');\n raw = JSON.parse(contents) as { role?: unknown };\n } catch {\n return;\n }\n if (isRole(raw.role)) return;\n\n try { await Effect.runPromise(killSession(agentId)); } catch { /* best effort */ }\n try {\n await fsp.rm(dirPath, { recursive: true, force: true });\n dropped++;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[agents] Failed to drop legacy agent state ${agentId}: ${msg}`);\n }\n }),\n );\n\n return dropped;\n}\n\n/**\n * Scan ~/.overdeck/agents/ for state files with bare numeric issueIds\n * (e.g. \"484\" instead of \"PAN-484\") and log warnings to stderr.\n *\n * These workspaces were created before the pan- prefix convention and may\n * cause cross-tracker pollution if their in_review transition is triggered.\n * Called once at server startup to surface legacy state files.\n */\n/**\n * PAN-1048 P2: bootstrap-path migration is async.\n *\n * Sweeps legacy state files missing a `role` field and warns on bare\n * numeric issueIds. Both passes used to be synchronous (readdirSync,\n * readFileSync, killSession, rmSync), which blocked the dashboard\n * server's event loop on startup. The async version scans the same\n * directory once per concern and uses fs/promises throughout.\n */\nexport async function warnOnBareNumericIssueIds(): Promise<void> {\n const droppedLegacyAgents = await dropLegacyAgentStatesMissingRoleAsync();\n if (droppedLegacyAgents > 0) {\n console.warn(`[agents] Dropped ${droppedLegacyAgents} legacy agent state file(s) missing role`);\n }\n\n if (!existsSync(AGENTS_DIR)) return;\n\n const fsp = await import('fs/promises');\n let entries: string[];\n try {\n entries = await fsp.readdir(AGENTS_DIR);\n } catch {\n return;\n }\n\n const legacy: string[] = [];\n await Promise.all(\n entries.map(async (entry) => {\n const dirPath = join(AGENTS_DIR, entry);\n try {\n const stat = await fsp.stat(dirPath);\n if (!stat.isDirectory()) return;\n } catch {\n return;\n }\n const state = await Effect.runPromise(getAgentState(entry));\n if (state?.issueId && /^\\d+$/.test(state.issueId)) {\n legacy.push(`${entry} (issueId: \"${state.issueId}\")`);\n }\n }),\n );\n\n if (legacy.length > 0) {\n console.warn(\n `[agents] WARNING: ${legacy.length} agent state file(s) have bare numeric issueIds ` +\n `(created before the pan- prefix convention). These agents will not be able to ` +\n `transition tracker state. Consider removing or updating them:\\n` +\n legacy.map(l => ` ~/.overdeck/agents/${l}`).join('\\n')\n );\n }\n}\n","import { readFileSync, mkdirSync, writeFileSync } from 'fs';\nimport { mkdir as mkdirAsync, writeFile as writeFileAsync } from 'fs/promises';\nimport { exec, execSync } from 'child_process';\nimport { promisify } from 'util';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport {\n getAgentDir,\n getAgentStateSync,\n getAgentState,\n saveAgentStateSync,\n saveAgentState,\n markAgentStoppedState,\n saveAgentRuntimeState,\n normalizeAgentId,\n} from '../agents.js';\nimport { emitAgentEvent } from '../agent-runtime.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport {\n sessionExistsSync,\n capturePaneSync,\n killSessionSync,\n sessionExists,\n capturePane,\n killSession,\n} from '../tmux.js';\nimport { FsError, TmuxError } from '../errors.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * True when the PID is a tmux process (client or server). Used to keep the\n * per-agent launcher kill sweep from ever signalling the shared tmux server\n * (PAN-1798). Reads /proc/<pid>/comm; on non-Linux or read failure returns\n * false (fail-open matches pre-fix behavior for non-tmux processes).\n */\nfunction isTmuxProcessSync(pid: number): boolean {\n try {\n return readFileSync(`/proc/${pid}/comm`, 'utf-8').trim() === 'tmux';\n } catch {\n return false;\n }\n}\n\n/**\n * Find and kill any running `launcher.sh` process for the given agent.\n *\n * PAN-1527: `tmux kill-session` only signals tmux-managed children. Planning\n * agents (and any agent whose launcher escapes its tmux session) leave\n * orphan launcher.sh processes alive — state.json says stopped, but bash is\n * still burning CPU and tokens hours later. This locates them by command\n * line and walks SIGTERM → grace → SIGKILL.\n *\n * Sync version: callable from CLI (`pan kill`) and from the existing\n * `stopAgentSync`. Uses execSync only via `pgrep`, which is fast and\n * non-blocking in practice. Acceptable per CLAUDE.md because this path is\n * sync-by-nature already and is only called from CLI contexts and existing\n * sync internals.\n */\nfunction killLauncherProcessSync(agentId: string): void {\n const launcherPath = join(AGENTS_DIR, agentId, 'launcher.sh');\n let pidsOut: string;\n try {\n pidsOut = execSync(\n `pgrep -f ${JSON.stringify(launcherPath)}`,\n { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] },\n ).trim();\n } catch {\n return; // pgrep exits 1 when there are no matches — nothing to kill\n }\n\n const pids = pidsOut\n .split('\\n')\n .map(s => Number.parseInt(s, 10))\n .filter(n => Number.isFinite(n) && n > 0 && n !== process.pid)\n // PAN-1798: when this agent's spawn FOUNDED the shared tmux server, the\n // server's cmdline embeds this launcher path (`tmux ... new-session ...\n // bash .../launcher.sh`), so pgrep -f matches the server itself. Killing\n // it destroys every session on the socket — agents, reviews, and all\n // conversations. Never signal a tmux process from the per-agent sweep.\n .filter(pid => !isTmuxProcessSync(pid));\n if (pids.length === 0) return;\n\n for (const pid of pids) {\n try { process.kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n\n // ~500ms grace period for orderly shutdown. Sync spawn of `sleep` is\n // acceptable in CLI context; this function is never reached from the\n // dashboard server (which uses the async `stopAgent` Effect below).\n try {\n execSync('sleep 0.5', { stdio: 'ignore' });\n } catch { /* ignore */ }\n\n const survivors: number[] = [];\n for (const pid of pids) {\n try {\n process.kill(pid, 0);\n survivors.push(pid);\n } catch {\n /* already dead */\n }\n }\n for (const pid of survivors) {\n try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ }\n }\n}\n\nasync function killLauncherProcessAsync(agentId: string): Promise<void> {\n const launcherPath = join(AGENTS_DIR, agentId, 'launcher.sh');\n let pidsOut: string;\n try {\n const { stdout } = await execAsync(`pgrep -f ${JSON.stringify(launcherPath)}`);\n pidsOut = stdout.trim();\n } catch {\n return; // pgrep exits 1 when there are no matches\n }\n\n const pids = pidsOut\n .split('\\n')\n .map(s => Number.parseInt(s, 10))\n .filter(n => Number.isFinite(n) && n > 0 && n !== process.pid)\n // PAN-1798: see killLauncherProcessSync — the founding tmux server's\n // cmdline embeds this launcher path; never signal tmux from this sweep.\n .filter(pid => !isTmuxProcessSync(pid));\n if (pids.length === 0) return;\n\n for (const pid of pids) {\n try { process.kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n\n await new Promise<void>(resolve => setTimeout(resolve, 500));\n\n for (const pid of pids) {\n try {\n process.kill(pid, 0);\n try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ }\n } catch {\n /* already dead */\n }\n }\n}\n\nexport function stopAgentSync(agentId: string): void {\n const normalizedId = normalizeAgentId(agentId);\n\n if (sessionExistsSync(normalizedId)) {\n // Capture tmux output before killing so logs remain viewable after stop\n try {\n const output = capturePaneSync(normalizedId, 5000);\n if (output) {\n const agentDir = getAgentDir(normalizedId);\n mkdirSync(agentDir, { recursive: true });\n writeFileSync(join(agentDir, 'output.log'), output);\n }\n } catch {\n // Non-fatal — best effort log capture\n }\n\n killSessionSync(normalizedId);\n }\n\n // PAN-1527: kill orphan launcher.sh processes that escape tmux (planning\n // agents, dashboard-spawned launchers, anything that survived tmux\n // kill-session). Runs even when no tmux session existed in the first place.\n killLauncherProcessSync(normalizedId);\n\n const state = getAgentStateSync(normalizedId);\n if (state) {\n // Ensure id is set — runtime state files may lack it (PAN-150)\n if (!state.id) state.id = normalizedId;\n\n markAgentStoppedState(state);\n saveAgentStateSync(state);\n }\n\n // Also mark runtime.json as stopped so Cloister/Deacon won't auto-restart.\n // state.json and runtime.json are separate files — both must agree the agent\n // was intentionally stopped to prevent race conditions with health check polls.\n console.log(`[agents] Stopping ${normalizedId}: tmux=${sessionExistsSync(normalizedId)} stateStatus=${state?.status ?? 'none'}`);\n saveAgentRuntimeState(normalizedId, {\n state: 'stopped',\n lastActivity: new Date().toISOString(),\n });\n}\n\nexport const stopAgent = (agentId: string): Effect.Effect<void, FsError | TmuxError> => {\n const normalizedId = normalizeAgentId(agentId);\n\n return Effect.gen(function* () {\n if (yield* sessionExists(normalizedId)) {\n yield* Effect.gen(function* () {\n const output = yield* capturePane(normalizedId, 5000);\n if (!output) return;\n\n const agentDir = getAgentDir(normalizedId);\n const outputFile = join(agentDir, 'output.log');\n yield* Effect.tryPromise({\n try: () => mkdirAsync(agentDir, { recursive: true }),\n catch: (cause) => new FsError({ operation: 'mkdir', path: agentDir, cause }),\n });\n yield* Effect.tryPromise({\n try: () => writeFileAsync(outputFile, output),\n catch: (cause) => new FsError({ operation: 'write', path: outputFile, cause }),\n });\n }).pipe(Effect.catch(() => Effect.void));\n\n yield* killSession(normalizedId);\n }\n\n // PAN-1527: same orphan-launcher kill as stopAgentSync. Runs after\n // killSession so tmux gets the first chance to take everything down\n // cleanly; falls through and kills any survivor by command-line match.\n yield* Effect.tryPromise({\n try: () => killLauncherProcessAsync(normalizedId),\n catch: (cause): never => { throw cause; },\n }).pipe(Effect.catch(() => Effect.void));\n\n const state = yield* getAgentState(normalizedId);\n if (state) {\n if (!state.id) state.id = normalizedId;\n\n markAgentStoppedState(state);\n yield* saveAgentState(state);\n }\n\n const tmuxActive = yield* sessionExists(normalizedId);\n console.log(`[agents] Stopping ${normalizedId} (async): tmux=${tmuxActive} stateStatus=${state?.status ?? 'none'}`);\n yield* Effect.forkDetach(emitAgentEvent(normalizedId, {\n kind: 'activity',\n activity: 'stopped',\n }));\n });\n};\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { mkdir, writeFile, writeFile as writeFileAsync } from 'fs/promises';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { randomUUID } from 'crypto';\nimport { homedir } from 'os';\nimport { dirname, join, resolve } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync, emitActivityTtsSync } from '../activity-logger.js';\nimport { assertIssueHasBeads, BeadsMissingError } from '../beads-query.js';\nimport { BdTransientFailure } from '../bd-process-lock.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { isTldrEnabledSync } from '../config-yaml.js';\nimport { createConversation, getConversationByName, reactivateConversationForSpawn } from '../overdeck/conversations.js';\nimport { startWorkSync } from '../cv.js';\nimport { generateFixedPointPromptSync, checkHookSync, initHookSync } from '../hooks.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { getProviderForModelSync, setupCredentialFileAuthSync, clearCredentialFileAuthSync } from '../providers.js';\nimport { resetPipelineVerdictsForWorkStartSync } from '../review-status.js';\nimport { resolveHarness } from '../harness-resolve.js';\nimport type { ModelId } from '../settings.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { writeBridgeTokenSync } from '../bridge-token.js';\nimport { createSession, exactPaneTarget, sessionExists, setOption } from '../tmux.js';\nimport { createActiveSlice } from '../vbrief/dag.js';\nimport { readWorkspacePlanSync } from '../vbrief/io.js';\nimport {\n getAgentDir,\n markAgentRunning,\n recordStartupSessionExit,\n saveAgentState,\n saveAgentStateSync,\n SESSION_EXITED_BEFORE_KICKOFF,\n type AgentState,\n type Role,\n} from './agent-state.js';\nimport { saveAgentRuntimeState } from './runtime-state.js';\nimport { clearReadySignal } from './identity.js';\nimport { deliverAgentMessage, deliverInitialPromptWithRetry } from './delivery.js';\nimport { determineModel, getProviderEnvForModel, getProviderExportsForModel } from './provider-env.js';\nimport {\n claudeSystemPromptFiles,\n getCodexLauncherFields,\n getOhmypiLauncherFields,\n getProviderAuthMode,\n getRoleRuntimeBaseCommand,\n waitForPromptReady,\n writeLauncherScriptAtomic,\n writeOhmypiAgentPrompt,\n} from './runtime-command.js';\nimport {\n buildAgentLaunchConfig,\n defaultRunWorkspace,\n flywheelEnvExports,\n resolveRegisteredSlotSpawn,\n resolveSlotTierSpawnParams,\n resolveSingleWorkTierSpawnParams,\n resolveFlywheelSpawnEnv,\n runAgentId,\n transitionIssueToInProgress,\n withSpawnTimeMemoryContext,\n assertWorkspaceStackHealthyForSpawn,\n type RegisteredSlotSpawn,\n type SpawnOptions,\n type SpawnRunOptions,\n} from './spawn-prep.js';\nimport { getConcurrencyLimits } from '../cloister/concurrency.js';\nimport { listAgentStates } from './queries.js';\nimport { findProjectByPathSync } from '../projects.js';\nimport { isStateMigrated } from '../state-home.js';\nimport { shouldCommitLegacyWorkspaceArtifacts } from '../state-read-home.js';\nimport {\n decideChannelsForWorkAgent,\n dismissDevChannelsDialog,\n prepareSupervisorForFreshLaunch,\n recordKickoffDeliveryFailure,\n writeChannelsBridgeMcpConfig,\n} from './supervisor-channels.js';\nimport { stopAgent } from './termination.js';\n\nconst execAsync = promisify(exec);\n\nexport async function spawnRun(issueId: string, role: Role, options: SpawnRunOptions = {}): Promise<AgentState> {\n const workspace = options.workspace ?? defaultRunWorkspace(issueId);\n const modelSpawnKey = `${role}:${issueId}`;\n const selectedModel = determineModel({ model: options.model, role, spawnKey: modelSpawnKey });\n\n if (role === 'work') {\n const slot = resolveRegisteredSlotSpawn(issueId, workspace, options);\n // Tiered execution (PAN-1791): when enabled, the slot item's difficulty\n // selects the worker — the resolved tier's model+harness replace the\n // parent default in the spawn params. Disabled → both stay as resolved\n // above, unchanged.\n let slotModel = selectedModel;\n let slotHarness = options.harness;\n if (slot) {\n assertRegisteredSlotCap(issueId, options.maxRegisteredSlots);\n const tierParams = resolveSlotTierSpawnParams(workspace, slot.slotItemId, options.model, modelSpawnKey);\n if (tierParams.model) {\n slotModel = determineModel({ model: tierParams.model, role, spawnKey: modelSpawnKey });\n // Implicit staffing (PAN-2397) omits harness — keep the parent's\n // historical harness handling in that case.\n slotHarness = tierParams.harness ?? options.harness;\n }\n await ensureRegisteredSlotWorktree(workspace, slot);\n }\n const prompt = slot\n ? buildRegisteredSlotPrompt(issueId, workspace, slot, options.prompt)\n : options.prompt;\n return spawnAgent({\n issueId,\n workspace: slot?.workspace ?? workspace,\n agentId: slot?.agentId,\n harness: slotHarness,\n model: slot ? slotModel : options.model,\n prompt,\n role: 'work',\n allowHost: options.allowHost,\n flywheelRunId: options.flywheelRunId,\n effort: options.effort,\n slotIndex: slot?.slotIndex,\n slotItemId: slot?.slotItemId,\n });\n }\n\n const flywheelEnv = resolveFlywheelSpawnEnv(role, options.flywheelRunId);\n\n const agentId = options.agentId ?? runAgentId(issueId, role, options.subRole);\n if (await Effect.runPromise(sessionExists(agentId))) {\n // PAN-2579 (warm-by-default lifecycle): advancing-role sessions are no longer\n // reaped at verdict time, so a session alive at dispatch time may be a\n // warm-idle leftover from the PREVIOUS cycle rather than an active run. Reap\n // it here — at the moment its slot is actually needed — when that is provable\n // (its phase verdict is terminal, or its pane process has exited). A live\n // session with a non-terminal verdict is genuinely active: keep throwing so\n // a concurrent duplicate dispatch cannot stomp it. (Review dispatch reuses\n // its warm session with context via spawnReviewRoleForIssue's resume path\n // before ever reaching this guard; test/ship runs start fresh by design.)\n let reapWarmIdle = false;\n const advancing = role === 'review' || role === 'test' || role === 'ship';\n try {\n const { isPaneDead } = await import('../tmux.js');\n if (await Effect.runPromise(isPaneDead(agentId))) {\n reapWarmIdle = true;\n } else if (advancing) {\n const { getReviewStatusSync } = await import('../review-status.js');\n const { isRoleTerminal } = await import('../cloister/reap-terminal-sessions.js');\n const status = getReviewStatusSync(issueId);\n reapWarmIdle = !!status && isRoleTerminal(role as 'review' | 'test' | 'ship', status);\n }\n } catch { /* probe failure → conservative: treat as active */ }\n if (!reapWarmIdle) {\n throw new Error(`Role run ${agentId} already running. Use 'pan tell' to message it.`);\n }\n console.log(`[spawn] ${agentId} is warm-idle from the previous cycle — reaping it for the new ${role} dispatch (PAN-2579)`);\n const { killSession } = await import('../tmux.js');\n await Effect.runPromise(killSession(agentId)).catch(() => {});\n }\n\n await assertWorkspaceStackHealthyForSpawn(issueId, role, options.allowHost, workspace);\n\n initHookSync(agentId);\n\n const resolvedHarness: RuntimeName = await resolveHarness({\n explicit: options.harness,\n role,\n model: selectedModel,\n });\n\n if (\n getProviderForModelSync(selectedModel).name === 'openai'\n && (await getProviderAuthMode(selectedModel)) === 'subscription'\n ) {\n const { isCliproxyRunning } = await import('../cliproxy.js');\n if (!(await Effect.runPromise(isCliproxyRunning()))) {\n throw new Error(\n 'CLIProxyAPI sidecar is not running. GPT subscription role runs route through '\n + 'a local cliproxy process managed by `pan up`. Run `pan up` (or restart the '\n + 'dashboard) before spawning a GPT role run.',\n );\n }\n }\n\n const state: AgentState = {\n id: agentId,\n issueId,\n workspace,\n harness: resolvedHarness,\n role,\n model: selectedModel,\n modelSpawnKey,\n status: 'starting',\n startedAt: new Date().toISOString(),\n costSoFar: 0,\n hostOverride: options.allowHost || undefined,\n slotIndex: options.slotIndex,\n slotItemId: options.slotItemId,\n };\n // PAN-1048 P1: spawnRun is on the dashboard hot path (Effect routes,\n // reactive Cloister scheduler). All disk I/O here uses async fs/promises\n // so we never block the Node event loop.\n await Effect.runPromise(saveAgentState(state));\n\n const isSpecialistRole = role === 'review' || role === 'test' || role === 'ship' || role === 'knowledge';\n const shouldRegisterConversation = isSpecialistRole || options.registerConversation === true;\n // PAN-1557: convoy sub-reviewers are now interactive specialists — deliver\n // their prompt via tmux after Claude boots (same as the orchestrator/test/\n // ship), not on stdin to a headless `claude --print`.\n const shouldDeliverPromptViaTmux = shouldRegisterConversation && resolvedHarness === 'claude-code';\n const shouldDeliverPromptViaPi = shouldRegisterConversation && resolvedHarness === 'ohmypi';\n const shouldDeliverPromptViaCodexTui = shouldRegisterConversation && resolvedHarness === 'codex';\n const prompt = options.prompt\n ? await withSpawnTimeMemoryContext({\n prompt: options.prompt,\n issueId,\n workspace,\n agentId,\n role,\n harness: resolvedHarness,\n })\n : '';\n\n let promptFile: string | undefined;\n const tracksKickoffDelivery = role === 'flywheel';\n if (prompt && (tracksKickoffDelivery || (!shouldDeliverPromptViaTmux && !shouldDeliverPromptViaPi && !shouldDeliverPromptViaCodexTui))) {\n promptFile = join(getAgentDir(agentId), 'initial-prompt.md');\n await writeFileAsync(promptFile, prompt);\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = false;\n await Effect.runPromise(saveAgentState(state));\n }\n }\n\n checkAndSetupHooks();\n\n const provider = getProviderForModelSync(selectedModel as ModelId);\n if (provider.authType === 'credential-file') {\n setupCredentialFileAuthSync(provider, workspace);\n } else {\n clearCredentialFileAuthSync(workspace);\n }\n\n const providerExports = await getProviderExportsForModel(selectedModel);\n const providerEnv = await getProviderEnvForModel(selectedModel);\n\n // PAN-1048 review feedback 005 (S1): when the resolved harness is ohmypi, thread\n // the per-agent ohmypi launcher fields (--session-dir, --extension, FIFO\n // redirect) through generateLauncherScript so the role launcher emits the\n // correct `omp --mode rpc` command instead of a malformed Claude command.\n // Without this, a config'd `roles.review.harness: ohmypi` produced a launcher\n // that silently fell back to Claude shape.\n const piLauncherFields = resolvedHarness === 'ohmypi'\n ? await getOhmypiLauncherFields(agentId, selectedModel)\n : {};\n const codexLauncherFields = resolvedHarness === 'codex'\n ? getCodexLauncherFields(agentId, selectedModel, workspace)\n : {};\n\n // Create a conversation record for every specialist role — sub-role reviewers,\n // the review orchestrator/synthesizer, test, and ship. The row is the index\n // the dashboard reads to (a) locate the JSONL via claude_session_id, (b) carry\n // pre-JSONL state (spawn_error, fork_status), and (c) let the\n // conversation-lifecycle service compute sessionAlive from real tmux liveness\n // instead of from the agent state machine's status field, which can lag.\n // Excluding the orchestrator here previously forced AgentOutputPanel to\n // synthesize a Conversation whose sessionAlive came from `agent.status`, and\n // stale snapshots made active synthesizers render as \"Starting…\".\n let sessionId: string | undefined;\n let rawSessionId: string | undefined;\n if (shouldRegisterConversation) {\n // When resuming, reuse the prior JSONL session so `claude --resume` reloads conversation history.\n // When starting fresh, generate a new UUID and use `claude --session-id`.\n rawSessionId = options.resumeSessionId ?? randomUUID();\n\n // Persist the session ID to <agentDir>/session.id so resolveClaudeSessionId can locate the\n // JSONL after the specialist exits. Works for both fresh (--session-id) and resumed (--resume).\n try {\n const agentDir = getAgentDir(agentId);\n await mkdir(agentDir, { recursive: true });\n await writeFile(join(agentDir, 'session.id'), rawSessionId, 'utf-8');\n } catch (err) {\n console.warn(`[spawnRun] Failed to persist session.id for ${agentId}:`, err instanceof Error ? err.message : String(err));\n }\n\n try {\n const conversation = {\n name: agentId,\n tmuxSession: agentId,\n cwd: workspace,\n issueId,\n claudeSessionId: rawSessionId,\n model: selectedModel,\n harness: resolvedHarness,\n };\n if (getConversationByName(agentId)) {\n reactivateConversationForSpawn(conversation);\n } else {\n createConversation(conversation);\n }\n } catch (err) {\n // Non-fatal: the specialist still runs, but without a conversation record\n console.warn(`[spawnRun] Failed to register conversation for ${agentId}:`, err instanceof Error ? err.message : String(err));\n }\n\n // Only set sessionId (→ --session-id flag) for fresh spawns.\n // Resumes pass resumeSessionId (→ --resume flag) to the launcher instead.\n if (!options.resumeSessionId) {\n sessionId = rawSessionId;\n }\n }\n\n // PAN-1557: convoy reviewers are interactive now, so the launcher no longer\n // owns the REVIEWER_READY/FAILED signal (which previously rode a `claude\n // --print` process exit). The Stop-hook delivers REVIEWER_READY to the\n // synthesis agent when the reviewer finishes its turn with a written report;\n // Deacon's REVIEWER_TIMEOUT remains the failure failsafe. We still persist\n // the synthesis/output wiring on state.json so the Stop-hook can read it.\n if (options.reviewSynthesisAgentId) state.reviewSynthesisAgentId = options.reviewSynthesisAgentId;\n if (options.reviewOutputPath) state.reviewOutputPath = options.reviewOutputPath;\n\n const extraEnvExports = [...flywheelEnvExports(flywheelEnv), ...(options.extraEnvExports ?? [])];\n if (role === 'knowledge' && !extraEnvExports.includes('export PATH=\"$HOME/.overdeck/bin:$PATH\"')) {\n extraEnvExports.push('export PATH=\"$HOME/.overdeck/bin:$PATH\"');\n }\n\n const launcherContent = generateLauncherScriptSync({\n role,\n workingDir: workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports,\n promptFile: shouldDeliverPromptViaTmux ? undefined : promptFile,\n promptFileMode: undefined,\n overdeckEnv: { agentId, issueId, sessionType: options.subRole ? `${role}.${options.subRole}` : role },\n extraEnvExports,\n baseCommand: await getRoleRuntimeBaseCommand(selectedModel, agentId, role, resolvedHarness, options.subRole, options.effort),\n appendSystemPromptFiles: await claudeSystemPromptFiles(workspace, resolvedHarness),\n sessionId,\n resumeSessionId: options.resumeSessionId,\n reviewSignal: undefined,\n trapHup: undefined,\n ...piLauncherFields,\n ...codexLauncherFields,\n });\n\n const launcherScript = join(getAgentDir(agentId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n const claudeCmd = `bash ${launcherScript}`;\n console.log(`[claude-invoke] purpose=role-run | role=${role} | model=${state.model} | source=agents.ts:spawnRun | session=${agentId} | command=\"${claudeCmd}\"`);\n\n try {\n const { preTrustDirectory } = await import('../workspace-manager.js') as { preTrustDirectory: (dir: string) => void };\n preTrustDirectory(workspace);\n } catch { /* non-fatal */ }\n\n // PAN-1594: clear any stale ready.json before launch so waitForReadySignal()\n // only observes the session-start signal from THIS launch.\n clearReadySignal(agentId);\n\n await Effect.runPromise(createSession(agentId, workspace, claudeCmd, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n TERM: 'xterm-256color',\n OVERDECK_AGENT_ID: agentId,\n OVERDECK_ISSUE_ID: issueId,\n OVERDECK_SESSION_TYPE: role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n GIT_SEQUENCE_EDITOR: 'false',\n ...flywheelEnv,\n ...providerEnv,\n },\n }));\n if (shouldRegisterConversation) {\n await saveAgentRuntimeState(agentId, {\n claudeSessionId: rawSessionId,\n ...(options.resumeSessionId ? {} : {\n sessionModel: selectedModel,\n sessionHarness: resolvedHarness,\n }),\n });\n }\n await Effect.runPromise(setOption(agentId, 'destroy-unattached', 'off'));\n await Effect.runPromise(setOption(exactPaneTarget(agentId), 'remain-on-exit', 'on'));\n\n if (prompt) {\n if (shouldDeliverPromptViaPi) {\n try {\n await writeOhmypiAgentPrompt(agentId, prompt);\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = true;\n await Effect.runPromise(saveAgentState(state));\n }\n } catch (err) {\n console.error(`[${agentId}] ohmypi prompt delivery failed:`, err instanceof Error ? err.message : String(err));\n }\n } else if (shouldDeliverPromptViaTmux || shouldDeliverPromptViaCodexTui) {\n if (tracksKickoffDelivery) {\n const delivery = await deliverInitialPromptWithRetry(agentId, prompt, 'spawnRun:initial-prompt');\n if (delivery.ok) {\n state.kickoffDelivered = true;\n await Effect.runPromise(saveAgentState(state));\n } else if (delivery.failure === SESSION_EXITED_BEFORE_KICKOFF) {\n await recordStartupSessionExit(state, issueId, role);\n return state;\n }\n } else {\n // PAN-1594: wait for the hook-written ready.json (session-start hook),\n // not a tmux pane-scrape. No dependency on permission-mode footer text.\n const ready = await waitForPromptReady(agentId, resolvedHarness, 30);\n if (ready) {\n await new Promise<void>((resolve) => setTimeout(resolve, 500));\n await deliverAgentMessage(agentId, prompt, 'spawnRun:initial-prompt');\n } else {\n console.error(`[${agentId}] ${resolvedHarness === 'codex' ? 'Codex' : 'Claude'} did not become ready within 30s`);\n }\n }\n }\n }\n\n markAgentRunning(state);\n\n // Stamp the workspace HEAD this role run was launched against. The reactive\n // scheduler uses this to tell a still-relevant run from a zombie session\n // left behind by an agent that finished work but never exited (the ship/test\n // stall class of bug). A non-fatal git probe — if it fails the marker is\n // simply absent and activeRoleRunExists falls back to status-only checks.\n try {\n const { stdout } = await execAsync('git rev-parse --short=8 HEAD', { cwd: workspace });\n const head = stdout.trim();\n if (head) state.roleRunHead = head;\n } catch { /* non-fatal — marker stays absent */ }\n\n await Effect.runPromise(saveAgentState(state));\n\n // PAN-1556: the review role emits a single dedicated \"Review role spawned\"\n // event from spawnReviewRoleForIssue. Suppress the generic per-spawn\n // \"role started\" for review so the orchestrator + 4 convoy sub-reviewers\n // don't each spam the session feed and bury conversations.\n if (role !== 'review') {\n emitActivityEntrySync({\n source: role,\n level: 'info',\n message: `${role} role started for ${issueId}`,\n issueId,\n });\n }\n\n return state;\n}\n\nexport async function spawnAgent(options: SpawnOptions): Promise<AgentState> {\n const role: 'work' | 'strike' | 'knowledge' = options.role ?? 'work';\n const sessionPrefix = role === 'strike' ? 'strike' : 'agent';\n const agentId = options.agentId ?? `${sessionPrefix}-${options.issueId.toLowerCase()}`;\n\n // Check if already running (scoped to the exact session name, including slot suffix)\n if (await Effect.runPromise(sessionExists(agentId))) {\n throw new Error(`Agent ${agentId} already running. Use 'pan tell' to message it.`);\n }\n\n await assertWorkspaceStackHealthyForSpawn(options.issueId, role, options.allowHost, options.workspace);\n\n // Initialize hook for this agent (FPP support)\n initHookSync(agentId);\n\n // Strike agents bypass the normal pipeline (no plan/beads/review/test) —\n // see roles/strike.md. The beads gate is the only thing we skip; everything\n // else (workspace health, supervisor wiring, launcher) is identical.\n if (role !== 'strike' && role !== 'knowledge' && options.slotItemId === undefined) {\n // Use a short lock timeout when spawning from HTTP handlers so dashboard\n // requests fail fast to the JSONL fallback instead of blocking behind CLI\n // processes that hold the cross-process bd lock. The CLI `pan start` path\n // already performs a long-timeout live query before reaching spawnAgent.\n try {\n await Effect.runPromise(\n assertIssueHasBeads(options.workspace, options.issueId, { acquisitionTimeoutMs: 500 }),\n );\n } catch (error) {\n if (error instanceof BeadsMissingError && error.transientFailure !== undefined) {\n const attempts = error.transientFailure instanceof BdTransientFailure\n ? ` after ${error.transientFailure.attempts} attempts`\n : '';\n throw new Error(\n `Beads database was temporarily locked while checking ${options.issueId}${attempts}; re-run shortly.`\n );\n }\n throw error;\n }\n }\n\n // Determine model based on role configuration\n const modelSpawnKey = `${role}:${options.issueId}`;\n const singleTierParams = role === 'work' && options.slotItemId === undefined && options.slotIndex === undefined\n ? resolveSingleWorkTierSpawnParams(options.workspace, options.model, modelSpawnKey)\n : {};\n const selectedModel = determineModel({ model: singleTierParams.model ?? options.model, role, spawnKey: modelSpawnKey });\n console.log(`[DEBUG] Selected model: ${selectedModel}`);\n\n // When routing a GPT agent through ChatGPT subscription auth, the local\n // CLIProxyAPI sidecar MUST already be running. We only check — never\n // install/start from here, because spawnAgent is reachable from dashboard\n // route handlers where blocking on curl/tar would freeze the event loop\n // (see PAN-70 / PAN-446 — no blocking I/O in server code).\n if (\n getProviderForModelSync(selectedModel).name === 'openai'\n && (await getProviderAuthMode(selectedModel)) === 'subscription'\n ) {\n const { isCliproxyRunning } = await import('../cliproxy.js');\n if (!(await Effect.runPromise(isCliproxyRunning()))) {\n throw new Error(\n 'CLIProxyAPI sidecar is not running. GPT subscription agents route through '\n + 'a local cliproxy process managed by `pan up`. Run `pan up` (or restart the '\n + 'dashboard) before spawning a GPT agent.',\n );\n }\n }\n\n const resolvedHarness: RuntimeName = await resolveHarness({\n explicit: options.harness ?? singleTierParams.harness,\n role,\n model: selectedModel,\n });\n\n // Create state\n const state: AgentState = {\n id: agentId,\n issueId: options.issueId,\n workspace: options.workspace,\n harness: resolvedHarness,\n role,\n model: selectedModel,\n modelSpawnKey,\n status: 'starting',\n startedAt: new Date().toISOString(),\n costSoFar: 0,\n hostOverride: options.allowHost || undefined,\n };\n\n const supervisorLaunch = await prepareSupervisorForFreshLaunch(agentId, options, state);\n\n saveAgentStateSync(state);\n\n // Transition issue tracker to \"in progress\" immediately so Linear reflects reality\n // while workspace setup continues. Best-effort, don't block agent spawn.\n // Only for work agents, not planning/specialist agents.\n if (role === 'work') {\n try {\n const resetStatus = resetPipelineVerdictsForWorkStartSync(options.issueId);\n if (resetStatus) {\n const { resetPostMergeState } = await import('../cloister/merge-agent.js');\n resetPostMergeState(options.issueId);\n }\n } catch (err) {\n console.warn(`[agents] Could not reset stale pipeline verdicts for ${options.issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n transitionIssueToInProgress(options.issueId, options.workspace).catch((err) => {\n console.warn(`[agents] Could not transition ${options.issueId} to in_progress: ${err.message}`);\n });\n }\n\n // For child stories: synthesize feature context from parent feature plan\n // before the agent starts so readFeatureContext has O(1) local access.\n if (role === 'work') {\n try {\n const { writeStoryFeatureContext } = await import('../cloister/work-agent-prompt.js');\n await writeStoryFeatureContext(options.workspace, options.issueId);\n } catch (ctxErr: any) {\n console.warn(`[agents] Could not write story feature context for ${options.issueId}: ${ctxErr.message}`);\n }\n }\n\n // PAN-1215: One-shot cleanup of tracked workspace-only .pan/ artifacts.\n // These files are gitignored but may still be tracked on older branches.\n // If tracked, checkpoint commits and rebases can drop them, breaking the\n // verification gate. Remove them from the index when the workspace is clean.\n if (role === 'work') {\n try {\n const workspace = options.workspace;\n const project = findProjectByPathSync(workspace);\n if (project && !shouldCommitLegacyWorkspaceArtifacts(await isStateMigrated(project))) {\n console.warn(`[agents] Deferred legacy .pan/ index cleanup for ${options.issueId} — migrated projects use gitignored .overdeck/ runtime files; historical entries retire with the branch`);\n } else {\n const { stdout: trackedFiles } = await execAsync(\n 'git ls-files .pan/continue.json .pan/spec.vbrief.json',\n { cwd: workspace },\n );\n if (trackedFiles.trim()) {\n const { stdout: porcelain } = await execAsync(\n 'git status --porcelain -- .pan/',\n { cwd: workspace },\n );\n if (!porcelain.trim()) {\n await execAsync(\n 'git rm --cached --ignore-unmatch .pan/continue.json .pan/spec.vbrief.json',\n { cwd: workspace },\n );\n await execAsync(\n 'git commit -m \"chore: untrack workspace .pan/ artifacts (PAN-1215)\"',\n { cwd: workspace },\n );\n console.log(`[agents] Untracked workspace .pan/ artifacts for ${options.issueId}`);\n } else {\n console.warn(`[agents] Skipping .pan/ untrack for ${options.issueId} — .pan/ paths have uncommitted changes`);\n }\n }\n }\n } catch (err: any) {\n console.warn(`[agents] .pan/ untrack cleanup failed for ${options.issueId}: ${err.message}`);\n }\n }\n\n // Build prompt with FPP work if available\n let prompt = options.prompt || '';\n\n // FPP: Check for pending work on hook\n const { hasWork } = checkHookSync(agentId);\n if (hasWork) {\n const fixedPointPrompt = generateFixedPointPromptSync(agentId);\n if (fixedPointPrompt) {\n prompt = fixedPointPrompt + '\\n\\n---\\n\\n' + prompt;\n }\n }\n\n if (prompt) {\n prompt = await withSpawnTimeMemoryContext({\n prompt,\n issueId: options.issueId,\n workspace: options.workspace,\n agentId,\n role,\n harness: resolvedHarness,\n });\n }\n\n // Write prompt to file for complex prompts (avoids shell escaping issues)\n const promptFile = join(getAgentDir(agentId), 'initial-prompt.md');\n const tracksKickoffDelivery = role === 'work' || role === 'strike';\n if (prompt) {\n await writeFileAsync(promptFile, prompt);\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = false;\n saveAgentStateSync(state);\n }\n }\n\n // Auto-setup hooks if not configured\n checkAndSetupHooks();\n\n // Ensure TLDR daemon is running for the workspace (non-blocking, non-fatal).\n // Gated by the operator TLDR toggle: when disabled, the daemon is not started\n // and the agent (whose prompt reports TLDR_AVAILABLE=false) degrades to direct\n // file reads.\n try {\n const venvPath = join(options.workspace, '.venv');\n if (isTldrEnabledSync() && existsSync(venvPath)) {\n const { getTldrDaemonServiceSync } = await import('../tldr-daemon.js');\n const tldrService = getTldrDaemonServiceSync(options.workspace, venvPath);\n const status = await tldrService.getStatus();\n if (!status.running) {\n await tldrService.start(true);\n console.log(`[${agentId}] Started TLDR daemon for workspace`);\n }\n }\n } catch {\n // Non-fatal — agents degrade to direct file reads if TLDR unavailable\n }\n\n // Write initial task cache for heartbeat hook\n writeTaskCache(agentId, options.issueId);\n\n // Clear ready signal before spawning (clean slate for PAN-87 fix)\n clearReadySignal(agentId);\n\n // Channels MCP gate: only the explicit legacy override writes a per-agent\n // .mcp.json, bridge token, and channelsEnabled state for new spawns. The PTY\n // supervisor remains the default delivery transport.\n const channelsDecision = decideChannelsForWorkAgent(agentId, options, state);\n let channelsBridgeMcpConfig: string | undefined;\n if (channelsDecision.eligible) {\n channelsBridgeMcpConfig = join(options.workspace, '.pan', 'agent-mcp.json');\n writeBridgeTokenSync(agentId);\n await writeChannelsBridgeMcpConfig(channelsBridgeMcpConfig, agentId);\n state.channelsEnabled = true;\n saveAgentStateSync(state);\n }\n\n const flywheelEnv = resolveFlywheelSpawnEnv(role, options.flywheelRunId);\n const { launcherContent, providerEnv } = await buildAgentLaunchConfig({\n agentId,\n model: selectedModel,\n workspace: options.workspace,\n role,\n isPlanning: false,\n channelsBridgeMcpConfig,\n useSupervisor: supervisorLaunch.useSupervisor,\n supervisorScriptPath: supervisorLaunch.supervisorScriptPath,\n harness: state.harness ?? 'claude-code',\n extraEnvExports: flywheelEnvExports(flywheelEnv),\n effort: options.effort,\n });\n\n const launcherScript = join(getAgentDir(agentId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n const claudeCmd = `bash ${launcherScript}`;\n console.log(`[claude-invoke] purpose=work-agent | model=${state.model} | source=agents.ts:spawnAgent | session=${agentId} | command=\"${claudeCmd}\"`);\n\n // Pre-trust workspace directory in Claude Code to avoid the trust prompt\n try {\n const { preTrustDirectory } = await import('../workspace-manager.js') as { preTrustDirectory: (dir: string) => void };\n preTrustDirectory(options.workspace);\n } catch { /* non-fatal */ }\n\n // Configure workspace for GitHub App bot identity (PAN-536)\n // Agents push as panopticon-agent[bot] with short-lived installation tokens\n try {\n const { isGitHubAppConfigured, generateInstallationToken, configureWorkspaceForBot } = await import('../github-app.js');\n if (isGitHubAppConfigured()) {\n const { findProjectByPathSync } = await import('../projects.js');\n const project = findProjectByPathSync(resolve(options.workspace, '..', '..'));\n const ghRepo = project?.github_repo;\n if (ghRepo) {\n const [owner, repo] = ghRepo.split('/');\n const { token } = await Effect.runPromise(generateInstallationToken());\n await configureWorkspaceForBot(options.workspace, owner, repo, token);\n console.log(`[${agentId}] Configured workspace for bot push (panopticon-agent[bot])`);\n }\n }\n } catch (err: any) {\n console.warn(`[${agentId}] GitHub App config failed (falling back to SSH): ${err.message}`);\n }\n\n clearReadySignal(agentId);\n\n await Effect.runPromise(createSession(agentId, options.workspace, claudeCmd, {\n env: {\n ...BLANKED_PROVIDER_ENV, // Blank stale provider vars inherited by tmux server\n TERM: 'xterm-256color',\n OVERDECK_AGENT_ID: agentId,\n OVERDECK_ISSUE_ID: options.issueId,\n OVERDECK_SESSION_TYPE: role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false', // Disable suggested prompts for autonomous agents (PAN-251)\n GIT_SEQUENCE_EDITOR: 'false', // Block interactive rebase / squash (agents forbidden from rewriting history)\n ...flywheelEnv,\n ...providerEnv, // Set correct provider env vars (BASE_URL, AUTH_TOKEN, etc.)\n }\n }));\n await saveAgentRuntimeState(agentId, {\n sessionModel: selectedModel,\n sessionHarness: resolvedHarness,\n });\n\n // Channels: start dismissing the dev-channels confirmation dialog as soon as\n // the tmux session exists, but only block on completion when we are about to\n // deliver an initial prompt. Spawn-only callers should not sit in a 20s poll\n // loop waiting for a dialog they may never need.\n const dismissChannelsDialogPromise = channelsBridgeMcpConfig\n ? dismissDevChannelsDialog(agentId).catch(() => undefined)\n : null;\n\n // Send the initial prompt after the interactive prompt is ready.\n if (prompt && resolvedHarness === 'ohmypi') {\n try {\n await writeOhmypiAgentPrompt(agentId, prompt);\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = true;\n saveAgentStateSync(state);\n }\n } catch (err) {\n console.error(`[${agentId}] ohmypi prompt delivery failed:`, err instanceof Error ? err.message : String(err));\n if (tracksKickoffDelivery) {\n await recordKickoffDeliveryFailure(state, options.issueId, role);\n if (role === 'strike') {\n await Effect.runPromise(stopAgent(agentId));\n throw new Error(`Agent ${agentId} kickoff delivery failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n return state;\n }\n }\n } else if (prompt) {\n if (dismissChannelsDialogPromise) {\n await dismissChannelsDialogPromise;\n }\n const delivery = await deliverInitialPromptWithRetry(agentId, prompt, 'spawnAgent:initial-prompt', state.deliveryMethod);\n if (delivery.ok) {\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = true;\n saveAgentStateSync(state);\n }\n } else if (tracksKickoffDelivery) {\n if (delivery.failure === SESSION_EXITED_BEFORE_KICKOFF) {\n await recordStartupSessionExit(state, options.issueId, role);\n }\n await recordKickoffDeliveryFailure(state, options.issueId, role);\n if (role === 'strike') {\n await Effect.runPromise(stopAgent(agentId));\n throw new Error(`Agent ${agentId} kickoff delivery failed: ${delivery.failure ?? 'unknown error'}`);\n }\n return state;\n }\n }\n\n // For codex work agents, poll for the first rollout JSONL in the background\n // and persist the thread-id so transcript/cost lookups hit the fast path\n // (PAN-1805). Non-blocking — codex writes its rollout only after the kickoff\n // prompt lands, so a blocking wait here would stall spawn. The latest-rollout\n // fallback covers sessions whose first turn lands after this window.\n if (resolvedHarness === 'codex') {\n const codexHomeForAgent = join(homedir(), '.overdeck', 'agents', agentId, 'codex-home');\n void (async () => {\n try {\n const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } =\n await import('../runtimes/codex.js');\n const rollout = await waitForCodexRollout(codexHomeForAgent, 120_000);\n if (rollout) {\n const threadId = extractThreadIdFromRollout(rollout);\n if (threadId) writeThreadId(agentId, threadId);\n }\n } catch { /* non-fatal — the latest-rollout fallback still resolves the transcript */ }\n })();\n }\n\n // Update status\n markAgentRunning(state);\n saveAgentStateSync(state);\n\n // Track work in CV\n startWorkSync(agentId, options.issueId);\n\n // Emit activity + TTS so the user knows an agent has started\n emitActivityEntrySync({\n source: role,\n level: 'info',\n message: `Work agent started for ${options.issueId}`,\n issueId: options.issueId,\n });\n emitActivityTtsSync({\n utterance: `Work agent started for ${options.issueId}`,\n priority: 2,\n issueId: options.issueId,\n source: 'work-agent',\n eventType: 'workAgent.started',\n });\n\n return state;\n}\n\nfunction assertRegisteredSlotCap(issueId: string, configuredCap?: number): void {\n const cap = configuredCap ?? getConcurrencyLimits().maxWorkAgents;\n if (!Number.isInteger(cap) || cap < 1) {\n throw new Error(`Registered slot cap must be a positive integer; got ${cap}.`);\n }\n\n const issueLower = issueId.toLowerCase();\n const slotAgentPattern = new RegExp(`^agent-${escapeRegExp(issueLower)}-slot-\\\\d+$`);\n const activeSlots = listAgentStates({ role: 'work' }).filter(agent =>\n slotAgentPattern.test(agent.id)\n && (agent.status === 'starting' || agent.status === 'running')\n );\n if (activeSlots.length >= cap) {\n throw new Error(\n `Registered slot cap reached for ${issueId}: ${activeSlots.length}/${cap} active slot agents.`\n );\n }\n}\n\nfunction buildRegisteredSlotPrompt(\n issueId: string,\n baseWorkspace: string,\n slot: RegisteredSlotSpawn,\n extraPrompt?: string,\n): string {\n const doc = readWorkspacePlanSync(baseWorkspace);\n if (!doc) {\n throw new Error(\n `Registered slot spawn for ${issueId} requires a readable vBRIEF plan in ${baseWorkspace}.`\n );\n }\n\n const slice = createActiveSlice(doc, {\n issueId: issueId.toUpperCase(),\n itemId: slot.slotItemId,\n currentItemIds: [slot.slotItemId],\n });\n\n const lines = [\n `# Registered Slot Assignment: ${slot.slotItemId}`,\n '',\n `Issue: ${issueId}`,\n `Slot: ${slot.slotIndex}`,\n `Agent: ${slot.agentId}`,\n `Branch: ${slot.branch}`,\n `Workspace: ${slot.workspace}`,\n '',\n 'You are a registered slot work agent. Implement only the target vBRIEF item below, keep changes scoped to that item, and do not merge this slot branch yourself.',\n '',\n slice.prompt,\n ];\n\n const trimmedExtra = extraPrompt?.trim();\n if (trimmedExtra) {\n lines.push('', '## Additional Foreman Instructions', trimmedExtra);\n }\n\n return lines.join('\\n');\n}\n\nasync function ensureRegisteredSlotWorktree(baseWorkspace: string, slot: RegisteredSlotSpawn): Promise<void> {\n if (existsSync(slot.workspace)) return;\n\n await mkdir(dirname(slot.workspace), { recursive: true });\n const branchExists = await gitBranchExists(baseWorkspace, slot.branch);\n const target = JSON.stringify(slot.workspace);\n const branch = JSON.stringify(slot.branch);\n const command = branchExists\n ? `git worktree add ${target} ${branch}`\n : `git worktree add -b ${branch} ${target} HEAD`;\n await execAsync(command, { cwd: baseWorkspace });\n}\n\nasync function gitBranchExists(workspace: string, branch: string): Promise<boolean> {\n try {\n await execAsync(`git show-ref --verify --quiet ${JSON.stringify(`refs/heads/${branch}`)}`, { cwd: workspace });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Check if Overdeck hooks are configured, and auto-setup if not\n */\nfunction checkAndSetupHooks(): void {\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n const hookPath = join(homedir(), '.overdeck', 'bin', 'heartbeat-hook');\n\n // Check if settings.json exists and has heartbeat hook configured\n if (existsSync(settingsPath)) {\n try {\n const settingsContent = readFileSync(settingsPath, 'utf-8');\n const settings = JSON.parse(settingsContent);\n const postToolUse = settings?.hooks?.PostToolUse || [];\n\n const hookConfigured = postToolUse.some((hookConfig: any) =>\n hookConfig.hooks?.some((hook: any) =>\n hook.command === hookPath ||\n hook.command?.includes('overdeck') ||\n hook.command?.includes('heartbeat-hook')\n )\n );\n\n if (hookConfigured) {\n return; // Already configured\n }\n } catch {\n // Ignore errors, will attempt setup\n }\n }\n\n // Hooks not configured - run setup silently\n try {\n console.log('Configuring Overdeck heartbeat hooks...');\n // Note: This runs during spawn which is now async, so we can use execAsync\n // But this is called from a sync context in checkAndSetupHooks, so we use fire-and-forget\n exec('pan admin hooks install', (error: Error | null) => {\n if (error) {\n console.warn('⚠ Failed to auto-configure hooks. Run `pan admin hooks install` manually.');\n } else {\n console.log('✓ Heartbeat hooks configured');\n }\n });\n } catch (error) {\n console.warn('⚠ Failed to auto-configure hooks. Run `pan admin hooks install` manually.');\n }\n}\n\n/**\n * Write task cache for heartbeat hook to use\n */\nfunction writeTaskCache(agentId: string, issueId: string): void {\n const cacheDir = join(getAgentDir(agentId));\n mkdirSync(cacheDir, { recursive: true });\n\n const cacheFile = join(cacheDir, 'current-task.json');\n writeFileSync(\n cacheFile,\n JSON.stringify({\n id: issueId,\n title: `Working on ${issueId}`,\n updated_at: new Date().toISOString()\n }, null, 2)\n );\n}\n","/**\n * oh-my-pi (omp) JSONL parser (PAN-1989). Forked from pi-parser.ts (PAN-636).\n *\n * omp sessions live at:\n * ~/.omp/agent/sessions/<encoded-cwd>/<timestamp>_<session-id>.jsonl\n *\n * Format (omp v3 — same as Pi v3):\n * - First line: { type: \"session\", version: 3, id, timestamp, cwd }\n * - Subsequent: every entry has an opaque `id` and a `parentId`. parentId\n * is null for the first non-session entry.\n * - The graph is a tree (omp supports forking/branching), not a list.\n *\n * To compute usage for the *active* branch we walk leaf -> root via parentId\n * from the latest leaf (max timestamp among entries that have no children).\n * omp assistant messages report per-call usage — input/output tokens are NOT\n * cumulative across the conversation — so summing along the active branch is\n * correct without subtracting pre-compaction input.\n *\n * SessionUsage shape comes from jsonl-parser.ts (the Claude Code parser); we\n * map into it unchanged so all upstream consumers keep working.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { Effect } from 'effect';\nimport type { SessionUsage } from './jsonl-parser.js';\nimport { getPricingSync, type AIProvider, type TokenUsage } from '../cost.js';\nimport { FsError } from '../errors.js';\n\n// Minimal entry shape we care about. Anything else is ignored.\ninterface PiSessionRoot {\n type: 'session';\n version: number;\n id: string;\n timestamp: string;\n cwd?: string;\n}\n\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n cost?: {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n total?: number;\n };\n}\n\ninterface PiMessageEntry {\n type: 'message';\n id: string;\n parentId: string | null;\n timestamp: string;\n message: {\n role: 'user' | 'assistant' | 'toolResult';\n content?: unknown;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n };\n}\n\nexport interface OhmypiCostEventUsage {\n requestId: string;\n timestamp: string;\n sessionId: string;\n sessionFile: string;\n provider: string | null;\n model: string;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n cost: number;\n warnings?: Array<{ type: 'unpriced-model'; provider: string | null; model: string; reason: string }>;\n}\n\ninterface PiGenericEntry {\n type: string;\n id: string;\n parentId: string | null;\n timestamp: string;\n // any other fields permitted; we only inspect them when type is known.\n [key: string]: unknown;\n}\n\ntype PiEntry = PiMessageEntry | PiGenericEntry;\n\nfunction isMessage(entry: PiEntry): entry is PiMessageEntry {\n return entry.type === 'message';\n}\n\nfunction isAssistantMessage(entry: PiEntry): entry is PiMessageEntry {\n return isMessage(entry) && entry.message?.role === 'assistant';\n}\n\nfunction pricingProviderFor(provider: string | null, model: string): AIProvider | null {\n const lowerProvider = provider?.toLowerCase() ?? '';\n const lowerModel = model.toLowerCase();\n if (lowerProvider === 'openai' || lowerProvider === 'openai-codex' || lowerModel.includes('gpt')) return 'openai';\n if (lowerProvider === 'google' || lowerModel.includes('gemini')) return 'google';\n if (lowerProvider === 'anthropic' || lowerModel.includes('claude')) return 'anthropic';\n if (\n lowerProvider === 'kimi'\n || lowerProvider === 'minimax'\n || lowerProvider === 'zai'\n || lowerProvider === 'zhipu'\n || lowerProvider === 'custom'\n || lowerModel.includes('kimi')\n || lowerModel.includes('minimax')\n || lowerModel.includes('glm')\n ) {\n return 'custom';\n }\n return null;\n}\n\nfunction computeCostFromPricing(\n provider: string | null,\n model: string,\n usage: TokenUsage,\n): { cost: number; reason?: string } {\n const pricingProvider = pricingProviderFor(provider, model);\n if (!pricingProvider) return { cost: 0, reason: 'unknown-provider' };\n\n const pricing = getPricingSync(pricingProvider, model);\n if (!pricing) return { cost: 0, reason: 'unpriced-model' };\n\n return {\n cost:\n (usage.inputTokens / 1000) * pricing.inputPer1k\n + (usage.outputTokens / 1000) * pricing.outputPer1k\n + ((usage.cacheReadTokens ?? 0) / 1000) * (pricing.cacheReadPer1k ?? 0)\n + ((usage.cacheWriteTokens ?? 0) / 1000) * (pricing.cacheWrite5mPer1k ?? 0),\n };\n}\n\nfunction tsMs(timestamp: string | undefined): number {\n if (!timestamp) return 0;\n const t = Date.parse(timestamp);\n return Number.isFinite(t) ? t : 0;\n}\n\nexport interface ParseResult {\n ok: boolean;\n reason?: string;\n usage?: SessionUsage;\n usageEvents?: OhmypiCostEventUsage[];\n unpricedModels?: Array<{ provider: string | null; model: string; reason: string }>;\n}\n\n// Known top-level entry types. Anything else is logged once per session.\nconst KNOWN_TYPES: ReadonlySet<string> = new Set([\n 'session',\n 'message',\n 'model_change',\n 'thinking_level_change',\n 'compaction_start',\n 'compaction_end',\n 'session_before_compact',\n 'session_compact',\n 'session_before_switch',\n 'session_before_fork',\n 'session_start',\n]);\n\n/**\n * Parse a Pi session JSONL file into a SessionUsage record.\n *\n * Returns null if the file does not exist, is empty, or is unparseable. Errors\n * during line parsing are tolerated (the line is skipped); we never throw.\n */\nexport function parseOhmypiSessionSync(filePath: string): SessionUsage | null {\n if (!existsSync(filePath)) return null;\n const result = parseOhmypiSessionContent(readFileSync(filePath, 'utf8'), filePath);\n return result.ok ? result.usage! : null;\n}\n\nexport function parseOhmypiSessionCostEventsSync(filePath: string): OhmypiCostEventUsage[] {\n if (!existsSync(filePath)) return [];\n const result = parseOhmypiSessionContent(readFileSync(filePath, 'utf8'), filePath);\n return result.ok ? result.usageEvents ?? [] : [];\n}\n\nexport function parseOhmypiSessionCostResultSync(filePath: string): ParseResult {\n if (!existsSync(filePath)) return { ok: false, reason: 'unreadable' };\n try {\n return parseOhmypiSessionContent(readFileSync(filePath, 'utf8'), filePath);\n } catch {\n return { ok: false, reason: 'unreadable' };\n }\n}\n\n/**\n * Parse Pi session JSONL content. Exposed for tests and callers that already\n * have the file content in memory.\n */\nexport function parseOhmypiSessionContent(content: string, filePath = '<inline>'): ParseResult {\n const lines = content.split('\\n').filter(l => l.trim().length > 0);\n if (lines.length === 0) {\n return { ok: false, reason: 'empty' };\n }\n\n let root: PiSessionRoot | null = null;\n const entriesById = new Map<string, PiEntry>();\n const childrenOf = new Map<string | null, string[]>();\n const unknownTypesSeen = new Set<string>();\n\n for (const line of lines) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n continue;\n }\n if (!parsed || typeof parsed !== 'object') continue;\n const entry = parsed as PiEntry;\n\n if (entry.type === 'session') {\n root = entry as unknown as PiSessionRoot;\n continue;\n }\n\n if (!entry.id) continue;\n\n if (!KNOWN_TYPES.has(entry.type)) {\n if (!unknownTypesSeen.has(entry.type)) {\n unknownTypesSeen.add(entry.type);\n // eslint-disable-next-line no-console\n console.debug(`[ohmypi-parser] unknown entry type \"${entry.type}\" in ${filePath} — ignoring`);\n }\n // Still track the entry in the graph so downstream branches stay linked.\n }\n\n entriesById.set(entry.id, entry);\n const list = childrenOf.get(entry.parentId ?? null);\n if (list) list.push(entry.id);\n else childrenOf.set(entry.parentId ?? null, [entry.id]);\n }\n\n if (!root) {\n return { ok: false, reason: 'missing-session-root' };\n }\n\n if (entriesById.size === 0) {\n // Session with only the root entry — return a minimal SessionUsage.\n return {\n ok: true,\n usage: emptyUsage(filePath, root),\n };\n }\n\n // Find leaves: entries that have no children.\n const leaves: PiEntry[] = [];\n for (const entry of entriesById.values()) {\n const kids = childrenOf.get(entry.id);\n if (!kids || kids.length === 0) leaves.push(entry);\n }\n if (leaves.length === 0) {\n // Cycle or all entries have children — fall back to \"pick latest entry\".\n leaves.push(...entriesById.values());\n }\n\n // Pick the latest leaf by timestamp; tie-break by file order via id appearance.\n let latestLeaf: PiEntry = leaves[0]!;\n for (const leaf of leaves) {\n if (tsMs(leaf.timestamp) > tsMs(latestLeaf.timestamp)) {\n latestLeaf = leaf;\n }\n }\n\n // Walk leaf -> root via parentId, collecting entries in reverse-chronological order.\n const branch: PiEntry[] = [];\n const visited = new Set<string>();\n let cursor: PiEntry | undefined = latestLeaf;\n while (cursor) {\n if (visited.has(cursor.id)) break; // cycle guard\n visited.add(cursor.id);\n branch.push(cursor);\n if (!cursor.parentId) break;\n cursor = entriesById.get(cursor.parentId);\n }\n branch.reverse(); // chronological\n\n // Aggregate usage and per-model breakdown across assistant messages on the\n // active branch.\n const totals: TokenUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n };\n let costV2 = 0;\n let messageCount = 0;\n let costRecomputedFromTokens = 0;\n const modelBreakdown: Record<\n string,\n { cost: number; inputTokens: number; outputTokens: number; messageCount: number; cacheReadTokens?: number; cacheWriteTokens?: number }\n > = {};\n const usageEvents: OhmypiCostEventUsage[] = [];\n const unpricedModels: Array<{ provider: string | null; model: string; reason: string }> = [];\n const modelsInOrder: string[] = [];\n let compactionCount = 0;\n\n let firstTs: string | null = null;\n let lastTs: string | null = null;\n\n for (const entry of branch) {\n if (entry.type === 'compaction_start' || entry.type === 'session_compact') {\n compactionCount += 1;\n }\n if (!firstTs && entry.timestamp) firstTs = entry.timestamp;\n if (entry.timestamp) lastTs = entry.timestamp;\n\n if (!isAssistantMessage(entry)) continue;\n const usage = entry.message.usage;\n if (!usage) continue;\n const model = entry.message.model || 'unknown';\n const provider = typeof entry.message.provider === 'string' ? entry.message.provider : null;\n const input = usage.input ?? 0;\n const output = usage.output ?? 0;\n const cacheRead = usage.cacheRead ?? 0;\n const cacheWrite = usage.cacheWrite ?? 0;\n const tokenUsage: TokenUsage = {\n inputTokens: input,\n outputTokens: output,\n cacheReadTokens: cacheRead,\n cacheWriteTokens: cacheWrite,\n };\n const hasInlineCost = usage.cost?.total !== undefined;\n let cost = usage.cost?.total ?? 0;\n let warning: OhmypiCostEventUsage['warnings'] | undefined;\n if (!hasInlineCost && input + output + cacheRead + cacheWrite > 0) {\n const computed = computeCostFromPricing(provider, model, tokenUsage);\n cost = computed.cost;\n if (computed.reason) {\n unpricedModels.push({ provider, model, reason: computed.reason });\n warning = [{ type: 'unpriced-model', provider, model, reason: computed.reason }];\n }\n }\n const localComputed = hasInlineCost\n ? (usage.cost?.input ?? 0) +\n (usage.cost?.output ?? 0) +\n (usage.cost?.cacheRead ?? 0) +\n (usage.cost?.cacheWrite ?? 0)\n : cost;\n\n totals.inputTokens += input;\n totals.outputTokens += output;\n totals.cacheReadTokens = (totals.cacheReadTokens ?? 0) + cacheRead;\n totals.cacheWriteTokens = (totals.cacheWriteTokens ?? 0) + cacheWrite;\n costV2 += cost;\n costRecomputedFromTokens += localComputed;\n messageCount += 1;\n\n if (!modelBreakdown[model]) {\n modelBreakdown[model] = { cost: 0, inputTokens: 0, outputTokens: 0, messageCount: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };\n modelsInOrder.push(model);\n }\n const slot = modelBreakdown[model]!;\n slot.cost += cost;\n slot.inputTokens += input;\n slot.outputTokens += output;\n slot.messageCount += 1;\n slot.cacheReadTokens = (slot.cacheReadTokens ?? 0) + cacheRead;\n slot.cacheWriteTokens = (slot.cacheWriteTokens ?? 0) + cacheWrite;\n\n usageEvents.push({\n requestId: `ohmypi:${root.id}:${entry.id}`,\n timestamp: entry.timestamp || root.timestamp,\n sessionId: root.id,\n sessionFile: filePath,\n provider,\n model,\n input,\n output,\n cacheRead,\n cacheWrite,\n cost,\n ...(warning ? { warnings: warning } : {}),\n });\n }\n\n // Observability: log Pi-inline-cost vs locally-recomputed-from-cost-fields\n // delta. This catches cases where Pi's pricing tables drift inside Pi\n // itself; we always trust costV2 (Pi's inline total).\n if (Math.abs(costV2 - costRecomputedFromTokens) > 1e-9) {\n // eslint-disable-next-line no-console\n console.debug(\n `[ohmypi-parser] inline cost vs sum-of-line-items delta=${(costV2 - costRecomputedFromTokens).toFixed(6)} in ${filePath}`,\n );\n }\n if (compactionCount > 0) {\n // eslint-disable-next-line no-console\n console.debug(\n `[ohmypi-parser] active branch in ${filePath} has ${compactionCount} compaction event(s) — usage summed per-message (no double-count)`,\n );\n }\n\n const display =\n modelsInOrder.length === 0\n ? 'unknown'\n : modelsInOrder.length === 1\n ? modelsInOrder[0]!\n : modelsInOrder.join(' → ');\n\n return {\n ok: true,\n usage: {\n sessionId: root.id,\n sessionFile: filePath,\n startTime: firstTs ?? root.timestamp,\n endTime: lastTs ?? root.timestamp,\n model: display,\n usage: totals,\n cost: costV2,\n cost_v2: costV2,\n messageCount,\n modelBreakdown,\n },\n usageEvents,\n unpricedModels,\n };\n}\n\nfunction emptyUsage(filePath: string, root: PiSessionRoot): SessionUsage {\n return {\n sessionId: root.id,\n sessionFile: filePath,\n startTime: root.timestamp,\n endTime: root.timestamp,\n model: 'unknown',\n usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\n cost: 0,\n cost_v2: 0,\n messageCount: 0,\n modelBreakdown: {},\n };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Effect variant of parseOhmypiSession. */\nexport const parseOhmypiSession = (\n filePath: string,\n): Effect.Effect<SessionUsage | null, FsError> =>\n Effect.try({\n try: () => parseOhmypiSessionSync(filePath),\n catch: (cause) => new FsError({ path: filePath, operation: 'parseOhmypiSession', cause }),\n });\n\nexport const parseOhmypiSessionCostEvents = (\n filePath: string,\n): Effect.Effect<OhmypiCostEventUsage[], FsError> =>\n Effect.try({\n try: () => parseOhmypiSessionCostEventsSync(filePath),\n catch: (cause) => new FsError({ path: filePath, operation: 'parseOhmypiSessionCostEvents', cause }),\n });\n","/**\n * oh-my-pi (omp) coding agent runtime adapter (PAN-1989).\n *\n * Implements AgentRuntime for the `omp` binary running in --mode rpc. The\n * adapter wires together three pieces:\n *\n * - ohmypi-fifo.ts — per-agent rpc.in named pipe lifecycle\n * - launcher-generator — emits the `exec omp --mode rpc ... < <fifo>` script\n * - ohmypi-extension — the vendored extension that writes ready.json,\n * heartbeats, and /pan-done markers from inside omp\n *\n * Spawn flow:\n * 1. mkdir agent dir\n * 2. createOhmypiFifo\n * 3. write launcher.sh via generateLauncherScript({ harness: 'ohmypi', ... })\n * 4. tmux new-session running launcher.sh\n * 5. wait for ~/.overdeck/agents/<id>/ready.json (max 120s)\n *\n * Runtime divergences vs pi (omp contract AC-1b, AC-3):\n * - omp binary requires Bun >=1.3.14 (shebang #!/usr/bin/env bun).\n * - Resume flag: `--resume <id>` (NOT `--session <id>` which omp accepts but\n * silently ignores for resume — see AC-3 in docs/ohmypi-contract.md).\n * - `--no-context-files` is removed; drop it from the launcher command.\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { promisify } from 'node:util'\nimport { exec } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport { Effect } from 'effect'\nimport type {\n AgentRuntime,\n AgentRuntimeSync,\n AgentRuntimeError,\n HarnessBehavior,\n Heartbeat,\n TokenUsage,\n CostBreakdown,\n Session,\n SpawnConfig,\n Agent,\n} from './types.js'\nimport { OHMYPI_BEHAVIOR } from './behavior.js'\nimport { sessionExists, killSession, createSession, listSessionsSync } from '../tmux.js'\nimport { parseOhmypiSessionSync } from '../cost-parsers/ohmypi-parser.js'\nimport { generateLauncherScriptSync } from '../launcher-generator.js'\nimport { createOhmypiFifo, destroyOhmypiFifoSync, writeOhmypiCommandSync, ohmypiFifoPaths, OhmypiNotReady } from './ohmypi-fifo.js'\nimport { ProcessSpawnError, ProcessTimeoutError, TmuxError } from '../errors.js'\nimport { getOverdeckHome } from '../paths.js'\n\nconst execAsync = promisify(exec)\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\nconst ACTIVE_HEARTBEAT_TTL_MS = 60_000\nconst SPAWN_READY_TIMEOUT_MS = 120_000\n\nfunction overdeckDir(): string {\n return getOverdeckHome()\n}\nfunction heartbeatsDir(): string {\n return join(overdeckDir(), 'heartbeats')\n}\nfunction agentsDir(): string {\n return join(overdeckDir(), 'agents')\n}\n\nexport class OhmypiSpawnTimeout extends Error {\n readonly code = 'OHMYPI_SPAWN_TIMEOUT' as const\n constructor(agentId: string) {\n super(`omp agent ${agentId} did not write ready.json within ${SPAWN_READY_TIMEOUT_MS}ms${describeSpawnFailure(agentId)}`)\n this.name = 'OhmypiSpawnTimeout'\n }\n}\n\nexport interface OhmypiSpawnConfig extends SpawnConfig {\n /** Absolute path to packages/ohmypi-extension/dist/index.js. */\n piExtensionPath: string\n /** Optional extra args appended to the `omp` command line. */\n extraPiArgs?: string\n}\n\nfunction agentDirFor(agentId: string): string {\n return join(agentsDir(), agentId)\n}\n\nfunction describeSpawnFailure(agentId: string): string {\n const outputPath = join(agentDirFor(agentId), 'output.log')\n if (!existsSync(outputPath)) return ''\n try {\n const tail = readFileSync(outputPath, 'utf8').slice(-1500).trim().split('\\n').slice(-8).join('\\n')\n return tail ? ` [output.log tail:\\n${tail}]` : ''\n } catch {\n return ''\n }\n}\n\nfunction ohmypiSessionDirFor(agentId: string): string {\n return join(agentDirFor(agentId), 'sessions')\n}\n\nfunction readyPathFor(agentId: string): string {\n return ohmypiFifoPaths(agentId).readyPath\n}\n\nfunction sessionIdPathFor(agentId: string): string {\n return join(agentDirFor(agentId), 'session.id')\n}\n\n/**\n * Read the persisted omp session id for resume.\n * Returns null when the file is absent or unreadable.\n */\nfunction readStoredSessionId(agentId: string): string | null {\n const path = sessionIdPathFor(agentId)\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf8').trim()\n return raw || null\n } catch {\n return null\n }\n}\n\n/**\n * Resolve the real omp session id from the freshest session JSONL (PAN-1988 parity).\n * Returns null when no prior session exists on disk.\n *\n * Exported (PAN-2098) so the generic recovery path in agents.ts\n * (`getLatestSessionIdSync`) can resume a crashed ohmypi agent — omp never\n * writes a `session.id` file, so without this fallback the deacon reports\n * \"no saved session id\" and can only respawn fresh, losing context.\n */\nexport function resolveLatestOhmypiSessionId(agentId: string): string | null {\n const root = ohmypiSessionDirFor(agentId)\n if (!existsSync(root)) return null\n const files: { path: string; mtime: number }[] = []\n walkJsonl(root, files)\n if (files.length === 0) return null\n files.sort((a, b) => b.mtime - a.mtime)\n const parsed = parseOhmypiSessionSync(files[0]!.path)\n return parsed?.sessionId ?? null\n}\n\nexport class OhmypiRuntimeSync implements AgentRuntimeSync {\n readonly name = 'ohmypi' as const\n\n getHarnessBehavior(): HarnessBehavior {\n return OHMYPI_BEHAVIOR\n }\n\n getSessionPath(agentId: string): string | null {\n const root = ohmypiSessionDirFor(agentId)\n if (!existsSync(root)) return null\n const files: { path: string; mtime: number }[] = []\n walkJsonl(root, files)\n if (files.length === 0) return null\n files.sort((a, b) => b.mtime - a.mtime)\n return files[0]!.path\n }\n\n getLastActivity(agentId: string): Date | null {\n const session = this.getSessionPath(agentId)\n if (!session) return null\n try {\n return statSync(session).mtime\n } catch {\n return null\n }\n }\n\n getHeartbeat(agentId: string): Heartbeat | null {\n // 1. Fresh active heartbeat (<60s old).\n const heartbeatPath = join(heartbeatsDir(), `${agentId}.json`)\n if (existsSync(heartbeatPath)) {\n try {\n const data = JSON.parse(readFileSync(heartbeatPath, 'utf-8')) as {\n timestamp?: string\n tool_name?: string\n last_action?: string\n pid?: number\n }\n if (data.timestamp) {\n const ts = new Date(data.timestamp)\n if (Date.now() - ts.getTime() < ACTIVE_HEARTBEAT_TTL_MS) {\n return {\n timestamp: ts,\n agentId,\n source: 'active-heartbeat',\n confidence: 'high',\n toolName: data.tool_name,\n lastAction: data.last_action,\n pid: data.pid,\n }\n }\n }\n } catch {\n // fall through\n }\n }\n\n // 2. JSONL mtime.\n const lastActivity = this.getLastActivity(agentId)\n if (lastActivity) {\n return {\n timestamp: lastActivity,\n agentId,\n source: 'jsonl',\n confidence: 'medium',\n }\n }\n\n // 3. tmux session creation time (best-effort).\n try {\n const sess = listSessionsSync().find(s => s.name === agentId)\n if (sess) {\n return {\n timestamp: sess.created,\n agentId,\n source: 'tmux',\n confidence: 'low',\n }\n }\n } catch {\n // ignore\n }\n return null\n }\n\n getTokenUsage(agentId: string): TokenUsage | null {\n const session = this.getSessionPath(agentId)\n if (!session) return null\n const parsed = parseOhmypiSessionSync(session)\n return parsed?.usage ?? null\n }\n\n getSessionCost(agentId: string): CostBreakdown | null {\n const session = this.getSessionPath(agentId)\n if (!session) return null\n const parsed = parseOhmypiSessionSync(session)\n if (!parsed) return null\n const totalCost = parsed.cost_v2 ?? parsed.cost ?? 0\n return {\n inputCost: 0,\n outputCost: 0,\n cacheReadCost: 0,\n cacheWriteCost: 0,\n totalCost,\n currency: 'USD',\n }\n }\n\n async sendMessage(agentId: string, message: string): Promise<void> {\n if (!existsSync(readyPathFor(agentId))) {\n throw new OhmypiNotReady(`omp agent ${agentId}: ready.json not present yet`)\n }\n // steer: omp delivers immediately when idle and queues mid-turn; a bare\n // prompt is rejected with AgentBusyError while a run is active.\n writeOhmypiCommandSync(agentId, { id: randomUUID(), type: 'prompt', message, streamingBehavior: 'steer' })\n }\n\n /**\n * Kill an omp agent via the documented escalation ladder.\n *\n * 1. Send {cmd:'abort'} via the RPC fifo (graceful).\n * 2. Wait up to 2s for the tmux session to disappear on its own.\n * 3. SIGTERM the omp process group via tmux send-signal.\n * 4. Wait up to 5s for the tmux session to disappear.\n * 5. tmux kill-session (effectively SIGKILL).\n * 6. Always unlink rpc.in, ready.json, heartbeat, and completed marker.\n * NEVER touch session JSONL files — per JSONL-is-sacred rule.\n */\n async killAgent(agentId: string): Promise<void> {\n // Step 1: graceful RPC abort.\n try {\n writeOhmypiCommandSync(agentId, { id: randomUUID(), type: 'abort' })\n } catch {\n // No reader / not ready — fall through to signal escalation.\n }\n\n // Step 2: poll for tmux exit up to 2s.\n if (await pollUntilSessionGone(agentId, 2_000)) {\n cleanupOhmypiTransientFiles(agentId)\n return\n }\n\n // Step 3: SIGTERM via tmux.\n try {\n await execAsync(`tmux -L overdeck send-keys -t ${agentId} C-c 2>/dev/null || true`)\n } catch {\n // ignore\n }\n try {\n const { stdout } = await execAsync(\n `tmux -L overdeck list-panes -t ${shellQuote(agentId)} -F '#{pane_pid}' 2>/dev/null`\n )\n const pid = stdout.trim()\n if (pid) {\n await execAsync(`kill -TERM -- -${pid} 2>/dev/null || kill -TERM ${pid} 2>/dev/null || true`)\n }\n } catch {\n // ignore\n }\n\n // Step 4: poll for tmux exit up to another 5s.\n if (await pollUntilSessionGone(agentId, 5_000)) {\n cleanupOhmypiTransientFiles(agentId)\n return\n }\n\n // Step 5: SIGKILL fallback via tmux kill-session.\n try {\n if (await Effect.runPromise(sessionExists(agentId))) {\n await Effect.runPromise(killSession(agentId))\n }\n } finally {\n cleanupOhmypiTransientFiles(agentId)\n }\n }\n\n async spawnAgent(config: SpawnConfig & { piExtensionPath?: string }): Promise<Agent> {\n const piExtensionPath = config.piExtensionPath\n if (!piExtensionPath) {\n throw new Error('OhmypiRuntime.spawnAgent requires piExtensionPath in config')\n }\n\n const agentId = config.agentId\n const dir = agentDirFor(agentId)\n const sessionDir = ohmypiSessionDirFor(agentId)\n const hadPriorSpawn = existsSync(sessionDir) &&\n readdirSync(sessionDir).some((f) => f.endsWith('.jsonl'))\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n mkdirSync(sessionDir, { recursive: true, mode: 0o700 })\n\n const fifoPath = await Effect.runPromise(createOhmypiFifo(agentId))\n\n const promptFile = config.prompt ? writeAgentPromptFile(agentId, config.prompt) : undefined\n\n // Resume from the stored session.id when present, else recover from the\n // freshest session JSONL (PAN-1988 parity). omp uses --resume (not --session).\n const resumeSessionId = readStoredSessionId(agentId) ?? resolveLatestOhmypiSessionId(agentId) ?? undefined\n if (!resumeSessionId && hadPriorSpawn) {\n console.warn(\n `[ohmypi-runtime] ${agentId}: prior session.jsonl exists but no resumable session id found — spawning a fresh omp session`,\n )\n }\n\n const launcherScript = generateLauncherScriptSync({\n role: 'work',\n workingDir: config.workspace,\n harness: 'ohmypi',\n piExtensionPath,\n piFifoPath: fifoPath,\n piSessionDir: sessionDir,\n model: config.model,\n promptFile,\n resumeSessionId,\n overdeckEnv: { agentId },\n setTerminalEnv: true,\n trapHup: true,\n })\n\n const launcherPath = join(dir, 'ohmypi-launcher.sh')\n writeFileSync(launcherPath, launcherScript)\n chmodSync(launcherPath, 0o755)\n\n await Effect.runPromise(createSession(agentId, config.workspace, `bash ${launcherPath}`, {\n env: { OVERDECK_AGENT_ID: agentId },\n }))\n\n await waitForReady(agentId, SPAWN_READY_TIMEOUT_MS)\n\n const readyData = readReady(agentId)\n return {\n id: agentId,\n sessionId: readyData?.sessionId ?? 'unknown',\n runtime: 'ohmypi',\n model: config.model ?? 'unknown',\n workspace: config.workspace,\n startedAt: new Date(),\n }\n }\n\n listSessions(workspace?: string): Session[] {\n const sessions: Session[] = []\n const root = agentsDir()\n if (!existsSync(root)) return sessions\n for (const agentName of safeReaddir(root)) {\n const sessionRoot = ohmypiSessionDirFor(agentName)\n if (!existsSync(sessionRoot)) continue\n const files: { path: string; mtime: number }[] = []\n walkJsonl(sessionRoot, files)\n for (const file of files) {\n const parsed = parseOhmypiSessionSync(file.path)\n if (!parsed) continue\n sessions.push({\n id: parsed.sessionId,\n agentId: agentName,\n workspace: workspace ?? 'unknown',\n model: parsed.model,\n startedAt: new Date(parsed.startTime),\n lastActivity: new Date(file.mtime),\n tokenUsage: parsed.usage,\n })\n }\n }\n return sessions\n }\n\n async isRunning(agentId: string): Promise<boolean> {\n return await Effect.runPromise(sessionExists(agentId))\n }\n}\n\nexport function createOhmypiRuntimeSync(): OhmypiRuntimeSync {\n return new OhmypiRuntimeSync()\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\nexport class OhmypiRuntime implements AgentRuntime {\n readonly name = 'ohmypi' as const\n private readonly inner: OhmypiRuntimeSync\n\n constructor(inner: OhmypiRuntimeSync = new OhmypiRuntimeSync()) {\n this.inner = inner\n }\n\n getSessionPath(agentId: string): string | null {\n return this.inner.getSessionPath(agentId)\n }\n getHarnessBehavior(): HarnessBehavior {\n return this.inner.getHarnessBehavior()\n }\n getLastActivity(agentId: string): Date | null {\n return this.inner.getLastActivity(agentId)\n }\n getHeartbeat(agentId: string): Heartbeat | null {\n return this.inner.getHeartbeat(agentId)\n }\n getTokenUsage(agentId: string): TokenUsage | null {\n return this.inner.getTokenUsage(agentId)\n }\n getSessionCost(agentId: string): CostBreakdown | null {\n return this.inner.getSessionCost(agentId)\n }\n listSessions(workspace?: string): Session[] {\n return this.inner.listSessions(workspace)\n }\n\n sendMessage(agentId: string, message: string): Effect.Effect<void, AgentRuntimeError> {\n return Effect.tryPromise({\n try: () => Promise.resolve(this.inner.sendMessage(agentId, message)),\n catch: (cause) =>\n new TmuxError({\n command: 'rpc-write',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n }\n\n killAgent(agentId: string): Effect.Effect<void, AgentRuntimeError> {\n return Effect.tryPromise({\n try: () => this.inner.killAgent(agentId),\n catch: (cause) =>\n new TmuxError({\n command: 'kill-session',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n }\n\n spawnAgent(config: SpawnConfig): Effect.Effect<Agent, AgentRuntimeError> {\n return Effect.tryPromise({\n try: () => this.inner.spawnAgent(config),\n catch: (cause) => {\n if (cause instanceof OhmypiSpawnTimeout) {\n return new ProcessTimeoutError({\n command: 'omp',\n args: ['--mode', 'rpc'],\n timeoutMs: SPAWN_READY_TIMEOUT_MS,\n })\n }\n return new ProcessSpawnError({\n command: 'omp',\n args: ['--mode', 'rpc'],\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n })\n },\n })\n }\n\n isRunning(agentId: string): Effect.Effect<boolean> {\n return Effect.promise(() => this.inner.isRunning(agentId))\n }\n}\n\nexport function createOhmypiRuntime(): OhmypiRuntime {\n return new OhmypiRuntime()\n}\n\n// ────────────────────────────────────────────────────────────────────────\n// helpers\n// ────────────────────────────────────────────────────────────────────────\n\nasync function pollUntilSessionGone(agentId: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs\n while (Date.now() < deadline) {\n if (!(await Effect.runPromise(sessionExists(agentId)))) return true\n await new Promise(r => setTimeout(r, 100))\n }\n return false\n}\n\n/**\n * Remove omp's transient per-agent files: rpc.in fifo, ready.json,\n * heartbeats/<id>.json, completed marker. Never touches session JSONLs\n * under <agentDir>/sessions/ — those are sacred (CLAUDE.md).\n */\nfunction cleanupOhmypiTransientFiles(agentId: string): void {\n try { destroyOhmypiFifoSync(agentId) } catch { /* ignore */ }\n const dir = agentDirFor(agentId)\n for (const transient of ['ready.json', 'completed']) {\n const path = join(dir, transient)\n if (existsSync(path)) {\n try { unlinkSync(path) } catch { /* ignore */ }\n }\n }\n const heartbeatPath = join(heartbeatsDir(), `${agentId}.json`)\n if (existsSync(heartbeatPath)) {\n try { unlinkSync(heartbeatPath) } catch { /* ignore */ }\n }\n // Sacred: NEVER touch <dir>/sessions/*.jsonl — JSONLs are conversation history.\n}\n\nfunction readReady(agentId: string): { sessionId?: string; reason?: string } | null {\n const path = readyPathFor(agentId)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return null\n }\n}\n\nasync function waitForReady(agentId: string, timeoutMs: number): Promise<void> {\n const deadline = Date.now() + timeoutMs\n const path = readyPathFor(agentId)\n while (Date.now() < deadline) {\n if (existsSync(path)) return\n await new Promise(r => setTimeout(r, 100))\n }\n throw new OhmypiSpawnTimeout(agentId)\n}\n\nfunction writeAgentPromptFile(agentId: string, prompt: string): string {\n const promptPath = join(agentDirFor(agentId), 'prompt.txt')\n writeFileSync(promptPath, prompt)\n return promptPath\n}\n\nfunction safeReaddir(dir: string): string[] {\n try {\n return readdirSync(dir)\n } catch {\n return []\n }\n}\n\nfunction walkJsonl(dir: string, out: { path: string; mtime: number }[]): void {\n let entries: string[]\n try {\n entries = readdirSync(dir)\n } catch {\n return\n }\n for (const entry of entries) {\n const full = join(dir, entry)\n let stat\n try {\n stat = statSync(full)\n } catch {\n continue\n }\n if (stat.isDirectory()) {\n walkJsonl(full, out)\n } else if (stat.isFile() && entry.endsWith('.jsonl')) {\n out.push({ path: full, mtime: stat.mtime.getTime() })\n }\n }\n}\n\n// Keep imports referenced even when only the types are used.\nvoid execAsync\nvoid tmpdir\n","import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, statSync } from 'fs';\nimport { readFile } from 'fs/promises';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport {\n getAgentDir,\n getAgentStateSync,\n getAgentRuntimeStateSync,\n getAgentRuntimeState,\n} from '../agents.js';\nimport { encodeClaudeProjectDir } from '../paths.js';\nimport { findLatestRollout, extractThreadIdFromRollout } from '../runtimes/codex.js';\nimport { resolveLatestOhmypiSessionId } from '../runtimes/ohmypi.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport { FsError } from '../errors.js';\n\n/** Activity log entry (still written by heartbeat-hook as a forensic artifact). */\nexport interface ActivityEntry {\n ts: string;\n tool: string;\n action?: string;\n state?: 'active' | 'idle';\n}\n\n/**\n * Append to activity log with automatic pruning to 100 entries\n */\nexport function appendActivity(agentId: string, entry: ActivityEntry): void {\n const dir = getAgentDir(agentId);\n mkdirSync(dir, { recursive: true });\n\n const activityFile = join(dir, 'activity.jsonl');\n\n // Append entry\n appendFileSync(activityFile, JSON.stringify(entry) + '\\n');\n\n // Prune to last 100 entries\n if (existsSync(activityFile)) {\n try {\n const lines = readFileSync(activityFile, 'utf8').trim().split('\\n');\n if (lines.length > 100) {\n const trimmed = lines.slice(-100);\n writeFileSync(activityFile, trimmed.join('\\n') + '\\n');\n }\n } catch (error) {\n // Ignore pruning errors - activity log is non-critical\n }\n }\n}\n\n/**\n * Read activity log (last N entries)\n */\nexport function getActivity(agentId: string, limit = 100): ActivityEntry[] {\n const activityFile = join(getAgentDir(agentId), 'activity.jsonl');\n\n if (!existsSync(activityFile)) {\n return [];\n }\n\n try {\n const lines = readFileSync(activityFile, 'utf8').trim().split('\\n');\n const entries = lines\n .filter(line => line.trim())\n .map(line => JSON.parse(line) as ActivityEntry)\n .slice(-limit);\n\n return entries;\n } catch {\n return [];\n }\n}\n\n/**\n * Save Claude session ID for later resume\n */\nexport function saveSessionId(agentId: string, sessionId: string): void {\n const dir = getAgentDir(agentId);\n mkdirSync(dir, { recursive: true });\n\n writeFileSync(join(dir, 'session.id'), sessionId);\n}\n\n/**\n * Get saved Claude session ID\n */\nexport function getSessionId(agentId: string): string | null {\n const sessionFile = join(getAgentDir(agentId), 'session.id');\n\n if (!existsSync(sessionFile)) {\n return null;\n }\n\n try {\n return readFileSync(sessionFile, 'utf8').trim();\n } catch {\n return null;\n }\n}\n\n/**\n * PAN-1988 — for a codex agent, resolve its REAL resumable thread id. codex writes a placeholder\n * UUID into `session.id` at spawn; the resumable id is the codex thread, recorded in the rollout.\n * Prefer the explicitly-captured `codex-thread-id`, then fall back to the freshest rollout on disk\n * (always current — codex writes a new rollout per resume, so this self-heals across resume cycles\n * without depending on the capture poll landing). Returns null for non-codex agents.\n */\nfunction resolveCodexThreadIdSync(agentId: string): string | null {\n const agentDir = getAgentDir(agentId);\n const codexHome = join(agentDir, 'codex-home');\n if (!existsSync(codexHome)) return null; // not a codex agent\n try {\n const threadIdPath = join(agentDir, 'codex-thread-id');\n if (existsSync(threadIdPath)) {\n const id = readFileSync(threadIdPath, 'utf-8').trim();\n if (id) return id;\n }\n } catch { /* non-fatal */ }\n try {\n const rollout = findLatestRollout(codexHome);\n if (rollout) {\n const id = extractThreadIdFromRollout(rollout);\n if (id) return id;\n }\n } catch { /* non-fatal */ }\n return null;\n}\n\n/**\n * Sync mirror of jsonl-resolver.ts's pickFreshestSessionId: from a list of\n * candidate session ids, return the one whose JSONL transcript has the most\n * recent mtime, skipping ids with no file on disk. Falls back to the last\n * appended id when none have a transcript (e.g. workspace moved). Returns null\n * only when there are no usable candidates.\n */\nfunction pickFreshestExistingSessionIdSync(agentId: string, candidates: unknown[]): string | null {\n const valid = candidates.filter((v): v is string => typeof v === 'string' && v.trim().length > 0);\n if (valid.length === 0) return null;\n const workspace = getAgentStateSync(agentId)?.workspace;\n if (workspace) {\n const projectDir = join(homedir(), '.claude', 'projects', encodeClaudeProjectDir(workspace));\n let best: { id: string; mtimeMs: number } | null = null;\n for (const id of valid) {\n try {\n const s = statSync(join(projectDir, `${id}.jsonl`));\n if (!best || s.mtimeMs > best.mtimeMs) best = { id, mtimeMs: s.mtimeMs };\n } catch { /* no JSONL for this id — skip */ }\n }\n if (best) return best.id;\n }\n return valid[valid.length - 1] ?? null;\n}\n\nexport function getLatestSessionIdSync(agentId: string): string | null {\n // 0. codex thread id FIRST — `session.id` below holds a placeholder UUID for codex agents, so\n // returning it would make resumeAgent target a non-existent thread and codex would drift into\n // a fresh rollout, losing conversation history (PAN-1988). The freshest rollout is the truth.\n const codexThreadId = resolveCodexThreadIdSync(agentId);\n if (codexThreadId) return codexThreadId;\n\n // 1. session.id (written by auto-suspend) — the real id for claude-code.\n const fromSessionFile = getSessionId(agentId);\n if (fromSessionFile) return fromSessionFile;\n\n // 2. sessions.json (append-only list of session ids the agent has used).\n // The array can hold aborted/empty ids (e.g. a fresh session that never\n // produced a transcript), so we can't trust \"last entry\" — pick the id\n // whose JSONL is freshest on disk, matching resolveClaudeSessionId\n // (jsonl-resolver.ts). Falls back to last-appended when none exist on disk.\n const sessionsFile = join(getAgentDir(agentId), 'sessions.json');\n try {\n if (existsSync(sessionsFile)) {\n const sessions = JSON.parse(readFileSync(sessionsFile, 'utf8'));\n if (Array.isArray(sessions) && sessions.length > 0) {\n const picked = pickFreshestExistingSessionIdSync(agentId, sessions);\n if (picked) return picked;\n }\n }\n } catch { /* non-fatal */ }\n\n // 3. runtime.json claudeSessionId\n const runtimeState = getAgentRuntimeStateSync(agentId);\n if (runtimeState?.claudeSessionId) {\n return runtimeState.claudeSessionId;\n }\n\n // 4. codex-thread-id (written after codex rollout appears; fallback so\n // resumeAgent can locate the Codex session even if session.id has a\n // stale random UUID from spawnRun's placeholder write).\n const codexThreadIdPath = join(getAgentDir(agentId), 'codex-thread-id');\n try {\n if (existsSync(codexThreadIdPath)) {\n const threadId = readFileSync(codexThreadIdPath, 'utf-8').trim();\n if (threadId) return threadId;\n }\n } catch { /* non-fatal */ }\n\n // 5. ohmypi (omp) — PAN-2098. omp never writes a `session.id` file, so none of\n // the claude-code/codex sources above can find it; the real id lives inside\n // the freshest session JSONL. Mirror the ohmypi runtime adapter's own resume\n // resolution so the deacon recovery path can resume a crashed ohmypi agent\n // instead of only respawning it fresh and losing context.\n const agentState = getAgentStateSync(agentId);\n if (agentState?.harness && getHarnessBehavior(agentState.harness).sessionIdSource === 'transcript-jsonl') {\n const ohmypiSessionId = resolveLatestOhmypiSessionId(agentId);\n if (ohmypiSessionId) return ohmypiSessionId;\n }\n\n return null;\n}\n\nexport const getLatestSessionId = (agentId: string): Effect.Effect<string | null> => {\n const agentDir = getAgentDir(agentId);\n const sessionFile = join(agentDir, 'session.id');\n const sessionsFile = join(agentDir, 'sessions.json');\n\n return Effect.gen(function* () {\n const sessionId = yield* Effect.tryPromise({\n try: () => readFile(sessionFile, 'utf8'),\n catch: (cause) => new FsError({ operation: 'read', path: sessionFile, cause }),\n }).pipe(\n Effect.map((content) => content.trim()),\n Effect.orElseSucceed(() => ''),\n );\n if (sessionId) return sessionId;\n\n const latestSession = yield* Effect.tryPromise({\n try: async () => JSON.parse(await readFile(sessionsFile, 'utf8')) as unknown,\n catch: (cause) => new FsError({ operation: 'read', path: sessionsFile, cause }),\n }).pipe(\n Effect.map((sessions) => Array.isArray(sessions) && sessions.length > 0 ? String(sessions[sessions.length - 1]) : null),\n Effect.orElseSucceed(() => null),\n );\n if (latestSession) return latestSession;\n\n const runtimeState = yield* getAgentRuntimeState(agentId);\n return runtimeState?.claudeSessionId ?? null;\n });\n};\n","import type { DomainEvent } from '@overdeck/contracts';\nimport { initEventStore } from '../dashboard/server/event-store.js';\n\nexport type OperatorInterventionKind = 'tell' | 'pause' | 'restart' | 'manual_edit' | 'deep_wipe' | 'unpause' | 'untroubled';\n\nexport interface OperatorInterventionInput {\n issueId: string;\n kind: OperatorInterventionKind;\n source: string;\n timestamp?: string;\n}\n\nexport function operatorInterventionEvent(input: OperatorInterventionInput): Omit<DomainEvent, 'sequence'> {\n return {\n type: 'operator.intervention',\n timestamp: input.timestamp ?? new Date().toISOString(),\n payload: {\n issueId: input.issueId,\n kind: input.kind,\n source: input.source,\n },\n } as Omit<DomainEvent, 'sequence'>;\n}\n\nexport async function appendOperatorInterventionEvent(input: OperatorInterventionInput): Promise<void> {\n const store = await initEventStore();\n await store.appendAsync(operatorInterventionEvent(input));\n}\n","/**\n * PAN-1980: session rotation on resume policy.\n *\n * Work agents used to rotate to a brand-new Claude session (a fresh JSONL\n * transcript) when a resume couldn't reuse the saved one — compact/overflow\n * recovery (PAN-1781), model/harness drift (PAN-1787/1797), or the messageAgent\n * resume-failure fresh-launch fallback. That rotation broke the conversation\n * panel (it pinned to a stale transcript while the terminal showed the live one)\n * and mostly papered over non-Anthropic CLIProxy overflow that native claude-code\n * handles in-place.\n *\n * Rotation is now DISABLED: a resume that would need a fresh session errors and\n * stops the agent for a human instead. Flip {@link ALLOW_SESSION_ROTATION_ON_RESUME}\n * to `true` to restore the old behavior; the intended replacement is in-place\n * `/compact` into the SAME session (the deacon already does this for\n * specialist/planning sessions).\n */\nexport const ALLOW_SESSION_ROTATION_ON_RESUME = false;\n\n/**\n * Whether a resume that would otherwise rotate to a fresh session must be\n * REFUSED. A resume needs a fresh session when it is a compact-recovery respawn\n * (`compactSeed` present) or the saved session drifted (model/harness). With\n * rotation disabled we refuse rather than rotate.\n */\nexport function sessionRotationRefused(opts: {\n compactSeed: boolean;\n driftReasons: readonly string[];\n allowExplicitRecovery?: boolean;\n}): boolean {\n const wouldRotate = opts.compactSeed || opts.driftReasons.length > 0;\n return wouldRotate && !ALLOW_SESSION_ROTATION_ON_RESUME && opts.allowExplicitRecovery !== true;\n}\n","import { mkdirSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { appendOperatorInterventionEvent } from '../operator-interventions.js';\nimport { logAgentLifecycleSync } from '../persistent-logger.js';\nimport { getProviderForModelSync, setupCredentialFileAuthSync, clearCredentialFileAuthSync } from '../providers.js';\nimport { ALLOW_SESSION_ROTATION_ON_RESUME } from '../session-rotation.js';\nimport type { ModelId } from '../settings.js';\nimport { createSession, killSession, listPaneValues, sessionExists } from '../tmux.js';\nimport {\n clearReadySignal,\n normalizeAgentId,\n waitForAgentIdle,\n} from './identity.js';\nimport {\n getAgentDir,\n getAgentResumeGateBlockReason,\n getAgentStateSync,\n markAgentRunning,\n saveAgentStateSync,\n type Role,\n} from './agent-state.js';\nimport { getLatestSessionIdSync } from './activity.js';\nimport {\n deliverAgentMessage,\n deliverResumeMessageWithTranscriptConfirmation,\n resilientDeliveryMethod,\n} from './delivery.js';\nimport { getAgentRuntimeStateSync } from './runtime-state.js';\nimport {\n claudeSystemPromptFiles,\n getCodexLauncherFields,\n getOhmypiLauncherFields,\n getRoleRuntimeBaseCommand,\n hasAgentRuntimeInSubtree,\n waitForPromptReady,\n} from './runtime-command.js';\nimport {\n buildResumeMessageForAgent,\n markKickoffRedelivered,\n prepareSupervisorForRelaunch,\n} from './supervisor-channels.js';\nimport {\n getProviderEnvForModel,\n getProviderExportsForModel,\n} from './provider-env.js';\n\nfunction queueAgentMail(agentId: string, message: string): void {\n const mailDir = join(getAgentDir(agentId), 'mail');\n mkdirSync(mailDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n writeFileSync(\n join(mailDir, `${timestamp}.md`),\n `# Message\\n\\n${message}\\n`\n );\n}\n\nconst USER_MESSAGE_INTERVENTION_SOURCES = new Set(['pan-tell', 'dashboard:user-message']);\n\nasync function appendTellInterventionForUserSource(normalizedId: string, caller: string): Promise<void> {\n if (!USER_MESSAGE_INTERVENTION_SOURCES.has(caller)) return;\n\n const agentState = getAgentStateSync(normalizedId);\n if (!agentState?.issueId) {\n console.debug(`[agents] Skipping tell intervention for ${normalizedId}; state.json has no issueId`);\n return;\n }\n\n await appendOperatorInterventionEvent({\n issueId: agentState.issueId,\n kind: 'tell',\n source: caller,\n });\n}\n\nexport async function messageAgent(agentId: string, message: string, caller = 'internal'): Promise<void> {\n const normalizedId = normalizeAgentId(agentId);\n const agentState = getAgentStateSync(normalizedId);\n if (agentState?.paused === true) {\n const gateBlockReason = getAgentResumeGateBlockReason(agentState)?.reason ?? 'agent is paused';\n queueAgentMail(normalizedId, message);\n logAgentLifecycleSync(normalizedId, `messageAgent queued mail without resume: ${gateBlockReason}`);\n console.log(`[agents] Queued message for ${normalizedId}; ${gateBlockReason}`);\n return;\n }\n\n // Check if agent is suspended - auto-resume if so (PAN-80)\n const runtimeState = getAgentRuntimeStateSync(normalizedId);\n if (runtimeState?.state === 'suspended') {\n const gateBlockReason = agentState ? getAgentResumeGateBlockReason(agentState)?.reason : undefined;\n if (gateBlockReason) {\n queueAgentMail(normalizedId, message);\n logAgentLifecycleSync(normalizedId, `messageAgent queued mail without resume: ${gateBlockReason}`);\n console.log(`[agents] Queued message for ${normalizedId}; ${gateBlockReason}`);\n return;\n }\n console.log(`[agents] Auto-resuming suspended agent ${normalizedId} to deliver message`);\n const { resumeAgent } = await import('../agents.js');\n const result = await resumeAgent(normalizedId, message);\n if (!result.success) {\n throw new Error(`Failed to auto-resume agent: ${result.error}`);\n }\n if (result.messageDelivered === false) {\n throw new Error(`Agent resumed but ready signal did not fire — message not delivered. Feedback is in the mail queue.`);\n }\n // Message already sent during resume\n await appendTellInterventionForUserSource(normalizedId, caller);\n return;\n }\n\n // Check if agent is stopped — auto-resume to deliver feedback (PAN-367 / PAN-705)\n //\n // IMPORTANT: We delegate to resumeAgent() so we pick up the saved Claude session id\n // (`claude --resume <id>`) instead of fresh-launching with a new, empty session.\n // The previous implementation of this branch called `getAgentRuntimeBaseCommand(model)`\n // and passed an inline \"You are resuming work\" prompt as a positional argument,\n // which booted Claude Code in a fresh session (ctx 0%) with no memory of the\n // prior conversation, destroying agent continuity every time feedback arrived.\n //\n // We also restart when the tmux session still exists. Planning/work sessions use\n // `remain-on-exit on` so the shell persists after the agent process exits, and\n // sessionExists() returns true for that dead shell. resumeAgent() kills the zombie\n // session before re-creating it.\n if (agentState && agentState.status === 'stopped') {\n const gateBlockReason = getAgentResumeGateBlockReason(agentState)?.reason;\n if (gateBlockReason) {\n queueAgentMail(normalizedId, message);\n logAgentLifecycleSync(normalizedId, `messageAgent queued mail without resume: ${gateBlockReason}`);\n console.log(`[agents] Queued message for ${normalizedId}; ${gateBlockReason}`);\n return;\n }\n console.log(`[agents] Auto-resuming stopped agent ${normalizedId} to deliver feedback (session exists: ${await Effect.runPromise(sessionExists(normalizedId))})`);\n\n const { resumeAgent } = await import('../agents.js');\n const resumeResult = await resumeAgent(normalizedId, message);\n\n // Save to mail queue regardless so the agent can re-read feedback if needed\n queueAgentMail(normalizedId, message);\n\n if (resumeResult.success && resumeResult.messageDelivered !== false) {\n await appendTellInterventionForUserSource(normalizedId, caller);\n console.log(`[agents] Resumed ${normalizedId} and delivered feedback`);\n return;\n }\n\n // Resume failed OR message was not delivered (ready signal timed out). Fall back to\n // a fresh launch so feedback is not silently dropped. This path intentionally mirrors\n // spawnAgent's launcher (provider exports + unset of leaked env vars) so the fallback\n // doesn't inherit stale ANTHROPIC_BASE_URL / OPENAI_API_KEY from the parent process.\n if (!resumeResult.success) {\n console.warn(`[agents] Resume failed for ${normalizedId}: ${resumeResult.error} — falling back to fresh launch`);\n } else {\n console.warn(`[agents] Resume succeeded for ${normalizedId} but message not delivered (ready signal timed out) — falling back to fresh launch`);\n }\n\n // PAN-1980: session rotation is disabled — do NOT fresh-launch a new session\n // as a fallback (that rotates the transcript and hides the resume failure).\n // Leave the agent stopped and surface it; the feedback was already queued in\n // the mail queue above, so it is not dropped.\n if (!ALLOW_SESSION_ROTATION_ON_RESUME) {\n const why = !resumeResult.success\n ? `resume failed (${resumeResult.error})`\n : 'resume succeeded but message delivery timed out';\n const stopMsg = `Not restarting ${normalizedId} with a fresh session — ${why}; session rotation is disabled (PAN-1980). Agent left stopped; feedback queued in mail.`;\n console.warn(`[agents] ${stopMsg}`);\n emitActivityEntrySync({ source: 'work-agent', level: 'error', message: `${normalizedId}: ${stopMsg}`, issueId: agentState.issueId });\n return;\n }\n\n const providerEnv = agentState.model ? await getProviderEnvForModel(agentState.model) : {};\n if (agentState.model) {\n const provider = getProviderForModelSync(agentState.model as ModelId);\n if (provider.authType === 'credential-file') {\n setupCredentialFileAuthSync(provider, agentState.workspace);\n } else {\n clearCredentialFileAuthSync(agentState.workspace);\n }\n }\n\n clearReadySignal(normalizedId);\n if (await Effect.runPromise(sessionExists(normalizedId))) {\n try { await Effect.runPromise(killSession(normalizedId)); } catch { /* ignore */ }\n }\n\n const providerExports = await getProviderExportsForModel(agentState.model || 'claude-sonnet-4-6');\n const fallbackLauncher = join(getAgentDir(normalizedId), 'launcher.sh');\n // PAN-1048 C4: resume must relaunch with the agent's actual role, not\n // hardcoded 'work'. A stopped review/test/ship run was previously\n // resurrected as a work agent because launcher generation ignored the\n // saved role. Use agentState.role and route through getRoleRuntimeBaseCommand\n // so the role-specific .claude/agents/* definition file is loaded.\n const resumeRole: Role = agentState.role ?? 'work';\n // PAN-1048 review feedback 006 (S1): Pi-backed resumes need the same\n // launcher fields the fresh-spawn path threads through generateLauncherScript.\n // buildPiCommand throws on missing piSessionDir, so the previous fallback\n // emitted a launcher that would crash on resume for any Pi role agent.\n const resumeModel = agentState.model || 'claude-sonnet-4-6';\n const fallbackHarness = agentState.harness ?? 'claude-code';\n const { assertWorkspaceStackHealthyForSpawn } = await import('../agents.js');\n await assertWorkspaceStackHealthyForSpawn(\n agentState.issueId || normalizedId.replace(/^agent-/, '').toUpperCase(),\n resumeRole,\n agentState.hostOverride === true,\n agentState.workspace,\n );\n const fallbackPiFields = fallbackHarness === 'ohmypi'\n ? await getOhmypiLauncherFields(normalizedId, resumeModel)\n : {};\n const fallbackCodexFields = fallbackHarness === 'codex'\n ? getCodexLauncherFields(normalizedId, resumeModel, agentState.workspace)\n : {};\n const fallbackSupervisorLaunch = await prepareSupervisorForRelaunch(normalizedId, agentState, resumeModel, fallbackHarness);\n const fallbackContent = generateLauncherScriptSync({\n role: resumeRole,\n workingDir: agentState.workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports,\n baseCommand: await getRoleRuntimeBaseCommand(\n resumeModel,\n normalizedId,\n resumeRole,\n fallbackHarness,\n ),\n appendSystemPromptFiles: await claudeSystemPromptFiles(agentState.workspace, fallbackHarness),\n useSupervisor: fallbackSupervisorLaunch.useSupervisor,\n supervisorScriptPath: fallbackSupervisorLaunch.supervisorScriptPath,\n ...fallbackPiFields,\n ...fallbackCodexFields,\n });\n writeFileSync(fallbackLauncher, fallbackContent, { mode: 0o755 });\n await Effect.runPromise(createSession(normalizedId, agentState.workspace, `bash ${fallbackLauncher}`, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: agentState.issueId || '',\n OVERDECK_SESSION_TYPE: agentState.role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n ...providerEnv\n }\n }));\n\n markAgentRunning(agentState);\n saveAgentStateSync(agentState);\n\n const ready = await waitForPromptReady(normalizedId, fallbackHarness, 30);\n const fallbackResumePrompt = `You are resuming work on ${agentState.issueId}. Check .pan/feedback/ for specialist feedback that arrived while you were stopped, then continue working.\\n\\n${message}`;\n const resumeMessage = await buildResumeMessageForAgent(agentState, fallbackResumePrompt, message);\n if (resumeMessage.error) {\n console.error(`[agents] Fallback-restarted ${normalizedId} but ${resumeMessage.error}`);\n emitActivityEntrySync({\n source: 'work-agent',\n level: 'error',\n message: `${normalizedId}: ${resumeMessage.error}`,\n issueId: agentState.issueId,\n });\n } else if (ready && resumeMessage.message) {\n let delivered = false;\n if (fallbackHarness === 'claude-code') {\n const fallbackSessionId = getLatestSessionIdSync(normalizedId);\n if (fallbackSessionId) {\n const delivery = await deliverResumeMessageWithTranscriptConfirmation({\n agentId: normalizedId,\n workspace: agentState.workspace,\n sessionId: fallbackSessionId,\n message: resumeMessage.message,\n caller: 'resumeAgent:resume-prompt',\n deliveryMethod: resilientDeliveryMethod(agentState.deliveryMethod),\n });\n delivered = delivery.delivered;\n if (!delivery.delivered) {\n console.error(`[agents] Fallback resume prompt did not land after ${delivery.attempts} delivery attempts`);\n }\n } else {\n console.error(`[agents] Fallback-restarted ${normalizedId} but no session id was recorded — feedback in mail queue`);\n }\n } else {\n const delivery = await deliverAgentMessage(normalizedId, resumeMessage.message, 'resumeAgent:resume-prompt', resilientDeliveryMethod(agentState.deliveryMethod));\n delivered = delivery.ok;\n }\n if (delivered) {\n if (resumeMessage.redeliveringKickoff) markKickoffRedelivered(agentState);\n await appendTellInterventionForUserSource(normalizedId, caller);\n console.log(`[agents] Fallback-restarted ${normalizedId} and delivered feedback`);\n }\n } else {\n console.warn(`[agents] Fallback-restarted ${normalizedId} but ready signal not detected — feedback in mail queue`);\n }\n\n return;\n }\n\n // Check if this is a remote agent\n const { loadRemoteAgentState, sendToRemoteAgent } = await import('../remote/remote-agents.js');\n const remoteState = loadRemoteAgentState(normalizedId);\n if (remoteState && remoteState.vmName) {\n console.log(`[agents] Sending message to remote agent ${normalizedId} on ${remoteState.vmName}`);\n await sendToRemoteAgent(normalizedId, remoteState.vmName, message);\n\n // Also save to mail queue for persistence\n queueAgentMail(normalizedId, message);\n await appendTellInterventionForUserSource(normalizedId, caller);\n return;\n }\n\n if (!(await Effect.runPromise(sessionExists(normalizedId)))) {\n throw new Error(`Agent ${normalizedId} not running`);\n }\n\n // Guard: if tmux session exists but Claude Code has exited, resume instead\n // of typing the message into a bare bash shell.\n //\n // Launchers differ: specialists `exec claude` so pane_pid IS claude, but\n // work-agent launchers run `bash launcher.sh` so pane_pid is bash and claude\n // runs as a descendant. Walk the pane's process subtree and treat the pane\n // as live if any descendant is the expected runtime for the saved harness.\n const panePids = await Effect.runPromise(listPaneValues(normalizedId, '#{pane_pid}'));\n const expectedHarness = agentState?.harness ?? 'claude-code';\n if (panePids.length > 0 && !(await hasAgentRuntimeInSubtree(panePids[0], expectedHarness))) {\n console.warn(`[agents] ${normalizedId} tmux session is a zombie (no ${expectedHarness} runtime) — attempting resume`);\n const { resumeAgent } = await import('../agents.js');\n const resumeResult = await resumeAgent(normalizedId, message);\n if (resumeResult.success) {\n if (resumeResult.messageDelivered !== false) {\n await appendTellInterventionForUserSource(normalizedId, caller);\n }\n return;\n }\n throw new Error(`Agent ${normalizedId} session is dead and resume failed: ${resumeResult.error}`);\n }\n\n // Wait for the agent to be idle at the prompt before sending — reduces dropped\n // Enter when Claude Code is still rendering. PAN-1594: hook-driven (runtime\n // mirror 'idle' via Stop/SessionStart hook), not a tmux pane-scrape.\n const promptReady = await waitForAgentIdle(normalizedId, 5000);\n if (!promptReady) {\n console.warn(`[agents] ${normalizedId} not at idle prompt after 5s — sending message anyway`);\n }\n\n const deliveryMethod = resilientDeliveryMethod(agentState?.deliveryMethod);\n await deliverAgentMessage(normalizedId, message, `messageAgent:${caller}`, deliveryMethod);\n\n // Also save to mail queue\n queueAgentMail(normalizedId, message);\n await appendTellInterventionForUserSource(normalizedId, caller);\n}\n","/**\n * Context-window overflow is NOT a transient error. It surfaces as e.g.\n * \"API Error: 400 Your input exceeds the context window of this model.\" and\n * nudging \"continue\" only re-sends the same oversized context for the same 400.\n */\nexport const CONTEXT_OVERFLOW_PATTERNS = [\n 'input exceeds the context window',\n 'exceeds the context window of this model',\n 'exceeded model token limit',\n];\n\nexport const CONTEXT_OVERFLOW_TAIL_LINES = 40;\n\n/**\n * Recognize context-window overflow from any thrown error, including the\n * claude -p non-zero exit envelope that carries \"result\":\"Prompt is too long\"\n * and \"terminal_reason\":\"blocking_limit\".\n */\nexport function isContextOverflowError(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n const normalized = message.toLowerCase();\n const patterns = [\n ...CONTEXT_OVERFLOW_PATTERNS,\n 'prompt is too long',\n 'blocking_limit',\n ];\n return patterns.some(pattern => normalized.includes(pattern.toLowerCase()));\n}\n\nexport function isContextOverflowTail(output: string): boolean {\n const recentTail = output.split('\\n').slice(-CONTEXT_OVERFLOW_TAIL_LINES).join('\\n').toLowerCase();\n const patterns = [...CONTEXT_OVERFLOW_PATTERNS, 'prompt is too long'];\n return patterns.some(pattern => recentTail.includes(pattern.toLowerCase()));\n}\n\n/**\n * PAN-1781: Opening prompt for a fresh session that replaces a context-wedged\n * one. The old session is summarized out-of-band and the summary is embedded\n * here — the fresh session never resumes the old JSONL, so there is no stale\n * leaf for the harness to rewind to (the failure mode that made in-place\n * boundary injection a silent no-op ~half the time).\n *\n * When summarization fails entirely, `summary` is null and the seed degrades\n * to durable-artifact reconstruction only — the same end state the old\n * /clear + reseed tier produced, minus the keystroke fragility.\n */\nexport function buildCompactRecoverySeedMessage(issueId: string, summary: string | null): string {\n const lines = [\n `Your previous session for ${issueId} hit the model's context-window limit, so you are starting a fresh session. This freed tokens; it did not reset your filesystem work.`,\n 'Do NOT start over and do NOT expect the prior conversation to be available.',\n ];\n if (summary) {\n lines.push('', 'Summary of the archived session:', '', summary, '');\n } else {\n lines.push('');\n }\n lines.push(\n 'Reconstruct your exact work-in-progress from durable artifacts:',\n '1. Read .pan/continue.json for resumePoint, decisions, hazards, feedback, and sessionHistory.',\n '2. Run `bd ready` for your open beads and `bd show <id>` for the bead you are working on.',\n '3. Inspect `git status` and `git diff` for uncommitted work already on disk.',\n 'Then continue from that reconstructed state and complete the next required bead — do not wait for further instructions.',\n );\n return lines.join('\\n');\n}\n","import { existsSync, unlinkSync } from 'fs';\nimport { randomUUID } from 'crypto';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { buildCompactRecoverySeedMessage } from '../context-overflow.js';\nimport { resolveHarness } from '../harness-resolve.js';\nimport { normalizeModelOverrideSync, requireModelOverrideSync } from '../model-validation.js';\nimport { getOverdeckDatabaseSync } from '../overdeck/infra.js';\nimport { sessionFilePath } from '../paths.js';\nimport { logAgentLifecycleSync } from '../persistent-logger.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { sessionRotationRefused } from '../session-rotation.js';\nimport { appendContinueSessionEntryForIssue } from '../vbrief/lifecycle-io.js';\nimport { createSession, isPaneDead, killSession, listPaneValues, sessionExists } from '../tmux.js';\nimport {\n decideResumeGate,\n getAgentDir,\n getAgentResumeGateBlockReason,\n getAgentStateSync,\n markAgentRunning,\n saveAgentStateSync,\n} from './agent-state.js';\nimport { getLatestSessionIdSync, saveSessionId } from './activity.js';\nimport {\n deliverInitialPromptWithRetry,\n deliverResumeMessageWithTranscriptConfirmation,\n resilientDeliveryMethod,\n} from './delivery.js';\nimport { clearReadySignal, normalizeAgentId, waitForReadySignal } from './identity.js';\nimport {\n getAgentRuntimeStateSync,\n saveAgentRuntimeState,\n sessionResumeDriftReasons,\n} from './runtime-state.js';\nimport {\n hasAgentRuntimeInSubtree,\n writeLauncherScriptAtomic,\n writeOhmypiAgentPrompt,\n} from './runtime-command.js';\nimport {\n buildDefaultResumeContinueMessage,\n buildResumeMessageForAgent,\n markKickoffRedelivered,\n prepareSupervisorForRelaunch,\n} from './supervisor-channels.js';\nimport {\n assertWorkspaceStackHealthyForSpawn,\n buildAgentLaunchConfig,\n} from './spawn-prep.js';\n\n/**\n * Resume a suspended agent (PAN-80)\n *\n * Reads saved session ID and creates new tmux session with --resume flag.\n * Optionally sends a message after resuming.\n *\n * Auto-resume triggers:\n * - Specialists: When queued work arrives\n * - Work agents: When message is sent via /work-tell\n */\n/**\n * PAN-1781: Build the opening prompt for a compact-recovery respawn — an\n * out-of-band summary of the wedged session plus durable-artifact reseed\n * instructions. Replaces PAN-1675's in-place JSONL compaction\n * (compactAgentSession), which appended a compact_boundary the harness's\n * resume leaf selection bypassed ~half the time, silently rebuilding the full\n * pre-compact context. The old JSONL is read-only here — never mutated.\n *\n * Never throws and never returns an unusable seed: smart summary → fallback\n * heuristic summary → reseed-instructions-only, in that order. A missing\n * sessionId or workspace skips straight to the reseed-only seed.\n */\nexport async function buildCompactRecoverySeed(agentId: string): Promise<{ seed: string; summarized: boolean }> {\n const normalizedId = normalizeAgentId(agentId);\n const agentState = getAgentStateSync(normalizedId);\n const sessionId = getLatestSessionIdSync(normalizedId);\n const issueId = agentState?.issueId || normalizedId.replace(/^agent-/, '').toUpperCase();\n\n let summary: string | null = null;\n if (agentState?.workspace && sessionId) {\n const sessionFile = sessionFilePath(agentState.workspace, sessionId);\n try {\n // Dynamic imports: keep conversation-compaction out of agents.ts's\n // top-level import graph (it pulls in dashboard server services).\n const [{ getConversationCompactionSettings }, { generateSmartSummary }] = await Promise.all([\n import('../../dashboard/server/services/conversation-compaction.js'),\n import('../conversations/smart-compaction.js'),\n ]);\n const settings = getConversationCompactionSettings();\n const result = await Effect.runPromise(generateSmartSummary({\n jsonlPath: sessionFile,\n model: settings.model,\n richMode: settings.richCompaction,\n mode: 'fork',\n }));\n summary = result.summary;\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n logAgentLifecycleSync(normalizedId, `compact-recovery smart summary failed (${error}); trying heuristic fallback`);\n try {\n const { generateFallbackSummary } = await import('../conversations/summary-fork.js');\n summary = await Effect.runPromise(generateFallbackSummary(sessionFile));\n } catch (fallbackErr) {\n const fallbackError = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);\n logAgentLifecycleSync(normalizedId, `compact-recovery fallback summary failed (${fallbackError}); seeding with reseed instructions only`);\n }\n }\n }\n\n return {\n seed: buildCompactRecoverySeedMessage(issueId, summary),\n summarized: summary !== null,\n };\n}\n\nexport async function resumeAgent(agentId: string, message?: string, opts?: { model?: string; harness?: RuntimeName; allowHost?: boolean; compact?: boolean; recoverGated?: boolean }): Promise<{ success: boolean; messageDelivered?: boolean; error?: string }> {\n const normalizedId = normalizeAgentId(agentId);\n const requestedModel = normalizeModelOverrideSync(opts?.model);\n logAgentLifecycleSync(normalizedId, `resumeAgent called (message=${message ? 'yes' : 'no'}, harness=${opts?.harness || 'unchanged'})`);\n\n // Check runtime state — allow both suspended (auto-suspend) and stopped/idle (manual stop, crash)\n const runtimeState = getAgentRuntimeStateSync(normalizedId);\n const agentState = getAgentStateSync(normalizedId);\n const gateBlock = agentState ? getAgentResumeGateBlockReason(agentState) : undefined;\n const gateDecision = decideResumeGate(gateBlock, 'operator-start');\n const bypassTroubledGate = opts?.compact === true && opts.recoverGated === true && agentState?.troubled === true && agentState.paused !== true;\n if (gateDecision.decision === 'block' && !bypassTroubledGate) {\n const reason = `Cannot resume ${normalizedId}: ${gateDecision.reason}. Clear the gate before resuming.`;\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return { success: false, error: reason };\n }\n if (gateDecision.decision === 'proceed' && gateDecision.warning) {\n logAgentLifecycleSync(normalizedId, `resumeAgent: ${gateDecision.warning}`);\n }\n if (bypassTroubledGate) {\n logAgentLifecycleSync(normalizedId, `resumeAgent: bypassing troubled gate for explicit compact recovery (${gateBlock?.reason})`);\n }\n const hasWorkspace = !!agentState?.workspace && existsSync(agentState.workspace);\n const isPlaceholder = !!agentState && agentState.status === 'starting' && typeof agentState.model === 'string' && agentState.model.startsWith('pending-');\n const allowedRuntimeStates = ['suspended', 'idle'];\n const allowedAgentStatuses = ['stopped', 'completed'];\n\n // Also allow resuming a \"running\" OR \"starting\" agent with no live tmux session —\n // this happens after a system crash where tmux was killed but state.json was never\n // updated to 'stopped'. For 'starting' this is a spawn that got past model\n // resolution but whose tmux session died mid-launch (the deacon patrol would\n // normally heal starting→stopped after its grace window, but that requires the\n // deacon to be running / not in OVERDECK_NO_RESUME mode). A non-placeholder\n // 'starting' agent with a saved session is resumable exactly like a crashed\n // 'running' agent; placeholder 'starting' agents (model starts with 'pending-')\n // are still rejected below because they never produced a resumable session.\n // The lifecycle UI model already treats runtime=stopped as isStopped, so this\n // keeps the gate consistent with the Resume button that model enables.\n // PAN-2098: a crash leaves the agent at status='running'/'starting' but with no\n // live process. Two shapes: (a) tmux session gone entirely, or (b) the tmux\n // session is still up while the harness process inside its pane has exited — a\n // \"keep-alive corpse\" (remain-on-exit leaves a dead pane in a live session).\n // The old check used `!sessionExists` alone, so a corpse (session present, pane\n // dead) was misclassified as a healthy running agent and refused resume with a\n // reasonless \"Cannot resume … runtime=active, status=running\". Treat a dead pane\n // as crashed too, matching the start path (flywheel-actions.ts isPaneDead).\n const isRunningOrStarting = agentState?.status === 'running' || agentState?.status === 'starting';\n const sessionAlive = isRunningOrStarting ? await Effect.runPromise(sessionExists(normalizedId)) : false;\n const paneDead = isRunningOrStarting && (!sessionAlive || await Effect.runPromise(isPaneDead(normalizedId)));\n const isCrashed = isRunningOrStarting && paneDead;\n\n // PAN-1675 (keystone): a `compact` resume exists specifically to recover a\n // context-wedged agent, which is typically status='running' with a LIVE (but\n // stuck) tmux session sitting at an overflow/idle prompt. The normal canResume\n // gate rejects running+live-session agents — which would make\n // resumeAgent({compact:true}) (the deacon's overflow recovery tiers AND\n // `pan resume --compact`) a silent no-op for exactly the agents it targets.\n // So a compact-resume of a running agent is allowed: the flow below compacts\n // the JSONL out-of-band and then kills the wedged session before relaunch.\n // This is safe because the only callers of {compact:true} act on agents they\n // have already determined to be context-overflow-wedged.\n const isCompactRecovery = opts?.compact === true && agentState?.status === 'running';\n\n const canResume = (runtimeState && allowedRuntimeStates.includes(runtimeState.state))\n || (agentState && allowedAgentStatuses.includes(agentState.status))\n || isCrashed\n || isCompactRecovery;\n\n if (!canResume) {\n // PAN-2098: never refuse without a concrete reason. A running/starting agent\n // that reached here has a live session AND a live pane (a crash would have set\n // isCrashed above), so it is genuinely healthy and there is nothing to resume.\n const reason = isRunningOrStarting\n ? `Cannot resume ${normalizedId}: it appears healthy (tmux session up, harness process alive) — there is nothing to resume. Stop it first if you intend to restart it.`\n : `Cannot resume ${normalizedId}: runtime=${runtimeState?.state || 'unknown'}, status=${agentState?.status || 'unknown'} is not a resumable state.`;\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return {\n success: false,\n error: reason\n };\n }\n\n // Get saved session ID from any available source\n const sessionId = getLatestSessionIdSync(normalizedId);\n if (!sessionId) {\n // PAN-2098: state the concrete reason. ohmypi now resolves from its session\n // JSONL (see getLatestSessionIdSync); reaching here means no id exists in any\n // source for this harness, so a fresh start is genuinely the only option.\n const harnessLabel = agentState?.harness ?? 'unknown';\n const reason = `Cannot resume ${normalizedId} (harness=${harnessLabel}): no resumable session id found — no session.id file, no sessions.json entry, and no recoverable session transcript on disk. Start a fresh agent instead.`;\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return {\n success: false,\n error: reason\n };\n }\n\n if (!agentState || !hasWorkspace || isPlaceholder) {\n const reason = 'Saved Claude session is orphaned because the backing workspace/agent state is missing or placeholder-only. Start a fresh agent instead.';\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return {\n success: false,\n error: reason\n };\n }\n\n try {\n await assertWorkspaceStackHealthyForSpawn(\n agentState.issueId || normalizedId.replace(/^agent-/, '').toUpperCase(),\n agentState.role ?? 'work',\n opts?.allowHost === true || agentState.hostOverride === true,\n agentState.workspace,\n );\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return { success: false, error: reason };\n }\n\n // PAN-1781: compact recovery = summarize the wedged session out-of-band and\n // respawn a FRESH session seeded with that summary. The previous approach\n // (PAN-1675: append a compact_boundary to the JSONL and relaunch with\n // --resume) was silently bypassed by the harness's resume leaf selection\n // ~half the time in the field — the relaunched session rebuilt the full\n // pre-compact context, re-overflowed, and escalated to /clear. A fresh\n // seeded session has nothing stale to rewind to, so its starting context is\n // bounded by construction. The old JSONL stays untouched on disk.\n // buildCompactRecoverySeed never throws: it degrades smart summary →\n // heuristic summary → reseed-instructions-only.\n let compactSeed: string | null = null;\n if (opts?.compact) {\n const seedResult = await buildCompactRecoverySeed(normalizedId);\n compactSeed = seedResult.seed;\n logAgentLifecycleSync(normalizedId, `compact recovery: respawning fresh session (seed=${seedResult.summarized ? 'summary' : 'reseed-only'})`);\n }\n\n // PAN-2009: capture whether the ohmypi process is actually alive BEFORE we kill any\n // zombie session below. A DEAD omp process cannot be resumed by session id —\n // `omp --resume` against a cleaned-up session never writes ready.json (the \"did\n // not become ready within 30s\" hang) — and there is no live transcript to\n // protect, so it must be fresh-launched (recovery, not rotation). A live\n // (suspended) omp process stays on the normal resume path.\n const piProcessWasAlive = getHarnessBehavior(agentState.harness).usesRpcFifo\n ? await hasAgentRuntimeInSession(normalizedId, 'ohmypi')\n : false;\n\n // Kill any zombie tmux session (crashed agent left behind)\n if (await Effect.runPromise(sessionExists(normalizedId))) {\n try {\n await Effect.runPromise(killSession(normalizedId));\n } catch { /* non-fatal */ }\n }\n\n // Remove completed marker so the agent can work again\n const completedFile = join(getAgentDir(normalizedId), 'completed');\n if (existsSync(completedFile)) {\n try { unlinkSync(completedFile); } catch { /* non-fatal */ }\n }\n\n // Append 'resume' session entry to continue state (PAN-946: workspace-44p)\n try {\n if (agentState?.workspace) {\n const issueId = agentState.issueId || normalizedId.replace('agent-', '').toUpperCase();\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n appendContinueSessionEntryForIssue(resolved.projectPath, issueId, {\n reason: 'resume',\n agentModel: agentState.model || undefined,\n });\n }\n }\n } catch (continueErr: any) {\n console.warn(`[resumeAgent] Failed to append resume entry to continue state (non-fatal): ${continueErr?.message ?? continueErr}`);\n }\n\n try {\n const resumeStartedAt = new Date().toISOString();\n // Clear ready signal before resuming (clean slate for PAN-87 fix)\n clearReadySignal(normalizedId);\n\n const model = requestedModel || requireModelOverrideSync(agentState.model || 'claude-sonnet-4-6');\n if (requestedModel && requestedModel !== agentState.model) {\n agentState.model = requestedModel;\n saveAgentStateSync(agentState);\n }\n // PAN-1797: agents predating session-origin metadata must not have their\n // stored harness treated as `explicit` on auto-resume — that pins a stale\n // pairing (e.g. gpt-5.5 on claude-code) over the provider default forever.\n // Re-resolve from the model for origin-less agents (only an operator-supplied\n // opts.harness counts as explicit); agents WITH origin metadata keep prior\n // behavior and are handled by sessionResumeDriftReasons below.\n const hasSessionOrigin = !!(runtimeState?.sessionModel && runtimeState?.sessionHarness);\n const priorHarness = agentState.harness;\n const effectiveHarness = await resolveHarness({\n explicit: hasSessionOrigin ? (opts?.harness ?? agentState.harness) : opts?.harness,\n role: agentState.role,\n model,\n });\n const legacyHarnessMigrated =\n !hasSessionOrigin && priorHarness !== undefined && priorHarness !== effectiveHarness;\n agentState.harness = effectiveHarness;\n const supervisorLaunch = await prepareSupervisorForRelaunch(normalizedId, agentState, model, effectiveHarness);\n saveAgentStateSync(agentState);\n const resumeDriftReasons = sessionResumeDriftReasons(runtimeState, model, effectiveHarness);\n if (legacyHarnessMigrated) {\n // PAN-1797: force a fresh session so the re-defaulted harness takes effect;\n // never reuse a session across a harness change.\n resumeDriftReasons.push(`legacy harness ${priorHarness}→${effectiveHarness} (PAN-1797 re-default)`);\n }\n // PAN-2009: a dead ohmypi process is fresh-launchable recovery — force a fresh\n // session (no `omp --resume`, which would hang waiting for ready.json) instead\n // of a doomed resume-by-id. This is NOT session rotation (no live session or\n // transcript exists to protect), so it is exempt from the PAN-1980 refusal\n // below — it adds no compact seed and no drift reason. Live (suspended) omp and\n // compact/drift resumes are unaffected.\n const piDeadRecovery = effectiveHarness === 'ohmypi' && !piProcessWasAlive\n && !compactSeed && resumeDriftReasons.length === 0;\n if (piDeadRecovery) {\n logAgentLifecycleSync(normalizedId, 'resumeAgent: dead ohmypi process — fresh-launching for recovery instead of omp --resume (PAN-2009)');\n }\n const shouldResumeSavedSession = !compactSeed && resumeDriftReasons.length === 0 && !piDeadRecovery;\n // PAN-1980: refuse to rotate to a new session. A resume that would need a\n // fresh session — compact/overflow recovery or model/harness drift — now\n // errors and stops instead of starting a new transcript.\n if (sessionRotationRefused({ compactSeed: Boolean(compactSeed), driftReasons: resumeDriftReasons, allowExplicitRecovery: opts?.compact === true && opts.recoverGated === true })) {\n const reason = compactSeed\n ? 'context-overflow compaction would respawn a fresh session'\n : `session drift (${resumeDriftReasons.join(', ')})`;\n const errMsg = `Refusing to rotate ${normalizedId} to a new session — ${reason}; session rotation is disabled (PAN-1980). Agent left stopped.`;\n logAgentLifecycleSync(normalizedId, `resumeAgent: ${errMsg}`);\n emitActivityEntrySync({ source: 'work-agent', level: 'error', message: `${normalizedId}: ${errMsg}`, issueId: agentState.issueId });\n return { success: false, error: errMsg };\n }\n const freshSessionId = !shouldResumeSavedSession && effectiveHarness === 'claude-code'\n ? randomUUID()\n : undefined;\n if (resumeDriftReasons.length > 0) {\n logAgentLifecycleSync(normalizedId, `resumeAgent: starting fresh session instead of --resume because session origin drifted (${resumeDriftReasons.join(', ')})`);\n }\n if (freshSessionId) {\n saveSessionId(normalizedId, freshSessionId);\n } else if (!shouldResumeSavedSession) {\n try {\n unlinkSync(join(getAgentDir(normalizedId), 'session.id'));\n } catch { /* absent or already cleared */ }\n }\n\n // Compute the effective message before building the launcher so codex can\n // embed it as the inline prompt in `codex exec resume <threadId> <message>`.\n // PAN-1781: a compact recovery skips the kickoff-redelivery machinery — its\n // seed (summary + reseed instructions) IS the opening prompt of the fresh\n // session; a caller-supplied message rides along after it.\n const issueId = agentState.issueId || normalizedId.replace(/^agent-/, '').toUpperCase();\n const defaultResumeMessage = buildDefaultResumeContinueMessage(issueId);\n const resumeMessage: { message?: string; redeliveringKickoff: boolean; error?: string } = compactSeed\n ? { message: message ? `${compactSeed}\\n\\n${message}` : compactSeed, redeliveringKickoff: false }\n : resumeDriftReasons.length > 0\n ? { message: message ?? defaultResumeMessage, redeliveringKickoff: false }\n : await buildResumeMessageForAgent(agentState, defaultResumeMessage, message);\n if (resumeMessage.error) {\n console.error(`[resumeAgent] ${resumeMessage.error}`);\n emitActivityEntrySync({\n source: 'work-agent',\n level: 'error',\n message: `${normalizedId}: ${resumeMessage.error}`,\n issueId,\n });\n return { success: false, error: resumeMessage.error };\n }\n const effectiveMessage = resumeMessage.message ?? defaultResumeMessage;\n\n const { launcherContent, providerEnv } = await buildAgentLaunchConfig({\n agentId: normalizedId,\n model,\n workspace: agentState.workspace,\n role: agentState.role,\n isPlanning: agentState.role === 'plan',\n // PAN-1781/PAN-1787: compact recovery and model/harness drift launch a\n // fresh session. Normal resumes keep re-attaching to the saved session.\n ...(shouldResumeSavedSession ? { spawnMode: 'resume' as const, resumeSessionId: sessionId } : {}),\n sessionId: freshSessionId,\n harness: effectiveHarness,\n useSupervisor: supervisorLaunch.useSupervisor,\n supervisorScriptPath: supervisorLaunch.supervisorScriptPath,\n });\n\n const launcherScript = join(getAgentDir(normalizedId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n const claudeCmd = `bash ${launcherScript}`;\n\n await Effect.runPromise(createSession(normalizedId, agentState.workspace, claudeCmd, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: agentState.issueId || '',\n OVERDECK_SESSION_TYPE: agentState.role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n ...providerEnv\n }\n }));\n\n // Always wake the resumed agent with a continue prompt — without it, the\n // re-attached session sits silently at its last state, and the user (or\n // deacon nudge loop) ends up sending one manually anyway. Default matches\n // restartAgent's wording so behaviour is consistent across both entry points.\n // Caller-supplied message wins.\n\n let messageDelivered = false;\n if (effectiveHarness === 'ohmypi') {\n // ohmypi does not fire the Claude SessionStart hook; wait for ready.json and\n // deliver the auto-continue prompt through the FIFO JSONL protocol.\n try {\n await writeOhmypiAgentPrompt(normalizedId, effectiveMessage);\n messageDelivered = true;\n if (resumeMessage.redeliveringKickoff) markKickoffRedelivered(agentState);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[resumeAgent] ohmypi prompt delivery failed: ${msg}`);\n }\n } else if (effectiveHarness === 'codex') {\n const delivery = await deliverInitialPromptWithRetry(normalizedId, effectiveMessage, 'resumeAgent:codex-continue', resilientDeliveryMethod(agentState.deliveryMethod));\n messageDelivered = delivery.ok;\n if (delivery.ok && resumeMessage.redeliveringKickoff) markKickoffRedelivered(agentState);\n if (!delivery.ok) {\n console.error(`[resumeAgent] Codex continue prompt did not land: ${delivery.failure ?? 'unknown failure'}`);\n }\n } else if (!shouldResumeSavedSession) {\n // Fresh session fallback — deliver like a kickoff. Transcript\n // confirmation is impossible here: the new session's id is unknown until\n // its SessionStart hook fires, and the saved sessionId points at the\n // archived or mismatched session. deliverInitialPromptWithRetry waits for\n // the ready signal internally.\n const delivery = await deliverInitialPromptWithRetry(normalizedId, effectiveMessage, 'resumeAgent:compact-seed', resilientDeliveryMethod(agentState.deliveryMethod));\n messageDelivered = delivery.ok;\n if (!delivery.ok) {\n console.error(`[resumeAgent] Fresh-session continue prompt did not land: ${delivery.failure ?? 'unknown failure'}`);\n }\n } else {\n // Wait for SessionStart hook to signal ready (PAN-87: reliable message delivery)\n const ready = await waitForReadySignal(normalizedId, 30);\n if (ready) {\n const delivery = await deliverResumeMessageWithTranscriptConfirmation({\n agentId: normalizedId,\n workspace: agentState.workspace,\n sessionId,\n message: effectiveMessage,\n caller: 'resumeAgent:auto-continue',\n deliveryMethod: resilientDeliveryMethod(agentState.deliveryMethod),\n });\n messageDelivered = delivery.delivered;\n if (delivery.delivered && resumeMessage.redeliveringKickoff) markKickoffRedelivered(agentState);\n if (!delivery.delivered) {\n console.error(`[resumeAgent] Auto-continue prompt did not land after ${delivery.attempts} delivery attempts`);\n }\n } else {\n console.error('Claude SessionStart hook did not fire during resume, continue prompt not sent');\n }\n }\n\n const resumedAt = new Date().toISOString();\n if (compactSeed) {\n console.log(`[agents] Respawned ${normalizedId} fresh with compact-recovery seed (archived session ${sessionId}${freshSessionId ? `, new session ${freshSessionId}` : ''})`);\n logAgentLifecycleSync(normalizedId, `resumeAgent SUCCESS: compact-recovery fresh respawn (archived sessionId=${sessionId}${freshSessionId ? `, newSessionId=${freshSessionId}` : ''}), messageDelivered=${messageDelivered}`);\n } else if (piDeadRecovery) {\n console.log(`[agents] Respawned ${normalizedId} fresh because the prior Pi process was dead (archived session ${sessionId})`);\n logAgentLifecycleSync(normalizedId, `resumeAgent SUCCESS: fresh respawn after dead Pi process (archived sessionId=${sessionId}), messageDelivered=${messageDelivered}`);\n } else if (!shouldResumeSavedSession) {\n console.log(`[agents] Respawned ${normalizedId} fresh because session origin drifted (archived session ${sessionId}${freshSessionId ? `, new session ${freshSessionId}` : ''})`);\n logAgentLifecycleSync(normalizedId, `resumeAgent SUCCESS: fresh respawn after origin drift (archived sessionId=${sessionId}${freshSessionId ? `, newSessionId=${freshSessionId}` : ''}), messageDelivered=${messageDelivered}`);\n } else {\n console.log(`[agents] Resumed ${normalizedId} with Claude session ${sessionId}`);\n logAgentLifecycleSync(normalizedId, `resumeAgent SUCCESS: sessionId=${sessionId}, messageDelivered=${messageDelivered}`);\n }\n await saveAgentRuntimeState(normalizedId, {\n state: 'active',\n lastActivity: resumedAt,\n });\n\n // Update agent state\n if (agentState) {\n if (bypassTroubledGate) {\n delete agentState.troubled;\n delete agentState.troubledAt;\n delete agentState.consecutiveFailures;\n delete agentState.firstFailureInRunAt;\n delete agentState.lastFailureAt;\n delete agentState.lastFailureReason;\n delete agentState.lastFailureNextRetryAt;\n }\n agentState.lastResumeAt = resumeStartedAt;\n markAgentRunning(agentState, { preserveFailureTracking: true });\n saveAgentStateSync(agentState);\n }\n\n // PAN-1675: a successful compaction-resume genuinely recovers a\n // context-overflow-wedged agent — so clear a context_overflow `stuck` flag\n // here (set by markWorkspaceStuck once the old /compact+/clear ladder\n // exhausted). Without this the agent would stay flagged stuck forever and\n // the deacon's overflowBlocked gate would keep skipping its recovery, even\n // though the agent is now healthy. Only clear when the stuck reason is\n // context_overflow (don't clobber an unrelated stuck state).\n if (opts?.compact && agentState?.issueId) {\n try {\n const db = getOverdeckDatabaseSync();\n const row = db.prepare('SELECT stuck, stuck_reason FROM review_status WHERE issue_id = ?')\n .get(agentState.issueId.toUpperCase()) as { stuck?: number; stuck_reason?: string | null } | undefined;\n if (row?.stuck === 1 && row.stuck_reason === 'context_overflow') {\n db.prepare(`\n UPDATE review_status\n SET stuck = 0, stuck_reason = NULL, stuck_at = NULL, stuck_details = NULL, updated_at = ?\n WHERE issue_id = ?\n `).run(Date.now(), agentState.issueId.toUpperCase());\n logAgentLifecycleSync(normalizedId, `cleared context_overflow stuck flag after compaction-resume for ${agentState.issueId}`);\n }\n } catch (clearErr) {\n console.warn(`[agents] Could not clear stuck flag after compaction-resume for ${normalizedId}:`, clearErr);\n }\n }\n\n return { success: true, messageDelivered };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `resumeAgent FAILED: ${msg}`);\n return {\n success: false,\n error: `Failed to resume agent: ${msg}`\n };\n }\n}\n\n/**\n * Check whether a tmux session has an active agent runtime.\n * A session may exist with only a bare bash shell after Claude exits.\n */\nasync function hasAgentRuntimeInSession(sessionName: string, harness: RuntimeName): Promise<boolean> {\n try {\n const panePids = await Effect.runPromise(listPaneValues(sessionName, '#{pane_pid}'));\n if (panePids.length === 0) return false;\n return hasAgentRuntimeInSubtree(panePids[0]!, harness);\n } catch {\n return false;\n }\n}\n","import { existsSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { sendEscapeKeyAsync, sendKeys } from './tmux.js';\nimport { getHarnessBehavior } from './runtimes/behavior.js';\nimport type { RuntimeName } from './runtimes/types.js';\n\nexport const GRACEFUL_RESTART_GRACE_MS = 60_000;\n\nexport async function sendGracefulRestartWarning(\n agentId: string,\n harness: RuntimeName | undefined,\n workspace: string,\n): Promise<void> {\n const warning = 'Restarting in 60s. Update .pan/continue.json now with all progress, decisions, hazards, and resume point.';\n try {\n if (harness && !getHarnessBehavior(harness).usesRpcFifo) {\n await sendEscapeKeyAsync(agentId, 2);\n await new Promise((r) => setTimeout(r, 1_000));\n }\n await Effect.runPromise(sendKeys(agentId, warning));\n } catch { /* non-fatal — session may already be dead */ }\n\n await new Promise(r => setTimeout(r, GRACEFUL_RESTART_GRACE_MS));\n\n const continueFile = join(workspace, '.pan', 'continue.json');\n if (existsSync(continueFile)) {\n const mtime = statSync(continueFile).mtimeMs;\n const ageMs = Date.now() - mtime;\n if (ageMs > 5 * 60 * 1000) {\n console.warn(`[restartAgent] continue.json is stale (${Math.round(ageMs / 1000)}s old) — proceeding anyway`);\n }\n }\n}\n","import { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { sendGracefulRestartWarning } from '../graceful-restart.js';\nimport { checkHookSync, generateFixedPointPromptSync } from '../hooks.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { resolveHarness } from '../harness-resolve.js';\nimport { normalizeModelOverrideSync, requireModelOverrideSync } from '../model-validation.js';\nimport { logAgentLifecycleSync } from '../persistent-logger.js';\nimport { getProviderForModelSync, setupCredentialFileAuthSync, clearCredentialFileAuthSync } from '../providers.js';\nimport type { ModelId } from '../settings.js';\nimport { normalizeHarness } from '../overdeck/conversations.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport {\n createSession,\n createSessionSync,\n killSessionSync,\n listPaneValues,\n sendKeys,\n sessionExists,\n sessionExistsSync,\n} from '../tmux.js';\nimport {\n decideResumeGate,\n getAgentDir,\n getAgentResumeGateBlockReason,\n getAgentStateSync,\n markAgentRunning,\n saveAgentStateSync,\n type AgentState,\n type Role,\n} from './agent-state.js';\nimport { deliverAgentMessage, deliverInitialPromptWithRetry, resilientDeliveryMethod } from './delivery.js';\nimport { clearReadySignal, normalizeAgentId } from './identity.js';\nimport { listRunningAgentsSync } from './queries.js';\nimport { getProviderEnvForModel, getProviderExportsForModel } from './provider-env.js';\nimport { saveAgentRuntimeState } from './runtime-state.js';\nimport {\n claudeSystemPromptFiles,\n getCodexLauncherFields,\n getRoleRuntimeBaseCommand,\n hasAgentRuntimeInSubtree,\n waitForPromptReady,\n writeLauncherScriptAtomic,\n writeOhmypiAgentPrompt,\n} from './runtime-command.js';\nimport { assertWorkspaceStackHealthyForSpawn, buildAgentLaunchConfig } from './spawn-prep.js';\nimport { prepareSupervisorForRelaunch } from './supervisor-channels.js';\nimport { stopAgent } from './termination.js';\n\nexport interface RestartAgentOptions {\n model?: string;\n harness?: RuntimeName;\n graceful?: boolean;\n message?: string;\n}\n\nexport async function restartAgent(\n agentId: string,\n opts: RestartAgentOptions = {},\n): Promise<{ success: boolean; error?: string }> {\n const normalizedId = normalizeAgentId(agentId);\n const { graceful = true, model: rawNewModel, harness: newHarness, message } = opts;\n const newModel = normalizeModelOverrideSync(rawNewModel);\n\n const agentState = getAgentStateSync(normalizedId);\n if (!agentState) {\n return { success: false, error: `Agent ${normalizedId} not found` };\n }\n const gateDecision = decideResumeGate(getAgentResumeGateBlockReason(agentState), 'operator-start');\n if (gateDecision.decision === 'block') {\n const reason = `Cannot restart ${normalizedId}: ${gateDecision.reason}. Clear the gate before restarting.`;\n logAgentLifecycleSync(normalizedId, `restartAgent BLOCKED: ${reason}`);\n return { success: false, error: reason };\n }\n if (!agentState.workspace || !existsSync(agentState.workspace)) {\n return { success: false, error: `Agent workspace missing: ${agentState.workspace}` };\n }\n\n logAgentLifecycleSync(normalizedId, `restartAgent called (graceful=${graceful}, model=${newModel || 'unchanged'}, harness=${newHarness || 'unchanged'})`);\n\n try {\n await assertWorkspaceStackHealthyForSpawn(\n agentState.issueId || normalizedId.replace(/^agent-/, '').toUpperCase(),\n agentState.role ?? 'work',\n agentState.hostOverride === true,\n agentState.workspace,\n );\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `restartAgent BLOCKED: ${reason}`);\n return { success: false, error: reason };\n }\n\n if (graceful && await Effect.runPromise(sessionExists(normalizedId))) {\n await sendGracefulRestartWarning(normalizedId, agentState.harness, agentState.workspace);\n }\n\n await Effect.runPromise(stopAgent(normalizedId));\n\n const effectiveModel = newModel || requireModelOverrideSync(agentState.model || 'claude-sonnet-4-6');\n const effectiveHarness = await resolveHarness({\n explicit: newHarness ?? agentState.harness,\n role: agentState.role,\n model: effectiveModel,\n });\n if (newModel && newModel !== agentState.model) {\n agentState.model = newModel;\n }\n agentState.harness = effectiveHarness;\n agentState.status = 'starting';\n saveAgentStateSync(agentState);\n\n try {\n clearReadySignal(normalizedId);\n const supervisorLaunch = await prepareSupervisorForRelaunch(normalizedId, agentState, effectiveModel, effectiveHarness);\n saveAgentStateSync(agentState);\n\n const { launcherContent, providerEnv } = await buildAgentLaunchConfig({\n agentId: normalizedId,\n model: effectiveModel,\n workspace: agentState.workspace,\n role: agentState.role,\n isPlanning: agentState.role === 'plan',\n harness: effectiveHarness,\n useSupervisor: supervisorLaunch.useSupervisor,\n supervisorScriptPath: supervisorLaunch.supervisorScriptPath,\n });\n\n const launcherScript = join(getAgentDir(normalizedId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n const claudeCmd = `bash ${launcherScript}`;\n\n await Effect.runPromise(createSession(normalizedId, agentState.workspace, claudeCmd, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n TERM: 'xterm-256color',\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: agentState.issueId || '',\n OVERDECK_SESSION_TYPE: agentState.role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n GIT_SEQUENCE_EDITOR: 'false',\n ...providerEnv,\n },\n }));\n\n const prompt = message || `You are resuming work on ${agentState.issueId}. Read .pan/continue.json for context and pick up where you left off.`;\n if (effectiveHarness === 'ohmypi') {\n // ohmypi does not fire the Claude SessionStart hook and does not read tmux\n // input — wait for ready.json and write the continue prompt through the\n // FIFO JSONL protocol.\n try {\n await writeOhmypiAgentPrompt(normalizedId, prompt);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[restartAgent] ohmypi prompt delivery failed for ${normalizedId}: ${msg}`);\n }\n } else {\n const ready = await waitForPromptReady(normalizedId, effectiveHarness, 30);\n if (ready) {\n await new Promise(r => setTimeout(r, 500));\n if (effectiveHarness === 'codex') {\n await deliverAgentMessage(normalizedId, prompt, 'restartAgent:continue-prompt', resilientDeliveryMethod(agentState.deliveryMethod));\n } else {\n await Effect.runPromise(sendKeys(normalizedId, prompt));\n }\n } else {\n console.error(`[restartAgent] ${effectiveHarness === 'codex' ? 'Codex' : 'Claude'} did not become ready within 30s for ${normalizedId}`);\n }\n }\n\n markAgentRunning(agentState);\n saveAgentStateSync(agentState);\n\n await saveAgentRuntimeState(normalizedId, {\n state: 'active',\n lastActivity: new Date().toISOString(),\n });\n\n logAgentLifecycleSync(normalizedId, `restartAgent SUCCESS: model=${effectiveModel}`);\n return { success: true };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `restartAgent FAILED: ${msg}`);\n return { success: false, error: `Failed to restart agent: ${msg}` };\n }\n}\n\n/**\n * Check whether a tmux session has an active agent runtime.\n * A session may exist with only a bare bash shell after Claude exits.\n */\nasync function hasAgentRuntimeInSession(sessionName: string, harness: RuntimeName): Promise<boolean> {\n try {\n const panePids = await Effect.runPromise(listPaneValues(sessionName, '#{pane_pid}'));\n if (panePids.length === 0) return false;\n return hasAgentRuntimeInSubtree(panePids[0]!, harness);\n } catch {\n return false;\n }\n}\n\n/**\n * Detect crashed agents (state shows running but tmux session is gone)\n */\nexport function detectCrashedAgents(): AgentState[] {\n const agents = listRunningAgentsSync();\n return agents.filter(\n (agent) => agent.status === 'running' && !agent.tmuxActive\n );\n}\n\n/**\n * Recover a crashed agent by restarting it with context\n */\nexport async function recoverAgent(\n agentId: string,\n opts: { modelOverride?: string } = {},\n): Promise<AgentState | null> {\n const normalizedId = normalizeAgentId(agentId);\n logAgentLifecycleSync(normalizedId, 'recoverAgent called');\n const state = getAgentStateSync(normalizedId);\n\n if (!state) {\n logAgentLifecycleSync(normalizedId, 'recoverAgent BLOCKED: no state.json');\n return null;\n }\n\n // Runtime state files may lack required fields (PAN-150)\n if (!state.id) state.id = normalizedId;\n const gateDecision = decideResumeGate(getAgentResumeGateBlockReason(state), 'operator-start');\n if (gateDecision.decision === 'block') {\n logAgentLifecycleSync(normalizedId, `recoverAgent BLOCKED: Cannot recover ${normalizedId}: ${gateDecision.reason}. Clear the gate before recovering.`);\n return null;\n }\n const modelOverride = normalizeModelOverrideSync(opts.modelOverride);\n if (modelOverride) {\n state.model = modelOverride;\n logAgentLifecycleSync(normalizedId, `recoverAgent: model overridden → ${modelOverride}`);\n }\n if (!state.workspace || !state.model) {\n const reason = `[agents] Cannot recover ${normalizedId}: state.json missing workspace or model`;\n console.error(reason);\n logAgentLifecycleSync(normalizedId, `recoverAgent BLOCKED: ${reason}`);\n return null;\n }\n\n const recoveryRole: Role = state.role\n ?? (normalizedId.startsWith('planning-') ? 'plan' : 'work');\n try {\n await assertWorkspaceStackHealthyForSpawn(\n state.issueId || normalizedId.replace(/^agent-/, '').toUpperCase(),\n recoveryRole,\n state.hostOverride === true,\n state.workspace,\n );\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `recoverAgent BLOCKED: ${reason}`);\n return null;\n }\n\n // Check if already running — session may exist with only a bare shell\n // after Claude exited (zombie session). Kill it and recover.\n if (sessionExistsSync(normalizedId)) {\n const recoveryHarness: RuntimeName = normalizeHarness(state.harness ?? null) ?? 'claude-code';\n if (await hasAgentRuntimeInSession(normalizedId, recoveryHarness)) {\n return state;\n }\n console.log(`[agents] ${normalizedId} tmux session is a zombie (no ${recoveryHarness} runtime) — killing and recovering`);\n try { killSessionSync(normalizedId); } catch { /* ignore */ }\n }\n\n // Update crash count in health file\n const healthFile = join(getAgentDir(normalizedId), 'health.json');\n let health = { consecutiveFailures: 0, killCount: 0, recoveryCount: 0 };\n if (existsSync(healthFile)) {\n try {\n health = { ...health, ...JSON.parse(readFileSync(healthFile, 'utf-8')) };\n } catch {}\n }\n health.recoveryCount = (health.recoveryCount || 0) + 1;\n writeFileSync(healthFile, JSON.stringify(health, null, 2));\n\n // Build recovery prompt\n const recoveryPrompt = generateRecoveryPrompt(state);\n\n // Get provider env for the agent's model (reads latest API key from settings)\n const providerEnv = state.model ? await getProviderEnvForModel(state.model) : {};\n\n // For credential-file providers, ensure apiKeyHelper is configured.\n // For all other providers, clear stale apiKeyHelper from previous runs.\n if (state.model) {\n const provider = getProviderForModelSync(state.model as ModelId);\n if (provider.authType === 'credential-file') {\n setupCredentialFileAuthSync(provider, state.workspace);\n } else {\n clearCredentialFileAuthSync(state.workspace);\n }\n }\n\n // Restart the agent with recovery context. PAN-1048 C4: derive the role from\n // the saved AgentState (or the session-id heuristic for legacy planning-* IDs)\n // and route through getRoleRuntimeBaseCommand so review/test/ship don't get\n // resurrected as work agents.\n const recoveryHarness: RuntimeName = normalizeHarness(state.harness ?? null) ?? 'claude-code';\n const recoverySupervisorLaunch = await prepareSupervisorForRelaunch(normalizedId, state, state.model, recoveryHarness);\n saveAgentStateSync(state);\n\n if (recoveryHarness === 'ohmypi') {\n // PAN-1055: ohmypi cannot consume the recovery prompt as a positional shell\n // argument the way the Claude direct command path does — ohmypi reads JSONL\n // commands from its FIFO. Build a real ohmypi launcher (extension path,\n // --session-dir, FIFO redirect) via buildAgentLaunchConfig, then deliver\n // the recovery prompt through the FIFO once omp reports ready.\n const { launcherContent, providerEnv: piProviderEnv } = await buildAgentLaunchConfig({\n agentId: normalizedId,\n model: state.model,\n workspace: state.workspace,\n role: recoveryRole,\n isPlanning: recoveryRole === 'plan',\n harness: 'ohmypi',\n });\n const launcherScript = join(getAgentDir(normalizedId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n await Effect.runPromise(createSession(normalizedId, state.workspace, `bash ${launcherScript}`, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: state.issueId || '',\n OVERDECK_SESSION_TYPE: recoveryRole,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n ...piProviderEnv,\n },\n }));\n try {\n await writeOhmypiAgentPrompt(normalizedId, recoveryPrompt);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[recoverAgent] ohmypi recovery prompt delivery failed for ${normalizedId}: ${msg}`);\n }\n markAgentRunning(state);\n saveAgentStateSync(state);\n logAgentLifecycleSync(normalizedId, `recoverAgent SUCCESS: recoveryCount=${health.recoveryCount} (ohmypi)`);\n return state;\n }\n\n const recoveryCodexFields = recoveryHarness === 'codex'\n ? getCodexLauncherFields(normalizedId, state.model, state.workspace)\n : {};\n const recoveryLauncherContent = generateLauncherScriptSync({\n role: recoveryRole,\n workingDir: state.workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports: (await getProviderExportsForModel(state.model)).trimEnd(),\n baseCommand: await getRoleRuntimeBaseCommand(state.model, normalizedId, recoveryRole, recoveryHarness),\n appendSystemPromptFiles: await claudeSystemPromptFiles(state.workspace, recoveryHarness),\n ...(recoveryHarness === 'codex' ? {} : { promptInline: recoveryPrompt }),\n useSupervisor: recoverySupervisorLaunch.useSupervisor,\n supervisorScriptPath: recoverySupervisorLaunch.supervisorScriptPath,\n ...recoveryCodexFields,\n });\n const launcherScript = join(getAgentDir(normalizedId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, recoveryLauncherContent);\n createSessionSync(normalizedId, state.workspace, `bash ${launcherScript}`, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: state.issueId || '',\n OVERDECK_SESSION_TYPE: state.role ?? (normalizedId.startsWith('planning-') ? 'plan' : 'work'),\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n ...providerEnv\n }\n });\n\n saveAgentStateSync(state);\n if (recoveryHarness === 'codex') {\n const delivery = await deliverInitialPromptWithRetry(normalizedId, recoveryPrompt, 'recoverAgent:recovery-prompt', state.deliveryMethod);\n if (!delivery.ok) {\n console.error(`[recoverAgent] Codex recovery prompt delivery failed for ${normalizedId}: ${delivery.failure ?? 'unknown failure'}`);\n }\n }\n // Update state\n markAgentRunning(state);\n saveAgentStateSync(state);\n\n logAgentLifecycleSync(normalizedId, `recoverAgent SUCCESS: recoveryCount=${health.recoveryCount}`);\n return state;\n}\n\n/**\n * Generate a recovery prompt for a crashed agent\n */\nfunction generateRecoveryPrompt(state: AgentState): string {\n const lines: string[] = [\n '# Agent Recovery',\n '',\n '⚠️ This agent session was recovered after a crash.',\n '',\n '## Previous Context',\n `- Issue: ${state.issueId}`,\n `- Workspace: ${state.workspace}`,\n `- Started: ${state.startedAt}`,\n '',\n '## Recovery Steps',\n '1. Check beads for context: `bd show ' + state.issueId + '`',\n '2. Review recent git commits: `git log --oneline -10`',\n '3. Check hook for pending work: `pan admin fpp check`',\n '4. Resume from last known state',\n '',\n '## FPP Reminder',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n // Add FPP work if available\n const { hasWork } = checkHookSync(state.id);\n if (hasWork) {\n const fixedPointPrompt = generateFixedPointPromptSync(state.id);\n if (fixedPointPrompt) {\n lines.push('---');\n lines.push('');\n lines.push(fixedPointPrompt);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Auto-recover all crashed agents\n */\nexport async function autoRecoverAgents(): Promise<{ recovered: string[]; failed: string[] }> {\n const crashed = detectCrashedAgents();\n const recovered: string[] = [];\n const failed: string[] = [];\n\n for (const agent of crashed) {\n try {\n const result = await recoverAgent(agent.id);\n if (result) {\n recovered.push(agent.id);\n } else {\n failed.push(agent.id);\n }\n } catch (error) {\n failed.push(agent.id);\n }\n }\n\n return { recovered, failed };\n}\n","import {\n compileGlob,\n getDispatchableItems,\n groupItemsByWave,\n hasFileOverlap,\n type CompiledGlob,\n type Wave,\n} from './dag.js';\nimport { Effect } from 'effect';\nimport type { FsError } from '../errors.js';\nimport { findSpecByIssue } from '../pan-dir/specs.js';\nimport type { FilesScopeConfidence, ItemReadiness, VBriefDocument, VBriefItem } from './types.js';\n\nexport interface SwarmReadinessOptions {\n hotspots?: string[];\n}\n\nexport interface SwarmReadinessOverlap {\n itemId: string;\n sharedFiles: string[];\n}\n\nexport interface SwarmReadinessItemVerdict {\n id: string;\n readiness?: ItemReadiness;\n slotEligible: boolean;\n scopeConfidence?: FilesScopeConfidence;\n missingScope: boolean;\n overlaps: SwarmReadinessOverlap[];\n}\n\nexport interface SwarmReadinessConflictGroup {\n itemIds: string[];\n sharedFiles: string[];\n reason: 'file_overlap' | 'low_confidence';\n}\n\nexport interface SwarmReadinessVerdict {\n items: SwarmReadinessItemVerdict[];\n waves: Wave[];\n conflictGroups: SwarmReadinessConflictGroup[];\n overlapMatrix: Record<string, Record<string, string[]>>;\n swarmEligible: boolean;\n}\n\nexport function computeIssueFootprint(doc: VBriefDocument): string[] {\n const footprint = new Set<string>();\n for (const item of doc.plan.items) {\n for (const filePath of item.metadata?.files_scope ?? []) {\n footprint.add(filePath);\n }\n }\n return Array.from(footprint).sort();\n}\n\nexport function resolveIssueFootprint(\n projectRoot: string,\n issueId: string,\n): Effect.Effect<string[], FsError> {\n return Effect.gen(function* () {\n const spec = yield* findSpecByIssue(projectRoot, issueId);\n return spec ? computeIssueFootprint(spec.document) : [];\n });\n}\n\ninterface NormalizedItem {\n item: VBriefItem;\n scope: string[];\n lowConfidence: boolean;\n missingScope: boolean;\n}\n\nexport function analyzeSwarmReadiness(\n doc: VBriefDocument,\n opts: SwarmReadinessOptions = {},\n): SwarmReadinessVerdict {\n const hotspots = (opts.hotspots ?? []).map(compileGlob);\n const normalizedItems = doc.plan.items.map(item => normalizeItem(item, hotspots));\n const normalizedById = new Map(normalizedItems.map(item => [item.item.id, item]));\n const itemVerdicts = new Map<string, SwarmReadinessItemVerdict>();\n const overlapMatrix = Object.fromEntries(\n doc.plan.items.map(item => [item.id, {} as Record<string, string[]>]),\n );\n const conflictGroups: SwarmReadinessConflictGroup[] = [];\n\n for (const item of doc.plan.items) {\n itemVerdicts.set(item.id, {\n id: item.id,\n readiness: item.metadata?.readiness,\n slotEligible: isSlotEligible(item, normalizedById.get(item.id)),\n scopeConfidence: item.metadata?.files_scope_confidence,\n missingScope: normalizedById.get(item.id)?.missingScope ?? true,\n overlaps: [],\n });\n }\n\n for (let i = 0; i < normalizedItems.length; i++) {\n for (let j = i + 1; j < normalizedItems.length; j++) {\n const left = normalizedItems[i]!;\n const right = normalizedItems[j]!;\n const overlap = analyzePairOverlap(left, right);\n if (!overlap.overlaps) continue;\n\n overlapMatrix[left.item.id]![right.item.id] = overlap.sharedFiles;\n overlapMatrix[right.item.id]![left.item.id] = overlap.sharedFiles;\n itemVerdicts.get(left.item.id)?.overlaps.push({ itemId: right.item.id, sharedFiles: overlap.sharedFiles });\n itemVerdicts.get(right.item.id)?.overlaps.push({ itemId: left.item.id, sharedFiles: overlap.sharedFiles });\n conflictGroups.push({\n itemIds: [left.item.id, right.item.id],\n sharedFiles: overlap.sharedFiles,\n reason: overlap.lowConfidence ? 'low_confidence' : 'file_overlap',\n });\n }\n }\n\n const dispatchableIds = new Set(getDispatchableItems(doc, new Set()).map(item => item.id));\n return {\n items: doc.plan.items.map(item => itemVerdicts.get(item.id)!),\n waves: groupItemsByWave(doc),\n conflictGroups,\n overlapMatrix,\n swarmEligible: Array.from(itemVerdicts.values()).some(item => item.slotEligible && dispatchableIds.has(item.id)),\n };\n}\n\nfunction normalizeItem(item: VBriefItem, hotspots: CompiledGlob[]): NormalizedItem {\n const rawScope = item.metadata?.files_scope ?? [];\n return {\n item,\n scope: rawScope.filter(filePath => !pathMatchesAnyCompiled(filePath, hotspots)),\n lowConfidence: item.metadata?.files_scope_confidence === 'low',\n missingScope: rawScope.length === 0,\n };\n}\n\nfunction isSlotEligible(item: VBriefItem, normalized?: NormalizedItem): boolean {\n if (!normalized || normalized.missingScope || normalized.lowConfidence) return false;\n return item.metadata?.readiness === 'ready'\n && normalized.scope.length > 0\n && (item.metadata.verify_commands?.length ?? 0) > 0\n && (item.metadata.expected_outputs?.length ?? 0) > 0;\n}\n\nfunction analyzePairOverlap(\n left: NormalizedItem,\n right: NormalizedItem,\n): { overlaps: boolean; sharedFiles: string[]; lowConfidence: boolean } {\n if (left.lowConfidence || right.lowConfidence) {\n return { overlaps: true, sharedFiles: sharedFilesFor(left, right), lowConfidence: true };\n }\n\n const leftItem = withScope(left.item, left.scope);\n const rightItem = withScope(right.item, right.scope);\n if (!hasFileOverlap([leftItem], rightItem)) {\n return { overlaps: false, sharedFiles: [], lowConfidence: false };\n }\n\n return { overlaps: true, sharedFiles: sharedFilesFor(left, right), lowConfidence: false };\n}\n\nfunction withScope(item: VBriefItem, scope: string[]): VBriefItem {\n return {\n ...item,\n metadata: {\n ...(item.metadata ?? {}),\n files_scope: scope,\n },\n };\n}\n\nfunction sharedFilesFor(left: NormalizedItem, right: NormalizedItem): string[] {\n const leftPatterns = left.scope.map(compileGlob);\n const rightPatterns = right.scope.map(compileGlob);\n const shared = new Set<string>();\n\n for (const filePath of left.scope) {\n if (pathMatchesAnyCompiled(filePath, rightPatterns)) shared.add(filePath);\n }\n for (const filePath of right.scope) {\n if (pathMatchesAnyCompiled(filePath, leftPatterns)) shared.add(filePath);\n }\n\n return Array.from(shared).sort();\n}\n\nfunction pathMatchesAnyCompiled(filePath: string, patterns: CompiledGlob[]): boolean {\n return patterns.some(pattern => pattern.regex.test(filePath) || pattern.exactDirectory === filePath);\n}\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { readIssueRecordForWorkspaceSync } from '../pan-dir/record.js';\nimport { findProjectByPathSync, getProjectSwarmHotspots } from '../projects.js';\nimport { analyzeSwarmReadiness } from '../vbrief/swarm-readiness.js';\nimport type { VBriefDocument } from '../vbrief/types.js';\nimport { listAgentStates } from './queries.js';\nimport type { AgentState } from './agent-state.js';\n\nconst execAsync = promisify(exec);\n\nexport type ReconciledSlotItemStatus = 'merged' | 'in_flight' | 'pending';\n\nexport interface ReconciledSlotBranch {\n slotIndex: number;\n branch: string;\n merged: boolean;\n}\n\nexport interface ReconciledSlotAgent {\n slotIndex: number;\n agentId: string;\n status: AgentState['status'];\n slotItemId?: string;\n}\n\nexport interface ReconciledSlotAssignment {\n slotIndex: number;\n itemId: string;\n agentId?: string;\n branch?: string;\n}\n\nexport interface ReconciledSlotItem {\n itemId: string;\n slotIndex: number;\n status: ReconciledSlotItemStatus;\n branch?: string;\n agentId?: string;\n}\n\nexport interface SlotReconcileResult {\n issueId: string;\n merged: ReconciledSlotItem[];\n inFlight: ReconciledSlotItem[];\n pending: ReconciledSlotItem[];\n branches: ReconciledSlotBranch[];\n agents: ReconciledSlotAgent[];\n superseded?: NonNullable<NonNullable<ReturnType<typeof readIssueRecordForWorkspaceSync>>['swarm']>['supersededAttempts'];\n}\n\nexport interface SlotReconcileDeps {\n listBranches: (issueId: string, workspace: string) => Promise<ReconciledSlotBranch[]>;\n listAgents: (issueId: string) => ReconciledSlotAgent[];\n listSlotAssignments: (issueId: string, workspace: string) => ReconciledSlotAssignment[];\n}\n\nexport interface SlotReconcileOptions {\n statusOverrides?: Record<string, string>;\n deps?: Partial<SlotReconcileDeps>;\n}\n\nexport async function reconcileSlotState(\n issueId: string,\n workspace: string,\n doc: VBriefDocument,\n options: SlotReconcileOptions = {},\n): Promise<SlotReconcileResult> {\n const deps: SlotReconcileDeps = {\n listBranches: listSlotBranches,\n listAgents: listSlotAgents,\n listSlotAssignments,\n ...options.deps,\n };\n const branches = await deps.listBranches(issueId, workspace);\n const agents = deps.listAgents(issueId);\n const assignments = deps.listSlotAssignments(issueId, workspace);\n const branchesBySlot = new Map(branches.map(branch => [branch.slotIndex, branch]));\n const agentsBySlot = new Map(agents.map(agent => [agent.slotIndex, agent]));\n const hotspots = getProjectSwarmHotspots(findProjectByPathSync(workspace));\n const slotEligibleItemIds = new Set(analyzeSwarmReadiness(doc, { hotspots }).items\n .filter(item => item.slotEligible)\n .map(item => item.id));\n const slotItems = resolveSlotItemOwnership(slotEligibleItemIds, assignments, agents);\n\n const result: SlotReconcileResult = {\n issueId,\n merged: [],\n inFlight: [],\n pending: [],\n branches,\n agents,\n superseded: readIssueRecordForWorkspaceSync(workspace, issueId.toUpperCase())?.swarm?.supersededAttempts ?? [],\n };\n\n for (const slotItem of slotItems) {\n const branch = branchesBySlot.get(slotItem.slotIndex);\n const agent = agentsBySlot.get(slotItem.slotIndex);\n const merged = options.statusOverrides?.[slotItem.itemId] === 'completed' || branch?.merged === true;\n const entry: ReconciledSlotItem = {\n ...slotItem,\n status: merged ? 'merged' : agent || branch ? 'in_flight' : 'pending',\n branch: branch?.branch,\n agentId: agent?.agentId,\n };\n\n if (entry.status === 'merged') result.merged.push(entry);\n else if (entry.status === 'in_flight') result.inFlight.push(entry);\n else result.pending.push(entry);\n }\n\n return result;\n}\n\nexport async function listSlotBranches(issueId: string, workspace: string): Promise<ReconciledSlotBranch[]> {\n const issueLower = issueId.toLowerCase();\n const pattern = `feature/${issueLower}-slot-*`;\n const [allBranches, mergedBranches] = await Promise.all([\n gitBranchNames(workspace, pattern, false),\n gitBranchNames(workspace, pattern, true),\n ]);\n const mergedSet = new Set(mergedBranches);\n return allBranches\n .map(branch => ({ branch, slotIndex: slotIndexFromBranch(issueLower, branch), merged: mergedSet.has(branch) }))\n .filter((branch): branch is ReconciledSlotBranch => branch.slotIndex !== null)\n .sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nexport function listSlotAgents(issueId: string): ReconciledSlotAgent[] {\n const issueLower = issueId.toLowerCase();\n const pattern = new RegExp(`^agent-${escapeRegExp(issueLower)}-slot-(\\\\d+)$`);\n return listAgentStates({ role: 'work' })\n .map(agent => {\n const match = pattern.exec(agent.id);\n if (!match) return null;\n const entry: ReconciledSlotAgent = {\n slotIndex: Number(match[1]),\n agentId: agent.id,\n status: agent.status,\n };\n if (agent.slotItemId) entry.slotItemId = agent.slotItemId;\n return entry;\n })\n .filter((agent): agent is ReconciledSlotAgent => agent !== null)\n .sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nexport function listSlotAssignments(issueId: string, workspace: string): ReconciledSlotAssignment[] {\n const record = readIssueRecordForWorkspaceSync(workspace, issueId.toUpperCase());\n return (record?.swarm?.slotAssignments ?? [])\n .filter(assignment => Number.isInteger(assignment.slotIndex) && assignment.slotIndex > 0 && assignment.itemId.trim().length > 0)\n .map(assignment => ({\n slotIndex: assignment.slotIndex,\n itemId: assignment.itemId,\n agentId: assignment.agentId,\n branch: assignment.branch,\n }))\n .sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nexport function listSlotOwnership(issueId: string, workspace: string): ReconciledSlotAssignment[] {\n const byItemId = new Map<string, ReconciledSlotAssignment>();\n for (const assignment of listSlotAssignments(issueId, workspace)) {\n byItemId.set(assignment.itemId, assignment);\n }\n for (const agent of listSlotAgents(issueId)) {\n if (!agent.slotItemId || byItemId.has(agent.slotItemId)) continue;\n byItemId.set(agent.slotItemId, {\n slotIndex: agent.slotIndex,\n itemId: agent.slotItemId,\n agentId: agent.agentId,\n });\n }\n return [...byItemId.values()].sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nasync function gitBranchNames(workspace: string, pattern: string, merged: boolean): Promise<string[]> {\n const { stdout } = await execAsync(\n `git branch ${merged ? '--merged HEAD ' : ''}--list ${JSON.stringify(pattern)}`,\n { cwd: workspace },\n );\n return stdout\n .split('\\n')\n .map(line => line.replace(/^[*+\\s]+/, '').trim())\n .filter(Boolean);\n}\n\nfunction resolveSlotItemOwnership(\n slotEligibleItemIds: Set<string>,\n assignments: ReconciledSlotAssignment[],\n agents: ReconciledSlotAgent[],\n): Array<{ itemId: string; slotIndex: number }> {\n const ownership = new Map<string, number>();\n\n for (const assignment of assignments) {\n if (!slotEligibleItemIds.has(assignment.itemId)) continue;\n ownership.set(assignment.itemId, assignment.slotIndex);\n }\n\n for (const agent of agents) {\n if (!agent.slotItemId || !slotEligibleItemIds.has(agent.slotItemId) || ownership.has(agent.slotItemId)) continue;\n ownership.set(agent.slotItemId, agent.slotIndex);\n }\n\n return [...ownership.entries()]\n .map(([itemId, slotIndex]) => ({ itemId, slotIndex }))\n .sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nfunction slotIndexFromBranch(issueLower: string, branch: string): number | null {\n const match = new RegExp(`^feature/${escapeRegExp(issueLower)}-slot-(\\\\d+)$`).exec(branch);\n if (!match) return null;\n return Number(match[1]);\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","/**\n * ⚠️ RETIRED SCHEDULER (PAN-2397 W3, 2026-07-05) — do not add production callers.\n *\n * StandingTierManager / computeTierRunSchedule shipped with PAN-1791 as a\n * second scheduler alongside the swarm slot allocator and never gained a\n * production caller. The Always Tiered unification made the swarm allocator\n * (deacon-swarm.ts dispatchNextWave + resolveStaffing) THE scheduler.\n *\n * No-loss audit of this module's capabilities at retirement:\n * - lazy per-tier spawn → swarm dispatch waves already spawn lazily\n * - per-bead tier staffing → resolveStaffing (staffing.ts), all spawn paths\n * - cross-bead session reuse → INAPPLICABLE under the swarm's\n * branch-per-item slots (a slot branch carries one bead, merges, is GC'd);\n * adopting it would require a branch-per-tier architecture — a separate,\n * deliberate epic decision, recorded in .pan/drafts/pan-2397.md.\n * - replay on context threshold → tier-replay.ts remains live; its production\n * seam is the slot-recovery/respawn path (PAN-2397 W3b follow-up).\n *\n * computeTierRunSchedule is kept as a pure planning helper (tests cover it);\n * the manager below is test-only until deleted.\n *\n * Original module doc:\n * Standing tier agents for tiered execution (PAN-1791, FR-6 scheduling half).\n *\n * A standing tier agent is a long-lived registered slot — spawned through\n * spawnRun(issueId, 'work', { slotIndex, slotItemId }) exactly like any other\n * registered slot agent (state file + tmux session + dashboard discovery\n * under the issue) — that persists across the many beads of its tier instead\n * of living per-bead. No new agent kind is introduced.\n *\n * The schedule is the plan-time tier-run sequence (PRD §5.6): waves are\n * linearized via groupItemsByWave, each item is annotated with its tier via\n * resolveTier, and consecutive same-tier items are cut into runs at\n * tier-change boundaries. Standing sessions are instantiated lazily — a\n * tier's session spawns when its first run is <=1 run away, not all at\n * start — so a cancelled run never strands sessions it would not have used.\n */\n\nimport type { VBriefDocument, VBriefItem } from '../vbrief/types.js';\nimport { groupItemsByWave } from '../vbrief/dag.js';\nimport { resolveTier, type ResolveTierConfig } from './resolve-tier.js';\nimport type { AgentState } from './agent-state.js';\nimport type { SpawnRunOptions } from './spawn-prep.js';\nimport { spawnRun } from './spawn.js';\n\n/** One tier run: consecutive beads in DAG order that route to the same tier. */\nexport interface TierRun {\n tierName: string;\n beadIds: string[];\n}\n\nexport class StandingTierError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StandingTierError';\n }\n}\n\n/**\n * Compute the ordered tier-run schedule from a plan and the tier table.\n * Waves are linearized in order (items within a wave in array order), each\n * item resolves to its tier, and a new run starts whenever the tier changes.\n *\n * Unroutable items (no difficulty, unknown tier) propagate resolveTier's\n * named error — scheduling failures are finalize-time errors in the same\n * family as the quality-lint checks, never silent fallbacks.\n */\nexport function computeTierRunSchedule(\n doc: VBriefDocument,\n config: ResolveTierConfig,\n): TierRun[] {\n const itemById = new Map(doc.plan.items.map((item) => [item.id, item]));\n const runs: TierRun[] = [];\n for (const wave of groupItemsByWave(doc)) {\n for (const waveItem of wave.items) {\n const item = itemById.get(waveItem.id);\n if (!item) continue;\n const tier = resolveTier(item, config);\n const currentRun = runs[runs.length - 1];\n if (currentRun && currentRun.tierName === tier.tierName) {\n currentRun.beadIds.push(item.id);\n } else {\n runs.push({ tierName: tier.tierName, beadIds: [item.id] });\n }\n }\n }\n return runs;\n}\n\n/** The exact set of tier names a schedule contains, in first-appearance order. */\nexport function tiersNeededForSchedule(schedule: TierRun[]): string[] {\n const seen = new Set<string>();\n for (const run of schedule) seen.add(run.tierName);\n return Array.from(seen);\n}\n\n/** A spawned standing tier agent tracked in the registry. */\nexport interface StandingTierAgent {\n tierName: string;\n slotIndex: number;\n agentId: string;\n /** The tier's first scheduled bead — the slotItemId its session was spawned for. */\n firstItemId: string;\n}\n\n/**\n * Spawn seam matching spawnRun's registered-slot signature. Injectable so the\n * manager is unit-testable; defaults to spawnRun.\n */\nexport type StandingTierSpawn = (\n issueId: string,\n role: 'work',\n options: SpawnRunOptions,\n) => Promise<AgentState>;\n\nexport interface StandingTierManagerOptions {\n issueId: string;\n schedule: TierRun[];\n /** Injectable for tests. Defaults to spawnRun's registered-slot path. */\n spawn?: StandingTierSpawn;\n /**\n * Slot index allocated to the first standing tier; subsequent tiers take\n * the following indexes. Defaults to 1.\n */\n firstSlotIndex?: number;\n /** Extra prompt appended to each standing slot's kickoff (foreman protocol). */\n prompt?: string;\n}\n\n/**\n * Manages the set of standing tier agents for one issue: which tiers the\n * schedule needs, lazy spawn, a registry keyed by tier name, and bead\n * routing to the standing slot.\n */\n/** The bead currently being implemented by a standing tier agent. */\nexport interface InFlightBead {\n beadId: string;\n tierName: string;\n agentId: string;\n}\n\nexport class StandingTierManager {\n private readonly registry = new Map<string, StandingTierAgent>();\n private nextSlotIndex: number;\n private inFlight: InFlightBead | undefined;\n\n constructor(private readonly options: StandingTierManagerOptions) {\n this.nextSlotIndex = options.firstSlotIndex ?? 1;\n }\n\n /** Exactly the tier names the schedule contains — no tier outside it ever spawns. */\n tiersNeeded(): string[] {\n return tiersNeededForSchedule(this.options.schedule);\n }\n\n /** The standing agent for a tier, if its session has been spawned. */\n getStandingAgent(tierName: string): StandingTierAgent | undefined {\n return this.registry.get(tierName);\n }\n\n /** 0-based index of the first run belonging to a tier, or -1 when absent. */\n firstRunIndexFor(tierName: string): number {\n return this.options.schedule.findIndex((run) => run.tierName === tierName);\n }\n\n /**\n * Lazy instantiation (PRD §5.6): ensure a standing session exists for every\n * scheduled tier whose first run is <=1 run away from currentRunIndex.\n * Already-spawned tiers are reused, never respawned. Returns the agents\n * spawned by this call.\n */\n async ensureStandingTiersForRun(currentRunIndex: number): Promise<StandingTierAgent[]> {\n const spawned: StandingTierAgent[] = [];\n for (const tierName of this.tiersNeeded()) {\n if (this.registry.has(tierName)) continue;\n const firstRun = this.firstRunIndexFor(tierName);\n if (firstRun <= currentRunIndex + 1) {\n spawned.push(await this.spawnStandingTier(tierName));\n }\n }\n return spawned;\n }\n\n /**\n * Ensure the registered slot for a tier exists, spawning the standing\n * session lazily if it does not exist yet, and return that slot's agent id.\n * Throws for a tier the schedule does not contain — the manager never\n * spawns outside the schedule.\n */\n async ensureStandingAgentForTier(tierName: string, bead: Pick<VBriefItem, 'id'>): Promise<string> {\n if (this.firstRunIndexFor(tierName) === -1) {\n throw new StandingTierError(\n `tier '${tierName}' is not in the schedule for ${this.options.issueId}; refusing to route bead '${bead.id}'`,\n );\n }\n const agent = this.registry.get(tierName) ?? await this.spawnStandingTier(tierName);\n return agent.agentId;\n }\n\n /** The bead currently in flight, if any. */\n getInFlightBead(): InFlightBead | undefined {\n return this.inFlight;\n }\n\n /**\n * Foreman dispatch step enforcing the single-implementer invariant: only\n * one implementation agent works a bead at a time — standing tiers share\n * the foreman's worktree, so two in-flight beads would race the same tree.\n * Throws while another bead is in flight; call completeBead after the\n * foreman has staged, committed, and broadcast the result.\n */\n async dispatchBeadToTier(tierName: string, bead: Pick<VBriefItem, 'id'>): Promise<string> {\n if (this.inFlight) {\n throw new StandingTierError(\n `bead '${this.inFlight.beadId}' is still in flight on tier '${this.inFlight.tierName}' for ${this.options.issueId}; `\n + `only one implementation agent works a bead at a time — complete it before dispatching '${bead.id}'`,\n );\n }\n const agentId = await this.ensureStandingAgentForTier(tierName, bead);\n this.inFlight = { beadId: bead.id, tierName, agentId };\n return agentId;\n }\n\n /** Backward-compatible name for callers that already spell the invariant explicitly. */\n async dispatchBeadExclusive(tierName: string, bead: Pick<VBriefItem, 'id'>): Promise<string> {\n return this.dispatchBeadToTier(tierName, bead);\n }\n\n /** Mark the in-flight bead complete (committed + broadcast), freeing dispatch. */\n completeBead(beadId: string): void {\n if (!this.inFlight || this.inFlight.beadId !== beadId) {\n throw new StandingTierError(\n `bead '${beadId}' is not the in-flight bead for ${this.options.issueId}`\n + `${this.inFlight ? ` ('${this.inFlight.beadId}' is)` : ' (nothing is in flight)'}`,\n );\n }\n this.inFlight = undefined;\n }\n\n private async spawnStandingTier(tierName: string): Promise<StandingTierAgent> {\n const firstRun = this.options.schedule.find((run) => run.tierName === tierName);\n const firstItemId = firstRun?.beadIds[0];\n if (!firstItemId) {\n throw new StandingTierError(\n `tier '${tierName}' has no scheduled beads for ${this.options.issueId}; nothing to spawn a standing session for`,\n );\n }\n const slotIndex = this.nextSlotIndex++;\n const spawn = this.options.spawn ?? spawnRun;\n const state = await spawn(this.options.issueId, 'work', {\n slotIndex,\n slotItemId: firstItemId,\n prompt: this.options.prompt,\n });\n const agent: StandingTierAgent = {\n tierName,\n slotIndex,\n agentId: state.id,\n firstItemId,\n };\n this.registry.set(tierName, agent);\n return agent;\n }\n}\n","import type { TierOverridesMap } from '../vbrief/io.js';\nimport type { VBriefDifficulty, VBriefItem } from '../vbrief/types.js';\nimport type { ValidatedEscalationConfig } from './tier-table.js';\nimport { TIERED_EXECUTION_DIFFICULTIES } from './tier-table.js';\n\nexport type EscalationTrigger =\n | { kind: 'supervisor-blocked'; beadId: string; sha: string; attemptsAtCurrentTier?: number }\n | { kind: 'verification-failed'; beadId: string; detail: string; attemptsAtCurrentTier?: number }\n | { kind: 'floundering'; beadId: string; dispatchedAt: string; now: string; attemptsAtCurrentTier?: number };\n\nexport type EscalationAction =\n | { action: 'retry'; attempt: number }\n | { action: 'promote'; from: VBriefDifficulty; to: VBriefDifficulty; reason: string }\n | { action: 'block'; reason: string };\n\nexport interface VerificationFailureEscalationInput {\n bead: Pick<VBriefItem, 'id' | 'metadata'>;\n config: ValidatedEscalationConfig;\n overrides: TierOverridesMap;\n detail: string;\n attemptsAtCurrentTier?: number;\n}\n\nexport interface FlounderingEscalationInput {\n bead: Pick<VBriefItem, 'id' | 'metadata'>;\n config: ValidatedEscalationConfig;\n overrides: TierOverridesMap;\n dispatchedAt: string;\n now: string;\n attemptsAtCurrentTier?: number;\n}\n\nexport function applyEffectiveDifficulty<T extends Pick<VBriefItem, 'id' | 'metadata'>>(\n item: T,\n overrides: TierOverridesMap,\n): T {\n const override = overrides[item.id];\n if (!override) return item;\n\n return {\n ...item,\n metadata: {\n ...(item.metadata ?? {}),\n difficulty: override.effectiveDifficulty,\n },\n };\n}\n\n/**\n * Pure escalation decision engine. It only emits the next action; callers own\n * retry event persistence, tier promotion writes, dispatch, and notifications.\n */\nexport function decideEscalation(\n trigger: EscalationTrigger,\n bead: Pick<VBriefItem, 'id' | 'metadata'>,\n config: ValidatedEscalationConfig,\n overrides: TierOverridesMap,\n): EscalationAction {\n const beadId = trigger.beadId || bead.id;\n const override = overrides[bead.id];\n const currentDifficulty = override?.effectiveDifficulty ?? bead.metadata?.difficulty;\n if (!currentDifficulty) {\n return { action: 'block', reason: `bead ${beadId} has no effective difficulty` };\n }\n\n const promotions = override?.promotions ?? 0;\n if (promotions >= config.max_promotions) {\n return {\n action: 'block',\n reason: `max promotions reached for ${beadId} at ${currentDifficulty}`,\n };\n }\n\n if (currentDifficulty === 'expert') {\n return {\n action: 'block',\n reason: `expert bead ${beadId} cannot promote beyond expert`,\n };\n }\n\n const attemptsAtCurrentTier = trigger.attemptsAtCurrentTier ?? 0;\n if (attemptsAtCurrentTier < config.retries_at_tier) {\n return { action: 'retry', attempt: attemptsAtCurrentTier + 1 };\n }\n\n const fromIndex = TIERED_EXECUTION_DIFFICULTIES.indexOf(currentDifficulty);\n const to = TIERED_EXECUTION_DIFFICULTIES[fromIndex + 1];\n if (!to) {\n return {\n action: 'block',\n reason: `bead ${beadId} cannot promote beyond ${currentDifficulty}`,\n };\n }\n\n return {\n action: 'promote',\n from: currentDifficulty,\n to,\n reason: triggerReason(trigger),\n };\n}\n\nexport function isFloundering(\n dispatchedAtIso: string,\n nowIso: string,\n budgetMinutes?: number,\n): boolean {\n if (budgetMinutes === undefined || budgetMinutes === null) return false;\n if (!Number.isFinite(budgetMinutes) || budgetMinutes <= 0) return false;\n\n const dispatchedAt = Date.parse(dispatchedAtIso);\n const now = Date.parse(nowIso);\n if (!Number.isFinite(dispatchedAt) || !Number.isFinite(now)) return false;\n\n return (now - dispatchedAt) / 60_000 > budgetMinutes;\n}\n\nexport function decideVerificationFailureEscalation(\n input: VerificationFailureEscalationInput,\n): EscalationAction {\n return decideEscalation({\n kind: 'verification-failed',\n beadId: input.bead.id,\n detail: input.detail,\n attemptsAtCurrentTier: input.attemptsAtCurrentTier,\n }, input.bead, input.config, input.overrides);\n}\n\nexport function decideFlounderingEscalation(\n input: FlounderingEscalationInput,\n): EscalationAction | null {\n const effectiveDifficulty = input.overrides[input.bead.id]?.effectiveDifficulty ?? input.bead.metadata?.difficulty;\n const budget = effectiveDifficulty ? input.config.flounder_budget_minutes[effectiveDifficulty] : undefined;\n if (!isFloundering(input.dispatchedAt, input.now, budget)) return null;\n return decideEscalation({\n kind: 'floundering',\n beadId: input.bead.id,\n dispatchedAt: input.dispatchedAt,\n now: input.now,\n attemptsAtCurrentTier: input.attemptsAtCurrentTier,\n }, input.bead, input.config, input.overrides);\n}\n\nfunction triggerReason(trigger: EscalationTrigger): string {\n switch (trigger.kind) {\n case 'supervisor-blocked':\n return `supervisor blocked commit ${trigger.sha}`;\n case 'verification-failed':\n return `verification failed: ${trigger.detail}`;\n case 'floundering':\n return `floundering since ${trigger.dispatchedAt}`;\n }\n}\n","/**\n * Commit feed for standing tier agents (PAN-1791, FR-7 / D10 v1).\n *\n * After each commit lands, broadcastCommit composes one ingestion-only feed\n * message (bead title + `git show <sha>` diff) and delivers it to EVERY\n * standing tier agent through deliverAgentMessage — the existing delivery\n * primitive, no new transport. v1 is everyone-hears-everything: no per-tier\n * relevance filtering, so any agent's feed is reconstructible from\n * `git log <base>..HEAD` plus the standing-tier set — exactly what replay\n * (tier-replay) consumes to rebuild a respawned session's feed.\n *\n * Feed messages are ingestion-only: the message text instructs the recipient\n * not to respond, so per-event output cost stays ~0 (NFR-1).\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { deliverAgentMessage, type DeliveryResult } from './delivery.js';\nimport type { StandingTierAgent } from './standing-tiers.js';\nimport { estimateFeedDeliveryTokens, recordTierFeedDelivery } from './tier-metrics.js';\nimport type { ValidatedTieredExecutionFeedConfig } from './tier-table.js';\nimport { DEFAULT_TIERED_EXECUTION_CONFIG } from './tier-table.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst DEFAULT_FEED_CONFIG = DEFAULT_TIERED_EXECUTION_CONFIG.feed;\n\nexport interface BroadcastCommitOptions {\n /** Workspace (or any checkout) whose git history contains the commit. */\n workspace: string;\n /** The landed commit to broadcast. */\n sha: string;\n /** Title of the bead the commit implements, shown in the feed header. */\n beadTitle: string;\n /** Id of the bead the commit implements, required for call-out corroboration. */\n beadId?: string;\n /** Commit subject used by feed.exclude_subjects. Defaults to beadTitle for legacy callers. */\n commitSubject?: string;\n /** The standing tier agents to deliver to — every one of them hears it. */\n tiers: Array<Pick<StandingTierAgent, 'tierName' | 'agentId'>>;\n /** Issue id for delivery metrics. */\n issueId?: string;\n /** Dashboard API base URL used in the listener call-out curl snippet. */\n apiUrl?: string;\n /** Feed filtering/rendering config. Defaults preserve today's raw git-show behavior. */\n feedConfig?: ValidatedTieredExecutionFeedConfig;\n /** Injectable delivery seam for tests. Defaults to deliverAgentMessage. */\n deliver?: (agentId: string, message: string, caller?: string) => Promise<DeliveryResult>;\n /** Legacy injectable `git show` runner for tests. Defaults to the shared renderer. */\n gitShow?: (workspace: string, sha: string) => Promise<string>;\n /** Injectable shared renderer seam for tests. Defaults to renderCommitFeedDiff. */\n renderDiff?: (workspace: string, sha: string, feedConfig: ValidatedTieredExecutionFeedConfig) => Promise<string>;\n /** Injectable metric recorder for tests. Defaults to the tier metrics JSONL log. */\n recordDelivery?: typeof recordTierFeedDelivery;\n /** Injectable clock for deterministic delivery metric tests. */\n now?: () => Date;\n}\n\nexport interface BroadcastDelivery {\n tierName: string;\n agentId: string;\n result: DeliveryResult;\n}\n\nexport interface RenderCommitFeedDiffDeps {\n gitShow?: (workspace: string, sha: string, args: string[]) => Promise<string>;\n}\n\nexport interface CommitFeedCalloutContext {\n apiUrl: string;\n issueId: string;\n beadId: string;\n tierName: string;\n agentId: string;\n}\n\nasync function runGitShow(workspace: string, sha: string, args: string[] = []): Promise<string> {\n const { stdout } = await execFileAsync('git', ['show', sha, ...args], {\n cwd: workspace,\n encoding: 'utf-8',\n maxBuffer: 16 * 1024 * 1024,\n });\n return stdout;\n}\n\nexport function shouldSkipFeedSubject(\n subject: string,\n feedConfig: Pick<ValidatedTieredExecutionFeedConfig, 'exclude_subjects'>,\n): boolean {\n return feedConfig.exclude_subjects.some(prefix => subject.startsWith(prefix));\n}\n\nexport async function renderCommitFeedDiff(\n workspace: string,\n sha: string,\n feedConfig: ValidatedTieredExecutionFeedConfig = DEFAULT_FEED_CONFIG,\n deps: RenderCommitFeedDiffDeps = {},\n): Promise<string> {\n const gitShow = deps.gitShow ?? runGitShow;\n const pathspecArgs = feedConfig.exclude.length > 0\n ? ['--', '.', ...feedConfig.exclude.map(glob => `:(exclude)${glob}`)]\n : [];\n const diff = await gitShow(workspace, sha, pathspecArgs);\n const maxBytes = feedConfig.max_diff_bytes;\n if (maxBytes === null || Buffer.byteLength(diff, 'utf-8') <= maxBytes) return diff;\n\n const stat = await gitShow(workspace, sha, ['--stat', ...pathspecArgs]);\n return [\n stat.trimEnd(),\n '',\n `[tiered_execution.feed.max_diff_bytes truncated commit diff: ${Buffer.byteLength(diff, 'utf-8')} bytes exceeded ${maxBytes} bytes]`,\n '',\n ].join('\\n');\n}\n\nexport function resolveFeedApiUrl(env: NodeJS.ProcessEnv = process.env): string {\n return env.OVERDECK_DASHBOARD_URL\n ?? env.DASHBOARD_URL\n ?? `http://localhost:${env.API_PORT ?? env.PORT ?? '3011'}`;\n}\n\n/**\n * Compose the ingestion-only feed message for one commit. Deterministic over\n * (sha, beadTitle, diff) so replay reconstructs byte-identical messages.\n */\nexport function composeCommitFeedMessage(\n sha: string,\n beadTitle: string,\n diff: string,\n callout?: CommitFeedCalloutContext,\n): string {\n const lines = [\n `# Commit feed (ingestion-only): ${sha}`,\n '',\n `Bead: ${beadTitle}`,\n '',\n ];\n\n if (callout) {\n const body = JSON.stringify({\n issueId: callout.issueId,\n sha,\n beadId: callout.beadId,\n tierName: callout.tierName,\n agentId: callout.agentId,\n claim: '<one sentence: what is wrong and where>',\n });\n lines.push(\n 'This is an ingestion-only feed delivery. Read the diff below to stay',\n 'current with work landing on this issue. By default: do NOT respond, do',\n 'NOT take any action, do NOT produce output — wait for your next dispatch.',\n '',\n 'ONE exception. If you believe this commit is WRONG — it misreads the bead\\'s',\n 'spec, hollows out a test, or breaks a stated invariant — you may raise at',\n 'most one call-out, then return to silence:',\n '',\n ` curl -X POST ${callout.apiUrl}/api/tiered/callouts \\\\`,\n ' -H \"Content-Type: application/json\" \\\\',\n ` -d '${body}'`,\n '',\n 'Do not fix it yourself. Do not edit files. A call-out is a flag, not a task.',\n );\n } else {\n lines.push(\n 'This is an ingestion-only feed delivery. Read the diff below to stay',\n 'current with work landing on this issue. Do NOT respond to this message,',\n 'do NOT take any action, and do NOT produce output — wait for your next',\n 'dispatch.',\n );\n }\n\n lines.push('', '```diff', diff.trimEnd(), '```');\n return lines.join('\\n');\n}\n\n/**\n * Broadcast one landed commit to every standing tier agent. Sends exactly one\n * delivery per tier (everyone hears everything). A failed delivery to one\n * tier does not block the others; failures surface in the returned results.\n */\nexport async function broadcastCommit(options: BroadcastCommitOptions): Promise<BroadcastDelivery[]> {\n const feedConfig = options.feedConfig ?? DEFAULT_FEED_CONFIG;\n const subject = options.commitSubject ?? options.beadTitle;\n if (shouldSkipFeedSubject(subject, feedConfig)) return [];\n\n const renderDiff = options.renderDiff\n ?? (options.gitShow\n ? (workspace: string, sha: string) => options.gitShow!(workspace, sha)\n : renderCommitFeedDiff);\n const deliver = options.deliver ?? deliverAgentMessage;\n const recordDelivery = options.recordDelivery ?? recordTierFeedDelivery;\n const now = options.now ?? (() => new Date());\n\n const diff = await renderDiff(options.workspace, options.sha, feedConfig);\n const baseMessage = composeCommitFeedMessage(options.sha, options.beadTitle, diff);\n const baseTokenCount = estimateFeedDeliveryTokens(baseMessage);\n const includeCallout = feedConfig.callouts !== 'off' && options.issueId !== undefined && options.beadId !== undefined;\n const apiUrl = options.apiUrl ?? resolveFeedApiUrl();\n\n const deliveries: BroadcastDelivery[] = [];\n for (const tier of options.tiers) {\n const message = includeCallout\n ? composeCommitFeedMessage(options.sha, options.beadTitle, diff, {\n apiUrl,\n issueId: options.issueId!,\n beadId: options.beadId!,\n tierName: tier.tierName,\n agentId: tier.agentId,\n })\n : baseMessage;\n const tokenCount = includeCallout ? estimateFeedDeliveryTokens(message) : baseTokenCount;\n let result: DeliveryResult;\n try {\n result = await deliver(tier.agentId, message, 'tier-feed:broadcastCommit');\n } catch (err) {\n result = {\n ok: false,\n path: 'tmux',\n failure: err instanceof Error ? err.message : String(err),\n };\n }\n await recordDelivery({\n ts: now().toISOString(),\n issueId: options.issueId,\n sha: options.sha,\n beadTitle: options.beadTitle,\n tierName: tier.tierName,\n agentId: tier.agentId,\n tokenCount,\n result,\n });\n deliveries.push({ tierName: tier.tierName, agentId: tier.agentId, result });\n }\n return deliveries;\n}\n","/**\n * Standing supervisor agent for tiered execution (PAN-1791).\n *\n * The supervisor is a long-lived, review-only agent spawned once per issue\n * from the tier table's `supervisor` block. It subscribes to the commit feed\n * according to the configured subscription policy and reviews work at commit\n * boundaries — it NEVER implements. This module owns spawn + subscription\n * only; verdict posting to the inspect-status surface is the next bead\n * (supervisor-verdict-surface).\n *\n * Verdict-surface slice (supervisor-verdict-surface, FR-4):\n *\n * This module carries the supervisor-verdict-surface slice (FR-4): for each\n * subscribed commit, compose a review request containing the commit diff plus\n * the bead's acceptance criteria (and the PRD's traced FR text when the item\n * carries metadata.traces), deliver it to the standing supervisor via the\n * existing deliverAgentMessage primitive (FR-7, no new transport), and\n * instruct the supervisor to land its ack or blocking finding on the EXISTING\n * inspect-status surface (POST /api/specialists/done with specialist\n * \"inspect\") so the foreman's wait-for-verdict path is reused unchanged:\n * a pass persists inspectStatus + saves the checkpoint via onInspectComplete,\n * a failure records a blocking finding and never changes tracker status.\n *\n * Sibling slice note: supervisor spawn + subscription policy\n * (shouldSupervise, item supervisor-subscribe) is owned by a separate bead\n * and lands alongside this module.\n */\n\nimport { execFile } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport type { VBriefDocument, VBriefEdge, VBriefItem, VBriefSubItem } from '../vbrief/types.js';\nimport type { AgentState } from './agent-state.js';\nimport { deliverAgentMessage } from './delivery.js';\nimport type { DeliveryResult } from './delivery.js';\nimport { runAgentId } from './spawn-prep.js';\nimport { spawnRun } from './spawn.js';\nimport type {\n TieredExecutionSubscription,\n TieredExecutionSupervisorConfig,\n} from './tier-table.js';\n\n/**\n * Sub-role under 'review' for the standing supervisor. Review sub-roles skip\n * the roles/review.md convoy definition injection (roleAgentDefinitionPath\n * returns null for review+subRole), so the supervisor's behavior comes\n * entirely from the prompt delivered here.\n */\nexport const SUPERVISOR_SUB_ROLE = 'supervisor';\n\n/** Default fraction of beads reviewed under the 'sampled' subscription policy. */\nexport const DEFAULT_SUPERVISOR_SAMPLE_RATE = 0.25;\n\nexport function supervisorAgentId(issueId: string): string {\n return runAgentId(issueId, 'review', SUPERVISOR_SUB_ROLE);\n}\n\nexport interface ShouldSuperviseOptions {\n /**\n * Sampling rate in (0, 1] applied under the 'sampled' policy.\n * Defaults to DEFAULT_SUPERVISOR_SAMPLE_RATE.\n */\n sampleRate?: number;\n}\n\n/**\n * Subscription policy: does the supervisor review commits for this bead?\n *\n * - 'all' → every bead.\n * - 'flagged' → only beads with metadata.requiresInspection === true.\n * - 'sampled' → a deterministic per-bead sample at the configured rate.\n */\nexport function shouldSupervise(\n bead: Pick<VBriefItem, 'id' | 'metadata'>,\n policy: TieredExecutionSubscription,\n options: ShouldSuperviseOptions = {},\n): boolean {\n switch (policy) {\n case 'all':\n return true;\n case 'flagged':\n return bead.metadata?.requiresInspection === true;\n case 'sampled': {\n const rate = options.sampleRate ?? DEFAULT_SUPERVISOR_SAMPLE_RATE;\n if (rate <= 0) return false;\n if (rate >= 1) return true;\n return sampleFraction(bead.id) < rate;\n }\n }\n}\n\n/**\n * Deterministic per-bead sample position: FNV-1a over the bead id mapped to\n * [0, 1). Deterministic (never Math.random) so a bead's subscription decision\n * is stable across the live commit feed, replay re-delivery (tier-replay),\n * and tests.\n */\nfunction sampleFraction(beadId: string): number {\n let hash = 0x811c9dc5;\n for (let i = 0; i < beadId.length; i++) {\n hash ^= beadId.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0) / 0x1_0000_0000;\n}\n\nexport interface SpawnTierSupervisorOptions {\n /** Workspace to run in. Defaults to spawnRun's per-issue default workspace. */\n workspace?: string;\n /** Override the generated standing-supervisor kickoff prompt. */\n prompt?: string;\n}\n\n/**\n * Spawn the standing supervisor as a registered role run (agent state +\n * conversation record + tmux session on the overdeck socket), so the\n * dashboard discovers it under its issue like any other specialist.\n *\n * Model comes from the tier table's supervisor block. The configured harness\n * is passed through for the spawn record, but per PAN-1984 harness resolution\n * is provider-default-only — the tier-table validator already guarantees the\n * configured model+harness+auth combination is policy-legal.\n */\nexport async function spawnTierSupervisor(\n issueId: string,\n supervisor: TieredExecutionSupervisorConfig,\n options: SpawnTierSupervisorOptions = {},\n): Promise<AgentState> {\n return spawnRun(issueId, 'review', {\n agentId: supervisorAgentId(issueId),\n subRole: SUPERVISOR_SUB_ROLE,\n model: supervisor.model,\n harness: supervisor.harness,\n workspace: options.workspace,\n prompt: options.prompt ?? buildSupervisorPrompt(issueId, supervisor.subscribe),\n });\n}\n\nexport function buildSupervisorPrompt(\n issueId: string,\n subscribe: TieredExecutionSubscription,\n): string {\n const policyLine = {\n all: 'You are subscribed to EVERY commit in this issue.',\n flagged: 'You are subscribed only to commits for beads flagged requiresInspection=true.',\n sampled: 'You are subscribed to a deterministic sample of the beads in this issue.',\n }[subscribe];\n\n return [\n `# Standing Supervisor: ${issueId}`,\n '',\n 'You are the standing supervisor for tiered execution on this issue. You are a',\n 'review-only agent: you NEVER write, edit, or commit implementation code.',\n '',\n `Subscription policy: ${subscribe}. ${policyLine}`,\n '',\n 'Commit diffs will be delivered to this session as the tier agents land work.',\n 'For each delivered commit, read the diff against the bead\\'s acceptance',\n 'criteria and note whether the work satisfies them. Ingest each delivery and',\n 'wait for the next; do not take action beyond review. The verdict-posting',\n 'surface is wired separately — until it is, record findings in your replies',\n 'to each delivery.',\n '',\n 'Stay resident. Do not exit after a review; the next commit delivery arrives',\n 'in this same session.',\n ].join('\\n');\n}\n\nconst execFileAsync = promisify(execFile);\n\n/** One commit-review event as delivered to the standing supervisor. */\nexport interface SupervisorReviewEvent {\n issueId: string;\n /** Beads-tracker id the verdict must reference (\"Bead <beadId> ...\"). */\n beadId: string;\n beadTitle: string;\n /** Full commit sha being reviewed. */\n sha: string;\n /** Output of `git show <sha>` for the commit. */\n diff: string;\n /** The bead's acceptance-criterion titles, in plan order. */\n acceptanceCriteria: string[];\n /** Traced PRD FR text, present when the item carries metadata.traces. */\n frText?: string;\n /** Dashboard base URL the verdict POST targets. */\n apiUrl: string;\n}\n\nexport interface SupervisorVerdict {\n /** Bead id parsed from the inspect-status notes prefix. */\n beadId: string;\n /** Supervisor ack clears prior blocking findings; failed/blocked records one. */\n status: 'passed' | 'ack' | 'failed' | 'blocked';\n}\n\nexport interface DeliverCommitForReviewOptions {\n /** Agent id of the standing supervisor session. */\n supervisorAgentId: string;\n /** Workspace the commit lives in (git worktree on the feature branch). */\n workspacePath: string;\n issueId: string;\n /** The vBRIEF item the commit implements. */\n item: VBriefItem;\n sha: string;\n /** Beads id for the verdict prefix; defaults to the vBRIEF item id. */\n beadId?: string;\n /**\n * PRD draft markdown to source traced FR text from. When omitted and the\n * item has metadata.traces, callers should pass the result of\n * loadPrdDraft(projectRoot, issueId).\n */\n prdMarkdown?: string;\n apiUrl?: string;\n /** Test seams — default to the real primitives. */\n deps?: {\n deliver?: typeof deliverAgentMessage;\n getDiff?: (workspacePath: string, sha: string) => Promise<string>;\n };\n}\n\nfunction resolveApiUrl(): string {\n return (\n process.env.OVERDECK_DASHBOARD_URL\n || process.env.DASHBOARD_URL\n || `http://localhost:${process.env.API_PORT || process.env.PORT || '3011'}`\n );\n}\n\n/**\n * Does the next bead depend on a bead with an unresolved supervisor block?\n *\n * Verdicts are interpreted chronologically: the latest verdict for each bead is\n * authoritative. A failed/blocked verdict halts dependents until a later\n * passed/ack verdict for that same bead records the fix commit's approval.\n */\nexport function shouldHaltDispatch(\n verdicts: readonly SupervisorVerdict[],\n nextBead: Pick<VBriefItem, 'id'>,\n dag: Pick<VBriefDocument, 'plan'>,\n): boolean {\n const latestByBead = new Map<string, SupervisorVerdict>();\n for (const verdict of verdicts) {\n latestByBead.set(verdict.beadId, verdict);\n }\n\n const unresolvedBlockedBeads = new Set<string>();\n for (const [beadId, verdict] of latestByBead) {\n if (verdict.status === 'failed' || verdict.status === 'blocked') {\n unresolvedBlockedBeads.add(beadId);\n }\n }\n\n if (unresolvedBlockedBeads.size === 0) return false;\n\n for (const dependencyId of dependencyClosure(nextBead.id, dag.plan.edges ?? [])) {\n if (unresolvedBlockedBeads.has(dependencyId)) return true;\n }\n\n return false;\n}\n\nfunction dependencyClosure(itemId: string, edges: readonly VBriefEdge[]): Set<string> {\n const incoming = new Map<string, string[]>();\n for (const edge of edges) {\n if (edge.type !== 'blocks') continue;\n const parents = incoming.get(edge.to) ?? [];\n parents.push(edge.from);\n incoming.set(edge.to, parents);\n }\n\n const dependencies = new Set<string>();\n const stack = [...(incoming.get(itemId) ?? [])];\n while (stack.length > 0) {\n const dependencyId = stack.pop()!;\n if (dependencies.has(dependencyId)) continue;\n dependencies.add(dependencyId);\n stack.push(...(incoming.get(dependencyId) ?? []));\n }\n\n return dependencies;\n}\n\nfunction childItems(item: VBriefItem): VBriefSubItem[] {\n // vBRIEF v0.6 uses `items`; v0.5 documents used `subItems` for the same\n // structure and are still read as a compatibility alias.\n return item.items ?? item.subItems ?? [];\n}\n\n/**\n * Pull the bead's acceptance-criterion titles from its vBRIEF child items\n * (child metadata.kind === 'acceptance_criterion').\n */\nexport function extractAcceptanceCriteria(item: VBriefItem): string[] {\n return childItems(item)\n .filter((child) => child.metadata?.kind === 'acceptance_criterion')\n .map((child) => child.title);\n}\n\n/**\n * Extract the PRD requirement text for the given trace ids (e.g. [\"FR-4\"]).\n *\n * PRD requirement bullets follow the prd-authoring convention:\n * - **FR-4 — Title.** Body text...\n * Each matched requirement is captured from its bullet line through any\n * indented continuation lines, up to the next bullet or heading. Returns\n * undefined when no trace resolves to a requirement in the document.\n */\nexport function extractTracedFrText(prdMarkdown: string, traces: readonly string[]): string | undefined {\n if (traces.length === 0) return undefined;\n const lines = prdMarkdown.split('\\n');\n const sections: string[] = [];\n\n for (const trace of traces) {\n const startPattern = new RegExp(`^\\\\s*[-*]\\\\s+\\\\*\\\\*${trace}\\\\b`);\n const start = lines.findIndex((line) => startPattern.test(line));\n if (start === -1) continue;\n\n const captured = [lines[start]];\n for (let i = start + 1; i < lines.length; i++) {\n const line = lines[i];\n // A new bullet, a heading, or a blank line ends the requirement body.\n if (/^\\s*[-*]\\s/.test(line) || /^#/.test(line) || line.trim() === '') break;\n captured.push(line);\n }\n sections.push(captured.join('\\n'));\n }\n\n return sections.length > 0 ? sections.join('\\n\\n') : undefined;\n}\n\n/**\n * Read the Overdeck-managed PRD draft for an issue\n * (<projectRoot>/.pan/drafts/<ISSUE>.md, falling back to the lowercase\n * filename). Returns undefined when no draft exists.\n */\nexport async function loadPrdDraft(projectRoot: string, issueId: string): Promise<string | undefined> {\n for (const name of [`${issueId.toUpperCase()}.md`, `${issueId.toLowerCase()}.md`]) {\n try {\n return await readFile(join(projectRoot, '.pan', 'drafts', name), 'utf-8');\n } catch {\n // Try the next casing; missing drafts are expected.\n }\n }\n return undefined;\n}\n\n/**\n * Compose the per-commit review request delivered to the standing supervisor.\n *\n * Unlike commit-feed messages this is NOT ingestion-only: the supervisor must\n * respond by posting exactly one verdict to the existing inspect surface.\n */\nexport function buildSupervisorReviewMessage(event: SupervisorReviewEvent): string {\n const shortSha = event.sha.slice(0, 8);\n const criteria = event.acceptanceCriteria.length > 0\n ? event.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join('\\n')\n : '(none recorded on the plan item — review against the bead title)';\n\n const frSection = event.frText\n ? `\\n## Traced requirements (PRD)\\n\\n${event.frText}\\n`\n : '';\n\n return `SUPERVISOR REVIEW REQUEST — commit ${shortSha} on ${event.issueId}\n\nYou are the standing supervisor for ${event.issueId}. Review this commit against the bead's acceptance criteria and respond with a verdict. You never implement — do not edit files, commit, or run write operations.\n\n## Bead under review\n\n- Issue: ${event.issueId}\n- Bead: ${event.beadId} — ${event.beadTitle}\n- Commit: ${event.sha}\n\n## Acceptance criteria\n\n${criteria}\n${frSection}\n## Commit diff (git show ${shortSha})\n\n\\`\\`\\`diff\n${event.diff}\n\\`\\`\\`\n\n## Verdict (REQUIRED — post exactly one)\n\nLand your verdict on the existing inspect-status surface. Your notes MUST begin with \"Bead ${event.beadId}\" — the server extracts the bead id from that prefix.\n\nIf the diff satisfies the acceptance criteria, post an ack (this persists inspectStatus and saves the bead checkpoint):\n\n\\`\\`\\`bash\ncurl -X POST ${event.apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"inspect\",\"issueId\":\"${event.issueId}\",\"status\":\"passed\",\"notes\":\"Bead ${event.beadId} ack: <one-line summary>\"}'\n\\`\\`\\`\n\nIf any acceptance criterion is not met, post a blocking finding with specific, actionable violations ([file:line] + what is wrong):\n\n\\`\\`\\`bash\ncurl -X POST ${event.apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"inspect\",\"issueId\":\"${event.issueId}\",\"status\":\"failed\",\"notes\":\"Bead ${event.beadId} BLOCKED: <specific violations and required fixes>\"}'\n\\`\\`\\`\n\nRules:\n- A blocking finding is recorded on the inspect-status surface only — it does NOT change tracker (Linear/GitHub) status. Never run \\`gh issue close\\` or edit tracker state.\n- Do not use any other endpoint or side channel for the verdict.\n- Only review this commit against this bead's acceptance criteria — compile, lint, and tests are the verification gate's job, and code style is the review convoy's job.`;\n}\n\nasync function getCommitDiff(workspacePath: string, sha: string): Promise<string> {\n const { stdout } = await execFileAsync('git', ['show', sha], {\n cwd: workspacePath,\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n return stdout;\n}\n\n/**\n * Deliver one subscribed commit to the standing supervisor for review:\n * resolve the diff, pull the bead's acceptance criteria from its vBRIEF item\n * (plus traced FR text when metadata.traces is present and a PRD draft was\n * provided), and send the composed review request via deliverAgentMessage.\n */\nexport async function deliverCommitForReview(options: DeliverCommitForReviewOptions): Promise<DeliveryResult> {\n const deliver = options.deps?.deliver ?? deliverAgentMessage;\n const getDiff = options.deps?.getDiff ?? getCommitDiff;\n\n const diff = await getDiff(options.workspacePath, options.sha);\n const traces = Array.isArray(options.item.metadata?.traces)\n ? (options.item.metadata.traces as string[])\n : [];\n const frText = options.prdMarkdown && traces.length > 0\n ? extractTracedFrText(options.prdMarkdown, traces)\n : undefined;\n\n const message = buildSupervisorReviewMessage({\n issueId: options.issueId,\n beadId: options.beadId ?? options.item.id,\n beadTitle: options.item.title,\n sha: options.sha,\n diff,\n acceptanceCriteria: extractAcceptanceCriteria(options.item),\n frText,\n apiUrl: options.apiUrl ?? resolveApiUrl(),\n });\n\n return deliver(options.supervisorAgentId, message, 'tier-supervisor:verdict');\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ModelId } from '../settings.js';\nimport type { VBriefDocument, VBriefItem } from '../vbrief/types.js';\nimport type { TierOverridesMap } from '../vbrief/io.js';\nimport { getModelCapabilitySync, hasModelCapabilitySync, resolveModelIdSync } from '../model-capabilities.js';\nimport type { AgentState } from './agent-state.js';\nimport type { DeliveryResult } from './delivery.js';\nimport { deliverAgentMessage } from './delivery.js';\nimport { spawnRun } from './spawn.js';\nimport type { SpawnRunOptions } from './spawn-prep.js';\nimport { listSlotOwnership } from './slot-reconcile.js';\nimport type { InFlightBead } from './standing-tiers.js';\nimport { computeTierRunSchedule, tiersNeededForSchedule } from './standing-tiers.js';\nimport { applyEffectiveDifficulty } from './tier-escalation.js';\nimport {\n composeCommitFeedMessage,\n renderCommitFeedDiff,\n resolveFeedApiUrl,\n shouldSkipFeedSubject,\n} from './tier-feed.js';\nimport {\n buildSupervisorReviewMessage,\n extractAcceptanceCriteria,\n extractTracedFrText,\n shouldSupervise,\n supervisorAgentId as defaultSupervisorAgentId,\n SUPERVISOR_SUB_ROLE,\n} from './tier-supervisor.js';\nimport type { TieredExecutionSupervisorConfig, ValidatedTieredExecutionConfig, ValidatedTieredExecutionFeedConfig } from './tier-table.js';\nimport { DEFAULT_TIERED_EXECUTION_CONFIG } from './tier-table.js';\nimport { stopAgent } from './termination.js';\n\nconst execFileAsync = promisify(execFile);\nconst DEFAULT_REPLAY_THRESHOLD = 0.5;\n\nexport interface ReplayCommit {\n sha: string;\n subject: string;\n diff: string;\n beadId?: string;\n}\n\nexport interface ReplayDelivery {\n sha: string;\n result: DeliveryResult;\n}\n\nexport interface ReplayResult {\n agent: AgentState;\n commits: ReplayCommit[];\n deliveries: ReplayDelivery[];\n}\n\nexport interface ReplayRerouteContext {\n doc: VBriefDocument;\n config: ValidatedTieredExecutionConfig;\n statusOverrides?: Record<string, string>;\n tierOverrides?: TierOverridesMap;\n}\n\nexport interface ReplayTargetBase {\n issueId: string;\n workspace: string;\n /**\n * Base revision for replay. The feed is reconstructed from\n * `git log --reverse <base>..HEAD`.\n */\n base: string;\n /** Existing dead/stale agent id, if known. */\n agentId?: string;\n /** Optional prompt for the fresh standing session. */\n prompt?: string;\n /** Feed filtering/rendering config. Defaults preserve today's raw git-show behavior. */\n feedConfig?: ValidatedTieredExecutionFeedConfig;\n /** Dashboard API base URL used when replaying callout-enabled tier feed messages. */\n apiUrl?: string;\n /** Optional current scheduling state used only when compaction_reroute is on. */\n reroute?: ReplayRerouteContext;\n}\n\nexport interface ReplayStandingTierTarget extends ReplayTargetBase {\n kind: 'tier';\n tierName: string;\n /** Registered standing slot index. Resolved from slot-reconcile when omitted. */\n slotIndex?: number;\n /** First item assigned to the standing slot. Resolved from slot-reconcile when omitted. */\n slotItemId?: string;\n}\n\nexport interface ReplaySupervisorTarget extends ReplayTargetBase {\n kind: 'supervisor';\n supervisor: TieredExecutionSupervisorConfig;\n /** Plan used to apply the supervisor subscription policy to replay commits. */\n doc: VBriefDocument;\n /** PRD markdown used to replay traced requirement text, when available. */\n prdMarkdown?: string;\n apiUrl?: string;\n}\n\nexport type ReplayTarget = ReplayStandingTierTarget | ReplaySupervisorTarget;\n\nexport interface TierReplayDeps {\n spawn?: typeof spawnRun;\n deliver?: typeof deliverAgentMessage;\n stop?: typeof stopAgent;\n gitLog?: (workspace: string, base: string) => Promise<Array<{ sha: string; subject: string }>>;\n gitShow?: (workspace: string, sha: string) => Promise<string>;\n renderDiff?: (workspace: string, sha: string, feedConfig: ValidatedTieredExecutionFeedConfig) => Promise<string>;\n listSlotOwnership?: typeof listSlotOwnership;\n}\n\nexport interface TierReplayOptions {\n deps?: TierReplayDeps;\n}\n\nexport interface TierRunCompactionInput {\n /** True only between tier runs. Compaction is forbidden inside a bead. */\n atRunBoundary: boolean;\n /** Any in-flight bead blocks compaction, even if the caller says it is at a boundary. */\n inFlightBead?: InFlightBead;\n estimatedContextTokens: number;\n modelContextWindow: number;\n replayThreshold?: number;\n}\n\nexport interface TierRunCompactionOptions {\n target: ReplayTarget;\n compaction: TierRunCompactionInput;\n deps?: TierReplayDeps;\n}\n\n/**\n * Replay a dead standing tier agent or standing supervisor:\n * respawn the registered session and re-deliver its commit feed reconstructed\n * from `git log <base>..HEAD`, oldest commit first.\n */\nexport async function replayStandingAgent(\n target: ReplayTarget,\n options: TierReplayOptions = {},\n): Promise<ReplayResult> {\n const deps = replayDeps(options.deps);\n const replayTarget: ReplayTarget = target.kind === 'tier'\n ? { ...target, ...resolveReplaySlot(target, deps) }\n : target;\n const agent = await spawnReplayTarget(replayTarget, deps);\n const commits = await loadReplayCommits(\n replayTarget.workspace,\n replayTarget.base,\n replayTarget.feedConfig ?? DEFAULT_TIERED_EXECUTION_CONFIG.feed,\n deps,\n );\n const deliveries = replayTarget.kind === 'tier'\n ? await replayTierFeed(agent.id, replayTarget, commits, deps)\n : await replaySupervisorFeed(agent.id, replayTarget, commits, deps);\n\n return { agent, commits, deliveries };\n}\n\n/**\n * Crash/orphan entry point. The caller has already determined the old session\n * is dead; replay respawns and reconstructs the feed without human input.\n */\nexport async function replayCrashedStandingAgent(\n target: ReplayStandingTierTarget & { reroute: ReplayRerouteContext },\n options?: TierReplayOptions,\n): Promise<ReplayResult | null>;\nexport async function replayCrashedStandingAgent(\n target: ReplayTarget,\n options?: TierReplayOptions,\n): Promise<ReplayResult>;\nexport async function replayCrashedStandingAgent(\n target: ReplayTarget,\n options: TierReplayOptions = {},\n): Promise<ReplayResult | null> {\n const rerouted = resolveReroutedReplayTarget(target);\n if (!rerouted) return null;\n return replayStandingAgent(rerouted, options);\n}\n\n/**\n * Threshold-triggered compaction entry point. It only acts at a tier-run\n * boundary and only when no bead is in flight; mid-bead compaction returns\n * `null` by construction.\n */\nexport async function compactAtTierRunBoundary(\n options: TierRunCompactionOptions,\n): Promise<ReplayResult | null> {\n if (!shouldReplayCompactAtTierRunBoundary(options.compaction)) return null;\n const deps = replayDeps(options.deps);\n const rerouted = resolveReroutedReplayTarget(options.target);\n if (!rerouted) {\n if (options.target.agentId) await deps.stop(options.target.agentId);\n return null;\n }\n if (rerouted.agentId) {\n await deps.stop(rerouted.agentId);\n }\n return replayStandingAgent(rerouted, { deps });\n}\n\nexport function shouldReplayCompactAtTierRunBoundary(input: TierRunCompactionInput): boolean {\n if (!input.atRunBoundary) return false;\n if (input.inFlightBead) return false;\n if (!Number.isFinite(input.estimatedContextTokens) || input.estimatedContextTokens < 0) return false;\n if (!Number.isFinite(input.modelContextWindow) || input.modelContextWindow <= 0) return false;\n const threshold = input.replayThreshold ?? DEFAULT_REPLAY_THRESHOLD;\n if (!Number.isFinite(threshold) || threshold <= 0 || threshold > 1) return false;\n return input.estimatedContextTokens / input.modelContextWindow >= threshold;\n}\n\nexport function contextWindowForModel(model: string): number | undefined {\n const resolved = resolveModelIdSync(model);\n if (!hasModelCapabilitySync(resolved)) return undefined;\n return getModelCapabilitySync(resolved as ModelId).contextWindow;\n}\n\nasync function spawnReplayTarget(target: ReplayTarget, deps: Required<TierReplayDeps>): Promise<AgentState> {\n if (target.kind === 'supervisor') {\n return deps.spawn(target.issueId, 'review', {\n agentId: target.agentId ?? defaultSupervisorAgentId(target.issueId),\n subRole: SUPERVISOR_SUB_ROLE,\n model: target.supervisor.model,\n harness: target.supervisor.harness,\n workspace: target.workspace,\n prompt: target.prompt,\n });\n }\n\n const slot = resolveReplaySlot(target, deps);\n const spawnOptions: SpawnRunOptions = {\n slotIndex: slot.slotIndex,\n slotItemId: slot.slotItemId,\n prompt: target.prompt,\n ...currentTierSpawnOverride(target),\n };\n return deps.spawn(target.issueId, 'work', spawnOptions);\n}\n\nfunction resolveReroutedReplayTarget(target: ReplayTarget): ReplayTarget | null {\n if (target.kind !== 'tier') return target;\n const reroute = target.reroute;\n if (!reroute || reroute.config.compaction_reroute === 'off') return target;\n\n const scheduleDoc = remainingScheduleDocument(reroute);\n const schedule = computeTierRunSchedule(scheduleDoc, reroute.config);\n if (!tiersNeededForSchedule(schedule).includes(target.tierName)) return null;\n return target;\n}\n\nfunction remainingScheduleDocument(reroute: ReplayRerouteContext): VBriefDocument {\n const statusOverrides = reroute.statusOverrides ?? {};\n const tierOverrides = reroute.tierOverrides ?? {};\n return {\n ...reroute.doc,\n plan: {\n ...reroute.doc.plan,\n items: reroute.doc.plan.items\n .filter((item) => (statusOverrides[item.id] ?? item.status) !== 'completed')\n .map((item) => applyEffectiveDifficulty(item, tierOverrides)),\n },\n };\n}\n\nfunction currentTierSpawnOverride(target: ReplayStandingTierTarget): Pick<SpawnRunOptions, 'model' | 'harness'> {\n const reroute = target.reroute;\n if (!reroute || reroute.config.compaction_reroute === 'off') return {};\n const tier = reroute.config.tiers[target.tierName];\n if (!tier) return {};\n return { model: tier.model, harness: tier.harness };\n}\n\nfunction resolveReplaySlot(\n target: ReplayStandingTierTarget,\n deps: Required<TierReplayDeps>,\n): { slotIndex: number; slotItemId: string } {\n if (target.slotIndex !== undefined && target.slotItemId !== undefined) {\n return { slotIndex: target.slotIndex, slotItemId: target.slotItemId };\n }\n\n const ownership = deps.listSlotOwnership(target.issueId, target.workspace);\n const match = target.slotItemId\n ? ownership.find((assignment) => assignment.itemId === target.slotItemId)\n : ownership.find((assignment) => assignment.agentId === target.agentId);\n if (!match) {\n throw new Error(\n `Cannot replay standing tier '${target.tierName}' for ${target.issueId}: slot ownership was not found`,\n );\n }\n return { slotIndex: match.slotIndex, slotItemId: match.itemId };\n}\n\nasync function replayTierFeed(\n agentId: string,\n target: ReplayStandingTierTarget,\n commits: ReplayCommit[],\n deps: Required<TierReplayDeps>,\n): Promise<ReplayDelivery[]> {\n const deliveries: ReplayDelivery[] = [];\n const feedConfig = target.feedConfig ?? DEFAULT_TIERED_EXECUTION_CONFIG.feed;\n const includeCallout = feedConfig.callouts !== 'off';\n const apiUrl = target.apiUrl ?? resolveFeedApiUrl();\n for (const commit of commits) {\n const beadId = commit.beadId;\n const message = composeCommitFeedMessage(\n commit.sha,\n commit.subject,\n commit.diff,\n includeCallout && beadId\n ? {\n apiUrl,\n issueId: target.issueId,\n beadId,\n tierName: target.tierName,\n agentId,\n }\n : undefined,\n );\n deliveries.push({\n sha: commit.sha,\n result: await deps.deliver(agentId, message, 'tier-replay:tier'),\n });\n }\n return deliveries;\n}\n\nasync function replaySupervisorFeed(\n agentId: string,\n target: ReplaySupervisorTarget,\n commits: ReplayCommit[],\n deps: Required<TierReplayDeps>,\n): Promise<ReplayDelivery[]> {\n const deliveries: ReplayDelivery[] = [];\n for (const commit of commits) {\n const item = findReplayItem(target.doc, commit);\n if (!item || !shouldSupervise(item, target.supervisor.subscribe)) continue;\n const traces = Array.isArray(item.metadata?.traces) ? item.metadata.traces as string[] : [];\n const frText = target.prdMarkdown && traces.length > 0\n ? extractTracedFrText(target.prdMarkdown, traces)\n : undefined;\n const message = buildSupervisorReviewMessage({\n issueId: target.issueId,\n beadId: item.id,\n beadTitle: item.title,\n sha: commit.sha,\n diff: commit.diff,\n acceptanceCriteria: extractAcceptanceCriteria(item),\n frText,\n apiUrl: target.apiUrl ?? 'http://localhost:3011',\n });\n deliveries.push({\n sha: commit.sha,\n result: await deps.deliver(agentId, message, 'tier-replay:supervisor'),\n });\n }\n return deliveries;\n}\n\nfunction findReplayItem(doc: VBriefDocument, commit: Pick<ReplayCommit, 'subject'>): VBriefItem | undefined {\n return doc.plan.items.find((item) => {\n const subject = commit.subject.toLowerCase();\n return subject.includes(item.id.toLowerCase()) || subject.includes(item.title.toLowerCase());\n });\n}\n\nasync function loadReplayCommits(\n workspace: string,\n base: string,\n feedConfig: ValidatedTieredExecutionFeedConfig,\n deps: Required<TierReplayDeps>,\n): Promise<ReplayCommit[]> {\n const entries = await deps.gitLog(workspace, base);\n const commits: ReplayCommit[] = [];\n for (const entry of entries) {\n if (shouldSkipFeedSubject(entry.subject, feedConfig)) continue;\n commits.push({\n ...entry,\n diff: await deps.renderDiff(workspace, entry.sha, feedConfig),\n });\n }\n return commits;\n}\n\nasync function runGitLog(workspace: string, base: string): Promise<Array<{ sha: string; subject: string }>> {\n const { stdout } = await execFileAsync('git', ['log', '--reverse', '--format=%H%x00%s', `${base}..HEAD`], {\n cwd: workspace,\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n return stdout\n .split('\\n')\n .map((line) => {\n const [sha, subject = ''] = line.split('\\0');\n return { sha, subject };\n })\n .filter((entry) => entry.sha.trim().length > 0);\n}\n\nasync function runGitShow(workspace: string, sha: string): Promise<string> {\n const { stdout } = await execFileAsync('git', ['show', sha], {\n cwd: workspace,\n encoding: 'utf-8',\n maxBuffer: 16 * 1024 * 1024,\n });\n return stdout;\n}\n\nfunction replayDeps(deps: TierReplayDeps = {}): Required<TierReplayDeps> {\n return {\n spawn: deps.spawn ?? spawnRun,\n deliver: deps.deliver ?? deliverAgentMessage,\n stop: deps.stop ?? stopAgent,\n gitLog: deps.gitLog ?? runGitLog,\n gitShow: deps.gitShow ?? runGitShow,\n renderDiff: deps.renderDiff\n ?? (deps.gitShow\n ? (workspace, sha) => deps.gitShow!(workspace, sha)\n : renderCommitFeedDiff),\n listSlotOwnership: deps.listSlotOwnership ?? listSlotOwnership,\n };\n}\n","export {\n clearReadySignal,\n isQualifiedAgentId,\n normalizeAgentId,\n resolveAgentTargetSync,\n waitForAgentIdle,\n waitForReadySignal,\n} from './agents/identity.js';\nexport {\n buildDefaultResumeContinueMessage,\n decideChannelsForWorkAgent,\n decideSupervisorForWorkAgent,\n dismissDevChannelsDialog,\n writeChannelsBridgeMcpConfig,\n} from './agents/supervisor-channels.js';\nexport {\n buildCavemanExports,\n buildSpawnEnvForModel,\n determineModel,\n getProviderEnvForModel,\n getProviderExportsForModel,\n getProviderTmuxFlags,\n} from './agents/provider-env.js';\nexport {\n OHMYPI_AGENT_READY_TIMEOUT_SECONDS,\n describeOhmypiSpawnFailure,\n getAgentRuntimeBaseCommand,\n getProviderAuthMode,\n getRoleRuntimeBaseCommand,\n injectPiConversationMemory,\n roleAgentDefinitionPath,\n waitForPromptReady,\n} from './agents/runtime-command.js';\nexport {\n assertWorkspaceStackHealthyForSpawn,\n buildAgentLaunchConfig,\n retrieveSpawnTimeMemoryContext,\n transitionIssueToInProgress,\n transitionIssueToInReview,\n type AgentLaunchConfig,\n type SpawnOptions,\n type SpawnRunOptions,\n} from './agents/spawn-prep.js';\n\nexport { spawnAgent, spawnRun } from './agents/spawn.js';\n\nexport { listRunningAgentsSync, listAgentStates, listRunningAgents, warnOnBareNumericIssueIds, dropLegacyAgentStatesMissingRoleAsync } from './agents/queries.js';\nexport {\n GOVERNOR_SLOT_PAUSE_REASON_PREFIX,\n SESSION_EXITED_BEFORE_KICKOFF,\n __testInternals,\n clearAgentPaused,\n clearAgentPausedSync,\n clearAgentTroubled,\n clearAgentTroubledSync,\n getAgentDir,\n getAgentState,\n getAgentStateFilePath,\n getAgentStateSync,\n isAgentPaused,\n isAgentTroubled,\n isRole,\n markAgentRunningState,\n markAgentStoppedState,\n markAgentTroubled,\n recordAgentFailure,\n recordAgentFailureSync,\n resetAgentFailureCount,\n saveAgentState,\n saveAgentStateSync,\n setAgentPaused,\n setAgentPausedSync,\n setAgentYieldedSync,\n clearYieldForResumeSync,\n wipeAgentStateDirs,\n writeAgentStateJsonSync,\n type AgentState,\n type Role,\n} from './agents/agent-state.js';\nexport { stopAgentSync, stopAgent } from './agents/termination.js';\nexport { type ActivityEntry, appendActivity, getActivity, saveSessionId, getSessionId, getLatestSessionIdSync, getLatestSessionId } from './agents/activity.js';\nexport { type AgentResolution, type AgentRuntimeState, getAgentRuntimeStateSync, getAgentRuntimeState, saveAgentRuntimeState } from './agents/runtime-state.js';\nexport { deliverAgentMessage, deliverInitialPromptWithRetry, deliverResumeMessageWithTranscriptConfirmation, deliverAgentPermissionDecision, setAgentDeliveryMethod, type DeliveryResult } from './agents/delivery.js';\nexport { messageAgent } from './agents/messaging.js';\n\nexport { buildCompactRecoverySeed, resumeAgent } from './agents/resume.js';\n\nexport { autoRecoverAgents, detectCrashedAgents, recoverAgent, restartAgent, type RestartAgentOptions } from './agents/recovery.js';\nexport {\n compactAtTierRunBoundary,\n contextWindowForModel,\n replayCrashedStandingAgent,\n replayStandingAgent,\n shouldReplayCompactAtTierRunBoundary,\n type ReplayCommit,\n type ReplayDelivery,\n type ReplayResult,\n type ReplayStandingTierTarget,\n type ReplaySupervisorTarget,\n type ReplayTarget,\n type TierReplayDeps,\n type TierRunCompactionInput,\n type TierRunCompactionOptions,\n} from './agents/tier-replay.js';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"agents-CgVr77Qq.js","names":["join","existsSync","readFileSync","join","readFileSync","randomUUID","readFile","fetchAgentRuntimeSnapshot","mkdirAsync","writeFileAsync","loadYamlConfig","loadYamlConfig","NodeServicesLayer","platform","ChildProcessSpawner","join","existsSync","execAsync","openSync","fsConstants","exec","execAsync","exec","renameAsync","statAsync","execAsync","loadYamlConfig","parseYaml","promisify","loadYamlConfig","loadYamlConfig","loadConfigSync","join","existsSync","readFileSync","createReadStream","existsSync","stat","httpRequest","parse","execAsync","promisify","mkdirAsync","writeFileAsync","writeFileAsync","execAsync","escapeRegExp","promisify","join","existsSync","readdirSync","statSync","execAsync","exec","hasAgentRuntimeInSession","exec","execFileAsync","execFile","readFile","join","execFileAsync","execFile","execFile"],"sources":["../src/lib/overdeck/agent-rollback-state.ts","../src/lib/agent-runtime.ts","../src/lib/agent-runtime-mirror.ts","../src/lib/overdeck/conversation-cost-session.ts","../src/lib/overdeck/conversations.ts","../src/lib/agents/runtime-state.ts","../src/lib/agents/identity.ts","../src/lib/cloister/auto-resume-config.ts","../src/lib/overdeck/agent-record-sync.ts","../src/lib/persistent-logger.ts","../src/lib/agents/agent-state.ts","../src/lib/agents/supervisor-channels.ts","../src/lib/openai-auth.ts","../src/lib/provider-health.ts","../src/lib/agents/provider-env.ts","../src/lib/claude-auth.ts","../src/lib/runtimes/ohmypi-fifo.ts","../src/lib/runtimes/pi-fifo.ts","../src/lib/agents/runtime-command.ts","../src/lib/agents/resolve-tier.ts","../src/lib/agents/staffing.ts","../src/lib/agents/spawn-prep.ts","../src/lib/cv.ts","../src/lib/hooks.ts","../src/lib/bridge-token.ts","../src/lib/transcript-landing.ts","../src/lib/agents/delivery.ts","../src/lib/cloister/config.ts","../src/lib/cloister/memory-verdict-cache.ts","../src/lib/cloister/concurrency.ts","../src/lib/agents/queries.ts","../src/lib/agents/termination.ts","../src/lib/agents/spawn.ts","../src/lib/cost-parsers/ohmypi-parser.ts","../src/lib/runtimes/ohmypi.ts","../src/lib/agents/activity.ts","../src/lib/operator-interventions.ts","../src/lib/session-rotation.ts","../src/lib/agents/messaging.ts","../src/lib/context-overflow.ts","../src/lib/agents/resume.ts","../src/lib/graceful-restart.ts","../src/lib/agents/recovery.ts","../src/lib/vbrief/swarm-readiness.ts","../src/lib/agents/slot-reconcile.ts","../src/lib/agents/standing-tiers.ts","../src/lib/agents/tier-escalation.ts","../src/lib/agents/tier-feed.ts","../src/lib/agents/tier-supervisor.ts","../src/lib/agents/tier-replay.ts","../src/lib/agents.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nimport { getOverdeckHome } from '../paths.js';\nimport type { AgentState } from '../agents.js';\n\nconst AGENT_STATE_ROLLBACK_FILE = 'state' + '.json';\n\nexport function getRollbackAgentStatePath(agentId: string): string {\n return join(getOverdeckHome(), 'agents', agentId, AGENT_STATE_ROLLBACK_FILE);\n}\n\nexport function readRollbackAgentStateSync<T>(\n agentId: string,\n parse: (content: string, normalizedId: string) => T | null,\n): T | null {\n const stateFile = getRollbackAgentStatePath(agentId);\n if (!existsSync(stateFile)) return null;\n return parse(readFileSync(stateFile, 'utf8'), agentId);\n}\n\nexport function writeRollbackAgentStateSync(\n state: AgentState,\n serialize: (state: AgentState) => string,\n): void {\n const stateFile = getRollbackAgentStatePath(state.id);\n mkdirSync(join(getOverdeckHome(), 'agents', state.id), { recursive: true });\n writeFileSync(stateFile, serialize(state));\n}\n\nexport async function readRollbackAgentHarnessFromDir(\n agentsDir: string,\n agentId: string,\n): Promise<string | null> {\n try {\n const raw = await readFile(join(agentsDir, agentId, AGENT_STATE_ROLLBACK_FILE), 'utf8');\n const state = JSON.parse(raw) as { harness?: unknown };\n return typeof state.harness === 'string' ? state.harness : null;\n } catch {\n return null;\n }\n}\n","/**\n * PAN-800 — out-of-process client for the canonical AgentRuntimeSnapshot.\n *\n * Every caller that previously read/wrote ~/.overdeck/agents/<id>/runtime.json\n * now goes through this module. There is no file fallback: the dashboard's\n * AgentStateService SubscriptionRef is the single source of truth for runtime\n * snapshots. CLI and lib modules that need runtime state hit the HTTP API.\n * Server-reachable code inside the dashboard process yields AgentStateService\n * directly (zero-roundtrip) — this module is for everything else.\n */\n\nimport { Data, Effect } from 'effect'\nimport type {\n AgentRuntimeSnapshot,\n Activity,\n AgentResolution,\n ChannelReplyArtifactRef,\n ChannelReplyKind,\n WaitingReason,\n} from '@overdeck/contracts'\nimport { ensureInternalTokenSync, INTERNAL_TOKEN_HEADER } from './internal-token.js'\n\n// Use 127.0.0.1 explicitly: when /etc/hosts resolves `localhost` to ::1\n// (IPv6 first), Node's undici-based fetch() connects to [::1]:3011 and\n// fails because the dashboard listens on the IPv4 wildcard 0.0.0.0.\n// curl falls back to IPv4; Node's fetch in this version does not.\nconst DASHBOARD_URL = process.env['OVERDECK_DASHBOARD_URL'] || 'http://127.0.0.1:3011'\nconst DEFAULT_TIMEOUT_MS = 1500\n\nfunction abortSignal(ms: number): AbortSignal {\n const controller = new AbortController()\n setTimeout(() => controller.abort(), ms).unref?.()\n return controller.signal\n}\n\nclass AgentRuntimeFetchError extends Data.TaggedError('AgentRuntimeFetchError')<{\n readonly url: string\n readonly cause?: unknown\n}> {}\n\nexport const getAgentRuntimeSnapshot = (\n agentId: string,\n): Effect.Effect<AgentRuntimeSnapshot | null> => {\n if (!agentId) return Effect.succeed(null)\n const url = `${DASHBOARD_URL}/api/agents/${encodeURIComponent(agentId)}/runtime`\n return Effect.gen(function* () {\n const res = yield* Effect.tryPromise({\n try: () => fetch(url, { signal: abortSignal(DEFAULT_TIMEOUT_MS) }),\n catch: (cause) => new AgentRuntimeFetchError({ url, cause }),\n })\n if (!res.ok) return null\n const body = yield* Effect.tryPromise({\n try: () => res.json() as Promise<{ success: boolean; snapshot?: AgentRuntimeSnapshot }>,\n catch: (cause) => new AgentRuntimeFetchError({ url, cause }),\n })\n return body.success && body.snapshot ? body.snapshot : null\n }).pipe(Effect.orElseSucceed(() => null))\n}\n\ntype HeartbeatBody =\n | { kind: 'activity'; activity: Activity; tool?: string }\n | { kind: 'thinking_start'; lastToolAt: string }\n | { kind: 'thinking_stop'; resolvedBy: 'tool' | 'waiting' | 'idle' | 'stopped' }\n | { kind: 'waiting_start'; reason: WaitingReason; message?: string }\n | { kind: 'waiting_clear'; clearedBy: 'user_response' | 'timeout' | 'stopped' | 'tool_resumed' }\n | { kind: 'message_received'; direction: 'to_agent' | 'from_agent'; source: 'user' | 'cloister' | 'specialist' | 'automated' }\n | { kind: 'channel_reply'; reply: { kind: ChannelReplyKind; summary: string; artifactRefs?: ChannelReplyArtifactRef[] } }\n | { kind: 'model_set'; model: string; claudeSessionId?: string; sessionModel?: string; sessionHarness?: string }\n | { kind: 'resolution_set'; resolution: AgentResolution; resolutionCount: number }\n | { kind: 'current_issue_set'; currentIssue?: string }\n | { kind: 'context_saturation_changed'; contextSaturatedAt?: string }\n\n/**\n * Emit a runtime event. Returns true if the dashboard accepted it, false on\n * any failure (timeout, network error, non-2xx). Callers get fire-and-forget\n * semantics — no retry, no buffering. The bash hooks have their own\n * pending-events.jsonl fallback; in-process callers just log and move on.\n */\nexport const emitAgentEvent = (\n agentId: string,\n body: HeartbeatBody,\n): Effect.Effect<boolean> => {\n if (!agentId) return Effect.succeed(false)\n const url = `${DASHBOARD_URL}/api/agents/${encodeURIComponent(agentId)}/heartbeat`\n return Effect.gen(function* () {\n // The heartbeat route requires the internal token\n // (validateAgentRuntimeEventAuth). Without it every event POSTed from a\n // CLI process was silently 403'd — a codex slot agent's `pan done`\n // resolution never reached AgentStateService, so the swarm coordinator\n // classified finished slots as still running, forever. The bash hooks\n // attach this same header; the lib emitter must too.\n const token = yield* Effect.try({\n try: (): string => ensureInternalTokenSync(),\n catch: (cause) => new AgentRuntimeFetchError({ url, cause }),\n }).pipe(Effect.orElseSucceed((): string | null => null))\n if (!token) return false\n const res = yield* Effect.tryPromise({\n try: () =>\n fetch(url, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json', [INTERNAL_TOKEN_HEADER]: token },\n body: JSON.stringify({ ...body, timestamp: new Date().toISOString() }),\n signal: abortSignal(DEFAULT_TIMEOUT_MS),\n }),\n catch: (cause) => new AgentRuntimeFetchError({ url, cause }),\n })\n return res.ok\n }).pipe(Effect.orElseSucceed(() => false))\n}\n\n// ─── Convenience wrappers mirroring the event taxonomy ────────────────────────\n\nexport const emitActivity = (\n agentId: string,\n activity: Activity,\n tool?: string,\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'activity', activity, tool })\n\nexport const emitWaitingStart = (\n agentId: string,\n reason: WaitingReason,\n message?: string,\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'waiting_start', reason, message })\n\nexport const emitWaitingClear = (\n agentId: string,\n clearedBy: 'user_response' | 'timeout' | 'stopped' | 'tool_resumed' = 'user_response',\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'waiting_clear', clearedBy })\n\nexport const emitModelSet = (\n agentId: string,\n model: string,\n claudeSessionId?: string,\n sessionOrigin?: { sessionModel?: string; sessionHarness?: string },\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'model_set', model, claudeSessionId, ...sessionOrigin })\n\nexport const emitMessageReceived = (\n agentId: string,\n direction: 'to_agent' | 'from_agent',\n source: 'user' | 'cloister' | 'specialist' | 'automated',\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'message_received', direction, source })\n\nexport const emitChannelReply = (\n agentId: string,\n reply: { kind: ChannelReplyKind; summary: string; artifactRefs?: ChannelReplyArtifactRef[] },\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'channel_reply', reply })\n\nexport const emitResolution = (\n agentId: string,\n resolution: AgentResolution,\n resolutionCount: number,\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'resolution_set', resolution, resolutionCount })\n\nexport const emitContextSaturationChanged = (\n agentId: string,\n contextSaturatedAt?: string,\n): Effect.Effect<boolean> => emitAgentEvent(agentId, { kind: 'context_saturation_changed', contextSaturatedAt })\n","/**\n * PAN-800 — in-process mirror of AgentStateService's canonical ref.\n *\n * The dashboard server writes into this mirror on every event fold so lib code\n * running in the same process can read the latest AgentRuntimeSnapshot.\n * CLI/out-of-process callers should prefer the async HTTP path in agent-runtime.ts.\n *\n * All exports are Effect-native (PAN-1249 wave-1 migration).\n */\n\nimport { Effect } from 'effect';\nimport type { AgentRuntimeSnapshot } from '@overdeck/contracts';\n\nlet mirror: Record<string, AgentRuntimeSnapshot> = {};\n\n/**\n * Set to true by AgentStateServiceLive's layer construction. When true, the\n * async HTTP adapter in agent-runtime.ts short-circuits to the sync mirror\n * instead of making a localhost HTTP call — we're running IN the dashboard,\n * so fetching our own HTTP server would deadlock if it's still bootstrapping.\n */\nlet inProcessService = false;\n\nexport function markAgentStateServiceInProcess(): Effect.Effect<void, never> {\n return Effect.sync(() => {\n inProcessService = true;\n });\n}\n\nexport function isAgentStateServiceInProcess(): Effect.Effect<boolean, never> {\n return Effect.sync(() => inProcessService);\n}\n\nexport function setAgentRuntimeMirror(next: Record<string, AgentRuntimeSnapshot>): Effect.Effect<void, never> {\n return Effect.sync(() => {\n mirror = next;\n });\n}\n\nexport function getRuntimeSnapshot(agentId: string): Effect.Effect<AgentRuntimeSnapshot | null, never> {\n return Effect.sync(() => mirror[agentId] ?? null);\n}\n","import { getOverdeckDatabaseSync } from './infra.js';\n\nexport function findConversationForCostSessionSync(input: {\n sessionId?: string | null;\n agentId?: string | null;\n}): { name: string } | null {\n const db = getOverdeckDatabaseSync();\n if (input.sessionId) {\n const row = db.prepare(\n `SELECT c.name\n FROM conversation_files cf\n JOIN conversations c ON c.id = cf.conversation_id\n WHERE cf.locator = ?\n ORDER BY cf.created_at DESC, cf.id DESC\n LIMIT 1`,\n ).get(input.sessionId) as { name: string } | undefined;\n if (row) return { name: row.name };\n }\n if (!input.agentId?.startsWith('conv-')) return null;\n const row = db.prepare(\n `SELECT c.name\n FROM conversations c\n WHERE c.tmux_session = ?\n ORDER BY c.created_at DESC\n LIMIT 1`,\n ).get(input.agentId) as { name: string } | undefined;\n return row ? { name: row.name } : null;\n}\n","/**\n * Conversations domain — ConversationsResolver (read door) + ConversationWriter (write door)\n * + TranscriptsResolver (shared read door over JSONL index) + TranscriptsWriter (cache-maint).\n *\n * Critical invariant: the backing session files (claude/pi/codex JSONL) are SACRED.\n * ConversationWriter touches only the DB and creates NEW backing files via forkNewFile;\n * it NEVER mutates, truncates, appends-to, or deletes an existing one.\n * TranscriptsResolver has no write methods at all — mechanically enforced by the surface.\n *\n * Writer durability diverges from Issues: conversations have NO git mirror.\n * The DB row IS the source of truth (schema 90-96). ConversationWriter has no Records dep.\n */\n\nimport { readFile } from 'node:fs/promises';\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nimport { Context, Effect, Layer, Schema, Stream } from 'effect';\nimport { and, eq, isNotNull, isNull } from 'drizzle-orm';\nimport { integer, real, sqliteTable, text } from 'drizzle-orm/sqlite-core';\nimport { HttpApiEndpoint, HttpApiGroup } from 'effect/unstable/httpapi';\n\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getOverdeckHome } from '../paths.js';\nimport { Db, EventBus, getOverdeckDatabaseSync } from './infra.js';\nimport { getEventStore } from '../../dashboard/server/event-store.js';\nimport { ensureDiscoveredSessionsSchema } from './discovered-sessions.js';\n\n// ── Local Drizzle table definitions ──────────────────────────────────────────\n// Mirror locked schema (docs/overdeck-remodel/overdeck-schema.ts:97-163).\n// No FK or index annotations here — those live in the compiled migration only.\n\nconst conversationsTable = sqliteTable('conversations', {\n id: text('id').primaryKey(),\n name: text('name').notNull(),\n cwd: text('cwd').notNull(),\n issueId: text('issue_id'),\n harness: text('harness'),\n model: text('model'),\n effort: text('effort'),\n title: text('title'),\n titleSource: text('title_source'),\n createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),\n archivedAt: integer('archived_at', { mode: 'timestamp_ms' }),\n handoffDocPath: text('handoff_doc_path'),\n handoffTargetConvId: text('handoff_target_conv_id'),\n clearedToConvId: text('cleared_to_conv_id'),\n tmuxSession: text('tmux_session'),\n status: text('status').notNull().default('active'),\n endedAt: integer('ended_at', { mode: 'timestamp_ms' }),\n lastAttachedAt: integer('last_attached_at', { mode: 'timestamp_ms' }),\n sessionFile: text('session_file'),\n totalCost: real('total_cost').default(0),\n totalTokens: integer('total_tokens').default(0),\n forkStatus: text('fork_status'),\n forkError: text('fork_error'),\n forkRetryCount: integer('fork_retry_count').notNull().default(0),\n forkRequest: text('fork_request'),\n forkFallbackReason: text('fork_fallback_reason'),\n deliveryMethod: text('delivery_method'),\n spawnError: text('spawn_error'),\n});\n\nconst conversationFilesTable = sqliteTable('conversation_files', {\n id: integer('id').primaryKey({ autoIncrement: true }),\n conversationId: text('conversation_id').notNull(),\n harness: text('harness').notNull(),\n locator: text('locator').notNull(),\n createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),\n});\n\nconst favoritesTable = sqliteTable('favorites', {\n type: text('type').notNull(),\n itemId: text('item_id').notNull(),\n createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull(),\n});\n\nconst transcriptsTable = sqliteTable('transcripts', {\n backingFilePath: text('backing_file_path').primaryKey(),\n sessionId: text('session_id'),\n harness: text('harness'),\n workspacePath: text('workspace_path'),\n messageCount: integer('message_count'),\n models: text('models', { mode: 'json' }).$type<string[] | null>(),\n tokenInput: integer('token_input'),\n tokenOutput: integer('token_output'),\n firstTs: integer('first_ts', { mode: 'timestamp_ms' }),\n lastTs: integer('last_ts', { mode: 'timestamp_ms' }),\n panIssueId: text('pan_issue_id'),\n panAgentId: text('pan_agent_id'),\n});\n\n// ── Entity schemas ────────────────────────────────────────────────────────────\n\nexport const ConversationId = Schema.String.pipe(Schema.brand('ConversationId'));\nexport type ConversationId = typeof ConversationId.Type;\n\nexport const ConversationName = Schema.String.pipe(Schema.brand('ConversationName'));\nexport type ConversationName = typeof ConversationName.Type;\n\nexport const Harness = Schema.Literals(['claude-code', 'pi', 'codex', 'kimi']);\nexport type Harness = typeof Harness.Type;\n\nexport const TitleSource = Schema.Literals(['manual', 'auto', 'ai', 'default']);\nexport type TitleSource = typeof TitleSource.Type;\n\nexport const FavoriteType = Schema.Literals(['conversation', 'project']);\nexport type FavoriteType = typeof FavoriteType.Type;\n\nexport const BackingFile = Schema.Struct({\n harness: Harness,\n locator: Schema.String,\n createdAt: Schema.Date,\n});\nexport type BackingFile = typeof BackingFile.Type;\n\nexport const Conversation = Schema.Struct({\n id: ConversationId,\n name: ConversationName,\n cwd: Schema.String,\n issueId: Schema.NullOr(Schema.String),\n harness: Schema.NullOr(Harness),\n model: Schema.NullOr(Schema.String),\n effort: Schema.NullOr(Schema.String),\n title: Schema.NullOr(Schema.String),\n titleSource: Schema.NullOr(TitleSource),\n createdAt: Schema.Date,\n archivedAt: Schema.NullOr(Schema.Date),\n handoffDocPath: Schema.NullOr(Schema.String),\n handoffTargetConvId: Schema.NullOr(ConversationId),\n clearedToConvId: Schema.NullOr(ConversationId),\n files: Schema.Array(BackingFile),\n});\nexport type Conversation = typeof Conversation.Type;\n\nexport const ConversationFilter = Schema.Struct({\n archived: Schema.optional(Schema.Boolean),\n issueId: Schema.optional(Schema.String),\n});\nexport type ConversationFilter = typeof ConversationFilter.Type;\n\nexport const ParsedTranscript = Schema.Struct({\n messages: Schema.Array(Schema.Unknown),\n messageCount: Schema.Number,\n models: Schema.Array(Schema.String),\n firstTs: Schema.NullOr(Schema.Date),\n lastTs: Schema.NullOr(Schema.Date),\n});\nexport type ParsedTranscript = typeof ParsedTranscript.Type;\n\nexport const TranscriptSubject = Schema.Union([Conversation, ConversationName]);\nexport type TranscriptSubject = typeof TranscriptSubject.Type;\n\nexport const Transcript = Schema.Struct({\n backingFilePath: Schema.String,\n sessionId: Schema.NullOr(Schema.String),\n harness: Schema.NullOr(Harness),\n workspacePath: Schema.NullOr(Schema.String),\n messageCount: Schema.NullOr(Schema.Number),\n models: Schema.NullOr(Schema.Array(Schema.String)),\n tokenInput: Schema.NullOr(Schema.Number),\n tokenOutput: Schema.NullOr(Schema.Number),\n firstTs: Schema.NullOr(Schema.Date),\n lastTs: Schema.NullOr(Schema.Date),\n panIssueId: Schema.NullOr(Schema.String),\n panAgentId: Schema.NullOr(Schema.String),\n});\nexport type Transcript = typeof Transcript.Type;\n\n// ── Error types ───────────────────────────────────────────────────────────────\n\nexport class ConversationNotFound extends Schema.TaggedErrorClass<ConversationNotFound>()(\n 'ConversationNotFound', { name: ConversationName },\n) {}\n\nexport class AlreadyArchived extends Schema.TaggedErrorClass<AlreadyArchived>()(\n 'AlreadyArchived', { name: ConversationName },\n) {}\n\nexport class NotArchived extends Schema.TaggedErrorClass<NotArchived>()(\n 'NotArchived', { name: ConversationName },\n) {}\n\n// ── Internal sync decoders (known-shape DB rows) ──────────────────────────────\n\nconst decodeConversation = Schema.decodeUnknownSync(Conversation);\nconst decodeTranscript = Schema.decodeUnknownSync(Transcript);\nconst decodeBackingFile = Schema.decodeUnknownSync(BackingFile);\n\n// ── Row type aliases ───────────────────────────────────────────────────────────\n\ntype ConvRow = typeof conversationsTable.$inferSelect;\ntype FileRow = typeof conversationFilesTable.$inferSelect;\ntype TransRow = typeof transcriptsTable.$inferSelect;\n\n// ── Internal row mappers ───────────────────────────────────────────────────────\n\nfunction rowToBackingFile(r: FileRow): BackingFile {\n return decodeBackingFile({ harness: r.harness, locator: r.locator, createdAt: r.createdAt });\n}\n\nfunction rowToConversation(row: ConvRow, files: FileRow[]): Conversation {\n return decodeConversation({\n id: row.id,\n name: row.name,\n cwd: row.cwd,\n issueId: row.issueId ?? null,\n harness: row.harness ?? null,\n model: row.model ?? null,\n effort: row.effort ?? null,\n title: row.title ?? null,\n titleSource: row.titleSource ?? null,\n createdAt: row.createdAt,\n archivedAt: row.archivedAt ?? null,\n handoffDocPath: row.handoffDocPath ?? null,\n handoffTargetConvId: row.handoffTargetConvId ?? null,\n clearedToConvId: row.clearedToConvId ?? null,\n files: files.map(rowToBackingFile),\n });\n}\n\nfunction rowToTranscript(r: TransRow): Transcript {\n return decodeTranscript({\n backingFilePath: r.backingFilePath,\n sessionId: r.sessionId ?? null,\n harness: r.harness ?? null,\n workspacePath: r.workspacePath ?? null,\n messageCount: r.messageCount ?? null,\n models: r.models ?? null,\n tokenInput: r.tokenInput ?? null,\n tokenOutput: r.tokenOutput ?? null,\n firstTs: r.firstTs ?? null,\n lastTs: r.lastTs ?? null,\n panIssueId: r.panIssueId ?? null,\n panAgentId: r.panAgentId ?? null,\n });\n}\n\n// ── ConversationsResolver — read door ─────────────────────────────────────────\n\nexport class ConversationsResolver extends Context.Service<ConversationsResolver, {\n readonly get: (name: ConversationName) => Effect.Effect<Conversation, ConversationNotFound>;\n readonly list: (f: ConversationFilter) => Effect.Effect<ReadonlyArray<Conversation>>;\n readonly getCurrent: () => Effect.Effect<Conversation, ConversationNotFound>;\n readonly getHandoffDoc: (name: ConversationName) => Effect.Effect<string, ConversationNotFound>;\n}>()('overdeck/ConversationsResolver') {}\n\nexport const ConversationsResolverLive = Layer.effect(\n ConversationsResolver,\n Effect.gen(function* () {\n const db = yield* Db;\n\n const withFiles = (row: ConvRow): Effect.Effect<Conversation> =>\n Effect.gen(function* () {\n const files = yield* Effect.promise(() =>\n db.q.select().from(conversationFilesTable).where(eq(conversationFilesTable.conversationId, row.id)),\n );\n return rowToConversation(row, files);\n });\n\n const get = (name: ConversationName): Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.gen(function* () {\n const [row] = yield* Effect.promise(() =>\n db.q.select().from(conversationsTable).where(eq(conversationsTable.name, name)),\n );\n if (!row) return yield* Effect.fail(new ConversationNotFound({ name }));\n return yield* withFiles(row);\n });\n\n const list = (f: ConversationFilter): Effect.Effect<ReadonlyArray<Conversation>> =>\n Effect.gen(function* () {\n const rows = yield* Effect.promise(() => {\n const base = db.q.select().from(conversationsTable);\n const conds = [];\n if (f.archived === true) conds.push(isNotNull(conversationsTable.archivedAt));\n if (f.archived === false) conds.push(isNull(conversationsTable.archivedAt));\n if (f.issueId) conds.push(eq(conversationsTable.issueId, f.issueId));\n return conds.length > 0 ? base.where(and(...conds)) : base;\n });\n return yield* Effect.forEach(rows, withFiles);\n });\n\n const getCurrent = (): Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.fail(new ConversationNotFound({ name: '' as ConversationName }));\n\n const getHandoffDoc = (name: ConversationName): Effect.Effect<string, ConversationNotFound> =>\n Effect.gen(function* () {\n const conv = yield* get(name);\n if (!conv.handoffDocPath) return yield* Effect.fail(new ConversationNotFound({ name }));\n return yield* Effect.promise(() => readFile(conv.handoffDocPath!, 'utf-8'));\n });\n\n return ConversationsResolver.of({ get, list, getCurrent, getHandoffDoc });\n }),\n);\n\n// ── TranscriptsResolver — shared read door (JSONL index + sacred file reads) ──\n// Read-only: no method writes a backing file or the transcripts index.\n\nexport class TranscriptsResolver extends Context.Service<TranscriptsResolver, {\n readonly resolveFiles: (subject: TranscriptSubject) => Effect.Effect<ReadonlyArray<string>>;\n readonly parse: (subject: TranscriptSubject) => Effect.Effect<ParsedTranscript>;\n readonly serialize: (subject: TranscriptSubject) => Effect.Effect<string>;\n readonly watch: (subject: TranscriptSubject) => Stream.Stream<ParsedTranscript>;\n readonly get: (key: string) => Effect.Effect<Transcript>;\n readonly list: (f: ConversationFilter) => Effect.Effect<ReadonlyArray<Transcript>>;\n readonly stats: () => Effect.Effect<{ count: number; managed: number }>;\n readonly search: (query: string) => Effect.Effect<ReadonlyArray<Transcript>>;\n}>()('overdeck/TranscriptsResolver') {}\n\nexport const TranscriptsResolverLive = Layer.effect(\n TranscriptsResolver,\n Effect.gen(function* () {\n const db = yield* Db;\n\n const resolveFiles = (_subject: TranscriptSubject): Effect.Effect<ReadonlyArray<string>> =>\n Effect.succeed([]);\n\n const parse = (_subject: TranscriptSubject): Effect.Effect<ParsedTranscript> =>\n Effect.succeed({ messages: [], messageCount: 0, models: [], firstTs: null, lastTs: null });\n\n const serialize = (_subject: TranscriptSubject): Effect.Effect<string> =>\n Effect.succeed('');\n\n const watch = (_subject: TranscriptSubject): Stream.Stream<ParsedTranscript> =>\n Stream.empty;\n\n const get = (key: string): Effect.Effect<Transcript> =>\n Effect.gen(function* () {\n const [row] = yield* Effect.promise(() =>\n db.q.select().from(transcriptsTable).where(eq(transcriptsTable.backingFilePath, key)),\n );\n if (!row) return yield* Effect.die(`Transcript not found: ${key}`);\n return rowToTranscript(row);\n });\n\n const list = (_f: ConversationFilter): Effect.Effect<ReadonlyArray<Transcript>> =>\n Effect.gen(function* () {\n const rows = yield* Effect.promise(() => db.q.select().from(transcriptsTable));\n return rows.map(rowToTranscript);\n });\n\n const stats = (): Effect.Effect<{ count: number; managed: number }> =>\n Effect.gen(function* () {\n const rows = yield* Effect.promise(() => db.q.select().from(transcriptsTable));\n return {\n count: rows.length,\n managed: rows.filter(r => r.panAgentId != null).length,\n };\n });\n\n const search = (_query: string): Effect.Effect<ReadonlyArray<Transcript>> =>\n Effect.succeed([]);\n\n return TranscriptsResolver.of({ resolveFiles, parse, serialize, watch, get, list, stats, search });\n }),\n);\n\n// ── TranscriptsWriter — cache-maintenance write door ─────────────────────────\n// Mutates the `transcripts` index (rebuilt from JSONL); NEVER writes a sacred file.\n\nexport class TranscriptsWriter extends Context.Service<TranscriptsWriter, {\n readonly scan: (dirs?: ReadonlyArray<string>) => Effect.Effect<{ scanned: number }>;\n readonly rebuild: (dirs?: ReadonlyArray<string>) => Effect.Effect<{ scanned: number }>;\n readonly enrich: (ids?: ReadonlyArray<string>) => Effect.Effect<{ enriched: number }>;\n readonly embed: (ids?: ReadonlyArray<string>) => Effect.Effect<{ embedded: number }>;\n}>()('overdeck/TranscriptsWriter') {}\n\nexport const TranscriptsWriterLive = Layer.succeed(\n TranscriptsWriter,\n TranscriptsWriter.of({\n scan: (_dirs?) => Effect.succeed({ scanned: 0 }),\n rebuild: (_dirs?) => Effect.succeed({ scanned: 0 }),\n enrich: (_ids?) => Effect.succeed({ enriched: 0 }),\n embed: (_ids?) => Effect.succeed({ embedded: 0 }),\n }),\n);\n\n// ── ConversationWriter — write door ───────────────────────────────────────────\n// Writes ONLY the DB (conversations / favorites / conversation_files tables) and\n// creates NEW backing files via the fork primitive. NEVER mutates an existing file.\n// NO Records dependency — the DB row IS the source of truth (no git mirror).\n\nexport class ConversationWriter extends Context.Service<ConversationWriter, {\n readonly create: (opts: {\n name: ConversationName; cwd: string; model?: string; effort?: string;\n harness?: Harness; issueId?: string; title?: string;\n }) => Effect.Effect<Conversation>;\n readonly archive: (name: ConversationName) => Effect.Effect<Conversation, ConversationNotFound | AlreadyArchived>;\n readonly unarchive: (name: ConversationName) => Effect.Effect<Conversation, ConversationNotFound | NotArchived>;\n readonly setFavorite: (type: 'conversation' | 'project', itemId: string) => Effect.Effect<void>;\n readonly unsetFavorite: (type: 'conversation' | 'project', itemId: string) => Effect.Effect<void>;\n readonly retitle: (name: ConversationName, title: string, source: 'manual' | 'auto' | 'ai') =>\n Effect.Effect<Conversation, ConversationNotFound>;\n readonly setModel: (name: ConversationName, model: string) => Effect.Effect<Conversation, ConversationNotFound>;\n readonly setHarness: (name: ConversationName, harness: Harness) => Effect.Effect<Conversation, ConversationNotFound>;\n readonly handoff: (source: ConversationName, target: ConversationName, docPath: string) =>\n Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound>;\n readonly clear: (source: ConversationName) =>\n Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound>;\n readonly summaryFork: (source: ConversationName, opts: { mode: 'summary' | 'plain'; model?: string }) =>\n Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound>;\n readonly compact: (name: ConversationName) =>\n Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound>;\n}>()('overdeck/ConversationWriter') {}\n\nexport const ConversationWriterLive = Layer.effect(\n ConversationWriter,\n Effect.gen(function* () {\n const db = yield* Db;\n const bus = yield* EventBus;\n const resolver = yield* ConversationsResolver;\n const now = () => new Date();\n\n const create = (opts: {\n name: ConversationName; cwd: string; model?: string; effort?: string;\n harness?: Harness; issueId?: string; title?: string;\n }): Effect.Effect<Conversation> =>\n Effect.gen(function* () {\n const id = randomUUID() as unknown as ConversationId;\n const ts = now();\n yield* Effect.promise(() =>\n db.q.insert(conversationsTable).values({\n id,\n name: opts.name,\n cwd: opts.cwd,\n issueId: opts.issueId ?? null,\n harness: opts.harness ?? null,\n model: opts.model ?? null,\n effort: opts.effort ?? null,\n title: opts.title ?? null,\n titleSource: opts.title ? 'manual' : null,\n createdAt: ts,\n }).onConflictDoNothing(),\n );\n yield* bus.emit({ type: 'conversation.created', payload: { name: opts.name } });\n return decodeConversation({\n id, name: opts.name, cwd: opts.cwd,\n issueId: opts.issueId ?? null, harness: opts.harness ?? null,\n model: opts.model ?? null, effort: opts.effort ?? null,\n title: opts.title ?? null, titleSource: opts.title ? 'manual' : null,\n createdAt: ts, archivedAt: null,\n handoffDocPath: null, handoffTargetConvId: null, clearedToConvId: null,\n files: [],\n });\n });\n\n const archive = (name: ConversationName): Effect.Effect<Conversation, ConversationNotFound | AlreadyArchived> =>\n Effect.gen(function* () {\n const conv = yield* resolver.get(name);\n if (conv.archivedAt) return yield* Effect.fail(new AlreadyArchived({ name }));\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ archivedAt: now() }).where(eq(conversationsTable.name, name)),\n );\n yield* Effect.promise(() =>\n db.q.delete(favoritesTable).where(\n and(eq(favoritesTable.type, 'conversation'), eq(favoritesTable.itemId, name)),\n ),\n );\n yield* bus.emit({ type: 'conversation.archived', payload: { name } });\n return yield* resolver.get(name);\n });\n\n const unarchive = (name: ConversationName): Effect.Effect<Conversation, ConversationNotFound | NotArchived> =>\n Effect.gen(function* () {\n const conv = yield* resolver.get(name);\n if (!conv.archivedAt) return yield* Effect.fail(new NotArchived({ name }));\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ archivedAt: null }).where(eq(conversationsTable.name, name)),\n );\n yield* bus.emit({ type: 'conversation.unarchived', payload: { name } });\n return yield* resolver.get(name);\n });\n\n const setFavorite = (type: 'conversation' | 'project', itemId: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* Effect.promise(() =>\n db.q.insert(favoritesTable).values({ type, itemId, createdAt: now() }).onConflictDoNothing(),\n );\n yield* bus.emit({ type: 'conversation.favorited', payload: { type, itemId } });\n });\n\n const unsetFavorite = (type: 'conversation' | 'project', itemId: string): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* Effect.promise(() =>\n db.q.delete(favoritesTable).where(\n and(eq(favoritesTable.type, type), eq(favoritesTable.itemId, itemId)),\n ),\n );\n yield* bus.emit({ type: 'conversation.unfavorited', payload: { type, itemId } });\n });\n\n const retitle = (name: ConversationName, title: string, source: 'manual' | 'auto' | 'ai'):\n Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.gen(function* () {\n yield* resolver.get(name);\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ title, titleSource: source }).where(eq(conversationsTable.name, name)),\n );\n yield* bus.emit({ type: 'conversation.retitled', payload: { name, title, source } });\n return yield* resolver.get(name);\n });\n\n const setModel = (name: ConversationName, model: string):\n Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.gen(function* () {\n yield* resolver.get(name);\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ model }).where(eq(conversationsTable.name, name)),\n );\n yield* bus.emit({ type: 'conversation.modelChanged', payload: { name, model } });\n return yield* resolver.get(name);\n });\n\n const setHarness = (name: ConversationName, harness: Harness):\n Effect.Effect<Conversation, ConversationNotFound> =>\n Effect.gen(function* () {\n yield* resolver.get(name);\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({ harness }).where(eq(conversationsTable.name, name)),\n );\n yield* bus.emit({ type: 'conversation.harnessChanged', payload: { name, harness } });\n return yield* resolver.get(name);\n });\n\n // The fork primitive — the ONLY mechanism that creates a new backing file.\n // Creates a fresh UUID locator, registers a conversation_files pointer, records lineage.\n // NEVER opens an existing file for write.\n const forkNewFile = (\n source: ConversationName,\n kind: 'handoff' | 'clear' | 'summary' | 'plain' | 'harness-switch' | 'compaction',\n opts?: { docPath?: string },\n ): Effect.Effect<{ conversation: Conversation; backingFile: string }, ConversationNotFound> =>\n Effect.gen(function* () {\n const conv = yield* resolver.get(source);\n const newLocator = randomUUID();\n yield* Effect.promise(() =>\n db.q.insert(conversationFilesTable).values({\n conversationId: conv.id,\n harness: conv.harness ?? 'claude-code',\n locator: newLocator,\n createdAt: now(),\n }).onConflictDoNothing(),\n );\n if (kind === 'handoff') {\n yield* Effect.promise(() =>\n db.q.update(conversationsTable).set({\n handoffDocPath: opts?.docPath ?? null,\n handoffTargetConvId: conv.id,\n }).where(eq(conversationsTable.name, source)),\n );\n } else if (kind === 'clear') {\n yield* Effect.promise(() =>\n db.q.update(conversationsTable)\n .set({ clearedToConvId: conv.id })\n .where(eq(conversationsTable.name, source)),\n );\n }\n yield* bus.emit({ type: 'conversation.forked', payload: { source, kind } });\n return { conversation: conv, backingFile: newLocator };\n });\n\n const handoff = (source: ConversationName, _target: ConversationName, docPath: string) =>\n forkNewFile(source, 'handoff', { docPath });\n\n const clear = (source: ConversationName) =>\n forkNewFile(source, 'clear');\n\n const summaryFork = (source: ConversationName, opts: { mode: 'summary' | 'plain'; model?: string }) =>\n forkNewFile(source, opts.mode === 'summary' ? 'summary' : 'plain');\n\n const compact = (name: ConversationName) =>\n forkNewFile(name, 'compaction');\n\n return ConversationWriter.of({\n create, archive, unarchive, setFavorite, unsetFavorite,\n retitle, setModel, setHarness,\n handoff, clear, summaryFork, compact,\n });\n }),\n);\n\n// ── ConversationsApi — HttpApiGroup (controller declarations) ─────────────────\n// Handlers wire in at bootstrap; R = ConversationsResolver | TranscriptsResolver |\n// ConversationWriter, never Db directly.\n\nexport const ConversationsApi = HttpApiGroup.make('conversations')\n .add(HttpApiEndpoint.get('list', '/conversations', {\n success: Schema.Array(Conversation),\n }))\n .add(HttpApiEndpoint.get('get', '/conversations/:name', {\n params: Schema.Struct({ name: ConversationName }),\n success: Conversation,\n error: ConversationNotFound,\n }))\n .add(HttpApiEndpoint.get('getHandoffDoc', '/conversations/:name/handoff-doc', {\n params: Schema.Struct({ name: ConversationName }),\n success: Schema.String,\n error: ConversationNotFound,\n }))\n .add(HttpApiEndpoint.post('create', '/conversations', {\n success: Conversation,\n }))\n .add(HttpApiEndpoint.post('archive', '/conversations/:name/archive', {\n params: Schema.Struct({ name: ConversationName }),\n success: Conversation,\n error: [ConversationNotFound, AlreadyArchived],\n }))\n .add(HttpApiEndpoint.post('unarchive', '/conversations/:name/unarchive', {\n params: Schema.Struct({ name: ConversationName }),\n success: Conversation,\n error: [ConversationNotFound, NotArchived],\n }));\n\n// ── Legacy-compatible sync door ──────────────────────────────────────────────\n//\n// These exports keep the current dashboard/CLI call sites synchronous while\n// moving the storage boundary to overdeck.db. They must not call the legacy\n// panopticon.db conversation helpers.\n\nexport type LegacyTitleSource = 'auto' | 'ai' | 'ai-refined' | 'ai-explicit' | 'manual' | 'default';\n\nexport interface ForkRequest {\n parentConversationName: string;\n sessionId: string;\n forkMode: 'summary' | 'plain' | 'handoff';\n summaryModel?: string;\n localSummaryOnly: boolean;\n includeThinkingInSummary?: boolean;\n summaryHarness?: RuntimeName;\n handoffFocus?: string;\n handoffAuthor: 'source' | 'external';\n handoffAuthorModel?: string;\n handoffAuthorHarness?: RuntimeName;\n}\n\nexport interface LegacyConversation {\n id: number;\n name: string;\n tmuxSession: string;\n status: 'active' | 'ended';\n cwd: string;\n issueId: string | null;\n createdAt: string;\n endedAt: string | null;\n lastAttachedAt: string | null;\n claudeSessionId: string | null;\n title: string | null;\n titleSource: LegacyTitleSource | null;\n titleSeed: string | null;\n totalCost: number;\n totalTokens: number;\n archivedAt: string | null;\n model: string | null;\n effort: string | null;\n forkStatus: string | null;\n forkError: string | null;\n harness: RuntimeName | null;\n deliveryMethod: 'auto' | 'channels' | 'tmux' | null;\n spawnError: string | null;\n handoffDocPath: string | null;\n handoffTargetConvId: number | null;\n forkFallbackReason: string | null;\n clearedToConvId: number | null;\n forkRequest: string | null;\n forkRetryCount: number;\n}\n\nexport interface ArchivedConversationWithEnrichment {\n id: number;\n name: string;\n cwd: string;\n issueId: string | null;\n createdAt: string;\n claudeSessionId: string | null;\n harness: RuntimeName | null;\n title: string | null;\n totalCost: number;\n archivedAt: string;\n model: string | null;\n discoveredJsonlPath: string | null;\n discoveredWorkspacePath: string | null;\n messageCount: number | null;\n firstTs: string | null;\n lastTs: string | null;\n primaryModel: string | null;\n tokenInput: number | null;\n tokenOutput: number | null;\n estimatedCost: number | null;\n toolsUsed: string | null;\n filesTouched: string | null;\n tags: string | null;\n summary: string | null;\n enrichmentLevel: number | null;\n enrichmentFailed: number | null;\n}\n\nexport interface ArchivedConversationListOptions {\n harness?: RuntimeName;\n workspacePath?: string;\n primaryModel?: string;\n unmanaged?: boolean;\n since?: string;\n before?: string;\n after?: string;\n minCost?: number;\n maxCost?: number;\n minMessages?: number;\n issueId?: string;\n enriched?: boolean;\n notEnriched?: boolean;\n enrichmentLevel?: number;\n enrichmentLevelLessThan?: number;\n tags?: string[];\n tools?: string[];\n files?: string[];\n limit?: number;\n offset?: number;\n}\n\nexport type LegacyFavoriteType = 'conversation';\n\ninterface LegacyConversationRow {\n legacy_id: number;\n id: string;\n name: string;\n cwd: string;\n issue_id: string | null;\n harness: string | null;\n model: string | null;\n effort: string | null;\n title: string | null;\n title_source: string | null;\n created_at: number | Date;\n archived_at: number | Date | null;\n handoff_doc_path: string | null;\n handoff_target_conv_id: string | null;\n cleared_to_conv_id: string | null;\n claude_session_id: string | null;\n tmux_session: string | null;\n status: string | null;\n ended_at: number | null;\n last_attached_at: number | null;\n session_file: string | null;\n total_cost: number | null;\n total_tokens: number | null;\n fork_status: string | null;\n fork_error: string | null;\n fork_retry_count: number | null;\n fork_request: string | null;\n fork_fallback_reason: string | null;\n delivery_method: string | null;\n spawn_error: string | null;\n}\n\nconst LEGACY_CONVERSATION_SELECT = `\n SELECT\n c.rowid AS legacy_id,\n c.id,\n c.name,\n c.cwd,\n c.issue_id,\n c.harness,\n c.model,\n c.effort,\n c.title,\n c.title_source,\n c.created_at,\n c.archived_at,\n c.handoff_doc_path,\n c.handoff_target_conv_id,\n c.cleared_to_conv_id,\n c.tmux_session,\n c.status,\n c.ended_at,\n c.last_attached_at,\n c.session_file,\n c.total_cost,\n c.total_tokens,\n c.fork_status,\n c.fork_error,\n c.fork_retry_count,\n c.fork_request,\n c.fork_fallback_reason,\n c.delivery_method,\n c.spawn_error,\n (\n SELECT cf.locator\n FROM conversation_files cf\n WHERE cf.conversation_id = c.id\n ORDER BY (cf.harness = 'claude-code') DESC, cf.created_at ASC, cf.id ASC\n LIMIT 1\n ) AS claude_session_id\n FROM conversations c\n`;\n\nconst AGENT_CONVERSATION_PREFIXES = ['agent-', 'planning-', 'specialist-'];\n\nexport function isAgentConversationName(name: string): boolean {\n return AGENT_CONVERSATION_PREFIXES.some((p) => name.startsWith(p));\n}\n\n/**\n * Live Claude session id for a conversation — consistent across every consumer (PAN-1866).\n *\n * Work-agent / specialist conversations rotate Claude sessions, but the DB only records the\n * FIRST session (the oldest conversation_files locator), so the stored id is stale by\n * construction. The agent folder's session.id is the authoritative live session — it is what\n * `claude --resume` actually runs. So for agent conversations resolve from the filesystem\n * (session.id, then sessions.json), falling back to the DB value; for human conversation-panel\n * sessions the DB value is canonical. Applied at the read door (rowToLegacyConversation) so the\n * CLI, panel, teardown, and frontend all observe the same id.\n *\n * Read inline (not via lib/agents.ts) to avoid the agents <-> conversations import cycle.\n */\nexport function resolveLiveSessionId(conv: {\n name: string;\n tmuxSession: string;\n claudeSessionId: string | null;\n}): string | null {\n if (!isAgentConversationName(conv.name)) return conv.claudeSessionId;\n const agentDir = join(getOverdeckHome(), 'agents', conv.tmuxSession);\n try {\n const sid = readFileSync(join(agentDir, 'session.id'), 'utf8').trim();\n if (sid) return sid;\n } catch { /* no session.id yet */ }\n try {\n const arr: unknown = JSON.parse(readFileSync(join(agentDir, 'sessions.json'), 'utf8'));\n if (Array.isArray(arr) && arr.length > 0) {\n const last = arr[arr.length - 1];\n if (typeof last === 'string' && last.trim()) return last.trim();\n }\n } catch { /* no/invalid sessions.json */ }\n return conv.claudeSessionId;\n}\n\nfunction overdeckDb() {\n return getOverdeckDatabaseSync();\n}\n\nfunction toIso(value: number | Date | null | undefined): string | null {\n if (value == null) return null;\n if (value instanceof Date) return value.toISOString();\n return new Date(value).toISOString();\n}\n\nfunction toMillis(value: Date | string | number = new Date()): number {\n if (value instanceof Date) return value.getTime();\n if (typeof value === 'number') return value;\n return new Date(value).getTime();\n}\n\n/** Map a raw DB harness string to a canonical RuntimeName, normalizing legacy 'pi' to 'ohmypi' on read. */\nexport function normalizeHarness(harness: string | null): RuntimeName | null {\n if (harness === 'pi' || harness === 'ohmypi') return 'ohmypi';\n if (harness === 'claude-code' || harness === 'codex') return harness;\n return null;\n}\n\nfunction legacyRowIdForConversationId(id: string | null): number | null {\n if (!id) return null;\n const row = overdeckDb()\n .prepare(`SELECT rowid AS id FROM conversations WHERE id = ?`)\n .get(id) as { id: number } | undefined;\n return row?.id ?? null;\n}\n\nfunction conversationUuidForLegacyId(id: number): string | null {\n const row = overdeckDb()\n .prepare(`SELECT id FROM conversations WHERE rowid = ?`)\n .get(id) as { id: string } | undefined;\n return row?.id ?? null;\n}\n\nfunction rowToLegacyConversation(row: LegacyConversationRow): LegacyConversation {\n const archivedAt = toIso(row.archived_at);\n return {\n id: row.legacy_id,\n name: row.name,\n tmuxSession: row.tmux_session ?? `conv-${row.name}`,\n status: (row.status ?? (archivedAt ? 'ended' : 'active')) as 'active' | 'ended',\n cwd: row.cwd,\n issueId: row.issue_id ?? null,\n createdAt: toIso(row.created_at) ?? new Date(0).toISOString(),\n endedAt: toIso(row.ended_at) ?? archivedAt,\n lastAttachedAt: toIso(row.last_attached_at),\n claudeSessionId: resolveLiveSessionId({\n name: row.name,\n tmuxSession: row.tmux_session ?? `conv-${row.name}`,\n claudeSessionId: row.claude_session_id ?? null,\n }),\n title: row.title ?? null,\n titleSource: (row.title_source as LegacyTitleSource | null) ?? null,\n titleSeed: row.title ?? null,\n totalCost: row.total_cost ?? 0,\n totalTokens: row.total_tokens ?? 0,\n archivedAt,\n model: row.model ?? null,\n effort: row.effort ?? null,\n forkStatus: row.fork_status ?? null,\n forkError: row.fork_error ?? null,\n harness: normalizeHarness(row.harness),\n deliveryMethod: (row.delivery_method as 'auto' | 'channels' | 'tmux' | null) ?? null,\n spawnError: row.spawn_error ?? null,\n handoffDocPath: row.handoff_doc_path ?? null,\n handoffTargetConvId: legacyRowIdForConversationId(row.handoff_target_conv_id),\n forkFallbackReason: row.fork_fallback_reason ?? null,\n clearedToConvId: legacyRowIdForConversationId(row.cleared_to_conv_id),\n forkRequest: row.fork_request ?? null,\n forkRetryCount: row.fork_retry_count ?? 0,\n };\n}\n\nfunction getConversationByUuid(id: string): LegacyConversation | null {\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT} WHERE c.id = ?`)\n .get(id) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\n\nfunction getConversationUuidByName(name: string): string | null {\n const row = overdeckDb()\n .prepare(`SELECT id FROM conversations WHERE name = ?`)\n .get(name) as { id: string } | undefined;\n return row?.id ?? null;\n}\n\n/**\n * Aggregate each conversation's cost + token usage from the canonical `cost_events`\n * ledger, keyed by session id and joined through `conversation_files`. This is the\n * source of truth for conversation cost: `conversations.total_cost` is only a\n * denormalized cache written when a conversation is opened (via the /messages\n * route), so it reads stale/zero for any conversation not opened since the\n * overdeck.db cutover. The list reads from this ledger so it shows live costs\n * without requiring each conversation to be opened first.\n *\n * Returns a map of conversation id → { cost, tokens }. A conversation predating the\n * ledger has no rows and is simply absent — callers fall back to the cached\n * `total_cost` column for those.\n */\nexport function getConversationLedgerCosts(): Map<string, { cost: number; tokens: number }> {\n // Keyed by the conversation's rowid, because that is the public `id`\n // {@link LegacyConversation.id} carries (c.rowid AS legacy_id), not the uuid.\n // cost_events.session_id matches conversation_files.locator; a conversation may\n // have several locators (relaunch/clear), so we sum across all of them.\n const rows = overdeckDb()\n .prepare(\n `SELECT c.rowid AS cid,\n COALESCE(SUM(ce.cost), 0) AS cost,\n COALESCE(SUM(ce.input + ce.output + ce.cache_read + ce.cache_write), 0) AS tokens\n FROM cost_events ce\n JOIN conversation_files cf ON cf.locator = ce.session_id\n JOIN conversations c ON c.id = cf.conversation_id\n GROUP BY c.rowid`,\n )\n .all() as { cid: number; cost: number; tokens: number }[];\n const map = new Map<string, { cost: number; tokens: number }>();\n for (const r of rows) map.set(String(r.cid), { cost: r.cost ?? 0, tokens: r.tokens ?? 0 });\n return map;\n}\n\nexport function listConversations(options?: { limit?: number; offset?: number }): LegacyConversation[] {\n let sql = `${LEGACY_CONVERSATION_SELECT}\n WHERE c.archived_at IS NULL\n AND c.name NOT LIKE 'agent-%'\n AND c.name NOT LIKE 'planning-%'\n AND c.name NOT LIKE 'specialist-%'\n ORDER BY c.created_at DESC`;\n const params: number[] = [];\n if (options?.limit !== undefined) {\n sql += ' LIMIT ?';\n params.push(options.limit);\n }\n if (options?.offset !== undefined) {\n sql += ' OFFSET ?';\n params.push(options.offset);\n }\n const rows = overdeckDb().prepare(sql).all(...params) as LegacyConversationRow[];\n return rows.map(rowToLegacyConversation);\n}\n\nexport function getConversationByName(name: string): LegacyConversation | null {\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT} WHERE c.name = ?`)\n .get(name) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\n\nexport function getConversationById(id: number): LegacyConversation | null {\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT} WHERE c.rowid = ?`)\n .get(id) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\n\nexport function getConversationByClaudeSessionId(claudeSessionId: string): LegacyConversation | null {\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT}\n WHERE EXISTS (\n SELECT 1 FROM conversation_files cf\n WHERE cf.conversation_id = c.id AND cf.locator = ?\n )`)\n .get(claudeSessionId) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\nexport { findConversationForCostSessionSync } from './conversation-cost-session.js';\nexport function getConversationByTmuxSession(tmuxSession: string): LegacyConversation | null {\n const name = tmuxSession.startsWith('conv-') ? tmuxSession.slice(5) : tmuxSession;\n const row = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT}\n WHERE c.name = ? AND c.archived_at IS NULL\n ORDER BY c.created_at DESC\n LIMIT 1`)\n .get(name) as LegacyConversationRow | undefined;\n return row ? rowToLegacyConversation(row) : null;\n}\n\nexport function listArchivedConversations(): LegacyConversation[] {\n const rows = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT}\n WHERE c.archived_at IS NOT NULL\n ORDER BY c.archived_at DESC, c.created_at DESC`)\n .all() as LegacyConversationRow[];\n return rows.map(rowToLegacyConversation);\n}\n\nexport function listArchivedConversationsWithEnrichment(options: ArchivedConversationListOptions = {}): ArchivedConversationWithEnrichment[] {\n ensureDiscoveredSessionsSchema();\n const db = overdeckDb();\n\n const conditions: string[] = ['c.archived_at IS NOT NULL'];\n const params: unknown[] = [];\n\n const lastTs = `COALESCE(ds.last_ts, c.archived_at)`;\n const firstTs = `COALESCE(ds.first_ts, c.created_at)`;\n const primaryModel = `COALESCE(ds.primary_model, c.model)`;\n const estimatedCost = `COALESCE(ds.estimated_cost, c.total_cost)`;\n const messageCount = `COALESCE(ds.message_count, 0)`;\n const enrichmentLevel = `COALESCE(ds.enrichment_level, 0)`;\n\n if (options.harness === 'claude-code') {\n conditions.push('(c.harness = ? OR c.harness IS NULL)');\n params.push(options.harness);\n } else if (options.harness === 'ohmypi') {\n conditions.push('(c.harness = ? OR c.harness = ?)');\n params.push(options.harness, 'pi');\n } else if (options.harness !== undefined) {\n conditions.push('c.harness = ?');\n params.push(options.harness);\n }\n if (options.workspacePath !== undefined) { conditions.push('c.cwd = ?'); params.push(options.workspacePath); }\n if (options.primaryModel !== undefined) { conditions.push(`${primaryModel} = ?`); params.push(options.primaryModel); }\n if (options.issueId !== undefined) { conditions.push('c.issue_id = ?'); params.push(options.issueId); }\n if (options.since !== undefined) { conditions.push(`${lastTs} >= ?`); params.push(toMillis(options.since)); }\n if (options.before !== undefined) { conditions.push(`${lastTs} < ?`); params.push(toMillis(options.before)); }\n if (options.after !== undefined) { conditions.push(`${firstTs} >= ?`); params.push(toMillis(options.after)); }\n if (options.minCost !== undefined) { conditions.push(`${estimatedCost} >= ?`); params.push(options.minCost); }\n if (options.maxCost !== undefined) { conditions.push(`${estimatedCost} <= ?`); params.push(options.maxCost); }\n if (options.minMessages !== undefined) { conditions.push(`${messageCount} >= ?`); params.push(options.minMessages); }\n if (options.unmanaged === true) { conditions.push('0 = 1'); }\n if (options.enriched === true) { conditions.push(`${enrichmentLevel} > 0`); }\n if (options.notEnriched === true) { conditions.push(`${enrichmentLevel} = 0`); }\n if (options.enrichmentLevel !== undefined) { conditions.push(`${enrichmentLevel} = ?`); params.push(options.enrichmentLevel); }\n if (options.enrichmentLevelLessThan !== undefined) { conditions.push(`${enrichmentLevel} < ?`); params.push(options.enrichmentLevelLessThan); }\n if (options.tags?.length) {\n for (const tag of options.tags) {\n conditions.push(`EXISTS (SELECT 1 FROM discovered_session_tags dst WHERE dst.session_id = ds.id AND dst.tag = ?)`);\n params.push(tag);\n }\n }\n if (options.tools?.length) {\n for (const tool of options.tools) {\n conditions.push(`EXISTS (SELECT 1 FROM discovered_session_tools dstool WHERE dstool.session_id = ds.id AND dstool.tool = ?)`);\n params.push(tool);\n }\n }\n if (options.files?.length) {\n for (const file of options.files) {\n conditions.push(`EXISTS (SELECT 1 FROM discovered_session_files dsfile WHERE dsfile.session_id = ds.id AND dsfile.file_path = ?)`);\n params.push(file);\n }\n }\n\n const safeLimit = Number.isFinite(options.limit) && options.limit! >= 0 ? options.limit! : undefined;\n const safeOffset = Number.isFinite(options.offset) && options.offset! >= 0 ? options.offset! : undefined;\n const limitClause = safeLimit !== undefined ? 'LIMIT ?' : safeOffset !== undefined ? 'LIMIT -1' : '';\n const offsetClause = safeOffset !== undefined ? 'OFFSET ?' : '';\n if (safeLimit !== undefined) params.push(safeLimit);\n if (safeOffset !== undefined) params.push(safeOffset);\n\n const where = `WHERE ${conditions.join(' AND ')}`;\n const sql = `\n SELECT\n c.rowid AS legacy_id,\n c.id AS uuid,\n c.name,\n c.cwd,\n c.issue_id,\n c.created_at,\n c.harness,\n (\n SELECT cf.locator FROM conversation_files cf\n WHERE cf.conversation_id = c.id\n ORDER BY (cf.harness = 'claude-code') DESC, cf.created_at ASC, cf.id ASC\n LIMIT 1\n ) AS claude_session_id,\n c.title,\n c.total_cost,\n c.archived_at,\n c.model,\n ds.jsonl_path AS discovered_jsonl_path,\n ds.workspace_path AS discovered_workspace_path,\n ds.message_count,\n ds.first_ts,\n ds.last_ts,\n ds.primary_model,\n ds.token_input,\n ds.token_output,\n ds.estimated_cost,\n ds.tools_used,\n ds.files_touched,\n ds.tags,\n ds.summary,\n ds.enrichment_level,\n ds.enrichment_failed\n FROM conversations c\n LEFT JOIN discovered_sessions ds ON ds.session_id = (\n SELECT cf.locator FROM conversation_files cf\n WHERE cf.conversation_id = c.id\n ORDER BY (cf.harness = 'claude-code') DESC, cf.created_at ASC, cf.id ASC\n LIMIT 1\n )\n ${where}\n ORDER BY c.archived_at DESC, c.created_at DESC\n ${limitClause} ${offsetClause}\n `;\n\n type RawRow = {\n legacy_id: number; uuid: string; name: string; cwd: string; issue_id: string | null;\n created_at: number; harness: string | null; claude_session_id: string | null; title: string | null;\n total_cost: number | null; archived_at: number | null; model: string | null;\n discovered_jsonl_path: string | null; discovered_workspace_path: string | null;\n message_count: number | null; first_ts: number | null; last_ts: number | null;\n primary_model: string | null; token_input: number | null; token_output: number | null;\n estimated_cost: number | null; tools_used: string | null; files_touched: string | null;\n tags: string | null; summary: string | null; enrichment_level: number | null;\n enrichment_failed: number | null;\n };\n\n const rawRows = db.prepare(sql).all(...params) as RawRow[];\n\n return rawRows.map((r): ArchivedConversationWithEnrichment => ({\n id: r.legacy_id,\n name: r.name,\n cwd: r.cwd,\n issueId: r.issue_id ?? null,\n createdAt: toIso(r.created_at) ?? new Date(0).toISOString(),\n claudeSessionId: r.claude_session_id ?? null,\n harness: normalizeHarness(r.harness),\n title: r.title ?? null,\n totalCost: r.total_cost ?? 0,\n archivedAt: toIso(r.archived_at) ?? toIso(r.created_at) ?? new Date(0).toISOString(),\n model: r.model ?? null,\n discoveredJsonlPath: r.discovered_jsonl_path ?? null,\n discoveredWorkspacePath: r.discovered_workspace_path ?? r.cwd,\n messageCount: r.message_count ?? null,\n firstTs: toIso(r.first_ts),\n lastTs: toIso(r.last_ts),\n primaryModel: r.primary_model ?? r.model ?? null,\n tokenInput: r.token_input ?? null,\n tokenOutput: r.token_output ?? null,\n estimatedCost: r.estimated_cost ?? null,\n toolsUsed: r.tools_used ?? null,\n filesTouched: r.files_touched ?? null,\n tags: r.tags ?? null,\n summary: r.summary ?? null,\n enrichmentLevel: r.enrichment_level ?? null,\n enrichmentFailed: r.enrichment_failed ?? null,\n }));\n}\n\nexport function listArchivedConversationNames(): string[] {\n return listArchivedConversations().map((conv) => conv.name);\n}\n\nexport function createConversation(opts: {\n name: string;\n tmuxSession: string;\n cwd: string;\n issueId?: string;\n claudeSessionId?: string;\n title?: string;\n titleSource?: LegacyTitleSource;\n titleSeed?: string;\n model?: string;\n effort?: string;\n forkStatus?: string;\n harness?: RuntimeName;\n deliveryMethod?: 'auto' | 'channels' | 'tmux';\n}): LegacyConversation {\n const db = overdeckDb();\n const id = randomUUID();\n const now = toMillis();\n\n db.transaction(() => {\n db.prepare(`DELETE FROM conversation_files WHERE conversation_id IN (SELECT id FROM conversations WHERE name = ?)`).run(opts.name);\n db.prepare(`DELETE FROM conversations WHERE name = ?`).run(opts.name);\n db.prepare(`\n INSERT INTO conversations\n (id, name, cwd, issue_id, harness, model, effort, title, title_source, created_at, archived_at,\n tmux_session, status, fork_status, fork_retry_count, delivery_method, spawn_error)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, 'active', ?, 0, ?, ?)\n `).run(\n id,\n opts.name,\n opts.cwd,\n opts.issueId ?? null,\n opts.harness ?? null,\n opts.model ?? null,\n opts.effort ?? null,\n opts.title ?? null,\n opts.titleSource ?? (opts.title ? 'auto' : null),\n now,\n opts.tmuxSession ?? null,\n opts.forkStatus ?? null,\n opts.deliveryMethod ?? null,\n null, // spawn_error starts null\n );\n if (opts.claudeSessionId) {\n db.prepare(`\n INSERT OR IGNORE INTO conversation_files (conversation_id, harness, locator, created_at)\n VALUES (?, ?, ?, ?)\n `).run(id, opts.harness ?? 'claude-code', opts.claudeSessionId, now);\n }\n })();\n\n const conv = getConversationByUuid(id);\n if (!conv) throw new Error(`Failed to create conversation ${opts.name}`);\n return conv;\n}\n\nexport function markConversationEnded(name: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET ended_at = ?, status = 'ended' WHERE name = ?`)\n .run(Date.now(), name);\n}\n\n// PAN-1972: resurrect a conversation the liveness poller previously latched to\n// 'ended'. tmux is the liveness oracle — when a session + harness are observed\n// alive, the row must read 'active'. The `status != 'active'` guard makes this a\n// true no-op (no needless write) when the row is already correct.\nexport function markConversationRunning(name: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET status = 'active', ended_at = NULL WHERE name = ? AND status != 'active'`)\n .run(name);\n}\n\nexport function markConversationActive(name: string): void {\n overdeckDb().prepare(`UPDATE conversations SET archived_at = NULL WHERE name = ?`).run(name);\n}\n\nexport function reactivateConversationForSpawn(opts: {\n name: string;\n tmuxSession: string;\n cwd: string;\n issueId?: string;\n claudeSessionId?: string;\n model?: string;\n harness?: RuntimeName;\n}): void {\n const db = overdeckDb();\n const now = toMillis();\n const id = getConversationUuidByName(opts.name);\n if (!id) return;\n db.prepare(`\n UPDATE conversations\n SET cwd = ?, issue_id = ?, model = ?, harness = ?, archived_at = NULL\n WHERE id = ?\n `).run(opts.cwd, opts.issueId ?? null, opts.model ?? null, opts.harness ?? null, id);\n if (opts.claudeSessionId) {\n db.prepare(`\n INSERT OR IGNORE INTO conversation_files (conversation_id, harness, locator, created_at)\n VALUES (?, ?, ?, ?)\n `).run(id, opts.harness ?? 'claude-code', opts.claudeSessionId, now);\n }\n}\n\nexport function updateLastAttached(name: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET last_attached_at = ? WHERE name = ?`)\n .run(Date.now(), name);\n}\n\nexport function markAllEndedOnStartup(): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET status = 'ended', ended_at = COALESCE(ended_at, ?) WHERE status = 'active'`)\n .run(Date.now());\n}\n\nexport function updateConversationTitle(name: string, title: string, titleSource?: LegacyTitleSource): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET title = ?, title_source = COALESCE(?, title_source) WHERE name = ?`)\n .run(title, titleSource ?? null, name);\n try {\n getEventStore().emitOnly({\n type: 'conversation.title_changed',\n timestamp: new Date().toISOString(),\n payload: { conversationName: name, title, titleSource: titleSource ?? '' },\n });\n } catch {\n // Event store is uninitialized in CLI/test contexts; title persistence is enough.\n }\n}\n\nexport function archiveConversation(name: string): void {\n const db = overdeckDb();\n db.prepare(`UPDATE conversations SET archived_at = ? WHERE name = ?`).run(toMillis(), name);\n db.prepare(`DELETE FROM favorites WHERE type = 'conversation' AND item_id = ?`).run(name);\n}\n\nexport function unarchiveConversation(name: string): void {\n overdeckDb().prepare(`UPDATE conversations SET archived_at = NULL WHERE name = ?`).run(name);\n}\n\nexport function updateConversationCost(name: string, totalCost: number, totalTokens?: number): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET total_cost = ?, total_tokens = COALESCE(?, total_tokens) WHERE name = ?`)\n .run(totalCost, totalTokens ?? null, name);\n}\n\nexport function setConversationModel(name: string, model: string): void {\n overdeckDb().prepare(`UPDATE conversations SET model = ? WHERE name = ?`).run(model, name);\n}\n\nexport function setConversationEffort(name: string, effort: string | null): void {\n overdeckDb().prepare(`UPDATE conversations SET effort = ? WHERE name = ?`).run(effort, name);\n}\n\nexport function setConversationHarness(name: string, harness: RuntimeName): void {\n overdeckDb().prepare(`UPDATE conversations SET harness = ? WHERE name = ?`).run(harness, name);\n}\n\nexport function setConversationClaudeSessionId(name: string, claudeSessionId: string): void {\n const db = overdeckDb();\n const id = getConversationUuidByName(name);\n if (!id) return;\n const harness = getConversationByName(name)?.harness ?? 'claude-code';\n db.prepare(`\n INSERT OR IGNORE INTO conversation_files (conversation_id, harness, locator, created_at)\n VALUES (?, ?, ?, ?)\n `).run(id, harness, claudeSessionId, toMillis());\n}\n\nexport function updateConversationDeliveryMethod(name: string, method: 'auto' | 'channels' | 'tmux' | null): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET delivery_method = ? WHERE name = ?`)\n .run(method, name);\n}\n\nexport function backfillConversationModel(name: string, model: string): void {\n overdeckDb().prepare(`UPDATE conversations SET model = ? WHERE name = ? AND model IS NULL`).run(model, name);\n}\n\nexport function updateForkStatus(name: string, status: string | null, error?: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET fork_status = ?, fork_error = ? WHERE name = ?`)\n .run(status, error ?? null, name);\n}\n\nexport function getStuckForks(): LegacyConversation[] {\n const rows = overdeckDb()\n .prepare(`${LEGACY_CONVERSATION_SELECT} WHERE c.fork_status IS NOT NULL AND c.fork_status != 'failed' ORDER BY c.created_at ASC`)\n .all() as LegacyConversationRow[];\n return rows.map(rowToLegacyConversation);\n}\n\nexport function setForkRequest(name: string, json: string): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET fork_request = ? WHERE name = ?`)\n .run(json, name);\n}\n\nexport function incrementForkRetryCount(name: string): number {\n const db = overdeckDb();\n db.prepare(`UPDATE conversations SET fork_retry_count = fork_retry_count + 1 WHERE name = ?`).run(name);\n const row = db.prepare(`SELECT fork_retry_count FROM conversations WHERE name = ?`).get(name) as\n | { fork_retry_count: number }\n | undefined;\n return row?.fork_retry_count ?? 0;\n}\n\nexport function updateConversationForkFallbackReason(name: string, reason: string | null): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET fork_fallback_reason = ? WHERE name = ?`)\n .run(reason, name);\n}\n\nexport function recordConversationHandoff(sourceName: string, targetName: string, docPath: string): LegacyConversation {\n const targetId = getConversationUuidByName(targetName);\n const target = targetId ? getConversationByUuid(targetId) : null;\n if (!targetId || !target) throw new Error(`Handoff target conversation ${targetName} not found`);\n const db = overdeckDb();\n db.prepare(`UPDATE conversations SET handoff_doc_path = ? WHERE id = ?`).run(docPath, targetId);\n db.prepare(`UPDATE conversations SET handoff_target_conv_id = ? WHERE name = ?`).run(targetId, sourceName);\n return getConversationByUuid(targetId) ?? target;\n}\n\nexport function setClearedToConvId(name: string, convId: number): void {\n const targetUuid = conversationUuidForLegacyId(convId);\n overdeckDb().prepare(`UPDATE conversations SET cleared_to_conv_id = ? WHERE name = ?`).run(targetUuid, name);\n}\n\nexport function hasOtherActiveConversationOnTmuxSession(_tmuxSession: string, _excludeName: string): boolean {\n return false;\n}\n\nexport function updateSpawnError(name: string, error: string | null): void {\n overdeckDb().prepare(`UPDATE conversations SET spawn_error = ? WHERE name = ?`).run(error, name);\n}\n\nexport function clearStuckForks(): number {\n return 0;\n}\n\nexport function canReplaceTitle(conv: LegacyConversation): boolean {\n if (conv.titleSource === 'manual') return false;\n return conv.titleSource === 'default' || conv.titleSource === 'auto';\n}\n\nexport function canRefineTitle(conv: LegacyConversation): boolean {\n return conv.titleSource === 'default'\n || conv.titleSource === 'auto'\n || conv.titleSource === 'ai'\n || conv.titleSource === 'ai-refined';\n}\n\nexport function listFavoritedIds(type: LegacyFavoriteType): string[] {\n const rows = overdeckDb()\n .prepare(`SELECT item_id FROM favorites WHERE type = ?`)\n .all(type) as Array<{ item_id: string }>;\n return rows.map((row) => row.item_id);\n}\n\nexport function setFavorite(type: LegacyFavoriteType, itemId: string): void {\n overdeckDb()\n .prepare(`INSERT OR IGNORE INTO favorites (type, item_id, created_at) VALUES (?, ?, ?)`)\n .run(type, itemId, toMillis());\n}\n\nexport function removeFavorite(type: LegacyFavoriteType, itemId: string): void {\n overdeckDb().prepare(`DELETE FROM favorites WHERE type = ? AND item_id = ?`).run(type, itemId);\n}\n\nexport interface ImportLegacyConversationMapped {\n name: string;\n tmuxSession: string | null;\n status: 'active' | 'ended';\n cwd: string;\n createdAt: number;\n endedAt: number | null;\n lastAttachedAt: number | null;\n sessionFile: string | null;\n claudeSessionId: string | null;\n title: string | null;\n titleSource: string | null;\n titleSeed: string | null;\n totalCost: number;\n totalTokens: number;\n archivedAt: number | null;\n model: string | null;\n effort: string | null;\n forkStatus: string | null;\n forkError: string | null;\n harness: string | null;\n deliveryMethod: string | null;\n spawnError: string | null;\n handoffDocPath: string | null;\n forkFallbackReason: string | null;\n forkRequest: string | null;\n forkRetryCount: number;\n}\n\nexport function importLegacyConversation(mapped: ImportLegacyConversationMapped): { uuid: string } {\n const db = overdeckDb();\n const uuid = randomUUID();\n db.transaction(() => {\n db.prepare(`\n INSERT OR IGNORE INTO conversations\n (id, name, cwd, harness, model, effort, title, title_source, created_at, archived_at,\n tmux_session, status, ended_at, last_attached_at, session_file, total_cost, total_tokens,\n fork_status, fork_error, fork_retry_count, fork_request, fork_fallback_reason,\n delivery_method, spawn_error, handoff_doc_path, handoff_target_conv_id, cleared_to_conv_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, NULL)\n `).run(\n uuid,\n mapped.name,\n mapped.cwd,\n mapped.harness,\n mapped.model,\n mapped.effort,\n mapped.title,\n mapped.titleSource,\n mapped.createdAt,\n mapped.archivedAt,\n mapped.tmuxSession ?? `conv-${mapped.name}`,\n mapped.status,\n mapped.endedAt,\n mapped.lastAttachedAt,\n mapped.sessionFile,\n mapped.totalCost,\n mapped.totalTokens,\n mapped.forkStatus,\n mapped.forkError,\n mapped.forkRetryCount,\n mapped.forkRequest,\n mapped.forkFallbackReason,\n mapped.deliveryMethod,\n mapped.spawnError,\n mapped.handoffDocPath,\n );\n if (mapped.claudeSessionId) {\n db.prepare(`\n INSERT OR IGNORE INTO conversation_files (conversation_id, harness, locator, created_at)\n VALUES (?, ?, ?, ?)\n `).run(uuid, mapped.harness ?? 'claude-code', mapped.claudeSessionId, mapped.createdAt);\n }\n })();\n return { uuid };\n}\n\nexport function setImportedConversationLinks(\n uuid: string,\n links: { handoffTargetUuid: string | null; clearedToUuid: string | null },\n): void {\n overdeckDb()\n .prepare(`UPDATE conversations SET handoff_target_conv_id = ?, cleared_to_conv_id = ? WHERE id = ?`)\n .run(links.handoffTargetUuid, links.clearedToUuid, uuid);\n}\n","import { readFile, mkdir, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport type { AgentRuntimeSnapshot } from '@overdeck/contracts';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport {\n getAgentRuntimeSnapshot as fetchAgentRuntimeSnapshot,\n emitAgentEvent,\n} from '../agent-runtime.js';\nimport { getRuntimeSnapshot, isAgentStateServiceInProcess } from '../agent-runtime-mirror.js';\nimport { normalizeHarness } from '../overdeck/conversations.js';\nimport { getAgentDir } from '../agents.js';\n\nexport type AgentResolution = 'working' | 'done' | 'needs_input' | 'stuck' | 'completed' | 'unclear' | 'abandoned';\n\n/** Callers consume this shape; data comes from AgentRuntimeSnapshot. */\nexport interface AgentRuntimeState {\n // 'suspended' retained for backward-compat with callers that still compare\n // against it defensively. The new event path never emits suspended — PAN-800\n // drops the auto-suspend feature; PAN-188 reintroduces it.\n state: 'active' | 'idle' | 'suspended' | 'stopped' | 'uninitialized' | 'waiting-on-human';\n lastActivity: string;\n currentTool?: string;\n claudeSessionId?: string;\n sessionModel?: string;\n sessionHarness?: RuntimeName;\n /**\n * For specialists: the issue currently being processed. Tracked per-agent in\n * the AgentStateService snapshot (see agent.current_issue_set event).\n */\n currentIssue?: string;\n resolution?: AgentResolution;\n resolutionCount?: number;\n resolutionUpdatedAt?: string;\n waitingReason?: string;\n waitingStartedAt?: string;\n waitingNotification?: string;\n contextSaturatedAt?: string;\n}\n\nexport function sessionResumeDriftReasons(\n runtimeState: AgentRuntimeState | null,\n model: string,\n harness: RuntimeName,\n): string[] {\n if (!runtimeState?.sessionModel || !runtimeState.sessionHarness) return [];\n const reasons: string[] = [];\n if (runtimeState.sessionModel !== model) {\n reasons.push(`model ${runtimeState.sessionModel}→${model}`);\n }\n if (runtimeState.sessionHarness !== harness) {\n reasons.push(`harness ${runtimeState.sessionHarness}→${harness}`);\n }\n return reasons;\n}\n\nfunction snapshotToRuntimeState(snap: AgentRuntimeSnapshot | null): AgentRuntimeState | null {\n if (!snap) return null;\n // Map Activity → legacy state. The legacy 'active' value collapses working\n // and thinking — neither consumer ever distinguished them.\n let state: AgentRuntimeState['state'];\n switch (snap.activity) {\n case 'working': state = 'active'; break;\n case 'thinking': state = 'active'; break;\n case 'idle': state = 'idle'; break;\n case 'stopped': state = 'stopped'; break;\n case 'waiting': state = 'waiting-on-human'; break;\n default: state = 'uninitialized';\n }\n return {\n state,\n lastActivity: snap.lastActivity,\n currentTool: snap.currentTool,\n claudeSessionId: snap.claudeSessionId,\n sessionModel: snap.sessionModel,\n sessionHarness: normalizeHarness(snap.sessionHarness ?? null) ?? undefined,\n currentIssue: snap.currentIssue,\n resolution: snap.resolution as AgentResolution | undefined,\n resolutionCount: snap.resolutionCount,\n resolutionUpdatedAt: snap.resolutionUpdatedAt,\n waitingReason: snap.waiting?.reason,\n waitingStartedAt: snap.waiting?.startedAt,\n waitingNotification: snap.waiting?.message,\n contextSaturatedAt: snap.contextSaturatedAt,\n };\n}\n\nexport function getAgentRuntimeStateSync(agentId: string): AgentRuntimeState | null {\n // Sync path: read from the in-process mirror (empty in fresh CLI processes,\n // populated inside the dashboard server). CLI commands should use\n // getAgentRuntimeStateProgram so they fall through to HTTP.\n return snapshotToRuntimeState(Effect.runSync(getRuntimeSnapshot(agentId)));\n}\n\nexport const getAgentRuntimeState = (agentId: string): Effect.Effect<AgentRuntimeState | null> =>\n Effect.gen(function* () {\n if (yield* isAgentStateServiceInProcess()) {\n return snapshotToRuntimeState(yield* getRuntimeSnapshot(agentId));\n }\n\n const snap = yield* fetchAgentRuntimeSnapshot(agentId);\n return snapshotToRuntimeState(snap);\n });\n\nasync function patchRuntimeJson(agentId: string, patch: Partial<AgentRuntimeState>): Promise<void> {\n const agentDir = getAgentDir(agentId);\n const runtimeFile = join(agentDir, 'runtime.json');\n let runtime: Record<string, unknown> = {};\n\n try {\n runtime = JSON.parse(await readFile(runtimeFile, 'utf-8')) as Record<string, unknown>;\n } catch {\n runtime = {};\n }\n\n if (Object.prototype.hasOwnProperty.call(patch, 'contextSaturatedAt')) {\n if (patch.contextSaturatedAt === undefined) {\n delete runtime.contextSaturatedAt;\n } else {\n runtime.contextSaturatedAt = patch.contextSaturatedAt;\n }\n }\n\n await mkdir(agentDir, { recursive: true });\n await writeFile(runtimeFile, JSON.stringify(runtime, null, 2));\n}\n\n/**\n * Emit events derived from a legacy-shape patch. Callers gradually migrate to\n * direct emitAgentEvent calls; this adapter keeps existing code working.\n */\nexport async function saveAgentRuntimeState(agentId: string, patch: Partial<AgentRuntimeState>): Promise<void> {\n if (patch.currentIssue !== undefined) {\n await Effect.runPromise(emitAgentEvent(agentId, {\n kind: 'current_issue_set',\n currentIssue: patch.currentIssue || undefined,\n }));\n }\n\n if (patch.resolution !== undefined && patch.resolutionCount !== undefined) {\n await Effect.runPromise(emitAgentEvent(agentId, {\n kind: 'resolution_set',\n resolution: patch.resolution,\n resolutionCount: patch.resolutionCount,\n }));\n }\n\n if (patch.state !== undefined) {\n if (patch.state === 'waiting-on-human') {\n await Effect.runPromise(emitAgentEvent(agentId, {\n kind: 'waiting_start',\n reason: (patch.waitingReason as 'tool_permission' | 'user_question' | 'disambiguation' | 'other') || 'other',\n message: patch.waitingNotification,\n }));\n } else if (patch.state === 'active') {\n await Effect.runPromise(emitAgentEvent(agentId, { kind: 'activity', activity: 'working', tool: patch.currentTool }));\n } else if (patch.state === 'idle') {\n await Effect.runPromise(emitAgentEvent(agentId, { kind: 'activity', activity: 'idle' }));\n } else if (patch.state === 'stopped') {\n await Effect.runPromise(emitAgentEvent(agentId, { kind: 'activity', activity: 'stopped' }));\n }\n } else if (patch.currentTool !== undefined) {\n await Effect.runPromise(emitAgentEvent(agentId, { kind: 'activity', activity: 'working', tool: patch.currentTool }));\n }\n\n if (patch.claudeSessionId || patch.sessionModel !== undefined || patch.sessionHarness !== undefined) {\n // model_set requires a model — use existing snapshot's model if present.\n const snap = getAgentRuntimeStateSync(agentId);\n if (snap || patch.claudeSessionId || patch.sessionModel !== undefined || patch.sessionHarness !== undefined) {\n const event: {\n kind: 'model_set';\n model: string;\n claudeSessionId?: string;\n sessionModel?: string;\n sessionHarness?: RuntimeName;\n } = {\n kind: 'model_set',\n model: 'unknown',\n };\n if (patch.claudeSessionId !== undefined) event.claudeSessionId = patch.claudeSessionId;\n if (patch.sessionModel !== undefined) event.sessionModel = patch.sessionModel;\n if (patch.sessionHarness !== undefined) event.sessionHarness = patch.sessionHarness;\n await Effect.runPromise(emitAgentEvent(agentId, {\n ...event,\n }));\n }\n }\n\n if (Object.prototype.hasOwnProperty.call(patch, 'contextSaturatedAt')) {\n await patchRuntimeJson(agentId, patch);\n await Effect.runPromise(emitAgentEvent(agentId, {\n kind: 'context_saturation_changed',\n contextSaturatedAt: patch.contextSaturatedAt,\n }));\n }\n}\n","import { existsSync, readFileSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { resolveBareNumericIdSync } from '../issue-id.js';\nimport { getOverdeckHome } from '../paths.js';\nimport { getRollbackAgentStatePath } from '../overdeck/agent-rollback-state.js';\nimport { getOverdeckAgentStateSync, listOverdeckAgentStatesSync } from '../overdeck/agent-state-sync.js';\nimport { getAgentRuntimeStateSync } from './runtime-state.js';\n\n/** Known agent ID prefixes — IDs with these prefixes are already normalized */\nconst AGENT_PREFIXES = ['agent-', 'planning-', 'conv-', 'strike-', 'inspect-'];\n// Singleton runners spawn under their own bare ID (spawnRun creates the tmux\n// session and agent dir from the raw ID). They MUST be listed here so\n// normalizeAgentId is a no-op for them — otherwise message delivery and state\n// lookups would target `agent-<id>` and miss the real session (PAN-1866: the\n// sequencer spawned but its prompt was delivered to a nonexistent\n// `agent-sequencer-runner` pane, leaving the agent idle).\nconst SINGLETON_AGENT_IDS = new Set(['flywheel-orchestrator', 'sequencer-runner']);\n\n/** Normalize agent ID: preserve known prefixes, add 'agent-' for bare issue IDs */\nexport function normalizeAgentId(agentId: string): string {\n if (SINGLETON_AGENT_IDS.has(agentId)) return agentId;\n if (AGENT_PREFIXES.some(p => agentId.startsWith(p))) {\n return agentId;\n }\n return `agent-${agentId.toLowerCase()}`;\n}\n\n/** True when the input is already a fully-qualified agent ID (known prefix or singleton), not an issue ID. */\nexport function isQualifiedAgentId(input: string): boolean {\n const lower = input.toLowerCase();\n return SINGLETON_AGENT_IDS.has(lower) || AGENT_PREFIXES.some(p => lower.startsWith(p));\n}\n\nfunction agentStateExistsSync(agentId: string): boolean {\n return Boolean(getOverdeckAgentStateSync(agentId)) || existsSync(getRollbackAgentStatePath(agentId));\n}\n\n/**\n * Resolve a CLI-supplied agent target to an on-disk agent ID (PAN-1760).\n * Accepts bare numerics (\"1148\"), issue IDs (\"PAN-1148\"), and fully-qualified\n * agent IDs (\"agent-pan-1148-ship\", \"strike-pan-1723\", \"inspect-pan-1744-x\",\n * \"flywheel-orchestrator\"). For issue IDs, prefers the canonical work-agent\n * directory when present, then falls back to the single registered agent state\n * for that issue. If no single fallback exists, preserves the historical\n * canonical agent-* target.\n */\nexport function resolveAgentTargetSync(input: string): string | null {\n if (isQualifiedAgentId(input)) return input.toLowerCase();\n const issueId = resolveBareNumericIdSync(input);\n if (!issueId) return null;\n\n const canonicalAgentId = normalizeAgentId(issueId);\n if (agentStateExistsSync(canonicalAgentId)) return canonicalAgentId;\n\n try {\n const wantedIssueId = issueId.toUpperCase();\n const matches = listOverdeckAgentStatesSync()\n .filter((agent) => agent.issueId.toUpperCase() === wantedIssueId)\n .map((agent) => agent.id);\n if (matches.length === 1) return matches[0].toLowerCase();\n return canonicalAgentId;\n } catch {\n return canonicalAgentId;\n }\n}\n\n/**\n * Get path to agent's ready signal file (written by SessionStart hook)\n */\nfunction getReadySignalPath(agentId: string): string {\n return join(getOverdeckHome(), 'agents', agentId, 'ready.json');\n}\n\n/**\n * Clear ready signal before spawning (clean slate)\n */\nexport function clearReadySignal(agentId: string): void {\n const readyPath = getReadySignalPath(agentId);\n if (existsSync(readyPath)) {\n try {\n unlinkSync(readyPath);\n } catch {\n // Ignore errors - non-critical\n }\n }\n}\n\nfunction isReadySignalPresent(readyPath: string): boolean {\n if (!existsSync(readyPath)) return false;\n try {\n const signal = JSON.parse(readFileSync(readyPath, 'utf-8'));\n // Accept both the Claude hook shape ({ ready: true, ... }) and the Pi\n // extension shape ({ agentId, sessionId, ... } with no `ready` field).\n return Boolean(signal && typeof signal === 'object' && signal.ready !== false);\n } catch {\n // File exists but mid-write / invalid — keep waiting.\n return false;\n }\n}\n\nexport async function waitForReadySignal(agentId: string, timeoutSeconds = 30): Promise<boolean> {\n const readyPath = getReadySignalPath(agentId);\n\n for (let i = 0; i < timeoutSeconds; i++) {\n if (isReadySignalPresent(readyPath)) return true;\n await new Promise(resolve => setTimeout(resolve, 1000)); // Non-blocking sleep\n }\n\n return isReadySignalPresent(readyPath);\n}\n\n/**\n * Wait until a hook-instrumented agent reports it is idle at the prompt, via the\n * runtime mirror (Stop / SessionStart hook → activity 'idle'), or the timeout\n * elapses. Returns true if idle was observed.\n *\n * PAN-1594/1596: this is the hook-derived \"is the agent idle right now\" check.\n * It replaced the tmux pane-scrape `waitForClaudePrompt` (since removed). Works\n * for any hook-instrumented session — agents AND conversations (`conv-*`), which\n * feed the runtime mirror once their heartbeat POSTs authenticate (PAN-1596). No\n * dependency on tmux output or permission mode.\n *\n * Distinct from waitForReadySignal: that answers the one-time \"has this\n * (re)launched session reached the prompt\" (ready.json gate, used by the\n * conversation reattach/fork paths); this answers \"is the running agent idle at\n * the prompt right now\".\n */\nexport async function waitForAgentIdle(agentId: string, timeoutMs = 5000): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n do {\n if (getAgentRuntimeStateSync(agentId)?.state === 'idle') return true;\n await new Promise(r => setTimeout(r, 250));\n } while (Date.now() < deadline);\n return getAgentRuntimeStateSync(agentId)?.state === 'idle';\n}\n","import { getProjectSync, resolveProjectFromIssueSync } from '../projects.js';\n\nexport interface AutoResumeConfig {\n maxConsecutiveFailures: number;\n troubledWindowMs: number;\n failureBackoffSchedule: number[];\n}\n\nexport const DEFAULT_AUTO_RESUME_CONFIG: AutoResumeConfig = {\n maxConsecutiveFailures: 3,\n troubledWindowMs: 10 * 60 * 1000,\n failureBackoffSchedule: [5, 30, 120],\n};\n\nfunction positiveNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value) && value > 0;\n}\n\nfunction validBackoffSchedule(value: unknown): value is number[] {\n return Array.isArray(value)\n && value.length > 0\n && value.every((entry) => typeof entry === 'number' && Number.isFinite(entry) && entry >= 0);\n}\n\nexport function normalizeAutoResumeConfig(\n override: Partial<AutoResumeConfig> | undefined,\n source: string,\n): AutoResumeConfig {\n if (override === undefined) return DEFAULT_AUTO_RESUME_CONFIG;\n\n const config: AutoResumeConfig = { ...DEFAULT_AUTO_RESUME_CONFIG };\n if (override.maxConsecutiveFailures !== undefined) {\n if (positiveNumber(override.maxConsecutiveFailures)) {\n config.maxConsecutiveFailures = override.maxConsecutiveFailures;\n } else {\n console.warn(`[auto-resume] Invalid ${source}.maxConsecutiveFailures; using default ${DEFAULT_AUTO_RESUME_CONFIG.maxConsecutiveFailures}`);\n }\n }\n\n if (override.troubledWindowMs !== undefined) {\n if (positiveNumber(override.troubledWindowMs)) {\n config.troubledWindowMs = override.troubledWindowMs;\n } else {\n console.warn(`[auto-resume] Invalid ${source}.troubledWindowMs; using default ${DEFAULT_AUTO_RESUME_CONFIG.troubledWindowMs}`);\n }\n }\n\n if (override.failureBackoffSchedule !== undefined) {\n if (validBackoffSchedule(override.failureBackoffSchedule)) {\n config.failureBackoffSchedule = override.failureBackoffSchedule;\n } else {\n console.warn(`[auto-resume] Invalid ${source}.failureBackoffSchedule; using default ${DEFAULT_AUTO_RESUME_CONFIG.failureBackoffSchedule.join(',')}`);\n }\n }\n\n return config;\n}\n\nexport function resolveAutoResumeConfigForIssue(issueId: string | undefined): AutoResumeConfig {\n if (!issueId) return DEFAULT_AUTO_RESUME_CONFIG;\n\n const resolved = resolveProjectFromIssueSync(issueId);\n if (!resolved) return DEFAULT_AUTO_RESUME_CONFIG;\n\n const project = getProjectSync(resolved.projectKey);\n return normalizeAutoResumeConfig(project?.autoResume, `projects.${resolved.projectKey}.autoResume`);\n}\n","import type { RuntimeName } from '../runtimes/types.js';\nimport {\n readIssueRecordSync,\n resolveProjectForIssue,\n writeAgentHarnessModelSync,\n} from '../pan-dir/record.js';\n\nexport function readAgentHarnessModelRecordSync(issueId: string): {\n harness?: RuntimeName;\n model?: string;\n} | null {\n const project = resolveProjectForIssue(issueId);\n if (!project) return null;\n const record = readIssueRecordSync(project, issueId);\n return {\n harness: record?.harness,\n model: record?.model,\n };\n}\n\nexport function writeAgentHarnessModelRecordSync(\n issueId: string,\n harness: RuntimeName,\n model: string,\n): void {\n const project = resolveProjectForIssue(issueId);\n if (!project) return;\n writeAgentHarnessModelSync(project, issueId, harness, model);\n}\n\n","/**\n * Append-only persistent logger for forensic audit trails.\n *\n * Complements the SQLite event store (activity-logger.ts) with flat-file logs\n * that survive event-store resets and are greppable from the shell.\n *\n * Files:\n * ~/.overdeck/logs/deacon.log — deacon startup recovery actions\n * ~/.overdeck/agents/<id>/lifecycle.log — per-agent state transitions\n */\n\nimport { appendFileSync, mkdirSync } from 'fs';\nimport { appendFile, mkdir } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { getOverdeckHome } from './paths.js';\n\nfunction logsDir(): string {\n return join(getOverdeckHome(), 'logs');\n}\n\nfunction agentDir(agentId: string): string {\n return join(getOverdeckHome(), 'agents', agentId);\n}\n\nfunction ensureLogsDir(): void {\n try {\n mkdirSync(logsDir(), { recursive: true });\n } catch { /* non-fatal */ }\n}\n\nfunction ensureAgentDir(agentId: string): void {\n try {\n mkdirSync(agentDir(agentId), { recursive: true });\n } catch { /* non-fatal */ }\n}\n\nfunction timestamp(): string {\n return new Date().toISOString();\n}\n\n/** Append a line to ~/.overdeck/logs/deacon.log */\nexport function logDeaconEventSync(message: string): void {\n ensureLogsDir();\n try {\n appendFileSync(join(logsDir(), 'deacon.log'), `[${timestamp()}] ${message}\\n`);\n } catch {\n // Non-fatal — logging must never break recovery logic\n }\n}\n\n/** Append a line to ~/.overdeck/agents/<agentId>/lifecycle.log */\nexport function logAgentLifecycleSync(agentId: string, message: string): void {\n ensureAgentDir(agentId);\n try {\n appendFileSync(\n join(agentDir(agentId), 'lifecycle.log'),\n `[${timestamp()}] ${message}\\n`,\n );\n } catch {\n // Non-fatal\n }\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n//\n// Logging must NEVER break recovery logic, so both Effect variants swallow all\n// errors and return Effect<void, never> — they never fail the parent Effect.\n\n/** Effect variant of {@link logDeaconEventSync}. Failures are swallowed silently. */\nexport const logDeaconEvent = (message: string): Effect.Effect<void, never> =>\n Effect.promise(async () => {\n try {\n const dir = logsDir();\n await mkdir(dir, { recursive: true });\n await appendFile(join(dir, 'deacon.log'), `[${timestamp()}] ${message}\\n`);\n } catch {\n // Non-fatal\n }\n });\n\n/** Effect variant of {@link logAgentLifecycleSync}. Failures are swallowed silently. */\nexport const logAgentLifecycle = (agentId: string, message: string): Effect.Effect<void, never> =>\n Effect.promise(async () => {\n try {\n const dir = agentDir(agentId);\n await mkdir(dir, { recursive: true });\n await appendFile(\n join(dir, 'lifecycle.log'),\n `[${timestamp()}] ${message}\\n`,\n );\n } catch {\n // Non-fatal\n }\n });\n","import { readdir, rm, writeFile as writeFileAsync, mkdir as mkdirAsync } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { AGENTS_DIR, getOverdeckHome } from '../paths.js';\nimport { FsError } from '../errors.js';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { resolveAutoResumeConfigForIssue } from '../cloister/auto-resume-config.js';\nimport { getRollbackAgentStatePath, readRollbackAgentStateSync, writeRollbackAgentStateSync } from '../overdeck/agent-rollback-state.js';\nimport { getOverdeckAgentStateSync, saveOverdeckAgentStateSync } from '../overdeck/agent-state-sync.js';\nimport { readAgentHarnessModelRecordSync, writeAgentHarnessModelRecordSync } from '../overdeck/agent-record-sync.js';\nimport { logAgentLifecycleSync } from '../persistent-logger.js';\nimport { recordFeatureRegistryLifecycle } from '../registry/feature-registry-population.js';\nimport { normalizeAgentId } from './identity.js';\n\nexport type Role = 'plan' | 'work' | 'review' | 'test' | 'ship' | 'flywheel' | 'strike' | 'sequencer' | 'knowledge';\n\nexport const SESSION_EXITED_BEFORE_KICKOFF = 'session-exited-before-kickoff';\n\nexport interface AgentState {\n id: string;\n issueId: string;\n workspace: string;\n /** Coding-agent harness this agent runs under (PAN-636). */\n harness?: RuntimeName;\n /** Unified role primitive (PAN-1048). */\n role: Role;\n model: string;\n /**\n * The exact spawn key fed to the weighted-distribution model picker at spawn\n * (`${role}:${issueId}`), persisted so the dashboard MODEL inspector (PAN-2053)\n * can show the faithful FNV-1a derivation without re-guessing the key's form.\n * Undefined for scalar-role agents and for agents spawned before PAN-2053.\n */\n modelSpawnKey?: string;\n status: 'starting' | 'running' | 'stopped' | 'error';\n startedAt: string;\n lastActivity?: string;\n lastResumeAt?: string;\n /**\n * Tri-state kickoff delivery signal for work-agent lifecycle monitoring:\n * undefined = legacy/pre-feature agent or non-applicable role;\n * false = spawned but kickoff delivery not yet confirmed;\n * true = kickoff delivery confirmed.\n */\n kickoffDelivered?: boolean;\n stoppedAt?: string;\n /** True when markAgentStopped was called (user-initiated stop). Cleared on\n * resume. Read by deacon's autoResumeStoppedWorkAgents to distinguish a\n * deliberate stop from a crash/orphan. */\n stoppedByUser?: boolean;\n stoppedByPause?: boolean;\n paused?: boolean;\n pausedReason?: string;\n pausedAt?: string;\n /**\n * PAN-2507: true when this work agent was paused by the preemptive scheduler\n * (yielded to free capacity for an advancing dispatch), as distinct from an\n * operator pause. Reuses `paused: true` so every existing no-resume gate\n * protects the yielded agent; this flag lets the deacon resume yielded agents\n * oldest-first and lets `pan unpause` self-clear the yield attribution.\n */\n yieldedByScheduler?: boolean;\n /** PAN-2507: ISO timestamp of the yield (oldest-first resume ordering). */\n yieldedAt?: string;\n /**\n * PAN-2507: ISO timestamp of the most recent resume-from-yield. Enforces the\n * re-yield cooldown (an agent just resumed from a yield may not be re-yielded\n * until `yield_cooldown_secs` elapse). Survives unpause (it is a cooldown\n * tracker, not a pause field).\n */\n lastYieldResumeAt?: string;\n troubled?: boolean;\n troubledAt?: string;\n consecutiveFailures?: number;\n firstFailureInRunAt?: string;\n lastFailureAt?: string;\n lastFailureReason?: string;\n lastFailureNextRetryAt?: string;\n branch?: string; // Git branch name for this agent\n costSoFar?: number;\n sessionId?: string; // For resuming sessions after handoff\n\n // Work type system (PAN-118)\n phase?: 'exploration' | 'implementation' | 'testing' | 'documentation' | 'review-response' | 'planning' | 'synthesis';\n workType?: string; // Current work type ID\n\n /**\n * Whether this work agent was launched with the experimental Claude Code\n * Channels prompt-delivery path enabled. Set at launch time after the\n * eligibility check; never mutated after. Read by deliverAgentMessage to\n * decide whether to attempt the bridge socket before falling back to\n * sendKeysAsync. Absent or false means tmux-only delivery (current default).\n */\n channelsEnabled?: boolean;\n /** True when this work agent was launched through the PTY supervisor wrapper. */\n supervisorEnabled?: boolean;\n /**\n * Delivery method for agent messages. 'auto' tries supervisor, then channels,\n * then tmux; explicit socket methods are strict (throw on failure); 'tmux'\n * bypasses socket transports entirely.\n */\n deliveryMethod?: 'auto' | 'supervisor' | 'channels' | 'tmux';\n\n /**\n * Short HEAD sha (8 chars) of the workspace at the moment this role run was\n * spawned. Used by the reactive scheduler's activeRoleRunExists() to detect a\n * stale/zombie role session: if the workspace HEAD has advanced past this\n * marker, the existing session ran against old code and must not block a\n * fresh re-dispatch for the new HEAD. Set for non-work roles in spawnRun.\n */\n roleRunHead?: string;\n\n /** Flywheel run that spawned this agent, if any. Absent for operator-started agents (PAN-1812). */\n flywheelRunId?: string;\n\n /** True when a planning session was launched with `pan plan --auto`. */\n auto?: boolean;\n\n /** Review-convoy metadata for server-side reviewer lifecycle monitoring. */\n reviewSubRole?: string;\n reviewRunId?: string;\n reviewOutputPath?: string;\n reviewSynthesisAgentId?: string;\n reviewDeadlineAt?: string;\n reviewMonitorSignaled?: 'ready' | 'failed' | 'timeout';\n /** Number of times Deacon has respawned this convoy reviewer (PAN-1806). */\n reviewRetryAttempt?: number;\n /** PAN-1862 Phase A (synthesis parent): discovery→fork orchestration state. */\n reviewDiscoveryPending?: boolean;\n reviewContextManifestPath?: string;\n reviewDiscoveryReadyAt?: string;\n reviewConvoyForkedAt?: string;\n reviewForkCacheChecked?: boolean;\n /** PAN-1862 Phase A (convoy reviewer): session was forked from the parent's discovery session. */\n reviewForkedFromParent?: boolean;\n hostOverride?: boolean;\n\n /** Inspect sub-role for inspect-* agents (PAN-1834). */\n inspectSubRole?: string;\n\n /** Registered swarm slot index for per-item work agents. */\n slotIndex?: number;\n /** vBRIEF item id explicitly assigned to this registered swarm slot. */\n slotItemId?: string;\n}\n\nconst toAgentFsError = (operation: string, path: string, cause: unknown): FsError =>\n new FsError({ operation, path, cause });\n\nexport function getAgentDir(agentId: string): string {\n return join(getOverdeckHome(), 'agents', agentId);\n}\n\nexport function getAgentStateFilePath(agentId: string): string {\n return getRollbackAgentStatePath(agentId);\n}\n\n/**\n * PAN-1985: wipe agent state directories for an issue, optionally scoped to\n * a role prefix. Used by the restart-fresh and review-restart paths to clean\n * state before respawning — the new agent then reads `.pan/continue.json`,\n * the vBRIEF, the beads, and the branch to pick up where the prior run left\n * off. Refuses to run against an empty or unsafe id.\n *\n * - `rolePrefix` omitted: wipes only the work agent dir (`agent-<id>`).\n * Specialist dirs (review, test, ship, etc.) are left alone.\n * - `rolePrefix` set (e.g. 'review'): wipes the role parent dir\n * (`agent-<id>-<prefix>`) AND any sub-roles (`agent-<id>-<prefix>-<anything>`),\n * leaving the work agent dir alone.\n *\n * Refuses to operate on the root `AGENTS_DIR` itself or on paths that escape\n * it; the `validateAgentId` guard below enforces a conservative id grammar.\n */\nexport async function wipeAgentStateDirs(\n issueId: string,\n opts: { rolePrefix?: string } = {},\n): Promise<{ removed: string[]; path: string }> {\n if (!issueId || !/^[A-Za-z]+-\\d+$/.test(issueId)) {\n throw new Error(`wipeAgentStateDirs: invalid issueId \"${issueId}\"`);\n }\n if (opts.rolePrefix !== undefined && !/^[a-z][a-z0-9-]*$/.test(opts.rolePrefix)) {\n throw new Error(`wipeAgentStateDirs: invalid rolePrefix \"${opts.rolePrefix}\"`);\n }\n const issueLower = issueId.toLowerCase();\n const dirPath = join(AGENTS_DIR, `agent-${issueLower}${opts.rolePrefix ? `-${opts.rolePrefix}` : ''}`);\n let entries: string[];\n try {\n entries = await readdir(AGENTS_DIR);\n } catch {\n return { removed: [], path: dirPath };\n }\n let targets: string[];\n if (opts.rolePrefix) {\n const base = `agent-${issueLower}-${opts.rolePrefix}`;\n targets = entries.filter((name) => name === base || name.startsWith(`${base}-`));\n } else {\n const work = `agent-${issueLower}`;\n targets = entries.filter((name) => name === work);\n }\n for (const name of targets) {\n try {\n await rm(join(AGENTS_DIR, name), { recursive: true, force: true });\n } catch { /* non-fatal — best-effort wipe */ }\n }\n return { removed: targets, path: dirPath };\n}\n\nexport function isRole(value: unknown): value is Role {\n return value === 'plan' || value === 'work' || value === 'review' || value === 'test' || value === 'ship' || value === 'flywheel' || value === 'strike' || value === 'sequencer' || value === 'knowledge';\n}\n\nfunction cleanAgentState(raw: AgentState): AgentState {\n return {\n id: raw.id,\n issueId: raw.issueId,\n workspace: raw.workspace,\n harness: raw.harness,\n role: raw.role,\n model: raw.model,\n status: raw.status,\n startedAt: raw.startedAt,\n lastActivity: raw.lastActivity,\n lastResumeAt: raw.lastResumeAt,\n kickoffDelivered: raw.kickoffDelivered,\n stoppedAt: raw.stoppedAt,\n stoppedByUser: raw.stoppedByUser,\n stoppedByPause: raw.stoppedByPause,\n paused: raw.paused,\n pausedReason: raw.pausedReason,\n pausedAt: raw.pausedAt,\n yieldedByScheduler: raw.yieldedByScheduler,\n yieldedAt: raw.yieldedAt,\n lastYieldResumeAt: raw.lastYieldResumeAt,\n troubled: raw.troubled,\n troubledAt: raw.troubledAt,\n consecutiveFailures: raw.consecutiveFailures,\n firstFailureInRunAt: raw.firstFailureInRunAt,\n lastFailureAt: raw.lastFailureAt,\n lastFailureReason: raw.lastFailureReason,\n lastFailureNextRetryAt: raw.lastFailureNextRetryAt,\n branch: raw.branch,\n costSoFar: raw.costSoFar,\n sessionId: raw.sessionId,\n roleRunHead: raw.roleRunHead,\n channelsEnabled: raw.channelsEnabled,\n supervisorEnabled: raw.supervisorEnabled,\n deliveryMethod: raw.deliveryMethod,\n reviewSubRole: raw.reviewSubRole,\n reviewRunId: raw.reviewRunId,\n reviewOutputPath: raw.reviewOutputPath,\n reviewSynthesisAgentId: raw.reviewSynthesisAgentId,\n reviewDeadlineAt: raw.reviewDeadlineAt,\n reviewMonitorSignaled: raw.reviewMonitorSignaled,\n reviewRetryAttempt: raw.reviewRetryAttempt,\n reviewDiscoveryPending: raw.reviewDiscoveryPending,\n reviewContextManifestPath: raw.reviewContextManifestPath,\n reviewDiscoveryReadyAt: raw.reviewDiscoveryReadyAt,\n reviewConvoyForkedAt: raw.reviewConvoyForkedAt,\n reviewForkCacheChecked: raw.reviewForkCacheChecked,\n reviewForkedFromParent: raw.reviewForkedFromParent,\n hostOverride: raw.hostOverride,\n inspectSubRole: raw.inspectSubRole,\n slotIndex: raw.slotIndex,\n slotItemId: raw.slotItemId,\n };\n}\n\nfunction parseAgentState(content: string, normalizedId: string): AgentState | null {\n try {\n const state = JSON.parse(content) as Partial<AgentState>;\n if (!isRole(state.role)) {\n // Roleless states are invisible to getAgentState; cleanup is handled\n // by warnOnBareNumericIssueIds / dropLegacyAgentStatesMissingRoleAsync.\n return null;\n }\n if (!state.id) state.id = normalizedId;\n return cleanAgentState(state as AgentState);\n } catch {\n return null;\n }\n}\n\nexport function getAgentStateSync(agentId: string): AgentState | null {\n const normalizedId = normalizeAgentId(agentId);\n\n const overdeckState = getOverdeckAgentStateSync(normalizedId);\n if (overdeckState) return cleanAgentState(overdeckState);\n\n const state = readRollbackAgentStateSync(normalizedId, parseAgentState);\n if (!state) return null;\n\n // PAN-1919: harness/model are no longer sourced from state.json. Merge from\n // the per-issue git-tracked record so cross-machine pickup works.\n if (state.issueId) {\n const record = readAgentHarnessModelRecordSync(state.issueId);\n if (record?.harness) state.harness = record.harness;\n if (record?.model) state.model = record.model;\n }\n\n return state;\n}\n\nexport const getAgentState = (agentId: string): Effect.Effect<AgentState | null, FsError> => {\n return Effect.try({\n try: () => getAgentStateSync(agentId),\n catch: (cause) => toAgentFsError('read', `agents-db:${agentId}`, cause),\n });\n};\n\nfunction prepareAgentStateForSave(state: AgentState): AgentState {\n if (state.status === 'running' || state.status === 'starting') {\n delete state.stoppedAt;\n } else if (state.status === 'stopped' && !state.stoppedAt) {\n state.stoppedAt = new Date().toISOString();\n }\n return state;\n}\n\nexport function writeAgentStateJsonSync(state: AgentState): void {\n writeRollbackAgentStateSync(state, (clean) => JSON.stringify(cleanAgentState(clean), null, 2));\n}\n\nexport function saveAgentStateSync(state: AgentState): void {\n // Detect status transition for audit trail\n const oldState = getAgentStateSync(state.id);\n const oldStatus = oldState?.status;\n\n prepareAgentStateForSave(state);\n\n saveOverdeckAgentStateSync(state);\n writeAgentStateJsonSync(state);\n\n // PAN-1919: mirror harness/model into the per-issue git-tracked record so\n // they travel with the branch. Done synchronously at save time; auto-commit\n // is suppressed here because spawn paths explicitly queue the commit.\n if (state.issueId && state.harness && state.model) {\n try {\n writeAgentHarnessModelRecordSync(state.issueId, state.harness, state.model);\n } catch (err) {\n console.warn(`[agents] Failed to mirror harness/model to record for ${state.issueId}: ${(err as Error).message}`);\n }\n }\n\n if (oldStatus && oldStatus !== state.status) {\n logAgentLifecycleSync(state.id, `status changed: ${oldStatus} → ${state.status} (saveAgentState)`);\n }\n}\n\nexport const saveAgentState = (state: AgentState): Effect.Effect<void, FsError> => {\n const dir = getAgentDir(state.id);\n const stateFile = getRollbackAgentStatePath(state.id);\n\n return Effect.gen(function* () {\n yield* Effect.tryPromise({\n try: () => mkdirAsync(dir, { recursive: true }),\n catch: (cause) => toAgentFsError('mkdir', dir, cause),\n });\n\n const oldState = yield* getAgentState(state.id);\n const oldStatus = oldState?.status;\n\n if (state.status === 'running' || state.status === 'starting') {\n delete state.stoppedAt;\n } else if (state.status === 'stopped' && !state.stoppedAt) {\n state.stoppedAt = new Date().toISOString();\n }\n\n yield* Effect.try({\n try: () => saveOverdeckAgentStateSync(state),\n catch: (cause) => toAgentFsError('write', `agents-db:${state.id}`, cause),\n });\n\n yield* Effect.tryPromise({\n try: () => writeFileAsync(stateFile, JSON.stringify(cleanAgentState(state), null, 2)),\n catch: (cause) => toAgentFsError('write', stateFile, cause),\n });\n recordFeatureRegistryAgentState(state);\n\n // PAN-1919: mirror harness/model into the per-issue git-tracked record.\n if (state.harness && state.model) {\n yield* Effect.try({\n try: () => writeAgentHarnessModelRecordSync(state.issueId, state.harness!, state.model!),\n catch: (cause) => toAgentFsError('write', `record:${state.issueId}`, cause),\n });\n }\n\n if (oldStatus && oldStatus !== state.status) {\n logAgentLifecycleSync(state.id, `status changed: ${oldStatus} → ${state.status} (saveAgentStateProgram)`);\n }\n });\n};\n\nfunction recordFeatureRegistryAgentState(state: AgentState): void {\n const status = state.status === 'starting' || state.status === 'running' ? 'active' : 'deferred';\n void recordFeatureRegistryLifecycle({\n issueId: state.issueId,\n workspacePath: state.workspace,\n agentId: state.id,\n status,\n });\n}\n\nfunction clearFailureTrackingFields(state: AgentState): void {\n state.consecutiveFailures = 0;\n delete state.firstFailureInRunAt;\n delete state.lastFailureAt;\n delete state.lastFailureReason;\n delete state.lastFailureNextRetryAt;\n}\n\n/**\n * Marker prefix used by the flywheel orchestrator when pausing an agent solely\n * to free a governor work slot. Pauses for this reason must never leave the\n * agent troubled (PAN-1812).\n */\nexport const GOVERNOR_SLOT_PAUSE_REASON_PREFIX = '[governor-slot]';\n\nfunction isGovernorSlotPauseReason(reason: string | undefined): boolean {\n return reason !== undefined && reason.startsWith(GOVERNOR_SLOT_PAUSE_REASON_PREFIX);\n}\n\n/** Sets the persistent manual pause gate used before stopping or suppressing resume. */\nfunction applyAgentPaused(state: AgentState, reason?: string, stoppedByPause = false): void {\n if (!state.paused) {\n state.pausedAt = new Date().toISOString();\n }\n state.paused = true;\n if (stoppedByPause) {\n state.stoppedByPause = true;\n }\n if (reason === undefined) {\n delete state.pausedReason;\n } else {\n state.pausedReason = reason;\n }\n\n // PAN-1812: a governor slot pause is a resource-hygiene action, not a fault.\n // Clear any existing troubled gate so the agent remains resumable when a slot\n // frees.\n if (isGovernorSlotPauseReason(reason)) {\n delete state.troubled;\n delete state.troubledAt;\n }\n}\n\n/** Sets the persistent manual pause gate used before stopping or suppressing resume. */\nexport function setAgentPausedSync(agentId: string, reason?: string, stoppedByPause = false): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n\n applyAgentPaused(state, reason, stoppedByPause);\n saveAgentStateSync(state);\n return true;\n}\n\nexport const setAgentPaused = (\n agentId: string,\n reason?: string,\n stoppedByPause = false,\n): Effect.Effect<AgentState | null, FsError> =>\n Effect.gen(function* () {\n const state = yield* getAgentState(agentId);\n if (!state) return null;\n\n applyAgentPaused(state, reason, stoppedByPause);\n yield* saveAgentState(state);\n return state;\n });\n\n/**\n * PAN-2507: pause an agent as a scheduler YIELD (distinct from an operator\n * pause). Reuses the pause write path so every no-resume gate protects the\n * yielded agent, and stamps the yield attribution used for oldest-first resume.\n */\nfunction applyAgentYielded(state: AgentState, reason: string): void {\n applyAgentPaused(state, reason, true);\n state.yieldedByScheduler = true;\n state.yieldedAt = new Date().toISOString();\n}\n\n/** PAN-2507: set the yield gate (pause + scheduler attribution) in one write. */\nexport function setAgentYieldedSync(agentId: string, reason: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n applyAgentYielded(state, reason);\n saveAgentStateSync(state);\n return true;\n}\n\n/**\n * PAN-2507: clear a scheduler yield ahead of a resume — drops the pause + yield\n * attribution (so `resumeAgent`'s pause gate passes) and stamps\n * `lastYieldResumeAt` to arm the re-yield cooldown, in a single write.\n */\nexport function clearYieldForResumeSync(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n applyAgentUnpaused(state);\n state.lastYieldResumeAt = new Date().toISOString();\n saveAgentStateSync(state);\n return true;\n}\n\nfunction applyAgentUnpaused(state: AgentState): void {\n if (state.stoppedByPause === true) {\n delete state.stoppedByUser;\n }\n delete state.stoppedByPause;\n delete state.paused;\n delete state.pausedReason;\n delete state.pausedAt;\n // PAN-2507 (FR-5): clearing a pause also clears the scheduler-yield\n // attribution, so an operator `pan unpause` on a yielded agent self-clears\n // the yield. `lastYieldResumeAt` is deliberately preserved — it is a\n // re-yield cooldown tracker, not a pause field.\n delete state.yieldedByScheduler;\n delete state.yieldedAt;\n}\n\nfunction isAgentPauseClear(state: AgentState): boolean {\n return !state.paused && state.pausedReason === undefined && state.pausedAt === undefined\n && state.yieldedByScheduler === undefined && state.yieldedAt === undefined;\n}\n\n/** Clears the persistent manual pause gate without spawning the agent. */\nexport function clearAgentPausedSync(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n if (isAgentPauseClear(state)) return true;\n\n applyAgentUnpaused(state);\n saveAgentStateSync(state);\n return true;\n}\n\nexport const clearAgentPaused = (agentId: string): Effect.Effect<AgentState | null, FsError> =>\n Effect.gen(function* () {\n const state = yield* getAgentState(agentId);\n if (!state) return null;\n if (isAgentPauseClear(state)) return state;\n\n applyAgentUnpaused(state);\n yield* saveAgentState(state);\n return state;\n });\n\n/** Marks an agent as troubled after repeated resume failures. */\nexport function markAgentTroubled(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n\n if (!state.troubled) {\n state.troubledAt = new Date().toISOString();\n }\n state.troubled = true;\n saveAgentStateSync(state);\n return true;\n}\n\nfunction isAgentTroubledClear(state: AgentState): boolean {\n return !state.troubled && state.troubledAt === undefined && (state.consecutiveFailures ?? 0) === 0 && state.firstFailureInRunAt === undefined && state.lastFailureAt === undefined && state.lastFailureReason === undefined && state.lastFailureNextRetryAt === undefined;\n}\n\nfunction applyAgentUntroubled(state: AgentState): void {\n delete state.troubled;\n delete state.troubledAt;\n clearFailureTrackingFields(state);\n}\n\n/** Clears the troubled gate and its accumulated failure state. */\nexport function clearAgentTroubledSync(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n if (isAgentTroubledClear(state)) return true;\n\n applyAgentUntroubled(state);\n saveAgentStateSync(state);\n return true;\n}\n\nexport const clearAgentTroubled = (agentId: string): Effect.Effect<AgentState | null, FsError> =>\n Effect.gen(function* () {\n const state = yield* getAgentState(agentId);\n if (!state) return null;\n if (isAgentTroubledClear(state)) return state;\n\n applyAgentUntroubled(state);\n yield* saveAgentState(state);\n return state;\n });\n\nfunction applyAgentFailure(state: AgentState, reason: string): void {\n const config = resolveAutoResumeConfigForIssue(state.issueId);\n const nowMs = Date.now();\n const now = new Date(nowMs).toISOString();\n const firstFailureMs = Date.parse(state.firstFailureInRunAt ?? '');\n const hasValidFirstFailure = Number.isFinite(firstFailureMs);\n const windowElapsed = hasValidFirstFailure\n && nowMs - firstFailureMs > config.troubledWindowMs;\n\n if (windowElapsed || !hasValidFirstFailure) {\n state.consecutiveFailures = 1;\n state.firstFailureInRunAt = now;\n } else {\n state.consecutiveFailures = (state.consecutiveFailures ?? 0) + 1;\n }\n\n const backoffSeconds = config.failureBackoffSchedule[\n Math.min(state.consecutiveFailures - 1, config.failureBackoffSchedule.length - 1)\n ];\n state.lastFailureAt = now;\n state.lastFailureReason = reason;\n state.lastFailureNextRetryAt = new Date(nowMs + backoffSeconds * 1000).toISOString();\n\n const firstFailureInRunMs = Date.parse(state.firstFailureInRunAt ?? '');\n const shouldMarkTroubled = state.consecutiveFailures >= config.maxConsecutiveFailures\n && Number.isFinite(firstFailureInRunMs)\n && nowMs - firstFailureInRunMs <= config.troubledWindowMs;\n\n if (shouldMarkTroubled) {\n if (!state.troubled) {\n state.troubledAt = now;\n }\n state.troubled = true;\n }\n}\n\n/** Records one failed resume/crash observation for later backoff and troubled gating. */\nexport function recordAgentFailureSync(agentId: string, reason: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n\n applyAgentFailure(state, reason);\n saveAgentStateSync(state);\n return true;\n}\n\nexport const recordAgentFailure = (agentId: string, reason: string): Effect.Effect<AgentState | null, FsError> =>\n Effect.gen(function* () {\n const state = yield* getAgentState(agentId);\n if (!state) return null;\n\n applyAgentFailure(state, reason);\n yield* saveAgentState(state);\n return state;\n });\n\n/** Resets failure tracking after an agent reaches running state. */\nexport function resetAgentFailureCount(agentId: string): boolean {\n const state = getAgentStateSync(agentId);\n if (!state) return false;\n if ((state.consecutiveFailures ?? 0) === 0 && state.firstFailureInRunAt === undefined && state.lastFailureAt === undefined && state.lastFailureReason === undefined && state.lastFailureNextRetryAt === undefined) return true;\n\n clearFailureTrackingFields(state);\n saveAgentStateSync(state);\n return true;\n}\n\n/** Reports whether callers should block start, resume, auto-resume, or message delivery on the manual pause gate. */\nexport function isAgentPaused(agentId: string): boolean {\n return getAgentStateSync(agentId)?.paused === true;\n}\n\n/** Reports whether callers should block start, resume, auto-resume, or message delivery on the troubled gate. */\nexport function isAgentTroubled(agentId: string): boolean {\n return getAgentStateSync(agentId)?.troubled === true;\n}\n\nexport async function recordStartupSessionExit(state: AgentState, issueId: string, source: Role | 'work-agent'): Promise<never> {\n await Effect.runPromise(recordAgentFailure(state.id, SESSION_EXITED_BEFORE_KICKOFF));\n const failedState = await Effect.runPromise(getAgentState(state.id));\n if (failedState) {\n failedState.status = 'stopped';\n failedState.stoppedAt = new Date().toISOString();\n failedState.kickoffDelivered = false;\n failedState.lastFailureReason = SESSION_EXITED_BEFORE_KICKOFF;\n await Effect.runPromise(saveAgentState(failedState));\n }\n state.status = 'stopped';\n state.stoppedAt = new Date().toISOString();\n state.kickoffDelivered = false;\n state.lastFailureReason = SESSION_EXITED_BEFORE_KICKOFF;\n emitActivityEntrySync({\n source,\n level: 'error',\n message: `${state.id}: session exited before kickoff could be delivered`,\n issueId,\n });\n throw new Error(`Agent ${state.id} exited before kickoff could be delivered`);\n}\n\nexport type ResumeGateBlock =\n | { gate: 'paused'; reason: string; pausedReason?: string; yieldedByScheduler?: boolean }\n | { gate: 'troubled'; reason: string; troubledAt?: string }\n | { gate: 'stopped-by-user'; reason: string }\n | { gate: 'failure-backoff'; reason: string; consecutiveFailures: number };\n\nexport type ResumeIntent = 'autonomous' | 'operator-start' | 'message-delivery';\n\nexport interface ResumeGateContext {\n hasCompletedHandoff?: boolean;\n owesRework?: boolean;\n}\n\nexport type ResumeGateDecision =\n | { decision: 'proceed'; clearStoppedByUser?: true; overrideFailureBackoff?: true; warning?: string }\n | { decision: 'block'; reason: string; needsYou?: true }\n | { decision: 'queue-message'; reason: string };\n\n/** Pure classification only. Call decideResumeGate before acting on the classified gate. */\nexport function getAgentResumeGateBlockReason(\n state: Pick<AgentState, 'paused' | 'pausedReason' | 'yieldedByScheduler' | 'troubled' | 'troubledAt' | 'stoppedByUser' | 'consecutiveFailures'>,\n): ResumeGateBlock | undefined {\n if (state.paused === true) {\n return {\n gate: 'paused',\n reason: state.pausedReason ? `agent is paused (${state.pausedReason})` : 'agent is paused',\n pausedReason: state.pausedReason,\n yieldedByScheduler: state.yieldedByScheduler,\n };\n }\n if (state.troubled === true) {\n const failures = state.consecutiveFailures ?? 0;\n return {\n gate: 'troubled',\n reason: `agent is troubled (${failures} failure${failures === 1 ? '' : 's'})`,\n troubledAt: state.troubledAt,\n };\n }\n if (state.stoppedByUser === true) return { gate: 'stopped-by-user', reason: 'agent was stopped by the operator' };\n const failures = state.consecutiveFailures ?? 0;\n if (failures > 0) return {\n gate: 'failure-backoff',\n reason: `agent is in failure backoff (${failures} failure${failures === 1 ? '' : 's'})`,\n consecutiveFailures: failures,\n };\n return undefined;\n}\n\n/** Intent-aware policy over a classified gate. This function never mutates agent state. */\nexport function decideResumeGate(\n block: ResumeGateBlock | undefined,\n intent: ResumeIntent,\n context: ResumeGateContext = {},\n): ResumeGateDecision {\n if (!block) return { decision: 'proceed' };\n if (intent === 'message-delivery') return { decision: 'queue-message', reason: block.reason };\n\n if (intent === 'operator-start') {\n if (block.gate === 'paused') return { decision: 'block', reason: `${block.reason}; run pan unpause first` };\n if (block.gate === 'troubled') return { decision: 'block', reason: `${block.reason}; run pan untroubled first` };\n if (block.gate === 'stopped-by-user') return { decision: 'proceed', clearStoppedByUser: true };\n return {\n decision: 'proceed',\n overrideFailureBackoff: true,\n warning: `Operator start overrides ${block.reason}`,\n };\n }\n\n if (block.gate === 'stopped-by-user') {\n if (context.hasCompletedHandoff === true && context.owesRework === true) {\n return { decision: 'proceed', clearStoppedByUser: true };\n }\n return { decision: 'block', reason: block.reason, needsYou: true };\n }\n return { decision: 'block', reason: block.reason };\n}\n\nfunction assertAgentCanTransitionToRunning(state: AgentState): void {\n const decision = decideResumeGate(getAgentResumeGateBlockReason(state), 'operator-start');\n if (decision.decision === 'block') {\n throw new Error(`Cannot run ${state.id}: ${decision.reason}. Clear the gate before resuming.`);\n }\n}\n\nexport function markAgentRunning(state: AgentState, options?: { preserveFailureTracking?: boolean }): void {\n assertAgentCanTransitionToRunning(state);\n const oldStatus = state.status;\n state.status = 'running';\n state.lastActivity = new Date().toISOString();\n if (options?.preserveFailureTracking !== true) {\n clearFailureTrackingFields(state);\n }\n delete state.stoppedAt;\n // Clear user-stop intent so a later crash/orphan can be auto-resumed. Without\n // this the flag is sticky across the stop→resume→crash sequence and autoResume\n // would permanently skip the agent on any subsequent orphan recovery.\n delete state.stoppedByUser;\n logAgentLifecycleSync(state.id, `status changed: ${oldStatus} → running (markAgentRunning)`);\n}\n\nfunction markAgentStopped(state: AgentState): void {\n const oldStatus = state.status;\n state.status = 'stopped';\n state.stoppedAt = new Date().toISOString();\n state.stoppedByUser = true;\n logAgentLifecycleSync(state.id, `status changed: ${oldStatus} → stopped (markAgentStopped, user-initiated)`);\n}\n\nexport function markAgentStoppedState(state: AgentState): AgentState {\n if (!state.id) {\n state.id = normalizeAgentId(state.issueId);\n }\n markAgentStopped(state);\n return state;\n}\n\nexport function markAgentRunningState(state: AgentState): AgentState {\n if (!state.id) {\n state.id = normalizeAgentId(state.issueId);\n }\n markAgentRunning(state);\n return state;\n}\n\n/** Test-only internals. Do not import outside of test files. */\nexport const __testInternals = { markAgentRunning, markAgentStopped };\n","import { existsSync } from 'fs';\nimport { readFile } from 'fs/promises';\nimport { homedir } from 'os';\nimport { dirname, join } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { isClaudeCodeChannelsMcpEnabled } from '../config-yaml.js';\nimport type { ModelId } from '../settings.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport { packageRoot } from '../paths.js';\nimport { getProviderForModelSync } from '../providers.js';\nimport { writePtyToken } from '../pty-token.js';\nimport { capturePane, sendRawKeystroke } from '../tmux.js';\nimport {\n getAgentDir,\n getAgentState,\n recordAgentFailure,\n saveAgentState,\n saveAgentStateSync,\n type AgentState,\n type Role,\n} from './agent-state.js';\n\ninterface SupervisorChannelsSpawnOptions {\n issueId: string;\n workspace: string;\n role?: Role;\n model?: string;\n harness?: RuntimeName;\n allowHost?: boolean;\n}\n\nexport function buildDefaultResumeContinueMessage(issueId: string): string {\n return `You are resuming work on ${issueId}. Read .pan/continue.json for context and pick up where you left off — do not wait for further instructions.`;\n}\n\nexport async function buildResumeMessageForAgent(\n state: AgentState,\n fallbackMessage: string,\n callerMessage?: string,\n): Promise<{ message?: string; redeliveringKickoff: boolean; error?: string }> {\n if (state.role !== 'work' || state.kickoffDelivered !== false) {\n return { message: callerMessage ?? fallbackMessage, redeliveringKickoff: false };\n }\n\n const promptPath = join(getAgentDir(state.id), 'initial-prompt.md');\n try {\n const kickoffPrompt = await readFile(promptPath, 'utf-8');\n const suffix = callerMessage\n ? `\\n\\n---\\n\\nAdditional message delivered during resume:\\n\\n${callerMessage}`\n : '';\n return { message: `${kickoffPrompt}${suffix}`, redeliveringKickoff: true };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n return {\n redeliveringKickoff: true,\n error: `kickoff prompt missing at ${promptPath}: ${reason}`,\n };\n }\n}\n\nexport function markKickoffRedelivered(state: AgentState): void {\n state.kickoffDelivered = true;\n saveAgentStateSync(state);\n}\n\nexport async function recordKickoffDeliveryFailure(state: AgentState, issueId: string, source: Role | 'work-agent'): Promise<void> {\n await Effect.runPromise(recordAgentFailure(state.id, 'kickoff delivery failed'));\n const failedState = await Effect.runPromise(getAgentState(state.id));\n if (failedState) {\n failedState.status = 'running';\n failedState.kickoffDelivered = false;\n await Effect.runPromise(saveAgentState(failedState));\n }\n state.status = 'running';\n state.kickoffDelivered = false;\n emitActivityEntrySync({\n source,\n level: 'error',\n message: `${state.id}: kickoff delivery failed`,\n issueId,\n });\n}\n\ninterface ChannelsDecision {\n eligible: boolean;\n reason?: string;\n}\n\ninterface SupervisorDecision {\n eligible: boolean;\n reason?: string;\n}\n\nexport function decideSupervisorForWorkAgent(\n agentId: string,\n options: SupervisorChannelsSpawnOptions,\n state: AgentState,\n): SupervisorDecision {\n void options;\n const log = (eligible: boolean, reason?: string): void => {\n const tag = eligible ? 'supervisor:eligible' : `supervisor:ineligible:${reason ?? 'unknown'}`;\n console.log(`[${agentId}] ${tag}`);\n };\n\n if (state.role !== 'work') {\n log(false, 'not-a-work-agent');\n return { eligible: false, reason: 'not-a-work-agent' };\n }\n\n if (process.env.OVERDECK_DOCKER_WORKSPACE === '1' || process.env.PAN_DOCKER === '1') {\n log(false, 'docker-not-supported-yet');\n return { eligible: false, reason: 'docker-not-supported-yet' };\n }\n\n const behavior = state.harness ? getHarnessBehavior(state.harness) : null;\n if (!behavior?.supportsPtySupervisor) {\n const reason = `harness-${state.harness ?? 'unknown'}`;\n log(false, reason);\n return { eligible: false, reason };\n }\n\n log(true);\n return { eligible: true };\n}\n\nexport async function prepareSupervisorForFreshLaunch(\n agentId: string,\n options: SupervisorChannelsSpawnOptions,\n state: AgentState,\n): Promise<{ useSupervisor: boolean; supervisorScriptPath?: string }> {\n const supervisorDecision = decideSupervisorForWorkAgent(agentId, options, state);\n if (!supervisorDecision.eligible) {\n delete state.supervisorEnabled;\n return { useSupervisor: false };\n }\n\n const supervisorScriptPath = resolvePtySupervisorScriptPath();\n if (!existsSync(supervisorScriptPath)) {\n throw new Error('pty-supervisor build artifact missing — run `npm run build`.');\n }\n await writePtyToken(agentId);\n state.supervisorEnabled = true;\n return { useSupervisor: true, supervisorScriptPath };\n}\n\nexport async function prepareSupervisorForRelaunch(\n agentId: string,\n state: AgentState,\n model: string,\n harness: RuntimeName,\n): Promise<{ useSupervisor: boolean; supervisorScriptPath?: string }> {\n if (state.supervisorEnabled !== true) {\n return { useSupervisor: false };\n }\n\n const relaunchState: AgentState = { ...state, model, harness };\n const supervisorDecision = decideSupervisorForWorkAgent(agentId, {\n issueId: state.issueId || agentId.replace(/^agent-/, '').toUpperCase(),\n workspace: state.workspace,\n role: 'work',\n model,\n harness,\n allowHost: state.hostOverride,\n }, relaunchState);\n if (!supervisorDecision.eligible) {\n delete state.supervisorEnabled;\n return { useSupervisor: false };\n }\n\n const supervisorScriptPath = resolvePtySupervisorScriptPath();\n if (!existsSync(supervisorScriptPath)) {\n throw new Error('pty-supervisor build artifact missing — run `npm run build`.');\n }\n await writePtyToken(agentId);\n state.supervisorEnabled = true;\n return { useSupervisor: true, supervisorScriptPath };\n}\n\nfunction resolvePtySupervisorScriptPath(): string {\n return join(packageRoot, 'dist', 'pty-supervisor.js');\n}\n\n/**\n * Decide whether to enable Claude Code Channels for a work-agent launch.\n *\n * Eligibility (all required):\n * - experimental.claudeCodeChannelsMcp is true in the merged config\n * - the agent is a work agent (specialists/conversations stay off MCP)\n * - the harness is Claude Code (not Pi or another runtime harness)\n * - auth provider is Anthropic-direct (excludes Bedrock/Vertex/Foundry)\n * - the workspace is not running inside a Docker container\n *\n * Logs the decision exactly once with a category prefix so users can see why\n * the bridge did or did not engage. The function is otherwise side-effect\n * free; the caller is responsible for writing the .mcp.json and mutating\n * state.channelsEnabled when eligible is true. This legacy MCP transport is now\n * opt-in for new spawns; the PTY supervisor is the default delivery transport.\n */\nexport function decideChannelsForWorkAgent(\n agentId: string,\n _options: SupervisorChannelsSpawnOptions,\n state: AgentState,\n): ChannelsDecision {\n const log = (eligible: boolean, reason?: string): void => {\n const tag = eligible ? 'channels:eligible' : `channels:ineligible:${reason ?? 'unknown'}`;\n console.log(`[${agentId}] ${tag}`);\n };\n\n if (!isClaudeCodeChannelsMcpEnabled()) {\n return { eligible: false, reason: 'mcp-default-off' };\n }\n\n if (state.role !== 'work') {\n log(false, 'not-a-work-agent');\n return { eligible: false, reason: 'not-a-work-agent' };\n }\n\n if (!state.harness || !getHarnessBehavior(state.harness).supportsChannelsBridge) {\n log(false, `harness-${state.harness ?? 'unknown'}`);\n return { eligible: false, reason: `harness-${state.harness ?? 'unknown'}` };\n }\n\n // Auth gate. The Channels capability is gated by Anthropic auth in the\n // compiled Claude Code binary; we only attempt the bridge when the model\n // routes to the anthropic provider.\n const provider = getProviderForModelSync(state.model as ModelId);\n if (provider.name !== 'anthropic') {\n log(false, `provider-${provider.name}`);\n return { eligible: false, reason: `provider-${provider.name}` };\n }\n\n if (\n process.env.CLAUDE_CODE_USE_BEDROCK === '1' ||\n process.env.CLAUDE_CODE_USE_VERTEX === '1' ||\n process.env.CLAUDE_CODE_USE_FOUNDRY === '1'\n ) {\n log(false, 'auth-bedrock-vertex-foundry');\n return { eligible: false, reason: 'auth-bedrock-vertex-foundry' };\n }\n\n // Docker workspace gate. We do not yet share a socket dir between host and\n // container; deferred to a follow-up issue (see hazards H10).\n if (\n process.env.OVERDECK_DOCKER_WORKSPACE === '1' ||\n process.env.PAN_DOCKER === '1'\n ) {\n log(false, 'docker-not-supported-yet');\n return { eligible: false, reason: 'docker-not-supported-yet' };\n }\n\n log(true);\n return { eligible: true };\n}\n\n/**\n * Write the per-agent MCP config that points claude at the overdeck-bridge\n * stdio server. The path is the workspace-local <workspace>/.pan/agent-mcp.json\n * — one config per agent, never shared, never reused.\n */\nexport async function writeChannelsBridgeMcpConfig(\n configPath: string,\n agentId: string,\n): Promise<void> {\n const fsp = await import('fs/promises');\n await fsp.mkdir(dirname(configPath), { recursive: true });\n // Resolve the bridge entrypoint from the project root. The source file\n // lives in src/lib/channels/ and is executed directly via `bun run`\n // (Bun runs TypeScript without pre-compilation). We must point at the\n // source, not a dist copy, because the build does not copy the bridge\n // script into the bundle output.\n const here = dirname(import.meta.url.replace('file://', ''));\n const projectRoot = join(here, '..', '..');\n const repoBridgePath = join(projectRoot, 'src', 'lib', 'channels', 'overdeck-bridge.ts');\n const mcpConfig = {\n mcpServers: {\n 'overdeck-bridge': {\n command: 'bun',\n args: ['run', repoBridgePath],\n env: {\n OVERDECK_AGENT_ID: agentId,\n OVERDECK_HOME: process.env.OVERDECK_HOME ?? join(homedir(), '.overdeck'),\n },\n },\n },\n };\n await fsp.writeFile(configPath, JSON.stringify(mcpConfig, null, 2), 'utf-8');\n}\n\n/**\n * Dismiss the dev-channels confirmation TUI dialog rendered by\n * `claude --dangerously-load-development-channels`. The dialog text\n * 'WARNING: Loading development channels' must be on screen before any prompt\n * is delivered, otherwise the channel listener never registers and every\n * early channel push silently falls back to tmux.\n *\n * Polling budget is 20s because cold-start claude with TLDR + Playwright MCP\n * servers attached commonly takes 8–15s to render the first frame; a tighter\n * budget false-negatives. If the dialog is not detected within the timeout,\n * we proceed — the dialog is suppressed in some auth states (e.g. when the\n * binary takes a non-interactive code path), and the launch must not block\n * forever.\n *\n * Uses sendRawKeystrokeAsync intentionally: sendKeysAsync's load-buffer +\n * paste-buffer machinery is for typing message bodies, not for a single\n * Enter on a TUI prompt where mistimed paste can fire before the dialog\n * accepts input.\n *\n * Once the dialog is detected we send Enter and KEEP checking — a single\n * keystroke can be dropped if the TUI is still mid-render, which left the\n * dialog on screen with the helper already returned. We re-send Enter every\n * RESEND_INTERVAL_MS until the needle is gone (bounded by DISMISS_BUDGET_MS).\n */\nexport async function dismissDevChannelsDialog(agentId: string): Promise<void> {\n const TIMEOUT_MS = 20_000;\n const POLL_INTERVAL_MS = 200;\n const RESEND_INTERVAL_MS = 150;\n const DISMISS_BUDGET_MS = 5_000;\n const NEEDLE = 'WARNING: Loading development channels';\n const start = Date.now();\n while (Date.now() - start < TIMEOUT_MS) {\n try {\n const pane = await Effect.runPromise(capturePane(agentId, 50));\n if (pane.includes(NEEDLE)) {\n // Dialog is up. Send Enter, then keep re-sending until the needle\n // clears — the first keystroke can land before the TUI is ready to\n // accept it, leaving the dialog stuck on screen.\n const dismissStart = Date.now();\n while (Date.now() - dismissStart < DISMISS_BUDGET_MS) {\n await Effect.runPromise(sendRawKeystroke(agentId, 'C-m', 'channels:dismiss-dev-dialog'));\n await new Promise((r) => setTimeout(r, RESEND_INTERVAL_MS));\n const after = await Effect.runPromise(\n capturePane(agentId, 50).pipe(Effect.catch(() => Effect.succeed(''))),\n );\n if (!after.includes(NEEDLE)) return;\n }\n console.log(`[${agentId}] channels:dismiss:dialog-still-present-after-budget`);\n return;\n }\n } catch {\n // Capture failures are transient (tmux session not yet visible to\n // the new pane); keep polling within the budget.\n }\n await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));\n }\n console.log(`[${agentId}] channels:dismiss:dialog-not-detected`);\n}\n","/**\n * openai-auth.ts\n *\n * Reads Codex/ChatGPT local auth state and keeps the CLIProxy subscription\n * credential store in sync. OpenAI models are supported only through\n * Codex/ChatGPT subscription auth routed via CLIProxyAPI; API-key fallback is\n * intentionally not used because api.openai.com does not expose an\n * Anthropic-compatible /v1/messages endpoint.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { readFile } from 'fs/promises';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { FsError } from './errors.js';\nimport { bridgeCodexAuthToCliproxySync } from './cliproxy.js';\n\nexport interface OpenAIAuthStatus {\n /** True if Codex auth storage exists locally. */\n installed: boolean;\n /** True if Codex/ChatGPT OAuth refresh credentials are available. */\n loggedIn: boolean;\n /** True if the access token is expired, even if refresh may still succeed. */\n expired: boolean;\n /** Auth mode persisted by Codex (usually \"chatgpt\" for subscription auth). */\n authMode: string | null;\n /** Codex account identifier, if present. */\n accountId: string | null;\n /** When Codex last refreshed auth state, if recorded. */\n lastRefresh: string | null;\n /** Access-token expiry timestamp in ms, if it can be derived from the JWT. */\n accessTokenExpiresAt: number | null;\n /** True when an OpenAI API key is present in auth.json or env. */\n hasOpenAIApiKey: boolean;\n /** True when Overdeck bridged ~/.codex/auth.json into CLIProxy config. */\n bridgedFromCodex: boolean;\n}\n\ninterface RawAuthFile {\n auth_mode?: unknown;\n last_refresh?: unknown;\n OPENAI_API_KEY?: unknown;\n tokens?: {\n id_token?: unknown;\n access_token?: unknown;\n refresh_token?: unknown;\n account_id?: unknown;\n };\n}\n\nfunction getCodexAuthPath(): string {\n return join(homedir(), '.codex', 'auth.json');\n}\n\nfunction decodeBase64Url(value: string): string | null {\n try {\n const normalized = value.replace(/-/g, '+').replace(/_/g, '/');\n const padding = normalized.length % 4 === 0 ? '' : '='.repeat(4 - (normalized.length % 4));\n return Buffer.from(normalized + padding, 'base64').toString('utf8');\n } catch {\n return null;\n }\n}\n\nfunction decodeJwtPayload(token: string | null): Record<string, unknown> | null {\n if (!token) return null;\n const parts = token.split('.');\n if (parts.length < 2) return null;\n\n const decoded = decodeBase64Url(parts[1]);\n if (!decoded) return null;\n\n try {\n return JSON.parse(decoded) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\nfunction getJwtExpiry(token: string | null): number | null {\n const payload = decodeJwtPayload(token);\n return typeof payload?.exp === 'number' ? payload.exp * 1000 : null;\n}\n\nasync function readCodexAuthAsync(authPath: string): Promise<RawAuthFile | null> {\n if (!existsSync(authPath)) return null;\n try {\n return JSON.parse(await readFile(authPath, 'utf8')) as RawAuthFile;\n } catch {\n return null;\n }\n}\n\nfunction readCodexAuthSync(authPath: string): RawAuthFile | null {\n if (!existsSync(authPath)) return null;\n try {\n return JSON.parse(readFileSync(authPath, 'utf8')) as RawAuthFile;\n } catch {\n return null;\n }\n}\n\nfunction hasApiKey(raw: RawAuthFile | null): boolean {\n return !!process.env.OPENAI_API_KEY\n || (typeof raw?.OPENAI_API_KEY === 'string' && raw.OPENAI_API_KEY.trim().length > 0);\n}\n\nfunction buildStatus(raw: RawAuthFile | null, installed: boolean, bridgedFromCodex: boolean): OpenAIAuthStatus {\n const authMode = typeof raw?.auth_mode === 'string' ? raw.auth_mode : null;\n const lastRefresh = typeof raw?.last_refresh === 'string' ? raw.last_refresh : null;\n const codexAccessToken = typeof raw?.tokens?.access_token === 'string' ? raw.tokens.access_token : null;\n const codexRefreshToken = typeof raw?.tokens?.refresh_token === 'string' ? raw.tokens.refresh_token : null;\n const codexIdToken = typeof raw?.tokens?.id_token === 'string' ? raw.tokens.id_token : null;\n const codexAccountId = typeof raw?.tokens?.account_id === 'string' ? raw.tokens.account_id : null;\n const accessTokenExpiresAt = getJwtExpiry(codexAccessToken) ?? getJwtExpiry(codexIdToken);\n\n return {\n installed,\n loggedIn: !!codexRefreshToken,\n expired: !!(accessTokenExpiresAt && accessTokenExpiresAt < Date.now()),\n authMode,\n accountId: codexAccountId,\n lastRefresh,\n accessTokenExpiresAt,\n hasOpenAIApiKey: hasApiKey(raw),\n bridgedFromCodex,\n };\n}async function getOpenAIAuthStatusPromise(): Promise<OpenAIAuthStatus> {\n const codexDir = join(homedir(), '.codex');\n const authPath = getCodexAuthPath();\n const installed = existsSync(codexDir) || existsSync(authPath);\n const raw = await readCodexAuthAsync(authPath);\n\n let bridgedFromCodex = false;\n try {\n if (bridgeCodexAuthToCliproxySync()) {\n bridgedFromCodex = true;\n }\n } catch { /* non-fatal — cliproxy bridge is best-effort */ }\n\n return buildStatus(raw, installed, bridgedFromCodex);\n}\n\n/** Effect variant of {@link getOpenAIAuthStatus}. */\nexport const getOpenAIAuthStatus = (): Effect.Effect<OpenAIAuthStatus, FsError> =>\n Effect.tryPromise({\n try: () => getOpenAIAuthStatusPromise(),\n catch: (cause) => new FsError({ path: getCodexAuthPath(), operation: 'getOpenAIAuthStatus', cause }),\n });\n\n/** Synchronous variant for CLI-side code. Dashboard server routes should use {@link getOpenAIAuthStatus}. */\nexport function getOpenAIAuthStatusSync(): OpenAIAuthStatus {\n const codexDir = join(homedir(), '.codex');\n const authPath = getCodexAuthPath();\n const installed = existsSync(codexDir) || existsSync(authPath);\n const raw = readCodexAuthSync(authPath);\n\n let bridgedFromCodex = false;\n try {\n if (bridgeCodexAuthToCliproxySync()) {\n bridgedFromCodex = true;\n }\n } catch { /* non-fatal */ }\n\n return buildStatus(raw, installed, bridgedFromCodex);\n}\n","/**\n * Provider Health Probing\n *\n * Pre-flight check for non-Anthropic direct providers before spawning agents.\n * Sends a minimal request to detect quota exhaustion, auth failures, and\n * network issues BEFORE the agent enters Claude Code's opaque retry loop.\n */\n\nimport { Effect } from 'effect';\nimport { getProviderEnvSync, getProviderForModelSync, type ProviderConfig } from './providers.js';\nimport { loadConfigSync as loadYamlConfig } from './config-yaml.js';\nimport { ensureOpenAICompatibleProxyRunning } from './openai-compatible-proxy.js';\nimport type { ModelId } from './settings.js';\n\nexport type ProbeResultKind = 'quota' | 'auth' | 'timeout' | 'network' | 'server' | 'unknown';\n\nexport type ProbeResult =\n | { ok: true }\n | { ok: false; kind: ProbeResultKind; status?: number; message: string };\n\ninterface CacheEntry {\n result: ProbeResult;\n expires: number;\n}\n\nconst CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes — for durable results (ok, auth, quota)\nconst TRANSIENT_CACHE_TTL_MS = 15 * 1000; // timeout/network/server/unknown are often a one-off blip\nconst PROBE_TIMEOUT_MS = 8000;\nconst cache = new Map<string, CacheEntry>();\n\nfunction cacheKey(provider: string, apiKey: string): string {\n // Hash the key to avoid storing raw secrets in memory map keys\n const keyHash = apiKey.slice(0, 8) + '...' + apiKey.slice(-4);\n return `${provider}:${keyHash}`;\n}\n\nfunction classifyError(status: number, body: string): ProbeResult {\n const lower = body.toLowerCase();\n\n if (status === 429) {\n if (lower.includes('quota') || lower.includes('exhausted') || lower.includes('limit')) {\n return { ok: false, kind: 'quota', status, message: parseErrorMessage(body) || 'Quota exhausted' };\n }\n return { ok: false, kind: 'quota', status, message: parseErrorMessage(body) || 'Rate limited (429)' };\n }\n\n if (status === 401 || status === 403) {\n return { ok: false, kind: 'auth', status, message: parseErrorMessage(body) || 'Invalid API key' };\n }\n\n if (status >= 500) {\n return { ok: false, kind: 'server', status, message: parseErrorMessage(body) || `Server error (${status})` };\n }\n\n return { ok: false, kind: 'unknown', status, message: parseErrorMessage(body) || `Unexpected status ${status}` };\n}\n\nfunction parseErrorMessage(body: string): string {\n try {\n const parsed = JSON.parse(body);\n return parsed?.error?.message || parsed?.message || '';\n } catch {\n return body.slice(0, 200);\n }\n}\n\nexport function buildAnthropicMessagesUrl(baseUrl: string): string {\n const normalized = baseUrl.replace(/\\/+$/, '');\n return normalized.endsWith('/v1')\n ? `${normalized}/messages`\n : `${normalized}/v1/messages`;\n}async function probeProviderPromise(\n provider: ProviderConfig,\n apiKey: string,\n model: string,\n): Promise<ProbeResult> {\n const key = cacheKey(provider.name, apiKey);\n const cached = cache.get(key);\n if (cached && Date.now() < cached.expires) {\n return cached.result;\n }\n\n const result = await doProbe(provider, apiKey, model);\n\n // Auth/quota failures are durable (a bad key or exhausted quota won't\n // resolve itself in seconds) and are worth caching for the full TTL.\n // timeout/network/server/unknown are frequently a one-off blip — caching\n // those for 5 minutes would keep telling the user a healthy provider is\n // down for the full window, blocking every retry with a stale verdict.\n const isTransientFailure = !result.ok && (result.kind === 'timeout' || result.kind === 'network' || result.kind === 'server' || result.kind === 'unknown');\n const ttl = isTransientFailure ? TRANSIENT_CACHE_TTL_MS : CACHE_TTL_MS;\n cache.set(key, { result, expires: Date.now() + ttl });\n return result;\n}\n\n/**\n * Clear cached probe result for a provider (e.g. after key change).\n */\nexport function invalidateProbeCacheSync(provider?: string): void {\n if (provider) {\n for (const k of cache.keys()) {\n if (k.startsWith(`${provider}:`)) cache.delete(k);\n }\n } else {\n cache.clear();\n }\n}\n\nasync function doProbe(\n provider: ProviderConfig,\n apiKey: string,\n model: string,\n): Promise<ProbeResult> {\n // Nous Portal routes through the local OpenAI-compatible proxy sidecar at\n // 127.0.0.1:12436. Without this, the probe runs before the proxy is booted\n // on the first spawn after every dashboard restart and surfaces a spurious\n // \"Cannot reach Nous Portal: fetch failed\" toast.\n if (provider.name === 'nous') {\n await Effect.runPromise(ensureOpenAICompatibleProxyRunning());\n // Use GET /v1/models instead of POST /v1/messages. The only Nous model\n // (qwen/qwen3.6-plus) is a reasoning model that ignores max_tokens for\n // its reasoning phase and routinely takes >8s to answer a one-character\n // probe, blowing the timeout. /v1/models verifies network + auth without\n // burning reasoning tokens.\n return probeModelsEndpoint(provider, apiKey);\n }\n\n const providerEnv = getProviderEnvSync(provider, apiKey);\n const baseUrl = providerEnv.ANTHROPIC_BASE_URL ?? provider.baseUrl;\n if (!baseUrl) return { ok: true };\n\n const url = buildAnthropicMessagesUrl(baseUrl);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n method: 'POST',\n signal: controller.signal,\n headers: {\n 'content-type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`,\n 'anthropic-version': '2023-06-01',\n },\n body: JSON.stringify({\n model,\n max_tokens: 1,\n messages: [{ role: 'user', content: '.' }],\n }),\n });\n\n clearTimeout(timer);\n const body = await response.text();\n\n // 2xx = provider is working (even if it returned empty/short content)\n if (response.ok) {\n return { ok: true };\n }\n\n return classifyError(response.status, body);\n } catch (err: unknown) {\n clearTimeout(timer);\n return classifyFetchError(err, provider);\n }\n}\n\nasync function probeModelsEndpoint(\n provider: ProviderConfig,\n apiKey: string,\n): Promise<ProbeResult> {\n const baseUrl = provider.baseUrl;\n if (!baseUrl) return { ok: true };\n\n const normalized = baseUrl.replace(/\\/+$/, '');\n const url = normalized.endsWith('/v1') ? `${normalized}/models` : `${normalized}/v1/models`;\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), PROBE_TIMEOUT_MS);\n\n try {\n const response = await fetch(url, {\n method: 'GET',\n signal: controller.signal,\n headers: {\n 'Authorization': `Bearer ${apiKey}`,\n accept: 'application/json',\n },\n });\n\n clearTimeout(timer);\n if (response.ok) return { ok: true };\n return classifyError(response.status, await response.text());\n } catch (err: unknown) {\n clearTimeout(timer);\n return classifyFetchError(err, provider);\n }\n}\n\nfunction classifyFetchError(err: unknown, provider: ProviderConfig): ProbeResult {\n if (err instanceof Error) {\n if (err.name === 'AbortError') {\n return { ok: false, kind: 'timeout', message: `Provider did not respond within ${PROBE_TIMEOUT_MS / 1000}s` };\n }\n if (err.message.includes('ECONNREFUSED') || err.message.includes('ENOTFOUND') || err.message.includes('fetch failed')) {\n return { ok: false, kind: 'network', message: `Cannot reach ${provider.displayName}: ${err.message}` };\n }\n }\n return { ok: false, kind: 'unknown', message: `Probe failed: ${(err as Error).message ?? err}` };\n}\n\n/**\n * User-facing error message for a failed probe, suitable for dashboard display.\n */\nexport function formatProbeError(provider: ProviderConfig, model: string, result: ProbeResult & { ok: false }): string {\n const prefix = `${provider.displayName} (${model})`;\n\n switch (result.kind) {\n case 'quota':\n return `${prefix}: quota exhausted — top up credits or switch to a different model`;\n case 'auth':\n return `${prefix}: authentication failed — check your API key in Settings`;\n case 'timeout':\n return `${prefix}: endpoint unreachable (timeout) — the provider may be down`;\n case 'network':\n return `${prefix}: network error — ${result.message}`;\n case 'server':\n return `${prefix}: provider returned ${result.status} server error — try again later or switch models`;\n default:\n return `${prefix}: pre-flight check failed — ${result.message}`;\n }\n}async function validateProviderHealthPromise(\n model: string,\n apiKey?: string,\n): Promise<void> {\n const provider = getProviderForModelSync(model as ModelId);\n\n // Skip: Anthropic native and OpenAI subscription routing have their own checks.\n if (provider.name === 'anthropic' || provider.name === 'openai') {\n return;\n }\n\n const key = apiKey ?? resolveApiKey(provider);\n if (!key) return; // No key configured — let downstream handle the \"no key\" error\n\n const result = await Effect.runPromise(probeProvider(provider, key, model));\n if (!result.ok) {\n throw new ProviderHealthError(provider, model, result);\n }\n}\n\nfunction resolveApiKey(provider: ProviderConfig): string | undefined {\n const { config } = loadYamlConfig();\n return config.apiKeys[provider.name as keyof typeof config.apiKeys];\n}\n\nexport class ProviderHealthError extends Error {\n public readonly provider: ProviderConfig;\n public readonly model: string;\n public readonly probeResult: ProbeResult & { ok: false };\n\n constructor(provider: ProviderConfig, model: string, result: ProbeResult & { ok: false }) {\n super(formatProbeError(provider, model, result));\n this.name = 'ProviderHealthError';\n this.provider = provider;\n this.model = model;\n this.probeResult = result;\n }\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect variant of {@link probeProvider}. Never fails — probe results\n * (including error classifications) are carried in the success channel as\n * `ProbeResult`.\n */\nexport const probeProvider = (\n provider: ProviderConfig,\n apiKey: string,\n model: string,\n): Effect.Effect<ProbeResult, never> =>\n Effect.promise(() => probeProviderPromise(provider, apiKey, model));\n\n/** Effect variant of {@link validateProviderHealth}. */\nexport const validateProviderHealth = (\n model: string,\n apiKey?: string,\n): Effect.Effect<void, ProviderHealthError> =>\n Effect.tryPromise({\n try: () => validateProviderHealthPromise(model, apiKey),\n catch: (cause) => {\n if (cause instanceof ProviderHealthError) return cause;\n // Should never happen — validateProviderHealth only throws ProviderHealthError.\n // Re-wrap defensively so the typed error channel stays narrow.\n const provider = getProviderForModelSync(model as ModelId);\n return new ProviderHealthError(provider, model, {\n ok: false,\n kind: 'unknown',\n message: cause instanceof Error ? cause.message : String(cause),\n });\n },\n });\n\n/** Effect variant of {@link invalidateProbeCacheSync}. Pure cache mutation; cannot fail. */\nexport const invalidateProbeCache = (provider?: string): Effect.Effect<void, never> =>\n Effect.sync(() => invalidateProbeCacheSync(provider));\n","import { Effect } from 'effect';\nimport type { NormalizedCavemanConfig } from '../config-yaml.js';\nimport { loadConfigSync as loadYamlConfig, resolveModel } from '../config-yaml.js';\nimport { readCavemanVariant } from '../caveman/workspace.js';\nimport { bridgeGeminiAuthToCliproxy, getCliproxyClientEnv } from '../cliproxy.js';\nimport { normalizeModelOverrideSync, requireModelOverrideSync } from '../model-validation.js';\nimport { getOpenAIAuthStatus } from '../openai-auth.js';\nimport { ensureOpenAICompatibleProxyRunning } from '../openai-compatible-proxy.js';\nimport { validateProviderHealth } from '../provider-health.js';\nimport { getProviderEnvSync, getProviderForModelSync } from '../providers.js';\nimport { hasModelCapabilitySync, getModelCapabilitySync, resolveModelIdSync } from '../model-capabilities.js';\nimport type { Role } from './agent-state.js';\n\n/** Map abstract/future model names to CLIProxy-supported names.\n * The CLIProxy registry has gpt-5.4 but not gpt-5.4-pro. */\nexport const CLI_PROXY_MODEL_ALIASES: Record<string, string> = {\n 'gpt-5.5-pro': 'gpt-5.5',\n 'gpt-5.4-pro': 'gpt-5.4',\n};\n\n/**\n * Get provider-specific env vars (BASE_URL, AUTH_TOKEN) for a model.\n * Reads the current API key from settings so resumed/recovered agents\n * always use the latest key.\n */\nexport async function getProviderEnvForModel(model: string): Promise<Record<string, string>> {\n const provider = getProviderForModelSync(model);\n if (provider.name === 'anthropic') return {};\n\n const { config } = loadYamlConfig();\n\n // OpenRouter API key is stored in config.yaml under providers.openrouter.api_key\n if (provider.name === 'openrouter') {\n const apiKey = config.apiKeys.openrouter;\n if (apiKey) {\n return getProviderEnvSync(provider, apiKey);\n }\n throw new Error(`OpenRouter API key not configured. Add your key in Settings → OpenRouter before using model \"${model}\".`);\n }\n\n const apiKey = config.apiKeys[provider.name as keyof typeof config.apiKeys];\n\n if (provider.name === 'google') {\n if (!apiKey) {\n throw new Error(`Google API key not configured. Add GOOGLE_API_KEY in Settings → Google or ~/.overdeck.env before using model \"${model}\".`);\n }\n\n if (!await Effect.runPromise(bridgeGeminiAuthToCliproxy(apiKey))) {\n throw new Error(`Failed to bridge Google API key into CLIProxy before using model \"${model}\".`);\n }\n\n return getCliproxyClientEnv();\n }\n\n if (provider.name === 'openai') {\n const authStatus = await Effect.runPromise(getOpenAIAuthStatus());\n if (authStatus.loggedIn) {\n // Route through the local CLIProxyAPI sidecar using the user's\n // ChatGPT subscription OAuth tokens. Claude Code sees a normal\n // Anthropic-compatible endpoint and never needs an API key.\n return getCliproxyClientEnv();\n }\n\n const configuredKey = apiKey || authStatus.hasOpenAIApiKey;\n throw new Error(\n configuredKey\n ? `OpenAI API-key routing is no longer supported for model \"${model}\" because api.openai.com does not expose an Anthropic-compatible /v1/messages endpoint. Sign in with a Codex/ChatGPT subscription via \\`pan admin specialists codex login\\` or Dashboard Settings → Codex Login.`\n : `Codex/ChatGPT subscription login required for OpenAI model \"${model}\". Sign in via \\`pan admin specialists codex login\\` or Dashboard Settings → Codex Login.`,\n );\n }\n\n if (apiKey) {\n if (provider.name === 'nous') {\n await Effect.runPromise(ensureOpenAICompatibleProxyRunning());\n }\n await Effect.runPromise(validateProviderHealth(model, apiKey));\n return getProviderEnvSync(provider, apiKey);\n }\n\n throw new Error(`No API key configured for ${provider.displayName}. Configure it in Settings before using model \"${model}\".`);\n}\n\n/**\n * Get bash export lines for provider env vars (for use in launcher scripts).\n * Returns empty string for Anthropic models.\n */\nconst PROVIDER_ENV_KEYS = [\n 'ANTHROPIC_API_KEY',\n 'ANTHROPIC_BASE_URL',\n 'ANTHROPIC_AUTH_TOKEN',\n 'ANTHROPIC_DEFAULT_HAIKU_MODEL',\n 'ANTHROPIC_DEFAULT_OPUS_MODEL',\n 'ANTHROPIC_DEFAULT_SONNET_MODEL',\n 'ANTHROPIC_SMALL_FAST_MODEL',\n 'CLAUDE_CODE_SUBAGENT_MODEL',\n 'CLAUDE_CODE_AUTO_COMPACT_WINDOW',\n 'OPENAI_API_KEY',\n 'GEMINI_API_KEY',\n 'API_TIMEOUT_MS',\n 'CLAUDE_CODE_API_KEY_HELPER_TTL_MS',\n // Pi-native provider env vars (bridged from Overdeck settings so Pi can auth)\n 'KIMI_API_KEY',\n 'MINIMAX_API_KEY',\n 'ZAI_API_KEY',\n 'MIMO_API_KEY',\n 'OPENROUTER_API_KEY',\n 'NOUS_API_KEY',\n 'DASHSCOPE_API_KEY',\n] as const;\n\nfunction getClaudeCodeAutoCompactWindowForModel(model: string): number | undefined {\n const provider = getProviderForModelSync(model);\n if (provider.name === 'anthropic') return undefined;\n\n const resolvedModel = resolveModelIdSync(model);\n if (!hasModelCapabilitySync(resolvedModel)) return undefined;\n return getModelCapabilitySync(resolvedModel).contextWindow;\n}\n\nexport async function getProviderExportsForModel(model: string): Promise<string> {\n const envVars = await getProviderEnvForModel(model);\n const unsetLines = PROVIDER_ENV_KEYS.map(key => `unset ${key}`);\n const autoCompactWindow = getClaudeCodeAutoCompactWindowForModel(model);\n const exportLines = Object.entries(envVars)\n .map(([k, v]) => `export ${k}=\"${v.replace(/\"/g, '\\\\\"')}\"`);\n if (autoCompactWindow !== undefined) {\n exportLines.push(`export CLAUDE_CODE_AUTO_COMPACT_WINDOW=\"${autoCompactWindow}\"`);\n }\n\n return [...unsetLines, ...exportLines].join('\\n') + '\\n';\n}\n\n/**\n * Build a sanitized env for programmatically spawning `claude`.\n *\n * The dashboard parent process may inherit provider env vars (e.g.\n * ANTHROPIC_BASE_URL pointing at the CLIProxy sidecar) that would mis-route\n * a child process targeting an Anthropic model. Launcher *scripts* strip\n * these via `unset` lines; programmatic spawns must do the same.\n *\n * Returns a copy of `baseEnv` (default: process.env) with all PROVIDER_ENV_KEYS\n * deleted, then overlaid with the correct provider env for `model`.\n */\nexport async function buildSpawnEnvForModel(\n model: string,\n baseEnv: NodeJS.ProcessEnv = process.env,\n): Promise<Record<string, string>> {\n const sanitized: Record<string, string> = {};\n for (const [k, v] of Object.entries(baseEnv)) {\n if (v === undefined) continue;\n if ((PROVIDER_ENV_KEYS as readonly string[]).includes(k)) continue;\n sanitized[k] = v;\n }\n const providerEnv = await getProviderEnvForModel(model);\n const autoCompactWindow = getClaudeCodeAutoCompactWindowForModel(model);\n return {\n ...sanitized,\n ...providerEnv,\n ...(autoCompactWindow !== undefined ? { CLAUDE_CODE_AUTO_COMPACT_WINDOW: String(autoCompactWindow) } : {}),\n };\n}\n\n/**\n * Get tmux -e flags for provider env vars (for use in tmux new-session).\n * Returns empty string for Anthropic models.\n */\nexport async function getProviderTmuxFlags(model: string): Promise<string> {\n const envVars = await getProviderEnvForModel(model);\n let flags = '';\n for (const [key, value] of Object.entries(envVars)) {\n flags += ` -e ${key}=\"${value.replace(/\"/g, '\\\\\"')}\"`;\n }\n return flags;\n}\n\n/**\n * Build shell export lines to inject into a work agent's launcher.sh.\n *\n * Sets CAVEMAN_DEFAULT_MODE and OVERDECK_CAVEMAN_VARIANT so the caveman\n * SessionStart hook activates at the right intensity level and cost events\n * carry the A/B test variant.\n *\n * @param workspacePath Absolute workspace path (to read stored variant)\n * @param config Normalized caveman config from YamlConfig\n * @param isPlanning True for planning agents — caveman always disabled there\n * @returns Shell export lines to prepend to the launcher script\n */\nexport async function buildCavemanExports(\n workspacePath: string,\n config: NormalizedCavemanConfig,\n isPlanning: boolean\n): Promise<string> {\n // Planning agents: never compress — output is user-facing\n if (isPlanning || !config.enabled) return '';\n\n const variant = await Effect.runPromise(readCavemanVariant(workspacePath));\n\n // If this workspace's A/B variant is 'disabled', set variant for tracking but no mode\n if (variant === 'off') return '';\n if (variant === 'disabled') {\n return `export OVERDECK_CAVEMAN_VARIANT=\"${variant}\"\\n`;\n }\n\n // Work agents use the 'work' intensity mode\n const mode = config.modes.work;\n if (mode === 'off' || mode === 'disabled') return '';\n\n return `export CAVEMAN_DEFAULT_MODE=\"${mode}\"\\nexport OVERDECK_CAVEMAN_VARIANT=\"${variant}\"\\n`;\n}\n\n/**\n * Determine which model to use for a role-based work agent.\n *\n * Priority:\n * 1. Explicitly provided model (options.model)\n * 2. Role routing via config.yaml roles/workhorses (defaults to work)\n *\n * Resolution failures propagate as spawn-time errors. Per PAN-1048 PRD:\n * invalid workhorse references and unresolved role configs must fail loudly\n * at config-load/spawn time, not silently fall back to a hidden default\n * model. Defaults are seeded into the config when entries are absent\n * (DEFAULT_WORKHORSES / DEFAULT_ROLES) — anything that still raises here\n * is a real configuration bug the user must see.\n */\n/**\n * Models that are known-broken for autonomous *work* agents and must never be\n * used to spawn one, even if a project pins them in config. The gate fails\n * loudly for the work role when the model wasn't an explicit per-spawn override.\n *\n * Empty as of PAN-1584: gpt-5.5 used to wedge at launch with CLIProxy \"System\n * messages are not allowed\", which was a stale CLIProxyAPI binary (6.9.45)\n * mis-translating Claude Code's request to the Codex backend. Upgrading the\n * pinned CLIProxyAPI to v7.1.39 (+ a version-aware installer) fixed it; gpt-5.5\n * work agents now launch clean under the claude-code harness. The mechanism is\n * retained for any future known-broken model. (Pi-harness gpt-5.5 was not\n * re-verified in this pass — re-add 'gpt-5.5' here if a Pi init hang resurfaces.)\n */\nconst WORK_AGENT_BROKEN_MODELS = new Set<string>([]);\n\nexport function determineModel(options: { model?: string; role?: Role; spawnKey?: string } = {}): string {\n const modelOverride = normalizeModelOverrideSync(options.model);\n const resolved = modelOverride\n ? modelOverride\n : requireModelOverrideSync(resolveModel(options.role ?? 'work', undefined, loadYamlConfig().config, options.spawnKey));\n\n // Work-agent safety net: a config pin (or smart-selection) must not spawn a\n // work agent on a model that is known to wedge for the work role. Fail loudly\n // rather than launch a dead agent or silently substitute another model. Only\n // applies to the work role and only when the model wasn't an explicit,\n // deliberate per-spawn override.\n const role = options.role ?? 'work';\n if (role === 'work' && !modelOverride && WORK_AGENT_BROKEN_MODELS.has(resolved)) {\n throw new Error(\n `Resolved work model \"${resolved}\" is known-broken for work agents. ` +\n 'Set roles.work.model to a working model in config.yaml, or pass an explicit --model override.',\n );\n }\n\n return resolved;\n}\n","/**\n * claude-auth.ts\n *\n * Reads Claude Code's credentials to determine whether the user is logged in\n * with a subscription (MAX / Pro / Team) or not.\n *\n * Credentials are checked in order:\n * 1. ~/.claude/.credentials.json (Linux, older Claude Code versions)\n * 2. macOS Keychain: \"Claude Code-credentials\" (macOS with newer Claude Code)\n *\n * The credential payload contains:\n * claudeAiOauth.subscriptionType — \"max\" | \"pro\" | \"team\" | null\n * claudeAiOauth.rateLimitTier — e.g. \"default_claude_max_20x\"\n * claudeAiOauth.accessToken — OAuth bearer token\n * claudeAiOauth.expiresAt — Unix timestamp (ms)\n */\n\nimport { existsSync } from 'fs';\nimport { homedir } from 'os';\nimport { join } from 'path';\nimport { platform } from 'process';\nimport { Duration, Effect, FileSystem } from 'effect';\nimport { ChildProcessSpawner } from 'effect/unstable/process/ChildProcessSpawner';\nimport { ChildProcess } from 'effect/unstable/process';\nimport { layer as NodeServicesLayer } from '@effect/platform-node/NodeServices';\nimport { ClaudeCredentialParseError } from './errors.js';\n\nexport interface ClaudeAuthStatus {\n /** True if the ~/.claude directory exists (i.e. Claude Code has been installed). */\n installed: boolean;\n /** True if a non-expired OAuth token is present. */\n loggedIn: boolean;\n /** True if the token exists but has already expired. */\n expired: boolean;\n /** \"max\" | \"pro\" | \"team\" | null — from claudeAiOauth.subscriptionType */\n subscriptionType: string | null;\n /** e.g. \"default_claude_max_20x\" */\n rateLimitTier: string | null;\n /** Token expiry timestamp in ms, or null if unknown. */\n expiresAt: number | null;\n /** True when ANTHROPIC_API_KEY is set in the server process environment.\n * When present, Claude Code prefers this over subscription auth. */\n hasAnthropicApiKey: boolean;\n}\n\nexport function parseOAuthPayload(raw: string): Effect.Effect<\n {\n loggedIn: boolean;\n expired: boolean;\n subscriptionType: string | null;\n rateLimitTier: string | null;\n expiresAt: number | null;\n },\n ClaudeCredentialParseError\n> {\n return Effect.try({\n try: () => {\n const creds = JSON.parse(raw) as Record<string, unknown>;\n const oauth = (creds.claudeAiOauth ?? {}) as Record<string, unknown>;\n\n if (!oauth.accessToken) {\n return { loggedIn: false, expired: false, subscriptionType: null, rateLimitTier: null, expiresAt: null };\n }\n\n const expiresAt = typeof oauth.expiresAt === 'number' ? oauth.expiresAt : null;\n const expired = !!(expiresAt && expiresAt < Date.now());\n\n // Treat as logged in even if expired — Claude Code auto-refreshes tokens\n // transparently. Only mark as not-logged-in if there's no token at all.\n return {\n loggedIn: true,\n expired,\n subscriptionType: typeof oauth.subscriptionType === 'string' ? oauth.subscriptionType : null,\n rateLimitTier: typeof oauth.rateLimitTier === 'string' ? oauth.rateLimitTier : null,\n expiresAt,\n };\n },\n catch: (cause) => new ClaudeCredentialParseError({ message: 'Failed to parse credentials JSON', cause }),\n });\n}\n\n/**\n * Read credentials JSON from macOS Keychain.\n * Claude Code ≥2.x stores credentials under service \"Claude Code-credentials\".\n */\nconst readKeychainCredentials: Effect.Effect<string | null, never, ChildProcessSpawner> =\n Effect.gen(function* () {\n if (platform !== 'darwin') return null;\n const spawner = yield* ChildProcessSpawner;\n const cmd = ChildProcess.make('security', [\n 'find-generic-password', '-s', 'Claude Code-credentials', '-w',\n ]);\n return yield* spawner.string(cmd).pipe(\n Effect.timeout(Duration.seconds(3)),\n Effect.map((s) => s.trim() || null),\n Effect.orElseSucceed(() => null),\n );\n });\n\nconst getClaudeAuthStatusImpl: Effect.Effect<\n ClaudeAuthStatus,\n never,\n FileSystem.FileSystem | ChildProcessSpawner\n> = Effect.gen(function* () {\n const claudeDir = join(homedir(), '.claude');\n const credPath = join(claudeDir, '.credentials.json');\n\n const installed = existsSync(claudeDir);\n const hasAnthropicApiKey = !!process.env.ANTHROPIC_API_KEY;\n\n let loggedIn = false;\n let expired = false;\n let subscriptionType: string | null = null;\n let rateLimitTier: string | null = null;\n let expiresAt: number | null = null;\n\n // 1. Try flat credentials file (Linux, older Claude Code)\n if (existsSync(credPath)) {\n const fs = yield* FileSystem.FileSystem;\n const result = yield* fs.readFileString(credPath, 'utf-8').pipe(\n Effect.flatMap(parseOAuthPayload),\n Effect.orElseSucceed(() => null),\n );\n if (result) {\n loggedIn = result.loggedIn;\n expired = result.expired;\n subscriptionType = result.subscriptionType;\n rateLimitTier = result.rateLimitTier;\n expiresAt = result.expiresAt;\n }\n }\n\n // 2. Fall back to macOS Keychain (Claude Code ≥2.x on macOS)\n if (!loggedIn) {\n const keychainRaw = yield* readKeychainCredentials;\n if (keychainRaw) {\n const result = yield* parseOAuthPayload(keychainRaw).pipe(\n Effect.orElseSucceed(() => null),\n );\n if (result) {\n loggedIn = result.loggedIn;\n expired = result.expired;\n subscriptionType = result.subscriptionType;\n rateLimitTier = result.rateLimitTier;\n expiresAt = result.expiresAt;\n }\n }\n }\n\n return { installed, loggedIn, expired, subscriptionType, rateLimitTier, expiresAt, hasAnthropicApiKey };\n});\n\nexport function getClaudeAuthStatus(): Effect.Effect<ClaudeAuthStatus, never> {\n return getClaudeAuthStatusImpl.pipe(\n Effect.scoped,\n Effect.provide(NodeServicesLayer),\n );\n}\n","/**\n * oh-my-pi (omp) RPC named-pipe lifecycle (PAN-1989).\n *\n * omp runs in `--mode rpc` and reads JSONL commands from stdin. We feed omp by\n * redirecting its stdin from a per-agent fifo at:\n *\n * $OVERDECK_HOME/agents/<agentId>/rpc.in\n *\n * The launcher script creates the fifo, then `exec omp --mode rpc ... < <fifo>`.\n * The runtime adapter (OhmypiRuntime.sendMessage) writes JSONL lines to the fifo.\n *\n * Why a fifo and not stdin pipe + tmux paste-buffer:\n * - tmux paste-buffer is unreliable for JSONL (Enter timing, terminal\n * echo, line buffering). The fifo path bypasses the TTY entirely.\n * - omp remains visible inside its tmux pane for crash isolation and\n * visual debugging — only its stdin is redirected.\n *\n * Ordering invariant:\n * - The launcher creates the fifo BEFORE spawning omp.\n * - omp's first stdout event is `session_start` which the extension turns\n * into ready.json under $OVERDECK_HOME/agents/<agentId>/.\n * - Adapter MUST wait for ready.json before opening the writer side of\n * the fifo.\n *\n * IMPORTANT: this module performs blocking I/O (mkfifo + file open) inside\n * async helpers. NEVER call it from a dashboard server route handler — only\n * from spawnAgent and killAgent paths that run on dedicated workers.\n */\n\nimport { existsSync, mkdirSync, openSync, writeSync, closeSync, unlinkSync, constants as fsConstants } from 'node:fs'\nimport { join } from 'node:path'\nimport { exec } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { Data, Effect } from 'effect'\nimport { getOverdeckHome } from '../paths.js'\n\nconst execAsync = promisify(exec)\n\nexport interface OhmypiFifoPaths {\n agentDir: string\n readyPath: string\n fifoPath: string\n}\n\nexport class OhmypiNotReady extends Error {\n readonly code = 'OHMYPI_NOT_READY' as const\n constructor(message: string) {\n super(message)\n this.name = 'OhmypiNotReady'\n }\n}\n\nexport function ohmypiFifoPaths(agentId: string, home?: string): OhmypiFifoPaths {\n const overdeckHome = home === undefined ? getOverdeckHome() : join(home, '.overdeck')\n const agentDir = join(overdeckHome, 'agents', agentId)\n return {\n agentDir,\n readyPath: join(agentDir, 'ready.json'),\n fifoPath: join(agentDir, 'rpc.in'),\n }\n}\n\nasync function createOhmypiFifoPromise(agentId: string, home?: string): Promise<string> {\n const paths = ohmypiFifoPaths(agentId, home)\n mkdirSync(paths.agentDir, { recursive: true, mode: 0o700 })\n if (existsSync(paths.fifoPath)) {\n unlinkSync(paths.fifoPath)\n }\n await execAsync(`mkfifo -m 600 ${shellQuote(paths.fifoPath)}`)\n return paths.fifoPath\n}\n\n/**\n * Unlink the fifo. Safe to call when the fifo does not exist.\n */\nexport function destroyOhmypiFifoSync(agentId: string, home?: string): void {\n const paths = ohmypiFifoPaths(agentId, home)\n try {\n unlinkSync(paths.fifoPath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err\n }\n}\n\n/**\n * Write a single JSONL command to the agent's fifo.\n *\n * Throws OhmypiNotReady (without blocking) when ready.json is not yet present.\n * Throws OhmypiNotReady when the fifo exists but omp has no read fd open (ENXIO).\n */\nexport function writeOhmypiCommandSync(agentId: string, command: unknown, home?: string): void {\n const paths = ohmypiFifoPaths(agentId, home)\n if (!existsSync(paths.readyPath)) {\n throw new OhmypiNotReady(\n `omp agent ${agentId}: ready.json not present yet (waiting for session_start)`,\n )\n }\n if (!existsSync(paths.fifoPath)) {\n throw new OhmypiNotReady(`omp agent ${agentId}: rpc.in fifo missing`)\n }\n\n let fd: number\n try {\n fd = openSync(paths.fifoPath, fsConstants.O_WRONLY | fsConstants.O_NONBLOCK)\n } catch (err) {\n const errno = (err as NodeJS.ErrnoException).code\n if (errno === 'ENXIO') {\n throw new OhmypiNotReady(`omp agent ${agentId}: no reader on rpc.in fifo (omp exited?)`)\n }\n throw err\n }\n try {\n const line = `${JSON.stringify(command)}\\n`\n writeSync(fd, line)\n } finally {\n closeSync(fd)\n }\n}\n\n// Tiny shell-arg quoter. Acceptable inputs are absolute paths under HOME.\nfunction shellQuote(s: string): string {\n if (/^[A-Za-z0-9._/-]+$/.test(s)) return s\n return `'${s.replace(/'/g, `'\\\\''`)}'`\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Tagged error for ohmypi-fifo Effect variants. */\nexport class OhmypiFifoError extends Data.TaggedError('OhmypiFifoError')<{\n readonly agentId: string\n readonly stage: 'create' | 'write' | 'destroy'\n readonly message: string\n readonly cause?: unknown\n}> {}\n\n/** Effect variant of `createOhmypiFifo`. */\nexport const createOhmypiFifo = (\n agentId: string,\n home?: string,\n): Effect.Effect<string, OhmypiFifoError> =>\n Effect.tryPromise({\n try: () => createOhmypiFifoPromise(agentId, home),\n catch: (cause) =>\n new OhmypiFifoError({\n agentId,\n stage: 'create',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n\n/** Effect variant of `writeOhmypiCommand`. */\nexport const writeOhmypiCommand = (\n agentId: string,\n command: unknown,\n home?: string,\n): Effect.Effect<void, OhmypiFifoError> =>\n Effect.try({\n try: () => writeOhmypiCommandSync(agentId, command, home),\n catch: (cause) =>\n new OhmypiFifoError({\n agentId,\n stage: 'write',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n\n/** Effect variant of `destroyOhmypiFifo`. */\nexport const destroyOhmypiFifo = (\n agentId: string,\n home?: string,\n): Effect.Effect<void, OhmypiFifoError> =>\n Effect.try({\n try: () => destroyOhmypiFifoSync(agentId, home),\n catch: (cause) =>\n new OhmypiFifoError({\n agentId,\n stage: 'destroy',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n","/**\n * Pi RPC named-pipe lifecycle (PAN-636).\n *\n * Pi runs in `--mode rpc` and reads JSONL commands from stdin. We feed Pi by\n * redirecting its stdin from a per-agent fifo at:\n *\n * $OVERDECK_HOME/agents/<agentId>/rpc.in\n *\n * The launcher script creates the fifo, then `exec pi --mode rpc ... < <fifo>`.\n * The runtime adapter (PiRuntime.sendMessage) writes JSONL lines to the fifo.\n *\n * Why a fifo and not stdin pipe + tmux paste-buffer:\n * - tmux paste-buffer is unreliable for JSONL (Enter timing, terminal\n * echo, line buffering). The fifo path bypasses the TTY entirely.\n * - Pi remains visible inside its tmux pane for crash isolation and\n * visual debugging — only its stdin is redirected.\n *\n * Ordering invariant (hazard H1):\n * - The launcher creates the fifo BEFORE spawning Pi.\n * - Pi's first stdout event is `session_start` which the extension turns\n * into ready.json under $OVERDECK_HOME/agents/<agentId>/.\n * - Adapter MUST wait for ready.json before opening the writer side of\n * the fifo. Opening for write before a reader exists would block\n * indefinitely (or, with O_NONBLOCK, fail fast with ENXIO). We use\n * readiness-by-marker as the explicit synchronization point.\n *\n * IMPORTANT: this module performs blocking I/O (mkfifo + file open) inside\n * async helpers. NEVER call it from a dashboard server route handler — only\n * from spawnAgent and killAgent paths that run on dedicated workers.\n */\n\nimport { existsSync, mkdirSync, openSync, writeSync, closeSync, unlinkSync, constants as fsConstants } from 'node:fs'\nimport { join } from 'node:path'\nimport { exec } from 'node:child_process'\nimport { promisify } from 'node:util'\nimport { Data, Effect } from 'effect'\nimport { getOverdeckHome } from '../paths.js'\n\nconst execAsync = promisify(exec)\n\nexport interface PiFifoPaths {\n agentDir: string\n readyPath: string\n fifoPath: string\n}\n\nexport class PiNotReady extends Error {\n readonly code = 'PI_NOT_READY' as const\n constructor(message: string) {\n super(message)\n this.name = 'PiNotReady'\n }\n}\n\nexport function piFifoPaths(agentId: string, home?: string): PiFifoPaths {\n const overdeckHome = home === undefined ? getOverdeckHome() : join(home, '.overdeck')\n const agentDir = join(overdeckHome, 'agents', agentId)\n return {\n agentDir,\n readyPath: join(agentDir, 'ready.json'),\n fifoPath: join(agentDir, 'rpc.in'),\n }\n}async function createPiFifoPromise(agentId: string, home?: string): Promise<string> {\n const paths = piFifoPaths(agentId, home)\n mkdirSync(paths.agentDir, { recursive: true, mode: 0o700 })\n if (existsSync(paths.fifoPath)) {\n unlinkSync(paths.fifoPath)\n }\n // Node has no native mkfifo. Shell out to /usr/bin/mkfifo. -m 600 sets the\n // mode atomically. We single-quote the path to defang any unexpected shell\n // metacharacters even though agent ids are alphanumeric + dash by contract.\n await execAsync(`mkfifo -m 600 ${shellQuote(paths.fifoPath)}`)\n return paths.fifoPath\n}\n\n/**\n * Unlink the fifo. Safe to call when the fifo does not exist.\n */\nexport function destroyPiFifoSync(agentId: string, home?: string): void {\n const paths = piFifoPaths(agentId, home)\n try {\n unlinkSync(paths.fifoPath)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err\n }\n}\n\n/**\n * Write a single JSONL command to the agent's fifo.\n *\n * Throws PiNotReady (without blocking) when ready.json is not yet present —\n * the adapter is expected to wait for the Pi extension to write ready.json\n * on session_start before issuing RPC writes (hazard H1).\n *\n * Throws PiNotReady (also without blocking) when the fifo exists but Pi has\n * no read fd open — opening O_WRONLY|O_NONBLOCK on a reader-less fifo fails\n * with ENXIO. This is the typed signal that Pi died and the adapter should\n * recycle the agent rather than retry.\n *\n * The command is JSON-stringified and a single trailing newline is added so\n * Pi's JSONL parser sees one record per line.\n */\nexport function writePiCommandSync(agentId: string, command: unknown, home?: string): void {\n const paths = piFifoPaths(agentId, home)\n if (!existsSync(paths.readyPath)) {\n throw new PiNotReady(\n `Pi agent ${agentId}: ready.json not present yet (waiting for session_start)`,\n )\n }\n if (!existsSync(paths.fifoPath)) {\n throw new PiNotReady(`Pi agent ${agentId}: rpc.in fifo missing`)\n }\n\n let fd: number\n try {\n fd = openSync(paths.fifoPath, fsConstants.O_WRONLY | fsConstants.O_NONBLOCK)\n } catch (err) {\n const errno = (err as NodeJS.ErrnoException).code\n if (errno === 'ENXIO') {\n throw new PiNotReady(`Pi agent ${agentId}: no reader on rpc.in fifo (Pi exited?)`)\n }\n throw err\n }\n try {\n const line = `${JSON.stringify(command)}\\n`\n writeSync(fd, line)\n } finally {\n closeSync(fd)\n }\n}\n\n// Tiny shell-arg quoter. Acceptable inputs are absolute paths under HOME.\nfunction shellQuote(s: string): string {\n if (/^[A-Za-z0-9._/-]+$/.test(s)) return s\n return `'${s.replace(/'/g, `'\\\\''`)}'`\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n//\n// Additive Effect-channel variants of the fifo helpers above. Sync/promise\n// variants are preserved so the existing PiRuntime adapter keeps working.\n\n/** Tagged error for pi-fifo Effect variants. */\nexport class PiFifoError extends Data.TaggedError('PiFifoError')<{\n readonly agentId: string\n readonly stage: 'create' | 'write' | 'destroy'\n readonly message: string\n readonly cause?: unknown\n}> {}\n\n/** Effect variant of `createPiFifo`. */\nexport const createPiFifo = (\n agentId: string,\n home?: string,\n): Effect.Effect<string, PiFifoError> =>\n Effect.tryPromise({\n try: () => createPiFifoPromise(agentId, home),\n catch: (cause) =>\n new PiFifoError({\n agentId,\n stage: 'create',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n\n/**\n * Effect variant of `writePiCommand`. Lifts the sync FD ops via `Effect.try`.\n * The PiNotReady signal is preserved in the cause field so callers can branch.\n */\nexport const writePiCommand = (\n agentId: string,\n command: unknown,\n home?: string,\n): Effect.Effect<void, PiFifoError> =>\n Effect.try({\n try: () => writePiCommandSync(agentId, command, home),\n catch: (cause) =>\n new PiFifoError({\n agentId,\n stage: 'write',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n\n/** Effect variant of `destroyPiFifo`. */\nexport const destroyPiFifo = (\n agentId: string,\n home?: string,\n): Effect.Effect<void, PiFifoError> =>\n Effect.try({\n try: () => destroyPiFifoSync(agentId, home),\n catch: (cause) =>\n new PiFifoError({\n agentId,\n stage: 'destroy',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n","import { existsSync, mkdirSync, readFileSync, statfsSync, statSync, writeFileSync } from 'fs';\nimport { mkdir, rename as renameAsync, stat as statAsync, writeFile } from 'fs/promises';\nimport { exec } from 'child_process';\nimport { randomUUID } from 'crypto';\nimport { homedir } from 'os';\nimport { basename, dirname, join, resolve } from 'path';\nimport { promisify } from 'util';\nimport { parse as parseYaml } from 'yaml';\nimport { Effect } from 'effect';\nimport type { MemoryIdentity } from '@overdeck/contracts';\nimport { getClaudePermissionFlagsStringSync } from '../claude-permissions.js';\nimport { loadConfigSync as loadYamlConfig } from '../config-yaml.js';\nimport type { RoleEffort } from '../config-yaml.js';\nimport { ensureSessionContextBriefingFile } from '../briefing-freshness.js';\nimport { getClaudeAuthStatus } from '../claude-auth.js';\nimport { workspaceContextFile } from '../context-layers/layers.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport { initCodexHome } from '../runtimes/codex.js';\nimport { createOhmypiFifo, ohmypiFifoPaths, OhmypiNotReady, writeOhmypiCommandSync } from '../runtimes/ohmypi-fifo.js';\nimport { createPiFifo, piFifoPaths, PiNotReady, writePiCommandSync } from '../runtimes/pi-fifo.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { requireModelOverrideSync, shellQuoteModelIdSync } from '../model-validation.js';\nimport { getOpenAIAuthStatus } from '../openai-auth.js';\nimport { getOverdeckHome, packageRoot, resolveOhmypiExtensionPath, resolvePiExtensionPath } from '../paths.js';\nimport { getProviderForModelSync } from '../providers.js';\nimport type { AuthMode } from '../subscription-types.js';\nimport { capturePane, sessionExists } from '../tmux.js';\nimport { getAgentDir, getAgentStateSync, type Role } from './agent-state.js';\nimport { waitForReadySignal } from './identity.js';\nimport { CLI_PROXY_MODEL_ALIASES } from './provider-env.js';\n\nconst execAsync = promisify(exec);\nconst missingRoleDefinitionWarnings = new Set<string>();\n\n/**\n * Write an agent launcher script atomically. Every agent shares a fixed\n * `launcher.sh` path inside its agent dir, and spawn/resume/restart paths can\n * overlap (e.g. a Deacon recovery racing a manual restart). Writing in place\n * lets one path read a half-written script; write to a unique temp file then\n * rename (atomic on the same filesystem).\n */\nexport async function writeLauncherScriptAtomic(launcherScript: string, content: string): Promise<void> {\n const tmp = `${launcherScript}.${randomUUID()}.tmp`;\n await writeFile(tmp, content, { mode: 0o755 });\n await renameAsync(tmp, launcherScript);\n}\n\nexport async function claudeSystemPromptFiles(workspace: string, harness: RuntimeName | undefined): Promise<string[]> {\n const files: string[] = [];\n const contextFile = workspaceContextFile(workspace);\n try {\n await statAsync(contextFile);\n files.push(contextFile);\n } catch (error) {\n if (!isNodeNotFound(error)) throw error;\n }\n files.push(await ensureSessionContextBriefingFile());\n\n // PAN-1566: ohmypi also receives the rendered global context layer.\n const behavior = getHarnessBehavior(harness);\n if (behavior.contextLayerKind === 'pi') {\n const { piGlobalContextFile } = await import('../context-layers/index.js');\n const globalFile = piGlobalContextFile();\n if (existsSync(globalFile)) {\n files.unshift(globalFile);\n }\n }\n\n // PAN-1574: Codex receives its rendered global context layer (codex-global.md).\n if (behavior.contextLayerKind === 'codex') {\n const { codexGlobalContextFile } = await import('../context-layers/index.js');\n const globalFile = codexGlobalContextFile();\n if (existsSync(globalFile)) {\n files.unshift(globalFile);\n }\n }\n\n return files;\n}\n\nfunction isNodeNotFound(error: unknown): boolean {\n return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';\n}\n\n/**\n * BFS-walk a process subtree rooted at `rootPid` looking for the active agent\n * runtime. Returns true if any process in the tree matches the expected harness,\n * false if the tree exists but no match, false on any error.\n *\n * Used by sendAgentMessage zombie detection. pane_pid is the tmux pane's root\n * process, which is bash for work-agent launchers (`bash launcher.sh`) but can\n * be the runtime directly for specialists (`exec claude ...` / `exec pi ...`).\n */\nexport async function hasAgentRuntimeInSubtree(rootPid: string, harness: RuntimeName = 'claude-code'): Promise<boolean> {\n const expectedProcessNames = new Set(getHarnessBehavior(harness).processNames);\n const queue: string[] = [rootPid];\n const seen = new Set<string>();\n while (queue.length > 0) {\n const pid = queue.shift()!;\n if (seen.has(pid) || !/^\\d+$/.test(pid)) continue;\n seen.add(pid);\n\n try {\n const { stdout: comm } = await execAsync(`ps -p ${pid} -o comm=`);\n const name = comm.trim();\n if (expectedProcessNames.has(name)) return true;\n } catch {\n continue;\n }\n\n try {\n const { stdout: kids } = await execAsync(`pgrep -P ${pid}`);\n for (const kid of kids.trim().split('\\n').filter(Boolean)) {\n queue.push(kid);\n }\n } catch {\n // pgrep exits non-zero when there are no children — not an error.\n }\n }\n return false;\n}\n\nexport async function getPiLauncherFields(agentId: string, model: string): Promise<{\n harness: 'ohmypi';\n piExtensionPath: string;\n piFifoPath: string;\n piSessionDir: string;\n model: string;\n}> {\n const paths = piFifoPaths(agentId);\n await mkdir(paths.agentDir, { recursive: true, mode: 0o700 });\n const piExtensionPath = resolvePiExtensionPath();\n if (!piExtensionPath) {\n throw new Error(\n `Pi extension not built. Run: npm run build\\n(looked for dist/extensions/pi.js and packages/pi-extension/dist/index.js under ${packageRoot})`\n );\n }\n // PAN-1048 review feedback 006 (S1): thread the resolved role/workhorse model\n // through to buildPiCommand. The Pi launcher branch ignores baseCommand and\n // rebuilds from scratch starting with the literal `pi`, so the only way to\n // surface --model is via the launcher config's `model` field. Without this,\n // a Pi-backed role silently fell back to Pi's default model and ignored the\n // configured workhorse model entirely.\n return {\n harness: 'ohmypi',\n piExtensionPath,\n piFifoPath: await Effect.runPromise(createPiFifo(agentId)),\n piSessionDir: paths.agentDir,\n model,\n };\n}\n\nexport async function getOhmypiLauncherFields(agentId: string, model: string): Promise<{\n harness: 'ohmypi';\n piExtensionPath: string;\n piFifoPath: string;\n piSessionDir: string;\n model: string;\n}> {\n const paths = ohmypiFifoPaths(agentId);\n await mkdir(paths.agentDir, { recursive: true, mode: 0o700 });\n const ohmypiExtensionPath = resolveOhmypiExtensionPath();\n if (!ohmypiExtensionPath) {\n throw new Error(\n `ohmypi extension not built. Run: npm run build\\n(looked for dist/extensions/ohmypi.js and packages/ohmypi-extension/dist/index.js under ${packageRoot})`\n );\n }\n return {\n harness: 'ohmypi',\n piExtensionPath: ohmypiExtensionPath,\n piFifoPath: await Effect.runPromise(createOhmypiFifo(agentId)),\n piSessionDir: paths.agentDir,\n model,\n };\n}\n\nexport function getCodexLauncherFields(agentId: string, model: string, workspacePath?: string): {\n harness: 'codex';\n codexMode: 'work-tui';\n codexHome: string;\n codexSessionDir: string;\n model: string;\n} {\n const codexHome = join(homedir(), '.overdeck', 'agents', agentId, 'codex-home');\n // PAN-1803: codex work agents must inherit the user's configured codex\n // permission level (Settings → Permissions → Codex) and pre-trust the\n // workspace, EXACTLY like the conversation path\n // (routes/conversations.ts). Without trustedDir, codex shows its first-run\n // folder-trust / \"load project-local config?\" wizard and blocks the pane.\n // Without the permission mapping, work agents ignore the Settings choice\n // and run hardcoded never+workspace-write.\n const codexPermMode = loadYamlConfig().config.codex?.permissionMode ?? 'workspace';\n const approvalPolicy = codexPermMode === 'full-access' ? 'never' : 'on-request';\n const sandboxMode =\n codexPermMode === 'full-access' ? 'danger-full-access'\n : codexPermMode === 'read-only' ? 'read-only'\n : 'workspace-write';\n const approvalsReviewer = codexPermMode === 'auto-review' ? 'auto_review' : undefined;\n initCodexHome(codexHome, {\n trustedDir: workspacePath,\n approvalPolicy,\n sandboxMode,\n approvalsReviewer,\n });\n return {\n harness: 'codex',\n codexMode: 'work-tui',\n codexHome,\n codexSessionDir: join(codexHome, 'sessions'),\n model,\n };\n}\n\n/**\n * Wait for the Pi work-agent ready marker (`ready.json`) to appear.\n * Pi does not produce the Claude SessionStart hook signal, so resume/restart\n * paths must use this instead of `waitForReadySignal()`.\n */\nasync function waitForPiAgentReady(agentId: string, timeoutSec = 30): Promise<boolean> {\n const { readyPath } = piFifoPaths(agentId);\n const deadline = Date.now() + timeoutSec * 1000;\n while (Date.now() < deadline) {\n if (existsSync(readyPath)) return true;\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\nexport const OHMYPI_AGENT_READY_TIMEOUT_SECONDS = 120;\n\nasync function waitForOhmypiAgentReady(agentId: string, timeoutSec = OHMYPI_AGENT_READY_TIMEOUT_SECONDS): Promise<boolean> {\n const { readyPath } = ohmypiFifoPaths(agentId);\n const deadline = Date.now() + timeoutSec * 1000;\n while (Date.now() < deadline) {\n if (existsSync(readyPath)) return true;\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\n/**\n * PAN-2100: build a diagnostic suffix for an ohmypi readiness failure.\n *\n * An ENOSPC (disk-full) omp crash surfaces as an opaque \"did not become ready\"\n * timeout — omp cannot write `ready.json` when the disk is full. That exact\n * condition crashed flywheel-orchestrator (RUN-20) and the only record of it sat\n * unread in output.log for hours, sending the flywheel down a wrong \"kimi→ohmypi\n * misroute\" diagnosis. Surface the real signals at throw time: free disk on the\n * Overdeck-home filesystem and the tail of the agent's output.log. Best-effort:\n * every probe is independently guarded so diagnostics never mask the real error.\n */\nexport function describeOhmypiSpawnFailure(agentId: string): string {\n const parts: string[] = [];\n try {\n const { bavail, bsize } = statfsSync(getOverdeckHome());\n const freeMb = Math.round((bavail * bsize) / (1024 * 1024));\n parts.push(`freeDisk=${freeMb}MiB`);\n if (freeMb < 512) parts.push('(DISK NEARLY FULL — likely ENOSPC crash)');\n } catch { /* statfs best-effort */ }\n try {\n const logPath = join(getAgentDir(agentId), 'output.log');\n if (existsSync(logPath)) {\n const tail = readFileSync(logPath, 'utf8').slice(-1500).trim().split('\\n').slice(-8).join('\\n');\n if (tail) parts.push(`output.log tail:\\n${tail}`);\n }\n } catch { /* best-effort */ }\n return parts.length ? ` [${parts.join(' ')}]` : '';\n}\n\nasync function waitForCodexTuiReady(agentId: string, timeoutSec = 30): Promise<boolean> {\n const deadline = Date.now() + timeoutSec * 1000;\n while (Date.now() < deadline) {\n try {\n if (!(await Effect.runPromise(sessionExists(agentId)))) return false;\n const pane = await Effect.runPromise(capturePane(agentId, 80));\n // The codex TUI is ready when its input prompt (a line starting with the\n // `›` glyph) AND its status line (`<model> ... · <cwd>`) are both on\n // screen. PAN-1803: the previous check keyed off the first-run\n // trust-wizard markers (\"press enter to continue\") — but pre-trusting the\n // workspace (correctly) skips that wizard, so those markers never appear\n // and the kickoff never fired. Detect the actual ready prompt instead.\n const hasInputPrompt = /^\\s*[›>]\\s/m.test(pane);\n const hasStatusLine = /·\\s+[~/]/.test(pane);\n if (hasInputPrompt && hasStatusLine) {\n return true;\n }\n // Fallback: if pre-trust ever fails and the wizard does appear, treat its\n // markers as ready (the kickoff paste will dismiss + drive it).\n if (/press enter to continue/i.test(pane) || /ctrl[+-][cj]/i.test(pane)) {\n return true;\n }\n } catch {\n // The pane may not exist yet immediately after tmux session creation.\n }\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n}\n\nexport async function waitForPromptReady(agentId: string, harness: RuntimeName | undefined, timeoutSec = 30): Promise<boolean> {\n if (getHarnessBehavior(harness).readinessKind === 'codex-tui-prompt') return waitForCodexTuiReady(agentId, timeoutSec);\n return waitForReadySignal(agentId, timeoutSec);\n}\n\nexport function inferMemoryProjectId(workspacePath: string): string {\n const workspaceName = basename(workspacePath);\n if (workspaceName.startsWith('feature-')) return basename(dirname(dirname(workspacePath)));\n return workspaceName;\n}\n\n/**\n * Inject prompt-time memory context into a Pi prompt (PAN-1546).\n * Mirrors Claude Code's UserPromptSubmit hook behaviour: every follow-up\n * prompt gets relevant memory surfaced via RAG.\n */\nasync function injectPiPromptTimeMemory(agentId: string, prompt: string): Promise<string> {\n if (!prompt.trim()) return prompt;\n\n const agentState = getAgentStateSync(agentId);\n if (!agentState || !agentState.workspace || !agentState.issueId) {\n return prompt;\n }\n\n try {\n const identity: MemoryIdentity = {\n projectId: inferMemoryProjectId(agentState.workspace),\n workspaceId: basename(agentState.workspace),\n issueId: agentState.issueId,\n runId: agentId,\n sessionId: agentId,\n agentRole: agentState.role ?? 'work',\n agentHarness: agentState.harness ?? 'claude-code',\n };\n const { injectPromptTimeMemory } = await import('../memory/injection.js');\n const result = await injectPromptTimeMemory({ prompt, identity, surface: 'user-prompt' });\n if (result.context) {\n return `${result.context}\\n\\n---\\n\\n${prompt}`;\n }\n } catch (error) {\n console.warn(`[agents] Prompt-time memory injection failed for ${agentId}:`, error instanceof Error ? error.message : String(error));\n }\n return prompt;\n}\n\n/**\n * Prompt-time memory injection for a Pi CONVERSATION follow-up (PAN-1546).\n *\n * The rpc/work-agent path (injectPiPromptTimeMemory) reads agent-state.json,\n * which conversations do not have — they deliver follow-ups via tmux\n * paste-buffer, not the FIFO. This builds the memory identity from the\n * conversation's cwd + issueId instead. Only issue-linked conversations have\n * memory to surface; everything else (and any failure) passes through\n * unchanged so a memory hiccup can never block a message.\n */\nexport async function injectPiConversationMemory(\n opts: { cwd: string; issueId?: string | null; conversationName: string },\n prompt: string,\n): Promise<string> {\n if (!prompt.trim() || !opts.issueId || !opts.cwd) return prompt;\n try {\n const identity: MemoryIdentity = {\n projectId: inferMemoryProjectId(opts.cwd),\n workspaceId: basename(opts.cwd),\n issueId: opts.issueId,\n runId: opts.conversationName,\n sessionId: opts.conversationName,\n agentRole: 'work',\n agentHarness: 'ohmypi',\n };\n const { injectPromptTimeMemory } = await import('../memory/injection.js');\n const result = await injectPromptTimeMemory({ prompt, identity, surface: 'user-prompt' });\n if (result.context) {\n return `${result.context}\\n\\n---\\n\\n${prompt}`;\n }\n } catch (error) {\n console.warn(`[agents] Conversation memory injection failed for ${opts.conversationName}:`, error instanceof Error ? error.message : String(error));\n }\n return prompt;\n}\n\n/**\n * Deliver a prompt to a Pi work agent through the FIFO JSONL command protocol.\n * Pi never reads tmux input — pasting prompts there is a no-op as far as the\n * model is concerned. Throws if Pi never reached readiness within the timeout.\n */\nexport async function writePiAgentPrompt(agentId: string, prompt: string, timeoutSec = 30): Promise<void> {\n const augmentedPrompt = await injectPiPromptTimeMemory(agentId, prompt);\n const ready = await waitForPiAgentReady(agentId, timeoutSec);\n if (!ready) {\n throw new Error(`Pi agent ${agentId} did not become ready within ${timeoutSec}s`);\n }\n try {\n // steer: Pi delivers immediately when idle and queues mid-turn; a bare\n // prompt is rejected with AgentBusyError while a run is active.\n writePiCommandSync(agentId, { id: randomUUID(), type: 'prompt', message: augmentedPrompt, streamingBehavior: 'steer' });\n } catch (err) {\n if (err instanceof PiNotReady) {\n throw new Error(`Pi agent ${agentId} reader gone before prompt could be delivered: ${err.message}`);\n }\n throw err;\n }\n}\n\nexport async function writeOhmypiAgentPrompt(agentId: string, prompt: string, timeoutSec = OHMYPI_AGENT_READY_TIMEOUT_SECONDS): Promise<void> {\n const augmentedPrompt = await injectPiPromptTimeMemory(agentId, prompt);\n const ready = await waitForOhmypiAgentReady(agentId, timeoutSec);\n if (!ready) {\n throw new Error(`ohmypi agent ${agentId} did not become ready within ${timeoutSec}s${describeOhmypiSpawnFailure(agentId)}`);\n }\n try {\n // steer: omp delivers immediately when idle and queues mid-turn; a bare\n // prompt is rejected with AgentBusyError while a run is active.\n writeOhmypiCommandSync(agentId, { id: randomUUID(), type: 'prompt', message: augmentedPrompt, streamingBehavior: 'steer' });\n } catch (err) {\n if (err instanceof OhmypiNotReady) {\n throw new Error(`ohmypi agent ${agentId} reader gone before prompt could be delivered: ${err.message}`);\n }\n throw err;\n }\n}\n\nexport async function getProviderAuthMode(model: string): Promise<AuthMode | undefined> {\n const provider = getProviderForModelSync(model);\n if (provider.name === 'anthropic') {\n const authStatus = await Effect.runPromise(getClaudeAuthStatus());\n if (authStatus.hasAnthropicApiKey) return 'api-key';\n return authStatus.loggedIn ? 'subscription' : undefined;\n }\n\n if (provider.name === 'openai') {\n const { config } = loadYamlConfig();\n const authStatus = await Effect.runPromise(getOpenAIAuthStatus());\n return authStatus.loggedIn\n ? 'subscription'\n : (config.providerAuth?.openai ?? 'api-key');\n }\n\n if (provider.name === 'google') {\n const { config } = loadYamlConfig();\n return config.providerAuth?.google;\n }\n\n return undefined;\n}\n\n/**\n * Build the base command that the launcher will exec for an agent.\n *\n * The `harness` parameter (PAN-636) selects between Claude Code (default)\n * and ohmypi/Pi. When the harness uses the ohmypi RPC command, the function\n * short-circuits to a\n * `omp --mode rpc --model <model>` line; the launcher generator then layers\n * --session-dir, --extension, --no-context-files, and the stdin-from-fifo\n * redirect on top via generateLauncherScript. The `agentName` (PAN-982:\n * --name) and `agentDefinition` (PAN-982: --agent) parameters only apply to the\n * Claude Code path — ohmypi has no agent-definition system.\n */\nexport async function getAgentRuntimeBaseCommand(\n model: string,\n agentName?: string,\n agentDefinition?: string,\n harness: RuntimeName = 'claude-code',\n effort?: RoleEffort,\n): Promise<string> {\n const validatedModel = requireModelOverrideSync(model);\n const quotedModel = shellQuoteModelIdSync(validatedModel);\n const behavior = getHarnessBehavior(harness);\n if (behavior.launchCommandKind === 'ohmypi-rpc') {\n return `omp --mode rpc --model ${quotedModel}`;\n }\n if (behavior.launchCommandKind === 'codex-work-tui') {\n // buildCodexCommand in launcher-generator builds the full Codex command;\n // return a stub base command so the launcher generator can short-circuit.\n return `codex`;\n }\n\n // Integration tests can inject a harmless harness command so a leaked or\n // intentionally-real tmux session never runs the production `claude` binary.\n if (process.env.OVERDECK_TEST_HARNESS_COMMAND) {\n return process.env.OVERDECK_TEST_HARNESS_COMMAND;\n }\n\n const provider = getProviderForModelSync(validatedModel);\n // PAN-982: --name <agentId> creates a human-readable Claude session name discoverable via\n // `claude --resume`.\n const nameFlag = agentName ? ` --name ${agentName}` : '';\n\n // Classify agentDefinition. A registered agent NAME (e.g. pan-review-agent,\n // resolved from ~/.claude/agents/*.md) still launches via `--agent <name>` —\n // Claude Code 2.1.195 resolves names natively, and its frontmatter owns\n // permissionMode/tools/effort. A role definition FILE (roles/<role>.md) can no\n // longer be passed to --agent (PAN-2087: 2.1.195 dropped --agent file support),\n // so its frontmatter-stripped body is injected as an appended system prompt.\n const defIsRoleFile = !!agentDefinition\n && existsSync(resolve(agentDefinition))\n && statSync(resolve(agentDefinition)).isFile();\n\n if (agentDefinition && !defIsRoleFile) {\n // Registered agent NAME — unchanged. --model still wins over any frontmatter\n // default model; permissionMode/effort come from the named agent's frontmatter.\n const agentFlag = ` --agent ${agentDefinition}`;\n const effortFlag = effort ? ` --effort ${effort}` : '';\n if (provider.name === 'openai' && (await getProviderAuthMode(validatedModel)) === 'subscription') {\n const resolvedModel = CLI_PROXY_MODEL_ALIASES[validatedModel] ?? validatedModel;\n return `claude${agentFlag} --model ${shellQuoteModelIdSync(resolvedModel)}${effortFlag}${nameFlag}`;\n }\n return `claude${agentFlag} --model ${quotedModel}${effortFlag}${nameFlag}`;\n }\n\n // PAN-2087: role FILE (inject body + effort) or no definition. permissionMode\n // comes from the global permission flags; the role's hooks fire globally via\n // ~/.claude/settings.json. roleInject folds in effort when a role file is\n // present; otherwise --effort is passed directly.\n const permissionFlags = getClaudePermissionFlagsStringSync();\n const roleInject = defIsRoleFile ? roleSystemPromptInjectionSync(agentDefinition as string, effort) : '';\n const effortFlag = (!defIsRoleFile && effort) ? ` --effort ${effort}` : '';\n\n // OpenAI subscription → local CLIProxyAPI sidecar exposes an\n // Anthropic-compatible /v1/messages endpoint, so Claude Code can drive\n // gpt-* models directly via ANTHROPIC_BASE_URL (no wrapper process).\n // The provider env vars are injected separately by getProviderEnvForModel.\n if (provider.name === 'openai' && (await getProviderAuthMode(validatedModel)) === 'subscription') {\n // CLIProxy supports gpt-5.x but not the -pro variant; map aliases to real names.\n const resolvedModel = CLI_PROXY_MODEL_ALIASES[validatedModel] ?? validatedModel;\n return `claude ${permissionFlags}${roleInject} --model ${shellQuoteModelIdSync(resolvedModel)}${effortFlag}${nameFlag}`;\n }\n\n return `claude ${permissionFlags}${roleInject} --model ${quotedModel}${effortFlag}${nameFlag}`;\n}\n\n/**\n * Resolve the role's Claude-harness agent-definition path.\n *\n * Returns the file Claude Code's `--agent` flag should load to seed the run\n * with the role's frontmatter (permissions, tools, hooks, default model).\n * Returns `null` when the role does not have a Claude agent definition — for\n * example, the review convoy sub-roles, whose prompts are harness-agnostic\n * templates the orchestrator inlines into the spawn message (see\n * `buildConvoyPrompt` in `src/lib/cloister/review-agent.ts`). Sub-role\n * templates live in `roles/review-<subRole>.md`; they are deliberately not\n * loaded via `--agent` so the same content drives Claude Code, Pi, and\n * future harnesses uniformly and never auto-discovers as an ambient Claude\n * subagent inside a work agent's session.\n *\n * Without a sub-role the return is always the top-level role file; callers\n * can rely on the overload to avoid null-handling on that path.\n */\nexport function roleAgentDefinitionPath(role: Role): string;\nexport function roleAgentDefinitionPath(role: Role, subRole: string | undefined): string | null;\nexport function roleAgentDefinitionPath(role: Role, subRole?: string): string | null {\n if (role === 'review' && subRole) {\n return null;\n }\n return join(packageRoot, 'roles', `${role}.md`);\n}\n\n/**\n * PAN-2087: Claude Code 2.1.195 removed support for passing an agent DEFINITION\n * FILE to `--agent` — it now resolves only registered agent NAMES (from\n * `~/.claude/agents/*.md`). Role definitions live as files (`roles/<role>.md`),\n * so `claude --agent roles/<role>.md` started failing with \"agent not found\" and\n * the agent exited on launch. Instead of `--agent`, inject the role's BODY as an\n * appended system prompt and translate its `effort:` frontmatter to `--effort`.\n *\n * Everything the role frontmatter used to supply via `--agent` is reconstituted\n * as flags:\n * - body → `--append-system-prompt-file` (frontmatter stripped)\n * - `effort:` → `--effort`\n * - `mcpServers:` → a generated `--mcp-config` JSON (PAN-2090)\n * - `tools:` → `--allowedTools` (PAN-2090), with each declared MCP server\n * appended as `mcp__<name>` so the role can still call its own MCP tools —\n * otherwise the strict allow-list would block e.g. playwright for `test`.\n *\n * The role frontmatter's hooks are deliberately NOT reconstituted: they are\n * installed globally in `~/.claude/settings.json` and fire for every claude\n * session, and per the PAN-1402 note Claude Code never honored path-form\n * `--agent` frontmatter hooks anyway. permissionMode is covered by the\n * launcher's permission flags. Generated artifacts are cached under\n * `~/.overdeck/role-prompts/` and referenced by absolute path, so they do not\n * depend on `roles/` existing in the agent's cwd.\n *\n * Returns the flags (with a leading space) to splice in place of the old\n * `--agent <file>` flag, or '' when the definition file is missing.\n */\nexport function roleSystemPromptInjectionSync(definitionPath: string, explicitEffort?: RoleEffort): string {\n const abs = resolve(definitionPath);\n if (!existsSync(abs)) return '';\n const raw = readFileSync(abs, 'utf8');\n let body = raw;\n let frontmatter: Record<string, unknown> = {};\n const fm = raw.match(/^---\\r?\\n([\\s\\S]*?)\\r?\\n---\\r?\\n?/);\n if (fm) {\n body = raw.slice(fm[0].length);\n try {\n const parsed = parseYaml(fm[1]);\n if (parsed && typeof parsed === 'object') frontmatter = parsed as Record<string, unknown>;\n } catch {\n // Malformed frontmatter — fall back to body-only injection.\n }\n }\n\n const dir = join(getOverdeckHome(), 'role-prompts');\n mkdirSync(dir, { recursive: true });\n const stem = basename(definitionPath).replace(/\\.md$/, '');\n const outPath = join(dir, `${stem}.md`);\n writeFileSync(outPath, body);\n\n const flags: string[] = [` --append-system-prompt-file '${outPath}'`];\n\n const effort = explicitEffort ?? (typeof frontmatter.effort === 'string' ? (frontmatter.effort as RoleEffort) : undefined);\n if (effort) flags.push(` --effort ${effort}`);\n\n // mcpServers: a YAML list of single-key maps ({ playwright: { … } }). Flatten\n // into one { mcpServers: { name: def } } config loaded via --mcp-config. It is\n // additive (the launcher's channels --mcp-config still applies; no\n // --strict-mcp-config), so the role keeps any project/global MCP servers too.\n const mcpNames: string[] = [];\n if (Array.isArray(frontmatter.mcpServers) && frontmatter.mcpServers.length > 0) {\n const servers: Record<string, unknown> = {};\n for (const entry of frontmatter.mcpServers) {\n if (entry && typeof entry === 'object') {\n for (const [name, def] of Object.entries(entry as Record<string, unknown>)) {\n servers[name] = def;\n mcpNames.push(name);\n }\n }\n }\n if (Object.keys(servers).length > 0) {\n const mcpPath = join(dir, `${stem}.mcp.json`);\n writeFileSync(mcpPath, JSON.stringify({ mcpServers: servers }, null, 2));\n flags.push(` --mcp-config '${mcpPath}'`);\n }\n }\n\n // tools: allow-list → --allowedTools (comma-joined single arg so the variadic\n // never swallows later flags). Append `mcp__<server>` for each declared MCP\n // server so its tools survive the strict allow-list.\n if (Array.isArray(frontmatter.tools)) {\n const toolNames = frontmatter.tools.filter((t): t is string => typeof t === 'string');\n if (toolNames.length > 0) {\n const allowed = [...toolNames, ...mcpNames.map((n) => `mcp__${n}`)];\n flags.push(` --allowedTools '${allowed.join(',')}'`);\n }\n }\n\n return flags.join('');\n}\n\n/** Build a Claude/ohmypi base command for role-based runs. */\nexport async function getRoleRuntimeBaseCommand(\n model: string,\n agentName: string,\n role: Role,\n harness: RuntimeName = 'claude-code',\n subRole?: string,\n effort?: RoleEffort,\n): Promise<string> {\n const validatedModel = requireModelOverrideSync(model);\n const quotedModel = shellQuoteModelIdSync(validatedModel);\n const behavior = getHarnessBehavior(harness);\n if (behavior.launchCommandKind === 'ohmypi-rpc') {\n return `omp --mode rpc --model ${quotedModel}`;\n }\n if (behavior.launchCommandKind === 'codex-work-tui') {\n return `codex`;\n }\n\n // Integration tests can inject a harmless harness command so a leaked or\n // intentionally-real tmux session never runs the production `claude` binary.\n if (process.env.OVERDECK_TEST_HARNESS_COMMAND) {\n return process.env.OVERDECK_TEST_HARNESS_COMMAND;\n }\n\n const provider = getProviderForModelSync(validatedModel);\n const requestedDefinitionPath = roleAgentDefinitionPath(role, subRole);\n const definitionPath = requestedDefinitionPath && existsSync(resolve(requestedDefinitionPath))\n ? requestedDefinitionPath\n : null;\n if (requestedDefinitionPath && !definitionPath && !missingRoleDefinitionWarnings.has(requestedDefinitionPath)) {\n missingRoleDefinitionWarnings.add(requestedDefinitionPath);\n console.warn(\n `[agents] Role definition ${resolve(requestedDefinitionPath)} does not exist; launching ${role} without its role system prompt`,\n );\n }\n // PAN-2087: inject the role body (+ effort frontmatter) as an appended system\n // prompt instead of `--agent <file>` (Claude Code dropped --agent file support).\n const roleInject = definitionPath ? roleSystemPromptInjectionSync(definitionPath, effort) : '';\n const nameFlag = ` --name ${agentName}`;\n const effortFlag = (!definitionPath && effort) ? ` --effort ${effort}` : '';\n // permissionMode now comes from the global permission flags for EVERY role\n // (the old --agent path relied on role frontmatter, which Claude Code no longer\n // applies). This honors the user's bypass/auto setting uniformly.\n const permissionFlags = ` ${getClaudePermissionFlagsStringSync()}`;\n\n // PAN-1557: convoy sub-reviewers now run as interactive, attachable sessions\n // (prompt delivered via tmux, completion signalled by the Stop-hook) instead\n // of headless `claude --print`. No role uses --print anymore.\n const printFlag = '';\n\n if (provider.name === 'openai' && (await getProviderAuthMode(validatedModel)) === 'subscription') {\n const resolvedModel = CLI_PROXY_MODEL_ALIASES[validatedModel] ?? validatedModel;\n return `claude${printFlag}${roleInject}${permissionFlags} --model ${shellQuoteModelIdSync(resolvedModel)}${effortFlag}${nameFlag}`;\n }\n\n return `claude${printFlag}${roleInject}${permissionFlags} --model ${quotedModel}${effortFlag}${nameFlag}`;\n}\n","import type { RuntimeName } from '../runtimes/types.js';\nimport type { VBriefDifficulty, VBriefItem, VBriefItemKind } from '../vbrief/types.js';\nimport type { TierDefinition } from './tier-table.js';\n\n/**\n * Model + harness resolution chain for tiered execution (PAN-1791, folding in\n * PAN-1196). Resolution order for each bead:\n *\n * 1. per-bead `metadata.model` override — wins for the model; the harness\n * (and dispatch lane) still comes from the rest of the chain\n * 2. `byKind[item.metadata.kind]` — subject-matter routing to a tier name\n * 3. `difficultyToTier[item.metadata.difficulty]` — the tier table\n * 4. `roleDefault` — the configured role default\n *\n * When nothing resolves and no default is configured, resolveTier throws a\n * named ResolveTierError. It NEVER falls back to a hardcoded model literal\n * (no-hardcoded-model-fallbacks rule).\n *\n * Pure function over already-validated config — no spawning, no I/O here.\n */\n\n/**\n * Reserved tierName values naming the resolution source when the result did\n * not come from a configured tier. Configured tier names must not use these.\n */\nexport const OVERRIDE_TIER_NAME = 'override';\nexport const ROLE_DEFAULT_TIER_NAME = 'role-default';\n\nexport interface ResolvedTier {\n tierName: string;\n model: string;\n harness: RuntimeName;\n}\n\nexport interface RoleDefaultTier {\n model: string;\n harness: RuntimeName;\n}\n\n/**\n * Structural subset of ValidatedTieredExecutionConfig (tiers +\n * difficultyToTier) plus the chain-specific inputs. Callers can spread a\n * validated tier-table config and attach byKind/roleDefault.\n */\nexport interface ResolveTierConfig {\n tiers: Record<string, TierDefinition>;\n difficultyToTier: Partial<Record<VBriefDifficulty, string>>;\n /** Subject-matter routing: item kind -> configured tier name. */\n byKind?: Partial<Record<VBriefItemKind, string>>;\n roleDefault?: RoleDefaultTier;\n}\n\nexport class ResolveTierError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ResolveTierError';\n }\n}\n\nfunction itemLabel(item: Pick<VBriefItem, 'id' | 'title'>): string {\n return item.id || item.title || '<unknown item>';\n}\n\nfunction lookupTier(\n config: ResolveTierConfig,\n tierName: string,\n source: string,\n item: Pick<VBriefItem, 'id' | 'title'>,\n): ResolvedTier {\n const tier = config.tiers[tierName];\n if (!tier) {\n throw new ResolveTierError(\n `${source} routes item '${itemLabel(item)}' to tier '${tierName}', but no such tier is configured`,\n );\n }\n return { tierName, model: tier.model, harness: tier.harness };\n}\n\n/** The chain below the per-bead override: byKind -> byDifficulty -> role default. */\nfunction resolveBaseTier(\n item: Pick<VBriefItem, 'id' | 'title' | 'metadata'>,\n config: ResolveTierConfig,\n): ResolvedTier | undefined {\n const kind = item.metadata?.kind;\n const kindTierName = kind ? config.byKind?.[kind] : undefined;\n if (kindTierName) {\n return lookupTier(config, kindTierName, `byKind['${kind}']`, item);\n }\n\n const difficulty = item.metadata?.difficulty;\n const difficultyTierName = difficulty ? config.difficultyToTier[difficulty] : undefined;\n if (difficultyTierName) {\n return lookupTier(config, difficultyTierName, `difficultyToTier['${difficulty}']`, item);\n }\n\n if (config.roleDefault) {\n return {\n tierName: ROLE_DEFAULT_TIER_NAME,\n model: config.roleDefault.model,\n harness: config.roleDefault.harness,\n };\n }\n\n return undefined;\n}\n\nexport function resolveTier(\n item: Pick<VBriefItem, 'id' | 'title' | 'metadata'>,\n config: ResolveTierConfig,\n): ResolvedTier {\n const base = resolveBaseTier(item, config);\n\n const override = item.metadata?.model;\n if (typeof override === 'string' && override.length > 0) {\n if (!base) {\n throw new ResolveTierError(\n `item '${itemLabel(item)}' sets metadata.model '${override}' but no tier resolves its harness: `\n + 'no byKind match, no byDifficulty tier, and no role default configured',\n );\n }\n return { tierName: OVERRIDE_TIER_NAME, model: override, harness: base.harness };\n }\n\n if (!base) {\n throw new ResolveTierError(\n `no tier/model configured for item '${itemLabel(item)}': no metadata.model override, `\n + 'no byKind match, no byDifficulty tier, and no role default configured',\n );\n }\n\n return base;\n}\n","/**\n * PAN-2397 W1 — Always Tiered: the single staffing resolver.\n *\n * Every bead dispatch resolves WHO runs it through this one function. When an\n * explicit tier table is enabled (globally or via the plan's per-issue\n * override), staffing comes from the tier resolution chain. Otherwise staffing\n * comes from the IMPLICIT tier table: a degenerate one-tier world whose\n * `default` tier is exactly today's roles.work resolution (same resolveModel\n * call, same spawnKey, so Role-Models work distributions keep their\n * deterministic per-spawn behavior). \"Non-tiered execution\" is therefore not\n * a separate code path — it is this degenerate configuration.\n *\n * Fallthrough rule (D-explicit-gap): when the explicit table cannot place a\n * bead (no difficulty, no by_kind match, no per-bead override — the\n * ResolveTierError case), staffing falls through to the implicit tier. That\n * reproduces the historical behavior where such beads ran on the role-default\n * model, without callers special-casing it.\n */\n\nimport type { RuntimeName } from '../runtimes/types.js';\nimport type { VBriefItem } from '../vbrief/types.js';\nimport { loadConfigSync as loadYamlConfig } from '../config-yaml.js';\nimport type { NormalizedConfig } from '../config-yaml/schema.js';\nimport { resolveModel } from '../config-yaml/roles.js';\nimport { requireModelOverrideSync } from '../model-validation.js';\nimport { getBuiltInDefaultHarness, getProviderForModelSync } from '../providers.js';\nimport { fmix32, fnv1a32 } from '../config-yaml/percent.js';\nimport { resolveTier } from './resolve-tier.js';\nimport { resolveTieredExecutionEnabled, type TierDistributionEntry } from './tier-table.js';\n\nexport const IMPLICIT_TIER_NAME = 'default';\n\nexport interface Staffing {\n tierName: string;\n model: string;\n harness: RuntimeName;\n /** true when staffing came from the implicit roles.work-derived tier. */\n implicit: boolean;\n}\n\nexport interface ResolveStaffingOptions {\n /** plan.metadata — carries the per-issue tiered_execution override. */\n planMetadata?: { [key: string]: unknown };\n /**\n * Deterministic key for Role-Models percent distributions in the implicit\n * tier (spawn.ts uses `${role}:${issueId}`). Required for byte-identical\n * behavior with the historical determineModel path.\n */\n spawnKey?: string;\n /** Injectable config for tests; defaults to the loaded config. */\n config?: Pick<NormalizedConfig, 'roles' | 'workhorses' | 'tieredExecution' | 'providerHarnesses'>;\n}\n\n/** Provider-default harness for a model (PAN-1984: harness is derived from the\n * model's provider — per-provider setting else built-in default). */\nexport function providerDefaultHarnessSync(\n model: string,\n config: Pick<NormalizedConfig, 'providerHarnesses'>,\n): RuntimeName {\n const provider = getProviderForModelSync(model).name;\n return config.providerHarnesses?.[provider] ?? getBuiltInDefaultHarness(provider);\n}\n\n/** The implicit tier: roles.work resolution as a Staffing. Fails loudly when\n * the work role is unresolvable — never a hardcoded fallback. */\nexport function resolveImplicitStaffing(\n config: Pick<NormalizedConfig, 'roles' | 'workhorses' | 'providerHarnesses'>,\n spawnKey?: string,\n): Staffing {\n const model = requireModelOverrideSync(resolveModel('work', undefined, config, spawnKey));\n return {\n tierName: IMPLICIT_TIER_NAME,\n model,\n harness: providerDefaultHarnessSync(model, config),\n implicit: true,\n };\n}\n\n/**\n * THE staffing resolver (FR-1). Explicit tier table when enabled for the\n * issue; implicit roles.work tier otherwise — and as the fallthrough when the\n * explicit table cannot place the bead.\n */\nexport function resolveStaffing(\n item: Pick<VBriefItem, 'id' | 'title' | 'metadata'>,\n options: ResolveStaffingOptions = {},\n): Staffing {\n const config = options.config ?? loadYamlConfig().config;\n const tiered = config.tieredExecution;\n\n if (tiered && resolveTieredExecutionEnabled(tiered, options.planMetadata)) {\n try {\n const tier = resolveTier(item, tiered);\n // PAN-2391: a distribution tier spreads beads across weighted\n // model+harness entries. Selection is deterministic per bead so\n // replay/re-resolution always lands on the same entry. A per-bead\n // metadata.model override never reaches here with the tier's name\n // (resolveTier returns the override pseudo-tier), so overrides win.\n const distribution = tiered.tiers?.[tier.tierName]?.distribution;\n if (distribution && distribution.length > 0) {\n const entry = pickDistributionEntry(distribution, `${options.spawnKey ?? ''}:${item.id}`);\n return { tierName: tier.tierName, model: entry.model, harness: entry.harness, implicit: false };\n }\n return { tierName: tier.tierName, model: tier.model, harness: tier.harness, implicit: false };\n } catch {\n // Explicit table cannot place this bead — fall through to the implicit\n // tier (historical role-default behavior, now uniform).\n }\n }\n\n return resolveImplicitStaffing(config, options.spawnKey);\n}\n\n/** Deterministic weighted pick (D6): FNV-1a of the selection key → bucket in\n * [0, 100). Same key always selects the same entry. */\nexport function pickDistributionEntry(\n entries: readonly TierDistributionEntry[],\n selectionKey: string,\n): TierDistributionEntry {\n const total = entries.reduce((sum, entry) => sum + entry.weight, 0);\n const bucket = fmix32(fnv1a32(selectionKey)) % total;\n let cursor = 0;\n for (const entry of entries) {\n cursor += entry.weight;\n if (bucket < cursor) return entry;\n }\n return entries[entries.length - 1]!;\n}\n","import { existsSync } from 'fs';\nimport { basename, join } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { getClaudePermissionFlagsStringSync } from '../claude-permissions.js';\nimport { loadConfigSync } from '../config.js';\nimport { loadConfigSync as loadYamlConfig } from '../config-yaml.js';\nimport type { RoleEffort } from '../config-yaml.js';\nimport { getFlywheelActiveRunIdSync } from '../overdeck/control-settings.js';\nimport { createTrackerFromConfig, createTracker } from '../tracker/factory.js';\nimport type { IssueState } from '../tracker/interface.js';\nimport { findProjectByPathSync, getIssuePrefix, resolveProjectFromIssueSync } from '../projects.js';\nimport { getWorkspaceStackHealth } from '../workspace/stack-health.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { getProviderForModelSync, setupCredentialFileAuthSync, clearCredentialFileAuthSync } from '../providers.js';\nimport type { ModelId, ComplexityLevel } from '../settings.js';\nimport { requireModelOverrideSync } from '../model-validation.js';\nimport type { MemoryIdentity } from '@overdeck/contracts';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { readWorkspacePlanSync } from '../vbrief/io.js';\nimport { getDispatchableItems } from '../vbrief/dag.js';\nimport { type Role } from './agent-state.js';\nimport type { TierAssignment } from './dispatch-tier.js';\nimport { resolveStaffing } from './staffing.js';\nimport { resolveTieredExecutionEnabled, resolveTieredExecutionEnabledForIssue } from './tier-table.js';\nimport {\n buildCavemanExports,\n getProviderEnvForModel,\n getProviderExportsForModel,\n} from './provider-env.js';\nimport {\n claudeSystemPromptFiles,\n getAgentRuntimeBaseCommand,\n getCodexLauncherFields,\n getOhmypiLauncherFields,\n inferMemoryProjectId,\n roleAgentDefinitionPath,\n roleSystemPromptInjectionSync,\n} from './runtime-command.js';\n\nexport type FlywheelSpawnEnv = {\n OVERDECK_FLYWHEEL_RUN_ID?: string;\n OVERDECK_FLYWHEEL_AGENT_ROLE?: Role;\n};\n\nexport function normalizeFlywheelRunId(runId: string | null | undefined): string | undefined {\n if (!runId) return undefined;\n const trimmed = runId.trim();\n return /^RUN-\\d+$/.test(trimmed) ? trimmed : undefined;\n}\n\nexport function resolveFlywheelSpawnEnv(role: Role, runIdOverride?: string | null): FlywheelSpawnEnv {\n const runId = normalizeFlywheelRunId(runIdOverride ?? getFlywheelActiveRunIdSync());\n return runId\n ? { OVERDECK_FLYWHEEL_RUN_ID: runId, OVERDECK_FLYWHEEL_AGENT_ROLE: role }\n : {};\n}\n\nexport function flywheelEnvExports(env: FlywheelSpawnEnv): string[] {\n return [\n env.OVERDECK_FLYWHEEL_RUN_ID ? `export OVERDECK_FLYWHEEL_RUN_ID=${env.OVERDECK_FLYWHEEL_RUN_ID}` : undefined,\n env.OVERDECK_FLYWHEEL_AGENT_ROLE ? `export OVERDECK_FLYWHEEL_AGENT_ROLE=${env.OVERDECK_FLYWHEEL_AGENT_ROLE}` : undefined,\n ].filter((value): value is string => value !== undefined);\n}\n\nexport interface SpawnOptions {\n issueId: string;\n workspace: string;\n /** Coding-agent harness (PAN-636). Defaults to 'claude-code' when omitted. */\n harness?: RuntimeName;\n model?: string;\n prompt?: string;\n /**\n * Spawn role. Defaults to 'work'. The 'strike' role is the bypass path that\n * skips plan/review/test/ship and lands directly on main — see roles/strike.md.\n * Strike sessions are named `strike-<issue-id>` instead of `agent-<issue-id>`.\n */\n role?: 'work' | 'strike' | 'knowledge';\n difficulty?: ComplexityLevel;\n agentType?: 'review-agent' | 'test-agent' | 'merge-agent' | 'work-agent';\n\n // Work type system (PAN-118)\n phase?: 'exploration' | 'implementation' | 'testing' | 'documentation' | 'review-response' | 'planning' | 'synthesis';\n workType?: string; // Explicit work type ID (overrides phase-based detection)\n\n /**\n * Optional registered slot agent id. Omitted for the default one-work-agent\n * path; set only by spawnRun when launching a per-item slot.\n */\n agentId?: string;\n /** Registered swarm slot index for per-item work-agent spawning. */\n slotIndex?: number;\n /** vBRIEF item id assigned to the registered swarm slot. */\n slotItemId?: string;\n allowHost?: boolean;\n flywheelRunId?: string;\n /** Claude Code `--effort` level for the spawned session (work/strike). */\n effort?: RoleEffort;\n}\n\nexport interface SpawnRunOptions {\n workspace?: string;\n harness?: RuntimeName;\n model?: string;\n prompt?: string;\n agentId?: string;\n /**\n * Sub-role within the review convoy (PAN-1059).\n * When set alongside role='review', each convoy reviewer gets its own\n * isolated tmux session using the code-review-<subRole> agent definition.\n * Values: 'security' | 'correctness' | 'performance' | 'requirements'\n */\n subRole?: string;\n /**\n * Review convoy wiring (PAN-977). When spawning a review sub-role, the\n * synthesis agent id and the reviewer's output path are passed in up front\n * so the generated launcher can own the REVIEWER_READY/FAILED/TIMEOUT signal\n * deterministically on process exit. Persisted onto AgentState too.\n */\n reviewSynthesisAgentId?: string;\n reviewOutputPath?: string;\n allowHost?: boolean;\n registerConversation?: boolean;\n effort?: RoleEffort;\n extraEnvExports?: string[];\n resumeSessionId?: string;\n flywheelRunId?: string;\n /** 1-based registered slot index for per-item work-agent spawning. */\n slotIndex?: number;\n /** vBRIEF item id assigned to this registered slot. Required with slotIndex. */\n slotItemId?: string;\n /** Optional per-spawn cap for registered work-agent slots. Defaults to the work-agent governor cap. */\n maxRegisteredSlots?: number;\n}\n\nexport interface RegisteredSlotSpawn {\n agentId: string;\n branch: string;\n workspace: string;\n slotIndex: number;\n slotItemId: string;\n}\n\n/**\n * Thread a tiered-execution tier assignment into spawn options (PAN-1791).\n * When the assignment resolved a tier, its model+harness replace the parent\n * defaults so the dispatched bead runs on the tier its difficulty selected.\n * With no assignment (tiering disabled), the options pass through unchanged.\n */\nexport function applyTierAssignment<T extends Pick<SpawnRunOptions, 'model' | 'harness'>>(\n options: T,\n assignment?: TierAssignment,\n): T {\n if (!assignment?.model) return options;\n return {\n ...options,\n model: assignment.model,\n harness: assignment.harness ?? options.harness,\n };\n}\n\nexport function resolveRegisteredSlotSpawn(\n issueId: string,\n baseWorkspace: string,\n options: Pick<SpawnRunOptions, 'slotIndex' | 'slotItemId'>,\n): RegisteredSlotSpawn | null {\n const { slotIndex, slotItemId: slotItemIdRaw } = options;\n if (slotIndex === undefined && slotItemIdRaw === undefined) return null;\n if (slotIndex === undefined || slotItemIdRaw === undefined) {\n throw new Error('Registered slot spawn requires both slotIndex and slotItemId.');\n }\n if (!Number.isInteger(slotIndex) || slotIndex < 1) {\n throw new Error(`Registered slot index must be a positive integer; got ${slotIndex}.`);\n }\n const slotItemId = slotItemIdRaw.trim();\n if (!slotItemId) {\n throw new Error('Registered slot spawn requires a non-empty slotItemId.');\n }\n\n const issueLower = issueId.toLowerCase();\n return {\n agentId: `agent-${issueLower}-slot-${slotIndex}`,\n branch: `feature/${issueLower}-slot-${slotIndex}`,\n workspace: `${baseWorkspace}-slot-${slotIndex}`,\n slotIndex,\n slotItemId,\n };\n}\n\nexport interface SlotTierSpawnParams {\n model?: string;\n harness?: RuntimeName;\n tierName?: string;\n /** PAN-2397: true when staffing came from the implicit roles.work tier.\n * Implicit staffing intentionally omits `harness` so the spawn keeps its\n * historical harness handling (provider-default derived from the model). */\n implicit?: boolean;\n}\n\n/**\n * Tiered-execution model resolution for a registered slot spawn (PAN-1791,\n * fixing PAN-1196's \"difficulty captured and ignored\"). When tiered execution\n * is enabled for the plan, resolve the slot item's tier through\n * assignDispatchTier and return its (model, harness) as spawn params so the\n * dispatched bead runs on the tier its difficulty selected.\n *\n * Returns {} — leaving the existing model resolution untouched — when:\n * - tiered execution is disabled (globally and per-plan), or\n * - an explicit per-spawn model override was passed (same precedence as\n * determineModel: an explicit override outranks routing), or\n * - the item carries no difficulty and no per-bead model override, in which\n * case the chain's role-default step IS the existing configured\n * role-default resolution in determineModel (nothing hardcoded here).\n */\nexport function resolveSlotTierSpawnParams(\n baseWorkspace: string,\n slotItemId: string,\n explicitModel?: string,\n spawnKey?: string,\n): SlotTierSpawnParams {\n // An explicit per-spawn model override outranks all staffing (same\n // precedence as determineModel).\n if (explicitModel) return {};\n const doc = readWorkspacePlanSync(baseWorkspace);\n if (!doc) return {};\n const planMetadata = doc?.plan?.metadata;\n\n // Extract issueId from workspace path: feature-<issueId>\n const workspaceName = basename(baseWorkspace);\n const issueIdMatch = workspaceName.match(/^feature-(.+)$/i);\n const issueId = issueIdMatch ? issueIdMatch[1].toUpperCase() : null;\n\n const config = loadYamlConfig().config;\n const tiered = config.tieredExecution;\n // Use issue-aware resolver to honor record override (PAN-2383)\n const explicitTableActive = Boolean(\n tiered && (issueId ? resolveTieredExecutionEnabledForIssue(tiered, issueId, planMetadata) : resolveTieredExecutionEnabled(tiered, planMetadata))\n );\n\n const item = doc.plan.items.find((candidate) => candidate.id === slotItemId);\n if (!item) {\n // With an explicit table active a missing item is a data-integrity signal;\n // otherwise fall back to the historical role-default spawn path.\n if (explicitTableActive) {\n throw new Error(\n `Tiered execution is enabled but item '${slotItemId}' was not found in the plan for ${baseWorkspace}.`,\n );\n }\n return {};\n }\n\n // PAN-2397 (Always Tiered): staffing ALWAYS resolves — explicit tier table\n // when enabled, else the implicit roles.work tier (same resolveModel +\n // spawnKey as determineModel, so distributions stay deterministic).\n const staffing = resolveStaffing(item, { planMetadata, spawnKey, config });\n return {\n model: staffing.model,\n harness: staffing.implicit ? undefined : staffing.harness,\n tierName: staffing.tierName,\n implicit: staffing.implicit,\n };\n}\n\n/**\n * Tiered-execution model resolution for the single work-agent path.\n * Auto-start and non-swarm issues still run one foreground work agent, but\n * the first dispatchable vBRIEF item is the same scheduling unit the agent is\n * about to execute. If tiered execution is enabled for the issue, route that\n * agent through the item's resolved tier; otherwise leave role-default model\n * resolution untouched.\n */\nexport function resolveSingleWorkTierSpawnParams(\n workspace: string,\n explicitModel?: string,\n spawnKey?: string,\n): SlotTierSpawnParams {\n if (explicitModel) return {};\n\n const doc = readWorkspacePlanSync(workspace);\n if (!doc) return {};\n const planMetadata = doc?.plan?.metadata;\n\n const item = getDispatchableItems(doc, new Set())[0];\n if (!item) return {};\n\n // Extract issueId from workspace path: feature-<issueId>\n const workspaceName = basename(workspace);\n const issueIdMatch = workspaceName.match(/^feature-(.+)$/i);\n const issueId = issueIdMatch ? issueIdMatch[1].toUpperCase() : null;\n\n // PAN-2397 (Always Tiered): the single-work path staffs through the same\n // resolver as slots — explicit table when enabled, implicit roles.work\n // tier otherwise. Use issue-aware resolver to honor record override (PAN-2383).\n const config = loadYamlConfig().config;\n const tiered = config.tieredExecution;\n const effectiveTieredEnabled = issueId\n ? resolveTieredExecutionEnabledForIssue(tiered, issueId, planMetadata)\n : resolveTieredExecutionEnabled(tiered, planMetadata);\n\n const staffing = resolveStaffing(item, {\n planMetadata,\n spawnKey,\n config: { ...config, tieredExecution: { ...tiered, enabled: effectiveTieredEnabled } },\n });\n return {\n model: staffing.model,\n harness: staffing.implicit ? undefined : staffing.harness,\n tierName: staffing.tierName,\n implicit: staffing.implicit,\n };\n}\n\n/**\n * Shared tracker resolution logic for issue state transitions.\n *\n * Resolution order (by project tracker type):\n * 1. github_repo → GitHub Issues (takes priority over issue_prefix, since projects\n * like overdeck use GitHub Issues with a prefix, not Linear)\n * 2. rally_project → Rally\n * 3. issue_prefix (no github_repo) → Linear (covers gitlab+linear and pure-linear projects)\n * 4. gitlab_repo only → warn and skip (GitLab doesn't support label-based state transitions)\n *\n * Precedence rationale: issue_prefix was renamed from linear_team but is now also set on\n * GitHub-hosted projects (e.g. issue_prefix: PAN for overdeck GitHub Issues).\n * github_repo must be checked first so GitHub projects don't misroute to Linear.\n */\nasync function transitionIssueState(issueId: string, state: IssueState, workspacePath?: string): Promise<void> {\n // Guard: bare numeric IDs (no alphabetic prefix, e.g. \"484\") must never reach\n // any tracker API. Linear's searchIssues(\"484\") would match MIN-484 in the wrong\n // team. Log a warning and skip — the workspace's project must use prefixed IDs.\n if (/^\\d+$/.test(issueId)) {\n console.warn(\n `[agents] Skipping ${state} transition for bare numeric ID \"${issueId}\" — ` +\n `issue IDs must include a project prefix (e.g. PAN-${issueId}). ` +\n `This workspace was likely created before the pan- prefix convention.`\n );\n return;\n }\n\n // Resolve the project from workspacePath — its configured tracker is authoritative.\n // Every issue MUST belong to a registered project with a tracker configured.\n const projectConfig = workspacePath ? findProjectByPathSync(workspacePath) : null;\n if (!projectConfig) {\n throw new Error(`Cannot transition ${issueId}: no project config found for workspace ${workspacePath || '(none)'}. Register the project in projects.yaml.`);\n }\n\n // Project has a GitHub repo — use GitHub Issues tracker.\n // Checked BEFORE issue_prefix because github_repo projects (e.g. overdeck)\n // set issue_prefix for their GitHub Issue prefix (PAN-), not for Linear.\n if (projectConfig.github_repo) {\n const [owner, repo] = projectConfig.github_repo.split('/');\n const tracker = createTracker({ type: 'github', owner, repo });\n await Effect.runPromise(tracker.transitionIssue(issueId, state));\n console.log(`[agents] Transitioned ${issueId} to ${state} via GitHub (${projectConfig.github_repo})`);\n return;\n }\n\n // Project has a Rally project — use Rally tracker\n if (projectConfig.rally_project) {\n const config = loadConfigSync();\n const trackersConfig = config.trackers;\n if (!trackersConfig?.rally) {\n throw new Error(`Project ${projectConfig.name} uses Rally (project: ${projectConfig.rally_project}) but no Rally tracker is configured in config.yaml`);\n }\n const tracker = createTrackerFromConfig(trackersConfig, 'rally');\n await Effect.runPromise(tracker.transitionIssue(issueId, state));\n console.log(`[agents] Transitioned ${issueId} to ${state} via Rally (project: ${projectConfig.rally_project})`);\n return;\n }\n\n // Project has a Linear team prefix (and no github_repo) — use Linear tracker.\n // This covers: pure-Linear projects and gitlab+Linear projects (e.g. mind-your-now).\n if (getIssuePrefix(projectConfig)) {\n const config = loadConfigSync();\n const trackersConfig = config.trackers;\n if (!trackersConfig?.linear) {\n throw new Error(`Project ${projectConfig.name} uses Linear (team: ${getIssuePrefix(projectConfig)}) but no Linear tracker is configured in config.yaml`);\n }\n const tracker = createTrackerFromConfig(trackersConfig, 'linear');\n await Effect.runPromise(tracker.transitionIssue(issueId, state));\n console.log(`[agents] Transitioned ${issueId} to ${state} via Linear (team: ${getIssuePrefix(projectConfig)})`);\n return;\n }\n\n if (projectConfig.gitlab_repo) {\n console.warn(`[agents] GitLab project detected (${projectConfig.gitlab_repo}) but GitLab does not support ${state} label transitions`);\n return;\n }\n\n throw new Error(`Project ${projectConfig.name} has no tracker configured (need issue_prefix, github_repo, or rally_project in projects.yaml)`);\n}\n\nexport async function transitionIssueToInProgress(issueId: string, workspacePath?: string): Promise<void> {\n return transitionIssueState(issueId, 'in_progress', workspacePath);\n}\n\n/**\n * Transitions an issue to \"in_review\" state in the configured issue tracker.\n * Fire-and-forget — logs warnings on failure but never blocks the pipeline.\n */\nexport async function transitionIssueToInReview(issueId: string, workspacePath?: string): Promise<void> {\n return transitionIssueState(issueId, 'in_review', workspacePath);\n}\n\nexport interface AgentLaunchConfig {\n launcherContent: string;\n providerEnv: Record<string, string>;\n}\n\nexport async function buildAgentLaunchConfig(opts: {\n agentId: string;\n model: string;\n workspace: string;\n role: Role;\n spawnMode?: 'resume';\n resumeSessionId?: string;\n isPlanning?: boolean;\n /** Per-agent .mcp.json path for the experimental Channels bridge. */\n channelsBridgeMcpConfig?: string;\n /** MCP server name to load as a Channel; defaults to 'overdeck-bridge'. */\n channelsBridgeServerName?: string;\n useSupervisor?: boolean;\n supervisorScriptPath?: string;\n /** Claude Code session id for fresh launches that need a known id before boot. */\n sessionId?: string;\n /**\n * Coding-agent harness (PAN-636). Defaults to 'claude-code' when omitted —\n * preserves bit-for-bit pre-PAN-636 behavior. When 'pi', the launcher is\n * built via the Pi command-line generator instead of the claude path; opts\n * like agentId-as-name and agent-frontmatter are ignored because Pi has\n * no agent-definition system.\n */\n harness?: RuntimeName;\n extraEnvExports?: string[];\n /** Claude Code `--effort` level threaded into the launcher command. */\n effort?: RoleEffort;\n /** Inline prompt to embed in launch commands that still support prompt arguments. */\n promptInline?: string;\n}): Promise<AgentLaunchConfig> {\n const model = requireModelOverrideSync(opts.model);\n\n // Substrate guard: inject permission deny rules for Overdeck infrastructure\n // paths (.claude/agents/, .claude/hooks/, ~/.overdeck/, JSONL session dirs)\n // into the workspace's .claude/settings.local.json. Idempotent. Without this\n // a vBRIEF action like \"delete the legacy pan-*-agent.md files\" can convince\n // an agent to brick its own runtime. PAN-1048 X1 incident, 2026-05-09.\n try {\n const { injectOverdeckInfraDeny } = await import('../claude-settings-overlay.js');\n await Effect.runPromise(injectOverdeckInfraDeny(opts.workspace));\n } catch (err) {\n console.warn(`[agents] injectOverdeckInfraDeny failed for ${opts.agentId} (non-fatal): ${err instanceof Error ? err.message : err}`);\n }\n\n const providerEnv = await getProviderEnvForModel(model);\n\n const provider = getProviderForModelSync(model as ModelId);\n if (provider.authType === 'credential-file') {\n setupCredentialFileAuthSync(provider, opts.workspace);\n } else {\n clearCredentialFileAuthSync(opts.workspace);\n }\n\n const providerExports = await getProviderExportsForModel(model);\n\n // PAN-1048: resume/restart launchers must respect the agent's role.\n // A resumed review/test/ship run loads the wrong frontmatter (and wrong\n // tool permissions) if it always points at roles/work.md.\n const launchRole: Role = opts.isPlanning ? 'plan' : opts.role;\n\n // PAN-1055: ohmypi harness needs --session-dir + fifo redirect threaded into\n // the launcher; getOhmypiLauncherFields() resolves them from the agent state\n // and they're spread into generateLauncherScript() below.\n // PAN-1574: codex harness needs its per-agent CODEX_HOME path.\n const behavior = getHarnessBehavior(opts.harness);\n const piLauncherFields = behavior.usesRpcFifo\n ? await getOhmypiLauncherFields(opts.agentId, model)\n : {};\n const codexLauncherFields = behavior.usesCodexHome\n ? getCodexLauncherFields(opts.agentId, model, opts.workspace)\n : {};\n\n if (opts.spawnMode === 'resume' && opts.resumeSessionId) {\n // Resume sessions adopt the role definition via --agent.\n // Permissions/model/tools/hooks come from roles/<role>.md frontmatter.\n // --name <agentId> gives the resumed Claude session a human-readable handle.\n //\n // The frontmatter's permissionMode: bypassPermissions only bypasses prompts\n // INSIDE cwd. Tools that touch siblings of cwd (e.g. bd reading\n // .beads/issues.jsonl through git subprocesses, pan reading\n // ~/.overdeck/...) still hit \"Do you want to proceed?\" without DSP.\n // Mid-Bash dialog dismissals (deacon nudge, paste-buffer write, sibling\n // hook output) cancel the in-flight tool call and surface as\n // `Interrupted · What should Claude do instead?` (PAN-1024 reproduced\n // this loop on every fresh resume of PAN-1044/PAN-934).\n //\n // Match the fresh-spawn path: when permissionMode resolves to 'bypass'\n // (PAN_YOLO=true OR claude.permissionMode=bypass in config), prepend\n // --dangerously-skip-permissions on resume too.\n // Use the shared helper so the only string literal for DSP lives in\n // claude-permissions.ts (see scripts/lint-permissions.sh allowlist).\n const launcherContent = generateLauncherScriptSync({\n role: launchRole,\n spawnMode: 'resume',\n workingDir: opts.workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports,\n // PAN-2087: claude-code resumes inject the role body (+ effort) as an\n // appended system prompt instead of `--agent <file>` (Claude Code 2.1.195\n // dropped --agent file support); permission flags come from the global\n // resolver. ohmypi/codex resumes route through getAgentRuntimeBaseCommand\n // which short-circuits to the omp/codex form.\n baseCommand: behavior.launchCommandKind !== 'claude-code'\n ? await getAgentRuntimeBaseCommand(model, opts.agentId, launchRole, opts.harness)\n : `claude ${getClaudePermissionFlagsStringSync()}${roleSystemPromptInjectionSync(roleAgentDefinitionPath(launchRole))}`,\n resumeSessionId: opts.resumeSessionId,\n model: behavior.launchCommandKind !== 'claude-code' || providerExports.includes('ANTHROPIC_BASE_URL') ? model : undefined,\n extraArgs: behavior.launchCommandKind !== 'claude-code' ? undefined : `--name ${opts.agentId}`,\n appendSystemPromptFiles: await claudeSystemPromptFiles(opts.workspace, opts.harness),\n extraEnvExports: opts.extraEnvExports,\n useSupervisor: opts.useSupervisor,\n supervisorScriptPath: opts.supervisorScriptPath,\n promptInline: opts.promptInline,\n ...piLauncherFields,\n ...codexLauncherFields,\n });\n return { launcherContent, providerEnv };\n }\n\n const yamlConfig = loadYamlConfig();\n const cavemanExports = await buildCavemanExports(\n opts.workspace,\n yamlConfig.config.caveman,\n opts.isPlanning ?? false,\n );\n\n // PAN-982: pass the role definition path + agentId through getAgentRuntimeBaseCommand so it\n // emits 'claude --agent roles/<role>.md --name <agentId>'.\n // PAN-636: when the harness uses the ohmypi RPC command, the helper\n // short-circuits to an omp --mode rpc line and the\n // agentName/agentDefinition arguments are ignored (Pi has no agent\n // definitions). The launcher generator's Pi branch then layers --session-dir\n // and the fifo redirect on top.\n const agentDefinition = roleAgentDefinitionPath(launchRole);\n const launcherContent = generateLauncherScriptSync({\n role: launchRole,\n workingDir: opts.workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports,\n cavemanExports,\n baseCommand: await getAgentRuntimeBaseCommand(model, opts.agentId, agentDefinition, opts.harness ?? 'claude-code', opts.effort),\n sessionId: behavior.sessionIdSource === 'launcher-session-id' ? opts.sessionId : undefined,\n appendSystemPromptFiles: await claudeSystemPromptFiles(opts.workspace, opts.harness),\n extraEnvExports: opts.extraEnvExports,\n useSupervisor: opts.useSupervisor,\n supervisorScriptPath: opts.supervisorScriptPath,\n promptInline: opts.promptInline,\n ...piLauncherFields,\n ...codexLauncherFields,\n ...(opts.channelsBridgeMcpConfig\n ? {\n channelsBridgeMcpConfig: opts.channelsBridgeMcpConfig,\n channelsBridgeServerName: opts.channelsBridgeServerName ?? 'overdeck-bridge',\n }\n : {}),\n });\n\n return { launcherContent, providerEnv };\n}\n\nexport function defaultRunWorkspace(issueId: string): string {\n const project = resolveProjectFromIssueSync(issueId);\n if (!project) {\n throw new Error(`Cannot spawn role run for ${issueId}: no project is configured for this issue prefix`);\n }\n return join(project.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n}\n\nexport async function retrieveSpawnTimeMemoryContext(input: {\n prompt: string;\n issueId: string;\n workspace: string;\n agentId: string;\n role: Role;\n harness: RuntimeName;\n}): Promise<string> {\n if (!input.prompt.trim()) return '';\n\n try {\n const identity: MemoryIdentity = {\n projectId: inferMemoryProjectId(input.workspace),\n workspaceId: basename(input.workspace),\n issueId: input.issueId,\n runId: input.agentId,\n sessionId: input.agentId,\n agentRole: input.role,\n agentHarness: input.harness,\n };\n const { injectPromptTimeMemory } = await import('../memory/injection.js');\n return (await injectPromptTimeMemory({ prompt: input.prompt, identity, surface: 'spawn' })).context;\n } catch (error) {\n console.warn(`[agents] Spawn-time memory context unavailable for ${input.agentId}:`, error instanceof Error ? error.message : String(error));\n return '';\n }\n}\n\nexport async function withSpawnTimeMemoryContext(input: {\n prompt: string;\n issueId: string;\n workspace: string;\n agentId: string;\n role: Role;\n harness: RuntimeName;\n}): Promise<string> {\n const context = await retrieveSpawnTimeMemoryContext(input);\n return context ? `${context}\\n\\n---\\n\\n${input.prompt}` : input.prompt;\n}\n\nexport function runAgentId(issueId: string, role: Role, subRole?: string): string {\n const base = role === 'work'\n ? `agent-${issueId.toLowerCase()}`\n : `agent-${issueId.toLowerCase()}-${role}`;\n return subRole ? `${base}-${subRole}` : base;\n}\n\n/**\n * Spawn-time stack-rebuild self-heal state. PAN-1618: the work-spawn gate\n * (`assertWorkspaceStackHealthyForSpawn`) used to fail hard when the workspace\n * docker stack was down, with only manual recoveries (`pan workspace rebuild`\n * or interactive `--host`). Under autonomous operation a fully-planned\n * `proposed` item whose stack happened to be down could never auto-start its\n * work agent — it sat at the gate forever. This mirrors the PAN-1247\n * orphan-test self-heal one role earlier: rebuild the stack before failing,\n * bounded by a cooldown + attempt cap so a stack that genuinely cannot be\n * rebuilt escalates to a human instead of looping `docker compose` forever.\n */\nconst spawnStackRebuildState: Map<string, { lastAttempt: number; attempts: number; escalated: boolean; hostFallbackNoticed?: boolean }> =\n new Map();\nconst SPAWN_STACK_REBUILD_COOLDOWN_MS = 15 * 60 * 1000;\nconst SPAWN_STACK_REBUILD_MAX_ATTEMPTS = 3;\n\n/**\n * Spawn a role-based Overdeck run. Work delegates to the existing work-agent\n * path; review/test/ship use the role definition files under roles/.\n */\nexport async function assertWorkspaceStackHealthyForSpawn(\n issueId: string,\n role: Role,\n allowHost = false,\n workspacePath?: string,\n): Promise<void> {\n if (role === 'plan' || role === 'knowledge') return;\n\n // PAN-1872: guard against an undefined issueId so workspace health checks do\n // not crash with `Cannot read properties of undefined (reading 'toUpperCase')`\n // while pan start is recovering from a sync-main conflict.\n const normalizedIssue = (issueId ?? '').toUpperCase();\n\n // PAN-1746: absence of a workspace must be a HARDER failure than an unhealthy\n // one. The host-fallback path below lets advancing roles (review/test/ship)\n // proceed when the docker stack is merely unhealthy — but a workspace\n // directory that does not exist at all means the launcher would fall back to\n // its cwd ($HOME) and wedge Claude at the folder-trust prompt while it holds\n // an advancing slot against the PAN-1665 governor. Refuse the spawn outright\n // instead of degrading to host. (`work`'s resume path already guards this in\n // restartAgent; this closes the same gap on the role-run spawn path.)\n if (workspacePath && !existsSync(workspacePath)) {\n throw new Error(\n `Workspace for ${normalizedIssue} does not exist at ${workspacePath} — refusing to spawn ${role}. `\n + `A missing workspace would land the agent in $HOME at the folder-trust prompt. `\n + `Recreate the workspace ('pan workspace rebuild ${normalizedIssue}') before retrying.`,\n );\n }\n\n const health = await Effect.runPromise(getWorkspaceStackHealth(issueId, { workspacePath }));\n if (health.healthy) {\n spawnStackRebuildState.delete(normalizedIssue);\n return;\n }\n\n const details = health.reasons.join('; ');\n const message = `Workspace docker stack for ${normalizedIssue} is not healthy: ${details}. Run 'pan workspace rebuild ${normalizedIssue}' or retry with --host to override.`;\n\n if (allowHost) {\n // PAN-1556: host-override is a per-spawn detail, not user-facing activity —\n // it fired once per convoy member and buried real feed items (conversations).\n // Keep the console.warn for debugging; do not emit to the session feed.\n console.warn(`[agents] ${message}`);\n return;\n }\n\n // PAN-1645 + PAN-1618: an unhealthy stack must NEVER *block* the advancing\n // roles. review/test/ship all operate on the HOST workspace — ship\n // rebases/pushes against the host .git, review reads the committed diff, and\n // test runs the project's quality gates (host-run unless a gate explicitly\n // opts into a container) — so they do not need the workspace's docker\n // containers at all. The long-standing manual `--host` workaround (PAN-1645)\n // burned enormous effort just rediscovering that ship-on-broken-docker is a\n // false gate. For these roles we still attempt one bounded autonomous rebuild\n // (so a project whose test gates DO run in containers gets a healthy stack\n // when recoverable), but if it can't be made healthy we AUTO-FALL-BACK TO\n // HOST and proceed instead of throwing.\n //\n // `work` is different: a work agent may rely on the dev container's services,\n // so silently running it on the host could build/test against a missing\n // environment. work keeps the hard gate (rebuild → escalate to a human).\n const hostFallbackEligible = role !== 'work';\n\n const record = spawnStackRebuildState.get(normalizedIssue)\n ?? { lastAttempt: 0, attempts: 0, escalated: false };\n const now = Date.now();\n\n const fallbackToHost = (reason: string): void => {\n console.warn(`[agents] ${message} — auto-falling back to host for ${role} (${reason})`);\n // Emit the host-fallback notice once per issue. Use a SEPARATE latch from\n // the work-escalation latch (`escalated`): if review/test/ship trip the\n // host fallback first, a later `work` spawn for the same broken-stack issue\n // must still be able to emit its own (error-level) dead-end marker — the\n // operator's only signal that a work agent is blocked on docker.\n if (!record.hostFallbackNoticed) {\n record.hostFallbackNoticed = true;\n spawnStackRebuildState.set(normalizedIssue, record);\n emitActivityEntrySync({\n source: role,\n level: 'warn',\n issueId: normalizedIssue,\n message: `agent-spawn-host-fallback: ${normalizedIssue}`,\n details: `Workspace docker stack unhealthy (${details}); ${role} runs on the host (rebase/verify use host .git + host gates), so proceeding without containers. ${reason}`,\n });\n }\n };\n\n const blockWork = (markerMessage: string, errDetails: string): never => {\n if (!record.escalated) {\n record.escalated = true;\n spawnStackRebuildState.set(normalizedIssue, record);\n emitActivityEntrySync({\n source: role,\n level: 'error',\n issueId: normalizedIssue,\n message: markerMessage,\n details: errDetails,\n });\n }\n throw new Error(message);\n };\n\n if (record.attempts >= SPAWN_STACK_REBUILD_MAX_ATTEMPTS) {\n if (hostFallbackEligible) {\n fallbackToHost(`rebuild exhausted after ${record.attempts} attempts`);\n return;\n }\n blockWork(\n `agent-spawn-stack-rebuild-exhausted: ${normalizedIssue}`,\n `Workspace docker stack still unhealthy after ${record.attempts} rebuild attempts: ${details}. Manual 'pan workspace rebuild ${normalizedIssue}' or retry with --host needed.`,\n );\n }\n\n if (now - record.lastAttempt < SPAWN_STACK_REBUILD_COOLDOWN_MS) {\n // A rebuild was attempted recently and the stack is still unhealthy —\n // don't hammer `docker compose` every spawn.\n if (hostFallbackEligible) {\n fallbackToHost('rebuild on cooldown');\n return;\n }\n blockWork(`agent-spawn-blocked-stack-unhealthy: ${normalizedIssue}`, details);\n }\n\n record.lastAttempt = now;\n record.attempts += 1;\n spawnStackRebuildState.set(normalizedIssue, record);\n console.log(\n `[agents] Workspace stack for ${normalizedIssue} unhealthy (${details}) — rebuilding ` +\n `before spawn (attempt ${record.attempts}/${SPAWN_STACK_REBUILD_MAX_ATTEMPTS})`,\n );\n\n const { rebuildWorkspaceStack } = await import('../workspace/rebuild-stack.js');\n const result = await Effect.runPromise(\n rebuildWorkspaceStack(issueId, {\n onProgress: (m) => console.log(`[agents] ${normalizedIssue} stack rebuild: ${m}`),\n }),\n ).catch((err: unknown) => ({ success: false as const, error: err instanceof Error ? err.message : String(err) }));\n\n if (result.success) {\n spawnStackRebuildState.delete(normalizedIssue);\n console.log(`[agents] Workspace stack for ${normalizedIssue} rebuilt — proceeding with spawn`);\n return;\n }\n\n console.warn(`[agents] Workspace stack rebuild failed for ${normalizedIssue}: ${result.error}`);\n if (hostFallbackEligible) {\n fallbackToHost(`rebuild failed: ${result.error ?? 'unknown'}`);\n return;\n }\n blockWork(`agent-spawn-stack-rebuild-failed: ${normalizedIssue}`, result.error ?? details);\n}\n","/**\n * Agent CV (Work History) System\n *\n * Tracks agent performance over time to enable capability-based routing.\n */\n\nimport { Effect } from 'effect';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\nimport { FsError } from './errors.js';\n\nexport interface WorkEntry {\n issueId: string;\n startedAt: string;\n completedAt?: string;\n outcome: 'success' | 'failed' | 'abandoned' | 'in_progress';\n duration?: number; // minutes\n skills?: string[];\n failureReason?: string;\n commits?: number;\n linesChanged?: number;\n}\n\nexport interface AgentCV {\n agentId: string;\n createdAt: string;\n lastActive: string;\n runtime: string;\n model: string;\n stats: {\n totalIssues: number;\n successCount: number;\n failureCount: number;\n abandonedCount: number;\n avgDuration: number; // minutes\n successRate: number; // 0-1\n };\n skillsUsed: string[];\n recentWork: WorkEntry[];\n}\n\nfunction getCVFile(agentId: string): string {\n return join(AGENTS_DIR, agentId, 'cv.json');\n}\n\n/**\n * Read an agent's CV without creating one.\n */\nexport function readAgentCVSync(agentId: string): AgentCV | null {\n const cvFile = getCVFile(agentId);\n\n if (!existsSync(cvFile)) return null;\n\n try {\n return JSON.parse(readFileSync(cvFile, 'utf-8'));\n } catch {\n return null;\n }\n}\n\n/**\n * Get or create an agent's CV\n */\nexport function getAgentCVSync(agentId: string): AgentCV {\n const existing = readAgentCVSync(agentId);\n if (existing) return existing;\n\n // Create new CV\n const cv: AgentCV = {\n agentId,\n createdAt: new Date().toISOString(),\n lastActive: new Date().toISOString(),\n runtime: 'claude',\n model: 'sonnet',\n stats: {\n totalIssues: 0,\n successCount: 0,\n failureCount: 0,\n abandonedCount: 0,\n avgDuration: 0,\n successRate: 0,\n },\n skillsUsed: [],\n recentWork: [],\n };\n\n saveAgentCVSync(cv);\n return cv;\n}\n\n/**\n * Save an agent's CV\n */\nexport function saveAgentCVSync(cv: AgentCV): void {\n const dir = join(AGENTS_DIR, cv.agentId);\n mkdirSync(dir, { recursive: true });\n writeFileSync(getCVFile(cv.agentId), JSON.stringify(cv, null, 2));\n}\n\n/**\n * Start tracking work for an agent\n */\nexport function startWorkSync(agentId: string, issueId: string, skills?: string[]): void {\n const cv = getAgentCVSync(agentId);\n\n const entry: WorkEntry = {\n issueId,\n startedAt: new Date().toISOString(),\n outcome: 'in_progress',\n skills,\n };\n\n cv.recentWork.unshift(entry);\n cv.stats.totalIssues++;\n cv.lastActive = new Date().toISOString();\n\n // Track skills\n if (skills) {\n for (const skill of skills) {\n if (!cv.skillsUsed.includes(skill)) {\n cv.skillsUsed.push(skill);\n }\n }\n }\n\n // Keep only last 50 entries\n if (cv.recentWork.length > 50) {\n cv.recentWork = cv.recentWork.slice(0, 50);\n }\n\n saveAgentCVSync(cv);\n}\n\n/**\n * Complete work for an agent\n */\nexport function completeWorkSync(\n agentId: string,\n issueId: string,\n outcome: 'success' | 'failed' | 'abandoned',\n details?: { commits?: number; linesChanged?: number; failureReason?: string }\n): void {\n const cv = getAgentCVSync(agentId);\n\n // Find the work entry\n const entry = cv.recentWork.find(\n (w) => w.issueId === issueId && w.outcome === 'in_progress'\n );\n\n if (entry) {\n entry.outcome = outcome;\n entry.completedAt = new Date().toISOString();\n entry.duration = Math.round(\n (new Date().getTime() - new Date(entry.startedAt).getTime()) / (1000 * 60)\n );\n if (details?.commits) entry.commits = details.commits;\n if (details?.linesChanged) entry.linesChanged = details.linesChanged;\n if (details?.failureReason) entry.failureReason = details.failureReason;\n }\n\n // Update stats\n if (outcome === 'success') {\n cv.stats.successCount++;\n } else if (outcome === 'failed') {\n cv.stats.failureCount++;\n } else if (outcome === 'abandoned') {\n cv.stats.abandonedCount++;\n }\n\n // Calculate success rate\n const completed = cv.stats.successCount + cv.stats.failureCount + cv.stats.abandonedCount;\n cv.stats.successRate = completed > 0 ? cv.stats.successCount / completed : 0;\n\n // Calculate average duration (only from completed work)\n const completedEntries = cv.recentWork.filter(\n (w) => w.duration !== undefined && w.outcome !== 'in_progress'\n );\n if (completedEntries.length > 0) {\n const totalDuration = completedEntries.reduce((sum, w) => sum + (w.duration || 0), 0);\n cv.stats.avgDuration = Math.round(totalDuration / completedEntries.length);\n }\n\n cv.lastActive = new Date().toISOString();\n saveAgentCVSync(cv);\n}\n\n/**\n * Get agent rankings by success rate\n */\nexport function getAgentRankingsSync(): Array<{\n agentId: string;\n successRate: number;\n totalIssues: number;\n avgDuration: number;\n}> {\n const rankings: Array<{\n agentId: string;\n successRate: number;\n totalIssues: number;\n avgDuration: number;\n }> = [];\n\n if (!existsSync(AGENTS_DIR)) return rankings;\n\n const dirs = readdirSync(AGENTS_DIR, { withFileTypes: true }).filter(\n (d) => d.isDirectory()\n );\n\n for (const dir of dirs) {\n const cv = getAgentCVSync(dir.name);\n if (cv.stats.totalIssues > 0) {\n rankings.push({\n agentId: dir.name,\n successRate: cv.stats.successRate,\n totalIssues: cv.stats.totalIssues,\n avgDuration: cv.stats.avgDuration,\n });\n }\n }\n\n // Sort by success rate, then by total issues\n rankings.sort((a, b) => {\n if (b.successRate !== a.successRate) {\n return b.successRate - a.successRate;\n }\n return b.totalIssues - a.totalIssues;\n });\n\n return rankings;\n}\n\n/**\n * Format CV for display\n */\nexport function formatCVSync(cv: AgentCV): string {\n const lines: string[] = [\n `# Agent CV: ${cv.agentId}`,\n '',\n `Runtime: ${cv.runtime} (${cv.model})`,\n `Created: ${cv.createdAt}`,\n `Last Active: ${cv.lastActive}`,\n '',\n '## Statistics',\n '',\n `- Total Issues: ${cv.stats.totalIssues}`,\n `- Success Rate: ${(cv.stats.successRate * 100).toFixed(1)}%`,\n `- Successes: ${cv.stats.successCount}`,\n `- Failures: ${cv.stats.failureCount}`,\n `- Abandoned: ${cv.stats.abandonedCount}`,\n `- Avg Duration: ${cv.stats.avgDuration} minutes`,\n '',\n ];\n\n if (cv.skillsUsed.length > 0) {\n lines.push('## Skills Used');\n lines.push('');\n lines.push(cv.skillsUsed.join(', '));\n lines.push('');\n }\n\n if (cv.recentWork.length > 0) {\n lines.push('## Recent Work');\n lines.push('');\n\n for (const work of cv.recentWork.slice(0, 10)) {\n const statusIcon = {\n success: '✓',\n failed: '✗',\n abandoned: '⊘',\n in_progress: '●',\n }[work.outcome];\n\n const duration = work.duration ? ` (${work.duration}m)` : '';\n lines.push(`${statusIcon} ${work.issueId}${duration}`);\n\n if (work.failureReason) {\n lines.push(` Reason: ${work.failureReason}`);\n }\n }\n lines.push('');\n }\n\n return lines.join('\\n');\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n// CV file IO is sync by design. Read paths return Effect.sync; write paths\n// surface FsError.\n\n/** Load (or create) the CV for an agent. Pure-ish. */\nexport const getAgentCV = (agentId: string): Effect.Effect<AgentCV> =>\n Effect.sync(() => getAgentCVSync(agentId));\n\n/** Persist a CV to disk. */\nexport const saveAgentCV = (cv: AgentCV): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => saveAgentCVSync(cv),\n catch: (cause) =>\n new FsError({ path: cv.agentId, operation: 'save-agent-cv', cause }),\n });\n\n/** Mark the start of a work item on an agent's CV. */\nexport const startWork = (\n agentId: string,\n issueId: string,\n skills?: string[],\n): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => startWorkSync(agentId, issueId, skills),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'cv-start-work', cause }),\n });\n\n/** Mark completion (success / failure / abandoned). */\nexport const completeWork = (\n ...args: Parameters<typeof completeWorkSync>\n): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => completeWorkSync(...args),\n catch: (cause) =>\n new FsError({ path: args[0], operation: 'cv-complete-work', cause }),\n });\n\n/** Aggregate rankings across all agents. Pure-ish. */\nexport const getAgentRankings = (): Effect.Effect<\n ReturnType<typeof getAgentRankingsSync>\n> => Effect.sync(() => getAgentRankingsSync());\n\n/** Render a CV as text. Pure. */\nexport const formatCV = (cv: AgentCV): Effect.Effect<string> =>\n Effect.sync(() => formatCVSync(cv));\n","/**\n * FPP Hooks System - Fixed Point Principle\n *\n * \"Any runnable action is a fixed point and must resolve before the system can rest.\"\n *\n * Inspired by Doctor Who: a fixed point in time must occur — it cannot be avoided.\n *\n * Hooks are persistent work queues for agents. When an agent starts,\n * it checks its hook for pending work and executes immediately.\n */\n\nimport { Effect } from 'effect';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, unlinkSync } from 'fs';\nimport { join } from 'path';\nimport { AGENTS_DIR } from './paths.js';\nimport { FsError } from './errors.js';\n\nexport interface HookItem {\n id: string;\n type: 'task' | 'message' | 'notification';\n priority: 'urgent' | 'high' | 'normal' | 'low';\n source: string;\n payload: {\n issueId?: string;\n message?: string;\n action?: string;\n context?: Record<string, any>;\n };\n createdAt: string;\n expiresAt?: string;\n}\n\nexport interface Hook {\n agentId: string;\n items: HookItem[];\n lastChecked?: string;\n}\n\nfunction getHookDir(agentId: string): string {\n return join(AGENTS_DIR, agentId);\n}\n\nfunction getHookFile(agentId: string): string {\n return join(getHookDir(agentId), 'hook.json');\n}\n\nfunction getMailDir(agentId: string): string {\n return join(getHookDir(agentId), 'mail');\n}\n\n/**\n * Initialize hook structure for an agent\n */\nexport function initHookSync(agentId: string): void {\n const hookDir = getHookDir(agentId);\n const mailDir = getMailDir(agentId);\n\n mkdirSync(hookDir, { recursive: true });\n mkdirSync(mailDir, { recursive: true });\n\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n const hook: Hook = {\n agentId,\n items: [],\n };\n writeFileSync(hookFile, JSON.stringify(hook, null, 2));\n }\n}\n\n/**\n * Get the hook for an agent\n */\nexport function getHookSync(agentId: string): Hook | null {\n const hookFile = getHookFile(agentId);\n if (!existsSync(hookFile)) {\n return null;\n }\n\n try {\n const content = readFileSync(hookFile, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * Add work to an agent's hook (FPP trigger)\n */\nexport function pushToHookSync(agentId: string, item: Omit<HookItem, 'id' | 'createdAt'>): HookItem {\n initHookSync(agentId);\n\n const hook = getHookSync(agentId) || { agentId, items: [] };\n\n const newItem: HookItem = {\n ...item,\n id: `hook-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n createdAt: new Date().toISOString(),\n };\n\n hook.items.push(newItem);\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return newItem;\n}\n\n/**\n * Check if agent has pending work (FPP check)\n */\nexport function checkHookSync(agentId: string): { hasWork: boolean; urgentCount: number; items: HookItem[] } {\n const hook = getHookSync(agentId);\n\n if (!hook || hook.items.length === 0) {\n // Also check mail directory for incoming messages\n const mailDir = getMailDir(agentId);\n if (existsSync(mailDir)) {\n const mails = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n if (mails.length > 0) {\n // Convert mail to hook items\n const mailItems: HookItem[] = mails.map((file) => {\n try {\n const content = readFileSync(join(mailDir, file), 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n }).filter(Boolean) as HookItem[];\n\n return {\n hasWork: mailItems.length > 0,\n urgentCount: mailItems.filter((i) => i.priority === 'urgent').length,\n items: mailItems,\n };\n }\n }\n\n return { hasWork: false, urgentCount: 0, items: [] };\n }\n\n // Filter out expired items\n const now = new Date();\n const activeItems = hook.items.filter((item) => {\n if (item.expiresAt) {\n return new Date(item.expiresAt) > now;\n }\n return true;\n });\n\n // Sort by priority: urgent > high > normal > low\n const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 };\n activeItems.sort((a, b) => priorityOrder[a.priority] - priorityOrder[b.priority]);\n\n return {\n hasWork: activeItems.length > 0,\n urgentCount: activeItems.filter((i) => i.priority === 'urgent').length,\n items: activeItems,\n };\n}\n\n/**\n * Pop the next work item from hook (after execution)\n */\nexport function popFromHookSync(agentId: string, itemId: string): boolean {\n const hook = getHookSync(agentId);\n if (!hook) return false;\n\n const index = hook.items.findIndex((i) => i.id === itemId);\n if (index === -1) return false;\n\n hook.items.splice(index, 1);\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Clear all items from hook\n */\nexport function clearHookSync(agentId: string): void {\n const hook = getHookSync(agentId);\n if (!hook) return;\n\n hook.items = [];\n hook.lastChecked = new Date().toISOString();\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n}\n\n/**\n * Reorder hook items by providing a new order of item IDs\n * Used for manual queue management from dashboard\n */\nexport function reorderHookItemsSync(agentId: string, orderedItemIds: string[]): boolean {\n const hook = getHookSync(agentId);\n if (!hook) return false;\n\n // Validate that all provided IDs exist in the hook\n const existingIds = new Set(hook.items.map((item) => item.id));\n const providedIds = new Set(orderedItemIds);\n\n // Check if all provided IDs exist\n for (const id of orderedItemIds) {\n if (!existingIds.has(id)) {\n console.error(`[hooks] Cannot reorder: item ${id} not found in hook`);\n return false;\n }\n }\n\n // Check if all existing IDs are provided\n if (existingIds.size !== providedIds.size) {\n console.error(`[hooks] Cannot reorder: mismatch in item count (existing: ${existingIds.size}, provided: ${providedIds.size})`);\n return false;\n }\n\n // Build a map for quick lookup\n const itemMap = new Map(hook.items.map((item) => [item.id, item]));\n\n // Reorder items based on provided IDs\n hook.items = orderedItemIds.map((id) => itemMap.get(id)!);\n\n // Write back to file\n writeFileSync(getHookFile(agentId), JSON.stringify(hook, null, 2));\n\n return true;\n}\n\n/**\n * Send a message to an agent's mailbox\n */\nexport function sendMailSync(\n toAgentId: string,\n from: string,\n message: string,\n priority: HookItem['priority'] = 'normal'\n): void {\n initHookSync(toAgentId);\n const mailDir = getMailDir(toAgentId);\n\n const mailItem: HookItem = {\n id: `mail-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,\n type: 'message',\n priority,\n source: from,\n payload: { message },\n createdAt: new Date().toISOString(),\n };\n\n writeFileSync(\n join(mailDir, `${mailItem.id}.json`),\n JSON.stringify(mailItem, null, 2)\n );\n}\n\n/**\n * Get and clear mail for an agent\n */\nexport function collectMailSync(agentId: string): HookItem[] {\n const mailDir = getMailDir(agentId);\n if (!existsSync(mailDir)) return [];\n\n const mails: HookItem[] = [];\n const files = readdirSync(mailDir).filter((f) => f.endsWith('.json'));\n\n for (const file of files) {\n const filePath = join(mailDir, file);\n try {\n const content = readFileSync(filePath, 'utf-8');\n mails.push(JSON.parse(content));\n unlinkSync(filePath); // Remove after reading\n } catch {\n // Skip invalid mail\n }\n }\n\n return mails;\n}\n\n/**\n * Generate Fixed Point prompt for agent startup\n */\nexport function generateFixedPointPromptSync(agentId: string): string | null {\n const { hasWork, urgentCount, items } = checkHookSync(agentId);\n\n if (!hasWork) return null;\n\n const lines: string[] = [\n '# FPP: Work Found on Your Hook',\n '',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n if (urgentCount > 0) {\n lines.push(`⚠️ **${urgentCount} URGENT item(s) require immediate attention**`);\n lines.push('');\n }\n\n lines.push(`## Pending Work Items (${items.length})`);\n lines.push('');\n\n for (const item of items) {\n const priorityEmoji = {\n urgent: '🔴',\n high: '🟠',\n normal: '🟢',\n low: '⚪',\n }[item.priority];\n\n lines.push(`### ${priorityEmoji} ${item.type.toUpperCase()}: ${item.id}`);\n lines.push(`- Source: ${item.source}`);\n lines.push(`- Created: ${item.createdAt}`);\n\n if (item.payload.issueId) {\n lines.push(`- Issue: ${item.payload.issueId}`);\n }\n if (item.payload.message) {\n lines.push(`- Message: ${item.payload.message}`);\n }\n if (item.payload.action) {\n lines.push(`- Action: ${item.payload.action}`);\n }\n lines.push('');\n }\n\n lines.push('---');\n lines.push('');\n lines.push('Execute these items in priority order. Use `bd hook pop <id>` after completing each item.');\n\n return lines.join('\\n');\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n// Hook (FPP work-queue) IO — sync FS by design. Read paths use Effect.sync;\n// mutating paths surface FsError on disk failure.\n\n/** Initialize an empty hook for an agent (no-op if it exists). */\nexport const initHook = (agentId: string): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => initHookSync(agentId),\n catch: (cause) => new FsError({ path: agentId, operation: 'init-hook', cause }),\n });\n\n/** Load an agent's hook (null when missing). Pure-ish. */\nexport const getHook = (agentId: string): Effect.Effect<Hook | null> =>\n Effect.sync(() => getHookSync(agentId));\n\n/** Push a new item onto the agent's hook. */\nexport const pushToHook = (\n agentId: string,\n item: Omit<HookItem, 'id' | 'createdAt'>,\n): Effect.Effect<HookItem, FsError> =>\n Effect.try({\n try: () => pushToHookSync(agentId, item),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'push-to-hook', cause }),\n });\n\n/** Inspect an agent's hook for pending work. Pure-ish. */\nexport const checkHook = (\n agentId: string,\n): Effect.Effect<ReturnType<typeof checkHookSync>> => Effect.sync(() => checkHookSync(agentId));\n\n/** Pop a specific item from the hook by id. */\nexport const popFromHook = (\n agentId: string,\n itemId: string,\n): Effect.Effect<boolean, FsError> =>\n Effect.try({\n try: () => popFromHookSync(agentId, itemId),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'pop-from-hook', cause }),\n });\n\n/** Clear all items from the hook. */\nexport const clearHook = (agentId: string): Effect.Effect<void, FsError> =>\n Effect.try({\n try: () => clearHookSync(agentId),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'clear-hook', cause }),\n });\n\n/** Reorder hook items by id (best-effort, only existing ids). */\nexport const reorderHookItems = (\n agentId: string,\n orderedItemIds: string[],\n): Effect.Effect<boolean, FsError> =>\n Effect.try({\n try: () => reorderHookItemsSync(agentId, orderedItemIds),\n catch: (cause) =>\n new FsError({ path: agentId, operation: 'reorder-hook', cause }),\n });\n\n/** Send a mail item to an agent's mail drop (becomes hook item on collect). */\nexport const sendMail = (\n ...args: Parameters<typeof sendMailSync>\n): Effect.Effect<ReturnType<typeof sendMailSync>, FsError> =>\n Effect.try({\n try: () => sendMailSync(...args),\n catch: (cause) =>\n new FsError({ path: args[0], operation: 'send-mail', cause }),\n });\n\n/** Drain an agent's mail drop into hook items. Pure-ish. */\nexport const collectMail = (agentId: string): Effect.Effect<HookItem[]> =>\n Effect.sync(() => collectMailSync(agentId));\n\n/** Build the FPP prompt text for an agent's pending work. Pure-ish. */\nexport const generateFixedPointPrompt = (\n agentId: string,\n): Effect.Effect<string | null> => Effect.sync(() => generateFixedPointPromptSync(agentId));\n","import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { randomBytes } from 'node:crypto';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\nimport { FsError } from './errors.js';\n\nimport { getOverdeckHome } from './paths.js';\n\nexport const BRIDGE_TOKEN_HEADER = 'x-overdeck-bridge-token';\n\nfunction bridgeTokensDir(): string {\n return join(getOverdeckHome(), 'bridge-tokens');\n}\n\nexport function getBridgeTokenPath(agentId: string): string {\n return join(bridgeTokensDir(), `${agentId}.token`);\n}\n\nexport function readBridgeTokenSync(agentId: string): string | null {\n const path = getBridgeTokenPath(agentId);\n if (!existsSync(path)) return null;\n try {\n const value = readFileSync(path, 'utf8').trim();\n return value.length > 0 ? value : null;\n } catch {\n return null;\n }\n}\n\nexport function writeBridgeTokenSync(agentId: string): string {\n const dir = bridgeTokensDir();\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n const token = randomBytes(32).toString('hex');\n const path = getBridgeTokenPath(agentId);\n writeFileSync(path, `${token}\\n`, { mode: 0o600 });\n try {\n chmodSync(path, 0o600);\n } catch {\n // best effort\n }\n return token;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Read the bridge token for an agent. Returns `null` if no token exists.\n * Effect-native variant — never fails (errors are swallowed to null like the Promise version).\n */\nexport const readBridgeToken = (agentId: string): Effect.Effect<string | null> =>\n Effect.sync(() => readBridgeTokenSync(agentId));\n\n/**\n * Generate and persist a new bridge token. Returns the new token.\n * Effect-native variant — fails with FsError if the write cannot be persisted.\n */\nexport const writeBridgeToken = (agentId: string): Effect.Effect<string, FsError> =>\n Effect.try({\n try: () => writeBridgeTokenSync(agentId),\n catch: (cause) =>\n new FsError({\n path: getBridgeTokenPath(agentId),\n operation: 'writeBridgeToken',\n cause,\n }),\n });\n","import { createReadStream, existsSync } from 'node:fs';\nimport { stat } from 'node:fs/promises';\nimport { sessionFilePath } from './paths.js';\n\nconst DEFAULT_TAIL_BYTES = 512 * 1024;\n\nexport interface TranscriptUserRecordSnapshot {\n readonly sessionFile: string;\n readonly userRecordCount: number;\n readonly fileSize?: number;\n /** Byte offset from which this snapshot was parsed. */\n readonly rangeStartByte?: number;\n /** Byte offset after the last complete JSONL line observed by this snapshot. */\n readonly readOffset?: number;\n readonly lastUserRecord?: {\n readonly lineNumber: number;\n readonly timestamp?: string;\n readonly uuid?: string;\n };\n}\n\nasync function readFileRange(filePath: string, start: number, endExclusive: number): Promise<string> {\n if (endExclusive <= start) return '';\n\n return await new Promise<string>((resolve, reject) => {\n const stream = createReadStream(filePath, { start, end: endExclusive - 1, encoding: 'utf8' });\n let data = '';\n stream.on('data', chunk => { data += chunk; });\n stream.on('end', () => resolve(data));\n stream.on('error', reject);\n });\n}\n\nfunction dropLeadingPartialLine(content: string): string {\n const firstNewline = content.indexOf('\\n');\n if (firstNewline === -1) return '';\n return content.slice(firstNewline + 1);\n}\n\nfunction nextCompleteLineOffset(rangeStartByte: number, rawContent: string, fileSize: number): number {\n if (!rawContent) return fileSize;\n if (rawContent.endsWith('\\n')) return fileSize;\n\n const lastNewline = rawContent.lastIndexOf('\\n');\n if (lastNewline === -1) return rangeStartByte;\n return rangeStartByte + Buffer.byteLength(rawContent.slice(0, lastNewline + 1), 'utf8');\n}\n\nfunction isLandedUserRecord(entry: unknown): boolean {\n if (!entry || typeof entry !== 'object') return false;\n const record = entry as {\n type?: unknown;\n message?: { role?: unknown; content?: unknown };\n };\n if (record.type !== 'user' || record.message?.role !== 'user') return false;\n\n const content = record.message.content;\n if (typeof content === 'string') return content.trim().length > 0;\n if (!Array.isArray(content)) return false;\n\n return content.some((item: unknown) => {\n if (!item || typeof item !== 'object') return false;\n return (item as { type?: unknown }).type !== 'tool_result';\n });\n}\n\nfunction parseLandedUserRecords(content: string): Pick<TranscriptUserRecordSnapshot, 'userRecordCount' | 'lastUserRecord'> {\n let userRecordCount = 0;\n let lastUserRecord: TranscriptUserRecordSnapshot['lastUserRecord'];\n\n const lines = content.split('\\n');\n for (const [index, line] of lines.entries()) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line) as { timestamp?: unknown; uuid?: unknown };\n if (!isLandedUserRecord(entry)) continue;\n userRecordCount += 1;\n lastUserRecord = {\n lineNumber: index + 1,\n ...(typeof entry.timestamp === 'string' ? { timestamp: entry.timestamp } : {}),\n ...(typeof entry.uuid === 'string' ? { uuid: entry.uuid } : {}),\n };\n } catch {\n // Claude may be appending the JSONL while we read it. Ignore malformed\n // partial lines; the next poll sees the completed record.\n }\n }\n\n return { userRecordCount, ...(lastUserRecord ? { lastUserRecord } : {}) };\n}\n\nexport async function captureTranscriptUserRecordSnapshot(\n workspace: string,\n sessionId: string,\n options: { tailBytes?: number; fromByteOffset?: number } = {},\n): Promise<TranscriptUserRecordSnapshot> {\n const sessionFile = sessionFilePath(workspace, sessionId);\n if (!existsSync(sessionFile)) {\n return { sessionFile, userRecordCount: 0, fileSize: 0, rangeStartByte: 0, readOffset: 0 };\n }\n\n try {\n const fileStat = await stat(sessionFile);\n const fileSize = fileStat.size;\n const rangeStartByte = options.fromByteOffset === undefined\n ? Math.max(0, fileSize - (options.tailBytes ?? DEFAULT_TAIL_BYTES))\n : Math.min(Math.max(0, options.fromByteOffset), fileSize);\n const rawContent = await readFileRange(sessionFile, rangeStartByte, fileSize);\n const content = options.fromByteOffset === undefined && rangeStartByte > 0\n ? dropLeadingPartialLine(rawContent)\n : rawContent;\n const parsed = parseLandedUserRecords(content);\n\n return {\n sessionFile,\n ...parsed,\n fileSize,\n rangeStartByte,\n readOffset: nextCompleteLineOffset(rangeStartByte, rawContent, fileSize),\n };\n } catch {\n return { sessionFile, userRecordCount: 0, fileSize: 0, rangeStartByte: 0, readOffset: 0 };\n }\n}\n\nexport function hasNewTranscriptUserRecord(\n before: TranscriptUserRecordSnapshot,\n after: TranscriptUserRecordSnapshot,\n): boolean {\n if (before.sessionFile !== after.sessionFile) return after.userRecordCount > 0;\n\n if (\n before.readOffset !== undefined &&\n after.rangeStartByte !== undefined &&\n after.rangeStartByte === before.readOffset\n ) {\n return after.userRecordCount > 0;\n }\n\n return after.userRecordCount > before.userRecordCount;\n}\n\nexport interface TranscriptWatchProbe {\n /** A landed user record whose content contains the watched message text. */\n matchedUserRecord: boolean;\n /** compact_boundary records observed at/after the probe's start offset. */\n compactBoundaryCount: number;\n}\n\nconst MATCH_PREFIX_CHARS = 120;\n\nfunction normalizeForContentMatch(value: string): string {\n return value.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Harness-meta user records the matcher must never treat as the user's own\n * message landing. Compaction itself writes user-type records (the\n * continuation summary, `<command-name>`/`<local-command-*>` entries), and a\n * continuation summary can even quote the watched message — counting any of\n * these as a landing would mask exactly the eaten-by-compaction case the\n * probe exists to detect (PAN-1635 / PAN-1769).\n */\nconst META_USER_CONTENT_PREFIXES = [\n 'This session is being continued',\n '<command-name>',\n '<local-command-',\n 'Caveat: The messages below',\n];\n\nfunction userRecordText(entry: unknown): string | null {\n if (!entry || typeof entry !== 'object') return null;\n const record = entry as { type?: unknown; message?: { role?: unknown; content?: unknown } };\n if (record.type !== 'user' || record.message?.role !== 'user') return null;\n const content = record.message.content;\n if (typeof content === 'string') return content;\n if (!Array.isArray(content)) return null;\n return content\n .filter((item): item is { type?: unknown; text?: unknown } => !!item && typeof item === 'object')\n .filter((item) => item.type === 'text' && typeof item.text === 'string')\n .map((item) => item.text as string)\n .join('\\n');\n}\n\nfunction isCompactBoundaryRecord(entry: unknown): boolean {\n if (!entry || typeof entry !== 'object') return false;\n const record = entry as { type?: unknown; subtype?: unknown };\n return record.type === 'system' && record.subtype === 'compact_boundary';\n}\n\n/**\n * Scan the transcript from `fromByteOffset` for (a) a user record carrying\n * `messageText` and (b) compact boundaries. Backs the eaten-by-compaction\n * watcher: a boundary appearing without the message means Claude Code's\n * submit-time compaction dropped the just-delivered prompt.\n */\nexport async function probeTranscriptSince(\n workspace: string,\n sessionId: string,\n fromByteOffset: number,\n messageText: string,\n): Promise<TranscriptWatchProbe> {\n const needle = normalizeForContentMatch(messageText).slice(0, MATCH_PREFIX_CHARS);\n const sessionFile = sessionFilePath(workspace, sessionId);\n if (!needle || !existsSync(sessionFile)) {\n return { matchedUserRecord: false, compactBoundaryCount: 0 };\n }\n\n try {\n const fileStat = await stat(sessionFile);\n const start = Math.min(Math.max(0, fromByteOffset), fileStat.size);\n const content = await readFileRange(sessionFile, start, fileStat.size);\n let matchedUserRecord = false;\n let compactBoundaryCount = 0;\n for (const line of content.split('\\n')) {\n if (!line.trim()) continue;\n try {\n const entry = JSON.parse(line) as unknown;\n if (isCompactBoundaryRecord(entry)) {\n compactBoundaryCount += 1;\n continue;\n }\n const text = userRecordText(entry);\n if (text === null) continue;\n if (META_USER_CONTENT_PREFIXES.some((prefix) => text.startsWith(prefix))) continue;\n if (normalizeForContentMatch(text).includes(needle)) matchedUserRecord = true;\n } catch {\n // Partial trailing line mid-append; the next probe sees it complete.\n }\n }\n return { matchedUserRecord, compactBoundaryCount };\n } catch {\n return { matchedUserRecord: false, compactBoundaryCount: 0 };\n }\n}\n","import { existsSync, mkdirSync, writeFileSync } from 'fs';\nimport { request as httpRequest } from 'node:http';\nimport { join, dirname } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { AgentState } from '../agents.js';\nimport {\n normalizeAgentId,\n getAgentState,\n saveAgentState,\n getAgentDir,\n waitForPromptReady,\n SESSION_EXITED_BEFORE_KICKOFF,\n} from '../agents.js';\nimport { getAgentRuntimeState } from './runtime-state.js';\nimport { isPaneDead, sendKeys, sessionExists } from '../tmux.js';\nimport { BRIDGE_TOKEN_HEADER, readBridgeTokenSync } from '../bridge-token.js';\nimport { PTY_TOKEN_HEADER, readPtyToken } from '../pty-token.js';\nimport {\n captureTranscriptUserRecordSnapshot,\n hasNewTranscriptUserRecord,\n type TranscriptUserRecordSnapshot,\n} from '../transcript-landing.js';\n\nexport type DeliveryResult = {\n ok: boolean;\n path: 'supervisor' | 'channels' | 'tmux' | 'pi' | 'codex';\n failure?: string;\n};\n\n/**\n * PAN-1988: resume / feedback / continue delivery must be RESILIENT. When an agent is pinned to\n * the strict 'supervisor' transport — which throws with NO fallback when its echo-confirmation\n * fails (the recurring \"input echo confirmation failed\" that left review feedback undelivered to\n * the work agent every round) — deliver via 'auto' instead, so a supervisor failure falls back to\n * the proven tmux paste-buffer and the message still lands. Other explicit methods\n * ('tmux'/'channels'/'auto') are preserved. The strict 'supervisor' contract itself (PAN-1769) is\n * intentionally left intact in deliverAgentMessage for callers that opt into it directly.\n */\nexport function resilientDeliveryMethod(\n method: 'auto' | 'supervisor' | 'channels' | 'tmux' | undefined,\n): 'auto' | 'supervisor' | 'channels' | 'tmux' | undefined {\n return method === 'supervisor' ? 'auto' : method;\n}\n\n/**\n * Resolve OVERDECK_HOME — same fallback semantics as overdeck-bridge.\n */\nfunction overdeckHomeForSockets(): string {\n return process.env.OVERDECK_HOME ?? join(homedir(), '.overdeck');\n}\n\nfunction overdeckHomeForChannels(): string {\n return overdeckHomeForSockets();\n}\n\n/**\n * Append a delivery-event log line to the per-agent bridge log. Best-effort.\n */\nasync function appendChannelDeliveryLog(\n agentId: string,\n entry: {\n path: 'supervisor' | 'channel' | 'tmux';\n reason?: string;\n caller?: string;\n 'pty-supervisor'?: string;\n channels?: string;\n },\n): Promise<void> {\n try {\n const home = overdeckHomeForSockets();\n const dir = join(home, 'logs');\n await (await import('fs/promises')).mkdir(dir, { recursive: true });\n const line = JSON.stringify({\n ts: new Date().toISOString(),\n agentId,\n ...entry,\n });\n await (await import('fs/promises')).appendFile(\n join(dir, `bridge-${agentId}.log`),\n `${line}\\n`,\n 'utf-8',\n );\n } catch {\n // Non-critical\n }\n}\n\n/**\n * POST a JSON body to a Unix-domain socket using node:net + a hand-rolled\n * minimal HTTP/1.1 request. Resolves on a 200-class response, rejects on any\n * error including socket-not-found, connection refused, write timeout, or\n * non-2xx status. Kept tiny on purpose: this is a hot path, only one caller,\n * and the whole point of a fallback to tmux is that we do not need a robust\n * HTTP client here.\n */\nasync function postUnixSocketJson(\n socketPath: string,\n body: unknown,\n timeoutMs: number,\n token: string,\n tokenHeader: string = BRIDGE_TOKEN_HEADER,\n): Promise<{ status: number; body: string }> {\n const payload = JSON.stringify(body);\n\n return new Promise((resolveCall, reject) => {\n // Settle exactly once. Without this guard a late idle-timeout or\n // post-response socket error could reject after the response already\n // resolved the promise.\n let settled = false;\n let timeout: ReturnType<typeof setTimeout> | undefined;\n const clearClientTimeout = () => {\n if (timeout) clearTimeout(timeout);\n timeout = undefined;\n };\n const finishOk = (value: { status: number; body: string }) => {\n if (settled) return;\n settled = true;\n clearClientTimeout();\n resolveCall(value);\n };\n const finishErr = (err: Error) => {\n if (settled) return;\n settled = true;\n clearClientTimeout();\n reject(err);\n };\n\n const req = httpRequest(\n {\n socketPath,\n path: '/',\n method: 'POST',\n agent: false,\n headers: {\n 'Content-Type': 'application/json',\n 'Content-Length': Buffer.byteLength(payload),\n [tokenHeader]: token,\n },\n },\n (res) => {\n let responseBody = '';\n res.setEncoding('utf8');\n res.on('data', (chunk) => {\n responseBody += chunk;\n });\n res.on('end', () => {\n const status = res.statusCode ?? 0;\n if (status >= 200 && status < 300) {\n finishOk({ status, body: responseBody });\n return;\n }\n finishErr(new Error(`socket POST: status ${status}: ${responseBody.slice(0, 100)}`));\n });\n },\n );\n\n timeout = setTimeout(() => {\n req.destroy(new Error('socket POST timeout'));\n }, timeoutMs);\n timeout.unref?.();\n req.on('error', (err: Error) => {\n finishErr(err);\n });\n req.write(payload);\n req.end();\n });\n}\n\n/**\n * Single delivery primitive for orchestrator-to-work-agent messages. Auto mode\n * tries the PTY supervisor socket, then legacy Channels MCP, then tmux. Explicit\n * socket methods are strict and throw instead of falling back.\n */\nexport async function deliverAgentMessage(\n agentId: string,\n message: string,\n caller: string = 'unknown',\n deliveryMethod?: 'auto' | 'supervisor' | 'channels' | 'tmux',\n): Promise<DeliveryResult> {\n const normalizedId = normalizeAgentId(agentId);\n\n let channelsEnabled = false;\n let resolvedMethod = deliveryMethod;\n try {\n const state = await Effect.runPromise(getAgentState(normalizedId));\n channelsEnabled = Boolean(state?.channelsEnabled);\n resolvedMethod ??= state?.deliveryMethod ?? 'auto';\n } catch {\n resolvedMethod ??= 'auto';\n }\n\n if (resolvedMethod === 'tmux') {\n await assertTmuxTargetCanReceive(normalizedId, caller);\n await Effect.runPromise(sendKeys(normalizedId, message));\n return { ok: true, path: 'tmux' };\n }\n\n let supervisorFailure: string | undefined;\n if (resolvedMethod === 'auto' || resolvedMethod === 'supervisor') {\n const supervisorSocketPath = join(overdeckHomeForSockets(), 'sockets', `pty-${normalizedId}.sock`);\n const ptyToken = await readPtyToken(normalizedId);\n if (!existsSync(supervisorSocketPath)) {\n supervisorFailure = 'socket-missing';\n } else if (!ptyToken) {\n supervisorFailure = 'pty-token-missing';\n } else {\n try {\n // Must exceed the supervisor's worst-case echo-confirmation path\n // (2 attempts × 2.5s + 2 purges × 150ms ≈ 5.3s, pty-supervisor.ts).\n // A shorter client timeout abandons the POST mid-retry and fires the\n // tmux fallback while the supervisor is still writing — re-creating\n // the duplicate-submit race PAN-1769 fixed.\n await postUnixSocketJson(\n supervisorSocketPath,\n { content: message, meta: { caller } },\n 8_000,\n ptyToken,\n PTY_TOKEN_HEADER,\n );\n await appendChannelDeliveryLog(normalizedId, { path: 'supervisor', caller });\n return { ok: true, path: 'supervisor' };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n supervisorFailure = `socket-post-failed: ${reason}`;\n }\n }\n\n if (resolvedMethod === 'supervisor') {\n throw new Error(`MessageDeliveryFailed: PTY supervisor delivery failed for ${normalizedId} (${caller}): ${supervisorFailure}`);\n }\n }\n\n if (resolvedMethod === 'auto' || resolvedMethod === 'channels') {\n let channelFailure: string | undefined;\n const socketPath = join(overdeckHomeForSockets(), 'sockets', `agent-${normalizedId}.sock`);\n if (!channelsEnabled) {\n channelFailure = 'channels-disabled';\n } else if (!existsSync(socketPath)) {\n channelFailure = 'socket-missing';\n } else {\n const bridgeToken = readBridgeTokenSync(normalizedId);\n if (!bridgeToken) {\n channelFailure = 'bridge-token-missing';\n } else {\n try {\n await postUnixSocketJson(\n socketPath,\n { content: message, meta: { caller } },\n 2000,\n bridgeToken,\n );\n await appendChannelDeliveryLog(normalizedId, {\n path: 'channel',\n caller,\n ...(supervisorFailure ? { 'pty-supervisor': supervisorFailure } : {}),\n });\n return { ok: true, path: 'channels' };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n channelFailure = `socket-post-failed: ${reason}`;\n }\n }\n }\n\n if (resolvedMethod === 'channels') {\n throw new Error(`MessageDeliveryFailed: Channels delivery failed for ${normalizedId} (${caller}): ${channelFailure}`);\n }\n\n await appendChannelDeliveryLog(normalizedId, {\n path: 'tmux',\n reason: channelFailure,\n caller,\n ...(supervisorFailure ? { 'pty-supervisor': supervisorFailure } : {}),\n ...(channelFailure ? { channels: channelFailure } : {}),\n });\n await assertTmuxTargetCanReceive(normalizedId, caller);\n await Effect.runPromise(sendKeys(normalizedId, message));\n return { ok: true, path: 'tmux', failure: channelFailure ?? supervisorFailure };\n }\n\n await assertTmuxTargetCanReceive(normalizedId, caller);\n await Effect.runPromise(sendKeys(normalizedId, message));\n return { ok: true, path: 'tmux' };\n}\n\n/**\n * PAN-2228: loud failure semantics for the tmux tier. `tmux send-keys` into a\n * session whose pane process has EXITED (a remain-on-exit corpse) succeeds at the\n * tmux level while the message lands nowhere — the zombie-kickoff failure mode\n * (PAN-2179): callers saw ok:true, treated feedback as delivered, and the issue\n * silently stalled. Verify a live pane BEFORE pasting, and throw the same\n * MessageDeliveryFailed shape the strict socket methods use so every caller's\n * existing failure path fires instead of a false success. (A fully MISSING\n * session needs no check here — tmux send-keys itself errors loudly on it.)\n */\nasync function assertTmuxTargetCanReceive(normalizedId: string, caller: string): Promise<void> {\n const alive = await Effect.runPromise(sessionExists(normalizedId));\n if (!alive) return; // send-keys will fail loudly on its own for a missing session\n const paneDead = await Effect.runPromise(isPaneDead(normalizedId));\n if (paneDead) {\n throw new Error(`MessageDeliveryFailed: tmux delivery failed for ${normalizedId} (${caller}): pane-dead (session alive but the harness process has exited)`);\n }\n}\n\nconst RESUME_TRANSCRIPT_CONFIRM_TIMEOUT_MS = 3_000;\nconst RESUME_TRANSCRIPT_CONFIRM_INTERVAL_MS = 100;\n\nasync function waitForTranscriptUserRecordLanding(\n workspace: string,\n sessionId: string,\n before: TranscriptUserRecordSnapshot,\n snapshot: typeof captureTranscriptUserRecordSnapshot,\n timeoutMs = RESUME_TRANSCRIPT_CONFIRM_TIMEOUT_MS,\n intervalMs = RESUME_TRANSCRIPT_CONFIRM_INTERVAL_MS,\n): Promise<boolean> {\n const deadline = Date.now() + timeoutMs;\n const fromByteOffset = before.readOffset ?? before.fileSize;\n do {\n const after = await snapshot(workspace, sessionId, { fromByteOffset });\n if (hasNewTranscriptUserRecord(before, after)) return true;\n await new Promise(resolve => setTimeout(resolve, intervalMs));\n } while (Date.now() < deadline);\n\n const after = await snapshot(workspace, sessionId, { fromByteOffset });\n return hasNewTranscriptUserRecord(before, after);\n}\n\nexport async function deliverResumeMessageWithTranscriptConfirmation(args: {\n agentId: string;\n workspace: string;\n sessionId: string;\n message: string;\n caller: string;\n deliveryMethod?: 'auto' | 'supervisor' | 'channels' | 'tmux';\n timeoutMs?: number;\n intervalMs?: number;\n deliver?: typeof deliverAgentMessage;\n snapshot?: typeof captureTranscriptUserRecordSnapshot;\n}): Promise<{ delivered: boolean; attempts: number; lastDelivery?: DeliveryResult }> {\n const snapshot = args.snapshot ?? captureTranscriptUserRecordSnapshot;\n const deliver = args.deliver ?? deliverAgentMessage;\n const before = await snapshot(args.workspace, args.sessionId);\n let lastDelivery: DeliveryResult | undefined;\n\n for (let attempt = 1; attempt <= 2; attempt += 1) {\n lastDelivery = await deliver(args.agentId, args.message, args.caller, args.deliveryMethod);\n if (lastDelivery.ok && await waitForTranscriptUserRecordLanding(\n args.workspace,\n args.sessionId,\n before,\n snapshot,\n args.timeoutMs,\n args.intervalMs,\n )) {\n return { delivered: true, attempts: attempt, lastDelivery };\n }\n if (attempt < 2) {\n console.warn(`[resumeAgent] Auto-continue prompt did not land in ${args.sessionId}; redelivering once.`);\n }\n }\n\n return { delivered: false, attempts: 2, ...(lastDelivery ? { lastDelivery } : {}) };\n}\n\nexport async function deliverInitialPromptWithRetry(\n agentId: string,\n prompt: string,\n caller: string,\n deliveryMethod?: 'auto' | 'supervisor' | 'channels' | 'tmux',\n options: {\n timeoutMs?: number;\n intervalMs?: number;\n settleDelayMs?: number;\n deliver?: typeof deliverAgentMessage;\n snapshot?: typeof captureTranscriptUserRecordSnapshot;\n getState?: (agentId: string) => Promise<AgentState | null>;\n waitForReady?: typeof waitForPromptReady;\n sessionExists?: (agentId: string) => Promise<boolean>;\n } = {},\n): Promise<DeliveryResult> {\n const normalizedId = normalizeAgentId(agentId);\n const deliver = options.deliver ?? deliverAgentMessage;\n const snapshot = options.snapshot ?? captureTranscriptUserRecordSnapshot;\n const getState = options.getState ?? (async (id: string) => {\n try {\n return await Effect.runPromise(getAgentState(normalizeAgentId(id)));\n } catch {\n return null;\n }\n });\n const waitForReady = options.waitForReady ?? waitForPromptReady;\n const sessionExistsForAgent = options.sessionExists ?? (async (id: string) => (\n Effect.runPromise(sessionExists(normalizeAgentId(id)))\n ));\n\n function promptReadyTimeoutSeconds(): number {\n const raw = process.env.OVERDECK_PROMPT_READY_TIMEOUT_SECONDS;\n if (!raw) return 30;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : 30;\n }\n\n async function resolveTranscriptConfirmationTarget(\n state: AgentState | null,\n ): Promise<{ workspace: string; sessionId: string; before: TranscriptUserRecordSnapshot } | null> {\n if (!state?.workspace || getHarnessBehavior(state.harness).transcriptKind !== 'claude-jsonl') {\n return null;\n }\n\n let sessionId = state.sessionId;\n if (!sessionId) {\n try {\n sessionId = (await Effect.runPromise(getAgentRuntimeState(normalizedId)))?.claudeSessionId;\n } catch {\n sessionId = undefined;\n }\n }\n if (!sessionId) return null;\n\n return {\n workspace: state.workspace,\n sessionId,\n before: await snapshot(state.workspace, sessionId),\n };\n }\n\n // PAN-1803: the codex TUI mangles a large pasted kickoff prompt — a multi-\n // thousand-character paste garbles its input and trips its \"Create a plan?\"\n // mode hint, so the agent never executes. Write the full brief to a file and\n // deliver a SHORT pointer instead (robust regardless of transport — the same\n // pattern that makes file-backed handoffs reliable). Only codex needs this;\n // claude-code/pi line-based input handle the full prompt fine.\n //\n // PAN-2330: keep the workspace-level .pan/kickoff.md owned by the work/strike\n // agent. Review, test, planning, flywheel, and other role prompts can share\n // the same workspace; writing those prompts to .pan/kickoff.md overwrites the\n // work agent's brief and a later resume can make it read the wrong task.\n let deliveredPrompt = prompt;\n let state = await getState(agentId);\n try {\n if (state?.harness && getHarnessBehavior(state.harness).usesCodexHome) {\n const ownsWorkspaceKickoff = state.workspace && (state.role === 'work' || state.role === 'strike');\n const kickoffPath = ownsWorkspaceKickoff\n ? join(state.workspace, '.pan', 'kickoff.md')\n : join(getAgentDir(normalizedId), 'kickoff.md');\n const displayPath = ownsWorkspaceKickoff ? '`.pan/kickoff.md`' : `\\`${kickoffPath}\\``;\n mkdirSync(dirname(kickoffPath), { recursive: true });\n writeFileSync(kickoffPath, prompt, 'utf-8');\n deliveredPrompt =\n `Your complete task brief has been written to ${displayPath}. `\n + 'Read that file in full now and execute it exactly — it is your full set of work '\n + 'instructions. Begin immediately and keep working autonomously until done; do not '\n + 'wait for further input.';\n }\n } catch {\n // Non-fatal: fall back to delivering the full prompt inline.\n }\n\n let lastFailure = 'not-attempted';\n for (let attempt = 1; attempt <= 2; attempt += 1) {\n let harness: RuntimeName | undefined;\n try {\n state = await getState(agentId);\n harness = state?.harness;\n } catch {\n harness = undefined;\n }\n const readyTimeoutSeconds = promptReadyTimeoutSeconds();\n const ready = await waitForReady(agentId, harness, readyTimeoutSeconds);\n if (!ready) {\n const alive = await sessionExistsForAgent(agentId);\n lastFailure = alive ? 'ready-signal-timeout' : SESSION_EXITED_BEFORE_KICKOFF;\n const displayName = getHarnessBehavior(harness).displayName;\n console.error(`[${agentId}] ${displayName} did not become ready within ${readyTimeoutSeconds}s (kickoff attempt ${attempt}/2)`);\n if (!alive) break;\n continue;\n }\n\n const settleDelayMs = options.settleDelayMs ?? 500;\n if (settleDelayMs > 0) {\n await new Promise<void>((resolve) => setTimeout(resolve, settleDelayMs));\n }\n try {\n const confirmationTarget = await resolveTranscriptConfirmationTarget(state);\n const result = await deliver(agentId, deliveredPrompt, caller, deliveryMethod);\n if (result.ok) {\n if (!confirmationTarget) return result;\n const confirmed = await waitForTranscriptUserRecordLanding(\n confirmationTarget.workspace,\n confirmationTarget.sessionId,\n confirmationTarget.before,\n snapshot,\n options.timeoutMs,\n options.intervalMs,\n );\n if (confirmed) return result;\n lastFailure = 'kickoff-not-confirmed';\n } else {\n lastFailure = result.failure ?? `delivery returned ok=false via ${result.path}`;\n }\n } catch (err) {\n lastFailure = err instanceof Error ? err.message : String(err);\n }\n console.error(`[${agentId}] Kickoff delivery attempt ${attempt}/2 failed: ${lastFailure}`);\n }\n\n return { ok: false, path: 'tmux', failure: lastFailure };\n}\n\nexport async function deliverAgentPermissionDecision(\n agentId: string,\n requestId: string,\n behavior: 'allow' | 'deny',\n): Promise<void> {\n const normalizedId = normalizeAgentId(agentId);\n\n let state: AgentState | null = null;\n try {\n state = await Effect.runPromise(getAgentState(normalizedId));\n } catch {\n state = null;\n }\n\n if (!state?.channelsEnabled) {\n throw new Error(`agent ${normalizedId} is not using Claude channels`);\n }\n\n const socketPath = join(overdeckHomeForChannels(), 'sockets', `agent-${normalizedId}.sock`);\n if (!existsSync(socketPath)) {\n throw new Error(`bridge socket missing for ${normalizedId}`);\n }\n\n const bridgeToken = readBridgeTokenSync(normalizedId);\n if (!bridgeToken) {\n throw new Error(`bridge token missing for ${normalizedId}`);\n }\n\n await postUnixSocketJson(\n socketPath,\n {\n type: 'permission_response',\n requestId,\n behavior,\n },\n 2000,\n bridgeToken,\n );\n\n await appendChannelDeliveryLog(normalizedId, {\n path: 'channel',\n caller: `permission-response:${requestId}:${behavior}`,\n });\n}\n\n/** Update just the delivery method on an agent's state file. */\nexport async function setAgentDeliveryMethod(\n agentId: string,\n deliveryMethod: 'auto' | 'supervisor' | 'channels' | 'tmux',\n): Promise<void> {\n const state = await Effect.runPromise(getAgentState(agentId));\n if (!state) return;\n state.deliveryMethod = deliveryMethod;\n await Effect.runPromise(saveAgentState(state));\n}\n","/**\n * Cloister Configuration\n *\n * Loads and manages Cloister configuration from ~/.overdeck/cloister.toml\n */\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';\nimport { mkdir, readFile, writeFile } from 'fs/promises';\nimport { parse, stringify } from '@iarna/toml';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { ConfigError, FsError } from '../errors.js';\nimport { OVERDECK_HOME } from '../paths.js';\nimport type { TieredExecutionConfig } from '../agents/tier-table.js';\n\nconst CLOISTER_CONFIG_FILE = join(OVERDECK_HOME, 'cloister.toml');\n\n/**\n * Health threshold configuration (in minutes)\n */\nexport interface HealthThresholds {\n stale: number;\n warning: number;\n stuck: number;\n}\n\n/**\n * Automatic action configuration\n */\nexport interface AutoActions {\n poke_on_warning: boolean;\n poke_on_stuck: boolean; // Poke agents idle > stuck threshold (default: true)\n kill_on_stuck: boolean;\n restart_on_kill: boolean;\n /** Minimum ms between pokes for the same agent. Prevents spam on repeated health checks. Default: 30 min */\n poke_cooldown_ms: number;\n}\n\nexport interface StuckRemediationConfig {\n enabled: boolean;\n stage1_minutes: number;\n stage2_minutes: number;\n stage3_minutes: number;\n flywheel_stage1_minutes: number;\n flywheel_stage2_minutes: number;\n flywheel_stage3_minutes: number;\n}\n\n/**\n * Monitoring configuration\n */\nexport interface MonitoringConfig {\n check_interval: number; // seconds between health checks\n heartbeat_sources: ('jsonl_mtime' | 'tmux_activity' | 'git_activity' | 'active_heartbeat')[];\n}\n\n/**\n * Concurrency limits for deacon auto-resume / auto-dispatch (PAN-1665).\n *\n * These are *gates on starting new work* — the deacon never kills a running agent\n * to satisfy them. If the system is already over a limit (e.g. after a forced\n * `pan start`, or a backlog at unfreeze), the deacon simply stops resuming/\n * dispatching until natural attrition brings the count down. To forcibly trim an\n * over-limit system back to the cap, the operator uses the explicit emergency\n * brake — it is never automatic.\n */\nexport interface ConcurrencyConfig {\n /** Max concurrently-running *work* agents the deacon will resume/spawn up to. */\n max_work_agents: number;\n /**\n * Slots kept free above `max_work_agents` for advancing roles (review/test/ship)\n * so the pipeline can always drain even when work is at its cap. The overall\n * ceiling for any auto-dispatch is `max_work_agents + reserved_advancing_slots`.\n */\n reserved_advancing_slots: number;\n /**\n * Dedicated swarm-slot reserve, isolated from `max_work_agents` and\n * `reserved_advancing_slots` (PAN-2212). Swarm slot dispatch draws only from this\n * reserve, so a busy pipeline never starves the swarm — and swarm slots never\n * starve review/test in reverse. Default 3.\n */\n reserved_swarm_slots: number;\n /**\n * When true, operator-started work agents (no flywheelRunId) are exempt from\n * the emergency brake/governor reaping so the operator's deliberate spawns are\n * not trimmed to satisfy the cap. Defaults to true (PAN-1812).\n */\n exempt_operator_started?: boolean;\n /**\n * PAN-2504: memory-DRIVEN work-agent admission. When true, the work-agent\n * ceiling is derived from live free memory — admit until the next agent's\n * estimated footprint would breach the governor's SOFT reserve — instead of\n * the fixed `max_work_agents` count. This lets a big box fill toward its\n * memory budget rather than idling at a hardcoded 6. Defaults to false\n * (opt-in): unset installs keep the count-based cap. Requires the memory\n * governor to have published a cached verdict; falls back to the count cap\n * until then.\n */\n memory_driven?: boolean;\n /**\n * Safety upper bound on concurrent work agents when `memory_driven` is on —\n * caps the memory-derived ceiling so a mis-estimated footprint can't march the\n * box to hundreds of agents. Default 24.\n */\n memory_driven_max_work_agents?: number;\n /**\n * Per-work-agent RSS estimate (GB) used for the memory-driven budget:\n * additional slots = floor((availableBytes - softReserve) / work_footprint_gb).\n * Conservative by design (over-estimating admits fewer). Default 2, matching\n * `resources.governorFootprintDefaultWorkGb`.\n */\n work_footprint_gb?: number;\n /**\n * PAN-2507: preemptive pipeline scheduler. When true, a blocked advancing\n * (review/test/ship) dispatch may YIELD an idle work agent — pause it\n * (resumable, session preserved) to free the slot/memory, dispatch the\n * advancing role, and auto-resume the yielded agent oldest-first once\n * capacity returns. Defaults to false (opt-in): unset installs keep the\n * static defer-until-attrition behavior at every dispatch site.\n */\n preemption?: boolean;\n /**\n * PAN-2507: anti-thrash cap on how many work agents may be in the yielded\n * (scheduler-paused) state at once. Default 3.\n */\n max_yielded?: number;\n /**\n * PAN-2507: anti-thrash cooldown (seconds) — an agent resumed from a yield\n * may not be re-yielded until this many seconds have elapsed since its\n * resume. Default 600.\n */\n yield_cooldown_secs?: number;\n}\n\nexport type SwarmInferCompletionMode = 'off' | 'nudge' | 'auto';\n\nexport interface SwarmConfig {\n infer_completion: SwarmInferCompletionMode;\n}\n\n/**\n * Startup configuration\n */\nexport interface StartupConfig {\n auto_start: boolean; // Start Cloister when dashboard starts\n reconciliation_grace_secs: number; // Boot reconciliation grace window before default resume\n reconciliation_max_candidate_age_secs: number; // Max age for stopped agents to appear in boot reconciliation\n}\n\n/**\n * Notification configuration (future feature)\n */\nexport interface NotificationConfig {\n slack_webhook?: string;\n email?: string;\n}\n\n/**\n * Specialist agent configuration\n */\nexport interface SpecialistConfig {\n enabled: boolean;\n auto_wake: boolean;\n}\n\n/**\n * Test agent specific configuration\n */\nexport interface TestAgentConfig extends SpecialistConfig {\n test_command?: string; // Optional test command override (e.g., \"npm test\", \"pytest\", etc.)\n}\n\n/**\n * Configuration for a single reviewer agent in the parallel review flow.\n */\nexport interface ReviewAgentConfig {\n /** Unique name / role identifier (e.g. 'correctness', 'security', 'performance') */\n name: string;\n /** Optional model override (e.g. 'claude-opus-4-6'). Falls back to work-type routing. */\n model?: string;\n /** Focus areas for this reviewer (informational, passed as context) */\n focus?: string[];\n /** Set to false to skip this reviewer. Defaults to true. */\n enabled?: boolean;\n}\n\n/**\n * All specialist agents configuration\n */\nexport interface SpecialistsConfig {\n merge_agent?: SpecialistConfig;\n review_agent?: SpecialistConfig;\n test_agent?: TestAgentConfig;\n inspect_agent?: SpecialistConfig;\n uat_agent?: SpecialistConfig;\n /** User-configurable list of parallel reviewer agents. Absent ⇒ 3 built-in defaults. */\n review_agents?: ReviewAgentConfig[];\n}\n\n/**\n * Model selection configuration\n */\nexport interface ModelSelectionConfig {\n default_model: 'opus' | 'sonnet' | 'haiku';\n complexity_routing: {\n trivial: 'opus' | 'sonnet' | 'haiku';\n simple: 'opus' | 'sonnet' | 'haiku';\n medium: 'opus' | 'sonnet' | 'haiku';\n complex: 'opus' | 'sonnet' | 'haiku';\n expert: 'opus' | 'sonnet' | 'haiku';\n };\n specialist_models: {\n merge_agent?: 'opus' | 'sonnet' | 'haiku';\n review_agent?: 'opus' | 'sonnet' | 'haiku';\n test_agent?: 'opus' | 'sonnet' | 'haiku';\n inspect_agent?: 'opus' | 'sonnet' | 'haiku';\n uat_agent?: 'opus' | 'sonnet' | 'haiku';\n };\n /**\n * PAN-636 legacy coding-agent harness override. PAN-1787 treats this as a\n * deprecated alias for roles.<role>.harness; roles.<role>.harness wins when\n * both are set. Absent keys are normal for older config files.\n */\n specialist_harnesses?: {\n merge_agent?: 'claude-code' | 'pi';\n review_agent?: 'claude-code' | 'pi';\n test_agent?: 'claude-code' | 'pi';\n inspect_agent?: 'claude-code' | 'pi';\n uat_agent?: 'claude-code' | 'pi';\n };\n}\n\n/**\n * Handoff trigger configuration\n */\nexport interface HandoffTriggersConfig {\n stuck_escalation?: {\n enabled: boolean;\n haiku_to_sonnet_minutes: number;\n sonnet_to_opus_minutes: number;\n };\n test_failure?: {\n enabled: boolean;\n from_model: 'opus' | 'sonnet' | 'haiku';\n to_model: 'opus' | 'sonnet' | 'haiku';\n trigger_on: 'any_failure' | '2_consecutive';\n };\n implementation_complete?: {\n enabled: boolean;\n to_specialist: string; // e.g., 'test-agent'\n };\n}\n\n/**\n * Handoff configuration\n */\nexport interface HandoffConfig {\n auto_triggers: HandoffTriggersConfig;\n}\n\n/**\n * Cost tracking configuration\n */\nexport interface CostTrackingConfig {\n display_enabled: boolean;\n log_to_jsonl: boolean;\n}\n\n/**\n * Auto-restart configuration\n */\nexport interface AutoRestartConfig {\n enabled: boolean;\n max_retries: number;\n backoff_seconds: number[]; // Array of backoff delays (e.g., [30, 60, 120])\n}\n\n/**\n * Cost limits configuration\n */\nexport interface CostLimitsConfig {\n per_agent_usd: number;\n per_issue_usd: number;\n daily_total_usd: number;\n alert_threshold: number; // Fraction (0.0-1.0) at which to start alerting\n}\n\n/**\n * Retention policy configuration\n */\nexport interface RetentionConfig {\n /**\n * Days to keep work/planning agent state dirs after their session ends.\n * Default 7. Event-driven cleanup (postMergeLifecycle, executeCloseOut)\n * deletes state at the moment it becomes useless; this retention is only\n * a safety net for cases where those events didn't fire.\n */\n agent_state_days: number;\n /**\n * Days to keep reviewer state dirs (review-* prefix). Default 1.\n * runParallelReview Phase 6 deletes reviewer state immediately after the\n * review posts; this retention catches crashed-mid-cleanup edge cases.\n */\n reviewer_state_days?: number;\n health_staleness_hours: number; // Hours before hiding stale agents in health API (default: 24)\n}\n\nexport interface CloseOutConfig {\n remove_workspace: boolean;\n delete_feature_branch: boolean;\n auto: boolean;\n auto_delay_minutes: number;\n}\n\nexport interface OrphanProposedReconcilerConfig {\n enabled: boolean;\n minAttemptIntervalMs: number;\n}\n\n/**\n * Complete Cloister configuration\n */\nexport interface CloisterConfig {\n startup: StartupConfig;\n thresholds: HealthThresholds;\n auto_actions: AutoActions;\n stuck_remediation?: StuckRemediationConfig;\n monitoring: MonitoringConfig;\n concurrency?: ConcurrencyConfig;\n swarm?: SwarmConfig;\n notifications?: NotificationConfig;\n specialists?: SpecialistsConfig;\n model_selection?: ModelSelectionConfig;\n tiered_execution?: TieredExecutionConfig;\n handoffs?: HandoffConfig;\n cost_tracking?: CostTrackingConfig;\n auto_restart?: AutoRestartConfig;\n cost_limits?: CostLimitsConfig;\n retention?: RetentionConfig;\n close_out?: CloseOutConfig;\n orphanProposedReconciler?: OrphanProposedReconcilerConfig;\n}\n\n/**\n * Default Cloister configuration\n */\nexport const DEFAULT_CLOISTER_CONFIG: CloisterConfig = {\n startup: {\n auto_start: true,\n reconciliation_grace_secs: 120,\n reconciliation_max_candidate_age_secs: 240,\n },\n thresholds: {\n stale: 5,\n warning: 15,\n stuck: 30,\n },\n auto_actions: {\n poke_on_warning: true,\n poke_on_stuck: true, // Poke agents that have been idle > stuck threshold\n kill_on_stuck: false, // Manual by default for safety\n restart_on_kill: false,\n poke_cooldown_ms: 30 * 60 * 1000, // 30 min between pokes for the same agent\n },\n stuck_remediation: {\n enabled: true,\n stage1_minutes: 20,\n stage2_minutes: 45,\n stage3_minutes: 90,\n flywheel_stage1_minutes: 20,\n flywheel_stage2_minutes: 24,\n flywheel_stage3_minutes: 28,\n },\n monitoring: {\n check_interval: 60, // 1 minute\n heartbeat_sources: ['jsonl_mtime', 'tmux_activity', 'git_activity'],\n },\n concurrency: {\n max_work_agents: 6,\n reserved_advancing_slots: 3,\n reserved_swarm_slots: 3,\n exempt_operator_started: true,\n memory_driven: false,\n memory_driven_max_work_agents: 24,\n work_footprint_gb: 2,\n preemption: false,\n max_yielded: 3,\n yield_cooldown_secs: 600,\n },\n swarm: {\n infer_completion: 'auto',\n },\n notifications: {\n slack_webhook: undefined,\n email: undefined,\n },\n specialists: {\n merge_agent: {\n enabled: true,\n auto_wake: false, // Only wake on explicit \"Approve & Merge\" click\n },\n review_agent: {\n enabled: true,\n auto_wake: false, // Woken by the verification pipeline, not ad hoc polling\n },\n test_agent: {\n enabled: true,\n auto_wake: true,\n },\n inspect_agent: {\n enabled: true,\n auto_wake: false, // Triggered explicitly per bead via pan inspect\n },\n uat_agent: {\n enabled: true,\n auto_wake: true,\n },\n },\n model_selection: {\n default_model: 'sonnet',\n complexity_routing: {\n trivial: 'haiku',\n simple: 'haiku',\n medium: 'sonnet',\n complex: 'sonnet',\n expert: 'opus',\n },\n specialist_models: {\n // PAN-754: no hardcoded defaults. User config.yaml role settings are authoritative.\n // Resolution falls through to role model config, then to the global fallback model.\n },\n specialist_harnesses: {\n // PAN-1787/PAN-1842: removed. Harness resolution for all spawn paths\n // now flows through resolveHarness(), which consults explicit, role,\n // providerHarnesses, and built-in provider defaults in order.\n },\n },\n tiered_execution: {\n enabled: false,\n tiers: {},\n supervisor: undefined,\n replay_threshold: 0.5,\n },\n handoffs: {\n auto_triggers: {\n stuck_escalation: {\n enabled: true,\n haiku_to_sonnet_minutes: 10,\n sonnet_to_opus_minutes: 20,\n },\n test_failure: {\n enabled: true,\n from_model: 'haiku',\n to_model: 'sonnet',\n trigger_on: 'any_failure',\n },\n implementation_complete: {\n enabled: true, // Auto-handoff to test-agent when implementation done\n to_specialist: 'test-agent',\n },\n },\n },\n cost_tracking: {\n display_enabled: true,\n log_to_jsonl: true,\n },\n auto_restart: {\n enabled: true,\n max_retries: 3,\n backoff_seconds: [30, 60, 120], // 30s, 1m, 2m\n },\n cost_limits: {\n per_agent_usd: 10.0,\n per_issue_usd: 25.0,\n daily_total_usd: 100.0,\n alert_threshold: 0.8, // Alert at 80%\n },\n retention: {\n agent_state_days: 7,\n reviewer_state_days: 1,\n health_staleness_hours: 24,\n },\n close_out: {\n remove_workspace: false,\n delete_feature_branch: false,\n auto: false,\n auto_delay_minutes: 60,\n },\n orphanProposedReconciler: {\n enabled: true,\n minAttemptIntervalMs: 5 * 60 * 1000,\n },\n};\n\n/**\n * Deep merge utility that recursively merges objects.\n * - Recursively merges nested objects\n * - Arrays in overrides replace defaults (not concatenated)\n * - User values take precedence over defaults\n */\nfunction deepMerge<T extends object>(defaults: T, overrides: Partial<T>): T {\n const result = { ...defaults };\n\n for (const key of Object.keys(overrides) as (keyof T)[]) {\n const defaultVal = defaults[key];\n const overrideVal = overrides[key];\n\n // Skip undefined values in overrides\n if (overrideVal === undefined) continue;\n\n // Deep merge if both values are non-array objects\n if (\n typeof defaultVal === 'object' &&\n defaultVal !== null &&\n !Array.isArray(defaultVal) &&\n typeof overrideVal === 'object' &&\n overrideVal !== null &&\n !Array.isArray(overrideVal)\n ) {\n result[key] = deepMerge(defaultVal as any, overrideVal as any);\n } else {\n // Direct override for primitives and arrays\n result[key] = overrideVal as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Load Cloister configuration\n *\n * Reads from ~/.overdeck/cloister.toml and merges with defaults.\n * Creates default config file if it doesn't exist.\n */\nexport function loadCloisterConfigSync(): CloisterConfig {\n // Ensure overdeck home exists\n if (!existsSync(OVERDECK_HOME)) {\n mkdirSync(OVERDECK_HOME, { recursive: true });\n }\n\n let config = DEFAULT_CLOISTER_CONFIG;\n\n // If config file doesn't exist, create it with defaults\n if (!existsSync(CLOISTER_CONFIG_FILE)) {\n saveCloisterConfigSync(DEFAULT_CLOISTER_CONFIG);\n } else {\n try {\n const content = readFileSync(CLOISTER_CONFIG_FILE, 'utf-8');\n const parsed = parse(content) as unknown as Partial<CloisterConfig>;\n\n // Deep merge with defaults\n config = deepMerge(DEFAULT_CLOISTER_CONFIG, parsed);\n } catch (error) {\n console.error('Failed to load Cloister config:', error);\n console.error('Using default configuration');\n config = DEFAULT_CLOISTER_CONFIG;\n }\n }\n\n return config;\n}\n\n\n/**\n * Save Cloister configuration\n *\n * Writes configuration to ~/.overdeck/cloister.toml\n */\nexport function saveCloisterConfigSync(config: CloisterConfig): void {\n // Ensure overdeck home exists\n if (!existsSync(OVERDECK_HOME)) {\n mkdirSync(OVERDECK_HOME, { recursive: true });\n }\n\n try {\n const content = stringify(config as any);\n writeFileSync(CLOISTER_CONFIG_FILE, content, 'utf-8');\n } catch (error) {\n console.error('Failed to save Cloister config:', error);\n throw error;\n }\n}\n\n/**\n * Update Cloister configuration\n *\n * Merges partial config updates with existing config.\n */\nexport function updateCloisterConfigSync(updates: Partial<CloisterConfig>): CloisterConfig {\n const current = loadCloisterConfigSync();\n const updated = deepMerge(current, updates);\n saveCloisterConfigSync(updated);\n return updated;\n}\n\n/**\n * Get the path to the Cloister config file\n */\nexport function getCloisterConfigPath(): string {\n return CLOISTER_CONFIG_FILE;\n}\n\n/**\n * Check if Cloister should auto-start\n */\nexport function shouldAutoStart(): boolean {\n const config = loadCloisterConfigSync();\n return config.startup.auto_start;\n}\n\n/**\n * Get health thresholds in milliseconds\n */\nexport function getHealthThresholdsMs(): {\n stale: number;\n warning: number;\n stuck: number;\n} {\n const config = loadCloisterConfigSync();\n return {\n stale: config.thresholds.stale * 60 * 1000,\n warning: config.thresholds.warning * 60 * 1000,\n stuck: config.thresholds.stuck * 60 * 1000,\n };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n//\n// Additive Effect-channel variants of the config helpers above. The sync\n// variants are preserved so existing callers (CLI scripts, top-level module\n// initialization) do not have to migrate; new Effect-based callers can compose\n// these directly without `Effect.runSync` round-tripping.\n\n/** Effect variant of `loadCloisterConfig`. Falls back to defaults on read/parse failures. */\nexport const loadCloisterConfig = (): Effect.Effect<CloisterConfig, FsError | ConfigError> =>\n Effect.gen(function* () {\n yield* Effect.tryPromise({\n try: () => mkdir(OVERDECK_HOME, { recursive: true }),\n catch: (cause) => new FsError({ path: OVERDECK_HOME, operation: 'mkdir', cause }),\n });\n\n let config: CloisterConfig = DEFAULT_CLOISTER_CONFIG;\n\n if (!existsSync(CLOISTER_CONFIG_FILE)) {\n yield* saveCloisterConfig(DEFAULT_CLOISTER_CONFIG);\n } else {\n const content: string | null = yield* Effect.tryPromise({\n try: () => readFile(CLOISTER_CONFIG_FILE, 'utf-8'),\n catch: (cause) => new FsError({ path: CLOISTER_CONFIG_FILE, operation: 'readFile', cause }),\n }).pipe(\n Effect.catch((err: FsError) => {\n console.error('Failed to load Cloister config:', err);\n console.error('Using default configuration');\n return Effect.succeed<string | null>(null);\n }),\n );\n\n if (content !== null) {\n try {\n const parsed = parse(content) as unknown as Partial<CloisterConfig>;\n config = deepMerge(DEFAULT_CLOISTER_CONFIG, parsed);\n } catch (error) {\n console.error('Failed to parse Cloister config:', error);\n console.error('Using default configuration');\n config = DEFAULT_CLOISTER_CONFIG;\n }\n }\n }\n\n return config;\n });\n\n/** Effect variant of `saveCloisterConfig`. */\nexport const saveCloisterConfig = (config: CloisterConfig): Effect.Effect<void, FsError | ConfigError> =>\n Effect.gen(function* () {\n yield* Effect.tryPromise({\n try: () => mkdir(OVERDECK_HOME, { recursive: true }),\n catch: (cause) => new FsError({ path: OVERDECK_HOME, operation: 'mkdir', cause }),\n });\n\n const content = yield* Effect.try({\n try: () => stringify(config as any),\n catch: (cause) => new ConfigError({ message: 'Failed to serialize Cloister config', cause }),\n });\n\n yield* Effect.tryPromise({\n try: () => writeFile(CLOISTER_CONFIG_FILE, content, 'utf-8'),\n catch: (cause) => new FsError({ path: CLOISTER_CONFIG_FILE, operation: 'writeFile', cause }),\n });\n });\n\n/** Effect variant of `updateCloisterConfig`. */\nexport const updateCloisterConfig = (\n updates: Partial<CloisterConfig>,\n): Effect.Effect<CloisterConfig, FsError | ConfigError> =>\n Effect.gen(function* () {\n const current = yield* loadCloisterConfig();\n const updated = deepMerge(current, updates);\n yield* saveCloisterConfig(updated);\n return updated;\n });\n","/**\n * PAN-2500: the cached MemoryVerdict, split out from memory-governor.ts into\n * its own zero-dependency module. concurrency.ts (canDispatchAdvancing /\n * tryReserveAdvancingSlot) needs to read this synchronously without importing\n * memory-governor.ts directly — memory-governor.ts pulls in\n * dashboard/server/routes/resources/stacks.ts, which transitively reaches\n * concurrency.ts itself (stacks.ts -> review-status.ts -> agents.ts ->\n * agents/spawn.ts -> concurrency.ts), so a direct concurrency.ts ->\n * memory-governor.ts import closes a real cycle (caught by\n * scripts/lint-circular-deps.sh). This module has no imports of its own, so\n * it cannot be part of any cycle.\n */\n\nexport type MemoryPressureBand = 'ok' | 'soft' | 'hard';\n\nexport interface MemoryPressureThresholds {\n warningBytes: number;\n criticalBytes: number;\n}\n\nexport interface MemoryVerdict {\n band: MemoryPressureBand;\n availableBytes: number;\n thresholds: MemoryPressureThresholds;\n}\n\nlet cachedVerdict: MemoryVerdict | null = null;\n\n/**\n * The verdict from the most recent assessMemoryPressure() call, or null before\n * any patrol has run one. Synchronous — no I/O.\n */\nexport function getCachedMemoryVerdict(): MemoryVerdict | null {\n return cachedVerdict;\n}\n\nexport function setCachedMemoryVerdict(verdict: MemoryVerdict | null): void {\n cachedVerdict = verdict;\n}\n\n/** Test-only. */\nexport function setCachedMemoryVerdictForTests(verdict: MemoryVerdict | null): void {\n cachedVerdict = verdict;\n}\n","/**\n * Concurrency governor for deacon auto-resume / auto-dispatch (PAN-1665).\n *\n * The deacon's patrol re-spawns work from several sources (resume stopped work\n * agents, re-dispatch orphaned review/test specialists, …). With no ceiling, an\n * unfreeze marches the box toward dozens of concurrent heavy `claude` processes.\n *\n * This module is the single budget every patrol spawn path consults. Two rules:\n *\n * 1. It is a **gate on starting new work** — it NEVER kills a running agent to\n * get back under a limit. If the system is already over (a forced `pan start`,\n * a backlog at unfreeze), the deacon simply resumes/dispatches nothing until\n * natural attrition drains the count. Forcibly trimming an over-limit system\n * is the operator's explicit emergency brake, never automatic.\n * 2. Advancing roles (review/test/ship) get reserved headroom above the work cap\n * so the pipeline can always drain instead of deadlocking with work agents.\n */\n\nimport { loadCloisterConfigSync } from './config.js';\nimport {\n listRunningAgentsSync,\n stopAgentSync,\n getAgentStateSync,\n saveAgentStateSync,\n getAgentRuntimeStateSync,\n} from '../agents.js';\nimport { countAgentsByStatus } from '../overdeck/agents.js';\nimport { getCachedMemoryVerdict } from './memory-verdict-cache.js';\nimport { isRoleTerminal, type AdvancingRole } from './reap-terminal-sessions.js';\nimport { readReviewStatusMap } from './review-status-source.js';\n\nconst DEFAULT_MAX_WORK_AGENTS = 6;\nconst DEFAULT_RESERVED_ADVANCING_SLOTS = 3;\nconst DEFAULT_RESERVED_SWARM_SLOTS = 3;\n\n/** Roles that advance work through the pipeline and must keep reserved headroom. */\nconst ADVANCING_ROLES = new Set(['review', 'test', 'ship']);\n\n/**\n * Swarm slots (PAN-2212) are work-role sessions with an `agent-<issue>-slot-N`\n * id. They draw from a dedicated swarm reserve and are counted APART from `work`,\n * so a busy pipeline never starves the swarm — and running swarm slots never\n * starve review/test in reverse.\n */\nconst SWARM_SLOT_ID = /-slot-\\d+$/;\n\nexport interface ConcurrencyLimits {\n maxWorkAgents: number;\n reservedAdvancingSlots: number;\n /** Dedicated swarm-slot reserve, isolated from the work/advancing ceiling (PAN-2212). */\n reservedSwarmSlots: number;\n /** Overall ceiling for any auto-dispatch: work cap + reserved advancing slots. */\n totalCeiling: number;\n /** Whether operator-started agents are exempt from governor reaping (PAN-1812). */\n exemptOperatorStarted: boolean;\n}\n\nfunction normalizeCount(value: unknown, fallback: number, min: number): number {\n if (typeof value !== 'number' || !Number.isFinite(value)) return fallback;\n const floored = Math.floor(value);\n return floored < min ? min : floored;\n}\n\nexport function getConcurrencyLimits(): ConcurrencyLimits {\n const c = loadCloisterConfigSync().concurrency;\n const maxWorkAgents = normalizeCount(c?.max_work_agents, DEFAULT_MAX_WORK_AGENTS, 1);\n const reservedAdvancingSlots = normalizeCount(c?.reserved_advancing_slots, DEFAULT_RESERVED_ADVANCING_SLOTS, 0);\n const reservedSwarmSlots = normalizeCount(c?.reserved_swarm_slots, DEFAULT_RESERVED_SWARM_SLOTS, 0);\n return {\n maxWorkAgents,\n reservedAdvancingSlots,\n reservedSwarmSlots,\n totalCeiling: maxWorkAgents + reservedAdvancingSlots,\n exemptOperatorStarted: c?.exempt_operator_started ?? true,\n };\n}\n\nexport interface RunningCounts {\n /** Regular work agents, EXCLUDING swarm slots (PAN-2212). */\n work: number;\n advancing: number;\n /** Swarm-slot work agents, counted apart from `work` (PAN-2212). */\n swarm: number;\n /** work + advancing (EXCLUDES swarm) — the ceiling every non-swarm dispatch consults. */\n total: number;\n}\n\n/**\n * Human-readable breakdown of what currently counts against the ceiling, for\n * deferral diagnostics (PAN-1716). Lists the offending session ids so a\n * starvation is diagnosable from the patrol log alone — the original livelock\n * (completed review/test sessions that never get reaped) is invisible from a\n * bare \"ceiling reached\" line.\n */\nexport function describeRunningAgents(): string {\n const alive = listRunningAgentsSync().filter(a => a.tmuxActive);\n const swarm = alive.filter(a => a.role === 'work' && SWARM_SLOT_ID.test(a.id)).map(a => a.id);\n const work = alive.filter(a => a.role === 'work' && !SWARM_SLOT_ID.test(a.id)).map(a => a.id);\n const advancing = alive.filter(a => a.role && ADVANCING_ROLES.has(a.role)).map(a => a.id);\n const warmIdle = countWarmIdleAdvancingAgents(alive);\n const { totalCeiling } = getConcurrencyLimits();\n return `counts: work=${work.length} advancing=${advancing.length} (warm-idle=${warmIdle}, excluded) swarm=${swarm.length} total=${work.length + Math.max(0, advancing.length - warmIdle)}/${totalCeiling}`\n + ` | advancing=[${advancing.join(', ')}] work=[${work.join(', ')}] swarm=[${swarm.join(', ')}]`;\n}\n\n/** Count currently-running agents by role class.\n *\n * PAN-1908: the agents table is the authoritative runtime registry. Counts are\n * derived from status='running' rows grouped by role; the deacon's event-driven\n * updates keep status in sync with tmux liveness.\n */\n/** Count tmux-alive swarm-slot work agents (agent-<issue>-slot-N) — PAN-2212. */\nfunction countRunningSwarmSlots(): number {\n return listRunningAgentsSync().filter(\n a => a.tmuxActive && a.role === 'work' && SWARM_SLOT_ID.test(a.id),\n ).length;\n}\n\n/**\n * Count tmux-ALIVE swarm-slot work agents for one issue (agent-<issue>-slot-N).\n * Stale agents-table rows whose tmux session is dead do NOT count — counting\n * them blocked all dispatch at zero live slots after a reset (PAN-2214).\n */\nexport function countRunningSwarmSlotsForIssue(\n issueId: string,\n agents: ReturnType<typeof listRunningAgentsSync> = listRunningAgentsSync(),\n): number {\n const prefix = `agent-${issueId.toLowerCase()}-slot-`;\n return agents.filter(\n a => a.tmuxActive && a.role === 'work' && SWARM_SLOT_ID.test(a.id) && a.id.startsWith(prefix),\n ).length;\n}\n\n/**\n * PAN-2579: count running advancing-role agents whose issue's phase verdict is\n * already terminal — \"warm-idle\" sessions kept alive for fast re-review under\n * the warm-by-default lifecycle. They are free capacity, not load: excluding\n * them from the ceiling is what lets warm sessions persist without recreating\n * the PAN-1716 livelock (completed reviewers starving every new dispatch).\n * Best-effort: if the review-status source is unregistered or unreadable,\n * count nothing as warm-idle (the ceiling stays conservative). The status map\n * is read through the cycle-free review-status-source leaf, registered by\n * review-status.ts at module load.\n */\nexport function countWarmIdleAdvancingAgents(\n agents: ReturnType<typeof listRunningAgentsSync> = listRunningAgentsSync(),\n): number {\n const advancingRows = agents.filter(a => a.role && ADVANCING_ROLES.has(a.role) && a.issueId);\n if (advancingRows.length === 0) return 0;\n const statuses = readReviewStatusMap();\n if (!statuses) return 0;\n let warmIdle = 0;\n for (const row of advancingRows) {\n const status = statuses[row.issueId!.toUpperCase()];\n if (status && isRoleTerminal(row.role as AdvancingRole, status)) warmIdle++;\n }\n return warmIdle;\n}\n\nexport function countRunningAgents(): RunningCounts {\n const counts = countAgentsByStatus('running');\n const workTotal = counts['work'] ?? 0;\n let advancingTotal = 0;\n for (const role of ADVANCING_ROLES) {\n advancingTotal += counts[role] ?? 0;\n }\n // Swarm slots are work-role sessions but draw from the dedicated swarm reserve,\n // so subtract them from `work` (PAN-2212): the swarm neither starves nor is\n // starved by the work/advancing ceiling.\n const swarm = countRunningSwarmSlots();\n const work = Math.max(0, workTotal - swarm);\n // PAN-2579: warm-idle advancing sessions (verdict terminal, kept alive for the\n // next cycle) do not occupy the ceiling.\n const advancing = Math.max(0, advancingTotal - countWarmIdleAdvancingAgents());\n return { work, advancing, swarm, total: work + advancing };\n}\n\nconst GIB = 1024 ** 3;\n\n/**\n * PAN-2504: additional work-agent admissions permitted by the *memory* budget,\n * rather than the fixed `max_work_agents` count. This is what makes the box fill\n * toward its RAM instead of idling at a hardcoded 6.\n *\n * additional = floor((availableBytes - softReserve) / work_footprint)\n * capped by (memory_driven_max_work_agents - runningWork)\n *\n * The SOFT reserve is read from the cached memory verdict's\n * `thresholds.warningBytes` — the governor already publishes it there, so this\n * needs no import of memory-governor.ts (which would close a real dependency\n * cycle; see memory-verdict-cache.ts).\n *\n * Returns null — meaning \"use the count-based cap\" — when memory-driven mode is\n * off OR the governor has not published a verdict yet (fail safe to the fixed\n * cap rather than admitting blind). availableBytes already reflects the RSS of\n * currently-running agents, so `additional` is genuinely how many *more* fit;\n * the per-iteration `assessMemoryPressure()` brake in the deacon loops still\n * catches an under-estimated footprint mid-fill.\n */\nexport function memoryDrivenWorkSlots(runningWork: number): number | null {\n const c = loadCloisterConfigSync().concurrency;\n if (c?.memory_driven !== true) return null;\n const verdict = getCachedMemoryVerdict();\n if (!verdict) return null;\n const perAgentBytes = Math.max(0.25, c?.work_footprint_gb ?? 2) * GIB;\n const budgetBytes = verdict.availableBytes - verdict.thresholds.warningBytes;\n const fits = budgetBytes > 0 ? Math.floor(budgetBytes / perAgentBytes) : 0;\n const ceiling = normalizeCount(c?.memory_driven_max_work_agents, 24, 1);\n return Math.max(0, Math.min(fits, ceiling - runningWork));\n}\n\n/**\n * How many work agents the deacon may resume/spawn this patrol. Zero when already\n * at or over the cap — the deacon then resumes nothing and lets attrition drain.\n *\n * PAN-2504: when `concurrency.memory_driven` is on and the governor has a cached\n * verdict, the ceiling is the memory budget (`memoryDrivenWorkSlots`) instead of\n * the fixed count cap.\n */\nexport function workResumeSlotsAvailable(\n counts: RunningCounts = countRunningAgents(),\n limits: ConcurrencyLimits = getConcurrencyLimits(),\n): number {\n const memSlots = memoryDrivenWorkSlots(counts.work);\n if (memSlots !== null) return memSlots;\n return Math.max(0, limits.maxWorkAgents - counts.work);\n}\n\n/**\n * Whether an advancing-role (review/test/ship) dispatch is allowed. Gated on the\n * overall ceiling so review/test/ship can always claim their reserved headroom —\n * and, per PAN-2500 specialist-budget, on the memory governor: don't reserve GB\n * for a specialist under memory pressure. Count-slot semantics are UNCHANGED\n * when the cached band is 'ok' (or no patrol has assessed memory yet).\n */\nexport function canDispatchAdvancing(\n counts: RunningCounts = countRunningAgents(),\n limits: ConcurrencyLimits = getConcurrencyLimits(),\n): boolean {\n const verdict = getCachedMemoryVerdict();\n if (verdict && verdict.band !== 'ok') return false;\n return counts.total < limits.totalCeiling;\n}\n\n// ---------------------------------------------------------------------------\n// Per-patrol advancing-dispatch reservation\n//\n// countRunningAgents() only sees tmux-alive sessions. Agents dispatched earlier\n// in the SAME patrol haven't registered a session yet, so several dispatch\n// functions (checkOrphanedReviewStatuses, checkMissingReviewStatuses,\n// checkPendingTestDispatch, checkPostReviewCommits) running back-to-back would\n// each see the stale low count and blow past the ceiling. runPatrol() resets\n// this counter at the top of every cycle; each dispatch site reserves a slot.\n// ---------------------------------------------------------------------------\nlet advancingReservedThisPatrol = 0;\n/** Dedicated per-patrol swarm-dispatch budget, isolated from advancing (PAN-2212). */\nlet swarmReservedThisPatrol = 0;\n\n/** Reset the per-patrol dispatch budgets. Called once at patrol start. */\nexport function resetPatrolDispatchBudget(): void {\n advancingReservedThisPatrol = 0;\n swarmReservedThisPatrol = 0;\n}\n\n/**\n * Claim one advancing-role (review/test/ship) dispatch slot for this patrol.\n * Returns false when the total ceiling is reached, or (PAN-2500 specialist-budget)\n * when the memory governor's cached band is not 'ok' — the caller must DEFER\n * (leave status untouched so a later patrol retries), never fail. Counts both\n * tmux-alive agents and advancing dispatches already reserved this patrol.\n */\nexport function tryReserveAdvancingSlot(\n counts: RunningCounts = countRunningAgents(),\n limits: ConcurrencyLimits = getConcurrencyLimits(),\n): boolean {\n const verdict = getCachedMemoryVerdict();\n if (verdict && verdict.band !== 'ok') return false;\n if (counts.total + advancingReservedThisPatrol >= limits.totalCeiling) return false;\n advancingReservedThisPatrol++;\n return true;\n}\n\n/** Release a same-patrol advancing reservation when dispatch was calmly gated. */\nexport function releaseAdvancingSlot(): void {\n advancingReservedThisPatrol = Math.max(0, advancingReservedThisPatrol - 1);\n}\n\n/**\n * Claim one swarm-slot dispatch for this patrol (PAN-2212). Gated ONLY on the\n * dedicated swarm reserve — never the work/advancing ceiling — so a busy pipeline\n * never starves the swarm. Returns false when the reserve is full; the caller\n * DEFERS (leave the item unclaimed so a later patrol retries), never fails.\n */\nexport function tryReserveSwarmSlot(\n counts: RunningCounts = countRunningAgents(),\n limits: ConcurrencyLimits = getConcurrencyLimits(),\n): boolean {\n if (counts.swarm + swarmReservedThisPatrol >= limits.reservedSwarmSlots) return false;\n swarmReservedThisPatrol++;\n return true;\n}\n\n/** Release a same-patrol swarm reservation when dispatch was calmly gated. */\nexport function releaseSwarmSlot(): void {\n swarmReservedThisPatrol = Math.max(0, swarmReservedThisPatrol - 1);\n}\n\n// ---------------------------------------------------------------------------\n// Emergency brake\n//\n// The governor never auto-kills to satisfy a limit, so an over-cap system\n// (forced `pan start`s, an unfreeze backlog) stays over until attrition drains.\n// The emergency brake is the *explicit operator action* that forcibly trims the\n// excess. It is deliberately separate from the nuclear `emergencyStop` (which\n// kills ALL agents): the brake stops only work agents above the cap, idle ones\n// first, and clears the user-stop flag so the deacon re-admits them as slots free\n// (drain-at-cap, not retirement). Never called automatically.\n// ---------------------------------------------------------------------------\nexport interface BrakeResult {\n /** Running work agents before the brake. */\n before: number;\n /** The configured work-agent cap. */\n cap: number;\n /** Agent ids stopped by the brake. */\n stopped: string[];\n /** Running work agents after the brake. */\n remaining: number;\n}\n\nexport function emergencyBrake(): BrakeResult {\n const { maxWorkAgents, exemptOperatorStarted } = getConcurrencyLimits();\n const runningWork = listRunningAgentsSync().filter(a => a.tmuxActive && a.role === 'work');\n const excess = runningWork.length - maxWorkAgents;\n if (excess <= 0) {\n return { before: runningWork.length, cap: maxWorkAgents, stopped: [], remaining: runningWork.length };\n }\n\n // PAN-1812: operator-started work agents (no flywheelRunId) are exempt from\n // automatic governor reaping when the config flag is enabled.\n const candidates = exemptOperatorStarted\n ? runningWork.filter(a => a.flywheelRunId !== undefined && a.flywheelRunId !== null && a.flywheelRunId !== '')\n : runningWork;\n\n // Stop the least-productive first: idle agents ahead of active ones, and among\n // equals the stalest (oldest lastActivity) first.\n const ordered = [...candidates].sort((a, b) => {\n const aIdle = getAgentRuntimeStateSync(a.id)?.state === 'idle' ? 0 : 1;\n const bIdle = getAgentRuntimeStateSync(b.id)?.state === 'idle' ? 0 : 1;\n if (aIdle !== bIdle) return aIdle - bIdle;\n return (Date.parse(a.lastActivity ?? '') || 0) - (Date.parse(b.lastActivity ?? '') || 0);\n });\n\n const stopped: string[] = [];\n for (const agent of ordered.slice(0, excess)) {\n try {\n stopAgentSync(agent.id);\n // stopAgentSync stamps stoppedByUser=true (deliberate stop). Clear it so the\n // deacon re-admits this agent when a slot frees — the brake trims to the cap,\n // it does not retire the work.\n const state = getAgentStateSync(agent.id);\n if (state) {\n delete state.stoppedByUser;\n saveAgentStateSync(state);\n }\n stopped.push(agent.id);\n } catch {\n // best effort — skip agents that fail to stop cleanly\n }\n }\n\n return {\n before: runningWork.length,\n cap: maxWorkAgents,\n stopped,\n remaining: runningWork.length - stopped.length,\n };\n}\n","import { existsSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { FsError, TmuxError } from '../errors.js';\nimport type { AgentStatus } from '@overdeck/contracts';\nimport type { AgentState, Role } from '../agents.js';\nimport { getAgentState, isRole, normalizeAgentId } from '../agents.js';\nimport { killSession, listSessions, listSessionsSync } from '../tmux.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport { getRollbackAgentStatePath } from '../overdeck/agent-rollback-state.js';\nimport { listOverdeckAgentStatesSync } from '../overdeck/agent-state-sync.js';\n\nexport function listRunningAgentsSync(): (AgentState & { tmuxActive: boolean })[] {\n // Match liveness against ALL overdeck-socket sessions, not just `agent-*`.\n // Agent state dirs are named by role prefix (planning-/agent-/conv-/strike-);\n // getAgentSessions only returns `agent-*`, so planning/conv/strike sessions\n // would always read tmuxActive:false and get dropped by the enrichment poller.\n const tmuxSessions = listSessionsSync();\n const tmuxNames = new Set(tmuxSessions.map(s => s.name));\n\n return listOverdeckAgentStatesSync().map((state) => {\n const normalizedId = normalizeAgentId(state.id);\n return {\n ...state,\n id: normalizedId,\n tmuxActive: tmuxNames.has(normalizedId),\n };\n });\n}\n\n/**\n * PAN-1908: list all agents in the SQLite registry with optional filtering.\n * This is the replacement for enumerating ~/.overdeck/agents/ directories.\n */\nexport function listAgentStates(options?: { status?: AgentStatus; role?: Role }): AgentState[] {\n return listOverdeckAgentStatesSync()\n .filter((state) => {\n if (options?.status && state.status !== options.status) return false;\n if (options?.role && state.role !== options.role) return false;\n return true;\n });\n}\n\n\nexport const listRunningAgents = (): Effect.Effect<(AgentState & { tmuxActive: boolean })[], FsError | TmuxError> =>\n Effect.gen(function* () {\n // PAN-1908: authoritative registry is the SQLite agents table; no directory scan.\n //\n // TRAP — `tmuxActive` reflects whether THIS process can see the agent's tmux\n // session on the `overdeck` socket. Run this from a one-off `tsx -e`/CLI\n // process that lacks access to that socket and `listSessions()` returns\n // empty, so EVERY agent comes back `tmuxActive: false` — including ones that\n // are genuinely running. Do not conclude \"the agent isn't running\" / \"the\n // enrichment poller skips it\" from an out-of-server-process reading. Trust\n // the live dashboard server's view (it owns the socket) or check the tmux\n // session directly with `tmux -L overdeck list-sessions`.\n //\n // Use the UNFILTERED session list (not getAgentSessions, which is `agent-*`\n // only): agent state dirs carry role prefixes (planning-/agent-/conv-/strike-),\n // and planning/conv/strike sessions must read tmuxActive:true so the\n // enrichment poller scans them for AskUserQuestion / pending input (PAN-1395).\n const tmuxSessions = yield* listSessions();\n const tmuxNames = new Set(tmuxSessions.map(s => s.name));\n\n return listOverdeckAgentStatesSync().map((state) => {\n const normalizedId = normalizeAgentId(state.id);\n return {\n ...state,\n id: normalizedId,\n tmuxActive: tmuxNames.has(normalizedId),\n };\n });\n });\n\n/**\n * PAN-1048 P2: async startup migration.\n *\n * The previous synchronous version used readdirSync, readFileSync,\n * killSession (sync tmux subprocess), and rmSync — all on the Node\n * event loop. Called from warnOnBareNumericIssueIds() during dashboard\n * read-model bootstrap, this blocked all HTTP/WebSocket/PTY traffic on\n * server startup while it scanned every agent dir, killed stale tmux\n * sessions, and recursively deleted directories.\n *\n * This async variant does the same work using fs/promises and the\n * already-async killSessionAsync() so the bootstrap path no longer\n * stalls the event loop.\n */\nexport async function dropLegacyAgentStatesMissingRoleAsync(): Promise<number> {\n if (!existsSync(AGENTS_DIR)) return 0;\n\n const fsp = await import('fs/promises');\n let entries: string[];\n try {\n entries = await fsp.readdir(AGENTS_DIR);\n } catch {\n return 0;\n }\n\n let dropped = 0;\n await Promise.all(\n entries.map(async (entry) => {\n const dirPath = join(AGENTS_DIR, entry);\n let stat;\n try {\n stat = await fsp.stat(dirPath);\n } catch {\n return;\n }\n if (!stat.isDirectory()) return;\n\n const agentId = normalizeAgentId(entry);\n const stateFile = getRollbackAgentStatePath(agentId);\n let raw: { role?: unknown };\n try {\n const contents = await fsp.readFile(stateFile, 'utf8');\n raw = JSON.parse(contents) as { role?: unknown };\n } catch {\n return;\n }\n if (isRole(raw.role)) return;\n\n try { await Effect.runPromise(killSession(agentId)); } catch { /* best effort */ }\n try {\n await fsp.rm(dirPath, { recursive: true, force: true });\n dropped++;\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.warn(`[agents] Failed to drop legacy agent state ${agentId}: ${msg}`);\n }\n }),\n );\n\n return dropped;\n}\n\n/**\n * Scan ~/.overdeck/agents/ for state files with bare numeric issueIds\n * (e.g. \"484\" instead of \"PAN-484\") and log warnings to stderr.\n *\n * These workspaces were created before the pan- prefix convention and may\n * cause cross-tracker pollution if their in_review transition is triggered.\n * Called once at server startup to surface legacy state files.\n */\n/**\n * PAN-1048 P2: bootstrap-path migration is async.\n *\n * Sweeps legacy state files missing a `role` field and warns on bare\n * numeric issueIds. Both passes used to be synchronous (readdirSync,\n * readFileSync, killSession, rmSync), which blocked the dashboard\n * server's event loop on startup. The async version scans the same\n * directory once per concern and uses fs/promises throughout.\n */\nexport async function warnOnBareNumericIssueIds(): Promise<void> {\n const droppedLegacyAgents = await dropLegacyAgentStatesMissingRoleAsync();\n if (droppedLegacyAgents > 0) {\n console.warn(`[agents] Dropped ${droppedLegacyAgents} legacy agent state file(s) missing role`);\n }\n\n if (!existsSync(AGENTS_DIR)) return;\n\n const fsp = await import('fs/promises');\n let entries: string[];\n try {\n entries = await fsp.readdir(AGENTS_DIR);\n } catch {\n return;\n }\n\n const legacy: string[] = [];\n await Promise.all(\n entries.map(async (entry) => {\n const dirPath = join(AGENTS_DIR, entry);\n try {\n const stat = await fsp.stat(dirPath);\n if (!stat.isDirectory()) return;\n } catch {\n return;\n }\n const state = await Effect.runPromise(getAgentState(entry));\n if (state?.issueId && /^\\d+$/.test(state.issueId)) {\n legacy.push(`${entry} (issueId: \"${state.issueId}\")`);\n }\n }),\n );\n\n if (legacy.length > 0) {\n console.warn(\n `[agents] WARNING: ${legacy.length} agent state file(s) have bare numeric issueIds ` +\n `(created before the pan- prefix convention). These agents will not be able to ` +\n `transition tracker state. Consider removing or updating them:\\n` +\n legacy.map(l => ` ~/.overdeck/agents/${l}`).join('\\n')\n );\n }\n}\n","import { readFileSync, mkdirSync, writeFileSync } from 'fs';\nimport { mkdir as mkdirAsync, writeFile as writeFileAsync } from 'fs/promises';\nimport { exec, execSync } from 'child_process';\nimport { promisify } from 'util';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport {\n getAgentDir,\n getAgentStateSync,\n getAgentState,\n saveAgentStateSync,\n saveAgentState,\n markAgentStoppedState,\n saveAgentRuntimeState,\n normalizeAgentId,\n} from '../agents.js';\nimport { emitAgentEvent } from '../agent-runtime.js';\nimport { AGENTS_DIR } from '../paths.js';\nimport {\n sessionExistsSync,\n capturePaneSync,\n killSessionSync,\n sessionExists,\n capturePane,\n killSession,\n} from '../tmux.js';\nimport { FsError, TmuxError } from '../errors.js';\n\nconst execAsync = promisify(exec);\n\n/**\n * True when the PID is a tmux process (client or server). Used to keep the\n * per-agent launcher kill sweep from ever signalling the shared tmux server\n * (PAN-1798). Reads /proc/<pid>/comm; on non-Linux or read failure returns\n * false (fail-open matches pre-fix behavior for non-tmux processes).\n */\nfunction isTmuxProcessSync(pid: number): boolean {\n try {\n return readFileSync(`/proc/${pid}/comm`, 'utf-8').trim() === 'tmux';\n } catch {\n return false;\n }\n}\n\n/**\n * Find and kill any running `launcher.sh` process for the given agent.\n *\n * PAN-1527: `tmux kill-session` only signals tmux-managed children. Planning\n * agents (and any agent whose launcher escapes its tmux session) leave\n * orphan launcher.sh processes alive — state.json says stopped, but bash is\n * still burning CPU and tokens hours later. This locates them by command\n * line and walks SIGTERM → grace → SIGKILL.\n *\n * Sync version: callable from CLI (`pan kill`) and from the existing\n * `stopAgentSync`. Uses execSync only via `pgrep`, which is fast and\n * non-blocking in practice. Acceptable per CLAUDE.md because this path is\n * sync-by-nature already and is only called from CLI contexts and existing\n * sync internals.\n */\nfunction killLauncherProcessSync(agentId: string): void {\n const launcherPath = join(AGENTS_DIR, agentId, 'launcher.sh');\n let pidsOut: string;\n try {\n pidsOut = execSync(\n `pgrep -f ${JSON.stringify(launcherPath)}`,\n { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] },\n ).trim();\n } catch {\n return; // pgrep exits 1 when there are no matches — nothing to kill\n }\n\n const pids = pidsOut\n .split('\\n')\n .map(s => Number.parseInt(s, 10))\n .filter(n => Number.isFinite(n) && n > 0 && n !== process.pid)\n // PAN-1798: when this agent's spawn FOUNDED the shared tmux server, the\n // server's cmdline embeds this launcher path (`tmux ... new-session ...\n // bash .../launcher.sh`), so pgrep -f matches the server itself. Killing\n // it destroys every session on the socket — agents, reviews, and all\n // conversations. Never signal a tmux process from the per-agent sweep.\n .filter(pid => !isTmuxProcessSync(pid));\n if (pids.length === 0) return;\n\n for (const pid of pids) {\n try { process.kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n\n // ~500ms grace period for orderly shutdown. Sync spawn of `sleep` is\n // acceptable in CLI context; this function is never reached from the\n // dashboard server (which uses the async `stopAgent` Effect below).\n try {\n execSync('sleep 0.5', { stdio: 'ignore' });\n } catch { /* ignore */ }\n\n const survivors: number[] = [];\n for (const pid of pids) {\n try {\n process.kill(pid, 0);\n survivors.push(pid);\n } catch {\n /* already dead */\n }\n }\n for (const pid of survivors) {\n try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ }\n }\n}\n\nasync function killLauncherProcessAsync(agentId: string): Promise<void> {\n const launcherPath = join(AGENTS_DIR, agentId, 'launcher.sh');\n let pidsOut: string;\n try {\n const { stdout } = await execAsync(`pgrep -f ${JSON.stringify(launcherPath)}`);\n pidsOut = stdout.trim();\n } catch {\n return; // pgrep exits 1 when there are no matches\n }\n\n const pids = pidsOut\n .split('\\n')\n .map(s => Number.parseInt(s, 10))\n .filter(n => Number.isFinite(n) && n > 0 && n !== process.pid)\n // PAN-1798: see killLauncherProcessSync — the founding tmux server's\n // cmdline embeds this launcher path; never signal tmux from this sweep.\n .filter(pid => !isTmuxProcessSync(pid));\n if (pids.length === 0) return;\n\n for (const pid of pids) {\n try { process.kill(pid, 'SIGTERM'); } catch { /* already gone */ }\n }\n\n await new Promise<void>(resolve => setTimeout(resolve, 500));\n\n for (const pid of pids) {\n try {\n process.kill(pid, 0);\n try { process.kill(pid, 'SIGKILL'); } catch { /* ignore */ }\n } catch {\n /* already dead */\n }\n }\n}\n\nexport function stopAgentSync(agentId: string): void {\n const normalizedId = normalizeAgentId(agentId);\n\n if (sessionExistsSync(normalizedId)) {\n // Capture tmux output before killing so logs remain viewable after stop\n try {\n const output = capturePaneSync(normalizedId, 5000);\n if (output) {\n const agentDir = getAgentDir(normalizedId);\n mkdirSync(agentDir, { recursive: true });\n writeFileSync(join(agentDir, 'output.log'), output);\n }\n } catch {\n // Non-fatal — best effort log capture\n }\n\n killSessionSync(normalizedId);\n }\n\n // PAN-1527: kill orphan launcher.sh processes that escape tmux (planning\n // agents, dashboard-spawned launchers, anything that survived tmux\n // kill-session). Runs even when no tmux session existed in the first place.\n killLauncherProcessSync(normalizedId);\n\n const state = getAgentStateSync(normalizedId);\n if (state) {\n // Ensure id is set — runtime state files may lack it (PAN-150)\n if (!state.id) state.id = normalizedId;\n\n markAgentStoppedState(state);\n saveAgentStateSync(state);\n }\n\n // Also mark runtime.json as stopped so Cloister/Deacon won't auto-restart.\n // state.json and runtime.json are separate files — both must agree the agent\n // was intentionally stopped to prevent race conditions with health check polls.\n console.log(`[agents] Stopping ${normalizedId}: tmux=${sessionExistsSync(normalizedId)} stateStatus=${state?.status ?? 'none'}`);\n saveAgentRuntimeState(normalizedId, {\n state: 'stopped',\n lastActivity: new Date().toISOString(),\n });\n}\n\nexport const stopAgent = (agentId: string): Effect.Effect<void, FsError | TmuxError> => {\n const normalizedId = normalizeAgentId(agentId);\n\n return Effect.gen(function* () {\n if (yield* sessionExists(normalizedId)) {\n yield* Effect.gen(function* () {\n const output = yield* capturePane(normalizedId, 5000);\n if (!output) return;\n\n const agentDir = getAgentDir(normalizedId);\n const outputFile = join(agentDir, 'output.log');\n yield* Effect.tryPromise({\n try: () => mkdirAsync(agentDir, { recursive: true }),\n catch: (cause) => new FsError({ operation: 'mkdir', path: agentDir, cause }),\n });\n yield* Effect.tryPromise({\n try: () => writeFileAsync(outputFile, output),\n catch: (cause) => new FsError({ operation: 'write', path: outputFile, cause }),\n });\n }).pipe(Effect.catch(() => Effect.void));\n\n yield* killSession(normalizedId);\n }\n\n // PAN-1527: same orphan-launcher kill as stopAgentSync. Runs after\n // killSession so tmux gets the first chance to take everything down\n // cleanly; falls through and kills any survivor by command-line match.\n yield* Effect.tryPromise({\n try: () => killLauncherProcessAsync(normalizedId),\n catch: (cause): never => { throw cause; },\n }).pipe(Effect.catch(() => Effect.void));\n\n const state = yield* getAgentState(normalizedId);\n if (state) {\n if (!state.id) state.id = normalizedId;\n\n markAgentStoppedState(state);\n yield* saveAgentState(state);\n }\n\n const tmuxActive = yield* sessionExists(normalizedId);\n console.log(`[agents] Stopping ${normalizedId} (async): tmux=${tmuxActive} stateStatus=${state?.status ?? 'none'}`);\n yield* Effect.forkDetach(emitAgentEvent(normalizedId, {\n kind: 'activity',\n activity: 'stopped',\n }));\n });\n};\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';\nimport { mkdir, writeFile, writeFile as writeFileAsync } from 'fs/promises';\nimport { exec } from 'child_process';\nimport { promisify } from 'util';\nimport { randomUUID } from 'crypto';\nimport { homedir } from 'os';\nimport { dirname, join, resolve } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync, emitActivityTtsSync } from '../activity-logger.js';\nimport { assertIssueHasBeads, BeadsMissingError } from '../beads-query.js';\nimport { BdTransientFailure } from '../bd-process-lock.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { isTldrEnabledSync } from '../config-yaml.js';\nimport { createConversation, getConversationByName, reactivateConversationForSpawn } from '../overdeck/conversations.js';\nimport { startWorkSync } from '../cv.js';\nimport { generateFixedPointPromptSync, checkHookSync, initHookSync } from '../hooks.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { getProviderForModelSync, setupCredentialFileAuthSync, clearCredentialFileAuthSync } from '../providers.js';\nimport { resetPipelineVerdictsForWorkStartSync } from '../review-status.js';\nimport { resolveHarness } from '../harness-resolve.js';\nimport type { ModelId } from '../settings.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { writeBridgeTokenSync } from '../bridge-token.js';\nimport { createSession, exactPaneTarget, sessionExists, setOption } from '../tmux.js';\nimport { createActiveSlice } from '../vbrief/dag.js';\nimport { readWorkspacePlanSync } from '../vbrief/io.js';\nimport {\n getAgentDir,\n markAgentRunning,\n recordStartupSessionExit,\n saveAgentState,\n saveAgentStateSync,\n SESSION_EXITED_BEFORE_KICKOFF,\n type AgentState,\n type Role,\n} from './agent-state.js';\nimport { saveAgentRuntimeState } from './runtime-state.js';\nimport { clearReadySignal } from './identity.js';\nimport { deliverAgentMessage, deliverInitialPromptWithRetry } from './delivery.js';\nimport { determineModel, getProviderEnvForModel, getProviderExportsForModel } from './provider-env.js';\nimport {\n claudeSystemPromptFiles,\n getCodexLauncherFields,\n getOhmypiLauncherFields,\n getProviderAuthMode,\n getRoleRuntimeBaseCommand,\n waitForPromptReady,\n writeLauncherScriptAtomic,\n writeOhmypiAgentPrompt,\n} from './runtime-command.js';\nimport {\n buildAgentLaunchConfig,\n defaultRunWorkspace,\n flywheelEnvExports,\n resolveRegisteredSlotSpawn,\n resolveSlotTierSpawnParams,\n resolveSingleWorkTierSpawnParams,\n resolveFlywheelSpawnEnv,\n runAgentId,\n transitionIssueToInProgress,\n withSpawnTimeMemoryContext,\n assertWorkspaceStackHealthyForSpawn,\n type RegisteredSlotSpawn,\n type SpawnOptions,\n type SpawnRunOptions,\n} from './spawn-prep.js';\nimport { getConcurrencyLimits } from '../cloister/concurrency.js';\nimport { listAgentStates } from './queries.js';\nimport { findProjectByPathSync } from '../projects.js';\nimport { isStateMigrated } from '../state-home.js';\nimport { shouldCommitLegacyWorkspaceArtifacts } from '../state-read-home.js';\nimport {\n decideChannelsForWorkAgent,\n dismissDevChannelsDialog,\n prepareSupervisorForFreshLaunch,\n recordKickoffDeliveryFailure,\n writeChannelsBridgeMcpConfig,\n} from './supervisor-channels.js';\nimport { stopAgent } from './termination.js';\n\nconst execAsync = promisify(exec);\n\nexport async function spawnRun(issueId: string, role: Role, options: SpawnRunOptions = {}): Promise<AgentState> {\n const workspace = options.workspace ?? defaultRunWorkspace(issueId);\n const modelSpawnKey = `${role}:${issueId}`;\n const selectedModel = determineModel({ model: options.model, role, spawnKey: modelSpawnKey });\n\n if (role === 'work') {\n const slot = resolveRegisteredSlotSpawn(issueId, workspace, options);\n // Tiered execution (PAN-1791): when enabled, the slot item's difficulty\n // selects the worker — the resolved tier's model+harness replace the\n // parent default in the spawn params. Disabled → both stay as resolved\n // above, unchanged.\n let slotModel = selectedModel;\n let slotHarness = options.harness;\n if (slot) {\n assertRegisteredSlotCap(issueId, options.maxRegisteredSlots);\n const tierParams = resolveSlotTierSpawnParams(workspace, slot.slotItemId, options.model, modelSpawnKey);\n if (tierParams.model) {\n slotModel = determineModel({ model: tierParams.model, role, spawnKey: modelSpawnKey });\n // Implicit staffing (PAN-2397) omits harness — keep the parent's\n // historical harness handling in that case.\n slotHarness = tierParams.harness ?? options.harness;\n }\n await ensureRegisteredSlotWorktree(workspace, slot);\n }\n const prompt = slot\n ? buildRegisteredSlotPrompt(issueId, workspace, slot, options.prompt)\n : options.prompt;\n return spawnAgent({\n issueId,\n workspace: slot?.workspace ?? workspace,\n agentId: slot?.agentId,\n harness: slotHarness,\n model: slot ? slotModel : options.model,\n prompt,\n role: 'work',\n allowHost: options.allowHost,\n flywheelRunId: options.flywheelRunId,\n effort: options.effort,\n slotIndex: slot?.slotIndex,\n slotItemId: slot?.slotItemId,\n });\n }\n\n const flywheelEnv = resolveFlywheelSpawnEnv(role, options.flywheelRunId);\n\n const agentId = options.agentId ?? runAgentId(issueId, role, options.subRole);\n if (await Effect.runPromise(sessionExists(agentId))) {\n // PAN-2579 (warm-by-default lifecycle): advancing-role sessions are no longer\n // reaped at verdict time, so a session alive at dispatch time may be a\n // warm-idle leftover from the PREVIOUS cycle rather than an active run. Reap\n // it here — at the moment its slot is actually needed — when that is provable\n // (its phase verdict is terminal, or its pane process has exited). A live\n // session with a non-terminal verdict is genuinely active: keep throwing so\n // a concurrent duplicate dispatch cannot stomp it. (Review dispatch reuses\n // its warm session with context via spawnReviewRoleForIssue's resume path\n // before ever reaching this guard; test/ship runs start fresh by design.)\n let reapWarmIdle = false;\n const advancing = role === 'review' || role === 'test' || role === 'ship';\n try {\n const { isPaneDead } = await import('../tmux.js');\n if (await Effect.runPromise(isPaneDead(agentId))) {\n reapWarmIdle = true;\n } else if (advancing) {\n const { getReviewStatusSync } = await import('../review-status.js');\n const { isRoleTerminal } = await import('../cloister/reap-terminal-sessions.js');\n const status = getReviewStatusSync(issueId);\n reapWarmIdle = !!status && isRoleTerminal(role as 'review' | 'test' | 'ship', status);\n }\n } catch { /* probe failure → conservative: treat as active */ }\n if (!reapWarmIdle) {\n throw new Error(`Role run ${agentId} already running. Use 'pan tell' to message it.`);\n }\n console.log(`[spawn] ${agentId} is warm-idle from the previous cycle — reaping it for the new ${role} dispatch (PAN-2579)`);\n const { killSession } = await import('../tmux.js');\n await Effect.runPromise(killSession(agentId)).catch(() => {});\n }\n\n await assertWorkspaceStackHealthyForSpawn(issueId, role, options.allowHost, workspace);\n\n initHookSync(agentId);\n\n const resolvedHarness: RuntimeName = await resolveHarness({\n explicit: options.harness,\n role,\n model: selectedModel,\n });\n\n if (\n getProviderForModelSync(selectedModel).name === 'openai'\n && (await getProviderAuthMode(selectedModel)) === 'subscription'\n ) {\n const { isCliproxyRunning } = await import('../cliproxy.js');\n if (!(await Effect.runPromise(isCliproxyRunning()))) {\n throw new Error(\n 'CLIProxyAPI sidecar is not running. GPT subscription role runs route through '\n + 'a local cliproxy process managed by `pan up`. Run `pan up` (or restart the '\n + 'dashboard) before spawning a GPT role run.',\n );\n }\n }\n\n const state: AgentState = {\n id: agentId,\n issueId,\n workspace,\n harness: resolvedHarness,\n role,\n model: selectedModel,\n modelSpawnKey,\n status: 'starting',\n startedAt: new Date().toISOString(),\n costSoFar: 0,\n hostOverride: options.allowHost || undefined,\n slotIndex: options.slotIndex,\n slotItemId: options.slotItemId,\n };\n // PAN-1048 P1: spawnRun is on the dashboard hot path (Effect routes,\n // reactive Cloister scheduler). All disk I/O here uses async fs/promises\n // so we never block the Node event loop.\n await Effect.runPromise(saveAgentState(state));\n\n const isSpecialistRole = role === 'review' || role === 'test' || role === 'ship' || role === 'knowledge';\n const shouldRegisterConversation = isSpecialistRole || options.registerConversation === true;\n // PAN-1557: convoy sub-reviewers are now interactive specialists — deliver\n // their prompt via tmux after Claude boots (same as the orchestrator/test/\n // ship), not on stdin to a headless `claude --print`.\n const shouldDeliverPromptViaTmux = shouldRegisterConversation && resolvedHarness === 'claude-code';\n const shouldDeliverPromptViaPi = shouldRegisterConversation && resolvedHarness === 'ohmypi';\n const shouldDeliverPromptViaCodexTui = shouldRegisterConversation && resolvedHarness === 'codex';\n const prompt = options.prompt\n ? await withSpawnTimeMemoryContext({\n prompt: options.prompt,\n issueId,\n workspace,\n agentId,\n role,\n harness: resolvedHarness,\n })\n : '';\n\n let promptFile: string | undefined;\n const tracksKickoffDelivery = role === 'flywheel';\n if (prompt && (tracksKickoffDelivery || (!shouldDeliverPromptViaTmux && !shouldDeliverPromptViaPi && !shouldDeliverPromptViaCodexTui))) {\n promptFile = join(getAgentDir(agentId), 'initial-prompt.md');\n await writeFileAsync(promptFile, prompt);\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = false;\n await Effect.runPromise(saveAgentState(state));\n }\n }\n\n checkAndSetupHooks();\n\n const provider = getProviderForModelSync(selectedModel as ModelId);\n if (provider.authType === 'credential-file') {\n setupCredentialFileAuthSync(provider, workspace);\n } else {\n clearCredentialFileAuthSync(workspace);\n }\n\n const providerExports = await getProviderExportsForModel(selectedModel);\n const providerEnv = await getProviderEnvForModel(selectedModel);\n\n // PAN-1048 review feedback 005 (S1): when the resolved harness is ohmypi, thread\n // the per-agent ohmypi launcher fields (--session-dir, --extension, FIFO\n // redirect) through generateLauncherScript so the role launcher emits the\n // correct `omp --mode rpc` command instead of a malformed Claude command.\n // Without this, a config'd `roles.review.harness: ohmypi` produced a launcher\n // that silently fell back to Claude shape.\n const piLauncherFields = resolvedHarness === 'ohmypi'\n ? await getOhmypiLauncherFields(agentId, selectedModel)\n : {};\n const codexLauncherFields = resolvedHarness === 'codex'\n ? getCodexLauncherFields(agentId, selectedModel, workspace)\n : {};\n\n // Create a conversation record for every specialist role — sub-role reviewers,\n // the review orchestrator/synthesizer, test, and ship. The row is the index\n // the dashboard reads to (a) locate the JSONL via claude_session_id, (b) carry\n // pre-JSONL state (spawn_error, fork_status), and (c) let the\n // conversation-lifecycle service compute sessionAlive from real tmux liveness\n // instead of from the agent state machine's status field, which can lag.\n // Excluding the orchestrator here previously forced AgentOutputPanel to\n // synthesize a Conversation whose sessionAlive came from `agent.status`, and\n // stale snapshots made active synthesizers render as \"Starting…\".\n let sessionId: string | undefined;\n let rawSessionId: string | undefined;\n if (shouldRegisterConversation) {\n // When resuming, reuse the prior JSONL session so `claude --resume` reloads conversation history.\n // When starting fresh, generate a new UUID and use `claude --session-id`.\n rawSessionId = options.resumeSessionId ?? randomUUID();\n\n // Persist the session ID to <agentDir>/session.id so resolveClaudeSessionId can locate the\n // JSONL after the specialist exits. Works for both fresh (--session-id) and resumed (--resume).\n try {\n const agentDir = getAgentDir(agentId);\n await mkdir(agentDir, { recursive: true });\n await writeFile(join(agentDir, 'session.id'), rawSessionId, 'utf-8');\n } catch (err) {\n console.warn(`[spawnRun] Failed to persist session.id for ${agentId}:`, err instanceof Error ? err.message : String(err));\n }\n\n try {\n const conversation = {\n name: agentId,\n tmuxSession: agentId,\n cwd: workspace,\n issueId,\n claudeSessionId: rawSessionId,\n model: selectedModel,\n harness: resolvedHarness,\n };\n if (getConversationByName(agentId)) {\n reactivateConversationForSpawn(conversation);\n } else {\n createConversation(conversation);\n }\n } catch (err) {\n // Non-fatal: the specialist still runs, but without a conversation record\n console.warn(`[spawnRun] Failed to register conversation for ${agentId}:`, err instanceof Error ? err.message : String(err));\n }\n\n // Only set sessionId (→ --session-id flag) for fresh spawns.\n // Resumes pass resumeSessionId (→ --resume flag) to the launcher instead.\n if (!options.resumeSessionId) {\n sessionId = rawSessionId;\n }\n }\n\n // PAN-1557: convoy reviewers are interactive now, so the launcher no longer\n // owns the REVIEWER_READY/FAILED signal (which previously rode a `claude\n // --print` process exit). The Stop-hook delivers REVIEWER_READY to the\n // synthesis agent when the reviewer finishes its turn with a written report;\n // Deacon's REVIEWER_TIMEOUT remains the failure failsafe. We still persist\n // the synthesis/output wiring on state.json so the Stop-hook can read it.\n if (options.reviewSynthesisAgentId) state.reviewSynthesisAgentId = options.reviewSynthesisAgentId;\n if (options.reviewOutputPath) state.reviewOutputPath = options.reviewOutputPath;\n\n const extraEnvExports = [...flywheelEnvExports(flywheelEnv), ...(options.extraEnvExports ?? [])];\n if (role === 'knowledge' && !extraEnvExports.includes('export PATH=\"$HOME/.overdeck/bin:$PATH\"')) {\n extraEnvExports.push('export PATH=\"$HOME/.overdeck/bin:$PATH\"');\n }\n\n const launcherContent = generateLauncherScriptSync({\n role,\n workingDir: workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports,\n promptFile: shouldDeliverPromptViaTmux ? undefined : promptFile,\n promptFileMode: undefined,\n overdeckEnv: { agentId, issueId, sessionType: options.subRole ? `${role}.${options.subRole}` : role },\n extraEnvExports,\n baseCommand: await getRoleRuntimeBaseCommand(selectedModel, agentId, role, resolvedHarness, options.subRole, options.effort),\n appendSystemPromptFiles: await claudeSystemPromptFiles(workspace, resolvedHarness),\n sessionId,\n resumeSessionId: options.resumeSessionId,\n reviewSignal: undefined,\n trapHup: undefined,\n ...piLauncherFields,\n ...codexLauncherFields,\n });\n\n const launcherScript = join(getAgentDir(agentId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n const claudeCmd = `bash ${launcherScript}`;\n console.log(`[claude-invoke] purpose=role-run | role=${role} | model=${state.model} | source=agents.ts:spawnRun | session=${agentId} | command=\"${claudeCmd}\"`);\n\n try {\n const { preTrustDirectory } = await import('../workspace-manager.js') as { preTrustDirectory: (dir: string) => void };\n preTrustDirectory(workspace);\n } catch { /* non-fatal */ }\n\n // PAN-1594: clear any stale ready.json before launch so waitForReadySignal()\n // only observes the session-start signal from THIS launch.\n clearReadySignal(agentId);\n\n await Effect.runPromise(createSession(agentId, workspace, claudeCmd, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n TERM: 'xterm-256color',\n OVERDECK_AGENT_ID: agentId,\n OVERDECK_ISSUE_ID: issueId,\n OVERDECK_SESSION_TYPE: role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n GIT_SEQUENCE_EDITOR: 'false',\n ...flywheelEnv,\n ...providerEnv,\n },\n }));\n if (shouldRegisterConversation) {\n await saveAgentRuntimeState(agentId, {\n claudeSessionId: rawSessionId,\n ...(options.resumeSessionId ? {} : {\n sessionModel: selectedModel,\n sessionHarness: resolvedHarness,\n }),\n });\n }\n await Effect.runPromise(setOption(agentId, 'destroy-unattached', 'off'));\n await Effect.runPromise(setOption(exactPaneTarget(agentId), 'remain-on-exit', 'on'));\n\n if (prompt) {\n if (shouldDeliverPromptViaPi) {\n try {\n await writeOhmypiAgentPrompt(agentId, prompt);\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = true;\n await Effect.runPromise(saveAgentState(state));\n }\n } catch (err) {\n console.error(`[${agentId}] ohmypi prompt delivery failed:`, err instanceof Error ? err.message : String(err));\n }\n } else if (shouldDeliverPromptViaTmux || shouldDeliverPromptViaCodexTui) {\n if (tracksKickoffDelivery) {\n const delivery = await deliverInitialPromptWithRetry(agentId, prompt, 'spawnRun:initial-prompt');\n if (delivery.ok) {\n state.kickoffDelivered = true;\n await Effect.runPromise(saveAgentState(state));\n } else if (delivery.failure === SESSION_EXITED_BEFORE_KICKOFF) {\n await recordStartupSessionExit(state, issueId, role);\n return state;\n }\n } else {\n // PAN-1594: wait for the hook-written ready.json (session-start hook),\n // not a tmux pane-scrape. No dependency on permission-mode footer text.\n const ready = await waitForPromptReady(agentId, resolvedHarness, 30);\n if (ready) {\n await new Promise<void>((resolve) => setTimeout(resolve, 500));\n await deliverAgentMessage(agentId, prompt, 'spawnRun:initial-prompt');\n } else {\n console.error(`[${agentId}] ${resolvedHarness === 'codex' ? 'Codex' : 'Claude'} did not become ready within 30s`);\n }\n }\n }\n }\n\n markAgentRunning(state);\n\n // Stamp the workspace HEAD this role run was launched against. The reactive\n // scheduler uses this to tell a still-relevant run from a zombie session\n // left behind by an agent that finished work but never exited (the ship/test\n // stall class of bug). A non-fatal git probe — if it fails the marker is\n // simply absent and activeRoleRunExists falls back to status-only checks.\n try {\n const { stdout } = await execAsync('git rev-parse --short=8 HEAD', { cwd: workspace });\n const head = stdout.trim();\n if (head) state.roleRunHead = head;\n } catch { /* non-fatal — marker stays absent */ }\n\n await Effect.runPromise(saveAgentState(state));\n\n // PAN-1556: the review role emits a single dedicated \"Review role spawned\"\n // event from spawnReviewRoleForIssue. Suppress the generic per-spawn\n // \"role started\" for review so the orchestrator + 4 convoy sub-reviewers\n // don't each spam the session feed and bury conversations.\n if (role !== 'review') {\n emitActivityEntrySync({\n source: role,\n level: 'info',\n message: `${role} role started for ${issueId}`,\n issueId,\n });\n }\n\n return state;\n}\n\nexport async function spawnAgent(options: SpawnOptions): Promise<AgentState> {\n const role: 'work' | 'strike' | 'knowledge' = options.role ?? 'work';\n const sessionPrefix = role === 'strike' ? 'strike' : 'agent';\n const agentId = options.agentId ?? `${sessionPrefix}-${options.issueId.toLowerCase()}`;\n\n // Check if already running (scoped to the exact session name, including slot suffix)\n if (await Effect.runPromise(sessionExists(agentId))) {\n throw new Error(`Agent ${agentId} already running. Use 'pan tell' to message it.`);\n }\n\n await assertWorkspaceStackHealthyForSpawn(options.issueId, role, options.allowHost, options.workspace);\n\n // Initialize hook for this agent (FPP support)\n initHookSync(agentId);\n\n // Strike agents bypass the normal pipeline (no plan/beads/review/test) —\n // see roles/strike.md. The beads gate is the only thing we skip; everything\n // else (workspace health, supervisor wiring, launcher) is identical.\n if (role !== 'strike' && role !== 'knowledge' && options.slotItemId === undefined) {\n // Use a short lock timeout when spawning from HTTP handlers so dashboard\n // requests fail fast to the JSONL fallback instead of blocking behind CLI\n // processes that hold the cross-process bd lock. The CLI `pan start` path\n // already performs a long-timeout live query before reaching spawnAgent.\n try {\n await Effect.runPromise(\n assertIssueHasBeads(options.workspace, options.issueId, { acquisitionTimeoutMs: 500 }),\n );\n } catch (error) {\n if (error instanceof BeadsMissingError && error.transientFailure !== undefined) {\n const attempts = error.transientFailure instanceof BdTransientFailure\n ? ` after ${error.transientFailure.attempts} attempts`\n : '';\n throw new Error(\n `Beads database was temporarily locked while checking ${options.issueId}${attempts}; re-run shortly.`\n );\n }\n throw error;\n }\n }\n\n // Determine model based on role configuration\n const modelSpawnKey = `${role}:${options.issueId}`;\n const singleTierParams = role === 'work' && options.slotItemId === undefined && options.slotIndex === undefined\n ? resolveSingleWorkTierSpawnParams(options.workspace, options.model, modelSpawnKey)\n : {};\n const selectedModel = determineModel({ model: singleTierParams.model ?? options.model, role, spawnKey: modelSpawnKey });\n console.log(`[DEBUG] Selected model: ${selectedModel}`);\n\n // When routing a GPT agent through ChatGPT subscription auth, the local\n // CLIProxyAPI sidecar MUST already be running. We only check — never\n // install/start from here, because spawnAgent is reachable from dashboard\n // route handlers where blocking on curl/tar would freeze the event loop\n // (see PAN-70 / PAN-446 — no blocking I/O in server code).\n if (\n getProviderForModelSync(selectedModel).name === 'openai'\n && (await getProviderAuthMode(selectedModel)) === 'subscription'\n ) {\n const { isCliproxyRunning } = await import('../cliproxy.js');\n if (!(await Effect.runPromise(isCliproxyRunning()))) {\n throw new Error(\n 'CLIProxyAPI sidecar is not running. GPT subscription agents route through '\n + 'a local cliproxy process managed by `pan up`. Run `pan up` (or restart the '\n + 'dashboard) before spawning a GPT agent.',\n );\n }\n }\n\n const resolvedHarness: RuntimeName = await resolveHarness({\n explicit: options.harness ?? singleTierParams.harness,\n role,\n model: selectedModel,\n });\n\n // Create state\n const state: AgentState = {\n id: agentId,\n issueId: options.issueId,\n workspace: options.workspace,\n harness: resolvedHarness,\n role,\n model: selectedModel,\n modelSpawnKey,\n status: 'starting',\n startedAt: new Date().toISOString(),\n costSoFar: 0,\n hostOverride: options.allowHost || undefined,\n };\n\n const supervisorLaunch = await prepareSupervisorForFreshLaunch(agentId, options, state);\n\n saveAgentStateSync(state);\n\n // Transition issue tracker to \"in progress\" immediately so Linear reflects reality\n // while workspace setup continues. Best-effort, don't block agent spawn.\n // Only for work agents, not planning/specialist agents.\n if (role === 'work') {\n try {\n const resetStatus = resetPipelineVerdictsForWorkStartSync(options.issueId);\n if (resetStatus) {\n const { resetPostMergeState } = await import('../cloister/merge-agent.js');\n resetPostMergeState(options.issueId);\n }\n } catch (err) {\n console.warn(`[agents] Could not reset stale pipeline verdicts for ${options.issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n transitionIssueToInProgress(options.issueId, options.workspace).catch((err) => {\n console.warn(`[agents] Could not transition ${options.issueId} to in_progress: ${err.message}`);\n });\n }\n\n // For child stories: synthesize feature context from parent feature plan\n // before the agent starts so readFeatureContext has O(1) local access.\n if (role === 'work') {\n try {\n const { writeStoryFeatureContext } = await import('../cloister/work-agent-prompt.js');\n await writeStoryFeatureContext(options.workspace, options.issueId);\n } catch (ctxErr: any) {\n console.warn(`[agents] Could not write story feature context for ${options.issueId}: ${ctxErr.message}`);\n }\n }\n\n // PAN-1215: One-shot cleanup of tracked workspace-only .pan/ artifacts.\n // These files are gitignored but may still be tracked on older branches.\n // If tracked, checkpoint commits and rebases can drop them, breaking the\n // verification gate. Remove them from the index when the workspace is clean.\n if (role === 'work') {\n try {\n const workspace = options.workspace;\n const project = findProjectByPathSync(workspace);\n if (project && !shouldCommitLegacyWorkspaceArtifacts(await isStateMigrated(project))) {\n console.warn(`[agents] Deferred legacy .pan/ index cleanup for ${options.issueId} — migrated projects use gitignored .overdeck/ runtime files; historical entries retire with the branch`);\n } else {\n const { stdout: trackedFiles } = await execAsync(\n 'git ls-files .pan/continue.json .pan/spec.vbrief.json',\n { cwd: workspace },\n );\n if (trackedFiles.trim()) {\n const { stdout: porcelain } = await execAsync(\n 'git status --porcelain -- .pan/',\n { cwd: workspace },\n );\n if (!porcelain.trim()) {\n await execAsync(\n 'git rm --cached --ignore-unmatch .pan/continue.json .pan/spec.vbrief.json',\n { cwd: workspace },\n );\n await execAsync(\n 'git commit -m \"chore: untrack workspace .pan/ artifacts (PAN-1215)\"',\n { cwd: workspace },\n );\n console.log(`[agents] Untracked workspace .pan/ artifacts for ${options.issueId}`);\n } else {\n console.warn(`[agents] Skipping .pan/ untrack for ${options.issueId} — .pan/ paths have uncommitted changes`);\n }\n }\n }\n } catch (err: any) {\n console.warn(`[agents] .pan/ untrack cleanup failed for ${options.issueId}: ${err.message}`);\n }\n }\n\n // Build prompt with FPP work if available\n let prompt = options.prompt || '';\n\n // FPP: Check for pending work on hook\n const { hasWork } = checkHookSync(agentId);\n if (hasWork) {\n const fixedPointPrompt = generateFixedPointPromptSync(agentId);\n if (fixedPointPrompt) {\n prompt = fixedPointPrompt + '\\n\\n---\\n\\n' + prompt;\n }\n }\n\n if (prompt) {\n prompt = await withSpawnTimeMemoryContext({\n prompt,\n issueId: options.issueId,\n workspace: options.workspace,\n agentId,\n role,\n harness: resolvedHarness,\n });\n }\n\n // Write prompt to file for complex prompts (avoids shell escaping issues)\n const promptFile = join(getAgentDir(agentId), 'initial-prompt.md');\n const tracksKickoffDelivery = role === 'work' || role === 'strike';\n if (prompt) {\n await writeFileAsync(promptFile, prompt);\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = false;\n saveAgentStateSync(state);\n }\n }\n\n // Auto-setup hooks if not configured\n checkAndSetupHooks();\n\n // Ensure TLDR daemon is running for the workspace (non-blocking, non-fatal).\n // Gated by the operator TLDR toggle: when disabled, the daemon is not started\n // and the agent (whose prompt reports TLDR_AVAILABLE=false) degrades to direct\n // file reads.\n try {\n const venvPath = join(options.workspace, '.venv');\n if (isTldrEnabledSync() && existsSync(venvPath)) {\n const { getTldrDaemonServiceSync } = await import('../tldr-daemon.js');\n const tldrService = getTldrDaemonServiceSync(options.workspace, venvPath);\n const status = await tldrService.getStatus();\n if (!status.running) {\n await tldrService.start(true);\n console.log(`[${agentId}] Started TLDR daemon for workspace`);\n }\n }\n } catch {\n // Non-fatal — agents degrade to direct file reads if TLDR unavailable\n }\n\n // Write initial task cache for heartbeat hook\n writeTaskCache(agentId, options.issueId);\n\n // Clear ready signal before spawning (clean slate for PAN-87 fix)\n clearReadySignal(agentId);\n\n // Channels MCP gate: only the explicit legacy override writes a per-agent\n // .mcp.json, bridge token, and channelsEnabled state for new spawns. The PTY\n // supervisor remains the default delivery transport.\n const channelsDecision = decideChannelsForWorkAgent(agentId, options, state);\n let channelsBridgeMcpConfig: string | undefined;\n if (channelsDecision.eligible) {\n channelsBridgeMcpConfig = join(options.workspace, '.pan', 'agent-mcp.json');\n writeBridgeTokenSync(agentId);\n await writeChannelsBridgeMcpConfig(channelsBridgeMcpConfig, agentId);\n state.channelsEnabled = true;\n saveAgentStateSync(state);\n }\n\n const flywheelEnv = resolveFlywheelSpawnEnv(role, options.flywheelRunId);\n const { launcherContent, providerEnv } = await buildAgentLaunchConfig({\n agentId,\n model: selectedModel,\n workspace: options.workspace,\n role,\n isPlanning: false,\n channelsBridgeMcpConfig,\n useSupervisor: supervisorLaunch.useSupervisor,\n supervisorScriptPath: supervisorLaunch.supervisorScriptPath,\n harness: state.harness ?? 'claude-code',\n extraEnvExports: flywheelEnvExports(flywheelEnv),\n effort: options.effort,\n });\n\n const launcherScript = join(getAgentDir(agentId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n const claudeCmd = `bash ${launcherScript}`;\n console.log(`[claude-invoke] purpose=work-agent | model=${state.model} | source=agents.ts:spawnAgent | session=${agentId} | command=\"${claudeCmd}\"`);\n\n // Pre-trust workspace directory in Claude Code to avoid the trust prompt\n try {\n const { preTrustDirectory } = await import('../workspace-manager.js') as { preTrustDirectory: (dir: string) => void };\n preTrustDirectory(options.workspace);\n } catch { /* non-fatal */ }\n\n // Configure workspace for GitHub App bot identity (PAN-536)\n // Agents push as panopticon-agent[bot] with short-lived installation tokens\n try {\n const { isGitHubAppConfigured, generateInstallationToken, configureWorkspaceForBot } = await import('../github-app.js');\n if (isGitHubAppConfigured()) {\n const { findProjectByPathSync } = await import('../projects.js');\n const project = findProjectByPathSync(resolve(options.workspace, '..', '..'));\n const ghRepo = project?.github_repo;\n if (ghRepo) {\n const [owner, repo] = ghRepo.split('/');\n const { token } = await Effect.runPromise(generateInstallationToken());\n await configureWorkspaceForBot(options.workspace, owner, repo, token);\n console.log(`[${agentId}] Configured workspace for bot push (panopticon-agent[bot])`);\n }\n }\n } catch (err: any) {\n console.warn(`[${agentId}] GitHub App config failed (falling back to SSH): ${err.message}`);\n }\n\n clearReadySignal(agentId);\n\n await Effect.runPromise(createSession(agentId, options.workspace, claudeCmd, {\n env: {\n ...BLANKED_PROVIDER_ENV, // Blank stale provider vars inherited by tmux server\n TERM: 'xterm-256color',\n OVERDECK_AGENT_ID: agentId,\n OVERDECK_ISSUE_ID: options.issueId,\n OVERDECK_SESSION_TYPE: role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false', // Disable suggested prompts for autonomous agents (PAN-251)\n GIT_SEQUENCE_EDITOR: 'false', // Block interactive rebase / squash (agents forbidden from rewriting history)\n ...flywheelEnv,\n ...providerEnv, // Set correct provider env vars (BASE_URL, AUTH_TOKEN, etc.)\n }\n }));\n await saveAgentRuntimeState(agentId, {\n sessionModel: selectedModel,\n sessionHarness: resolvedHarness,\n });\n\n // Channels: start dismissing the dev-channels confirmation dialog as soon as\n // the tmux session exists, but only block on completion when we are about to\n // deliver an initial prompt. Spawn-only callers should not sit in a 20s poll\n // loop waiting for a dialog they may never need.\n const dismissChannelsDialogPromise = channelsBridgeMcpConfig\n ? dismissDevChannelsDialog(agentId).catch(() => undefined)\n : null;\n\n // Send the initial prompt after the interactive prompt is ready.\n if (prompt && resolvedHarness === 'ohmypi') {\n try {\n await writeOhmypiAgentPrompt(agentId, prompt);\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = true;\n saveAgentStateSync(state);\n }\n } catch (err) {\n console.error(`[${agentId}] ohmypi prompt delivery failed:`, err instanceof Error ? err.message : String(err));\n if (tracksKickoffDelivery) {\n await recordKickoffDeliveryFailure(state, options.issueId, role);\n if (role === 'strike') {\n await Effect.runPromise(stopAgent(agentId));\n throw new Error(`Agent ${agentId} kickoff delivery failed: ${err instanceof Error ? err.message : String(err)}`);\n }\n return state;\n }\n }\n } else if (prompt) {\n if (dismissChannelsDialogPromise) {\n await dismissChannelsDialogPromise;\n }\n const delivery = await deliverInitialPromptWithRetry(agentId, prompt, 'spawnAgent:initial-prompt', state.deliveryMethod);\n if (delivery.ok) {\n if (tracksKickoffDelivery) {\n state.kickoffDelivered = true;\n saveAgentStateSync(state);\n }\n } else if (tracksKickoffDelivery) {\n if (delivery.failure === SESSION_EXITED_BEFORE_KICKOFF) {\n await recordStartupSessionExit(state, options.issueId, role);\n }\n await recordKickoffDeliveryFailure(state, options.issueId, role);\n if (role === 'strike') {\n await Effect.runPromise(stopAgent(agentId));\n throw new Error(`Agent ${agentId} kickoff delivery failed: ${delivery.failure ?? 'unknown error'}`);\n }\n return state;\n }\n }\n\n // For codex work agents, poll for the first rollout JSONL in the background\n // and persist the thread-id so transcript/cost lookups hit the fast path\n // (PAN-1805). Non-blocking — codex writes its rollout only after the kickoff\n // prompt lands, so a blocking wait here would stall spawn. The latest-rollout\n // fallback covers sessions whose first turn lands after this window.\n if (resolvedHarness === 'codex') {\n const codexHomeForAgent = join(homedir(), '.overdeck', 'agents', agentId, 'codex-home');\n void (async () => {\n try {\n const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } =\n await import('../runtimes/codex.js');\n const rollout = await waitForCodexRollout(codexHomeForAgent, 120_000);\n if (rollout) {\n const threadId = extractThreadIdFromRollout(rollout);\n if (threadId) writeThreadId(agentId, threadId);\n }\n } catch { /* non-fatal — the latest-rollout fallback still resolves the transcript */ }\n })();\n }\n\n // Update status\n markAgentRunning(state);\n saveAgentStateSync(state);\n\n // Track work in CV\n startWorkSync(agentId, options.issueId);\n\n // Emit activity + TTS so the user knows an agent has started\n emitActivityEntrySync({\n source: role,\n level: 'info',\n message: `Work agent started for ${options.issueId}`,\n issueId: options.issueId,\n });\n emitActivityTtsSync({\n utterance: `Work agent started for ${options.issueId}`,\n priority: 2,\n issueId: options.issueId,\n source: 'work-agent',\n eventType: 'workAgent.started',\n });\n\n return state;\n}\n\nfunction assertRegisteredSlotCap(issueId: string, configuredCap?: number): void {\n const cap = configuredCap ?? getConcurrencyLimits().maxWorkAgents;\n if (!Number.isInteger(cap) || cap < 1) {\n throw new Error(`Registered slot cap must be a positive integer; got ${cap}.`);\n }\n\n const issueLower = issueId.toLowerCase();\n const slotAgentPattern = new RegExp(`^agent-${escapeRegExp(issueLower)}-slot-\\\\d+$`);\n const activeSlots = listAgentStates({ role: 'work' }).filter(agent =>\n slotAgentPattern.test(agent.id)\n && (agent.status === 'starting' || agent.status === 'running')\n );\n if (activeSlots.length >= cap) {\n throw new Error(\n `Registered slot cap reached for ${issueId}: ${activeSlots.length}/${cap} active slot agents.`\n );\n }\n}\n\nfunction buildRegisteredSlotPrompt(\n issueId: string,\n baseWorkspace: string,\n slot: RegisteredSlotSpawn,\n extraPrompt?: string,\n): string {\n const doc = readWorkspacePlanSync(baseWorkspace);\n if (!doc) {\n throw new Error(\n `Registered slot spawn for ${issueId} requires a readable vBRIEF plan in ${baseWorkspace}.`\n );\n }\n\n const slice = createActiveSlice(doc, {\n issueId: issueId.toUpperCase(),\n itemId: slot.slotItemId,\n currentItemIds: [slot.slotItemId],\n });\n\n const lines = [\n `# Registered Slot Assignment: ${slot.slotItemId}`,\n '',\n `Issue: ${issueId}`,\n `Slot: ${slot.slotIndex}`,\n `Agent: ${slot.agentId}`,\n `Branch: ${slot.branch}`,\n `Workspace: ${slot.workspace}`,\n '',\n 'You are a registered slot work agent. Implement only the target vBRIEF item below, keep changes scoped to that item, and do not merge this slot branch yourself.',\n '',\n slice.prompt,\n ];\n\n const trimmedExtra = extraPrompt?.trim();\n if (trimmedExtra) {\n lines.push('', '## Additional Foreman Instructions', trimmedExtra);\n }\n\n return lines.join('\\n');\n}\n\nasync function ensureRegisteredSlotWorktree(baseWorkspace: string, slot: RegisteredSlotSpawn): Promise<void> {\n if (existsSync(slot.workspace)) return;\n\n await mkdir(dirname(slot.workspace), { recursive: true });\n const branchExists = await gitBranchExists(baseWorkspace, slot.branch);\n const target = JSON.stringify(slot.workspace);\n const branch = JSON.stringify(slot.branch);\n const command = branchExists\n ? `git worktree add ${target} ${branch}`\n : `git worktree add -b ${branch} ${target} HEAD`;\n await execAsync(command, { cwd: baseWorkspace });\n}\n\nasync function gitBranchExists(workspace: string, branch: string): Promise<boolean> {\n try {\n await execAsync(`git show-ref --verify --quiet ${JSON.stringify(`refs/heads/${branch}`)}`, { cwd: workspace });\n return true;\n } catch {\n return false;\n }\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Check if Overdeck hooks are configured, and auto-setup if not\n */\nfunction checkAndSetupHooks(): void {\n const settingsPath = join(homedir(), '.claude', 'settings.json');\n const hookPath = join(homedir(), '.overdeck', 'bin', 'heartbeat-hook');\n\n // Check if settings.json exists and has heartbeat hook configured\n if (existsSync(settingsPath)) {\n try {\n const settingsContent = readFileSync(settingsPath, 'utf-8');\n const settings = JSON.parse(settingsContent);\n const postToolUse = settings?.hooks?.PostToolUse || [];\n\n const hookConfigured = postToolUse.some((hookConfig: any) =>\n hookConfig.hooks?.some((hook: any) =>\n hook.command === hookPath ||\n hook.command?.includes('overdeck') ||\n hook.command?.includes('heartbeat-hook')\n )\n );\n\n if (hookConfigured) {\n return; // Already configured\n }\n } catch {\n // Ignore errors, will attempt setup\n }\n }\n\n // Hooks not configured - run setup silently\n try {\n console.log('Configuring Overdeck heartbeat hooks...');\n // Note: This runs during spawn which is now async, so we can use execAsync\n // But this is called from a sync context in checkAndSetupHooks, so we use fire-and-forget\n exec('pan admin hooks install', (error: Error | null) => {\n if (error) {\n console.warn('⚠ Failed to auto-configure hooks. Run `pan admin hooks install` manually.');\n } else {\n console.log('✓ Heartbeat hooks configured');\n }\n });\n } catch (error) {\n console.warn('⚠ Failed to auto-configure hooks. Run `pan admin hooks install` manually.');\n }\n}\n\n/**\n * Write task cache for heartbeat hook to use\n */\nfunction writeTaskCache(agentId: string, issueId: string): void {\n const cacheDir = join(getAgentDir(agentId));\n mkdirSync(cacheDir, { recursive: true });\n\n const cacheFile = join(cacheDir, 'current-task.json');\n writeFileSync(\n cacheFile,\n JSON.stringify({\n id: issueId,\n title: `Working on ${issueId}`,\n updated_at: new Date().toISOString()\n }, null, 2)\n );\n}\n","/**\n * oh-my-pi (omp) JSONL parser (PAN-1989). Forked from pi-parser.ts (PAN-636).\n *\n * omp sessions live at:\n * ~/.omp/agent/sessions/<encoded-cwd>/<timestamp>_<session-id>.jsonl\n *\n * Format (omp v3 — same as Pi v3):\n * - First line: { type: \"session\", version: 3, id, timestamp, cwd }\n * - Subsequent: every entry has an opaque `id` and a `parentId`. parentId\n * is null for the first non-session entry.\n * - The graph is a tree (omp supports forking/branching), not a list.\n *\n * To compute usage for the *active* branch we walk leaf -> root via parentId\n * from the latest leaf (max timestamp among entries that have no children).\n * omp assistant messages report per-call usage — input/output tokens are NOT\n * cumulative across the conversation — so summing along the active branch is\n * correct without subtracting pre-compaction input.\n *\n * SessionUsage shape comes from jsonl-parser.ts (the Claude Code parser); we\n * map into it unchanged so all upstream consumers keep working.\n */\n\nimport { existsSync, readFileSync } from 'fs';\nimport { Effect } from 'effect';\nimport type { SessionUsage } from './jsonl-parser.js';\nimport { getPricingSync, type AIProvider, type TokenUsage } from '../cost.js';\nimport { FsError } from '../errors.js';\n\n// Minimal entry shape we care about. Anything else is ignored.\ninterface PiSessionRoot {\n type: 'session';\n version: number;\n id: string;\n timestamp: string;\n cwd?: string;\n}\n\ninterface PiUsage {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n totalTokens?: number;\n cost?: {\n input?: number;\n output?: number;\n cacheRead?: number;\n cacheWrite?: number;\n total?: number;\n };\n}\n\ninterface PiMessageEntry {\n type: 'message';\n id: string;\n parentId: string | null;\n timestamp: string;\n message: {\n role: 'user' | 'assistant' | 'toolResult';\n content?: unknown;\n model?: string;\n provider?: string;\n usage?: PiUsage;\n };\n}\n\nexport interface OhmypiCostEventUsage {\n requestId: string;\n timestamp: string;\n sessionId: string;\n sessionFile: string;\n provider: string | null;\n model: string;\n input: number;\n output: number;\n cacheRead: number;\n cacheWrite: number;\n cost: number;\n warnings?: Array<{ type: 'unpriced-model'; provider: string | null; model: string; reason: string }>;\n}\n\ninterface PiGenericEntry {\n type: string;\n id: string;\n parentId: string | null;\n timestamp: string;\n // any other fields permitted; we only inspect them when type is known.\n [key: string]: unknown;\n}\n\ntype PiEntry = PiMessageEntry | PiGenericEntry;\n\nfunction isMessage(entry: PiEntry): entry is PiMessageEntry {\n return entry.type === 'message';\n}\n\nfunction isAssistantMessage(entry: PiEntry): entry is PiMessageEntry {\n return isMessage(entry) && entry.message?.role === 'assistant';\n}\n\nfunction pricingProviderFor(provider: string | null, model: string): AIProvider | null {\n const lowerProvider = provider?.toLowerCase() ?? '';\n const lowerModel = model.toLowerCase();\n if (lowerProvider === 'openai' || lowerProvider === 'openai-codex' || lowerModel.includes('gpt')) return 'openai';\n if (lowerProvider === 'google' || lowerModel.includes('gemini')) return 'google';\n if (lowerProvider === 'anthropic' || lowerModel.includes('claude')) return 'anthropic';\n if (\n lowerProvider === 'kimi'\n || lowerProvider === 'minimax'\n || lowerProvider === 'zai'\n || lowerProvider === 'zhipu'\n || lowerProvider === 'custom'\n || lowerModel.includes('kimi')\n || lowerModel.includes('minimax')\n || lowerModel.includes('glm')\n ) {\n return 'custom';\n }\n return null;\n}\n\nfunction computeCostFromPricing(\n provider: string | null,\n model: string,\n usage: TokenUsage,\n): { cost: number; reason?: string } {\n const pricingProvider = pricingProviderFor(provider, model);\n if (!pricingProvider) return { cost: 0, reason: 'unknown-provider' };\n\n const pricing = getPricingSync(pricingProvider, model);\n if (!pricing) return { cost: 0, reason: 'unpriced-model' };\n\n return {\n cost:\n (usage.inputTokens / 1000) * pricing.inputPer1k\n + (usage.outputTokens / 1000) * pricing.outputPer1k\n + ((usage.cacheReadTokens ?? 0) / 1000) * (pricing.cacheReadPer1k ?? 0)\n + ((usage.cacheWriteTokens ?? 0) / 1000) * (pricing.cacheWrite5mPer1k ?? 0),\n };\n}\n\nfunction tsMs(timestamp: string | undefined): number {\n if (!timestamp) return 0;\n const t = Date.parse(timestamp);\n return Number.isFinite(t) ? t : 0;\n}\n\nexport interface ParseResult {\n ok: boolean;\n reason?: string;\n usage?: SessionUsage;\n usageEvents?: OhmypiCostEventUsage[];\n unpricedModels?: Array<{ provider: string | null; model: string; reason: string }>;\n}\n\n// Known top-level entry types. Anything else is logged once per session.\nconst KNOWN_TYPES: ReadonlySet<string> = new Set([\n 'session',\n 'message',\n 'model_change',\n 'thinking_level_change',\n 'compaction_start',\n 'compaction_end',\n 'session_before_compact',\n 'session_compact',\n 'session_before_switch',\n 'session_before_fork',\n 'session_start',\n]);\n\n/**\n * Parse a Pi session JSONL file into a SessionUsage record.\n *\n * Returns null if the file does not exist, is empty, or is unparseable. Errors\n * during line parsing are tolerated (the line is skipped); we never throw.\n */\nexport function parseOhmypiSessionSync(filePath: string): SessionUsage | null {\n if (!existsSync(filePath)) return null;\n const result = parseOhmypiSessionContent(readFileSync(filePath, 'utf8'), filePath);\n return result.ok ? result.usage! : null;\n}\n\nexport function parseOhmypiSessionCostEventsSync(filePath: string): OhmypiCostEventUsage[] {\n if (!existsSync(filePath)) return [];\n const result = parseOhmypiSessionContent(readFileSync(filePath, 'utf8'), filePath);\n return result.ok ? result.usageEvents ?? [] : [];\n}\n\nexport function parseOhmypiSessionCostResultSync(filePath: string): ParseResult {\n if (!existsSync(filePath)) return { ok: false, reason: 'unreadable' };\n try {\n return parseOhmypiSessionContent(readFileSync(filePath, 'utf8'), filePath);\n } catch {\n return { ok: false, reason: 'unreadable' };\n }\n}\n\n/**\n * Parse Pi session JSONL content. Exposed for tests and callers that already\n * have the file content in memory.\n */\nexport function parseOhmypiSessionContent(content: string, filePath = '<inline>'): ParseResult {\n const lines = content.split('\\n').filter(l => l.trim().length > 0);\n if (lines.length === 0) {\n return { ok: false, reason: 'empty' };\n }\n\n let root: PiSessionRoot | null = null;\n const entriesById = new Map<string, PiEntry>();\n const childrenOf = new Map<string | null, string[]>();\n const unknownTypesSeen = new Set<string>();\n\n for (const line of lines) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(line);\n } catch {\n continue;\n }\n if (!parsed || typeof parsed !== 'object') continue;\n const entry = parsed as PiEntry;\n\n if (entry.type === 'session') {\n root = entry as unknown as PiSessionRoot;\n continue;\n }\n\n if (!entry.id) continue;\n\n if (!KNOWN_TYPES.has(entry.type)) {\n if (!unknownTypesSeen.has(entry.type)) {\n unknownTypesSeen.add(entry.type);\n // eslint-disable-next-line no-console\n console.debug(`[ohmypi-parser] unknown entry type \"${entry.type}\" in ${filePath} — ignoring`);\n }\n // Still track the entry in the graph so downstream branches stay linked.\n }\n\n entriesById.set(entry.id, entry);\n const list = childrenOf.get(entry.parentId ?? null);\n if (list) list.push(entry.id);\n else childrenOf.set(entry.parentId ?? null, [entry.id]);\n }\n\n if (!root) {\n return { ok: false, reason: 'missing-session-root' };\n }\n\n if (entriesById.size === 0) {\n // Session with only the root entry — return a minimal SessionUsage.\n return {\n ok: true,\n usage: emptyUsage(filePath, root),\n };\n }\n\n // Find leaves: entries that have no children.\n const leaves: PiEntry[] = [];\n for (const entry of entriesById.values()) {\n const kids = childrenOf.get(entry.id);\n if (!kids || kids.length === 0) leaves.push(entry);\n }\n if (leaves.length === 0) {\n // Cycle or all entries have children — fall back to \"pick latest entry\".\n leaves.push(...entriesById.values());\n }\n\n // Pick the latest leaf by timestamp; tie-break by file order via id appearance.\n let latestLeaf: PiEntry = leaves[0]!;\n for (const leaf of leaves) {\n if (tsMs(leaf.timestamp) > tsMs(latestLeaf.timestamp)) {\n latestLeaf = leaf;\n }\n }\n\n // Walk leaf -> root via parentId, collecting entries in reverse-chronological order.\n const branch: PiEntry[] = [];\n const visited = new Set<string>();\n let cursor: PiEntry | undefined = latestLeaf;\n while (cursor) {\n if (visited.has(cursor.id)) break; // cycle guard\n visited.add(cursor.id);\n branch.push(cursor);\n if (!cursor.parentId) break;\n cursor = entriesById.get(cursor.parentId);\n }\n branch.reverse(); // chronological\n\n // Aggregate usage and per-model breakdown across assistant messages on the\n // active branch.\n const totals: TokenUsage = {\n inputTokens: 0,\n outputTokens: 0,\n cacheReadTokens: 0,\n cacheWriteTokens: 0,\n };\n let costV2 = 0;\n let messageCount = 0;\n let costRecomputedFromTokens = 0;\n const modelBreakdown: Record<\n string,\n { cost: number; inputTokens: number; outputTokens: number; messageCount: number; cacheReadTokens?: number; cacheWriteTokens?: number }\n > = {};\n const usageEvents: OhmypiCostEventUsage[] = [];\n const unpricedModels: Array<{ provider: string | null; model: string; reason: string }> = [];\n const modelsInOrder: string[] = [];\n let compactionCount = 0;\n\n let firstTs: string | null = null;\n let lastTs: string | null = null;\n\n for (const entry of branch) {\n if (entry.type === 'compaction_start' || entry.type === 'session_compact') {\n compactionCount += 1;\n }\n if (!firstTs && entry.timestamp) firstTs = entry.timestamp;\n if (entry.timestamp) lastTs = entry.timestamp;\n\n if (!isAssistantMessage(entry)) continue;\n const usage = entry.message.usage;\n if (!usage) continue;\n const model = entry.message.model || 'unknown';\n const provider = typeof entry.message.provider === 'string' ? entry.message.provider : null;\n const input = usage.input ?? 0;\n const output = usage.output ?? 0;\n const cacheRead = usage.cacheRead ?? 0;\n const cacheWrite = usage.cacheWrite ?? 0;\n const tokenUsage: TokenUsage = {\n inputTokens: input,\n outputTokens: output,\n cacheReadTokens: cacheRead,\n cacheWriteTokens: cacheWrite,\n };\n const hasInlineCost = usage.cost?.total !== undefined;\n let cost = usage.cost?.total ?? 0;\n let warning: OhmypiCostEventUsage['warnings'] | undefined;\n if (!hasInlineCost && input + output + cacheRead + cacheWrite > 0) {\n const computed = computeCostFromPricing(provider, model, tokenUsage);\n cost = computed.cost;\n if (computed.reason) {\n unpricedModels.push({ provider, model, reason: computed.reason });\n warning = [{ type: 'unpriced-model', provider, model, reason: computed.reason }];\n }\n }\n const localComputed = hasInlineCost\n ? (usage.cost?.input ?? 0) +\n (usage.cost?.output ?? 0) +\n (usage.cost?.cacheRead ?? 0) +\n (usage.cost?.cacheWrite ?? 0)\n : cost;\n\n totals.inputTokens += input;\n totals.outputTokens += output;\n totals.cacheReadTokens = (totals.cacheReadTokens ?? 0) + cacheRead;\n totals.cacheWriteTokens = (totals.cacheWriteTokens ?? 0) + cacheWrite;\n costV2 += cost;\n costRecomputedFromTokens += localComputed;\n messageCount += 1;\n\n if (!modelBreakdown[model]) {\n modelBreakdown[model] = { cost: 0, inputTokens: 0, outputTokens: 0, messageCount: 0, cacheReadTokens: 0, cacheWriteTokens: 0 };\n modelsInOrder.push(model);\n }\n const slot = modelBreakdown[model]!;\n slot.cost += cost;\n slot.inputTokens += input;\n slot.outputTokens += output;\n slot.messageCount += 1;\n slot.cacheReadTokens = (slot.cacheReadTokens ?? 0) + cacheRead;\n slot.cacheWriteTokens = (slot.cacheWriteTokens ?? 0) + cacheWrite;\n\n usageEvents.push({\n requestId: `ohmypi:${root.id}:${entry.id}`,\n timestamp: entry.timestamp || root.timestamp,\n sessionId: root.id,\n sessionFile: filePath,\n provider,\n model,\n input,\n output,\n cacheRead,\n cacheWrite,\n cost,\n ...(warning ? { warnings: warning } : {}),\n });\n }\n\n // Observability: log Pi-inline-cost vs locally-recomputed-from-cost-fields\n // delta. This catches cases where Pi's pricing tables drift inside Pi\n // itself; we always trust costV2 (Pi's inline total).\n if (Math.abs(costV2 - costRecomputedFromTokens) > 1e-9) {\n // eslint-disable-next-line no-console\n console.debug(\n `[ohmypi-parser] inline cost vs sum-of-line-items delta=${(costV2 - costRecomputedFromTokens).toFixed(6)} in ${filePath}`,\n );\n }\n if (compactionCount > 0) {\n // eslint-disable-next-line no-console\n console.debug(\n `[ohmypi-parser] active branch in ${filePath} has ${compactionCount} compaction event(s) — usage summed per-message (no double-count)`,\n );\n }\n\n const display =\n modelsInOrder.length === 0\n ? 'unknown'\n : modelsInOrder.length === 1\n ? modelsInOrder[0]!\n : modelsInOrder.join(' → ');\n\n return {\n ok: true,\n usage: {\n sessionId: root.id,\n sessionFile: filePath,\n startTime: firstTs ?? root.timestamp,\n endTime: lastTs ?? root.timestamp,\n model: display,\n usage: totals,\n cost: costV2,\n cost_v2: costV2,\n messageCount,\n modelBreakdown,\n },\n usageEvents,\n unpricedModels,\n };\n}\n\nfunction emptyUsage(filePath: string, root: PiSessionRoot): SessionUsage {\n return {\n sessionId: root.id,\n sessionFile: filePath,\n startTime: root.timestamp,\n endTime: root.timestamp,\n model: 'unknown',\n usage: { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0 },\n cost: 0,\n cost_v2: 0,\n messageCount: 0,\n modelBreakdown: {},\n };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/** Effect variant of parseOhmypiSession. */\nexport const parseOhmypiSession = (\n filePath: string,\n): Effect.Effect<SessionUsage | null, FsError> =>\n Effect.try({\n try: () => parseOhmypiSessionSync(filePath),\n catch: (cause) => new FsError({ path: filePath, operation: 'parseOhmypiSession', cause }),\n });\n\nexport const parseOhmypiSessionCostEvents = (\n filePath: string,\n): Effect.Effect<OhmypiCostEventUsage[], FsError> =>\n Effect.try({\n try: () => parseOhmypiSessionCostEventsSync(filePath),\n catch: (cause) => new FsError({ path: filePath, operation: 'parseOhmypiSessionCostEvents', cause }),\n });\n","/**\n * oh-my-pi (omp) coding agent runtime adapter (PAN-1989).\n *\n * Implements AgentRuntime for the `omp` binary running in --mode rpc. The\n * adapter wires together three pieces:\n *\n * - ohmypi-fifo.ts — per-agent rpc.in named pipe lifecycle\n * - launcher-generator — emits the `exec omp --mode rpc ... < <fifo>` script\n * - ohmypi-extension — the vendored extension that writes ready.json,\n * heartbeats, and /pan-done markers from inside omp\n *\n * Spawn flow:\n * 1. mkdir agent dir\n * 2. createOhmypiFifo\n * 3. write launcher.sh via generateLauncherScript({ harness: 'ohmypi', ... })\n * 4. tmux new-session running launcher.sh\n * 5. wait for ~/.overdeck/agents/<id>/ready.json (max 120s)\n *\n * Runtime divergences vs pi (omp contract AC-1b, AC-3):\n * - omp binary requires Bun >=1.3.14 (shebang #!/usr/bin/env bun).\n * - Resume flag: `--resume <id>` (NOT `--session <id>` which omp accepts but\n * silently ignores for resume — see AC-3 in docs/ohmypi-contract.md).\n * - `--no-context-files` is removed; drop it from the launcher command.\n */\n\nimport { existsSync, readFileSync, readdirSync, statSync, mkdirSync, writeFileSync, chmodSync, unlinkSync } from 'node:fs'\nimport { join } from 'node:path'\nimport { tmpdir } from 'node:os'\nimport { promisify } from 'node:util'\nimport { exec } from 'node:child_process'\nimport { randomUUID } from 'node:crypto'\nimport { Effect } from 'effect'\nimport type {\n AgentRuntime,\n AgentRuntimeSync,\n AgentRuntimeError,\n HarnessBehavior,\n Heartbeat,\n TokenUsage,\n CostBreakdown,\n Session,\n SpawnConfig,\n Agent,\n} from './types.js'\nimport { OHMYPI_BEHAVIOR } from './behavior.js'\nimport { sessionExists, killSession, createSession, listSessionsSync } from '../tmux.js'\nimport { parseOhmypiSessionSync } from '../cost-parsers/ohmypi-parser.js'\nimport { generateLauncherScriptSync } from '../launcher-generator.js'\nimport { createOhmypiFifo, destroyOhmypiFifoSync, writeOhmypiCommandSync, ohmypiFifoPaths, OhmypiNotReady } from './ohmypi-fifo.js'\nimport { ProcessSpawnError, ProcessTimeoutError, TmuxError } from '../errors.js'\nimport { getOverdeckHome } from '../paths.js'\n\nconst execAsync = promisify(exec)\n\nfunction shellQuote(value: string): string {\n return `'${value.replace(/'/g, `'\\\\''`)}'`\n}\n\nconst ACTIVE_HEARTBEAT_TTL_MS = 60_000\nconst SPAWN_READY_TIMEOUT_MS = 120_000\n\nfunction overdeckDir(): string {\n return getOverdeckHome()\n}\nfunction heartbeatsDir(): string {\n return join(overdeckDir(), 'heartbeats')\n}\nfunction agentsDir(): string {\n return join(overdeckDir(), 'agents')\n}\n\nexport class OhmypiSpawnTimeout extends Error {\n readonly code = 'OHMYPI_SPAWN_TIMEOUT' as const\n constructor(agentId: string) {\n super(`omp agent ${agentId} did not write ready.json within ${SPAWN_READY_TIMEOUT_MS}ms${describeSpawnFailure(agentId)}`)\n this.name = 'OhmypiSpawnTimeout'\n }\n}\n\nexport interface OhmypiSpawnConfig extends SpawnConfig {\n /** Absolute path to packages/ohmypi-extension/dist/index.js. */\n piExtensionPath: string\n /** Optional extra args appended to the `omp` command line. */\n extraPiArgs?: string\n}\n\nfunction agentDirFor(agentId: string): string {\n return join(agentsDir(), agentId)\n}\n\nfunction describeSpawnFailure(agentId: string): string {\n const outputPath = join(agentDirFor(agentId), 'output.log')\n if (!existsSync(outputPath)) return ''\n try {\n const tail = readFileSync(outputPath, 'utf8').slice(-1500).trim().split('\\n').slice(-8).join('\\n')\n return tail ? ` [output.log tail:\\n${tail}]` : ''\n } catch {\n return ''\n }\n}\n\nfunction ohmypiSessionDirFor(agentId: string): string {\n return join(agentDirFor(agentId), 'sessions')\n}\n\nfunction readyPathFor(agentId: string): string {\n return ohmypiFifoPaths(agentId).readyPath\n}\n\nfunction sessionIdPathFor(agentId: string): string {\n return join(agentDirFor(agentId), 'session.id')\n}\n\n/**\n * Read the persisted omp session id for resume.\n * Returns null when the file is absent or unreadable.\n */\nfunction readStoredSessionId(agentId: string): string | null {\n const path = sessionIdPathFor(agentId)\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf8').trim()\n return raw || null\n } catch {\n return null\n }\n}\n\n/**\n * Resolve the real omp session id from the freshest session JSONL (PAN-1988 parity).\n * Returns null when no prior session exists on disk.\n *\n * Exported (PAN-2098) so the generic recovery path in agents.ts\n * (`getLatestSessionIdSync`) can resume a crashed ohmypi agent — omp never\n * writes a `session.id` file, so without this fallback the deacon reports\n * \"no saved session id\" and can only respawn fresh, losing context.\n */\nexport function resolveLatestOhmypiSessionId(agentId: string): string | null {\n const root = ohmypiSessionDirFor(agentId)\n if (!existsSync(root)) return null\n const files: { path: string; mtime: number }[] = []\n walkJsonl(root, files)\n if (files.length === 0) return null\n files.sort((a, b) => b.mtime - a.mtime)\n const parsed = parseOhmypiSessionSync(files[0]!.path)\n return parsed?.sessionId ?? null\n}\n\nexport class OhmypiRuntimeSync implements AgentRuntimeSync {\n readonly name = 'ohmypi' as const\n\n getHarnessBehavior(): HarnessBehavior {\n return OHMYPI_BEHAVIOR\n }\n\n getSessionPath(agentId: string): string | null {\n const root = ohmypiSessionDirFor(agentId)\n if (!existsSync(root)) return null\n const files: { path: string; mtime: number }[] = []\n walkJsonl(root, files)\n if (files.length === 0) return null\n files.sort((a, b) => b.mtime - a.mtime)\n return files[0]!.path\n }\n\n getLastActivity(agentId: string): Date | null {\n const session = this.getSessionPath(agentId)\n if (!session) return null\n try {\n return statSync(session).mtime\n } catch {\n return null\n }\n }\n\n getHeartbeat(agentId: string): Heartbeat | null {\n // 1. Fresh active heartbeat (<60s old).\n const heartbeatPath = join(heartbeatsDir(), `${agentId}.json`)\n if (existsSync(heartbeatPath)) {\n try {\n const data = JSON.parse(readFileSync(heartbeatPath, 'utf-8')) as {\n timestamp?: string\n tool_name?: string\n last_action?: string\n pid?: number\n }\n if (data.timestamp) {\n const ts = new Date(data.timestamp)\n if (Date.now() - ts.getTime() < ACTIVE_HEARTBEAT_TTL_MS) {\n return {\n timestamp: ts,\n agentId,\n source: 'active-heartbeat',\n confidence: 'high',\n toolName: data.tool_name,\n lastAction: data.last_action,\n pid: data.pid,\n }\n }\n }\n } catch {\n // fall through\n }\n }\n\n // 2. JSONL mtime.\n const lastActivity = this.getLastActivity(agentId)\n if (lastActivity) {\n return {\n timestamp: lastActivity,\n agentId,\n source: 'jsonl',\n confidence: 'medium',\n }\n }\n\n // 3. tmux session creation time (best-effort).\n try {\n const sess = listSessionsSync().find(s => s.name === agentId)\n if (sess) {\n return {\n timestamp: sess.created,\n agentId,\n source: 'tmux',\n confidence: 'low',\n }\n }\n } catch {\n // ignore\n }\n return null\n }\n\n getTokenUsage(agentId: string): TokenUsage | null {\n const session = this.getSessionPath(agentId)\n if (!session) return null\n const parsed = parseOhmypiSessionSync(session)\n return parsed?.usage ?? null\n }\n\n getSessionCost(agentId: string): CostBreakdown | null {\n const session = this.getSessionPath(agentId)\n if (!session) return null\n const parsed = parseOhmypiSessionSync(session)\n if (!parsed) return null\n const totalCost = parsed.cost_v2 ?? parsed.cost ?? 0\n return {\n inputCost: 0,\n outputCost: 0,\n cacheReadCost: 0,\n cacheWriteCost: 0,\n totalCost,\n currency: 'USD',\n }\n }\n\n async sendMessage(agentId: string, message: string): Promise<void> {\n if (!existsSync(readyPathFor(agentId))) {\n throw new OhmypiNotReady(`omp agent ${agentId}: ready.json not present yet`)\n }\n // steer: omp delivers immediately when idle and queues mid-turn; a bare\n // prompt is rejected with AgentBusyError while a run is active.\n writeOhmypiCommandSync(agentId, { id: randomUUID(), type: 'prompt', message, streamingBehavior: 'steer' })\n }\n\n /**\n * Kill an omp agent via the documented escalation ladder.\n *\n * 1. Send {cmd:'abort'} via the RPC fifo (graceful).\n * 2. Wait up to 2s for the tmux session to disappear on its own.\n * 3. SIGTERM the omp process group via tmux send-signal.\n * 4. Wait up to 5s for the tmux session to disappear.\n * 5. tmux kill-session (effectively SIGKILL).\n * 6. Always unlink rpc.in, ready.json, heartbeat, and completed marker.\n * NEVER touch session JSONL files — per JSONL-is-sacred rule.\n */\n async killAgent(agentId: string): Promise<void> {\n // Step 1: graceful RPC abort.\n try {\n writeOhmypiCommandSync(agentId, { id: randomUUID(), type: 'abort' })\n } catch {\n // No reader / not ready — fall through to signal escalation.\n }\n\n // Step 2: poll for tmux exit up to 2s.\n if (await pollUntilSessionGone(agentId, 2_000)) {\n cleanupOhmypiTransientFiles(agentId)\n return\n }\n\n // Step 3: SIGTERM via tmux.\n try {\n await execAsync(`tmux -L overdeck send-keys -t ${agentId} C-c 2>/dev/null || true`)\n } catch {\n // ignore\n }\n try {\n const { stdout } = await execAsync(\n `tmux -L overdeck list-panes -t ${shellQuote(agentId)} -F '#{pane_pid}' 2>/dev/null`\n )\n const pid = stdout.trim()\n if (pid) {\n await execAsync(`kill -TERM -- -${pid} 2>/dev/null || kill -TERM ${pid} 2>/dev/null || true`)\n }\n } catch {\n // ignore\n }\n\n // Step 4: poll for tmux exit up to another 5s.\n if (await pollUntilSessionGone(agentId, 5_000)) {\n cleanupOhmypiTransientFiles(agentId)\n return\n }\n\n // Step 5: SIGKILL fallback via tmux kill-session.\n try {\n if (await Effect.runPromise(sessionExists(agentId))) {\n await Effect.runPromise(killSession(agentId))\n }\n } finally {\n cleanupOhmypiTransientFiles(agentId)\n }\n }\n\n async spawnAgent(config: SpawnConfig & { piExtensionPath?: string }): Promise<Agent> {\n const piExtensionPath = config.piExtensionPath\n if (!piExtensionPath) {\n throw new Error('OhmypiRuntime.spawnAgent requires piExtensionPath in config')\n }\n\n const agentId = config.agentId\n const dir = agentDirFor(agentId)\n const sessionDir = ohmypiSessionDirFor(agentId)\n const hadPriorSpawn = existsSync(sessionDir) &&\n readdirSync(sessionDir).some((f) => f.endsWith('.jsonl'))\n mkdirSync(dir, { recursive: true, mode: 0o700 })\n mkdirSync(sessionDir, { recursive: true, mode: 0o700 })\n\n const fifoPath = await Effect.runPromise(createOhmypiFifo(agentId))\n\n const promptFile = config.prompt ? writeAgentPromptFile(agentId, config.prompt) : undefined\n\n // Resume from the stored session.id when present, else recover from the\n // freshest session JSONL (PAN-1988 parity). omp uses --resume (not --session).\n const resumeSessionId = readStoredSessionId(agentId) ?? resolveLatestOhmypiSessionId(agentId) ?? undefined\n if (!resumeSessionId && hadPriorSpawn) {\n console.warn(\n `[ohmypi-runtime] ${agentId}: prior session.jsonl exists but no resumable session id found — spawning a fresh omp session`,\n )\n }\n\n const launcherScript = generateLauncherScriptSync({\n role: 'work',\n workingDir: config.workspace,\n harness: 'ohmypi',\n piExtensionPath,\n piFifoPath: fifoPath,\n piSessionDir: sessionDir,\n model: config.model,\n promptFile,\n resumeSessionId,\n overdeckEnv: { agentId },\n setTerminalEnv: true,\n trapHup: true,\n })\n\n const launcherPath = join(dir, 'ohmypi-launcher.sh')\n writeFileSync(launcherPath, launcherScript)\n chmodSync(launcherPath, 0o755)\n\n await Effect.runPromise(createSession(agentId, config.workspace, `bash ${launcherPath}`, {\n env: { OVERDECK_AGENT_ID: agentId },\n }))\n\n await waitForReady(agentId, SPAWN_READY_TIMEOUT_MS)\n\n const readyData = readReady(agentId)\n return {\n id: agentId,\n sessionId: readyData?.sessionId ?? 'unknown',\n runtime: 'ohmypi',\n model: config.model ?? 'unknown',\n workspace: config.workspace,\n startedAt: new Date(),\n }\n }\n\n listSessions(workspace?: string): Session[] {\n const sessions: Session[] = []\n const root = agentsDir()\n if (!existsSync(root)) return sessions\n for (const agentName of safeReaddir(root)) {\n const sessionRoot = ohmypiSessionDirFor(agentName)\n if (!existsSync(sessionRoot)) continue\n const files: { path: string; mtime: number }[] = []\n walkJsonl(sessionRoot, files)\n for (const file of files) {\n const parsed = parseOhmypiSessionSync(file.path)\n if (!parsed) continue\n sessions.push({\n id: parsed.sessionId,\n agentId: agentName,\n workspace: workspace ?? 'unknown',\n model: parsed.model,\n startedAt: new Date(parsed.startTime),\n lastActivity: new Date(file.mtime),\n tokenUsage: parsed.usage,\n })\n }\n }\n return sessions\n }\n\n async isRunning(agentId: string): Promise<boolean> {\n return await Effect.runPromise(sessionExists(agentId))\n }\n}\n\nexport function createOhmypiRuntimeSync(): OhmypiRuntimeSync {\n return new OhmypiRuntimeSync()\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\nexport class OhmypiRuntime implements AgentRuntime {\n readonly name = 'ohmypi' as const\n private readonly inner: OhmypiRuntimeSync\n\n constructor(inner: OhmypiRuntimeSync = new OhmypiRuntimeSync()) {\n this.inner = inner\n }\n\n getSessionPath(agentId: string): string | null {\n return this.inner.getSessionPath(agentId)\n }\n getHarnessBehavior(): HarnessBehavior {\n return this.inner.getHarnessBehavior()\n }\n getLastActivity(agentId: string): Date | null {\n return this.inner.getLastActivity(agentId)\n }\n getHeartbeat(agentId: string): Heartbeat | null {\n return this.inner.getHeartbeat(agentId)\n }\n getTokenUsage(agentId: string): TokenUsage | null {\n return this.inner.getTokenUsage(agentId)\n }\n getSessionCost(agentId: string): CostBreakdown | null {\n return this.inner.getSessionCost(agentId)\n }\n listSessions(workspace?: string): Session[] {\n return this.inner.listSessions(workspace)\n }\n\n sendMessage(agentId: string, message: string): Effect.Effect<void, AgentRuntimeError> {\n return Effect.tryPromise({\n try: () => Promise.resolve(this.inner.sendMessage(agentId, message)),\n catch: (cause) =>\n new TmuxError({\n command: 'rpc-write',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n }\n\n killAgent(agentId: string): Effect.Effect<void, AgentRuntimeError> {\n return Effect.tryPromise({\n try: () => this.inner.killAgent(agentId),\n catch: (cause) =>\n new TmuxError({\n command: 'kill-session',\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n })\n }\n\n spawnAgent(config: SpawnConfig): Effect.Effect<Agent, AgentRuntimeError> {\n return Effect.tryPromise({\n try: () => this.inner.spawnAgent(config),\n catch: (cause) => {\n if (cause instanceof OhmypiSpawnTimeout) {\n return new ProcessTimeoutError({\n command: 'omp',\n args: ['--mode', 'rpc'],\n timeoutMs: SPAWN_READY_TIMEOUT_MS,\n })\n }\n return new ProcessSpawnError({\n command: 'omp',\n args: ['--mode', 'rpc'],\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n })\n },\n })\n }\n\n isRunning(agentId: string): Effect.Effect<boolean> {\n return Effect.promise(() => this.inner.isRunning(agentId))\n }\n}\n\nexport function createOhmypiRuntime(): OhmypiRuntime {\n return new OhmypiRuntime()\n}\n\n// ────────────────────────────────────────────────────────────────────────\n// helpers\n// ────────────────────────────────────────────────────────────────────────\n\nasync function pollUntilSessionGone(agentId: string, timeoutMs: number): Promise<boolean> {\n const deadline = Date.now() + timeoutMs\n while (Date.now() < deadline) {\n if (!(await Effect.runPromise(sessionExists(agentId)))) return true\n await new Promise(r => setTimeout(r, 100))\n }\n return false\n}\n\n/**\n * Remove omp's transient per-agent files: rpc.in fifo, ready.json,\n * heartbeats/<id>.json, completed marker. Never touches session JSONLs\n * under <agentDir>/sessions/ — those are sacred (CLAUDE.md).\n */\nfunction cleanupOhmypiTransientFiles(agentId: string): void {\n try { destroyOhmypiFifoSync(agentId) } catch { /* ignore */ }\n const dir = agentDirFor(agentId)\n for (const transient of ['ready.json', 'completed']) {\n const path = join(dir, transient)\n if (existsSync(path)) {\n try { unlinkSync(path) } catch { /* ignore */ }\n }\n }\n const heartbeatPath = join(heartbeatsDir(), `${agentId}.json`)\n if (existsSync(heartbeatPath)) {\n try { unlinkSync(heartbeatPath) } catch { /* ignore */ }\n }\n // Sacred: NEVER touch <dir>/sessions/*.jsonl — JSONLs are conversation history.\n}\n\nfunction readReady(agentId: string): { sessionId?: string; reason?: string } | null {\n const path = readyPathFor(agentId)\n if (!existsSync(path)) return null\n try {\n return JSON.parse(readFileSync(path, 'utf-8'))\n } catch {\n return null\n }\n}\n\nasync function waitForReady(agentId: string, timeoutMs: number): Promise<void> {\n const deadline = Date.now() + timeoutMs\n const path = readyPathFor(agentId)\n while (Date.now() < deadline) {\n if (existsSync(path)) return\n await new Promise(r => setTimeout(r, 100))\n }\n throw new OhmypiSpawnTimeout(agentId)\n}\n\nfunction writeAgentPromptFile(agentId: string, prompt: string): string {\n const promptPath = join(agentDirFor(agentId), 'prompt.txt')\n writeFileSync(promptPath, prompt)\n return promptPath\n}\n\nfunction safeReaddir(dir: string): string[] {\n try {\n return readdirSync(dir)\n } catch {\n return []\n }\n}\n\nfunction walkJsonl(dir: string, out: { path: string; mtime: number }[]): void {\n let entries: string[]\n try {\n entries = readdirSync(dir)\n } catch {\n return\n }\n for (const entry of entries) {\n const full = join(dir, entry)\n let stat\n try {\n stat = statSync(full)\n } catch {\n continue\n }\n if (stat.isDirectory()) {\n walkJsonl(full, out)\n } else if (stat.isFile() && entry.endsWith('.jsonl')) {\n out.push({ path: full, mtime: stat.mtime.getTime() })\n }\n }\n}\n\n// Keep imports referenced even when only the types are used.\nvoid execAsync\nvoid tmpdir\n","import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync, statSync } from 'fs';\nimport { readFile } from 'fs/promises';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { Effect } from 'effect';\nimport {\n getAgentDir,\n getAgentStateSync,\n getAgentRuntimeStateSync,\n getAgentRuntimeState,\n} from '../agents.js';\nimport { encodeClaudeProjectDir } from '../paths.js';\nimport { findLatestRollout, extractThreadIdFromRollout } from '../runtimes/codex.js';\nimport { resolveLatestOhmypiSessionId } from '../runtimes/ohmypi.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport { FsError } from '../errors.js';\n\n/** Activity log entry (still written by heartbeat-hook as a forensic artifact). */\nexport interface ActivityEntry {\n ts: string;\n tool: string;\n action?: string;\n state?: 'active' | 'idle';\n}\n\n/**\n * Append to activity log with automatic pruning to 100 entries\n */\nexport function appendActivity(agentId: string, entry: ActivityEntry): void {\n const dir = getAgentDir(agentId);\n mkdirSync(dir, { recursive: true });\n\n const activityFile = join(dir, 'activity.jsonl');\n\n // Append entry\n appendFileSync(activityFile, JSON.stringify(entry) + '\\n');\n\n // Prune to last 100 entries\n if (existsSync(activityFile)) {\n try {\n const lines = readFileSync(activityFile, 'utf8').trim().split('\\n');\n if (lines.length > 100) {\n const trimmed = lines.slice(-100);\n writeFileSync(activityFile, trimmed.join('\\n') + '\\n');\n }\n } catch (error) {\n // Ignore pruning errors - activity log is non-critical\n }\n }\n}\n\n/**\n * Read activity log (last N entries)\n */\nexport function getActivity(agentId: string, limit = 100): ActivityEntry[] {\n const activityFile = join(getAgentDir(agentId), 'activity.jsonl');\n\n if (!existsSync(activityFile)) {\n return [];\n }\n\n try {\n const lines = readFileSync(activityFile, 'utf8').trim().split('\\n');\n const entries = lines\n .filter(line => line.trim())\n .map(line => JSON.parse(line) as ActivityEntry)\n .slice(-limit);\n\n return entries;\n } catch {\n return [];\n }\n}\n\n/**\n * Save Claude session ID for later resume\n */\nexport function saveSessionId(agentId: string, sessionId: string): void {\n const dir = getAgentDir(agentId);\n mkdirSync(dir, { recursive: true });\n\n writeFileSync(join(dir, 'session.id'), sessionId);\n}\n\n/**\n * Get saved Claude session ID\n */\nexport function getSessionId(agentId: string): string | null {\n const sessionFile = join(getAgentDir(agentId), 'session.id');\n\n if (!existsSync(sessionFile)) {\n return null;\n }\n\n try {\n return readFileSync(sessionFile, 'utf8').trim();\n } catch {\n return null;\n }\n}\n\n/**\n * PAN-1988 — for a codex agent, resolve its REAL resumable thread id. codex writes a placeholder\n * UUID into `session.id` at spawn; the resumable id is the codex thread, recorded in the rollout.\n * Prefer the explicitly-captured `codex-thread-id`, then fall back to the freshest rollout on disk\n * (always current — codex writes a new rollout per resume, so this self-heals across resume cycles\n * without depending on the capture poll landing). Returns null for non-codex agents.\n */\nfunction resolveCodexThreadIdSync(agentId: string): string | null {\n const agentDir = getAgentDir(agentId);\n const codexHome = join(agentDir, 'codex-home');\n if (!existsSync(codexHome)) return null; // not a codex agent\n try {\n const threadIdPath = join(agentDir, 'codex-thread-id');\n if (existsSync(threadIdPath)) {\n const id = readFileSync(threadIdPath, 'utf-8').trim();\n if (id) return id;\n }\n } catch { /* non-fatal */ }\n try {\n const rollout = findLatestRollout(codexHome);\n if (rollout) {\n const id = extractThreadIdFromRollout(rollout);\n if (id) return id;\n }\n } catch { /* non-fatal */ }\n return null;\n}\n\n/**\n * Sync mirror of jsonl-resolver.ts's pickFreshestSessionId: from a list of\n * candidate session ids, return the one whose JSONL transcript has the most\n * recent mtime, skipping ids with no file on disk. Falls back to the last\n * appended id when none have a transcript (e.g. workspace moved). Returns null\n * only when there are no usable candidates.\n */\nfunction pickFreshestExistingSessionIdSync(agentId: string, candidates: unknown[]): string | null {\n const valid = candidates.filter((v): v is string => typeof v === 'string' && v.trim().length > 0);\n if (valid.length === 0) return null;\n const workspace = getAgentStateSync(agentId)?.workspace;\n if (workspace) {\n const projectDir = join(homedir(), '.claude', 'projects', encodeClaudeProjectDir(workspace));\n let best: { id: string; mtimeMs: number } | null = null;\n for (const id of valid) {\n try {\n const s = statSync(join(projectDir, `${id}.jsonl`));\n if (!best || s.mtimeMs > best.mtimeMs) best = { id, mtimeMs: s.mtimeMs };\n } catch { /* no JSONL for this id — skip */ }\n }\n if (best) return best.id;\n }\n return valid[valid.length - 1] ?? null;\n}\n\nexport function getLatestSessionIdSync(agentId: string): string | null {\n // 0. codex thread id FIRST — `session.id` below holds a placeholder UUID for codex agents, so\n // returning it would make resumeAgent target a non-existent thread and codex would drift into\n // a fresh rollout, losing conversation history (PAN-1988). The freshest rollout is the truth.\n const codexThreadId = resolveCodexThreadIdSync(agentId);\n if (codexThreadId) return codexThreadId;\n\n // 1. session.id (written by auto-suspend) — the real id for claude-code.\n const fromSessionFile = getSessionId(agentId);\n if (fromSessionFile) return fromSessionFile;\n\n // 2. sessions.json (append-only list of session ids the agent has used).\n // The array can hold aborted/empty ids (e.g. a fresh session that never\n // produced a transcript), so we can't trust \"last entry\" — pick the id\n // whose JSONL is freshest on disk, matching resolveClaudeSessionId\n // (jsonl-resolver.ts). Falls back to last-appended when none exist on disk.\n const sessionsFile = join(getAgentDir(agentId), 'sessions.json');\n try {\n if (existsSync(sessionsFile)) {\n const sessions = JSON.parse(readFileSync(sessionsFile, 'utf8'));\n if (Array.isArray(sessions) && sessions.length > 0) {\n const picked = pickFreshestExistingSessionIdSync(agentId, sessions);\n if (picked) return picked;\n }\n }\n } catch { /* non-fatal */ }\n\n // 3. runtime.json claudeSessionId\n const runtimeState = getAgentRuntimeStateSync(agentId);\n if (runtimeState?.claudeSessionId) {\n return runtimeState.claudeSessionId;\n }\n\n // 4. codex-thread-id (written after codex rollout appears; fallback so\n // resumeAgent can locate the Codex session even if session.id has a\n // stale random UUID from spawnRun's placeholder write).\n const codexThreadIdPath = join(getAgentDir(agentId), 'codex-thread-id');\n try {\n if (existsSync(codexThreadIdPath)) {\n const threadId = readFileSync(codexThreadIdPath, 'utf-8').trim();\n if (threadId) return threadId;\n }\n } catch { /* non-fatal */ }\n\n // 5. ohmypi (omp) — PAN-2098. omp never writes a `session.id` file, so none of\n // the claude-code/codex sources above can find it; the real id lives inside\n // the freshest session JSONL. Mirror the ohmypi runtime adapter's own resume\n // resolution so the deacon recovery path can resume a crashed ohmypi agent\n // instead of only respawning it fresh and losing context.\n const agentState = getAgentStateSync(agentId);\n if (agentState?.harness && getHarnessBehavior(agentState.harness).sessionIdSource === 'transcript-jsonl') {\n const ohmypiSessionId = resolveLatestOhmypiSessionId(agentId);\n if (ohmypiSessionId) return ohmypiSessionId;\n }\n\n return null;\n}\n\nexport const getLatestSessionId = (agentId: string): Effect.Effect<string | null> => {\n const agentDir = getAgentDir(agentId);\n const sessionFile = join(agentDir, 'session.id');\n const sessionsFile = join(agentDir, 'sessions.json');\n\n return Effect.gen(function* () {\n const sessionId = yield* Effect.tryPromise({\n try: () => readFile(sessionFile, 'utf8'),\n catch: (cause) => new FsError({ operation: 'read', path: sessionFile, cause }),\n }).pipe(\n Effect.map((content) => content.trim()),\n Effect.orElseSucceed(() => ''),\n );\n if (sessionId) return sessionId;\n\n const latestSession = yield* Effect.tryPromise({\n try: async () => JSON.parse(await readFile(sessionsFile, 'utf8')) as unknown,\n catch: (cause) => new FsError({ operation: 'read', path: sessionsFile, cause }),\n }).pipe(\n Effect.map((sessions) => Array.isArray(sessions) && sessions.length > 0 ? String(sessions[sessions.length - 1]) : null),\n Effect.orElseSucceed(() => null),\n );\n if (latestSession) return latestSession;\n\n const runtimeState = yield* getAgentRuntimeState(agentId);\n return runtimeState?.claudeSessionId ?? null;\n });\n};\n","import type { DomainEvent } from '@overdeck/contracts';\nimport { initEventStore } from '../dashboard/server/event-store.js';\n\nexport type OperatorInterventionKind = 'tell' | 'pause' | 'restart' | 'manual_edit' | 'deep_wipe' | 'unpause' | 'untroubled';\n\nexport interface OperatorInterventionInput {\n issueId: string;\n kind: OperatorInterventionKind;\n source: string;\n timestamp?: string;\n}\n\nexport function operatorInterventionEvent(input: OperatorInterventionInput): Omit<DomainEvent, 'sequence'> {\n return {\n type: 'operator.intervention',\n timestamp: input.timestamp ?? new Date().toISOString(),\n payload: {\n issueId: input.issueId,\n kind: input.kind,\n source: input.source,\n },\n } as Omit<DomainEvent, 'sequence'>;\n}\n\nexport async function appendOperatorInterventionEvent(input: OperatorInterventionInput): Promise<void> {\n const store = await initEventStore();\n await store.appendAsync(operatorInterventionEvent(input));\n}\n","/**\n * PAN-1980: session rotation on resume policy.\n *\n * Work agents used to rotate to a brand-new Claude session (a fresh JSONL\n * transcript) when a resume couldn't reuse the saved one — compact/overflow\n * recovery (PAN-1781), model/harness drift (PAN-1787/1797), or the messageAgent\n * resume-failure fresh-launch fallback. That rotation broke the conversation\n * panel (it pinned to a stale transcript while the terminal showed the live one)\n * and mostly papered over non-Anthropic CLIProxy overflow that native claude-code\n * handles in-place.\n *\n * Rotation is now DISABLED: a resume that would need a fresh session errors and\n * stops the agent for a human instead. Flip {@link ALLOW_SESSION_ROTATION_ON_RESUME}\n * to `true` to restore the old behavior; the intended replacement is in-place\n * `/compact` into the SAME session (the deacon already does this for\n * specialist/planning sessions).\n */\nexport const ALLOW_SESSION_ROTATION_ON_RESUME = false;\n\n/**\n * Whether a resume that would otherwise rotate to a fresh session must be\n * REFUSED. A resume needs a fresh session when it is a compact-recovery respawn\n * (`compactSeed` present) or the saved session drifted (model/harness). With\n * rotation disabled we refuse rather than rotate.\n */\nexport function sessionRotationRefused(opts: {\n compactSeed: boolean;\n driftReasons: readonly string[];\n allowExplicitRecovery?: boolean;\n}): boolean {\n const wouldRotate = opts.compactSeed || opts.driftReasons.length > 0;\n return wouldRotate && !ALLOW_SESSION_ROTATION_ON_RESUME && opts.allowExplicitRecovery !== true;\n}\n","import { mkdirSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { appendOperatorInterventionEvent } from '../operator-interventions.js';\nimport { logAgentLifecycleSync } from '../persistent-logger.js';\nimport { getProviderForModelSync, setupCredentialFileAuthSync, clearCredentialFileAuthSync } from '../providers.js';\nimport { ALLOW_SESSION_ROTATION_ON_RESUME } from '../session-rotation.js';\nimport type { ModelId } from '../settings.js';\nimport { createSession, killSession, listPaneValues, sessionExists } from '../tmux.js';\nimport {\n clearReadySignal,\n normalizeAgentId,\n waitForAgentIdle,\n} from './identity.js';\nimport {\n getAgentDir,\n getAgentResumeGateBlockReason,\n getAgentStateSync,\n markAgentRunning,\n saveAgentStateSync,\n type Role,\n} from './agent-state.js';\nimport { getLatestSessionIdSync } from './activity.js';\nimport {\n deliverAgentMessage,\n deliverResumeMessageWithTranscriptConfirmation,\n resilientDeliveryMethod,\n} from './delivery.js';\nimport { getAgentRuntimeStateSync } from './runtime-state.js';\nimport {\n claudeSystemPromptFiles,\n getCodexLauncherFields,\n getOhmypiLauncherFields,\n getRoleRuntimeBaseCommand,\n hasAgentRuntimeInSubtree,\n waitForPromptReady,\n} from './runtime-command.js';\nimport {\n buildResumeMessageForAgent,\n markKickoffRedelivered,\n prepareSupervisorForRelaunch,\n} from './supervisor-channels.js';\nimport {\n getProviderEnvForModel,\n getProviderExportsForModel,\n} from './provider-env.js';\n\nfunction queueAgentMail(agentId: string, message: string): void {\n const mailDir = join(getAgentDir(agentId), 'mail');\n mkdirSync(mailDir, { recursive: true });\n const timestamp = new Date().toISOString().replace(/[:.]/g, '-');\n writeFileSync(\n join(mailDir, `${timestamp}.md`),\n `# Message\\n\\n${message}\\n`\n );\n}\n\nconst USER_MESSAGE_INTERVENTION_SOURCES = new Set(['pan-tell', 'dashboard:user-message']);\n\nasync function appendTellInterventionForUserSource(normalizedId: string, caller: string): Promise<void> {\n if (!USER_MESSAGE_INTERVENTION_SOURCES.has(caller)) return;\n\n const agentState = getAgentStateSync(normalizedId);\n if (!agentState?.issueId) {\n console.debug(`[agents] Skipping tell intervention for ${normalizedId}; state.json has no issueId`);\n return;\n }\n\n await appendOperatorInterventionEvent({\n issueId: agentState.issueId,\n kind: 'tell',\n source: caller,\n });\n}\n\nexport async function messageAgent(agentId: string, message: string, caller = 'internal'): Promise<void> {\n const normalizedId = normalizeAgentId(agentId);\n const agentState = getAgentStateSync(normalizedId);\n if (agentState?.paused === true) {\n const gateBlockReason = getAgentResumeGateBlockReason(agentState)?.reason ?? 'agent is paused';\n queueAgentMail(normalizedId, message);\n logAgentLifecycleSync(normalizedId, `messageAgent queued mail without resume: ${gateBlockReason}`);\n console.log(`[agents] Queued message for ${normalizedId}; ${gateBlockReason}`);\n return;\n }\n\n // Check if agent is suspended - auto-resume if so (PAN-80)\n const runtimeState = getAgentRuntimeStateSync(normalizedId);\n if (runtimeState?.state === 'suspended') {\n const gateBlockReason = agentState ? getAgentResumeGateBlockReason(agentState)?.reason : undefined;\n if (gateBlockReason) {\n queueAgentMail(normalizedId, message);\n logAgentLifecycleSync(normalizedId, `messageAgent queued mail without resume: ${gateBlockReason}`);\n console.log(`[agents] Queued message for ${normalizedId}; ${gateBlockReason}`);\n return;\n }\n console.log(`[agents] Auto-resuming suspended agent ${normalizedId} to deliver message`);\n const { resumeAgent } = await import('../agents.js');\n const result = await resumeAgent(normalizedId, message);\n if (!result.success) {\n throw new Error(`Failed to auto-resume agent: ${result.error}`);\n }\n if (result.messageDelivered === false) {\n throw new Error(`Agent resumed but ready signal did not fire — message not delivered. Feedback is in the mail queue.`);\n }\n // Message already sent during resume\n await appendTellInterventionForUserSource(normalizedId, caller);\n return;\n }\n\n // Check if agent is stopped — auto-resume to deliver feedback (PAN-367 / PAN-705)\n //\n // IMPORTANT: We delegate to resumeAgent() so we pick up the saved Claude session id\n // (`claude --resume <id>`) instead of fresh-launching with a new, empty session.\n // The previous implementation of this branch called `getAgentRuntimeBaseCommand(model)`\n // and passed an inline \"You are resuming work\" prompt as a positional argument,\n // which booted Claude Code in a fresh session (ctx 0%) with no memory of the\n // prior conversation, destroying agent continuity every time feedback arrived.\n //\n // We also restart when the tmux session still exists. Planning/work sessions use\n // `remain-on-exit on` so the shell persists after the agent process exits, and\n // sessionExists() returns true for that dead shell. resumeAgent() kills the zombie\n // session before re-creating it.\n if (agentState && agentState.status === 'stopped') {\n const gateBlockReason = getAgentResumeGateBlockReason(agentState)?.reason;\n if (gateBlockReason) {\n queueAgentMail(normalizedId, message);\n logAgentLifecycleSync(normalizedId, `messageAgent queued mail without resume: ${gateBlockReason}`);\n console.log(`[agents] Queued message for ${normalizedId}; ${gateBlockReason}`);\n return;\n }\n console.log(`[agents] Auto-resuming stopped agent ${normalizedId} to deliver feedback (session exists: ${await Effect.runPromise(sessionExists(normalizedId))})`);\n\n const { resumeAgent } = await import('../agents.js');\n const resumeResult = await resumeAgent(normalizedId, message);\n\n // Save to mail queue regardless so the agent can re-read feedback if needed\n queueAgentMail(normalizedId, message);\n\n if (resumeResult.success && resumeResult.messageDelivered !== false) {\n await appendTellInterventionForUserSource(normalizedId, caller);\n console.log(`[agents] Resumed ${normalizedId} and delivered feedback`);\n return;\n }\n\n // Resume failed OR message was not delivered (ready signal timed out). Fall back to\n // a fresh launch so feedback is not silently dropped. This path intentionally mirrors\n // spawnAgent's launcher (provider exports + unset of leaked env vars) so the fallback\n // doesn't inherit stale ANTHROPIC_BASE_URL / OPENAI_API_KEY from the parent process.\n if (!resumeResult.success) {\n console.warn(`[agents] Resume failed for ${normalizedId}: ${resumeResult.error} — falling back to fresh launch`);\n } else {\n console.warn(`[agents] Resume succeeded for ${normalizedId} but message not delivered (ready signal timed out) — falling back to fresh launch`);\n }\n\n // PAN-1980: session rotation is disabled — do NOT fresh-launch a new session\n // as a fallback (that rotates the transcript and hides the resume failure).\n // Leave the agent stopped and surface it; the feedback was already queued in\n // the mail queue above, so it is not dropped.\n if (!ALLOW_SESSION_ROTATION_ON_RESUME) {\n const why = !resumeResult.success\n ? `resume failed (${resumeResult.error})`\n : 'resume succeeded but message delivery timed out';\n const stopMsg = `Not restarting ${normalizedId} with a fresh session — ${why}; session rotation is disabled (PAN-1980). Agent left stopped; feedback queued in mail.`;\n console.warn(`[agents] ${stopMsg}`);\n emitActivityEntrySync({ source: 'work-agent', level: 'error', message: `${normalizedId}: ${stopMsg}`, issueId: agentState.issueId });\n return;\n }\n\n const providerEnv = agentState.model ? await getProviderEnvForModel(agentState.model) : {};\n if (agentState.model) {\n const provider = getProviderForModelSync(agentState.model as ModelId);\n if (provider.authType === 'credential-file') {\n setupCredentialFileAuthSync(provider, agentState.workspace);\n } else {\n clearCredentialFileAuthSync(agentState.workspace);\n }\n }\n\n clearReadySignal(normalizedId);\n if (await Effect.runPromise(sessionExists(normalizedId))) {\n try { await Effect.runPromise(killSession(normalizedId)); } catch { /* ignore */ }\n }\n\n const providerExports = await getProviderExportsForModel(agentState.model || 'claude-sonnet-4-6');\n const fallbackLauncher = join(getAgentDir(normalizedId), 'launcher.sh');\n // PAN-1048 C4: resume must relaunch with the agent's actual role, not\n // hardcoded 'work'. A stopped review/test/ship run was previously\n // resurrected as a work agent because launcher generation ignored the\n // saved role. Use agentState.role and route through getRoleRuntimeBaseCommand\n // so the role-specific .claude/agents/* definition file is loaded.\n const resumeRole: Role = agentState.role ?? 'work';\n // PAN-1048 review feedback 006 (S1): Pi-backed resumes need the same\n // launcher fields the fresh-spawn path threads through generateLauncherScript.\n // buildPiCommand throws on missing piSessionDir, so the previous fallback\n // emitted a launcher that would crash on resume for any Pi role agent.\n const resumeModel = agentState.model || 'claude-sonnet-4-6';\n const fallbackHarness = agentState.harness ?? 'claude-code';\n const { assertWorkspaceStackHealthyForSpawn } = await import('../agents.js');\n await assertWorkspaceStackHealthyForSpawn(\n agentState.issueId || normalizedId.replace(/^agent-/, '').toUpperCase(),\n resumeRole,\n agentState.hostOverride === true,\n agentState.workspace,\n );\n const fallbackPiFields = fallbackHarness === 'ohmypi'\n ? await getOhmypiLauncherFields(normalizedId, resumeModel)\n : {};\n const fallbackCodexFields = fallbackHarness === 'codex'\n ? getCodexLauncherFields(normalizedId, resumeModel, agentState.workspace)\n : {};\n const fallbackSupervisorLaunch = await prepareSupervisorForRelaunch(normalizedId, agentState, resumeModel, fallbackHarness);\n const fallbackContent = generateLauncherScriptSync({\n role: resumeRole,\n workingDir: agentState.workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports,\n baseCommand: await getRoleRuntimeBaseCommand(\n resumeModel,\n normalizedId,\n resumeRole,\n fallbackHarness,\n ),\n appendSystemPromptFiles: await claudeSystemPromptFiles(agentState.workspace, fallbackHarness),\n useSupervisor: fallbackSupervisorLaunch.useSupervisor,\n supervisorScriptPath: fallbackSupervisorLaunch.supervisorScriptPath,\n ...fallbackPiFields,\n ...fallbackCodexFields,\n });\n writeFileSync(fallbackLauncher, fallbackContent, { mode: 0o755 });\n await Effect.runPromise(createSession(normalizedId, agentState.workspace, `bash ${fallbackLauncher}`, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: agentState.issueId || '',\n OVERDECK_SESSION_TYPE: agentState.role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n ...providerEnv\n }\n }));\n\n markAgentRunning(agentState);\n saveAgentStateSync(agentState);\n\n const ready = await waitForPromptReady(normalizedId, fallbackHarness, 30);\n const fallbackResumePrompt = `You are resuming work on ${agentState.issueId}. Check .pan/feedback/ for specialist feedback that arrived while you were stopped, then continue working.\\n\\n${message}`;\n const resumeMessage = await buildResumeMessageForAgent(agentState, fallbackResumePrompt, message);\n if (resumeMessage.error) {\n console.error(`[agents] Fallback-restarted ${normalizedId} but ${resumeMessage.error}`);\n emitActivityEntrySync({\n source: 'work-agent',\n level: 'error',\n message: `${normalizedId}: ${resumeMessage.error}`,\n issueId: agentState.issueId,\n });\n } else if (ready && resumeMessage.message) {\n let delivered = false;\n if (fallbackHarness === 'claude-code') {\n const fallbackSessionId = getLatestSessionIdSync(normalizedId);\n if (fallbackSessionId) {\n const delivery = await deliverResumeMessageWithTranscriptConfirmation({\n agentId: normalizedId,\n workspace: agentState.workspace,\n sessionId: fallbackSessionId,\n message: resumeMessage.message,\n caller: 'resumeAgent:resume-prompt',\n deliveryMethod: resilientDeliveryMethod(agentState.deliveryMethod),\n });\n delivered = delivery.delivered;\n if (!delivery.delivered) {\n console.error(`[agents] Fallback resume prompt did not land after ${delivery.attempts} delivery attempts`);\n }\n } else {\n console.error(`[agents] Fallback-restarted ${normalizedId} but no session id was recorded — feedback in mail queue`);\n }\n } else {\n const delivery = await deliverAgentMessage(normalizedId, resumeMessage.message, 'resumeAgent:resume-prompt', resilientDeliveryMethod(agentState.deliveryMethod));\n delivered = delivery.ok;\n }\n if (delivered) {\n if (resumeMessage.redeliveringKickoff) markKickoffRedelivered(agentState);\n await appendTellInterventionForUserSource(normalizedId, caller);\n console.log(`[agents] Fallback-restarted ${normalizedId} and delivered feedback`);\n }\n } else {\n console.warn(`[agents] Fallback-restarted ${normalizedId} but ready signal not detected — feedback in mail queue`);\n }\n\n return;\n }\n\n // Check if this is a remote agent\n const { loadRemoteAgentState, sendToRemoteAgent } = await import('../remote/remote-agents.js');\n const remoteState = loadRemoteAgentState(normalizedId);\n if (remoteState && remoteState.vmName) {\n console.log(`[agents] Sending message to remote agent ${normalizedId} on ${remoteState.vmName}`);\n await sendToRemoteAgent(normalizedId, remoteState.vmName, message);\n\n // Also save to mail queue for persistence\n queueAgentMail(normalizedId, message);\n await appendTellInterventionForUserSource(normalizedId, caller);\n return;\n }\n\n if (!(await Effect.runPromise(sessionExists(normalizedId)))) {\n throw new Error(`Agent ${normalizedId} not running`);\n }\n\n // Guard: if tmux session exists but Claude Code has exited, resume instead\n // of typing the message into a bare bash shell.\n //\n // Launchers differ: specialists `exec claude` so pane_pid IS claude, but\n // work-agent launchers run `bash launcher.sh` so pane_pid is bash and claude\n // runs as a descendant. Walk the pane's process subtree and treat the pane\n // as live if any descendant is the expected runtime for the saved harness.\n const panePids = await Effect.runPromise(listPaneValues(normalizedId, '#{pane_pid}'));\n const expectedHarness = agentState?.harness ?? 'claude-code';\n if (panePids.length > 0 && !(await hasAgentRuntimeInSubtree(panePids[0], expectedHarness))) {\n console.warn(`[agents] ${normalizedId} tmux session is a zombie (no ${expectedHarness} runtime) — attempting resume`);\n const { resumeAgent } = await import('../agents.js');\n const resumeResult = await resumeAgent(normalizedId, message);\n if (resumeResult.success) {\n if (resumeResult.messageDelivered !== false) {\n await appendTellInterventionForUserSource(normalizedId, caller);\n }\n return;\n }\n throw new Error(`Agent ${normalizedId} session is dead and resume failed: ${resumeResult.error}`);\n }\n\n // Wait for the agent to be idle at the prompt before sending — reduces dropped\n // Enter when Claude Code is still rendering. PAN-1594: hook-driven (runtime\n // mirror 'idle' via Stop/SessionStart hook), not a tmux pane-scrape.\n const promptReady = await waitForAgentIdle(normalizedId, 5000);\n if (!promptReady) {\n console.warn(`[agents] ${normalizedId} not at idle prompt after 5s — sending message anyway`);\n }\n\n const deliveryMethod = resilientDeliveryMethod(agentState?.deliveryMethod);\n await deliverAgentMessage(normalizedId, message, `messageAgent:${caller}`, deliveryMethod);\n\n // Also save to mail queue\n queueAgentMail(normalizedId, message);\n await appendTellInterventionForUserSource(normalizedId, caller);\n}\n","/**\n * Context-window overflow is NOT a transient error. It surfaces as e.g.\n * \"API Error: 400 Your input exceeds the context window of this model.\" and\n * nudging \"continue\" only re-sends the same oversized context for the same 400.\n */\nexport const CONTEXT_OVERFLOW_PATTERNS = [\n 'input exceeds the context window',\n 'exceeds the context window of this model',\n 'exceeded model token limit',\n];\n\nexport const CONTEXT_OVERFLOW_TAIL_LINES = 40;\n\n/**\n * Recognize context-window overflow from any thrown error, including the\n * claude -p non-zero exit envelope that carries \"result\":\"Prompt is too long\"\n * and \"terminal_reason\":\"blocking_limit\".\n */\nexport function isContextOverflowError(err: unknown): boolean {\n const message = err instanceof Error ? err.message : String(err);\n const normalized = message.toLowerCase();\n const patterns = [\n ...CONTEXT_OVERFLOW_PATTERNS,\n 'prompt is too long',\n 'blocking_limit',\n ];\n return patterns.some(pattern => normalized.includes(pattern.toLowerCase()));\n}\n\nexport function isContextOverflowTail(output: string): boolean {\n const recentTail = output.split('\\n').slice(-CONTEXT_OVERFLOW_TAIL_LINES).join('\\n').toLowerCase();\n const patterns = [...CONTEXT_OVERFLOW_PATTERNS, 'prompt is too long'];\n return patterns.some(pattern => recentTail.includes(pattern.toLowerCase()));\n}\n\n/**\n * PAN-1781: Opening prompt for a fresh session that replaces a context-wedged\n * one. The old session is summarized out-of-band and the summary is embedded\n * here — the fresh session never resumes the old JSONL, so there is no stale\n * leaf for the harness to rewind to (the failure mode that made in-place\n * boundary injection a silent no-op ~half the time).\n *\n * When summarization fails entirely, `summary` is null and the seed degrades\n * to durable-artifact reconstruction only — the same end state the old\n * /clear + reseed tier produced, minus the keystroke fragility.\n */\nexport function buildCompactRecoverySeedMessage(issueId: string, summary: string | null): string {\n const lines = [\n `Your previous session for ${issueId} hit the model's context-window limit, so you are starting a fresh session. This freed tokens; it did not reset your filesystem work.`,\n 'Do NOT start over and do NOT expect the prior conversation to be available.',\n ];\n if (summary) {\n lines.push('', 'Summary of the archived session:', '', summary, '');\n } else {\n lines.push('');\n }\n lines.push(\n 'Reconstruct your exact work-in-progress from durable artifacts:',\n '1. Read .pan/continue.json for resumePoint, decisions, hazards, feedback, and sessionHistory.',\n '2. Run `bd ready` for your open beads and `bd show <id>` for the bead you are working on.',\n '3. Inspect `git status` and `git diff` for uncommitted work already on disk.',\n 'Then continue from that reconstructed state and complete the next required bead — do not wait for further instructions.',\n );\n return lines.join('\\n');\n}\n","import { existsSync, unlinkSync } from 'fs';\nimport { randomUUID } from 'crypto';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { emitActivityEntrySync } from '../activity-logger.js';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { buildCompactRecoverySeedMessage } from '../context-overflow.js';\nimport { resolveHarness } from '../harness-resolve.js';\nimport { normalizeModelOverrideSync, requireModelOverrideSync } from '../model-validation.js';\nimport { getOverdeckDatabaseSync } from '../overdeck/infra.js';\nimport { sessionFilePath } from '../paths.js';\nimport { logAgentLifecycleSync } from '../persistent-logger.js';\nimport { resolveProjectFromIssueSync } from '../projects.js';\nimport { getHarnessBehavior } from '../runtimes/behavior.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport { sessionRotationRefused } from '../session-rotation.js';\nimport { appendContinueSessionEntryForIssue } from '../vbrief/lifecycle-io.js';\nimport { createSession, isPaneDead, killSession, listPaneValues, sessionExists } from '../tmux.js';\nimport {\n decideResumeGate,\n getAgentDir,\n getAgentResumeGateBlockReason,\n getAgentStateSync,\n markAgentRunning,\n saveAgentStateSync,\n} from './agent-state.js';\nimport { getLatestSessionIdSync, saveSessionId } from './activity.js';\nimport {\n deliverInitialPromptWithRetry,\n deliverResumeMessageWithTranscriptConfirmation,\n resilientDeliveryMethod,\n} from './delivery.js';\nimport { clearReadySignal, normalizeAgentId, waitForReadySignal } from './identity.js';\nimport {\n getAgentRuntimeStateSync,\n saveAgentRuntimeState,\n sessionResumeDriftReasons,\n} from './runtime-state.js';\nimport {\n hasAgentRuntimeInSubtree,\n writeLauncherScriptAtomic,\n writeOhmypiAgentPrompt,\n} from './runtime-command.js';\nimport {\n buildDefaultResumeContinueMessage,\n buildResumeMessageForAgent,\n markKickoffRedelivered,\n prepareSupervisorForRelaunch,\n} from './supervisor-channels.js';\nimport {\n assertWorkspaceStackHealthyForSpawn,\n buildAgentLaunchConfig,\n} from './spawn-prep.js';\n\n/**\n * Resume a suspended agent (PAN-80)\n *\n * Reads saved session ID and creates new tmux session with --resume flag.\n * Optionally sends a message after resuming.\n *\n * Auto-resume triggers:\n * - Specialists: When queued work arrives\n * - Work agents: When message is sent via /work-tell\n */\n/**\n * PAN-1781: Build the opening prompt for a compact-recovery respawn — an\n * out-of-band summary of the wedged session plus durable-artifact reseed\n * instructions. Replaces PAN-1675's in-place JSONL compaction\n * (compactAgentSession), which appended a compact_boundary the harness's\n * resume leaf selection bypassed ~half the time, silently rebuilding the full\n * pre-compact context. The old JSONL is read-only here — never mutated.\n *\n * Never throws and never returns an unusable seed: smart summary → fallback\n * heuristic summary → reseed-instructions-only, in that order. A missing\n * sessionId or workspace skips straight to the reseed-only seed.\n */\nexport async function buildCompactRecoverySeed(agentId: string): Promise<{ seed: string; summarized: boolean }> {\n const normalizedId = normalizeAgentId(agentId);\n const agentState = getAgentStateSync(normalizedId);\n const sessionId = getLatestSessionIdSync(normalizedId);\n const issueId = agentState?.issueId || normalizedId.replace(/^agent-/, '').toUpperCase();\n\n let summary: string | null = null;\n if (agentState?.workspace && sessionId) {\n const sessionFile = sessionFilePath(agentState.workspace, sessionId);\n try {\n // Dynamic imports: keep conversation-compaction out of agents.ts's\n // top-level import graph (it pulls in dashboard server services).\n const [{ getConversationCompactionSettings }, { generateSmartSummary }] = await Promise.all([\n import('../../dashboard/server/services/conversation-compaction.js'),\n import('../conversations/smart-compaction.js'),\n ]);\n const settings = getConversationCompactionSettings();\n const result = await Effect.runPromise(generateSmartSummary({\n jsonlPath: sessionFile,\n model: settings.model,\n richMode: settings.richCompaction,\n mode: 'fork',\n }));\n summary = result.summary;\n } catch (err) {\n const error = err instanceof Error ? err.message : String(err);\n logAgentLifecycleSync(normalizedId, `compact-recovery smart summary failed (${error}); trying heuristic fallback`);\n try {\n const { generateFallbackSummary } = await import('../conversations/summary-fork.js');\n summary = await Effect.runPromise(generateFallbackSummary(sessionFile));\n } catch (fallbackErr) {\n const fallbackError = fallbackErr instanceof Error ? fallbackErr.message : String(fallbackErr);\n logAgentLifecycleSync(normalizedId, `compact-recovery fallback summary failed (${fallbackError}); seeding with reseed instructions only`);\n }\n }\n }\n\n return {\n seed: buildCompactRecoverySeedMessage(issueId, summary),\n summarized: summary !== null,\n };\n}\n\nexport async function resumeAgent(agentId: string, message?: string, opts?: { model?: string; harness?: RuntimeName; allowHost?: boolean; compact?: boolean; recoverGated?: boolean }): Promise<{ success: boolean; messageDelivered?: boolean; error?: string }> {\n const normalizedId = normalizeAgentId(agentId);\n const requestedModel = normalizeModelOverrideSync(opts?.model);\n logAgentLifecycleSync(normalizedId, `resumeAgent called (message=${message ? 'yes' : 'no'}, harness=${opts?.harness || 'unchanged'})`);\n\n // Check runtime state — allow both suspended (auto-suspend) and stopped/idle (manual stop, crash)\n const runtimeState = getAgentRuntimeStateSync(normalizedId);\n const agentState = getAgentStateSync(normalizedId);\n const gateBlock = agentState ? getAgentResumeGateBlockReason(agentState) : undefined;\n const gateDecision = decideResumeGate(gateBlock, 'operator-start');\n const bypassTroubledGate = opts?.compact === true && opts.recoverGated === true && agentState?.troubled === true && agentState.paused !== true;\n if (gateDecision.decision === 'block' && !bypassTroubledGate) {\n const reason = `Cannot resume ${normalizedId}: ${gateDecision.reason}. Clear the gate before resuming.`;\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return { success: false, error: reason };\n }\n if (gateDecision.decision === 'proceed' && gateDecision.warning) {\n logAgentLifecycleSync(normalizedId, `resumeAgent: ${gateDecision.warning}`);\n }\n if (bypassTroubledGate) {\n logAgentLifecycleSync(normalizedId, `resumeAgent: bypassing troubled gate for explicit compact recovery (${gateBlock?.reason})`);\n }\n const hasWorkspace = !!agentState?.workspace && existsSync(agentState.workspace);\n const isPlaceholder = !!agentState && agentState.status === 'starting' && typeof agentState.model === 'string' && agentState.model.startsWith('pending-');\n const allowedRuntimeStates = ['suspended', 'idle'];\n const allowedAgentStatuses = ['stopped', 'completed'];\n\n // Also allow resuming a \"running\" OR \"starting\" agent with no live tmux session —\n // this happens after a system crash where tmux was killed but state.json was never\n // updated to 'stopped'. For 'starting' this is a spawn that got past model\n // resolution but whose tmux session died mid-launch (the deacon patrol would\n // normally heal starting→stopped after its grace window, but that requires the\n // deacon to be running / not in OVERDECK_NO_RESUME mode). A non-placeholder\n // 'starting' agent with a saved session is resumable exactly like a crashed\n // 'running' agent; placeholder 'starting' agents (model starts with 'pending-')\n // are still rejected below because they never produced a resumable session.\n // The lifecycle UI model already treats runtime=stopped as isStopped, so this\n // keeps the gate consistent with the Resume button that model enables.\n // PAN-2098: a crash leaves the agent at status='running'/'starting' but with no\n // live process. Two shapes: (a) tmux session gone entirely, or (b) the tmux\n // session is still up while the harness process inside its pane has exited — a\n // \"keep-alive corpse\" (remain-on-exit leaves a dead pane in a live session).\n // The old check used `!sessionExists` alone, so a corpse (session present, pane\n // dead) was misclassified as a healthy running agent and refused resume with a\n // reasonless \"Cannot resume … runtime=active, status=running\". Treat a dead pane\n // as crashed too, matching the start path (flywheel-actions.ts isPaneDead).\n const isRunningOrStarting = agentState?.status === 'running' || agentState?.status === 'starting';\n const sessionAlive = isRunningOrStarting ? await Effect.runPromise(sessionExists(normalizedId)) : false;\n const paneDead = isRunningOrStarting && (!sessionAlive || await Effect.runPromise(isPaneDead(normalizedId)));\n const isCrashed = isRunningOrStarting && paneDead;\n\n // PAN-1675 (keystone): a `compact` resume exists specifically to recover a\n // context-wedged agent, which is typically status='running' with a LIVE (but\n // stuck) tmux session sitting at an overflow/idle prompt. The normal canResume\n // gate rejects running+live-session agents — which would make\n // resumeAgent({compact:true}) (the deacon's overflow recovery tiers AND\n // `pan resume --compact`) a silent no-op for exactly the agents it targets.\n // So a compact-resume of a running agent is allowed: the flow below compacts\n // the JSONL out-of-band and then kills the wedged session before relaunch.\n // This is safe because the only callers of {compact:true} act on agents they\n // have already determined to be context-overflow-wedged.\n const isCompactRecovery = opts?.compact === true && agentState?.status === 'running';\n\n const canResume = (runtimeState && allowedRuntimeStates.includes(runtimeState.state))\n || (agentState && allowedAgentStatuses.includes(agentState.status))\n || isCrashed\n || isCompactRecovery;\n\n if (!canResume) {\n // PAN-2098: never refuse without a concrete reason. A running/starting agent\n // that reached here has a live session AND a live pane (a crash would have set\n // isCrashed above), so it is genuinely healthy and there is nothing to resume.\n const reason = isRunningOrStarting\n ? `Cannot resume ${normalizedId}: it appears healthy (tmux session up, harness process alive) — there is nothing to resume. Stop it first if you intend to restart it.`\n : `Cannot resume ${normalizedId}: runtime=${runtimeState?.state || 'unknown'}, status=${agentState?.status || 'unknown'} is not a resumable state.`;\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return {\n success: false,\n error: reason\n };\n }\n\n // Get saved session ID from any available source\n const sessionId = getLatestSessionIdSync(normalizedId);\n if (!sessionId) {\n // PAN-2098: state the concrete reason. ohmypi now resolves from its session\n // JSONL (see getLatestSessionIdSync); reaching here means no id exists in any\n // source for this harness, so a fresh start is genuinely the only option.\n const harnessLabel = agentState?.harness ?? 'unknown';\n const reason = `Cannot resume ${normalizedId} (harness=${harnessLabel}): no resumable session id found — no session.id file, no sessions.json entry, and no recoverable session transcript on disk. Start a fresh agent instead.`;\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return {\n success: false,\n error: reason\n };\n }\n\n if (!agentState || !hasWorkspace || isPlaceholder) {\n const reason = 'Saved Claude session is orphaned because the backing workspace/agent state is missing or placeholder-only. Start a fresh agent instead.';\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return {\n success: false,\n error: reason\n };\n }\n\n try {\n await assertWorkspaceStackHealthyForSpawn(\n agentState.issueId || normalizedId.replace(/^agent-/, '').toUpperCase(),\n agentState.role ?? 'work',\n opts?.allowHost === true || agentState.hostOverride === true,\n agentState.workspace,\n );\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `resumeAgent BLOCKED: ${reason}`);\n return { success: false, error: reason };\n }\n\n // PAN-1781: compact recovery = summarize the wedged session out-of-band and\n // respawn a FRESH session seeded with that summary. The previous approach\n // (PAN-1675: append a compact_boundary to the JSONL and relaunch with\n // --resume) was silently bypassed by the harness's resume leaf selection\n // ~half the time in the field — the relaunched session rebuilt the full\n // pre-compact context, re-overflowed, and escalated to /clear. A fresh\n // seeded session has nothing stale to rewind to, so its starting context is\n // bounded by construction. The old JSONL stays untouched on disk.\n // buildCompactRecoverySeed never throws: it degrades smart summary →\n // heuristic summary → reseed-instructions-only.\n let compactSeed: string | null = null;\n if (opts?.compact) {\n const seedResult = await buildCompactRecoverySeed(normalizedId);\n compactSeed = seedResult.seed;\n logAgentLifecycleSync(normalizedId, `compact recovery: respawning fresh session (seed=${seedResult.summarized ? 'summary' : 'reseed-only'})`);\n }\n\n // PAN-2009: capture whether the ohmypi process is actually alive BEFORE we kill any\n // zombie session below. A DEAD omp process cannot be resumed by session id —\n // `omp --resume` against a cleaned-up session never writes ready.json (the \"did\n // not become ready within 30s\" hang) — and there is no live transcript to\n // protect, so it must be fresh-launched (recovery, not rotation). A live\n // (suspended) omp process stays on the normal resume path.\n const piProcessWasAlive = getHarnessBehavior(agentState.harness).usesRpcFifo\n ? await hasAgentRuntimeInSession(normalizedId, 'ohmypi')\n : false;\n\n // Kill any zombie tmux session (crashed agent left behind)\n if (await Effect.runPromise(sessionExists(normalizedId))) {\n try {\n await Effect.runPromise(killSession(normalizedId));\n } catch { /* non-fatal */ }\n }\n\n // Remove completed marker so the agent can work again\n const completedFile = join(getAgentDir(normalizedId), 'completed');\n if (existsSync(completedFile)) {\n try { unlinkSync(completedFile); } catch { /* non-fatal */ }\n }\n\n // Append 'resume' session entry to continue state (PAN-946: workspace-44p)\n try {\n if (agentState?.workspace) {\n const issueId = agentState.issueId || normalizedId.replace('agent-', '').toUpperCase();\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) {\n appendContinueSessionEntryForIssue(resolved.projectPath, issueId, {\n reason: 'resume',\n agentModel: agentState.model || undefined,\n });\n }\n }\n } catch (continueErr: any) {\n console.warn(`[resumeAgent] Failed to append resume entry to continue state (non-fatal): ${continueErr?.message ?? continueErr}`);\n }\n\n try {\n const resumeStartedAt = new Date().toISOString();\n // Clear ready signal before resuming (clean slate for PAN-87 fix)\n clearReadySignal(normalizedId);\n\n const model = requestedModel || requireModelOverrideSync(agentState.model || 'claude-sonnet-4-6');\n if (requestedModel && requestedModel !== agentState.model) {\n agentState.model = requestedModel;\n saveAgentStateSync(agentState);\n }\n // PAN-1797: agents predating session-origin metadata must not have their\n // stored harness treated as `explicit` on auto-resume — that pins a stale\n // pairing (e.g. gpt-5.5 on claude-code) over the provider default forever.\n // Re-resolve from the model for origin-less agents (only an operator-supplied\n // opts.harness counts as explicit); agents WITH origin metadata keep prior\n // behavior and are handled by sessionResumeDriftReasons below.\n const hasSessionOrigin = !!(runtimeState?.sessionModel && runtimeState?.sessionHarness);\n const priorHarness = agentState.harness;\n const effectiveHarness = await resolveHarness({\n explicit: hasSessionOrigin ? (opts?.harness ?? agentState.harness) : opts?.harness,\n role: agentState.role,\n model,\n });\n const legacyHarnessMigrated =\n !hasSessionOrigin && priorHarness !== undefined && priorHarness !== effectiveHarness;\n agentState.harness = effectiveHarness;\n const supervisorLaunch = await prepareSupervisorForRelaunch(normalizedId, agentState, model, effectiveHarness);\n saveAgentStateSync(agentState);\n const resumeDriftReasons = sessionResumeDriftReasons(runtimeState, model, effectiveHarness);\n if (legacyHarnessMigrated) {\n // PAN-1797: force a fresh session so the re-defaulted harness takes effect;\n // never reuse a session across a harness change.\n resumeDriftReasons.push(`legacy harness ${priorHarness}→${effectiveHarness} (PAN-1797 re-default)`);\n }\n // PAN-2009: a dead ohmypi process is fresh-launchable recovery — force a fresh\n // session (no `omp --resume`, which would hang waiting for ready.json) instead\n // of a doomed resume-by-id. This is NOT session rotation (no live session or\n // transcript exists to protect), so it is exempt from the PAN-1980 refusal\n // below — it adds no compact seed and no drift reason. Live (suspended) omp and\n // compact/drift resumes are unaffected.\n const piDeadRecovery = effectiveHarness === 'ohmypi' && !piProcessWasAlive\n && !compactSeed && resumeDriftReasons.length === 0;\n if (piDeadRecovery) {\n logAgentLifecycleSync(normalizedId, 'resumeAgent: dead ohmypi process — fresh-launching for recovery instead of omp --resume (PAN-2009)');\n }\n const shouldResumeSavedSession = !compactSeed && resumeDriftReasons.length === 0 && !piDeadRecovery;\n // PAN-1980: refuse to rotate to a new session. A resume that would need a\n // fresh session — compact/overflow recovery or model/harness drift — now\n // errors and stops instead of starting a new transcript.\n if (sessionRotationRefused({ compactSeed: Boolean(compactSeed), driftReasons: resumeDriftReasons, allowExplicitRecovery: opts?.compact === true && opts.recoverGated === true })) {\n const reason = compactSeed\n ? 'context-overflow compaction would respawn a fresh session'\n : `session drift (${resumeDriftReasons.join(', ')})`;\n const errMsg = `Refusing to rotate ${normalizedId} to a new session — ${reason}; session rotation is disabled (PAN-1980). Agent left stopped.`;\n logAgentLifecycleSync(normalizedId, `resumeAgent: ${errMsg}`);\n emitActivityEntrySync({ source: 'work-agent', level: 'error', message: `${normalizedId}: ${errMsg}`, issueId: agentState.issueId });\n return { success: false, error: errMsg };\n }\n const freshSessionId = !shouldResumeSavedSession && effectiveHarness === 'claude-code'\n ? randomUUID()\n : undefined;\n if (resumeDriftReasons.length > 0) {\n logAgentLifecycleSync(normalizedId, `resumeAgent: starting fresh session instead of --resume because session origin drifted (${resumeDriftReasons.join(', ')})`);\n }\n if (freshSessionId) {\n saveSessionId(normalizedId, freshSessionId);\n } else if (!shouldResumeSavedSession) {\n try {\n unlinkSync(join(getAgentDir(normalizedId), 'session.id'));\n } catch { /* absent or already cleared */ }\n }\n\n // Compute the effective message before building the launcher so codex can\n // embed it as the inline prompt in `codex exec resume <threadId> <message>`.\n // PAN-1781: a compact recovery skips the kickoff-redelivery machinery — its\n // seed (summary + reseed instructions) IS the opening prompt of the fresh\n // session; a caller-supplied message rides along after it.\n const issueId = agentState.issueId || normalizedId.replace(/^agent-/, '').toUpperCase();\n const defaultResumeMessage = buildDefaultResumeContinueMessage(issueId);\n const resumeMessage: { message?: string; redeliveringKickoff: boolean; error?: string } = compactSeed\n ? { message: message ? `${compactSeed}\\n\\n${message}` : compactSeed, redeliveringKickoff: false }\n : resumeDriftReasons.length > 0\n ? { message: message ?? defaultResumeMessage, redeliveringKickoff: false }\n : await buildResumeMessageForAgent(agentState, defaultResumeMessage, message);\n if (resumeMessage.error) {\n console.error(`[resumeAgent] ${resumeMessage.error}`);\n emitActivityEntrySync({\n source: 'work-agent',\n level: 'error',\n message: `${normalizedId}: ${resumeMessage.error}`,\n issueId,\n });\n return { success: false, error: resumeMessage.error };\n }\n const effectiveMessage = resumeMessage.message ?? defaultResumeMessage;\n\n const { launcherContent, providerEnv } = await buildAgentLaunchConfig({\n agentId: normalizedId,\n model,\n workspace: agentState.workspace,\n role: agentState.role,\n isPlanning: agentState.role === 'plan',\n // PAN-1781/PAN-1787: compact recovery and model/harness drift launch a\n // fresh session. Normal resumes keep re-attaching to the saved session.\n ...(shouldResumeSavedSession ? { spawnMode: 'resume' as const, resumeSessionId: sessionId } : {}),\n sessionId: freshSessionId,\n harness: effectiveHarness,\n useSupervisor: supervisorLaunch.useSupervisor,\n supervisorScriptPath: supervisorLaunch.supervisorScriptPath,\n });\n\n const launcherScript = join(getAgentDir(normalizedId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n const claudeCmd = `bash ${launcherScript}`;\n\n await Effect.runPromise(createSession(normalizedId, agentState.workspace, claudeCmd, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: agentState.issueId || '',\n OVERDECK_SESSION_TYPE: agentState.role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n ...providerEnv\n }\n }));\n\n // Always wake the resumed agent with a continue prompt — without it, the\n // re-attached session sits silently at its last state, and the user (or\n // deacon nudge loop) ends up sending one manually anyway. Default matches\n // restartAgent's wording so behaviour is consistent across both entry points.\n // Caller-supplied message wins.\n\n let messageDelivered = false;\n if (effectiveHarness === 'ohmypi') {\n // ohmypi does not fire the Claude SessionStart hook; wait for ready.json and\n // deliver the auto-continue prompt through the FIFO JSONL protocol.\n try {\n await writeOhmypiAgentPrompt(normalizedId, effectiveMessage);\n messageDelivered = true;\n if (resumeMessage.redeliveringKickoff) markKickoffRedelivered(agentState);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[resumeAgent] ohmypi prompt delivery failed: ${msg}`);\n }\n } else if (effectiveHarness === 'codex') {\n const delivery = await deliverInitialPromptWithRetry(normalizedId, effectiveMessage, 'resumeAgent:codex-continue', resilientDeliveryMethod(agentState.deliveryMethod));\n messageDelivered = delivery.ok;\n if (delivery.ok && resumeMessage.redeliveringKickoff) markKickoffRedelivered(agentState);\n if (!delivery.ok) {\n console.error(`[resumeAgent] Codex continue prompt did not land: ${delivery.failure ?? 'unknown failure'}`);\n }\n } else if (!shouldResumeSavedSession) {\n // Fresh session fallback — deliver like a kickoff. Transcript\n // confirmation is impossible here: the new session's id is unknown until\n // its SessionStart hook fires, and the saved sessionId points at the\n // archived or mismatched session. deliverInitialPromptWithRetry waits for\n // the ready signal internally.\n const delivery = await deliverInitialPromptWithRetry(normalizedId, effectiveMessage, 'resumeAgent:compact-seed', resilientDeliveryMethod(agentState.deliveryMethod));\n messageDelivered = delivery.ok;\n if (!delivery.ok) {\n console.error(`[resumeAgent] Fresh-session continue prompt did not land: ${delivery.failure ?? 'unknown failure'}`);\n }\n } else {\n // Wait for SessionStart hook to signal ready (PAN-87: reliable message delivery)\n const ready = await waitForReadySignal(normalizedId, 30);\n if (ready) {\n const delivery = await deliverResumeMessageWithTranscriptConfirmation({\n agentId: normalizedId,\n workspace: agentState.workspace,\n sessionId,\n message: effectiveMessage,\n caller: 'resumeAgent:auto-continue',\n deliveryMethod: resilientDeliveryMethod(agentState.deliveryMethod),\n });\n messageDelivered = delivery.delivered;\n if (delivery.delivered && resumeMessage.redeliveringKickoff) markKickoffRedelivered(agentState);\n if (!delivery.delivered) {\n console.error(`[resumeAgent] Auto-continue prompt did not land after ${delivery.attempts} delivery attempts`);\n }\n } else {\n console.error('Claude SessionStart hook did not fire during resume, continue prompt not sent');\n }\n }\n\n const resumedAt = new Date().toISOString();\n if (compactSeed) {\n console.log(`[agents] Respawned ${normalizedId} fresh with compact-recovery seed (archived session ${sessionId}${freshSessionId ? `, new session ${freshSessionId}` : ''})`);\n logAgentLifecycleSync(normalizedId, `resumeAgent SUCCESS: compact-recovery fresh respawn (archived sessionId=${sessionId}${freshSessionId ? `, newSessionId=${freshSessionId}` : ''}), messageDelivered=${messageDelivered}`);\n } else if (piDeadRecovery) {\n console.log(`[agents] Respawned ${normalizedId} fresh because the prior Pi process was dead (archived session ${sessionId})`);\n logAgentLifecycleSync(normalizedId, `resumeAgent SUCCESS: fresh respawn after dead Pi process (archived sessionId=${sessionId}), messageDelivered=${messageDelivered}`);\n } else if (!shouldResumeSavedSession) {\n console.log(`[agents] Respawned ${normalizedId} fresh because session origin drifted (archived session ${sessionId}${freshSessionId ? `, new session ${freshSessionId}` : ''})`);\n logAgentLifecycleSync(normalizedId, `resumeAgent SUCCESS: fresh respawn after origin drift (archived sessionId=${sessionId}${freshSessionId ? `, newSessionId=${freshSessionId}` : ''}), messageDelivered=${messageDelivered}`);\n } else {\n console.log(`[agents] Resumed ${normalizedId} with Claude session ${sessionId}`);\n logAgentLifecycleSync(normalizedId, `resumeAgent SUCCESS: sessionId=${sessionId}, messageDelivered=${messageDelivered}`);\n }\n await saveAgentRuntimeState(normalizedId, {\n state: 'active',\n lastActivity: resumedAt,\n });\n\n // Update agent state\n if (agentState) {\n if (bypassTroubledGate) {\n delete agentState.troubled;\n delete agentState.troubledAt;\n delete agentState.consecutiveFailures;\n delete agentState.firstFailureInRunAt;\n delete agentState.lastFailureAt;\n delete agentState.lastFailureReason;\n delete agentState.lastFailureNextRetryAt;\n }\n agentState.lastResumeAt = resumeStartedAt;\n markAgentRunning(agentState, { preserveFailureTracking: true });\n saveAgentStateSync(agentState);\n }\n\n // PAN-1675: a successful compaction-resume genuinely recovers a\n // context-overflow-wedged agent — so clear a context_overflow `stuck` flag\n // here (set by markWorkspaceStuck once the old /compact+/clear ladder\n // exhausted). Without this the agent would stay flagged stuck forever and\n // the deacon's overflowBlocked gate would keep skipping its recovery, even\n // though the agent is now healthy. Only clear when the stuck reason is\n // context_overflow (don't clobber an unrelated stuck state).\n if (opts?.compact && agentState?.issueId) {\n try {\n const db = getOverdeckDatabaseSync();\n const row = db.prepare('SELECT stuck, stuck_reason FROM review_status WHERE issue_id = ?')\n .get(agentState.issueId.toUpperCase()) as { stuck?: number; stuck_reason?: string | null } | undefined;\n if (row?.stuck === 1 && row.stuck_reason === 'context_overflow') {\n db.prepare(`\n UPDATE review_status\n SET stuck = 0, stuck_reason = NULL, stuck_at = NULL, stuck_details = NULL, updated_at = ?\n WHERE issue_id = ?\n `).run(Date.now(), agentState.issueId.toUpperCase());\n logAgentLifecycleSync(normalizedId, `cleared context_overflow stuck flag after compaction-resume for ${agentState.issueId}`);\n }\n } catch (clearErr) {\n console.warn(`[agents] Could not clear stuck flag after compaction-resume for ${normalizedId}:`, clearErr);\n }\n }\n\n return { success: true, messageDelivered };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `resumeAgent FAILED: ${msg}`);\n return {\n success: false,\n error: `Failed to resume agent: ${msg}`\n };\n }\n}\n\n/**\n * Check whether a tmux session has an active agent runtime.\n * A session may exist with only a bare bash shell after Claude exits.\n */\nasync function hasAgentRuntimeInSession(sessionName: string, harness: RuntimeName): Promise<boolean> {\n try {\n const panePids = await Effect.runPromise(listPaneValues(sessionName, '#{pane_pid}'));\n if (panePids.length === 0) return false;\n return hasAgentRuntimeInSubtree(panePids[0]!, harness);\n } catch {\n return false;\n }\n}\n","import { existsSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { sendEscapeKeyAsync, sendKeys } from './tmux.js';\nimport { getHarnessBehavior } from './runtimes/behavior.js';\nimport type { RuntimeName } from './runtimes/types.js';\n\nexport const GRACEFUL_RESTART_GRACE_MS = 60_000;\n\nexport async function sendGracefulRestartWarning(\n agentId: string,\n harness: RuntimeName | undefined,\n workspace: string,\n): Promise<void> {\n const warning = 'Restarting in 60s. Update .pan/continue.json now with all progress, decisions, hazards, and resume point.';\n try {\n if (harness && !getHarnessBehavior(harness).usesRpcFifo) {\n await sendEscapeKeyAsync(agentId, 2);\n await new Promise((r) => setTimeout(r, 1_000));\n }\n await Effect.runPromise(sendKeys(agentId, warning));\n } catch { /* non-fatal — session may already be dead */ }\n\n await new Promise(r => setTimeout(r, GRACEFUL_RESTART_GRACE_MS));\n\n const continueFile = join(workspace, '.pan', 'continue.json');\n if (existsSync(continueFile)) {\n const mtime = statSync(continueFile).mtimeMs;\n const ageMs = Date.now() - mtime;\n if (ageMs > 5 * 60 * 1000) {\n console.warn(`[restartAgent] continue.json is stale (${Math.round(ageMs / 1000)}s old) — proceeding anyway`);\n }\n }\n}\n","import { existsSync, readFileSync, writeFileSync } from 'fs';\nimport { join } from 'path';\nimport { Effect } from 'effect';\nimport { BLANKED_PROVIDER_ENV } from '../child-env.js';\nimport { sendGracefulRestartWarning } from '../graceful-restart.js';\nimport { checkHookSync, generateFixedPointPromptSync } from '../hooks.js';\nimport { generateLauncherScriptSync } from '../launcher-generator.js';\nimport { resolveHarness } from '../harness-resolve.js';\nimport { normalizeModelOverrideSync, requireModelOverrideSync } from '../model-validation.js';\nimport { logAgentLifecycleSync } from '../persistent-logger.js';\nimport { getProviderForModelSync, setupCredentialFileAuthSync, clearCredentialFileAuthSync } from '../providers.js';\nimport type { ModelId } from '../settings.js';\nimport { normalizeHarness } from '../overdeck/conversations.js';\nimport type { RuntimeName } from '../runtimes/types.js';\nimport {\n createSession,\n createSessionSync,\n killSessionSync,\n listPaneValues,\n sendKeys,\n sessionExists,\n sessionExistsSync,\n} from '../tmux.js';\nimport {\n decideResumeGate,\n getAgentDir,\n getAgentResumeGateBlockReason,\n getAgentStateSync,\n markAgentRunning,\n saveAgentStateSync,\n type AgentState,\n type Role,\n} from './agent-state.js';\nimport { deliverAgentMessage, deliverInitialPromptWithRetry, resilientDeliveryMethod } from './delivery.js';\nimport { clearReadySignal, normalizeAgentId } from './identity.js';\nimport { listRunningAgentsSync } from './queries.js';\nimport { getProviderEnvForModel, getProviderExportsForModel } from './provider-env.js';\nimport { saveAgentRuntimeState } from './runtime-state.js';\nimport {\n claudeSystemPromptFiles,\n getCodexLauncherFields,\n getRoleRuntimeBaseCommand,\n hasAgentRuntimeInSubtree,\n waitForPromptReady,\n writeLauncherScriptAtomic,\n writeOhmypiAgentPrompt,\n} from './runtime-command.js';\nimport { assertWorkspaceStackHealthyForSpawn, buildAgentLaunchConfig } from './spawn-prep.js';\nimport { prepareSupervisorForRelaunch } from './supervisor-channels.js';\nimport { stopAgent } from './termination.js';\n\nexport interface RestartAgentOptions {\n model?: string;\n harness?: RuntimeName;\n graceful?: boolean;\n message?: string;\n}\n\nexport async function restartAgent(\n agentId: string,\n opts: RestartAgentOptions = {},\n): Promise<{ success: boolean; error?: string }> {\n const normalizedId = normalizeAgentId(agentId);\n const { graceful = true, model: rawNewModel, harness: newHarness, message } = opts;\n const newModel = normalizeModelOverrideSync(rawNewModel);\n\n const agentState = getAgentStateSync(normalizedId);\n if (!agentState) {\n return { success: false, error: `Agent ${normalizedId} not found` };\n }\n const gateDecision = decideResumeGate(getAgentResumeGateBlockReason(agentState), 'operator-start');\n if (gateDecision.decision === 'block') {\n const reason = `Cannot restart ${normalizedId}: ${gateDecision.reason}. Clear the gate before restarting.`;\n logAgentLifecycleSync(normalizedId, `restartAgent BLOCKED: ${reason}`);\n return { success: false, error: reason };\n }\n if (!agentState.workspace || !existsSync(agentState.workspace)) {\n return { success: false, error: `Agent workspace missing: ${agentState.workspace}` };\n }\n\n logAgentLifecycleSync(normalizedId, `restartAgent called (graceful=${graceful}, model=${newModel || 'unchanged'}, harness=${newHarness || 'unchanged'})`);\n\n try {\n await assertWorkspaceStackHealthyForSpawn(\n agentState.issueId || normalizedId.replace(/^agent-/, '').toUpperCase(),\n agentState.role ?? 'work',\n agentState.hostOverride === true,\n agentState.workspace,\n );\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `restartAgent BLOCKED: ${reason}`);\n return { success: false, error: reason };\n }\n\n if (graceful && await Effect.runPromise(sessionExists(normalizedId))) {\n await sendGracefulRestartWarning(normalizedId, agentState.harness, agentState.workspace);\n }\n\n await Effect.runPromise(stopAgent(normalizedId));\n\n const effectiveModel = newModel || requireModelOverrideSync(agentState.model || 'claude-sonnet-4-6');\n const effectiveHarness = await resolveHarness({\n explicit: newHarness ?? agentState.harness,\n role: agentState.role,\n model: effectiveModel,\n });\n if (newModel && newModel !== agentState.model) {\n agentState.model = newModel;\n }\n agentState.harness = effectiveHarness;\n agentState.status = 'starting';\n saveAgentStateSync(agentState);\n\n try {\n clearReadySignal(normalizedId);\n const supervisorLaunch = await prepareSupervisorForRelaunch(normalizedId, agentState, effectiveModel, effectiveHarness);\n saveAgentStateSync(agentState);\n\n const { launcherContent, providerEnv } = await buildAgentLaunchConfig({\n agentId: normalizedId,\n model: effectiveModel,\n workspace: agentState.workspace,\n role: agentState.role,\n isPlanning: agentState.role === 'plan',\n harness: effectiveHarness,\n useSupervisor: supervisorLaunch.useSupervisor,\n supervisorScriptPath: supervisorLaunch.supervisorScriptPath,\n });\n\n const launcherScript = join(getAgentDir(normalizedId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n const claudeCmd = `bash ${launcherScript}`;\n\n await Effect.runPromise(createSession(normalizedId, agentState.workspace, claudeCmd, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n TERM: 'xterm-256color',\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: agentState.issueId || '',\n OVERDECK_SESSION_TYPE: agentState.role,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n GIT_SEQUENCE_EDITOR: 'false',\n ...providerEnv,\n },\n }));\n\n const prompt = message || `You are resuming work on ${agentState.issueId}. Read .pan/continue.json for context and pick up where you left off.`;\n if (effectiveHarness === 'ohmypi') {\n // ohmypi does not fire the Claude SessionStart hook and does not read tmux\n // input — wait for ready.json and write the continue prompt through the\n // FIFO JSONL protocol.\n try {\n await writeOhmypiAgentPrompt(normalizedId, prompt);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[restartAgent] ohmypi prompt delivery failed for ${normalizedId}: ${msg}`);\n }\n } else {\n const ready = await waitForPromptReady(normalizedId, effectiveHarness, 30);\n if (ready) {\n await new Promise(r => setTimeout(r, 500));\n if (effectiveHarness === 'codex') {\n await deliverAgentMessage(normalizedId, prompt, 'restartAgent:continue-prompt', resilientDeliveryMethod(agentState.deliveryMethod));\n } else {\n await Effect.runPromise(sendKeys(normalizedId, prompt));\n }\n } else {\n console.error(`[restartAgent] ${effectiveHarness === 'codex' ? 'Codex' : 'Claude'} did not become ready within 30s for ${normalizedId}`);\n }\n }\n\n markAgentRunning(agentState);\n saveAgentStateSync(agentState);\n\n await saveAgentRuntimeState(normalizedId, {\n state: 'active',\n lastActivity: new Date().toISOString(),\n });\n\n logAgentLifecycleSync(normalizedId, `restartAgent SUCCESS: model=${effectiveModel}`);\n return { success: true };\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `restartAgent FAILED: ${msg}`);\n return { success: false, error: `Failed to restart agent: ${msg}` };\n }\n}\n\n/**\n * Check whether a tmux session has an active agent runtime.\n * A session may exist with only a bare bash shell after Claude exits.\n */\nasync function hasAgentRuntimeInSession(sessionName: string, harness: RuntimeName): Promise<boolean> {\n try {\n const panePids = await Effect.runPromise(listPaneValues(sessionName, '#{pane_pid}'));\n if (panePids.length === 0) return false;\n return hasAgentRuntimeInSubtree(panePids[0]!, harness);\n } catch {\n return false;\n }\n}\n\n/**\n * Detect crashed agents (state shows running but tmux session is gone)\n */\nexport function detectCrashedAgents(): AgentState[] {\n const agents = listRunningAgentsSync();\n return agents.filter(\n (agent) => agent.status === 'running' && !agent.tmuxActive\n );\n}\n\n/**\n * Recover a crashed agent by restarting it with context\n */\nexport async function recoverAgent(\n agentId: string,\n opts: { modelOverride?: string } = {},\n): Promise<AgentState | null> {\n const normalizedId = normalizeAgentId(agentId);\n logAgentLifecycleSync(normalizedId, 'recoverAgent called');\n const state = getAgentStateSync(normalizedId);\n\n if (!state) {\n logAgentLifecycleSync(normalizedId, 'recoverAgent BLOCKED: no state.json');\n return null;\n }\n\n // Runtime state files may lack required fields (PAN-150)\n if (!state.id) state.id = normalizedId;\n const gateDecision = decideResumeGate(getAgentResumeGateBlockReason(state), 'operator-start');\n if (gateDecision.decision === 'block') {\n logAgentLifecycleSync(normalizedId, `recoverAgent BLOCKED: Cannot recover ${normalizedId}: ${gateDecision.reason}. Clear the gate before recovering.`);\n return null;\n }\n const modelOverride = normalizeModelOverrideSync(opts.modelOverride);\n if (modelOverride) {\n state.model = modelOverride;\n logAgentLifecycleSync(normalizedId, `recoverAgent: model overridden → ${modelOverride}`);\n }\n if (!state.workspace || !state.model) {\n const reason = `[agents] Cannot recover ${normalizedId}: state.json missing workspace or model`;\n console.error(reason);\n logAgentLifecycleSync(normalizedId, `recoverAgent BLOCKED: ${reason}`);\n return null;\n }\n\n const recoveryRole: Role = state.role\n ?? (normalizedId.startsWith('planning-') ? 'plan' : 'work');\n try {\n await assertWorkspaceStackHealthyForSpawn(\n state.issueId || normalizedId.replace(/^agent-/, '').toUpperCase(),\n recoveryRole,\n state.hostOverride === true,\n state.workspace,\n );\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n logAgentLifecycleSync(normalizedId, `recoverAgent BLOCKED: ${reason}`);\n return null;\n }\n\n // Check if already running — session may exist with only a bare shell\n // after Claude exited (zombie session). Kill it and recover.\n if (sessionExistsSync(normalizedId)) {\n const recoveryHarness: RuntimeName = normalizeHarness(state.harness ?? null) ?? 'claude-code';\n if (await hasAgentRuntimeInSession(normalizedId, recoveryHarness)) {\n return state;\n }\n console.log(`[agents] ${normalizedId} tmux session is a zombie (no ${recoveryHarness} runtime) — killing and recovering`);\n try { killSessionSync(normalizedId); } catch { /* ignore */ }\n }\n\n // Update crash count in health file\n const healthFile = join(getAgentDir(normalizedId), 'health.json');\n let health = { consecutiveFailures: 0, killCount: 0, recoveryCount: 0 };\n if (existsSync(healthFile)) {\n try {\n health = { ...health, ...JSON.parse(readFileSync(healthFile, 'utf-8')) };\n } catch {}\n }\n health.recoveryCount = (health.recoveryCount || 0) + 1;\n writeFileSync(healthFile, JSON.stringify(health, null, 2));\n\n // Build recovery prompt\n const recoveryPrompt = generateRecoveryPrompt(state);\n\n // Get provider env for the agent's model (reads latest API key from settings)\n const providerEnv = state.model ? await getProviderEnvForModel(state.model) : {};\n\n // For credential-file providers, ensure apiKeyHelper is configured.\n // For all other providers, clear stale apiKeyHelper from previous runs.\n if (state.model) {\n const provider = getProviderForModelSync(state.model as ModelId);\n if (provider.authType === 'credential-file') {\n setupCredentialFileAuthSync(provider, state.workspace);\n } else {\n clearCredentialFileAuthSync(state.workspace);\n }\n }\n\n // Restart the agent with recovery context. PAN-1048 C4: derive the role from\n // the saved AgentState (or the session-id heuristic for legacy planning-* IDs)\n // and route through getRoleRuntimeBaseCommand so review/test/ship don't get\n // resurrected as work agents.\n const recoveryHarness: RuntimeName = normalizeHarness(state.harness ?? null) ?? 'claude-code';\n const recoverySupervisorLaunch = await prepareSupervisorForRelaunch(normalizedId, state, state.model, recoveryHarness);\n saveAgentStateSync(state);\n\n if (recoveryHarness === 'ohmypi') {\n // PAN-1055: ohmypi cannot consume the recovery prompt as a positional shell\n // argument the way the Claude direct command path does — ohmypi reads JSONL\n // commands from its FIFO. Build a real ohmypi launcher (extension path,\n // --session-dir, FIFO redirect) via buildAgentLaunchConfig, then deliver\n // the recovery prompt through the FIFO once omp reports ready.\n const { launcherContent, providerEnv: piProviderEnv } = await buildAgentLaunchConfig({\n agentId: normalizedId,\n model: state.model,\n workspace: state.workspace,\n role: recoveryRole,\n isPlanning: recoveryRole === 'plan',\n harness: 'ohmypi',\n });\n const launcherScript = join(getAgentDir(normalizedId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, launcherContent);\n await Effect.runPromise(createSession(normalizedId, state.workspace, `bash ${launcherScript}`, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: state.issueId || '',\n OVERDECK_SESSION_TYPE: recoveryRole,\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n ...piProviderEnv,\n },\n }));\n try {\n await writeOhmypiAgentPrompt(normalizedId, recoveryPrompt);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[recoverAgent] ohmypi recovery prompt delivery failed for ${normalizedId}: ${msg}`);\n }\n markAgentRunning(state);\n saveAgentStateSync(state);\n logAgentLifecycleSync(normalizedId, `recoverAgent SUCCESS: recoveryCount=${health.recoveryCount} (ohmypi)`);\n return state;\n }\n\n const recoveryCodexFields = recoveryHarness === 'codex'\n ? getCodexLauncherFields(normalizedId, state.model, state.workspace)\n : {};\n const recoveryLauncherContent = generateLauncherScriptSync({\n role: recoveryRole,\n workingDir: state.workspace,\n changeDir: false,\n setTerminalEnv: true,\n providerExports: (await getProviderExportsForModel(state.model)).trimEnd(),\n baseCommand: await getRoleRuntimeBaseCommand(state.model, normalizedId, recoveryRole, recoveryHarness),\n appendSystemPromptFiles: await claudeSystemPromptFiles(state.workspace, recoveryHarness),\n ...(recoveryHarness === 'codex' ? {} : { promptInline: recoveryPrompt }),\n useSupervisor: recoverySupervisorLaunch.useSupervisor,\n supervisorScriptPath: recoverySupervisorLaunch.supervisorScriptPath,\n ...recoveryCodexFields,\n });\n const launcherScript = join(getAgentDir(normalizedId), 'launcher.sh');\n await writeLauncherScriptAtomic(launcherScript, recoveryLauncherContent);\n createSessionSync(normalizedId, state.workspace, `bash ${launcherScript}`, {\n env: {\n ...BLANKED_PROVIDER_ENV,\n OVERDECK_AGENT_ID: normalizedId,\n OVERDECK_ISSUE_ID: state.issueId || '',\n OVERDECK_SESSION_TYPE: state.role ?? (normalizedId.startsWith('planning-') ? 'plan' : 'work'),\n CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION: 'false',\n ...providerEnv\n }\n });\n\n saveAgentStateSync(state);\n if (recoveryHarness === 'codex') {\n const delivery = await deliverInitialPromptWithRetry(normalizedId, recoveryPrompt, 'recoverAgent:recovery-prompt', state.deliveryMethod);\n if (!delivery.ok) {\n console.error(`[recoverAgent] Codex recovery prompt delivery failed for ${normalizedId}: ${delivery.failure ?? 'unknown failure'}`);\n }\n }\n // Update state\n markAgentRunning(state);\n saveAgentStateSync(state);\n\n logAgentLifecycleSync(normalizedId, `recoverAgent SUCCESS: recoveryCount=${health.recoveryCount}`);\n return state;\n}\n\n/**\n * Generate a recovery prompt for a crashed agent\n */\nfunction generateRecoveryPrompt(state: AgentState): string {\n const lines: string[] = [\n '# Agent Recovery',\n '',\n '⚠️ This agent session was recovered after a crash.',\n '',\n '## Previous Context',\n `- Issue: ${state.issueId}`,\n `- Workspace: ${state.workspace}`,\n `- Started: ${state.startedAt}`,\n '',\n '## Recovery Steps',\n '1. Check beads for context: `bd show ' + state.issueId + '`',\n '2. Review recent git commits: `git log --oneline -10`',\n '3. Check hook for pending work: `pan admin fpp check`',\n '4. Resume from last known state',\n '',\n '## FPP Reminder',\n '> \"Any runnable action is a fixed point and must resolve before the system can rest.\"',\n '',\n ];\n\n // Add FPP work if available\n const { hasWork } = checkHookSync(state.id);\n if (hasWork) {\n const fixedPointPrompt = generateFixedPointPromptSync(state.id);\n if (fixedPointPrompt) {\n lines.push('---');\n lines.push('');\n lines.push(fixedPointPrompt);\n }\n }\n\n return lines.join('\\n');\n}\n\n/**\n * Auto-recover all crashed agents\n */\nexport async function autoRecoverAgents(): Promise<{ recovered: string[]; failed: string[] }> {\n const crashed = detectCrashedAgents();\n const recovered: string[] = [];\n const failed: string[] = [];\n\n for (const agent of crashed) {\n try {\n const result = await recoverAgent(agent.id);\n if (result) {\n recovered.push(agent.id);\n } else {\n failed.push(agent.id);\n }\n } catch (error) {\n failed.push(agent.id);\n }\n }\n\n return { recovered, failed };\n}\n","import {\n compileGlob,\n getDispatchableItems,\n groupItemsByWave,\n hasFileOverlap,\n type CompiledGlob,\n type Wave,\n} from './dag.js';\nimport { Effect } from 'effect';\nimport type { FsError } from '../errors.js';\nimport { findSpecByIssue } from '../pan-dir/specs.js';\nimport type { FilesScopeConfidence, ItemReadiness, VBriefDocument, VBriefItem } from './types.js';\n\nexport interface SwarmReadinessOptions {\n hotspots?: string[];\n}\n\nexport interface SwarmReadinessOverlap {\n itemId: string;\n sharedFiles: string[];\n}\n\nexport interface SwarmReadinessItemVerdict {\n id: string;\n readiness?: ItemReadiness;\n slotEligible: boolean;\n scopeConfidence?: FilesScopeConfidence;\n missingScope: boolean;\n overlaps: SwarmReadinessOverlap[];\n}\n\nexport interface SwarmReadinessConflictGroup {\n itemIds: string[];\n sharedFiles: string[];\n reason: 'file_overlap' | 'low_confidence';\n}\n\nexport interface SwarmReadinessVerdict {\n items: SwarmReadinessItemVerdict[];\n waves: Wave[];\n conflictGroups: SwarmReadinessConflictGroup[];\n overlapMatrix: Record<string, Record<string, string[]>>;\n swarmEligible: boolean;\n}\n\nexport function computeIssueFootprint(doc: VBriefDocument): string[] {\n const footprint = new Set<string>();\n for (const item of doc.plan.items) {\n for (const filePath of item.metadata?.files_scope ?? []) {\n footprint.add(filePath);\n }\n }\n return Array.from(footprint).sort();\n}\n\nexport function resolveIssueFootprint(\n projectRoot: string,\n issueId: string,\n): Effect.Effect<string[], FsError> {\n return Effect.gen(function* () {\n const spec = yield* findSpecByIssue(projectRoot, issueId);\n return spec ? computeIssueFootprint(spec.document) : [];\n });\n}\n\ninterface NormalizedItem {\n item: VBriefItem;\n scope: string[];\n lowConfidence: boolean;\n missingScope: boolean;\n}\n\nexport function analyzeSwarmReadiness(\n doc: VBriefDocument,\n opts: SwarmReadinessOptions = {},\n): SwarmReadinessVerdict {\n const hotspots = (opts.hotspots ?? []).map(compileGlob);\n const normalizedItems = doc.plan.items.map(item => normalizeItem(item, hotspots));\n const normalizedById = new Map(normalizedItems.map(item => [item.item.id, item]));\n const itemVerdicts = new Map<string, SwarmReadinessItemVerdict>();\n const overlapMatrix = Object.fromEntries(\n doc.plan.items.map(item => [item.id, {} as Record<string, string[]>]),\n );\n const conflictGroups: SwarmReadinessConflictGroup[] = [];\n\n for (const item of doc.plan.items) {\n itemVerdicts.set(item.id, {\n id: item.id,\n readiness: item.metadata?.readiness,\n slotEligible: isSlotEligible(item, normalizedById.get(item.id)),\n scopeConfidence: item.metadata?.files_scope_confidence,\n missingScope: normalizedById.get(item.id)?.missingScope ?? true,\n overlaps: [],\n });\n }\n\n for (let i = 0; i < normalizedItems.length; i++) {\n for (let j = i + 1; j < normalizedItems.length; j++) {\n const left = normalizedItems[i]!;\n const right = normalizedItems[j]!;\n const overlap = analyzePairOverlap(left, right);\n if (!overlap.overlaps) continue;\n\n overlapMatrix[left.item.id]![right.item.id] = overlap.sharedFiles;\n overlapMatrix[right.item.id]![left.item.id] = overlap.sharedFiles;\n itemVerdicts.get(left.item.id)?.overlaps.push({ itemId: right.item.id, sharedFiles: overlap.sharedFiles });\n itemVerdicts.get(right.item.id)?.overlaps.push({ itemId: left.item.id, sharedFiles: overlap.sharedFiles });\n conflictGroups.push({\n itemIds: [left.item.id, right.item.id],\n sharedFiles: overlap.sharedFiles,\n reason: overlap.lowConfidence ? 'low_confidence' : 'file_overlap',\n });\n }\n }\n\n const dispatchableIds = new Set(getDispatchableItems(doc, new Set()).map(item => item.id));\n return {\n items: doc.plan.items.map(item => itemVerdicts.get(item.id)!),\n waves: groupItemsByWave(doc),\n conflictGroups,\n overlapMatrix,\n swarmEligible: Array.from(itemVerdicts.values()).some(item => item.slotEligible && dispatchableIds.has(item.id)),\n };\n}\n\nfunction normalizeItem(item: VBriefItem, hotspots: CompiledGlob[]): NormalizedItem {\n const rawScope = item.metadata?.files_scope ?? [];\n return {\n item,\n scope: rawScope.filter(filePath => !pathMatchesAnyCompiled(filePath, hotspots)),\n lowConfidence: item.metadata?.files_scope_confidence === 'low',\n missingScope: rawScope.length === 0,\n };\n}\n\nfunction isSlotEligible(item: VBriefItem, normalized?: NormalizedItem): boolean {\n if (!normalized || normalized.missingScope || normalized.lowConfidence) return false;\n return item.metadata?.readiness === 'ready'\n && normalized.scope.length > 0\n && (item.metadata.verify_commands?.length ?? 0) > 0\n && (item.metadata.expected_outputs?.length ?? 0) > 0;\n}\n\nfunction analyzePairOverlap(\n left: NormalizedItem,\n right: NormalizedItem,\n): { overlaps: boolean; sharedFiles: string[]; lowConfidence: boolean } {\n if (left.lowConfidence || right.lowConfidence) {\n return { overlaps: true, sharedFiles: sharedFilesFor(left, right), lowConfidence: true };\n }\n\n const leftItem = withScope(left.item, left.scope);\n const rightItem = withScope(right.item, right.scope);\n if (!hasFileOverlap([leftItem], rightItem)) {\n return { overlaps: false, sharedFiles: [], lowConfidence: false };\n }\n\n return { overlaps: true, sharedFiles: sharedFilesFor(left, right), lowConfidence: false };\n}\n\nfunction withScope(item: VBriefItem, scope: string[]): VBriefItem {\n return {\n ...item,\n metadata: {\n ...(item.metadata ?? {}),\n files_scope: scope,\n },\n };\n}\n\nfunction sharedFilesFor(left: NormalizedItem, right: NormalizedItem): string[] {\n const leftPatterns = left.scope.map(compileGlob);\n const rightPatterns = right.scope.map(compileGlob);\n const shared = new Set<string>();\n\n for (const filePath of left.scope) {\n if (pathMatchesAnyCompiled(filePath, rightPatterns)) shared.add(filePath);\n }\n for (const filePath of right.scope) {\n if (pathMatchesAnyCompiled(filePath, leftPatterns)) shared.add(filePath);\n }\n\n return Array.from(shared).sort();\n}\n\nfunction pathMatchesAnyCompiled(filePath: string, patterns: CompiledGlob[]): boolean {\n return patterns.some(pattern => pattern.regex.test(filePath) || pattern.exactDirectory === filePath);\n}\n","import { exec } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { readIssueRecordForWorkspaceSync } from '../pan-dir/record.js';\nimport { findProjectByPathSync, getProjectSwarmHotspots } from '../projects.js';\nimport { analyzeSwarmReadiness } from '../vbrief/swarm-readiness.js';\nimport type { VBriefDocument } from '../vbrief/types.js';\nimport { listAgentStates } from './queries.js';\nimport type { AgentState } from './agent-state.js';\n\nconst execAsync = promisify(exec);\n\nexport type ReconciledSlotItemStatus = 'merged' | 'in_flight' | 'pending';\n\nexport interface ReconciledSlotBranch {\n slotIndex: number;\n branch: string;\n merged: boolean;\n}\n\nexport interface ReconciledSlotAgent {\n slotIndex: number;\n agentId: string;\n status: AgentState['status'];\n slotItemId?: string;\n}\n\nexport interface ReconciledSlotAssignment {\n slotIndex: number;\n itemId: string;\n agentId?: string;\n branch?: string;\n}\n\nexport interface ReconciledSlotItem {\n itemId: string;\n slotIndex: number;\n status: ReconciledSlotItemStatus;\n branch?: string;\n agentId?: string;\n}\n\nexport interface SlotReconcileResult {\n issueId: string;\n merged: ReconciledSlotItem[];\n inFlight: ReconciledSlotItem[];\n pending: ReconciledSlotItem[];\n branches: ReconciledSlotBranch[];\n agents: ReconciledSlotAgent[];\n superseded?: NonNullable<NonNullable<ReturnType<typeof readIssueRecordForWorkspaceSync>>['swarm']>['supersededAttempts'];\n}\n\nexport interface SlotReconcileDeps {\n listBranches: (issueId: string, workspace: string) => Promise<ReconciledSlotBranch[]>;\n listAgents: (issueId: string) => ReconciledSlotAgent[];\n listSlotAssignments: (issueId: string, workspace: string) => ReconciledSlotAssignment[];\n}\n\nexport interface SlotReconcileOptions {\n statusOverrides?: Record<string, string>;\n deps?: Partial<SlotReconcileDeps>;\n}\n\nexport async function reconcileSlotState(\n issueId: string,\n workspace: string,\n doc: VBriefDocument,\n options: SlotReconcileOptions = {},\n): Promise<SlotReconcileResult> {\n const deps: SlotReconcileDeps = {\n listBranches: listSlotBranches,\n listAgents: listSlotAgents,\n listSlotAssignments,\n ...options.deps,\n };\n const branches = await deps.listBranches(issueId, workspace);\n const agents = deps.listAgents(issueId);\n const assignments = deps.listSlotAssignments(issueId, workspace);\n const branchesBySlot = new Map(branches.map(branch => [branch.slotIndex, branch]));\n const agentsBySlot = new Map(agents.map(agent => [agent.slotIndex, agent]));\n const hotspots = getProjectSwarmHotspots(findProjectByPathSync(workspace));\n const slotEligibleItemIds = new Set(analyzeSwarmReadiness(doc, { hotspots }).items\n .filter(item => item.slotEligible)\n .map(item => item.id));\n const slotItems = resolveSlotItemOwnership(slotEligibleItemIds, assignments, agents);\n\n const result: SlotReconcileResult = {\n issueId,\n merged: [],\n inFlight: [],\n pending: [],\n branches,\n agents,\n superseded: readIssueRecordForWorkspaceSync(workspace, issueId.toUpperCase())?.swarm?.supersededAttempts ?? [],\n };\n\n for (const slotItem of slotItems) {\n const branch = branchesBySlot.get(slotItem.slotIndex);\n const agent = agentsBySlot.get(slotItem.slotIndex);\n const merged = options.statusOverrides?.[slotItem.itemId] === 'completed' || branch?.merged === true;\n const entry: ReconciledSlotItem = {\n ...slotItem,\n status: merged ? 'merged' : agent || branch ? 'in_flight' : 'pending',\n branch: branch?.branch,\n agentId: agent?.agentId,\n };\n\n if (entry.status === 'merged') result.merged.push(entry);\n else if (entry.status === 'in_flight') result.inFlight.push(entry);\n else result.pending.push(entry);\n }\n\n return result;\n}\n\nexport async function listSlotBranches(issueId: string, workspace: string): Promise<ReconciledSlotBranch[]> {\n const issueLower = issueId.toLowerCase();\n const pattern = `feature/${issueLower}-slot-*`;\n const [allBranches, mergedBranches] = await Promise.all([\n gitBranchNames(workspace, pattern, false),\n gitBranchNames(workspace, pattern, true),\n ]);\n const mergedSet = new Set(mergedBranches);\n return allBranches\n .map(branch => ({ branch, slotIndex: slotIndexFromBranch(issueLower, branch), merged: mergedSet.has(branch) }))\n .filter((branch): branch is ReconciledSlotBranch => branch.slotIndex !== null)\n .sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nexport function listSlotAgents(issueId: string): ReconciledSlotAgent[] {\n const issueLower = issueId.toLowerCase();\n const pattern = new RegExp(`^agent-${escapeRegExp(issueLower)}-slot-(\\\\d+)$`);\n return listAgentStates({ role: 'work' })\n .map(agent => {\n const match = pattern.exec(agent.id);\n if (!match) return null;\n const entry: ReconciledSlotAgent = {\n slotIndex: Number(match[1]),\n agentId: agent.id,\n status: agent.status,\n };\n if (agent.slotItemId) entry.slotItemId = agent.slotItemId;\n return entry;\n })\n .filter((agent): agent is ReconciledSlotAgent => agent !== null)\n .sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nexport function listSlotAssignments(issueId: string, workspace: string): ReconciledSlotAssignment[] {\n const record = readIssueRecordForWorkspaceSync(workspace, issueId.toUpperCase());\n return (record?.swarm?.slotAssignments ?? [])\n .filter(assignment => Number.isInteger(assignment.slotIndex) && assignment.slotIndex > 0 && assignment.itemId.trim().length > 0)\n .map(assignment => ({\n slotIndex: assignment.slotIndex,\n itemId: assignment.itemId,\n agentId: assignment.agentId,\n branch: assignment.branch,\n }))\n .sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nexport function listSlotOwnership(issueId: string, workspace: string): ReconciledSlotAssignment[] {\n const byItemId = new Map<string, ReconciledSlotAssignment>();\n for (const assignment of listSlotAssignments(issueId, workspace)) {\n byItemId.set(assignment.itemId, assignment);\n }\n for (const agent of listSlotAgents(issueId)) {\n if (!agent.slotItemId || byItemId.has(agent.slotItemId)) continue;\n byItemId.set(agent.slotItemId, {\n slotIndex: agent.slotIndex,\n itemId: agent.slotItemId,\n agentId: agent.agentId,\n });\n }\n return [...byItemId.values()].sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nasync function gitBranchNames(workspace: string, pattern: string, merged: boolean): Promise<string[]> {\n const { stdout } = await execAsync(\n `git branch ${merged ? '--merged HEAD ' : ''}--list ${JSON.stringify(pattern)}`,\n { cwd: workspace },\n );\n return stdout\n .split('\\n')\n .map(line => line.replace(/^[*+\\s]+/, '').trim())\n .filter(Boolean);\n}\n\nfunction resolveSlotItemOwnership(\n slotEligibleItemIds: Set<string>,\n assignments: ReconciledSlotAssignment[],\n agents: ReconciledSlotAgent[],\n): Array<{ itemId: string; slotIndex: number }> {\n const ownership = new Map<string, number>();\n\n for (const assignment of assignments) {\n if (!slotEligibleItemIds.has(assignment.itemId)) continue;\n ownership.set(assignment.itemId, assignment.slotIndex);\n }\n\n for (const agent of agents) {\n if (!agent.slotItemId || !slotEligibleItemIds.has(agent.slotItemId) || ownership.has(agent.slotItemId)) continue;\n ownership.set(agent.slotItemId, agent.slotIndex);\n }\n\n return [...ownership.entries()]\n .map(([itemId, slotIndex]) => ({ itemId, slotIndex }))\n .sort((a, b) => a.slotIndex - b.slotIndex);\n}\n\nfunction slotIndexFromBranch(issueLower: string, branch: string): number | null {\n const match = new RegExp(`^feature/${escapeRegExp(issueLower)}-slot-(\\\\d+)$`).exec(branch);\n if (!match) return null;\n return Number(match[1]);\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n","/**\n * ⚠️ RETIRED SCHEDULER (PAN-2397 W3, 2026-07-05) — do not add production callers.\n *\n * StandingTierManager / computeTierRunSchedule shipped with PAN-1791 as a\n * second scheduler alongside the swarm slot allocator and never gained a\n * production caller. The Always Tiered unification made the swarm allocator\n * (deacon-swarm.ts dispatchNextWave + resolveStaffing) THE scheduler.\n *\n * No-loss audit of this module's capabilities at retirement:\n * - lazy per-tier spawn → swarm dispatch waves already spawn lazily\n * - per-bead tier staffing → resolveStaffing (staffing.ts), all spawn paths\n * - cross-bead session reuse → INAPPLICABLE under the swarm's\n * branch-per-item slots (a slot branch carries one bead, merges, is GC'd);\n * adopting it would require a branch-per-tier architecture — a separate,\n * deliberate epic decision, recorded in .pan/drafts/pan-2397.md.\n * - replay on context threshold → tier-replay.ts remains live; its production\n * seam is the slot-recovery/respawn path (PAN-2397 W3b follow-up).\n *\n * computeTierRunSchedule is kept as a pure planning helper (tests cover it);\n * the manager below is test-only until deleted.\n *\n * Original module doc:\n * Standing tier agents for tiered execution (PAN-1791, FR-6 scheduling half).\n *\n * A standing tier agent is a long-lived registered slot — spawned through\n * spawnRun(issueId, 'work', { slotIndex, slotItemId }) exactly like any other\n * registered slot agent (state file + tmux session + dashboard discovery\n * under the issue) — that persists across the many beads of its tier instead\n * of living per-bead. No new agent kind is introduced.\n *\n * The schedule is the plan-time tier-run sequence (PRD §5.6): waves are\n * linearized via groupItemsByWave, each item is annotated with its tier via\n * resolveTier, and consecutive same-tier items are cut into runs at\n * tier-change boundaries. Standing sessions are instantiated lazily — a\n * tier's session spawns when its first run is <=1 run away, not all at\n * start — so a cancelled run never strands sessions it would not have used.\n */\n\nimport type { VBriefDocument, VBriefItem } from '../vbrief/types.js';\nimport { groupItemsByWave } from '../vbrief/dag.js';\nimport { resolveTier, type ResolveTierConfig } from './resolve-tier.js';\nimport type { AgentState } from './agent-state.js';\nimport type { SpawnRunOptions } from './spawn-prep.js';\nimport { spawnRun } from './spawn.js';\n\n/** One tier run: consecutive beads in DAG order that route to the same tier. */\nexport interface TierRun {\n tierName: string;\n beadIds: string[];\n}\n\nexport class StandingTierError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'StandingTierError';\n }\n}\n\n/**\n * Compute the ordered tier-run schedule from a plan and the tier table.\n * Waves are linearized in order (items within a wave in array order), each\n * item resolves to its tier, and a new run starts whenever the tier changes.\n *\n * Unroutable items (no difficulty, unknown tier) propagate resolveTier's\n * named error — scheduling failures are finalize-time errors in the same\n * family as the quality-lint checks, never silent fallbacks.\n */\nexport function computeTierRunSchedule(\n doc: VBriefDocument,\n config: ResolveTierConfig,\n): TierRun[] {\n const itemById = new Map(doc.plan.items.map((item) => [item.id, item]));\n const runs: TierRun[] = [];\n for (const wave of groupItemsByWave(doc)) {\n for (const waveItem of wave.items) {\n const item = itemById.get(waveItem.id);\n if (!item) continue;\n const tier = resolveTier(item, config);\n const currentRun = runs[runs.length - 1];\n if (currentRun && currentRun.tierName === tier.tierName) {\n currentRun.beadIds.push(item.id);\n } else {\n runs.push({ tierName: tier.tierName, beadIds: [item.id] });\n }\n }\n }\n return runs;\n}\n\n/** The exact set of tier names a schedule contains, in first-appearance order. */\nexport function tiersNeededForSchedule(schedule: TierRun[]): string[] {\n const seen = new Set<string>();\n for (const run of schedule) seen.add(run.tierName);\n return Array.from(seen);\n}\n\n/** A spawned standing tier agent tracked in the registry. */\nexport interface StandingTierAgent {\n tierName: string;\n slotIndex: number;\n agentId: string;\n /** The tier's first scheduled bead — the slotItemId its session was spawned for. */\n firstItemId: string;\n}\n\n/**\n * Spawn seam matching spawnRun's registered-slot signature. Injectable so the\n * manager is unit-testable; defaults to spawnRun.\n */\nexport type StandingTierSpawn = (\n issueId: string,\n role: 'work',\n options: SpawnRunOptions,\n) => Promise<AgentState>;\n\nexport interface StandingTierManagerOptions {\n issueId: string;\n schedule: TierRun[];\n /** Injectable for tests. Defaults to spawnRun's registered-slot path. */\n spawn?: StandingTierSpawn;\n /**\n * Slot index allocated to the first standing tier; subsequent tiers take\n * the following indexes. Defaults to 1.\n */\n firstSlotIndex?: number;\n /** Extra prompt appended to each standing slot's kickoff (foreman protocol). */\n prompt?: string;\n}\n\n/**\n * Manages the set of standing tier agents for one issue: which tiers the\n * schedule needs, lazy spawn, a registry keyed by tier name, and bead\n * routing to the standing slot.\n */\n/** The bead currently being implemented by a standing tier agent. */\nexport interface InFlightBead {\n beadId: string;\n tierName: string;\n agentId: string;\n}\n\nexport class StandingTierManager {\n private readonly registry = new Map<string, StandingTierAgent>();\n private nextSlotIndex: number;\n private inFlight: InFlightBead | undefined;\n\n constructor(private readonly options: StandingTierManagerOptions) {\n this.nextSlotIndex = options.firstSlotIndex ?? 1;\n }\n\n /** Exactly the tier names the schedule contains — no tier outside it ever spawns. */\n tiersNeeded(): string[] {\n return tiersNeededForSchedule(this.options.schedule);\n }\n\n /** The standing agent for a tier, if its session has been spawned. */\n getStandingAgent(tierName: string): StandingTierAgent | undefined {\n return this.registry.get(tierName);\n }\n\n /** 0-based index of the first run belonging to a tier, or -1 when absent. */\n firstRunIndexFor(tierName: string): number {\n return this.options.schedule.findIndex((run) => run.tierName === tierName);\n }\n\n /**\n * Lazy instantiation (PRD §5.6): ensure a standing session exists for every\n * scheduled tier whose first run is <=1 run away from currentRunIndex.\n * Already-spawned tiers are reused, never respawned. Returns the agents\n * spawned by this call.\n */\n async ensureStandingTiersForRun(currentRunIndex: number): Promise<StandingTierAgent[]> {\n const spawned: StandingTierAgent[] = [];\n for (const tierName of this.tiersNeeded()) {\n if (this.registry.has(tierName)) continue;\n const firstRun = this.firstRunIndexFor(tierName);\n if (firstRun <= currentRunIndex + 1) {\n spawned.push(await this.spawnStandingTier(tierName));\n }\n }\n return spawned;\n }\n\n /**\n * Ensure the registered slot for a tier exists, spawning the standing\n * session lazily if it does not exist yet, and return that slot's agent id.\n * Throws for a tier the schedule does not contain — the manager never\n * spawns outside the schedule.\n */\n async ensureStandingAgentForTier(tierName: string, bead: Pick<VBriefItem, 'id'>): Promise<string> {\n if (this.firstRunIndexFor(tierName) === -1) {\n throw new StandingTierError(\n `tier '${tierName}' is not in the schedule for ${this.options.issueId}; refusing to route bead '${bead.id}'`,\n );\n }\n const agent = this.registry.get(tierName) ?? await this.spawnStandingTier(tierName);\n return agent.agentId;\n }\n\n /** The bead currently in flight, if any. */\n getInFlightBead(): InFlightBead | undefined {\n return this.inFlight;\n }\n\n /**\n * Foreman dispatch step enforcing the single-implementer invariant: only\n * one implementation agent works a bead at a time — standing tiers share\n * the foreman's worktree, so two in-flight beads would race the same tree.\n * Throws while another bead is in flight; call completeBead after the\n * foreman has staged, committed, and broadcast the result.\n */\n async dispatchBeadToTier(tierName: string, bead: Pick<VBriefItem, 'id'>): Promise<string> {\n if (this.inFlight) {\n throw new StandingTierError(\n `bead '${this.inFlight.beadId}' is still in flight on tier '${this.inFlight.tierName}' for ${this.options.issueId}; `\n + `only one implementation agent works a bead at a time — complete it before dispatching '${bead.id}'`,\n );\n }\n const agentId = await this.ensureStandingAgentForTier(tierName, bead);\n this.inFlight = { beadId: bead.id, tierName, agentId };\n return agentId;\n }\n\n /** Backward-compatible name for callers that already spell the invariant explicitly. */\n async dispatchBeadExclusive(tierName: string, bead: Pick<VBriefItem, 'id'>): Promise<string> {\n return this.dispatchBeadToTier(tierName, bead);\n }\n\n /** Mark the in-flight bead complete (committed + broadcast), freeing dispatch. */\n completeBead(beadId: string): void {\n if (!this.inFlight || this.inFlight.beadId !== beadId) {\n throw new StandingTierError(\n `bead '${beadId}' is not the in-flight bead for ${this.options.issueId}`\n + `${this.inFlight ? ` ('${this.inFlight.beadId}' is)` : ' (nothing is in flight)'}`,\n );\n }\n this.inFlight = undefined;\n }\n\n private async spawnStandingTier(tierName: string): Promise<StandingTierAgent> {\n const firstRun = this.options.schedule.find((run) => run.tierName === tierName);\n const firstItemId = firstRun?.beadIds[0];\n if (!firstItemId) {\n throw new StandingTierError(\n `tier '${tierName}' has no scheduled beads for ${this.options.issueId}; nothing to spawn a standing session for`,\n );\n }\n const slotIndex = this.nextSlotIndex++;\n const spawn = this.options.spawn ?? spawnRun;\n const state = await spawn(this.options.issueId, 'work', {\n slotIndex,\n slotItemId: firstItemId,\n prompt: this.options.prompt,\n });\n const agent: StandingTierAgent = {\n tierName,\n slotIndex,\n agentId: state.id,\n firstItemId,\n };\n this.registry.set(tierName, agent);\n return agent;\n }\n}\n","import type { TierOverridesMap } from '../vbrief/io.js';\nimport type { VBriefDifficulty, VBriefItem } from '../vbrief/types.js';\nimport type { ValidatedEscalationConfig } from './tier-table.js';\nimport { TIERED_EXECUTION_DIFFICULTIES } from './tier-table.js';\n\nexport type EscalationTrigger =\n | { kind: 'supervisor-blocked'; beadId: string; sha: string; attemptsAtCurrentTier?: number }\n | { kind: 'verification-failed'; beadId: string; detail: string; attemptsAtCurrentTier?: number }\n | { kind: 'floundering'; beadId: string; dispatchedAt: string; now: string; attemptsAtCurrentTier?: number };\n\nexport type EscalationAction =\n | { action: 'retry'; attempt: number }\n | { action: 'promote'; from: VBriefDifficulty; to: VBriefDifficulty; reason: string }\n | { action: 'block'; reason: string };\n\nexport interface VerificationFailureEscalationInput {\n bead: Pick<VBriefItem, 'id' | 'metadata'>;\n config: ValidatedEscalationConfig;\n overrides: TierOverridesMap;\n detail: string;\n attemptsAtCurrentTier?: number;\n}\n\nexport interface FlounderingEscalationInput {\n bead: Pick<VBriefItem, 'id' | 'metadata'>;\n config: ValidatedEscalationConfig;\n overrides: TierOverridesMap;\n dispatchedAt: string;\n now: string;\n attemptsAtCurrentTier?: number;\n}\n\nexport function applyEffectiveDifficulty<T extends Pick<VBriefItem, 'id' | 'metadata'>>(\n item: T,\n overrides: TierOverridesMap,\n): T {\n const override = overrides[item.id];\n if (!override) return item;\n\n return {\n ...item,\n metadata: {\n ...(item.metadata ?? {}),\n difficulty: override.effectiveDifficulty,\n },\n };\n}\n\n/**\n * Pure escalation decision engine. It only emits the next action; callers own\n * retry event persistence, tier promotion writes, dispatch, and notifications.\n */\nexport function decideEscalation(\n trigger: EscalationTrigger,\n bead: Pick<VBriefItem, 'id' | 'metadata'>,\n config: ValidatedEscalationConfig,\n overrides: TierOverridesMap,\n): EscalationAction {\n const beadId = trigger.beadId || bead.id;\n const override = overrides[bead.id];\n const currentDifficulty = override?.effectiveDifficulty ?? bead.metadata?.difficulty;\n if (!currentDifficulty) {\n return { action: 'block', reason: `bead ${beadId} has no effective difficulty` };\n }\n\n const promotions = override?.promotions ?? 0;\n if (promotions >= config.max_promotions) {\n return {\n action: 'block',\n reason: `max promotions reached for ${beadId} at ${currentDifficulty}`,\n };\n }\n\n if (currentDifficulty === 'expert') {\n return {\n action: 'block',\n reason: `expert bead ${beadId} cannot promote beyond expert`,\n };\n }\n\n const attemptsAtCurrentTier = trigger.attemptsAtCurrentTier ?? 0;\n if (attemptsAtCurrentTier < config.retries_at_tier) {\n return { action: 'retry', attempt: attemptsAtCurrentTier + 1 };\n }\n\n const fromIndex = TIERED_EXECUTION_DIFFICULTIES.indexOf(currentDifficulty);\n const to = TIERED_EXECUTION_DIFFICULTIES[fromIndex + 1];\n if (!to) {\n return {\n action: 'block',\n reason: `bead ${beadId} cannot promote beyond ${currentDifficulty}`,\n };\n }\n\n return {\n action: 'promote',\n from: currentDifficulty,\n to,\n reason: triggerReason(trigger),\n };\n}\n\nexport function isFloundering(\n dispatchedAtIso: string,\n nowIso: string,\n budgetMinutes?: number,\n): boolean {\n if (budgetMinutes === undefined || budgetMinutes === null) return false;\n if (!Number.isFinite(budgetMinutes) || budgetMinutes <= 0) return false;\n\n const dispatchedAt = Date.parse(dispatchedAtIso);\n const now = Date.parse(nowIso);\n if (!Number.isFinite(dispatchedAt) || !Number.isFinite(now)) return false;\n\n return (now - dispatchedAt) / 60_000 > budgetMinutes;\n}\n\nexport function decideVerificationFailureEscalation(\n input: VerificationFailureEscalationInput,\n): EscalationAction {\n return decideEscalation({\n kind: 'verification-failed',\n beadId: input.bead.id,\n detail: input.detail,\n attemptsAtCurrentTier: input.attemptsAtCurrentTier,\n }, input.bead, input.config, input.overrides);\n}\n\nexport function decideFlounderingEscalation(\n input: FlounderingEscalationInput,\n): EscalationAction | null {\n const effectiveDifficulty = input.overrides[input.bead.id]?.effectiveDifficulty ?? input.bead.metadata?.difficulty;\n const budget = effectiveDifficulty ? input.config.flounder_budget_minutes[effectiveDifficulty] : undefined;\n if (!isFloundering(input.dispatchedAt, input.now, budget)) return null;\n return decideEscalation({\n kind: 'floundering',\n beadId: input.bead.id,\n dispatchedAt: input.dispatchedAt,\n now: input.now,\n attemptsAtCurrentTier: input.attemptsAtCurrentTier,\n }, input.bead, input.config, input.overrides);\n}\n\nfunction triggerReason(trigger: EscalationTrigger): string {\n switch (trigger.kind) {\n case 'supervisor-blocked':\n return `supervisor blocked commit ${trigger.sha}`;\n case 'verification-failed':\n return `verification failed: ${trigger.detail}`;\n case 'floundering':\n return `floundering since ${trigger.dispatchedAt}`;\n }\n}\n","/**\n * Commit feed for standing tier agents (PAN-1791, FR-7 / D10 v1).\n *\n * After each commit lands, broadcastCommit composes one ingestion-only feed\n * message (bead title + `git show <sha>` diff) and delivers it to EVERY\n * standing tier agent through deliverAgentMessage — the existing delivery\n * primitive, no new transport. v1 is everyone-hears-everything: no per-tier\n * relevance filtering, so any agent's feed is reconstructible from\n * `git log <base>..HEAD` plus the standing-tier set — exactly what replay\n * (tier-replay) consumes to rebuild a respawned session's feed.\n *\n * Feed messages are ingestion-only: the message text instructs the recipient\n * not to respond, so per-event output cost stays ~0 (NFR-1).\n */\n\nimport { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { deliverAgentMessage, type DeliveryResult } from './delivery.js';\nimport type { StandingTierAgent } from './standing-tiers.js';\nimport { estimateFeedDeliveryTokens, recordTierFeedDelivery } from './tier-metrics.js';\nimport type { ValidatedTieredExecutionFeedConfig } from './tier-table.js';\nimport { DEFAULT_TIERED_EXECUTION_CONFIG } from './tier-table.js';\n\nconst execFileAsync = promisify(execFile);\n\nconst DEFAULT_FEED_CONFIG = DEFAULT_TIERED_EXECUTION_CONFIG.feed;\n\nexport interface BroadcastCommitOptions {\n /** Workspace (or any checkout) whose git history contains the commit. */\n workspace: string;\n /** The landed commit to broadcast. */\n sha: string;\n /** Title of the bead the commit implements, shown in the feed header. */\n beadTitle: string;\n /** Id of the bead the commit implements, required for call-out corroboration. */\n beadId?: string;\n /** Commit subject used by feed.exclude_subjects. Defaults to beadTitle for legacy callers. */\n commitSubject?: string;\n /** The standing tier agents to deliver to — every one of them hears it. */\n tiers: Array<Pick<StandingTierAgent, 'tierName' | 'agentId'>>;\n /** Issue id for delivery metrics. */\n issueId?: string;\n /** Dashboard API base URL used in the listener call-out curl snippet. */\n apiUrl?: string;\n /** Feed filtering/rendering config. Defaults preserve today's raw git-show behavior. */\n feedConfig?: ValidatedTieredExecutionFeedConfig;\n /** Injectable delivery seam for tests. Defaults to deliverAgentMessage. */\n deliver?: (agentId: string, message: string, caller?: string) => Promise<DeliveryResult>;\n /** Legacy injectable `git show` runner for tests. Defaults to the shared renderer. */\n gitShow?: (workspace: string, sha: string) => Promise<string>;\n /** Injectable shared renderer seam for tests. Defaults to renderCommitFeedDiff. */\n renderDiff?: (workspace: string, sha: string, feedConfig: ValidatedTieredExecutionFeedConfig) => Promise<string>;\n /** Injectable metric recorder for tests. Defaults to the tier metrics JSONL log. */\n recordDelivery?: typeof recordTierFeedDelivery;\n /** Injectable clock for deterministic delivery metric tests. */\n now?: () => Date;\n}\n\nexport interface BroadcastDelivery {\n tierName: string;\n agentId: string;\n result: DeliveryResult;\n}\n\nexport interface RenderCommitFeedDiffDeps {\n gitShow?: (workspace: string, sha: string, args: string[]) => Promise<string>;\n}\n\nexport interface CommitFeedCalloutContext {\n apiUrl: string;\n issueId: string;\n beadId: string;\n tierName: string;\n agentId: string;\n}\n\nasync function runGitShow(workspace: string, sha: string, args: string[] = []): Promise<string> {\n const { stdout } = await execFileAsync('git', ['show', sha, ...args], {\n cwd: workspace,\n encoding: 'utf-8',\n maxBuffer: 16 * 1024 * 1024,\n });\n return stdout;\n}\n\nexport function shouldSkipFeedSubject(\n subject: string,\n feedConfig: Pick<ValidatedTieredExecutionFeedConfig, 'exclude_subjects'>,\n): boolean {\n return feedConfig.exclude_subjects.some(prefix => subject.startsWith(prefix));\n}\n\nexport async function renderCommitFeedDiff(\n workspace: string,\n sha: string,\n feedConfig: ValidatedTieredExecutionFeedConfig = DEFAULT_FEED_CONFIG,\n deps: RenderCommitFeedDiffDeps = {},\n): Promise<string> {\n const gitShow = deps.gitShow ?? runGitShow;\n const pathspecArgs = feedConfig.exclude.length > 0\n ? ['--', '.', ...feedConfig.exclude.map(glob => `:(exclude)${glob}`)]\n : [];\n const diff = await gitShow(workspace, sha, pathspecArgs);\n const maxBytes = feedConfig.max_diff_bytes;\n if (maxBytes === null || Buffer.byteLength(diff, 'utf-8') <= maxBytes) return diff;\n\n const stat = await gitShow(workspace, sha, ['--stat', ...pathspecArgs]);\n return [\n stat.trimEnd(),\n '',\n `[tiered_execution.feed.max_diff_bytes truncated commit diff: ${Buffer.byteLength(diff, 'utf-8')} bytes exceeded ${maxBytes} bytes]`,\n '',\n ].join('\\n');\n}\n\nexport function resolveFeedApiUrl(env: NodeJS.ProcessEnv = process.env): string {\n return env.OVERDECK_DASHBOARD_URL\n ?? env.DASHBOARD_URL\n ?? `http://localhost:${env.API_PORT ?? env.PORT ?? '3011'}`;\n}\n\n/**\n * Compose the ingestion-only feed message for one commit. Deterministic over\n * (sha, beadTitle, diff) so replay reconstructs byte-identical messages.\n */\nexport function composeCommitFeedMessage(\n sha: string,\n beadTitle: string,\n diff: string,\n callout?: CommitFeedCalloutContext,\n): string {\n const lines = [\n `# Commit feed (ingestion-only): ${sha}`,\n '',\n `Bead: ${beadTitle}`,\n '',\n ];\n\n if (callout) {\n const body = JSON.stringify({\n issueId: callout.issueId,\n sha,\n beadId: callout.beadId,\n tierName: callout.tierName,\n agentId: callout.agentId,\n claim: '<one sentence: what is wrong and where>',\n });\n lines.push(\n 'This is an ingestion-only feed delivery. Read the diff below to stay',\n 'current with work landing on this issue. By default: do NOT respond, do',\n 'NOT take any action, do NOT produce output — wait for your next dispatch.',\n '',\n 'ONE exception. If you believe this commit is WRONG — it misreads the bead\\'s',\n 'spec, hollows out a test, or breaks a stated invariant — you may raise at',\n 'most one call-out, then return to silence:',\n '',\n ` curl -X POST ${callout.apiUrl}/api/tiered/callouts \\\\`,\n ' -H \"Content-Type: application/json\" \\\\',\n ` -d '${body}'`,\n '',\n 'Do not fix it yourself. Do not edit files. A call-out is a flag, not a task.',\n );\n } else {\n lines.push(\n 'This is an ingestion-only feed delivery. Read the diff below to stay',\n 'current with work landing on this issue. Do NOT respond to this message,',\n 'do NOT take any action, and do NOT produce output — wait for your next',\n 'dispatch.',\n );\n }\n\n lines.push('', '```diff', diff.trimEnd(), '```');\n return lines.join('\\n');\n}\n\n/**\n * Broadcast one landed commit to every standing tier agent. Sends exactly one\n * delivery per tier (everyone hears everything). A failed delivery to one\n * tier does not block the others; failures surface in the returned results.\n */\nexport async function broadcastCommit(options: BroadcastCommitOptions): Promise<BroadcastDelivery[]> {\n const feedConfig = options.feedConfig ?? DEFAULT_FEED_CONFIG;\n const subject = options.commitSubject ?? options.beadTitle;\n if (shouldSkipFeedSubject(subject, feedConfig)) return [];\n\n const renderDiff = options.renderDiff\n ?? (options.gitShow\n ? (workspace: string, sha: string) => options.gitShow!(workspace, sha)\n : renderCommitFeedDiff);\n const deliver = options.deliver ?? deliverAgentMessage;\n const recordDelivery = options.recordDelivery ?? recordTierFeedDelivery;\n const now = options.now ?? (() => new Date());\n\n const diff = await renderDiff(options.workspace, options.sha, feedConfig);\n const baseMessage = composeCommitFeedMessage(options.sha, options.beadTitle, diff);\n const baseTokenCount = estimateFeedDeliveryTokens(baseMessage);\n const includeCallout = feedConfig.callouts !== 'off' && options.issueId !== undefined && options.beadId !== undefined;\n const apiUrl = options.apiUrl ?? resolveFeedApiUrl();\n\n const deliveries: BroadcastDelivery[] = [];\n for (const tier of options.tiers) {\n const message = includeCallout\n ? composeCommitFeedMessage(options.sha, options.beadTitle, diff, {\n apiUrl,\n issueId: options.issueId!,\n beadId: options.beadId!,\n tierName: tier.tierName,\n agentId: tier.agentId,\n })\n : baseMessage;\n const tokenCount = includeCallout ? estimateFeedDeliveryTokens(message) : baseTokenCount;\n let result: DeliveryResult;\n try {\n result = await deliver(tier.agentId, message, 'tier-feed:broadcastCommit');\n } catch (err) {\n result = {\n ok: false,\n path: 'tmux',\n failure: err instanceof Error ? err.message : String(err),\n };\n }\n await recordDelivery({\n ts: now().toISOString(),\n issueId: options.issueId,\n sha: options.sha,\n beadTitle: options.beadTitle,\n tierName: tier.tierName,\n agentId: tier.agentId,\n tokenCount,\n result,\n });\n deliveries.push({ tierName: tier.tierName, agentId: tier.agentId, result });\n }\n return deliveries;\n}\n","/**\n * Standing supervisor agent for tiered execution (PAN-1791).\n *\n * The supervisor is a long-lived, review-only agent spawned once per issue\n * from the tier table's `supervisor` block. It subscribes to the commit feed\n * according to the configured subscription policy and reviews work at commit\n * boundaries — it NEVER implements. This module owns spawn + subscription\n * only; verdict posting to the inspect-status surface is the next bead\n * (supervisor-verdict-surface).\n *\n * Verdict-surface slice (supervisor-verdict-surface, FR-4):\n *\n * This module carries the supervisor-verdict-surface slice (FR-4): for each\n * subscribed commit, compose a review request containing the commit diff plus\n * the bead's acceptance criteria (and the PRD's traced FR text when the item\n * carries metadata.traces), deliver it to the standing supervisor via the\n * existing deliverAgentMessage primitive (FR-7, no new transport), and\n * instruct the supervisor to land its ack or blocking finding on the EXISTING\n * inspect-status surface (POST /api/specialists/done with specialist\n * \"inspect\") so the foreman's wait-for-verdict path is reused unchanged:\n * a pass persists inspectStatus + saves the checkpoint via onInspectComplete,\n * a failure records a blocking finding and never changes tracker status.\n *\n * Sibling slice note: supervisor spawn + subscription policy\n * (shouldSupervise, item supervisor-subscribe) is owned by a separate bead\n * and lands alongside this module.\n */\n\nimport { execFile } from 'node:child_process';\nimport { readFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\nimport type { VBriefDocument, VBriefEdge, VBriefItem, VBriefSubItem } from '../vbrief/types.js';\nimport type { AgentState } from './agent-state.js';\nimport { deliverAgentMessage } from './delivery.js';\nimport type { DeliveryResult } from './delivery.js';\nimport { runAgentId } from './spawn-prep.js';\nimport { spawnRun } from './spawn.js';\nimport type {\n TieredExecutionSubscription,\n TieredExecutionSupervisorConfig,\n} from './tier-table.js';\n\n/**\n * Sub-role under 'review' for the standing supervisor. Review sub-roles skip\n * the roles/review.md convoy definition injection (roleAgentDefinitionPath\n * returns null for review+subRole), so the supervisor's behavior comes\n * entirely from the prompt delivered here.\n */\nexport const SUPERVISOR_SUB_ROLE = 'supervisor';\n\n/** Default fraction of beads reviewed under the 'sampled' subscription policy. */\nexport const DEFAULT_SUPERVISOR_SAMPLE_RATE = 0.25;\n\nexport function supervisorAgentId(issueId: string): string {\n return runAgentId(issueId, 'review', SUPERVISOR_SUB_ROLE);\n}\n\nexport interface ShouldSuperviseOptions {\n /**\n * Sampling rate in (0, 1] applied under the 'sampled' policy.\n * Defaults to DEFAULT_SUPERVISOR_SAMPLE_RATE.\n */\n sampleRate?: number;\n}\n\n/**\n * Subscription policy: does the supervisor review commits for this bead?\n *\n * - 'all' → every bead.\n * - 'flagged' → only beads with metadata.requiresInspection === true.\n * - 'sampled' → a deterministic per-bead sample at the configured rate.\n */\nexport function shouldSupervise(\n bead: Pick<VBriefItem, 'id' | 'metadata'>,\n policy: TieredExecutionSubscription,\n options: ShouldSuperviseOptions = {},\n): boolean {\n switch (policy) {\n case 'all':\n return true;\n case 'flagged':\n return bead.metadata?.requiresInspection === true;\n case 'sampled': {\n const rate = options.sampleRate ?? DEFAULT_SUPERVISOR_SAMPLE_RATE;\n if (rate <= 0) return false;\n if (rate >= 1) return true;\n return sampleFraction(bead.id) < rate;\n }\n }\n}\n\n/**\n * Deterministic per-bead sample position: FNV-1a over the bead id mapped to\n * [0, 1). Deterministic (never Math.random) so a bead's subscription decision\n * is stable across the live commit feed, replay re-delivery (tier-replay),\n * and tests.\n */\nfunction sampleFraction(beadId: string): number {\n let hash = 0x811c9dc5;\n for (let i = 0; i < beadId.length; i++) {\n hash ^= beadId.charCodeAt(i);\n hash = Math.imul(hash, 0x01000193);\n }\n return (hash >>> 0) / 0x1_0000_0000;\n}\n\nexport interface SpawnTierSupervisorOptions {\n /** Workspace to run in. Defaults to spawnRun's per-issue default workspace. */\n workspace?: string;\n /** Override the generated standing-supervisor kickoff prompt. */\n prompt?: string;\n}\n\n/**\n * Spawn the standing supervisor as a registered role run (agent state +\n * conversation record + tmux session on the overdeck socket), so the\n * dashboard discovers it under its issue like any other specialist.\n *\n * Model comes from the tier table's supervisor block. The configured harness\n * is passed through for the spawn record, but per PAN-1984 harness resolution\n * is provider-default-only — the tier-table validator already guarantees the\n * configured model+harness+auth combination is policy-legal.\n */\nexport async function spawnTierSupervisor(\n issueId: string,\n supervisor: TieredExecutionSupervisorConfig,\n options: SpawnTierSupervisorOptions = {},\n): Promise<AgentState> {\n return spawnRun(issueId, 'review', {\n agentId: supervisorAgentId(issueId),\n subRole: SUPERVISOR_SUB_ROLE,\n model: supervisor.model,\n harness: supervisor.harness,\n workspace: options.workspace,\n prompt: options.prompt ?? buildSupervisorPrompt(issueId, supervisor.subscribe),\n });\n}\n\nexport function buildSupervisorPrompt(\n issueId: string,\n subscribe: TieredExecutionSubscription,\n): string {\n const policyLine = {\n all: 'You are subscribed to EVERY commit in this issue.',\n flagged: 'You are subscribed only to commits for beads flagged requiresInspection=true.',\n sampled: 'You are subscribed to a deterministic sample of the beads in this issue.',\n }[subscribe];\n\n return [\n `# Standing Supervisor: ${issueId}`,\n '',\n 'You are the standing supervisor for tiered execution on this issue. You are a',\n 'review-only agent: you NEVER write, edit, or commit implementation code.',\n '',\n `Subscription policy: ${subscribe}. ${policyLine}`,\n '',\n 'Commit diffs will be delivered to this session as the tier agents land work.',\n 'For each delivered commit, read the diff against the bead\\'s acceptance',\n 'criteria and note whether the work satisfies them. Ingest each delivery and',\n 'wait for the next; do not take action beyond review. The verdict-posting',\n 'surface is wired separately — until it is, record findings in your replies',\n 'to each delivery.',\n '',\n 'Stay resident. Do not exit after a review; the next commit delivery arrives',\n 'in this same session.',\n ].join('\\n');\n}\n\nconst execFileAsync = promisify(execFile);\n\n/** One commit-review event as delivered to the standing supervisor. */\nexport interface SupervisorReviewEvent {\n issueId: string;\n /** Beads-tracker id the verdict must reference (\"Bead <beadId> ...\"). */\n beadId: string;\n beadTitle: string;\n /** Full commit sha being reviewed. */\n sha: string;\n /** Output of `git show <sha>` for the commit. */\n diff: string;\n /** The bead's acceptance-criterion titles, in plan order. */\n acceptanceCriteria: string[];\n /** Traced PRD FR text, present when the item carries metadata.traces. */\n frText?: string;\n /** Dashboard base URL the verdict POST targets. */\n apiUrl: string;\n}\n\nexport interface SupervisorVerdict {\n /** Bead id parsed from the inspect-status notes prefix. */\n beadId: string;\n /** Supervisor ack clears prior blocking findings; failed/blocked records one. */\n status: 'passed' | 'ack' | 'failed' | 'blocked';\n}\n\nexport interface DeliverCommitForReviewOptions {\n /** Agent id of the standing supervisor session. */\n supervisorAgentId: string;\n /** Workspace the commit lives in (git worktree on the feature branch). */\n workspacePath: string;\n issueId: string;\n /** The vBRIEF item the commit implements. */\n item: VBriefItem;\n sha: string;\n /** Beads id for the verdict prefix; defaults to the vBRIEF item id. */\n beadId?: string;\n /**\n * PRD draft markdown to source traced FR text from. When omitted and the\n * item has metadata.traces, callers should pass the result of\n * loadPrdDraft(projectRoot, issueId).\n */\n prdMarkdown?: string;\n apiUrl?: string;\n /** Test seams — default to the real primitives. */\n deps?: {\n deliver?: typeof deliverAgentMessage;\n getDiff?: (workspacePath: string, sha: string) => Promise<string>;\n };\n}\n\nfunction resolveApiUrl(): string {\n return (\n process.env.OVERDECK_DASHBOARD_URL\n || process.env.DASHBOARD_URL\n || `http://localhost:${process.env.API_PORT || process.env.PORT || '3011'}`\n );\n}\n\n/**\n * Does the next bead depend on a bead with an unresolved supervisor block?\n *\n * Verdicts are interpreted chronologically: the latest verdict for each bead is\n * authoritative. A failed/blocked verdict halts dependents until a later\n * passed/ack verdict for that same bead records the fix commit's approval.\n */\nexport function shouldHaltDispatch(\n verdicts: readonly SupervisorVerdict[],\n nextBead: Pick<VBriefItem, 'id'>,\n dag: Pick<VBriefDocument, 'plan'>,\n): boolean {\n const latestByBead = new Map<string, SupervisorVerdict>();\n for (const verdict of verdicts) {\n latestByBead.set(verdict.beadId, verdict);\n }\n\n const unresolvedBlockedBeads = new Set<string>();\n for (const [beadId, verdict] of latestByBead) {\n if (verdict.status === 'failed' || verdict.status === 'blocked') {\n unresolvedBlockedBeads.add(beadId);\n }\n }\n\n if (unresolvedBlockedBeads.size === 0) return false;\n\n for (const dependencyId of dependencyClosure(nextBead.id, dag.plan.edges ?? [])) {\n if (unresolvedBlockedBeads.has(dependencyId)) return true;\n }\n\n return false;\n}\n\nfunction dependencyClosure(itemId: string, edges: readonly VBriefEdge[]): Set<string> {\n const incoming = new Map<string, string[]>();\n for (const edge of edges) {\n if (edge.type !== 'blocks') continue;\n const parents = incoming.get(edge.to) ?? [];\n parents.push(edge.from);\n incoming.set(edge.to, parents);\n }\n\n const dependencies = new Set<string>();\n const stack = [...(incoming.get(itemId) ?? [])];\n while (stack.length > 0) {\n const dependencyId = stack.pop()!;\n if (dependencies.has(dependencyId)) continue;\n dependencies.add(dependencyId);\n stack.push(...(incoming.get(dependencyId) ?? []));\n }\n\n return dependencies;\n}\n\nfunction childItems(item: VBriefItem): VBriefSubItem[] {\n // vBRIEF v0.6 uses `items`; v0.5 documents used `subItems` for the same\n // structure and are still read as a compatibility alias.\n return item.items ?? item.subItems ?? [];\n}\n\n/**\n * Pull the bead's acceptance-criterion titles from its vBRIEF child items\n * (child metadata.kind === 'acceptance_criterion').\n */\nexport function extractAcceptanceCriteria(item: VBriefItem): string[] {\n return childItems(item)\n .filter((child) => child.metadata?.kind === 'acceptance_criterion')\n .map((child) => child.title);\n}\n\n/**\n * Extract the PRD requirement text for the given trace ids (e.g. [\"FR-4\"]).\n *\n * PRD requirement bullets follow the prd-authoring convention:\n * - **FR-4 — Title.** Body text...\n * Each matched requirement is captured from its bullet line through any\n * indented continuation lines, up to the next bullet or heading. Returns\n * undefined when no trace resolves to a requirement in the document.\n */\nexport function extractTracedFrText(prdMarkdown: string, traces: readonly string[]): string | undefined {\n if (traces.length === 0) return undefined;\n const lines = prdMarkdown.split('\\n');\n const sections: string[] = [];\n\n for (const trace of traces) {\n const startPattern = new RegExp(`^\\\\s*[-*]\\\\s+\\\\*\\\\*${trace}\\\\b`);\n const start = lines.findIndex((line) => startPattern.test(line));\n if (start === -1) continue;\n\n const captured = [lines[start]];\n for (let i = start + 1; i < lines.length; i++) {\n const line = lines[i];\n // A new bullet, a heading, or a blank line ends the requirement body.\n if (/^\\s*[-*]\\s/.test(line) || /^#/.test(line) || line.trim() === '') break;\n captured.push(line);\n }\n sections.push(captured.join('\\n'));\n }\n\n return sections.length > 0 ? sections.join('\\n\\n') : undefined;\n}\n\n/**\n * Read the Overdeck-managed PRD draft for an issue\n * (<projectRoot>/.pan/drafts/<ISSUE>.md, falling back to the lowercase\n * filename). Returns undefined when no draft exists.\n */\nexport async function loadPrdDraft(projectRoot: string, issueId: string): Promise<string | undefined> {\n for (const name of [`${issueId.toUpperCase()}.md`, `${issueId.toLowerCase()}.md`]) {\n try {\n return await readFile(join(projectRoot, '.pan', 'drafts', name), 'utf-8');\n } catch {\n // Try the next casing; missing drafts are expected.\n }\n }\n return undefined;\n}\n\n/**\n * Compose the per-commit review request delivered to the standing supervisor.\n *\n * Unlike commit-feed messages this is NOT ingestion-only: the supervisor must\n * respond by posting exactly one verdict to the existing inspect surface.\n */\nexport function buildSupervisorReviewMessage(event: SupervisorReviewEvent): string {\n const shortSha = event.sha.slice(0, 8);\n const criteria = event.acceptanceCriteria.length > 0\n ? event.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join('\\n')\n : '(none recorded on the plan item — review against the bead title)';\n\n const frSection = event.frText\n ? `\\n## Traced requirements (PRD)\\n\\n${event.frText}\\n`\n : '';\n\n return `SUPERVISOR REVIEW REQUEST — commit ${shortSha} on ${event.issueId}\n\nYou are the standing supervisor for ${event.issueId}. Review this commit against the bead's acceptance criteria and respond with a verdict. You never implement — do not edit files, commit, or run write operations.\n\n## Bead under review\n\n- Issue: ${event.issueId}\n- Bead: ${event.beadId} — ${event.beadTitle}\n- Commit: ${event.sha}\n\n## Acceptance criteria\n\n${criteria}\n${frSection}\n## Commit diff (git show ${shortSha})\n\n\\`\\`\\`diff\n${event.diff}\n\\`\\`\\`\n\n## Verdict (REQUIRED — post exactly one)\n\nLand your verdict on the existing inspect-status surface. Your notes MUST begin with \"Bead ${event.beadId}\" — the server extracts the bead id from that prefix.\n\nIf the diff satisfies the acceptance criteria, post an ack (this persists inspectStatus and saves the bead checkpoint):\n\n\\`\\`\\`bash\ncurl -X POST ${event.apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"inspect\",\"issueId\":\"${event.issueId}\",\"status\":\"passed\",\"notes\":\"Bead ${event.beadId} ack: <one-line summary>\"}'\n\\`\\`\\`\n\nIf any acceptance criterion is not met, post a blocking finding with specific, actionable violations ([file:line] + what is wrong):\n\n\\`\\`\\`bash\ncurl -X POST ${event.apiUrl}/api/specialists/done \\\\\n -H \"Content-Type: application/json\" \\\\\n -d '{\"specialist\":\"inspect\",\"issueId\":\"${event.issueId}\",\"status\":\"failed\",\"notes\":\"Bead ${event.beadId} BLOCKED: <specific violations and required fixes>\"}'\n\\`\\`\\`\n\nRules:\n- A blocking finding is recorded on the inspect-status surface only — it does NOT change tracker (Linear/GitHub) status. Never run \\`gh issue close\\` or edit tracker state.\n- Do not use any other endpoint or side channel for the verdict.\n- Only review this commit against this bead's acceptance criteria — compile, lint, and tests are the verification gate's job, and code style is the review convoy's job.`;\n}\n\nasync function getCommitDiff(workspacePath: string, sha: string): Promise<string> {\n const { stdout } = await execFileAsync('git', ['show', sha], {\n cwd: workspacePath,\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n return stdout;\n}\n\n/**\n * Deliver one subscribed commit to the standing supervisor for review:\n * resolve the diff, pull the bead's acceptance criteria from its vBRIEF item\n * (plus traced FR text when metadata.traces is present and a PRD draft was\n * provided), and send the composed review request via deliverAgentMessage.\n */\nexport async function deliverCommitForReview(options: DeliverCommitForReviewOptions): Promise<DeliveryResult> {\n const deliver = options.deps?.deliver ?? deliverAgentMessage;\n const getDiff = options.deps?.getDiff ?? getCommitDiff;\n\n const diff = await getDiff(options.workspacePath, options.sha);\n const traces = Array.isArray(options.item.metadata?.traces)\n ? (options.item.metadata.traces as string[])\n : [];\n const frText = options.prdMarkdown && traces.length > 0\n ? extractTracedFrText(options.prdMarkdown, traces)\n : undefined;\n\n const message = buildSupervisorReviewMessage({\n issueId: options.issueId,\n beadId: options.beadId ?? options.item.id,\n beadTitle: options.item.title,\n sha: options.sha,\n diff,\n acceptanceCriteria: extractAcceptanceCriteria(options.item),\n frText,\n apiUrl: options.apiUrl ?? resolveApiUrl(),\n });\n\n return deliver(options.supervisorAgentId, message, 'tier-supervisor:verdict');\n}\n","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport type { ModelId } from '../settings.js';\nimport type { VBriefDocument, VBriefItem } from '../vbrief/types.js';\nimport type { TierOverridesMap } from '../vbrief/io.js';\nimport { getModelCapabilitySync, hasModelCapabilitySync, resolveModelIdSync } from '../model-capabilities.js';\nimport type { AgentState } from './agent-state.js';\nimport type { DeliveryResult } from './delivery.js';\nimport { deliverAgentMessage } from './delivery.js';\nimport { spawnRun } from './spawn.js';\nimport type { SpawnRunOptions } from './spawn-prep.js';\nimport { listSlotOwnership } from './slot-reconcile.js';\nimport type { InFlightBead } from './standing-tiers.js';\nimport { computeTierRunSchedule, tiersNeededForSchedule } from './standing-tiers.js';\nimport { applyEffectiveDifficulty } from './tier-escalation.js';\nimport {\n composeCommitFeedMessage,\n renderCommitFeedDiff,\n resolveFeedApiUrl,\n shouldSkipFeedSubject,\n} from './tier-feed.js';\nimport {\n buildSupervisorReviewMessage,\n extractAcceptanceCriteria,\n extractTracedFrText,\n shouldSupervise,\n supervisorAgentId as defaultSupervisorAgentId,\n SUPERVISOR_SUB_ROLE,\n} from './tier-supervisor.js';\nimport type { TieredExecutionSupervisorConfig, ValidatedTieredExecutionConfig, ValidatedTieredExecutionFeedConfig } from './tier-table.js';\nimport { DEFAULT_TIERED_EXECUTION_CONFIG } from './tier-table.js';\nimport { stopAgent } from './termination.js';\n\nconst execFileAsync = promisify(execFile);\nconst DEFAULT_REPLAY_THRESHOLD = 0.5;\n\nexport interface ReplayCommit {\n sha: string;\n subject: string;\n diff: string;\n beadId?: string;\n}\n\nexport interface ReplayDelivery {\n sha: string;\n result: DeliveryResult;\n}\n\nexport interface ReplayResult {\n agent: AgentState;\n commits: ReplayCommit[];\n deliveries: ReplayDelivery[];\n}\n\nexport interface ReplayRerouteContext {\n doc: VBriefDocument;\n config: ValidatedTieredExecutionConfig;\n statusOverrides?: Record<string, string>;\n tierOverrides?: TierOverridesMap;\n}\n\nexport interface ReplayTargetBase {\n issueId: string;\n workspace: string;\n /**\n * Base revision for replay. The feed is reconstructed from\n * `git log --reverse <base>..HEAD`.\n */\n base: string;\n /** Existing dead/stale agent id, if known. */\n agentId?: string;\n /** Optional prompt for the fresh standing session. */\n prompt?: string;\n /** Feed filtering/rendering config. Defaults preserve today's raw git-show behavior. */\n feedConfig?: ValidatedTieredExecutionFeedConfig;\n /** Dashboard API base URL used when replaying callout-enabled tier feed messages. */\n apiUrl?: string;\n /** Optional current scheduling state used only when compaction_reroute is on. */\n reroute?: ReplayRerouteContext;\n}\n\nexport interface ReplayStandingTierTarget extends ReplayTargetBase {\n kind: 'tier';\n tierName: string;\n /** Registered standing slot index. Resolved from slot-reconcile when omitted. */\n slotIndex?: number;\n /** First item assigned to the standing slot. Resolved from slot-reconcile when omitted. */\n slotItemId?: string;\n}\n\nexport interface ReplaySupervisorTarget extends ReplayTargetBase {\n kind: 'supervisor';\n supervisor: TieredExecutionSupervisorConfig;\n /** Plan used to apply the supervisor subscription policy to replay commits. */\n doc: VBriefDocument;\n /** PRD markdown used to replay traced requirement text, when available. */\n prdMarkdown?: string;\n apiUrl?: string;\n}\n\nexport type ReplayTarget = ReplayStandingTierTarget | ReplaySupervisorTarget;\n\nexport interface TierReplayDeps {\n spawn?: typeof spawnRun;\n deliver?: typeof deliverAgentMessage;\n stop?: typeof stopAgent;\n gitLog?: (workspace: string, base: string) => Promise<Array<{ sha: string; subject: string }>>;\n gitShow?: (workspace: string, sha: string) => Promise<string>;\n renderDiff?: (workspace: string, sha: string, feedConfig: ValidatedTieredExecutionFeedConfig) => Promise<string>;\n listSlotOwnership?: typeof listSlotOwnership;\n}\n\nexport interface TierReplayOptions {\n deps?: TierReplayDeps;\n}\n\nexport interface TierRunCompactionInput {\n /** True only between tier runs. Compaction is forbidden inside a bead. */\n atRunBoundary: boolean;\n /** Any in-flight bead blocks compaction, even if the caller says it is at a boundary. */\n inFlightBead?: InFlightBead;\n estimatedContextTokens: number;\n modelContextWindow: number;\n replayThreshold?: number;\n}\n\nexport interface TierRunCompactionOptions {\n target: ReplayTarget;\n compaction: TierRunCompactionInput;\n deps?: TierReplayDeps;\n}\n\n/**\n * Replay a dead standing tier agent or standing supervisor:\n * respawn the registered session and re-deliver its commit feed reconstructed\n * from `git log <base>..HEAD`, oldest commit first.\n */\nexport async function replayStandingAgent(\n target: ReplayTarget,\n options: TierReplayOptions = {},\n): Promise<ReplayResult> {\n const deps = replayDeps(options.deps);\n const replayTarget: ReplayTarget = target.kind === 'tier'\n ? { ...target, ...resolveReplaySlot(target, deps) }\n : target;\n const agent = await spawnReplayTarget(replayTarget, deps);\n const commits = await loadReplayCommits(\n replayTarget.workspace,\n replayTarget.base,\n replayTarget.feedConfig ?? DEFAULT_TIERED_EXECUTION_CONFIG.feed,\n deps,\n );\n const deliveries = replayTarget.kind === 'tier'\n ? await replayTierFeed(agent.id, replayTarget, commits, deps)\n : await replaySupervisorFeed(agent.id, replayTarget, commits, deps);\n\n return { agent, commits, deliveries };\n}\n\n/**\n * Crash/orphan entry point. The caller has already determined the old session\n * is dead; replay respawns and reconstructs the feed without human input.\n */\nexport async function replayCrashedStandingAgent(\n target: ReplayStandingTierTarget & { reroute: ReplayRerouteContext },\n options?: TierReplayOptions,\n): Promise<ReplayResult | null>;\nexport async function replayCrashedStandingAgent(\n target: ReplayTarget,\n options?: TierReplayOptions,\n): Promise<ReplayResult>;\nexport async function replayCrashedStandingAgent(\n target: ReplayTarget,\n options: TierReplayOptions = {},\n): Promise<ReplayResult | null> {\n const rerouted = resolveReroutedReplayTarget(target);\n if (!rerouted) return null;\n return replayStandingAgent(rerouted, options);\n}\n\n/**\n * Threshold-triggered compaction entry point. It only acts at a tier-run\n * boundary and only when no bead is in flight; mid-bead compaction returns\n * `null` by construction.\n */\nexport async function compactAtTierRunBoundary(\n options: TierRunCompactionOptions,\n): Promise<ReplayResult | null> {\n if (!shouldReplayCompactAtTierRunBoundary(options.compaction)) return null;\n const deps = replayDeps(options.deps);\n const rerouted = resolveReroutedReplayTarget(options.target);\n if (!rerouted) {\n if (options.target.agentId) await deps.stop(options.target.agentId);\n return null;\n }\n if (rerouted.agentId) {\n await deps.stop(rerouted.agentId);\n }\n return replayStandingAgent(rerouted, { deps });\n}\n\nexport function shouldReplayCompactAtTierRunBoundary(input: TierRunCompactionInput): boolean {\n if (!input.atRunBoundary) return false;\n if (input.inFlightBead) return false;\n if (!Number.isFinite(input.estimatedContextTokens) || input.estimatedContextTokens < 0) return false;\n if (!Number.isFinite(input.modelContextWindow) || input.modelContextWindow <= 0) return false;\n const threshold = input.replayThreshold ?? DEFAULT_REPLAY_THRESHOLD;\n if (!Number.isFinite(threshold) || threshold <= 0 || threshold > 1) return false;\n return input.estimatedContextTokens / input.modelContextWindow >= threshold;\n}\n\nexport function contextWindowForModel(model: string): number | undefined {\n const resolved = resolveModelIdSync(model);\n if (!hasModelCapabilitySync(resolved)) return undefined;\n return getModelCapabilitySync(resolved as ModelId).contextWindow;\n}\n\nasync function spawnReplayTarget(target: ReplayTarget, deps: Required<TierReplayDeps>): Promise<AgentState> {\n if (target.kind === 'supervisor') {\n return deps.spawn(target.issueId, 'review', {\n agentId: target.agentId ?? defaultSupervisorAgentId(target.issueId),\n subRole: SUPERVISOR_SUB_ROLE,\n model: target.supervisor.model,\n harness: target.supervisor.harness,\n workspace: target.workspace,\n prompt: target.prompt,\n });\n }\n\n const slot = resolveReplaySlot(target, deps);\n const spawnOptions: SpawnRunOptions = {\n slotIndex: slot.slotIndex,\n slotItemId: slot.slotItemId,\n prompt: target.prompt,\n ...currentTierSpawnOverride(target),\n };\n return deps.spawn(target.issueId, 'work', spawnOptions);\n}\n\nfunction resolveReroutedReplayTarget(target: ReplayTarget): ReplayTarget | null {\n if (target.kind !== 'tier') return target;\n const reroute = target.reroute;\n if (!reroute || reroute.config.compaction_reroute === 'off') return target;\n\n const scheduleDoc = remainingScheduleDocument(reroute);\n const schedule = computeTierRunSchedule(scheduleDoc, reroute.config);\n if (!tiersNeededForSchedule(schedule).includes(target.tierName)) return null;\n return target;\n}\n\nfunction remainingScheduleDocument(reroute: ReplayRerouteContext): VBriefDocument {\n const statusOverrides = reroute.statusOverrides ?? {};\n const tierOverrides = reroute.tierOverrides ?? {};\n return {\n ...reroute.doc,\n plan: {\n ...reroute.doc.plan,\n items: reroute.doc.plan.items\n .filter((item) => (statusOverrides[item.id] ?? item.status) !== 'completed')\n .map((item) => applyEffectiveDifficulty(item, tierOverrides)),\n },\n };\n}\n\nfunction currentTierSpawnOverride(target: ReplayStandingTierTarget): Pick<SpawnRunOptions, 'model' | 'harness'> {\n const reroute = target.reroute;\n if (!reroute || reroute.config.compaction_reroute === 'off') return {};\n const tier = reroute.config.tiers[target.tierName];\n if (!tier) return {};\n return { model: tier.model, harness: tier.harness };\n}\n\nfunction resolveReplaySlot(\n target: ReplayStandingTierTarget,\n deps: Required<TierReplayDeps>,\n): { slotIndex: number; slotItemId: string } {\n if (target.slotIndex !== undefined && target.slotItemId !== undefined) {\n return { slotIndex: target.slotIndex, slotItemId: target.slotItemId };\n }\n\n const ownership = deps.listSlotOwnership(target.issueId, target.workspace);\n const match = target.slotItemId\n ? ownership.find((assignment) => assignment.itemId === target.slotItemId)\n : ownership.find((assignment) => assignment.agentId === target.agentId);\n if (!match) {\n throw new Error(\n `Cannot replay standing tier '${target.tierName}' for ${target.issueId}: slot ownership was not found`,\n );\n }\n return { slotIndex: match.slotIndex, slotItemId: match.itemId };\n}\n\nasync function replayTierFeed(\n agentId: string,\n target: ReplayStandingTierTarget,\n commits: ReplayCommit[],\n deps: Required<TierReplayDeps>,\n): Promise<ReplayDelivery[]> {\n const deliveries: ReplayDelivery[] = [];\n const feedConfig = target.feedConfig ?? DEFAULT_TIERED_EXECUTION_CONFIG.feed;\n const includeCallout = feedConfig.callouts !== 'off';\n const apiUrl = target.apiUrl ?? resolveFeedApiUrl();\n for (const commit of commits) {\n const beadId = commit.beadId;\n const message = composeCommitFeedMessage(\n commit.sha,\n commit.subject,\n commit.diff,\n includeCallout && beadId\n ? {\n apiUrl,\n issueId: target.issueId,\n beadId,\n tierName: target.tierName,\n agentId,\n }\n : undefined,\n );\n deliveries.push({\n sha: commit.sha,\n result: await deps.deliver(agentId, message, 'tier-replay:tier'),\n });\n }\n return deliveries;\n}\n\nasync function replaySupervisorFeed(\n agentId: string,\n target: ReplaySupervisorTarget,\n commits: ReplayCommit[],\n deps: Required<TierReplayDeps>,\n): Promise<ReplayDelivery[]> {\n const deliveries: ReplayDelivery[] = [];\n for (const commit of commits) {\n const item = findReplayItem(target.doc, commit);\n if (!item || !shouldSupervise(item, target.supervisor.subscribe)) continue;\n const traces = Array.isArray(item.metadata?.traces) ? item.metadata.traces as string[] : [];\n const frText = target.prdMarkdown && traces.length > 0\n ? extractTracedFrText(target.prdMarkdown, traces)\n : undefined;\n const message = buildSupervisorReviewMessage({\n issueId: target.issueId,\n beadId: item.id,\n beadTitle: item.title,\n sha: commit.sha,\n diff: commit.diff,\n acceptanceCriteria: extractAcceptanceCriteria(item),\n frText,\n apiUrl: target.apiUrl ?? 'http://localhost:3011',\n });\n deliveries.push({\n sha: commit.sha,\n result: await deps.deliver(agentId, message, 'tier-replay:supervisor'),\n });\n }\n return deliveries;\n}\n\nfunction findReplayItem(doc: VBriefDocument, commit: Pick<ReplayCommit, 'subject'>): VBriefItem | undefined {\n return doc.plan.items.find((item) => {\n const subject = commit.subject.toLowerCase();\n return subject.includes(item.id.toLowerCase()) || subject.includes(item.title.toLowerCase());\n });\n}\n\nasync function loadReplayCommits(\n workspace: string,\n base: string,\n feedConfig: ValidatedTieredExecutionFeedConfig,\n deps: Required<TierReplayDeps>,\n): Promise<ReplayCommit[]> {\n const entries = await deps.gitLog(workspace, base);\n const commits: ReplayCommit[] = [];\n for (const entry of entries) {\n if (shouldSkipFeedSubject(entry.subject, feedConfig)) continue;\n commits.push({\n ...entry,\n diff: await deps.renderDiff(workspace, entry.sha, feedConfig),\n });\n }\n return commits;\n}\n\nasync function runGitLog(workspace: string, base: string): Promise<Array<{ sha: string; subject: string }>> {\n const { stdout } = await execFileAsync('git', ['log', '--reverse', '--format=%H%x00%s', `${base}..HEAD`], {\n cwd: workspace,\n encoding: 'utf-8',\n maxBuffer: 10 * 1024 * 1024,\n });\n return stdout\n .split('\\n')\n .map((line) => {\n const [sha, subject = ''] = line.split('\\0');\n return { sha, subject };\n })\n .filter((entry) => entry.sha.trim().length > 0);\n}\n\nasync function runGitShow(workspace: string, sha: string): Promise<string> {\n const { stdout } = await execFileAsync('git', ['show', sha], {\n cwd: workspace,\n encoding: 'utf-8',\n maxBuffer: 16 * 1024 * 1024,\n });\n return stdout;\n}\n\nfunction replayDeps(deps: TierReplayDeps = {}): Required<TierReplayDeps> {\n return {\n spawn: deps.spawn ?? spawnRun,\n deliver: deps.deliver ?? deliverAgentMessage,\n stop: deps.stop ?? stopAgent,\n gitLog: deps.gitLog ?? runGitLog,\n gitShow: deps.gitShow ?? runGitShow,\n renderDiff: deps.renderDiff\n ?? (deps.gitShow\n ? (workspace, sha) => deps.gitShow!(workspace, sha)\n : renderCommitFeedDiff),\n listSlotOwnership: deps.listSlotOwnership ?? listSlotOwnership,\n };\n}\n","export {\n clearReadySignal,\n isQualifiedAgentId,\n normalizeAgentId,\n resolveAgentTargetSync,\n waitForAgentIdle,\n waitForReadySignal,\n} from './agents/identity.js';\nexport {\n buildDefaultResumeContinueMessage,\n decideChannelsForWorkAgent,\n decideSupervisorForWorkAgent,\n dismissDevChannelsDialog,\n writeChannelsBridgeMcpConfig,\n} from './agents/supervisor-channels.js';\nexport {\n buildCavemanExports,\n buildSpawnEnvForModel,\n determineModel,\n getProviderEnvForModel,\n getProviderExportsForModel,\n getProviderTmuxFlags,\n} from './agents/provider-env.js';\nexport {\n OHMYPI_AGENT_READY_TIMEOUT_SECONDS,\n describeOhmypiSpawnFailure,\n getAgentRuntimeBaseCommand,\n getProviderAuthMode,\n getRoleRuntimeBaseCommand,\n injectPiConversationMemory,\n roleAgentDefinitionPath,\n waitForPromptReady,\n} from './agents/runtime-command.js';\nexport {\n assertWorkspaceStackHealthyForSpawn,\n buildAgentLaunchConfig,\n retrieveSpawnTimeMemoryContext,\n transitionIssueToInProgress,\n transitionIssueToInReview,\n type AgentLaunchConfig,\n type SpawnOptions,\n type SpawnRunOptions,\n} from './agents/spawn-prep.js';\n\nexport { spawnAgent, spawnRun } from './agents/spawn.js';\n\nexport { listRunningAgentsSync, listAgentStates, listRunningAgents, warnOnBareNumericIssueIds, dropLegacyAgentStatesMissingRoleAsync } from './agents/queries.js';\nexport {\n GOVERNOR_SLOT_PAUSE_REASON_PREFIX,\n SESSION_EXITED_BEFORE_KICKOFF,\n __testInternals,\n clearAgentPaused,\n clearAgentPausedSync,\n clearAgentTroubled,\n clearAgentTroubledSync,\n getAgentDir,\n getAgentState,\n getAgentStateFilePath,\n getAgentStateSync,\n isAgentPaused,\n isAgentTroubled,\n isRole,\n markAgentRunningState,\n markAgentStoppedState,\n markAgentTroubled,\n recordAgentFailure,\n recordAgentFailureSync,\n resetAgentFailureCount,\n saveAgentState,\n saveAgentStateSync,\n setAgentPaused,\n setAgentPausedSync,\n setAgentYieldedSync,\n clearYieldForResumeSync,\n wipeAgentStateDirs,\n writeAgentStateJsonSync,\n type AgentState,\n type Role,\n} from './agents/agent-state.js';\nexport { stopAgentSync, stopAgent } from './agents/termination.js';\nexport { type ActivityEntry, appendActivity, getActivity, saveSessionId, getSessionId, getLatestSessionIdSync, getLatestSessionId } from './agents/activity.js';\nexport { type AgentResolution, type AgentRuntimeState, getAgentRuntimeStateSync, getAgentRuntimeState, saveAgentRuntimeState } from './agents/runtime-state.js';\nexport { deliverAgentMessage, deliverInitialPromptWithRetry, deliverResumeMessageWithTranscriptConfirmation, deliverAgentPermissionDecision, setAgentDeliveryMethod, type DeliveryResult } from './agents/delivery.js';\nexport { messageAgent } from './agents/messaging.js';\n\nexport { buildCompactRecoverySeed, resumeAgent } from './agents/resume.js';\n\nexport { autoRecoverAgents, detectCrashedAgents, recoverAgent, restartAgent, type RestartAgentOptions } from './agents/recovery.js';\nexport {\n compactAtTierRunBoundary,\n contextWindowForModel,\n replayCrashedStandingAgent,\n replayStandingAgent,\n shouldReplayCompactAtTierRunBoundary,\n type ReplayCommit,\n type ReplayDelivery,\n type ReplayResult,\n type ReplayStandingTierTarget,\n type ReplaySupervisorTarget,\n type ReplayTarget,\n type TierReplayDeps,\n type TierRunCompactionInput,\n type TierRunCompactionOptions,\n} from './agents/tier-replay.js';\n"],"mappings":"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"}
|