termdock 1.4.242 → 1.4.244
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-FW1IFvG9.js +155 -0
- package/dist/client/assets/App-FW1IFvG9.js.br +0 -0
- package/dist/client/assets/App-FW1IFvG9.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-4kynRLRs.js → ChangeWalkthroughPanel-BQ945Bej.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-BQ945Bej.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BQ945Bej.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-Yx7XBwmM.js → DagPlayground-QYiwXLCf.js} +1 -1
- package/dist/client/assets/DagPlayground-QYiwXLCf.js.br +0 -0
- package/dist/client/assets/DagPlayground-QYiwXLCf.js.gz +0 -0
- package/dist/client/assets/{DiffLab-Cn1f9iHY.js → DiffLab-DsDtvzvo.js} +1 -1
- package/dist/client/assets/DiffLab-DsDtvzvo.js.br +0 -0
- package/dist/client/assets/DiffLab-DsDtvzvo.js.gz +0 -0
- package/dist/client/assets/{DiffReview-BJQ92hBr.js → DiffReview-C8SvxrKp.js} +1 -1
- package/dist/client/assets/DiffReview-C8SvxrKp.js.br +0 -0
- package/dist/client/assets/DiffReview-C8SvxrKp.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-CN3a3LhH.js → DiffReviewLab-DZ9wpIFu.js} +1 -1
- package/dist/client/assets/DiffReviewLab-DZ9wpIFu.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DZ9wpIFu.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-BFn2Gz8v.js → DiffViewer-BrYrQJYY.js} +1 -1
- package/dist/client/assets/DiffViewer-BrYrQJYY.js.br +0 -0
- package/dist/client/assets/DiffViewer-BrYrQJYY.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-CC8Uf4et.js → ModelPreview-BY9svfPx.js} +1 -1
- package/dist/client/assets/ModelPreview-BY9svfPx.js.br +0 -0
- package/dist/client/assets/ModelPreview-BY9svfPx.js.gz +0 -0
- package/dist/client/assets/{RightSidebar-Bz9BkzCR.js → RightSidebar-u4a5jDE4.js} +29 -29
- package/dist/client/assets/RightSidebar-u4a5jDE4.js.br +0 -0
- package/dist/client/assets/RightSidebar-u4a5jDE4.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-hQSzKoeJ.js → abnfDiagram-VCTEODGH-CQ1MR88F.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-CQ1MR88F.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CQ1MR88F.js.gz +0 -0
- package/dist/client/assets/{app-CgwJz96Y.js → app-D3aiC8mC.js} +13 -13
- package/dist/client/assets/app-D3aiC8mC.js.br +0 -0
- package/dist/client/assets/app-D3aiC8mC.js.gz +0 -0
- package/dist/client/assets/{arc-DNdtsFHs.js → arc-a2lHWdMY.js} +1 -1
- package/dist/client/assets/arc-a2lHWdMY.js.br +0 -0
- package/dist/client/assets/arc-a2lHWdMY.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-ClYN-ZWK.js → architectureDiagram-5GKGNRK7-F8mNJBxK.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-F8mNJBxK.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-F8mNJBxK.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-ybw8N2u6.js → blockDiagram-I7D4REHJ--OYBQtE8.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ--OYBQtE8.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ--OYBQtE8.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-BUySB1D8.js → c4Diagram-7LVT6UL2-c_K-ixGW.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-c_K-ixGW.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-c_K-ixGW.js.gz +0 -0
- package/dist/client/assets/channel-obns06Q1.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-DEG-dAIQ.js → chunk-2Q5K7J3B-2q9j8pom.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-DkNem54n.js → chunk-5VM5RSS4-C3ihsc7q.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-D32jqKl9.js → chunk-F27PBJKO-C-K3W6Eb.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-C-K3W6Eb.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-C-K3W6Eb.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-JxJHaym5.js → chunk-IMKFNOWR-CWUQQf5j.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-CWUQQf5j.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CWUQQf5j.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-CS7uV1GH.js → chunk-JWPE2WC7-BdNW1Vo8.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-B02kbaJZ.js → chunk-POPQ4Y6H-TPOUuFyc.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-Bc9XpKmH.js → chunk-SVP7TREG-DJr0I19p.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-DJr0I19p.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DJr0I19p.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-DW69sulY.js → chunk-TICWLB2K-CUxOG8CH.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-CUxOG8CH.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-CUxOG8CH.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-CqHkJ6cA.js → chunk-XXDRQBXY-BMyAcAgT.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-BkGa0_zW.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BkGa0_zW.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DJAx_fGd.js → cose-bilkent-JH36ORCC-CwrMounB.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CwrMounB.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CwrMounB.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BJmu05nC.js → cynefin-OW5HDTMX-D_otwgC7.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-D_otwgC7.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-D_otwgC7.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-B0m0YIa6.js → cynefinDiagram-5FMLGOSQ-BfGwcdGo.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BfGwcdGo.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BfGwcdGo.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-Cbs-u5oy.js → dagre-GXQ25YYZ-B570O1G8.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-B570O1G8.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-B570O1G8.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-DnFgNCva.js → diagram-S7CK7UJ4-D3teDC9g.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-D3teDC9g.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D3teDC9g.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-CRbBEuHO.js → diagram-UQ7AKVKN-B_HcYNTc.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-B_HcYNTc.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-B_HcYNTc.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-CIPivMwW.js → diagram-VSXAHHWV-cQy3CtkB.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-cQy3CtkB.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-cQy3CtkB.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-CY8G867I.js → diagram-VX7I27RA-DqD1fcrv.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-DqD1fcrv.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DqD1fcrv.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-owVjuxom.js → diagram-Z3DM3KII-C_7mxX9V.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-C_7mxX9V.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-C_7mxX9V.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-DMoOEDnf.js → ebnfDiagram-PWID7BFC-DG1ESZh0.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DG1ESZh0.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DG1ESZh0.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DuEHUYT0.js → erDiagram-RLTQ6QDP-B3L9zOO_.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-B3L9zOO_.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-B3L9zOO_.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-Duys8g5v.js → flowDiagram-HODETNUW-DSd6qrfy.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-DSd6qrfy.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DSd6qrfy.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-4uNGC3eO.js → ganttDiagram-EL5Y4UJY-D-DUxvr6.js} +1 -1
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-4uNGC3eO.js.br → ganttDiagram-EL5Y4UJY-D-DUxvr6.js.br} +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-D-DUxvr6.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-CTk63JTf.js → gestureArbiter-BMIA4t5C.js} +1 -1
- package/dist/client/assets/gestureArbiter-BMIA4t5C.js.br +0 -0
- package/dist/client/assets/gestureArbiter-BMIA4t5C.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CoNcYxET.js → gitGraphDiagram-WWUBYQGX-DY039fQr.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DY039fQr.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DY039fQr.js.gz +0 -0
- package/dist/client/assets/{index-QFRD5OND.css → index-fjWWhVQp.css} +1 -1
- package/dist/client/assets/index-fjWWhVQp.css.br +0 -0
- package/dist/client/assets/index-fjWWhVQp.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-D1APvjL0.js → infoDiagram-27XIBGKW-PSwKOizR.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-B0CGmgD9.js → ishikawaDiagram-5VMMS53U-DHZQZ4vw.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DHZQZ4vw.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DHZQZ4vw.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-BIcr4OmP.js → journeyDiagram-3NMN7TZE-BCfZMJT_.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BCfZMJT_.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BCfZMJT_.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-BGY-UCFp.js → kanban-definition-UXKFOSKX-De0Usp6V.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-De0Usp6V.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-De0Usp6V.js.gz +0 -0
- package/dist/client/assets/{layout-Dy-XBjcQ.js → layout-DRT_q9Re.js} +1 -1
- package/dist/client/assets/layout-DRT_q9Re.js.br +0 -0
- package/dist/client/assets/layout-DRT_q9Re.js.gz +0 -0
- package/dist/client/assets/{linear-DGdoGGqI.js → linear-DmeV9lrJ.js} +1 -1
- package/dist/client/assets/linear-DmeV9lrJ.js.br +0 -0
- package/dist/client/assets/linear-DmeV9lrJ.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-uUtQ6Zvd.js → mermaid.core-2A66Zjaf.js} +5 -5
- package/dist/client/assets/mermaid.core-2A66Zjaf.js.br +0 -0
- package/dist/client/assets/mermaid.core-2A66Zjaf.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-BBMP8prO.js → mindmap-definition-YA3MSWOX-OHFYLKUs.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-OHFYLKUs.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-OHFYLKUs.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-CazWrYzP.js → passwordBootstrap-CnQozPhg.js} +1 -1
- package/dist/client/assets/passwordBootstrap-CnQozPhg.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-CnQozPhg.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BvxAwgS8.js → pegDiagram-XKGWAZYB-DoGiGSNv.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-DoGiGSNv.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DoGiGSNv.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-DZ4F26FA.js → pieDiagram-E7YTZNPT-D9bvL_bd.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-D9bvL_bd.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-D9bvL_bd.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-FJGBvmNp.js → quadrantDiagram-AXDQQJYC-9WG1qRAc.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-9WG1qRAc.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-9WG1qRAc.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-B4f16qlN.js → railroadDiagram-O6MQD6OU-D9k32-Rn.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-D9k32-Rn.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-D9k32-Rn.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-4ydKrZtC.js → referenceLongPress-Crd3vCzP.js} +1 -1
- package/dist/client/assets/referenceLongPress-Crd3vCzP.js.br +0 -0
- package/dist/client/assets/referenceLongPress-Crd3vCzP.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-DDEDP28y.js → requirementDiagram-BXWQKSXE-CGV7OJvo.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CGV7OJvo.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CGV7OJvo.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-eZbHgAJ2.js → sankeyDiagram-P5KCCOFB-BpTZur2K.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BpTZur2K.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BpTZur2K.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-C26sID_J.js → sequenceDiagram-WJ2MYXX4-CDI7zDK4.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CDI7zDK4.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CDI7zDK4.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-CunqRjZH.js → sizeCapture-INFHLROL-BIU7z9BO.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-BIU7z9BO.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BIU7z9BO.js.gz +0 -0
- package/dist/client/assets/{sparkles-Cs8KAbL4.js → sparkles-CZRolW6a.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-BDCgrd2K.js → stateDiagram-D77RDMKH-DCYwIQuv.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-DCYwIQuv.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DCYwIQuv.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CE6DH4T3.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-ClJ98iVo.js → swimlanes-42K2YHIH-d18e8Jz2.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-d18e8Jz2.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-d18e8Jz2.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-pa0oACxD.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-CDCDunzZ.js → timeline-definition-24CTP7MA-Cr7e3sjv.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-Cr7e3sjv.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-Cr7e3sjv.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-B0gXRJJm.js → triangle-alert-DDc8KEjo.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-BBqRUM5-.js → vennDiagram-4TSXK5OY-CaNnkHEJ.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-CaNnkHEJ.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CaNnkHEJ.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DKpVgjww.js → wardleyDiagram-VM6X3IG4-BBj61WiS.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BBj61WiS.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BBj61WiS.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-CHwA2dfA.js → xychartDiagram-S5SC5T6Z-BP67tz6A.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BP67tz6A.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BP67tz6A.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 +125 -86
- package/dist/server/agent/collaborationGroupRoutes.js +17 -3
- package/dist/server/agent/collaborationPrompt.js +63 -72
- package/dist/server/agent/collaborationProtocol.js +6 -1
- package/dist/server/agent/collaborationService.js +1 -1
- package/dist/server/agent/collaborationStore.js +2 -2
- package/dist/server/routes/terminal.js +8 -2
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-CVuPbhP4.js +0 -155
- package/dist/client/assets/App-CVuPbhP4.js.br +0 -0
- package/dist/client/assets/App-CVuPbhP4.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-4kynRLRs.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-4kynRLRs.js.gz +0 -0
- package/dist/client/assets/DagPlayground-Yx7XBwmM.js.br +0 -0
- package/dist/client/assets/DagPlayground-Yx7XBwmM.js.gz +0 -0
- package/dist/client/assets/DiffLab-Cn1f9iHY.js.br +0 -0
- package/dist/client/assets/DiffLab-Cn1f9iHY.js.gz +0 -0
- package/dist/client/assets/DiffReview-BJQ92hBr.js.br +0 -0
- package/dist/client/assets/DiffReview-BJQ92hBr.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CN3a3LhH.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CN3a3LhH.js.gz +0 -0
- package/dist/client/assets/DiffViewer-BFn2Gz8v.js.br +0 -0
- package/dist/client/assets/DiffViewer-BFn2Gz8v.js.gz +0 -0
- package/dist/client/assets/ModelPreview-CC8Uf4et.js.br +0 -0
- package/dist/client/assets/ModelPreview-CC8Uf4et.js.gz +0 -0
- package/dist/client/assets/RightSidebar-Bz9BkzCR.js.br +0 -0
- package/dist/client/assets/RightSidebar-Bz9BkzCR.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-hQSzKoeJ.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-hQSzKoeJ.js.gz +0 -0
- package/dist/client/assets/app-CgwJz96Y.js.br +0 -0
- package/dist/client/assets/app-CgwJz96Y.js.gz +0 -0
- package/dist/client/assets/arc-DNdtsFHs.js.br +0 -0
- package/dist/client/assets/arc-DNdtsFHs.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-ClYN-ZWK.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-ClYN-ZWK.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-ybw8N2u6.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-ybw8N2u6.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BUySB1D8.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BUySB1D8.js.gz +0 -0
- package/dist/client/assets/channel-C6y0-VyK.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-D32jqKl9.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-D32jqKl9.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-JxJHaym5.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-JxJHaym5.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Bc9XpKmH.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Bc9XpKmH.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DW69sulY.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DW69sulY.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-zxEN3eAk.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-zxEN3eAk.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DJAx_fGd.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DJAx_fGd.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BJmu05nC.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BJmu05nC.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B0m0YIa6.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B0m0YIa6.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Cbs-u5oy.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Cbs-u5oy.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DnFgNCva.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DnFgNCva.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CRbBEuHO.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CRbBEuHO.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CIPivMwW.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CIPivMwW.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CY8G867I.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CY8G867I.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-owVjuxom.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-owVjuxom.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DMoOEDnf.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DMoOEDnf.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DuEHUYT0.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DuEHUYT0.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Duys8g5v.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Duys8g5v.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-4uNGC3eO.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-CTk63JTf.js.br +0 -0
- package/dist/client/assets/gestureArbiter-CTk63JTf.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CoNcYxET.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CoNcYxET.js.gz +0 -0
- package/dist/client/assets/index-QFRD5OND.css.br +0 -0
- package/dist/client/assets/index-QFRD5OND.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-B0CGmgD9.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-B0CGmgD9.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BIcr4OmP.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BIcr4OmP.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BGY-UCFp.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BGY-UCFp.js.gz +0 -0
- package/dist/client/assets/layout-Dy-XBjcQ.js.br +0 -0
- package/dist/client/assets/layout-Dy-XBjcQ.js.gz +0 -0
- package/dist/client/assets/linear-DGdoGGqI.js.br +0 -0
- package/dist/client/assets/linear-DGdoGGqI.js.gz +0 -0
- package/dist/client/assets/mermaid.core-uUtQ6Zvd.js.br +0 -0
- package/dist/client/assets/mermaid.core-uUtQ6Zvd.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BBMP8prO.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BBMP8prO.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-CazWrYzP.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-CazWrYzP.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BvxAwgS8.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BvxAwgS8.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DZ4F26FA.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DZ4F26FA.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-FJGBvmNp.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-FJGBvmNp.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-B4f16qlN.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-B4f16qlN.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-4ydKrZtC.js.br +0 -0
- package/dist/client/assets/referenceLongPress-4ydKrZtC.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DDEDP28y.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DDEDP28y.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-eZbHgAJ2.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-eZbHgAJ2.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C26sID_J.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C26sID_J.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CunqRjZH.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CunqRjZH.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BDCgrd2K.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BDCgrd2K.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-JBMsRqan.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-ClJ98iVo.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-ClJ98iVo.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BvtpepbK.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-CDCDunzZ.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-CDCDunzZ.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BBqRUM5-.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BBqRUM5-.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DKpVgjww.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DKpVgjww.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CHwA2dfA.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CHwA2dfA.js.gz +0 -0
|
@@ -2,76 +2,85 @@ import fs from 'node:fs';
|
|
|
2
2
|
import { randomUUID } from 'node:crypto';
|
|
3
3
|
import { COLLAB_LIMITS, CollaborationError, canonicalShortId } from './collaborationProtocol.js';
|
|
4
4
|
export const COLLAB_HELP = `td collab — durable messages; no agent-specific hooks required
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
我想…
|
|
6
|
+
回复刚收到的消息 → 用投递「来自」行里的 id: td collab reply <id> "内容" --text
|
|
7
|
+
看有谁、我在哪些组 → td collab status --text
|
|
8
|
+
主动找人 → td collab send <成员id> "消息内容" --text
|
|
9
|
+
看对方在干什么 → td collab capture <成员id> --text(只读巡屏,不发送按键)
|
|
10
|
+
取大消息全文 → td collab message get <id> --text
|
|
11
|
+
改我的显示名 → td collab rename <我的id> <新名字>
|
|
12
|
+
设角色定位 → td collab role set <组id> <成员id> <定位>
|
|
13
|
+
查/改群规 → td collab rules get|set <组id>
|
|
14
|
+
消息发出去没动静 → td collab message get <id>(投递状态与重试原因)
|
|
15
|
+
Output: --json (default) | --jsonl | --text (human-readable lines; prefer it
|
|
16
|
+
when reading results yourself. Commands without a dedicated --text form
|
|
17
|
+
still print JSON)
|
|
8
18
|
Source identity (all collab commands): --session <full-Termdock-session-id>
|
|
9
19
|
or TERMDOCK_COLLAB_SESSION_ID. CLI option wins; explicit identity bypasses
|
|
10
20
|
environment/tmux detection. Use your own full member ID from status output,
|
|
11
21
|
not a tmux name, backend ID or Claude session ID. Unknown IDs are refused.
|
|
12
|
-
This selects a source in the trusted local-user context; it is not proof
|
|
13
|
-
of identity or isolation between processes running as the same OS user.
|
|
14
22
|
Example: td collab --session <your-full-session-id> status
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
23
|
+
Ids: wherever an id is taken, the short id shown in deliveries and --text
|
|
24
|
+
output works too; a shorter unique prefix down to 4 characters also
|
|
25
|
+
resolves — an ambiguous prefix is refused, so use more characters.
|
|
18
26
|
Scheduled self-reminders: td automation create --name 'Review progress' --every 30 --self --prompt 'Review group progress and continue'
|
|
19
27
|
Scheduling help: td automation --help
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
{serviceId, origin, caFingerprint256?} entries, including this service.
|
|
34
|
-
Obtain pins from each administrator's transport info over a trusted channel.
|
|
35
|
-
Registered servers deliver without an open browser or Desktop client.
|
|
36
|
-
rebind [--pane %3] (explicitly bind this peer to its current Agent; resumes queued delivery)
|
|
37
|
-
send <session-id> <message> | reply <message-id> <message> | handoff <session-id> <message>
|
|
38
|
-
(send fan-out: comma-separated same-group ids, e.g. send a,b "任务"; every
|
|
39
|
-
recipient sees it as 群发 with the sibling list; waiting options apply to
|
|
40
|
-
single-recipient sends only)
|
|
41
|
-
--group <id> --thread <id> --idempotency-key <key>
|
|
42
|
-
--file <path> | --stdin (instead of inline body; -- ends option parsing)
|
|
43
|
-
--wait-until queued|delivered --timeout 30s
|
|
44
|
-
--expect-reply ack|result|any
|
|
45
|
-
--response-kind ack|progress|result --metadata '<JSON object>'
|
|
46
|
-
--expires-at <ISO timestamp or epoch milliseconds>
|
|
47
|
-
reply: --task-envelope '<JSON: task_id,status,progress?,evidence?,blocker?>'
|
|
28
|
+
|
|
29
|
+
── 常用 ──
|
|
30
|
+
status (who is in my groups, their names, roles, observed activity)
|
|
31
|
+
send <session-id> <message> (deliver to one member; comma-separated same-group
|
|
32
|
+
ids fan out, e.g. send a,b "任务" — recipients see it as 群发)
|
|
33
|
+
reply <message-id> <message> (answer a message you received; one-to-one)
|
|
34
|
+
handoff <session-id> <message> (like send, framed as a task hand-off)
|
|
35
|
+
shared options: --group <id> --thread <id> --idempotency-key <key>
|
|
36
|
+
--file <path> | --stdin (instead of inline body; -- ends option parsing)
|
|
37
|
+
--wait-until queued|delivered --timeout 30s --expect-reply ack|result|any
|
|
38
|
+
--response-kind ack|progress|result --metadata '<JSON object>'
|
|
39
|
+
--expires-at <ISO timestamp or epoch milliseconds>
|
|
40
|
+
reply also: --task-envelope '<JSON: task_id,status,progress?,evidence?,blocker?>'
|
|
48
41
|
(status reporting happens only via reply; send/handoff dispatch carries no task state)
|
|
49
|
-
message get <
|
|
42
|
+
message get <id> [--receipt-only] [--raw] [--no-rules] [--text]
|
|
43
|
+
(delivery status, retries and the full body of large messages)
|
|
50
44
|
message confirm-shell <message-id> (sender confirms delivery into a shell; continues the same message)
|
|
51
45
|
message watch <message-id> [--wait-until delivered] [--timeout 30s]
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
46
|
+
capture <session-id> [--lines 1..10000] [--raw] [--text]
|
|
47
|
+
(查看伙伴正在做什么:读取同组、本机 tmux 成员的当前屏幕,不发送按键、不打断对方。
|
|
48
|
+
不是完整聊天历史或完成凭证;用 status --text 查成员 ID;不支持远端成员,远端请 send 询问进展。)
|
|
49
|
+
── 群与规则 ──
|
|
50
|
+
group save --file group.json
|
|
51
|
+
JSON: {name, sessionIds, id?, expectedUpdatedAt?}; same service API as the UI.
|
|
57
52
|
add|remove <group-id> <session-id>
|
|
58
53
|
spawn <group-id> <agent-slug> [--name <name>] [--cwd <path>] [--task <text>]
|
|
59
|
-
rules get <group-id> [--text]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
traits list|set|unset (alias of role; long-lived capabilities/preferences)
|
|
64
|
-
role list <group-id>
|
|
65
|
-
role set <group-id> <session-id> <role…> (trailing words join as the role)
|
|
66
|
-
role unset <group-id> <session-id>
|
|
67
|
-
(roles are shared within the group; members set each other's, your own
|
|
68
|
-
rides the delivery shell header)
|
|
54
|
+
rules get <group-id> [--text] | rules set <group-id> <text> | --file <path> | --stdin [--if-version <version>]
|
|
55
|
+
| rules clear <group-id> [--if-version <version>]
|
|
56
|
+
(shared group guidance, up to 8192 UTF-8 bytes; message envelopes retain
|
|
57
|
+
its version. if-version rejects concurrent overwrites with GROUP_CHANGED)
|
|
69
58
|
rename <session-id> <name…> (rename a member of any shared group;
|
|
70
59
|
trailing words join as the new name; roster and shells show it at once)
|
|
71
|
-
|
|
72
|
-
(
|
|
73
|
-
|
|
74
|
-
|
|
60
|
+
role list <group-id> | role set <group-id> <session-id> <role…> | role unset <group-id> <session-id>
|
|
61
|
+
(roles are shared within the group; members set each other's, your own
|
|
62
|
+
rides the delivery shell header. traits is an alias of role)
|
|
63
|
+
── 收件箱与游标 ──
|
|
64
|
+
inbox [--since <ISO or epoch-ms>] [--after-id <id>] [--cursor <token>]
|
|
65
|
+
[--consumer <name>] [--limit 1..200] [--from <id>] [--group <id>]
|
|
66
|
+
[--thread <id>] [--kind <kind>] [--response-kind ack|progress|result]
|
|
67
|
+
[--follow] [--timeout 30s]
|
|
68
|
+
(defaults to newest 50; cursor/consumer mode reads oldest unseen first.
|
|
69
|
+
Reading never advances a consumer. Read receipts are not supported)
|
|
70
|
+
cursor commit <token> --consumer <name> (commit after processing the page)
|
|
71
|
+
── 传输与运维 ──
|
|
72
|
+
transport list (server-owned remote session directory) | transport info (this service's public identity and CA fingerprint, no private keys)
|
|
73
|
+
transport invite --origin https://this-service:9834
|
|
74
|
+
(create a 10-minute invitation granting collaboration directory/group
|
|
75
|
+
access; transfer its JSON privately — no browser required)
|
|
76
|
+
transport accept --origin https://this-service:9834 --file invitation.json
|
|
77
|
+
(pair both services once; discovery, groups and delivery become automatic)
|
|
78
|
+
transport register <group-id> --file <nodes.json>
|
|
79
|
+
(one-time server peer registration; nodes.json is an array of public
|
|
80
|
+
{serviceId, origin, caFingerprint256?} entries, including this service.
|
|
81
|
+
Obtain pins from each administrator's transport info over a trusted
|
|
82
|
+
channel. Registered servers deliver without an open browser or Desktop client)
|
|
83
|
+
rebind [--pane %3] (explicitly bind this peer to its current Agent; resumes queued delivery)
|
|
75
84
|
drive <session-id> approve|enter|escape|space|left|right|up|down|capture
|
|
76
85
|
drive <session-id> run <command…>
|
|
77
86
|
(drive the terminal of a member session you share a group with — terminal
|
|
@@ -79,30 +88,30 @@ export const COLLAB_HELP = `td collab — durable messages; no agent-specific ho
|
|
|
79
88
|
dialog and refuses unless one is actually showing; named keys inject one
|
|
80
89
|
key; capture reads the current screen back; run submits one line and
|
|
81
90
|
returns the screen. Works on plain shell members too (no agent needed) for
|
|
82
|
-
run/capture; key actions
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
executes what you send.)
|
|
91
|
+
run/capture; key actions require an agent pane and are refused while the
|
|
92
|
+
user has the pane scrolled into copy-mode. Cannot target your own session.
|
|
93
|
+
Treat every drive as strong control: the member's shell executes what you send)
|
|
86
94
|
cleanup <session-id>… 移除协作会话并终止其 tmux/进程(仅限与你同组的会话;
|
|
87
95
|
不能清理当前会话自身,也不能通过清理解散你所在的组)
|
|
88
96
|
风险操作:默认只打印清理计划并拒绝执行(exit 1)——这是不可恢复的删除。
|
|
89
97
|
必须先向用户(人类)说明将清理的会话与影响并获得其明确同意,
|
|
90
98
|
才能以 --confirm 重跑执行;每次执行都需要当场的人类授权。
|
|
91
|
-
|
|
99
|
+
help (this text; when a managed session is available it also lists the
|
|
100
|
+
groups you are in with every member's id, name and role)
|
|
92
101
|
Exit codes: 0 requested condition met; 1 invalid request/network error;
|
|
93
102
|
cleanup without --confirm prints the plan and refuses (also 1);
|
|
94
103
|
2 wait timeout (message may still deliver); 3 failed/expired.
|
|
95
104
|
Message limit: ${COLLAB_LIMITS.message_bytes} UTF-8 bytes; metadata: ${COLLAB_LIMITS.metadata_bytes} bytes.
|
|
96
105
|
Idempotency retention: 7 days. Terminal delivery, ACK and result never imply each other.
|
|
97
|
-
|
|
98
|
-
|
|
106
|
+
Delivery semantics: delivered = written to the terminal — never proof of
|
|
107
|
+
reading or task completion; a timeout stops waiting, it does not cancel delivery.`;
|
|
99
108
|
/** One roster row: prefer the member's human name, keep the full session id
|
|
100
109
|
* reachable for role set/unset targeting, mark unset members explicitly. */
|
|
101
110
|
function roleLine(member, role) {
|
|
102
111
|
const label = member.name ? `${member.name} (${member.sessionId})` : member.sessionId;
|
|
103
112
|
return role ? `- ${label}:${role}` : `- ${label}(未设置)`;
|
|
104
113
|
}
|
|
105
|
-
const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'follow', 'stdin', 'receipt-only', 'confirm', 'raw', 'help']);
|
|
114
|
+
const BOOLEAN_OPTIONS = new Set(['json', 'jsonl', 'text', 'follow', 'stdin', 'receipt-only', 'confirm', 'raw', 'help', 'no-rules']);
|
|
106
115
|
const VALUE_OPTIONS = new Set(['session', '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', 'lines', 'if-version', 'origin']);
|
|
107
116
|
export function parseCollaborationCommand(argv) {
|
|
108
117
|
const options = {};
|
|
@@ -271,7 +280,7 @@ export function parseCollaborationCommand(argv) {
|
|
|
271
280
|
handoff: ['group', 'thread', 'idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'expires-at'],
|
|
272
281
|
reply: ['idempotency-key', 'file', 'stdin', 'wait-until', 'timeout', 'expect-reply', 'response-kind', 'metadata', 'task-envelope', 'expires-at'],
|
|
273
282
|
inbox: ['raw', 'since', 'after-id', 'cursor', 'consumer', 'limit', 'from', 'group', 'thread', 'kind', 'response-kind', 'follow', 'timeout'],
|
|
274
|
-
message: ['raw', 'receipt-only', 'follow', 'wait-until', 'timeout', 'expect-reply'], cursor: ['consumer'],
|
|
283
|
+
message: ['raw', 'receipt-only', 'no-rules', 'follow', 'wait-until', 'timeout', 'expect-reply'], cursor: ['consumer'],
|
|
275
284
|
add: [], remove: [], spawn: ['name', 'cwd', 'task'], role: [], rename: [], cleanup: ['confirm'], drive: ['lines', 'raw'],
|
|
276
285
|
};
|
|
277
286
|
for (const option of byAction[action])
|
|
@@ -329,8 +338,10 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
329
338
|
io.write(message.content);
|
|
330
339
|
}
|
|
331
340
|
else if (value.message) {
|
|
332
|
-
|
|
333
|
-
|
|
341
|
+
// One line instead of the full rules text — the CLI is stateless so it
|
|
342
|
+
// cannot diff versions across calls; --no-rules silences it entirely.
|
|
343
|
+
if (!o['no-rules'] && value.message.instructions?.text)
|
|
344
|
+
io.write(`消息附群规版本 ${value.message.instructions.version};td collab rules get <组id> --text 查看全文`);
|
|
334
345
|
io.write(`[${value.status}] ${canonicalShortId(String(value.message_id ?? ''))}\n${value.message.content}`);
|
|
335
346
|
}
|
|
336
347
|
else if (value.message_id) {
|
|
@@ -340,8 +351,12 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
340
351
|
if (value.code === 'WAIT_TIMEOUT' && value.stage_reached && value.expect_reply) {
|
|
341
352
|
io.write(`\n投递已完成;等待${value.expect_reply === 'result' ? '结果' : '回复'}超时(expect-reply=${value.expect_reply})`);
|
|
342
353
|
}
|
|
354
|
+
// The screen rides the receipt as the delivery credential: the service
|
|
355
|
+
// observes terminal writes, never reading, so this capture is the only
|
|
356
|
+
// evidence the message actually landed. Kept visible in --text, but
|
|
357
|
+
// fenced and labelled so it cannot be mistaken for delivery state.
|
|
343
358
|
if (value.snapshot)
|
|
344
|
-
io.write(`\n${value.snapshot}
|
|
359
|
+
io.write(`\n对方终端当前屏幕(凭证:确认你的消息已写入;不代表已读或任务完成):\n───\n${value.snapshot}\n───`);
|
|
345
360
|
}
|
|
346
361
|
else
|
|
347
362
|
io.write(JSON.stringify(value, null, 2));
|
|
@@ -382,7 +397,12 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
382
397
|
if (command.action === 'group') {
|
|
383
398
|
if (fs.statSync(String(o.file)).size > 64 * 1024)
|
|
384
399
|
throw new Error('Group input exceeds 64 KiB');
|
|
385
|
-
|
|
400
|
+
const saved = await request('POST', '/group', { input: JSON.parse(fs.readFileSync(String(o.file), 'utf8')) });
|
|
401
|
+
if (o.text && saved.group) {
|
|
402
|
+
io.write(`已保存协作组「${saved.group.name ?? saved.group.id ?? ''}」(${saved.group.sessionIds?.length ?? 0} 个成员,updatedAt=${saved.group.updatedAt ?? '未知'})`);
|
|
403
|
+
}
|
|
404
|
+
else
|
|
405
|
+
output(saved);
|
|
386
406
|
return 0;
|
|
387
407
|
}
|
|
388
408
|
if (command.action === 'transport') {
|
|
@@ -420,6 +440,7 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
420
440
|
for (const id of group.sessionIds)
|
|
421
441
|
io.write(roleLine({ sessionId: id, name: members.get(id) ?? null }, group.roles?.[id]));
|
|
422
442
|
}
|
|
443
|
+
io.write('给成员发消息: td collab send <成员id> "消息内容" --text(成员 id 见上表)');
|
|
423
444
|
}
|
|
424
445
|
}
|
|
425
446
|
catch { /* help stays available without a server or session */ }
|
|
@@ -427,31 +448,41 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
427
448
|
}
|
|
428
449
|
if (command.action === 'capabilities' || command.action === 'status') {
|
|
429
450
|
const result = await request('GET', command.action === 'status' ? '/peers' : '/capabilities');
|
|
430
|
-
output(result);
|
|
431
451
|
if (command.action === 'status' && o.text) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
452
|
+
// Human-readable status: no JSON dump, one line per fact.
|
|
453
|
+
const source = result.source;
|
|
454
|
+
if (source?.sessionId)
|
|
455
|
+
io.write(`我:${source.name ?? ''}(${source.sessionId})`);
|
|
456
|
+
for (const peer of result.peers ?? []) {
|
|
457
|
+
const facts = peer;
|
|
458
|
+
const line = `- ${facts.name || facts.sessionId}(${facts.sessionId})${facts.status ? ` · ${facts.status}` : ''}`;
|
|
459
|
+
io.write(line);
|
|
460
|
+
if (facts.last_terminal_output_at && facts.activity_observed_at) {
|
|
461
|
+
io.write(` 观测时距终端输出 ${facts.output_idle_seconds ?? Math.max(0, Math.floor((facts.activity_observed_at - facts.last_terminal_output_at) / 1000))} 秒(不代表任务进度)`);
|
|
437
462
|
}
|
|
438
463
|
}
|
|
439
|
-
for (const
|
|
440
|
-
|
|
441
|
-
|
|
464
|
+
for (const group of result.groups ?? []) {
|
|
465
|
+
const view = group;
|
|
466
|
+
io.write(`组「${view.name}」成员定位:`);
|
|
467
|
+
const peersAndSource = [...(result.peers ?? []), result.source];
|
|
468
|
+
for (const id of view.sessionIds ?? []) {
|
|
469
|
+
const member = peersAndSource.find(item => item?.sessionId === id);
|
|
470
|
+
io.write(roleLine({ sessionId: id, name: member?.name }, view.roles?.[id]));
|
|
442
471
|
}
|
|
443
|
-
|
|
444
|
-
if (command.action === 'status' && o.text && Array.isArray(result.peers) && result.peers.length) {
|
|
472
|
+
}
|
|
445
473
|
io.write('查看伙伴当前屏幕(只读,仅本机 tmux):');
|
|
446
|
-
for (const peer of result.peers) {
|
|
474
|
+
for (const peer of result.peers ?? []) {
|
|
447
475
|
const id = peer.sessionId;
|
|
448
|
-
|
|
476
|
+
const name = peer.name;
|
|
477
|
+
if (typeof id !== 'string' || id === source?.sessionId)
|
|
449
478
|
continue;
|
|
450
|
-
io.write(`- ${
|
|
479
|
+
io.write(`- ${name || id}:${id.startsWith('remote:')
|
|
451
480
|
? `远端成员,请用 td collab send ${id} "当前进展?" --text`
|
|
452
481
|
: `td collab capture ${id} --text`}`);
|
|
453
482
|
}
|
|
483
|
+
return 0;
|
|
454
484
|
}
|
|
485
|
+
output(result);
|
|
455
486
|
return 0;
|
|
456
487
|
}
|
|
457
488
|
if (command.action === 'rebind') {
|
|
@@ -551,7 +582,7 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
551
582
|
role: command.operation === 'unset' ? null : command.role });
|
|
552
583
|
if (o.text) {
|
|
553
584
|
io.write(command.operation === 'set'
|
|
554
|
-
? `定位已设置:${command.sessionId} = ${body.group.roles?.[command.sessionId] ?? ''}
|
|
585
|
+
? `定位已设置:${command.sessionId} = ${body.group.roles?.[command.sessionId] ?? ''}\n(改成员显示名: td collab rename ${command.sessionId} <新名字>)`
|
|
555
586
|
: `定位已清除:${command.sessionId}`);
|
|
556
587
|
}
|
|
557
588
|
else
|
|
@@ -666,8 +697,16 @@ export async function executeCollaborationCommand(command, context, io) {
|
|
|
666
697
|
output({ ...receipt, ok: false, code: 'WAIT_TIMEOUT', delivery_continues: true, last_error: error instanceof Error ? error.message : String(error) });
|
|
667
698
|
return 2;
|
|
668
699
|
}
|
|
669
|
-
|
|
670
|
-
|
|
700
|
+
const code = error instanceof CollaborationError ? error.code : 'COLLABORATION_ERROR';
|
|
701
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
702
|
+
if (o.text) {
|
|
703
|
+
// Single-line errors in text mode; the enqueued-message hint keeps a
|
|
704
|
+
// mid-wait failure from reading as a lost message.
|
|
705
|
+
io.write(`${code}: ${message}${receipt?.message_id ? `(消息 ${canonicalShortId(String(receipt.message_id))} 已入队,投递继续)` : ''}`);
|
|
706
|
+
return 1;
|
|
707
|
+
}
|
|
708
|
+
output({ ...(receipt ?? {}), ok: false, code,
|
|
709
|
+
error: message, ...(idempotencyKey ? { idempotency_key: idempotencyKey } : {}), ...(receipt ? { delivery_continues: true } : {}) });
|
|
671
710
|
return 1;
|
|
672
711
|
}
|
|
673
712
|
}
|
|
@@ -10,7 +10,17 @@ export function collaborationGroupRoutes(options) {
|
|
|
10
10
|
});
|
|
11
11
|
router.post('/collaboration-groups', async (req, res) => {
|
|
12
12
|
try {
|
|
13
|
-
const
|
|
13
|
+
const body = req.body ?? {};
|
|
14
|
+
const { id, name, sessionIds, expectedUpdatedAt } = body;
|
|
15
|
+
// Unknown fields used to be dropped silently while the save still
|
|
16
|
+
// returned ok — a caller submitting `members` to rename someone got a
|
|
17
|
+
// success and no rename. Reject them and route the common case (member
|
|
18
|
+
// renames) to the command that actually does it.
|
|
19
|
+
const unknownKeys = Object.keys(body).filter((key) => !['id', 'name', 'sessionIds', 'expectedUpdatedAt'].includes(key));
|
|
20
|
+
if (unknownKeys.length) {
|
|
21
|
+
const hint = unknownKeys.includes('members') ? ';要改成员显示名请用 td collab rename <session-id> <新名字>' : '';
|
|
22
|
+
throw new CollaborationError('INVALID_GROUP', `含未知字段 ${unknownKeys.join('、')}(合法字段:name, sessionIds, id, expectedUpdatedAt)${hint}`, 400);
|
|
23
|
+
}
|
|
14
24
|
if ((id !== undefined && (typeof id !== 'string' || !id))
|
|
15
25
|
|| typeof name !== 'string' || !name.trim() || !Array.isArray(sessionIds)
|
|
16
26
|
|| sessionIds.some((value) => typeof value !== 'string' || !value.trim())) {
|
|
@@ -23,7 +33,7 @@ export function collaborationGroupRoutes(options) {
|
|
|
23
33
|
if (id && (!existing || existing.deleted))
|
|
24
34
|
throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
|
|
25
35
|
if (expectedUpdatedAt !== undefined && (!Number.isFinite(expectedUpdatedAt) || expectedUpdatedAt !== existing?.updatedAt)) {
|
|
26
|
-
throw new CollaborationError('GROUP_CHANGED',
|
|
36
|
+
throw new CollaborationError('GROUP_CHANGED', `协作组已被修改(当前 updatedAt=${existing?.updatedAt})。请先确认最新成员列表,再以 expectedUpdatedAt=${existing?.updatedAt} 重新提交`, 409, { currentUpdatedAt: existing?.updatedAt });
|
|
27
37
|
}
|
|
28
38
|
const ids = [...new Set(sessionIds)];
|
|
29
39
|
if (ids.length < 2)
|
|
@@ -41,6 +51,7 @@ export function collaborationGroupRoutes(options) {
|
|
|
41
51
|
catch (error) {
|
|
42
52
|
res.status(error instanceof CollaborationError ? error.httpStatus : 500)
|
|
43
53
|
.json({ code: error instanceof CollaborationError ? error.code : 'GROUP_SAVE_FAILED',
|
|
54
|
+
...(error instanceof CollaborationError && error.details ? error.details : {}),
|
|
44
55
|
error: error instanceof Error ? error.message : '协作组保存失败' });
|
|
45
56
|
}
|
|
46
57
|
});
|
|
@@ -57,6 +68,7 @@ export function collaborationGroupRoutes(options) {
|
|
|
57
68
|
catch (error) {
|
|
58
69
|
res.status(error instanceof CollaborationError ? error.httpStatus : 500)
|
|
59
70
|
.json({ code: error instanceof CollaborationError ? error.code : 'GROUP_MOVE_FAILED',
|
|
71
|
+
...(error instanceof CollaborationError && error.details ? error.details : {}),
|
|
60
72
|
error: error instanceof Error ? error.message : '成员移动失败' });
|
|
61
73
|
}
|
|
62
74
|
});
|
|
@@ -97,13 +109,15 @@ export function collaborationGroupRoutes(options) {
|
|
|
97
109
|
catch (error) {
|
|
98
110
|
res.status(error instanceof CollaborationError ? error.httpStatus : 400)
|
|
99
111
|
.json({ code: error instanceof CollaborationError ? error.code : 'GROUP_PROMOTION_FAILED',
|
|
112
|
+
...(error instanceof CollaborationError && error.details ? error.details : {}),
|
|
100
113
|
error: error instanceof Error ? error.message : '跨服务组转换失败' });
|
|
101
114
|
}
|
|
102
115
|
});
|
|
103
116
|
router.delete('/collaboration-groups/:groupId', (req, res) => {
|
|
104
117
|
const existing = options.store.getGroup(req.params.groupId);
|
|
105
118
|
if (req.query.expectedUpdatedAt !== undefined && Number(req.query.expectedUpdatedAt) !== existing?.updatedAt) {
|
|
106
|
-
return res.status(409).json({ code: 'GROUP_CHANGED',
|
|
119
|
+
return res.status(409).json({ code: 'GROUP_CHANGED', currentUpdatedAt: existing?.updatedAt,
|
|
120
|
+
error: `协作组已变化(当前 updatedAt=${existing?.updatedAt}),请刷新后重新操作` });
|
|
107
121
|
}
|
|
108
122
|
if (!options.store.remove(req.params.groupId))
|
|
109
123
|
return res.status(404).json({ error: '协作组不存在' });
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { canonicalShortId } from './collaborationProtocol.js';
|
|
2
2
|
import { sanitizeCollaborationRole } from './collaborationStore.js';
|
|
3
|
-
const SHELL_RULE = '─'.repeat(
|
|
3
|
+
const SHELL_RULE = '─'.repeat(3);
|
|
4
4
|
/** Body larger than this (in UTF-8 bytes) is replaced by a retrieval pointer
|
|
5
5
|
* instead of being injected into the terminal verbatim. */
|
|
6
6
|
const MAX_INLINE_BODY_BYTES = 4_096;
|
|
@@ -61,41 +61,22 @@ export function collaborationMessageAnchorTokens(messages) {
|
|
|
61
61
|
return [message.id, counts.get(short) === 1 ? short : message.id];
|
|
62
62
|
}));
|
|
63
63
|
}
|
|
64
|
-
/**
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* so
|
|
70
|
-
* the
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
? `回复:td collab reply ${token} "内容" --text`
|
|
76
|
-
: `详情:td collab message get ${token} --json`;
|
|
77
|
-
}
|
|
78
|
-
/** A single delivered message: `来自:X · kind` over a fenced body, with the
|
|
79
|
-
* anchor line (reply route / read-back route) kept outside the fence. A fan-out
|
|
80
|
-
* dispatch (message.fanOutIds present) is flagged `· 群发` and names the
|
|
81
|
-
* sibling recipients on their own line, so a broadcast is never mistaken for
|
|
82
|
-
* a one-to-one assignment — raw ids fall back to the sanitized id itself. */
|
|
83
|
-
function formatCollaborationMessage(message, source, fannedNames, fence, token, now) {
|
|
84
|
-
const lines = [`来自:${source}${message.kind === 'message' ? '' : ` · ${message.kind}`}${fannedNames.length ? ' · 群发' : ''}`];
|
|
64
|
+
/** A single delivered message: `来自:X · kind · id` over a fenced body. The
|
|
65
|
+
* message id rides the source line — it is the one place a recipient can
|
|
66
|
+
* look up how to reply (`td collab --help`, always present below) without a
|
|
67
|
+
* per-message command template. A fan-out dispatch (message.fanOutIds
|
|
68
|
+
* present) is flagged `· 群发` and names the sibling recipients on their own
|
|
69
|
+
* line, so a broadcast is never mistaken for a one-to-one assignment — raw
|
|
70
|
+
* ids fall back to the sanitized id itself. The id must stay in every block:
|
|
71
|
+
* the delivery-confirm gate searches the terminal text for this exact token. */
|
|
72
|
+
function formatCollaborationMessage(message, source, fannedNames, fence, token) {
|
|
73
|
+
const kindSuffix = message.kind === 'message' ? '' : ` · ${message.kind}`;
|
|
74
|
+
const lines = [`来自:${source}${kindSuffix}${fannedNames.length ? ' · 群发' : ''} · ${token}`];
|
|
85
75
|
if (fannedNames.length)
|
|
86
76
|
lines.push(`同时发给了:${fannedNames.join('、')}`);
|
|
87
|
-
const rules = message.instructions;
|
|
88
|
-
if (rules?.text) {
|
|
89
|
-
const age = collaborationRulesAge(rules.updatedAt, now);
|
|
90
|
-
const inline = Array.from(rules.text).length <= 200 && rules.text.split(/\r\n?|\n/).length <= 4;
|
|
91
|
-
lines.push(inline
|
|
92
|
-
? `群规[${age}]:${rules.text}`
|
|
93
|
-
: `查看群规[${age}]:td collab rules get ${message.groupId} --text`);
|
|
94
|
-
}
|
|
95
77
|
lines.push('', fence, message.content, fence);
|
|
96
78
|
if (message.task)
|
|
97
79
|
lines.push('', `任务上报:${JSON.stringify(message.task)}`);
|
|
98
|
-
lines.push('', collaborationMessageAnchorLine(message, token));
|
|
99
80
|
return lines.join('\n');
|
|
100
81
|
}
|
|
101
82
|
export function formatCollaborationDelivery(input) {
|
|
@@ -117,10 +98,7 @@ export function formatCollaborationDelivery(input) {
|
|
|
117
98
|
const fence = '`'.repeat(Math.max(3, ...Array.from(body.matchAll(/`+/g), (match) => match[0].length + 1)));
|
|
118
99
|
const fannedNames = (message.fanOutIds ?? [])
|
|
119
100
|
.map((sessionId) => sanitizeCollaborationName(sessionsById.get(sessionId)?.name ?? sessionId));
|
|
120
|
-
|
|
121
|
-
// The stored message snapshot remains available through message get.
|
|
122
|
-
const instructions = groupsById.get(message.groupId)?.instructions ?? message.instructions;
|
|
123
|
-
blocks.push(formatCollaborationMessage({ ...message, content: body, instructions }, source, fannedNames, fence, token, now));
|
|
101
|
+
blocks.push(formatCollaborationMessage({ ...message, content: body }, source, fannedNames, fence, token));
|
|
124
102
|
}
|
|
125
103
|
// The shell header names the group only when every block belongs to one;
|
|
126
104
|
// a mixed batch drops the header entirely (no single-group claim to make).
|
|
@@ -128,53 +106,66 @@ export function formatCollaborationDelivery(input) {
|
|
|
128
106
|
const groupIds = [...new Set(input.messages.map((message) => message.groupId))];
|
|
129
107
|
const singleGroup = groupIds.length === 1 ? groupsById.get(groupIds[0]) : null;
|
|
130
108
|
const shellHeader = singleGroup
|
|
131
|
-
? `「${sanitizeCollaborationName(singleGroup.name ?? '协作组')}
|
|
109
|
+
? `「${sanitizeCollaborationName(singleGroup.name ?? '协作组')}」td群 · ${singleGroup.sessionIds.length} 人`
|
|
132
110
|
: '';
|
|
111
|
+
// One-time education block: static guidance that only rides the first
|
|
112
|
+
// delivery (and again whenever the roster or the group rules change).
|
|
113
|
+
// Current group guidance is preferred over the message's stored snapshot —
|
|
114
|
+
// the snapshot stays reachable through message get.
|
|
115
|
+
const educated = input.showRoutingHelp !== false;
|
|
116
|
+
const education = [];
|
|
117
|
+
if (educated) {
|
|
118
|
+
const rules = singleGroup?.instructions ?? input.messages.find((message) => message.instructions)?.instructions;
|
|
119
|
+
if (rules?.text) {
|
|
120
|
+
const age = collaborationRulesAge(rules.updatedAt, now);
|
|
121
|
+
const inline = Array.from(rules.text).length <= 200 && rules.text.split(/\r\n?|\n/).length <= 4;
|
|
122
|
+
education.push(inline
|
|
123
|
+
? `群规[${age}]:${rules.text}`
|
|
124
|
+
: `查看群规[${age}]:td collab rules get ${singleGroup?.id ?? input.messages[0].groupId} --text`);
|
|
125
|
+
}
|
|
126
|
+
const peerIds = Array.from(new Set(input.groups.flatMap((group) => group.sessionIds)))
|
|
127
|
+
.filter((sessionId) => sessionId !== input.targetSessionId);
|
|
128
|
+
// Keep routing IDs once, only for peers who are not already directly replyable.
|
|
129
|
+
const sourceIds = new Set(input.messages.map((message) => message.fromSessionId));
|
|
130
|
+
const peers = peerIds.filter((id) => !sourceIds.has(id)).map((sessionId) => {
|
|
131
|
+
const session = sessionsById.get(sessionId);
|
|
132
|
+
return `- ${session ? sanitizeCollaborationName(session.name) : '离线会话'}:\`td collab send ${sessionId} "消息内容" --text\``;
|
|
133
|
+
});
|
|
134
|
+
if (peers.length)
|
|
135
|
+
education.push('联系其他成员:', ...peers);
|
|
136
|
+
const captureHelp = '查看屏幕:`td collab capture <会话ID> --text`(只读,不打断对方;仅同组本机 tmux,远端用 send 询问)。ID:`td collab status --text`。快照不代表完成,勿循环轮询。';
|
|
137
|
+
education.push(captureHelp);
|
|
138
|
+
if (peerIds.some((id) => id.startsWith('remote:'))) {
|
|
139
|
+
education.push('跨服务通信使用 td collab;已登记节点由服务后台直接投递,使用 message get 查看送达回执与重试原因。');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Dynamic notices report current conditions and are never gated:
|
|
143
|
+
// an unreachable peer changes what a reply can actually do, every delivery.
|
|
133
144
|
const peerIds = Array.from(new Set(input.groups.flatMap((group) => group.sessionIds)))
|
|
134
145
|
.filter((sessionId) => sessionId !== input.targetSessionId);
|
|
135
|
-
// Keep routing IDs once, only for peers who are not already directly replyable.
|
|
136
|
-
const sourceIds = new Set(input.messages.map((message) => message.fromSessionId));
|
|
137
|
-
const peers = peerIds.filter((id) => !sourceIds.has(id)).map((sessionId) => {
|
|
138
|
-
const session = sessionsById.get(sessionId);
|
|
139
|
-
return `- ${session ? sanitizeCollaborationName(session.name) : '离线会话'}:\`td collab send ${sessionId} "消息内容" --text\``;
|
|
140
|
-
});
|
|
141
146
|
const unreachable = peerIds.filter((id) => sessionsById.get(id)?.status === 'service-unreachable');
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
...unreachable.map((id) => {
|
|
148
|
-
const session = sessionsById.get(id);
|
|
149
|
-
return `注意:${session ? sanitizeCollaborationName(session.name) : id} 服务不可达,消息无法送达;仅可排队等待重连。`;
|
|
150
|
-
}),
|
|
151
|
-
];
|
|
152
|
-
const captureHelp = '查看屏幕:`td collab capture <会话ID> --text`(只读,不打断对方;仅同组本机 tmux,远端用 send 询问)。ID:`td collab status --text`。快照不代表完成,勿循环轮询。';
|
|
153
|
-
const notes = [...routingHelp, ...dynamicNotices,
|
|
154
|
-
...(input.showRoutingHelp === false ? [] : [captureHelp]), '帮助:td collab --help'];
|
|
155
|
-
// Notes (education + dynamic notices) live inside the shell, after the last
|
|
156
|
-
// message, so the closing rule still marks the end of the delivered block.
|
|
147
|
+
const dynamicNotices = unreachable.map((id) => {
|
|
148
|
+
const session = sessionsById.get(id);
|
|
149
|
+
return `注意:${session ? sanitizeCollaborationName(session.name) : id} 服务不可达,消息无法送达;仅可排队等待重连。`;
|
|
150
|
+
});
|
|
151
|
+
const notes = [...education, ...dynamicNotices, '帮助:td collab --help'];
|
|
157
152
|
if (!blocks.length)
|
|
158
153
|
return notes.join('\n');
|
|
159
|
-
//
|
|
160
|
-
// knows the name peers see (a rename lands on the next delivery — no
|
|
161
|
-
// separate notification needed) and, in a single-group delivery, its role
|
|
162
|
-
// The name needs no group claim and survives mixed batches; the
|
|
163
|
-
// only rides when the batch maps to exactly one group.
|
|
154
|
+
// Recipient identity rides the shell under the header so the recipient
|
|
155
|
+
// always knows the name peers see (a rename lands on the next delivery — no
|
|
156
|
+
// separate notification needed) and, in a single-group delivery, its role
|
|
157
|
+
// (定位). The name needs no group claim and survives mixed batches; the
|
|
158
|
+
// role only rides when the batch maps to exactly one group.
|
|
164
159
|
const ownName = sanitizeCollaborationName(sessionsById.get(input.targetSessionId)?.name ?? '');
|
|
165
160
|
const ownRole = singleGroup
|
|
166
161
|
? sanitizeCollaborationRole(singleGroup.roles?.[input.targetSessionId] ?? '')
|
|
167
162
|
: '';
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
lines.push(`你的定位:${ownRole}`);
|
|
175
|
-
// No blank line between the identity block and the first message, nor
|
|
176
|
-
// between the last message and the notes: the shell reads as one compact
|
|
177
|
-
// block, and only message-to-message boundaries keep a blank line.
|
|
163
|
+
const contextLines = [shellHeader, ownName ? `收件人:${ownName}${ownRole ? ` · 定位:${ownRole}` : ''}` : ''].filter(Boolean);
|
|
164
|
+
const lines = [SHELL_RULE, ...contextLines];
|
|
165
|
+
// A blank line separates the who/where context block from the message
|
|
166
|
+
// stream: the shell reads as context, then one compact block per message.
|
|
167
|
+
if (contextLines.length)
|
|
168
|
+
lines.push('');
|
|
178
169
|
lines.push(blocks.join('\n\n'), ...notes, SHELL_RULE);
|
|
179
170
|
return lines.join('\n');
|
|
180
171
|
}
|
|
@@ -13,10 +13,15 @@ export const COLLAB_LIMITS = {
|
|
|
13
13
|
export class CollaborationError extends Error {
|
|
14
14
|
code;
|
|
15
15
|
httpStatus;
|
|
16
|
-
|
|
16
|
+
details;
|
|
17
|
+
/** Structured fields merged into the JSON error body (e.g. the group's
|
|
18
|
+
* current updatedAt on GROUP_CHANGED) so callers can retry programmatically
|
|
19
|
+
* without scraping the sentence. */
|
|
20
|
+
constructor(code, message, httpStatus = 400, details) {
|
|
17
21
|
super(message);
|
|
18
22
|
this.code = code;
|
|
19
23
|
this.httpStatus = httpStatus;
|
|
24
|
+
this.details = details;
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
/** A delivered terminal shows a short id, because a 36-character one wraps in
|
|
@@ -274,7 +274,7 @@ export class CollaborationService {
|
|
|
274
274
|
if (input.id && (!existing || existing.deleted))
|
|
275
275
|
throw new Error('GROUP_NOT_FOUND');
|
|
276
276
|
if (existing && input.expectedUpdatedAt !== existing.updatedAt)
|
|
277
|
-
throw new CollaborationError('GROUP_CHANGED',
|
|
277
|
+
throw new CollaborationError('GROUP_CHANGED', `协作组已变化(当前 updatedAt=${existing.updatedAt}),请刷新后重试`, 409, { currentUpdatedAt: existing.updatedAt });
|
|
278
278
|
if (typeof input.name !== 'string' || !input.name.trim() || COLLAB_NAME_FORBIDDEN.test(input.name) || !Array.isArray(input.sessionIds) || input.sessionIds.some(id => typeof id !== 'string') || new Set(input.sessionIds).size < 2)
|
|
279
279
|
throw new Error('INVALID_GROUP');
|
|
280
280
|
if (!input.sessionIds.some(id => address(id)) && !existing?.federated) {
|
|
@@ -182,7 +182,7 @@ export class CollaborationStore {
|
|
|
182
182
|
throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
|
|
183
183
|
if (source.id === target.id || !source.sessionIds.includes(input.sessionId)
|
|
184
184
|
|| source.updatedAt !== input.expectedSourceUpdatedAt || target.updatedAt !== input.expectedTargetUpdatedAt) {
|
|
185
|
-
throw new CollaborationError('GROUP_CHANGED',
|
|
185
|
+
throw new CollaborationError('GROUP_CHANGED', `协作组已变化(当前 updatedAt:源组=${source.updatedAt},目标组=${target.updatedAt}),请刷新后重新移动成员`, 409, { currentSourceUpdatedAt: source.updatedAt, currentTargetUpdatedAt: target.updatedAt });
|
|
186
186
|
}
|
|
187
187
|
const sourceIds = source.sessionIds.filter((id) => id !== input.sessionId);
|
|
188
188
|
const dissolved = sourceIds.length < 2;
|
|
@@ -448,7 +448,7 @@ export class CollaborationStore {
|
|
|
448
448
|
if (!existing || existing.deleted)
|
|
449
449
|
throw new CollaborationError('GROUP_NOT_FOUND', '协作组已删除,请刷新列表', 404);
|
|
450
450
|
if (existing.federated || existing.updatedAt !== expectedUpdatedAt || this.getGroup(group.id)) {
|
|
451
|
-
throw new CollaborationError('GROUP_CHANGED',
|
|
451
|
+
throw new CollaborationError('GROUP_CHANGED', `协作组已被修改(当前 updatedAt=${existing.updatedAt}),请刷新后再保存`, 409, { currentUpdatedAt: existing.updatedAt });
|
|
452
452
|
}
|
|
453
453
|
const promoted = { ...group, createdAt: existing.createdAt, updatedAt: Math.max(Date.now(), existing.updatedAt + 1, group.updatedAt) };
|
|
454
454
|
const idempotency = { ...this.document.idempotency };
|