termdock 1.4.173 → 1.4.175
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/.desktop-dist/main.js +204 -7
- package/dist/client/assets/App-BXYZyI_k.js +150 -0
- package/dist/client/assets/App-BXYZyI_k.js.br +0 -0
- package/dist/client/assets/App-BXYZyI_k.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js +2 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js.gz +0 -0
- package/dist/client/assets/DagPlayground-ZIqLZoMo.js +1 -0
- package/dist/client/assets/DagPlayground-ZIqLZoMo.js.br +0 -0
- package/dist/client/assets/DagPlayground-ZIqLZoMo.js.gz +0 -0
- package/dist/client/assets/{DiffLab-xuS6rtpv.js → DiffLab-CSMf8So2.js} +2 -2
- package/dist/client/assets/DiffLab-CSMf8So2.js.br +0 -0
- package/dist/client/assets/DiffLab-CSMf8So2.js.gz +0 -0
- package/dist/client/assets/DiffReview-CE5lW1zQ.js +1 -0
- package/dist/client/assets/DiffReview-CE5lW1zQ.js.br +0 -0
- package/dist/client/assets/DiffReview-CE5lW1zQ.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CpUW75fZ.js +2 -0
- package/dist/client/assets/DiffReviewLab-CpUW75fZ.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CpUW75fZ.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-BQGXUr8t.js → DiffViewer-BomvZ4kq.js} +5 -5
- package/dist/client/assets/DiffViewer-BomvZ4kq.js.br +0 -0
- package/dist/client/assets/DiffViewer-BomvZ4kq.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-BfZzS1_d.js → ModelPreview-BWdZFyhh.js} +3 -3
- package/dist/client/assets/ModelPreview-BWdZFyhh.js.br +0 -0
- package/dist/client/assets/ModelPreview-BWdZFyhh.js.gz +0 -0
- package/dist/client/assets/RightSidebar-CYVwFbOc.js +79 -0
- package/dist/client/assets/RightSidebar-CYVwFbOc.js.br +0 -0
- package/dist/client/assets/RightSidebar-CYVwFbOc.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-Dn_lmPcC.js → abnfDiagram-VCTEODGH-BIPsLStE.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-BIPsLStE.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BIPsLStE.js.gz +0 -0
- package/dist/client/assets/app-D6KzR8B3.js +34 -0
- package/dist/client/assets/app-D6KzR8B3.js.br +0 -0
- package/dist/client/assets/app-D6KzR8B3.js.gz +0 -0
- package/dist/client/assets/{arc-DUybtKGZ.js → arc-DzeT6hBj.js} +1 -1
- package/dist/client/assets/arc-DzeT6hBj.js.br +0 -0
- package/dist/client/assets/arc-DzeT6hBj.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-CLEmOHBp.js → architectureDiagram-5GKGNRK7-B1qsxd_5.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-B1qsxd_5.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-B1qsxd_5.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-O_XM5b9_.js → blockDiagram-I7D4REHJ-NLsa29Jp.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-NLsa29Jp.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-NLsa29Jp.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DlUEslcV.js → c4Diagram-7LVT6UL2-DBAUvou3.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-DBAUvou3.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DBAUvou3.js.gz +0 -0
- package/dist/client/assets/channel-BhwBAWhq.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-DCrP6W79.js → chunk-2Q5K7J3B-BsVAXGLa.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Zm6_pYQv.js → chunk-5VM5RSS4-BKtILsst.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-DkyD-X9z.js → chunk-F27PBJKO-Dn9zcQZG.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-Dn9zcQZG.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-Dn9zcQZG.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-UvWxXAkh.js → chunk-IMKFNOWR-MVtVQeTr.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-MVtVQeTr.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-MVtVQeTr.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-BjeBAFYM.js → chunk-JWPE2WC7-DliIIMe7.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-CnqrQdZC.js → chunk-POPQ4Y6H-XBSus3IC.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-C4a1s0v4.js → chunk-SVP7TREG-wleSQcJR.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-wleSQcJR.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-wleSQcJR.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-hnmjYAl_.js → chunk-TICWLB2K-Dq6LdswN.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-Dq6LdswN.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Dq6LdswN.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-xR4LOz28.js → chunk-XXDRQBXY-xafyKlM-.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-slQB0ns2.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-slQB0ns2.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CqY84fFp.js → cose-bilkent-JH36ORCC-BR7Ohnm_.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-BR7Ohnm_.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-BR7Ohnm_.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-Bd14zuaD.js → cynefin-OW5HDTMX-CmPjQgER.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-CmPjQgER.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-CmPjQgER.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-DSHcNXDs.js → cynefinDiagram-5FMLGOSQ-CFyR9RvM.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CFyR9RvM.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CFyR9RvM.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-CIGWQYeP.js → dagre-GXQ25YYZ-D_RVMRO1.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-D_RVMRO1.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D_RVMRO1.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-C9Fg7OLN.js → diagram-S7CK7UJ4-6fr-ZoPj.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-6fr-ZoPj.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-6fr-ZoPj.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-DoRhl7Fd.js → diagram-UQ7AKVKN-CgwiUtz0.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-CgwiUtz0.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-CgwiUtz0.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-CI83g3iM.js → diagram-VSXAHHWV-CLxzTB9k.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CLxzTB9k.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CLxzTB9k.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-Dp2kP4PB.js → diagram-VX7I27RA-DjxDAvdZ.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-DjxDAvdZ.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DjxDAvdZ.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-KtLMCM-V.js → diagram-Z3DM3KII-CDPOjiz4.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-CDPOjiz4.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CDPOjiz4.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-D_zseu1m.js → ebnfDiagram-PWID7BFC-BLbwyF1O.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLbwyF1O.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLbwyF1O.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DaWUl_Kz.js → erDiagram-RLTQ6QDP-DbjtZnJx.js} +1 -1
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DaWUl_Kz.js.br → erDiagram-RLTQ6QDP-DbjtZnJx.js.br} +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DbjtZnJx.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-BMoJ46zJ.js → flowDiagram-HODETNUW-BirJInAN.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-BirJInAN.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BirJInAN.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-lV_iVmoW.js → ganttDiagram-EL5Y4UJY-d6Q9vSbv.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-d6Q9vSbv.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-d6Q9vSbv.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-C2J0D0kV.css +1 -0
- package/dist/client/assets/gestureArbiter-C2J0D0kV.css.br +0 -0
- package/dist/client/assets/gestureArbiter-C2J0D0kV.css.gz +0 -0
- package/dist/client/assets/gestureArbiter-D1SNS_gK.js +1 -0
- package/dist/client/assets/gestureArbiter-D1SNS_gK.js.br +0 -0
- package/dist/client/assets/gestureArbiter-D1SNS_gK.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BVCT9Btd.js → gitGraphDiagram-WWUBYQGX-BWBoIeS6.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BWBoIeS6.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BWBoIeS6.js.gz +0 -0
- package/dist/client/assets/{index-CnKSUg74.css → index-DUI8KRf6.css} +1 -1
- package/dist/client/assets/index-DUI8KRf6.css.br +0 -0
- package/dist/client/assets/index-DUI8KRf6.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-BVhXOx1S.js → infoDiagram-27XIBGKW-BH4AynkM.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Cyzfuoys.js → ishikawaDiagram-5VMMS53U-BQw90AKR.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BQw90AKR.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-BQw90AKR.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-CEIPDhQF.js → journeyDiagram-3NMN7TZE-DISkQocf.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DISkQocf.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DISkQocf.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-C8OulEOF.js → kanban-definition-UXKFOSKX-Qg2LyEVy.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-Qg2LyEVy.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-Qg2LyEVy.js.gz +0 -0
- package/dist/client/assets/{layout-YN93qRzb.js → layout-Cd7-4_Vr.js} +1 -1
- package/dist/client/assets/layout-Cd7-4_Vr.js.br +0 -0
- package/dist/client/assets/layout-Cd7-4_Vr.js.gz +0 -0
- package/dist/client/assets/{linear-D9T4dYh7.js → linear-DFTi2fTn.js} +1 -1
- package/dist/client/assets/linear-DFTi2fTn.js.br +0 -0
- package/dist/client/assets/linear-DFTi2fTn.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-BdCvvCGV.js → mermaid.core-2v726lUU.js} +5 -5
- package/dist/client/assets/mermaid.core-2v726lUU.js.br +0 -0
- package/dist/client/assets/mermaid.core-2v726lUU.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-CSpES3Jh.js → mindmap-definition-YA3MSWOX-_FpwteCy.js} +4 -4
- package/dist/client/assets/mindmap-definition-YA3MSWOX-_FpwteCy.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-_FpwteCy.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js +2 -0
- package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-6f4vAA1Q.js → pegDiagram-XKGWAZYB-DLFf-HmZ.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-DLFf-HmZ.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DLFf-HmZ.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-BC4Jd-I8.js → pieDiagram-E7YTZNPT-DqTnfXHG.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-DqTnfXHG.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DqTnfXHG.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B0pnPaEN.js → quadrantDiagram-AXDQQJYC-B_oBH52Q.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B_oBH52Q.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B_oBH52Q.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-B-oOamZT.js → railroadDiagram-O6MQD6OU-HxFQ7Pce.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-HxFQ7Pce.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-HxFQ7Pce.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-B3NGIjvv.js +1 -0
- package/dist/client/assets/referenceLongPress-B3NGIjvv.js.br +0 -0
- package/dist/client/assets/referenceLongPress-B3NGIjvv.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-DBSyxt0k.js → requirementDiagram-BXWQKSXE-NZdfaZFk.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-NZdfaZFk.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-NZdfaZFk.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CcohzEZE.js → sankeyDiagram-P5KCCOFB-BgCctv6X.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BgCctv6X.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BgCctv6X.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-D7KS6rq_.js → sequenceDiagram-WJ2MYXX4-Br42Za1R.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Br42Za1R.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Br42Za1R.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-CaRXtn4g.js → sizeCapture-INFHLROL-FT4z4S1b.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-FT4z4S1b.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-FT4z4S1b.js.gz +0 -0
- package/dist/client/assets/sparkles-BaGaevME.js +1 -0
- package/dist/client/assets/{stateDiagram-D77RDMKH-Dz836LqY.js → stateDiagram-D77RDMKH-DD0t9f84.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-DD0t9f84.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DD0t9f84.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BhvVVB56.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-BK4KAY00.js → swimlanes-42K2YHIH-DcC8pGLx.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-DcC8pGLx.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-DcC8pGLx.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DSC5lz0i.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-DWgxTDhO.js → timeline-definition-24CTP7MA-DnwO_Pxd.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-DnwO_Pxd.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DnwO_Pxd.js.gz +0 -0
- package/dist/client/assets/triangle-alert-BCQO9Ap9.js +1 -0
- package/dist/client/assets/{vennDiagram-4TSXK5OY-BV1TIfOp.js → vennDiagram-4TSXK5OY-IVNSrZDM.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-IVNSrZDM.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-IVNSrZDM.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-GYClC5F0.js → wardleyDiagram-VM6X3IG4-BnGetaQL.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BnGetaQL.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-BnGetaQL.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-D1oKV5W0.js → xychartDiagram-S5SC5T6Z-C01eBN-W.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C01eBN-W.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-C01eBN-W.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/preview-shell.html +1 -0
- package/dist/client/preview-shell.js +20 -0
- package/dist/client/preview-shell.js.br +0 -0
- package/dist/client/preview-shell.js.gz +0 -0
- package/dist/client/sw-secure-api.js +132 -0
- package/dist/client/sw-secure-api.js.br +0 -0
- package/dist/client/sw-secure-api.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/collaborationCli.js +298 -0
- package/dist/server/agent/collaborationPrompt.js +12 -3
- package/dist/server/agent/collaborationProtocol.js +50 -0
- package/dist/server/agent/collaborationRoutes.js +86 -0
- package/dist/server/agent/collaborationStore.js +311 -20
- package/dist/server/cli.js +28 -163
- package/dist/server/entry.js +114 -51
- package/dist/server/federation/accessPolicy.js +122 -0
- package/dist/server/federation/authorization.js +226 -0
- package/dist/server/federation/bootstrapCors.js +23 -0
- package/dist/server/federation/cli.js +185 -0
- package/dist/server/federation/deviceNames.js +29 -0
- package/dist/server/federation/directRoutes.js +72 -0
- package/dist/server/federation/invitations.js +123 -0
- package/dist/server/federation/openAccess.js +25 -0
- package/dist/server/federation/originPolicy.js +19 -0
- package/dist/server/federation/packets.js +116 -0
- package/dist/server/federation/passwordBootstrap.js +86 -0
- package/dist/server/federation/passwordLoginRoutes.js +98 -0
- package/dist/server/federation/relay.js +346 -0
- package/dist/server/federation/requestContext.js +4 -0
- package/dist/server/federation/routeAccess.js +124 -0
- package/dist/server/federation/routeInvitations.js +160 -0
- package/dist/server/federation/runtime.js +537 -0
- package/dist/server/federation/secureProtocol.js +223 -0
- package/dist/server/federation/socketDuplex.js +42 -0
- package/dist/server/federation/streamLimits.js +5 -0
- package/dist/server/routes/filesystem.js +2 -1
- package/dist/server/routes/terminal.js +89 -79
- package/dist/server/utils/authProtection.js +13 -0
- package/dist/server/utils/csrfProtection.js +3 -0
- package/dist/server/utils/publicSecurity.js +8 -3
- package/package.json +10 -3
- package/runtime-manifest.json +4 -4
- package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js +0 -2
- package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js.gz +0 -0
- package/dist/client/assets/DagPlayground-D2Wui4jd.js +0 -1
- package/dist/client/assets/DagPlayground-D2Wui4jd.js.br +0 -0
- package/dist/client/assets/DagPlayground-D2Wui4jd.js.gz +0 -0
- package/dist/client/assets/DiffLab-xuS6rtpv.js.br +0 -0
- package/dist/client/assets/DiffLab-xuS6rtpv.js.gz +0 -0
- package/dist/client/assets/DiffReview-CAwpqM_w.js +0 -1
- package/dist/client/assets/DiffReview-CAwpqM_w.js.br +0 -0
- package/dist/client/assets/DiffReview-CAwpqM_w.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-BM_uGdyI.js +0 -2
- package/dist/client/assets/DiffReviewLab-BM_uGdyI.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-BM_uGdyI.js.gz +0 -0
- package/dist/client/assets/DiffViewer-BQGXUr8t.js.br +0 -0
- package/dist/client/assets/DiffViewer-BQGXUr8t.js.gz +0 -0
- package/dist/client/assets/ModelPreview-BfZzS1_d.js.br +0 -0
- package/dist/client/assets/ModelPreview-BfZzS1_d.js.gz +0 -0
- package/dist/client/assets/RightSidebar-Cdqt3C7V.js +0 -55
- package/dist/client/assets/RightSidebar-Cdqt3C7V.js.br +0 -0
- package/dist/client/assets/RightSidebar-Cdqt3C7V.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-Dn_lmPcC.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-Dn_lmPcC.js.gz +0 -0
- package/dist/client/assets/app-DDCkFsNe.js +0 -175
- package/dist/client/assets/app-DDCkFsNe.js.br +0 -0
- package/dist/client/assets/app-DDCkFsNe.js.gz +0 -0
- package/dist/client/assets/arc-DUybtKGZ.js.br +0 -0
- package/dist/client/assets/arc-DUybtKGZ.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CLEmOHBp.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CLEmOHBp.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-O_XM5b9_.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-O_XM5b9_.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DlUEslcV.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DlUEslcV.js.gz +0 -0
- package/dist/client/assets/channel-Bk1VkYvd.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-DkyD-X9z.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DkyD-X9z.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-UvWxXAkh.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-UvWxXAkh.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-C4a1s0v4.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-C4a1s0v4.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-hnmjYAl_.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-hnmjYAl_.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-_tUz3Cxt.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-_tUz3Cxt.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CqY84fFp.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CqY84fFp.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-Bd14zuaD.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-Bd14zuaD.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DSHcNXDs.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DSHcNXDs.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CIGWQYeP.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-CIGWQYeP.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-C9Fg7OLN.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-C9Fg7OLN.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DoRhl7Fd.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DoRhl7Fd.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CI83g3iM.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CI83g3iM.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Dp2kP4PB.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Dp2kP4PB.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-KtLMCM-V.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-KtLMCM-V.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D_zseu1m.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-D_zseu1m.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DaWUl_Kz.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMoJ46zJ.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BMoJ46zJ.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-lV_iVmoW.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-lV_iVmoW.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVCT9Btd.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVCT9Btd.js.gz +0 -0
- package/dist/client/assets/index-CnKSUg74.css.br +0 -0
- package/dist/client/assets/index-CnKSUg74.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cyzfuoys.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cyzfuoys.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CEIPDhQF.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CEIPDhQF.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-C8OulEOF.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-C8OulEOF.js.gz +0 -0
- package/dist/client/assets/layout-YN93qRzb.js.br +0 -0
- package/dist/client/assets/layout-YN93qRzb.js.gz +0 -0
- package/dist/client/assets/linear-D9T4dYh7.js.br +0 -0
- package/dist/client/assets/linear-D9T4dYh7.js.gz +0 -0
- package/dist/client/assets/mermaid.core-BdCvvCGV.js.br +0 -0
- package/dist/client/assets/mermaid.core-BdCvvCGV.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CSpES3Jh.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CSpES3Jh.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-6f4vAA1Q.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-6f4vAA1Q.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-BC4Jd-I8.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-BC4Jd-I8.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0pnPaEN.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0pnPaEN.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-B-oOamZT.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-B-oOamZT.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DBSyxt0k.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DBSyxt0k.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CcohzEZE.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CcohzEZE.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D7KS6rq_.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D7KS6rq_.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CaRXtn4g.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CaRXtn4g.js.gz +0 -0
- package/dist/client/assets/sparkles-CUdUt4qy.js +0 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-Dz836LqY.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-Dz836LqY.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-5V5Y3wwn.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-BK4KAY00.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-BK4KAY00.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Sj-7YK2P.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-DWgxTDhO.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DWgxTDhO.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BV1TIfOp.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BV1TIfOp.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-GYClC5F0.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-GYClC5F0.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1oKV5W0.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1oKV5W0.js.gz +0 -0
package/dist/server/cli.js
CHANGED
|
@@ -10,6 +10,12 @@ import { resolve } from 'path';
|
|
|
10
10
|
// dotenv 不会覆盖已存在的环境变量,因此系统环境变量和命令行参数优先级最高
|
|
11
11
|
loadDotenv({ path: resolve(os.homedir(), '.termdock', '.env'), quiet: true });
|
|
12
12
|
loadDotenv({ path: resolve(process.cwd(), '.env'), quiet: true });
|
|
13
|
+
// Federation commands run before normal server/terminal CLI initialization.
|
|
14
|
+
if (process.argv.slice(2).some(arg => arg === '--federation-pairing' || arg === '--federation-relay')) {
|
|
15
|
+
const { runFederationCli } = await import('./federation/cli.js');
|
|
16
|
+
process.exit(await runFederationCli(process.argv.slice(2)));
|
|
17
|
+
}
|
|
18
|
+
import { parseCollaborationCommand, executeCollaborationCommand, COLLAB_HELP } from './agent/collaborationCli.js';
|
|
13
19
|
import fs from 'fs';
|
|
14
20
|
import http from 'http';
|
|
15
21
|
import https from 'https';
|
|
@@ -183,7 +189,7 @@ Short commands:
|
|
|
183
189
|
Emit one lifecycle/status event from an Agent hook.
|
|
184
190
|
pi Same as agent-plugin
|
|
185
191
|
collab status Show this Session's collaboration groups and peers
|
|
186
|
-
collab inbox Read
|
|
192
|
+
collab inbox Read messages without acknowledging (see collab --help)
|
|
187
193
|
collab send <session-id> <message>
|
|
188
194
|
Send a durable message to a related Session
|
|
189
195
|
collab handoff <session-id> <summary>
|
|
@@ -678,53 +684,11 @@ function parseArgs(argv) {
|
|
|
678
684
|
}
|
|
679
685
|
}
|
|
680
686
|
else if (command === 'collab') {
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if (action === 'status' || action === 'inbox') {
|
|
684
|
-
collab = { action, json };
|
|
685
|
-
}
|
|
686
|
-
else if (action === 'send' || action === 'handoff' || action === 'reply') {
|
|
687
|
-
const target = argv[2];
|
|
688
|
-
const message = argv.slice(3).filter((part) => part !== '--json').join(' ').trim();
|
|
689
|
-
if (!target || !message) {
|
|
690
|
-
console.error(`${ICON.err} ${c.red(`Usage: td collab ${action} <${action === 'reply' ? 'message-id' : 'session-id'}> <message>`)}`);
|
|
691
|
-
process.exit(1);
|
|
692
|
-
}
|
|
693
|
-
collab = { action, target, message, json };
|
|
694
|
-
}
|
|
695
|
-
else if (action === 'add' || action === 'remove') {
|
|
696
|
-
const groupId = argv[2];
|
|
697
|
-
const sessionId = argv[3];
|
|
698
|
-
if (!groupId || !sessionId || groupId.startsWith('-') || sessionId.startsWith('-')) {
|
|
699
|
-
console.error(`${ICON.err} ${c.red(`Usage: td collab ${action} <group-id> <session-id>`)}`);
|
|
700
|
-
process.exit(1);
|
|
701
|
-
}
|
|
702
|
-
collab = { action, groupId, sessionId, json };
|
|
703
|
-
}
|
|
704
|
-
else if (action === 'spawn') {
|
|
705
|
-
const groupId = argv[2];
|
|
706
|
-
const agentSlug = argv[3];
|
|
707
|
-
const optionValue = (flag) => {
|
|
708
|
-
const index = argv.indexOf(flag);
|
|
709
|
-
const value = index >= 0 ? argv[index + 1] : undefined;
|
|
710
|
-
return value && !value.startsWith('--') ? value : undefined;
|
|
711
|
-
};
|
|
712
|
-
if (!groupId || !agentSlug || groupId.startsWith('-') || agentSlug.startsWith('-')) {
|
|
713
|
-
console.error(`${ICON.err} ${c.red('Usage: td collab spawn <group-id> <agent-slug> [--name <name>] [--cwd <path>] [--task <text>]')}`);
|
|
714
|
-
process.exit(1);
|
|
715
|
-
}
|
|
716
|
-
collab = {
|
|
717
|
-
action,
|
|
718
|
-
groupId,
|
|
719
|
-
agentSlug,
|
|
720
|
-
name: optionValue('--name'),
|
|
721
|
-
cwd: optionValue('--cwd'),
|
|
722
|
-
task: optionValue('--task'),
|
|
723
|
-
json,
|
|
724
|
-
};
|
|
687
|
+
try {
|
|
688
|
+
collab = parseCollaborationCommand(argv.slice(1));
|
|
725
689
|
}
|
|
726
|
-
|
|
727
|
-
console.error(
|
|
690
|
+
catch (error) {
|
|
691
|
+
console.error(JSON.stringify({ ok: false, code: 'INVALID_ARGUMENT', error: error instanceof Error ? error.message : String(error) }));
|
|
728
692
|
process.exit(1);
|
|
729
693
|
}
|
|
730
694
|
argv = [];
|
|
@@ -1219,7 +1183,7 @@ async function readStdinText() {
|
|
|
1219
1183
|
process.stdin.on('error', reject);
|
|
1220
1184
|
});
|
|
1221
1185
|
}
|
|
1222
|
-
async function postLocalJson(baseUrl, token, endpoint, payload) {
|
|
1186
|
+
async function postLocalJson(baseUrl, token, endpoint, payload, timeoutMs = 30_000) {
|
|
1223
1187
|
const url = new URL(endpoint, baseUrl);
|
|
1224
1188
|
const body = JSON.stringify(payload);
|
|
1225
1189
|
const isHttps = url.protocol === 'https:';
|
|
@@ -1244,12 +1208,14 @@ async function postLocalJson(baseUrl, token, endpoint, payload) {
|
|
|
1244
1208
|
res.on('data', (chunk) => { responseBody += chunk; });
|
|
1245
1209
|
res.on('end', () => resolve({ statusCode: res.statusCode ?? 0, body: responseBody }));
|
|
1246
1210
|
});
|
|
1211
|
+
const timer = setTimeout(() => req.destroy(new Error('Local collaboration request timed out')), timeoutMs);
|
|
1212
|
+
req.on('close', () => clearTimeout(timer));
|
|
1247
1213
|
req.on('error', reject);
|
|
1248
1214
|
req.write(body);
|
|
1249
1215
|
req.end();
|
|
1250
1216
|
});
|
|
1251
1217
|
}
|
|
1252
|
-
async function getLocalJson(baseUrl, token, endpoint) {
|
|
1218
|
+
async function getLocalJson(baseUrl, token, endpoint, timeoutMs = 30_000) {
|
|
1253
1219
|
const url = new URL(endpoint, baseUrl);
|
|
1254
1220
|
const isHttps = url.protocol === 'https:';
|
|
1255
1221
|
const requestImpl = isHttps ? https.request : http.request;
|
|
@@ -1271,14 +1237,20 @@ async function getLocalJson(baseUrl, token, endpoint) {
|
|
|
1271
1237
|
res.on('data', (chunk) => { responseBody += chunk; });
|
|
1272
1238
|
res.on('end', () => resolve({ statusCode: res.statusCode ?? 0, body: responseBody }));
|
|
1273
1239
|
});
|
|
1240
|
+
const timer = setTimeout(() => req.destroy(new Error('Local collaboration request timed out')), timeoutMs);
|
|
1241
|
+
req.on('close', () => clearTimeout(timer));
|
|
1274
1242
|
req.on('error', reject);
|
|
1275
1243
|
req.end();
|
|
1276
1244
|
});
|
|
1277
1245
|
}
|
|
1278
1246
|
async function runCollab(command) {
|
|
1247
|
+
if (command.action === 'help') {
|
|
1248
|
+
console.log(COLLAB_HELP);
|
|
1249
|
+
return;
|
|
1250
|
+
}
|
|
1279
1251
|
const runningState = getRunningState();
|
|
1280
1252
|
if (!runningState?.localApiToken) {
|
|
1281
|
-
console.error(
|
|
1253
|
+
console.error(JSON.stringify({ ok: false, code: 'SERVICE_UNAVAILABLE', error: 'Termdock is not running or its local API token is unavailable.' }));
|
|
1282
1254
|
process.exit(1);
|
|
1283
1255
|
}
|
|
1284
1256
|
const backendSessionId = process.env.TERMDOCK_BACKEND_SESSION_ID?.trim() || null;
|
|
@@ -1299,123 +1271,16 @@ async function runCollab(command) {
|
|
|
1299
1271
|
}
|
|
1300
1272
|
}
|
|
1301
1273
|
if (!backendSessionId && !tmuxSessionName) {
|
|
1302
|
-
console.error(
|
|
1274
|
+
console.error(JSON.stringify({ ok: false, code: 'SESSION_NOT_FOUND', error: 'td collab must run inside a Termdock-managed Session.' }));
|
|
1303
1275
|
process.exit(1);
|
|
1304
1276
|
}
|
|
1305
1277
|
const context = backendSessionId ? { backendSessionId } : { tmuxSessionName };
|
|
1306
|
-
const contextQuery = new URLSearchParams(Object.entries(context).filter((entry) => typeof entry[1] === 'string')).toString();
|
|
1307
1278
|
const baseUrl = runningState.localUrl
|
|
1308
1279
|
?? `${runningState.scheme ?? 'http'}://${runningState.host === '0.0.0.0' ? 'localhost' : runningState.host}:${runningState.port}`;
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
else if (command.action === 'inbox') {
|
|
1314
|
-
response = await getLocalJson(baseUrl, runningState.localApiToken, `/api/terminal/operations/orchestration/inbox?${contextQuery}&markRead=true`);
|
|
1315
|
-
}
|
|
1316
|
-
else if (command.action === 'reply') {
|
|
1317
|
-
response = await postLocalJson(baseUrl, runningState.localApiToken, '/api/terminal/operations/orchestration/reply', {
|
|
1318
|
-
...context,
|
|
1319
|
-
messageId: command.target,
|
|
1320
|
-
content: command.message,
|
|
1321
|
-
});
|
|
1322
|
-
}
|
|
1323
|
-
else if (command.action === 'add' || command.action === 'remove') {
|
|
1324
|
-
response = await postLocalJson(baseUrl, runningState.localApiToken, '/api/terminal/operations/orchestration/members', {
|
|
1325
|
-
...context,
|
|
1326
|
-
groupId: command.groupId,
|
|
1327
|
-
targetSessionId: command.sessionId,
|
|
1328
|
-
action: command.action,
|
|
1329
|
-
});
|
|
1330
|
-
}
|
|
1331
|
-
else if (command.action === 'spawn') {
|
|
1332
|
-
response = await postLocalJson(baseUrl, runningState.localApiToken, '/api/terminal/operations/orchestration/spawn', {
|
|
1333
|
-
...context,
|
|
1334
|
-
groupId: command.groupId,
|
|
1335
|
-
agentSlug: command.agentSlug,
|
|
1336
|
-
name: command.name,
|
|
1337
|
-
cwd: command.cwd,
|
|
1338
|
-
task: command.task,
|
|
1339
|
-
});
|
|
1340
|
-
}
|
|
1341
|
-
else {
|
|
1342
|
-
response = await postLocalJson(baseUrl, runningState.localApiToken, '/api/terminal/operations/orchestration/send', {
|
|
1343
|
-
...context,
|
|
1344
|
-
targetSessionId: command.target,
|
|
1345
|
-
message: command.message,
|
|
1346
|
-
kind: command.action === 'handoff' ? 'handoff' : 'message',
|
|
1347
|
-
});
|
|
1348
|
-
}
|
|
1349
|
-
let body = {};
|
|
1350
|
-
try {
|
|
1351
|
-
body = JSON.parse(response.body || '{}');
|
|
1352
|
-
}
|
|
1353
|
-
catch { /* handled below */ }
|
|
1354
|
-
if (response.statusCode < 200 || response.statusCode >= 300) {
|
|
1355
|
-
console.error(`${ICON.err} ${c.red(typeof body.error === 'string' ? body.error : 'Collaboration request failed')}`);
|
|
1356
|
-
process.exit(1);
|
|
1357
|
-
}
|
|
1358
|
-
if (command.json) {
|
|
1359
|
-
console.log(JSON.stringify(body, null, 2));
|
|
1360
|
-
return;
|
|
1361
|
-
}
|
|
1362
|
-
if (command.action === 'status') {
|
|
1363
|
-
const groups = Array.isArray(body.groups) ? body.groups : [];
|
|
1364
|
-
const peers = Array.isArray(body.peers) ? body.peers : [];
|
|
1365
|
-
const sessions = Array.isArray(body.sessions) ? body.sessions : [];
|
|
1366
|
-
const agents = Array.isArray(body.agents) ? body.agents : [];
|
|
1367
|
-
console.log(`${ICON.ok} ${c.green(`Collaboration: ${groups.length} group(s), ${peers.length} peer(s)`)}`);
|
|
1368
|
-
for (const group of groups) {
|
|
1369
|
-
console.log(` ${c.green(group.name ?? 'Group')} ${c.dim(`[${group.id ?? ''}]`)}`);
|
|
1370
|
-
if (group.sessionIds?.length)
|
|
1371
|
-
console.log(` ${c.dim(`${group.sessionIds.length} member(s)`)}`);
|
|
1372
|
-
}
|
|
1373
|
-
if (peers.length > 0)
|
|
1374
|
-
console.log(`\n${c.dim('Current group peers:')}`);
|
|
1375
|
-
for (const peer of peers) {
|
|
1376
|
-
console.log(` ${c.cyan(peer.name ?? peer.sessionId ?? 'Session')} ${c.dim(`[${peer.sessionId ?? ''}]`)}`);
|
|
1377
|
-
console.log(` ${peer.status ?? 'offline'} · ${peer.capability ?? ''}`);
|
|
1378
|
-
if (peer.currentTask)
|
|
1379
|
-
console.log(` ${c.dim(peer.currentTask)}`);
|
|
1380
|
-
}
|
|
1381
|
-
for (const group of groups) {
|
|
1382
|
-
const memberIds = new Set(group.sessionIds ?? []);
|
|
1383
|
-
const candidates = sessions.filter((session) => session.sessionId && !memberIds.has(session.sessionId));
|
|
1384
|
-
if (candidates.length > 0) {
|
|
1385
|
-
console.log(`\n${c.dim(`Sessions available to add to ${group.name ?? 'group'} [${group.id ?? ''}]:`)}`);
|
|
1386
|
-
for (const session of candidates) {
|
|
1387
|
-
console.log(` ${session.name ?? 'Session'} ${c.dim(`[${session.sessionId ?? ''}]`)} · ${session.status ?? 'offline'}`);
|
|
1388
|
-
if (session.cwd)
|
|
1389
|
-
console.log(` ${c.dim(session.cwd)}`);
|
|
1390
|
-
}
|
|
1391
|
-
}
|
|
1392
|
-
}
|
|
1393
|
-
if (agents.length > 0) {
|
|
1394
|
-
console.log(`\n${c.dim('Agent types available to spawn:')}`);
|
|
1395
|
-
for (const agent of agents)
|
|
1396
|
-
console.log(` ${agent.displayName ?? agent.slug ?? 'Agent'} ${c.dim(`[${agent.slug ?? ''}]`)}`);
|
|
1397
|
-
}
|
|
1398
|
-
return;
|
|
1399
|
-
}
|
|
1400
|
-
if (command.action === 'inbox') {
|
|
1401
|
-
const messages = Array.isArray(body.messages) ? body.messages : [];
|
|
1402
|
-
if (messages.length === 0) {
|
|
1403
|
-
console.log(`${ICON.ok} ${c.dim('Collaboration inbox is empty.')}`);
|
|
1404
|
-
return;
|
|
1405
|
-
}
|
|
1406
|
-
for (const message of messages) {
|
|
1407
|
-
console.log(`${c.cyan(message.kind ?? 'message')} ${c.dim(`#${message.id ?? ''}`)} from ${message.fromSessionId ?? 'user'}`);
|
|
1408
|
-
console.log(` ${message.content ?? ''}`);
|
|
1409
|
-
}
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
|
-
const success = command.action === 'reply' ? 'Reply sent.'
|
|
1413
|
-
: command.action === 'handoff' ? 'Handoff sent.'
|
|
1414
|
-
: command.action === 'add' ? 'Session added to collaboration group.'
|
|
1415
|
-
: command.action === 'remove' ? 'Session removed from collaboration group.'
|
|
1416
|
-
: command.action === 'spawn' ? 'Agent Session created and added to collaboration group.'
|
|
1417
|
-
: 'Message sent.';
|
|
1418
|
-
console.log(`${ICON.ok} ${c.green(success)}`);
|
|
1280
|
+
process.exitCode = await executeCollaborationCommand(command, Object.fromEntries(Object.entries(context).filter((entry) => typeof entry[1] === 'string')), { request: (method, endpoint, body, timeout) => method === 'GET'
|
|
1281
|
+
? getLocalJson(baseUrl, runningState.localApiToken, endpoint, timeout)
|
|
1282
|
+
: postLocalJson(baseUrl, runningState.localApiToken, endpoint, body, timeout),
|
|
1283
|
+
write: (line) => console.log(line), stdin: readStdinText });
|
|
1419
1284
|
}
|
|
1420
1285
|
async function runInjectChangeAudit(source) {
|
|
1421
1286
|
const raw = source === true
|
|
@@ -3458,7 +3323,7 @@ async function main() {
|
|
|
3458
3323
|
}
|
|
3459
3324
|
if (options.collab) {
|
|
3460
3325
|
await runCollab(options.collab);
|
|
3461
|
-
|
|
3326
|
+
return; // Preserve the receipt exit code and allow piped JSON output to drain.
|
|
3462
3327
|
}
|
|
3463
3328
|
if (options.pluginInit) {
|
|
3464
3329
|
if (options.pluginInitJson)
|
package/dist/server/entry.js
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
import { createOpenAccessRouter } from './federation/openAccess.js';
|
|
2
|
+
import { createPasswordLoginRouter } from './federation/passwordLoginRoutes.js';
|
|
1
3
|
import { apiAccessGate, isTrustedLocalRequest } from './utils/apiAccess.js';
|
|
4
|
+
import { isEncryptedRequest } from './federation/requestContext.js';
|
|
5
|
+
import { createFederationRuntime } from './federation/runtime.js';
|
|
6
|
+
import { RouteAccess } from './federation/routeAccess.js';
|
|
7
|
+
import { RouteInvitationStore } from './federation/routeInvitations.js';
|
|
8
|
+
import { attachRegisteredDirectTargets, verifyDirectTarget } from './federation/directRoutes.js';
|
|
9
|
+
import { secureChannelOriginAllowed } from './federation/originPolicy.js';
|
|
10
|
+
import { RelayRouter } from './federation/relay.js';
|
|
2
11
|
import { assertPublicSecurity, securityHeaders } from './utils/publicSecurity.js';
|
|
3
|
-
import { guardWebSocketSession } from './utils/webSocketSecurity.js';
|
|
4
|
-
import { readTerminalHandshakeDimensions } from './utils/terminalHandshakeDimensions.js';
|
|
5
12
|
import { apiCachePolicy } from './utils/apiCachePolicy.js';
|
|
6
13
|
import 'dotenv/config';
|
|
7
14
|
import express from 'express';
|
|
@@ -23,7 +30,7 @@ import { createOnboardingRouter } from './routes/onboarding.js';
|
|
|
23
30
|
import { createLocalRouter } from './routes/local.js';
|
|
24
31
|
import { csrfProtection } from './utils/csrfProtection.js';
|
|
25
32
|
import { pathValidator } from './utils/pathValidator.js';
|
|
26
|
-
import {
|
|
33
|
+
import { requireAuth, renewSessionMiddleware } from './utils/authProtection.js';
|
|
27
34
|
import { localAccessManager } from './utils/localAccess.js';
|
|
28
35
|
import { isAllowedHost, isUpgradeOriginAllowed, validateHostMiddleware, validateOriginMiddleware, } from './utils/requestSecurity.js';
|
|
29
36
|
import { getCookieSecurityOptions, setSecureCookieMode } from './utils/cookieSecurity.js';
|
|
@@ -37,22 +44,6 @@ import { getTermdockVersion, TERMDOCK_CAPABILITIES, TERMDOCK_PROTOCOL_VERSION, }
|
|
|
37
44
|
import { PORT, DEFAULT_HOST } from './config.js';
|
|
38
45
|
const CLIENT_STATE_COOKIE = 'termdock-client';
|
|
39
46
|
export const DEFAULT_PORT = PORT.backend;
|
|
40
|
-
/** Read one cookie from a raw Cookie header (upgrade requests bypass the
|
|
41
|
-
* cookie-parser middleware). Values are percent-encoded. */
|
|
42
|
-
function getCookieValue(cookieHeader, name) {
|
|
43
|
-
if (!cookieHeader)
|
|
44
|
-
return undefined;
|
|
45
|
-
const pattern = new RegExp(`(?:^|;\\s*)${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}=([^;]*)`);
|
|
46
|
-
const match = cookieHeader.match(pattern);
|
|
47
|
-
if (!match)
|
|
48
|
-
return undefined;
|
|
49
|
-
try {
|
|
50
|
-
return decodeURIComponent(match[1]);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
return match[1];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
47
|
const CLIENT_LOG_DEDUP_WINDOW_MS = 5_000;
|
|
57
48
|
const CLIENT_LOG_RATE_WINDOW_MS = 10_000;
|
|
58
49
|
const CLIENT_LOG_RATE_LIMIT = 120;
|
|
@@ -146,6 +137,17 @@ export function createApp(options = {}) {
|
|
|
146
137
|
app.disable('x-powered-by');
|
|
147
138
|
app.use(securityHeaders);
|
|
148
139
|
app.use(validateHostMiddleware);
|
|
140
|
+
if (options.secureRequired)
|
|
141
|
+
app.use('/api/auth/open', createOpenAccessRouter(() => app.locals.passwordRuntime?.serviceId));
|
|
142
|
+
if (options.secureRequired)
|
|
143
|
+
app.use('/api/auth/password', createPasswordLoginRouter(() => app.locals.passwordRuntime));
|
|
144
|
+
app.use('/api', (req, res, next) => {
|
|
145
|
+
if (!options.secureRequired || isEncryptedRequest(req) || isTrustedLocalRequest(req, options.localApiToken))
|
|
146
|
+
return next();
|
|
147
|
+
if (req.method === 'GET' && ['/meta', '/auth/status'].includes(req.path))
|
|
148
|
+
return next();
|
|
149
|
+
res.status(426).json({ error: 'Pair this device to use an encrypted connection', code: 'E2EE_REQUIRED' });
|
|
150
|
+
});
|
|
149
151
|
// Authenticate before allocating request bodies, including future API routes.
|
|
150
152
|
app.use(cookieParser());
|
|
151
153
|
app.use('/api', renewSessionMiddleware);
|
|
@@ -393,12 +395,78 @@ export function startServer(options = {}) {
|
|
|
393
395
|
});
|
|
394
396
|
runtimeMonitor.start();
|
|
395
397
|
const app = createApp({
|
|
398
|
+
secureRequired: true,
|
|
396
399
|
port: options.onboardingPort ?? port,
|
|
397
400
|
httpsCaPath: options.httpsCaPath,
|
|
398
401
|
localApiToken: options.localApiToken,
|
|
399
402
|
runtimeMonitor,
|
|
400
403
|
});
|
|
401
404
|
const { server, scheme } = createServerForApp(app, options);
|
|
405
|
+
let entrySubjectAllowed = (_subjectId) => false;
|
|
406
|
+
let routeInvitations;
|
|
407
|
+
const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true);
|
|
408
|
+
const relayRouter = new RelayRouter({
|
|
409
|
+
authenticate: context => context,
|
|
410
|
+
allowRegister: (principal, serviceId) => routeAccess.allowRegister(principal, serviceId),
|
|
411
|
+
allowRoute: (principal, serviceId) => routeAccess.allowRoute(principal, serviceId),
|
|
412
|
+
});
|
|
413
|
+
const dynamicDirectTargets = new Map();
|
|
414
|
+
server.once('close', () => { for (const close of dynamicDirectTargets.values())
|
|
415
|
+
close(); });
|
|
416
|
+
try {
|
|
417
|
+
const detachDirectTargets = attachRegisteredDirectTargets(relayRouter, routeAccess.configuredDirectTargets(), path.join(homedir(), '.termdock', 'federation'));
|
|
418
|
+
server.once('close', detachDirectTargets);
|
|
419
|
+
}
|
|
420
|
+
catch {
|
|
421
|
+
console.error('Configured direct routes unavailable; target access remains closed.');
|
|
422
|
+
}
|
|
423
|
+
server.once('close', () => relayRouter.close());
|
|
424
|
+
const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
|
|
425
|
+
terminal: handleTerminalWebSocket, control: handleControlWebSocket,
|
|
426
|
+
}, {
|
|
427
|
+
listRouteAccess: () => routeInvitations?.list() || [],
|
|
428
|
+
grantRouteAccess: async (issuerId, targetServiceId, subjectId, url) => {
|
|
429
|
+
if (!routeInvitations)
|
|
430
|
+
throw new Error('ROUTE_NOT_AVAILABLE');
|
|
431
|
+
if (!routeAccess.hasConfiguredTarget(targetServiceId)) {
|
|
432
|
+
if (!url)
|
|
433
|
+
throw new Error('ROUTE_TARGET_NOT_CONFIGURED');
|
|
434
|
+
const target = { serviceId: targetServiceId, url };
|
|
435
|
+
await verifyDirectTarget(target);
|
|
436
|
+
const runtime = await federation;
|
|
437
|
+
if (!runtime.store.authorize({ subjectId: issuerId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed)
|
|
438
|
+
throw new Error('AUTHORIZATION_DENIED');
|
|
439
|
+
routeAccess.addDirectTarget(target);
|
|
440
|
+
if (!dynamicDirectTargets.has(targetServiceId))
|
|
441
|
+
dynamicDirectTargets.set(targetServiceId, attachRegisteredDirectTargets(relayRouter, [target]));
|
|
442
|
+
}
|
|
443
|
+
return routeInvitations.grant(issuerId, targetServiceId, subjectId);
|
|
444
|
+
},
|
|
445
|
+
revokeRouteAccess: id => routeInvitations?.revoke(id) || false,
|
|
446
|
+
issueRouteTicket: (subjectId, serviceId) => routeAccess.issueRouteTicket(subjectId, serviceId),
|
|
447
|
+
hasRouteGrant: (subjectId, serviceId) => routeInvitations?.allows(subjectId, serviceId) === true,
|
|
448
|
+
createRouteInvitation: (issuerId, serviceId) => {
|
|
449
|
+
if (!routeInvitations)
|
|
450
|
+
throw new Error('ROUTE_NOT_AVAILABLE');
|
|
451
|
+
return routeInvitations.create(issuerId, serviceId);
|
|
452
|
+
},
|
|
453
|
+
consumeRouteInvitation: (code, subjectId) => {
|
|
454
|
+
if (!routeInvitations)
|
|
455
|
+
throw new Error('ROUTE_NOT_AVAILABLE');
|
|
456
|
+
return routeInvitations.consume(code, subjectId);
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
void federation.then(runtime => {
|
|
460
|
+
app.locals.passwordRuntime = runtime;
|
|
461
|
+
entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
|
|
462
|
+
routeInvitations = new RouteInvitationStore({
|
|
463
|
+
filePath: path.join(homedir(), '.termdock', 'federation', 'route-invitations.json'), serviceId: runtime.serviceId,
|
|
464
|
+
issuerAllowed: subjectId => entrySubjectAllowed(subjectId),
|
|
465
|
+
targetAvailable: serviceId => routeAccess.hasConfiguredTarget(serviceId) && relayRouter.hasRoute(serviceId),
|
|
466
|
+
});
|
|
467
|
+
}).catch(() => console.error('Route invitations unavailable; uninitialized route grants remain denied.'));
|
|
468
|
+
void federation.catch(() => console.error('Encrypted access initialization failed; business access remains closed.'));
|
|
469
|
+
server.once('close', () => { void federation.then(runtime => runtime.close()).catch(() => { }); });
|
|
402
470
|
server.headersTimeout = 10_000;
|
|
403
471
|
server.requestTimeout = 120_000;
|
|
404
472
|
server.keepAliveTimeout = 5_000;
|
|
@@ -464,14 +532,12 @@ export function startServer(options = {}) {
|
|
|
464
532
|
zlibDeflateOptions: { level: 6, memLevel: 7 },
|
|
465
533
|
},
|
|
466
534
|
});
|
|
467
|
-
const WS_PATH_RE = /^\/api\/terminal\/([^/]+)\/ws$/;
|
|
468
|
-
const CONTROL_WS_PATH = '/api/control/ws';
|
|
469
535
|
server.on('upgrade', (request, socket, head) => {
|
|
470
536
|
if (wss.clients.size >= 256) {
|
|
471
537
|
socket.end('HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\n\r\n');
|
|
472
538
|
return;
|
|
473
539
|
}
|
|
474
|
-
if (!isAllowedHost(request.headers.host)
|
|
540
|
+
if (!isAllowedHost(request.headers.host)) {
|
|
475
541
|
socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
476
542
|
socket.destroy();
|
|
477
543
|
return;
|
|
@@ -485,41 +551,38 @@ export function startServer(options = {}) {
|
|
|
485
551
|
return;
|
|
486
552
|
}
|
|
487
553
|
const pathname = url.pathname;
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
554
|
+
if (pathname === '/api/federation/relay') {
|
|
555
|
+
// Native relay peers have no Origin. Authenticate explicitly before upgrade.
|
|
556
|
+
if (scheme !== 'https' || (request.headers.origin && !isUpgradeOriginAllowed(request.headers.origin, request.headers.host))) {
|
|
557
|
+
socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const principal = routeAccess.authenticate(request.headers.authorization, url.searchParams.get('routeToken'));
|
|
561
|
+
if (!principal) {
|
|
562
|
+
socket.end('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
wss.handleUpgrade(request, socket, head, ws => {
|
|
566
|
+
void relayRouter.attach(ws, principal).then(attached => { if (attached)
|
|
567
|
+
ws.send(JSON.stringify({ type: 'ready' })); });
|
|
568
|
+
});
|
|
496
569
|
return;
|
|
497
570
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
571
|
+
if (pathname === '/api/federation/secure') {
|
|
572
|
+
// This exception applies only to the cookie-independent Noise handshake.
|
|
573
|
+
// Target pinning and device authorization remain mandatory inside it.
|
|
574
|
+
if (!secureChannelOriginAllowed(request.headers.origin, () => isUpgradeOriginAllowed(request.headers.origin, request.headers.host))) {
|
|
575
|
+
socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
501
578
|
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
502
|
-
|
|
503
|
-
guardWebSocketSession(ws, cookieHeader);
|
|
579
|
+
void federation.then(runtime => runtime.accept(ws, { allowOpenAccess: !!request.headers.origin && isUpgradeOriginAllowed(request.headers.origin, request.headers.host) })).catch(() => ws.close(1011, 'Encrypted access unavailable'));
|
|
504
580
|
});
|
|
505
581
|
return;
|
|
506
582
|
}
|
|
507
|
-
//
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
socket.destroy();
|
|
511
|
-
return;
|
|
512
|
-
}
|
|
513
|
-
const sessionId = match[1];
|
|
514
|
-
const clientId = crypto.randomUUID();
|
|
515
|
-
const pushClientId = getCookieValue(cookieHeader, CLIENT_STATE_COOKIE);
|
|
516
|
-
// 短线重连时客户端会带上 ?since=<lastSeq>,让服务端只补发增量。
|
|
517
|
-
const sinceParam = url.searchParams.get('since');
|
|
518
|
-
const sinceSeq = sinceParam ? Math.max(0, Number.parseInt(sinceParam, 10) || 0) : 0;
|
|
519
|
-
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
520
|
-
handleTerminalWebSocket(ws, sessionId, clientId, { sinceSeq, pushClientId, streamEpoch: url.searchParams.get('epoch') ?? undefined, flowControl: url.searchParams.get('flow') === '2', independentTmux: url.searchParams.get('transport') === 'tmux-client', outputActive: url.searchParams.get('active') !== '0' }, readTerminalHandshakeDimensions(url.searchParams));
|
|
521
|
-
guardWebSocketSession(ws, cookieHeader);
|
|
522
|
-
});
|
|
583
|
+
// Business WebSockets have no plaintext fallback after pairing migration.
|
|
584
|
+
socket.end('HTTP/1.1 426 Upgrade Required\r\nConnection: close\r\n\r\n');
|
|
585
|
+
return;
|
|
523
586
|
});
|
|
524
587
|
server.listen(port, host, () => {
|
|
525
588
|
const displayHost = host === '0.0.0.0' ? 'localhost' : host;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
const METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);
|
|
2
|
+
export function canonicalApiPath(path) {
|
|
3
|
+
if (typeof path !== 'string' || !path.startsWith('/api/') || /[\\\x00-\x20\x7f#]/.test(path))
|
|
4
|
+
return null;
|
|
5
|
+
const pathname = path.split('?')[0];
|
|
6
|
+
if (pathname.includes('//'))
|
|
7
|
+
return null;
|
|
8
|
+
try {
|
|
9
|
+
const segments = pathname.split('/').map((segment, index) => {
|
|
10
|
+
if (index === 0 || (segment === '' && index === pathname.split('/').length - 1))
|
|
11
|
+
return segment;
|
|
12
|
+
const value = decodeURIComponent(segment);
|
|
13
|
+
// Decode exactly once. Never allow an encoded separator, traversal segment,
|
|
14
|
+
// control byte, invalid UTF-8 or a second escape layer to change routing.
|
|
15
|
+
if (!value || value === '.' || value === '..' || /[\\/\x00-\x1f\x7f]/.test(value) || /%[a-f0-9]{2}/i.test(value))
|
|
16
|
+
throw new Error('Ambiguous API path');
|
|
17
|
+
return value;
|
|
18
|
+
});
|
|
19
|
+
const canonical = segments.join('/');
|
|
20
|
+
if (canonical.endsWith('/') && !canonical.startsWith('/api/terminal/fs/preview/'))
|
|
21
|
+
return null;
|
|
22
|
+
return canonical;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const pathOnly = canonicalApiPath;
|
|
29
|
+
/** Restricts a full-service tunnel to business APIs, excluding authentication,
|
|
30
|
+
* local-only operations and recursive federation/bootstrap endpoints. */
|
|
31
|
+
export function isBusinessApiPath(method, path) {
|
|
32
|
+
if (!METHODS.has(method))
|
|
33
|
+
return false;
|
|
34
|
+
const pathname = pathOnly(path);
|
|
35
|
+
if (!pathname)
|
|
36
|
+
return false;
|
|
37
|
+
return !/^\/api\/(?:auth|local|federation|onboarding|csrf)(?:\/|$)/i.test(pathname);
|
|
38
|
+
}
|
|
39
|
+
const fileReads = new Set(['list', 'watch', 'search', 'read', 'git-blob', 'eda-preview', 'eda-inspect', 'blob', 'video', 'download', 'diff', 'diff-files', 'untracked-files', 'git-context', 'git-bundle', 'change-audit', 'branch-diff', 'commit-diff', 'git-recent-commits', 'branch-audit', 'git-action/status', 'local-open-availability']);
|
|
40
|
+
const filePosts = new Set(['git-action', 'apply-hunk', 'upload', 'open-in-file-browser']);
|
|
41
|
+
const settings = new Set(['settings', 'toolbar-presets', 'program-detection', 'context-draft']);
|
|
42
|
+
const serviceReads = new Set(['update', 'auto-title/catalog', 'agent-hooks', 'agent-launchers', 'agent-plugins', 'quota', 'tmux/status', 'tmux/sessions', 'agent-resume-history']);
|
|
43
|
+
const reservedSessionIds = new Set(['fs', 'settings', 'client-state', 'session-inventory', 'operations', 'tmux', 'create', 'force-kill', 'serialize-state', 'agent-resume-history', 'toolbar-presets', 'context-draft', 'program-detection', 'update', 'quota', 'agent-hooks', 'agent-launchers', 'agent-plugins', 'agent-plugin-icon', 'auto-title', 'directory-suggestions']);
|
|
44
|
+
export function resolveAccessRequest(method, path) {
|
|
45
|
+
if (!isBusinessApiPath(method, path))
|
|
46
|
+
return null;
|
|
47
|
+
const pathname = pathOnly(path);
|
|
48
|
+
const read = method === 'GET' || method === 'HEAD';
|
|
49
|
+
if (/^\/api\/notifications\/(status|subscribe|preferences|unsubscribe)$/.test(pathname)) {
|
|
50
|
+
if (read && pathname.endsWith('/status'))
|
|
51
|
+
return { action: 'service.view' };
|
|
52
|
+
if (method === 'POST' && !pathname.endsWith('/status'))
|
|
53
|
+
return { action: 'service.configure' };
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
const prefix = '/api/terminal/';
|
|
57
|
+
if (!pathname.startsWith(prefix))
|
|
58
|
+
return null;
|
|
59
|
+
const route = pathname.slice(prefix.length);
|
|
60
|
+
if (route.startsWith('fs/')) {
|
|
61
|
+
const op = route.slice(3);
|
|
62
|
+
if (read && (fileReads.has(op) || /^preview\/.+/.test(op)))
|
|
63
|
+
return { action: 'file.read' };
|
|
64
|
+
if ((method === 'POST' && filePosts.has(op)) || (method === 'DELETE' && ['file', 'change-audit', 'branch-audit'].includes(op)) || (method === 'GET' && op === 'cancel-slot'))
|
|
65
|
+
return { action: 'file.write' };
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
if (read && route === 'session-inventory')
|
|
69
|
+
return { action: 'service.view', metadata: 'session-inventory' };
|
|
70
|
+
if (read && route === 'client-state')
|
|
71
|
+
return { action: 'service.view', metadata: 'global-client-state' };
|
|
72
|
+
if (settings.has(route)) {
|
|
73
|
+
if (read)
|
|
74
|
+
return { action: 'service.view' };
|
|
75
|
+
if (method === 'PUT' || (method === 'DELETE' && route === 'program-detection'))
|
|
76
|
+
return { action: 'service.configure' };
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (read && serviceReads.has(route))
|
|
80
|
+
return { action: 'service.view' };
|
|
81
|
+
if (read && route === 'directory-suggestions')
|
|
82
|
+
return { action: 'file.read' };
|
|
83
|
+
if (method === 'POST' && route === 'create')
|
|
84
|
+
return { action: 'service.create-session' };
|
|
85
|
+
const session = /^([^/]+)(?:\/(health|attach|stream|input|resize|tmux|restart|agent-resume))?$/.exec(route);
|
|
86
|
+
if (session && !reservedSessionIds.has(session[1].toLowerCase())) {
|
|
87
|
+
const sessionId = session[1];
|
|
88
|
+
const op = session[2];
|
|
89
|
+
if (read && ['health', 'attach', 'stream', 'agent-resume'].includes(op))
|
|
90
|
+
return { action: 'session.view', sessionId };
|
|
91
|
+
if (method === 'POST' && op === 'input')
|
|
92
|
+
return { action: 'session.input', sessionId };
|
|
93
|
+
if (method === 'POST' && op === 'resize')
|
|
94
|
+
return { action: 'session.resize', sessionId };
|
|
95
|
+
if (method === 'DELETE' && !op)
|
|
96
|
+
return { action: 'session.terminate', sessionId };
|
|
97
|
+
// tmux switch-session can reach other backends; restart/agent-resume accept
|
|
98
|
+
// launch configuration. Their unrestricted bodies require service authority.
|
|
99
|
+
if (method === 'POST' && ['tmux', 'restart', 'agent-resume'].includes(op))
|
|
100
|
+
return { action: 'service.configure' };
|
|
101
|
+
}
|
|
102
|
+
// Complex operations manipulate global state or accept additional resource IDs.
|
|
103
|
+
// An unregistered operation intentionally receives no custom-grant mapping.
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
/** WS control mapping; caller must bind sessionId to the opened channel. */
|
|
107
|
+
export function terminalMessageAction(type) {
|
|
108
|
+
switch (type) {
|
|
109
|
+
case 'input':
|
|
110
|
+
case 'focus':
|
|
111
|
+
case 'agent-review-ack': return 'session.input';
|
|
112
|
+
case 'resize': return 'session.resize';
|
|
113
|
+
case 'tmux': return 'service.configure';
|
|
114
|
+
case 'ping':
|
|
115
|
+
case 'viewing':
|
|
116
|
+
case 'output-subscription':
|
|
117
|
+
case 'output-ack': return 'session.view';
|
|
118
|
+
// Legacy flow-control can pause the shared PTY; grant only to controllers.
|
|
119
|
+
case 'flow-control': return 'session.input';
|
|
120
|
+
default: return null;
|
|
121
|
+
}
|
|
122
|
+
}
|