termdock 1.4.190 → 1.4.192
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-BkcaQXGe.js → App-BbIl0DAa.js} +37 -37
- package/dist/client/assets/App-BbIl0DAa.js.br +0 -0
- package/dist/client/assets/App-BbIl0DAa.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-BkTM7zQ0.js → ChangeWalkthroughPanel-CT7mLy_e.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-CT7mLy_e.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-CT7mLy_e.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-7UxLIhH5.js → DagPlayground-Bsf4bv7e.js} +1 -1
- package/dist/client/assets/DagPlayground-Bsf4bv7e.js.br +0 -0
- package/dist/client/assets/DagPlayground-Bsf4bv7e.js.gz +0 -0
- package/dist/client/assets/{DiffLab-Cn2bPbbB.js → DiffLab-BjiZcUSn.js} +1 -1
- package/dist/client/assets/DiffLab-BjiZcUSn.js.br +0 -0
- package/dist/client/assets/DiffLab-BjiZcUSn.js.gz +0 -0
- package/dist/client/assets/{DiffReview-Bax7DQmN.js → DiffReview-DmkAyOh4.js} +1 -1
- package/dist/client/assets/DiffReview-DmkAyOh4.js.br +0 -0
- package/dist/client/assets/DiffReview-DmkAyOh4.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-TG4kjagX.js → DiffReviewLab-wXYUmdna.js} +1 -1
- package/dist/client/assets/DiffReviewLab-wXYUmdna.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-wXYUmdna.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-3MezBn_a.js → DiffViewer-BlwwMnsB.js} +1 -1
- package/dist/client/assets/DiffViewer-BlwwMnsB.js.br +0 -0
- package/dist/client/assets/DiffViewer-BlwwMnsB.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-BV_0H-Ls.js → ModelPreview-DGIaQAcq.js} +1 -1
- package/dist/client/assets/ModelPreview-DGIaQAcq.js.br +0 -0
- package/dist/client/assets/ModelPreview-DGIaQAcq.js.gz +0 -0
- package/dist/client/assets/{RightSidebar-BHlwv3gW.js → RightSidebar-PgJ0XzEh.js} +47 -47
- package/dist/client/assets/RightSidebar-PgJ0XzEh.js.br +0 -0
- package/dist/client/assets/RightSidebar-PgJ0XzEh.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-DOk0otQq.js → abnfDiagram-VCTEODGH-B_sCIKms.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-B_sCIKms.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-B_sCIKms.js.gz +0 -0
- package/dist/client/assets/app-5zC_J9mq.js +34 -0
- package/dist/client/assets/app-5zC_J9mq.js.br +0 -0
- package/dist/client/assets/app-5zC_J9mq.js.gz +0 -0
- package/dist/client/assets/{arc-CfeOYtSi.js → arc-Ps2GTfJ3.js} +1 -1
- package/dist/client/assets/arc-Ps2GTfJ3.js.br +0 -0
- package/dist/client/assets/arc-Ps2GTfJ3.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-Dys1Wr-I.js → architectureDiagram-5GKGNRK7-DxKfqPAR.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DxKfqPAR.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DxKfqPAR.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-D8DbjMtS.js → blockDiagram-I7D4REHJ-BNL8tkC0.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-BNL8tkC0.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BNL8tkC0.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DJcMUQXb.js → c4Diagram-7LVT6UL2-hyEavwo6.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-hyEavwo6.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-hyEavwo6.js.gz +0 -0
- package/dist/client/assets/channel-Bkfq4dnz.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-BPmsHIzM.js → chunk-2Q5K7J3B-DN_HrxPz.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-BTxB35XY.js → chunk-5VM5RSS4-BofAFEcC.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-BiUpuqFN.js → chunk-F27PBJKO-CLNw1JDM.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-CLNw1JDM.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CLNw1JDM.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-Bvca4yZA.js → chunk-IMKFNOWR-DsVDl8p7.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-DsVDl8p7.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DsVDl8p7.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-CxCNQSVG.js → chunk-JWPE2WC7-CdfzOrPC.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-C9E5B9o7.js → chunk-POPQ4Y6H-MXmTC-sG.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-D4Qm28xz.js → chunk-SVP7TREG-DfwfQAY6.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-DfwfQAY6.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DfwfQAY6.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-CS0AERD9.js → chunk-TICWLB2K-N3OPIvmU.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-N3OPIvmU.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-N3OPIvmU.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-C1vCDgbp.js → chunk-XXDRQBXY-IurIojt5.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-Dahb2o7K.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-Dahb2o7K.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-D0-yuoIn.js → cose-bilkent-JH36ORCC-DHbDHTvp.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DHbDHTvp.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DHbDHTvp.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-C9ZqMdi9.js → cynefin-OW5HDTMX-DyTW2Iyi.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-DyTW2Iyi.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DyTW2Iyi.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Drvx9Dke.js → cynefinDiagram-5FMLGOSQ-DEan3pcN.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DEan3pcN.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DEan3pcN.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-mUXNFR_H.js → dagre-GXQ25YYZ-Rnw2v1uV.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-Rnw2v1uV.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Rnw2v1uV.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-CKJgF0VS.js → diagram-S7CK7UJ4-CmBa29S9.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-CmBa29S9.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CmBa29S9.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-Cp9NE2Gc.js → diagram-UQ7AKVKN-Cqzmpane.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-Cqzmpane.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-Cqzmpane.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-mnyDXn4l.js → diagram-VSXAHHWV-CrHTQozH.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CrHTQozH.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CrHTQozH.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-6lyjUw6O.js → diagram-VX7I27RA-C7nQgan9.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-C7nQgan9.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-C7nQgan9.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-3K_Dhn1t.js → diagram-Z3DM3KII-93X66n3v.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-93X66n3v.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-93X66n3v.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-B2mHkMsE.js → ebnfDiagram-PWID7BFC-LfScrrH8.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-LfScrrH8.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-LfScrrH8.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-Brfc7YEf.js → erDiagram-RLTQ6QDP-DAWKsrIB.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-DAWKsrIB.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DAWKsrIB.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-UGHsvQMc.js → flowDiagram-HODETNUW-CcdXfpU7.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-CcdXfpU7.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CcdXfpU7.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CC30lZuL.js → ganttDiagram-EL5Y4UJY-DPLNbHSY.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DPLNbHSY.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DPLNbHSY.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-DIYXjBYY.js → gestureArbiter-B5PxTluh.js} +1 -1
- package/dist/client/assets/gestureArbiter-B5PxTluh.js.br +0 -0
- package/dist/client/assets/gestureArbiter-B5PxTluh.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CiwHG61R.js → gitGraphDiagram-WWUBYQGX-P741cwUf.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-P741cwUf.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-P741cwUf.js.gz +0 -0
- package/dist/client/assets/{index-TEmneMvF.css → index-oKp6kSMR.css} +1 -1
- package/dist/client/assets/index-oKp6kSMR.css.br +0 -0
- package/dist/client/assets/{index-TEmneMvF.css.gz → index-oKp6kSMR.css.gz} +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-CcAeYa6X.js → infoDiagram-27XIBGKW-xrl-VW74.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-COFtLTBs.js → ishikawaDiagram-5VMMS53U-BJmiVWrB.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BJmiVWrB.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BJmiVWrB.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-D0iXlEDw.js → journeyDiagram-3NMN7TZE-DZX8uYK2.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DZX8uYK2.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DZX8uYK2.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-Dr2EVP3G.js → kanban-definition-UXKFOSKX-1SmdXf0S.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-1SmdXf0S.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-1SmdXf0S.js.gz +0 -0
- package/dist/client/assets/{layout-CCzGp_3m.js → layout-De4QxmMN.js} +1 -1
- package/dist/client/assets/layout-De4QxmMN.js.br +0 -0
- package/dist/client/assets/layout-De4QxmMN.js.gz +0 -0
- package/dist/client/assets/{linear-B-AlDkuD.js → linear-Dc93ZnhP.js} +1 -1
- package/dist/client/assets/linear-Dc93ZnhP.js.br +0 -0
- package/dist/client/assets/linear-Dc93ZnhP.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-C8GTgQvN.js → mermaid.core-Ny4ifEul.js} +5 -5
- package/dist/client/assets/mermaid.core-Ny4ifEul.js.br +0 -0
- package/dist/client/assets/mermaid.core-Ny4ifEul.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-B1_AIM7r.js → mindmap-definition-YA3MSWOX-CE75rUrK.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CE75rUrK.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CE75rUrK.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-DUJCjLgA.js → passwordBootstrap-BuXV9qsA.js} +1 -1
- package/dist/client/assets/passwordBootstrap-BuXV9qsA.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-BuXV9qsA.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BGri_6xk.js → pegDiagram-XKGWAZYB-CR8E9wWF.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-CR8E9wWF.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-CR8E9wWF.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-DyJUIUtO.js → pieDiagram-E7YTZNPT-CKZz2-Iz.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-CKZz2-Iz.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CKZz2-Iz.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-ChYx832r.js → quadrantDiagram-AXDQQJYC-w0wqPGg1.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-w0wqPGg1.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-w0wqPGg1.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-Ll7CEGCv.js → railroadDiagram-O6MQD6OU-CWYxj4Iu.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CWYxj4Iu.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CWYxj4Iu.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-DhODL_ZR.js → referenceLongPress-xQTJmyUv.js} +1 -1
- package/dist/client/assets/referenceLongPress-xQTJmyUv.js.br +0 -0
- package/dist/client/assets/referenceLongPress-xQTJmyUv.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-WAFfi6wC.js → requirementDiagram-BXWQKSXE-DzcUlIZo.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DzcUlIZo.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DzcUlIZo.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-Bmk9JzLH.js → sankeyDiagram-P5KCCOFB-B1KFzqqb.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B1KFzqqb.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B1KFzqqb.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CQLp4wz7.js → sequenceDiagram-WJ2MYXX4-Bph5Zy95.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Bph5Zy95.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Bph5Zy95.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-7biER85h.js → sizeCapture-INFHLROL-B22qdSe2.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-B22qdSe2.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B22qdSe2.js.gz +0 -0
- package/dist/client/assets/{sparkles-Bdif6oju.js → sparkles-iFGazcNw.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-Cy9r_YvI.js → stateDiagram-D77RDMKH-qt4ZIeIU.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-qt4ZIeIU.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-qt4ZIeIU.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-xKezo5Hj.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-DeAGlPA-.js → swimlanes-42K2YHIH-BLCfdR2d.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BLCfdR2d.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BLCfdR2d.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CNA032xr.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-BRAobv6g.js → timeline-definition-24CTP7MA-B1KrZ6Jd.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-B1KrZ6Jd.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-B1KrZ6Jd.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-Bllwcw-n.js → triangle-alert-BNMREFNU.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-B4uz7tHn.js → vennDiagram-4TSXK5OY-DWNDoCmv.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-DWNDoCmv.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DWNDoCmv.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-uRNjWdqL.js → wardleyDiagram-VM6X3IG4-kdVZqb5Z.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-kdVZqb5Z.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-kdVZqb5Z.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DCT4HGtL.js → xychartDiagram-S5SC5T6Z-C1U9Cr8F.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C1U9Cr8F.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C1U9Cr8F.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 +224 -11
- package/dist/server/agent/collaborationDeliveryWorker.js +94 -1
- package/dist/server/agent/collaborationGroupRoutes.js +25 -0
- package/dist/server/agent/collaborationPrompt.js +97 -34
- package/dist/server/agent/collaborationRoutes.js +119 -9
- package/dist/server/agent/collaborationSpawn.js +16 -0
- package/dist/server/agent/collaborationStore.js +86 -12
- package/dist/server/agent/collaborationTmuxDelivery.js +86 -1
- package/dist/server/cli.js +11 -4
- package/dist/server/routes/terminal.js +271 -13
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-BkcaQXGe.js.br +0 -0
- package/dist/client/assets/App-BkcaQXGe.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BkTM7zQ0.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BkTM7zQ0.js.gz +0 -0
- package/dist/client/assets/DagPlayground-7UxLIhH5.js.br +0 -0
- package/dist/client/assets/DagPlayground-7UxLIhH5.js.gz +0 -0
- package/dist/client/assets/DiffLab-Cn2bPbbB.js.br +0 -0
- package/dist/client/assets/DiffLab-Cn2bPbbB.js.gz +0 -0
- package/dist/client/assets/DiffReview-Bax7DQmN.js.br +0 -0
- package/dist/client/assets/DiffReview-Bax7DQmN.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-TG4kjagX.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-TG4kjagX.js.gz +0 -0
- package/dist/client/assets/DiffViewer-3MezBn_a.js.br +0 -0
- package/dist/client/assets/DiffViewer-3MezBn_a.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BV_0H-Ls.js.br +0 -0
- package/dist/client/assets/ModelPreview-BV_0H-Ls.js.gz +0 -0
- package/dist/client/assets/RightSidebar-BHlwv3gW.js.br +0 -0
- package/dist/client/assets/RightSidebar-BHlwv3gW.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DOk0otQq.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DOk0otQq.js.gz +0 -0
- package/dist/client/assets/app-B0az5Yz6.js +0 -34
- package/dist/client/assets/app-B0az5Yz6.js.br +0 -0
- package/dist/client/assets/app-B0az5Yz6.js.gz +0 -0
- package/dist/client/assets/arc-CfeOYtSi.js.br +0 -0
- package/dist/client/assets/arc-CfeOYtSi.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Dys1Wr-I.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Dys1Wr-I.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-D8DbjMtS.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-D8DbjMtS.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DJcMUQXb.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DJcMUQXb.js.gz +0 -0
- package/dist/client/assets/channel-SwcBL8jW.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-BiUpuqFN.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BiUpuqFN.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-Bvca4yZA.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-Bvca4yZA.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D4Qm28xz.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D4Qm28xz.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-CS0AERD9.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-CS0AERD9.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-BOSTHjwM.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BOSTHjwM.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-D0-yuoIn.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-D0-yuoIn.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-C9ZqMdi9.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-C9ZqMdi9.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Drvx9Dke.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Drvx9Dke.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-mUXNFR_H.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-mUXNFR_H.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CKJgF0VS.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CKJgF0VS.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-Cp9NE2Gc.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-Cp9NE2Gc.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-mnyDXn4l.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-mnyDXn4l.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-6lyjUw6O.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-6lyjUw6O.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-3K_Dhn1t.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-3K_Dhn1t.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-B2mHkMsE.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-B2mHkMsE.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Brfc7YEf.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Brfc7YEf.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-UGHsvQMc.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-UGHsvQMc.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CC30lZuL.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CC30lZuL.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-DIYXjBYY.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DIYXjBYY.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CiwHG61R.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CiwHG61R.js.gz +0 -0
- package/dist/client/assets/index-TEmneMvF.css.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-COFtLTBs.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-COFtLTBs.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-D0iXlEDw.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-D0iXlEDw.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-Dr2EVP3G.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-Dr2EVP3G.js.gz +0 -0
- package/dist/client/assets/layout-CCzGp_3m.js.br +0 -0
- package/dist/client/assets/layout-CCzGp_3m.js.gz +0 -0
- package/dist/client/assets/linear-B-AlDkuD.js.br +0 -0
- package/dist/client/assets/linear-B-AlDkuD.js.gz +0 -0
- package/dist/client/assets/mermaid.core-C8GTgQvN.js.br +0 -0
- package/dist/client/assets/mermaid.core-C8GTgQvN.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-B1_AIM7r.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-B1_AIM7r.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-DUJCjLgA.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-DUJCjLgA.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BGri_6xk.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BGri_6xk.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DyJUIUtO.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DyJUIUtO.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-ChYx832r.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-ChYx832r.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Ll7CEGCv.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Ll7CEGCv.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-DhODL_ZR.js.br +0 -0
- package/dist/client/assets/referenceLongPress-DhODL_ZR.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-WAFfi6wC.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-WAFfi6wC.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bmk9JzLH.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bmk9JzLH.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CQLp4wz7.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CQLp4wz7.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-7biER85h.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-7biER85h.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-Cy9r_YvI.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-Cy9r_YvI.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DOly_YL3.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-DeAGlPA-.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-DeAGlPA-.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DxYgGhLH.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-BRAobv6g.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BRAobv6g.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-B4uz7tHn.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-B4uz7tHn.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-uRNjWdqL.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-uRNjWdqL.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DCT4HGtL.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-DCT4HGtL.js.gz +0 -0
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { COLLAB_LIMITS, CollaborationError, extrasFromBody } from './collaborationProtocol.js';
|
|
3
|
-
|
|
3
|
+
/** Member names arrive agent-authored over a CLI; control characters would
|
|
4
|
+
* corrupt terminal shells, tmux options and persisted records. Fold them to
|
|
5
|
+
* spaces, collapse runs, and cap the length (display sanitization for the
|
|
6
|
+
* delivery shell stays in the presentation layer). */
|
|
7
|
+
export function cleanMemberName(value) {
|
|
8
|
+
return value.replace(/[\u0000-\u001f\u007f]/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 80);
|
|
9
|
+
}
|
|
10
|
+
export function collaborationRoutes({ store, resolveSession, deliver, rebind, resolveNames, renameSession }) {
|
|
4
11
|
const router = Router();
|
|
5
12
|
const run = (handler) => async (req, res) => {
|
|
6
13
|
try {
|
|
@@ -34,6 +41,27 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind })
|
|
|
34
41
|
const receipts = ids.map((id) => store.receipt(id));
|
|
35
42
|
res.json({ ok: true, ...receipts[0], messages, receipts });
|
|
36
43
|
};
|
|
44
|
+
/** When the recipient is served by this server's own delivery worker, give
|
|
45
|
+
* the write (and its post-write snapshot capture) a short window to finish
|
|
46
|
+
* before the receipt leaves the send/reply call — the sender then sees the
|
|
47
|
+
* recipient's screen right away. Un-waitable targets (relayed/federated,
|
|
48
|
+
* offline, or a mock deliver) return immediately with the message queued. */
|
|
49
|
+
const awaitDelivery = async (id, outcome) => {
|
|
50
|
+
if (!outcome?.waitable)
|
|
51
|
+
return;
|
|
52
|
+
const deadline = Date.now() + 2_000;
|
|
53
|
+
for (;;) {
|
|
54
|
+
const receipt = store.receipt(id);
|
|
55
|
+
const settled = receipt.status !== 'pending'
|
|
56
|
+
|| receipt.snapshot != null
|
|
57
|
+
|| (receipt.attempt_count > 0
|
|
58
|
+
&& receipt.last_error !== 'DELIVERY_IN_PROGRESS'
|
|
59
|
+
&& (receipt.next_retry_at ?? 0) > Date.now());
|
|
60
|
+
if (settled || Date.now() >= deadline)
|
|
61
|
+
return;
|
|
62
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
63
|
+
}
|
|
64
|
+
};
|
|
37
65
|
router.get('/capabilities', run((_req, res) => {
|
|
38
66
|
res.json({ protocol_version: 2, limits: COLLAB_LIMITS,
|
|
39
67
|
routing: { background_recovery: true, explicit_rebind: Boolean(rebind), fixed_tmux_pane: true },
|
|
@@ -44,20 +72,37 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind })
|
|
|
44
72
|
timeout: 'stops waiting, does not cancel delivery',
|
|
45
73
|
retry: 'same message id may be submitted again after a crash or uncertain transport result; consumers deduplicate by message id' } });
|
|
46
74
|
}));
|
|
47
|
-
router.post('/send', run((req, res, sessionId) => {
|
|
75
|
+
router.post('/send', run(async (req, res, sessionId) => {
|
|
48
76
|
const target = typeof req.body.targetSessionId === 'string' ? req.body.targetSessionId.trim() : '';
|
|
49
|
-
|
|
77
|
+
// A fan-out send lists every recipient in toSessionIds; the store persists
|
|
78
|
+
// each edge with its sibling fanOutIds so recipients can tell a broadcast
|
|
79
|
+
// from a one-to-one assignment. The single-recipient shape stays intact.
|
|
80
|
+
const requestedTargets = Array.isArray(req.body.toSessionIds)
|
|
81
|
+
? [...new Set(req.body.toSessionIds.filter((id) => typeof id === 'string' && id.trim().length > 0))]
|
|
82
|
+
: [];
|
|
83
|
+
const targets = requestedTargets.length > 0 ? requestedTargets : (target ? [target] : []);
|
|
84
|
+
if (!targets.length)
|
|
85
|
+
throw new CollaborationError('NO_TARGET', 'send requires a targetSessionId (or toSessionIds for a fan-out)', 400);
|
|
86
|
+
const groups = store.groupsForSession(sessionId)
|
|
87
|
+
.filter((group) => targets.every((candidate) => group.sessionIds.includes(candidate)) && (!req.body.group_id || group.id === req.body.group_id));
|
|
50
88
|
if (!groups.length)
|
|
51
|
-
throw new CollaborationError('GROUP_NOT_FOUND', 'Sender and
|
|
89
|
+
throw new CollaborationError('GROUP_NOT_FOUND', 'Sender and recipients must share the specified group');
|
|
52
90
|
if (groups.length > 1 && !req.body.group_id)
|
|
53
91
|
throw new CollaborationError('AMBIGUOUS_GROUP', 'Multiple shared groups; specify --group');
|
|
54
|
-
|
|
92
|
+
if (req.body.task)
|
|
93
|
+
throw new CollaborationError('DISPATCH_CARRIES_TASK', 'Dispatch carries no task state; recipients report task status through replies', 400);
|
|
94
|
+
const messages = store.send({ ...extrasFromBody(req.body), groupId: groups[0].id, fromSessionId: sessionId, toSessionIds: targets,
|
|
55
95
|
kind: (req.body.kind ?? 'message'), content: typeof req.body.message === 'string' ? req.body.message : '',
|
|
56
96
|
threadId: typeof req.body.thread_id === 'string' ? req.body.thread_id : undefined });
|
|
57
|
-
|
|
97
|
+
// Wake and await every local recipient edge in parallel; relayed or
|
|
98
|
+
// federated ids answer immediately with the message queued, so fan-out
|
|
99
|
+
// never stalls on a peer.
|
|
100
|
+
await Promise.all(messages.map(async (message) => {
|
|
101
|
+
await awaitDelivery(message.id, (await deliver(message.toSessionId)) ?? null);
|
|
102
|
+
}));
|
|
58
103
|
sent(res, messages.map((message) => message.id));
|
|
59
104
|
}));
|
|
60
|
-
router.post('/reply', run((req, res, sessionId) => {
|
|
105
|
+
router.post('/reply', run(async (req, res, sessionId) => {
|
|
61
106
|
const original = ownMessage(String(req.body.messageId ?? ''), sessionId, true);
|
|
62
107
|
if (!original.fromSessionId)
|
|
63
108
|
throw new CollaborationError('NO_REPLY_TARGET', 'User messages have no agent reply target');
|
|
@@ -66,7 +111,7 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind })
|
|
|
66
111
|
replyTo: original.id, threadId: original.threadId });
|
|
67
112
|
// Reply is explicit consumption; only mark after successful validation/persistence.
|
|
68
113
|
store.markRead([original.id]);
|
|
69
|
-
deliver(original.fromSessionId);
|
|
114
|
+
await awaitDelivery(messages[0].id, (await deliver(original.fromSessionId)) ?? null);
|
|
70
115
|
sent(res, messages.map((message) => message.id));
|
|
71
116
|
}));
|
|
72
117
|
router.get('/message/:id', run((req, res, sessionId) => {
|
|
@@ -87,11 +132,76 @@ export function collaborationRoutes({ store, resolveSession, deliver, rebind })
|
|
|
87
132
|
const page = store.page(sessionId, { unread: string('unread') === 'true', since, afterId: string('after_id'), cursor: string('cursor'),
|
|
88
133
|
consumer: string('consumer'), limit: string('limit') ? Number(string('limit')) : undefined,
|
|
89
134
|
from: string('from'), group: string('group'), thread: string('thread'), kind: string('kind'), responseKind: string('response_kind'), order: string('order') });
|
|
90
|
-
|
|
135
|
+
// Fan-out edges carry sibling recipients; the names map lets the CLI text
|
|
136
|
+
// renderer show 同时发给了:… as people instead of bare session ids. The
|
|
137
|
+
// fallback to the raw id stays server-side agnostic (federated/offline).
|
|
138
|
+
const nameIds = [...new Set(page.messages.flatMap((message) => [message.fromSessionId, ...(message.fanOutIds ?? [])].filter((id) => typeof id === 'string' && id.length > 0)))];
|
|
139
|
+
res.json({ ok: true, ...page, ...(nameIds.length ? { names: resolveNames?.(nameIds) ?? {} } : {}) });
|
|
91
140
|
}));
|
|
92
141
|
router.post('/cursor/commit', run((req, res, sessionId) => {
|
|
93
142
|
store.commitCursor(sessionId, String(req.body.cursor ?? ''), String(req.body.consumer ?? ''));
|
|
94
143
|
res.json({ ok: true, consumer: req.body.consumer, cursor: req.body.cursor });
|
|
95
144
|
}));
|
|
145
|
+
const groupOf = (id, sessionId) => {
|
|
146
|
+
const group = store.getGroup(id);
|
|
147
|
+
if (!group || group.deleted)
|
|
148
|
+
throw new CollaborationError('GROUP_NOT_FOUND', 'Collaboration group not found', 404);
|
|
149
|
+
if (!group.sessionIds.includes(sessionId))
|
|
150
|
+
throw new CollaborationError('NOT_A_MEMBER', 'Session is not a member of this group', 403);
|
|
151
|
+
return group;
|
|
152
|
+
};
|
|
153
|
+
// Member names ride along so role lists read as people, not bare ids; the
|
|
154
|
+
// caller falls back to the session id when a name is unknown (federated or
|
|
155
|
+
// offline members have no local record).
|
|
156
|
+
const groupView = (group) => {
|
|
157
|
+
const names = resolveNames?.(group.sessionIds) ?? {};
|
|
158
|
+
return {
|
|
159
|
+
id: group.id, name: group.name, sessionIds: group.sessionIds,
|
|
160
|
+
members: group.sessionIds.map((sessionId) => ({ sessionId, name: names[sessionId] ?? null })),
|
|
161
|
+
roles: group.roles ?? {},
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
router.get('/role', run((req, res, sessionId) => {
|
|
165
|
+
const groupId = String(req.query.group ?? '');
|
|
166
|
+
if (groupId) {
|
|
167
|
+
res.json({ ok: true, group: groupView(groupOf(groupId, sessionId)) });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
// Without a group this lists the caller's own groups with full role
|
|
171
|
+
// snapshots — what `td collab --help` renders so a newcomer sees every
|
|
172
|
+
// member's role in one step instead of hunting the group id first.
|
|
173
|
+
res.json({ ok: true, groups: store.groupsForSession(sessionId)
|
|
174
|
+
.filter((group) => !group.deleted)
|
|
175
|
+
.map(groupView) });
|
|
176
|
+
}));
|
|
177
|
+
router.post('/role', run((req, res, sessionId) => {
|
|
178
|
+
const group = groupOf(String(req.body.group_id ?? ''), sessionId);
|
|
179
|
+
const role = req.body.role;
|
|
180
|
+
if (typeof req.body.session_id !== 'string' || !group.sessionIds.includes(req.body.session_id)) {
|
|
181
|
+
throw new CollaborationError('NOT_A_MEMBER', 'Role target must be a member of this group', 400);
|
|
182
|
+
}
|
|
183
|
+
if (role === undefined || (role !== null && typeof role !== 'string')) {
|
|
184
|
+
throw new CollaborationError('INVALID_ROLE', 'role must be text or null', 400);
|
|
185
|
+
}
|
|
186
|
+
const updated = store.setRole({ groupId: group.id, sessionId: req.body.session_id, role: role });
|
|
187
|
+
res.json({ ok: true, group: { ...updated, roles: updated.roles ?? {} } });
|
|
188
|
+
}));
|
|
189
|
+
router.post('/name', run(async (req, res, sessionId) => {
|
|
190
|
+
if (!renameSession)
|
|
191
|
+
throw new CollaborationError('RENAME_UNAVAILABLE', 'Member renaming is not available', 503);
|
|
192
|
+
const target = typeof req.body.session_id === 'string' ? req.body.session_id.trim() : '';
|
|
193
|
+
if (!store.groupsForSession(sessionId).some((group) => !group.deleted && group.sessionIds.includes(target))) {
|
|
194
|
+
throw new CollaborationError('NOT_A_MEMBER', 'Rename target must share a collaboration group with the caller', 403);
|
|
195
|
+
}
|
|
196
|
+
const name = typeof req.body.name === 'string' ? cleanMemberName(req.body.name) : '';
|
|
197
|
+
if (!name)
|
|
198
|
+
throw new CollaborationError('INVALID_NAME', 'name must be non-empty text up to 80 characters', 400);
|
|
199
|
+
const result = await renameSession(target, name);
|
|
200
|
+
if (!result.ok) {
|
|
201
|
+
const missing = result.code === 'SESSION_NOT_FOUND';
|
|
202
|
+
throw new CollaborationError(missing ? 'SESSION_NOT_FOUND' : 'RENAME_FAILED', result.error ?? 'Unable to rename the member', missing ? 404 : 400);
|
|
203
|
+
}
|
|
204
|
+
res.json({ ok: true, sessionId: target, name });
|
|
205
|
+
}));
|
|
96
206
|
return router;
|
|
97
207
|
}
|
|
@@ -7,3 +7,19 @@ export function resolveCollaborationSpawnMode(input) {
|
|
|
7
7
|
?? asSpawnMode(input.fallbackMode)
|
|
8
8
|
?? 'shell';
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* Shell command line that boots a collaboration member agent. Spawned agents
|
|
12
|
+
* have no human at the keyboard, yet their delivery shells instruct them to
|
|
13
|
+
* run `td collab …` — without pre-authorization every such call stops at the
|
|
14
|
+
* agent's permission dialog and the collaboration loop stalls. Agents whose
|
|
15
|
+
* CLI flags are known get a scoped allowlist (collab surface only; the server
|
|
16
|
+
* guards every collab action anyway). Unsupported agents launch plain — the
|
|
17
|
+
* delivery confirm gate can dismiss their dialogs instead.
|
|
18
|
+
*/
|
|
19
|
+
export function buildCollaborationSpawnCommand(input) {
|
|
20
|
+
const { slug, command } = input;
|
|
21
|
+
if (slug === 'claude') {
|
|
22
|
+
return `${command} --allowedTools "Bash(td collab *)"`;
|
|
23
|
+
}
|
|
24
|
+
return command;
|
|
25
|
+
}
|
|
@@ -2,6 +2,25 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import crypto from 'node:crypto';
|
|
4
4
|
import { COLLAB_LIMITS, CollaborationError, STATUS_RANK, validateExtras } from './collaborationProtocol.js';
|
|
5
|
+
/** Characters that would corrupt a role line injected into the delivery shell
|
|
6
|
+
* (`你的定位:...`) or smuggle terminal output. Unlike names, roles are plain
|
|
7
|
+
* prose — 「」 and · are harmless there — so only control sequences are barred. */
|
|
8
|
+
export const COLLAB_ROLE_FORBIDDEN = /[\x00-\x1f\x7f]/;
|
|
9
|
+
export const COLLAB_ROLE_MAX_CHARS = 200;
|
|
10
|
+
/** Data/render-safe role: control characters collapse to spaces, whitespace
|
|
11
|
+
* folds, and the display length is capped with an ellipsis. Applied on write
|
|
12
|
+
* and again defensively at render time (federated peers may carry junk). */
|
|
13
|
+
export function sanitizeCollaborationRole(role, max = COLLAB_ROLE_MAX_CHARS) {
|
|
14
|
+
const cleaned = role.replace(/[\x00-\x1f\x7f]/g, ' ').replace(/\s+/g, ' ').trim();
|
|
15
|
+
return cleaned.length > max ? `${cleaned.slice(0, max - 1)}…` : cleaned;
|
|
16
|
+
}
|
|
17
|
+
/** Drop roles whose member has left the group; survivors keep theirs. */
|
|
18
|
+
function pruneRoles(group, sessionIds) {
|
|
19
|
+
if (!group.roles)
|
|
20
|
+
return group;
|
|
21
|
+
const roles = Object.fromEntries(Object.entries(group.roles).filter(([id]) => sessionIds.includes(id)));
|
|
22
|
+
return Object.keys(roles).length ? { ...group, roles } : { ...group, roles: undefined };
|
|
23
|
+
}
|
|
5
24
|
const MAX_MESSAGES = 2_000;
|
|
6
25
|
const MESSAGE_KINDS = new Set(['message', 'ask', 'reply', 'task', 'handoff', 'done']);
|
|
7
26
|
export class CollaborationStore {
|
|
@@ -41,21 +60,44 @@ export class CollaborationStore {
|
|
|
41
60
|
save(input) {
|
|
42
61
|
const now = Date.now();
|
|
43
62
|
const existing = input.id ? this.document.groups.find((group) => group.id === input.id) : null;
|
|
44
|
-
const
|
|
63
|
+
const sessionIds = Array.from(new Set(input.sessionIds.map((id) => id.trim()).filter(Boolean)));
|
|
64
|
+
const group = pruneRoles({
|
|
45
65
|
...existing,
|
|
46
|
-
...(existing?.remoteSessions ? { remoteSessions: existing.remoteSessions.filter((session) =>
|
|
66
|
+
...(existing?.remoteSessions ? { remoteSessions: existing.remoteSessions.filter((session) => sessionIds.includes(session.sessionId)) } : {}),
|
|
47
67
|
id: existing?.id ?? crypto.randomUUID(),
|
|
48
68
|
name: input.name.trim(),
|
|
49
|
-
sessionIds
|
|
69
|
+
sessionIds,
|
|
50
70
|
createdAt: existing?.createdAt ?? now,
|
|
51
71
|
updatedAt: Math.max(now, (existing?.updatedAt ?? 0) + 1),
|
|
52
|
-
};
|
|
72
|
+
}, sessionIds);
|
|
53
73
|
this.document.groups = existing
|
|
54
74
|
? this.document.groups.map((candidate) => candidate.id === group.id ? group : candidate)
|
|
55
75
|
: [...this.document.groups, group];
|
|
56
76
|
this.persist();
|
|
57
77
|
return group;
|
|
58
78
|
}
|
|
79
|
+
/** Set or clear a member's role (定位). The target may be any group member —
|
|
80
|
+
* oneself, a peer, or a federated remote session; peers can set each other's
|
|
81
|
+
* roles. Roles ride the group document into federation and are dropped
|
|
82
|
+
* together with the member on removal. */
|
|
83
|
+
setRole(input) {
|
|
84
|
+
const group = this.getGroup(input.groupId);
|
|
85
|
+
if (!group || group.deleted)
|
|
86
|
+
throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
|
|
87
|
+
if (!group.sessionIds.includes(input.sessionId))
|
|
88
|
+
throw new CollaborationError('NOT_A_MEMBER', '该会话不在协作组中,无法设置定位', 400);
|
|
89
|
+
const role = input.role === null ? null : sanitizeCollaborationRole(input.role);
|
|
90
|
+
const roles = { ...(group.roles ?? {}) };
|
|
91
|
+
if (role)
|
|
92
|
+
roles[input.sessionId] = role;
|
|
93
|
+
else
|
|
94
|
+
delete roles[input.sessionId];
|
|
95
|
+
const updated = { ...group, roles: Object.keys(roles).length ? roles : undefined,
|
|
96
|
+
updatedAt: Math.max(Date.now(), group.updatedAt + 1) };
|
|
97
|
+
this.document.groups = this.document.groups.map((candidate) => candidate.id === group.id ? updated : candidate);
|
|
98
|
+
this.persist();
|
|
99
|
+
return updated;
|
|
100
|
+
}
|
|
59
101
|
remove(id) {
|
|
60
102
|
const before = this.document.groups.length;
|
|
61
103
|
const existing = this.getGroup(id);
|
|
@@ -88,9 +130,9 @@ export class CollaborationStore {
|
|
|
88
130
|
if (group.id === source.id) {
|
|
89
131
|
if (dissolved)
|
|
90
132
|
return group.federated ? [{ ...group, deleted: true, updatedAt: Math.max(now, group.updatedAt + 1) }] : [];
|
|
91
|
-
return [{ ...group, sessionIds: sourceIds,
|
|
133
|
+
return [pruneRoles({ ...group, sessionIds: sourceIds,
|
|
92
134
|
...(group.remoteSessions ? { remoteSessions: group.remoteSessions.filter((session) => sourceIds.includes(session.sessionId)) } : {}),
|
|
93
|
-
updatedAt: Math.max(now, group.updatedAt + 1) }];
|
|
135
|
+
updatedAt: Math.max(now, group.updatedAt + 1) }, sourceIds)];
|
|
94
136
|
}
|
|
95
137
|
return group.id === target.id ? [{ ...group, sessionIds: [...new Set([...group.sessionIds, input.sessionId])], updatedAt: Math.max(now, group.updatedAt + 1) }] : [group];
|
|
96
138
|
}),
|
|
@@ -109,7 +151,8 @@ export class CollaborationStore {
|
|
|
109
151
|
return [group];
|
|
110
152
|
if (dissolvedIds.has(group.id))
|
|
111
153
|
return group.federated ? [{ ...group, deleted: true, updatedAt: Math.max(now, group.updatedAt + 1) }] : [];
|
|
112
|
-
|
|
154
|
+
const remaining = group.sessionIds.filter((id) => id !== sessionId);
|
|
155
|
+
return [pruneRoles({ ...group, sessionIds: remaining, updatedAt: Math.max(now, group.updatedAt + 1) }, remaining)];
|
|
113
156
|
});
|
|
114
157
|
this.document.messages = this.document.messages.filter((message) => !dissolvedIds.has(message.groupId)
|
|
115
158
|
&& message.fromSessionId !== sessionId
|
|
@@ -160,6 +203,7 @@ export class CollaborationStore {
|
|
|
160
203
|
sequence: this.nextSequence(),
|
|
161
204
|
id: crypto.randomUUID(), groupId: group.id, fromSessionId: input.fromSessionId,
|
|
162
205
|
toSessionId, kind: input.kind, content, threadId,
|
|
206
|
+
fanOutIds: recipients.length > 1 ? recipients.filter((id) => id !== toSessionId) : undefined,
|
|
163
207
|
replyTo: input.replyTo?.trim() || null, status: 'pending', createdAt: now,
|
|
164
208
|
deliveredAt: null, readAt: null,
|
|
165
209
|
}));
|
|
@@ -195,10 +239,14 @@ export class CollaborationStore {
|
|
|
195
239
|
markDelivered(messageIds) {
|
|
196
240
|
return this.updateStatus(messageIds, 'delivered');
|
|
197
241
|
}
|
|
198
|
-
|
|
199
|
-
|
|
242
|
+
/**
|
|
243
|
+
* @param source 'explicit' — a consumer acknowledged the message (CLI/UI).
|
|
244
|
+
* Idempotent; never implies an application-level ACK.
|
|
245
|
+
*/
|
|
246
|
+
markRead(messageIds, source = 'explicit') {
|
|
247
|
+
return this.updateStatus(messageIds, 'read', source);
|
|
200
248
|
}
|
|
201
|
-
updateStatus(messageIds, status) {
|
|
249
|
+
updateStatus(messageIds, status, readSource = 'explicit') {
|
|
202
250
|
const ids = new Set(messageIds);
|
|
203
251
|
this.expire();
|
|
204
252
|
const now = Date.now();
|
|
@@ -207,7 +255,7 @@ export class CollaborationStore {
|
|
|
207
255
|
if (!ids.has(message.id) || message.status === 'expired' || message.status === 'failed' || message.status === 'read')
|
|
208
256
|
return message;
|
|
209
257
|
const updated = status === 'read'
|
|
210
|
-
? { ...message, status, deliveredAt: message.deliveredAt ?? now, readAt: now, readSource
|
|
258
|
+
? { ...message, status, deliveredAt: message.deliveredAt ?? now, readAt: now, readSource, deliverySource: message.deliverySource ?? (message.deliveredAt === null ? 'consumer_read' : 'legacy_or_unspecified') }
|
|
211
259
|
: message.status === 'pending' ? { ...message, status, deliveredAt: now, deliverySource: 'pty_written' } : message;
|
|
212
260
|
changed.push(updated);
|
|
213
261
|
return updated;
|
|
@@ -321,6 +369,13 @@ export class CollaborationStore {
|
|
|
321
369
|
this.persist();
|
|
322
370
|
}
|
|
323
371
|
}
|
|
372
|
+
setSnapshot(id, snapshot) {
|
|
373
|
+
const message = this.getMessage(id);
|
|
374
|
+
if (!message)
|
|
375
|
+
return;
|
|
376
|
+
message.snapshot = snapshot;
|
|
377
|
+
this.persist();
|
|
378
|
+
}
|
|
324
379
|
diagnostic(id) { return this.document.transport?.[id] ?? null; }
|
|
325
380
|
recordTransport(id, diagnostic) {
|
|
326
381
|
if (!this.getMessage(id))
|
|
@@ -337,6 +392,7 @@ export class CollaborationStore {
|
|
|
337
392
|
return { message_id: id, thread_id: message.threadId, status: message.status, queued_at: message.createdAt,
|
|
338
393
|
delivered_at: message.deliveredAt, read_at: message.readAt, expires_at: message.expiresAt ?? null,
|
|
339
394
|
failure_reason: message.failureReason ?? null, delivery_semantics: message.deliverySource ?? (message.deliveredAt === null ? 'not_delivered' : 'legacy_or_unspecified'), read_semantics: message.readAt ? message.readSource ?? 'legacy_or_unspecified' : 'not_read',
|
|
395
|
+
snapshot: message.snapshot ?? null,
|
|
340
396
|
idempotency_key: message.idempotencyKey ?? null,
|
|
341
397
|
ack_at: replies.find((reply) => reply.responseKind === 'ack')?.createdAt ?? null,
|
|
342
398
|
reply_ids: replies.map((reply) => reply.id), result_ids: replies.filter((reply) => reply.responseKind === 'result').map((reply) => reply.id),
|
|
@@ -354,11 +410,29 @@ export class CollaborationStore {
|
|
|
354
410
|
for (const message of reports)
|
|
355
411
|
if (message.task)
|
|
356
412
|
tasks.set(message.task.task_id, { task_id: message.task.task_id, status: message.task.status, reported_at: message.createdAt, message_id: message.id });
|
|
413
|
+
// Aggregated from the *latest* report of each task, never inferred: an
|
|
414
|
+
// agent handed a task that has not yet replied is simply idle here —
|
|
415
|
+
// dispatch in flight is the sender-side receipt's concern (pending), not a
|
|
416
|
+
// fact about this session. blocked counts as active: the agent holds the
|
|
417
|
+
// task open. A failed terminal state only surfaces once no task is still
|
|
418
|
+
// in flight, since another in-progress task means the agent is busy.
|
|
419
|
+
const latest = [...tasks.values()];
|
|
420
|
+
const taskState = latest.some((task) => task.status === 'ack' || task.status === 'working' || task.status === 'blocked') ? 'active'
|
|
421
|
+
: latest.some((task) => task.status === 'failed') ? 'failed'
|
|
422
|
+
: 'idle';
|
|
357
423
|
return { session_state: online ? 'online' : 'offline', turn_state: turnState === 'done' ? 'ended' : turnState ?? 'unknown',
|
|
358
|
-
task_state:
|
|
424
|
+
task_state: taskState, tasks: [...tasks.values()], last_heartbeat: null, last_tool_activity_at: null,
|
|
359
425
|
last_message_at: reports.length ? Math.max(...reports.map((message) => message.createdAt)) : null,
|
|
360
426
|
state_source: 'optional_adapter', task_source: 'explicit_message' };
|
|
361
427
|
}
|
|
428
|
+
/** Timeline of one task as reported by a session, oldest first. The lineage
|
|
429
|
+
* starts at the recipient's first reply carrying that task_id — a bare
|
|
430
|
+
* dispatch carries no envelope. Messages without a task simply never
|
|
431
|
+
* match. */
|
|
432
|
+
byTask(sessionId, taskId) {
|
|
433
|
+
this.expire();
|
|
434
|
+
return this.document.messages.filter((message) => message.fromSessionId === sessionId && message.task?.task_id === taskId);
|
|
435
|
+
}
|
|
362
436
|
page(sessionId, options = {}) {
|
|
363
437
|
this.expire();
|
|
364
438
|
if (options.responseKind && !['ack', 'progress', 'result'].includes(options.responseKind))
|
|
@@ -1,10 +1,95 @@
|
|
|
1
1
|
import { buildBracketedSubmitBytes } from './promptDelivery.js';
|
|
2
|
-
|
|
2
|
+
const TMUX_KEY_NAMES = {
|
|
3
|
+
enter: 'Enter', escape: 'Escape', space: 'Space', left: 'Left', right: 'Right', up: 'Up', down: 'Down',
|
|
4
|
+
};
|
|
5
|
+
/** Screen text that means an interactive approval/confirm dialog is showing.
|
|
6
|
+
* Driving approve sends Enter, which activates the highlighted option — the
|
|
7
|
+
* number of tools that render such prompts is finite, but the phrasing is
|
|
8
|
+
* not, so match generously and never press keys without this evidence. */
|
|
9
|
+
const APPROVAL_DIALOG_PATTERNS = [
|
|
10
|
+
/requires approval/i,
|
|
11
|
+
/needs? approval/i,
|
|
12
|
+
/do you want to proceed/i,
|
|
13
|
+
/do you want to (continue|run this)/i,
|
|
14
|
+
/is this ok/i,
|
|
15
|
+
/\b1\.\s*yes\b/i,
|
|
16
|
+
/需要批准|需要授权|等待批准/i,
|
|
17
|
+
/是否(允许|继续|执行|批准)/,
|
|
18
|
+
/\b1\.\s*是\b/,
|
|
19
|
+
/允许.*执行|确认.*执行/i,
|
|
20
|
+
];
|
|
21
|
+
/** Agent TUIs collapse pasted input in the input box to a marker line like
|
|
22
|
+
* "[Pasted text #3 +42 lines]". While the paste sits unsubmitted the marker
|
|
23
|
+
* stays on screen; a submitted paste clears the box and only the transcript
|
|
24
|
+
* holds the content. Session-unique #N lets us diff screen captures. */
|
|
25
|
+
const PASTE_MARKER_PATTERN = /\[Pasted text #(\d+)/g;
|
|
26
|
+
export function detectApprovalDialog(content) {
|
|
27
|
+
return APPROVAL_DIALOG_PATTERNS.some((pattern) => pattern.test(content));
|
|
28
|
+
}
|
|
29
|
+
/** Paste marker sequence numbers visible in a screen capture. */
|
|
30
|
+
export function extractPasteMarkerNumbers(content) {
|
|
31
|
+
return new Set([...content.matchAll(PASTE_MARKER_PATTERN)].map((match) => Number(match[1])));
|
|
32
|
+
}
|
|
33
|
+
/** True when `after` shows a paste marker that was not in `before`. Diffing
|
|
34
|
+
* prevents a recovery Enter from submitting a stale paste that predates this
|
|
35
|
+
* delivery (or someone else's draft): we only ever submit what we just put
|
|
36
|
+
* there, at most once per delivery. */
|
|
37
|
+
export function hasNewPasteMarker(after, before) {
|
|
38
|
+
const beforeNumbers = extractPasteMarkerNumbers(before);
|
|
39
|
+
return [...extractPasteMarkerNumbers(after)].some((number) => !beforeNumbers.has(number));
|
|
40
|
+
}
|
|
41
|
+
async function assertSamePane(run, pane) {
|
|
3
42
|
const identity = (await run(['display-message', '-p', '-t', pane.paneId,
|
|
4
43
|
'#{pid}:#{session_id}:#{pane_id}:#{pane_pid}'])).trim();
|
|
5
44
|
if (identity !== `${pane.serverPid}:${pane.sessionId}:${pane.paneId}:${pane.panePid}`)
|
|
6
45
|
throw new Error('TMUX_PANE_CHANGED');
|
|
46
|
+
}
|
|
47
|
+
export async function writeCollaborationTmuxPane(run, pane, prompt) {
|
|
48
|
+
await assertSamePane(run, pane);
|
|
7
49
|
// A fixed pane target is independent of the current window, keyboard focus
|
|
8
50
|
// and browser presence. One invocation preserves the bracketed submit bytes.
|
|
9
51
|
await run(['send-keys', '-t', pane.paneId, '-l', '--', buildBracketedSubmitBytes(prompt)]);
|
|
10
52
|
}
|
|
53
|
+
/** Inject a named key into a member pane after proving the pane identity
|
|
54
|
+
* unchanged. Named keys only — no freeform keystroke passthrough. */
|
|
55
|
+
export async function sendTmuxPaneKey(run, pane, key) {
|
|
56
|
+
await assertSamePane(run, pane);
|
|
57
|
+
await run(['send-keys', '-t', pane.paneId, TMUX_KEY_NAMES[key]]);
|
|
58
|
+
}
|
|
59
|
+
/** Current pane screen text (viewport), identity-checked. */
|
|
60
|
+
export async function captureTmuxPaneText(run, pane) {
|
|
61
|
+
await assertSamePane(run, pane);
|
|
62
|
+
const raw = await run(['capture-pane', '-p', '-J', '-t', pane.paneId]);
|
|
63
|
+
return raw.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '').replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '');
|
|
64
|
+
}
|
|
65
|
+
/** Dismiss an interactive approval dialog on the member pane by pressing
|
|
66
|
+
* Enter (the highlighted option). Refuses unless the screen actually shows
|
|
67
|
+
* an approval prompt: driving a dialog that isn't there would only inject
|
|
68
|
+
* a stray keystroke into the agent's work. Returns false when no dialog. */
|
|
69
|
+
export async function approveCollaborationDialog(run, pane) {
|
|
70
|
+
const content = await captureTmuxPaneText(run, pane);
|
|
71
|
+
if (!detectApprovalDialog(content))
|
|
72
|
+
return false;
|
|
73
|
+
await sendTmuxPaneKey(run, pane, 'enter');
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
/** Submit a paste that arrived while the agent was finishing its previous
|
|
77
|
+
* turn and is still sitting unsubmitted in the input box. Only acts on
|
|
78
|
+
* differential evidence — a paste marker that appeared after the write —
|
|
79
|
+
* never on pre-existing stale markers, and returns whether Enter was sent.
|
|
80
|
+
* Committing the whole input buffer is safe: each delivery is written once,
|
|
81
|
+
* so each marker's content enters the transcript exactly once. */
|
|
82
|
+
export async function recoverStuckPaste(run, pane, baseline) {
|
|
83
|
+
const viewport = await captureTmuxPaneText(run, pane);
|
|
84
|
+
if (!hasNewPasteMarker(viewport, baseline))
|
|
85
|
+
return false;
|
|
86
|
+
await sendTmuxPaneKey(run, pane, 'enter');
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
/** History-inclusive pane text (scrollback + viewport) used to confirm a
|
|
90
|
+
* delivery was actually rendered by the agent, not wiped by a boot clear. */
|
|
91
|
+
export async function captureTmuxPaneHistory(run, pane, lines = 800) {
|
|
92
|
+
await assertSamePane(run, pane);
|
|
93
|
+
const raw = await run(['capture-pane', '-p', '-J', '-S', `-${lines}`, '-t', pane.paneId]);
|
|
94
|
+
return raw.replace(/\x1b\][^\x07]*(\x07|\x1b\\)/g, '').replace(/\x1b\[[0-9;?]*[a-zA-Z]/g, '');
|
|
95
|
+
}
|
package/dist/server/cli.js
CHANGED
|
@@ -1244,12 +1244,13 @@ async function getLocalJson(baseUrl, token, endpoint, timeoutMs = 30_000) {
|
|
|
1244
1244
|
});
|
|
1245
1245
|
}
|
|
1246
1246
|
async function runCollab(command) {
|
|
1247
|
-
if (command.action === 'help') {
|
|
1248
|
-
console.log(COLLAB_HELP);
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
1247
|
const runningState = getRunningState();
|
|
1252
1248
|
if (!runningState?.localApiToken) {
|
|
1249
|
+
// Help is a plain read: it must stay available without a running server.
|
|
1250
|
+
if (command.action === 'help') {
|
|
1251
|
+
console.log(COLLAB_HELP);
|
|
1252
|
+
return;
|
|
1253
|
+
}
|
|
1253
1254
|
console.error(JSON.stringify({ ok: false, code: 'SERVICE_UNAVAILABLE', error: 'Termdock is not running or its local API token is unavailable.' }));
|
|
1254
1255
|
process.exit(1);
|
|
1255
1256
|
}
|
|
@@ -1275,6 +1276,12 @@ async function runCollab(command) {
|
|
|
1275
1276
|
}
|
|
1276
1277
|
}
|
|
1277
1278
|
if (!backendSessionId && !tmuxSessionName) {
|
|
1279
|
+
// Bare help stays reachable from a plain shell; the roster snapshot in
|
|
1280
|
+
// help needs a session identity and only appears when one is available.
|
|
1281
|
+
if (command.action === 'help') {
|
|
1282
|
+
console.log(COLLAB_HELP);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1278
1285
|
console.error(JSON.stringify({ ok: false, code: 'SESSION_NOT_FOUND', error: 'td collab must run inside a Termdock-managed Session.' }));
|
|
1279
1286
|
process.exit(1);
|
|
1280
1287
|
}
|