termdock 1.4.314 → 1.4.315
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-BCTGnwU1.js → App-B2SFV3qg.js} +6 -6
- package/dist/client/assets/App-B2SFV3qg.js.br +0 -0
- package/dist/client/assets/App-B2SFV3qg.js.gz +0 -0
- package/dist/client/assets/{ArchitecturePanel-CVTIYbyj.js → ArchitecturePanel-Cj7za6DZ.js} +2 -2
- package/dist/client/assets/ArchitecturePanel-Cj7za6DZ.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-Cj7za6DZ.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-BKsuofw6.js → ChangeWalkthroughPanel-BB52iLLC.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-BB52iLLC.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BB52iLLC.js.gz +0 -0
- package/dist/client/assets/{CollaborationMainWorkspace-DZ0qwLUz.js → CollaborationMainWorkspace-D5WKV3rA.js} +1 -1
- package/dist/client/assets/CollaborationMainWorkspace-D5WKV3rA.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-D5WKV3rA.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-MwUgcmG_.js +2 -0
- package/dist/client/assets/ComputerControlView-Dkc4R1IU.js +5 -0
- package/dist/client/assets/ComputerControlView-Dkc4R1IU.js.br +0 -0
- package/dist/client/assets/ComputerControlView-Dkc4R1IU.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-ZgszT1TH.js → DagPlayground-BHY3l8UG.js} +1 -1
- package/dist/client/assets/DagPlayground-BHY3l8UG.js.br +0 -0
- package/dist/client/assets/DagPlayground-BHY3l8UG.js.gz +0 -0
- package/dist/client/assets/{DiffLab-D_9U-zfZ.js → DiffLab-BSQiKfBJ.js} +1 -1
- package/dist/client/assets/DiffLab-BSQiKfBJ.js.br +0 -0
- package/dist/client/assets/DiffLab-BSQiKfBJ.js.gz +0 -0
- package/dist/client/assets/{DiffReview-8Pfe_BNO.js → DiffReview-CYrBBq0V.js} +1 -1
- package/dist/client/assets/DiffReview-CYrBBq0V.js.br +0 -0
- package/dist/client/assets/DiffReview-CYrBBq0V.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-p3ZSl0P2.js → DiffReviewLab-VSIbEnxm.js} +1 -1
- package/dist/client/assets/DiffReviewLab-VSIbEnxm.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-VSIbEnxm.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-Cz64sLJT.js → DiffViewer-Cx5fyiMB.js} +1 -1
- package/dist/client/assets/DiffViewer-Cx5fyiMB.js.br +0 -0
- package/dist/client/assets/DiffViewer-Cx5fyiMB.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-B28x060d.js → ModelPreview-B22odexG.js} +1 -1
- package/dist/client/assets/ModelPreview-B22odexG.js.br +0 -0
- package/dist/client/assets/ModelPreview-B22odexG.js.gz +0 -0
- package/dist/client/assets/{RightSidebar-SIqDjggD.js → RightSidebar-DKA0jXPW.js} +3 -3
- package/dist/client/assets/RightSidebar-DKA0jXPW.js.br +0 -0
- package/dist/client/assets/RightSidebar-DKA0jXPW.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH--t0bQKCQ.js → abnfDiagram-VCTEODGH-sbUSsiPn.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-sbUSsiPn.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-sbUSsiPn.js.gz +0 -0
- package/dist/client/assets/{app-DWidKLU2.js → app-CYOLJycb.js} +3 -3
- package/dist/client/assets/app-CYOLJycb.js.br +0 -0
- package/dist/client/assets/app-CYOLJycb.js.gz +0 -0
- package/dist/client/assets/{arc-DMWsmL7t.js → arc-GhJ9aeQ3.js} +1 -1
- package/dist/client/assets/arc-GhJ9aeQ3.js.br +0 -0
- package/dist/client/assets/arc-GhJ9aeQ3.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-BYkvSKnR.js → architectureDiagram-5GKGNRK7-BYmAIJBx.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BYmAIJBx.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BYmAIJBx.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-B574d9TS.js → blockDiagram-I7D4REHJ-CZNZT_ez.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-CZNZT_ez.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CZNZT_ez.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-D__CwUct.js → c4Diagram-7LVT6UL2-6xcSowLn.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-6xcSowLn.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-6xcSowLn.js.gz +0 -0
- package/dist/client/assets/channel-C1FwyjiU.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BvwmL72I.js → chunk-2Q5K7J3B-C06nkhUQ.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-BktO-a5l.js → chunk-5VM5RSS4-BHY43hJO.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DeClqFBe.js → chunk-F27PBJKO-Dl8JYcZO.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-Dl8JYcZO.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-Dl8JYcZO.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-DBRCslkn.js → chunk-IMKFNOWR-BALi4Q3g.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-BALi4Q3g.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BALi4Q3g.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BfpbqgrR.js → chunk-JWPE2WC7-BBD2d7j7.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-Bw-dqneu.js → chunk-POPQ4Y6H-CZN0_ndj.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-BdxOWBjP.js → chunk-SVP7TREG-CD-Zf9rn.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-CD-Zf9rn.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CD-Zf9rn.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-BtNXehgz.js → chunk-TICWLB2K-Clpg1zwp.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-Clpg1zwp.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Clpg1zwp.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-CUxGdImw.js → chunk-XXDRQBXY-BanPVnIr.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-FyKDan8T.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-FyKDan8T.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Buod2YZ0.js → cose-bilkent-JH36ORCC-431u7cfU.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-431u7cfU.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-431u7cfU.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-i2eiybdj.js → cynefin-OW5HDTMX-BuLFGx14.js} +1 -1
- package/dist/client/assets/{cynefin-OW5HDTMX-i2eiybdj.js.br → cynefin-OW5HDTMX-BuLFGx14.js.br} +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-i2eiybdj.js.gz → cynefin-OW5HDTMX-BuLFGx14.js.gz} +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-D_YF2Yi3.js → cynefinDiagram-5FMLGOSQ-qLzjwMMG.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-qLzjwMMG.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-qLzjwMMG.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-CfLEvt7Z.js → dagre-GXQ25YYZ-CUdd5M5g.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-CUdd5M5g.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CUdd5M5g.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-BV0Wackt.js → diagram-S7CK7UJ4-DLQClBzm.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DLQClBzm.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DLQClBzm.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-jwaoLY_P.js → diagram-UQ7AKVKN-1RGASXU_.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-1RGASXU_.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-1RGASXU_.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-CuAFyl0n.js → diagram-VSXAHHWV-DqnCzn2R.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-DqnCzn2R.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-DqnCzn2R.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-bbmA7rRk.js → diagram-VX7I27RA-CMXsPqTS.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-CMXsPqTS.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CMXsPqTS.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-CX3-Mxwo.js → diagram-Z3DM3KII-BM0N1kCR.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-BM0N1kCR.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BM0N1kCR.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-Pkq6omzy.js → ebnfDiagram-PWID7BFC-CvaVo-wh.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-CvaVo-wh.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-CvaVo-wh.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-CjdD0JCI.js → erDiagram-RLTQ6QDP-okw-AY7i.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-okw-AY7i.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-okw-AY7i.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-DKECYHVY.js → flowDiagram-HODETNUW-B_DWtzOe.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-B_DWtzOe.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B_DWtzOe.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BPCGT0Kd.js → ganttDiagram-EL5Y4UJY-Bm4nYUVg.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bm4nYUVg.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bm4nYUVg.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-DVPPQnKq.js → gestureArbiter-XalN4TaN.js} +1 -1
- package/dist/client/assets/gestureArbiter-XalN4TaN.js.br +0 -0
- package/dist/client/assets/gestureArbiter-XalN4TaN.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-DFCvVJ6L.js → gitGraphDiagram-WWUBYQGX-B7LAo8S2.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-B7LAo8S2.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-B7LAo8S2.js.gz +0 -0
- package/dist/client/assets/index-BObjbh2R.css +1 -0
- package/dist/client/assets/index-BObjbh2R.css.br +0 -0
- package/dist/client/assets/{index-CSp4mPGE.css.gz → index-BObjbh2R.css.gz} +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-BTL8-sSN.js → infoDiagram-27XIBGKW-DXncUrwz.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Xtc0AfGK.js → ishikawaDiagram-5VMMS53U-Ds28H7pI.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Ds28H7pI.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Ds28H7pI.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-BLtTDKtV.js → journeyDiagram-3NMN7TZE-WeF_efZ2.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-WeF_efZ2.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-WeF_efZ2.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-C65azoTU.js → kanban-definition-UXKFOSKX-CMbZcy6U.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-CMbZcy6U.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CMbZcy6U.js.gz +0 -0
- package/dist/client/assets/{layout-TEicKqeV.js → layout-DNapC8T-.js} +1 -1
- package/dist/client/assets/layout-DNapC8T-.js.br +0 -0
- package/dist/client/assets/layout-DNapC8T-.js.gz +0 -0
- package/dist/client/assets/{linear-DQSikGMo.js → linear-B9AFx-TF.js} +1 -1
- package/dist/client/assets/linear-B9AFx-TF.js.br +0 -0
- package/dist/client/assets/linear-B9AFx-TF.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-vZB1UnWX.js → mermaid.core-Ddh1Z0hf.js} +5 -5
- package/dist/client/assets/mermaid.core-Ddh1Z0hf.js.br +0 -0
- package/dist/client/assets/mermaid.core-Ddh1Z0hf.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-B-coBapV.js → mindmap-definition-YA3MSWOX-B0xMGUol.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-B0xMGUol.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-B0xMGUol.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-LSQpoVpD.js → passwordBootstrap-DQRd5vCF.js} +1 -1
- package/dist/client/assets/passwordBootstrap-DQRd5vCF.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-DQRd5vCF.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB--jWB5n1V.js → pegDiagram-XKGWAZYB-BVZ-3kEF.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-BVZ-3kEF.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BVZ-3kEF.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-DmAqsAUK.js → pieDiagram-E7YTZNPT-Cim4Hk3I.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-Cim4Hk3I.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Cim4Hk3I.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-CqsfUApA.js → quadrantDiagram-AXDQQJYC-CYiEj5sp.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CYiEj5sp.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CYiEj5sp.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-CzvYijD5.js → railroadDiagram-O6MQD6OU-DjNNfYra.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DjNNfYra.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DjNNfYra.js.gz +0 -0
- package/dist/client/assets/{rdpSession-DGvDRc95.js → rdpSession-DEzSQAuN.js} +1 -1
- package/dist/client/assets/rdpSession-DEzSQAuN.js.br +0 -0
- package/dist/client/assets/rdpSession-DEzSQAuN.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-BwP_B3l2.js → referenceLongPress-BjyTtoDY.js} +1 -1
- package/dist/client/assets/referenceLongPress-BjyTtoDY.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BjyTtoDY.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-E2FSO_hc.js → requirementDiagram-BXWQKSXE-ZwiKdiI3.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-ZwiKdiI3.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-ZwiKdiI3.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-uSCR6gDg.js → sankeyDiagram-P5KCCOFB-BYR7ctps.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BYR7ctps.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BYR7ctps.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BqxdP03l.js → sequenceDiagram-WJ2MYXX4-DxYCvK80.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DxYCvK80.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DxYCvK80.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-B7GSZxIt.js → sizeCapture-INFHLROL-CXruk8Ko.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-CXruk8Ko.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CXruk8Ko.js.gz +0 -0
- package/dist/client/assets/{sparkles-DBVpVoIl.js → sparkles-DRH97PDH.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-DJ9UpQL9.js → stateDiagram-D77RDMKH-CENDGrxr.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-CENDGrxr.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CENDGrxr.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-De-tGy_n.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-B6f_z8xv.js → swimlanes-42K2YHIH-BZQeVFeA.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BZQeVFeA.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BZQeVFeA.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-D1YMLrIO.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-CMNs0nui.js → timeline-definition-24CTP7MA-DiJ9i33g.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-DiJ9i33g.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DiJ9i33g.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-CN3m1g76.js → triangle-alert-DECoBoOV.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-B2glpBlo.js → vennDiagram-4TSXK5OY-DBiEJQ9Y.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-DBiEJQ9Y.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DBiEJQ9Y.js.gz +0 -0
- package/dist/client/assets/{videoCompression-DwronsgA.js → videoCompression-8r1jOza9.js} +1 -1
- package/dist/client/assets/videoCompression-8r1jOza9.js.br +0 -0
- package/dist/client/assets/videoCompression-8r1jOza9.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DyMNl-ZC.js → wardleyDiagram-VM6X3IG4-B6wCSfya.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-B6wCSfya.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-B6wCSfya.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DhZFXjY5.js → xychartDiagram-S5SC5T6Z-DlNQayU5.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DlNQayU5.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DlNQayU5.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.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/agent/collaborationCli.js +17 -7
- package/dist/server/agent/integrationCli.js +9 -2
- package/dist/server/agent/integrationServer.js +12 -0
- package/dist/server/agent/integrationSessions.js +197 -3
- package/dist/server/agent/integrationStore.js +1 -1
- package/dist/server/agent/sessionLifecycle.js +4 -0
- package/dist/server/agent/sessionManagement.js +87 -0
- package/dist/server/routes/terminal.js +124 -2
- package/docs/integrations.md +110 -0
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-BCTGnwU1.js.br +0 -0
- package/dist/client/assets/App-BCTGnwU1.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-CVTIYbyj.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CVTIYbyj.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BKsuofw6.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BKsuofw6.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DZ0qwLUz.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DZ0qwLUz.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-BLQNknOq.js +0 -2
- package/dist/client/assets/ComputerControlView-DN6cXOnb.js +0 -5
- package/dist/client/assets/ComputerControlView-DN6cXOnb.js.br +0 -0
- package/dist/client/assets/ComputerControlView-DN6cXOnb.js.gz +0 -0
- package/dist/client/assets/DagPlayground-ZgszT1TH.js.br +0 -0
- package/dist/client/assets/DagPlayground-ZgszT1TH.js.gz +0 -0
- package/dist/client/assets/DiffLab-D_9U-zfZ.js.br +0 -0
- package/dist/client/assets/DiffLab-D_9U-zfZ.js.gz +0 -0
- package/dist/client/assets/DiffReview-8Pfe_BNO.js.br +0 -0
- package/dist/client/assets/DiffReview-8Pfe_BNO.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-p3ZSl0P2.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-p3ZSl0P2.js.gz +0 -0
- package/dist/client/assets/DiffViewer-Cz64sLJT.js.br +0 -0
- package/dist/client/assets/DiffViewer-Cz64sLJT.js.gz +0 -0
- package/dist/client/assets/ModelPreview-B28x060d.js.br +0 -0
- package/dist/client/assets/ModelPreview-B28x060d.js.gz +0 -0
- package/dist/client/assets/RightSidebar-SIqDjggD.js.br +0 -0
- package/dist/client/assets/RightSidebar-SIqDjggD.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH--t0bQKCQ.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH--t0bQKCQ.js.gz +0 -0
- package/dist/client/assets/app-DWidKLU2.js.br +0 -0
- package/dist/client/assets/app-DWidKLU2.js.gz +0 -0
- package/dist/client/assets/arc-DMWsmL7t.js.br +0 -0
- package/dist/client/assets/arc-DMWsmL7t.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BYkvSKnR.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BYkvSKnR.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-B574d9TS.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-B574d9TS.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-D__CwUct.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-D__CwUct.js.gz +0 -0
- package/dist/client/assets/channel-BonXK9tr.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DeClqFBe.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DeClqFBe.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DBRCslkn.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DBRCslkn.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BdxOWBjP.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BdxOWBjP.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BtNXehgz.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BtNXehgz.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-CXO_pgVP.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CXO_pgVP.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Buod2YZ0.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-Buod2YZ0.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-D_YF2Yi3.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-D_YF2Yi3.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CfLEvt7Z.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CfLEvt7Z.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BV0Wackt.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BV0Wackt.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-jwaoLY_P.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-jwaoLY_P.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CuAFyl0n.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CuAFyl0n.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-bbmA7rRk.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-bbmA7rRk.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CX3-Mxwo.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CX3-Mxwo.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Pkq6omzy.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Pkq6omzy.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-CjdD0JCI.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-CjdD0JCI.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DKECYHVY.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DKECYHVY.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BPCGT0Kd.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BPCGT0Kd.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-DVPPQnKq.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DVPPQnKq.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DFCvVJ6L.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DFCvVJ6L.js.gz +0 -0
- package/dist/client/assets/index-CSp4mPGE.css +0 -1
- package/dist/client/assets/index-CSp4mPGE.css.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Xtc0AfGK.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Xtc0AfGK.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BLtTDKtV.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BLtTDKtV.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-C65azoTU.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-C65azoTU.js.gz +0 -0
- package/dist/client/assets/layout-TEicKqeV.js.br +0 -0
- package/dist/client/assets/layout-TEicKqeV.js.gz +0 -0
- package/dist/client/assets/linear-DQSikGMo.js.br +0 -0
- package/dist/client/assets/linear-DQSikGMo.js.gz +0 -0
- package/dist/client/assets/mermaid.core-vZB1UnWX.js.br +0 -0
- package/dist/client/assets/mermaid.core-vZB1UnWX.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-B-coBapV.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-B-coBapV.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-LSQpoVpD.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-LSQpoVpD.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB--jWB5n1V.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB--jWB5n1V.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DmAqsAUK.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DmAqsAUK.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CqsfUApA.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CqsfUApA.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CzvYijD5.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CzvYijD5.js.gz +0 -0
- package/dist/client/assets/rdpSession-DGvDRc95.js.br +0 -0
- package/dist/client/assets/rdpSession-DGvDRc95.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-BwP_B3l2.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BwP_B3l2.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-E2FSO_hc.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-E2FSO_hc.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-uSCR6gDg.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-uSCR6gDg.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BqxdP03l.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BqxdP03l.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B7GSZxIt.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B7GSZxIt.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DJ9UpQL9.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DJ9UpQL9.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-P9vVPNJW.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-B6f_z8xv.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-B6f_z8xv.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Deucv-sT.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-CMNs0nui.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-CMNs0nui.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-B2glpBlo.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-B2glpBlo.js.gz +0 -0
- package/dist/client/assets/videoCompression-DwronsgA.js.br +0 -0
- package/dist/client/assets/videoCompression-DwronsgA.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DyMNl-ZC.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DyMNl-ZC.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DhZFXjY5.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DhZFXjY5.js.gz +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import { cleanMemberName } from './collaborationRoutes.js';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
4
5
|
import { atomicJson, integrationError } from './integrationStore.js';
|
|
@@ -22,8 +23,8 @@ function startupInput(record, now) {
|
|
|
22
23
|
return condition ? { state: 'pending', deadline: now + (condition.timeoutMs ?? 120000), matched_since: null, observed_at: null } : undefined;
|
|
23
24
|
}
|
|
24
25
|
export function publicIntegrationSession(record) {
|
|
25
|
-
const { profile: _, deadline: _deadline, launch_submitted: _submitted, startup_condition: _condition, restore_diagnostics: diagnostics, launch_diagnostics: launchDiagnostics, launch_diagnostics_history: _history, message_history_seen: _messages, ...publicRecord } = record;
|
|
26
|
-
return { ...publicRecord, ...(diagnostics ? { restore_diagnostics_available: true } : {}),
|
|
26
|
+
const { profile: _, deadline: _deadline, launch_submitted: _submitted, startup_condition: _condition, restore_diagnostics: diagnostics, launch_diagnostics: launchDiagnostics, launch_diagnostics_history: _history, message_history_seen: _messages, close_intent: _closeIntent, ...publicRecord } = record;
|
|
27
|
+
return { ...publicRecord, close_pending: !!_closeIntent, ...(diagnostics ? { restore_diagnostics_available: true } : {}),
|
|
27
28
|
...(launchDiagnostics ? { launch_diagnostics_available: true } : {}) };
|
|
28
29
|
}
|
|
29
30
|
export function permittedCwd(profile, cwd) {
|
|
@@ -60,6 +61,7 @@ export class IntegrationSessions {
|
|
|
60
61
|
lifecycle = new SessionLifecycleCoordinator();
|
|
61
62
|
mutations = new Map();
|
|
62
63
|
coldRetries = new Set();
|
|
64
|
+
closingOperations = new Set();
|
|
63
65
|
timers = new Set();
|
|
64
66
|
stopObservation;
|
|
65
67
|
stopDeliveryGuard;
|
|
@@ -84,7 +86,7 @@ export class IntegrationSessions {
|
|
|
84
86
|
record.runtime_present ??= null;
|
|
85
87
|
}
|
|
86
88
|
for (const record of this.doc.sessions)
|
|
87
|
-
if (['starting', 'restoring', 'binding_pending'].includes(record.state) || record.state
|
|
89
|
+
if (['starting', 'restoring', 'binding_pending'].includes(record.state) || ['releasing', 'closing'].includes(record.state) || record.startup_input?.state === 'pending')
|
|
88
90
|
this.scheduleRefresh(record);
|
|
89
91
|
// A pre-restart partial match cannot prove continuity of the live screen.
|
|
90
92
|
for (const record of this.doc.sessions)
|
|
@@ -245,8 +247,14 @@ export class IntegrationSessions {
|
|
|
245
247
|
}
|
|
246
248
|
assertRuntimeMessage(id) {
|
|
247
249
|
const record = this.doc.sessions.find(s => s.session_id === id);
|
|
250
|
+
if (this.closingOperations.has(id))
|
|
251
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Session close preflight is in progress', 409);
|
|
248
252
|
if (this.coldRetries.has(id) || record?.cold_retry_pending)
|
|
249
253
|
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Cold retry must finish or be reconciled before sending', 409);
|
|
254
|
+
if (record?.state === 'closing' && !record.operation_uncertain)
|
|
255
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Session close is in progress', 409);
|
|
256
|
+
if (record?.close_intent)
|
|
257
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Close outcome requires reconciliation', 409);
|
|
250
258
|
assertMessageRuntime(record);
|
|
251
259
|
// Durable, conservative latch before admission; even a later enqueue
|
|
252
260
|
// failure cannot make a previously attempted business request disappear.
|
|
@@ -301,6 +309,7 @@ export class IntegrationSessions {
|
|
|
301
309
|
const request = this.request(principal, key, ['retry-cold', id, generation]);
|
|
302
310
|
if (request.previous)
|
|
303
311
|
return this.replayRequest(principal, id, request.previous);
|
|
312
|
+
this.assertManagementSettled(this.lookup(principal, id));
|
|
304
313
|
if (!this.adapter.retryCold || !this.adapter.messageHistory)
|
|
305
314
|
integrationError('SESSION_COLD_RETRY_UNSUPPORTED', 'Runtime adapter does not support verified cold retry', 409);
|
|
306
315
|
if (this.pending.has(id))
|
|
@@ -308,6 +317,7 @@ export class IntegrationSessions {
|
|
|
308
317
|
this.coldRetries.add(id);
|
|
309
318
|
try {
|
|
310
319
|
const record = await this.get(principal, id);
|
|
320
|
+
this.assertManagementSettled(record);
|
|
311
321
|
assertReleaseGeneration(record, generation);
|
|
312
322
|
if (record.cold_retry_pending || record.operation_uncertain)
|
|
313
323
|
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Reconcile prior lifecycle outcome before cold retry', 409);
|
|
@@ -373,6 +383,7 @@ export class IntegrationSessions {
|
|
|
373
383
|
let releaseNative;
|
|
374
384
|
try {
|
|
375
385
|
const record = await this.get(principal, id);
|
|
386
|
+
this.assertManagementSettled(record);
|
|
376
387
|
if (record.cold_retry_pending)
|
|
377
388
|
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Reconcile the cold retry before native restore', 409);
|
|
378
389
|
if (!record.agent_native_session_id)
|
|
@@ -448,6 +459,7 @@ export class IntegrationSessions {
|
|
|
448
459
|
const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
|
|
449
460
|
if (!record)
|
|
450
461
|
return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
|
|
462
|
+
this.assertManagementSettled(record);
|
|
451
463
|
assertReleaseGeneration(record, generation);
|
|
452
464
|
if (this.pending.has(id) || record.operation_uncertain || ['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state)) {
|
|
453
465
|
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the current lifecycle operation before clearing startup input conditions', 409);
|
|
@@ -469,6 +481,7 @@ export class IntegrationSessions {
|
|
|
469
481
|
const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
|
|
470
482
|
if (!record)
|
|
471
483
|
return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
|
|
484
|
+
this.assertManagementSettled(record);
|
|
472
485
|
assertReleaseGeneration(record, generation);
|
|
473
486
|
if (this.pending.has(id) || ['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state))
|
|
474
487
|
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the current operation before confirming compatibility', 409);
|
|
@@ -481,16 +494,145 @@ export class IntegrationSessions {
|
|
|
481
494
|
return record;
|
|
482
495
|
});
|
|
483
496
|
}
|
|
497
|
+
assertManagementSettled(record) {
|
|
498
|
+
if (record.state === 'closed')
|
|
499
|
+
integrationError('SESSION_CLOSED', 'Explicitly closed sessions cannot be restored or restarted', 409);
|
|
500
|
+
if (record.state === 'closing' && !record.operation_uncertain)
|
|
501
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Session close is in progress', 409);
|
|
502
|
+
if (record.close_intent || record.state === 'closing')
|
|
503
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Reconcile the pending close before any lifecycle operation', 409);
|
|
504
|
+
if (record.rename_pending)
|
|
505
|
+
integrationError('SESSION_RENAME_SYNC_PENDING', 'Read session get to reconcile the pending name update', 409);
|
|
506
|
+
}
|
|
507
|
+
assertManagementIdle(record) {
|
|
508
|
+
if (this.pending.has(record.session_id) || record.cold_retry_pending || record.operation_uncertain || ['starting', 'restoring', 'binding_pending', 'releasing', 'closing'].includes(record.state)) {
|
|
509
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the current lifecycle operation first', 409);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
async rename(principal, id, generation, value, key) {
|
|
513
|
+
if (typeof value !== 'string')
|
|
514
|
+
integrationError('INVALID_NAME', 'name must be non-empty text up to 80 characters');
|
|
515
|
+
const name = cleanMemberName(value);
|
|
516
|
+
if (!name)
|
|
517
|
+
integrationError('INVALID_NAME', 'name must be non-empty text up to 80 characters');
|
|
518
|
+
return this.mutate(principal, key, ['rename', id, generation, name], () => this.lifecycle.run(id, async () => {
|
|
519
|
+
const request = this.request(principal, key, ['rename', id, generation, name]);
|
|
520
|
+
if (request.previous)
|
|
521
|
+
return this.replayRequest(principal, id, request.previous);
|
|
522
|
+
if (!this.adapter.rename)
|
|
523
|
+
integrationError('SESSION_RENAME_UNSUPPORTED', 'Runtime adapter does not support renaming', 409);
|
|
524
|
+
const record = await this.get(principal, id);
|
|
525
|
+
assertReleaseGeneration(record, generation);
|
|
526
|
+
if (record.close_intent || record.rename_pending)
|
|
527
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the pending management operation first', 409);
|
|
528
|
+
this.assertManagementIdle(record);
|
|
529
|
+
await this.adapter.prepareRename?.(record);
|
|
530
|
+
record.name = name;
|
|
531
|
+
record.name_updated_at = Date.now();
|
|
532
|
+
record.updated_at = record.name_updated_at;
|
|
533
|
+
record.generation = generation + 1;
|
|
534
|
+
record.name_generation = record.generation;
|
|
535
|
+
record.name_operation_id = record.operation_id;
|
|
536
|
+
record.rename_pending = true;
|
|
537
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
|
|
538
|
+
try {
|
|
539
|
+
this.saveRecord(record);
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
delete this.doc.requests[request.scoped];
|
|
543
|
+
throw error;
|
|
544
|
+
}
|
|
545
|
+
try {
|
|
546
|
+
await this.adapter.rename(record);
|
|
547
|
+
record.rename_pending = false;
|
|
548
|
+
this.saveRecord(record);
|
|
549
|
+
}
|
|
550
|
+
catch { /* Durable intent is retried by get/restart; no runtime is created. */ }
|
|
551
|
+
return this.lookup(principal, id);
|
|
552
|
+
}));
|
|
553
|
+
}
|
|
554
|
+
async closeSession(principal, id, generation, key) {
|
|
555
|
+
return this.mutate(principal, key, ['close', id, generation], () => this.lifecycle.run(id, async () => {
|
|
556
|
+
const request = this.request(principal, key, ['close', id, generation]);
|
|
557
|
+
if (request.previous)
|
|
558
|
+
return this.replayRequest(principal, id, request.previous);
|
|
559
|
+
this.closingOperations.add(id);
|
|
560
|
+
try {
|
|
561
|
+
if (!this.adapter.prepareClose || !this.adapter.closeRuntime || !this.adapter.runtimePresent)
|
|
562
|
+
integrationError('SESSION_CLOSE_UNSUPPORTED', 'Runtime adapter does not support closing', 409);
|
|
563
|
+
const record = await this.get(principal, id);
|
|
564
|
+
assertReleaseGeneration(record, generation);
|
|
565
|
+
if (record.close_intent || record.rename_pending)
|
|
566
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Read session get to reconcile prior management intent', 409);
|
|
567
|
+
this.assertManagementIdle(record);
|
|
568
|
+
if (record.state === 'closed') {
|
|
569
|
+
if (await this.adapter.runtimePresent(record))
|
|
570
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Unexpected runtime exists for a closed record', 409);
|
|
571
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
|
|
572
|
+
try {
|
|
573
|
+
this.save();
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
delete this.doc.requests[request.scoped];
|
|
577
|
+
throw error;
|
|
578
|
+
}
|
|
579
|
+
return record;
|
|
580
|
+
}
|
|
581
|
+
if (this.adapter.pendingMessages?.(id))
|
|
582
|
+
integrationError('SESSION_PENDING_MESSAGES', 'Pending messages must be resolved before closing', 409);
|
|
583
|
+
const target = await this.adapter.prepareClose(record);
|
|
584
|
+
record.operation_id = randomUUID();
|
|
585
|
+
record.generation = generation + 1;
|
|
586
|
+
record.state = 'closing';
|
|
587
|
+
record.close_intent = { operation_id: record.operation_id, target };
|
|
588
|
+
record.error_code = null;
|
|
589
|
+
record.operation_uncertain = false;
|
|
590
|
+
record.updated_at = Date.now();
|
|
591
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
|
|
592
|
+
try {
|
|
593
|
+
this.saveRecord(record);
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
delete this.doc.requests[request.scoped];
|
|
597
|
+
throw error;
|
|
598
|
+
}
|
|
599
|
+
try {
|
|
600
|
+
await this.adapter.closeRuntime(record, false);
|
|
601
|
+
if (await this.adapter.runtimePresent(record))
|
|
602
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Runtime removal cannot be confirmed', 409);
|
|
603
|
+
record.state = 'closed';
|
|
604
|
+
record.runtime_present = false;
|
|
605
|
+
record.terminal_binding = null;
|
|
606
|
+
record.startup_input = undefined;
|
|
607
|
+
record.close_intent = undefined;
|
|
608
|
+
}
|
|
609
|
+
catch (error) {
|
|
610
|
+
record.state = 'failed';
|
|
611
|
+
record.operation_uncertain = true;
|
|
612
|
+
record.runtime_present = null;
|
|
613
|
+
record.error_code = typeof error.code === 'string' ? error.code : 'SESSION_CLOSE_FAILED';
|
|
614
|
+
}
|
|
615
|
+
record.updated_at = Date.now();
|
|
616
|
+
this.saveRecord(record);
|
|
617
|
+
return record;
|
|
618
|
+
}
|
|
619
|
+
finally {
|
|
620
|
+
this.closingOperations.delete(id);
|
|
621
|
+
}
|
|
622
|
+
}));
|
|
623
|
+
}
|
|
484
624
|
async release(principal, id, generation, key) {
|
|
485
625
|
return this.mutate(principal, key, ['release', id, generation], () => this.lifecycle.run(id, async () => {
|
|
486
626
|
const request = this.request(principal, key, ['release', id, generation]);
|
|
487
627
|
if (request.previous)
|
|
488
628
|
return this.replayRequest(principal, id, request.previous);
|
|
629
|
+
this.assertManagementSettled(this.lookup(principal, id));
|
|
489
630
|
if (!this.adapter.release || !this.adapter.runtimePresent)
|
|
490
631
|
integrationError('SESSION_RELEASE_UNSUPPORTED', 'Runtime adapter does not support release', 409);
|
|
491
632
|
if (this.pending.has(id))
|
|
492
633
|
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Launch is in progress', 409);
|
|
493
634
|
const record = await this.get(principal, id);
|
|
635
|
+
this.assertManagementSettled(record);
|
|
494
636
|
assertReleaseGeneration(record, generation);
|
|
495
637
|
if (record.state !== 'released') {
|
|
496
638
|
assertReleaseFacts(record, await this.adapter.inspect(record));
|
|
@@ -552,6 +694,52 @@ export class IntegrationSessions {
|
|
|
552
694
|
const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
|
|
553
695
|
if (!record)
|
|
554
696
|
return;
|
|
697
|
+
if (record.rename_pending) {
|
|
698
|
+
if (this.lifecycle.active(id))
|
|
699
|
+
return;
|
|
700
|
+
try {
|
|
701
|
+
await this.lifecycle.run(id, async () => {
|
|
702
|
+
if (!this.adapter.rename)
|
|
703
|
+
return;
|
|
704
|
+
await this.adapter.rename(record);
|
|
705
|
+
if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
|
|
706
|
+
return;
|
|
707
|
+
record.rename_pending = false;
|
|
708
|
+
this.saveRecord(record);
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
catch { /* Keep the persisted intent for a later get/restart. */ }
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (record.state === 'closed' || record.state === 'closing' || record.close_intent) {
|
|
715
|
+
if (this.lifecycle.active(id))
|
|
716
|
+
return;
|
|
717
|
+
try {
|
|
718
|
+
if (record.state !== 'closed') {
|
|
719
|
+
if (!this.adapter.closeRuntime)
|
|
720
|
+
integrationError('SESSION_CLOSE_UNSUPPORTED', 'Close reconciliation is unavailable', 409);
|
|
721
|
+
await this.adapter.closeRuntime(record, true);
|
|
722
|
+
}
|
|
723
|
+
if (!this.adapter.runtimePresent || await this.adapter.runtimePresent(record))
|
|
724
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Runtime exists for a closed or closing session', 409);
|
|
725
|
+
const next = { ...record, state: 'closed', runtime_present: false, terminal_binding: null, close_intent: undefined,
|
|
726
|
+
operation_uncertain: false, error_code: null, startup_input: undefined };
|
|
727
|
+
if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
|
|
728
|
+
return;
|
|
729
|
+
if (JSON.stringify(next) !== JSON.stringify(record))
|
|
730
|
+
this.saveRecord({ ...next, updated_at: Date.now() });
|
|
731
|
+
}
|
|
732
|
+
catch (error) {
|
|
733
|
+
if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
|
|
734
|
+
return;
|
|
735
|
+
// Keep closed terminal, and keep unresolved intent fenced. Never kill on refresh.
|
|
736
|
+
const next = { ...record, runtime_present: null, operation_uncertain: true,
|
|
737
|
+
error_code: typeof error.code === 'string' ? error.code : 'OPERATION_OUTCOME_UNCONFIRMED' };
|
|
738
|
+
if (JSON.stringify(next) !== JSON.stringify(record))
|
|
739
|
+
this.saveRecord({ ...next, updated_at: Date.now() });
|
|
740
|
+
}
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
555
743
|
if (record.cold_retry_pending && (this.lifecycle.active(id) || this.pending.has(id)))
|
|
556
744
|
return;
|
|
557
745
|
if (record.state === 'released' || record.state === 'releasing' || record.operation_uncertain) {
|
|
@@ -688,6 +876,12 @@ export class IntegrationSessions {
|
|
|
688
876
|
const existing = this.doc.sessions.find(record => record.session_id === id);
|
|
689
877
|
if (!existing)
|
|
690
878
|
return null;
|
|
879
|
+
if (this.closingOperations.has(id))
|
|
880
|
+
return { allowed: false, reason: 'SESSION_OPERATION_IN_PROGRESS' };
|
|
881
|
+
if (existing.state === 'closed')
|
|
882
|
+
return { allowed: false, reason: 'SESSION_CLOSED' };
|
|
883
|
+
if (existing.close_intent || existing.state === 'closing')
|
|
884
|
+
return { allowed: false, reason: 'SESSION_OPERATION_IN_PROGRESS' };
|
|
691
885
|
if (existing.state === 'released' || existing.state === 'releasing' || existing.operation_uncertain)
|
|
692
886
|
return { allowed: false, reason: existing.state === 'released' ? 'SESSION_RELEASED' : 'SESSION_OPERATION_IN_PROGRESS' };
|
|
693
887
|
if (!existing.startup_input)
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
4
4
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
5
5
|
export const INTEGRATION_PROTOCOL = 1;
|
|
6
|
-
export const INTEGRATION_OPERATIONS = ['message.send', 'message.read', 'task.read', 'task.create', 'task.configure', 'task.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore', 'session.release'];
|
|
6
|
+
export const INTEGRATION_OPERATIONS = ['message.send', 'message.read', 'task.read', 'task.create', 'task.configure', 'task.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore', 'session.release', 'session.rename', 'session.close'];
|
|
7
7
|
export function integrationError(code, message, status = 400) { throw new CollaborationError(code, message, status); }
|
|
8
8
|
export function atomicJson(file, value) {
|
|
9
9
|
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
@@ -50,6 +50,10 @@ export function assertReleaseFacts(record, facts) {
|
|
|
50
50
|
export function assertMessageRuntime(record) {
|
|
51
51
|
if (!record)
|
|
52
52
|
return;
|
|
53
|
+
if (record.state === 'closed')
|
|
54
|
+
fail('SESSION_CLOSED', 'Session was explicitly closed; it cannot be sent to or restored');
|
|
55
|
+
if (record.state === 'closing')
|
|
56
|
+
fail('SESSION_OPERATION_IN_PROGRESS', 'Session close is in progress');
|
|
53
57
|
if (record.state === 'released')
|
|
54
58
|
fail('SESSION_RELEASED', 'Runtime was released; explicitly restore this session before sending');
|
|
55
59
|
if (record.state === 'releasing')
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
3
|
+
import { promisify } from 'node:util';
|
|
4
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
5
|
+
const fail = (code, message) => { throw new CollaborationError(code, message, 409); };
|
|
6
|
+
/** Read the live foreground process instance, not a historical PID. */
|
|
7
|
+
export async function foregroundProcessInstance(pid) {
|
|
8
|
+
if (!Number.isSafeInteger(pid) || pid < 1)
|
|
9
|
+
return fail('SESSION_CLOSE_TARGET_UNCONFIRMED', 'Foreground process is unavailable');
|
|
10
|
+
try {
|
|
11
|
+
if (process.platform === 'linux') {
|
|
12
|
+
const stat = await fs.readFile(`/proc/${pid}/stat`, 'utf8');
|
|
13
|
+
const fields = stat.slice(stat.lastIndexOf(')') + 2).trim().split(/\s+/);
|
|
14
|
+
if (fields[0] === 'Z' || Number(fields[2]) <= 0 || fields[2] !== fields[5] || !/^\d+$/.test(fields[19]))
|
|
15
|
+
throw new Error();
|
|
16
|
+
return `linux:${fields[19]}`;
|
|
17
|
+
}
|
|
18
|
+
if (process.platform === 'darwin') {
|
|
19
|
+
const { stdout } = await promisify(execFile)('ps', ['-p', String(pid), '-o', 'pgid=,tpgid=,stat=,lstart='], { timeout: 2000 });
|
|
20
|
+
const match = stdout.trim().match(/^(\d+)\s+(\d+)\s+(\S+)\s+(.+)$/);
|
|
21
|
+
if (!match || Number(match[1]) < 1 || match[1] !== match[2] || match[3].startsWith('Z'))
|
|
22
|
+
throw new Error();
|
|
23
|
+
return `darwin:${match[4]}`;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch { /* Never fall back to PID existence. */ }
|
|
27
|
+
return fail('SESSION_CLOSE_TARGET_UNCONFIRMED', 'Current foreground process instance cannot be verified');
|
|
28
|
+
}
|
|
29
|
+
/** A missing PID is absence; inaccessible identity remains an error. */
|
|
30
|
+
export async function processStartIdentity(pid) {
|
|
31
|
+
if (process.platform === 'linux') {
|
|
32
|
+
try {
|
|
33
|
+
const stat = await fs.readFile(`/proc/${pid}/stat`, 'utf8');
|
|
34
|
+
const fields = stat.slice(stat.lastIndexOf(')') + 2).trim().split(/\s+/);
|
|
35
|
+
if (fields[0] === 'Z')
|
|
36
|
+
return null;
|
|
37
|
+
if (!/^\d+$/.test(fields[19]))
|
|
38
|
+
throw new Error();
|
|
39
|
+
return `linux:${fields[19]}`;
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
if (error.code === 'ENOENT')
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
else if (process.platform === 'darwin') {
|
|
47
|
+
try {
|
|
48
|
+
const { stdout } = await promisify(execFile)('ps', ['-p', String(pid), '-o', 'stat=,lstart='], { timeout: 2000 });
|
|
49
|
+
const match = stdout.trim().match(/^(\S+)\s+(.+)$/);
|
|
50
|
+
if (match)
|
|
51
|
+
return match[1].startsWith('Z') ? null : `darwin:${match[2]}`;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
try {
|
|
55
|
+
process.kill(pid, 0);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.code === 'ESRCH')
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return fail('SESSION_CLOSE_TARGET_UNCONFIRMED', 'Process disappearance cannot be verified');
|
|
64
|
+
}
|
|
65
|
+
/** Restart reconciliation never issues termination. External tmux/process
|
|
66
|
+
* changes are outside the service lock; final fresh checks reduce, not erase,
|
|
67
|
+
* that race. The exact tmux session ID is used for teardown. */
|
|
68
|
+
export async function closeSessionRuntime(adapter, target, reconcile) {
|
|
69
|
+
await adapter.barrier(async () => {
|
|
70
|
+
if (adapter.pendingMessages())
|
|
71
|
+
fail('SESSION_PENDING_MESSAGES', 'Pending messages must be resolved before closing');
|
|
72
|
+
if (await adapter.exists()) {
|
|
73
|
+
if (reconcile)
|
|
74
|
+
fail('OPERATION_OUTCOME_UNCONFIRMED', 'Runtime still exists; reconciliation will not terminate a process');
|
|
75
|
+
if (!target || JSON.stringify(await adapter.observe()) !== JSON.stringify(target))
|
|
76
|
+
fail('SESSION_CLOSE_TARGET_CHANGED', 'Runtime or process instance changed after close intent');
|
|
77
|
+
await adapter.destroy(target);
|
|
78
|
+
const deadline = Date.now() + 2000;
|
|
79
|
+
// Observe actual disappearance; do not issue repeated termination.
|
|
80
|
+
while (await adapter.exists() && Date.now() < deadline)
|
|
81
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
82
|
+
}
|
|
83
|
+
if (await adapter.exists())
|
|
84
|
+
fail('OPERATION_OUTCOME_UNCONFIRMED', 'Runtime absence cannot be confirmed');
|
|
85
|
+
await adapter.forgetRuntime();
|
|
86
|
+
});
|
|
87
|
+
}
|
|
@@ -2,6 +2,7 @@ import { collaborationSessionDisplayName } from '../utils/sessionDisplayName.js'
|
|
|
2
2
|
import { integrationError } from '../agent/integrationStore.js';
|
|
3
3
|
import { assertNativeResumeAvailable, collectNativeResumeOwnerCandidates } from '../agent/nativeResumeOwner.js';
|
|
4
4
|
import { readNativeProcessIdentity } from '../agent/nativeProcessIdentity.js';
|
|
5
|
+
import { closeSessionRuntime, foregroundProcessInstance, processStartIdentity } from '../agent/sessionManagement.js';
|
|
5
6
|
import { releaseSessionRuntime } from '../agent/sessionLifecycle.js';
|
|
6
7
|
import { resumeConfigurationFingerprint } from '../agent/sessionResumeConfiguration.js';
|
|
7
8
|
import { integrationLaunchCommand } from '../agent/integrationSessions.js';
|
|
@@ -629,7 +630,7 @@ function schedulePersistGlobalState() {
|
|
|
629
630
|
}
|
|
630
631
|
}, 200);
|
|
631
632
|
}
|
|
632
|
-
async function persistGlobalStateNow() {
|
|
633
|
+
async function persistGlobalStateNow(strict = false) {
|
|
633
634
|
if (persistGlobalStateTimer) {
|
|
634
635
|
clearTimeout(persistGlobalStateTimer);
|
|
635
636
|
persistGlobalStateTimer = null;
|
|
@@ -639,6 +640,8 @@ async function persistGlobalStateNow() {
|
|
|
639
640
|
}
|
|
640
641
|
catch (error) {
|
|
641
642
|
console.warn('[session-persist] Failed to persist global state:', getErrorMessage(error));
|
|
643
|
+
if (strict)
|
|
644
|
+
throw error;
|
|
642
645
|
}
|
|
643
646
|
}
|
|
644
647
|
// 进程退出前立即刷盘,避免 tsx watch 重启导致状态丢失
|
|
@@ -1980,10 +1983,129 @@ async function integrationTmuxPresent(record) {
|
|
|
1980
1983
|
throw error;
|
|
1981
1984
|
}
|
|
1982
1985
|
}
|
|
1986
|
+
async function verifyIntegrationRuntimeOwnership(record) {
|
|
1987
|
+
await integrationSessionAdapter.prepareRelease(record);
|
|
1988
|
+
const terminal = integrationTerminalRecord(record);
|
|
1989
|
+
if (terminal && globalSessionState.sessions.some(other => other.sessionId !== record.session_id
|
|
1990
|
+
&& (other.tmuxSessionName === terminal.tmuxSessionName || terminal.backendSessionId && other.backendSessionId === terminal.backendSessionId))) {
|
|
1991
|
+
integrationError('SESSION_IDENTITY_MISMATCH', 'Runtime has another logical owner', 409);
|
|
1992
|
+
}
|
|
1993
|
+
}
|
|
1994
|
+
async function forgetIntegrationRuntime(record) {
|
|
1995
|
+
if (await integrationTmuxPresent(record))
|
|
1996
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Owned tmux still exists', 409);
|
|
1997
|
+
await verifyIntegrationRuntimeOwnership(record);
|
|
1998
|
+
for (const [id, backend] of terminalSessions)
|
|
1999
|
+
if (backend.mode === 'tmux' && backend.tmuxSessionName === `wt-integration-${record.session_id}`)
|
|
2000
|
+
cleanupSession(id, { killProcess: true });
|
|
2001
|
+
removeGlobalSessionRecord(record.session_id);
|
|
2002
|
+
collaborationStore.archiveSession(record.session_id);
|
|
2003
|
+
collaborationRouting.remove(record.session_id);
|
|
2004
|
+
await persistGlobalStateNow(true);
|
|
2005
|
+
broadcastClientState();
|
|
2006
|
+
}
|
|
2007
|
+
async function observeIntegrationCloseTarget(record) {
|
|
2008
|
+
await verifyIntegrationRuntimeOwnership(record);
|
|
2009
|
+
const terminal = integrationTerminalRecord(record);
|
|
2010
|
+
if (!terminal || !record.terminal_binding)
|
|
2011
|
+
integrationError('SESSION_CLOSE_TARGET_UNCONFIRMED', 'Original runtime binding is unavailable', 409);
|
|
2012
|
+
const inspected = await inspectCollaborationTmux({ sessionId: record.session_id, backendSessionId: terminal.backendSessionId,
|
|
2013
|
+
mode: 'tmux', tmuxSessionName: terminal.tmuxSessionName, agentSlug: record.agent_slug,
|
|
2014
|
+
nativeSessionId: record.agent_native_session_id, pane: record.terminal_binding }, undefined, false, true);
|
|
2015
|
+
if (inspected.state !== 'ready' || !inspected.pane)
|
|
2016
|
+
integrationError('SESSION_CLOSE_TARGET_UNCONFIRMED', 'Original pinned pane cannot be verified', 409);
|
|
2017
|
+
const layout = await getTmuxLayout(terminal.tmuxSessionName);
|
|
2018
|
+
const panes = layout.windows.flatMap(window => window.panes);
|
|
2019
|
+
if (panes.length !== 1 || layout.activePaneId !== record.terminal_binding.paneId)
|
|
2020
|
+
integrationError('SESSION_IDENTITY_MISMATCH', 'Only the single original owned pane can be closed', 409);
|
|
2021
|
+
const pane = panes[0];
|
|
2022
|
+
const paneOwners = (await runTmux(['list-panes', '-a', '-F', '#{session_id} #{pane_id}'])).trim().split('\n');
|
|
2023
|
+
if (paneOwners.some(line => { const [owner, id] = line.trim().split(/\s+/); return id === pane.id && owner !== record.terminal_binding.sessionId; }))
|
|
2024
|
+
integrationError('SESSION_SCOPE_CONFLICT', 'Owned pane is linked into another tmux session', 409);
|
|
2025
|
+
if (process.platform === 'linux')
|
|
2026
|
+
await getProcessSnapshot(true);
|
|
2027
|
+
const program = await resolveTmuxPaneProgram(pane, true, true);
|
|
2028
|
+
const agent = detectAgentFromCommand(program?.rawArgs ?? program?.command ?? '', agentCustomCommands());
|
|
2029
|
+
let kind, pid, nativeId = null;
|
|
2030
|
+
if (agent) {
|
|
2031
|
+
if (!record.agent_native_session_id || agent.slug !== record.agent_slug || program?.source !== 'tmux-tty' || !program.processPid) {
|
|
2032
|
+
integrationError('SESSION_CLOSE_TARGET_UNCONFIRMED', 'A running Agent requires a live exact native identity', 409);
|
|
2033
|
+
}
|
|
2034
|
+
const identity = await observeProgramNativeIdentity(agent, program);
|
|
2035
|
+
if (identity.failure || identity.nativeId !== record.agent_native_session_id)
|
|
2036
|
+
integrationError('SESSION_IDENTITY_MISMATCH', 'Current Agent native identity differs from the owned record', 409);
|
|
2037
|
+
kind = 'agent';
|
|
2038
|
+
pid = program.processPid;
|
|
2039
|
+
nativeId = identity.nativeId;
|
|
2040
|
+
}
|
|
2041
|
+
else {
|
|
2042
|
+
if (!await integrationPaneIsIdleShell(inspected.pane))
|
|
2043
|
+
integrationError('SESSION_CLOSE_TARGET_UNCONFIRMED', 'Current runtime is not a verified foreground shell', 409);
|
|
2044
|
+
kind = 'shell';
|
|
2045
|
+
pid = pane.pid;
|
|
2046
|
+
}
|
|
2047
|
+
const serverStart = await processStartIdentity(record.terminal_binding.serverPid);
|
|
2048
|
+
if (!serverStart)
|
|
2049
|
+
integrationError('SESSION_CLOSE_TARGET_UNCONFIRMED', 'Current tmux server instance is unavailable', 409);
|
|
2050
|
+
return { binding: { ...record.terminal_binding }, tmux_server_start_id: serverStart, kind, process_pid: pid,
|
|
2051
|
+
process_start_id: await foregroundProcessInstance(pid), native_id: nativeId };
|
|
2052
|
+
}
|
|
1983
2053
|
export const integrationSessionAdapter = {
|
|
1984
2054
|
subscribe(listener) { integrationSessionObservers.add(listener); return () => { integrationSessionObservers.delete(listener); }; },
|
|
1985
2055
|
registerDeliveryGuard(guard) { integrationDeliveryGuard = guard; return () => { if (integrationDeliveryGuard === guard)
|
|
1986
2056
|
integrationDeliveryGuard = undefined; }; },
|
|
2057
|
+
async prepareRename(record) {
|
|
2058
|
+
await verifyIntegrationRuntimeOwnership(record);
|
|
2059
|
+
if (!integrationTerminalRecord(record) && await integrationTmuxPresent(record))
|
|
2060
|
+
integrationError('SESSION_IDENTITY_MISMATCH', 'Live tmux lacks its owned terminal registration', 409);
|
|
2061
|
+
},
|
|
2062
|
+
async rename(record) {
|
|
2063
|
+
await integrationSessionAdapter.prepareRename(record);
|
|
2064
|
+
const terminal = integrationTerminalRecord(record);
|
|
2065
|
+
if (!terminal)
|
|
2066
|
+
return; // Metadata survives released/closed; never recreate a runtime.
|
|
2067
|
+
if (!record.name)
|
|
2068
|
+
integrationError('INVALID_NAME', 'Missing persistent display name');
|
|
2069
|
+
if (await integrationTmuxPresent(record))
|
|
2070
|
+
await setTmuxOption(terminal.tmuxSessionName, '@termdock-friendly-name', record.name);
|
|
2071
|
+
upsertGlobalSessionRecord({ ...terminal, name: record.name, customName: true, autoTitle: null });
|
|
2072
|
+
await persistGlobalStateNow(true);
|
|
2073
|
+
await getSessionInventorySnapshot({ refresh: true });
|
|
2074
|
+
broadcastClientState();
|
|
2075
|
+
},
|
|
2076
|
+
async prepareClose(record) {
|
|
2077
|
+
if (collaborationStore.pendingCount(record.session_id))
|
|
2078
|
+
integrationError('SESSION_PENDING_MESSAGES', 'Pending messages must be resolved before closing', 409);
|
|
2079
|
+
await verifyIntegrationRuntimeOwnership(record);
|
|
2080
|
+
return await integrationTmuxPresent(record) ? observeIntegrationCloseTarget(record) : null;
|
|
2081
|
+
},
|
|
2082
|
+
async closeRuntime(record, reconcile) {
|
|
2083
|
+
await closeSessionRuntime({
|
|
2084
|
+
barrier: action => collaborationDeliveryWorker.reconfigure(record.session_id, action),
|
|
2085
|
+
async exists() {
|
|
2086
|
+
if (await integrationTmuxPresent(record))
|
|
2087
|
+
return true;
|
|
2088
|
+
const target = record.close_intent?.target;
|
|
2089
|
+
return !!target && await processStartIdentity(target.process_pid) === target.process_start_id;
|
|
2090
|
+
},
|
|
2091
|
+
pendingMessages: () => collaborationStore.pendingCount(record.session_id),
|
|
2092
|
+
observe: () => observeIntegrationCloseTarget(record),
|
|
2093
|
+
async destroy(target) {
|
|
2094
|
+
// Kill by immutable tmux session ID, not by a potentially reused name.
|
|
2095
|
+
// No process-group kill or application prompt is sent.
|
|
2096
|
+
if (JSON.stringify(await observeIntegrationCloseTarget(record)) !== JSON.stringify(target))
|
|
2097
|
+
integrationError('SESSION_CLOSE_TARGET_CHANGED', 'Close target changed before teardown', 409);
|
|
2098
|
+
intentionallyDeletingTmuxSessions.add(`wt-integration-${record.session_id}`);
|
|
2099
|
+
try {
|
|
2100
|
+
await runTmux(['kill-session', '-t', target.binding.sessionId]);
|
|
2101
|
+
}
|
|
2102
|
+
finally {
|
|
2103
|
+
intentionallyDeletingTmuxSessions.delete(`wt-integration-${record.session_id}`);
|
|
2104
|
+
}
|
|
2105
|
+
},
|
|
2106
|
+
forgetRuntime: () => forgetIntegrationRuntime(record),
|
|
2107
|
+
}, record.close_intent?.target ?? null, reconcile);
|
|
2108
|
+
},
|
|
1987
2109
|
pendingMessages(id) { return collaborationStore.pendingCount(id); },
|
|
1988
2110
|
messageHistory(id) { return collaborationStore.hasTerminalMessageHistory(id); },
|
|
1989
2111
|
async prepareRelease(record) {
|
|
@@ -2186,7 +2308,7 @@ async function launchIntegrationTerminal(record, restoring, prepared, progress,
|
|
|
2186
2308
|
}
|
|
2187
2309
|
const opened = await openInventorySession({}, { preferredFrontendSessionId: record.session_id,
|
|
2188
2310
|
mode: 'tmux', tmuxSessionName: `wt-integration-${record.session_id}`, cwd: record.cwd,
|
|
2189
|
-
name: `${record.agent_slug} · ${group.name}`, customName: true });
|
|
2311
|
+
name: record.name ?? `${record.agent_slug} · ${group.name}`, customName: true });
|
|
2190
2312
|
if (opened.session.sessionId !== record.session_id)
|
|
2191
2313
|
integrationError('SESSION_IDENTITY_MISMATCH', 'Terminal id changed', 409);
|
|
2192
2314
|
const terminal = integrationTerminalRecord(record);
|