@overdeck/desktop 0.45.13 → 0.45.16
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/package.json +1 -1
- package/server/{acceptance-criteria-CfujPRrz.js → acceptance-criteria-BZ_nQS1f.js} +2 -2
- package/server/{advancing-selfheal-Bn5QiLtn.js → advancing-selfheal-DJYAvGv-.js} +6 -5
- package/server/{agent-gc-Cl9Nm0Zu.js → agent-gc-D0wDIeV1.js} +3 -3
- package/server/{agent-state-C6Rm6FPR.js → agent-state-lGvgPP4m.js} +1 -1
- package/server/{agent-state-service-U8feBf8t.js → agent-state-service-CZrayD_G.js} +1 -1
- package/server/{agent-state-service-JhFzYCP_.js → agent-state-service-DFslS5go.js} +9 -52
- package/server/{agents-DqQa4Dq_.js → agents-B9GwfCyp.js} +155 -153
- package/server/{agents-CxbuBjID.js → agents-CEcaxSkR.js} +1 -1
- package/server/{archive-planning-CPitEZo4.js → archive-planning-CbghE9-o.js} +5 -5
- package/server/{archive-planning-DmpT1_14.js → archive-planning-CoyIbgGK.js} +1 -1
- package/server/{artifact-index-jobs-sMnNtPhy.js → artifact-index-jobs-CcSrUoSE.js} +2 -2
- package/server/auto-commit-IW-eik_b.js +3 -0
- package/server/{backlog-auto-trigger-Cf9C8fwD.js → backlog-auto-trigger-BfgDsII0.js} +3 -3
- package/server/{bd-process-lock-D1aJxeYv.js → bd-process-lock-XAXAet9O.js} +32 -11
- package/server/{beads-CivpXztb.js → beads-B1u04n3o.js} +1 -1
- package/server/{beads-DRoj6-e1.js → beads-BeSMEjGT.js} +96 -196
- package/server/{beads-query-tOsAYP5h.js → beads-query-Dl4FhT8J.js} +24 -99
- package/server/beads-restore-BUItPOjI.js +23 -0
- package/server/behavior-BTpYbAv4.js +10 -0
- package/server/{briefing-freshness-BL3Q6p7R.js → briefing-freshness-DESTCWo-.js} +4 -4
- package/server/{checkpoint-manager-DCxjQvP_.js → checkpoint-manager-BwFdegJF.js} +2 -2
- package/server/{checkpoint-manager-j5URlFY7.js → checkpoint-manager-DWSXooZV.js} +1 -1
- package/server/checkpoint-worker.js +1 -1
- package/server/{checkpoints-BoX1H2Hf.js → checkpoints-BtUonbs3.js} +2 -2
- package/server/{checkpoints-Bu_M_Q9W.js → checkpoints-fjadfOKF.js} +1 -1
- package/server/{claude-hooks-provision-D-jGXmu1.js → claude-hooks-provision-DAtxutOs.js} +132 -114
- package/server/claude-hooks-provision-DiK62u0k.js +3 -0
- package/server/{claude-permissions-DxIjkOVl.js → claude-permissions-CThLgf8d.js} +2 -2
- package/server/{claude-settings-overlay-BPDS6GfE.js → claude-settings-overlay-BF9MPIwx.js} +2 -2
- package/server/{claude-settings-overlay-5b9X2S0C.js → claude-settings-overlay-Dx_Uqcmv.js} +1 -1
- package/server/{cli-s7bPWy-E.js → cli-ByKIIMfT.js} +9 -9
- package/server/cli-D9cUTbBr.js +3 -0
- package/server/{cliproxy-BituqWtR.js → cliproxy-B4c1BUvs.js} +2 -2
- package/server/{cliproxy-D3sHB0sI.js → cliproxy-C_GuT5DU.js} +1 -1
- package/server/{close-issue-CrBE7voz.js → close-issue-BOuLKr6e.js} +5 -5
- package/server/{closed-issue-reaper-BRQBc7Xa.js → closed-issue-reaper-CksfuiFD.js} +1 -1
- package/server/{closed-issue-reaper-DX9mYUji.js → closed-issue-reaper-DCAYzNXW.js} +102 -30
- package/server/{codex-Bi1ITSOo.js → codex-BYPz6ZdF.js} +1 -1
- package/server/{codex-CO0HrK3j.js → codex-BbqIRZjd.js} +6 -5
- package/server/{behavior-D3d2mVyD.js → codex-parser-mFYZh8vR.js} +3 -9
- package/server/{compact-beads-DUkCUFxA.js → compact-beads-CyJtoXJi.js} +15 -8
- package/server/{compact-beads-CrA8-k10.js → compact-beads-N00E4EVa.js} +1 -1
- package/server/{config-DsCxzjXK.js → config-DVLdmnMJ.js} +2 -2
- package/server/{config-B4_d7z7z.js → config-Dj6N8EP9.js} +1 -1
- package/server/{config-yaml-C_djSurJ.js → config-yaml-cNOZcEww.js} +118 -23
- package/server/{conflict-gate-D47Kl78u.js → conflict-gate-BMKm_5DV.js} +3 -3
- package/server/{conflict-gate-CpOn9KC-.js → conflict-gate-CuXUv-bP.js} +1 -1
- package/server/{context-layers-CHl3Nroz.js → context-layers-CeAwSYNt.js} +2 -2
- package/server/{context-layers-BNhUBTkf.js → context-layers-c0JqYHcS.js} +3 -3
- package/server/{control-settings-KyYNXZLv.js → control-settings-D4EFsRBd.js} +4 -4
- package/server/{conversation-compaction-D3aS5PnS.js → conversation-compaction-BCggyih4.js} +8 -8
- package/server/{conversation-compaction-D4Wed1BJ.js → conversation-compaction-kHVfVRg3.js} +1 -1
- package/server/{conversation-service-CFZIjs-M.js → conversation-service-B8LFQQm1.js} +6 -5
- package/server/{conversation-service-DcDB5usR.js → conversation-service-BTzObqGi.js} +1 -1
- package/server/{conversations-BSOU5qnu.js → conversations-B-lcHv-5.js} +4 -4
- package/server/{conversations-BbDPrz89.js → conversations-BM7zlXjr.js} +1 -1
- package/server/{cost-BE_ulOrw.js → cost-BcpRQ-7o.js} +3 -3
- package/server/{cost-CaiG63FG.js → cost-D9kPIY_5.js} +2 -2
- package/server/{cost-sync-D6AX8jv-.js → cost-sync-DF6odxK9.js} +3 -3
- package/server/{dag-BQFtXOuJ.js → dag-DKI3sOmX.js} +3 -3
- package/server/{dangerous-git-ops-04Ba-Qu3.js → dangerous-git-ops-Bn18PEcN.js} +1 -1
- package/server/dashboard-db-worker.js +6 -6
- package/server/{deacon-Bz-6Wjmn.js → deacon-DONuopff.js} +3 -3
- package/server/{deacon-2EvA9Ci3.js → deacon-DdARMjZq.js} +188 -1601
- package/server/{deacon-swarm-DYYOzHRH.js → deacon-swarm-BNGkmYR_.js} +12 -105
- package/server/deacon-swarm-CvdFekIO.js +2 -0
- package/server/deacon.js +7 -7
- package/server/{devcontainer-renderer-Dx0C7VWJ.js → devcontainer-renderer-C-34Sxyz.js} +3 -3
- package/server/{dist-ya66D9ze.js → dist-BMXvKiZP.js} +1 -1
- package/server/dist-BNwrgsUO.js +3 -0
- package/server/{dist-src-Byxt1ifL.js → dist-src-BgGUblXg.js} +1 -1
- package/server/{docker-D2TbXDO6.js → docker-apZ9pBYw.js} +67 -28
- package/server/{ensure-devcontainer-BTiMfFp5.js → ensure-devcontainer-Bqi1EF1P.js} +4 -4
- package/server/{ensure-devcontainer-BIShzs3M.js → ensure-devcontainer-Dq3KoOIl.js} +1 -1
- package/server/{esm-shims-DiV9jHa_.js → esm-shims-BEjvINX3.js} +1 -1
- package/server/{event-store-C02Gprug.js → event-store-CqDgfbAs.js} +1 -1
- package/server/{event-store-C71x6h0i.js → event-store-D89fMfXS.js} +3 -3
- package/server/{events-BFDuPV6n.js → events-S_C0pvl6.js} +3 -3
- package/server/{events-D2gP2-Yv.js → events-rIGncmP1.js} +1 -1
- package/server/export-BJHSVwKP.js +87 -0
- package/server/{factory-ByfbrCab.js → factory-DV18m0rc.js} +5 -5
- package/server/{feature-registry-population-CSH66UO8.js → feature-registry-population-CqZSnqQi.js} +4 -4
- package/server/{feature-registry-storage-DW7dUefc.js → feature-registry-storage-BeXmNBwK.js} +2 -2
- package/server/{features-x_aLR7U9.js → features-u5XeMaOK.js} +2 -2
- package/server/{feedback-target-BFo9fScH.js → feedback-target-CwyWf0Dg.js} +6 -6
- package/server/{feedback-target-C4VMPTZu.js → feedback-target-DDG1PWl3.js} +1 -1
- package/server/{feedback-writer-DCyd75yJ.js → feedback-writer-BsJiw5aT.js} +1 -1
- package/server/{feedback-writer-B9ZqnnX3.js → feedback-writer-D20HSXre.js} +4 -4
- package/server/{flywheel-DwQCoZQG.js → flywheel-9Y1X7IM-.js} +13 -13
- package/server/{flywheel-D9NKvWqb.js → flywheel-Dczmgchd.js} +23 -15
- package/server/{flywheel-DB48X7Nn.js → flywheel-RCnHAgc4.js} +1 -1
- package/server/{flywheel-merge-order-DrjmFXg4.js → flywheel-merge-order-CtCYsFJP.js} +7 -7
- package/server/{flywheel-merge-order-f1bi5XIf.js → flywheel-merge-order-DEtEd3aK.js} +1 -1
- package/server/forge-8y6aEPTB.js +2 -0
- package/server/{forge-C-SoKy8U.js → forge-DFDThFil.js} +2 -2
- package/server/{from-fu7ZumnY.js → from-NSJypkI8.js} +1 -1
- package/server/{fts-db-CL-YzSRG.js → fts-db-C5A_TTOM.js} +2 -2
- package/server/{fts-operations-Bylu5NjM.js → fts-operations-DvCSimfU.js} +2 -2
- package/server/{git-utils-Ym380hJZ.js → git-utils-C0neHvQx.js} +1 -1
- package/server/{git-utils-C8koBs8j.js → git-utils-Dr0L7mFk.js} +2 -2
- package/server/{github-app-DIr70YLg.js → github-app-Bwt9lK4A.js} +1 -1
- package/server/{github-app-DhycHVLF.js → github-app-u9-SX7Gc.js} +2 -2
- package/server/{github-client-7gkcXcBS.js → github-client-Btyy3uan.js} +3 -3
- package/server/{github-client-Ji9lUTNF.js → github-client-Do3k-N1W.js} +1 -1
- package/server/{github-pr-lookup-rt7T9FwA.js → github-pr-lookup-DOF1U2WW.js} +2 -2
- package/server/{harness-resolve-CvGXUnAF.js → harness-resolve-C6g2R0Cd.js} +3 -3
- package/server/{health-events-D4E1DLN-.js → health-events-B4u-2a9m.js} +2 -2
- package/server/{health-events-DDfkoO5a.js → health-events-Bos3sk5g.js} +1 -1
- package/server/{hooks-D_6pAbuJ.js → hooks-BCoOjL_R.js} +2 -2
- package/server/{hooks-DvdnCVv6.js → hooks-uEr8WTx_.js} +1 -1
- package/server/{hume-DkVOs1Qi.js → hume-BqxYKbUf.js} +2 -2
- package/server/hume-CerGi2RH.js +3 -0
- package/server/{idle-stack-reaper-cDRXDpgE.js → idle-stack-reaper-DbZ8ukMd.js} +2 -2
- package/server/{idle-stack-reaper-DNbV9-fj.js → idle-stack-reaper-H0W6XtHh.js} +1 -1
- package/server/{infra-Clew3DpF.js → infra-Ba9mS6e6.js} +3 -3
- package/server/{injection-QCIHOpk6.js → injection-C1DKXeIZ.js} +1 -1
- package/server/{injection-ql0Nddcn.js → injection-yrMBxzVq.js} +7 -7
- package/server/{inspect-agent-DY18KEx4.js → inspect-agent-B-mnb4uh.js} +9 -9
- package/server/inspect-agent-CWkeAB7J.js +2 -0
- package/server/{internal-token-C90jBp-y.js → internal-token-BPYEaXFh.js} +1 -1
- package/server/{internal-token-BeXjPLKy.js → internal-token-BtqSbKTx.js} +2 -2
- package/server/{io-CbAFXQge.js → io-VEGSAqsd.js} +15 -22
- package/server/{issue-closed-BYC2TCk-.js → issue-closed-BxcePXL-.js} +4 -4
- package/server/{issue-lifecycle-BoJa6qIt.js → issue-lifecycle--8MRxrG1.js} +22 -22
- package/server/{issue-lifecycle-CLHbj8RS.js → issue-lifecycle-CdMUauyb.js} +1 -1
- package/server/{issue-service-singleton-BS9NENuR.js → issue-service-singleton-CkTUI-eu.js} +10 -10
- package/server/{issue-service-singleton-3g_CCY-1.js → issue-service-singleton-MZsDhAob.js} +1 -1
- package/server/{issues-BPcqPqfV.js → issues-CBAsuoAO.js} +2 -2
- package/server/jsonl-parser-BUW5_sSK.js +245 -0
- package/server/{label-cleanup-CwiCSZrb.js → label-cleanup-htK6fBRU.js} +3 -3
- package/server/{layers-OHHLphl0.js → layers-jjL16At6.js} +2 -2
- package/server/{lifecycle-B8IdvUbt.js → lifecycle-Ck2UR27D.js} +7 -7
- package/server/{lifecycle-io-BMCH1Bxg.js → lifecycle-io-DXv8Utct.js} +1 -1
- package/server/{lifecycle-io-C73m5e6R.js → lifecycle-io-IehcRt2v.js} +36 -47
- package/server/{linear-client-BMxgycoy.js → linear-client-By7hjwuN.js} +1 -1
- package/server/{linear-client-CgQlM4aT.js → linear-client-D9cSQfvD.js} +3 -3
- package/server/memory-fts-worker.js +1 -1
- package/server/{merge-BRkVaIx-.js → merge-BLcEZNXz.js} +1 -1
- package/server/{merge-B7Oi-g6k.js → merge-BN8FGJ2q.js} +3 -3
- package/server/{merge-agent-DNFFsiEm.js → merge-agent-B3u2szOL.js} +29 -29
- package/server/{merge-agent-Bwm34MZ5.js → merge-agent-ejC7Wos8.js} +1 -1
- package/server/{merge-batch-BpYXUaqJ.js → merge-batch-DyqlgYGp.js} +1 -1
- package/server/{merge-blockers-DIOZmqdK.js → merge-blockers-cq-aS2KG.js} +2 -2
- package/server/merge-ops-CFkQtXEj.js +2 -0
- package/server/{merge-rebase-C8WH39MB.js → merge-rebase-Dl7yIxtV.js} +2 -2
- package/server/{merge-set-CsEE2CyQ.js → merge-set-CZ9Nbay0.js} +3 -3
- package/server/{merge-set-DOsBTBQc.js → merge-set-Dy0D0r3i.js} +1 -1
- package/server/{merge-sync-CcLM2vwW.js → merge-sync-CG9kDCdT.js} +1 -1
- package/server/{merge-sync-C2B322Ot.js → merge-sync-D7jWLgCC.js} +2 -2
- package/server/{merge-train-BIm7-7cy.js → merge-train-CH33XJOH.js} +3 -3
- package/server/{merge-train-deps-DG1oQ2DT.js → merge-train-deps-JoVdGnOg.js} +4 -4
- package/server/{merge-verification-BAbjxEd5.js → merge-verification-D4wTChdX.js} +4 -4
- package/server/{multipart-parser-jKtKAXtl.js → multipart-parser-Csn8U-K9.js} +2 -2
- package/server/{no-resume-mode-yrPEQ_pC.js → no-resume-mode-Gh5R_YLF.js} +1 -1
- package/server/node_modules/.package-lock.json +1 -1
- package/server/{orphan-proposed-reconciler-BqVGaOIj.js → orphan-proposed-reconciler-BIcNu4tl.js} +1 -1
- package/server/{orphan-proposed-reconciler-zHrwJ3xr.js → orphan-proposed-reconciler-DI_IS1p_.js} +15 -39
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
- package/server/{pan-dir-BlNrLzW7.js → pan-dir-CQIfKG8c.js} +4 -4
- package/server/{paths-CiVeo4N_.js → paths-Blj6A9AJ.js} +2 -2
- package/server/{pickup-KLRUhThD.js → pickup-BtvRBbvI.js} +1 -1
- package/server/{pipeline-notifier-nkWCe042.js → pipeline-notifier-BH4PAi-w.js} +1 -1
- package/server/{pipeline-notifier-DBF6dHxH.js → pipeline-notifier-D_WYtEwl.js} +2 -2
- package/server/project-pipelines-jMTitpeO.js +162 -0
- package/server/projects-CrBE6IhN.js +3 -0
- package/server/{prompts-CXbQvevY.js → prompts-B3njaImU.js} +2 -2
- package/server/{providers-CUdAb5Qu.js → providers-BbEyFhPX.js} +7 -7
- package/server/providers-DnPxkHoG.js +3 -0
- package/server/public/assets/chunk-BO2N2NFS-3qh9TUJi.js +103 -0
- package/server/public/assets/{diagram-OG6HWLK6-NACO1xW5.js → diagram-OG6HWLK6-D8UVTPj7.js} +1 -1
- package/server/public/assets/{dist-DQD-ZjQY.js → dist-BvLLn1YC.js} +3 -3
- package/server/public/assets/{dist-D1FjqGgm.js → dist-CyubL7Wj.js} +1 -1
- package/server/public/assets/{ganttDiagram-6RSMTGT7-CEMAKgmE.js → ganttDiagram-6RSMTGT7-BI4E1LbQ.js} +1 -1
- package/server/public/assets/{highlighted-body-OFNGDK62-D5lC7FW8.js → highlighted-body-OFNGDK62-D_bapWkF.js} +1 -1
- package/server/public/assets/index-BAZxZ-8I.js +796 -0
- package/server/public/assets/index-FBG3JBfq.css +1 -0
- package/server/public/assets/{linear-DDHAMRPB.js → linear-CEdobObr.js} +1 -1
- package/server/public/assets/mermaid-GHXKKRXX-BYP8MI37.js +1 -0
- package/server/public/assets/{pieDiagram-4H26LBE5-DwBdeKBb.js → pieDiagram-4H26LBE5-DT79L01f.js} +1 -1
- package/server/public/assets/{prod-DAZt0c4G.js → prod-CR_K2DFV.js} +4 -4
- package/server/public/assets/{quadrantDiagram-W4KKPZXB-CNDLp-_m.js → quadrantDiagram-W4KKPZXB-BEkKXXrL.js} +1 -1
- package/server/public/assets/{sankeyDiagram-5OEKKPKP-oIUL_uO1.js → sankeyDiagram-5OEKKPKP-BxSCqn-j.js} +1 -1
- package/server/public/assets/{xychartDiagram-2RQKCTM6-B-IM6Cck.js → xychartDiagram-2RQKCTM6-DicwJGG_.js} +1 -1
- package/server/public/index.html +2 -2
- package/server/{pull-requests-DZ-V7_LB.js → pull-requests-43LPDfM8.js} +1 -1
- package/server/{pull-requests-kTRl7cWC.js → pull-requests-DyUkQl4W.js} +3 -3
- package/server/{rally-xCTZ-UcI.js → rally-BtWjybOa.js} +3 -3
- package/server/{rally-api-jOk4vit_.js → rally-api-B-YA_oXx.js} +1 -1
- package/server/{rally-api-DpZo3WwF.js → rally-api-B-osULNL.js} +2 -2
- package/server/{rally-client-BQjm3aaq.js → rally-client-CzFymP7P.js} +1 -1
- package/server/{rally-client-CoWZeVEj.js → rally-client-sErNQBO0.js} +5 -5
- package/server/rally-oggyp4cs.js +3 -0
- package/server/{reap-terminal-sessions-CAmURC2h.js → reap-terminal-sessions-2s-fvilz.js} +1 -1
- package/server/{reap-terminal-sessions-BwJb2DLJ.js → reap-terminal-sessions-DnDQ7pf9.js} +1 -1
- package/server/{rebuild-stack-BZ7j27xY.js → rebuild-stack-CoU-HqZj.js} +6 -6
- package/server/{rebuild-stack-r-cxcr26.js → rebuild-stack-nGD_nPZo.js} +1 -1
- package/server/{reconstruct-cache-13EkMg_i.js → reconstruct-cache-Wj8GrYB0.js} +6 -6
- package/server/record-lEYK__LD.js +3 -0
- package/server/recovery-trip-BT7nb9OV.js +98 -0
- package/server/{release-engine-YLTnEO_K.js → release-engine-CBLQXDlH.js} +5 -5
- package/server/{release-set-DwvT_Opu.js → release-set-BNiqi7hy.js} +3 -3
- package/server/{remote-B062S1Kc.js → remote-DW_TWQnr.js} +3 -3
- package/server/{remote-9u9WL0NJ.js → remote-DcO9An_f.js} +3 -3
- package/server/{remote-agents-BBTg-vLT.js → remote-agents-C4cyxHQS.js} +6 -6
- package/server/{remote-agents-B4eWgwNd.js → remote-agents-Cv-U-_V8.js} +1 -1
- package/server/{remote-completion-B0rmBnbL.js → remote-completion-PhXGGFQy.js} +13 -13
- package/server/resolver-C7dLn8OK.js +158 -0
- package/server/resume-BYZBIab-.js +3 -0
- package/server/{review-agent-tfR5RDzG.js → review-agent-Dn-yn7Zj.js} +23 -23
- package/server/{review-agent-Bf9XjLnl.js → review-agent-u5q3ip7H.js} +1 -1
- package/server/{review-artifacts-D0_IE6DQ.js → review-artifacts-D4T04aYI.js} +6 -6
- package/server/{review-context-CXO5ml5D.js → review-context-Rc0VDLG_.js} +8 -8
- package/server/{review-monitor-DfFUSx0J.js → review-monitor-BC--miCO.js} +2 -2
- package/server/{review-status-B0Axo-Ma.js → review-status-CG0iDuWa.js} +1 -1
- package/server/{review-status-BcfwcAcO.js → review-status-Dc1l4dVz.js} +18 -18
- package/server/{review-status-enrichment-Cp5kFTGl.js → review-status-enrichment-C1a1OG_1.js} +3 -3
- package/server/{review-status-sync-CEFty1pW.js → review-status-sync-CAr-hfnJ.js} +2 -2
- package/server/{review-status-sync-Bc0c_u12.js → review-status-sync-CfSixhhX.js} +1 -1
- package/server/{review-verdict-feedback-BOKRLLDf.js → review-verdict-feedback-CVDbIDBe.js} +7 -7
- package/server/{review-verdict-feedback-BnGtT0vN.js → review-verdict-feedback-D2Q960wX.js} +1 -1
- package/server/{rollup-BVqFuiS9.js → rollup-CakjztPl.js} +1 -1
- package/server/{rollup-C22QDVxh.js → rollup-DctoIu-A.js} +8 -8
- package/server/{scanner-BLCnuXqY.js → scanner-BTM1jc_u.js} +7 -7
- package/server/{sequence-io-BozlUb6f.js → sequence-io-pnoIDGv4.js} +4 -4
- package/server/{sequencer-agent-BEDEHfd2.js → sequencer-agent-9Soi-WL-.js} +10 -6
- package/server/server.js +1239 -1836
- package/server/{service-C8jKYZ0w.js → service-DfTVoqJg.js} +1668 -1408
- package/server/{session-fork-BDGksK4e.js → session-fork-C4jAFHj8.js} +2 -2
- package/server/session-history-DanhhiCw.js +168 -0
- package/server/{settings-CSLhh6qR.js → settings-Cuc12oP_.js} +2 -2
- package/server/settings-Cy7SCFO9.js +2 -0
- package/server/{settings-DrRQCren.js → settings-DfT3f86D.js} +3 -3
- package/server/{shadow-engineering-BMcBN-Of.js → shadow-engineering-bKuKTCn-.js} +3 -3
- package/server/{shadow-state-CHqWHD3T.js → shadow-state-CVHpRWlb.js} +1 -1
- package/server/{shadow-state-B4ayC7nx.js → shadow-state-DJz6_pb_.js} +2 -2
- package/server/{ship-log-CDVmOlZP.js → ship-log-CwqPFfqx.js} +1 -1
- package/server/{smart-compaction-V9DBrGcr.js → smart-compaction-B4KlpZGN.js} +1 -1
- package/server/{smart-compaction-C-F5sP9P.js → smart-compaction-CiWfGfRZ.js} +7 -7
- package/server/{spawn-planning-session-dSpOU0lP.js → spawn-planning-session-CU-DwyDl.js} +15 -15
- package/server/{spawn-planning-session-Bz4_JES2.js → spawn-planning-session-D5N3RuLG.js} +1 -1
- package/server/{specialist-completion-Coz3eTSr.js → specialist-completion-CJcv-NXH.js} +1 -1
- package/server/{specialist-context-fmb03vXQ.js → specialist-context-B2HlbNxS.js} +5 -5
- package/server/{specialist-handoff-logger-B9HK_lRu.js → specialist-handoff-logger-CQyF1ygu.js} +2 -2
- package/server/{specialist-logs-B24R2B9Y.js → specialist-logs-CdcUpfjj.js} +3 -3
- package/server/{specialists-CL7E8yUg.js → specialists-CFgw_0Q6.js} +1657 -113
- package/server/{specialists-BGNZ0_ZW.js → specialists-CwzFdapR.js} +20 -256
- package/server/{specialists-B6f5-5nG.js → specialists-D7AcFi1u.js} +1 -1
- package/server/{specialists-Chvvwf5z.js → specialists-k22_Btaq.js} +1 -1
- package/server/specs-RzcDgozr.js +3 -0
- package/server/{src-DGAEtABV.js → src-B4TTzNSr.js} +84 -2
- package/server/{src-BVxZuHMg.js → src-BvpMg4EB.js} +3 -3
- package/server/{src-D-VXCC9C.js → src-D4YUZrwR.js} +6 -6
- package/server/{stack-health-C-le5erA.js → stack-health-CSgcYcr8.js} +2 -2
- package/server/{stack-health-BKbTTOT2.js → stack-health-DVN7zRnL.js} +1 -1
- package/server/{state-home-CoyWsECg.js → state-home-CEvD_v5F.js} +6 -6
- package/server/{state-mapping-BvAgFK8n.js → state-mapping-C13UaCL7.js} +1 -1
- package/server/stuck-remediation-CxsyxwFO.js +1513 -0
- package/server/stuck-remediation-nTIvhGOR.js +2 -0
- package/server/{summary-fork-BF_0vxrq.js → summary-fork-CnRMPCYd.js} +2 -2
- package/server/{summary-fork-BlE30Q-I.js → summary-fork-gKSoFAnK.js} +8 -8
- package/server/{supervisor-BSeML_p0.js → supervisor-CZMs8s0M.js} +2 -2
- package/server/supervisor-CfX192ma.js +2 -0
- package/server/{swarm-readiness-CQ4sjrXc.js → swarm-readiness-h6-pwY8w.js} +3 -3
- package/server/{system-prerequisites-DnmLYOnU.js → system-prerequisites-DZZTi2G6.js} +1 -1
- package/server/{teardown-workspace-CpcjDrZ4.js → teardown-workspace-B4D-oCVg.js} +37 -68
- package/server/{test-agent-queue-Bq298zZl.js → test-agent-queue-CnuYku4B.js} +6 -6
- package/server/{test-verdict-CCDd25uN.js → test-verdict-CRsPpByy.js} +1 -1
- package/server/{test-verdict-Bm4uv_AA.js → test-verdict-CXMLATGc.js} +1 -1
- package/server/{tldr-daemon-Rn1Zue0P.js → tldr-daemon-CgZQ3jmZ.js} +1 -1
- package/server/{tldr-daemon-CaSUXd9r.js → tldr-daemon-CuA8hz21.js} +2 -2
- package/server/{tmux-CbcrkUx5.js → tmux-By8E_Yv_.js} +1 -1
- package/server/{tmux-BVc-NeKM.js → tmux-C3g9VFcg.js} +2 -2
- package/server/{token-D2OC_9ms.js → token-g_sJ19g3.js} +2 -2
- package/server/token-util-C9u4An3K.js +3 -0
- package/server/{token-util-CROoF5ic.js → token-util-DiwnnbJo.js} +1 -1
- package/server/{tracker-config-EsBoOWEy.js → tracker-config-BzfVj41Z.js} +1 -1
- package/server/{tracker-config-C1Y_2CIo.js → tracker-config-dQxC15vH.js} +2 -2
- package/server/{tracker-utils-lph9C3Pw.js → tracker-utils--z8FLSWn.js} +2 -2
- package/server/{tracker-utils-DSb9cfrF.js → tracker-utils-DkKyRT0H.js} +1 -1
- package/server/{traefik-connect-5EP1WS_Z.js → traefik-connect-CZYB3dlm.js} +1 -1
- package/server/{tunnel-BFBCeo97.js → tunnel-BpWZs0Jy.js} +3 -3
- package/server/{tunnel-DDe-ATFF.js → tunnel-CN2px87h.js} +1 -1
- package/server/{typed-errors-DSPn_xWP.js → typed-errors-CrN1AByg.js} +1 -1
- package/server/{typed-errors-BEJhbjJg.js → typed-errors-sLpGfegz.js} +1 -1
- package/server/{types-CE1m_daD.js → types-BLS_Zbmd.js} +1 -1
- package/server/{types-B5u3NxMl.js → types-tlXf80x3.js} +1 -1
- package/server/{uat-train-DtKHHy1U.js → uat-train-BRo6DM7p.js} +24 -24
- package/server/{uat-train-8T24rPzc.js → uat-train-utGVmqdm.js} +1 -1
- package/server/{validation-lyudzPvD.js → validation-CsPVsoA0.js} +4 -4
- package/server/{validation-COOXwNXU.js → validation-Dq_wwcw3.js} +1 -1
- package/server/{verification-runner-DO0nYTJx.js → verification-runner-CMT5WRMS.js} +12 -12
- package/server/{verification-runner-Dd1FX8Qk.js → verification-runner-D0Uh-9jc.js} +1 -1
- package/server/{vtt-parser-C57tEF7-.js → vtt-parser-C_XPlifj.js} +1 -1
- package/server/{work-agent-prompt-B2F3ZwKm.js → work-agent-prompt-ZTAb2Hyf.js} +12 -12
- package/server/workflows-BLwxUMP7.js +2 -0
- package/server/{workflows-Clz_AFwR.js → workflows-CXRwhw_o.js} +14 -14
- package/server/{workspace-D0rCFemz.js → workspace-9UEsKuiP.js} +1 -1
- package/server/{workspace-CY_2-Ddv.js → workspace-CNfuj1-5.js} +3 -3
- package/server/{workspace-config-DA8U97k9.js → workspace-config-ChWOECy0.js} +1 -1
- package/server/{workspace-manager-BIfpzqdU.js → workspace-manager-BO-6k3sP.js} +1 -1
- package/server/{workspace-manager-D6NgGkiF.js → workspace-manager-D8AByFIT.js} +13 -13
- package/server/{workspace-metadata-C8yn8kAH.js → workspace-metadata-CvC268MN.js} +2 -2
- package/server/{workspace-metadata-DfgR40YU.js → workspace-metadata-DZiJtnuF.js} +1 -1
- package/server/{workspaces-DOeHyKmA.js → workspaces-D-uIoqbg.js} +104 -122
- package/server/writer-BUweuRPx.js +3 -0
- package/server/writer-CrP2xuBn.js +251 -0
- package/sync-sources/hooks/ask-user-question-hook +7 -1
- package/sync-sources/hooks/record-cost-event.js +4 -3
- package/sync-sources/hooks/record-cost-event.js.map +1 -1
- package/server/agent-runtime-mirror-DrWxopNA.js +0 -37
- package/server/auto-commit-wZ9X7Isd.js +0 -3
- package/server/bd-mutex-BItGvLQ8.js +0 -35
- package/server/beads-restore-CMbDSB5R.js +0 -60
- package/server/cli-CqgiSAU8.js +0 -3
- package/server/deacon-swarm-B8pgK2a7.js +0 -2
- package/server/dist-BYSj075e.js +0 -3
- package/server/forge-CUCYK7h_.js +0 -2
- package/server/hume-x9voULey.js +0 -3
- package/server/inspect-agent-D8UBDgfR.js +0 -2
- package/server/merge-ops-B7m-hBEZ.js +0 -2
- package/server/projects-BqRMlDOv.js +0 -3
- package/server/providers-gZ0cUgQM.js +0 -3
- package/server/public/assets/chunk-BO2N2NFS-C-6y8VFI.js +0 -103
- package/server/public/assets/index-BDqWyuZQ.js +0 -796
- package/server/public/assets/index-CZiADHcL.css +0 -1
- package/server/public/assets/mermaid-GHXKKRXX-DRM2ANcR.js +0 -1
- package/server/rally-CRteqXe0.js +0 -3
- package/server/record-CJsjvRO4.js +0 -3
- package/server/resume-R7xkSmLW.js +0 -3
- package/server/settings-CSaXtkoA.js +0 -2
- package/server/specs-oaaYaA5c.js +0 -3
- package/server/supervisor-03Tj_Wra.js +0 -2
- package/server/token-util-xNaV-00f.js +0 -3
- package/server/workflows-B4fIN8VP.js +0 -2
|
@@ -1,46 +1,54 @@
|
|
|
1
1
|
import { a as __toCommonJS } from "./rolldown-runtime-DNCzzKnI.js";
|
|
2
|
-
import {
|
|
2
|
+
import { B as piProviderForModel, Br as encodeClaudeProjectDir, C as init_tier_table, D as canUseHarnessSync, Fn as init_projects, Jr as init_paths, N as UnknownModelError, R as getProviderForModelSync, T as resolveTieredExecutionEnabledForIssue, Z as resolveModel, Zr as resolveOhmypiExtensionPath, c as isClaudeCodeChannelsEnabled, dr as FsError, f as loadConfigSync, gn as shouldCommitLegacyWorkspaceArtifacts, ir as parseIssueIdSync, k as init_harness_policy, kn as getProjectSync, mn as init_state_read_home, nr as init_issue_id, qn as resolveProjectFromIssueSync, qr as getOverdeckHome, t as init_config_yaml, tr as extractPrefixSync, w as resolveTieredExecutionEnabled, xr as init_errors, z as init_providers, zn as listProjectsSync } from "./config-yaml-cNOZcEww.js";
|
|
3
3
|
import { a as init_activity_logger, n as emitActivityEntrySync } from "./activity-logger-pcbf-e30.js";
|
|
4
|
-
import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-
|
|
5
|
-
import { K as buildChildEnvWithoutTmuxSync, L as sessionExists, R as sessionExistsSync, i as capturePane, k as listSessions, q as init_child_env, w as killSession, x as init_tmux } from "./tmux-
|
|
6
|
-
import { a as init_event_store, n as getEventStore } from "./event-store-
|
|
7
|
-
import { C as normalizeChannelReplyPayload, a as getHarnessBehavior, b as DomainEvent, t as init_src } from "./src-
|
|
8
|
-
import { D as AGENT_COLUMNS_FOR_DB, M as stateToOverdeckParamsForDb, c as init_review_status, k as init_agent_state_sync, s as getReviewStatusSync, u as loadReviewStatuses, y as setReviewStatusSync } from "./review-status-
|
|
9
|
-
import { a as EventStoreService, c as ReadModelService, d as init_read_model, i as init_issue_lifecycle, s as init_domain_services, t as IssueLifecycle, u as getClosedIssueIdsForReadSource } from "./issue-lifecycle
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { t as
|
|
13
|
-
import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import { a as
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { i as
|
|
25
|
-
import { a as
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import { c as
|
|
32
|
-
import {
|
|
4
|
+
import { d as getOverdeckDatabaseSync, f as init_infra } from "./infra-Ba9mS6e6.js";
|
|
5
|
+
import { F as sendKeysAsync, I as sendRawKeystroke, K as buildChildEnvWithoutTmuxSync, L as sessionExists, O as listSessionNames, R as sessionExistsSync, S as isHarnessProcessAlive, W as BLANKED_PROVIDER_ENV, d as exactPaneTarget, i as capturePane, k as listSessions, o as createSession, p as findManagedServerPidSync, q as init_child_env, w as killSession, x as init_tmux, z as setOption } from "./tmux-C3g9VFcg.js";
|
|
6
|
+
import { a as init_event_store, n as getEventStore } from "./event-store-D89fMfXS.js";
|
|
7
|
+
import { C as normalizeChannelReplyPayload, a as getHarnessBehavior, b as DomainEvent, t as init_src } from "./src-B4TTzNSr.js";
|
|
8
|
+
import { D as AGENT_COLUMNS_FOR_DB, M as stateToOverdeckParamsForDb, c as init_review_status, k as init_agent_state_sync, s as getReviewStatusSync, u as loadReviewStatuses, y as setReviewStatusSync } from "./review-status-Dc1l4dVz.js";
|
|
9
|
+
import { a as EventStoreService, c as ReadModelService, d as init_read_model, i as init_issue_lifecycle, s as init_domain_services, t as IssueLifecycle, u as getClosedIssueIdsForReadSource } from "./issue-lifecycle--8MRxrG1.js";
|
|
10
|
+
import { n as ensureSessionContextBriefingFile, r as init_briefing_freshness } from "./briefing-freshness-DESTCWo-.js";
|
|
11
|
+
import { o as getPricingSync, s as init_cost, t as calculateCostSync } from "./cost-D9kPIY_5.js";
|
|
12
|
+
import { $n as writeAgentStateJsonSync, An as getAgentDir, At as init_bridge_token, Bt as resolveStaffing, C as restartAgent, Ct as deliverAgentPermissionDecision, E as resumeAgent, En as clearAgentTroubled, Et as setAgentDeliveryMethod, F as getActivity, Gt as getAgentRuntimeBaseCommand, I as getLatestSessionId, Jn as saveAgentStateSync, Jt as getProviderAuthMode, K as stopAgent, L as getLatestSessionIdSync, Lt as transitionIssueToInProgress, M as init_operator_interventions, Mn as getAgentState, N as operatorInterventionEvent, Pn as getAgentStateSync, Qn as wipeAgentStateDirs, R as getSessionId, S as recoverAgent, St as deliverAgentMessage, Vn as markAgentStoppedState, X as listAgentStates, Yn as setAgentPaused, Z as listRunningAgents, _t as init_config, ar as resolvePtySupervisorScriptPath, bn as writeChannelsBridgeMcpConfig, cn as determineModel, cr as normalizeAgentId, dr as waitForReadySignal, en as init_pi_fifo, fn as init_provider_health, fr as getAgentRuntimeState, g as init_tier_escalation, h as decideEscalation, hr as saveAgentRuntimeState, ir as init_pty_supervisor_locate, j as messageAgent, jt as writeBridgeTokenSync, ln as getProviderEnvForModel, nn as init_ohmypi_fifo, nr as init_pty_token, or as clearReadySignal, pn as validateProviderHealth, pr as getAgentRuntimeStateSync, qn as saveAgentState, rn as ohmypiFifoPaths, rr as writePtyToken, t as init_agents, tn as piFifoPaths, un as getProviderExportsForModel, wn as clearAgentPaused, yn as dismissDevChannelsDialog, yt as loadCloisterConfigSync, z as saveSessionId, zt as init_staffing } from "./agents-B9GwfCyp.js";
|
|
13
|
+
import { a as performHandoff, i as checkAllTriggers, s as getAgentHealth, t as getCloisterService } from "./service-DfTVoqJg.js";
|
|
14
|
+
import { o as init_persistent_logger, s as logAgentLifecycleSync } from "./session-history-DanhhiCw.js";
|
|
15
|
+
import { t as init_behavior } from "./behavior-BTpYbAv4.js";
|
|
16
|
+
import { a as init_tracker_config, t as getGitHubConfig } from "./tracker-config-dQxC15vH.js";
|
|
17
|
+
import { n as init_issue_service_singleton, r as issue_service_singleton_exports } from "./issue-service-singleton-CkTUI-eu.js";
|
|
18
|
+
import { a as PAN_DIRNAME, i as PAN_CONTINUE_FILENAME, p as init_types } from "./types-BLS_Zbmd.js";
|
|
19
|
+
import { F as generateVBriefFilename, I as init_lifecycle, f as readTierOverrides, g as serializeVBriefDocument, h as recordTierPromotion, m as readWorkspacePlanSync, o as init_io, r as findPlan, u as readPlan } from "./io-VEGSAqsd.js";
|
|
20
|
+
import { d as getCliproxyAuthDir, g as getCliproxyLogPath, l as decodeJwtPayload, v as init_cliproxy } from "./cliproxy-B4c1BUvs.js";
|
|
21
|
+
import { B as removeFavorite, E as hasOtherActiveConversationOnTmuxSession, F as markConversationEnded, G as setConversationHarness, K as setConversationModel, M as listConversations, P as markConversationActive, Q as updateConversationDeliveryMethod, S as getConversationByName, W as setConversationEffort, g as backfillConversationModel, h as archiveConversation, k as init_conversations, nt as updateLastAttached, rt as updateSpawnError, x as getConversationById, y as createConversation } from "./conversations-B-lcHv-5.js";
|
|
22
|
+
import { _ as init_codex_conversation_parser, c as parseEntireConversation, d as init_pi_conversation_parser, g as parseOhmypiConversationMessages, m as init_ohmypi_conversation_parser, p as parsePiConversationMessages, t as init_conversation_service, v as parseCodexConversationMessages } from "./conversation-service-B8LFQQm1.js";
|
|
23
|
+
import { n as recordFeatureRegistryLifecycle, t as init_feature_registry_population } from "./feature-registry-population-CqZSnqQi.js";
|
|
24
|
+
import { a as init_launcher_generator, c as requireModelOverrideSync, i as generateLauncherScriptSync, n as init_state_home, o as init_model_validation, r as isStateMigrated, s as normalizeModelOverrideSync } from "./state-home-CEvD_v5F.js";
|
|
25
|
+
import { a as init_claude_permissions, o as resolvePermissionModeSync, r as getClaudePermissionFlagsStringSync, t as BYPASS_PERMISSION_MODE } from "./claude-permissions-CThLgf8d.js";
|
|
26
|
+
import { h as workspaceContextFile, l as piGlobalContextFile, o as init_layers } from "./layers-jjL16At6.js";
|
|
27
|
+
import { a as init_stack_health, i as getWorkspaceStackHealth } from "./stack-health-CSgcYcr8.js";
|
|
28
|
+
import { s as getDispatchableItems, u as init_dag } from "./dag-DKI3sOmX.js";
|
|
29
|
+
import { i as queryBeadById, r as init_beads_query } from "./beads-query-Dl4FhT8J.js";
|
|
30
|
+
import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-C6g2R0Cd.js";
|
|
31
|
+
import { a as init_lifecycle_io, c as updatePlanStatus, s as transitionVBriefOnMain } from "./lifecycle-io-IehcRt2v.js";
|
|
32
|
+
import { _ as parseCodexApprovalPrompt, d as getAgentPendingQuestions$1, f as getAgentWorkspace$1, g as normalizeAwaitingInputPrompt, m as detectAwaitingInputForAgent, o as getRuntimeForAgent, p as scanPendingInputsPromise, s as computeAgentEnrichment, u as getAgentJsonlPath$1 } from "./stuck-remediation-CxsyxwFO.js";
|
|
33
|
+
import { Ht as getResourceConfig, Lt as classifyMemoryPressure, Q as createInFlightGuard, Ut as getSystemHealthSnapshot } from "./deacon-DdARMjZq.js";
|
|
34
|
+
import { o as stopWorkspaceDocker, r as init_workspace_manager } from "./workspace-manager-D8AByFIT.js";
|
|
35
|
+
import { a as init_jsonl_parser, o as normalizeModelName } from "./jsonl-parser-BUW5_sSK.js";
|
|
36
|
+
import { a as loadWorkspaceMetadataSync } from "./workspace-metadata-CvC268MN.js";
|
|
37
|
+
import { c as syncBeadStatusToVBrief, l as withBdMutex } from "./beads-BeSMEjGT.js";
|
|
38
|
+
import { basename, delimiter, dirname, join, resolve } from "node:path";
|
|
33
39
|
import { Cause, Data, Effect, Layer, Option, Schema, Stream } from "effect";
|
|
34
40
|
import { join as join$1 } from "path";
|
|
35
41
|
import { homedir } from "node:os";
|
|
36
|
-
import { existsSync } from "node:fs";
|
|
42
|
+
import { createReadStream, existsSync, mkdirSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
|
|
37
43
|
import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
|
|
38
|
-
import { access, appendFile, cp, lstat, mkdir, open, readFile, readdir, rm, stat as stat$1, writeFile } from "node:fs/promises";
|
|
44
|
+
import { access, appendFile, chmod, cp, lstat, mkdir, open, readFile, readdir, realpath, rename, rm, stat as stat$1, writeFile } from "node:fs/promises";
|
|
39
45
|
import { randomUUID } from "crypto";
|
|
40
46
|
import { mkdir as mkdir$1, open as open$1, readFile as readFile$1, stat as stat$2, writeFile as writeFile$1 } from "fs/promises";
|
|
41
47
|
import { exec, execFile, spawn } from "node:child_process";
|
|
42
48
|
import { promisify } from "node:util";
|
|
43
|
-
import { timingSafeEqual } from "node:crypto";
|
|
49
|
+
import { randomUUID as randomUUID$1, timingSafeEqual } from "node:crypto";
|
|
50
|
+
import { pathToFileURL } from "node:url";
|
|
51
|
+
import { createInterface } from "node:readline";
|
|
44
52
|
import * as Result$1 from "effect/Result";
|
|
45
53
|
//#region http-helpers.ts
|
|
46
54
|
init_paths();
|
|
@@ -65,10 +73,11 @@ function jsonStringResponse(json, options) {
|
|
|
65
73
|
});
|
|
66
74
|
}
|
|
67
75
|
//#endregion
|
|
68
|
-
//#region
|
|
76
|
+
//#region ../../lib/default-cwd.ts
|
|
69
77
|
init_activity_logger();
|
|
70
78
|
init_issue_id();
|
|
71
79
|
init_projects();
|
|
80
|
+
init_providers();
|
|
72
81
|
init_harness_policy();
|
|
73
82
|
init_tier_table();
|
|
74
83
|
init_config_yaml();
|
|
@@ -79,12 +88,34 @@ init_event_store();
|
|
|
79
88
|
init_src();
|
|
80
89
|
init_agent_state_sync();
|
|
81
90
|
init_read_model();
|
|
91
|
+
init_briefing_freshness();
|
|
82
92
|
init_domain_services();
|
|
93
|
+
init_cost();
|
|
94
|
+
init_behavior();
|
|
95
|
+
init_persistent_logger();
|
|
96
|
+
init_tracker_config();
|
|
83
97
|
init_types();
|
|
84
98
|
init_review_status();
|
|
85
99
|
init_lifecycle();
|
|
86
100
|
init_io();
|
|
87
101
|
/**
|
|
102
|
+
* Resolve the default cwd used when no project is selected — the "devroot"
|
|
103
|
+
* fallback. Prefers `~/Projects` if it exists, otherwise the user's home
|
|
104
|
+
* directory.
|
|
105
|
+
*
|
|
106
|
+
* Replaces the deprecated `sync.devroot` config (PAN-1201) for the runtime
|
|
107
|
+
* code paths that still need a sensible default working directory:
|
|
108
|
+
*
|
|
109
|
+
* - New conversations created without a `projectKey` (conversations.ts)
|
|
110
|
+
* - The global editor-launcher in the dashboard top bar (PanOpenInPicker)
|
|
111
|
+
*/
|
|
112
|
+
function getDefaultCwd() {
|
|
113
|
+
const candidate = join(homedir(), "Projects");
|
|
114
|
+
return existsSync(candidate) ? candidate : homedir();
|
|
115
|
+
}
|
|
116
|
+
//#endregion
|
|
117
|
+
//#region routes/http-handler.ts
|
|
118
|
+
/**
|
|
88
119
|
* httpHandler — typed error-to-HTTP mapping wrapper (PAN-470)
|
|
89
120
|
*
|
|
90
121
|
* Wraps an Effect route body and catches all typed TaggedErrors,
|
|
@@ -153,8 +184,6 @@ function httpHandler(effect) {
|
|
|
153
184
|
}
|
|
154
185
|
//#endregion
|
|
155
186
|
//#region ../../lib/codex-auth.ts
|
|
156
|
-
init_cost();
|
|
157
|
-
init_tracker_config();
|
|
158
187
|
init_cliproxy();
|
|
159
188
|
/** Wrapper error for Codex auth probing — preserves the underlying cause. */
|
|
160
189
|
var CodexAuthCheckError = class extends Data.TaggedError("CodexAuthCheckError") {};
|
|
@@ -408,19 +437,92 @@ function isTrustedOriginForHost(origin, _host) {
|
|
|
408
437
|
return getTrustedOrigins().includes(normalized);
|
|
409
438
|
}
|
|
410
439
|
//#endregion
|
|
440
|
+
//#region services/pending-respawn.ts
|
|
441
|
+
/**
|
|
442
|
+
* Tracks tmux sessions that are mid-respawn so that `/ws/terminal`
|
|
443
|
+
* reconnects landing in the kill→spawn gap don't get a fatal 4404.
|
|
444
|
+
*
|
|
445
|
+
* Background: switch-model, resume, and restart-all all do
|
|
446
|
+
*
|
|
447
|
+
* await killSessionAsync(name) // old tmux dies, PTY exits,
|
|
448
|
+
* // ws-terminal closes clients with 1000
|
|
449
|
+
* await spawnConversationSession(...) // new tmux session with same name
|
|
450
|
+
* await waitForTmuxSession(name)
|
|
451
|
+
* await waitForReadySignal(name, 30) // up to 30s
|
|
452
|
+
*
|
|
453
|
+
* The terminal frontend (`XTerminal.tsx`) reconnects on close 1000 with
|
|
454
|
+
* exponential backoff (1s, 2s, 4s, 8s, 16s). Every reconnect re-enters
|
|
455
|
+
* `ws-terminal.ts`, which checks the tmux session list upfront and
|
|
456
|
+
* `close(4404, 'session-not-found')` if the name isn't there yet.
|
|
457
|
+
* 4404 is treated as fatal on the client (no retry — the session is
|
|
458
|
+
* presumed gone), so the panel sticks on "Could not reconnect" even
|
|
459
|
+
* after the respawn completes a moment later.
|
|
460
|
+
*
|
|
461
|
+
* This registry lets `ws-terminal.ts` distinguish "transient absence
|
|
462
|
+
* during respawn" from "session is actually gone". Respawn sites mark
|
|
463
|
+
* the name with `markRespawnPending()` for the duration of their kill+
|
|
464
|
+
* spawn block; ws-terminal calls `waitForSessionRespawn()` instead of
|
|
465
|
+
* 4404-ing immediately when the session is missing AND
|
|
466
|
+
* `isRespawnPending()` is true.
|
|
467
|
+
*/
|
|
468
|
+
const pendingRespawns = /* @__PURE__ */ new Set();
|
|
469
|
+
/**
|
|
470
|
+
* Mark a tmux session as mid-respawn. The returned `done()` must run in
|
|
471
|
+
* a `finally` so the marker is cleared even if the respawn throws.
|
|
472
|
+
*/
|
|
473
|
+
function markRespawnPending(sessionName) {
|
|
474
|
+
pendingRespawns.add(sessionName);
|
|
475
|
+
return { done: () => {
|
|
476
|
+
pendingRespawns.delete(sessionName);
|
|
477
|
+
} };
|
|
478
|
+
}
|
|
479
|
+
/** True if a respawn is currently in progress for this session name. */
|
|
480
|
+
function isRespawnPending(sessionName) {
|
|
481
|
+
return pendingRespawns.has(sessionName);
|
|
482
|
+
}
|
|
483
|
+
const POLL_INTERVAL_MS = 200;
|
|
484
|
+
/**
|
|
485
|
+
* Poll for the session to come back, up to `timeoutMs`. Returns whether
|
|
486
|
+
* it did. Bails early once `pendingRespawns` no longer contains the name
|
|
487
|
+
* (the wrapping respawn block finished, success or failure) and the
|
|
488
|
+
* session still isn't there — at that point the absence is real.
|
|
489
|
+
*
|
|
490
|
+
* `timeoutMs` should comfortably exceed the longest respawn window. The
|
|
491
|
+
* dominant cost is `waitForReadySignal`'s 30s ceiling in the conversation
|
|
492
|
+
* routes, so 35s is the right default for those callers.
|
|
493
|
+
*/
|
|
494
|
+
async function waitForSessionRespawn(sessionName, timeoutMs) {
|
|
495
|
+
const deadline = Date.now() + timeoutMs;
|
|
496
|
+
while (Date.now() < deadline) {
|
|
497
|
+
if (await Effect.runPromise(sessionExists(sessionName))) return true;
|
|
498
|
+
if (!pendingRespawns.has(sessionName)) return Effect.runPromise(sessionExists(sessionName));
|
|
499
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
|
|
500
|
+
}
|
|
501
|
+
return Effect.runPromise(sessionExists(sessionName));
|
|
502
|
+
}
|
|
503
|
+
//#endregion
|
|
411
504
|
//#region routes/jsonl-resolver.ts
|
|
505
|
+
init_conversations();
|
|
412
506
|
init_codex_conversation_parser();
|
|
413
507
|
init_ohmypi_conversation_parser();
|
|
414
508
|
init_pi_conversation_parser();
|
|
415
509
|
init_conversation_service();
|
|
416
|
-
|
|
510
|
+
init_pty_supervisor_locate();
|
|
511
|
+
init_pty_token();
|
|
417
512
|
init_feature_registry_population();
|
|
418
513
|
init_model_validation();
|
|
419
514
|
init_provider_health();
|
|
515
|
+
init_claude_permissions();
|
|
516
|
+
init_layers();
|
|
517
|
+
init_ohmypi_fifo();
|
|
518
|
+
init_pi_fifo();
|
|
420
519
|
init_stack_health();
|
|
520
|
+
init_launcher_generator();
|
|
421
521
|
init_dag();
|
|
422
522
|
init_staffing();
|
|
423
523
|
init_beads_query();
|
|
524
|
+
init_harness_resolve();
|
|
525
|
+
init_bridge_token();
|
|
424
526
|
init_operator_interventions();
|
|
425
527
|
init_lifecycle_io();
|
|
426
528
|
/**
|
|
@@ -435,7 +537,8 @@ init_lifecycle_io();
|
|
|
435
537
|
*
|
|
436
538
|
* For claude-code agents the JSONL filename is the *Claude session ID* (a UUID
|
|
437
539
|
* written by Claude Code itself), NOT the agent/tmux name. Lookup order:
|
|
438
|
-
* 1. session.id —
|
|
540
|
+
* 1. session.id — UUID pinned before fresh work-agent launch and updated
|
|
541
|
+
* by auto-suspend/resume flows
|
|
439
542
|
* 2. sessions.json — array of UUIDs the agent has used; the heartbeat hook
|
|
440
543
|
* APPENDS new IDs and dedupes, so once a session has been
|
|
441
544
|
* seen its array index never moves. After a `pan resume`
|
|
@@ -448,7 +551,13 @@ init_lifecycle_io();
|
|
|
448
551
|
* server's event loop.
|
|
449
552
|
*/
|
|
450
553
|
init_agents();
|
|
451
|
-
|
|
554
|
+
const transcriptResolutionSignatures = /* @__PURE__ */ new Map();
|
|
555
|
+
function logTranscriptResolution(agentId, signature, message, opts) {
|
|
556
|
+
const signatureKey = `${agentId}:${opts.agentsDirOverride ?? "live"}`;
|
|
557
|
+
if (transcriptResolutionSignatures.get(signatureKey) === signature) return;
|
|
558
|
+
transcriptResolutionSignatures.set(signatureKey, signature);
|
|
559
|
+
(opts.logDiagnostic ?? (opts.agentsDirOverride ? void 0 : logAgentLifecycleSync))?.(agentId, `transcript resolution: ${message}`);
|
|
560
|
+
}
|
|
452
561
|
async function pathExists$1(p) {
|
|
453
562
|
return access(p).then(() => true, () => false);
|
|
454
563
|
}
|
|
@@ -629,7 +738,7 @@ async function resolveCodexRolloutPath(agentId, opts = {}) {
|
|
|
629
738
|
const agentDir = join(opts.agentsDirOverride ?? join(getOverdeckHome(), "agents"), agentId);
|
|
630
739
|
const codexHome = join(agentDir, "codex-home");
|
|
631
740
|
if (!await pathExists$1(codexHome)) return null;
|
|
632
|
-
const { findRolloutPath, findLatestRollout } = await import("./codex-
|
|
741
|
+
const { findRolloutPath, findLatestRollout } = await import("./codex-BYPz6ZdF.js");
|
|
633
742
|
const threadId = (await readOptional(join(agentDir, "codex-thread-id")))?.trim();
|
|
634
743
|
if (threadId) {
|
|
635
744
|
const rollout = findRolloutPath(codexHome, threadId);
|
|
@@ -681,18 +790,27 @@ async function resolvePiSessionPath(agentId, opts = {}) {
|
|
|
681
790
|
* the corresponding JSONL file exists; otherwise null.
|
|
682
791
|
*/
|
|
683
792
|
async function resolveJsonlPath(agentId, workspacePath, opts = {}) {
|
|
684
|
-
const
|
|
793
|
+
const harness = await resolveAgentHarness(agentId, opts);
|
|
794
|
+
const behavior = behaviorForHarness(harness);
|
|
685
795
|
if (behavior.transcriptKind === "codex-rollout-jsonl") return resolveCodexRolloutPath(agentId, opts);
|
|
686
796
|
if (behavior.transcriptKind === "ohmypi-jsonl") return resolvePiSessionPath(agentId, opts);
|
|
687
797
|
const claudeSessionId = await resolveClaudeSessionId(agentId, opts);
|
|
688
|
-
if (!claudeSessionId)
|
|
798
|
+
if (!claudeSessionId) {
|
|
799
|
+
logTranscriptResolution(agentId, `missing-session-id:${harness ?? "unknown"}`, `failed harness=${harness ?? "unknown"} reason=no-session-id checked=session.id,sessions.json,runtime-state`, opts);
|
|
800
|
+
return null;
|
|
801
|
+
}
|
|
689
802
|
const jsonlPath = join(opts.claudeProjectsDirOverride ?? join(homedir(), ".claude", "projects"), encodeClaudeProjectDir(workspacePath), `${claudeSessionId}.jsonl`);
|
|
690
|
-
if (await pathExists$1(jsonlPath))
|
|
803
|
+
if (await pathExists$1(jsonlPath)) {
|
|
804
|
+
logTranscriptResolution(agentId, `resolved:${jsonlPath}`, `resolved harness=${harness ?? "claude-code"} sessionId=${claudeSessionId} path=${jsonlPath}`, opts);
|
|
805
|
+
return jsonlPath;
|
|
806
|
+
}
|
|
807
|
+
logTranscriptResolution(agentId, `missing-jsonl:${jsonlPath}`, `failed harness=${harness ?? "claude-code"} reason=jsonl-missing sessionId=${claudeSessionId} workspace=${workspacePath} expectedPath=${jsonlPath}`, opts);
|
|
691
808
|
return null;
|
|
692
809
|
}
|
|
693
810
|
//#endregion
|
|
694
811
|
//#region ../../lib/work-agent-lifecycle.ts
|
|
695
812
|
init_agents();
|
|
813
|
+
init_tmux();
|
|
696
814
|
async function pathExists(path) {
|
|
697
815
|
try {
|
|
698
816
|
await access(path);
|
|
@@ -861,7 +979,7 @@ const getWorkAgentLifecycleState = (agentOrIssueId) => Effect.promise(() => getW
|
|
|
861
979
|
*
|
|
862
980
|
* All I/O is async (execFile / fs.promises). No execSync, no readFileSync.
|
|
863
981
|
*/
|
|
864
|
-
const execFileAsync$
|
|
982
|
+
const execFileAsync$3 = promisify(execFile);
|
|
865
983
|
const GIT_INFO_TTL_MS = 3e4;
|
|
866
984
|
const GIT_INFO_TIMEOUT_MS = 2e3;
|
|
867
985
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -902,7 +1020,7 @@ async function readHeadMtimeMs(absPath) {
|
|
|
902
1020
|
}
|
|
903
1021
|
}
|
|
904
1022
|
async function readGitInfoFresh(absPath) {
|
|
905
|
-
const [branchResult, worktreeResult] = await Promise.all([execFileAsync$
|
|
1023
|
+
const [branchResult, worktreeResult] = await Promise.all([execFileAsync$3("git", [
|
|
906
1024
|
"-C",
|
|
907
1025
|
absPath,
|
|
908
1026
|
"rev-parse",
|
|
@@ -911,7 +1029,7 @@ async function readGitInfoFresh(absPath) {
|
|
|
911
1029
|
], {
|
|
912
1030
|
encoding: "utf-8",
|
|
913
1031
|
timeout: GIT_INFO_TIMEOUT_MS
|
|
914
|
-
}).catch(() => null), execFileAsync$
|
|
1032
|
+
}).catch(() => null), execFileAsync$3("git", [
|
|
915
1033
|
"-C",
|
|
916
1034
|
absPath,
|
|
917
1035
|
"rev-parse",
|
|
@@ -986,8 +1104,8 @@ async function resolveAgentGitInfo(workspacePath, expectedBranch) {
|
|
|
986
1104
|
init_paths();
|
|
987
1105
|
init_agents();
|
|
988
1106
|
init_tmux();
|
|
989
|
-
const execAsync$
|
|
990
|
-
const execFileAsync$
|
|
1107
|
+
const execAsync$2 = promisify(exec);
|
|
1108
|
+
const execFileAsync$2 = promisify(execFile);
|
|
991
1109
|
function buildPanStartArgs(input) {
|
|
992
1110
|
return [
|
|
993
1111
|
"start",
|
|
@@ -1335,9 +1453,9 @@ async function getGitStatusAsync(workspacePath) {
|
|
|
1335
1453
|
try {
|
|
1336
1454
|
if (!existsSync(workspacePath)) return null;
|
|
1337
1455
|
const [branchResult, uncommittedResult, commitResult] = await Promise.all([
|
|
1338
|
-
execAsync$
|
|
1339
|
-
execAsync$
|
|
1340
|
-
execAsync$
|
|
1456
|
+
execAsync$2("git rev-parse --abbrev-ref HEAD 2>/dev/null || echo \"\"", { cwd: workspacePath }),
|
|
1457
|
+
execAsync$2("git status --porcelain 2>/dev/null | wc -l", { cwd: workspacePath }),
|
|
1458
|
+
execAsync$2("git log -1 --pretty=format:\"%s\" 2>/dev/null || echo \"\"", { cwd: workspacePath })
|
|
1341
1459
|
]);
|
|
1342
1460
|
const branch = branchResult.stdout.trim();
|
|
1343
1461
|
const uncommitted = uncommittedResult.stdout.trim();
|
|
@@ -1720,7 +1838,7 @@ const getAgentOutputRoute = HttpRouter.add("GET", "/api/agents/:id/output", http
|
|
|
1720
1838
|
} catch {}
|
|
1721
1839
|
let stdout;
|
|
1722
1840
|
if (isRemote && vmName) {
|
|
1723
|
-
const { getRemoteAgentOutput } = await import("./remote-agents-
|
|
1841
|
+
const { getRemoteAgentOutput } = await import("./remote-agents-Cv-U-_V8.js");
|
|
1724
1842
|
stdout = await getRemoteAgentOutput(id, vmName, parseInt(String(lines), 10) || 100);
|
|
1725
1843
|
} else stdout = await Effect.runPromise(capturePane(id, parseInt(String(lines), 10) || 100));
|
|
1726
1844
|
if (!stdout || stdout.trim() === "" || stdout.trim() === "Session not found") {
|
|
@@ -1821,7 +1939,7 @@ const getAgentFilesRoute = HttpRouter.add("GET", "/api/agents/:id/files", httpHa
|
|
|
1821
1939
|
if (!agentState?.workspace) return jsonResponse({ files: [] });
|
|
1822
1940
|
const workspacePath = agentState.workspace;
|
|
1823
1941
|
if (!existsSync(workspacePath)) return jsonResponse({ files: [] });
|
|
1824
|
-
const { stdout } = yield* Effect.promise(() => execAsync$
|
|
1942
|
+
const { stdout } = yield* Effect.promise(() => execAsync$2("git diff --name-status HEAD 2>/dev/null || git status --porcelain 2>/dev/null || echo \"\"", {
|
|
1825
1943
|
cwd: workspacePath,
|
|
1826
1944
|
encoding: "utf-8"
|
|
1827
1945
|
}));
|
|
@@ -1908,6 +2026,1403 @@ const postAgentPokeRoute = HttpRouter.add("POST", "/api/agents/:id/poke", httpHa
|
|
|
1908
2026
|
});
|
|
1909
2027
|
})));
|
|
1910
2028
|
//#endregion
|
|
2029
|
+
//#region ../../lib/runtimes/conversation-control.ts
|
|
2030
|
+
function conversationControlPaths(agentId, home) {
|
|
2031
|
+
const { agentDir } = ohmypiFifoPaths(agentId, home);
|
|
2032
|
+
return {
|
|
2033
|
+
agentDir,
|
|
2034
|
+
controlDir: join(agentDir, "control")
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
2037
|
+
async function writeConversationControlCommand(agentId, command, home) {
|
|
2038
|
+
const paths = conversationControlPaths(agentId, home);
|
|
2039
|
+
await mkdir(paths.controlDir, {
|
|
2040
|
+
recursive: true,
|
|
2041
|
+
mode: 448
|
|
2042
|
+
});
|
|
2043
|
+
await chmod(paths.controlDir, 448);
|
|
2044
|
+
const finalPath = join(paths.controlDir, `${Date.now()}-${command.id}-${randomUUID$1()}.json`);
|
|
2045
|
+
const tempPath = `${finalPath}.tmp`;
|
|
2046
|
+
await writeFile(tempPath, `${JSON.stringify(command)}\n`, {
|
|
2047
|
+
encoding: "utf8",
|
|
2048
|
+
mode: 384
|
|
2049
|
+
});
|
|
2050
|
+
await chmod(tempPath, 384);
|
|
2051
|
+
await rename(tempPath, finalPath);
|
|
2052
|
+
await chmod(finalPath, 384);
|
|
2053
|
+
return finalPath;
|
|
2054
|
+
}
|
|
2055
|
+
//#endregion
|
|
2056
|
+
//#region ../../lib/ohmypi-codex-auth.ts
|
|
2057
|
+
/**
|
|
2058
|
+
* oh-my-pi (omp) OAuth helpers for the openai-codex (ChatGPT subscription) provider.
|
|
2059
|
+
* Forked from pi-codex-auth.ts (PAN-1520) for the omp migration (PAN-1989).
|
|
2060
|
+
*
|
|
2061
|
+
* omp stores its general credentials in ~/.omp/agent/agent.db (SQLite). The
|
|
2062
|
+
* openai-codex OAuth credential is persisted separately at ~/.omp/agent/auth.json
|
|
2063
|
+
* (same JSON format as Pi's ~/.pi/agent/auth.json) so that this helper's
|
|
2064
|
+
* read/write logic is unchanged from the Pi side.
|
|
2065
|
+
*
|
|
2066
|
+
* This module:
|
|
2067
|
+
* - reads/writes the openai-codex credential under ~/.omp/agent/auth.json,
|
|
2068
|
+
* - reports auth status (for `pan doctor` and the conversation spawn gate),
|
|
2069
|
+
* - refreshes an expired-but-recoverable token, and
|
|
2070
|
+
* - drives omp's headless device-code login to re-authenticate.
|
|
2071
|
+
*
|
|
2072
|
+
* The OAuth module is loaded from the installed `omp` package dynamically.
|
|
2073
|
+
* A missing or relocated omp OAuth module degrades to 'unavailable' — never throws.
|
|
2074
|
+
*/
|
|
2075
|
+
const PI_CODEX_PROVIDER = "openai-codex";
|
|
2076
|
+
function getOhmypiAuthPath() {
|
|
2077
|
+
return join(homedir(), ".omp", "agent", "auth.json");
|
|
2078
|
+
}
|
|
2079
|
+
function readAuthFile() {
|
|
2080
|
+
const path = getOhmypiAuthPath();
|
|
2081
|
+
if (!existsSync(path)) return {};
|
|
2082
|
+
try {
|
|
2083
|
+
const parsed = JSON.parse(readFileSync(path, "utf-8"));
|
|
2084
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
2085
|
+
} catch {
|
|
2086
|
+
return {};
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
function readOhmypiCodexCredential() {
|
|
2090
|
+
const cred = readAuthFile()[PI_CODEX_PROVIDER];
|
|
2091
|
+
if (cred && typeof cred === "object" && typeof cred["access"] === "string" && typeof cred["refresh"] === "string") return cred;
|
|
2092
|
+
return null;
|
|
2093
|
+
}
|
|
2094
|
+
/** Atomically merge a fresh codex credential into auth.json (mode 0600). */
|
|
2095
|
+
function writeOhmypiCodexCredential(tokens) {
|
|
2096
|
+
const path = getOhmypiAuthPath();
|
|
2097
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
2098
|
+
const auth = readAuthFile();
|
|
2099
|
+
const stored = {
|
|
2100
|
+
type: "oauth",
|
|
2101
|
+
access: tokens.access,
|
|
2102
|
+
refresh: tokens.refresh,
|
|
2103
|
+
expires: tokens.expires,
|
|
2104
|
+
accountId: tokens.accountId
|
|
2105
|
+
};
|
|
2106
|
+
auth[PI_CODEX_PROVIDER] = stored;
|
|
2107
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
2108
|
+
writeFileSync(tmp, JSON.stringify(auth, null, 2), { mode: 384 });
|
|
2109
|
+
renameSync(tmp, path);
|
|
2110
|
+
return stored;
|
|
2111
|
+
}
|
|
2112
|
+
/** Locate the `omp` executable on PATH (no subprocess — event-loop safe). */
|
|
2113
|
+
function findOmpBinary() {
|
|
2114
|
+
for (const dir of (process.env["PATH"] ?? "").split(delimiter)) {
|
|
2115
|
+
if (!dir) continue;
|
|
2116
|
+
const candidate = join(dir, "omp");
|
|
2117
|
+
if (existsSync(candidate)) return candidate;
|
|
2118
|
+
}
|
|
2119
|
+
return null;
|
|
2120
|
+
}
|
|
2121
|
+
/** omp is @oh-my-pi/pi-coding-agent; the oauth submodule path mirrors pi-ai's layout. */
|
|
2122
|
+
const OMP_AI_OAUTH_SUBPATH = join("@oh-my-pi", "pi-ai", "dist", "utils", "oauth", "openai-codex.js");
|
|
2123
|
+
/**
|
|
2124
|
+
* Resolve omp's openai-codex OAuth submodule by walking node_modules upward
|
|
2125
|
+
* from the real omp binary. Returns null when not found — the caller degrades
|
|
2126
|
+
* gracefully to 'unavailable' status.
|
|
2127
|
+
*/
|
|
2128
|
+
function resolveOmpCodexOAuthFile(ompBin) {
|
|
2129
|
+
let dir = dirname(realpathSync(ompBin));
|
|
2130
|
+
for (let i = 0; i < 10; i++) {
|
|
2131
|
+
const candidate = join(dir, "node_modules", OMP_AI_OAUTH_SUBPATH);
|
|
2132
|
+
if (existsSync(candidate)) return candidate;
|
|
2133
|
+
const parent = dirname(dir);
|
|
2134
|
+
if (parent === dir) break;
|
|
2135
|
+
dir = parent;
|
|
2136
|
+
}
|
|
2137
|
+
return null;
|
|
2138
|
+
}
|
|
2139
|
+
let cachedModule = null;
|
|
2140
|
+
/**
|
|
2141
|
+
* Dynamically load omp's openai-codex OAuth submodule from the installed `omp`
|
|
2142
|
+
* package. Returns null on any failure so callers can degrade gracefully.
|
|
2143
|
+
*/
|
|
2144
|
+
async function loadOhmypiCodexOAuth() {
|
|
2145
|
+
if (cachedModule) return cachedModule;
|
|
2146
|
+
cachedModule = (async () => {
|
|
2147
|
+
try {
|
|
2148
|
+
const ompBin = findOmpBinary();
|
|
2149
|
+
if (!ompBin) return null;
|
|
2150
|
+
const oauthFile = resolveOmpCodexOAuthFile(ompBin);
|
|
2151
|
+
if (!oauthFile) return null;
|
|
2152
|
+
const mod = await import(pathToFileURL(oauthFile).href);
|
|
2153
|
+
if (typeof mod.refreshOpenAICodexToken !== "function" || typeof mod.loginOpenAICodexDeviceCode !== "function") return null;
|
|
2154
|
+
return mod;
|
|
2155
|
+
} catch {
|
|
2156
|
+
return null;
|
|
2157
|
+
}
|
|
2158
|
+
})();
|
|
2159
|
+
return cachedModule;
|
|
2160
|
+
}
|
|
2161
|
+
const EXPIRY_MARGIN_MS = 6e4;
|
|
2162
|
+
async function getOhmypiCodexAuthStatus(opts) {
|
|
2163
|
+
const cred = readOhmypiCodexCredential();
|
|
2164
|
+
if (!cred) return { status: "missing" };
|
|
2165
|
+
if (cred.expires > Date.now() + EXPIRY_MARGIN_MS) return {
|
|
2166
|
+
status: "ok",
|
|
2167
|
+
expiresAt: cred.expires
|
|
2168
|
+
};
|
|
2169
|
+
if (opts?.refreshIfExpired) {
|
|
2170
|
+
const refreshed = await refreshOhmypiCodexAuth();
|
|
2171
|
+
if (refreshed) return {
|
|
2172
|
+
status: "ok",
|
|
2173
|
+
expiresAt: refreshed.expires
|
|
2174
|
+
};
|
|
2175
|
+
return {
|
|
2176
|
+
status: "expired",
|
|
2177
|
+
expiresAt: cred.expires,
|
|
2178
|
+
refreshFailed: true
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
return {
|
|
2182
|
+
status: "expired",
|
|
2183
|
+
expiresAt: cred.expires,
|
|
2184
|
+
refreshFailed: false
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
async function refreshOhmypiCodexAuth() {
|
|
2188
|
+
const cred = readOhmypiCodexCredential();
|
|
2189
|
+
if (!cred?.refresh) return null;
|
|
2190
|
+
const mod = await loadOhmypiCodexOAuth();
|
|
2191
|
+
if (!mod) return null;
|
|
2192
|
+
try {
|
|
2193
|
+
return writeOhmypiCodexCredential(await mod.refreshOpenAICodexToken(cred.refresh));
|
|
2194
|
+
} catch {
|
|
2195
|
+
return null;
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
//#endregion
|
|
2199
|
+
//#region services/conversation-attachments.ts
|
|
2200
|
+
init_paths();
|
|
2201
|
+
const CONVERSATION_ATTACHMENTS_DIR = "conversation-attachments";
|
|
2202
|
+
/** Run async tasks in bounded batches to avoid unbounded Promise.all
|
|
2203
|
+
* that can exhaust file descriptors or memory under heavy load. */
|
|
2204
|
+
async function runInBatches(items, batchSize, fn) {
|
|
2205
|
+
for (let i = 0; i < items.length; i += batchSize) {
|
|
2206
|
+
const batch = items.slice(i, i + batchSize);
|
|
2207
|
+
await Promise.all(batch.map(fn));
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
/** Max JSONL line length to process. Lines larger than this are skipped to
|
|
2211
|
+
* prevent readline from stalling permanently on oversized input (e.g. a
|
|
2212
|
+
* single-line base64 payload >15 MB). */
|
|
2213
|
+
const MAX_JSONL_LINE_LENGTH = 15 * 1024 * 1024;
|
|
2214
|
+
/** Bounded LRU cache for readSessionAttachmentBasenames to avoid full JSONL
|
|
2215
|
+
* rescans when the session file has not changed. Key = sessionFile:mtimeMs. */
|
|
2216
|
+
const SESSION_ATTACHMENT_CACHE_MAX = 100;
|
|
2217
|
+
const sessionAttachmentCache = /* @__PURE__ */ new Map();
|
|
2218
|
+
function getAttachmentCacheKey(sessionFile, mtimeMs) {
|
|
2219
|
+
return `${sessionFile}:${mtimeMs}`;
|
|
2220
|
+
}
|
|
2221
|
+
function setAttachmentCache(sessionFile, mtimeMs, basenames) {
|
|
2222
|
+
const key = getAttachmentCacheKey(sessionFile, mtimeMs);
|
|
2223
|
+
sessionAttachmentCache.set(key, basenames);
|
|
2224
|
+
if (sessionAttachmentCache.size > SESSION_ATTACHMENT_CACHE_MAX) {
|
|
2225
|
+
const firstKey = sessionAttachmentCache.keys().next().value;
|
|
2226
|
+
if (firstKey !== void 0) sessionAttachmentCache.delete(firstKey);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
function getAttachmentCache(sessionFile, mtimeMs) {
|
|
2230
|
+
const key = getAttachmentCacheKey(sessionFile, mtimeMs);
|
|
2231
|
+
return sessionAttachmentCache.get(key);
|
|
2232
|
+
}
|
|
2233
|
+
/** Conversation names are sanitized to [a-zA-Z0-9_-]{1,64} on creation.
|
|
2234
|
+
* Re-validate here for defense-in-depth against path traversal. */
|
|
2235
|
+
function assertSafeName(name) {
|
|
2236
|
+
if (!/^[a-zA-Z0-9_-]{1,64}$/.test(name)) throw new Error("Invalid conversation name");
|
|
2237
|
+
}
|
|
2238
|
+
function getConversationAttachmentsRoot() {
|
|
2239
|
+
return join(getOverdeckHome(), CONVERSATION_ATTACHMENTS_DIR);
|
|
2240
|
+
}
|
|
2241
|
+
function getConversationAttachmentDir(name) {
|
|
2242
|
+
assertSafeName(name);
|
|
2243
|
+
return join(getConversationAttachmentsRoot(), name);
|
|
2244
|
+
}
|
|
2245
|
+
async function ensureConversationAttachmentDir(name) {
|
|
2246
|
+
const dir = getConversationAttachmentDir(name);
|
|
2247
|
+
await mkdir(dir, { recursive: true });
|
|
2248
|
+
return dir;
|
|
2249
|
+
}
|
|
2250
|
+
async function cleanupConversationAttachments(name) {
|
|
2251
|
+
assertSafeName(name);
|
|
2252
|
+
await rm(getConversationAttachmentDir(name), {
|
|
2253
|
+
recursive: true,
|
|
2254
|
+
force: true
|
|
2255
|
+
});
|
|
2256
|
+
}
|
|
2257
|
+
/** Safety-net cleanup: remove attachment directories for conversations that no
|
|
2258
|
+
* longer exist in the database. Catches orphaned files from abandoned/crashed
|
|
2259
|
+
* sessions that the lifecycle poller missed. */
|
|
2260
|
+
async function cleanupOrphanedConversationAttachments() {
|
|
2261
|
+
try {
|
|
2262
|
+
const { listConversations } = await import("./conversations-BM7zlXjr.js");
|
|
2263
|
+
const convNames = new Set(listConversations().map((c) => c.name));
|
|
2264
|
+
const root = getConversationAttachmentsRoot();
|
|
2265
|
+
await runInBatches((await readdir(root, { withFileTypes: true })).filter((entry) => entry.isDirectory() && !convNames.has(entry.name)).map((entry) => join(root, entry.name)), 10, (path) => rm(path, {
|
|
2266
|
+
recursive: true,
|
|
2267
|
+
force: true
|
|
2268
|
+
}).catch(() => {}));
|
|
2269
|
+
} catch {}
|
|
2270
|
+
}
|
|
2271
|
+
async function listConversationAttachmentPaths(name) {
|
|
2272
|
+
const dir = getConversationAttachmentDir(name);
|
|
2273
|
+
try {
|
|
2274
|
+
return (await readdir(dir, { withFileTypes: true })).filter((entry) => entry.isFile()).map((entry) => join(dir, entry.name));
|
|
2275
|
+
} catch {
|
|
2276
|
+
return [];
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
/** Synchronous containment check: does the normalized path start with the
|
|
2280
|
+
* pre-resolved attachment directory prefix? Uses resolve() (not realpath)
|
|
2281
|
+
* so the check is I/O-free — safe because we are only cataloguing references
|
|
2282
|
+
* in the JSONL, not accessing the filesystem. */
|
|
2283
|
+
function isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath) {
|
|
2284
|
+
try {
|
|
2285
|
+
return resolve(attachmentPath).startsWith(`${resolvedAttachmentDir}/`);
|
|
2286
|
+
} catch {
|
|
2287
|
+
return false;
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
async function readSessionAttachmentBasenames(sessionFile, name) {
|
|
2291
|
+
try {
|
|
2292
|
+
let mtimeMs;
|
|
2293
|
+
try {
|
|
2294
|
+
mtimeMs = (await stat$1(sessionFile)).mtimeMs;
|
|
2295
|
+
} catch {
|
|
2296
|
+
return /* @__PURE__ */ new Set();
|
|
2297
|
+
}
|
|
2298
|
+
const cached = getAttachmentCache(sessionFile, mtimeMs);
|
|
2299
|
+
if (cached) return cached;
|
|
2300
|
+
let resolvedAttachmentDir;
|
|
2301
|
+
try {
|
|
2302
|
+
resolvedAttachmentDir = await realpath(getConversationAttachmentDir(name));
|
|
2303
|
+
} catch {
|
|
2304
|
+
resolvedAttachmentDir = resolve(getConversationAttachmentDir(name));
|
|
2305
|
+
}
|
|
2306
|
+
const referenced = /* @__PURE__ */ new Set();
|
|
2307
|
+
const stream = createReadStream(sessionFile, { encoding: "utf-8" });
|
|
2308
|
+
const rl = createInterface({
|
|
2309
|
+
input: stream,
|
|
2310
|
+
crlfDelay: Infinity
|
|
2311
|
+
});
|
|
2312
|
+
try {
|
|
2313
|
+
for await (const line of rl) {
|
|
2314
|
+
if (!line.trim()) continue;
|
|
2315
|
+
if (line.length > MAX_JSONL_LINE_LENGTH) continue;
|
|
2316
|
+
let text;
|
|
2317
|
+
let structuredFoundPaths = false;
|
|
2318
|
+
try {
|
|
2319
|
+
const entry = JSON.parse(line);
|
|
2320
|
+
if (typeof entry?.message?.content === "string") text = entry.message.content;
|
|
2321
|
+
else if (Array.isArray(entry?.message?.content)) text = entry.message.content.map((c) => typeof c === "string" ? c : c?.text ?? "").join("");
|
|
2322
|
+
else if (typeof entry?.message?.text === "string") text = entry.message.text;
|
|
2323
|
+
else if (typeof entry?.text === "string") text = entry.text;
|
|
2324
|
+
} catch {
|
|
2325
|
+
text = line;
|
|
2326
|
+
}
|
|
2327
|
+
if (text) {
|
|
2328
|
+
for (const attachmentPath of extractConversationAttachmentPaths(text)) if (isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath)) {
|
|
2329
|
+
referenced.add(basename(attachmentPath));
|
|
2330
|
+
structuredFoundPaths = true;
|
|
2331
|
+
}
|
|
2332
|
+
}
|
|
2333
|
+
if (!structuredFoundPaths) for (const match of line.matchAll(/"@([^"]+)"/g)) {
|
|
2334
|
+
const rawValue = match[1];
|
|
2335
|
+
if (!rawValue.startsWith("/") && !rawValue.startsWith("\\/")) continue;
|
|
2336
|
+
let attachmentPath;
|
|
2337
|
+
try {
|
|
2338
|
+
attachmentPath = JSON.parse(`"${rawValue}"`);
|
|
2339
|
+
} catch {
|
|
2340
|
+
attachmentPath = rawValue;
|
|
2341
|
+
}
|
|
2342
|
+
if (typeof attachmentPath === "string" && attachmentPath.startsWith("/")) {
|
|
2343
|
+
if (isUnderAttachmentDir(resolvedAttachmentDir, attachmentPath)) referenced.add(basename(attachmentPath));
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
} finally {
|
|
2348
|
+
rl.close();
|
|
2349
|
+
stream.destroy();
|
|
2350
|
+
}
|
|
2351
|
+
setAttachmentCache(sessionFile, mtimeMs, referenced);
|
|
2352
|
+
return referenced;
|
|
2353
|
+
} catch {
|
|
2354
|
+
return /* @__PURE__ */ new Set();
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
async function cleanupUnreferencedConversationAttachments(conversation) {
|
|
2358
|
+
const attachmentPaths = await listConversationAttachmentPaths(conversation.name);
|
|
2359
|
+
if (attachmentPaths.length === 0) return;
|
|
2360
|
+
if (!conversation.sessionFile) {
|
|
2361
|
+
await runInBatches(attachmentPaths, 10, async (attachmentPath) => {
|
|
2362
|
+
await rm(attachmentPath, { force: true });
|
|
2363
|
+
});
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
2366
|
+
let sessionMtimeMs;
|
|
2367
|
+
try {
|
|
2368
|
+
sessionMtimeMs = (await stat$1(conversation.sessionFile)).mtimeMs;
|
|
2369
|
+
} catch {
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
const referencedBasenames = await readSessionAttachmentBasenames(conversation.sessionFile, conversation.name);
|
|
2373
|
+
try {
|
|
2374
|
+
const freshStats = await stat$1(conversation.sessionFile);
|
|
2375
|
+
if (freshStats.mtimeMs > sessionMtimeMs) sessionMtimeMs = freshStats.mtimeMs;
|
|
2376
|
+
} catch {}
|
|
2377
|
+
await runInBatches(attachmentPaths, 10, async (attachmentPath) => {
|
|
2378
|
+
if (referencedBasenames.has(basename(attachmentPath))) return;
|
|
2379
|
+
try {
|
|
2380
|
+
if ((await stat$1(attachmentPath)).mtimeMs >= sessionMtimeMs) return;
|
|
2381
|
+
} catch {
|
|
2382
|
+
return;
|
|
2383
|
+
}
|
|
2384
|
+
await rm(attachmentPath, { force: true });
|
|
2385
|
+
});
|
|
2386
|
+
}
|
|
2387
|
+
function extractConversationAttachmentPaths(message) {
|
|
2388
|
+
return Array.from(message.matchAll(/(?<!\S)@(\/[^\s]+)/g), ([, attachmentPath]) => {
|
|
2389
|
+
let path = attachmentPath;
|
|
2390
|
+
while (path.length > 1 && /[.,;:!?)\]}+"']$/.test(path)) path = path.slice(0, -1);
|
|
2391
|
+
return path;
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
/** Resolve a path for containment checking, walking parent directories with
|
|
2395
|
+
* realpath to preserve symlink detection even when the target file is missing. */
|
|
2396
|
+
async function resolveForContainment(attachmentPath) {
|
|
2397
|
+
try {
|
|
2398
|
+
return await realpath(attachmentPath);
|
|
2399
|
+
} catch {
|
|
2400
|
+
let dir = dirname(attachmentPath);
|
|
2401
|
+
const remaining = [basename(attachmentPath)];
|
|
2402
|
+
while (dir !== "/" && dir !== ".") try {
|
|
2403
|
+
return join(await realpath(dir), ...remaining);
|
|
2404
|
+
} catch {
|
|
2405
|
+
remaining.unshift(basename(dir));
|
|
2406
|
+
dir = dirname(dir);
|
|
2407
|
+
}
|
|
2408
|
+
return resolve(attachmentPath);
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
async function isManagedConversationAttachmentPath(attachmentPath) {
|
|
2412
|
+
try {
|
|
2413
|
+
const attachmentsRoot = await realpath(getConversationAttachmentsRoot());
|
|
2414
|
+
return (await resolveForContainment(attachmentPath)).startsWith(`${attachmentsRoot}/`);
|
|
2415
|
+
} catch {
|
|
2416
|
+
return false;
|
|
2417
|
+
}
|
|
2418
|
+
}
|
|
2419
|
+
async function isConversationAttachmentPath(name, attachmentPath) {
|
|
2420
|
+
try {
|
|
2421
|
+
const attachmentDir = resolve(getConversationAttachmentDir(name));
|
|
2422
|
+
return (await resolveForContainment(attachmentPath)).startsWith(`${attachmentDir}/`);
|
|
2423
|
+
} catch {
|
|
2424
|
+
return false;
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
async function hasConversationAttachment(name, attachmentPath) {
|
|
2428
|
+
try {
|
|
2429
|
+
const attachmentDir = await realpath(getConversationAttachmentDir(name));
|
|
2430
|
+
const candidate = await resolveForContainment(attachmentPath);
|
|
2431
|
+
if (!candidate.startsWith(`${attachmentDir}/`)) return false;
|
|
2432
|
+
return existsSync(candidate);
|
|
2433
|
+
} catch {
|
|
2434
|
+
return false;
|
|
2435
|
+
}
|
|
2436
|
+
}
|
|
2437
|
+
async function removeConversationAttachment(name, attachmentPath) {
|
|
2438
|
+
if (!await isConversationAttachmentPath(name, attachmentPath)) return false;
|
|
2439
|
+
await rm(attachmentPath, { force: true });
|
|
2440
|
+
return true;
|
|
2441
|
+
}
|
|
2442
|
+
//#endregion
|
|
2443
|
+
//#region ../../lib/overdeck/conversation-runtime.ts
|
|
2444
|
+
init_projects();
|
|
2445
|
+
init_tmux();
|
|
2446
|
+
init_agents();
|
|
2447
|
+
init_behavior();
|
|
2448
|
+
init_paths();
|
|
2449
|
+
const execAsync$1 = promisify(exec);
|
|
2450
|
+
const execFileAsync$1 = promisify(execFile);
|
|
2451
|
+
const PROCESS_CLEANUP_GRACE_MS = 750;
|
|
2452
|
+
const PTY_SUPERVISOR_SOCKET_WAIT_MS = 3e4;
|
|
2453
|
+
function parseProcessTable(output) {
|
|
2454
|
+
return output.split("\n").map((line) => {
|
|
2455
|
+
const match = line.match(/^\s*(\d+)\s+(\d+)\s+(.*)$/);
|
|
2456
|
+
if (!match) return null;
|
|
2457
|
+
return {
|
|
2458
|
+
pid: Number(match[1]),
|
|
2459
|
+
ppid: Number(match[2]),
|
|
2460
|
+
args: match[3] ?? ""
|
|
2461
|
+
};
|
|
2462
|
+
}).filter((row) => row !== null && Number.isFinite(row.pid) && Number.isFinite(row.ppid));
|
|
2463
|
+
}
|
|
2464
|
+
async function readProcessTable() {
|
|
2465
|
+
const { stdout } = await execFileAsync$1("ps", ["-eo", "pid=,ppid=,args="], {
|
|
2466
|
+
encoding: "utf-8",
|
|
2467
|
+
maxBuffer: 10 * 1024 * 1024
|
|
2468
|
+
});
|
|
2469
|
+
return parseProcessTable(stdout);
|
|
2470
|
+
}
|
|
2471
|
+
function collectProcessTree(rootPids, rows) {
|
|
2472
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
2473
|
+
for (const row of rows) {
|
|
2474
|
+
const children = childrenByParent.get(row.ppid) ?? [];
|
|
2475
|
+
children.push(row.pid);
|
|
2476
|
+
childrenByParent.set(row.ppid, children);
|
|
2477
|
+
}
|
|
2478
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2479
|
+
const ordered = [];
|
|
2480
|
+
const visit = (pid) => {
|
|
2481
|
+
if (seen.has(pid) || pid === process.pid) return;
|
|
2482
|
+
seen.add(pid);
|
|
2483
|
+
for (const child of childrenByParent.get(pid) ?? []) visit(child);
|
|
2484
|
+
ordered.push(pid);
|
|
2485
|
+
};
|
|
2486
|
+
for (const pid of rootPids) visit(pid);
|
|
2487
|
+
return ordered;
|
|
2488
|
+
}
|
|
2489
|
+
function isProcessAlive(pid) {
|
|
2490
|
+
try {
|
|
2491
|
+
process.kill(pid, 0);
|
|
2492
|
+
return true;
|
|
2493
|
+
} catch {
|
|
2494
|
+
return false;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
async function terminatePids(pids) {
|
|
2498
|
+
if (pids.length === 0) return;
|
|
2499
|
+
for (const pid of pids) try {
|
|
2500
|
+
process.kill(pid, "SIGTERM");
|
|
2501
|
+
} catch {}
|
|
2502
|
+
await new Promise((resolve) => setTimeout(resolve, PROCESS_CLEANUP_GRACE_MS));
|
|
2503
|
+
for (const pid of pids) {
|
|
2504
|
+
if (!isProcessAlive(pid)) continue;
|
|
2505
|
+
try {
|
|
2506
|
+
process.kill(pid, "SIGKILL");
|
|
2507
|
+
} catch {}
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
function conversationRuntimeRootPids(conv, rows) {
|
|
2511
|
+
const launcherScript = join(getOverdeckHome(), "conversations", conv.tmuxSession, "launcher.sh");
|
|
2512
|
+
const sessionId = conv.claudeSessionId?.trim();
|
|
2513
|
+
const sessionNeedles = sessionId ? [`--resume ${sessionId}`, `--session-id ${sessionId}`] : [];
|
|
2514
|
+
const serverPid = findManagedServerPidSync();
|
|
2515
|
+
return rows.filter((row) => {
|
|
2516
|
+
if (row.pid === process.pid) return false;
|
|
2517
|
+
if (serverPid !== void 0 && row.pid === serverPid) return false;
|
|
2518
|
+
if (row.args.includes(launcherScript)) return true;
|
|
2519
|
+
return sessionNeedles.some((needle) => row.args.includes(needle));
|
|
2520
|
+
}).map((row) => row.pid);
|
|
2521
|
+
}
|
|
2522
|
+
async function killConversationRuntimeProcesses(conv) {
|
|
2523
|
+
const rows = await readProcessTable();
|
|
2524
|
+
await terminatePids(collectProcessTree(conversationRuntimeRootPids(conv, rows), rows));
|
|
2525
|
+
}
|
|
2526
|
+
async function stopConversationRuntime(conv, name) {
|
|
2527
|
+
if (hasOtherActiveConversationOnTmuxSession(conv.tmuxSession, name)) return;
|
|
2528
|
+
await Effect.runPromise(killSession(conv.tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
|
|
2529
|
+
try {
|
|
2530
|
+
await killConversationRuntimeProcesses(conv);
|
|
2531
|
+
} catch (error) {
|
|
2532
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2533
|
+
console.warn(`[conversations] failed to cleanup runtime processes for ${name}: ${msg}`);
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
/** Quote a string for safe use in a bash script using single-quote wrapping. */
|
|
2537
|
+
function shellQuote(str) {
|
|
2538
|
+
return "'" + str.replace(/'/g, "'\"'\"'") + "'";
|
|
2539
|
+
}
|
|
2540
|
+
const SAFE_MODEL_PATTERN = /^[a-zA-Z0-9_.:\/-]+$/;
|
|
2541
|
+
const SAFE_EFFORT_PATTERN = /^(low|medium|high)$/;
|
|
2542
|
+
const SAFE_ISSUE_ID_PATTERN = /^[A-Z0-9]+-[0-9]+$/;
|
|
2543
|
+
const PI_CONVERSATION_SOURCE_CONTRACT = [
|
|
2544
|
+
"Pi conversation source contract:",
|
|
2545
|
+
"A message marked source:'extension' was injected by the Overdeck orchestrator or another agent, not typed by the human operator.",
|
|
2546
|
+
"Treat it as coordination guidance; do not attribute it to the human operator."
|
|
2547
|
+
].join(" ");
|
|
2548
|
+
async function resolveAllowedHarness(requested, model) {
|
|
2549
|
+
if (!model) return "claude-code";
|
|
2550
|
+
return resolveHarness({
|
|
2551
|
+
model,
|
|
2552
|
+
explicit: requested === "ohmypi" || requested === "claude-code" || requested === "codex" ? requested : void 0
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
async function isInsideGitWorkTree(dir) {
|
|
2556
|
+
try {
|
|
2557
|
+
const { stdout } = await execAsync$1("git rev-parse --is-inside-work-tree", {
|
|
2558
|
+
cwd: dir,
|
|
2559
|
+
encoding: "utf-8"
|
|
2560
|
+
});
|
|
2561
|
+
return stdout.trim() === "true";
|
|
2562
|
+
} catch {
|
|
2563
|
+
return false;
|
|
2564
|
+
}
|
|
2565
|
+
}
|
|
2566
|
+
async function validateCwdContainment(cwd) {
|
|
2567
|
+
if (!cwd.startsWith("/")) return false;
|
|
2568
|
+
if (cwd.split("/").filter(Boolean).includes("..")) return false;
|
|
2569
|
+
try {
|
|
2570
|
+
const resolved = await realpath(cwd);
|
|
2571
|
+
if (!(await stat$1(resolved)).isDirectory()) return false;
|
|
2572
|
+
const home = homedir();
|
|
2573
|
+
return resolved.startsWith(`${home}/`) || resolved === home;
|
|
2574
|
+
} catch {
|
|
2575
|
+
return false;
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2578
|
+
async function waitForClaudeReady(tmuxSession) {
|
|
2579
|
+
const deadline = Date.now() + 3e4;
|
|
2580
|
+
while (Date.now() < deadline) {
|
|
2581
|
+
if ((await Effect.runPromise(capturePane(tmuxSession, 200))).includes("❯")) {
|
|
2582
|
+
console.log(`[conversations] Claude Code ready in ${tmuxSession}`);
|
|
2583
|
+
return;
|
|
2584
|
+
}
|
|
2585
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2586
|
+
}
|
|
2587
|
+
console.warn(`[conversations] Timed out waiting for Claude Code prompt in ${tmuxSession}`);
|
|
2588
|
+
}
|
|
2589
|
+
function isPiTuiInputReady(snapshot) {
|
|
2590
|
+
return /^\s*[❯›>]\s/m.test(snapshot) || /(?:^|\s)0(?:\.\d+)?%\s+context\s+used\b/i.test(snapshot) || /⬢[^\n]*◕/.test(snapshot);
|
|
2591
|
+
}
|
|
2592
|
+
async function waitForPiTuiReady(tmuxSession, timeoutMs = 6e4) {
|
|
2593
|
+
const deadline = Date.now() + timeoutMs;
|
|
2594
|
+
while (Date.now() < deadline) {
|
|
2595
|
+
if (isPiTuiInputReady(await Effect.runPromise(capturePane(tmuxSession, 40).pipe(Effect.catch(() => Effect.succeed("")))))) {
|
|
2596
|
+
console.log(`[conversations] Pi TUI ready for ${tmuxSession}`);
|
|
2597
|
+
return true;
|
|
2598
|
+
}
|
|
2599
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
2600
|
+
}
|
|
2601
|
+
console.warn(`[conversations] Timed out waiting for Pi TUI to render in ${tmuxSession}`);
|
|
2602
|
+
return false;
|
|
2603
|
+
}
|
|
2604
|
+
function shouldReportUnresolvedLiveSession(conv) {
|
|
2605
|
+
if (!conv || conv.status !== "active") return false;
|
|
2606
|
+
return getHarnessBehavior(conv.harness).transcriptKind === "claude-jsonl";
|
|
2607
|
+
}
|
|
2608
|
+
function conversationSessionAliveFromState(conv, tmuxSessionAlive) {
|
|
2609
|
+
return conv.status === "active" && !conv.forkStatus && tmuxSessionAlive;
|
|
2610
|
+
}
|
|
2611
|
+
function conversationNeedsRunningRepair(conv, tmuxSessionAlive, harnessProcessAlive) {
|
|
2612
|
+
return conv.status === "ended" && !conv.forkStatus && tmuxSessionAlive && harnessProcessAlive;
|
|
2613
|
+
}
|
|
2614
|
+
/** Generate a default conversation name, e.g. 20260404-1234 */
|
|
2615
|
+
function generateConversationName() {
|
|
2616
|
+
return `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10).replace(/-/g, "")}-${Math.floor(Math.random() * 9e3 + 1e3)}`;
|
|
2617
|
+
}
|
|
2618
|
+
async function tmuxSessionExists(sessionName) {
|
|
2619
|
+
return Effect.runPromise(sessionExists(sessionName));
|
|
2620
|
+
}
|
|
2621
|
+
async function waitForTmuxSession(sessionName, timeoutMs = 3e4) {
|
|
2622
|
+
const start = Date.now();
|
|
2623
|
+
while (Date.now() - start < timeoutMs) {
|
|
2624
|
+
if (await Effect.runPromise(sessionExists(sessionName))) return;
|
|
2625
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
2626
|
+
}
|
|
2627
|
+
throw new Error(`Timed out waiting for tmux session ${sessionName}`);
|
|
2628
|
+
}
|
|
2629
|
+
function shouldUseSupervisorForConversation(harness) {
|
|
2630
|
+
return getHarnessBehavior(harness).supportsPtySupervisor && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1";
|
|
2631
|
+
}
|
|
2632
|
+
async function waitForConversationRuntimeReady(tmuxSession, harness, mode) {
|
|
2633
|
+
const transcriptKind = getHarnessBehavior(harness).transcriptKind;
|
|
2634
|
+
if (transcriptKind === "ohmypi-jsonl") {
|
|
2635
|
+
if (!await waitForPiTuiReady(tmuxSession)) throw new Error(`Pi (ohmypi) did not become interactive in ${tmuxSession} within the startup window — MCP server connections are likely still in progress. Retry once MCP servers finish connecting, or check ~/.claude/mcp.json for a slow/hanging server.`);
|
|
2636
|
+
} else if (transcriptKind !== "codex-rollout-jsonl" && mode === "spawn") {
|
|
2637
|
+
await waitForClaudeReady(tmuxSession);
|
|
2638
|
+
console.log(`[conversations] Claude ready in ${tmuxSession}`);
|
|
2639
|
+
} else if (transcriptKind !== "codex-rollout-jsonl") await waitForReadySignal(tmuxSession, 30);
|
|
2640
|
+
}
|
|
2641
|
+
async function handleConversationSwitchModel(name, body) {
|
|
2642
|
+
const conv = getConversationByName(name);
|
|
2643
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
2644
|
+
const model = typeof body["model"] === "string" && body["model"].trim() ? body["model"].trim() : conv.model ?? void 0;
|
|
2645
|
+
const currentHarness = conv.harness ?? "claude-code";
|
|
2646
|
+
const requestedHarness = body["harness"];
|
|
2647
|
+
let harness = currentHarness;
|
|
2648
|
+
if (requestedHarness === "ohmypi" || requestedHarness === "pi" || requestedHarness === "claude-code" || requestedHarness === "codex") {
|
|
2649
|
+
const requestedRuntime = requestedHarness === "pi" ? "ohmypi" : requestedHarness;
|
|
2650
|
+
if (requestedRuntime !== currentHarness) {
|
|
2651
|
+
const policyModel = model ?? conv.model ?? "";
|
|
2652
|
+
const decision = canUseHarnessSync(requestedRuntime, policyModel, await getProviderAuthMode(policyModel));
|
|
2653
|
+
if (!decision.allowed) return jsonResponse({ error: decision.reason ?? "Harness not allowed for this model" }, { status: 400 });
|
|
2654
|
+
}
|
|
2655
|
+
harness = requestedRuntime;
|
|
2656
|
+
}
|
|
2657
|
+
const harnessChanged = harness !== currentHarness;
|
|
2658
|
+
if (!await validateCwdContainment(conv.cwd)) return jsonResponse({ error: "Invalid cwd" }, { status: 400 });
|
|
2659
|
+
if (model && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
|
|
2660
|
+
const livePiSwitch = isPiControlChannelHarness(currentHarness) && conv.status !== "ended";
|
|
2661
|
+
if (conv.claudeSessionId && harnessChanged) return jsonResponse({ error: "Conversation harness is locked once a conversation has started" }, { status: 409 });
|
|
2662
|
+
if (conv.claudeSessionId && !livePiSwitch) return jsonResponse({ error: "Conversation model is locked once a conversation has started" }, { status: 409 });
|
|
2663
|
+
if (model) {
|
|
2664
|
+
if (livePiSwitch) await sendConversationControlCommand(conv, {
|
|
2665
|
+
type: "set_model",
|
|
2666
|
+
model
|
|
2667
|
+
});
|
|
2668
|
+
setConversationModel(name, model);
|
|
2669
|
+
}
|
|
2670
|
+
if (harnessChanged) setConversationHarness(name, harness);
|
|
2671
|
+
const updated = getConversationByName(name) ?? conv;
|
|
2672
|
+
return jsonResponse({
|
|
2673
|
+
...updated,
|
|
2674
|
+
model: model ?? updated.model,
|
|
2675
|
+
harness,
|
|
2676
|
+
sessionAlive: false
|
|
2677
|
+
});
|
|
2678
|
+
}
|
|
2679
|
+
function getPtySupervisorSocketPath(agentId) {
|
|
2680
|
+
return join(getOverdeckHome(), "sockets", `pty-${agentId}.sock`);
|
|
2681
|
+
}
|
|
2682
|
+
async function waitForPtySupervisorSocket(agentId, timeoutMs = PTY_SUPERVISOR_SOCKET_WAIT_MS) {
|
|
2683
|
+
const socketPath = getPtySupervisorSocketPath(agentId);
|
|
2684
|
+
const start = Date.now();
|
|
2685
|
+
while (Date.now() - start < timeoutMs) {
|
|
2686
|
+
try {
|
|
2687
|
+
if (((await stat$1(socketPath)).mode & 511) === 384) return;
|
|
2688
|
+
} catch {}
|
|
2689
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
2690
|
+
}
|
|
2691
|
+
throw new Error(`Timed out waiting for PTY supervisor socket ${socketPath}`);
|
|
2692
|
+
}
|
|
2693
|
+
async function extractModelFromSessionFile(sessionFile) {
|
|
2694
|
+
try {
|
|
2695
|
+
if (!existsSync(sessionFile)) return null;
|
|
2696
|
+
const stream = createReadStream(sessionFile, { encoding: "utf-8" });
|
|
2697
|
+
const rl = createInterface({
|
|
2698
|
+
input: stream,
|
|
2699
|
+
crlfDelay: Infinity
|
|
2700
|
+
});
|
|
2701
|
+
try {
|
|
2702
|
+
for await (const line of rl) {
|
|
2703
|
+
if (!line.trim()) continue;
|
|
2704
|
+
const entry = JSON.parse(line);
|
|
2705
|
+
if (entry.type === "assistant" && entry.message?.model) return entry.message.model;
|
|
2706
|
+
}
|
|
2707
|
+
} finally {
|
|
2708
|
+
rl.close();
|
|
2709
|
+
stream.destroy();
|
|
2710
|
+
}
|
|
2711
|
+
} catch {}
|
|
2712
|
+
return null;
|
|
2713
|
+
}
|
|
2714
|
+
let backfillRunning = false;
|
|
2715
|
+
async function backfillConversationModels(resolveSessionFile) {
|
|
2716
|
+
if (backfillRunning) return;
|
|
2717
|
+
backfillRunning = true;
|
|
2718
|
+
try {
|
|
2719
|
+
const convs = listConversations();
|
|
2720
|
+
const probe = await Promise.all(convs.map(async (conv) => !conv.model ? await resolveSessionFile(conv) : null));
|
|
2721
|
+
const candidates = convs.filter((_, i) => probe[i] !== null);
|
|
2722
|
+
const BATCH_SIZE = 10;
|
|
2723
|
+
let backfilled = 0;
|
|
2724
|
+
for (let i = 0; i < candidates.length; i += BATCH_SIZE) {
|
|
2725
|
+
const batch = candidates.slice(i, i + BATCH_SIZE);
|
|
2726
|
+
const results = await Promise.all(batch.map(async (conv) => {
|
|
2727
|
+
const sessionFile = await resolveSessionFile(conv);
|
|
2728
|
+
if (!sessionFile) return false;
|
|
2729
|
+
const model = await extractModelFromSessionFile(sessionFile);
|
|
2730
|
+
if (model && SAFE_MODEL_PATTERN.test(model)) {
|
|
2731
|
+
backfillConversationModel(conv.name, model);
|
|
2732
|
+
return true;
|
|
2733
|
+
}
|
|
2734
|
+
return false;
|
|
2735
|
+
}));
|
|
2736
|
+
backfilled += results.filter(Boolean).length;
|
|
2737
|
+
}
|
|
2738
|
+
if (backfilled > 0) console.log(`[conversations] Backfilled model for ${backfilled} conversation(s)`);
|
|
2739
|
+
} finally {
|
|
2740
|
+
backfillRunning = false;
|
|
2741
|
+
}
|
|
2742
|
+
}
|
|
2743
|
+
function startConversationModelBackfill(resolveSessionFile) {
|
|
2744
|
+
backfillConversationModels(resolveSessionFile).catch((err) => {
|
|
2745
|
+
console.error("[conversations] Model backfill failed:", err);
|
|
2746
|
+
});
|
|
2747
|
+
}
|
|
2748
|
+
async function claudeConversationSystemPromptFiles(cwd) {
|
|
2749
|
+
const files = [];
|
|
2750
|
+
const contextFile = workspaceContextFile(cwd);
|
|
2751
|
+
try {
|
|
2752
|
+
await stat$1(contextFile);
|
|
2753
|
+
files.push(contextFile);
|
|
2754
|
+
} catch (error) {
|
|
2755
|
+
if (!isNotFound(error)) throw error;
|
|
2756
|
+
}
|
|
2757
|
+
files.push(await ensureSessionContextBriefingFile());
|
|
2758
|
+
return files;
|
|
2759
|
+
}
|
|
2760
|
+
async function ensurePiConversationSourceContractFile() {
|
|
2761
|
+
const contextDir = join(getOverdeckHome(), "context");
|
|
2762
|
+
await mkdir(contextDir, { recursive: true });
|
|
2763
|
+
const path = join(contextDir, "pi-conversation-source-contract.md");
|
|
2764
|
+
await writeFile(path, `${PI_CONVERSATION_SOURCE_CONTRACT}\n`, "utf-8");
|
|
2765
|
+
return path;
|
|
2766
|
+
}
|
|
2767
|
+
async function piConversationSystemPromptFiles(cwd) {
|
|
2768
|
+
const files = [];
|
|
2769
|
+
const globalFile = piGlobalContextFile();
|
|
2770
|
+
try {
|
|
2771
|
+
await stat$1(globalFile);
|
|
2772
|
+
files.push(globalFile);
|
|
2773
|
+
} catch (error) {
|
|
2774
|
+
if (!isNotFound(error)) throw error;
|
|
2775
|
+
}
|
|
2776
|
+
files.push(await ensurePiConversationSourceContractFile());
|
|
2777
|
+
const contextFile = workspaceContextFile(cwd);
|
|
2778
|
+
try {
|
|
2779
|
+
await stat$1(contextFile);
|
|
2780
|
+
files.push(contextFile);
|
|
2781
|
+
} catch (error) {
|
|
2782
|
+
if (!isNotFound(error)) throw error;
|
|
2783
|
+
}
|
|
2784
|
+
files.push(await ensureSessionContextBriefingFile());
|
|
2785
|
+
return files;
|
|
2786
|
+
}
|
|
2787
|
+
function isNotFound(error) {
|
|
2788
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
2789
|
+
}
|
|
2790
|
+
async function spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, resume = false, harness = "claude-code", plainFork = false) {
|
|
2791
|
+
const behavior = getHarnessBehavior(harness);
|
|
2792
|
+
const stateDir = join(getOverdeckHome(), "conversations", tmuxSession);
|
|
2793
|
+
await mkdir(stateDir, { recursive: true });
|
|
2794
|
+
clearReadySignal(tmuxSession);
|
|
2795
|
+
const launcherScript = join(stateDir, "launcher.sh");
|
|
2796
|
+
let runtimeCommand = `claude ${getClaudePermissionFlagsStringSync()}`;
|
|
2797
|
+
let providerExportsStr = "";
|
|
2798
|
+
let piFields;
|
|
2799
|
+
let codexFields;
|
|
2800
|
+
if (model) {
|
|
2801
|
+
if (!SAFE_MODEL_PATTERN.test(model)) throw new Error("Invalid model name");
|
|
2802
|
+
runtimeCommand = await getAgentRuntimeBaseCommand(model, void 0, void 0, harness);
|
|
2803
|
+
const mode = resolvePermissionModeSync();
|
|
2804
|
+
if (!runtimeCommand.includes("--permission-mode")) runtimeCommand = `${runtimeCommand} --permission-mode ${mode === "auto" ? "auto" : BYPASS_PERMISSION_MODE}`;
|
|
2805
|
+
providerExportsStr = (await getProviderExportsForModel(model)).trim();
|
|
2806
|
+
await getProviderEnvForModel(model);
|
|
2807
|
+
if (behavior.transcriptKind === "ohmypi-jsonl") {
|
|
2808
|
+
if (getProviderForModelSync(model).name === "openai") {
|
|
2809
|
+
const auth = await getOhmypiCodexAuthStatus({ refreshIfExpired: true });
|
|
2810
|
+
if (auth.status === "missing" || auth.status === "expired") throw new Error("ohmypi ChatGPT/Codex login (openai-codex) has expired and could not be refreshed. Re-authenticate with `pan pi-auth login`, then retry.");
|
|
2811
|
+
}
|
|
2812
|
+
const paths = piFifoPaths(tmuxSession);
|
|
2813
|
+
const piSessionDir = join(paths.agentDir, "sessions");
|
|
2814
|
+
await mkdir(paths.agentDir, {
|
|
2815
|
+
recursive: true,
|
|
2816
|
+
mode: 448
|
|
2817
|
+
});
|
|
2818
|
+
await mkdir(piSessionDir, {
|
|
2819
|
+
recursive: true,
|
|
2820
|
+
mode: 448
|
|
2821
|
+
});
|
|
2822
|
+
const storedPiSessionId = resume ? await readFile(join(paths.agentDir, "session.id"), "utf-8").then((s) => s.trim()).catch(() => void 0) : void 0;
|
|
2823
|
+
piFields = {
|
|
2824
|
+
harness: "ohmypi",
|
|
2825
|
+
piMode: "tui",
|
|
2826
|
+
piExtensionPath: resolveOhmypiExtensionPath() ?? resolve(process.cwd(), "packages/ohmypi-extension/dist/index.js"),
|
|
2827
|
+
piSessionDir,
|
|
2828
|
+
resumeSessionId: storedPiSessionId || void 0
|
|
2829
|
+
};
|
|
2830
|
+
} else if (behavior.usesCodexHome) {
|
|
2831
|
+
const codexHome = join(getOverdeckHome(), "agents", tmuxSession, "codex-home");
|
|
2832
|
+
const codexPermMode = loadConfigSync().config.codex?.permissionMode ?? "workspace";
|
|
2833
|
+
const codexApprovalPolicy = codexPermMode === "full-access" ? "never" : "on-request";
|
|
2834
|
+
const codexSandboxMode = codexPermMode === "full-access" ? "danger-full-access" : codexPermMode === "read-only" ? "read-only" : "workspace-write";
|
|
2835
|
+
const codexApprovalsReviewer = codexPermMode === "auto-review" ? "auto_review" : void 0;
|
|
2836
|
+
const { initCodexHome, extractThreadIdFromRollout } = await import("./codex-BYPz6ZdF.js");
|
|
2837
|
+
initCodexHome(codexHome, {
|
|
2838
|
+
trustedDir: cwd,
|
|
2839
|
+
approvalPolicy: codexApprovalPolicy,
|
|
2840
|
+
sandboxMode: codexSandboxMode,
|
|
2841
|
+
approvalsReviewer: codexApprovalsReviewer
|
|
2842
|
+
});
|
|
2843
|
+
const resumeSessionId = resume ? await resolveCodexRolloutPath(tmuxSession, { agentsDirOverride: join(getOverdeckHome(), "agents") }).then((rollout) => rollout ? extractThreadIdFromRollout(rollout) ?? void 0 : void 0) : void 0;
|
|
2844
|
+
codexFields = {
|
|
2845
|
+
harness: "codex",
|
|
2846
|
+
codexMode: "tui",
|
|
2847
|
+
codexHome,
|
|
2848
|
+
codexSessionDir: join(codexHome, "sessions"),
|
|
2849
|
+
resumeSessionId
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
let launcherModel = model;
|
|
2854
|
+
if (behavior.contextLayerKind === "pi" && model) {
|
|
2855
|
+
const piProvider = piProviderForModel(model);
|
|
2856
|
+
if (piProvider) launcherModel = `${piProvider}/${model}`;
|
|
2857
|
+
}
|
|
2858
|
+
if (effort && !SAFE_EFFORT_PATTERN.test(effort)) throw new Error("Invalid effort level");
|
|
2859
|
+
const useSupervisor = shouldUseSupervisorForConversation(harness);
|
|
2860
|
+
let supervisorScriptPath;
|
|
2861
|
+
if (useSupervisor) {
|
|
2862
|
+
supervisorScriptPath = resolvePtySupervisorScriptPath();
|
|
2863
|
+
await writePtyToken(tmuxSession);
|
|
2864
|
+
}
|
|
2865
|
+
let channelsBridgeMcpConfig;
|
|
2866
|
+
if (!piFields && !codexFields && !plainFork && isClaudeCodeChannelsEnabled() && (!model || getProviderForModelSync(model).name === "anthropic") && process.env.CLAUDE_CODE_USE_BEDROCK !== "1" && process.env.CLAUDE_CODE_USE_VERTEX !== "1" && process.env.CLAUDE_CODE_USE_FOUNDRY !== "1" && process.env.OVERDECK_DOCKER_WORKSPACE !== "1" && process.env.PAN_DOCKER !== "1") {
|
|
2867
|
+
channelsBridgeMcpConfig = join(stateDir, "agent-mcp.json");
|
|
2868
|
+
writeBridgeTokenSync(tmuxSession);
|
|
2869
|
+
await writeChannelsBridgeMcpConfig(channelsBridgeMcpConfig, tmuxSession);
|
|
2870
|
+
}
|
|
2871
|
+
const launcherTmp = `${launcherScript}.${randomUUID$1()}.tmp`;
|
|
2872
|
+
await writeFile(launcherTmp, generateLauncherScriptSync({
|
|
2873
|
+
role: "work",
|
|
2874
|
+
spawnMode: "conversation",
|
|
2875
|
+
workingDir: cwd,
|
|
2876
|
+
setTerminalEnv: true,
|
|
2877
|
+
unsetProviderEnv: true,
|
|
2878
|
+
overdeckEnv: {
|
|
2879
|
+
...issueId ? { issueId } : {},
|
|
2880
|
+
...piFields || codexFields || useSupervisor ? { agentId: tmuxSession } : {}
|
|
2881
|
+
},
|
|
2882
|
+
extraEnvExports: [`export OVERDECK_DASHBOARD_URL="http://127.0.0.1:${process.env["API_PORT"] ?? process.env["PORT"] ?? "3011"}"`],
|
|
2883
|
+
providerExports: providerExportsStr || void 0,
|
|
2884
|
+
trapHup: true,
|
|
2885
|
+
baseCommand: runtimeCommand,
|
|
2886
|
+
appendSystemPromptFiles: piFields ? await piConversationSystemPromptFiles(cwd) : codexFields ? [] : await claudeConversationSystemPromptFiles(cwd),
|
|
2887
|
+
model: launcherModel,
|
|
2888
|
+
...piFields ?? codexFields ?? {
|
|
2889
|
+
resumeSessionId: resume ? claudeSessionId : void 0,
|
|
2890
|
+
sessionId: resume ? void 0 : claudeSessionId
|
|
2891
|
+
},
|
|
2892
|
+
extraArgs: !piFields && effort ? `--effort "${effort}"` : void 0,
|
|
2893
|
+
keepAlive: true,
|
|
2894
|
+
fileMode: 448,
|
|
2895
|
+
channelsBridgeMcpConfig,
|
|
2896
|
+
useSupervisor,
|
|
2897
|
+
supervisorScriptPath
|
|
2898
|
+
}), { mode: 448 });
|
|
2899
|
+
await rename(launcherTmp, launcherScript);
|
|
2900
|
+
try {
|
|
2901
|
+
await Effect.runPromise(killSession(tmuxSession));
|
|
2902
|
+
} catch {}
|
|
2903
|
+
console.log(`[claude-invoke] purpose=conversation-session | model=${model || "default"} | source=conversations.ts:spawnConversationSession | session=${tmuxSession} | resume=${resume} | command="${runtimeCommand}"`);
|
|
2904
|
+
try {
|
|
2905
|
+
const { preTrustDirectory } = await import("./workspace-manager-BO-6k3sP.js");
|
|
2906
|
+
preTrustDirectory(cwd);
|
|
2907
|
+
} catch {}
|
|
2908
|
+
try {
|
|
2909
|
+
await Effect.runPromise(createSession(tmuxSession, cwd, `bash ${shellQuote(launcherScript)}`, { env: {
|
|
2910
|
+
...BLANKED_PROVIDER_ENV,
|
|
2911
|
+
TERM: "xterm-256color"
|
|
2912
|
+
} }));
|
|
2913
|
+
} catch (err) {
|
|
2914
|
+
if (err?.code === "ENOENT") throw new Error("tmux is not installed. Install it with: brew install tmux (macOS) or sudo apt-get install tmux (Linux)");
|
|
2915
|
+
throw err;
|
|
2916
|
+
}
|
|
2917
|
+
if (useSupervisor) await waitForPtySupervisorSocket(tmuxSession);
|
|
2918
|
+
if (behavior.usesCodexHome && codexFields?.codexHome) {
|
|
2919
|
+
const codexHomeDir = codexFields.codexHome;
|
|
2920
|
+
(async () => {
|
|
2921
|
+
try {
|
|
2922
|
+
const { waitForCodexRollout, extractThreadIdFromRollout, writeThreadId } = await import("./codex-BYPz6ZdF.js");
|
|
2923
|
+
const rollout = await waitForCodexRollout(codexHomeDir, 12e4);
|
|
2924
|
+
if (rollout) {
|
|
2925
|
+
const threadId = extractThreadIdFromRollout(rollout);
|
|
2926
|
+
if (threadId) writeThreadId(tmuxSession, threadId);
|
|
2927
|
+
}
|
|
2928
|
+
} catch {}
|
|
2929
|
+
})();
|
|
2930
|
+
}
|
|
2931
|
+
if (channelsBridgeMcpConfig) dismissDevChannelsDialog(tmuxSession).catch((err) => {
|
|
2932
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2933
|
+
console.error(`[conversations] dismissDevChannelsDialog failed for ${tmuxSession}: ${msg}`);
|
|
2934
|
+
});
|
|
2935
|
+
await Effect.runPromise(setOption(tmuxSession, "destroy-unattached", "off"));
|
|
2936
|
+
await Effect.runPromise(setOption(exactPaneTarget(tmuxSession), "remain-on-exit", "on"));
|
|
2937
|
+
}
|
|
2938
|
+
/**
|
|
2939
|
+
* Resolve a conversation's cwd from a project identifier.
|
|
2940
|
+
*
|
|
2941
|
+
* The Command Deck identifies projects by display name, not yaml key
|
|
2942
|
+
* (PAN-2590) — accept either, like GET /api/session-trees does.
|
|
2943
|
+
*/
|
|
2944
|
+
function resolveProjectCwd(projectKey) {
|
|
2945
|
+
const projectConfig = getProjectSync(projectKey) ?? listProjectsSync().find((p) => p.config.name === projectKey)?.config ?? null;
|
|
2946
|
+
if (!projectConfig) return { error: `Unknown project: ${projectKey}` };
|
|
2947
|
+
if (!projectConfig.path || !existsSync(projectConfig.path)) return { error: `Project path does not exist: ${projectConfig.path || "(unset)"} (project: ${projectKey})` };
|
|
2948
|
+
return { cwd: projectConfig.path };
|
|
2949
|
+
}
|
|
2950
|
+
async function handleConversationCreate(body, deps) {
|
|
2951
|
+
try {
|
|
2952
|
+
const message = typeof body["message"] === "string" ? body["message"].trim() : "";
|
|
2953
|
+
const model = typeof body["model"] === "string" ? body["model"].trim() : void 0;
|
|
2954
|
+
const effort = typeof body["effort"] === "string" ? body["effort"].trim() : void 0;
|
|
2955
|
+
const harness = await resolveAllowedHarness(body["harness"], model);
|
|
2956
|
+
const issueId = typeof body["issueId"] === "string" ? body["issueId"] : void 0;
|
|
2957
|
+
const projectKey = typeof body["projectKey"] === "string" ? body["projectKey"].trim() : void 0;
|
|
2958
|
+
if (issueId && !SAFE_ISSUE_ID_PATTERN.test(issueId)) return jsonResponse({ error: "Invalid issueId" }, { status: 400 });
|
|
2959
|
+
if (model && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
|
|
2960
|
+
if (effort && !SAFE_EFFORT_PATTERN.test(effort)) return jsonResponse({ error: "Invalid effort" }, { status: 400 });
|
|
2961
|
+
let cwd = getDefaultCwd();
|
|
2962
|
+
if (projectKey) {
|
|
2963
|
+
const resolved = resolveProjectCwd(projectKey);
|
|
2964
|
+
if ("error" in resolved) return jsonResponse({ error: resolved.error }, { status: 400 });
|
|
2965
|
+
cwd = resolved.cwd;
|
|
2966
|
+
}
|
|
2967
|
+
if (message && message.length > 5e4) return jsonResponse({ error: "message exceeds maximum length of 50000 characters" }, { status: 400 });
|
|
2968
|
+
let name = generateConversationName();
|
|
2969
|
+
for (let i = 0; i < 5 && getConversationByName(name); i++) name = generateConversationName();
|
|
2970
|
+
const tmuxSession = `conv-${name}`;
|
|
2971
|
+
const claudeSessionId = randomUUID$1();
|
|
2972
|
+
console.log(`[conversations] Creating conversation "${name}" with model=${model ?? "default"} effort=${effort ?? "default"} cwd=${cwd}`);
|
|
2973
|
+
const MAX_TITLE_LEN = 60;
|
|
2974
|
+
const title = message ? message.slice(0, MAX_TITLE_LEN) + (message.length > MAX_TITLE_LEN ? "…" : "") : "New conversation";
|
|
2975
|
+
const conv = createConversation({
|
|
2976
|
+
name,
|
|
2977
|
+
tmuxSession,
|
|
2978
|
+
cwd,
|
|
2979
|
+
issueId,
|
|
2980
|
+
claudeSessionId,
|
|
2981
|
+
title,
|
|
2982
|
+
titleSource: message ? "auto" : "default",
|
|
2983
|
+
titleSeed: title,
|
|
2984
|
+
model,
|
|
2985
|
+
effort,
|
|
2986
|
+
harness
|
|
2987
|
+
});
|
|
2988
|
+
getEventStore().emitOnly({
|
|
2989
|
+
type: "conversation.created",
|
|
2990
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2991
|
+
payload: { conversationName: name }
|
|
2992
|
+
});
|
|
2993
|
+
(async () => {
|
|
2994
|
+
try {
|
|
2995
|
+
await spawnConversationSession(tmuxSession, cwd, claudeSessionId, model, effort, issueId, false, harness);
|
|
2996
|
+
console.log(`[conversations] tmux session ${tmuxSession} spawned, sessionId: ${claudeSessionId}`);
|
|
2997
|
+
await waitForConversationRuntimeReady(tmuxSession, harness, "spawn");
|
|
2998
|
+
if (message) await deliverAgentMessage(tmuxSession, message, "conversation-message", resolveConversationDeliveryMethod(conv));
|
|
2999
|
+
} catch (spawnErr) {
|
|
3000
|
+
const msg = spawnErr instanceof Error ? spawnErr.message : String(spawnErr);
|
|
3001
|
+
console.error(`[conversations] background spawn failed for ${tmuxSession}: ${msg}`);
|
|
3002
|
+
updateSpawnError(name, msg);
|
|
3003
|
+
getEventStore().emitOnly({
|
|
3004
|
+
type: "conversation.created",
|
|
3005
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3006
|
+
payload: { conversationName: name }
|
|
3007
|
+
});
|
|
3008
|
+
}
|
|
3009
|
+
})();
|
|
3010
|
+
if (message) deps.generateAiTitle(name, message).catch((err) => {
|
|
3011
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3012
|
+
console.error(`[TITLE-GEN-FAILED] AI title generation FAILED for "${name}" — NO RETRY, NO FALLBACK:`, msg);
|
|
3013
|
+
});
|
|
3014
|
+
return jsonResponse({
|
|
3015
|
+
...conv,
|
|
3016
|
+
sessionAlive: false
|
|
3017
|
+
}, { status: 201 });
|
|
3018
|
+
} catch (error) {
|
|
3019
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3020
|
+
console.error("[conversations] create conversation failed:", msg);
|
|
3021
|
+
return jsonResponse({ error: msg || "Internal server error" }, { status: error instanceof UnknownModelError ? 400 : 500 });
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
async function handleConversationStop(name, deps) {
|
|
3025
|
+
try {
|
|
3026
|
+
const conv = getConversationByName(name);
|
|
3027
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3028
|
+
await stopConversationRuntime(conv, name);
|
|
3029
|
+
markConversationEnded(name);
|
|
3030
|
+
(async () => {
|
|
3031
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
3032
|
+
await cleanupUnreferencedConversationAttachments({
|
|
3033
|
+
name: conv.name,
|
|
3034
|
+
sessionFile: deps.resolveSessionFileForCleanup?.(conv) ?? null
|
|
3035
|
+
});
|
|
3036
|
+
})();
|
|
3037
|
+
return jsonResponse({ success: true });
|
|
3038
|
+
} catch (error) {
|
|
3039
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3040
|
+
console.error("[conversations] stop conversation failed:", msg);
|
|
3041
|
+
return jsonResponse({ error: "Internal server error" }, { status: 500 });
|
|
3042
|
+
}
|
|
3043
|
+
}
|
|
3044
|
+
async function handleConversationResume(name, body, deps) {
|
|
3045
|
+
try {
|
|
3046
|
+
const conv = getConversationByName(name);
|
|
3047
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3048
|
+
const model = typeof body["model"] === "string" && body["model"].trim() ? body["model"].trim() : conv.model ?? void 0;
|
|
3049
|
+
const effort = typeof body["effort"] === "string" && body["effort"].trim() ? body["effort"].trim() : conv.effort ?? void 0;
|
|
3050
|
+
if (await isHarnessProcessAlive(conv.tmuxSession)) {
|
|
3051
|
+
updateLastAttached(name);
|
|
3052
|
+
markConversationActive(name);
|
|
3053
|
+
return jsonResponse({
|
|
3054
|
+
...conv,
|
|
3055
|
+
status: "active",
|
|
3056
|
+
reattached: true
|
|
3057
|
+
});
|
|
3058
|
+
}
|
|
3059
|
+
const oldSessionId = conv.claudeSessionId;
|
|
3060
|
+
const harness = conv.harness ?? "claude-code";
|
|
3061
|
+
const modelChanged = !!model && model !== conv.model;
|
|
3062
|
+
if (!await validateCwdContainment(conv.cwd)) return jsonResponse({ error: "Invalid cwd" }, { status: 400 });
|
|
3063
|
+
if (model && modelChanged && !SAFE_MODEL_PATTERN.test(model)) return jsonResponse({ error: "Invalid model" }, { status: 400 });
|
|
3064
|
+
if (model && modelChanged) setConversationModel(name, model);
|
|
3065
|
+
let canResume = !!oldSessionId;
|
|
3066
|
+
if (oldSessionId) {
|
|
3067
|
+
const resumeFile = await deps.resolveSessionFile(conv);
|
|
3068
|
+
if (!resumeFile || !existsSync(resumeFile)) {
|
|
3069
|
+
canResume = false;
|
|
3070
|
+
console.error(`[conversations] SESSION-LOST ${name} harness=${harness} claudeSessionId=${oldSessionId} resolved=${resumeFile ?? "null"} — resuming with a fresh session`);
|
|
3071
|
+
}
|
|
3072
|
+
}
|
|
3073
|
+
const respawn = markRespawnPending(conv.tmuxSession);
|
|
3074
|
+
try {
|
|
3075
|
+
await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), model, effort, conv.issueId ?? void 0, canResume, harness);
|
|
3076
|
+
await waitForTmuxSession(conv.tmuxSession);
|
|
3077
|
+
await waitForConversationRuntimeReady(conv.tmuxSession, harness, "respawn");
|
|
3078
|
+
markConversationActive(name);
|
|
3079
|
+
return jsonResponse({
|
|
3080
|
+
...conv,
|
|
3081
|
+
status: "active",
|
|
3082
|
+
model: model ?? conv.model,
|
|
3083
|
+
harness,
|
|
3084
|
+
reattached: false,
|
|
3085
|
+
sessionAlive: true
|
|
3086
|
+
});
|
|
3087
|
+
} finally {
|
|
3088
|
+
respawn.done();
|
|
3089
|
+
}
|
|
3090
|
+
} catch (error) {
|
|
3091
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3092
|
+
console.error("[conversations] resume conversation failed:", msg);
|
|
3093
|
+
return jsonResponse({ error: msg || "Internal server error" }, { status: 500 });
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
async function handleConversationDelete(name, deps) {
|
|
3097
|
+
try {
|
|
3098
|
+
const conv = getConversationByName(name);
|
|
3099
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3100
|
+
await stopConversationRuntime(conv, name);
|
|
3101
|
+
markConversationEnded(name);
|
|
3102
|
+
archiveConversation(name);
|
|
3103
|
+
removeFavorite("conversation", name);
|
|
3104
|
+
deps.invalidateFavoritesCache();
|
|
3105
|
+
await cleanupConversationAttachments(name);
|
|
3106
|
+
return jsonResponse({ success: true });
|
|
3107
|
+
} catch (error) {
|
|
3108
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3109
|
+
console.error("[conversations] delete conversation failed:", msg);
|
|
3110
|
+
return jsonResponse({ error: "Internal server error" }, { status: 500 });
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
async function handleConversationRestartAll(deps) {
|
|
3114
|
+
try {
|
|
3115
|
+
const allConvs = listConversations();
|
|
3116
|
+
const liveSessionNames = new Set(await Effect.runPromise(listSessionNames()));
|
|
3117
|
+
const convs = allConvs.filter((c) => liveSessionNames.has(c.tmuxSession));
|
|
3118
|
+
const results = [];
|
|
3119
|
+
for (const conv of convs) {
|
|
3120
|
+
const respawn = markRespawnPending(conv.tmuxSession);
|
|
3121
|
+
try {
|
|
3122
|
+
await Effect.runPromise(killSession(conv.tmuxSession).pipe(Effect.catch(() => Effect.succeed(void 0))));
|
|
3123
|
+
const oldSessionId = conv.claudeSessionId;
|
|
3124
|
+
const sessionFileForResume = await deps.resolveSessionFile(conv);
|
|
3125
|
+
const canResume = !!oldSessionId && !!sessionFileForResume && existsSync(sessionFileForResume);
|
|
3126
|
+
const harness = await resolveAllowedHarness(conv.harness, conv.model);
|
|
3127
|
+
await spawnConversationSession(conv.tmuxSession, conv.cwd, oldSessionId ?? randomUUID$1(), conv.model ?? void 0, conv.effort ?? void 0, conv.issueId ?? void 0, canResume, harness);
|
|
3128
|
+
setConversationHarness(conv.name, harness);
|
|
3129
|
+
markConversationActive(conv.name);
|
|
3130
|
+
results.push({
|
|
3131
|
+
name: conv.name,
|
|
3132
|
+
model: conv.model,
|
|
3133
|
+
status: "restarted"
|
|
3134
|
+
});
|
|
3135
|
+
} catch (err) {
|
|
3136
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3137
|
+
console.error(`[conversations] Failed to restart ${conv.name}:`, msg);
|
|
3138
|
+
results.push({
|
|
3139
|
+
name: conv.name,
|
|
3140
|
+
model: conv.model,
|
|
3141
|
+
status: "failed"
|
|
3142
|
+
});
|
|
3143
|
+
} finally {
|
|
3144
|
+
respawn.done();
|
|
3145
|
+
}
|
|
3146
|
+
}
|
|
3147
|
+
console.log(`[conversations] Restarted ${results.filter((r) => r.status === "restarted").length}/${convs.length} conversations`);
|
|
3148
|
+
return jsonResponse({
|
|
3149
|
+
restarted: results.length,
|
|
3150
|
+
results
|
|
3151
|
+
});
|
|
3152
|
+
} catch (error) {
|
|
3153
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3154
|
+
console.error("[conversations] restart conversations failed:", msg);
|
|
3155
|
+
return jsonResponse({ error: "Internal server error" }, { status: 500 });
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
//#endregion
|
|
3159
|
+
//#region ../../lib/overdeck/conversation-delivery.ts
|
|
3160
|
+
init_conversations();
|
|
3161
|
+
init_behavior();
|
|
3162
|
+
init_tmux();
|
|
3163
|
+
init_agents();
|
|
3164
|
+
const CONTROL_ACK_TIMEOUT_MS = 1e4;
|
|
3165
|
+
const pendingConversationControlAcks = /* @__PURE__ */ new Map();
|
|
3166
|
+
const PLAN_ACTION_KEYSTROKES = {
|
|
3167
|
+
"approve-auto": "1",
|
|
3168
|
+
"approve-manual": "2",
|
|
3169
|
+
"reject-ultraplan": "3"
|
|
3170
|
+
};
|
|
3171
|
+
/**
|
|
3172
|
+
* PAN-1520 (FR-4) — deliver a native plan-menu action to any tmux session
|
|
3173
|
+
* (conversation or agent). The plan menu is answered with raw keystrokes
|
|
3174
|
+
* (1/2/3/4), not a text message; `reject-feedback` follows the '4' keystroke
|
|
3175
|
+
* with the operator's feedback through the normal message pipeline.
|
|
3176
|
+
* Returns null on success, or an error string for an invalid action.
|
|
3177
|
+
*/
|
|
3178
|
+
async function deliverPlanActionToSession(sessionName, action, feedback, deliveryMethod = "auto") {
|
|
3179
|
+
if (action === "reject-feedback") {
|
|
3180
|
+
await Effect.runPromise(sendRawKeystroke(sessionName, "4", "plan-action-reject"));
|
|
3181
|
+
if (feedback) {
|
|
3182
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
3183
|
+
await deliverAgentMessage(sessionName, feedback, "plan-action-feedback", deliveryMethod);
|
|
3184
|
+
}
|
|
3185
|
+
return null;
|
|
3186
|
+
}
|
|
3187
|
+
const keystroke = PLAN_ACTION_KEYSTROKES[action];
|
|
3188
|
+
if (!keystroke) return `Invalid action: ${action}`;
|
|
3189
|
+
await Effect.runPromise(sendRawKeystroke(sessionName, keystroke, `plan-action-${action}`));
|
|
3190
|
+
return null;
|
|
3191
|
+
}
|
|
3192
|
+
function registerConversationControlAck(commandId, timeoutMs = CONTROL_ACK_TIMEOUT_MS) {
|
|
3193
|
+
const existing = pendingConversationControlAcks.get(commandId);
|
|
3194
|
+
if (existing) {
|
|
3195
|
+
clearTimeout(existing.timer);
|
|
3196
|
+
existing.reject(/* @__PURE__ */ new Error(`Replaced pending conversation control ack ${commandId}`));
|
|
3197
|
+
pendingConversationControlAcks.delete(commandId);
|
|
3198
|
+
}
|
|
3199
|
+
return new Promise((resolve, reject) => {
|
|
3200
|
+
const timer = setTimeout(() => {
|
|
3201
|
+
pendingConversationControlAcks.delete(commandId);
|
|
3202
|
+
reject(/* @__PURE__ */ new Error(`Timed out waiting for conversation control ack ${commandId}`));
|
|
3203
|
+
}, timeoutMs);
|
|
3204
|
+
pendingConversationControlAcks.set(commandId, {
|
|
3205
|
+
resolve: () => {
|
|
3206
|
+
clearTimeout(timer);
|
|
3207
|
+
pendingConversationControlAcks.delete(commandId);
|
|
3208
|
+
resolve();
|
|
3209
|
+
},
|
|
3210
|
+
reject: (error) => {
|
|
3211
|
+
clearTimeout(timer);
|
|
3212
|
+
pendingConversationControlAcks.delete(commandId);
|
|
3213
|
+
reject(error);
|
|
3214
|
+
},
|
|
3215
|
+
timer
|
|
3216
|
+
});
|
|
3217
|
+
});
|
|
3218
|
+
}
|
|
3219
|
+
function resolveConversationControlAck(ack) {
|
|
3220
|
+
const pending = pendingConversationControlAcks.get(ack.id);
|
|
3221
|
+
if (!pending) return "unknown";
|
|
3222
|
+
if (ack.ok) {
|
|
3223
|
+
pending.resolve();
|
|
3224
|
+
return "resolved";
|
|
3225
|
+
}
|
|
3226
|
+
pending.reject(new Error(ack.error || `Conversation control command ${ack.id} failed`));
|
|
3227
|
+
return "rejected";
|
|
3228
|
+
}
|
|
3229
|
+
function handleConversationControlAck(body) {
|
|
3230
|
+
const id = typeof body["id"] === "string" ? body["id"].trim() : "";
|
|
3231
|
+
if (!id) return {
|
|
3232
|
+
status: 400,
|
|
3233
|
+
body: { error: "id is required" }
|
|
3234
|
+
};
|
|
3235
|
+
const ok = body["ok"] === true;
|
|
3236
|
+
const error = typeof body["error"] === "string" ? body["error"] : void 0;
|
|
3237
|
+
return {
|
|
3238
|
+
status: 200,
|
|
3239
|
+
body: {
|
|
3240
|
+
ok: true,
|
|
3241
|
+
outcome: resolveConversationControlAck({
|
|
3242
|
+
id,
|
|
3243
|
+
ok,
|
|
3244
|
+
...error !== void 0 ? { error } : {}
|
|
3245
|
+
})
|
|
3246
|
+
}
|
|
3247
|
+
};
|
|
3248
|
+
}
|
|
3249
|
+
function validateConversationControlAckOrigin(headers, method = "POST") {
|
|
3250
|
+
const origin = getHeaderFromMap(headers, "origin");
|
|
3251
|
+
const referer = getHeaderFromMap(headers, "referer");
|
|
3252
|
+
if (!origin && !referer) return { ok: true };
|
|
3253
|
+
return validateOriginHeaders(headers, method);
|
|
3254
|
+
}
|
|
3255
|
+
async function handleConversationCodexApproval(rawId, body) {
|
|
3256
|
+
try {
|
|
3257
|
+
const optionNumber = Number(body.optionNumber);
|
|
3258
|
+
if (!Number.isInteger(optionNumber) || optionNumber < 1 || optionNumber > 9) return jsonResponse({ error: "optionNumber must be an integer 1-9" }, { status: 400 });
|
|
3259
|
+
const numericId = Number(rawId);
|
|
3260
|
+
const conv = !Number.isNaN(numericId) && /^\d+$/.test(rawId) ? getConversationById(numericId) : getConversationByName(rawId);
|
|
3261
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3262
|
+
if (getHarnessBehavior(conv.harness).transcriptKind !== "codex-rollout-jsonl") return jsonResponse({ error: "Not a Codex conversation" }, { status: 400 });
|
|
3263
|
+
if (!await tmuxSessionExists(conv.tmuxSession)) return jsonResponse({ error: "Conversation session is not running" }, { status: 409 });
|
|
3264
|
+
const detection = await Effect.runPromise(detectAwaitingInputForAgent(conv.tmuxSession, {
|
|
3265
|
+
isPlanning: false,
|
|
3266
|
+
cache: false
|
|
3267
|
+
}));
|
|
3268
|
+
const parsed = detection ? parseCodexApprovalPrompt(detection.prompt) : null;
|
|
3269
|
+
if (!parsed) return jsonResponse({ error: "No Codex approval prompt is currently pending" }, { status: 409 });
|
|
3270
|
+
if (optionNumber > parsed.options.length) return jsonResponse({ error: `optionNumber out of range (1-${parsed.options.length})` }, { status: 400 });
|
|
3271
|
+
await deliverCodexApprovalChoice(conv.tmuxSession, optionNumber);
|
|
3272
|
+
return jsonResponse({
|
|
3273
|
+
ok: true,
|
|
3274
|
+
optionNumber
|
|
3275
|
+
});
|
|
3276
|
+
} catch (error) {
|
|
3277
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3278
|
+
console.error("[conversations] codex approval failed:", msg);
|
|
3279
|
+
return jsonResponse({ error: "Internal server error" }, { status: 500 });
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
async function handleConversationPlanAction(name, body) {
|
|
3283
|
+
try {
|
|
3284
|
+
const conv = getConversationByName(name);
|
|
3285
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3286
|
+
const action = typeof body["action"] === "string" ? body["action"] : "";
|
|
3287
|
+
const feedback = typeof body["feedback"] === "string" ? body["feedback"].trim() : "";
|
|
3288
|
+
const error = await deliverPlanActionToSession(conv.tmuxSession, action, feedback, resolveConversationDeliveryMethod(conv));
|
|
3289
|
+
if (error) return jsonResponse({ error }, { status: 400 });
|
|
3290
|
+
return jsonResponse({ ok: true });
|
|
3291
|
+
} catch (error) {
|
|
3292
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3293
|
+
console.error("[conversations] plan action failed:", msg);
|
|
3294
|
+
return jsonResponse({ error: "Internal server error" }, { status: 500 });
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
const THINKING_LEVELS = [
|
|
3298
|
+
"off",
|
|
3299
|
+
"minimal",
|
|
3300
|
+
"low",
|
|
3301
|
+
"medium",
|
|
3302
|
+
"high",
|
|
3303
|
+
"xhigh"
|
|
3304
|
+
];
|
|
3305
|
+
const PI_CONVERSATION_ABORT_KEY = "Escape";
|
|
3306
|
+
function isPiControlChannelHarness(harness) {
|
|
3307
|
+
return harness === "ohmypi" || harness === "pi";
|
|
3308
|
+
}
|
|
3309
|
+
function parseThinkingLevel(value) {
|
|
3310
|
+
return typeof value === "string" && THINKING_LEVELS.includes(value) ? value : null;
|
|
3311
|
+
}
|
|
3312
|
+
function pickDeliverAs(bodyDeliverAs) {
|
|
3313
|
+
if (bodyDeliverAs === "follow_up") return "follow_up";
|
|
3314
|
+
return "steer";
|
|
3315
|
+
}
|
|
3316
|
+
function resolveConversationDeliveryMethod(conv) {
|
|
3317
|
+
const harness = conv.harness ?? "claude-code";
|
|
3318
|
+
if (isPiControlChannelHarness(harness)) return "auto";
|
|
3319
|
+
return conv.deliveryMethod ?? (getHarnessBehavior(harness).deliveryKind === "rpc-fifo" ? "tmux" : "auto");
|
|
3320
|
+
}
|
|
3321
|
+
async function sendConversationControlCommand(conv, commandInput) {
|
|
3322
|
+
const id = randomUUID$1();
|
|
3323
|
+
const ackPromise = registerConversationControlAck(id);
|
|
3324
|
+
const command = {
|
|
3325
|
+
id,
|
|
3326
|
+
...commandInput
|
|
3327
|
+
};
|
|
3328
|
+
try {
|
|
3329
|
+
await writeConversationControlCommand(conv.tmuxSession, command);
|
|
3330
|
+
} catch (err) {
|
|
3331
|
+
resolveConversationControlAck({
|
|
3332
|
+
id,
|
|
3333
|
+
ok: false,
|
|
3334
|
+
error: err instanceof Error ? err.message : String(err)
|
|
3335
|
+
});
|
|
3336
|
+
}
|
|
3337
|
+
await ackPromise;
|
|
3338
|
+
}
|
|
3339
|
+
async function deliverConversationViaControlChannel(conv, message, options) {
|
|
3340
|
+
await sendConversationControlCommand(conv, {
|
|
3341
|
+
type: options.deliverAs,
|
|
3342
|
+
message,
|
|
3343
|
+
source: options.source
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
async function handleConversationThinkingLevel(name, body) {
|
|
3347
|
+
const conv = getConversationByName(name);
|
|
3348
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3349
|
+
if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Thinking level control is only supported for Pi conversations" }, { status: 400 });
|
|
3350
|
+
if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
|
|
3351
|
+
const level = parseThinkingLevel(body["level"]);
|
|
3352
|
+
if (!level) return jsonResponse({ error: "Invalid thinking level" }, { status: 400 });
|
|
3353
|
+
await sendConversationControlCommand(conv, {
|
|
3354
|
+
type: "set_thinking_level",
|
|
3355
|
+
level
|
|
3356
|
+
});
|
|
3357
|
+
setConversationEffort(name, level);
|
|
3358
|
+
return jsonResponse({
|
|
3359
|
+
ok: true,
|
|
3360
|
+
effort: (getConversationByName(name) ?? conv).effort ?? level
|
|
3361
|
+
});
|
|
3362
|
+
}
|
|
3363
|
+
async function handleConversationCompact(name) {
|
|
3364
|
+
const conv = getConversationByName(name);
|
|
3365
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3366
|
+
if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Compact control endpoint is only supported for Pi conversations" }, { status: 400 });
|
|
3367
|
+
if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
|
|
3368
|
+
await sendConversationControlCommand(conv, { type: "compact" });
|
|
3369
|
+
return jsonResponse({ ok: true });
|
|
3370
|
+
}
|
|
3371
|
+
async function handleConversationAbort(name) {
|
|
3372
|
+
const conv = getConversationByName(name);
|
|
3373
|
+
if (!conv) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3374
|
+
if (!isPiControlChannelHarness(conv.harness ?? "claude-code")) return jsonResponse({ error: "Abort control endpoint is only supported for Pi conversations" }, { status: 400 });
|
|
3375
|
+
if (conv.status === "ended") return jsonResponse({ error: "Session has ended — start a new run to interact" }, { status: 422 });
|
|
3376
|
+
await sendKeysAsync(conv.tmuxSession, PI_CONVERSATION_ABORT_KEY, "conversation-abort");
|
|
3377
|
+
return jsonResponse({
|
|
3378
|
+
ok: true,
|
|
3379
|
+
key: PI_CONVERSATION_ABORT_KEY
|
|
3380
|
+
});
|
|
3381
|
+
}
|
|
3382
|
+
async function handleConversationDeliveryMethod(name, body) {
|
|
3383
|
+
if (!getConversationByName(name)) return jsonResponse({ error: "Conversation not found" }, { status: 404 });
|
|
3384
|
+
const deliveryMethod = body["deliveryMethod"] ?? body["method"];
|
|
3385
|
+
if (deliveryMethod !== "auto" && deliveryMethod !== "channels" && deliveryMethod !== "tmux" && deliveryMethod !== null) return jsonResponse({ error: "deliveryMethod must be 'auto', 'channels', 'tmux', or null" }, { status: 400 });
|
|
3386
|
+
updateConversationDeliveryMethod(name, deliveryMethod);
|
|
3387
|
+
return jsonResponse({
|
|
3388
|
+
ok: true,
|
|
3389
|
+
deliveryMethod
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
const CODEX_APPROVAL_TOOL_PREFIX = "codex-approval:";
|
|
3393
|
+
async function codexConversationPendingInput(conv, sessionAlive, askedAt) {
|
|
3394
|
+
if (!sessionAlive || getHarnessBehavior(conv.harness).transcriptKind !== "codex-rollout-jsonl") return { kinds: [] };
|
|
3395
|
+
try {
|
|
3396
|
+
const detection = await Effect.runPromise(detectAwaitingInputForAgent(conv.tmuxSession, { isPlanning: false }));
|
|
3397
|
+
if (!detection) return { kinds: [] };
|
|
3398
|
+
if (detection.reason === "session_resume") return { kinds: ["sessionResume"] };
|
|
3399
|
+
const parsed = parseCodexApprovalPrompt(detection.prompt);
|
|
3400
|
+
if (parsed) return {
|
|
3401
|
+
kinds: ["permissionRequest"],
|
|
3402
|
+
approval: {
|
|
3403
|
+
toolUseId: `${CODEX_APPROVAL_TOOL_PREFIX}${conv.tmuxSession}`,
|
|
3404
|
+
askedAt,
|
|
3405
|
+
questions: [{
|
|
3406
|
+
question: parsed.detail ? `${parsed.header}\n\n${parsed.detail}` : parsed.header,
|
|
3407
|
+
header: "Codex approval",
|
|
3408
|
+
multiSelect: false,
|
|
3409
|
+
options: parsed.options.map((o) => ({ label: `${o.number}. ${o.label}` }))
|
|
3410
|
+
}]
|
|
3411
|
+
}
|
|
3412
|
+
};
|
|
3413
|
+
return { kinds: ["permissionRequest"] };
|
|
3414
|
+
} catch {
|
|
3415
|
+
return { kinds: [] };
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
async function deliverCodexApprovalChoice(tmuxSession, optionNumber) {
|
|
3419
|
+
for (let i = 1; i < optionNumber; i += 1) {
|
|
3420
|
+
await Effect.runPromise(sendRawKeystroke(tmuxSession, "Down", "codex-approval"));
|
|
3421
|
+
await new Promise((r) => setTimeout(r, 60));
|
|
3422
|
+
}
|
|
3423
|
+
await Effect.runPromise(sendRawKeystroke(tmuxSession, "Enter", "codex-approval"));
|
|
3424
|
+
}
|
|
3425
|
+
//#endregion
|
|
1911
3426
|
//#region ../../lib/channels/permission-payload.ts
|
|
1912
3427
|
const CHANNEL_PERMISSION_LIMITS = {
|
|
1913
3428
|
requestIdBytes: 128,
|
|
@@ -2127,6 +3642,30 @@ const postAgentAnswerQuestionRoute = HttpRouter.add("POST", "/api/agents/:id/ans
|
|
|
2127
3642
|
delivered: answers.length
|
|
2128
3643
|
});
|
|
2129
3644
|
})));
|
|
3645
|
+
const postAgentPlanActionRoute = HttpRouter.add("POST", "/api/agents/:id/plan-action", httpHandler(Effect.gen(function* () {
|
|
3646
|
+
const id = (yield* HttpRouter.params)["id"] ?? "";
|
|
3647
|
+
if (!id.trim()) return jsonResponse({ error: "missing agent id" }, { status: 400 });
|
|
3648
|
+
const body = yield* readJsonBody$1;
|
|
3649
|
+
const action = typeof body["action"] === "string" ? body["action"] : "";
|
|
3650
|
+
const feedback = typeof body["feedback"] === "string" ? body["feedback"].trim() : "";
|
|
3651
|
+
const agentState = yield* getAgentState(id).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
3652
|
+
if (!agentState) return jsonResponse({ error: "Agent not found" }, { status: 404 });
|
|
3653
|
+
const jsonlPath = yield* getAgentJsonlPath$1(id).pipe(Effect.catch(() => Effect.succeed(null)));
|
|
3654
|
+
if (!(jsonlPath ? yield* Effect.promise(() => scanPendingInputsPromise(jsonlPath)) : null)?.exitPlanModePending) return jsonResponse({ error: "No pending plan approval for this agent" }, { status: 409 });
|
|
3655
|
+
const error = yield* Effect.promise(() => deliverPlanActionToSession(id, action, feedback));
|
|
3656
|
+
if (error) return jsonResponse({ error }, { status: 400 });
|
|
3657
|
+
emitActivityEntrySync({
|
|
3658
|
+
source: "dashboard",
|
|
3659
|
+
level: "info",
|
|
3660
|
+
issueId: agentState.issueId,
|
|
3661
|
+
message: `Operator ${action.startsWith("approve") ? "approved" : "requested changes to"} ${id}'s plan from the dashboard`
|
|
3662
|
+
});
|
|
3663
|
+
return jsonResponse({
|
|
3664
|
+
success: true,
|
|
3665
|
+
agentId: id,
|
|
3666
|
+
action
|
|
3667
|
+
});
|
|
3668
|
+
})));
|
|
2130
3669
|
const postInternalAgentPermissionRequestRoute = HttpRouter.add("POST", "/api/internal/agents/:id/permissions/request", httpHandler(Effect.gen(function* () {
|
|
2131
3670
|
const id = (yield* HttpRouter.params)["id"] ?? "";
|
|
2132
3671
|
if (!id.trim()) return jsonResponse({
|
|
@@ -2134,7 +3673,7 @@ const postInternalAgentPermissionRequestRoute = HttpRouter.add("POST", "/api/int
|
|
|
2134
3673
|
error: "missing agent id"
|
|
2135
3674
|
}, { status: 400 });
|
|
2136
3675
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
2137
|
-
const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-
|
|
3676
|
+
const { INTERNAL_TOKEN_HEADER, getInternalTokenSync } = yield* Effect.promise(() => import("./internal-token-BPYEaXFh.js"));
|
|
2138
3677
|
const expected = getInternalTokenSync();
|
|
2139
3678
|
if (!expected) return jsonResponse({
|
|
2140
3679
|
ok: false,
|
|
@@ -2401,7 +3940,7 @@ const getAgentHealthHistoryRoute = HttpRouter.add("GET", "/api/agents/:id/health
|
|
|
2401
3940
|
const request = yield* HttpServerRequest.HttpServerRequest;
|
|
2402
3941
|
const urlOpt = HttpServerRequest.toURL(request);
|
|
2403
3942
|
const hours = Option.isSome(urlOpt) ? urlOpt.value.searchParams.get("hours") ?? "24" : "24";
|
|
2404
|
-
const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-
|
|
3943
|
+
const { getHealthHistory } = yield* Effect.promise(() => import("./health-events-Bos3sk5g.js"));
|
|
2405
3944
|
const endTime = /* @__PURE__ */ new Date();
|
|
2406
3945
|
const startTime = /* @__PURE__ */ new Date(endTime.getTime() - parseInt(hours) * 60 * 60 * 1e3);
|
|
2407
3946
|
const events = getHealthHistory(id, startTime.toISOString(), endTime.toISOString());
|
|
@@ -2442,7 +3981,7 @@ function emitAgentRuntimeEvent(id, body, timestamp) {
|
|
|
2442
3981
|
detail: String(decoded.failure)
|
|
2443
3982
|
}, { status: 400 })
|
|
2444
3983
|
};
|
|
2445
|
-
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-
|
|
3984
|
+
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-CZrayD_G.js"));
|
|
2446
3985
|
yield* (yield* AgentStateService).emit(decoded.success);
|
|
2447
3986
|
return {
|
|
2448
3987
|
ok: true,
|
|
@@ -2524,7 +4063,7 @@ const getAgentRuntimeRoute = HttpRouter.add("GET", "/api/agents/:id/runtime", ht
|
|
|
2524
4063
|
success: false,
|
|
2525
4064
|
error: "missing agent id"
|
|
2526
4065
|
}, { status: 400 });
|
|
2527
|
-
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-
|
|
4066
|
+
const { AgentStateService } = yield* Effect.promise(() => import("./agent-state-service-CZrayD_G.js"));
|
|
2528
4067
|
const snapshot = yield* (yield* AgentStateService).get(id);
|
|
2529
4068
|
if (!snapshot) return jsonResponse({
|
|
2530
4069
|
success: false,
|
|
@@ -2550,7 +4089,9 @@ init_workspace_manager();
|
|
|
2550
4089
|
* commit, the persisted event is emitted to the event store's subscribers so
|
|
2551
4090
|
* the in-memory read model stays current.
|
|
2552
4091
|
*/
|
|
4092
|
+
init_event_store();
|
|
2553
4093
|
init_agents();
|
|
4094
|
+
init_persistent_logger();
|
|
2554
4095
|
function prepareAgentStateForSave(state) {
|
|
2555
4096
|
if (state.status === "running" || state.status === "starting") delete state.stoppedAt;
|
|
2556
4097
|
else if (state.status === "stopped" && !state.stoppedAt) state.stoppedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -2635,8 +4176,8 @@ function createAgentStopHandler(lifecycleEvent) {
|
|
|
2635
4176
|
yield* stopAgent(id);
|
|
2636
4177
|
if (stateBeforeStop?.issueId) yield* Effect.promise(async () => {
|
|
2637
4178
|
try {
|
|
2638
|
-
const { resolveProjectFromIssueSync } = await import("./projects-
|
|
2639
|
-
const { findWorkspacePath } = await import("./archive-planning-
|
|
4179
|
+
const { resolveProjectFromIssueSync } = await import("./projects-CrBE6IhN.js");
|
|
4180
|
+
const { findWorkspacePath } = await import("./archive-planning-CoyIbgGK.js");
|
|
2640
4181
|
const issueLower = stateBeforeStop.issueId.toLowerCase();
|
|
2641
4182
|
const workspacePath = findWorkspacePath(resolveProjectFromIssueSync(stateBeforeStop.issueId)?.projectPath ?? process.cwd(), issueLower);
|
|
2642
4183
|
if (workspacePath) {
|
|
@@ -2806,7 +4347,9 @@ const postAgentUntroubledRoute = HttpRouter.add("POST", "/api/agents/:id/untroub
|
|
|
2806
4347
|
})));
|
|
2807
4348
|
//#endregion
|
|
2808
4349
|
//#region routes/agents/lifecycle-restart.ts
|
|
4350
|
+
init_config_yaml();
|
|
2809
4351
|
init_agents();
|
|
4352
|
+
init_harness_policy();
|
|
2810
4353
|
init_operator_interventions();
|
|
2811
4354
|
init_projects();
|
|
2812
4355
|
init_tmux();
|
|
@@ -3711,19 +5254,20 @@ const writeAutoStartVBrief = (projectRoot, workspacePath, issue) => Effect.tryPr
|
|
|
3711
5254
|
//#endregion
|
|
3712
5255
|
//#region routes/agents/spawn-helpers.ts
|
|
3713
5256
|
init_issue_lifecycle();
|
|
5257
|
+
init_child_env();
|
|
3714
5258
|
init_agents();
|
|
3715
5259
|
function handleRemoteAgentSpawn(input) {
|
|
3716
5260
|
return Effect.gen(function* () {
|
|
3717
5261
|
const { issueId, workspacePath, workspaceMetadata, spawnModel, projectPath, spawnGuardrails, lifecycle } = input;
|
|
3718
|
-
const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-
|
|
3719
|
-
const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-
|
|
3720
|
-
const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-
|
|
5262
|
+
const { spawnRemoteAgent, checkRemoteSpendCap } = yield* Effect.promise(() => import("./remote-agents-Cv-U-_V8.js"));
|
|
5263
|
+
const { createFlyProviderFromConfig } = yield* Effect.promise(() => import("./remote-DW_TWQnr.js"));
|
|
5264
|
+
const { loadConfigSync: loadPanConfig } = yield* Effect.promise(() => import("./config-Dj6N8EP9.js"));
|
|
3721
5265
|
const panConfig = loadPanConfig();
|
|
3722
5266
|
const spendCap = checkRemoteSpendCap(panConfig);
|
|
3723
5267
|
if (!spendCap.allowed) return jsonResponse({ error: spendCap.message }, { status: 429 });
|
|
3724
5268
|
const fly = createFlyProviderFromConfig(panConfig.remote);
|
|
3725
5269
|
yield* Effect.promise(() => fly.syncAllCredentials(workspaceMetadata.vmName));
|
|
3726
|
-
const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-
|
|
5270
|
+
const { buildWorkAgentPrompt, getTrackerContext } = yield* Effect.promise(() => import("./work-agent-prompt-ZTAb2Hyf.js"));
|
|
3727
5271
|
const trackerContext = yield* Effect.promise(() => getTrackerContext(issueId, workspacePath));
|
|
3728
5272
|
const agentPrompt = yield* Effect.promise(() => buildWorkAgentPrompt({
|
|
3729
5273
|
issueId,
|
|
@@ -3809,7 +5353,7 @@ function handleContainerOrchestration(input) {
|
|
|
3809
5353
|
if (existsSync(workspacePath) && existsSync(devScript)) {
|
|
3810
5354
|
let dockerRunning = false;
|
|
3811
5355
|
try {
|
|
3812
|
-
yield* Effect.promise(() => execAsync$
|
|
5356
|
+
yield* Effect.promise(() => execAsync$2("docker info >/dev/null 2>&1", { encoding: "utf-8" }));
|
|
3813
5357
|
dockerRunning = true;
|
|
3814
5358
|
} catch {}
|
|
3815
5359
|
if (dockerRunning) {
|
|
@@ -3852,7 +5396,7 @@ function handleContainerOrchestration(input) {
|
|
|
3852
5396
|
}));
|
|
3853
5397
|
let containersReady = false;
|
|
3854
5398
|
try {
|
|
3855
|
-
const { stdout: existing } = yield* Effect.promise(() => execFileAsync$
|
|
5399
|
+
const { stdout: existing } = yield* Effect.promise(() => execFileAsync$2("docker", [
|
|
3856
5400
|
"ps",
|
|
3857
5401
|
"--filter",
|
|
3858
5402
|
`name=${featureName}`,
|
|
@@ -3918,7 +5462,7 @@ function handleContainerOrchestration(input) {
|
|
|
3918
5462
|
let healthy = false;
|
|
3919
5463
|
while (Date.now() - startTime < maxWaitMs) {
|
|
3920
5464
|
try {
|
|
3921
|
-
const { stdout } = await execFileAsync$
|
|
5465
|
+
const { stdout } = await execFileAsync$2("docker", [
|
|
3922
5466
|
"ps",
|
|
3923
5467
|
"--filter",
|
|
3924
5468
|
`name=${featureName}`,
|
|
@@ -4085,7 +5629,7 @@ function spawnGuardrailResourcesHint(hint) {
|
|
|
4085
5629
|
* invisible to a JS `try/catch`. Before this helper, that escaped as an
|
|
4086
5630
|
* unhandled 500 on POST /api/agents instead of the designed 422.
|
|
4087
5631
|
*/
|
|
4088
|
-
const countBeadsForIssue = (workspacePath, issueLower) => withBdMutex(() => Effect.promise(() => execFileAsync$
|
|
5632
|
+
const countBeadsForIssue = (workspacePath, issueLower) => withBdMutex(() => Effect.promise(() => execFileAsync$2("bd", [
|
|
4089
5633
|
"list",
|
|
4090
5634
|
"--json",
|
|
4091
5635
|
"-l",
|
|
@@ -4176,7 +5720,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
4176
5720
|
const workspacePath = join(projectPath, "workspaces", `feature-${issueLower}`);
|
|
4177
5721
|
if (!existsSync(workspacePath)) try {
|
|
4178
5722
|
const nodeDir = dirname(process.execPath);
|
|
4179
|
-
yield* Effect.promise(() => execAsync$
|
|
5723
|
+
yield* Effect.promise(() => execAsync$2(`pan workspace create ${issueId} --local`, {
|
|
4180
5724
|
cwd: projectPath,
|
|
4181
5725
|
encoding: "utf-8",
|
|
4182
5726
|
timeout: 6e4,
|
|
@@ -4252,7 +5796,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
4252
5796
|
if (!hasBeads) {
|
|
4253
5797
|
console.log(`[agents] No beads for ${issueId} — attempting auto-recovery via createBeadsFromVBrief`);
|
|
4254
5798
|
try {
|
|
4255
|
-
const { createBeadsFromVBrief } = yield* Effect.promise(() => import("./beads-
|
|
5799
|
+
const { createBeadsFromVBrief } = yield* Effect.promise(() => import("./beads-B1u04n3o.js"));
|
|
4256
5800
|
const recovery = yield* createBeadsFromVBrief(workspacePath);
|
|
4257
5801
|
beadCount = recovery.created.length;
|
|
4258
5802
|
hasBeads = recovery.created.length > 0 && (planItemCount === null || recovery.created.length === planItemCount);
|
|
@@ -4398,24 +5942,24 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
4398
5942
|
if (shouldCommitLegacyWorkspaceArtifacts(projectConfig ? yield* Effect.promise(() => isStateMigrated(projectConfig)) : false) && (existsSync(workspacePanContinuePath) || existsSync(workspacePanDir))) yield* Effect.gen(function* () {
|
|
4399
5943
|
const gitRoot = workspacePath;
|
|
4400
5944
|
if (existsSync(join(gitRoot, ".pan"))) {
|
|
4401
|
-
yield* Effect.promise(() => execAsync$
|
|
5945
|
+
yield* Effect.promise(() => execAsync$2(`git add .pan/`, {
|
|
4402
5946
|
cwd: gitRoot,
|
|
4403
5947
|
encoding: "utf-8"
|
|
4404
5948
|
}));
|
|
4405
|
-
yield* Effect.promise(() => execAsync$
|
|
5949
|
+
yield* Effect.promise(() => execAsync$2(`git reset HEAD -- .pan/kickoff.md .pan/continue.json .pan/handoff-*.md .pan/spec.vbrief.json`, {
|
|
4406
5950
|
cwd: gitRoot,
|
|
4407
5951
|
encoding: "utf-8"
|
|
4408
5952
|
}));
|
|
4409
5953
|
}
|
|
4410
|
-
if (existsSync(join(gitRoot, ".beads"))) yield* Effect.promise(() => execAsync$
|
|
5954
|
+
if (existsSync(join(gitRoot, ".beads"))) yield* Effect.promise(() => execAsync$2(`git add .beads/`, {
|
|
4411
5955
|
cwd: gitRoot,
|
|
4412
5956
|
encoding: "utf-8"
|
|
4413
5957
|
}));
|
|
4414
|
-
if (yield* Effect.promise(() => execAsync$
|
|
5958
|
+
if (yield* Effect.promise(() => execAsync$2(`git diff --cached --quiet`, {
|
|
4415
5959
|
cwd: gitRoot,
|
|
4416
5960
|
encoding: "utf-8"
|
|
4417
5961
|
}).then(() => false).catch(() => true))) {
|
|
4418
|
-
yield* Effect.promise(() => execAsync$
|
|
5962
|
+
yield* Effect.promise(() => execAsync$2(`git commit -m "chore: planning artifacts for ${issueId} before agent start"`, {
|
|
4419
5963
|
cwd: gitRoot,
|
|
4420
5964
|
encoding: "utf-8"
|
|
4421
5965
|
}));
|
|
@@ -4442,7 +5986,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
4442
5986
|
console.warn(`[start-agent] Failed to set plan.status=running (non-fatal): ${planStatusErr?.message ?? planStatusErr}`);
|
|
4443
5987
|
}
|
|
4444
5988
|
try {
|
|
4445
|
-
const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = yield* Effect.promise(() => import("./record-
|
|
5989
|
+
const { appendSessionEntry, getProjectConfigFromWorkspacePath, resolveProjectForIssue } = yield* Effect.promise(() => import("./record-lEYK__LD.js"));
|
|
4446
5990
|
const recordProject = resolveProjectForIssue(issueId) ?? getProjectConfigFromWorkspacePath(workspacePath);
|
|
4447
5991
|
yield* Effect.promise(() => appendSessionEntry(recordProject, issueId, {
|
|
4448
5992
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4491,12 +6035,12 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
4491
6035
|
console.log(`[start-agent] Killed stale tmux session ${agentSessionName}`);
|
|
4492
6036
|
}).pipe(Effect.catch(() => Effect.void));
|
|
4493
6037
|
if (!(body.acknowledgeDirtyWorkspace === true)) try {
|
|
4494
|
-
const { stdout: statusOut } = yield* Effect.promise(() => execAsync$
|
|
6038
|
+
const { stdout: statusOut } = yield* Effect.promise(() => execAsync$2("git status --porcelain", {
|
|
4495
6039
|
cwd: workspacePath,
|
|
4496
6040
|
encoding: "utf-8"
|
|
4497
6041
|
}));
|
|
4498
6042
|
if (statusOut.trim()) {
|
|
4499
|
-
const { stdout: diffOut } = yield* Effect.promise(() => execAsync$
|
|
6043
|
+
const { stdout: diffOut } = yield* Effect.promise(() => execAsync$2("git diff HEAD --stat", {
|
|
4500
6044
|
cwd: workspacePath,
|
|
4501
6045
|
encoding: "utf-8"
|
|
4502
6046
|
}).catch(() => ({ stdout: "" })));
|
|
@@ -4688,7 +6232,7 @@ const postAgentsRoute = HttpRouter.add("POST", "/api/agents", httpHandler(Effect
|
|
|
4688
6232
|
* Composes all /api/agents/* route layers and preserves the public helper
|
|
4689
6233
|
* exports that external callers historically imported from routes/agents.js.
|
|
4690
6234
|
*/
|
|
4691
|
-
const agentsRouteLayer = Layer.mergeAll(getAgentsRoute, getAgentOutputRoute, getAgentConversationRoute, postAgentMessageRoute, postAgentTellRoute, deleteAgentRoute, postAgentStopRoute, getAgentHealthHistoryRoute, postAgentPokeRoute, getAgentPendingQuestionsRoute, postAgentAnswerQuestionRoute, postAgentHeartbeatRoute, postAgentWorkCompleteRoute, postAgentStuckRoute, postAgentClassifyCompletionRoute, postInternalAgentPermissionRequestRoute, postAgentPermissionResponseRoute, getAgentRuntimeRoute, getAgentGitInfoRoute, getAgentActivityRoute, getAgentFilesRoute, getAgentTimelineRoute, postAgentSuspendRoute, postAgentPauseRoute, postAgentUnpauseRoute, postAgentUntroubledRoute, postAgentResumeRoute, postAgentRecoverRoute, postAgentRestartRoute, getAgentCloisterHealthRoute, getAgentHandoffSuggestionRoute, postAgentHandoffRoute, getAgentCostRoute, postAgentsRoute, postAgentsRestartAllRoute, getAgentTmuxAliveRoute, getAgentHasSessionRoute, postAgentResetSessionRoute, postAgentSwitchModelRoute, postAgentRestartFreshRoute, postAgentDeliveryMethodRoute, getAgentsRestartConfigRoute, postAgentsRestartWithConfigRoute);
|
|
6235
|
+
const agentsRouteLayer = Layer.mergeAll(getAgentsRoute, getAgentOutputRoute, getAgentConversationRoute, postAgentMessageRoute, postAgentTellRoute, deleteAgentRoute, postAgentStopRoute, getAgentHealthHistoryRoute, postAgentPokeRoute, getAgentPendingQuestionsRoute, postAgentAnswerQuestionRoute, postAgentPlanActionRoute, postAgentHeartbeatRoute, postAgentWorkCompleteRoute, postAgentStuckRoute, postAgentClassifyCompletionRoute, postInternalAgentPermissionRequestRoute, postAgentPermissionResponseRoute, getAgentRuntimeRoute, getAgentGitInfoRoute, getAgentActivityRoute, getAgentFilesRoute, getAgentTimelineRoute, postAgentSuspendRoute, postAgentPauseRoute, postAgentUnpauseRoute, postAgentUntroubledRoute, postAgentResumeRoute, postAgentRecoverRoute, postAgentRestartRoute, getAgentCloisterHealthRoute, getAgentHandoffSuggestionRoute, postAgentHandoffRoute, getAgentCostRoute, postAgentsRoute, postAgentsRestartAllRoute, getAgentTmuxAliveRoute, getAgentHasSessionRoute, postAgentResetSessionRoute, postAgentSwitchModelRoute, postAgentRestartFreshRoute, postAgentDeliveryMethodRoute, getAgentsRestartConfigRoute, postAgentsRestartWithConfigRoute);
|
|
4692
6236
|
//#endregion
|
|
4693
6237
|
//#region ../../lib/cloister/task-readiness.ts
|
|
4694
6238
|
init_io();
|
|
@@ -4860,10 +6404,10 @@ const _serverManagedMerges = /* @__PURE__ */ new Set();
|
|
|
4860
6404
|
function firePostMergeLifecycle(issueId, options) {
|
|
4861
6405
|
const started = postMergeGuard.run(issueId, async () => {
|
|
4862
6406
|
const projectPath = getProjectPathForIssue(extractPrefixSync(issueId) ?? issueId.split("-")[0]);
|
|
4863
|
-
const { postMergeLifecycle } = await import("./merge-agent-
|
|
6407
|
+
const { postMergeLifecycle } = await import("./merge-agent-ejC7Wos8.js");
|
|
4864
6408
|
await postMergeLifecycle(issueId, projectPath, options?.sourceBranch, options?.verifiedMergedRef ? { verifiedMergedRef: options.verifiedMergedRef } : void 0);
|
|
4865
6409
|
console.log(`[merge] post-merge lifecycle completed for ${issueId}`);
|
|
4866
|
-
const { runMergeTrainReconcile } = await import("./merge-train-
|
|
6410
|
+
const { runMergeTrainReconcile } = await import("./merge-train-CH33XJOH.js");
|
|
4867
6411
|
const outcomes = await runMergeTrainReconcile(issueId);
|
|
4868
6412
|
if (outcomes.length > 0) console.log(`[merge-train] reconciled ${outcomes.length} sibling(s) after ${issueId}: ` + outcomes.map((o) => `${o.issueId}=${o.result}`).join(", "));
|
|
4869
6413
|
}, (err) => console.error(`[merge] post-merge lifecycle failed for ${issueId}:`, err));
|
|
@@ -4884,15 +6428,15 @@ init_io();
|
|
|
4884
6428
|
init_domain_services();
|
|
4885
6429
|
init_tmux();
|
|
4886
6430
|
const getSpecialistsRoute = HttpRouter.add("GET", "/api/specialists", httpHandler(Effect.gen(function* () {
|
|
4887
|
-
const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-
|
|
6431
|
+
const { getAllSpecialistStatus, getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
4888
6432
|
return jsonResponse({
|
|
4889
6433
|
specialists: yield* Effect.promise(() => getAllSpecialistStatus()),
|
|
4890
6434
|
projects: yield* Effect.promise(() => getAllProjectSpecialistStatuses())
|
|
4891
6435
|
});
|
|
4892
6436
|
})));
|
|
4893
6437
|
const postSpecialistsResetAllRoute = HttpRouter.add("POST", "/api/specialists/reset-all", httpHandler(Effect.gen(function* () {
|
|
4894
|
-
const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-
|
|
4895
|
-
const { clearHookSync } = yield* Effect.promise(() => import("./hooks-
|
|
6438
|
+
const { getAllSpecialists, isRunning, getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
6439
|
+
const { clearHookSync } = yield* Effect.promise(() => import("./hooks-uEr8WTx_.js"));
|
|
4896
6440
|
const specialists = getAllSpecialists();
|
|
4897
6441
|
const results = [];
|
|
4898
6442
|
for (const specialist of specialists) {
|
|
@@ -4942,7 +6486,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
4942
6486
|
const normalizedIssueId = issueId.toUpperCase();
|
|
4943
6487
|
console.log(`[specialists/done] ${specialist} signaling ${status} for ${normalizedIssueId}`);
|
|
4944
6488
|
if (specialist === "merge") {
|
|
4945
|
-
const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-
|
|
6489
|
+
const { reportSpecialistCompletion } = yield* Effect.promise(() => import("./specialist-completion-CJcv-NXH.js"));
|
|
4946
6490
|
if (yield* reportSpecialistCompletion(normalizedIssueId, {
|
|
4947
6491
|
status,
|
|
4948
6492
|
notes
|
|
@@ -4989,7 +6533,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
4989
6533
|
if (specialist === "inspect" && status === "failed") yield* Effect.promise(() => reportTieredInspectFailureEscalation(normalizedIssueId, notes));
|
|
4990
6534
|
yield* Effect.promise(async () => {
|
|
4991
6535
|
try {
|
|
4992
|
-
const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-
|
|
6536
|
+
const { getTmuxSessionName, updateRunMetadata, makeSpecialistRegistryKey } = await import("./specialists-k22_Btaq.js");
|
|
4993
6537
|
const projectKey = resolveProjectFromIssueSync(normalizedIssueId)?.projectKey;
|
|
4994
6538
|
const tmuxSession = projectKey ? getTmuxSessionName(`${specialist}-agent`, projectKey, normalizedIssueId) : getTmuxSessionName(`${specialist}-agent`);
|
|
4995
6539
|
saveAgentRuntimeState(tmuxSession, {
|
|
@@ -5008,7 +6552,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
5008
6552
|
});
|
|
5009
6553
|
console.log(`[specialists/done] Cleared registry lock for ${registryKey} (${projectKey})`);
|
|
5010
6554
|
}
|
|
5011
|
-
const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-
|
|
6555
|
+
const { updateSpecialistHandoffStatus } = await import("./specialist-handoff-logger-CQyF1ygu.js");
|
|
5012
6556
|
if (await Effect.runPromise(updateSpecialistHandoffStatus(normalizedIssueId, `${specialist}-agent`, status === "passed" ? "completed" : "failed", status === "passed" ? "success" : "failure"))) console.log(`[specialists/done] Updated handoff log: ${specialist}-agent ${normalizedIssueId} → ${status}`);
|
|
5013
6557
|
} catch (err) {
|
|
5014
6558
|
console.error(`[specialists/done] Error managing specialist state:`, err);
|
|
@@ -5020,7 +6564,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
5020
6564
|
if (project) {
|
|
5021
6565
|
const workspacePath = join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`);
|
|
5022
6566
|
if (existsSync(workspacePath)) {
|
|
5023
|
-
const { getWorkspaceGitInfo } = await import("./git-utils-
|
|
6567
|
+
const { getWorkspaceGitInfo } = await import("./git-utils-C0neHvQx.js");
|
|
5024
6568
|
const { HEAD } = await Effect.runPromise(getWorkspaceGitInfo(workspacePath));
|
|
5025
6569
|
setReviewStatusSync(normalizedIssueId, { reviewedAtCommit: HEAD });
|
|
5026
6570
|
console.log(`[specialists/done] Snapshotted reviewedAtCommit=${HEAD.substring(0, 8)} for ${normalizedIssueId}`);
|
|
@@ -5032,7 +6576,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
5032
6576
|
});
|
|
5033
6577
|
if (specialist === "inspect" && status === "passed") yield* Effect.promise(async () => {
|
|
5034
6578
|
try {
|
|
5035
|
-
const { onInspectComplete } = await import("./inspect-agent-
|
|
6579
|
+
const { onInspectComplete } = await import("./inspect-agent-CWkeAB7J.js");
|
|
5036
6580
|
const beadId = (notes?.match(/[Bb]ead\s+(\S+)/))?.[1] || "unknown";
|
|
5037
6581
|
const project = resolveProjectFromIssueSync(normalizedIssueId);
|
|
5038
6582
|
if (project) {
|
|
@@ -5077,7 +6621,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
5077
6621
|
if (project) {
|
|
5078
6622
|
if (existsSync(join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`))) {
|
|
5079
6623
|
setReviewStatusSync(normalizedIssueId, { testStatus: "passed" });
|
|
5080
|
-
const { initEventStore } = await import("./event-store-
|
|
6624
|
+
const { initEventStore } = await import("./event-store-CqDgfbAs.js");
|
|
5081
6625
|
await (await initEventStore()).appendAsync({
|
|
5082
6626
|
type: "test.passed",
|
|
5083
6627
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -5130,8 +6674,8 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
5130
6674
|
if (notes?.toLowerCase().includes("conflict")) yield* Effect.promise(async () => {
|
|
5131
6675
|
try {
|
|
5132
6676
|
const workAgentId = `agent-${normalizedIssueId.toLowerCase()}`;
|
|
5133
|
-
const { sessionExists } = await import("./tmux-
|
|
5134
|
-
const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-
|
|
6677
|
+
const { sessionExists } = await import("./tmux-By8E_Yv_.js");
|
|
6678
|
+
const { messageAgent, spawnAgent, getAgentStateSync } = await import("./agents-CEcaxSkR.js");
|
|
5135
6679
|
if (await Effect.runPromise(sessionExists(workAgentId))) {
|
|
5136
6680
|
await messageAgent(workAgentId, `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}`);
|
|
5137
6681
|
console.log(`[specialists/done] Sent rebase instructions to ${workAgentId}`);
|
|
@@ -5145,7 +6689,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
5145
6689
|
try {
|
|
5146
6690
|
const project = resolveProjectFromIssueSync(normalizedIssueId);
|
|
5147
6691
|
const workspacePath = project ? join(project.projectPath, "workspaces", `feature-${normalizedIssueId.toLowerCase()}`) : void 0;
|
|
5148
|
-
const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-
|
|
6692
|
+
const { deliverReviewVerdictFeedback } = await import("./review-verdict-feedback-D2Q960wX.js");
|
|
5149
6693
|
const result = await Effect.runPromise(deliverReviewVerdictFeedback({
|
|
5150
6694
|
issueId: normalizedIssueId,
|
|
5151
6695
|
verdict: status === "failed" ? "failed" : "blocked",
|
|
@@ -5186,7 +6730,7 @@ const postSpecialistsDoneRoute = HttpRouter.add("POST", "/api/specialists/done",
|
|
|
5186
6730
|
});
|
|
5187
6731
|
})));
|
|
5188
6732
|
const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/specialists/logs/cleanup-all", httpHandler(Effect.gen(function* () {
|
|
5189
|
-
const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-
|
|
6733
|
+
const { cleanupAllLogsSync } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
|
|
5190
6734
|
const results = cleanupAllLogsSync();
|
|
5191
6735
|
return jsonResponse({
|
|
5192
6736
|
success: true,
|
|
@@ -5196,7 +6740,7 @@ const postSpecialistsLogsCleanupAllRoute = HttpRouter.add("POST", "/api/speciali
|
|
|
5196
6740
|
});
|
|
5197
6741
|
})));
|
|
5198
6742
|
const getSpecialistsProjectsRoute = HttpRouter.add("GET", "/api/specialists/projects", httpHandler(Effect.gen(function* () {
|
|
5199
|
-
const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-
|
|
6743
|
+
const { getAllProjectSpecialistStatuses } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5200
6744
|
return jsonResponse(yield* Effect.promise(() => getAllProjectSpecialistStatuses()));
|
|
5201
6745
|
})));
|
|
5202
6746
|
const postSpecialistWakeRoute = HttpRouter.add("POST", "/api/specialists/:name/wake", httpHandler(Effect.gen(function* () {
|
|
@@ -5240,7 +6784,7 @@ const postSpecialistReportStatusRoute = HttpRouter.add("POST", "/api/specialists
|
|
|
5240
6784
|
"blocked",
|
|
5241
6785
|
"failed"
|
|
5242
6786
|
].includes(status)) {
|
|
5243
|
-
const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-
|
|
6787
|
+
const { getTmuxSessionName } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5244
6788
|
saveAgentRuntimeState(getTmuxSessionName(name), {
|
|
5245
6789
|
state: "idle",
|
|
5246
6790
|
lastActivity: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -5361,7 +6905,7 @@ const postProjectSpecialistKillRoute = HttpRouter.add("POST", "/api/specialists/
|
|
|
5361
6905
|
const issueId = params["issueId"];
|
|
5362
6906
|
const type = params["type"];
|
|
5363
6907
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
5364
|
-
const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-
|
|
6908
|
+
const { getTmuxSessionName, makeSpecialistRegistryKey, getRunMetadata } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5365
6909
|
const tmuxSession = getRunMetadata(project, makeSpecialistRegistryKey(type, issueId)).tmuxSession ?? getTmuxSessionName(type, project, issueId);
|
|
5366
6910
|
yield* Effect.promise(() => Effect.runPromise(killSession(tmuxSession)).catch(() => {}));
|
|
5367
6911
|
saveAgentRuntimeState(tmuxSession, {
|
|
@@ -5388,7 +6932,7 @@ const getProjectSpecialistRunsRoute = HttpRouter.add("GET", "/api/specialists/:p
|
|
|
5388
6932
|
if (limitParam) limit = parseInt(limitParam, 10);
|
|
5389
6933
|
if (offsetParam) offset = parseInt(offsetParam, 10);
|
|
5390
6934
|
}
|
|
5391
|
-
const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-
|
|
6935
|
+
const { listRunLogsSync } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
|
|
5392
6936
|
return jsonResponse(listRunLogsSync(project, type, {
|
|
5393
6937
|
limit,
|
|
5394
6938
|
offset
|
|
@@ -5399,7 +6943,7 @@ const getProjectSpecialistRunStreamRoute = HttpRouter.add("GET", "/api/specialis
|
|
|
5399
6943
|
const project = params["project"];
|
|
5400
6944
|
const type = params["type"];
|
|
5401
6945
|
const runId = params["runId"];
|
|
5402
|
-
const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-
|
|
6946
|
+
const { getRunLogPath, isRunLogActive } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
|
|
5403
6947
|
const logPath = getRunLogPath(project, type, runId);
|
|
5404
6948
|
if (!existsSync(logPath)) return jsonResponse({ error: "Run log not found" }, { status: 404 });
|
|
5405
6949
|
const encoder = new TextEncoder();
|
|
@@ -5458,7 +7002,7 @@ const getProjectSpecialistRunRoute = HttpRouter.add("GET", "/api/specialists/:pr
|
|
|
5458
7002
|
const project = params["project"];
|
|
5459
7003
|
const type = params["type"];
|
|
5460
7004
|
const runId = params["runId"];
|
|
5461
|
-
const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-
|
|
7005
|
+
const { getRunLogSync, parseLogMetadata } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
|
|
5462
7006
|
const content = getRunLogSync(project, type, runId);
|
|
5463
7007
|
if (!content) return jsonResponse({ error: "Run log not found" }, { status: 404 });
|
|
5464
7008
|
return jsonResponse({
|
|
@@ -5472,7 +7016,7 @@ const postProjectSpecialistRunTerminateRoute = HttpRouter.add("POST", "/api/spec
|
|
|
5472
7016
|
const project = params["project"];
|
|
5473
7017
|
const type = params["type"];
|
|
5474
7018
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
5475
|
-
const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-
|
|
7019
|
+
const { terminateSpecialist } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5476
7020
|
yield* Effect.promise(() => terminateSpecialist(project, type));
|
|
5477
7021
|
return jsonResponse({
|
|
5478
7022
|
success: true,
|
|
@@ -5484,7 +7028,7 @@ const postProjectSpecialistGracePauseRoute = HttpRouter.add("POST", "/api/specia
|
|
|
5484
7028
|
const project = params["project"];
|
|
5485
7029
|
const type = params["type"];
|
|
5486
7030
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
5487
|
-
const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-
|
|
7031
|
+
const { pauseGracePeriod } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5488
7032
|
if (pauseGracePeriod(project, type)) return jsonResponse({
|
|
5489
7033
|
success: true,
|
|
5490
7034
|
message: "Grace period paused"
|
|
@@ -5496,7 +7040,7 @@ const postProjectSpecialistGraceResumeRoute = HttpRouter.add("POST", "/api/speci
|
|
|
5496
7040
|
const project = params["project"];
|
|
5497
7041
|
const type = params["type"];
|
|
5498
7042
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
5499
|
-
const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-
|
|
7043
|
+
const { resumeGracePeriod } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5500
7044
|
if (resumeGracePeriod(project, type)) return jsonResponse({
|
|
5501
7045
|
success: true,
|
|
5502
7046
|
message: "Grace period resumed"
|
|
@@ -5508,7 +7052,7 @@ const postProjectSpecialistGraceExitRoute = HttpRouter.add("POST", "/api/special
|
|
|
5508
7052
|
const project = params["project"];
|
|
5509
7053
|
const type = params["type"];
|
|
5510
7054
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
5511
|
-
const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-
|
|
7055
|
+
const { exitGracePeriod } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5512
7056
|
exitGracePeriod(project, type);
|
|
5513
7057
|
return jsonResponse({
|
|
5514
7058
|
success: true,
|
|
@@ -5520,7 +7064,7 @@ const getProjectSpecialistGraceRoute = HttpRouter.add("GET", "/api/specialists/:
|
|
|
5520
7064
|
const project = params["project"];
|
|
5521
7065
|
const type = params["type"];
|
|
5522
7066
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
5523
|
-
const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-
|
|
7067
|
+
const { getGracePeriodState } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5524
7068
|
const state = getGracePeriodState(project, type);
|
|
5525
7069
|
if (state) return jsonResponse(state);
|
|
5526
7070
|
else return jsonResponse({ error: "No active grace period" }, { status: 404 });
|
|
@@ -5529,7 +7073,7 @@ const getProjectSpecialistContextRoute = HttpRouter.add("GET", "/api/specialists
|
|
|
5529
7073
|
const params = yield* HttpRouter.params;
|
|
5530
7074
|
const project = params["project"];
|
|
5531
7075
|
const type = params["type"];
|
|
5532
|
-
const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-
|
|
7076
|
+
const { loadContextDigest } = yield* Effect.promise(() => import("./specialist-context-B2HlbNxS.js"));
|
|
5533
7077
|
const digest = loadContextDigest(project, type);
|
|
5534
7078
|
if (digest) return jsonResponse({ digest });
|
|
5535
7079
|
else return jsonResponse({ error: "No context digest found" }, { status: 404 });
|
|
@@ -5538,7 +7082,7 @@ const postProjectSpecialistContextRegenerateRoute = HttpRouter.add("POST", "/api
|
|
|
5538
7082
|
const params = yield* HttpRouter.params;
|
|
5539
7083
|
const project = params["project"];
|
|
5540
7084
|
const type = params["type"];
|
|
5541
|
-
const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-
|
|
7085
|
+
const { regenerateContextDigest } = yield* Effect.promise(() => import("./specialist-context-B2HlbNxS.js"));
|
|
5542
7086
|
const digest = yield* Effect.promise(() => regenerateContextDigest(project, type));
|
|
5543
7087
|
if (digest) return jsonResponse({
|
|
5544
7088
|
digest,
|
|
@@ -5557,7 +7101,7 @@ const postProjectSpecialistCompleteRoute = HttpRouter.add("POST", "/api/speciali
|
|
|
5557
7101
|
"blocked"
|
|
5558
7102
|
].includes(status)) return jsonResponse({ error: "Valid status (passed/failed/blocked) is required" }, { status: 400 });
|
|
5559
7103
|
if (!validateSpecialistAgentName(type)) return jsonResponse({ error: "Invalid specialist type. Must be review-agent, test-agent, or merge-agent" }, { status: 400 });
|
|
5560
|
-
const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-
|
|
7104
|
+
const { signalSpecialistCompletion } = yield* Effect.promise(() => import("./specialists-k22_Btaq.js"));
|
|
5561
7105
|
signalSpecialistCompletion(project, type, {
|
|
5562
7106
|
status,
|
|
5563
7107
|
notes
|
|
@@ -5591,8 +7135,8 @@ const postProjectSpecialistLogsCleanupRoute = HttpRouter.add("POST", "/api/speci
|
|
|
5591
7135
|
const params = yield* HttpRouter.params;
|
|
5592
7136
|
const project = params["project"];
|
|
5593
7137
|
const type = params["type"];
|
|
5594
|
-
const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-
|
|
5595
|
-
const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-
|
|
7138
|
+
const { cleanupOldLogsSync } = yield* Effect.promise(() => import("./specialist-logs-CdcUpfjj.js"));
|
|
7139
|
+
const { getSpecialistRetention } = yield* Effect.promise(() => import("./projects-CrBE6IhN.js"));
|
|
5596
7140
|
const retention = getSpecialistRetention(project);
|
|
5597
7141
|
const deleted = cleanupOldLogsSync(project, type, {
|
|
5598
7142
|
maxDays: retention.max_days,
|
|
@@ -5613,7 +7157,7 @@ const postProjectReviewRestartRoute = HttpRouter.add("POST", "/api/specialists/:
|
|
|
5613
7157
|
const project = params["project"];
|
|
5614
7158
|
const issueId = params["issueId"];
|
|
5615
7159
|
const { model, harness } = yield* readJsonBody;
|
|
5616
|
-
const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-
|
|
7160
|
+
const { killAllReviewerSessions } = yield* Effect.promise(() => import("./review-agent-u5q3ip7H.js"));
|
|
5617
7161
|
const killResult = yield* killAllReviewerSessions(project, issueId);
|
|
5618
7162
|
const projectConfig = resolveProjectFromIssueSync(issueId);
|
|
5619
7163
|
if (!projectConfig) return jsonResponse({ error: `Cannot resolve project for ${issueId}` }, { status: 404 });
|
|
@@ -5627,7 +7171,7 @@ const postProjectReviewRestartRoute = HttpRouter.add("POST", "/api/specialists/:
|
|
|
5627
7171
|
}));
|
|
5628
7172
|
branch = stdout.trim() || "unknown";
|
|
5629
7173
|
} catch {}
|
|
5630
|
-
const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-
|
|
7174
|
+
const { spawnReviewRoleForIssue } = yield* Effect.promise(() => import("./review-agent-u5q3ip7H.js"));
|
|
5631
7175
|
const prUrl = getReviewStatusSync(issueId)?.prUrl;
|
|
5632
7176
|
const result = yield* spawnReviewRoleForIssue({
|
|
5633
7177
|
issueId,
|
|
@@ -5776,6 +7320,6 @@ const specialistsProjectRouteLayer = Layer.mergeAll(getProjectSpecialistStatusRo
|
|
|
5776
7320
|
*/
|
|
5777
7321
|
const specialistsRouteLayer = Layer.mergeAll(specialistsLegacyRouteLayer, specialistsProjectRouteLayer);
|
|
5778
7322
|
//#endregion
|
|
5779
|
-
export {
|
|
7323
|
+
export { readLauncherPinnedSessionId as $, resolveAllowedHarness as A, cleanupUnreferencedConversationAttachments as B, handleConversationCreate as C, handleConversationStop as D, handleConversationResume as E, tmuxSessionExists as F, isManagedConversationAttachmentPath as G, extractConversationAttachmentPaths as H, waitForPiTuiReady as I, evaluateSpawnGuardrails as J, removeConversationAttachment as K, waitForTmuxSession as L, spawnConversationSession as M, startConversationModelBackfill as N, handleConversationSwitchModel as O, stopConversationRuntime as P, getWorkAgentLifecycleStateSync as Q, cleanupConversationAttachments as R, conversationSessionAliveFromState as S, handleConversationRestartAll as T, getConversationAttachmentsRoot as U, ensureConversationAttachmentDir as V, hasConversationAttachment as W, resolveAgentGitInfo as X, invalidateAgentsCache as Y, resolveConversationGitInfo as Z, isPiControlChannelHarness as _, jsonStringResponse as _t, agentsRouteLayer as a, isRespawnPending as at, validateConversationControlAckOrigin as b, codexConversationPendingInput as c, getTrustedOrigins as ct, handleConversationCodexApproval as d, validateOrigin as dt, resolveAgentHarness as et, handleConversationCompact as f, validateOriginHeaders as ft, handleConversationThinkingLevel as g, jsonResponse as gt, handleConversationPlanAction as h, getDefaultCwd as ht, validateSpecialistAutoCompleteMetadata as i, resolvePiSessionPath as it, shouldReportUnresolvedLiveSession as j, isInsideGitWorkTree as k, deliverConversationViaControlChannel as l, isTrustedOriginForHost as lt, handleConversationDeliveryMethod as m, httpHandler as mt, _serverManagedMerges as n, resolveCodexRolloutPath as nt, saveAgentStateAndEmitEvent as o, waitForSessionRespawn as ot, handleConversationControlAck as p, checkCodexAuthStatus as pt, runInBatches as q, firePostMergeLifecycle as r, resolveJsonlPath as rt, saveAgentStateAndEmitEventProgram as s, getHeaderFromMap as st, specialistsRouteLayer as t, resolveClaudeSessionId as tt, handleConversationAbort as u, normalizeOrigin as ut, pickDeliverAs as v, handleConversationDelete as w, conversationNeedsRunningRepair as x, resolveConversationDeliveryMethod as y, cleanupOrphanedConversationAttachments as z };
|
|
5780
7324
|
|
|
5781
|
-
//# sourceMappingURL=specialists-
|
|
7325
|
+
//# sourceMappingURL=specialists-CFgw_0Q6.js.map
|