termdock 1.4.295 → 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-Dv5XRtRh.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-mq69dKyr.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-BqdHYGi5.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-BvzGxpuc.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-DrfddB7x.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-3bJV_A5s.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-CX7VtoJB.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-PJwhLTPH.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-l3FXjCEl.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-DdNbKi-4.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-Cu63oaxR.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-DQiV_yk6.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-COKJ79Ld.js → chunk-2Q5K7J3B-CXJ1kP4X.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-BfV-m7oa.js → chunk-5VM5RSS4-Dpdum_2z.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-sLDCEnU8.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-DYu5qS7g.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-DixkILcl.js → chunk-JWPE2WC7-BWiXpQT9.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-CfW5Vm_-.js → chunk-POPQ4Y6H-vQSslMNv.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-nJnbUFzs.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-BwyiSQgx.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-TCk4c2XN.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-rroo-EOb.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-CkFeV85k.js → cynefin-OW5HDTMX-C7HA99Zc.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-C7HA99Zc.js.br +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-CkFeV85k.js.gz → cynefin-OW5HDTMX-C7HA99Zc.js.gz} +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-hBwpU_bW.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-B321-hdp.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-bY87_ptA.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--cez0H_o.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-BupH_VRK.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-DewqaxvF.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-wDumcTuR.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-C0OY91We.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-Cw9aNqvJ.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-BC7XUXeI.js → flowDiagram-HODETNUW-Cp4Nu5jY.js} +1 -1
- package/dist/client/assets/{flowDiagram-HODETNUW-BC7XUXeI.js.br → flowDiagram-HODETNUW-Cp4Nu5jY.js.br} +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Cp4Nu5jY.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DovhJIz5.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-wjVok2Lg.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-BmrbbyW1.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-CQEu4Jwy.js → infoDiagram-27XIBGKW-BN4RpQTL.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BPqibqsm.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-DIkhubnf.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-CBT0DIUR.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-C1CMlOIU.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-C4gyqor0.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-CMfa4ncd.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-C-JmgKOI.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-BmUG2IwA.js → passwordBootstrap-E0piP814.js} +1 -1
- 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-IY-AEKQa.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-9Qq43qED.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-BjQncx5i.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-Cr1rL0wZ.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-ConC5-ym.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-DU2q_iZg.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-BYlx9kXC.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-D_uAKme-.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-H_uqscf4.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-D2z4Ue6G.js → sparkles-CgaUBIwH.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-BvsQjIIW.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-LmUJag-p.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-DZ5n0Pim.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-Ct6iIBmv.js → triangle-alert-C9IHD_eF.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-BkTA_Ciz.js → vennDiagram-4TSXK5OY-C9f5jYgk.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-BkTA_Ciz.js.br → vennDiagram-4TSXK5OY-C9f5jYgk.js.br} +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-C9f5jYgk.js.gz +0 -0
- package/dist/client/assets/{videoCompression-VZIoFnyJ.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-7hzU176j.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-Bx4P96Bb.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 +45 -4
- package/dist/server/agent/collaborationDeliveryWorker.js +1 -1
- package/dist/server/agent/collaborationGroupRoutes.js +0 -2
- package/dist/server/agent/collaborationPeerTransport.js +8 -2
- package/dist/server/agent/collaborationResultPresentation.js +14 -0
- package/dist/server/agent/collaborationRoutes.js +18 -2
- package/dist/server/agent/collaborationService.js +46 -2
- package/dist/server/agent/collaborationStore.js +12 -21
- package/dist/server/agent/collaborationTaskBundles.js +121 -0
- package/dist/server/agent/collaborationTaskCli.js +54 -0
- package/dist/server/agent/collaborationTaskRoutes.js +58 -0
- package/dist/server/agent/collaborationTaskService.js +355 -0
- package/dist/server/agent/collaborationTaskStore.js +835 -0
- package/dist/server/agent/collaborationTaskTypes.js +1 -0
- package/dist/server/agent/collaborationTaskWorkspace.js +165 -0
- package/dist/server/agent/collaborationTeam.js +16 -0
- package/dist/server/agent/executionArchives.js +52 -0
- package/dist/server/entry.js +17 -2
- package/dist/server/federation/runtime.js +30 -7
- 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 +441 -362
- 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-D-cerXsD.js +0 -158
- package/dist/client/assets/App-D-cerXsD.js.br +0 -0
- package/dist/client/assets/App-D-cerXsD.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-Dv5XRtRh.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-Dv5XRtRh.js.gz +0 -0
- package/dist/client/assets/DagPlayground-mq69dKyr.js.br +0 -0
- package/dist/client/assets/DagPlayground-mq69dKyr.js.gz +0 -0
- package/dist/client/assets/DiffLab-BqdHYGi5.js.br +0 -0
- package/dist/client/assets/DiffLab-BqdHYGi5.js.gz +0 -0
- package/dist/client/assets/DiffReview-BvzGxpuc.js.br +0 -0
- package/dist/client/assets/DiffReview-BvzGxpuc.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-DrfddB7x.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DrfddB7x.js.gz +0 -0
- package/dist/client/assets/DiffViewer-3bJV_A5s.js.br +0 -0
- package/dist/client/assets/DiffViewer-3bJV_A5s.js.gz +0 -0
- package/dist/client/assets/ModelPreview-CX7VtoJB.js.br +0 -0
- package/dist/client/assets/ModelPreview-CX7VtoJB.js.gz +0 -0
- package/dist/client/assets/RightSidebar-BK6vWNw3.js +0 -1678
- package/dist/client/assets/RightSidebar-BK6vWNw3.js.br +0 -0
- package/dist/client/assets/RightSidebar-BK6vWNw3.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-PJwhLTPH.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-PJwhLTPH.js.gz +0 -0
- package/dist/client/assets/app-C7pXMvSE.js +0 -35
- package/dist/client/assets/app-C7pXMvSE.js.br +0 -0
- package/dist/client/assets/app-C7pXMvSE.js.gz +0 -0
- package/dist/client/assets/arc-l3FXjCEl.js.br +0 -0
- package/dist/client/assets/arc-l3FXjCEl.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DdNbKi-4.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DdNbKi-4.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Cu63oaxR.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Cu63oaxR.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DQiV_yk6.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DQiV_yk6.js.gz +0 -0
- package/dist/client/assets/channel-CwC957gi.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-sLDCEnU8.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-sLDCEnU8.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DYu5qS7g.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DYu5qS7g.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-nJnbUFzs.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-nJnbUFzs.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BwyiSQgx.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BwyiSQgx.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-CmoJ7-rE.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CmoJ7-rE.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-rroo-EOb.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-rroo-EOb.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CkFeV85k.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-hBwpU_bW.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-hBwpU_bW.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-B321-hdp.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-B321-hdp.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-bY87_ptA.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-bY87_ptA.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN--cez0H_o.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN--cez0H_o.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BupH_VRK.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BupH_VRK.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DewqaxvF.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DewqaxvF.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-wDumcTuR.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-wDumcTuR.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-C0OY91We.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-C0OY91We.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Cw9aNqvJ.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Cw9aNqvJ.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BC7XUXeI.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DovhJIz5.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DovhJIz5.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-wjVok2Lg.js.br +0 -0
- package/dist/client/assets/gestureArbiter-wjVok2Lg.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BmrbbyW1.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BmrbbyW1.js.gz +0 -0
- package/dist/client/assets/index-BeD4iJ6L.css +0 -1
- package/dist/client/assets/index-BeD4iJ6L.css.br +0 -0
- package/dist/client/assets/index-BeD4iJ6L.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BPqibqsm.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BPqibqsm.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DIkhubnf.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DIkhubnf.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CBT0DIUR.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-CBT0DIUR.js.gz +0 -0
- package/dist/client/assets/layout-C1CMlOIU.js.br +0 -0
- package/dist/client/assets/layout-C1CMlOIU.js.gz +0 -0
- package/dist/client/assets/linear-C4gyqor0.js.br +0 -0
- package/dist/client/assets/linear-C4gyqor0.js.gz +0 -0
- package/dist/client/assets/mermaid.core-CMfa4ncd.js.br +0 -0
- package/dist/client/assets/mermaid.core-CMfa4ncd.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-C-JmgKOI.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-C-JmgKOI.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-BmUG2IwA.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-BmUG2IwA.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-IY-AEKQa.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-IY-AEKQa.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-9Qq43qED.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-9Qq43qED.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BjQncx5i.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BjQncx5i.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Cr1rL0wZ.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Cr1rL0wZ.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-ConC5-ym.js.br +0 -0
- package/dist/client/assets/referenceLongPress-ConC5-ym.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DU2q_iZg.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DU2q_iZg.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BYlx9kXC.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BYlx9kXC.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D_uAKme-.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D_uAKme-.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-H_uqscf4.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-H_uqscf4.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BvsQjIIW.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-BvsQjIIW.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DNgoExtD.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-LmUJag-p.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-LmUJag-p.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BRuArMR5.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-DZ5n0Pim.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DZ5n0Pim.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BkTA_Ciz.js.gz +0 -0
- package/dist/client/assets/videoCompression-VZIoFnyJ.js.br +0 -0
- package/dist/client/assets/videoCompression-VZIoFnyJ.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-7hzU176j.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-7hzU176j.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Bx4P96Bb.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Bx4P96Bb.js.gz +0 -0
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { collaborationResultPresentation } from './collaborationResultPresentation.js';
|
|
5
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
6
|
+
import { taskMemberKey } from './collaborationTaskTypes.js';
|
|
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
|
+
});
|
|
17
|
+
const sameMember = (a, b) => a !== null && b !== null && taskMemberKey(a) === taskMemberKey(b);
|
|
18
|
+
function text(value, name, limit = 32_000, required = false) {
|
|
19
|
+
if (value !== undefined && typeof value !== 'string')
|
|
20
|
+
throw new CollaborationError('INVALID_TASK', `${name}必须是文本`);
|
|
21
|
+
const result = typeof value === 'string' ? value.trim() : '';
|
|
22
|
+
if (Buffer.byteLength(result) > limit || required && !result)
|
|
23
|
+
throw new CollaborationError('INVALID_TASK', `${name}${required ? '不能为空,且' : ''}最多 ${limit} 字节`);
|
|
24
|
+
return result;
|
|
25
|
+
}
|
|
26
|
+
function member(value) {
|
|
27
|
+
const m = value;
|
|
28
|
+
if (!m || typeof m.serviceId !== 'string' || !/^12D3KooW[a-zA-Z0-9]{30,60}$/.test(m.serviceId)
|
|
29
|
+
|| typeof m.sessionId !== 'string' || !m.sessionId || m.sessionId.length > 256 || m.sessionId.startsWith('remote:')) {
|
|
30
|
+
throw new CollaborationError('INVALID_MEMBER', '成员身份无效,请重新选择');
|
|
31
|
+
}
|
|
32
|
+
return { serviceId: m.serviceId, sessionId: m.sessionId };
|
|
33
|
+
}
|
|
34
|
+
const stable = (value) => JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
|
|
35
|
+
? Object.fromEntries(Object.keys(v).sort().map(key => [key, v[key]])) : v);
|
|
36
|
+
function validateSnapshot(task) {
|
|
37
|
+
const validId = (value) => typeof value === 'string' && /^[a-f0-9]{32}$/.test(value);
|
|
38
|
+
const time = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
39
|
+
const reportStatus = (value) => ['ack', 'working', 'blocked', 'complete', 'failed'].includes(String(value));
|
|
40
|
+
const invalid = () => { throw new CollaborationError('INVALID_TASK_SNAPSHOT', '远端任务记录无效'); };
|
|
41
|
+
if (!task || !validId(task.id) || typeof task.groupId !== 'string' || !task.groupId || !time(task.createdAt) || !time(task.updatedAt)
|
|
42
|
+
|| !Number.isSafeInteger(task.revision) || task.revision < 1 || !['open', 'accepted', 'closed'].includes(task.status)
|
|
43
|
+
|| task.activeAttemptId !== null && !validId(task.activeAttemptId) || task.parentTaskId !== null && !validId(task.parentTaskId)
|
|
44
|
+
|| !Array.isArray(task.dependsOn) || task.dependsOn.length > 32 || task.dependsOn.some(v => !validId(v))
|
|
45
|
+
|| !Array.isArray(task.attempts) || task.attempts.length > 100 || !Array.isArray(task.events) || task.events.length > 1000
|
|
46
|
+
|| !Array.isArray(task.decisions) || task.decisions.length > 1000 || !Array.isArray(task.artifacts) || task.artifacts.length > 200
|
|
47
|
+
|| !Array.isArray(task.deliveries) || task.deliveries.length > 2000 || Buffer.byteLength(JSON.stringify(task)) > 512_000)
|
|
48
|
+
invalid();
|
|
49
|
+
text(task.title, '标题', 640, true);
|
|
50
|
+
text(task.spec, '任务内容', 32_000, true);
|
|
51
|
+
text(task.constraints, '约束');
|
|
52
|
+
text(task.acceptance, '完成标准');
|
|
53
|
+
member({ serviceId: task.ownerServiceId, sessionId: 'validation' });
|
|
54
|
+
if (task.coordinator !== null)
|
|
55
|
+
member(task.coordinator);
|
|
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();
|
|
59
|
+
if (!['goal', 'step'].includes(task.workflow.kind) || typeof task.workflow.paused !== 'boolean' || typeof task.workflow.isolated !== 'boolean'
|
|
60
|
+
|| !Array.isArray(task.workflow.reviewers) || task.workflow.reviewers.length > 32 || !Number.isInteger(task.workflow.maxRevisions)
|
|
61
|
+
|| task.workflow.maxRevisions < 1 || task.workflow.maxRevisions > 10 || task.workflow.rootTaskId && !validId(task.workflow.rootTaskId))
|
|
62
|
+
invalid();
|
|
63
|
+
if (task.workflow.maxParallel !== undefined && (!Number.isInteger(task.workflow.maxParallel) || task.workflow.maxParallel < 1 || task.workflow.maxParallel > 4))
|
|
64
|
+
invalid();
|
|
65
|
+
task.workflow.reviewers.forEach(member);
|
|
66
|
+
}
|
|
67
|
+
if (task.scheduledAssignee)
|
|
68
|
+
member(task.scheduledAssignee);
|
|
69
|
+
if (task.workspace && [task.workspace.cwd, task.workspace.repository, task.workspace.branch, task.workspace.base].some(v => typeof v !== 'string' || !v || v.length > 4096))
|
|
70
|
+
invalid();
|
|
71
|
+
for (const attempt of task.attempts) {
|
|
72
|
+
if (!attempt || !validId(attempt.id) || !validId(attempt.threadId) || !time(attempt.createdAt))
|
|
73
|
+
invalid();
|
|
74
|
+
member(attempt.assignee);
|
|
75
|
+
if (attempt.report && (!reportStatus(attempt.report.status) || !time(attempt.report.createdAt) || typeof attempt.report.content !== 'string'))
|
|
76
|
+
invalid();
|
|
77
|
+
}
|
|
78
|
+
if (task.activeAttemptId && !task.attempts.some(a => a.id === task.activeAttemptId))
|
|
79
|
+
invalid();
|
|
80
|
+
for (const event of task.events) {
|
|
81
|
+
if (!event || !validId(event.id) || !Number.isSafeInteger(event.sequence) || event.sequence < 1 || typeof event.kind !== 'string'
|
|
82
|
+
|| typeof event.content !== 'string' || !time(event.createdAt) || event.attemptId !== null && !task.attempts.some(a => a.id === event.attemptId))
|
|
83
|
+
invalid();
|
|
84
|
+
if (event.deliveryId !== undefined && !validId(event.deliveryId))
|
|
85
|
+
invalid();
|
|
86
|
+
if (event.source !== undefined && !['system', 'user'].includes(event.source))
|
|
87
|
+
invalid();
|
|
88
|
+
if (event.actor !== null)
|
|
89
|
+
member(event.actor);
|
|
90
|
+
if (event.target)
|
|
91
|
+
member(event.target);
|
|
92
|
+
}
|
|
93
|
+
for (const decision of task.decisions) {
|
|
94
|
+
if (!decision || !validId(decision.id) || typeof decision.question !== 'string' || !time(decision.createdAt)
|
|
95
|
+
|| !task.attempts.some(a => a.id === decision.attemptId) || !['pending', 'answered', 'superseded'].includes(decision.status)
|
|
96
|
+
|| !Array.isArray(decision.options) || decision.options.length > 8 || decision.options.some(o => typeof o !== 'string')
|
|
97
|
+
|| decision.status === 'answered' && (typeof decision.answer !== 'string' || !time(decision.answeredAt)))
|
|
98
|
+
invalid();
|
|
99
|
+
}
|
|
100
|
+
for (const artifact of task.artifacts) {
|
|
101
|
+
if (!artifact || !validId(artifact.id) || !task.attempts.some(a => a.id === artifact.attemptId) || !['plan', 'result', 'review'].includes(artifact.kind)
|
|
102
|
+
|| typeof artifact.content !== 'string' || !time(artifact.createdAt))
|
|
103
|
+
invalid();
|
|
104
|
+
text(artifact.summary, '结果摘要', 2400);
|
|
105
|
+
member(artifact.actor);
|
|
106
|
+
if (artifact.verdict && !['pass', 'changes', 'blocked'].includes(artifact.verdict))
|
|
107
|
+
invalid();
|
|
108
|
+
}
|
|
109
|
+
for (const delivery of task.deliveries)
|
|
110
|
+
if (!delivery || !validId(delivery.id) || typeof delivery.messageId !== 'string'
|
|
111
|
+
|| typeof delivery.status !== 'string' || delivery.error !== null && typeof delivery.error !== 'string'
|
|
112
|
+
|| delivery.attemptId !== null && !task.attempts.some(a => a.id === delivery.attemptId))
|
|
113
|
+
invalid();
|
|
114
|
+
}
|
|
115
|
+
/** Atomic document writes keep decisions and their outgoing messages in one transaction. */
|
|
116
|
+
export class CollaborationTaskStore {
|
|
117
|
+
file;
|
|
118
|
+
document = { version: 1, tasks: [], outbox: [], requests: {} };
|
|
119
|
+
constructor(file) {
|
|
120
|
+
this.file = file;
|
|
121
|
+
try {
|
|
122
|
+
const doc = JSON.parse(readFileSync(file, 'utf8'));
|
|
123
|
+
if (doc.version !== 1 || !Array.isArray(doc.tasks) || !Array.isArray(doc.outbox) || !doc.requests)
|
|
124
|
+
throw new Error('任务记录格式无效');
|
|
125
|
+
this.document = doc;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (error.code !== 'ENOENT')
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
transaction(mutate) {
|
|
133
|
+
const next = structuredClone(this.document);
|
|
134
|
+
const result = mutate(next);
|
|
135
|
+
if (next.tasks.some(task => Buffer.byteLength(JSON.stringify(task)) > 512_000))
|
|
136
|
+
throw new CollaborationError('TASK_HISTORY_LIMIT', '此任务记录达到 500 KiB,请建立后续任务并保留本任务为历史', 409);
|
|
137
|
+
const serialized = JSON.stringify(next);
|
|
138
|
+
if (Buffer.byteLength(serialized) > 64 * 1024 * 1024)
|
|
139
|
+
throw new CollaborationError('TASK_STORAGE_FULL', '任务记录已达到保存上限,请导出历史后整理', 409);
|
|
140
|
+
mkdirSync(dirname(this.file), { recursive: true, mode: 0o700 });
|
|
141
|
+
const temporary = `${this.file}.${process.pid}.tmp`;
|
|
142
|
+
writeFileSync(temporary, serialized, { mode: 0o600 });
|
|
143
|
+
renameSync(temporary, this.file);
|
|
144
|
+
this.document = next;
|
|
145
|
+
return structuredClone(result);
|
|
146
|
+
}
|
|
147
|
+
get(taskId) { return structuredClone(this.document.tasks.find(t => t.id === taskId) ?? null); }
|
|
148
|
+
owned(owner) { return structuredClone(this.document.tasks.filter(t => t.ownerServiceId === owner)); }
|
|
149
|
+
children(taskId) {
|
|
150
|
+
return structuredClone(this.document.tasks.filter(t => t.parentTaskId === taskId || t.workflow?.rootTaskId === taskId)
|
|
151
|
+
.map(({ id, title, status, revision, completionMode, workspace }) => ({ id, title, status, revision, completionMode, workspace })));
|
|
152
|
+
}
|
|
153
|
+
list(groupIds, summary = false) {
|
|
154
|
+
const tasks = this.document.tasks.filter(t => groupIds.includes(t.groupId)).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
155
|
+
return structuredClone(tasks.map(task => {
|
|
156
|
+
if (!summary)
|
|
157
|
+
return task;
|
|
158
|
+
const current = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
159
|
+
const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
|
|
160
|
+
const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
|
|
161
|
+
return { ...task, spec: task.spec.slice(0, 512), constraints: '', acceptance: '',
|
|
162
|
+
deliveries: task.deliveries.filter(d => d.attemptId === task.activeAttemptId && d.error).slice(-1),
|
|
163
|
+
attempts: current ? [{ ...current, report: current.report ? { ...current.report, content: '', evidence: undefined } : 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 })),
|
|
165
|
+
decisions: task.decisions.filter(d => d.status === 'pending').map(d => ({ ...d, question: '', options: [] })),
|
|
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) : '' })) };
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
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 })); }
|
|
170
|
+
pending(taskId, dueOnly = false) {
|
|
171
|
+
return structuredClone(this.document.outbox
|
|
172
|
+
.filter(o => (!taskId || o.taskId === taskId) && (!dueOnly || (o.nextRetryAt ?? 0) <= Date.now())));
|
|
173
|
+
}
|
|
174
|
+
event(task, kind, actor, content, attemptId = task.activeAttemptId) {
|
|
175
|
+
const event = { id: id(), sequence: (task.events.at(-1)?.sequence ?? 0) + 1, kind, actor, content, attemptId, createdAt: Math.max(Date.now(), (task.events.at(-1)?.createdAt ?? 0) + 1) };
|
|
176
|
+
task.events.push(event);
|
|
177
|
+
return event;
|
|
178
|
+
}
|
|
179
|
+
request(doc, actor, key, payload) {
|
|
180
|
+
if (typeof key !== 'string' || !key.trim() || key.length > 256)
|
|
181
|
+
throw new CollaborationError('INVALID_IDEMPOTENCY_KEY', '需要有效的请求幂等键');
|
|
182
|
+
const scoped = `${actor ? taskMemberKey(actor) : 'user'}:${key}`;
|
|
183
|
+
const hash = createHash('sha256').update(stable(payload)).digest('hex');
|
|
184
|
+
const previous = doc.requests[scoped];
|
|
185
|
+
if (previous && previous.hash !== hash)
|
|
186
|
+
throw new CollaborationError('IDEMPOTENCY_CONFLICT', '该请求已用于不同内容;请保留原请求或重新开始', 409);
|
|
187
|
+
return { key: scoped, hash, previous: previous ? doc.tasks.find(t => t.id === previous.taskId) : undefined };
|
|
188
|
+
}
|
|
189
|
+
assign(doc, task, assignee, actor, content) {
|
|
190
|
+
doc.outbox = doc.outbox.filter(o => o.taskId !== task.id || !!o.messageId);
|
|
191
|
+
for (const decision of task.decisions)
|
|
192
|
+
if (decision.status === 'pending')
|
|
193
|
+
decision.status = 'superseded';
|
|
194
|
+
const attemptId = id(), threadId = id();
|
|
195
|
+
task.activeAttemptId = attemptId;
|
|
196
|
+
task.attempts.push({ id: attemptId, assignee: member(assignee), createdAt: Date.now(), threadId });
|
|
197
|
+
this.event(task, 'assigned', actor, content || '任务已分派');
|
|
198
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId, target: assignee, kind: 'task', threadId,
|
|
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。只按用户授权运行测试。' : ''}` });
|
|
200
|
+
}
|
|
201
|
+
goalInstructions(task) {
|
|
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 '直接回答用户的简短结果和限制';只读目标直接汇总已核对结果,不创建集成或重复汇总任务。正文保留证据、评审结论和限制,仅代码目标补充目录、分支和提交,等待用户最终验收。任何异常都要留下明确说明,不伪造完成或进度。`;
|
|
203
|
+
}
|
|
204
|
+
notifyCoordinator(doc, task, update) {
|
|
205
|
+
if (!task.coordinator)
|
|
206
|
+
return;
|
|
207
|
+
if (task.workflow)
|
|
208
|
+
return; // Managed goals are woken once per persisted event set by advance().
|
|
209
|
+
const attempt = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
210
|
+
if (update ? sameMember(task.coordinator, update.actor) : attempt && sameMember(task.coordinator, attempt.assignee))
|
|
211
|
+
return;
|
|
212
|
+
if (update && ['answer', 'revise'].includes(update.kind) && attempt && sameMember(task.coordinator, attempt.assignee))
|
|
213
|
+
return;
|
|
214
|
+
if (update) {
|
|
215
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: attempt?.id ?? null, target: task.coordinator, kind: 'message', threadId: attempt?.threadId ?? task.id,
|
|
216
|
+
content: `协调任务「${task.title}」有明确更新:${update.kind}${update.reportStatus ? ` / ${update.reportStatus}` : ''}\n原始记录时间:${new Date(update.createdAt).toISOString()}\n${update.attemptId && update.attemptId !== task.activeAttemptId ? '这是历史分派的报告,请先核对当前任务。\n' : ''}${update.content.slice(0, 4000)}\n任务 ID:${task.id}\n用 td collab task get ${task.id} --text 查看完整原文与当前版本;问题回答与结果验收由用户决定。` });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: attempt?.id ?? null, target: task.coordinator, kind: 'message', threadId: attempt?.threadId ?? task.id,
|
|
220
|
+
content: `你被指定为任务「${task.title}」的协调者。\n目标:${task.spec}\n约束:${task.constraints || '未补充'}\n验收标准:${task.acceptance || '由用户决定'}\n任务 ID:${task.id}\n用 td collab task get ${task.id} --text 查看原始记录;用 task list --group ${task.groupId} 查看工作组。需要拆分时用 task create --group ${task.groupId} --parent ${task.id} --title '子任务标题' --content '任务内容'。分派前查询 revision,再用 task assign --revision;把必要的人类决策交给 task ask,不推断成员忙闲,不停止其他成员终端。` });
|
|
221
|
+
}
|
|
222
|
+
create(owner, input, actor) {
|
|
223
|
+
const title = text(input.title, '标题', 640, true), spec = text(input.spec, '任务内容', 32_000, true);
|
|
224
|
+
const constraints = text(input.constraints, '约束'), acceptance = text(input.acceptance, '完成标准');
|
|
225
|
+
if (input.dependsOn !== undefined && (!Array.isArray(input.dependsOn) || input.dependsOn.length > 32 || input.dependsOn.some(v => typeof v !== 'string')))
|
|
226
|
+
throw new CollaborationError('INVALID_DEPENDENCIES', '依赖列表无效');
|
|
227
|
+
if (input.coordinator)
|
|
228
|
+
member(input.coordinator);
|
|
229
|
+
if (input.assignee)
|
|
230
|
+
member(input.assignee);
|
|
231
|
+
if (input.managed !== undefined && typeof input.managed !== 'boolean' || input.isolated !== undefined && typeof input.isolated !== 'boolean'
|
|
232
|
+
|| input.integration !== undefined && typeof input.integration !== 'boolean')
|
|
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', '只读任务不需要代码集成');
|
|
238
|
+
if (input.reviewers && (!Array.isArray(input.reviewers) || input.reviewers.length > 32))
|
|
239
|
+
throw new CollaborationError('INVALID_REVIEWERS', '评审成员列表无效');
|
|
240
|
+
input.reviewers?.forEach(member);
|
|
241
|
+
return this.transaction(doc => {
|
|
242
|
+
const request = this.request(doc, actor, input.idempotencyKey, input);
|
|
243
|
+
if (request.previous)
|
|
244
|
+
return request.previous;
|
|
245
|
+
if (doc.tasks.length >= 2000)
|
|
246
|
+
throw new CollaborationError('TASK_LIMIT', '任务记录达到上限,请导出并整理历史', 409);
|
|
247
|
+
const dependsOn = [...new Set(input.dependsOn ?? [])];
|
|
248
|
+
for (const dependency of [...dependsOn, ...(input.parentTaskId ? [input.parentTaskId] : [])]) {
|
|
249
|
+
if (!doc.tasks.some(t => t.id === dependency && t.groupId === input.groupId))
|
|
250
|
+
throw new CollaborationError('TASK_DEPENDENCY_NOT_FOUND', '父任务或依赖任务不在当前协作组', 404);
|
|
251
|
+
}
|
|
252
|
+
const now = Date.now();
|
|
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');
|
|
257
|
+
if (parent?.workflow && actor && !sameMember(actor, parent.coordinator))
|
|
258
|
+
throw new CollaborationError('TASK_PERMISSION_DENIED', '只有协调者可以创建此目标的子任务', 403);
|
|
259
|
+
if (parent?.status !== undefined && parent.status !== 'open')
|
|
260
|
+
throw new CollaborationError('TASK_ARCHIVED', '目标已归档', 409);
|
|
261
|
+
if (parent?.workflow && doc.tasks.filter(t => t.workflow?.rootTaskId === (parent.workflow?.rootTaskId ?? parent.id)).length >= 32)
|
|
262
|
+
throw new CollaborationError('GOAL_TASK_LIMIT', '单个目标最多 32 个子任务,请收敛分工或建立后续目标', 409);
|
|
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;
|
|
265
|
+
if (workflow?.kind === 'goal' && (!input.coordinator || workflow.reviewers.length < 1))
|
|
266
|
+
throw new CollaborationError('WORKFLOW_MEMBERS_REQUIRED', '请选择协调者和至少一位执行成员', 400);
|
|
267
|
+
if (workflow?.kind === 'goal' && !workflow.reviewers.some(m => !sameMember(m, input.coordinator ?? null)))
|
|
268
|
+
throw new CollaborationError('WORKFLOW_MEMBERS_REQUIRED', '执行成员需要与协调者独立');
|
|
269
|
+
if (workflow && parent && !input.assignee)
|
|
270
|
+
throw new CollaborationError('ASSIGNEE_REQUIRED', '自动协作子任务需要执行成员');
|
|
271
|
+
if (workflow && parent && sameMember(input.assignee ?? null, parent.coordinator))
|
|
272
|
+
throw new CollaborationError('COORDINATOR_IS_NOT_WORKER', '请把实施分派给执行成员,协调者负责组织与评审');
|
|
273
|
+
if (workflow && parent && !workflow.reviewers.some(m => sameMember(m, input.assignee ?? null)))
|
|
274
|
+
throw new CollaborationError('WORKER_TEMPLATE_REQUIRED', '请从此目标的执行成员中选择模板');
|
|
275
|
+
if (workflow?.rootTaskId && doc.tasks.find(t => t.id === workflow.rootTaskId)?.workflow?.paused)
|
|
276
|
+
throw new CollaborationError('WORKFLOW_PAUSED', '目标已暂停,请先继续协调');
|
|
277
|
+
if (workflow?.integration) {
|
|
278
|
+
const required = codeSteps(doc.tasks, workflow.rootTaskId);
|
|
279
|
+
if (!required.length || required.some(t => !dependsOn.includes(t.id)))
|
|
280
|
+
throw new CollaborationError('INTEGRATION_DEPENDENCIES_REQUIRED', '集成任务必须依赖目标下全部实施子任务');
|
|
281
|
+
}
|
|
282
|
+
const task = { id: id(), ownerServiceId: owner, groupId: input.groupId, title, spec, constraints, acceptance,
|
|
283
|
+
coordinator: parent?.workflow ? parent.coordinator : input.coordinator !== undefined ? input.coordinator : input.parentTaskId ? doc.tasks.find(t => t.id === input.parentTaskId)?.coordinator ?? actor : null,
|
|
284
|
+
parentTaskId: input.parentTaskId ?? null, dependsOn, createdAt: now, updatedAt: now, revision: 1,
|
|
285
|
+
status: 'open', activeAttemptId: null, attempts: [], decisions: [], artifacts: [], events: [], deliveries: [], ...(workflow ? { workflow } : {}) };
|
|
286
|
+
this.event(task, 'created', actor, spec);
|
|
287
|
+
if (parent?.constraints)
|
|
288
|
+
task.constraints = text([parent.constraints, constraints].filter(Boolean).join('\n'), '继承约束');
|
|
289
|
+
if (workflow?.kind === 'goal') {
|
|
290
|
+
this.assign(doc, task, member(input.coordinator), actor, this.goalInstructions(task));
|
|
291
|
+
task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: 0, notifiedAt: now };
|
|
292
|
+
}
|
|
293
|
+
else if (workflow && input.assignee) {
|
|
294
|
+
task.scheduledAssignee = input.assignee;
|
|
295
|
+
this.event(task, 'scheduled', actor, dependsOn.length ? '依赖评审通过后自动分派' : '等待准备执行目录与分派');
|
|
296
|
+
}
|
|
297
|
+
else if (input.assignee) {
|
|
298
|
+
if (dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
|
|
299
|
+
throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收;可以先保存任务,稍后分派', 409);
|
|
300
|
+
this.assign(doc, task, input.assignee, actor, '');
|
|
301
|
+
}
|
|
302
|
+
this.notifyCoordinator(doc, task);
|
|
303
|
+
doc.tasks.push(task);
|
|
304
|
+
this.touchParent(doc, task, '新子任务已创建');
|
|
305
|
+
doc.requests[request.key] = { hash: request.hash, taskId: task.id };
|
|
306
|
+
return task;
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
apply(taskId, input, actor) {
|
|
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', '摘要只能随完成结果提交');
|
|
314
|
+
if (input.verdict !== undefined && !['pass', 'changes', 'blocked'].includes(input.verdict))
|
|
315
|
+
throw new CollaborationError('INVALID_VERDICT', '评审结论无效');
|
|
316
|
+
if (input.evidence !== undefined && Buffer.byteLength(JSON.stringify(input.evidence)) > 16_384)
|
|
317
|
+
throw new CollaborationError('EVIDENCE_TOO_LARGE', '证据最多 16 KiB');
|
|
318
|
+
return this.transaction(doc => {
|
|
319
|
+
const task = doc.tasks.find(t => t.id === taskId);
|
|
320
|
+
if (!task)
|
|
321
|
+
throw new CollaborationError('TASK_NOT_FOUND', '任务不存在或无权查看', 404);
|
|
322
|
+
const request = this.request(doc, actor, input.idempotencyKey, [taskId, input]);
|
|
323
|
+
if (request.previous)
|
|
324
|
+
return request.previous;
|
|
325
|
+
const coordination = actor === null || sameMember(actor, task.coordinator);
|
|
326
|
+
if (['assign', 'coordinator', 'close', 'reopen', 'revise', 'request-review', 'coordinate', 'pause', 'resume', 'retry'].includes(input.kind) && !coordination)
|
|
327
|
+
throw new CollaborationError('TASK_PERMISSION_DENIED', '此操作需要用户或当前协调者', 403);
|
|
328
|
+
if (['answer', 'accept', 'approve-plan'].includes(input.kind) && actor !== null)
|
|
329
|
+
throw new CollaborationError('USER_DECISION_REQUIRED', '回答、方案确认与验收需要用户操作', 403);
|
|
330
|
+
if (!['report', 'ask', 'submit-plan', 'review', 'comment', 'answer'].includes(input.kind) && input.expectedRevision !== task.revision)
|
|
331
|
+
throw new CollaborationError('TASK_CHANGED', '任务已更新,请查看最新记录后重试', 409);
|
|
332
|
+
const userFollowup = actor === null && input.kind === 'revise' && !!content;
|
|
333
|
+
if (task.status !== 'open' && !['reopen', 'report'].includes(input.kind) && !(task.status === 'accepted' && userFollowup))
|
|
334
|
+
throw new CollaborationError('TASK_ARCHIVED', '任务已归档;需要继续时请重新打开', 409);
|
|
335
|
+
if (task.workflow && task.status !== 'open' && input.kind !== 'reopen' && !(task.status === 'accepted' && userFollowup))
|
|
336
|
+
throw new CollaborationError('TASK_ARCHIVED', '自动协作的已完成版本不能追加执行报告,请先重新打开', 409);
|
|
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))
|
|
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
|
+
}
|
|
356
|
+
const attempt = task.attempts.find(a => a.id === input.attemptId);
|
|
357
|
+
if (['report', 'ask', 'submit-plan'].includes(input.kind) && (!attempt || !sameMember(actor, attempt.assignee)))
|
|
358
|
+
throw new CollaborationError('ATTEMPT_PERMISSION_DENIED', '只能对自己负责的执行尝试提交报告', 403);
|
|
359
|
+
if (['ask', 'submit-plan'].includes(input.kind) && attempt?.id !== task.activeAttemptId)
|
|
360
|
+
throw new CollaborationError('ATTEMPT_SUPERSEDED', '这轮分派已被替换,不能再请求用户决定', 409);
|
|
361
|
+
if (task.workflow && ['report', 'ask', 'submit-plan'].includes(input.kind) && attempt?.id !== task.activeAttemptId)
|
|
362
|
+
throw new CollaborationError('ATTEMPT_SUPERSEDED', '自动协作只能推进当前分派', 409);
|
|
363
|
+
if (input.kind === 'assign') {
|
|
364
|
+
if (task.workflow?.kind === 'goal' && !sameMember(input.assignee ?? null, task.coordinator))
|
|
365
|
+
throw new CollaborationError('GOAL_COORDINATOR_REQUIRED', '目标由协调者负责,请在子任务中分派实施');
|
|
366
|
+
if (task.dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
|
|
367
|
+
throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收', 409);
|
|
368
|
+
if (task.workflow?.kind === 'step' && task.workflow.isolated) {
|
|
369
|
+
task.scheduledAssignee = member(input.assignee);
|
|
370
|
+
task.activeAttemptId = null;
|
|
371
|
+
delete task.workspace;
|
|
372
|
+
delete task.automationIssue;
|
|
373
|
+
this.event(task, 'scheduled', actor, content || '准备新一轮独立执行目录');
|
|
374
|
+
}
|
|
375
|
+
else
|
|
376
|
+
this.assign(doc, task, member(input.assignee), actor, content);
|
|
377
|
+
this.notifyCoordinator(doc, task);
|
|
378
|
+
}
|
|
379
|
+
else if (input.kind === 'coordinator') {
|
|
380
|
+
if (task.workflow && !input.coordinator)
|
|
381
|
+
throw new CollaborationError('GOAL_COORDINATOR_REQUIRED', '自动协作需要协调者');
|
|
382
|
+
const previousCoordinator = task.coordinator;
|
|
383
|
+
task.coordinator = input.coordinator ? member(input.coordinator) : null;
|
|
384
|
+
this.event(task, 'coordinator', actor, content || '协调者已更新');
|
|
385
|
+
if (task.workflow?.kind === 'goal') {
|
|
386
|
+
this.assign(doc, task, task.coordinator, actor, this.goalInstructions(task));
|
|
387
|
+
task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: 0, notifiedAt: Date.now() };
|
|
388
|
+
task.workflow.reviewers = [...task.workflow.reviewers.filter(m => !sameMember(m, task.coordinator)), ...(previousCoordinator && !sameMember(previousCoordinator, task.coordinator) ? [previousCoordinator] : [])];
|
|
389
|
+
for (const child of doc.tasks.filter(t => t.workflow?.rootTaskId === task.id)) {
|
|
390
|
+
child.coordinator = task.coordinator;
|
|
391
|
+
child.workflow.reviewers = task.workflow.reviewers;
|
|
392
|
+
child.revision++;
|
|
393
|
+
child.updatedAt = Date.now();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
this.notifyCoordinator(doc, task);
|
|
397
|
+
}
|
|
398
|
+
else if (input.kind === 'report') {
|
|
399
|
+
if (!['ack', 'working', 'blocked', 'complete', 'failed'].includes(input.status ?? '') || !content)
|
|
400
|
+
throw new CollaborationError('INVALID_REPORT', '报告需要明确状态和正文');
|
|
401
|
+
if (input.status === 'complete' && task.workflow?.kind === 'goal' && (!doc.tasks.some(t => t.workflow?.rootTaskId === task.id)
|
|
402
|
+
|| doc.tasks.some(t => t.workflow?.rootTaskId === task.id && !['accepted', 'closed'].includes(t.status))))
|
|
403
|
+
throw new CollaborationError('CHILD_TASKS_PENDING', '请先完成全部子任务的独立评审,再汇总交付', 409);
|
|
404
|
+
if (input.status === 'complete' && task.workflow?.kind === 'goal' && task.workflow.isolated) {
|
|
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))))
|
|
407
|
+
throw new CollaborationError('INTEGRATION_PENDING', '代码目标需要依赖全部实施任务的集成交付通过评审', 409);
|
|
408
|
+
}
|
|
409
|
+
if (input.status === 'complete') {
|
|
410
|
+
const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
|
|
411
|
+
if (plan && task.approvedPlanArtifactId !== plan.id)
|
|
412
|
+
throw new CollaborationError('PLAN_PENDING', '方案尚未由用户确认,请先等待决定', 409);
|
|
413
|
+
}
|
|
414
|
+
attempt.report = { status: input.status, content, evidence: input.evidence, createdAt: Date.now() };
|
|
415
|
+
const event = this.event(task, 'report', actor, content, attempt.id);
|
|
416
|
+
event.reportStatus = input.status;
|
|
417
|
+
event.evidence = input.evidence;
|
|
418
|
+
if (input.status === 'complete')
|
|
419
|
+
task.artifacts.push({ id: id(), attemptId: attempt.id, kind: 'result', content, ...(summary ? { summary } : {}), evidence: input.evidence, actor: actor, createdAt: event.createdAt });
|
|
420
|
+
}
|
|
421
|
+
else if (input.kind === 'ask') {
|
|
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)))
|
|
423
|
+
throw new CollaborationError('INVALID_QUESTION', '问题正文与选项无效');
|
|
424
|
+
task.decisions.push({ id: id(), attemptId: attempt.id, question: content, options: input.options ?? [], status: 'pending', createdAt: Date.now() });
|
|
425
|
+
this.event(task, 'question', actor, content, attempt.id);
|
|
426
|
+
}
|
|
427
|
+
else if (input.kind === 'answer') {
|
|
428
|
+
const decision = task.decisions.find(d => d.id === input.decisionId);
|
|
429
|
+
if (!decision || decision.status !== 'pending' || decision.attemptId !== task.activeAttemptId)
|
|
430
|
+
throw new CollaborationError('QUESTION_SETTLED', '该问题已回答或已不再适用,请查看最新记录', 409);
|
|
431
|
+
if (!content)
|
|
432
|
+
throw new CollaborationError('INVALID_ANSWER', '答案不能为空');
|
|
433
|
+
decision.status = 'answered';
|
|
434
|
+
decision.answer = content;
|
|
435
|
+
decision.answeredAt = Date.now();
|
|
436
|
+
this.event(task, 'answer', null, content, decision.attemptId);
|
|
437
|
+
const recipient = task.attempts.find(a => a.id === decision.attemptId);
|
|
438
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: recipient.id, target: recipient.assignee, kind: 'message', threadId: recipient.threadId,
|
|
439
|
+
content: `任务「${task.title}」问题已回答\n问题:${decision.question}\n用户回答:${content}\n任务 ID:${task.id}\n尝试 ID:${recipient.id}` });
|
|
440
|
+
}
|
|
441
|
+
else if (input.kind === 'submit-plan' || input.kind === 'review') {
|
|
442
|
+
if (!content || !actor)
|
|
443
|
+
throw new CollaborationError('INVALID_ARTIFACT', '交付物需要成员身份和正文');
|
|
444
|
+
const reviewed = input.kind === 'review' ? task.artifacts.find(a => a.id === input.artifactId && a.kind !== 'review') : undefined;
|
|
445
|
+
if (input.kind === 'review' && (!reviewed || sameMember(actor, reviewed.actor)))
|
|
446
|
+
throw new CollaborationError('INVALID_REVIEW', '独立评审需要选择另一成员提交的明确版本');
|
|
447
|
+
if (input.kind === 'review' && task.workflow) {
|
|
448
|
+
const request = task.events.filter(e => e.kind === 'request-review' && e.artifactId === reviewed?.id).at(-1);
|
|
449
|
+
if (!request?.target || !sameMember(actor, request.target))
|
|
450
|
+
throw new CollaborationError('REVIEW_PERMISSION_DENIED', '只有此版本指定的独立评审者可以推进自动流程', 403);
|
|
451
|
+
if (!['pass', 'changes', 'blocked'].includes(input.verdict ?? ''))
|
|
452
|
+
throw new CollaborationError('REVIEW_VERDICT_REQUIRED', '自动评审需要 --verdict pass|changes|blocked');
|
|
453
|
+
if (reviewed?.attemptId !== task.activeAttemptId || task.artifacts.filter(a => a.kind === reviewed.kind && a.attemptId === task.activeAttemptId).at(-1)?.id !== reviewed.id)
|
|
454
|
+
throw new CollaborationError('RESULT_CHANGED', '请评审当前分派的最新版本', 409);
|
|
455
|
+
}
|
|
456
|
+
const artifactAttempt = reviewed?.attemptId ?? attempt.id;
|
|
457
|
+
task.artifacts.push({ id: id(), attemptId: artifactAttempt, kind: input.kind === 'review' ? 'review' : 'plan', content, evidence: input.evidence,
|
|
458
|
+
actor, createdAt: Date.now(), ...(reviewed ? { reviewsArtifactId: reviewed.id, ...(input.verdict ? { verdict: input.verdict } : {}) } : {}) });
|
|
459
|
+
this.event(task, input.kind, actor, content, artifactAttempt);
|
|
460
|
+
}
|
|
461
|
+
else if (input.kind === 'request-review' || input.kind === 'approve-plan') {
|
|
462
|
+
const artifact = task.artifacts.find(a => a.id === input.artifactId && a.kind !== 'review' && a.attemptId === task.activeAttemptId);
|
|
463
|
+
if (!artifact)
|
|
464
|
+
throw new CollaborationError('ARTIFACT_CHANGED', '交付物已不属于当前分派', 409);
|
|
465
|
+
const recipient = input.kind === 'request-review' ? member(input.assignee) : artifact.actor;
|
|
466
|
+
if (input.kind === 'request-review' && sameMember(recipient, artifact.actor))
|
|
467
|
+
throw new CollaborationError('INVALID_REVIEW', '请选择独立评审成员');
|
|
468
|
+
if (input.kind === 'approve-plan') {
|
|
469
|
+
if (artifact.kind !== 'plan' || task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1)?.id !== artifact.id)
|
|
470
|
+
throw new CollaborationError('PLAN_CHANGED', '请选择本轮最新方案确认', 409);
|
|
471
|
+
task.approvedPlanArtifactId = artifact.id;
|
|
472
|
+
}
|
|
473
|
+
const event = this.event(task, input.kind, actor, content || (input.kind === 'request-review' ? '已请求独立评审' : '用户确认了此版本方案'));
|
|
474
|
+
event.artifactId = artifact.id;
|
|
475
|
+
event.target = recipient;
|
|
476
|
+
const threadId = task.attempts.find(a => a.id === task.activeAttemptId).threadId;
|
|
477
|
+
doc.outbox.push({ id: id(), taskId, attemptId: task.activeAttemptId, target: recipient, kind: 'message', threadId,
|
|
478
|
+
content: input.kind === 'request-review' ? `请独立评审任务「${task.title}」的交付物 ${artifact.id}。\n${content}\n用 td collab task get ${task.id} --text 查看上下文;用 td collab task review ${task.id} --artifact ${artifact.id} --content '评审结论' 提交。`
|
|
479
|
+
: `任务「${task.title}」方案 ${artifact.id} 已由用户确认。\n${content}\n任务 ID:${task.id}` });
|
|
480
|
+
}
|
|
481
|
+
else if (input.kind === 'accept') {
|
|
482
|
+
if (task.workflow?.kind === 'goal' && doc.tasks.some(t => t.workflow?.rootTaskId === task.id && !['accepted', 'closed'].includes(t.status)))
|
|
483
|
+
throw new CollaborationError('CHILD_TASKS_PENDING', '子任务尚未全部完成', 409);
|
|
484
|
+
if (task.workflow?.kind === 'goal' && task.workflow.isolated) {
|
|
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))))
|
|
487
|
+
throw new CollaborationError('INTEGRATION_PENDING', '需要全部实施任务的集成交付通过评审');
|
|
488
|
+
}
|
|
489
|
+
if (task.dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
|
|
490
|
+
throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收', 409);
|
|
491
|
+
if (task.decisions.some(d => d.status === 'pending'))
|
|
492
|
+
throw new CollaborationError('QUESTION_PENDING', '请先回答本轮待处理问题,再验收结果', 409);
|
|
493
|
+
const artifact = task.artifacts.find(a => a.id === input.artifactId && a.kind === 'result' && a.attemptId === task.activeAttemptId);
|
|
494
|
+
const latest = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
|
|
495
|
+
if (!artifact || latest?.id !== artifact.id)
|
|
496
|
+
throw new CollaborationError('RESULT_CHANGED', '请选择本轮最新结果验收', 409);
|
|
497
|
+
if (task.events.some(e => e.kind === 'revise' && e.attemptId === task.activeAttemptId && e.createdAt >= artifact.createdAt))
|
|
498
|
+
throw new CollaborationError('REVISION_PENDING', '修改要求之后需要提交新结果才能验收', 409);
|
|
499
|
+
if (task.workflow && task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === artifact.id).at(-1)?.verdict !== 'pass')
|
|
500
|
+
throw new CollaborationError('REVIEW_PENDING', '当前结果尚未通过独立评审', 409);
|
|
501
|
+
task.status = 'accepted';
|
|
502
|
+
task.acceptedArtifactId = artifact.id;
|
|
503
|
+
this.event(task, 'accepted', null, content || '用户接受了此版本结果');
|
|
504
|
+
for (const decision of task.decisions)
|
|
505
|
+
if (decision.status === 'pending')
|
|
506
|
+
decision.status = 'superseded';
|
|
507
|
+
}
|
|
508
|
+
else if (input.kind === 'revise' || input.kind === 'comment') {
|
|
509
|
+
if (!content)
|
|
510
|
+
throw new CollaborationError('INVALID_COMMENT', '反馈不能为空');
|
|
511
|
+
const event = this.event(task, input.kind, actor, content);
|
|
512
|
+
if (actor === null)
|
|
513
|
+
event.source = 'user';
|
|
514
|
+
const recipient = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
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
|
+
}
|
|
530
|
+
}
|
|
531
|
+
else if (input.kind === 'close' || input.kind === 'reopen') {
|
|
532
|
+
task.status = input.kind === 'close' ? 'closed' : 'open';
|
|
533
|
+
this.event(task, input.kind, actor, content || (input.kind === 'close' ? '任务已关闭,终端工作未被中止' : '任务已重新打开'));
|
|
534
|
+
if (input.kind === 'close')
|
|
535
|
+
doc.outbox = doc.outbox.filter(o => o.taskId !== task.id || !!o.messageId);
|
|
536
|
+
else
|
|
537
|
+
delete task.acceptedArtifactId;
|
|
538
|
+
if (input.kind === 'close')
|
|
539
|
+
for (const decision of task.decisions)
|
|
540
|
+
if (decision.status === 'pending')
|
|
541
|
+
decision.status = 'superseded';
|
|
542
|
+
if (task.workflow?.kind === 'goal')
|
|
543
|
+
task.workflow.paused = input.kind === 'close';
|
|
544
|
+
}
|
|
545
|
+
else if (input.kind === 'coordinate') {
|
|
546
|
+
if (task.workflow?.kind !== 'goal' || !sameMember(actor, task.coordinator) || !content)
|
|
547
|
+
throw new CollaborationError('COORDINATOR_REQUIRED', '请由目标协调者提交本次协调说明', 403);
|
|
548
|
+
this.event(task, 'coordinate', actor, content);
|
|
549
|
+
task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: task.events.at(-1).sequence, notifiedAt: Date.now() };
|
|
550
|
+
delete task.automationIssue;
|
|
551
|
+
}
|
|
552
|
+
else if (input.kind === 'pause' || input.kind === 'resume' || input.kind === 'retry') {
|
|
553
|
+
if (!task.workflow)
|
|
554
|
+
throw new CollaborationError('WORKFLOW_REQUIRED', '此任务未启用自动协作');
|
|
555
|
+
if (input.kind !== 'retry')
|
|
556
|
+
task.workflow.paused = input.kind === 'pause';
|
|
557
|
+
delete task.automationIssue;
|
|
558
|
+
this.event(task, input.kind, actor, content || (input.kind === 'pause' ? '暂停后续自动安排,已写入终端的工作继续保留' : '继续推进自动协作'));
|
|
559
|
+
if (input.kind === 'retry' && task.coordination)
|
|
560
|
+
task.coordination.notifiedSequence = 0;
|
|
561
|
+
if (input.kind === 'retry')
|
|
562
|
+
for (const outbox of doc.outbox.filter(o => o.taskId === task.id)) {
|
|
563
|
+
const receipt = task.deliveries.find(d => d.id === outbox.id);
|
|
564
|
+
if (receipt && ['failed', 'expired'].includes(receipt.status)) {
|
|
565
|
+
outbox.id = id();
|
|
566
|
+
delete outbox.messageId;
|
|
567
|
+
}
|
|
568
|
+
delete outbox.lastError;
|
|
569
|
+
delete outbox.nextRetryAt;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
else
|
|
573
|
+
throw new CollaborationError('INVALID_TASK_OPERATION', '不支持的任务操作');
|
|
574
|
+
if (['report', 'ask', 'answer', 'submit-plan', 'review', 'accept', 'revise', 'close', 'reopen'].includes(input.kind))
|
|
575
|
+
this.notifyCoordinator(doc, task, task.events.at(-1));
|
|
576
|
+
if (task.events.length > 1000 || task.artifacts.length > 200 || task.attempts.length > 100)
|
|
577
|
+
throw new CollaborationError('TASK_HISTORY_LIMIT', '此任务历史达到上限,请建立后续任务并保留本任务为历史', 409);
|
|
578
|
+
task.revision++;
|
|
579
|
+
task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
|
|
580
|
+
if (task.workflow && input.kind !== 'coordinate')
|
|
581
|
+
this.touchParent(doc, task, `${input.kind}${input.status ? ` / ${input.status}` : ''}:${content.slice(0, 1500)}`);
|
|
582
|
+
if (task.workflow && ['blocked', 'failed'].includes(input.status ?? ''))
|
|
583
|
+
task.automationIssue = content.slice(0, 1000);
|
|
584
|
+
if (task.workflow && input.kind === 'report' && ['ack', 'working', 'complete'].includes(input.status ?? ''))
|
|
585
|
+
delete task.automationIssue;
|
|
586
|
+
doc.requests[request.key] = { hash: request.hash, taskId };
|
|
587
|
+
return task;
|
|
588
|
+
});
|
|
589
|
+
}
|
|
590
|
+
touchParent(doc, task, content) {
|
|
591
|
+
const root = task.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
|
|
592
|
+
if (!root || root.status !== 'open')
|
|
593
|
+
return;
|
|
594
|
+
this.event(root, 'child-update', null, `子任务「${task.title}」(${task.id}):${content}`);
|
|
595
|
+
const result = root.artifacts.filter(a => a.kind === 'result' && a.attemptId === root.activeAttemptId).at(-1);
|
|
596
|
+
if (result && !root.events.some(e => e.kind === 'revise' && e.attemptId === root.activeAttemptId && e.createdAt >= result.createdAt)) {
|
|
597
|
+
this.event(root, 'revise', null, '子任务在汇总之后发生变化,请重新核对并提交最终结果').source = 'system';
|
|
598
|
+
}
|
|
599
|
+
root.revision++;
|
|
600
|
+
root.updatedAt = Date.now();
|
|
601
|
+
}
|
|
602
|
+
scheduled(owner) {
|
|
603
|
+
return this.owned(owner).filter(t => t.workflow && t.status === 'open' && t.scheduledAssignee && !t.activeAttemptId && !t.automationIssue
|
|
604
|
+
&& !t.workflow.paused && !this.document.tasks.find(r => r.id === t.workflow?.rootTaskId)?.workflow?.paused
|
|
605
|
+
&& this.document.tasks.find(r => r.id === t.workflow?.rootTaskId)?.status === 'open'
|
|
606
|
+
&& !this.planPending(this.document.tasks.find(r => r.id === t.workflow?.rootTaskId))
|
|
607
|
+
&& t.dependsOn.every(id => this.document.tasks.find(d => d.id === id)?.status === 'accepted'));
|
|
608
|
+
}
|
|
609
|
+
planPending(task) {
|
|
610
|
+
const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
|
|
611
|
+
return !!plan && task.approvedPlanArtifactId !== plan.id;
|
|
612
|
+
}
|
|
613
|
+
activateScheduled(taskId, expectedRevision, assignee, workspace) {
|
|
614
|
+
this.transaction(doc => {
|
|
615
|
+
const task = doc.tasks.find(t => t.id === taskId);
|
|
616
|
+
const root = task?.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
|
|
617
|
+
if (!task || task.revision !== expectedRevision || task.activeAttemptId || !task.scheduledAssignee || task.status !== 'open'
|
|
618
|
+
|| task.workflow?.paused || !root || root.status !== 'open' || root.workflow?.paused || this.planPending(root)
|
|
619
|
+
|| task.dependsOn.some(id => doc.tasks.find(t => t.id === id)?.status !== 'accepted'))
|
|
620
|
+
return;
|
|
621
|
+
if (workspace)
|
|
622
|
+
task.workspace = workspace;
|
|
623
|
+
this.assign(doc, task, assignee, task.coordinator, '服务已接续依赖并准备执行');
|
|
624
|
+
delete task.scheduledAssignee;
|
|
625
|
+
task.revision++;
|
|
626
|
+
task.updatedAt = Date.now();
|
|
627
|
+
this.touchParent(doc, task, '已自动分派');
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
automationFailed(taskId, error) {
|
|
631
|
+
if (this.get(taskId)?.automationIssue === error.slice(0, 1000))
|
|
632
|
+
return;
|
|
633
|
+
this.transaction(doc => {
|
|
634
|
+
const task = doc.tasks.find(t => t.id === taskId);
|
|
635
|
+
if (!task)
|
|
636
|
+
return;
|
|
637
|
+
task.automationIssue = error.slice(0, 1000);
|
|
638
|
+
this.event(task, 'automation-blocked', null, task.automationIssue).source = 'system';
|
|
639
|
+
task.revision++;
|
|
640
|
+
task.updatedAt = Date.now();
|
|
641
|
+
this.touchParent(doc, task, task.automationIssue);
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
nextAction(task, doc) {
|
|
645
|
+
if (!task.workflow || task.status !== 'open' || task.workflow.paused)
|
|
646
|
+
return null;
|
|
647
|
+
const root = task.workflow.rootTaskId && doc.tasks.find(r => r.id === task.workflow.rootTaskId);
|
|
648
|
+
if (root && (root.status !== 'open' || root.workflow?.paused || this.planPending(root)))
|
|
649
|
+
return null;
|
|
650
|
+
const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
|
|
651
|
+
if (result && !task.automationIssue && !task.decisions.some(d => d.status === 'pending')
|
|
652
|
+
&& !task.events.some(e => e.kind === 'revise' && e.attemptId === task.activeAttemptId && e.createdAt >= result.createdAt)) {
|
|
653
|
+
const review = task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result.id).at(-1);
|
|
654
|
+
if (!review && !task.events.some(e => e.kind === 'request-review' && e.artifactId === result.id))
|
|
655
|
+
return 'request-review';
|
|
656
|
+
if (review?.verdict === 'pass' && task.workflow.kind === 'step' && task.dependsOn.every(id => doc.tasks.find(t => t.id === id)?.status === 'accepted'))
|
|
657
|
+
return 'review-passed';
|
|
658
|
+
if (review && ['changes', 'blocked'].includes(review.verdict ?? '') && !task.events.some(e => e.kind === 'review-blocked' && e.artifactId === result.id))
|
|
659
|
+
return 'review-feedback';
|
|
660
|
+
}
|
|
661
|
+
if (task.workflow.kind === 'goal' && task.coordination) {
|
|
662
|
+
if (Date.now() - task.updatedAt >= 1000 && task.events.some(e => e.sequence > task.coordination.notifiedSequence
|
|
663
|
+
&& !['coordinate', 'request-review', 'assigned'].includes(e.kind) && !sameMember(e.actor, task.coordinator)))
|
|
664
|
+
return 'wake';
|
|
665
|
+
if (!task.automationIssue && task.coordination.acknowledgedSequence < task.coordination.notifiedSequence
|
|
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)
|
|
667
|
+
return 'coordination-timeout';
|
|
668
|
+
}
|
|
669
|
+
return null;
|
|
670
|
+
}
|
|
671
|
+
/** Explicit, version-bound transitions; the browser does not drive this loop. */
|
|
672
|
+
advance(owner) {
|
|
673
|
+
if (!this.document.tasks.some(t => t.ownerServiceId === owner && this.nextAction(t, this.document)))
|
|
674
|
+
return;
|
|
675
|
+
this.transaction(doc => {
|
|
676
|
+
for (const task of doc.tasks.filter(t => t.ownerServiceId === owner)) {
|
|
677
|
+
const action = this.nextAction(task, doc);
|
|
678
|
+
if (!action)
|
|
679
|
+
continue;
|
|
680
|
+
const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
|
|
681
|
+
const review = task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result?.id).at(-1);
|
|
682
|
+
const attempt = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
683
|
+
if (action === 'request-review') {
|
|
684
|
+
const reviewers = [task.coordinator, ...task.workflow.reviewers].filter((m) => !!m && !sameMember(m, result.actor));
|
|
685
|
+
const reviewer = reviewers.find(m => m.serviceId === result.actor.serviceId) ?? reviewers[0];
|
|
686
|
+
if (!reviewer)
|
|
687
|
+
task.automationIssue = '没有可用的独立评审成员,请调整协调者或成员后继续';
|
|
688
|
+
else {
|
|
689
|
+
const event = this.event(task, 'request-review', null, '服务自动安排此版本独立评审');
|
|
690
|
+
event.artifactId = result.id;
|
|
691
|
+
event.target = reviewer;
|
|
692
|
+
event.source = 'system';
|
|
693
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: reviewer, kind: 'message', threadId: attempt.threadId,
|
|
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 留下必须处理的原因。只按用户授权运行测试,未执行不能称通过。` });
|
|
695
|
+
}
|
|
696
|
+
this.touchParent(doc, task, '已自动请求独立评审');
|
|
697
|
+
}
|
|
698
|
+
else if (action === 'review-passed') {
|
|
699
|
+
task.status = 'accepted';
|
|
700
|
+
task.acceptedArtifactId = result.id;
|
|
701
|
+
task.completionMode = 'reviewed';
|
|
702
|
+
this.event(task, 'review-passed', review.actor, review.content).source = 'system';
|
|
703
|
+
this.touchParent(doc, task, '独立评审通过,后续依赖可以接续');
|
|
704
|
+
}
|
|
705
|
+
else if (action === 'review-feedback') {
|
|
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;
|
|
708
|
+
const blocked = review.verdict === 'blocked' || rounds >= task.workflow.maxRevisions;
|
|
709
|
+
const event = this.event(task, blocked ? 'review-blocked' : 'revise', null, review.content);
|
|
710
|
+
event.artifactId = result.id;
|
|
711
|
+
event.source = 'system';
|
|
712
|
+
if (blocked)
|
|
713
|
+
task.automationIssue = `${review.verdict === 'blocked' ? '独立评审受阻' : '连续返工需要调整方案'}:${review.content.slice(0, 800)}`;
|
|
714
|
+
else
|
|
715
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: result.actor, kind: 'message', threadId: attempt.threadId,
|
|
716
|
+
content: `任务「${task.title}」独立评审要求修改:\n${review.content}\n请继续完成当前分派并重新 report --status complete;新版本会重新独立评审。任务 ${task.id},尝试 ${task.activeAttemptId}。` });
|
|
717
|
+
this.touchParent(doc, task, blocked ? task.automationIssue : '已自动交回修改');
|
|
718
|
+
}
|
|
719
|
+
else if (action === 'wake') {
|
|
720
|
+
const updates = task.events.filter(e => e.sequence > task.coordination.notifiedSequence && e.kind !== 'coordinate');
|
|
721
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: task.coordinator, kind: 'message', threadId: task.id,
|
|
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 的交付没有代码变化,不需要集成;有代码变化才安排必要集成。用户有新要求时继续本目标,交付新版本,不重做已有采样。最终汇总通过评审且无新要求时等待用户验收,不重复提交相同结果。` });
|
|
723
|
+
task.coordination.notifiedSequence = task.events.at(-1).sequence;
|
|
724
|
+
task.coordination.notifiedAt = Date.now();
|
|
725
|
+
}
|
|
726
|
+
else
|
|
727
|
+
task.automationIssue = '协调通知发出后 15 分钟未收到本次协调说明,请查看终端或点击继续协调。';
|
|
728
|
+
task.revision++;
|
|
729
|
+
task.updatedAt = Date.now();
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
merge(task, issuer) {
|
|
734
|
+
validateSnapshot(task);
|
|
735
|
+
if (task.ownerServiceId !== issuer)
|
|
736
|
+
throw new CollaborationError('INVALID_TASK_OWNER', '任务来源身份不符');
|
|
737
|
+
const current = this.get(task.id);
|
|
738
|
+
if (current && current.ownerServiceId !== issuer)
|
|
739
|
+
throw new CollaborationError('TASK_ID_CONFLICT', '任务身份冲突');
|
|
740
|
+
if (current && current.revision >= task.revision)
|
|
741
|
+
return;
|
|
742
|
+
this.transaction(doc => {
|
|
743
|
+
const index = doc.tasks.findIndex(t => t.id === task.id);
|
|
744
|
+
if (index < 0) {
|
|
745
|
+
if (doc.tasks.length >= 2000)
|
|
746
|
+
throw new CollaborationError('TASK_LIMIT', '任务记录达到上限', 409);
|
|
747
|
+
doc.tasks.push(task);
|
|
748
|
+
}
|
|
749
|
+
else
|
|
750
|
+
doc.tasks[index] = task;
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
delivered(outboxId, receipt) {
|
|
754
|
+
if (!receipt || typeof receipt.messageId !== 'string' || !receipt.messageId || receipt.messageId.length > 128
|
|
755
|
+
|| !['pending', 'delivered', 'failed', 'expired'].includes(receipt.status)
|
|
756
|
+
|| receipt.deliveredAt !== null && (!Number.isSafeInteger(receipt.deliveredAt) || receipt.deliveredAt <= 0)
|
|
757
|
+
|| receipt.error !== null && typeof receipt.error !== 'string')
|
|
758
|
+
throw new CollaborationError('INVALID_TASK_RECEIPT', '远端投递凭证无效');
|
|
759
|
+
const current = this.document.outbox.find(o => o.id === outboxId);
|
|
760
|
+
if (!current)
|
|
761
|
+
return;
|
|
762
|
+
const previous = this.document.tasks.find(t => t.id === current.taskId)?.deliveries.find(d => d.id === outboxId);
|
|
763
|
+
if (JSON.stringify(previous) === JSON.stringify({ id: outboxId, attemptId: current.attemptId, kind: current.kind, ...receipt })) {
|
|
764
|
+
current.nextRetryAt = Date.now() + (receipt.status === 'pending' ? 2000 : 30_000);
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
this.transaction(doc => {
|
|
768
|
+
const outbox = doc.outbox.find(o => o.id === outboxId);
|
|
769
|
+
outbox.messageId = receipt.messageId;
|
|
770
|
+
outbox.lastError = receipt.error ?? undefined;
|
|
771
|
+
const task = doc.tasks.find(t => t.id === outbox.taskId);
|
|
772
|
+
const attempt = task.attempts.find(a => a.id === outbox.attemptId);
|
|
773
|
+
const previous = task.deliveries.find(d => d.id === outboxId);
|
|
774
|
+
const delivery = { id: outboxId, attemptId: outbox.attemptId, kind: outbox.kind, ...receipt };
|
|
775
|
+
const changed = JSON.stringify(previous) !== JSON.stringify(delivery);
|
|
776
|
+
if (changed) {
|
|
777
|
+
if (previous)
|
|
778
|
+
Object.assign(previous, delivery);
|
|
779
|
+
else
|
|
780
|
+
task.deliveries.push(delivery);
|
|
781
|
+
task.revision++;
|
|
782
|
+
task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
|
|
783
|
+
}
|
|
784
|
+
// Later feedback has its own receipt; it must not overwrite the initial dispatch.
|
|
785
|
+
if (outbox.kind === 'task') {
|
|
786
|
+
attempt.messageId = receipt.messageId;
|
|
787
|
+
attempt.deliveryStatus = receipt.status;
|
|
788
|
+
attempt.deliveredAt = receipt.deliveredAt;
|
|
789
|
+
attempt.deliveryError = receipt.error;
|
|
790
|
+
}
|
|
791
|
+
if (receipt.status === 'delivered' || !task.workflow && ['failed', 'expired'].includes(receipt.status))
|
|
792
|
+
doc.outbox = doc.outbox.filter(o => o.id !== outboxId);
|
|
793
|
+
else
|
|
794
|
+
outbox.nextRetryAt = Date.now() + (receipt.status === 'pending' ? 2000 : 30_000);
|
|
795
|
+
if (task.workflow && ['failed', 'expired'].includes(receipt.status)) {
|
|
796
|
+
task.automationIssue = `消息投递${receipt.status === 'expired' ? '过期' : '失败'}:${receipt.error ?? '请检查成员终端后重试'}`;
|
|
797
|
+
this.touchParent(doc, task, task.automationIssue);
|
|
798
|
+
}
|
|
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)))
|
|
802
|
+
delete task.automationIssue;
|
|
803
|
+
});
|
|
804
|
+
}
|
|
805
|
+
failed(outboxId, error) {
|
|
806
|
+
const current = this.document.outbox.find(o => o.id === outboxId);
|
|
807
|
+
if (!current)
|
|
808
|
+
return;
|
|
809
|
+
if (current.lastError === error.slice(0, 500)) {
|
|
810
|
+
current.nextRetryAt = Date.now() + 10_000;
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
this.transaction(doc => {
|
|
814
|
+
const outbox = doc.outbox.find(o => o.id === outboxId);
|
|
815
|
+
const changed = outbox.lastError !== error.slice(0, 500);
|
|
816
|
+
outbox.lastError = error.slice(0, 500);
|
|
817
|
+
outbox.nextRetryAt = Date.now() + 10_000;
|
|
818
|
+
const task = doc.tasks.find(t => t.id === outbox.taskId);
|
|
819
|
+
if (changed) {
|
|
820
|
+
const previous = task.deliveries.find(d => d.id === outboxId);
|
|
821
|
+
const record = { id: outboxId, attemptId: outbox.attemptId, kind: outbox.kind, messageId: outbox.messageId ?? '', status: 'retrying', deliveredAt: null, error: outbox.lastError };
|
|
822
|
+
if (previous)
|
|
823
|
+
Object.assign(previous, record);
|
|
824
|
+
else
|
|
825
|
+
task.deliveries.push(record);
|
|
826
|
+
task.revision++;
|
|
827
|
+
task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
|
|
828
|
+
if (task.workflow && !task.automationIssue) {
|
|
829
|
+
task.automationIssue = `消息投递正在重试:${outbox.lastError}`;
|
|
830
|
+
this.touchParent(doc, task, task.automationIssue);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|