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
|
@@ -5,13 +5,17 @@ export const COLLAB_HELP = `td collab — durable messages; no agent-specific ho
|
|
|
5
5
|
status | capabilities
|
|
6
6
|
rebind [--pane %3] (explicitly bind this peer to its current Agent; resumes queued delivery)
|
|
7
7
|
send <session-id> <message> | reply <message-id> <message> | handoff <session-id> <message>
|
|
8
|
+
(send fan-out: comma-separated same-group ids, e.g. send a,b "任务"; every
|
|
9
|
+
recipient sees it as 群发 with the sibling list; waiting options apply to
|
|
10
|
+
single-recipient sends only)
|
|
8
11
|
--group <id> --thread <id> --idempotency-key <key>
|
|
9
12
|
--file <path> | --stdin (instead of inline body; -- ends option parsing)
|
|
10
13
|
--wait-until queued|delivered|read --timeout 30s
|
|
11
14
|
--expect-reply ack|result|any
|
|
12
15
|
--response-kind ack|progress|result --metadata '<JSON object>'
|
|
13
|
-
--task-envelope '<JSON: task_id,status,progress?,evidence?,blocker?>'
|
|
14
16
|
--expires-at <ISO timestamp or epoch milliseconds>
|
|
17
|
+
reply: --task-envelope '<JSON: task_id,status,progress?,evidence?,blocker?>'
|
|
18
|
+
(status reporting happens only via reply; send/handoff dispatch carries no task state)
|
|
15
19
|
message get <message-id> [--receipt-only]
|
|
16
20
|
message read <message-id> (explicit consumption; not application ACK)
|
|
17
21
|
message watch <message-id> [--wait-until delivered|read] [--timeout 30s]
|
|
@@ -22,14 +26,41 @@ export const COLLAB_HELP = `td collab — durable messages; no agent-specific ho
|
|
|
22
26
|
cursor commit <token> --consumer <name> (commit after processing the page)
|
|
23
27
|
add|remove <group-id> <session-id>
|
|
24
28
|
spawn <group-id> <agent-slug> [--name <name>] [--cwd <path>] [--task <text>]
|
|
29
|
+
role list <group-id>
|
|
30
|
+
role set <group-id> <session-id> <role…> (trailing words join as the role)
|
|
31
|
+
role unset <group-id> <session-id>
|
|
32
|
+
(roles are shared within the group; members set each other's, your own
|
|
33
|
+
rides the delivery shell header)
|
|
34
|
+
rename <session-id> <name…> (rename a member of any shared group;
|
|
35
|
+
trailing words join as the new name; roster and shells show it at once)
|
|
36
|
+
drive <session-id> approve|enter|escape|space|left|right|up|down|capture
|
|
37
|
+
drive <session-id> run <command…>
|
|
38
|
+
(drive the terminal of a member session you share a group with — terminal
|
|
39
|
+
operations are shell operations: approve dismisses an interactive approval
|
|
40
|
+
dialog and refuses unless one is actually showing; named keys inject one
|
|
41
|
+
key; capture reads the current screen back; run submits one line and
|
|
42
|
+
returns the screen. Cannot target your own session. Treat every drive as
|
|
43
|
+
strong control: the member's shell executes what you send.)
|
|
44
|
+
cleanup <session-id>… 移除协作会话并终止其 tmux/进程(仅限与你同组的会话;
|
|
45
|
+
不能清理当前会话自身,也不能通过清理解散你所在的组)
|
|
46
|
+
风险操作:默认只打印清理计划并拒绝执行(exit 1)——这是不可恢复的删除。
|
|
47
|
+
必须先向用户(人类)说明将清理的会话与影响并获得其明确同意,
|
|
48
|
+
才能以 --confirm 重跑执行;每次执行都需要当场的人类授权。
|
|
25
49
|
--json (default) | --jsonl | --text
|
|
26
50
|
Exit codes: 0 requested condition met; 1 invalid request/network error;
|
|
51
|
+
cleanup without --confirm prints the plan and refuses (also 1);
|
|
27
52
|
2 wait timeout (message may still deliver); 3 failed/expired.
|
|
28
53
|
Message limit: ${COLLAB_LIMITS.message_bytes} UTF-8 bytes; metadata: ${COLLAB_LIMITS.metadata_bytes} bytes.
|
|
29
54
|
Idempotency retention: 7 days. read/ACK/result never imply each other.
|
|
30
55
|
Inbox defaults to unread first, newest 50; cursor/consumer mode reads oldest unseen first.
|
|
31
56
|
Reading never advances a consumer or marks messages read automatically.`;
|
|
32
|
-
|
|
57
|
+
/** One roster row: prefer the member's human name, keep the full session id
|
|
58
|
+
* reachable for role set/unset targeting, mark unset members explicitly. */
|
|
59
|
+
function roleLine(member, role) {
|
|
60
|
+
const label = member.name ? `${member.name} (${member.sessionId})` : member.sessionId;
|
|
61
|
+
return role ? `- ${label}:${role}` : `- ${label}(未设置)`;
|
|
62
|
+
}
|
|
63
|
+
const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'unread', 'follow', 'stdin', 'receipt-only', 'confirm', 'help']);
|
|
33
64
|
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']);
|
|
34
65
|
export function parseCollaborationCommand(argv) {
|
|
35
66
|
const options = {};
|
|
@@ -61,7 +92,7 @@ export function parseCollaborationCommand(argv) {
|
|
|
61
92
|
positional.push(value);
|
|
62
93
|
}
|
|
63
94
|
const action = (options.help ? 'help' : positional.shift() ?? 'status');
|
|
64
|
-
if (!['status', 'inbox', 'send', 'handoff', 'reply', 'add', 'remove', 'spawn', 'message', 'cursor', 'rebind', 'capabilities', 'help'].includes(action))
|
|
95
|
+
if (!['status', 'inbox', 'send', 'handoff', 'reply', 'add', 'remove', 'spawn', 'message', 'cursor', 'rebind', 'role', 'rename', 'cleanup', 'drive', 'capabilities', 'help'].includes(action))
|
|
65
96
|
throw new Error('Unknown collaboration command; see td collab --help');
|
|
66
97
|
if (options.pane && !/^%\d+$/.test(String(options.pane)))
|
|
67
98
|
throw new Error('pane must be a tmux pane id such as %3');
|
|
@@ -106,17 +137,63 @@ export function parseCollaborationCommand(argv) {
|
|
|
106
137
|
command.cwd = options.cwd;
|
|
107
138
|
command.task = options.task;
|
|
108
139
|
}
|
|
140
|
+
else if (action === 'role') {
|
|
141
|
+
command.operation = positional.shift();
|
|
142
|
+
command.groupId = positional.shift();
|
|
143
|
+
if (command.operation === 'list') {
|
|
144
|
+
if (!command.groupId || positional.length)
|
|
145
|
+
throw new Error('Usage: td collab role list <group-id>');
|
|
146
|
+
}
|
|
147
|
+
else if (command.operation === 'set') {
|
|
148
|
+
command.sessionId = positional.shift();
|
|
149
|
+
command.role = positional.join(' ');
|
|
150
|
+
if (!command.groupId || !command.sessionId || !command.role?.trim())
|
|
151
|
+
throw new Error('Usage: td collab role set <group-id> <session-id> <role…>');
|
|
152
|
+
}
|
|
153
|
+
else if (command.operation === 'unset') {
|
|
154
|
+
command.sessionId = positional.shift();
|
|
155
|
+
if (!command.groupId || !command.sessionId || positional.length)
|
|
156
|
+
throw new Error('Usage: td collab role unset <group-id> <session-id>');
|
|
157
|
+
}
|
|
158
|
+
else
|
|
159
|
+
throw new Error('Usage: td collab role list|set|unset (see td collab --help)');
|
|
160
|
+
}
|
|
161
|
+
else if (action === 'rename') {
|
|
162
|
+
command.sessionId = positional.shift();
|
|
163
|
+
command.name = positional.join(' ');
|
|
164
|
+
if (!command.sessionId || !command.name?.trim())
|
|
165
|
+
throw new Error('Usage: td collab rename <session-id> <name…>');
|
|
166
|
+
}
|
|
167
|
+
else if (action === 'cleanup') {
|
|
168
|
+
command.sessionIds = [...positional];
|
|
169
|
+
if (command.sessionIds.length === 0)
|
|
170
|
+
throw new Error('Usage: td collab cleanup <session-id> [<session-id>…]');
|
|
171
|
+
}
|
|
172
|
+
else if (action === 'drive') {
|
|
173
|
+
command.sessionId = positional.shift();
|
|
174
|
+
command.operation = positional.shift();
|
|
175
|
+
if (!command.sessionId || !command.operation)
|
|
176
|
+
throw new Error('Usage: td collab drive <session-id> approve|enter|escape|space|left|right|up|down|capture|run <command…>');
|
|
177
|
+
if (command.operation === 'run') {
|
|
178
|
+
command.message = positional.join(' ');
|
|
179
|
+
if (!command.message?.trim())
|
|
180
|
+
throw new Error('Usage: td collab drive <session-id> run <command…>');
|
|
181
|
+
}
|
|
182
|
+
else if (positional.length || !['approve', 'enter', 'escape', 'space', 'left', 'right', 'up', 'down', 'capture'].includes(command.operation)) {
|
|
183
|
+
throw new Error(`Unknown drive action ${command.operation ?? ''}; use approve|enter|escape|space|left|right|up|down|capture|run`);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
109
186
|
else if (positional.length && action !== 'help')
|
|
110
187
|
throw new Error(`Unexpected arguments for ${action}`);
|
|
111
188
|
const allowed = new Set(['json', 'jsonl', 'text', 'help']);
|
|
112
189
|
const byAction = {
|
|
113
190
|
status: [], capabilities: [], rebind: ['pane'], help: [...BOOLEAN_OPTIONS, ...VALUE_OPTIONS],
|
|
114
|
-
send: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', '
|
|
115
|
-
handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', '
|
|
191
|
+
send: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at', 'kind'],
|
|
192
|
+
handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at'],
|
|
116
193
|
reply: ['idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
|
|
117
194
|
inbox: ['unread', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'group', 'thread', 'kind', 'response-kind', 'follow', 'timeout'],
|
|
118
195
|
message: ['receipt-only', 'follow', 'wait-until', 'timeout', 'expect-reply'], cursor: ['consumer'],
|
|
119
|
-
add: [], remove: [], spawn: ['name', 'cwd', 'task'],
|
|
196
|
+
add: [], remove: [], spawn: ['name', 'cwd', 'task'], role: [], rename: [], cleanup: ['confirm'], drive: [],
|
|
120
197
|
};
|
|
121
198
|
for (const option of byAction[action])
|
|
122
199
|
allowed.add(option);
|
|
@@ -160,12 +237,28 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
160
237
|
const output = (value) => {
|
|
161
238
|
if (o.text) {
|
|
162
239
|
if (Array.isArray(value.messages))
|
|
163
|
-
for (const message of value.messages)
|
|
164
|
-
|
|
240
|
+
for (const message of value.messages) {
|
|
241
|
+
const fanIds = Array.isArray(message.fanOutIds) ? message.fanOutIds : [];
|
|
242
|
+
const names = value.names ?? {};
|
|
243
|
+
io.write(`[${message.responseKind ?? message.kind}] ${message.id} from ${message.fromSessionId ?? 'user'}${fanIds.length ? ' · 群发' : ''}`);
|
|
244
|
+
// A fan-out dispatch names its sibling recipients so a broadcast is
|
|
245
|
+
// never read as a one-to-one assignment; unknown ids stay raw.
|
|
246
|
+
if (fanIds.length)
|
|
247
|
+
io.write(`同时发给了:${fanIds.map((id) => names[id] ?? id).join('、')}`);
|
|
248
|
+
io.write(message.content);
|
|
249
|
+
}
|
|
165
250
|
else if (value.message)
|
|
166
251
|
io.write(`[${value.status}] ${value.message_id}\n${value.message.content}`);
|
|
167
|
-
else if (value.message_id)
|
|
252
|
+
else if (value.message_id) {
|
|
168
253
|
io.write(`${value.status} ${value.message_id} thread=${value.thread_id}${value.code ? ` ${value.code}` : ''}${value.failure_reason ? ` ${value.failure_reason}` : ''}`);
|
|
254
|
+
// A timeout after the wait stage itself was reached means delivery
|
|
255
|
+
// (or reading) completed — only the expected reply/result is late.
|
|
256
|
+
if (value.code === 'WAIT_TIMEOUT' && value.stage_reached && value.expect_reply) {
|
|
257
|
+
io.write(`\n投递已完成;等待${value.expect_reply === 'result' ? '结果' : '回复'}超时(expect-reply=${value.expect_reply})`);
|
|
258
|
+
}
|
|
259
|
+
if (value.snapshot)
|
|
260
|
+
io.write(`\n${value.snapshot}`);
|
|
261
|
+
}
|
|
169
262
|
else
|
|
170
263
|
io.write(JSON.stringify(value, null, 2));
|
|
171
264
|
if (value.next_cursor)
|
|
@@ -184,6 +277,24 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
184
277
|
try {
|
|
185
278
|
if (command.action === 'help') {
|
|
186
279
|
io.write(COLLAB_HELP);
|
|
280
|
+
// Append the caller's own groups with every member role — help doubles
|
|
281
|
+
// as the newcomer's one-shot introduction to the current roster. The
|
|
282
|
+
// snapshot requires a managed session, so it degrades to bare help
|
|
283
|
+
// outside one (a plain CLI read still prints the full surface).
|
|
284
|
+
try {
|
|
285
|
+
const mine = await request('GET', '/role');
|
|
286
|
+
const groups = mine.groups ?? [];
|
|
287
|
+
if (groups.length) {
|
|
288
|
+
io.write('\n本会话所在协作组的成员定位:');
|
|
289
|
+
for (const group of groups) {
|
|
290
|
+
io.write(`组「${group.name ?? ''}」(${group.sessionIds.length} 个成员)`);
|
|
291
|
+
const members = new Map((group.members ?? []).map((member) => [member.sessionId, member.name ?? null]));
|
|
292
|
+
for (const id of group.sessionIds)
|
|
293
|
+
io.write(roleLine({ sessionId: id, name: members.get(id) ?? null }, group.roles?.[id]));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch { /* help stays available without a server or session */ }
|
|
187
298
|
return 0;
|
|
188
299
|
}
|
|
189
300
|
if (command.action === 'capabilities' || command.action === 'status') {
|
|
@@ -252,13 +363,114 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
252
363
|
idempotencyKey = String(o['idempotency-key'] ?? randomUUID());
|
|
253
364
|
const extras = { group_id: o.group, thread_id: o.thread, idempotency_key: idempotencyKey, response_kind: o['response-kind'],
|
|
254
365
|
metadata: o.metadata ? JSON.parse(String(o.metadata)) : undefined, task: o['task-envelope'] ? JSON.parse(String(o['task-envelope'])) : undefined, expires_at: expiresAt };
|
|
366
|
+
// A comma-separated target list fans one dispatch out to several
|
|
367
|
+
// recipients of a shared group; every edge carries the sibling list so
|
|
368
|
+
// recipients see the 群发 framing. Reply stays one-to-one by nature.
|
|
369
|
+
const targets = command.target.split(',').map((id) => id.trim()).filter(Boolean);
|
|
370
|
+
const fanOut = command.action !== 'reply' && targets.length > 1;
|
|
371
|
+
if (command.action !== 'reply' && !targets.length)
|
|
372
|
+
throw new Error(`${command.action} requires a target session id`);
|
|
373
|
+
if (fanOut && (o['wait-until'] || o['expect-reply']))
|
|
374
|
+
throw new Error('--wait-until/--expect-reply need a single recipient; fan-out confirms queued delivery only');
|
|
255
375
|
deadline = now() + duration(String(o.timeout ?? '30s'));
|
|
256
376
|
receipt = await request('POST', command.action === 'reply' ? '/reply' : '/send', command.action === 'reply'
|
|
257
|
-
? { ...extras, messageId: command.target, content }
|
|
377
|
+
? { ...extras, messageId: command.target, content }
|
|
378
|
+
: { ...extras, ...(fanOut ? { toSessionIds: targets } : { targetSessionId: targets[0] }), message: content, kind: command.action === 'handoff' ? 'handoff' : o.kind ?? 'message' });
|
|
258
379
|
// CLI receipts stay small even when the message is a large evidence package.
|
|
259
380
|
delete receipt.messages;
|
|
260
381
|
receipt.idempotency_key = idempotencyKey;
|
|
261
382
|
}
|
|
383
|
+
else if (command.action === 'role') {
|
|
384
|
+
if (command.operation === 'list') {
|
|
385
|
+
const body = await request('GET', `/role?group=${encodeURIComponent(command.groupId)}`);
|
|
386
|
+
if (o.text) {
|
|
387
|
+
const group = body.group;
|
|
388
|
+
io.write(`定位表(${group.name ? `组「${group.name}」· ` : ''}${group.sessionIds.length} 个成员):`);
|
|
389
|
+
const members = new Map((group.members ?? []).map((member) => [member.sessionId, member.name ?? null]));
|
|
390
|
+
for (const id of group.sessionIds)
|
|
391
|
+
io.write(roleLine({ sessionId: id, name: members.get(id) ?? null }, group.roles?.[id]));
|
|
392
|
+
}
|
|
393
|
+
else
|
|
394
|
+
output(body);
|
|
395
|
+
return 0;
|
|
396
|
+
}
|
|
397
|
+
const body = await request('POST', '/role', { group_id: command.groupId, session_id: command.sessionId,
|
|
398
|
+
role: command.operation === 'unset' ? null : command.role });
|
|
399
|
+
if (o.text) {
|
|
400
|
+
io.write(command.operation === 'set'
|
|
401
|
+
? `定位已设置:${command.sessionId} = ${body.group.roles?.[command.sessionId] ?? ''}`
|
|
402
|
+
: `定位已清除:${command.sessionId}`);
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
output(body);
|
|
406
|
+
return 0;
|
|
407
|
+
}
|
|
408
|
+
else if (command.action === 'rename') {
|
|
409
|
+
const body = await request('POST', '/name', { session_id: command.sessionId, name: command.name });
|
|
410
|
+
if (o.text)
|
|
411
|
+
io.write(`已改名:${command.sessionId} = ${body.name ?? command.name}`);
|
|
412
|
+
else
|
|
413
|
+
output(body);
|
|
414
|
+
return 0;
|
|
415
|
+
}
|
|
416
|
+
else if (command.action === 'drive') {
|
|
417
|
+
const body = await request('POST', '/drive', { session: command.sessionId, action: command.operation, text: command.message });
|
|
418
|
+
const result = body;
|
|
419
|
+
if (o.text) {
|
|
420
|
+
if (command.operation === 'run') {
|
|
421
|
+
io.write(`已向 ${command.sessionId} 发送一行并提交:${command.message}`);
|
|
422
|
+
if (result.snapshot)
|
|
423
|
+
io.write(`--- ${command.sessionId} 当前屏幕 ---\n${result.snapshot}`);
|
|
424
|
+
}
|
|
425
|
+
else if (command.operation === 'capture') {
|
|
426
|
+
if (result.snapshot)
|
|
427
|
+
io.write(result.snapshot);
|
|
428
|
+
else
|
|
429
|
+
io.write('(无快照)');
|
|
430
|
+
}
|
|
431
|
+
else if (command.operation === 'approve') {
|
|
432
|
+
io.write(result.approved ? `已批准 ${command.sessionId} 的审批对话框` : '目标面板当前没有显示审批对话框;未发送任何按键');
|
|
433
|
+
}
|
|
434
|
+
else
|
|
435
|
+
io.write(`已向 ${command.sessionId} 发送 ${command.operation}`);
|
|
436
|
+
}
|
|
437
|
+
else
|
|
438
|
+
output(body);
|
|
439
|
+
return 0;
|
|
440
|
+
}
|
|
441
|
+
else if (command.action === 'cleanup') {
|
|
442
|
+
const plan = await request('POST', '/cleanup', { sessionIds: command.sessionIds, confirmed: Boolean(o.confirm) });
|
|
443
|
+
const targets = (plan.plan?.targets ?? []);
|
|
444
|
+
const groups = (plan.plan?.groups ?? []);
|
|
445
|
+
if (plan.ok !== true) {
|
|
446
|
+
if (o.text) {
|
|
447
|
+
io.write('清理计划(未执行 —— 需要人类授权)');
|
|
448
|
+
for (const target of targets)
|
|
449
|
+
io.write(`- ${target.name ?? target.sessionId}(${target.sessionId})${target.mode === 'tmux' && target.tmuxSessionName ? ` · tmux ${target.tmuxSessionName}` : target.mode ? ` · ${target.mode}` : ''}`);
|
|
450
|
+
if (groups.length) {
|
|
451
|
+
io.write('协作组影响:');
|
|
452
|
+
for (const group of groups)
|
|
453
|
+
io.write(group.dissolves
|
|
454
|
+
? `- 组「${group.name ?? group.id}」将被解散(组内消息一并清除)`
|
|
455
|
+
: `- 组「${group.name ?? group.id}」:${group.sizeBefore} 个成员 → ${group.sizeAfter} 个成员`);
|
|
456
|
+
}
|
|
457
|
+
io.write('⚠ 这是不可恢复的风险操作:将移除上述会话记录并终止其 tmux/进程。');
|
|
458
|
+
io.write('执行前必须先向用户(人类)说明以上内容并获得明确同意,再以 --confirm 重跑本命令;每次执行都需要当场的人类授权。');
|
|
459
|
+
}
|
|
460
|
+
else
|
|
461
|
+
output(plan);
|
|
462
|
+
return 1;
|
|
463
|
+
}
|
|
464
|
+
const removed = (plan.removed ?? []);
|
|
465
|
+
if (o.text) {
|
|
466
|
+
io.write(`已清理 ${removed.length} 个会话:`);
|
|
467
|
+
for (const entry of removed)
|
|
468
|
+
io.write(`- ${entry.name ?? entry.sessionId}(${entry.sessionId}):${entry.tmuxSessionName ? (entry.alreadyGone ? 'tmux 已不存在,记录已移除' : `tmux ${entry.tmuxSessionName} 已终止`) : entry.killed ? '进程已终止,记录已移除' : '记录已移除'}`);
|
|
469
|
+
}
|
|
470
|
+
else
|
|
471
|
+
output(plan);
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
262
474
|
else {
|
|
263
475
|
const body = command.action === 'spawn' ? { groupId: command.groupId, agentSlug: command.agentSlug, name: command.name, cwd: command.cwd, task: command.task }
|
|
264
476
|
: { groupId: command.groupId, targetSessionId: command.sessionId, action: command.action };
|
|
@@ -284,7 +496,8 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
284
496
|
}
|
|
285
497
|
}
|
|
286
498
|
if (now() >= deadline) {
|
|
287
|
-
output({ ...receipt, ok: false, code: 'WAIT_TIMEOUT', wait_until: stage,
|
|
499
|
+
output({ ...receipt, ok: false, code: 'WAIT_TIMEOUT', wait_until: stage, stage_reached: waitSatisfied(receipt, stage, undefined),
|
|
500
|
+
expect_reply: o['expect-reply'] ?? null, delivery_continues: true });
|
|
288
501
|
return 2;
|
|
289
502
|
}
|
|
290
503
|
await sleep(Math.min(500, deadline - now()));
|
|
@@ -4,6 +4,14 @@ export class CollaborationDeliveryWorker {
|
|
|
4
4
|
states = new Map();
|
|
5
5
|
failures = new Map();
|
|
6
6
|
submitted = new Set();
|
|
7
|
+
/** Sessions whose latest delivery was confirmed rendered in this process,
|
|
8
|
+
* mapped to the timestamp until which confirmations are skipped. A recent
|
|
9
|
+
* confirmed delivery means the agent is actively consuming input, so
|
|
10
|
+
* follow-up messages settle immediately; once the cooldown lapses the
|
|
11
|
+
* agent's state is unknown again (it may be mid-turn, or finishing one)
|
|
12
|
+
* and the gate re-engages. Settled-but-unconfirmed deliveries never grant
|
|
13
|
+
* cooldown — that would re-open the turn-handover hole the gate closes. */
|
|
14
|
+
confirmedUntil = new Map();
|
|
7
15
|
timer = null;
|
|
8
16
|
ticking = null;
|
|
9
17
|
constructor(options) {
|
|
@@ -111,10 +119,33 @@ export class CollaborationDeliveryWorker {
|
|
|
111
119
|
return;
|
|
112
120
|
}
|
|
113
121
|
const message = pending[0];
|
|
122
|
+
const attempts = store.diagnostic(message.id)?.attempt_count ?? 0;
|
|
123
|
+
// Confirm gate: the route becoming ready only proves the agent process is
|
|
124
|
+
// up, not that its TUI is consuming — a write landing while the agent
|
|
125
|
+
// finishes another turn can sit unsubmitted in its input box. When the
|
|
126
|
+
// route can read terminal history, hold deliveries outside the session's
|
|
127
|
+
// confirm cooldown until our message appears in the history (the agent
|
|
128
|
+
// rendered it). Unconfirmed writes are re-attempted up to the configured
|
|
129
|
+
// bound; at-least-once transport is preserved throughout.
|
|
130
|
+
const confirmMs = this.options.firstDeliveryConfirmMs ?? 1_500;
|
|
131
|
+
const gateActive = Boolean(route.confirm) && confirmMs > 0
|
|
132
|
+
&& Date.now() >= (this.confirmedUntil.get(id) ?? 0)
|
|
133
|
+
&& attempts + 1 < (this.options.maxUnconfirmedWrites ?? 3);
|
|
134
|
+
// Differential baseline for stuck-paste recovery: captured before the
|
|
135
|
+
// write so a later screen diff can tell our paste apart from stale ones.
|
|
136
|
+
let baseline = '';
|
|
137
|
+
if (gateActive && route.recoverStuck && !this.submitted.has(message.id) && route.capture) {
|
|
138
|
+
try {
|
|
139
|
+
baseline = (await route.capture()) ?? '';
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
baseline = '';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
114
145
|
if (!this.submitted.has(message.id)) {
|
|
115
146
|
store.recordTransport(message.id, {
|
|
116
147
|
relay_online: null, peer_reachable: true,
|
|
117
|
-
attempt_count:
|
|
148
|
+
attempt_count: attempts + 1,
|
|
118
149
|
next_retry_at: Date.now() + 2_000, last_error: 'DELIVERY_IN_PROGRESS', checked_at: Date.now(),
|
|
119
150
|
});
|
|
120
151
|
try {
|
|
@@ -125,11 +156,73 @@ export class CollaborationDeliveryWorker {
|
|
|
125
156
|
return;
|
|
126
157
|
}
|
|
127
158
|
this.submitted.add(message.id);
|
|
159
|
+
// Capture the recipient terminal before marking delivered, so a sender
|
|
160
|
+
// waiting on delivered always finds the snapshot present. The capture is
|
|
161
|
+
// strictly best-effort: a failure must never block delivery completion.
|
|
162
|
+
if (route.capture) {
|
|
163
|
+
try {
|
|
164
|
+
const snapshot = await route.capture();
|
|
165
|
+
if (snapshot)
|
|
166
|
+
store.setSnapshot(message.id, snapshot);
|
|
167
|
+
}
|
|
168
|
+
catch { /* best-effort */ }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (gateActive) {
|
|
172
|
+
if (!(await this.confirmConsumed(pending, route, confirmMs, baseline))) {
|
|
173
|
+
this.submitted.delete(message.id);
|
|
174
|
+
store.recordTransport(message.id, {
|
|
175
|
+
relay_online: null, peer_reachable: true,
|
|
176
|
+
attempt_count: attempts + 1,
|
|
177
|
+
next_retry_at: Date.now() + 4_000, last_error: 'AGENT_CONSUME_UNCONFIRMED', checked_at: Date.now(),
|
|
178
|
+
});
|
|
179
|
+
this.failures.delete(id);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Confirmed rendered: refresh the exemption window so follow-ups in the
|
|
183
|
+
// agent's active turn settle immediately.
|
|
184
|
+
this.confirmedUntil.set(id, Date.now() + (this.options.confirmCooldownMs ?? 30_000));
|
|
128
185
|
}
|
|
129
186
|
// If persistence fails after writing, the in-process guard avoids a
|
|
130
187
|
// second write on retry. Across a crash, transport is at-least-once.
|
|
131
188
|
this.complete(id, message);
|
|
132
189
|
}
|
|
190
|
+
/** Wait out the confirm window, then look for the written messages in the
|
|
191
|
+
* recipient's terminal history. A message found there was rendered by the
|
|
192
|
+
* agent and counts as consumed. While the agent is blocked on an approval
|
|
193
|
+
* dialog (its Bash call needs permission) dismiss it once per cycle, and
|
|
194
|
+
* on the first cycle submit a paste that arrived during turn handover and
|
|
195
|
+
* is still sitting unsubmitted (differential baseline evidence only, at
|
|
196
|
+
* most once per delivery). Each recovery sends Enter on evidence, never
|
|
197
|
+
* blind. */
|
|
198
|
+
async confirmConsumed(messages, route, delayMs, baseline) {
|
|
199
|
+
let recovered = false;
|
|
200
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
201
|
+
await new Promise((resolve) => setTimeout(resolve, attempt === 0 ? delayMs : Math.min(1_200, delayMs)));
|
|
202
|
+
let content = null;
|
|
203
|
+
try {
|
|
204
|
+
content = await route.confirm();
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
content = null;
|
|
208
|
+
}
|
|
209
|
+
if (content && messages.some((message) => content.includes(message.id)))
|
|
210
|
+
return true;
|
|
211
|
+
if (!recovered && route.recoverStuck && baseline) {
|
|
212
|
+
try {
|
|
213
|
+
recovered = await route.recoverStuck(baseline);
|
|
214
|
+
}
|
|
215
|
+
catch { /* a failed submit must not settle delivery */ }
|
|
216
|
+
}
|
|
217
|
+
if (content && route.approve) {
|
|
218
|
+
try {
|
|
219
|
+
await route.approve();
|
|
220
|
+
}
|
|
221
|
+
catch { /* a failed dismiss must not settle delivery */ }
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
133
226
|
complete(id, message) {
|
|
134
227
|
const { store } = this.options;
|
|
135
228
|
store.markDelivered([message.id]);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Router } from 'express';
|
|
2
2
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
3
|
+
import { COLLAB_NAME_FORBIDDEN } from './collaborationPrompt.js';
|
|
3
4
|
/** The current service owns group membership, including retained offline members. */
|
|
4
5
|
export function collaborationGroupRoutes(options) {
|
|
5
6
|
const router = Router();
|
|
@@ -15,6 +16,9 @@ export function collaborationGroupRoutes(options) {
|
|
|
15
16
|
|| sessionIds.some((value) => typeof value !== 'string' || !value.trim())) {
|
|
16
17
|
throw new CollaborationError('INVALID_GROUP', '名称和成员列表无效', 400);
|
|
17
18
|
}
|
|
19
|
+
if (COLLAB_NAME_FORBIDDEN.test(name.trim())) {
|
|
20
|
+
throw new CollaborationError('INVALID_GROUP', '名称不能包含「【】」、间隔号「·」、换行或控制字符', 400);
|
|
21
|
+
}
|
|
18
22
|
const existing = id ? options.store.getGroup(id) : null;
|
|
19
23
|
if (id && (!existing || existing.deleted))
|
|
20
24
|
throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
|
|
@@ -52,6 +56,27 @@ export function collaborationGroupRoutes(options) {
|
|
|
52
56
|
error: error instanceof Error ? error.message : '成员移动失败' });
|
|
53
57
|
}
|
|
54
58
|
});
|
|
59
|
+
router.post('/collaboration-groups/:groupId/role', (req, res) => {
|
|
60
|
+
try {
|
|
61
|
+
const existing = options.store.getGroup(req.params.groupId);
|
|
62
|
+
if (!existing || existing.deleted)
|
|
63
|
+
throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
|
|
64
|
+
const { sessionId, role } = req.body ?? {};
|
|
65
|
+
if (typeof sessionId !== 'string' || !existing.sessionIds.includes(sessionId)) {
|
|
66
|
+
throw new CollaborationError('NOT_A_MEMBER', '该会话不在协作组中,无法设置定位', 400);
|
|
67
|
+
}
|
|
68
|
+
if (role === undefined || (role !== null && typeof role !== 'string')) {
|
|
69
|
+
throw new CollaborationError('INVALID_ROLE', '定位必须是文本;清除请传 null', 400);
|
|
70
|
+
}
|
|
71
|
+
const updated = options.store.setRole({ groupId: existing.id, sessionId, role: role });
|
|
72
|
+
res.json({ group: { ...updated, roles: updated.roles ?? {} } });
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
res.status(error instanceof CollaborationError ? error.httpStatus : 400)
|
|
76
|
+
.json({ code: error instanceof CollaborationError ? error.code : 'GROUP_ROLE_FAILED',
|
|
77
|
+
error: error instanceof Error ? error.message : '定位设置失败' });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
55
80
|
router.post('/collaboration-groups/:groupId/promote', (req, res) => {
|
|
56
81
|
try {
|
|
57
82
|
const { group, expectedUpdatedAt } = req.body ?? {};
|
|
@@ -1,54 +1,117 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { sanitizeCollaborationRole } from './collaborationStore.js';
|
|
2
|
+
const SHELL_RULE = '─'.repeat(30);
|
|
3
|
+
/** Body larger than this (in UTF-8 bytes) is replaced by a retrieval pointer
|
|
4
|
+
* instead of being injected into the terminal verbatim. */
|
|
5
|
+
const MAX_INLINE_BODY_BYTES = 8_192;
|
|
6
|
+
/**
|
|
7
|
+
* Characters that would corrupt the delivery shell header `「X」群(N 个成员)`
|
|
8
|
+
* and the per-message `来自:X · kind` line: the quote brackets and the middle
|
|
9
|
+
* dot, which the render layer uses as structure, plus control sequences
|
|
10
|
+
* (C0 controls + DEL) that could inject terminal output. Legacy and federated
|
|
11
|
+
* names may carry them, so the render layer neutralizes them defensively;
|
|
12
|
+
* user-entered names are rejected at creation instead.
|
|
13
|
+
*/
|
|
14
|
+
export const COLLAB_NAME_FORBIDDEN = /[【】「」·\x00-\x1f\x7f]/;
|
|
15
|
+
/** Render-safe name for shell header / per-message source lines: reserved
|
|
16
|
+
* bracket and delimiter characters become spaces (they would otherwise split
|
|
17
|
+
* the `「X」群` and `来自:X · kind` lines), control characters are
|
|
18
|
+
* neutralized, whitespace collapses, and the display length is capped. */
|
|
19
|
+
export function sanitizeCollaborationName(name, max = 40) {
|
|
20
|
+
const cleaned = name
|
|
21
|
+
.replace(/[【】「」]/g, ' ')
|
|
22
|
+
.replace(/·/g, ' ')
|
|
23
|
+
.replace(/[\x00-\x1f\x7f]/g, ' ')
|
|
24
|
+
.replace(/\s+/g, ' ')
|
|
25
|
+
.trim();
|
|
26
|
+
return cleaned.length > max ? `${cleaned.slice(0, max - 1)}…` : cleaned;
|
|
27
|
+
}
|
|
28
|
+
/** A single delivered message: `来自:X · kind` over a fenced body, with the
|
|
29
|
+
* reply route for agent-sourced messages kept outside the fence. A fan-out
|
|
30
|
+
* dispatch (message.fanOutIds present) is flagged `· 群发` and names the
|
|
31
|
+
* sibling recipients on their own line, so a broadcast is never mistaken for
|
|
32
|
+
* a one-to-one assignment — raw ids fall back to the sanitized id itself. */
|
|
33
|
+
function formatCollaborationMessage(message, source, fannedNames, fence) {
|
|
34
|
+
const lines = [`来自:${source} · ${message.kind}${fannedNames.length ? ' · 群发' : ''}`];
|
|
35
|
+
if (fannedNames.length)
|
|
36
|
+
lines.push(`同时发给了:${fannedNames.join('、')}`);
|
|
37
|
+
lines.push('', fence, message.content, fence);
|
|
38
|
+
if (message.task)
|
|
39
|
+
lines.push('', `任务上报:${JSON.stringify(message.task)}`);
|
|
40
|
+
if (message.fromSessionId)
|
|
41
|
+
lines.push('', `回复:td collab reply ${message.id} "回复内容" --text`);
|
|
42
|
+
return lines.join('\n');
|
|
43
|
+
}
|
|
9
44
|
export function formatCollaborationDelivery(input) {
|
|
10
45
|
const groupsById = new Map(input.groups.map((group) => [group.id, group]));
|
|
11
46
|
const sessionsById = new Map(input.sessions.map((session) => [session.sessionId, session]));
|
|
12
|
-
const
|
|
47
|
+
const blocks = [];
|
|
48
|
+
for (const message of input.messages) {
|
|
13
49
|
const sourceSession = message.fromSessionId ? sessionsById.get(message.fromSessionId) : null;
|
|
14
50
|
const source = message.fromSessionId
|
|
15
|
-
? sourceSession?.name ?? message.fromSessionId
|
|
51
|
+
? sanitizeCollaborationName(sourceSession?.name ?? message.fromSessionId)
|
|
16
52
|
: '用户';
|
|
17
|
-
const
|
|
18
|
-
const body =
|
|
19
|
-
? `大消息已完整保存(${
|
|
53
|
+
const bytes = Buffer.byteLength(message.content);
|
|
54
|
+
const body = bytes > MAX_INLINE_BODY_BYTES
|
|
55
|
+
? `大消息已完整保存(${bytes} 字节)。使用 td collab message get ${message.id} --json 获取正文;不要把这条提示当作消息正文。`
|
|
20
56
|
: message.content;
|
|
21
57
|
const fence = '`'.repeat(Math.max(3, ...Array.from(body.matchAll(/`+/g), (match) => match[0].length + 1)));
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
58
|
+
const fannedNames = (message.fanOutIds ?? [])
|
|
59
|
+
.map((sessionId) => sanitizeCollaborationName(sessionsById.get(sessionId)?.name ?? sessionId));
|
|
60
|
+
blocks.push(formatCollaborationMessage({ ...message, content: body }, source, fannedNames, fence));
|
|
61
|
+
}
|
|
62
|
+
// The shell header names the group only when every block belongs to one;
|
|
63
|
+
// a mixed batch drops the header entirely (no single-group claim to make).
|
|
64
|
+
// The member count rides along as the at-a-glance size of that group.
|
|
65
|
+
const groupIds = [...new Set(input.messages.map((message) => message.groupId))];
|
|
66
|
+
const singleGroup = groupIds.length === 1 ? groupsById.get(groupIds[0]) : null;
|
|
67
|
+
const shellHeader = singleGroup
|
|
68
|
+
? `「${sanitizeCollaborationName(singleGroup.name ?? '协作组')}」群(${singleGroup.sessionIds.length} 个成员)`
|
|
69
|
+
: '';
|
|
32
70
|
const peerIds = Array.from(new Set(input.groups.flatMap((group) => group.sessionIds)))
|
|
33
71
|
.filter((sessionId) => sessionId !== input.targetSessionId);
|
|
34
72
|
// Keep routing IDs once, only for peers who are not already directly replyable.
|
|
35
73
|
const sourceIds = new Set(input.messages.map((message) => message.fromSessionId));
|
|
36
74
|
const peers = peerIds.filter((id) => !sourceIds.has(id)).map((sessionId) => {
|
|
37
75
|
const session = sessionsById.get(sessionId);
|
|
38
|
-
return `- ${session
|
|
76
|
+
return `- ${session ? sanitizeCollaborationName(session.name) : '离线会话'}:\`td collab send ${sessionId} "消息内容" --text\``;
|
|
39
77
|
});
|
|
40
78
|
const unreachable = peerIds.filter((id) => sessionsById.get(id)?.status === 'service-unreachable');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
79
|
+
// Education (peer roster examples) decays once the session knows the group;
|
|
80
|
+
// the `--help` pointer stays permanently as the one-line entrance to the
|
|
81
|
+
// full command surface — everything else can be looked up from there.
|
|
82
|
+
const routingHelp = input.showRoutingHelp === false ? [] : (peers.length ? ['联系其他成员:', ...peers] : []);
|
|
83
|
+
const dynamicNotices = [
|
|
44
84
|
...(peerIds.some((id) => id.startsWith('remote:'))
|
|
45
85
|
? ['跨服务通信使用 td collab;转发客户端须保持运行,网页或 PWA 暂停后需返回前台继续转发。'] : []),
|
|
46
|
-
...unreachable.map((id) =>
|
|
86
|
+
...unreachable.map((id) => {
|
|
87
|
+
const session = sessionsById.get(id);
|
|
88
|
+
return `注意:${session ? sanitizeCollaborationName(session.name) : id} 服务不可达,消息无法送达;仅可排队等待重连。`;
|
|
89
|
+
}),
|
|
47
90
|
];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
91
|
+
const notes = [...routingHelp, ...dynamicNotices, '更多操作:`td collab --help`。'];
|
|
92
|
+
// Notes (education + dynamic notices) live inside the shell, after the last
|
|
93
|
+
// message, so the closing rule still marks the end of the delivered block.
|
|
94
|
+
if (!blocks.length)
|
|
95
|
+
return notes.join('\n');
|
|
96
|
+
// Self-identity rides the shell under the header so the recipient always
|
|
97
|
+
// knows the name peers see (a rename lands on the next delivery — no
|
|
98
|
+
// separate notification needed) and, in a single-group delivery, its role.
|
|
99
|
+
// The name needs no group claim and survives mixed batches; the role (定位)
|
|
100
|
+
// only rides when the batch maps to exactly one group.
|
|
101
|
+
const ownName = sanitizeCollaborationName(sessionsById.get(input.targetSessionId)?.name ?? '');
|
|
102
|
+
const ownRole = singleGroup
|
|
103
|
+
? sanitizeCollaborationRole(singleGroup.roles?.[input.targetSessionId] ?? '')
|
|
104
|
+
: '';
|
|
105
|
+
const lines = [SHELL_RULE];
|
|
106
|
+
if (shellHeader)
|
|
107
|
+
lines.push(shellHeader);
|
|
108
|
+
if (ownName)
|
|
109
|
+
lines.push(`你的名字:${ownName}`);
|
|
110
|
+
if (ownRole)
|
|
111
|
+
lines.push(`你的定位:${ownRole}`);
|
|
112
|
+
// No blank line between the identity block and the first message, nor
|
|
113
|
+
// between the last message and the notes: the shell reads as one compact
|
|
114
|
+
// block, and only message-to-message boundaries keep a blank line.
|
|
115
|
+
lines.push(blocks.join('\n\n'), ...notes, SHELL_RULE);
|
|
116
|
+
return lines.join('\n');
|
|
54
117
|
}
|