termdock 1.4.299 → 1.4.301
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +1 -1
- package/README.md +2 -1
- package/dist/client/assets/App-BziVLy1b.js +179 -0
- package/dist/client/assets/App-BziVLy1b.js.br +0 -0
- package/dist/client/assets/App-BziVLy1b.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-CARNBEqQ.js +12 -0
- package/dist/client/assets/ArchitecturePanel-CARNBEqQ.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CARNBEqQ.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-2lXfrgk4.js → ChangeWalkthroughPanel-00oa0z6w.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-00oa0z6w.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-00oa0z6w.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DuEeKQKZ.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-DuEeKQKZ.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-DuEeKQKZ.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-B_mzpNuw.js +2 -0
- package/dist/client/assets/ComputerControlView-C9v1Ooc9.js +5 -0
- package/dist/client/assets/ComputerControlView-C9v1Ooc9.js.br +0 -0
- package/dist/client/assets/ComputerControlView-C9v1Ooc9.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-EFUb9S3u.js → DagPlayground-DfUpMALj.js} +1 -1
- package/dist/client/assets/DagPlayground-DfUpMALj.js.br +0 -0
- package/dist/client/assets/DagPlayground-DfUpMALj.js.gz +0 -0
- package/dist/client/assets/{DiffLab-CXWjSE2q.js → DiffLab-Cf_URhQt.js} +1 -1
- package/dist/client/assets/DiffLab-Cf_URhQt.js.br +0 -0
- package/dist/client/assets/DiffLab-Cf_URhQt.js.gz +0 -0
- package/dist/client/assets/{DiffReview-ByMkBaD9.js → DiffReview-DjG9vCpH.js} +1 -1
- package/dist/client/assets/DiffReview-DjG9vCpH.js.br +0 -0
- package/dist/client/assets/DiffReview-DjG9vCpH.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-DoWaKm94.js → DiffReviewLab-BKOvHdJY.js} +2 -2
- package/dist/client/assets/DiffReviewLab-BKOvHdJY.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-BKOvHdJY.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-B4hn9z0k.js → DiffViewer-6n3tgsdc.js} +8 -8
- package/dist/client/assets/DiffViewer-6n3tgsdc.js.br +0 -0
- package/dist/client/assets/DiffViewer-6n3tgsdc.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-C-LJZ8so.js → ModelPreview-BNs2sRct.js} +1 -1
- package/dist/client/assets/ModelPreview-BNs2sRct.js.br +0 -0
- package/dist/client/assets/ModelPreview-BNs2sRct.js.gz +0 -0
- package/dist/client/assets/RightSidebar-CcMSU1v8.js +1677 -0
- package/dist/client/assets/RightSidebar-CcMSU1v8.js.br +0 -0
- package/dist/client/assets/RightSidebar-CcMSU1v8.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-BY-pfYKk.js → abnfDiagram-VCTEODGH-DogjvRKp.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-DogjvRKp.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-DogjvRKp.js.gz +0 -0
- package/dist/client/assets/app-D6vN8PW-.js +35 -0
- package/dist/client/assets/app-D6vN8PW-.js.br +0 -0
- package/dist/client/assets/app-D6vN8PW-.js.gz +0 -0
- package/dist/client/assets/{arc-DIENgCZn.js → arc-Cqel4Ted.js} +1 -1
- package/dist/client/assets/arc-Cqel4Ted.js.br +0 -0
- package/dist/client/assets/arc-Cqel4Ted.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-C1h4e4az.js → architectureDiagram-5GKGNRK7-DgvPtgNb.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DgvPtgNb.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DgvPtgNb.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-B_yOr54K.js → blockDiagram-I7D4REHJ-BxbWO4x4.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-BxbWO4x4.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-BxbWO4x4.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DRD4zxIo.js → c4Diagram-7LVT6UL2-gsA37gjL.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-gsA37gjL.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-gsA37gjL.js.gz +0 -0
- package/dist/client/assets/channel-CBc8-n7y.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CXytnjmD.js → chunk-2Q5K7J3B-ChC9yYOd.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Bjmajkf7.js → chunk-5VM5RSS4-Bi4Hc8yL.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-CQCL8mJG.js → chunk-F27PBJKO-DvVWvQEk.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-DvVWvQEk.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-DvVWvQEk.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-ruHGCADe.js → chunk-IMKFNOWR-BnwQLR5S.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-BnwQLR5S.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BnwQLR5S.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-U18S7rxZ.js → chunk-JWPE2WC7-BkNnaDiR.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-DWnwssfL.js → chunk-POPQ4Y6H-BRzm6Uzi.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-DMHj3NSq.js → chunk-SVP7TREG-CDYm-dzk.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-CDYm-dzk.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CDYm-dzk.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-Dh_ekvFW.js → chunk-TICWLB2K-BKaW9Z9i.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-BKaW9Z9i.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BKaW9Z9i.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-Bq5wD6iJ.js → chunk-XXDRQBXY-DcHZ_FHb.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-CNFc2dOc.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CNFc2dOc.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CsySWhYg.js → cose-bilkent-JH36ORCC-DCbbTNc6.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-DCbbTNc6.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-DCbbTNc6.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-9DKE5wV7.js → cynefin-OW5HDTMX-i23hD0Z2.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-i23hD0Z2.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-i23hD0Z2.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-B_8fvr9c.js → cynefinDiagram-5FMLGOSQ-CbyLYcqc.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CbyLYcqc.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CbyLYcqc.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-BOdsFNFT.js → dagre-GXQ25YYZ-iJ4VKzZm.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-iJ4VKzZm.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-iJ4VKzZm.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-6v_qvAvi.js → diagram-S7CK7UJ4-BNVZcdsO.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-BNVZcdsO.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BNVZcdsO.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-c03oOGhD.js → diagram-UQ7AKVKN-1ghTvPiu.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-1ghTvPiu.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-1ghTvPiu.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-MMIhRYp_.js → diagram-VSXAHHWV-Ce_0nMd3.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-Ce_0nMd3.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-Ce_0nMd3.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-B_l-uebl.js → diagram-VX7I27RA-CLHEkhUt.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-CLHEkhUt.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CLHEkhUt.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-DKoBC5gn.js → diagram-Z3DM3KII-iyb5DkP6.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-iyb5DkP6.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-iyb5DkP6.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-Bs3wu8Az.js → ebnfDiagram-PWID7BFC-Dxh2TzXm.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Dxh2TzXm.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Dxh2TzXm.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-mYnHYbKl.js → erDiagram-RLTQ6QDP-Cr8IkdCC.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-Cr8IkdCC.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-Cr8IkdCC.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-CwCLWbib.js → flowDiagram-HODETNUW-B8ZF1bwr.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-B8ZF1bwr.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-B8ZF1bwr.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-mse8c-ni.js → ganttDiagram-EL5Y4UJY-zhdL10eM.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-zhdL10eM.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-zhdL10eM.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-BviL29ps.js → gestureArbiter-CXYHfU__.js} +1 -1
- package/dist/client/assets/gestureArbiter-CXYHfU__.js.br +0 -0
- package/dist/client/assets/gestureArbiter-CXYHfU__.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BVUfLjAX.js → gitGraphDiagram-WWUBYQGX-BQf2fHBo.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BQf2fHBo.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BQf2fHBo.js.gz +0 -0
- package/dist/client/assets/{index-C6ftfFlG.css → index-CNY7CTVa.css} +1 -1
- package/dist/client/assets/index-CNY7CTVa.css.br +0 -0
- package/dist/client/assets/index-CNY7CTVa.css.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-XjoFF4BC.js → infoDiagram-27XIBGKW-BT8xey7S.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-bH299bWL.js → ishikawaDiagram-5VMMS53U-DunPdwW9.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DunPdwW9.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DunPdwW9.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-BG0u_X1A.js → journeyDiagram-3NMN7TZE-B0y75iIg.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-B0y75iIg.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-B0y75iIg.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-DRoc9N-i.js → kanban-definition-UXKFOSKX-BfgA5WLo.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-BfgA5WLo.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BfgA5WLo.js.gz +0 -0
- package/dist/client/assets/{layout-mh0t6JpE.js → layout-BvjABLff.js} +1 -1
- package/dist/client/assets/layout-BvjABLff.js.br +0 -0
- package/dist/client/assets/layout-BvjABLff.js.gz +0 -0
- package/dist/client/assets/{linear-B9KTRuUV.js → linear-DgeFRliF.js} +1 -1
- package/dist/client/assets/linear-DgeFRliF.js.br +0 -0
- package/dist/client/assets/linear-DgeFRliF.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-Bu2G9zhK.js → mermaid.core-DM6Rlal9.js} +5 -5
- package/dist/client/assets/mermaid.core-DM6Rlal9.js.br +0 -0
- package/dist/client/assets/mermaid.core-DM6Rlal9.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-Go6Bg5xm.js → mindmap-definition-YA3MSWOX-DhEiABv6.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DhEiABv6.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-DhEiABv6.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-C7-MUAHQ.js → passwordBootstrap-Dxv0I4wM.js} +1 -1
- package/dist/client/assets/passwordBootstrap-Dxv0I4wM.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-Dxv0I4wM.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-DKUIl_U6.js → pegDiagram-XKGWAZYB-LTlVaJfm.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-LTlVaJfm.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-LTlVaJfm.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-aoDNlV7J.js → pieDiagram-E7YTZNPT-9bnryna3.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-9bnryna3.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-9bnryna3.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BKuuaF2s.js → quadrantDiagram-AXDQQJYC-ZBmgIpsJ.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BKuuaF2s.js.br → quadrantDiagram-AXDQQJYC-ZBmgIpsJ.js.br} +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-ZBmgIpsJ.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-BwfCW2bp.js → railroadDiagram-O6MQD6OU-DTtXvKOu.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DTtXvKOu.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DTtXvKOu.js.gz +0 -0
- package/dist/client/assets/rdpSession-Ggo3N5s5.js +1 -0
- package/dist/client/assets/rdpSession-Ggo3N5s5.js.br +0 -0
- package/dist/client/assets/rdpSession-Ggo3N5s5.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-Df8W2cyP.js +1 -0
- package/dist/client/assets/referenceLongPress-Df8W2cyP.js.br +0 -0
- package/dist/client/assets/referenceLongPress-Df8W2cyP.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-DbpU9Uig.js → requirementDiagram-BXWQKSXE-BNia9zI0.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BNia9zI0.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BNia9zI0.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-Bxa22XVM.js → sankeyDiagram-P5KCCOFB-uvg5npfK.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-uvg5npfK.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-uvg5npfK.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-C4GvzT9z.js → sequenceDiagram-WJ2MYXX4-DpJNf2MD.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DpJNf2MD.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DpJNf2MD.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-B9zJJwQw.js → sizeCapture-INFHLROL-CkWB2AnF.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-CkWB2AnF.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CkWB2AnF.js.gz +0 -0
- package/dist/client/assets/{sparkles-BJbfT-v-.js → sparkles-DOuEtn62.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-fqbw6LZa.js → stateDiagram-D77RDMKH-CeMzMQq-.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-CeMzMQq-.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CeMzMQq-.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CNYUpLxt.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-Hpc28YZY.js → swimlanes-42K2YHIH-Bo9GejWo.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Bo9GejWo.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Bo9GejWo.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-EB7zBRFR.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-qDieK7Re.js → timeline-definition-24CTP7MA-cvWXYEZ4.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-cvWXYEZ4.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-cvWXYEZ4.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-YOCAWhtT.js → triangle-alert-DzV0gQXz.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-BJpZlHao.js → vennDiagram-4TSXK5OY-CzleWcmk.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-BJpZlHao.js.br → vennDiagram-4TSXK5OY-CzleWcmk.js.br} +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-CzleWcmk.js.gz +0 -0
- package/dist/client/assets/{videoCompression-DI9vP0dm.js → videoCompression-DCnzjEfU.js} +1 -1
- package/dist/client/assets/videoCompression-DCnzjEfU.js.br +0 -0
- package/dist/client/assets/videoCompression-DCnzjEfU.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-CQJfj6Ns.js → wardleyDiagram-VM6X3IG4-Cir1iutR.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cir1iutR.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cir1iutR.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Cy-lkWBX.js → xychartDiagram-S5SC5T6Z-pNPcVJ1C.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-pNPcVJ1C.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-pNPcVJ1C.js.gz +0 -0
- package/dist/client/index.html +2 -2
- package/dist/client/index.html.br +0 -0
- package/dist/client/index.html.gz +0 -0
- package/dist/client/sw.js +1 -1
- package/dist/client/sw.js.br +0 -0
- package/dist/client/sw.js.gz +0 -0
- package/dist/server/agent/collaborationCli.js +56 -10
- package/dist/server/agent/collaborationGroupRoutes.js +10 -5
- package/dist/server/agent/collaborationLaunchProfiles.js +43 -0
- package/dist/server/agent/collaborationRoutes.js +6 -2
- package/dist/server/agent/collaborationService.js +8 -2
- package/dist/server/agent/collaborationStore.js +32 -1
- package/dist/server/agent/collaborationTaskCli.js +11 -3
- package/dist/server/agent/collaborationTaskService.js +16 -10
- package/dist/server/agent/collaborationTaskStore.js +70 -25
- package/dist/server/agent/collaborationTeam.js +3 -3
- package/dist/server/agent/integrationCli.js +243 -0
- package/dist/server/agent/integrationServer.js +331 -0
- package/dist/server/agent/integrationSessions.js +344 -0
- package/dist/server/agent/integrationStore.js +222 -0
- package/dist/server/cli.js +29 -0
- package/dist/server/entry.js +26 -7
- package/dist/server/routes/filesystem.js +120 -87
- package/dist/server/routes/terminal.js +196 -13
- package/dist/server/utils/localAccess.js +27 -7
- package/dist/server/utils/sessionDisplayName.js +53 -0
- package/docs/integrations.md +304 -0
- package/package.json +3 -2
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-DzuUkEI_.js +0 -179
- package/dist/client/assets/App-DzuUkEI_.js.br +0 -0
- package/dist/client/assets/App-DzuUkEI_.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-QodxiiCc.js +0 -7
- package/dist/client/assets/ArchitecturePanel-QodxiiCc.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-QodxiiCc.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-2lXfrgk4.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-2lXfrgk4.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-05-Py9N8.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-05-Py9N8.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-05-Py9N8.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-BJe9TUtZ.js +0 -2
- package/dist/client/assets/ComputerControlView-B5vigCoW.js +0 -5
- package/dist/client/assets/ComputerControlView-B5vigCoW.js.br +0 -0
- package/dist/client/assets/ComputerControlView-B5vigCoW.js.gz +0 -0
- package/dist/client/assets/DagPlayground-EFUb9S3u.js.br +0 -0
- package/dist/client/assets/DagPlayground-EFUb9S3u.js.gz +0 -0
- package/dist/client/assets/DiffLab-CXWjSE2q.js.br +0 -0
- package/dist/client/assets/DiffLab-CXWjSE2q.js.gz +0 -0
- package/dist/client/assets/DiffReview-ByMkBaD9.js.br +0 -0
- package/dist/client/assets/DiffReview-ByMkBaD9.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-DoWaKm94.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-DoWaKm94.js.gz +0 -0
- package/dist/client/assets/DiffViewer-B4hn9z0k.js.br +0 -0
- package/dist/client/assets/DiffViewer-B4hn9z0k.js.gz +0 -0
- package/dist/client/assets/ModelPreview-C-LJZ8so.js.br +0 -0
- package/dist/client/assets/ModelPreview-C-LJZ8so.js.gz +0 -0
- package/dist/client/assets/RightSidebar-EaMm4prW.js +0 -1678
- package/dist/client/assets/RightSidebar-EaMm4prW.js.br +0 -0
- package/dist/client/assets/RightSidebar-EaMm4prW.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BY-pfYKk.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-BY-pfYKk.js.gz +0 -0
- package/dist/client/assets/app-Ch0UDtJ-.js +0 -35
- package/dist/client/assets/app-Ch0UDtJ-.js.br +0 -0
- package/dist/client/assets/app-Ch0UDtJ-.js.gz +0 -0
- package/dist/client/assets/arc-DIENgCZn.js.br +0 -0
- package/dist/client/assets/arc-DIENgCZn.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C1h4e4az.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-C1h4e4az.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-B_yOr54K.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-B_yOr54K.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DRD4zxIo.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DRD4zxIo.js.gz +0 -0
- package/dist/client/assets/channel-MOXuUKM7.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-CQCL8mJG.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CQCL8mJG.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-ruHGCADe.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-ruHGCADe.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DMHj3NSq.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-DMHj3NSq.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Dh_ekvFW.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-Dh_ekvFW.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-CEXMdudw.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-CEXMdudw.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CsySWhYg.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CsySWhYg.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-9DKE5wV7.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-9DKE5wV7.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B_8fvr9c.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-B_8fvr9c.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BOdsFNFT.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-BOdsFNFT.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-6v_qvAvi.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-6v_qvAvi.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-c03oOGhD.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-c03oOGhD.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-MMIhRYp_.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-MMIhRYp_.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-B_l-uebl.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-B_l-uebl.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKoBC5gn.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DKoBC5gn.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Bs3wu8Az.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Bs3wu8Az.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-mYnHYbKl.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-mYnHYbKl.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CwCLWbib.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-CwCLWbib.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-mse8c-ni.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-mse8c-ni.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-BviL29ps.js.br +0 -0
- package/dist/client/assets/gestureArbiter-BviL29ps.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVUfLjAX.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVUfLjAX.js.gz +0 -0
- package/dist/client/assets/index-C6ftfFlG.css.br +0 -0
- package/dist/client/assets/index-C6ftfFlG.css.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-bH299bWL.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-bH299bWL.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BG0u_X1A.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BG0u_X1A.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-DRoc9N-i.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-DRoc9N-i.js.gz +0 -0
- package/dist/client/assets/layout-mh0t6JpE.js.br +0 -0
- package/dist/client/assets/layout-mh0t6JpE.js.gz +0 -0
- package/dist/client/assets/linear-B9KTRuUV.js.br +0 -0
- package/dist/client/assets/linear-B9KTRuUV.js.gz +0 -0
- package/dist/client/assets/mermaid.core-Bu2G9zhK.js.br +0 -0
- package/dist/client/assets/mermaid.core-Bu2G9zhK.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-Go6Bg5xm.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-Go6Bg5xm.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-C7-MUAHQ.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-C7-MUAHQ.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DKUIl_U6.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DKUIl_U6.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-aoDNlV7J.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-aoDNlV7J.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BKuuaF2s.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BwfCW2bp.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BwfCW2bp.js.gz +0 -0
- package/dist/client/assets/rdpSession--y6Q98uo.js +0 -1
- package/dist/client/assets/rdpSession--y6Q98uo.js.br +0 -0
- package/dist/client/assets/rdpSession--y6Q98uo.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-BeG1PAdI.js +0 -1
- package/dist/client/assets/referenceLongPress-BeG1PAdI.js.br +0 -0
- package/dist/client/assets/referenceLongPress-BeG1PAdI.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DbpU9Uig.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-DbpU9Uig.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bxa22XVM.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-Bxa22XVM.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C4GvzT9z.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C4GvzT9z.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B9zJJwQw.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B9zJJwQw.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-fqbw6LZa.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-fqbw6LZa.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BO23Pbrn.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Hpc28YZY.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Hpc28YZY.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BLS7y4Di.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-qDieK7Re.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-qDieK7Re.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-BJpZlHao.js.gz +0 -0
- package/dist/client/assets/videoCompression-DI9vP0dm.js.br +0 -0
- package/dist/client/assets/videoCompression-DI9vP0dm.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CQJfj6Ns.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-CQJfj6Ns.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Cy-lkWBX.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Cy-lkWBX.js.gz +0 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
4
|
+
import { atomicJson, integrationError } from './integrationStore.js';
|
|
5
|
+
/** Quote each configured argument as literal shell data, including paths. */
|
|
6
|
+
export function integrationLaunchCommand(record, restore) {
|
|
7
|
+
const launchArgs = record.profile.argv.map(arg => arg === '{cwd}' ? record.cwd : arg);
|
|
8
|
+
const args = restore ? record.profile.resumeArgv.flatMap(arg => arg === '{launchArgs}' ? launchArgs
|
|
9
|
+
: [arg === '{sessionId}' ? record.agent_native_session_id : arg === '{cwd}' ? record.cwd : arg]) : launchArgs;
|
|
10
|
+
const quote = (value) => "'" + value.replaceAll("'", "'\\''") + "'";
|
|
11
|
+
return `cd -- ${quote(record.cwd)} && ${[record.profile.executable, ...args].map(quote).join(' ')}`;
|
|
12
|
+
}
|
|
13
|
+
function startupInput(record, now) {
|
|
14
|
+
const condition = record.startup_condition ?? record.profile.startupInput;
|
|
15
|
+
return condition ? { state: 'pending', deadline: now + (condition.timeoutMs ?? 120000), matched_since: null, observed_at: null } : undefined;
|
|
16
|
+
}
|
|
17
|
+
export function publicIntegrationSession(record) {
|
|
18
|
+
const { profile: _, deadline: _deadline, launch_submitted: _submitted, startup_condition: _condition, ...publicRecord } = record;
|
|
19
|
+
return publicRecord;
|
|
20
|
+
}
|
|
21
|
+
export function permittedCwd(profile, cwd) {
|
|
22
|
+
if (typeof cwd !== 'string' || !path.isAbsolute(cwd) || /[\x00-\x1f\x7f]/.test(cwd))
|
|
23
|
+
integrationError('SESSION_CWD_DENIED', 'cwd must be absolute', 403);
|
|
24
|
+
let real;
|
|
25
|
+
try {
|
|
26
|
+
real = fs.realpathSync(cwd);
|
|
27
|
+
if (/[\x00-\x1f\x7f]/.test(real) || !fs.statSync(real).isDirectory())
|
|
28
|
+
throw new Error();
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return integrationError('SESSION_CWD_MISSING', 'Task working directory must exist');
|
|
32
|
+
}
|
|
33
|
+
if (!profile.cwdRoots.some(root => { try {
|
|
34
|
+
const relative = path.relative(fs.realpathSync(root), real);
|
|
35
|
+
return !relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return false;
|
|
39
|
+
} }))
|
|
40
|
+
integrationError('SESSION_CWD_DENIED', 'Working directory is outside the configured roots', 403);
|
|
41
|
+
return real;
|
|
42
|
+
}
|
|
43
|
+
/** Creation intent and exact launch configuration are durable before any input
|
|
44
|
+
* enters a PTY. An ambiguous crash never starts another Agent automatically. */
|
|
45
|
+
export class IntegrationSessions {
|
|
46
|
+
file;
|
|
47
|
+
adapter;
|
|
48
|
+
bindingTimeout;
|
|
49
|
+
doc = { version: 1, sessions: [], requests: {} };
|
|
50
|
+
pending = new Map();
|
|
51
|
+
listeners = new Set();
|
|
52
|
+
restoring = new Set();
|
|
53
|
+
timers = new Set();
|
|
54
|
+
stopObservation;
|
|
55
|
+
stopDeliveryGuard;
|
|
56
|
+
observing = false;
|
|
57
|
+
observeAgain = false;
|
|
58
|
+
constructor(file, adapter, bindingTimeout = 120000) {
|
|
59
|
+
this.file = file;
|
|
60
|
+
this.adapter = adapter;
|
|
61
|
+
this.bindingTimeout = bindingTimeout;
|
|
62
|
+
try {
|
|
63
|
+
const doc = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
64
|
+
if (doc.version !== 1 || !Array.isArray(doc.sessions) || !doc.requests)
|
|
65
|
+
throw new Error('Invalid integration session store');
|
|
66
|
+
this.doc = doc;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.code !== 'ENOENT')
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
for (const record of this.doc.sessions)
|
|
73
|
+
if (['starting', 'restoring', 'binding_pending'].includes(record.state) || record.startup_input?.state === 'pending')
|
|
74
|
+
this.scheduleRefresh(record);
|
|
75
|
+
// A pre-restart partial match cannot prove continuity of the live screen.
|
|
76
|
+
for (const record of this.doc.sessions)
|
|
77
|
+
if (record.startup_input && record.startup_input.state !== 'observed')
|
|
78
|
+
record.startup_input.matched_since = null;
|
|
79
|
+
this.stopDeliveryGuard = adapter.registerDeliveryGuard?.(id => this.deliveryReadiness(id));
|
|
80
|
+
this.stopObservation = adapter.subscribe(() => { void this.refreshAll().catch(() => { }); });
|
|
81
|
+
}
|
|
82
|
+
scheduleRefresh(record) {
|
|
83
|
+
for (const deadline of new Set([record.deadline, ...(record.startup_input ? [record.startup_input.deadline] : [])])) {
|
|
84
|
+
const timer = setTimeout(() => { this.timers.delete(timer); void this.refresh(record.session_id).catch(() => { }); }, Math.max(1, deadline - Date.now() + 5));
|
|
85
|
+
timer.unref();
|
|
86
|
+
this.timers.add(timer);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }
|
|
90
|
+
snapshot() { return structuredClone(this.doc.sessions); }
|
|
91
|
+
save() { atomicJson(this.file, this.doc); for (const listener of this.listeners) {
|
|
92
|
+
try {
|
|
93
|
+
listener();
|
|
94
|
+
}
|
|
95
|
+
catch { /* Durable session state is reconciled on restart. */ }
|
|
96
|
+
} }
|
|
97
|
+
saveRecord(record) {
|
|
98
|
+
const before = this.doc.sessions;
|
|
99
|
+
this.doc.sessions = [...before.filter(s => s.session_id !== record.session_id), structuredClone(record)];
|
|
100
|
+
try {
|
|
101
|
+
this.save();
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.doc.sessions = before;
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
lookup(principal, id) {
|
|
109
|
+
const record = this.doc.sessions.find(s => s.session_id === id && s.group_id === principal.groupId && s.principal_id === principal.id);
|
|
110
|
+
if (!record)
|
|
111
|
+
integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Session is not owned by this integration', 404);
|
|
112
|
+
return structuredClone(record);
|
|
113
|
+
}
|
|
114
|
+
request(principal, key, input) {
|
|
115
|
+
if (!key || typeof key !== 'string' || key.length > 256)
|
|
116
|
+
integrationError('INVALID_IDEMPOTENCY_KEY', 'An idempotency key is required');
|
|
117
|
+
const scoped = `${principal.id}:${key}`, hash = createHash('sha256').update(JSON.stringify(input)).digest('hex');
|
|
118
|
+
const previous = this.doc.requests[scoped];
|
|
119
|
+
if (previous && previous.hash !== hash)
|
|
120
|
+
integrationError('IDEMPOTENCY_CONFLICT', 'Key was already used for different session parameters', 409);
|
|
121
|
+
return { scoped, hash, previous };
|
|
122
|
+
}
|
|
123
|
+
async create(principal, input) {
|
|
124
|
+
const request = this.request(principal, input.idempotencyKey, ['create', input.profile, input.cwd]);
|
|
125
|
+
if (request.previous)
|
|
126
|
+
return this.pending.get(request.previous.sessionId) ?? this.get(principal, request.previous.sessionId);
|
|
127
|
+
const profile = principal.launchProfiles.find(p => p.id === input.profile);
|
|
128
|
+
if (!profile)
|
|
129
|
+
integrationError('LAUNCH_PROFILE_DENIED', 'Profile is not allowed by this integration', 403);
|
|
130
|
+
const cwd = permittedCwd(profile, input.cwd);
|
|
131
|
+
try {
|
|
132
|
+
fs.accessSync(profile.executable, fs.constants.X_OK);
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
return integrationError('LAUNCHER_UNAVAILABLE', 'Configured executable is not available');
|
|
136
|
+
}
|
|
137
|
+
if (this.doc.sessions.length >= 2000)
|
|
138
|
+
integrationError('INTEGRATION_SESSION_LIMIT', 'Integration session storage is full', 409);
|
|
139
|
+
const now = Date.now(), record = { operation_id: randomUUID(), session_id: randomBytes(6).toString('hex'),
|
|
140
|
+
group_id: principal.groupId, principal_id: principal.id, agent_slug: profile.agentSlug, agent_native_session_id: null,
|
|
141
|
+
state: 'starting', error_code: null, cwd, launch_profile: profile.id, profile: structuredClone(profile),
|
|
142
|
+
created_at: now, updated_at: now, deadline: now + this.bindingTimeout, launch_submitted: false };
|
|
143
|
+
record.startup_input = startupInput(record, now);
|
|
144
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: record.session_id };
|
|
145
|
+
try {
|
|
146
|
+
this.saveRecord(record);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
delete this.doc.requests[request.scoped];
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
return this.launch(record, false);
|
|
153
|
+
}
|
|
154
|
+
launch(record, restoring) {
|
|
155
|
+
const promise = (async () => {
|
|
156
|
+
try {
|
|
157
|
+
// Store intent before launching. A failed persistence cannot execute.
|
|
158
|
+
record.launch_submitted = true;
|
|
159
|
+
this.saveRecord(record);
|
|
160
|
+
const prepared = (binding) => { record.terminal_binding = binding; this.saveRecord(record); };
|
|
161
|
+
if (restoring)
|
|
162
|
+
await this.adapter.restore(record, prepared);
|
|
163
|
+
else
|
|
164
|
+
await this.adapter.create(record, prepared);
|
|
165
|
+
await this.refresh(record.session_id);
|
|
166
|
+
this.scheduleRefresh(record);
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
const current = this.doc.sessions.find(s => s.session_id === record.session_id);
|
|
170
|
+
const code = error.code;
|
|
171
|
+
this.saveRecord({ ...current, state: 'failed', error_code: typeof code === 'string' ? code : 'SESSION_LAUNCH_FAILED', updated_at: Date.now() });
|
|
172
|
+
}
|
|
173
|
+
return structuredClone(this.doc.sessions.find(s => s.session_id === record.session_id));
|
|
174
|
+
})().finally(() => this.pending.delete(record.session_id));
|
|
175
|
+
this.pending.set(record.session_id, promise);
|
|
176
|
+
return promise;
|
|
177
|
+
}
|
|
178
|
+
async get(principal, id) { this.lookup(principal, id); await this.refresh(id); return this.lookup(principal, id); }
|
|
179
|
+
async restore(principal, id, idempotencyKey) {
|
|
180
|
+
const request = this.request(principal, idempotencyKey, ['restore', id]);
|
|
181
|
+
if (request.previous)
|
|
182
|
+
return this.pending.get(id) ?? this.get(principal, id);
|
|
183
|
+
if (this.pending.has(id) || this.restoring.has(id))
|
|
184
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Another session operation is in progress', 409);
|
|
185
|
+
this.restoring.add(id);
|
|
186
|
+
try {
|
|
187
|
+
const record = await this.get(principal, id);
|
|
188
|
+
if (!record.agent_native_session_id)
|
|
189
|
+
integrationError('NATIVE_SESSION_ID_MISSING', 'An exact native session binding is required', 409);
|
|
190
|
+
// Recheck the current grant and actual directories; restore uses the
|
|
191
|
+
// original profile snapshot, never changed model or wrapper defaults.
|
|
192
|
+
if (!principal.launchProfiles.some(p => p.id === record.launch_profile))
|
|
193
|
+
integrationError('LAUNCH_PROFILE_DENIED', 'Original launch profile is no longer authorized', 403);
|
|
194
|
+
permittedCwd(record.profile, record.cwd);
|
|
195
|
+
const observed = await this.adapter.inspect(record);
|
|
196
|
+
if (observed.running) {
|
|
197
|
+
if (observed.agentSlug !== record.agent_slug || observed.nativeId !== record.agent_native_session_id)
|
|
198
|
+
integrationError('SESSION_IDENTITY_MISMATCH', 'Existing process does not prove the exact native binding', 409);
|
|
199
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id };
|
|
200
|
+
try {
|
|
201
|
+
this.save();
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
delete this.doc.requests[request.scoped];
|
|
205
|
+
throw error;
|
|
206
|
+
}
|
|
207
|
+
return record;
|
|
208
|
+
}
|
|
209
|
+
if (['starting', 'restoring', 'binding_pending'].includes(record.state))
|
|
210
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Prior launch outcome is not yet resolved; inspect the original session', 409);
|
|
211
|
+
if (observed.exists && !observed.shell)
|
|
212
|
+
integrationError('SESSION_TARGET_NOT_SHELL', 'Original pane is not a verified shell', 409);
|
|
213
|
+
record.state = 'restoring';
|
|
214
|
+
record.error_code = null;
|
|
215
|
+
record.operation_id = randomUUID();
|
|
216
|
+
record.deadline = Date.now() + this.bindingTimeout;
|
|
217
|
+
record.updated_at = Date.now();
|
|
218
|
+
if (!record.profile.startupInput && !record.startup_condition)
|
|
219
|
+
record.startup_condition = structuredClone(principal.launchProfiles.find(profile => profile.id === record.launch_profile)?.startupInput);
|
|
220
|
+
record.startup_input = startupInput(record, Date.now());
|
|
221
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id };
|
|
222
|
+
try {
|
|
223
|
+
this.saveRecord(record);
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
delete this.doc.requests[request.scoped];
|
|
227
|
+
throw error;
|
|
228
|
+
}
|
|
229
|
+
return await this.launch(record, true);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
this.restoring.delete(id);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async refresh(id) {
|
|
236
|
+
const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
|
|
237
|
+
if (!record)
|
|
238
|
+
return;
|
|
239
|
+
let observed;
|
|
240
|
+
try {
|
|
241
|
+
observed = await this.adapter.inspect(record);
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
const current = this.doc.sessions.find(s => s.session_id === id);
|
|
245
|
+
if (JSON.stringify(current) !== JSON.stringify(record))
|
|
246
|
+
return;
|
|
247
|
+
const code = error.code;
|
|
248
|
+
if (record.state !== 'failed' || record.error_code !== code)
|
|
249
|
+
this.saveRecord({ ...record, state: 'failed', error_code: typeof code === 'string' ? code : 'SESSION_OBSERVATION_FAILED', updated_at: Date.now() });
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const next = { ...record };
|
|
253
|
+
if (observed.nativeId && (typeof observed.nativeId !== 'string' || observed.nativeId.length > 256 || /[\x00-\x1f\x7f]/.test(observed.nativeId))) {
|
|
254
|
+
next.state = 'failed';
|
|
255
|
+
next.error_code = 'NATIVE_SESSION_ID_INVALID';
|
|
256
|
+
}
|
|
257
|
+
else if (observed.running && observed.agentSlug !== record.agent_slug) {
|
|
258
|
+
next.state = 'failed';
|
|
259
|
+
next.error_code = 'SESSION_IDENTITY_MISMATCH';
|
|
260
|
+
}
|
|
261
|
+
else if (observed.running && observed.nativeId) {
|
|
262
|
+
if (record.agent_native_session_id && record.agent_native_session_id !== observed.nativeId) {
|
|
263
|
+
next.state = 'failed';
|
|
264
|
+
next.error_code = 'NATIVE_SESSION_ID_MISMATCH';
|
|
265
|
+
}
|
|
266
|
+
else {
|
|
267
|
+
next.agent_native_session_id = observed.nativeId;
|
|
268
|
+
if (next.terminal_binding)
|
|
269
|
+
next.terminal_binding = { ...next.terminal_binding, nativeSessionId: observed.nativeId };
|
|
270
|
+
next.state = 'ready';
|
|
271
|
+
next.error_code = null;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
else if (observed.running) {
|
|
275
|
+
next.state = Date.now() > record.deadline ? 'failed' : 'binding_pending';
|
|
276
|
+
next.error_code = next.state === 'failed' ? 'SESSION_BINDING_TIMEOUT' : null;
|
|
277
|
+
}
|
|
278
|
+
else if (record.state === 'ready') {
|
|
279
|
+
next.state = 'failed';
|
|
280
|
+
next.error_code = 'AGENT_NOT_RUNNING';
|
|
281
|
+
}
|
|
282
|
+
else if (Date.now() > record.deadline && record.state !== 'failed') {
|
|
283
|
+
next.state = 'failed';
|
|
284
|
+
next.error_code = 'SESSION_BINDING_TIMEOUT';
|
|
285
|
+
}
|
|
286
|
+
if (next.startup_input && next.startup_input.state !== 'observed') {
|
|
287
|
+
const condition = next.startup_condition ?? next.profile.startupInput;
|
|
288
|
+
let matched = false;
|
|
289
|
+
if (observed.running && observed.agentSlug === record.agent_slug && (!next.error_code || next.error_code === 'SESSION_BINDING_TIMEOUT') && this.adapter.capture) {
|
|
290
|
+
try {
|
|
291
|
+
const fold = (text) => text.replace(/[^\S\r\n]+/g, ' ').trim();
|
|
292
|
+
const viewport = fold(await this.adapter.capture(next));
|
|
293
|
+
matched = condition.allOf.every(term => viewport.includes(fold(term))) && !(condition.noneOf ?? []).some(term => viewport.includes(fold(term)));
|
|
294
|
+
}
|
|
295
|
+
catch { /* Missing capture never opens the delivery guard. */ }
|
|
296
|
+
}
|
|
297
|
+
const input = { ...next.startup_input };
|
|
298
|
+
input.matched_since = matched ? input.matched_since ?? Date.now() : null;
|
|
299
|
+
if (matched && Date.now() - input.matched_since >= (condition.stableMs ?? 1000)) {
|
|
300
|
+
input.state = 'observed';
|
|
301
|
+
input.observed_at = Date.now();
|
|
302
|
+
}
|
|
303
|
+
else
|
|
304
|
+
input.state = Date.now() >= input.deadline ? 'timed_out' : 'pending';
|
|
305
|
+
next.startup_input = input;
|
|
306
|
+
}
|
|
307
|
+
if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
|
|
308
|
+
return;
|
|
309
|
+
if (JSON.stringify(next) !== JSON.stringify(record)) {
|
|
310
|
+
next.updated_at = Date.now();
|
|
311
|
+
this.saveRecord(next);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
/** Only gates configured launches. Native UUID and Agent turn state are not input prerequisites. */
|
|
315
|
+
async deliveryReadiness(id) {
|
|
316
|
+
if (!this.doc.sessions.find(record => record.session_id === id)?.startup_input)
|
|
317
|
+
return null;
|
|
318
|
+
await this.refresh(id);
|
|
319
|
+
const record = this.doc.sessions.find(record => record.session_id === id);
|
|
320
|
+
if (record.error_code && record.error_code !== 'SESSION_BINDING_TIMEOUT')
|
|
321
|
+
return { allowed: false, reason: record.error_code };
|
|
322
|
+
return record.startup_input.state === 'observed' ? { allowed: true, reason: null }
|
|
323
|
+
: { allowed: false, reason: record.startup_input.state === 'timed_out' ? 'SESSION_STARTUP_INPUT_TIMEOUT' : 'SESSION_STARTUP_INPUT_PENDING' };
|
|
324
|
+
}
|
|
325
|
+
async refreshAll() {
|
|
326
|
+
if (this.observing) {
|
|
327
|
+
this.observeAgain = true;
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
this.observing = true;
|
|
331
|
+
try {
|
|
332
|
+
do {
|
|
333
|
+
this.observeAgain = false;
|
|
334
|
+
for (const record of this.snapshot())
|
|
335
|
+
await this.refresh(record.session_id);
|
|
336
|
+
} while (this.observeAgain);
|
|
337
|
+
}
|
|
338
|
+
finally {
|
|
339
|
+
this.observing = false;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
close() { this.stopObservation(); this.stopDeliveryGuard?.(); for (const timer of this.timers)
|
|
343
|
+
clearTimeout(timer); }
|
|
344
|
+
}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
4
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
5
|
+
export const INTEGRATION_PROTOCOL = 1;
|
|
6
|
+
export const INTEGRATION_OPERATIONS = ['task.read', 'task.create', 'task.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore'];
|
|
7
|
+
export function integrationError(code, message, status = 400) { throw new CollaborationError(code, message, status); }
|
|
8
|
+
export function atomicJson(file, value) {
|
|
9
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
10
|
+
const temporary = `${file}.${process.pid}.tmp`;
|
|
11
|
+
const descriptor = fs.openSync(temporary, 'w', 0o600);
|
|
12
|
+
try {
|
|
13
|
+
fs.fchmodSync(descriptor, 0o600);
|
|
14
|
+
fs.writeFileSync(descriptor, JSON.stringify(value));
|
|
15
|
+
fs.fsyncSync(descriptor);
|
|
16
|
+
}
|
|
17
|
+
finally {
|
|
18
|
+
fs.closeSync(descriptor);
|
|
19
|
+
}
|
|
20
|
+
fs.renameSync(temporary, file);
|
|
21
|
+
const directory = fs.openSync(path.dirname(file), 'r');
|
|
22
|
+
try {
|
|
23
|
+
fs.fsyncSync(directory);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
fs.closeSync(directory);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const hash = (value) => createHash('sha256').update(value).digest('hex');
|
|
30
|
+
function identifier(value, label) {
|
|
31
|
+
if (typeof value !== 'string' || !/^[a-zA-Z0-9._-]{1,80}$/.test(value))
|
|
32
|
+
integrationError('INVALID_INTEGRATION_POLICY', `Invalid ${label}`);
|
|
33
|
+
}
|
|
34
|
+
export function validatePolicy(value) {
|
|
35
|
+
identifier(value?.id, 'principal id');
|
|
36
|
+
if (!value.groupId || typeof value.groupId !== 'string' || value.groupId.length > 160)
|
|
37
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'A single groupId is required');
|
|
38
|
+
if (!Array.isArray(value.permissions) || value.permissions.some(p => !INTEGRATION_OPERATIONS.includes(p)))
|
|
39
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Unknown permission');
|
|
40
|
+
if (!Array.isArray(value.launchProfiles) || value.launchProfiles.length > 16)
|
|
41
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'launchProfiles must be an array');
|
|
42
|
+
const ids = new Set();
|
|
43
|
+
for (const profile of value.launchProfiles) {
|
|
44
|
+
identifier(profile?.id, 'profile id');
|
|
45
|
+
identifier(profile.agentSlug, 'agent slug');
|
|
46
|
+
if (ids.has(profile.id))
|
|
47
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Duplicate launch profile');
|
|
48
|
+
ids.add(profile.id);
|
|
49
|
+
if (typeof profile.executable !== 'string' || !path.isAbsolute(profile.executable) || /[\x00-\x1f\x7f]/.test(profile.executable))
|
|
50
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Launcher must be an absolute path');
|
|
51
|
+
if (!Array.isArray(profile.argv) || !Array.isArray(profile.resumeArgv) || [...profile.argv, ...profile.resumeArgv].some(a => typeof a !== 'string' || /[\x00-\x1f\x7f]/.test(a) || a.length > 8192) || profile.argv.length > 128 || profile.resumeArgv.length > 128)
|
|
52
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Invalid structured argv');
|
|
53
|
+
if (profile.resumeArgv.filter(a => a === '{sessionId}').length !== 1 || profile.resumeArgv.filter(a => a === '{launchArgs}').length !== 1 || profile.resumeArgv.some(a => a.includes('{sessionId}') && a !== '{sessionId}') || profile.resumeArgv.includes('--last'))
|
|
54
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'resumeArgv requires one {sessionId} and one {launchArgs}; --last is forbidden');
|
|
55
|
+
if (!Array.isArray(profile.cwdRoots) || !profile.cwdRoots.length || profile.cwdRoots.some(root => typeof root !== 'string' || !path.isAbsolute(root) || /[\x00-\x1f\x7f]/.test(root)))
|
|
56
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'cwdRoots must be absolute paths');
|
|
57
|
+
if (profile.startupInput !== undefined) {
|
|
58
|
+
const condition = profile.startupInput;
|
|
59
|
+
const validTerms = (terms) => Array.isArray(terms) && terms.length <= 16
|
|
60
|
+
&& terms.every(term => typeof term === 'string' && !!term.trim() && term.length <= 256 && !/[\x00-\x1f\x7f]/.test(term));
|
|
61
|
+
if (!condition || !validTerms(condition.allOf) || !condition.allOf.length || condition.noneOf !== undefined && !validTerms(condition.noneOf)
|
|
62
|
+
|| condition.stableMs !== undefined && (!Number.isInteger(condition.stableMs) || condition.stableMs < 500 || condition.stableMs > 5000)
|
|
63
|
+
|| condition.timeoutMs !== undefined && (!Number.isInteger(condition.timeoutMs) || condition.timeoutMs < 1000 || condition.timeoutMs > 120000)) {
|
|
64
|
+
integrationError('INVALID_INTEGRATION_POLICY', 'Invalid startupInput literal conditions or timing bounds');
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// Policies contain launcher configuration, never authentication material.
|
|
69
|
+
return structuredClone({ id: value.id, groupId: value.groupId, permissions: value.permissions, launchProfiles: value.launchProfiles });
|
|
70
|
+
}
|
|
71
|
+
export class IntegrationStore {
|
|
72
|
+
file;
|
|
73
|
+
retention;
|
|
74
|
+
doc = { version: 1, principals: [], events: [], sequences: {}, prunedThrough: {}, sources: {}, consumers: {} };
|
|
75
|
+
listeners = new Set();
|
|
76
|
+
constructor(file, retention = 10000) {
|
|
77
|
+
this.file = file;
|
|
78
|
+
this.retention = retention;
|
|
79
|
+
try {
|
|
80
|
+
const document = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
81
|
+
if (document.version !== 1 || !Array.isArray(document.events) || !Array.isArray(document.principals) || !document.sources || !document.consumers || !document.sequences || !document.prunedThrough)
|
|
82
|
+
throw new Error('Invalid integration journal');
|
|
83
|
+
this.doc = document;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (error.code !== 'ENOENT')
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
write(next) {
|
|
91
|
+
if (Buffer.byteLength(JSON.stringify(next)) > 64 * 1024 * 1024)
|
|
92
|
+
integrationError('EVENT_STORAGE_FULL', 'Integration journal is full; no event was acknowledged', 503);
|
|
93
|
+
atomicJson(this.file, next);
|
|
94
|
+
this.doc = next;
|
|
95
|
+
for (const listener of this.listeners) {
|
|
96
|
+
try {
|
|
97
|
+
listener();
|
|
98
|
+
}
|
|
99
|
+
catch { /* Subscription reports its own failure. */ }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }
|
|
103
|
+
policies() { return this.doc.principals.map(({ tokenHash: _, ...principal }) => structuredClone(principal)); }
|
|
104
|
+
provision(policy) {
|
|
105
|
+
const checked = validatePolicy(policy);
|
|
106
|
+
if (this.doc.principals.some(p => p.id === checked.id))
|
|
107
|
+
integrationError('INTEGRATION_EXISTS', 'Use a new id; credentials cannot be silently replaced', 409);
|
|
108
|
+
const token = randomBytes(32).toString('base64url');
|
|
109
|
+
const next = structuredClone(this.doc);
|
|
110
|
+
next.principals.push({ ...checked, tokenHash: hash(token), revoked: false });
|
|
111
|
+
this.write(next);
|
|
112
|
+
return { principal: checked, token };
|
|
113
|
+
}
|
|
114
|
+
update(policy) {
|
|
115
|
+
const checked = validatePolicy(policy), next = structuredClone(this.doc);
|
|
116
|
+
const index = next.principals.findIndex(principal => principal.id === checked.id);
|
|
117
|
+
if (index < 0)
|
|
118
|
+
integrationError('INTEGRATION_NOT_FOUND', 'Integration does not exist', 404);
|
|
119
|
+
const existing = next.principals[index];
|
|
120
|
+
if (existing.revoked)
|
|
121
|
+
integrationError('INTEGRATION_REVOKED', 'Revoked identities cannot be reactivated', 403);
|
|
122
|
+
if (existing.groupId !== checked.groupId)
|
|
123
|
+
integrationError('INTEGRATION_GROUP_IMMUTABLE', 'An existing identity cannot change its group', 409);
|
|
124
|
+
next.principals[index] = { ...checked, tokenHash: existing.tokenHash, revoked: false };
|
|
125
|
+
this.write(next);
|
|
126
|
+
}
|
|
127
|
+
revoke(id) {
|
|
128
|
+
const next = structuredClone(this.doc), principal = next.principals.find(p => p.id === id);
|
|
129
|
+
if (!principal)
|
|
130
|
+
integrationError('INTEGRATION_NOT_FOUND', 'Integration does not exist', 404);
|
|
131
|
+
principal.revoked = true;
|
|
132
|
+
this.write(next);
|
|
133
|
+
}
|
|
134
|
+
authenticate(id, token) {
|
|
135
|
+
const principal = this.doc.principals.find(p => p.id === id);
|
|
136
|
+
const given = Buffer.from(hash(token)), expected = Buffer.from(principal?.tokenHash ?? hash('invalid'));
|
|
137
|
+
if (!principal || !timingSafeEqual(given, expected))
|
|
138
|
+
integrationError('INTEGRATION_UNAUTHORIZED', 'Invalid integration credential', 401);
|
|
139
|
+
if (principal.revoked)
|
|
140
|
+
integrationError('INTEGRATION_REVOKED', 'Integration authorization was revoked', 403);
|
|
141
|
+
return structuredClone(principal);
|
|
142
|
+
}
|
|
143
|
+
authorize(principal, permission, groupId = principal.groupId) {
|
|
144
|
+
const active = this.doc.principals.find(p => p.id === principal.id);
|
|
145
|
+
if (!active || active.revoked)
|
|
146
|
+
integrationError('INTEGRATION_REVOKED', 'Integration authorization was revoked', 403);
|
|
147
|
+
if (groupId !== active.groupId || !active.permissions.includes(permission))
|
|
148
|
+
integrationError('INTEGRATION_PERMISSION_DENIED', 'Operation is outside the integration grant', 403);
|
|
149
|
+
}
|
|
150
|
+
append(updates) {
|
|
151
|
+
const next = structuredClone(this.doc);
|
|
152
|
+
let changed = false;
|
|
153
|
+
for (const update of updates) {
|
|
154
|
+
if (next.sources[update.sourceKey] === update.sourceVersion)
|
|
155
|
+
continue;
|
|
156
|
+
const group = update.event.group_id, sequence = (next.sequences[group] ?? 0) + 1;
|
|
157
|
+
next.events.push({ ...update.event, event_id: update.eventId ?? randomUUID(), sequence, cursor: this.cursor(group, sequence) });
|
|
158
|
+
next.sequences[group] = sequence;
|
|
159
|
+
next.sources[update.sourceKey] = update.sourceVersion;
|
|
160
|
+
changed = true;
|
|
161
|
+
}
|
|
162
|
+
if (!changed)
|
|
163
|
+
return;
|
|
164
|
+
const counts = new Map();
|
|
165
|
+
next.events = next.events.reverse().filter(event => {
|
|
166
|
+
const count = (counts.get(event.group_id) ?? 0) + 1;
|
|
167
|
+
counts.set(event.group_id, count);
|
|
168
|
+
if (count <= this.retention)
|
|
169
|
+
return true;
|
|
170
|
+
next.prunedThrough[event.group_id] = Math.max(next.prunedThrough[event.group_id] ?? 0, event.sequence);
|
|
171
|
+
return false;
|
|
172
|
+
}).reverse();
|
|
173
|
+
this.write(next);
|
|
174
|
+
}
|
|
175
|
+
cursor(group, sequence) { return Buffer.from(JSON.stringify({ v: 1, group, sequence })).toString('base64url'); }
|
|
176
|
+
decode(group, cursor) {
|
|
177
|
+
let value;
|
|
178
|
+
try {
|
|
179
|
+
value = JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8'));
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
return integrationError('INVALID_EVENT_CURSOR', 'Invalid cursor');
|
|
183
|
+
}
|
|
184
|
+
if (value.v !== 1 || value.group !== group || !Number.isSafeInteger(value.sequence) || value.sequence < 0 || value.sequence > (this.doc.sequences[group] ?? 0))
|
|
185
|
+
integrationError('INVALID_EVENT_CURSOR', 'Cursor does not belong to this group');
|
|
186
|
+
return value.sequence;
|
|
187
|
+
}
|
|
188
|
+
key(principal, consumer) { identifier(consumer, 'consumer'); return `${principal.id}:${principal.groupId}:${consumer}`; }
|
|
189
|
+
page(principal, consumer, cursor) {
|
|
190
|
+
this.authorize(principal, 'events.read');
|
|
191
|
+
const key = this.key(principal, consumer), group = principal.groupId;
|
|
192
|
+
const floor = this.doc.prunedThrough[group] ?? 0;
|
|
193
|
+
// A new consumer explicitly starts at the earliest retained fact. Existing
|
|
194
|
+
// consumers never silently skip a retention gap.
|
|
195
|
+
if (this.doc.consumers[key] === undefined) {
|
|
196
|
+
const next = structuredClone(this.doc);
|
|
197
|
+
next.consumers[key] = floor;
|
|
198
|
+
this.write(next);
|
|
199
|
+
}
|
|
200
|
+
const after = cursor ? this.decode(group, cursor) : this.doc.consumers[key];
|
|
201
|
+
if (after < floor)
|
|
202
|
+
integrationError('EVENT_RETENTION_GAP', 'Confirmed cursor is older than retained history; reconcile task/session snapshots and use a new consumer', 409);
|
|
203
|
+
return { events: structuredClone(this.doc.events.filter(e => e.group_id === group && e.sequence > after).slice(0, 100)), cursor: this.cursor(group, after) };
|
|
204
|
+
}
|
|
205
|
+
ack(principal, consumer, cursor) {
|
|
206
|
+
this.authorize(principal, 'events.read');
|
|
207
|
+
const key = this.key(principal, consumer), sequence = this.decode(principal.groupId, cursor), confirmed = this.doc.consumers[key];
|
|
208
|
+
if (confirmed === undefined)
|
|
209
|
+
integrationError('EVENT_CONSUMER_NOT_STARTED', 'Subscribe before acknowledging');
|
|
210
|
+
if (sequence < confirmed)
|
|
211
|
+
integrationError('EVENT_ACK_REWIND', 'Acknowledgement cannot rewind');
|
|
212
|
+
if (confirmed < (this.doc.prunedThrough[principal.groupId] ?? 0))
|
|
213
|
+
integrationError('EVENT_RETENTION_GAP', 'Consumer has a retention gap', 409);
|
|
214
|
+
// Cursor is a cumulative declaration that every preceding event is safely
|
|
215
|
+
// in the bridge inbox. The subscription server separately checks it was sent.
|
|
216
|
+
const next = structuredClone(this.doc);
|
|
217
|
+
next.consumers[key] = sequence;
|
|
218
|
+
this.write(next);
|
|
219
|
+
return { cursor, ack_semantics: 'durable_received' };
|
|
220
|
+
}
|
|
221
|
+
cursorSequence(group, cursor) { return this.decode(group, cursor); }
|
|
222
|
+
}
|
package/dist/server/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ if (process.argv.slice(2).some(arg => arg === '--federation-pairing' || arg ===
|
|
|
15
15
|
const { runFederationCli } = await import('./federation/cli.js');
|
|
16
16
|
process.exit(await runFederationCli(process.argv.slice(2)));
|
|
17
17
|
}
|
|
18
|
+
import { INTEGRATION_HELP, runIntegrationAdmin, revokeIntegration, runIntegrationCollab } from './agent/integrationCli.js';
|
|
18
19
|
import { parseNotifyCommand, NOTIFY_HELP, NOTIFY_PROMPT } from './agent/notifyCli.js';
|
|
19
20
|
import { detectLocalSessionContext, resolveLocalCollaborationContext } from './agent/localSessionContext.js';
|
|
20
21
|
import { parseAutomationCommand, executeAutomationCommand, AUTOMATION_HELP } from './agent/automationCli.js';
|
|
@@ -207,6 +208,7 @@ Short commands:
|
|
|
207
208
|
n [message] Progress reminder; run td n for help and Agent instructions
|
|
208
209
|
notify [message] Same as n (see td n --prompt)
|
|
209
210
|
automation Manage scheduled tasks (see td automation --help)
|
|
211
|
+
integration Manage local scoped integrations (see integration help)
|
|
210
212
|
collab status Show this Session's collaboration groups and peers
|
|
211
213
|
collab inbox Read messages without acknowledging (see collab --help)
|
|
212
214
|
collab send <session-id> <message>
|
|
@@ -658,6 +660,7 @@ function parseArgs(argv) {
|
|
|
658
660
|
let pluginHooks;
|
|
659
661
|
let pluginRemove;
|
|
660
662
|
let agentEvent;
|
|
663
|
+
let integration;
|
|
661
664
|
let collab;
|
|
662
665
|
let automation;
|
|
663
666
|
let notify;
|
|
@@ -797,6 +800,10 @@ function parseArgs(argv) {
|
|
|
797
800
|
}
|
|
798
801
|
argv = [];
|
|
799
802
|
}
|
|
803
|
+
else if (command === 'integration') {
|
|
804
|
+
integration = argv.slice(1);
|
|
805
|
+
argv = [];
|
|
806
|
+
}
|
|
800
807
|
else if (command === 'collab') {
|
|
801
808
|
try {
|
|
802
809
|
collab = parseCollaborationCommand(argv.slice(1));
|
|
@@ -1183,6 +1190,7 @@ function parseArgs(argv) {
|
|
|
1183
1190
|
pluginHooks,
|
|
1184
1191
|
pluginRemove,
|
|
1185
1192
|
agentEvent,
|
|
1193
|
+
integration,
|
|
1186
1194
|
collab,
|
|
1187
1195
|
automation,
|
|
1188
1196
|
notify,
|
|
@@ -1434,6 +1442,10 @@ async function runAutomation(command) {
|
|
|
1434
1442
|
}
|
|
1435
1443
|
async function runCollab(command) {
|
|
1436
1444
|
const runningState = getRunningState();
|
|
1445
|
+
if (command.options.principal) {
|
|
1446
|
+
process.exitCode = await runIntegrationCollab(command, runningState?.port ?? 9834, { write: line => console.log(line), stdin: readStdinText });
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1437
1449
|
if (!runningState?.localApiToken) {
|
|
1438
1450
|
// Help is a plain read: it must stay available without a running server.
|
|
1439
1451
|
if (command.action === 'help') {
|
|
@@ -3475,6 +3487,23 @@ async function main() {
|
|
|
3475
3487
|
await runAutomation(options.automation);
|
|
3476
3488
|
return;
|
|
3477
3489
|
}
|
|
3490
|
+
if (options.integration) {
|
|
3491
|
+
const argv = options.integration;
|
|
3492
|
+
if (!argv.length || ['help', '--help'].includes(argv[0])) {
|
|
3493
|
+
console.log(INTEGRATION_HELP);
|
|
3494
|
+
return;
|
|
3495
|
+
}
|
|
3496
|
+
const state = getRunningState();
|
|
3497
|
+
if (!state?.localApiToken) {
|
|
3498
|
+
console.error(JSON.stringify({ ok: false, code: 'SERVICE_UNAVAILABLE', error: 'Termdock is not running' }));
|
|
3499
|
+
process.exitCode = 1;
|
|
3500
|
+
return;
|
|
3501
|
+
}
|
|
3502
|
+
process.exitCode = argv[0] === 'revoke' && argv.length === 2
|
|
3503
|
+
? await revokeIntegration(argv[1], state.port, state.localApiToken, line => console.log(line))
|
|
3504
|
+
: await runIntegrationAdmin(argv, state.port, state.localApiToken, line => console.log(line));
|
|
3505
|
+
return;
|
|
3506
|
+
}
|
|
3478
3507
|
if (options.collab) {
|
|
3479
3508
|
await runCollab(options.collab);
|
|
3480
3509
|
return; // Preserve the receipt exit code and allow piped JSON output to drain.
|