termdock 1.4.350 → 1.4.352
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-DkOZxYLg.js +3 -0
- package/dist/client/assets/AndroidMirrorView-DkOZxYLg.js.br +0 -0
- package/dist/client/assets/AndroidMirrorView-DkOZxYLg.js.gz +0 -0
- package/dist/client/assets/App-DqRkfVSm.js +178 -0
- package/dist/client/assets/App-DqRkfVSm.js.br +0 -0
- package/dist/client/assets/App-DqRkfVSm.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-m9GKl3cl.js +9 -0
- package/dist/client/assets/ArchitecturePanel-m9GKl3cl.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-m9GKl3cl.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-HZ0zvFvi.js +2 -0
- package/dist/client/assets/ChangeWalkthroughPanel-HZ0zvFvi.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-HZ0zvFvi.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-B4OMk92m.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-B4OMk92m.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-B4OMk92m.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-Cbg3iH6E.js +2 -0
- package/dist/client/assets/ComputerControlView-CSs54lf0.js +6 -0
- package/dist/client/assets/ComputerControlView-CSs54lf0.js.br +0 -0
- package/dist/client/assets/ComputerControlView-CSs54lf0.js.gz +0 -0
- package/dist/client/assets/DagPlayground-yoS2llnB.js +1 -0
- package/dist/client/assets/DagPlayground-yoS2llnB.js.br +0 -0
- package/dist/client/assets/DagPlayground-yoS2llnB.js.gz +0 -0
- package/dist/client/assets/{DiffLab-DEw4y4Hk.js → DiffLab-Dj1MT9pY.js} +2 -2
- package/dist/client/assets/DiffLab-Dj1MT9pY.js.br +0 -0
- package/dist/client/assets/DiffLab-Dj1MT9pY.js.gz +0 -0
- package/dist/client/assets/DiffReview-BX2S9hNp.js +1 -0
- package/dist/client/assets/DiffReview-BX2S9hNp.js.br +0 -0
- package/dist/client/assets/DiffReview-BX2S9hNp.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-CDXsy0jE.js → DiffReviewLab-DqI14GEk.js} +2 -2
- package/dist/client/assets/DiffReviewLab-DqI14GEk.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DqI14GEk.js.gz +0 -0
- package/dist/client/assets/DiffViewer-AZvdDGfH.js +34 -0
- package/dist/client/assets/DiffViewer-AZvdDGfH.js.br +0 -0
- package/dist/client/assets/DiffViewer-AZvdDGfH.js.gz +0 -0
- package/dist/client/assets/GitLoadingSkeleton-CBPykkZP.js +1 -0
- package/dist/client/assets/GitLoadingSkeleton-CBPykkZP.js.br +0 -0
- package/dist/client/assets/GitLoadingSkeleton-CBPykkZP.js.gz +0 -0
- package/dist/client/assets/MarkdownPreview-DpvTnKkj.js +19 -0
- package/dist/client/assets/MarkdownPreview-DpvTnKkj.js.br +0 -0
- package/dist/client/assets/MarkdownPreview-DpvTnKkj.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-BEiOgE0G.js → ModelPreview-Du_AyPr9.js} +166 -166
- package/dist/client/assets/ModelPreview-Du_AyPr9.js.br +0 -0
- package/dist/client/assets/ModelPreview-Du_AyPr9.js.gz +0 -0
- package/dist/client/assets/RecordingSaveDialog-CtXaGgIo.js +1 -0
- package/dist/client/assets/RecordingSaveDialog-CtXaGgIo.js.br +0 -0
- package/dist/client/assets/RecordingSaveDialog-CtXaGgIo.js.gz +0 -0
- package/dist/client/assets/RightSidebar-DU3gwdzg.js +1662 -0
- package/dist/client/assets/RightSidebar-DU3gwdzg.js.br +0 -0
- package/dist/client/assets/RightSidebar-DU3gwdzg.js.gz +0 -0
- package/dist/client/assets/SessionChatView-CxfwJMuK.js +13 -0
- package/dist/client/assets/SessionChatView-CxfwJMuK.js.br +0 -0
- package/dist/client/assets/SessionChatView-CxfwJMuK.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-B31PU57J.js +2 -0
- package/dist/client/assets/SessionResourceSidebar-B31PU57J.js.br +0 -0
- package/dist/client/assets/SessionResourceSidebar-B31PU57J.js.gz +0 -0
- package/dist/client/assets/TodoWorkspace-j9BLgk94.js +1 -0
- package/dist/client/assets/TodoWorkspace-j9BLgk94.js.br +0 -0
- package/dist/client/assets/TodoWorkspace-j9BLgk94.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-ByzlUu_B.js → abnfDiagram-VCTEODGH-LfmMIpPX.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-LfmMIpPX.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-LfmMIpPX.js.gz +0 -0
- package/dist/client/assets/app-BMJesFDU.js +36 -0
- package/dist/client/assets/app-BMJesFDU.js.br +0 -0
- package/dist/client/assets/app-BMJesFDU.js.gz +0 -0
- package/dist/client/assets/{arc-BA6JM6vl.js → arc-DMzcmH2U.js} +1 -1
- package/dist/client/assets/arc-DMzcmH2U.js.br +0 -0
- package/dist/client/assets/arc-DMzcmH2U.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-hCHxOfKw.js → architectureDiagram-5GKGNRK7-CphS_S5Q.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CphS_S5Q.js.br +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-hCHxOfKw.js.gz → architectureDiagram-5GKGNRK7-CphS_S5Q.js.gz} +0 -0
- package/dist/client/assets/arrow-down-D-WZS308.js +1 -0
- package/dist/client/assets/arrow-up-CelbB9FE.js +1 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-OmEN9hUB.js → blockDiagram-I7D4REHJ-Dx_4qkcL.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-Dx_4qkcL.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-Dx_4qkcL.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DfLTv8zh.js → c4Diagram-7LVT6UL2-DiGdnPo4.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-DiGdnPo4.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DiGdnPo4.js.gz +0 -0
- package/dist/client/assets/channel-BxAJ9Qmh.js +1 -0
- package/dist/client/assets/chevron-left-5sk7gL3u.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-C3O76PNZ.js → chunk-2Q5K7J3B-M50KNmR8.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-CsWZd30k.js → chunk-5VM5RSS4-wXVM-Max.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-yxCv2Uqk.js → chunk-F27PBJKO-BfQILS9u.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-BfQILS9u.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BfQILS9u.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-CdPjlb9f.js → chunk-IMKFNOWR-D111Jpl3.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-D111Jpl3.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-D111Jpl3.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BiiOLq2R.js → chunk-JWPE2WC7-BBDyly5c.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-BOu_1Brt.js → chunk-POPQ4Y6H-BaI-_yc7.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-D1tOJung.js → chunk-SVP7TREG-BvQafCMJ.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-BvQafCMJ.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BvQafCMJ.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-DZjDfYNw.js → chunk-TICWLB2K-CQXPwGBz.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-CQXPwGBz.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-CQXPwGBz.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-CtGPyXWh.js → chunk-XXDRQBXY-CmwZc4xV.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-D0oXVFxd.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-D0oXVFxd.js +1 -0
- package/dist/client/assets/clipboard-uSv3AiJM.js +1 -0
- package/dist/client/assets/clipboardText-5puxqqm_.js +1 -0
- package/dist/client/assets/clipboardText-5puxqqm_.js.br +0 -0
- package/dist/client/assets/clipboardText-5puxqqm_.js.gz +0 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-C4Wb5lqD.js → cose-bilkent-JH36ORCC-CqWarkUj.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CqWarkUj.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CqWarkUj.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BvWKB1Xk.js → cynefin-OW5HDTMX-CFW2ifig.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-CFW2ifig.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CFW2ifig.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-7tAkHD2P.js → cynefinDiagram-5FMLGOSQ-yggxM55S.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-yggxM55S.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-yggxM55S.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-Bs6IPwks.js → dagre-GXQ25YYZ-inq7o--x.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-inq7o--x.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-inq7o--x.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-CmYXaTya.js → diagram-S7CK7UJ4-BUXMnyQN.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-BUXMnyQN.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BUXMnyQN.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-CqxoefB7.js → diagram-UQ7AKVKN-BJbw-Imf.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-BJbw-Imf.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BJbw-Imf.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-Dxp1MKPT.js → diagram-VSXAHHWV-CSfl8yS7.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CSfl8yS7.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CSfl8yS7.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-BKbWvvEr.js → diagram-VX7I27RA-zV-Vrv89.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-zV-Vrv89.js.br +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-BKbWvvEr.js.gz → diagram-VX7I27RA-zV-Vrv89.js.gz} +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-BZnNaP7F.js → diagram-Z3DM3KII-CRnE-snw.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-CRnE-snw.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CRnE-snw.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-BXVlN_P7.js → ebnfDiagram-PWID7BFC-CE30oImc.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-CE30oImc.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-CE30oImc.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-CEdYDZU7.js → erDiagram-RLTQ6QDP-D-5bIySI.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-D-5bIySI.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-D-5bIySI.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-Bbw8GPCZ.js → flowDiagram-HODETNUW-E_jPKtQR.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-E_jPKtQR.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-E_jPKtQR.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CcFbtP2y.js → ganttDiagram-EL5Y4UJY-BWvLOUzC.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BWvLOUzC.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-BWvLOUzC.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-BlcHEuBI.js +1 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-Eiust154.js → gitGraphDiagram-WWUBYQGX-nOlVCBmL.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-nOlVCBmL.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-nOlVCBmL.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-DUmaNZJW.js → infoDiagram-27XIBGKW-CjVm9Lzz.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-qTwgXfx_.js → ishikawaDiagram-5VMMS53U-DmVMaGVp.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-qTwgXfx_.js.br → ishikawaDiagram-5VMMS53U-DmVMaGVp.js.br} +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DmVMaGVp.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-DKy1msWo.js → journeyDiagram-3NMN7TZE-DLwkoAy9.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DLwkoAy9.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DLwkoAy9.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-BxibybDd.js → kanban-definition-UXKFOSKX-GWkp_QiO.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-GWkp_QiO.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-GWkp_QiO.js.gz +0 -0
- package/dist/client/assets/{layout-Bh6fKHRM.js → layout-DFg5a61y.js} +1 -1
- package/dist/client/assets/layout-DFg5a61y.js.br +0 -0
- package/dist/client/assets/layout-DFg5a61y.js.gz +0 -0
- package/dist/client/assets/{linear-SRlL3VWX.js → linear-B0OdJLe1.js} +1 -1
- package/dist/client/assets/{linear-SRlL3VWX.js.br → linear-B0OdJLe1.js.br} +0 -0
- package/dist/client/assets/linear-B0OdJLe1.js.gz +0 -0
- package/dist/client/assets/link-BnxdKJ7b.js +8 -0
- package/dist/client/assets/link-BnxdKJ7b.js.br +0 -0
- package/dist/client/assets/link-BnxdKJ7b.js.gz +0 -0
- package/dist/client/assets/list-tree-Bou8VSzi.js +1 -0
- package/dist/client/assets/loader-circle-q7h8uwFI.js +1 -0
- package/dist/client/assets/maximize-2-BGTBw-Ku.js +1 -0
- package/dist/client/assets/{mermaid.core-D0bcNdw3.js → mermaid.core-DQA3ykXv.js} +5 -5
- package/dist/client/assets/mermaid.core-DQA3ykXv.js.br +0 -0
- package/dist/client/assets/mermaid.core-DQA3ykXv.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-CImZ_aI0.js → mindmap-definition-YA3MSWOX-DsZAVAGP.js} +2 -2
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DsZAVAGP.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DsZAVAGP.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-CEZSL94g.js +2 -0
- package/dist/client/assets/passwordBootstrap-CEZSL94g.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-CEZSL94g.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-BT4pTf8O.js → pegDiagram-XKGWAZYB-C3ar01Lf.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-C3ar01Lf.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-C3ar01Lf.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-C5K58ien.js → pieDiagram-E7YTZNPT-C8LIa9gE.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-C8LIa9gE.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-C8LIa9gE.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-DzSugYXz.js → quadrantDiagram-AXDQQJYC-BuRJ1f9F.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BuRJ1f9F.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BuRJ1f9F.js.gz +0 -0
- package/dist/client/assets/quote-qXNI_NTi.js +1 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-BY07SouQ.js → railroadDiagram-O6MQD6OU-D7iZy3dO.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-D7iZy3dO.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-D7iZy3dO.js.gz +0 -0
- package/dist/client/assets/rdpSession-DUBlG26K.js +1 -0
- package/dist/client/assets/rdpSession-DUBlG26K.js.br +0 -0
- package/dist/client/assets/rdpSession-DUBlG26K.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-C7WieVuE.js → requirementDiagram-BXWQKSXE-DhY8g8qK.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DhY8g8qK.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DhY8g8qK.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-BJsDWNs4.js → sankeyDiagram-P5KCCOFB-DDA_LQcg.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DDA_LQcg.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-DDA_LQcg.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BeJ_7853.js → sequenceDiagram-WJ2MYXX4-bWgfdfwY.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BeJ_7853.js.br → sequenceDiagram-WJ2MYXX4-bWgfdfwY.js.br} +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-bWgfdfwY.js.gz +0 -0
- package/dist/client/assets/settings-2-B-sr3wQM.js +1 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-DOsjXKPV.js → sizeCapture-INFHLROL-BYxi7e0R.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-BYxi7e0R.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-BYxi7e0R.js.gz +0 -0
- package/dist/client/assets/{sparkles-e9SkpP7a.js → sparkles-CCLflSFy.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-6s709_RT.js → stateDiagram-D77RDMKH-6vqC2h36.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-6vqC2h36.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-6vqC2h36.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-Bb23lB9W.js +1 -0
- package/dist/client/assets/sun-B9_TfxlA.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-5R5dw9_R.js → swimlanes-42K2YHIH-B4qrXam7.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-B4qrXam7.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-B4qrXam7.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CQ1XJUVU.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-bcOB6oww.js +4 -0
- package/dist/client/assets/syntaxHighlight-bcOB6oww.js.br +0 -0
- package/dist/client/assets/syntaxHighlight-bcOB6oww.js.gz +0 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-B4YN0lsx.js → timeline-definition-24CTP7MA-D0_004MF.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-D0_004MF.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-D0_004MF.js.gz +0 -0
- package/dist/client/assets/triangle-alert-D3w7mA17.js +1 -0
- package/dist/client/assets/unplug-BSblgecr.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-Wd83SmWI.js → vennDiagram-4TSXK5OY-Bqrx-47p.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-Bqrx-47p.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-Bqrx-47p.js.gz +0 -0
- package/dist/client/assets/videoCompression-df1Ej3AR.js +3 -0
- package/dist/client/assets/videoCompression-df1Ej3AR.js.br +0 -0
- package/dist/client/assets/videoCompression-df1Ej3AR.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-CGsYvqWN.js → wardleyDiagram-VM6X3IG4-C4LCUdfd.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-C4LCUdfd.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-C4LCUdfd.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-ednJq1_u.js → xychartDiagram-S5SC5T6Z-E5AQcZVI.js} +1 -1
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-ednJq1_u.js.br → xychartDiagram-S5SC5T6Z-E5AQcZVI.js.br} +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-E5AQcZVI.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-encrypted-background.js +1 -1
- package/dist/client/sw-encrypted-background.js.br +0 -0
- package/dist/client/sw-encrypted-background.js.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/collaborationRoutes.js +2 -0
- 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 +26 -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/relay.js +21 -3
- package/dist/server/federation/requestContext.js +5 -1
- package/dist/server/federation/resourceAccess.js +63 -0
- package/dist/server/federation/runtime.js +67 -18
- package/dist/server/federation/sessionSharing.js +39 -0
- package/dist/server/federation/socketDuplex.js +3 -3
- 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/version.js +2 -0
- package/dist/server/utils/workspaceCacheMaintenance.js +158 -0
- package/docs/integrations.md +23 -0
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-TPJoJbGD.js +0 -187
- package/dist/client/assets/App-TPJoJbGD.js.br +0 -0
- package/dist/client/assets/App-TPJoJbGD.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-DzVy71gM.js +0 -9
- package/dist/client/assets/ArchitecturePanel-DzVy71gM.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-DzVy71gM.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DN7BCzqC.js +0 -2
- package/dist/client/assets/ChangeWalkthroughPanel-DN7BCzqC.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DN7BCzqC.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-B1PZuCC6.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-B1PZuCC6.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-B1PZuCC6.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-CG7g1LMH.js +0 -2
- package/dist/client/assets/ComputerControlView-BlI9Ai_k.js +0 -6
- package/dist/client/assets/ComputerControlView-BlI9Ai_k.js.br +0 -0
- package/dist/client/assets/ComputerControlView-BlI9Ai_k.js.gz +0 -0
- package/dist/client/assets/DagPlayground-V0hTKB-S.js +0 -1
- package/dist/client/assets/DagPlayground-V0hTKB-S.js.br +0 -0
- package/dist/client/assets/DagPlayground-V0hTKB-S.js.gz +0 -0
- package/dist/client/assets/DiffLab-DEw4y4Hk.js.br +0 -0
- package/dist/client/assets/DiffLab-DEw4y4Hk.js.gz +0 -0
- package/dist/client/assets/DiffReview-Cnnt3TMK.js +0 -1
- package/dist/client/assets/DiffReview-Cnnt3TMK.js.br +0 -0
- package/dist/client/assets/DiffReview-Cnnt3TMK.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CDXsy0jE.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CDXsy0jE.js.gz +0 -0
- package/dist/client/assets/DiffViewer-Dh40XiCM.js +0 -37
- package/dist/client/assets/DiffViewer-Dh40XiCM.js.br +0 -0
- package/dist/client/assets/DiffViewer-Dh40XiCM.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BEiOgE0G.js.br +0 -0
- package/dist/client/assets/ModelPreview-BEiOgE0G.js.gz +0 -0
- package/dist/client/assets/RightSidebar-DiECnX5D.js +0 -1679
- package/dist/client/assets/RightSidebar-DiECnX5D.js.br +0 -0
- package/dist/client/assets/RightSidebar-DiECnX5D.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-ByzlUu_B.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-ByzlUu_B.js.gz +0 -0
- package/dist/client/assets/app-CKAFlgMD.js +0 -35
- package/dist/client/assets/app-CKAFlgMD.js.br +0 -0
- package/dist/client/assets/app-CKAFlgMD.js.gz +0 -0
- package/dist/client/assets/arc-BA6JM6vl.js.br +0 -0
- package/dist/client/assets/arc-BA6JM6vl.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-hCHxOfKw.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-OmEN9hUB.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-OmEN9hUB.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DfLTv8zh.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DfLTv8zh.js.gz +0 -0
- package/dist/client/assets/channel-gcmN0N0m.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-yxCv2Uqk.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-yxCv2Uqk.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CdPjlb9f.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CdPjlb9f.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D1tOJung.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D1tOJung.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DZjDfYNw.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DZjDfYNw.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-k4BRymfG.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-k4BRymfG.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-C4Wb5lqD.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-C4Wb5lqD.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BvWKB1Xk.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BvWKB1Xk.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-7tAkHD2P.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-7tAkHD2P.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Bs6IPwks.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Bs6IPwks.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CmYXaTya.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CmYXaTya.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CqxoefB7.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CqxoefB7.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Dxp1MKPT.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Dxp1MKPT.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-BKbWvvEr.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BZnNaP7F.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-BZnNaP7F.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BXVlN_P7.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BXVlN_P7.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-CEdYDZU7.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-CEdYDZU7.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Bbw8GPCZ.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Bbw8GPCZ.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CcFbtP2y.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-CcFbtP2y.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-sdzs-66-.js +0 -1
- package/dist/client/assets/gestureArbiter-sdzs-66-.js.br +0 -0
- package/dist/client/assets/gestureArbiter-sdzs-66-.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Eiust154.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Eiust154.js.gz +0 -0
- package/dist/client/assets/index-DtPsPZ7W.css +0 -1
- package/dist/client/assets/index-DtPsPZ7W.css.br +0 -0
- package/dist/client/assets/index-DtPsPZ7W.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-qTwgXfx_.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DKy1msWo.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DKy1msWo.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BxibybDd.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BxibybDd.js.gz +0 -0
- package/dist/client/assets/layout-Bh6fKHRM.js.br +0 -0
- package/dist/client/assets/layout-Bh6fKHRM.js.gz +0 -0
- package/dist/client/assets/linear-SRlL3VWX.js.gz +0 -0
- package/dist/client/assets/mermaid.core-D0bcNdw3.js.br +0 -0
- package/dist/client/assets/mermaid.core-D0bcNdw3.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CImZ_aI0.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CImZ_aI0.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-f7-mFb1b.js +0 -2
- package/dist/client/assets/passwordBootstrap-f7-mFb1b.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-f7-mFb1b.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BT4pTf8O.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-BT4pTf8O.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-C5K58ien.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-C5K58ien.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DzSugYXz.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-DzSugYXz.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BY07SouQ.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BY07SouQ.js.gz +0 -0
- package/dist/client/assets/rdpSession-RNjO6yLw.js +0 -1
- package/dist/client/assets/rdpSession-RNjO6yLw.js.br +0 -0
- package/dist/client/assets/rdpSession-RNjO6yLw.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-B9K9yPKR.js +0 -1
- package/dist/client/assets/referenceLongPress-B9K9yPKR.js.br +0 -0
- package/dist/client/assets/referenceLongPress-B9K9yPKR.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-C7WieVuE.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-C7WieVuE.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BJsDWNs4.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BJsDWNs4.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BeJ_7853.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-DOsjXKPV.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-DOsjXKPV.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-6s709_RT.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-6s709_RT.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B43A9Hsz.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-5R5dw9_R.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-5R5dw9_R.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DoFbqN_4.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-B4YN0lsx.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-B4YN0lsx.js.gz +0 -0
- package/dist/client/assets/triangle-alert-BzBDJvJX.js +0 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-Wd83SmWI.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-Wd83SmWI.js.gz +0 -0
- package/dist/client/assets/videoCompression-Bsw--tMn.js +0 -3
- package/dist/client/assets/videoCompression-Bsw--tMn.js.br +0 -0
- package/dist/client/assets/videoCompression-Bsw--tMn.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CGsYvqWN.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CGsYvqWN.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-ednJq1_u.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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
export const TODO_HELP = `td todo — persistent todos on the local Termdock service
|
|
4
|
+
list
|
|
5
|
+
add --title <title> [--description <text> | --file <path> | --stdin]
|
|
6
|
+
[--source <direction>] [--external-id <stable-id>] [--url <https-url>]
|
|
7
|
+
[--collector <collector-id>] [--idempotency-key <key>]
|
|
8
|
+
report <run-id> --status complete|blocked|failed (--content <text> | --file <path> | --stdin)
|
|
9
|
+
complete|reopen|ignore <todo-id>
|
|
10
|
+
--help | --json
|
|
11
|
+
External IDs deduplicate within a source. Reports attach to the exact execution run;
|
|
12
|
+
only complete marks a todo completed. An Agent report never accepts a todo for the user.
|
|
13
|
+
Use the running local service (same OS user); no unauthenticated HTTP fallback.`;
|
|
14
|
+
export function parseTodoCommand(argv) {
|
|
15
|
+
const positional = [], options = {};
|
|
16
|
+
const values = ['title', 'description', 'file', 'source', 'external-id', 'url', 'collector', 'idempotency-key', 'status', 'content'];
|
|
17
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18
|
+
const arg = argv[i];
|
|
19
|
+
if (!arg.startsWith('--')) {
|
|
20
|
+
positional.push(arg);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const [key, ...inline] = arg.slice(2).split('=');
|
|
24
|
+
if (Object.hasOwn(options, key))
|
|
25
|
+
throw new Error(`Duplicate option --${key}`);
|
|
26
|
+
if (['help', 'json', 'stdin'].includes(key)) {
|
|
27
|
+
if (inline.length)
|
|
28
|
+
throw new Error(`--${key} takes no value`);
|
|
29
|
+
options[key] = true;
|
|
30
|
+
}
|
|
31
|
+
else if (values.includes(key)) {
|
|
32
|
+
const value = inline.length ? inline.join('=') : argv[++i];
|
|
33
|
+
if (!value || (!inline.length && value.startsWith('--')))
|
|
34
|
+
throw new Error(`--${key} requires a value`);
|
|
35
|
+
options[key] = value;
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
throw new Error(`Unknown option --${key}`);
|
|
39
|
+
}
|
|
40
|
+
const action = (options.help ? 'help' : positional.shift() ?? 'help');
|
|
41
|
+
if (!['help', 'list', 'add', 'report', 'complete', 'reopen', 'ignore'].includes(action))
|
|
42
|
+
throw new Error('See td todo --help');
|
|
43
|
+
if (action === 'help')
|
|
44
|
+
return { action, options };
|
|
45
|
+
const allowed = { list: [], add: ['title', 'description', 'file', 'stdin', 'source', 'external-id', 'url', 'collector', 'idempotency-key'], report: ['status', 'content', 'file', 'stdin'], complete: [], reopen: [], ignore: [] };
|
|
46
|
+
for (const flag of Object.keys(options))
|
|
47
|
+
if (flag !== 'json' && !allowed[action].includes(flag))
|
|
48
|
+
throw new Error(`--${flag} is not supported by ${action}`);
|
|
49
|
+
const id = !['list', 'add'].includes(action) ? positional.shift() : undefined;
|
|
50
|
+
if ((!['list', 'add'].includes(action) && !id) || positional.length)
|
|
51
|
+
throw new Error('Invalid arguments; see td todo --help');
|
|
52
|
+
if (action === 'add' && !options.title)
|
|
53
|
+
throw new Error('--title is required');
|
|
54
|
+
if (['description', 'content', 'file', 'stdin'].filter(k => options[k]).length > 1)
|
|
55
|
+
throw new Error('Choose one content input');
|
|
56
|
+
if (action === 'report' && (!['complete', 'blocked', 'failed'].includes(String(options.status)) || !['content', 'file', 'stdin'].some(k => options[k])))
|
|
57
|
+
throw new Error('report requires --status and content');
|
|
58
|
+
return { action, id, options };
|
|
59
|
+
}
|
|
60
|
+
export async function executeTodoCommand(command, io) {
|
|
61
|
+
try {
|
|
62
|
+
if (command.action === 'help') {
|
|
63
|
+
io.write(TODO_HELP);
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
const base = '/api/terminal/operations/todos', o = command.options;
|
|
67
|
+
const request = async (method, suffix = '', body) => { const response = await io.request(method, base + suffix, body); const data = JSON.parse(response.body); if (response.statusCode < 200 || response.statusCode >= 300)
|
|
68
|
+
throw new Error(data.error || 'Todo request failed'); return data; };
|
|
69
|
+
const content = o.file ? fs.readFileSync(String(o.file), 'utf8') : o.stdin ? await io.stdin() : String(o.content ?? o.description ?? '');
|
|
70
|
+
let result;
|
|
71
|
+
if (command.action === 'list')
|
|
72
|
+
result = await request('GET');
|
|
73
|
+
else if (command.action === 'add')
|
|
74
|
+
result = await request('POST', '', { title: o.title, description: content, source: o.source || '外部注入', sourceUrl: o.url, externalId: o['external-id'], collectorId: o.collector, idempotencyKey: o['idempotency-key'] || randomUUID() });
|
|
75
|
+
else if (command.action === 'report')
|
|
76
|
+
result = await request('POST', `/runs/${encodeURIComponent(command.id)}/report`, { status: o.status, content });
|
|
77
|
+
else {
|
|
78
|
+
const snapshot = await request('GET');
|
|
79
|
+
const todo = snapshot.todos.find((t) => t.id === command.id);
|
|
80
|
+
if (!todo)
|
|
81
|
+
throw new Error('Todo not found');
|
|
82
|
+
result = await request('POST', `/${encodeURIComponent(command.id)}`, { revision: todo.revision, status: command.action === 'complete' ? 'completed' : command.action === 'ignore' ? 'ignored' : 'open' });
|
|
83
|
+
}
|
|
84
|
+
io.write(JSON.stringify(result));
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
io.write(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { launchConfig, TodoError } from './store.js';
|
|
3
|
+
function shellQuote(value) { return "'" + value.replace(/'/g, "'\"'\"'") + "'"; }
|
|
4
|
+
export function todoPrompt(store, run) {
|
|
5
|
+
const report = `完成或受阻时,请将结果写入一个 UTF-8 文件,并调用 td todo report ${run.id} --status complete --file <文件路径>;受阻时将 complete 换为 blocked,失败时换为 failed。报告只记录本次结果,由用户决定待办是否完成。`;
|
|
6
|
+
if (run.kind === 'collect')
|
|
7
|
+
return `${run.config.prompt}\n\n请将收集到的每条可执行待办通过本机 CLI 写入 Termdock,避免只输出列表。使用:\ntd todo add --title <标题> --description <描述> --source ${shellQuote(run.name)} --collector ${run.collectorId} --external-id <在此方向中稳定且唯一的标识>\n请附带必要上下文和可选的 --url https://... 。先用 td todo list 查看已有待办,避免重复。只收集待办,本轮不执行这些待办。\n${report}`;
|
|
8
|
+
const todo = store.get(run.todoId);
|
|
9
|
+
return `${run.config.prompt || '请处理下面这条待办,完成后说明结果和验证情况。'}\n\n待办:${todo.title}\n${todo.description}\n来源:${todo.source}\n${todo.sourceUrl ? `来源链接:${todo.sourceUrl}\n` : ''}\n待办 ID:${todo.id}\n执行记录 ID:${run.id}\n${report}`;
|
|
10
|
+
}
|
|
11
|
+
export class TodoRunner {
|
|
12
|
+
store;
|
|
13
|
+
runtime;
|
|
14
|
+
constructor(store, runtime) {
|
|
15
|
+
this.store = store;
|
|
16
|
+
this.runtime = runtime;
|
|
17
|
+
}
|
|
18
|
+
async start(kind, id, input, key, req) {
|
|
19
|
+
// Read-only validation happens before reserving a run; launch is always recorded before any side effect.
|
|
20
|
+
const base = kind === 'collect' ? this.store.collector(id).config : this.store.snapshot().defaults;
|
|
21
|
+
const config = await this.runtime.validate(launchConfig(input ?? {}, base), req);
|
|
22
|
+
const { run, fresh } = this.store.begin(kind, id, config, key);
|
|
23
|
+
if (fresh)
|
|
24
|
+
void this.runtime.launch(run, todoPrompt(this.store, run), sessionId => this.store.mark(run.id, { sessionId }), req)
|
|
25
|
+
.then(() => this.store.mark(run.id, { status: 'delivered' }))
|
|
26
|
+
.catch(error => { this.store.mark(run.id, { status: 'failed', error: error instanceof Error ? error.message : String(error) }); });
|
|
27
|
+
return run;
|
|
28
|
+
}
|
|
29
|
+
async tick() {
|
|
30
|
+
for (const collector of this.store.due()) {
|
|
31
|
+
try {
|
|
32
|
+
await this.start('collect', collector.id, undefined, `schedule:${collector.id}:${collector.nextRunAt}`);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
// Scheduled configuration failures also need a visible record and a new due time.
|
|
36
|
+
try {
|
|
37
|
+
const { run, fresh } = this.store.begin('collect', collector.id, collector.config, `schedule:${collector.id}:${collector.nextRunAt}`);
|
|
38
|
+
if (fresh)
|
|
39
|
+
this.store.mark(run.id, { status: 'failed', error: error instanceof Error ? error.message : String(error) });
|
|
40
|
+
}
|
|
41
|
+
catch (recordError) {
|
|
42
|
+
console.warn('[todo-collector]', collector.name, recordError instanceof Error ? recordError.message : recordError);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export function todoRoutes(store, runner, runtime) {
|
|
49
|
+
const router = Router();
|
|
50
|
+
const route = (fn) => async (req, res) => {
|
|
51
|
+
try {
|
|
52
|
+
await fn(req, res);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
res.status(error instanceof TodoError ? error.status : 400).json({ error: error instanceof Error ? error.message : String(error) });
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
router.get('/summary', route((_req, res) => res.json({ count: store.openCount() })));
|
|
59
|
+
router.get('/', route((_req, res) => res.json(store.snapshot())));
|
|
60
|
+
router.post('/', route((req, res) => res.json({ todo: store.create(req.body ?? {}) })));
|
|
61
|
+
router.post('/defaults', route(async (req, res) => res.json({ defaults: store.defaults(await runtime.validate(launchConfig(req.body ?? {}), req)) })));
|
|
62
|
+
router.post('/collectors', route(async (req, res) => {
|
|
63
|
+
const input = req.body ?? {};
|
|
64
|
+
const config = await runtime.validate(launchConfig(input.config ?? {}), req);
|
|
65
|
+
res.json({ collector: store.saveCollector({ ...input, config }) });
|
|
66
|
+
}));
|
|
67
|
+
router.post('/collectors/:id/remove', route((req, res) => { store.removeCollector(req.params.id, req.body?.revision); res.json({ ok: true }); }));
|
|
68
|
+
router.post('/collectors/:id/run', route(async (req, res) => res.json({ run: await runner.start('collect', req.params.id, undefined, req.body?.idempotencyKey, req) })));
|
|
69
|
+
router.post('/runs/:id/report', route((req, res) => res.json({ run: store.report(req.params.id, req.body?.status, req.body?.content) })));
|
|
70
|
+
router.post('/:id', route((req, res) => res.json({ todo: store.update(req.params.id, req.body ?? {}) })));
|
|
71
|
+
router.post('/:id/run', route(async (req, res) => res.json({ run: await runner.start('execute', req.params.id, req.body?.config, req.body?.idempotencyKey, req) })));
|
|
72
|
+
return router;
|
|
73
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID, createHash } from 'node:crypto';
|
|
4
|
+
import { nextAutomationRunAt, normalizeAutomationSchedule } from '../agent/automationStore.js';
|
|
5
|
+
export class TodoError extends Error {
|
|
6
|
+
status;
|
|
7
|
+
constructor(message, status = 400) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.status = status;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function string(value, max, fallback = '') {
|
|
13
|
+
if (value === undefined)
|
|
14
|
+
return fallback;
|
|
15
|
+
if (typeof value !== 'string' || value.length > max || value.includes('\0'))
|
|
16
|
+
throw new TodoError('内容格式或长度无效');
|
|
17
|
+
return value.trim();
|
|
18
|
+
}
|
|
19
|
+
function title(value) { const s = string(value, 200); if (!s)
|
|
20
|
+
throw new TodoError('请填写标题'); return s; }
|
|
21
|
+
export function launchConfig(input, base) {
|
|
22
|
+
return { agentSlug: string(input.agentSlug, 100, base?.agentSlug), command: string(input.command, 4000, base?.command),
|
|
23
|
+
cwd: string(input.cwd, 4096, base?.cwd), prompt: string(input.prompt, 20000, base?.prompt) };
|
|
24
|
+
}
|
|
25
|
+
const blank = () => ({ version: 1, todos: [], collectors: [], runs: [], requests: {}, defaults: { agentSlug: '', command: '', cwd: '', prompt: '' } });
|
|
26
|
+
export class TodoStore {
|
|
27
|
+
file;
|
|
28
|
+
doc = blank();
|
|
29
|
+
constructor(file) {
|
|
30
|
+
this.file = file;
|
|
31
|
+
try {
|
|
32
|
+
const doc = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
33
|
+
if (doc.version !== 1 || !Array.isArray(doc.todos) || !Array.isArray(doc.collectors) || !Array.isArray(doc.runs) || !doc.requests || !doc.defaults)
|
|
34
|
+
throw new Error('无效的 Todo 数据文件');
|
|
35
|
+
this.doc = doc;
|
|
36
|
+
// Never replay a launch after a crash: it might already have reached a terminal.
|
|
37
|
+
if (doc.runs.some(r => r.status === 'launching'))
|
|
38
|
+
this.transaction(next => {
|
|
39
|
+
for (const run of next.runs)
|
|
40
|
+
if (run.status === 'launching') {
|
|
41
|
+
run.status = 'failed';
|
|
42
|
+
run.error = '服务重启中断了启动确认,请检查已有终端后重新发起';
|
|
43
|
+
run.updatedAt = Date.now();
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
if (error.code !== 'ENOENT')
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
transaction(change) {
|
|
53
|
+
const next = structuredClone(this.doc), result = change(next);
|
|
54
|
+
const bytes = JSON.stringify(next);
|
|
55
|
+
if (Buffer.byteLength(bytes) > 32 * 1024 * 1024)
|
|
56
|
+
throw new TodoError('待办记录已达容量上限,请整理后重试', 409);
|
|
57
|
+
fs.mkdirSync(path.dirname(this.file), { recursive: true, mode: 0o700 });
|
|
58
|
+
const temporary = `${this.file}.${process.pid}.tmp`;
|
|
59
|
+
const fd = fs.openSync(temporary, 'w', 0o600);
|
|
60
|
+
try {
|
|
61
|
+
fs.writeFileSync(fd, bytes);
|
|
62
|
+
fs.fsyncSync(fd);
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
fs.closeSync(fd);
|
|
66
|
+
}
|
|
67
|
+
fs.renameSync(temporary, this.file);
|
|
68
|
+
const directory = fs.openSync(path.dirname(this.file), 'r');
|
|
69
|
+
try {
|
|
70
|
+
fs.fsyncSync(directory);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
fs.closeSync(directory);
|
|
74
|
+
}
|
|
75
|
+
this.doc = next;
|
|
76
|
+
return structuredClone(result);
|
|
77
|
+
}
|
|
78
|
+
openCount() { return this.doc.todos.filter(t => t.status === 'open').length; }
|
|
79
|
+
snapshot() { const { requests: _, version: _v, ...snapshot } = this.doc; return structuredClone(snapshot); }
|
|
80
|
+
get(id) { const todo = this.doc.todos.find(t => t.id === id); if (!todo)
|
|
81
|
+
throw new TodoError('待办不存在', 404); return structuredClone(todo); }
|
|
82
|
+
collector(id) { const collector = this.doc.collectors.find(c => c.id === id); if (!collector)
|
|
83
|
+
throw new TodoError('收集方向不存在', 404); return structuredClone(collector); }
|
|
84
|
+
run(id) { const run = this.doc.runs.find(r => r.id === id); if (!run)
|
|
85
|
+
throw new TodoError('执行记录不存在', 404); return structuredClone(run); }
|
|
86
|
+
check(revision, current) { if (revision !== current)
|
|
87
|
+
throw new TodoError('记录已更新,请刷新后重试', 409); }
|
|
88
|
+
create(input) {
|
|
89
|
+
const sourceUrl = string(input.sourceUrl, 4096);
|
|
90
|
+
if (sourceUrl) {
|
|
91
|
+
try {
|
|
92
|
+
if (!['http:', 'https:'].includes(new URL(sourceUrl).protocol))
|
|
93
|
+
throw new Error();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
throw new TodoError('来源链接须为有效的 http 或 https 地址');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
const collectorId = string(input.collectorId, 100) || null;
|
|
100
|
+
const collectorName = collectorId ? this.doc.collectors.find(c => c.id === collectorId)?.name ?? this.doc.runs.find(r => r.collectorId === collectorId)?.name : null;
|
|
101
|
+
if (collectorId && !collectorName)
|
|
102
|
+
throw new TodoError('收集方向不存在', 404);
|
|
103
|
+
const values = { title: title(input.title), description: string(input.description, 40000), source: string(input.source, 200, collectorName ?? '手动添加') || '外部注入',
|
|
104
|
+
sourceUrl, externalId: string(input.externalId, 500), collectorId };
|
|
105
|
+
const key = string(input.idempotencyKey, 200);
|
|
106
|
+
const hash = this.hash(values);
|
|
107
|
+
const previous = key && this.doc.requests[`create:${key}`];
|
|
108
|
+
if (previous) {
|
|
109
|
+
if (previous.hash !== hash)
|
|
110
|
+
throw new TodoError('同一请求标识的内容不一致', 409);
|
|
111
|
+
return this.get(previous.id);
|
|
112
|
+
}
|
|
113
|
+
const existing = values.externalId && this.doc.todos.find(t => t.source === values.source && t.externalId === values.externalId);
|
|
114
|
+
if (existing)
|
|
115
|
+
return structuredClone(existing);
|
|
116
|
+
return this.transaction(doc => {
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
const todo = { ...values, id: randomUUID(), status: 'open', createdAt: now, updatedAt: now, revision: 1 };
|
|
119
|
+
doc.todos.unshift(todo);
|
|
120
|
+
if (key)
|
|
121
|
+
doc.requests[`create:${key}`] = { hash, id: todo.id };
|
|
122
|
+
return todo;
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
update(id, input) {
|
|
126
|
+
return this.transaction(doc => {
|
|
127
|
+
const todo = doc.todos.find(t => t.id === id);
|
|
128
|
+
if (!todo)
|
|
129
|
+
throw new TodoError('待办不存在', 404);
|
|
130
|
+
this.check(input.revision, todo.revision);
|
|
131
|
+
if (input.title !== undefined)
|
|
132
|
+
todo.title = title(input.title);
|
|
133
|
+
if (input.description !== undefined)
|
|
134
|
+
todo.description = string(input.description, 40000);
|
|
135
|
+
if (input.status !== undefined) {
|
|
136
|
+
if (!['open', 'completed', 'ignored'].includes(input.status))
|
|
137
|
+
throw new TodoError('待办状态无效');
|
|
138
|
+
todo.status = input.status;
|
|
139
|
+
}
|
|
140
|
+
todo.revision++;
|
|
141
|
+
todo.updatedAt = Date.now();
|
|
142
|
+
return todo;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
defaults(input) { const config = launchConfig(input, this.doc.defaults); return this.transaction(doc => doc.defaults = config); }
|
|
146
|
+
saveCollector(input) {
|
|
147
|
+
const name = title(input.name), config = launchConfig(input.config);
|
|
148
|
+
if (!config.agentSlug || !config.command || !config.prompt)
|
|
149
|
+
throw new TodoError('请选择 Agent 并填写收集提示词');
|
|
150
|
+
const schedule = input.schedule == null ? null : normalizeAutomationSchedule(input.schedule);
|
|
151
|
+
if (input.schedule != null && !schedule)
|
|
152
|
+
throw new TodoError('运行频率无效');
|
|
153
|
+
return this.transaction(doc => {
|
|
154
|
+
const previous = input.id ? doc.collectors.find(c => c.id === input.id) : null;
|
|
155
|
+
if (input.id && !previous)
|
|
156
|
+
throw new TodoError('收集方向不存在', 404);
|
|
157
|
+
if (previous)
|
|
158
|
+
this.check(input.revision, previous.revision);
|
|
159
|
+
const now = Date.now(), enabled = input.enabled !== false;
|
|
160
|
+
const collector = { id: previous?.id ?? randomUUID(), name, config, enabled, schedule,
|
|
161
|
+
nextRunAt: schedule && enabled ? (previous?.enabled && JSON.stringify(previous.schedule) === JSON.stringify(schedule) ? previous.nextRunAt : nextAutomationRunAt(schedule, now, now)) : null,
|
|
162
|
+
createdAt: previous?.createdAt ?? now, updatedAt: now, revision: (previous?.revision ?? 0) + 1 };
|
|
163
|
+
if (previous)
|
|
164
|
+
Object.assign(previous, collector);
|
|
165
|
+
else
|
|
166
|
+
doc.collectors.push(collector);
|
|
167
|
+
return collector;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
removeCollector(id, revision) { this.transaction(doc => { const c = doc.collectors.find(c => c.id === id); if (!c)
|
|
171
|
+
throw new TodoError('收集方向不存在', 404); this.check(revision, c.revision); doc.collectors = doc.collectors.filter(c => c.id !== id); }); }
|
|
172
|
+
due(now = Date.now()) { return this.doc.collectors.filter(c => c.enabled && c.nextRunAt !== null && c.nextRunAt <= now).map(c => structuredClone(c)); }
|
|
173
|
+
begin(kind, id, config, key) {
|
|
174
|
+
if (!key || key.length > 200)
|
|
175
|
+
throw new TodoError('缺少有效的请求标识');
|
|
176
|
+
const hash = this.hash({ kind, id, config });
|
|
177
|
+
const previous = this.doc.requests[`run:${key}`];
|
|
178
|
+
if (previous) {
|
|
179
|
+
if (previous.hash !== hash)
|
|
180
|
+
throw new TodoError('同一请求标识的内容不一致', 409);
|
|
181
|
+
return { run: this.run(previous.id), fresh: false };
|
|
182
|
+
}
|
|
183
|
+
const item = kind === 'execute' ? this.get(id) : this.collector(id);
|
|
184
|
+
if (kind === 'execute' && item.status !== 'open')
|
|
185
|
+
throw new TodoError('请先重新打开这条待办', 409);
|
|
186
|
+
if (this.doc.runs.some(r => r.status === 'launching' && (kind === 'execute' ? r.todoId === id : r.collectorId === id)))
|
|
187
|
+
throw new TodoError('此项正在启动,请稍候', 409);
|
|
188
|
+
return this.transaction(doc => {
|
|
189
|
+
const now = Date.now();
|
|
190
|
+
const run = { id: randomUUID(), kind, todoId: kind === 'execute' ? id : null, collectorId: kind === 'collect' ? id : null,
|
|
191
|
+
name: kind === 'execute' ? item.title : item.name, config, status: 'launching', createdAt: now, updatedAt: now, sessionId: null, result: '', error: '' };
|
|
192
|
+
doc.runs.push(run);
|
|
193
|
+
doc.requests[`run:${key}`] = { hash, id: run.id };
|
|
194
|
+
if (kind === 'collect') {
|
|
195
|
+
const c = doc.collectors.find(c => c.id === id);
|
|
196
|
+
c.nextRunAt = c.schedule && c.enabled ? nextAutomationRunAt(c.schedule, now + 1, c.createdAt) : null;
|
|
197
|
+
c.updatedAt = now;
|
|
198
|
+
c.revision++;
|
|
199
|
+
}
|
|
200
|
+
return { run, fresh: true };
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
mark(id, update) {
|
|
204
|
+
return this.transaction(doc => {
|
|
205
|
+
const run = doc.runs.find(r => r.id === id);
|
|
206
|
+
if (!run)
|
|
207
|
+
throw new TodoError('执行记录不存在', 404);
|
|
208
|
+
if (update.sessionId)
|
|
209
|
+
run.sessionId = update.sessionId;
|
|
210
|
+
// An Agent can submit its report before launch delivery acknowledgement returns.
|
|
211
|
+
if (run.status === 'launching') {
|
|
212
|
+
if (update.status)
|
|
213
|
+
run.status = update.status;
|
|
214
|
+
if (update.error)
|
|
215
|
+
run.error = update.error;
|
|
216
|
+
}
|
|
217
|
+
run.updatedAt = Date.now();
|
|
218
|
+
return run;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
report(id, status, content) {
|
|
222
|
+
if (!['complete', 'blocked', 'failed'].includes(status))
|
|
223
|
+
throw new TodoError('结果状态无效');
|
|
224
|
+
const result = string(content, 40000);
|
|
225
|
+
if (!result)
|
|
226
|
+
throw new TodoError('请填写结果');
|
|
227
|
+
return this.transaction(doc => {
|
|
228
|
+
const run = doc.runs.find(r => r.id === id);
|
|
229
|
+
if (!run)
|
|
230
|
+
throw new TodoError('执行记录不存在', 404);
|
|
231
|
+
if (run.result && (run.status !== status || run.result !== result))
|
|
232
|
+
throw new TodoError('此轮已有结果,请发起新一轮执行', 409);
|
|
233
|
+
run.status = status;
|
|
234
|
+
run.result = result;
|
|
235
|
+
run.updatedAt = Date.now();
|
|
236
|
+
return run;
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
hash(value) { return createHash('sha256').update(JSON.stringify(value)).digest('hex'); }
|
|
240
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -3,6 +3,8 @@ import path from 'node:path';
|
|
|
3
3
|
import { compareVersions, verifyRunningTermdockInstallation, updateTermdockFromOfficialRegistry, } from './npmUpdate.js';
|
|
4
4
|
export const AUTO_UPDATE_INITIAL_DELAY_MS = 15_000;
|
|
5
5
|
export const AUTO_UPDATE_INTERVAL_MS = 6 * 60 * 60 * 1_000;
|
|
6
|
+
export class TermdockUpdateStateChangedError extends Error {
|
|
7
|
+
}
|
|
6
8
|
function initialState(currentVersion) {
|
|
7
9
|
return {
|
|
8
10
|
status: 'idle',
|
|
@@ -86,9 +88,12 @@ export class TermdockAutoUpdateManager {
|
|
|
86
88
|
});
|
|
87
89
|
return this.checkPromise;
|
|
88
90
|
}
|
|
89
|
-
confirmRestart() {
|
|
91
|
+
confirmRestart(expectedVersion) {
|
|
90
92
|
if (this.state.status !== 'ready' || !hasPendingTermdockUpdate(this.state)) {
|
|
91
|
-
throw new
|
|
93
|
+
throw new TermdockUpdateStateChangedError('No installed Termdock update is waiting for restart.');
|
|
94
|
+
}
|
|
95
|
+
if (expectedVersion !== undefined && expectedVersion !== this.state.latestVersion) {
|
|
96
|
+
throw new TermdockUpdateStateChangedError('The pending Termdock update has changed. Please confirm the new version.');
|
|
92
97
|
}
|
|
93
98
|
try {
|
|
94
99
|
this.verifyInstalled(this.state.latestVersion);
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import syncFs from 'node:fs';
|
|
3
|
+
import crypto from 'node:crypto';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const DAY = 24 * 60 * 60_000;
|
|
6
|
+
const KEY = /^[a-f0-9]{24}$/;
|
|
7
|
+
async function realDirectory(directory) {
|
|
8
|
+
try {
|
|
9
|
+
return (await fs.lstat(directory)).isDirectory() && await fs.realpath(directory) === path.resolve(directory);
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
async function snapshot(directory) {
|
|
16
|
+
const key = path.basename(directory);
|
|
17
|
+
if (!KEY.test(key) || !await realDirectory(directory))
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
const names = await fs.readdir(directory);
|
|
21
|
+
if (names.length !== 2 || !names.includes('dist') || !names.includes('snapshot-manifest.json'))
|
|
22
|
+
return null;
|
|
23
|
+
const manifestPath = path.join(directory, 'snapshot-manifest.json');
|
|
24
|
+
if (!(await fs.lstat(manifestPath)).isFile())
|
|
25
|
+
return null;
|
|
26
|
+
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf8'));
|
|
27
|
+
if (Object.keys(manifest).sort().join() !== ['schemaVersion', 'serverBundleHash', 'clientBundleHash', 'clientEntrypoint'].sort().join()
|
|
28
|
+
|| manifest.schemaVersion !== 1 || manifest.clientEntrypoint !== 'dist/client/index.html'
|
|
29
|
+
|| typeof manifest.serverBundleHash !== 'string' || typeof manifest.clientBundleHash !== 'string'
|
|
30
|
+
|| !/^sha256-[A-Za-z0-9+/]{43}=$/.test(manifest.clientBundleHash)
|
|
31
|
+
|| crypto.createHash('sha256').update(`${manifest.serverBundleHash}\0${manifest.clientBundleHash}`).digest('hex').slice(0, 24) !== key)
|
|
32
|
+
return null;
|
|
33
|
+
const dist = path.join(directory, 'dist'), client = path.join(dist, 'client');
|
|
34
|
+
if (!await realDirectory(dist) || !await realDirectory(client) || (await fs.readdir(dist)).join() !== 'client')
|
|
35
|
+
return null;
|
|
36
|
+
if (!(await fs.lstat(path.join(client, 'index.html'))).isFile())
|
|
37
|
+
return null;
|
|
38
|
+
return { key, directory, client, hash: manifest.clientBundleHash, touchedAt: (await fs.stat(directory)).mtimeMs };
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** Same byte/path hash as generate-runtime-manifest.mjs; links are never eligible. */
|
|
45
|
+
export async function hashGeneratedClient(directory) {
|
|
46
|
+
const digest = crypto.createHash('sha256');
|
|
47
|
+
const observed = [];
|
|
48
|
+
let bytes = 0, visited = 0;
|
|
49
|
+
const identity = (stat) => `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
50
|
+
const visit = async (current, relative = '') => {
|
|
51
|
+
const stat = await fs.lstat(current);
|
|
52
|
+
observed.push({ file: current, identity: identity(stat) });
|
|
53
|
+
if (!stat.isDirectory())
|
|
54
|
+
throw new Error('Snapshot contains a link or special entry');
|
|
55
|
+
for (const entry of (await fs.readdir(current, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
56
|
+
if (++visited > 10_000)
|
|
57
|
+
throw new Error('Snapshot entry limit reached');
|
|
58
|
+
const file = path.join(current, entry.name), name = path.join(relative, entry.name);
|
|
59
|
+
if (entry.isDirectory())
|
|
60
|
+
await visit(file, name);
|
|
61
|
+
else if (entry.isFile()) {
|
|
62
|
+
const before = await fs.lstat(file);
|
|
63
|
+
observed.push({ file, identity: identity(before) });
|
|
64
|
+
bytes += before.size;
|
|
65
|
+
digest.update(name.split(path.sep).join('/'));
|
|
66
|
+
digest.update('\0');
|
|
67
|
+
for await (const chunk of syncFs.createReadStream(file))
|
|
68
|
+
digest.update(chunk);
|
|
69
|
+
digest.update('\0');
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
throw new Error('Snapshot contains a link or special entry');
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
await visit(directory);
|
|
76
|
+
return { hash: `sha256-${digest.digest('base64')}`, bytes, unchanged: async () => {
|
|
77
|
+
for (const item of observed) {
|
|
78
|
+
try {
|
|
79
|
+
if (identity(await fs.lstat(item.file)) !== item.identity)
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
} };
|
|
88
|
+
}
|
|
89
|
+
function alive(pid) {
|
|
90
|
+
try {
|
|
91
|
+
process.kill(pid, 0);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return error.code !== 'ESRCH';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
async function leasedKeys(stateDirectory, reapDead) {
|
|
99
|
+
const result = new Set(), directory = path.join(stateDirectory, 'client-snapshot-leases');
|
|
100
|
+
try {
|
|
101
|
+
try {
|
|
102
|
+
await fs.lstat(directory);
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
if (error.code === 'ENOENT')
|
|
106
|
+
return result;
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
if (!await realDirectory(directory))
|
|
110
|
+
throw new Error('Snapshot lease directory is not verified');
|
|
111
|
+
for (const entry of await fs.readdir(directory, { withFileTypes: true })) {
|
|
112
|
+
if (!entry.isFile() || !/^\d+-[a-f0-9]{16}\.json$/.test(entry.name))
|
|
113
|
+
continue;
|
|
114
|
+
const file = path.join(directory, entry.name);
|
|
115
|
+
try {
|
|
116
|
+
const lease = JSON.parse(await fs.readFile(file, 'utf8'));
|
|
117
|
+
if (lease.kind !== 'termdock-client-snapshot-lease' || lease.version !== 1 || !KEY.test(lease.snapshotKey)
|
|
118
|
+
|| !Number.isSafeInteger(lease.pid) || lease.pid < 1 || !entry.name.startsWith(`${lease.pid}-`))
|
|
119
|
+
throw new Error('Unverified lease');
|
|
120
|
+
if (alive(lease.pid))
|
|
121
|
+
result.add(lease.snapshotKey);
|
|
122
|
+
else if (reapDead && Object.keys(lease).length === 4)
|
|
123
|
+
await fs.unlink(file);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
throw new Error('Client snapshot leases could not be verified');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error.code !== 'ENOENT')
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
/** Protect every live server's pinned client, including other local ports. */
|
|
137
|
+
export function leaseClientSnapshot(clientDirectory, stateDirectory) {
|
|
138
|
+
const root = path.join(stateDirectory, 'client-snapshots'), directory = path.resolve(clientDirectory, '..', '..');
|
|
139
|
+
if (path.dirname(directory) !== path.resolve(root) || !KEY.test(path.basename(directory)))
|
|
140
|
+
return () => { };
|
|
141
|
+
const leases = path.join(stateDirectory, 'client-snapshot-leases');
|
|
142
|
+
try {
|
|
143
|
+
syncFs.mkdirSync(leases, { recursive: true, mode: 0o700 });
|
|
144
|
+
if (syncFs.realpathSync(leases) !== path.resolve(leases))
|
|
145
|
+
return () => { };
|
|
146
|
+
const file = path.join(leases, `${process.pid}-${crypto.randomBytes(8).toString('hex')}.json`);
|
|
147
|
+
syncFs.writeFileSync(file, JSON.stringify({ kind: 'termdock-client-snapshot-lease', version: 1, pid: process.pid, snapshotKey: path.basename(directory) }), { mode: 0o600, flag: 'wx' });
|
|
148
|
+
const remove = () => { try {
|
|
149
|
+
syncFs.unlinkSync(file);
|
|
150
|
+
}
|
|
151
|
+
catch { /* already removed */ } };
|
|
152
|
+
process.once('exit', remove);
|
|
153
|
+
return () => { process.off('exit', remove); remove(); };
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return () => { };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export async function pruneClientSnapshots(options) {
|
|
160
|
+
const root = path.join(options.stateDirectory, 'client-snapshots');
|
|
161
|
+
if (!await realDirectory(root))
|
|
162
|
+
return [];
|
|
163
|
+
const snapshots = [], result = [];
|
|
164
|
+
for (const entry of await fs.readdir(root, { withFileTypes: true })) {
|
|
165
|
+
if (!entry.isDirectory() || !KEY.test(entry.name))
|
|
166
|
+
continue;
|
|
167
|
+
const directory = path.join(root, entry.name), candidate = await snapshot(directory);
|
|
168
|
+
if (candidate)
|
|
169
|
+
snapshots.push(candidate);
|
|
170
|
+
else
|
|
171
|
+
result.push({ path: directory, action: 'retained', reason: 'unrecognized-or-modified-snapshot', bytes: 0 });
|
|
172
|
+
}
|
|
173
|
+
snapshots.sort((a, b) => b.touchedAt - a.touchedAt);
|
|
174
|
+
const current = path.resolve(options.clientDirectory, '..', '..');
|
|
175
|
+
const newest = new Set(snapshots.slice(0, options.keepNewest ?? 3).map(item => item.key));
|
|
176
|
+
for (const item of snapshots) {
|
|
177
|
+
if (item.directory === current || newest.has(item.key) || (options.now ?? Date.now()) - item.touchedAt < (options.graceMs ?? 7 * DAY))
|
|
178
|
+
continue;
|
|
179
|
+
try {
|
|
180
|
+
// Validate the full original build before deleting any legacy directory.
|
|
181
|
+
const original = await hashGeneratedClient(item.client);
|
|
182
|
+
const fresh = await snapshot(item.directory);
|
|
183
|
+
const protectedKeys = await leasedKeys(options.stateDirectory, !options.dryRun);
|
|
184
|
+
if (!fresh || fresh.touchedAt !== item.touchedAt || fresh.hash !== item.hash || original.hash !== item.hash
|
|
185
|
+
|| !await original.unchanged() || protectedKeys.has(item.key)) {
|
|
186
|
+
result.push({ path: item.directory, action: 'retained', reason: 'modified-in-use-or-unverified-snapshot', bytes: 0 });
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if ((await fs.stat(item.directory)).mtimeMs !== item.touchedAt)
|
|
190
|
+
continue;
|
|
191
|
+
if (!options.dryRun)
|
|
192
|
+
await fs.rm(item.directory, { recursive: true });
|
|
193
|
+
result.push({ path: item.directory, action: options.dryRun ? 'would-remove' : 'removed', reason: 'verified-expired-generated-client', bytes: original.bytes });
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
result.push({ path: item.directory, action: 'retained', reason: 'snapshot-validation-failed', bytes: 0 });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|