termdock 1.4.299 → 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-2lXfrgk4.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-EFUb9S3u.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-CXWjSE2q.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-ByMkBaD9.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-DoWaKm94.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-B4hn9z0k.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-C-LJZ8so.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-BY-pfYKk.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-DIENgCZn.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-C1h4e4az.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-B_yOr54K.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-DRD4zxIo.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-CXytnjmD.js → chunk-2Q5K7J3B-BoiPA32P.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Bjmajkf7.js → chunk-5VM5RSS4-G5G1YBLk.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-CQCL8mJG.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-ruHGCADe.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-U18S7rxZ.js → chunk-JWPE2WC7-UO-0iK-G.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-DWnwssfL.js → chunk-POPQ4Y6H-Cox9z-e2.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-DMHj3NSq.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-Dh_ekvFW.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-Bq5wD6iJ.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-CsySWhYg.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-9DKE5wV7.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-9DKE5wV7.js.gz → cynefin-OW5HDTMX-ByWE_fUn.js.gz} +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-B_8fvr9c.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-BOdsFNFT.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-6v_qvAvi.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-c03oOGhD.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-MMIhRYp_.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-B_l-uebl.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-DKoBC5gn.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-Bs3wu8Az.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-mYnHYbKl.js → erDiagram-RLTQ6QDP-DcYHv9hK.js} +1 -1
- package/dist/client/assets/{erDiagram-RLTQ6QDP-mYnHYbKl.js.br → erDiagram-RLTQ6QDP-DcYHv9hK.js.br} +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DcYHv9hK.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-CwCLWbib.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-mse8c-ni.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-BviL29ps.js → gestureArbiter-Do0TIdkh.js} +1 -1
- package/dist/client/assets/gestureArbiter-Do0TIdkh.js.br +0 -0
- package/dist/client/assets/{gestureArbiter-BviL29ps.js.gz → gestureArbiter-Do0TIdkh.js.gz} +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BVUfLjAX.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-XjoFF4BC.js → infoDiagram-27XIBGKW-B6y0nIx8.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-bH299bWL.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-BG0u_X1A.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-DRoc9N-i.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-mh0t6JpE.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-B9KTRuUV.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-Bu2G9zhK.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-Go6Bg5xm.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-C7-MUAHQ.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-DKUIl_U6.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-aoDNlV7J.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-BKuuaF2s.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-BwfCW2bp.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-DbpU9Uig.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-Bxa22XVM.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-C4GvzT9z.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-B9zJJwQw.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-BJbfT-v-.js → sparkles-ButxQzs4.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-fqbw6LZa.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-Hpc28YZY.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-qDieK7Re.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-YOCAWhtT.js → triangle-alert-BRy562K_.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-BJpZlHao.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-DI9vP0dm.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-CQJfj6Ns.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-Cy-lkWBX.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-DzuUkEI_.js +0 -179
- package/dist/client/assets/App-DzuUkEI_.js.br +0 -0
- package/dist/client/assets/App-DzuUkEI_.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-QodxiiCc.js +0 -7
- package/dist/client/assets/ArchitecturePanel-QodxiiCc.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-QodxiiCc.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-2lXfrgk4.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-2lXfrgk4.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-05-Py9N8.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-05-Py9N8.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-05-Py9N8.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-BJe9TUtZ.js +0 -2
- package/dist/client/assets/ComputerControlView-B5vigCoW.js +0 -5
- package/dist/client/assets/ComputerControlView-B5vigCoW.js.br +0 -0
- package/dist/client/assets/ComputerControlView-B5vigCoW.js.gz +0 -0
- package/dist/client/assets/DagPlayground-EFUb9S3u.js.br +0 -0
- package/dist/client/assets/DagPlayground-EFUb9S3u.js.gz +0 -0
- package/dist/client/assets/DiffLab-CXWjSE2q.js.br +0 -0
- package/dist/client/assets/DiffLab-CXWjSE2q.js.gz +0 -0
- package/dist/client/assets/DiffReview-ByMkBaD9.js.br +0 -0
- package/dist/client/assets/DiffReview-ByMkBaD9.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-DoWaKm94.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DoWaKm94.js.gz +0 -0
- package/dist/client/assets/DiffViewer-B4hn9z0k.js.br +0 -0
- package/dist/client/assets/DiffViewer-B4hn9z0k.js.gz +0 -0
- package/dist/client/assets/ModelPreview-C-LJZ8so.js.br +0 -0
- package/dist/client/assets/ModelPreview-C-LJZ8so.js.gz +0 -0
- package/dist/client/assets/RightSidebar-EaMm4prW.js +0 -1678
- package/dist/client/assets/RightSidebar-EaMm4prW.js.br +0 -0
- package/dist/client/assets/RightSidebar-EaMm4prW.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BY-pfYKk.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BY-pfYKk.js.gz +0 -0
- package/dist/client/assets/app-Ch0UDtJ-.js +0 -35
- package/dist/client/assets/app-Ch0UDtJ-.js.br +0 -0
- package/dist/client/assets/app-Ch0UDtJ-.js.gz +0 -0
- package/dist/client/assets/arc-DIENgCZn.js.br +0 -0
- package/dist/client/assets/arc-DIENgCZn.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C1h4e4az.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C1h4e4az.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-B_yOr54K.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-B_yOr54K.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DRD4zxIo.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DRD4zxIo.js.gz +0 -0
- package/dist/client/assets/channel-MOXuUKM7.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-CQCL8mJG.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CQCL8mJG.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-ruHGCADe.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-ruHGCADe.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DMHj3NSq.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DMHj3NSq.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Dh_ekvFW.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Dh_ekvFW.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-CEXMdudw.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CEXMdudw.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CsySWhYg.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CsySWhYg.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-9DKE5wV7.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B_8fvr9c.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B_8fvr9c.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BOdsFNFT.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BOdsFNFT.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-6v_qvAvi.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-6v_qvAvi.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-c03oOGhD.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-c03oOGhD.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-MMIhRYp_.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-MMIhRYp_.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-B_l-uebl.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-B_l-uebl.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKoBC5gn.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKoBC5gn.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Bs3wu8Az.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Bs3wu8Az.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-mYnHYbKl.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CwCLWbib.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CwCLWbib.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-mse8c-ni.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-mse8c-ni.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-BviL29ps.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVUfLjAX.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVUfLjAX.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-bH299bWL.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-bH299bWL.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BG0u_X1A.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BG0u_X1A.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-DRoc9N-i.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-DRoc9N-i.js.gz +0 -0
- package/dist/client/assets/layout-mh0t6JpE.js.br +0 -0
- package/dist/client/assets/layout-mh0t6JpE.js.gz +0 -0
- package/dist/client/assets/linear-B9KTRuUV.js.br +0 -0
- package/dist/client/assets/linear-B9KTRuUV.js.gz +0 -0
- package/dist/client/assets/mermaid.core-Bu2G9zhK.js.br +0 -0
- package/dist/client/assets/mermaid.core-Bu2G9zhK.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-Go6Bg5xm.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-Go6Bg5xm.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-C7-MUAHQ.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-C7-MUAHQ.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DKUIl_U6.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DKUIl_U6.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-aoDNlV7J.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-aoDNlV7J.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BKuuaF2s.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BKuuaF2s.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BwfCW2bp.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BwfCW2bp.js.gz +0 -0
- package/dist/client/assets/rdpSession--y6Q98uo.js +0 -1
- package/dist/client/assets/rdpSession--y6Q98uo.js.br +0 -0
- package/dist/client/assets/rdpSession--y6Q98uo.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-BeG1PAdI.js +0 -1
- package/dist/client/assets/referenceLongPress-BeG1PAdI.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BeG1PAdI.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DbpU9Uig.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DbpU9Uig.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bxa22XVM.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bxa22XVM.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C4GvzT9z.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C4GvzT9z.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B9zJJwQw.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B9zJJwQw.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-fqbw6LZa.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-fqbw6LZa.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BO23Pbrn.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Hpc28YZY.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Hpc28YZY.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BLS7y4Di.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-qDieK7Re.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-qDieK7Re.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BJpZlHao.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BJpZlHao.js.gz +0 -0
- package/dist/client/assets/videoCompression-DI9vP0dm.js.br +0 -0
- package/dist/client/assets/videoCompression-DI9vP0dm.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CQJfj6Ns.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CQJfj6Ns.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Cy-lkWBX.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Cy-lkWBX.js.gz +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
4
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
5
|
+
export const INTEGRATION_PROTOCOL = 1;
|
|
6
|
+
export const INTEGRATION_OPERATIONS = ['task.read', 'task.create', 'task.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore'];
|
|
7
|
+
export function integrationError(code, message, status = 400) { throw new CollaborationError(code, message, status); }
|
|
8
|
+
export function atomicJson(file, value) {
|
|
9
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
10
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
11
|
+
const descriptor = fs.openSync(temporary, 'w', 0o600);
|
|
12
|
+
try {
|
|
13
|
+
fs.fchmodSync(descriptor, 0o600);
|
|
14
|
+
fs.writeFileSync(descriptor, JSON.stringify(value));
|
|
15
|
+
fs.fsyncSync(descriptor);
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
fs.closeSync(descriptor);
|
|
19
|
+
}
|
|
20
|
+
fs.renameSync(temporary, file);
|
|
21
|
+
const directory = fs.openSync(path.dirname(file), 'r');
|
|
22
|
+
try {
|
|
23
|
+
fs.fsyncSync(directory);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
fs.closeSync(directory);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const hash = (value) => createHash('sha256').update(value).digest('hex');
|
|
30
|
+
function identifier(value, label) {
|
|
31
|
+
if (typeof value !== 'string' || !/^[a-zA-Z0-9._-]{1,80}$/.test(value))
|
|
32
|
+
integrationError('INVALID_INTEGRATION_POLICY', `Invalid ${label}`);
|
|
33
|
+
}
|
|
34
|
+
export function validatePolicy(value) {
|
|
35
|
+
identifier(value?.id, 'principal id');
|
|
36
|
+
if (!value.groupId || typeof value.groupId !== 'string' || value.groupId.length > 160)
|
|
37
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'A single groupId is required');
|
|
38
|
+
if (!Array.isArray(value.permissions) || value.permissions.some(p => !INTEGRATION_OPERATIONS.includes(p)))
|
|
39
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Unknown permission');
|
|
40
|
+
if (!Array.isArray(value.launchProfiles) || value.launchProfiles.length > 16)
|
|
41
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'launchProfiles must be an array');
|
|
42
|
+
const ids = new Set();
|
|
43
|
+
for (const profile of value.launchProfiles) {
|
|
44
|
+
identifier(profile?.id, 'profile id');
|
|
45
|
+
identifier(profile.agentSlug, 'agent slug');
|
|
46
|
+
if (ids.has(profile.id))
|
|
47
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Duplicate launch profile');
|
|
48
|
+
ids.add(profile.id);
|
|
49
|
+
if (typeof profile.executable !== 'string' || !path.isAbsolute(profile.executable) || /[\x00-\x1f\x7f]/.test(profile.executable))
|
|
50
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Launcher must be an absolute path');
|
|
51
|
+
if (!Array.isArray(profile.argv) || !Array.isArray(profile.resumeArgv) || [...profile.argv, ...profile.resumeArgv].some(a => typeof a !== 'string' || /[\x00-\x1f\x7f]/.test(a) || a.length > 8192) || profile.argv.length > 128 || profile.resumeArgv.length > 128)
|
|
52
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Invalid structured argv');
|
|
53
|
+
if (profile.resumeArgv.filter(a => a === '{sessionId}').length !== 1 || profile.resumeArgv.filter(a => a === '{launchArgs}').length !== 1 || profile.resumeArgv.some(a => a.includes('{sessionId}') && a !== '{sessionId}') || profile.resumeArgv.includes('--last'))
|
|
54
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'resumeArgv requires one {sessionId} and one {launchArgs}; --last is forbidden');
|
|
55
|
+
if (!Array.isArray(profile.cwdRoots) || !profile.cwdRoots.length || profile.cwdRoots.some(root => typeof root !== 'string' || !path.isAbsolute(root) || /[\x00-\x1f\x7f]/.test(root)))
|
|
56
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'cwdRoots must be absolute paths');
|
|
57
|
+
}
|
|
58
|
+
// Policies contain launcher configuration, never authentication material.
|
|
59
|
+
return structuredClone({ id: value.id, groupId: value.groupId, permissions: value.permissions, launchProfiles: value.launchProfiles });
|
|
60
|
+
}
|
|
61
|
+
export class IntegrationStore {
|
|
62
|
+
file;
|
|
63
|
+
retention;
|
|
64
|
+
doc = { version: 1, principals: [], events: [], sequences: {}, prunedThrough: {}, sources: {}, consumers: {} };
|
|
65
|
+
listeners = new Set();
|
|
66
|
+
constructor(file, retention = 10000) {
|
|
67
|
+
this.file = file;
|
|
68
|
+
this.retention = retention;
|
|
69
|
+
try {
|
|
70
|
+
const document = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
71
|
+
if (document.version !== 1 || !Array.isArray(document.events) || !Array.isArray(document.principals) || !document.sources || !document.consumers || !document.sequences || !document.prunedThrough)
|
|
72
|
+
throw new Error('Invalid integration journal');
|
|
73
|
+
this.doc = document;
|
|
74
|
+
}
|
|
75
|
+
catch (error) {
|
|
76
|
+
if (error.code !== 'ENOENT')
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
write(next) {
|
|
81
|
+
if (Buffer.byteLength(JSON.stringify(next)) > 64 * 1024 * 1024)
|
|
82
|
+
integrationError('EVENT_STORAGE_FULL', 'Integration journal is full; no event was acknowledged', 503);
|
|
83
|
+
atomicJson(this.file, next);
|
|
84
|
+
this.doc = next;
|
|
85
|
+
for (const listener of this.listeners) {
|
|
86
|
+
try {
|
|
87
|
+
listener();
|
|
88
|
+
}
|
|
89
|
+
catch { /* Subscription reports its own failure. */ }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }
|
|
93
|
+
policies() { return this.doc.principals.map(({ tokenHash: _, ...principal }) => structuredClone(principal)); }
|
|
94
|
+
provision(policy) {
|
|
95
|
+
const checked = validatePolicy(policy);
|
|
96
|
+
if (this.doc.principals.some(p => p.id === checked.id))
|
|
97
|
+
integrationError('INTEGRATION_EXISTS', 'Use a new id; credentials cannot be silently replaced', 409);
|
|
98
|
+
const token = randomBytes(32).toString('base64url');
|
|
99
|
+
const next = structuredClone(this.doc);
|
|
100
|
+
next.principals.push({ ...checked, tokenHash: hash(token), revoked: false });
|
|
101
|
+
this.write(next);
|
|
102
|
+
return { principal: checked, token };
|
|
103
|
+
}
|
|
104
|
+
revoke(id) {
|
|
105
|
+
const next = structuredClone(this.doc), principal = next.principals.find(p => p.id === id);
|
|
106
|
+
if (!principal)
|
|
107
|
+
integrationError('INTEGRATION_NOT_FOUND', 'Integration does not exist', 404);
|
|
108
|
+
principal.revoked = true;
|
|
109
|
+
this.write(next);
|
|
110
|
+
}
|
|
111
|
+
authenticate(id, token) {
|
|
112
|
+
const principal = this.doc.principals.find(p => p.id === id);
|
|
113
|
+
const given = Buffer.from(hash(token)), expected = Buffer.from(principal?.tokenHash ?? hash('invalid'));
|
|
114
|
+
if (!principal || !timingSafeEqual(given, expected))
|
|
115
|
+
integrationError('INTEGRATION_UNAUTHORIZED', 'Invalid integration credential', 401);
|
|
116
|
+
if (principal.revoked)
|
|
117
|
+
integrationError('INTEGRATION_REVOKED', 'Integration authorization was revoked', 403);
|
|
118
|
+
return structuredClone(principal);
|
|
119
|
+
}
|
|
120
|
+
authorize(principal, permission, groupId = principal.groupId) {
|
|
121
|
+
const active = this.doc.principals.find(p => p.id === principal.id);
|
|
122
|
+
if (!active || active.revoked)
|
|
123
|
+
integrationError('INTEGRATION_REVOKED', 'Integration authorization was revoked', 403);
|
|
124
|
+
if (groupId !== active.groupId || !active.permissions.includes(permission))
|
|
125
|
+
integrationError('INTEGRATION_PERMISSION_DENIED', 'Operation is outside the integration grant', 403);
|
|
126
|
+
}
|
|
127
|
+
append(updates) {
|
|
128
|
+
const next = structuredClone(this.doc);
|
|
129
|
+
let changed = false;
|
|
130
|
+
for (const update of updates) {
|
|
131
|
+
if (next.sources[update.sourceKey] === update.sourceVersion)
|
|
132
|
+
continue;
|
|
133
|
+
const group = update.event.group_id, sequence = (next.sequences[group] ?? 0) + 1;
|
|
134
|
+
next.events.push({ ...update.event, event_id: update.eventId ?? randomUUID(), sequence, cursor: this.cursor(group, sequence) });
|
|
135
|
+
next.sequences[group] = sequence;
|
|
136
|
+
next.sources[update.sourceKey] = update.sourceVersion;
|
|
137
|
+
changed = true;
|
|
138
|
+
}
|
|
139
|
+
if (!changed)
|
|
140
|
+
return;
|
|
141
|
+
const counts = new Map();
|
|
142
|
+
next.events = next.events.reverse().filter(event => {
|
|
143
|
+
const count = (counts.get(event.group_id) ?? 0) + 1;
|
|
144
|
+
counts.set(event.group_id, count);
|
|
145
|
+
if (count <= this.retention)
|
|
146
|
+
return true;
|
|
147
|
+
next.prunedThrough[event.group_id] = Math.max(next.prunedThrough[event.group_id] ?? 0, event.sequence);
|
|
148
|
+
return false;
|
|
149
|
+
}).reverse();
|
|
150
|
+
this.write(next);
|
|
151
|
+
}
|
|
152
|
+
cursor(group, sequence) { return Buffer.from(JSON.stringify({ v: 1, group, sequence })).toString('base64url'); }
|
|
153
|
+
decode(group, cursor) {
|
|
154
|
+
let value;
|
|
155
|
+
try {
|
|
156
|
+
value = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return integrationError('INVALID_EVENT_CURSOR', 'Invalid cursor');
|
|
160
|
+
}
|
|
161
|
+
if (value.v !== 1 || value.group !== group || !Number.isSafeInteger(value.sequence) || value.sequence < 0 || value.sequence > (this.doc.sequences[group] ?? 0))
|
|
162
|
+
integrationError('INVALID_EVENT_CURSOR', 'Cursor does not belong to this group');
|
|
163
|
+
return value.sequence;
|
|
164
|
+
}
|
|
165
|
+
key(principal, consumer) { identifier(consumer, 'consumer'); return `${principal.id}:${principal.groupId}:${consumer}`; }
|
|
166
|
+
page(principal, consumer, cursor) {
|
|
167
|
+
this.authorize(principal, 'events.read');
|
|
168
|
+
const key = this.key(principal, consumer), group = principal.groupId;
|
|
169
|
+
const floor = this.doc.prunedThrough[group] ?? 0;
|
|
170
|
+
// A new consumer explicitly starts at the earliest retained fact. Existing
|
|
171
|
+
// consumers never silently skip a retention gap.
|
|
172
|
+
if (this.doc.consumers[key] === undefined) {
|
|
173
|
+
const next = structuredClone(this.doc);
|
|
174
|
+
next.consumers[key] = floor;
|
|
175
|
+
this.write(next);
|
|
176
|
+
}
|
|
177
|
+
const after = cursor ? this.decode(group, cursor) : this.doc.consumers[key];
|
|
178
|
+
if (after < floor)
|
|
179
|
+
integrationError('EVENT_RETENTION_GAP', 'Confirmed cursor is older than retained history; reconcile task/session snapshots and use a new consumer', 409);
|
|
180
|
+
return { events: structuredClone(this.doc.events.filter(e => e.group_id === group && e.sequence > after).slice(0, 100)), cursor: this.cursor(group, after) };
|
|
181
|
+
}
|
|
182
|
+
ack(principal, consumer, cursor) {
|
|
183
|
+
this.authorize(principal, 'events.read');
|
|
184
|
+
const key = this.key(principal, consumer), sequence = this.decode(principal.groupId, cursor), confirmed = this.doc.consumers[key];
|
|
185
|
+
if (confirmed === undefined)
|
|
186
|
+
integrationError('EVENT_CONSUMER_NOT_STARTED', 'Subscribe before acknowledging');
|
|
187
|
+
if (sequence < confirmed)
|
|
188
|
+
integrationError('EVENT_ACK_REWIND', 'Acknowledgement cannot rewind');
|
|
189
|
+
if (confirmed < (this.doc.prunedThrough[principal.groupId] ?? 0))
|
|
190
|
+
integrationError('EVENT_RETENTION_GAP', 'Consumer has a retention gap', 409);
|
|
191
|
+
// Cursor is a cumulative declaration that every preceding event is safely
|
|
192
|
+
// in the bridge inbox. The subscription server separately checks it was sent.
|
|
193
|
+
const next = structuredClone(this.doc);
|
|
194
|
+
next.consumers[key] = sequence;
|
|
195
|
+
this.write(next);
|
|
196
|
+
return { cursor, ack_semantics: 'durable_received' };
|
|
197
|
+
}
|
|
198
|
+
cursorSequence(group, cursor) { return this.decode(group, cursor); }
|
|
199
|
+
}
|
package/dist/server/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ if (process.argv.slice(2).some(arg => arg === '--federation-pairing' || arg ===
|
|
|
15
15
|
const { runFederationCli } = await import('./federation/cli.js');
|
|
16
16
|
process.exit(await runFederationCli(process.argv.slice(2)));
|
|
17
17
|
}
|
|
18
|
+
import { INTEGRATION_HELP, runIntegrationAdmin, revokeIntegration, runIntegrationCollab } from './agent/integrationCli.js';
|
|
18
19
|
import { parseNotifyCommand, NOTIFY_HELP, NOTIFY_PROMPT } from './agent/notifyCli.js';
|
|
19
20
|
import { detectLocalSessionContext, resolveLocalCollaborationContext } from './agent/localSessionContext.js';
|
|
20
21
|
import { parseAutomationCommand, executeAutomationCommand, AUTOMATION_HELP } from './agent/automationCli.js';
|
|
@@ -207,6 +208,7 @@ Short commands:
|
|
|
207
208
|
n [message] Progress reminder; run td n for help and Agent instructions
|
|
208
209
|
notify [message] Same as n (see td n --prompt)
|
|
209
210
|
automation Manage scheduled tasks (see td automation --help)
|
|
211
|
+
integration Manage local scoped integrations (see integration help)
|
|
210
212
|
collab status Show this Session's collaboration groups and peers
|
|
211
213
|
collab inbox Read messages without acknowledging (see collab --help)
|
|
212
214
|
collab send <session-id> <message>
|
|
@@ -658,6 +660,7 @@ function parseArgs(argv) {
|
|
|
658
660
|
let pluginHooks;
|
|
659
661
|
let pluginRemove;
|
|
660
662
|
let agentEvent;
|
|
663
|
+
let integration;
|
|
661
664
|
let collab;
|
|
662
665
|
let automation;
|
|
663
666
|
let notify;
|
|
@@ -797,6 +800,10 @@ function parseArgs(argv) {
|
|
|
797
800
|
}
|
|
798
801
|
argv = [];
|
|
799
802
|
}
|
|
803
|
+
else if (command === 'integration') {
|
|
804
|
+
integration = argv.slice(1);
|
|
805
|
+
argv = [];
|
|
806
|
+
}
|
|
800
807
|
else if (command === 'collab') {
|
|
801
808
|
try {
|
|
802
809
|
collab = parseCollaborationCommand(argv.slice(1));
|
|
@@ -1183,6 +1190,7 @@ function parseArgs(argv) {
|
|
|
1183
1190
|
pluginHooks,
|
|
1184
1191
|
pluginRemove,
|
|
1185
1192
|
agentEvent,
|
|
1193
|
+
integration,
|
|
1186
1194
|
collab,
|
|
1187
1195
|
automation,
|
|
1188
1196
|
notify,
|
|
@@ -1434,6 +1442,10 @@ async function runAutomation(command) {
|
|
|
1434
1442
|
}
|
|
1435
1443
|
async function runCollab(command) {
|
|
1436
1444
|
const runningState = getRunningState();
|
|
1445
|
+
if (command.options.principal) {
|
|
1446
|
+
process.exitCode = await runIntegrationCollab(command, runningState?.port ?? 9834, { write: line => console.log(line), stdin: readStdinText });
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1437
1449
|
if (!runningState?.localApiToken) {
|
|
1438
1450
|
// Help is a plain read: it must stay available without a running server.
|
|
1439
1451
|
if (command.action === 'help') {
|
|
@@ -3475,6 +3487,23 @@ async function main() {
|
|
|
3475
3487
|
await runAutomation(options.automation);
|
|
3476
3488
|
return;
|
|
3477
3489
|
}
|
|
3490
|
+
if (options.integration) {
|
|
3491
|
+
const argv = options.integration;
|
|
3492
|
+
if (!argv.length || ['help', '--help'].includes(argv[0])) {
|
|
3493
|
+
console.log(INTEGRATION_HELP);
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
const state = getRunningState();
|
|
3497
|
+
if (!state?.localApiToken) {
|
|
3498
|
+
console.error(JSON.stringify({ ok: false, code: 'SERVICE_UNAVAILABLE', error: 'Termdock is not running' }));
|
|
3499
|
+
process.exitCode = 1;
|
|
3500
|
+
return;
|
|
3501
|
+
}
|
|
3502
|
+
process.exitCode = argv[0] === 'revoke' && argv.length === 2
|
|
3503
|
+
? await revokeIntegration(argv[1], state.port, state.localApiToken, line => console.log(line))
|
|
3504
|
+
: await runIntegrationAdmin(argv, state.port, state.localApiToken, line => console.log(line));
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3478
3507
|
if (options.collab) {
|
|
3479
3508
|
await runCollab(options.collab);
|
|
3480
3509
|
return; // Preserve the receipt exit code and allow piped JSON output to drain.
|
package/dist/server/entry.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import { IntegrationRuntime, integrationSocketPath } from './agent/integrationServer.js';
|
|
1
2
|
import { CollaborationService } from './agent/collaborationService.js';
|
|
2
3
|
import { X509Certificate } from 'node:crypto';
|
|
3
4
|
import { CollaborationPeerTransport, connectCollaborationRpc } from './agent/collaborationPeerTransport.js';
|
|
4
|
-
import { collaborationStore, collaborationTaskStore, deliverPeerCollaboration, collaborationLocalActivity, collaborationDirectorySessions, prepareCollaborationTaskWorker } from './routes/terminal.js';
|
|
5
|
+
import { collaborationStore, collaborationTaskStore, deliverPeerCollaboration, collaborationLocalActivity, collaborationDirectorySessions, prepareCollaborationTaskWorker, integrationSessionAdapter } from './routes/terminal.js';
|
|
5
6
|
import { CollaborationTaskService } from './agent/collaborationTaskService.js';
|
|
6
7
|
import { setPushTargetPeerId } from './notifications/pushService.js';
|
|
7
8
|
import { desktopDirectTargets } from './federation/desktopTargets.js';
|
|
@@ -571,7 +572,7 @@ export function startServer(options = {}) {
|
|
|
571
572
|
return routeInvitations.consume(code, subjectId);
|
|
572
573
|
},
|
|
573
574
|
});
|
|
574
|
-
void federation.then(runtime => {
|
|
575
|
+
void federation.then(async (runtime) => {
|
|
575
576
|
federationServiceId = runtime.serviceId;
|
|
576
577
|
setPushTargetPeerId(runtime.serviceId);
|
|
577
578
|
refreshDirectTargets();
|
|
@@ -634,6 +635,21 @@ export function startServer(options = {}) {
|
|
|
634
635
|
app.locals.collaborationService = collaborationService;
|
|
635
636
|
const collaborationTasks = new CollaborationTaskService(collaborationTaskStore, collaborationStore, collaborationService, deliverPeerCollaboration, (task, template, dependencies) => prepareCollaborationTaskWorker(task, template, dependencies, collaborationService, (taskId, dependencyId, offset) => collaborationTasks.dependencyChunk(taskId, dependencyId, offset)));
|
|
636
637
|
app.locals.collaborationTasks = collaborationTasks;
|
|
638
|
+
if (options.port !== 0) {
|
|
639
|
+
let integration;
|
|
640
|
+
try {
|
|
641
|
+
const port = options.port ?? DEFAULT_PORT;
|
|
642
|
+
integration = new IntegrationRuntime({ directory: path.join(homedir(), '.termdock', 'integration-state', String(port)), socketPath: integrationSocketPath(port), adminToken: options.localApiToken,
|
|
643
|
+
messages: collaborationStore, taskStore: collaborationTaskStore, tasks: collaborationTasks, peers: collaborationService, sessions: integrationSessionAdapter });
|
|
644
|
+
await integration.listen();
|
|
645
|
+
app.locals.integrationRuntime = integration;
|
|
646
|
+
server.once('close', () => { void integration?.close(); });
|
|
647
|
+
}
|
|
648
|
+
catch {
|
|
649
|
+
await integration?.close();
|
|
650
|
+
console.error('[integrations] Local integration endpoint could not start; existing terminal service remains available');
|
|
651
|
+
}
|
|
652
|
+
}
|
|
637
653
|
collaborationTasks.start();
|
|
638
654
|
server.once('close', () => collaborationTasks.close());
|
|
639
655
|
collaborationService.start();
|
|
@@ -786,15 +802,18 @@ export function startServer(options = {}) {
|
|
|
786
802
|
return;
|
|
787
803
|
});
|
|
788
804
|
server.listen(port, host, () => {
|
|
805
|
+
const listeningAddress = server.address();
|
|
806
|
+
const listeningPort = listeningAddress && typeof listeningAddress !== 'string' ? listeningAddress.port : port;
|
|
807
|
+
const listeningHost = listeningAddress && typeof listeningAddress !== 'string' ? listeningAddress.address : host;
|
|
789
808
|
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
790
|
-
console.log(`Termdock server running at ${scheme}://${displayHost}:${
|
|
791
|
-
console.log(`Health check: ${scheme}://${displayHost}:${
|
|
809
|
+
console.log(`Termdock server running at ${scheme}://${displayHost}:${listeningPort}`);
|
|
810
|
+
console.log(`Health check: ${scheme}://${displayHost}:${listeningPort}/health`);
|
|
792
811
|
const onboardingServerState = scheme === 'https'
|
|
793
|
-
? startOnboardingServer({ httpsPort:
|
|
812
|
+
? startOnboardingServer({ httpsPort: listeningPort, caCertPath: options.httpsCaPath })
|
|
794
813
|
: { server: null, url: null };
|
|
795
|
-
void localAccessManager.start({ host, port, scheme, caCertPath: options.httpsCaPath, onboardingPort: options.onboardingPort ??
|
|
814
|
+
void localAccessManager.start({ host: listeningHost, port: listeningPort, scheme, caCertPath: options.httpsCaPath, onboardingPort: options.onboardingPort ?? listeningPort }).then((state) => {
|
|
796
815
|
const publishState = () => {
|
|
797
|
-
const onboardingUrl = onboardingServerState.url ?? state.onboardingUrl;
|
|
816
|
+
const onboardingUrl = state.status === 'loopback-only' ? null : onboardingServerState.url ?? state.onboardingUrl;
|
|
798
817
|
latestLocalAccessState = state;
|
|
799
818
|
latestOnboardingUrl = onboardingUrl;
|
|
800
819
|
if (state.status === 'active') {
|
|
@@ -5064,89 +5064,136 @@ router.post('/upload', async (req, res) => {
|
|
|
5064
5064
|
res.status(400).json({ error: 'Missing dir query parameter', code: 'MISSING_DIR' });
|
|
5065
5065
|
return;
|
|
5066
5066
|
}
|
|
5067
|
+
// New clients can reject an oversized selection before sending any files.
|
|
5068
|
+
// Older clients are still checked against the actual multipart contents.
|
|
5069
|
+
const declaredCount = req.query.fileCount;
|
|
5070
|
+
if (declaredCount !== undefined && (typeof declaredCount !== 'string' || !/^\d+$/.test(declaredCount))) {
|
|
5071
|
+
res.status(400).json({ error: 'Invalid fileCount query parameter', code: 'INVALID_FILE_COUNT' });
|
|
5072
|
+
return;
|
|
5073
|
+
}
|
|
5074
|
+
if (typeof declaredCount === 'string' && Number(declaredCount) > MAX_UPLOAD_FILES) {
|
|
5075
|
+
res.status(413).json({ error: 'Upload limit exceeded', code: 'UPLOAD_LIMIT', files: [], results: [], maxFiles: MAX_UPLOAD_FILES });
|
|
5076
|
+
return;
|
|
5077
|
+
}
|
|
5067
5078
|
const resolvedDir = await pathValidator.validatePathAsync(dir);
|
|
5068
5079
|
const stat = await fs.promises.stat(resolvedDir);
|
|
5069
5080
|
if (!stat.isDirectory()) {
|
|
5070
5081
|
res.status(400).json({ error: 'Target is not a directory', code: 'NOT_A_DIRECTORY' });
|
|
5071
5082
|
return;
|
|
5072
5083
|
}
|
|
5073
|
-
const
|
|
5074
|
-
|
|
5075
|
-
let
|
|
5084
|
+
const results = [];
|
|
5085
|
+
const createdPaths = [];
|
|
5086
|
+
let failure;
|
|
5076
5087
|
let totalSize = 0;
|
|
5088
|
+
const fail = (error, code = 'UPLOAD_FAILED', status = 500) => {
|
|
5089
|
+
failure ??= { error, code, status };
|
|
5090
|
+
};
|
|
5077
5091
|
const bb = busboy({
|
|
5078
5092
|
headers: req.headers,
|
|
5079
5093
|
// Browser FormData sends UTF-8 filenames, while busboy defaults bare
|
|
5080
5094
|
// multipart filename parameters to latin1. Without this, dropped files
|
|
5081
5095
|
// named with CJK characters turn into mojibake like "æ¥è¯¢".
|
|
5082
5096
|
defParamCharset: 'utf8',
|
|
5083
|
-
|
|
5097
|
+
// Enforce the count in the file handler rather than Busboy's files limit:
|
|
5098
|
+
// the latter silently omits the names of files beyond the limit.
|
|
5099
|
+
// Busboy marks a stream truncated as soon as this boundary is reached;
|
|
5100
|
+
// one extra byte lets an exactly-100MB file remain a valid upload.
|
|
5101
|
+
limits: { fileSize: MAX_UPLOAD_SIZE + 1 },
|
|
5084
5102
|
});
|
|
5085
5103
|
const writePromises = [];
|
|
5086
5104
|
bb.on('file', (_fieldname, fileStream, info) => {
|
|
5087
|
-
|
|
5088
|
-
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
aborted = true;
|
|
5094
|
-
fileStream.resume();
|
|
5095
|
-
return;
|
|
5096
|
-
}
|
|
5097
|
-
const { filename } = info;
|
|
5098
|
-
const destName = sanitizeUploadFilename(filename, `file_${fileCount}`);
|
|
5105
|
+
const index = results.length;
|
|
5106
|
+
const destName = sanitizeUploadFilename(info.filename, `file_${index + 1}`);
|
|
5107
|
+
const result = { index, name: destName, status: 'failed' };
|
|
5108
|
+
results.push(result);
|
|
5109
|
+
if (results.length > MAX_UPLOAD_FILES)
|
|
5110
|
+
fail('Upload limit exceeded', 'UPLOAD_LIMIT', 413);
|
|
5099
5111
|
const baseDestPath = path.join(resolvedDir, destName);
|
|
5100
|
-
|
|
5112
|
+
fileStream.on('limit', () => fail('Upload limit exceeded', 'UPLOAD_LIMIT', 413));
|
|
5113
|
+
// Parsing/cancellation can destroy this stream while exclusive open is
|
|
5114
|
+
// pending. The async iterator below will still observe the same error.
|
|
5115
|
+
fileStream.on('error', () => { });
|
|
5101
5116
|
const writePromise = (async () => {
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5117
|
+
let destination;
|
|
5118
|
+
let fileSize = 0;
|
|
5119
|
+
try {
|
|
5120
|
+
if (!failure) {
|
|
5121
|
+
destination = await createUniqueUploadFile(baseDestPath);
|
|
5122
|
+
createdPaths.push(destination.path);
|
|
5123
|
+
}
|
|
5124
|
+
// Keep draining rejected files so every multipart item can be
|
|
5125
|
+
// reported, and Busboy can finish without a dangling write stream.
|
|
5126
|
+
for await (const chunk of fileStream) {
|
|
5127
|
+
const data = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
5128
|
+
fileSize += data.length;
|
|
5129
|
+
totalSize += data.length;
|
|
5130
|
+
if (totalSize > MAX_UPLOAD_SIZE)
|
|
5131
|
+
fail('Upload limit exceeded', 'UPLOAD_LIMIT', 413);
|
|
5132
|
+
if (destination && !failure) {
|
|
5133
|
+
try {
|
|
5134
|
+
await destination.handle.writeFile(data);
|
|
5135
|
+
}
|
|
5136
|
+
catch (error) {
|
|
5137
|
+
// Do not throw from the iterator body: that would destroy the
|
|
5138
|
+
// multipart source before Busboy can drain the rest of it.
|
|
5139
|
+
fail(error instanceof Error ? error.message : 'Upload failed');
|
|
5140
|
+
}
|
|
5114
5141
|
}
|
|
5115
|
-
}
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
}
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
}
|
|
5142
|
+
}
|
|
5143
|
+
if (destination && !failure) {
|
|
5144
|
+
result.status = 'uploaded';
|
|
5145
|
+
result.path = destination.path;
|
|
5146
|
+
result.size = fileSize;
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
catch (error) {
|
|
5150
|
+
fail(error instanceof Error ? error.message : 'Upload failed');
|
|
5151
|
+
fileStream.resume();
|
|
5152
|
+
}
|
|
5153
|
+
finally {
|
|
5154
|
+
try {
|
|
5155
|
+
await destination?.handle.close();
|
|
5156
|
+
}
|
|
5157
|
+
catch (error) {
|
|
5158
|
+
fail(error instanceof Error ? error.message : 'Upload failed');
|
|
5159
|
+
}
|
|
5160
|
+
}
|
|
5126
5161
|
})();
|
|
5127
5162
|
writePromises.push(writePromise);
|
|
5128
5163
|
});
|
|
5129
|
-
bb.
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5164
|
+
const onAborted = () => bb.destroy(new Error('Upload canceled'));
|
|
5165
|
+
req.once('aborted', onAborted);
|
|
5166
|
+
try {
|
|
5167
|
+
await new Promise((resolve, reject) => {
|
|
5168
|
+
bb.once('finish', resolve);
|
|
5169
|
+
bb.once('error', reject);
|
|
5170
|
+
req.pipe(bb);
|
|
5171
|
+
});
|
|
5172
|
+
}
|
|
5173
|
+
catch (error) {
|
|
5174
|
+
fail(error instanceof Error ? error.message : 'Upload failed');
|
|
5175
|
+
}
|
|
5176
|
+
finally {
|
|
5177
|
+
req.off('aborted', onAborted);
|
|
5178
|
+
}
|
|
5179
|
+
await Promise.all(writePromises);
|
|
5180
|
+
if (res.destroyed)
|
|
5181
|
+
fail('Upload canceled');
|
|
5182
|
+
if (failure) {
|
|
5183
|
+
// Paths enter this list only after an exclusive create succeeded. Never
|
|
5184
|
+
// remove an existing file or another concurrent request's destination.
|
|
5185
|
+
await Promise.all(createdPaths.map(filePath => fs.promises.unlink(filePath).catch(error => {
|
|
5186
|
+
if (error.code !== 'ENOENT')
|
|
5187
|
+
throw error;
|
|
5188
|
+
})));
|
|
5189
|
+
const { error, code, status } = failure;
|
|
5190
|
+
if (!res.destroyed)
|
|
5191
|
+
res.status(status).json({ error, code, files: [], maxFiles: MAX_UPLOAD_FILES,
|
|
5192
|
+
results: results.map(result => ({ index: result.index, name: result.name, status: 'failed', error, code })) });
|
|
5193
|
+
return;
|
|
5194
|
+
}
|
|
5195
|
+
const files = results.map(result => ({ name: result.name, path: result.path, size: result.size }));
|
|
5196
|
+
res.status(200).json({ files, results });
|
|
5150
5197
|
}
|
|
5151
5198
|
catch (error) {
|
|
5152
5199
|
if (error instanceof Error && error.code === 'PATH_NOT_ALLOWED') {
|
|
@@ -5157,32 +5204,18 @@ router.post('/upload', async (req, res) => {
|
|
|
5157
5204
|
res.status(500).json({ error: message, code: 'UPLOAD_FAILED' });
|
|
5158
5205
|
}
|
|
5159
5206
|
});
|
|
5160
|
-
async function
|
|
5161
|
-
|
|
5162
|
-
|
|
5163
|
-
|
|
5164
|
-
const
|
|
5165
|
-
|
|
5166
|
-
|
|
5167
|
-
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
}
|
|
5172
|
-
return candidate;
|
|
5173
|
-
}
|
|
5174
|
-
catch {
|
|
5175
|
-
// File doesn't exist — use as-is
|
|
5176
|
-
return filePath;
|
|
5177
|
-
}
|
|
5178
|
-
}
|
|
5179
|
-
async function exists(p) {
|
|
5180
|
-
try {
|
|
5181
|
-
await fs.promises.access(p);
|
|
5182
|
-
return true;
|
|
5183
|
-
}
|
|
5184
|
-
catch {
|
|
5185
|
-
return false;
|
|
5207
|
+
async function createUniqueUploadFile(filePath) {
|
|
5208
|
+
const ext = path.extname(filePath);
|
|
5209
|
+
const base = filePath.slice(0, filePath.length - ext.length);
|
|
5210
|
+
for (let counter = 0;; counter++) {
|
|
5211
|
+
const candidate = counter === 0 ? filePath : `${base}_${counter}${ext}`;
|
|
5212
|
+
try {
|
|
5213
|
+
return { path: candidate, handle: await fs.promises.open(candidate, 'wx') };
|
|
5214
|
+
}
|
|
5215
|
+
catch (error) {
|
|
5216
|
+
if (error.code !== 'EEXIST')
|
|
5217
|
+
throw error;
|
|
5218
|
+
}
|
|
5186
5219
|
}
|
|
5187
5220
|
}
|
|
5188
5221
|
export default router;
|