termdock 1.4.296 → 1.4.297
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.desktop-dist/main.js +35 -2
- package/README.en.md +257 -0
- package/README.md +284 -133
- package/dist/client/assets/App-BbqtNY2x.js +177 -0
- package/dist/client/assets/App-BbqtNY2x.js.br +0 -0
- package/dist/client/assets/App-BbqtNY2x.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-DXprT-7m.js → ChangeWalkthroughPanel-CvQJIp_C.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-CvQJIp_C.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-CvQJIp_C.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-C01GXOEq.js +2 -0
- package/dist/client/assets/ComputerControlView-CW1uA133.js +4 -0
- package/dist/client/assets/ComputerControlView-CW1uA133.js.br +0 -0
- package/dist/client/assets/ComputerControlView-CW1uA133.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-DZcUunT5.js → DagPlayground-CdE3tQsl.js} +1 -1
- package/dist/client/assets/DagPlayground-CdE3tQsl.js.br +0 -0
- package/dist/client/assets/DagPlayground-CdE3tQsl.js.gz +0 -0
- package/dist/client/assets/{DiffLab-Brv4DCTb.js → DiffLab-BLGNnazd.js} +1 -1
- package/dist/client/assets/DiffLab-BLGNnazd.js.br +0 -0
- package/dist/client/assets/DiffLab-BLGNnazd.js.gz +0 -0
- package/dist/client/assets/{DiffReview-BuLu1qCi.js → DiffReview-CYw3ASI6.js} +1 -1
- package/dist/client/assets/DiffReview-CYw3ASI6.js.br +0 -0
- package/dist/client/assets/DiffReview-CYw3ASI6.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-DSGamuAD.js → DiffReviewLab-CySIuWq_.js} +1 -1
- package/dist/client/assets/DiffReviewLab-CySIuWq_.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CySIuWq_.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-BUCYxg27.js → DiffViewer-WsYvN75z.js} +13 -13
- package/dist/client/assets/DiffViewer-WsYvN75z.js.br +0 -0
- package/dist/client/assets/DiffViewer-WsYvN75z.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-Bnlo-oVE.js → ModelPreview-Dnlx9ByX.js} +1 -1
- package/dist/client/assets/ModelPreview-Dnlx9ByX.js.br +0 -0
- package/dist/client/assets/ModelPreview-Dnlx9ByX.js.gz +0 -0
- package/dist/client/assets/RightSidebar-Cp5b_y0u.js +1678 -0
- package/dist/client/assets/RightSidebar-Cp5b_y0u.js.br +0 -0
- package/dist/client/assets/RightSidebar-Cp5b_y0u.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-K6Ppkrbt.js → abnfDiagram-VCTEODGH-CK1mGFRQ.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-CK1mGFRQ.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CK1mGFRQ.js.gz +0 -0
- package/dist/client/assets/app-DfACMixk.js +35 -0
- package/dist/client/assets/app-DfACMixk.js.br +0 -0
- package/dist/client/assets/app-DfACMixk.js.gz +0 -0
- package/dist/client/assets/{arc-Co2OQqtH.js → arc-yZE1gqDl.js} +1 -1
- package/dist/client/assets/arc-yZE1gqDl.js.br +0 -0
- package/dist/client/assets/arc-yZE1gqDl.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-BQnl7tVF.js → architectureDiagram-5GKGNRK7-U5XSC7cY.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-U5XSC7cY.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-U5XSC7cY.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-Bm5D5-qq.js → blockDiagram-I7D4REHJ-pVJnntSG.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-pVJnntSG.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-pVJnntSG.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-B9oPpey8.js → c4Diagram-7LVT6UL2-Bwz0AJJV.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-Bwz0AJJV.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-Bwz0AJJV.js.gz +0 -0
- package/dist/client/assets/channel-qqjDJtcZ.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-TMaWHH0U.js → chunk-2Q5K7J3B-CXJ1kP4X.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CH7XpknV.js → chunk-5VM5RSS4-Dpdum_2z.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-CJ1rotBe.js → chunk-F27PBJKO-BCG5Zi_-.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-BCG5Zi_-.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BCG5Zi_-.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-UYKZ5uOG.js → chunk-IMKFNOWR-BM-GcNcv.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-BM-GcNcv.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BM-GcNcv.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BYL8aXjQ.js → chunk-JWPE2WC7-BWiXpQT9.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-C3E8yfU2.js → chunk-POPQ4Y6H-vQSslMNv.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-DCTDYa1E.js → chunk-SVP7TREG-Aqfz6nPA.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-Aqfz6nPA.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Aqfz6nPA.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-wHiaOsBh.js → chunk-TICWLB2K-DDjekMd0.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-DDjekMd0.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DDjekMd0.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-D_d479hK.js → chunk-XXDRQBXY-BegIBcFg.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-BDuBrfNz.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BDuBrfNz.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-DnhlPBLr.js → cose-bilkent-JH36ORCC-B2izFsBY.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-B2izFsBY.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-B2izFsBY.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BsjzNqJZ.js → cynefin-OW5HDTMX-C7HA99Zc.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-C7HA99Zc.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-C7HA99Zc.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-sVUb0zY5.js → cynefinDiagram-5FMLGOSQ-vNIzcCYl.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-vNIzcCYl.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-vNIzcCYl.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-YErk8S-c.js → dagre-GXQ25YYZ-Bv9WHNT9.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-Bv9WHNT9.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Bv9WHNT9.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-BaHbV5ho.js → diagram-S7CK7UJ4-DFg5UNic.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DFg5UNic.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DFg5UNic.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BocCYx3V.js → diagram-UQ7AKVKN-BFQsCAs7.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-BFQsCAs7.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BFQsCAs7.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-ZMTYwWOQ.js → diagram-VSXAHHWV-C4R8TKEv.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-C4R8TKEv.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-C4R8TKEv.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-xmk_EY1h.js → diagram-VX7I27RA-CL75HIf0.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-CL75HIf0.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CL75HIf0.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-DKDY_cG6.js → diagram-Z3DM3KII-CfJampuO.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-CfJampuO.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CfJampuO.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-C_0z-Jx9.js → ebnfDiagram-PWID7BFC-Ct53jhNd.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Ct53jhNd.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Ct53jhNd.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DHUaG7mt.js → erDiagram-RLTQ6QDP-BgL2xOOa.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-BgL2xOOa.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BgL2xOOa.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-BMNsdMBD.js → flowDiagram-HODETNUW-Cp4Nu5jY.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-Cp4Nu5jY.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Cp4Nu5jY.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-BH1AiKuF.js → ganttDiagram-EL5Y4UJY-DI0rdyPX.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DI0rdyPX.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DI0rdyPX.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-DvupkPra.js → gestureArbiter-Belsq4xq.js} +1 -1
- package/dist/client/assets/gestureArbiter-Belsq4xq.js.br +0 -0
- package/dist/client/assets/gestureArbiter-Belsq4xq.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-DAgV0MV-.js → gitGraphDiagram-WWUBYQGX-C0H4loQP.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C0H4loQP.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C0H4loQP.js.gz +0 -0
- package/dist/client/assets/index-CYmI1EsU.css +1 -0
- package/dist/client/assets/index-CYmI1EsU.css.br +0 -0
- package/dist/client/assets/index-CYmI1EsU.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-C7iSg0cB.js → infoDiagram-27XIBGKW-BN4RpQTL.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BpZwsQvw.js → ishikawaDiagram-5VMMS53U-BZQ5qZB_.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BZQ5qZB_.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BZQ5qZB_.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-DXEeFLvw.js → journeyDiagram-3NMN7TZE-Q8bx0_N4.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Q8bx0_N4.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Q8bx0_N4.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-CV9ivL5u.js → kanban-definition-UXKFOSKX-D7JhGtpO.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-D7JhGtpO.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-D7JhGtpO.js.gz +0 -0
- package/dist/client/assets/{layout-DsKKsJKX.js → layout-DERUg0xU.js} +1 -1
- package/dist/client/assets/layout-DERUg0xU.js.br +0 -0
- package/dist/client/assets/layout-DERUg0xU.js.gz +0 -0
- package/dist/client/assets/{linear-Dl9MDNV3.js → linear-lNTe4x00.js} +1 -1
- package/dist/client/assets/linear-lNTe4x00.js.br +0 -0
- package/dist/client/assets/linear-lNTe4x00.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-CxZFe3as.js → mermaid.core-WHCwLQDQ.js} +5 -5
- package/dist/client/assets/mermaid.core-WHCwLQDQ.js.br +0 -0
- package/dist/client/assets/mermaid.core-WHCwLQDQ.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-C3ormYdL.js → mindmap-definition-YA3MSWOX-BouapA4v.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BouapA4v.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BouapA4v.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-a8z6jJNY.js → passwordBootstrap-E0piP814.js} +2 -2
- package/dist/client/assets/passwordBootstrap-E0piP814.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-E0piP814.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-CG0wNylH.js → pegDiagram-XKGWAZYB-Curpo5Rq.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-Curpo5Rq.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-Curpo5Rq.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-D2-ydqyX.js → pieDiagram-E7YTZNPT-BLQCHaOG.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-BLQCHaOG.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-BLQCHaOG.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-CK3fRC18.js → quadrantDiagram-AXDQQJYC-PViFqGao.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-PViFqGao.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-PViFqGao.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-V4EFlydu.js → railroadDiagram-O6MQD6OU-BtHo1LpT.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BtHo1LpT.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BtHo1LpT.js.gz +0 -0
- package/dist/client/assets/rdpSession-O_f9ejOI.js +1 -0
- package/dist/client/assets/rdpSession-O_f9ejOI.js.br +0 -0
- package/dist/client/assets/rdpSession-O_f9ejOI.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-BjmYd1vB.js → referenceLongPress-BZr7Lqzh.js} +1 -1
- package/dist/client/assets/referenceLongPress-BZr7Lqzh.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BZr7Lqzh.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-Di1_tyAX.js → requirementDiagram-BXWQKSXE-BUm81HUU.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BUm81HUU.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BUm81HUU.js.gz +0 -0
- package/dist/client/assets/rfb-CxsXsMUX.js +5 -0
- package/dist/client/assets/rfb-CxsXsMUX.js.br +0 -0
- package/dist/client/assets/rfb-CxsXsMUX.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-D-qpWNza.js → sankeyDiagram-P5KCCOFB-B-_-aZ_y.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B-_-aZ_y.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B-_-aZ_y.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DmIxafrB.js → sequenceDiagram-WJ2MYXX4-DT6LDIfx.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DT6LDIfx.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DT6LDIfx.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-4MTh3tTt.js → sizeCapture-INFHLROL-CTYerd9j.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-CTYerd9j.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CTYerd9j.js.gz +0 -0
- package/dist/client/assets/{sparkles-Dk93lHg0.js → sparkles-CgaUBIwH.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-CgYgneIv.js → stateDiagram-D77RDMKH-CQbb5x2_.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-CQbb5x2_.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CQbb5x2_.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CJAVXcx1.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-OWA9HFVf.js → swimlanes-42K2YHIH-BHZ4X2Fd.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BHZ4X2Fd.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BHZ4X2Fd.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-VL2nyI27.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-BAzx5DyO.js → timeline-definition-24CTP7MA-BrjH6R6w.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-BrjH6R6w.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BrjH6R6w.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-CUIBOwqW.js → triangle-alert-C9IHD_eF.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-CBZ-PhTQ.js → vennDiagram-4TSXK5OY-C9f5jYgk.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-C9f5jYgk.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-C9f5jYgk.js.gz +0 -0
- package/dist/client/assets/{videoCompression-BCnqCkRK.js → videoCompression-DJaU8exd.js} +1 -1
- package/dist/client/assets/videoCompression-DJaU8exd.js.br +0 -0
- package/dist/client/assets/videoCompression-DJaU8exd.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DbWzC6l5.js → wardleyDiagram-VM6X3IG4-6ljFvtiC.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-6ljFvtiC.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-6ljFvtiC.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BmApnTWE.js → xychartDiagram-S5SC5T6Z-BaR2hm3e.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BaR2hm3e.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BaR2hm3e.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 +3 -3
- package/dist/server/agent/collaborationGroupRoutes.js +0 -2
- package/dist/server/agent/collaborationResultPresentation.js +14 -0
- package/dist/server/agent/collaborationService.js +3 -1
- package/dist/server/agent/collaborationStore.js +12 -21
- package/dist/server/agent/collaborationTaskBundles.js +2 -2
- package/dist/server/agent/collaborationTaskCli.js +4 -4
- package/dist/server/agent/collaborationTaskStore.js +87 -25
- package/dist/server/agent/collaborationTaskWorkspace.js +66 -1
- package/dist/server/agent/collaborationTeam.js +16 -0
- package/dist/server/agent/executionArchives.js +52 -0
- package/dist/server/entry.js +6 -1
- package/dist/server/federation/runtime.js +24 -6
- package/dist/server/routes/computer.js +198 -0
- package/dist/server/routes/computerTarget.js +23 -0
- package/dist/server/routes/guacamoleProtocol.js +55 -0
- package/dist/server/routes/rdp.js +162 -0
- package/dist/server/routes/terminal.js +375 -360
- package/dist/server/shared/computerPreferences.js +29 -0
- package/dist/server/utils/computerCredentials.js +88 -0
- package/dist/server/utils/sharedSampling.js +93 -0
- package/dist/server/utils/terminalOutputDelivery.js +8 -1
- package/dist/server/utils/tmuxMetadataWriter.js +61 -0
- package/package.json +4 -1
- package/runtime-manifest.json +4 -4
- package/dist/client/assets/App-BiLjlrk-.js +0 -171
- package/dist/client/assets/App-BiLjlrk-.js.br +0 -0
- package/dist/client/assets/App-BiLjlrk-.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DXprT-7m.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DXprT-7m.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DZcUunT5.js.br +0 -0
- package/dist/client/assets/DagPlayground-DZcUunT5.js.gz +0 -0
- package/dist/client/assets/DiffLab-Brv4DCTb.js.br +0 -0
- package/dist/client/assets/DiffLab-Brv4DCTb.js.gz +0 -0
- package/dist/client/assets/DiffReview-BuLu1qCi.js.br +0 -0
- package/dist/client/assets/DiffReview-BuLu1qCi.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-DSGamuAD.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DSGamuAD.js.gz +0 -0
- package/dist/client/assets/DiffViewer-BUCYxg27.js.br +0 -0
- package/dist/client/assets/DiffViewer-BUCYxg27.js.gz +0 -0
- package/dist/client/assets/ModelPreview-Bnlo-oVE.js.br +0 -0
- package/dist/client/assets/ModelPreview-Bnlo-oVE.js.gz +0 -0
- package/dist/client/assets/RightSidebar-r_3Lk5Rp.js +0 -1678
- package/dist/client/assets/RightSidebar-r_3Lk5Rp.js.br +0 -0
- package/dist/client/assets/RightSidebar-r_3Lk5Rp.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-K6Ppkrbt.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-K6Ppkrbt.js.gz +0 -0
- package/dist/client/assets/app-CATnTUZF.js +0 -35
- package/dist/client/assets/app-CATnTUZF.js.br +0 -0
- package/dist/client/assets/app-CATnTUZF.js.gz +0 -0
- package/dist/client/assets/arc-Co2OQqtH.js.br +0 -0
- package/dist/client/assets/arc-Co2OQqtH.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BQnl7tVF.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-BQnl7tVF.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Bm5D5-qq.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Bm5D5-qq.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-B9oPpey8.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-B9oPpey8.js.gz +0 -0
- package/dist/client/assets/channel-Czv2W4qA.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-CJ1rotBe.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CJ1rotBe.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-UYKZ5uOG.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-UYKZ5uOG.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DCTDYa1E.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DCTDYa1E.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-wHiaOsBh.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-wHiaOsBh.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-BdvOOL9k.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BdvOOL9k.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DnhlPBLr.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DnhlPBLr.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BsjzNqJZ.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BsjzNqJZ.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-sVUb0zY5.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-sVUb0zY5.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-YErk8S-c.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-YErk8S-c.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BaHbV5ho.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BaHbV5ho.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BocCYx3V.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BocCYx3V.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-ZMTYwWOQ.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-ZMTYwWOQ.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-xmk_EY1h.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-xmk_EY1h.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKDY_cG6.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKDY_cG6.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-C_0z-Jx9.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-C_0z-Jx9.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DHUaG7mt.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DHUaG7mt.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMNsdMBD.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMNsdMBD.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BH1AiKuF.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BH1AiKuF.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-DvupkPra.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DvupkPra.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DAgV0MV-.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DAgV0MV-.js.gz +0 -0
- package/dist/client/assets/index-Bi9A6QUP.css +0 -1
- package/dist/client/assets/index-Bi9A6QUP.css.br +0 -0
- package/dist/client/assets/index-Bi9A6QUP.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BpZwsQvw.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BpZwsQvw.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DXEeFLvw.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DXEeFLvw.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CV9ivL5u.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CV9ivL5u.js.gz +0 -0
- package/dist/client/assets/layout-DsKKsJKX.js.br +0 -0
- package/dist/client/assets/layout-DsKKsJKX.js.gz +0 -0
- package/dist/client/assets/linear-Dl9MDNV3.js.br +0 -0
- package/dist/client/assets/linear-Dl9MDNV3.js.gz +0 -0
- package/dist/client/assets/mermaid.core-CxZFe3as.js.br +0 -0
- package/dist/client/assets/mermaid.core-CxZFe3as.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-C3ormYdL.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-C3ormYdL.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-a8z6jJNY.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-a8z6jJNY.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-CG0wNylH.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-CG0wNylH.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-D2-ydqyX.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-D2-ydqyX.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CK3fRC18.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-CK3fRC18.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-V4EFlydu.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-V4EFlydu.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-BjmYd1vB.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BjmYd1vB.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Di1_tyAX.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Di1_tyAX.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-D-qpWNza.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-D-qpWNza.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DmIxafrB.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DmIxafrB.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-4MTh3tTt.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-4MTh3tTt.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CgYgneIv.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CgYgneIv.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CvPf0fxf.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-OWA9HFVf.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-OWA9HFVf.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CZ_OauyW.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-BAzx5DyO.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-BAzx5DyO.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CBZ-PhTQ.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CBZ-PhTQ.js.gz +0 -0
- package/dist/client/assets/videoCompression-BCnqCkRK.js.br +0 -0
- package/dist/client/assets/videoCompression-BCnqCkRK.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DbWzC6l5.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DbWzC6l5.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmApnTWE.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmApnTWE.js.gz +0 -0
|
@@ -4,8 +4,8 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
4
4
|
const o = command.options, operation = command.operation;
|
|
5
5
|
const bodyOptions = ['content', 'file', 'stdin', 'idempotency-key'];
|
|
6
6
|
const allowed = {
|
|
7
|
-
list: ['group'], get: [], create: [...bodyOptions, 'group', 'title', 'constraints', 'acceptance', 'assignee', 'coordinator', 'parent', 'depends-on', 'managed', 'reviewers', 'shared-directory', 'integration'],
|
|
8
|
-
assign: [...bodyOptions, 'assignee', 'revision'], report: [...bodyOptions, 'attempt', 'status', 'evidence'], ask: [...bodyOptions, 'attempt', 'options'],
|
|
7
|
+
list: ['group'], get: [], create: [...bodyOptions, 'group', 'title', 'constraints', 'acceptance', 'assignee', 'coordinator', 'parent', 'depends-on', 'managed', 'reviewers', 'shared-directory', 'integration', 'work-type'],
|
|
8
|
+
assign: [...bodyOptions, 'assignee', 'revision'], report: [...bodyOptions, 'attempt', 'status', 'evidence', 'summary'], ask: [...bodyOptions, 'attempt', 'options'],
|
|
9
9
|
plan: [...bodyOptions, 'attempt', 'evidence'], review: [...bodyOptions, 'artifact', 'evidence', 'verdict'], comment: bodyOptions,
|
|
10
10
|
'request-review': [...bodyOptions, 'artifact', 'assignee', 'revision'],
|
|
11
11
|
revise: [...bodyOptions, 'revision'], close: [...bodyOptions, 'revision'], reopen: [...bodyOptions, 'revision'],
|
|
@@ -40,7 +40,7 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
40
40
|
return request('POST', '/tasks', { input: { idempotencyKey, groupId: o.group, title: o.title, spec: content,
|
|
41
41
|
constraints: o.constraints, acceptance: o.acceptance, assigneeSessionId: o.assignee, coordinatorSessionId: o.coordinator,
|
|
42
42
|
parentTaskId: o.parent, dependsOn: o['depends-on'] ? String(o['depends-on']).split(',').filter(Boolean) : undefined,
|
|
43
|
-
integration: o.integration === true, managed: o.managed === true, isolated: o['shared-directory'] !== true,
|
|
43
|
+
integration: o.integration === true, managed: o.managed === true, isolated: o['shared-directory'] !== true, workType: o['work-type'],
|
|
44
44
|
reviewerSessionIds: o.reviewers ? String(o.reviewers).split(',').filter(Boolean) : undefined } });
|
|
45
45
|
}
|
|
46
46
|
const options = o.options ? JSON.parse(String(o.options)) : undefined;
|
|
@@ -49,6 +49,6 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
49
49
|
return request('POST', `/tasks/${encodeURIComponent(command.target)}`, { input: {
|
|
50
50
|
kind: operation === 'plan' ? 'submit-plan' : operation, idempotencyKey, expectedRevision, content,
|
|
51
51
|
assigneeSessionId: o.assignee, attemptId: o.attempt, status: o.status, artifactId: o.artifact, options, verdict: o.verdict,
|
|
52
|
-
evidence: o.evidence ? JSON.parse(String(o.evidence)) : undefined,
|
|
52
|
+
evidence: o.evidence ? JSON.parse(String(o.evidence)) : undefined, summary: o.summary,
|
|
53
53
|
} });
|
|
54
54
|
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { randomBytes, createHash } from 'node:crypto';
|
|
2
2
|
import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
|
+
import { collaborationResultPresentation } from './collaborationResultPresentation.js';
|
|
4
5
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
5
6
|
import { taskMemberKey } from './collaborationTaskTypes.js';
|
|
6
7
|
const id = () => randomBytes(16).toString('hex');
|
|
8
|
+
const codeSteps = (tasks, rootId) => tasks.filter(t => {
|
|
9
|
+
if (t.workflow?.rootTaskId !== rootId || !t.workflow.isolated || t.workflow.workType === 'read-only' || t.workflow.integration || t.status === 'closed')
|
|
10
|
+
return false;
|
|
11
|
+
const accepted = t.artifacts.find(artifact => artifact.id === t.acceptedArtifactId);
|
|
12
|
+
const commit = accepted?.evidence?.commit;
|
|
13
|
+
// The service captures the worktree commit. A reviewed HEAD equal to its base
|
|
14
|
+
// proves there is no code delta to integrate, including legacy query tasks.
|
|
15
|
+
return !(t.status === 'accepted' && t.workspace && commit === t.workspace.base);
|
|
16
|
+
});
|
|
7
17
|
const sameMember = (a, b) => a !== null && b !== null && taskMemberKey(a) === taskMemberKey(b);
|
|
8
18
|
function text(value, name, limit = 32_000, required = false) {
|
|
9
19
|
if (value !== undefined && typeof value !== 'string')
|
|
@@ -44,6 +54,8 @@ function validateSnapshot(task) {
|
|
|
44
54
|
if (task.coordinator !== null)
|
|
45
55
|
member(task.coordinator);
|
|
46
56
|
if (task.workflow) {
|
|
57
|
+
if (task.workflow.workType !== undefined && (!['auto', 'code', 'read-only'].includes(task.workflow.workType) || task.workflow.workType === 'auto' && task.workflow.kind !== 'goal'))
|
|
58
|
+
invalid();
|
|
47
59
|
if (!['goal', 'step'].includes(task.workflow.kind) || typeof task.workflow.paused !== 'boolean' || typeof task.workflow.isolated !== 'boolean'
|
|
48
60
|
|| !Array.isArray(task.workflow.reviewers) || task.workflow.reviewers.length > 32 || !Number.isInteger(task.workflow.maxRevisions)
|
|
49
61
|
|| task.workflow.maxRevisions < 1 || task.workflow.maxRevisions > 10 || task.workflow.rootTaskId && !validId(task.workflow.rootTaskId))
|
|
@@ -69,6 +81,10 @@ function validateSnapshot(task) {
|
|
|
69
81
|
if (!event || !validId(event.id) || !Number.isSafeInteger(event.sequence) || event.sequence < 1 || typeof event.kind !== 'string'
|
|
70
82
|
|| typeof event.content !== 'string' || !time(event.createdAt) || event.attemptId !== null && !task.attempts.some(a => a.id === event.attemptId))
|
|
71
83
|
invalid();
|
|
84
|
+
if (event.deliveryId !== undefined && !validId(event.deliveryId))
|
|
85
|
+
invalid();
|
|
86
|
+
if (event.source !== undefined && !['system', 'user'].includes(event.source))
|
|
87
|
+
invalid();
|
|
72
88
|
if (event.actor !== null)
|
|
73
89
|
member(event.actor);
|
|
74
90
|
if (event.target)
|
|
@@ -85,6 +101,7 @@ function validateSnapshot(task) {
|
|
|
85
101
|
if (!artifact || !validId(artifact.id) || !task.attempts.some(a => a.id === artifact.attemptId) || !['plan', 'result', 'review'].includes(artifact.kind)
|
|
86
102
|
|| typeof artifact.content !== 'string' || !time(artifact.createdAt))
|
|
87
103
|
invalid();
|
|
104
|
+
text(artifact.summary, '结果摘要', 2400);
|
|
88
105
|
member(artifact.actor);
|
|
89
106
|
if (artifact.verdict && !['pass', 'changes', 'blocked'].includes(artifact.verdict))
|
|
90
107
|
invalid();
|
|
@@ -144,9 +161,9 @@ export class CollaborationTaskStore {
|
|
|
144
161
|
return { ...task, spec: task.spec.slice(0, 512), constraints: '', acceptance: '',
|
|
145
162
|
deliveries: task.deliveries.filter(d => d.attemptId === task.activeAttemptId && d.error).slice(-1),
|
|
146
163
|
attempts: current ? [{ ...current, report: current.report ? { ...current.report, content: '', evidence: undefined } : undefined }] : [],
|
|
147
|
-
artifacts: [plan, result, ...task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result?.id)].filter((a) => !!a).map(a => ({ ...a, content: '', evidence: undefined })),
|
|
164
|
+
artifacts: [plan, result, ...task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result?.id)].filter((a) => !!a).map(a => ({ ...a, content: '', summary: (a.summary || (a.kind === 'result' && collaborationResultPresentation(a.content).condensed ? collaborationResultPresentation(a.content).summary : undefined))?.slice(0, 800), evidence: undefined })),
|
|
148
165
|
decisions: task.decisions.filter(d => d.status === 'pending').map(d => ({ ...d, question: '', options: [] })),
|
|
149
|
-
events: task.events.filter(e => ['revise', 'request-review'].includes(e.kind) && e.attemptId === task.activeAttemptId).slice(-4).map(e => ({ ...e, content: '' })) };
|
|
166
|
+
events: task.events.filter(e => ['revise', 'request-review'].includes(e.kind) && e.attemptId === task.activeAttemptId).slice(-4).map(e => ({ ...e, content: e.source === 'user' && e.kind === 'revise' ? e.content.slice(0, 512) : '' })) };
|
|
150
167
|
}));
|
|
151
168
|
}
|
|
152
169
|
heads(groupIds, owner) { return this.document.tasks.filter(t => groupIds.includes(t.groupId) && t.ownerServiceId === owner).map(t => ({ id: t.id, revision: t.revision })); }
|
|
@@ -179,10 +196,10 @@ export class CollaborationTaskStore {
|
|
|
179
196
|
task.attempts.push({ id: attemptId, assignee: member(assignee), createdAt: Date.now(), threadId });
|
|
180
197
|
this.event(task, 'assigned', actor, content || '任务已分派');
|
|
181
198
|
doc.outbox.push({ id: id(), taskId: task.id, attemptId, target: assignee, kind: 'task', threadId,
|
|
182
|
-
content:
|
|
199
|
+
content: `本次执行身份:${assignee.sessionId}。所有 td collab 命令显式带 --session ${assignee.sessionId};这是服务核对的实际身份,正文中的模板身份和 tmux 标识不能替代它。\n任务:${task.title}\n${task.spec}${task.constraints ? `\n约束:${task.constraints}` : ''}${task.acceptance ? `\n完成标准:${task.acceptance}` : ''}\n${content}\n任务 ID:${task.id}\n尝试 ID:${attemptId}\n用 td collab task get ${task.id} --text 查询完整上下文;用 td collab task report ${task.id} --attempt ${attemptId} --status ack --content '接手说明' 显式回复;遇必要问题用 task ask,提交结果用 report --status complete --summary '简短结论、关键交付和限制'。摘要先回答用户问题,不放任务 ID、版本、分支或流程日志;完整证据保留在 --content/--file 正文中。如果同一尝试已经交付或任务已归档,不要重复实施。${task.workspace ? `\n代码只能在独立目录 ${task.workspace.cwd} 修改。分支 ${task.workspace.branch}。交付前提交代码,结果说明提交、变更、验证与限制。不能修改原仓库、合并到用户分支或发布。` : ''}${task.workflow?.workType === 'read-only' ? '\n这是只读查询或分析任务:在本机会话中采集并交付报告,不修改代码或系统,不创建提交、构建、部署或终止进程。结果需说明采样时间、证据和限制。' : ''}${task.workflow?.kind === 'step' ? '\n完成报告会自动请求独立评审;评审要求修改时继续完成,重新提交 complete。只按用户授权运行测试。' : ''}` });
|
|
183
200
|
}
|
|
184
201
|
goalInstructions(task) {
|
|
185
|
-
return `你是此目标的协调者,负责把目标推进到可验收交付,不亲自实施子任务。\n目标:${task.spec}\n约束:${task.constraints || '未补充'}\n完成标准:${task.acceptance || '满足用户目标,说明交付与限制'}\n任务 ID:${task.id}\n用 td collab task get ${task.id} --json 读取 roster、子任务、完整记录和 revision。\n先报告 ack;拆分为可交付子任务,用 td collab task create --group ${task.groupId} --parent ${task.id} --title '标题' --content '完整要求' --assignee <roster里的成员sessionId> --acceptance '明确标准'
|
|
202
|
+
return `你是此目标的协调者,负责把目标推进到可验收交付,不亲自实施子任务。\n目标:${task.spec}\n约束:${task.constraints || '未补充'}\n完成标准:${task.acceptance || '满足用户目标,说明交付与限制'}\n任务 ID:${task.id}\n当前协调身份:${task.coordinator?.sessionId};所有命令显式带 --session ${task.coordinator?.sessionId},不使用 tmux 标识。\n用 td collab task get ${task.id} --json 读取 roster、子任务、完整记录和 revision。\n先报告 ack;拆分为可交付子任务,用 td collab task create --group ${task.groupId} --parent ${task.id} --title '标题' --content '完整要求' --assignee <roster里的成员sessionId> --acceptance '明确标准' --work-type read-only|code。先按目标明确分类:查询、检查、研究、分析用 read-only,复用现有执行成员会话,不创建 Git 目录;修改代码用 code,才创建独立目录。简单问题只建一个必要任务,不为了形式拆成采集、复述、汇总多份任务。子任务继承自动评审与协调者。依赖任务用 --depends-on id,id,服务在依赖评审通过后自动分派。保持分工合理,不根据终端输出推断谁有空。\n每次收到明确更新后读取最新记录,处理失败与交接。服务自动安排独立评审、返工和依赖接续;不要重复请求评审或催促正在等待回复的成员。必要的人类决策用 task ask --attempt <本轮id>;方案需要用户确认时提交 task plan,不把普通回复当授权。\n只有已交付代码任务实际包含相对 base 的提交变化时,才需创建 --work-type code --integration 的最终集成子任务,--depends-on 依赖全部代码实施任务;独立目录会包含依赖的提交。不得在原仓库自动合并或发布。\n结束本次协调用 td collab task coordinate ${task.id} --revision <最新版本> --content '已安排的工作和下一步';有新事件服务会再次唤起你。所有子任务评审通过后报告 complete --summary '直接回答用户的简短结果和限制';只读目标直接汇总已核对结果,不创建集成或重复汇总任务。正文保留证据、评审结论和限制,仅代码目标补充目录、分支和提交,等待用户最终验收。任何异常都要留下明确说明,不伪造完成或进度。`;
|
|
186
203
|
}
|
|
187
204
|
notifyCoordinator(doc, task, update) {
|
|
188
205
|
if (!task.coordinator)
|
|
@@ -214,6 +231,10 @@ export class CollaborationTaskStore {
|
|
|
214
231
|
if (input.managed !== undefined && typeof input.managed !== 'boolean' || input.isolated !== undefined && typeof input.isolated !== 'boolean'
|
|
215
232
|
|| input.integration !== undefined && typeof input.integration !== 'boolean')
|
|
216
233
|
throw new CollaborationError('INVALID_WORKFLOW', '协作模式无效');
|
|
234
|
+
if (input.workType !== undefined && !['code', 'read-only'].includes(input.workType))
|
|
235
|
+
throw new CollaborationError('INVALID_WORK_TYPE', '任务类型必须是 code 或 read-only');
|
|
236
|
+
if (input.integration && input.workType === 'read-only')
|
|
237
|
+
throw new CollaborationError('INVALID_WORK_TYPE', '只读任务不需要代码集成');
|
|
217
238
|
if (input.reviewers && (!Array.isArray(input.reviewers) || input.reviewers.length > 32))
|
|
218
239
|
throw new CollaborationError('INVALID_REVIEWERS', '评审成员列表无效');
|
|
219
240
|
input.reviewers?.forEach(member);
|
|
@@ -230,14 +251,17 @@ export class CollaborationTaskStore {
|
|
|
230
251
|
}
|
|
231
252
|
const now = Date.now();
|
|
232
253
|
const parent = doc.tasks.find(t => t.id === input.parentTaskId);
|
|
254
|
+
const root = parent?.workflow?.rootTaskId ? doc.tasks.find(t => t.id === parent.workflow.rootTaskId) : parent;
|
|
255
|
+
if (parent?.workflow && !input.workType)
|
|
256
|
+
throw new CollaborationError('WORK_TYPE_REQUIRED', '请先明确子任务类型:查询、分析用 --work-type read-only;修改代码用 --work-type code');
|
|
233
257
|
if (parent?.workflow && actor && !sameMember(actor, parent.coordinator))
|
|
234
258
|
throw new CollaborationError('TASK_PERMISSION_DENIED', '只有协调者可以创建此目标的子任务', 403);
|
|
235
259
|
if (parent?.status !== undefined && parent.status !== 'open')
|
|
236
260
|
throw new CollaborationError('TASK_ARCHIVED', '目标已归档', 409);
|
|
237
261
|
if (parent?.workflow && doc.tasks.filter(t => t.workflow?.rootTaskId === (parent.workflow?.rootTaskId ?? parent.id)).length >= 32)
|
|
238
262
|
throw new CollaborationError('GOAL_TASK_LIMIT', '单个目标最多 32 个子任务,请收敛分工或建立后续目标', 409);
|
|
239
|
-
const workflow = parent?.workflow ? { ...parent.workflow, kind: 'step', rootTaskId: parent.workflow.rootTaskId ?? parent.id, paused: false, integration: input.integration === true }
|
|
240
|
-
: input.managed ? { kind: 'goal', reviewers: input.reviewers ?? [], isolated: input.isolated !== false, paused: false, maxRevisions: 3, maxParallel: Math.min(4, input.reviewers?.length ?? 1) } : undefined;
|
|
263
|
+
const workflow = parent?.workflow ? { ...parent.workflow, kind: 'step', rootTaskId: parent.workflow.rootTaskId ?? parent.id, paused: false, integration: input.integration === true, workType: input.workType ?? 'code', isolated: input.workType === 'read-only' ? false : root?.workflow?.isolated ?? parent.workflow.isolated }
|
|
264
|
+
: input.managed ? { kind: 'goal', workType: 'auto', reviewers: input.reviewers ?? [], isolated: input.isolated !== false, paused: false, maxRevisions: 3, maxParallel: Math.min(4, input.reviewers?.length ?? 1) } : undefined;
|
|
241
265
|
if (workflow?.kind === 'goal' && (!input.coordinator || workflow.reviewers.length < 1))
|
|
242
266
|
throw new CollaborationError('WORKFLOW_MEMBERS_REQUIRED', '请选择协调者和至少一位执行成员', 400);
|
|
243
267
|
if (workflow?.kind === 'goal' && !workflow.reviewers.some(m => !sameMember(m, input.coordinator ?? null)))
|
|
@@ -251,7 +275,7 @@ export class CollaborationTaskStore {
|
|
|
251
275
|
if (workflow?.rootTaskId && doc.tasks.find(t => t.id === workflow.rootTaskId)?.workflow?.paused)
|
|
252
276
|
throw new CollaborationError('WORKFLOW_PAUSED', '目标已暂停,请先继续协调');
|
|
253
277
|
if (workflow?.integration) {
|
|
254
|
-
const required = doc.tasks
|
|
278
|
+
const required = codeSteps(doc.tasks, workflow.rootTaskId);
|
|
255
279
|
if (!required.length || required.some(t => !dependsOn.includes(t.id)))
|
|
256
280
|
throw new CollaborationError('INTEGRATION_DEPENDENCIES_REQUIRED', '集成任务必须依赖目标下全部实施子任务');
|
|
257
281
|
}
|
|
@@ -284,6 +308,9 @@ export class CollaborationTaskStore {
|
|
|
284
308
|
}
|
|
285
309
|
apply(taskId, input, actor) {
|
|
286
310
|
const content = text(input.content, '内容');
|
|
311
|
+
const summary = text(input.summary, '结果摘要', 2400);
|
|
312
|
+
if (summary && (input.kind !== 'report' || input.status !== 'complete'))
|
|
313
|
+
throw new CollaborationError('INVALID_SUMMARY', '摘要只能随完成结果提交');
|
|
287
314
|
if (input.verdict !== undefined && !['pass', 'changes', 'blocked'].includes(input.verdict))
|
|
288
315
|
throw new CollaborationError('INVALID_VERDICT', '评审结论无效');
|
|
289
316
|
if (input.evidence !== undefined && Buffer.byteLength(JSON.stringify(input.evidence)) > 16_384)
|
|
@@ -302,12 +329,30 @@ export class CollaborationTaskStore {
|
|
|
302
329
|
throw new CollaborationError('USER_DECISION_REQUIRED', '回答、方案确认与验收需要用户操作', 403);
|
|
303
330
|
if (!['report', 'ask', 'submit-plan', 'review', 'comment', 'answer'].includes(input.kind) && input.expectedRevision !== task.revision)
|
|
304
331
|
throw new CollaborationError('TASK_CHANGED', '任务已更新,请查看最新记录后重试', 409);
|
|
305
|
-
|
|
332
|
+
const userFollowup = actor === null && input.kind === 'revise' && !!content;
|
|
333
|
+
if (task.status !== 'open' && !['reopen', 'report'].includes(input.kind) && !(task.status === 'accepted' && userFollowup))
|
|
306
334
|
throw new CollaborationError('TASK_ARCHIVED', '任务已归档;需要继续时请重新打开', 409);
|
|
307
|
-
if (task.workflow && task.status !== 'open' && input.kind !== 'reopen')
|
|
335
|
+
if (task.workflow && task.status !== 'open' && input.kind !== 'reopen' && !(task.status === 'accepted' && userFollowup))
|
|
308
336
|
throw new CollaborationError('TASK_ARCHIVED', '自动协作的已完成版本不能追加执行报告,请先重新打开', 409);
|
|
309
|
-
|
|
337
|
+
const root = task.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
|
|
338
|
+
if (root && root.status !== 'open' && !(root.status === 'accepted' && userFollowup))
|
|
310
339
|
throw new CollaborationError('GOAL_ARCHIVED', '请先重新打开所属目标,再继续子任务', 409);
|
|
340
|
+
if (userFollowup && task.status === 'accepted') {
|
|
341
|
+
task.status = 'open';
|
|
342
|
+
delete task.acceptedArtifactId;
|
|
343
|
+
delete task.completionMode;
|
|
344
|
+
this.event(task, 'reopen', null, '用户要求继续跟进,保留上一版交付');
|
|
345
|
+
}
|
|
346
|
+
if (userFollowup && root?.status === 'accepted') {
|
|
347
|
+
root.status = 'open';
|
|
348
|
+
delete root.acceptedArtifactId;
|
|
349
|
+
delete root.completionMode;
|
|
350
|
+
if (root.workflow)
|
|
351
|
+
root.workflow.paused = false;
|
|
352
|
+
this.event(root, 'reopen', null, '用户要求继续子任务,所属目标同步重新开启');
|
|
353
|
+
root.revision++;
|
|
354
|
+
root.updatedAt = Date.now();
|
|
355
|
+
}
|
|
311
356
|
const attempt = task.attempts.find(a => a.id === input.attemptId);
|
|
312
357
|
if (['report', 'ask', 'submit-plan'].includes(input.kind) && (!attempt || !sameMember(actor, attempt.assignee)))
|
|
313
358
|
throw new CollaborationError('ATTEMPT_PERMISSION_DENIED', '只能对自己负责的执行尝试提交报告', 403);
|
|
@@ -357,8 +402,8 @@ export class CollaborationTaskStore {
|
|
|
357
402
|
|| doc.tasks.some(t => t.workflow?.rootTaskId === task.id && !['accepted', 'closed'].includes(t.status))))
|
|
358
403
|
throw new CollaborationError('CHILD_TASKS_PENDING', '请先完成全部子任务的独立评审,再汇总交付', 409);
|
|
359
404
|
if (input.status === 'complete' && task.workflow?.kind === 'goal' && task.workflow.isolated) {
|
|
360
|
-
const steps = doc.tasks
|
|
361
|
-
if (!doc.tasks.some(t => t.workflow?.rootTaskId === task.id && t.workflow.integration && t.status === 'accepted' && steps.every(s => t.dependsOn.includes(s.id))))
|
|
405
|
+
const steps = codeSteps(doc.tasks, task.id);
|
|
406
|
+
if (steps.length && !doc.tasks.some(t => t.workflow?.rootTaskId === task.id && t.workflow.integration && t.status === 'accepted' && steps.every(s => t.dependsOn.includes(s.id))))
|
|
362
407
|
throw new CollaborationError('INTEGRATION_PENDING', '代码目标需要依赖全部实施任务的集成交付通过评审', 409);
|
|
363
408
|
}
|
|
364
409
|
if (input.status === 'complete') {
|
|
@@ -371,7 +416,7 @@ export class CollaborationTaskStore {
|
|
|
371
416
|
event.reportStatus = input.status;
|
|
372
417
|
event.evidence = input.evidence;
|
|
373
418
|
if (input.status === 'complete')
|
|
374
|
-
task.artifacts.push({ id: id(), attemptId: attempt.id, kind: 'result', content, evidence: input.evidence, actor: actor, createdAt: event.createdAt });
|
|
419
|
+
task.artifacts.push({ id: id(), attemptId: attempt.id, kind: 'result', content, ...(summary ? { summary } : {}), evidence: input.evidence, actor: actor, createdAt: event.createdAt });
|
|
375
420
|
}
|
|
376
421
|
else if (input.kind === 'ask') {
|
|
377
422
|
if (!content || input.options !== undefined && (!Array.isArray(input.options) || input.options.length > 8 || input.options.some(v => typeof v !== 'string' || !v.trim() || v.length > 500)))
|
|
@@ -437,8 +482,8 @@ export class CollaborationTaskStore {
|
|
|
437
482
|
if (task.workflow?.kind === 'goal' && doc.tasks.some(t => t.workflow?.rootTaskId === task.id && !['accepted', 'closed'].includes(t.status)))
|
|
438
483
|
throw new CollaborationError('CHILD_TASKS_PENDING', '子任务尚未全部完成', 409);
|
|
439
484
|
if (task.workflow?.kind === 'goal' && task.workflow.isolated) {
|
|
440
|
-
const steps = doc.tasks
|
|
441
|
-
if (!doc.tasks.some(t => t.workflow?.rootTaskId === task.id && t.workflow.integration && t.status === 'accepted' && steps.every(s => t.dependsOn.includes(s.id))))
|
|
485
|
+
const steps = codeSteps(doc.tasks, task.id);
|
|
486
|
+
if (steps.length && !doc.tasks.some(t => t.workflow?.rootTaskId === task.id && t.workflow.integration && t.status === 'accepted' && steps.every(s => t.dependsOn.includes(s.id))))
|
|
442
487
|
throw new CollaborationError('INTEGRATION_PENDING', '需要全部实施任务的集成交付通过评审');
|
|
443
488
|
}
|
|
444
489
|
if (task.dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
|
|
@@ -463,11 +508,25 @@ export class CollaborationTaskStore {
|
|
|
463
508
|
else if (input.kind === 'revise' || input.kind === 'comment') {
|
|
464
509
|
if (!content)
|
|
465
510
|
throw new CollaborationError('INVALID_COMMENT', '反馈不能为空');
|
|
466
|
-
this.event(task, input.kind, actor, content);
|
|
511
|
+
const event = this.event(task, input.kind, actor, content);
|
|
512
|
+
if (actor === null)
|
|
513
|
+
event.source = 'user';
|
|
467
514
|
const recipient = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
468
|
-
if (recipient && !sameMember(actor, recipient.assignee))
|
|
469
|
-
|
|
470
|
-
|
|
515
|
+
if (recipient && !sameMember(actor, recipient.assignee)) {
|
|
516
|
+
event.target = recipient.assignee;
|
|
517
|
+
event.deliveryId = id();
|
|
518
|
+
doc.outbox.push({ id: event.deliveryId, taskId: task.id, attemptId: recipient.id, target: recipient.assignee,
|
|
519
|
+
kind: 'message', threadId: recipient.threadId, content: `任务「${task.title}」${input.kind === 'revise' ? actor === null ? '用户要求继续跟进' : '修改要求' : '补充反馈'}:\n${content}\n任务 ID:${task.id}\n尝试 ID:${recipient.id}${task.workflow?.kind === 'goal' ? `\n本轮继续沿用此目标和已交付证据,只推进新增要求。\n${this.goalInstructions(task)}` : '\n按这次要求继续当前分派,重新提交 complete 和摘要;新版本会重新独立评审。'}` });
|
|
520
|
+
if (task.workflow?.kind === 'goal' && task.coordination) {
|
|
521
|
+
task.coordination.notifiedSequence = event.sequence;
|
|
522
|
+
task.coordination.notifiedAt = Date.now();
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (input.kind === 'revise') {
|
|
526
|
+
delete task.automationIssue;
|
|
527
|
+
if (userFollowup && task.workflow)
|
|
528
|
+
task.workflow.paused = false;
|
|
529
|
+
}
|
|
471
530
|
}
|
|
472
531
|
else if (input.kind === 'close' || input.kind === 'reopen') {
|
|
473
532
|
task.status = input.kind === 'close' ? 'closed' : 'open';
|
|
@@ -535,7 +594,7 @@ export class CollaborationTaskStore {
|
|
|
535
594
|
this.event(root, 'child-update', null, `子任务「${task.title}」(${task.id}):${content}`);
|
|
536
595
|
const result = root.artifacts.filter(a => a.kind === 'result' && a.attemptId === root.activeAttemptId).at(-1);
|
|
537
596
|
if (result && !root.events.some(e => e.kind === 'revise' && e.attemptId === root.activeAttemptId && e.createdAt >= result.createdAt)) {
|
|
538
|
-
this.event(root, 'revise', null, '子任务在汇总之后发生变化,请重新核对并提交最终结果');
|
|
597
|
+
this.event(root, 'revise', null, '子任务在汇总之后发生变化,请重新核对并提交最终结果').source = 'system';
|
|
539
598
|
}
|
|
540
599
|
root.revision++;
|
|
541
600
|
root.updatedAt = Date.now();
|
|
@@ -604,7 +663,7 @@ export class CollaborationTaskStore {
|
|
|
604
663
|
&& !['coordinate', 'request-review', 'assigned'].includes(e.kind) && !sameMember(e.actor, task.coordinator)))
|
|
605
664
|
return 'wake';
|
|
606
665
|
if (!task.automationIssue && task.coordination.acknowledgedSequence < task.coordination.notifiedSequence
|
|
607
|
-
&& !result && Date.now() - task.coordination.notifiedAt > 15 * 60_000)
|
|
666
|
+
&& (!result || task.events.some(e => e.kind === 'revise' && e.attemptId === task.activeAttemptId && e.createdAt >= result.createdAt)) && Date.now() - task.coordination.notifiedAt > 15 * 60_000)
|
|
608
667
|
return 'coordination-timeout';
|
|
609
668
|
}
|
|
610
669
|
return null;
|
|
@@ -632,7 +691,7 @@ export class CollaborationTaskStore {
|
|
|
632
691
|
event.target = reviewer;
|
|
633
692
|
event.source = 'system';
|
|
634
693
|
doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: reviewer, kind: 'message', threadId: attempt.threadId,
|
|
635
|
-
content:
|
|
694
|
+
content: `本次评审身份:${reviewer.sessionId};所有命令带 --session ${reviewer.sessionId},不使用作者或模板身份。\n请独立评审「${task.title}」版本 ${result.id}。用 td collab task get ${task.id} --json 查看目标、结果、摘要和证据;摘要必须与正文一致,遗漏关键限制或夸大结果需要求修改。${task.workspace ? `代码目录 ${task.workspace.cwd},分支 ${task.workspace.branch};只读检查此目录,不修改原仓库。` : ''}\n提交 td collab task review ${task.id} --artifact ${result.id} --verdict pass|changes|blocked --content '结论、具体问题与证据'。pass 表示符合完成标准;changes 给出可执行修改要求;blocked 留下必须处理的原因。只按用户授权运行测试,未执行不能称通过。` });
|
|
636
695
|
}
|
|
637
696
|
this.touchParent(doc, task, '已自动请求独立评审');
|
|
638
697
|
}
|
|
@@ -644,7 +703,8 @@ export class CollaborationTaskStore {
|
|
|
644
703
|
this.touchParent(doc, task, '独立评审通过,后续依赖可以接续');
|
|
645
704
|
}
|
|
646
705
|
else if (action === 'review-feedback') {
|
|
647
|
-
const
|
|
706
|
+
const boundary = task.events.filter(e => e.kind === 'revise' && e.source === 'user' && e.attemptId === task.activeAttemptId).at(-1)?.sequence ?? 0;
|
|
707
|
+
const rounds = task.events.filter(e => e.kind === 'revise' && e.actor === null && e.artifactId && e.attemptId === task.activeAttemptId && e.sequence > boundary).length;
|
|
648
708
|
const blocked = review.verdict === 'blocked' || rounds >= task.workflow.maxRevisions;
|
|
649
709
|
const event = this.event(task, blocked ? 'review-blocked' : 'revise', null, review.content);
|
|
650
710
|
event.artifactId = result.id;
|
|
@@ -659,7 +719,7 @@ export class CollaborationTaskStore {
|
|
|
659
719
|
else if (action === 'wake') {
|
|
660
720
|
const updates = task.events.filter(e => e.sequence > task.coordination.notifiedSequence && e.kind !== 'coordinate');
|
|
661
721
|
doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: task.coordinator, kind: 'message', threadId: task.id,
|
|
662
|
-
content:
|
|
722
|
+
content: `当前协调身份:${task.coordinator.sessionId};命令带 --session ${task.coordinator.sessionId}。\n目标「${task.title}」有新的明确事件,请继续协调:\n${updates.slice(-8).map(e => `${new Date(e.createdAt).toISOString()} ${e.content.slice(0, 1200)}`).join('\n')}\n用 td collab task get ${task.id} --json 读取完整上下文与子任务;安排必要下一步后 task coordinate ${task.id} --revision <最新版本> --content '协调说明'。自动评审和依赖已由服务接续,不要重复分派。\n当前流程指南:查询、检查、分析必须 --work-type read-only,复用 roster 现有执行成员;只有修改代码才用 code 和独立目录。只读报告由你直接汇总,不新增仅转述的汇总子任务。已评审且 HEAD 等于 base 的交付没有代码变化,不需要集成;有代码变化才安排必要集成。用户有新要求时继续本目标,交付新版本,不重做已有采样。最终汇总通过评审且无新要求时等待用户验收,不重复提交相同结果。` });
|
|
663
723
|
task.coordination.notifiedSequence = task.events.at(-1).sequence;
|
|
664
724
|
task.coordination.notifiedAt = Date.now();
|
|
665
725
|
}
|
|
@@ -736,7 +796,9 @@ export class CollaborationTaskStore {
|
|
|
736
796
|
task.automationIssue = `消息投递${receipt.status === 'expired' ? '过期' : '失败'}:${receipt.error ?? '请检查成员终端后重试'}`;
|
|
737
797
|
this.touchParent(doc, task, task.automationIssue);
|
|
738
798
|
}
|
|
739
|
-
else if (receipt.status === 'delivered' && task.automationIssue?.startsWith('
|
|
799
|
+
else if (receipt.status === 'delivered' && task.automationIssue?.startsWith('消息投递')
|
|
800
|
+
&& !doc.outbox.some(o => o.taskId === task.id && o.lastError)
|
|
801
|
+
&& !task.deliveries.some(d => d.attemptId === task.activeAttemptId && ['failed', 'expired'].includes(d.status)))
|
|
740
802
|
delete task.automationIssue;
|
|
741
803
|
});
|
|
742
804
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
2
|
import { promisify } from 'node:util';
|
|
3
|
-
import { mkdir, readFile, writeFile, rename, realpath } from 'node:fs/promises';
|
|
3
|
+
import { mkdir, readFile, writeFile, rename, realpath, stat } from 'node:fs/promises';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
const exec = promisify(execFile);
|
|
@@ -98,3 +98,68 @@ export async function captureTaskCommit(workspace) {
|
|
|
98
98
|
const commit = await git(workspace.cwd, ['rev-parse', 'HEAD']);
|
|
99
99
|
return { commit, branch: workspace.branch, cwd: workspace.cwd, base: workspace.base };
|
|
100
100
|
}
|
|
101
|
+
async function assertManagedWorkspace(workspace) {
|
|
102
|
+
const relative = path.relative(home, workspace.cwd);
|
|
103
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)
|
|
104
|
+
|| !/^termdock\/task\/[a-f0-9]{32}-[a-f0-9]{8}$/.test(workspace.branch))
|
|
105
|
+
throw new Error('目录不是 Termdock 独立执行工作区');
|
|
106
|
+
const actual = await realpath(workspace.cwd);
|
|
107
|
+
if (actual !== path.resolve(workspace.cwd) || !actual.startsWith(`${await realpath(home)}${path.sep}`)
|
|
108
|
+
|| await git(actual, ['rev-parse', '--show-toplevel']) !== actual
|
|
109
|
+
|| await git(actual, ['branch', '--show-current']) !== workspace.branch)
|
|
110
|
+
throw new Error('执行目录或分支发生变化,现场已保留');
|
|
111
|
+
const common = async (cwd) => realpath(path.resolve(cwd, await git(cwd, ['rev-parse', '--git-common-dir'])));
|
|
112
|
+
if (await common(actual) !== await common(workspace.repository))
|
|
113
|
+
throw new Error('执行目录不属于原仓库,现场已保留');
|
|
114
|
+
}
|
|
115
|
+
/** Remove only a verified, clean worktree. Keep its branch and all Git commits. */
|
|
116
|
+
export async function archiveTaskWorkspace(workspace) {
|
|
117
|
+
try {
|
|
118
|
+
await assertManagedWorkspace(workspace);
|
|
119
|
+
const commit = await git(workspace.cwd, ['rev-parse', 'HEAD']);
|
|
120
|
+
if (await git(workspace.cwd, ['status', '--porcelain']) || await git(workspace.cwd, ['ls-files', '--others', '--ignored', '--exclude-standard'])) {
|
|
121
|
+
return { state: 'retained', reason: '目录含未提交或忽略的文件,已保留现场', commit };
|
|
122
|
+
}
|
|
123
|
+
await git(workspace.repository, ['worktree', 'remove', workspace.cwd]);
|
|
124
|
+
return { state: 'removed', commit };
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
return { state: 'retained', reason: error instanceof Error ? error.message : String(error) };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
export async function snapshotTaskWorkspace(workspace) {
|
|
131
|
+
await assertManagedWorkspace(workspace);
|
|
132
|
+
return git(workspace.cwd, ['rev-parse', 'HEAD']);
|
|
133
|
+
}
|
|
134
|
+
export async function restoreTaskWorkspace(workspace, cleanup) {
|
|
135
|
+
if (cleanup.state !== 'removed') {
|
|
136
|
+
try {
|
|
137
|
+
await assertManagedWorkspace(workspace);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (cleanup.state !== 'pending' || error.code !== 'ENOENT')
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
const relative = path.relative(home, workspace.cwd);
|
|
146
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)
|
|
147
|
+
|| !/^termdock\/task\/[a-f0-9]{32}-[a-f0-9]{8}$/.test(workspace.branch) || !cleanup.commit)
|
|
148
|
+
throw new Error('工作区恢复记录无效');
|
|
149
|
+
try {
|
|
150
|
+
await stat(workspace.cwd);
|
|
151
|
+
await assertManagedWorkspace(workspace);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
if (error.code !== 'ENOENT')
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
if (await git(workspace.repository, ['rev-parse', workspace.branch]) !== cleanup.commit)
|
|
159
|
+
throw new Error('归档分支已有变化,请核对后再恢复;不会重置分支');
|
|
160
|
+
await mkdir(path.dirname(workspace.cwd), { recursive: true, mode: 0o700 });
|
|
161
|
+
if (!(await realpath(path.dirname(workspace.cwd))).startsWith(`${await realpath(home)}${path.sep}`))
|
|
162
|
+
throw new Error('工作区父目录发生变化');
|
|
163
|
+
await git(workspace.repository, ['worktree', 'add', workspace.cwd, workspace.branch]);
|
|
164
|
+
await assertManagedWorkspace(workspace);
|
|
165
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Provision only missing roles. Persist each member before proceeding so a
|
|
2
|
+
* failed launch, retry or server restart cannot create another full team. */
|
|
3
|
+
export async function ensureTeam(options) {
|
|
4
|
+
const ids = [];
|
|
5
|
+
const labels = [`自动协调者 (${options.agentSlug})`, `自动执行与评审 (${options.agentSlug})`];
|
|
6
|
+
for (const role of labels) {
|
|
7
|
+
const members = options.members();
|
|
8
|
+
const reserved = members.find(member => member.role?.startsWith(role.split(' (')[0]));
|
|
9
|
+
if (reserved && (reserved.role !== role || reserved.cwd !== options.cwd)) {
|
|
10
|
+
throw new Error('本组已有自动配置的成员,请使用原 Agent 和目录,或在成员设置中移出后重新配置');
|
|
11
|
+
}
|
|
12
|
+
const existing = reserved ?? members.find(member => !ids.includes(member.id) && member.agentSlug === options.agentSlug && member.cwd === options.cwd);
|
|
13
|
+
ids.push(existing?.id ?? await options.spawn(role));
|
|
14
|
+
}
|
|
15
|
+
return { coordinatorSessionId: ids[0], reviewerSessionIds: [ids[1]] };
|
|
16
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { normalizePersistedAgentResumeInfo } from './resumePersistence.js';
|
|
4
|
+
/** Explicit archives are durable records, independent of the recent-history cap. */
|
|
5
|
+
export class ExecutionArchiveStore {
|
|
6
|
+
file;
|
|
7
|
+
entries;
|
|
8
|
+
constructor(file) {
|
|
9
|
+
this.file = file;
|
|
10
|
+
try {
|
|
11
|
+
const document = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
12
|
+
if (document.version !== 1 || !Array.isArray(document.entries))
|
|
13
|
+
throw new Error('执行归档文件格式无效');
|
|
14
|
+
this.entries = document.entries.map((entry) => {
|
|
15
|
+
const agent = normalizePersistedAgentResumeInfo(entry.agent);
|
|
16
|
+
if (!agent || !entry.sessionId || !entry.workspace?.cwd || !Array.isArray(entry.groups) || !Array.isArray(entry.taskIds))
|
|
17
|
+
throw new Error('执行归档记录无效');
|
|
18
|
+
return { ...entry, agent };
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (error.code !== 'ENOENT')
|
|
23
|
+
throw error;
|
|
24
|
+
this.entries = [];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
list() { return structuredClone(this.entries); }
|
|
28
|
+
get(id) { return this.list().find(entry => entry.sessionId === id); }
|
|
29
|
+
save(entry) {
|
|
30
|
+
if (!normalizePersistedAgentResumeInfo(entry.agent))
|
|
31
|
+
throw new Error('没有可恢复的 Agent 原生会话 ID,终端已保留');
|
|
32
|
+
const entries = [structuredClone(entry), ...this.entries.filter(item => item.sessionId !== entry.sessionId)];
|
|
33
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true, mode: 0o700 });
|
|
34
|
+
const temporary = `${this.file}.${process.pid}.tmp`;
|
|
35
|
+
fs.writeFileSync(temporary, JSON.stringify({ version: 1, entries }), { mode: 0o600 });
|
|
36
|
+
fs.renameSync(temporary, this.file);
|
|
37
|
+
this.entries = entries;
|
|
38
|
+
return structuredClone(entry);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function completedExecution(tasks, serviceId, sessionId) {
|
|
42
|
+
const matches = (member) => member?.serviceId === serviceId && member.sessionId === sessionId;
|
|
43
|
+
if (tasks.some(task => matches(task.coordinator) || task.workflow?.reviewers.some(matches)
|
|
44
|
+
|| task.status === 'open' && (matches(task.attempts.find(a => a.id === task.activeAttemptId)?.assignee) || matches(task.scheduledAssignee)))) {
|
|
45
|
+
throw new Error('此会话仍承担协作工作,不能归档;请完成当前任务后再操作');
|
|
46
|
+
}
|
|
47
|
+
const completed = tasks.filter(task => task.status !== 'open' && task.workspace && task.workflow?.kind === 'step' && task.workflow.isolated
|
|
48
|
+
&& matches(task.attempts.find(a => a.id === task.activeAttemptId)?.assignee));
|
|
49
|
+
if (!completed.length)
|
|
50
|
+
throw new Error('此会话没有已完成的独立执行任务,终端已保留');
|
|
51
|
+
return completed;
|
|
52
|
+
}
|
package/dist/server/entry.js
CHANGED
|
@@ -32,6 +32,7 @@ import { fileURLToPath } from 'url';
|
|
|
32
32
|
import { WebSocketServer } from 'ws';
|
|
33
33
|
import terminalRoutes, { handleTerminalWebSocket, handleControlWebSocket, resolveStableFederationSessionId } from './routes/terminal.js';
|
|
34
34
|
import androidRoutes, { handleAndroidWebSocket } from './routes/android.js';
|
|
35
|
+
import computerRoutes, { handleComputerWebSocket, stopComputerBridges } from './routes/computer.js';
|
|
35
36
|
import { exitWithStartupFailure } from './utils/startupFailure.js';
|
|
36
37
|
import { androidRecordings } from './android/recording.js';
|
|
37
38
|
import { stopAllScrcpySessions } from './android/scrcpy.js';
|
|
@@ -349,6 +350,9 @@ export function createApp(options = {}) {
|
|
|
349
350
|
app.use('/api/android', requireAuth({ bypass: isTrustedLocalCliRequest }));
|
|
350
351
|
app.use('/api/android', csrfProtection.verifyMiddleware({ bypass: isTrustedLocalCliRequest }));
|
|
351
352
|
app.use('/api/android', androidRoutes);
|
|
353
|
+
app.use('/api/computer', requireAuth({ bypass: isTrustedLocalCliRequest }));
|
|
354
|
+
app.use('/api/computer', csrfProtection.verifyMiddleware({ bypass: isTrustedLocalCliRequest }));
|
|
355
|
+
app.use('/api/computer', computerRoutes);
|
|
352
356
|
if (fs.existsSync(bundledClientIndexPath)) {
|
|
353
357
|
const clientPath = pinBundledRuntimeClientDist(bundledClientDistPath);
|
|
354
358
|
const compression = createStaticCompressionMiddleware(clientPath);
|
|
@@ -500,8 +504,9 @@ export function startServer(options = {}) {
|
|
|
500
504
|
target.close(); });
|
|
501
505
|
server.once('close', () => relayRouter.close());
|
|
502
506
|
server.once('close', () => { void stopAllScrcpySessions(); void androidRecordings.stopAll(); });
|
|
507
|
+
server.once('close', stopComputerBridges);
|
|
503
508
|
const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
|
|
504
|
-
terminal: handleTerminalWebSocket, control: handleControlWebSocket, android: handleAndroidWebSocket,
|
|
509
|
+
terminal: handleTerminalWebSocket, control: handleControlWebSocket, android: handleAndroidWebSocket, computer: handleComputerWebSocket,
|
|
505
510
|
}, {
|
|
506
511
|
resolveSessionId: resolveStableFederationSessionId,
|
|
507
512
|
collaborationConnected: (subjectId, rpc) => {
|
|
@@ -252,8 +252,8 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
252
252
|
}
|
|
253
253
|
}
|
|
254
254
|
revokeTimer = setInterval(() => {
|
|
255
|
-
for (const { socket: logical, sessionId, android } of sockets.values()) {
|
|
256
|
-
if (android) {
|
|
255
|
+
for (const { socket: logical, sessionId, android, computer } of sockets.values()) {
|
|
256
|
+
if (android || computer) {
|
|
257
257
|
if (!full(subjectId))
|
|
258
258
|
logical.close(4003, 'Authorization revoked');
|
|
259
259
|
continue;
|
|
@@ -569,18 +569,20 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
569
569
|
const url = new URL(packet.path, 'http://inner');
|
|
570
570
|
const match = /^\/api\/terminal\/([^/%]+)\/ws$/.exec(url.pathname);
|
|
571
571
|
const androidMatch = /^\/api\/android\/([^/%]+)\/ws$/.exec(url.pathname);
|
|
572
|
+
const computer = url.pathname === '/api/computer/ws';
|
|
572
573
|
if (match)
|
|
573
574
|
check('session.view', match[1]);
|
|
574
575
|
else if (url.pathname === '/api/control/ws')
|
|
575
576
|
check('service.view');
|
|
576
|
-
else if (androidMatch) {
|
|
577
|
+
else if (androidMatch || computer) {
|
|
577
578
|
if (!full(subjectId))
|
|
578
579
|
throw new Error('API_NOT_ALLOWED');
|
|
579
580
|
}
|
|
580
581
|
else
|
|
581
582
|
throw new Error('API_NOT_ALLOWED');
|
|
582
|
-
const logical = new LogicalSocket(channel, packet.id, () => androidMatch ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]));
|
|
583
|
-
sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: Boolean(androidMatch)
|
|
583
|
+
const logical = new LogicalSocket(channel, packet.id, () => (androidMatch || computer) ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]));
|
|
584
|
+
sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: Boolean(androidMatch), computer,
|
|
585
|
+
computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
|
|
584
586
|
logical.once('close', () => sockets.delete(packet.id));
|
|
585
587
|
send({ type: 'ws-ready', id: packet.id });
|
|
586
588
|
if (match)
|
|
@@ -598,6 +600,15 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
598
600
|
maxFps: Number(url.searchParams.get('max_fps')) || undefined,
|
|
599
601
|
});
|
|
600
602
|
}
|
|
603
|
+
else if (computer) {
|
|
604
|
+
if (!handlers.computer) {
|
|
605
|
+
logical.close(4403, 'API_NOT_ALLOWED');
|
|
606
|
+
continue;
|
|
607
|
+
}
|
|
608
|
+
handlers.computer(logical, url.searchParams.get('host') ?? '', {
|
|
609
|
+
protocol: url.searchParams.get('protocol') ?? undefined, port: url.searchParams.get('port') ?? undefined,
|
|
610
|
+
});
|
|
611
|
+
}
|
|
601
612
|
else
|
|
602
613
|
handlers.control(logical, randomUUID());
|
|
603
614
|
}
|
|
@@ -606,7 +617,14 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
606
617
|
if (!item || typeof packet.data !== 'string')
|
|
607
618
|
throw new Error('INVALID_STREAM');
|
|
608
619
|
const data = JSON.parse(packet.data);
|
|
609
|
-
if (item.
|
|
620
|
+
if (item.computer) {
|
|
621
|
+
if (!full(subjectId))
|
|
622
|
+
throw new Error('AUTHORIZATION_DENIED');
|
|
623
|
+
const types = item.computerProtocol === 'rdp' ? ['start', 'instruction', 'ack'] : ['data', 'ack'];
|
|
624
|
+
if (!types.includes(data?.type))
|
|
625
|
+
throw new Error('ACTION_NOT_ALLOWED');
|
|
626
|
+
}
|
|
627
|
+
else if (item.android) {
|
|
610
628
|
if (!full(subjectId))
|
|
611
629
|
throw new Error('AUTHORIZATION_DENIED');
|
|
612
630
|
if (!['control', 'ack', 'ping', 'bitrate'].includes(data?.type))
|