@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":"specialists-D07ybnam.js","names":["readFile","stat","open","pathExists","join","getAgentWorkspace","stat","execFileAsync","join","stat","execAsync","execFileAsync","join","readJsonBody","getAgentWorkspaceShared","getAgentJsonlPathShared","getAgentPendingQuestionsShared","join","join","execAsync","join","readJsonBody","Result","readJsonBody","readJsonBody","readJsonBody","readJsonBody","dirname","join","readJsonBody","join","mkdir","writeFile","execAsync","join","execFileAsync","execFileAsync","readJsonBody","loadWorkspaceMetadataFn","join","dirname","execAsync","join","join","setReviewStatusBase","join","join"],"sources":["../../src/dashboard/server/http-helpers.ts","../../src/dashboard/server/routes/http-handler.ts","../../src/lib/codex-auth.ts","../../src/dashboard/server/routes/origin-validation.ts","../../src/dashboard/server/routes/jsonl-resolver.ts","../../src/lib/work-agent-lifecycle.ts","../../src/dashboard/server/services/git-info.ts","../../src/dashboard/server/routes/agents/shared.ts","../../src/dashboard/server/routes/agents/listing.ts","../../src/dashboard/server/routes/agents/conversation.ts","../../src/dashboard/server/routes/agents/messaging.ts","../../src/lib/channels/permission-payload.ts","../../src/dashboard/server/routes/agent-permissions.ts","../../src/dashboard/server/routes/agents/permissions.ts","../../src/dashboard/server/services/agent-event-utils.ts","../../src/dashboard/server/routes/agents/runtime-events.ts","../../src/dashboard/server/services/agent-projection.ts","../../src/dashboard/server/routes/agents/lifecycle-stop.ts","../../src/dashboard/server/routes/agents/lifecycle-restart.ts","../../src/dashboard/server/routes/agents/control.ts","../../src/lib/vbrief/auto-synthesize.ts","../../src/dashboard/server/routes/agents/spawn-helpers.ts","../../src/dashboard/server/routes/agents/spawn.ts","../../src/dashboard/server/routes/agents.ts","../../src/lib/cloister/task-readiness.ts","../../src/dashboard/server/routes/tiered-inspect-escalation.ts","../../src/dashboard/server/routes/specialists/shared.ts","../../src/dashboard/server/routes/specialists/legacy-routes.ts","../../src/dashboard/server/routes/specialists/project-routes.ts","../../src/dashboard/server/routes/specialists.ts"],"sourcesContent":["/**\n * Workaround for Effect 4.0.0-beta.45 bug where HttpServerResponse.json()\n * creates a response without a proper body._tag, causing NodeHttpServer to crash\n * with \"undefined is not an object (evaluating 'body._tag')\".\n * \n * HttpServerResponse.text() works fine, so we use it with manual JSON.stringify.\n * Remove this workaround when Effect stabilizes HttpServerResponse.json().\n */\nimport { HttpServerResponse } from 'effect/unstable/http';\n\nexport function jsonResponse(\n data: unknown,\n options?: number | { status?: number; headers?: Record<string, string> },\n): HttpServerResponse.HttpServerResponse {\n return jsonStringResponse(JSON.stringify(data), options);\n}\n\nexport function jsonStringResponse(\n json: string,\n options?: number | { status?: number; headers?: Record<string, string> },\n): HttpServerResponse.HttpServerResponse {\n const opts = typeof options === 'number' ? { status: options } : options;\n return HttpServerResponse.text(\n json,\n {\n status: opts?.status ?? 200,\n contentType: 'application/json',\n headers: opts?.headers,\n }\n );\n}\n","/**\n * httpHandler — typed error-to-HTTP mapping wrapper (PAN-470)\n *\n * Wraps an Effect route body and catches all typed TaggedErrors,\n * mapping them to appropriate HTTP status codes. Unknown/unhandled\n * errors fall through as 500 Internal Server Error.\n *\n * Usage:\n * HttpRouter.add('GET', '/api/foo', httpHandler(Effect.gen(function* () {\n * const result = yield* someService.doThing();\n * return jsonResponse(result);\n * })))\n */\n\nimport { Cause, Effect, Option } from 'effect';\nimport { HttpServerRequest, HttpServerResponse as HttpServerResponseModule } from 'effect/unstable/http';\n\ntype HttpServerResponse = HttpServerResponseModule.HttpServerResponse;\n\nimport { jsonResponse } from '../http-helpers.js';\nimport {\n AgentAlreadyRunning,\n AgentStartError,\n BeadsNotInitialized,\n IssueNotFound,\n PlanEmpty,\n RateLimited,\n TrackerApiError,\n TrackerNotConfigured,\n WorkspaceCreateError,\n WorkspaceNotFound,\n} from '../services/typed-errors.js';\n\n// Burst-control for the opaque-defect log: log the first OPAQUE_FULL_BURST\n// occurrences in full, then once per OPAQUE_BURST_INTERVAL_MS, emit a summary\n// with the count of suppressed events. This stops a runaway null-defect source\n// from filling the log + saturating the event loop with formatting work, while\n// preserving full detail for the first few occurrences so diagnosis is possible.\nconst OPAQUE_FULL_BURST = 5;\nconst OPAQUE_BURST_INTERVAL_MS = 30_000;\nlet opaqueLogged = 0;\nlet opaqueSuppressed = 0;\nlet opaqueLastSummaryAt = 0;\n\n/**\n * Wraps a route effect with typed error-to-HTTP status mapping.\n *\n * Error → Status mapping:\n * IssueNotFound, WorkspaceNotFound → 404 Not Found\n * TrackerNotConfigured → 503 Service Unavailable\n * RateLimited → 429 Too Many Requests\n * AgentAlreadyRunning → 409 Conflict\n * BeadsNotInitialized, PlanEmpty → 422 Unprocessable Entity\n * TrackerApiError → 502 Bad Gateway\n * WorkspaceCreateError, AgentStartError → 500 Internal Server Error\n * Unknown errors → 500 Internal Server Error\n */\nexport function httpHandler<R, E>(\n effect: Effect.Effect<HttpServerResponse, E, R>\n): Effect.Effect<HttpServerResponse, never, R> {\n // Capture the registration call site (where the route called httpHandler(...))\n // so when a fiber emits a typed Fail with no stack — e.g. Effect.fail(null) —\n // we can still tell which route module the handler belongs to.\n const registeredAt = (new Error('httpHandler registered')).stack ?? '<no stack>';\n type AllKnownErrors =\n | IssueNotFound\n | WorkspaceNotFound\n | TrackerNotConfigured\n | RateLimited\n | AgentAlreadyRunning\n | BeadsNotInitialized\n | PlanEmpty\n | TrackerApiError\n | WorkspaceCreateError\n | AgentStartError;\n return (effect as Effect.Effect<HttpServerResponse, AllKnownErrors, R>).pipe(\n Effect.catchTag('IssueNotFound', (err: IssueNotFound) =>\n Effect.succeed(jsonResponse({ error: `Issue not found: ${err.id}` }, { status: 404 }))\n ),\n Effect.catchTag('WorkspaceNotFound', (err: WorkspaceNotFound) =>\n Effect.succeed(jsonResponse({ error: `Workspace not found: ${err.id}` }, { status: 404 }))\n ),\n Effect.catchTag('TrackerNotConfigured', (err: TrackerNotConfigured) =>\n Effect.succeed(jsonResponse({ error: `Tracker not configured: ${err.tracker}` }, { status: 503 }))\n ),\n Effect.catchTag('RateLimited', (err: RateLimited) =>\n Effect.succeed(\n jsonResponse({ error: 'Rate limited', retryAfter: err.retryAfter }, { status: 429 })\n )\n ),\n Effect.catchTag('AgentAlreadyRunning', (err: AgentAlreadyRunning) =>\n Effect.succeed(jsonResponse({ error: `Agent already running for issue: ${err.id}` }, { status: 409 }))\n ),\n Effect.catchTag('BeadsNotInitialized', (err: BeadsNotInitialized) =>\n Effect.succeed(\n jsonResponse({ error: `Beads not initialized for workspace: ${err.workspace}` }, { status: 422 })\n )\n ),\n Effect.catchTag('PlanEmpty', (err: PlanEmpty) =>\n Effect.succeed(jsonResponse({ error: `Plan is empty for issue: ${err.id}` }, { status: 422 }))\n ),\n Effect.catchTag('TrackerApiError', (err: TrackerApiError) =>\n Effect.succeed(\n jsonResponse({ error: `Tracker API error (${err.tracker}): ${err.message}` }, { status: 502 })\n )\n ),\n Effect.catchTag('WorkspaceCreateError', (err: WorkspaceCreateError) =>\n Effect.succeed(\n jsonResponse({ error: `Workspace creation failed for ${err.id}: ${err.message}` }, { status: 500 })\n )\n ),\n Effect.catchTag('AgentStartError', (err: AgentStartError) =>\n Effect.succeed(\n jsonResponse({ error: `Agent start failed for ${err.id}: ${err.message}` }, { status: 500 })\n )\n ),\n Effect.catchCause((cause) =>\n Effect.gen(function* () {\n // Interrupt-only causes mean the consumer (browser tab, abort signal) cancelled\n // the request. That isn't a server error — silence it instead of spamming logs.\n if (Cause.hasInterruptsOnly(cause)) {\n return jsonResponse({ error: 'Request cancelled' }, { status: 499 });\n }\n const error = Cause.squash(cause);\n const message = error instanceof Error ? error.message : 'Internal server error';\n const reqOption = yield* Effect.serviceOption(HttpServerRequest.HttpServerRequest);\n const route = Option.match(reqOption, {\n onNone: () => 'unknown',\n onSome: (req) => `${req.method} ${req.url}`,\n });\n // When the squashed defect is null/undefined or a non-Error value the pretty\n // printer produces `Error: Unknown error: null` with no stack, which leaves\n // the source impossible to locate. Capture extra context for those cases:\n // the JSON shape of the cause (failures + defects), the typeof, and a stack\n // sampled from this catch site so we can at least see what caller chained\n // into httpHandler.\n const isOpaque = !(error instanceof Error);\n if (isOpaque) {\n if (opaqueLogged < OPAQUE_FULL_BURST) {\n opaqueLogged += 1;\n let causeJson: string;\n try {\n causeJson = JSON.stringify(cause, (_k, v) => (typeof v === 'function' ? '[fn]' : v), 2);\n } catch {\n causeJson = '<cause not serializable>';\n }\n console.error(\n `[httpHandler] Opaque non-Error defect on ${route} (#${opaqueLogged}): typeof=${typeof error} value=${String(error)}\\n` +\n `cause=${causeJson}\\n` +\n `registeredAt=${registeredAt}`,\n );\n } else {\n opaqueSuppressed += 1;\n const now = Date.now();\n if (now - opaqueLastSummaryAt >= OPAQUE_BURST_INTERVAL_MS) {\n opaqueLastSummaryAt = now;\n console.error(\n `[httpHandler] Opaque non-Error defect on ${route} — ${opaqueSuppressed} more suppressed since last summary ` +\n `(set DEBUG_OPAQUE_DEFECTS=1 to see all). value=${String(error)}`,\n );\n opaqueSuppressed = 0;\n }\n }\n } else {\n console.error(`[httpHandler] Unhandled error on ${route}:\\n${Cause.pretty(cause)}`);\n }\n return jsonResponse({ error: message }, { status: 500 });\n }),\n )\n ) as Effect.Effect<HttpServerResponse, never, R>;\n}\n","import { open, readFile, stat } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect, Data } from 'effect';\nimport { decodeJwtPayload, getCliproxyAuthDir, getCliproxyLogPath } from './cliproxy.js';\n\n/** Wrapper error for Codex auth probing — preserves the underlying cause. */\nexport class CodexAuthCheckError extends Data.TaggedError('CodexAuthCheckError')<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\nexport interface CodexAuthValid {\n status: 'valid';\n email: string;\n expiresAt: string;\n}\n\nexport interface CodexAuthExpired {\n status: 'expired';\n email: string;\n expiresAt: string;\n}\n\nexport interface CodexAuthBurned {\n status: 'burned';\n email: string;\n expiresAt: string;\n}\n\nexport interface CodexAuthMissing {\n status: 'missing';\n}\n\nexport interface CodexAuthUnknown {\n status: 'unknown';\n message?: string;\n}\n\nexport type CodexAuthStatus = CodexAuthValid | CodexAuthExpired | CodexAuthBurned | CodexAuthMissing | CodexAuthUnknown;\n\ninterface CheckCodexAuthOptions {\n ignoreBurnBefore?: number;\n}\n\ninterface CliproxyCodexCredentials {\n access_token?: string;\n email?: string;\n type?: string;\n}async function checkCodexAuthStatusPromise(options: CheckCodexAuthOptions = {}): Promise<CodexAuthStatus> {\n const credPath = join(getCliproxyAuthDir(), 'codex-primary.json');\n\n let raw: string;\n let credMtimeMs: number | null = null;\n try {\n raw = await readFile(credPath, 'utf8');\n credMtimeMs = (await stat(credPath)).mtimeMs;\n } catch {\n return { status: 'missing' };\n }\n\n let creds: CliproxyCodexCredentials;\n try {\n creds = JSON.parse(raw) as CliproxyCodexCredentials;\n } catch {\n return { status: 'unknown', message: 'Malformed credential file' };\n }\n\n const accessToken = typeof creds.access_token === 'string' ? creds.access_token : null;\n if (!accessToken) {\n return { status: 'unknown', message: 'Missing access_token in credential file' };\n }\n\n const claims = decodeJwtPayload(accessToken);\n if (!claims) {\n return { status: 'unknown', message: 'Unable to decode access_token' };\n }\n\n const expSec = typeof claims.exp === 'number' ? claims.exp : null;\n if (expSec === null) {\n return { status: 'unknown', message: 'Missing exp claim in access_token' };\n }\n\n const email = typeof creds.email === 'string' ? creds.email : '';\n const expiresAt = new Date(expSec * 1000).toISOString();\n\n if (expSec * 1000 <= Date.now()) {\n return { status: 'expired', email, expiresAt };\n }\n\n const jwtStatus: CodexAuthStatus = { status: 'valid', email, expiresAt };\n // The credential file's own write time is the authoritative \"you last logged\n // in at\" reference: any auth failure logged AFTER it means the CURRENT token\n // is dead. An explicit caller option (the re-auth flow's session.createdAt)\n // still wins. Without this, callers that pass no option fell back to the crude\n // 1h staleness window and reported a burned token as \"valid\" once the burn\n // line aged out — exactly the gap that let gpt-5.5 agents spawn into 503s.\n const effectiveOptions: CheckCodexAuthOptions = {\n ignoreBurnBefore: options.ignoreBurnBefore ?? credMtimeMs ?? undefined,\n };\n return await applyBurnedTokenOverride(jwtStatus, email, expiresAt, effectiveOptions);\n}\n\n/** Read the trailing window of the cliproxy log for burn/failure analysis. */\nasync function readLogTail(path: string): Promise<string> {\n const TAIL_BYTES = 128 * 1024;\n const file = await open(path, 'r');\n try {\n const stat = await file.stat();\n const length = Math.min(stat.size, TAIL_BYTES);\n const buffer = Buffer.alloc(length);\n await file.read(buffer, 0, length, stat.size - length);\n return buffer.toString('utf8');\n } finally {\n await file.close();\n }\n}\n\nasync function applyBurnedTokenOverride(\n baseStatus: CodexAuthStatus,\n email: string,\n expiresAt: string,\n options: CheckCodexAuthOptions,\n): Promise<CodexAuthStatus> {\n let logRaw: string;\n try {\n logRaw = await readLogTail(getCliproxyLogPath());\n } catch {\n return baseStatus;\n }\n return evaluateBurnedFromLog(logRaw, baseStatus, email, expiresAt, options);\n}\n\nconst BURN_STALENESS_MS = 60 * 60 * 1000;\n\n/**\n * Pure decision: given the cliproxy log tail, decide whether a JWT-valid token\n * is actually burned. Exported for regression testing (PAN-1584).\n *\n * \"Auth failure\" evidence = either a refresh-token burn line\n * (`refresh token has already been used`) OR a `503` on `/v1/messages` /\n * `/v1/chat/completions` (the live symptom the agent hits once cliproxy disables\n * the provider — and which keeps appearing after the burn line stops, since a\n * disabled provider no longer attempts refreshes). \"Success\" = a `200` on those\n * same paths (proves the auth path works right now).\n *\n * Decision:\n * - No failure evidence → trust base status.\n * - Success AFTER the last failure → trust base status (recovered).\n * - Failure AFTER the credential's last write (ignoreBurnBefore) → BURNED,\n * regardless of age. The credential write time is when you last logged in;\n * a failure after it means the current token is dead. This is authoritative\n * and is the fix for the bug where a >1h-stale burn line was dismissed as\n * \"valid\" during a quiet period even though the token was dead (PAN-1584).\n * - Failure BEFORE that cutoff → trust base status (re-authed since).\n * - No cutoff available → fall back to the staleness backstop:\n * burned only if the last failure is within BURN_STALENESS_MS.\n *\n * We intentionally don't probe cliproxy with HTTP — `GET /v1/models` always\n * 401s (it needs real OAuth, not the local key) and would generate spurious log\n * lines on every dashboard load.\n */\nexport function evaluateBurnedFromLog(\n logRaw: string,\n baseStatus: CodexAuthStatus,\n email: string,\n expiresAt: string,\n options: CheckCodexAuthOptions & { now?: number } = {},\n): CodexAuthStatus {\n const now = options.now ?? Date.now();\n // Scan a wider window than the original 50 lines so a quiet recovery period\n // can still surface a later success.\n const lines = logRaw.split('\\n').slice(-500);\n\n // PAN-913 matched `refresh token has already been used`; PAN-1455 adds `refresh_token_reused`.\n const isBurnLine = (l: string) =>\n l.includes('refresh token has already been used') || l.includes('refresh_token_reused');\n const isAuthFailure503 = (l: string) =>\n /\\b503 \\|/.test(l) && /POST\\s+\"\\/v1\\/(messages|chat\\/completions)/.test(l);\n const isSuccess = (l: string) =>\n /\\b200 \\|/.test(l) && /POST\\s+\"\\/v1\\/(messages|chat\\/completions)/.test(l);\n\n // The bracketed timestamp lives on the gin_logger/openai_auth line; burn\n // messages sit on a JSON continuation line with no prefix, so scan back a few\n // lines for the nearest one.\n const timestampAt = (idx: number): number | null => {\n for (let j = idx; j >= Math.max(0, idx - 10); j--) {\n const m = lines[j]?.match(/^\\[(\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2})\\]/);\n if (m) {\n const t = Date.parse(`${m[1]}T${m[2]}Z`);\n if (Number.isFinite(t)) return t;\n }\n }\n return null;\n };\n\n let lastFailureIdx = -1;\n let lastFailureTimestamp: number | null = null;\n let lastSuccessIdx = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i] ?? '';\n if (lastFailureIdx < 0 && (isBurnLine(line) || isAuthFailure503(line))) {\n lastFailureIdx = i;\n lastFailureTimestamp = timestampAt(i);\n }\n if (lastSuccessIdx < 0 && isSuccess(line)) {\n lastSuccessIdx = i;\n }\n if (lastFailureIdx >= 0 && lastSuccessIdx >= 0) break;\n }\n\n // No failure evidence at all → trust the JWT-based status.\n if (lastFailureIdx < 0) return baseStatus;\n\n // A successful LLM call came AFTER the failure → auth path works again.\n if (lastSuccessIdx > lastFailureIdx) return baseStatus;\n\n // Authoritative path: we know when the credential was last written. A failure\n // after that write means the CURRENT token is dead — flag burned no matter how\n // old the line is. (Pad by 1000ms: burn lines carry second-precision stamps, so\n // a credential written in the same second as the failure shouldn't look newer.)\n if (options.ignoreBurnBefore !== undefined) {\n if (lastFailureTimestamp !== null && lastFailureTimestamp + 1000 <= options.ignoreBurnBefore) {\n return baseStatus;\n }\n return { status: 'burned', email, expiresAt };\n }\n\n // No credential-write reference → staleness backstop only.\n if (lastFailureTimestamp !== null && now - lastFailureTimestamp > BURN_STALENESS_MS) {\n return baseStatus;\n }\n\n return { status: 'burned', email, expiresAt };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect-native checkCodexAuthStatus. The Promise version is designed to\n * swallow all I/O errors and report the auth state through the typed status\n * union. The Effect variant wraps that to make it composable; it only fails\n * with CodexAuthCheckError if the underlying call itself throws unexpectedly\n * (i.e., not from the documented \"missing/unknown\" branches).\n */\nexport const checkCodexAuthStatus = (\n options: { ignoreBurnBefore?: number } = {},\n): Effect.Effect<CodexAuthStatus, CodexAuthCheckError> =>\n Effect.tryPromise({\n try: () => checkCodexAuthStatusPromise(options),\n catch: (cause) =>\n new CodexAuthCheckError({\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n","import type { HttpServerRequest } from 'effect/unstable/http';\n\nexport type HeaderMap = Record<string, string | string[] | undefined>;\n\nlet cachedTrustedOrigins: string[] | undefined;\n\nfunction addTrustedOrigin(origins: Set<string>, raw: string | undefined): void {\n if (!raw) return;\n const normalized = normalizeOrigin(raw.trim());\n if (normalized) origins.add(normalized);\n}\n\nexport function getTrustedOrigins(): string[] {\n if (cachedTrustedOrigins !== undefined) {\n return cachedTrustedOrigins;\n }\n const port = parseInt(process.env['API_PORT'] ?? process.env['PORT'] ?? '3011', 10);\n const dashboardUrl = process.env['DASHBOARD_URL'] ?? `http://localhost:${port}`;\n const origins = new Set<string>();\n addTrustedOrigin(origins, dashboardUrl);\n addTrustedOrigin(origins, `http://localhost:${port}`);\n addTrustedOrigin(origins, `http://127.0.0.1:${port}`);\n addTrustedOrigin(origins, 'http://localhost:3010');\n addTrustedOrigin(origins, 'http://127.0.0.1:3010');\n\n const trustedOrigins = process.env['OVERDECK_TRUSTED_ORIGINS'];\n for (const origin of trustedOrigins?.split(',') ?? []) {\n addTrustedOrigin(origins, origin);\n }\n\n const traefikDomain = process.env['OVERDECK_TRAEFIK_DOMAIN'] ?? process.env['TRAEFIK_DOMAIN'];\n if (process.env['OVERDECK_TRAEFIK_ENABLED'] === '1' && traefikDomain) {\n addTrustedOrigin(origins, `https://${traefikDomain}`);\n }\n\n if (process.env['NODE_ENV'] === 'development') {\n addTrustedOrigin(origins, 'http://localhost:3000');\n addTrustedOrigin(origins, 'http://127.0.0.1:3000');\n }\n cachedTrustedOrigins = Array.from(origins);\n return cachedTrustedOrigins;\n}\n\nexport function normalizeOrigin(origin: string): string | null {\n try {\n const url = new URL(origin);\n return `${url.protocol}//${url.host}`;\n } catch {\n return null;\n }\n}\n\nexport function getHeaderFromMap(headers: HeaderMap, name: string): string | undefined {\n const direct = headers[name];\n if (Array.isArray(direct)) return direct[0];\n if (direct) return direct;\n\n const lowerName = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== lowerName) continue;\n return Array.isArray(value) ? value[0] : value;\n }\n return undefined;\n}\n\nexport function validateOriginHeaders(\n headers: HeaderMap,\n method: string,\n): { ok: true } | { ok: false; error: string } {\n const origin = getHeaderFromMap(headers, 'origin');\n const referer = getHeaderFromMap(headers, 'referer');\n const trusted = getTrustedOrigins();\n\n if (!origin && !referer) {\n const upperMethod = method.toUpperCase();\n if (upperMethod === 'GET' || upperMethod === 'HEAD') {\n return { ok: true };\n }\n return { ok: false, error: 'Missing origin' };\n }\n\n if (origin) {\n const normalized = normalizeOrigin(origin);\n if (normalized && trusted.includes(normalized)) {\n return { ok: true };\n }\n return { ok: false, error: 'Invalid origin' };\n }\n\n if (!referer) {\n return { ok: false, error: 'Invalid referer' };\n }\n const normalized = normalizeOrigin(referer);\n if (normalized && trusted.includes(normalized)) {\n return { ok: true };\n }\n return { ok: false, error: 'Invalid referer' };\n}\n\nexport function validateOrigin(\n request: HttpServerRequest.HttpServerRequest,\n): { ok: true } | { ok: false; error: string } {\n return validateOriginHeaders(request.headers as HeaderMap, request.method);\n}\n\nexport function _resetTrustedOriginsForTests(): void {\n cachedTrustedOrigins = undefined;\n}\n\n/**\n * Origin trust check used by raw WebSocket upgrade paths (autopreso, voice).\n * Only explicitly configured dashboard origins are trusted; the request Host\n * header is attacker-controlled and must not influence this decision.\n */\nexport function isTrustedOriginForHost(\n origin: string | undefined,\n _host: string | string[] | undefined,\n): boolean {\n if (!origin) return false;\n const normalized = normalizeOrigin(origin);\n if (!normalized) return false;\n return getTrustedOrigins().includes(normalized);\n}\n","/**\n * JSONL transcript resolver for the Command Deck (PAN-830).\n *\n * Maps an agent ID (e.g. `agent-pan-830`, `planning-pan-830`, or a canonical\n * specialist tmux session name) to the agent's JSONL transcript file on disk.\n *\n * Codex agents (PAN-1805) write rollout JSONLs under the per-agent\n * `codex-home/sessions/` tree — resolution dispatches on the harness recorded\n * in state.json (thread-id fast path, then latest-rollout fallback).\n *\n * For claude-code agents the JSONL filename is the *Claude session ID* (a UUID\n * written by Claude Code itself), NOT the agent/tmux name. Lookup order:\n * 1. session.id — single UUID written by auto-suspend\n * 2. sessions.json — array of UUIDs the agent has used; the heartbeat hook\n * APPENDS new IDs and dedupes, so once a session has been\n * seen its array index never moves. After a `pan resume`\n * brings an older session back as the live one, the array\n * order no longer reflects time-recency. We disambiguate\n * across all listed IDs by JSONL mtime.\n * 3. runtime state — in-process mirror, populated by hooks\n *\n * Async-only (fs/promises) because this code path runs inside the dashboard\n * server's event loop.\n */\nimport { access, readFile, stat, readdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { getAgentRuntimeState, getAgentStateSync } from '../../../lib/agents.js';\nimport { encodeClaudeProjectDir, getOverdeckHome } from '../../../lib/paths.js';\nimport { getAgentWorkspace } from '../../../lib/agent-enrichment.js';\nimport { getHarnessBehavior } from '../../../lib/runtimes/behavior.js';\nimport type { HarnessName } from '../../../lib/runtimes/types.js';\n\nexport interface ResolveJsonlPathOptions {\n /** Override the ~/.overdeck/agents directory (test hook). */\n agentsDirOverride?: string;\n /** Override the ~/.claude/projects directory (test hook). */\n claudeProjectsDirOverride?: string;\n /** Override the runtime-state lookup (test hook). */\n getRuntimeStateAsync?: (agentId: string) => Promise<{ claudeSessionId?: string } | null>;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n return access(p).then(() => true, () => false);\n}\n\nasync function readOptional(p: string): Promise<string | null> {\n return readFile(p, 'utf-8').catch(() => null);\n}\n\nfunction behaviorForHarness(harness: string | null | undefined) {\n return getHarnessBehavior(harness as HarnessName | null | undefined);\n}\n\n/**\n * Pick the candidate UUID whose JSONL transcript has the most recent mtime.\n *\n * The heartbeat hook appends to sessions.json and dedupes — so once an ID is\n * recorded, its array index never moves even if a later resume makes it the\n * current session again. Choosing by JSONL mtime is the only reliable signal\n * for \"which session is actually live right now.\"\n *\n * Returns null if no candidate has a corresponding JSONL on disk. Falls back\n * to scanning every project dir if the workspace path can't be resolved\n * (covers specialist sessions whose workspace lives elsewhere).\n */\nasync function pickFreshestSessionId(\n candidates: ReadonlyArray<string>,\n agentId: string,\n opts: ResolveJsonlPathOptions,\n): Promise<string | null> {\n if (candidates.length === 0) return null;\n if (candidates.length === 1) return candidates[0]!;\n\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n\n // Fast path: derive the agent's workspace and look only in the matching\n // project dir. Avoids fanning out across every Claude project.\n let candidatePaths: Array<{ id: string; path: string }> = [];\n try {\n const workspace = await Effect.runPromise(getAgentWorkspace(agentId));\n if (workspace) {\n const projectDir = join(projectsRoot, encodeClaudeProjectDir(workspace));\n candidatePaths = candidates.map((id) => ({ id, path: join(projectDir, `${id}.jsonl`) }));\n }\n } catch { /* non-fatal — fall back to project-dir scan */ }\n\n // Slow path: scan all project dirs (specialist agents, multi-workspace cases).\n if (candidatePaths.length === 0) {\n try {\n const dirs = await readdir(projectsRoot);\n const SAFE_DIR = /^[a-zA-Z0-9_.-]+$/;\n for (const id of candidates) {\n for (const dir of dirs) {\n if (!SAFE_DIR.test(dir)) continue;\n candidatePaths.push({ id, path: join(projectsRoot, dir, `${id}.jsonl`) });\n }\n }\n } catch { /* fall through to no-mtime path */ }\n }\n\n // Stat each candidate path; pick the (id, mtime) with the newest mtime.\n let best: { id: string; mtimeMs: number } | null = null;\n for (const { id, path } of candidatePaths) {\n try {\n const s = await stat(path);\n if (!best || s.mtimeMs > best.mtimeMs) {\n best = { id, mtimeMs: s.mtimeMs };\n }\n } catch { /* missing file — skip */ }\n }\n\n // If no candidate has a JSONL on disk, fall back to the historical \"last\n // appended\" heuristic — the previous behavior. Better than returning null\n // for callers that only need a session ID, not necessarily a live JSONL.\n return best ? best.id : (candidates[candidates.length - 1] ?? null);\n}\n\n/** Async equivalent of getLatestSessionId from lib/agents.ts. */\nexport async function resolveClaudeSessionId(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n\n // 1. session.id — single UUID written by auto-suspend. Authoritative when\n // present (only one session is alive when suspend writes this file).\n const sessionIdRaw = await readOptional(join(agentDir, 'session.id'));\n const sessionIdTrimmed = sessionIdRaw?.trim();\n if (sessionIdTrimmed) return sessionIdTrimmed;\n\n // 2. sessions.json — array of UUIDs the agent has ever used. We can't trust\n // array order (see file-level docs), so disambiguate by JSONL mtime.\n const sessionsRaw = await readOptional(join(agentDir, 'sessions.json'));\n if (sessionsRaw) {\n try {\n const parsed: unknown = JSON.parse(sessionsRaw);\n if (Array.isArray(parsed)) {\n const valid = parsed.filter((v): v is string => typeof v === 'string' && v.trim().length > 0);\n const fresh = await pickFreshestSessionId(valid, agentId, opts);\n if (fresh) return fresh;\n }\n } catch { /* non-fatal */ }\n }\n\n // 3. runtime state claudeSessionId (in-process mirror)\n try {\n const lookup = opts.getRuntimeStateAsync ?? ((id: string) => Effect.runPromise(getAgentRuntimeState(id)));\n const runtimeState = await lookup(agentId);\n if (runtimeState?.claudeSessionId) return runtimeState.claudeSessionId;\n } catch { /* non-fatal */ }\n\n return null;\n}\n\n/**\n * Read the Claude session id PINNED into an agent/conversation launcher.sh.\n *\n * The launcher is what spawns the live tmux pane: it runs\n * `claude … --session-id <uuid>` (or `--resume <uuid>`), so the pinned id is the\n * EXACT session the Terminal tab attaches to — the only deterministic ground\n * truth for \"which session is live right now.\" Resolving the transcript panel\n * from this id makes the Conversation tab match the Terminal tab by construction,\n * instead of guessing via JSONL mtime (racy: an older session's file gets touched\n * by a compaction summary write-back or a transient relaunch, its mtime jumps\n * ahead of the live session's, and the panel renders the wrong transcript).\n *\n * Checks the conversation launcher dir first, then the agent launcher dir.\n * Returns the uuid, or null when no launcher exists or it pins no session id.\n */\nconst LAUNCHER_UUID =\n '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';\nconst LAUNCHER_SESSION_ID_RE = new RegExp(`--session-id\\\\s+'?(${LAUNCHER_UUID})'?`);\nconst LAUNCHER_RESUME_RE = new RegExp(`--resume\\\\s+'?(${LAUNCHER_UUID})'?`);\n\nexport async function readLauncherPinnedSessionId(\n tmuxSession: string,\n opts: { overdeckHomeOverride?: string } = {},\n): Promise<string | null> {\n const home = opts.overdeckHomeOverride ?? getOverdeckHome();\n const candidates = [\n join(home, 'conversations', tmuxSession, 'launcher.sh'),\n join(home, 'agents', tmuxSession, 'launcher.sh'),\n ];\n for (const path of candidates) {\n const text = await readOptional(path);\n if (!text) continue;\n const match = LAUNCHER_SESSION_ID_RE.exec(text) ?? LAUNCHER_RESUME_RE.exec(text);\n if (match) return match[1]!;\n }\n return null;\n}\n\n/**\n * Read the harness + workspace recorded for an agent, honoring the test\n * override dir. Used by resolveAgentHarness / agentHasClaudeTranscript so the\n * stale-harness self-correction runs identically in prod and under test.\n */\nasync function readRecordedState(\n agentId: string,\n opts: ResolveJsonlPathOptions,\n): Promise<{ harness: string | null; workspace?: string }> {\n if (opts.agentsDirOverride) {\n try {\n const raw = await readFile(join(opts.agentsDirOverride, agentId, 'state.json'), 'utf8');\n const s = JSON.parse(raw) as { harness?: unknown; workspace?: unknown };\n return {\n harness: typeof s.harness === 'string' ? s.harness : null,\n workspace: typeof s.workspace === 'string' ? s.workspace : undefined,\n };\n } catch {\n return { harness: null };\n }\n }\n const st = getAgentStateSync(agentId);\n return { harness: st?.harness ?? null, workspace: st?.workspace };\n}\n\n/**\n * Resolve the harness for transcript routing. Reads state.json, then\n * self-corrects when the recorded value is stale.\n *\n * Trust an explicit non-default harness (codex / pi / ohmypi) — it was chosen\n * by resolveHarness at spawn time and is not the generic fallback. The\n * 'claude-code' default, however, goes stale in one observed case: a\n * wipe-and-respawn that changed the provider-default harness left state.json\n * carrying the pre-respawn 'claude-code' while the fresh launch was\n * codex/ohmypi. The resolver then took the claude-code branch, found no\n * claudeSessionId, and the dashboard showed \"No conversation data available\"\n * for a live agent whose real transcript sat untouched under codex-home.\n *\n * Self-correction (claude-code/null recorded only): if the agent has NO\n * claude-code transcript on disk but DOES have a codex rollout / pi session,\n * the on-disk runtime wins — those artifacts are written only by that runtime,\n * so this cannot surface a wrong transcript. When a claude-code transcript IS\n * present it always wins, so a past codex run's codex-home can't shadow a\n * current claude-code session.\n */\nexport async function resolveAgentHarness(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const recorded = (await readRecordedState(agentId, opts)).harness;\n if (recorded === 'codex' || recorded === 'pi' || recorded === 'ohmypi') {\n return recorded;\n }\n // 'claude-code' (or null) is the default that can go stale. Correct it from\n // on-disk artifacts only when no claude-code transcript exists for the agent.\n if (await agentHasClaudeTranscript(agentId, opts)) {\n return recorded ?? 'claude-code';\n }\n if (await resolveCodexRolloutPath(agentId, opts)) return 'codex';\n if (await resolvePiSessionPath(agentId, opts)) return 'ohmypi';\n return recorded;\n}\n\n/**\n * Whether the agent has a resolvable claude-code transcript on disk. Used by\n * resolveAgentHarness to decide if a claude-code recording is still live\n * (vs. a stale default that should be corrected to codex/ohmypi).\n */\nasync function agentHasClaudeTranscript(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<boolean> {\n const sessionId = await resolveClaudeSessionId(agentId, opts);\n if (!sessionId) return false;\n const workspace = (await readRecordedState(agentId, opts)).workspace;\n if (!workspace) return false;\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n return pathExists(join(projectsRoot, encodeClaudeProjectDir(workspace), `${sessionId}.jsonl`));\n}\n\n/**\n * Resolve the Codex rollout JSONL for a codex-harness agent (PAN-1805).\n *\n * Fast path: the persisted codex-thread-id maps directly to its rollout file.\n * Lazy fallback (same shape as the conversation panel's PAN-1690 fix): codex\n * writes the rollout only on the first turn, so a spawn-time thread-id capture\n * can miss it — the per-agent CODEX_HOME holds only this agent's rollouts, so\n * the newest one is its current thread.\n */\nexport async function resolveCodexRolloutPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n const codexHome = join(agentDir, 'codex-home');\n if (!(await pathExists(codexHome))) return null;\n\n const { findRolloutPath, findLatestRollout } = await import('../../../lib/runtimes/codex.js');\n\n const threadId = (await readOptional(join(agentDir, 'codex-thread-id')))?.trim();\n if (threadId) {\n const rollout = findRolloutPath(codexHome, threadId);\n if (rollout) return rollout;\n }\n return findLatestRollout(codexHome);\n}\n\n/**\n * Resolve the pi/kimi session JSONL (PAN-1908). Pi writes its session transcript\n * as `<iso-ts>_<session-id>.jsonl` either in the agent dir's `sessions/` subdir\n * (conversations) OR in the agent dir root (work agents) — so check both. The dir\n * also holds `cost-events.jsonl` / `activity.jsonl`, which are NOT transcripts.\n * Return the freshest transcript by mtime, or null if pi hasn't written one yet.\n */\nexport async function resolvePiSessionPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n const NON_TRANSCRIPT = new Set(['cost-events.jsonl', 'activity.jsonl', 'pending-events.jsonl']);\n let best: { path: string; mtime: number } | null = null;\n for (const dir of [join(agentDir, 'sessions'), agentDir]) {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of entries) {\n if (!name.endsWith('.jsonl') || NON_TRANSCRIPT.has(name)) continue;\n const p = join(dir, name);\n try {\n const m = (await stat(p)).mtimeMs;\n if (!best || m > best.mtime) best = { path: p, mtime: m };\n } catch { /* unreadable entry — skip */ }\n }\n }\n return best?.path ?? null;\n}\n\n/**\n * Resolve the JSONL transcript for an agent.\n *\n * Codex agents resolve to their rollout JSONL (PAN-1805). For claude-code\n * agents, returns the absolute path if both the claudeSessionId is known AND\n * the corresponding JSONL file exists; otherwise null.\n */\nexport async function resolveJsonlPath(\n agentId: string,\n workspacePath: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n // Dispatch on the recorded harness so a stale session.id from an earlier\n // claude-code run of the same agent id can't shadow the codex transcript.\n const harness = await resolveAgentHarness(agentId, opts);\n const behavior = behaviorForHarness(harness);\n if (behavior.transcriptKind === 'codex-rollout-jsonl') {\n return resolveCodexRolloutPath(agentId, opts);\n }\n if (behavior.transcriptKind === 'ohmypi-jsonl') {\n return resolvePiSessionPath(agentId, opts);\n }\n\n const claudeSessionId = await resolveClaudeSessionId(agentId, opts);\n if (!claudeSessionId) return null;\n\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n const encodedDir = encodeClaudeProjectDir(workspacePath);\n const jsonlPath = join(projectsRoot, encodedDir, `${claudeSessionId}.jsonl`);\n if (await pathExists(jsonlPath)) return jsonlPath;\n return null;\n}\n","import { existsSync } from 'node:fs';\nimport { access } from 'node:fs/promises';\nimport { Data, Effect } from 'effect';\nimport { getAgentStateSync, getAgentState, getAgentRuntimeStateSync, getAgentRuntimeState, getLatestSessionIdSync, getLatestSessionId, normalizeAgentId } from './agents.js';\nimport { sessionExistsSync, sessionExists } from './tmux.js';\n\nexport type WorkAgentOperation = 'start' | 'resume' | 'restart_with_context' | 'reset_session';\nexport type WorkAgentRecommendedAction = 'start' | 'resume' | 'restart_with_context' | 'reset_session' | 'none';\n\nexport interface WorkAgentLifecycleState {\n agentId: string;\n hasAgentState: boolean;\n hasLiveTmuxSession: boolean;\n hasSavedSession: boolean;\n hasWorkspace: boolean;\n isPlaceholder: boolean;\n isOrphaned: boolean;\n isRunning: boolean;\n /** Agent has a live tmux session and running status, but its runtime is idle or\n * suspended — meaning the model stopped producing output (e.g. model errors).\n * The session should be restarted via resume rather than messaged via pan tell. */\n isRunningButStuck: boolean;\n isStopped: boolean;\n isCompleted: boolean;\n isCrashed: boolean;\n runtimeState: string;\n agentStatus: string;\n canStartFresh: boolean;\n /** True when the session can be resumed (stopped, crashed, or running-but-stuck).\n * Always false when `isRunning` is true — a live, active session is not resumable. */\n canResumeSession: boolean;\n canRestartWithContext: boolean;\n canResetSession: boolean;\n requiresSessionResetBeforeFreshStart: boolean;\n recommendedAction: WorkAgentRecommendedAction;\n reason?: string;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getWorkAgentLifecycleStateSync(agentOrIssueId: string): WorkAgentLifecycleState {\n const agentId = normalizeAgentId(agentOrIssueId);\n const agentState = getAgentStateSync(agentId);\n const runtimeState = getAgentRuntimeStateSync(agentId);\n const hasAgentState = !!agentState;\n const hasSavedSession = !!getLatestSessionIdSync(agentId);\n const hasLiveTmuxSession = sessionExistsSync(agentId);\n const hasWorkspace = !!agentState?.workspace && existsSync(agentState.workspace);\n const agentStatus = agentState?.status || 'unknown';\n const runtime = runtimeState?.state || 'uninitialized';\n const isCompleted = runtimeState?.resolution === 'completed';\n const isPlaceholder = !!agentState && agentStatus === 'starting' && typeof agentState.model === 'string' && agentState.model.startsWith('pending-');\n const isStopped = agentStatus === 'stopped' || agentStatus === 'error' || isCompleted || runtime === 'stopped' || runtime === 'idle' || runtime === 'suspended';\n const isRunning = (agentStatus === 'running' || isPlaceholder) && hasLiveTmuxSession;\n const isCrashed = (agentStatus === 'running' || isPlaceholder) && !hasLiveTmuxSession;\n // Running-but-stuck: live session + running status, but the runtime is idle or suspended\n // (e.g. the model returned errors and stopped producing output). The tmux session exists but\n // the agent is no longer making progress — it needs a resume, not a message.\n const isRunningButStuck = isRunning && (runtime === 'idle' || runtime === 'suspended');\n const hasResumableBackingState = hasAgentState && hasWorkspace && !isPlaceholder;\n const isOrphaned = !hasLiveTmuxSession && (\n (hasSavedSession && !hasResumableBackingState)\n || (hasAgentState && (!hasWorkspace || isPlaceholder))\n );\n const requiresSessionResetBeforeFreshStart = hasSavedSession && !hasLiveTmuxSession && hasResumableBackingState && (isStopped || isCrashed);\n\n let recommendedAction: WorkAgentRecommendedAction = 'start';\n let reason: string | undefined;\n\n if (isRunningButStuck) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a live session but its runtime is ${runtime} — it is no longer making progress. Use 'pan resume ${agentOrIssueId}' to restart it.`;\n } else if (hasLiveTmuxSession && agentStatus === 'running') {\n recommendedAction = 'none';\n reason = `Agent ${agentId} is already running. Use 'pan tell' to message it.`;\n } else if (hasLiveTmuxSession && isStopped) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a live tmux session but is stopped. Use 'pan resume ${agentOrIssueId}' to continue or 'pan start ${agentOrIssueId}' will kill the session and start fresh.`;\n } else if (isOrphaned) {\n recommendedAction = 'start';\n reason = hasSavedSession\n ? `Agent ${agentId} has stale/orphaned session metadata without a resumable workspace-backed agent state. Start Agent should create a fresh session.`\n : `Agent ${agentId} is an orphaned placeholder/stale record. Start Agent should create a fresh session.`;\n } else if (requiresSessionResetBeforeFreshStart) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a resumable Claude session. Use 'pan resume ${agentOrIssueId}' to continue it, or 'pan start ${agentOrIssueId} --fresh' to start a new session (e.g. to switch model).`;\n } else if (hasAgentState && !hasSavedSession && isStopped) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} is stopped and has no saved Claude session. Start Agent will create a fresh session in the existing workspace.`;\n } else if (!hasAgentState && !hasSavedSession) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} has no prior resumable session. Start Agent will create a fresh workspace-backed session.`;\n }\n\n return {\n agentId,\n hasAgentState,\n hasLiveTmuxSession,\n hasSavedSession,\n hasWorkspace,\n isPlaceholder,\n isOrphaned,\n isRunning,\n isRunningButStuck,\n isStopped,\n isCompleted,\n isCrashed,\n runtimeState: runtime,\n agentStatus,\n canStartFresh: (!hasLiveTmuxSession || (hasLiveTmuxSession && isStopped)) && (!requiresSessionResetBeforeFreshStart || isOrphaned),\n // A live, actively-running agent (isRunning=true, isRunningButStuck=false) is already in\n // session — no resume needed. Stuck agents (isRunning=true, isRunningButStuck=true) must\n // use the dedicated isRunningButStuck flag at call sites; canResumeSession stays false for\n // them so `isRunning` and `canResumeSession` are never simultaneously true.\n canResumeSession: !isRunning && hasSavedSession && hasResumableBackingState && (isStopped || isCrashed),\n canRestartWithContext: hasAgentState && hasWorkspace,\n canResetSession: hasSavedSession && hasResumableBackingState,\n requiresSessionResetBeforeFreshStart,\n recommendedAction,\n reason,\n };\n}\n\nasync function getWorkAgentLifecycleStateSnapshot(agentOrIssueId: string): Promise<WorkAgentLifecycleState> {\n const agentId = normalizeAgentId(agentOrIssueId);\n const agentState = await Effect.runPromise(getAgentState(agentId));\n const runtimeState = await Effect.runPromise(getAgentRuntimeState(agentId));\n const hasAgentState = !!agentState;\n const hasSavedSession = !!(await Effect.runPromise(getLatestSessionId(agentId)));\n const hasLiveTmuxSession = await Effect.runPromise(sessionExists(agentId));\n const hasWorkspace = !!agentState?.workspace && await pathExists(agentState.workspace);\n const agentStatus = agentState?.status || 'unknown';\n const runtime = runtimeState?.state || 'uninitialized';\n const isCompleted = runtimeState?.resolution === 'completed';\n const isPlaceholder = !!agentState && agentStatus === 'starting' && typeof agentState.model === 'string' && agentState.model.startsWith('pending-');\n const isStopped = agentStatus === 'stopped' || agentStatus === 'error' || isCompleted || runtime === 'stopped' || runtime === 'idle' || runtime === 'suspended';\n const isRunning = (agentStatus === 'running' || isPlaceholder) && hasLiveTmuxSession;\n const isCrashed = (agentStatus === 'running' || isPlaceholder) && !hasLiveTmuxSession;\n const isRunningButStuck = isRunning && (runtime === 'idle' || runtime === 'suspended');\n const hasResumableBackingState = hasAgentState && hasWorkspace && !isPlaceholder;\n const isOrphaned = !hasLiveTmuxSession && (\n (hasSavedSession && !hasResumableBackingState)\n || (hasAgentState && (!hasWorkspace || isPlaceholder))\n );\n const requiresSessionResetBeforeFreshStart = hasSavedSession && !hasLiveTmuxSession && hasResumableBackingState && (isStopped || isCrashed);\n\n let recommendedAction: WorkAgentRecommendedAction = 'start';\n let reason: string | undefined;\n\n if (isRunningButStuck) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a live session but its runtime is ${runtime} — it is no longer making progress. Use 'pan resume ${agentOrIssueId}' to restart it.`;\n } else if (hasLiveTmuxSession && agentStatus === 'running') {\n recommendedAction = 'none';\n reason = `Agent ${agentId} is already running. Use 'pan tell' to message it.`;\n } else if (hasLiveTmuxSession && isStopped) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a live tmux session but is stopped. Use 'pan resume ${agentOrIssueId}' to continue or 'pan start ${agentOrIssueId}' will kill the session and start fresh.`;\n } else if (isOrphaned) {\n recommendedAction = 'start';\n reason = hasSavedSession\n ? `Agent ${agentId} has stale/orphaned session metadata without a resumable workspace-backed agent state. Start Agent should create a fresh session.`\n : `Agent ${agentId} is an orphaned placeholder/stale record. Start Agent should create a fresh session.`;\n } else if (requiresSessionResetBeforeFreshStart) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a resumable Claude session. Use 'pan resume ${agentOrIssueId}' to continue it, or 'pan start ${agentOrIssueId} --fresh' to start a new session (e.g. to switch model).`;\n } else if (hasAgentState && !hasSavedSession && isStopped) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} is stopped and has no saved Claude session. Start Agent will create a fresh session in the existing workspace.`;\n } else if (!hasAgentState && !hasSavedSession) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} has no prior resumable session. Start Agent will create a fresh workspace-backed session.`;\n }\n\n return {\n agentId,\n hasAgentState,\n hasLiveTmuxSession,\n hasSavedSession,\n hasWorkspace,\n isPlaceholder,\n isOrphaned,\n isRunning,\n isRunningButStuck,\n isStopped,\n isCompleted,\n isCrashed,\n runtimeState: runtime,\n agentStatus,\n canStartFresh: (!hasLiveTmuxSession || (hasLiveTmuxSession && isStopped)) && (!requiresSessionResetBeforeFreshStart || isOrphaned),\n canResumeSession: !isRunning && hasResumableBackingState && (isStopped || isCrashed),\n canRestartWithContext: hasAgentState && hasWorkspace,\n canResetSession: hasSavedSession && hasResumableBackingState,\n requiresSessionResetBeforeFreshStart,\n recommendedAction,\n reason,\n };\n}\n\ninterface StartFreshOptions {\n allowPausedForce?: boolean;\n}\n\nexport function assertCanStartFreshSync(agentOrIssueId: string, options: StartFreshOptions = {}): WorkAgentLifecycleState {\n const lifecycle = getWorkAgentLifecycleStateSync(agentOrIssueId);\n const pausedForceOverride = options.allowPausedForce === true\n && lifecycle.requiresSessionResetBeforeFreshStart\n && getAgentStateSync(lifecycle.agentId)?.paused === true;\n if (!lifecycle.canStartFresh && !pausedForceOverride) {\n throw new Error(lifecycle.reason || `Cannot start fresh for ${lifecycle.agentId}`);\n }\n return lifecycle;\n}\n\nexport function assertCanResumeSessionSync(agentOrIssueId: string): WorkAgentLifecycleState {\n const lifecycle = getWorkAgentLifecycleStateSync(agentOrIssueId);\n if (!lifecycle.canResumeSession && !lifecycle.isRunningButStuck) {\n throw new Error(lifecycle.reason || `Cannot resume session for ${lifecycle.agentId}`);\n }\n return lifecycle;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Asserts about agent lifecycle (cannot start fresh / cannot resume) fail in\n * the typed error channel as `WorkAgentLifecycleViolation`.\n */\nexport class WorkAgentLifecycleViolation extends Data.TaggedError('WorkAgentLifecycleViolation')<{\n readonly agentId: string;\n readonly reason: string;\n}> {}\n\nexport const getWorkAgentLifecycleState = (\n agentOrIssueId: string,\n): Effect.Effect<WorkAgentLifecycleState> =>\n Effect.promise(() => getWorkAgentLifecycleStateSnapshot(agentOrIssueId));\n\n/** Assert the agent can start fresh; lifts the synchronous throw to a typed error. */\nexport const assertCanStartFresh = (\n agentOrIssueId: string,\n options: { allowPausedForce?: boolean } = {},\n): Effect.Effect<WorkAgentLifecycleState, WorkAgentLifecycleViolation> =>\n Effect.try({\n try: () => assertCanStartFreshSync(agentOrIssueId, options),\n catch: (cause) =>\n new WorkAgentLifecycleViolation({\n agentId: agentOrIssueId,\n reason: cause instanceof Error ? cause.message : String(cause),\n }),\n });\n\n/** Assert the agent can resume; lifts the synchronous throw to a typed error. */\nexport const assertCanResumeSession = (\n agentOrIssueId: string,\n): Effect.Effect<WorkAgentLifecycleState, WorkAgentLifecycleViolation> =>\n Effect.try({\n try: () => assertCanResumeSessionSync(agentOrIssueId),\n catch: (cause) =>\n new WorkAgentLifecycleViolation({\n agentId: agentOrIssueId,\n reason: cause instanceof Error ? cause.message : String(cause),\n }),\n });\n","/**\n * Git info enricher (PAN-1523)\n *\n * Derives branch + worktree-status for an absolute filesystem path. Used to\n * surface branch/worktree chips on conversation rows, conversation terminals,\n * and agent terminals.\n *\n * Two enricher entrypoints with different semantics:\n * - resolveConversationGitInfo() — best-effort: returns null branch when\n * the path isn't a git repo. Used for conversations whose cwd can be\n * anywhere.\n * - resolveAgentGitInfo() — verifies a work-agent workspace against its\n * expected feature branch. Reports drift and missing-workspace state\n * because both are silent failure modes today.\n *\n * Cache: in-memory, 30s TTL, keyed by absolute path. Cache entries are\n * invalidated when the mtime of <path>/.git/HEAD changes, so a checkout\n * under a live conversation is picked up on the next list call without\n * waiting for TTL.\n *\n * All I/O is async (execFile / fs.promises). No execSync, no readFileSync.\n */\n\nimport { execFile } from 'node:child_process';\nimport { readFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\nconst GIT_INFO_TTL_MS = 30_000;\nconst GIT_INFO_TIMEOUT_MS = 2_000;\n\nexport interface ConversationGitInfo {\n /** Current branch (HEAD), null if cwd is not a git repo. */\n branch: string | null;\n /** True when cwd points at a secondary git worktree (not the primary checkout). */\n isWorktree: boolean;\n}\n\nexport interface AgentGitInfo {\n /** Current branch (HEAD), null if the workspace path is missing or not a git repo. */\n actualBranch: string | null;\n /** True when actualBranch differs from the expected feature branch. */\n branchDrifted: boolean;\n /** True when the workspacePath does not exist on disk, or is no longer a git worktree. */\n workspaceMissing: boolean;\n}\n\ninterface CacheEntry {\n value: ConversationGitInfo;\n expiresAt: number;\n headMtimeMs: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\n\n/**\n * Locate the HEAD file for a working tree at `absPath`.\n *\n * In a primary checkout `.git` is a directory and HEAD lives at `.git/HEAD`.\n * In a secondary worktree `.git` is a small text file containing\n * `gitdir: /abs/path/to/.git/worktrees/<name>` and HEAD lives at\n * `<that-gitdir>/HEAD`. We follow the pointer so the mtime check actually\n * sees branch flips in worktrees.\n */\nasync function locateHeadPath(absPath: string): Promise<string | null> {\n const gitMarker = join(absPath, '.git');\n let markerStat;\n try {\n markerStat = await stat(gitMarker);\n } catch {\n return null;\n }\n\n if (markerStat.isDirectory()) {\n return join(gitMarker, 'HEAD');\n }\n\n if (markerStat.isFile()) {\n try {\n const contents = await readFile(gitMarker, 'utf-8');\n const match = contents.match(/^gitdir:\\s*(.+)$/m);\n if (!match) return null;\n return join(match[1].trim(), 'HEAD');\n } catch {\n return null;\n }\n }\n\n return null;\n}\n\nasync function readHeadMtimeMs(absPath: string): Promise<number | null> {\n const headPath = await locateHeadPath(absPath);\n if (headPath === null) return null;\n try {\n const stats = await stat(headPath);\n return stats.mtimeMs;\n } catch {\n return null;\n }\n}\n\nasync function readGitInfoFresh(absPath: string): Promise<ConversationGitInfo> {\n const [branchResult, worktreeResult] = await Promise.all([\n execFileAsync('git', ['-C', absPath, 'rev-parse', '--abbrev-ref', 'HEAD'], {\n encoding: 'utf-8',\n timeout: GIT_INFO_TIMEOUT_MS,\n }).catch(() => null),\n execFileAsync('git', ['-C', absPath, 'rev-parse', '--git-common-dir', '--git-dir'], {\n encoding: 'utf-8',\n timeout: GIT_INFO_TIMEOUT_MS,\n }).catch(() => null),\n ]);\n\n if (!branchResult) {\n return { branch: null, isWorktree: false };\n }\n\n const branch = branchResult.stdout.trim() || null;\n\n // A secondary worktree has `git-dir` !== `git-common-dir` (worktree-specific\n // dir lives at <primary>/.git/worktrees/<name>; common dir is <primary>/.git).\n // The primary checkout has them equal.\n let isWorktree = false;\n if (worktreeResult) {\n const [commonDir, gitDir] = worktreeResult.stdout.trim().split('\\n').map((line) => line.trim());\n if (commonDir && gitDir && commonDir !== gitDir) {\n isWorktree = true;\n }\n }\n\n return { branch, isWorktree };\n}\n\n/**\n * Resolve branch + worktree status for a conversation cwd. Cached for\n * GIT_INFO_TTL_MS, invalidated on HEAD mtime change.\n */\nexport async function resolveConversationGitInfo(absPath: string): Promise<ConversationGitInfo> {\n const now = Date.now();\n const headMtimeMs = await readHeadMtimeMs(absPath);\n if (headMtimeMs === null) {\n return { branch: null, isWorktree: false };\n }\n\n const cached = cache.get(absPath);\n if (cached && cached.expiresAt > now && cached.headMtimeMs === headMtimeMs) {\n return cached.value;\n }\n\n const value = await readGitInfoFresh(absPath);\n cache.set(absPath, {\n value,\n expiresAt: now + GIT_INFO_TTL_MS,\n headMtimeMs,\n });\n return value;\n}\n\n/**\n * Resolve drift + missing-workspace state for a work-agent workspace.\n * `expectedBranch` is the deterministic feature/<issue-id> branch from\n * resource-discovery. Reuses the conversation cache for the branch read,\n * then layers drift detection on top.\n */\nexport async function resolveAgentGitInfo(\n workspacePath: string,\n expectedBranch: string,\n): Promise<AgentGitInfo> {\n const headMtimeMs = await readHeadMtimeMs(workspacePath);\n if (headMtimeMs === null) {\n return {\n actualBranch: null,\n branchDrifted: false,\n workspaceMissing: true,\n };\n }\n\n const info = await resolveConversationGitInfo(workspacePath);\n if (info.branch === null) {\n // .git/HEAD exists but rev-parse failed — corrupt or stale worktree.\n return {\n actualBranch: null,\n branchDrifted: false,\n workspaceMissing: true,\n };\n }\n\n return {\n actualBranch: info.branch,\n branchDrifted: info.branch !== expectedBranch,\n workspaceMissing: false,\n };\n}\n\n/** Test-only: reset the cache between unit tests. */\nexport function _resetGitInfoCacheForTests(): void {\n cache.clear();\n}\n","import { exec, execFile, spawn } from 'node:child_process';\nimport { timingSafeEqual } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { appendFile, mkdir, open, readFile, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect } from 'effect';\nimport { HttpServerRequest } from 'effect/unstable/http';\nimport type { AgentStatus } from '@overdeck/contracts';\n\nimport { jsonResponse } from '../../http-helpers.js';\nimport { getHeaderFromMap } from '../origin-validation.js';\nimport { getOverdeckHome } from '../../../../lib/paths.js';\nimport {\n getAgentDir,\n normalizeAgentId,\n type AgentRuntimeState,\n type AgentState,\n} from '../../../../lib/agents.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { getGitHubConfig } from '../../services/tracker-config.js';\nimport { getClosedIssueIdsForReadSource } from '../../read-model.js';\nimport { recordFeatureRegistryLifecycle } from '../../../../lib/registry/feature-registry-population.js';\nimport {\n getClaudeProjectDir as getClaudeProjectDirShared,\n getActiveSessionPath as getActiveSessionPathShared,\n getAgentWorkspace as getAgentWorkspaceShared,\n getAgentJsonlPath as getAgentJsonlPathShared,\n getPendingQuestions as getPendingQuestionsShared,\n getAgentPendingQuestions as getAgentPendingQuestionsShared,\n} from '../../../../lib/agent-enrichment.js';\nimport type { WorkAgentLifecycleState, WorkAgentRecommendedAction } from '../../../../lib/work-agent-lifecycle.js';\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { getResourceConfig, type HealthLeakedSpecialist, type SystemHealthSnapshot } from '../../services/system-health-service.js';\nimport { classifyMemoryPressure } from '../../../../lib/cloister/memory-governor.js';\nimport { capturePane } from '../../../../lib/tmux.js';\nimport type { RuntimeName } from '../../../../lib/runtimes/types.js';\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n\ntype StartAgentPhase = 'stackHealthGate' | 'guardrails' | 'spawn';\n\nexport function buildPanStartArgs(input: {\n issueId: string;\n model: string;\n harness?: RuntimeName | null;\n allowHost?: boolean;\n}): string[] {\n return [\n 'start',\n input.issueId,\n '--local',\n '--model',\n input.model,\n ...(input.harness ? ['--harness', input.harness] : []),\n ...(input.allowHost ? ['--host', '--yes'] : []),\n ];\n}\n\n/**\n * PAN-1985: detached-spawn helper for `pan <args>`, shared between the\n * standard work-spawn route and the restart-fresh route. Opens a spawn.log\n * inside the agent dir (creating the dir if missing — the dir may have been\n * just wiped), spawns `pan` detached with stdio to that log, and resolves\n * with an activity id once the child closes with code 0. Throws an Error\n * with the log contents attached on non-zero exit.\n */\nexport async function spawnPanCommandDetached(input: {\n agentSessionName: string;\n issueId: string;\n role: string;\n workspacePath: string;\n args: string[];\n cwd?: string;\n}): Promise<string> {\n const { agentSessionName, issueId, role, workspacePath, args } = input;\n const cwd = input.cwd ?? workspacePath;\n const activityId = `activity-${Date.now()}`;\n const agentDir = join(homedir(), '.overdeck', 'agents', agentSessionName);\n await mkdir(agentDir, { recursive: true });\n const spawnLogPath = join(agentDir, 'spawn.log');\n const spawnLogHandle = await open(spawnLogPath, 'a');\n const child = spawn('pan', args, {\n cwd,\n detached: true,\n stdio: ['ignore', spawnLogHandle.fd, spawnLogHandle.fd],\n });\n child.once('spawn', () => {\n void appendAgentLifecycleLog(agentSessionName, 'agent.work_spawn_process_spawned', {\n issueId,\n role,\n workspacePath,\n activityId,\n pid: child.pid,\n args,\n cwd,\n spawnLogPath,\n }).catch(() => undefined);\n });\n try {\n const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => {\n child.once('error', (error) => {\n void appendAgentLifecycleLog(agentSessionName, 'agent.work_spawn_process_error', {\n issueId,\n role,\n workspacePath,\n activityId,\n error: error.message,\n args,\n cwd,\n spawnLogPath,\n }).catch(() => undefined);\n reject(error);\n });\n child.once('close', (code, signal) => {\n void appendAgentLifecycleLog(agentSessionName, 'agent.work_spawn_process_closed', {\n issueId,\n role,\n workspacePath,\n activityId,\n code,\n signal,\n args,\n cwd,\n spawnLogPath,\n }).catch(() => undefined);\n resolve({ code, signal });\n });\n });\n if (result.code !== 0) {\n const output = await readFile(spawnLogPath, 'utf-8').catch(() => '');\n const error = new Error(output.trim() || `pan ${args.join(' ')} exited with code ${result.code ?? 'null'}`);\n Object.assign(error, { activityId, spawnLogPath, code: result.code, signal: result.signal, output });\n throw error;\n }\n return activityId;\n } finally {\n await spawnLogHandle.close();\n }\n}\ntype StartAgentPhaseStatus = 'start' | 'success' | 'failure' | 'skipped';\n\nfunction emitStartAgentPhase(\n issueId: string,\n phase: StartAgentPhase,\n status: StartAgentPhaseStatus,\n reason: string,\n details: Record<string, unknown> = {},\n): void {\n const timestamp = new Date().toISOString();\n emitActivityEntrySync({\n source: 'start-agent',\n level: status === 'failure' ? 'error' : status === 'skipped' ? 'warn' : 'info',\n message: `start-agent.phase=${phase}`,\n issueId: issueId.toUpperCase(),\n details: JSON.stringify({ issueId: issueId.toUpperCase(), timestamp, phase, status, reason, ...details }),\n });\n}\n\nconst INTERNAL_TOKEN_HEADER = 'x-overdeck-internal-token';\n\nfunction constantTimeTokenEqual(provided: string | undefined, expected: string): boolean {\n if (!provided) return false;\n const providedBuffer = Buffer.from(provided, 'utf8');\n const expectedBuffer = Buffer.from(expected, 'utf8');\n if (providedBuffer.length !== expectedBuffer.length) {\n return false;\n }\n return timingSafeEqual(providedBuffer, expectedBuffer);\n}\n\nasync function readInternalTokenForRequest(): Promise<string | null> {\n const fromEnv = process.env.OVERDECK_INTERNAL_TOKEN;\n if (fromEnv && fromEnv.length > 0) return fromEnv;\n try {\n const token = (await readFile(join(getOverdeckHome(), 'internal-token'), 'utf8')).trim();\n return token.length > 0 ? token : null;\n } catch {\n return null;\n }\n}\n\nexport async function validateAgentRuntimeEventAuth(\n request: HttpServerRequest.HttpServerRequest,\n) {\n const expected = await readInternalTokenForRequest();\n if (!expected) {\n return {\n ok: false as const,\n response: jsonResponse({ success: false, error: 'internal token not configured' }, { status: 503 }),\n };\n }\n\n const provided = getHeaderFromMap(request.headers as Record<string, string | string[] | undefined>, INTERNAL_TOKEN_HEADER);\n if (constantTimeTokenEqual(provided, expected)) return { ok: true as const };\n return {\n ok: false as const,\n response: jsonResponse({ success: false, error: 'forbidden' }, { status: 403 }),\n };\n}\n\nasync function appendAgentLifecycleLog(agentId: string, event: string, details: Record<string, unknown> = {}): Promise<void> {\n const agentDir = join(homedir(), '.overdeck', 'agents', agentId);\n await mkdir(agentDir, { recursive: true });\n const logLine = JSON.stringify({\n ts: new Date().toISOString(),\n event,\n ...details,\n });\n await appendFile(join(agentDir, 'lifecycle.log'), logLine + '\\n');\n}\n\nfunction updateRegistryForAgentStart(issueId: string, workspacePath: string, agentId: string): void {\n void recordFeatureRegistryLifecycle({\n issueId,\n workspacePath,\n agentId,\n status: 'active',\n });\n}\n\n\n// ─── Shared IssueDataService singleton ───────────────────────────────────────\n\nfunction getIssueDataService(): import('../../services/issue-data-service.js').IssueDataService {\n const { getSharedIssueService } = require('../../services/issue-service-singleton.js');\n return getSharedIssueService();\n}\n\n// ─── Cache ────────────────────────────────────────────────────────────────────\n\nconst AGENTS_CACHE_TTL_MS = 5000;\nexport const agentsCache: { data: unknown[] | null; timestamp: number } = { data: null, timestamp: 0 };\n\n/** Invalidate the agents cache so the next request re-reads all agent state. */\nexport function invalidateAgentsCache(): void {\n agentsCache.data = null;\n agentsCache.timestamp = 0;\n}\n\nfunction filterClosedIssueAgents<T>(agents: T[], issues: unknown[]): T[] {\n const closedIssueIds = getClosedIssueIdsForReadSource(issues);\n if (closedIssueIds.size === 0) return agents;\n return agents.filter((agent) => {\n if (!agent || typeof agent !== 'object') return true;\n const issueId = (agent as { issueId?: unknown }).issueId;\n return typeof issueId !== 'string' || !closedIssueIds.has(issueId.toUpperCase());\n });\n}\n\n// ─── Local helpers ────────────────────────────────────────────────────────────\n\n// Read the request body as unknown JSON\nconst readJsonBody = Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const text = yield* request.text;\n try {\n return text ? (JSON.parse(text) ?? {}) : {};\n } catch {\n return {};\n }\n});\n\nfunction toAgentStatusPayload(status: AgentState['status'] | undefined): AgentStatus {\n return status === 'starting' || status === 'running' || status === 'stopped' || status === 'error'\n ? status\n : 'unknown';\n}\n\nfunction buildAgentControlEventPayload(state: AgentState, previousStatus?: AgentStatus) {\n return {\n agentId: state.id,\n issueId: state.issueId,\n status: toAgentStatusPayload(state.status),\n previousStatus,\n stoppedByUser: state.stoppedByUser === true,\n paused: state.paused === true,\n pausedReason: state.pausedReason ?? null,\n pausedAt: state.pausedAt ?? null,\n troubled: state.troubled === true,\n troubledAt: state.troubledAt ?? null,\n consecutiveFailures: state.consecutiveFailures ?? 0,\n firstFailureInRunAt: state.firstFailureInRunAt ?? null,\n lastFailureAt: state.lastFailureAt ?? null,\n lastFailureReason: state.lastFailureReason ?? null,\n lastFailureNextRetryAt: state.lastFailureNextRetryAt ?? null,\n };\n}\n\nfunction buildAgentGateFailureSnapshot(state: Partial<AgentState>) {\n return {\n stoppedByUser: state.stoppedByUser === true,\n paused: state.paused === true,\n pausedReason: state.pausedReason ?? null,\n pausedAt: state.pausedAt ?? null,\n troubled: state.troubled === true,\n troubledAt: state.troubledAt ?? null,\n consecutiveFailures: state.consecutiveFailures ?? 0,\n firstFailureInRunAt: state.firstFailureInRunAt ?? null,\n lastFailureAt: state.lastFailureAt ?? null,\n lastFailureReason: state.lastFailureReason ?? null,\n lastFailureNextRetryAt: state.lastFailureNextRetryAt ?? null,\n };\n}\n\nfunction buildStoppedAgentLifecycle(\n agentOrIssueId: string,\n state: Partial<AgentState>,\n runtimeData: Partial<AgentRuntimeState>,\n): WorkAgentLifecycleState {\n const agentId = normalizeAgentId(agentOrIssueId);\n const hasAgentState = true;\n const hasLiveTmuxSession = false;\n const hasSavedSession = !!runtimeData.claudeSessionId;\n const hasWorkspace = typeof state.workspace === 'string' && state.workspace.length > 0;\n const agentStatus = state.status || 'unknown';\n const runtime = runtimeData.state || 'uninitialized';\n const isCompleted = runtimeData.resolution === 'completed';\n const isPlaceholder = agentStatus === 'starting' && typeof state.model === 'string' && state.model.startsWith('pending-');\n const isStopped = agentStatus === 'stopped' || agentStatus === 'error' || isCompleted || runtime === 'stopped' || runtime === 'idle' || runtime === 'suspended';\n const isRunning = false;\n const isCrashed = (agentStatus === 'running' || isPlaceholder) && !hasLiveTmuxSession;\n const isRunningButStuck = false;\n const hasResumableBackingState = hasAgentState && hasWorkspace && !isPlaceholder;\n const isOrphaned = !hasLiveTmuxSession && (\n (hasSavedSession && !hasResumableBackingState)\n || (hasAgentState && (!hasWorkspace || isPlaceholder))\n );\n const requiresSessionResetBeforeFreshStart = hasSavedSession && hasResumableBackingState && (isStopped || isCrashed);\n\n let recommendedAction: WorkAgentRecommendedAction = 'start';\n let reason: string | undefined;\n\n if (isOrphaned) {\n recommendedAction = 'start';\n reason = hasSavedSession\n ? `Agent ${agentId} has stale/orphaned session metadata without a resumable workspace-backed agent state. Start Agent should create a fresh session.`\n : `Agent ${agentId} is an orphaned placeholder/stale record. Start Agent should create a fresh session.`;\n } else if (requiresSessionResetBeforeFreshStart) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a resumable Claude session. Use 'pan resume ${agentOrIssueId}' to continue it, or 'pan start ${agentOrIssueId} --fresh' to start a new session (e.g. to switch model).`;\n } else if (hasAgentState && !hasSavedSession && isStopped) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} is stopped and has no saved Claude session. Start Agent will create a fresh session in the existing workspace.`;\n }\n\n return {\n agentId,\n hasAgentState,\n hasLiveTmuxSession,\n hasSavedSession,\n hasWorkspace,\n isPlaceholder,\n isOrphaned,\n isRunning,\n isRunningButStuck,\n isStopped,\n isCompleted,\n isCrashed,\n runtimeState: runtime,\n agentStatus,\n canStartFresh: !requiresSessionResetBeforeFreshStart || isOrphaned,\n canResumeSession: hasSavedSession && hasResumableBackingState && (isStopped || isCrashed),\n canRestartWithContext: hasAgentState && hasWorkspace,\n canResetSession: hasSavedSession && hasResumableBackingState,\n requiresSessionResetBeforeFreshStart,\n recommendedAction,\n reason,\n };\n}\n\nasync function readRemoteAgentState(agentId: string): Promise<Record<string, unknown>> {\n const remoteStateFile = join(homedir(), '.overdeck', 'agents', agentId, 'remote-state.json');\n if (!existsSync(remoteStateFile)) return {};\n try {\n return JSON.parse(await readFile(remoteStateFile, 'utf-8')) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nasync function captureAgentOutputBeforeKill(agentId: string): Promise<void> {\n const output = await Effect.runPromise(\n capturePane(agentId, 5000).pipe(Effect.catch(() => Effect.succeed(''))),\n );\n if (!output) return;\n\n const agentDir = getAgentDir(agentId);\n await mkdir(agentDir, { recursive: true });\n await writeFile(join(agentDir, 'output.log'), output);\n}\n\nfunction buildHostOverrideConfirmation(issueId: string): string {\n return `I understand this bypasses workspace isolation for ${issueId.toUpperCase()}`;\n}\n\nfunction getProjectPath(linearProjectId?: string, issuePrefix?: string): string {\n if (issuePrefix) {\n const issueId = `${issuePrefix}-1`;\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) return resolved.projectPath;\n const config = getGitHubConfig();\n if (config) {\n for (const { owner, repo, prefix } of config.repos) {\n const repoPrefix = prefix || repo.toUpperCase().replace(/-CLI$/, '').replace(/-/g, '');\n if (repoPrefix.toUpperCase() === issuePrefix.toUpperCase()) {\n const possiblePaths = [\n join(homedir(), 'Projects', repo),\n join(homedir(), 'Projects', repo.replace(/-cli$/, '')),\n join(homedir(), 'Projects', owner, repo),\n ];\n for (const path of possiblePaths) {\n if (existsSync(path)) return path;\n }\n }\n }\n }\n }\n return join(homedir(), 'Projects');\n}\n\nasync function getWorkspaceLocation(issueId: string): Promise<'local' | 'remote' | undefined> {\n try {\n const workspacesDir = join(homedir(), '.overdeck', 'workspaces');\n const variations = [issueId.toLowerCase(), issueId.toUpperCase(), issueId];\n for (const v of variations) {\n const yamlPath = join(workspacesDir, `${v}.yaml`);\n if (existsSync(yamlPath)) {\n const content = await readFile(yamlPath, 'utf-8');\n if (content.includes('location: remote')) return 'remote';\n return 'local';\n }\n }\n } catch {}\n return undefined;\n}\n\nasync function getGitStatusAsync(workspacePath: string): Promise<{ branch: string; uncommittedFiles: number; latestCommit: string } | null> {\n try {\n if (!existsSync(workspacePath)) return null;\n const [branchResult, uncommittedResult, commitResult] = await Promise.all([\n execAsync('git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"', { cwd: workspacePath }),\n execAsync('git status --porcelain 2>/dev/null | wc -l', { cwd: workspacePath }),\n execAsync('git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"', { cwd: workspacePath }),\n ]);\n const branch = branchResult.stdout.trim();\n const uncommitted = uncommittedResult.stdout.trim();\n const latestCommit = commitResult.stdout.trim();\n if (!branch) return null;\n return {\n branch,\n uncommittedFiles: parseInt(uncommitted) || 0,\n latestCommit: latestCommit.slice(0, 60) + (latestCommit.length > 60 ? '...' : ''),\n };\n } catch {\n return null;\n }\n}\n\ninterface SpawnGuardrailAdvisory {\n severity: 'warning' | 'critical';\n code: 'memory_pressure' | 'agent_capacity' | 'leaked_specialists';\n message: string;\n}\n\nexport interface SpawnGuardrailDecision {\n blocked: boolean;\n requiresAcknowledgement: boolean;\n status: number;\n error?: string;\n hint?: string;\n warnings: SpawnGuardrailAdvisory[];\n health: Pick<SystemHealthSnapshot, 'severity' | 'summary' | 'reasons' | 'leakedSpecialists'>;\n}\n\nfunction formatLeakedSpecialistSummary(leaked: HealthLeakedSpecialist[]): string {\n return leaked\n .slice(0, 3)\n .map((item) => `${item.name} (${item.currentIssue})`)\n .join(', ');\n}\n\nfunction resolveAgentCountEnv(varName: string, fallback: number): number {\n const raw = process.env[varName];\n if (raw == null || raw.trim() === '') return fallback;\n const parsed = Number(raw);\n return Number.isFinite(parsed) ? Math.max(1, Math.floor(parsed)) : fallback;\n}\n\nexport interface AgentStartGateDecision {\n success: false;\n blocked: true;\n skipped: true;\n error: string;\n hint: string;\n agentId: string;\n paused: boolean;\n troubled: boolean;\n}\n\nexport function evaluateAgentStartGate(\n agentId: string,\n state: Pick<AgentState, 'paused' | 'pausedReason' | 'troubled' | 'consecutiveFailures'> | null | undefined,\n): AgentStartGateDecision | null {\n if (state?.paused === true) {\n const reason = state.pausedReason ? ` (${state.pausedReason})` : '';\n return {\n success: false,\n blocked: true,\n skipped: true,\n error: `Agent ${agentId} is paused${reason}.`,\n hint: `Run pan unpause ${agentId} before starting it from the dashboard.`,\n agentId,\n paused: true,\n troubled: state.troubled === true,\n };\n }\n\n if (state?.troubled === true) {\n const failures = state.consecutiveFailures ?? 0;\n return {\n success: false,\n blocked: true,\n skipped: true,\n error: `Agent ${agentId} is troubled (${failures} failure${failures === 1 ? '' : 's'}).`,\n hint: `Investigate the crash cause, then run pan untroubled ${agentId} before starting it from the dashboard.`,\n agentId,\n paused: false,\n troubled: true,\n };\n }\n\n return null;\n}\n\nexport function hasActiveAgentGateOrRetry(\n state: Pick<AgentState, 'paused' | 'troubled' | 'lastFailureNextRetryAt'>,\n nowMs: number = Date.now(),\n): boolean {\n if (state.paused === true || state.troubled === true) return true;\n if (!state.lastFailureNextRetryAt) return false;\n const retryAtMs = Date.parse(state.lastFailureNextRetryAt);\n return Number.isFinite(retryAtMs) && retryAtMs > nowMs;\n}\n\nexport function evaluateSpawnGuardrails(health: SystemHealthSnapshot): SpawnGuardrailDecision {\n const warnings: SpawnGuardrailAdvisory[] = [];\n const availableGb = Math.round((health.summary.availableMemoryBytes / (1024 ** 3)) * 10) / 10;\n const workAgentCount = health.summary.workAgentCount;\n const leakedSpecialists = health.leakedSpecialists;\n const resourceConfig = getResourceConfig();\n const hardWorkAgentLimit = resolveAgentCountEnv('PAN_AGENT_BLOCK_COUNT', resourceConfig.agentBlockCount);\n const warnWorkAgentLimit = resolveAgentCountEnv('PAN_AGENT_WARN_COUNT', resourceConfig.agentWarnCount);\n\n const memoryBand = classifyMemoryPressure(health.summary.availableMemoryBytes, {\n warningBytes: health.thresholds.memoryAvailableWarningBytes,\n criticalBytes: health.thresholds.memoryAvailableCriticalBytes,\n });\n if (memoryBand === 'hard') {\n warnings.push({\n severity: 'critical',\n code: 'memory_pressure',\n message: `Available RAM is critically low (${availableGb} GB).`,\n });\n } else if (memoryBand === 'soft') {\n warnings.push({\n severity: 'warning',\n code: 'memory_pressure',\n message: `Available RAM is tight (${availableGb} GB).`,\n });\n }\n\n if (workAgentCount >= hardWorkAgentLimit) {\n warnings.push({\n severity: 'warning',\n code: 'agent_capacity',\n message: `Work agent count is at the configured ceiling (${workAgentCount}/${hardWorkAgentLimit}).`,\n });\n } else if (workAgentCount >= warnWorkAgentLimit) {\n warnings.push({\n severity: 'warning',\n code: 'agent_capacity',\n message: `Work agent count is high (${workAgentCount}/${hardWorkAgentLimit}).`,\n });\n }\n\n if (leakedSpecialists.length > 0) {\n warnings.push({\n severity: health.summary.availableMemoryBytes < health.thresholds.memoryAvailableCriticalBytes ? 'critical' : 'warning',\n code: 'leaked_specialists',\n message: `Leaked specialist sessions detected: ${formatLeakedSpecialistSummary(leakedSpecialists)}${leakedSpecialists.length > 3 ? `, +${leakedSpecialists.length - 3} more` : ''}.`,\n });\n }\n\n const blockingWarnings = warnings.filter((warning) => warning.severity === 'critical');\n if (blockingWarnings.length > 0) {\n const hasLeakedSpecialists = leakedSpecialists.length > 0;\n return {\n blocked: true,\n requiresAcknowledgement: false,\n status: 429,\n error: blockingWarnings[0]?.message ?? 'System health is blocking new agent spawns.',\n hint: hasLeakedSpecialists\n ? 'Clean up leaked specialist sessions first, then retry the spawn.'\n : 'Reduce memory pressure or active work-agent count before retrying.',\n warnings,\n health: {\n severity: health.severity,\n summary: health.summary,\n reasons: health.reasons,\n leakedSpecialists: health.leakedSpecialists,\n },\n };\n }\n\n return {\n blocked: false,\n requiresAcknowledgement: warnings.length > 0,\n status: warnings.length > 0 ? 409 : 200,\n hint: warnings.length > 0 ? 'Acknowledge the system health warnings before starting this agent.' : undefined,\n warnings,\n health: {\n severity: health.severity,\n summary: health.summary,\n reasons: health.reasons,\n leakedSpecialists: health.leakedSpecialists,\n },\n };\n}\n\n// Shared enrichment utilities (PAN-440) — aliases for readability\nconst getClaudeProjectDir = getClaudeProjectDirShared;\nconst getActiveSessionPath = getActiveSessionPathShared;\nconst getAgentWorkspace = getAgentWorkspaceShared;\nconst getAgentJsonlPath = getAgentJsonlPathShared;\nconst getPendingQuestions = getPendingQuestionsShared;\nconst getAgentPendingQuestions = getAgentPendingQuestionsShared;\n\nfunction flyExecCmd(vmName: string, command: string): string {\n const appName = vmName.replace(/\\/.*$/, ''); // simplified: use vmName as app name\n return `fly ssh console -a ${appName} -C ${JSON.stringify(command)}`;\n}\n\nexport {\n execAsync,\n execFileAsync,\n constantTimeTokenEqual,\n emitStartAgentPhase,\n appendAgentLifecycleLog,\n updateRegistryForAgentStart,\n getIssueDataService,\n AGENTS_CACHE_TTL_MS,\n filterClosedIssueAgents,\n readJsonBody,\n toAgentStatusPayload,\n buildAgentControlEventPayload,\n buildAgentGateFailureSnapshot,\n buildStoppedAgentLifecycle,\n readRemoteAgentState,\n captureAgentOutputBeforeKill,\n buildHostOverrideConfirmation,\n getProjectPath,\n getWorkspaceLocation,\n getGitStatusAsync,\n resolveAgentCountEnv,\n formatLeakedSpecialistSummary,\n getClaudeProjectDir,\n getActiveSessionPath,\n getAgentWorkspace,\n getAgentJsonlPath,\n getPendingQuestions,\n getAgentPendingQuestions,\n flyExecCmd,\n};\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter } from 'effect/unstable/http';\n\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getAgentState,\n getAgentRuntimeState,\n listAgentStates,\n type AgentState,\n} from '../../../../lib/agents.js';\nimport { getReviewStatusSync } from '../../../../lib/review-status.js';\nimport { computeAgentEnrichment } from '../../../../lib/agent-enrichment.js';\nimport { normalizeAwaitingInputPrompt } from '../../../../lib/agent-input-detection.js';\nimport { getWorkAgentLifecycleState } from '../../../../lib/work-agent-lifecycle.js';\nimport { resolveAgentGitInfo } from '../../services/git-info.js';\nimport { listSessions, sessionExists } from '../../../../lib/tmux.js';\nimport {\n AGENTS_CACHE_TTL_MS,\n agentsCache,\n buildAgentGateFailureSnapshot,\n buildStoppedAgentLifecycle,\n filterClosedIssueAgents,\n getGitStatusAsync,\n getIssueDataService,\n getWorkspaceLocation,\n hasActiveAgentGateOrRetry,\n readRemoteAgentState,\n} from './shared.js';\n\n// ─── Route: GET /api/agents ───────────────────────────────────────────────────\n\nexport const getAgentsRoute = HttpRouter.add(\n 'GET',\n '/api/agents',\n httpHandler(Effect.gen(function* () {\n const now = Date.now();\n\n if (agentsCache.data && (now - agentsCache.timestamp) < AGENTS_CACHE_TTL_MS) {\n return jsonResponse(agentsCache.data);\n }\n\n const sessions = yield* listSessions();\n const sessionByName = new Map(sessions.map((session) => [session.name, session]));\n const registeredStates = listAgentStates()\n .filter((state) => state.id.startsWith('agent-') || state.id.startsWith('planning-') || state.id.startsWith('strike-'));\n\n const allAgents = (yield* Effect.promise(() => Promise.all(\n registeredStates.map(async (state) => {\n const name = state.id;\n const isPlanning = name.startsWith('planning-');\n const isStrike = name.startsWith('strike-');\n const issueId = state.issueId?.toUpperCase() ||\n (isPlanning ? name.replace('planning-', '') : isStrike ? name.replace('strike-', '') : name.replace('agent-', '')).toUpperCase();\n const session = sessionByName.get(name);\n const remoteState = await readRemoteAgentState(name);\n const isRemote = remoteState.location === 'remote';\n const runtimeData = await Effect.runPromise(getAgentRuntimeState(name));\n const startedAt = state.startedAt || (session ? new Date(session.created).toISOString() : new Date().toISOString());\n const healthFile = join(homedir(), '.overdeck', 'agents', name, 'health.json');\n let health: any = { killCount: 0 };\n if (existsSync(healthFile)) {\n try { health = { ...health, ...JSON.parse(await readFile(healthFile, 'utf-8')) }; } catch {}\n }\n\n if (state.status === 'stopped') {\n const stoppedTimestamp = state.stoppedAt || runtimeData?.lastActivity || state.lastActivity;\n const stoppedAt = stoppedTimestamp ? new Date(stoppedTimestamp) : null;\n const reviewStatus = getReviewStatusSync(issueId);\n const keepStoppedAgentVisible =\n hasActiveAgentGateOrRetry(state, now) ||\n (\n !!reviewStatus &&\n reviewStatus.mergeStatus !== 'merged' &&\n (\n !!reviewStatus.prUrl ||\n reviewStatus.readyForMerge === true ||\n reviewStatus.reviewStatus !== 'pending' ||\n reviewStatus.testStatus !== 'pending' ||\n reviewStatus.mergeStatus === 'failed'\n )\n );\n if (stoppedAt && (now - stoppedAt.getTime()) > 60 * 60 * 1000 && !keepStoppedAgentVisible) return null;\n const lifecycle = buildStoppedAgentLifecycle(name, state, runtimeData ?? {});\n const needsInput = runtimeData?.resolution === 'needs_input';\n const pendingQuestionPrompt = needsInput\n ? normalizeAwaitingInputPrompt(\n runtimeData?.waitingNotification ||\n 'Agent stopped because it needs human input or hit a blocker',\n )\n : undefined;\n const pendingQuestionReason = needsInput\n ? runtimeData?.waitingReason || 'other'\n : undefined;\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'stopped' as const,\n startedAt,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace: state.workspace || null,\n workspaceLocation: isRemote ? 'remote' : 'local',\n git: null,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasPendingQuestion: needsInput,\n pendingQuestionCount: 0,\n pendingQuestionPrompt,\n pendingQuestionReason,\n resolution: runtimeData?.resolution || 'working',\n resolutionCount: runtimeData?.resolutionCount || 0,\n hasSession: lifecycle.canResumeSession,\n lifecycle,\n ...(isRemote ? { remote: true, vmName: remoteState.vmName } : {}),\n };\n }\n\n if (state.status === 'starting') {\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'starting' as const,\n startedAt,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace: state.workspace || null,\n workspaceLocation: isRemote ? 'remote' : 'local',\n git: null,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasPendingQuestion: false,\n pendingQuestionCount: 0,\n message: (state as { message?: string }).message || 'Starting...',\n ...(isRemote ? { remote: true, vmName: remoteState.vmName } : {}),\n };\n }\n\n if (state.status === 'error') {\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'error' as const,\n startedAt,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace: state.workspace || null,\n workspaceLocation: isRemote ? 'remote' : 'local',\n git: null,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasPendingQuestion: false,\n pendingQuestionCount: 0,\n error: state.lastFailureReason || 'Unknown error',\n ...(isRemote ? { remote: true, vmName: remoteState.vmName } : {}),\n };\n }\n\n if (!session && !isRemote) {\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'unknown' as const,\n startedAt,\n lastActivity: runtimeData?.lastActivity || state.lastActivity,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace: state.workspace || null,\n workspaceLocation: 'local' as const,\n git: null,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasLiveTmuxSession: false,\n hasPendingQuestion: false,\n pendingQuestionCount: 0,\n lastFailureReason: state.lastFailureReason || 'No live tmux session found for registered agent',\n resolution: runtimeData?.resolution || 'working',\n resolutionCount: runtimeData?.resolutionCount || 0,\n };\n }\n\n const issueReviewStatus = getReviewStatusSync(issueId);\n const hasActiveSpecialist = issueReviewStatus?.reviewStatus === 'reviewing'\n || issueReviewStatus?.testStatus === 'testing'\n || issueReviewStatus?.mergeStatus === 'merging';\n const enrichment = await Effect.runPromise(computeAgentEnrichment(name, startedAt, hasActiveSpecialist));\n const workspaceLocation = isRemote ? 'remote' : await getWorkspaceLocation(issueId);\n const workspace = isRemote && remoteState.vmName\n ? `/workspace (${String(remoteState.vmName)})`\n : state.workspace || null;\n const gitStatus = workspace && !isRemote ? await getGitStatusAsync(workspace) : null;\n\n let contextPercent: number | null = null;\n let initialContextPercent: number | null = null;\n const agentCtxDir = join(homedir(), '.overdeck', 'agents', name);\n try {\n const ctxFile = join(agentCtxDir, 'context-pct');\n contextPercent = parseInt((await readFile(ctxFile, 'utf-8').catch(() => '')).trim(), 10) || null;\n const initCtxFile = join(agentCtxDir, 'initial-context-pct');\n initialContextPercent = parseInt((await readFile(initCtxFile, 'utf-8').catch(() => '')).trim(), 10) || null;\n } catch {}\n\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'healthy' as const,\n startedAt,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace,\n workspaceLocation,\n git: gitStatus,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasLiveTmuxSession: true,\n hasPendingQuestion: enrichment.hasPendingQuestion,\n pendingQuestionCount: enrichment.pendingQuestionCount,\n pendingQuestionPrompt: enrichment.pendingQuestionPrompt,\n pendingQuestionReason: enrichment.pendingQuestionReason,\n pendingInputCount: enrichment.pendingInputCount,\n pendingInputKinds: enrichment.pendingInputKinds,\n pendingAskUserQuestion: enrichment.pendingAskUserQuestion,\n resolution: runtimeData?.resolution || enrichment.resolution || 'working',\n resolutionCount: runtimeData?.resolutionCount || enrichment.resolutionCount || 0,\n contextPercent,\n initialContextPercent,\n ...(isRemote ? { remote: true, vmName: remoteState.vmName } : {}),\n };\n }),\n ))).filter(Boolean);\n const visibleAgents = filterClosedIssueAgents(allAgents, getIssueDataService().getIssues());\n agentsCache.data = visibleAgents;\n agentsCache.timestamp = now;\n return jsonResponse(visibleAgents);\n })),\n);\n\n// ─── Route: GET /api/agents/:id/git-info ─────────────────────────────────────\n//\n// Branch + worktree status for the agent's workspace (PAN-1523). Used by\n// AgentOutputPanel to render the Local/Worktree/Drifted chip in the panel\n// header. Work agents don't have a conversation row to enrich, so the panel\n// queries this dedicated endpoint instead.\n\n/**\n * True when the agent has a concrete workspace + issue we can evaluate git\n * state for. When false, the git-info route must NOT claim the worktree is\n * missing — an unresolvable session id (e.g. a legacy / JSONL-only \"Planning\n * state\" node) or an agent that never got a workspace is \"unknown\", not\n * \"workspace gone from disk\". Conflating the two made such nodes falsely render\n * \"Worktree missing\" in the SessionPanel chip (PAN-1718).\n */\nexport function agentHasResolvableWorkspace(\n agentState: AgentState | null,\n): agentState is AgentState {\n return Boolean(agentState?.workspace && agentState.issueId);\n}\n\n/**\n * Benign git-info response for a session we cannot resolve to a workspace-bound\n * agent. workspaceMissing is false (not true): we have no path to stat, so we\n * cannot assert the worktree is gone. The frontend chip hides on this shape\n * (showChip = actualBranch || workspaceMissing). The genuine on-disk\n * \"workspace missing\" case is detected separately by resolveAgentGitInfo, which\n * stats the real path. See PAN-1718.\n */\nexport const UNRESOLVABLE_AGENT_GIT_INFO = {\n actualBranch: null,\n branchDrifted: false,\n workspaceMissing: false,\n expectedBranch: null,\n} as const;\n\nexport const getAgentGitInfoRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/git-info',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ error: 'missing agent id' }, { status: 400 });\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentHasResolvableWorkspace(agentState)) {\n // PAN-1718: unknown session id / no workspace bound → \"unknown\", not\n // \"worktree missing\". Return the benign shape so the chip hides instead of\n // flashing a false alarm. Real on-disk absence is caught by\n // resolveAgentGitInfo below.\n return jsonResponse(UNRESOLVABLE_AGENT_GIT_INFO);\n }\n\n const expectedBranch = `feature/${agentState.issueId.toLowerCase()}`;\n const info = yield* Effect.promise(() =>\n resolveAgentGitInfo(agentState.workspace as string, expectedBranch),\n );\n return jsonResponse({\n ...info,\n expectedBranch,\n workspacePath: agentState.workspace,\n });\n })),\n);\n\n// ─── Route: GET /api/agents/:id/tmux-alive ──────────────────────────────────\n\nexport const getAgentTmuxAliveRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/tmux-alive',\n Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const agentId = params['id'] ?? '';\n const alive = yield* sessionExists(agentId);\n return jsonResponse({ alive });\n }),\n);\n\n// ─── Route: GET /api/agents/:id/has-session ─────────────────────────────────\n// Returns whether a stopped agent has a resumable Claude session.\n\nexport const getAgentHasSessionRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/has-session',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const lifecycle = yield* getWorkAgentLifecycleState(id);\n return jsonResponse({\n hasSession: lifecycle.canResumeSession,\n lifecycle,\n });\n })),\n);\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport type { ConversationResponse } from '@overdeck/contracts';\nimport { Effect, Option } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { encodeClaudeProjectDir } from '../../../../lib/paths.js';\nimport {\n getActivity,\n getAgentState,\n} from '../../../../lib/agents.js';\nimport { capturePane } from '../../../../lib/tmux.js';\nimport { parseEntireConversation } from '../../services/conversation-service.js';\nimport { parsePiConversationMessages } from '../../services/pi-conversation-parser.js';\nimport { parseOhmypiConversationMessages } from '../../services/ohmypi-conversation-parser.js';\nimport { parseCodexConversationMessages } from '../../services/codex-conversation-parser.js';\nimport {\n readLauncherPinnedSessionId,\n resolvePiSessionPath,\n resolveCodexRolloutPath,\n resolveAgentHarness,\n} from '../jsonl-resolver.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n execAsync,\n getAgentJsonlPath,\n getAgentWorkspace,\n} from './shared.js';\n\n// ─── Route: GET /api/agents/:id/output ───────────────────────────────────────\n\nexport const getAgentOutputRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/output',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n const lines = Option.isSome(urlOpt) ? (urlOpt.value.searchParams.get('lines') ?? '100') : '100';\n\n return yield* Effect.promise(async () => {\n try {\n const agentStateDir = join(homedir(), '.overdeck', 'agents', id);\n const remoteStateFile = join(agentStateDir, 'remote-state.json');\n let isRemote = false;\n let vmName = '';\n\n if (existsSync(remoteStateFile)) {\n try {\n const state = JSON.parse(await readFile(remoteStateFile, 'utf-8'));\n if (state.location === 'remote' && state.vmName) {\n isRemote = true;\n vmName = state.vmName;\n }\n } catch {}\n }\n\n let stdout: string;\n if (isRemote && vmName) {\n const { getRemoteAgentOutput } = await import('../../../../lib/remote/remote-agents.js');\n stdout = await getRemoteAgentOutput(id, vmName, parseInt(String(lines), 10) || 100);\n } else {\n stdout = await Effect.runPromise(capturePane(id, parseInt(String(lines), 10) || 100));\n }\n\n if (!stdout || stdout.trim() === '' || stdout.trim() === 'Session not found') {\n const savedLog = join(agentStateDir, 'output.log');\n const logContent = await readFile(savedLog, 'utf-8').catch(() => null);\n if (logContent) {\n const logLines = logContent.split('\\n');\n const numLines = parseInt(String(lines), 10) || 100;\n stdout = logLines.slice(-numLines).join('\\n');\n }\n }\n\n if (stdout?.trim() === 'Session not found') {\n stdout = '';\n }\n\n return jsonResponse({ output: stdout });\n } catch (error: unknown) {\n // Try saved log on error\n try {\n const agentStateDir = join(homedir(), '.overdeck', 'agents', id);\n const savedLog = join(agentStateDir, 'output.log');\n const logContent = await readFile(savedLog, 'utf-8').catch(() => null);\n if (logContent) return jsonResponse({ output: logContent });\n } catch {}\n return jsonResponse({ output: '' });\n }\n })\n })),\n);\n\n// ─── Route: GET /api/agents/:id/conversation ─────────────────────────────────\n\nconst EMPTY_CONVERSATION: ConversationResponse = { messages: [], workLog: [], streaming: false, totalCost: 0, byteOffset: 0 };\n\n/**\n * Resolve and parse an agent's conversation JSONL file.\n * Exported for unit testing — the Effect route layer is not directly unit-testable.\n *\n * Dispatches on harness so Pi and Codex agents get their native parsers (PAN-2012).\n * For claude-code agents, tries the launcher-pinned --session-id first (the exact\n * session the Terminal tab attaches to) before falling back to mtime-based pick\n * (PAN-2011). This makes the Conversation tab match the Terminal tab by construction.\n */\nexport async function buildConversationResponse(id: string): Promise<ConversationResponse> {\n try {\n const harness = await resolveAgentHarness(id);\n\n if (harness === 'ohmypi') {\n const sessionFile = await resolvePiSessionPath(id);\n if (!sessionFile || !existsSync(sessionFile)) return EMPTY_CONVERSATION;\n const result = await parseOhmypiConversationMessages(sessionFile);\n return { ...result, streaming: false };\n }\n\n if (harness === 'pi') {\n const sessionFile = await resolvePiSessionPath(id);\n if (!sessionFile || !existsSync(sessionFile)) return EMPTY_CONVERSATION;\n const result = await parsePiConversationMessages(sessionFile);\n return { ...result, streaming: false };\n }\n\n if (harness === 'codex') {\n const sessionFile = await resolveCodexRolloutPath(id);\n if (!sessionFile || !existsSync(sessionFile)) return EMPTY_CONVERSATION;\n const result = await parseCodexConversationMessages(sessionFile);\n return { ...result, streaming: false };\n }\n\n // claude-code (default): try launcher-pinned session ID first (ground truth),\n // then fall back to mtime-based pick.\n let jsonlPath: string | null = null;\n const pinnedSessionId = await readLauncherPinnedSessionId(id);\n if (pinnedSessionId) {\n const workspace = await Effect.runPromise(getAgentWorkspace(id));\n if (workspace) {\n const candidate = join(\n homedir(), '.claude', 'projects',\n encodeClaudeProjectDir(workspace),\n `${pinnedSessionId}.jsonl`,\n );\n if (existsSync(candidate)) jsonlPath = candidate;\n }\n }\n if (!jsonlPath) {\n jsonlPath = await Effect.runPromise(getAgentJsonlPath(id));\n }\n\n if (!jsonlPath || !existsSync(jsonlPath)) return EMPTY_CONVERSATION;\n // parseEntireConversation, not parseConversationMessages: a single parse caps\n // at MAX_READ_BYTES (10 MB) and would drop the most recent turns of a larger\n // transcript (PAN-1989). This one-shot endpoint must return the whole file.\n const result = await parseEntireConversation(jsonlPath);\n // Force streaming: false — tmux session is dead, any \"streaming\" state is stale\n return { ...result, streaming: false };\n } catch (err) {\n console.error('[conversation] failed for', id, err);\n return EMPTY_CONVERSATION;\n }\n}\n\nexport const getAgentConversationRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/conversation',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n return yield* Effect.promise(async () => jsonResponse(await buildConversationResponse(id)));\n })),\n);\n\n// ─── Route: GET /api/agents/:id/activity ─────────────────────────────────────\n\nexport const getAgentActivityRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/activity',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n const limitStr = Option.isSome(urlOpt) ? (urlOpt.value.searchParams.get('limit') ?? '100') : '100';\n const limit = parseInt(limitStr) || 100;\n\n const activity = getActivity(id, limit);\n return jsonResponse({ activity });\n })),\n);\n\n// ─── Route: GET /api/agents/:id/files ────────────────────────────────────────\n\nexport const getAgentFilesRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/files',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const agentState = yield* getAgentState(id);\n if (!agentState?.workspace) {\n return jsonResponse({ files: [] });\n }\n const workspacePath = agentState.workspace;\n if (!existsSync(workspacePath)) {\n return jsonResponse({ files: [] });\n }\n const { stdout } = yield* Effect.promise(() => execAsync(\n 'git diff --name-status HEAD 2>/dev/null || git status --porcelain 2>/dev/null || echo \"\"',\n { cwd: workspacePath, encoding: 'utf-8' }\n ));\n const files = stdout\n .split('\\n')\n .filter(l => l.trim())\n .map(l => {\n const parts = l.trim().split(/\\s+/);\n if (parts.length >= 2) {\n return { status: parts[0], path: parts[parts.length - 1] };\n }\n return { status: '?', path: l.trim() };\n })\n .filter(f => f.path);\n return jsonResponse({ files });\n })),\n);\n\n// ─── Route: GET /api/agents/:id/timeline ─────────────────────────────────────\n\nexport const getAgentTimelineRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/timeline',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n const limitStr = Option.isSome(urlOpt) ? (urlOpt.value.searchParams.get('limit') ?? '50') : '50';\n const limit = parseInt(limitStr) || 50;\n\n const activity = getActivity(id, limit);\n const agentState = yield* getAgentState(id);\n const events = activity.map((a: any) => ({\n timestamp: a.timestamp || new Date().toISOString(),\n type: a.type || 'activity',\n message: a.message || a.content || '',\n }));\n if (agentState?.startedAt) {\n events.unshift({ timestamp: agentState.startedAt, type: 'started', message: 'Agent started' });\n }\n events.sort((a: any, b: any) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n return jsonResponse({ timeline: events.slice(0, limit) });\n })),\n);\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { messageAgent } from '../../../../lib/agents.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport { readJsonBody } from './shared.js';\n\nasync function sendAgentMessage(id: string, message: string) {\n const agentStateDir = join(homedir(), '.overdeck', 'agents', id);\n const remoteStateFile = join(agentStateDir, 'remote-state.json');\n let isRemote = false;\n\n if (existsSync(remoteStateFile)) {\n try {\n const state = JSON.parse(await readFile(remoteStateFile, 'utf-8'));\n isRemote = state.location === 'remote' && Boolean(state.vmName);\n } catch {}\n }\n\n await messageAgent(id, message, 'dashboard:user-message');\n return isRemote ? { success: true, remote: true } : { success: true };\n}\n\nexport function validateAgentMessageOrigin(request: HttpServerRequest.HttpServerRequest) {\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return {\n ok: false as const,\n response: jsonResponse({ ok: false, error: originCheck.error }, { status: 403 }),\n };\n }\n return { ok: true as const };\n}\n\nfunction postAgentMessageLikeRoute(path: `/${string}`) {\n return HttpRouter.add(\n 'POST',\n path,\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateAgentMessageOrigin(request);\n if (!originCheck.ok) return originCheck.response;\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { message } = body as any;\n if (!message) {\n return jsonResponse({ error: 'Message required' }, { status: 400 });\n }\n\n return yield* Effect.promise(() => sendAgentMessage(id, message)).pipe(\n Effect.map((result) => jsonResponse(result)),\n );\n })),\n );\n}\n\n// ─── Route: POST /api/agents/:id/message ─────────────────────────────────────\n\nexport const postAgentMessageRoute = postAgentMessageLikeRoute('/api/agents/:id/message');\n\n// ─── Route: POST /api/agents/:id/tell ────────────────────────────────────────\n\nexport const postAgentTellRoute = postAgentMessageLikeRoute('/api/agents/:id/tell');\n\n// ─── Route: POST /api/agents/:id/poke ────────────────────────────────────────\n\nexport const postAgentPokeRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/poke',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { message } = body as any;\n const defaultPokeMessage =\n \"You seem to have been inactive for a while. If you're stuck:\\n\" +\n '1. Check your current task in continue.vbrief.json\\n' +\n '2. Try an alternative approach if blocked\\n' +\n '3. Ask for help if needed\\n\\n' +\n \"What's your current status?\";\n const pokeMsg = message || defaultPokeMessage;\n yield* Effect.promise(() => messageAgent(id, pokeMsg));\n return jsonResponse({ success: true, message: 'Agent poked successfully' });\n })),\n);\n","import { Data, Effect } from 'effect';\n\nexport const CHANNEL_PERMISSION_LIMITS = {\n requestIdBytes: 128,\n toolNameBytes: 128,\n descriptionBytes: 2 * 1024,\n inputPreviewBytes: 16 * 1024,\n} as const;\n\nexport interface NormalizedChannelPermissionRequestFields {\n requestId: string;\n toolName: string;\n description: string;\n inputPreview: string;\n}\n\n/** A permission request field failed validation. */\nexport class PermissionPayloadValidationError extends Data.TaggedError(\n 'PermissionPayloadValidationError',\n)<{\n readonly field: string;\n readonly message: string;\n}> {}\n\nexport function utf8ByteLength(value: string): number {\n return Buffer.byteLength(value, 'utf8');\n}\n\nexport function normalizePermissionInputPreview(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nexport function normalizeChannelPermissionRequestFields(input: {\n requestId: unknown;\n toolName: unknown;\n description: unknown;\n inputPreview?: unknown;\n}): Effect.Effect<NormalizedChannelPermissionRequestFields, PermissionPayloadValidationError> {\n return Effect.gen(function* () {\n const requestId = typeof input.requestId === 'string' ? input.requestId.trim() : '';\n if (!requestId) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({ field: 'requestId', message: 'requestId is required' }),\n );\n }\n if (utf8ByteLength(requestId) > CHANNEL_PERMISSION_LIMITS.requestIdBytes) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'requestId',\n message: `requestId exceeds ${CHANNEL_PERMISSION_LIMITS.requestIdBytes} bytes`,\n }),\n );\n }\n\n const toolName = typeof input.toolName === 'string' ? input.toolName.trim() : '';\n if (!toolName) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({ field: 'toolName', message: 'toolName is required' }),\n );\n }\n if (utf8ByteLength(toolName) > CHANNEL_PERMISSION_LIMITS.toolNameBytes) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'toolName',\n message: `toolName exceeds ${CHANNEL_PERMISSION_LIMITS.toolNameBytes} bytes`,\n }),\n );\n }\n\n const description = typeof input.description === 'string' ? input.description.trim() : '';\n if (!description) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'description',\n message: 'description is required',\n }),\n );\n }\n if (utf8ByteLength(description) > CHANNEL_PERMISSION_LIMITS.descriptionBytes) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'description',\n message: `description exceeds ${CHANNEL_PERMISSION_LIMITS.descriptionBytes} bytes`,\n }),\n );\n }\n\n const inputPreview = normalizePermissionInputPreview(input.inputPreview);\n if (utf8ByteLength(inputPreview) > CHANNEL_PERMISSION_LIMITS.inputPreviewBytes) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'inputPreview',\n message: `inputPreview exceeds ${CHANNEL_PERMISSION_LIMITS.inputPreviewBytes} bytes`,\n }),\n );\n }\n\n return { requestId, toolName, description, inputPreview };\n });\n}\n","import type { ChannelPermissionRequestSnapshot, ResolvedChannelPermissionDecision as ContractsResolvedChannelPermissionDecision } from '@overdeck/contracts';\nimport { Effect } from 'effect';\nimport * as Result from 'effect/Result';\n\nimport {\n normalizeChannelPermissionRequestFields,\n type NormalizedChannelPermissionRequestFields,\n} from '../../../lib/channels/permission-payload.js';\n\nconst MAX_ACTIVITY_PREVIEW_CHARS = 2048;\n\nexport type ResolvedChannelPermissionDecision = ContractsResolvedChannelPermissionDecision;\n\nexport function buildPermissionWaitingMessage(toolName: string, description: string): string {\n return `Waiting for permission: ${toolName} — ${description}`;\n}\n\nexport function permissionResolutionVerb(behavior: 'allow' | 'deny'): 'allowed' | 'denied' {\n return behavior === 'allow' ? 'allowed' : 'denied';\n}\n\nexport function normalizePermissionRequestBody(body: Record<string, unknown>):\n | { ok: true; value: NormalizedChannelPermissionRequestFields }\n | { ok: false; error: string } {\n const result = Effect.runSync(\n Effect.result(\n normalizeChannelPermissionRequestFields({\n requestId: body['requestId'],\n toolName: body['toolName'],\n description: body['description'],\n inputPreview: body['inputPreview'],\n }),\n ),\n );\n if (Result.isFailure(result)) {\n return { ok: false, error: result.failure.message };\n }\n return { ok: true, value: result.success };\n}\n\nexport function parsePermissionResponseBehavior(body: Record<string, unknown>):\n | { ok: true; value: 'allow' | 'deny' }\n | { ok: false; error: string } {\n const behavior = body['behavior'];\n if (behavior !== 'allow' && behavior !== 'deny') {\n return { ok: false, error: 'behavior must be allow or deny' };\n }\n return { ok: true, value: behavior };\n}\n\nexport function redactPermissionInputPreview(inputPreview: string): string {\n let redacted = inputPreview;\n redacted = redacted.replace(\n /(((?:[\"']?(?:api[_-]?key|token|secret|password|passwd)[\"']?)\\s*[:=]\\s*[\"']?))([^\\s,'\"`]+)([\"']?)/gi,\n (_match, prefix: string, _quotedPrefix: string, _value: string, suffix: string) => `${prefix}[REDACTED]${suffix}`,\n );\n redacted = redacted.replace(\n /(Bearer\\s+)([A-Za-z0-9._~+/=-]+)/g,\n (_match, prefix: string) => `${prefix}[REDACTED]`,\n );\n redacted = redacted.replace(\n /(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]+/g,\n '[REDACTED]',\n );\n redacted = redacted.replace(\n /(?:sk|rk)-[A-Za-z0-9]+/g,\n '[REDACTED]',\n );\n if (redacted.length <= MAX_ACTIVITY_PREVIEW_CHARS) {\n return redacted;\n }\n return `${redacted.slice(0, MAX_ACTIVITY_PREVIEW_CHARS)}…[truncated]`;\n}\n\nexport function buildPermissionActivityDetails(description: string, inputPreview: string): string {\n const safePreview = redactPermissionInputPreview(inputPreview);\n return safePreview.trim().length > 0\n ? `${description}\\n\\nInput preview:\\n${safePreview}`\n : description;\n}\n\nexport interface ProcessPermissionResponseDeps {\n getPendingRequest: (requestId: string) => Promise<ChannelPermissionRequestSnapshot | null>;\n getResolvedDecision: (requestId: string) => Promise<ResolvedChannelPermissionDecision | null>;\n appendResolutionEvents: (\n request: ChannelPermissionRequestSnapshot,\n behavior: 'allow' | 'deny',\n ) => Promise<void>;\n deliverDecision: (\n agentId: string,\n requestId: string,\n behavior: 'allow' | 'deny',\n ) => Promise<void>;\n emitResolvedActivity: (request: ChannelPermissionRequestSnapshot, behavior: 'allow' | 'deny') => void;\n}\n\nexport interface ProcessPermissionResponseResult {\n status: number;\n body: { ok: boolean; error?: string; duplicate?: boolean };\n}\n\nexport async function processPermissionResponse(\n deps: ProcessPermissionResponseDeps,\n args: {\n agentId: string;\n requestId: string;\n behavior: 'allow' | 'deny';\n },\n): Promise<ProcessPermissionResponseResult> {\n const pendingRequest = await deps.getPendingRequest(args.requestId);\n if (pendingRequest) {\n if (pendingRequest.agentId !== args.agentId) {\n return {\n status: 409,\n body: {\n ok: false,\n error: `permission request ${args.requestId} belongs to ${pendingRequest.agentId}`,\n },\n };\n }\n\n try {\n await deps.appendResolutionEvents(pendingRequest, args.behavior);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n status: 500,\n body: { ok: false, error: `failed to persist permission decision: ${msg}` },\n };\n }\n\n try {\n await deps.deliverDecision(args.agentId, args.requestId, args.behavior);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n status: 502,\n body: { ok: false, error: `failed to deliver permission decision: ${msg}` },\n };\n }\n\n deps.emitResolvedActivity(pendingRequest, args.behavior);\n return { status: 200, body: { ok: true } };\n }\n\n const resolvedDecision = await deps.getResolvedDecision(args.requestId);\n if (!resolvedDecision) {\n return {\n status: 404,\n body: { ok: false, error: `permission request ${args.requestId} not found` },\n };\n }\n if (resolvedDecision.agentId !== args.agentId) {\n return {\n status: 409,\n body: {\n ok: false,\n error: `permission request ${args.requestId} belongs to ${resolvedDecision.agentId}`,\n },\n };\n }\n if (resolvedDecision.behavior !== args.behavior) {\n return {\n status: 409,\n body: {\n ok: false,\n error: `permission request ${args.requestId} was already ${resolvedDecision.behavior}`,\n },\n };\n }\n\n try {\n await deps.deliverDecision(args.agentId, args.requestId, args.behavior);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n status: 502,\n body: { ok: false, error: `failed to deliver permission decision: ${msg}` },\n };\n }\n\n return { status: 200, body: { ok: true, duplicate: true } };\n}\n","import { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport {\n deliverAgentMessage,\n deliverAgentPermissionDecision,\n getAgentRuntimeState,\n getAgentState,\n} from '../../../../lib/agents.js';\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { ReadModelService } from '../../read-model.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport {\n buildPermissionActivityDetails,\n buildPermissionWaitingMessage,\n normalizePermissionRequestBody,\n parsePermissionResponseBehavior,\n permissionResolutionVerb,\n processPermissionResponse,\n} from '../agent-permissions.js';\nimport {\n constantTimeTokenEqual,\n getAgentPendingQuestions,\n readJsonBody,\n} from './shared.js';\n\n// ─── Route: GET /api/agents/:id/pending-questions ────────────────────────────\n\nexport const getAgentPendingQuestionsRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/pending-questions',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const questions = yield* getAgentPendingQuestions(id);\n return jsonResponse({ pending: questions.length > 0, questions });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/answer-question (PAN-1520) ──────────────────\n//\n// Operator answer for an AskUserQuestion the agent is blocked on. The Phase 1\n// hook (sync-sources/hooks/ask-user-question-hook) denies the upstream tool\n// call to prevent silent corruption (upstream returns option #1 under\n// --dangerously-skip-permissions), so by the time this endpoint is hit the\n// agent has restated the question as plain text and is waiting on a normal\n// user message. We compose that user message from the chosen option labels\n// and deliver it through the standard message pipeline.\n//\n// Body: { answers: string[] } — one chosen-option label per question.\n\nexport const postAgentAnswerQuestionRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/answer-question',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ error: 'missing agent id' }, { status: 400 });\n }\n const body = (yield* readJsonBody) as Record<string, unknown>;\n\n const answers = body['answers'];\n if (!Array.isArray(answers) || answers.length === 0) {\n return jsonResponse({ error: 'answers array required' }, { status: 400 });\n }\n if (!answers.every((a): a is string => typeof a === 'string' && a.length > 0)) {\n return jsonResponse({ error: 'every answer must be a non-empty string' }, { status: 400 });\n }\n\n const pendingQuestions = yield* getAgentPendingQuestions(id);\n if (pendingQuestions.length === 0) {\n return jsonResponse({ error: 'No pending questions found for this agent' }, { status: 404 });\n }\n\n const questionSet = pendingQuestions[0];\n const questions = questionSet.questions;\n const lines: string[] = [];\n for (let i = 0; i < answers.length && i < questions.length; i++) {\n const q = questions[i].question ?? `Question ${i + 1}`;\n lines.push(`Q: ${q}\\nA: ${answers[i]}`);\n }\n const message = `Operator answered the pending question${answers.length > 1 ? 's' : ''}:\\n\\n${lines.join('\\n\\n')}`;\n\n yield* Effect.promise(() => deliverAgentMessage(id, message, 'ask-user-question-answer'));\n return jsonResponse({ success: true, agentId: id, delivered: answers.length });\n })),\n);\n\nexport const postInternalAgentPermissionRequestRoute = HttpRouter.add(\n 'POST',\n '/api/internal/agents/:id/permissions/request',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ ok: false, error: 'missing agent id' }, { status: 400 });\n }\n\n const request = yield* HttpServerRequest.HttpServerRequest;\n const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() =>\n import('../../../../lib/internal-token.js'),\n );\n const expected = getInternalTokenSync();\n if (!expected) {\n return jsonResponse({ ok: false, error: 'internal token not configured' }, { status: 503 });\n }\n const headers = request.headers as Record<string, string | string[] | undefined>;\n const rawHeader = headers[INTERNAL_TOKEN_HEADER];\n const provided = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;\n if (!constantTimeTokenEqual(provided, expected)) {\n return jsonResponse({ ok: false, error: 'forbidden' }, { status: 403 });\n }\n\n const body = (yield* readJsonBody) as Record<string, unknown>;\n const normalized = normalizePermissionRequestBody(body);\n if (!normalized.ok) {\n return jsonResponse({ ok: false, error: normalized.error }, { status: 400 });\n }\n const { requestId, toolName, description, inputPreview } = normalized.value;\n\n const readModel = yield* ReadModelService;\n const existing = yield* readModel.getChannelPermissionRequest(requestId);\n if (existing) {\n if (existing.agentId !== id) {\n return jsonResponse({ ok: false, error: `request ${requestId} already belongs to ${existing.agentId}` }, { status: 409 });\n }\n return jsonResponse({ ok: true, duplicate: true });\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ ok: false, error: `agent ${id} not found` }, { status: 404 });\n }\n const runtimeState = yield* getAgentRuntimeState(id);\n const issueId = runtimeState?.currentIssue ?? agentState.issueId;\n\n const eventStore = yield* EventStoreService;\n const timestamp = new Date().toISOString();\n yield* eventStore.append({\n type: 'agent.permission_requested',\n timestamp,\n payload: {\n requestId,\n agentId: id,\n issueId,\n toolName,\n description,\n inputPreview,\n createdAt: timestamp,\n },\n } as never);\n yield* eventStore.append({\n type: 'agent.waiting_started',\n timestamp,\n payload: {\n agentId: id,\n reason: 'tool_permission',\n message: buildPermissionWaitingMessage(toolName, description),\n },\n } as never);\n\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'warn',\n message: `Permission requested for ${toolName}`,\n details: buildPermissionActivityDetails(description, inputPreview),\n issueId,\n });\n\n return jsonResponse({ ok: true });\n })),\n);\n\nexport const postAgentPermissionResponseRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/permissions/:requestId/respond',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const requestId = params['requestId'] ?? '';\n if (!id.trim() || !requestId.trim()) {\n return jsonResponse({ ok: false, error: 'missing agent id or request id' }, { status: 400 });\n }\n\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const body = (yield* readJsonBody) as Record<string, unknown>;\n const behaviorResult = parsePermissionResponseBehavior(body);\n if (!behaviorResult.ok) {\n return jsonResponse({ ok: false, error: behaviorResult.error }, { status: 400 });\n }\n const behavior = behaviorResult.value;\n\n const readModel = yield* ReadModelService;\n const eventStore = yield* EventStoreService;\n const result = yield* Effect.promise(() => processPermissionResponse(\n {\n getPendingRequest: (permissionRequestId) =>\n Effect.runPromise(readModel.getChannelPermissionRequest(permissionRequestId)),\n getResolvedDecision: (permissionRequestId) =>\n Effect.runPromise(readModel.getResolvedChannelPermissionDecision(permissionRequestId)),\n appendResolutionEvents: async (pendingRequest, decisionBehavior) => {\n const timestamp = new Date().toISOString();\n await Effect.runPromise(eventStore.append({\n type: 'agent.permission_resolved',\n timestamp,\n payload: {\n requestId: pendingRequest.requestId,\n agentId: pendingRequest.agentId,\n issueId: pendingRequest.issueId,\n behavior: decisionBehavior,\n },\n } as never));\n await Effect.runPromise(eventStore.append({\n type: 'agent.waiting_cleared',\n timestamp,\n payload: {\n agentId: pendingRequest.agentId,\n clearedBy: 'tool_resumed',\n },\n } as never));\n },\n deliverDecision: (agentId, permissionRequestId, decisionBehavior) =>\n deliverAgentPermissionDecision(agentId, permissionRequestId, decisionBehavior),\n emitResolvedActivity: (pendingRequest, decisionBehavior) => {\n emitActivityEntrySync({\n source: 'dashboard',\n level: decisionBehavior === 'allow' ? 'success' : 'warn',\n message: `Permission ${permissionResolutionVerb(decisionBehavior)} for ${pendingRequest.toolName}`,\n details: buildPermissionActivityDetails(\n pendingRequest.description,\n pendingRequest.inputPreview,\n ),\n issueId: pendingRequest.issueId,\n });\n },\n },\n {\n agentId: id,\n requestId,\n behavior,\n },\n ));\n\n return jsonResponse(result.body, { status: result.status });\n })),\n);\n","/**\n * Agent event utilities — shared between HTTP route handlers and background\n * pollers (PAN-1134). Extracted from routes/agents.ts so Pi event polling\n * can reuse bodyToEvent without importing a heavy route module.\n */\n\nimport { Schema } from 'effect';\nimport {\n DomainEvent,\n normalizeChannelReplyPayload,\n} from '@overdeck/contracts';\n\n/**\n * Translate a decoded body into an unsigned DomainEvent (no sequence yet).\n * Returns null if the body doesn't map to a runtime event.\n */\nexport const bodyToEvent = (\n agentId: string,\n body: Record<string, unknown>,\n timestamp: string,\n): Record<string, unknown> | null => {\n const source = body;\n if (typeof source['kind'] !== 'string') return null;\n const kind = source['kind'] as string;\n switch (kind) {\n case 'activity':\n return {\n type: 'agent.activity_changed',\n timestamp,\n payload: {\n agentId,\n activity: source['activity'],\n currentTool: source['tool'] as string | undefined,\n },\n };\n case 'thinking_start':\n return {\n type: 'agent.thinking_started',\n timestamp,\n payload: {\n agentId,\n lastToolAt: (source['lastToolAt'] as string) ?? timestamp,\n },\n };\n case 'thinking_stop':\n return {\n type: 'agent.thinking_stopped',\n timestamp,\n payload: {\n agentId,\n resolvedBy: source['resolvedBy'] ?? 'tool',\n },\n };\n case 'waiting_start':\n return {\n type: 'agent.waiting_started',\n timestamp,\n payload: {\n agentId,\n reason: source['reason'] ?? 'other',\n message: source['message'] as string | undefined,\n },\n };\n case 'waiting_clear':\n return {\n type: 'agent.waiting_cleared',\n timestamp,\n payload: {\n agentId,\n clearedBy: source['clearedBy'] ?? 'user_response',\n },\n };\n case 'message_received':\n return {\n type: 'agent.message_received',\n timestamp,\n payload: {\n agentId,\n direction: source['direction'] ?? 'to_agent',\n source: source['source'] ?? 'user',\n },\n };\n case 'channel_reply': {\n const reply = normalizeChannelReplyPayload(source['reply'], 'reply');\n return {\n type: 'agent.channel_reply',\n timestamp,\n payload: {\n agentId,\n reply: {\n ...reply,\n reportedAt: timestamp,\n },\n },\n };\n }\n case 'model_set':\n return {\n type: 'agent.model_set',\n timestamp,\n payload: {\n agentId,\n model: source['model'],\n claudeSessionId: source['claudeSessionId'] as string | undefined,\n sessionModel: source['sessionModel'] as string | undefined,\n sessionHarness: source['sessionHarness'] as string | undefined,\n },\n };\n case 'cost-event': {\n const usage = source['usage'] && typeof source['usage'] === 'object'\n ? source['usage'] as Record<string, unknown>\n : {};\n return {\n type: 'cost.event_recorded',\n timestamp,\n payload: {\n agentId,\n issueId: typeof source['issueId'] === 'string' && source['issueId'] ? source['issueId'] : 'UNKNOWN-0',\n cost: typeof source['costUsd'] === 'number' ? source['costUsd'] : 0,\n inputTokens: typeof usage['inputTokens'] === 'number' ? usage['inputTokens'] : 0,\n outputTokens: typeof usage['outputTokens'] === 'number' ? usage['outputTokens'] : 0,\n },\n };\n }\n case 'resolution_set':\n return {\n type: 'agent.resolution_changed',\n timestamp,\n payload: {\n agentId,\n resolution: source['resolution'],\n resolutionCount: Number(source['resolutionCount'] ?? 1),\n },\n };\n case 'current_issue_set':\n return {\n type: 'agent.current_issue_set',\n timestamp,\n payload: {\n agentId,\n currentIssue: source['currentIssue'] as string | undefined,\n },\n };\n case 'context_saturation_changed':\n return {\n type: 'agent.context_saturation_changed',\n timestamp,\n payload: {\n agentId,\n contextSaturatedAt: source['contextSaturatedAt'] as string | undefined,\n },\n };\n default:\n return null;\n }\n};\n\n// Runtime-event decoder. We validate the assembled event (with a placeholder\n// sequence) against the DomainEvent union — bad payloads are rejected rather\n// than silently corrupting the AgentRuntimeSnapshot.\nexport const decodeDomainEvent = Schema.decodeUnknownResult(DomainEvent);\n","import { Effect, Option } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { bodyToEvent, decodeDomainEvent } from '../../services/agent-event-utils.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n readJsonBody,\n validateAgentRuntimeEventAuth,\n} from './shared.js';\n\n// ─── Route: GET /api/agents/:id/health-history ───────────────────────────────\n\nexport const getAgentHealthHistoryRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/health-history',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n const hours = Option.isSome(urlOpt) ? (urlOpt.value.searchParams.get('hours') ?? '24') : '24';\n\n const { getHealthHistory } = yield* Effect.promise(() => import('../../../../lib/overdeck/health-events.js'));\n const endTime = new Date();\n const startTime = new Date(endTime.getTime() - parseInt(hours) * 60 * 60 * 1000);\n const events = getHealthHistory(id, startTime.toISOString(), endTime.toISOString());\n return jsonResponse({\n agentId: id,\n startTime: startTime.toISOString(),\n endTime: endTime.toISOString(),\n events,\n });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/heartbeat (PAN-800 ingestion) ──────────────\n//\n// Typed event ingestion for agent runtime state. Hooks POST a Schema-validated\n// body describing a single runtime transition; the handler translates to an\n// agent.* DomainEvent and hands it to AgentStateService.emit (which durably\n// appendAsyncs via EventStore).\n//\n// Body shape (discriminated by `kind`):\n// {kind: \"activity\", activity, tool?}\n// {kind: \"thinking_start\", lastToolAt}\n// {kind: \"thinking_stop\", resolvedBy}\n// {kind: \"waiting_start\", reason, message?}\n// {kind: \"waiting_clear\", clearedBy}\n// {kind: \"message_received\", direction, source}\n// {kind: \"model_set\", model, claudeSessionId?}\n// {kind: \"resolution_set\", resolution, resolutionCount}\n// {kind: \"current_issue_set\", currentIssue?}\n// {kind: \"context_saturation_changed\", contextSaturatedAt?}\n\nfunction emitAgentRuntimeEvent(id: string, body: Record<string, unknown>, timestamp: string) {\n return Effect.gen(function* () {\n let raw: Record<string, unknown> | null;\n try {\n raw = bodyToEvent(id, body, timestamp);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'invalid heartbeat payload';\n return { ok: false as const, response: jsonResponse({ success: false, error: message }, { status: 400 }) };\n }\n if (!raw) {\n return { ok: true as const, emitted: false };\n }\n\n const candidate = { ...raw, sequence: 0 };\n const decoded = decodeDomainEvent(candidate);\n if (decoded._tag === 'Failure') {\n return {\n ok: false as const,\n response: jsonResponse(\n { success: false, error: 'invalid event', detail: String(decoded.failure) },\n { status: 400 },\n ),\n };\n }\n\n const { AgentStateService } = yield* Effect.promise(\n () => import('../../services/agent-state-service.js'),\n );\n const agentState = yield* AgentStateService;\n yield* agentState.emit(decoded.success as never);\n return { ok: true as const, emitted: true };\n });\n}\n\nexport const postAgentHeartbeatRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/heartbeat',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const auth = yield* Effect.promise(() => validateAgentRuntimeEventAuth(request));\n if (!auth.ok) return auth.response;\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ success: false, error: 'missing agent id' }, { status: 400 });\n }\n const body = (yield* readJsonBody) as Record<string, unknown>;\n const timestamp = (body['timestamp'] as string) ?? new Date().toISOString();\n const result = yield* emitAgentRuntimeEvent(id, body, timestamp);\n if (!result.ok) return result.response;\n return jsonResponse({ success: true, emitted: result.emitted });\n })),\n);\n\nexport const postAgentWorkCompleteRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/work-complete',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const auth = yield* Effect.promise(() => validateAgentRuntimeEventAuth(request));\n if (!auth.ok) return auth.response;\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) return jsonResponse({ success: false, error: 'missing agent id' }, { status: 400 });\n const result = yield* emitAgentRuntimeEvent(id, { kind: 'resolution_set', resolution: 'done', resolutionCount: 1 }, new Date().toISOString());\n if (!result.ok) return result.response;\n return jsonResponse({ success: true, emitted: result.emitted });\n })),\n);\n\nexport const postAgentStuckRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/stuck',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const auth = yield* Effect.promise(() => validateAgentRuntimeEventAuth(request));\n if (!auth.ok) return auth.response;\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) return jsonResponse({ success: false, error: 'missing agent id' }, { status: 400 });\n const result = yield* emitAgentRuntimeEvent(id, { kind: 'resolution_set', resolution: 'stuck', resolutionCount: 1 }, new Date().toISOString());\n if (!result.ok) return result.response;\n return jsonResponse({ success: true, emitted: result.emitted });\n })),\n);\n\nfunction hasNegatedCompletionOutput(output: string): boolean {\n return /\\b(not|never|no|cannot|can't|blocked|waiting|needs input|not yet|isn't|aren't)\\b.{0,48}\\b(implementation complete|all beads closed|ready for review|work complete)\\b/i.test(output);\n}\n\nexport const postAgentClassifyCompletionRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/classify-completion',\n httpHandler(Effect.gen(function* () {\n const body = (yield* readJsonBody) as Record<string, unknown>;\n const output = typeof body['output'] === 'string' ? body['output'] : '';\n const verdict = /blocked|needs input|waiting for|not ready for review/i.test(output) || hasNegatedCompletionOutput(output)\n ? 'STOPPED_FOR_INPUT'\n : /Implementation complete|all beads closed|ready for review|work complete/i.test(output)\n ? 'FORGOT_COMPLETION'\n : 'UNCLEAR';\n return jsonResponse({ success: true, verdict });\n })),\n);\n\n// ─── Route: GET /api/agents/:id/runtime (PAN-800) ────────────────────────────\n// Exposes AgentRuntimeSnapshot to out-of-process readers (CLI, tests).\n\nexport const getAgentRuntimeRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/runtime',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ success: false, error: 'missing agent id' }, { status: 400 });\n }\n const { AgentStateService } = yield* Effect.promise(\n () => import('../../services/agent-state-service.js'),\n );\n const agentState = yield* AgentStateService;\n const snapshot = yield* agentState.get(id);\n if (!snapshot) {\n return jsonResponse({ success: false, error: 'not found' }, { status: 404 });\n }\n return jsonResponse({ success: true, snapshot });\n })),\n);\n","/**\n * Agent lifecycle projection — write-through transactional boundary (PAN-1908)\n *\n * Every agent lifecycle transition writes the rollback source (state.json)\n * and then commits the authoritative row upsert + event append inside one\n * SQLite transaction. This replaces save-then-append, which a crash could\n * leave out of sync.\n *\n * The transaction runs on the shared overdeck.db connection (PAN-1938). After\n * commit, the persisted event is emitted to the event store's subscribers so\n * the in-memory read model stays current.\n */\n\nimport { Effect } from 'effect';\nimport type { SqliteDatabase } from '../../../lib/database/driver.js';\nimport { getOverdeckDatabaseSync } from '../../../lib/overdeck/infra.js';\nimport { stateToOverdeckParamsForDb, AGENT_COLUMNS_FOR_DB } from '../../../lib/overdeck/agent-state-sync.js';\nimport { getEventStore, type EventStore, type StoredEvent } from '../event-store.js';\nimport { writeAgentStateJsonSync, type AgentState } from '../../../lib/agents.js';\nimport { logAgentLifecycleSync } from '../../../lib/persistent-logger.js';\nimport type { DomainEvent } from '@overdeck/contracts';\n\nexport interface AgentProjectionResult {\n /** Assigned event sequence number. */\n sequence: number;\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\nfunction buildStoredEvent(\n event: Omit<DomainEvent, 'sequence'>,\n sequence: number,\n): StoredEvent {\n const record = event as Record<string, unknown>;\n return {\n sequence,\n type: event.type,\n timestamp: (record['timestamp'] as string) ?? new Date().toISOString(),\n payload: (record['payload'] as Record<string, unknown>) ?? {},\n };\n}\n\n/**\n * Atomically persist an agent state change and its lifecycle event.\n *\n * 1. Prepares the state (stoppedAt stamping).\n * 2. Writes state.json (rollback source) outside the SQLite tx.\n * 3. Begins an overdeck.db transaction, upserts the agents row, inserts the event.\n * 4. Commits and emits the stored event to subscribers.\n *\n * @throws If the SQLite transaction is rolled back, neither the row nor the\n * event is persisted and state.json may be slightly ahead.\n */\nexport function saveAgentStateAndEmitEvent(\n state: AgentState,\n event: Omit<DomainEvent, 'sequence'>,\n): AgentProjectionResult {\n const db = getOverdeckDatabaseSync();\n const eventStore = getEventStore();\n return saveAgentStateAndEmitEventWithDeps(db, eventStore, state, event);\n}\n\n/**\n * Effect wrapper for server routes. Runs the synchronous projection under\n * Effect.sync so callers in Effect.gen can compose it without blocking.\n */\nexport function saveAgentStateAndEmitEventProgram(\n state: AgentState,\n event: Omit<DomainEvent, 'sequence'>,\n): Effect.Effect<AgentProjectionResult> {\n return Effect.sync(() => saveAgentStateAndEmitEvent(state, event));\n}\n\n/**\n * Dependency-injected variant for tests.\n */\nexport function saveAgentStateAndEmitEventWithDeps(\n db: SqliteDatabase,\n eventStore: Pick<EventStore, 'emitStored'>,\n state: AgentState,\n event: Omit<DomainEvent, 'sequence'>,\n): AgentProjectionResult {\n prepareAgentStateForSave(state);\n\n // Rollback source lives on the filesystem; keep it outside the SQLite tx\n // so a tx failure does not corrupt it.\n writeAgentStateJsonSync(state);\n\n const record = event as Record<string, unknown>;\n const timestamp = (record['timestamp'] as string) ?? new Date().toISOString();\n const timestampMs = new Date(timestamp).getTime();\n const payload = JSON.stringify(record['payload'] ?? {});\n const updatedAt = Date.now();\n\n db.exec('BEGIN IMMEDIATE');\n try {\n // Ensure the issues row exists (overdeck FK requirement).\n db.prepare(\n `INSERT OR IGNORE INTO issues (id, stage, updated_at) VALUES (?, 'working', ?)`,\n ).run(state.issueId, updatedAt);\n\n // Upsert the agents row.\n db.prepare(\n `INSERT OR REPLACE INTO agents (${AGENT_COLUMNS_FOR_DB.join(', ')}) VALUES (${AGENT_COLUMNS_FOR_DB.map(() => '?').join(', ')})`,\n ).run(...stateToOverdeckParamsForDb(state, updatedAt));\n\n // Append the event. overdeck events.timestamp is integer unix milliseconds\n // (schema: integer(timestamp_ms); event-store decodes via new Date(row.timestamp)).\n db.prepare(\n `INSERT INTO events (type, timestamp, payload) VALUES (?, ?, ?)`,\n ).run(event.type, timestampMs, payload);\n\n const row = db.prepare(`SELECT last_insert_rowid() AS sequence`).get() as\n | { sequence: number }\n | undefined;\n const sequence = row?.sequence ?? 0;\n db.exec('COMMIT');\n\n const stored = buildStoredEvent(event, sequence);\n eventStore.emitStored(stored);\n\n logAgentLifecycleSync(\n state.id,\n `projected ${event.type} (seq=${sequence}) for ${state.id}`,\n );\n\n return { sequence };\n } catch (err) {\n try {\n db.exec('ROLLBACK');\n } catch {\n // Ignore rollback failures; the connection may already be rolled back.\n }\n throw err;\n }\n}\n","import { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport {\n clearAgentPaused,\n clearAgentTroubled,\n getAgentState,\n getSessionId,\n markAgentStoppedState,\n saveAgentRuntimeState,\n saveSessionId,\n setAgentPaused,\n stopAgent,\n} from '../../../../lib/agents.js';\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { operatorInterventionEvent } from '../../../../lib/operator-interventions.js';\nimport { stopWorkspaceDocker } from '../../../../lib/workspace-manager.js';\nimport { killSession, sessionExists } from '../../../../lib/tmux.js';\nimport { saveAgentStateAndEmitEventProgram } from '../../services/agent-projection.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport {\n appendAgentLifecycleLog,\n buildAgentControlEventPayload,\n captureAgentOutputBeforeKill,\n invalidateAgentsCache,\n readJsonBody,\n toAgentStatusPayload,\n} from './shared.js';\n\nexport function createAgentStopHandler(\n lifecycleEvent: 'agent.delete_requested' | 'agent.stop_requested',\n) {\n return httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const eventStore = yield* EventStoreService;\n\n const stateBeforeStop = yield* getAgentState(id);\n yield* Effect.promise(() => appendAgentLifecycleLog(id, lifecycleEvent));\n yield* stopAgent(id);\n\n // PAN-1316/PAN-1326: tear down the workspace Docker stack on user-initiated stop.\n // Without this, dev-server containers (Vite/Webpack) outlive their owning\n // agent and can degrade the host via inotify-fallback polling storms.\n // Internal stops (restart) take a different path and don't reach here.\n //\n // Resolve the workspace from the issue (not from the agent's own state) so\n // killing a specialist (review/test/ship) — whose state.workspace may not\n // point at the work agent's workspace — still tears down the right stack.\n // Mirrors the postMergeLifecycle pattern in merge-agent.ts.\n if (stateBeforeStop?.issueId) {\n yield* Effect.promise(async () => {\n try {\n const { resolveProjectFromIssueSync } = await import('../../../../lib/projects.js');\n const { findWorkspacePath } = await import('../../../../lib/lifecycle/archive-planning.js');\n const issueLower = stateBeforeStop.issueId!.toLowerCase();\n const project = resolveProjectFromIssueSync(stateBeforeStop.issueId!);\n const projectPath = project?.projectPath ?? process.cwd();\n const workspacePath = findWorkspacePath(projectPath, issueLower);\n if (workspacePath) {\n const dockerResult = await Effect.runPromise(stopWorkspaceDocker(workspacePath, issueLower));\n if (dockerResult.containersFound) {\n console.log(`[agents] ✓ Stopped Docker stack for ${id}: ${dockerResult.steps.join('; ')}`);\n }\n }\n } catch (err) {\n console.warn(`[agents] Docker teardown failed for ${id} (non-fatal): ${err instanceof Error ? err.message : String(err)}`);\n }\n });\n }\n\n // PAN-1048 review feedback 004 (C1): AgentStoppedEvent requires both\n // agentId AND issueId on the payload (packages/contracts/src/events.ts:36);\n // ws-rpc drops events that fail Schema validation, so emits without issueId\n // never reach subscribers and the dashboard misses the stop transition.\n //\n // PAN-1908: write-through projection — re-upsert the stopped row and append\n // the lifecycle event in one SQLite transaction. stopAgent already saved\n // state, but repeating the upsert here makes the event append atomic.\n const stateAfterStop = yield* getAgentState(id);\n if (stateAfterStop) {\n yield* saveAgentStateAndEmitEventProgram(stateAfterStop, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: stateAfterStop.issueId || stateBeforeStop?.issueId || '' },\n });\n }\n const issueId = stateBeforeStop?.issueId;\n // PAN-1048: derive label from role; legacy state.phase no longer exists.\n const phaseLabel = stateBeforeStop?.role === 'plan' ? 'planning' : 'work';\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n message: issueId\n ? `User stopped ${issueId} ${phaseLabel} agent`\n : `User stopped agent ${id}`,\n issueId,\n });\n invalidateAgentsCache();\n return jsonResponse({ success: true });\n }));\n}\n\nfunction agentStopRoute(\n method: 'DELETE' | 'POST',\n path: `/${string}`,\n lifecycleEvent: 'agent.delete_requested' | 'agent.stop_requested',\n) {\n return HttpRouter.add(method, path, createAgentStopHandler(lifecycleEvent));\n}\n\n// ─── Route: DELETE /api/agents/:id ───────────────────────────────────────────\n\nexport const deleteAgentRoute = agentStopRoute('DELETE', '/api/agents/:id', 'agent.delete_requested');\n\n// ─── Route: POST /api/agents/:id/stop ────────────────────────────────────────\n\nexport const postAgentStopRoute = agentStopRoute('POST', '/api/agents/:id/stop', 'agent.stop_requested');\n\n// ─── Route: POST /api/agents/:id/suspend ─────────────────────────────────────\n\nexport const postAgentSuspendRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/suspend',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n\n const { sessionId } = body as any;\n const effectiveSessionId = sessionId || getSessionId(id);\n\n if (!effectiveSessionId) {\n return jsonResponse({ error: 'Session ID required for suspend' }, { status: 400 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.suspend_requested', { sessionId: effectiveSessionId }));\n saveSessionId(id, effectiveSessionId);\n // PAN-1048 review feedback 004 (C1): resolve issueId before kill so we can\n // include it on the agent.stopped payload (the contract requires it).\n const suspendIssueId = (yield* getAgentState(id))?.issueId ?? '';\n yield* killSession(id).pipe(Effect.catch(() => Effect.void));\n saveAgentRuntimeState(id, {\n state: 'suspended',\n lastActivity: new Date().toISOString(),\n claudeSessionId: effectiveSessionId,\n });\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction. Preserve the existing agent-table status\n // (suspend does not flip it to stopped).\n const stateAfterSuspend = yield* getAgentState(id);\n if (stateAfterSuspend) {\n yield* saveAgentStateAndEmitEventProgram(stateAfterSuspend, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: stateAfterSuspend.issueId || suspendIssueId },\n });\n }\n\n invalidateAgentsCache();\n return jsonResponse({ success: true });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/pause ────────────────────────────────────────\n\nexport const postAgentPauseRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/pause',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const reason = (body as { reason?: unknown }).reason;\n\n if (reason !== undefined && typeof reason !== 'string') {\n return jsonResponse({ error: 'reason must be a string' }, { status: 400 });\n }\n\n const stateBeforePause = yield* getAgentState(id);\n if (!stateBeforePause) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n const previousStatus = toAgentStatusPayload(stateBeforePause.status);\n const hasLiveSession = yield* sessionExists(id);\n const stoppedByPause = hasLiveSession || stateBeforePause.status === 'running' || stateBeforePause.status === 'starting';\n let updatedState = yield* setAgentPaused(id, reason, stoppedByPause);\n if (!updatedState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n if (hasLiveSession) {\n yield* Effect.promise(() => captureAgentOutputBeforeKill(id));\n yield* killSession(id);\n }\n\n if (hasLiveSession || updatedState.status === 'running' || updatedState.status === 'starting') {\n const stoppedState = markAgentStoppedState(updatedState);\n updatedState = stoppedState;\n yield* Effect.promise(() => saveAgentRuntimeState(id, {\n state: 'stopped',\n lastActivity: new Date().toISOString(),\n }));\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.pause_requested', { reason }));\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: updatedState.issueId || stateBeforePause.issueId || id.replace(/^agent-/, '').toUpperCase(),\n kind: 'pause',\n source: 'dashboard',\n }));\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.status_changed',\n timestamp: new Date().toISOString(),\n payload: buildAgentControlEventPayload(updatedState, previousStatus),\n });\n\n invalidateAgentsCache();\n return jsonResponse({ success: true, agent: updatedState });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/unpause ──────────────────────────────────────\n\nexport const postAgentUnpauseRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/unpause',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const eventStore = yield* EventStoreService;\n\n const stateBeforeUnpause = yield* getAgentState(id);\n if (!stateBeforeUnpause) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n const updatedState = yield* clearAgentPaused(id);\n if (!updatedState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.unpause_requested'));\n if (stateBeforeUnpause.paused === true) {\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: updatedState.issueId || stateBeforeUnpause.issueId || id.replace(/^agent-/, '').toUpperCase(),\n kind: 'unpause',\n source: 'dashboard:agent-unpause',\n }));\n }\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.status_changed',\n timestamp: new Date().toISOString(),\n payload: buildAgentControlEventPayload(updatedState, toAgentStatusPayload(stateBeforeUnpause.status)),\n });\n\n invalidateAgentsCache();\n return jsonResponse({ success: true, agent: updatedState });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/untroubled ───────────────────────────────────\n\nexport const postAgentUntroubledRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/untroubled',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const eventStore = yield* EventStoreService;\n\n const stateBeforeClear = yield* getAgentState(id);\n if (!stateBeforeClear) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n const updatedState = yield* clearAgentTroubled(id);\n if (!updatedState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.untroubled_requested'));\n if (stateBeforeClear.troubled === true || (stateBeforeClear.consecutiveFailures ?? 0) > 0) {\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: updatedState.issueId || stateBeforeClear.issueId || id.replace(/^agent-/, '').toUpperCase(),\n kind: 'untroubled',\n source: 'dashboard:agent-untroubled',\n }));\n }\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.status_changed',\n timestamp: new Date().toISOString(),\n payload: buildAgentControlEventPayload(updatedState, toAgentStatusPayload(stateBeforeClear.status)),\n });\n\n invalidateAgentsCache();\n return jsonResponse({ success: true, agent: updatedState });\n })),\n);\n","import { existsSync } from 'node:fs';\nimport { readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join, basename } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter } from 'effect/unstable/http';\nimport type { RuntimeName } from '../../../../lib/runtimes/types.js';\nimport { resolveStaffing } from '../../../../lib/agents/staffing.js';\nimport { findPlanSync, readWorkspacePlanSync } from '../../../../lib/vbrief/io.js';\nimport { resolveTieredExecutionEnabled, resolveTieredExecutionEnabledForIssue } from '../../../../lib/agents/tier-table.js';\nimport { getDispatchableItems } from '../../../../lib/vbrief/dag.js';\nimport { loadConfigSync } from '../../../../lib/config-yaml.js';\n\nimport {\n getAgentState,\n getLatestSessionId,\n recoverAgent,\n resumeAgent,\n restartAgent,\n saveAgentStateSync,\n getAgentDir,\n getProviderAuthMode,\n listRunningAgents,\n wipeAgentStateDirs,\n} from '../../../../lib/agents.js';\nimport { canUseHarnessSync } from '../../../../lib/harness-policy.js';\nimport { normalizeModelOverrideSync, requireModelOverrideSync } from '../../../../lib/model-validation.js';\nimport { operatorInterventionEvent } from '../../../../lib/operator-interventions.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { getWorkAgentLifecycleState } from '../../../../lib/work-agent-lifecycle.js';\nimport { killSession } from '../../../../lib/tmux.js';\nimport { saveAgentStateAndEmitEventProgram } from '../../services/agent-projection.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n appendAgentLifecycleLog,\n buildPanStartArgs,\n invalidateAgentsCache,\n readJsonBody,\n spawnPanCommandDetached,\n} from './shared.js';\n\n// ─── Route: POST /api/agents/:id/resume ──────────────────────────────────────\n\nexport const postAgentResumeRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/resume',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { message, model, harness, compact } = body as { message?: string; model?: string; harness?: RuntimeName; compact?: boolean };\n let resumeModel: string | undefined;\n try {\n resumeModel = normalizeModelOverrideSync(model);\n } catch (err) {\n console.warn(`[agents/resume] ${id} model validation failed: ${err instanceof Error ? err.message : String(err)}`);\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n // PAN-1985 follow-up: structured log at the route entry so the operator\n // can trace every resume attempt in the server console / pty-supervisor\n // log even when the front-end's toast is missed.\n console.log(`[agents/resume] ${id} requested: model=${resumeModel ?? 'unchanged'} harness=${harness ?? 'unchanged'} hasMessage=${!!message} compact=${compact === true}`);\n const eventStore = yield* EventStoreService;\n // Snapshot lifecycle state BEFORE taking any action so callers can see the\n // temporal context (why was this resume allowed) without recomputing state.\n const lifecycleBefore = yield* getWorkAgentLifecycleState(id);\n console.log(`[agents/resume] ${id} lifecycle: canResume=${lifecycleBefore.canResumeSession} hasSavedSession=${lifecycleBefore.hasSavedSession} hasLiveTmux=${lifecycleBefore.hasLiveTmuxSession} isCrashed=${lifecycleBefore.isCrashed} isStopped=${lifecycleBefore.isStopped}`);\n // PAN-1675: a compact-resume targets a context-wedged agent that is usually\n // still 'running' (a live but stuck session), which the normal gate rejects.\n // Allow it through for compact === true — resumeAgent summarizes the wedged\n // session out-of-band, kills it, and respawns a fresh session seeded with\n // the summary (PAN-1781; its own canResume handles the running case).\n // Non-compact resumes keep the strict gate.\n if (!lifecycleBefore.canResumeSession && !lifecycleBefore.isRunningButStuck && compact !== true) {\n console.warn(`[agents/resume] ${id} rejected: ${lifecycleBefore.reason}`);\n return jsonResponse({\n error: lifecycleBefore.reason || `Cannot resume agent ${lifecycleBefore.agentId}`,\n lifecycle: lifecycleBefore,\n }, { status: 409 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.resume_requested', {\n hasMessage: !!message,\n model: resumeModel || undefined,\n harness: harness || undefined,\n lifecycle: lifecycleBefore,\n }));\n const resumeOpts = resumeModel || harness || compact === true\n ? { ...(resumeModel ? { model: resumeModel } : {}), ...(harness ? { harness } : {}), ...(compact === true ? { compact: true } : {}) }\n : undefined;\n console.log(`[agents/resume] ${id} dispatching resumeAgent() with opts=${JSON.stringify(resumeOpts)}`);\n const result = yield* Effect.promise(() => resumeAgent(id, message, resumeOpts));\n console.log(`[agents/resume] ${id} resumeAgent returned: success=${result.success} messageDelivered=${result.messageDelivered} error=${result.error ?? 'none'}`);\n if (result.success) {\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction so the read model transitions agent\n // status from 'stopped' → 'running' and the frontend updates immediately.\n const agentState = yield* getAgentState(id);\n if (agentState) {\n yield* saveAgentStateAndEmitEventProgram(agentState, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: id,\n issueId: agentState.issueId,\n agent: {\n id,\n issueId: agentState.issueId,\n workspace: agentState.workspace,\n runtime: agentState.harness ?? 'claude-code',\n model: agentState.model,\n status: 'running',\n startedAt: agentState.startedAt,\n lastActivity: new Date().toISOString(),\n role: agentState.role ?? 'work',\n },\n },\n });\n }\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.resume_succeeded', {\n hasMessage: !!message,\n messageDelivered: result.messageDelivered !== false,\n }));\n invalidateAgentsCache();\n // PAN-1985 follow-up: the messageDelivered flag distinguishes \"agent is\n // resumed and your message landed in its composer\" from \"agent is\n // resumed but your message did NOT land in its composer (PTY supervisor\n // echo-confirm timed out, harness/session.id mismatch, etc.)\". The\n // former gets a 'delivered' toast; the latter gets a clear 'queued in\n // mail' warning so the operator can intervene if needed.\n const delivered = result.messageDelivered !== false;\n console.log(`[agents/resume] ${id} returning: success=${true} delivered=${delivered}`);\n return jsonResponse({\n success: true,\n resumed: true,\n messageDelivered: delivered,\n hint: delivered\n ? 'Continue prompt delivered to the agent.'\n : 'The continue prompt was queued in the agent mail/ folder because the live delivery path did not confirm in time. The agent will read it on its next session start.',\n lifecycle: { before: lifecycleBefore, after: yield* getWorkAgentLifecycleState(id) },\n });\n } else {\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.resume_failed', {\n hasMessage: !!message,\n error: result.error,\n }));\n return jsonResponse({\n error: result.error,\n lifecycle: { before: lifecycleBefore, after: yield* getWorkAgentLifecycleState(id) },\n }, { status: 400 });\n }\n })),\n);\n\n// ─── Route: POST /api/agents/:id/recover ──────────────────────────────────────\n\nexport const postAgentRecoverRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/recover',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const { model } = body as { model?: string };\n let recoveryModel: string | undefined;\n try {\n recoveryModel = normalizeModelOverrideSync(model);\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n\n const stateBeforeRecover = yield* getAgentState(id);\n if (!stateBeforeRecover) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.recover_requested', {\n model: recoveryModel || undefined,\n }));\n\n const result = yield* Effect.promise(() => recoverAgent(id, recoveryModel ? { modelOverride: recoveryModel } : undefined));\n if (!result) {\n const error = `Could not recover agent ${id}`;\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.recover_failed', { error }));\n return jsonResponse({ success: false, error }, { status: 400 });\n }\n\n const updatedState = yield* getAgentState(id);\n if (updatedState) {\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: id,\n issueId: updatedState.issueId || stateBeforeRecover.issueId || id.replace('agent-', '').toUpperCase(),\n agent: {\n id,\n issueId: updatedState.issueId || stateBeforeRecover.issueId,\n workspace: updatedState.workspace,\n model: updatedState.model,\n status: 'running',\n startedAt: updatedState.startedAt,\n lastActivity: updatedState.lastActivity,\n role: updatedState.role ?? 'work',\n },\n },\n });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.recover_succeeded'));\n invalidateAgentsCache();\n return jsonResponse({ success: true, recovered: true, agent: updatedState ?? null });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/restart ──────────────────────────────────────\n//\n// Restart an agent with optional model override. Graceful mode sends a 30s\n// warning then restarts; quick mode kills and relaunches immediately.\n// Returns 202 for graceful (async work), 200 for quick.\n\nexport const postAgentRestartRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/restart',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n\n const { model, harness, graceful = true, message } = body as {\n model?: string;\n harness?: 'claude-code' | 'ohmypi' | 'codex';\n graceful?: boolean;\n message?: string;\n };\n let restartModel: string | undefined;\n try {\n restartModel = normalizeModelOverrideSync(model);\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.restart_requested', {\n model: restartModel || agentState.model,\n harness: harness || undefined,\n graceful,\n hasMessage: !!message,\n }));\n\n if (graceful) {\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: agentState.issueId,\n kind: 'restart',\n source: 'dashboard',\n }));\n // Kick off async restart — don't block the HTTP response for 30s\n (async () => {\n try {\n // PAN-1908: write-through projection — agents-row upsert + lifecycle\n // event append in one SQLite transaction.\n await Effect.runPromise(saveAgentStateAndEmitEventProgram(agentState, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: agentState.issueId },\n }));\n\n const result = await restartAgent(id, { model: restartModel, harness, graceful: true, message });\n\n if (result.success) {\n const updatedState = await Effect.runPromise(getAgentState(id));\n // PAN-1908: write-through projection — agents-row upsert + lifecycle\n // event append in one SQLite transaction.\n if (updatedState) {\n await Effect.runPromise(saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: id,\n issueId: updatedState.issueId || agentState.issueId,\n agent: {\n id,\n issueId: updatedState.issueId || agentState.issueId,\n workspace: updatedState.workspace || agentState.workspace,\n // PAN-1048 review feedback 004 (C3): same as quick-restart\n // below — surface the actual harness so Pi agents do not\n // get mis-labelled as Claude Code on graceful restart.\n runtime: updatedState.harness ?? agentState.harness ?? 'claude-code',\n model: restartModel || updatedState.model || agentState.model,\n status: 'running',\n startedAt: updatedState.startedAt || agentState.startedAt,\n lastActivity: new Date().toISOString(),\n role: updatedState.role ?? agentState.role,\n },\n },\n }));\n }\n invalidateAgentsCache();\n }\n await appendAgentLifecycleLog(id, 'agent.restart_completed', {\n success: result.success,\n error: result.error,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[agents] Graceful restart failed for ${id}: ${msg}`);\n await appendAgentLifecycleLog(id, 'agent.restart_failed', { error: msg });\n }\n })();\n\n return jsonResponse({ accepted: true, graceful: true, agentId: id }, { status: 202 });\n }\n\n // Quick restart — synchronous\n const result = yield* Effect.promise(() => restartAgent(id, { model: restartModel, harness, graceful: false, message }));\n\n if (result.success) {\n const updatedState = yield* getAgentState(id);\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: updatedState?.issueId || agentState.issueId,\n kind: 'restart',\n source: 'dashboard',\n }));\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction. restartAgent already saved state, but\n // repeating the upsert here makes each lifecycle event atomic.\n if (updatedState) {\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: updatedState.issueId || agentState.issueId },\n });\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: id,\n issueId: updatedState.issueId || agentState.issueId,\n agent: {\n id,\n issueId: updatedState.issueId || agentState.issueId,\n workspace: updatedState.workspace || agentState.workspace,\n // PAN-1048 review feedback 004 (C3): preserve the agent's actual\n // harness instead of hard-coding 'claude'. AgentSnapshot.runtime\n // is what getHarness() reads, so a Pi agent restarted through\n // this path was being mis-labelled as Claude Code.\n runtime: updatedState.harness ?? agentState.harness ?? 'claude-code',\n model: restartModel || updatedState.model || agentState.model,\n status: 'running',\n startedAt: updatedState.startedAt || agentState.startedAt,\n lastActivity: new Date().toISOString(),\n role: updatedState.role ?? agentState.role,\n },\n },\n });\n }\n invalidateAgentsCache();\n return jsonResponse({ success: true, restarted: true, agentId: id, model: restartModel || agentState.model });\n }\n\n return jsonResponse({ error: result.error }, { status: 500 });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/restart-fresh ────────────────────────────────\n//\n// PAN-1985: wipe the work agent's state directory under ~/.overdeck/agents/,\n// then optionally spawn a fresh work agent with a new harness/model. This is\n// the deliberate operator override path — for harness/model switches (the\n// Claude-session JSONL can't be resumed under a different harness) and for\n// \"I want a clean work run\" recovery. The NORMAL review flow continues the\n// same session across re-dispatches (PAN-1862); this route is the escape\n// hatch that pays the re-research cost.\n//\n// Modes (driven by request body):\n// { spawn: true, model, harness } — wipe + respawn a new work agent with\n// the chosen harness/model\n// { spawn: false } — wipe only; user clicks Start afterwards\n// (this is the backend of the new\n// 'completeWorkReset' issue action)\n//\n// Refuses (409) if a live tmux session is alive — same gate as reset-session.\n// Workspace, vBRIEF, beads, .pan/continue.json, .pan/feedback/, the branch,\n// and the commit history are all left untouched. The new agent (whether\n// auto-spawned or manually started) reads .pan/continue.json + branch state\n// to pick up where the prior run left off.\n\nexport const postAgentRestartFreshRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/restart-fresh',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { spawn: spawnFlag, model: rawModel, harness } = body as {\n spawn?: boolean;\n model?: string;\n harness?: 'claude-code' | 'ohmypi' | 'codex';\n };\n const wantsSpawn = spawnFlag !== false; // default to spawn when omitted (picker path)\n\n let newModel: string | undefined;\n if (wantsSpawn && rawModel) {\n try {\n newModel = requireModelOverrideSync(rawModel);\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n const issueId = agentState.issueId ?? id.replace(/^agent-/, '').toUpperCase();\n\n const lifecycle = yield* getWorkAgentLifecycleState(id);\n if (lifecycle.hasLiveTmuxSession) {\n return jsonResponse({\n error: `Agent ${id} has a live tmux session. Run 'pan kill ${issueId}' first, then retry.`,\n lifecycle,\n }, { status: 409 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.restart_fresh_requested', {\n wantsSpawn,\n model: newModel,\n harness,\n issueId,\n }));\n\n // Kill any zombie tmux session (shouldn't happen with the gate above, but\n // belt-and-suspenders) before wiping state.\n yield* killSession(id).pipe(Effect.catch(() => Effect.void));\n\n // Wipe the work agent dir only — leave specialist dirs (review, etc.)\n // alone. The new agent will read .pan/continue.json to pick up.\n const wipeResult = yield* Effect.promise(() => wipeAgentStateDirs(issueId));\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.restart_fresh_wiped', {\n removed: wipeResult.removed,\n path: wipeResult.path,\n }));\n\n if (!wantsSpawn) {\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n spawn: false,\n agentId: id,\n issueId,\n removed: wipeResult.removed,\n hint: 'Agent dir wiped. Click Start agent to respawn with a fresh session.',\n });\n }\n\n // Auto-spawn path: dispatch to the existing /api/agents spawn flow.\n // We don't go through HTTP — we call the spawn primitives directly so\n // the caller gets a single 200 with both wipe and spawn confirmed.\n const spawnModel = newModel ?? agentState.model ?? 'claude-sonnet-5';\n let effectiveHarness: 'claude-code' | 'ohmypi' | 'codex' | null = null;\n if (harness) {\n const harnessDecision = yield* Effect.promise(async () =>\n canUseHarnessSync(harness, spawnModel, await getProviderAuthMode(spawnModel)),\n );\n effectiveHarness = harnessDecision.allowed ? harness : 'claude-code';\n }\n\n const agentSessionName = `agent-${issueId.toLowerCase()}`;\n const projectPath = agentState.workspace\n ? dirname(agentState.workspace)\n : undefined;\n const projectConfig = resolveProjectFromIssueSync(issueId);\n const projectRoot = projectConfig?.projectPath ?? projectPath ?? process.cwd();\n const workspacePath = agentState.workspace ?? join(projectRoot, 'workspaces', `feature-${issueId.toLowerCase()}`);\n\n const args = buildPanStartArgs({\n issueId,\n model: spawnModel,\n harness: effectiveHarness,\n });\n\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.restart_fresh_spawn_requested', {\n args,\n model: spawnModel,\n harness: effectiveHarness,\n }));\n\n // Spawn detached `pan start` — same pattern the existing POST /api/agents\n // route uses, minus the HTTP hop. We deliberately write a placeholder\n // state.json (matching the existing spawn flow) so the dashboard\n // transitions the agent from \"stopped\" to \"starting\" within one refresh.\n saveAgentStateSync({\n id: agentSessionName,\n issueId,\n workspace: workspacePath,\n harness: effectiveHarness ?? 'claude-code',\n role: 'work',\n model: 'pending-work-spawn',\n status: 'starting',\n startedAt: new Date().toISOString(),\n });\n\n try {\n yield* Effect.promise(() => spawnPanCommandDetached({\n agentSessionName,\n issueId,\n role: 'work',\n workspacePath,\n args,\n cwd: workspacePath,\n }));\n } catch (err: any) {\n return jsonResponse({\n success: false,\n error: `Agent dir wiped but spawn failed: ${err?.message ?? String(err)}`,\n wiped: wipeResult.removed,\n }, { status: 500 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.restart_fresh_spawn_requested_complete', {\n model: spawnModel,\n harness: effectiveHarness,\n }));\n\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n spawn: true,\n agentId: id,\n issueId,\n removed: wipeResult.removed,\n spawnedModel: spawnModel,\n spawnedHarness: effectiveHarness,\n hint: 'Fresh work agent spawned. It will read .pan/continue.json and the branch state to continue.',\n });\n })),\n);\n\n// ─── Route: POST /api/agents/restart-all ──────────────────────────────────────\n//\n// Restart all running workspace agents using restartAgent() directly.\n// Quick mode (no graceful delay) to avoid serializing 30s waits across N agents.\n\nexport const postAgentsRestartAllRoute = HttpRouter.add(\n 'POST',\n '/api/agents/restart-all',\n Effect.gen(function* () {\n return yield* Effect.promise(async () => {\n try {\n const running = (await Effect.runPromise(listRunningAgents())).filter(a => a.tmuxActive);\n const results: { id: string; issueId: string; model: string; status: string }[] = [];\n\n for (const agent of running) {\n try {\n const result = await restartAgent(agent.id, { graceful: false });\n if (result.success) {\n results.push({ id: agent.id, issueId: agent.issueId, model: agent.model, status: 'restarted' });\n } else {\n results.push({ id: agent.id, issueId: agent.issueId, model: agent.model, status: `failed: ${result.error}` });\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[agents] Failed to restart ${agent.id}:`, msg);\n results.push({ id: agent.id, issueId: agent.issueId, model: agent.model, status: `failed: ${msg}` });\n }\n }\n\n const succeeded = results.filter(r => r.status === 'restarted').length;\n console.log(`[agents] Restarted ${succeeded}/${running.length} workspace agents`);\n return jsonResponse({ restarted: succeeded, total: running.length, results });\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return jsonResponse({ error: 'Failed to restart agents: ' + msg }, { status: 500 });\n }\n });\n }),\n);\n\n// ─── Route: POST /api/agents/:id/reset-session ─────────────────────────────\n// Clears saved Claude session tracking so the next start creates a fresh session.\n// Workspace, beads, and git state are preserved. JSONL files kept for cost history.\n\nexport const postAgentResetSessionRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/reset-session',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const eventStore = yield* EventStoreService;\n\n const lifecycle = yield* getWorkAgentLifecycleState(id);\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: `Agent ${id} not found`, lifecycle }, { status: 404 });\n }\n\n if (lifecycle.hasLiveTmuxSession) {\n return jsonResponse({ error: `Agent ${id} is running. Stop it first.`, lifecycle }, { status: 409 });\n }\n\n const previousSessionId = yield* getLatestSessionId(id);\n if (!previousSessionId) {\n return jsonResponse({ error: `Agent ${id} has no saved session to reset`, lifecycle }, { status: 404 });\n }\n\n const agentDir = getAgentDir(id);\n\n // Clear session.id\n yield* Effect.promise(() => rm(join(agentDir, 'session.id'), { force: true }));\n\n // Clear sessions.json\n yield* Effect.promise(() => rm(join(agentDir, 'sessions.json'), { force: true }));\n\n // Clear claudeSessionId from runtime.json (preserve other fields).\n // Must read/write directly — saveAgentRuntimeState merges with existing file.\n const runtimeFile = join(agentDir, 'runtime.json');\n if (existsSync(runtimeFile)) {\n try {\n const runtimeContent = yield* Effect.promise(() => readFile(runtimeFile, 'utf-8'));\n const runtime = JSON.parse(runtimeContent);\n delete runtime.claudeSessionId;\n yield* Effect.promise(() => writeFile(runtimeFile, JSON.stringify(runtime, null, 2)));\n } catch { /* non-fatal */ }\n }\n\n yield* killSession(id).pipe(Effect.catch(() => Effect.void));\n\n // Emit event so dashboard updates. PAN-1048 review feedback 004 (C1):\n // include issueId — without it AgentStoppedEvent fails Schema validation.\n //\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(agentState, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: agentState.issueId },\n });\n\n console.log(`[reset-session] Cleared session for ${id} (was: ${previousSessionId.slice(0, 8)}...)`);\n invalidateAgentsCache();\n return jsonResponse({ success: true, agentId: id, previousSessionId, lifecycle: yield* getWorkAgentLifecycleState(id) });\n })),\n);\n\n// ─── Route: POST /api/agents/restart-with-current-config ────────────────────\n// List all agents with their current vs new staffing, allowing operator to\n// selectively restart with new config\n\nexport interface RestartConfigChangeItem {\n agentId: string;\n issueId: string;\n currentModel: string;\n currentHarness: string;\n newModel: string;\n newHarness: string;\n changed: boolean;\n paused: boolean;\n troubled: boolean;\n status: string;\n}\n\nasync function buildRestartConfigChangeList(): Promise<RestartConfigChangeItem[]> {\n const agents = await Effect.runPromise(listRunningAgents());\n\n const items: RestartConfigChangeItem[] = [];\n\n for (const agent of agents) {\n if (!agent.issueId) continue;\n\n // Only include actually running agents with active tmux sessions\n if (!((agent as any).tmuxActive === true)) {\n continue;\n }\n\n // Skip paused/troubled agents (their gates stand)\n if ((agent as any).paused || (agent as any).troubled) {\n continue;\n }\n\n // Only work agents eligible for restart (skip conversations)\n if (agent.role !== 'work' && agent.role !== undefined) {\n continue;\n }\n\n // Compute new staffing from current config by reading the real vBRIEF work item\n const staffingResult = await resolveCurrentStaffing(agent.id, agent, agent.issueId);\n\n if (staffingResult.error) {\n // Skip agents where staffing cannot be resolved\n console.warn(`[restart-config] Skipping ${agent.id}: ${staffingResult.error}`);\n continue;\n }\n\n const newModel = staffingResult.newModel;\n const newHarness = staffingResult.newHarness;\n\n items.push({\n agentId: agent.id,\n issueId: agent.issueId,\n currentModel: agent.model,\n currentHarness: agent.harness ?? 'claude-code',\n newModel,\n newHarness,\n changed: agent.model !== newModel || (agent.harness ?? 'claude-code') !== newHarness,\n paused: (agent as any).paused ?? false,\n troubled: (agent as any).troubled ?? false,\n status: agent.status,\n });\n }\n\n return items;\n}\n\nexport const getAgentsRestartConfigRoute = HttpRouter.add(\n 'GET',\n '/api/agents/restart-with-current-config',\n httpHandler(Effect.gen(function* () {\n try {\n const items = yield* Effect.promise(() => buildRestartConfigChangeList());\n return jsonResponse({\n success: true,\n items,\n totalAgents: items.length,\n changedAgents: items.filter(i => i.changed).length,\n });\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return jsonResponse({ error: `Failed to build restart config list: ${msg}` }, { status: 500 });\n }\n })),\n);\n\n// ─── Route: POST /api/agents/restart-with-current-config ────────────────────\n// Restart selected agents with current config\n\nasync function resolveCurrentStaffing(agentId: string, agentState: any, issueId: string): Promise<{ newModel: string; newHarness: string; error?: string }> {\n const workspacePath = agentState.workspace;\n if (!workspacePath) {\n return { newModel: agentState.model, newHarness: agentState.harness ?? 'claude-code', error: 'No workspace path available' };\n }\n\n try {\n // Read the actual vBRIEF plan\n const plan = readWorkspacePlanSync(workspacePath);\n if (!plan || !plan.plan.items) {\n return { newModel: agentState.model, newHarness: agentState.harness ?? 'claude-code', error: 'Invalid plan structure' };\n }\n\n // Find the work item using the same logic as spawn-prep:\n // For slot agents, use slotItemId; for single-work agents, use first dispatchable item\n let workItem = null;\n if (agentState.slotItemId) {\n // Slot agent: find the item by ID\n workItem = plan.plan.items.find((item: any) => item.id === agentState.slotItemId);\n if (!workItem) {\n return { newModel: agentState.model, newHarness: agentState.harness ?? 'claude-code', error: `Scheduled item ${agentState.slotItemId} not found in plan` };\n }\n } else {\n // Single-work agent: use first dispatchable item (same as spawn-prep)\n const dispatchableItems = getDispatchableItems(plan, new Set());\n workItem = dispatchableItems[0];\n if (!workItem) {\n return { newModel: agentState.model, newHarness: agentState.harness ?? 'claude-code', error: 'No dispatchable items in plan' };\n }\n }\n\n // Use the real work item with plan metadata for staffing resolution\n // This matches the logic in resolveSingleWorkTierSpawnParams\n const config = loadConfigSync().config;\n const tiered = config.tieredExecution;\n const effectiveTieredEnabled = issueId\n ? resolveTieredExecutionEnabledForIssue(tiered, issueId, plan.plan.metadata)\n : resolveTieredExecutionEnabled(tiered, plan.plan.metadata);\n\n const staffing = resolveStaffing(workItem, {\n planMetadata: plan.plan.metadata,\n spawnKey: `work:${issueId.toLowerCase()}`,\n config: { ...config, tieredExecution: { ...tiered, enabled: effectiveTieredEnabled } },\n });\n\n return {\n newModel: staffing.model,\n newHarness: staffing.harness,\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n newModel: agentState.model,\n newHarness: agentState.harness ?? 'claude-code',\n error: `Failed to resolve staffing: ${msg}`,\n };\n }\n}\n\nexport const postAgentsRestartWithConfigRoute = HttpRouter.add(\n 'POST',\n '/api/agents/restart-with-current-config',\n httpHandler(Effect.gen(function* () {\n const body = yield* readJsonBody;\n const { agentIds } = body as { agentIds?: string[] };\n\n if (!agentIds || !Array.isArray(agentIds) || agentIds.length === 0) {\n return jsonResponse({ error: 'agentIds array is required and must not be empty' }, { status: 400 });\n }\n\n // Get eligible agents from the same predicate as the GET route\n const eligibleAgents = yield* Effect.promise(() => buildRestartConfigChangeList());\n const eligibleIds = new Set(eligibleAgents.map(a => a.agentId));\n\n const results: { id: string; status: string; newModel?: string; newHarness?: string; error?: string }[] = [];\n\n for (const agentId of agentIds) {\n try {\n // Check if agent is in the eligible set\n if (!eligibleIds.has(agentId)) {\n results.push({ id: agentId, status: 'ineligible', error: 'Agent is not eligible for restart (paused, troubled, stopped, or not a work agent)' });\n continue;\n }\n\n const agentState = yield* getAgentState(agentId);\n if (!agentState) {\n results.push({ id: agentId, status: 'not_found', error: `Agent state not found` });\n continue;\n }\n\n const issueId = agentState.issueId ?? agentId.replace(/^agent-/, '').toUpperCase();\n\n // Resolve new staffing using the real work item\n const staffingResult = yield* Effect.promise(() => resolveCurrentStaffing(agentId, agentState, issueId));\n\n if (staffingResult.error) {\n results.push({\n id: agentId,\n status: 'staffing_error',\n error: staffingResult.error,\n });\n continue;\n }\n\n const newModel = staffingResult.newModel;\n const newHarness = staffingResult.newHarness;\n\n // Restart with new config\n const restartResult = yield* Effect.promise(() => restartAgent(agentId, {\n model: newModel,\n harness: newHarness as any,\n graceful: false,\n }));\n\n if (restartResult.success) {\n results.push({\n id: agentId,\n status: 'restarted',\n newModel,\n newHarness,\n });\n } else {\n results.push({\n id: agentId,\n status: 'failed',\n error: restartResult.error,\n });\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n results.push({\n id: agentId,\n status: 'error',\n error: msg,\n });\n }\n }\n\n const succeeded = results.filter(r => r.status === 'restarted').length;\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n restarted: succeeded,\n total: agentIds.length,\n results,\n });\n })),\n);\n","import { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { getAgentHealth } from '../../../../lib/cloister/health.js';\nimport { performHandoff } from '../../../../lib/cloister/handoff.js';\nimport { loadCloisterConfigSync } from '../../../../lib/cloister/config.js';\nimport { getCloisterService } from '../../../../lib/cloister/service.js';\nimport { checkAllTriggers } from '../../../../lib/cloister/triggers.js';\nimport { calculateCostSync, getPricingSync, type TokenUsage } from '../../../../lib/cost.js';\nimport { normalizeModelName } from '../../../../lib/cost-parsers/jsonl-parser.js';\nimport { requireModelOverrideSync } from '../../../../lib/model-validation.js';\nimport { encodeClaudeProjectDir } from '../../../../lib/paths.js';\nimport { getRuntimeForAgent } from '../../../../lib/runtimes/index.js';\nimport {\n getAgentState,\n setAgentDeliveryMethod,\n} from '../../../../lib/agents.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport { readJsonBody } from './shared.js';\n\n// ─── Route: GET /api/agents/:id/cloister-health ──────────────────────────────\n\nexport const getAgentCloisterHealthRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/cloister-health',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const service = getCloisterService();\n const health = service.getAgentHealth(id);\n if (!health) {\n return jsonResponse({ error: 'Agent not found or runtime not available' }, { status: 404 });\n }\n return jsonResponse(health);\n })),\n);\n\n// ─── Route: GET /api/agents/:id/handoff/suggestion ───────────────────────────\n\nexport const getAgentHandoffSuggestionRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/handoff/suggestion',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: 'Agent not found' }, { status: 404 });\n }\n\n const runtime = getRuntimeForAgent(id);\n if (!runtime) {\n return jsonResponse({ error: 'Runtime not found for agent' }, { status: 404 });\n }\n\n const health = getAgentHealth(id, runtime);\n const triggers = yield* checkAllTriggers(\n id,\n agentState.workspace,\n agentState.issueId,\n agentState.model,\n health,\n loadCloisterConfigSync()\n );\n\n if (triggers.length > 0) {\n const trigger = triggers[0];\n return jsonResponse({\n suggested: true,\n trigger: trigger.type,\n currentModel: agentState.model,\n suggestedModel: trigger.suggestedModel,\n reason: trigger.reason,\n });\n }\n\n return jsonResponse({\n suggested: false,\n trigger: null,\n currentModel: agentState.model,\n suggestedModel: null,\n reason: 'No handoff triggers detected',\n });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/handoff ─────────────────────────────────────\n\nexport const postAgentHandoffRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/handoff',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { toModel, reason } = body as any;\n let targetModel: string;\n try {\n targetModel = requireModelOverrideSync(toModel);\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n\n const result = yield* performHandoff(id, {\n targetModel,\n reason: reason || 'Manual handoff from dashboard',\n });\n\n if (result.success) {\n return jsonResponse({\n success: true,\n newAgentId: result.newAgentId,\n newSessionId: result.newSessionId,\n });\n } else {\n return jsonResponse({ success: false, error: result.error }, { status: 500 });\n }\n })),\n);\n\n\n// ─── Route: GET /api/agents/:id/cost ─────────────────────────────────────────\n\nexport const getAgentCostRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/cost',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: 'Agent not found' }, { status: 404 });\n }\n\n let cost = 0;\n let inputTokens = 0;\n let outputTokens = 0;\n let cacheReadTokens = 0;\n let cacheWriteTokens = 0;\n let detectedModel = agentState.model || '';\n // Claude Code repeats the same `usage` on every JSONL line of one API response\n // (text line, each tool_use line, …). Dedup on requestId/message.id so a multi-block\n // turn is counted once instead of inflating tokens/cost ~2-3×.\n const countedUsageIds = new Set<string>();\n\n const homeDir = process.env.HOME || homedir();\n const claudeProjectsDir = join(homeDir, '.claude', 'projects');\n const workspacePath = agentState.workspace;\n\n if (workspacePath) {\n const projectDirName = encodeClaudeProjectDir(workspacePath);\n const projectDir = join(claudeProjectsDir, projectDirName);\n const sessionsIndexPath = join(projectDir, 'sessions-index.json');\n\n const parseJsonlCost = async (filePath: string) => {\n const jsonlContent = await readFile(filePath, 'utf-8');\n const lines = jsonlContent.split('\\n').filter((l: string) => l.trim());\n for (const line of lines) {\n try {\n const entry = JSON.parse(line);\n const usage = entry.message?.usage || entry.usage;\n const model = entry.message?.model || entry.model;\n const usageId = entry.requestId ?? entry.message?.id;\n if (usage && (usageId === undefined || !countedUsageIds.has(usageId))) {\n if (usageId !== undefined) countedUsageIds.add(usageId);\n inputTokens += usage.input_tokens || 0;\n outputTokens += usage.output_tokens || 0;\n cacheReadTokens += usage.cache_read_input_tokens || 0;\n cacheWriteTokens += usage.cache_creation_input_tokens || 0;\n }\n if (model && !detectedModel) {\n detectedModel = model;\n }\n } catch {}\n }\n };\n\n if (existsSync(sessionsIndexPath)) {\n try {\n const indexContent = JSON.parse(yield* Effect.promise(() => readFile(sessionsIndexPath, 'utf-8')));\n for (const sessionEntry of (indexContent.entries || [])) {\n if (sessionEntry?.fullPath && existsSync(sessionEntry.fullPath)) {\n yield* Effect.promise(() => parseJsonlCost(sessionEntry.fullPath));\n }\n }\n } catch {}\n }\n\n if (inputTokens === 0 && existsSync(projectDir)) {\n try {\n const files = (yield* Effect.promise(() => readdir(projectDir))).filter(f => f.endsWith('.jsonl'));\n for (const file of files) {\n yield* Effect.promise(() => parseJsonlCost(join(projectDir, file)));\n }\n } catch {}\n }\n }\n\n if (inputTokens > 0 || outputTokens > 0) {\n const modelInfo = normalizeModelName(detectedModel || 'claude-sonnet-4');\n const pricing = getPricingSync(modelInfo.provider, modelInfo.model);\n if (pricing) {\n const usage: TokenUsage = {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheWriteTokens,\n };\n cost = calculateCostSync(usage, pricing);\n }\n }\n\n return jsonResponse({\n agentId: id,\n model: detectedModel || agentState.model,\n tokens: {\n input: inputTokens,\n output: outputTokens,\n cacheRead: cacheReadTokens,\n cacheWrite: cacheWriteTokens,\n },\n cost,\n });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/delivery-method ─────────────────────────────\n// Updates the agent's delivery method (auto | channels | tmux) in state.json.\n\nexport function validateAgentDeliveryMethodOrigin(\n request: HttpServerRequest.HttpServerRequest,\n): { ok: true } | { ok: false; status: 403; body: { error: 'forbidden' } } {\n const originCheck = validateOrigin(request);\n if (originCheck.ok) return { ok: true };\n return { ok: false, status: 403, body: { error: 'forbidden' } };\n}\n\nexport const postAgentDeliveryMethodRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/delivery-method',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originDecision = validateAgentDeliveryMethodOrigin(request);\n if (!originDecision.ok) {\n return jsonResponse(originDecision.body, { status: originDecision.status });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const { deliveryMethod } = body as { deliveryMethod?: 'auto' | 'channels' | 'tmux' };\n\n if (!deliveryMethod || !['auto', 'channels', 'tmux'].includes(deliveryMethod)) {\n return jsonResponse({ error: 'deliveryMethod must be auto, channels, or tmux' }, { status: 400 });\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => setAgentDeliveryMethod(id, deliveryMethod));\n return jsonResponse({ success: true, agentId: id, deliveryMethod });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/switch-model ────────────────────────────────\n// Pipeline agent models are fixed at spawn. Changing a model tears down the\n// live session and discards context, so this route is retained only as a\n// server-side compatibility rejection for older clients.\n\nexport const postAgentSwitchModelRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/switch-model',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n return jsonResponse({\n error: `Agent ${id} model is locked once the agent is spawned`,\n }, { status: 409 });\n })),\n);\n","import { mkdir, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { randomUUID } from 'crypto';\nimport { Effect } from 'effect';\n\nimport { generateVBriefFilename } from './lifecycle.js';\nimport { serializeVBriefDocument } from './io.js';\nimport { FsError } from '../errors.js';\nimport type { VBriefDocument, VBriefSubItem } from './types.js';\n\nexport interface AutoSynthesizeIssueInput {\n issueId: string;\n title: string;\n body?: string | null;\n url?: string | null;\n}\n\nexport interface AutoSynthesizeResult {\n document: VBriefDocument;\n workspaceSpecPath: string;\n projectSpecPath: string;\n canonicalFilename: string;\n}\n\nfunction cleanMarkdownLine(line: string): string {\n return line\n .replace(/^[-*+]\\s+\\[[ xX]\\]\\s+/, '')\n .replace(/^[-*+]\\s+/, '')\n .replace(/^#+\\s+/, '')\n .replace(/`([^`]+)`/g, '$1')\n .trim();\n}\n\nexport function extractAcceptanceCriteriaFromIssue(title: string, body?: string | null): string[] {\n const text = body?.trim() ?? '';\n if (!text) return [`Implement ${title}`];\n\n const lines = text.split(/\\r?\\n/);\n // Only a real heading (or standalone bold/label line) counts — the phrase\n // appearing mid-prose must not start a section (PAN-2404).\n const headingIndex = lines.findIndex((line) =>\n /^#{1,6}\\s+.*acceptance(\\s+criteria)?\\b/i.test(line)\n || /^\\s*\\*{0,2}acceptance criteria\\*{0,2}:?\\s*$/i.test(line));\n\n // If an explicit Acceptance Criteria section exists, use only that section\n if (headingIndex >= 0) {\n // Find the end of this section (next heading or end of text)\n const sectionEndIndex = lines.findIndex((line, idx) =>\n idx > headingIndex && /^#+\\s+/.test(line)\n );\n const endIndex = sectionEndIndex >= 0 ? sectionEndIndex : lines.length;\n const sectionLines = lines.slice(headingIndex + 1, endIndex);\n\n const checklist = sectionLines\n .filter((line) => /^\\s*[-*+]\\s+\\[[ xX]\\]\\s+/.test(line))\n .map(cleanMarkdownLine)\n .filter(Boolean);\n if (checklist.length > 0) return checklist;\n\n const bullets = sectionLines\n .filter((line) => /^\\s*[-*+]\\s+/.test(line))\n .map(cleanMarkdownLine)\n .filter(Boolean)\n .slice(0, 8);\n if (bullets.length > 0) return bullets;\n }\n\n // No explicit Acceptance Criteria section: return a single generic AC\n return [`Issue's stated fix implemented with tests`];\n}\n\nexport function synthesizeMinimalVBrief(issue: AutoSynthesizeIssueInput): VBriefDocument {\n const issueId = issue.issueId.toUpperCase();\n const issueLabel = issueId.toLowerCase();\n const now = new Date().toISOString();\n const criteria = extractAcceptanceCriteriaFromIssue(issue.title, issue.body);\n const items: VBriefSubItem[] = criteria.map((criterion, index) => ({\n id: `auto-start.ac${index + 1}`,\n title: criterion,\n status: 'pending',\n created: now,\n metadata: { kind: 'acceptance_criterion' },\n }));\n\n const canonicalFilename = generateVBriefFilename(issueId, issue.title, now);\n\n return {\n vBRIEFInfo: {\n version: '0.8',\n created: now,\n updated: now,\n author: 'overdeck/auto-start',\n description: `Auto-synthesized minimal plan for ${issueId}: ${issue.title}`,\n inspectionPolicy: 'never',\n },\n plan: {\n id: issueLabel,\n title: issue.title,\n status: 'proposed',\n uid: randomUUID(),\n author: 'overdeck/auto-start',\n sequence: 1,\n created: now,\n updated: now,\n references: issue.url ? [{ uri: issue.url, label: issueId, type: 'issue' }] : [],\n tags: ['auto-start'],\n metadata: { canonicalFilename },\n narratives: {\n Problem: issue.body?.trim() || issue.title,\n Proposal: 'Implement the issue directly from the tracker-provided title and body.',\n },\n items: [\n {\n id: 'auto-start',\n title: 'Implement issue',\n status: 'pending',\n priority: 'medium',\n created: now,\n metadata: {\n difficulty: 'simple',\n issueLabel,\n requiresInspection: false,\n inspectionDepth: 'fast',\n },\n narrative: { Action: issue.body?.trim() || issue.title },\n items,\n },\n ],\n edges: [],\n },\n };\n}async function writeAutoStartVBriefPromise(\n projectRoot: string,\n workspacePath: string,\n issue: AutoSynthesizeIssueInput,\n): Promise<AutoSynthesizeResult> {\n const document = synthesizeMinimalVBrief(issue);\n const canonicalFilename = document.plan.metadata?.canonicalFilename as string;\n\n const projectSpecsDir = join(projectRoot, '.pan', 'specs');\n const projectSpecPath = join(projectSpecsDir, canonicalFilename);\n const projectDocument: VBriefDocument = {\n ...document,\n plan: { ...document.plan, status: 'proposed' },\n };\n\n await mkdir(projectSpecsDir, { recursive: true });\n await writeFile(projectSpecPath, serializeVBriefDocument(projectDocument), 'utf-8');\n\n return {\n document,\n workspaceSpecPath: projectSpecPath,\n projectSpecPath,\n canonicalFilename,\n };\n}\n\n// ─── Effect variant (PAN-1249) ────────────────────────────────────────────────\n\n/**\n * Effect variant of writeAutoStartVBrief. Wraps mkdir + writeFile in a typed\n * FsError channel so callers in Effect-native code can compose with this\n * synthesis step without losing failure typing.\n */\nexport const writeAutoStartVBrief = (\n projectRoot: string,\n workspacePath: string,\n issue: AutoSynthesizeIssueInput,\n): Effect.Effect<AutoSynthesizeResult, FsError> =>\n Effect.tryPromise({\n try: () => writeAutoStartVBriefPromise(projectRoot, workspacePath, issue),\n catch: (cause) =>\n new FsError({ path: projectRoot, operation: 'writeAutoStartVBrief', cause }),\n });\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { lstat, mkdir, readFile, rm } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport type { Role } from '@overdeck/contracts';\nimport { Effect } from 'effect';\n\nimport { buildChildEnvWithoutTmuxSync } from '../../../../lib/child-env.js';\nimport {\n saveAgentState,\n saveAgentStateSync,\n} from '../../../../lib/agents.js';\nimport type { RemoteWorkspaceMetadata } from '../../../../lib/remote/interface.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { saveAgentStateAndEmitEventProgram } from '../../services/agent-projection.js';\nimport {\n appendAgentLifecycleLog,\n buildPanStartArgs,\n emitStartAgentPhase,\n execAsync,\n execFileAsync,\n getIssueDataService,\n invalidateAgentsCache,\n updateRegistryForAgentStart,\n type SpawnGuardrailDecision,\n} from './shared.js';\n\ntype LifecycleTransition = {\n transitionTo: (\n issueId: string,\n status: 'in_progress',\n ) => Effect.Effect<unknown, unknown, never>;\n};\n\ntype EventStoreAppend = {\n append: (event: {\n type: 'issue.statusChanged';\n timestamp: string;\n payload: { issueId: string; status: 'In Progress'; canonicalStatus: 'in_progress' };\n }) => Effect.Effect<unknown, unknown, never>;\n};\n\ntype SpawnPanCommand = (args: string[], cwd?: string) => Promise<string>;\n\nexport function handleRemoteAgentSpawn(input: {\n issueId: string;\n workspacePath: string;\n workspaceMetadata: RemoteWorkspaceMetadata;\n spawnModel: string;\n projectPath: string;\n spawnGuardrails: SpawnGuardrailDecision;\n lifecycle: LifecycleTransition;\n}) {\n return Effect.gen(function* () {\n const {\n issueId,\n workspacePath,\n workspaceMetadata,\n spawnModel,\n projectPath,\n spawnGuardrails,\n lifecycle,\n } = input;\n\n const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import('../../../../lib/remote/remote-agents.js'));\n const { createFlyProviderFromConfig } = yield* Effect.promise(() => import('../../../../lib/remote/index.js'));\n const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import('../../../../lib/config.js'));\n const panConfig = loadPanConfig();\n const spendCap = checkRemoteSpendCap(panConfig);\n if (!spendCap.allowed) {\n return jsonResponse({ error: spendCap.message }, { status: 429 });\n }\n const fly = createFlyProviderFromConfig(panConfig.remote);\n yield* Effect.promise(() => fly.syncAllCredentials(workspaceMetadata.vmName));\n\n const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import('../../../../lib/cloister/work-agent-prompt.js'));\n const trackerContext = yield* Effect.promise(() => getTrackerContext(issueId, workspacePath));\n const agentPrompt = yield* Effect.promise(() => buildWorkAgentPrompt({\n issueId,\n env: 'REMOTE',\n workspacePath: '/workspace',\n skipDynamicContext: true,\n trackerContext,\n }));\n\n emitStartAgentPhase(issueId, 'spawn', 'start', 'starting remote work agent', {\n workspacePath,\n vmName: workspaceMetadata.vmName,\n });\n const state = yield* Effect.promise(() => spawnRemoteAgent({\n issueId,\n workspace: workspaceMetadata,\n prompt: agentPrompt,\n model: spawnModel,\n tier: fly.getResiliencyTier(),\n }));\n\n // Write canonical state.json so activeRoleRunExists() sees this remote\n // work agent as active before we emit the lifecycle transition below.\n // spawnRemoteAgent only writes remote-state.json; without state.json the\n // Cloister duplicate-spawn guard misses the in-flight remote agent and\n // would spawn a second local work run when in_progress is emitted.\n yield* saveAgentState({\n id: state.id,\n issueId: state.issueId,\n workspace: workspacePath,\n role: 'work',\n model: spawnModel,\n status: 'starting',\n startedAt: state.startedAt,\n harness: 'claude-code',\n });\n updateRegistryForAgentStart(state.issueId, workspacePath, state.id);\n\n // PAN-1048: lifecycle.transitionTo() is the single source of issue.transitioned.\n // The redundant issue.statusChanged emit was racing with reactive Cloister:\n // Cloister mapped 'in_progress' → 'work' role and tried to spawn a second\n // run while activeRoleRunExists() still saw no state.json for the\n // in-flight spawn above. state.json is now written before this emit.\n yield* Effect.promise(() => Effect.runPromise(\n lifecycle.transitionTo(issueId, 'in_progress').pipe(Effect.catch(() => Effect.void))\n ));\n\n // PAN-1048 review feedback 003: emit a contract-compliant agent.started\n // event. The reducer writes event.payload.agent into agentsById keyed by\n // event.payload.agentId — the previous shape ({ agentId: issueId, issueId })\n // omitted .agent and used the issue ID as the key, inserting `undefined`\n // into the read model and breaking dashboard consumers.\n //\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram({\n id: state.id,\n issueId: state.issueId,\n workspace: workspacePath,\n role: 'work',\n model: spawnModel,\n status: state.status,\n startedAt: state.startedAt,\n harness: 'claude-code',\n }, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: state.id,\n issueId: state.issueId,\n agent: {\n id: state.id,\n issueId: state.issueId,\n role: 'work' as const,\n model: spawnModel,\n status: state.status,\n startedAt: state.startedAt,\n lastActivity: state.lastActivity,\n },\n },\n });\n emitStartAgentPhase(issueId, 'spawn', 'success', 'remote work agent spawn requested', {\n agentId: state.id,\n vmName: workspaceMetadata.vmName,\n });\n try { getIssueDataService().patchIssue(issueId, { status: 'In Progress', canonicalStatus: 'in_progress' }); } catch { /* non-fatal */ }\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n message: `Starting remote agent for ${issueId}`,\n remote: true,\n vmName: workspaceMetadata.vmName,\n agentId: state.id,\n projectPath,\n guardrails: spawnGuardrails,\n });\n });\n}\n\nexport function handleContainerOrchestration(input: {\n issueId: string;\n workspacePath: string;\n devScript: string;\n agentSessionName: string;\n role: Role;\n effectiveHarness: 'claude-code' | 'ohmypi' | 'codex' | null;\n allowHost: boolean;\n spawnModel: string;\n spawnGuardrails: SpawnGuardrailDecision;\n projectPath: string;\n eventStore: EventStoreAppend;\n spawnPanCommand: SpawnPanCommand;\n updateIssueStatus: () => Promise<void>;\n}) {\n return Effect.gen(function* () {\n const {\n issueId,\n workspacePath,\n devScript,\n agentSessionName,\n role,\n effectiveHarness,\n allowHost,\n spawnModel,\n spawnGuardrails,\n projectPath,\n eventStore,\n spawnPanCommand,\n updateIssueStatus,\n } = input;\n\n if (existsSync(workspacePath) && existsSync(devScript)) {\n let dockerRunning = false;\n try {\n yield* Effect.promise(() => execAsync('docker info >/dev/null 2>&1', { encoding: 'utf-8' }));\n dockerRunning = true;\n } catch {}\n\n if (dockerRunning) {\n const getComposeProjectName = async (id: string, wPath: string): Promise<string> => {\n const featureFolder = `feature-${id.toLowerCase()}`;\n const expected = `overdeck-${featureFolder}`;\n const validate = (value: string, devPath: string): string => {\n if (value !== expected) {\n throw new Error(`Invalid COMPOSE_PROJECT_NAME in ${devPath}: expected ${expected}`);\n }\n return value;\n };\n\n const devScriptPaths = [join(wPath, '.devcontainer', 'dev'), join(wPath, 'dev')];\n for (const devPath of devScriptPaths) {\n try {\n if (existsSync(devPath)) {\n const content = await readFile(devPath, 'utf-8');\n const match = content.match(/COMPOSE_PROJECT_NAME=\"([^$\"]*)\\$\\{FEATURE_FOLDER\\}\"/);\n if (match) return validate(`${match[1]}${featureFolder}`, devPath);\n const literalMatch = content.match(/COMPOSE_PROJECT_NAME=\"([^\"]+)\"/);\n if (literalMatch) return validate(literalMatch[1], devPath);\n }\n } catch (error) {\n if (error instanceof Error && error.message.startsWith('Invalid COMPOSE_PROJECT_NAME')) throw error;\n }\n }\n return expected;\n };\n\n let featureName: string;\n try {\n featureName = yield* Effect.promise(() => getComposeProjectName(issueId, workspacePath));\n } catch (error) {\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: error instanceof Error ? error.message : String(error),\n }, { status: 422 });\n }\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_container_check', {\n issueId,\n featureName,\n workspacePath,\n }));\n let containersReady = false;\n\n try {\n const { stdout: existing } = yield* Effect.promise(() => execFileAsync(\n 'docker',\n ['ps', '--filter', `name=${featureName}`, '--format', '{{.Names}}|{{.Status}}'],\n { encoding: 'utf-8' }\n ));\n const runningContainers = existing.trim().split('\\n').filter(Boolean);\n const allHealthy = runningContainers.length > 0 && runningContainers.every(line => {\n const status = line.split('|')[1] || '';\n return status.includes('Up') && (!status.includes('(') || status.includes('(healthy)'));\n });\n if (allHealthy) containersReady = true;\n } catch {}\n\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_container_check_result', {\n issueId,\n featureName,\n containersReady,\n }));\n\n if (!containersReady && !allowHost) {\n const earlyAgentId = agentSessionName;\n const earlyStateDir = join(homedir(), '.overdeck', 'agents', earlyAgentId);\n yield* Effect.promise(() => mkdir(earlyStateDir, { recursive: true }));\n // PAN-1048 R2: legacy `runtime` field removed; PAN-1055: persist user-picked harness.\n saveAgentStateSync({\n id: earlyAgentId,\n issueId,\n ...(effectiveHarness ? { harness: effectiveHarness } : {}),\n model: 'pending-container-start',\n status: 'starting',\n startedAt: new Date().toISOString(),\n workspace: workspacePath,\n role,\n hostOverride: allowHost || undefined,\n });\n updateRegistryForAgentStart(issueId, workspacePath, earlyAgentId);\n yield* Effect.promise(() => appendAgentLifecycleLog(earlyAgentId, 'agent.start_waiting_for_containers', {\n issueId,\n featureName,\n workspacePath,\n role,\n }));\n\n const containerActivityId = `containers-${Date.now()}`;\n\n // Start containers in background and spawn agent when ready\n (async () => {\n try {\n const containerUid = process.getuid?.() ?? 1000;\n const containerGid = process.getgid?.() ?? 1000;\n await appendAgentLifecycleLog(earlyAgentId, 'agent.container_start_spawned', {\n issueId,\n featureName,\n workspacePath,\n });\n const containerChild = spawn('./dev', ['all'], {\n cwd: workspacePath,\n stdio: 'ignore',\n env: buildChildEnvWithoutTmuxSync(process.env, { UID: String(containerUid), GID: String(containerGid), DOCKER_USER: `${containerUid}:${containerGid}` }),\n detached: true,\n });\n containerChild.unref();\n\n const maxWaitMs = 3 * 60 * 1000;\n const pollIntervalMs = 3000;\n const startTime = Date.now();\n let healthy = false;\n\n while (Date.now() - startTime < maxWaitMs) {\n try {\n const { stdout } = await execFileAsync(\n 'docker',\n ['ps', '--filter', `name=${featureName}`, '--format', '{{.Names}}|{{.Status}}'],\n { encoding: 'utf-8' }\n );\n const containers = stdout.trim().split('\\n').filter(Boolean);\n const allH = containers.length > 0 && containers.every(line => {\n const status = line.split('|')[1] || '';\n return status.includes('Up') && (!status.includes('(') || status.includes('(healthy)'));\n });\n if (allH) { healthy = true; break; }\n } catch {}\n await new Promise(r => setTimeout(r, pollIntervalMs));\n }\n\n await appendAgentLifecycleLog(earlyAgentId, healthy ? 'agent.container_wait_succeeded' : 'agent.container_wait_timed_out', {\n issueId,\n featureName,\n waitedMs: Date.now() - startTime,\n });\n\n if (!healthy) {\n // PAN-1048 R2: legacy `runtime` removed; PAN-1055: persist user-picked harness.\n saveAgentStateSync({\n id: earlyAgentId,\n issueId,\n ...(effectiveHarness ? { harness: effectiveHarness } : {}),\n model: 'pending-container-start',\n status: 'error',\n startedAt: new Date().toISOString(),\n workspace: workspacePath,\n role,\n });\n return;\n }\n\n // Docker named volumes may create root-owned empty node_modules.\n // Remove them — workspace creation runs bun install which creates\n // correct workspace-aware node_modules with proper local package resolution.\n for (const nmDir of [join(workspacePath, 'node_modules'), join(workspacePath, 'src', 'dashboard', 'frontend', 'node_modules')]) {\n try {\n if (existsSync(nmDir)) {\n const stat = await lstat(nmDir);\n if (!stat.isSymbolicLink()) {\n await rm(nmDir, { recursive: true, force: true });\n console.log(`[start-agent] Removed Docker-created ${nmDir}`);\n }\n }\n } catch (nmErr: any) {\n console.warn(`[start-agent] Could not remove ${nmDir}: ${nmErr.message}`);\n }\n }\n\n await appendAgentLifecycleLog(earlyAgentId, 'agent.work_spawn_requested_after_containers', {\n issueId,\n role,\n workspacePath,\n harness: effectiveHarness,\n });\n emitStartAgentPhase(issueId, 'spawn', 'start', 'starting local work agent after containers became healthy', { workspacePath });\n const activityId = await spawnPanCommand(\n buildPanStartArgs({\n issueId,\n model: spawnModel,\n harness: effectiveHarness,\n allowHost,\n }),\n workspacePath,\n );\n emitStartAgentPhase(issueId, 'spawn', 'success', 'local work agent spawn requested after container startup', {\n workspacePath,\n activityId,\n });\n await updateIssueStatus();\n } catch (err: any) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n emitStartAgentPhase(issueId, 'spawn', 'failure', errorMessage, { workspacePath });\n await appendAgentLifecycleLog(earlyAgentId, 'agent.container_start_failed', {\n issueId,\n error: errorMessage,\n }).catch(() => undefined);\n // PAN-1048 R2: legacy `runtime` removed from state writes.\n try { saveAgentStateSync({\n id: earlyAgentId,\n issueId,\n model: 'pending-container-start',\n status: 'error',\n startedAt: new Date().toISOString(),\n workspace: workspacePath,\n role,\n }); } catch { /* non-fatal */ }\n console.error(`[start-agent] Background container startup failed for ${issueId}:`, err);\n }\n })();\n\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'issue.statusChanged',\n timestamp: new Date().toISOString(),\n payload: { issueId, status: 'In Progress', canonicalStatus: 'in_progress' },\n })));\n try { getIssueDataService().patchIssue(issueId, { status: 'In Progress', canonicalStatus: 'in_progress' }); } catch { /* non-fatal */ }\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n message: `Starting containers and agent for ${issueId} (this may take a few minutes)`,\n startingContainers: true,\n containerActivityId,\n agentId: earlyAgentId,\n projectPath,\n guardrails: spawnGuardrails,\n });\n }\n }\n }\n\n return null;\n });\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { cp, mkdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { saveAgentStateSync, determineModel, getProviderAuthMode, getAgentState } from '../../../../lib/agents.js';\nimport { buildChildEnvWithoutTmuxSync } from '../../../../lib/child-env.js';\nimport { checkCodexAuthStatus } from '../../../../lib/codex-auth.js';\nimport { canUseHarnessSync } from '../../../../lib/harness-policy.js';\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { extractPrefixSync, parseIssueIdSync } from '../../../../lib/issue-id.js';\nimport { PAN_CONTINUE_FILENAME, PAN_DIRNAME } from '../../../../lib/pan-dir/types.js';\nimport { loadWorkspaceMetadataSync as loadWorkspaceMetadataFn } from '../../../../lib/remote/workspace-metadata.js';\nimport { getWorkAgentLifecycleState } from '../../../../lib/work-agent-lifecycle.js';\nimport { withBdMutex } from '../../../../lib/bd-mutex.js';\nimport { validateProviderHealth } from '../../../../lib/provider-health.js';\nimport { getProjectSync, resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { isStateMigrated } from '../../../../lib/state-home.js';\nimport { shouldCommitLegacyWorkspaceArtifacts } from '../../../../lib/state-read-home.js';\nimport { getWorkspaceStackHealth } from '../../../../lib/workspace/stack-health.js';\nimport { writeAutoStartVBrief } from '../../../../lib/vbrief/auto-synthesize.js';\nimport { findPlan, readPlan } from '../../../../lib/vbrief/io.js';\nimport { transitionVBriefOnMain, updatePlanStatus } from '../../../../lib/vbrief/lifecycle-io.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { ReadModelService } from '../../read-model.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { saveAgentStateAndEmitEventProgram } from '../../services/agent-projection.js';\nimport { IssueLifecycle } from '../../services/issue-lifecycle.js';\nimport { getSystemHealthSnapshot } from '../../services/system-health-service.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport { sessionExists, killSession } from '../../../../lib/tmux.js';\nimport {\n appendAgentLifecycleLog,\n buildHostOverrideConfirmation,\n buildPanStartArgs,\n emitStartAgentPhase,\n evaluateAgentStartGate,\n evaluateSpawnGuardrails,\n execAsync,\n execFileAsync,\n getIssueDataService,\n getProjectPath,\n invalidateAgentsCache,\n readJsonBody,\n spawnPanCommandDetached,\n updateRegistryForAgentStart,\n} from './shared.js';\nimport {\n handleContainerOrchestration,\n handleRemoteAgentSpawn,\n} from './spawn-helpers.js';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * PAN-2386: emit a dashboard activity event when start-agent refuses to spawn\n * because the workspace is dirty. Kept as a pure function so the refusal path\n * can be unit-tested without booting the full Effect route stack.\n */\nexport function emitDirtyWorkspaceRefusalActivity(issueId: string, porcelain: string): void {\n try {\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'warn',\n message: `Workspace dirty — agent start refused for ${issueId}`,\n issueId,\n details: JSON.stringify({\n reason: 'Workspace has uncommitted changes. Commit, discard, or resolve and retry.',\n porcelain: porcelain.split('\\n').slice(0, 5),\n }),\n });\n } catch { /* non-fatal — activity emit should not block the response */ }\n}\n\nexport function spawnGuardrailResourcesHint(hint?: string): string {\n const resourcesHint = 'Open /resources to inspect Machine Room pressure before retrying.';\n return hint ? `${hint} ${resourcesHint}` : resourcesHint;\n}\n\n/**\n * Count materialized beads for an issue by shelling out to `bd list`.\n *\n * Returns 0 on ANY bd failure — non-zero exit (e.g. bd refusing with \"Linear\n * data has never been pulled\"), an unparseable JSON body, or the 10s timeout.\n * The caller treats 0 as \"no beads yet\" and falls through to auto-recovery\n * (createBeadsFromVBrief) and, failing that, a clean 422 `beads_required`.\n *\n * The failure is folded into the success channel with `Effect.matchCause`. A\n * bare `try/catch` around `yield* withBdMutex(...)` does NOT work: withBdMutex\n * converts the child-process rejection into an Effect error-channel failure\n * (via its internal `Effect.tryPromise`), which short-circuits the fiber and is\n * invisible to a JS `try/catch`. Before this helper, that escaped as an\n * unhandled 500 on POST /api/agents instead of the designed 422.\n */\nexport const countBeadsForIssue = (\n workspacePath: string,\n issueLower: string,\n): Effect.Effect<number, never> =>\n withBdMutex(() => Effect.promise(() => execFileAsync(\n 'bd',\n ['list', '--json', '-l', issueLower, '--status', 'all', '--limit', '0'],\n { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 },\n ))).pipe(\n Effect.matchCause({\n onFailure: () => 0,\n onSuccess: ({ stdout }) => {\n try {\n const bdTasks = JSON.parse(stdout.trim() || '[]');\n return Array.isArray(bdTasks) ? bdTasks.length : 0;\n } catch {\n return 0;\n }\n },\n }),\n );\n\n// ─── Route: POST /api/agents (start agent) ───────────────────────────────────\n\nexport const postAgentsRoute = HttpRouter.add(\n 'POST',\n '/api/agents',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const lifecycle = yield* IssueLifecycle;\n const readModel = yield* ReadModelService;\n\n const { issueId, projectId } = body as any;\n const autoStart = (body as any).auto === true;\n const guardrailAcknowledged = (body as any).guardrailAcknowledged === true;\n const requestedHostOverride = (body as any).host === true || (body as any).allowHost === true;\n\n if (!issueId) {\n return jsonResponse({ error: 'issueId required' }, { status: 400 });\n }\n\n const legacyFields = ['workType', 'phase', 'agentType'].filter((field) => field in (body as Record<string, unknown>));\n if (legacyFields.length > 0) {\n return jsonResponse({\n error: `Legacy start-agent field(s) are no longer accepted: ${legacyFields.join(', ')}. Send role: 'work' instead.`,\n }, { status: 400 });\n }\n\n const role = (body as any).role ?? 'work';\n if (role !== 'work') {\n return jsonResponse({ error: `Unsupported agent role \"${String(role)}\". POST /api/agents only starts role: 'work'.` }, { status: 400 });\n }\n\n // Reject bare numeric IDs (e.g. \"484\") — they have no project prefix, so tracker\n // routing and workspace naming both fail. Require \"PAN-484\" style.\n if (/^\\d+$/.test(String(issueId))) {\n return jsonResponse(\n {\n error: `Invalid issueId \"${issueId}\": bare numeric IDs are not allowed. Use a prefixed ID (e.g. PAN-${issueId}).`,\n hint: 'Issue IDs must include a project prefix (e.g. PAN-484, MIN-123).',\n },\n { status: 422 },\n );\n }\n\n const parsedIssueId = parseIssueIdSync(String(issueId));\n if (!parsedIssueId) {\n return jsonResponse(\n {\n error: `Invalid issueId \"${issueId}\": issue IDs must use a supported project format (e.g. PAN-484, MIN-123).`,\n hint: 'Issue IDs must include a project prefix and numeric identifier.',\n },\n { status: 422 },\n );\n }\n\n const hostOverrideConfirmation = buildHostOverrideConfirmation(String(issueId));\n const allowHost = requestedHostOverride && (body as any).hostOverrideConfirmation === hostOverrideConfirmation;\n if (requestedHostOverride && !allowHost) {\n return jsonResponse({\n success: false,\n error: 'host_override_confirmation_required',\n requiresHostConfirmation: true,\n confirmation: hostOverrideConfirmation,\n hint: `Host override bypasses workspace isolation. Retry only after explicitly confirming: ${hostOverrideConfirmation}`,\n }, { status: 409 });\n }\n\n // Guard: reject starting agents for already-closed issues\n const issueDataService = getIssueDataService();\n const cachedIssues = issueDataService.getIssues();\n const cachedIssue = cachedIssues.find(\n (i: any) => (i.identifier || '').toUpperCase() === issueId.toUpperCase()\n );\n if (cachedIssue && (cachedIssue.canonicalStatus === 'done' || cachedIssue.canonicalStatus === 'canceled')) {\n return jsonResponse(\n {\n error: `Issue ${issueId} is already closed (${cachedIssue.canonicalStatus}). Cannot start an agent for a closed issue.`,\n hint: 'Reopen the issue first if you need to resume work.',\n },\n { status: 422 },\n );\n }\n\n const issueLower = parsedIssueId.normalized;\n const agentSessionName = `agent-${issueLower}`;\n const startGateBlock = evaluateAgentStartGate(agentSessionName, yield* getAgentState(agentSessionName));\n if (startGateBlock) {\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_blocked_gate', {\n issueId,\n paused: startGateBlock.paused,\n troubled: startGateBlock.troubled,\n reason: startGateBlock.error,\n }));\n return jsonResponse(startGateBlock, { status: 409 });\n }\n\n const workspaceMetadata = loadWorkspaceMetadataFn(issueId);\n const isRemote = workspaceMetadata?.location === 'remote';\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const resolvedProject = resolveProjectFromIssueSync(String(issueId));\n const projectConfig = resolvedProject ? getProjectSync(resolvedProject.projectKey) : null;\n const projectPath = projectConfig?.path ?? getProjectPath(projectId, issuePrefix);\n\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n if (!existsSync(workspacePath)) {\n try {\n const nodeDir = dirname(process.execPath);\n yield* Effect.promise(() => execAsync(\n `pan workspace create ${issueId} --local`,\n { cwd: projectPath, encoding: 'utf-8', timeout: 60000, env: buildChildEnvWithoutTmuxSync(process.env, { PATH: `${nodeDir}:${process.env.PATH ?? ''}` }) }\n ));\n } catch (wsErr) {\n return jsonResponse({\n error: `Failed to create workspace for ${issueId}: ${(wsErr as Error).message}`,\n hint: 'Try creating the workspace manually: pan workspace create ' + issueId + ' --local',\n }, { status: 500 });\n }\n }\n\n const workspacePanDir = join(workspacePath, PAN_DIRNAME);\n const workspacePanContinuePath = join(workspacePanDir, PAN_CONTINUE_FILENAME);\n\n const workspaceBeadsDir = join(workspacePath, '.beads');\n if (!existsSync(workspaceBeadsDir)) {\n const projectRootBeadsDir = join(projectPath, '.beads');\n if (existsSync(projectRootBeadsDir)) {\n try {\n yield* Effect.promise(() => cp(projectRootBeadsDir, workspaceBeadsDir, { recursive: true }));\n } catch {}\n }\n }\n\n let planPath = yield* findPlan(workspacePath);\n if (autoStart && !planPath) {\n const issueTitle = cachedIssue?.title || issueId;\n const issueBody = cachedIssue?.description || '';\n // writeAutoStartVBrief is Effect-returning — yield it directly (PAN-1768).\n yield* writeAutoStartVBrief(projectPath, workspacePath, {\n issueId,\n title: issueTitle,\n body: issueBody,\n url: cachedIssue?.url,\n });\n planPath = yield* findPlan(workspacePath);\n }\n if (!planPath) {\n return jsonResponse({\n error: `No workspace vBRIEF found for ${issueId}. Work agents require a finalized plan with matching beads.`,\n hint: 'Run planning first, or use auto-start to synthesize a plan before starting the work agent.',\n issueId,\n }, { status: 422 });\n }\n\n const planReadResult = yield* readPlan(planPath).pipe(\n Effect.match({\n onFailure: (planErr) => ({ _tag: 'failure' as const, planErr }),\n onSuccess: (planDoc) => ({ _tag: 'success' as const, planDoc }),\n }),\n );\n if (planReadResult._tag === 'failure') {\n const { planErr } = planReadResult;\n return jsonResponse({\n error: `Could not read workspace vBRIEF for ${issueId}: ${planErr instanceof Error ? planErr.message : String(planErr)}`,\n hint: 'Re-run planning to produce a readable vBRIEF before starting the work agent.',\n issueId,\n }, { status: 422 });\n }\n const { planDoc } = planReadResult;\n\n const planIssueId = planDoc?.plan?.id;\n if (planIssueId && planIssueId.toLowerCase() !== issueLower) {\n return jsonResponse({\n error: `Plan in workspace is for ${planIssueId.toUpperCase()}, not ${issueId}. The workspace contains stale planning artifacts from a different issue.`,\n hint: 'Run planning for this issue first, or clean the workspace planning artifacts.',\n issueId,\n expectedIssue: issueId,\n actualIssue: planIssueId.toUpperCase(),\n }, { status: 422 });\n }\n\n const planItemCount = planDoc?.plan?.items?.length ?? 0;\n if (planItemCount === 0) {\n return jsonResponse({\n error: 'Plan exists but contains no items. Planning may have failed or produced an empty plan.',\n hint: 'Re-run planning to produce a plan with tasks and acceptance criteria.',\n issueId,\n }, { status: 422 });\n }\n\n // countBeadsForIssue swallows all bd failures into 0 — a bd error here (e.g.\n // \"Linear data has never been pulled\" exiting non-zero) must fall through to\n // auto-recovery + a clean 422, never escape as an unhandled 500.\n let beadCount = yield* countBeadsForIssue(workspacePath, issueLower);\n let hasBeads = beadCount > 0;\n if (planItemCount !== null && planItemCount > 0 && beadCount !== planItemCount) {\n hasBeads = false;\n }\n\n let recoveryError: string | null = null;\n if (!hasBeads) {\n // Auto-recovery: beads DB may not have been initialized (fresh install, or planning\n // completed before bd init ran). Attempt to create beads from the vBRIEF plan now.\n console.log(`[agents] No beads for ${issueId} — attempting auto-recovery via createBeadsFromVBrief`);\n try {\n const { createBeadsFromVBrief } = yield* Effect.promise(() => import('../../../../lib/vbrief/beads.js'));\n const recovery = yield* createBeadsFromVBrief(workspacePath);\n beadCount = recovery.created.length;\n hasBeads = recovery.created.length > 0 && (planItemCount === null || recovery.created.length === planItemCount);\n if (hasBeads) {\n console.log(`[agents] Auto-recovery created ${recovery.created.length} beads for ${issueId}`);\n } else if (recovery.created.length > 0 && planItemCount !== null) {\n recoveryError = `created ${recovery.created.length} beads, but vBRIEF has ${planItemCount} plan items`;\n } else if (recovery.errors.length > 0) {\n recoveryError = recovery.errors[0] ?? 'Unknown error during beads creation';\n console.warn(`[agents] Auto-recovery errors: ${recovery.errors.join(', ')}`);\n } else {\n recoveryError = 'createBeadsFromVBrief returned no beads and no errors';\n }\n } catch (recoveryErr: any) {\n recoveryError = recoveryErr.message;\n console.warn(`[agents] Auto-recovery failed: ${recoveryErr.message}`);\n }\n }\n\n if (!hasBeads) {\n // PAN-1048 C6: Beads are a hard requirement for the work role — without\n // them the agent has nothing to claim, no Jidoka inspection scope, and\n // commits would batch across multiple beads. Recovery already attempted\n // above via createBeadsFromVBrief; if that failed the workspace's vBRIEF\n // is missing or malformed, which is a planning bug that must surface to\n // the operator. Refuse the spawn with 422 instead of starting a half-\n // configured work agent that will silently misbehave.\n const detail = recoveryError\n ? ` Beads recovery failed: ${recoveryError}.`\n : ' No beads exist for this issue.';\n console.warn(`[agents] Refusing to start work agent for ${issueId} without beads.${detail}`);\n return jsonResponse({\n success: false,\n error: 'beads_required',\n message: `Cannot start work agent for ${issueId} without beads.${detail} `\n + 'Re-run planning so beads can be materialized from the vBRIEF plan, then retry.',\n ...(recoveryError ? { recoveryError } : {}),\n }, { status: 422 });\n }\n\n const health = yield* readModel.getSnapshot.pipe(\n Effect.flatMap((snapshot) => Effect.promise(() => getSystemHealthSnapshot(snapshot))),\n );\n emitStartAgentPhase(issueId, 'guardrails', 'start', 'evaluating spawn guardrails');\n const spawnGuardrails = evaluateSpawnGuardrails(health);\n if (spawnGuardrails.blocked) {\n emitStartAgentPhase(issueId, 'guardrails', 'failure', spawnGuardrails.error ?? 'guardrails blocked', {\n status: spawnGuardrails.status,\n hint: spawnGuardrails.hint,\n });\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: spawnGuardrails.error,\n hint: spawnGuardrails.hint,\n guardrails: spawnGuardrails,\n }, { status: spawnGuardrails.status });\n }\n if (spawnGuardrails.requiresAcknowledgement && !guardrailAcknowledged) {\n emitStartAgentPhase(issueId, 'guardrails', 'skipped', 'guardrail acknowledgement required', {\n status: spawnGuardrails.status,\n hint: spawnGuardrails.hint,\n });\n return jsonResponse({\n success: false,\n blocked: false,\n skipped: true,\n requiresAcknowledgement: true,\n hint: spawnGuardrailResourcesHint(spawnGuardrails.hint),\n guardrails: spawnGuardrails,\n }, { status: spawnGuardrails.status });\n }\n emitStartAgentPhase(issueId, 'guardrails', 'success', 'spawn guardrails passed');\n\n let spawnModel: string;\n try {\n spawnModel = determineModel({\n model: (body as any).model,\n role,\n spawnKey: `${role}:${issueId}`,\n });\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n const providerAuthMode = yield* Effect.promise(() => getProviderAuthMode(spawnModel));\n if (providerAuthMode === 'subscription') {\n const codexAuth = yield* checkCodexAuthStatus();\n if (codexAuth.status === 'expired' || codexAuth.status === 'burned') {\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: `Codex authentication ${codexAuth.status}. GPT subscription agents cannot spawn with expired/burned tokens.`,\n hint: 'Click \"Re-authenticate\" in the Codex auth banner or Settings page to refresh your OpenAI subscription tokens.',\n }, { status: 429 });\n }\n }\n\n // Pre-flight provider health check — detect quota/auth/network errors\n // before spawning the agent into Claude Code's opaque retry loop.\n // validateProviderHealth returns an Effect (typed ProviderHealthError\n // channel) — wrapping it in Effect.promise handed a non-thenable to the\n // runtime and crashed the whole request (PAN-1768).\n const providerHealthCheck = yield* validateProviderHealth(spawnModel).pipe(\n Effect.match({\n onFailure: (err) => ({ _tag: 'failure' as const, err }),\n onSuccess: () => ({ _tag: 'success' as const, err: null }),\n }),\n );\n if (providerHealthCheck._tag === 'failure' && providerHealthCheck.err) {\n const err = providerHealthCheck.err;\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: err.message,\n hint: err.probeResult.kind === 'quota'\n ? 'Top up your credits on the provider dashboard, or switch this agent to a different model.'\n : err.probeResult.kind === 'auth'\n ? 'Check your API key in Settings → Providers.'\n : 'The provider may be temporarily unavailable. Try again later or switch models.',\n providerHealth: {\n provider: err.provider.name,\n model: err.model,\n kind: err.probeResult.kind,\n status: err.probeResult.status,\n },\n }, { status: 429 });\n }\n\n if (!isRemote) {\n emitStartAgentPhase(issueId, 'stackHealthGate', 'start', 'checking workspace docker stack health', { workspacePath });\n const stackHealth = yield* getWorkspaceStackHealth(issueId, { projectConfig, workspacePath });\n if (!stackHealth.healthy) {\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_blocked_stack_unhealthy', {\n issueId,\n reasons: stackHealth.reasons,\n lastObserved: stackHealth.lastObserved,\n }));\n if (!allowHost) {\n emitStartAgentPhase(issueId, 'stackHealthGate', 'failure', stackHealth.reasons.join('; '), {\n workspacePath,\n lastObserved: stackHealth.lastObserved,\n });\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'error',\n issueId: issueId.toUpperCase(),\n message: `agent-spawn-blocked-stack-unhealthy: ${issueId.toUpperCase()}`,\n details: stackHealth.reasons.join('; '),\n });\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: `Workspace docker stack for ${issueId} is not healthy: ${stackHealth.reasons.join('; ')}`,\n hint: `Run 'pan workspace rebuild ${issueId}' or use the CLI break-glass path: pan start ${issueId} --host.`,\n stackHealth,\n }, { status: 422 });\n }\n emitStartAgentPhase(issueId, 'stackHealthGate', 'skipped', 'stack unhealthy but host override confirmed', {\n workspacePath,\n reasons: stackHealth.reasons,\n });\n } else {\n emitStartAgentPhase(issueId, 'stackHealthGate', 'success', 'workspace docker stack healthy', { workspacePath });\n }\n } else {\n emitStartAgentPhase(issueId, 'stackHealthGate', 'skipped', 'remote workspace skips local stack-health gate', { workspacePath });\n }\n\n if (allowHost) {\n // PAN-1556: host-override is a spawn detail, not user-facing activity.\n console.warn(`[agents] agent-spawn-host-override: ${issueId.toUpperCase()} (dashboard-confirmed)`);\n }\n\n const migratedState = projectConfig ? yield* Effect.promise(() => isStateMigrated(projectConfig)) : false;\n if (shouldCommitLegacyWorkspaceArtifacts(migratedState) && (existsSync(workspacePanContinuePath) || existsSync(workspacePanDir))) {\n // Commit workspace orchestration artifacts before handing off to the work agent.\n // The entire block is best-effort — never let git errors abort the agent start.\n yield* Effect.gen(function* () {\n const gitRoot = workspacePath;\n if (existsSync(join(gitRoot, PAN_DIRNAME))) {\n // PAN-1819: use plain git add (never -f) and exclude workspace-state/sync-target paths.\n yield* Effect.promise(() => execAsync(`git add .pan/`, { cwd: gitRoot, encoding: 'utf-8' }));\n yield* Effect.promise(() => execAsync(\n `git reset HEAD -- .pan/kickoff.md .pan/continue.json .pan/handoff-*.md .pan/spec.vbrief.json`,\n { cwd: gitRoot, encoding: 'utf-8' },\n ));\n }\n if (existsSync(join(gitRoot, '.beads'))) {\n yield* Effect.promise(() => execAsync(`git add .beads/`, { cwd: gitRoot, encoding: 'utf-8' }));\n }\n // git diff --cached --quiet exits 1 when there ARE staged changes (normal).\n // Handle exit-1 in the Promise so it never becomes an Effect failure.\n const diffResult = yield* Effect.promise(() =>\n execAsync(`git diff --cached --quiet`, { cwd: gitRoot, encoding: 'utf-8' })\n .then(() => false)\n .catch(() => true)\n );\n if (diffResult) {\n yield* Effect.promise(() => execAsync(`git commit -m \"chore: planning artifacts for ${issueId} before agent start\"`, { cwd: gitRoot, encoding: 'utf-8' }));\n const pushChild = spawn('git', ['push'], { cwd: gitRoot, detached: true, stdio: 'ignore' });\n pushChild.unref();\n }\n }).pipe(Effect.catch(() => Effect.void));\n }\n\n // Approval transition (PAN-946): move the scope vBRIEF on main from\n // proposed/ → active/ and stamp plan.status='approved'. Idempotent: if the\n // vBRIEF is already in active/ with status approved, this is a no-op. The\n // commit only happens when projectPath is on main; otherwise the on-disk\n // move still applies and a later sync will pick it up. Failure is non-fatal\n // — agent spawn proceeds even if the lifecycle move fails.\n // transitionVBriefOnMain is Effect-returning — match on it directly (PAN-1768).\n yield* transitionVBriefOnMain(\n projectPath,\n issueId,\n 'active',\n 'approved',\n `scope: approve ${issueId.toUpperCase()} vBRIEF`,\n ).pipe(\n Effect.match({\n onSuccess: (result) => {\n if (result.moved) {\n console.log(`[start-agent] vBRIEF moved ${result.fromDir} → active for ${issueId}`);\n }\n if (result.committed) {\n console.log(`[start-agent] Committed approval transition on main for ${issueId}`);\n }\n },\n onFailure: (err) => {\n console.warn(`[start-agent] vBRIEF approval transition failed (non-fatal): ${err?.message ?? err}`);\n },\n }),\n );\n\n // Running transition (PAN-946): set workspace plan.status to 'running'.\n // This is the worktree-side state — the workspace vBRIEF resolved by\n // findPlan() is updated directly, and the planning artifacts commit below\n // will pick it up. Non-fatal: agent starts even if the write fails.\n if (existsSync(planPath)) {\n try {\n updatePlanStatus(planPath, 'running');\n console.log(`[start-agent] Set plan.status=running for ${issueId}`);\n } catch (planStatusErr: any) {\n console.warn(`[start-agent] Failed to set plan.status=running (non-fatal): ${planStatusErr?.message ?? planStatusErr}`);\n }\n }\n\n // Write start session entry to per-issue record (PAN-1919)\n try {\n const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } =\n yield* Effect.promise(() => import('../../../../lib/pan-dir/record.js'));\n const recordProject = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);\n yield* Effect.promise(() => appendSessionEntry(recordProject, issueId, {\n timestamp: new Date().toISOString(),\n reason: 'start',\n agentModel: spawnModel,\n }));\n console.log(`[start-agent] Wrote start session entry to record for ${issueId}`);\n } catch (continueErr: any) {\n console.warn(`[start-agent] Failed to write start entry to record (non-fatal): ${continueErr?.message ?? continueErr}`);\n }\n\n if (isRemote && workspaceMetadata) {\n return yield* handleRemoteAgentSpawn({\n issueId,\n workspacePath,\n workspaceMetadata,\n spawnModel,\n projectPath,\n spawnGuardrails,\n lifecycle,\n });\n }\n\n // Local workspace\n const devScript = join(workspacePath, 'dev');\n const hasPlanning = existsSync(join(workspacePath, PAN_DIRNAME));\n\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_requested', {\n issueId,\n workspacePath,\n hasPlanning,\n role,\n }));\n\n const agentLifecycle = yield* getWorkAgentLifecycleState(agentSessionName);\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_lifecycle_evaluated', {\n issueId,\n lifecycle: agentLifecycle,\n }));\n if (!agentLifecycle.canStartFresh) {\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_blocked', {\n issueId,\n reason: agentLifecycle.reason,\n lifecycle: agentLifecycle,\n }));\n return jsonResponse({\n error: agentLifecycle.reason || `Cannot start agent for ${issueId}`,\n lifecycle: agentLifecycle,\n }, { status: 409 });\n }\n\n yield* Effect.gen(function* () {\n const exists = yield* sessionExists(agentSessionName);\n if (exists) yield* killSession(agentSessionName);\n console.log(`[start-agent] Killed stale tmux session ${agentSessionName}`);\n }).pipe(Effect.catch(() => Effect.void));\n\n // PAN-1531: dirty-worktree refusal replaces silent pre-spawn stashing.\n // If the workspace has uncommitted changes the route returns 409 with the\n // diff so the dashboard can present the user three explicit choices:\n // Commit / Discard (typed confirmation required) / Stash as salvageable.\n // Clients that have already resolved the dirtiness MUST pass\n // `acknowledgeDirtyWorkspace: true` to bypass this gate (typically after\n // the user clicked one of the three modal buttons).\n const acknowledgeDirtyWorkspace = (body as any).acknowledgeDirtyWorkspace === true;\n if (!acknowledgeDirtyWorkspace) {\n try {\n const { stdout: statusOut } = yield* Effect.promise(() => execAsync('git status --porcelain', {\n cwd: workspacePath,\n encoding: 'utf-8',\n }));\n if (statusOut.trim()) {\n const { stdout: diffOut } = yield* Effect.promise(() => execAsync('git diff HEAD --stat', {\n cwd: workspacePath,\n encoding: 'utf-8',\n }).catch(() => ({ stdout: '' })));\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_refused_dirty_workspace', {\n issueId,\n workspacePath,\n porcelain: statusOut.trim(),\n }));\n // PAN-2386: surface the refusal in the dashboard activity feed so the operator\n // sees a toast instead of having to dig through lifecycle.log.\n emitDirtyWorkspaceRefusalActivity(issueId, statusOut.trim());\n return jsonResponse({\n error: `Workspace ${workspacePath} has uncommitted changes. Choose an action and retry start with acknowledgeDirtyWorkspace=true.`,\n code: 'WORKSPACE_DIRTY',\n workspacePath,\n porcelain: statusOut.trim(),\n diffStat: diffOut.trim(),\n actions: ['commit', 'discard', 'stash-salvage'],\n }, { status: 409 });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[start-agent] Failed to check workspace status for ${issueId}: ${message}`);\n }\n }\n\n // PAN-1048 review feedback 003: the route only resolves harness when the\n // dashboard launch panel explicitly chose one. Otherwise pass nothing and\n // let pan start → spawnAgent resolve from roles.work.harness (the new\n // single source of truth for per-role harness). The legacy `phase`\n // variable and the workType/harnessOverrides map are gone — the\n // legacy-field guard above (line 1872) blocks any client still sending\n // them. Note: when bodyHarness is set we still run it through\n // canUseHarness() so we can fail fast on a model+harness incompatibility\n // before spawning the subprocess.\n const bodyHarness = (body as any).harness;\n const userPickedHarness: 'claude-code' | 'ohmypi' | 'codex' | null =\n bodyHarness === 'ohmypi' || bodyHarness === 'claude-code' || bodyHarness === 'codex' ? bodyHarness : null;\n let effectiveHarness: 'claude-code' | 'ohmypi' | 'codex' | null = null;\n if (userPickedHarness !== null) {\n const harnessDecision = yield* Effect.promise(async () =>\n canUseHarnessSync(userPickedHarness, spawnModel, await getProviderAuthMode(spawnModel))\n );\n effectiveHarness = harnessDecision.allowed ? userPickedHarness : 'claude-code';\n }\n\n // Spawn pan start command\n const spawnPanCommand = async (args: string[], cwd?: string): Promise<string> => spawnPanCommandDetached({\n agentSessionName,\n issueId,\n role,\n workspacePath,\n args,\n cwd,\n });\n\n // Use IssueLifecycle service to transition issue to \"In Progress\" (PAN-449)\n const updateIssueStatus = async () => {\n await Effect.runPromise(\n lifecycle.transitionTo(issueId, 'in_progress').pipe(Effect.catch(() => Effect.void))\n );\n };\n\n const containerResponse = yield* handleContainerOrchestration({\n issueId,\n workspacePath,\n devScript,\n agentSessionName,\n role,\n effectiveHarness,\n allowHost,\n spawnModel,\n spawnGuardrails,\n projectPath,\n eventStore,\n spawnPanCommand,\n updateIssueStatus,\n });\n if (containerResponse) return containerResponse;\n\n // Containers already ready or no containers needed\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.work_spawn_requested', {\n issueId,\n role,\n workspacePath,\n }));\n let activityId: string;\n try {\n emitStartAgentPhase(issueId, 'spawn', 'start', 'starting local work agent', { workspacePath });\n activityId = yield* Effect.promise(() => spawnPanCommand(\n buildPanStartArgs({\n issueId,\n model: spawnModel,\n harness: effectiveHarness,\n allowHost,\n }),\n workspacePath,\n ));\n emitStartAgentPhase(issueId, 'spawn', 'success', 'local work agent spawn requested', {\n workspacePath,\n activityId,\n });\n } catch (error: any) {\n const output = String(error?.output ?? error?.message ?? '');\n if (output.includes(`Workspace docker stack for ${issueId}`) && output.includes('is not healthy')) {\n const failedStackHealth = yield* getWorkspaceStackHealth(issueId, { projectConfig, workspacePath });\n emitStartAgentPhase(issueId, 'stackHealthGate', 'failure', failedStackHealth.reasons.length > 0 ? failedStackHealth.reasons.join('; ') : output.trim(), {\n workspacePath,\n activityId: error?.activityId,\n });\n emitStartAgentPhase(issueId, 'spawn', 'failure', output.trim() || `Failed to start agent for ${issueId}`, {\n workspacePath,\n activityId: error?.activityId,\n });\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'error',\n issueId: issueId.toUpperCase(),\n message: `agent-spawn-blocked-stack-unhealthy: ${issueId.toUpperCase()}`,\n details: failedStackHealth.reasons.length > 0 ? failedStackHealth.reasons.join('; ') : output.trim(),\n });\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: failedStackHealth.reasons.length > 0\n ? `Workspace docker stack for ${issueId} is not healthy: ${failedStackHealth.reasons.join('; ')}`\n : output.trim(),\n hint: `Run 'pan workspace rebuild ${issueId}' or use the CLI break-glass path: pan start ${issueId} --host.`,\n stackHealth: failedStackHealth,\n activityId: error?.activityId,\n }, { status: 422 });\n }\n emitStartAgentPhase(issueId, 'spawn', 'failure', output.trim() || `Failed to start agent for ${issueId}`, {\n workspacePath,\n activityId: error?.activityId,\n });\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: output.trim() || `Failed to start agent for ${issueId}`,\n activityId: error?.activityId,\n }, { status: 500 });\n }\n\n // Write early state.json so the dashboard immediately shows agent-<id> as the\n // active agent. Without this there's a race window between spawnPanCommand returning\n // and pan start calling saveAgentState(), during which the workspace detail\n // panel shows the stale planning-<id> session and \"No saved output available.\"\n const earlyAgentId = agentSessionName; // e.g. \"agent-pan-488\"\n const earlyStateDir = join(homedir(), '.overdeck', 'agents', earlyAgentId);\n yield* Effect.promise(() => mkdir(earlyStateDir, { recursive: true }));\n // PAN-1048 R2: legacy `runtime` removed; PAN-1055: persist user-picked harness.\n saveAgentStateSync({\n id: earlyAgentId,\n issueId,\n ...(effectiveHarness ? { harness: effectiveHarness } : {}),\n model: 'pending-work-spawn',\n status: 'starting',\n startedAt: new Date().toISOString(),\n workspace: workspacePath,\n role,\n hostOverride: allowHost || undefined,\n });\n updateRegistryForAgentStart(issueId, workspacePath, earlyAgentId);\n yield* Effect.promise(() => appendAgentLifecycleLog(earlyAgentId, 'agent.start_placeholder_created', {\n issueId,\n role,\n workspacePath,\n activityId,\n }));\n\n yield* Effect.promise(() => updateIssueStatus());\n\n // PAN-1048: lifecycle.transitionTo() inside updateIssueStatus() is the\n // single source of issue.transitioned. The duplicate issue.statusChanged\n // emit raced with reactive Cloister: the early state.json above (with\n // role: 'work', status: 'starting') is already on disk, so any code path\n // that wants to know about the in-flight spawn can read it. Removing the\n // redundant emit collapses two-source-of-truth into one.\n //\n // PAN-1048 review feedback 003: emit a contract-compliant agent.started\n // payload (agentId = session name, agent = AgentSnapshot) so the read-model\n // reducer writes a real snapshot into agentsById instead of `undefined`.\n // Mirrors the early state.json shape we just wrote at line 2693.\n //\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram({\n id: earlyAgentId,\n issueId,\n workspace: workspacePath,\n role,\n ...(effectiveHarness ? { harness: effectiveHarness } : {}),\n model: 'pending-work-spawn',\n status: 'starting',\n startedAt: new Date().toISOString(),\n hostOverride: allowHost || undefined,\n }, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: earlyAgentId,\n issueId,\n agent: {\n id: earlyAgentId,\n issueId,\n workspace: workspacePath,\n status: 'starting',\n startedAt: new Date().toISOString(),\n role,\n ...(effectiveHarness ? { runtime: effectiveHarness } : {}),\n },\n },\n });\n try { getIssueDataService().patchIssue(issueId, { status: 'In Progress', canonicalStatus: 'in_progress' }); } catch { /* non-fatal */ }\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n message: `Starting agent for ${issueId}`,\n activityId,\n projectPath,\n guardrails: spawnGuardrails,\n });\n })),\n);\n","/**\n * Agents route barrel — Effect HttpRouter.Layer (PAN-428 B7)\n *\n * Composes all /api/agents/* route layers and preserves the public helper\n * exports that external callers historically imported from routes/agents.js.\n */\n\nimport { Layer } from 'effect';\n\nimport {\n getAgentsRoute,\n getAgentGitInfoRoute,\n getAgentTmuxAliveRoute,\n getAgentHasSessionRoute,\n} from './agents/listing.js';\nimport {\n getAgentOutputRoute,\n getAgentConversationRoute,\n getAgentActivityRoute,\n getAgentFilesRoute,\n getAgentTimelineRoute,\n} from './agents/conversation.js';\nimport {\n postAgentMessageRoute,\n postAgentTellRoute,\n postAgentPokeRoute,\n} from './agents/messaging.js';\nimport {\n getAgentPendingQuestionsRoute,\n postAgentAnswerQuestionRoute,\n postInternalAgentPermissionRequestRoute,\n postAgentPermissionResponseRoute,\n} from './agents/permissions.js';\nimport {\n getAgentHealthHistoryRoute,\n postAgentHeartbeatRoute,\n postAgentWorkCompleteRoute,\n postAgentStuckRoute,\n postAgentClassifyCompletionRoute,\n getAgentRuntimeRoute,\n} from './agents/runtime-events.js';\nimport {\n deleteAgentRoute,\n postAgentStopRoute,\n postAgentSuspendRoute,\n postAgentPauseRoute,\n postAgentUnpauseRoute,\n postAgentUntroubledRoute,\n} from './agents/lifecycle-stop.js';\nimport {\n postAgentResumeRoute,\n postAgentRecoverRoute,\n postAgentRestartRoute,\n postAgentRestartFreshRoute,\n postAgentsRestartAllRoute,\n postAgentResetSessionRoute,\n getAgentsRestartConfigRoute,\n postAgentsRestartWithConfigRoute,\n} from './agents/lifecycle-restart.js';\nimport {\n getAgentCloisterHealthRoute,\n getAgentHandoffSuggestionRoute,\n postAgentHandoffRoute,\n getAgentCostRoute,\n postAgentDeliveryMethodRoute,\n postAgentSwitchModelRoute,\n} from './agents/control.js';\nimport { postAgentsRoute } from './agents/spawn.js';\n\nexport {\n buildPanStartArgs,\n spawnPanCommandDetached,\n validateAgentRuntimeEventAuth,\n invalidateAgentsCache,\n evaluateAgentStartGate,\n hasActiveAgentGateOrRetry,\n evaluateSpawnGuardrails,\n} from './agents/shared.js';\n\nexport type {\n SpawnGuardrailDecision,\n AgentStartGateDecision,\n} from './agents/shared.js';\n\nexport {\n agentHasResolvableWorkspace,\n UNRESOLVABLE_AGENT_GIT_INFO,\n} from './agents/listing.js';\nexport { buildConversationResponse } from './agents/conversation.js';\nexport { validateAgentMessageOrigin } from './agents/messaging.js';\nexport { createAgentStopHandler } from './agents/lifecycle-stop.js';\nexport { validateAgentDeliveryMethodOrigin } from './agents/control.js';\n\nexport const agentsRouteLayer = Layer.mergeAll(\n getAgentsRoute,\n getAgentOutputRoute,\n getAgentConversationRoute,\n postAgentMessageRoute,\n postAgentTellRoute,\n deleteAgentRoute,\n postAgentStopRoute,\n getAgentHealthHistoryRoute,\n postAgentPokeRoute,\n getAgentPendingQuestionsRoute,\n postAgentAnswerQuestionRoute,\n postAgentHeartbeatRoute,\n postAgentWorkCompleteRoute,\n postAgentStuckRoute,\n postAgentClassifyCompletionRoute,\n postInternalAgentPermissionRequestRoute,\n postAgentPermissionResponseRoute,\n getAgentRuntimeRoute,\n getAgentGitInfoRoute,\n getAgentActivityRoute,\n getAgentFilesRoute,\n getAgentTimelineRoute,\n postAgentSuspendRoute,\n postAgentPauseRoute,\n postAgentUnpauseRoute,\n postAgentUntroubledRoute,\n postAgentResumeRoute,\n postAgentRecoverRoute,\n postAgentRestartRoute,\n getAgentCloisterHealthRoute,\n getAgentHandoffSuggestionRoute,\n postAgentHandoffRoute,\n getAgentCostRoute,\n postAgentsRoute,\n postAgentsRestartAllRoute,\n getAgentTmuxAliveRoute,\n getAgentHasSessionRoute,\n postAgentResetSessionRoute,\n postAgentSwitchModelRoute,\n postAgentRestartFreshRoute,\n postAgentDeliveryMethodRoute,\n getAgentsRestartConfigRoute,\n postAgentsRestartWithConfigRoute,\n);\n\nexport default agentsRouteLayer;\n","/**\n * DAG-aware task readiness module for Cloister scheduling (PAN-388 Phase 4)\n *\n * Provides:\n * - isTaskReady(): checks if a vBRIEF item's hard dependencies are all done\n * - getUnblockedItems(): returns items newly unblocked after a task completes\n *\n * Gracefully degrades when no plan.vbrief.json exists — all tasks are ready.\n */\n\nimport { Effect } from 'effect';\nimport { readWorkspacePlanSync, readWorkspacePlan, type VBriefReadError } from '../vbrief/io.js';\nimport type { VBriefItemStatus } from '../vbrief/types.js';\n\nconst TERMINAL_STATUSES: VBriefItemStatus[] = ['completed', 'cancelled'];\n\n/**\n * Returns true if the given vBRIEF item can be worked on — i.e., all items\n * that block it have reached a terminal status (completed or cancelled).\n *\n * When no plan exists for the workspace, returns true (no-op for legacy flows).\n */\nexport function isTaskReadySync(itemId: string, workspacePath: string): boolean {\n const doc = readWorkspacePlanSync(workspacePath);\n if (!doc) return true; // No plan → all tasks ready\n\n // If item doesn't exist in this plan, don't block it (e.g., legacy bead not in vBRIEF)\n const itemExists = doc.plan.items.some(i => i.id === itemId);\n if (!itemExists) return true;\n\n // Find all items that block this task (type: 'blocks', edge.to === itemId)\n const blockerIds = doc.plan.edges\n .filter(e => e.type === 'blocks' && e.to === itemId)\n .map(e => e.from);\n\n if (blockerIds.length === 0) return true; // No blockers\n\n const itemById = new Map(doc.plan.items.map(i => [i.id, i]));\n\n return blockerIds.every(blockerId => {\n const blocker = itemById.get(blockerId);\n if (!blocker) return true; // Unknown blocker — don't block\n return TERMINAL_STATUSES.includes(blocker.status);\n });\n}\n\n/**\n * Returns the list of item IDs that are newly unblocked after `justCompletedId`\n * reaches a terminal status.\n *\n * \"Newly unblocked\" means: the item was not ready before (had at least one\n * non-terminal blocker) and is now ready after this completion.\n *\n * Returns [] when no plan exists.\n */\nexport function getUnblockedItemsSync(workspacePath: string, justCompletedId: string): string[] {\n const doc = readWorkspacePlanSync(workspacePath);\n if (!doc) return [];\n\n const itemById = new Map(doc.plan.items.map(i => [i.id, i]));\n\n // Find items that this completed item directly blocks\n const directlyUnblocking = doc.plan.edges\n .filter(e => e.type === 'blocks' && e.from === justCompletedId)\n .map(e => e.to);\n\n const unblocked: string[] = [];\n\n for (const candidateId of directlyUnblocking) {\n const candidate = itemById.get(candidateId);\n if (!candidate) continue;\n if (TERMINAL_STATUSES.includes(candidate.status)) continue; // Already done\n\n // Check all blockers of this candidate (excluding the just-completed one)\n const blockers = doc.plan.edges\n .filter(e => e.type === 'blocks' && e.to === candidateId)\n .map(e => e.from);\n\n const allBlockersTerminal = blockers.every(blockerId => {\n const blocker = itemById.get(blockerId);\n if (!blocker) return true;\n // The just-completed item counts as terminal even if status not yet written\n if (blockerId === justCompletedId) return true;\n return TERMINAL_STATUSES.includes(blocker.status);\n });\n\n if (allBlockersTerminal) {\n unblocked.push(candidateId);\n }\n }\n\n return unblocked;\n}\n\n// ─── Effect variants (PAN-1249) ──────────────────────────────────────────────\n\n/**\n * Effect variant of {@link isTaskReadySync}. Reads the workspace plan asynchronously\n * via the typed Effect API. Returns `true` when the plan is missing or the item\n * is unknown (gracefully degrades for legacy flows).\n */\nexport const isTaskReady = (\n itemId: string,\n workspacePath: string,\n): Effect.Effect<boolean, VBriefReadError> =>\n Effect.gen(function* () {\n const doc = yield* readWorkspacePlan(workspacePath);\n if (!doc) return true;\n\n const itemExists = doc.plan.items.some((i) => i.id === itemId);\n if (!itemExists) return true;\n\n const blockerIds = doc.plan.edges\n .filter((e) => e.type === 'blocks' && e.to === itemId)\n .map((e) => e.from);\n\n if (blockerIds.length === 0) return true;\n\n const itemById = new Map(doc.plan.items.map((i) => [i.id, i]));\n return blockerIds.every((blockerId) => {\n const blocker = itemById.get(blockerId);\n if (!blocker) return true;\n return TERMINAL_STATUSES.includes(blocker.status);\n });\n });\n\n/**\n * Effect variant of {@link getUnblockedItemsSync}. Returns the list of item IDs\n * that are newly unblocked after `justCompletedId` reaches a terminal status.\n */\nexport const getUnblockedItems = (\n workspacePath: string,\n justCompletedId: string,\n): Effect.Effect<string[], VBriefReadError> =>\n Effect.gen(function* () {\n const doc = yield* readWorkspacePlan(workspacePath);\n if (!doc) return [];\n\n const itemById = new Map(doc.plan.items.map((i) => [i.id, i]));\n const directlyUnblocking = doc.plan.edges\n .filter((e) => e.type === 'blocks' && e.from === justCompletedId)\n .map((e) => e.to);\n\n const unblocked: string[] = [];\n for (const candidateId of directlyUnblocking) {\n const candidate = itemById.get(candidateId);\n if (!candidate) continue;\n if (TERMINAL_STATUSES.includes(candidate.status)) continue;\n\n const blockers = doc.plan.edges\n .filter((e) => e.type === 'blocks' && e.to === candidateId)\n .map((e) => e.from);\n\n const allBlockersTerminal = blockers.every((blockerId) => {\n const blocker = itemById.get(blockerId);\n if (!blocker) return true;\n if (blockerId === justCompletedId) return true;\n return TERMINAL_STATUSES.includes(blocker.status);\n });\n\n if (allBlockersTerminal) unblocked.push(candidateId);\n }\n\n return unblocked;\n });\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { decideEscalation, type EscalationAction } from '../../../lib/agents/tier-escalation.js';\nimport { resolveTieredExecutionEnabled } from '../../../lib/agents/tier-table.js';\nimport { loadConfigSync } from '../../../lib/config-yaml.js';\nimport { resolveProjectFromIssueSync } from '../../../lib/projects.js';\nimport { getReviewStatusSync } from '../../../lib/review-status.js';\nimport {\n readTierOverrides,\n readWorkspacePlanSync,\n recordTierPromotion,\n} from '../../../lib/vbrief/io.js';\n\nexport interface TieredInspectFailureEscalationDeps {\n loadConfig?: typeof loadConfigSync;\n resolveProject?: typeof resolveProjectFromIssueSync;\n exists?: typeof existsSync;\n readPlan?: typeof readWorkspacePlanSync;\n readOverrides?: typeof readTierOverrides;\n decide?: typeof decideEscalation;\n recordPromotion?: typeof recordTierPromotion;\n}\n\nexport function handleTieredInspectFailureEscalation(\n issueId: string,\n notes: string | undefined,\n deps: TieredInspectFailureEscalationDeps = {},\n): EscalationAction | null {\n const loadConfig = deps.loadConfig ?? loadConfigSync;\n const resolveProject = deps.resolveProject ?? resolveProjectFromIssueSync;\n const exists = deps.exists ?? existsSync;\n const readPlan = deps.readPlan ?? readWorkspacePlanSync;\n const readOverrides = deps.readOverrides ?? readTierOverrides;\n const decide = deps.decide ?? decideEscalation;\n const recordPromotion = deps.recordPromotion ?? recordTierPromotion;\n\n const tiered = loadConfig().config.tieredExecution;\n if (!tiered.escalation.enabled) return null;\n\n const project = resolveProject(issueId);\n if (!project) return null;\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`,\n );\n if (!exists(workspacePath)) return null;\n\n const doc = readPlan(workspacePath);\n if (!resolveTieredExecutionEnabled(tiered, doc?.plan.metadata)) return null;\n\n const beadId = notes?.match(/[Bb]ead\\s+(\\S+)/)?.[1];\n if (!doc || !beadId) return null;\n const item = doc.plan.items.find(candidate => candidate.id === beadId);\n if (!item) return null;\n\n const overrides = readOverrides(workspacePath);\n const decision = decide({\n kind: 'supervisor-blocked',\n beadId,\n sha: getReviewStatusSync(issueId)?.reviewedAtCommit ?? 'unknown',\n attemptsAtCurrentTier: tiered.escalation.retries_at_tier,\n }, item, tiered.escalation, overrides);\n\n if (decision.action === 'promote') {\n recordPromotion(workspacePath, beadId, decision.from, decision.to, decision.reason);\n }\n\n return decision;\n}\n\nexport async function reportTieredInspectFailureEscalation(\n issueId: string,\n notes: string | undefined,\n deps: TieredInspectFailureEscalationDeps = {},\n): Promise<void> {\n try {\n const decision = handleTieredInspectFailureEscalation(issueId, notes, deps);\n if (decision) {\n console.log(`[specialists/done] Tier escalation decision for ${issueId}: ${decision.action}`);\n }\n } catch (err) {\n console.warn(`[specialists/done] Tier escalation handling failed for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n","import { exec, execFile } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect } from 'effect';\nimport { HttpServerRequest } from 'effect/unstable/http';\n\nimport {\n type AgentRuntimeState,\n type AgentState,\n} from '../../../../lib/agents.js';\nimport { createInFlightGuard } from '../../../../lib/cloister/in-flight-guard.js';\nimport { extractPrefixSync } from '../../../../lib/issue-id.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\n\nexport const execAsync = promisify(exec);\nexport const execFileAsync = promisify(execFile);\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nexport type SpecialistAgentName = 'merge-agent' | 'review-agent' | 'test-agent' | 'inspect-agent' | 'uat-agent';\nexport type ProjectSpecialistAgentName = 'review-agent' | 'test-agent' | 'merge-agent';\nexport type SpecialistEventRole = 'review' | 'test' | 'ship';\n\nexport function validateSpecialistAgentName(type: string): type is ProjectSpecialistAgentName {\n return type === 'review-agent' || type === 'test-agent' || type === 'merge-agent';\n}\n\nexport function specialistEventRole(name: string): SpecialistEventRole | undefined {\n if (name === 'review' || name === 'review-agent') return 'review';\n if (name === 'test' || name === 'test-agent') return 'test';\n if (name === 'merge' || name === 'merge-agent') return 'ship';\n return undefined;\n}\n\nexport function specialistNameForRole(role: string | undefined): ProjectSpecialistAgentName | null {\n const baseRole = role?.split('.')[0];\n if (baseRole === 'review') return 'review-agent';\n if (baseRole === 'test') return 'test-agent';\n if (baseRole === 'ship') return 'merge-agent';\n return null;\n}\n\nexport type SpecialistAutoCompleteBody = {\n agentId?: string;\n issueId?: string;\n role?: string;\n sessionId?: string | null;\n status?: string;\n notes?: string;\n};\n\nexport function validateSpecialistAutoCompleteMetadata(\n name: string,\n body: SpecialistAutoCompleteBody,\n agentState: AgentState | null,\n runtimeState: AgentRuntimeState | null,\n): { ok: true } | { ok: false; status: number; error: string } {\n if (!validateSpecialistAgentName(name)) {\n return { ok: false, status: 400, error: `Invalid specialist name: ${name}` };\n }\n if (!body.issueId || !body.status) {\n return { ok: false, status: 400, error: 'issueId and status required' };\n }\n if (body.status !== 'passed' && body.status !== 'failed') {\n return { ok: false, status: 400, error: 'status must be passed or failed' };\n }\n if (!body.agentId || !body.role) {\n return { ok: false, status: 400, error: 'agentId and role required' };\n }\n\n const normalizedIssueId = body.issueId.toUpperCase();\n const expectedName = specialistNameForRole(body.role);\n if (expectedName !== name) {\n return { ok: false, status: 403, error: 'role does not match specialist' };\n }\n if (!agentState || agentState.id !== body.agentId) {\n return { ok: false, status: 403, error: 'agent run not found' };\n }\n if (agentState.issueId.toUpperCase() !== normalizedIssueId) {\n return { ok: false, status: 403, error: 'agent issue does not match request' };\n }\n if (specialistNameForRole(agentState.role) !== name) {\n return { ok: false, status: 403, error: 'agent role does not match specialist' };\n }\n if (agentState.status !== 'running' && agentState.status !== 'starting') {\n return { ok: false, status: 409, error: 'agent run is not active' };\n }\n if (runtimeState?.currentIssue && runtimeState.currentIssue.toUpperCase() !== normalizedIssueId) {\n return { ok: false, status: 403, error: 'runtime issue does not match request' };\n }\n if (runtimeState?.claudeSessionId && body.sessionId !== runtimeState.claudeSessionId) {\n return { ok: false, status: 403, error: 'session does not match active run' };\n }\n\n return { ok: true };\n}\n\n// Read the request body as unknown JSON\nexport const readJsonBody = Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const text = yield* request.text;\n try {\n return text ? JSON.parse(text) : {};\n } catch {\n return {};\n }\n});\n\n// ─── Idempotency guard: prevent concurrent postMergeLifecycle re-entry ────────\n// PAN-328: the loop specialists/done → onMergeComplete → postMergeLifecycle →\n// (re-trigger) → specialists/done once burned 24,626 tracker API calls. The\n// in-flight guard makes a second *concurrent* call for the same issue a no-op.\n// The invariant is enforced by tests/unit/lib/cloister/in-flight-guard.test.ts\n// — delete the guard and that suite goes red.\nconst postMergeGuard = createInFlightGuard();\n\n// Track issues where the server is managing the merge lifecycle (polyrepo).\n// Exported so the workspaces route can register/unregister server-managed merges.\nexport const _serverManagedMerges = new Set<string>();\n\n/**\n * Exported for the UAT batch-promote route (PAN-1737): batch promotion fans\n * out the per-member post-merge through THIS guard instance so an issue's\n * lifecycle still runs at most once regardless of which path merged it.\n * Returns false when a run for the issue is already in flight.\n */\nexport interface FirePostMergeLifecycleOptions {\n sourceBranch?: string;\n verifiedMergedRef?: string;\n}\n\nexport function firePostMergeLifecycle(issueId: string, options?: FirePostMergeLifecycleOptions): boolean {\n const started = postMergeGuard.run(\n issueId,\n async () => {\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPathForIssue(issuePrefix);\n const { postMergeLifecycle } = await import('../../../../lib/cloister/merge-agent.js');\n await postMergeLifecycle(\n issueId,\n projectPath,\n options?.sourceBranch,\n options?.verifiedMergedRef ? { verifiedMergedRef: options.verifiedMergedRef } : undefined,\n );\n console.log(`[merge] post-merge lifecycle completed for ${issueId}`);\n\n // PAN-1691: roll the merge train — rebase ready siblings onto the new main,\n // re-verify the clean ones, agent-resolve conflicts. No-op unless the\n // flywheel.merge_train_enabled flag is on. Runs inside the in-flight guard,\n // so it cannot re-enter postMergeLifecycle for this issue.\n const { runMergeTrainReconcile } = await import('../../../../lib/cloister/merge-train.js');\n const outcomes = await runMergeTrainReconcile(issueId);\n if (outcomes.length > 0) {\n console.log(\n `[merge-train] reconciled ${outcomes.length} sibling(s) after ${issueId}: ` +\n outcomes.map((o) => `${o.issueId}=${o.result}`).join(', '),\n );\n }\n },\n (err) => console.error(`[merge] post-merge lifecycle failed for ${issueId}:`, err),\n );\n if (!started) {\n console.log(`[merge] firePostMergeLifecycle: skipping ${issueId} — already in flight`);\n }\n return started;\n}\n\nexport function getProjectPathForIssue(issuePrefix: string): string {\n const issueId = `${issuePrefix}-1`;\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) return resolved.projectPath;\n return join(homedir(), 'Projects');\n}\n","import { existsSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { getAgentState, getAgentRuntimeState, messageAgent, saveAgentRuntimeState, transitionIssueToInProgress } from '../../../../lib/agents.js';\nimport { queryBeadById } from '../../../../lib/beads-query.js';\nimport { getUnblockedItemsSync } from '../../../../lib/cloister/task-readiness.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { getReviewStatusSync, loadReviewStatuses, setReviewStatusSync as setReviewStatusBase, type ReviewStatus } from '../../../../lib/review-status.js';\nimport { syncBeadStatusToVBrief } from '../../../../lib/vbrief/beads.js';\nimport { readWorkspacePlanSync } from '../../../../lib/vbrief/io.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { validateAgentRuntimeEventAuth } from '../agents.js';\nimport { httpHandler } from '../http-handler.js';\nimport { reportTieredInspectFailureEscalation } from '../tiered-inspect-escalation.js';\nimport { killSession } from '../../../../lib/tmux.js';\nimport {\n _serverManagedMerges,\n execFileAsync,\n firePostMergeLifecycle,\n readJsonBody,\n specialistEventRole,\n validateSpecialistAutoCompleteMetadata,\n type SpecialistAgentName,\n type SpecialistAutoCompleteBody,\n} from './shared.js';\n\n// ─── Route: GET /api/specialists ─────────────────────────────────────────────\n\nconst getSpecialistsRoute = HttpRouter.add(\n 'GET',\n '/api/specialists',\n httpHandler(Effect.gen(function* () {\n const {\n getAllSpecialistStatus,\n getAllProjectSpecialistStatuses,\n } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n\n const legacySpecialists = yield* Effect.promise(() => getAllSpecialistStatus());\n const projectSpecialists = yield* Effect.promise(() => getAllProjectSpecialistStatuses());\n\n return jsonResponse({\n specialists: legacySpecialists,\n projects: projectSpecialists,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/reset-all ───────────────────────────────────\n// NOTE: Must be registered before /:name/reset to avoid \"reset-all\" matching as :name\n\nconst postSpecialistsResetAllRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/reset-all',\n httpHandler(Effect.gen(function* () {\n const {\n getAllSpecialists,\n isRunning,\n getTmuxSessionName,\n } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const { clearHookSync } = yield* Effect.promise(() => import('../../../../lib/hooks.js'));\n\n const specialists = getAllSpecialists();\n const results: { name: string; killed: boolean; sessionCleared: boolean; queueCleared: boolean }[] = [];\n\n for (const specialist of specialists) {\n const name = specialist.name;\n let killed = false;\n\n if (yield* Effect.promise(() => isRunning(name))) {\n const tmuxSession = getTmuxSessionName(name);\n const killResult = yield* killSession(tmuxSession).pipe(\n Effect.as(true),\n Effect.catch(() => Effect.succeed(false)),\n );\n killed = killResult;\n }\n\n clearHookSync(name);\n results.push({ name, killed, sessionCleared: false, queueCleared: true });\n }\n\n // Reset any \"reviewing\" statuses to \"pending\" — use per-issue atomic updates\n // to avoid the read-all/write-all race that saveReviewStatuses() would reintroduce.\n let reviewStatusesReset = 0;\n try {\n const statuses = loadReviewStatuses();\n for (const key of Object.keys(statuses)) {\n if (statuses[key].reviewStatus === 'reviewing') {\n setReviewStatusBase(key, { reviewStatus: 'pending' });\n reviewStatusesReset++;\n }\n }\n } catch (e) {\n console.error('Failed to reset review statuses:', e);\n }\n\n return jsonResponse({\n success: true,\n message: `Reset ${results.length} specialists, reset ${reviewStatusesReset} review statuses`,\n results,\n reviewStatusesReset,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/done ───────────────────────────────────────\n// CRITICAL: This endpoint has idempotency guards — see CLAUDE.md.\n// Must be registered before /:name/* routes to prevent \"done\" matching as :name.\n\nconst postSpecialistsDoneRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/done',\n httpHandler(Effect.gen(function* () {\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const { specialist, issueId, status, notes } = body as {\n specialist: string;\n issueId: string;\n status: string;\n notes?: string;\n };\n\n // Validate specialist type\n const validSpecialists = ['review', 'test', 'merge', 'inspect', 'uat', 'ship'];\n if (!validSpecialists.includes(specialist)) {\n return jsonResponse(\n { error: `Invalid specialist: ${specialist}. Valid: ${validSpecialists.join(', ')}` },\n { status: 400 },\n );\n }\n\n // Validate status\n if (!status || !['passed', 'failed'].includes(status)) {\n return jsonResponse(\n { error: `Invalid status: ${status}. Must be 'passed' or 'failed'` },\n { status: 400 },\n );\n }\n\n // Validate issueId\n if (!issueId) {\n return jsonResponse({ error: 'issueId is required' }, { status: 400 });\n }\n\n const normalizedIssueId = issueId.toUpperCase();\n console.log(`[specialists/done] ${specialist} signaling ${status} for ${normalizedIssueId}`);\n\n // Resolve any pending specialist completion waiters (PAN-632: event-driven completion).\n // This replaces polling loops in spawnMergeAgentForBranches / syncMainIntoWorkspace.\n if (specialist === 'merge') {\n const { reportSpecialistCompletion } = yield* Effect.promise(() => import('../../../../lib/cloister/specialist-completion.js'));\n const resolved = yield* reportSpecialistCompletion(normalizedIssueId, {\n status: status as 'passed' | 'failed',\n notes,\n });\n if (resolved) {\n console.log(`[specialists/done] Resolved pending completion waiter for ${normalizedIssueId}`);\n }\n }\n\n // GUARD: If this issue is in a server-managed merge (polyrepo), the server handles\n // the merge lifecycle. Acknowledge the agent's call but do NOT trigger onMergeComplete.\n if (specialist === 'merge' && _serverManagedMerges.has(normalizedIssueId)) {\n console.log(`[specialists/done] ${normalizedIssueId} is server-managed merge — acknowledging without triggering lifecycle`);\n return jsonResponse({\n success: true,\n specialist,\n issueId: normalizedIssueId,\n status,\n notes,\n serverManaged: true,\n });\n }\n\n // Build the update based on specialist type\n const update: Partial<ReviewStatus> = {};\n\n switch (specialist) {\n case 'review':\n update.reviewStatus = status === 'passed' ? 'passed' : 'blocked';\n if (notes) update.reviewNotes = notes;\n break;\n\n case 'test':\n update.testStatus = status as typeof update.testStatus;\n if (notes) update.testNotes = notes;\n break;\n\n case 'merge':\n update.mergeStatus = status === 'passed' ? 'merged' : 'failed';\n break;\n\n case 'inspect':\n update.inspectStatus = status as typeof update.inspectStatus;\n if (notes) update.inspectNotes = notes;\n break;\n\n case 'uat':\n update.uatStatus = status as typeof update.uatStatus;\n if (notes) update.uatNotes = notes;\n if (status === 'passed') {\n update.readyForMerge = true;\n }\n break;\n\n case 'ship':\n if (status === 'passed') {\n update.readyForMerge = true;\n }\n break;\n }\n\n // Apply the update (triggers side effects like idle state, queue processing)\n const updatedStatus = setReviewStatusBase(normalizedIssueId, update);\n if (specialist === 'inspect' && status === 'failed') yield* Effect.promise(() => reportTieredInspectFailureEscalation(normalizedIssueId, notes));\n\n // Set specialist state to idle and clear registry write-scope.\n // CRITICAL: No `await` between the mergeStatus write above and the guard check below.\n yield* Effect.promise(async () => {\n try {\n const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } =\n await import('../../../../lib/cloister/specialists.js');\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n const projectKey = project?.projectKey;\n const tmuxSession = projectKey\n ? getTmuxSessionName(`${specialist}-agent` as SpecialistAgentName, projectKey, normalizedIssueId)\n : getTmuxSessionName(`${specialist}-agent` as SpecialistAgentName);\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n });\n console.log(`[specialists/done] Set ${tmuxSession} to idle`);\n\n // PAN-2579 (warm-by-default lifecycle): the verdict is recorded; the session\n // stays ALIVE so the next review/test cycle resumes it with context intact.\n // Warm-idle sessions no longer count against the advancing ceiling\n // (countRunningAgents excludes them) and the memory governor sheds them\n // first under HARD pressure — eviction is the governor's job, never a\n // side effect of recording a verdict. (Supersedes the PAN-846 kill and\n // the PAN-2007 keep-alive flag.)\n console.log(`[specialists/done] Warm lifecycle: verdict recorded, leaving ${tmuxSession} running (PAN-2579)`);\n\n // Clear write-scope lock so the next specialist can claim the workspace\n if (projectKey) {\n const registryKey = makeSpecialistRegistryKey(`${specialist}-agent`, normalizedIssueId);\n updateRunMetadata(projectKey, registryKey, {\n currentRun: null,\n writeScope: undefined,\n workspace: null,\n currentActivity: null,\n });\n console.log(`[specialists/done] Cleared registry lock for ${registryKey} (${projectKey})`);\n }\n\n // Update specialist handoff log so success-rate metrics reflect actual outcome\n const { updateSpecialistHandoffStatus } = await import('../../../../lib/cloister/specialist-handoff-logger.js');\n const updated = await Effect.runPromise(updateSpecialistHandoffStatus(\n normalizedIssueId,\n `${specialist}-agent`,\n status === 'passed' ? 'completed' : 'failed',\n status === 'passed' ? 'success' : 'failure',\n ));\n if (updated) {\n console.log(`[specialists/done] Updated handoff log: ${specialist}-agent ${normalizedIssueId} → ${status}`);\n }\n } catch (err) {\n console.error(`[specialists/done] Error managing specialist state:`, err);\n }\n });\n\n // When review passes, snapshot the current HEAD commit so we can detect\n // if the agent makes new commits before merge (which invalidates the review).\n if (specialist === 'review' && status === 'passed') {\n yield* Effect.promise(async () => {\n try {\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${normalizedIssueId.toLowerCase()}`,\n );\n if (existsSync(workspacePath)) {\n const { getWorkspaceGitInfo } = await import('../../../../lib/git-utils.js');\n const { HEAD } = await Effect.runPromise(getWorkspaceGitInfo(workspacePath));\n setReviewStatusBase(normalizedIssueId, { reviewedAtCommit: HEAD });\n console.log(`[specialists/done] Snapshotted reviewedAtCommit=${HEAD.substring(0, 8)} for ${normalizedIssueId}`);\n }\n }\n } catch (err) {\n console.error(`[specialists/done] Failed to snapshot reviewedAtCommit for ${normalizedIssueId}:`, err);\n }\n });\n }\n\n // When inspect specialist reports success, save checkpoint\n if (specialist === 'inspect' && status === 'passed') {\n yield* Effect.promise(async () => {\n try {\n const { onInspectComplete } = await import('../../../../lib/cloister/inspect-agent.js');\n // Extract beadId from notes (format: \"Bead <beadId> matches spec...\")\n const beadMatch = notes?.match(/[Bb]ead\\s+(\\S+)/);\n const beadId = beadMatch?.[1] || 'unknown';\n // Resolve project to get workspace path\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${normalizedIssueId.toLowerCase()}`,\n );\n if (existsSync(workspacePath)) {\n (await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, beadId, 'passed', workspacePath)));\n\n // Sync bead completion to vBRIEF plan\n try {\n const beadData = await Effect.runPromise(queryBeadById(workspacePath, beadId));\n const updatedItemId = await Effect.runPromise(syncBeadStatusToVBrief(beadId, workspacePath, 'completed', beadData?.title));\n if (updatedItemId) {\n // Check which tasks are now unblocked and wake the work agent\n try {\n const unblockedItems = getUnblockedItemsSync(workspacePath, updatedItemId);\n if (unblockedItems.length > 0) {\n console.log(\n `[auto-wake] ${normalizedIssueId}: items unblocked after \"${updatedItemId}\": ${unblockedItems.join(', ')}`,\n );\n const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;\n const doc = readWorkspacePlanSync(workspacePath);\n const unblockedTitles = unblockedItems\n .map((id) => {\n const it = doc?.plan.items.find((i) => i.id === id);\n return it ? `\"${it.title}\"` : `\"${id}\"`;\n })\n .join(', ');\n const wakeMsg = `DAG SCHEDULER: Task${unblockedItems.length > 1 ? 's' : ''} now unblocked after completing \"${updatedItemId}\": ${unblockedTitles}. Pick up the next available task.`;\n await messageAgent(workAgentId, wakeMsg).catch((err: unknown) => {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.log(\n `[auto-wake] Could not wake ${workAgentId} (may not be running): ${errMsg}`,\n );\n });\n }\n } catch (wakeErr: unknown) {\n const errMsg = wakeErr instanceof Error ? wakeErr.message : String(wakeErr);\n console.warn(`[auto-wake] Failed to check unblocked items: ${errMsg}`);\n }\n }\n } catch (syncErr: unknown) {\n const errMsg = syncErr instanceof Error ? syncErr.message : String(syncErr);\n console.warn(`[specialists/done] vBRIEF sync failed: ${errMsg}`);\n }\n }\n }\n } catch (err) {\n console.error(`[specialists/done] Error saving inspect checkpoint:`, err);\n }\n });\n }\n\n // When the test specialist reports success, persist testStatus and emit\n // test.passed so reactive Cloister records the shipping lifecycle phase.\n // PAN-1650 derives readyForMerge from review/test gate state server-side;\n // no ship role is spawned.\n if (specialist === 'test' && status === 'passed') {\n yield* Effect.promise(async () => {\n try {\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${normalizedIssueId.toLowerCase()}`,\n );\n if (existsSync(workspacePath)) {\n setReviewStatusBase(normalizedIssueId, { testStatus: 'passed' });\n const { initEventStore } = await import('../../event-store.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'test.passed',\n timestamp: new Date().toISOString(),\n payload: { issueId: normalizedIssueId },\n } as any);\n console.log(`[specialists/done] ${normalizedIssueId} emitted test.passed; shipping lifecycle recorded`);\n }\n }\n } catch (err) {\n console.error(`[specialists/done] Error emitting test.passed for ${normalizedIssueId}:`, err);\n }\n });\n }\n\n // When merge specialist reports success, run post-merge lifecycle ONCE.\n // Use firePostMergeLifecycle directly rather than onMergeComplete: onMergeComplete\n // has a guard that checks mergeStatus !== 'merged', but setReviewStatusBase above\n // already set mergeStatus='merged' — that guard would always fire and the lifecycle\n // would never run. firePostMergeLifecycle skips that guard and uses the\n // in-flight guard (postMergeGuard, concurrency) + postMergeLifecycle's\n // _completedPostMerge (defense-in-depth).\n if (specialist === 'merge' && status === 'passed') {\n firePostMergeLifecycle(normalizedIssueId);\n }\n\n // When any specialist reports failure, transition issue back to In Progress\n // (inspect failures don't change Linear status — they're mid-implementation gates).\n if (status === 'failed' && specialist !== 'inspect') {\n try {\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${normalizedIssueId.toLowerCase()}`,\n );\n const wsPath = existsSync(workspacePath) ? workspacePath : undefined;\n transitionIssueToInProgress(normalizedIssueId, wsPath).catch((err: unknown) => {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.warn(\n `[specialists/done] Could not transition ${normalizedIssueId} back to in_progress: ${errMsg}`,\n );\n });\n console.log(\n `[specialists/done] ${specialist} failed → transitioning ${normalizedIssueId} back to In Progress`,\n );\n }\n } catch (err: unknown) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.warn(`[specialists/done] Could not transition issue back to in_progress:`, errMsg);\n }\n }\n\n // When merge fails, post a comment on the GitHub PR so the failure is visible\n // outside the dashboard, and send feedback to the work agent.\n if (specialist === 'merge' && status === 'failed') {\n yield* Effect.promise(async () => {\n try {\n // Post comment on the PR\n const reviewStatus = loadReviewStatuses()[normalizedIssueId];\n const prUrl = reviewStatus?.prUrl;\n if (prUrl) {\n // Extract owner/repo#number from PR URL\n const prMatch = prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (prMatch) {\n const [, owner, repo, prNumber] = prMatch;\n const commentBody = `## Merge Failed\\n\\n${notes || 'Merge could not be completed.'}\\n\\nThe issue has been moved back to In Progress. The work agent needs to resolve conflicts and resubmit.`;\n await execFileAsync(\n 'gh',\n ['api', `repos/${owner}/${repo}/issues/${prNumber}/comments`, '--field', `body=${commentBody}`],\n { encoding: 'utf-8' },\n );\n console.log(`[specialists/done] Posted merge failure comment on ${prUrl}`);\n }\n }\n } catch (err: any) {\n console.warn(`[specialists/done] Failed to post merge failure comment: ${err.message}`);\n }\n });\n\n // If merge failed due to conflicts, send rebase instructions to the work agent.\n if (notes?.toLowerCase().includes('conflict')) {\n yield* Effect.promise(async () => {\n try {\n const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;\n const { sessionExists } = await import('../../../../lib/tmux.js');\n const { messageAgent, spawnAgent, getAgentStateSync } = await import('../../../../lib/agents.js');\n\n if (await Effect.runPromise(sessionExists(workAgentId))) {\n // Agent is running — send rebase instructions directly\n const rebaseMsg = `MERGE CONFLICT: The merge-agent could not rebase your branch onto main due to conflicts. Please fix this now:\\n\\n1. git fetch origin main\\n2. git rebase origin/main\\n3. Resolve any conflicts (git add <file> && git rebase --continue)\\n4. git push --force-with-lease\\n5. Resubmit: curl -s -X POST http://localhost:3011/api/review/${normalizedIssueId}/request -H \"Content-Type: application/json\" -d \"{}\"\\n\\nConflict details: ${notes}`;\n await messageAgent(workAgentId, rebaseMsg);\n console.log(`[specialists/done] Sent rebase instructions to ${workAgentId}`);\n } else {\n // Agent is stopped — start fresh (don't resume, sessions may be corrupted: PAN-612)\n console.log(`[specialists/done] Work agent ${workAgentId} not running — will need manual restart or next pan start dispatch`);\n }\n } catch (err: any) {\n console.warn(`[specialists/done] Failed to send rebase feedback to work agent: ${err.message}`);\n }\n });\n }\n }\n\n if (specialist === 'review' && (status === 'failed' || status === 'blocked')) {\n yield* Effect.promise(async () => {\n try {\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n const workspacePath = project\n ? join(project.projectPath, 'workspaces', `feature-${normalizedIssueId.toLowerCase()}`)\n : undefined;\n const { deliverReviewVerdictFeedback } = await import(\n '../../../../lib/cloister/review-verdict-feedback.js'\n );\n const result = await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId: normalizedIssueId,\n verdict: status === 'failed' ? 'failed' : 'blocked',\n notes,\n workspacePath,\n prUrl: updatedStatus.prUrl,\n }));\n console.log(\n `[specialists/done] Delivered review verdict feedback for ${normalizedIssueId}` +\n ` (feedback=${result.feedbackPath ?? 'none'}, synthesis=${result.synthesisPath ?? 'none'}, prComment=${result.prCommentPosted})`,\n );\n } catch (err: any) {\n console.warn(`[specialists/done] Failed to deliver review verdict feedback: ${err.message}`);\n }\n });\n }\n\n // Emit domain event for role-backed specialist completion/failure.\n const eventRole = specialistEventRole(specialist);\n if (eventRole) {\n if (status === 'passed') {\n yield* eventStore.append({\n type: 'specialist.completed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId: normalizedIssueId },\n });\n } else {\n yield* eventStore.append({\n type: 'specialist.failed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId: normalizedIssueId, error: notes || `${specialist} failed` },\n });\n }\n }\n\n return jsonResponse({\n success: true,\n specialist,\n issueId: normalizedIssueId,\n status,\n notes,\n currentStatus: updatedStatus,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/logs/cleanup-all ────────────────────────────\n// NOTE: Must be registered before /:project/:type routes.\n\nconst postSpecialistsLogsCleanupAllRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/logs/cleanup-all',\n httpHandler(Effect.gen(function* () {\n const { cleanupAllLogsSync } = yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n const results = cleanupAllLogsSync();\n\n return jsonResponse({\n success: true,\n totalDeleted: results.totalDeleted,\n byProject: results.byProject,\n message: `Cleaned up ${results.totalDeleted} old logs`,\n });\n })),\n);\n\n// ─── Route: GET /api/specialists/projects ────────────────────────────────────\n// NOTE: Must be registered before /:name routes.\n\nconst getSpecialistsProjectsRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/projects',\n httpHandler(Effect.gen(function* () {\n const { getAllProjectSpecialistStatuses } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const specialists = yield* Effect.promise(() => getAllProjectSpecialistStatuses());\n return jsonResponse(specialists);\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/wake ─────────────────────────────────\n\nconst postSpecialistWakeRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/wake',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n return jsonResponse(\n { error: `Legacy specialist wake is no longer supported for ${name}; role runs spawn agents directly.` },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/reset ─────────────────────────────────\n\nconst postSpecialistResetRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/reset',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n return jsonResponse(\n { error: `Legacy specialist session reset is no longer supported for ${name}; role agents are managed through the normal agent lifecycle.` },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/init ──────────────────────────────────\n\nconst postSpecialistInitRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/init',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n return jsonResponse(\n { error: `Legacy specialist initialization is no longer supported for ${name}; role flows spawn agents on demand.` },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/report-status ────────────────────────\n\nconst postSpecialistReportStatusRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/report-status',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const { issueId, status, notes } = body as {\n issueId?: string;\n status?: string;\n notes?: string;\n };\n\n if (!issueId || !status) {\n return jsonResponse(\n { error: 'issueId and status required' },\n { status: 400 },\n );\n }\n\n if (!['passed', 'blocked', 'failed', 'in-progress'].includes(status)) {\n return jsonResponse(\n { error: 'status must be: passed, blocked, failed, or in-progress' },\n { status: 400 },\n );\n }\n\n // Write status to specialist's state directory\n const specialistDir = join(homedir(), '.overdeck', 'specialists', name);\n yield* Effect.promise(() => mkdir(specialistDir, { recursive: true }));\n\n const statusFile = join(specialistDir, `${issueId}-status.json`);\n const statusData = {\n issueId,\n specialist: name,\n status,\n notes: notes || '',\n timestamp: new Date().toISOString(),\n };\n\n yield* Effect.promise(() => writeFile(statusFile, JSON.stringify(statusData, null, 2)));\n\n console.log(`[specialists] ${name} reported status for ${issueId}: ${status}`);\n\n // When specialist reports completion (passed/blocked/failed), set state to idle\n if (['passed', 'blocked', 'failed'].includes(status)) {\n const { getTmuxSessionName } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const tmuxSession = getTmuxSessionName(name as SpecialistAgentName);\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n });\n }\n\n // Emit domain event based on status\n const eventRole = specialistEventRole(name);\n if (eventRole && status === 'passed') {\n yield* eventStore.append({\n type: 'specialist.completed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId },\n });\n } else if (eventRole && (status === 'failed' || status === 'blocked')) {\n yield* eventStore.append({\n type: 'specialist.failed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId, error: notes || `${name} reported ${status}` },\n });\n }\n\n return jsonResponse({ success: true });\n })),\n);\n\n// ─── Route: GET /api/specialists/:name/cost ───────────────────────────────────\n\nconst getSpecialistCostRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:name/cost',\n httpHandler(Effect.gen(function* () {\n return jsonResponse({ cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, model: '' });\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/auto-complete ────────────────────────\n\nconst postSpecialistAutoCompleteRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/auto-complete',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const auth = yield* Effect.promise(() => validateAgentRuntimeEventAuth(request));\n if (!auth.ok) return auth.response;\n\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n const body = (yield* readJsonBody) as SpecialistAutoCompleteBody;\n const eventStore = yield* EventStoreService;\n const { issueId: requestIssueId, status: requestStatus, agentId } = body;\n\n const agentState = agentId ? yield* getAgentState(agentId) : null;\n const runtimeState = agentId ? yield* getAgentRuntimeState(agentId) : null;\n const metadata = validateSpecialistAutoCompleteMetadata(name, body, agentState, runtimeState);\n if (!metadata.ok) {\n return jsonResponse({ error: metadata.error }, { status: metadata.status });\n }\n\n const issueId = requestIssueId!;\n const status = requestStatus!;\n const completingAgentId = agentId!;\n\n console.log(`[specialists] Auto-detected completion for ${name}: ${issueId} -> ${status}`);\n\n yield* Effect.promise(() => saveAgentRuntimeState(completingAgentId, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n currentIssue: undefined,\n }));\n\n // Update review/test status based on specialist type\n const existingStatus = getReviewStatusSync(issueId);\n\n if (name === 'review-agent') {\n const alreadyReported =\n existingStatus?.reviewNotes &&\n !existingStatus.reviewNotes.startsWith('Auto-detected:');\n if (alreadyReported) {\n console.log(\n `[specialists] Skipping auto-detect for ${name}/${issueId}: specialist already reported (${existingStatus!.reviewStatus})`,\n );\n } else {\n setReviewStatusBase(issueId, {\n reviewStatus: status === 'passed' ? 'passed' : 'blocked',\n reviewNotes: `Auto-detected: ${status}`,\n });\n }\n\n if ((alreadyReported ? existingStatus!.reviewStatus : status === 'passed' ? 'passed' : 'blocked') === 'passed') {\n console.log(`[specialists] ${issueId} review approved; reactive Cloister will dispatch the test role`);\n }\n } else if (name === 'test-agent') {\n const alreadyReported =\n existingStatus?.testNotes && !existingStatus.testNotes.startsWith('Auto-detected:');\n if (alreadyReported) {\n console.log(\n `[specialists] Skipping auto-detect for ${name}/${issueId}: specialist already reported (${existingStatus!.testStatus})`,\n );\n } else {\n const testPassed = status === 'passed';\n setReviewStatusBase(issueId, {\n testStatus: testPassed ? 'passed' : 'failed',\n testNotes: `Auto-detected: ${status}`,\n });\n if (testPassed) {\n // Emit test.passed so reactive Cloister records the shipping\n // lifecycle phase; readyForMerge is derived server-side.\n yield* eventStore.append({\n type: 'test.passed',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n });\n console.log(`[specialists] ${issueId} emitted test.passed after auto-detected test pass`);\n }\n }\n }\n\n const eventRole = specialistEventRole(name);\n if (eventRole) {\n yield* eventStore.append({\n type: 'specialist.completed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId },\n });\n }\n\n return jsonResponse({\n success: true,\n status,\n issueId,\n });\n })),\n);\n\nexport const specialistsLegacyRouteLayer = Layer.mergeAll(\n getSpecialistsRoute,\n getSpecialistsProjectsRoute,\n postSpecialistsResetAllRoute,\n postSpecialistsDoneRoute,\n postSpecialistsLogsCleanupAllRoute,\n postSpecialistWakeRoute,\n postSpecialistResetRoute,\n postSpecialistInitRoute,\n postSpecialistReportStatusRoute,\n getSpecialistCostRoute,\n postSpecialistAutoCompleteRoute,\n);\n","import { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect, Layer, Option, Stream } from 'effect';\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from 'effect/unstable/http';\n\nimport { getClaudePermissionFlagsStringSync } from '../../../../lib/claude-permissions.js';\nimport { normalizeModelName } from '../../../../lib/cost-parsers/jsonl-parser.js';\nimport { calculateCostSync, getPricingSync, type TokenUsage } from '../../../../lib/cost.js';\nimport { loadConfigSync, resolveModel } from '../../../../lib/config-yaml.js';\nimport { encodeClaudeProjectDir } from '../../../../lib/paths.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { getReviewStatusSync } from '../../../../lib/review-status.js';\nimport { getAgentCommandSync } from '../../../../lib/settings.js';\nimport { killSession } from '../../../../lib/tmux.js';\nimport { saveAgentRuntimeState } from '../../../../lib/agents.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { execAsync, readJsonBody, validateSpecialistAgentName } from './shared.js';\n\n// ─── Route: GET /api/specialists/:project/:issueId/:type/status ───────────────\n//\n// PAN-1048 review feedback 003 (REQ-16): the legacy specialist-status route\n// returned metadata sourced from ~/.overdeck/specialists/registry.json, which\n// the role-primitive refactor explicitly retires. The startup cleanup in\n// service.ts deletes that directory on every boot; preserving the read path\n// would silently recreate it via getRunMetadata() → loadRegistry()/saveRegistry().\n//\n// The route is now a 410 Gone with a pointer to the role-aware status surface.\n// The frontend's only caller (AgentOutputPanel) only fires when\n// parseSpecialistSession(agentId) matches the legacy `specialist-…` session\n// naming, which the new role spawns no longer use, so the dead-letter response\n// is invisible to current dashboards.\n\nconst getProjectSpecialistStatusRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:issueId/:type/status',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n return jsonResponse(\n {\n error: 'specialist-status route retired',\n hint: 'Specialist identity is replaced by the role primitive. Use GET /api/agents and read the role/status fields off the AgentSnapshot for the role-scoped session (e.g. agent-pan-509-review).',\n retiredFor: {\n project: params['project'],\n issueId: params['issueId'],\n type: params['type'],\n },\n },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:issueId/:type/kill ───────────────\n\nconst postProjectSpecialistKillRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:issueId/:type/kill',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const issueId = params['issueId'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n\n const registryKey = makeSpecialistRegistryKey(type, issueId);\n const tmuxSession = getRunMetadata(project, registryKey).tmuxSession\n ?? getTmuxSessionName(type, project, issueId);\n\n yield* Effect.promise(() => Effect.runPromise(killSession(tmuxSession)).catch(() => {}));\n // Leave Claude JSONL/session artifacts intact; only reset Overdeck runtime state.\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n });\n return jsonResponse({\n success: true,\n message: `Killed ${type} (${project}/${issueId})`,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/spawn ───────────────────────\n//\n// PAN-1048 R1: removed. The legacy /spawn endpoint dispatched arbitrary\n// \"specialist types\" (review-agent, test-agent, merge-agent) by issuing a\n// generic spawnEphemeralSpecialist call. Under the role primitive, review/test\n// dispatch through lifecycle-aware role paths. Shipping is now server-side;\n// `ship` remains only as the merge-specialist identity for model routing and\n// historical activity attribution. The endpoint had no remaining in-tree caller\n// and is replaced by reactive Cloister scheduling on issue state transitions\n// plus the role spawn primitive.\n//\n// Old shape (removed):\n// POST /api/specialists/:project/:type/spawn { issueId, branch, ... }\n//\n// Replacement: drive role spawns via lifecycle.transitionTo() and the\n// reactive scheduler in src/lib/cloister/service.ts.\n\n// ─── Route: GET /api/specialists/:project/:type/runs ──────────────────────────\n\nconst getProjectSpecialistRunsRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/runs',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n\n let limit: number | undefined;\n let offset = 0;\n\n if (Option.isSome(urlOpt)) {\n const sp = urlOpt.value.searchParams;\n const limitParam = sp.get('limit');\n const offsetParam = sp.get('offset');\n if (limitParam) limit = parseInt(limitParam, 10);\n if (offsetParam) offset = parseInt(offsetParam, 10);\n }\n\n const { listRunLogsSync } = yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n const runs = listRunLogsSync(project, type, { limit, offset });\n return jsonResponse(runs);\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/runs/:runId/stream ────────────\n// NOTE: Must be registered before /:project/:type/runs/:runId to avoid route conflict.\n\nconst getProjectSpecialistRunStreamRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/runs/:runId/stream',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n const runId = params['runId'] as string;\n\n const { getRunLogPath, isRunLogActive } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n\n const logPath = getRunLogPath(project, type, runId);\n\n if (!existsSync(logPath)) {\n return jsonResponse({ error: 'Run log not found' }, { status: 404 });\n }\n\n // Build an SSE stream using Effect Stream + Node ReadableStream\n const encoder = new TextEncoder();\n\n const nodeStream = new ReadableStream({\n async start(controller) {\n // Send initial content\n const content = await readFile(logPath, 'utf-8').catch(() => '');\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({ type: 'content', data: content })}\\n\\n`,\n ),\n );\n let lastSize = content.length;\n\n // Poll for updates\n const poll = async () => {\n if (!isRunLogActive(project, type, runId)) {\n // Log completed — send final update and close\n try {\n const finalContent = await readFile(logPath, 'utf-8');\n if (finalContent.length > lastSize) {\n const newContent = finalContent.substring(lastSize);\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({ type: 'append', data: newContent })}\\n\\n`,\n ),\n );\n }\n } catch {}\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify({ type: 'complete' })}\\n\\n`),\n );\n controller.close();\n return;\n }\n\n try {\n const currentContent = await readFile(logPath, 'utf-8');\n if (currentContent.length > lastSize) {\n const newContent = currentContent.substring(lastSize);\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({ type: 'append', data: newContent })}\\n\\n`,\n ),\n );\n lastSize = currentContent.length;\n }\n } catch {}\n\n await new Promise((r) => setTimeout(r, 1000));\n await poll();\n };\n\n await new Promise((r) => setTimeout(r, 1000));\n await poll();\n },\n });\n\n const effectStream = Stream.fromReadableStream<Uint8Array, unknown>({\n evaluate: () => nodeStream,\n onError: (err) => err,\n });\n\n return HttpServerResponse.stream(effectStream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/runs/:runId ───────────────────\n\nconst getProjectSpecialistRunRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/runs/:runId',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n const runId = params['runId'] as string;\n\n const { getRunLogSync, parseLogMetadata } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n const content = getRunLogSync(project, type, runId);\n\n if (!content) {\n return jsonResponse({ error: 'Run log not found' }, { status: 404 });\n }\n\n const metadata = parseLogMetadata(content);\n return jsonResponse({ runId, content, metadata });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/runs/:runId/terminate ────────\n\nconst postProjectSpecialistRunTerminateRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/runs/:runId/terminate',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { terminateSpecialist } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n yield* Effect.promise(() => terminateSpecialist(project, type));\n return jsonResponse({ success: true, message: 'Specialist terminated' });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/grace/pause ──────────────────\n\nconst postProjectSpecialistGracePauseRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/grace/pause',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { pauseGracePeriod } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const success = pauseGracePeriod(project, type);\n\n if (success) {\n return jsonResponse({ success: true, message: 'Grace period paused' });\n } else {\n return jsonResponse(\n { error: 'No active grace period to pause' },\n { status: 400 },\n );\n }\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/grace/resume ─────────────────\n\nconst postProjectSpecialistGraceResumeRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/grace/resume',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { resumeGracePeriod } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const success = resumeGracePeriod(project, type);\n\n if (success) {\n return jsonResponse({ success: true, message: 'Grace period resumed' });\n } else {\n return jsonResponse(\n { error: 'No paused grace period to resume' },\n { status: 400 },\n );\n }\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/grace/exit ───────────────────\n\nconst postProjectSpecialistGraceExitRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/grace/exit',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { exitGracePeriod } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n exitGracePeriod(project, type);\n return jsonResponse({\n success: true,\n message: 'Specialist terminated immediately',\n });\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/grace ────────────────────────\n\nconst getProjectSpecialistGraceRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/grace',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { getGracePeriodState } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const state = getGracePeriodState(project, type);\n\n if (state) {\n return jsonResponse(state);\n } else {\n return jsonResponse({ error: 'No active grace period' }, { status: 404 });\n }\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/context ──────────────────────\n\nconst getProjectSpecialistContextRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/context',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n const { loadContextDigest } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialist-context.js'));\n const digest = loadContextDigest(project, type);\n\n if (digest) {\n return jsonResponse({ digest });\n } else {\n return jsonResponse({ error: 'No context digest found' }, { status: 404 });\n }\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/context/regenerate ───────────\n\nconst postProjectSpecialistContextRegenerateRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/context/regenerate',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n const { regenerateContextDigest } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialist-context.js'));\n const digest = yield* Effect.promise(() => regenerateContextDigest(project, type));\n\n if (digest) {\n return jsonResponse({ digest, message: 'Context digest regenerated' });\n } else {\n return jsonResponse(\n { error: 'Failed to generate context digest' },\n { status: 500 },\n );\n }\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/complete ─────────────────────\n\nconst postProjectSpecialistCompleteRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/complete',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n const body = yield* readJsonBody;\n const { status, notes, issueId } = body as { status?: string; notes?: string; issueId?: string };\n\n if (!status || !['passed', 'failed', 'blocked'].includes(status)) {\n return jsonResponse(\n { error: 'Valid status (passed/failed/blocked) is required' },\n { status: 400 },\n );\n }\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { signalSpecialistCompletion } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n signalSpecialistCompletion(project, type, { status: status as 'passed' | 'failed' | 'blocked', notes }, issueId);\n return jsonResponse({\n success: true,\n message: 'Specialist completion signaled, grace period started',\n });\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/latest-log ───────────────────\n\nconst getProjectSpecialistLatestLogRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/latest-log',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n const runsDir = join(homedir(), '.overdeck', 'specialists', project, type, 'runs');\n if (!existsSync(runsDir)) {\n return jsonResponse({ log: null, message: 'No runs found' });\n }\n\n const files = (yield* Effect.promise(() => readdir(runsDir)))\n .filter((f) => f.endsWith('.log'))\n .sort()\n .reverse();\n\n if (files.length === 0) {\n return jsonResponse({ log: null, message: 'No run logs found' });\n }\n\n const latestLog = yield* Effect.promise(() => readFile(join(runsDir, files[0]), 'utf-8'));\n return jsonResponse({\n log: latestLog,\n file: files[0],\n totalRuns: files.length,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/logs/cleanup ────────────────\n\nconst postProjectSpecialistLogsCleanupRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/logs/cleanup',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n const { cleanupOldLogsSync } = yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n const { getSpecialistRetention } = yield* Effect.promise(() => import('../../../../lib/projects.js'));\n\n const retention = getSpecialistRetention(project);\n const deleted = cleanupOldLogsSync(project, type, { maxDays: retention.max_days, maxRuns: retention.max_runs });\n\n return jsonResponse({\n success: true,\n deleted,\n message: `Cleaned up ${deleted} old logs`,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/projects/:project/:name/reset-session ───────\n// Bumps the session generation so the next dispatch starts a fresh Claude session.\n// Old JSONL files are preserved.\n\nconst postProjectSpecialistResetSessionRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/projects/:project/:name/reset-session',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const projectKey = params['project'] ?? '';\n const name = params['name'] ?? '';\n return jsonResponse(\n { error: `Legacy specialist session rotation is no longer supported for ${projectKey}/${name}.` },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:issueId/review/restart ───────────\n// Kills all reviewer tmux sessions + coordinator, wipes the review agent\n// state directories (PAN-1985), then dispatches a fresh review through the\n// role primitive. The new review starts with a fresh `state.json` and a new\n// session id because the old dirs are gone.\n//\n// Convoy view vs Quick Review view is purely a presentation concern (the\n// menu label is \"Restart All\" vs \"Restart\"); the route behavior is the\n// same for both. Wipe+respawn is the deliberate override path for harness/\n// model switches — the NORMAL review flow continues the same session\n// across re-dispatches (PAN-1862), and this endpoint is the escape hatch\n// that pays the re-research cost.\n\nconst postProjectReviewRestartRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:issueId/review/restart',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const issueId = params['issueId'] as string;\n const body = yield* readJsonBody;\n const { model, harness } = body as { model?: string; harness?: 'claude-code' | 'pi' | 'codex' };\n\n const { killAllReviewerSessions } = yield* Effect.promise(\n () => import('../../../../lib/cloister/review-agent.js'),\n );\n const killResult = yield* killAllReviewerSessions(project, issueId);\n\n // PAN-1862: do NOT wipe here. The review session (state.json + saved session id) is preserved\n // so spawnReviewRoleForIssue can RESUME it — keeping the prior review's context so a restart\n // with the same model checks the fix instead of re-researching the whole diff. It wipes +\n // fresh-spawns internally ONLY when the harness/model actually changed.\n\n // Resolve workspace info for re-dispatch\n const projectConfig = resolveProjectFromIssueSync(issueId);\n if (!projectConfig) {\n return jsonResponse({ error: `Cannot resolve project for ${issueId}` }, { status: 404 });\n }\n const workspacePath = join(projectConfig.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: `Workspace not found: ${workspacePath}` }, { status: 404 });\n }\n\n // Detect branch\n let branch = 'unknown';\n try {\n const { stdout } = yield* Effect.promise(() => execAsync(\n `cd \"${workspacePath}\" && git branch --show-current`,\n { encoding: 'utf-8', timeout: 5000 },\n ));\n branch = stdout.trim() || 'unknown';\n } catch { /* non-fatal */ }\n\n // PAN-1048 R3: review now spawns through the role primitive.\n const { spawnReviewRoleForIssue } = yield* Effect.promise(\n () => import('../../../../lib/cloister/review-agent.js'),\n );\n const prUrl = getReviewStatusSync(issueId)?.prUrl;\n const result = yield* spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePath,\n branch,\n prUrl,\n model,\n harness,\n });\n\n if (result.gated) {\n return jsonResponse({\n success: false,\n gated: true,\n message: result.message,\n killed: killResult.killed,\n wiped: [],\n model: model ?? undefined,\n harness: harness ?? undefined,\n }, { status: 409 });\n }\n\n return jsonResponse({\n success: result.success,\n message: result.message,\n killed: killResult.killed,\n wiped: [],\n model: model ?? undefined,\n harness: harness ?? undefined,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:issueId/reviewer/:role/restart ───\n//\n// PAN-1048 review feedback 005 (C5): the per-reviewer restart endpoint is\n// retired. The role primitive launches the four code-review-* sub-agents in a\n// single review-role run via the Agent tool — there is no longer a per-axis\n// tmux session to restart, no `pan-review-agent` shell to relaunch, and no\n// per-reviewer prompt file to feed back in. Returning 410 with a pointer to\n// the supported restart surface keeps any frontend cache/intent that still\n// hits this URL from silently re-establishing the legacy machinery.\n\nconst postProjectReviewerRoleRestartRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:issueId/reviewer/:role/restart',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n return jsonResponse(\n {\n error: 'per-reviewer restart route retired',\n hint: 'The review role launches all four code-review-* sub-agents in a single run via the Agent tool. To re-run a review, POST /api/review/:issueId/trigger (full review restart through spawnReviewRoleForIssue), which dispatches role: review and re-fans-out the convoy.',\n retiredFor: {\n project: params['project'],\n issueId: params['issueId'],\n role: params['role'],\n },\n },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: GET /api/models/resolve ──────────────────────────────────────────\n// Returns the resolved default model for each session/work type.\n\nconst getModelsResolveRoute = HttpRouter.add(\n 'GET',\n '/api/models/resolve',\n httpHandler(Effect.gen(function* () {\n const config = loadConfigSync().config;\n\n const routes = [\n { key: 'role:plan', role: 'plan' },\n { key: 'role:work', role: 'work' },\n { key: 'role:work.inspect', role: 'work', subRole: 'inspect' },\n { key: 'role:work.inspect-deep', role: 'work', subRole: 'inspect-deep' },\n { key: 'role:strike', role: 'strike' },\n { key: 'role:review', role: 'review' },\n { key: 'role:review.correctness', role: 'review', subRole: 'correctness' },\n { key: 'role:review.security', role: 'review', subRole: 'security' },\n { key: 'role:review.performance', role: 'review', subRole: 'performance' },\n { key: 'role:review.requirements', role: 'review', subRole: 'requirements' },\n { key: 'role:test', role: 'test' },\n { key: 'role:ship', role: 'ship' },\n ] as const;\n\n const resolved: Record<string, string | null> = {};\n for (const route of routes) {\n try {\n resolved[route.key] = resolveModel(route.role, (route as { subRole?: string }).subRole, config);\n } catch {\n resolved[route.key] = null;\n }\n }\n\n return jsonResponse(resolved);\n })),\n);\n\nexport const specialistsProjectRouteLayer = Layer.mergeAll(\n // PAN-1048 R1: postProjectSpecialistSpawnRoute removed (see above).\n getProjectSpecialistStatusRoute,\n postProjectSpecialistKillRoute,\n getProjectSpecialistRunsRoute,\n getProjectSpecialistRunStreamRoute, // /runs/:runId/stream — before /runs/:runId\n getProjectSpecialistRunRoute, // /runs/:runId\n postProjectSpecialistRunTerminateRoute, // /runs/:runId/terminate\n postProjectSpecialistGracePauseRoute, // /grace/pause\n postProjectSpecialistGraceResumeRoute, // /grace/resume\n postProjectSpecialistGraceExitRoute, // /grace/exit\n getProjectSpecialistGraceRoute, // /grace\n getProjectSpecialistContextRoute, // /context\n postProjectSpecialistContextRegenerateRoute, // /context/regenerate\n postProjectSpecialistCompleteRoute, // /complete\n getProjectSpecialistLatestLogRoute, // /latest-log\n postProjectSpecialistLogsCleanupRoute, // /logs/cleanup\n postProjectSpecialistResetSessionRoute, // /reset-session\n postProjectReviewRestartRoute, // /:project/:issueId/review/restart\n postProjectReviewerRoleRestartRoute, // /:project/:issueId/reviewer/:role/restart\n getModelsResolveRoute, // /models/resolve\n);\n","/**\n * Specialists route module — Effect HttpRouter.Layer (PAN-428 B9)\n *\n * Implements all /api/specialists/* endpoints from the Express server:\n * GET /api/specialists\n * POST /api/specialists/reset-all\n * POST /api/specialists/done\n * POST /api/specialists/logs/cleanup-all\n * GET /api/specialists/queues\n * GET /api/specialists/projects\n * POST /api/specialists/:name/wake\n * POST /api/specialists/:name/reset\n * POST /api/specialists/:name/init\n * POST /api/specialists/:name/report-status\n * GET /api/specialists/:name/cost\n * GET /api/specialists/:name/queue\n * POST /api/specialists/:name/queue\n * DELETE /api/specialists/:name/queue/:itemId\n * PUT /api/specialists/:name/queue/reorder\n * POST /api/specialists/:name/auto-complete\n * GET /api/specialists/:project/:issueId/:type/status\n * POST /api/specialists/:project/:issueId/:type/kill\n * GET /api/specialists/:project/:type/queue\n * POST /api/specialists/:project/:type/spawn\n * GET /api/specialists/:project/:type/runs\n * GET /api/specialists/:project/:type/runs/:runId\n * GET /api/specialists/:project/:type/runs/:runId/stream\n * POST /api/specialists/:project/:type/runs/:runId/terminate\n * POST /api/specialists/:project/:type/grace/pause\n * POST /api/specialists/:project/:type/grace/resume\n * POST /api/specialists/:project/:type/grace/exit\n * GET /api/specialists/:project/:type/grace\n * GET /api/specialists/:project/:type/context\n * POST /api/specialists/:project/:type/context/regenerate\n * POST /api/specialists/:project/:type/complete\n * GET /api/specialists/:project/:type/latest-log\n * POST /api/specialists/:project/:type/logs/cleanup\n */\n\nimport { Layer } from 'effect';\n\nimport { specialistsLegacyRouteLayer } from './specialists/legacy-routes.js';\nimport { specialistsProjectRouteLayer } from './specialists/project-routes.js';\n\nexport {\n _serverManagedMerges,\n firePostMergeLifecycle,\n validateSpecialistAutoCompleteMetadata,\n} from './specialists/shared.js';\n\nexport const specialistsRouteLayer = Layer.mergeAll(\n specialistsLegacyRouteLayer,\n specialistsProjectRouteLayer,\n);\n\nexport default specialistsRouteLayer;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"specialists-DEfrAbWs.js","names":["readFile","stat","open","pathExists","join","getAgentWorkspace","stat","execFileAsync","join","stat","execAsync","execFileAsync","join","readJsonBody","getAgentWorkspaceShared","getAgentJsonlPathShared","getAgentPendingQuestionsShared","join","join","execAsync","join","readJsonBody","Result","readJsonBody","readJsonBody","readJsonBody","readJsonBody","dirname","join","readJsonBody","join","mkdir","writeFile","execAsync","join","execFileAsync","execFileAsync","readJsonBody","loadWorkspaceMetadataFn","join","dirname","execAsync","join","join","setReviewStatusBase","join","join"],"sources":["../../src/dashboard/server/http-helpers.ts","../../src/dashboard/server/routes/http-handler.ts","../../src/lib/codex-auth.ts","../../src/dashboard/server/routes/origin-validation.ts","../../src/dashboard/server/routes/jsonl-resolver.ts","../../src/lib/work-agent-lifecycle.ts","../../src/dashboard/server/services/git-info.ts","../../src/dashboard/server/routes/agents/shared.ts","../../src/dashboard/server/routes/agents/listing.ts","../../src/dashboard/server/routes/agents/conversation.ts","../../src/dashboard/server/routes/agents/messaging.ts","../../src/lib/channels/permission-payload.ts","../../src/dashboard/server/routes/agent-permissions.ts","../../src/dashboard/server/routes/agents/permissions.ts","../../src/dashboard/server/services/agent-event-utils.ts","../../src/dashboard/server/routes/agents/runtime-events.ts","../../src/dashboard/server/services/agent-projection.ts","../../src/dashboard/server/routes/agents/lifecycle-stop.ts","../../src/dashboard/server/routes/agents/lifecycle-restart.ts","../../src/dashboard/server/routes/agents/control.ts","../../src/lib/vbrief/auto-synthesize.ts","../../src/dashboard/server/routes/agents/spawn-helpers.ts","../../src/dashboard/server/routes/agents/spawn.ts","../../src/dashboard/server/routes/agents.ts","../../src/lib/cloister/task-readiness.ts","../../src/dashboard/server/routes/tiered-inspect-escalation.ts","../../src/dashboard/server/routes/specialists/shared.ts","../../src/dashboard/server/routes/specialists/legacy-routes.ts","../../src/dashboard/server/routes/specialists/project-routes.ts","../../src/dashboard/server/routes/specialists.ts"],"sourcesContent":["/**\n * Workaround for Effect 4.0.0-beta.45 bug where HttpServerResponse.json()\n * creates a response without a proper body._tag, causing NodeHttpServer to crash\n * with \"undefined is not an object (evaluating 'body._tag')\".\n * \n * HttpServerResponse.text() works fine, so we use it with manual JSON.stringify.\n * Remove this workaround when Effect stabilizes HttpServerResponse.json().\n */\nimport { HttpServerResponse } from 'effect/unstable/http';\n\nexport function jsonResponse(\n data: unknown,\n options?: number | { status?: number; headers?: Record<string, string> },\n): HttpServerResponse.HttpServerResponse {\n return jsonStringResponse(JSON.stringify(data), options);\n}\n\nexport function jsonStringResponse(\n json: string,\n options?: number | { status?: number; headers?: Record<string, string> },\n): HttpServerResponse.HttpServerResponse {\n const opts = typeof options === 'number' ? { status: options } : options;\n return HttpServerResponse.text(\n json,\n {\n status: opts?.status ?? 200,\n contentType: 'application/json',\n headers: opts?.headers,\n }\n );\n}\n","/**\n * httpHandler — typed error-to-HTTP mapping wrapper (PAN-470)\n *\n * Wraps an Effect route body and catches all typed TaggedErrors,\n * mapping them to appropriate HTTP status codes. Unknown/unhandled\n * errors fall through as 500 Internal Server Error.\n *\n * Usage:\n * HttpRouter.add('GET', '/api/foo', httpHandler(Effect.gen(function* () {\n * const result = yield* someService.doThing();\n * return jsonResponse(result);\n * })))\n */\n\nimport { Cause, Effect, Option } from 'effect';\nimport { HttpServerRequest, HttpServerResponse as HttpServerResponseModule } from 'effect/unstable/http';\n\ntype HttpServerResponse = HttpServerResponseModule.HttpServerResponse;\n\nimport { jsonResponse } from '../http-helpers.js';\nimport {\n AgentAlreadyRunning,\n AgentStartError,\n BeadsNotInitialized,\n IssueNotFound,\n PlanEmpty,\n RateLimited,\n TrackerApiError,\n TrackerNotConfigured,\n WorkspaceCreateError,\n WorkspaceNotFound,\n} from '../services/typed-errors.js';\n\n// Burst-control for the opaque-defect log: log the first OPAQUE_FULL_BURST\n// occurrences in full, then once per OPAQUE_BURST_INTERVAL_MS, emit a summary\n// with the count of suppressed events. This stops a runaway null-defect source\n// from filling the log + saturating the event loop with formatting work, while\n// preserving full detail for the first few occurrences so diagnosis is possible.\nconst OPAQUE_FULL_BURST = 5;\nconst OPAQUE_BURST_INTERVAL_MS = 30_000;\nlet opaqueLogged = 0;\nlet opaqueSuppressed = 0;\nlet opaqueLastSummaryAt = 0;\n\n/**\n * Wraps a route effect with typed error-to-HTTP status mapping.\n *\n * Error → Status mapping:\n * IssueNotFound, WorkspaceNotFound → 404 Not Found\n * TrackerNotConfigured → 503 Service Unavailable\n * RateLimited → 429 Too Many Requests\n * AgentAlreadyRunning → 409 Conflict\n * BeadsNotInitialized, PlanEmpty → 422 Unprocessable Entity\n * TrackerApiError → 502 Bad Gateway\n * WorkspaceCreateError, AgentStartError → 500 Internal Server Error\n * Unknown errors → 500 Internal Server Error\n */\nexport function httpHandler<R, E>(\n effect: Effect.Effect<HttpServerResponse, E, R>\n): Effect.Effect<HttpServerResponse, never, R> {\n // Capture the registration call site (where the route called httpHandler(...))\n // so when a fiber emits a typed Fail with no stack — e.g. Effect.fail(null) —\n // we can still tell which route module the handler belongs to.\n const registeredAt = (new Error('httpHandler registered')).stack ?? '<no stack>';\n type AllKnownErrors =\n | IssueNotFound\n | WorkspaceNotFound\n | TrackerNotConfigured\n | RateLimited\n | AgentAlreadyRunning\n | BeadsNotInitialized\n | PlanEmpty\n | TrackerApiError\n | WorkspaceCreateError\n | AgentStartError;\n return (effect as Effect.Effect<HttpServerResponse, AllKnownErrors, R>).pipe(\n Effect.catchTag('IssueNotFound', (err: IssueNotFound) =>\n Effect.succeed(jsonResponse({ error: `Issue not found: ${err.id}` }, { status: 404 }))\n ),\n Effect.catchTag('WorkspaceNotFound', (err: WorkspaceNotFound) =>\n Effect.succeed(jsonResponse({ error: `Workspace not found: ${err.id}` }, { status: 404 }))\n ),\n Effect.catchTag('TrackerNotConfigured', (err: TrackerNotConfigured) =>\n Effect.succeed(jsonResponse({ error: `Tracker not configured: ${err.tracker}` }, { status: 503 }))\n ),\n Effect.catchTag('RateLimited', (err: RateLimited) =>\n Effect.succeed(\n jsonResponse({ error: 'Rate limited', retryAfter: err.retryAfter }, { status: 429 })\n )\n ),\n Effect.catchTag('AgentAlreadyRunning', (err: AgentAlreadyRunning) =>\n Effect.succeed(jsonResponse({ error: `Agent already running for issue: ${err.id}` }, { status: 409 }))\n ),\n Effect.catchTag('BeadsNotInitialized', (err: BeadsNotInitialized) =>\n Effect.succeed(\n jsonResponse({ error: `Beads not initialized for workspace: ${err.workspace}` }, { status: 422 })\n )\n ),\n Effect.catchTag('PlanEmpty', (err: PlanEmpty) =>\n Effect.succeed(jsonResponse({ error: `Plan is empty for issue: ${err.id}` }, { status: 422 }))\n ),\n Effect.catchTag('TrackerApiError', (err: TrackerApiError) =>\n Effect.succeed(\n jsonResponse({ error: `Tracker API error (${err.tracker}): ${err.message}` }, { status: 502 })\n )\n ),\n Effect.catchTag('WorkspaceCreateError', (err: WorkspaceCreateError) =>\n Effect.succeed(\n jsonResponse({ error: `Workspace creation failed for ${err.id}: ${err.message}` }, { status: 500 })\n )\n ),\n Effect.catchTag('AgentStartError', (err: AgentStartError) =>\n Effect.succeed(\n jsonResponse({ error: `Agent start failed for ${err.id}: ${err.message}` }, { status: 500 })\n )\n ),\n Effect.catchCause((cause) =>\n Effect.gen(function* () {\n // Interrupt-only causes mean the consumer (browser tab, abort signal) cancelled\n // the request. That isn't a server error — silence it instead of spamming logs.\n if (Cause.hasInterruptsOnly(cause)) {\n return jsonResponse({ error: 'Request cancelled' }, { status: 499 });\n }\n const error = Cause.squash(cause);\n const message = error instanceof Error ? error.message : 'Internal server error';\n const reqOption = yield* Effect.serviceOption(HttpServerRequest.HttpServerRequest);\n const route = Option.match(reqOption, {\n onNone: () => 'unknown',\n onSome: (req) => `${req.method} ${req.url}`,\n });\n // When the squashed defect is null/undefined or a non-Error value the pretty\n // printer produces `Error: Unknown error: null` with no stack, which leaves\n // the source impossible to locate. Capture extra context for those cases:\n // the JSON shape of the cause (failures + defects), the typeof, and a stack\n // sampled from this catch site so we can at least see what caller chained\n // into httpHandler.\n const isOpaque = !(error instanceof Error);\n if (isOpaque) {\n if (opaqueLogged < OPAQUE_FULL_BURST) {\n opaqueLogged += 1;\n let causeJson: string;\n try {\n causeJson = JSON.stringify(cause, (_k, v) => (typeof v === 'function' ? '[fn]' : v), 2);\n } catch {\n causeJson = '<cause not serializable>';\n }\n console.error(\n `[httpHandler] Opaque non-Error defect on ${route} (#${opaqueLogged}): typeof=${typeof error} value=${String(error)}\\n` +\n `cause=${causeJson}\\n` +\n `registeredAt=${registeredAt}`,\n );\n } else {\n opaqueSuppressed += 1;\n const now = Date.now();\n if (now - opaqueLastSummaryAt >= OPAQUE_BURST_INTERVAL_MS) {\n opaqueLastSummaryAt = now;\n console.error(\n `[httpHandler] Opaque non-Error defect on ${route} — ${opaqueSuppressed} more suppressed since last summary ` +\n `(set DEBUG_OPAQUE_DEFECTS=1 to see all). value=${String(error)}`,\n );\n opaqueSuppressed = 0;\n }\n }\n } else {\n console.error(`[httpHandler] Unhandled error on ${route}:\\n${Cause.pretty(cause)}`);\n }\n return jsonResponse({ error: message }, { status: 500 });\n }),\n )\n ) as Effect.Effect<HttpServerResponse, never, R>;\n}\n","import { open, readFile, stat } from 'fs/promises';\nimport { join } from 'path';\nimport { Effect, Data } from 'effect';\nimport { decodeJwtPayload, getCliproxyAuthDir, getCliproxyLogPath } from './cliproxy.js';\n\n/** Wrapper error for Codex auth probing — preserves the underlying cause. */\nexport class CodexAuthCheckError extends Data.TaggedError('CodexAuthCheckError')<{\n readonly message: string;\n readonly cause?: unknown;\n}> {}\n\nexport interface CodexAuthValid {\n status: 'valid';\n email: string;\n expiresAt: string;\n}\n\nexport interface CodexAuthExpired {\n status: 'expired';\n email: string;\n expiresAt: string;\n}\n\nexport interface CodexAuthBurned {\n status: 'burned';\n email: string;\n expiresAt: string;\n}\n\nexport interface CodexAuthMissing {\n status: 'missing';\n}\n\nexport interface CodexAuthUnknown {\n status: 'unknown';\n message?: string;\n}\n\nexport type CodexAuthStatus = CodexAuthValid | CodexAuthExpired | CodexAuthBurned | CodexAuthMissing | CodexAuthUnknown;\n\ninterface CheckCodexAuthOptions {\n ignoreBurnBefore?: number;\n}\n\ninterface CliproxyCodexCredentials {\n access_token?: string;\n email?: string;\n type?: string;\n}async function checkCodexAuthStatusPromise(options: CheckCodexAuthOptions = {}): Promise<CodexAuthStatus> {\n const credPath = join(getCliproxyAuthDir(), 'codex-primary.json');\n\n let raw: string;\n let credMtimeMs: number | null = null;\n try {\n raw = await readFile(credPath, 'utf8');\n credMtimeMs = (await stat(credPath)).mtimeMs;\n } catch {\n return { status: 'missing' };\n }\n\n let creds: CliproxyCodexCredentials;\n try {\n creds = JSON.parse(raw) as CliproxyCodexCredentials;\n } catch {\n return { status: 'unknown', message: 'Malformed credential file' };\n }\n\n const accessToken = typeof creds.access_token === 'string' ? creds.access_token : null;\n if (!accessToken) {\n return { status: 'unknown', message: 'Missing access_token in credential file' };\n }\n\n const claims = decodeJwtPayload(accessToken);\n if (!claims) {\n return { status: 'unknown', message: 'Unable to decode access_token' };\n }\n\n const expSec = typeof claims.exp === 'number' ? claims.exp : null;\n if (expSec === null) {\n return { status: 'unknown', message: 'Missing exp claim in access_token' };\n }\n\n const email = typeof creds.email === 'string' ? creds.email : '';\n const expiresAt = new Date(expSec * 1000).toISOString();\n\n if (expSec * 1000 <= Date.now()) {\n return { status: 'expired', email, expiresAt };\n }\n\n const jwtStatus: CodexAuthStatus = { status: 'valid', email, expiresAt };\n // The credential file's own write time is the authoritative \"you last logged\n // in at\" reference: any auth failure logged AFTER it means the CURRENT token\n // is dead. An explicit caller option (the re-auth flow's session.createdAt)\n // still wins. Without this, callers that pass no option fell back to the crude\n // 1h staleness window and reported a burned token as \"valid\" once the burn\n // line aged out — exactly the gap that let gpt-5.5 agents spawn into 503s.\n const effectiveOptions: CheckCodexAuthOptions = {\n ignoreBurnBefore: options.ignoreBurnBefore ?? credMtimeMs ?? undefined,\n };\n return await applyBurnedTokenOverride(jwtStatus, email, expiresAt, effectiveOptions);\n}\n\n/** Read the trailing window of the cliproxy log for burn/failure analysis. */\nasync function readLogTail(path: string): Promise<string> {\n const TAIL_BYTES = 128 * 1024;\n const file = await open(path, 'r');\n try {\n const stat = await file.stat();\n const length = Math.min(stat.size, TAIL_BYTES);\n const buffer = Buffer.alloc(length);\n await file.read(buffer, 0, length, stat.size - length);\n return buffer.toString('utf8');\n } finally {\n await file.close();\n }\n}\n\nasync function applyBurnedTokenOverride(\n baseStatus: CodexAuthStatus,\n email: string,\n expiresAt: string,\n options: CheckCodexAuthOptions,\n): Promise<CodexAuthStatus> {\n let logRaw: string;\n try {\n logRaw = await readLogTail(getCliproxyLogPath());\n } catch {\n return baseStatus;\n }\n return evaluateBurnedFromLog(logRaw, baseStatus, email, expiresAt, options);\n}\n\nconst BURN_STALENESS_MS = 60 * 60 * 1000;\n\n/**\n * Pure decision: given the cliproxy log tail, decide whether a JWT-valid token\n * is actually burned. Exported for regression testing (PAN-1584).\n *\n * \"Auth failure\" evidence = either a refresh-token burn line\n * (`refresh token has already been used`) OR a `503` on `/v1/messages` /\n * `/v1/chat/completions` (the live symptom the agent hits once cliproxy disables\n * the provider — and which keeps appearing after the burn line stops, since a\n * disabled provider no longer attempts refreshes). \"Success\" = a `200` on those\n * same paths (proves the auth path works right now).\n *\n * Decision:\n * - No failure evidence → trust base status.\n * - Success AFTER the last failure → trust base status (recovered).\n * - Failure AFTER the credential's last write (ignoreBurnBefore) → BURNED,\n * regardless of age. The credential write time is when you last logged in;\n * a failure after it means the current token is dead. This is authoritative\n * and is the fix for the bug where a >1h-stale burn line was dismissed as\n * \"valid\" during a quiet period even though the token was dead (PAN-1584).\n * - Failure BEFORE that cutoff → trust base status (re-authed since).\n * - No cutoff available → fall back to the staleness backstop:\n * burned only if the last failure is within BURN_STALENESS_MS.\n *\n * We intentionally don't probe cliproxy with HTTP — `GET /v1/models` always\n * 401s (it needs real OAuth, not the local key) and would generate spurious log\n * lines on every dashboard load.\n */\nexport function evaluateBurnedFromLog(\n logRaw: string,\n baseStatus: CodexAuthStatus,\n email: string,\n expiresAt: string,\n options: CheckCodexAuthOptions & { now?: number } = {},\n): CodexAuthStatus {\n const now = options.now ?? Date.now();\n // Scan a wider window than the original 50 lines so a quiet recovery period\n // can still surface a later success.\n const lines = logRaw.split('\\n').slice(-500);\n\n // PAN-913 matched `refresh token has already been used`; PAN-1455 adds `refresh_token_reused`.\n const isBurnLine = (l: string) =>\n l.includes('refresh token has already been used') || l.includes('refresh_token_reused');\n const isAuthFailure503 = (l: string) =>\n /\\b503 \\|/.test(l) && /POST\\s+\"\\/v1\\/(messages|chat\\/completions)/.test(l);\n const isSuccess = (l: string) =>\n /\\b200 \\|/.test(l) && /POST\\s+\"\\/v1\\/(messages|chat\\/completions)/.test(l);\n\n // The bracketed timestamp lives on the gin_logger/openai_auth line; burn\n // messages sit on a JSON continuation line with no prefix, so scan back a few\n // lines for the nearest one.\n const timestampAt = (idx: number): number | null => {\n for (let j = idx; j >= Math.max(0, idx - 10); j--) {\n const m = lines[j]?.match(/^\\[(\\d{4}-\\d{2}-\\d{2}) (\\d{2}:\\d{2}:\\d{2})\\]/);\n if (m) {\n const t = Date.parse(`${m[1]}T${m[2]}Z`);\n if (Number.isFinite(t)) return t;\n }\n }\n return null;\n };\n\n let lastFailureIdx = -1;\n let lastFailureTimestamp: number | null = null;\n let lastSuccessIdx = -1;\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i] ?? '';\n if (lastFailureIdx < 0 && (isBurnLine(line) || isAuthFailure503(line))) {\n lastFailureIdx = i;\n lastFailureTimestamp = timestampAt(i);\n }\n if (lastSuccessIdx < 0 && isSuccess(line)) {\n lastSuccessIdx = i;\n }\n if (lastFailureIdx >= 0 && lastSuccessIdx >= 0) break;\n }\n\n // No failure evidence at all → trust the JWT-based status.\n if (lastFailureIdx < 0) return baseStatus;\n\n // A successful LLM call came AFTER the failure → auth path works again.\n if (lastSuccessIdx > lastFailureIdx) return baseStatus;\n\n // Authoritative path: we know when the credential was last written. A failure\n // after that write means the CURRENT token is dead — flag burned no matter how\n // old the line is. (Pad by 1000ms: burn lines carry second-precision stamps, so\n // a credential written in the same second as the failure shouldn't look newer.)\n if (options.ignoreBurnBefore !== undefined) {\n if (lastFailureTimestamp !== null && lastFailureTimestamp + 1000 <= options.ignoreBurnBefore) {\n return baseStatus;\n }\n return { status: 'burned', email, expiresAt };\n }\n\n // No credential-write reference → staleness backstop only.\n if (lastFailureTimestamp !== null && now - lastFailureTimestamp > BURN_STALENESS_MS) {\n return baseStatus;\n }\n\n return { status: 'burned', email, expiresAt };\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Effect-native checkCodexAuthStatus. The Promise version is designed to\n * swallow all I/O errors and report the auth state through the typed status\n * union. The Effect variant wraps that to make it composable; it only fails\n * with CodexAuthCheckError if the underlying call itself throws unexpectedly\n * (i.e., not from the documented \"missing/unknown\" branches).\n */\nexport const checkCodexAuthStatus = (\n options: { ignoreBurnBefore?: number } = {},\n): Effect.Effect<CodexAuthStatus, CodexAuthCheckError> =>\n Effect.tryPromise({\n try: () => checkCodexAuthStatusPromise(options),\n catch: (cause) =>\n new CodexAuthCheckError({\n message: cause instanceof Error ? cause.message : String(cause),\n cause,\n }),\n });\n","import type { HttpServerRequest } from 'effect/unstable/http';\n\nexport type HeaderMap = Record<string, string | string[] | undefined>;\n\nlet cachedTrustedOrigins: string[] | undefined;\n\nfunction addTrustedOrigin(origins: Set<string>, raw: string | undefined): void {\n if (!raw) return;\n const normalized = normalizeOrigin(raw.trim());\n if (normalized) origins.add(normalized);\n}\n\nexport function getTrustedOrigins(): string[] {\n if (cachedTrustedOrigins !== undefined) {\n return cachedTrustedOrigins;\n }\n const port = parseInt(process.env['API_PORT'] ?? process.env['PORT'] ?? '3011', 10);\n const dashboardUrl = process.env['DASHBOARD_URL'] ?? `http://localhost:${port}`;\n const origins = new Set<string>();\n addTrustedOrigin(origins, dashboardUrl);\n addTrustedOrigin(origins, `http://localhost:${port}`);\n addTrustedOrigin(origins, `http://127.0.0.1:${port}`);\n addTrustedOrigin(origins, 'http://localhost:3010');\n addTrustedOrigin(origins, 'http://127.0.0.1:3010');\n\n const trustedOrigins = process.env['OVERDECK_TRUSTED_ORIGINS'];\n for (const origin of trustedOrigins?.split(',') ?? []) {\n addTrustedOrigin(origins, origin);\n }\n\n const traefikDomain = process.env['OVERDECK_TRAEFIK_DOMAIN'] ?? process.env['TRAEFIK_DOMAIN'];\n if (process.env['OVERDECK_TRAEFIK_ENABLED'] === '1' && traefikDomain) {\n addTrustedOrigin(origins, `https://${traefikDomain}`);\n }\n\n if (process.env['NODE_ENV'] === 'development') {\n addTrustedOrigin(origins, 'http://localhost:3000');\n addTrustedOrigin(origins, 'http://127.0.0.1:3000');\n }\n cachedTrustedOrigins = Array.from(origins);\n return cachedTrustedOrigins;\n}\n\nexport function normalizeOrigin(origin: string): string | null {\n try {\n const url = new URL(origin);\n return `${url.protocol}//${url.host}`;\n } catch {\n return null;\n }\n}\n\nexport function getHeaderFromMap(headers: HeaderMap, name: string): string | undefined {\n const direct = headers[name];\n if (Array.isArray(direct)) return direct[0];\n if (direct) return direct;\n\n const lowerName = name.toLowerCase();\n for (const [key, value] of Object.entries(headers)) {\n if (key.toLowerCase() !== lowerName) continue;\n return Array.isArray(value) ? value[0] : value;\n }\n return undefined;\n}\n\nexport function validateOriginHeaders(\n headers: HeaderMap,\n method: string,\n): { ok: true } | { ok: false; error: string } {\n const origin = getHeaderFromMap(headers, 'origin');\n const referer = getHeaderFromMap(headers, 'referer');\n const trusted = getTrustedOrigins();\n\n if (!origin && !referer) {\n const upperMethod = method.toUpperCase();\n if (upperMethod === 'GET' || upperMethod === 'HEAD') {\n return { ok: true };\n }\n return { ok: false, error: 'Missing origin' };\n }\n\n if (origin) {\n const normalized = normalizeOrigin(origin);\n if (normalized && trusted.includes(normalized)) {\n return { ok: true };\n }\n return { ok: false, error: 'Invalid origin' };\n }\n\n if (!referer) {\n return { ok: false, error: 'Invalid referer' };\n }\n const normalized = normalizeOrigin(referer);\n if (normalized && trusted.includes(normalized)) {\n return { ok: true };\n }\n return { ok: false, error: 'Invalid referer' };\n}\n\nexport function validateOrigin(\n request: HttpServerRequest.HttpServerRequest,\n): { ok: true } | { ok: false; error: string } {\n return validateOriginHeaders(request.headers as HeaderMap, request.method);\n}\n\nexport function _resetTrustedOriginsForTests(): void {\n cachedTrustedOrigins = undefined;\n}\n\n/**\n * Origin trust check used by raw WebSocket upgrade paths (autopreso, voice).\n * Only explicitly configured dashboard origins are trusted; the request Host\n * header is attacker-controlled and must not influence this decision.\n */\nexport function isTrustedOriginForHost(\n origin: string | undefined,\n _host: string | string[] | undefined,\n): boolean {\n if (!origin) return false;\n const normalized = normalizeOrigin(origin);\n if (!normalized) return false;\n return getTrustedOrigins().includes(normalized);\n}\n","/**\n * JSONL transcript resolver for the Command Deck (PAN-830).\n *\n * Maps an agent ID (e.g. `agent-pan-830`, `planning-pan-830`, or a canonical\n * specialist tmux session name) to the agent's JSONL transcript file on disk.\n *\n * Codex agents (PAN-1805) write rollout JSONLs under the per-agent\n * `codex-home/sessions/` tree — resolution dispatches on the harness recorded\n * in state.json (thread-id fast path, then latest-rollout fallback).\n *\n * For claude-code agents the JSONL filename is the *Claude session ID* (a UUID\n * written by Claude Code itself), NOT the agent/tmux name. Lookup order:\n * 1. session.id — single UUID written by auto-suspend\n * 2. sessions.json — array of UUIDs the agent has used; the heartbeat hook\n * APPENDS new IDs and dedupes, so once a session has been\n * seen its array index never moves. After a `pan resume`\n * brings an older session back as the live one, the array\n * order no longer reflects time-recency. We disambiguate\n * across all listed IDs by JSONL mtime.\n * 3. runtime state — in-process mirror, populated by hooks\n *\n * Async-only (fs/promises) because this code path runs inside the dashboard\n * server's event loop.\n */\nimport { access, readFile, stat, readdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { getAgentRuntimeState, getAgentStateSync } from '../../../lib/agents.js';\nimport { encodeClaudeProjectDir, getOverdeckHome } from '../../../lib/paths.js';\nimport { getAgentWorkspace } from '../../../lib/agent-enrichment.js';\nimport { getHarnessBehavior } from '../../../lib/runtimes/behavior.js';\nimport type { HarnessName } from '../../../lib/runtimes/types.js';\n\nexport interface ResolveJsonlPathOptions {\n /** Override the ~/.overdeck/agents directory (test hook). */\n agentsDirOverride?: string;\n /** Override the ~/.claude/projects directory (test hook). */\n claudeProjectsDirOverride?: string;\n /** Override the runtime-state lookup (test hook). */\n getRuntimeStateAsync?: (agentId: string) => Promise<{ claudeSessionId?: string } | null>;\n}\n\nasync function pathExists(p: string): Promise<boolean> {\n return access(p).then(() => true, () => false);\n}\n\nasync function readOptional(p: string): Promise<string | null> {\n return readFile(p, 'utf-8').catch(() => null);\n}\n\nfunction behaviorForHarness(harness: string | null | undefined) {\n return getHarnessBehavior(harness as HarnessName | null | undefined);\n}\n\n/**\n * Pick the candidate UUID whose JSONL transcript has the most recent mtime.\n *\n * The heartbeat hook appends to sessions.json and dedupes — so once an ID is\n * recorded, its array index never moves even if a later resume makes it the\n * current session again. Choosing by JSONL mtime is the only reliable signal\n * for \"which session is actually live right now.\"\n *\n * Returns null if no candidate has a corresponding JSONL on disk. Falls back\n * to scanning every project dir if the workspace path can't be resolved\n * (covers specialist sessions whose workspace lives elsewhere).\n */\nasync function pickFreshestSessionId(\n candidates: ReadonlyArray<string>,\n agentId: string,\n opts: ResolveJsonlPathOptions,\n): Promise<string | null> {\n if (candidates.length === 0) return null;\n if (candidates.length === 1) return candidates[0]!;\n\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n\n // Fast path: derive the agent's workspace and look only in the matching\n // project dir. Avoids fanning out across every Claude project.\n let candidatePaths: Array<{ id: string; path: string }> = [];\n try {\n const workspace = await Effect.runPromise(getAgentWorkspace(agentId));\n if (workspace) {\n const projectDir = join(projectsRoot, encodeClaudeProjectDir(workspace));\n candidatePaths = candidates.map((id) => ({ id, path: join(projectDir, `${id}.jsonl`) }));\n }\n } catch { /* non-fatal — fall back to project-dir scan */ }\n\n // Slow path: scan all project dirs (specialist agents, multi-workspace cases).\n if (candidatePaths.length === 0) {\n try {\n const dirs = await readdir(projectsRoot);\n const SAFE_DIR = /^[a-zA-Z0-9_.-]+$/;\n for (const id of candidates) {\n for (const dir of dirs) {\n if (!SAFE_DIR.test(dir)) continue;\n candidatePaths.push({ id, path: join(projectsRoot, dir, `${id}.jsonl`) });\n }\n }\n } catch { /* fall through to no-mtime path */ }\n }\n\n // Stat each candidate path; pick the (id, mtime) with the newest mtime.\n let best: { id: string; mtimeMs: number } | null = null;\n for (const { id, path } of candidatePaths) {\n try {\n const s = await stat(path);\n if (!best || s.mtimeMs > best.mtimeMs) {\n best = { id, mtimeMs: s.mtimeMs };\n }\n } catch { /* missing file — skip */ }\n }\n\n // If no candidate has a JSONL on disk, fall back to the historical \"last\n // appended\" heuristic — the previous behavior. Better than returning null\n // for callers that only need a session ID, not necessarily a live JSONL.\n return best ? best.id : (candidates[candidates.length - 1] ?? null);\n}\n\n/** Async equivalent of getLatestSessionId from lib/agents.ts. */\nexport async function resolveClaudeSessionId(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n\n // 1. session.id — single UUID written by auto-suspend. Authoritative when\n // present (only one session is alive when suspend writes this file).\n const sessionIdRaw = await readOptional(join(agentDir, 'session.id'));\n const sessionIdTrimmed = sessionIdRaw?.trim();\n if (sessionIdTrimmed) return sessionIdTrimmed;\n\n // 2. sessions.json — array of UUIDs the agent has ever used. We can't trust\n // array order (see file-level docs), so disambiguate by JSONL mtime.\n const sessionsRaw = await readOptional(join(agentDir, 'sessions.json'));\n if (sessionsRaw) {\n try {\n const parsed: unknown = JSON.parse(sessionsRaw);\n if (Array.isArray(parsed)) {\n const valid = parsed.filter((v): v is string => typeof v === 'string' && v.trim().length > 0);\n const fresh = await pickFreshestSessionId(valid, agentId, opts);\n if (fresh) return fresh;\n }\n } catch { /* non-fatal */ }\n }\n\n // 3. runtime state claudeSessionId (in-process mirror)\n try {\n const lookup = opts.getRuntimeStateAsync ?? ((id: string) => Effect.runPromise(getAgentRuntimeState(id)));\n const runtimeState = await lookup(agentId);\n if (runtimeState?.claudeSessionId) return runtimeState.claudeSessionId;\n } catch { /* non-fatal */ }\n\n return null;\n}\n\n/**\n * Read the Claude session id PINNED into an agent/conversation launcher.sh.\n *\n * The launcher is what spawns the live tmux pane: it runs\n * `claude … --session-id <uuid>` (or `--resume <uuid>`), so the pinned id is the\n * EXACT session the Terminal tab attaches to — the only deterministic ground\n * truth for \"which session is live right now.\" Resolving the transcript panel\n * from this id makes the Conversation tab match the Terminal tab by construction,\n * instead of guessing via JSONL mtime (racy: an older session's file gets touched\n * by a compaction summary write-back or a transient relaunch, its mtime jumps\n * ahead of the live session's, and the panel renders the wrong transcript).\n *\n * Checks the conversation launcher dir first, then the agent launcher dir.\n * Returns the uuid, or null when no launcher exists or it pins no session id.\n */\nconst LAUNCHER_UUID =\n '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}';\nconst LAUNCHER_SESSION_ID_RE = new RegExp(`--session-id\\\\s+'?(${LAUNCHER_UUID})'?`);\nconst LAUNCHER_RESUME_RE = new RegExp(`--resume\\\\s+'?(${LAUNCHER_UUID})'?`);\n\nexport async function readLauncherPinnedSessionId(\n tmuxSession: string,\n opts: { overdeckHomeOverride?: string } = {},\n): Promise<string | null> {\n const home = opts.overdeckHomeOverride ?? getOverdeckHome();\n const candidates = [\n join(home, 'conversations', tmuxSession, 'launcher.sh'),\n join(home, 'agents', tmuxSession, 'launcher.sh'),\n ];\n for (const path of candidates) {\n const text = await readOptional(path);\n if (!text) continue;\n const match = LAUNCHER_SESSION_ID_RE.exec(text) ?? LAUNCHER_RESUME_RE.exec(text);\n if (match) return match[1]!;\n }\n return null;\n}\n\n/**\n * Read the harness + workspace recorded for an agent, honoring the test\n * override dir. Used by resolveAgentHarness / agentHasClaudeTranscript so the\n * stale-harness self-correction runs identically in prod and under test.\n */\nasync function readRecordedState(\n agentId: string,\n opts: ResolveJsonlPathOptions,\n): Promise<{ harness: string | null; workspace?: string }> {\n if (opts.agentsDirOverride) {\n try {\n const raw = await readFile(join(opts.agentsDirOverride, agentId, 'state.json'), 'utf8');\n const s = JSON.parse(raw) as { harness?: unknown; workspace?: unknown };\n return {\n harness: typeof s.harness === 'string' ? s.harness : null,\n workspace: typeof s.workspace === 'string' ? s.workspace : undefined,\n };\n } catch {\n return { harness: null };\n }\n }\n const st = getAgentStateSync(agentId);\n return { harness: st?.harness ?? null, workspace: st?.workspace };\n}\n\n/**\n * Resolve the harness for transcript routing. Reads state.json, then\n * self-corrects when the recorded value is stale.\n *\n * Trust an explicit non-default harness (codex / pi / ohmypi) — it was chosen\n * by resolveHarness at spawn time and is not the generic fallback. The\n * 'claude-code' default, however, goes stale in one observed case: a\n * wipe-and-respawn that changed the provider-default harness left state.json\n * carrying the pre-respawn 'claude-code' while the fresh launch was\n * codex/ohmypi. The resolver then took the claude-code branch, found no\n * claudeSessionId, and the dashboard showed \"No conversation data available\"\n * for a live agent whose real transcript sat untouched under codex-home.\n *\n * Self-correction (claude-code/null recorded only): if the agent has NO\n * claude-code transcript on disk but DOES have a codex rollout / pi session,\n * the on-disk runtime wins — those artifacts are written only by that runtime,\n * so this cannot surface a wrong transcript. When a claude-code transcript IS\n * present it always wins, so a past codex run's codex-home can't shadow a\n * current claude-code session.\n */\nexport async function resolveAgentHarness(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const recorded = (await readRecordedState(agentId, opts)).harness;\n if (recorded === 'codex' || recorded === 'pi' || recorded === 'ohmypi') {\n return recorded;\n }\n // 'claude-code' (or null) is the default that can go stale. Correct it from\n // on-disk artifacts only when no claude-code transcript exists for the agent.\n if (await agentHasClaudeTranscript(agentId, opts)) {\n return recorded ?? 'claude-code';\n }\n if (await resolveCodexRolloutPath(agentId, opts)) return 'codex';\n if (await resolvePiSessionPath(agentId, opts)) return 'ohmypi';\n return recorded;\n}\n\n/**\n * Whether the agent has a resolvable claude-code transcript on disk. Used by\n * resolveAgentHarness to decide if a claude-code recording is still live\n * (vs. a stale default that should be corrected to codex/ohmypi).\n */\nasync function agentHasClaudeTranscript(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<boolean> {\n const sessionId = await resolveClaudeSessionId(agentId, opts);\n if (!sessionId) return false;\n const workspace = (await readRecordedState(agentId, opts)).workspace;\n if (!workspace) return false;\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n return pathExists(join(projectsRoot, encodeClaudeProjectDir(workspace), `${sessionId}.jsonl`));\n}\n\n/**\n * Resolve the Codex rollout JSONL for a codex-harness agent (PAN-1805).\n *\n * Fast path: the persisted codex-thread-id maps directly to its rollout file.\n * Lazy fallback (same shape as the conversation panel's PAN-1690 fix): codex\n * writes the rollout only on the first turn, so a spawn-time thread-id capture\n * can miss it — the per-agent CODEX_HOME holds only this agent's rollouts, so\n * the newest one is its current thread.\n */\nexport async function resolveCodexRolloutPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n const codexHome = join(agentDir, 'codex-home');\n if (!(await pathExists(codexHome))) return null;\n\n const { findRolloutPath, findLatestRollout } = await import('../../../lib/runtimes/codex.js');\n\n const threadId = (await readOptional(join(agentDir, 'codex-thread-id')))?.trim();\n if (threadId) {\n const rollout = findRolloutPath(codexHome, threadId);\n if (rollout) return rollout;\n }\n return findLatestRollout(codexHome);\n}\n\n/**\n * Resolve the pi/kimi session JSONL (PAN-1908). Pi writes its session transcript\n * as `<iso-ts>_<session-id>.jsonl` either in the agent dir's `sessions/` subdir\n * (conversations) OR in the agent dir root (work agents) — so check both. The dir\n * also holds `cost-events.jsonl` / `activity.jsonl`, which are NOT transcripts.\n * Return the freshest transcript by mtime, or null if pi hasn't written one yet.\n */\nexport async function resolvePiSessionPath(\n agentId: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n const agentsRoot = opts.agentsDirOverride ?? join(getOverdeckHome(), 'agents');\n const agentDir = join(agentsRoot, agentId);\n const NON_TRANSCRIPT = new Set(['cost-events.jsonl', 'activity.jsonl', 'pending-events.jsonl']);\n let best: { path: string; mtime: number } | null = null;\n for (const dir of [join(agentDir, 'sessions'), agentDir]) {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n continue;\n }\n for (const name of entries) {\n if (!name.endsWith('.jsonl') || NON_TRANSCRIPT.has(name)) continue;\n const p = join(dir, name);\n try {\n const m = (await stat(p)).mtimeMs;\n if (!best || m > best.mtime) best = { path: p, mtime: m };\n } catch { /* unreadable entry — skip */ }\n }\n }\n return best?.path ?? null;\n}\n\n/**\n * Resolve the JSONL transcript for an agent.\n *\n * Codex agents resolve to their rollout JSONL (PAN-1805). For claude-code\n * agents, returns the absolute path if both the claudeSessionId is known AND\n * the corresponding JSONL file exists; otherwise null.\n */\nexport async function resolveJsonlPath(\n agentId: string,\n workspacePath: string,\n opts: ResolveJsonlPathOptions = {},\n): Promise<string | null> {\n // Dispatch on the recorded harness so a stale session.id from an earlier\n // claude-code run of the same agent id can't shadow the codex transcript.\n const harness = await resolveAgentHarness(agentId, opts);\n const behavior = behaviorForHarness(harness);\n if (behavior.transcriptKind === 'codex-rollout-jsonl') {\n return resolveCodexRolloutPath(agentId, opts);\n }\n if (behavior.transcriptKind === 'ohmypi-jsonl') {\n return resolvePiSessionPath(agentId, opts);\n }\n\n const claudeSessionId = await resolveClaudeSessionId(agentId, opts);\n if (!claudeSessionId) return null;\n\n const projectsRoot = opts.claudeProjectsDirOverride ?? join(homedir(), '.claude', 'projects');\n const encodedDir = encodeClaudeProjectDir(workspacePath);\n const jsonlPath = join(projectsRoot, encodedDir, `${claudeSessionId}.jsonl`);\n if (await pathExists(jsonlPath)) return jsonlPath;\n return null;\n}\n","import { existsSync } from 'node:fs';\nimport { access } from 'node:fs/promises';\nimport { Data, Effect } from 'effect';\nimport { getAgentStateSync, getAgentState, getAgentRuntimeStateSync, getAgentRuntimeState, getLatestSessionIdSync, getLatestSessionId, normalizeAgentId } from './agents.js';\nimport { sessionExistsSync, sessionExists } from './tmux.js';\n\nexport type WorkAgentOperation = 'start' | 'resume' | 'restart_with_context' | 'reset_session';\nexport type WorkAgentRecommendedAction = 'start' | 'resume' | 'restart_with_context' | 'reset_session' | 'none';\n\nexport interface WorkAgentLifecycleState {\n agentId: string;\n hasAgentState: boolean;\n hasLiveTmuxSession: boolean;\n hasSavedSession: boolean;\n hasWorkspace: boolean;\n isPlaceholder: boolean;\n isOrphaned: boolean;\n isRunning: boolean;\n /** Agent has a live tmux session and running status, but its runtime is idle or\n * suspended — meaning the model stopped producing output (e.g. model errors).\n * The session should be restarted via resume rather than messaged via pan tell. */\n isRunningButStuck: boolean;\n isStopped: boolean;\n isCompleted: boolean;\n isCrashed: boolean;\n runtimeState: string;\n agentStatus: string;\n canStartFresh: boolean;\n /** True when the session can be resumed (stopped, crashed, or running-but-stuck).\n * Always false when `isRunning` is true — a live, active session is not resumable. */\n canResumeSession: boolean;\n canRestartWithContext: boolean;\n canResetSession: boolean;\n requiresSessionResetBeforeFreshStart: boolean;\n recommendedAction: WorkAgentRecommendedAction;\n reason?: string;\n}\n\nasync function pathExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function getWorkAgentLifecycleStateSync(agentOrIssueId: string): WorkAgentLifecycleState {\n const agentId = normalizeAgentId(agentOrIssueId);\n const agentState = getAgentStateSync(agentId);\n const runtimeState = getAgentRuntimeStateSync(agentId);\n const hasAgentState = !!agentState;\n const hasSavedSession = !!getLatestSessionIdSync(agentId);\n const hasLiveTmuxSession = sessionExistsSync(agentId);\n const hasWorkspace = !!agentState?.workspace && existsSync(agentState.workspace);\n const agentStatus = agentState?.status || 'unknown';\n const runtime = runtimeState?.state || 'uninitialized';\n const isCompleted = runtimeState?.resolution === 'completed';\n const isPlaceholder = !!agentState && agentStatus === 'starting' && typeof agentState.model === 'string' && agentState.model.startsWith('pending-');\n const isStopped = agentStatus === 'stopped' || agentStatus === 'error' || isCompleted || runtime === 'stopped' || runtime === 'idle' || runtime === 'suspended';\n const isRunning = (agentStatus === 'running' || isPlaceholder) && hasLiveTmuxSession;\n const isCrashed = (agentStatus === 'running' || isPlaceholder) && !hasLiveTmuxSession;\n // Running-but-stuck: live session + running status, but the runtime is idle or suspended\n // (e.g. the model returned errors and stopped producing output). The tmux session exists but\n // the agent is no longer making progress — it needs a resume, not a message.\n const isRunningButStuck = isRunning && (runtime === 'idle' || runtime === 'suspended');\n const hasResumableBackingState = hasAgentState && hasWorkspace && !isPlaceholder;\n const isOrphaned = !hasLiveTmuxSession && (\n (hasSavedSession && !hasResumableBackingState)\n || (hasAgentState && (!hasWorkspace || isPlaceholder))\n );\n const requiresSessionResetBeforeFreshStart = hasSavedSession && !hasLiveTmuxSession && hasResumableBackingState && (isStopped || isCrashed);\n\n let recommendedAction: WorkAgentRecommendedAction = 'start';\n let reason: string | undefined;\n\n if (isRunningButStuck) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a live session but its runtime is ${runtime} — it is no longer making progress. Use 'pan resume ${agentOrIssueId}' to restart it.`;\n } else if (hasLiveTmuxSession && agentStatus === 'running') {\n recommendedAction = 'none';\n reason = `Agent ${agentId} is already running. Use 'pan tell' to message it.`;\n } else if (hasLiveTmuxSession && isStopped) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a live tmux session but is stopped. Use 'pan resume ${agentOrIssueId}' to continue or 'pan start ${agentOrIssueId}' will kill the session and start fresh.`;\n } else if (isOrphaned) {\n recommendedAction = 'start';\n reason = hasSavedSession\n ? `Agent ${agentId} has stale/orphaned session metadata without a resumable workspace-backed agent state. Start Agent should create a fresh session.`\n : `Agent ${agentId} is an orphaned placeholder/stale record. Start Agent should create a fresh session.`;\n } else if (requiresSessionResetBeforeFreshStart) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a resumable Claude session. Use 'pan resume ${agentOrIssueId}' to continue it, or 'pan start ${agentOrIssueId} --fresh' to start a new session (e.g. to switch model).`;\n } else if (hasAgentState && !hasSavedSession && isStopped) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} is stopped and has no saved Claude session. Start Agent will create a fresh session in the existing workspace.`;\n } else if (!hasAgentState && !hasSavedSession) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} has no prior resumable session. Start Agent will create a fresh workspace-backed session.`;\n }\n\n return {\n agentId,\n hasAgentState,\n hasLiveTmuxSession,\n hasSavedSession,\n hasWorkspace,\n isPlaceholder,\n isOrphaned,\n isRunning,\n isRunningButStuck,\n isStopped,\n isCompleted,\n isCrashed,\n runtimeState: runtime,\n agentStatus,\n canStartFresh: (!hasLiveTmuxSession || (hasLiveTmuxSession && isStopped)) && (!requiresSessionResetBeforeFreshStart || isOrphaned),\n // A live, actively-running agent (isRunning=true, isRunningButStuck=false) is already in\n // session — no resume needed. Stuck agents (isRunning=true, isRunningButStuck=true) must\n // use the dedicated isRunningButStuck flag at call sites; canResumeSession stays false for\n // them so `isRunning` and `canResumeSession` are never simultaneously true.\n canResumeSession: !isRunning && hasSavedSession && hasResumableBackingState && (isStopped || isCrashed),\n canRestartWithContext: hasAgentState && hasWorkspace,\n canResetSession: hasSavedSession && hasResumableBackingState,\n requiresSessionResetBeforeFreshStart,\n recommendedAction,\n reason,\n };\n}\n\nasync function getWorkAgentLifecycleStateSnapshot(agentOrIssueId: string): Promise<WorkAgentLifecycleState> {\n const agentId = normalizeAgentId(agentOrIssueId);\n const agentState = await Effect.runPromise(getAgentState(agentId));\n const runtimeState = await Effect.runPromise(getAgentRuntimeState(agentId));\n const hasAgentState = !!agentState;\n const hasSavedSession = !!(await Effect.runPromise(getLatestSessionId(agentId)));\n const hasLiveTmuxSession = await Effect.runPromise(sessionExists(agentId));\n const hasWorkspace = !!agentState?.workspace && await pathExists(agentState.workspace);\n const agentStatus = agentState?.status || 'unknown';\n const runtime = runtimeState?.state || 'uninitialized';\n const isCompleted = runtimeState?.resolution === 'completed';\n const isPlaceholder = !!agentState && agentStatus === 'starting' && typeof agentState.model === 'string' && agentState.model.startsWith('pending-');\n const isStopped = agentStatus === 'stopped' || agentStatus === 'error' || isCompleted || runtime === 'stopped' || runtime === 'idle' || runtime === 'suspended';\n const isRunning = (agentStatus === 'running' || isPlaceholder) && hasLiveTmuxSession;\n const isCrashed = (agentStatus === 'running' || isPlaceholder) && !hasLiveTmuxSession;\n const isRunningButStuck = isRunning && (runtime === 'idle' || runtime === 'suspended');\n const hasResumableBackingState = hasAgentState && hasWorkspace && !isPlaceholder;\n const isOrphaned = !hasLiveTmuxSession && (\n (hasSavedSession && !hasResumableBackingState)\n || (hasAgentState && (!hasWorkspace || isPlaceholder))\n );\n const requiresSessionResetBeforeFreshStart = hasSavedSession && !hasLiveTmuxSession && hasResumableBackingState && (isStopped || isCrashed);\n\n let recommendedAction: WorkAgentRecommendedAction = 'start';\n let reason: string | undefined;\n\n if (isRunningButStuck) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a live session but its runtime is ${runtime} — it is no longer making progress. Use 'pan resume ${agentOrIssueId}' to restart it.`;\n } else if (hasLiveTmuxSession && agentStatus === 'running') {\n recommendedAction = 'none';\n reason = `Agent ${agentId} is already running. Use 'pan tell' to message it.`;\n } else if (hasLiveTmuxSession && isStopped) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a live tmux session but is stopped. Use 'pan resume ${agentOrIssueId}' to continue or 'pan start ${agentOrIssueId}' will kill the session and start fresh.`;\n } else if (isOrphaned) {\n recommendedAction = 'start';\n reason = hasSavedSession\n ? `Agent ${agentId} has stale/orphaned session metadata without a resumable workspace-backed agent state. Start Agent should create a fresh session.`\n : `Agent ${agentId} is an orphaned placeholder/stale record. Start Agent should create a fresh session.`;\n } else if (requiresSessionResetBeforeFreshStart) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a resumable Claude session. Use 'pan resume ${agentOrIssueId}' to continue it, or 'pan start ${agentOrIssueId} --fresh' to start a new session (e.g. to switch model).`;\n } else if (hasAgentState && !hasSavedSession && isStopped) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} is stopped and has no saved Claude session. Start Agent will create a fresh session in the existing workspace.`;\n } else if (!hasAgentState && !hasSavedSession) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} has no prior resumable session. Start Agent will create a fresh workspace-backed session.`;\n }\n\n return {\n agentId,\n hasAgentState,\n hasLiveTmuxSession,\n hasSavedSession,\n hasWorkspace,\n isPlaceholder,\n isOrphaned,\n isRunning,\n isRunningButStuck,\n isStopped,\n isCompleted,\n isCrashed,\n runtimeState: runtime,\n agentStatus,\n canStartFresh: (!hasLiveTmuxSession || (hasLiveTmuxSession && isStopped)) && (!requiresSessionResetBeforeFreshStart || isOrphaned),\n canResumeSession: !isRunning && hasResumableBackingState && (isStopped || isCrashed),\n canRestartWithContext: hasAgentState && hasWorkspace,\n canResetSession: hasSavedSession && hasResumableBackingState,\n requiresSessionResetBeforeFreshStart,\n recommendedAction,\n reason,\n };\n}\n\ninterface StartFreshOptions {\n allowPausedForce?: boolean;\n}\n\nexport function assertCanStartFreshSync(agentOrIssueId: string, options: StartFreshOptions = {}): WorkAgentLifecycleState {\n const lifecycle = getWorkAgentLifecycleStateSync(agentOrIssueId);\n const pausedForceOverride = options.allowPausedForce === true\n && lifecycle.requiresSessionResetBeforeFreshStart\n && getAgentStateSync(lifecycle.agentId)?.paused === true;\n if (!lifecycle.canStartFresh && !pausedForceOverride) {\n throw new Error(lifecycle.reason || `Cannot start fresh for ${lifecycle.agentId}`);\n }\n return lifecycle;\n}\n\nexport function assertCanResumeSessionSync(agentOrIssueId: string): WorkAgentLifecycleState {\n const lifecycle = getWorkAgentLifecycleStateSync(agentOrIssueId);\n if (!lifecycle.canResumeSession && !lifecycle.isRunningButStuck) {\n throw new Error(lifecycle.reason || `Cannot resume session for ${lifecycle.agentId}`);\n }\n return lifecycle;\n}\n\n// ─── Effect variants (PAN-1249) ───────────────────────────────────────────────\n\n/**\n * Asserts about agent lifecycle (cannot start fresh / cannot resume) fail in\n * the typed error channel as `WorkAgentLifecycleViolation`.\n */\nexport class WorkAgentLifecycleViolation extends Data.TaggedError('WorkAgentLifecycleViolation')<{\n readonly agentId: string;\n readonly reason: string;\n}> {}\n\nexport const getWorkAgentLifecycleState = (\n agentOrIssueId: string,\n): Effect.Effect<WorkAgentLifecycleState> =>\n Effect.promise(() => getWorkAgentLifecycleStateSnapshot(agentOrIssueId));\n\n/** Assert the agent can start fresh; lifts the synchronous throw to a typed error. */\nexport const assertCanStartFresh = (\n agentOrIssueId: string,\n options: { allowPausedForce?: boolean } = {},\n): Effect.Effect<WorkAgentLifecycleState, WorkAgentLifecycleViolation> =>\n Effect.try({\n try: () => assertCanStartFreshSync(agentOrIssueId, options),\n catch: (cause) =>\n new WorkAgentLifecycleViolation({\n agentId: agentOrIssueId,\n reason: cause instanceof Error ? cause.message : String(cause),\n }),\n });\n\n/** Assert the agent can resume; lifts the synchronous throw to a typed error. */\nexport const assertCanResumeSession = (\n agentOrIssueId: string,\n): Effect.Effect<WorkAgentLifecycleState, WorkAgentLifecycleViolation> =>\n Effect.try({\n try: () => assertCanResumeSessionSync(agentOrIssueId),\n catch: (cause) =>\n new WorkAgentLifecycleViolation({\n agentId: agentOrIssueId,\n reason: cause instanceof Error ? cause.message : String(cause),\n }),\n });\n","/**\n * Git info enricher (PAN-1523)\n *\n * Derives branch + worktree-status for an absolute filesystem path. Used to\n * surface branch/worktree chips on conversation rows, conversation terminals,\n * and agent terminals.\n *\n * Two enricher entrypoints with different semantics:\n * - resolveConversationGitInfo() — best-effort: returns null branch when\n * the path isn't a git repo. Used for conversations whose cwd can be\n * anywhere.\n * - resolveAgentGitInfo() — verifies a work-agent workspace against its\n * expected feature branch. Reports drift and missing-workspace state\n * because both are silent failure modes today.\n *\n * Cache: in-memory, 30s TTL, keyed by absolute path. Cache entries are\n * invalidated when the mtime of <path>/.git/HEAD changes, so a checkout\n * under a live conversation is picked up on the next list call without\n * waiting for TTL.\n *\n * All I/O is async (execFile / fs.promises). No execSync, no readFileSync.\n */\n\nimport { execFile } from 'node:child_process';\nimport { readFile, stat } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nconst execFileAsync = promisify(execFile);\n\nconst GIT_INFO_TTL_MS = 30_000;\nconst GIT_INFO_TIMEOUT_MS = 2_000;\n\nexport interface ConversationGitInfo {\n /** Current branch (HEAD), null if cwd is not a git repo. */\n branch: string | null;\n /** True when cwd points at a secondary git worktree (not the primary checkout). */\n isWorktree: boolean;\n}\n\nexport interface AgentGitInfo {\n /** Current branch (HEAD), null if the workspace path is missing or not a git repo. */\n actualBranch: string | null;\n /** True when actualBranch differs from the expected feature branch. */\n branchDrifted: boolean;\n /** True when the workspacePath does not exist on disk, or is no longer a git worktree. */\n workspaceMissing: boolean;\n}\n\ninterface CacheEntry {\n value: ConversationGitInfo;\n expiresAt: number;\n headMtimeMs: number;\n}\n\nconst cache = new Map<string, CacheEntry>();\n\n/**\n * Locate the HEAD file for a working tree at `absPath`.\n *\n * In a primary checkout `.git` is a directory and HEAD lives at `.git/HEAD`.\n * In a secondary worktree `.git` is a small text file containing\n * `gitdir: /abs/path/to/.git/worktrees/<name>` and HEAD lives at\n * `<that-gitdir>/HEAD`. We follow the pointer so the mtime check actually\n * sees branch flips in worktrees.\n */\nasync function locateHeadPath(absPath: string): Promise<string | null> {\n const gitMarker = join(absPath, '.git');\n let markerStat;\n try {\n markerStat = await stat(gitMarker);\n } catch {\n return null;\n }\n\n if (markerStat.isDirectory()) {\n return join(gitMarker, 'HEAD');\n }\n\n if (markerStat.isFile()) {\n try {\n const contents = await readFile(gitMarker, 'utf-8');\n const match = contents.match(/^gitdir:\\s*(.+)$/m);\n if (!match) return null;\n return join(match[1].trim(), 'HEAD');\n } catch {\n return null;\n }\n }\n\n return null;\n}\n\nasync function readHeadMtimeMs(absPath: string): Promise<number | null> {\n const headPath = await locateHeadPath(absPath);\n if (headPath === null) return null;\n try {\n const stats = await stat(headPath);\n return stats.mtimeMs;\n } catch {\n return null;\n }\n}\n\nasync function readGitInfoFresh(absPath: string): Promise<ConversationGitInfo> {\n const [branchResult, worktreeResult] = await Promise.all([\n execFileAsync('git', ['-C', absPath, 'rev-parse', '--abbrev-ref', 'HEAD'], {\n encoding: 'utf-8',\n timeout: GIT_INFO_TIMEOUT_MS,\n }).catch(() => null),\n execFileAsync('git', ['-C', absPath, 'rev-parse', '--git-common-dir', '--git-dir'], {\n encoding: 'utf-8',\n timeout: GIT_INFO_TIMEOUT_MS,\n }).catch(() => null),\n ]);\n\n if (!branchResult) {\n return { branch: null, isWorktree: false };\n }\n\n const branch = branchResult.stdout.trim() || null;\n\n // A secondary worktree has `git-dir` !== `git-common-dir` (worktree-specific\n // dir lives at <primary>/.git/worktrees/<name>; common dir is <primary>/.git).\n // The primary checkout has them equal.\n let isWorktree = false;\n if (worktreeResult) {\n const [commonDir, gitDir] = worktreeResult.stdout.trim().split('\\n').map((line) => line.trim());\n if (commonDir && gitDir && commonDir !== gitDir) {\n isWorktree = true;\n }\n }\n\n return { branch, isWorktree };\n}\n\n/**\n * Resolve branch + worktree status for a conversation cwd. Cached for\n * GIT_INFO_TTL_MS, invalidated on HEAD mtime change.\n */\nexport async function resolveConversationGitInfo(absPath: string): Promise<ConversationGitInfo> {\n const now = Date.now();\n const headMtimeMs = await readHeadMtimeMs(absPath);\n if (headMtimeMs === null) {\n return { branch: null, isWorktree: false };\n }\n\n const cached = cache.get(absPath);\n if (cached && cached.expiresAt > now && cached.headMtimeMs === headMtimeMs) {\n return cached.value;\n }\n\n const value = await readGitInfoFresh(absPath);\n cache.set(absPath, {\n value,\n expiresAt: now + GIT_INFO_TTL_MS,\n headMtimeMs,\n });\n return value;\n}\n\n/**\n * Resolve drift + missing-workspace state for a work-agent workspace.\n * `expectedBranch` is the deterministic feature/<issue-id> branch from\n * resource-discovery. Reuses the conversation cache for the branch read,\n * then layers drift detection on top.\n */\nexport async function resolveAgentGitInfo(\n workspacePath: string,\n expectedBranch: string,\n): Promise<AgentGitInfo> {\n const headMtimeMs = await readHeadMtimeMs(workspacePath);\n if (headMtimeMs === null) {\n return {\n actualBranch: null,\n branchDrifted: false,\n workspaceMissing: true,\n };\n }\n\n const info = await resolveConversationGitInfo(workspacePath);\n if (info.branch === null) {\n // .git/HEAD exists but rev-parse failed — corrupt or stale worktree.\n return {\n actualBranch: null,\n branchDrifted: false,\n workspaceMissing: true,\n };\n }\n\n return {\n actualBranch: info.branch,\n branchDrifted: info.branch !== expectedBranch,\n workspaceMissing: false,\n };\n}\n\n/** Test-only: reset the cache between unit tests. */\nexport function _resetGitInfoCacheForTests(): void {\n cache.clear();\n}\n","import { exec, execFile, spawn } from 'node:child_process';\nimport { timingSafeEqual } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { appendFile, mkdir, open, readFile, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect } from 'effect';\nimport { HttpServerRequest } from 'effect/unstable/http';\nimport type { AgentStatus } from '@overdeck/contracts';\n\nimport { jsonResponse } from '../../http-helpers.js';\nimport { getHeaderFromMap } from '../origin-validation.js';\nimport { getOverdeckHome } from '../../../../lib/paths.js';\nimport {\n getAgentDir,\n normalizeAgentId,\n type AgentRuntimeState,\n type AgentState,\n} from '../../../../lib/agents.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { getGitHubConfig } from '../../services/tracker-config.js';\nimport { getClosedIssueIdsForReadSource } from '../../read-model.js';\nimport { recordFeatureRegistryLifecycle } from '../../../../lib/registry/feature-registry-population.js';\nimport {\n getClaudeProjectDir as getClaudeProjectDirShared,\n getActiveSessionPath as getActiveSessionPathShared,\n getAgentWorkspace as getAgentWorkspaceShared,\n getAgentJsonlPath as getAgentJsonlPathShared,\n getPendingQuestions as getPendingQuestionsShared,\n getAgentPendingQuestions as getAgentPendingQuestionsShared,\n} from '../../../../lib/agent-enrichment.js';\nimport type { WorkAgentLifecycleState, WorkAgentRecommendedAction } from '../../../../lib/work-agent-lifecycle.js';\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { getResourceConfig, type HealthLeakedSpecialist, type SystemHealthSnapshot } from '../../services/system-health-service.js';\nimport { classifyMemoryPressure } from '../../../../lib/cloister/memory-governor.js';\nimport { capturePane } from '../../../../lib/tmux.js';\nimport type { RuntimeName } from '../../../../lib/runtimes/types.js';\n\nconst execAsync = promisify(exec);\nconst execFileAsync = promisify(execFile);\n\ntype StartAgentPhase = 'stackHealthGate' | 'guardrails' | 'spawn';\n\nexport function buildPanStartArgs(input: {\n issueId: string;\n model: string;\n harness?: RuntimeName | null;\n allowHost?: boolean;\n}): string[] {\n return [\n 'start',\n input.issueId,\n '--local',\n '--model',\n input.model,\n ...(input.harness ? ['--harness', input.harness] : []),\n ...(input.allowHost ? ['--host', '--yes'] : []),\n ];\n}\n\n/**\n * PAN-1985: detached-spawn helper for `pan <args>`, shared between the\n * standard work-spawn route and the restart-fresh route. Opens a spawn.log\n * inside the agent dir (creating the dir if missing — the dir may have been\n * just wiped), spawns `pan` detached with stdio to that log, and resolves\n * with an activity id once the child closes with code 0. Throws an Error\n * with the log contents attached on non-zero exit.\n */\nexport async function spawnPanCommandDetached(input: {\n agentSessionName: string;\n issueId: string;\n role: string;\n workspacePath: string;\n args: string[];\n cwd?: string;\n}): Promise<string> {\n const { agentSessionName, issueId, role, workspacePath, args } = input;\n const cwd = input.cwd ?? workspacePath;\n const activityId = `activity-${Date.now()}`;\n const agentDir = join(homedir(), '.overdeck', 'agents', agentSessionName);\n await mkdir(agentDir, { recursive: true });\n const spawnLogPath = join(agentDir, 'spawn.log');\n const spawnLogHandle = await open(spawnLogPath, 'a');\n const child = spawn('pan', args, {\n cwd,\n detached: true,\n stdio: ['ignore', spawnLogHandle.fd, spawnLogHandle.fd],\n });\n child.once('spawn', () => {\n void appendAgentLifecycleLog(agentSessionName, 'agent.work_spawn_process_spawned', {\n issueId,\n role,\n workspacePath,\n activityId,\n pid: child.pid,\n args,\n cwd,\n spawnLogPath,\n }).catch(() => undefined);\n });\n try {\n const result = await new Promise<{ code: number | null; signal: NodeJS.Signals | null }>((resolve, reject) => {\n child.once('error', (error) => {\n void appendAgentLifecycleLog(agentSessionName, 'agent.work_spawn_process_error', {\n issueId,\n role,\n workspacePath,\n activityId,\n error: error.message,\n args,\n cwd,\n spawnLogPath,\n }).catch(() => undefined);\n reject(error);\n });\n child.once('close', (code, signal) => {\n void appendAgentLifecycleLog(agentSessionName, 'agent.work_spawn_process_closed', {\n issueId,\n role,\n workspacePath,\n activityId,\n code,\n signal,\n args,\n cwd,\n spawnLogPath,\n }).catch(() => undefined);\n resolve({ code, signal });\n });\n });\n if (result.code !== 0) {\n const output = await readFile(spawnLogPath, 'utf-8').catch(() => '');\n const error = new Error(output.trim() || `pan ${args.join(' ')} exited with code ${result.code ?? 'null'}`);\n Object.assign(error, { activityId, spawnLogPath, code: result.code, signal: result.signal, output });\n throw error;\n }\n return activityId;\n } finally {\n await spawnLogHandle.close();\n }\n}\ntype StartAgentPhaseStatus = 'start' | 'success' | 'failure' | 'skipped';\n\nfunction emitStartAgentPhase(\n issueId: string,\n phase: StartAgentPhase,\n status: StartAgentPhaseStatus,\n reason: string,\n details: Record<string, unknown> = {},\n): void {\n const timestamp = new Date().toISOString();\n emitActivityEntrySync({\n source: 'start-agent',\n level: status === 'failure' ? 'error' : status === 'skipped' ? 'warn' : 'info',\n message: `start-agent.phase=${phase}`,\n issueId: issueId.toUpperCase(),\n details: JSON.stringify({ issueId: issueId.toUpperCase(), timestamp, phase, status, reason, ...details }),\n });\n}\n\nconst INTERNAL_TOKEN_HEADER = 'x-overdeck-internal-token';\n\nfunction constantTimeTokenEqual(provided: string | undefined, expected: string): boolean {\n if (!provided) return false;\n const providedBuffer = Buffer.from(provided, 'utf8');\n const expectedBuffer = Buffer.from(expected, 'utf8');\n if (providedBuffer.length !== expectedBuffer.length) {\n return false;\n }\n return timingSafeEqual(providedBuffer, expectedBuffer);\n}\n\nasync function readInternalTokenForRequest(): Promise<string | null> {\n const fromEnv = process.env.OVERDECK_INTERNAL_TOKEN;\n if (fromEnv && fromEnv.length > 0) return fromEnv;\n try {\n const token = (await readFile(join(getOverdeckHome(), 'internal-token'), 'utf8')).trim();\n return token.length > 0 ? token : null;\n } catch {\n return null;\n }\n}\n\nexport async function validateAgentRuntimeEventAuth(\n request: HttpServerRequest.HttpServerRequest,\n) {\n const expected = await readInternalTokenForRequest();\n if (!expected) {\n return {\n ok: false as const,\n response: jsonResponse({ success: false, error: 'internal token not configured' }, { status: 503 }),\n };\n }\n\n const provided = getHeaderFromMap(request.headers as Record<string, string | string[] | undefined>, INTERNAL_TOKEN_HEADER);\n if (constantTimeTokenEqual(provided, expected)) return { ok: true as const };\n return {\n ok: false as const,\n response: jsonResponse({ success: false, error: 'forbidden' }, { status: 403 }),\n };\n}\n\nasync function appendAgentLifecycleLog(agentId: string, event: string, details: Record<string, unknown> = {}): Promise<void> {\n const agentDir = join(homedir(), '.overdeck', 'agents', agentId);\n await mkdir(agentDir, { recursive: true });\n const logLine = JSON.stringify({\n ts: new Date().toISOString(),\n event,\n ...details,\n });\n await appendFile(join(agentDir, 'lifecycle.log'), logLine + '\\n');\n}\n\nfunction updateRegistryForAgentStart(issueId: string, workspacePath: string, agentId: string): void {\n void recordFeatureRegistryLifecycle({\n issueId,\n workspacePath,\n agentId,\n status: 'active',\n });\n}\n\n\n// ─── Shared IssueDataService singleton ───────────────────────────────────────\n\nfunction getIssueDataService(): import('../../services/issue-data-service.js').IssueDataService {\n const { getSharedIssueService } = require('../../services/issue-service-singleton.js');\n return getSharedIssueService();\n}\n\n// ─── Cache ────────────────────────────────────────────────────────────────────\n\nconst AGENTS_CACHE_TTL_MS = 5000;\nexport const agentsCache: { data: unknown[] | null; timestamp: number } = { data: null, timestamp: 0 };\n\n/** Invalidate the agents cache so the next request re-reads all agent state. */\nexport function invalidateAgentsCache(): void {\n agentsCache.data = null;\n agentsCache.timestamp = 0;\n}\n\nfunction filterClosedIssueAgents<T>(agents: T[], issues: unknown[]): T[] {\n const closedIssueIds = getClosedIssueIdsForReadSource(issues);\n if (closedIssueIds.size === 0) return agents;\n return agents.filter((agent) => {\n if (!agent || typeof agent !== 'object') return true;\n const issueId = (agent as { issueId?: unknown }).issueId;\n return typeof issueId !== 'string' || !closedIssueIds.has(issueId.toUpperCase());\n });\n}\n\n// ─── Local helpers ────────────────────────────────────────────────────────────\n\n// Read the request body as unknown JSON\nconst readJsonBody = Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const text = yield* request.text;\n try {\n return text ? (JSON.parse(text) ?? {}) : {};\n } catch {\n return {};\n }\n});\n\nfunction toAgentStatusPayload(status: AgentState['status'] | undefined): AgentStatus {\n return status === 'starting' || status === 'running' || status === 'stopped' || status === 'error'\n ? status\n : 'unknown';\n}\n\nfunction buildAgentControlEventPayload(state: AgentState, previousStatus?: AgentStatus) {\n return {\n agentId: state.id,\n issueId: state.issueId,\n status: toAgentStatusPayload(state.status),\n previousStatus,\n stoppedByUser: state.stoppedByUser === true,\n paused: state.paused === true,\n pausedReason: state.pausedReason ?? null,\n pausedAt: state.pausedAt ?? null,\n troubled: state.troubled === true,\n troubledAt: state.troubledAt ?? null,\n consecutiveFailures: state.consecutiveFailures ?? 0,\n firstFailureInRunAt: state.firstFailureInRunAt ?? null,\n lastFailureAt: state.lastFailureAt ?? null,\n lastFailureReason: state.lastFailureReason ?? null,\n lastFailureNextRetryAt: state.lastFailureNextRetryAt ?? null,\n };\n}\n\nfunction buildAgentGateFailureSnapshot(state: Partial<AgentState>) {\n return {\n stoppedByUser: state.stoppedByUser === true,\n paused: state.paused === true,\n pausedReason: state.pausedReason ?? null,\n pausedAt: state.pausedAt ?? null,\n troubled: state.troubled === true,\n troubledAt: state.troubledAt ?? null,\n consecutiveFailures: state.consecutiveFailures ?? 0,\n firstFailureInRunAt: state.firstFailureInRunAt ?? null,\n lastFailureAt: state.lastFailureAt ?? null,\n lastFailureReason: state.lastFailureReason ?? null,\n lastFailureNextRetryAt: state.lastFailureNextRetryAt ?? null,\n };\n}\n\nfunction buildStoppedAgentLifecycle(\n agentOrIssueId: string,\n state: Partial<AgentState>,\n runtimeData: Partial<AgentRuntimeState>,\n): WorkAgentLifecycleState {\n const agentId = normalizeAgentId(agentOrIssueId);\n const hasAgentState = true;\n const hasLiveTmuxSession = false;\n const hasSavedSession = !!runtimeData.claudeSessionId;\n const hasWorkspace = typeof state.workspace === 'string' && state.workspace.length > 0;\n const agentStatus = state.status || 'unknown';\n const runtime = runtimeData.state || 'uninitialized';\n const isCompleted = runtimeData.resolution === 'completed';\n const isPlaceholder = agentStatus === 'starting' && typeof state.model === 'string' && state.model.startsWith('pending-');\n const isStopped = agentStatus === 'stopped' || agentStatus === 'error' || isCompleted || runtime === 'stopped' || runtime === 'idle' || runtime === 'suspended';\n const isRunning = false;\n const isCrashed = (agentStatus === 'running' || isPlaceholder) && !hasLiveTmuxSession;\n const isRunningButStuck = false;\n const hasResumableBackingState = hasAgentState && hasWorkspace && !isPlaceholder;\n const isOrphaned = !hasLiveTmuxSession && (\n (hasSavedSession && !hasResumableBackingState)\n || (hasAgentState && (!hasWorkspace || isPlaceholder))\n );\n const requiresSessionResetBeforeFreshStart = hasSavedSession && hasResumableBackingState && (isStopped || isCrashed);\n\n let recommendedAction: WorkAgentRecommendedAction = 'start';\n let reason: string | undefined;\n\n if (isOrphaned) {\n recommendedAction = 'start';\n reason = hasSavedSession\n ? `Agent ${agentId} has stale/orphaned session metadata without a resumable workspace-backed agent state. Start Agent should create a fresh session.`\n : `Agent ${agentId} is an orphaned placeholder/stale record. Start Agent should create a fresh session.`;\n } else if (requiresSessionResetBeforeFreshStart) {\n recommendedAction = 'resume';\n reason = `Agent ${agentId} has a resumable Claude session. Use 'pan resume ${agentOrIssueId}' to continue it, or 'pan start ${agentOrIssueId} --fresh' to start a new session (e.g. to switch model).`;\n } else if (hasAgentState && !hasSavedSession && isStopped) {\n recommendedAction = 'start';\n reason = `Agent ${agentId} is stopped and has no saved Claude session. Start Agent will create a fresh session in the existing workspace.`;\n }\n\n return {\n agentId,\n hasAgentState,\n hasLiveTmuxSession,\n hasSavedSession,\n hasWorkspace,\n isPlaceholder,\n isOrphaned,\n isRunning,\n isRunningButStuck,\n isStopped,\n isCompleted,\n isCrashed,\n runtimeState: runtime,\n agentStatus,\n canStartFresh: !requiresSessionResetBeforeFreshStart || isOrphaned,\n canResumeSession: hasSavedSession && hasResumableBackingState && (isStopped || isCrashed),\n canRestartWithContext: hasAgentState && hasWorkspace,\n canResetSession: hasSavedSession && hasResumableBackingState,\n requiresSessionResetBeforeFreshStart,\n recommendedAction,\n reason,\n };\n}\n\nasync function readRemoteAgentState(agentId: string): Promise<Record<string, unknown>> {\n const remoteStateFile = join(homedir(), '.overdeck', 'agents', agentId, 'remote-state.json');\n if (!existsSync(remoteStateFile)) return {};\n try {\n return JSON.parse(await readFile(remoteStateFile, 'utf-8')) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n\nasync function captureAgentOutputBeforeKill(agentId: string): Promise<void> {\n const output = await Effect.runPromise(\n capturePane(agentId, 5000).pipe(Effect.catch(() => Effect.succeed(''))),\n );\n if (!output) return;\n\n const agentDir = getAgentDir(agentId);\n await mkdir(agentDir, { recursive: true });\n await writeFile(join(agentDir, 'output.log'), output);\n}\n\nfunction buildHostOverrideConfirmation(issueId: string): string {\n return `I understand this bypasses workspace isolation for ${issueId.toUpperCase()}`;\n}\n\nfunction getProjectPath(linearProjectId?: string, issuePrefix?: string): string {\n if (issuePrefix) {\n const issueId = `${issuePrefix}-1`;\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) return resolved.projectPath;\n const config = getGitHubConfig();\n if (config) {\n for (const { owner, repo, prefix } of config.repos) {\n const repoPrefix = prefix || repo.toUpperCase().replace(/-CLI$/, '').replace(/-/g, '');\n if (repoPrefix.toUpperCase() === issuePrefix.toUpperCase()) {\n const possiblePaths = [\n join(homedir(), 'Projects', repo),\n join(homedir(), 'Projects', repo.replace(/-cli$/, '')),\n join(homedir(), 'Projects', owner, repo),\n ];\n for (const path of possiblePaths) {\n if (existsSync(path)) return path;\n }\n }\n }\n }\n }\n return join(homedir(), 'Projects');\n}\n\nasync function getWorkspaceLocation(issueId: string): Promise<'local' | 'remote' | undefined> {\n try {\n const workspacesDir = join(homedir(), '.overdeck', 'workspaces');\n const variations = [issueId.toLowerCase(), issueId.toUpperCase(), issueId];\n for (const v of variations) {\n const yamlPath = join(workspacesDir, `${v}.yaml`);\n if (existsSync(yamlPath)) {\n const content = await readFile(yamlPath, 'utf-8');\n if (content.includes('location: remote')) return 'remote';\n return 'local';\n }\n }\n } catch {}\n return undefined;\n}\n\nasync function getGitStatusAsync(workspacePath: string): Promise<{ branch: string; uncommittedFiles: number; latestCommit: string } | null> {\n try {\n if (!existsSync(workspacePath)) return null;\n const [branchResult, uncommittedResult, commitResult] = await Promise.all([\n execAsync('git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"', { cwd: workspacePath }),\n execAsync('git status --porcelain 2>/dev/null | wc -l', { cwd: workspacePath }),\n execAsync('git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"', { cwd: workspacePath }),\n ]);\n const branch = branchResult.stdout.trim();\n const uncommitted = uncommittedResult.stdout.trim();\n const latestCommit = commitResult.stdout.trim();\n if (!branch) return null;\n return {\n branch,\n uncommittedFiles: parseInt(uncommitted) || 0,\n latestCommit: latestCommit.slice(0, 60) + (latestCommit.length > 60 ? '...' : ''),\n };\n } catch {\n return null;\n }\n}\n\ninterface SpawnGuardrailAdvisory {\n severity: 'warning' | 'critical';\n code: 'memory_pressure' | 'agent_capacity' | 'leaked_specialists';\n message: string;\n}\n\nexport interface SpawnGuardrailDecision {\n blocked: boolean;\n requiresAcknowledgement: boolean;\n status: number;\n error?: string;\n hint?: string;\n warnings: SpawnGuardrailAdvisory[];\n health: Pick<SystemHealthSnapshot, 'severity' | 'summary' | 'reasons' | 'leakedSpecialists'>;\n}\n\nfunction formatLeakedSpecialistSummary(leaked: HealthLeakedSpecialist[]): string {\n return leaked\n .slice(0, 3)\n .map((item) => `${item.name} (${item.currentIssue})`)\n .join(', ');\n}\n\nfunction resolveAgentCountEnv(varName: string, fallback: number): number {\n const raw = process.env[varName];\n if (raw == null || raw.trim() === '') return fallback;\n const parsed = Number(raw);\n return Number.isFinite(parsed) ? Math.max(1, Math.floor(parsed)) : fallback;\n}\n\nexport interface AgentStartGateDecision {\n success: false;\n blocked: true;\n skipped: true;\n error: string;\n hint: string;\n agentId: string;\n paused: boolean;\n troubled: boolean;\n}\n\nexport function evaluateAgentStartGate(\n agentId: string,\n state: Pick<AgentState, 'paused' | 'pausedReason' | 'troubled' | 'consecutiveFailures'> | null | undefined,\n): AgentStartGateDecision | null {\n if (state?.paused === true) {\n const reason = state.pausedReason ? ` (${state.pausedReason})` : '';\n return {\n success: false,\n blocked: true,\n skipped: true,\n error: `Agent ${agentId} is paused${reason}.`,\n hint: `Run pan unpause ${agentId} before starting it from the dashboard.`,\n agentId,\n paused: true,\n troubled: state.troubled === true,\n };\n }\n\n if (state?.troubled === true) {\n const failures = state.consecutiveFailures ?? 0;\n return {\n success: false,\n blocked: true,\n skipped: true,\n error: `Agent ${agentId} is troubled (${failures} failure${failures === 1 ? '' : 's'}).`,\n hint: `Investigate the crash cause, then run pan untroubled ${agentId} before starting it from the dashboard.`,\n agentId,\n paused: false,\n troubled: true,\n };\n }\n\n return null;\n}\n\nexport function hasActiveAgentGateOrRetry(\n state: Pick<AgentState, 'paused' | 'troubled' | 'lastFailureNextRetryAt'>,\n nowMs: number = Date.now(),\n): boolean {\n if (state.paused === true || state.troubled === true) return true;\n if (!state.lastFailureNextRetryAt) return false;\n const retryAtMs = Date.parse(state.lastFailureNextRetryAt);\n return Number.isFinite(retryAtMs) && retryAtMs > nowMs;\n}\n\nexport function evaluateSpawnGuardrails(health: SystemHealthSnapshot): SpawnGuardrailDecision {\n const warnings: SpawnGuardrailAdvisory[] = [];\n const availableGb = Math.round((health.summary.availableMemoryBytes / (1024 ** 3)) * 10) / 10;\n const workAgentCount = health.summary.workAgentCount;\n const leakedSpecialists = health.leakedSpecialists;\n const resourceConfig = getResourceConfig();\n const hardWorkAgentLimit = resolveAgentCountEnv('PAN_AGENT_BLOCK_COUNT', resourceConfig.agentBlockCount);\n const warnWorkAgentLimit = resolveAgentCountEnv('PAN_AGENT_WARN_COUNT', resourceConfig.agentWarnCount);\n\n const memoryBand = classifyMemoryPressure(health.summary.availableMemoryBytes, {\n warningBytes: health.thresholds.memoryAvailableWarningBytes,\n criticalBytes: health.thresholds.memoryAvailableCriticalBytes,\n });\n if (memoryBand === 'hard') {\n warnings.push({\n severity: 'critical',\n code: 'memory_pressure',\n message: `Available RAM is critically low (${availableGb} GB).`,\n });\n } else if (memoryBand === 'soft') {\n warnings.push({\n severity: 'warning',\n code: 'memory_pressure',\n message: `Available RAM is tight (${availableGb} GB).`,\n });\n }\n\n if (workAgentCount >= hardWorkAgentLimit) {\n warnings.push({\n severity: 'warning',\n code: 'agent_capacity',\n message: `Work agent count is at the configured ceiling (${workAgentCount}/${hardWorkAgentLimit}).`,\n });\n } else if (workAgentCount >= warnWorkAgentLimit) {\n warnings.push({\n severity: 'warning',\n code: 'agent_capacity',\n message: `Work agent count is high (${workAgentCount}/${hardWorkAgentLimit}).`,\n });\n }\n\n if (leakedSpecialists.length > 0) {\n warnings.push({\n severity: health.summary.availableMemoryBytes < health.thresholds.memoryAvailableCriticalBytes ? 'critical' : 'warning',\n code: 'leaked_specialists',\n message: `Leaked specialist sessions detected: ${formatLeakedSpecialistSummary(leakedSpecialists)}${leakedSpecialists.length > 3 ? `, +${leakedSpecialists.length - 3} more` : ''}.`,\n });\n }\n\n const blockingWarnings = warnings.filter((warning) => warning.severity === 'critical');\n if (blockingWarnings.length > 0) {\n const hasLeakedSpecialists = leakedSpecialists.length > 0;\n return {\n blocked: true,\n requiresAcknowledgement: false,\n status: 429,\n error: blockingWarnings[0]?.message ?? 'System health is blocking new agent spawns.',\n hint: hasLeakedSpecialists\n ? 'Clean up leaked specialist sessions first, then retry the spawn.'\n : 'Reduce memory pressure or active work-agent count before retrying.',\n warnings,\n health: {\n severity: health.severity,\n summary: health.summary,\n reasons: health.reasons,\n leakedSpecialists: health.leakedSpecialists,\n },\n };\n }\n\n return {\n blocked: false,\n requiresAcknowledgement: warnings.length > 0,\n status: warnings.length > 0 ? 409 : 200,\n hint: warnings.length > 0 ? 'Acknowledge the system health warnings before starting this agent.' : undefined,\n warnings,\n health: {\n severity: health.severity,\n summary: health.summary,\n reasons: health.reasons,\n leakedSpecialists: health.leakedSpecialists,\n },\n };\n}\n\n// Shared enrichment utilities (PAN-440) — aliases for readability\nconst getClaudeProjectDir = getClaudeProjectDirShared;\nconst getActiveSessionPath = getActiveSessionPathShared;\nconst getAgentWorkspace = getAgentWorkspaceShared;\nconst getAgentJsonlPath = getAgentJsonlPathShared;\nconst getPendingQuestions = getPendingQuestionsShared;\nconst getAgentPendingQuestions = getAgentPendingQuestionsShared;\n\nfunction flyExecCmd(vmName: string, command: string): string {\n const appName = vmName.replace(/\\/.*$/, ''); // simplified: use vmName as app name\n return `fly ssh console -a ${appName} -C ${JSON.stringify(command)}`;\n}\n\nexport {\n execAsync,\n execFileAsync,\n constantTimeTokenEqual,\n emitStartAgentPhase,\n appendAgentLifecycleLog,\n updateRegistryForAgentStart,\n getIssueDataService,\n AGENTS_CACHE_TTL_MS,\n filterClosedIssueAgents,\n readJsonBody,\n toAgentStatusPayload,\n buildAgentControlEventPayload,\n buildAgentGateFailureSnapshot,\n buildStoppedAgentLifecycle,\n readRemoteAgentState,\n captureAgentOutputBeforeKill,\n buildHostOverrideConfirmation,\n getProjectPath,\n getWorkspaceLocation,\n getGitStatusAsync,\n resolveAgentCountEnv,\n formatLeakedSpecialistSummary,\n getClaudeProjectDir,\n getActiveSessionPath,\n getAgentWorkspace,\n getAgentJsonlPath,\n getPendingQuestions,\n getAgentPendingQuestions,\n flyExecCmd,\n};\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter } from 'effect/unstable/http';\n\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n getAgentState,\n getAgentRuntimeState,\n listAgentStates,\n type AgentState,\n} from '../../../../lib/agents.js';\nimport { getReviewStatusSync } from '../../../../lib/review-status.js';\nimport { computeAgentEnrichment } from '../../../../lib/agent-enrichment.js';\nimport { normalizeAwaitingInputPrompt } from '../../../../lib/agent-input-detection.js';\nimport { getWorkAgentLifecycleState } from '../../../../lib/work-agent-lifecycle.js';\nimport { resolveAgentGitInfo } from '../../services/git-info.js';\nimport { listSessions, sessionExists } from '../../../../lib/tmux.js';\nimport {\n AGENTS_CACHE_TTL_MS,\n agentsCache,\n buildAgentGateFailureSnapshot,\n buildStoppedAgentLifecycle,\n filterClosedIssueAgents,\n getGitStatusAsync,\n getIssueDataService,\n getWorkspaceLocation,\n hasActiveAgentGateOrRetry,\n readRemoteAgentState,\n} from './shared.js';\n\n// ─── Route: GET /api/agents ───────────────────────────────────────────────────\n\nexport const getAgentsRoute = HttpRouter.add(\n 'GET',\n '/api/agents',\n httpHandler(Effect.gen(function* () {\n const now = Date.now();\n\n if (agentsCache.data && (now - agentsCache.timestamp) < AGENTS_CACHE_TTL_MS) {\n return jsonResponse(agentsCache.data);\n }\n\n const sessions = yield* listSessions();\n const sessionByName = new Map(sessions.map((session) => [session.name, session]));\n const registeredStates = listAgentStates()\n .filter((state) => state.id.startsWith('agent-') || state.id.startsWith('planning-') || state.id.startsWith('strike-'));\n\n const allAgents = (yield* Effect.promise(() => Promise.all(\n registeredStates.map(async (state) => {\n const name = state.id;\n const isPlanning = name.startsWith('planning-');\n const isStrike = name.startsWith('strike-');\n const issueId = state.issueId?.toUpperCase() ||\n (isPlanning ? name.replace('planning-', '') : isStrike ? name.replace('strike-', '') : name.replace('agent-', '')).toUpperCase();\n const session = sessionByName.get(name);\n const remoteState = await readRemoteAgentState(name);\n const isRemote = remoteState.location === 'remote';\n const runtimeData = await Effect.runPromise(getAgentRuntimeState(name));\n const startedAt = state.startedAt || (session ? new Date(session.created).toISOString() : new Date().toISOString());\n const healthFile = join(homedir(), '.overdeck', 'agents', name, 'health.json');\n let health: any = { killCount: 0 };\n if (existsSync(healthFile)) {\n try { health = { ...health, ...JSON.parse(await readFile(healthFile, 'utf-8')) }; } catch {}\n }\n\n if (state.status === 'stopped') {\n const stoppedTimestamp = state.stoppedAt || runtimeData?.lastActivity || state.lastActivity;\n const stoppedAt = stoppedTimestamp ? new Date(stoppedTimestamp) : null;\n const reviewStatus = getReviewStatusSync(issueId);\n const keepStoppedAgentVisible =\n hasActiveAgentGateOrRetry(state, now) ||\n (\n !!reviewStatus &&\n reviewStatus.mergeStatus !== 'merged' &&\n (\n !!reviewStatus.prUrl ||\n reviewStatus.readyForMerge === true ||\n reviewStatus.reviewStatus !== 'pending' ||\n reviewStatus.testStatus !== 'pending' ||\n reviewStatus.mergeStatus === 'failed'\n )\n );\n if (stoppedAt && (now - stoppedAt.getTime()) > 60 * 60 * 1000 && !keepStoppedAgentVisible) return null;\n const lifecycle = buildStoppedAgentLifecycle(name, state, runtimeData ?? {});\n const needsInput = runtimeData?.resolution === 'needs_input';\n const pendingQuestionPrompt = needsInput\n ? normalizeAwaitingInputPrompt(\n runtimeData?.waitingNotification ||\n 'Agent stopped because it needs human input or hit a blocker',\n )\n : undefined;\n const pendingQuestionReason = needsInput\n ? runtimeData?.waitingReason || 'other'\n : undefined;\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'stopped' as const,\n startedAt,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace: state.workspace || null,\n workspaceLocation: isRemote ? 'remote' : 'local',\n git: null,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasPendingQuestion: needsInput,\n pendingQuestionCount: 0,\n pendingQuestionPrompt,\n pendingQuestionReason,\n resolution: runtimeData?.resolution || 'working',\n resolutionCount: runtimeData?.resolutionCount || 0,\n hasSession: lifecycle.canResumeSession,\n lifecycle,\n ...(isRemote ? { remote: true, vmName: remoteState.vmName } : {}),\n };\n }\n\n if (state.status === 'starting') {\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'starting' as const,\n startedAt,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace: state.workspace || null,\n workspaceLocation: isRemote ? 'remote' : 'local',\n git: null,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasPendingQuestion: false,\n pendingQuestionCount: 0,\n message: (state as { message?: string }).message || 'Starting...',\n ...(isRemote ? { remote: true, vmName: remoteState.vmName } : {}),\n };\n }\n\n if (state.status === 'error') {\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'error' as const,\n startedAt,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace: state.workspace || null,\n workspaceLocation: isRemote ? 'remote' : 'local',\n git: null,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasPendingQuestion: false,\n pendingQuestionCount: 0,\n error: state.lastFailureReason || 'Unknown error',\n ...(isRemote ? { remote: true, vmName: remoteState.vmName } : {}),\n };\n }\n\n if (!session && !isRemote) {\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'unknown' as const,\n startedAt,\n lastActivity: runtimeData?.lastActivity || state.lastActivity,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace: state.workspace || null,\n workspaceLocation: 'local' as const,\n git: null,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasLiveTmuxSession: false,\n hasPendingQuestion: false,\n pendingQuestionCount: 0,\n lastFailureReason: state.lastFailureReason || 'No live tmux session found for registered agent',\n resolution: runtimeData?.resolution || 'working',\n resolutionCount: runtimeData?.resolutionCount || 0,\n };\n }\n\n const issueReviewStatus = getReviewStatusSync(issueId);\n const hasActiveSpecialist = issueReviewStatus?.reviewStatus === 'reviewing'\n || issueReviewStatus?.testStatus === 'testing'\n || issueReviewStatus?.mergeStatus === 'merging';\n const enrichment = await Effect.runPromise(computeAgentEnrichment(name, startedAt, hasActiveSpecialist));\n const workspaceLocation = isRemote ? 'remote' : await getWorkspaceLocation(issueId);\n const workspace = isRemote && remoteState.vmName\n ? `/workspace (${String(remoteState.vmName)})`\n : state.workspace || null;\n const gitStatus = workspace && !isRemote ? await getGitStatusAsync(workspace) : null;\n\n let contextPercent: number | null = null;\n let initialContextPercent: number | null = null;\n const agentCtxDir = join(homedir(), '.overdeck', 'agents', name);\n try {\n const ctxFile = join(agentCtxDir, 'context-pct');\n contextPercent = parseInt((await readFile(ctxFile, 'utf-8').catch(() => '')).trim(), 10) || null;\n const initCtxFile = join(agentCtxDir, 'initial-context-pct');\n initialContextPercent = parseInt((await readFile(initCtxFile, 'utf-8').catch(() => '')).trim(), 10) || null;\n } catch {}\n\n return {\n id: name,\n issueId,\n runtime: state.harness ?? 'claude-code',\n model: state.model || (isPlanning ? 'opus' : 'sonnet'),\n status: 'healthy' as const,\n startedAt,\n ...buildAgentGateFailureSnapshot(state),\n killCount: health.killCount || 0,\n workspace,\n workspaceLocation,\n git: gitStatus,\n type: 'agent',\n role: state.role ?? (isStrike ? 'strike' : isPlanning ? 'plan' : 'work'),\n hasLiveTmuxSession: true,\n hasPendingQuestion: enrichment.hasPendingQuestion,\n pendingQuestionCount: enrichment.pendingQuestionCount,\n pendingQuestionPrompt: enrichment.pendingQuestionPrompt,\n pendingQuestionReason: enrichment.pendingQuestionReason,\n pendingInputCount: enrichment.pendingInputCount,\n pendingInputKinds: enrichment.pendingInputKinds,\n pendingAskUserQuestion: enrichment.pendingAskUserQuestion,\n resolution: runtimeData?.resolution || enrichment.resolution || 'working',\n resolutionCount: runtimeData?.resolutionCount || enrichment.resolutionCount || 0,\n contextPercent,\n initialContextPercent,\n ...(isRemote ? { remote: true, vmName: remoteState.vmName } : {}),\n };\n }),\n ))).filter(Boolean);\n const visibleAgents = filterClosedIssueAgents(allAgents, getIssueDataService().getIssues());\n agentsCache.data = visibleAgents;\n agentsCache.timestamp = now;\n return jsonResponse(visibleAgents);\n })),\n);\n\n// ─── Route: GET /api/agents/:id/git-info ─────────────────────────────────────\n//\n// Branch + worktree status for the agent's workspace (PAN-1523). Used by\n// AgentOutputPanel to render the Local/Worktree/Drifted chip in the panel\n// header. Work agents don't have a conversation row to enrich, so the panel\n// queries this dedicated endpoint instead.\n\n/**\n * True when the agent has a concrete workspace + issue we can evaluate git\n * state for. When false, the git-info route must NOT claim the worktree is\n * missing — an unresolvable session id (e.g. a legacy / JSONL-only \"Planning\n * state\" node) or an agent that never got a workspace is \"unknown\", not\n * \"workspace gone from disk\". Conflating the two made such nodes falsely render\n * \"Worktree missing\" in the SessionPanel chip (PAN-1718).\n */\nexport function agentHasResolvableWorkspace(\n agentState: AgentState | null,\n): agentState is AgentState {\n return Boolean(agentState?.workspace && agentState.issueId);\n}\n\n/**\n * Benign git-info response for a session we cannot resolve to a workspace-bound\n * agent. workspaceMissing is false (not true): we have no path to stat, so we\n * cannot assert the worktree is gone. The frontend chip hides on this shape\n * (showChip = actualBranch || workspaceMissing). The genuine on-disk\n * \"workspace missing\" case is detected separately by resolveAgentGitInfo, which\n * stats the real path. See PAN-1718.\n */\nexport const UNRESOLVABLE_AGENT_GIT_INFO = {\n actualBranch: null,\n branchDrifted: false,\n workspaceMissing: false,\n expectedBranch: null,\n} as const;\n\nexport const getAgentGitInfoRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/git-info',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ error: 'missing agent id' }, { status: 400 });\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentHasResolvableWorkspace(agentState)) {\n // PAN-1718: unknown session id / no workspace bound → \"unknown\", not\n // \"worktree missing\". Return the benign shape so the chip hides instead of\n // flashing a false alarm. Real on-disk absence is caught by\n // resolveAgentGitInfo below.\n return jsonResponse(UNRESOLVABLE_AGENT_GIT_INFO);\n }\n\n const expectedBranch = `feature/${agentState.issueId.toLowerCase()}`;\n const info = yield* Effect.promise(() =>\n resolveAgentGitInfo(agentState.workspace as string, expectedBranch),\n );\n return jsonResponse({\n ...info,\n expectedBranch,\n workspacePath: agentState.workspace,\n });\n })),\n);\n\n// ─── Route: GET /api/agents/:id/tmux-alive ──────────────────────────────────\n\nexport const getAgentTmuxAliveRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/tmux-alive',\n Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const agentId = params['id'] ?? '';\n const alive = yield* sessionExists(agentId);\n return jsonResponse({ alive });\n }),\n);\n\n// ─── Route: GET /api/agents/:id/has-session ─────────────────────────────────\n// Returns whether a stopped agent has a resumable Claude session.\n\nexport const getAgentHasSessionRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/has-session',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const lifecycle = yield* getWorkAgentLifecycleState(id);\n return jsonResponse({\n hasSession: lifecycle.canResumeSession,\n lifecycle,\n });\n })),\n);\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport type { ConversationResponse } from '@overdeck/contracts';\nimport { Effect, Option } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { encodeClaudeProjectDir } from '../../../../lib/paths.js';\nimport {\n getActivity,\n getAgentState,\n} from '../../../../lib/agents.js';\nimport { capturePane } from '../../../../lib/tmux.js';\nimport { parseEntireConversation } from '../../services/conversation-service.js';\nimport { parsePiConversationMessages } from '../../services/pi-conversation-parser.js';\nimport { parseOhmypiConversationMessages } from '../../services/ohmypi-conversation-parser.js';\nimport { parseCodexConversationMessages } from '../../services/codex-conversation-parser.js';\nimport {\n readLauncherPinnedSessionId,\n resolvePiSessionPath,\n resolveCodexRolloutPath,\n resolveAgentHarness,\n} from '../jsonl-resolver.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n execAsync,\n getAgentJsonlPath,\n getAgentWorkspace,\n} from './shared.js';\n\n// ─── Route: GET /api/agents/:id/output ───────────────────────────────────────\n\nexport const getAgentOutputRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/output',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n const lines = Option.isSome(urlOpt) ? (urlOpt.value.searchParams.get('lines') ?? '100') : '100';\n\n return yield* Effect.promise(async () => {\n try {\n const agentStateDir = join(homedir(), '.overdeck', 'agents', id);\n const remoteStateFile = join(agentStateDir, 'remote-state.json');\n let isRemote = false;\n let vmName = '';\n\n if (existsSync(remoteStateFile)) {\n try {\n const state = JSON.parse(await readFile(remoteStateFile, 'utf-8'));\n if (state.location === 'remote' && state.vmName) {\n isRemote = true;\n vmName = state.vmName;\n }\n } catch {}\n }\n\n let stdout: string;\n if (isRemote && vmName) {\n const { getRemoteAgentOutput } = await import('../../../../lib/remote/remote-agents.js');\n stdout = await getRemoteAgentOutput(id, vmName, parseInt(String(lines), 10) || 100);\n } else {\n stdout = await Effect.runPromise(capturePane(id, parseInt(String(lines), 10) || 100));\n }\n\n if (!stdout || stdout.trim() === '' || stdout.trim() === 'Session not found') {\n const savedLog = join(agentStateDir, 'output.log');\n const logContent = await readFile(savedLog, 'utf-8').catch(() => null);\n if (logContent) {\n const logLines = logContent.split('\\n');\n const numLines = parseInt(String(lines), 10) || 100;\n stdout = logLines.slice(-numLines).join('\\n');\n }\n }\n\n if (stdout?.trim() === 'Session not found') {\n stdout = '';\n }\n\n return jsonResponse({ output: stdout });\n } catch (error: unknown) {\n // Try saved log on error\n try {\n const agentStateDir = join(homedir(), '.overdeck', 'agents', id);\n const savedLog = join(agentStateDir, 'output.log');\n const logContent = await readFile(savedLog, 'utf-8').catch(() => null);\n if (logContent) return jsonResponse({ output: logContent });\n } catch {}\n return jsonResponse({ output: '' });\n }\n })\n })),\n);\n\n// ─── Route: GET /api/agents/:id/conversation ─────────────────────────────────\n\nconst EMPTY_CONVERSATION: ConversationResponse = { messages: [], workLog: [], streaming: false, totalCost: 0, byteOffset: 0 };\n\n/**\n * Resolve and parse an agent's conversation JSONL file.\n * Exported for unit testing — the Effect route layer is not directly unit-testable.\n *\n * Dispatches on harness so Pi and Codex agents get their native parsers (PAN-2012).\n * For claude-code agents, tries the launcher-pinned --session-id first (the exact\n * session the Terminal tab attaches to) before falling back to mtime-based pick\n * (PAN-2011). This makes the Conversation tab match the Terminal tab by construction.\n */\nexport async function buildConversationResponse(id: string): Promise<ConversationResponse> {\n try {\n const harness = await resolveAgentHarness(id);\n\n if (harness === 'ohmypi') {\n const sessionFile = await resolvePiSessionPath(id);\n if (!sessionFile || !existsSync(sessionFile)) return EMPTY_CONVERSATION;\n const result = await parseOhmypiConversationMessages(sessionFile);\n return { ...result, streaming: false };\n }\n\n if (harness === 'pi') {\n const sessionFile = await resolvePiSessionPath(id);\n if (!sessionFile || !existsSync(sessionFile)) return EMPTY_CONVERSATION;\n const result = await parsePiConversationMessages(sessionFile);\n return { ...result, streaming: false };\n }\n\n if (harness === 'codex') {\n const sessionFile = await resolveCodexRolloutPath(id);\n if (!sessionFile || !existsSync(sessionFile)) return EMPTY_CONVERSATION;\n const result = await parseCodexConversationMessages(sessionFile);\n return { ...result, streaming: false };\n }\n\n // claude-code (default): try launcher-pinned session ID first (ground truth),\n // then fall back to mtime-based pick.\n let jsonlPath: string | null = null;\n const pinnedSessionId = await readLauncherPinnedSessionId(id);\n if (pinnedSessionId) {\n const workspace = await Effect.runPromise(getAgentWorkspace(id));\n if (workspace) {\n const candidate = join(\n homedir(), '.claude', 'projects',\n encodeClaudeProjectDir(workspace),\n `${pinnedSessionId}.jsonl`,\n );\n if (existsSync(candidate)) jsonlPath = candidate;\n }\n }\n if (!jsonlPath) {\n jsonlPath = await Effect.runPromise(getAgentJsonlPath(id));\n }\n\n if (!jsonlPath || !existsSync(jsonlPath)) return EMPTY_CONVERSATION;\n // parseEntireConversation, not parseConversationMessages: a single parse caps\n // at MAX_READ_BYTES (10 MB) and would drop the most recent turns of a larger\n // transcript (PAN-1989). This one-shot endpoint must return the whole file.\n const result = await parseEntireConversation(jsonlPath);\n // Force streaming: false — tmux session is dead, any \"streaming\" state is stale\n return { ...result, streaming: false };\n } catch (err) {\n console.error('[conversation] failed for', id, err);\n return EMPTY_CONVERSATION;\n }\n}\n\nexport const getAgentConversationRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/conversation',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n return yield* Effect.promise(async () => jsonResponse(await buildConversationResponse(id)));\n })),\n);\n\n// ─── Route: GET /api/agents/:id/activity ─────────────────────────────────────\n\nexport const getAgentActivityRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/activity',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n const limitStr = Option.isSome(urlOpt) ? (urlOpt.value.searchParams.get('limit') ?? '100') : '100';\n const limit = parseInt(limitStr) || 100;\n\n const activity = getActivity(id, limit);\n return jsonResponse({ activity });\n })),\n);\n\n// ─── Route: GET /api/agents/:id/files ────────────────────────────────────────\n\nexport const getAgentFilesRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/files',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const agentState = yield* getAgentState(id);\n if (!agentState?.workspace) {\n return jsonResponse({ files: [] });\n }\n const workspacePath = agentState.workspace;\n if (!existsSync(workspacePath)) {\n return jsonResponse({ files: [] });\n }\n const { stdout } = yield* Effect.promise(() => execAsync(\n 'git diff --name-status HEAD 2>/dev/null || git status --porcelain 2>/dev/null || echo \"\"',\n { cwd: workspacePath, encoding: 'utf-8' }\n ));\n const files = stdout\n .split('\\n')\n .filter(l => l.trim())\n .map(l => {\n const parts = l.trim().split(/\\s+/);\n if (parts.length >= 2) {\n return { status: parts[0], path: parts[parts.length - 1] };\n }\n return { status: '?', path: l.trim() };\n })\n .filter(f => f.path);\n return jsonResponse({ files });\n })),\n);\n\n// ─── Route: GET /api/agents/:id/timeline ─────────────────────────────────────\n\nexport const getAgentTimelineRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/timeline',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n const limitStr = Option.isSome(urlOpt) ? (urlOpt.value.searchParams.get('limit') ?? '50') : '50';\n const limit = parseInt(limitStr) || 50;\n\n const activity = getActivity(id, limit);\n const agentState = yield* getAgentState(id);\n const events = activity.map((a: any) => ({\n timestamp: a.timestamp || new Date().toISOString(),\n type: a.type || 'activity',\n message: a.message || a.content || '',\n }));\n if (agentState?.startedAt) {\n events.unshift({ timestamp: agentState.startedAt, type: 'started', message: 'Agent started' });\n }\n events.sort((a: any, b: any) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());\n return jsonResponse({ timeline: events.slice(0, limit) });\n })),\n);\n","import { existsSync } from 'node:fs';\nimport { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { messageAgent } from '../../../../lib/agents.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport { readJsonBody } from './shared.js';\n\nasync function sendAgentMessage(id: string, message: string) {\n const agentStateDir = join(homedir(), '.overdeck', 'agents', id);\n const remoteStateFile = join(agentStateDir, 'remote-state.json');\n let isRemote = false;\n\n if (existsSync(remoteStateFile)) {\n try {\n const state = JSON.parse(await readFile(remoteStateFile, 'utf-8'));\n isRemote = state.location === 'remote' && Boolean(state.vmName);\n } catch {}\n }\n\n await messageAgent(id, message, 'dashboard:user-message');\n return isRemote ? { success: true, remote: true } : { success: true };\n}\n\nexport function validateAgentMessageOrigin(request: HttpServerRequest.HttpServerRequest) {\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return {\n ok: false as const,\n response: jsonResponse({ ok: false, error: originCheck.error }, { status: 403 }),\n };\n }\n return { ok: true as const };\n}\n\nfunction postAgentMessageLikeRoute(path: `/${string}`) {\n return HttpRouter.add(\n 'POST',\n path,\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateAgentMessageOrigin(request);\n if (!originCheck.ok) return originCheck.response;\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { message } = body as any;\n if (!message) {\n return jsonResponse({ error: 'Message required' }, { status: 400 });\n }\n\n return yield* Effect.promise(() => sendAgentMessage(id, message)).pipe(\n Effect.map((result) => jsonResponse(result)),\n );\n })),\n );\n}\n\n// ─── Route: POST /api/agents/:id/message ─────────────────────────────────────\n\nexport const postAgentMessageRoute = postAgentMessageLikeRoute('/api/agents/:id/message');\n\n// ─── Route: POST /api/agents/:id/tell ────────────────────────────────────────\n\nexport const postAgentTellRoute = postAgentMessageLikeRoute('/api/agents/:id/tell');\n\n// ─── Route: POST /api/agents/:id/poke ────────────────────────────────────────\n\nexport const postAgentPokeRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/poke',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { message } = body as any;\n const defaultPokeMessage =\n \"You seem to have been inactive for a while. If you're stuck:\\n\" +\n '1. Check your current task in continue.vbrief.json\\n' +\n '2. Try an alternative approach if blocked\\n' +\n '3. Ask for help if needed\\n\\n' +\n \"What's your current status?\";\n const pokeMsg = message || defaultPokeMessage;\n yield* Effect.promise(() => messageAgent(id, pokeMsg));\n return jsonResponse({ success: true, message: 'Agent poked successfully' });\n })),\n);\n","import { Data, Effect } from 'effect';\n\nexport const CHANNEL_PERMISSION_LIMITS = {\n requestIdBytes: 128,\n toolNameBytes: 128,\n descriptionBytes: 2 * 1024,\n inputPreviewBytes: 16 * 1024,\n} as const;\n\nexport interface NormalizedChannelPermissionRequestFields {\n requestId: string;\n toolName: string;\n description: string;\n inputPreview: string;\n}\n\n/** A permission request field failed validation. */\nexport class PermissionPayloadValidationError extends Data.TaggedError(\n 'PermissionPayloadValidationError',\n)<{\n readonly field: string;\n readonly message: string;\n}> {}\n\nexport function utf8ByteLength(value: string): number {\n return Buffer.byteLength(value, 'utf8');\n}\n\nexport function normalizePermissionInputPreview(value: unknown): string {\n return typeof value === 'string' ? value : '';\n}\n\nexport function normalizeChannelPermissionRequestFields(input: {\n requestId: unknown;\n toolName: unknown;\n description: unknown;\n inputPreview?: unknown;\n}): Effect.Effect<NormalizedChannelPermissionRequestFields, PermissionPayloadValidationError> {\n return Effect.gen(function* () {\n const requestId = typeof input.requestId === 'string' ? input.requestId.trim() : '';\n if (!requestId) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({ field: 'requestId', message: 'requestId is required' }),\n );\n }\n if (utf8ByteLength(requestId) > CHANNEL_PERMISSION_LIMITS.requestIdBytes) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'requestId',\n message: `requestId exceeds ${CHANNEL_PERMISSION_LIMITS.requestIdBytes} bytes`,\n }),\n );\n }\n\n const toolName = typeof input.toolName === 'string' ? input.toolName.trim() : '';\n if (!toolName) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({ field: 'toolName', message: 'toolName is required' }),\n );\n }\n if (utf8ByteLength(toolName) > CHANNEL_PERMISSION_LIMITS.toolNameBytes) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'toolName',\n message: `toolName exceeds ${CHANNEL_PERMISSION_LIMITS.toolNameBytes} bytes`,\n }),\n );\n }\n\n const description = typeof input.description === 'string' ? input.description.trim() : '';\n if (!description) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'description',\n message: 'description is required',\n }),\n );\n }\n if (utf8ByteLength(description) > CHANNEL_PERMISSION_LIMITS.descriptionBytes) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'description',\n message: `description exceeds ${CHANNEL_PERMISSION_LIMITS.descriptionBytes} bytes`,\n }),\n );\n }\n\n const inputPreview = normalizePermissionInputPreview(input.inputPreview);\n if (utf8ByteLength(inputPreview) > CHANNEL_PERMISSION_LIMITS.inputPreviewBytes) {\n return yield* Effect.fail(\n new PermissionPayloadValidationError({\n field: 'inputPreview',\n message: `inputPreview exceeds ${CHANNEL_PERMISSION_LIMITS.inputPreviewBytes} bytes`,\n }),\n );\n }\n\n return { requestId, toolName, description, inputPreview };\n });\n}\n","import type { ChannelPermissionRequestSnapshot, ResolvedChannelPermissionDecision as ContractsResolvedChannelPermissionDecision } from '@overdeck/contracts';\nimport { Effect } from 'effect';\nimport * as Result from 'effect/Result';\n\nimport {\n normalizeChannelPermissionRequestFields,\n type NormalizedChannelPermissionRequestFields,\n} from '../../../lib/channels/permission-payload.js';\n\nconst MAX_ACTIVITY_PREVIEW_CHARS = 2048;\n\nexport type ResolvedChannelPermissionDecision = ContractsResolvedChannelPermissionDecision;\n\nexport function buildPermissionWaitingMessage(toolName: string, description: string): string {\n return `Waiting for permission: ${toolName} — ${description}`;\n}\n\nexport function permissionResolutionVerb(behavior: 'allow' | 'deny'): 'allowed' | 'denied' {\n return behavior === 'allow' ? 'allowed' : 'denied';\n}\n\nexport function normalizePermissionRequestBody(body: Record<string, unknown>):\n | { ok: true; value: NormalizedChannelPermissionRequestFields }\n | { ok: false; error: string } {\n const result = Effect.runSync(\n Effect.result(\n normalizeChannelPermissionRequestFields({\n requestId: body['requestId'],\n toolName: body['toolName'],\n description: body['description'],\n inputPreview: body['inputPreview'],\n }),\n ),\n );\n if (Result.isFailure(result)) {\n return { ok: false, error: result.failure.message };\n }\n return { ok: true, value: result.success };\n}\n\nexport function parsePermissionResponseBehavior(body: Record<string, unknown>):\n | { ok: true; value: 'allow' | 'deny' }\n | { ok: false; error: string } {\n const behavior = body['behavior'];\n if (behavior !== 'allow' && behavior !== 'deny') {\n return { ok: false, error: 'behavior must be allow or deny' };\n }\n return { ok: true, value: behavior };\n}\n\nexport function redactPermissionInputPreview(inputPreview: string): string {\n let redacted = inputPreview;\n redacted = redacted.replace(\n /(((?:[\"']?(?:api[_-]?key|token|secret|password|passwd)[\"']?)\\s*[:=]\\s*[\"']?))([^\\s,'\"`]+)([\"']?)/gi,\n (_match, prefix: string, _quotedPrefix: string, _value: string, suffix: string) => `${prefix}[REDACTED]${suffix}`,\n );\n redacted = redacted.replace(\n /(Bearer\\s+)([A-Za-z0-9._~+/=-]+)/g,\n (_match, prefix: string) => `${prefix}[REDACTED]`,\n );\n redacted = redacted.replace(\n /(?:ghp|gho|ghu|ghs|github_pat)_[A-Za-z0-9_]+/g,\n '[REDACTED]',\n );\n redacted = redacted.replace(\n /(?:sk|rk)-[A-Za-z0-9]+/g,\n '[REDACTED]',\n );\n if (redacted.length <= MAX_ACTIVITY_PREVIEW_CHARS) {\n return redacted;\n }\n return `${redacted.slice(0, MAX_ACTIVITY_PREVIEW_CHARS)}…[truncated]`;\n}\n\nexport function buildPermissionActivityDetails(description: string, inputPreview: string): string {\n const safePreview = redactPermissionInputPreview(inputPreview);\n return safePreview.trim().length > 0\n ? `${description}\\n\\nInput preview:\\n${safePreview}`\n : description;\n}\n\nexport interface ProcessPermissionResponseDeps {\n getPendingRequest: (requestId: string) => Promise<ChannelPermissionRequestSnapshot | null>;\n getResolvedDecision: (requestId: string) => Promise<ResolvedChannelPermissionDecision | null>;\n appendResolutionEvents: (\n request: ChannelPermissionRequestSnapshot,\n behavior: 'allow' | 'deny',\n ) => Promise<void>;\n deliverDecision: (\n agentId: string,\n requestId: string,\n behavior: 'allow' | 'deny',\n ) => Promise<void>;\n emitResolvedActivity: (request: ChannelPermissionRequestSnapshot, behavior: 'allow' | 'deny') => void;\n}\n\nexport interface ProcessPermissionResponseResult {\n status: number;\n body: { ok: boolean; error?: string; duplicate?: boolean };\n}\n\nexport async function processPermissionResponse(\n deps: ProcessPermissionResponseDeps,\n args: {\n agentId: string;\n requestId: string;\n behavior: 'allow' | 'deny';\n },\n): Promise<ProcessPermissionResponseResult> {\n const pendingRequest = await deps.getPendingRequest(args.requestId);\n if (pendingRequest) {\n if (pendingRequest.agentId !== args.agentId) {\n return {\n status: 409,\n body: {\n ok: false,\n error: `permission request ${args.requestId} belongs to ${pendingRequest.agentId}`,\n },\n };\n }\n\n try {\n await deps.appendResolutionEvents(pendingRequest, args.behavior);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n status: 500,\n body: { ok: false, error: `failed to persist permission decision: ${msg}` },\n };\n }\n\n try {\n await deps.deliverDecision(args.agentId, args.requestId, args.behavior);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n status: 502,\n body: { ok: false, error: `failed to deliver permission decision: ${msg}` },\n };\n }\n\n deps.emitResolvedActivity(pendingRequest, args.behavior);\n return { status: 200, body: { ok: true } };\n }\n\n const resolvedDecision = await deps.getResolvedDecision(args.requestId);\n if (!resolvedDecision) {\n return {\n status: 404,\n body: { ok: false, error: `permission request ${args.requestId} not found` },\n };\n }\n if (resolvedDecision.agentId !== args.agentId) {\n return {\n status: 409,\n body: {\n ok: false,\n error: `permission request ${args.requestId} belongs to ${resolvedDecision.agentId}`,\n },\n };\n }\n if (resolvedDecision.behavior !== args.behavior) {\n return {\n status: 409,\n body: {\n ok: false,\n error: `permission request ${args.requestId} was already ${resolvedDecision.behavior}`,\n },\n };\n }\n\n try {\n await deps.deliverDecision(args.agentId, args.requestId, args.behavior);\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return {\n status: 502,\n body: { ok: false, error: `failed to deliver permission decision: ${msg}` },\n };\n }\n\n return { status: 200, body: { ok: true, duplicate: true } };\n}\n","import { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport {\n deliverAgentMessage,\n deliverAgentPermissionDecision,\n getAgentRuntimeState,\n getAgentState,\n} from '../../../../lib/agents.js';\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { ReadModelService } from '../../read-model.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport {\n buildPermissionActivityDetails,\n buildPermissionWaitingMessage,\n normalizePermissionRequestBody,\n parsePermissionResponseBehavior,\n permissionResolutionVerb,\n processPermissionResponse,\n} from '../agent-permissions.js';\nimport {\n constantTimeTokenEqual,\n getAgentPendingQuestions,\n readJsonBody,\n} from './shared.js';\n\n// ─── Route: GET /api/agents/:id/pending-questions ────────────────────────────\n\nexport const getAgentPendingQuestionsRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/pending-questions',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const questions = yield* getAgentPendingQuestions(id);\n return jsonResponse({ pending: questions.length > 0, questions });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/answer-question (PAN-1520) ──────────────────\n//\n// Operator answer for an AskUserQuestion the agent is blocked on. The Phase 1\n// hook (sync-sources/hooks/ask-user-question-hook) denies the upstream tool\n// call to prevent silent corruption (upstream returns option #1 under\n// --dangerously-skip-permissions), so by the time this endpoint is hit the\n// agent has restated the question as plain text and is waiting on a normal\n// user message. We compose that user message from the chosen option labels\n// and deliver it through the standard message pipeline.\n//\n// Body: { answers: string[] } — one chosen-option label per question.\n\nexport const postAgentAnswerQuestionRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/answer-question',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ error: 'missing agent id' }, { status: 400 });\n }\n const body = (yield* readJsonBody) as Record<string, unknown>;\n\n const answers = body['answers'];\n if (!Array.isArray(answers) || answers.length === 0) {\n return jsonResponse({ error: 'answers array required' }, { status: 400 });\n }\n if (!answers.every((a): a is string => typeof a === 'string' && a.length > 0)) {\n return jsonResponse({ error: 'every answer must be a non-empty string' }, { status: 400 });\n }\n\n const pendingQuestions = yield* getAgentPendingQuestions(id);\n if (pendingQuestions.length === 0) {\n return jsonResponse({ error: 'No pending questions found for this agent' }, { status: 404 });\n }\n\n const questionSet = pendingQuestions[0];\n const questions = questionSet.questions;\n const lines: string[] = [];\n for (let i = 0; i < answers.length && i < questions.length; i++) {\n const q = questions[i].question ?? `Question ${i + 1}`;\n lines.push(`Q: ${q}\\nA: ${answers[i]}`);\n }\n const message = `Operator answered the pending question${answers.length > 1 ? 's' : ''}:\\n\\n${lines.join('\\n\\n')}`;\n\n yield* Effect.promise(() => deliverAgentMessage(id, message, 'ask-user-question-answer'));\n return jsonResponse({ success: true, agentId: id, delivered: answers.length });\n })),\n);\n\nexport const postInternalAgentPermissionRequestRoute = HttpRouter.add(\n 'POST',\n '/api/internal/agents/:id/permissions/request',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ ok: false, error: 'missing agent id' }, { status: 400 });\n }\n\n const request = yield* HttpServerRequest.HttpServerRequest;\n const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() =>\n import('../../../../lib/internal-token.js'),\n );\n const expected = getInternalTokenSync();\n if (!expected) {\n return jsonResponse({ ok: false, error: 'internal token not configured' }, { status: 503 });\n }\n const headers = request.headers as Record<string, string | string[] | undefined>;\n const rawHeader = headers[INTERNAL_TOKEN_HEADER];\n const provided = Array.isArray(rawHeader) ? rawHeader[0] : rawHeader;\n if (!constantTimeTokenEqual(provided, expected)) {\n return jsonResponse({ ok: false, error: 'forbidden' }, { status: 403 });\n }\n\n const body = (yield* readJsonBody) as Record<string, unknown>;\n const normalized = normalizePermissionRequestBody(body);\n if (!normalized.ok) {\n return jsonResponse({ ok: false, error: normalized.error }, { status: 400 });\n }\n const { requestId, toolName, description, inputPreview } = normalized.value;\n\n const readModel = yield* ReadModelService;\n const existing = yield* readModel.getChannelPermissionRequest(requestId);\n if (existing) {\n if (existing.agentId !== id) {\n return jsonResponse({ ok: false, error: `request ${requestId} already belongs to ${existing.agentId}` }, { status: 409 });\n }\n return jsonResponse({ ok: true, duplicate: true });\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ ok: false, error: `agent ${id} not found` }, { status: 404 });\n }\n const runtimeState = yield* getAgentRuntimeState(id);\n const issueId = runtimeState?.currentIssue ?? agentState.issueId;\n\n const eventStore = yield* EventStoreService;\n const timestamp = new Date().toISOString();\n yield* eventStore.append({\n type: 'agent.permission_requested',\n timestamp,\n payload: {\n requestId,\n agentId: id,\n issueId,\n toolName,\n description,\n inputPreview,\n createdAt: timestamp,\n },\n } as never);\n yield* eventStore.append({\n type: 'agent.waiting_started',\n timestamp,\n payload: {\n agentId: id,\n reason: 'tool_permission',\n message: buildPermissionWaitingMessage(toolName, description),\n },\n } as never);\n\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'warn',\n message: `Permission requested for ${toolName}`,\n details: buildPermissionActivityDetails(description, inputPreview),\n issueId,\n });\n\n return jsonResponse({ ok: true });\n })),\n);\n\nexport const postAgentPermissionResponseRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/permissions/:requestId/respond',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const requestId = params['requestId'] ?? '';\n if (!id.trim() || !requestId.trim()) {\n return jsonResponse({ ok: false, error: 'missing agent id or request id' }, { status: 400 });\n }\n\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const body = (yield* readJsonBody) as Record<string, unknown>;\n const behaviorResult = parsePermissionResponseBehavior(body);\n if (!behaviorResult.ok) {\n return jsonResponse({ ok: false, error: behaviorResult.error }, { status: 400 });\n }\n const behavior = behaviorResult.value;\n\n const readModel = yield* ReadModelService;\n const eventStore = yield* EventStoreService;\n const result = yield* Effect.promise(() => processPermissionResponse(\n {\n getPendingRequest: (permissionRequestId) =>\n Effect.runPromise(readModel.getChannelPermissionRequest(permissionRequestId)),\n getResolvedDecision: (permissionRequestId) =>\n Effect.runPromise(readModel.getResolvedChannelPermissionDecision(permissionRequestId)),\n appendResolutionEvents: async (pendingRequest, decisionBehavior) => {\n const timestamp = new Date().toISOString();\n await Effect.runPromise(eventStore.append({\n type: 'agent.permission_resolved',\n timestamp,\n payload: {\n requestId: pendingRequest.requestId,\n agentId: pendingRequest.agentId,\n issueId: pendingRequest.issueId,\n behavior: decisionBehavior,\n },\n } as never));\n await Effect.runPromise(eventStore.append({\n type: 'agent.waiting_cleared',\n timestamp,\n payload: {\n agentId: pendingRequest.agentId,\n clearedBy: 'tool_resumed',\n },\n } as never));\n },\n deliverDecision: (agentId, permissionRequestId, decisionBehavior) =>\n deliverAgentPermissionDecision(agentId, permissionRequestId, decisionBehavior),\n emitResolvedActivity: (pendingRequest, decisionBehavior) => {\n emitActivityEntrySync({\n source: 'dashboard',\n level: decisionBehavior === 'allow' ? 'success' : 'warn',\n message: `Permission ${permissionResolutionVerb(decisionBehavior)} for ${pendingRequest.toolName}`,\n details: buildPermissionActivityDetails(\n pendingRequest.description,\n pendingRequest.inputPreview,\n ),\n issueId: pendingRequest.issueId,\n });\n },\n },\n {\n agentId: id,\n requestId,\n behavior,\n },\n ));\n\n return jsonResponse(result.body, { status: result.status });\n })),\n);\n","/**\n * Agent event utilities — shared between HTTP route handlers and background\n * pollers (PAN-1134). Extracted from routes/agents.ts so Pi event polling\n * can reuse bodyToEvent without importing a heavy route module.\n */\n\nimport { Schema } from 'effect';\nimport {\n DomainEvent,\n normalizeChannelReplyPayload,\n} from '@overdeck/contracts';\n\n/**\n * Translate a decoded body into an unsigned DomainEvent (no sequence yet).\n * Returns null if the body doesn't map to a runtime event.\n */\nexport const bodyToEvent = (\n agentId: string,\n body: Record<string, unknown>,\n timestamp: string,\n): Record<string, unknown> | null => {\n const source = body;\n if (typeof source['kind'] !== 'string') return null;\n const kind = source['kind'] as string;\n switch (kind) {\n case 'activity':\n return {\n type: 'agent.activity_changed',\n timestamp,\n payload: {\n agentId,\n activity: source['activity'],\n currentTool: source['tool'] as string | undefined,\n },\n };\n case 'thinking_start':\n return {\n type: 'agent.thinking_started',\n timestamp,\n payload: {\n agentId,\n lastToolAt: (source['lastToolAt'] as string) ?? timestamp,\n },\n };\n case 'thinking_stop':\n return {\n type: 'agent.thinking_stopped',\n timestamp,\n payload: {\n agentId,\n resolvedBy: source['resolvedBy'] ?? 'tool',\n },\n };\n case 'waiting_start':\n return {\n type: 'agent.waiting_started',\n timestamp,\n payload: {\n agentId,\n reason: source['reason'] ?? 'other',\n message: source['message'] as string | undefined,\n },\n };\n case 'waiting_clear':\n return {\n type: 'agent.waiting_cleared',\n timestamp,\n payload: {\n agentId,\n clearedBy: source['clearedBy'] ?? 'user_response',\n },\n };\n case 'message_received':\n return {\n type: 'agent.message_received',\n timestamp,\n payload: {\n agentId,\n direction: source['direction'] ?? 'to_agent',\n source: source['source'] ?? 'user',\n },\n };\n case 'channel_reply': {\n const reply = normalizeChannelReplyPayload(source['reply'], 'reply');\n return {\n type: 'agent.channel_reply',\n timestamp,\n payload: {\n agentId,\n reply: {\n ...reply,\n reportedAt: timestamp,\n },\n },\n };\n }\n case 'model_set':\n return {\n type: 'agent.model_set',\n timestamp,\n payload: {\n agentId,\n model: source['model'],\n claudeSessionId: source['claudeSessionId'] as string | undefined,\n sessionModel: source['sessionModel'] as string | undefined,\n sessionHarness: source['sessionHarness'] as string | undefined,\n },\n };\n case 'cost-event': {\n const usage = source['usage'] && typeof source['usage'] === 'object'\n ? source['usage'] as Record<string, unknown>\n : {};\n return {\n type: 'cost.event_recorded',\n timestamp,\n payload: {\n agentId,\n issueId: typeof source['issueId'] === 'string' && source['issueId'] ? source['issueId'] : 'UNKNOWN-0',\n cost: typeof source['costUsd'] === 'number' ? source['costUsd'] : 0,\n inputTokens: typeof usage['inputTokens'] === 'number' ? usage['inputTokens'] : 0,\n outputTokens: typeof usage['outputTokens'] === 'number' ? usage['outputTokens'] : 0,\n },\n };\n }\n case 'resolution_set':\n return {\n type: 'agent.resolution_changed',\n timestamp,\n payload: {\n agentId,\n resolution: source['resolution'],\n resolutionCount: Number(source['resolutionCount'] ?? 1),\n },\n };\n case 'current_issue_set':\n return {\n type: 'agent.current_issue_set',\n timestamp,\n payload: {\n agentId,\n currentIssue: source['currentIssue'] as string | undefined,\n },\n };\n case 'context_saturation_changed':\n return {\n type: 'agent.context_saturation_changed',\n timestamp,\n payload: {\n agentId,\n contextSaturatedAt: source['contextSaturatedAt'] as string | undefined,\n },\n };\n default:\n return null;\n }\n};\n\n// Runtime-event decoder. We validate the assembled event (with a placeholder\n// sequence) against the DomainEvent union — bad payloads are rejected rather\n// than silently corrupting the AgentRuntimeSnapshot.\nexport const decodeDomainEvent = Schema.decodeUnknownResult(DomainEvent);\n","import { Effect, Option } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { bodyToEvent, decodeDomainEvent } from '../../services/agent-event-utils.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n readJsonBody,\n validateAgentRuntimeEventAuth,\n} from './shared.js';\n\n// ─── Route: GET /api/agents/:id/health-history ───────────────────────────────\n\nexport const getAgentHealthHistoryRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/health-history',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n const hours = Option.isSome(urlOpt) ? (urlOpt.value.searchParams.get('hours') ?? '24') : '24';\n\n const { getHealthHistory } = yield* Effect.promise(() => import('../../../../lib/overdeck/health-events.js'));\n const endTime = new Date();\n const startTime = new Date(endTime.getTime() - parseInt(hours) * 60 * 60 * 1000);\n const events = getHealthHistory(id, startTime.toISOString(), endTime.toISOString());\n return jsonResponse({\n agentId: id,\n startTime: startTime.toISOString(),\n endTime: endTime.toISOString(),\n events,\n });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/heartbeat (PAN-800 ingestion) ──────────────\n//\n// Typed event ingestion for agent runtime state. Hooks POST a Schema-validated\n// body describing a single runtime transition; the handler translates to an\n// agent.* DomainEvent and hands it to AgentStateService.emit (which durably\n// appendAsyncs via EventStore).\n//\n// Body shape (discriminated by `kind`):\n// {kind: \"activity\", activity, tool?}\n// {kind: \"thinking_start\", lastToolAt}\n// {kind: \"thinking_stop\", resolvedBy}\n// {kind: \"waiting_start\", reason, message?}\n// {kind: \"waiting_clear\", clearedBy}\n// {kind: \"message_received\", direction, source}\n// {kind: \"model_set\", model, claudeSessionId?}\n// {kind: \"resolution_set\", resolution, resolutionCount}\n// {kind: \"current_issue_set\", currentIssue?}\n// {kind: \"context_saturation_changed\", contextSaturatedAt?}\n\nfunction emitAgentRuntimeEvent(id: string, body: Record<string, unknown>, timestamp: string) {\n return Effect.gen(function* () {\n let raw: Record<string, unknown> | null;\n try {\n raw = bodyToEvent(id, body, timestamp);\n } catch (error) {\n const message = error instanceof Error ? error.message : 'invalid heartbeat payload';\n return { ok: false as const, response: jsonResponse({ success: false, error: message }, { status: 400 }) };\n }\n if (!raw) {\n return { ok: true as const, emitted: false };\n }\n\n const candidate = { ...raw, sequence: 0 };\n const decoded = decodeDomainEvent(candidate);\n if (decoded._tag === 'Failure') {\n return {\n ok: false as const,\n response: jsonResponse(\n { success: false, error: 'invalid event', detail: String(decoded.failure) },\n { status: 400 },\n ),\n };\n }\n\n const { AgentStateService } = yield* Effect.promise(\n () => import('../../services/agent-state-service.js'),\n );\n const agentState = yield* AgentStateService;\n yield* agentState.emit(decoded.success as never);\n return { ok: true as const, emitted: true };\n });\n}\n\nexport const postAgentHeartbeatRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/heartbeat',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const auth = yield* Effect.promise(() => validateAgentRuntimeEventAuth(request));\n if (!auth.ok) return auth.response;\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ success: false, error: 'missing agent id' }, { status: 400 });\n }\n const body = (yield* readJsonBody) as Record<string, unknown>;\n const timestamp = (body['timestamp'] as string) ?? new Date().toISOString();\n const result = yield* emitAgentRuntimeEvent(id, body, timestamp);\n if (!result.ok) return result.response;\n return jsonResponse({ success: true, emitted: result.emitted });\n })),\n);\n\nexport const postAgentWorkCompleteRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/work-complete',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const auth = yield* Effect.promise(() => validateAgentRuntimeEventAuth(request));\n if (!auth.ok) return auth.response;\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) return jsonResponse({ success: false, error: 'missing agent id' }, { status: 400 });\n const result = yield* emitAgentRuntimeEvent(id, { kind: 'resolution_set', resolution: 'done', resolutionCount: 1 }, new Date().toISOString());\n if (!result.ok) return result.response;\n return jsonResponse({ success: true, emitted: result.emitted });\n })),\n);\n\nexport const postAgentStuckRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/stuck',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const auth = yield* Effect.promise(() => validateAgentRuntimeEventAuth(request));\n if (!auth.ok) return auth.response;\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) return jsonResponse({ success: false, error: 'missing agent id' }, { status: 400 });\n const result = yield* emitAgentRuntimeEvent(id, { kind: 'resolution_set', resolution: 'stuck', resolutionCount: 1 }, new Date().toISOString());\n if (!result.ok) return result.response;\n return jsonResponse({ success: true, emitted: result.emitted });\n })),\n);\n\nfunction hasNegatedCompletionOutput(output: string): boolean {\n return /\\b(not|never|no|cannot|can't|blocked|waiting|needs input|not yet|isn't|aren't)\\b.{0,48}\\b(implementation complete|all beads closed|ready for review|work complete)\\b/i.test(output);\n}\n\nexport const postAgentClassifyCompletionRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/classify-completion',\n httpHandler(Effect.gen(function* () {\n const body = (yield* readJsonBody) as Record<string, unknown>;\n const output = typeof body['output'] === 'string' ? body['output'] : '';\n const verdict = /blocked|needs input|waiting for|not ready for review/i.test(output) || hasNegatedCompletionOutput(output)\n ? 'STOPPED_FOR_INPUT'\n : /Implementation complete|all beads closed|ready for review|work complete/i.test(output)\n ? 'FORGOT_COMPLETION'\n : 'UNCLEAR';\n return jsonResponse({ success: true, verdict });\n })),\n);\n\n// ─── Route: GET /api/agents/:id/runtime (PAN-800) ────────────────────────────\n// Exposes AgentRuntimeSnapshot to out-of-process readers (CLI, tests).\n\nexport const getAgentRuntimeRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/runtime',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n if (!id.trim()) {\n return jsonResponse({ success: false, error: 'missing agent id' }, { status: 400 });\n }\n const { AgentStateService } = yield* Effect.promise(\n () => import('../../services/agent-state-service.js'),\n );\n const agentState = yield* AgentStateService;\n const snapshot = yield* agentState.get(id);\n if (!snapshot) {\n return jsonResponse({ success: false, error: 'not found' }, { status: 404 });\n }\n return jsonResponse({ success: true, snapshot });\n })),\n);\n","/**\n * Agent lifecycle projection — write-through transactional boundary (PAN-1908)\n *\n * Every agent lifecycle transition writes the rollback source (state.json)\n * and then commits the authoritative row upsert + event append inside one\n * SQLite transaction. This replaces save-then-append, which a crash could\n * leave out of sync.\n *\n * The transaction runs on the shared overdeck.db connection (PAN-1938). After\n * commit, the persisted event is emitted to the event store's subscribers so\n * the in-memory read model stays current.\n */\n\nimport { Effect } from 'effect';\nimport type { SqliteDatabase } from '../../../lib/database/driver.js';\nimport { getOverdeckDatabaseSync } from '../../../lib/overdeck/infra.js';\nimport { stateToOverdeckParamsForDb, AGENT_COLUMNS_FOR_DB } from '../../../lib/overdeck/agent-state-sync.js';\nimport { getEventStore, type EventStore, type StoredEvent } from '../event-store.js';\nimport { writeAgentStateJsonSync, type AgentState } from '../../../lib/agents.js';\nimport { logAgentLifecycleSync } from '../../../lib/persistent-logger.js';\nimport type { DomainEvent } from '@overdeck/contracts';\n\nexport interface AgentProjectionResult {\n /** Assigned event sequence number. */\n sequence: number;\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\nfunction buildStoredEvent(\n event: Omit<DomainEvent, 'sequence'>,\n sequence: number,\n): StoredEvent {\n const record = event as Record<string, unknown>;\n return {\n sequence,\n type: event.type,\n timestamp: (record['timestamp'] as string) ?? new Date().toISOString(),\n payload: (record['payload'] as Record<string, unknown>) ?? {},\n };\n}\n\n/**\n * Atomically persist an agent state change and its lifecycle event.\n *\n * 1. Prepares the state (stoppedAt stamping).\n * 2. Writes state.json (rollback source) outside the SQLite tx.\n * 3. Begins an overdeck.db transaction, upserts the agents row, inserts the event.\n * 4. Commits and emits the stored event to subscribers.\n *\n * @throws If the SQLite transaction is rolled back, neither the row nor the\n * event is persisted and state.json may be slightly ahead.\n */\nexport function saveAgentStateAndEmitEvent(\n state: AgentState,\n event: Omit<DomainEvent, 'sequence'>,\n): AgentProjectionResult {\n const db = getOverdeckDatabaseSync();\n const eventStore = getEventStore();\n return saveAgentStateAndEmitEventWithDeps(db, eventStore, state, event);\n}\n\n/**\n * Effect wrapper for server routes. Runs the synchronous projection under\n * Effect.sync so callers in Effect.gen can compose it without blocking.\n */\nexport function saveAgentStateAndEmitEventProgram(\n state: AgentState,\n event: Omit<DomainEvent, 'sequence'>,\n): Effect.Effect<AgentProjectionResult> {\n return Effect.sync(() => saveAgentStateAndEmitEvent(state, event));\n}\n\n/**\n * Dependency-injected variant for tests.\n */\nexport function saveAgentStateAndEmitEventWithDeps(\n db: SqliteDatabase,\n eventStore: Pick<EventStore, 'emitStored'>,\n state: AgentState,\n event: Omit<DomainEvent, 'sequence'>,\n): AgentProjectionResult {\n prepareAgentStateForSave(state);\n\n // Rollback source lives on the filesystem; keep it outside the SQLite tx\n // so a tx failure does not corrupt it.\n writeAgentStateJsonSync(state);\n\n const record = event as Record<string, unknown>;\n const timestamp = (record['timestamp'] as string) ?? new Date().toISOString();\n const timestampMs = new Date(timestamp).getTime();\n const payload = JSON.stringify(record['payload'] ?? {});\n const updatedAt = Date.now();\n\n db.exec('BEGIN IMMEDIATE');\n try {\n // Ensure the issues row exists (overdeck FK requirement).\n db.prepare(\n `INSERT OR IGNORE INTO issues (id, stage, updated_at) VALUES (?, 'working', ?)`,\n ).run(state.issueId, updatedAt);\n\n // Upsert the agents row.\n db.prepare(\n `INSERT OR REPLACE INTO agents (${AGENT_COLUMNS_FOR_DB.join(', ')}) VALUES (${AGENT_COLUMNS_FOR_DB.map(() => '?').join(', ')})`,\n ).run(...stateToOverdeckParamsForDb(state, updatedAt));\n\n // Append the event. overdeck events.timestamp is integer unix milliseconds\n // (schema: integer(timestamp_ms); event-store decodes via new Date(row.timestamp)).\n db.prepare(\n `INSERT INTO events (type, timestamp, payload) VALUES (?, ?, ?)`,\n ).run(event.type, timestampMs, payload);\n\n const row = db.prepare(`SELECT last_insert_rowid() AS sequence`).get() as\n | { sequence: number }\n | undefined;\n const sequence = row?.sequence ?? 0;\n db.exec('COMMIT');\n\n const stored = buildStoredEvent(event, sequence);\n eventStore.emitStored(stored);\n\n logAgentLifecycleSync(\n state.id,\n `projected ${event.type} (seq=${sequence}) for ${state.id}`,\n );\n\n return { sequence };\n } catch (err) {\n try {\n db.exec('ROLLBACK');\n } catch {\n // Ignore rollback failures; the connection may already be rolled back.\n }\n throw err;\n }\n}\n","import { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport {\n clearAgentPaused,\n clearAgentTroubled,\n getAgentState,\n getSessionId,\n markAgentStoppedState,\n saveAgentRuntimeState,\n saveSessionId,\n setAgentPaused,\n stopAgent,\n} from '../../../../lib/agents.js';\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { operatorInterventionEvent } from '../../../../lib/operator-interventions.js';\nimport { stopWorkspaceDocker } from '../../../../lib/workspace-manager.js';\nimport { killSession, sessionExists } from '../../../../lib/tmux.js';\nimport { saveAgentStateAndEmitEventProgram } from '../../services/agent-projection.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport {\n appendAgentLifecycleLog,\n buildAgentControlEventPayload,\n captureAgentOutputBeforeKill,\n invalidateAgentsCache,\n readJsonBody,\n toAgentStatusPayload,\n} from './shared.js';\n\nexport function createAgentStopHandler(\n lifecycleEvent: 'agent.delete_requested' | 'agent.stop_requested',\n) {\n return httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const eventStore = yield* EventStoreService;\n\n const stateBeforeStop = yield* getAgentState(id);\n yield* Effect.promise(() => appendAgentLifecycleLog(id, lifecycleEvent));\n yield* stopAgent(id);\n\n // PAN-1316/PAN-1326: tear down the workspace Docker stack on user-initiated stop.\n // Without this, dev-server containers (Vite/Webpack) outlive their owning\n // agent and can degrade the host via inotify-fallback polling storms.\n // Internal stops (restart) take a different path and don't reach here.\n //\n // Resolve the workspace from the issue (not from the agent's own state) so\n // killing a specialist (review/test/ship) — whose state.workspace may not\n // point at the work agent's workspace — still tears down the right stack.\n // Mirrors the postMergeLifecycle pattern in merge-agent.ts.\n if (stateBeforeStop?.issueId) {\n yield* Effect.promise(async () => {\n try {\n const { resolveProjectFromIssueSync } = await import('../../../../lib/projects.js');\n const { findWorkspacePath } = await import('../../../../lib/lifecycle/archive-planning.js');\n const issueLower = stateBeforeStop.issueId!.toLowerCase();\n const project = resolveProjectFromIssueSync(stateBeforeStop.issueId!);\n const projectPath = project?.projectPath ?? process.cwd();\n const workspacePath = findWorkspacePath(projectPath, issueLower);\n if (workspacePath) {\n const dockerResult = await Effect.runPromise(stopWorkspaceDocker(workspacePath, issueLower));\n if (dockerResult.containersFound) {\n console.log(`[agents] ✓ Stopped Docker stack for ${id}: ${dockerResult.steps.join('; ')}`);\n }\n }\n } catch (err) {\n console.warn(`[agents] Docker teardown failed for ${id} (non-fatal): ${err instanceof Error ? err.message : String(err)}`);\n }\n });\n }\n\n // PAN-1048 review feedback 004 (C1): AgentStoppedEvent requires both\n // agentId AND issueId on the payload (packages/contracts/src/events.ts:36);\n // ws-rpc drops events that fail Schema validation, so emits without issueId\n // never reach subscribers and the dashboard misses the stop transition.\n //\n // PAN-1908: write-through projection — re-upsert the stopped row and append\n // the lifecycle event in one SQLite transaction. stopAgent already saved\n // state, but repeating the upsert here makes the event append atomic.\n const stateAfterStop = yield* getAgentState(id);\n if (stateAfterStop) {\n yield* saveAgentStateAndEmitEventProgram(stateAfterStop, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: stateAfterStop.issueId || stateBeforeStop?.issueId || '' },\n });\n }\n const issueId = stateBeforeStop?.issueId;\n // PAN-1048: derive label from role; legacy state.phase no longer exists.\n const phaseLabel = stateBeforeStop?.role === 'plan' ? 'planning' : 'work';\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'info',\n message: issueId\n ? `User stopped ${issueId} ${phaseLabel} agent`\n : `User stopped agent ${id}`,\n issueId,\n });\n invalidateAgentsCache();\n return jsonResponse({ success: true });\n }));\n}\n\nfunction agentStopRoute(\n method: 'DELETE' | 'POST',\n path: `/${string}`,\n lifecycleEvent: 'agent.delete_requested' | 'agent.stop_requested',\n) {\n return HttpRouter.add(method, path, createAgentStopHandler(lifecycleEvent));\n}\n\n// ─── Route: DELETE /api/agents/:id ───────────────────────────────────────────\n\nexport const deleteAgentRoute = agentStopRoute('DELETE', '/api/agents/:id', 'agent.delete_requested');\n\n// ─── Route: POST /api/agents/:id/stop ────────────────────────────────────────\n\nexport const postAgentStopRoute = agentStopRoute('POST', '/api/agents/:id/stop', 'agent.stop_requested');\n\n// ─── Route: POST /api/agents/:id/suspend ─────────────────────────────────────\n\nexport const postAgentSuspendRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/suspend',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n\n const { sessionId } = body as any;\n const effectiveSessionId = sessionId || getSessionId(id);\n\n if (!effectiveSessionId) {\n return jsonResponse({ error: 'Session ID required for suspend' }, { status: 400 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.suspend_requested', { sessionId: effectiveSessionId }));\n saveSessionId(id, effectiveSessionId);\n // PAN-1048 review feedback 004 (C1): resolve issueId before kill so we can\n // include it on the agent.stopped payload (the contract requires it).\n const suspendIssueId = (yield* getAgentState(id))?.issueId ?? '';\n yield* killSession(id).pipe(Effect.catch(() => Effect.void));\n saveAgentRuntimeState(id, {\n state: 'suspended',\n lastActivity: new Date().toISOString(),\n claudeSessionId: effectiveSessionId,\n });\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction. Preserve the existing agent-table status\n // (suspend does not flip it to stopped).\n const stateAfterSuspend = yield* getAgentState(id);\n if (stateAfterSuspend) {\n yield* saveAgentStateAndEmitEventProgram(stateAfterSuspend, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: stateAfterSuspend.issueId || suspendIssueId },\n });\n }\n\n invalidateAgentsCache();\n return jsonResponse({ success: true });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/pause ────────────────────────────────────────\n\nexport const postAgentPauseRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/pause',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const reason = (body as { reason?: unknown }).reason;\n\n if (reason !== undefined && typeof reason !== 'string') {\n return jsonResponse({ error: 'reason must be a string' }, { status: 400 });\n }\n\n const stateBeforePause = yield* getAgentState(id);\n if (!stateBeforePause) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n const previousStatus = toAgentStatusPayload(stateBeforePause.status);\n const hasLiveSession = yield* sessionExists(id);\n const stoppedByPause = hasLiveSession || stateBeforePause.status === 'running' || stateBeforePause.status === 'starting';\n let updatedState = yield* setAgentPaused(id, reason, stoppedByPause);\n if (!updatedState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n if (hasLiveSession) {\n yield* Effect.promise(() => captureAgentOutputBeforeKill(id));\n yield* killSession(id);\n }\n\n if (hasLiveSession || updatedState.status === 'running' || updatedState.status === 'starting') {\n const stoppedState = markAgentStoppedState(updatedState);\n updatedState = stoppedState;\n yield* Effect.promise(() => saveAgentRuntimeState(id, {\n state: 'stopped',\n lastActivity: new Date().toISOString(),\n }));\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.pause_requested', { reason }));\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: updatedState.issueId || stateBeforePause.issueId || id.replace(/^agent-/, '').toUpperCase(),\n kind: 'pause',\n source: 'dashboard',\n }));\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.status_changed',\n timestamp: new Date().toISOString(),\n payload: buildAgentControlEventPayload(updatedState, previousStatus),\n });\n\n invalidateAgentsCache();\n return jsonResponse({ success: true, agent: updatedState });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/unpause ──────────────────────────────────────\n\nexport const postAgentUnpauseRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/unpause',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const eventStore = yield* EventStoreService;\n\n const stateBeforeUnpause = yield* getAgentState(id);\n if (!stateBeforeUnpause) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n const updatedState = yield* clearAgentPaused(id);\n if (!updatedState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.unpause_requested'));\n if (stateBeforeUnpause.paused === true) {\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: updatedState.issueId || stateBeforeUnpause.issueId || id.replace(/^agent-/, '').toUpperCase(),\n kind: 'unpause',\n source: 'dashboard:agent-unpause',\n }));\n }\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.status_changed',\n timestamp: new Date().toISOString(),\n payload: buildAgentControlEventPayload(updatedState, toAgentStatusPayload(stateBeforeUnpause.status)),\n });\n\n invalidateAgentsCache();\n return jsonResponse({ success: true, agent: updatedState });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/untroubled ───────────────────────────────────\n\nexport const postAgentUntroubledRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/untroubled',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const eventStore = yield* EventStoreService;\n\n const stateBeforeClear = yield* getAgentState(id);\n if (!stateBeforeClear) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n const updatedState = yield* clearAgentTroubled(id);\n if (!updatedState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.untroubled_requested'));\n if (stateBeforeClear.troubled === true || (stateBeforeClear.consecutiveFailures ?? 0) > 0) {\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: updatedState.issueId || stateBeforeClear.issueId || id.replace(/^agent-/, '').toUpperCase(),\n kind: 'untroubled',\n source: 'dashboard:agent-untroubled',\n }));\n }\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.status_changed',\n timestamp: new Date().toISOString(),\n payload: buildAgentControlEventPayload(updatedState, toAgentStatusPayload(stateBeforeClear.status)),\n });\n\n invalidateAgentsCache();\n return jsonResponse({ success: true, agent: updatedState });\n })),\n);\n","import { existsSync } from 'node:fs';\nimport { readFile, rm, writeFile } from 'node:fs/promises';\nimport { dirname, join, basename } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter } from 'effect/unstable/http';\nimport type { RuntimeName } from '../../../../lib/runtimes/types.js';\nimport { resolveStaffing } from '../../../../lib/agents/staffing.js';\nimport { findPlanSync, readWorkspacePlanSync } from '../../../../lib/vbrief/io.js';\nimport { resolveTieredExecutionEnabled, resolveTieredExecutionEnabledForIssue } from '../../../../lib/agents/tier-table.js';\nimport { getDispatchableItems } from '../../../../lib/vbrief/dag.js';\nimport { loadConfigSync } from '../../../../lib/config-yaml.js';\n\nimport {\n getAgentState,\n getLatestSessionId,\n recoverAgent,\n resumeAgent,\n restartAgent,\n saveAgentStateSync,\n getAgentDir,\n getProviderAuthMode,\n listRunningAgents,\n wipeAgentStateDirs,\n} from '../../../../lib/agents.js';\nimport { canUseHarnessSync } from '../../../../lib/harness-policy.js';\nimport { normalizeModelOverrideSync, requireModelOverrideSync } from '../../../../lib/model-validation.js';\nimport { operatorInterventionEvent } from '../../../../lib/operator-interventions.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { getWorkAgentLifecycleState } from '../../../../lib/work-agent-lifecycle.js';\nimport { killSession } from '../../../../lib/tmux.js';\nimport { saveAgentStateAndEmitEventProgram } from '../../services/agent-projection.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport {\n appendAgentLifecycleLog,\n buildPanStartArgs,\n invalidateAgentsCache,\n readJsonBody,\n spawnPanCommandDetached,\n} from './shared.js';\n\n// ─── Route: POST /api/agents/:id/resume ──────────────────────────────────────\n\nexport const postAgentResumeRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/resume',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { message, model, harness, compact } = body as { message?: string; model?: string; harness?: RuntimeName; compact?: boolean };\n let resumeModel: string | undefined;\n try {\n resumeModel = normalizeModelOverrideSync(model);\n } catch (err) {\n console.warn(`[agents/resume] ${id} model validation failed: ${err instanceof Error ? err.message : String(err)}`);\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n // PAN-1985 follow-up: structured log at the route entry so the operator\n // can trace every resume attempt in the server console / pty-supervisor\n // log even when the front-end's toast is missed.\n console.log(`[agents/resume] ${id} requested: model=${resumeModel ?? 'unchanged'} harness=${harness ?? 'unchanged'} hasMessage=${!!message} compact=${compact === true}`);\n const eventStore = yield* EventStoreService;\n // Snapshot lifecycle state BEFORE taking any action so callers can see the\n // temporal context (why was this resume allowed) without recomputing state.\n const lifecycleBefore = yield* getWorkAgentLifecycleState(id);\n console.log(`[agents/resume] ${id} lifecycle: canResume=${lifecycleBefore.canResumeSession} hasSavedSession=${lifecycleBefore.hasSavedSession} hasLiveTmux=${lifecycleBefore.hasLiveTmuxSession} isCrashed=${lifecycleBefore.isCrashed} isStopped=${lifecycleBefore.isStopped}`);\n // PAN-1675: a compact-resume targets a context-wedged agent that is usually\n // still 'running' (a live but stuck session), which the normal gate rejects.\n // Allow it through for compact === true — resumeAgent summarizes the wedged\n // session out-of-band, kills it, and respawns a fresh session seeded with\n // the summary (PAN-1781; its own canResume handles the running case).\n // Non-compact resumes keep the strict gate.\n if (!lifecycleBefore.canResumeSession && !lifecycleBefore.isRunningButStuck && compact !== true) {\n console.warn(`[agents/resume] ${id} rejected: ${lifecycleBefore.reason}`);\n return jsonResponse({\n error: lifecycleBefore.reason || `Cannot resume agent ${lifecycleBefore.agentId}`,\n lifecycle: lifecycleBefore,\n }, { status: 409 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.resume_requested', {\n hasMessage: !!message,\n model: resumeModel || undefined,\n harness: harness || undefined,\n lifecycle: lifecycleBefore,\n }));\n const resumeOpts = resumeModel || harness || compact === true\n ? { ...(resumeModel ? { model: resumeModel } : {}), ...(harness ? { harness } : {}), ...(compact === true ? { compact: true } : {}) }\n : undefined;\n console.log(`[agents/resume] ${id} dispatching resumeAgent() with opts=${JSON.stringify(resumeOpts)}`);\n const result = yield* Effect.promise(() => resumeAgent(id, message, resumeOpts));\n console.log(`[agents/resume] ${id} resumeAgent returned: success=${result.success} messageDelivered=${result.messageDelivered} error=${result.error ?? 'none'}`);\n if (result.success) {\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction so the read model transitions agent\n // status from 'stopped' → 'running' and the frontend updates immediately.\n const agentState = yield* getAgentState(id);\n if (agentState) {\n yield* saveAgentStateAndEmitEventProgram(agentState, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: id,\n issueId: agentState.issueId,\n agent: {\n id,\n issueId: agentState.issueId,\n workspace: agentState.workspace,\n runtime: agentState.harness ?? 'claude-code',\n model: agentState.model,\n status: 'running',\n startedAt: agentState.startedAt,\n lastActivity: new Date().toISOString(),\n role: agentState.role ?? 'work',\n },\n },\n });\n }\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.resume_succeeded', {\n hasMessage: !!message,\n messageDelivered: result.messageDelivered !== false,\n }));\n invalidateAgentsCache();\n // PAN-1985 follow-up: the messageDelivered flag distinguishes \"agent is\n // resumed and your message landed in its composer\" from \"agent is\n // resumed but your message did NOT land in its composer (PTY supervisor\n // echo-confirm timed out, harness/session.id mismatch, etc.)\". The\n // former gets a 'delivered' toast; the latter gets a clear 'queued in\n // mail' warning so the operator can intervene if needed.\n const delivered = result.messageDelivered !== false;\n console.log(`[agents/resume] ${id} returning: success=${true} delivered=${delivered}`);\n return jsonResponse({\n success: true,\n resumed: true,\n messageDelivered: delivered,\n hint: delivered\n ? 'Continue prompt delivered to the agent.'\n : 'The continue prompt was queued in the agent mail/ folder because the live delivery path did not confirm in time. The agent will read it on its next session start.',\n lifecycle: { before: lifecycleBefore, after: yield* getWorkAgentLifecycleState(id) },\n });\n } else {\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.resume_failed', {\n hasMessage: !!message,\n error: result.error,\n }));\n return jsonResponse({\n error: result.error,\n lifecycle: { before: lifecycleBefore, after: yield* getWorkAgentLifecycleState(id) },\n }, { status: 400 });\n }\n })),\n);\n\n// ─── Route: POST /api/agents/:id/recover ──────────────────────────────────────\n\nexport const postAgentRecoverRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/recover',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const { model } = body as { model?: string };\n let recoveryModel: string | undefined;\n try {\n recoveryModel = normalizeModelOverrideSync(model);\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n\n const stateBeforeRecover = yield* getAgentState(id);\n if (!stateBeforeRecover) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.recover_requested', {\n model: recoveryModel || undefined,\n }));\n\n const result = yield* Effect.promise(() => recoverAgent(id, recoveryModel ? { modelOverride: recoveryModel } : undefined));\n if (!result) {\n const error = `Could not recover agent ${id}`;\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.recover_failed', { error }));\n return jsonResponse({ success: false, error }, { status: 400 });\n }\n\n const updatedState = yield* getAgentState(id);\n if (updatedState) {\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: id,\n issueId: updatedState.issueId || stateBeforeRecover.issueId || id.replace('agent-', '').toUpperCase(),\n agent: {\n id,\n issueId: updatedState.issueId || stateBeforeRecover.issueId,\n workspace: updatedState.workspace,\n model: updatedState.model,\n status: 'running',\n startedAt: updatedState.startedAt,\n lastActivity: updatedState.lastActivity,\n role: updatedState.role ?? 'work',\n },\n },\n });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.recover_succeeded'));\n invalidateAgentsCache();\n return jsonResponse({ success: true, recovered: true, agent: updatedState ?? null });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/restart ──────────────────────────────────────\n//\n// Restart an agent with optional model override. Graceful mode sends a 30s\n// warning then restarts; quick mode kills and relaunches immediately.\n// Returns 202 for graceful (async work), 200 for quick.\n\nexport const postAgentRestartRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/restart',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n\n const { model, harness, graceful = true, message } = body as {\n model?: string;\n harness?: 'claude-code' | 'ohmypi' | 'codex';\n graceful?: boolean;\n message?: string;\n };\n let restartModel: string | undefined;\n try {\n restartModel = normalizeModelOverrideSync(model);\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.restart_requested', {\n model: restartModel || agentState.model,\n harness: harness || undefined,\n graceful,\n hasMessage: !!message,\n }));\n\n if (graceful) {\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: agentState.issueId,\n kind: 'restart',\n source: 'dashboard',\n }));\n // Kick off async restart — don't block the HTTP response for 30s\n (async () => {\n try {\n // PAN-1908: write-through projection — agents-row upsert + lifecycle\n // event append in one SQLite transaction.\n await Effect.runPromise(saveAgentStateAndEmitEventProgram(agentState, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: agentState.issueId },\n }));\n\n const result = await restartAgent(id, { model: restartModel, harness, graceful: true, message });\n\n if (result.success) {\n const updatedState = await Effect.runPromise(getAgentState(id));\n // PAN-1908: write-through projection — agents-row upsert + lifecycle\n // event append in one SQLite transaction.\n if (updatedState) {\n await Effect.runPromise(saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: id,\n issueId: updatedState.issueId || agentState.issueId,\n agent: {\n id,\n issueId: updatedState.issueId || agentState.issueId,\n workspace: updatedState.workspace || agentState.workspace,\n // PAN-1048 review feedback 004 (C3): same as quick-restart\n // below — surface the actual harness so Pi agents do not\n // get mis-labelled as Claude Code on graceful restart.\n runtime: updatedState.harness ?? agentState.harness ?? 'claude-code',\n model: restartModel || updatedState.model || agentState.model,\n status: 'running',\n startedAt: updatedState.startedAt || agentState.startedAt,\n lastActivity: new Date().toISOString(),\n role: updatedState.role ?? agentState.role,\n },\n },\n }));\n }\n invalidateAgentsCache();\n }\n await appendAgentLifecycleLog(id, 'agent.restart_completed', {\n success: result.success,\n error: result.error,\n });\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[agents] Graceful restart failed for ${id}: ${msg}`);\n await appendAgentLifecycleLog(id, 'agent.restart_failed', { error: msg });\n }\n })();\n\n return jsonResponse({ accepted: true, graceful: true, agentId: id }, { status: 202 });\n }\n\n // Quick restart — synchronous\n const result = yield* Effect.promise(() => restartAgent(id, { model: restartModel, harness, graceful: false, message }));\n\n if (result.success) {\n const updatedState = yield* getAgentState(id);\n yield* eventStore.appendAsync(operatorInterventionEvent({\n issueId: updatedState?.issueId || agentState.issueId,\n kind: 'restart',\n source: 'dashboard',\n }));\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction. restartAgent already saved state, but\n // repeating the upsert here makes each lifecycle event atomic.\n if (updatedState) {\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: updatedState.issueId || agentState.issueId },\n });\n yield* saveAgentStateAndEmitEventProgram(updatedState, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: id,\n issueId: updatedState.issueId || agentState.issueId,\n agent: {\n id,\n issueId: updatedState.issueId || agentState.issueId,\n workspace: updatedState.workspace || agentState.workspace,\n // PAN-1048 review feedback 004 (C3): preserve the agent's actual\n // harness instead of hard-coding 'claude'. AgentSnapshot.runtime\n // is what getHarness() reads, so a Pi agent restarted through\n // this path was being mis-labelled as Claude Code.\n runtime: updatedState.harness ?? agentState.harness ?? 'claude-code',\n model: restartModel || updatedState.model || agentState.model,\n status: 'running',\n startedAt: updatedState.startedAt || agentState.startedAt,\n lastActivity: new Date().toISOString(),\n role: updatedState.role ?? agentState.role,\n },\n },\n });\n }\n invalidateAgentsCache();\n return jsonResponse({ success: true, restarted: true, agentId: id, model: restartModel || agentState.model });\n }\n\n return jsonResponse({ error: result.error }, { status: 500 });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/restart-fresh ────────────────────────────────\n//\n// PAN-1985: wipe the work agent's state directory under ~/.overdeck/agents/,\n// then optionally spawn a fresh work agent with a new harness/model. This is\n// the deliberate operator override path — for harness/model switches (the\n// Claude-session JSONL can't be resumed under a different harness) and for\n// \"I want a clean work run\" recovery. The NORMAL review flow continues the\n// same session across re-dispatches (PAN-1862); this route is the escape\n// hatch that pays the re-research cost.\n//\n// Modes (driven by request body):\n// { spawn: true, model, harness } — wipe + respawn a new work agent with\n// the chosen harness/model\n// { spawn: false } — wipe only; user clicks Start afterwards\n// (this is the backend of the new\n// 'completeWorkReset' issue action)\n//\n// Refuses (409) if a live tmux session is alive — same gate as reset-session.\n// Workspace, vBRIEF, beads, .pan/continue.json, .pan/feedback/, the branch,\n// and the commit history are all left untouched. The new agent (whether\n// auto-spawned or manually started) reads .pan/continue.json + branch state\n// to pick up where the prior run left off.\n\nexport const postAgentRestartFreshRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/restart-fresh',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { spawn: spawnFlag, model: rawModel, harness } = body as {\n spawn?: boolean;\n model?: string;\n harness?: 'claude-code' | 'ohmypi' | 'codex';\n };\n const wantsSpawn = spawnFlag !== false; // default to spawn when omitted (picker path)\n\n let newModel: string | undefined;\n if (wantsSpawn && rawModel) {\n try {\n newModel = requireModelOverrideSync(rawModel);\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n const issueId = agentState.issueId ?? id.replace(/^agent-/, '').toUpperCase();\n\n const lifecycle = yield* getWorkAgentLifecycleState(id);\n if (lifecycle.hasLiveTmuxSession) {\n return jsonResponse({\n error: `Agent ${id} has a live tmux session. Run 'pan kill ${issueId}' first, then retry.`,\n lifecycle,\n }, { status: 409 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.restart_fresh_requested', {\n wantsSpawn,\n model: newModel,\n harness,\n issueId,\n }));\n\n // Kill any zombie tmux session (shouldn't happen with the gate above, but\n // belt-and-suspenders) before wiping state.\n yield* killSession(id).pipe(Effect.catch(() => Effect.void));\n\n // Wipe the work agent dir only — leave specialist dirs (review, etc.)\n // alone. The new agent will read .pan/continue.json to pick up.\n const wipeResult = yield* Effect.promise(() => wipeAgentStateDirs(issueId));\n yield* Effect.promise(() => appendAgentLifecycleLog(id, 'agent.restart_fresh_wiped', {\n removed: wipeResult.removed,\n path: wipeResult.path,\n }));\n\n if (!wantsSpawn) {\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n spawn: false,\n agentId: id,\n issueId,\n removed: wipeResult.removed,\n hint: 'Agent dir wiped. Click Start agent to respawn with a fresh session.',\n });\n }\n\n // Auto-spawn path: dispatch to the existing /api/agents spawn flow.\n // We don't go through HTTP — we call the spawn primitives directly so\n // the caller gets a single 200 with both wipe and spawn confirmed.\n const spawnModel = newModel ?? agentState.model ?? 'claude-sonnet-5';\n let effectiveHarness: 'claude-code' | 'ohmypi' | 'codex' | null = null;\n if (harness) {\n const harnessDecision = yield* Effect.promise(async () =>\n canUseHarnessSync(harness, spawnModel, await getProviderAuthMode(spawnModel)),\n );\n effectiveHarness = harnessDecision.allowed ? harness : 'claude-code';\n }\n\n const agentSessionName = `agent-${issueId.toLowerCase()}`;\n const projectPath = agentState.workspace\n ? dirname(agentState.workspace)\n : undefined;\n const projectConfig = resolveProjectFromIssueSync(issueId);\n const projectRoot = projectConfig?.projectPath ?? projectPath ?? process.cwd();\n const workspacePath = agentState.workspace ?? join(projectRoot, 'workspaces', `feature-${issueId.toLowerCase()}`);\n\n const args = buildPanStartArgs({\n issueId,\n model: spawnModel,\n harness: effectiveHarness,\n });\n\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.restart_fresh_spawn_requested', {\n args,\n model: spawnModel,\n harness: effectiveHarness,\n }));\n\n // Spawn detached `pan start` — same pattern the existing POST /api/agents\n // route uses, minus the HTTP hop. We deliberately write a placeholder\n // state.json (matching the existing spawn flow) so the dashboard\n // transitions the agent from \"stopped\" to \"starting\" within one refresh.\n saveAgentStateSync({\n id: agentSessionName,\n issueId,\n workspace: workspacePath,\n harness: effectiveHarness ?? 'claude-code',\n role: 'work',\n model: 'pending-work-spawn',\n status: 'starting',\n startedAt: new Date().toISOString(),\n });\n\n try {\n yield* Effect.promise(() => spawnPanCommandDetached({\n agentSessionName,\n issueId,\n role: 'work',\n workspacePath,\n args,\n cwd: workspacePath,\n }));\n } catch (err: any) {\n return jsonResponse({\n success: false,\n error: `Agent dir wiped but spawn failed: ${err?.message ?? String(err)}`,\n wiped: wipeResult.removed,\n }, { status: 500 });\n }\n\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.restart_fresh_spawn_requested_complete', {\n model: spawnModel,\n harness: effectiveHarness,\n }));\n\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n spawn: true,\n agentId: id,\n issueId,\n removed: wipeResult.removed,\n spawnedModel: spawnModel,\n spawnedHarness: effectiveHarness,\n hint: 'Fresh work agent spawned. It will read .pan/continue.json and the branch state to continue.',\n });\n })),\n);\n\n// ─── Route: POST /api/agents/restart-all ──────────────────────────────────────\n//\n// Restart all running workspace agents using restartAgent() directly.\n// Quick mode (no graceful delay) to avoid serializing 30s waits across N agents.\n\nexport const postAgentsRestartAllRoute = HttpRouter.add(\n 'POST',\n '/api/agents/restart-all',\n Effect.gen(function* () {\n return yield* Effect.promise(async () => {\n try {\n const running = (await Effect.runPromise(listRunningAgents())).filter(a => a.tmuxActive);\n const results: { id: string; issueId: string; model: string; status: string }[] = [];\n\n for (const agent of running) {\n try {\n const result = await restartAgent(agent.id, { graceful: false });\n if (result.success) {\n results.push({ id: agent.id, issueId: agent.issueId, model: agent.model, status: 'restarted' });\n } else {\n results.push({ id: agent.id, issueId: agent.issueId, model: agent.model, status: `failed: ${result.error}` });\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n console.error(`[agents] Failed to restart ${agent.id}:`, msg);\n results.push({ id: agent.id, issueId: agent.issueId, model: agent.model, status: `failed: ${msg}` });\n }\n }\n\n const succeeded = results.filter(r => r.status === 'restarted').length;\n console.log(`[agents] Restarted ${succeeded}/${running.length} workspace agents`);\n return jsonResponse({ restarted: succeeded, total: running.length, results });\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return jsonResponse({ error: 'Failed to restart agents: ' + msg }, { status: 500 });\n }\n });\n }),\n);\n\n// ─── Route: POST /api/agents/:id/reset-session ─────────────────────────────\n// Clears saved Claude session tracking so the next start creates a fresh session.\n// Workspace, beads, and git state are preserved. JSONL files kept for cost history.\n\nexport const postAgentResetSessionRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/reset-session',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const eventStore = yield* EventStoreService;\n\n const lifecycle = yield* getWorkAgentLifecycleState(id);\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: `Agent ${id} not found`, lifecycle }, { status: 404 });\n }\n\n if (lifecycle.hasLiveTmuxSession) {\n return jsonResponse({ error: `Agent ${id} is running. Stop it first.`, lifecycle }, { status: 409 });\n }\n\n const previousSessionId = yield* getLatestSessionId(id);\n if (!previousSessionId) {\n return jsonResponse({ error: `Agent ${id} has no saved session to reset`, lifecycle }, { status: 404 });\n }\n\n const agentDir = getAgentDir(id);\n\n // Clear session.id\n yield* Effect.promise(() => rm(join(agentDir, 'session.id'), { force: true }));\n\n // Clear sessions.json\n yield* Effect.promise(() => rm(join(agentDir, 'sessions.json'), { force: true }));\n\n // Clear claudeSessionId from runtime.json (preserve other fields).\n // Must read/write directly — saveAgentRuntimeState merges with existing file.\n const runtimeFile = join(agentDir, 'runtime.json');\n if (existsSync(runtimeFile)) {\n try {\n const runtimeContent = yield* Effect.promise(() => readFile(runtimeFile, 'utf-8'));\n const runtime = JSON.parse(runtimeContent);\n delete runtime.claudeSessionId;\n yield* Effect.promise(() => writeFile(runtimeFile, JSON.stringify(runtime, null, 2)));\n } catch { /* non-fatal */ }\n }\n\n yield* killSession(id).pipe(Effect.catch(() => Effect.void));\n\n // Emit event so dashboard updates. PAN-1048 review feedback 004 (C1):\n // include issueId — without it AgentStoppedEvent fails Schema validation.\n //\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram(agentState, {\n type: 'agent.stopped',\n timestamp: new Date().toISOString(),\n payload: { agentId: id, issueId: agentState.issueId },\n });\n\n console.log(`[reset-session] Cleared session for ${id} (was: ${previousSessionId.slice(0, 8)}...)`);\n invalidateAgentsCache();\n return jsonResponse({ success: true, agentId: id, previousSessionId, lifecycle: yield* getWorkAgentLifecycleState(id) });\n })),\n);\n\n// ─── Route: POST /api/agents/restart-with-current-config ────────────────────\n// List all agents with their current vs new staffing, allowing operator to\n// selectively restart with new config\n\nexport interface RestartConfigChangeItem {\n agentId: string;\n issueId: string;\n currentModel: string;\n currentHarness: string;\n newModel: string;\n newHarness: string;\n changed: boolean;\n paused: boolean;\n troubled: boolean;\n status: string;\n}\n\nasync function buildRestartConfigChangeList(): Promise<RestartConfigChangeItem[]> {\n const agents = await Effect.runPromise(listRunningAgents());\n\n const items: RestartConfigChangeItem[] = [];\n\n for (const agent of agents) {\n if (!agent.issueId) continue;\n\n // Only include actually running agents with active tmux sessions\n if (!((agent as any).tmuxActive === true)) {\n continue;\n }\n\n // Skip paused/troubled agents (their gates stand)\n if ((agent as any).paused || (agent as any).troubled) {\n continue;\n }\n\n // Only work agents eligible for restart (skip conversations)\n if (agent.role !== 'work' && agent.role !== undefined) {\n continue;\n }\n\n // Compute new staffing from current config by reading the real vBRIEF work item\n const staffingResult = await resolveCurrentStaffing(agent.id, agent, agent.issueId);\n\n if (staffingResult.error) {\n // Skip agents where staffing cannot be resolved\n console.warn(`[restart-config] Skipping ${agent.id}: ${staffingResult.error}`);\n continue;\n }\n\n const newModel = staffingResult.newModel;\n const newHarness = staffingResult.newHarness;\n\n items.push({\n agentId: agent.id,\n issueId: agent.issueId,\n currentModel: agent.model,\n currentHarness: agent.harness ?? 'claude-code',\n newModel,\n newHarness,\n changed: agent.model !== newModel || (agent.harness ?? 'claude-code') !== newHarness,\n paused: (agent as any).paused ?? false,\n troubled: (agent as any).troubled ?? false,\n status: agent.status,\n });\n }\n\n return items;\n}\n\nexport const getAgentsRestartConfigRoute = HttpRouter.add(\n 'GET',\n '/api/agents/restart-with-current-config',\n httpHandler(Effect.gen(function* () {\n try {\n const items = yield* Effect.promise(() => buildRestartConfigChangeList());\n return jsonResponse({\n success: true,\n items,\n totalAgents: items.length,\n changedAgents: items.filter(i => i.changed).length,\n });\n } catch (error: unknown) {\n const msg = error instanceof Error ? error.message : String(error);\n return jsonResponse({ error: `Failed to build restart config list: ${msg}` }, { status: 500 });\n }\n })),\n);\n\n// ─── Route: POST /api/agents/restart-with-current-config ────────────────────\n// Restart selected agents with current config\n\nasync function resolveCurrentStaffing(agentId: string, agentState: any, issueId: string): Promise<{ newModel: string; newHarness: string; error?: string }> {\n const workspacePath = agentState.workspace;\n if (!workspacePath) {\n return { newModel: agentState.model, newHarness: agentState.harness ?? 'claude-code', error: 'No workspace path available' };\n }\n\n try {\n // Read the actual vBRIEF plan\n const plan = readWorkspacePlanSync(workspacePath);\n if (!plan || !plan.plan.items) {\n return { newModel: agentState.model, newHarness: agentState.harness ?? 'claude-code', error: 'Invalid plan structure' };\n }\n\n // Find the work item using the same logic as spawn-prep:\n // For slot agents, use slotItemId; for single-work agents, use first dispatchable item\n let workItem = null;\n if (agentState.slotItemId) {\n // Slot agent: find the item by ID\n workItem = plan.plan.items.find((item: any) => item.id === agentState.slotItemId);\n if (!workItem) {\n return { newModel: agentState.model, newHarness: agentState.harness ?? 'claude-code', error: `Scheduled item ${agentState.slotItemId} not found in plan` };\n }\n } else {\n // Single-work agent: use first dispatchable item (same as spawn-prep)\n const dispatchableItems = getDispatchableItems(plan, new Set());\n workItem = dispatchableItems[0];\n if (!workItem) {\n return { newModel: agentState.model, newHarness: agentState.harness ?? 'claude-code', error: 'No dispatchable items in plan' };\n }\n }\n\n // Use the real work item with plan metadata for staffing resolution\n // This matches the logic in resolveSingleWorkTierSpawnParams\n const config = loadConfigSync().config;\n const tiered = config.tieredExecution;\n const effectiveTieredEnabled = issueId\n ? resolveTieredExecutionEnabledForIssue(tiered, issueId, plan.plan.metadata)\n : resolveTieredExecutionEnabled(tiered, plan.plan.metadata);\n\n const staffing = resolveStaffing(workItem, {\n planMetadata: plan.plan.metadata,\n spawnKey: `work:${issueId.toLowerCase()}`,\n config: { ...config, tieredExecution: { ...tiered, enabled: effectiveTieredEnabled } },\n });\n\n return {\n newModel: staffing.model,\n newHarness: staffing.harness,\n };\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n newModel: agentState.model,\n newHarness: agentState.harness ?? 'claude-code',\n error: `Failed to resolve staffing: ${msg}`,\n };\n }\n}\n\nexport const postAgentsRestartWithConfigRoute = HttpRouter.add(\n 'POST',\n '/api/agents/restart-with-current-config',\n httpHandler(Effect.gen(function* () {\n const body = yield* readJsonBody;\n const { agentIds } = body as { agentIds?: string[] };\n\n if (!agentIds || !Array.isArray(agentIds) || agentIds.length === 0) {\n return jsonResponse({ error: 'agentIds array is required and must not be empty' }, { status: 400 });\n }\n\n // Get eligible agents from the same predicate as the GET route\n const eligibleAgents = yield* Effect.promise(() => buildRestartConfigChangeList());\n const eligibleIds = new Set(eligibleAgents.map(a => a.agentId));\n\n const results: { id: string; status: string; newModel?: string; newHarness?: string; error?: string }[] = [];\n\n for (const agentId of agentIds) {\n try {\n // Check if agent is in the eligible set\n if (!eligibleIds.has(agentId)) {\n results.push({ id: agentId, status: 'ineligible', error: 'Agent is not eligible for restart (paused, troubled, stopped, or not a work agent)' });\n continue;\n }\n\n const agentState = yield* getAgentState(agentId);\n if (!agentState) {\n results.push({ id: agentId, status: 'not_found', error: `Agent state not found` });\n continue;\n }\n\n const issueId = agentState.issueId ?? agentId.replace(/^agent-/, '').toUpperCase();\n\n // Resolve new staffing using the real work item\n const staffingResult = yield* Effect.promise(() => resolveCurrentStaffing(agentId, agentState, issueId));\n\n if (staffingResult.error) {\n results.push({\n id: agentId,\n status: 'staffing_error',\n error: staffingResult.error,\n });\n continue;\n }\n\n const newModel = staffingResult.newModel;\n const newHarness = staffingResult.newHarness;\n\n // Restart with new config\n const restartResult = yield* Effect.promise(() => restartAgent(agentId, {\n model: newModel,\n harness: newHarness as any,\n graceful: false,\n }));\n\n if (restartResult.success) {\n results.push({\n id: agentId,\n status: 'restarted',\n newModel,\n newHarness,\n });\n } else {\n results.push({\n id: agentId,\n status: 'failed',\n error: restartResult.error,\n });\n }\n } catch (err: unknown) {\n const msg = err instanceof Error ? err.message : String(err);\n results.push({\n id: agentId,\n status: 'error',\n error: msg,\n });\n }\n }\n\n const succeeded = results.filter(r => r.status === 'restarted').length;\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n restarted: succeeded,\n total: agentIds.length,\n results,\n });\n })),\n);\n","import { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { getAgentHealth } from '../../../../lib/cloister/health.js';\nimport { performHandoff } from '../../../../lib/cloister/handoff.js';\nimport { loadCloisterConfigSync } from '../../../../lib/cloister/config.js';\nimport { getCloisterService } from '../../../../lib/cloister/service.js';\nimport { checkAllTriggers } from '../../../../lib/cloister/triggers.js';\nimport { calculateCostSync, getPricingSync, type TokenUsage } from '../../../../lib/cost.js';\nimport { normalizeModelName } from '../../../../lib/cost-parsers/jsonl-parser.js';\nimport { requireModelOverrideSync } from '../../../../lib/model-validation.js';\nimport { encodeClaudeProjectDir } from '../../../../lib/paths.js';\nimport { getRuntimeForAgent } from '../../../../lib/runtimes/index.js';\nimport {\n getAgentState,\n setAgentDeliveryMethod,\n} from '../../../../lib/agents.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport { readJsonBody } from './shared.js';\n\n// ─── Route: GET /api/agents/:id/cloister-health ──────────────────────────────\n\nexport const getAgentCloisterHealthRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/cloister-health',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const service = getCloisterService();\n const health = service.getAgentHealth(id);\n if (!health) {\n return jsonResponse({ error: 'Agent not found or runtime not available' }, { status: 404 });\n }\n return jsonResponse(health);\n })),\n);\n\n// ─── Route: GET /api/agents/:id/handoff/suggestion ───────────────────────────\n\nexport const getAgentHandoffSuggestionRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/handoff/suggestion',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: 'Agent not found' }, { status: 404 });\n }\n\n const runtime = getRuntimeForAgent(id);\n if (!runtime) {\n return jsonResponse({ error: 'Runtime not found for agent' }, { status: 404 });\n }\n\n const health = getAgentHealth(id, runtime);\n const triggers = yield* checkAllTriggers(\n id,\n agentState.workspace,\n agentState.issueId,\n agentState.model,\n health,\n loadCloisterConfigSync()\n );\n\n if (triggers.length > 0) {\n const trigger = triggers[0];\n return jsonResponse({\n suggested: true,\n trigger: trigger.type,\n currentModel: agentState.model,\n suggestedModel: trigger.suggestedModel,\n reason: trigger.reason,\n });\n }\n\n return jsonResponse({\n suggested: false,\n trigger: null,\n currentModel: agentState.model,\n suggestedModel: null,\n reason: 'No handoff triggers detected',\n });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/handoff ─────────────────────────────────────\n\nexport const postAgentHandoffRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/handoff',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n\n const { toModel, reason } = body as any;\n let targetModel: string;\n try {\n targetModel = requireModelOverrideSync(toModel);\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n\n const result = yield* performHandoff(id, {\n targetModel,\n reason: reason || 'Manual handoff from dashboard',\n });\n\n if (result.success) {\n return jsonResponse({\n success: true,\n newAgentId: result.newAgentId,\n newSessionId: result.newSessionId,\n });\n } else {\n return jsonResponse({ success: false, error: result.error }, { status: 500 });\n }\n })),\n);\n\n\n// ─── Route: GET /api/agents/:id/cost ─────────────────────────────────────────\n\nexport const getAgentCostRoute = HttpRouter.add(\n 'GET',\n '/api/agents/:id/cost',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: 'Agent not found' }, { status: 404 });\n }\n\n let cost = 0;\n let inputTokens = 0;\n let outputTokens = 0;\n let cacheReadTokens = 0;\n let cacheWriteTokens = 0;\n let detectedModel = agentState.model || '';\n // Claude Code repeats the same `usage` on every JSONL line of one API response\n // (text line, each tool_use line, …). Dedup on requestId/message.id so a multi-block\n // turn is counted once instead of inflating tokens/cost ~2-3×.\n const countedUsageIds = new Set<string>();\n\n const homeDir = process.env.HOME || homedir();\n const claudeProjectsDir = join(homeDir, '.claude', 'projects');\n const workspacePath = agentState.workspace;\n\n if (workspacePath) {\n const projectDirName = encodeClaudeProjectDir(workspacePath);\n const projectDir = join(claudeProjectsDir, projectDirName);\n const sessionsIndexPath = join(projectDir, 'sessions-index.json');\n\n const parseJsonlCost = async (filePath: string) => {\n const jsonlContent = await readFile(filePath, 'utf-8');\n const lines = jsonlContent.split('\\n').filter((l: string) => l.trim());\n for (const line of lines) {\n try {\n const entry = JSON.parse(line);\n const usage = entry.message?.usage || entry.usage;\n const model = entry.message?.model || entry.model;\n const usageId = entry.requestId ?? entry.message?.id;\n if (usage && (usageId === undefined || !countedUsageIds.has(usageId))) {\n if (usageId !== undefined) countedUsageIds.add(usageId);\n inputTokens += usage.input_tokens || 0;\n outputTokens += usage.output_tokens || 0;\n cacheReadTokens += usage.cache_read_input_tokens || 0;\n cacheWriteTokens += usage.cache_creation_input_tokens || 0;\n }\n if (model && !detectedModel) {\n detectedModel = model;\n }\n } catch {}\n }\n };\n\n if (existsSync(sessionsIndexPath)) {\n try {\n const indexContent = JSON.parse(yield* Effect.promise(() => readFile(sessionsIndexPath, 'utf-8')));\n for (const sessionEntry of (indexContent.entries || [])) {\n if (sessionEntry?.fullPath && existsSync(sessionEntry.fullPath)) {\n yield* Effect.promise(() => parseJsonlCost(sessionEntry.fullPath));\n }\n }\n } catch {}\n }\n\n if (inputTokens === 0 && existsSync(projectDir)) {\n try {\n const files = (yield* Effect.promise(() => readdir(projectDir))).filter(f => f.endsWith('.jsonl'));\n for (const file of files) {\n yield* Effect.promise(() => parseJsonlCost(join(projectDir, file)));\n }\n } catch {}\n }\n }\n\n if (inputTokens > 0 || outputTokens > 0) {\n const modelInfo = normalizeModelName(detectedModel || 'claude-sonnet-4');\n const pricing = getPricingSync(modelInfo.provider, modelInfo.model);\n if (pricing) {\n const usage: TokenUsage = {\n inputTokens,\n outputTokens,\n cacheReadTokens,\n cacheWriteTokens,\n };\n cost = calculateCostSync(usage, pricing);\n }\n }\n\n return jsonResponse({\n agentId: id,\n model: detectedModel || agentState.model,\n tokens: {\n input: inputTokens,\n output: outputTokens,\n cacheRead: cacheReadTokens,\n cacheWrite: cacheWriteTokens,\n },\n cost,\n });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/delivery-method ─────────────────────────────\n// Updates the agent's delivery method (auto | channels | tmux) in state.json.\n\nexport function validateAgentDeliveryMethodOrigin(\n request: HttpServerRequest.HttpServerRequest,\n): { ok: true } | { ok: false; status: 403; body: { error: 'forbidden' } } {\n const originCheck = validateOrigin(request);\n if (originCheck.ok) return { ok: true };\n return { ok: false, status: 403, body: { error: 'forbidden' } };\n}\n\nexport const postAgentDeliveryMethodRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/delivery-method',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originDecision = validateAgentDeliveryMethodOrigin(request);\n if (!originDecision.ok) {\n return jsonResponse(originDecision.body, { status: originDecision.status });\n }\n\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n const body = yield* readJsonBody;\n const { deliveryMethod } = body as { deliveryMethod?: 'auto' | 'channels' | 'tmux' };\n\n if (!deliveryMethod || !['auto', 'channels', 'tmux'].includes(deliveryMethod)) {\n return jsonResponse({ error: 'deliveryMethod must be auto, channels, or tmux' }, { status: 400 });\n }\n\n const agentState = yield* getAgentState(id);\n if (!agentState) {\n return jsonResponse({ error: `Agent ${id} not found` }, { status: 404 });\n }\n\n yield* Effect.promise(() => setAgentDeliveryMethod(id, deliveryMethod));\n return jsonResponse({ success: true, agentId: id, deliveryMethod });\n })),\n);\n\n// ─── Route: POST /api/agents/:id/switch-model ────────────────────────────────\n// Pipeline agent models are fixed at spawn. Changing a model tears down the\n// live session and discards context, so this route is retained only as a\n// server-side compatibility rejection for older clients.\n\nexport const postAgentSwitchModelRoute = HttpRouter.add(\n 'POST',\n '/api/agents/:id/switch-model',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const id = params['id'] ?? '';\n return jsonResponse({\n error: `Agent ${id} model is locked once the agent is spawned`,\n }, { status: 409 });\n })),\n);\n","import { mkdir, writeFile } from 'fs/promises';\nimport { join } from 'path';\nimport { randomUUID } from 'crypto';\nimport { Effect } from 'effect';\n\nimport { generateVBriefFilename } from './lifecycle.js';\nimport { serializeVBriefDocument } from './io.js';\nimport { FsError } from '../errors.js';\nimport type { VBriefDocument, VBriefSubItem } from './types.js';\n\nexport interface AutoSynthesizeIssueInput {\n issueId: string;\n title: string;\n body?: string | null;\n url?: string | null;\n}\n\nexport interface AutoSynthesizeResult {\n document: VBriefDocument;\n workspaceSpecPath: string;\n projectSpecPath: string;\n canonicalFilename: string;\n}\n\nfunction cleanMarkdownLine(line: string): string {\n return line\n .replace(/^[-*+]\\s+\\[[ xX]\\]\\s+/, '')\n .replace(/^[-*+]\\s+/, '')\n .replace(/^#+\\s+/, '')\n .replace(/`([^`]+)`/g, '$1')\n .trim();\n}\n\nexport function extractAcceptanceCriteriaFromIssue(title: string, body?: string | null): string[] {\n const text = body?.trim() ?? '';\n if (!text) return [`Implement ${title}`];\n\n const lines = text.split(/\\r?\\n/);\n // Only a real heading (or standalone bold/label line) counts — the phrase\n // appearing mid-prose must not start a section (PAN-2404).\n const headingIndex = lines.findIndex((line) =>\n /^#{1,6}\\s+.*acceptance(\\s+criteria)?\\b/i.test(line)\n || /^\\s*\\*{0,2}acceptance criteria\\*{0,2}:?\\s*$/i.test(line));\n\n // If an explicit Acceptance Criteria section exists, use only that section\n if (headingIndex >= 0) {\n // Find the end of this section (next heading or end of text)\n const sectionEndIndex = lines.findIndex((line, idx) =>\n idx > headingIndex && /^#+\\s+/.test(line)\n );\n const endIndex = sectionEndIndex >= 0 ? sectionEndIndex : lines.length;\n const sectionLines = lines.slice(headingIndex + 1, endIndex);\n\n const checklist = sectionLines\n .filter((line) => /^\\s*[-*+]\\s+\\[[ xX]\\]\\s+/.test(line))\n .map(cleanMarkdownLine)\n .filter(Boolean);\n if (checklist.length > 0) return checklist;\n\n const bullets = sectionLines\n .filter((line) => /^\\s*[-*+]\\s+/.test(line))\n .map(cleanMarkdownLine)\n .filter(Boolean)\n .slice(0, 8);\n if (bullets.length > 0) return bullets;\n }\n\n // No explicit Acceptance Criteria section: return a single generic AC\n return [`Issue's stated fix implemented with tests`];\n}\n\nexport function synthesizeMinimalVBrief(issue: AutoSynthesizeIssueInput): VBriefDocument {\n const issueId = issue.issueId.toUpperCase();\n const issueLabel = issueId.toLowerCase();\n const now = new Date().toISOString();\n const criteria = extractAcceptanceCriteriaFromIssue(issue.title, issue.body);\n const items: VBriefSubItem[] = criteria.map((criterion, index) => ({\n id: `auto-start.ac${index + 1}`,\n title: criterion,\n status: 'pending',\n created: now,\n metadata: { kind: 'acceptance_criterion' },\n }));\n\n const canonicalFilename = generateVBriefFilename(issueId, issue.title, now);\n\n return {\n vBRIEFInfo: {\n version: '0.8',\n created: now,\n updated: now,\n author: 'overdeck/auto-start',\n description: `Auto-synthesized minimal plan for ${issueId}: ${issue.title}`,\n inspectionPolicy: 'never',\n },\n plan: {\n id: issueLabel,\n title: issue.title,\n status: 'proposed',\n uid: randomUUID(),\n author: 'overdeck/auto-start',\n sequence: 1,\n created: now,\n updated: now,\n references: issue.url ? [{ uri: issue.url, label: issueId, type: 'issue' }] : [],\n tags: ['auto-start'],\n metadata: { canonicalFilename },\n narratives: {\n Problem: issue.body?.trim() || issue.title,\n Proposal: 'Implement the issue directly from the tracker-provided title and body.',\n },\n items: [\n {\n id: 'auto-start',\n title: 'Implement issue',\n status: 'pending',\n priority: 'medium',\n created: now,\n metadata: {\n difficulty: 'simple',\n issueLabel,\n requiresInspection: false,\n inspectionDepth: 'fast',\n },\n narrative: { Action: issue.body?.trim() || issue.title },\n items,\n },\n ],\n edges: [],\n },\n };\n}async function writeAutoStartVBriefPromise(\n projectRoot: string,\n workspacePath: string,\n issue: AutoSynthesizeIssueInput,\n): Promise<AutoSynthesizeResult> {\n const document = synthesizeMinimalVBrief(issue);\n const canonicalFilename = document.plan.metadata?.canonicalFilename as string;\n\n const projectSpecsDir = join(projectRoot, '.pan', 'specs');\n const projectSpecPath = join(projectSpecsDir, canonicalFilename);\n const projectDocument: VBriefDocument = {\n ...document,\n plan: { ...document.plan, status: 'proposed' },\n };\n\n await mkdir(projectSpecsDir, { recursive: true });\n await writeFile(projectSpecPath, serializeVBriefDocument(projectDocument), 'utf-8');\n\n return {\n document,\n workspaceSpecPath: projectSpecPath,\n projectSpecPath,\n canonicalFilename,\n };\n}\n\n// ─── Effect variant (PAN-1249) ────────────────────────────────────────────────\n\n/**\n * Effect variant of writeAutoStartVBrief. Wraps mkdir + writeFile in a typed\n * FsError channel so callers in Effect-native code can compose with this\n * synthesis step without losing failure typing.\n */\nexport const writeAutoStartVBrief = (\n projectRoot: string,\n workspacePath: string,\n issue: AutoSynthesizeIssueInput,\n): Effect.Effect<AutoSynthesizeResult, FsError> =>\n Effect.tryPromise({\n try: () => writeAutoStartVBriefPromise(projectRoot, workspacePath, issue),\n catch: (cause) =>\n new FsError({ path: projectRoot, operation: 'writeAutoStartVBrief', cause }),\n });\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { lstat, mkdir, readFile, rm } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport type { Role } from '@overdeck/contracts';\nimport { Effect } from 'effect';\n\nimport { buildChildEnvWithoutTmuxSync } from '../../../../lib/child-env.js';\nimport {\n saveAgentState,\n saveAgentStateSync,\n} from '../../../../lib/agents.js';\nimport type { RemoteWorkspaceMetadata } from '../../../../lib/remote/interface.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { saveAgentStateAndEmitEventProgram } from '../../services/agent-projection.js';\nimport {\n appendAgentLifecycleLog,\n buildPanStartArgs,\n emitStartAgentPhase,\n execAsync,\n execFileAsync,\n getIssueDataService,\n invalidateAgentsCache,\n updateRegistryForAgentStart,\n type SpawnGuardrailDecision,\n} from './shared.js';\n\ntype LifecycleTransition = {\n transitionTo: (\n issueId: string,\n status: 'in_progress',\n ) => Effect.Effect<unknown, unknown, never>;\n};\n\ntype EventStoreAppend = {\n append: (event: {\n type: 'issue.statusChanged';\n timestamp: string;\n payload: { issueId: string; status: 'In Progress'; canonicalStatus: 'in_progress' };\n }) => Effect.Effect<unknown, unknown, never>;\n};\n\ntype SpawnPanCommand = (args: string[], cwd?: string) => Promise<string>;\n\nexport function handleRemoteAgentSpawn(input: {\n issueId: string;\n workspacePath: string;\n workspaceMetadata: RemoteWorkspaceMetadata;\n spawnModel: string;\n projectPath: string;\n spawnGuardrails: SpawnGuardrailDecision;\n lifecycle: LifecycleTransition;\n}) {\n return Effect.gen(function* () {\n const {\n issueId,\n workspacePath,\n workspaceMetadata,\n spawnModel,\n projectPath,\n spawnGuardrails,\n lifecycle,\n } = input;\n\n const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import('../../../../lib/remote/remote-agents.js'));\n const { createFlyProviderFromConfig } = yield* Effect.promise(() => import('../../../../lib/remote/index.js'));\n const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import('../../../../lib/config.js'));\n const panConfig = loadPanConfig();\n const spendCap = checkRemoteSpendCap(panConfig);\n if (!spendCap.allowed) {\n return jsonResponse({ error: spendCap.message }, { status: 429 });\n }\n const fly = createFlyProviderFromConfig(panConfig.remote);\n yield* Effect.promise(() => fly.syncAllCredentials(workspaceMetadata.vmName));\n\n const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import('../../../../lib/cloister/work-agent-prompt.js'));\n const trackerContext = yield* Effect.promise(() => getTrackerContext(issueId, workspacePath));\n const agentPrompt = yield* Effect.promise(() => buildWorkAgentPrompt({\n issueId,\n env: 'REMOTE',\n workspacePath: '/workspace',\n skipDynamicContext: true,\n trackerContext,\n }));\n\n emitStartAgentPhase(issueId, 'spawn', 'start', 'starting remote work agent', {\n workspacePath,\n vmName: workspaceMetadata.vmName,\n });\n const state = yield* Effect.promise(() => spawnRemoteAgent({\n issueId,\n workspace: workspaceMetadata,\n prompt: agentPrompt,\n model: spawnModel,\n tier: fly.getResiliencyTier(),\n }));\n\n // Write canonical state.json so activeRoleRunExists() sees this remote\n // work agent as active before we emit the lifecycle transition below.\n // spawnRemoteAgent only writes remote-state.json; without state.json the\n // Cloister duplicate-spawn guard misses the in-flight remote agent and\n // would spawn a second local work run when in_progress is emitted.\n yield* saveAgentState({\n id: state.id,\n issueId: state.issueId,\n workspace: workspacePath,\n role: 'work',\n model: spawnModel,\n status: 'starting',\n startedAt: state.startedAt,\n harness: 'claude-code',\n });\n updateRegistryForAgentStart(state.issueId, workspacePath, state.id);\n\n // PAN-1048: lifecycle.transitionTo() is the single source of issue.transitioned.\n // The redundant issue.statusChanged emit was racing with reactive Cloister:\n // Cloister mapped 'in_progress' → 'work' role and tried to spawn a second\n // run while activeRoleRunExists() still saw no state.json for the\n // in-flight spawn above. state.json is now written before this emit.\n yield* Effect.promise(() => Effect.runPromise(\n lifecycle.transitionTo(issueId, 'in_progress').pipe(Effect.catch(() => Effect.void))\n ));\n\n // PAN-1048 review feedback 003: emit a contract-compliant agent.started\n // event. The reducer writes event.payload.agent into agentsById keyed by\n // event.payload.agentId — the previous shape ({ agentId: issueId, issueId })\n // omitted .agent and used the issue ID as the key, inserting `undefined`\n // into the read model and breaking dashboard consumers.\n //\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram({\n id: state.id,\n issueId: state.issueId,\n workspace: workspacePath,\n role: 'work',\n model: spawnModel,\n status: state.status,\n startedAt: state.startedAt,\n harness: 'claude-code',\n }, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: state.id,\n issueId: state.issueId,\n agent: {\n id: state.id,\n issueId: state.issueId,\n role: 'work' as const,\n model: spawnModel,\n status: state.status,\n startedAt: state.startedAt,\n lastActivity: state.lastActivity,\n },\n },\n });\n emitStartAgentPhase(issueId, 'spawn', 'success', 'remote work agent spawn requested', {\n agentId: state.id,\n vmName: workspaceMetadata.vmName,\n });\n try { getIssueDataService().patchIssue(issueId, { status: 'In Progress', canonicalStatus: 'in_progress' }); } catch { /* non-fatal */ }\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n message: `Starting remote agent for ${issueId}`,\n remote: true,\n vmName: workspaceMetadata.vmName,\n agentId: state.id,\n projectPath,\n guardrails: spawnGuardrails,\n });\n });\n}\n\nexport function handleContainerOrchestration(input: {\n issueId: string;\n workspacePath: string;\n devScript: string;\n agentSessionName: string;\n role: Role;\n effectiveHarness: 'claude-code' | 'ohmypi' | 'codex' | null;\n allowHost: boolean;\n spawnModel: string;\n spawnGuardrails: SpawnGuardrailDecision;\n projectPath: string;\n eventStore: EventStoreAppend;\n spawnPanCommand: SpawnPanCommand;\n updateIssueStatus: () => Promise<void>;\n}) {\n return Effect.gen(function* () {\n const {\n issueId,\n workspacePath,\n devScript,\n agentSessionName,\n role,\n effectiveHarness,\n allowHost,\n spawnModel,\n spawnGuardrails,\n projectPath,\n eventStore,\n spawnPanCommand,\n updateIssueStatus,\n } = input;\n\n if (existsSync(workspacePath) && existsSync(devScript)) {\n let dockerRunning = false;\n try {\n yield* Effect.promise(() => execAsync('docker info >/dev/null 2>&1', { encoding: 'utf-8' }));\n dockerRunning = true;\n } catch {}\n\n if (dockerRunning) {\n const getComposeProjectName = async (id: string, wPath: string): Promise<string> => {\n const featureFolder = `feature-${id.toLowerCase()}`;\n const expected = `overdeck-${featureFolder}`;\n const validate = (value: string, devPath: string): string => {\n if (value !== expected) {\n throw new Error(`Invalid COMPOSE_PROJECT_NAME in ${devPath}: expected ${expected}`);\n }\n return value;\n };\n\n const devScriptPaths = [join(wPath, '.devcontainer', 'dev'), join(wPath, 'dev')];\n for (const devPath of devScriptPaths) {\n try {\n if (existsSync(devPath)) {\n const content = await readFile(devPath, 'utf-8');\n const match = content.match(/COMPOSE_PROJECT_NAME=\"([^$\"]*)\\$\\{FEATURE_FOLDER\\}\"/);\n if (match) return validate(`${match[1]}${featureFolder}`, devPath);\n const literalMatch = content.match(/COMPOSE_PROJECT_NAME=\"([^\"]+)\"/);\n if (literalMatch) return validate(literalMatch[1], devPath);\n }\n } catch (error) {\n if (error instanceof Error && error.message.startsWith('Invalid COMPOSE_PROJECT_NAME')) throw error;\n }\n }\n return expected;\n };\n\n let featureName: string;\n try {\n featureName = yield* Effect.promise(() => getComposeProjectName(issueId, workspacePath));\n } catch (error) {\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: error instanceof Error ? error.message : String(error),\n }, { status: 422 });\n }\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_container_check', {\n issueId,\n featureName,\n workspacePath,\n }));\n let containersReady = false;\n\n try {\n const { stdout: existing } = yield* Effect.promise(() => execFileAsync(\n 'docker',\n ['ps', '--filter', `name=${featureName}`, '--format', '{{.Names}}|{{.Status}}'],\n { encoding: 'utf-8' }\n ));\n const runningContainers = existing.trim().split('\\n').filter(Boolean);\n const allHealthy = runningContainers.length > 0 && runningContainers.every(line => {\n const status = line.split('|')[1] || '';\n return status.includes('Up') && (!status.includes('(') || status.includes('(healthy)'));\n });\n if (allHealthy) containersReady = true;\n } catch {}\n\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_container_check_result', {\n issueId,\n featureName,\n containersReady,\n }));\n\n if (!containersReady && !allowHost) {\n const earlyAgentId = agentSessionName;\n const earlyStateDir = join(homedir(), '.overdeck', 'agents', earlyAgentId);\n yield* Effect.promise(() => mkdir(earlyStateDir, { recursive: true }));\n // PAN-1048 R2: legacy `runtime` field removed; PAN-1055: persist user-picked harness.\n saveAgentStateSync({\n id: earlyAgentId,\n issueId,\n ...(effectiveHarness ? { harness: effectiveHarness } : {}),\n model: 'pending-container-start',\n status: 'starting',\n startedAt: new Date().toISOString(),\n workspace: workspacePath,\n role,\n hostOverride: allowHost || undefined,\n });\n updateRegistryForAgentStart(issueId, workspacePath, earlyAgentId);\n yield* Effect.promise(() => appendAgentLifecycleLog(earlyAgentId, 'agent.start_waiting_for_containers', {\n issueId,\n featureName,\n workspacePath,\n role,\n }));\n\n const containerActivityId = `containers-${Date.now()}`;\n\n // Start containers in background and spawn agent when ready\n (async () => {\n try {\n const containerUid = process.getuid?.() ?? 1000;\n const containerGid = process.getgid?.() ?? 1000;\n await appendAgentLifecycleLog(earlyAgentId, 'agent.container_start_spawned', {\n issueId,\n featureName,\n workspacePath,\n });\n const containerChild = spawn('./dev', ['all'], {\n cwd: workspacePath,\n stdio: 'ignore',\n env: buildChildEnvWithoutTmuxSync(process.env, { UID: String(containerUid), GID: String(containerGid), DOCKER_USER: `${containerUid}:${containerGid}` }),\n detached: true,\n });\n containerChild.unref();\n\n const maxWaitMs = 3 * 60 * 1000;\n const pollIntervalMs = 3000;\n const startTime = Date.now();\n let healthy = false;\n\n while (Date.now() - startTime < maxWaitMs) {\n try {\n const { stdout } = await execFileAsync(\n 'docker',\n ['ps', '--filter', `name=${featureName}`, '--format', '{{.Names}}|{{.Status}}'],\n { encoding: 'utf-8' }\n );\n const containers = stdout.trim().split('\\n').filter(Boolean);\n const allH = containers.length > 0 && containers.every(line => {\n const status = line.split('|')[1] || '';\n return status.includes('Up') && (!status.includes('(') || status.includes('(healthy)'));\n });\n if (allH) { healthy = true; break; }\n } catch {}\n await new Promise(r => setTimeout(r, pollIntervalMs));\n }\n\n await appendAgentLifecycleLog(earlyAgentId, healthy ? 'agent.container_wait_succeeded' : 'agent.container_wait_timed_out', {\n issueId,\n featureName,\n waitedMs: Date.now() - startTime,\n });\n\n if (!healthy) {\n // PAN-1048 R2: legacy `runtime` removed; PAN-1055: persist user-picked harness.\n saveAgentStateSync({\n id: earlyAgentId,\n issueId,\n ...(effectiveHarness ? { harness: effectiveHarness } : {}),\n model: 'pending-container-start',\n status: 'error',\n startedAt: new Date().toISOString(),\n workspace: workspacePath,\n role,\n });\n return;\n }\n\n // Docker named volumes may create root-owned empty node_modules.\n // Remove them — workspace creation runs bun install which creates\n // correct workspace-aware node_modules with proper local package resolution.\n for (const nmDir of [join(workspacePath, 'node_modules'), join(workspacePath, 'src', 'dashboard', 'frontend', 'node_modules')]) {\n try {\n if (existsSync(nmDir)) {\n const stat = await lstat(nmDir);\n if (!stat.isSymbolicLink()) {\n await rm(nmDir, { recursive: true, force: true });\n console.log(`[start-agent] Removed Docker-created ${nmDir}`);\n }\n }\n } catch (nmErr: any) {\n console.warn(`[start-agent] Could not remove ${nmDir}: ${nmErr.message}`);\n }\n }\n\n await appendAgentLifecycleLog(earlyAgentId, 'agent.work_spawn_requested_after_containers', {\n issueId,\n role,\n workspacePath,\n harness: effectiveHarness,\n });\n emitStartAgentPhase(issueId, 'spawn', 'start', 'starting local work agent after containers became healthy', { workspacePath });\n const activityId = await spawnPanCommand(\n buildPanStartArgs({\n issueId,\n model: spawnModel,\n harness: effectiveHarness,\n allowHost,\n }),\n workspacePath,\n );\n emitStartAgentPhase(issueId, 'spawn', 'success', 'local work agent spawn requested after container startup', {\n workspacePath,\n activityId,\n });\n await updateIssueStatus();\n } catch (err: any) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n emitStartAgentPhase(issueId, 'spawn', 'failure', errorMessage, { workspacePath });\n await appendAgentLifecycleLog(earlyAgentId, 'agent.container_start_failed', {\n issueId,\n error: errorMessage,\n }).catch(() => undefined);\n // PAN-1048 R2: legacy `runtime` removed from state writes.\n try { saveAgentStateSync({\n id: earlyAgentId,\n issueId,\n model: 'pending-container-start',\n status: 'error',\n startedAt: new Date().toISOString(),\n workspace: workspacePath,\n role,\n }); } catch { /* non-fatal */ }\n console.error(`[start-agent] Background container startup failed for ${issueId}:`, err);\n }\n })();\n\n yield* Effect.promise(() => Effect.runPromise(eventStore.append({\n type: 'issue.statusChanged',\n timestamp: new Date().toISOString(),\n payload: { issueId, status: 'In Progress', canonicalStatus: 'in_progress' },\n })));\n try { getIssueDataService().patchIssue(issueId, { status: 'In Progress', canonicalStatus: 'in_progress' }); } catch { /* non-fatal */ }\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n message: `Starting containers and agent for ${issueId} (this may take a few minutes)`,\n startingContainers: true,\n containerActivityId,\n agentId: earlyAgentId,\n projectPath,\n guardrails: spawnGuardrails,\n });\n }\n }\n }\n\n return null;\n });\n}\n","import { spawn } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { cp, mkdir } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { dirname, join } from 'node:path';\n\nimport { Effect } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { saveAgentStateSync, determineModel, getProviderAuthMode, getAgentState } from '../../../../lib/agents.js';\nimport { buildChildEnvWithoutTmuxSync } from '../../../../lib/child-env.js';\nimport { checkCodexAuthStatus } from '../../../../lib/codex-auth.js';\nimport { canUseHarnessSync } from '../../../../lib/harness-policy.js';\nimport { emitActivityEntrySync } from '../../../../lib/activity-logger.js';\nimport { extractPrefixSync, parseIssueIdSync } from '../../../../lib/issue-id.js';\nimport { PAN_CONTINUE_FILENAME, PAN_DIRNAME } from '../../../../lib/pan-dir/types.js';\nimport { loadWorkspaceMetadataSync as loadWorkspaceMetadataFn } from '../../../../lib/remote/workspace-metadata.js';\nimport { getWorkAgentLifecycleState } from '../../../../lib/work-agent-lifecycle.js';\nimport { withBdMutex } from '../../../../lib/bd-mutex.js';\nimport { validateProviderHealth } from '../../../../lib/provider-health.js';\nimport { getProjectSync, resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { isStateMigrated } from '../../../../lib/state-home.js';\nimport { shouldCommitLegacyWorkspaceArtifacts } from '../../../../lib/state-read-home.js';\nimport { getWorkspaceStackHealth } from '../../../../lib/workspace/stack-health.js';\nimport { writeAutoStartVBrief } from '../../../../lib/vbrief/auto-synthesize.js';\nimport { findPlan, readPlan } from '../../../../lib/vbrief/io.js';\nimport { transitionVBriefOnMain, updatePlanStatus } from '../../../../lib/vbrief/lifecycle-io.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { ReadModelService } from '../../read-model.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { saveAgentStateAndEmitEventProgram } from '../../services/agent-projection.js';\nimport { IssueLifecycle } from '../../services/issue-lifecycle.js';\nimport { getSystemHealthSnapshot } from '../../services/system-health-service.js';\nimport { httpHandler } from '../http-handler.js';\nimport { validateOrigin } from '../origin-validation.js';\nimport { sessionExists, killSession } from '../../../../lib/tmux.js';\nimport {\n appendAgentLifecycleLog,\n buildHostOverrideConfirmation,\n buildPanStartArgs,\n emitStartAgentPhase,\n evaluateAgentStartGate,\n evaluateSpawnGuardrails,\n execAsync,\n execFileAsync,\n getIssueDataService,\n getProjectPath,\n invalidateAgentsCache,\n readJsonBody,\n spawnPanCommandDetached,\n updateRegistryForAgentStart,\n} from './shared.js';\nimport {\n handleContainerOrchestration,\n handleRemoteAgentSpawn,\n} from './spawn-helpers.js';\n\n// ─── Helpers ─────────────────────────────────────────────────────────────────\n\n/**\n * PAN-2386: emit a dashboard activity event when start-agent refuses to spawn\n * because the workspace is dirty. Kept as a pure function so the refusal path\n * can be unit-tested without booting the full Effect route stack.\n */\nexport function emitDirtyWorkspaceRefusalActivity(issueId: string, porcelain: string): void {\n try {\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'warn',\n message: `Workspace dirty — agent start refused for ${issueId}`,\n issueId,\n details: JSON.stringify({\n reason: 'Workspace has uncommitted changes. Commit, discard, or resolve and retry.',\n porcelain: porcelain.split('\\n').slice(0, 5),\n }),\n });\n } catch { /* non-fatal — activity emit should not block the response */ }\n}\n\nexport function spawnGuardrailResourcesHint(hint?: string): string {\n const resourcesHint = 'Open /resources to inspect Machine Room pressure before retrying.';\n return hint ? `${hint} ${resourcesHint}` : resourcesHint;\n}\n\n/**\n * Count materialized beads for an issue by shelling out to `bd list`.\n *\n * Returns 0 on ANY bd failure — non-zero exit (e.g. bd refusing with \"Linear\n * data has never been pulled\"), an unparseable JSON body, or the 10s timeout.\n * The caller treats 0 as \"no beads yet\" and falls through to auto-recovery\n * (createBeadsFromVBrief) and, failing that, a clean 422 `beads_required`.\n *\n * The failure is folded into the success channel with `Effect.matchCause`. A\n * bare `try/catch` around `yield* withBdMutex(...)` does NOT work: withBdMutex\n * converts the child-process rejection into an Effect error-channel failure\n * (via its internal `Effect.tryPromise`), which short-circuits the fiber and is\n * invisible to a JS `try/catch`. Before this helper, that escaped as an\n * unhandled 500 on POST /api/agents instead of the designed 422.\n */\nexport const countBeadsForIssue = (\n workspacePath: string,\n issueLower: string,\n): Effect.Effect<number, never> =>\n withBdMutex(() => Effect.promise(() => execFileAsync(\n 'bd',\n ['list', '--json', '-l', issueLower, '--status', 'all', '--limit', '0'],\n { cwd: workspacePath, encoding: 'utf-8', timeout: 10000 },\n ))).pipe(\n Effect.matchCause({\n onFailure: () => 0,\n onSuccess: ({ stdout }) => {\n try {\n const bdTasks = JSON.parse(stdout.trim() || '[]');\n return Array.isArray(bdTasks) ? bdTasks.length : 0;\n } catch {\n return 0;\n }\n },\n }),\n );\n\n// ─── Route: POST /api/agents (start agent) ───────────────────────────────────\n\nexport const postAgentsRoute = HttpRouter.add(\n 'POST',\n '/api/agents',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const originCheck = validateOrigin(request);\n if (!originCheck.ok) {\n return jsonResponse({ ok: false, error: originCheck.error }, { status: 403 });\n }\n\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const lifecycle = yield* IssueLifecycle;\n const readModel = yield* ReadModelService;\n\n const { issueId, projectId } = body as any;\n const autoStart = (body as any).auto === true;\n const guardrailAcknowledged = (body as any).guardrailAcknowledged === true;\n const requestedHostOverride = (body as any).host === true || (body as any).allowHost === true;\n\n if (!issueId) {\n return jsonResponse({ error: 'issueId required' }, { status: 400 });\n }\n\n const legacyFields = ['workType', 'phase', 'agentType'].filter((field) => field in (body as Record<string, unknown>));\n if (legacyFields.length > 0) {\n return jsonResponse({\n error: `Legacy start-agent field(s) are no longer accepted: ${legacyFields.join(', ')}. Send role: 'work' instead.`,\n }, { status: 400 });\n }\n\n const role = (body as any).role ?? 'work';\n if (role !== 'work') {\n return jsonResponse({ error: `Unsupported agent role \"${String(role)}\". POST /api/agents only starts role: 'work'.` }, { status: 400 });\n }\n\n // Reject bare numeric IDs (e.g. \"484\") — they have no project prefix, so tracker\n // routing and workspace naming both fail. Require \"PAN-484\" style.\n if (/^\\d+$/.test(String(issueId))) {\n return jsonResponse(\n {\n error: `Invalid issueId \"${issueId}\": bare numeric IDs are not allowed. Use a prefixed ID (e.g. PAN-${issueId}).`,\n hint: 'Issue IDs must include a project prefix (e.g. PAN-484, MIN-123).',\n },\n { status: 422 },\n );\n }\n\n const parsedIssueId = parseIssueIdSync(String(issueId));\n if (!parsedIssueId) {\n return jsonResponse(\n {\n error: `Invalid issueId \"${issueId}\": issue IDs must use a supported project format (e.g. PAN-484, MIN-123).`,\n hint: 'Issue IDs must include a project prefix and numeric identifier.',\n },\n { status: 422 },\n );\n }\n\n const hostOverrideConfirmation = buildHostOverrideConfirmation(String(issueId));\n const allowHost = requestedHostOverride && (body as any).hostOverrideConfirmation === hostOverrideConfirmation;\n if (requestedHostOverride && !allowHost) {\n return jsonResponse({\n success: false,\n error: 'host_override_confirmation_required',\n requiresHostConfirmation: true,\n confirmation: hostOverrideConfirmation,\n hint: `Host override bypasses workspace isolation. Retry only after explicitly confirming: ${hostOverrideConfirmation}`,\n }, { status: 409 });\n }\n\n // Guard: reject starting agents for already-closed issues\n const issueDataService = getIssueDataService();\n const cachedIssues = issueDataService.getIssues();\n const cachedIssue = cachedIssues.find(\n (i: any) => (i.identifier || '').toUpperCase() === issueId.toUpperCase()\n );\n if (cachedIssue && (cachedIssue.canonicalStatus === 'done' || cachedIssue.canonicalStatus === 'canceled')) {\n return jsonResponse(\n {\n error: `Issue ${issueId} is already closed (${cachedIssue.canonicalStatus}). Cannot start an agent for a closed issue.`,\n hint: 'Reopen the issue first if you need to resume work.',\n },\n { status: 422 },\n );\n }\n\n const issueLower = parsedIssueId.normalized;\n const agentSessionName = `agent-${issueLower}`;\n const startGateBlock = evaluateAgentStartGate(agentSessionName, yield* getAgentState(agentSessionName));\n if (startGateBlock) {\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_blocked_gate', {\n issueId,\n paused: startGateBlock.paused,\n troubled: startGateBlock.troubled,\n reason: startGateBlock.error,\n }));\n return jsonResponse(startGateBlock, { status: 409 });\n }\n\n const workspaceMetadata = loadWorkspaceMetadataFn(issueId);\n const isRemote = workspaceMetadata?.location === 'remote';\n\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const resolvedProject = resolveProjectFromIssueSync(String(issueId));\n const projectConfig = resolvedProject ? getProjectSync(resolvedProject.projectKey) : null;\n const projectPath = projectConfig?.path ?? getProjectPath(projectId, issuePrefix);\n\n const workspacePath = join(projectPath, 'workspaces', `feature-${issueLower}`);\n if (!existsSync(workspacePath)) {\n try {\n const nodeDir = dirname(process.execPath);\n yield* Effect.promise(() => execAsync(\n `pan workspace create ${issueId} --local`,\n { cwd: projectPath, encoding: 'utf-8', timeout: 60000, env: buildChildEnvWithoutTmuxSync(process.env, { PATH: `${nodeDir}:${process.env.PATH ?? ''}` }) }\n ));\n } catch (wsErr) {\n return jsonResponse({\n error: `Failed to create workspace for ${issueId}: ${(wsErr as Error).message}`,\n hint: 'Try creating the workspace manually: pan workspace create ' + issueId + ' --local',\n }, { status: 500 });\n }\n }\n\n const workspacePanDir = join(workspacePath, PAN_DIRNAME);\n const workspacePanContinuePath = join(workspacePanDir, PAN_CONTINUE_FILENAME);\n\n const workspaceBeadsDir = join(workspacePath, '.beads');\n if (!existsSync(workspaceBeadsDir)) {\n const projectRootBeadsDir = join(projectPath, '.beads');\n if (existsSync(projectRootBeadsDir)) {\n try {\n yield* Effect.promise(() => cp(projectRootBeadsDir, workspaceBeadsDir, { recursive: true }));\n } catch {}\n }\n }\n\n let planPath = yield* findPlan(workspacePath);\n if (autoStart && !planPath) {\n const issueTitle = cachedIssue?.title || issueId;\n const issueBody = cachedIssue?.description || '';\n // writeAutoStartVBrief is Effect-returning — yield it directly (PAN-1768).\n yield* writeAutoStartVBrief(projectPath, workspacePath, {\n issueId,\n title: issueTitle,\n body: issueBody,\n url: cachedIssue?.url,\n });\n planPath = yield* findPlan(workspacePath);\n }\n if (!planPath) {\n return jsonResponse({\n error: `No workspace vBRIEF found for ${issueId}. Work agents require a finalized plan with matching beads.`,\n hint: 'Run planning first, or use auto-start to synthesize a plan before starting the work agent.',\n issueId,\n }, { status: 422 });\n }\n\n const planReadResult = yield* readPlan(planPath).pipe(\n Effect.match({\n onFailure: (planErr) => ({ _tag: 'failure' as const, planErr }),\n onSuccess: (planDoc) => ({ _tag: 'success' as const, planDoc }),\n }),\n );\n if (planReadResult._tag === 'failure') {\n const { planErr } = planReadResult;\n return jsonResponse({\n error: `Could not read workspace vBRIEF for ${issueId}: ${planErr instanceof Error ? planErr.message : String(planErr)}`,\n hint: 'Re-run planning to produce a readable vBRIEF before starting the work agent.',\n issueId,\n }, { status: 422 });\n }\n const { planDoc } = planReadResult;\n\n const planIssueId = planDoc?.plan?.id;\n if (planIssueId && planIssueId.toLowerCase() !== issueLower) {\n return jsonResponse({\n error: `Plan in workspace is for ${planIssueId.toUpperCase()}, not ${issueId}. The workspace contains stale planning artifacts from a different issue.`,\n hint: 'Run planning for this issue first, or clean the workspace planning artifacts.',\n issueId,\n expectedIssue: issueId,\n actualIssue: planIssueId.toUpperCase(),\n }, { status: 422 });\n }\n\n const planItemCount = planDoc?.plan?.items?.length ?? 0;\n if (planItemCount === 0) {\n return jsonResponse({\n error: 'Plan exists but contains no items. Planning may have failed or produced an empty plan.',\n hint: 'Re-run planning to produce a plan with tasks and acceptance criteria.',\n issueId,\n }, { status: 422 });\n }\n\n // countBeadsForIssue swallows all bd failures into 0 — a bd error here (e.g.\n // \"Linear data has never been pulled\" exiting non-zero) must fall through to\n // auto-recovery + a clean 422, never escape as an unhandled 500.\n let beadCount = yield* countBeadsForIssue(workspacePath, issueLower);\n let hasBeads = beadCount > 0;\n if (planItemCount !== null && planItemCount > 0 && beadCount !== planItemCount) {\n hasBeads = false;\n }\n\n let recoveryError: string | null = null;\n if (!hasBeads) {\n // Auto-recovery: beads DB may not have been initialized (fresh install, or planning\n // completed before bd init ran). Attempt to create beads from the vBRIEF plan now.\n console.log(`[agents] No beads for ${issueId} — attempting auto-recovery via createBeadsFromVBrief`);\n try {\n const { createBeadsFromVBrief } = yield* Effect.promise(() => import('../../../../lib/vbrief/beads.js'));\n const recovery = yield* createBeadsFromVBrief(workspacePath);\n beadCount = recovery.created.length;\n hasBeads = recovery.created.length > 0 && (planItemCount === null || recovery.created.length === planItemCount);\n if (hasBeads) {\n console.log(`[agents] Auto-recovery created ${recovery.created.length} beads for ${issueId}`);\n } else if (recovery.created.length > 0 && planItemCount !== null) {\n recoveryError = `created ${recovery.created.length} beads, but vBRIEF has ${planItemCount} plan items`;\n } else if (recovery.errors.length > 0) {\n recoveryError = recovery.errors[0] ?? 'Unknown error during beads creation';\n console.warn(`[agents] Auto-recovery errors: ${recovery.errors.join(', ')}`);\n } else {\n recoveryError = 'createBeadsFromVBrief returned no beads and no errors';\n }\n } catch (recoveryErr: any) {\n recoveryError = recoveryErr.message;\n console.warn(`[agents] Auto-recovery failed: ${recoveryErr.message}`);\n }\n }\n\n if (!hasBeads) {\n // PAN-1048 C6: Beads are a hard requirement for the work role — without\n // them the agent has nothing to claim, no Jidoka inspection scope, and\n // commits would batch across multiple beads. Recovery already attempted\n // above via createBeadsFromVBrief; if that failed the workspace's vBRIEF\n // is missing or malformed, which is a planning bug that must surface to\n // the operator. Refuse the spawn with 422 instead of starting a half-\n // configured work agent that will silently misbehave.\n const detail = recoveryError\n ? ` Beads recovery failed: ${recoveryError}.`\n : ' No beads exist for this issue.';\n console.warn(`[agents] Refusing to start work agent for ${issueId} without beads.${detail}`);\n return jsonResponse({\n success: false,\n error: 'beads_required',\n message: `Cannot start work agent for ${issueId} without beads.${detail} `\n + 'Re-run planning so beads can be materialized from the vBRIEF plan, then retry.',\n ...(recoveryError ? { recoveryError } : {}),\n }, { status: 422 });\n }\n\n const health = yield* readModel.getSnapshot.pipe(\n Effect.flatMap((snapshot) => Effect.promise(() => getSystemHealthSnapshot(snapshot))),\n );\n emitStartAgentPhase(issueId, 'guardrails', 'start', 'evaluating spawn guardrails');\n const spawnGuardrails = evaluateSpawnGuardrails(health);\n if (spawnGuardrails.blocked) {\n emitStartAgentPhase(issueId, 'guardrails', 'failure', spawnGuardrails.error ?? 'guardrails blocked', {\n status: spawnGuardrails.status,\n hint: spawnGuardrails.hint,\n });\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: spawnGuardrails.error,\n hint: spawnGuardrails.hint,\n guardrails: spawnGuardrails,\n }, { status: spawnGuardrails.status });\n }\n if (spawnGuardrails.requiresAcknowledgement && !guardrailAcknowledged) {\n emitStartAgentPhase(issueId, 'guardrails', 'skipped', 'guardrail acknowledgement required', {\n status: spawnGuardrails.status,\n hint: spawnGuardrails.hint,\n });\n return jsonResponse({\n success: false,\n blocked: false,\n skipped: true,\n requiresAcknowledgement: true,\n hint: spawnGuardrailResourcesHint(spawnGuardrails.hint),\n guardrails: spawnGuardrails,\n }, { status: spawnGuardrails.status });\n }\n emitStartAgentPhase(issueId, 'guardrails', 'success', 'spawn guardrails passed');\n\n let spawnModel: string;\n try {\n spawnModel = determineModel({\n model: (body as any).model,\n role,\n spawnKey: `${role}:${issueId}`,\n });\n } catch (err) {\n return jsonResponse({ error: err instanceof Error ? err.message : String(err) }, { status: 400 });\n }\n const providerAuthMode = yield* Effect.promise(() => getProviderAuthMode(spawnModel));\n if (providerAuthMode === 'subscription') {\n const codexAuth = yield* checkCodexAuthStatus();\n if (codexAuth.status === 'expired' || codexAuth.status === 'burned') {\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: `Codex authentication ${codexAuth.status}. GPT subscription agents cannot spawn with expired/burned tokens.`,\n hint: 'Click \"Re-authenticate\" in the Codex auth banner or Settings page to refresh your OpenAI subscription tokens.',\n }, { status: 429 });\n }\n }\n\n // Pre-flight provider health check — detect quota/auth/network errors\n // before spawning the agent into Claude Code's opaque retry loop.\n // validateProviderHealth returns an Effect (typed ProviderHealthError\n // channel) — wrapping it in Effect.promise handed a non-thenable to the\n // runtime and crashed the whole request (PAN-1768).\n const providerHealthCheck = yield* validateProviderHealth(spawnModel).pipe(\n Effect.match({\n onFailure: (err) => ({ _tag: 'failure' as const, err }),\n onSuccess: () => ({ _tag: 'success' as const, err: null }),\n }),\n );\n if (providerHealthCheck._tag === 'failure' && providerHealthCheck.err) {\n const err = providerHealthCheck.err;\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: err.message,\n hint: err.probeResult.kind === 'quota'\n ? 'Top up your credits on the provider dashboard, or switch this agent to a different model.'\n : err.probeResult.kind === 'auth'\n ? 'Check your API key in Settings → Providers.'\n : 'The provider may be temporarily unavailable. Try again later or switch models.',\n providerHealth: {\n provider: err.provider.name,\n model: err.model,\n kind: err.probeResult.kind,\n status: err.probeResult.status,\n },\n }, { status: 429 });\n }\n\n if (!isRemote) {\n emitStartAgentPhase(issueId, 'stackHealthGate', 'start', 'checking workspace docker stack health', { workspacePath });\n const stackHealth = yield* getWorkspaceStackHealth(issueId, { projectConfig, workspacePath });\n if (!stackHealth.healthy) {\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_blocked_stack_unhealthy', {\n issueId,\n reasons: stackHealth.reasons,\n lastObserved: stackHealth.lastObserved,\n }));\n if (!allowHost) {\n emitStartAgentPhase(issueId, 'stackHealthGate', 'failure', stackHealth.reasons.join('; '), {\n workspacePath,\n lastObserved: stackHealth.lastObserved,\n });\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'error',\n issueId: issueId.toUpperCase(),\n message: `agent-spawn-blocked-stack-unhealthy: ${issueId.toUpperCase()}`,\n details: stackHealth.reasons.join('; '),\n });\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: `Workspace docker stack for ${issueId} is not healthy: ${stackHealth.reasons.join('; ')}`,\n hint: `Run 'pan workspace rebuild ${issueId}' or use the CLI break-glass path: pan start ${issueId} --host.`,\n stackHealth,\n }, { status: 422 });\n }\n emitStartAgentPhase(issueId, 'stackHealthGate', 'skipped', 'stack unhealthy but host override confirmed', {\n workspacePath,\n reasons: stackHealth.reasons,\n });\n } else {\n emitStartAgentPhase(issueId, 'stackHealthGate', 'success', 'workspace docker stack healthy', { workspacePath });\n }\n } else {\n emitStartAgentPhase(issueId, 'stackHealthGate', 'skipped', 'remote workspace skips local stack-health gate', { workspacePath });\n }\n\n if (allowHost) {\n // PAN-1556: host-override is a spawn detail, not user-facing activity.\n console.warn(`[agents] agent-spawn-host-override: ${issueId.toUpperCase()} (dashboard-confirmed)`);\n }\n\n const migratedState = projectConfig ? yield* Effect.promise(() => isStateMigrated(projectConfig)) : false;\n if (shouldCommitLegacyWorkspaceArtifacts(migratedState) && (existsSync(workspacePanContinuePath) || existsSync(workspacePanDir))) {\n // Commit workspace orchestration artifacts before handing off to the work agent.\n // The entire block is best-effort — never let git errors abort the agent start.\n yield* Effect.gen(function* () {\n const gitRoot = workspacePath;\n if (existsSync(join(gitRoot, PAN_DIRNAME))) {\n // PAN-1819: use plain git add (never -f) and exclude workspace-state/sync-target paths.\n yield* Effect.promise(() => execAsync(`git add .pan/`, { cwd: gitRoot, encoding: 'utf-8' }));\n yield* Effect.promise(() => execAsync(\n `git reset HEAD -- .pan/kickoff.md .pan/continue.json .pan/handoff-*.md .pan/spec.vbrief.json`,\n { cwd: gitRoot, encoding: 'utf-8' },\n ));\n }\n if (existsSync(join(gitRoot, '.beads'))) {\n yield* Effect.promise(() => execAsync(`git add .beads/`, { cwd: gitRoot, encoding: 'utf-8' }));\n }\n // git diff --cached --quiet exits 1 when there ARE staged changes (normal).\n // Handle exit-1 in the Promise so it never becomes an Effect failure.\n const diffResult = yield* Effect.promise(() =>\n execAsync(`git diff --cached --quiet`, { cwd: gitRoot, encoding: 'utf-8' })\n .then(() => false)\n .catch(() => true)\n );\n if (diffResult) {\n yield* Effect.promise(() => execAsync(`git commit -m \"chore: planning artifacts for ${issueId} before agent start\"`, { cwd: gitRoot, encoding: 'utf-8' }));\n const pushChild = spawn('git', ['push'], { cwd: gitRoot, detached: true, stdio: 'ignore' });\n pushChild.unref();\n }\n }).pipe(Effect.catch(() => Effect.void));\n }\n\n // Approval transition (PAN-946): move the scope vBRIEF on main from\n // proposed/ → active/ and stamp plan.status='approved'. Idempotent: if the\n // vBRIEF is already in active/ with status approved, this is a no-op. The\n // commit only happens when projectPath is on main; otherwise the on-disk\n // move still applies and a later sync will pick it up. Failure is non-fatal\n // — agent spawn proceeds even if the lifecycle move fails.\n // transitionVBriefOnMain is Effect-returning — match on it directly (PAN-1768).\n yield* transitionVBriefOnMain(\n projectPath,\n issueId,\n 'active',\n 'approved',\n `scope: approve ${issueId.toUpperCase()} vBRIEF`,\n ).pipe(\n Effect.match({\n onSuccess: (result) => {\n if (result.moved) {\n console.log(`[start-agent] vBRIEF moved ${result.fromDir} → active for ${issueId}`);\n }\n if (result.committed) {\n console.log(`[start-agent] Committed approval transition on main for ${issueId}`);\n }\n },\n onFailure: (err) => {\n console.warn(`[start-agent] vBRIEF approval transition failed (non-fatal): ${err?.message ?? err}`);\n },\n }),\n );\n\n // Running transition (PAN-946): set workspace plan.status to 'running'.\n // This is the worktree-side state — the workspace vBRIEF resolved by\n // findPlan() is updated directly, and the planning artifacts commit below\n // will pick it up. Non-fatal: agent starts even if the write fails.\n if (existsSync(planPath)) {\n try {\n updatePlanStatus(planPath, 'running');\n console.log(`[start-agent] Set plan.status=running for ${issueId}`);\n } catch (planStatusErr: any) {\n console.warn(`[start-agent] Failed to set plan.status=running (non-fatal): ${planStatusErr?.message ?? planStatusErr}`);\n }\n }\n\n // Write start session entry to per-issue record (PAN-1919)\n try {\n const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } =\n yield* Effect.promise(() => import('../../../../lib/pan-dir/record.js'));\n const recordProject = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);\n yield* Effect.promise(() => appendSessionEntry(recordProject, issueId, {\n timestamp: new Date().toISOString(),\n reason: 'start',\n agentModel: spawnModel,\n }));\n console.log(`[start-agent] Wrote start session entry to record for ${issueId}`);\n } catch (continueErr: any) {\n console.warn(`[start-agent] Failed to write start entry to record (non-fatal): ${continueErr?.message ?? continueErr}`);\n }\n\n if (isRemote && workspaceMetadata) {\n return yield* handleRemoteAgentSpawn({\n issueId,\n workspacePath,\n workspaceMetadata,\n spawnModel,\n projectPath,\n spawnGuardrails,\n lifecycle,\n });\n }\n\n // Local workspace\n const devScript = join(workspacePath, 'dev');\n const hasPlanning = existsSync(join(workspacePath, PAN_DIRNAME));\n\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_requested', {\n issueId,\n workspacePath,\n hasPlanning,\n role,\n }));\n\n const agentLifecycle = yield* getWorkAgentLifecycleState(agentSessionName);\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_lifecycle_evaluated', {\n issueId,\n lifecycle: agentLifecycle,\n }));\n if (!agentLifecycle.canStartFresh) {\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_blocked', {\n issueId,\n reason: agentLifecycle.reason,\n lifecycle: agentLifecycle,\n }));\n return jsonResponse({\n error: agentLifecycle.reason || `Cannot start agent for ${issueId}`,\n lifecycle: agentLifecycle,\n }, { status: 409 });\n }\n\n yield* Effect.gen(function* () {\n const exists = yield* sessionExists(agentSessionName);\n if (exists) yield* killSession(agentSessionName);\n console.log(`[start-agent] Killed stale tmux session ${agentSessionName}`);\n }).pipe(Effect.catch(() => Effect.void));\n\n // PAN-1531: dirty-worktree refusal replaces silent pre-spawn stashing.\n // If the workspace has uncommitted changes the route returns 409 with the\n // diff so the dashboard can present the user three explicit choices:\n // Commit / Discard (typed confirmation required) / Stash as salvageable.\n // Clients that have already resolved the dirtiness MUST pass\n // `acknowledgeDirtyWorkspace: true` to bypass this gate (typically after\n // the user clicked one of the three modal buttons).\n const acknowledgeDirtyWorkspace = (body as any).acknowledgeDirtyWorkspace === true;\n if (!acknowledgeDirtyWorkspace) {\n try {\n const { stdout: statusOut } = yield* Effect.promise(() => execAsync('git status --porcelain', {\n cwd: workspacePath,\n encoding: 'utf-8',\n }));\n if (statusOut.trim()) {\n const { stdout: diffOut } = yield* Effect.promise(() => execAsync('git diff HEAD --stat', {\n cwd: workspacePath,\n encoding: 'utf-8',\n }).catch(() => ({ stdout: '' })));\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.start_refused_dirty_workspace', {\n issueId,\n workspacePath,\n porcelain: statusOut.trim(),\n }));\n // PAN-2386: surface the refusal in the dashboard activity feed so the operator\n // sees a toast instead of having to dig through lifecycle.log.\n emitDirtyWorkspaceRefusalActivity(issueId, statusOut.trim());\n return jsonResponse({\n error: `Workspace ${workspacePath} has uncommitted changes. Choose an action and retry start with acknowledgeDirtyWorkspace=true.`,\n code: 'WORKSPACE_DIRTY',\n workspacePath,\n porcelain: statusOut.trim(),\n diffStat: diffOut.trim(),\n actions: ['commit', 'discard', 'stash-salvage'],\n }, { status: 409 });\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n console.warn(`[start-agent] Failed to check workspace status for ${issueId}: ${message}`);\n }\n }\n\n // PAN-1048 review feedback 003: the route only resolves harness when the\n // dashboard launch panel explicitly chose one. Otherwise pass nothing and\n // let pan start → spawnAgent resolve from roles.work.harness (the new\n // single source of truth for per-role harness). The legacy `phase`\n // variable and the workType/harnessOverrides map are gone — the\n // legacy-field guard above (line 1872) blocks any client still sending\n // them. Note: when bodyHarness is set we still run it through\n // canUseHarness() so we can fail fast on a model+harness incompatibility\n // before spawning the subprocess.\n const bodyHarness = (body as any).harness;\n const userPickedHarness: 'claude-code' | 'ohmypi' | 'codex' | null =\n bodyHarness === 'ohmypi' || bodyHarness === 'claude-code' || bodyHarness === 'codex' ? bodyHarness : null;\n let effectiveHarness: 'claude-code' | 'ohmypi' | 'codex' | null = null;\n if (userPickedHarness !== null) {\n const harnessDecision = yield* Effect.promise(async () =>\n canUseHarnessSync(userPickedHarness, spawnModel, await getProviderAuthMode(spawnModel))\n );\n effectiveHarness = harnessDecision.allowed ? userPickedHarness : 'claude-code';\n }\n\n // Spawn pan start command\n const spawnPanCommand = async (args: string[], cwd?: string): Promise<string> => spawnPanCommandDetached({\n agentSessionName,\n issueId,\n role,\n workspacePath,\n args,\n cwd,\n });\n\n // Use IssueLifecycle service to transition issue to \"In Progress\" (PAN-449)\n const updateIssueStatus = async () => {\n await Effect.runPromise(\n lifecycle.transitionTo(issueId, 'in_progress').pipe(Effect.catch(() => Effect.void))\n );\n };\n\n const containerResponse = yield* handleContainerOrchestration({\n issueId,\n workspacePath,\n devScript,\n agentSessionName,\n role,\n effectiveHarness,\n allowHost,\n spawnModel,\n spawnGuardrails,\n projectPath,\n eventStore,\n spawnPanCommand,\n updateIssueStatus,\n });\n if (containerResponse) return containerResponse;\n\n // Containers already ready or no containers needed\n yield* Effect.promise(() => appendAgentLifecycleLog(agentSessionName, 'agent.work_spawn_requested', {\n issueId,\n role,\n workspacePath,\n }));\n let activityId: string;\n try {\n emitStartAgentPhase(issueId, 'spawn', 'start', 'starting local work agent', { workspacePath });\n activityId = yield* Effect.promise(() => spawnPanCommand(\n buildPanStartArgs({\n issueId,\n model: spawnModel,\n harness: effectiveHarness,\n allowHost,\n }),\n workspacePath,\n ));\n emitStartAgentPhase(issueId, 'spawn', 'success', 'local work agent spawn requested', {\n workspacePath,\n activityId,\n });\n } catch (error: any) {\n const output = String(error?.output ?? error?.message ?? '');\n if (output.includes(`Workspace docker stack for ${issueId}`) && output.includes('is not healthy')) {\n const failedStackHealth = yield* getWorkspaceStackHealth(issueId, { projectConfig, workspacePath });\n emitStartAgentPhase(issueId, 'stackHealthGate', 'failure', failedStackHealth.reasons.length > 0 ? failedStackHealth.reasons.join('; ') : output.trim(), {\n workspacePath,\n activityId: error?.activityId,\n });\n emitStartAgentPhase(issueId, 'spawn', 'failure', output.trim() || `Failed to start agent for ${issueId}`, {\n workspacePath,\n activityId: error?.activityId,\n });\n emitActivityEntrySync({\n source: 'dashboard',\n level: 'error',\n issueId: issueId.toUpperCase(),\n message: `agent-spawn-blocked-stack-unhealthy: ${issueId.toUpperCase()}`,\n details: failedStackHealth.reasons.length > 0 ? failedStackHealth.reasons.join('; ') : output.trim(),\n });\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: failedStackHealth.reasons.length > 0\n ? `Workspace docker stack for ${issueId} is not healthy: ${failedStackHealth.reasons.join('; ')}`\n : output.trim(),\n hint: `Run 'pan workspace rebuild ${issueId}' or use the CLI break-glass path: pan start ${issueId} --host.`,\n stackHealth: failedStackHealth,\n activityId: error?.activityId,\n }, { status: 422 });\n }\n emitStartAgentPhase(issueId, 'spawn', 'failure', output.trim() || `Failed to start agent for ${issueId}`, {\n workspacePath,\n activityId: error?.activityId,\n });\n return jsonResponse({\n success: false,\n blocked: true,\n skipped: true,\n error: output.trim() || `Failed to start agent for ${issueId}`,\n activityId: error?.activityId,\n }, { status: 500 });\n }\n\n // Write early state.json so the dashboard immediately shows agent-<id> as the\n // active agent. Without this there's a race window between spawnPanCommand returning\n // and pan start calling saveAgentState(), during which the workspace detail\n // panel shows the stale planning-<id> session and \"No saved output available.\"\n const earlyAgentId = agentSessionName; // e.g. \"agent-pan-488\"\n const earlyStateDir = join(homedir(), '.overdeck', 'agents', earlyAgentId);\n yield* Effect.promise(() => mkdir(earlyStateDir, { recursive: true }));\n // PAN-1048 R2: legacy `runtime` removed; PAN-1055: persist user-picked harness.\n saveAgentStateSync({\n id: earlyAgentId,\n issueId,\n ...(effectiveHarness ? { harness: effectiveHarness } : {}),\n model: 'pending-work-spawn',\n status: 'starting',\n startedAt: new Date().toISOString(),\n workspace: workspacePath,\n role,\n hostOverride: allowHost || undefined,\n });\n updateRegistryForAgentStart(issueId, workspacePath, earlyAgentId);\n yield* Effect.promise(() => appendAgentLifecycleLog(earlyAgentId, 'agent.start_placeholder_created', {\n issueId,\n role,\n workspacePath,\n activityId,\n }));\n\n yield* Effect.promise(() => updateIssueStatus());\n\n // PAN-1048: lifecycle.transitionTo() inside updateIssueStatus() is the\n // single source of issue.transitioned. The duplicate issue.statusChanged\n // emit raced with reactive Cloister: the early state.json above (with\n // role: 'work', status: 'starting') is already on disk, so any code path\n // that wants to know about the in-flight spawn can read it. Removing the\n // redundant emit collapses two-source-of-truth into one.\n //\n // PAN-1048 review feedback 003: emit a contract-compliant agent.started\n // payload (agentId = session name, agent = AgentSnapshot) so the read-model\n // reducer writes a real snapshot into agentsById instead of `undefined`.\n // Mirrors the early state.json shape we just wrote at line 2693.\n //\n // PAN-1908: write-through projection — agents-row upsert + lifecycle event\n // append in one SQLite transaction.\n yield* saveAgentStateAndEmitEventProgram({\n id: earlyAgentId,\n issueId,\n workspace: workspacePath,\n role,\n ...(effectiveHarness ? { harness: effectiveHarness } : {}),\n model: 'pending-work-spawn',\n status: 'starting',\n startedAt: new Date().toISOString(),\n hostOverride: allowHost || undefined,\n }, {\n type: 'agent.started',\n timestamp: new Date().toISOString(),\n payload: {\n agentId: earlyAgentId,\n issueId,\n agent: {\n id: earlyAgentId,\n issueId,\n workspace: workspacePath,\n status: 'starting',\n startedAt: new Date().toISOString(),\n role,\n ...(effectiveHarness ? { runtime: effectiveHarness } : {}),\n },\n },\n });\n try { getIssueDataService().patchIssue(issueId, { status: 'In Progress', canonicalStatus: 'in_progress' }); } catch { /* non-fatal */ }\n invalidateAgentsCache();\n return jsonResponse({\n success: true,\n message: `Starting agent for ${issueId}`,\n activityId,\n projectPath,\n guardrails: spawnGuardrails,\n });\n })),\n);\n","/**\n * Agents route barrel — Effect HttpRouter.Layer (PAN-428 B7)\n *\n * Composes all /api/agents/* route layers and preserves the public helper\n * exports that external callers historically imported from routes/agents.js.\n */\n\nimport { Layer } from 'effect';\n\nimport {\n getAgentsRoute,\n getAgentGitInfoRoute,\n getAgentTmuxAliveRoute,\n getAgentHasSessionRoute,\n} from './agents/listing.js';\nimport {\n getAgentOutputRoute,\n getAgentConversationRoute,\n getAgentActivityRoute,\n getAgentFilesRoute,\n getAgentTimelineRoute,\n} from './agents/conversation.js';\nimport {\n postAgentMessageRoute,\n postAgentTellRoute,\n postAgentPokeRoute,\n} from './agents/messaging.js';\nimport {\n getAgentPendingQuestionsRoute,\n postAgentAnswerQuestionRoute,\n postInternalAgentPermissionRequestRoute,\n postAgentPermissionResponseRoute,\n} from './agents/permissions.js';\nimport {\n getAgentHealthHistoryRoute,\n postAgentHeartbeatRoute,\n postAgentWorkCompleteRoute,\n postAgentStuckRoute,\n postAgentClassifyCompletionRoute,\n getAgentRuntimeRoute,\n} from './agents/runtime-events.js';\nimport {\n deleteAgentRoute,\n postAgentStopRoute,\n postAgentSuspendRoute,\n postAgentPauseRoute,\n postAgentUnpauseRoute,\n postAgentUntroubledRoute,\n} from './agents/lifecycle-stop.js';\nimport {\n postAgentResumeRoute,\n postAgentRecoverRoute,\n postAgentRestartRoute,\n postAgentRestartFreshRoute,\n postAgentsRestartAllRoute,\n postAgentResetSessionRoute,\n getAgentsRestartConfigRoute,\n postAgentsRestartWithConfigRoute,\n} from './agents/lifecycle-restart.js';\nimport {\n getAgentCloisterHealthRoute,\n getAgentHandoffSuggestionRoute,\n postAgentHandoffRoute,\n getAgentCostRoute,\n postAgentDeliveryMethodRoute,\n postAgentSwitchModelRoute,\n} from './agents/control.js';\nimport { postAgentsRoute } from './agents/spawn.js';\n\nexport {\n buildPanStartArgs,\n spawnPanCommandDetached,\n validateAgentRuntimeEventAuth,\n invalidateAgentsCache,\n evaluateAgentStartGate,\n hasActiveAgentGateOrRetry,\n evaluateSpawnGuardrails,\n} from './agents/shared.js';\n\nexport type {\n SpawnGuardrailDecision,\n AgentStartGateDecision,\n} from './agents/shared.js';\n\nexport {\n agentHasResolvableWorkspace,\n UNRESOLVABLE_AGENT_GIT_INFO,\n} from './agents/listing.js';\nexport { buildConversationResponse } from './agents/conversation.js';\nexport { validateAgentMessageOrigin } from './agents/messaging.js';\nexport { createAgentStopHandler } from './agents/lifecycle-stop.js';\nexport { validateAgentDeliveryMethodOrigin } from './agents/control.js';\n\nexport const agentsRouteLayer = Layer.mergeAll(\n getAgentsRoute,\n getAgentOutputRoute,\n getAgentConversationRoute,\n postAgentMessageRoute,\n postAgentTellRoute,\n deleteAgentRoute,\n postAgentStopRoute,\n getAgentHealthHistoryRoute,\n postAgentPokeRoute,\n getAgentPendingQuestionsRoute,\n postAgentAnswerQuestionRoute,\n postAgentHeartbeatRoute,\n postAgentWorkCompleteRoute,\n postAgentStuckRoute,\n postAgentClassifyCompletionRoute,\n postInternalAgentPermissionRequestRoute,\n postAgentPermissionResponseRoute,\n getAgentRuntimeRoute,\n getAgentGitInfoRoute,\n getAgentActivityRoute,\n getAgentFilesRoute,\n getAgentTimelineRoute,\n postAgentSuspendRoute,\n postAgentPauseRoute,\n postAgentUnpauseRoute,\n postAgentUntroubledRoute,\n postAgentResumeRoute,\n postAgentRecoverRoute,\n postAgentRestartRoute,\n getAgentCloisterHealthRoute,\n getAgentHandoffSuggestionRoute,\n postAgentHandoffRoute,\n getAgentCostRoute,\n postAgentsRoute,\n postAgentsRestartAllRoute,\n getAgentTmuxAliveRoute,\n getAgentHasSessionRoute,\n postAgentResetSessionRoute,\n postAgentSwitchModelRoute,\n postAgentRestartFreshRoute,\n postAgentDeliveryMethodRoute,\n getAgentsRestartConfigRoute,\n postAgentsRestartWithConfigRoute,\n);\n\nexport default agentsRouteLayer;\n","/**\n * DAG-aware task readiness module for Cloister scheduling (PAN-388 Phase 4)\n *\n * Provides:\n * - isTaskReady(): checks if a vBRIEF item's hard dependencies are all done\n * - getUnblockedItems(): returns items newly unblocked after a task completes\n *\n * Gracefully degrades when no plan.vbrief.json exists — all tasks are ready.\n */\n\nimport { Effect } from 'effect';\nimport { readWorkspacePlanSync, readWorkspacePlan, type VBriefReadError } from '../vbrief/io.js';\nimport type { VBriefItemStatus } from '../vbrief/types.js';\n\nconst TERMINAL_STATUSES: VBriefItemStatus[] = ['completed', 'cancelled'];\n\n/**\n * Returns true if the given vBRIEF item can be worked on — i.e., all items\n * that block it have reached a terminal status (completed or cancelled).\n *\n * When no plan exists for the workspace, returns true (no-op for legacy flows).\n */\nexport function isTaskReadySync(itemId: string, workspacePath: string): boolean {\n const doc = readWorkspacePlanSync(workspacePath);\n if (!doc) return true; // No plan → all tasks ready\n\n // If item doesn't exist in this plan, don't block it (e.g., legacy bead not in vBRIEF)\n const itemExists = doc.plan.items.some(i => i.id === itemId);\n if (!itemExists) return true;\n\n // Find all items that block this task (type: 'blocks', edge.to === itemId)\n const blockerIds = doc.plan.edges\n .filter(e => e.type === 'blocks' && e.to === itemId)\n .map(e => e.from);\n\n if (blockerIds.length === 0) return true; // No blockers\n\n const itemById = new Map(doc.plan.items.map(i => [i.id, i]));\n\n return blockerIds.every(blockerId => {\n const blocker = itemById.get(blockerId);\n if (!blocker) return true; // Unknown blocker — don't block\n return TERMINAL_STATUSES.includes(blocker.status);\n });\n}\n\n/**\n * Returns the list of item IDs that are newly unblocked after `justCompletedId`\n * reaches a terminal status.\n *\n * \"Newly unblocked\" means: the item was not ready before (had at least one\n * non-terminal blocker) and is now ready after this completion.\n *\n * Returns [] when no plan exists.\n */\nexport function getUnblockedItemsSync(workspacePath: string, justCompletedId: string): string[] {\n const doc = readWorkspacePlanSync(workspacePath);\n if (!doc) return [];\n\n const itemById = new Map(doc.plan.items.map(i => [i.id, i]));\n\n // Find items that this completed item directly blocks\n const directlyUnblocking = doc.plan.edges\n .filter(e => e.type === 'blocks' && e.from === justCompletedId)\n .map(e => e.to);\n\n const unblocked: string[] = [];\n\n for (const candidateId of directlyUnblocking) {\n const candidate = itemById.get(candidateId);\n if (!candidate) continue;\n if (TERMINAL_STATUSES.includes(candidate.status)) continue; // Already done\n\n // Check all blockers of this candidate (excluding the just-completed one)\n const blockers = doc.plan.edges\n .filter(e => e.type === 'blocks' && e.to === candidateId)\n .map(e => e.from);\n\n const allBlockersTerminal = blockers.every(blockerId => {\n const blocker = itemById.get(blockerId);\n if (!blocker) return true;\n // The just-completed item counts as terminal even if status not yet written\n if (blockerId === justCompletedId) return true;\n return TERMINAL_STATUSES.includes(blocker.status);\n });\n\n if (allBlockersTerminal) {\n unblocked.push(candidateId);\n }\n }\n\n return unblocked;\n}\n\n// ─── Effect variants (PAN-1249) ──────────────────────────────────────────────\n\n/**\n * Effect variant of {@link isTaskReadySync}. Reads the workspace plan asynchronously\n * via the typed Effect API. Returns `true` when the plan is missing or the item\n * is unknown (gracefully degrades for legacy flows).\n */\nexport const isTaskReady = (\n itemId: string,\n workspacePath: string,\n): Effect.Effect<boolean, VBriefReadError> =>\n Effect.gen(function* () {\n const doc = yield* readWorkspacePlan(workspacePath);\n if (!doc) return true;\n\n const itemExists = doc.plan.items.some((i) => i.id === itemId);\n if (!itemExists) return true;\n\n const blockerIds = doc.plan.edges\n .filter((e) => e.type === 'blocks' && e.to === itemId)\n .map((e) => e.from);\n\n if (blockerIds.length === 0) return true;\n\n const itemById = new Map(doc.plan.items.map((i) => [i.id, i]));\n return blockerIds.every((blockerId) => {\n const blocker = itemById.get(blockerId);\n if (!blocker) return true;\n return TERMINAL_STATUSES.includes(blocker.status);\n });\n });\n\n/**\n * Effect variant of {@link getUnblockedItemsSync}. Returns the list of item IDs\n * that are newly unblocked after `justCompletedId` reaches a terminal status.\n */\nexport const getUnblockedItems = (\n workspacePath: string,\n justCompletedId: string,\n): Effect.Effect<string[], VBriefReadError> =>\n Effect.gen(function* () {\n const doc = yield* readWorkspacePlan(workspacePath);\n if (!doc) return [];\n\n const itemById = new Map(doc.plan.items.map((i) => [i.id, i]));\n const directlyUnblocking = doc.plan.edges\n .filter((e) => e.type === 'blocks' && e.from === justCompletedId)\n .map((e) => e.to);\n\n const unblocked: string[] = [];\n for (const candidateId of directlyUnblocking) {\n const candidate = itemById.get(candidateId);\n if (!candidate) continue;\n if (TERMINAL_STATUSES.includes(candidate.status)) continue;\n\n const blockers = doc.plan.edges\n .filter((e) => e.type === 'blocks' && e.to === candidateId)\n .map((e) => e.from);\n\n const allBlockersTerminal = blockers.every((blockerId) => {\n const blocker = itemById.get(blockerId);\n if (!blocker) return true;\n if (blockerId === justCompletedId) return true;\n return TERMINAL_STATUSES.includes(blocker.status);\n });\n\n if (allBlockersTerminal) unblocked.push(candidateId);\n }\n\n return unblocked;\n });\n","import { existsSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { decideEscalation, type EscalationAction } from '../../../lib/agents/tier-escalation.js';\nimport { resolveTieredExecutionEnabled } from '../../../lib/agents/tier-table.js';\nimport { loadConfigSync } from '../../../lib/config-yaml.js';\nimport { resolveProjectFromIssueSync } from '../../../lib/projects.js';\nimport { getReviewStatusSync } from '../../../lib/review-status.js';\nimport {\n readTierOverrides,\n readWorkspacePlanSync,\n recordTierPromotion,\n} from '../../../lib/vbrief/io.js';\n\nexport interface TieredInspectFailureEscalationDeps {\n loadConfig?: typeof loadConfigSync;\n resolveProject?: typeof resolveProjectFromIssueSync;\n exists?: typeof existsSync;\n readPlan?: typeof readWorkspacePlanSync;\n readOverrides?: typeof readTierOverrides;\n decide?: typeof decideEscalation;\n recordPromotion?: typeof recordTierPromotion;\n}\n\nexport function handleTieredInspectFailureEscalation(\n issueId: string,\n notes: string | undefined,\n deps: TieredInspectFailureEscalationDeps = {},\n): EscalationAction | null {\n const loadConfig = deps.loadConfig ?? loadConfigSync;\n const resolveProject = deps.resolveProject ?? resolveProjectFromIssueSync;\n const exists = deps.exists ?? existsSync;\n const readPlan = deps.readPlan ?? readWorkspacePlanSync;\n const readOverrides = deps.readOverrides ?? readTierOverrides;\n const decide = deps.decide ?? decideEscalation;\n const recordPromotion = deps.recordPromotion ?? recordTierPromotion;\n\n const tiered = loadConfig().config.tieredExecution;\n if (!tiered.escalation.enabled) return null;\n\n const project = resolveProject(issueId);\n if (!project) return null;\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${issueId.toLowerCase()}`,\n );\n if (!exists(workspacePath)) return null;\n\n const doc = readPlan(workspacePath);\n if (!resolveTieredExecutionEnabled(tiered, doc?.plan.metadata)) return null;\n\n const beadId = notes?.match(/[Bb]ead\\s+(\\S+)/)?.[1];\n if (!doc || !beadId) return null;\n const item = doc.plan.items.find(candidate => candidate.id === beadId);\n if (!item) return null;\n\n const overrides = readOverrides(workspacePath);\n const decision = decide({\n kind: 'supervisor-blocked',\n beadId,\n sha: getReviewStatusSync(issueId)?.reviewedAtCommit ?? 'unknown',\n attemptsAtCurrentTier: tiered.escalation.retries_at_tier,\n }, item, tiered.escalation, overrides);\n\n if (decision.action === 'promote') {\n recordPromotion(workspacePath, beadId, decision.from, decision.to, decision.reason);\n }\n\n return decision;\n}\n\nexport async function reportTieredInspectFailureEscalation(\n issueId: string,\n notes: string | undefined,\n deps: TieredInspectFailureEscalationDeps = {},\n): Promise<void> {\n try {\n const decision = handleTieredInspectFailureEscalation(issueId, notes, deps);\n if (decision) {\n console.log(`[specialists/done] Tier escalation decision for ${issueId}: ${decision.action}`);\n }\n } catch (err) {\n console.warn(`[specialists/done] Tier escalation handling failed for ${issueId}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n","import { exec, execFile } from 'node:child_process';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { promisify } from 'node:util';\n\nimport { Effect } from 'effect';\nimport { HttpServerRequest } from 'effect/unstable/http';\n\nimport {\n type AgentRuntimeState,\n type AgentState,\n} from '../../../../lib/agents.js';\nimport { createInFlightGuard } from '../../../../lib/cloister/in-flight-guard.js';\nimport { extractPrefixSync } from '../../../../lib/issue-id.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\n\nexport const execAsync = promisify(exec);\nexport const execFileAsync = promisify(execFile);\n\n// ─── Helpers ──────────────────────────────────────────────────────────────────\n\nexport type SpecialistAgentName = 'merge-agent' | 'review-agent' | 'test-agent' | 'inspect-agent' | 'uat-agent';\nexport type ProjectSpecialistAgentName = 'review-agent' | 'test-agent' | 'merge-agent';\nexport type SpecialistEventRole = 'review' | 'test' | 'ship';\n\nexport function validateSpecialistAgentName(type: string): type is ProjectSpecialistAgentName {\n return type === 'review-agent' || type === 'test-agent' || type === 'merge-agent';\n}\n\nexport function specialistEventRole(name: string): SpecialistEventRole | undefined {\n if (name === 'review' || name === 'review-agent') return 'review';\n if (name === 'test' || name === 'test-agent') return 'test';\n if (name === 'merge' || name === 'merge-agent') return 'ship';\n return undefined;\n}\n\nexport function specialistNameForRole(role: string | undefined): ProjectSpecialistAgentName | null {\n const baseRole = role?.split('.')[0];\n if (baseRole === 'review') return 'review-agent';\n if (baseRole === 'test') return 'test-agent';\n if (baseRole === 'ship') return 'merge-agent';\n return null;\n}\n\nexport type SpecialistAutoCompleteBody = {\n agentId?: string;\n issueId?: string;\n role?: string;\n sessionId?: string | null;\n status?: string;\n notes?: string;\n};\n\nexport function validateSpecialistAutoCompleteMetadata(\n name: string,\n body: SpecialistAutoCompleteBody,\n agentState: AgentState | null,\n runtimeState: AgentRuntimeState | null,\n): { ok: true } | { ok: false; status: number; error: string } {\n if (!validateSpecialistAgentName(name)) {\n return { ok: false, status: 400, error: `Invalid specialist name: ${name}` };\n }\n if (!body.issueId || !body.status) {\n return { ok: false, status: 400, error: 'issueId and status required' };\n }\n if (body.status !== 'passed' && body.status !== 'failed') {\n return { ok: false, status: 400, error: 'status must be passed or failed' };\n }\n if (!body.agentId || !body.role) {\n return { ok: false, status: 400, error: 'agentId and role required' };\n }\n\n const normalizedIssueId = body.issueId.toUpperCase();\n const expectedName = specialistNameForRole(body.role);\n if (expectedName !== name) {\n return { ok: false, status: 403, error: 'role does not match specialist' };\n }\n if (!agentState || agentState.id !== body.agentId) {\n return { ok: false, status: 403, error: 'agent run not found' };\n }\n if (agentState.issueId.toUpperCase() !== normalizedIssueId) {\n return { ok: false, status: 403, error: 'agent issue does not match request' };\n }\n if (specialistNameForRole(agentState.role) !== name) {\n return { ok: false, status: 403, error: 'agent role does not match specialist' };\n }\n if (agentState.status !== 'running' && agentState.status !== 'starting') {\n return { ok: false, status: 409, error: 'agent run is not active' };\n }\n if (runtimeState?.currentIssue && runtimeState.currentIssue.toUpperCase() !== normalizedIssueId) {\n return { ok: false, status: 403, error: 'runtime issue does not match request' };\n }\n if (runtimeState?.claudeSessionId && body.sessionId !== runtimeState.claudeSessionId) {\n return { ok: false, status: 403, error: 'session does not match active run' };\n }\n\n return { ok: true };\n}\n\n// Read the request body as unknown JSON\nexport const readJsonBody = Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const text = yield* request.text;\n try {\n return text ? JSON.parse(text) : {};\n } catch {\n return {};\n }\n});\n\n// ─── Idempotency guard: prevent concurrent postMergeLifecycle re-entry ────────\n// PAN-328: the loop specialists/done → onMergeComplete → postMergeLifecycle →\n// (re-trigger) → specialists/done once burned 24,626 tracker API calls. The\n// in-flight guard makes a second *concurrent* call for the same issue a no-op.\n// The invariant is enforced by tests/unit/lib/cloister/in-flight-guard.test.ts\n// — delete the guard and that suite goes red.\nconst postMergeGuard = createInFlightGuard();\n\n// Track issues where the server is managing the merge lifecycle (polyrepo).\n// Exported so the workspaces route can register/unregister server-managed merges.\nexport const _serverManagedMerges = new Set<string>();\n\n/**\n * Exported for the UAT batch-promote route (PAN-1737): batch promotion fans\n * out the per-member post-merge through THIS guard instance so an issue's\n * lifecycle still runs at most once regardless of which path merged it.\n * Returns false when a run for the issue is already in flight.\n */\nexport interface FirePostMergeLifecycleOptions {\n sourceBranch?: string;\n verifiedMergedRef?: string;\n}\n\nexport function firePostMergeLifecycle(issueId: string, options?: FirePostMergeLifecycleOptions): boolean {\n const started = postMergeGuard.run(\n issueId,\n async () => {\n const issuePrefix = extractPrefixSync(issueId) ?? issueId.split('-')[0];\n const projectPath = getProjectPathForIssue(issuePrefix);\n const { postMergeLifecycle } = await import('../../../../lib/cloister/merge-agent.js');\n await postMergeLifecycle(\n issueId,\n projectPath,\n options?.sourceBranch,\n options?.verifiedMergedRef ? { verifiedMergedRef: options.verifiedMergedRef } : undefined,\n );\n console.log(`[merge] post-merge lifecycle completed for ${issueId}`);\n\n // PAN-1691: roll the merge train — rebase ready siblings onto the new main,\n // re-verify the clean ones, agent-resolve conflicts. No-op unless the\n // flywheel.merge_train_enabled flag is on. Runs inside the in-flight guard,\n // so it cannot re-enter postMergeLifecycle for this issue.\n const { runMergeTrainReconcile } = await import('../../../../lib/cloister/merge-train.js');\n const outcomes = await runMergeTrainReconcile(issueId);\n if (outcomes.length > 0) {\n console.log(\n `[merge-train] reconciled ${outcomes.length} sibling(s) after ${issueId}: ` +\n outcomes.map((o) => `${o.issueId}=${o.result}`).join(', '),\n );\n }\n },\n (err) => console.error(`[merge] post-merge lifecycle failed for ${issueId}:`, err),\n );\n if (!started) {\n console.log(`[merge] firePostMergeLifecycle: skipping ${issueId} — already in flight`);\n }\n return started;\n}\n\nexport function getProjectPathForIssue(issuePrefix: string): string {\n const issueId = `${issuePrefix}-1`;\n const resolved = resolveProjectFromIssueSync(issueId);\n if (resolved) return resolved.projectPath;\n return join(homedir(), 'Projects');\n}\n","import { existsSync } from 'node:fs';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect, Layer } from 'effect';\nimport { HttpRouter, HttpServerRequest } from 'effect/unstable/http';\n\nimport { getAgentState, getAgentRuntimeState, messageAgent, saveAgentRuntimeState, transitionIssueToInProgress } from '../../../../lib/agents.js';\nimport { queryBeadById } from '../../../../lib/beads-query.js';\nimport { getUnblockedItemsSync } from '../../../../lib/cloister/task-readiness.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { getReviewStatusSync, loadReviewStatuses, setReviewStatusSync as setReviewStatusBase, type ReviewStatus } from '../../../../lib/review-status.js';\nimport { syncBeadStatusToVBrief } from '../../../../lib/vbrief/beads.js';\nimport { readWorkspacePlanSync } from '../../../../lib/vbrief/io.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { EventStoreService } from '../../services/domain-services.js';\nimport { validateAgentRuntimeEventAuth } from '../agents.js';\nimport { httpHandler } from '../http-handler.js';\nimport { reportTieredInspectFailureEscalation } from '../tiered-inspect-escalation.js';\nimport { killSession } from '../../../../lib/tmux.js';\nimport {\n _serverManagedMerges,\n execFileAsync,\n firePostMergeLifecycle,\n readJsonBody,\n specialistEventRole,\n validateSpecialistAutoCompleteMetadata,\n type SpecialistAgentName,\n type SpecialistAutoCompleteBody,\n} from './shared.js';\n\n// ─── Route: GET /api/specialists ─────────────────────────────────────────────\n\nconst getSpecialistsRoute = HttpRouter.add(\n 'GET',\n '/api/specialists',\n httpHandler(Effect.gen(function* () {\n const {\n getAllSpecialistStatus,\n getAllProjectSpecialistStatuses,\n } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n\n const legacySpecialists = yield* Effect.promise(() => getAllSpecialistStatus());\n const projectSpecialists = yield* Effect.promise(() => getAllProjectSpecialistStatuses());\n\n return jsonResponse({\n specialists: legacySpecialists,\n projects: projectSpecialists,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/reset-all ───────────────────────────────────\n// NOTE: Must be registered before /:name/reset to avoid \"reset-all\" matching as :name\n\nconst postSpecialistsResetAllRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/reset-all',\n httpHandler(Effect.gen(function* () {\n const {\n getAllSpecialists,\n isRunning,\n getTmuxSessionName,\n } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const { clearHookSync } = yield* Effect.promise(() => import('../../../../lib/hooks.js'));\n\n const specialists = getAllSpecialists();\n const results: { name: string; killed: boolean; sessionCleared: boolean; queueCleared: boolean }[] = [];\n\n for (const specialist of specialists) {\n const name = specialist.name;\n let killed = false;\n\n if (yield* Effect.promise(() => isRunning(name))) {\n const tmuxSession = getTmuxSessionName(name);\n const killResult = yield* killSession(tmuxSession).pipe(\n Effect.as(true),\n Effect.catch(() => Effect.succeed(false)),\n );\n killed = killResult;\n }\n\n clearHookSync(name);\n results.push({ name, killed, sessionCleared: false, queueCleared: true });\n }\n\n // Reset any \"reviewing\" statuses to \"pending\" — use per-issue atomic updates\n // to avoid the read-all/write-all race that saveReviewStatuses() would reintroduce.\n let reviewStatusesReset = 0;\n try {\n const statuses = loadReviewStatuses();\n for (const key of Object.keys(statuses)) {\n if (statuses[key].reviewStatus === 'reviewing') {\n setReviewStatusBase(key, { reviewStatus: 'pending' });\n reviewStatusesReset++;\n }\n }\n } catch (e) {\n console.error('Failed to reset review statuses:', e);\n }\n\n return jsonResponse({\n success: true,\n message: `Reset ${results.length} specialists, reset ${reviewStatusesReset} review statuses`,\n results,\n reviewStatusesReset,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/done ───────────────────────────────────────\n// CRITICAL: This endpoint has idempotency guards — see CLAUDE.md.\n// Must be registered before /:name/* routes to prevent \"done\" matching as :name.\n\nconst postSpecialistsDoneRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/done',\n httpHandler(Effect.gen(function* () {\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const { specialist, issueId, status, notes } = body as {\n specialist: string;\n issueId: string;\n status: string;\n notes?: string;\n };\n\n // Validate specialist type\n const validSpecialists = ['review', 'test', 'merge', 'inspect', 'uat', 'ship'];\n if (!validSpecialists.includes(specialist)) {\n return jsonResponse(\n { error: `Invalid specialist: ${specialist}. Valid: ${validSpecialists.join(', ')}` },\n { status: 400 },\n );\n }\n\n // Validate status\n if (!status || !['passed', 'failed'].includes(status)) {\n return jsonResponse(\n { error: `Invalid status: ${status}. Must be 'passed' or 'failed'` },\n { status: 400 },\n );\n }\n\n // Validate issueId\n if (!issueId) {\n return jsonResponse({ error: 'issueId is required' }, { status: 400 });\n }\n\n const normalizedIssueId = issueId.toUpperCase();\n console.log(`[specialists/done] ${specialist} signaling ${status} for ${normalizedIssueId}`);\n\n // Resolve any pending specialist completion waiters (PAN-632: event-driven completion).\n // This replaces polling loops in spawnMergeAgentForBranches / syncMainIntoWorkspace.\n if (specialist === 'merge') {\n const { reportSpecialistCompletion } = yield* Effect.promise(() => import('../../../../lib/cloister/specialist-completion.js'));\n const resolved = yield* reportSpecialistCompletion(normalizedIssueId, {\n status: status as 'passed' | 'failed',\n notes,\n });\n if (resolved) {\n console.log(`[specialists/done] Resolved pending completion waiter for ${normalizedIssueId}`);\n }\n }\n\n // GUARD: If this issue is in a server-managed merge (polyrepo), the server handles\n // the merge lifecycle. Acknowledge the agent's call but do NOT trigger onMergeComplete.\n if (specialist === 'merge' && _serverManagedMerges.has(normalizedIssueId)) {\n console.log(`[specialists/done] ${normalizedIssueId} is server-managed merge — acknowledging without triggering lifecycle`);\n return jsonResponse({\n success: true,\n specialist,\n issueId: normalizedIssueId,\n status,\n notes,\n serverManaged: true,\n });\n }\n\n // Build the update based on specialist type\n const update: Partial<ReviewStatus> = {};\n\n switch (specialist) {\n case 'review':\n update.reviewStatus = status === 'passed' ? 'passed' : 'blocked';\n if (notes) update.reviewNotes = notes;\n break;\n\n case 'test':\n update.testStatus = status as typeof update.testStatus;\n if (notes) update.testNotes = notes;\n break;\n\n case 'merge':\n update.mergeStatus = status === 'passed' ? 'merged' : 'failed';\n break;\n\n case 'inspect':\n update.inspectStatus = status as typeof update.inspectStatus;\n if (notes) update.inspectNotes = notes;\n break;\n\n case 'uat':\n update.uatStatus = status as typeof update.uatStatus;\n if (notes) update.uatNotes = notes;\n if (status === 'passed') {\n update.readyForMerge = true;\n }\n break;\n\n case 'ship':\n if (status === 'passed') {\n update.readyForMerge = true;\n }\n break;\n }\n\n // Apply the update (triggers side effects like idle state, queue processing)\n const updatedStatus = setReviewStatusBase(normalizedIssueId, update);\n if (specialist === 'inspect' && status === 'failed') yield* Effect.promise(() => reportTieredInspectFailureEscalation(normalizedIssueId, notes));\n\n // Set specialist state to idle and clear registry write-scope.\n // CRITICAL: No `await` between the mergeStatus write above and the guard check below.\n yield* Effect.promise(async () => {\n try {\n const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } =\n await import('../../../../lib/cloister/specialists.js');\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n const projectKey = project?.projectKey;\n const tmuxSession = projectKey\n ? getTmuxSessionName(`${specialist}-agent` as SpecialistAgentName, projectKey, normalizedIssueId)\n : getTmuxSessionName(`${specialist}-agent` as SpecialistAgentName);\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n });\n console.log(`[specialists/done] Set ${tmuxSession} to idle`);\n\n // PAN-2579 (warm-by-default lifecycle): the verdict is recorded; the session\n // stays ALIVE so the next review/test cycle resumes it with context intact.\n // Warm-idle sessions no longer count against the advancing ceiling\n // (countRunningAgents excludes them) and the memory governor sheds them\n // first under HARD pressure — eviction is the governor's job, never a\n // side effect of recording a verdict. (Supersedes the PAN-846 kill and\n // the PAN-2007 keep-alive flag.)\n console.log(`[specialists/done] Warm lifecycle: verdict recorded, leaving ${tmuxSession} running (PAN-2579)`);\n\n // Clear write-scope lock so the next specialist can claim the workspace\n if (projectKey) {\n const registryKey = makeSpecialistRegistryKey(`${specialist}-agent`, normalizedIssueId);\n updateRunMetadata(projectKey, registryKey, {\n currentRun: null,\n writeScope: undefined,\n workspace: null,\n currentActivity: null,\n });\n console.log(`[specialists/done] Cleared registry lock for ${registryKey} (${projectKey})`);\n }\n\n // Update specialist handoff log so success-rate metrics reflect actual outcome\n const { updateSpecialistHandoffStatus } = await import('../../../../lib/cloister/specialist-handoff-logger.js');\n const updated = await Effect.runPromise(updateSpecialistHandoffStatus(\n normalizedIssueId,\n `${specialist}-agent`,\n status === 'passed' ? 'completed' : 'failed',\n status === 'passed' ? 'success' : 'failure',\n ));\n if (updated) {\n console.log(`[specialists/done] Updated handoff log: ${specialist}-agent ${normalizedIssueId} → ${status}`);\n }\n } catch (err) {\n console.error(`[specialists/done] Error managing specialist state:`, err);\n }\n });\n\n // When review passes, snapshot the current HEAD commit so we can detect\n // if the agent makes new commits before merge (which invalidates the review).\n if (specialist === 'review' && status === 'passed') {\n yield* Effect.promise(async () => {\n try {\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${normalizedIssueId.toLowerCase()}`,\n );\n if (existsSync(workspacePath)) {\n const { getWorkspaceGitInfo } = await import('../../../../lib/git-utils.js');\n const { HEAD } = await Effect.runPromise(getWorkspaceGitInfo(workspacePath));\n setReviewStatusBase(normalizedIssueId, { reviewedAtCommit: HEAD });\n console.log(`[specialists/done] Snapshotted reviewedAtCommit=${HEAD.substring(0, 8)} for ${normalizedIssueId}`);\n }\n }\n } catch (err) {\n console.error(`[specialists/done] Failed to snapshot reviewedAtCommit for ${normalizedIssueId}:`, err);\n }\n });\n }\n\n // When inspect specialist reports success, save checkpoint\n if (specialist === 'inspect' && status === 'passed') {\n yield* Effect.promise(async () => {\n try {\n const { onInspectComplete } = await import('../../../../lib/cloister/inspect-agent.js');\n // Extract beadId from notes (format: \"Bead <beadId> matches spec...\")\n const beadMatch = notes?.match(/[Bb]ead\\s+(\\S+)/);\n const beadId = beadMatch?.[1] || 'unknown';\n // Resolve project to get workspace path\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${normalizedIssueId.toLowerCase()}`,\n );\n if (existsSync(workspacePath)) {\n (await Effect.runPromise(onInspectComplete(project.projectKey, normalizedIssueId, beadId, 'passed', workspacePath)));\n\n // Sync bead completion to vBRIEF plan\n try {\n const beadData = await Effect.runPromise(queryBeadById(workspacePath, beadId));\n const updatedItemId = await Effect.runPromise(syncBeadStatusToVBrief(beadId, workspacePath, 'completed', beadData?.title));\n if (updatedItemId) {\n // Check which tasks are now unblocked and wake the work agent\n try {\n const unblockedItems = getUnblockedItemsSync(workspacePath, updatedItemId);\n if (unblockedItems.length > 0) {\n console.log(\n `[auto-wake] ${normalizedIssueId}: items unblocked after \"${updatedItemId}\": ${unblockedItems.join(', ')}`,\n );\n const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;\n const doc = readWorkspacePlanSync(workspacePath);\n const unblockedTitles = unblockedItems\n .map((id) => {\n const it = doc?.plan.items.find((i) => i.id === id);\n return it ? `\"${it.title}\"` : `\"${id}\"`;\n })\n .join(', ');\n const wakeMsg = `DAG SCHEDULER: Task${unblockedItems.length > 1 ? 's' : ''} now unblocked after completing \"${updatedItemId}\": ${unblockedTitles}. Pick up the next available task.`;\n await messageAgent(workAgentId, wakeMsg).catch((err: unknown) => {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.log(\n `[auto-wake] Could not wake ${workAgentId} (may not be running): ${errMsg}`,\n );\n });\n }\n } catch (wakeErr: unknown) {\n const errMsg = wakeErr instanceof Error ? wakeErr.message : String(wakeErr);\n console.warn(`[auto-wake] Failed to check unblocked items: ${errMsg}`);\n }\n }\n } catch (syncErr: unknown) {\n const errMsg = syncErr instanceof Error ? syncErr.message : String(syncErr);\n console.warn(`[specialists/done] vBRIEF sync failed: ${errMsg}`);\n }\n }\n }\n } catch (err) {\n console.error(`[specialists/done] Error saving inspect checkpoint:`, err);\n }\n });\n }\n\n // When the test specialist reports success, persist testStatus and emit\n // test.passed so reactive Cloister records the shipping lifecycle phase.\n // PAN-1650 derives readyForMerge from review/test gate state server-side;\n // no ship role is spawned.\n if (specialist === 'test' && status === 'passed') {\n yield* Effect.promise(async () => {\n try {\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${normalizedIssueId.toLowerCase()}`,\n );\n if (existsSync(workspacePath)) {\n setReviewStatusBase(normalizedIssueId, { testStatus: 'passed' });\n const { initEventStore } = await import('../../event-store.js');\n const store = await initEventStore();\n await store.appendAsync({\n type: 'test.passed',\n timestamp: new Date().toISOString(),\n payload: { issueId: normalizedIssueId },\n } as any);\n console.log(`[specialists/done] ${normalizedIssueId} emitted test.passed; shipping lifecycle recorded`);\n }\n }\n } catch (err) {\n console.error(`[specialists/done] Error emitting test.passed for ${normalizedIssueId}:`, err);\n }\n });\n }\n\n // When merge specialist reports success, run post-merge lifecycle ONCE.\n // Use firePostMergeLifecycle directly rather than onMergeComplete: onMergeComplete\n // has a guard that checks mergeStatus !== 'merged', but setReviewStatusBase above\n // already set mergeStatus='merged' — that guard would always fire and the lifecycle\n // would never run. firePostMergeLifecycle skips that guard and uses the\n // in-flight guard (postMergeGuard, concurrency) + postMergeLifecycle's\n // _completedPostMerge (defense-in-depth).\n if (specialist === 'merge' && status === 'passed') {\n firePostMergeLifecycle(normalizedIssueId);\n }\n\n // When any specialist reports failure, transition issue back to In Progress\n // (inspect failures don't change Linear status — they're mid-implementation gates).\n if (status === 'failed' && specialist !== 'inspect') {\n try {\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n if (project) {\n const workspacePath = join(\n project.projectPath,\n 'workspaces',\n `feature-${normalizedIssueId.toLowerCase()}`,\n );\n const wsPath = existsSync(workspacePath) ? workspacePath : undefined;\n transitionIssueToInProgress(normalizedIssueId, wsPath).catch((err: unknown) => {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.warn(\n `[specialists/done] Could not transition ${normalizedIssueId} back to in_progress: ${errMsg}`,\n );\n });\n console.log(\n `[specialists/done] ${specialist} failed → transitioning ${normalizedIssueId} back to In Progress`,\n );\n }\n } catch (err: unknown) {\n const errMsg = err instanceof Error ? err.message : String(err);\n console.warn(`[specialists/done] Could not transition issue back to in_progress:`, errMsg);\n }\n }\n\n // When merge fails, post a comment on the GitHub PR so the failure is visible\n // outside the dashboard, and send feedback to the work agent.\n if (specialist === 'merge' && status === 'failed') {\n yield* Effect.promise(async () => {\n try {\n // Post comment on the PR\n const reviewStatus = loadReviewStatuses()[normalizedIssueId];\n const prUrl = reviewStatus?.prUrl;\n if (prUrl) {\n // Extract owner/repo#number from PR URL\n const prMatch = prUrl.match(/github\\.com\\/([^/]+)\\/([^/]+)\\/pull\\/(\\d+)/);\n if (prMatch) {\n const [, owner, repo, prNumber] = prMatch;\n const commentBody = `## Merge Failed\\n\\n${notes || 'Merge could not be completed.'}\\n\\nThe issue has been moved back to In Progress. The work agent needs to resolve conflicts and resubmit.`;\n await execFileAsync(\n 'gh',\n ['api', `repos/${owner}/${repo}/issues/${prNumber}/comments`, '--field', `body=${commentBody}`],\n { encoding: 'utf-8' },\n );\n console.log(`[specialists/done] Posted merge failure comment on ${prUrl}`);\n }\n }\n } catch (err: any) {\n console.warn(`[specialists/done] Failed to post merge failure comment: ${err.message}`);\n }\n });\n\n // If merge failed due to conflicts, send rebase instructions to the work agent.\n if (notes?.toLowerCase().includes('conflict')) {\n yield* Effect.promise(async () => {\n try {\n const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;\n const { sessionExists } = await import('../../../../lib/tmux.js');\n const { messageAgent, spawnAgent, getAgentStateSync } = await import('../../../../lib/agents.js');\n\n if (await Effect.runPromise(sessionExists(workAgentId))) {\n // Agent is running — send rebase instructions directly\n const rebaseMsg = `MERGE CONFLICT: The merge-agent could not rebase your branch onto main due to conflicts. Please fix this now:\\n\\n1. git fetch origin main\\n2. git rebase origin/main\\n3. Resolve any conflicts (git add <file> && git rebase --continue)\\n4. git push --force-with-lease\\n5. Resubmit: curl -s -X POST http://localhost:3011/api/review/${normalizedIssueId}/request -H \"Content-Type: application/json\" -d \"{}\"\\n\\nConflict details: ${notes}`;\n await messageAgent(workAgentId, rebaseMsg);\n console.log(`[specialists/done] Sent rebase instructions to ${workAgentId}`);\n } else {\n // Agent is stopped — start fresh (don't resume, sessions may be corrupted: PAN-612)\n console.log(`[specialists/done] Work agent ${workAgentId} not running — will need manual restart or next pan start dispatch`);\n }\n } catch (err: any) {\n console.warn(`[specialists/done] Failed to send rebase feedback to work agent: ${err.message}`);\n }\n });\n }\n }\n\n if (specialist === 'review' && (status === 'failed' || status === 'blocked')) {\n yield* Effect.promise(async () => {\n try {\n const project = resolveProjectFromIssueSync(normalizedIssueId);\n const workspacePath = project\n ? join(project.projectPath, 'workspaces', `feature-${normalizedIssueId.toLowerCase()}`)\n : undefined;\n const { deliverReviewVerdictFeedback } = await import(\n '../../../../lib/cloister/review-verdict-feedback.js'\n );\n const result = await Effect.runPromise(deliverReviewVerdictFeedback({\n issueId: normalizedIssueId,\n verdict: status === 'failed' ? 'failed' : 'blocked',\n notes,\n workspacePath,\n prUrl: updatedStatus.prUrl,\n }));\n console.log(\n `[specialists/done] Delivered review verdict feedback for ${normalizedIssueId}` +\n ` (feedback=${result.feedbackPath ?? 'none'}, synthesis=${result.synthesisPath ?? 'none'}, prComment=${result.prCommentPosted})`,\n );\n } catch (err: any) {\n console.warn(`[specialists/done] Failed to deliver review verdict feedback: ${err.message}`);\n }\n });\n }\n\n // Emit domain event for role-backed specialist completion/failure.\n const eventRole = specialistEventRole(specialist);\n if (eventRole) {\n if (status === 'passed') {\n yield* eventStore.append({\n type: 'specialist.completed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId: normalizedIssueId },\n });\n } else {\n yield* eventStore.append({\n type: 'specialist.failed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId: normalizedIssueId, error: notes || `${specialist} failed` },\n });\n }\n }\n\n return jsonResponse({\n success: true,\n specialist,\n issueId: normalizedIssueId,\n status,\n notes,\n currentStatus: updatedStatus,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/logs/cleanup-all ────────────────────────────\n// NOTE: Must be registered before /:project/:type routes.\n\nconst postSpecialistsLogsCleanupAllRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/logs/cleanup-all',\n httpHandler(Effect.gen(function* () {\n const { cleanupAllLogsSync } = yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n const results = cleanupAllLogsSync();\n\n return jsonResponse({\n success: true,\n totalDeleted: results.totalDeleted,\n byProject: results.byProject,\n message: `Cleaned up ${results.totalDeleted} old logs`,\n });\n })),\n);\n\n// ─── Route: GET /api/specialists/projects ────────────────────────────────────\n// NOTE: Must be registered before /:name routes.\n\nconst getSpecialistsProjectsRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/projects',\n httpHandler(Effect.gen(function* () {\n const { getAllProjectSpecialistStatuses } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const specialists = yield* Effect.promise(() => getAllProjectSpecialistStatuses());\n return jsonResponse(specialists);\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/wake ─────────────────────────────────\n\nconst postSpecialistWakeRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/wake',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n return jsonResponse(\n { error: `Legacy specialist wake is no longer supported for ${name}; role runs spawn agents directly.` },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/reset ─────────────────────────────────\n\nconst postSpecialistResetRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/reset',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n return jsonResponse(\n { error: `Legacy specialist session reset is no longer supported for ${name}; role agents are managed through the normal agent lifecycle.` },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/init ──────────────────────────────────\n\nconst postSpecialistInitRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/init',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n return jsonResponse(\n { error: `Legacy specialist initialization is no longer supported for ${name}; role flows spawn agents on demand.` },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/report-status ────────────────────────\n\nconst postSpecialistReportStatusRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/report-status',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n const body = yield* readJsonBody;\n const eventStore = yield* EventStoreService;\n const { issueId, status, notes } = body as {\n issueId?: string;\n status?: string;\n notes?: string;\n };\n\n if (!issueId || !status) {\n return jsonResponse(\n { error: 'issueId and status required' },\n { status: 400 },\n );\n }\n\n if (!['passed', 'blocked', 'failed', 'in-progress'].includes(status)) {\n return jsonResponse(\n { error: 'status must be: passed, blocked, failed, or in-progress' },\n { status: 400 },\n );\n }\n\n // Write status to specialist's state directory\n const specialistDir = join(homedir(), '.overdeck', 'specialists', name);\n yield* Effect.promise(() => mkdir(specialistDir, { recursive: true }));\n\n const statusFile = join(specialistDir, `${issueId}-status.json`);\n const statusData = {\n issueId,\n specialist: name,\n status,\n notes: notes || '',\n timestamp: new Date().toISOString(),\n };\n\n yield* Effect.promise(() => writeFile(statusFile, JSON.stringify(statusData, null, 2)));\n\n console.log(`[specialists] ${name} reported status for ${issueId}: ${status}`);\n\n // When specialist reports completion (passed/blocked/failed), set state to idle\n if (['passed', 'blocked', 'failed'].includes(status)) {\n const { getTmuxSessionName } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const tmuxSession = getTmuxSessionName(name as SpecialistAgentName);\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n });\n }\n\n // Emit domain event based on status\n const eventRole = specialistEventRole(name);\n if (eventRole && status === 'passed') {\n yield* eventStore.append({\n type: 'specialist.completed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId },\n });\n } else if (eventRole && (status === 'failed' || status === 'blocked')) {\n yield* eventStore.append({\n type: 'specialist.failed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId, error: notes || `${name} reported ${status}` },\n });\n }\n\n return jsonResponse({ success: true });\n })),\n);\n\n// ─── Route: GET /api/specialists/:name/cost ───────────────────────────────────\n\nconst getSpecialistCostRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:name/cost',\n httpHandler(Effect.gen(function* () {\n return jsonResponse({ cost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, model: '' });\n })),\n);\n\n// ─── Route: POST /api/specialists/:name/auto-complete ────────────────────────\n\nconst postSpecialistAutoCompleteRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:name/auto-complete',\n httpHandler(Effect.gen(function* () {\n const request = yield* HttpServerRequest.HttpServerRequest;\n const auth = yield* Effect.promise(() => validateAgentRuntimeEventAuth(request));\n if (!auth.ok) return auth.response;\n\n const params = yield* HttpRouter.params;\n const name = params['name'] as string;\n const body = (yield* readJsonBody) as SpecialistAutoCompleteBody;\n const eventStore = yield* EventStoreService;\n const { issueId: requestIssueId, status: requestStatus, agentId } = body;\n\n const agentState = agentId ? yield* getAgentState(agentId) : null;\n const runtimeState = agentId ? yield* getAgentRuntimeState(agentId) : null;\n const metadata = validateSpecialistAutoCompleteMetadata(name, body, agentState, runtimeState);\n if (!metadata.ok) {\n return jsonResponse({ error: metadata.error }, { status: metadata.status });\n }\n\n const issueId = requestIssueId!;\n const status = requestStatus!;\n const completingAgentId = agentId!;\n\n console.log(`[specialists] Auto-detected completion for ${name}: ${issueId} -> ${status}`);\n\n yield* Effect.promise(() => saveAgentRuntimeState(completingAgentId, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n currentIssue: undefined,\n }));\n\n // Update review/test status based on specialist type\n const existingStatus = getReviewStatusSync(issueId);\n\n if (name === 'review-agent') {\n const alreadyReported =\n existingStatus?.reviewNotes &&\n !existingStatus.reviewNotes.startsWith('Auto-detected:');\n if (alreadyReported) {\n console.log(\n `[specialists] Skipping auto-detect for ${name}/${issueId}: specialist already reported (${existingStatus!.reviewStatus})`,\n );\n } else {\n setReviewStatusBase(issueId, {\n reviewStatus: status === 'passed' ? 'passed' : 'blocked',\n reviewNotes: `Auto-detected: ${status}`,\n });\n }\n\n if ((alreadyReported ? existingStatus!.reviewStatus : status === 'passed' ? 'passed' : 'blocked') === 'passed') {\n console.log(`[specialists] ${issueId} review approved; reactive Cloister will dispatch the test role`);\n }\n } else if (name === 'test-agent') {\n const alreadyReported =\n existingStatus?.testNotes && !existingStatus.testNotes.startsWith('Auto-detected:');\n if (alreadyReported) {\n console.log(\n `[specialists] Skipping auto-detect for ${name}/${issueId}: specialist already reported (${existingStatus!.testStatus})`,\n );\n } else {\n const testPassed = status === 'passed';\n setReviewStatusBase(issueId, {\n testStatus: testPassed ? 'passed' : 'failed',\n testNotes: `Auto-detected: ${status}`,\n });\n if (testPassed) {\n // Emit test.passed so reactive Cloister records the shipping\n // lifecycle phase; readyForMerge is derived server-side.\n yield* eventStore.append({\n type: 'test.passed',\n timestamp: new Date().toISOString(),\n payload: { issueId },\n });\n console.log(`[specialists] ${issueId} emitted test.passed after auto-detected test pass`);\n }\n }\n }\n\n const eventRole = specialistEventRole(name);\n if (eventRole) {\n yield* eventStore.append({\n type: 'specialist.completed',\n timestamp: new Date().toISOString(),\n payload: { name: eventRole, issueId },\n });\n }\n\n return jsonResponse({\n success: true,\n status,\n issueId,\n });\n })),\n);\n\nexport const specialistsLegacyRouteLayer = Layer.mergeAll(\n getSpecialistsRoute,\n getSpecialistsProjectsRoute,\n postSpecialistsResetAllRoute,\n postSpecialistsDoneRoute,\n postSpecialistsLogsCleanupAllRoute,\n postSpecialistWakeRoute,\n postSpecialistResetRoute,\n postSpecialistInitRoute,\n postSpecialistReportStatusRoute,\n getSpecialistCostRoute,\n postSpecialistAutoCompleteRoute,\n);\n","import { existsSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { Effect, Layer, Option, Stream } from 'effect';\nimport { HttpRouter, HttpServerRequest, HttpServerResponse } from 'effect/unstable/http';\n\nimport { getClaudePermissionFlagsStringSync } from '../../../../lib/claude-permissions.js';\nimport { normalizeModelName } from '../../../../lib/cost-parsers/jsonl-parser.js';\nimport { calculateCostSync, getPricingSync, type TokenUsage } from '../../../../lib/cost.js';\nimport { loadConfigSync, resolveModel } from '../../../../lib/config-yaml.js';\nimport { encodeClaudeProjectDir } from '../../../../lib/paths.js';\nimport { resolveProjectFromIssueSync } from '../../../../lib/projects.js';\nimport { getReviewStatusSync } from '../../../../lib/review-status.js';\nimport { getAgentCommandSync } from '../../../../lib/settings.js';\nimport { killSession } from '../../../../lib/tmux.js';\nimport { saveAgentRuntimeState } from '../../../../lib/agents.js';\nimport { jsonResponse } from '../../http-helpers.js';\nimport { httpHandler } from '../http-handler.js';\nimport { execAsync, readJsonBody, validateSpecialistAgentName } from './shared.js';\n\n// ─── Route: GET /api/specialists/:project/:issueId/:type/status ───────────────\n//\n// PAN-1048 review feedback 003 (REQ-16): the legacy specialist-status route\n// returned metadata sourced from ~/.overdeck/specialists/registry.json, which\n// the role-primitive refactor explicitly retires. The startup cleanup in\n// service.ts deletes that directory on every boot; preserving the read path\n// would silently recreate it via getRunMetadata() → loadRegistry()/saveRegistry().\n//\n// The route is now a 410 Gone with a pointer to the role-aware status surface.\n// The frontend's only caller (AgentOutputPanel) only fires when\n// parseSpecialistSession(agentId) matches the legacy `specialist-…` session\n// naming, which the new role spawns no longer use, so the dead-letter response\n// is invisible to current dashboards.\n\nconst getProjectSpecialistStatusRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:issueId/:type/status',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n return jsonResponse(\n {\n error: 'specialist-status route retired',\n hint: 'Specialist identity is replaced by the role primitive. Use GET /api/agents and read the role/status fields off the AgentSnapshot for the role-scoped session (e.g. agent-pan-509-review).',\n retiredFor: {\n project: params['project'],\n issueId: params['issueId'],\n type: params['type'],\n },\n },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:issueId/:type/kill ───────────────\n\nconst postProjectSpecialistKillRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:issueId/:type/kill',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const issueId = params['issueId'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n\n const registryKey = makeSpecialistRegistryKey(type, issueId);\n const tmuxSession = getRunMetadata(project, registryKey).tmuxSession\n ?? getTmuxSessionName(type, project, issueId);\n\n yield* Effect.promise(() => Effect.runPromise(killSession(tmuxSession)).catch(() => {}));\n // Leave Claude JSONL/session artifacts intact; only reset Overdeck runtime state.\n saveAgentRuntimeState(tmuxSession, {\n state: 'idle',\n lastActivity: new Date().toISOString(),\n });\n return jsonResponse({\n success: true,\n message: `Killed ${type} (${project}/${issueId})`,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/spawn ───────────────────────\n//\n// PAN-1048 R1: removed. The legacy /spawn endpoint dispatched arbitrary\n// \"specialist types\" (review-agent, test-agent, merge-agent) by issuing a\n// generic spawnEphemeralSpecialist call. Under the role primitive, review/test\n// dispatch through lifecycle-aware role paths. Shipping is now server-side;\n// `ship` remains only as the merge-specialist identity for model routing and\n// historical activity attribution. The endpoint had no remaining in-tree caller\n// and is replaced by reactive Cloister scheduling on issue state transitions\n// plus the role spawn primitive.\n//\n// Old shape (removed):\n// POST /api/specialists/:project/:type/spawn { issueId, branch, ... }\n//\n// Replacement: drive role spawns via lifecycle.transitionTo() and the\n// reactive scheduler in src/lib/cloister/service.ts.\n\n// ─── Route: GET /api/specialists/:project/:type/runs ──────────────────────────\n\nconst getProjectSpecialistRunsRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/runs',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n const request = yield* HttpServerRequest.HttpServerRequest;\n const urlOpt = HttpServerRequest.toURL(request);\n\n let limit: number | undefined;\n let offset = 0;\n\n if (Option.isSome(urlOpt)) {\n const sp = urlOpt.value.searchParams;\n const limitParam = sp.get('limit');\n const offsetParam = sp.get('offset');\n if (limitParam) limit = parseInt(limitParam, 10);\n if (offsetParam) offset = parseInt(offsetParam, 10);\n }\n\n const { listRunLogsSync } = yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n const runs = listRunLogsSync(project, type, { limit, offset });\n return jsonResponse(runs);\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/runs/:runId/stream ────────────\n// NOTE: Must be registered before /:project/:type/runs/:runId to avoid route conflict.\n\nconst getProjectSpecialistRunStreamRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/runs/:runId/stream',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n const runId = params['runId'] as string;\n\n const { getRunLogPath, isRunLogActive } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n\n const logPath = getRunLogPath(project, type, runId);\n\n if (!existsSync(logPath)) {\n return jsonResponse({ error: 'Run log not found' }, { status: 404 });\n }\n\n // Build an SSE stream using Effect Stream + Node ReadableStream\n const encoder = new TextEncoder();\n\n const nodeStream = new ReadableStream({\n async start(controller) {\n // Send initial content\n const content = await readFile(logPath, 'utf-8').catch(() => '');\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({ type: 'content', data: content })}\\n\\n`,\n ),\n );\n let lastSize = content.length;\n\n // Poll for updates\n const poll = async () => {\n if (!isRunLogActive(project, type, runId)) {\n // Log completed — send final update and close\n try {\n const finalContent = await readFile(logPath, 'utf-8');\n if (finalContent.length > lastSize) {\n const newContent = finalContent.substring(lastSize);\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({ type: 'append', data: newContent })}\\n\\n`,\n ),\n );\n }\n } catch {}\n controller.enqueue(\n encoder.encode(`data: ${JSON.stringify({ type: 'complete' })}\\n\\n`),\n );\n controller.close();\n return;\n }\n\n try {\n const currentContent = await readFile(logPath, 'utf-8');\n if (currentContent.length > lastSize) {\n const newContent = currentContent.substring(lastSize);\n controller.enqueue(\n encoder.encode(\n `data: ${JSON.stringify({ type: 'append', data: newContent })}\\n\\n`,\n ),\n );\n lastSize = currentContent.length;\n }\n } catch {}\n\n await new Promise((r) => setTimeout(r, 1000));\n await poll();\n };\n\n await new Promise((r) => setTimeout(r, 1000));\n await poll();\n },\n });\n\n const effectStream = Stream.fromReadableStream<Uint8Array, unknown>({\n evaluate: () => nodeStream,\n onError: (err) => err,\n });\n\n return HttpServerResponse.stream(effectStream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n Connection: 'keep-alive',\n },\n });\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/runs/:runId ───────────────────\n\nconst getProjectSpecialistRunRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/runs/:runId',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n const runId = params['runId'] as string;\n\n const { getRunLogSync, parseLogMetadata } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n const content = getRunLogSync(project, type, runId);\n\n if (!content) {\n return jsonResponse({ error: 'Run log not found' }, { status: 404 });\n }\n\n const metadata = parseLogMetadata(content);\n return jsonResponse({ runId, content, metadata });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/runs/:runId/terminate ────────\n\nconst postProjectSpecialistRunTerminateRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/runs/:runId/terminate',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { terminateSpecialist } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n yield* Effect.promise(() => terminateSpecialist(project, type));\n return jsonResponse({ success: true, message: 'Specialist terminated' });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/grace/pause ──────────────────\n\nconst postProjectSpecialistGracePauseRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/grace/pause',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { pauseGracePeriod } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const success = pauseGracePeriod(project, type);\n\n if (success) {\n return jsonResponse({ success: true, message: 'Grace period paused' });\n } else {\n return jsonResponse(\n { error: 'No active grace period to pause' },\n { status: 400 },\n );\n }\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/grace/resume ─────────────────\n\nconst postProjectSpecialistGraceResumeRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/grace/resume',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { resumeGracePeriod } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const success = resumeGracePeriod(project, type);\n\n if (success) {\n return jsonResponse({ success: true, message: 'Grace period resumed' });\n } else {\n return jsonResponse(\n { error: 'No paused grace period to resume' },\n { status: 400 },\n );\n }\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/grace/exit ───────────────────\n\nconst postProjectSpecialistGraceExitRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/grace/exit',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { exitGracePeriod } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n exitGracePeriod(project, type);\n return jsonResponse({\n success: true,\n message: 'Specialist terminated immediately',\n });\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/grace ────────────────────────\n\nconst getProjectSpecialistGraceRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/grace',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { getGracePeriodState } = yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n const state = getGracePeriodState(project, type);\n\n if (state) {\n return jsonResponse(state);\n } else {\n return jsonResponse({ error: 'No active grace period' }, { status: 404 });\n }\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/context ──────────────────────\n\nconst getProjectSpecialistContextRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/context',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n const { loadContextDigest } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialist-context.js'));\n const digest = loadContextDigest(project, type);\n\n if (digest) {\n return jsonResponse({ digest });\n } else {\n return jsonResponse({ error: 'No context digest found' }, { status: 404 });\n }\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/context/regenerate ───────────\n\nconst postProjectSpecialistContextRegenerateRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/context/regenerate',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n const { regenerateContextDigest } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialist-context.js'));\n const digest = yield* Effect.promise(() => regenerateContextDigest(project, type));\n\n if (digest) {\n return jsonResponse({ digest, message: 'Context digest regenerated' });\n } else {\n return jsonResponse(\n { error: 'Failed to generate context digest' },\n { status: 500 },\n );\n }\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/complete ─────────────────────\n\nconst postProjectSpecialistCompleteRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/complete',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n const body = yield* readJsonBody;\n const { status, notes, issueId } = body as { status?: string; notes?: string; issueId?: string };\n\n if (!status || !['passed', 'failed', 'blocked'].includes(status)) {\n return jsonResponse(\n { error: 'Valid status (passed/failed/blocked) is required' },\n { status: 400 },\n );\n }\n\n if (!validateSpecialistAgentName(type)) {\n return jsonResponse(\n { error: 'Invalid specialist type. Must be review-agent, test-agent, or merge-agent' },\n { status: 400 },\n );\n }\n\n const { signalSpecialistCompletion } =\n yield* Effect.promise(() => import('../../../../lib/cloister/specialists.js'));\n signalSpecialistCompletion(project, type, { status: status as 'passed' | 'failed' | 'blocked', notes }, issueId);\n return jsonResponse({\n success: true,\n message: 'Specialist completion signaled, grace period started',\n });\n })),\n);\n\n// ─── Route: GET /api/specialists/:project/:type/latest-log ───────────────────\n\nconst getProjectSpecialistLatestLogRoute = HttpRouter.add(\n 'GET',\n '/api/specialists/:project/:type/latest-log',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n const runsDir = join(homedir(), '.overdeck', 'specialists', project, type, 'runs');\n if (!existsSync(runsDir)) {\n return jsonResponse({ log: null, message: 'No runs found' });\n }\n\n const files = (yield* Effect.promise(() => readdir(runsDir)))\n .filter((f) => f.endsWith('.log'))\n .sort()\n .reverse();\n\n if (files.length === 0) {\n return jsonResponse({ log: null, message: 'No run logs found' });\n }\n\n const latestLog = yield* Effect.promise(() => readFile(join(runsDir, files[0]), 'utf-8'));\n return jsonResponse({\n log: latestLog,\n file: files[0],\n totalRuns: files.length,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:type/logs/cleanup ────────────────\n\nconst postProjectSpecialistLogsCleanupRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:type/logs/cleanup',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const type = params['type'] as string;\n\n const { cleanupOldLogsSync } = yield* Effect.promise(() => import('../../../../lib/cloister/specialist-logs.js'));\n const { getSpecialistRetention } = yield* Effect.promise(() => import('../../../../lib/projects.js'));\n\n const retention = getSpecialistRetention(project);\n const deleted = cleanupOldLogsSync(project, type, { maxDays: retention.max_days, maxRuns: retention.max_runs });\n\n return jsonResponse({\n success: true,\n deleted,\n message: `Cleaned up ${deleted} old logs`,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/projects/:project/:name/reset-session ───────\n// Bumps the session generation so the next dispatch starts a fresh Claude session.\n// Old JSONL files are preserved.\n\nconst postProjectSpecialistResetSessionRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/projects/:project/:name/reset-session',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const projectKey = params['project'] ?? '';\n const name = params['name'] ?? '';\n return jsonResponse(\n { error: `Legacy specialist session rotation is no longer supported for ${projectKey}/${name}.` },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:issueId/review/restart ───────────\n// Kills all reviewer tmux sessions + coordinator, wipes the review agent\n// state directories (PAN-1985), then dispatches a fresh review through the\n// role primitive. The new review starts with a fresh `state.json` and a new\n// session id because the old dirs are gone.\n//\n// Convoy view vs Quick Review view is purely a presentation concern (the\n// menu label is \"Restart All\" vs \"Restart\"); the route behavior is the\n// same for both. Wipe+respawn is the deliberate override path for harness/\n// model switches — the NORMAL review flow continues the same session\n// across re-dispatches (PAN-1862), and this endpoint is the escape hatch\n// that pays the re-research cost.\n\nconst postProjectReviewRestartRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:issueId/review/restart',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n const project = params['project'] as string;\n const issueId = params['issueId'] as string;\n const body = yield* readJsonBody;\n const { model, harness } = body as { model?: string; harness?: 'claude-code' | 'pi' | 'codex' };\n\n const { killAllReviewerSessions } = yield* Effect.promise(\n () => import('../../../../lib/cloister/review-agent.js'),\n );\n const killResult = yield* killAllReviewerSessions(project, issueId);\n\n // PAN-1862: do NOT wipe here. The review session (state.json + saved session id) is preserved\n // so spawnReviewRoleForIssue can RESUME it — keeping the prior review's context so a restart\n // with the same model checks the fix instead of re-researching the whole diff. It wipes +\n // fresh-spawns internally ONLY when the harness/model actually changed.\n\n // Resolve workspace info for re-dispatch\n const projectConfig = resolveProjectFromIssueSync(issueId);\n if (!projectConfig) {\n return jsonResponse({ error: `Cannot resolve project for ${issueId}` }, { status: 404 });\n }\n const workspacePath = join(projectConfig.projectPath, 'workspaces', `feature-${issueId.toLowerCase()}`);\n if (!existsSync(workspacePath)) {\n return jsonResponse({ error: `Workspace not found: ${workspacePath}` }, { status: 404 });\n }\n\n // Detect branch\n let branch = 'unknown';\n try {\n const { stdout } = yield* Effect.promise(() => execAsync(\n `cd \"${workspacePath}\" && git branch --show-current`,\n { encoding: 'utf-8', timeout: 5000 },\n ));\n branch = stdout.trim() || 'unknown';\n } catch { /* non-fatal */ }\n\n // PAN-1048 R3: review now spawns through the role primitive.\n const { spawnReviewRoleForIssue } = yield* Effect.promise(\n () => import('../../../../lib/cloister/review-agent.js'),\n );\n const prUrl = getReviewStatusSync(issueId)?.prUrl;\n const result = yield* spawnReviewRoleForIssue({\n issueId,\n workspace: workspacePath,\n branch,\n prUrl,\n model,\n harness,\n });\n\n if (result.gated) {\n return jsonResponse({\n success: false,\n gated: true,\n message: result.message,\n killed: killResult.killed,\n wiped: [],\n model: model ?? undefined,\n harness: harness ?? undefined,\n }, { status: 409 });\n }\n\n return jsonResponse({\n success: result.success,\n message: result.message,\n killed: killResult.killed,\n wiped: [],\n model: model ?? undefined,\n harness: harness ?? undefined,\n });\n })),\n);\n\n// ─── Route: POST /api/specialists/:project/:issueId/reviewer/:role/restart ───\n//\n// PAN-1048 review feedback 005 (C5): the per-reviewer restart endpoint is\n// retired. The role primitive launches the four code-review-* sub-agents in a\n// single review-role run via the Agent tool — there is no longer a per-axis\n// tmux session to restart, no `pan-review-agent` shell to relaunch, and no\n// per-reviewer prompt file to feed back in. Returning 410 with a pointer to\n// the supported restart surface keeps any frontend cache/intent that still\n// hits this URL from silently re-establishing the legacy machinery.\n\nconst postProjectReviewerRoleRestartRoute = HttpRouter.add(\n 'POST',\n '/api/specialists/:project/:issueId/reviewer/:role/restart',\n httpHandler(Effect.gen(function* () {\n const params = yield* HttpRouter.params;\n return jsonResponse(\n {\n error: 'per-reviewer restart route retired',\n hint: 'The review role launches all four code-review-* sub-agents in a single run via the Agent tool. To re-run a review, POST /api/review/:issueId/trigger (full review restart through spawnReviewRoleForIssue), which dispatches role: review and re-fans-out the convoy.',\n retiredFor: {\n project: params['project'],\n issueId: params['issueId'],\n role: params['role'],\n },\n },\n { status: 410 },\n );\n })),\n);\n\n// ─── Route: GET /api/models/resolve ──────────────────────────────────────────\n// Returns the resolved default model for each session/work type.\n\nconst getModelsResolveRoute = HttpRouter.add(\n 'GET',\n '/api/models/resolve',\n httpHandler(Effect.gen(function* () {\n const config = loadConfigSync().config;\n\n const routes = [\n { key: 'role:plan', role: 'plan' },\n { key: 'role:work', role: 'work' },\n { key: 'role:work.inspect', role: 'work', subRole: 'inspect' },\n { key: 'role:work.inspect-deep', role: 'work', subRole: 'inspect-deep' },\n { key: 'role:strike', role: 'strike' },\n { key: 'role:review', role: 'review' },\n { key: 'role:review.correctness', role: 'review', subRole: 'correctness' },\n { key: 'role:review.security', role: 'review', subRole: 'security' },\n { key: 'role:review.performance', role: 'review', subRole: 'performance' },\n { key: 'role:review.requirements', role: 'review', subRole: 'requirements' },\n { key: 'role:test', role: 'test' },\n { key: 'role:ship', role: 'ship' },\n ] as const;\n\n const resolved: Record<string, string | null> = {};\n for (const route of routes) {\n try {\n resolved[route.key] = resolveModel(route.role, (route as { subRole?: string }).subRole, config);\n } catch {\n resolved[route.key] = null;\n }\n }\n\n return jsonResponse(resolved);\n })),\n);\n\nexport const specialistsProjectRouteLayer = Layer.mergeAll(\n // PAN-1048 R1: postProjectSpecialistSpawnRoute removed (see above).\n getProjectSpecialistStatusRoute,\n postProjectSpecialistKillRoute,\n getProjectSpecialistRunsRoute,\n getProjectSpecialistRunStreamRoute, // /runs/:runId/stream — before /runs/:runId\n getProjectSpecialistRunRoute, // /runs/:runId\n postProjectSpecialistRunTerminateRoute, // /runs/:runId/terminate\n postProjectSpecialistGracePauseRoute, // /grace/pause\n postProjectSpecialistGraceResumeRoute, // /grace/resume\n postProjectSpecialistGraceExitRoute, // /grace/exit\n getProjectSpecialistGraceRoute, // /grace\n getProjectSpecialistContextRoute, // /context\n postProjectSpecialistContextRegenerateRoute, // /context/regenerate\n postProjectSpecialistCompleteRoute, // /complete\n getProjectSpecialistLatestLogRoute, // /latest-log\n postProjectSpecialistLogsCleanupRoute, // /logs/cleanup\n postProjectSpecialistResetSessionRoute, // /reset-session\n postProjectReviewRestartRoute, // /:project/:issueId/review/restart\n postProjectReviewerRoleRestartRoute, // /:project/:issueId/reviewer/:role/restart\n getModelsResolveRoute, // /models/resolve\n);\n","/**\n * Specialists route module — Effect HttpRouter.Layer (PAN-428 B9)\n *\n * Implements all /api/specialists/* endpoints from the Express server:\n * GET /api/specialists\n * POST /api/specialists/reset-all\n * POST /api/specialists/done\n * POST /api/specialists/logs/cleanup-all\n * GET /api/specialists/queues\n * GET /api/specialists/projects\n * POST /api/specialists/:name/wake\n * POST /api/specialists/:name/reset\n * POST /api/specialists/:name/init\n * POST /api/specialists/:name/report-status\n * GET /api/specialists/:name/cost\n * GET /api/specialists/:name/queue\n * POST /api/specialists/:name/queue\n * DELETE /api/specialists/:name/queue/:itemId\n * PUT /api/specialists/:name/queue/reorder\n * POST /api/specialists/:name/auto-complete\n * GET /api/specialists/:project/:issueId/:type/status\n * POST /api/specialists/:project/:issueId/:type/kill\n * GET /api/specialists/:project/:type/queue\n * POST /api/specialists/:project/:type/spawn\n * GET /api/specialists/:project/:type/runs\n * GET /api/specialists/:project/:type/runs/:runId\n * GET /api/specialists/:project/:type/runs/:runId/stream\n * POST /api/specialists/:project/:type/runs/:runId/terminate\n * POST /api/specialists/:project/:type/grace/pause\n * POST /api/specialists/:project/:type/grace/resume\n * POST /api/specialists/:project/:type/grace/exit\n * GET /api/specialists/:project/:type/grace\n * GET /api/specialists/:project/:type/context\n * POST /api/specialists/:project/:type/context/regenerate\n * POST /api/specialists/:project/:type/complete\n * GET /api/specialists/:project/:type/latest-log\n * POST /api/specialists/:project/:type/logs/cleanup\n */\n\nimport { Layer } from 'effect';\n\nimport { specialistsLegacyRouteLayer } from './specialists/legacy-routes.js';\nimport { specialistsProjectRouteLayer } from './specialists/project-routes.js';\n\nexport {\n _serverManagedMerges,\n firePostMergeLifecycle,\n validateSpecialistAutoCompleteMetadata,\n} from './specialists/shared.js';\n\nexport const specialistsRouteLayer = Layer.mergeAll(\n specialistsLegacyRouteLayer,\n specialistsProjectRouteLayer,\n);\n\nexport default specialistsRouteLayer;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAUA,SAAA,aAAA,MAAA,SAAA;AAIE,QAAA,mBAAA,KAAA,UAAA,KAAA,EAAA,QAAA;;AAGF,SAAA,mBAAA,MAAA,SAAA;;AAKE,QAAA,mBAAA,KAAA,MAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACgBF,MAAA,oBAAA;AACA,MAAA,2BAAA;AACA,IAAA,eAAA;AACA,IAAA,mBAAA;AACA,IAAA,sBAAA;;;;;;;;;;;;;;AAeA,SAAA,YAAA,QAAA;;AAkBE,QAAA,OAAA,KAAA,OAAA,SAAA,kBAAA,QAAA,OAAA,QAAA,aAAA,EAAA,OAAA,oBAAA,IAAA,MAAA,EAAA,EAAA,QAAA,KAAA,CAAA,CAAA,CAAA,EAAA,OAAA,SAAA,sBAAA,QAAA,OAAA,QAAA,aAAA,EAAA,OAAA,wBAAA,IAAA,MAAA,EAAA,EAAA,QAAA,KAAA,CAAA,CAAA,CAAA,EAAA,OAAA,SAAA,yBAAA,QAAA,OAAA,QAAA,aAAA,EAAA,OAAA,2BAAA,IAAA,WAAA,EAAA,EAAA,QAAA,KAAA,CAAA,CAAA,CAAA,EAAA,OAAA,SAAA,gBAAA,QAAA,OAAA,QAAA,aAAA;;;;AA6CM,MAAA,MAAA,kBAAA,MAAA,CAAA,QAAA,aAAA,EAAA,OAAA,qBAAA,EAAA,EAAA,QAAA,KAAA,CAAA;;;;;;;;AAiBA,MAAA,EAAA,iBAAA,OAAA,KAAA,eAAA,mBAAA;AAEI,mBAAA;;AAEA,OAAA;AACE,gBAAA,KAAA,UAAA,QAAA,IAAA,MAAA,OAAA,MAAA,aAAA,SAAA,GAAA,EAAA;;AAEA,gBAAA;;AAEF,WAAA,MAAA,4CAAA,MAAA,KAAA,aAAA,YAAA,OAAA,MAAA,SAAA,OAAA,MAAA,CAAA,UAAA,UAAA,iBAAA,eAAA;;AAMA,uBAAA;;AAEA,OAAA,MAAA,uBAAA,0BAAA;AACE,0BAAA;AACA,YAAA,MAAA,4CAAA,MAAA,KAAA,iBAAA,qFAAA,OAAA,MAAA,GAAA;AAIA,uBAAA;;;;AAMN,SAAA,aAAA,EAAA,OAAA,SAAA,EAAA,EAAA,QAAA,KAAA,CAAA;;;;;;;;;AChKR,IAAA,sBAAA,cAAA,KAAA,YAAA,sBAAA,CAAA;AA0CC,eAAA,4BAAA,UAAA,EAAA,EAAA;;;;AAKC,KAAA;AACE,QAAA,MAAA,WAAA,UAAA,OAAA;AACA,iBAAA,MAAA,OAAA,SAAA,EAAA;;AAEA,SAAA,EAAA,QAAA,WAAA;;;AAIF,KAAA;AACE,UAAA,KAAA,MAAA,IAAA;;AAEA,SAAA;;;;;;AAIF,KAAA,CAAA,YAAA,QAAA;;;;;AAKA,KAAA,CAAA,OAAA,QAAA;;;;;AAKA,KAAA,WAAA,KAAA,QAAA;;;;;;AAOA,KAAA,SAAA,OAAA,KAAA,KAAA,CAAA,QAAA;;;;;AAcA,QAAA,MAAA,yBAAA;;;;;;;AAIF,eAAA,YAAA,MAAA;;;AAGE,KAAA;;;;AAIE,QAAA,KAAA,KAAA,QAAA,GAAA,QAAA,KAAA,OAAA,OAAA;AACA,SAAA,OAAA,SAAA,OAAA;;AAEA,QAAA,KAAA,OAAA;;;AAIJ,eAAA,yBAAA,YAAA,OAAA,WAAA,SAAA;;AAOE,KAAA;AACE,WAAA,MAAA,YAAA,oBAAA,CAAA;;AAEA,SAAA;;AAEF,QAAA,sBAAA,QAAA,YAAA,OAAA,WAAA,QAAA;;AAGF,MAAA,oBAAA,OAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BA,SAAA,sBAAA,QAAA,YAAA,OAAA,WAAA,UAAA,EAAA,EAAA;;;;;;;AAwBI,OAAA,IAAA,IAAA,KAAA,KAAA,KAAA,IAAA,GAAA,MAAA,GAAA,EAAA,KAAA;;AAEE,OAAA,GAAA;;AAEE,QAAA,OAAA,SAAA,EAAA,CAAA,QAAA;;;AAGJ,SAAA;;;;;AAMF,MAAA,IAAA,IAAA,MAAA,SAAA,GAAA,KAAA,GAAA,KAAA;;AAEE,MAAA,iBAAA,MAAA,WAAA,KAAA,IAAA,iBAAA,KAAA,GAAA;AACE,oBAAA;AACA,0BAAA,YAAA,EAAA;;AAEF,MAAA,iBAAA,KAAA,UAAA,KAAA,CAAA,kBAAA;AAGA,MAAA,kBAAA,KAAA,kBAAA,EAAA;;AAIF,KAAA,iBAAA,EAAA,QAAA;AAGA,KAAA,iBAAA,eAAA,QAAA;AAMA,KAAA,QAAA,qBAAA,KAAA,GAAA;AACE,MAAA,yBAAA,QAAA,uBAAA,OAAA,QAAA,iBAAA,QAAA;AAGA,SAAA;;;;;;AAIF,KAAA,yBAAA,QAAA,MAAA,uBAAA,kBAAA,QAAA;AAIA,QAAA;;;;;;;;;;;;;AAYF,MAAA,wBAAA,UAAA,EAAA,KAAA,OAAA,WAAA;;;;;;;;;AChPA,IAAI;AAEJ,SAAS,iBAAiB,SAAsB,KAA+B;AAC7E,KAAI,CAAC,IAAK;CACV,MAAM,aAAa,gBAAgB,IAAI,MAAM,CAAC;AAC9C,KAAI,WAAY,SAAQ,IAAI,WAAW;;AAGzC,SAAgB,oBAA8B;AAC5C,KAAI,yBAAyB,KAAA,EAC3B,QAAO;CAET,MAAM,OAAO,SAAS,QAAQ,IAAI,eAAe,QAAQ,IAAI,WAAW,QAAQ,GAAG;CACnF,MAAM,eAAe,QAAQ,IAAI,oBAAoB,oBAAoB;CACzE,MAAM,0BAAU,IAAI,KAAa;AACjC,kBAAiB,SAAS,aAAa;AACvC,kBAAiB,SAAS,oBAAoB,OAAO;AACrD,kBAAiB,SAAS,oBAAoB,OAAO;AACrD,kBAAiB,SAAS,wBAAwB;AAClD,kBAAiB,SAAS,wBAAwB;CAElD,MAAM,iBAAiB,QAAQ,IAAI;AACnC,MAAK,MAAM,UAAU,gBAAgB,MAAM,IAAI,IAAI,EAAE,CACnD,kBAAiB,SAAS,OAAO;CAGnC,MAAM,gBAAgB,QAAQ,IAAI,8BAA8B,QAAQ,IAAI;AAC5E,KAAI,QAAQ,IAAI,gCAAgC,OAAO,cACrD,kBAAiB,SAAS,WAAW,gBAAgB;AAGvD,KAAI,QAAQ,IAAI,gBAAgB,eAAe;AAC7C,mBAAiB,SAAS,wBAAwB;AAClD,mBAAiB,SAAS,wBAAwB;;AAEpD,wBAAuB,MAAM,KAAK,QAAQ;AAC1C,QAAO;;AAGT,SAAgB,gBAAgB,QAA+B;AAC7D,KAAI;EACF,MAAM,MAAM,IAAI,IAAI,OAAO;AAC3B,SAAO,GAAG,IAAI,SAAS,IAAI,IAAI;SACzB;AACN,SAAO;;;AAIX,SAAgB,iBAAiB,SAAoB,MAAkC;CACrF,MAAM,SAAS,QAAQ;AACvB,KAAI,MAAM,QAAQ,OAAO,CAAE,QAAO,OAAO;AACzC,KAAI,OAAQ,QAAO;CAEnB,MAAM,YAAY,KAAK,aAAa;AACpC,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE;AAClD,MAAI,IAAI,aAAa,KAAK,UAAW;AACrC,SAAO,MAAM,QAAQ,MAAM,GAAG,MAAM,KAAK;;;AAK7C,SAAgB,sBACd,SACA,QAC6C;CAC7C,MAAM,SAAS,iBAAiB,SAAS,SAAS;CAClD,MAAM,UAAU,iBAAiB,SAAS,UAAU;CACpD,MAAM,UAAU,mBAAmB;AAEnC,KAAI,CAAC,UAAU,CAAC,SAAS;EACvB,MAAM,cAAc,OAAO,aAAa;AACxC,MAAI,gBAAgB,SAAS,gBAAgB,OAC3C,QAAO,EAAE,IAAI,MAAM;AAErB,SAAO;GAAE,IAAI;GAAO,OAAO;GAAkB;;AAG/C,KAAI,QAAQ;EACV,MAAM,aAAa,gBAAgB,OAAO;AAC1C,MAAI,cAAc,QAAQ,SAAS,WAAW,CAC5C,QAAO,EAAE,IAAI,MAAM;AAErB,SAAO;GAAE,IAAI;GAAO,OAAO;GAAkB;;AAG/C,KAAI,CAAC,QACH,QAAO;EAAE,IAAI;EAAO,OAAO;EAAmB;CAEhD,MAAM,aAAa,gBAAgB,QAAQ;AAC3C,KAAI,cAAc,QAAQ,SAAS,WAAW,CAC5C,QAAO,EAAE,IAAI,MAAM;AAErB,QAAO;EAAE,IAAI;EAAO,OAAO;EAAmB;;AAGhD,SAAgB,eACd,SAC6C;AAC7C,QAAO,sBAAsB,QAAQ,SAAsB,QAAQ,OAAO;;;;;;;AAY5E,SAAgB,uBACd,QACA,OACS;AACT,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,aAAa,gBAAgB,OAAO;AAC1C,KAAI,CAAC,WAAY,QAAO;AACxB,QAAO,mBAAmB,CAAC,SAAS,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7EjD,eAAA,aAAA,GAAA;AACE,QAAA,OAAA,EAAA,CAAA,WAAA,YAAA,MAAA;;AAGF,eAAA,aAAA,GAAA;AACE,QAAA,SAAA,GAAA,QAAA,CAAA,YAAA,KAAA;;AAGF,SAAA,mBAAA,SAAA;AACE,QAAA,mBAAA,QAAA;;;;;;;;;;;;;;AAeF,eAAA,sBAAA,YAAA,SAAA,MAAA;AAKE,KAAA,WAAA,WAAA,EAAA,QAAA;AACA,KAAA,WAAA,WAAA,EAAA,QAAA,WAAA;;;AAOA,KAAA;;AAEE,MAAA,WAAA;;AAEE,oBAAA,WAAA,KAAA,QAAA;;;;;;AAKJ,KAAA,eAAA,WAAA,EAAA,KAAA;;;AAII,OAAA,MAAA,MAAA,WAAA,MAAA,MAAA,OAAA,MAAA;AAEI,OAAA,CAAA,SAAA,KAAA,IAAA,CAAA;AACA,kBAAA,KAAA;;;;;;;AAQR,MAAA,MAAA,EAAA,IAAA,UAAA,eAAA,KAAA;;AAGI,MAAA,CAAA,QAAA,EAAA,UAAA,KAAA,QAAA,QAAA;;;;;AASJ,QAAA,OAAA,KAAA,KAAA,WAAA,WAAA,SAAA,MAAA;;;AAIF,eAAA,uBAAA,SAAA,OAAA,EAAA,EAAA;;;AAWE,KAAA,iBAAA,QAAA;;AAKA,KAAA,YAAA,KAAA;;AAGI,MAAA,MAAA,QAAA,OAAA,EAAA;;AAGE,OAAA,MAAA,QAAA;;;AAMN,KAAA;;AAGE,MAAA,cAAA,gBAAA,QAAA,aAAA;;AAGF,QAAA;;;;;;;;;;;;;;;;;AAkBF,MAAA,gBAAA;AAEA,MAAA,yBAAA,IAAA,OAAA,sBAAA,cAAA,KAAA;AACA,MAAA,qBAAA,IAAA,OAAA,kBAAA,cAAA,KAAA;AAEA,eAAA,4BAAA,aAAA,OAAA,EAAA,EAAA;;;AASE,MAAA,MAAA,QAAA,YAAA;;AAEE,MAAA,CAAA,KAAA;;AAEA,MAAA,MAAA,QAAA,MAAA;;AAEF,QAAA;;;;;;;AAQF,eAAA,kBAAA,SAAA,MAAA;AAIE,KAAA,KAAA,kBAAA,KAAA;;;AAII,SAAA;;;;;AAKA,SAAA,EAAA,SAAA,MAAA;;;AAIJ,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;AAuBF,eAAA,oBAAA,SAAA,OAAA,EAAA,EAAA;;AAKE,KAAA,aAAA,WAAA,aAAA,QAAA,aAAA,SAAA,QAAA;AAKA,KAAA,MAAA,yBAAA,SAAA,KAAA,CAAA,QAAA,YAAA;AAGA,KAAA,MAAA,wBAAA,SAAA,KAAA,CAAA,QAAA;AACA,KAAA,MAAA,qBAAA,SAAA,KAAA,CAAA,QAAA;AACA,QAAA;;;;;;;AAQF,eAAA,yBAAA,SAAA,OAAA,EAAA,EAAA;;AAKE,KAAA,CAAA,UAAA,QAAA;;AAEA,KAAA,CAAA,UAAA,QAAA;AAEA,QAAA,aAAA,OAAA,KAAA,6BAAA,OAAA,SAAA,EAAA,WAAA,WAAA,EAAA,uBAAA,UAAA,EAAA,GAAA,UAAA,QAAA,CAAA;;;;;;;;;;;AAYF,eAAA,wBAAA,SAAA,OAAA,EAAA,EAAA;;;AAOE,KAAA,CAAA,MAAA,aAAA,UAAA,CAAA,QAAA;;;AAKA,KAAA,UAAA;;AAEE,MAAA,QAAA,QAAA;;AAEF,QAAA,kBAAA,UAAA;;;;;;;;;AAUF,eAAA,qBAAA,SAAA,OAAA,EAAA,EAAA;;;;;;;;AAQE,MAAA,MAAA,OAAA,CAAA,OAAA,UAAA,WAAA,EAAA,SAAA,EAAA;;AAEE,MAAA;AACE,aAAA,MAAA,QAAA,IAAA;;AAEA;;AAEF,OAAA,MAAA,QAAA,SAAA;AACE,OAAA,CAAA,KAAA,SAAA,SAAA,IAAA,eAAA,IAAA,KAAA,CAAA;;AAEA,OAAA;;AAEE,QAAA,CAAA,QAAA,IAAA,KAAA,MAAA,QAAA;;;;;;;AAIN,QAAA,MAAA,QAAA;;;;;;;;;AAUF,eAAA,iBAAA,SAAA,eAAA,OAAA,EAAA,EAAA;;AASE,KAAA,SAAA,mBAAA,sBAAA,QAAA,wBAAA,SAAA,KAAA;AAGA,KAAA,SAAA,mBAAA,eAAA,QAAA,qBAAA,SAAA,KAAA;;AAKA,KAAA,CAAA,gBAAA,QAAA;;AAKA,KAAA,MAAA,aAAA,UAAA,CAAA,QAAA;AACA,QAAA;;;;aC7W2K;AAmC7K,eAAe,WAAW,MAAgC;AACxD,KAAI;AACF,QAAM,OAAO,KAAK;AAClB,SAAO;SACD;AACN,SAAO;;;AAIX,SAAgB,+BAA+B,gBAAiD;CAC9F,MAAM,UAAU,iBAAiB,eAAe;CAChD,MAAM,aAAa,kBAAkB,QAAQ;CAC7C,MAAM,eAAe,yBAAyB,QAAQ;CACtD,MAAM,gBAAgB,CAAC,CAAC;CACxB,MAAM,kBAAkB,CAAC,CAAC,uBAAuB,QAAQ;CACzD,MAAM,qBAAqB,kBAAkB,QAAQ;CACrD,MAAM,eAAe,CAAC,CAAC,YAAY,aAAa,WAAW,WAAW,UAAU;CAChF,MAAM,cAAc,YAAY,UAAU;CAC1C,MAAM,UAAU,cAAc,SAAS;CACvC,MAAM,cAAc,cAAc,eAAe;CACjD,MAAM,gBAAgB,CAAC,CAAC,cAAc,gBAAgB,cAAc,OAAO,WAAW,UAAU,YAAY,WAAW,MAAM,WAAW,WAAW;CACnJ,MAAM,YAAY,gBAAgB,aAAa,gBAAgB,WAAW,eAAe,YAAY,aAAa,YAAY,UAAU,YAAY;CACpJ,MAAM,aAAa,gBAAgB,aAAa,kBAAkB;CAClE,MAAM,aAAa,gBAAgB,aAAa,kBAAkB,CAAC;CAInE,MAAM,oBAAoB,cAAc,YAAY,UAAU,YAAY;CAC1E,MAAM,2BAA2B,iBAAiB,gBAAgB,CAAC;CACnE,MAAM,aAAa,CAAC,uBACjB,mBAAmB,CAAC,4BACjB,kBAAkB,CAAC,gBAAgB;CAEzC,MAAM,uCAAuC,mBAAmB,CAAC,sBAAsB,6BAA6B,aAAa;CAEjI,IAAI,oBAAgD;CACpD,IAAI;AAEJ,KAAI,mBAAmB;AACrB,sBAAoB;AACpB,WAAS,SAAS,QAAQ,yCAAyC,QAAQ,sDAAsD,eAAe;YACvI,sBAAsB,gBAAgB,WAAW;AAC1D,sBAAoB;AACpB,WAAS,SAAS,QAAQ;YACjB,sBAAsB,WAAW;AAC1C,sBAAoB;AACpB,WAAS,SAAS,QAAQ,2DAA2D,eAAe,8BAA8B,eAAe;YACxI,YAAY;AACrB,sBAAoB;AACpB,WAAS,kBACL,SAAS,QAAQ,qIACjB,SAAS,QAAQ;YACZ,sCAAsC;AAC/C,sBAAoB;AACpB,WAAS,SAAS,QAAQ,mDAAmD,eAAe,kCAAkC,eAAe;YACpI,iBAAiB,CAAC,mBAAmB,WAAW;AACzD,sBAAoB;AACpB,WAAS,SAAS,QAAQ;YACjB,CAAC,iBAAiB,CAAC,iBAAiB;AAC7C,sBAAoB;AACpB,WAAS,SAAS,QAAQ;;AAG5B,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA,gBAAgB,CAAC,sBAAuB,sBAAsB,eAAgB,CAAC,wCAAwC;EAKvH,kBAAkB,CAAC,aAAa,mBAAmB,6BAA6B,aAAa;EAC7F,uBAAuB,iBAAiB;EACxC,iBAAiB,mBAAmB;EACpC;EACA;EACA;EACD;;AAGH,eAAe,mCAAmC,gBAA0D;CAC1G,MAAM,UAAU,iBAAiB,eAAe;CAChD,MAAM,aAAa,MAAM,OAAO,WAAW,cAAc,QAAQ,CAAC;CAClE,MAAM,eAAe,MAAM,OAAO,WAAW,qBAAqB,QAAQ,CAAC;CAC3E,MAAM,gBAAgB,CAAC,CAAC;CACxB,MAAM,kBAAkB,CAAC,CAAE,MAAM,OAAO,WAAW,mBAAmB,QAAQ,CAAC;CAC/E,MAAM,qBAAqB,MAAM,OAAO,WAAW,cAAc,QAAQ,CAAC;CAC1E,MAAM,eAAe,CAAC,CAAC,YAAY,aAAa,MAAM,WAAW,WAAW,UAAU;CACtF,MAAM,cAAc,YAAY,UAAU;CAC1C,MAAM,UAAU,cAAc,SAAS;CACvC,MAAM,cAAc,cAAc,eAAe;CACjD,MAAM,gBAAgB,CAAC,CAAC,cAAc,gBAAgB,cAAc,OAAO,WAAW,UAAU,YAAY,WAAW,MAAM,WAAW,WAAW;CACnJ,MAAM,YAAY,gBAAgB,aAAa,gBAAgB,WAAW,eAAe,YAAY,aAAa,YAAY,UAAU,YAAY;CACpJ,MAAM,aAAa,gBAAgB,aAAa,kBAAkB;CAClE,MAAM,aAAa,gBAAgB,aAAa,kBAAkB,CAAC;CACnE,MAAM,oBAAoB,cAAc,YAAY,UAAU,YAAY;CAC1E,MAAM,2BAA2B,iBAAiB,gBAAgB,CAAC;CACnE,MAAM,aAAa,CAAC,uBACjB,mBAAmB,CAAC,4BACjB,kBAAkB,CAAC,gBAAgB;CAEzC,MAAM,uCAAuC,mBAAmB,CAAC,sBAAsB,6BAA6B,aAAa;CAEjI,IAAI,oBAAgD;CACpD,IAAI;AAEJ,KAAI,mBAAmB;AACrB,sBAAoB;AACpB,WAAS,SAAS,QAAQ,yCAAyC,QAAQ,sDAAsD,eAAe;YACvI,sBAAsB,gBAAgB,WAAW;AAC1D,sBAAoB;AACpB,WAAS,SAAS,QAAQ;YACjB,sBAAsB,WAAW;AAC1C,sBAAoB;AACpB,WAAS,SAAS,QAAQ,2DAA2D,eAAe,8BAA8B,eAAe;YACxI,YAAY;AACrB,sBAAoB;AACpB,WAAS,kBACL,SAAS,QAAQ,qIACjB,SAAS,QAAQ;YACZ,sCAAsC;AAC/C,sBAAoB;AACpB,WAAS,SAAS,QAAQ,mDAAmD,eAAe,kCAAkC,eAAe;YACpI,iBAAiB,CAAC,mBAAmB,WAAW;AACzD,sBAAoB;AACpB,WAAS,SAAS,QAAQ;YACjB,CAAC,iBAAiB,CAAC,iBAAiB;AAC7C,sBAAoB;AACpB,WAAS,SAAS,QAAQ;;AAG5B,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA,gBAAgB,CAAC,sBAAuB,sBAAsB,eAAgB,CAAC,wCAAwC;EACvH,kBAAkB,CAAC,aAAa,6BAA6B,aAAa;EAC1E,uBAAuB,iBAAiB;EACxC,iBAAiB,mBAAmB;EACpC;EACA;EACA;EACD;;AAgC8C,KAAK,YAAY,8BAA8B;AAKhG,MAAa,8BACX,mBAEA,OAAO,cAAc,mCAAmC,eAAe,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;ACvN1E,MAAMO,kBAAgB,UAAU,SAAS;AAEzC,MAAM,kBAAkB;AACxB,MAAM,sBAAsB;AAwB5B,MAAM,wBAAQ,IAAI,KAAyB;;;;;;;;;;AAW3C,eAAe,eAAe,SAAyC;CACrE,MAAM,YAAYC,OAAK,SAAS,OAAO;CACvC,IAAI;AACJ,KAAI;AACF,eAAa,MAAMC,OAAK,UAAU;SAC5B;AACN,SAAO;;AAGT,KAAI,WAAW,aAAa,CAC1B,QAAOD,OAAK,WAAW,OAAO;AAGhC,KAAI,WAAW,QAAQ,CACrB,KAAI;EAEF,MAAM,SAAQ,MADS,SAAS,WAAW,QAAQ,EAC5B,MAAM,oBAAoB;AACjD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAOA,OAAK,MAAM,GAAG,MAAM,EAAE,OAAO;SAC9B;AACN,SAAO;;AAIX,QAAO;;AAGT,eAAe,gBAAgB,SAAyC;CACtE,MAAM,WAAW,MAAM,eAAe,QAAQ;AAC9C,KAAI,aAAa,KAAM,QAAO;AAC9B,KAAI;AAEF,UAAO,MADaC,OAAK,SAAS,EACrB;SACP;AACN,SAAO;;;AAIX,eAAe,iBAAiB,SAA+C;CAC7E,MAAM,CAAC,cAAc,kBAAkB,MAAM,QAAQ,IAAI,CACvDF,gBAAc,OAAO;EAAC;EAAM;EAAS;EAAa;EAAgB;EAAO,EAAE;EACzE,UAAU;EACV,SAAS;EACV,CAAC,CAAC,YAAY,KAAK,EACpBA,gBAAc,OAAO;EAAC;EAAM;EAAS;EAAa;EAAoB;EAAY,EAAE;EAClF,UAAU;EACV,SAAS;EACV,CAAC,CAAC,YAAY,KAAK,CACrB,CAAC;AAEF,KAAI,CAAC,aACH,QAAO;EAAE,QAAQ;EAAM,YAAY;EAAO;CAG5C,MAAM,SAAS,aAAa,OAAO,MAAM,IAAI;CAK7C,IAAI,aAAa;AACjB,KAAI,gBAAgB;EAClB,MAAM,CAAC,WAAW,UAAU,eAAe,OAAO,MAAM,CAAC,MAAM,KAAK,CAAC,KAAK,SAAS,KAAK,MAAM,CAAC;AAC/F,MAAI,aAAa,UAAU,cAAc,OACvC,cAAa;;AAIjB,QAAO;EAAE;EAAQ;EAAY;;;;;;AAO/B,eAAsB,2BAA2B,SAA+C;CAC9F,MAAM,MAAM,KAAK,KAAK;CACtB,MAAM,cAAc,MAAM,gBAAgB,QAAQ;AAClD,KAAI,gBAAgB,KAClB,QAAO;EAAE,QAAQ;EAAM,YAAY;EAAO;CAG5C,MAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,KAAI,UAAU,OAAO,YAAY,OAAO,OAAO,gBAAgB,YAC7D,QAAO,OAAO;CAGhB,MAAM,QAAQ,MAAM,iBAAiB,QAAQ;AAC7C,OAAM,IAAI,SAAS;EACjB;EACA,WAAW,MAAM;EACjB;EACD,CAAC;AACF,QAAO;;;;;;;;AAST,eAAsB,oBACpB,eACA,gBACuB;AAEvB,KAAI,MADsB,gBAAgB,cAAc,KACpC,KAClB,QAAO;EACL,cAAc;EACd,eAAe;EACf,kBAAkB;EACnB;CAGH,MAAM,OAAO,MAAM,2BAA2B,cAAc;AAC5D,KAAI,KAAK,WAAW,KAElB,QAAO;EACL,cAAc;EACd,eAAe;EACf,kBAAkB;EACnB;AAGH,QAAO;EACL,cAAc,KAAK;EACnB,eAAe,KAAK,WAAW;EAC/B,kBAAkB;EACnB;;;;YCpLwD;aAMxB;WAiBmB;AAGtD,MAAMG,cAAY,UAAU,KAAK;AACjC,MAAMC,kBAAgB,UAAU,SAAS;AAIzC,SAAgB,kBAAkB,OAKrB;AACX,QAAO;EACL;EACA,MAAM;EACN;EACA;EACA,MAAM;EACN,GAAI,MAAM,UAAU,CAAC,aAAa,MAAM,QAAQ,GAAG,EAAE;EACrD,GAAI,MAAM,YAAY,CAAC,UAAU,QAAQ,GAAG,EAAE;EAC/C;;;;;;;;;;AAWH,eAAsB,wBAAwB,OAO1B;CAClB,MAAM,EAAE,kBAAkB,SAAS,MAAM,eAAe,SAAS;CACjE,MAAM,MAAM,MAAM,OAAO;CACzB,MAAM,aAAa,YAAY,KAAK,KAAK;CACzC,MAAM,WAAWC,OAAK,SAAS,EAAE,aAAa,UAAU,iBAAiB;AACzE,OAAM,MAAM,UAAU,EAAE,WAAW,MAAM,CAAC;CAC1C,MAAM,eAAeA,OAAK,UAAU,YAAY;CAChD,MAAM,iBAAiB,MAAM,KAAK,cAAc,IAAI;CACpD,MAAM,QAAQ,MAAM,OAAO,MAAM;EAC/B;EACA,UAAU;EACV,OAAO;GAAC;GAAU,eAAe;GAAI,eAAe;GAAG;EACxD,CAAC;AACF,OAAM,KAAK,eAAe;AACnB,0BAAwB,kBAAkB,oCAAoC;GACjF;GACA;GACA;GACA;GACA,KAAK,MAAM;GACX;GACA;GACA;GACD,CAAC,CAAC,YAAY,KAAA,EAAU;GACzB;AACF,KAAI;EACF,MAAM,SAAS,MAAM,IAAI,SAAiE,SAAS,WAAW;AAC5G,SAAM,KAAK,UAAU,UAAU;AACxB,4BAAwB,kBAAkB,kCAAkC;KAC/E;KACA;KACA;KACA;KACA,OAAO,MAAM;KACb;KACA;KACA;KACD,CAAC,CAAC,YAAY,KAAA,EAAU;AACzB,WAAO,MAAM;KACb;AACF,SAAM,KAAK,UAAU,MAAM,WAAW;AAC/B,4BAAwB,kBAAkB,mCAAmC;KAChF;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACA;KACD,CAAC,CAAC,YAAY,KAAA,EAAU;AACzB,YAAQ;KAAE;KAAM;KAAQ,CAAC;KACzB;IACF;AACF,MAAI,OAAO,SAAS,GAAG;GACrB,MAAM,SAAS,MAAM,SAAS,cAAc,QAAQ,CAAC,YAAY,GAAG;GACpE,MAAM,QAAQ,IAAI,MAAM,OAAO,MAAM,IAAI,OAAO,KAAK,KAAK,IAAI,CAAC,oBAAoB,OAAO,QAAQ,SAAS;AAC3G,UAAO,OAAO,OAAO;IAAE;IAAY;IAAc,MAAM,OAAO;IAAM,QAAQ,OAAO;IAAQ;IAAQ,CAAC;AACpG,SAAM;;AAER,SAAO;WACC;AACR,QAAM,eAAe,OAAO;;;AAKhC,SAAS,oBACP,SACA,OACA,QACA,QACA,UAAmC,EAAE,EAC/B;CACN,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,uBAAsB;EACpB,QAAQ;EACR,OAAO,WAAW,YAAY,UAAU,WAAW,YAAY,SAAS;EACxE,SAAS,qBAAqB;EAC9B,SAAS,QAAQ,aAAa;EAC9B,SAAS,KAAK,UAAU;GAAE,SAAS,QAAQ,aAAa;GAAE;GAAW;GAAO;GAAQ;GAAQ,GAAG;GAAS,CAAC;EAC1G,CAAC;;AAGJ,MAAM,wBAAwB;AAE9B,SAAS,uBAAuB,UAA8B,UAA2B;AACvF,KAAI,CAAC,SAAU,QAAO;CACtB,MAAM,iBAAiB,OAAO,KAAK,UAAU,OAAO;CACpD,MAAM,iBAAiB,OAAO,KAAK,UAAU,OAAO;AACpD,KAAI,eAAe,WAAW,eAAe,OAC3C,QAAO;AAET,QAAO,gBAAgB,gBAAgB,eAAe;;AAGxD,eAAe,8BAAsD;CACnE,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,WAAW,QAAQ,SAAS,EAAG,QAAO;AAC1C,KAAI;EACF,MAAM,SAAS,MAAM,SAASA,OAAK,iBAAiB,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM;AACxF,SAAO,MAAM,SAAS,IAAI,QAAQ;SAC5B;AACN,SAAO;;;AAIX,eAAsB,8BACpB,SACA;CACA,MAAM,WAAW,MAAM,6BAA6B;AACpD,KAAI,CAAC,SACH,QAAO;EACL,IAAI;EACJ,UAAU,aAAa;GAAE,SAAS;GAAO,OAAO;GAAiC,EAAE,EAAE,QAAQ,KAAK,CAAC;EACpG;AAIH,KAAI,uBADa,iBAAiB,QAAQ,SAA0D,sBACjE,EAAE,SAAS,CAAE,QAAO,EAAE,IAAI,MAAe;AAC5E,QAAO;EACL,IAAI;EACJ,UAAU,aAAa;GAAE,SAAS;GAAO,OAAO;GAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;EAChF;;AAGH,eAAe,wBAAwB,SAAiB,OAAe,UAAmC,EAAE,EAAiB;CAC3H,MAAM,WAAWA,OAAK,SAAS,EAAE,aAAa,UAAU,QAAQ;AAChE,OAAM,MAAM,UAAU,EAAE,WAAW,MAAM,CAAC;CAC1C,MAAM,UAAU,KAAK,UAAU;EAC7B,qBAAI,IAAI,MAAM,EAAC,aAAa;EAC5B;EACA,GAAG;EACJ,CAAC;AACF,OAAM,WAAWA,OAAK,UAAU,gBAAgB,EAAE,UAAU,KAAK;;AAGnE,SAAS,4BAA4B,SAAiB,eAAuB,SAAuB;AAC7F,gCAA+B;EAClC;EACA;EACA;EACA,QAAQ;EACT,CAAC;;AAMJ,SAAS,sBAAuF;CAC9F,MAAM,EAAE,2BAAA,8BAAA,EAAA,aAAA,gCAAA;AACR,QAAO,uBAAuB;;AAMhC,MAAa,cAA6D;CAAE,MAAM;CAAM,WAAW;CAAG;;AAGtG,SAAgB,wBAA8B;AAC5C,aAAY,OAAO;AACnB,aAAY,YAAY;;AAG1B,SAAS,wBAA2B,QAAa,QAAwB;CACvE,MAAM,iBAAiB,+BAA+B,OAAO;AAC7D,KAAI,eAAe,SAAS,EAAG,QAAO;AACtC,QAAO,OAAO,QAAQ,UAAU;AAC9B,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;EAChD,MAAM,UAAW,MAAgC;AACjD,SAAO,OAAO,YAAY,YAAY,CAAC,eAAe,IAAI,QAAQ,aAAa,CAAC;GAChF;;AAMJ,MAAMC,iBAAe,OAAO,IAAI,aAAa;CAE3C,MAAM,OAAO,QAAO,OADG,kBAAkB,mBACb;AAC5B,KAAI;AACF,SAAO,OAAQ,KAAK,MAAM,KAAK,IAAI,EAAE,GAAI,EAAE;SACrC;AACN,SAAO,EAAE;;EAEX;AAEF,SAAS,qBAAqB,QAAuD;AACnF,QAAO,WAAW,cAAc,WAAW,aAAa,WAAW,aAAa,WAAW,UACvF,SACA;;AAGN,SAAS,8BAA8B,OAAmB,gBAA8B;AACtF,QAAO;EACL,SAAS,MAAM;EACf,SAAS,MAAM;EACf,QAAQ,qBAAqB,MAAM,OAAO;EAC1C;EACA,eAAe,MAAM,kBAAkB;EACvC,QAAQ,MAAM,WAAW;EACzB,cAAc,MAAM,gBAAgB;EACpC,UAAU,MAAM,YAAY;EAC5B,UAAU,MAAM,aAAa;EAC7B,YAAY,MAAM,cAAc;EAChC,qBAAqB,MAAM,uBAAuB;EAClD,qBAAqB,MAAM,uBAAuB;EAClD,eAAe,MAAM,iBAAiB;EACtC,mBAAmB,MAAM,qBAAqB;EAC9C,wBAAwB,MAAM,0BAA0B;EACzD;;AAGH,SAAS,8BAA8B,OAA4B;AACjE,QAAO;EACL,eAAe,MAAM,kBAAkB;EACvC,QAAQ,MAAM,WAAW;EACzB,cAAc,MAAM,gBAAgB;EACpC,UAAU,MAAM,YAAY;EAC5B,UAAU,MAAM,aAAa;EAC7B,YAAY,MAAM,cAAc;EAChC,qBAAqB,MAAM,uBAAuB;EAClD,qBAAqB,MAAM,uBAAuB;EAClD,eAAe,MAAM,iBAAiB;EACtC,mBAAmB,MAAM,qBAAqB;EAC9C,wBAAwB,MAAM,0BAA0B;EACzD;;AAGH,SAAS,2BACP,gBACA,OACA,aACyB;CACzB,MAAM,UAAU,iBAAiB,eAAe;CAChD,MAAM,gBAAgB;CACtB,MAAM,qBAAqB;CAC3B,MAAM,kBAAkB,CAAC,CAAC,YAAY;CACtC,MAAM,eAAe,OAAO,MAAM,cAAc,YAAY,MAAM,UAAU,SAAS;CACrF,MAAM,cAAc,MAAM,UAAU;CACpC,MAAM,UAAU,YAAY,SAAS;CACrC,MAAM,cAAc,YAAY,eAAe;CAC/C,MAAM,gBAAgB,gBAAgB,cAAc,OAAO,MAAM,UAAU,YAAY,MAAM,MAAM,WAAW,WAAW;CACzH,MAAM,YAAY,gBAAgB,aAAa,gBAAgB,WAAW,eAAe,YAAY,aAAa,YAAY,UAAU,YAAY;CACpJ,MAAM,YAAY;CAClB,MAAM,aAAa,gBAAgB,aAAa,kBAAkB;CAClE,MAAM,oBAAoB;CAC1B,MAAM,2BAA4C,gBAAgB,CAAC;CACnE,MAAM,aACH,mBAAmB,CAAC,4BACC,CAAC,gBAAgB;CAEzC,MAAM,uCAAuC,mBAAmB,6BAA6B,aAAa;CAE1G,IAAI,oBAAgD;CACpD,IAAI;AAEJ,KAAI,YAAY;AACd,sBAAoB;AACpB,WAAS,kBACL,SAAS,QAAQ,qIACjB,SAAS,QAAQ;YACZ,sCAAsC;AAC/C,sBAAoB;AACpB,WAAS,SAAS,QAAQ,mDAAmD,eAAe,kCAAkC,eAAe;YACnH,CAAC,mBAAmB,WAAW;AACzD,sBAAoB;AACpB,WAAS,SAAS,QAAQ;;AAG5B,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA,eAAe,CAAC,wCAAwC;EACxD,kBAAkB,mBAAmB,6BAA6B,aAAa;EAC/E,uBAAwC;EACxC,iBAAiB,mBAAmB;EACpC;EACA;EACA;EACD;;AAGH,eAAe,qBAAqB,SAAmD;CACrF,MAAM,kBAAkBD,OAAK,SAAS,EAAE,aAAa,UAAU,SAAS,oBAAoB;AAC5F,KAAI,CAAC,WAAW,gBAAgB,CAAE,QAAO,EAAE;AAC3C,KAAI;AACF,SAAO,KAAK,MAAM,MAAM,SAAS,iBAAiB,QAAQ,CAAC;SACrD;AACN,SAAO,EAAE;;;AAIb,eAAe,6BAA6B,SAAgC;CAC1E,MAAM,SAAS,MAAM,OAAO,WAC1B,YAAY,SAAS,IAAK,CAAC,KAAK,OAAO,YAAY,OAAO,QAAQ,GAAG,CAAC,CAAC,CACxE;AACD,KAAI,CAAC,OAAQ;CAEb,MAAM,WAAW,YAAY,QAAQ;AACrC,OAAM,MAAM,UAAU,EAAE,WAAW,MAAM,CAAC;AAC1C,OAAM,UAAUA,OAAK,UAAU,aAAa,EAAE,OAAO;;AAGvD,SAAS,8BAA8B,SAAyB;AAC9D,QAAO,sDAAsD,QAAQ,aAAa;;AAGpF,SAAS,eAAe,iBAA0B,aAA8B;AAC9E,KAAI,aAAa;EAEf,MAAM,WAAW,4BAA4B,GAD1B,YAAY,IACsB;AACrD,MAAI,SAAU,QAAO,SAAS;EAC9B,MAAM,SAAS,iBAAiB;AAChC,MAAI;QACG,MAAM,EAAE,OAAO,MAAM,YAAY,OAAO,MAE3C,MADmB,UAAU,KAAK,aAAa,CAAC,QAAQ,SAAS,GAAG,CAAC,QAAQ,MAAM,GAAG,EACvE,aAAa,KAAK,YAAY,aAAa,EAAE;IAC1D,MAAM,gBAAgB;KACpBA,OAAK,SAAS,EAAE,YAAY,KAAK;KACjCA,OAAK,SAAS,EAAE,YAAY,KAAK,QAAQ,SAAS,GAAG,CAAC;KACtDA,OAAK,SAAS,EAAE,YAAY,OAAO,KAAK;KACzC;AACD,SAAK,MAAM,QAAQ,cACjB,KAAI,WAAW,KAAK,CAAE,QAAO;;;;AAMvC,QAAOA,OAAK,SAAS,EAAE,WAAW;;AAGpC,eAAe,qBAAqB,SAA0D;AAC5F,KAAI;EACF,MAAM,gBAAgBA,OAAK,SAAS,EAAE,aAAa,aAAa;EAChE,MAAM,aAAa;GAAC,QAAQ,aAAa;GAAE,QAAQ,aAAa;GAAE;GAAQ;AAC1E,OAAK,MAAM,KAAK,YAAY;GAC1B,MAAM,WAAWA,OAAK,eAAe,GAAG,EAAE,OAAO;AACjD,OAAI,WAAW,SAAS,EAAE;AAExB,SAAI,MADkB,SAAS,UAAU,QAAQ,EACrC,SAAS,mBAAmB,CAAE,QAAO;AACjD,WAAO;;;SAGL;;AAIV,eAAe,kBAAkB,eAA2G;AAC1I,KAAI;AACF,MAAI,CAAC,WAAW,cAAc,CAAE,QAAO;EACvC,MAAM,CAAC,cAAc,mBAAmB,gBAAgB,MAAM,QAAQ,IAAI;GACxEF,YAAU,4DAA0D,EAAE,KAAK,eAAe,CAAC;GAC3FA,YAAU,8CAA8C,EAAE,KAAK,eAAe,CAAC;GAC/EA,YAAU,8DAA0D,EAAE,KAAK,eAAe,CAAC;GAC5F,CAAC;EACF,MAAM,SAAS,aAAa,OAAO,MAAM;EACzC,MAAM,cAAc,kBAAkB,OAAO,MAAM;EACnD,MAAM,eAAe,aAAa,OAAO,MAAM;AAC/C,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO;GACL;GACA,kBAAkB,SAAS,YAAY,IAAI;GAC3C,cAAc,aAAa,MAAM,GAAG,GAAG,IAAI,aAAa,SAAS,KAAK,QAAQ;GAC/E;SACK;AACN,SAAO;;;AAoBX,SAAS,8BAA8B,QAA0C;AAC/E,QAAO,OACJ,MAAM,GAAG,EAAE,CACX,KAAK,SAAS,GAAG,KAAK,KAAK,IAAI,KAAK,aAAa,GAAG,CACpD,KAAK,KAAK;;AAGf,SAAS,qBAAqB,SAAiB,UAA0B;CACvE,MAAM,MAAM,QAAQ,IAAI;AACxB,KAAI,OAAO,QAAQ,IAAI,MAAM,KAAK,GAAI,QAAO;CAC7C,MAAM,SAAS,OAAO,IAAI;AAC1B,QAAO,OAAO,SAAS,OAAO,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,GAAG;;AAcrE,SAAgB,uBACd,SACA,OAC+B;AAC/B,KAAI,OAAO,WAAW,KAEpB,QAAO;EACL,SAAS;EACT,SAAS;EACT,SAAS;EACT,OAAO,SAAS,QAAQ,YALX,MAAM,eAAe,KAAK,MAAM,aAAa,KAAK,GAKpB;EAC3C,MAAM,mBAAmB,QAAQ;EACjC;EACA,QAAQ;EACR,UAAU,MAAM,aAAa;EAC9B;AAGH,KAAI,OAAO,aAAa,MAAM;EAC5B,MAAM,WAAW,MAAM,uBAAuB;AAC9C,SAAO;GACL,SAAS;GACT,SAAS;GACT,SAAS;GACT,OAAO,SAAS,QAAQ,gBAAgB,SAAS,UAAU,aAAa,IAAI,KAAK,IAAI;GACrF,MAAM,wDAAwD,QAAQ;GACtE;GACA,QAAQ;GACR,UAAU;GACX;;AAGH,QAAO;;AAGT,SAAgB,0BACd,OACA,QAAgB,KAAK,KAAK,EACjB;AACT,KAAI,MAAM,WAAW,QAAQ,MAAM,aAAa,KAAM,QAAO;AAC7D,KAAI,CAAC,MAAM,uBAAwB,QAAO;CAC1C,MAAM,YAAY,KAAK,MAAM,MAAM,uBAAuB;AAC1D,QAAO,OAAO,SAAS,UAAU,IAAI,YAAY;;AAGnD,SAAgB,wBAAwB,QAAsD;CAC5F,MAAM,WAAqC,EAAE;CAC7C,MAAM,cAAc,KAAK,MAAO,OAAO,QAAQ,uBAAwB,QAAQ,IAAM,GAAG,GAAG;CAC3F,MAAM,iBAAiB,OAAO,QAAQ;CACtC,MAAM,oBAAoB,OAAO;CACjC,MAAM,iBAAiB,mBAAmB;CAC1C,MAAM,qBAAqB,qBAAqB,yBAAyB,eAAe,gBAAgB;CACxG,MAAM,qBAAqB,qBAAqB,wBAAwB,eAAe,eAAe;CAEtG,MAAM,aAAa,uBAAuB,OAAO,QAAQ,sBAAsB;EAC7E,cAAc,OAAO,WAAW;EAChC,eAAe,OAAO,WAAW;EAClC,CAAC;AACF,KAAI,eAAe,OACjB,UAAS,KAAK;EACZ,UAAU;EACV,MAAM;EACN,SAAS,oCAAoC,YAAY;EAC1D,CAAC;UACO,eAAe,OACxB,UAAS,KAAK;EACZ,UAAU;EACV,MAAM;EACN,SAAS,2BAA2B,YAAY;EACjD,CAAC;AAGJ,KAAI,kBAAkB,mBACpB,UAAS,KAAK;EACZ,UAAU;EACV,MAAM;EACN,SAAS,kDAAkD,eAAe,GAAG,mBAAmB;EACjG,CAAC;UACO,kBAAkB,mBAC3B,UAAS,KAAK;EACZ,UAAU;EACV,MAAM;EACN,SAAS,6BAA6B,eAAe,GAAG,mBAAmB;EAC5E,CAAC;AAGJ,KAAI,kBAAkB,SAAS,EAC7B,UAAS,KAAK;EACZ,UAAU,OAAO,QAAQ,uBAAuB,OAAO,WAAW,+BAA+B,aAAa;EAC9G,MAAM;EACN,SAAS,wCAAwC,8BAA8B,kBAAkB,GAAG,kBAAkB,SAAS,IAAI,MAAM,kBAAkB,SAAS,EAAE,SAAS,GAAG;EACnL,CAAC;CAGJ,MAAM,mBAAmB,SAAS,QAAQ,YAAY,QAAQ,aAAa,WAAW;AACtF,KAAI,iBAAiB,SAAS,GAAG;EAC/B,MAAM,uBAAuB,kBAAkB,SAAS;AACxD,SAAO;GACL,SAAS;GACT,yBAAyB;GACzB,QAAQ;GACR,OAAO,iBAAiB,IAAI,WAAW;GACvC,MAAM,uBACF,qEACA;GACJ;GACA,QAAQ;IACN,UAAU,OAAO;IACjB,SAAS,OAAO;IAChB,SAAS,OAAO;IAChB,mBAAmB,OAAO;IAC3B;GACF;;AAGH,QAAO;EACL,SAAS;EACT,yBAAyB,SAAS,SAAS;EAC3C,QAAQ,SAAS,SAAS,IAAI,MAAM;EACpC,MAAM,SAAS,SAAS,IAAI,uEAAuE,KAAA;EACnG;EACA,QAAQ;GACN,UAAU,OAAO;GACjB,SAAS,OAAO;GAChB,SAAS,OAAO;GAChB,mBAAmB,OAAO;GAC3B;EACF;;AAMH,MAAM,oBAAoBI;AAC1B,MAAM,oBAAoBC;AAE1B,MAAM,2BAA2BC;;;aChnBE;WAMmC;AAgBtE,MAAa,iBAAiB,WAAW,IACvC,OACA,eACA,YAAY,OAAO,IAAI,aAAa;CAC9B,MAAM,MAAM,KAAK,KAAK;AAEtB,KAAI,YAAY,QAAS,MAAM,YAAY,YAAA,IACzC,QAAO,aAAa,YAAY,KAAK;CAGvC,MAAM,WAAW,OAAO,cAAc;CACtC,MAAM,gBAAgB,IAAI,IAAI,SAAS,KAAK,YAAY,CAAC,QAAQ,MAAM,QAAQ,CAAC,CAAC;CACjF,MAAM,mBAAmB,iBAAiB,CACvC,QAAQ,UAAU,MAAM,GAAG,WAAW,SAAS,IAAI,MAAM,GAAG,WAAW,YAAY,IAAI,MAAM,GAAG,WAAW,UAAU,CAAC;CAmMzH,MAAM,gBAAgB,yBAjMH,OAAO,OAAO,cAAc,QAAQ,IACrD,iBAAiB,IAAI,OAAO,UAAU;EACpC,MAAM,OAAO,MAAM;EACnB,MAAM,aAAa,KAAK,WAAW,YAAY;EAC/C,MAAM,WAAW,KAAK,WAAW,UAAU;EAC3C,MAAM,UAAU,MAAM,SAAS,aAAa,KACzC,aAAa,KAAK,QAAQ,aAAa,GAAG,GAAG,WAAW,KAAK,QAAQ,WAAW,GAAG,GAAG,KAAK,QAAQ,UAAU,GAAG,EAAE,aAAa;EAClI,MAAM,UAAU,cAAc,IAAI,KAAK;EACvC,MAAM,cAAc,MAAM,qBAAqB,KAAK;EACpD,MAAM,WAAW,YAAY,aAAa;EAC1C,MAAM,cAAc,MAAM,OAAO,WAAW,qBAAqB,KAAK,CAAC;EACvE,MAAM,YAAY,MAAM,cAAc,UAAU,IAAI,KAAK,QAAQ,QAAQ,CAAC,aAAa,oBAAG,IAAI,MAAM,EAAC,aAAa;EAClH,MAAM,aAAaC,OAAK,SAAS,EAAE,aAAa,UAAU,MAAM,cAAc;EAC9E,IAAI,SAAc,EAAE,WAAW,GAAG;AAClC,MAAI,WAAW,WAAW,CACxB,KAAI;AAAE,YAAS;IAAE,GAAG;IAAQ,GAAG,KAAK,MAAM,MAAM,SAAS,YAAY,QAAQ,CAAC;IAAE;UAAU;AAG5F,MAAI,MAAM,WAAW,WAAW;GAC9B,MAAM,mBAAmB,MAAM,aAAa,aAAa,gBAAgB,MAAM;GAC/E,MAAM,YAAY,mBAAmB,IAAI,KAAK,iBAAiB,GAAG;GAClE,MAAM,eAAe,oBAAoB,QAAQ;GACjD,MAAM,0BACJ,0BAA0B,OAAO,IAAI,IAEnC,CAAC,CAAC,gBACF,aAAa,gBAAgB,aAE3B,CAAC,CAAC,aAAa,SACf,aAAa,kBAAkB,QAC/B,aAAa,iBAAiB,aAC9B,aAAa,eAAe,aAC5B,aAAa,gBAAgB;AAGnC,OAAI,aAAc,MAAM,UAAU,SAAS,GAAI,OAAU,OAAQ,CAAC,wBAAyB,QAAO;GAClG,MAAM,YAAY,2BAA2B,MAAM,OAAO,eAAe,EAAE,CAAC;GAC5E,MAAM,aAAa,aAAa,eAAe;GAC/C,MAAM,wBAAwB,aAC1B,6BACE,aAAa,uBACX,8DACH,GACD,KAAA;GACJ,MAAM,wBAAwB,aAC1B,aAAa,iBAAiB,UAC9B,KAAA;AACJ,UAAO;IACL,IAAI;IACJ;IACA,SAAS,MAAM,WAAW;IAC1B,OAAO,MAAM,UAAU,aAAa,SAAS;IAC7C,QAAQ;IACR;IACA,GAAG,8BAA8B,MAAM;IACvC,WAAW,OAAO,aAAa;IAC/B,WAAW,MAAM,aAAa;IAC9B,mBAAmB,WAAW,WAAW;IACzC,KAAK;IACL,MAAM;IACN,MAAM,MAAM,SAAS,WAAW,WAAW,aAAa,SAAS;IACjE,oBAAoB;IACpB,sBAAsB;IACtB;IACA;IACA,YAAY,aAAa,cAAc;IACvC,iBAAiB,aAAa,mBAAmB;IACjD,YAAY,UAAU;IACtB;IACA,GAAI,WAAW;KAAE,QAAQ;KAAM,QAAQ,YAAY;KAAQ,GAAG,EAAE;IACjE;;AAGH,MAAI,MAAM,WAAW,WACnB,QAAO;GACL,IAAI;GACJ;GACA,SAAS,MAAM,WAAW;GAC1B,OAAO,MAAM,UAAU,aAAa,SAAS;GAC7C,QAAQ;GACR;GACA,GAAG,8BAA8B,MAAM;GACvC,WAAW,OAAO,aAAa;GAC/B,WAAW,MAAM,aAAa;GAC9B,mBAAmB,WAAW,WAAW;GACzC,KAAK;GACL,MAAM;GACN,MAAM,MAAM,SAAS,WAAW,WAAW,aAAa,SAAS;GACjE,oBAAoB;GACpB,sBAAsB;GACtB,SAAU,MAA+B,WAAW;GACpD,GAAI,WAAW;IAAE,QAAQ;IAAM,QAAQ,YAAY;IAAQ,GAAG,EAAE;GACjE;AAGH,MAAI,MAAM,WAAW,QACnB,QAAO;GACL,IAAI;GACJ;GACA,SAAS,MAAM,WAAW;GAC1B,OAAO,MAAM,UAAU,aAAa,SAAS;GAC7C,QAAQ;GACR;GACA,GAAG,8BAA8B,MAAM;GACvC,WAAW,OAAO,aAAa;GAC/B,WAAW,MAAM,aAAa;GAC9B,mBAAmB,WAAW,WAAW;GACzC,KAAK;GACL,MAAM;GACN,MAAM,MAAM,SAAS,WAAW,WAAW,aAAa,SAAS;GACjE,oBAAoB;GACpB,sBAAsB;GACtB,OAAO,MAAM,qBAAqB;GAClC,GAAI,WAAW;IAAE,QAAQ;IAAM,QAAQ,YAAY;IAAQ,GAAG,EAAE;GACjE;AAGH,MAAI,CAAC,WAAW,CAAC,SACf,QAAO;GACL,IAAI;GACJ;GACA,SAAS,MAAM,WAAW;GAC1B,OAAO,MAAM,UAAU,aAAa,SAAS;GAC7C,QAAQ;GACR;GACA,cAAc,aAAa,gBAAgB,MAAM;GACjD,GAAG,8BAA8B,MAAM;GACvC,WAAW,OAAO,aAAa;GAC/B,WAAW,MAAM,aAAa;GAC9B,mBAAmB;GACnB,KAAK;GACL,MAAM;GACN,MAAM,MAAM,SAAS,WAAW,WAAW,aAAa,SAAS;GACjE,oBAAoB;GACpB,oBAAoB;GACpB,sBAAsB;GACtB,mBAAmB,MAAM,qBAAqB;GAC9C,YAAY,aAAa,cAAc;GACvC,iBAAiB,aAAa,mBAAmB;GAClD;EAGH,MAAM,oBAAoB,oBAAoB,QAAQ;EACtD,MAAM,sBAAsB,mBAAmB,iBAAiB,eAC3D,mBAAmB,eAAe,aAClC,mBAAmB,gBAAgB;EACxC,MAAM,aAAa,MAAM,OAAO,WAAW,uBAAuB,MAAM,WAAW,oBAAoB,CAAC;EACxG,MAAM,oBAAoB,WAAW,WAAW,MAAM,qBAAqB,QAAQ;EACnF,MAAM,YAAY,YAAY,YAAY,SACtC,eAAe,OAAO,YAAY,OAAO,CAAC,KAC1C,MAAM,aAAa;EACvB,MAAM,YAAY,aAAa,CAAC,WAAW,MAAM,kBAAkB,UAAU,GAAG;EAEhF,IAAI,iBAAgC;EACpC,IAAI,wBAAuC;EAC3C,MAAM,cAAcA,OAAK,SAAS,EAAE,aAAa,UAAU,KAAK;AAChE,MAAI;GACF,MAAM,UAAUA,OAAK,aAAa,cAAc;AAChD,oBAAiB,UAAU,MAAM,SAAS,SAAS,QAAQ,CAAC,YAAY,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI;GAC5F,MAAM,cAAcA,OAAK,aAAa,sBAAsB;AAC5D,2BAAwB,UAAU,MAAM,SAAS,aAAa,QAAQ,CAAC,YAAY,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI;UACjG;AAER,SAAO;GACL,IAAI;GACJ;GACA,SAAS,MAAM,WAAW;GAC1B,OAAO,MAAM,UAAU,aAAa,SAAS;GAC7C,QAAQ;GACR;GACA,GAAG,8BAA8B,MAAM;GACvC,WAAW,OAAO,aAAa;GAC/B;GACA;GACA,KAAK;GACL,MAAM;GACN,MAAM,MAAM,SAAS,WAAW,WAAW,aAAa,SAAS;GACjE,oBAAoB;GACpB,oBAAoB,WAAW;GAC/B,sBAAsB,WAAW;GACjC,uBAAuB,WAAW;GAClC,uBAAuB,WAAW;GAClC,mBAAmB,WAAW;GAC9B,mBAAmB,WAAW;GAC9B,wBAAwB,WAAW;GACnC,YAAY,aAAa,cAAc,WAAW,cAAc;GAChE,iBAAiB,aAAa,mBAAmB,WAAW,mBAAmB;GAC/E;GACA;GACA,GAAI,WAAW;IAAE,QAAQ;IAAM,QAAQ,YAAY;IAAQ,GAAG,EAAE;GACjE;GACD,CACH,CAAC,EAAE,OAAO,QAC4C,EAAE,qBAAqB,CAAC,WAAW,CAAC;AAC3F,aAAY,OAAO;AACnB,aAAY,YAAY;AACxB,QAAO,aAAa,cAAc;EACtC,CAAC,CACJ;;;;;;;;;AAiBD,SAAgB,4BACd,YAC0B;AAC1B,QAAO,QAAQ,YAAY,aAAa,WAAW,QAAQ;;;;;;;;;;AAW7D,MAAa,8BAA8B;CACzC,cAAc;CACd,eAAe;CACf,kBAAkB;CAClB,gBAAgB;CACjB;AAED,MAAa,uBAAuB,WAAW,IAC7C,OACA,4BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AAC3B,KAAI,CAAC,GAAG,MAAM,CACZ,QAAO,aAAa,EAAE,OAAO,oBAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGrE,MAAM,aAAa,OAAO,cAAc,GAAG;AAC3C,KAAI,CAAC,4BAA4B,WAAW,CAK1C,QAAO,aAAa,4BAA4B;CAGlD,MAAM,iBAAiB,WAAW,WAAW,QAAQ,aAAa;AAIlE,QAAO,aAAa;EAClB,GAAG,OAJe,OAAO,cACzB,oBAAoB,WAAW,WAAqB,eAAe,CACpE;EAGC;EACA,eAAe,WAAW;EAC3B,CAAC;EACF,CAAC,CACJ;AAID,MAAa,yBAAyB,WAAW,IAC/C,OACA,8BACA,OAAO,IAAI,aAAa;AAItB,QAAO,aAAa,EAAE,OAAA,OADD,eADL,OADM,WAAW,QACV,SAAS,GACW,EACd,CAAC;EAC9B,CACH;AAKD,MAAa,0BAA0B,WAAW,IAChD,OACA,+BACA,YAAY,OAAO,IAAI,aAAa;CAGlC,MAAM,YAAY,OAAO,4BADd,OADW,WAAW,QACf,SAAS,GAC4B;AACvD,QAAO,aAAa;EAClB,YAAY,UAAU;EACtB;EACD,CAAC;EACF,CAAC,CACJ;;;YClViE;aAI/B;WACmB;AAqBtD,MAAa,sBAAsB,WAAW,IAC5C,OACA,0BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,SAAS,kBAAkB,MAAM,QAAQ;CAC/C,MAAM,QAAQ,OAAO,OAAO,OAAO,GAAI,OAAO,MAAM,aAAa,IAAI,QAAQ,IAAI,QAAS;AAE1F,QAAO,OAAO,OAAO,QAAQ,YAAY;AACrC,MAAI;GACF,MAAM,gBAAgBC,OAAK,SAAS,EAAE,aAAa,UAAU,GAAG;GAChE,MAAM,kBAAkBA,OAAK,eAAe,oBAAoB;GAChE,IAAI,WAAW;GACf,IAAI,SAAS;AAEb,OAAI,WAAW,gBAAgB,CAC7B,KAAI;IACF,MAAM,QAAQ,KAAK,MAAM,MAAM,SAAS,iBAAiB,QAAQ,CAAC;AAClE,QAAI,MAAM,aAAa,YAAY,MAAM,QAAQ;AAC/C,gBAAW;AACX,cAAS,MAAM;;WAEX;GAGV,IAAI;AACJ,OAAI,YAAY,QAAQ;IACtB,MAAM,EAAE,yBAAyB,MAAM,OAAO;AAC9C,aAAS,MAAM,qBAAqB,IAAI,QAAQ,SAAS,OAAO,MAAM,EAAE,GAAG,IAAI,IAAI;SAEnF,UAAS,MAAM,OAAO,WAAW,YAAY,IAAI,SAAS,OAAO,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC;AAGvF,OAAI,CAAC,UAAU,OAAO,MAAM,KAAK,MAAM,OAAO,MAAM,KAAK,qBAAqB;IAE5E,MAAM,aAAa,MAAM,SADRA,OAAK,eAAe,aACK,EAAE,QAAQ,CAAC,YAAY,KAAK;AACtE,QAAI,YAAY;KACd,MAAM,WAAW,WAAW,MAAM,KAAK;KACvC,MAAM,WAAW,SAAS,OAAO,MAAM,EAAE,GAAG,IAAI;AAChD,cAAS,SAAS,MAAM,CAAC,SAAS,CAAC,KAAK,KAAK;;;AAIjD,OAAI,QAAQ,MAAM,KAAK,oBACrB,UAAS;AAGX,UAAO,aAAa,EAAE,QAAQ,QAAQ,CAAC;WAChC,OAAgB;AAEvB,OAAI;IAGF,MAAM,aAAa,MAAM,SADRA,OADKA,OAAK,SAAS,EAAE,aAAa,UAAU,GAC1B,EAAE,aACK,EAAE,QAAQ,CAAC,YAAY,KAAK;AACtE,QAAI,WAAY,QAAO,aAAa,EAAE,QAAQ,YAAY,CAAC;WACrD;AACR,UAAO,aAAa,EAAE,QAAQ,IAAI,CAAC;;GAErC;EACJ,CAAC,CACJ;AAID,MAAM,qBAA2C;CAAE,UAAU,EAAE;CAAE,SAAS,EAAE;CAAE,WAAW;CAAO,WAAW;CAAG,YAAY;CAAG;;;;;;;;;;AAW7H,eAAsB,0BAA0B,IAA2C;AACzF,KAAI;EACF,MAAM,UAAU,MAAM,oBAAoB,GAAG;AAE7C,MAAI,YAAY,UAAU;GACxB,MAAM,cAAc,MAAM,qBAAqB,GAAG;AAClD,OAAI,CAAC,eAAe,CAAC,WAAW,YAAY,CAAE,QAAO;AAErD,UAAO;IAAE,GAAG,MADS,gCAAgC,YAAY;IAC7C,WAAW;IAAO;;AAGxC,MAAI,YAAY,MAAM;GACpB,MAAM,cAAc,MAAM,qBAAqB,GAAG;AAClD,OAAI,CAAC,eAAe,CAAC,WAAW,YAAY,CAAE,QAAO;AAErD,UAAO;IAAE,GAAG,MADS,4BAA4B,YAAY;IACzC,WAAW;IAAO;;AAGxC,MAAI,YAAY,SAAS;GACvB,MAAM,cAAc,MAAM,wBAAwB,GAAG;AACrD,OAAI,CAAC,eAAe,CAAC,WAAW,YAAY,CAAE,QAAO;AAErD,UAAO;IAAE,GAAG,MADS,+BAA+B,YAAY;IAC5C,WAAW;IAAO;;EAKxC,IAAI,YAA2B;EAC/B,MAAM,kBAAkB,MAAM,4BAA4B,GAAG;AAC7D,MAAI,iBAAiB;GACnB,MAAM,YAAY,MAAM,OAAO,WAAW,kBAAkB,GAAG,CAAC;AAChE,OAAI,WAAW;IACb,MAAM,YAAYA,OAChB,SAAS,EAAE,WAAW,YACtB,uBAAuB,UAAU,EACjC,GAAG,gBAAgB,QACpB;AACD,QAAI,WAAW,UAAU,CAAE,aAAY;;;AAG3C,MAAI,CAAC,UACH,aAAY,MAAM,OAAO,WAAW,kBAAkB,GAAG,CAAC;AAG5D,MAAI,CAAC,aAAa,CAAC,WAAW,UAAU,CAAE,QAAO;AAMjD,SAAO;GAAE,GAAG,MAFS,wBAAwB,UAAU;GAEnC,WAAW;GAAO;UAC/B,KAAK;AACZ,UAAQ,MAAM,6BAA6B,IAAI,IAAI;AACnD,SAAO;;;AAIX,MAAa,4BAA4B,WAAW,IAClD,OACA,gCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AAC3B,QAAO,OAAO,OAAO,QAAQ,YAAY,aAAa,MAAM,0BAA0B,GAAG,CAAC,CAAC;EAC3F,CAAC,CACJ;AAID,MAAa,wBAAwB,WAAW,IAC9C,OACA,4BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,SAAS,kBAAkB,MAAM,QAAQ;CAC/C,MAAM,WAAW,OAAO,OAAO,OAAO,GAAI,OAAO,MAAM,aAAa,IAAI,QAAQ,IAAI,QAAS;AAI7F,QAAO,aAAa,EAAE,UADL,YAAY,IAFf,SAAS,SAAS,IAAI,IAGN,EAAE,CAAC;EACjC,CAAC,CACJ;AAID,MAAa,qBAAqB,WAAW,IAC3C,OACA,yBACA,YAAY,OAAO,IAAI,aAAa;CAIlC,MAAM,aAAa,OAAO,eAFf,OADW,WAAW,QACf,SAAS,GAEgB;AAC3C,KAAI,CAAC,YAAY,UACf,QAAO,aAAa,EAAE,OAAO,EAAE,EAAE,CAAC;CAEpC,MAAM,gBAAgB,WAAW;AACjC,KAAI,CAAC,WAAW,cAAc,CAC5B,QAAO,aAAa,EAAE,OAAO,EAAE,EAAE,CAAC;CAEpC,MAAM,EAAE,WAAW,OAAO,OAAO,cAAcC,YAC7C,8FACA;EAAE,KAAK;EAAe,UAAU;EAAS,CAC1C,CAAC;AAYF,QAAO,aAAa,EAAE,OAXR,OACX,MAAM,KAAK,CACX,QAAO,MAAK,EAAE,MAAM,CAAC,CACrB,KAAI,MAAK;EACR,MAAM,QAAQ,EAAE,MAAM,CAAC,MAAM,MAAM;AACnC,MAAI,MAAM,UAAU,EAClB,QAAO;GAAE,QAAQ,MAAM;GAAI,MAAM,MAAM,MAAM,SAAS;GAAI;AAE5D,SAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,MAAM;GAAE;GACtC,CACD,QAAO,MAAK,EAAE,KACU,EAAE,CAAC;EAC9B,CAAC,CACJ;AAID,MAAa,wBAAwB,WAAW,IAC9C,OACA,4BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,SAAS,kBAAkB,MAAM,QAAQ;CAC/C,MAAM,WAAW,OAAO,OAAO,OAAO,GAAI,OAAO,MAAM,aAAa,IAAI,QAAQ,IAAI,OAAQ;CAC5F,MAAM,QAAQ,SAAS,SAAS,IAAI;CAEpC,MAAM,WAAW,YAAY,IAAI,MAAM;CACvC,MAAM,aAAa,OAAO,cAAc,GAAG;CAC3C,MAAM,SAAS,SAAS,KAAK,OAAY;EACvC,WAAW,EAAE,8BAAa,IAAI,MAAM,EAAC,aAAa;EAClD,MAAM,EAAE,QAAQ;EAChB,SAAS,EAAE,WAAW,EAAE,WAAW;EACpC,EAAE;AACH,KAAI,YAAY,UACd,QAAO,QAAQ;EAAE,WAAW,WAAW;EAAW,MAAM;EAAW,SAAS;EAAiB,CAAC;AAEhG,QAAO,MAAM,GAAQ,MAAW,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC;AAClG,QAAO,aAAa,EAAE,UAAU,OAAO,MAAM,GAAG,MAAM,EAAE,CAAC;EACzD,CAAC,CACJ;;;aC3PwD;AAMzD,eAAe,iBAAiB,IAAY,SAAiB;CAE3D,MAAM,kBAAkBC,OADFA,OAAK,SAAS,EAAE,aAAa,UAAU,GACnB,EAAE,oBAAoB;CAChE,IAAI,WAAW;AAEf,KAAI,WAAW,gBAAgB,CAC7B,KAAI;EACF,MAAM,QAAQ,KAAK,MAAM,MAAM,SAAS,iBAAiB,QAAQ,CAAC;AAClE,aAAW,MAAM,aAAa,YAAY,QAAQ,MAAM,OAAO;SACzD;AAGV,OAAM,aAAa,IAAI,SAAS,yBAAyB;AACzD,QAAO,WAAW;EAAE,SAAS;EAAM,QAAQ;EAAM,GAAG,EAAE,SAAS,MAAM;;AAGvE,SAAgB,2BAA2B,SAA8C;CACvF,MAAM,cAAc,eAAe,QAAQ;AAC3C,KAAI,CAAC,YAAY,GACf,QAAO;EACL,IAAI;EACJ,UAAU,aAAa;GAAE,IAAI;GAAO,OAAO,YAAY;GAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;EACjF;AAEH,QAAO,EAAE,IAAI,MAAe;;AAG9B,SAAS,0BAA0B,MAAoB;AACrD,QAAO,WAAW,IAChB,QACA,MACA,YAAY,OAAO,IAAI,aAAa;EAElC,MAAM,cAAc,2BAA2B,OADxB,kBAAkB,kBACc;AACvD,MAAI,CAAC,YAAY,GAAI,QAAO,YAAY;EAGxC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;EAG3B,MAAM,EAAE,YAAY,OAFAC;AAGpB,MAAI,CAAC,QACH,QAAO,aAAa,EAAE,OAAO,oBAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGrE,SAAO,OAAO,OAAO,cAAc,iBAAiB,IAAI,QAAQ,CAAC,CAAC,KAChE,OAAO,KAAK,WAAW,aAAa,OAAO,CAAC,CAC7C;GACD,CAAC,CACJ;;AAKH,MAAa,wBAAwB,0BAA0B,0BAA0B;AAIzF,MAAa,qBAAqB,0BAA0B,uBAAuB;AAInF,MAAa,qBAAqB,WAAW,IAC3C,QACA,wBACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAG3B,MAAM,EAAE,YAAY,OAFAA;CASpB,MAAM,UAAU,WAAW;AAC3B,QAAO,OAAO,cAAc,aAAa,IAAI,QAAQ,CAAC;AACtD,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS;EAA4B,CAAC;EAC3E,CAAC,CACJ;;;AC7FD,MAAa,4BAA4B;CACvC,gBAAgB;CAChB,eAAe;CACf,kBAAkB,IAAI;CACtB,mBAAmB,KAAK;CACzB;;AAUD,IAAa,mCAAb,cAAsD,KAAK,YACzD,mCACD,CAGE;AAEH,SAAgB,eAAe,OAAuB;AACpD,QAAO,OAAO,WAAW,OAAO,OAAO;;AAGzC,SAAgB,gCAAgC,OAAwB;AACtE,QAAO,OAAO,UAAU,WAAW,QAAQ;;AAG7C,SAAgB,wCAAwC,OAKsC;AAC5F,QAAO,OAAO,IAAI,aAAa;EAC7B,MAAM,YAAY,OAAO,MAAM,cAAc,WAAW,MAAM,UAAU,MAAM,GAAG;AACjF,MAAI,CAAC,UACH,QAAO,OAAO,OAAO,KACnB,IAAI,iCAAiC;GAAE,OAAO;GAAa,SAAS;GAAyB,CAAC,CAC/F;AAEH,MAAI,eAAe,UAAU,GAAG,0BAA0B,eACxD,QAAO,OAAO,OAAO,KACnB,IAAI,iCAAiC;GACnC,OAAO;GACP,SAAS,qBAAqB,0BAA0B,eAAe;GACxE,CAAC,CACH;EAGH,MAAM,WAAW,OAAO,MAAM,aAAa,WAAW,MAAM,SAAS,MAAM,GAAG;AAC9E,MAAI,CAAC,SACH,QAAO,OAAO,OAAO,KACnB,IAAI,iCAAiC;GAAE,OAAO;GAAY,SAAS;GAAwB,CAAC,CAC7F;AAEH,MAAI,eAAe,SAAS,GAAG,0BAA0B,cACvD,QAAO,OAAO,OAAO,KACnB,IAAI,iCAAiC;GACnC,OAAO;GACP,SAAS,oBAAoB,0BAA0B,cAAc;GACtE,CAAC,CACH;EAGH,MAAM,cAAc,OAAO,MAAM,gBAAgB,WAAW,MAAM,YAAY,MAAM,GAAG;AACvF,MAAI,CAAC,YACH,QAAO,OAAO,OAAO,KACnB,IAAI,iCAAiC;GACnC,OAAO;GACP,SAAS;GACV,CAAC,CACH;AAEH,MAAI,eAAe,YAAY,GAAG,0BAA0B,iBAC1D,QAAO,OAAO,OAAO,KACnB,IAAI,iCAAiC;GACnC,OAAO;GACP,SAAS,uBAAuB,0BAA0B,iBAAiB;GAC5E,CAAC,CACH;EAGH,MAAM,eAAe,gCAAgC,MAAM,aAAa;AACxE,MAAI,eAAe,aAAa,GAAG,0BAA0B,kBAC3D,QAAO,OAAO,OAAO,KACnB,IAAI,iCAAiC;GACnC,OAAO;GACP,SAAS,wBAAwB,0BAA0B,kBAAkB;GAC9E,CAAC,CACH;AAGH,SAAO;GAAE;GAAW;GAAU;GAAa;GAAc;GACzD;;;;ACzFJ,MAAM,6BAA6B;AAInC,SAAgB,8BAA8B,UAAkB,aAA6B;AAC3F,QAAO,2BAA2B,SAAS,KAAK;;AAGlD,SAAgB,yBAAyB,UAAkD;AACzF,QAAO,aAAa,UAAU,YAAY;;AAG5C,SAAgB,+BAA+B,MAEd;CAC/B,MAAM,SAAS,OAAO,QACpB,OAAO,OACL,wCAAwC;EACtC,WAAW,KAAK;EAChB,UAAU,KAAK;EACf,aAAa,KAAK;EAClB,cAAc,KAAK;EACpB,CAAC,CACH,CACF;AACD,KAAIC,SAAO,UAAU,OAAO,CAC1B,QAAO;EAAE,IAAI;EAAO,OAAO,OAAO,QAAQ;EAAS;AAErD,QAAO;EAAE,IAAI;EAAM,OAAO,OAAO;EAAS;;AAG5C,SAAgB,gCAAgC,MAEf;CAC/B,MAAM,WAAW,KAAK;AACtB,KAAI,aAAa,WAAW,aAAa,OACvC,QAAO;EAAE,IAAI;EAAO,OAAO;EAAkC;AAE/D,QAAO;EAAE,IAAI;EAAM,OAAO;EAAU;;AAGtC,SAAgB,6BAA6B,cAA8B;CACzE,IAAI,WAAW;AACf,YAAW,SAAS,QAClB,uGACC,QAAQ,QAAgB,eAAuB,QAAgB,WAAmB,GAAG,OAAO,YAAY,SAC1G;AACD,YAAW,SAAS,QAClB,sCACC,QAAQ,WAAmB,GAAG,OAAO,YACvC;AACD,YAAW,SAAS,QAClB,iDACA,aACD;AACD,YAAW,SAAS,QAClB,2BACA,aACD;AACD,KAAI,SAAS,UAAU,2BACrB,QAAO;AAET,QAAO,GAAG,SAAS,MAAM,GAAG,2BAA2B,CAAC;;AAG1D,SAAgB,+BAA+B,aAAqB,cAA8B;CAChG,MAAM,cAAc,6BAA6B,aAAa;AAC9D,QAAO,YAAY,MAAM,CAAC,SAAS,IAC/B,GAAG,YAAY,sBAAsB,gBACrC;;AAuBN,eAAsB,0BACpB,MACA,MAK0C;CAC1C,MAAM,iBAAiB,MAAM,KAAK,kBAAkB,KAAK,UAAU;AACnE,KAAI,gBAAgB;AAClB,MAAI,eAAe,YAAY,KAAK,QAClC,QAAO;GACL,QAAQ;GACR,MAAM;IACJ,IAAI;IACJ,OAAO,sBAAsB,KAAK,UAAU,cAAc,eAAe;IAC1E;GACF;AAGH,MAAI;AACF,SAAM,KAAK,uBAAuB,gBAAgB,KAAK,SAAS;WACzD,OAAgB;AAEvB,UAAO;IACL,QAAQ;IACR,MAAM;KAAE,IAAI;KAAO,OAAO,0CAHhB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;KAGW;IAC5E;;AAGH,MAAI;AACF,SAAM,KAAK,gBAAgB,KAAK,SAAS,KAAK,WAAW,KAAK,SAAS;WAChE,OAAgB;AAEvB,UAAO;IACL,QAAQ;IACR,MAAM;KAAE,IAAI;KAAO,OAAO,0CAHhB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;KAGW;IAC5E;;AAGH,OAAK,qBAAqB,gBAAgB,KAAK,SAAS;AACxD,SAAO;GAAE,QAAQ;GAAK,MAAM,EAAE,IAAI,MAAM;GAAE;;CAG5C,MAAM,mBAAmB,MAAM,KAAK,oBAAoB,KAAK,UAAU;AACvE,KAAI,CAAC,iBACH,QAAO;EACL,QAAQ;EACR,MAAM;GAAE,IAAI;GAAO,OAAO,sBAAsB,KAAK,UAAU;GAAa;EAC7E;AAEH,KAAI,iBAAiB,YAAY,KAAK,QACpC,QAAO;EACL,QAAQ;EACR,MAAM;GACJ,IAAI;GACJ,OAAO,sBAAsB,KAAK,UAAU,cAAc,iBAAiB;GAC5E;EACF;AAEH,KAAI,iBAAiB,aAAa,KAAK,SACrC,QAAO;EACL,QAAQ;EACR,MAAM;GACJ,IAAI;GACJ,OAAO,sBAAsB,KAAK,UAAU,eAAe,iBAAiB;GAC7E;EACF;AAGH,KAAI;AACF,QAAM,KAAK,gBAAgB,KAAK,SAAS,KAAK,WAAW,KAAK,SAAS;UAChE,OAAgB;AAEvB,SAAO;GACL,QAAQ;GACR,MAAM;IAAE,IAAI;IAAO,OAAO,0CAHhB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;IAGW;GAC5E;;AAGH,QAAO;EAAE,QAAQ;EAAK,MAAM;GAAE,IAAI;GAAM,WAAW;GAAM;EAAE;;;;aC7K1B;sBACwC;iBACpB;AAqBvD,MAAa,gCAAgC,WAAW,IACtD,OACA,qCACA,YAAY,OAAO,IAAI,aAAa;CAIlC,MAAM,YAAY,OAAO,0BAFd,OADW,WAAW,QACf,SAAS,GAE0B;AACrD,QAAO,aAAa;EAAE,SAAS,UAAU,SAAS;EAAG;EAAW,CAAC;EACjE,CAAC,CACJ;AAcD,MAAa,+BAA+B,WAAW,IACrD,QACA,mCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AAC3B,KAAI,CAAC,GAAG,MAAM,CACZ,QAAO,aAAa,EAAE,OAAO,oBAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAIrE,MAAM,WAAU,OAFKC,gBAEA;AACrB,KAAI,CAAC,MAAM,QAAQ,QAAQ,IAAI,QAAQ,WAAW,EAChD,QAAO,aAAa,EAAE,OAAO,0BAA0B,EAAE,EAAE,QAAQ,KAAK,CAAC;AAE3E,KAAI,CAAC,QAAQ,OAAO,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS,EAAE,CAC3E,QAAO,aAAa,EAAE,OAAO,2CAA2C,EAAE,EAAE,QAAQ,KAAK,CAAC;CAG5F,MAAM,mBAAmB,OAAO,yBAAyB,GAAG;AAC5D,KAAI,iBAAiB,WAAW,EAC9B,QAAO,aAAa,EAAE,OAAO,6CAA6C,EAAE,EAAE,QAAQ,KAAK,CAAC;CAI9F,MAAM,YADc,iBAAiB,GACP;CAC9B,MAAM,QAAkB,EAAE;AAC1B,MAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,UAAU,IAAI,UAAU,QAAQ,KAAK;EAC/D,MAAM,IAAI,UAAU,GAAG,YAAY,YAAY,IAAI;AACnD,QAAM,KAAK,MAAM,EAAE,OAAO,QAAQ,KAAK;;CAEzC,MAAM,UAAU,yCAAyC,QAAQ,SAAS,IAAI,MAAM,GAAG,OAAO,MAAM,KAAK,OAAO;AAEhH,QAAO,OAAO,cAAc,oBAAoB,IAAI,SAAS,2BAA2B,CAAC;AACzF,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS;EAAI,WAAW,QAAQ;EAAQ,CAAC;EAC9E,CAAC,CACJ;AAED,MAAa,0CAA0C,WAAW,IAChE,QACA,gDACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AAC3B,KAAI,CAAC,GAAG,MAAM,CACZ,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO;EAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGhF,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,EAAE,uBAAuB,yBAAyB,OAAO,OAAO,cACpE,OAAO,gCACR;CACD,MAAM,WAAW,sBAAsB;AACvC,KAAI,CAAC,SACH,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO;EAAiC,EAAE,EAAE,QAAQ,KAAK,CAAC;CAG7F,MAAM,YADU,QAAQ,QACE;AAE1B,KAAI,CAAC,uBADY,MAAM,QAAQ,UAAU,GAAG,UAAU,KAAK,WACrB,SAAS,CAC7C,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO;EAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;CAIzE,MAAM,aAAa,+BAA+B,OAD7BA,eACkC;AACvD,KAAI,CAAC,WAAW,GACd,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO,WAAW;EAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;CAE9E,MAAM,EAAE,WAAW,UAAU,aAAa,iBAAiB,WAAW;CAGtE,MAAM,WAAW,QAAO,OADC,kBACS,4BAA4B,UAAU;AACxE,KAAI,UAAU;AACZ,MAAI,SAAS,YAAY,GACvB,QAAO,aAAa;GAAE,IAAI;GAAO,OAAO,WAAW,UAAU,sBAAsB,SAAS;GAAW,EAAE,EAAE,QAAQ,KAAK,CAAC;AAE3H,SAAO,aAAa;GAAE,IAAI;GAAM,WAAW;GAAM,CAAC;;CAGpD,MAAM,aAAa,OAAO,cAAc,GAAG;AAC3C,KAAI,CAAC,WACH,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO,SAAS,GAAG;EAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGrF,MAAM,WAAU,OADY,qBAAqB,GAAG,GACtB,gBAAgB,WAAW;CAEzD,MAAM,aAAa,OAAO;CAC1B,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,QAAO,WAAW,OAAO;EACvB,MAAM;EACN;EACA,SAAS;GACP;GACA,SAAS;GACT;GACA;GACA;GACA;GACA,WAAW;GACZ;EACF,CAAU;AACX,QAAO,WAAW,OAAO;EACvB,MAAM;EACN;EACA,SAAS;GACP,SAAS;GACT,QAAQ;GACR,SAAS,8BAA8B,UAAU,YAAY;GAC9D;EACF,CAAU;AAEX,uBAAsB;EACpB,QAAQ;EACR,OAAO;EACP,SAAS,4BAA4B;EACrC,SAAS,+BAA+B,aAAa,aAAa;EAClE;EACD,CAAC;AAEF,QAAO,aAAa,EAAE,IAAI,MAAM,CAAC;EACjC,CAAC,CACJ;AAED,MAAa,mCAAmC,WAAW,IACzD,QACA,kDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,KAAK,OAAO,SAAS;CAC3B,MAAM,YAAY,OAAO,gBAAgB;AACzC,KAAI,CAAC,GAAG,MAAM,IAAI,CAAC,UAAU,MAAM,CACjC,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO;EAAkC,EAAE,EAAE,QAAQ,KAAK,CAAC;CAI9F,MAAM,cAAc,eAAe,OADZ,kBAAkB,kBACE;AAC3C,KAAI,CAAC,YAAY,GACf,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO,YAAY;EAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;CAI/E,MAAM,iBAAiB,gCAAgC,OADlCA,eACuC;AAC5D,KAAI,CAAC,eAAe,GAClB,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO,eAAe;EAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;CAElF,MAAM,WAAW,eAAe;CAEhC,MAAM,YAAY,OAAO;CACzB,MAAM,aAAa,OAAO;CAC1B,MAAM,SAAS,OAAO,OAAO,cAAc,0BACzC;EACE,oBAAoB,wBAClB,OAAO,WAAW,UAAU,4BAA4B,oBAAoB,CAAC;EAC/E,sBAAsB,wBACpB,OAAO,WAAW,UAAU,qCAAqC,oBAAoB,CAAC;EACxF,wBAAwB,OAAO,gBAAgB,qBAAqB;GAClE,MAAM,6BAAY,IAAI,MAAM,EAAC,aAAa;AAC1C,SAAM,OAAO,WAAW,WAAW,OAAO;IACxC,MAAM;IACN;IACA,SAAS;KACP,WAAW,eAAe;KAC1B,SAAS,eAAe;KACxB,SAAS,eAAe;KACxB,UAAU;KACX;IACF,CAAU,CAAC;AACZ,SAAM,OAAO,WAAW,WAAW,OAAO;IACxC,MAAM;IACN;IACA,SAAS;KACP,SAAS,eAAe;KACxB,WAAW;KACZ;IACF,CAAU,CAAC;;EAEd,kBAAkB,SAAS,qBAAqB,qBAC9C,+BAA+B,SAAS,qBAAqB,iBAAiB;EAChF,uBAAuB,gBAAgB,qBAAqB;AAC1D,yBAAsB;IACpB,QAAQ;IACR,OAAO,qBAAqB,UAAU,YAAY;IAClD,SAAS,cAAc,yBAAyB,iBAAiB,CAAC,OAAO,eAAe;IACxF,SAAS,+BACP,eAAe,aACf,eAAe,aAChB;IACD,SAAS,eAAe;IACzB,CAAC;;EAEL,EACD;EACE,SAAS;EACT;EACA;EACD,CACF,CAAC;AAEF,QAAO,aAAa,OAAO,MAAM,EAAE,QAAQ,OAAO,QAAQ,CAAC;EAC3D,CAAC,CACJ;;;;;;;;;;;;AC/OD,MAAa,eACX,SACA,MACA,cACmC;CACnC,MAAM,SAAS;AACf,KAAI,OAAO,OAAO,YAAY,SAAU,QAAO;AAE/C,SADa,OAAO,SACpB;EACE,KAAK,WACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,UAAU,OAAO;IACjB,aAAa,OAAO;IACrB;GACF;EACH,KAAK,iBACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,YAAa,OAAO,iBAA4B;IACjD;GACF;EACH,KAAK,gBACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,YAAY,OAAO,iBAAiB;IACrC;GACF;EACH,KAAK,gBACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,QAAQ,OAAO,aAAa;IAC5B,SAAS,OAAO;IACjB;GACF;EACH,KAAK,gBACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,WAAW,OAAO,gBAAgB;IACnC;GACF;EACH,KAAK,mBACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,WAAW,OAAO,gBAAgB;IAClC,QAAQ,OAAO,aAAa;IAC7B;GACF;EACH,KAAK,gBAEH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,OAAO;KACL,GAPQ,6BAA6B,OAAO,UAAU,QAO9C;KACR,YAAY;KACb;IACF;GACF;EAEH,KAAK,YACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,OAAO,OAAO;IACd,iBAAiB,OAAO;IACxB,cAAc,OAAO;IACrB,gBAAgB,OAAO;IACxB;GACF;EACH,KAAK,cAAc;GACjB,MAAM,QAAQ,OAAO,YAAY,OAAO,OAAO,aAAa,WACxD,OAAO,WACP,EAAE;AACN,UAAO;IACL,MAAM;IACN;IACA,SAAS;KACP;KACA,SAAS,OAAO,OAAO,eAAe,YAAY,OAAO,aAAa,OAAO,aAAa;KAC1F,MAAM,OAAO,OAAO,eAAe,WAAW,OAAO,aAAa;KAClE,aAAa,OAAO,MAAM,mBAAmB,WAAW,MAAM,iBAAiB;KAC/E,cAAc,OAAO,MAAM,oBAAoB,WAAW,MAAM,kBAAkB;KACnF;IACF;;EAEH,KAAK,iBACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,YAAY,OAAO;IACnB,iBAAiB,OAAO,OAAO,sBAAsB,EAAE;IACxD;GACF;EACH,KAAK,oBACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,cAAc,OAAO;IACtB;GACF;EACH,KAAK,6BACH,QAAO;GACL,MAAM;GACN;GACA,SAAS;IACP;IACA,oBAAoB,OAAO;IAC5B;GACF;EACH,QACE,QAAO;;;AAOb,MAAa,oBAAoB,OAAO,oBAAoB,YAAY;;;ACnJxE,MAAa,6BAA6B,WAAW,IACnD,OACA,kCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,SAAS,kBAAkB,MAAM,QAAQ;CAC/C,MAAM,QAAQ,OAAO,OAAO,OAAO,GAAI,OAAO,MAAM,aAAa,IAAI,QAAQ,IAAI,OAAQ;CAEzF,MAAM,EAAE,qBAAqB,OAAO,OAAO,cAAc,OAAO,+BAA6C;CAC7G,MAAM,0BAAU,IAAI,MAAM;CAC1B,MAAM,4BAAY,IAAI,KAAK,QAAQ,SAAS,GAAG,SAAS,MAAM,GAAG,KAAK,KAAK,IAAK;CAChF,MAAM,SAAS,iBAAiB,IAAI,UAAU,aAAa,EAAE,QAAQ,aAAa,CAAC;AACnF,QAAO,aAAa;EAClB,SAAS;EACT,WAAW,UAAU,aAAa;EAClC,SAAS,QAAQ,aAAa;EAC9B;EACD,CAAC;EACF,CAAC,CACJ;AAqBD,SAAS,sBAAsB,IAAY,MAA+B,WAAmB;AAC3F,QAAO,OAAO,IAAI,aAAa;EAC7B,IAAI;AACJ,MAAI;AACF,SAAM,YAAY,IAAI,MAAM,UAAU;WAC/B,OAAO;AAEd,UAAO;IAAE,IAAI;IAAgB,UAAU,aAAa;KAAE,SAAS;KAAO,OADtD,iBAAiB,QAAQ,MAAM,UAAU;KAC6B,EAAE,EAAE,QAAQ,KAAK,CAAC;IAAE;;AAE5G,MAAI,CAAC,IACH,QAAO;GAAE,IAAI;GAAe,SAAS;GAAO;EAI9C,MAAM,UAAU,kBAAkB;GADd,GAAG;GAAK,UAAU;GACK,CAAC;AAC5C,MAAI,QAAQ,SAAS,UACnB,QAAO;GACL,IAAI;GACJ,UAAU,aACR;IAAE,SAAS;IAAO,OAAO;IAAiB,QAAQ,OAAO,QAAQ,QAAQ;IAAE,EAC3E,EAAE,QAAQ,KAAK,CAChB;GACF;EAGH,MAAM,EAAE,sBAAsB,OAAO,OAAO,cACpC,OAAO,qCACd;AAED,UAAO,OADmB,mBACR,KAAK,QAAQ,QAAiB;AAChD,SAAO;GAAE,IAAI;GAAe,SAAS;GAAM;GAC3C;;AAGJ,MAAa,0BAA0B,WAAW,IAChD,QACA,6BACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,OAAO,OAAO,OAAO,cAAc,8BAA8B,QAAQ,CAAC;AAChF,KAAI,CAAC,KAAK,GAAI,QAAO,KAAK;CAG1B,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AAC3B,KAAI,CAAC,GAAG,MAAM,CACZ,QAAO,aAAa;EAAE,SAAS;EAAO,OAAO;EAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAErF,MAAM,OAAQ,OAAOC;CAErB,MAAM,SAAS,OAAO,sBAAsB,IAAI,MAD7B,KAAK,iCAA2B,IAAI,MAAM,EAAC,aAAa,CACX;AAChE,KAAI,CAAC,OAAO,GAAI,QAAO,OAAO;AAC9B,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS,OAAO;EAAS,CAAC;EAC/D,CAAC,CACJ;AAED,MAAa,6BAA6B,WAAW,IACnD,QACA,iCACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,OAAO,OAAO,OAAO,cAAc,8BAA8B,QAAQ,CAAC;AAChF,KAAI,CAAC,KAAK,GAAI,QAAO,KAAK;CAG1B,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AAC3B,KAAI,CAAC,GAAG,MAAM,CAAE,QAAO,aAAa;EAAE,SAAS;EAAO,OAAO;EAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;CACnG,MAAM,SAAS,OAAO,sBAAsB,IAAI;EAAE,MAAM;EAAkB,YAAY;EAAQ,iBAAiB;EAAG,mBAAE,IAAI,MAAM,EAAC,aAAa,CAAC;AAC7I,KAAI,CAAC,OAAO,GAAI,QAAO,OAAO;AAC9B,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS,OAAO;EAAS,CAAC;EAC/D,CAAC,CACJ;AAED,MAAa,sBAAsB,WAAW,IAC5C,QACA,yBACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,OAAO,OAAO,OAAO,cAAc,8BAA8B,QAAQ,CAAC;AAChF,KAAI,CAAC,KAAK,GAAI,QAAO,KAAK;CAG1B,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AAC3B,KAAI,CAAC,GAAG,MAAM,CAAE,QAAO,aAAa;EAAE,SAAS;EAAO,OAAO;EAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;CACnG,MAAM,SAAS,OAAO,sBAAsB,IAAI;EAAE,MAAM;EAAkB,YAAY;EAAS,iBAAiB;EAAG,mBAAE,IAAI,MAAM,EAAC,aAAa,CAAC;AAC9I,KAAI,CAAC,OAAO,GAAI,QAAO,OAAO;AAC9B,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS,OAAO;EAAS,CAAC;EAC/D,CAAC,CACJ;AAED,SAAS,2BAA2B,QAAyB;AAC3D,QAAO,wKAAwK,KAAK,OAAO;;AAG7L,MAAa,mCAAmC,WAAW,IACzD,QACA,uCACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,OAAQ,OAAOA;CACrB,MAAM,SAAS,OAAO,KAAK,cAAc,WAAW,KAAK,YAAY;AAMrE,QAAO,aAAa;EAAE,SAAS;EAAM,SALrB,wDAAwD,KAAK,OAAO,IAAI,2BAA2B,OAAO,GACtH,sBACA,2EAA2E,KAAK,OAAO,GACrF,sBACA;EACwC,CAAC;EAC/C,CAAC,CACJ;AAKD,MAAa,uBAAuB,WAAW,IAC7C,OACA,2BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AAC3B,KAAI,CAAC,GAAG,MAAM,CACZ,QAAO,aAAa;EAAE,SAAS;EAAO,OAAO;EAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAErF,MAAM,EAAE,sBAAsB,OAAO,OAAO,cACpC,OAAO,qCACd;CAED,MAAM,WAAW,QAAO,OADE,mBACS,IAAI,GAAG;AAC1C,KAAI,CAAC,SACH,QAAO,aAAa;EAAE,SAAS;EAAO,OAAO;EAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;AAE9E,QAAO,aAAa;EAAE,SAAS;EAAM;EAAU,CAAC;EAChD,CAAC,CACJ;;;;;;;;;;;;;;;;;AC9JD,SAAA,yBAAA,OAAA;AACE,KAAA,MAAA,WAAA,aAAA,MAAA,WAAA,WAAA,QAAA,MAAA;;AAKA,QAAA;;AAGF,SAAA,iBAAA,OAAA,UAAA;;AAKE,QAAA;;;;;;;;;;;;;;;;;;AAmBF,SAAA,2BAAA,OAAA,OAAA;AAME,QAAA,mCAAA,yBAAA,EAAA,eAAA,EAAA,OAAA,MAAA;;;;;;AAOF,SAAA,kCAAA,OAAA,OAAA;AAIE,QAAA,OAAA,WAAA,2BAAA,OAAA,MAAA,CAAA;;;;;AAMF,SAAA,mCAAA,IAAA,YAAA,OAAA,OAAA;AAME,0BAAA,MAAA;AAIA,yBAAA,MAAA;;;;;;AAQA,IAAA,KAAA,kBAAA;AACA,KAAA;AAEE,KAAA,QAAA,gFAAA,CAAA,IAAA,MAAA,SAAA,UAAA;AAKA,KAAA,QAAA,kCAAA,qBAAA,KAAA,KAAA,CAAA,YAAA,qBAAA,UAAA,IAAA,CAAA,KAAA,KAAA,CAAA,GAAA,CAAA,IAAA,GAAA,2BAAA,OAAA,UAAA,CAAA;AAMA,KAAA,QAAA,iEAAA,CAAA,IAAA,MAAA,MAAA,aAAA,QAAA;;AAQA,KAAA,KAAA,SAAA;;AAGA,aAAA,WAAA,OAAA;AAEA,wBAAA,MAAA,IAAA,aAAA,MAAA,KAAA,QAAA,SAAA,QAAA,MAAA,KAAA;AAKA,SAAA,EAAA,UAAA;;AAEA,MAAA;AACE,MAAA,KAAA,WAAA;;AAIF,QAAA;;;;;aC/H+B;sBACwC;WAGN;sBAEC;AAatE,SAAgB,uBACd,gBACA;AACA,QAAO,YAAY,OAAO,IAAI,aAAa;EAEzC,MAAM,cAAc,eAAe,OADZ,kBAAkB,kBACE;AAC3C,MAAI,CAAC,YAAY,GACf,QAAO,aAAa;GAAE,IAAI;GAAO,OAAO,YAAY;GAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;EAI/E,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AACR,SAAO;EAE1B,MAAM,kBAAkB,OAAO,cAAc,GAAG;AAChD,SAAO,OAAO,cAAc,wBAAwB,IAAI,eAAe,CAAC;AACxE,SAAO,UAAU,GAAG;AAWpB,MAAI,iBAAiB,QACnB,QAAO,OAAO,QAAQ,YAAY;AAChC,OAAI;IACF,MAAM,EAAE,gCAAgC,MAAM,OAAO;IACrD,MAAM,EAAE,sBAAsB,MAAM,OAAO;IAC3C,MAAM,aAAa,gBAAgB,QAAS,aAAa;IAGzD,MAAM,gBAAgB,kBAFN,4BAA4B,gBAAgB,QACjC,EAAE,eAAe,QAAQ,KAAK,EACJ,WAAW;AAChE,QAAI,eAAe;KACjB,MAAM,eAAe,MAAM,OAAO,WAAW,oBAAoB,eAAe,WAAW,CAAC;AAC5F,SAAI,aAAa,gBACf,SAAQ,IAAI,uCAAuC,GAAG,IAAI,aAAa,MAAM,KAAK,KAAK,GAAG;;YAGvF,KAAK;AACZ,YAAQ,KAAK,uCAAuC,GAAG,gBAAgB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;IAE5H;EAWJ,MAAM,iBAAiB,OAAO,cAAc,GAAG;AAC/C,MAAI,eACF,QAAO,kCAAkC,gBAAgB;GACvD,MAAM;GACN,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,SAAS;IAAE,SAAS;IAAI,SAAS,eAAe,WAAW,iBAAiB,WAAW;IAAI;GAC5F,CAAC;EAEJ,MAAM,UAAU,iBAAiB;EAEjC,MAAM,aAAa,iBAAiB,SAAS,SAAS,aAAa;AACnE,wBAAsB;GACpB,QAAQ;GACR,OAAO;GACP,SAAS,UACL,gBAAgB,QAAQ,GAAG,WAAW,UACtC,sBAAsB;GAC1B;GACD,CAAC;AACF,yBAAuB;AACvB,SAAO,aAAa,EAAE,SAAS,MAAM,CAAC;GACtC,CAAC;;AAGL,SAAS,eACP,QACA,MACA,gBACA;AACA,QAAO,WAAW,IAAI,QAAQ,MAAM,uBAAuB,eAAe,CAAC;;AAK7E,MAAa,mBAAmB,eAAe,UAAU,mBAAmB,yBAAyB;AAIrG,MAAa,qBAAqB,eAAe,QAAQ,wBAAwB,uBAAuB;AAIxG,MAAa,wBAAwB,WAAW,IAC9C,QACA,2BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,OAAO,OAAOC;AACD,QAAO;CAE1B,MAAM,EAAE,cAAc;CACtB,MAAM,qBAAqB,aAAa,aAAa,GAAG;AAExD,KAAI,CAAC,mBACH,QAAO,aAAa,EAAE,OAAO,mCAAmC,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGpF,QAAO,OAAO,cAAc,wBAAwB,IAAI,2BAA2B,EAAE,WAAW,oBAAoB,CAAC,CAAC;AACtH,eAAc,IAAI,mBAAmB;CAGrC,MAAM,kBAAkB,OAAO,cAAc,GAAG,GAAG,WAAW;AAC9D,QAAO,YAAY,GAAG,CAAC,KAAK,OAAO,YAAY,OAAO,KAAK,CAAC;AAC5D,uBAAsB,IAAI;EACxB,OAAO;EACP,+BAAc,IAAI,MAAM,EAAC,aAAa;EACtC,iBAAiB;EAClB,CAAC;CAIF,MAAM,oBAAoB,OAAO,cAAc,GAAG;AAClD,KAAI,kBACF,QAAO,kCAAkC,mBAAmB;EAC1D,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GAAE,SAAS;GAAI,SAAS,kBAAkB,WAAW;GAAgB;EAC/E,CAAC;AAGJ,wBAAuB;AACvB,QAAO,aAAa,EAAE,SAAS,MAAM,CAAC;EACtC,CAAC,CACJ;AAID,MAAa,sBAAsB,WAAW,IAC5C,QACA,yBACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,cAAc,eAAe,OADZ,kBAAkB,kBACE;AAC3C,KAAI,CAAC,YAAY,GACf,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO,YAAY;EAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;CAI/E,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,OAAO,OAAOA;CACpB,MAAM,aAAa,OAAO;CAC1B,MAAM,SAAU,KAA8B;AAE9C,KAAI,WAAW,KAAA,KAAa,OAAO,WAAW,SAC5C,QAAO,aAAa,EAAE,OAAO,2BAA2B,EAAE,EAAE,QAAQ,KAAK,CAAC;CAG5E,MAAM,mBAAmB,OAAO,cAAc,GAAG;AACjD,KAAI,CAAC,iBACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;CAG1E,MAAM,iBAAiB,qBAAqB,iBAAiB,OAAO;CACpE,MAAM,iBAAiB,OAAO,cAAc,GAAG;CAE/C,IAAI,eAAe,OAAO,eAAe,IAAI,QADtB,kBAAkB,iBAAiB,WAAW,aAAa,iBAAiB,WAAW,WAC1C;AACpE,KAAI,CAAC,aACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;AAG1E,KAAI,gBAAgB;AAClB,SAAO,OAAO,cAAc,6BAA6B,GAAG,CAAC;AAC7D,SAAO,YAAY,GAAG;;AAGxB,KAAI,kBAAkB,aAAa,WAAW,aAAa,aAAa,WAAW,YAAY;AAE7F,iBADqB,sBAAsB,aAChB;AAC3B,SAAO,OAAO,cAAc,sBAAsB,IAAI;GACpD,OAAO;GACP,+BAAc,IAAI,MAAM,EAAC,aAAa;GACvC,CAAC,CAAC;;AAGL,QAAO,OAAO,cAAc,wBAAwB,IAAI,yBAAyB,EAAE,QAAQ,CAAC,CAAC;AAC7F,QAAO,WAAW,YAAY,0BAA0B;EACtD,SAAS,aAAa,WAAW,iBAAiB,WAAW,GAAG,QAAQ,WAAW,GAAG,CAAC,aAAa;EACpG,MAAM;EACN,QAAQ;EACT,CAAC,CAAC;AAGH,QAAO,kCAAkC,cAAc;EACrD,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS,8BAA8B,cAAc,eAAe;EACrE,CAAC;AAEF,wBAAuB;AACvB,QAAO,aAAa;EAAE,SAAS;EAAM,OAAO;EAAc,CAAC;EAC3D,CAAC,CACJ;AAID,MAAa,wBAAwB,WAAW,IAC9C,QACA,2BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,cAAc,eAAe,OADZ,kBAAkB,kBACE;AAC3C,KAAI,CAAC,YAAY,GACf,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO,YAAY;EAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;CAI/E,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,aAAa,OAAO;CAE1B,MAAM,qBAAqB,OAAO,cAAc,GAAG;AACnD,KAAI,CAAC,mBACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;CAG1E,MAAM,eAAe,OAAO,iBAAiB,GAAG;AAChD,KAAI,CAAC,aACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;AAG1E,QAAO,OAAO,cAAc,wBAAwB,IAAI,0BAA0B,CAAC;AACnF,KAAI,mBAAmB,WAAW,KAChC,QAAO,WAAW,YAAY,0BAA0B;EACtD,SAAS,aAAa,WAAW,mBAAmB,WAAW,GAAG,QAAQ,WAAW,GAAG,CAAC,aAAa;EACtG,MAAM;EACN,QAAQ;EACT,CAAC,CAAC;AAIL,QAAO,kCAAkC,cAAc;EACrD,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS,8BAA8B,cAAc,qBAAqB,mBAAmB,OAAO,CAAC;EACtG,CAAC;AAEF,wBAAuB;AACvB,QAAO,aAAa;EAAE,SAAS;EAAM,OAAO;EAAc,CAAC;EAC3D,CAAC,CACJ;AAID,MAAa,2BAA2B,WAAW,IACjD,QACA,8BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,cAAc,eAAe,OADZ,kBAAkB,kBACE;AAC3C,KAAI,CAAC,YAAY,GACf,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO,YAAY;EAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;CAI/E,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,aAAa,OAAO;CAE1B,MAAM,mBAAmB,OAAO,cAAc,GAAG;AACjD,KAAI,CAAC,iBACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;CAG1E,MAAM,eAAe,OAAO,mBAAmB,GAAG;AAClD,KAAI,CAAC,aACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;AAG1E,QAAO,OAAO,cAAc,wBAAwB,IAAI,6BAA6B,CAAC;AACtF,KAAI,iBAAiB,aAAa,SAAS,iBAAiB,uBAAuB,KAAK,EACtF,QAAO,WAAW,YAAY,0BAA0B;EACtD,SAAS,aAAa,WAAW,iBAAiB,WAAW,GAAG,QAAQ,WAAW,GAAG,CAAC,aAAa;EACpG,MAAM;EACN,QAAQ;EACT,CAAC,CAAC;AAIL,QAAO,kCAAkC,cAAc;EACrD,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS,8BAA8B,cAAc,qBAAqB,iBAAiB,OAAO,CAAC;EACpG,CAAC;AAEF,wBAAuB;AACvB,QAAO,aAAa;EAAE,SAAS;EAAM,OAAO;EAAc,CAAC;EAC3D,CAAC,CACJ;;;aCtTkC;6BAGmD;eACZ;WAEpB;sBAEgB;AAatE,MAAa,uBAAuB,WAAW,IAC7C,QACA,0BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAG3B,MAAM,EAAE,SAAS,OAAO,SAAS,YAAY,OAFzBC;CAGpB,IAAI;AACJ,KAAI;AACF,gBAAc,2BAA2B,MAAM;UACxC,KAAK;AACZ,UAAQ,KAAK,mBAAmB,GAAG,4BAA4B,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AAClH,SAAO,aAAa,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EAAE,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAKnG,SAAQ,IAAI,mBAAmB,GAAG,oBAAoB,eAAe,YAAY,WAAW,WAAW,YAAY,cAAc,CAAC,CAAC,QAAQ,WAAW,YAAY,OAAO;AACtJ,QAAO;CAG1B,MAAM,kBAAkB,OAAO,2BAA2B,GAAG;AAC7D,SAAQ,IAAI,mBAAmB,GAAG,wBAAwB,gBAAgB,iBAAiB,mBAAmB,gBAAgB,gBAAgB,eAAe,gBAAgB,mBAAmB,aAAa,gBAAgB,UAAU,aAAa,gBAAgB,YAAY;AAOhR,KAAI,CAAC,gBAAgB,oBAAoB,CAAC,gBAAgB,qBAAqB,YAAY,MAAM;AAC/F,UAAQ,KAAK,mBAAmB,GAAG,aAAa,gBAAgB,SAAS;AACzE,SAAO,aAAa;GAClB,OAAO,gBAAgB,UAAU,uBAAuB,gBAAgB;GACxE,WAAW;GACZ,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAGrB,QAAO,OAAO,cAAc,wBAAwB,IAAI,0BAA0B;EAChF,YAAY,CAAC,CAAC;EACd,OAAO,eAAe,KAAA;EACtB,SAAS,WAAW,KAAA;EACpB,WAAW;EACZ,CAAC,CAAC;CACH,MAAM,aAAa,eAAe,WAAW,YAAY,OACrD;EAAE,GAAI,cAAc,EAAE,OAAO,aAAa,GAAG,EAAE;EAAG,GAAI,UAAU,EAAE,SAAS,GAAG,EAAE;EAAG,GAAI,YAAY,OAAO,EAAE,SAAS,MAAM,GAAG,EAAE;EAAG,GACnI,KAAA;AACJ,SAAQ,IAAI,mBAAmB,GAAG,uCAAuC,KAAK,UAAU,WAAW,GAAG;CACtG,MAAM,SAAS,OAAO,OAAO,cAAc,YAAY,IAAI,SAAS,WAAW,CAAC;AAChF,SAAQ,IAAI,mBAAmB,GAAG,iCAAiC,OAAO,QAAQ,oBAAoB,OAAO,iBAAiB,SAAS,OAAO,SAAS,SAAS;AAChK,KAAI,OAAO,SAAS;EAIlB,MAAM,aAAa,OAAO,cAAc,GAAG;AAC3C,MAAI,WACF,QAAO,kCAAkC,YAAY;GACnD,MAAM;GACN,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,SAAS;IACP,SAAS;IACT,SAAS,WAAW;IACpB,OAAO;KACL;KACA,SAAS,WAAW;KACpB,WAAW,WAAW;KACtB,SAAS,WAAW,WAAW;KAC/B,OAAO,WAAW;KAClB,QAAQ;KACR,WAAW,WAAW;KACtB,+BAAc,IAAI,MAAM,EAAC,aAAa;KACtC,MAAM,WAAW,QAAQ;KAC1B;IACF;GACF,CAAC;AAEJ,SAAO,OAAO,cAAc,wBAAwB,IAAI,0BAA0B;GAChF,YAAY,CAAC,CAAC;GACd,kBAAkB,OAAO,qBAAqB;GAC/C,CAAC,CAAC;AACH,yBAAuB;EAOvB,MAAM,YAAY,OAAO,qBAAqB;AAC9C,UAAQ,IAAI,mBAAmB,GAAG,qCAAwC,YAAY;AACtF,SAAO,aAAa;GAClB,SAAS;GACT,SAAS;GACT,kBAAkB;GAClB,MAAM,YACF,4CACA;GACJ,WAAW;IAAE,QAAQ;IAAiB,OAAO,OAAO,2BAA2B,GAAG;IAAE;GACrF,CAAC;QACG;AACL,SAAO,OAAO,cAAc,wBAAwB,IAAI,uBAAuB;GAC7E,YAAY,CAAC,CAAC;GACd,OAAO,OAAO;GACf,CAAC,CAAC;AACH,SAAO,aAAa;GAClB,OAAO,OAAO;GACd,WAAW;IAAE,QAAQ;IAAiB,OAAO,OAAO,2BAA2B,GAAG;IAAE;GACrF,EAAE,EAAE,QAAQ,KAAK,CAAC;;EAErB,CAAC,CACJ;AAID,MAAa,wBAAwB,WAAW,IAC9C,QACA,2BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,OAAO,OAAOA;AACD,QAAO;CAC1B,MAAM,EAAE,UAAU;CAClB,IAAI;AACJ,KAAI;AACF,kBAAgB,2BAA2B,MAAM;UAC1C,KAAK;AACZ,SAAO,aAAa,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EAAE,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAGnG,MAAM,qBAAqB,OAAO,cAAc,GAAG;AACnD,KAAI,CAAC,mBACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;AAG1E,QAAO,OAAO,cAAc,wBAAwB,IAAI,2BAA2B,EACjF,OAAO,iBAAiB,KAAA,GACzB,CAAC,CAAC;AAGH,KAAI,EAAC,OADiB,OAAO,cAAc,aAAa,IAAI,gBAAgB,EAAE,eAAe,eAAe,GAAG,KAAA,EAAU,CAAC,GAC7G;EACX,MAAM,QAAQ,2BAA2B;AACzC,SAAO,OAAO,cAAc,wBAAwB,IAAI,wBAAwB,EAAE,OAAO,CAAC,CAAC;AAC3F,SAAO,aAAa;GAAE,SAAS;GAAO;GAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAGjE,MAAM,eAAe,OAAO,cAAc,GAAG;AAC7C,KAAI,aAGF,QAAO,kCAAkC,cAAc;EACrD,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GACP,SAAS;GACT,SAAS,aAAa,WAAW,mBAAmB,WAAW,GAAG,QAAQ,UAAU,GAAG,CAAC,aAAa;GACrG,OAAO;IACL;IACA,SAAS,aAAa,WAAW,mBAAmB;IACpD,WAAW,aAAa;IACxB,OAAO,aAAa;IACpB,QAAQ;IACR,WAAW,aAAa;IACxB,cAAc,aAAa;IAC3B,MAAM,aAAa,QAAQ;IAC5B;GACF;EACF,CAAC;AAGJ,QAAO,OAAO,cAAc,wBAAwB,IAAI,0BAA0B,CAAC;AACnF,wBAAuB;AACvB,QAAO,aAAa;EAAE,SAAS;EAAM,WAAW;EAAM,OAAO,gBAAgB;EAAM,CAAC;EACpF,CAAC,CACJ;AAQD,MAAa,wBAAwB,WAAW,IAC9C,QACA,2BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAC3B,MAAM,OAAO,OAAOA;CACpB,MAAM,aAAa,OAAO;CAE1B,MAAM,EAAE,OAAO,SAAS,WAAW,MAAM,YAAY;CAMrD,IAAI;AACJ,KAAI;AACF,iBAAe,2BAA2B,MAAM;UACzC,KAAK;AACZ,SAAO,aAAa,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EAAE,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAGnG,MAAM,aAAa,OAAO,cAAc,GAAG;AAC3C,KAAI,CAAC,WACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;AAG1E,QAAO,OAAO,cAAc,wBAAwB,IAAI,2BAA2B;EACjF,OAAO,gBAAgB,WAAW;EAClC,SAAS,WAAW,KAAA;EACpB;EACA,YAAY,CAAC,CAAC;EACf,CAAC,CAAC;AAEH,KAAI,UAAU;AACZ,SAAO,WAAW,YAAY,0BAA0B;GACtD,SAAS,WAAW;GACpB,MAAM;GACN,QAAQ;GACT,CAAC,CAAC;AAEH,GAAC,YAAY;AACX,OAAI;AAGF,UAAM,OAAO,WAAW,kCAAkC,YAAY;KACpE,MAAM;KACN,4BAAW,IAAI,MAAM,EAAC,aAAa;KACnC,SAAS;MAAE,SAAS;MAAI,SAAS,WAAW;MAAS;KACtD,CAAC,CAAC;IAEH,MAAM,SAAS,MAAM,aAAa,IAAI;KAAE,OAAO;KAAc;KAAS,UAAU;KAAM;KAAS,CAAC;AAEhG,QAAI,OAAO,SAAS;KAClB,MAAM,eAAe,MAAM,OAAO,WAAW,cAAc,GAAG,CAAC;AAG/D,SAAI,aACF,OAAM,OAAO,WAAW,kCAAkC,cAAc;MACtE,MAAM;MACN,4BAAW,IAAI,MAAM,EAAC,aAAa;MACnC,SAAS;OACP,SAAS;OACT,SAAS,aAAa,WAAW,WAAW;OAC5C,OAAO;QACL;QACA,SAAS,aAAa,WAAW,WAAW;QAC5C,WAAW,aAAa,aAAa,WAAW;QAIhD,SAAS,aAAa,WAAW,WAAW,WAAW;QACvD,OAAO,gBAAgB,aAAa,SAAS,WAAW;QACxD,QAAQ;QACR,WAAW,aAAa,aAAa,WAAW;QAChD,+BAAc,IAAI,MAAM,EAAC,aAAa;QACtC,MAAM,aAAa,QAAQ,WAAW;QACvC;OACF;MACF,CAAC,CAAC;AAEL,4BAAuB;;AAEzB,UAAM,wBAAwB,IAAI,2BAA2B;KAC3D,SAAS,OAAO;KAChB,OAAO,OAAO;KACf,CAAC;YACK,KAAc;IACrB,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,YAAQ,MAAM,wCAAwC,GAAG,IAAI,MAAM;AACnE,UAAM,wBAAwB,IAAI,wBAAwB,EAAE,OAAO,KAAK,CAAC;;MAEzE;AAEJ,SAAO,aAAa;GAAE,UAAU;GAAM,UAAU;GAAM,SAAS;GAAI,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAIvF,MAAM,SAAS,OAAO,OAAO,cAAc,aAAa,IAAI;EAAE,OAAO;EAAc;EAAS,UAAU;EAAO;EAAS,CAAC,CAAC;AAExH,KAAI,OAAO,SAAS;EAClB,MAAM,eAAe,OAAO,cAAc,GAAG;AAC7C,SAAO,WAAW,YAAY,0BAA0B;GACtD,SAAS,cAAc,WAAW,WAAW;GAC7C,MAAM;GACN,QAAQ;GACT,CAAC,CAAC;AAIH,MAAI,cAAc;AAChB,UAAO,kCAAkC,cAAc;IACrD,MAAM;IACN,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,SAAS;KAAE,SAAS;KAAI,SAAS,aAAa,WAAW,WAAW;KAAS;IAC9E,CAAC;AACF,UAAO,kCAAkC,cAAc;IACrD,MAAM;IACN,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,SAAS;KACP,SAAS;KACT,SAAS,aAAa,WAAW,WAAW;KAC5C,OAAO;MACL;MACA,SAAS,aAAa,WAAW,WAAW;MAC5C,WAAW,aAAa,aAAa,WAAW;MAKhD,SAAS,aAAa,WAAW,WAAW,WAAW;MACvD,OAAO,gBAAgB,aAAa,SAAS,WAAW;MACxD,QAAQ;MACR,WAAW,aAAa,aAAa,WAAW;MAChD,+BAAc,IAAI,MAAM,EAAC,aAAa;MACtC,MAAM,aAAa,QAAQ,WAAW;MACvC;KACF;IACF,CAAC;;AAEJ,yBAAuB;AACvB,SAAO,aAAa;GAAE,SAAS;GAAM,WAAW;GAAM,SAAS;GAAI,OAAO,gBAAgB,WAAW;GAAO,CAAC;;AAG/G,QAAO,aAAa,EAAE,OAAO,OAAO,OAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;EAC7D,CAAC,CACJ;AAyBD,MAAa,6BAA6B,WAAW,IACnD,QACA,iCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAG3B,MAAM,EAAE,OAAO,WAAW,OAAO,UAAU,YAAY,OAFnCA;CAOpB,MAAM,aAAa,cAAc;CAEjC,IAAI;AACJ,KAAI,cAAc,SAChB,KAAI;AACF,aAAW,yBAAyB,SAAS;UACtC,KAAK;AACZ,SAAO,aAAa,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EAAE,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAIrG,MAAM,aAAa,OAAO,cAAc,GAAG;AAC3C,KAAI,CAAC,WACH,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;CAE1E,MAAM,UAAU,WAAW,WAAW,GAAG,QAAQ,WAAW,GAAG,CAAC,aAAa;CAE7E,MAAM,YAAY,OAAO,2BAA2B,GAAG;AACvD,KAAI,UAAU,mBACZ,QAAO,aAAa;EAClB,OAAO,SAAS,GAAG,0CAA0C,QAAQ;EACrE;EACD,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGrB,QAAO,OAAO,cAAc,wBAAwB,IAAI,iCAAiC;EACvF;EACA,OAAO;EACP;EACA;EACD,CAAC,CAAC;AAIH,QAAO,YAAY,GAAG,CAAC,KAAK,OAAO,YAAY,OAAO,KAAK,CAAC;CAI5D,MAAM,aAAa,OAAO,OAAO,cAAc,mBAAmB,QAAQ,CAAC;AAC3E,QAAO,OAAO,cAAc,wBAAwB,IAAI,6BAA6B;EACnF,SAAS,WAAW;EACpB,MAAM,WAAW;EAClB,CAAC,CAAC;AAEH,KAAI,CAAC,YAAY;AACf,yBAAuB;AACvB,SAAO,aAAa;GAClB,SAAS;GACT,OAAO;GACP,SAAS;GACT;GACA,SAAS,WAAW;GACpB,MAAM;GACP,CAAC;;CAMJ,MAAM,aAAa,YAAY,WAAW,SAAS;CACnD,IAAI,mBAA8D;AAClE,KAAI,QAIF,qBAAmB,OAHY,OAAO,QAAQ,YAC5C,kBAAkB,SAAS,YAAY,MAAM,oBAAoB,WAAW,CAAC,CAC9E,EACkC,UAAU,UAAU;CAGzD,MAAM,mBAAmB,SAAS,QAAQ,aAAa;CACvD,MAAM,cAAc,WAAW,YAC3BC,UAAQ,WAAW,UAAU,GAC7B,KAAA;CAEJ,MAAM,cADgB,4BAA4B,QACjB,EAAE,eAAe,eAAe,QAAQ,KAAK;CAC9E,MAAM,gBAAgB,WAAW,aAAaC,OAAK,aAAa,cAAc,WAAW,QAAQ,aAAa,GAAG;CAEjH,MAAM,OAAO,kBAAkB;EAC7B;EACA,OAAO;EACP,SAAS;EACV,CAAC;AAEF,QAAO,OAAO,cAAc,wBAAwB,kBAAkB,uCAAuC;EAC3G;EACA,OAAO;EACP,SAAS;EACV,CAAC,CAAC;AAMH,oBAAmB;EACjB,IAAI;EACJ;EACA,WAAW;EACX,SAAS,oBAAoB;EAC7B,MAAM;EACN,OAAO;EACP,QAAQ;EACR,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC,CAAC;AAEF,KAAI;AACF,SAAO,OAAO,cAAc,wBAAwB;GAClD;GACA;GACA,MAAM;GACN;GACA;GACA,KAAK;GACN,CAAC,CAAC;UACI,KAAU;AACjB,SAAO,aAAa;GAClB,SAAS;GACT,OAAO,qCAAqC,KAAK,WAAW,OAAO,IAAI;GACvE,OAAO,WAAW;GACnB,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAGrB,QAAO,OAAO,cAAc,wBAAwB,kBAAkB,gDAAgD;EACpH,OAAO;EACP,SAAS;EACV,CAAC,CAAC;AAEH,wBAAuB;AACvB,QAAO,aAAa;EAClB,SAAS;EACT,OAAO;EACP,SAAS;EACT;EACA,SAAS,WAAW;EACpB,cAAc;EACd,gBAAgB;EAChB,MAAM;EACP,CAAC;EACF,CAAC,CACJ;AAOD,MAAa,4BAA4B,WAAW,IAClD,QACA,2BACA,OAAO,IAAI,aAAa;AACtB,QAAO,OAAO,OAAO,QAAQ,YAAY;AACvC,MAAI;GACF,MAAM,WAAW,MAAM,OAAO,WAAW,mBAAmB,CAAC,EAAE,QAAO,MAAK,EAAE,WAAW;GACxF,MAAM,UAA4E,EAAE;AAEpF,QAAK,MAAM,SAAS,QAClB,KAAI;IACF,MAAM,SAAS,MAAM,aAAa,MAAM,IAAI,EAAE,UAAU,OAAO,CAAC;AAChE,QAAI,OAAO,QACT,SAAQ,KAAK;KAAE,IAAI,MAAM;KAAI,SAAS,MAAM;KAAS,OAAO,MAAM;KAAO,QAAQ;KAAa,CAAC;QAE/F,SAAQ,KAAK;KAAE,IAAI,MAAM;KAAI,SAAS,MAAM;KAAS,OAAO,MAAM;KAAO,QAAQ,WAAW,OAAO;KAAS,CAAC;YAExG,KAAc;IACrB,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,YAAQ,MAAM,8BAA8B,MAAM,GAAG,IAAI,IAAI;AAC7D,YAAQ,KAAK;KAAE,IAAI,MAAM;KAAI,SAAS,MAAM;KAAS,OAAO,MAAM;KAAO,QAAQ,WAAW;KAAO,CAAC;;GAIxG,MAAM,YAAY,QAAQ,QAAO,MAAK,EAAE,WAAW,YAAY,CAAC;AAChE,WAAQ,IAAI,sBAAsB,UAAU,GAAG,QAAQ,OAAO,mBAAmB;AACjF,UAAO,aAAa;IAAE,WAAW;IAAW,OAAO,QAAQ;IAAQ;IAAS,CAAC;WACtE,OAAgB;AAEvB,UAAO,aAAa,EAAE,OAAO,gCADjB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACD,EAAE,EAAE,QAAQ,KAAK,CAAC;;GAErF;EACF,CACH;AAMD,MAAa,6BAA6B,WAAW,IACnD,QACA,iCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;AACR,QAAO;CAE1B,MAAM,YAAY,OAAO,2BAA2B,GAAG;CACvD,MAAM,aAAa,OAAO,cAAc,GAAG;AAC3C,KAAI,CAAC,WACH,QAAO,aAAa;EAAE,OAAO,SAAS,GAAG;EAAa;EAAW,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGrF,KAAI,UAAU,mBACZ,QAAO,aAAa;EAAE,OAAO,SAAS,GAAG;EAA8B;EAAW,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGtG,MAAM,oBAAoB,OAAO,mBAAmB,GAAG;AACvD,KAAI,CAAC,kBACH,QAAO,aAAa;EAAE,OAAO,SAAS,GAAG;EAAiC;EAAW,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGzG,MAAM,WAAW,YAAY,GAAG;AAGhC,QAAO,OAAO,cAAc,GAAGA,OAAK,UAAU,aAAa,EAAE,EAAE,OAAO,MAAM,CAAC,CAAC;AAG9E,QAAO,OAAO,cAAc,GAAGA,OAAK,UAAU,gBAAgB,EAAE,EAAE,OAAO,MAAM,CAAC,CAAC;CAIjF,MAAM,cAAcA,OAAK,UAAU,eAAe;AAClD,KAAI,WAAW,YAAY,CACzB,KAAI;EACF,MAAM,iBAAiB,OAAO,OAAO,cAAc,SAAS,aAAa,QAAQ,CAAC;EAClF,MAAM,UAAU,KAAK,MAAM,eAAe;AAC1C,SAAO,QAAQ;AACf,SAAO,OAAO,cAAc,UAAU,aAAa,KAAK,UAAU,SAAS,MAAM,EAAE,CAAC,CAAC;SAC/E;AAGV,QAAO,YAAY,GAAG,CAAC,KAAK,OAAO,YAAY,OAAO,KAAK,CAAC;AAO5D,QAAO,kCAAkC,YAAY;EACnD,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GAAE,SAAS;GAAI,SAAS,WAAW;GAAS;EACtD,CAAC;AAEF,SAAQ,IAAI,uCAAuC,GAAG,SAAS,kBAAkB,MAAM,GAAG,EAAE,CAAC,MAAM;AACnG,wBAAuB;AACvB,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS;EAAI;EAAmB,WAAW,OAAO,2BAA2B,GAAG;EAAE,CAAC;EACxH,CAAC,CACJ;AAmBD,eAAe,+BAAmE;CAChF,MAAM,SAAS,MAAM,OAAO,WAAW,mBAAmB,CAAC;CAE3D,MAAM,QAAmC,EAAE;AAE3C,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,CAAC,MAAM,QAAS;AAGpB,MAAI,EAAG,MAAc,eAAe,MAClC;AAIF,MAAK,MAAc,UAAW,MAAc,SAC1C;AAIF,MAAI,MAAM,SAAS,UAAU,MAAM,SAAS,KAAA,EAC1C;EAIF,MAAM,iBAAiB,MAAM,uBAAuB,MAAM,IAAI,OAAO,MAAM,QAAQ;AAEnF,MAAI,eAAe,OAAO;AAExB,WAAQ,KAAK,6BAA6B,MAAM,GAAG,IAAI,eAAe,QAAQ;AAC9E;;EAGF,MAAM,WAAW,eAAe;EAChC,MAAM,aAAa,eAAe;AAElC,QAAM,KAAK;GACT,SAAS,MAAM;GACf,SAAS,MAAM;GACf,cAAc,MAAM;GACpB,gBAAgB,MAAM,WAAW;GACjC;GACA;GACA,SAAS,MAAM,UAAU,aAAa,MAAM,WAAW,mBAAmB;GAC1E,QAAS,MAAc,UAAU;GACjC,UAAW,MAAc,YAAY;GACrC,QAAQ,MAAM;GACf,CAAC;;AAGJ,QAAO;;AAGT,MAAa,8BAA8B,WAAW,IACpD,OACA,2CACA,YAAY,OAAO,IAAI,aAAa;AAClC,KAAI;EACF,MAAM,QAAQ,OAAO,OAAO,cAAc,8BAA8B,CAAC;AACzE,SAAO,aAAa;GAClB,SAAS;GACT;GACA,aAAa,MAAM;GACnB,eAAe,MAAM,QAAO,MAAK,EAAE,QAAQ,CAAC;GAC7C,CAAC;UACK,OAAgB;AAEvB,SAAO,aAAa,EAAE,OAAO,wCADjB,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,IACU,EAAE,EAAE,QAAQ,KAAK,CAAC;;EAEhG,CAAC,CACJ;AAKD,eAAe,uBAAuB,SAAiB,YAAiB,SAAoF;CAC1J,MAAM,gBAAgB,WAAW;AACjC,KAAI,CAAC,cACH,QAAO;EAAE,UAAU,WAAW;EAAO,YAAY,WAAW,WAAW;EAAe,OAAO;EAA+B;AAG9H,KAAI;EAEF,MAAM,OAAO,sBAAsB,cAAc;AACjD,MAAI,CAAC,QAAQ,CAAC,KAAK,KAAK,MACtB,QAAO;GAAE,UAAU,WAAW;GAAO,YAAY,WAAW,WAAW;GAAe,OAAO;GAA0B;EAKzH,IAAI,WAAW;AACf,MAAI,WAAW,YAAY;AAEzB,cAAW,KAAK,KAAK,MAAM,MAAM,SAAc,KAAK,OAAO,WAAW,WAAW;AACjF,OAAI,CAAC,SACH,QAAO;IAAE,UAAU,WAAW;IAAO,YAAY,WAAW,WAAW;IAAe,OAAO,kBAAkB,WAAW,WAAW;IAAqB;SAEvJ;AAGL,cAD0B,qBAAqB,sBAAM,IAAI,KAAK,CAClC,CAAC;AAC7B,OAAI,CAAC,SACH,QAAO;IAAE,UAAU,WAAW;IAAO,YAAY,WAAW,WAAW;IAAe,OAAO;IAAiC;;EAMlI,MAAM,SAAS,gBAAgB,CAAC;EAChC,MAAM,SAAS,OAAO;EACtB,MAAM,yBAAyB,UAC3B,sCAAsC,QAAQ,SAAS,KAAK,KAAK,SAAS,GAC1E,8BAA8B,QAAQ,KAAK,KAAK,SAAS;EAE7D,MAAM,WAAW,gBAAgB,UAAU;GACzC,cAAc,KAAK,KAAK;GACxB,UAAU,QAAQ,QAAQ,aAAa;GACvC,QAAQ;IAAE,GAAG;IAAQ,iBAAiB;KAAE,GAAG;KAAQ,SAAS;KAAwB;IAAE;GACvF,CAAC;AAEF,SAAO;GACL,UAAU,SAAS;GACnB,YAAY,SAAS;GACtB;UACM,KAAc;EACrB,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,SAAO;GACL,UAAU,WAAW;GACrB,YAAY,WAAW,WAAW;GAClC,OAAO,+BAA+B;GACvC;;;AAIL,MAAa,mCAAmC,WAAW,IACzD,QACA,2CACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,EAAE,aAAa,OADDF;AAGpB,KAAI,CAAC,YAAY,CAAC,MAAM,QAAQ,SAAS,IAAI,SAAS,WAAW,EAC/D,QAAO,aAAa,EAAE,OAAO,oDAAoD,EAAE,EAAE,QAAQ,KAAK,CAAC;CAIrG,MAAM,iBAAiB,OAAO,OAAO,cAAc,8BAA8B,CAAC;CAClF,MAAM,cAAc,IAAI,IAAI,eAAe,KAAI,MAAK,EAAE,QAAQ,CAAC;CAE/D,MAAM,UAAoG,EAAE;AAE5G,MAAK,MAAM,WAAW,SACpB,KAAI;AAEF,MAAI,CAAC,YAAY,IAAI,QAAQ,EAAE;AAC7B,WAAQ,KAAK;IAAE,IAAI;IAAS,QAAQ;IAAc,OAAO;IAAsF,CAAC;AAChJ;;EAGF,MAAM,aAAa,OAAO,cAAc,QAAQ;AAChD,MAAI,CAAC,YAAY;AACf,WAAQ,KAAK;IAAE,IAAI;IAAS,QAAQ;IAAa,OAAO;IAAyB,CAAC;AAClF;;EAGF,MAAM,UAAU,WAAW,WAAW,QAAQ,QAAQ,WAAW,GAAG,CAAC,aAAa;EAGlF,MAAM,iBAAiB,OAAO,OAAO,cAAc,uBAAuB,SAAS,YAAY,QAAQ,CAAC;AAExG,MAAI,eAAe,OAAO;AACxB,WAAQ,KAAK;IACX,IAAI;IACJ,QAAQ;IACR,OAAO,eAAe;IACvB,CAAC;AACF;;EAGF,MAAM,WAAW,eAAe;EAChC,MAAM,aAAa,eAAe;EAGlC,MAAM,gBAAgB,OAAO,OAAO,cAAc,aAAa,SAAS;GACtE,OAAO;GACP,SAAS;GACT,UAAU;GACX,CAAC,CAAC;AAEH,MAAI,cAAc,QAChB,SAAQ,KAAK;GACX,IAAI;GACJ,QAAQ;GACR;GACA;GACD,CAAC;MAEF,SAAQ,KAAK;GACX,IAAI;GACJ,QAAQ;GACR,OAAO,cAAc;GACtB,CAAC;UAEG,KAAc;EACrB,MAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC5D,UAAQ,KAAK;GACX,IAAI;GACJ,QAAQ;GACR,OAAO;GACR,CAAC;;CAIN,MAAM,YAAY,QAAQ,QAAO,MAAK,EAAE,WAAW,YAAY,CAAC;AAChE,wBAAuB;AACvB,QAAO,aAAa;EAClB,SAAS;EACT,WAAW;EACX,OAAO,SAAS;EAChB;EACD,CAAC;EACF,CAAC,CACJ;;;aCp3B2E;mBAIM;uBACH;YACb;aAK/B;AAQnC,MAAa,8BAA8B,WAAW,IACpD,OACA,mCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAG3B,MAAM,SADU,oBACM,CAAC,eAAe,GAAG;AACzC,KAAI,CAAC,OACH,QAAO,aAAa,EAAE,OAAO,4CAA4C,EAAE,EAAE,QAAQ,KAAK,CAAC;AAE7F,QAAO,aAAa,OAAO;EAC3B,CAAC,CACJ;AAID,MAAa,iCAAiC,WAAW,IACvD,OACA,sCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAE3B,MAAM,aAAa,OAAO,cAAc,GAAG;AAC3C,KAAI,CAAC,WACH,QAAO,aAAa,EAAE,OAAO,mBAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGpE,MAAM,UAAU,mBAAmB,GAAG;AACtC,KAAI,CAAC,QACH,QAAO,aAAa,EAAE,OAAO,+BAA+B,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGhF,MAAM,SAAS,eAAe,IAAI,QAAQ;CAC1C,MAAM,WAAW,OAAO,iBACtB,IACA,WAAW,WACX,WAAW,SACX,WAAW,OACX,QACA,wBAAwB,CACzB;AAED,KAAI,SAAS,SAAS,GAAG;EACvB,MAAM,UAAU,SAAS;AACzB,SAAO,aAAa;GAClB,WAAW;GACX,SAAS,QAAQ;GACjB,cAAc,WAAW;GACzB,gBAAgB,QAAQ;GACxB,QAAQ,QAAQ;GACjB,CAAC;;AAGJ,QAAO,aAAa;EAClB,WAAW;EACX,SAAS;EACT,cAAc,WAAW;EACzB,gBAAgB;EAChB,QAAQ;EACT,CAAC;EACF,CAAC,CACJ;AAID,MAAa,wBAAwB,WAAW,IAC9C,QACA,2BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAG3B,MAAM,EAAE,SAAS,WAAW,OAFRG;CAGpB,IAAI;AACJ,KAAI;AACF,gBAAc,yBAAyB,QAAQ;UACxC,KAAK;AACZ,SAAO,aAAa,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EAAE,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAGnG,MAAM,SAAS,OAAO,eAAe,IAAI;EACvC;EACA,QAAQ,UAAU;EACnB,CAAC;AAEF,KAAI,OAAO,QACT,QAAO,aAAa;EAClB,SAAS;EACT,YAAY,OAAO;EACnB,cAAc,OAAO;EACtB,CAAC;KAEF,QAAO,aAAa;EAAE,SAAS;EAAO,OAAO,OAAO;EAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;EAE/E,CAAC,CACJ;AAKD,MAAa,oBAAoB,WAAW,IAC1C,OACA,wBACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAE3B,MAAM,aAAa,OAAO,cAAc,GAAG;AAC3C,KAAI,CAAC,WACH,QAAO,aAAa,EAAE,OAAO,mBAAmB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGpE,IAAI,OAAO;CACX,IAAI,cAAc;CAClB,IAAI,eAAe;CACnB,IAAI,kBAAkB;CACtB,IAAI,mBAAmB;CACvB,IAAI,gBAAgB,WAAW,SAAS;CAIxC,MAAM,kCAAkB,IAAI,KAAa;CAGzC,MAAM,oBAAoBC,OADV,QAAQ,IAAI,QAAQ,SAAS,EACL,WAAW,WAAW;CAC9D,MAAM,gBAAgB,WAAW;AAEjC,KAAI,eAAe;EAEjB,MAAM,aAAaA,OAAK,mBADD,uBAAuB,cACW,CAAC;EAC1D,MAAM,oBAAoBA,OAAK,YAAY,sBAAsB;EAEjE,MAAM,iBAAiB,OAAO,aAAqB;GAEjD,MAAM,SAAQ,MADa,SAAS,UAAU,QAAQ,EAC3B,MAAM,KAAK,CAAC,QAAQ,MAAc,EAAE,MAAM,CAAC;AACtE,QAAK,MAAM,QAAQ,MACjB,KAAI;IACF,MAAM,QAAQ,KAAK,MAAM,KAAK;IAC9B,MAAM,QAAQ,MAAM,SAAS,SAAS,MAAM;IAC5C,MAAM,QAAQ,MAAM,SAAS,SAAS,MAAM;IAC5C,MAAM,UAAU,MAAM,aAAa,MAAM,SAAS;AAClD,QAAI,UAAU,YAAY,KAAA,KAAa,CAAC,gBAAgB,IAAI,QAAQ,GAAG;AACrE,SAAI,YAAY,KAAA,EAAW,iBAAgB,IAAI,QAAQ;AACvD,oBAAe,MAAM,gBAAgB;AACrC,qBAAgB,MAAM,iBAAiB;AACvC,wBAAmB,MAAM,2BAA2B;AACpD,yBAAoB,MAAM,+BAA+B;;AAE3D,QAAI,SAAS,CAAC,cACZ,iBAAgB;WAEZ;;AAIZ,MAAI,WAAW,kBAAkB,CAC/B,KAAI;GACF,MAAM,eAAe,KAAK,MAAM,OAAO,OAAO,cAAc,SAAS,mBAAmB,QAAQ,CAAC,CAAC;AAClG,QAAK,MAAM,gBAAiB,aAAa,WAAW,EAAE,CACpD,KAAI,cAAc,YAAY,WAAW,aAAa,SAAS,CAC7D,QAAO,OAAO,cAAc,eAAe,aAAa,SAAS,CAAC;UAGhE;AAGV,MAAI,gBAAgB,KAAK,WAAW,WAAW,CAC7C,KAAI;GACF,MAAM,SAAS,OAAO,OAAO,cAAc,QAAQ,WAAW,CAAC,EAAE,QAAO,MAAK,EAAE,SAAS,SAAS,CAAC;AAClG,QAAK,MAAM,QAAQ,MACjB,QAAO,OAAO,cAAc,eAAeA,OAAK,YAAY,KAAK,CAAC,CAAC;UAE/D;;AAIZ,KAAI,cAAc,KAAK,eAAe,GAAG;EACvC,MAAM,YAAY,mBAAmB,iBAAiB,kBAAkB;EACxE,MAAM,UAAU,eAAe,UAAU,UAAU,UAAU,MAAM;AACnE,MAAI,QAOF,QAAO,kBAAkB;GALvB;GACA;GACA;GACA;GAE4B,EAAE,QAAQ;;AAI5C,QAAO,aAAa;EAClB,SAAS;EACT,OAAO,iBAAiB,WAAW;EACnC,QAAQ;GACN,OAAO;GACP,QAAQ;GACR,WAAW;GACX,YAAY;GACb;EACD;EACD,CAAC;EACF,CAAC,CACJ;AAKD,SAAgB,kCACd,SACyE;AAEzE,KADoB,eAAe,QACpB,CAAC,GAAI,QAAO,EAAE,IAAI,MAAM;AACvC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,MAAM,EAAE,OAAO,aAAa;EAAE;;AAGjE,MAAa,+BAA+B,WAAW,IACrD,QACA,mCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,iBAAiB,kCAAkC,OADlC,kBAAkB,kBACwB;AACjE,KAAI,CAAC,eAAe,GAClB,QAAO,aAAa,eAAe,MAAM,EAAE,QAAQ,eAAe,QAAQ,CAAC;CAI7E,MAAM,MAAK,OADW,WAAW,QACf,SAAS;CAE3B,MAAM,EAAE,mBAAmB,OADPD;AAGpB,KAAI,CAAC,kBAAkB,CAAC;EAAC;EAAQ;EAAY;EAAO,CAAC,SAAS,eAAe,CAC3E,QAAO,aAAa,EAAE,OAAO,kDAAkD,EAAE,EAAE,QAAQ,KAAK,CAAC;AAInG,KAAI,EAAC,OADqB,cAAc,GAAG,EAEzC,QAAO,aAAa,EAAE,OAAO,SAAS,GAAG,aAAa,EAAE,EAAE,QAAQ,KAAK,CAAC;AAG1E,QAAO,OAAO,cAAc,uBAAuB,IAAI,eAAe,CAAC;AACvE,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS;EAAI;EAAgB,CAAC;EACnE,CAAC,CACJ;AAOD,MAAa,4BAA4B,WAAW,IAClD,QACA,gCACA,YAAY,OAAO,IAAI,aAAa;AAGlC,QAAO,aAAa,EAClB,OAAO,UAFE,OADW,WAAW,QACf,SAAS,GAEN,6CACpB,EAAE,EAAE,QAAQ,KAAK,CAAC;EACnB,CAAC,CACJ;;;;;;AC5QD,SAAA,kBAAA,MAAA;AACE,QAAA,KAAA,QAAA,yBAAA,GAAA,CAAA,QAAA,aAAA,GAAA,CAAA,QAAA,UAAA,GAAA,CAAA,QAAA,cAAA,KAAA,CAAA,MAAA;;AAQF,SAAA,mCAAA,OAAA,MAAA;;AAEE,KAAA,CAAA,KAAA,QAAA,CAAA,aAAA,QAAA;;;AAUA,KAAA,gBAAA,GAAA;;;;;AAYE,MAAA,UAAA,SAAA,EAAA,QAAA;;AAOA,MAAA,QAAA,SAAA,EAAA,QAAA;;AAIF,QAAA,CAAA,4CAAA;;AAGF,SAAA,wBAAA,OAAA;;;;;;;;;;;;AAeE,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6CD,eAAA,4BAAA,aAAA,eAAA,OAAA;;;;;;;;;;;;AAeC,OAAA,QAAA,iBAAA,EAAA,WAAA,MAAA,CAAA;AACA,OAAA,YAAA,iBAAA,wBAAA,gBAAA,EAAA,QAAA;AAEA,QAAA;;;;;;;;;;;;AAeF,MAAA,wBAAA,aAAA,eAAA,UAAA,OAAA,WAAA;;;;;;;;;;;;ACtHA,SAAA,uBAAA,OAAA;AASE,QAAA,OAAA,IAAA,aAAA;;;;;;;AAgBE,MAAA,CAAA,SAAA,QAAA,QAAA,aAAA,EAAA,OAAA,SAAA,SAAA,EAAA,EAAA,QAAA,KAAA,CAAA;;AAIA,SAAA,OAAA,cAAA,IAAA,mBAAA,kBAAA,OAAA,CAAA;;;;;;;;;;AAYA,sBAAA,SAAA,SAAA,SAAA,8BAAA;;;;;;;;;;;AAiBA,SAAA,eAAA;;;;;;;;;;AAUA,8BAAA,MAAA,SAAA,eAAA,MAAA,GAAA;AAOA,SAAA,OAAA,cAAA,OAAA,WAAA,UAAA,aAAA,SAAA,cAAA,CAAA,KAAA,OAAA,YAAA,OAAA,KAAA,CAAA,CAAA,CAAA;AAYA,SAAA,kCAAA;;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,sBAAA,SAAA,SAAA,WAAA,qCAAA;;;;AAIA,MAAA;AAAM,wBAAA,CAAA,WAAA,SAAA;;;;;AACN,yBAAA;AACA,SAAA,aAAA;;;;;;;;;;;AAYJ,SAAA,6BAAA,OAAA;AAeE,QAAA,OAAA,IAAA,aAAA;;AAiBE,MAAA,WAAA,cAAA,IAAA,WAAA,UAAA,EAAA;;AAEE,OAAA;AACE,WAAA,OAAA,cAAA,YAAA,+BAAA,EAAA,UAAA,SAAA,CAAA,CAAA;AACA,oBAAA;;AAGF,OAAA,eAAA;;;;;AAKM,UAAA,UAAA,SAAA,OAAA,IAAA,MAAA,mCAAA,QAAA,aAAA,WAAA;AAGA,aAAA;;;AAIF,UAAA,MAAA,WAAA,eAAA,KAAA;AAEI,UAAA,WAAA,QAAA,EAAA;;;AAGE,WAAA,MAAA,QAAA,SAAA,GAAA,MAAA,KAAA,iBAAA,QAAA;;AAEA,WAAA,aAAA,QAAA,SAAA,aAAA,IAAA,QAAA;;;AAGF,UAAA,iBAAA,SAAA,MAAA,QAAA,WAAA,+BAAA,CAAA,OAAA;;AAGJ,YAAA;;;AAIF,QAAA;AACE,mBAAA,OAAA,OAAA,cAAA,sBAAA,SAAA,cAAA,CAAA;;AAEA,YAAA,aAAA;;;;;;;AAOF,WAAA,OAAA,cAAA,wBAAA,kBAAA,+BAAA;;;;;;AAOA,QAAA;;;;;;;;;AAWE,SAAA,kBAAA,SAAA,KAAA,kBAAA,OAAA,SAAA;;AAFE,aAAA,OAAA,SAAA,KAAA,KAAA,CAAA,OAAA,SAAA,IAAA,IAAA,OAAA,SAAA,YAAA;;;AAKJ,WAAA,OAAA,cAAA,wBAAA,kBAAA,sCAAA;;;;;AAMA,QAAA,CAAA,mBAAA,CAAA,WAAA;;;AAGE,YAAA,OAAA,cAAA,MAAA,eAAA,EAAA,WAAA,MAAA,CAAA,CAAA;AAEA,wBAAA;;;;;;;;;;;AAWA,iCAAA,SAAA,eAAA,aAAA;AACA,YAAA,OAAA,cAAA,wBAAA,cAAA,sCAAA;;;;;;;AAUI,MAAA,YAAA;AACE,UAAA;;;AAGE,aAAA,wBAAA,cAAA,iCAAA;;;;;AAWA,aAAA,SAAA,CAAA,MAAA,EAAA;;;;;;;;;;;;;;AAOA,cAAA,KAAA,KAAA,GAAA,YAAA,WAAA;AACE,YAAA;;;;;;;;;AAWE,aAAA,WAAA,SAAA,KAAA,WAAA,OAAA,SAAA;;AAFE,iBAAA,OAAA,SAAA,KAAA,KAAA,CAAA,OAAA,SAAA,IAAA,IAAA,OAAA,SAAA,YAAA;;AAEU,oBAAA;AAAgB;;;AAE9B,cAAA,IAAA,SAAA,MAAA,WAAA,GAAA,eAAA,CAAA;;AAGF,aAAA,wBAAA,cAAA,UAAA,mCAAA,kCAAA;;;;;AAMA,WAAA,CAAA,SAAA;AAEE,2BAAA;;;;;;;;;;AAUA;;AAMF,YAAA,MAAA,SAAA,CAAA,OAAA,eAAA,eAAA,EAAA,OAAA,eAAA,OAAA,aAAA,YAAA,eAAA,CAAA,CAAA,KAAA;AAEI,YAAA,WAAA,MAAA;;AAGI,gBAAA,GAAA,OAAA;;;;AACA,kBAAA,IAAA,wCAAA,QAAA;;;;AAIJ,gBAAA,KAAA,kCAAA,MAAA,IAAA,MAAA,UAAA;;AAIJ,aAAA,wBAAA,cAAA,+CAAA;;;;;;AAMA,2BAAA,SAAA,SAAA,SAAA,6DAAA,EAAA,eAAA,CAAA;AAUA,2BAAA,SAAA,SAAA,WAAA,4DAAA;;;;;;;;;AAIA,aAAA,mBAAA;;;AAGA,2BAAA,SAAA,SAAA,WAAA,cAAA,EAAA,eAAA,CAAA;AACA,aAAA,wBAAA,cAAA,gCAAA;;;;AAKA,WAAA;AAAM,2BAAA;;;;;;;;;;AASN,eAAA,MAAA,yDAAA,QAAA,IAAA,IAAA;;;AAIR,YAAA,OAAA,cAAA,OAAA,WAAA,WAAA,OAAA;;;;;;;;;AAKA,SAAA;AAAM,2BAAA,CAAA,WAAA,SAAA;;;;;AACN,4BAAA;AACA,YAAA,aAAA;;;;;;;;;;;;AAaN,SAAA;;;;;aCvb+G;gBACvC;qBAEN;sBACK;eAOe;SAKxB;iBAGX;sBACe;WAMD;;;;;;AA6BrE,SAAgB,kCAAkC,SAAiB,WAAyB;AAC1F,KAAI;AACF,wBAAsB;GACpB,QAAQ;GACR,OAAO;GACP,SAAS,6CAA6C;GACtD;GACA,SAAS,KAAK,UAAU;IACtB,QAAQ;IACR,WAAW,UAAU,MAAM,KAAK,CAAC,MAAM,GAAG,EAAE;IAC7C,CAAC;GACH,CAAC;SACI;;AAGV,SAAgB,4BAA4B,MAAuB;CACjE,MAAM,gBAAgB;AACtB,QAAO,OAAO,GAAG,KAAK,GAAG,kBAAkB;;;;;;;;;;;;;;;;;AAkB7C,MAAa,sBACX,eACA,eAEA,kBAAkB,OAAO,cAAcO,gBACrC,MACA;CAAC;CAAQ;CAAU;CAAM;CAAY;CAAY;CAAO;CAAW;CAAI,EACvE;CAAE,KAAK;CAAe,UAAU;CAAS,SAAS;CAAO,CAC1D,CAAC,CAAC,CAAC,KACF,OAAO,WAAW;CAChB,iBAAiB;CACjB,YAAY,EAAE,aAAa;AACzB,MAAI;GACF,MAAM,UAAU,KAAK,MAAM,OAAO,MAAM,IAAI,KAAK;AACjD,UAAO,MAAM,QAAQ,QAAQ,GAAG,QAAQ,SAAS;UAC3C;AACN,UAAO;;;CAGZ,CAAC,CACH;AAIH,MAAa,kBAAkB,WAAW,IACxC,QACA,eACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,cAAc,eAAe,OADZ,kBAAkB,kBACE;AAC3C,KAAI,CAAC,YAAY,GACf,QAAO,aAAa;EAAE,IAAI;EAAO,OAAO,YAAY;EAAO,EAAE,EAAE,QAAQ,KAAK,CAAC;CAG/E,MAAM,OAAO,OAAOC;CACpB,MAAM,aAAa,OAAO;CAC1B,MAAM,YAAY,OAAO;CACzB,MAAM,YAAY,OAAO;CAEzB,MAAM,EAAE,SAAS,cAAc;CAC/B,MAAM,YAAa,KAAa,SAAS;CACzC,MAAM,wBAAyB,KAAa,0BAA0B;CACtE,MAAM,wBAAyB,KAAa,SAAS,QAAS,KAAa,cAAc;AAEzF,KAAI,CAAC,QACH,QAAO,aAAa,EAAE,OAAO,oBAAoB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGrE,MAAM,eAAe;EAAC;EAAY;EAAS;EAAY,CAAC,QAAQ,UAAU,SAAU,KAAiC;AACrH,KAAI,aAAa,SAAS,EACxB,QAAO,aAAa,EAClB,OAAO,uDAAuD,aAAa,KAAK,KAAK,CAAC,+BACvF,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGrB,MAAM,OAAQ,KAAa,QAAQ;AACnC,KAAI,SAAS,OACX,QAAO,aAAa,EAAE,OAAO,2BAA2B,OAAO,KAAK,CAAC,gDAAgD,EAAE,EAAE,QAAQ,KAAK,CAAC;AAKzI,KAAI,QAAQ,KAAK,OAAO,QAAQ,CAAC,CAC/B,QAAO,aACL;EACE,OAAO,oBAAoB,QAAQ,mEAAmE,QAAQ;EAC9G,MAAM;EACP,EACD,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,gBAAgB,iBAAiB,OAAO,QAAQ,CAAC;AACvD,KAAI,CAAC,cACH,QAAO,aACL;EACE,OAAO,oBAAoB,QAAQ;EACnC,MAAM;EACP,EACD,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,2BAA2B,8BAA8B,OAAO,QAAQ,CAAC;CAC/E,MAAM,YAAY,yBAA0B,KAAa,6BAA6B;AACtF,KAAI,yBAAyB,CAAC,UAC5B,QAAO,aAAa;EAClB,SAAS;EACT,OAAO;EACP,0BAA0B;EAC1B,cAAc;EACd,MAAM,uFAAuF;EAC9F,EAAE,EAAE,QAAQ,KAAK,CAAC;CAMrB,MAAM,cAFmB,qBACY,CAAC,WACN,CAAC,MAC9B,OAAY,EAAE,cAAc,IAAI,aAAa,KAAK,QAAQ,aAAa,CACzE;AACD,KAAI,gBAAgB,YAAY,oBAAoB,UAAU,YAAY,oBAAoB,YAC5F,QAAO,aACL;EACE,OAAO,SAAS,QAAQ,sBAAsB,YAAY,gBAAgB;EAC1E,MAAM;EACP,EACD,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,aAAa,cAAc;CACjC,MAAM,mBAAmB,SAAS;CAClC,MAAM,iBAAiB,uBAAuB,kBAAkB,OAAO,cAAc,iBAAiB,CAAC;AACvG,KAAI,gBAAgB;AAClB,SAAO,OAAO,cAAc,wBAAwB,kBAAkB,4BAA4B;GAChG;GACA,QAAQ,eAAe;GACvB,UAAU,eAAe;GACzB,QAAQ,eAAe;GACxB,CAAC,CAAC;AACH,SAAO,aAAa,gBAAgB,EAAE,QAAQ,KAAK,CAAC;;CAGtD,MAAM,oBAAoBC,0BAAwB,QAAQ;CAC1D,MAAM,WAAW,mBAAmB,aAAa;CAEjD,MAAM,cAAc,kBAAkB,QAAQ,IAAI,QAAQ,MAAM,IAAI,CAAC;CACrE,MAAM,kBAAkB,4BAA4B,OAAO,QAAQ,CAAC;CACpE,MAAM,gBAAgB,kBAAkB,eAAe,gBAAgB,WAAW,GAAG;CACrF,MAAM,cAAc,eAAe,QAAQ,eAAe,WAAW,YAAY;CAEjF,MAAM,gBAAgBC,OAAK,aAAa,cAAc,WAAW,aAAa;AAC9E,KAAI,CAAC,WAAW,cAAc,CAC5B,KAAI;EACF,MAAM,UAAUC,UAAQ,QAAQ,SAAS;AACzC,SAAO,OAAO,cAAcC,YAC1B,wBAAwB,QAAQ,WAChC;GAAE,KAAK;GAAa,UAAU;GAAS,SAAS;GAAO,KAAK,6BAA6B,QAAQ,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,IAAI,QAAQ,MAAM,CAAC;GAAE,CAC1J,CAAC;UACK,OAAO;AACd,SAAO,aAAa;GAClB,OAAO,kCAAkC,QAAQ,IAAK,MAAgB;GACtE,MAAM,+DAA+D,UAAU;GAChF,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAIvB,MAAM,kBAAkBF,OAAK,eAAe,YAAY;CACxD,MAAM,2BAA2BA,OAAK,iBAAiB,sBAAsB;CAE7E,MAAM,oBAAoBA,OAAK,eAAe,SAAS;AACvD,KAAI,CAAC,WAAW,kBAAkB,EAAE;EAClC,MAAM,sBAAsBA,OAAK,aAAa,SAAS;AACvD,MAAI,WAAW,oBAAoB,CACjC,KAAI;AACF,UAAO,OAAO,cAAc,GAAG,qBAAqB,mBAAmB,EAAE,WAAW,MAAM,CAAC,CAAC;UACtF;;CAIZ,IAAI,WAAW,OAAO,SAAS,cAAc;AAC7C,KAAI,aAAa,CAAC,UAAU;AAI1B,SAAO,qBAAqB,aAAa,eAAe;GACtD;GACA,OALiB,aAAa,SAAS;GAMvC,MALgB,aAAa,eAAe;GAM5C,KAAK,aAAa;GACnB,CAAC;AACF,aAAW,OAAO,SAAS,cAAc;;AAE3C,KAAI,CAAC,SACH,QAAO,aAAa;EAClB,OAAO,iCAAiC,QAAQ;EAChD,MAAM;EACN;EACD,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGrB,MAAM,iBAAiB,OAAO,SAAS,SAAS,CAAC,KAC/C,OAAO,MAAM;EACX,YAAY,aAAa;GAAE,MAAM;GAAoB;GAAS;EAC9D,YAAY,aAAa;GAAE,MAAM;GAAoB;GAAS;EAC/D,CAAC,CACH;AACD,KAAI,eAAe,SAAS,WAAW;EACrC,MAAM,EAAE,YAAY;AACpB,SAAO,aAAa;GAClB,OAAO,uCAAuC,QAAQ,IAAI,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,QAAQ;GACtH,MAAM;GACN;GACD,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAErB,MAAM,EAAE,YAAY;CAEpB,MAAM,cAAc,SAAS,MAAM;AACnC,KAAI,eAAe,YAAY,aAAa,KAAK,WAC/C,QAAO,aAAa;EAClB,OAAO,4BAA4B,YAAY,aAAa,CAAC,QAAQ,QAAQ;EAC7E,MAAM;EACN;EACA,eAAe;EACf,aAAa,YAAY,aAAa;EACvC,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGrB,MAAM,gBAAgB,SAAS,MAAM,OAAO,UAAU;AACtD,KAAI,kBAAkB,EACpB,QAAO,aAAa;EAClB,OAAO;EACP,MAAM;EACN;EACD,EAAE,EAAE,QAAQ,KAAK,CAAC;CAMrB,IAAI,YAAY,OAAO,mBAAmB,eAAe,WAAW;CACpE,IAAI,WAAW,YAAY;AAC3B,KAAI,kBAAkB,QAAQ,gBAAgB,KAAK,cAAc,cAC/D,YAAW;CAGb,IAAI,gBAA+B;AACnC,KAAI,CAAC,UAAU;AAGb,UAAQ,IAAI,yBAAyB,QAAQ,uDAAuD;AACpG,MAAI;GACF,MAAM,EAAE,0BAA0B,OAAO,OAAO,cAAc,OAAO,uBAAmC;GACxG,MAAM,WAAW,OAAO,sBAAsB,cAAc;AAC5D,eAAY,SAAS,QAAQ;AAC7B,cAAW,SAAS,QAAQ,SAAS,MAAM,kBAAkB,QAAQ,SAAS,QAAQ,WAAW;AACjG,OAAI,SACF,SAAQ,IAAI,kCAAkC,SAAS,QAAQ,OAAO,aAAa,UAAU;YACpF,SAAS,QAAQ,SAAS,KAAK,kBAAkB,KAC1D,iBAAgB,WAAW,SAAS,QAAQ,OAAO,yBAAyB,cAAc;YACjF,SAAS,OAAO,SAAS,GAAG;AACrC,oBAAgB,SAAS,OAAO,MAAM;AACtC,YAAQ,KAAK,kCAAkC,SAAS,OAAO,KAAK,KAAK,GAAG;SAE5E,iBAAgB;WAEX,aAAkB;AACzB,mBAAgB,YAAY;AAC5B,WAAQ,KAAK,kCAAkC,YAAY,UAAU;;;AAIzE,KAAI,CAAC,UAAU;EAQb,MAAM,SAAS,gBACX,2BAA2B,cAAc,KACzC;AACJ,UAAQ,KAAK,6CAA6C,QAAQ,iBAAiB,SAAS;AAC5F,SAAO,aAAa;GAClB,SAAS;GACT,OAAO;GACP,SAAS,+BAA+B,QAAQ,iBAAiB,OAAO;GAExE,GAAI,gBAAgB,EAAE,eAAe,GAAG,EAAE;GAC3C,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAGrB,MAAM,SAAS,OAAO,UAAU,YAAY,KAC1C,OAAO,SAAS,aAAa,OAAO,cAAc,wBAAwB,SAAS,CAAC,CAAC,CACtF;AACD,qBAAoB,SAAS,cAAc,SAAS,8BAA8B;CAClF,MAAM,kBAAkB,wBAAwB,OAAO;AACvD,KAAI,gBAAgB,SAAS;AAC3B,sBAAoB,SAAS,cAAc,WAAW,gBAAgB,SAAS,sBAAsB;GACnG,QAAQ,gBAAgB;GACxB,MAAM,gBAAgB;GACvB,CAAC;AACF,SAAO,aAAa;GAClB,SAAS;GACT,SAAS;GACT,SAAS;GACT,OAAO,gBAAgB;GACvB,MAAM,gBAAgB;GACtB,YAAY;GACb,EAAE,EAAE,QAAQ,gBAAgB,QAAQ,CAAC;;AAExC,KAAI,gBAAgB,2BAA2B,CAAC,uBAAuB;AACrE,sBAAoB,SAAS,cAAc,WAAW,sCAAsC;GAC1F,QAAQ,gBAAgB;GACxB,MAAM,gBAAgB;GACvB,CAAC;AACF,SAAO,aAAa;GAClB,SAAS;GACT,SAAS;GACT,SAAS;GACT,yBAAyB;GACzB,MAAM,4BAA4B,gBAAgB,KAAK;GACvD,YAAY;GACb,EAAE,EAAE,QAAQ,gBAAgB,QAAQ,CAAC;;AAExC,qBAAoB,SAAS,cAAc,WAAW,0BAA0B;CAEhF,IAAI;AACJ,KAAI;AACF,eAAa,eAAe;GAC1B,OAAQ,KAAa;GACrB;GACA,UAAU,GAAG,KAAK,GAAG;GACtB,CAAC;UACK,KAAK;AACZ,SAAO,aAAa,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EAAE,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAGnG,MAAI,OAD4B,OAAO,cAAc,oBAAoB,WAAW,CAAC,MAC5D,gBAAgB;EACvC,MAAM,YAAY,OAAO,sBAAsB;AAC/C,MAAI,UAAU,WAAW,aAAa,UAAU,WAAW,SACzD,QAAO,aAAa;GAClB,SAAS;GACT,SAAS;GACT,SAAS;GACT,OAAO,wBAAwB,UAAU,OAAO;GAChD,MAAM;GACP,EAAE,EAAE,QAAQ,KAAK,CAAC;;CASvB,MAAM,sBAAsB,OAAO,uBAAuB,WAAW,CAAC,KACpE,OAAO,MAAM;EACX,YAAY,SAAS;GAAE,MAAM;GAAoB;GAAK;EACtD,kBAAkB;GAAE,MAAM;GAAoB,KAAK;GAAM;EAC1D,CAAC,CACH;AACD,KAAI,oBAAoB,SAAS,aAAa,oBAAoB,KAAK;EACrE,MAAM,MAAM,oBAAoB;AAChC,SAAO,aAAa;GAClB,SAAS;GACT,SAAS;GACT,SAAS;GACT,OAAO,IAAI;GACX,MAAM,IAAI,YAAY,SAAS,UAC3B,8FACA,IAAI,YAAY,SAAS,SACvB,gDACA;GACN,gBAAgB;IACd,UAAU,IAAI,SAAS;IACvB,OAAO,IAAI;IACX,MAAM,IAAI,YAAY;IACtB,QAAQ,IAAI,YAAY;IACzB;GACF,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAGrB,KAAI,CAAC,UAAU;AACb,sBAAoB,SAAS,mBAAmB,SAAS,0CAA0C,EAAE,eAAe,CAAC;EACrH,MAAM,cAAc,OAAO,wBAAwB,SAAS;GAAE;GAAe;GAAe,CAAC;AAC7F,MAAI,CAAC,YAAY,SAAS;AACxB,UAAO,OAAO,cAAc,wBAAwB,kBAAkB,uCAAuC;IAC3G;IACA,SAAS,YAAY;IACrB,cAAc,YAAY;IAC3B,CAAC,CAAC;AACH,OAAI,CAAC,WAAW;AACd,wBAAoB,SAAS,mBAAmB,WAAW,YAAY,QAAQ,KAAK,KAAK,EAAE;KACzF;KACA,cAAc,YAAY;KAC3B,CAAC;AACF,0BAAsB;KACpB,QAAQ;KACR,OAAO;KACP,SAAS,QAAQ,aAAa;KAC9B,SAAS,wCAAwC,QAAQ,aAAa;KACtE,SAAS,YAAY,QAAQ,KAAK,KAAK;KACxC,CAAC;AACF,WAAO,aAAa;KAClB,SAAS;KACT,SAAS;KACT,SAAS;KACT,OAAO,8BAA8B,QAAQ,mBAAmB,YAAY,QAAQ,KAAK,KAAK;KAC9F,MAAM,8BAA8B,QAAQ,+CAA+C,QAAQ;KACnG;KACD,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAErB,uBAAoB,SAAS,mBAAmB,WAAW,+CAA+C;IACxG;IACA,SAAS,YAAY;IACtB,CAAC;QAEF,qBAAoB,SAAS,mBAAmB,WAAW,kCAAkC,EAAE,eAAe,CAAC;OAGjH,qBAAoB,SAAS,mBAAmB,WAAW,kDAAkD,EAAE,eAAe,CAAC;AAGjI,KAAI,UAEF,SAAQ,KAAK,uCAAuC,QAAQ,aAAa,CAAC,wBAAwB;AAIpG,KAAI,qCADkB,gBAAgB,OAAO,OAAO,cAAc,gBAAgB,cAAc,CAAC,GAAG,MAC7C,KAAK,WAAW,yBAAyB,IAAI,WAAW,gBAAgB,EAG7H,QAAO,OAAO,IAAI,aAAa;EAC7B,MAAM,UAAU;AAChB,MAAI,WAAWA,OAAK,SAAA,OAAqB,CAAC,EAAE;AAE1C,UAAO,OAAO,cAAcE,YAAU,iBAAiB;IAAE,KAAK;IAAS,UAAU;IAAS,CAAC,CAAC;AAC5F,UAAO,OAAO,cAAcA,YAC1B,gGACA;IAAE,KAAK;IAAS,UAAU;IAAS,CACpC,CAAC;;AAEJ,MAAI,WAAWF,OAAK,SAAS,SAAS,CAAC,CACrC,QAAO,OAAO,cAAcE,YAAU,mBAAmB;GAAE,KAAK;GAAS,UAAU;GAAS,CAAC,CAAC;AAShG,MAAI,OALsB,OAAO,cAC/BA,YAAU,6BAA6B;GAAE,KAAK;GAAS,UAAU;GAAS,CAAC,CACxE,WAAW,MAAM,CACjB,YAAY,KAAK,CACrB,EACe;AACd,UAAO,OAAO,cAAcA,YAAU,gDAAgD,QAAQ,uBAAuB;IAAE,KAAK;IAAS,UAAU;IAAS,CAAC,CAAC;AACxI,SAAM,OAAO,CAAC,OAAO,EAAE;IAAE,KAAK;IAAS,UAAU;IAAM,OAAO;IAAU,CACjF,CAAC,OAAO;;GAEnB,CAAC,KAAK,OAAO,YAAY,OAAO,KAAK,CAAC;AAU1C,QAAO,uBACL,aACA,SACA,UACA,YACA,kBAAkB,QAAQ,aAAa,CAAC,SACzC,CAAC,KACA,OAAO,MAAM;EACX,YAAY,WAAW;AACrB,OAAI,OAAO,MACT,SAAQ,IAAI,8BAA8B,OAAO,QAAQ,gBAAgB,UAAU;AAErF,OAAI,OAAO,UACT,SAAQ,IAAI,2DAA2D,UAAU;;EAGrF,YAAY,QAAQ;AAClB,WAAQ,KAAK,gEAAgE,KAAK,WAAW,MAAM;;EAEtG,CAAC,CACH;AAMD,KAAI,WAAW,SAAS,CACtB,KAAI;AACF,mBAAiB,UAAU,UAAU;AACrC,UAAQ,IAAI,6CAA6C,UAAU;UAC5D,eAAoB;AAC3B,UAAQ,KAAK,gEAAgE,eAAe,WAAW,gBAAgB;;AAK3H,KAAI;EACF,MAAM,EAAE,oBAAoB,mCAAmC,2BAC7D,OAAO,OAAO,cAAc,OAAO,wBAAqC;EAC1E,MAAM,gBAAgB,uBAAuB,QAAQ,IAAI,kCAAkC,cAAc;AACzG,SAAO,OAAO,cAAc,mBAAmB,eAAe,SAAS;GACrE,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,QAAQ;GACR,YAAY;GACb,CAAC,CAAC;AACH,UAAQ,IAAI,yDAAyD,UAAU;UACxE,aAAkB;AACzB,UAAQ,KAAK,oEAAoE,aAAa,WAAW,cAAc;;AAGzH,KAAI,YAAY,kBACd,QAAO,OAAO,uBAAuB;EACnC;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CAIJ,MAAM,YAAYF,OAAK,eAAe,MAAM;CAC5C,MAAM,cAAc,WAAWA,OAAK,eAAe,YAAY,CAAC;AAEhE,QAAO,OAAO,cAAc,wBAAwB,kBAAkB,yBAAyB;EAC7F;EACA;EACA;EACA;EACD,CAAC,CAAC;CAEH,MAAM,iBAAiB,OAAO,2BAA2B,iBAAiB;AAC1E,QAAO,OAAO,cAAc,wBAAwB,kBAAkB,mCAAmC;EACvG;EACA,WAAW;EACZ,CAAC,CAAC;AACH,KAAI,CAAC,eAAe,eAAe;AACjC,SAAO,OAAO,cAAc,wBAAwB,kBAAkB,uBAAuB;GAC3F;GACA,QAAQ,eAAe;GACvB,WAAW;GACZ,CAAC,CAAC;AACH,SAAO,aAAa;GAClB,OAAO,eAAe,UAAU,0BAA0B;GAC1D,WAAW;GACZ,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAGrB,QAAO,OAAO,IAAI,aAAa;AAE7B,MAAI,OADkB,cAAc,iBAAiB,CACzC,QAAO,YAAY,iBAAiB;AAChD,UAAQ,IAAI,2CAA2C,mBAAmB;GAC1E,CAAC,KAAK,OAAO,YAAY,OAAO,KAAK,CAAC;AAUxC,KAAI,EAD+B,KAAa,8BAA8B,MAE5E,KAAI;EACF,MAAM,EAAE,QAAQ,cAAc,OAAO,OAAO,cAAcE,YAAU,0BAA0B;GAC5F,KAAK;GACL,UAAU;GACX,CAAC,CAAC;AACH,MAAI,UAAU,MAAM,EAAE;GACpB,MAAM,EAAE,QAAQ,YAAY,OAAO,OAAO,cAAcA,YAAU,wBAAwB;IACxF,KAAK;IACL,UAAU;IACX,CAAC,CAAC,aAAa,EAAE,QAAQ,IAAI,EAAE,CAAC;AACjC,UAAO,OAAO,cAAc,wBAAwB,kBAAkB,uCAAuC;IAC3G;IACA;IACA,WAAW,UAAU,MAAM;IAC5B,CAAC,CAAC;AAGH,qCAAkC,SAAS,UAAU,MAAM,CAAC;AAC5D,UAAO,aAAa;IAClB,OAAO,aAAa,cAAc;IAClC,MAAM;IACN;IACA,WAAW,UAAU,MAAM;IAC3B,UAAU,QAAQ,MAAM;IACxB,SAAS;KAAC;KAAU;KAAW;KAAgB;IAChD,EAAE,EAAE,QAAQ,KAAK,CAAC;;UAEd,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,UAAQ,KAAK,sDAAsD,QAAQ,IAAI,UAAU;;CAa7F,MAAM,cAAe,KAAa;CAClC,MAAM,oBACJ,gBAAgB,YAAY,gBAAgB,iBAAiB,gBAAgB,UAAU,cAAc;CACvG,IAAI,mBAA8D;AAClE,KAAI,sBAAsB,KAIxB,qBAAmB,OAHY,OAAO,QAAQ,YAC5C,kBAAkB,mBAAmB,YAAY,MAAM,oBAAoB,WAAW,CAAC,CACxF,EACkC,UAAU,oBAAoB;CAInE,MAAM,kBAAkB,OAAO,MAAgB,QAAkC,wBAAwB;EACvG;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;CAGF,MAAM,oBAAoB,YAAY;AACpC,QAAM,OAAO,WACX,UAAU,aAAa,SAAS,cAAc,CAAC,KAAK,OAAO,YAAY,OAAO,KAAK,CAAC,CACrF;;CAGH,MAAM,oBAAoB,OAAO,6BAA6B;EAC5D;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC;AACF,KAAI,kBAAmB,QAAO;AAG9B,QAAO,OAAO,cAAc,wBAAwB,kBAAkB,8BAA8B;EAClG;EACA;EACA;EACD,CAAC,CAAC;CACH,IAAI;AACJ,KAAI;AACF,sBAAoB,SAAS,SAAS,SAAS,6BAA6B,EAAE,eAAe,CAAC;AAC9F,eAAa,OAAO,OAAO,cAAc,gBACvC,kBAAkB;GAChB;GACA,OAAO;GACP,SAAS;GACT;GACD,CAAC,EACF,cACD,CAAC;AACF,sBAAoB,SAAS,SAAS,WAAW,oCAAoC;GACnF;GACA;GACD,CAAC;UACK,OAAY;EACnB,MAAM,SAAS,OAAO,OAAO,UAAU,OAAO,WAAW,GAAG;AAC5D,MAAI,OAAO,SAAS,8BAA8B,UAAU,IAAI,OAAO,SAAS,iBAAiB,EAAE;GACjG,MAAM,oBAAoB,OAAO,wBAAwB,SAAS;IAAE;IAAe;IAAe,CAAC;AACnG,uBAAoB,SAAS,mBAAmB,WAAW,kBAAkB,QAAQ,SAAS,IAAI,kBAAkB,QAAQ,KAAK,KAAK,GAAG,OAAO,MAAM,EAAE;IACtJ;IACA,YAAY,OAAO;IACpB,CAAC;AACF,uBAAoB,SAAS,SAAS,WAAW,OAAO,MAAM,IAAI,6BAA6B,WAAW;IACxG;IACA,YAAY,OAAO;IACpB,CAAC;AACF,yBAAsB;IACpB,QAAQ;IACR,OAAO;IACP,SAAS,QAAQ,aAAa;IAC9B,SAAS,wCAAwC,QAAQ,aAAa;IACtE,SAAS,kBAAkB,QAAQ,SAAS,IAAI,kBAAkB,QAAQ,KAAK,KAAK,GAAG,OAAO,MAAM;IACrG,CAAC;AACF,UAAO,aAAa;IAClB,SAAS;IACT,SAAS;IACT,SAAS;IACT,OAAO,kBAAkB,QAAQ,SAAS,IACtC,8BAA8B,QAAQ,mBAAmB,kBAAkB,QAAQ,KAAK,KAAK,KAC7F,OAAO,MAAM;IACjB,MAAM,8BAA8B,QAAQ,+CAA+C,QAAQ;IACnG,aAAa;IACb,YAAY,OAAO;IACpB,EAAE,EAAE,QAAQ,KAAK,CAAC;;AAErB,sBAAoB,SAAS,SAAS,WAAW,OAAO,MAAM,IAAI,6BAA6B,WAAW;GACxG;GACA,YAAY,OAAO;GACpB,CAAC;AACF,SAAO,aAAa;GAClB,SAAS;GACT,SAAS;GACT,SAAS;GACT,OAAO,OAAO,MAAM,IAAI,6BAA6B;GACrD,YAAY,OAAO;GACpB,EAAE,EAAE,QAAQ,KAAK,CAAC;;CAOrB,MAAM,eAAe;CACrB,MAAM,gBAAgBF,OAAK,SAAS,EAAE,aAAa,UAAU,aAAa;AAC1E,QAAO,OAAO,cAAc,MAAM,eAAe,EAAE,WAAW,MAAM,CAAC,CAAC;AAEtE,oBAAmB;EACjB,IAAI;EACJ;EACA,GAAI,mBAAmB,EAAE,SAAS,kBAAkB,GAAG,EAAE;EACzD,OAAO;EACP,QAAQ;EACR,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,WAAW;EACX;EACA,cAAc,aAAa,KAAA;EAC5B,CAAC;AACF,6BAA4B,SAAS,eAAe,aAAa;AACjE,QAAO,OAAO,cAAc,wBAAwB,cAAc,mCAAmC;EACnG;EACA;EACA;EACA;EACD,CAAC,CAAC;AAEH,QAAO,OAAO,cAAc,mBAAmB,CAAC;AAgBhD,QAAO,kCAAkC;EACvC,IAAI;EACJ;EACA,WAAW;EACX;EACA,GAAI,mBAAmB,EAAE,SAAS,kBAAkB,GAAG,EAAE;EACzD,OAAO;EACP,QAAQ;EACR,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,cAAc,aAAa,KAAA;EAC5B,EAAE;EACD,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GACP,SAAS;GACT;GACA,OAAO;IACL,IAAI;IACJ;IACA,WAAW;IACX,QAAQ;IACR,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC;IACA,GAAI,mBAAmB,EAAE,SAAS,kBAAkB,GAAG,EAAE;IAC1D;GACF;EACF,CAAC;AACF,KAAI;AAAE,uBAAqB,CAAC,WAAW,SAAS;GAAE,QAAQ;GAAe,iBAAiB;GAAe,CAAC;SAAU;AACpH,wBAAuB;AACvB,QAAO,aAAa;EAClB,SAAS;EACT,SAAS,sBAAsB;EAC/B;EACA;EACA,YAAY;EACb,CAAC;EACF,CAAC,CACJ;;;;;;;;;AC1xBD,MAAa,mBAAmB,MAAM,SACpC,gBACA,qBACA,2BACA,uBACA,oBACA,kBACA,oBACA,4BACA,oBACA,+BACA,8BACA,yBACA,4BACA,qBACA,kCACA,yCACA,kCACA,sBACA,sBACA,uBACA,oBACA,uBACA,uBACA,qBACA,uBACA,0BACA,sBACA,uBACA,uBACA,6BACA,gCACA,uBACA,mBACA,iBACA,2BACA,wBACA,yBACA,4BACA,2BACA,4BACA,8BACA,6BACA,iCACD;;;SC9HgG;AAGjG,MAAM,oBAAwC,CAAC,aAAa,YAAY;;;;;;;;;;AAyCxE,SAAgB,sBAAsB,eAAuB,iBAAmC;CAC9F,MAAM,MAAM,sBAAsB,cAAc;AAChD,KAAI,CAAC,IAAK,QAAO,EAAE;CAEnB,MAAM,WAAW,IAAI,IAAI,IAAI,KAAK,MAAM,KAAI,MAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;CAG5D,MAAM,qBAAqB,IAAI,KAAK,MACjC,QAAO,MAAK,EAAE,SAAS,YAAY,EAAE,SAAS,gBAAgB,CAC9D,KAAI,MAAK,EAAE,GAAG;CAEjB,MAAM,YAAsB,EAAE;AAE9B,MAAK,MAAM,eAAe,oBAAoB;EAC5C,MAAM,YAAY,SAAS,IAAI,YAAY;AAC3C,MAAI,CAAC,UAAW;AAChB,MAAI,kBAAkB,SAAS,UAAU,OAAO,CAAE;AAelD,MAZiB,IAAI,KAAK,MACvB,QAAO,MAAK,EAAE,SAAS,YAAY,EAAE,OAAO,YAAY,CACxD,KAAI,MAAK,EAAE,KAEsB,CAAC,OAAM,cAAa;GACtD,MAAM,UAAU,SAAS,IAAI,UAAU;AACvC,OAAI,CAAC,QAAS,QAAO;AAErB,OAAI,cAAc,gBAAiB,QAAO;AAC1C,UAAO,kBAAkB,SAAS,QAAQ,OAAO;IAG5B,CACrB,WAAU,KAAK,YAAY;;AAI/B,QAAO;;;;sBCxFwF;iBACf;kBACrB;eACU;oBACH;SAKjC;AAYnC,SAAgB,qCACd,SACA,OACA,OAA2C,EAAE,EACpB;CACzB,MAAM,aAAa,KAAK,cAAc;CACtC,MAAM,iBAAiB,KAAK,kBAAkB;CAC9C,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,WAAW,KAAK,YAAY;CAClC,MAAM,gBAAgB,KAAK,iBAAiB;CAC5C,MAAM,SAAS,KAAK,UAAU;CAC9B,MAAM,kBAAkB,KAAK,mBAAmB;CAEhD,MAAM,SAAS,YAAY,CAAC,OAAO;AACnC,KAAI,CAAC,OAAO,WAAW,QAAS,QAAO;CAEvC,MAAM,UAAU,eAAe,QAAQ;AACvC,KAAI,CAAC,QAAS,QAAO;CACrB,MAAM,gBAAgBG,OACpB,QAAQ,aACR,cACA,WAAW,QAAQ,aAAa,GACjC;AACD,KAAI,CAAC,OAAO,cAAc,CAAE,QAAO;CAEnC,MAAM,MAAM,SAAS,cAAc;AACnC,KAAI,CAAC,8BAA8B,QAAQ,KAAK,KAAK,SAAS,CAAE,QAAO;CAEvE,MAAM,SAAS,OAAO,MAAM,kBAAkB,GAAG;AACjD,KAAI,CAAC,OAAO,CAAC,OAAQ,QAAO;CAC5B,MAAM,OAAO,IAAI,KAAK,MAAM,MAAK,cAAa,UAAU,OAAO,OAAO;AACtE,KAAI,CAAC,KAAM,QAAO;CAElB,MAAM,YAAY,cAAc,cAAc;CAC9C,MAAM,WAAW,OAAO;EACtB,MAAM;EACN;EACA,KAAK,oBAAoB,QAAQ,EAAE,oBAAoB;EACvD,uBAAuB,OAAO,WAAW;EAC1C,EAAE,MAAM,OAAO,YAAY,UAAU;AAEtC,KAAI,SAAS,WAAW,UACtB,iBAAgB,eAAe,QAAQ,SAAS,MAAM,SAAS,IAAI,SAAS,OAAO;AAGrF,QAAO;;AAGT,eAAsB,qCACpB,SACA,OACA,OAA2C,EAAE,EAC9B;AACf,KAAI;EACF,MAAM,WAAW,qCAAqC,SAAS,OAAO,KAAK;AAC3E,MAAI,SACF,SAAQ,IAAI,mDAAmD,QAAQ,IAAI,SAAS,SAAS;UAExF,KAAK;AACZ,UAAQ,KAAK,0DAA0D,QAAQ,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;;;;;eCtE1E;eACU;AAE1E,MAAa,YAAY,UAAU,KAAK;AACxC,MAAa,gBAAgB,UAAU,SAAS;AAQhD,SAAgB,4BAA4B,MAAkD;AAC5F,QAAO,SAAS,kBAAkB,SAAS,gBAAgB,SAAS;;AAGtE,SAAgB,oBAAoB,MAA+C;AACjF,KAAI,SAAS,YAAY,SAAS,eAAgB,QAAO;AACzD,KAAI,SAAS,UAAU,SAAS,aAAc,QAAO;AACrD,KAAI,SAAS,WAAW,SAAS,cAAe,QAAO;;AAIzD,SAAgB,sBAAsB,MAA6D;CACjG,MAAM,WAAW,MAAM,MAAM,IAAI,CAAC;AAClC,KAAI,aAAa,SAAU,QAAO;AAClC,KAAI,aAAa,OAAQ,QAAO;AAChC,KAAI,aAAa,OAAQ,QAAO;AAChC,QAAO;;AAYT,SAAgB,uCACd,MACA,MACA,YACA,cAC6D;AAC7D,KAAI,CAAC,4BAA4B,KAAK,CACpC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO,4BAA4B;EAAQ;AAE9E,KAAI,CAAC,KAAK,WAAW,CAAC,KAAK,OACzB,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAA+B;AAEzE,KAAI,KAAK,WAAW,YAAY,KAAK,WAAW,SAC9C,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAAmC;AAE7E,KAAI,CAAC,KAAK,WAAW,CAAC,KAAK,KACzB,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAA6B;CAGvE,MAAM,oBAAoB,KAAK,QAAQ,aAAa;AAEpD,KADqB,sBAAsB,KAAK,KAChC,KAAK,KACnB,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAAkC;AAE5E,KAAI,CAAC,cAAc,WAAW,OAAO,KAAK,QACxC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAAuB;AAEjE,KAAI,WAAW,QAAQ,aAAa,KAAK,kBACvC,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAAsC;AAEhF,KAAI,sBAAsB,WAAW,KAAK,KAAK,KAC7C,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAAwC;AAElF,KAAI,WAAW,WAAW,aAAa,WAAW,WAAW,WAC3D,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAA2B;AAErE,KAAI,cAAc,gBAAgB,aAAa,aAAa,aAAa,KAAK,kBAC5E,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAAwC;AAElF,KAAI,cAAc,mBAAmB,KAAK,cAAc,aAAa,gBACnE,QAAO;EAAE,IAAI;EAAO,QAAQ;EAAK,OAAO;EAAqC;AAG/E,QAAO,EAAE,IAAI,MAAM;;AAIrB,MAAa,eAAe,OAAO,IAAI,aAAa;CAElD,MAAM,OAAO,QAAO,OADG,kBAAkB,mBACb;AAC5B,KAAI;AACF,SAAO,OAAO,KAAK,MAAM,KAAK,GAAG,EAAE;SAC7B;AACN,SAAO,EAAE;;EAEX;AAQF,MAAM,iBAAiB,qBAAqB;AAI5C,MAAa,uCAAuB,IAAI,KAAa;AAarD,SAAgB,uBAAuB,SAAiB,SAAkD;CACxG,MAAM,UAAU,eAAe,IAC7B,SACA,YAAY;EAEV,MAAM,cAAc,uBADA,kBAAkB,QAAQ,IAAI,QAAQ,MAAM,IAAI,CAAC,GACd;EACvD,MAAM,EAAE,uBAAuB,MAAM,OAAO;AAC5C,QAAM,mBACJ,SACA,aACA,SAAS,cACT,SAAS,oBAAoB,EAAE,mBAAmB,QAAQ,mBAAmB,GAAG,KAAA,EACjF;AACD,UAAQ,IAAI,8CAA8C,UAAU;EAMpE,MAAM,EAAE,2BAA2B,MAAM,OAAO;EAChD,MAAM,WAAW,MAAM,uBAAuB,QAAQ;AACtD,MAAI,SAAS,SAAS,EACpB,SAAQ,IACN,4BAA4B,SAAS,OAAO,oBAAoB,QAAQ,MACtE,SAAS,KAAK,MAAM,GAAG,EAAE,QAAQ,GAAG,EAAE,SAAS,CAAC,KAAK,KAAK,CAC7D;KAGJ,QAAQ,QAAQ,MAAM,2CAA2C,QAAQ,IAAI,IAAI,CACnF;AACD,KAAI,CAAC,QACH,SAAQ,IAAI,4CAA4C,QAAQ,sBAAsB;AAExF,QAAO;;AAGT,SAAgB,uBAAuB,aAA6B;CAElE,MAAM,WAAW,4BAA4B,GAD1B,YAAY,IACsB;AACrD,KAAI,SAAU,QAAO,SAAS;AAC9B,QAAOC,OAAK,SAAS,EAAE,WAAW;;;;aCrK8G;eAGxE;oBACgF;SAErF;sBAEC;WAIhB;AActD,MAAM,sBAAsB,WAAW,IACrC,OACA,oBACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,EACJ,wBACA,oCACE,OAAO,OAAO,cAAc,OAAO,6BAA2C;AAKlF,QAAO,aAAa;EAClB,aAAa,OAJkB,OAAO,cAAc,wBAAwB,CAAC;EAK7E,UAAU,OAJsB,OAAO,cAAc,iCAAiC,CAAC;EAKxF,CAAC;EACF,CAAC,CACJ;AAKD,MAAM,+BAA+B,WAAW,IAC9C,QACA,8BACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,EACJ,mBACA,WACA,uBACE,OAAO,OAAO,cAAc,OAAO,6BAA2C;CAClF,MAAM,EAAE,kBAAkB,OAAO,OAAO,cAAc,OAAO,uBAA4B;CAEzF,MAAM,cAAc,mBAAmB;CACvC,MAAM,UAA+F,EAAE;AAEvG,MAAK,MAAM,cAAc,aAAa;EACpC,MAAM,OAAO,WAAW;EACxB,IAAI,SAAS;AAEb,MAAI,OAAO,OAAO,cAAc,UAAU,KAAK,CAAC,CAM9C,UAAS,OAJiB,YADN,mBAAmB,KACU,CAAC,CAAC,KACjD,OAAO,GAAG,KAAK,EACf,OAAO,YAAY,OAAO,QAAQ,MAAM,CAAC,CAC1C;AAIH,gBAAc,KAAK;AACnB,UAAQ,KAAK;GAAE;GAAM;GAAQ,gBAAgB;GAAO,cAAc;GAAM,CAAC;;CAK3E,IAAI,sBAAsB;AAC1B,KAAI;EACF,MAAM,WAAW,oBAAoB;AACrC,OAAK,MAAM,OAAO,OAAO,KAAK,SAAS,CACrC,KAAI,SAAS,KAAK,iBAAiB,aAAa;AAC9C,uBAAoB,KAAK,EAAE,cAAc,WAAW,CAAC;AACrD;;UAGG,GAAG;AACV,UAAQ,MAAM,oCAAoC,EAAE;;AAGtD,QAAO,aAAa;EAClB,SAAS;EACT,SAAS,SAAS,QAAQ,OAAO,sBAAsB,oBAAoB;EAC3E;EACA;EACD,CAAC;EACF,CAAC,CACJ;AAMD,MAAM,2BAA2B,WAAW,IAC1C,QACA,yBACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,OAAO,OAAO;CACpB,MAAM,aAAa,OAAO;CAC1B,MAAM,EAAE,YAAY,SAAS,QAAQ,UAAU;CAQ/C,MAAM,mBAAmB;EAAC;EAAU;EAAQ;EAAS;EAAW;EAAO;EAAO;AAC9E,KAAI,CAAC,iBAAiB,SAAS,WAAW,CACxC,QAAO,aACL,EAAE,OAAO,uBAAuB,WAAW,WAAW,iBAAiB,KAAK,KAAK,IAAI,EACrF,EAAE,QAAQ,KAAK,CAChB;AAIH,KAAI,CAAC,UAAU,CAAC,CAAC,UAAU,SAAS,CAAC,SAAS,OAAO,CACnD,QAAO,aACL,EAAE,OAAO,mBAAmB,OAAO,iCAAiC,EACpE,EAAE,QAAQ,KAAK,CAChB;AAIH,KAAI,CAAC,QACH,QAAO,aAAa,EAAE,OAAO,uBAAuB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAGxE,MAAM,oBAAoB,QAAQ,aAAa;AAC/C,SAAQ,IAAI,sBAAsB,WAAW,aAAa,OAAO,OAAO,oBAAoB;AAI5F,KAAI,eAAe,SAAS;EAC1B,MAAM,EAAE,+BAA+B,OAAO,OAAO,cAAc,OAAO,uCAAqD;AAK/H,MAAI,OAJoB,2BAA2B,mBAAmB;GAC5D;GACR;GACD,CAAC,CAEA,SAAQ,IAAI,6DAA6D,oBAAoB;;AAMjG,KAAI,eAAe,WAAW,qBAAqB,IAAI,kBAAkB,EAAE;AACzE,UAAQ,IAAI,sBAAsB,kBAAkB,uEAAuE;AAC3H,SAAO,aAAa;GAClB,SAAS;GACT;GACA,SAAS;GACT;GACA;GACA,eAAe;GAChB,CAAC;;CAIJ,MAAM,SAAgC,EAAE;AAExC,SAAQ,YAAR;EACE,KAAK;AACH,UAAO,eAAe,WAAW,WAAW,WAAW;AACvD,OAAI,MAAO,QAAO,cAAc;AAChC;EAEF,KAAK;AACH,UAAO,aAAa;AACpB,OAAI,MAAO,QAAO,YAAY;AAC9B;EAEF,KAAK;AACH,UAAO,cAAc,WAAW,WAAW,WAAW;AACtD;EAEF,KAAK;AACH,UAAO,gBAAgB;AACvB,OAAI,MAAO,QAAO,eAAe;AACjC;EAEF,KAAK;AACH,UAAO,YAAY;AACnB,OAAI,MAAO,QAAO,WAAW;AAC7B,OAAI,WAAW,SACb,QAAO,gBAAgB;AAEzB;EAEF,KAAK;AACH,OAAI,WAAW,SACb,QAAO,gBAAgB;AAEzB;;CAIJ,MAAM,gBAAgBC,oBAAoB,mBAAmB,OAAO;AACpE,KAAI,eAAe,aAAa,WAAW,SAAU,QAAO,OAAO,cAAc,qCAAqC,mBAAmB,MAAM,CAAC;AAIhJ,QAAO,OAAO,QAAQ,YAAY;AAChC,MAAI;GACF,MAAM,EAAE,oBAAoB,mBAAmB,8BAC7C,MAAM,OAAO;GAEf,MAAM,aADU,4BAA4B,kBAClB,EAAE;GAC5B,MAAM,cAAc,aAChB,mBAAmB,GAAG,WAAW,SAAgC,YAAY,kBAAkB,GAC/F,mBAAmB,GAAG,WAAW,QAA+B;AACpE,yBAAsB,aAAa;IACjC,OAAO;IACP,+BAAc,IAAI,MAAM,EAAC,aAAa;IACvC,CAAC;AACF,WAAQ,IAAI,0BAA0B,YAAY,UAAU;AAS5D,WAAQ,IAAI,gEAAgE,YAAY,qBAAqB;AAG7G,OAAI,YAAY;IACd,MAAM,cAAc,0BAA0B,GAAG,WAAW,SAAS,kBAAkB;AACvF,sBAAkB,YAAY,aAAa;KACzC,YAAY;KACZ,YAAY,KAAA;KACZ,WAAW;KACX,iBAAiB;KAClB,CAAC;AACF,YAAQ,IAAI,gDAAgD,YAAY,IAAI,WAAW,GAAG;;GAI5F,MAAM,EAAE,kCAAkC,MAAM,OAAO;AAOvD,OAAI,MANkB,OAAO,WAAW,8BACtC,mBACA,GAAG,WAAW,SACd,WAAW,WAAW,cAAc,UACpC,WAAW,WAAW,YAAY,UACnC,CAAC,CAEA,SAAQ,IAAI,2CAA2C,WAAW,SAAS,kBAAkB,KAAK,SAAS;WAEtG,KAAK;AACZ,WAAQ,MAAM,uDAAuD,IAAI;;GAE3E;AAIF,KAAI,eAAe,YAAY,WAAW,SACxC,QAAO,OAAO,QAAQ,YAAY;AAChC,MAAI;GACF,MAAM,UAAU,4BAA4B,kBAAkB;AAC9D,OAAI,SAAS;IACX,MAAM,gBAAgBC,OACpB,QAAQ,aACR,cACA,WAAW,kBAAkB,aAAa,GAC3C;AACD,QAAI,WAAW,cAAc,EAAE;KAC7B,MAAM,EAAE,wBAAwB,MAAM,OAAO;KAC7C,MAAM,EAAE,SAAS,MAAM,OAAO,WAAW,oBAAoB,cAAc,CAAC;AAC5E,yBAAoB,mBAAmB,EAAE,kBAAkB,MAAM,CAAC;AAClE,aAAQ,IAAI,mDAAmD,KAAK,UAAU,GAAG,EAAE,CAAC,OAAO,oBAAoB;;;WAG5G,KAAK;AACZ,WAAQ,MAAM,8DAA8D,kBAAkB,IAAI,IAAI;;GAExG;AAIJ,KAAI,eAAe,aAAa,WAAW,SACzC,QAAO,OAAO,QAAQ,YAAY;AAChC,MAAI;GACF,MAAM,EAAE,sBAAsB,MAAM,OAAO;GAG3C,MAAM,UADY,OAAO,MAAM,kBAAkB,IACtB,MAAM;GAEjC,MAAM,UAAU,4BAA4B,kBAAkB;AAC9D,OAAI,SAAS;IACX,MAAM,gBAAgBA,OACpB,QAAQ,aACR,cACA,WAAW,kBAAkB,aAAa,GAC3C;AACD,QAAI,WAAW,cAAc,EAAE;AAC5B,WAAM,OAAO,WAAW,kBAAkB,QAAQ,YAAY,mBAAmB,QAAQ,UAAU,cAAc,CAAC;AAGnH,SAAI;MACF,MAAM,WAAW,MAAM,OAAO,WAAW,cAAc,eAAe,OAAO,CAAC;MAC9E,MAAM,gBAAgB,MAAM,OAAO,WAAW,uBAAuB,QAAQ,eAAe,aAAa,UAAU,MAAM,CAAC;AAC1H,UAAI,cAEF,KAAI;OACF,MAAM,iBAAiB,sBAAsB,eAAe,cAAc;AAC1E,WAAI,eAAe,SAAS,GAAG;AAC7B,gBAAQ,IACN,eAAe,kBAAkB,2BAA2B,cAAc,KAAK,eAAe,KAAK,KAAK,GACzG;QACD,MAAM,cAAc,SAAS,kBAAkB,aAAa;QAC5D,MAAM,MAAM,sBAAsB,cAAc;QAChD,MAAM,kBAAkB,eACrB,KAAK,OAAO;SACX,MAAM,KAAK,KAAK,KAAK,MAAM,MAAM,MAAM,EAAE,OAAO,GAAG;AACnD,gBAAO,KAAK,IAAI,GAAG,MAAM,KAAK,IAAI,GAAG;UACrC,CACD,KAAK,KAAK;AAEb,cAAM,aAAa,aAAa,sBADM,eAAe,SAAS,IAAI,MAAM,GAAG,mCAAmC,cAAc,KAAK,gBAAgB,oCACzG,CAAC,OAAO,QAAiB;SAC/D,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC/D,iBAAQ,IACN,8BAA8B,YAAY,yBAAyB,SACpE;UACD;;eAEG,SAAkB;OACzB,MAAM,SAAS,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,QAAQ;AAC3E,eAAQ,KAAK,gDAAgD,SAAS;;cAGnE,SAAkB;MACzB,MAAM,SAAS,mBAAmB,QAAQ,QAAQ,UAAU,OAAO,QAAQ;AAC3E,cAAQ,KAAK,0CAA0C,SAAS;;;;WAI/D,KAAK;AACZ,WAAQ,MAAM,uDAAuD,IAAI;;GAE3E;AAOJ,KAAI,eAAe,UAAU,WAAW,SACtC,QAAO,OAAO,QAAQ,YAAY;AAChC,MAAI;GACF,MAAM,UAAU,4BAA4B,kBAAkB;AAC9D,OAAI;QAME,WALkBA,OACpB,QAAQ,aACR,cACA,WAAW,kBAAkB,aAAa,GAEhB,CAAC,EAAE;AAC7B,yBAAoB,mBAAmB,EAAE,YAAY,UAAU,CAAC;KAChE,MAAM,EAAE,mBAAmB,MAAM,OAAO;AAExC,YAAM,MADc,gBAAgB,EACxB,YAAY;MACtB,MAAM;MACN,4BAAW,IAAI,MAAM,EAAC,aAAa;MACnC,SAAS,EAAE,SAAS,mBAAmB;MACxC,CAAQ;AACT,aAAQ,IAAI,sBAAsB,kBAAkB,mDAAmD;;;WAGpG,KAAK;AACZ,WAAQ,MAAM,qDAAqD,kBAAkB,IAAI,IAAI;;GAE/F;AAUJ,KAAI,eAAe,WAAW,WAAW,SACvC,wBAAuB,kBAAkB;AAK3C,KAAI,WAAW,YAAY,eAAe,UACxC,KAAI;EACF,MAAM,UAAU,4BAA4B,kBAAkB;AAC9D,MAAI,SAAS;GACX,MAAM,gBAAgBA,OACpB,QAAQ,aACR,cACA,WAAW,kBAAkB,aAAa,GAC3C;AAED,+BAA4B,mBADb,WAAW,cAAc,GAAG,gBAAgB,KAAA,EACL,CAAC,OAAO,QAAiB;IAC7E,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC/D,YAAQ,KACN,2CAA2C,kBAAkB,wBAAwB,SACtF;KACD;AACF,WAAQ,IACN,sBAAsB,WAAW,0BAA0B,kBAAkB,sBAC9E;;UAEI,KAAc;EACrB,MAAM,SAAS,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAC/D,UAAQ,KAAK,sEAAsE,OAAO;;AAM9F,KAAI,eAAe,WAAW,WAAW,UAAU;AACjD,SAAO,OAAO,QAAQ,YAAY;AAChC,OAAI;IAGF,MAAM,QADe,oBAAoB,CAAC,oBACd;AAC5B,QAAI,OAAO;KAET,MAAM,UAAU,MAAM,MAAM,6CAA6C;AACzE,SAAI,SAAS;MACX,MAAM,GAAG,OAAO,MAAM,YAAY;MAClC,MAAM,cAAc,sBAAsB,SAAS,gCAAgC;AACnF,YAAM,cACJ,MACA;OAAC;OAAO,SAAS,MAAM,GAAG,KAAK,UAAU,SAAS;OAAY;OAAW,QAAQ;OAAc,EAC/F,EAAE,UAAU,SAAS,CACtB;AACD,cAAQ,IAAI,sDAAsD,QAAQ;;;YAGvE,KAAU;AACjB,YAAQ,KAAK,4DAA4D,IAAI,UAAU;;IAEzF;AAGF,MAAI,OAAO,aAAa,CAAC,SAAS,WAAW,CAC3C,QAAO,OAAO,QAAQ,YAAY;AAChC,OAAI;IACF,MAAM,cAAc,SAAS,kBAAkB,aAAa;IAC5D,MAAM,EAAE,kBAAkB,MAAM,OAAO;IACvC,MAAM,EAAE,cAAc,YAAY,sBAAsB,MAAM,OAAO;AAErE,QAAI,MAAM,OAAO,WAAW,cAAc,YAAY,CAAC,EAAE;AAGvD,WAAM,aAAa,aAAa,2UAD6T,kBAAkB,4EAA4E,QACjZ;AAC1C,aAAQ,IAAI,kDAAkD,cAAc;UAG5E,SAAQ,IAAI,iCAAiC,YAAY,oEAAoE;YAExH,KAAU;AACjB,YAAQ,KAAK,oEAAoE,IAAI,UAAU;;IAEjG;;AAIN,KAAI,eAAe,aAAa,WAAW,YAAY,WAAW,WAChE,QAAO,OAAO,QAAQ,YAAY;AAChC,MAAI;GACF,MAAM,UAAU,4BAA4B,kBAAkB;GAC9D,MAAM,gBAAgB,UAClBA,OAAK,QAAQ,aAAa,cAAc,WAAW,kBAAkB,aAAa,GAAG,GACrF,KAAA;GACJ,MAAM,EAAE,iCAAiC,MAAM,OAC7C;GAEF,MAAM,SAAS,MAAM,OAAO,WAAW,6BAA6B;IAClE,SAAS;IACT,SAAS,WAAW,WAAW,WAAW;IAC1C;IACA;IACA,OAAO,cAAc;IACtB,CAAC,CAAC;AACH,WAAQ,IACN,4DAA4D,kBAAA,aAC5C,OAAO,gBAAgB,OAAO,cAAc,OAAO,iBAAiB,OAAO,cAAc,OAAO,gBAAgB,GACjI;WACM,KAAU;AACjB,WAAQ,KAAK,iEAAiE,IAAI,UAAU;;GAE9F;CAIJ,MAAM,YAAY,oBAAoB,WAAW;AACjD,KAAI,UACF,KAAI,WAAW,SACb,QAAO,WAAW,OAAO;EACvB,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GAAE,MAAM;GAAW,SAAS;GAAmB;EACzD,CAAC;KAEF,QAAO,WAAW,OAAO;EACvB,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GAAE,MAAM;GAAW,SAAS;GAAmB,OAAO,SAAS,GAAG,WAAW;GAAU;EACjG,CAAC;AAIN,QAAO,aAAa;EAClB,SAAS;EACT;EACA,SAAS;EACT;EACA;EACA,eAAe;EAChB,CAAC;EACF,CAAC,CACJ;AAKD,MAAM,qCAAqC,WAAW,IACpD,QACA,qCACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,EAAE,uBAAuB,OAAO,OAAO,cAAc,OAAO,iCAA+C;CACjH,MAAM,UAAU,oBAAoB;AAEpC,QAAO,aAAa;EAClB,SAAS;EACT,cAAc,QAAQ;EACtB,WAAW,QAAQ;EACnB,SAAS,cAAc,QAAQ,aAAa;EAC7C,CAAC;EACF,CAAC,CACJ;AAKD,MAAM,8BAA8B,WAAW,IAC7C,OACA,6BACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,EAAE,oCACN,OAAO,OAAO,cAAc,OAAO,6BAA2C;AAEhF,QAAO,aAAa,OADO,OAAO,cAAc,iCAAiC,CAAC,CAClD;EAChC,CAAC,CACJ;AAID,MAAM,0BAA0B,WAAW,IACzC,QACA,+BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,QAAO,OADS,WAAW,QACb;AACpB,QAAO,aACL,EAAE,OAAO,qDAAqD,KAAK,qCAAqC,EACxG,EAAE,QAAQ,KAAK,CAChB;EACD,CAAC,CACJ;AAID,MAAM,2BAA2B,WAAW,IAC1C,QACA,gCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,QAAO,OADS,WAAW,QACb;AACpB,QAAO,aACL,EAAE,OAAO,8DAA8D,KAAK,gEAAgE,EAC5I,EAAE,QAAQ,KAAK,CAChB;EACD,CAAC,CACJ;AAID,MAAM,0BAA0B,WAAW,IACzC,QACA,+BACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,QAAO,OADS,WAAW,QACb;AACpB,QAAO,aACL,EAAE,OAAO,+DAA+D,KAAK,uCAAuC,EACpH,EAAE,QAAQ,KAAK,CAChB;EACD,CAAC,CACJ;AAID,MAAM,kCAAkC,WAAW,IACjD,QACA,wCACA,YAAY,OAAO,IAAI,aAAa;CAElC,MAAM,QAAO,OADS,WAAW,QACb;CACpB,MAAM,OAAO,OAAO;CACpB,MAAM,aAAa,OAAO;CAC1B,MAAM,EAAE,SAAS,QAAQ,UAAU;AAMnC,KAAI,CAAC,WAAW,CAAC,OACf,QAAO,aACL,EAAE,OAAO,+BAA+B,EACxC,EAAE,QAAQ,KAAK,CAChB;AAGH,KAAI,CAAC;EAAC;EAAU;EAAW;EAAU;EAAc,CAAC,SAAS,OAAO,CAClE,QAAO,aACL,EAAE,OAAO,2DAA2D,EACpE,EAAE,QAAQ,KAAK,CAChB;CAIH,MAAM,gBAAgBA,OAAK,SAAS,EAAE,aAAa,eAAe,KAAK;AACvE,QAAO,OAAO,cAAc,MAAM,eAAe,EAAE,WAAW,MAAM,CAAC,CAAC;CAEtE,MAAM,aAAaA,OAAK,eAAe,GAAG,QAAQ,cAAc;CAChE,MAAM,aAAa;EACjB;EACA,YAAY;EACZ;EACA,OAAO,SAAS;EAChB,4BAAW,IAAI,MAAM,EAAC,aAAa;EACpC;AAED,QAAO,OAAO,cAAc,UAAU,YAAY,KAAK,UAAU,YAAY,MAAM,EAAE,CAAC,CAAC;AAEvF,SAAQ,IAAI,iBAAiB,KAAK,uBAAuB,QAAQ,IAAI,SAAS;AAG9E,KAAI;EAAC;EAAU;EAAW;EAAS,CAAC,SAAS,OAAO,EAAE;EACpD,MAAM,EAAE,uBAAuB,OAAO,OAAO,cAAc,OAAO,6BAA2C;AAE7G,wBADoB,mBAAmB,KACN,EAAE;GACjC,OAAO;GACP,+BAAc,IAAI,MAAM,EAAC,aAAa;GACvC,CAAC;;CAIJ,MAAM,YAAY,oBAAoB,KAAK;AAC3C,KAAI,aAAa,WAAW,SAC1B,QAAO,WAAW,OAAO;EACvB,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GAAE,MAAM;GAAW;GAAS;EACtC,CAAC;UACO,cAAc,WAAW,YAAY,WAAW,WACzD,QAAO,WAAW,OAAO;EACvB,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GAAE,MAAM;GAAW;GAAS,OAAO,SAAS,GAAG,KAAK,YAAY;GAAU;EACpF,CAAC;AAGJ,QAAO,aAAa,EAAE,SAAS,MAAM,CAAC;EACtC,CAAC,CACJ;AAID,MAAM,yBAAyB,WAAW,IACxC,OACA,+BACA,YAAY,OAAO,IAAI,aAAa;AAClC,QAAO,aAAa;EAAE,MAAM;EAAG,aAAa;EAAG,cAAc;EAAG,iBAAiB;EAAG,kBAAkB;EAAG,OAAO;EAAI,CAAC;EACrH,CAAC,CACJ;AAID,MAAM,kCAAkC,WAAW,IACjD,QACA,wCACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,OAAO,OAAO,OAAO,cAAc,8BAA8B,QAAQ,CAAC;AAChF,KAAI,CAAC,KAAK,GAAI,QAAO,KAAK;CAG1B,MAAM,QAAO,OADS,WAAW,QACb;CACpB,MAAM,OAAQ,OAAO;CACrB,MAAM,aAAa,OAAO;CAC1B,MAAM,EAAE,SAAS,gBAAgB,QAAQ,eAAe,YAAY;CAIpE,MAAM,WAAW,uCAAuC,MAAM,MAF3C,UAAU,OAAO,cAAc,QAAQ,GAAG,MACxC,UAAU,OAAO,qBAAqB,QAAQ,GAAG,KACuB;AAC7F,KAAI,CAAC,SAAS,GACZ,QAAO,aAAa,EAAE,OAAO,SAAS,OAAO,EAAE,EAAE,QAAQ,SAAS,QAAQ,CAAC;CAG7E,MAAM,UAAU;CAChB,MAAM,SAAS;CACf,MAAM,oBAAoB;AAE1B,SAAQ,IAAI,8CAA8C,KAAK,IAAI,QAAQ,MAAM,SAAS;AAE1F,QAAO,OAAO,cAAc,sBAAsB,mBAAmB;EACnE,OAAO;EACP,+BAAc,IAAI,MAAM,EAAC,aAAa;EACtC,cAAc,KAAA;EACf,CAAC,CAAC;CAGH,MAAM,iBAAiB,oBAAoB,QAAQ;AAEnD,KAAI,SAAS,gBAAgB;EAC3B,MAAM,kBACJ,gBAAgB,eAChB,CAAC,eAAe,YAAY,WAAW,iBAAiB;AAC1D,MAAI,gBACF,SAAQ,IACN,0CAA0C,KAAK,GAAG,QAAQ,iCAAiC,eAAgB,aAAa,GACzH;MAED,qBAAoB,SAAS;GAC3B,cAAc,WAAW,WAAW,WAAW;GAC/C,aAAa,kBAAkB;GAChC,CAAC;AAGJ,OAAK,kBAAkB,eAAgB,eAAe,WAAW,WAAW,WAAW,eAAe,SACpG,SAAQ,IAAI,iBAAiB,QAAQ,iEAAiE;YAE/F,SAAS,aAGlB,KADE,gBAAgB,aAAa,CAAC,eAAe,UAAU,WAAW,iBAAiB,CAEnF,SAAQ,IACN,0CAA0C,KAAK,GAAG,QAAQ,iCAAiC,eAAgB,WAAW,GACvH;MACI;EACL,MAAM,aAAa,WAAW;AAC9B,sBAAoB,SAAS;GAC3B,YAAY,aAAa,WAAW;GACpC,WAAW,kBAAkB;GAC9B,CAAC;AACF,MAAI,YAAY;AAGd,UAAO,WAAW,OAAO;IACvB,MAAM;IACN,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,SAAS,EAAE,SAAS;IACrB,CAAC;AACF,WAAQ,IAAI,iBAAiB,QAAQ,oDAAoD;;;CAK/F,MAAM,YAAY,oBAAoB,KAAK;AAC3C,KAAI,UACF,QAAO,WAAW,OAAO;EACvB,MAAM;EACN,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,SAAS;GAAE,MAAM;GAAW;GAAS;EACtC,CAAC;AAGJ,QAAO,aAAa;EAClB,SAAS;EACT;EACA;EACD,CAAC;EACF,CAAC,CACJ;AAED,MAAa,8BAA8B,MAAM,SAC/C,qBACA,6BACA,8BACA,0BACA,oCACA,yBACA,0BACA,yBACA,iCACA,wBACA,gCACD;;;kBCxyB6E;eAEJ;oBACH;WAEjB;aACY;AAmBlE,MAAM,kCAAkC,WAAW,IACjD,OACA,mDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;AACjC,QAAO,aACL;EACE,OAAO;EACP,MAAM;EACN,YAAY;GACV,SAAS,OAAO;GAChB,SAAS,OAAO;GAChB,MAAM,OAAO;GACd;EACF,EACD,EAAE,QAAQ,KAAK,CAChB;EACD,CAAC,CACJ;AAID,MAAM,iCAAiC,WAAW,IAChD,QACA,iDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;AAEpB,KAAI,CAAC,4BAA4B,KAAK,CACpC,QAAO,aACL,EAAE,OAAO,6EAA6E,EACtF,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,EAAE,oBAAoB,2BAA2B,mBACrD,OAAO,OAAO,cAAc,OAAO,6BAA2C;CAGhF,MAAM,cAAc,eAAe,SADf,0BAA0B,MAAM,QACG,CAAC,CAAC,eACpD,mBAAmB,MAAM,SAAS,QAAQ;AAE/C,QAAO,OAAO,cAAc,OAAO,WAAW,YAAY,YAAY,CAAC,CAAC,YAAY,GAAG,CAAC;AAExF,uBAAsB,aAAa;EACjC,OAAO;EACP,+BAAc,IAAI,MAAM,EAAC,aAAa;EACvC,CAAC;AACF,QAAO,aAAa;EAClB,SAAS;EACT,SAAS,UAAU,KAAK,IAAI,QAAQ,GAAG,QAAQ;EAChD,CAAC;EACF,CAAC,CACJ;AAqBD,MAAM,gCAAgC,WAAW,IAC/C,OACA,wCACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;CACpB,MAAM,UAAU,OAAO,kBAAkB;CACzC,MAAM,SAAS,kBAAkB,MAAM,QAAQ;CAE/C,IAAI;CACJ,IAAI,SAAS;AAEb,KAAI,OAAO,OAAO,OAAO,EAAE;EACzB,MAAM,KAAK,OAAO,MAAM;EACxB,MAAM,aAAa,GAAG,IAAI,QAAQ;EAClC,MAAM,cAAc,GAAG,IAAI,SAAS;AACpC,MAAI,WAAY,SAAQ,SAAS,YAAY,GAAG;AAChD,MAAI,YAAa,UAAS,SAAS,aAAa,GAAG;;CAGrD,MAAM,EAAE,oBAAoB,OAAO,OAAO,cAAc,OAAO,iCAA+C;AAE9G,QAAO,aADM,gBAAgB,SAAS,MAAM;EAAE;EAAO;EAAQ,CACrC,CAAC;EACzB,CAAC,CACJ;AAKD,MAAM,qCAAqC,WAAW,IACpD,OACA,sDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;CACpB,MAAM,QAAQ,OAAO;CAErB,MAAM,EAAE,eAAe,mBACrB,OAAO,OAAO,cAAc,OAAO,iCAA+C;CAEhF,MAAM,UAAU,cAAc,SAAS,MAAM,MAAM;AAEnD,KAAI,CAAC,WAAW,QAAQ,CACtB,QAAO,aAAa,EAAE,OAAO,qBAAqB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAItE,MAAM,UAAU,IAAI,aAAa;CAEjC,MAAM,aAAa,IAAI,eAAe,EACpC,MAAM,MAAM,YAAY;EAEtB,MAAM,UAAU,MAAM,SAAS,SAAS,QAAQ,CAAC,YAAY,GAAG;AAChE,aAAW,QACT,QAAQ,OACN,SAAS,KAAK,UAAU;GAAE,MAAM;GAAW,MAAM;GAAS,CAAC,CAAC,MAC7D,CACF;EACD,IAAI,WAAW,QAAQ;EAGvB,MAAM,OAAO,YAAY;AACvB,OAAI,CAAC,eAAe,SAAS,MAAM,MAAM,EAAE;AAEzC,QAAI;KACF,MAAM,eAAe,MAAM,SAAS,SAAS,QAAQ;AACrD,SAAI,aAAa,SAAS,UAAU;MAClC,MAAM,aAAa,aAAa,UAAU,SAAS;AACnD,iBAAW,QACT,QAAQ,OACN,SAAS,KAAK,UAAU;OAAE,MAAM;OAAU,MAAM;OAAY,CAAC,CAAC,MAC/D,CACF;;YAEG;AACR,eAAW,QACT,QAAQ,OAAO,SAAS,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC,CAAC,MAAM,CACpE;AACD,eAAW,OAAO;AAClB;;AAGF,OAAI;IACF,MAAM,iBAAiB,MAAM,SAAS,SAAS,QAAQ;AACvD,QAAI,eAAe,SAAS,UAAU;KACpC,MAAM,aAAa,eAAe,UAAU,SAAS;AACrD,gBAAW,QACT,QAAQ,OACN,SAAS,KAAK,UAAU;MAAE,MAAM;MAAU,MAAM;MAAY,CAAC,CAAC,MAC/D,CACF;AACD,gBAAW,eAAe;;WAEtB;AAER,SAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAK,CAAC;AAC7C,SAAM,MAAM;;AAGd,QAAM,IAAI,SAAS,MAAM,WAAW,GAAG,IAAK,CAAC;AAC7C,QAAM,MAAM;IAEf,CAAC;CAEF,MAAM,eAAe,OAAO,mBAAwC;EAClE,gBAAgB;EAChB,UAAU,QAAQ;EACnB,CAAC;AAEF,QAAO,mBAAmB,OAAO,cAAc,EAC7C,SAAS;EACP,gBAAgB;EAChB,iBAAiB;EACjB,YAAY;EACb,EACF,CAAC;EACN,CAAC,CACJ;AAID,MAAM,+BAA+B,WAAW,IAC9C,OACA,+CACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;CACpB,MAAM,QAAQ,OAAO;CAErB,MAAM,EAAE,eAAe,qBACrB,OAAO,OAAO,cAAc,OAAO,iCAA+C;CACpF,MAAM,UAAU,cAAc,SAAS,MAAM,MAAM;AAEnD,KAAI,CAAC,QACH,QAAO,aAAa,EAAE,OAAO,qBAAqB,EAAE,EAAE,QAAQ,KAAK,CAAC;AAItE,QAAO,aAAa;EAAE;EAAO;EAAS,UADrB,iBAAiB,QACY;EAAE,CAAC;EACjD,CAAC,CACJ;AAID,MAAM,yCAAyC,WAAW,IACxD,QACA,yDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;AAEpB,KAAI,CAAC,4BAA4B,KAAK,CACpC,QAAO,aACL,EAAE,OAAO,6EAA6E,EACtF,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,EAAE,wBAAwB,OAAO,OAAO,cAAc,OAAO,6BAA2C;AAC9G,QAAO,OAAO,cAAc,oBAAoB,SAAS,KAAK,CAAC;AAC/D,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS;EAAyB,CAAC;EACxE,CAAC,CACJ;AAID,MAAM,uCAAuC,WAAW,IACtD,QACA,+CACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;AAEpB,KAAI,CAAC,4BAA4B,KAAK,CACpC,QAAO,aACL,EAAE,OAAO,6EAA6E,EACtF,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,EAAE,qBAAqB,OAAO,OAAO,cAAc,OAAO,6BAA2C;AAG3G,KAFgB,iBAAiB,SAAS,KAE/B,CACT,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS;EAAuB,CAAC;KAEtE,QAAO,aACL,EAAE,OAAO,mCAAmC,EAC5C,EAAE,QAAQ,KAAK,CAChB;EAEH,CAAC,CACJ;AAID,MAAM,wCAAwC,WAAW,IACvD,QACA,gDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;AAEpB,KAAI,CAAC,4BAA4B,KAAK,CACpC,QAAO,aACL,EAAE,OAAO,6EAA6E,EACtF,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,EAAE,sBAAsB,OAAO,OAAO,cAAc,OAAO,6BAA2C;AAG5G,KAFgB,kBAAkB,SAAS,KAEhC,CACT,QAAO,aAAa;EAAE,SAAS;EAAM,SAAS;EAAwB,CAAC;KAEvE,QAAO,aACL,EAAE,OAAO,oCAAoC,EAC7C,EAAE,QAAQ,KAAK,CAChB;EAEH,CAAC,CACJ;AAID,MAAM,sCAAsC,WAAW,IACrD,QACA,8CACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;AAEpB,KAAI,CAAC,4BAA4B,KAAK,CACpC,QAAO,aACL,EAAE,OAAO,6EAA6E,EACtF,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,EAAE,oBAAoB,OAAO,OAAO,cAAc,OAAO,6BAA2C;AAC1G,iBAAgB,SAAS,KAAK;AAC9B,QAAO,aAAa;EAClB,SAAS;EACT,SAAS;EACV,CAAC;EACF,CAAC,CACJ;AAID,MAAM,iCAAiC,WAAW,IAChD,OACA,yCACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;AAEpB,KAAI,CAAC,4BAA4B,KAAK,CACpC,QAAO,aACL,EAAE,OAAO,6EAA6E,EACtF,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,EAAE,wBAAwB,OAAO,OAAO,cAAc,OAAO,6BAA2C;CAC9G,MAAM,QAAQ,oBAAoB,SAAS,KAAK;AAEhD,KAAI,MACF,QAAO,aAAa,MAAM;KAE1B,QAAO,aAAa,EAAE,OAAO,0BAA0B,EAAE,EAAE,QAAQ,KAAK,CAAC;EAE3E,CAAC,CACJ;AAID,MAAM,mCAAmC,WAAW,IAClD,OACA,2CACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;CAEpB,MAAM,EAAE,sBACN,OAAO,OAAO,cAAc,OAAO,oCAAkD;CACvF,MAAM,SAAS,kBAAkB,SAAS,KAAK;AAE/C,KAAI,OACF,QAAO,aAAa,EAAE,QAAQ,CAAC;KAE/B,QAAO,aAAa,EAAE,OAAO,2BAA2B,EAAE,EAAE,QAAQ,KAAK,CAAC;EAE5E,CAAC,CACJ;AAID,MAAM,8CAA8C,WAAW,IAC7D,QACA,sDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;CAEpB,MAAM,EAAE,4BACN,OAAO,OAAO,cAAc,OAAO,oCAAkD;CACvF,MAAM,SAAS,OAAO,OAAO,cAAc,wBAAwB,SAAS,KAAK,CAAC;AAElF,KAAI,OACF,QAAO,aAAa;EAAE;EAAQ,SAAS;EAA8B,CAAC;KAEtE,QAAO,aACL,EAAE,OAAO,qCAAqC,EAC9C,EAAE,QAAQ,KAAK,CAChB;EAEH,CAAC,CACJ;AAID,MAAM,qCAAqC,WAAW,IACpD,QACA,4CACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;CAEpB,MAAM,EAAE,QAAQ,OAAO,YAAY,OADf;AAGpB,KAAI,CAAC,UAAU,CAAC;EAAC;EAAU;EAAU;EAAU,CAAC,SAAS,OAAO,CAC9D,QAAO,aACL,EAAE,OAAO,oDAAoD,EAC7D,EAAE,QAAQ,KAAK,CAChB;AAGH,KAAI,CAAC,4BAA4B,KAAK,CACpC,QAAO,aACL,EAAE,OAAO,6EAA6E,EACtF,EAAE,QAAQ,KAAK,CAChB;CAGH,MAAM,EAAE,+BACN,OAAO,OAAO,cAAc,OAAO,6BAA2C;AAChF,4BAA2B,SAAS,MAAM;EAAU;EAA2C;EAAO,EAAE,QAAQ;AAChH,QAAO,aAAa;EAClB,SAAS;EACT,SAAS;EACV,CAAC;EACF,CAAC,CACJ;AAID,MAAM,qCAAqC,WAAW,IACpD,OACA,8CACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;CAEpB,MAAM,UAAUC,OAAK,SAAS,EAAE,aAAa,eAAe,SAAS,MAAM,OAAO;AAClF,KAAI,CAAC,WAAW,QAAQ,CACtB,QAAO,aAAa;EAAE,KAAK;EAAM,SAAS;EAAiB,CAAC;CAG9D,MAAM,SAAS,OAAO,OAAO,cAAc,QAAQ,QAAQ,CAAC,EACzD,QAAQ,MAAM,EAAE,SAAS,OAAO,CAAC,CACjC,MAAM,CACN,SAAS;AAEZ,KAAI,MAAM,WAAW,EACnB,QAAO,aAAa;EAAE,KAAK;EAAM,SAAS;EAAqB,CAAC;AAIlE,QAAO,aAAa;EAClB,KAAK,OAFkB,OAAO,cAAc,SAASA,OAAK,SAAS,MAAM,GAAG,EAAE,QAAQ,CAAC;EAGvF,MAAM,MAAM;EACZ,WAAW,MAAM;EAClB,CAAC;EACF,CAAC,CACJ;AAID,MAAM,wCAAwC,WAAW,IACvD,QACA,gDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,OAAO,OAAO;CAEpB,MAAM,EAAE,uBAAuB,OAAO,OAAO,cAAc,OAAO,iCAA+C;CACjH,MAAM,EAAE,2BAA2B,OAAO,OAAO,cAAc,OAAO,0BAA+B;CAErG,MAAM,YAAY,uBAAuB,QAAQ;CACjD,MAAM,UAAU,mBAAmB,SAAS,MAAM;EAAE,SAAS,UAAU;EAAU,SAAS,UAAU;EAAU,CAAC;AAE/G,QAAO,aAAa;EAClB,SAAS;EACT;EACA,SAAS,cAAc,QAAQ;EAChC,CAAC;EACF,CAAC,CACJ;AAMD,MAAM,yCAAyC,WAAW,IACxD,QACA,0DACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;AAGjC,QAAO,aACL,EAAE,OAAO,iEAHQ,OAAO,cAAc,GAG+C,GAF1E,OAAO,WAAW,GAEgE,IAAI,EACjG,EAAE,QAAQ,KAAK,CAChB;EACD,CAAC,CACJ;AAeD,MAAM,gCAAgC,WAAW,IAC/C,QACA,qDACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;CACjC,MAAM,UAAU,OAAO;CACvB,MAAM,UAAU,OAAO;CAEvB,MAAM,EAAE,OAAO,YAAY,OADP;CAGpB,MAAM,EAAE,4BAA4B,OAAO,OAAO,cAC1C,OAAO,8BACd;CACD,MAAM,aAAa,OAAO,wBAAwB,SAAS,QAAQ;CAQnE,MAAM,gBAAgB,4BAA4B,QAAQ;AAC1D,KAAI,CAAC,cACH,QAAO,aAAa,EAAE,OAAO,8BAA8B,WAAW,EAAE,EAAE,QAAQ,KAAK,CAAC;CAE1F,MAAM,gBAAgBA,OAAK,cAAc,aAAa,cAAc,WAAW,QAAQ,aAAa,GAAG;AACvG,KAAI,CAAC,WAAW,cAAc,CAC5B,QAAO,aAAa,EAAE,OAAO,wBAAwB,iBAAiB,EAAE,EAAE,QAAQ,KAAK,CAAC;CAI1F,IAAI,SAAS;AACb,KAAI;EACF,MAAM,EAAE,WAAW,OAAO,OAAO,cAAc,UAC7C,OAAO,cAAc,iCACrB;GAAE,UAAU;GAAS,SAAS;GAAM,CACrC,CAAC;AACF,WAAS,OAAO,MAAM,IAAI;SACpB;CAGR,MAAM,EAAE,4BAA4B,OAAO,OAAO,cAC1C,OAAO,8BACd;CACD,MAAM,QAAQ,oBAAoB,QAAQ,EAAE;CAC5C,MAAM,SAAS,OAAO,wBAAwB;EAC5C;EACA,WAAW;EACX;EACA;EACA;EACA;EACD,CAAC;AAEF,KAAI,OAAO,MACT,QAAO,aAAa;EAClB,SAAS;EACT,OAAO;EACP,SAAS,OAAO;EAChB,QAAQ,WAAW;EACnB,OAAO,EAAE;EACT,OAAO,SAAS,KAAA;EAChB,SAAS,WAAW,KAAA;EACrB,EAAE,EAAE,QAAQ,KAAK,CAAC;AAGrB,QAAO,aAAa;EAClB,SAAS,OAAO;EAChB,SAAS,OAAO;EAChB,QAAQ,WAAW;EACnB,OAAO,EAAE;EACT,OAAO,SAAS,KAAA;EAChB,SAAS,WAAW,KAAA;EACrB,CAAC;EACF,CAAC,CACJ;AAYD,MAAM,sCAAsC,WAAW,IACrD,QACA,6DACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,OAAO,WAAW;AACjC,QAAO,aACL;EACE,OAAO;EACP,MAAM;EACN,YAAY;GACV,SAAS,OAAO;GAChB,SAAS,OAAO;GAChB,MAAM,OAAO;GACd;EACF,EACD,EAAE,QAAQ,KAAK,CAChB;EACD,CAAC,CACJ;AAKD,MAAM,wBAAwB,WAAW,IACvC,OACA,uBACA,YAAY,OAAO,IAAI,aAAa;CAClC,MAAM,SAAS,gBAAgB,CAAC;CAEhC,MAAM,SAAS;EACb;GAAE,KAAK;GAAa,MAAM;GAAQ;EAClC;GAAE,KAAK;GAAa,MAAM;GAAQ;EAClC;GAAE,KAAK;GAAqB,MAAM;GAAQ,SAAS;GAAW;EAC9D;GAAE,KAAK;GAA0B,MAAM;GAAQ,SAAS;GAAgB;EACxE;GAAE,KAAK;GAAe,MAAM;GAAU;EACtC;GAAE,KAAK;GAAe,MAAM;GAAU;EACtC;GAAE,KAAK;GAA2B,MAAM;GAAU,SAAS;GAAe;EAC1E;GAAE,KAAK;GAAwB,MAAM;GAAU,SAAS;GAAY;EACpE;GAAE,KAAK;GAA2B,MAAM;GAAU,SAAS;GAAe;EAC1E;GAAE,KAAK;GAA4B,MAAM;GAAU,SAAS;GAAgB;EAC5E;GAAE,KAAK;GAAa,MAAM;GAAQ;EAClC;GAAE,KAAK;GAAa,MAAM;GAAQ;EACnC;CAED,MAAM,WAA0C,EAAE;AAClD,MAAK,MAAM,SAAS,OAClB,KAAI;AACF,WAAS,MAAM,OAAO,aAAa,MAAM,MAAO,MAA+B,SAAS,OAAO;SACzF;AACN,WAAS,MAAM,OAAO;;AAI1B,QAAO,aAAa,SAAS;EAC7B,CAAC,CACJ;AAED,MAAa,+BAA+B,MAAM,SAEhD,iCACA,gCACA,+BACA,oCACA,8BACA,wCACA,sCACA,uCACA,qCACA,gCACA,kCACA,6CACA,oCACA,oCACA,uCACA,wCACA,+BACA,qCACA,sBACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC3qBD,MAAa,wBAAwB,MAAM,SACzC,6BACA,6BACD"}
|