termdock 1.4.294 → 1.4.296
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/App-BiLjlrk-.js +171 -0
- 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-D2hm-GX-.js → ChangeWalkthroughPanel-DXprT-7m.js} +1 -1
- 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-DLmKJylJ.js → DagPlayground-DZcUunT5.js} +1 -1
- 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-Bje07ujV.js → DiffLab-Brv4DCTb.js} +1 -1
- 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-CcLHnQu-.js → DiffReview-BuLu1qCi.js} +1 -1
- 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-CIieaScN.js → DiffReviewLab-DSGamuAD.js} +1 -1
- 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-0nWShFhC.js → DiffViewer-BUCYxg27.js} +1 -1
- 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-B2Hrizox.js → ModelPreview-Bnlo-oVE.js} +1 -1
- 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-CiYxOpjs.js → RightSidebar-r_3Lk5Rp.js} +48 -52
- 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-BvhG-Int.js → abnfDiagram-VCTEODGH-K6Ppkrbt.js} +1 -1
- 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 +35 -0
- 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-Cfd1r0w4.js → arc-Co2OQqtH.js} +1 -1
- 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-CYcaAY_y.js → architectureDiagram-5GKGNRK7-BQnl7tVF.js} +1 -1
- 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-uGZowYBC.js → blockDiagram-I7D4REHJ-Bm5D5-qq.js} +1 -1
- 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-BuAjHXw2.js → c4Diagram-7LVT6UL2-B9oPpey8.js} +1 -1
- 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 +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-DpblcTaf.js → chunk-2Q5K7J3B-TMaWHH0U.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-9DQ9Xhrj.js → chunk-5VM5RSS4-CH7XpknV.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-C99DgtYa.js → chunk-F27PBJKO-CJ1rotBe.js} +1 -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-CJs2LWtE.js → chunk-IMKFNOWR-UYKZ5uOG.js} +1 -1
- 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-JWPE2WC7-Bi6NDDYZ.js → chunk-JWPE2WC7-BYL8aXjQ.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-EDihZ7ei.js → chunk-POPQ4Y6H-C3E8yfU2.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-Deb_Pzwv.js → chunk-SVP7TREG-DCTDYa1E.js} +1 -1
- 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-C6wEoKIF.js → chunk-TICWLB2K-wHiaOsBh.js} +1 -1
- 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/{chunk-XXDRQBXY-DkhA8D3i.js → chunk-XXDRQBXY-D_d479hK.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-BdvOOL9k.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BdvOOL9k.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CAImJ8rh.js → cose-bilkent-JH36ORCC-DnhlPBLr.js} +1 -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-D3Egll3y.js → cynefin-OW5HDTMX-BsjzNqJZ.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-BsjzNqJZ.js.br +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-D3Egll3y.js.gz → cynefin-OW5HDTMX-BsjzNqJZ.js.gz} +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-MjvCZkx7.js → cynefinDiagram-5FMLGOSQ-sVUb0zY5.js} +1 -1
- 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-CrJn07Be.js → dagre-GXQ25YYZ-YErk8S-c.js} +1 -1
- 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-x2YTsmxU.js → diagram-S7CK7UJ4-BaHbV5ho.js} +1 -1
- 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-D98MzrbS.js → diagram-UQ7AKVKN-BocCYx3V.js} +1 -1
- 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-DB_ACbg-.js → diagram-VSXAHHWV-ZMTYwWOQ.js} +1 -1
- 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-Drt4iWsQ.js → diagram-VX7I27RA-xmk_EY1h.js} +1 -1
- 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-D8mGxerZ.js → diagram-Z3DM3KII-DKDY_cG6.js} +1 -1
- 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-DTYnrl_Q.js → ebnfDiagram-PWID7BFC-C_0z-Jx9.js} +1 -1
- 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-ChsNovQf.js → erDiagram-RLTQ6QDP-DHUaG7mt.js} +1 -1
- 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-DsFiio6_.js → flowDiagram-HODETNUW-BMNsdMBD.js} +1 -1
- 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-BbHyLMqr.js → ganttDiagram-EL5Y4UJY-BH1AiKuF.js} +1 -1
- 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-3rZyzlwR.js → gestureArbiter-DvupkPra.js} +1 -1
- 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-CTPZoYHy.js → gitGraphDiagram-WWUBYQGX-DAgV0MV-.js} +1 -1
- 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-DxhkHH1u.css → index-Bi9A6QUP.css} +1 -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/{infoDiagram-27XIBGKW-DqG9-3Dv.js → infoDiagram-27XIBGKW-C7iSg0cB.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-C7BUr6Q1.js → ishikawaDiagram-5VMMS53U-BpZwsQvw.js} +1 -1
- 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-DeKgSie7.js → journeyDiagram-3NMN7TZE-DXEeFLvw.js} +1 -1
- 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-BXLMRLCs.js → kanban-definition-UXKFOSKX-CV9ivL5u.js} +1 -1
- 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-BIGVsIVB.js → layout-DsKKsJKX.js} +1 -1
- 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-BhqHS_Ls.js → linear-Dl9MDNV3.js} +1 -1
- 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-DYTWj2Pu.js → mermaid.core-CxZFe3as.js} +5 -5
- 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-BKVI5pEJ.js → mindmap-definition-YA3MSWOX-C3ormYdL.js} +2 -2
- 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-DyrBaUP8.js → passwordBootstrap-a8z6jJNY.js} +2 -2
- 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-DdRPwDgf.js → pegDiagram-XKGWAZYB-CG0wNylH.js} +1 -1
- 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-KzblsFOl.js → pieDiagram-E7YTZNPT-D2-ydqyX.js} +1 -1
- 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-Co2Mu1vo.js → quadrantDiagram-AXDQQJYC-CK3fRC18.js} +1 -1
- 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-DjvJ0Ysn.js → railroadDiagram-O6MQD6OU-V4EFlydu.js} +1 -1
- 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-VL4F6lii.js → referenceLongPress-BjmYd1vB.js} +1 -1
- 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-QJ6afxsR.js → requirementDiagram-BXWQKSXE-Di1_tyAX.js} +1 -1
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-QJ6afxsR.js.br → 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-CYd9OO8p.js → sankeyDiagram-P5KCCOFB-D-qpWNza.js} +1 -1
- 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-Cau471RQ.js → sequenceDiagram-WJ2MYXX4-DmIxafrB.js} +1 -1
- 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-CWBvI9Ex.js → sizeCapture-INFHLROL-4MTh3tTt.js} +1 -1
- 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/{sparkles-BpYEa9ns.js → sparkles-Dk93lHg0.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-ih47Vg8d.js → stateDiagram-D77RDMKH-CgYgneIv.js} +1 -1
- 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 +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-Cks23MF4.js → swimlanes-42K2YHIH-OWA9HFVf.js} +1 -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 +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-C3Z6mGRy.js → timeline-definition-24CTP7MA-BAzx5DyO.js} +1 -1
- 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/{triangle-alert-onS3H-d9.js → triangle-alert-CUIBOwqW.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-9GLhPNes.js → vennDiagram-4TSXK5OY-CBZ-PhTQ.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-9GLhPNes.js.br → 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-KoXLuxZT.js → videoCompression-BCnqCkRK.js} +1 -1
- 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-BsZeAlBb.js → wardleyDiagram-VM6X3IG4-DbWzC6l5.js} +1 -1
- 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-BRT0eEuC.js → xychartDiagram-S5SC5T6Z-BmApnTWE.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmApnTWE.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BmApnTWE.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/collaborationPeerTransport.js +8 -2
- package/dist/server/agent/collaborationRoutes.js +18 -2
- package/dist/server/agent/collaborationService.js +43 -1
- 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 +773 -0
- package/dist/server/agent/collaborationTaskTypes.js +1 -0
- package/dist/server/agent/collaborationTaskWorkspace.js +100 -0
- package/dist/server/entry.js +11 -1
- package/dist/server/federation/runtime.js +6 -1
- package/dist/server/routes/terminal.js +66 -2
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-CDGrOo_r.js +0 -158
- package/dist/client/assets/App-CDGrOo_r.js.br +0 -0
- package/dist/client/assets/App-CDGrOo_r.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-D2hm-GX-.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-D2hm-GX-.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DLmKJylJ.js.br +0 -0
- package/dist/client/assets/DagPlayground-DLmKJylJ.js.gz +0 -0
- package/dist/client/assets/DiffLab-Bje07ujV.js.br +0 -0
- package/dist/client/assets/DiffLab-Bje07ujV.js.gz +0 -0
- package/dist/client/assets/DiffReview-CcLHnQu-.js.br +0 -0
- package/dist/client/assets/DiffReview-CcLHnQu-.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CIieaScN.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CIieaScN.js.gz +0 -0
- package/dist/client/assets/DiffViewer-0nWShFhC.js.br +0 -0
- package/dist/client/assets/DiffViewer-0nWShFhC.js.gz +0 -0
- package/dist/client/assets/ModelPreview-B2Hrizox.js.br +0 -0
- package/dist/client/assets/ModelPreview-B2Hrizox.js.gz +0 -0
- package/dist/client/assets/RightSidebar-CiYxOpjs.js.br +0 -0
- package/dist/client/assets/RightSidebar-CiYxOpjs.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BvhG-Int.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BvhG-Int.js.gz +0 -0
- package/dist/client/assets/app-CjvwGDVy.js +0 -35
- package/dist/client/assets/app-CjvwGDVy.js.br +0 -0
- package/dist/client/assets/app-CjvwGDVy.js.gz +0 -0
- package/dist/client/assets/arc-Cfd1r0w4.js.br +0 -0
- package/dist/client/assets/arc-Cfd1r0w4.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CYcaAY_y.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CYcaAY_y.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-uGZowYBC.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-uGZowYBC.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BuAjHXw2.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BuAjHXw2.js.gz +0 -0
- package/dist/client/assets/channel-klM1FsDR.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-C99DgtYa.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-C99DgtYa.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CJs2LWtE.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CJs2LWtE.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Deb_Pzwv.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Deb_Pzwv.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-C6wEoKIF.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-C6wEoKIF.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-Ccewxt_X.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-Ccewxt_X.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CAImJ8rh.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CAImJ8rh.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-D3Egll3y.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-MjvCZkx7.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-MjvCZkx7.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CrJn07Be.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CrJn07Be.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-x2YTsmxU.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-x2YTsmxU.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-D98MzrbS.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-D98MzrbS.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-DB_ACbg-.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-DB_ACbg-.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Drt4iWsQ.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Drt4iWsQ.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-D8mGxerZ.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-D8mGxerZ.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DTYnrl_Q.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DTYnrl_Q.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-ChsNovQf.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-ChsNovQf.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DsFiio6_.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DsFiio6_.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BbHyLMqr.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BbHyLMqr.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-3rZyzlwR.js.br +0 -0
- package/dist/client/assets/gestureArbiter-3rZyzlwR.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CTPZoYHy.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CTPZoYHy.js.gz +0 -0
- package/dist/client/assets/index-DxhkHH1u.css.br +0 -0
- package/dist/client/assets/index-DxhkHH1u.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C7BUr6Q1.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C7BUr6Q1.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DeKgSie7.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DeKgSie7.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BXLMRLCs.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BXLMRLCs.js.gz +0 -0
- package/dist/client/assets/layout-BIGVsIVB.js.br +0 -0
- package/dist/client/assets/layout-BIGVsIVB.js.gz +0 -0
- package/dist/client/assets/linear-BhqHS_Ls.js.br +0 -0
- package/dist/client/assets/linear-BhqHS_Ls.js.gz +0 -0
- package/dist/client/assets/mermaid.core-DYTWj2Pu.js.br +0 -0
- package/dist/client/assets/mermaid.core-DYTWj2Pu.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BKVI5pEJ.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BKVI5pEJ.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-DyrBaUP8.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-DyrBaUP8.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DdRPwDgf.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DdRPwDgf.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-KzblsFOl.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-KzblsFOl.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-Co2Mu1vo.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-Co2Mu1vo.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DjvJ0Ysn.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DjvJ0Ysn.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-VL4F6lii.js.br +0 -0
- package/dist/client/assets/referenceLongPress-VL4F6lii.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-QJ6afxsR.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CYd9OO8p.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CYd9OO8p.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cau471RQ.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cau471RQ.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CWBvI9Ex.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CWBvI9Ex.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-ih47Vg8d.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-ih47Vg8d.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-D0ybQxyy.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Cks23MF4.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Cks23MF4.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CTe40kKh.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-C3Z6mGRy.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-C3Z6mGRy.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-9GLhPNes.js.gz +0 -0
- package/dist/client/assets/videoCompression-KoXLuxZT.js.br +0 -0
- package/dist/client/assets/videoCompression-KoXLuxZT.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BsZeAlBb.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BsZeAlBb.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BRT0eEuC.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BRT0eEuC.js.gz +0 -0
|
@@ -0,0 +1,773 @@
|
|
|
1
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
2
|
+
import { readFileSync, writeFileSync, mkdirSync, renameSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
5
|
+
import { taskMemberKey } from './collaborationTaskTypes.js';
|
|
6
|
+
const id = () => randomBytes(16).toString('hex');
|
|
7
|
+
const sameMember = (a, b) => a !== null && b !== null && taskMemberKey(a) === taskMemberKey(b);
|
|
8
|
+
function text(value, name, limit = 32_000, required = false) {
|
|
9
|
+
if (value !== undefined && typeof value !== 'string')
|
|
10
|
+
throw new CollaborationError('INVALID_TASK', `${name}必须是文本`);
|
|
11
|
+
const result = typeof value === 'string' ? value.trim() : '';
|
|
12
|
+
if (Buffer.byteLength(result) > limit || required && !result)
|
|
13
|
+
throw new CollaborationError('INVALID_TASK', `${name}${required ? '不能为空,且' : ''}最多 ${limit} 字节`);
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
function member(value) {
|
|
17
|
+
const m = value;
|
|
18
|
+
if (!m || typeof m.serviceId !== 'string' || !/^12D3KooW[a-zA-Z0-9]{30,60}$/.test(m.serviceId)
|
|
19
|
+
|| typeof m.sessionId !== 'string' || !m.sessionId || m.sessionId.length > 256 || m.sessionId.startsWith('remote:')) {
|
|
20
|
+
throw new CollaborationError('INVALID_MEMBER', '成员身份无效,请重新选择');
|
|
21
|
+
}
|
|
22
|
+
return { serviceId: m.serviceId, sessionId: m.sessionId };
|
|
23
|
+
}
|
|
24
|
+
const stable = (value) => JSON.stringify(value, (_key, v) => v && typeof v === 'object' && !Array.isArray(v)
|
|
25
|
+
? Object.fromEntries(Object.keys(v).sort().map(key => [key, v[key]])) : v);
|
|
26
|
+
function validateSnapshot(task) {
|
|
27
|
+
const validId = (value) => typeof value === 'string' && /^[a-f0-9]{32}$/.test(value);
|
|
28
|
+
const time = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value > 0;
|
|
29
|
+
const reportStatus = (value) => ['ack', 'working', 'blocked', 'complete', 'failed'].includes(String(value));
|
|
30
|
+
const invalid = () => { throw new CollaborationError('INVALID_TASK_SNAPSHOT', '远端任务记录无效'); };
|
|
31
|
+
if (!task || !validId(task.id) || typeof task.groupId !== 'string' || !task.groupId || !time(task.createdAt) || !time(task.updatedAt)
|
|
32
|
+
|| !Number.isSafeInteger(task.revision) || task.revision < 1 || !['open', 'accepted', 'closed'].includes(task.status)
|
|
33
|
+
|| task.activeAttemptId !== null && !validId(task.activeAttemptId) || task.parentTaskId !== null && !validId(task.parentTaskId)
|
|
34
|
+
|| !Array.isArray(task.dependsOn) || task.dependsOn.length > 32 || task.dependsOn.some(v => !validId(v))
|
|
35
|
+
|| !Array.isArray(task.attempts) || task.attempts.length > 100 || !Array.isArray(task.events) || task.events.length > 1000
|
|
36
|
+
|| !Array.isArray(task.decisions) || task.decisions.length > 1000 || !Array.isArray(task.artifacts) || task.artifacts.length > 200
|
|
37
|
+
|| !Array.isArray(task.deliveries) || task.deliveries.length > 2000 || Buffer.byteLength(JSON.stringify(task)) > 512_000)
|
|
38
|
+
invalid();
|
|
39
|
+
text(task.title, '标题', 640, true);
|
|
40
|
+
text(task.spec, '任务内容', 32_000, true);
|
|
41
|
+
text(task.constraints, '约束');
|
|
42
|
+
text(task.acceptance, '完成标准');
|
|
43
|
+
member({ serviceId: task.ownerServiceId, sessionId: 'validation' });
|
|
44
|
+
if (task.coordinator !== null)
|
|
45
|
+
member(task.coordinator);
|
|
46
|
+
if (task.workflow) {
|
|
47
|
+
if (!['goal', 'step'].includes(task.workflow.kind) || typeof task.workflow.paused !== 'boolean' || typeof task.workflow.isolated !== 'boolean'
|
|
48
|
+
|| !Array.isArray(task.workflow.reviewers) || task.workflow.reviewers.length > 32 || !Number.isInteger(task.workflow.maxRevisions)
|
|
49
|
+
|| task.workflow.maxRevisions < 1 || task.workflow.maxRevisions > 10 || task.workflow.rootTaskId && !validId(task.workflow.rootTaskId))
|
|
50
|
+
invalid();
|
|
51
|
+
if (task.workflow.maxParallel !== undefined && (!Number.isInteger(task.workflow.maxParallel) || task.workflow.maxParallel < 1 || task.workflow.maxParallel > 4))
|
|
52
|
+
invalid();
|
|
53
|
+
task.workflow.reviewers.forEach(member);
|
|
54
|
+
}
|
|
55
|
+
if (task.scheduledAssignee)
|
|
56
|
+
member(task.scheduledAssignee);
|
|
57
|
+
if (task.workspace && [task.workspace.cwd, task.workspace.repository, task.workspace.branch, task.workspace.base].some(v => typeof v !== 'string' || !v || v.length > 4096))
|
|
58
|
+
invalid();
|
|
59
|
+
for (const attempt of task.attempts) {
|
|
60
|
+
if (!attempt || !validId(attempt.id) || !validId(attempt.threadId) || !time(attempt.createdAt))
|
|
61
|
+
invalid();
|
|
62
|
+
member(attempt.assignee);
|
|
63
|
+
if (attempt.report && (!reportStatus(attempt.report.status) || !time(attempt.report.createdAt) || typeof attempt.report.content !== 'string'))
|
|
64
|
+
invalid();
|
|
65
|
+
}
|
|
66
|
+
if (task.activeAttemptId && !task.attempts.some(a => a.id === task.activeAttemptId))
|
|
67
|
+
invalid();
|
|
68
|
+
for (const event of task.events) {
|
|
69
|
+
if (!event || !validId(event.id) || !Number.isSafeInteger(event.sequence) || event.sequence < 1 || typeof event.kind !== 'string'
|
|
70
|
+
|| typeof event.content !== 'string' || !time(event.createdAt) || event.attemptId !== null && !task.attempts.some(a => a.id === event.attemptId))
|
|
71
|
+
invalid();
|
|
72
|
+
if (event.actor !== null)
|
|
73
|
+
member(event.actor);
|
|
74
|
+
if (event.target)
|
|
75
|
+
member(event.target);
|
|
76
|
+
}
|
|
77
|
+
for (const decision of task.decisions) {
|
|
78
|
+
if (!decision || !validId(decision.id) || typeof decision.question !== 'string' || !time(decision.createdAt)
|
|
79
|
+
|| !task.attempts.some(a => a.id === decision.attemptId) || !['pending', 'answered', 'superseded'].includes(decision.status)
|
|
80
|
+
|| !Array.isArray(decision.options) || decision.options.length > 8 || decision.options.some(o => typeof o !== 'string')
|
|
81
|
+
|| decision.status === 'answered' && (typeof decision.answer !== 'string' || !time(decision.answeredAt)))
|
|
82
|
+
invalid();
|
|
83
|
+
}
|
|
84
|
+
for (const artifact of task.artifacts) {
|
|
85
|
+
if (!artifact || !validId(artifact.id) || !task.attempts.some(a => a.id === artifact.attemptId) || !['plan', 'result', 'review'].includes(artifact.kind)
|
|
86
|
+
|| typeof artifact.content !== 'string' || !time(artifact.createdAt))
|
|
87
|
+
invalid();
|
|
88
|
+
member(artifact.actor);
|
|
89
|
+
if (artifact.verdict && !['pass', 'changes', 'blocked'].includes(artifact.verdict))
|
|
90
|
+
invalid();
|
|
91
|
+
}
|
|
92
|
+
for (const delivery of task.deliveries)
|
|
93
|
+
if (!delivery || !validId(delivery.id) || typeof delivery.messageId !== 'string'
|
|
94
|
+
|| typeof delivery.status !== 'string' || delivery.error !== null && typeof delivery.error !== 'string'
|
|
95
|
+
|| delivery.attemptId !== null && !task.attempts.some(a => a.id === delivery.attemptId))
|
|
96
|
+
invalid();
|
|
97
|
+
}
|
|
98
|
+
/** Atomic document writes keep decisions and their outgoing messages in one transaction. */
|
|
99
|
+
export class CollaborationTaskStore {
|
|
100
|
+
file;
|
|
101
|
+
document = { version: 1, tasks: [], outbox: [], requests: {} };
|
|
102
|
+
constructor(file) {
|
|
103
|
+
this.file = file;
|
|
104
|
+
try {
|
|
105
|
+
const doc = JSON.parse(readFileSync(file, 'utf8'));
|
|
106
|
+
if (doc.version !== 1 || !Array.isArray(doc.tasks) || !Array.isArray(doc.outbox) || !doc.requests)
|
|
107
|
+
throw new Error('任务记录格式无效');
|
|
108
|
+
this.document = doc;
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error.code !== 'ENOENT')
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
transaction(mutate) {
|
|
116
|
+
const next = structuredClone(this.document);
|
|
117
|
+
const result = mutate(next);
|
|
118
|
+
if (next.tasks.some(task => Buffer.byteLength(JSON.stringify(task)) > 512_000))
|
|
119
|
+
throw new CollaborationError('TASK_HISTORY_LIMIT', '此任务记录达到 500 KiB,请建立后续任务并保留本任务为历史', 409);
|
|
120
|
+
const serialized = JSON.stringify(next);
|
|
121
|
+
if (Buffer.byteLength(serialized) > 64 * 1024 * 1024)
|
|
122
|
+
throw new CollaborationError('TASK_STORAGE_FULL', '任务记录已达到保存上限,请导出历史后整理', 409);
|
|
123
|
+
mkdirSync(dirname(this.file), { recursive: true, mode: 0o700 });
|
|
124
|
+
const temporary = `${this.file}.${process.pid}.tmp`;
|
|
125
|
+
writeFileSync(temporary, serialized, { mode: 0o600 });
|
|
126
|
+
renameSync(temporary, this.file);
|
|
127
|
+
this.document = next;
|
|
128
|
+
return structuredClone(result);
|
|
129
|
+
}
|
|
130
|
+
get(taskId) { return structuredClone(this.document.tasks.find(t => t.id === taskId) ?? null); }
|
|
131
|
+
owned(owner) { return structuredClone(this.document.tasks.filter(t => t.ownerServiceId === owner)); }
|
|
132
|
+
children(taskId) {
|
|
133
|
+
return structuredClone(this.document.tasks.filter(t => t.parentTaskId === taskId || t.workflow?.rootTaskId === taskId)
|
|
134
|
+
.map(({ id, title, status, revision, completionMode, workspace }) => ({ id, title, status, revision, completionMode, workspace })));
|
|
135
|
+
}
|
|
136
|
+
list(groupIds, summary = false) {
|
|
137
|
+
const tasks = this.document.tasks.filter(t => groupIds.includes(t.groupId)).sort((a, b) => b.updatedAt - a.updatedAt);
|
|
138
|
+
return structuredClone(tasks.map(task => {
|
|
139
|
+
if (!summary)
|
|
140
|
+
return task;
|
|
141
|
+
const current = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
142
|
+
const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
|
|
143
|
+
const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
|
|
144
|
+
return { ...task, spec: task.spec.slice(0, 512), constraints: '', acceptance: '',
|
|
145
|
+
deliveries: task.deliveries.filter(d => d.attemptId === task.activeAttemptId && d.error).slice(-1),
|
|
146
|
+
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 })),
|
|
148
|
+
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: '' })) };
|
|
150
|
+
}));
|
|
151
|
+
}
|
|
152
|
+
heads(groupIds, owner) { return this.document.tasks.filter(t => groupIds.includes(t.groupId) && t.ownerServiceId === owner).map(t => ({ id: t.id, revision: t.revision })); }
|
|
153
|
+
pending(taskId, dueOnly = false) {
|
|
154
|
+
return structuredClone(this.document.outbox
|
|
155
|
+
.filter(o => (!taskId || o.taskId === taskId) && (!dueOnly || (o.nextRetryAt ?? 0) <= Date.now())));
|
|
156
|
+
}
|
|
157
|
+
event(task, kind, actor, content, attemptId = task.activeAttemptId) {
|
|
158
|
+
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) };
|
|
159
|
+
task.events.push(event);
|
|
160
|
+
return event;
|
|
161
|
+
}
|
|
162
|
+
request(doc, actor, key, payload) {
|
|
163
|
+
if (typeof key !== 'string' || !key.trim() || key.length > 256)
|
|
164
|
+
throw new CollaborationError('INVALID_IDEMPOTENCY_KEY', '需要有效的请求幂等键');
|
|
165
|
+
const scoped = `${actor ? taskMemberKey(actor) : 'user'}:${key}`;
|
|
166
|
+
const hash = createHash('sha256').update(stable(payload)).digest('hex');
|
|
167
|
+
const previous = doc.requests[scoped];
|
|
168
|
+
if (previous && previous.hash !== hash)
|
|
169
|
+
throw new CollaborationError('IDEMPOTENCY_CONFLICT', '该请求已用于不同内容;请保留原请求或重新开始', 409);
|
|
170
|
+
return { key: scoped, hash, previous: previous ? doc.tasks.find(t => t.id === previous.taskId) : undefined };
|
|
171
|
+
}
|
|
172
|
+
assign(doc, task, assignee, actor, content) {
|
|
173
|
+
doc.outbox = doc.outbox.filter(o => o.taskId !== task.id || !!o.messageId);
|
|
174
|
+
for (const decision of task.decisions)
|
|
175
|
+
if (decision.status === 'pending')
|
|
176
|
+
decision.status = 'superseded';
|
|
177
|
+
const attemptId = id(), threadId = id();
|
|
178
|
+
task.activeAttemptId = attemptId;
|
|
179
|
+
task.attempts.push({ id: attemptId, assignee: member(assignee), createdAt: Date.now(), threadId });
|
|
180
|
+
this.event(task, 'assigned', actor, content || '任务已分派');
|
|
181
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId, target: assignee, kind: 'task', threadId,
|
|
182
|
+
content: `任务:${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。如果同一尝试已经交付或任务已归档,不要重复实施。${task.workspace ? `\n代码只能在独立目录 ${task.workspace.cwd} 修改。分支 ${task.workspace.branch}。交付前提交代码,结果说明提交、变更、验证与限制。不能修改原仓库、合并到用户分支或发布。` : ''}${task.workflow?.kind === 'step' ? '\n完成报告会自动请求独立评审;评审要求修改时继续完成,重新提交 complete。只按用户授权运行测试。' : ''}` });
|
|
183
|
+
}
|
|
184
|
+
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 '明确标准'。子任务继承自动评审、隔离目录与协调者。依赖任务用 --depends-on id,id,服务在依赖评审通过后自动分派。保持分工合理,不根据终端输出推断谁有空。\n每次收到明确更新后读取最新记录,处理失败与交接。服务自动安排独立评审、返工和依赖接续;不要重复请求评审或催促正在等待回复的成员。必要的人类决策用 task ask --attempt <本轮id>;方案需要用户确认时提交 task plan,不把普通回复当授权。\n代码任务需创建 --integration 的最终集成子任务,--depends-on 依赖全部实施任务;独立目录会包含依赖的提交。不得在原仓库自动合并或发布。\n结束本次协调用 td collab task coordinate ${task.id} --revision <最新版本> --content '已安排的工作和下一步';有新事件服务会再次唤起你。所有子任务评审通过后报告 complete,汇总交付目录、分支、提交、评审结论和限制,等待用户最终验收。任何异常都要留下明确说明,不伪造完成或进度。`;
|
|
186
|
+
}
|
|
187
|
+
notifyCoordinator(doc, task, update) {
|
|
188
|
+
if (!task.coordinator)
|
|
189
|
+
return;
|
|
190
|
+
if (task.workflow)
|
|
191
|
+
return; // Managed goals are woken once per persisted event set by advance().
|
|
192
|
+
const attempt = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
193
|
+
if (update ? sameMember(task.coordinator, update.actor) : attempt && sameMember(task.coordinator, attempt.assignee))
|
|
194
|
+
return;
|
|
195
|
+
if (update && ['answer', 'revise'].includes(update.kind) && attempt && sameMember(task.coordinator, attempt.assignee))
|
|
196
|
+
return;
|
|
197
|
+
if (update) {
|
|
198
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: attempt?.id ?? null, target: task.coordinator, kind: 'message', threadId: attempt?.threadId ?? task.id,
|
|
199
|
+
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 查看完整原文与当前版本;问题回答与结果验收由用户决定。` });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: attempt?.id ?? null, target: task.coordinator, kind: 'message', threadId: attempt?.threadId ?? task.id,
|
|
203
|
+
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,不推断成员忙闲,不停止其他成员终端。` });
|
|
204
|
+
}
|
|
205
|
+
create(owner, input, actor) {
|
|
206
|
+
const title = text(input.title, '标题', 640, true), spec = text(input.spec, '任务内容', 32_000, true);
|
|
207
|
+
const constraints = text(input.constraints, '约束'), acceptance = text(input.acceptance, '完成标准');
|
|
208
|
+
if (input.dependsOn !== undefined && (!Array.isArray(input.dependsOn) || input.dependsOn.length > 32 || input.dependsOn.some(v => typeof v !== 'string')))
|
|
209
|
+
throw new CollaborationError('INVALID_DEPENDENCIES', '依赖列表无效');
|
|
210
|
+
if (input.coordinator)
|
|
211
|
+
member(input.coordinator);
|
|
212
|
+
if (input.assignee)
|
|
213
|
+
member(input.assignee);
|
|
214
|
+
if (input.managed !== undefined && typeof input.managed !== 'boolean' || input.isolated !== undefined && typeof input.isolated !== 'boolean'
|
|
215
|
+
|| input.integration !== undefined && typeof input.integration !== 'boolean')
|
|
216
|
+
throw new CollaborationError('INVALID_WORKFLOW', '协作模式无效');
|
|
217
|
+
if (input.reviewers && (!Array.isArray(input.reviewers) || input.reviewers.length > 32))
|
|
218
|
+
throw new CollaborationError('INVALID_REVIEWERS', '评审成员列表无效');
|
|
219
|
+
input.reviewers?.forEach(member);
|
|
220
|
+
return this.transaction(doc => {
|
|
221
|
+
const request = this.request(doc, actor, input.idempotencyKey, input);
|
|
222
|
+
if (request.previous)
|
|
223
|
+
return request.previous;
|
|
224
|
+
if (doc.tasks.length >= 2000)
|
|
225
|
+
throw new CollaborationError('TASK_LIMIT', '任务记录达到上限,请导出并整理历史', 409);
|
|
226
|
+
const dependsOn = [...new Set(input.dependsOn ?? [])];
|
|
227
|
+
for (const dependency of [...dependsOn, ...(input.parentTaskId ? [input.parentTaskId] : [])]) {
|
|
228
|
+
if (!doc.tasks.some(t => t.id === dependency && t.groupId === input.groupId))
|
|
229
|
+
throw new CollaborationError('TASK_DEPENDENCY_NOT_FOUND', '父任务或依赖任务不在当前协作组', 404);
|
|
230
|
+
}
|
|
231
|
+
const now = Date.now();
|
|
232
|
+
const parent = doc.tasks.find(t => t.id === input.parentTaskId);
|
|
233
|
+
if (parent?.workflow && actor && !sameMember(actor, parent.coordinator))
|
|
234
|
+
throw new CollaborationError('TASK_PERMISSION_DENIED', '只有协调者可以创建此目标的子任务', 403);
|
|
235
|
+
if (parent?.status !== undefined && parent.status !== 'open')
|
|
236
|
+
throw new CollaborationError('TASK_ARCHIVED', '目标已归档', 409);
|
|
237
|
+
if (parent?.workflow && doc.tasks.filter(t => t.workflow?.rootTaskId === (parent.workflow?.rootTaskId ?? parent.id)).length >= 32)
|
|
238
|
+
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;
|
|
241
|
+
if (workflow?.kind === 'goal' && (!input.coordinator || workflow.reviewers.length < 1))
|
|
242
|
+
throw new CollaborationError('WORKFLOW_MEMBERS_REQUIRED', '请选择协调者和至少一位执行成员', 400);
|
|
243
|
+
if (workflow?.kind === 'goal' && !workflow.reviewers.some(m => !sameMember(m, input.coordinator ?? null)))
|
|
244
|
+
throw new CollaborationError('WORKFLOW_MEMBERS_REQUIRED', '执行成员需要与协调者独立');
|
|
245
|
+
if (workflow && parent && !input.assignee)
|
|
246
|
+
throw new CollaborationError('ASSIGNEE_REQUIRED', '自动协作子任务需要执行成员');
|
|
247
|
+
if (workflow && parent && sameMember(input.assignee ?? null, parent.coordinator))
|
|
248
|
+
throw new CollaborationError('COORDINATOR_IS_NOT_WORKER', '请把实施分派给执行成员,协调者负责组织与评审');
|
|
249
|
+
if (workflow && parent && !workflow.reviewers.some(m => sameMember(m, input.assignee ?? null)))
|
|
250
|
+
throw new CollaborationError('WORKER_TEMPLATE_REQUIRED', '请从此目标的执行成员中选择模板');
|
|
251
|
+
if (workflow?.rootTaskId && doc.tasks.find(t => t.id === workflow.rootTaskId)?.workflow?.paused)
|
|
252
|
+
throw new CollaborationError('WORKFLOW_PAUSED', '目标已暂停,请先继续协调');
|
|
253
|
+
if (workflow?.integration) {
|
|
254
|
+
const required = doc.tasks.filter(t => t.workflow?.rootTaskId === workflow.rootTaskId && !t.workflow?.integration && t.status !== 'closed');
|
|
255
|
+
if (!required.length || required.some(t => !dependsOn.includes(t.id)))
|
|
256
|
+
throw new CollaborationError('INTEGRATION_DEPENDENCIES_REQUIRED', '集成任务必须依赖目标下全部实施子任务');
|
|
257
|
+
}
|
|
258
|
+
const task = { id: id(), ownerServiceId: owner, groupId: input.groupId, title, spec, constraints, acceptance,
|
|
259
|
+
coordinator: parent?.workflow ? parent.coordinator : input.coordinator !== undefined ? input.coordinator : input.parentTaskId ? doc.tasks.find(t => t.id === input.parentTaskId)?.coordinator ?? actor : null,
|
|
260
|
+
parentTaskId: input.parentTaskId ?? null, dependsOn, createdAt: now, updatedAt: now, revision: 1,
|
|
261
|
+
status: 'open', activeAttemptId: null, attempts: [], decisions: [], artifacts: [], events: [], deliveries: [], ...(workflow ? { workflow } : {}) };
|
|
262
|
+
this.event(task, 'created', actor, spec);
|
|
263
|
+
if (parent?.constraints)
|
|
264
|
+
task.constraints = text([parent.constraints, constraints].filter(Boolean).join('\n'), '继承约束');
|
|
265
|
+
if (workflow?.kind === 'goal') {
|
|
266
|
+
this.assign(doc, task, member(input.coordinator), actor, this.goalInstructions(task));
|
|
267
|
+
task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: 0, notifiedAt: now };
|
|
268
|
+
}
|
|
269
|
+
else if (workflow && input.assignee) {
|
|
270
|
+
task.scheduledAssignee = input.assignee;
|
|
271
|
+
this.event(task, 'scheduled', actor, dependsOn.length ? '依赖评审通过后自动分派' : '等待准备执行目录与分派');
|
|
272
|
+
}
|
|
273
|
+
else if (input.assignee) {
|
|
274
|
+
if (dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
|
|
275
|
+
throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收;可以先保存任务,稍后分派', 409);
|
|
276
|
+
this.assign(doc, task, input.assignee, actor, '');
|
|
277
|
+
}
|
|
278
|
+
this.notifyCoordinator(doc, task);
|
|
279
|
+
doc.tasks.push(task);
|
|
280
|
+
this.touchParent(doc, task, '新子任务已创建');
|
|
281
|
+
doc.requests[request.key] = { hash: request.hash, taskId: task.id };
|
|
282
|
+
return task;
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
apply(taskId, input, actor) {
|
|
286
|
+
const content = text(input.content, '内容');
|
|
287
|
+
if (input.verdict !== undefined && !['pass', 'changes', 'blocked'].includes(input.verdict))
|
|
288
|
+
throw new CollaborationError('INVALID_VERDICT', '评审结论无效');
|
|
289
|
+
if (input.evidence !== undefined && Buffer.byteLength(JSON.stringify(input.evidence)) > 16_384)
|
|
290
|
+
throw new CollaborationError('EVIDENCE_TOO_LARGE', '证据最多 16 KiB');
|
|
291
|
+
return this.transaction(doc => {
|
|
292
|
+
const task = doc.tasks.find(t => t.id === taskId);
|
|
293
|
+
if (!task)
|
|
294
|
+
throw new CollaborationError('TASK_NOT_FOUND', '任务不存在或无权查看', 404);
|
|
295
|
+
const request = this.request(doc, actor, input.idempotencyKey, [taskId, input]);
|
|
296
|
+
if (request.previous)
|
|
297
|
+
return request.previous;
|
|
298
|
+
const coordination = actor === null || sameMember(actor, task.coordinator);
|
|
299
|
+
if (['assign', 'coordinator', 'close', 'reopen', 'revise', 'request-review', 'coordinate', 'pause', 'resume', 'retry'].includes(input.kind) && !coordination)
|
|
300
|
+
throw new CollaborationError('TASK_PERMISSION_DENIED', '此操作需要用户或当前协调者', 403);
|
|
301
|
+
if (['answer', 'accept', 'approve-plan'].includes(input.kind) && actor !== null)
|
|
302
|
+
throw new CollaborationError('USER_DECISION_REQUIRED', '回答、方案确认与验收需要用户操作', 403);
|
|
303
|
+
if (!['report', 'ask', 'submit-plan', 'review', 'comment', 'answer'].includes(input.kind) && input.expectedRevision !== task.revision)
|
|
304
|
+
throw new CollaborationError('TASK_CHANGED', '任务已更新,请查看最新记录后重试', 409);
|
|
305
|
+
if (task.status !== 'open' && !['reopen', 'report'].includes(input.kind))
|
|
306
|
+
throw new CollaborationError('TASK_ARCHIVED', '任务已归档;需要继续时请重新打开', 409);
|
|
307
|
+
if (task.workflow && task.status !== 'open' && input.kind !== 'reopen')
|
|
308
|
+
throw new CollaborationError('TASK_ARCHIVED', '自动协作的已完成版本不能追加执行报告,请先重新打开', 409);
|
|
309
|
+
if (task.workflow?.rootTaskId && doc.tasks.find(t => t.id === task.workflow.rootTaskId)?.status !== 'open')
|
|
310
|
+
throw new CollaborationError('GOAL_ARCHIVED', '请先重新打开所属目标,再继续子任务', 409);
|
|
311
|
+
const attempt = task.attempts.find(a => a.id === input.attemptId);
|
|
312
|
+
if (['report', 'ask', 'submit-plan'].includes(input.kind) && (!attempt || !sameMember(actor, attempt.assignee)))
|
|
313
|
+
throw new CollaborationError('ATTEMPT_PERMISSION_DENIED', '只能对自己负责的执行尝试提交报告', 403);
|
|
314
|
+
if (['ask', 'submit-plan'].includes(input.kind) && attempt?.id !== task.activeAttemptId)
|
|
315
|
+
throw new CollaborationError('ATTEMPT_SUPERSEDED', '这轮分派已被替换,不能再请求用户决定', 409);
|
|
316
|
+
if (task.workflow && ['report', 'ask', 'submit-plan'].includes(input.kind) && attempt?.id !== task.activeAttemptId)
|
|
317
|
+
throw new CollaborationError('ATTEMPT_SUPERSEDED', '自动协作只能推进当前分派', 409);
|
|
318
|
+
if (input.kind === 'assign') {
|
|
319
|
+
if (task.workflow?.kind === 'goal' && !sameMember(input.assignee ?? null, task.coordinator))
|
|
320
|
+
throw new CollaborationError('GOAL_COORDINATOR_REQUIRED', '目标由协调者负责,请在子任务中分派实施');
|
|
321
|
+
if (task.dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
|
|
322
|
+
throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收', 409);
|
|
323
|
+
if (task.workflow?.kind === 'step' && task.workflow.isolated) {
|
|
324
|
+
task.scheduledAssignee = member(input.assignee);
|
|
325
|
+
task.activeAttemptId = null;
|
|
326
|
+
delete task.workspace;
|
|
327
|
+
delete task.automationIssue;
|
|
328
|
+
this.event(task, 'scheduled', actor, content || '准备新一轮独立执行目录');
|
|
329
|
+
}
|
|
330
|
+
else
|
|
331
|
+
this.assign(doc, task, member(input.assignee), actor, content);
|
|
332
|
+
this.notifyCoordinator(doc, task);
|
|
333
|
+
}
|
|
334
|
+
else if (input.kind === 'coordinator') {
|
|
335
|
+
if (task.workflow && !input.coordinator)
|
|
336
|
+
throw new CollaborationError('GOAL_COORDINATOR_REQUIRED', '自动协作需要协调者');
|
|
337
|
+
const previousCoordinator = task.coordinator;
|
|
338
|
+
task.coordinator = input.coordinator ? member(input.coordinator) : null;
|
|
339
|
+
this.event(task, 'coordinator', actor, content || '协调者已更新');
|
|
340
|
+
if (task.workflow?.kind === 'goal') {
|
|
341
|
+
this.assign(doc, task, task.coordinator, actor, this.goalInstructions(task));
|
|
342
|
+
task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: 0, notifiedAt: Date.now() };
|
|
343
|
+
task.workflow.reviewers = [...task.workflow.reviewers.filter(m => !sameMember(m, task.coordinator)), ...(previousCoordinator && !sameMember(previousCoordinator, task.coordinator) ? [previousCoordinator] : [])];
|
|
344
|
+
for (const child of doc.tasks.filter(t => t.workflow?.rootTaskId === task.id)) {
|
|
345
|
+
child.coordinator = task.coordinator;
|
|
346
|
+
child.workflow.reviewers = task.workflow.reviewers;
|
|
347
|
+
child.revision++;
|
|
348
|
+
child.updatedAt = Date.now();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
this.notifyCoordinator(doc, task);
|
|
352
|
+
}
|
|
353
|
+
else if (input.kind === 'report') {
|
|
354
|
+
if (!['ack', 'working', 'blocked', 'complete', 'failed'].includes(input.status ?? '') || !content)
|
|
355
|
+
throw new CollaborationError('INVALID_REPORT', '报告需要明确状态和正文');
|
|
356
|
+
if (input.status === 'complete' && task.workflow?.kind === 'goal' && (!doc.tasks.some(t => t.workflow?.rootTaskId === task.id)
|
|
357
|
+
|| doc.tasks.some(t => t.workflow?.rootTaskId === task.id && !['accepted', 'closed'].includes(t.status))))
|
|
358
|
+
throw new CollaborationError('CHILD_TASKS_PENDING', '请先完成全部子任务的独立评审,再汇总交付', 409);
|
|
359
|
+
if (input.status === 'complete' && task.workflow?.kind === 'goal' && task.workflow.isolated) {
|
|
360
|
+
const steps = doc.tasks.filter(t => t.workflow?.rootTaskId === task.id && !t.workflow.integration && t.status !== 'closed');
|
|
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))))
|
|
362
|
+
throw new CollaborationError('INTEGRATION_PENDING', '代码目标需要依赖全部实施任务的集成交付通过评审', 409);
|
|
363
|
+
}
|
|
364
|
+
if (input.status === 'complete') {
|
|
365
|
+
const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
|
|
366
|
+
if (plan && task.approvedPlanArtifactId !== plan.id)
|
|
367
|
+
throw new CollaborationError('PLAN_PENDING', '方案尚未由用户确认,请先等待决定', 409);
|
|
368
|
+
}
|
|
369
|
+
attempt.report = { status: input.status, content, evidence: input.evidence, createdAt: Date.now() };
|
|
370
|
+
const event = this.event(task, 'report', actor, content, attempt.id);
|
|
371
|
+
event.reportStatus = input.status;
|
|
372
|
+
event.evidence = input.evidence;
|
|
373
|
+
if (input.status === 'complete')
|
|
374
|
+
task.artifacts.push({ id: id(), attemptId: attempt.id, kind: 'result', content, evidence: input.evidence, actor: actor, createdAt: event.createdAt });
|
|
375
|
+
}
|
|
376
|
+
else if (input.kind === 'ask') {
|
|
377
|
+
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)))
|
|
378
|
+
throw new CollaborationError('INVALID_QUESTION', '问题正文与选项无效');
|
|
379
|
+
task.decisions.push({ id: id(), attemptId: attempt.id, question: content, options: input.options ?? [], status: 'pending', createdAt: Date.now() });
|
|
380
|
+
this.event(task, 'question', actor, content, attempt.id);
|
|
381
|
+
}
|
|
382
|
+
else if (input.kind === 'answer') {
|
|
383
|
+
const decision = task.decisions.find(d => d.id === input.decisionId);
|
|
384
|
+
if (!decision || decision.status !== 'pending' || decision.attemptId !== task.activeAttemptId)
|
|
385
|
+
throw new CollaborationError('QUESTION_SETTLED', '该问题已回答或已不再适用,请查看最新记录', 409);
|
|
386
|
+
if (!content)
|
|
387
|
+
throw new CollaborationError('INVALID_ANSWER', '答案不能为空');
|
|
388
|
+
decision.status = 'answered';
|
|
389
|
+
decision.answer = content;
|
|
390
|
+
decision.answeredAt = Date.now();
|
|
391
|
+
this.event(task, 'answer', null, content, decision.attemptId);
|
|
392
|
+
const recipient = task.attempts.find(a => a.id === decision.attemptId);
|
|
393
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: recipient.id, target: recipient.assignee, kind: 'message', threadId: recipient.threadId,
|
|
394
|
+
content: `任务「${task.title}」问题已回答\n问题:${decision.question}\n用户回答:${content}\n任务 ID:${task.id}\n尝试 ID:${recipient.id}` });
|
|
395
|
+
}
|
|
396
|
+
else if (input.kind === 'submit-plan' || input.kind === 'review') {
|
|
397
|
+
if (!content || !actor)
|
|
398
|
+
throw new CollaborationError('INVALID_ARTIFACT', '交付物需要成员身份和正文');
|
|
399
|
+
const reviewed = input.kind === 'review' ? task.artifacts.find(a => a.id === input.artifactId && a.kind !== 'review') : undefined;
|
|
400
|
+
if (input.kind === 'review' && (!reviewed || sameMember(actor, reviewed.actor)))
|
|
401
|
+
throw new CollaborationError('INVALID_REVIEW', '独立评审需要选择另一成员提交的明确版本');
|
|
402
|
+
if (input.kind === 'review' && task.workflow) {
|
|
403
|
+
const request = task.events.filter(e => e.kind === 'request-review' && e.artifactId === reviewed?.id).at(-1);
|
|
404
|
+
if (!request?.target || !sameMember(actor, request.target))
|
|
405
|
+
throw new CollaborationError('REVIEW_PERMISSION_DENIED', '只有此版本指定的独立评审者可以推进自动流程', 403);
|
|
406
|
+
if (!['pass', 'changes', 'blocked'].includes(input.verdict ?? ''))
|
|
407
|
+
throw new CollaborationError('REVIEW_VERDICT_REQUIRED', '自动评审需要 --verdict pass|changes|blocked');
|
|
408
|
+
if (reviewed?.attemptId !== task.activeAttemptId || task.artifacts.filter(a => a.kind === reviewed.kind && a.attemptId === task.activeAttemptId).at(-1)?.id !== reviewed.id)
|
|
409
|
+
throw new CollaborationError('RESULT_CHANGED', '请评审当前分派的最新版本', 409);
|
|
410
|
+
}
|
|
411
|
+
const artifactAttempt = reviewed?.attemptId ?? attempt.id;
|
|
412
|
+
task.artifacts.push({ id: id(), attemptId: artifactAttempt, kind: input.kind === 'review' ? 'review' : 'plan', content, evidence: input.evidence,
|
|
413
|
+
actor, createdAt: Date.now(), ...(reviewed ? { reviewsArtifactId: reviewed.id, ...(input.verdict ? { verdict: input.verdict } : {}) } : {}) });
|
|
414
|
+
this.event(task, input.kind, actor, content, artifactAttempt);
|
|
415
|
+
}
|
|
416
|
+
else if (input.kind === 'request-review' || input.kind === 'approve-plan') {
|
|
417
|
+
const artifact = task.artifacts.find(a => a.id === input.artifactId && a.kind !== 'review' && a.attemptId === task.activeAttemptId);
|
|
418
|
+
if (!artifact)
|
|
419
|
+
throw new CollaborationError('ARTIFACT_CHANGED', '交付物已不属于当前分派', 409);
|
|
420
|
+
const recipient = input.kind === 'request-review' ? member(input.assignee) : artifact.actor;
|
|
421
|
+
if (input.kind === 'request-review' && sameMember(recipient, artifact.actor))
|
|
422
|
+
throw new CollaborationError('INVALID_REVIEW', '请选择独立评审成员');
|
|
423
|
+
if (input.kind === 'approve-plan') {
|
|
424
|
+
if (artifact.kind !== 'plan' || task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1)?.id !== artifact.id)
|
|
425
|
+
throw new CollaborationError('PLAN_CHANGED', '请选择本轮最新方案确认', 409);
|
|
426
|
+
task.approvedPlanArtifactId = artifact.id;
|
|
427
|
+
}
|
|
428
|
+
const event = this.event(task, input.kind, actor, content || (input.kind === 'request-review' ? '已请求独立评审' : '用户确认了此版本方案'));
|
|
429
|
+
event.artifactId = artifact.id;
|
|
430
|
+
event.target = recipient;
|
|
431
|
+
const threadId = task.attempts.find(a => a.id === task.activeAttemptId).threadId;
|
|
432
|
+
doc.outbox.push({ id: id(), taskId, attemptId: task.activeAttemptId, target: recipient, kind: 'message', threadId,
|
|
433
|
+
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 '评审结论' 提交。`
|
|
434
|
+
: `任务「${task.title}」方案 ${artifact.id} 已由用户确认。\n${content}\n任务 ID:${task.id}` });
|
|
435
|
+
}
|
|
436
|
+
else if (input.kind === 'accept') {
|
|
437
|
+
if (task.workflow?.kind === 'goal' && doc.tasks.some(t => t.workflow?.rootTaskId === task.id && !['accepted', 'closed'].includes(t.status)))
|
|
438
|
+
throw new CollaborationError('CHILD_TASKS_PENDING', '子任务尚未全部完成', 409);
|
|
439
|
+
if (task.workflow?.kind === 'goal' && task.workflow.isolated) {
|
|
440
|
+
const steps = doc.tasks.filter(t => t.workflow?.rootTaskId === task.id && !t.workflow.integration && t.status !== 'closed');
|
|
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))))
|
|
442
|
+
throw new CollaborationError('INTEGRATION_PENDING', '需要全部实施任务的集成交付通过评审');
|
|
443
|
+
}
|
|
444
|
+
if (task.dependsOn.some(dep => doc.tasks.find(t => t.id === dep)?.status !== 'accepted'))
|
|
445
|
+
throw new CollaborationError('DEPENDENCY_PENDING', '依赖任务尚未验收', 409);
|
|
446
|
+
if (task.decisions.some(d => d.status === 'pending'))
|
|
447
|
+
throw new CollaborationError('QUESTION_PENDING', '请先回答本轮待处理问题,再验收结果', 409);
|
|
448
|
+
const artifact = task.artifacts.find(a => a.id === input.artifactId && a.kind === 'result' && a.attemptId === task.activeAttemptId);
|
|
449
|
+
const latest = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
|
|
450
|
+
if (!artifact || latest?.id !== artifact.id)
|
|
451
|
+
throw new CollaborationError('RESULT_CHANGED', '请选择本轮最新结果验收', 409);
|
|
452
|
+
if (task.events.some(e => e.kind === 'revise' && e.attemptId === task.activeAttemptId && e.createdAt >= artifact.createdAt))
|
|
453
|
+
throw new CollaborationError('REVISION_PENDING', '修改要求之后需要提交新结果才能验收', 409);
|
|
454
|
+
if (task.workflow && task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === artifact.id).at(-1)?.verdict !== 'pass')
|
|
455
|
+
throw new CollaborationError('REVIEW_PENDING', '当前结果尚未通过独立评审', 409);
|
|
456
|
+
task.status = 'accepted';
|
|
457
|
+
task.acceptedArtifactId = artifact.id;
|
|
458
|
+
this.event(task, 'accepted', null, content || '用户接受了此版本结果');
|
|
459
|
+
for (const decision of task.decisions)
|
|
460
|
+
if (decision.status === 'pending')
|
|
461
|
+
decision.status = 'superseded';
|
|
462
|
+
}
|
|
463
|
+
else if (input.kind === 'revise' || input.kind === 'comment') {
|
|
464
|
+
if (!content)
|
|
465
|
+
throw new CollaborationError('INVALID_COMMENT', '反馈不能为空');
|
|
466
|
+
this.event(task, input.kind, actor, content);
|
|
467
|
+
const recipient = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
468
|
+
if (recipient && !sameMember(actor, recipient.assignee))
|
|
469
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: recipient.id, target: recipient.assignee,
|
|
470
|
+
kind: 'message', threadId: recipient.threadId, content: `任务「${task.title}」${input.kind === 'revise' ? '要求修改' : '补充反馈'}:\n${content}\n任务 ID:${task.id}\n尝试 ID:${recipient.id}` });
|
|
471
|
+
}
|
|
472
|
+
else if (input.kind === 'close' || input.kind === 'reopen') {
|
|
473
|
+
task.status = input.kind === 'close' ? 'closed' : 'open';
|
|
474
|
+
this.event(task, input.kind, actor, content || (input.kind === 'close' ? '任务已关闭,终端工作未被中止' : '任务已重新打开'));
|
|
475
|
+
if (input.kind === 'close')
|
|
476
|
+
doc.outbox = doc.outbox.filter(o => o.taskId !== task.id || !!o.messageId);
|
|
477
|
+
else
|
|
478
|
+
delete task.acceptedArtifactId;
|
|
479
|
+
if (input.kind === 'close')
|
|
480
|
+
for (const decision of task.decisions)
|
|
481
|
+
if (decision.status === 'pending')
|
|
482
|
+
decision.status = 'superseded';
|
|
483
|
+
if (task.workflow?.kind === 'goal')
|
|
484
|
+
task.workflow.paused = input.kind === 'close';
|
|
485
|
+
}
|
|
486
|
+
else if (input.kind === 'coordinate') {
|
|
487
|
+
if (task.workflow?.kind !== 'goal' || !sameMember(actor, task.coordinator) || !content)
|
|
488
|
+
throw new CollaborationError('COORDINATOR_REQUIRED', '请由目标协调者提交本次协调说明', 403);
|
|
489
|
+
this.event(task, 'coordinate', actor, content);
|
|
490
|
+
task.coordination = { notifiedSequence: task.events.at(-1).sequence, acknowledgedSequence: task.events.at(-1).sequence, notifiedAt: Date.now() };
|
|
491
|
+
delete task.automationIssue;
|
|
492
|
+
}
|
|
493
|
+
else if (input.kind === 'pause' || input.kind === 'resume' || input.kind === 'retry') {
|
|
494
|
+
if (!task.workflow)
|
|
495
|
+
throw new CollaborationError('WORKFLOW_REQUIRED', '此任务未启用自动协作');
|
|
496
|
+
if (input.kind !== 'retry')
|
|
497
|
+
task.workflow.paused = input.kind === 'pause';
|
|
498
|
+
delete task.automationIssue;
|
|
499
|
+
this.event(task, input.kind, actor, content || (input.kind === 'pause' ? '暂停后续自动安排,已写入终端的工作继续保留' : '继续推进自动协作'));
|
|
500
|
+
if (input.kind === 'retry' && task.coordination)
|
|
501
|
+
task.coordination.notifiedSequence = 0;
|
|
502
|
+
if (input.kind === 'retry')
|
|
503
|
+
for (const outbox of doc.outbox.filter(o => o.taskId === task.id)) {
|
|
504
|
+
const receipt = task.deliveries.find(d => d.id === outbox.id);
|
|
505
|
+
if (receipt && ['failed', 'expired'].includes(receipt.status)) {
|
|
506
|
+
outbox.id = id();
|
|
507
|
+
delete outbox.messageId;
|
|
508
|
+
}
|
|
509
|
+
delete outbox.lastError;
|
|
510
|
+
delete outbox.nextRetryAt;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
else
|
|
514
|
+
throw new CollaborationError('INVALID_TASK_OPERATION', '不支持的任务操作');
|
|
515
|
+
if (['report', 'ask', 'answer', 'submit-plan', 'review', 'accept', 'revise', 'close', 'reopen'].includes(input.kind))
|
|
516
|
+
this.notifyCoordinator(doc, task, task.events.at(-1));
|
|
517
|
+
if (task.events.length > 1000 || task.artifacts.length > 200 || task.attempts.length > 100)
|
|
518
|
+
throw new CollaborationError('TASK_HISTORY_LIMIT', '此任务历史达到上限,请建立后续任务并保留本任务为历史', 409);
|
|
519
|
+
task.revision++;
|
|
520
|
+
task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
|
|
521
|
+
if (task.workflow && input.kind !== 'coordinate')
|
|
522
|
+
this.touchParent(doc, task, `${input.kind}${input.status ? ` / ${input.status}` : ''}:${content.slice(0, 1500)}`);
|
|
523
|
+
if (task.workflow && ['blocked', 'failed'].includes(input.status ?? ''))
|
|
524
|
+
task.automationIssue = content.slice(0, 1000);
|
|
525
|
+
if (task.workflow && input.kind === 'report' && ['ack', 'working', 'complete'].includes(input.status ?? ''))
|
|
526
|
+
delete task.automationIssue;
|
|
527
|
+
doc.requests[request.key] = { hash: request.hash, taskId };
|
|
528
|
+
return task;
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
touchParent(doc, task, content) {
|
|
532
|
+
const root = task.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
|
|
533
|
+
if (!root || root.status !== 'open')
|
|
534
|
+
return;
|
|
535
|
+
this.event(root, 'child-update', null, `子任务「${task.title}」(${task.id}):${content}`);
|
|
536
|
+
const result = root.artifacts.filter(a => a.kind === 'result' && a.attemptId === root.activeAttemptId).at(-1);
|
|
537
|
+
if (result && !root.events.some(e => e.kind === 'revise' && e.attemptId === root.activeAttemptId && e.createdAt >= result.createdAt)) {
|
|
538
|
+
this.event(root, 'revise', null, '子任务在汇总之后发生变化,请重新核对并提交最终结果');
|
|
539
|
+
}
|
|
540
|
+
root.revision++;
|
|
541
|
+
root.updatedAt = Date.now();
|
|
542
|
+
}
|
|
543
|
+
scheduled(owner) {
|
|
544
|
+
return this.owned(owner).filter(t => t.workflow && t.status === 'open' && t.scheduledAssignee && !t.activeAttemptId && !t.automationIssue
|
|
545
|
+
&& !t.workflow.paused && !this.document.tasks.find(r => r.id === t.workflow?.rootTaskId)?.workflow?.paused
|
|
546
|
+
&& this.document.tasks.find(r => r.id === t.workflow?.rootTaskId)?.status === 'open'
|
|
547
|
+
&& !this.planPending(this.document.tasks.find(r => r.id === t.workflow?.rootTaskId))
|
|
548
|
+
&& t.dependsOn.every(id => this.document.tasks.find(d => d.id === id)?.status === 'accepted'));
|
|
549
|
+
}
|
|
550
|
+
planPending(task) {
|
|
551
|
+
const plan = task.artifacts.filter(a => a.kind === 'plan' && a.attemptId === task.activeAttemptId).at(-1);
|
|
552
|
+
return !!plan && task.approvedPlanArtifactId !== plan.id;
|
|
553
|
+
}
|
|
554
|
+
activateScheduled(taskId, expectedRevision, assignee, workspace) {
|
|
555
|
+
this.transaction(doc => {
|
|
556
|
+
const task = doc.tasks.find(t => t.id === taskId);
|
|
557
|
+
const root = task?.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
|
|
558
|
+
if (!task || task.revision !== expectedRevision || task.activeAttemptId || !task.scheduledAssignee || task.status !== 'open'
|
|
559
|
+
|| task.workflow?.paused || !root || root.status !== 'open' || root.workflow?.paused || this.planPending(root)
|
|
560
|
+
|| task.dependsOn.some(id => doc.tasks.find(t => t.id === id)?.status !== 'accepted'))
|
|
561
|
+
return;
|
|
562
|
+
if (workspace)
|
|
563
|
+
task.workspace = workspace;
|
|
564
|
+
this.assign(doc, task, assignee, task.coordinator, '服务已接续依赖并准备执行');
|
|
565
|
+
delete task.scheduledAssignee;
|
|
566
|
+
task.revision++;
|
|
567
|
+
task.updatedAt = Date.now();
|
|
568
|
+
this.touchParent(doc, task, '已自动分派');
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
automationFailed(taskId, error) {
|
|
572
|
+
if (this.get(taskId)?.automationIssue === error.slice(0, 1000))
|
|
573
|
+
return;
|
|
574
|
+
this.transaction(doc => {
|
|
575
|
+
const task = doc.tasks.find(t => t.id === taskId);
|
|
576
|
+
if (!task)
|
|
577
|
+
return;
|
|
578
|
+
task.automationIssue = error.slice(0, 1000);
|
|
579
|
+
this.event(task, 'automation-blocked', null, task.automationIssue).source = 'system';
|
|
580
|
+
task.revision++;
|
|
581
|
+
task.updatedAt = Date.now();
|
|
582
|
+
this.touchParent(doc, task, task.automationIssue);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
nextAction(task, doc) {
|
|
586
|
+
if (!task.workflow || task.status !== 'open' || task.workflow.paused)
|
|
587
|
+
return null;
|
|
588
|
+
const root = task.workflow.rootTaskId && doc.tasks.find(r => r.id === task.workflow.rootTaskId);
|
|
589
|
+
if (root && (root.status !== 'open' || root.workflow?.paused || this.planPending(root)))
|
|
590
|
+
return null;
|
|
591
|
+
const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
|
|
592
|
+
if (result && !task.automationIssue && !task.decisions.some(d => d.status === 'pending')
|
|
593
|
+
&& !task.events.some(e => e.kind === 'revise' && e.attemptId === task.activeAttemptId && e.createdAt >= result.createdAt)) {
|
|
594
|
+
const review = task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result.id).at(-1);
|
|
595
|
+
if (!review && !task.events.some(e => e.kind === 'request-review' && e.artifactId === result.id))
|
|
596
|
+
return 'request-review';
|
|
597
|
+
if (review?.verdict === 'pass' && task.workflow.kind === 'step' && task.dependsOn.every(id => doc.tasks.find(t => t.id === id)?.status === 'accepted'))
|
|
598
|
+
return 'review-passed';
|
|
599
|
+
if (review && ['changes', 'blocked'].includes(review.verdict ?? '') && !task.events.some(e => e.kind === 'review-blocked' && e.artifactId === result.id))
|
|
600
|
+
return 'review-feedback';
|
|
601
|
+
}
|
|
602
|
+
if (task.workflow.kind === 'goal' && task.coordination) {
|
|
603
|
+
if (Date.now() - task.updatedAt >= 1000 && task.events.some(e => e.sequence > task.coordination.notifiedSequence
|
|
604
|
+
&& !['coordinate', 'request-review', 'assigned'].includes(e.kind) && !sameMember(e.actor, task.coordinator)))
|
|
605
|
+
return 'wake';
|
|
606
|
+
if (!task.automationIssue && task.coordination.acknowledgedSequence < task.coordination.notifiedSequence
|
|
607
|
+
&& !result && Date.now() - task.coordination.notifiedAt > 15 * 60_000)
|
|
608
|
+
return 'coordination-timeout';
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
/** Explicit, version-bound transitions; the browser does not drive this loop. */
|
|
613
|
+
advance(owner) {
|
|
614
|
+
if (!this.document.tasks.some(t => t.ownerServiceId === owner && this.nextAction(t, this.document)))
|
|
615
|
+
return;
|
|
616
|
+
this.transaction(doc => {
|
|
617
|
+
for (const task of doc.tasks.filter(t => t.ownerServiceId === owner)) {
|
|
618
|
+
const action = this.nextAction(task, doc);
|
|
619
|
+
if (!action)
|
|
620
|
+
continue;
|
|
621
|
+
const result = task.artifacts.filter(a => a.kind === 'result' && a.attemptId === task.activeAttemptId).at(-1);
|
|
622
|
+
const review = task.artifacts.filter(a => a.kind === 'review' && a.reviewsArtifactId === result?.id).at(-1);
|
|
623
|
+
const attempt = task.attempts.find(a => a.id === task.activeAttemptId);
|
|
624
|
+
if (action === 'request-review') {
|
|
625
|
+
const reviewers = [task.coordinator, ...task.workflow.reviewers].filter((m) => !!m && !sameMember(m, result.actor));
|
|
626
|
+
const reviewer = reviewers.find(m => m.serviceId === result.actor.serviceId) ?? reviewers[0];
|
|
627
|
+
if (!reviewer)
|
|
628
|
+
task.automationIssue = '没有可用的独立评审成员,请调整协调者或成员后继续';
|
|
629
|
+
else {
|
|
630
|
+
const event = this.event(task, 'request-review', null, '服务自动安排此版本独立评审');
|
|
631
|
+
event.artifactId = result.id;
|
|
632
|
+
event.target = reviewer;
|
|
633
|
+
event.source = 'system';
|
|
634
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: reviewer, kind: 'message', threadId: attempt.threadId,
|
|
635
|
+
content: `请独立评审「${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
|
+
}
|
|
637
|
+
this.touchParent(doc, task, '已自动请求独立评审');
|
|
638
|
+
}
|
|
639
|
+
else if (action === 'review-passed') {
|
|
640
|
+
task.status = 'accepted';
|
|
641
|
+
task.acceptedArtifactId = result.id;
|
|
642
|
+
task.completionMode = 'reviewed';
|
|
643
|
+
this.event(task, 'review-passed', review.actor, review.content).source = 'system';
|
|
644
|
+
this.touchParent(doc, task, '独立评审通过,后续依赖可以接续');
|
|
645
|
+
}
|
|
646
|
+
else if (action === 'review-feedback') {
|
|
647
|
+
const rounds = task.events.filter(e => e.kind === 'revise' && e.actor === null && e.artifactId && e.attemptId === task.activeAttemptId).length;
|
|
648
|
+
const blocked = review.verdict === 'blocked' || rounds >= task.workflow.maxRevisions;
|
|
649
|
+
const event = this.event(task, blocked ? 'review-blocked' : 'revise', null, review.content);
|
|
650
|
+
event.artifactId = result.id;
|
|
651
|
+
event.source = 'system';
|
|
652
|
+
if (blocked)
|
|
653
|
+
task.automationIssue = `${review.verdict === 'blocked' ? '独立评审受阻' : '连续返工需要调整方案'}:${review.content.slice(0, 800)}`;
|
|
654
|
+
else
|
|
655
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: result.actor, kind: 'message', threadId: attempt.threadId,
|
|
656
|
+
content: `任务「${task.title}」独立评审要求修改:\n${review.content}\n请继续完成当前分派并重新 report --status complete;新版本会重新独立评审。任务 ${task.id},尝试 ${task.activeAttemptId}。` });
|
|
657
|
+
this.touchParent(doc, task, blocked ? task.automationIssue : '已自动交回修改');
|
|
658
|
+
}
|
|
659
|
+
else if (action === 'wake') {
|
|
660
|
+
const updates = task.events.filter(e => e.sequence > task.coordination.notifiedSequence && e.kind !== 'coordinate');
|
|
661
|
+
doc.outbox.push({ id: id(), taskId: task.id, attemptId: task.activeAttemptId, target: task.coordinator, kind: 'message', threadId: task.id,
|
|
662
|
+
content: `目标「${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 '协调说明'。自动评审和依赖已由服务接续,不要重复分派。最终汇总已经通过评审时等待用户验收,不要重复提交相同结果。` });
|
|
663
|
+
task.coordination.notifiedSequence = task.events.at(-1).sequence;
|
|
664
|
+
task.coordination.notifiedAt = Date.now();
|
|
665
|
+
}
|
|
666
|
+
else
|
|
667
|
+
task.automationIssue = '协调通知发出后 15 分钟未收到本次协调说明,请查看终端或点击继续协调。';
|
|
668
|
+
task.revision++;
|
|
669
|
+
task.updatedAt = Date.now();
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
merge(task, issuer) {
|
|
674
|
+
validateSnapshot(task);
|
|
675
|
+
if (task.ownerServiceId !== issuer)
|
|
676
|
+
throw new CollaborationError('INVALID_TASK_OWNER', '任务来源身份不符');
|
|
677
|
+
const current = this.get(task.id);
|
|
678
|
+
if (current && current.ownerServiceId !== issuer)
|
|
679
|
+
throw new CollaborationError('TASK_ID_CONFLICT', '任务身份冲突');
|
|
680
|
+
if (current && current.revision >= task.revision)
|
|
681
|
+
return;
|
|
682
|
+
this.transaction(doc => {
|
|
683
|
+
const index = doc.tasks.findIndex(t => t.id === task.id);
|
|
684
|
+
if (index < 0) {
|
|
685
|
+
if (doc.tasks.length >= 2000)
|
|
686
|
+
throw new CollaborationError('TASK_LIMIT', '任务记录达到上限', 409);
|
|
687
|
+
doc.tasks.push(task);
|
|
688
|
+
}
|
|
689
|
+
else
|
|
690
|
+
doc.tasks[index] = task;
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
delivered(outboxId, receipt) {
|
|
694
|
+
if (!receipt || typeof receipt.messageId !== 'string' || !receipt.messageId || receipt.messageId.length > 128
|
|
695
|
+
|| !['pending', 'delivered', 'failed', 'expired'].includes(receipt.status)
|
|
696
|
+
|| receipt.deliveredAt !== null && (!Number.isSafeInteger(receipt.deliveredAt) || receipt.deliveredAt <= 0)
|
|
697
|
+
|| receipt.error !== null && typeof receipt.error !== 'string')
|
|
698
|
+
throw new CollaborationError('INVALID_TASK_RECEIPT', '远端投递凭证无效');
|
|
699
|
+
const current = this.document.outbox.find(o => o.id === outboxId);
|
|
700
|
+
if (!current)
|
|
701
|
+
return;
|
|
702
|
+
const previous = this.document.tasks.find(t => t.id === current.taskId)?.deliveries.find(d => d.id === outboxId);
|
|
703
|
+
if (JSON.stringify(previous) === JSON.stringify({ id: outboxId, attemptId: current.attemptId, kind: current.kind, ...receipt })) {
|
|
704
|
+
current.nextRetryAt = Date.now() + (receipt.status === 'pending' ? 2000 : 30_000);
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
this.transaction(doc => {
|
|
708
|
+
const outbox = doc.outbox.find(o => o.id === outboxId);
|
|
709
|
+
outbox.messageId = receipt.messageId;
|
|
710
|
+
outbox.lastError = receipt.error ?? undefined;
|
|
711
|
+
const task = doc.tasks.find(t => t.id === outbox.taskId);
|
|
712
|
+
const attempt = task.attempts.find(a => a.id === outbox.attemptId);
|
|
713
|
+
const previous = task.deliveries.find(d => d.id === outboxId);
|
|
714
|
+
const delivery = { id: outboxId, attemptId: outbox.attemptId, kind: outbox.kind, ...receipt };
|
|
715
|
+
const changed = JSON.stringify(previous) !== JSON.stringify(delivery);
|
|
716
|
+
if (changed) {
|
|
717
|
+
if (previous)
|
|
718
|
+
Object.assign(previous, delivery);
|
|
719
|
+
else
|
|
720
|
+
task.deliveries.push(delivery);
|
|
721
|
+
task.revision++;
|
|
722
|
+
task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
|
|
723
|
+
}
|
|
724
|
+
// Later feedback has its own receipt; it must not overwrite the initial dispatch.
|
|
725
|
+
if (outbox.kind === 'task') {
|
|
726
|
+
attempt.messageId = receipt.messageId;
|
|
727
|
+
attempt.deliveryStatus = receipt.status;
|
|
728
|
+
attempt.deliveredAt = receipt.deliveredAt;
|
|
729
|
+
attempt.deliveryError = receipt.error;
|
|
730
|
+
}
|
|
731
|
+
if (receipt.status === 'delivered' || !task.workflow && ['failed', 'expired'].includes(receipt.status))
|
|
732
|
+
doc.outbox = doc.outbox.filter(o => o.id !== outboxId);
|
|
733
|
+
else
|
|
734
|
+
outbox.nextRetryAt = Date.now() + (receipt.status === 'pending' ? 2000 : 30_000);
|
|
735
|
+
if (task.workflow && ['failed', 'expired'].includes(receipt.status)) {
|
|
736
|
+
task.automationIssue = `消息投递${receipt.status === 'expired' ? '过期' : '失败'}:${receipt.error ?? '请检查成员终端后重试'}`;
|
|
737
|
+
this.touchParent(doc, task, task.automationIssue);
|
|
738
|
+
}
|
|
739
|
+
else if (receipt.status === 'delivered' && task.automationIssue?.startsWith('消息投递正在重试'))
|
|
740
|
+
delete task.automationIssue;
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
failed(outboxId, error) {
|
|
744
|
+
const current = this.document.outbox.find(o => o.id === outboxId);
|
|
745
|
+
if (!current)
|
|
746
|
+
return;
|
|
747
|
+
if (current.lastError === error.slice(0, 500)) {
|
|
748
|
+
current.nextRetryAt = Date.now() + 10_000;
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
this.transaction(doc => {
|
|
752
|
+
const outbox = doc.outbox.find(o => o.id === outboxId);
|
|
753
|
+
const changed = outbox.lastError !== error.slice(0, 500);
|
|
754
|
+
outbox.lastError = error.slice(0, 500);
|
|
755
|
+
outbox.nextRetryAt = Date.now() + 10_000;
|
|
756
|
+
const task = doc.tasks.find(t => t.id === outbox.taskId);
|
|
757
|
+
if (changed) {
|
|
758
|
+
const previous = task.deliveries.find(d => d.id === outboxId);
|
|
759
|
+
const record = { id: outboxId, attemptId: outbox.attemptId, kind: outbox.kind, messageId: outbox.messageId ?? '', status: 'retrying', deliveredAt: null, error: outbox.lastError };
|
|
760
|
+
if (previous)
|
|
761
|
+
Object.assign(previous, record);
|
|
762
|
+
else
|
|
763
|
+
task.deliveries.push(record);
|
|
764
|
+
task.revision++;
|
|
765
|
+
task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
|
|
766
|
+
if (task.workflow && !task.automationIssue) {
|
|
767
|
+
task.automationIssue = `消息投递正在重试:${outbox.lastError}`;
|
|
768
|
+
this.touchParent(doc, task, task.automationIssue);
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|