termdock 1.4.351 → 1.4.353
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client/assets/AndroidMirrorView-C39BgFa_.js +3 -0
- package/dist/client/assets/AndroidMirrorView-C39BgFa_.js.br +0 -0
- package/dist/client/assets/AndroidMirrorView-C39BgFa_.js.gz +0 -0
- package/dist/client/assets/App-AjjtXg93.js +178 -0
- package/dist/client/assets/App-AjjtXg93.js.br +0 -0
- package/dist/client/assets/App-AjjtXg93.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js +9 -0
- package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js +2 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-D_NDzA-t.js +2 -0
- package/dist/client/assets/ComputerControlView-BGvF5NeQ.js +6 -0
- package/dist/client/assets/ComputerControlView-BGvF5NeQ.js.br +0 -0
- package/dist/client/assets/ComputerControlView-BGvF5NeQ.js.gz +0 -0
- package/dist/client/assets/DagPlayground-CKqB6zap.js +1 -0
- package/dist/client/assets/DagPlayground-CKqB6zap.js.br +0 -0
- package/dist/client/assets/DagPlayground-CKqB6zap.js.gz +0 -0
- package/dist/client/assets/{DiffLab-DFut8ZHi.js → DiffLab-CDt8EP6I.js} +2 -2
- package/dist/client/assets/DiffLab-CDt8EP6I.js.br +0 -0
- package/dist/client/assets/DiffLab-CDt8EP6I.js.gz +0 -0
- package/dist/client/assets/DiffReview-YADkwVd6.js +1 -0
- package/dist/client/assets/DiffReview-YADkwVd6.js.br +0 -0
- package/dist/client/assets/DiffReview-YADkwVd6.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-Y9JlI5DQ.js → DiffReviewLab-YqgsRn_K.js} +2 -2
- package/dist/client/assets/DiffReviewLab-YqgsRn_K.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-YqgsRn_K.js.gz +0 -0
- package/dist/client/assets/DiffViewer-CT5_YwM2.js +34 -0
- package/dist/client/assets/DiffViewer-CT5_YwM2.js.br +0 -0
- package/dist/client/assets/DiffViewer-CT5_YwM2.js.gz +0 -0
- package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js +1 -0
- package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js.br +0 -0
- package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js.gz +0 -0
- package/dist/client/assets/MarkdownPreview-DHLp5cqt.js +19 -0
- package/dist/client/assets/MarkdownPreview-DHLp5cqt.js.br +0 -0
- package/dist/client/assets/MarkdownPreview-DHLp5cqt.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-Cv6JgunE.js → ModelPreview-DymFzc55.js} +166 -166
- package/dist/client/assets/ModelPreview-DymFzc55.js.br +0 -0
- package/dist/client/assets/ModelPreview-DymFzc55.js.gz +0 -0
- package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js +1 -0
- package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js.br +0 -0
- package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js.gz +0 -0
- package/dist/client/assets/RightSidebar-eIuDnhJn.js +1662 -0
- package/dist/client/assets/RightSidebar-eIuDnhJn.js.br +0 -0
- package/dist/client/assets/RightSidebar-eIuDnhJn.js.gz +0 -0
- package/dist/client/assets/SessionChatView-C5wXAt7h.js +13 -0
- package/dist/client/assets/SessionChatView-C5wXAt7h.js.br +0 -0
- package/dist/client/assets/SessionChatView-C5wXAt7h.js.gz +0 -0
- package/dist/client/assets/SessionChatView-D8GoESjb.css +1 -0
- package/dist/client/assets/SessionChatView-D8GoESjb.css.br +0 -0
- package/dist/client/assets/SessionChatView-D8GoESjb.css.gz +0 -0
- package/dist/client/assets/SessionResourceSidebar-Czai5Bt3.js +2 -0
- package/dist/client/assets/SessionResourceSidebar-Czai5Bt3.js.br +0 -0
- package/dist/client/assets/SessionResourceSidebar-Czai5Bt3.js.gz +0 -0
- package/dist/client/assets/TodoWorkspace-BftnnEU0.js +1 -0
- package/dist/client/assets/TodoWorkspace-BftnnEU0.js.br +0 -0
- package/dist/client/assets/TodoWorkspace-BftnnEU0.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-CcCffdpj.js → abnfDiagram-VCTEODGH-DI1Y-QAP.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-DI1Y-QAP.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DI1Y-QAP.js.gz +0 -0
- package/dist/client/assets/app-DvMXSNN7.js +36 -0
- package/dist/client/assets/app-DvMXSNN7.js.br +0 -0
- package/dist/client/assets/app-DvMXSNN7.js.gz +0 -0
- package/dist/client/assets/{arc-CXcCuzb3.js → arc-QdJAJx_D.js} +1 -1
- package/dist/client/assets/arc-QdJAJx_D.js.br +0 -0
- package/dist/client/assets/arc-QdJAJx_D.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-CmR7whZz.js → architectureDiagram-5GKGNRK7-Bw2CvoGg.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Bw2CvoGg.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-Bw2CvoGg.js.gz +0 -0
- package/dist/client/assets/arrow-down-CZKuaGwN.js +1 -0
- package/dist/client/assets/arrow-up-CYLC6_to.js +1 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-DODhUpHm.js → blockDiagram-I7D4REHJ-CM2GI6sC.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-CM2GI6sC.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CM2GI6sC.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-BF9eEb2Y.js → c4Diagram-7LVT6UL2-BAB2TKr4.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-BAB2TKr4.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BAB2TKr4.js.gz +0 -0
- package/dist/client/assets/channel-CcDRt0AM.js +1 -0
- package/dist/client/assets/chevron-left-BNby2AjR.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CB7-8Zex.js → chunk-2Q5K7J3B-DI2pKKMQ.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-8Uxe2lki.js → chunk-5VM5RSS4-D5l5zyYl.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-Bv45U-az.js → chunk-F27PBJKO-Japl432v.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-Bv45U-az.js.br → chunk-F27PBJKO-Japl432v.js.br} +0 -0
- package/dist/client/assets/chunk-F27PBJKO-Japl432v.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-CfEErbEd.js → chunk-IMKFNOWR-CDJoLgpw.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-CDJoLgpw.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CDJoLgpw.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-CjUqcCSL.js → chunk-JWPE2WC7-BQ2cpPDE.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-BHbP3O75.js → chunk-POPQ4Y6H-CWjI0yRx.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-CbyJW-OT.js → chunk-SVP7TREG-BzEphMpl.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-BzEphMpl.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BzEphMpl.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-t93EivmS.js → chunk-TICWLB2K-B2hsw7jN.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-B2hsw7jN.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-B2hsw7jN.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-C_Pe8Osb.js → chunk-XXDRQBXY-BEWjHo7s.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-Dz0qH9Hg.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-Dz0qH9Hg.js +1 -0
- package/dist/client/assets/clipboard-6kE6vzDi.js +1 -0
- package/dist/client/assets/clipboardText-B7202UNa.js +1 -0
- package/dist/client/assets/clipboardText-B7202UNa.js.br +0 -0
- package/dist/client/assets/clipboardText-B7202UNa.js.gz +0 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CksG0bvP.js → cose-bilkent-JH36ORCC-DpS77pec.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DpS77pec.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DpS77pec.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-CV3T078G.js → cynefin-OW5HDTMX-D1y5kNwR.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-D1y5kNwR.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-D1y5kNwR.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BQnJabEs.js → cynefinDiagram-5FMLGOSQ-CqTCLyMC.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CqTCLyMC.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CqTCLyMC.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-BeIQp5Pv.js → dagre-GXQ25YYZ-DkJ-kRn6.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-DkJ-kRn6.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-DkJ-kRn6.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-D-Mibxiq.js → diagram-S7CK7UJ4-DenrrE_C.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DenrrE_C.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DenrrE_C.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-eiIMWp9t.js → diagram-UQ7AKVKN-B2ikSD8K.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-B2ikSD8K.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-B2ikSD8K.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-CTVOWtNL.js → diagram-VSXAHHWV-CQcNoZDC.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CQcNoZDC.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CQcNoZDC.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-NKKk6uh7.js → diagram-VX7I27RA-Cq3FaI-1.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-Cq3FaI-1.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Cq3FaI-1.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-DKYdBuNs.js → diagram-Z3DM3KII-BcWReLM8.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-BcWReLM8.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BcWReLM8.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-DaM3D5Ed.js → ebnfDiagram-PWID7BFC-DwwFkjaL.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DwwFkjaL.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DwwFkjaL.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-Df9kQwWg.js → erDiagram-RLTQ6QDP-B1MNvouo.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-B1MNvouo.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-B1MNvouo.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-CWjDvpsN.js → flowDiagram-HODETNUW-B_tIl0Vb.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-B_tIl0Vb.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B_tIl0Vb.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-mKt-CgrH.js → ganttDiagram-EL5Y4UJY-CV0YyZHk.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CV0YyZHk.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CV0YyZHk.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-DszagC-d.js +1 -0
- package/dist/client/assets/gestureArbiter-DszagC-d.js.br +0 -0
- package/dist/client/assets/gestureArbiter-DszagC-d.js.gz +0 -0
- package/dist/client/assets/git-compare-CGiJqTYP.js +1 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js → gitGraphDiagram-WWUBYQGX-DZvnSoyE.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DZvnSoyE.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DZvnSoyE.js.gz +0 -0
- package/dist/client/assets/index-CA1fR_jI.css +1 -0
- package/dist/client/assets/index-CA1fR_jI.css.br +0 -0
- package/dist/client/assets/index-CA1fR_jI.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-CkniyUX9.js → infoDiagram-27XIBGKW-G1OS50nR.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CKo9urjS.js → ishikawaDiagram-5VMMS53U-C0-s6pfy.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C0-s6pfy.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C0-s6pfy.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-Cd4jK-oI.js → journeyDiagram-3NMN7TZE-B-9Omdit.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-B-9Omdit.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-B-9Omdit.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-BuulZGO-.js → kanban-definition-UXKFOSKX-BS_qXegp.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-BS_qXegp.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BS_qXegp.js.gz +0 -0
- package/dist/client/assets/{layout-BpJ2ofrz.js → layout-0NYin1Qz.js} +1 -1
- package/dist/client/assets/{layout-BpJ2ofrz.js.br → layout-0NYin1Qz.js.br} +0 -0
- package/dist/client/assets/layout-0NYin1Qz.js.gz +0 -0
- package/dist/client/assets/{linear-CaSbGLIA.js → linear-CTVKrsxK.js} +1 -1
- package/dist/client/assets/linear-CTVKrsxK.js.br +0 -0
- package/dist/client/assets/linear-CTVKrsxK.js.gz +0 -0
- package/dist/client/assets/link-C5oPMHMJ.js +8 -0
- package/dist/client/assets/link-C5oPMHMJ.js.br +0 -0
- package/dist/client/assets/link-C5oPMHMJ.js.gz +0 -0
- package/dist/client/assets/list-tree-BPg9QdiM.js +1 -0
- package/dist/client/assets/loader-circle-CTtHKNWd.js +1 -0
- package/dist/client/assets/maximize-2-32OfQ0kG.js +1 -0
- package/dist/client/assets/{mermaid.core-BlkorXd6.js → mermaid.core-BgCZP9ke.js} +5 -5
- package/dist/client/assets/mermaid.core-BgCZP9ke.js.br +0 -0
- package/dist/client/assets/mermaid.core-BgCZP9ke.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-BqKhK1NJ.js → mindmap-definition-YA3MSWOX-BjSZRjkg.js} +2 -2
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BjSZRjkg.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BjSZRjkg.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-CXQgj5nw.js +2 -0
- package/dist/client/assets/passwordBootstrap-CXQgj5nw.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-CXQgj5nw.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BlWgUklx.js → pegDiagram-XKGWAZYB-qDU0sU6m.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-qDU0sU6m.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-qDU0sU6m.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-Bp32kHHQ.js → pieDiagram-E7YTZNPT-CZF3MEOJ.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-CZF3MEOJ.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-CZF3MEOJ.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-0qeSh9-W.js → quadrantDiagram-AXDQQJYC-DcZW4RMG.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DcZW4RMG.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DcZW4RMG.js.gz +0 -0
- package/dist/client/assets/quote-CuTMveNr.js +1 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-COmWghVD.js → railroadDiagram-O6MQD6OU-DtQROK31.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DtQROK31.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DtQROK31.js.gz +0 -0
- package/dist/client/assets/rdpSession-a1ZvAyiW.js +1 -0
- package/dist/client/assets/rdpSession-a1ZvAyiW.js.br +0 -0
- package/dist/client/assets/rdpSession-a1ZvAyiW.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-CeohDJAC.js → requirementDiagram-BXWQKSXE-B8yR39lm.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-B8yR39lm.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-B8yR39lm.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-rA5x9iOw.js → sankeyDiagram-P5KCCOFB-D27zuIFg.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-D27zuIFg.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-D27zuIFg.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js → sequenceDiagram-WJ2MYXX4-BI3gSsUa.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BI3gSsUa.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BI3gSsUa.js.gz +0 -0
- package/dist/client/assets/settings-2-GFFo5UwO.js +1 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-Dz1ykd8a.js → sizeCapture-INFHLROL-NBzIIRER.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-NBzIIRER.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-NBzIIRER.js.gz +0 -0
- package/dist/client/assets/{sparkles-0HeBLLwx.js → sparkles-ZXBp-KvJ.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-DHfjTfLj.js → stateDiagram-D77RDMKH-D-D0FB4D.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-D-D0FB4D.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-D-D0FB4D.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-Dk-QpCnJ.js +1 -0
- package/dist/client/assets/sun-CO5F9zS4.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-CAJkSUVz.js → swimlanes-42K2YHIH-CH_jxbV3.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-CH_jxbV3.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-CH_jxbV3.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-C4E5FOdv.js +8 -0
- package/dist/client/assets/swiper-DJjq1TYT.js +1 -0
- package/dist/client/assets/swiper-DJjq1TYT.js.br +0 -0
- package/dist/client/assets/swiper-DJjq1TYT.js.gz +0 -0
- package/dist/client/assets/syntaxHighlight-C9PKWUqs.js +4 -0
- package/dist/client/assets/syntaxHighlight-C9PKWUqs.js.br +0 -0
- package/dist/client/assets/syntaxHighlight-C9PKWUqs.js.gz +0 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-plr9yv12.js → timeline-definition-24CTP7MA-DuiaCBON.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-DuiaCBON.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DuiaCBON.js.gz +0 -0
- package/dist/client/assets/triangle-alert-CuGUScPU.js +1 -0
- package/dist/client/assets/unplug-C0pb-BKU.js +1 -0
- package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js +1 -0
- package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js.br +0 -0
- package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js.gz +0 -0
- package/dist/client/assets/v4-EwEgHOG0.js +1 -0
- package/dist/client/assets/{vennDiagram-4TSXK5OY-Cz04de3S.js → vennDiagram-4TSXK5OY-DUEZFEzo.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-DUEZFEzo.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DUEZFEzo.js.gz +0 -0
- package/dist/client/assets/videoCompression-JAcp0atl.js +3 -0
- package/dist/client/assets/videoCompression-JAcp0atl.js.br +0 -0
- package/dist/client/assets/videoCompression-JAcp0atl.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DnT-q6At.js → wardleyDiagram-VM6X3IG4-CnHmIbot.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CnHmIbot.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CnHmIbot.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-C0y08MEX.js → xychartDiagram-S5SC5T6Z-CDAoHQTr.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CDAoHQTr.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CDAoHQTr.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/collaborationDeliveryWorker.js +11 -0
- package/dist/server/agent/collaborationPrompt.js +4 -2
- package/dist/server/agent/collaborationStore.js +7 -1
- package/dist/server/agent/sessionAttachmentRoutes.js +39 -0
- package/dist/server/agent/sessionChatRoutes.js +64 -0
- package/dist/server/agent/sessionViewModeRoutes.js +72 -0
- package/dist/server/android/recording.js +19 -17
- package/dist/server/cli.js +34 -0
- package/dist/server/entry.js +25 -17
- package/dist/server/federation/accessPolicy.js +8 -3
- package/dist/server/federation/authorization.js +26 -3
- package/dist/server/federation/invitations.js +27 -6
- package/dist/server/federation/requestContext.js +5 -1
- package/dist/server/federation/resourceAccess.js +63 -0
- package/dist/server/federation/runtime.js +60 -17
- package/dist/server/federation/sessionSharing.js +39 -0
- package/dist/server/routes/android.js +36 -7
- package/dist/server/routes/computer.js +14 -4
- package/dist/server/routes/filesystem.js +6 -3
- package/dist/server/routes/rdp.js +4 -3
- package/dist/server/routes/sharedFiles.js +51 -0
- package/dist/server/routes/terminal.js +83 -14
- package/dist/server/routes/vncReadOnly.js +134 -0
- package/dist/server/shared/shareResources.js +37 -0
- package/dist/server/todos/cli.js +91 -0
- package/dist/server/todos/routes.js +73 -0
- package/dist/server/todos/store.js +240 -0
- package/dist/server/todos/types.js +1 -0
- package/dist/server/utils/autoUpdate.js +7 -2
- package/dist/server/utils/clientSnapshotMaintenance.js +200 -0
- package/dist/server/utils/runtimeClient.js +79 -0
- package/dist/server/utils/serverCleanup.js +24 -0
- package/dist/server/utils/storageMaintenance.js +60 -0
- package/dist/server/utils/workspaceCacheMaintenance.js +158 -0
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-BiDKeTpx.js +0 -187
- package/dist/client/assets/App-BiDKeTpx.js.br +0 -0
- package/dist/client/assets/App-BiDKeTpx.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js +0 -9
- package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js +0 -2
- package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-Ce6gFw26.js +0 -2
- package/dist/client/assets/ComputerControlView-BKwuceeG.js +0 -6
- package/dist/client/assets/ComputerControlView-BKwuceeG.js.br +0 -0
- package/dist/client/assets/ComputerControlView-BKwuceeG.js.gz +0 -0
- package/dist/client/assets/DagPlayground-Cshoqo5l.js +0 -1
- package/dist/client/assets/DagPlayground-Cshoqo5l.js.br +0 -0
- package/dist/client/assets/DagPlayground-Cshoqo5l.js.gz +0 -0
- package/dist/client/assets/DiffLab-DFut8ZHi.js.br +0 -0
- package/dist/client/assets/DiffLab-DFut8ZHi.js.gz +0 -0
- package/dist/client/assets/DiffReview-DkEi5c3c.js +0 -1
- package/dist/client/assets/DiffReview-DkEi5c3c.js.br +0 -0
- package/dist/client/assets/DiffReview-DkEi5c3c.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-Y9JlI5DQ.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-Y9JlI5DQ.js.gz +0 -0
- package/dist/client/assets/DiffViewer-3JGF6U0m.js +0 -37
- package/dist/client/assets/DiffViewer-3JGF6U0m.js.br +0 -0
- package/dist/client/assets/DiffViewer-3JGF6U0m.js.gz +0 -0
- package/dist/client/assets/ModelPreview-Cv6JgunE.js.br +0 -0
- package/dist/client/assets/ModelPreview-Cv6JgunE.js.gz +0 -0
- package/dist/client/assets/RightSidebar-akYx_nRq.js +0 -1679
- package/dist/client/assets/RightSidebar-akYx_nRq.js.br +0 -0
- package/dist/client/assets/RightSidebar-akYx_nRq.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CcCffdpj.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CcCffdpj.js.gz +0 -0
- package/dist/client/assets/app-BB0CAQ8D.js +0 -35
- package/dist/client/assets/app-BB0CAQ8D.js.br +0 -0
- package/dist/client/assets/app-BB0CAQ8D.js.gz +0 -0
- package/dist/client/assets/arc-CXcCuzb3.js.br +0 -0
- package/dist/client/assets/arc-CXcCuzb3.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CmR7whZz.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CmR7whZz.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DODhUpHm.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DODhUpHm.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BF9eEb2Y.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-BF9eEb2Y.js.gz +0 -0
- package/dist/client/assets/channel-BKkTA-X_.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-Bv45U-az.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CfEErbEd.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CfEErbEd.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CbyJW-OT.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CbyJW-OT.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-t93EivmS.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-t93EivmS.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-D9nwA51-.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-D9nwA51-.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CksG0bvP.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CksG0bvP.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CV3T078G.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CV3T078G.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BQnJabEs.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BQnJabEs.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BeIQp5Pv.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BeIQp5Pv.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D-Mibxiq.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-D-Mibxiq.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-eiIMWp9t.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-eiIMWp9t.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CTVOWtNL.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CTVOWtNL.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-NKKk6uh7.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-NKKk6uh7.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKYdBuNs.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKYdBuNs.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DaM3D5Ed.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DaM3D5Ed.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Df9kQwWg.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Df9kQwWg.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CWjDvpsN.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CWjDvpsN.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-mKt-CgrH.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-mKt-CgrH.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-C8TBuoU3.js +0 -1
- package/dist/client/assets/gestureArbiter-C8TBuoU3.js.br +0 -0
- package/dist/client/assets/gestureArbiter-C8TBuoU3.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js.gz +0 -0
- package/dist/client/assets/index-3lPto_1X.css +0 -1
- package/dist/client/assets/index-3lPto_1X.css.br +0 -0
- package/dist/client/assets/index-3lPto_1X.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CKo9urjS.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CKo9urjS.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Cd4jK-oI.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Cd4jK-oI.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BuulZGO-.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BuulZGO-.js.gz +0 -0
- package/dist/client/assets/layout-BpJ2ofrz.js.gz +0 -0
- package/dist/client/assets/linear-CaSbGLIA.js.br +0 -0
- package/dist/client/assets/linear-CaSbGLIA.js.gz +0 -0
- package/dist/client/assets/mermaid.core-BlkorXd6.js.br +0 -0
- package/dist/client/assets/mermaid.core-BlkorXd6.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BqKhK1NJ.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-BqKhK1NJ.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-DLUamTjd.js +0 -2
- package/dist/client/assets/passwordBootstrap-DLUamTjd.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-DLUamTjd.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BlWgUklx.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BlWgUklx.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Bp32kHHQ.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Bp32kHHQ.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-0qeSh9-W.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-0qeSh9-W.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-COmWghVD.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-COmWghVD.js.gz +0 -0
- package/dist/client/assets/rdpSession-Bbycnmly.js +0 -1
- package/dist/client/assets/rdpSession-Bbycnmly.js.br +0 -0
- package/dist/client/assets/rdpSession-Bbycnmly.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-CQ9ote5C.js +0 -1
- package/dist/client/assets/referenceLongPress-CQ9ote5C.js.br +0 -0
- package/dist/client/assets/referenceLongPress-CQ9ote5C.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CeohDJAC.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CeohDJAC.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-rA5x9iOw.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-rA5x9iOw.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-Dz1ykd8a.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-Dz1ykd8a.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DHfjTfLj.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DHfjTfLj.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B9AqPaxQ.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-CAJkSUVz.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-CAJkSUVz.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DPbqRuWV.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-plr9yv12.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-plr9yv12.js.gz +0 -0
- package/dist/client/assets/triangle-alert-ibl6FV9I.js +0 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-Cz04de3S.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-Cz04de3S.js.gz +0 -0
- package/dist/client/assets/videoCompression-DCsUD6X6.js +0 -3
- package/dist/client/assets/videoCompression-DCsUD6X6.js.br +0 -0
- package/dist/client/assets/videoCompression-DCsUD6X6.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DnT-q6At.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-DnT-q6At.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C0y08MEX.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C0y08MEX.js.gz +0 -0
- /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css → swiper-C2J0D0kV.css} +0 -0
- /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css.br → swiper-C2J0D0kV.css.br} +0 -0
- /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css.gz → swiper-C2J0D0kV.css.gz} +0 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { encryptedRequestResources, encryptedRequestSubject, hasEncryptedServiceAccess } from '../federation/requestContext.js';
|
|
2
|
+
import { directoryResource } from '../federation/resourceAccess.js';
|
|
1
3
|
import { Router } from 'express';
|
|
2
4
|
import { connectDevice, disconnectDevice, listDevices, resolveAdbBinary } from '../android/adb.js';
|
|
3
5
|
import { createScrcpySession, resolveScrcpyBinaries } from '../android/scrcpy.js';
|
|
@@ -10,7 +12,7 @@ const DEBUG_ANDROID = process.env.TERMDOCK_ANDROID_DEBUG === '1';
|
|
|
10
12
|
export function isValidAndroidSerial(serial) {
|
|
11
13
|
return serial.length <= 128 && SERIAL_PATTERN.test(serial);
|
|
12
14
|
}
|
|
13
|
-
router.get('/devices', async (
|
|
15
|
+
router.get('/devices', async (req, res) => {
|
|
14
16
|
try {
|
|
15
17
|
const devices = await listDevices();
|
|
16
18
|
let scrcpyVersion = null;
|
|
@@ -21,7 +23,8 @@ router.get('/devices', async (_req, res) => {
|
|
|
21
23
|
catch {
|
|
22
24
|
scrcpyVersion = null;
|
|
23
25
|
}
|
|
24
|
-
|
|
26
|
+
const shared = encryptedRequestResources(req);
|
|
27
|
+
res.json({ adbAvailable, scrcpyVersion, devices: shared && !hasEncryptedServiceAccess(req) ? devices.filter(d => shared.android?.some(a => a.serial === d.serial)) : devices });
|
|
25
28
|
}
|
|
26
29
|
catch (error) {
|
|
27
30
|
res.status(503).json({ error: error instanceof Error ? error.message : 'ADB_UNAVAILABLE', code: 'ADB_UNAVAILABLE' });
|
|
@@ -48,6 +51,32 @@ router.post('/disconnect', async (req, res) => {
|
|
|
48
51
|
res.status(400).json({ error: message, code: message });
|
|
49
52
|
}
|
|
50
53
|
});
|
|
54
|
+
const recordingOwners = new WeakMap();
|
|
55
|
+
router.use('/recordings', async (req, res, next) => {
|
|
56
|
+
const resources = encryptedRequestResources(req);
|
|
57
|
+
if (!resources || hasEncryptedServiceAccess(req)) {
|
|
58
|
+
next();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const subject = encryptedRequestSubject(req);
|
|
62
|
+
if (!subject) {
|
|
63
|
+
res.sendStatus(403);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const id = /^\/([^/]+)(?:\/(?:stop|save))?$/.exec(req.path)?.[1];
|
|
68
|
+
const serial = id ? (await androidRecordings.info(id, subject)).serial : req.method === 'GET' ? req.query.serial : req.body?.serial;
|
|
69
|
+
if (typeof serial !== 'string' || !resources.android?.some(device => device.serial === serial))
|
|
70
|
+
throw new Error('PHONE_NOT_SHARED');
|
|
71
|
+
if (req.body?.directory !== undefined && (typeof req.body.directory !== 'string' || !directoryResource(resources, req.body.directory, true)))
|
|
72
|
+
throw new Error('DIRECTORY_NOT_SHARED');
|
|
73
|
+
recordingOwners.set(req, subject);
|
|
74
|
+
next();
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
res.status(403).json({ error: '此手机或录像未获授权', code: 'SHARE_ACCESS_DENIED' });
|
|
78
|
+
}
|
|
79
|
+
});
|
|
51
80
|
router.get('/recordings', async (req, res) => {
|
|
52
81
|
const serial = typeof req.query.serial === 'string' ? req.query.serial : '';
|
|
53
82
|
if (!isValidAndroidSerial(serial)) {
|
|
@@ -55,7 +84,7 @@ router.get('/recordings', async (req, res) => {
|
|
|
55
84
|
return;
|
|
56
85
|
}
|
|
57
86
|
try {
|
|
58
|
-
res.json({ recordings: await androidRecordings.list(serial) });
|
|
87
|
+
res.json({ recordings: await androidRecordings.list(serial, recordingOwners.get(req)) });
|
|
59
88
|
}
|
|
60
89
|
catch (error) {
|
|
61
90
|
res.status(500).json({ error: String(error) });
|
|
@@ -68,7 +97,7 @@ router.post('/recordings', async (req, res) => {
|
|
|
68
97
|
return;
|
|
69
98
|
}
|
|
70
99
|
try {
|
|
71
|
-
res.json(await androidRecordings.start(serial));
|
|
100
|
+
res.json(await androidRecordings.start(serial, recordingOwners.get(req)));
|
|
72
101
|
}
|
|
73
102
|
catch (error) {
|
|
74
103
|
res.status(500).json({ error: error instanceof Error ? error.message : String(error) });
|
|
@@ -76,7 +105,7 @@ router.post('/recordings', async (req, res) => {
|
|
|
76
105
|
});
|
|
77
106
|
router.post('/recordings/:id/stop', async (req, res) => {
|
|
78
107
|
try {
|
|
79
|
-
res.json(await androidRecordings.stop(req.params.id));
|
|
108
|
+
res.json(await androidRecordings.stop(req.params.id, recordingOwners.get(req)));
|
|
80
109
|
}
|
|
81
110
|
catch (error) {
|
|
82
111
|
res.status(400).json({ error: String(error) });
|
|
@@ -86,7 +115,7 @@ router.post('/recordings/:id/save', async (req, res) => {
|
|
|
86
115
|
try {
|
|
87
116
|
const directory = req.body?.directory === undefined ? undefined
|
|
88
117
|
: await (req.pathValidator ?? pathValidator).validateAsync(req.body.directory);
|
|
89
|
-
res.json({ path: await androidRecordings.save(req.params.id, directory) });
|
|
118
|
+
res.json({ path: await androidRecordings.save(req.params.id, directory, recordingOwners.get(req)) });
|
|
90
119
|
}
|
|
91
120
|
catch (error) {
|
|
92
121
|
res.status(400).json({ error: error instanceof Error ? error.message : String(error) });
|
|
@@ -94,7 +123,7 @@ router.post('/recordings/:id/save', async (req, res) => {
|
|
|
94
123
|
});
|
|
95
124
|
router.delete('/recordings/:id', async (req, res) => {
|
|
96
125
|
try {
|
|
97
|
-
await androidRecordings.discard(req.params.id);
|
|
126
|
+
await androidRecordings.discard(req.params.id, recordingOwners.get(req));
|
|
98
127
|
res.json({ ok: true });
|
|
99
128
|
}
|
|
100
129
|
catch (error) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { VncReadOnlyFilter } from './vncReadOnly.js';
|
|
1
2
|
import { Router } from 'express';
|
|
2
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
3
4
|
import { hostname } from 'node:os';
|
|
@@ -99,13 +100,14 @@ export function stopComputerBridges() {
|
|
|
99
100
|
* The eight-record receive window applies TCP backpressure without dropping bytes. */
|
|
100
101
|
export function handleComputerWebSocket(socket, host, options) {
|
|
101
102
|
if (options?.protocol === 'rdp') {
|
|
102
|
-
handleRdpWebSocket(socket, host, Number(options.port ?? 3389));
|
|
103
|
+
handleRdpWebSocket(socket, host, Number(options.port ?? 3389), options.readOnly);
|
|
103
104
|
return;
|
|
104
105
|
}
|
|
105
106
|
if (options?.protocol && options.protocol !== 'vnc') {
|
|
106
107
|
socket.close(4400, 'COMPUTER_INVALID_PROTOCOL');
|
|
107
108
|
return;
|
|
108
109
|
}
|
|
110
|
+
const filter = options?.readOnly ? new VncReadOnlyFilter() : undefined;
|
|
109
111
|
let closed = false;
|
|
110
112
|
let tcp;
|
|
111
113
|
let sent = 0;
|
|
@@ -138,6 +140,13 @@ export function handleComputerWebSocket(socket, host, options) {
|
|
|
138
140
|
break;
|
|
139
141
|
if (sent === acked)
|
|
140
142
|
lastAckAt = Date.now();
|
|
143
|
+
try {
|
|
144
|
+
filter?.serverData(bytes);
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
finish(4403, 'COMPUTER_READ_ONLY_PROTOCOL');
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
141
150
|
socket.send(JSON.stringify({ type: 'data', seq: ++sent, data: bytes.toString('base64') }));
|
|
142
151
|
if (closed)
|
|
143
152
|
break;
|
|
@@ -164,13 +173,14 @@ export function handleComputerWebSocket(socket, host, options) {
|
|
|
164
173
|
const bytes = Buffer.from(message.data, 'base64');
|
|
165
174
|
if (!bytes.length || bytes.length > 64 * 1024 || tcp.writableLength + bytes.length > 1024 * 1024)
|
|
166
175
|
throw new Error('INPUT_LIMIT');
|
|
167
|
-
|
|
176
|
+
for (const permitted of filter ? filter.clientData(bytes) : [bytes])
|
|
177
|
+
tcp.write(permitted);
|
|
168
178
|
}
|
|
169
179
|
else
|
|
170
180
|
throw new Error('INVALID_MESSAGE');
|
|
171
181
|
}
|
|
172
|
-
catch {
|
|
173
|
-
finish(4400, 'COMPUTER_INVALID_MESSAGE');
|
|
182
|
+
catch (error) {
|
|
183
|
+
finish(4400, error instanceof Error && error.message === 'COMPUTER_READ_ONLY_PROTOCOL' ? error.message : 'COMPUTER_INVALID_MESSAGE');
|
|
174
184
|
}
|
|
175
185
|
});
|
|
176
186
|
void (async () => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sharedFileBoundary } from './sharedFiles.js';
|
|
1
2
|
import { getHideGitIgnoredRootsSetting, getGitIgnoreExceptionsSetting } from '../utils/settings.js';
|
|
2
3
|
import { readRecentCommitHistory } from '../utils/recentCommitHistory.js';
|
|
3
4
|
import { AUTH_COOKIE, isSessionValid } from '../utils/authProtection.js';
|
|
@@ -26,6 +27,7 @@ import { convertHeicPreview, HEIC_PREVIEW_MIME_TYPES, isHeicPreviewPath } from '
|
|
|
26
27
|
import { getWatchErrorCode, diffWatchSnapshots, enqueueLatestWatchEvent, inspectLinuxInotifyUsage, isWatchResourceExhaustion, normalizeDirectoryWatchRoots, resolveDirectWatchEventPath, WATCH_IGNORED_NAMES, WATCH_RESOURCE_BACKOFF_MS, } from '../utils/fileWatchPolicy.js';
|
|
27
28
|
import { isResponseWritable, writeResponseChunk } from '../utils/httpResponse.js';
|
|
28
29
|
const router = Router();
|
|
30
|
+
router.use(sharedFileBoundary);
|
|
29
31
|
const MAX_FILE_SIZE = 1024 * 1024; // 1MB
|
|
30
32
|
const MAX_IMAGE_PREVIEW_SIZE = 20 * 1024 * 1024; // 20MB
|
|
31
33
|
const MAX_HTML_PREVIEW_SIZE = 20 * 1024 * 1024; // 20MB
|
|
@@ -5057,9 +5059,9 @@ router.post('/open-in-file-browser', async (req, res) => {
|
|
|
5057
5059
|
res.status(500).json({ error: message, code: 'OPEN_FILE_BROWSER_FAILED' });
|
|
5058
5060
|
}
|
|
5059
5061
|
});
|
|
5060
|
-
|
|
5062
|
+
export async function handleFileUpload(req, res, destinationDirectory) {
|
|
5061
5063
|
try {
|
|
5062
|
-
const dir = typeof req.query.dir === 'string' ? req.query.dir : '';
|
|
5064
|
+
const dir = destinationDirectory ?? (typeof req.query.dir === 'string' ? req.query.dir : '');
|
|
5063
5065
|
if (!dir) {
|
|
5064
5066
|
res.status(400).json({ error: 'Missing dir query parameter', code: 'MISSING_DIR' });
|
|
5065
5067
|
return;
|
|
@@ -5203,7 +5205,8 @@ router.post('/upload', async (req, res) => {
|
|
|
5203
5205
|
const message = error instanceof Error ? error.message : 'Upload failed';
|
|
5204
5206
|
res.status(500).json({ error: message, code: 'UPLOAD_FAILED' });
|
|
5205
5207
|
}
|
|
5206
|
-
}
|
|
5208
|
+
}
|
|
5209
|
+
router.post('/upload', (req, res) => handleFileUpload(req, res));
|
|
5207
5210
|
async function createUniqueUploadFile(filePath) {
|
|
5208
5211
|
const ext = path.extname(filePath);
|
|
5209
5212
|
const base = filePath.slice(0, filePath.length - ext.length);
|
|
@@ -19,7 +19,7 @@ export function rdpBackendAvailable() {
|
|
|
19
19
|
}
|
|
20
20
|
/** Only this server builds guacd's handshake. Credentials arrive inside the
|
|
21
21
|
* authenticated encrypted logical socket, never query strings or logs. */
|
|
22
|
-
export function handleRdpWebSocket(socket, host, port) {
|
|
22
|
+
export function handleRdpWebSocket(socket, host, port, readOnly = false) {
|
|
23
23
|
let closed = false;
|
|
24
24
|
let started = false;
|
|
25
25
|
let ready = false;
|
|
@@ -119,7 +119,7 @@ export function handleRdpWebSocket(socket, host, port) {
|
|
|
119
119
|
width: String(Math.max(640, Math.min(2560, Number(message.width) || 1280))),
|
|
120
120
|
height: String(Math.max(480, Math.min(1600, Number(message.height) || 800))),
|
|
121
121
|
'color-depth': '32', 'disable-audio': 'true', 'enable-drive': 'false',
|
|
122
|
-
'disable-copy':
|
|
122
|
+
'read-only': String(readOnly), 'disable-copy': String(readOnly), 'disable-paste': String(readOnly), 'server-layout': 'en-us-qwerty', 'resize-method': 'display-update',
|
|
123
123
|
};
|
|
124
124
|
void resolveComputerHost(host).then(async (address) => {
|
|
125
125
|
if (closed)
|
|
@@ -174,7 +174,8 @@ export function handleRdpWebSocket(socket, host, port) {
|
|
|
174
174
|
throw new Error('INVALID_INSTRUCTION');
|
|
175
175
|
if (message.opcode === 'size' && (message.args.length !== 2 || message.args.some((value, index) => !/^\d{1,4}$/.test(value) || Number(value) < (index ? 480 : 640) || Number(value) > (index ? 1600 : 2560))))
|
|
176
176
|
throw new Error('INVALID_SIZE');
|
|
177
|
-
|
|
177
|
+
if (!readOnly || ['sync', 'ack', 'nop', 'disconnect'].includes(message.opcode))
|
|
178
|
+
tcp.write(guacamoleInstruction(message.opcode, ...message.args));
|
|
178
179
|
}
|
|
179
180
|
else
|
|
180
181
|
throw new Error('INVALID_MESSAGE');
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { constants, promises as fs } from 'node:fs';
|
|
3
|
+
import { encryptedRequestResources, hasEncryptedServiceAccess } from '../federation/requestContext.js';
|
|
4
|
+
import { resourceHttpAllowed } from '../federation/resourceAccess.js';
|
|
5
|
+
import { pathValidator } from '../utils/pathValidator.js';
|
|
6
|
+
/** Applied before filesystem handlers as a second boundary on their exact URL. */
|
|
7
|
+
export const sharedFileBoundary = Router();
|
|
8
|
+
sharedFileBoundary.use((req, res, next) => {
|
|
9
|
+
const resources = encryptedRequestResources(req);
|
|
10
|
+
if (resources && !hasEncryptedServiceAccess(req) && !resourceHttpAllowed(resources, req.method, req.originalUrl)) {
|
|
11
|
+
res.status(403).json({ error: '此路径或操作未获分享授权', code: 'SHARE_ACCESS_DENIED' });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
next();
|
|
15
|
+
});
|
|
16
|
+
sharedFileBoundary.put('/write', async (req, res) => {
|
|
17
|
+
let handle;
|
|
18
|
+
try {
|
|
19
|
+
const requested = typeof req.query.path === 'string' ? req.query.path : '';
|
|
20
|
+
if (typeof req.body?.content !== 'string' || Buffer.byteLength(req.body.content) > 1024 * 1024 || typeof req.body?.original !== 'string') {
|
|
21
|
+
res.status(400).json({ error: '仅支持修改 1 MB 以内的文本文件' });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const resolved = await pathValidator.validatePathAsync(requested);
|
|
25
|
+
handle = await fs.open(resolved, constants.O_RDWR | constants.O_NOFOLLOW);
|
|
26
|
+
const stat = await handle.stat();
|
|
27
|
+
if (!stat.isFile() || stat.nlink !== 1 || stat.size > 1024 * 1024) {
|
|
28
|
+
res.status(403).json({ error: '此文件无法安全修改' });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const before = await handle.readFile('utf8');
|
|
32
|
+
if (before.includes('\0') || before !== req.body.original) {
|
|
33
|
+
res.status(409).json({ error: '文件已更新,请重新打开后再编辑' });
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
// Explicit position is required after readFile advances the descriptor.
|
|
37
|
+
const bytes = Buffer.from(req.body.content);
|
|
38
|
+
let written = 0;
|
|
39
|
+
while (written < bytes.length)
|
|
40
|
+
written += (await handle.write(bytes, written, bytes.length - written, written)).bytesWritten;
|
|
41
|
+
await handle.truncate(bytes.length);
|
|
42
|
+
await handle.sync();
|
|
43
|
+
res.json({ ok: true });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
res.status(400).json({ error: '无法保存文件,请确认目录授权和文件状态' });
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
await handle?.close();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { sessionViewModeRoutes, SessionViewModeStore } from '../agent/sessionViewModeRoutes.js';
|
|
2
|
+
import { sessionAttachmentRoutes } from '../agent/sessionAttachmentRoutes.js';
|
|
3
|
+
import { TodoStore, TodoError } from '../todos/store.js';
|
|
4
|
+
import { TodoRunner, todoRoutes } from '../todos/routes.js';
|
|
1
5
|
import { TerminalInputQueue } from '../utils/terminalInputQueue.js';
|
|
2
6
|
import { copyTmuxSelection } from '../utils/tmuxCopySelection.js';
|
|
3
7
|
import { isConfirmedEmptyTmuxServer } from '../utils/tmuxEmptyServerProbe.js';
|
|
@@ -66,6 +70,7 @@ import { prepareSupervisionEnable } from '../utils/enableSupervision.js';
|
|
|
66
70
|
import { buildServerHealthState, dismissServerHealth } from '../utils/serverHealth.js';
|
|
67
71
|
import { collaborationRoutes } from '../agent/collaborationRoutes.js';
|
|
68
72
|
import { extrasFromBody } from '../agent/collaborationProtocol.js';
|
|
73
|
+
import { sessionChatRoutes } from '../agent/sessionChatRoutes.js';
|
|
69
74
|
import { CollaborationStore } from '../agent/collaborationStore.js';
|
|
70
75
|
import { COLLAB_NAME_FORBIDDEN, formatCollaborationDelivery } from '../agent/collaborationPrompt.js';
|
|
71
76
|
import { ambiguousIdMessage } from '../agent/collaborationProtocol.js';
|
|
@@ -93,7 +98,7 @@ import { clearPluginAgents, registerPluginAgents } from '../agent/registry.js';
|
|
|
93
98
|
import { notifyAgentTransition, notifyTerminalExit } from '../notifications/pushService.js';
|
|
94
99
|
import { setClientViewingSession } from '../notifications/pushViewers.js';
|
|
95
100
|
import { getQuotaStatus, refreshQuota, startQuotaManager, } from '../quota/QuotaManager.js';
|
|
96
|
-
import { TermdockAutoUpdateManager } from '../utils/autoUpdate.js';
|
|
101
|
+
import { TermdockAutoUpdateManager, TermdockUpdateStateChangedError } from '../utils/autoUpdate.js';
|
|
97
102
|
import { AUTO_TITLE_LONG_RUNNING_DELAY_MS, AUTO_TITLE_MIN_CONTEXT_CHARS, cleanTerminalContext, generateAgentTitle, hasSubstantiveAutoTitleContext, isLongRunningAutoTitleTurnEligible, isNewAgentSessionId, isAutoTitleReevaluationDue, shouldReplaceAutoTitle, } from '../agent/autoTitle.js';
|
|
98
103
|
import { getTitleNamerCatalog, invalidateTitleNamerCatalog, probePluginTitleNamer } from '../agent/titleNamerCatalog.js';
|
|
99
104
|
import { RenderedTerminalContext } from '../agent/renderedTerminalContext.js';
|
|
@@ -105,6 +110,7 @@ const router = express.Router();
|
|
|
105
110
|
const execFileAsync = promisify(execFile);
|
|
106
111
|
const TERMDOCK_DIR = `${os.homedir()}/.termdock`;
|
|
107
112
|
const automationStore = new AutomationStore(`${TERMDOCK_DIR}/automations.json`);
|
|
113
|
+
const todoStore = new TodoStore(`${TERMDOCK_DIR}/todos.json`);
|
|
108
114
|
export const collaborationTaskStore = new CollaborationTaskStore(`${TERMDOCK_DIR}/collaboration-tasks.json`);
|
|
109
115
|
export const collaborationStore = new CollaborationStore(`${TERMDOCK_DIR}/collaboration-groups.json`);
|
|
110
116
|
const collaborationRouting = new CollaborationRoutingStore(`${TERMDOCK_DIR}/collaboration-routing.json`);
|
|
@@ -1210,7 +1216,20 @@ async function markAllPersistedTmuxSessionsDetached() {
|
|
|
1210
1216
|
console.warn('[session-inventory] failed to list tmux sessions for clear-all:', errorMessage);
|
|
1211
1217
|
}
|
|
1212
1218
|
}
|
|
1213
|
-
await Promise.all(Array.from(tmuxSessionNames).map((tmuxSessionName) =>
|
|
1219
|
+
await Promise.all(Array.from(tmuxSessionNames).map((tmuxSessionName) => markTmuxSessionClosed(tmuxSessionName, detachedAt)));
|
|
1220
|
+
}
|
|
1221
|
+
// Record user intent before removing either the backend or its inventory entry.
|
|
1222
|
+
// Unlike display metadata, failure here must stop the close: otherwise a later
|
|
1223
|
+
// scan mistakes the surviving tmux process for an unexpectedly lost GUI session.
|
|
1224
|
+
async function markTmuxSessionClosed(name, closedAt = String(Date.now())) {
|
|
1225
|
+
try {
|
|
1226
|
+
await runTmux(['set-option', '-t', name, TERMDOCK_GUI_DETACHED_AT_OPTION, closedAt]);
|
|
1227
|
+
}
|
|
1228
|
+
catch (error) {
|
|
1229
|
+
const message = getErrorMessage(error);
|
|
1230
|
+
if (!isTmuxSessionMissingMessage(message) && !isTmuxServerMissingMessage(message))
|
|
1231
|
+
throw error;
|
|
1232
|
+
}
|
|
1214
1233
|
}
|
|
1215
1234
|
function getClientSessionView(inventory, frontendSessionId) {
|
|
1216
1235
|
const session = inventory.clientSessions.find((entry) => entry.sessionId === frontendSessionId);
|
|
@@ -6590,6 +6609,28 @@ router.get('/session-inventory', async (_req, res) => {
|
|
|
6590
6609
|
res.status(500).json({ error: errorMessage || 'Failed to build session inventory' });
|
|
6591
6610
|
}
|
|
6592
6611
|
});
|
|
6612
|
+
const todoRuntime = {
|
|
6613
|
+
async validate(config, req) {
|
|
6614
|
+
const launcher = (await listDetectedAgentLaunchers()).find(agent => agent.slug === config.agentSlug);
|
|
6615
|
+
if (!launcher)
|
|
6616
|
+
throw new TodoError('请选择当前服务可用的 Agent');
|
|
6617
|
+
const command = config.command || launcher.command;
|
|
6618
|
+
const cwd = await resolveWorkingDirectory(req ?? {}, config.cwd || os.homedir());
|
|
6619
|
+
return { ...config, command, cwd };
|
|
6620
|
+
},
|
|
6621
|
+
async launch(run, prompt, onSession, req) {
|
|
6622
|
+
const opened = await openInventorySession(req ?? {}, {
|
|
6623
|
+
name: `${run.kind === 'collect' ? '收集' : '待办'} · ${run.name}`.slice(0, 120), customName: true, mode: 'tmux', cwd: run.config.cwd,
|
|
6624
|
+
});
|
|
6625
|
+
const sessionId = opened.session.sessionId;
|
|
6626
|
+
onSession(sessionId);
|
|
6627
|
+
const launchGuard = await beginAgentLaunchCheck(sessionId, run.config.agentSlug);
|
|
6628
|
+
await writeAutomaticTerminalPrompt(sessionId, run.config.command, undefined, launchGuard);
|
|
6629
|
+
await deliverAutomationPromptWhenReady(opened.terminalSession.sessionId, prompt);
|
|
6630
|
+
},
|
|
6631
|
+
};
|
|
6632
|
+
const todoRunner = new TodoRunner(todoStore, todoRuntime);
|
|
6633
|
+
router.use('/operations/todos', todoRoutes(todoStore, todoRunner, todoRuntime));
|
|
6593
6634
|
router.get('/operations/automations', (_req, res) => {
|
|
6594
6635
|
res.json({ automations: automationStore.list(), runs: automationStore.listRuns() });
|
|
6595
6636
|
});
|
|
@@ -6713,6 +6754,12 @@ export function collaborationLocalActivity() {
|
|
|
6713
6754
|
});
|
|
6714
6755
|
}
|
|
6715
6756
|
export function deliverPeerCollaboration(sessionId) { void tryDeliverCollaborationInbox(sessionId); }
|
|
6757
|
+
router.use(sessionViewModeRoutes(new SessionViewModeStore(`${TERMDOCK_DIR}/session-view-modes.json`), resolveStableFederationSessionId));
|
|
6758
|
+
router.use(sessionAttachmentRoutes(`${TERMDOCK_DIR}/chat-attachments`, resolveStableFederationSessionId));
|
|
6759
|
+
router.use(sessionChatRoutes({ store: collaborationStore, resolve: id => {
|
|
6760
|
+
const stable = resolveStableFederationSessionId(id);
|
|
6761
|
+
return stable ? collaborationDirectorySessions().find(s => s.sessionId === stable) : undefined;
|
|
6762
|
+
}, deliver: deliverPeerCollaboration }));
|
|
6716
6763
|
router.use('/operations/collaboration-tasks', collaborationTaskRoutes({ agent: false, store: collaborationStore, resolveSession: resolveFrontendSessionId }));
|
|
6717
6764
|
router.use('/operations/orchestration/tasks', collaborationTaskRoutes({ agent: true, store: collaborationStore, resolveSession: resolveFrontendSessionId }));
|
|
6718
6765
|
router.use('/operations', collaborationGroupRoutes({ store: collaborationStore,
|
|
@@ -7519,12 +7566,12 @@ router.delete('/session-inventory/sessions/:frontendSessionId', async (req, res)
|
|
|
7519
7566
|
return res.status(400).json({ error: 'frontendSessionId is required' });
|
|
7520
7567
|
}
|
|
7521
7568
|
const removedSession = globalSessionState.sessions.find((session) => session.sessionId === frontendSessionId) ?? null;
|
|
7569
|
+
if (removedSession?.mode === 'tmux' && removedSession.tmuxSessionName) {
|
|
7570
|
+
await markTmuxSessionClosed(removedSession.tmuxSessionName);
|
|
7571
|
+
}
|
|
7522
7572
|
archiveAgentResumeRecord(removedSession, 'closed');
|
|
7523
7573
|
const changed = removeGlobalSessionRecord(frontendSessionId);
|
|
7524
7574
|
if (changed) {
|
|
7525
|
-
if (removedSession?.mode === 'tmux' && removedSession.tmuxSessionName) {
|
|
7526
|
-
await setTmuxOption(removedSession.tmuxSessionName, TERMDOCK_GUI_DETACHED_AT_OPTION, String(Date.now()));
|
|
7527
|
-
}
|
|
7528
7575
|
await persistGlobalStateNow();
|
|
7529
7576
|
broadcastClientState();
|
|
7530
7577
|
}
|
|
@@ -7651,13 +7698,20 @@ router.post('/update/check', async (_req, res) => {
|
|
|
7651
7698
|
res.status(503).json({ error: error instanceof Error ? error.message : String(error) });
|
|
7652
7699
|
}
|
|
7653
7700
|
});
|
|
7654
|
-
router.post('/update/restart', async (
|
|
7701
|
+
router.post('/update/restart', async (req, res) => {
|
|
7655
7702
|
try {
|
|
7656
|
-
const
|
|
7703
|
+
const version = req.body?.version;
|
|
7704
|
+
if (version !== undefined && typeof version !== 'string')
|
|
7705
|
+
return res.status(400).json({ error: 'Invalid update version' });
|
|
7706
|
+
const state = npmAutoUpdateManager.confirmRestart(version);
|
|
7657
7707
|
res.status(202).json(state);
|
|
7658
7708
|
}
|
|
7659
7709
|
catch (error) {
|
|
7660
|
-
res.status(409).json({
|
|
7710
|
+
res.status(409).json({
|
|
7711
|
+
error: error instanceof Error ? error.message : String(error),
|
|
7712
|
+
code: error instanceof TermdockUpdateStateChangedError ? 'UPDATE_STATE_CHANGED' : 'UPDATE_RESTART_FAILED',
|
|
7713
|
+
state: npmAutoUpdateManager.getState(),
|
|
7714
|
+
});
|
|
7661
7715
|
}
|
|
7662
7716
|
});
|
|
7663
7717
|
// 「服务最近出过什么事」:红点与设置面板的唯一数据源。
|
|
@@ -8742,16 +8796,30 @@ router.post('/:sessionId/tmux', async (req, res) => {
|
|
|
8742
8796
|
return res.status(500).json({ error: errorMessage || 'Failed to execute tmux action' });
|
|
8743
8797
|
}
|
|
8744
8798
|
});
|
|
8745
|
-
router.delete('/:sessionId', (req, res) => {
|
|
8799
|
+
router.delete('/:sessionId', async (req, res) => {
|
|
8746
8800
|
const { sessionId } = req.params;
|
|
8747
8801
|
const session = terminalSessions.get(sessionId);
|
|
8748
|
-
|
|
8749
|
-
return res.json({ success: true, alreadyGone: true });
|
|
8750
|
-
}
|
|
8802
|
+
const records = globalSessionState.sessions.filter(record => record.backendSessionId === sessionId);
|
|
8751
8803
|
try {
|
|
8752
|
-
|
|
8804
|
+
const tmuxNames = new Set(records.flatMap(record => record.mode === 'tmux' && record.tmuxSessionName ? [record.tmuxSessionName] : []));
|
|
8805
|
+
if (session?.mode === 'tmux' && session.tmuxSessionName)
|
|
8806
|
+
tmuxNames.add(session.tmuxSessionName);
|
|
8807
|
+
for (const name of tmuxNames)
|
|
8808
|
+
await markTmuxSessionClosed(name);
|
|
8809
|
+
if (session)
|
|
8810
|
+
cleanupSession(sessionId, { killProcess: true });
|
|
8811
|
+
// Complete the close on the server even if the page exits before its second
|
|
8812
|
+
// DELETE (inventory removal). A stale/missing wrapper can still own a record.
|
|
8813
|
+
for (const record of records) {
|
|
8814
|
+
archiveAgentResumeRecord(record, 'closed');
|
|
8815
|
+
removeGlobalSessionRecord(record.sessionId);
|
|
8816
|
+
}
|
|
8817
|
+
if (records.length) {
|
|
8818
|
+
await persistGlobalStateNow();
|
|
8819
|
+
broadcastClientState();
|
|
8820
|
+
}
|
|
8753
8821
|
console.log(`Closed terminal session: ${sessionId}`);
|
|
8754
|
-
res.json({ success: true });
|
|
8822
|
+
res.json({ success: true, ...(!session ? { alreadyGone: true } : {}) });
|
|
8755
8823
|
}
|
|
8756
8824
|
catch (error) {
|
|
8757
8825
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -9587,6 +9655,7 @@ const reconcileTimer = setInterval(() => {
|
|
|
9587
9655
|
// Don't keep the event loop alive for housekeeping.
|
|
9588
9656
|
reconcileTimer.unref?.();
|
|
9589
9657
|
const automationTimer = setInterval(() => {
|
|
9658
|
+
void todoRunner.tick().catch(error => console.warn('[todo-collector]', getErrorMessage(error)));
|
|
9590
9659
|
for (const automation of automationStore.due()) {
|
|
9591
9660
|
void runAgentAutomation(automation).catch((error) => {
|
|
9592
9661
|
console.warn(`[automation] ${automation.id} failed:`, getErrorMessage(error));
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/** Stateful RFB client filter. Handshake bytes are length-delimited; after
|
|
2
|
+
* ClientInit only framebuffer negotiation/requests pass. Never scan raw bytes
|
|
3
|
+
* for opcodes: credentials and fragmented records can contain any byte. */
|
|
4
|
+
export class VncReadOnlyFilter {
|
|
5
|
+
client = Buffer.alloc(0);
|
|
6
|
+
server = Buffer.alloc(0);
|
|
7
|
+
stage = 'version';
|
|
8
|
+
serverStage = 'version';
|
|
9
|
+
security = 0;
|
|
10
|
+
authBytes = 0;
|
|
11
|
+
offered = [];
|
|
12
|
+
serverData(bytes) {
|
|
13
|
+
if (this.serverStage === 'done')
|
|
14
|
+
return;
|
|
15
|
+
this.server = Buffer.concat([this.server, bytes]);
|
|
16
|
+
if (this.server.length > 65536)
|
|
17
|
+
throw new Error('COMPUTER_INVALID_MESSAGE');
|
|
18
|
+
if (this.serverStage === 'version' && this.server.length >= 12) {
|
|
19
|
+
this.server = this.server.subarray(12);
|
|
20
|
+
this.serverStage = 'security';
|
|
21
|
+
}
|
|
22
|
+
if (this.serverStage === 'security' && this.server.length >= 1 && this.server.length >= 1 + this.server[0]) {
|
|
23
|
+
this.offered = [...this.server.subarray(1, 1 + this.server[0])];
|
|
24
|
+
this.server = this.server.subarray(1 + this.server[0]);
|
|
25
|
+
this.serverStage = 'challenge';
|
|
26
|
+
}
|
|
27
|
+
if (this.serverStage === 'challenge' && this.security === 30 && this.server.length >= 4) {
|
|
28
|
+
const length = this.server.readUInt16BE(2);
|
|
29
|
+
if (length < 16 || length > 512)
|
|
30
|
+
throw new Error('COMPUTER_INVALID_MESSAGE');
|
|
31
|
+
this.authBytes = 128 + length;
|
|
32
|
+
this.serverStage = 'done';
|
|
33
|
+
this.server = Buffer.alloc(0);
|
|
34
|
+
}
|
|
35
|
+
else if (this.serverStage === 'challenge' && [1, 2].includes(this.security)) {
|
|
36
|
+
this.serverStage = 'done';
|
|
37
|
+
this.server = Buffer.alloc(0);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
clientData(bytes) {
|
|
41
|
+
this.client = Buffer.concat([this.client, bytes]);
|
|
42
|
+
if (this.client.length > 1024 * 1024)
|
|
43
|
+
throw new Error('COMPUTER_INVALID_MESSAGE');
|
|
44
|
+
const output = [];
|
|
45
|
+
const consume = (n, forward = true) => { if (forward)
|
|
46
|
+
output.push(this.client.subarray(0, n)); this.client = this.client.subarray(n); };
|
|
47
|
+
while (this.client.length) {
|
|
48
|
+
if (this.stage === 'version') {
|
|
49
|
+
if (this.client.length < 12)
|
|
50
|
+
break;
|
|
51
|
+
// 3.8 has explicit security selection/results; fail closed for legacy.
|
|
52
|
+
if (this.client.subarray(0, 12).toString() !== 'RFB 003.008\n')
|
|
53
|
+
throw new Error('COMPUTER_READ_ONLY_PROTOCOL');
|
|
54
|
+
consume(12);
|
|
55
|
+
this.stage = 'security';
|
|
56
|
+
}
|
|
57
|
+
else if (this.stage === 'security') {
|
|
58
|
+
this.security = this.client[0];
|
|
59
|
+
if (![1, 2, 30].includes(this.security) || !this.offered.includes(this.security))
|
|
60
|
+
throw new Error('COMPUTER_READ_ONLY_PROTOCOL');
|
|
61
|
+
this.authBytes = this.security === 2 ? 16 : 0;
|
|
62
|
+
consume(1);
|
|
63
|
+
this.stage = this.security === 1 ? 'init' : 'auth';
|
|
64
|
+
this.serverData(Buffer.alloc(0));
|
|
65
|
+
}
|
|
66
|
+
else if (this.stage === 'auth') {
|
|
67
|
+
if (!this.authBytes || this.client.length < this.authBytes)
|
|
68
|
+
break;
|
|
69
|
+
consume(this.authBytes);
|
|
70
|
+
this.stage = 'init';
|
|
71
|
+
}
|
|
72
|
+
else if (this.stage === 'init') {
|
|
73
|
+
if (this.client[0] !== 1)
|
|
74
|
+
throw new Error('COMPUTER_READ_ONLY_PROTOCOL');
|
|
75
|
+
consume(1);
|
|
76
|
+
this.stage = 'messages';
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const type = this.client[0];
|
|
80
|
+
let length;
|
|
81
|
+
if (type === 0)
|
|
82
|
+
length = 20;
|
|
83
|
+
else if (type === 2) {
|
|
84
|
+
if (this.client.length < 4)
|
|
85
|
+
break;
|
|
86
|
+
length = 4 + 4 * this.client.readUInt16BE(2);
|
|
87
|
+
}
|
|
88
|
+
else if (type === 3 || type === 150)
|
|
89
|
+
length = 10;
|
|
90
|
+
else if (type === 4)
|
|
91
|
+
length = 8;
|
|
92
|
+
else if (type === 5)
|
|
93
|
+
length = 6;
|
|
94
|
+
else if (type === 6) {
|
|
95
|
+
if (this.client.length < 8)
|
|
96
|
+
break;
|
|
97
|
+
length = 8 + Math.abs(this.client.readInt32BE(4));
|
|
98
|
+
}
|
|
99
|
+
else if (type === 248) {
|
|
100
|
+
if (this.client.length < 9)
|
|
101
|
+
break;
|
|
102
|
+
length = 9 + this.client[8];
|
|
103
|
+
}
|
|
104
|
+
else if (type === 251) {
|
|
105
|
+
if (this.client.length < 8)
|
|
106
|
+
break;
|
|
107
|
+
length = 8 + 16 * this.client[6];
|
|
108
|
+
}
|
|
109
|
+
else if (type === 255)
|
|
110
|
+
length = 12;
|
|
111
|
+
else
|
|
112
|
+
throw new Error('COMPUTER_READ_ONLY_PROTOCOL');
|
|
113
|
+
if (length > 1024 * 1024)
|
|
114
|
+
throw new Error('COMPUTER_INVALID_MESSAGE');
|
|
115
|
+
if (this.client.length < length)
|
|
116
|
+
break;
|
|
117
|
+
if (type === 2) {
|
|
118
|
+
// Do not advertise clipboard extensions to a view-only client.
|
|
119
|
+
const encodings = [];
|
|
120
|
+
for (let i = 4; i < length; i += 4)
|
|
121
|
+
if (this.client.readUInt32BE(i) !== 0xc0a1e5ce)
|
|
122
|
+
encodings.push(this.client.subarray(i, i + 4));
|
|
123
|
+
const header = Buffer.from([2, 0, 0, 0]);
|
|
124
|
+
header.writeUInt16BE(encodings.length, 2);
|
|
125
|
+
output.push(Buffer.concat([header, ...encodings]));
|
|
126
|
+
consume(length, false);
|
|
127
|
+
}
|
|
128
|
+
else
|
|
129
|
+
consume(length, [0, 3, 150, 248].includes(type));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return output;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
|
|
2
|
+
const text = (v, max) => typeof v === 'string' && v.length > 0 && v.length <= max && !/[\x00-\x1f\x7f]/.test(v);
|
|
3
|
+
export function validShareResources(value) {
|
|
4
|
+
if (!record(value) || Object.keys(value).some(key => !['android', 'computers', 'directories'].includes(key)))
|
|
5
|
+
return false;
|
|
6
|
+
return Object.entries(value).every(([key, items]) => Array.isArray(items) && items.length <= 64 && items.every(item => {
|
|
7
|
+
if (!record(item))
|
|
8
|
+
return false;
|
|
9
|
+
if (key === 'android')
|
|
10
|
+
return Object.keys(item).every(k => ['serial', 'access'].includes(k)) && text(item.serial, 128) && /^(?:[0-9a-zA-Z_.:\-]+|\[[0-9a-fA-F:.]+\]:\d{1,5})$/.test(item.serial) && ['view', 'control'].includes(String(item.access));
|
|
11
|
+
if (key === 'computers')
|
|
12
|
+
return Object.keys(item).every(k => ['host', 'protocol', 'port', 'access'].includes(k)) && text(item.host, 253) && /^[a-zA-Z0-9:.\-]+$/.test(item.host) && ['vnc', 'rdp'].includes(String(item.protocol)) && Number.isInteger(item.port) && Number(item.port) > 0 && Number(item.port) <= 65535 && (item.protocol !== 'vnc' || item.port === 5900) && ['view', 'control'].includes(String(item.access));
|
|
13
|
+
return Object.keys(item).every(k => ['path', 'access'].includes(k)) && text(item.path, 4096) && (item.path.startsWith('/') || /^[A-Za-z]:[\\/]/.test(item.path)) && ['read', 'write'].includes(String(item.access));
|
|
14
|
+
}));
|
|
15
|
+
}
|
|
16
|
+
export function resourceSubset(child, parent) {
|
|
17
|
+
return (!child?.android?.length || child.android.every(c => parent?.android?.some(p => p.serial === c.serial && (p.access === 'control' || c.access === 'view'))))
|
|
18
|
+
&& (!child?.computers?.length || child.computers.every(c => parent?.computers?.some(p => p.host === c.host && p.protocol === c.protocol && p.port === c.port && (p.access === 'control' || c.access === 'view'))))
|
|
19
|
+
&& (!child?.directories?.length || child.directories.every(c => parent?.directories?.some(p => p.path === c.path && (p.access === 'write' || c.access === 'read'))));
|
|
20
|
+
}
|
|
21
|
+
export function mergeShareResources(resources) {
|
|
22
|
+
const android = new Map(), computers = new Map(), directories = new Map();
|
|
23
|
+
for (const r of resources) {
|
|
24
|
+
for (const item of r?.android ?? [])
|
|
25
|
+
if (android.get(item.serial)?.access !== 'control')
|
|
26
|
+
android.set(item.serial, item);
|
|
27
|
+
for (const item of r?.computers ?? []) {
|
|
28
|
+
const key = JSON.stringify([item.host.toLowerCase(), item.protocol, item.port]);
|
|
29
|
+
if (computers.get(key)?.access !== 'control')
|
|
30
|
+
computers.set(key, item);
|
|
31
|
+
}
|
|
32
|
+
for (const item of r?.directories ?? [])
|
|
33
|
+
if (directories.get(item.path)?.access !== 'write')
|
|
34
|
+
directories.set(item.path, item);
|
|
35
|
+
}
|
|
36
|
+
return { android: [...android.values()], computers: [...computers.values()], directories: [...directories.values()] };
|
|
37
|
+
}
|