termdock 1.4.322 → 1.4.325
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/App-D8MWr9xp.js +186 -0
- package/dist/client/assets/App-D8MWr9xp.js.br +0 -0
- package/dist/client/assets/App-D8MWr9xp.js.gz +0 -0
- package/dist/client/assets/{ArchitecturePanel-VDM6hrwp.js → ArchitecturePanel-CJXvqmtu.js} +2 -2
- package/dist/client/assets/ArchitecturePanel-CJXvqmtu.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CJXvqmtu.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-B64j3i8o.js → ChangeWalkthroughPanel-ChPZVfRz.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-ChPZVfRz.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-ChPZVfRz.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DkNhqCj4.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-DkNhqCj4.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DkNhqCj4.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-BA3bVNJo.js +2 -0
- package/dist/client/assets/{ComputerControlView-Co-TTYas.js → ComputerControlView-BTntDI6C.js} +2 -2
- package/dist/client/assets/ComputerControlView-BTntDI6C.js.br +0 -0
- package/dist/client/assets/ComputerControlView-BTntDI6C.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-kJczdJ6J.js → DagPlayground-BIL5vjer.js} +1 -1
- package/dist/client/assets/DagPlayground-BIL5vjer.js.br +0 -0
- package/dist/client/assets/DagPlayground-BIL5vjer.js.gz +0 -0
- package/dist/client/assets/{DiffLab-BaaioC-q.js → DiffLab-CX1AwU2v.js} +1 -1
- package/dist/client/assets/DiffLab-CX1AwU2v.js.br +0 -0
- package/dist/client/assets/DiffLab-CX1AwU2v.js.gz +0 -0
- package/dist/client/assets/{DiffReview-Wfck3RIS.js → DiffReview-CLc1hwO7.js} +1 -1
- package/dist/client/assets/DiffReview-CLc1hwO7.js.br +0 -0
- package/dist/client/assets/DiffReview-CLc1hwO7.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-Dblr3wgw.js → DiffReviewLab-DE6m1o4p.js} +1 -1
- package/dist/client/assets/DiffReviewLab-DE6m1o4p.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DE6m1o4p.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-C-utC5Ga.js → DiffViewer-BDwtwbQM.js} +1 -1
- package/dist/client/assets/DiffViewer-BDwtwbQM.js.br +0 -0
- package/dist/client/assets/DiffViewer-BDwtwbQM.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-BBLnHWVQ.js → ModelPreview-DEwATnxi.js} +1 -1
- package/dist/client/assets/ModelPreview-DEwATnxi.js.br +0 -0
- package/dist/client/assets/ModelPreview-DEwATnxi.js.gz +0 -0
- package/dist/client/assets/{RightSidebar-CUHSFkmZ.js → RightSidebar-jOQJePDV.js} +36 -36
- package/dist/client/assets/RightSidebar-jOQJePDV.js.br +0 -0
- package/dist/client/assets/RightSidebar-jOQJePDV.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-DDd99QpA.js → abnfDiagram-VCTEODGH-GBSf8AEr.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-GBSf8AEr.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-GBSf8AEr.js.gz +0 -0
- package/dist/client/assets/app-VCmiASzh.js +35 -0
- package/dist/client/assets/app-VCmiASzh.js.br +0 -0
- package/dist/client/assets/app-VCmiASzh.js.gz +0 -0
- package/dist/client/assets/{arc-C3dck_E0.js → arc-BecvFgjf.js} +1 -1
- package/dist/client/assets/arc-BecvFgjf.js.br +0 -0
- package/dist/client/assets/arc-BecvFgjf.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-C3bCQc2w.js → architectureDiagram-5GKGNRK7-Cfk47zEF.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Cfk47zEF.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Cfk47zEF.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-znMn6iCQ.js → blockDiagram-I7D4REHJ-BHYgsLvx.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-BHYgsLvx.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BHYgsLvx.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-C0sopIsj.js → c4Diagram-7LVT6UL2-ClWWjTyz.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-ClWWjTyz.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-ClWWjTyz.js.gz +0 -0
- package/dist/client/assets/channel-8F8qxLSK.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-f7USyccJ.js → chunk-2Q5K7J3B-Cat1Dsqx.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Ck2xirDs.js → chunk-5VM5RSS4-CT0S2-E8.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DaDCIj30.js → chunk-F27PBJKO-7o5_yA1b.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-7o5_yA1b.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-7o5_yA1b.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-DEC_On5x.js → chunk-IMKFNOWR-iAZxGMup.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-iAZxGMup.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-iAZxGMup.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BrswC3n2.js → chunk-JWPE2WC7-DLvwOWZm.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-ClMbDV47.js → chunk-POPQ4Y6H-BpiRuG56.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-D7_1VwC5.js → chunk-SVP7TREG-DpB7xpqI.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-DpB7xpqI.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DpB7xpqI.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-D5jzVTZ8.js → chunk-TICWLB2K-Z_Sti6yX.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-Z_Sti6yX.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Z_Sti6yX.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-DOhaoLcX.js → chunk-XXDRQBXY-DKngx1rf.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-C0AmKz7U.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-C0AmKz7U.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DOsaKgiX.js → cose-bilkent-JH36ORCC-CDo8eUXo.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CDo8eUXo.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CDo8eUXo.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-CMcLh_wh.js → cynefin-OW5HDTMX-R6vltrl4.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-R6vltrl4.js.br +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-CMcLh_wh.js.gz → cynefin-OW5HDTMX-R6vltrl4.js.gz} +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Dd8bfq9V.js → cynefinDiagram-5FMLGOSQ-BTGMG-zO.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BTGMG-zO.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BTGMG-zO.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-BlW7fAi0.js → dagre-GXQ25YYZ-D67dNSHU.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-D67dNSHU.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D67dNSHU.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-B0-tKahG.js → diagram-S7CK7UJ4-CxZmcHs4.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-CxZmcHs4.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CxZmcHs4.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BQumj4Dw.js → diagram-UQ7AKVKN-BErivsDz.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-BErivsDz.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BErivsDz.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-Zx_FLmPP.js → diagram-VSXAHHWV-BQWEvK9d.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-BQWEvK9d.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BQWEvK9d.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-CEZvatNH.js → diagram-VX7I27RA-COR5k4JL.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-COR5k4JL.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-COR5k4JL.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-CJ9IEQ8j.js → diagram-Z3DM3KII-Cx6oTlVi.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-Cx6oTlVi.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Cx6oTlVi.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-sQQMv3ea.js → ebnfDiagram-PWID7BFC-dKNZki9k.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-dKNZki9k.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-dKNZki9k.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-D3rMpj7M.js → erDiagram-RLTQ6QDP-DD06d9Gl.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-DD06d9Gl.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DD06d9Gl.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-B6QZiE0-.js → flowDiagram-HODETNUW-DXNAvw1d.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-DXNAvw1d.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DXNAvw1d.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-Bw9bltcu.js → ganttDiagram-EL5Y4UJY-BSMu3Rsd.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BSMu3Rsd.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BSMu3Rsd.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-Cczz34oI.js → gestureArbiter-D1TteC76.js} +1 -1
- package/dist/client/assets/gestureArbiter-D1TteC76.js.br +0 -0
- package/dist/client/assets/gestureArbiter-D1TteC76.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-mdnkxjeD.js → gitGraphDiagram-WWUBYQGX-DNDKI803.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DNDKI803.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DNDKI803.js.gz +0 -0
- package/dist/client/assets/index-Ctz-IDjR.css +1 -0
- package/dist/client/assets/index-Ctz-IDjR.css.br +0 -0
- package/dist/client/assets/{index-B4bEMUyn.css.gz → index-Ctz-IDjR.css.gz} +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-DyrPP3Yf.js → infoDiagram-27XIBGKW-BRmB1D5B.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CEPH_ePM.js → ishikawaDiagram-5VMMS53U-DXU4nnZ6.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DXU4nnZ6.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DXU4nnZ6.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-CQ0PjBy7.js → journeyDiagram-3NMN7TZE-DFx7AfiP.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DFx7AfiP.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DFx7AfiP.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-BpQUXA60.js → kanban-definition-UXKFOSKX-B1ikfXJe.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-B1ikfXJe.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-B1ikfXJe.js.gz +0 -0
- package/dist/client/assets/{layout-BdptC7rq.js → layout-DkyvZr1Y.js} +1 -1
- package/dist/client/assets/layout-DkyvZr1Y.js.br +0 -0
- package/dist/client/assets/layout-DkyvZr1Y.js.gz +0 -0
- package/dist/client/assets/{linear-C2cSRCxu.js → linear-CECAbRuX.js} +1 -1
- package/dist/client/assets/linear-CECAbRuX.js.br +0 -0
- package/dist/client/assets/linear-CECAbRuX.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-ClVMFyAU.js → mermaid.core-CYY7Wb75.js} +5 -5
- package/dist/client/assets/mermaid.core-CYY7Wb75.js.br +0 -0
- package/dist/client/assets/mermaid.core-CYY7Wb75.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-D0AFCmsY.js → mindmap-definition-YA3MSWOX-CTs-Y3Lq.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CTs-Y3Lq.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CTs-Y3Lq.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-R2qlU2Pq.js → passwordBootstrap-CPNGDwv6.js} +1 -1
- package/dist/client/assets/passwordBootstrap-CPNGDwv6.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-CPNGDwv6.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BGAh0bwg.js → pegDiagram-XKGWAZYB-Y1oZrwtK.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-Y1oZrwtK.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-Y1oZrwtK.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-CDOZUF44.js → pieDiagram-E7YTZNPT-DYSm5OxQ.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-DYSm5OxQ.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DYSm5OxQ.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-D_Qn6NTU.js → quadrantDiagram-AXDQQJYC-DpibIBz0.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DpibIBz0.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DpibIBz0.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-qUVGWhJN.js → railroadDiagram-O6MQD6OU-b7jp7C1K.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-b7jp7C1K.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-b7jp7C1K.js.gz +0 -0
- package/dist/client/assets/{rdpSession-hgCM4i-j.js → rdpSession-DU_OJfX5.js} +1 -1
- package/dist/client/assets/rdpSession-DU_OJfX5.js.br +0 -0
- package/dist/client/assets/rdpSession-DU_OJfX5.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-BEMsP11a.js → referenceLongPress-BPQ_i68g.js} +1 -1
- package/dist/client/assets/referenceLongPress-BPQ_i68g.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BPQ_i68g.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-BBQMQuVZ.js → requirementDiagram-BXWQKSXE-BMinO1mf.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BMinO1mf.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BMinO1mf.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CeEExsqW.js → sankeyDiagram-P5KCCOFB-CmC977VA.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CmC977VA.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CmC977VA.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-7ZPO-X0a.js → sequenceDiagram-WJ2MYXX4-D6eGbgK8.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D6eGbgK8.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D6eGbgK8.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-MBjz7Snm.js → sizeCapture-INFHLROL-CnRvyBd8.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-CnRvyBd8.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CnRvyBd8.js.gz +0 -0
- package/dist/client/assets/{sparkles-C9ShmGPf.js → sparkles-DzadqrlX.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-DtLp89uP.js → stateDiagram-D77RDMKH-DRSalH10.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-DRSalH10.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DRSalH10.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DKe8v_ic.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-Bc02S1q-.js → swimlanes-42K2YHIH-u-NnDSzq.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-u-NnDSzq.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-u-NnDSzq.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-ei5b8ZYx.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-BLLwbOm5.js → timeline-definition-24CTP7MA-DkLijg_J.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-DkLijg_J.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DkLijg_J.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-i_L8ws6U.js → triangle-alert-D_6xTTiX.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-8FcrAdbk.js → vennDiagram-4TSXK5OY-AWGJtiv3.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-8FcrAdbk.js.br → vennDiagram-4TSXK5OY-AWGJtiv3.js.br} +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-AWGJtiv3.js.gz +0 -0
- package/dist/client/assets/{videoCompression-RIaDabW4.js → videoCompression-D3y7Bq8J.js} +1 -1
- package/dist/client/assets/videoCompression-D3y7Bq8J.js.br +0 -0
- package/dist/client/assets/videoCompression-D3y7Bq8J.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-paL2wRrJ.js → wardleyDiagram-VM6X3IG4-DI0aZr_y.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DI0aZr_y.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DI0aZr_y.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BM5qJ4E7.js → xychartDiagram-S5SC5T6Z-CCoIN3uU.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CCoIN3uU.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CCoIN3uU.js.gz +0 -0
- package/dist/client/index.html +2 -2
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/sw-encrypted-background.js +1 -1
- package/dist/client/sw-encrypted-background.js.br +0 -0
- package/dist/client/sw-encrypted-background.js.gz +0 -0
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/agent/collaborationGroupRoutes.js +15 -0
- package/dist/server/agent/collaborationStore.js +63 -2
- package/dist/server/agent/collaborationTaskRoutes.js +5 -1
- package/dist/server/agent/integrationCli.js +38 -20
- package/dist/server/agent/integrationServer.js +145 -3
- package/dist/server/federation/packets.js +179 -28
- package/dist/server/federation/requestContext.js +6 -1
- package/dist/server/federation/runtime.js +19 -8
- package/dist/server/routes/terminal.js +285 -85
- package/dist/server/utils/autoUpdate.js +55 -8
- package/dist/server/utils/hostResources.js +128 -0
- package/dist/server/utils/npmUpdate.js +84 -8
- package/dist/server/utils/terminalPasteBuffer.js +66 -0
- package/dist/server/utils/tmuxClientCapabilities.js +4 -0
- package/dist/server/utils/tmuxCopySelection.js +94 -0
- package/dist/server/utils/tmuxEmptyServerProbe.js +15 -0
- package/dist/server/utils/tmuxRecoveryOperations.js +348 -0
- package/docs/integrations.md +36 -0
- package/package.json +5 -2
- package/runtime-manifest.json +3 -3
- package/.desktop-dist/main.js +0 -1989
- package/dist/client/assets/App-DEKo4SNL.js +0 -186
- package/dist/client/assets/App-DEKo4SNL.js.br +0 -0
- package/dist/client/assets/App-DEKo4SNL.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-VDM6hrwp.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-VDM6hrwp.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-B64j3i8o.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-B64j3i8o.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DG8TQNqo.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-DG8TQNqo.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DG8TQNqo.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-msQsgVaI.js +0 -2
- package/dist/client/assets/ComputerControlView-Co-TTYas.js.br +0 -0
- package/dist/client/assets/ComputerControlView-Co-TTYas.js.gz +0 -0
- package/dist/client/assets/DagPlayground-kJczdJ6J.js.br +0 -0
- package/dist/client/assets/DagPlayground-kJczdJ6J.js.gz +0 -0
- package/dist/client/assets/DiffLab-BaaioC-q.js.br +0 -0
- package/dist/client/assets/DiffLab-BaaioC-q.js.gz +0 -0
- package/dist/client/assets/DiffReview-Wfck3RIS.js.br +0 -0
- package/dist/client/assets/DiffReview-Wfck3RIS.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-Dblr3wgw.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-Dblr3wgw.js.gz +0 -0
- package/dist/client/assets/DiffViewer-C-utC5Ga.js.br +0 -0
- package/dist/client/assets/DiffViewer-C-utC5Ga.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BBLnHWVQ.js.br +0 -0
- package/dist/client/assets/ModelPreview-BBLnHWVQ.js.gz +0 -0
- package/dist/client/assets/RightSidebar-CUHSFkmZ.js.br +0 -0
- package/dist/client/assets/RightSidebar-CUHSFkmZ.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DDd99QpA.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DDd99QpA.js.gz +0 -0
- package/dist/client/assets/app-CfqF7_nh.js +0 -35
- package/dist/client/assets/app-CfqF7_nh.js.br +0 -0
- package/dist/client/assets/app-CfqF7_nh.js.gz +0 -0
- package/dist/client/assets/arc-C3dck_E0.js.br +0 -0
- package/dist/client/assets/arc-C3dck_E0.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C3bCQc2w.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C3bCQc2w.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-znMn6iCQ.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-znMn6iCQ.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-C0sopIsj.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-C0sopIsj.js.gz +0 -0
- package/dist/client/assets/channel-CMC8t83j.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DaDCIj30.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DaDCIj30.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DEC_On5x.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DEC_On5x.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D7_1VwC5.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D7_1VwC5.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-D5jzVTZ8.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-D5jzVTZ8.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-ElMHzA-E.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-ElMHzA-E.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DOsaKgiX.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DOsaKgiX.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CMcLh_wh.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Dd8bfq9V.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Dd8bfq9V.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BlW7fAi0.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BlW7fAi0.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-B0-tKahG.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-B0-tKahG.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BQumj4Dw.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BQumj4Dw.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Zx_FLmPP.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Zx_FLmPP.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CEZvatNH.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CEZvatNH.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CJ9IEQ8j.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CJ9IEQ8j.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-sQQMv3ea.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-sQQMv3ea.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-D3rMpj7M.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-D3rMpj7M.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B6QZiE0-.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B6QZiE0-.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bw9bltcu.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bw9bltcu.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-Cczz34oI.js.br +0 -0
- package/dist/client/assets/gestureArbiter-Cczz34oI.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-mdnkxjeD.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-mdnkxjeD.js.gz +0 -0
- package/dist/client/assets/index-B4bEMUyn.css +0 -1
- package/dist/client/assets/index-B4bEMUyn.css.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CEPH_ePM.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CEPH_ePM.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CQ0PjBy7.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CQ0PjBy7.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BpQUXA60.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BpQUXA60.js.gz +0 -0
- package/dist/client/assets/layout-BdptC7rq.js.br +0 -0
- package/dist/client/assets/layout-BdptC7rq.js.gz +0 -0
- package/dist/client/assets/linear-C2cSRCxu.js.br +0 -0
- package/dist/client/assets/linear-C2cSRCxu.js.gz +0 -0
- package/dist/client/assets/mermaid.core-ClVMFyAU.js.br +0 -0
- package/dist/client/assets/mermaid.core-ClVMFyAU.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0AFCmsY.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0AFCmsY.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-R2qlU2Pq.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-R2qlU2Pq.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BGAh0bwg.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BGAh0bwg.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CDOZUF44.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CDOZUF44.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-D_Qn6NTU.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-D_Qn6NTU.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-qUVGWhJN.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-qUVGWhJN.js.gz +0 -0
- package/dist/client/assets/rdpSession-hgCM4i-j.js.br +0 -0
- package/dist/client/assets/rdpSession-hgCM4i-j.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-BEMsP11a.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BEMsP11a.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BBQMQuVZ.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BBQMQuVZ.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CeEExsqW.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CeEExsqW.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-7ZPO-X0a.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-7ZPO-X0a.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-MBjz7Snm.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-MBjz7Snm.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DtLp89uP.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DtLp89uP.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CIQb4Dp7.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Bc02S1q-.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Bc02S1q-.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-RiKL7eXI.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-BLLwbOm5.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BLLwbOm5.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-8FcrAdbk.js.gz +0 -0
- package/dist/client/assets/videoCompression-RIaDabW4.js.br +0 -0
- package/dist/client/assets/videoCompression-RIaDabW4.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-paL2wRrJ.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-paL2wRrJ.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BM5qJ4E7.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BM5qJ4E7.js.gz +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { copyTmuxSelection } from '../utils/tmuxCopySelection.js';
|
|
2
|
+
import { isConfirmedEmptyTmuxServer } from '../utils/tmuxEmptyServerProbe.js';
|
|
1
3
|
import { collaborationSessionDisplayName } from '../utils/sessionDisplayName.js';
|
|
2
4
|
import { integrationError } from '../agent/integrationStore.js';
|
|
3
5
|
import { assertNativeResumeAvailable, collectNativeResumeOwnerCandidates } from '../agent/nativeResumeOwner.js';
|
|
@@ -14,6 +16,7 @@ import { ensureNodePty } from '../utils/ensureNodePty.js';
|
|
|
14
16
|
import { PtySpawnBackoff, PtySpawnDeferredError } from '../utils/ptySpawnBackoff.js';
|
|
15
17
|
import { collaborationGroupRoutes } from '../agent/collaborationGroupRoutes.js';
|
|
16
18
|
import { TerminalClientAttachment } from '../utils/terminalClientAttachment.js';
|
|
19
|
+
import { TerminalPasteBuffer } from '../utils/terminalPasteBuffer.js';
|
|
17
20
|
import { SharedSessionSampler, SharedSnapshotCache } from '../utils/sharedSampling.js';
|
|
18
21
|
import { batchTmuxOptions, TmuxMetadataWriter } from '../utils/tmuxMetadataWriter.js';
|
|
19
22
|
import { redrawTmuxClient } from '../utils/tmuxClientRedraw.js';
|
|
@@ -49,6 +52,8 @@ import { AgentResumeHistoryStore } from '../agent/resumeHistory.js';
|
|
|
49
52
|
import { completedExecution, ExecutionArchiveStore } from '../agent/executionArchives.js';
|
|
50
53
|
import { archiveTaskWorkspace, restoreTaskWorkspace, snapshotTaskWorkspace } from '../agent/collaborationTaskWorkspace.js';
|
|
51
54
|
import { isTmuxRecoveryCandidate } from '../utils/tmuxRecoveryCandidate.js';
|
|
55
|
+
import { configuredTmuxRecoveryConcurrency, TmuxRecoveryOperationStore } from '../utils/tmuxRecoveryOperations.js';
|
|
56
|
+
import { assessAgentResourceBudget, collectHostResourceSnapshot, configuredAgentResourceBudget } from '../utils/hostResources.js';
|
|
52
57
|
import { AutomationStore, normalizeAutomationSchedule } from '../agent/automationStore.js';
|
|
53
58
|
import { writeBracketedPrompt, canDeliverPromptToAgent } from '../agent/promptDelivery.js';
|
|
54
59
|
import { captureTerminalInputGuard, captureRuntimeInputGuard, waitAndWriteTerminalInput } from '../agent/terminalInput.js';
|
|
@@ -201,7 +206,11 @@ const agentResumeHistory = new AgentResumeHistoryStore(`${TERMDOCK_DIR}/agent-re
|
|
|
201
206
|
const executionArchives = new ExecutionArchiveStore(`${TERMDOCK_DIR}/execution-archives.json`);
|
|
202
207
|
let executionArchiveMutation = Promise.resolve();
|
|
203
208
|
const TMUX_RECOVERY_STATE_FILE = `${TERMDOCK_DIR}/tmux-recovery-state.json`;
|
|
209
|
+
const TMUX_RECOVERY_OPERATIONS_FILE = `${TERMDOCK_DIR}/tmux-recovery-operations.json`;
|
|
204
210
|
const tmuxLifecycle = new TmuxLifecycleCoordinator();
|
|
211
|
+
const tmuxRecoveryConcurrencyLimit = configuredTmuxRecoveryConcurrency();
|
|
212
|
+
const tmuxRecoveryOperations = new TmuxRecoveryOperationStore(TMUX_RECOVERY_OPERATIONS_FILE, tmuxRecoveryConcurrencyLimit, (error) => console.error('[tmux-recovery] operation worker error:', getErrorMessage(error)));
|
|
213
|
+
let tmuxRecoveryHealthTimer = null;
|
|
205
214
|
const intentionallyDeletingTmuxSessions = new Set();
|
|
206
215
|
let tmuxRecoveryState = { lastServerPid: null, incident: null, dismissedSessionIds: [] };
|
|
207
216
|
let persistGlobalStateTimer = null;
|
|
@@ -212,6 +221,10 @@ let globalSessionStateReloadTimer = null;
|
|
|
212
221
|
// receives deltas on every mutation (PUT/DELETE client-state, dead-session
|
|
213
222
|
// reconciliation, etc.). Replaces the 5-second poll on the front-end. ──
|
|
214
223
|
const controlClients = new Map();
|
|
224
|
+
tmuxRecoveryOperations.subscribe(() => {
|
|
225
|
+
latestSessionInventoryAt = 0;
|
|
226
|
+
broadcastClientState();
|
|
227
|
+
});
|
|
215
228
|
let latestSessionInventory = null;
|
|
216
229
|
// Only authoritative observations may remove persisted tmux sessions.
|
|
217
230
|
const inventoryTmuxLiveness = new WeakMap();
|
|
@@ -540,8 +553,10 @@ async function loadGlobalSessionStateFromDisk() {
|
|
|
540
553
|
async function loadTmuxRecoveryStateFromDisk() {
|
|
541
554
|
try {
|
|
542
555
|
const data = await readJsonFileIfExists(TMUX_RECOVERY_STATE_FILE);
|
|
543
|
-
if (!data)
|
|
556
|
+
if (!data) {
|
|
557
|
+
await tmuxRecoveryOperations.load();
|
|
544
558
|
return;
|
|
559
|
+
}
|
|
545
560
|
tmuxRecoveryState = {
|
|
546
561
|
lastServerPid: typeof data.lastServerPid === 'number' && Number.isInteger(data.lastServerPid)
|
|
547
562
|
? data.lastServerPid
|
|
@@ -551,6 +566,7 @@ async function loadTmuxRecoveryStateFromDisk() {
|
|
|
551
566
|
? data.dismissedSessionIds.filter((id) => typeof id === 'string')
|
|
552
567
|
: [],
|
|
553
568
|
};
|
|
569
|
+
await tmuxRecoveryOperations.load();
|
|
554
570
|
}
|
|
555
571
|
catch (error) {
|
|
556
572
|
console.warn('[tmux-recovery] Failed to load recovery state:', getErrorMessage(error));
|
|
@@ -663,6 +679,8 @@ const shutdownWithRecordings = async () => {
|
|
|
663
679
|
if (shuttingDown)
|
|
664
680
|
return;
|
|
665
681
|
shuttingDown = true;
|
|
682
|
+
if (tmuxRecoveryHealthTimer)
|
|
683
|
+
clearInterval(tmuxRecoveryHealthTimer);
|
|
666
684
|
flushPersistAndExit();
|
|
667
685
|
void persistToolbarPresetsNow();
|
|
668
686
|
caffeinateManager.shutdown();
|
|
@@ -679,6 +697,9 @@ process.on('SIGINT', () => { void shutdownWithRecordings(); });
|
|
|
679
697
|
// 服务启动时从磁盘加载(带去重,防止历史累积的重复条目复活)
|
|
680
698
|
void (async () => {
|
|
681
699
|
await Promise.all([loadGlobalSessionStateFromDisk(), loadTmuxRecoveryStateFromDisk()]);
|
|
700
|
+
await sampleTmuxRecoveryHealth();
|
|
701
|
+
tmuxRecoveryHealthTimer = setInterval(() => void sampleTmuxRecoveryHealth(), 15_000);
|
|
702
|
+
tmuxRecoveryHealthTimer.unref?.();
|
|
682
703
|
// Delivery needs the loaded inventory, not optional PTY adoption/metadata backfill.
|
|
683
704
|
// Those steps must not leave durable messages without a background scheduler.
|
|
684
705
|
collaborationDeliveryWorker.start();
|
|
@@ -691,6 +712,7 @@ void (async () => {
|
|
|
691
712
|
});
|
|
692
713
|
pruneOrphanSessions();
|
|
693
714
|
await backfillPersistedTmuxMetadata();
|
|
715
|
+
await tmuxRecoveryOperations.resumePending();
|
|
694
716
|
})();
|
|
695
717
|
caffeinateManager.startNetworkMonitor();
|
|
696
718
|
// 清理磁盘恢复后后端已不存在的 session 引用。
|
|
@@ -2522,10 +2544,10 @@ function getTmuxBinary() {
|
|
|
2522
2544
|
}
|
|
2523
2545
|
return 'tmux'; // fallback to PATH
|
|
2524
2546
|
}
|
|
2525
|
-
async function runTmux(args) {
|
|
2547
|
+
async function runTmux(args, options) {
|
|
2526
2548
|
const { stdout } = await execFileAsync(getTmuxBinary(), args, {
|
|
2527
2549
|
timeout: 5000,
|
|
2528
|
-
maxBuffer: 2 * 1024 * 1024,
|
|
2550
|
+
maxBuffer: options?.maxBuffer ?? 2 * 1024 * 1024,
|
|
2529
2551
|
env: buildInteractiveColorEnvironment(process.env),
|
|
2530
2552
|
});
|
|
2531
2553
|
return stdout;
|
|
@@ -3006,8 +3028,21 @@ async function getTmuxServerPid() {
|
|
|
3006
3028
|
}
|
|
3007
3029
|
function getTmuxRecoveryView() {
|
|
3008
3030
|
const incident = tmuxRecoveryState.incident;
|
|
3009
|
-
|
|
3010
|
-
|
|
3031
|
+
const latestOperation = tmuxRecoveryOperations.latest();
|
|
3032
|
+
if (!incident) {
|
|
3033
|
+
if (!latestOperation || ['succeeded', 'partial_failure'].includes(latestOperation.state))
|
|
3034
|
+
return null;
|
|
3035
|
+
const sessions = latestOperation.items.flatMap(item => {
|
|
3036
|
+
const record = globalSessionState.sessions.find(candidate => candidate.sessionId === item.sessionId);
|
|
3037
|
+
const agent = record?.agentResume ? agentBySlug(record.agentResume.slug) : null;
|
|
3038
|
+
if (!record?.tmuxSessionName || !record.cwd || !agent || !record.agentResume?.sessionId)
|
|
3039
|
+
return [];
|
|
3040
|
+
return [{ sessionId: record.sessionId, name: record.name, tmuxSessionName: record.tmuxSessionName,
|
|
3041
|
+
cwd: record.cwd, agentSlug: agent.slug, agentDisplayName: agent.displayName }];
|
|
3042
|
+
});
|
|
3043
|
+
return { id: latestOperation.incidentId, detectedAt: latestOperation.createdAt, operation: latestOperation, sessions };
|
|
3044
|
+
}
|
|
3045
|
+
const operation = latestOperation?.incidentId === incident.id ? latestOperation : null;
|
|
3011
3046
|
const affected = new Set(incident.affectedSessionIds);
|
|
3012
3047
|
const sessions = globalSessionState.sessions.flatMap((record) => {
|
|
3013
3048
|
if (!affected.has(record.sessionId) || record.mode !== 'tmux' || !record.tmuxSessionName
|
|
@@ -3026,7 +3061,7 @@ function getTmuxRecoveryView() {
|
|
|
3026
3061
|
}];
|
|
3027
3062
|
});
|
|
3028
3063
|
return sessions.length > 0
|
|
3029
|
-
? { id: incident.id, detectedAt: incident.detectedAt, sessions }
|
|
3064
|
+
? { id: incident.id, detectedAt: incident.detectedAt, operation, sessions }
|
|
3030
3065
|
: null;
|
|
3031
3066
|
}
|
|
3032
3067
|
async function observeTmuxServerGeneration(liveTmuxSessions) {
|
|
@@ -4684,19 +4719,26 @@ async function resolveTmuxClientTty(sessionName, preferredClientPid) {
|
|
|
4684
4719
|
}
|
|
4685
4720
|
async function ensureTmuxSessionExists(sessionName, cwd) {
|
|
4686
4721
|
let serverWasMissing = false;
|
|
4722
|
+
let serverIsEmpty = false;
|
|
4723
|
+
let sessionExists = false;
|
|
4687
4724
|
try {
|
|
4688
4725
|
await runTmux(['has-session', '-t', sessionName]);
|
|
4689
|
-
|
|
4690
|
-
return;
|
|
4726
|
+
sessionExists = true;
|
|
4691
4727
|
}
|
|
4692
4728
|
catch (error) {
|
|
4693
4729
|
const errorMessage = getErrorMessage(error);
|
|
4694
4730
|
serverWasMissing = isTmuxServerMissingMessage(errorMessage);
|
|
4695
4731
|
if (!isTmuxSessionMissingMessage(errorMessage)) {
|
|
4696
|
-
|
|
4732
|
+
serverIsEmpty = await isConfirmedEmptyTmuxServer(error, sessionName, runTmux);
|
|
4733
|
+
if (!serverIsEmpty)
|
|
4734
|
+
throw error;
|
|
4697
4735
|
}
|
|
4698
4736
|
}
|
|
4699
|
-
if (
|
|
4737
|
+
if (sessionExists) {
|
|
4738
|
+
await ensureTmuxColorEnvironment(sessionName);
|
|
4739
|
+
return;
|
|
4740
|
+
}
|
|
4741
|
+
if (!serverWasMissing && !serverIsEmpty) {
|
|
4700
4742
|
await ensureTmuxColorEnvironment();
|
|
4701
4743
|
}
|
|
4702
4744
|
const innerTerm = await resolveTmuxInnerTerm();
|
|
@@ -5928,7 +5970,7 @@ async function resolveShellIntegrationDir() {
|
|
|
5928
5970
|
/**
|
|
5929
5971
|
* Inject termdock shell integration via environment variables.
|
|
5930
5972
|
*
|
|
5931
|
-
* This replaces the old injectShellTitleHooks with
|
|
5973
|
+
* This replaces the old injectShellTitleHooks with full shell
|
|
5932
5974
|
* integration that emits OSC 133 (prompt marks), OSC 2 (title), and OSC 7 (cwd).
|
|
5933
5975
|
*
|
|
5934
5976
|
* Works for both shell mode (env passed to PTY spawn) and tmux mode
|
|
@@ -5948,7 +5990,7 @@ async function injectShellIntegration(shellPath, baseEnv) {
|
|
|
5948
5990
|
}
|
|
5949
5991
|
if (shellType === 'zsh') {
|
|
5950
5992
|
// Create a temporary ZDOTDIR that sources user's zshenv/zshrc then
|
|
5951
|
-
// sources our integration script.
|
|
5993
|
+
// sources our integration script. This preserves the user shell configuration.
|
|
5952
5994
|
const zdotdir = '/tmp/termdock-zsh-' + String(process.pid);
|
|
5953
5995
|
try {
|
|
5954
5996
|
await fs.promises.mkdir(zdotdir, { recursive: true });
|
|
@@ -6112,74 +6154,202 @@ router.get('/tmux/status', async (_req, res) => {
|
|
|
6112
6154
|
const status = await getTmuxStatus();
|
|
6113
6155
|
res.json(status);
|
|
6114
6156
|
});
|
|
6115
|
-
|
|
6116
|
-
const
|
|
6117
|
-
if (!
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
|
|
6122
|
-
|
|
6123
|
-
|
|
6124
|
-
|
|
6125
|
-
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
if (
|
|
6132
|
-
|
|
6133
|
-
|
|
6134
|
-
|
|
6135
|
-
const
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
};
|
|
6140
|
-
const activeBackend = findActiveAgentResumeOwner('', target);
|
|
6141
|
-
const activeWriterPid = activeBackend ? null : await findExternalCodexWriter(target);
|
|
6142
|
-
if (activeBackend || activeWriterPid) {
|
|
6143
|
-
outcomes.push({ sessionId: frontendSessionId, restored: true });
|
|
6144
|
-
continue;
|
|
6157
|
+
async function restoreTmuxRecoverySession(operationId, frontendSessionId, reportPhase) {
|
|
6158
|
+
const record = globalSessionState.sessions.find((session) => session.sessionId === frontendSessionId);
|
|
6159
|
+
if (!record || record.mode !== 'tmux' || !record.tmuxSessionName || !record.cwd || !record.agentResume?.sessionId) {
|
|
6160
|
+
throw Object.assign(new Error('Recovery metadata is incomplete'), { code: 'RECOVERY_METADATA_INCOMPLETE' });
|
|
6161
|
+
}
|
|
6162
|
+
const agent = agentBySlug(record.agentResume.slug);
|
|
6163
|
+
const command = agent ? buildResumeCommand(agent, record.agentResume.sessionId, record.agentResume.launchArgv) : null;
|
|
6164
|
+
if (!agent || !command)
|
|
6165
|
+
throw Object.assign(new Error('Agent does not support exact resume'), { code: 'EXACT_RESUME_UNSUPPORTED' });
|
|
6166
|
+
const target = { slug: agent.slug, nativeSessionId: record.agentResume.sessionId, command };
|
|
6167
|
+
const activeBackend = findActiveAgentResumeOwner('', target);
|
|
6168
|
+
const activeWriterPid = activeBackend ? null : await findExternalCodexWriter(target);
|
|
6169
|
+
if (activeBackend || activeWriterPid)
|
|
6170
|
+
return {};
|
|
6171
|
+
await collaborationDeliveryWorker.reconfigure(frontendSessionId, () => tmuxLifecycle.run(`restore:${record.tmuxSessionName}`, async () => {
|
|
6172
|
+
// Reconcile ownership again inside the per-terminal serialization fence.
|
|
6173
|
+
if (findActiveAgentResumeOwner('', target) || await findExternalCodexWriter(target))
|
|
6174
|
+
return;
|
|
6175
|
+
reportPhase('runtime_resolving');
|
|
6176
|
+
await prepareManagedTmuxSessionUnlocked(record.tmuxSessionName, record.cwd);
|
|
6177
|
+
const metadata = await resolveLiveTmuxMetadata(record.tmuxSessionName);
|
|
6178
|
+
const foreground = metadata?.program?.toLowerCase() ?? null;
|
|
6179
|
+
if (foreground && !shellNamesBackend.has(foreground)) {
|
|
6180
|
+
throw Object.assign(new Error(`pane is busy with ${foreground}`), { code: 'RECOVERY_TARGET_BUSY' });
|
|
6145
6181
|
}
|
|
6182
|
+
const previous = collaborationRouting.get(frontendSessionId);
|
|
6183
|
+
if (previous)
|
|
6184
|
+
collaborationRouting.bind({ ...previous, pane: null });
|
|
6185
|
+
reportPhase('guard_captured');
|
|
6186
|
+
const launchGuard = await beginAgentLaunchCheck(frontendSessionId, agent.slug, target.nativeSessionId);
|
|
6146
6187
|
try {
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
const metadata = await resolveLiveTmuxMetadata(record.tmuxSessionName);
|
|
6150
|
-
const foreground = metadata?.program?.toLowerCase() ?? null;
|
|
6151
|
-
if (foreground && !shellNamesBackend.has(foreground)) {
|
|
6152
|
-
throw new Error(`pane is busy with ${foreground}`);
|
|
6153
|
-
}
|
|
6154
|
-
// Explicit recovery may replace the vanished tmux pane. A pending
|
|
6155
|
-
// message alone must never replace that binding.
|
|
6156
|
-
const previous = collaborationRouting.get(frontendSessionId);
|
|
6157
|
-
if (previous)
|
|
6158
|
-
collaborationRouting.bind({ ...previous, pane: null });
|
|
6159
|
-
const launchGuard = await beginAgentLaunchCheck(frontendSessionId, agent.slug, target.nativeSessionId);
|
|
6160
|
-
await writeAutomaticTerminalPrompt(frontendSessionId, command, undefined, launchGuard);
|
|
6161
|
-
}));
|
|
6162
|
-
outcomes.push({ sessionId: frontendSessionId, restored: true });
|
|
6188
|
+
reportPhase('paste_submit');
|
|
6189
|
+
await writeAutomaticTerminalPrompt(frontendSessionId, command, stage => reportPhase(stage), launchGuard);
|
|
6163
6190
|
}
|
|
6164
6191
|
catch (error) {
|
|
6165
|
-
|
|
6192
|
+
const message = getErrorMessage(error);
|
|
6193
|
+
const reportedCode = error?.code;
|
|
6194
|
+
const code = reportedCode === 'TERMINAL_INPUT_TARGET_CHANGED' || message.includes('TERMINAL_INPUT_TARGET_CHANGED')
|
|
6195
|
+
? 'TERMINAL_INPUT_TARGET_CHANGED' : 'RECOVERY_LAUNCH_FAILED';
|
|
6196
|
+
throw Object.assign(new Error(message), { code, operationId, sessionId: frontendSessionId });
|
|
6166
6197
|
}
|
|
6198
|
+
}));
|
|
6199
|
+
const incident = tmuxRecoveryState.incident;
|
|
6200
|
+
if (incident?.affectedSessionIds.includes(frontendSessionId)) {
|
|
6201
|
+
const remaining = incident.affectedSessionIds.filter(id => id !== frontendSessionId);
|
|
6202
|
+
tmuxRecoveryState.incident = remaining.length ? { ...incident, affectedSessionIds: remaining } : null;
|
|
6203
|
+
await persistTmuxRecoveryState();
|
|
6167
6204
|
}
|
|
6168
|
-
const
|
|
6169
|
-
const
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6205
|
+
const paneTty = await runTmux(['display-message', '-p', '-t', record.tmuxSessionName, '#{pane_tty}']).catch(() => '');
|
|
6206
|
+
const normalizedTty = paneTty.trim().replace(/^\/dev\//, '');
|
|
6207
|
+
if (normalizedTty) {
|
|
6208
|
+
const resources = await collectHostResourceSnapshot();
|
|
6209
|
+
const rows = resources.processRows.filter(row => row.tty === normalizedTty);
|
|
6210
|
+
if (rows.some(row => row.agent))
|
|
6211
|
+
return { actualRssBytes: rows.reduce((sum, row) => sum + row.rssBytes, 0) };
|
|
6212
|
+
}
|
|
6213
|
+
return {};
|
|
6214
|
+
}
|
|
6215
|
+
async function resolveTmuxRecoveryScope(scope, incident) {
|
|
6216
|
+
const candidates = incident.affectedSessionIds;
|
|
6217
|
+
if (scope.kind === 'incident')
|
|
6218
|
+
return candidates;
|
|
6219
|
+
if (scope.kind === 'sessions') {
|
|
6220
|
+
if (!Array.isArray(scope.sessionIds) || scope.sessionIds.length === 0)
|
|
6221
|
+
throw Object.assign(new Error('sessionIds must be a non-empty array'), { code: 'INVALID_RECOVERY_SCOPE' });
|
|
6222
|
+
if (scope.sessionIds.some(id => !candidates.includes(id)))
|
|
6223
|
+
throw Object.assign(new Error('Explicit scope may only include sessions in the current recovery incident'), { code: 'RECOVERY_SCOPE_OUTSIDE_INCIDENT' });
|
|
6224
|
+
return [...new Set(scope.sessionIds)];
|
|
6225
|
+
}
|
|
6226
|
+
if (!scope.id)
|
|
6227
|
+
throw Object.assign(new Error('scope id is required'), { code: 'INVALID_RECOVERY_SCOPE' });
|
|
6228
|
+
if (scope.kind === 'group') {
|
|
6229
|
+
const group = collaborationStore.getGroup(scope.id);
|
|
6230
|
+
if (!group || group.deleted)
|
|
6231
|
+
throw Object.assign(new Error('Collaboration group was not found'), { code: 'RECOVERY_GROUP_NOT_FOUND' });
|
|
6232
|
+
return candidates.filter(id => group.sessionIds.includes(id));
|
|
6233
|
+
}
|
|
6234
|
+
const integrationRoot = `${TERMDOCK_DIR}/integration-state`;
|
|
6235
|
+
const portDirectories = await fs.promises.readdir(integrationRoot, { withFileTypes: true }).catch(() => []);
|
|
6236
|
+
const memberIds = new Set();
|
|
6237
|
+
for (const directory of portDirectories.filter(item => item.isDirectory())) {
|
|
6238
|
+
const state = await readJsonFileIfExists(path.join(integrationRoot, directory.name, 'integration-sessions.json'));
|
|
6239
|
+
for (const item of state?.sessions ?? [])
|
|
6240
|
+
if (item.principal_id === scope.id && typeof item.session_id === 'string')
|
|
6241
|
+
memberIds.add(item.session_id);
|
|
6242
|
+
}
|
|
6243
|
+
return candidates.filter(id => memberIds.has(id));
|
|
6244
|
+
}
|
|
6245
|
+
tmuxRecoveryOperations.setRunner(restoreTmuxRecoverySession);
|
|
6246
|
+
const tmuxAgentResourceBudget = configuredAgentResourceBudget();
|
|
6247
|
+
tmuxRecoveryOperations.setAdmission(async (_operationId, sessionId) => {
|
|
6248
|
+
try {
|
|
6249
|
+
const snapshot = await collectHostResourceSnapshot();
|
|
6250
|
+
const group = collaborationStore.list().find(item => item.sessionIds.includes(sessionId));
|
|
6251
|
+
let residentAgentsInGroup = 0;
|
|
6252
|
+
if (group) {
|
|
6253
|
+
const rows = globalSessionState.sessions.filter(item => group.sessionIds.includes(item.sessionId) && item.agentResume && item.tmuxSessionName);
|
|
6254
|
+
const liveSessions = await listLiveTmuxInventorySessions();
|
|
6255
|
+
const liveAgents = new Set(liveSessions.filter(item => item.program && agentBySlug(item.program) !== null).map(item => item.name));
|
|
6256
|
+
residentAgentsInGroup = rows.filter(item => liveAgents.has(item.tmuxSessionName)).length;
|
|
6257
|
+
const memberIds = new Set(group.sessionIds);
|
|
6258
|
+
residentAgentsInGroup += tmuxRecoveryOperations.all().flatMap(operation => operation.items)
|
|
6259
|
+
.filter(item => item.state === 'running' && memberIds.has(item.sessionId)).length;
|
|
6260
|
+
}
|
|
6261
|
+
const result = assessAgentResourceBudget({ snapshot: { ...snapshot, residentAgentCount: snapshot.residentAgentCount + tmuxRecoveryOperations.stats().activeLaunches }, budget: tmuxAgentResourceBudget, residentAgentsInGroup });
|
|
6262
|
+
return { ...result, memAvailableBytes: snapshot.memoryAvailableBytes, estimatedRssBytes: tmuxAgentResourceBudget.estimatedRssBytes };
|
|
6263
|
+
}
|
|
6264
|
+
catch {
|
|
6265
|
+
return { admitted: false, reason: 'RESOURCE_SNAPSHOT_UNAVAILABLE', estimatedRssBytes: tmuxAgentResourceBudget.estimatedRssBytes };
|
|
6266
|
+
}
|
|
6267
|
+
});
|
|
6268
|
+
const TMUX_RECOVERY_HEALTH_FILE = `${TERMDOCK_DIR}/tmux-recovery-health.json`;
|
|
6269
|
+
let latestTmuxRecoveryHealth = null;
|
|
6270
|
+
let tmuxRecoveryHealthSampling = false;
|
|
6271
|
+
async function sampleTmuxRecoveryHealth() {
|
|
6272
|
+
if (tmuxRecoveryHealthSampling)
|
|
6273
|
+
return;
|
|
6274
|
+
tmuxRecoveryHealthSampling = true;
|
|
6275
|
+
try {
|
|
6276
|
+
const snapshot = await collectHostResourceSnapshot();
|
|
6277
|
+
const diagnostics = {
|
|
6278
|
+
sampledAt: snapshot.sampledAt,
|
|
6279
|
+
platform: snapshot.platform,
|
|
6280
|
+
memoryAvailableBytes: snapshot.memoryAvailableBytes,
|
|
6281
|
+
memoryTotalBytes: snapshot.memoryTotalBytes,
|
|
6282
|
+
memorySnapshotAvailable: snapshot.memorySnapshotAvailable,
|
|
6283
|
+
processSnapshotAvailable: snapshot.processSnapshotAvailable,
|
|
6284
|
+
memoryPressure: snapshot.memoryPressure,
|
|
6285
|
+
kswapd: snapshot.kswapd,
|
|
6286
|
+
dStateProcessCount: snapshot.dStateProcessCount,
|
|
6287
|
+
residentAgentCount: snapshot.residentAgentCount,
|
|
6288
|
+
residentAgentRssBytes: snapshot.residentAgentRssBytes,
|
|
6289
|
+
browserProcessCount: snapshot.browserProcessCount,
|
|
6290
|
+
recovery: tmuxRecoveryOperations.stats(),
|
|
6291
|
+
resourceBudget: tmuxAgentResourceBudget,
|
|
6292
|
+
};
|
|
6293
|
+
latestTmuxRecoveryHealth = diagnostics;
|
|
6294
|
+
const temporaryPath = `${TMUX_RECOVERY_HEALTH_FILE}.${process.pid}.tmp`;
|
|
6295
|
+
await fs.promises.writeFile(temporaryPath, JSON.stringify(diagnostics, null, 2), { mode: 0o600 });
|
|
6296
|
+
await fs.promises.rename(temporaryPath, TMUX_RECOVERY_HEALTH_FILE);
|
|
6297
|
+
}
|
|
6298
|
+
catch (error) {
|
|
6299
|
+
console.warn('[tmux-recovery] resource health sample failed:', getErrorMessage(error));
|
|
6300
|
+
}
|
|
6301
|
+
finally {
|
|
6302
|
+
tmuxRecoveryHealthSampling = false;
|
|
6303
|
+
}
|
|
6304
|
+
}
|
|
6305
|
+
router.get('/tmux/recovery/diagnostics', async (_req, res) => {
|
|
6306
|
+
await sampleTmuxRecoveryHealth();
|
|
6307
|
+
return res.json({ health: latestTmuxRecoveryHealth, operation: tmuxRecoveryOperations.latest(), concurrencyLimit: tmuxRecoveryConcurrencyLimit });
|
|
6308
|
+
});
|
|
6309
|
+
router.post('/tmux/recovery/restore-all', async (req, res) => {
|
|
6310
|
+
const incident = tmuxRecoveryState.incident;
|
|
6311
|
+
if (!incident)
|
|
6312
|
+
return res.status(404).json({ error: 'no tmux recovery is pending' });
|
|
6313
|
+
const rawScope = req.body?.scope;
|
|
6314
|
+
let scope = { kind: 'incident' };
|
|
6315
|
+
if (rawScope !== undefined) {
|
|
6316
|
+
if (!rawScope || !['incident', 'group', 'principal', 'sessions'].includes(rawScope.kind))
|
|
6317
|
+
return res.status(400).json({ error: 'invalid recovery scope' });
|
|
6318
|
+
scope = rawScope;
|
|
6319
|
+
}
|
|
6320
|
+
const sessionIds = await resolveTmuxRecoveryScope(scope, incident).catch(error => {
|
|
6321
|
+
res.status(400).json({ error: getErrorMessage(error), code: error.code ?? 'INVALID_RECOVERY_SCOPE' });
|
|
6322
|
+
return null;
|
|
6180
6323
|
});
|
|
6324
|
+
if (!sessionIds)
|
|
6325
|
+
return;
|
|
6326
|
+
try {
|
|
6327
|
+
const operation = await tmuxRecoveryOperations.create({ incidentId: incident.id, sessionIds, scope,
|
|
6328
|
+
idempotencyKey: String(req.body?.idempotencyKey ?? req.headers['idempotency-key'] ?? ''),
|
|
6329
|
+
...(req.body?.concurrency !== undefined ? { concurrency: Number(req.body.concurrency) } : {}) });
|
|
6330
|
+
res.status(202).json({ accepted: true, operation_id: operation.id, operation: tmuxRecoveryOperations.get(operation.id) });
|
|
6331
|
+
}
|
|
6332
|
+
catch (error) {
|
|
6333
|
+
res.status(409).json({ error: getErrorMessage(error), code: error.code ?? 'RECOVERY_OPERATION_FAILED', operation_id: error.operationId });
|
|
6334
|
+
}
|
|
6335
|
+
});
|
|
6336
|
+
router.get('/tmux/recovery/operations', (_req, res) => res.json({ operations: tmuxRecoveryOperations.all().slice(-50) }));
|
|
6337
|
+
router.get('/tmux/recovery/operations/:id', (req, res) => {
|
|
6338
|
+
const operation = tmuxRecoveryOperations.get(req.params.id);
|
|
6339
|
+
return operation ? res.json({ operation }) : res.status(404).json({ error: 'recovery operation not found' });
|
|
6340
|
+
});
|
|
6341
|
+
router.post('/tmux/recovery/operations/:id/pause', async (req, res) => {
|
|
6342
|
+
const operation = await tmuxRecoveryOperations.pause(req.params.id);
|
|
6343
|
+
return operation ? res.json({ operation }) : res.status(404).json({ error: 'recovery operation not found' });
|
|
6344
|
+
});
|
|
6345
|
+
router.post('/tmux/recovery/operations/:id/resume', async (req, res) => {
|
|
6346
|
+
const operation = await tmuxRecoveryOperations.resume(req.params.id);
|
|
6347
|
+
return operation ? res.json({ operation }) : res.status(404).json({ error: 'recovery operation not found' });
|
|
6181
6348
|
});
|
|
6182
6349
|
router.delete('/tmux/recovery', async (_req, res) => {
|
|
6350
|
+
if (tmuxRecoveryOperations.all().some(operation => !['succeeded', 'partial_failure'].includes(operation.state))) {
|
|
6351
|
+
return res.status(409).json({ error: 'a recovery operation is active; pause or finish it before dismissing the incident', code: 'RECOVERY_OPERATION_ACTIVE' });
|
|
6352
|
+
}
|
|
6183
6353
|
tmuxRecoveryState.dismissedSessionIds = Array.from(new Set([
|
|
6184
6354
|
...tmuxRecoveryState.dismissedSessionIds,
|
|
6185
6355
|
...(tmuxRecoveryState.incident?.affectedSessionIds ?? []),
|
|
@@ -8306,7 +8476,10 @@ router.post('/:sessionId/input', express.text({ type: '*/*' }), (req, res) => {
|
|
|
8306
8476
|
}
|
|
8307
8477
|
const data = typeof req.body === 'string' ? req.body : '';
|
|
8308
8478
|
try {
|
|
8309
|
-
|
|
8479
|
+
const paste = new TerminalPasteBuffer(() => { });
|
|
8480
|
+
const parts = paste.complete(data);
|
|
8481
|
+
for (const part of parts)
|
|
8482
|
+
session.ptyProcess.write(part);
|
|
8310
8483
|
session.lastActivity = Date.now();
|
|
8311
8484
|
res.json({ success: true });
|
|
8312
8485
|
}
|
|
@@ -8475,7 +8648,7 @@ router.post('/:sessionId/tmux', async (req, res) => {
|
|
|
8475
8648
|
broadcastEvent(sessionId, { type: 'tmux-layout', layout });
|
|
8476
8649
|
return res.json({ success: true, layout });
|
|
8477
8650
|
}
|
|
8478
|
-
return res.json({ success: true });
|
|
8651
|
+
return res.json({ success: true, ...(result.selection ? { selection: result.selection } : {}) });
|
|
8479
8652
|
}
|
|
8480
8653
|
catch (error) {
|
|
8481
8654
|
const errorMessage = getErrorMessage(error);
|
|
@@ -8570,6 +8743,10 @@ router.post('/force-kill', (req, res) => {
|
|
|
8570
8743
|
async function executeTmuxAction(tmuxTarget, action, body, control) {
|
|
8571
8744
|
let shouldBroadcastLayout = true;
|
|
8572
8745
|
switch (action) {
|
|
8746
|
+
case 'copy-selection': {
|
|
8747
|
+
const selection = await copyTmuxSelection(tmuxTarget, runTmux);
|
|
8748
|
+
return { shouldBroadcastLayout: false, selection };
|
|
8749
|
+
}
|
|
8573
8750
|
case 'select-pane': {
|
|
8574
8751
|
const paneId = typeof body.paneId === 'string' ? body.paneId : '';
|
|
8575
8752
|
if (!paneId)
|
|
@@ -8960,6 +9137,12 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}, i
|
|
|
8960
9137
|
if (ws.readyState === ws.OPEN)
|
|
8961
9138
|
ws.send(JSON.stringify(event));
|
|
8962
9139
|
});
|
|
9140
|
+
const pasteInput = new TerminalPasteBuffer(error => {
|
|
9141
|
+
if (ws.readyState === ws.OPEN) {
|
|
9142
|
+
ws.send(JSON.stringify({ type: 'error', error }));
|
|
9143
|
+
ws.close(1008, 'Incomplete terminal paste');
|
|
9144
|
+
}
|
|
9145
|
+
});
|
|
8963
9146
|
// Handle client → server messages
|
|
8964
9147
|
ws.on('message', async (raw) => {
|
|
8965
9148
|
const messageReceivedAt = performance.now();
|
|
@@ -8974,20 +9157,30 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}, i
|
|
|
8974
9157
|
switch (msg.type) {
|
|
8975
9158
|
case 'input': {
|
|
8976
9159
|
if (typeof msg.data === 'string' && msg.data.length > 0) {
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
|
|
8980
|
-
|
|
9160
|
+
let parts;
|
|
9161
|
+
try {
|
|
9162
|
+
parts = pasteInput.push(msg.data);
|
|
9163
|
+
}
|
|
9164
|
+
catch (error) {
|
|
9165
|
+
ws.send(JSON.stringify({ type: 'error', error: getErrorMessage(error) }));
|
|
9166
|
+
ws.close(1008, 'Invalid terminal paste');
|
|
9167
|
+
return;
|
|
8981
9168
|
}
|
|
8982
|
-
|
|
8983
|
-
|
|
9169
|
+
for (const data of parts) {
|
|
9170
|
+
if (attachment) {
|
|
9171
|
+
session.lastActivity = Date.now();
|
|
9172
|
+
attachment.write(data);
|
|
9173
|
+
}
|
|
9174
|
+
else if (session.mode === 'tmux') {
|
|
9175
|
+
await enqueueTmuxIo(session, async () => {
|
|
9176
|
+
session.lastActivity = Date.now();
|
|
9177
|
+
session.ptyProcess.write(data);
|
|
9178
|
+
});
|
|
9179
|
+
}
|
|
9180
|
+
else {
|
|
8984
9181
|
session.lastActivity = Date.now();
|
|
8985
9182
|
session.ptyProcess.write(data);
|
|
8986
|
-
}
|
|
8987
|
-
}
|
|
8988
|
-
else {
|
|
8989
|
-
session.lastActivity = Date.now();
|
|
8990
|
-
session.ptyProcess.write(data);
|
|
9183
|
+
}
|
|
8991
9184
|
}
|
|
8992
9185
|
}
|
|
8993
9186
|
break;
|
|
@@ -9055,8 +9248,14 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}, i
|
|
|
9055
9248
|
}
|
|
9056
9249
|
const result = await enqueueTmuxIo(session, async () => {
|
|
9057
9250
|
return executeTmuxAction(session.tmuxSessionName, msg.action, msg, session.tmuxControl);
|
|
9251
|
+
}).catch((error) => {
|
|
9252
|
+
// A correlated copy failure must settle this socket's request, not
|
|
9253
|
+
// become an unrelated terminal error followed by a 30-second wait.
|
|
9254
|
+
if (msg.action !== 'copy-selection')
|
|
9255
|
+
throw error;
|
|
9256
|
+
return { shouldBroadcastLayout: false, error: getErrorMessage(error) || 'Tmux selection copy failed' };
|
|
9058
9257
|
});
|
|
9059
|
-
ws.send(JSON.stringify({ type: 'tmux-result', reqId, success: !result.error, error: result.error }));
|
|
9258
|
+
ws.send(JSON.stringify({ type: 'tmux-result', reqId, success: !result.error, error: result.error, ...(result.selection ? { selection: result.selection } : {}) }));
|
|
9060
9259
|
if (result.shouldBroadcastLayout && session.tmuxSessionName) {
|
|
9061
9260
|
try {
|
|
9062
9261
|
const layout = await getTmuxLayout(session.tmuxSessionName);
|
|
@@ -9154,6 +9353,7 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}, i
|
|
|
9154
9353
|
});
|
|
9155
9354
|
// Cleanup on close
|
|
9156
9355
|
ws.on('close', () => {
|
|
9356
|
+
pasteInput.clear();
|
|
9157
9357
|
initialScreen?.cancel();
|
|
9158
9358
|
initialScreen = null;
|
|
9159
9359
|
attachment?.close();
|