termdock 1.4.178 → 1.4.180
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/.desktop-dist/main.js +1 -1
- package/dist/client/assets/App-DBds0RoW.js +150 -0
- package/dist/client/assets/App-DBds0RoW.js.br +0 -0
- package/dist/client/assets/App-DBds0RoW.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-BNWqAnqc.js → ChangeWalkthroughPanel-DonkfMV2.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-DonkfMV2.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DonkfMV2.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-IlTGwr-I.js → DagPlayground-CYL8vhaU.js} +1 -1
- package/dist/client/assets/DagPlayground-CYL8vhaU.js.br +0 -0
- package/dist/client/assets/DagPlayground-CYL8vhaU.js.gz +0 -0
- package/dist/client/assets/{DiffLab-CwhNpJvo.js → DiffLab-COUjebir.js} +1 -1
- package/dist/client/assets/DiffLab-COUjebir.js.br +0 -0
- package/dist/client/assets/DiffLab-COUjebir.js.gz +0 -0
- package/dist/client/assets/{DiffReview-DyhINv0F.js → DiffReview-DqiRXomQ.js} +1 -1
- package/dist/client/assets/DiffReview-DqiRXomQ.js.br +0 -0
- package/dist/client/assets/DiffReview-DqiRXomQ.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-D9Kt5VwB.js → DiffReviewLab-Aqib8Qr6.js} +1 -1
- package/dist/client/assets/DiffReviewLab-Aqib8Qr6.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-Aqib8Qr6.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-3zz2PI-8.js → DiffViewer-KR2GhX72.js} +1 -1
- package/dist/client/assets/DiffViewer-KR2GhX72.js.br +0 -0
- package/dist/client/assets/DiffViewer-KR2GhX72.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-CN8xsiGC.js → ModelPreview-Bo6giTPm.js} +1 -1
- package/dist/client/assets/ModelPreview-Bo6giTPm.js.br +0 -0
- package/dist/client/assets/ModelPreview-Bo6giTPm.js.gz +0 -0
- package/dist/client/assets/RightSidebar-rROjRuPY.js +1678 -0
- package/dist/client/assets/RightSidebar-rROjRuPY.js.br +0 -0
- package/dist/client/assets/RightSidebar-rROjRuPY.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-DkdoJ2ZH.js → abnfDiagram-VCTEODGH-m85vHRkF.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-m85vHRkF.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-m85vHRkF.js.gz +0 -0
- package/dist/client/assets/app-Djvvt1Mi.js +34 -0
- package/dist/client/assets/app-Djvvt1Mi.js.br +0 -0
- package/dist/client/assets/app-Djvvt1Mi.js.gz +0 -0
- package/dist/client/assets/{arc-ctzz5PcK.js → arc-DmZ3EVhj.js} +1 -1
- package/dist/client/assets/arc-DmZ3EVhj.js.br +0 -0
- package/dist/client/assets/arc-DmZ3EVhj.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-DNLcYfGQ.js → architectureDiagram-5GKGNRK7-C8yGaNGM.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C8yGaNGM.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C8yGaNGM.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-Coye1ou5.js → blockDiagram-I7D4REHJ-CZhh__cR.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-CZhh__cR.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CZhh__cR.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DTa8jyPp.js → c4Diagram-7LVT6UL2-py7O2OOO.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-py7O2OOO.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-py7O2OOO.js.gz +0 -0
- package/dist/client/assets/channel-BnKOV34C.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BpDyjNfs.js → chunk-2Q5K7J3B-SXGoQcYW.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-DgwdqXKl.js → chunk-5VM5RSS4-1ZVyPSGv.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DWf2gZ96.js → chunk-F27PBJKO-DkIqnjYj.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-DkIqnjYj.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DkIqnjYj.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-l42o_E2c.js → chunk-IMKFNOWR-CLS_EByq.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-CLS_EByq.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CLS_EByq.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-8CkEf05l.js → chunk-JWPE2WC7-8LtK4UYW.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-BKKOfbdg.js → chunk-POPQ4Y6H-D1PhOQEv.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-RORrf2aM.js → chunk-SVP7TREG-cVWIfR_4.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-cVWIfR_4.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-cVWIfR_4.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-hPqafEtC.js → chunk-TICWLB2K-BiO8AIZ7.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-BiO8AIZ7.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BiO8AIZ7.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-CrvjJwms.js → chunk-XXDRQBXY-CwP0rAj8.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-CZ5ElqWa.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CZ5ElqWa.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CwRSiQLa.js → cose-bilkent-JH36ORCC-Dv9GK27H.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Dv9GK27H.js.br +0 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CwRSiQLa.js.gz → cose-bilkent-JH36ORCC-Dv9GK27H.js.gz} +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-CXnppZ8I.js → cynefin-OW5HDTMX-DDI6luT6.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-DDI6luT6.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DDI6luT6.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BSDrHSIA.js → cynefinDiagram-5FMLGOSQ-DQpaBQcE.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DQpaBQcE.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DQpaBQcE.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-d-5j4vkq.js → dagre-GXQ25YYZ-CZ-1rzmK.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-CZ-1rzmK.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CZ-1rzmK.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-DizMBpGW.js → diagram-S7CK7UJ4-DeYvGv1c.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DeYvGv1c.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DeYvGv1c.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-CNQK26rH.js → diagram-UQ7AKVKN-BaYQC1t6.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-BaYQC1t6.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BaYQC1t6.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-Dua-2_46.js → diagram-VSXAHHWV-B517BnK0.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-B517BnK0.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-B517BnK0.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-CIq-4729.js → diagram-VX7I27RA-DyjVlDcv.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-DyjVlDcv.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DyjVlDcv.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-BukQ0u-x.js → diagram-Z3DM3KII-Cy9JXHOz.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-Cy9JXHOz.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-Cy9JXHOz.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-D2zRkRBI.js → ebnfDiagram-PWID7BFC-xvEH045b.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-xvEH045b.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-xvEH045b.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DbX6A3ls.js → erDiagram-RLTQ6QDP-BTJDgwKq.js} +1 -1
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DbX6A3ls.js.br → erDiagram-RLTQ6QDP-BTJDgwKq.js.br} +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BTJDgwKq.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-YECDWA2G.js → flowDiagram-HODETNUW-6pq1SF-S.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-6pq1SF-S.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-6pq1SF-S.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-D3EJdUov.js → ganttDiagram-EL5Y4UJY-DS_l5v9m.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DS_l5v9m.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DS_l5v9m.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-D3IZpZS7.js → gestureArbiter-FtwbRpdp.js} +1 -1
- package/dist/client/assets/gestureArbiter-FtwbRpdp.js.br +0 -0
- package/dist/client/assets/gestureArbiter-FtwbRpdp.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-tr_HKoQK.js → gitGraphDiagram-WWUBYQGX-DEGFo3aa.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DEGFo3aa.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DEGFo3aa.js.gz +0 -0
- package/dist/client/assets/{index-C4Ny0NDQ.css → index-PWu1VEN_.css} +1 -1
- package/dist/client/assets/index-PWu1VEN_.css.br +0 -0
- package/dist/client/assets/index-PWu1VEN_.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-xBV63YYg.js → infoDiagram-27XIBGKW-CFVSyJdD.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BrYSZu1R.js → ishikawaDiagram-5VMMS53U-_YX0U_wu.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-_YX0U_wu.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-_YX0U_wu.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-C9Ft--aA.js → journeyDiagram-3NMN7TZE-DCujswss.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DCujswss.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DCujswss.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-FCxqSu11.js → kanban-definition-UXKFOSKX-DC6lqIBi.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-DC6lqIBi.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-DC6lqIBi.js.gz +0 -0
- package/dist/client/assets/{layout-BqjIyPqO.js → layout-D_hlL0MC.js} +1 -1
- package/dist/client/assets/layout-D_hlL0MC.js.br +0 -0
- package/dist/client/assets/layout-D_hlL0MC.js.gz +0 -0
- package/dist/client/assets/{linear-CEAGQX5N.js → linear-BxpjJ2yt.js} +1 -1
- package/dist/client/assets/linear-BxpjJ2yt.js.br +0 -0
- package/dist/client/assets/linear-BxpjJ2yt.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-DvAY78t_.js → mermaid.core-BRLYB1Uj.js} +5 -5
- package/dist/client/assets/mermaid.core-BRLYB1Uj.js.br +0 -0
- package/dist/client/assets/mermaid.core-BRLYB1Uj.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-D7_qIQ9H.js → mindmap-definition-YA3MSWOX-BF2f-Z54.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BF2f-Z54.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BF2f-Z54.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-Dksfdvk3.js → passwordBootstrap-hj2Lcgoj.js} +1 -1
- package/dist/client/assets/passwordBootstrap-hj2Lcgoj.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-hj2Lcgoj.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-DeTIBKGC.js → pegDiagram-XKGWAZYB-FxHPBhCy.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-FxHPBhCy.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-FxHPBhCy.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-z5IcoVwx.js → pieDiagram-E7YTZNPT-DqAcArGH.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-DqAcArGH.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DqAcArGH.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B-zCZvES.js → quadrantDiagram-AXDQQJYC-31lk30-Y.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B-zCZvES.js.br → quadrantDiagram-AXDQQJYC-31lk30-Y.js.br} +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-31lk30-Y.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-DbpeB7P2.js → railroadDiagram-O6MQD6OU-C-Jt17Wl.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-C-Jt17Wl.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-C-Jt17Wl.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-p56g9xFb.js → referenceLongPress-BPFSnCDQ.js} +1 -1
- package/dist/client/assets/referenceLongPress-BPFSnCDQ.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BPFSnCDQ.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-UhJYMJ_U.js → requirementDiagram-BXWQKSXE-DdrbsuCJ.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DdrbsuCJ.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DdrbsuCJ.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-xKsuFR17.js → sankeyDiagram-P5KCCOFB-CZjs9KFo.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZjs9KFo.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CZjs9KFo.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DrjBl65C.js → sequenceDiagram-WJ2MYXX4-CkD1f6v4.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CkD1f6v4.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CkD1f6v4.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-BIlGJ9tm.js → sizeCapture-INFHLROL-BMuKV0gL.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-BMuKV0gL.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BMuKV0gL.js.gz +0 -0
- package/dist/client/assets/{sparkles-B-uEi0Pa.js → sparkles-DuRvUCWG.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-C3k9XvA6.js → stateDiagram-D77RDMKH-B1lLo2WA.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-B1lLo2WA.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-B1lLo2WA.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-F5MbngH8.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-BN-x1rXp.js → swimlanes-42K2YHIH-CfC5CNjA.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-CfC5CNjA.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-CfC5CNjA.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BqbZwNvL.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-Dcedjtic.js → timeline-definition-24CTP7MA-0mM-Skdo.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-0mM-Skdo.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-0mM-Skdo.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-BSNAwyll.js → triangle-alert-wyGdnPOg.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-DAIk-5Rt.js → vennDiagram-4TSXK5OY-BYsJo_hU.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-BYsJo_hU.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BYsJo_hU.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-3iXOTpAg.js → wardleyDiagram-VM6X3IG4-ree3u-pI.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-ree3u-pI.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-ree3u-pI.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-OD1a_9Qg.js → xychartDiagram-S5SC5T6Z-DDono4Wc.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DDono4Wc.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DDono4Wc.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-downloads.js +40 -0
- package/dist/client/sw-downloads.js.br +0 -0
- package/dist/client/sw-downloads.js.gz +0 -0
- package/dist/client/sw-encrypted-background.js +25 -0
- package/dist/client/sw-encrypted-background.js.br +0 -0
- package/dist/client/sw-encrypted-background.js.gz +0 -0
- package/dist/client/sw-notifications.js +11 -43
- package/dist/client/sw-notifications.js.br +0 -0
- package/dist/client/sw-notifications.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/collaborationCli.js +10 -3
- package/dist/server/agent/collaborationDeliveryWorker.js +154 -0
- package/dist/server/agent/collaborationRoutes.js +15 -4
- package/dist/server/agent/collaborationRouting.js +94 -0
- package/dist/server/agent/collaborationStore.js +6 -1
- package/dist/server/agent/collaborationTmuxDelivery.js +10 -0
- package/dist/server/agent/sessionBindingRecovery.js +80 -0
- package/dist/server/cli.js +9 -5
- package/dist/server/entry.js +32 -13
- package/dist/server/federation/desktopTargets.js +64 -0
- package/dist/server/federation/directRoutes.js +27 -19
- package/dist/server/federation/requestContext.js +7 -1
- package/dist/server/federation/routeAccess.js +14 -7
- package/dist/server/federation/runtime.js +5 -3
- package/dist/server/federation/trustedCa.js +55 -0
- package/dist/server/notifications/pushService.js +3 -0
- package/dist/server/routes/notifications.js +4 -0
- package/dist/server/routes/terminal.js +235 -78
- package/dist/server/utils/publicSecurity.js +1 -1
- package/package.json +2 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-CAEtVL8Z.js +0 -150
- package/dist/client/assets/App-CAEtVL8Z.js.br +0 -0
- package/dist/client/assets/App-CAEtVL8Z.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BNWqAnqc.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BNWqAnqc.js.gz +0 -0
- package/dist/client/assets/DagPlayground-IlTGwr-I.js.br +0 -0
- package/dist/client/assets/DagPlayground-IlTGwr-I.js.gz +0 -0
- package/dist/client/assets/DiffLab-CwhNpJvo.js.br +0 -0
- package/dist/client/assets/DiffLab-CwhNpJvo.js.gz +0 -0
- package/dist/client/assets/DiffReview-DyhINv0F.js.br +0 -0
- package/dist/client/assets/DiffReview-DyhINv0F.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-D9Kt5VwB.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-D9Kt5VwB.js.gz +0 -0
- package/dist/client/assets/DiffViewer-3zz2PI-8.js.br +0 -0
- package/dist/client/assets/DiffViewer-3zz2PI-8.js.gz +0 -0
- package/dist/client/assets/ModelPreview-CN8xsiGC.js.br +0 -0
- package/dist/client/assets/ModelPreview-CN8xsiGC.js.gz +0 -0
- package/dist/client/assets/RightSidebar-DQk1cwKH.js +0 -79
- package/dist/client/assets/RightSidebar-DQk1cwKH.js.br +0 -0
- package/dist/client/assets/RightSidebar-DQk1cwKH.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DkdoJ2ZH.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DkdoJ2ZH.js.gz +0 -0
- package/dist/client/assets/app-B42CoF1r.js +0 -34
- package/dist/client/assets/app-B42CoF1r.js.br +0 -0
- package/dist/client/assets/app-B42CoF1r.js.gz +0 -0
- package/dist/client/assets/arc-ctzz5PcK.js.br +0 -0
- package/dist/client/assets/arc-ctzz5PcK.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DNLcYfGQ.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DNLcYfGQ.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Coye1ou5.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Coye1ou5.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DTa8jyPp.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DTa8jyPp.js.gz +0 -0
- package/dist/client/assets/channel-CzvSDv06.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DWf2gZ96.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DWf2gZ96.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-l42o_E2c.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-l42o_E2c.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-RORrf2aM.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-RORrf2aM.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-hPqafEtC.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-hPqafEtC.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-CDknJfE0.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CDknJfE0.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CwRSiQLa.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CXnppZ8I.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CXnppZ8I.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BSDrHSIA.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BSDrHSIA.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-d-5j4vkq.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-d-5j4vkq.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DizMBpGW.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DizMBpGW.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CNQK26rH.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CNQK26rH.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Dua-2_46.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Dua-2_46.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CIq-4729.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CIq-4729.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BukQ0u-x.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BukQ0u-x.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D2zRkRBI.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D2zRkRBI.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DbX6A3ls.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-YECDWA2G.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-YECDWA2G.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-D3EJdUov.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-D3EJdUov.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-D3IZpZS7.js.br +0 -0
- package/dist/client/assets/gestureArbiter-D3IZpZS7.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tr_HKoQK.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tr_HKoQK.js.gz +0 -0
- package/dist/client/assets/index-C4Ny0NDQ.css.br +0 -0
- package/dist/client/assets/index-C4Ny0NDQ.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BrYSZu1R.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BrYSZu1R.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-C9Ft--aA.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-C9Ft--aA.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-FCxqSu11.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-FCxqSu11.js.gz +0 -0
- package/dist/client/assets/layout-BqjIyPqO.js.br +0 -0
- package/dist/client/assets/layout-BqjIyPqO.js.gz +0 -0
- package/dist/client/assets/linear-CEAGQX5N.js.br +0 -0
- package/dist/client/assets/linear-CEAGQX5N.js.gz +0 -0
- package/dist/client/assets/mermaid.core-DvAY78t_.js.br +0 -0
- package/dist/client/assets/mermaid.core-DvAY78t_.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D7_qIQ9H.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D7_qIQ9H.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-Dksfdvk3.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-Dksfdvk3.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DeTIBKGC.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DeTIBKGC.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-z5IcoVwx.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-z5IcoVwx.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B-zCZvES.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DbpeB7P2.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DbpeB7P2.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-p56g9xFb.js.br +0 -0
- package/dist/client/assets/referenceLongPress-p56g9xFb.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-UhJYMJ_U.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-UhJYMJ_U.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-xKsuFR17.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-xKsuFR17.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DrjBl65C.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DrjBl65C.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BIlGJ9tm.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BIlGJ9tm.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-C3k9XvA6.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-C3k9XvA6.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B7GnCu2o.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BN-x1rXp.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BN-x1rXp.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BH_gm34Y.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-Dcedjtic.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-Dcedjtic.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DAIk-5Rt.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DAIk-5Rt.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-3iXOTpAg.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-3iXOTpAg.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-OD1a_9Qg.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-OD1a_9Qg.js.gz +0 -0
|
@@ -17,6 +17,7 @@ self.addEventListener('push', (event) => {
|
|
|
17
17
|
data: {
|
|
18
18
|
url: payload.url || '/',
|
|
19
19
|
sessionId: payload.sessionId,
|
|
20
|
+
targetPeerId: payload.targetPeerId,
|
|
20
21
|
},
|
|
21
22
|
};
|
|
22
23
|
event.waitUntil((async () => {
|
|
@@ -60,35 +61,13 @@ self.addEventListener('push', (event) => {
|
|
|
60
61
|
self.addEventListener('pushsubscriptionchange', (event) => {
|
|
61
62
|
event.waitUntil((async () => {
|
|
62
63
|
try {
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
const subscription = event.newSubscription || await self.registration.pushManager.getSubscription();
|
|
67
|
-
if (subscription) {
|
|
68
|
-
const tokenResponse = await fetch('/api/csrf-token', { credentials: 'same-origin' });
|
|
69
|
-
if (tokenResponse.ok) {
|
|
70
|
-
const { csrfToken } = await tokenResponse.json();
|
|
71
|
-
await fetch('/api/notifications/subscribe', {
|
|
72
|
-
method: 'POST',
|
|
73
|
-
credentials: 'same-origin',
|
|
74
|
-
headers: {
|
|
75
|
-
'Content-Type': 'application/json',
|
|
76
|
-
'X-XSRF-TOKEN': csrfToken,
|
|
77
|
-
},
|
|
78
|
-
body: JSON.stringify({
|
|
79
|
-
subscription: subscription.toJSON(),
|
|
80
|
-
aiEnabled: status.subscription?.aiEnabled !== false,
|
|
81
|
-
exitEnabled: status.subscription?.exitEnabled === true,
|
|
82
|
-
alertStyle: status.subscription?.alertStyle || 'normal',
|
|
83
|
-
locale: status.subscription?.locale || 'zh-CN',
|
|
84
|
-
}),
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
64
|
+
let subscription = event.newSubscription || await self.registration.pushManager.getSubscription();
|
|
65
|
+
if (!subscription && event.oldSubscription?.options) {
|
|
66
|
+
subscription = await self.registration.pushManager.subscribe(event.oldSubscription.options);
|
|
88
67
|
}
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
68
|
+
if (subscription) await self.termdockBackground?.renewSubscriptions(subscription.toJSON());
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn('Background encrypted subscription sync failed', error);
|
|
92
71
|
}
|
|
93
72
|
const windows = await self.clients.matchAll({ type: 'window', includeUncontrolled: true });
|
|
94
73
|
for (const client of windows) {
|
|
@@ -98,20 +77,8 @@ self.addEventListener('pushsubscriptionchange', (event) => {
|
|
|
98
77
|
});
|
|
99
78
|
|
|
100
79
|
async function reportNotificationClick(stage, traceId, data = {}) {
|
|
101
|
-
try {
|
|
102
|
-
|
|
103
|
-
method: 'POST',
|
|
104
|
-
credentials: 'same-origin',
|
|
105
|
-
headers: { 'Content-Type': 'application/json' },
|
|
106
|
-
body: JSON.stringify({
|
|
107
|
-
level: 'info',
|
|
108
|
-
message: `PWA_NOTIFICATION_CLICK ${stage}`,
|
|
109
|
-
data: { traceId, ts: Date.now(), ...data },
|
|
110
|
-
}),
|
|
111
|
-
});
|
|
112
|
-
} catch {
|
|
113
|
-
// Diagnostics must never interfere with notification navigation.
|
|
114
|
-
}
|
|
80
|
+
try { await self.termdockBackground?.reportClick(stage, traceId, data); }
|
|
81
|
+
catch { console.debug(`PWA_NOTIFICATION_CLICK ${stage}`, { traceId, ...data }); }
|
|
115
82
|
}
|
|
116
83
|
|
|
117
84
|
const NOTIFICATION_TARGET_CACHE = 'termdock-notification-target-v1';
|
|
@@ -176,6 +143,7 @@ self.addEventListener('notificationclick', (event) => {
|
|
|
176
143
|
|
|
177
144
|
event.waitUntil((async () => {
|
|
178
145
|
await reportNotificationClick('sw-click-start', traceId, {
|
|
146
|
+
targetPeerId: data.targetPeerId,
|
|
179
147
|
sessionId: data.sessionId || null,
|
|
180
148
|
url: data.url || null,
|
|
181
149
|
tag: event.notification.tag || null,
|
|
@@ -254,5 +222,5 @@ self.addEventListener('notificationclick', (event) => {
|
|
|
254
222
|
resultUrl: openedClient?.url || null,
|
|
255
223
|
});
|
|
256
224
|
}
|
|
257
|
-
})());
|
|
225
|
+
})().finally(() => self.termdockBackground?.flushLogs().catch(() => {})));
|
|
258
226
|
});
|
|
Binary file
|
|
Binary file
|
package/dist/client/sw.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
if(!self.define){let e,s={};const
|
|
1
|
+
if(!self.define){let e,s={};const n=(n,i)=>(n=new URL(n+".js",i).href,s[n]||new Promise(s=>{if("document"in self){const e=document.createElement("script");e.src=n,e.onload=s,document.head.appendChild(e)}else e=n,importScripts(n),s()}).then(()=>{let e=s[n];if(!e)throw new Error(`Module ${n} didn’t register its module`);return e}));self.define=(i,a)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let c={};const r=e=>n(e,o),t={module:{uri:o},exports:c,require:r};s[o]=Promise.all(i.map(e=>t[e]||r(e))).then(e=>(a(...e),c))}}define(["./workbox-9845bf53"],function(e){"use strict";importScripts("sw-encrypted-background.js","sw-notifications.js","sw-secure-api.js","sw-downloads.js"),self.addEventListener("message",e=>{e.data&&"SKIP_WAITING"===e.data.type&&self.skipWaiting()}),e.clientsClaim(),e.precacheAndRoute([{url:"index.html",revision:"1d87402f5f09e34eb3f563b011b5205f"},{url:"assets/app-Djvvt1Mi.js",revision:null},{url:"assets/react-XDCjal2T.js",revision:null},{url:"assets/dnd-BCFAPC0L.js",revision:null},{url:"assets/terminal-C7X98Amf.js",revision:null},{url:"assets/index-PWu1VEN_.css",revision:null},{url:"icons/agents/opencode.svg",revision:"851c2931d95821ba2b39b68587b48976"},{url:"icons/agents/kimi.svg",revision:"5366c710f4f763022fe159a69cf17409"},{url:"icons/agents/grok.svg",revision:"cd745b0b964545bc7a6b4f36021c3298"},{url:"icons/agents/goose.svg",revision:"c79b927278983c19098d77b4b0c11ab3"},{url:"icons/agents/gemini.svg",revision:"7a64f6d6f80a908255e9c0256903882e"},{url:"icons/agents/droid.svg",revision:"f28723b8c5c115e0908ba3c72613e256"},{url:"icons/agents/cursor.svg",revision:"a1528f3d3cda497f75a5db28defb19de"},{url:"icons/agents/copilot.svg",revision:"dd52e96c1eac0b6ec4bedcd0ddd3b52d"},{url:"icons/agents/codex.svg",revision:"608e55babb0b217e1f2da287342b46d3"},{url:"icons/agents/claude.svg",revision:"08af0225b7acdd8e8f573f3247e5e1c3"},{url:"icons/agents/amp.svg",revision:"af12f8b33faa81e4d46ea7b93c7e43f3"},{url:"apple-touch-icon-180x180.png",revision:"628c2e2f28a9b545aa57be1352025643"},{url:"favicon.ico",revision:"2994debfa6f59a0898e24eb0c54d64b5"},{url:"maskable-icon-512x512.png",revision:"4d489331fd81f0713c400b26426442b9"},{url:"pwa-192x192.png",revision:"4d6f899888cfb8ccd7a1fd3c9e583ad2"},{url:"pwa-512x512.png",revision:"0885dd7ec8932e7f878b013d51a380a4"},{url:"robots.txt",revision:"d9339907142917dee4aac78494ac9cfe"},{url:"sw-downloads.js",revision:"29b4d23945d109a334e1c3902a8cf02a"},{url:"sw-notifications.js",revision:"a5a2b585a4336d66519195e5e63aee2d"},{url:"sw-secure-api.js",revision:"739c54d3fc8f4cbb87bab12b6b8a8160"},{url:"manifest.webmanifest",revision:"83f9a43b74c236012adafb077fb6d774"}],{}),e.cleanupOutdatedCaches(),e.registerRoute(new e.NavigationRoute(e.createHandlerBoundToURL("index.html"),{denylist:[/^\/api\//,/^\/__termdock-download\//,/^\/health$/]})),e.registerRoute(/\/assets\/.*\.(?:js|css)$/,new e.CacheFirst({cacheName:"termdock-lazy-assets-v1",plugins:[new e.CacheableResponsePlugin({statuses:[0,200]}),new e.ExpirationPlugin({maxEntries:96,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\/fonts\/terminal\/[^/]+-[a-f0-9]{12}\.woff2$/,new e.CacheFirst({cacheName:"termdock-fonts-v2",plugins:[new e.CacheableResponsePlugin({statuses:[0,200]}),new e.ExpirationPlugin({maxEntries:384,maxAgeSeconds:2592e3})]}),"GET"),e.registerRoute(/\/fonts\/.*\.(?:woff2|ttf)$/,new e.StaleWhileRevalidate({cacheName:"termdock-fonts-v1",plugins:[new e.ExpirationPlugin({maxEntries:24,maxAgeSeconds:2592e3})]}),"GET")});
|
package/dist/client/sw.js.br
CHANGED
|
Binary file
|
package/dist/client/sw.js.gz
CHANGED
|
Binary file
|
|
@@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
import { COLLAB_LIMITS, CollaborationError } from './collaborationProtocol.js';
|
|
4
4
|
export const COLLAB_HELP = `td collab — durable messages; no agent-specific hooks required
|
|
5
5
|
status | capabilities
|
|
6
|
+
rebind [--pane %3] (explicitly bind this peer to its current Agent; resumes queued delivery)
|
|
6
7
|
send <session-id> <message> | reply <message-id> <message> | handoff <session-id> <message>
|
|
7
8
|
--group <id> --thread <id> --idempotency-key <key>
|
|
8
9
|
--file <path> | --stdin (instead of inline body; -- ends option parsing)
|
|
@@ -29,7 +30,7 @@ Idempotency retention: 7 days. read/ACK/result never imply each other.
|
|
|
29
30
|
Inbox defaults to unread first, newest 50; cursor/consumer mode reads oldest unseen first.
|
|
30
31
|
Reading never advances a consumer or marks messages read automatically.`;
|
|
31
32
|
const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'unread', 'follow', 'stdin', 'receipt-only', 'help']);
|
|
32
|
-
const VALUE_OPTIONS = new Set(['group', 'thread', 'idempotency-key', 'file', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'kind', 'name', 'cwd', 'task']);
|
|
33
|
+
const VALUE_OPTIONS = new Set(['group', 'thread', 'idempotency-key', 'file', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'kind', 'name', 'cwd', 'task', 'pane']);
|
|
33
34
|
export function parseCollaborationCommand(argv) {
|
|
34
35
|
const options = {};
|
|
35
36
|
const positional = [];
|
|
@@ -60,8 +61,10 @@ export function parseCollaborationCommand(argv) {
|
|
|
60
61
|
positional.push(value);
|
|
61
62
|
}
|
|
62
63
|
const action = (options.help ? 'help' : positional.shift() ?? 'status');
|
|
63
|
-
if (!['status', 'inbox', 'send', 'handoff', 'reply', 'add', 'remove', 'spawn', 'message', 'cursor', 'capabilities', 'help'].includes(action))
|
|
64
|
+
if (!['status', 'inbox', 'send', 'handoff', 'reply', 'add', 'remove', 'spawn', 'message', 'cursor', 'rebind', 'capabilities', 'help'].includes(action))
|
|
64
65
|
throw new Error('Unknown collaboration command; see td collab --help');
|
|
66
|
+
if (options.pane && !/^%\d+$/.test(String(options.pane)))
|
|
67
|
+
throw new Error('pane must be a tmux pane id such as %3');
|
|
65
68
|
if (['json', 'jsonl', 'text'].filter((key) => options[key]).length > 1)
|
|
66
69
|
throw new Error('Choose one output format');
|
|
67
70
|
if (options['wait-until'] && !['queued', 'delivered', 'read'].includes(String(options['wait-until'])))
|
|
@@ -107,7 +110,7 @@ export function parseCollaborationCommand(argv) {
|
|
|
107
110
|
throw new Error(`Unexpected arguments for ${action}`);
|
|
108
111
|
const allowed = new Set(['json', 'jsonl', 'text', 'help']);
|
|
109
112
|
const byAction = {
|
|
110
|
-
status: [], capabilities: [], help: [...BOOLEAN_OPTIONS, ...VALUE_OPTIONS],
|
|
113
|
+
status: [], capabilities: [], rebind: ['pane'], help: [...BOOLEAN_OPTIONS, ...VALUE_OPTIONS],
|
|
111
114
|
send: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at', 'kind'],
|
|
112
115
|
handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
|
|
113
116
|
reply: ['idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
|
|
@@ -187,6 +190,10 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
187
190
|
output(await request('GET', command.action === 'status' ? '/peers' : '/capabilities'));
|
|
188
191
|
return 0;
|
|
189
192
|
}
|
|
193
|
+
if (command.action === 'rebind') {
|
|
194
|
+
output(await request('POST', '/route/rebind', { pane: o.pane ?? null }));
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
190
197
|
if (command.action === 'inbox') {
|
|
191
198
|
let cursor = o.cursor;
|
|
192
199
|
do {
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
export class CollaborationDeliveryWorker {
|
|
2
|
+
options;
|
|
3
|
+
running = new Map();
|
|
4
|
+
states = new Map();
|
|
5
|
+
failures = new Map();
|
|
6
|
+
submitted = new Set();
|
|
7
|
+
timer = null;
|
|
8
|
+
ticking = null;
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.options = options;
|
|
11
|
+
}
|
|
12
|
+
start() {
|
|
13
|
+
if (this.timer)
|
|
14
|
+
return;
|
|
15
|
+
this.timer = setInterval(() => { void this.tick(); }, 2_000);
|
|
16
|
+
this.timer.unref?.();
|
|
17
|
+
void this.tick();
|
|
18
|
+
}
|
|
19
|
+
stop() {
|
|
20
|
+
if (this.timer)
|
|
21
|
+
clearInterval(this.timer);
|
|
22
|
+
this.timer = null;
|
|
23
|
+
}
|
|
24
|
+
state(id) {
|
|
25
|
+
const state = this.states.get(id);
|
|
26
|
+
return state && Date.now() - state.checkedAt < 15_000 ? state
|
|
27
|
+
: { state: 'recovering', reason: 'ROUTE_NOT_CHECKED', checkedAt: state?.checkedAt ?? null };
|
|
28
|
+
}
|
|
29
|
+
wake(id) {
|
|
30
|
+
void this.run(id).catch(this.options.onError);
|
|
31
|
+
}
|
|
32
|
+
tick() {
|
|
33
|
+
if (this.ticking)
|
|
34
|
+
return this.ticking;
|
|
35
|
+
this.ticking = this.tickOnce().finally(() => { this.ticking = null; });
|
|
36
|
+
return this.ticking;
|
|
37
|
+
}
|
|
38
|
+
async tickOnce() {
|
|
39
|
+
try {
|
|
40
|
+
const ids = [...new Set([...this.options.peers(), ...this.options.store.pendingRecipients()])].filter(this.options.isLocal);
|
|
41
|
+
// Bound process probes/attachments while keeping a broken peer from
|
|
42
|
+
// serially blocking the entire queue.
|
|
43
|
+
let cursor = 0;
|
|
44
|
+
await Promise.all(Array.from({ length: Math.min(4, ids.length) }, async () => {
|
|
45
|
+
while (cursor < ids.length)
|
|
46
|
+
await this.run(ids[cursor++]).catch(this.options.onError);
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
this.options.onError(error);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
run(id) {
|
|
54
|
+
const running = this.running.get(id);
|
|
55
|
+
if (running)
|
|
56
|
+
return running;
|
|
57
|
+
if (!this.options.isLocal(id))
|
|
58
|
+
return Promise.resolve();
|
|
59
|
+
const operation = Promise.resolve().then(() => this.deliver(id)).finally(() => {
|
|
60
|
+
if (this.running.get(id) === operation)
|
|
61
|
+
this.running.delete(id);
|
|
62
|
+
});
|
|
63
|
+
this.running.set(id, operation);
|
|
64
|
+
return operation;
|
|
65
|
+
}
|
|
66
|
+
reconfigure(id, configure) {
|
|
67
|
+
const previous = this.running.get(id) ?? Promise.resolve();
|
|
68
|
+
const operation = previous.catch(() => undefined).then(async () => {
|
|
69
|
+
await configure();
|
|
70
|
+
this.failures.delete(id);
|
|
71
|
+
this.states.delete(id);
|
|
72
|
+
for (const message of this.options.store.inbox(id, { pendingOnly: true, limit: 200 })) {
|
|
73
|
+
const diagnostic = this.options.store.diagnostic(message.id);
|
|
74
|
+
if (diagnostic)
|
|
75
|
+
this.options.store.recordTransport(message.id, { ...diagnostic, next_retry_at: null, last_error: null });
|
|
76
|
+
}
|
|
77
|
+
}).finally(() => {
|
|
78
|
+
if (this.running.get(id) === operation)
|
|
79
|
+
this.running.delete(id);
|
|
80
|
+
});
|
|
81
|
+
this.running.set(id, operation);
|
|
82
|
+
return operation;
|
|
83
|
+
}
|
|
84
|
+
async deliver(id) {
|
|
85
|
+
const { store } = this.options;
|
|
86
|
+
const first = store.inbox(id, { pendingOnly: true, limit: 1 })[0];
|
|
87
|
+
if (!first && Date.now() - (this.states.get(id)?.checkedAt ?? 0) < 5_000)
|
|
88
|
+
return;
|
|
89
|
+
if (first && this.submitted.has(first.id)) {
|
|
90
|
+
this.complete(id, first);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (first && (store.diagnostic(first.id)?.next_retry_at ?? 0) > Date.now())
|
|
94
|
+
return;
|
|
95
|
+
this.states.set(id, { state: 'recovering', reason: null, checkedAt: Date.now() });
|
|
96
|
+
let route;
|
|
97
|
+
try {
|
|
98
|
+
route = await this.options.resolve(id);
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
route = { state: 'unavailable', reason: `ROUTE_RECOVERY_FAILED: ${error instanceof Error ? error.message : String(error)}` };
|
|
102
|
+
}
|
|
103
|
+
this.states.set(id, { state: route.state, reason: route.reason ?? null, checkedAt: Date.now() });
|
|
104
|
+
// Re-read after asynchronous recovery: a consumer may have read the
|
|
105
|
+
// message, or its TTL may have elapsed while tmux was being inspected.
|
|
106
|
+
const pending = store.inbox(id, { pendingOnly: true, limit: 1 });
|
|
107
|
+
if (!pending.length)
|
|
108
|
+
return;
|
|
109
|
+
if (route.state !== 'ready' || !route.write) {
|
|
110
|
+
this.retry(id, pending, route.reason ?? route.state, false);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
const message = pending[0];
|
|
114
|
+
if (!this.submitted.has(message.id)) {
|
|
115
|
+
store.recordTransport(message.id, {
|
|
116
|
+
relay_online: null, peer_reachable: true,
|
|
117
|
+
attempt_count: (store.diagnostic(message.id)?.attempt_count ?? 0) + 1,
|
|
118
|
+
next_retry_at: Date.now() + 2_000, last_error: 'DELIVERY_IN_PROGRESS', checked_at: Date.now(),
|
|
119
|
+
});
|
|
120
|
+
try {
|
|
121
|
+
await route.write(pending);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
this.retry(id, pending, `TERMINAL_WRITE_FAILED: ${error instanceof Error ? error.message : String(error)}`, true);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
this.submitted.add(message.id);
|
|
128
|
+
}
|
|
129
|
+
// If persistence fails after writing, the in-process guard avoids a
|
|
130
|
+
// second write on retry. Across a crash, transport is at-least-once.
|
|
131
|
+
this.complete(id, message);
|
|
132
|
+
}
|
|
133
|
+
complete(id, message) {
|
|
134
|
+
const { store } = this.options;
|
|
135
|
+
store.markDelivered([message.id]);
|
|
136
|
+
this.submitted.delete(message.id);
|
|
137
|
+
store.recordTransport(message.id, {
|
|
138
|
+
relay_online: null, peer_reachable: true, attempt_count: store.diagnostic(message.id)?.attempt_count ?? 1,
|
|
139
|
+
next_retry_at: null, last_error: null, checked_at: Date.now(),
|
|
140
|
+
});
|
|
141
|
+
this.failures.delete(id);
|
|
142
|
+
}
|
|
143
|
+
retry(id, messages, reason, reachable) {
|
|
144
|
+
const failures = (this.failures.get(id) ?? 0) + 1;
|
|
145
|
+
this.failures.set(id, failures);
|
|
146
|
+
const next = Date.now() + Math.min(30_000, 2_000 * 2 ** Math.min(failures - 1, 4));
|
|
147
|
+
for (const message of messages)
|
|
148
|
+
this.options.store.recordTransport(message.id, {
|
|
149
|
+
relay_online: null, peer_reachable: reachable,
|
|
150
|
+
attempt_count: this.options.store.diagnostic(message.id)?.attempt_count ?? 0,
|
|
151
|
+
next_retry_at: next, last_error: reason, checked_at: Date.now(),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -1,19 +1,28 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { COLLAB_LIMITS, CollaborationError, extrasFromBody } from './collaborationProtocol.js';
|
|
3
|
-
export function collaborationRoutes({ store, resolveSession, deliver }) {
|
|
3
|
+
export function collaborationRoutes({ store, resolveSession, deliver, rebind }) {
|
|
4
4
|
const router = Router();
|
|
5
|
-
const run = (handler) => (req, res) => {
|
|
5
|
+
const run = (handler) => async (req, res) => {
|
|
6
6
|
try {
|
|
7
7
|
const sessionId = resolveSession((req.method === 'GET' ? req.query : req.body) ?? {});
|
|
8
8
|
if (!sessionId)
|
|
9
9
|
throw new CollaborationError('SESSION_NOT_FOUND', 'Run inside a Termdock managed session', 404);
|
|
10
|
-
handler(req, res, sessionId);
|
|
10
|
+
await handler(req, res, sessionId);
|
|
11
11
|
}
|
|
12
12
|
catch (error) {
|
|
13
13
|
res.status(error instanceof CollaborationError ? error.httpStatus : 400).json({ ok: false,
|
|
14
14
|
code: error instanceof CollaborationError ? error.code : 'COLLABORATION_ERROR', error: error instanceof Error ? error.message : String(error) });
|
|
15
15
|
}
|
|
16
16
|
};
|
|
17
|
+
router.post('/route/rebind', run(async (req, res, sessionId) => {
|
|
18
|
+
if (!rebind)
|
|
19
|
+
throw new CollaborationError('ROUTE_REBIND_UNAVAILABLE', 'Route rebinding is not available');
|
|
20
|
+
const pane = req.body.pane;
|
|
21
|
+
if (pane !== undefined && pane !== null && (typeof pane !== 'string' || !/^%\d+$/.test(pane))) {
|
|
22
|
+
throw new CollaborationError('INVALID_PANE', 'pane must be a tmux pane id such as %3');
|
|
23
|
+
}
|
|
24
|
+
res.json({ ok: true, route: await rebind(sessionId, pane ?? null) });
|
|
25
|
+
}));
|
|
17
26
|
const ownMessage = (id, sessionId, recipientOnly = false) => {
|
|
18
27
|
const message = store.getMessage(id);
|
|
19
28
|
if (!message || (message.toSessionId !== sessionId && (recipientOnly || message.fromSessionId !== sessionId)))
|
|
@@ -27,11 +36,13 @@ export function collaborationRoutes({ store, resolveSession, deliver }) {
|
|
|
27
36
|
};
|
|
28
37
|
router.get('/capabilities', run((_req, res) => {
|
|
29
38
|
res.json({ protocol_version: 2, limits: COLLAB_LIMITS,
|
|
39
|
+
routing: { background_recovery: true, explicit_rebind: Boolean(rebind), fixed_tmux_pane: true },
|
|
30
40
|
statuses: ['pending', 'delivered', 'read', 'failed', 'expired'], queued_status: 'pending',
|
|
31
41
|
semantics: { delivered: 'written to terminal; no application acknowledgement implied', read: 'explicit consumer acknowledgement',
|
|
32
42
|
ack: 'explicit response_kind=ack', result: 'explicit response_kind=result; inspect task.status and evidence',
|
|
33
43
|
done: 'adapter reports turn ended, never proof of task completion', heartbeat: 'null unless explicitly observed',
|
|
34
|
-
timeout: 'stops waiting, does not cancel delivery'
|
|
44
|
+
timeout: 'stops waiting, does not cancel delivery',
|
|
45
|
+
retry: 'same message id may be submitted again after a crash or uncertain transport result; consumers deduplicate by message id' } });
|
|
35
46
|
}));
|
|
36
47
|
router.post('/send', run((req, res, sessionId) => {
|
|
37
48
|
const target = typeof req.body.targetSessionId === 'string' ? req.body.targetSessionId.trim() : '';
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export class CollaborationRoutingStore {
|
|
4
|
+
file;
|
|
5
|
+
bindings = new Map();
|
|
6
|
+
loadError = null;
|
|
7
|
+
constructor(file) {
|
|
8
|
+
this.file = file;
|
|
9
|
+
try {
|
|
10
|
+
const data = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
11
|
+
if (data.version !== 1 || !Array.isArray(data.bindings))
|
|
12
|
+
throw new Error('Invalid collaboration routing store');
|
|
13
|
+
for (const binding of data.bindings) {
|
|
14
|
+
if (!binding || typeof binding.sessionId !== 'string' || !['shell', 'tmux'].includes(binding.mode)
|
|
15
|
+
|| ![binding.backendSessionId, binding.tmuxSessionName, binding.agentSlug, binding.nativeSessionId].every((value) => value === null || typeof value === 'string')
|
|
16
|
+
|| (binding.pane !== null && (!binding.pane || !Number.isInteger(binding.pane.serverPid) || !Number.isInteger(binding.pane.panePid)
|
|
17
|
+
|| !/^%\d+$/.test(binding.pane.paneId) || !/^\$\d+$/.test(binding.pane.sessionId) || typeof binding.pane.agentSlug !== 'string'))) {
|
|
18
|
+
throw new Error('Invalid collaboration binding');
|
|
19
|
+
}
|
|
20
|
+
this.bindings.set(binding.sessionId, binding);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error.code !== 'ENOENT') {
|
|
25
|
+
this.bindings.clear();
|
|
26
|
+
this.loadError = error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
get(id) {
|
|
31
|
+
const binding = this.bindings.get(id);
|
|
32
|
+
return binding ? structuredClone(binding) : null;
|
|
33
|
+
}
|
|
34
|
+
assertReadable() {
|
|
35
|
+
if (this.loadError)
|
|
36
|
+
throw new Error('COLLABORATION_ROUTING_STORE_UNREADABLE', { cause: this.loadError });
|
|
37
|
+
}
|
|
38
|
+
ownerOfBackend(id) {
|
|
39
|
+
return [...this.bindings.values()].find((binding) => binding.backendSessionId === id)?.sessionId ?? null;
|
|
40
|
+
}
|
|
41
|
+
bind(binding) {
|
|
42
|
+
this.assertReadable();
|
|
43
|
+
const owner = binding.backendSessionId ? this.ownerOfBackend(binding.backendSessionId) : null;
|
|
44
|
+
if (owner && owner !== binding.sessionId)
|
|
45
|
+
throw new Error('BACKEND_ALREADY_BOUND');
|
|
46
|
+
if (JSON.stringify(this.bindings.get(binding.sessionId)) === JSON.stringify(binding))
|
|
47
|
+
return;
|
|
48
|
+
const next = new Map(this.bindings);
|
|
49
|
+
next.set(binding.sessionId, structuredClone(binding));
|
|
50
|
+
this.persist(next);
|
|
51
|
+
}
|
|
52
|
+
remove(id) {
|
|
53
|
+
if (!this.bindings.has(id))
|
|
54
|
+
return;
|
|
55
|
+
const next = new Map(this.bindings);
|
|
56
|
+
next.delete(id);
|
|
57
|
+
this.persist(next);
|
|
58
|
+
}
|
|
59
|
+
persist(next) {
|
|
60
|
+
this.assertReadable();
|
|
61
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true });
|
|
62
|
+
const temporary = `${this.file}.${process.pid}.tmp`;
|
|
63
|
+
fs.writeFileSync(temporary, JSON.stringify({ version: 1, bindings: [...next.values()] }), { mode: 0o600 });
|
|
64
|
+
fs.renameSync(temporary, this.file);
|
|
65
|
+
this.bindings = next;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Once pinned, changing the active pane cannot change the recipient. A reused
|
|
69
|
+
// tmux name/pane after a server restart must not inherit an old peer's inbox.
|
|
70
|
+
export function selectCollaborationPane(binding, panes) {
|
|
71
|
+
if (binding.pane) {
|
|
72
|
+
const previous = binding.pane;
|
|
73
|
+
const pane = panes.find((candidate) => candidate.serverPid === previous.serverPid
|
|
74
|
+
&& candidate.sessionId === previous.sessionId && candidate.paneId === previous.paneId && candidate.panePid === previous.panePid);
|
|
75
|
+
if (!pane)
|
|
76
|
+
return { state: 'identity-mismatch', reason: 'TMUX_PANE_CHANGED' };
|
|
77
|
+
if (!pane.agentSlug)
|
|
78
|
+
return { state: 'agent-exited', reason: 'AGENT_NOT_RUNNING' };
|
|
79
|
+
if (pane.agentSlug !== previous.agentSlug || (previous.nativeSessionId && pane.nativeSessionId && previous.nativeSessionId !== pane.nativeSessionId)) {
|
|
80
|
+
return { state: 'identity-mismatch', reason: 'AGENT_IDENTITY_CHANGED' };
|
|
81
|
+
}
|
|
82
|
+
return { state: 'ready', pane };
|
|
83
|
+
}
|
|
84
|
+
const candidates = panes.filter((pane) => pane.agentSlug && (!binding.agentSlug || binding.agentSlug === pane.agentSlug)
|
|
85
|
+
&& (!binding.nativeSessionId || !pane.nativeSessionId || binding.nativeSessionId === pane.nativeSessionId));
|
|
86
|
+
const exact = binding.nativeSessionId ? candidates.filter((pane) => pane.nativeSessionId === binding.nativeSessionId) : [];
|
|
87
|
+
const matches = exact.length ? exact : candidates;
|
|
88
|
+
if (matches.length === 1)
|
|
89
|
+
return { state: 'ready', pane: matches[0] };
|
|
90
|
+
if (!matches.length && panes.some((pane) => pane.agentSlug))
|
|
91
|
+
return { state: 'identity-mismatch', reason: 'AGENT_IDENTITY_CHANGED' };
|
|
92
|
+
return matches.length > 1 ? { state: 'ambiguous', reason: 'MULTIPLE_AGENT_PANES' }
|
|
93
|
+
: { state: 'agent-exited', reason: 'AGENT_NOT_RUNNING_OR_IDENTITY_CHANGED' };
|
|
94
|
+
}
|
|
@@ -160,6 +160,10 @@ export class CollaborationStore {
|
|
|
160
160
|
this.expire();
|
|
161
161
|
return this.document.messages.filter((message) => message.toSessionId === sessionId && message.status === 'pending').length;
|
|
162
162
|
}
|
|
163
|
+
pendingRecipients() {
|
|
164
|
+
this.expire();
|
|
165
|
+
return [...new Set(this.document.messages.filter((message) => message.status === 'pending').map((message) => message.toSessionId))];
|
|
166
|
+
}
|
|
163
167
|
markDelivered(messageIds) {
|
|
164
168
|
return this.updateStatus(messageIds, 'delivered');
|
|
165
169
|
}
|
|
@@ -249,7 +253,8 @@ export class CollaborationStore {
|
|
|
249
253
|
expire() {
|
|
250
254
|
let changed = false;
|
|
251
255
|
for (const message of this.document.messages)
|
|
252
|
-
if (message.status === 'pending' && message.expiresAt && message.expiresAt <= Date.now()
|
|
256
|
+
if (message.status === 'pending' && message.expiresAt && message.expiresAt <= Date.now()
|
|
257
|
+
&& this.document.transport?.[message.id]?.last_error !== 'DELIVERY_IN_PROGRESS') {
|
|
253
258
|
message.status = 'expired';
|
|
254
259
|
message.failureReason = 'MESSAGE_EXPIRED';
|
|
255
260
|
changed = true;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { buildBracketedSubmitBytes } from './promptDelivery.js';
|
|
2
|
+
export async function writeCollaborationTmuxPane(run, pane, prompt) {
|
|
3
|
+
const identity = (await run(['display-message', '-p', '-t', pane.paneId,
|
|
4
|
+
'#{pid}:#{session_id}:#{pane_id}:#{pane_pid}'])).trim();
|
|
5
|
+
if (identity !== `${pane.serverPid}:${pane.sessionId}:${pane.paneId}:${pane.panePid}`)
|
|
6
|
+
throw new Error('TMUX_PANE_CHANGED');
|
|
7
|
+
// A fixed pane target is independent of the current window, keyboard focus
|
|
8
|
+
// and browser presence. One invocation preserves the bracketed submit bytes.
|
|
9
|
+
await run(['send-keys', '-t', pane.paneId, '-l', '--', buildBracketedSubmitBytes(prompt)]);
|
|
10
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
export function resolveCollaborationSessionId(input, records, routing) {
|
|
2
|
+
const sessionId = typeof input.sessionId === 'string' ? input.sessionId.trim() : '';
|
|
3
|
+
if (sessionId && records.some((record) => record.sessionId === sessionId))
|
|
4
|
+
return sessionId;
|
|
5
|
+
const backendSessionId = typeof input.backendSessionId === 'string' ? input.backendSessionId.trim() : '';
|
|
6
|
+
if (backendSessionId) {
|
|
7
|
+
const owner = routing.ownerOfBackend(backendSessionId);
|
|
8
|
+
if (owner && records.some((record) => record.sessionId === owner))
|
|
9
|
+
return owner;
|
|
10
|
+
const resolved = records.find((record) => record.backendSessionId === backendSessionId)?.sessionId;
|
|
11
|
+
if (resolved)
|
|
12
|
+
return resolved;
|
|
13
|
+
}
|
|
14
|
+
const tmuxSessionName = typeof input.tmuxSessionName === 'string' ? input.tmuxSessionName.trim() : '';
|
|
15
|
+
if (!tmuxSessionName)
|
|
16
|
+
return null;
|
|
17
|
+
const bound = records.find((record) => routing.get(record.sessionId)?.tmuxSessionName === tmuxSessionName);
|
|
18
|
+
if (bound)
|
|
19
|
+
return bound.sessionId;
|
|
20
|
+
return records.find((record) => record.tmuxSessionName === tmuxSessionName)?.sessionId ?? null;
|
|
21
|
+
}
|
|
22
|
+
export function resolveCollaborationBackend(record, records, backends, routing) {
|
|
23
|
+
routing.assertReadable();
|
|
24
|
+
const binding = routing.get(record.sessionId);
|
|
25
|
+
const target = binding ? { ...record, mode: binding.mode, tmuxSessionName: binding.tmuxSessionName,
|
|
26
|
+
backendSessionId: binding.backendSessionId,
|
|
27
|
+
agentResume: binding.agentSlug ? { slug: binding.agentSlug, sessionId: binding.nativeSessionId } : record.agentResume } : record;
|
|
28
|
+
const existing = target.backendSessionId ? backends.get(target.backendSessionId) : null;
|
|
29
|
+
const recovered = existing && existing.mode === target.mode && existing.tmuxSessionName === target.tmuxSessionName
|
|
30
|
+
? [target.backendSessionId, existing]
|
|
31
|
+
: recoverSessionBinding({ ...target, backendSessionId: null }, records, backends, binding);
|
|
32
|
+
if (!recovered)
|
|
33
|
+
return null;
|
|
34
|
+
const [id, backend] = recovered;
|
|
35
|
+
if (backend.mode !== 'shell' && backend.mode !== 'tmux')
|
|
36
|
+
return null;
|
|
37
|
+
const owner = routing.ownerOfBackend(id);
|
|
38
|
+
if (owner && owner !== record.sessionId)
|
|
39
|
+
return null;
|
|
40
|
+
if (records.some((other) => other.sessionId !== record.sessionId && other.backendSessionId === id))
|
|
41
|
+
return null;
|
|
42
|
+
routing.bind({ sessionId: record.sessionId, backendSessionId: id, mode: backend.mode,
|
|
43
|
+
tmuxSessionName: backend.tmuxSessionName,
|
|
44
|
+
agentSlug: binding?.agentSlug ?? backend.agent?.slug ?? record.agentResume?.slug ?? null,
|
|
45
|
+
nativeSessionId: binding?.nativeSessionId ?? backend.agentSession?.sessionId ?? record.agentResume?.sessionId ?? null,
|
|
46
|
+
pane: binding?.pane ?? null });
|
|
47
|
+
return recovered;
|
|
48
|
+
}
|
|
49
|
+
// Match live runtime evidence, never the search index or a title/cwd heuristic.
|
|
50
|
+
export function recoverSessionBinding(record, records, backends, binding) {
|
|
51
|
+
if (binding && binding.sessionId !== record.sessionId)
|
|
52
|
+
return null;
|
|
53
|
+
if (record.backendSessionId && backends.has(record.backendSessionId))
|
|
54
|
+
return null;
|
|
55
|
+
const nativeSessionId = binding?.nativeSessionId ?? record.agentResume?.sessionId;
|
|
56
|
+
const agentSlug = binding?.agentSlug ?? record.agentResume?.slug;
|
|
57
|
+
const candidates = [...backends].filter(([id, backend]) => {
|
|
58
|
+
if (records.some((other) => other.sessionId !== record.sessionId && other.backendSessionId === id))
|
|
59
|
+
return false;
|
|
60
|
+
if (backend.mode !== record.mode)
|
|
61
|
+
return false;
|
|
62
|
+
if (record.tmuxSessionName && backend.tmuxSessionName !== record.tmuxSessionName)
|
|
63
|
+
return false;
|
|
64
|
+
const nativeId = backend.agentSession?.sessionId;
|
|
65
|
+
const slug = backend.agent?.slug;
|
|
66
|
+
if (agentSlug && slug && agentSlug !== slug)
|
|
67
|
+
return false;
|
|
68
|
+
if (nativeSessionId && nativeId && nativeSessionId !== nativeId)
|
|
69
|
+
return false;
|
|
70
|
+
if (record.agentResume && slug && record.agentResume.slug !== slug)
|
|
71
|
+
return false;
|
|
72
|
+
if (record.agentResume?.sessionId && nativeId && record.agentResume.sessionId !== nativeId)
|
|
73
|
+
return false;
|
|
74
|
+
const tmuxMatch = record.mode === 'tmux' && Boolean(record.tmuxSessionName)
|
|
75
|
+
&& backend.tmuxSessionName === record.tmuxSessionName;
|
|
76
|
+
const nativeMatch = Boolean(nativeId) && nativeSessionId === nativeId && agentSlug === slug;
|
|
77
|
+
return tmuxMatch || nativeMatch;
|
|
78
|
+
});
|
|
79
|
+
return candidates.length === 1 ? candidates[0] : null;
|
|
80
|
+
}
|
package/dist/server/cli.js
CHANGED
|
@@ -1255,9 +1255,12 @@ async function runCollab(command) {
|
|
|
1255
1255
|
}
|
|
1256
1256
|
const backendSessionId = process.env.TERMDOCK_BACKEND_SESSION_ID?.trim() || null;
|
|
1257
1257
|
let tmuxSessionName = null;
|
|
1258
|
-
if (
|
|
1258
|
+
if (process.env.TMUX) {
|
|
1259
1259
|
try {
|
|
1260
|
-
const
|
|
1260
|
+
const paneId = process.env.TMUX_PANE?.trim();
|
|
1261
|
+
const { stdout } = await execFileAsync(process.env.TMUX_BIN || 'tmux', [
|
|
1262
|
+
'display-message', '-p', ...(/^%\d+$/.test(paneId ?? '') ? ['-t', paneId] : []), '#S',
|
|
1263
|
+
], {
|
|
1261
1264
|
timeout: 2_000,
|
|
1262
1265
|
maxBuffer: 16 * 1024,
|
|
1263
1266
|
});
|
|
@@ -1266,15 +1269,16 @@ async function runCollab(command) {
|
|
|
1266
1269
|
tmuxSessionName = detected;
|
|
1267
1270
|
}
|
|
1268
1271
|
catch {
|
|
1269
|
-
//
|
|
1270
|
-
//
|
|
1272
|
+
// Keep a working backend context if tmux inspection fails. A surviving
|
|
1273
|
+
// pane may retain an obsolete backend id after TD reattaches it, so
|
|
1274
|
+
// send both identities whenever tmux inspection succeeds.
|
|
1271
1275
|
}
|
|
1272
1276
|
}
|
|
1273
1277
|
if (!backendSessionId && !tmuxSessionName) {
|
|
1274
1278
|
console.error(JSON.stringify({ ok: false, code: 'SESSION_NOT_FOUND', error: 'td collab must run inside a Termdock-managed Session.' }));
|
|
1275
1279
|
process.exit(1);
|
|
1276
1280
|
}
|
|
1277
|
-
const context =
|
|
1281
|
+
const context = { backendSessionId, tmuxSessionName };
|
|
1278
1282
|
const baseUrl = runningState.localUrl
|
|
1279
1283
|
?? `${runningState.scheme ?? 'http'}://${runningState.host === '0.0.0.0' ? 'localhost' : runningState.host}:${runningState.port}`;
|
|
1280
1284
|
process.exitCode = await executeCollaborationCommand(command, Object.fromEntries(Object.entries(context).filter((entry) => typeof entry[1] === 'string')), { request: (method, endpoint, body, timeout) => method === 'GET'
|