termdock 1.4.298 → 1.4.300
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/README.en.md +1 -1
- package/README.md +2 -1
- package/dist/client/assets/App-7k2I_09f.js +179 -0
- package/dist/client/assets/App-7k2I_09f.js.br +0 -0
- package/dist/client/assets/App-7k2I_09f.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-BrfM7rGb.js +12 -0
- package/dist/client/assets/ArchitecturePanel-BrfM7rGb.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-BrfM7rGb.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-BrAwRa20.js → ChangeWalkthroughPanel-RoMt6giT.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-RoMt6giT.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-RoMt6giT.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CK2BKKnn.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-CK2BKKnn.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CK2BKKnn.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-FlTbV9e1.js +2 -0
- package/dist/client/assets/ComputerControlView-AdjmvNUa.js +5 -0
- package/dist/client/assets/ComputerControlView-AdjmvNUa.js.br +0 -0
- package/dist/client/assets/ComputerControlView-AdjmvNUa.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-DiAEIEvj.js → DagPlayground-x8-mp6wC.js} +1 -1
- package/dist/client/assets/DagPlayground-x8-mp6wC.js.br +0 -0
- package/dist/client/assets/DagPlayground-x8-mp6wC.js.gz +0 -0
- package/dist/client/assets/{DiffLab-BOXARpIG.js → DiffLab-B9KLZ--H.js} +1 -1
- package/dist/client/assets/DiffLab-B9KLZ--H.js.br +0 -0
- package/dist/client/assets/DiffLab-B9KLZ--H.js.gz +0 -0
- package/dist/client/assets/{DiffReview-C90Qt0Gx.js → DiffReview-IEcDVREB.js} +1 -1
- package/dist/client/assets/DiffReview-IEcDVREB.js.br +0 -0
- package/dist/client/assets/DiffReview-IEcDVREB.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-CrVXGE1A.js → DiffReviewLab-lWra5pTJ.js} +2 -2
- package/dist/client/assets/DiffReviewLab-lWra5pTJ.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-lWra5pTJ.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-BA_9bZxs.js → DiffViewer-B2Rue9PA.js} +8 -8
- package/dist/client/assets/DiffViewer-B2Rue9PA.js.br +0 -0
- package/dist/client/assets/DiffViewer-B2Rue9PA.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-BX75IksJ.js → ModelPreview-NMQsfqAe.js} +1 -1
- package/dist/client/assets/ModelPreview-NMQsfqAe.js.br +0 -0
- package/dist/client/assets/ModelPreview-NMQsfqAe.js.gz +0 -0
- package/dist/client/assets/RightSidebar-BOBbegdP.js +1677 -0
- package/dist/client/assets/RightSidebar-BOBbegdP.js.br +0 -0
- package/dist/client/assets/RightSidebar-BOBbegdP.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-BGF7DKjR.js → abnfDiagram-VCTEODGH-BIVuPQbA.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-BIVuPQbA.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BIVuPQbA.js.gz +0 -0
- package/dist/client/assets/app-CHoOHnjf.js +35 -0
- package/dist/client/assets/app-CHoOHnjf.js.br +0 -0
- package/dist/client/assets/app-CHoOHnjf.js.gz +0 -0
- package/dist/client/assets/{arc-DZUR8G8M.js → arc-CNTT1p1A.js} +1 -1
- package/dist/client/assets/arc-CNTT1p1A.js.br +0 -0
- package/dist/client/assets/arc-CNTT1p1A.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-CWXE4hoZ.js → architectureDiagram-5GKGNRK7-Cl3gg8ZE.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Cl3gg8ZE.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Cl3gg8ZE.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-BZdUQzE2.js → blockDiagram-I7D4REHJ-Sxmfy_3_.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-Sxmfy_3_.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Sxmfy_3_.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DA7m-AbK.js → c4Diagram-7LVT6UL2-DaTnrOn7.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-DaTnrOn7.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DaTnrOn7.js.gz +0 -0
- package/dist/client/assets/channel-CEvBVvt3.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-Dg-zs5cw.js → chunk-2Q5K7J3B-BoiPA32P.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-DKMA2xz_.js → chunk-5VM5RSS4-G5G1YBLk.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-ByyS2RgO.js → chunk-F27PBJKO-BNHv_EnL.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-BNHv_EnL.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BNHv_EnL.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-DTTmE4mJ.js → chunk-IMKFNOWR-CxgtyKdq.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-CxgtyKdq.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CxgtyKdq.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-D9nr-n4D.js → chunk-JWPE2WC7-UO-0iK-G.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-BPVZ3XrQ.js → chunk-POPQ4Y6H-Cox9z-e2.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-Ctn5SKJf.js → chunk-SVP7TREG-B2W8-Lxh.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-B2W8-Lxh.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-B2W8-Lxh.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-BCNSq9V4.js → chunk-TICWLB2K-BYDr9nkD.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-BYDr9nkD.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BYDr9nkD.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-BlJ1FL7I.js → chunk-XXDRQBXY-atArreox.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-nSmGM9Fl.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-nSmGM9Fl.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Cv4gLm7L.js → cose-bilkent-JH36ORCC-CvfJj195.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CvfJj195.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CvfJj195.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BNbPniZY.js → cynefin-OW5HDTMX-ByWE_fUn.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-ByWE_fUn.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-ByWE_fUn.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-DFR2fyKM.js → cynefinDiagram-5FMLGOSQ-BRT0d0MN.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BRT0d0MN.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BRT0d0MN.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-DM9QOAp3.js → dagre-GXQ25YYZ-Twtyv7ud.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-Twtyv7ud.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Twtyv7ud.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-DqeCdIIK.js → diagram-S7CK7UJ4-Vl06hZtd.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-Vl06hZtd.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-Vl06hZtd.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-Cc1KiE_N.js → diagram-UQ7AKVKN-Cp39Mpse.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-Cp39Mpse.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-Cp39Mpse.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-I_0NpClr.js → diagram-VSXAHHWV-Drchji0N.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-Drchji0N.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Drchji0N.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-DLjDAEEa.js → diagram-VX7I27RA-rp8n_lct.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-rp8n_lct.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-rp8n_lct.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-DHo74-rP.js → diagram-Z3DM3KII-BEGDaMBm.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-BEGDaMBm.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BEGDaMBm.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-S9dCP2Z9.js → ebnfDiagram-PWID7BFC-kvgdD4A7.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-kvgdD4A7.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-kvgdD4A7.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-CR99Mjq9.js → erDiagram-RLTQ6QDP-DcYHv9hK.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-DcYHv9hK.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DcYHv9hK.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-CIqCbTFC.js → flowDiagram-HODETNUW-T9FQKn1F.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-T9FQKn1F.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-T9FQKn1F.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CSpUeAKu.js → ganttDiagram-EL5Y4UJY-BEB_X-y5.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BEB_X-y5.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BEB_X-y5.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-C3GqyqzH.js → gestureArbiter-Do0TIdkh.js} +1 -1
- package/dist/client/assets/gestureArbiter-Do0TIdkh.js.br +0 -0
- package/dist/client/assets/gestureArbiter-Do0TIdkh.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-DBICHCGp.js → gitGraphDiagram-WWUBYQGX-_0Y7kKA2.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-_0Y7kKA2.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-_0Y7kKA2.js.gz +0 -0
- package/dist/client/assets/{index-C6ftfFlG.css → index-D2_-ypCL.css} +1 -1
- package/dist/client/assets/index-D2_-ypCL.css.br +0 -0
- package/dist/client/assets/index-D2_-ypCL.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-CjPrG37z.js → infoDiagram-27XIBGKW-B6y0nIx8.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-C718heAs.js → ishikawaDiagram-5VMMS53U-ebGuykGn.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-ebGuykGn.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-ebGuykGn.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-fu76FPSY.js → journeyDiagram-3NMN7TZE-DJDkcIq5.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DJDkcIq5.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DJDkcIq5.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-BVj6v9-z.js → kanban-definition-UXKFOSKX-pTBbviRZ.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-pTBbviRZ.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-pTBbviRZ.js.gz +0 -0
- package/dist/client/assets/{layout-Dspjm0zn.js → layout-Dw9FB4Uc.js} +1 -1
- package/dist/client/assets/layout-Dw9FB4Uc.js.br +0 -0
- package/dist/client/assets/layout-Dw9FB4Uc.js.gz +0 -0
- package/dist/client/assets/{linear-BktGv6ei.js → linear-CyLfVGgB.js} +1 -1
- package/dist/client/assets/linear-CyLfVGgB.js.br +0 -0
- package/dist/client/assets/linear-CyLfVGgB.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-Dt2TVLfQ.js → mermaid.core-xRkirK0l.js} +5 -5
- package/dist/client/assets/mermaid.core-xRkirK0l.js.br +0 -0
- package/dist/client/assets/mermaid.core-xRkirK0l.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-DwxtrCyv.js → mindmap-definition-YA3MSWOX-BxvaDEGQ.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BxvaDEGQ.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BxvaDEGQ.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-B5Inu7kn.js → passwordBootstrap-CaYbIQDC.js} +1 -1
- package/dist/client/assets/passwordBootstrap-CaYbIQDC.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-CaYbIQDC.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-eDDhkygS.js → pegDiagram-XKGWAZYB-Cxh-Bbay.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-Cxh-Bbay.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-Cxh-Bbay.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-C-A_a53h.js → pieDiagram-E7YTZNPT-CgOsk0Ai.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-CgOsk0Ai.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CgOsk0Ai.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BRmQ1U56.js → quadrantDiagram-AXDQQJYC-D1S_gKcT.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-D1S_gKcT.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-D1S_gKcT.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-BRmkfIUn.js → railroadDiagram-O6MQD6OU-BG5e87Np.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BG5e87Np.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BG5e87Np.js.gz +0 -0
- package/dist/client/assets/rdpSession-DL1rZ49m.js +1 -0
- package/dist/client/assets/rdpSession-DL1rZ49m.js.br +0 -0
- package/dist/client/assets/rdpSession-DL1rZ49m.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-DB039WbW.js +1 -0
- package/dist/client/assets/referenceLongPress-DB039WbW.js.br +0 -0
- package/dist/client/assets/referenceLongPress-DB039WbW.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-C95pjpXU.js → requirementDiagram-BXWQKSXE-C37DsQfu.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-C37DsQfu.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-C37DsQfu.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-BD7SJZ8y.js → sankeyDiagram-P5KCCOFB-DLzhCYoj.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DLzhCYoj.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DLzhCYoj.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-D0egOILU.js → sequenceDiagram-WJ2MYXX4-DeVpSvB0.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DeVpSvB0.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DeVpSvB0.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-BaJGHebj.js → sizeCapture-INFHLROL-CE03E66U.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-CE03E66U.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CE03E66U.js.gz +0 -0
- package/dist/client/assets/{sparkles-D4t6BWgT.js → sparkles-ButxQzs4.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-DEZo4cxr.js → stateDiagram-D77RDMKH-DuqhHT3k.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-DuqhHT3k.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DuqhHT3k.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CxfzEwAH.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-DSZuZ910.js → swimlanes-42K2YHIH-DxkSdJ8g.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-DxkSdJ8g.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-DxkSdJ8g.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-B_muKJLi.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-Bw0gUCrJ.js → timeline-definition-24CTP7MA-DVpcJ6y5.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-DVpcJ6y5.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DVpcJ6y5.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-Cf9mn5Ug.js → triangle-alert-BRy562K_.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-B0gzw-En.js → vennDiagram-4TSXK5OY-BEKXLWGa.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-BEKXLWGa.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BEKXLWGa.js.gz +0 -0
- package/dist/client/assets/{videoCompression-CyTyZG2b.js → videoCompression-CoVXZ8qw.js} +1 -1
- package/dist/client/assets/videoCompression-CoVXZ8qw.js.br +0 -0
- package/dist/client/assets/videoCompression-CoVXZ8qw.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-vaiRXtUT.js → wardleyDiagram-VM6X3IG4-Cj30GTa9.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cj30GTa9.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cj30GTa9.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Dosv-6JB.js → xychartDiagram-S5SC5T6Z-Cm7NEdCl.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Cm7NEdCl.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Cm7NEdCl.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 -6
- package/dist/server/agent/collaborationRoutes.js +4 -2
- package/dist/server/agent/collaborationStore.js +9 -0
- package/dist/server/agent/collaborationTaskCli.js +11 -3
- package/dist/server/agent/collaborationTaskService.js +14 -8
- package/dist/server/agent/collaborationTaskStore.js +67 -25
- package/dist/server/agent/integrationCli.js +227 -0
- package/dist/server/agent/integrationServer.js +326 -0
- package/dist/server/agent/integrationSessions.js +296 -0
- package/dist/server/agent/integrationStore.js +199 -0
- package/dist/server/cli.js +29 -0
- package/dist/server/entry.js +26 -7
- package/dist/server/routes/filesystem.js +120 -87
- package/dist/server/routes/terminal.js +158 -4
- package/dist/server/utils/localAccess.js +27 -7
- package/dist/server/utils/sessionDisplayName.js +53 -0
- package/docs/integrations.md +239 -0
- package/package.json +3 -2
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-D7FKaW3e.js +0 -179
- package/dist/client/assets/App-D7FKaW3e.js.br +0 -0
- package/dist/client/assets/App-D7FKaW3e.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-CU5vv8Fm.js +0 -7
- package/dist/client/assets/ArchitecturePanel-CU5vv8Fm.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CU5vv8Fm.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BrAwRa20.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BrAwRa20.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CH6mfIDe.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-CH6mfIDe.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CH6mfIDe.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-DkhB6WXO.js +0 -2
- package/dist/client/assets/ComputerControlView-CI-TMTRg.js +0 -5
- package/dist/client/assets/ComputerControlView-CI-TMTRg.js.br +0 -0
- package/dist/client/assets/ComputerControlView-CI-TMTRg.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DiAEIEvj.js.br +0 -0
- package/dist/client/assets/DagPlayground-DiAEIEvj.js.gz +0 -0
- package/dist/client/assets/DiffLab-BOXARpIG.js.br +0 -0
- package/dist/client/assets/DiffLab-BOXARpIG.js.gz +0 -0
- package/dist/client/assets/DiffReview-C90Qt0Gx.js.br +0 -0
- package/dist/client/assets/DiffReview-C90Qt0Gx.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CrVXGE1A.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CrVXGE1A.js.gz +0 -0
- package/dist/client/assets/DiffViewer-BA_9bZxs.js.br +0 -0
- package/dist/client/assets/DiffViewer-BA_9bZxs.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BX75IksJ.js.br +0 -0
- package/dist/client/assets/ModelPreview-BX75IksJ.js.gz +0 -0
- package/dist/client/assets/RightSidebar-B_dO0nU5.js +0 -1678
- package/dist/client/assets/RightSidebar-B_dO0nU5.js.br +0 -0
- package/dist/client/assets/RightSidebar-B_dO0nU5.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BGF7DKjR.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BGF7DKjR.js.gz +0 -0
- package/dist/client/assets/app-BJP8Xgmi.js +0 -35
- package/dist/client/assets/app-BJP8Xgmi.js.br +0 -0
- package/dist/client/assets/app-BJP8Xgmi.js.gz +0 -0
- package/dist/client/assets/arc-DZUR8G8M.js.br +0 -0
- package/dist/client/assets/arc-DZUR8G8M.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CWXE4hoZ.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CWXE4hoZ.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BZdUQzE2.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BZdUQzE2.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DA7m-AbK.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DA7m-AbK.js.gz +0 -0
- package/dist/client/assets/channel-DD06PSxm.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-ByyS2RgO.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-ByyS2RgO.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DTTmE4mJ.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DTTmE4mJ.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Ctn5SKJf.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-Ctn5SKJf.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BCNSq9V4.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BCNSq9V4.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-DkVrFG7y.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-DkVrFG7y.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Cv4gLm7L.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-Cv4gLm7L.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BNbPniZY.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BNbPniZY.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DFR2fyKM.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DFR2fyKM.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-DM9QOAp3.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-DM9QOAp3.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DqeCdIIK.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DqeCdIIK.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-Cc1KiE_N.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-Cc1KiE_N.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-I_0NpClr.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-I_0NpClr.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DLjDAEEa.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DLjDAEEa.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DHo74-rP.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DHo74-rP.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-S9dCP2Z9.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-S9dCP2Z9.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-CR99Mjq9.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-CR99Mjq9.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CIqCbTFC.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CIqCbTFC.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CSpUeAKu.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CSpUeAKu.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-C3GqyqzH.js.br +0 -0
- package/dist/client/assets/gestureArbiter-C3GqyqzH.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DBICHCGp.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DBICHCGp.js.gz +0 -0
- package/dist/client/assets/index-C6ftfFlG.css.br +0 -0
- package/dist/client/assets/index-C6ftfFlG.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C718heAs.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C718heAs.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-fu76FPSY.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-fu76FPSY.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BVj6v9-z.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BVj6v9-z.js.gz +0 -0
- package/dist/client/assets/layout-Dspjm0zn.js.br +0 -0
- package/dist/client/assets/layout-Dspjm0zn.js.gz +0 -0
- package/dist/client/assets/linear-BktGv6ei.js.br +0 -0
- package/dist/client/assets/linear-BktGv6ei.js.gz +0 -0
- package/dist/client/assets/mermaid.core-Dt2TVLfQ.js.br +0 -0
- package/dist/client/assets/mermaid.core-Dt2TVLfQ.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DwxtrCyv.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DwxtrCyv.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-B5Inu7kn.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-B5Inu7kn.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-eDDhkygS.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-eDDhkygS.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-C-A_a53h.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-C-A_a53h.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BRmQ1U56.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BRmQ1U56.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BRmkfIUn.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BRmkfIUn.js.gz +0 -0
- package/dist/client/assets/rdpSession-DknTu7rD.js +0 -1
- package/dist/client/assets/rdpSession-DknTu7rD.js.br +0 -0
- package/dist/client/assets/rdpSession-DknTu7rD.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-CnGp4Gdi.js +0 -1
- package/dist/client/assets/referenceLongPress-CnGp4Gdi.js.br +0 -0
- package/dist/client/assets/referenceLongPress-CnGp4Gdi.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-C95pjpXU.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-C95pjpXU.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BD7SJZ8y.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BD7SJZ8y.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D0egOILU.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D0egOILU.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BaJGHebj.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BaJGHebj.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DEZo4cxr.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DEZo4cxr.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CJg7AUmk.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-DSZuZ910.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-DSZuZ910.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CW58IimW.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-Bw0gUCrJ.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-Bw0gUCrJ.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-B0gzw-En.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-B0gzw-En.js.gz +0 -0
- package/dist/client/assets/videoCompression-CyTyZG2b.js.br +0 -0
- package/dist/client/assets/videoCompression-CyTyZG2b.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-vaiRXtUT.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-vaiRXtUT.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Dosv-6JB.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Dosv-6JB.js.gz +0 -0
|
@@ -3,8 +3,12 @@ import { randomUUID } from 'node:crypto';
|
|
|
3
3
|
export async function executeTaskCommand(command, request, io) {
|
|
4
4
|
const o = command.options, operation = command.operation;
|
|
5
5
|
const bodyOptions = ['content', 'file', 'stdin', 'idempotency-key'];
|
|
6
|
+
const originOptions = ['source', 'external-actor', 'external-message-id', 'metadata'];
|
|
7
|
+
if (originOptions.some(key => o[key]) && !o.principal)
|
|
8
|
+
throw new Error('External source metadata requires --principal');
|
|
6
9
|
const allowed = {
|
|
7
10
|
list: ['group'], get: [], create: [...bodyOptions, 'group', 'title', 'constraints', 'acceptance', 'assignee', 'coordinator', 'parent', 'depends-on', 'managed', 'reviewers', 'shared-directory', 'integration', 'work-type'],
|
|
11
|
+
answer: [...bodyOptions, 'decision'], respond: [...bodyOptions, 'attempt', 'to-event', 'evidence'],
|
|
8
12
|
assign: [...bodyOptions, 'assignee', 'revision'], report: [...bodyOptions, 'attempt', 'status', 'evidence', 'summary'], ask: [...bodyOptions, 'attempt', 'options'],
|
|
9
13
|
plan: [...bodyOptions, 'attempt', 'evidence'], review: [...bodyOptions, 'artifact', 'evidence', 'verdict'], comment: bodyOptions,
|
|
10
14
|
'request-review': [...bodyOptions, 'artifact', 'assignee', 'revision'],
|
|
@@ -12,7 +16,7 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
12
16
|
coordinate: [...bodyOptions, 'revision'], pause: [...bodyOptions, 'revision'], resume: [...bodyOptions, 'revision'], retry: [...bodyOptions, 'revision'],
|
|
13
17
|
};
|
|
14
18
|
for (const flag of Object.keys(o))
|
|
15
|
-
if (![...allowed[operation], 'session', 'json', 'jsonl', 'text', 'help'].includes(flag))
|
|
19
|
+
if (![...allowed[operation], ...(o.principal ? originOptions : []), 'principal', 'session', 'json', 'jsonl', 'text', 'help'].includes(flag))
|
|
16
20
|
throw new Error(`--${flag} is not supported by task ${operation}`);
|
|
17
21
|
if (operation === 'list')
|
|
18
22
|
return request('GET', `/tasks${o.group ? `?group=${encodeURIComponent(String(o.group))}` : ''}`);
|
|
@@ -32,8 +36,12 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
32
36
|
throw new Error('Read task get first, then supply --revision to avoid overwriting another decision');
|
|
33
37
|
if (['assign', 'request-review'].includes(operation) && !o.assignee)
|
|
34
38
|
throw new Error('This task operation requires --assignee');
|
|
35
|
-
if (['report', 'ask', 'plan'].includes(operation) && !o.attempt)
|
|
39
|
+
if (['report', 'respond', 'ask', 'plan'].includes(operation) && !o.attempt)
|
|
36
40
|
throw new Error('Use --attempt from the dispatch message; never guess the current attempt');
|
|
41
|
+
if (operation === 'respond' && !o['to-event'])
|
|
42
|
+
throw new Error('respond requires --to-event <comment-event-id>');
|
|
43
|
+
if (operation === 'answer' && !o.decision)
|
|
44
|
+
throw new Error('answer requires --decision <question-id>');
|
|
37
45
|
if (operation === 'create') {
|
|
38
46
|
if (!o.title || !o.group)
|
|
39
47
|
throw new Error('task create requires --title and --group');
|
|
@@ -47,7 +55,7 @@ export async function executeTaskCommand(command, request, io) {
|
|
|
47
55
|
if (options !== undefined && (!Array.isArray(options) || options.some(v => typeof v !== 'string')))
|
|
48
56
|
throw new Error('--options must be a JSON array of strings');
|
|
49
57
|
return request('POST', `/tasks/${encodeURIComponent(command.target)}`, { input: {
|
|
50
|
-
kind: operation === 'plan' ? 'submit-plan' : operation, idempotencyKey, expectedRevision, content,
|
|
58
|
+
replyToEventId: o['to-event'], decisionId: o.decision, kind: operation === 'plan' ? 'submit-plan' : operation, idempotencyKey, expectedRevision, content,
|
|
51
59
|
assigneeSessionId: o.assignee, attemptId: o.attempt, status: o.status, artifactId: o.artifact, options, verdict: o.verdict,
|
|
52
60
|
evidence: o.evidence ? JSON.parse(String(o.evidence)) : undefined, summary: o.summary,
|
|
53
61
|
} });
|
|
@@ -70,7 +70,7 @@ export class CollaborationTaskService {
|
|
|
70
70
|
this.group(task.groupId, actor);
|
|
71
71
|
return this.view(task);
|
|
72
72
|
}
|
|
73
|
-
async create(input, actor) {
|
|
73
|
+
async create(input, actor, origin) {
|
|
74
74
|
this.group(input.groupId, actor);
|
|
75
75
|
this.validateMembers(input.groupId, input);
|
|
76
76
|
const dependencies = [...(input.dependsOn ?? []), ...(input.parentTaskId ? [input.parentTaskId] : [])];
|
|
@@ -81,22 +81,26 @@ export class CollaborationTaskService {
|
|
|
81
81
|
if (this.tasks.get(dependency)?.ownerServiceId !== authority)
|
|
82
82
|
throw new CollaborationError('DEPENDENCY_AUTHORITY_REQUIRED', '依赖与父任务需在同一来源服务创建', 409);
|
|
83
83
|
}
|
|
84
|
+
if (origin && authority !== this.self)
|
|
85
|
+
throw new CollaborationError('INTEGRATION_LOCAL_ONLY', '首版集成任务须由本机服务保存', 409);
|
|
84
86
|
if (authority !== this.self) {
|
|
85
87
|
const result = await this.peers.requestTasks(authority, { op: 'create', input, actorSessionId: actor?.sessionId ?? null });
|
|
86
88
|
const task = result.task;
|
|
87
89
|
this.tasks.merge(task, authority);
|
|
88
90
|
return this.view(task);
|
|
89
91
|
}
|
|
90
|
-
const task = this.tasks.create(this.self, input, actor);
|
|
92
|
+
const task = this.tasks.create(this.self, input, actor, origin);
|
|
91
93
|
void this.flush();
|
|
92
94
|
return this.view(task);
|
|
93
95
|
}
|
|
94
|
-
async apply(taskId, input, actor) {
|
|
96
|
+
async apply(taskId, input, actor, origin) {
|
|
95
97
|
const task = this.tasks.get(taskId);
|
|
96
98
|
if (!task)
|
|
97
99
|
throw new CollaborationError('TASK_NOT_FOUND', '任务不存在', 404);
|
|
98
100
|
this.group(task.groupId, actor);
|
|
99
101
|
this.validateMembers(task.groupId, input);
|
|
102
|
+
if (origin && task.ownerServiceId !== this.self)
|
|
103
|
+
throw new CollaborationError('INTEGRATION_LOCAL_ONLY', '首版集成任务须由本机服务保存', 409);
|
|
100
104
|
if (task.ownerServiceId !== this.self) {
|
|
101
105
|
const result = await this.peers.requestTasks(task.ownerServiceId, { op: 'apply', taskId, input, actorSessionId: actor?.sessionId ?? null });
|
|
102
106
|
this.tasks.merge(result.task, task.ownerServiceId);
|
|
@@ -126,7 +130,7 @@ export class CollaborationTaskService {
|
|
|
126
130
|
artifactId: child.acceptedArtifactId, integration: child.workflow?.integration === true,
|
|
127
131
|
commit: child.artifacts.find(a => a.id === child.acceptedArtifactId)?.evidence?.commit })) } };
|
|
128
132
|
}
|
|
129
|
-
const updated = this.tasks.apply(taskId, input, actor);
|
|
133
|
+
const updated = this.tasks.apply(taskId, input, actor, origin);
|
|
130
134
|
void this.flush();
|
|
131
135
|
return this.view(updated);
|
|
132
136
|
}
|
|
@@ -196,7 +200,7 @@ export class CollaborationTaskService {
|
|
|
196
200
|
const queued = this.messages.send({ groupId: group.id, fromSessionId: null, toSessionIds: [recipient.sessionId],
|
|
197
201
|
kind: packet.kind === 'task' ? 'task' : 'message', content: String(packet.content ?? ''), threadId: attempt?.threadId ?? task.id,
|
|
198
202
|
idempotencyKey: `task:${peer.serviceId}:${String(packet.outboxId)}`,
|
|
199
|
-
metadata: { taskOutboxId: packet.outboxId, termdockTask: { taskId: task.id, attemptId: attempt?.id ?? null, ownerServiceId: peer.serviceId } } });
|
|
203
|
+
metadata: { taskOutboxId: packet.outboxId, termdockTask: { taskId: task.id, attemptId: attempt?.id ?? null, ownerServiceId: peer.serviceId, replyToEventId: task.events.find(e => e.deliveryId === packet.outboxId && e.attemptId === attempt?.id && e.kind === 'comment')?.id } } });
|
|
200
204
|
this.deliver(recipient.sessionId);
|
|
201
205
|
return { receipt: this.receipt(queued[0].id) };
|
|
202
206
|
}
|
|
@@ -313,14 +317,16 @@ export class CollaborationTaskService {
|
|
|
313
317
|
if (!task?.workflow)
|
|
314
318
|
return true;
|
|
315
319
|
const root = task.workflow.rootTaskId ? this.tasks.get(task.workflow.rootTaskId) : task;
|
|
316
|
-
|
|
320
|
+
const consultation = outbox.replyToEventId && task.events.some(e => e.id === outbox.replyToEventId && e.kind === 'comment');
|
|
321
|
+
return consultation || !task.workflow.paused && !root?.workflow?.paused && root?.status === 'open';
|
|
317
322
|
}).slice(0, 16);
|
|
318
323
|
for (const outbox of ready) {
|
|
319
324
|
try {
|
|
320
325
|
const task = this.tasks.get(outbox.taskId);
|
|
321
326
|
this.group(task.groupId, outbox.target);
|
|
322
327
|
const root = task.workflow?.rootTaskId ? this.tasks.get(task.workflow.rootTaskId) : task;
|
|
323
|
-
|
|
328
|
+
const consultation = outbox.replyToEventId && task.events.some(e => e.id === outbox.replyToEventId && e.kind === 'comment');
|
|
329
|
+
if (task.workflow && !consultation && (task.workflow.paused || root?.workflow?.paused || root?.status !== 'open'))
|
|
324
330
|
continue;
|
|
325
331
|
if (outbox.target.serviceId === this.self) {
|
|
326
332
|
if (outbox.messageId && this.messages.getMessage(outbox.messageId)) {
|
|
@@ -330,7 +336,7 @@ export class CollaborationTaskService {
|
|
|
330
336
|
}
|
|
331
337
|
const queued = this.messages.send({ groupId: task.groupId, fromSessionId: null, toSessionIds: [outbox.target.sessionId],
|
|
332
338
|
kind: outbox.kind, content: outbox.content, threadId: outbox.threadId, idempotencyKey: `task:${this.self}:${outbox.id}`,
|
|
333
|
-
metadata: { termdockTask: { taskId: task.id, attemptId: outbox.attemptId, ownerServiceId: this.self } } });
|
|
339
|
+
metadata: { termdockTask: { taskId: task.id, attemptId: outbox.attemptId, ownerServiceId: this.self, replyToEventId: outbox.replyToEventId } } });
|
|
334
340
|
this.deliver(outbox.target.sessionId);
|
|
335
341
|
this.tasks.delivered(outbox.id, this.receipt(queued[0].id));
|
|
336
342
|
}
|
|
@@ -83,7 +83,7 @@ function validateSnapshot(task) {
|
|
|
83
83
|
invalid();
|
|
84
84
|
if (event.deliveryId !== undefined && !validId(event.deliveryId))
|
|
85
85
|
invalid();
|
|
86
|
-
if (event.source !== undefined && !['system', 'user'].includes(event.source))
|
|
86
|
+
if (event.source !== undefined && !['system', 'user', 'integration'].includes(event.source))
|
|
87
87
|
invalid();
|
|
88
88
|
if (event.actor !== null)
|
|
89
89
|
member(event.actor);
|
|
@@ -115,6 +115,9 @@ function validateSnapshot(task) {
|
|
|
115
115
|
/** Atomic document writes keep decisions and their outgoing messages in one transaction. */
|
|
116
116
|
export class CollaborationTaskStore {
|
|
117
117
|
file;
|
|
118
|
+
listeners = new Set();
|
|
119
|
+
subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }
|
|
120
|
+
snapshot() { return structuredClone(this.document.tasks); }
|
|
118
121
|
document = { version: 1, tasks: [], outbox: [], requests: {} };
|
|
119
122
|
constructor(file) {
|
|
120
123
|
this.file = file;
|
|
@@ -129,9 +132,18 @@ export class CollaborationTaskStore {
|
|
|
129
132
|
throw error;
|
|
130
133
|
}
|
|
131
134
|
}
|
|
132
|
-
transaction(mutate) {
|
|
135
|
+
transaction(mutate, origin) {
|
|
133
136
|
const next = structuredClone(this.document);
|
|
134
137
|
const result = mutate(next);
|
|
138
|
+
if (origin) {
|
|
139
|
+
const previous = new Set(this.document.tasks.flatMap(t => t.events.map(e => e.id)));
|
|
140
|
+
for (const task of next.tasks)
|
|
141
|
+
for (const event of task.events)
|
|
142
|
+
if (!previous.has(event.id) && event.actor === null && event.source !== 'system') {
|
|
143
|
+
event.source = 'integration';
|
|
144
|
+
event.origin = structuredClone(origin);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
135
147
|
if (next.tasks.some(task => Buffer.byteLength(JSON.stringify(task)) > 512_000))
|
|
136
148
|
throw new CollaborationError('TASK_HISTORY_LIMIT', '此任务记录达到 500 KiB,请建立后续任务并保留本任务为历史', 409);
|
|
137
149
|
const serialized = JSON.stringify(next);
|
|
@@ -142,8 +154,18 @@ export class CollaborationTaskStore {
|
|
|
142
154
|
writeFileSync(temporary, serialized, { mode: 0o600 });
|
|
143
155
|
renameSync(temporary, this.file);
|
|
144
156
|
this.document = next;
|
|
157
|
+
for (const listener of this.listeners) {
|
|
158
|
+
try {
|
|
159
|
+
listener();
|
|
160
|
+
}
|
|
161
|
+
catch { /* Durable sources allow the integration journal to retry. */ }
|
|
162
|
+
}
|
|
145
163
|
return structuredClone(result);
|
|
146
164
|
}
|
|
165
|
+
integrationRequestEvent(principalId, key, taskId) {
|
|
166
|
+
const request = this.document.requests[`integration:${principalId}:${key}`];
|
|
167
|
+
return request?.taskId === taskId ? structuredClone(this.document.tasks.find(t => t.id === taskId)?.events.find(e => e.id === request.eventId)) : undefined;
|
|
168
|
+
}
|
|
147
169
|
get(taskId) { return structuredClone(this.document.tasks.find(t => t.id === taskId) ?? null); }
|
|
148
170
|
owned(owner) { return structuredClone(this.document.tasks.filter(t => t.ownerServiceId === owner)); }
|
|
149
171
|
children(taskId) {
|
|
@@ -166,7 +188,7 @@ export class CollaborationTaskStore {
|
|
|
166
188
|
attempts: current ? [{ ...current, report: current.report ? { ...current.report, summary: reportSummary, content: '', evidence: undefined } : undefined }] : [],
|
|
167
189
|
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 })),
|
|
168
190
|
decisions: task.decisions.filter(d => d.status === 'pending').map(d => ({ ...d, question: d.question.slice(0, 512), options: [] })),
|
|
169
|
-
events: task.events.filter(e => ['revise', 'request-review'].includes(e.kind) && e.attemptId === task.activeAttemptId).slice(-4).map(e => ({ ...e, content: e.source
|
|
191
|
+
events: task.events.filter(e => ['revise', 'request-review'].includes(e.kind) && e.attemptId === task.activeAttemptId).slice(-4).map(e => ({ ...e, content: ['user', 'integration'].includes(e.source ?? '') && e.kind === 'revise' ? e.content.slice(0, 512) : '' })) };
|
|
170
192
|
}));
|
|
171
193
|
}
|
|
172
194
|
heads(groupIds, owner) { return this.document.tasks.filter(t => groupIds.includes(t.groupId) && t.ownerServiceId === owner).map(t => ({ id: t.id, revision: t.revision })); }
|
|
@@ -179,11 +201,11 @@ export class CollaborationTaskStore {
|
|
|
179
201
|
task.events.push(event);
|
|
180
202
|
return event;
|
|
181
203
|
}
|
|
182
|
-
request(doc, actor, key, payload) {
|
|
204
|
+
request(doc, actor, key, payload, origin) {
|
|
183
205
|
if (typeof key !== 'string' || !key.trim() || key.length > 256)
|
|
184
206
|
throw new CollaborationError('INVALID_IDEMPOTENCY_KEY', '需要有效的请求幂等键');
|
|
185
|
-
const scoped = `${actor ? taskMemberKey(actor) : 'user'}:${key}`;
|
|
186
|
-
const hash = createHash('sha256').update(stable(payload)).digest('hex');
|
|
207
|
+
const scoped = `${origin ? `integration:${origin.integrationId}` : actor ? taskMemberKey(actor) : 'user'}:${key}`;
|
|
208
|
+
const hash = createHash('sha256').update(stable(origin ? [payload, origin] : payload)).digest('hex');
|
|
187
209
|
const previous = doc.requests[scoped];
|
|
188
210
|
if (previous && previous.hash !== hash)
|
|
189
211
|
throw new CollaborationError('IDEMPOTENCY_CONFLICT', '该请求已用于不同内容;请保留原请求或重新开始', 409);
|
|
@@ -222,7 +244,7 @@ export class CollaborationTaskStore {
|
|
|
222
244
|
doc.outbox.push({ id: id(), taskId: task.id, attemptId: attempt?.id ?? null, target: task.coordinator, kind: 'message', threadId: attempt?.threadId ?? task.id,
|
|
223
245
|
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,不推断成员忙闲,不停止其他成员终端。` });
|
|
224
246
|
}
|
|
225
|
-
create(owner, input, actor) {
|
|
247
|
+
create(owner, input, actor, origin) {
|
|
226
248
|
const title = text(input.title, '标题', 640, true), spec = text(input.spec, '任务内容', 32_000, true);
|
|
227
249
|
const constraints = text(input.constraints, '约束'), acceptance = text(input.acceptance, '完成标准');
|
|
228
250
|
if (input.dependsOn !== undefined && (!Array.isArray(input.dependsOn) || input.dependsOn.length > 32 || input.dependsOn.some(v => typeof v !== 'string')))
|
|
@@ -242,7 +264,7 @@ export class CollaborationTaskStore {
|
|
|
242
264
|
throw new CollaborationError('INVALID_REVIEWERS', '评审成员列表无效');
|
|
243
265
|
input.reviewers?.forEach(member);
|
|
244
266
|
return this.transaction(doc => {
|
|
245
|
-
const request = this.request(doc, actor, input.idempotencyKey, input);
|
|
267
|
+
const request = this.request(doc, actor, input.idempotencyKey, input, origin);
|
|
246
268
|
if (request.previous)
|
|
247
269
|
return request.previous;
|
|
248
270
|
if (doc.tasks.length >= 2000)
|
|
@@ -285,7 +307,7 @@ export class CollaborationTaskStore {
|
|
|
285
307
|
const task = { id: id(), ownerServiceId: owner, groupId: input.groupId, title, spec, constraints, acceptance,
|
|
286
308
|
coordinator: parent?.workflow ? parent.coordinator : input.coordinator !== undefined ? input.coordinator : input.parentTaskId ? doc.tasks.find(t => t.id === input.parentTaskId)?.coordinator ?? actor : null,
|
|
287
309
|
parentTaskId: input.parentTaskId ?? null, dependsOn, createdAt: now, updatedAt: now, revision: 1,
|
|
288
|
-
status: 'open', activeAttemptId: null, attempts: [], decisions: [], artifacts: [], events: [], deliveries: [], ...(workflow ? { workflow } : {}) };
|
|
310
|
+
...(origin ? { origin: structuredClone(origin) } : {}), status: 'open', activeAttemptId: null, attempts: [], decisions: [], artifacts: [], events: [], deliveries: [], ...(workflow ? { workflow } : {}) };
|
|
289
311
|
this.event(task, 'created', actor, spec);
|
|
290
312
|
if (parent?.constraints)
|
|
291
313
|
task.constraints = text([parent.constraints, constraints].filter(Boolean).join('\n'), '继承约束');
|
|
@@ -307,9 +329,9 @@ export class CollaborationTaskStore {
|
|
|
307
329
|
this.touchParent(doc, task, '新子任务已创建');
|
|
308
330
|
doc.requests[request.key] = { hash: request.hash, taskId: task.id };
|
|
309
331
|
return task;
|
|
310
|
-
});
|
|
332
|
+
}, origin);
|
|
311
333
|
}
|
|
312
|
-
apply(taskId, input, actor) {
|
|
334
|
+
apply(taskId, input, actor, origin) {
|
|
313
335
|
const content = text(input.content, '内容');
|
|
314
336
|
const summary = text(input.summary, '结果摘要', 2400);
|
|
315
337
|
if (summary && (input.kind !== 'report' || input.status !== 'complete'))
|
|
@@ -322,23 +344,25 @@ export class CollaborationTaskStore {
|
|
|
322
344
|
const task = doc.tasks.find(t => t.id === taskId);
|
|
323
345
|
if (!task)
|
|
324
346
|
throw new CollaborationError('TASK_NOT_FOUND', '任务不存在或无权查看', 404);
|
|
325
|
-
const request = this.request(doc, actor, input.idempotencyKey, [taskId, input]);
|
|
347
|
+
const request = this.request(doc, actor, input.idempotencyKey, [taskId, input], origin);
|
|
326
348
|
if (request.previous)
|
|
327
349
|
return request.previous;
|
|
350
|
+
if (origin && ['accept', 'approve-plan', 'report', 'ask', 'submit-plan', 'review', 'respond'].includes(input.kind))
|
|
351
|
+
throw new CollaborationError('INTEGRATION_OPERATION_DENIED', '此操作需要用户或原执行成员身份', 403);
|
|
328
352
|
const coordination = actor === null || sameMember(actor, task.coordinator);
|
|
329
353
|
if (['assign', 'coordinator', 'close', 'reopen', 'revise', 'request-review', 'coordinate', 'pause', 'resume', 'retry'].includes(input.kind) && !coordination)
|
|
330
354
|
throw new CollaborationError('TASK_PERMISSION_DENIED', '此操作需要用户或当前协调者', 403);
|
|
331
355
|
if (['answer', 'accept', 'approve-plan'].includes(input.kind) && actor !== null)
|
|
332
356
|
throw new CollaborationError('USER_DECISION_REQUIRED', '回答、方案确认与验收需要用户操作', 403);
|
|
333
|
-
if (!['report', 'ask', 'submit-plan', 'review', 'comment', 'answer'].includes(input.kind) && input.expectedRevision !== task.revision)
|
|
357
|
+
if (!['report', 'ask', 'submit-plan', 'review', 'comment', 'respond', 'answer'].includes(input.kind) && input.expectedRevision !== task.revision)
|
|
334
358
|
throw new CollaborationError('TASK_CHANGED', '任务已更新,请查看最新记录后重试', 409);
|
|
335
359
|
const userFollowup = actor === null && input.kind === 'revise' && !!content;
|
|
336
|
-
if (task.status !== 'open' && !['reopen', 'report'].includes(input.kind) && !(task.status === 'accepted' && userFollowup))
|
|
360
|
+
if (task.status !== 'open' && !['reopen', 'report'].includes(input.kind) && !(task.status === 'accepted' && (userFollowup || ['comment', 'respond'].includes(input.kind))))
|
|
337
361
|
throw new CollaborationError('TASK_ARCHIVED', '任务已归档;需要继续时请重新打开', 409);
|
|
338
|
-
if (task.workflow && task.status !== 'open' && input.kind !== 'reopen' && !(task.status === 'accepted' && userFollowup))
|
|
362
|
+
if (task.workflow && task.status !== 'open' && input.kind !== 'reopen' && !(task.status === 'accepted' && (userFollowup || ['comment', 'respond'].includes(input.kind))))
|
|
339
363
|
throw new CollaborationError('TASK_ARCHIVED', '自动协作的已完成版本不能追加执行报告,请先重新打开', 409);
|
|
340
364
|
const root = task.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
|
|
341
|
-
if (root && root.status !== 'open' && !(root.status === 'accepted' && userFollowup))
|
|
365
|
+
if (root && root.status !== 'open' && !(root.status === 'accepted' && (userFollowup || ['comment', 'respond'].includes(input.kind))))
|
|
342
366
|
throw new CollaborationError('GOAL_ARCHIVED', '请先重新打开所属目标,再继续子任务', 409);
|
|
343
367
|
if (userFollowup && task.status === 'accepted') {
|
|
344
368
|
task.status = 'open';
|
|
@@ -418,8 +442,26 @@ export class CollaborationTaskStore {
|
|
|
418
442
|
const event = this.event(task, 'report', actor, content, attempt.id);
|
|
419
443
|
event.reportStatus = input.status;
|
|
420
444
|
event.evidence = input.evidence;
|
|
421
|
-
if (input.status === 'complete')
|
|
422
|
-
task.
|
|
445
|
+
if (input.status === 'complete' && task.status === 'accepted') {
|
|
446
|
+
task.status = 'open';
|
|
447
|
+
delete task.acceptedArtifactId;
|
|
448
|
+
delete task.completionMode;
|
|
449
|
+
}
|
|
450
|
+
if (input.status === 'complete') {
|
|
451
|
+
event.artifactId = id();
|
|
452
|
+
task.artifacts.push({ id: event.artifactId, attemptId: attempt.id, kind: 'result', content, ...(summary ? { summary } : {}), evidence: input.evidence, actor: actor, createdAt: event.createdAt });
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else if (input.kind === 'respond') {
|
|
456
|
+
const original = task.events.find(e => e.id === input.replyToEventId && e.kind === 'comment');
|
|
457
|
+
const originalAttempt = original && task.attempts.find(a => a.id === original.attemptId);
|
|
458
|
+
if (!original || !originalAttempt || originalAttempt.id !== input.attemptId || !sameMember(actor, originalAttempt.assignee))
|
|
459
|
+
throw new CollaborationError('RESPONSE_PERMISSION_DENIED', '请由原追问对应的执行成员回复,并指定原事件和分派', 403);
|
|
460
|
+
if (!content)
|
|
461
|
+
throw new CollaborationError('INVALID_RESPONSE', '答复不能为空');
|
|
462
|
+
const response = this.event(task, 'respond', actor, content, originalAttempt.id);
|
|
463
|
+
response.replyToEventId = original.id;
|
|
464
|
+
response.evidence = input.evidence;
|
|
423
465
|
}
|
|
424
466
|
else if (input.kind === 'ask') {
|
|
425
467
|
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)))
|
|
@@ -439,7 +481,7 @@ export class CollaborationTaskStore {
|
|
|
439
481
|
this.event(task, 'answer', null, content, decision.attemptId);
|
|
440
482
|
const recipient = task.attempts.find(a => a.id === decision.attemptId);
|
|
441
483
|
doc.outbox.push({ id: id(), taskId: task.id, attemptId: recipient.id, target: recipient.assignee, kind: 'message', threadId: recipient.threadId,
|
|
442
|
-
content: `任务「${task.title}」问题已回答\n问题:${decision.question}\n
|
|
484
|
+
content: `任务「${task.title}」问题已回答\n问题:${decision.question}\n${origin ? '外部接入回答' : '用户回答'}:${content}\n任务 ID:${task.id}\n尝试 ID:${recipient.id}` });
|
|
443
485
|
}
|
|
444
486
|
else if (input.kind === 'submit-plan' || input.kind === 'review') {
|
|
445
487
|
if (!content || !actor)
|
|
@@ -519,7 +561,7 @@ export class CollaborationTaskStore {
|
|
|
519
561
|
event.target = recipient.assignee;
|
|
520
562
|
event.deliveryId = id();
|
|
521
563
|
doc.outbox.push({ id: event.deliveryId, taskId: task.id, attemptId: recipient.id, target: recipient.assignee,
|
|
522
|
-
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 和摘要;新版本会重新独立评审。'}` });
|
|
564
|
+
kind: 'message', threadId: recipient.threadId, replyToEventId: event.id, content: `任务「${task.title}」${input.kind === 'revise' ? actor === null ? origin ? '外部接入要求继续跟进' : '用户要求继续跟进' : '修改要求' : '补充反馈'}:\n${content}\n任务 ID:${task.id}\n尝试 ID:${recipient.id}${input.kind === 'comment' ? `\n这是纯咨询,保留当前结果与验收。请用 td collab --session ${recipient.assignee.sessionId} task respond ${task.id} --attempt ${recipient.id} --to-event ${event.id} --content '答复' 关联回复;只有明确提交新的交付物才用 complete。` : task.workflow?.kind === 'goal' ? `\n本轮继续沿用此目标和已交付证据,只推进新增要求。\n${this.goalInstructions(task)}` : '\n按这次要求继续当前分派,重新提交 complete 和摘要;新版本会重新独立评审。'}` });
|
|
523
565
|
if (task.workflow?.kind === 'goal' && task.coordination) {
|
|
524
566
|
task.coordination.notifiedSequence = event.sequence;
|
|
525
567
|
task.coordination.notifiedAt = Date.now();
|
|
@@ -584,21 +626,21 @@ export class CollaborationTaskStore {
|
|
|
584
626
|
}
|
|
585
627
|
else
|
|
586
628
|
throw new CollaborationError('INVALID_TASK_OPERATION', '不支持的任务操作');
|
|
587
|
-
if (['report', 'ask', 'answer', 'submit-plan', 'review', 'accept', 'revise', 'close', 'reopen'].includes(input.kind))
|
|
629
|
+
if (['report', 'respond', 'ask', 'answer', 'submit-plan', 'review', 'accept', 'revise', 'close', 'reopen'].includes(input.kind))
|
|
588
630
|
this.notifyCoordinator(doc, task, task.events.at(-1));
|
|
589
631
|
if (task.events.length > 1000 || task.artifacts.length > 200 || task.attempts.length > 100)
|
|
590
632
|
throw new CollaborationError('TASK_HISTORY_LIMIT', '此任务历史达到上限,请建立后续任务并保留本任务为历史', 409);
|
|
591
633
|
task.revision++;
|
|
592
634
|
task.updatedAt = Math.max(Date.now(), task.updatedAt + 1);
|
|
593
|
-
if (task.workflow &&
|
|
635
|
+
if (task.workflow && !['coordinate', 'comment', 'respond'].includes(input.kind))
|
|
594
636
|
this.touchParent(doc, task, `${input.kind}${input.status ? ` / ${input.status}` : ''}:${content.slice(0, 1500)}`);
|
|
595
637
|
if (task.workflow && ['blocked', 'failed'].includes(input.status ?? ''))
|
|
596
638
|
task.automationIssue = content.slice(0, 1000);
|
|
597
639
|
if (task.workflow && input.kind === 'report' && ['ack', 'working', 'complete'].includes(input.status ?? ''))
|
|
598
640
|
delete task.automationIssue;
|
|
599
|
-
doc.requests[request.key] = { hash: request.hash, taskId };
|
|
641
|
+
doc.requests[request.key] = { hash: request.hash, taskId, eventId: task.events.at(-1)?.id };
|
|
600
642
|
return task;
|
|
601
|
-
});
|
|
643
|
+
}, origin);
|
|
602
644
|
}
|
|
603
645
|
touchParent(doc, task, content) {
|
|
604
646
|
const root = task.workflow?.rootTaskId ? doc.tasks.find(t => t.id === task.workflow.rootTaskId) : undefined;
|
|
@@ -717,7 +759,7 @@ export class CollaborationTaskStore {
|
|
|
717
759
|
this.touchParent(doc, task, '独立评审通过,后续依赖可以接续');
|
|
718
760
|
}
|
|
719
761
|
else if (action === 'review-feedback') {
|
|
720
|
-
const boundary = task.events.filter(e => e.kind === 'revise' && e.source
|
|
762
|
+
const boundary = task.events.filter(e => e.kind === 'revise' && ['user', 'integration'].includes(e.source ?? '') && e.attemptId === task.activeAttemptId).at(-1)?.sequence ?? 0;
|
|
721
763
|
const rounds = task.events.filter(e => e.kind === 'revise' && e.actor === null && e.artifactId && e.attemptId === task.activeAttemptId && e.sequence > boundary).length;
|
|
722
764
|
const blocked = review.verdict === 'blocked' || rounds >= task.workflow.maxRevisions;
|
|
723
765
|
const event = this.event(task, blocked ? 'review-blocked' : 'revise', null, review.content);
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import http from 'node:http';
|
|
4
|
+
import { INTEGRATION_PROTOCOL } from './integrationStore.js';
|
|
5
|
+
import { integrationSocketPath } from './integrationServer.js';
|
|
6
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
7
|
+
import { executeCollaborationCommand, duration } from './collaborationCli.js';
|
|
8
|
+
import { getTermdockVersion } from '../utils/version.js';
|
|
9
|
+
export const INTEGRATION_HELP = `td integration — local scoped integration administration
|
|
10
|
+
groups | list
|
|
11
|
+
create --file <policy.json> --credential-file <private-output.json>
|
|
12
|
+
revoke <principal-id>
|
|
13
|
+
Credentials are saved as a private file and never printed. Workers set
|
|
14
|
+
TERMDOCK_INTEGRATION_CREDENTIAL_FILE, then use td collab --principal <id> …
|
|
15
|
+
Only the local administrator can provision/revoke grants. See docs/integrations.md.`;
|
|
16
|
+
export function readIntegrationCredential(file, principalId) {
|
|
17
|
+
const info = fs.lstatSync(file);
|
|
18
|
+
if (!info.isFile() || info.size > 4096 || (info.mode & 0o077) || process.getuid && info.uid !== process.getuid())
|
|
19
|
+
throw new CollaborationError('INTEGRATION_CREDENTIAL_FILE_DENIED', 'Credential file must be owned by this user and private (0600)', 403);
|
|
20
|
+
const credential = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
|
+
if (credential.id !== principalId || !credential.token || credential.protocol !== INTEGRATION_PROTOCOL)
|
|
22
|
+
throw new CollaborationError('INTEGRATION_CREDENTIAL_MISMATCH', 'Credential identity/protocol does not match the requested principal', 403);
|
|
23
|
+
return credential;
|
|
24
|
+
}
|
|
25
|
+
function headers(credential, adminToken) {
|
|
26
|
+
return { 'Content-Type': 'application/json', 'x-termdock-integration-protocol': String(INTEGRATION_PROTOCOL),
|
|
27
|
+
'x-termdock-cli-version': getTermdockVersion(),
|
|
28
|
+
...(credential ? { 'x-termdock-integration-id': credential.id, authorization: `Bearer ${credential.token}` } : {}),
|
|
29
|
+
...(adminToken ? { 'x-termdock-local-token': adminToken } : {}) };
|
|
30
|
+
}
|
|
31
|
+
function request(socketPath, method, route, body, requestHeaders, timeout = 30000) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const req = http.request({ socketPath, method, path: route, headers: requestHeaders }, res => {
|
|
34
|
+
let raw = '';
|
|
35
|
+
res.setEncoding('utf8');
|
|
36
|
+
res.on('data', chunk => { raw += chunk; if (raw.length > 4 * 1024 * 1024)
|
|
37
|
+
req.destroy(new Error('Integration response exceeds limit')); });
|
|
38
|
+
res.once('end', () => resolve({ statusCode: res.statusCode ?? 500, body: raw }));
|
|
39
|
+
res.once('error', reject);
|
|
40
|
+
});
|
|
41
|
+
req.setTimeout(timeout, () => req.destroy(new Error('Integration request timed out')));
|
|
42
|
+
req.once('error', reject);
|
|
43
|
+
req.end(body === undefined ? undefined : JSON.stringify(body));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function parsed(response) {
|
|
47
|
+
const result = JSON.parse(response.body);
|
|
48
|
+
if (response.statusCode >= 300)
|
|
49
|
+
throw new CollaborationError(result.code ?? 'INTEGRATION_REQUEST_FAILED', result.error ?? 'Integration request failed', response.statusCode);
|
|
50
|
+
return result;
|
|
51
|
+
}
|
|
52
|
+
export async function runIntegrationAdmin(argv, port, adminToken, write) {
|
|
53
|
+
let reserved;
|
|
54
|
+
try {
|
|
55
|
+
const action = argv[0] ?? 'help', options = {};
|
|
56
|
+
if (action === 'help' || action === '--help') {
|
|
57
|
+
write(INTEGRATION_HELP);
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
for (let index = 1; index < argv.length; index += 2) {
|
|
61
|
+
const flag = argv[index];
|
|
62
|
+
if (!['--file', '--credential-file'].includes(flag) || !argv[index + 1] || options[flag])
|
|
63
|
+
throw new Error('Invalid integration arguments');
|
|
64
|
+
options[flag] = argv[index + 1];
|
|
65
|
+
}
|
|
66
|
+
const socketPath = integrationSocketPath(port);
|
|
67
|
+
if (action === 'groups' || action === 'list') {
|
|
68
|
+
if (argv.length !== 1)
|
|
69
|
+
throw new Error('Unexpected arguments');
|
|
70
|
+
write(JSON.stringify(parsed(await request(socketPath, 'GET', action === 'groups' ? '/admin/groups' : '/admin/principals', undefined, headers(undefined, adminToken)))));
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
73
|
+
if (action === 'create') {
|
|
74
|
+
if (!options['--file'] || !options['--credential-file'] || Object.keys(options).length !== 2)
|
|
75
|
+
throw new Error('create requires --file and --credential-file');
|
|
76
|
+
const policy = JSON.parse(fs.readFileSync(options['--file'], 'utf8'));
|
|
77
|
+
const destination = path.resolve(options['--credential-file']);
|
|
78
|
+
fs.mkdirSync(path.dirname(destination), { recursive: true, mode: 0o700 });
|
|
79
|
+
fs.closeSync(fs.openSync(destination, 'wx', 0o600));
|
|
80
|
+
reserved = destination;
|
|
81
|
+
const credential = parsed(await request(socketPath, 'POST', '/admin/principals', policy, headers(undefined, adminToken)));
|
|
82
|
+
fs.writeFileSync(reserved, JSON.stringify(credential), { mode: 0o600 });
|
|
83
|
+
write(JSON.stringify({ ok: true, id: credential.id, credential_file: reserved }));
|
|
84
|
+
reserved = undefined;
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
throw new Error('Unknown integration operation');
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
if (reserved)
|
|
91
|
+
fs.unlinkSync(reserved);
|
|
92
|
+
write(JSON.stringify({ ok: false, code: error instanceof CollaborationError ? error.code : 'INTEGRATION_ADMIN_ERROR', error: error instanceof CollaborationError ? error.message : 'Integration administration failed; inspect arguments and private file paths' }));
|
|
93
|
+
return 1;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export async function revokeIntegration(id, port, adminToken, write) {
|
|
97
|
+
try {
|
|
98
|
+
write(JSON.stringify(parsed(await request(integrationSocketPath(port), 'POST', `/admin/principals/${encodeURIComponent(id)}/revoke`, {}, headers(undefined, adminToken)))));
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
write(JSON.stringify({ ok: false, code: error instanceof CollaborationError ? error.code : 'INTEGRATION_ADMIN_ERROR', error: error instanceof CollaborationError ? error.message : 'Integration revocation failed' }));
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export async function runIntegrationCollab(command, port, io) {
|
|
107
|
+
try {
|
|
108
|
+
const principalId = String(command.options.principal), file = process.env.TERMDOCK_INTEGRATION_CREDENTIAL_FILE;
|
|
109
|
+
if (!file)
|
|
110
|
+
throw new CollaborationError('INTEGRATION_CREDENTIAL_REQUIRED', 'Set TERMDOCK_INTEGRATION_CREDENTIAL_FILE to a private credential file', 401);
|
|
111
|
+
const credential = readIntegrationCredential(file, principalId), socketPath = integrationSocketPath(port);
|
|
112
|
+
const capability = parsed(await request(socketPath, 'GET', '/capabilities', undefined, headers(credential)));
|
|
113
|
+
if (capability.integration_protocol !== INTEGRATION_PROTOCOL)
|
|
114
|
+
throw new CollaborationError('INCOMPATIBLE_INTEGRATION_PROTOCOL', 'CLI/service protocols are incompatible', 409);
|
|
115
|
+
if (command.action === 'capabilities') {
|
|
116
|
+
io.write(JSON.stringify({ ...capability, cli_version: getTermdockVersion() }));
|
|
117
|
+
return 0;
|
|
118
|
+
}
|
|
119
|
+
if (command.action === 'events') {
|
|
120
|
+
const consumer = String(command.options.consumer ?? 'default');
|
|
121
|
+
if (command.operation === 'ack') {
|
|
122
|
+
io.write(JSON.stringify(parsed(await request(socketPath, 'POST', '/events/ack', { consumer, cursor: command.target }, headers(credential)))));
|
|
123
|
+
return 0;
|
|
124
|
+
}
|
|
125
|
+
const params = new URLSearchParams({ consumer });
|
|
126
|
+
if (command.options.group)
|
|
127
|
+
params.set('group', String(command.options.group));
|
|
128
|
+
return subscribe(socketPath, `/events?${params}`, credential, command.options.timeout ? duration(String(command.options.timeout)) : undefined, io.write);
|
|
129
|
+
}
|
|
130
|
+
if (command.action === 'session') {
|
|
131
|
+
const route = command.operation === 'create' ? '/sessions' : `/sessions/${encodeURIComponent(command.target)}${command.operation === 'restore' ? '/restore' : ''}`;
|
|
132
|
+
const body = command.operation === 'create' ? { group_id: command.options.group, launch_profile: command.options['launch-profile'], cwd: command.options.cwd, idempotency_key: command.options['idempotency-key'] }
|
|
133
|
+
: command.operation === 'restore' ? { idempotency_key: command.options['idempotency-key'] } : undefined;
|
|
134
|
+
io.write(JSON.stringify(parsed(await request(socketPath, command.operation === 'get' ? 'GET' : 'POST', route, body, headers(credential)))));
|
|
135
|
+
return 0;
|
|
136
|
+
}
|
|
137
|
+
if (!['task', 'message', 'help'].includes(command.action) || command.action === 'message' && command.operation !== 'get')
|
|
138
|
+
throw new CollaborationError('INTEGRATION_OPERATION_DENIED', 'Command is not exposed to integration identities', 403);
|
|
139
|
+
return executeCollaborationCommand(command, {}, { ...io, request: async (method, endpoint, body, timeout) => {
|
|
140
|
+
const origin = { source: command.options.source, externalActor: command.options['external-actor'] ? JSON.parse(String(command.options['external-actor'])) : undefined,
|
|
141
|
+
externalMessageId: command.options['external-message-id'], metadata: command.options.metadata ? JSON.parse(String(command.options.metadata)) : undefined };
|
|
142
|
+
return request(socketPath, method, endpoint.replace('/api/terminal/operations/orchestration', ''), body === undefined ? undefined : { ...body, origin }, headers(credential), timeout);
|
|
143
|
+
} });
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
io.write(JSON.stringify({ ok: false, code: error instanceof CollaborationError ? error.code : 'INTEGRATION_CONNECTION_FAILED', error: error instanceof CollaborationError ? error.message : 'Local integration connection failed; credentials were not sent to a public service' }));
|
|
147
|
+
return 1;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function subscribe(socketPath, route, credential, timeout, write) {
|
|
151
|
+
const deadline = timeout ? Date.now() + timeout : Infinity;
|
|
152
|
+
let backoff = 1000;
|
|
153
|
+
while (Date.now() < deadline) {
|
|
154
|
+
const outcome = await new Promise(resolve => {
|
|
155
|
+
let timer;
|
|
156
|
+
const req = http.request({ socketPath, method: 'GET', path: route, headers: headers(credential) }, res => {
|
|
157
|
+
res.setEncoding('utf8');
|
|
158
|
+
let pending = '', ended = false;
|
|
159
|
+
res.on('data', chunk => {
|
|
160
|
+
pending += chunk;
|
|
161
|
+
if (pending.length > 2 * 1024 * 1024) {
|
|
162
|
+
ended = true;
|
|
163
|
+
req.destroy();
|
|
164
|
+
resolve('stop');
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
for (;;) {
|
|
168
|
+
const newline = pending.indexOf('\n');
|
|
169
|
+
if (newline < 0)
|
|
170
|
+
break;
|
|
171
|
+
const line = pending.slice(0, newline);
|
|
172
|
+
pending = pending.slice(newline + 1);
|
|
173
|
+
if (!line.trim())
|
|
174
|
+
continue;
|
|
175
|
+
let event;
|
|
176
|
+
try {
|
|
177
|
+
event = JSON.parse(line);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
write(JSON.stringify({ type: 'error', code: 'INVALID_EVENT_RESPONSE', retryable: false }));
|
|
181
|
+
ended = true;
|
|
182
|
+
req.destroy();
|
|
183
|
+
resolve('stop');
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
write(line);
|
|
187
|
+
if (event.type === 'error' && !event.retryable) {
|
|
188
|
+
ended = true;
|
|
189
|
+
req.destroy();
|
|
190
|
+
resolve('stop');
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
res.once('end', () => {
|
|
195
|
+
if ((res.statusCode ?? 500) >= 300 && pending) {
|
|
196
|
+
try {
|
|
197
|
+
const error = JSON.parse(pending);
|
|
198
|
+
write(JSON.stringify(error));
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
write(JSON.stringify({ type: 'error', code: 'INVALID_EVENT_RESPONSE', retryable: false }));
|
|
202
|
+
}
|
|
203
|
+
resolve((res.statusCode ?? 500) >= 500 ? 'retry' : 'stop');
|
|
204
|
+
}
|
|
205
|
+
else if (!ended)
|
|
206
|
+
resolve('retry');
|
|
207
|
+
});
|
|
208
|
+
res.once('close', () => { if (!ended)
|
|
209
|
+
resolve('retry'); });
|
|
210
|
+
});
|
|
211
|
+
req.once('error', () => resolve('retry'));
|
|
212
|
+
req.once('close', () => { if (timer)
|
|
213
|
+
clearTimeout(timer); });
|
|
214
|
+
if (Number.isFinite(deadline))
|
|
215
|
+
timer = setTimeout(() => { resolve('timeout'); req.destroy(); }, Math.max(1, deadline - Date.now()));
|
|
216
|
+
req.end();
|
|
217
|
+
});
|
|
218
|
+
if (outcome === 'stop')
|
|
219
|
+
return 1;
|
|
220
|
+
if (outcome === 'timeout')
|
|
221
|
+
return 0;
|
|
222
|
+
write(JSON.stringify({ type: 'connection', state: 'reconnecting', retry_in_ms: backoff }));
|
|
223
|
+
await new Promise(resolve => setTimeout(resolve, Math.min(backoff, Math.max(0, deadline - Date.now()))));
|
|
224
|
+
backoff = Math.min(backoff * 2, 30000);
|
|
225
|
+
}
|
|
226
|
+
return 0;
|
|
227
|
+
}
|