termdock 1.4.308 → 1.4.310
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 +3 -1
- package/README.md +3 -1
- package/THIRD_PARTY_NOTICES.md +30 -0
- package/dist/client/assets/{App-ggIA_xqR.js → App-CytIMrFr.js} +62 -55
- package/dist/client/assets/App-CytIMrFr.js.br +0 -0
- package/dist/client/assets/App-CytIMrFr.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-BRTR2IsK.js +9 -0
- package/dist/client/assets/ArchitecturePanel-BRTR2IsK.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-BRTR2IsK.js.gz +0 -0
- package/dist/client/assets/{ArchitecturePanel-Cmpmp6FS.css → ArchitecturePanel-B_BDrrfR.css} +1 -1
- package/dist/client/assets/ArchitecturePanel-B_BDrrfR.css.br +0 -0
- package/dist/client/assets/ArchitecturePanel-B_BDrrfR.css.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-D1Puc7B3.js → ChangeWalkthroughPanel-Bm9Tvd6H.js} +2 -2
- package/dist/client/assets/ChangeWalkthroughPanel-Bm9Tvd6H.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-Bm9Tvd6H.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BagYBDlJ.js +1 -0
- package/dist/client/assets/CollaborationMainWorkspace-BagYBDlJ.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BagYBDlJ.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-D7LXylWr.js +2 -0
- package/dist/client/assets/{ComputerControlView-D5Lw9sNA.js → ComputerControlView-CC6r2cD3.js} +2 -2
- package/dist/client/assets/ComputerControlView-CC6r2cD3.js.br +0 -0
- package/dist/client/assets/ComputerControlView-CC6r2cD3.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-DYJrGfiG.js → DagPlayground-BqAadqBT.js} +1 -1
- package/dist/client/assets/DagPlayground-BqAadqBT.js.br +0 -0
- package/dist/client/assets/DagPlayground-BqAadqBT.js.gz +0 -0
- package/dist/client/assets/{DiffLab-5zdcFW3z.js → DiffLab-CrEVLy-j.js} +1 -1
- package/dist/client/assets/DiffLab-CrEVLy-j.js.br +0 -0
- package/dist/client/assets/DiffLab-CrEVLy-j.js.gz +0 -0
- package/dist/client/assets/{DiffReview-BBBC_NjV.js → DiffReview-C8e2GQxb.js} +1 -1
- package/dist/client/assets/DiffReview-C8e2GQxb.js.br +0 -0
- package/dist/client/assets/DiffReview-C8e2GQxb.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-CbhosFSB.js → DiffReviewLab-RQ1NmpZE.js} +2 -2
- package/dist/client/assets/DiffReviewLab-RQ1NmpZE.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-RQ1NmpZE.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-BuQwFQ4V.js → DiffViewer-KljgMa9h.js} +8 -8
- package/dist/client/assets/DiffViewer-KljgMa9h.js.br +0 -0
- package/dist/client/assets/DiffViewer-KljgMa9h.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-nO2VmqdP.js → ModelPreview-B9UR1TR3.js} +1 -1
- package/dist/client/assets/ModelPreview-B9UR1TR3.js.br +0 -0
- package/dist/client/assets/ModelPreview-B9UR1TR3.js.gz +0 -0
- package/dist/client/assets/RightSidebar-DSH93gN5.js +1677 -0
- package/dist/client/assets/RightSidebar-DSH93gN5.js.br +0 -0
- package/dist/client/assets/RightSidebar-DSH93gN5.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-yi0K8GDG.js → abnfDiagram-VCTEODGH-WFUf9n1A.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-WFUf9n1A.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-WFUf9n1A.js.gz +0 -0
- package/dist/client/assets/{app-B6FdOF6K.js → app-Bey8S6UU.js} +4 -4
- package/dist/client/assets/app-Bey8S6UU.js.br +0 -0
- package/dist/client/assets/app-Bey8S6UU.js.gz +0 -0
- package/dist/client/assets/{arc-_nLI_-XZ.js → arc-BXKxIsA3.js} +1 -1
- package/dist/client/assets/arc-BXKxIsA3.js.br +0 -0
- package/dist/client/assets/arc-BXKxIsA3.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-CikrA78O.js → architectureDiagram-5GKGNRK7-DAfRrSxr.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DAfRrSxr.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-DAfRrSxr.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-CWJq6qJ7.js → blockDiagram-I7D4REHJ-1HbSy7rb.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-1HbSy7rb.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-1HbSy7rb.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-gzPzLGvs.js → c4Diagram-7LVT6UL2-Dxwui-M_.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-Dxwui-M_.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-Dxwui-M_.js.gz +0 -0
- package/dist/client/assets/channel-Dkh0SMv-.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CfX9l18E.js → chunk-2Q5K7J3B-CJSdJ_A1.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-JSV1yrXM.js → chunk-5VM5RSS4-BNEXn4bI.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-CQ0LXeap.js → chunk-F27PBJKO-BaM6iLMb.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-BaM6iLMb.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BaM6iLMb.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-CJOEPkK9.js → chunk-IMKFNOWR-DxPJBdLq.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-DxPJBdLq.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-DxPJBdLq.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-Dpp7Xu_E.js → chunk-JWPE2WC7-BwEOSoiu.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-9hDa1b8z.js → chunk-POPQ4Y6H-CbYcBz8G.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-BskkG9gg.js → chunk-SVP7TREG-CpnycZ3K.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-CpnycZ3K.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-CpnycZ3K.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-DyzuP-9I.js → chunk-TICWLB2K-5oWwjAOF.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-5oWwjAOF.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-5oWwjAOF.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-C31_1i6z.js → chunk-XXDRQBXY-VWUm4Sz5.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-DqvmSdwt.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-DqvmSdwt.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-Bm-ESDOc.js → cose-bilkent-JH36ORCC-gpF5Tw44.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-gpF5Tw44.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-gpF5Tw44.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-DbFWAPvX.js → cynefin-OW5HDTMX-HFr7zRQ0.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-HFr7zRQ0.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-HFr7zRQ0.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js → cynefinDiagram-5FMLGOSQ-LccKgJj0.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-LccKgJj0.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-LccKgJj0.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-D_5uEMjJ.js → dagre-GXQ25YYZ-C7eLk1r1.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-C7eLk1r1.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-C7eLk1r1.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-DWVpndRP.js → diagram-S7CK7UJ4-DkWPZYAg.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-DkWPZYAg.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DkWPZYAg.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-DmYTya3w.js → diagram-UQ7AKVKN-DluTsMXz.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-DluTsMXz.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DluTsMXz.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-BEA6kqNj.js → diagram-VSXAHHWV-BLXEmXmu.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-BLXEmXmu.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BLXEmXmu.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-CU9vP0P0.js → diagram-VX7I27RA-DO3J99WU.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-DO3J99WU.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-DO3J99WU.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-B_GvE2g-.js → diagram-Z3DM3KII-kvkSusTy.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-kvkSusTy.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-kvkSusTy.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-BLrPQuLE.js → ebnfDiagram-PWID7BFC-Bxd9hatc.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Bxd9hatc.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-Bxd9hatc.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-DlXgoyZ-.js → erDiagram-RLTQ6QDP-BWc3uM23.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-BWc3uM23.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BWc3uM23.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-fPHWxEDZ.js → flowDiagram-HODETNUW-BQhPtc9z.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-BQhPtc9z.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-BQhPtc9z.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-B8MOv6ug.js → ganttDiagram-EL5Y4UJY-DzmTLgVr.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DzmTLgVr.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-DzmTLgVr.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-JW8vcVRm.js → gestureArbiter-BuNZMZ7A.js} +1 -1
- package/dist/client/assets/gestureArbiter-BuNZMZ7A.js.br +0 -0
- package/dist/client/assets/gestureArbiter-BuNZMZ7A.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-lH9gSoFx.js → gitGraphDiagram-WWUBYQGX-r0Onfl7K.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-r0Onfl7K.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-r0Onfl7K.js.gz +0 -0
- package/dist/client/assets/{index-CNY7CTVa.css → index-C6D5a-dg.css} +1 -1
- package/dist/client/assets/index-C6D5a-dg.css.br +0 -0
- package/dist/client/assets/{index-CNY7CTVa.css.gz → index-C6D5a-dg.css.gz} +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-CSG29Ty_.js → infoDiagram-27XIBGKW-C52jpDzv.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CcwSE5qV.js → ishikawaDiagram-5VMMS53U-C-l0eHmO.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C-l0eHmO.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-C-l0eHmO.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-CXFUeP2c.js → journeyDiagram-3NMN7TZE-DmhgEOjX.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DmhgEOjX.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-DmhgEOjX.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-KEHwljd2.js → kanban-definition-UXKFOSKX-BnZWlxIT.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-BnZWlxIT.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BnZWlxIT.js.gz +0 -0
- package/dist/client/assets/{layout-DqYx0MnO.js → layout-BrxPpC7u.js} +1 -1
- package/dist/client/assets/layout-BrxPpC7u.js.br +0 -0
- package/dist/client/assets/layout-BrxPpC7u.js.gz +0 -0
- package/dist/client/assets/{linear-BnG3XaJW.js → linear-DaEpqVcH.js} +1 -1
- package/dist/client/assets/linear-DaEpqVcH.js.br +0 -0
- package/dist/client/assets/linear-DaEpqVcH.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-Ce00rdM6.js → mermaid.core-CidoOS12.js} +5 -5
- package/dist/client/assets/mermaid.core-CidoOS12.js.br +0 -0
- package/dist/client/assets/mermaid.core-CidoOS12.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-D0hOW4SY.js → mindmap-definition-YA3MSWOX-Bpr3CVLy.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-Bpr3CVLy.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-Bpr3CVLy.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-D7hFrc0r.js +2 -0
- package/dist/client/assets/passwordBootstrap-D7hFrc0r.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-D7hFrc0r.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-UKp9t91x.js → pegDiagram-XKGWAZYB-Bw2JpSYn.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-Bw2JpSYn.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-Bw2JpSYn.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-Dif97wbH.js → pieDiagram-E7YTZNPT-BaKc3ifQ.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-BaKc3ifQ.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-BaKc3ifQ.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BPyHtH91.js → quadrantDiagram-AXDQQJYC-B0Yi-dl9.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0Yi-dl9.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0Yi-dl9.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-Wleq9LmS.js → railroadDiagram-O6MQD6OU-DCcCQofU.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DCcCQofU.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-DCcCQofU.js.gz +0 -0
- package/dist/client/assets/{rdpSession-CobFMSUs.js → rdpSession-DY81W5BY.js} +1 -1
- package/dist/client/assets/rdpSession-DY81W5BY.js.br +0 -0
- package/dist/client/assets/rdpSession-DY81W5BY.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-bZ0xp-v2.js → referenceLongPress-CAzWqXsY.js} +1 -1
- package/dist/client/assets/referenceLongPress-CAzWqXsY.js.br +0 -0
- package/dist/client/assets/referenceLongPress-CAzWqXsY.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-BL4pp0Ta.js → requirementDiagram-BXWQKSXE-CEJFRPWH.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CEJFRPWH.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-CEJFRPWH.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CV1cKx37.js → sankeyDiagram-P5KCCOFB-B3Wk45FA.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B3Wk45FA.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B3Wk45FA.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BF2InLwV.js → sequenceDiagram-WJ2MYXX4-BejEsITb.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BejEsITb.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BejEsITb.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-CldUyStR.js → sizeCapture-INFHLROL-DZ5NJZnM.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-DZ5NJZnM.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-DZ5NJZnM.js.gz +0 -0
- package/dist/client/assets/{sparkles-Cg-yPdE2.js → sparkles-DQGLdN2b.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-CByehbEd.js → stateDiagram-D77RDMKH-D8SSEwHa.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-D8SSEwHa.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-D8SSEwHa.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-D1KQOetv.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH--1_7YTL4.js → swimlanes-42K2YHIH-Dow-EKMG.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Dow-EKMG.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Dow-EKMG.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DQbZCzq9.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-CXfXJFK6.js → timeline-definition-24CTP7MA-DpQ9D5f6.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-DpQ9D5f6.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-DpQ9D5f6.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-CbMRJ6sx.js → triangle-alert-Bdf_dBch.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-byr6UQpL.js → vennDiagram-4TSXK5OY-DoemAmM2.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-DoemAmM2.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-DoemAmM2.js.gz +0 -0
- package/dist/client/assets/{videoCompression-ivJAy00d.js → videoCompression-Bg4gUQUf.js} +1 -1
- package/dist/client/assets/videoCompression-Bg4gUQUf.js.br +0 -0
- package/dist/client/assets/videoCompression-Bg4gUQUf.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-FJduk-7s.js → wardleyDiagram-VM6X3IG4-MernEZ54.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-MernEZ54.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-MernEZ54.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Ct1fBPFS.js → xychartDiagram-S5SC5T6Z-CzO3HEHD.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CzO3HEHD.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-CzO3HEHD.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 +21 -5
- package/dist/server/agent/collaborationStore.js +5 -0
- package/dist/server/agent/integrationCli.js +60 -2
- package/dist/server/agent/integrationServer.js +22 -2
- package/dist/server/agent/integrationSessions.js +250 -19
- package/dist/server/agent/integrationStore.js +1 -1
- package/dist/server/agent/sessionLifecycle.js +81 -0
- package/dist/server/agent/sessionResumeConfiguration.js +32 -0
- package/dist/server/architecture/cli.js +295 -0
- package/dist/server/architecture/document.js +328 -0
- package/dist/server/architecture/documentDiff.js +34 -0
- package/dist/server/architecture/fieldMutation.js +52 -0
- package/dist/server/architecture/flowchart.js +62 -0
- package/dist/server/architecture/mermaidDiff.js +85 -0
- package/dist/server/architecture/routes.js +117 -0
- package/dist/server/architecture/store.js +502 -0
- package/dist/server/cli.js +7 -1
- package/dist/server/entry.js +2 -0
- package/dist/server/federation/accessPolicy.js +9 -1
- package/dist/server/routes/terminal.js +75 -1
- package/docs/integrations.md +178 -3
- package/package.json +3 -1
- package/runtime-manifest.json +4 -4
- package/dist/client/assets/App-ggIA_xqR.js.br +0 -0
- package/dist/client/assets/App-ggIA_xqR.js.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-Cmpmp6FS.css.br +0 -0
- package/dist/client/assets/ArchitecturePanel-Cmpmp6FS.css.gz +0 -0
- package/dist/client/assets/ArchitecturePanel-CoBspW_C.js +0 -12
- package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.br +0 -0
- package/dist/client/assets/ArchitecturePanel-CoBspW_C.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-D1Puc7B3.js.gz +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js +0 -1
- package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.br +0 -0
- package/dist/client/assets/CollaborationMainWorkspace-BPyK5Xs8.js.gz +0 -0
- package/dist/client/assets/CollaborationTaskContent-CTSJJqze.js +0 -2
- package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.br +0 -0
- package/dist/client/assets/ComputerControlView-D5Lw9sNA.js.gz +0 -0
- package/dist/client/assets/DagPlayground-DYJrGfiG.js.br +0 -0
- package/dist/client/assets/DagPlayground-DYJrGfiG.js.gz +0 -0
- package/dist/client/assets/DiffLab-5zdcFW3z.js.br +0 -0
- package/dist/client/assets/DiffLab-5zdcFW3z.js.gz +0 -0
- package/dist/client/assets/DiffReview-BBBC_NjV.js.br +0 -0
- package/dist/client/assets/DiffReview-BBBC_NjV.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CbhosFSB.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CbhosFSB.js.gz +0 -0
- package/dist/client/assets/DiffViewer-BuQwFQ4V.js.br +0 -0
- package/dist/client/assets/DiffViewer-BuQwFQ4V.js.gz +0 -0
- package/dist/client/assets/ModelPreview-nO2VmqdP.js.br +0 -0
- package/dist/client/assets/ModelPreview-nO2VmqdP.js.gz +0 -0
- package/dist/client/assets/RightSidebar-BoNzpS2K.js +0 -1677
- package/dist/client/assets/RightSidebar-BoNzpS2K.js.br +0 -0
- package/dist/client/assets/RightSidebar-BoNzpS2K.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-yi0K8GDG.js.gz +0 -0
- package/dist/client/assets/app-B6FdOF6K.js.br +0 -0
- package/dist/client/assets/app-B6FdOF6K.js.gz +0 -0
- package/dist/client/assets/arc-_nLI_-XZ.js.br +0 -0
- package/dist/client/assets/arc-_nLI_-XZ.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CikrA78O.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-CWJq6qJ7.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-gzPzLGvs.js.gz +0 -0
- package/dist/client/assets/channel-BYsFmOmF.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CQ0LXeap.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CJOEPkK9.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-BskkG9gg.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-DyzuP-9I.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-BnmZv6Ns.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-BnmZv6Ns.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-Bm-ESDOc.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DbFWAPvX.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Ca0IKRdH.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-D_5uEMjJ.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-DWVpndRP.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DmYTya3w.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BEA6kqNj.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-CU9vP0P0.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-B_GvE2g-.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-BLrPQuLE.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-DlXgoyZ-.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-fPHWxEDZ.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-B8MOv6ug.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-JW8vcVRm.js.br +0 -0
- package/dist/client/assets/gestureArbiter-JW8vcVRm.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-lH9gSoFx.js.gz +0 -0
- package/dist/client/assets/index-CNY7CTVa.css.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-CcwSE5qV.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-CXFUeP2c.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-KEHwljd2.js.gz +0 -0
- package/dist/client/assets/layout-DqYx0MnO.js.br +0 -0
- package/dist/client/assets/layout-DqYx0MnO.js.gz +0 -0
- package/dist/client/assets/linear-BnG3XaJW.js.br +0 -0
- package/dist/client/assets/linear-BnG3XaJW.js.gz +0 -0
- package/dist/client/assets/mermaid.core-Ce00rdM6.js.br +0 -0
- package/dist/client/assets/mermaid.core-Ce00rdM6.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-D0hOW4SY.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-Bncb8qF_.js +0 -2
- package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-Bncb8qF_.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-UKp9t91x.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-Dif97wbH.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-BPyHtH91.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-Wleq9LmS.js.gz +0 -0
- package/dist/client/assets/rdpSession-CobFMSUs.js.br +0 -0
- package/dist/client/assets/rdpSession-CobFMSUs.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.br +0 -0
- package/dist/client/assets/referenceLongPress-bZ0xp-v2.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-BL4pp0Ta.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-CV1cKx37.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BF2InLwV.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-CldUyStR.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-CByehbEd.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CTcrJ-w0.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH--1_7YTL4.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-RSMV9AhO.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-CXfXJFK6.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-byr6UQpL.js.gz +0 -0
- package/dist/client/assets/videoCompression-ivJAy00d.js.br +0 -0
- package/dist/client/assets/videoCompression-ivJAy00d.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-FJduk-7s.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-Ct1fBPFS.js.gz +0 -0
|
@@ -8,6 +8,26 @@ import { COLLAB_HELP, executeCollaborationCommand, duration } from './collaborat
|
|
|
8
8
|
import { getTermdockVersion } from '../utils/version.js';
|
|
9
9
|
export const INTEGRATION_HELP = `td integration — local scoped integration administration
|
|
10
10
|
groups | list
|
|
11
|
+
clear-startup-input <TD-session-id> --generation <N>
|
|
12
|
+
Administrator removes this session's saved screen conditions and disables
|
|
13
|
+
inheritance on later restores; original identity/launch/history are preserved.
|
|
14
|
+
Pending messages may then use the ordinary delivery worker; delivered bodies
|
|
15
|
+
are never replayed. No Agent restart, resize, RPC or readiness claim.
|
|
16
|
+
The generation increments once; read session get for the new generation and
|
|
17
|
+
startup_input_disabled=true. Repeating with the old generation is rejected.
|
|
18
|
+
Minimum CLI/running service 1.4.310; session_startup_input_clear=true.
|
|
19
|
+
Errors: SESSION_STARTUP_INPUT_CLEAR_UNSUPPORTED, INVALID_SESSION_GENERATION,
|
|
20
|
+
SESSION_GENERATION_MISMATCH, SESSION_OPERATION_IN_PROGRESS,
|
|
21
|
+
INTEGRATION_SESSION_NOT_FOUND. Profile policy update alone keeps old snapshots.
|
|
22
|
+
resume-configuration <TD-session-id> (read current/recorded fingerprints; no mutation)
|
|
23
|
+
confirm-resume <TD-session-id> --generation <N> --fingerprint <sha256>
|
|
24
|
+
Administrator explicitly authorizes the current launcher/plugin recovery semantics.
|
|
25
|
+
This never proves unknown historical file contents or modifies Agent configuration.
|
|
26
|
+
Old records need this confirmation before release; new records capture a fingerprint.
|
|
27
|
+
Later changes fail closed. Grant session.release separately for runtime reclamation.
|
|
28
|
+
Minimum CLI/running service 1.4.309; capabilities.session_resume_configuration=true.
|
|
29
|
+
Errors: RESUME_CONFIGURATION_CHANGED, EXACT_RESUME_UNSUPPORTED,
|
|
30
|
+
SESSION_GENERATION_MISMATCH, SESSION_OPERATION_IN_PROGRESS, LAUNCHER_UNAVAILABLE.
|
|
11
31
|
diagnostics <TD-session-id> (read the last restore check; administrator only)
|
|
12
32
|
create --file <policy.json> --credential-file <private-output.json>
|
|
13
33
|
update --file <policy.json> (same identity/group; preserves the credential)
|
|
@@ -63,6 +83,42 @@ export async function runIntegrationAdmin(argv, port, adminToken, write) {
|
|
|
63
83
|
return 0;
|
|
64
84
|
}
|
|
65
85
|
const socketPath = integrationSocketPath(port);
|
|
86
|
+
if (action === 'clear-startup-input') {
|
|
87
|
+
const id = argv[1];
|
|
88
|
+
if (argv.length !== 4 || !id || !/^[a-zA-Z0-9._-]{1,80}$/.test(id) || argv[2] !== '--generation'
|
|
89
|
+
|| !/^[1-9][0-9]*$/.test(argv[3]) || !Number.isSafeInteger(Number(argv[3]))) {
|
|
90
|
+
throw new Error('Usage: td integration clear-startup-input <TD-session-id> --generation <positive integer>');
|
|
91
|
+
}
|
|
92
|
+
const capabilities = parsed(await request(socketPath, 'GET', '/admin/capabilities', undefined, headers(undefined, adminToken)));
|
|
93
|
+
if (capabilities.session_startup_input_clear !== true)
|
|
94
|
+
throw new CollaborationError('SESSION_STARTUP_INPUT_CLEAR_UNSUPPORTED', 'CLI and running service must be at least 1.4.310', 409);
|
|
95
|
+
write(JSON.stringify(parsed(await request(socketPath, 'POST', `/admin/sessions/${encodeURIComponent(id)}/startup-input/clear`, { generation: Number(argv[3]) }, headers(undefined, adminToken)))));
|
|
96
|
+
return 0;
|
|
97
|
+
}
|
|
98
|
+
if (action === 'resume-configuration' || action === 'confirm-resume') {
|
|
99
|
+
const id = argv[1];
|
|
100
|
+
if (!id || !/^[a-zA-Z0-9._-]{1,80}$/.test(id))
|
|
101
|
+
throw new Error('A full TD session id is required');
|
|
102
|
+
const capabilities = parsed(await request(socketPath, 'GET', '/admin/capabilities', undefined, headers(undefined, adminToken)));
|
|
103
|
+
if (capabilities.session_resume_configuration !== true)
|
|
104
|
+
throw new CollaborationError('SESSION_RELEASE_UNSUPPORTED', 'CLI and running service must be at least 1.4.309', 409);
|
|
105
|
+
let body;
|
|
106
|
+
if (action === 'confirm-resume') {
|
|
107
|
+
const args = {};
|
|
108
|
+
for (let i = 2; i < argv.length; i += 2) {
|
|
109
|
+
if (!['--generation', '--fingerprint'].includes(argv[i]) || !argv[i + 1] || args[argv[i]])
|
|
110
|
+
throw new Error('confirm-resume requires --generation and --fingerprint');
|
|
111
|
+
args[argv[i]] = argv[i + 1];
|
|
112
|
+
}
|
|
113
|
+
if (!/^[1-9][0-9]*$/.test(args['--generation'] ?? '') || !Number.isSafeInteger(Number(args['--generation'])) || !/^[a-f0-9]{64}$/.test(args['--fingerprint'] ?? ''))
|
|
114
|
+
throw new Error('Invalid generation or fingerprint');
|
|
115
|
+
body = { generation: Number(args['--generation']), fingerprint: args['--fingerprint'] };
|
|
116
|
+
}
|
|
117
|
+
else if (argv.length !== 2)
|
|
118
|
+
throw new Error('resume-configuration requires one TD session id');
|
|
119
|
+
write(JSON.stringify(parsed(await request(socketPath, body ? 'POST' : 'GET', `/admin/sessions/${encodeURIComponent(id)}/resume-configuration`, body, headers(undefined, adminToken)))));
|
|
120
|
+
return 0;
|
|
121
|
+
}
|
|
66
122
|
if (action === 'diagnostics') {
|
|
67
123
|
if (argv.length !== 2 || !/^[a-zA-Z0-9._-]{1,80}$/.test(argv[1]))
|
|
68
124
|
throw new Error('diagnostics requires one TD session id');
|
|
@@ -161,9 +217,11 @@ export async function runIntegrationCollab(command, port, io) {
|
|
|
161
217
|
return subscribe(socketPath, `/events?${params}`, credential, command.options.timeout ? duration(String(command.options.timeout)) : undefined, io.write);
|
|
162
218
|
}
|
|
163
219
|
if (command.action === 'session') {
|
|
164
|
-
|
|
220
|
+
if (command.operation === 'release' && (capability.session_runtime_release !== true || capability.session_lifecycle_generation !== true || capability.stable_session_identity !== true))
|
|
221
|
+
throw new CollaborationError('SESSION_RELEASE_UNSUPPORTED', 'CLI and running service must support the lifecycle contract (minimum 1.4.309)', 409);
|
|
222
|
+
const route = command.operation === 'create' ? '/sessions' : `/sessions/${encodeURIComponent(command.target)}${['restore', 'release'].includes(command.operation) ? `/${command.operation}` : ''}`;
|
|
165
223
|
const body = command.operation === 'create' ? { group_id: command.options.group, launch_profile: command.options['launch-profile'], cwd: command.options.cwd, idempotency_key: command.options['idempotency-key'] }
|
|
166
|
-
: command.operation === '
|
|
224
|
+
: ['restore', 'release'].includes(command.operation) ? { ...(command.operation === 'release' ? { generation: Number(command.options.generation) } : {}), idempotency_key: command.options['idempotency-key'] } : undefined;
|
|
167
225
|
io.write(JSON.stringify(parsed(await request(socketPath, command.operation === 'get' ? 'GET' : 'POST', route, body, headers(credential)))));
|
|
168
226
|
return 0;
|
|
169
227
|
}
|
|
@@ -34,6 +34,7 @@ export class IntegrationRuntime {
|
|
|
34
34
|
this.options = options;
|
|
35
35
|
this.store = new IntegrationStore(path.join(options.directory, 'integrations.json'));
|
|
36
36
|
this.sessions = new IntegrationSessions(path.join(options.directory, 'integration-sessions.json'), options.sessions);
|
|
37
|
+
this.stops.push(options.messages.registerRuntimeGuard(id => this.sessions.assertRuntimeMessage(id)));
|
|
37
38
|
const changed = () => { try {
|
|
38
39
|
this.sync();
|
|
39
40
|
}
|
|
@@ -70,6 +71,14 @@ export class IntegrationRuntime {
|
|
|
70
71
|
app.get('/admin/groups', run(req => { admin(req); return { groups: options.messages.list().filter(g => !g.federated).map(g => ({ id: g.id, name: g.name })) }; }));
|
|
71
72
|
app.get('/admin/capabilities', run(req => { admin(req); return this.capabilities(); }));
|
|
72
73
|
app.get('/admin/sessions/:id/restore-diagnostics', run(req => { admin(req); return this.sessions.restoreDiagnostics(req.params.id); }));
|
|
74
|
+
app.get('/admin/sessions/:id/resume-configuration', run(async (req) => { admin(req); return this.sessions.resumeConfiguration(req.params.id); }));
|
|
75
|
+
app.post('/admin/sessions/:id/resume-configuration', run(async (req) => { admin(req); return { session: publicIntegrationSession(await this.sessions.confirmResumeConfiguration(req.params.id, req.body?.generation, req.body?.fingerprint)) }; }));
|
|
76
|
+
app.post('/admin/sessions/:id/startup-input/clear', run(async (req) => {
|
|
77
|
+
admin(req);
|
|
78
|
+
const session = await this.sessions.clearStartupInput(req.params.id, req.body?.generation);
|
|
79
|
+
this.sync();
|
|
80
|
+
return { session: publicIntegrationSession(session) };
|
|
81
|
+
}));
|
|
73
82
|
app.get('/admin/principals', run(req => { admin(req); return { integrations: this.store.policies() }; }));
|
|
74
83
|
app.post('/admin/principals', run(req => { admin(req); this.group(req.body?.groupId); const provisioned = this.store.provision(req.body); try {
|
|
75
84
|
this.sync();
|
|
@@ -120,7 +129,13 @@ export class IntegrationRuntime {
|
|
|
120
129
|
const targets = Array.isArray(raw.toSessionIds) ? raw.toSessionIds : [raw.targetSessionId];
|
|
121
130
|
if (!targets.length || targets.some((id) => typeof id !== 'string'))
|
|
122
131
|
integrationError('INVALID_MESSAGE_TARGET', 'Specify a local group member');
|
|
123
|
-
const
|
|
132
|
+
const descriptors = this.sessions.snapshot().filter(s => s.group_id === p.groupId);
|
|
133
|
+
const recipients = targets.map((id) => scopedId(id, [...group.sessionIds, ...descriptors.map(s => s.session_id)], 'SESSION'));
|
|
134
|
+
for (const id of recipients) {
|
|
135
|
+
this.sessions.assertMessageTarget(p, id);
|
|
136
|
+
if (!group.sessionIds.includes(id))
|
|
137
|
+
integrationError('SESSION_NOT_FOUND', 'Restore the original runtime and membership before sending', 404);
|
|
138
|
+
}
|
|
124
139
|
let thread;
|
|
125
140
|
if (raw.thread_id)
|
|
126
141
|
thread = scopedId(raw.thread_id, options.messages.snapshotMessages().filter(m => m.groupId === p.groupId).map(m => m.threadId), 'THREAD');
|
|
@@ -135,6 +150,8 @@ export class IntegrationRuntime {
|
|
|
135
150
|
const raw = req.body ?? {}, original = this.message(p, raw.messageId);
|
|
136
151
|
const target = original.toPrincipalId === p.id ? original.fromSessionId
|
|
137
152
|
: original.integrationOrigin?.integrationId === p.id ? original.toSessionId : null;
|
|
153
|
+
if (target)
|
|
154
|
+
this.sessions.assertMessageTarget(p, target);
|
|
138
155
|
if (!target || !this.group(p.groupId).sessionIds.includes(target))
|
|
139
156
|
integrationError('NO_REPLY_TARGET', 'Reply requires your own message and its original live group member', 409);
|
|
140
157
|
const messages = options.messages.send({ ...this.messageExtras(raw), groupId: p.groupId, fromSessionId: null,
|
|
@@ -180,6 +197,7 @@ export class IntegrationRuntime {
|
|
|
180
197
|
}));
|
|
181
198
|
app.get('/sessions/:id', run(async (req) => { const p = principal(req); this.authorize(p, 'session.read'); return { session: publicIntegrationSession(await this.sessions.get(p, req.params.id)) }; }));
|
|
182
199
|
app.post('/sessions/:id/restore', run(async (req) => { const p = principal(req); this.authorize(p, 'session.restore'); const session = await this.sessions.restore(p, req.params.id, req.body?.idempotency_key); this.sync(); return { session: publicIntegrationSession(session) }; }));
|
|
200
|
+
app.post('/sessions/:id/release', run(async (req) => { const p = principal(req); this.authorize(p, 'session.release'); const session = await this.sessions.release(p, req.params.id, req.body?.generation, req.body?.idempotency_key); this.sync(); return { session: publicIntegrationSession(session) }; }));
|
|
183
201
|
app.get('/events', run((req, res) => {
|
|
184
202
|
const p = principal(req);
|
|
185
203
|
this.authorize(p, 'events.read', req.query.group);
|
|
@@ -203,7 +221,9 @@ export class IntegrationRuntime {
|
|
|
203
221
|
integration_principal: true, group_scoped_permissions: true, durable_event_push: true,
|
|
204
222
|
event_ack_semantics: 'durable_received', correlated_task_responses: true, accepted_task_comments: true,
|
|
205
223
|
idempotent_session_create: true, exact_session_restore: true, external_validation: false,
|
|
206
|
-
startup_input_conditions: true,
|
|
224
|
+
startup_input_conditions: true, session_runtime_release: true, stable_session_identity: true, session_lifecycle_generation: true,
|
|
225
|
+
session_resume_configuration: true, session_lifecycle_min_version: '1.4.309',
|
|
226
|
+
session_startup_input_clear: true, session_startup_input_clear_min_version: '1.4.310',
|
|
207
227
|
integration_policy_update: true,
|
|
208
228
|
session_restore_diagnostics: true,
|
|
209
229
|
native_identity_linux_flock_owner: process.platform === 'linux',
|
|
@@ -2,6 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { createHash, randomBytes, randomUUID } from 'node:crypto';
|
|
4
4
|
import { atomicJson, integrationError } from './integrationStore.js';
|
|
5
|
+
import { SessionLifecycleCoordinator, assertMessageRuntime, assertReleaseFacts, assertReleaseGeneration } from './sessionLifecycle.js';
|
|
6
|
+
import { resumeConfigurationFingerprint } from './sessionResumeConfiguration.js';
|
|
5
7
|
import { NativeResumeOwnerError } from './nativeResumeOwner.js';
|
|
6
8
|
/** Quote each configured argument as literal shell data, including paths. */
|
|
7
9
|
export function integrationLaunchCommand(record, restore) {
|
|
@@ -12,6 +14,8 @@ export function integrationLaunchCommand(record, restore) {
|
|
|
12
14
|
return `cd -- ${quote(record.cwd)} && ${[record.profile.executable, ...args].map(quote).join(' ')}`;
|
|
13
15
|
}
|
|
14
16
|
function startupInput(record, now) {
|
|
17
|
+
if (record.startup_input_disabled)
|
|
18
|
+
return undefined;
|
|
15
19
|
const condition = record.startup_condition ?? record.profile.startupInput;
|
|
16
20
|
return condition ? { state: 'pending', deadline: now + (condition.timeoutMs ?? 120000), matched_since: null, observed_at: null } : undefined;
|
|
17
21
|
}
|
|
@@ -50,7 +54,8 @@ export class IntegrationSessions {
|
|
|
50
54
|
doc = { version: 1, sessions: [], requests: {} };
|
|
51
55
|
pending = new Map();
|
|
52
56
|
listeners = new Set();
|
|
53
|
-
|
|
57
|
+
lifecycle = new SessionLifecycleCoordinator();
|
|
58
|
+
mutations = new Map();
|
|
54
59
|
timers = new Set();
|
|
55
60
|
stopObservation;
|
|
56
61
|
stopDeliveryGuard;
|
|
@@ -70,8 +75,12 @@ export class IntegrationSessions {
|
|
|
70
75
|
if (error.code !== 'ENOENT')
|
|
71
76
|
throw error;
|
|
72
77
|
}
|
|
78
|
+
for (const record of this.doc.sessions) {
|
|
79
|
+
record.generation ??= 1;
|
|
80
|
+
record.runtime_present ??= null;
|
|
81
|
+
}
|
|
73
82
|
for (const record of this.doc.sessions)
|
|
74
|
-
if (['starting', 'restoring', 'binding_pending'].includes(record.state) || record.startup_input?.state === 'pending')
|
|
83
|
+
if (['starting', 'restoring', 'binding_pending'].includes(record.state) || record.state === 'releasing' || record.startup_input?.state === 'pending')
|
|
75
84
|
this.scheduleRefresh(record);
|
|
76
85
|
// A pre-restart partial match cannot prove continuity of the live screen.
|
|
77
86
|
for (const record of this.doc.sessions)
|
|
@@ -89,12 +98,17 @@ export class IntegrationSessions {
|
|
|
89
98
|
}
|
|
90
99
|
subscribe(listener) { this.listeners.add(listener); return () => { this.listeners.delete(listener); }; }
|
|
91
100
|
snapshot() { return structuredClone(this.doc.sessions); }
|
|
92
|
-
save() {
|
|
93
|
-
|
|
94
|
-
|
|
101
|
+
save() {
|
|
102
|
+
if (Buffer.byteLength(JSON.stringify(this.doc)) > 64 * 1024 * 1024)
|
|
103
|
+
integrationError('SESSION_STORAGE_FULL', 'Recovery descriptor journal is full; no new operation was committed', 503);
|
|
104
|
+
atomicJson(this.file, this.doc);
|
|
105
|
+
for (const listener of this.listeners) {
|
|
106
|
+
try {
|
|
107
|
+
listener();
|
|
108
|
+
}
|
|
109
|
+
catch { /* Durable session state is reconciled on restart. */ }
|
|
95
110
|
}
|
|
96
|
-
|
|
97
|
-
} }
|
|
111
|
+
}
|
|
98
112
|
saveRecord(record) {
|
|
99
113
|
const before = this.doc.sessions;
|
|
100
114
|
this.doc.sessions = [...before.filter(s => s.session_id !== record.session_id), structuredClone(record)];
|
|
@@ -139,7 +153,7 @@ export class IntegrationSessions {
|
|
|
139
153
|
integrationError('INTEGRATION_SESSION_LIMIT', 'Integration session storage is full', 409);
|
|
140
154
|
const now = Date.now(), record = { operation_id: randomUUID(), session_id: randomBytes(6).toString('hex'),
|
|
141
155
|
group_id: principal.groupId, principal_id: principal.id, agent_slug: profile.agentSlug, agent_native_session_id: null,
|
|
142
|
-
state: 'starting', error_code: null, cwd, launch_profile: profile.id, profile: structuredClone(profile),
|
|
156
|
+
state: 'starting', generation: 1, runtime_present: null, error_code: null, cwd, launch_profile: profile.id, profile: structuredClone(profile),
|
|
143
157
|
created_at: now, updated_at: now, deadline: now + this.bindingTimeout, launch_submitted: false };
|
|
144
158
|
record.startup_input = startupInput(record, now);
|
|
145
159
|
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: record.session_id };
|
|
@@ -156,6 +170,21 @@ export class IntegrationSessions {
|
|
|
156
170
|
const promise = (async () => {
|
|
157
171
|
try {
|
|
158
172
|
// Store intent before launching. A failed persistence cannot execute.
|
|
173
|
+
if (this.adapter.resumeConfiguration) {
|
|
174
|
+
try {
|
|
175
|
+
const fingerprint = await resumeConfigurationFingerprint(record, this.adapter);
|
|
176
|
+
if (record.resume_configuration_fingerprint && record.resume_configuration_fingerprint !== fingerprint)
|
|
177
|
+
integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
|
|
178
|
+
if (!restoring)
|
|
179
|
+
record.resume_configuration_fingerprint = fingerprint;
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
// Existing creation supports plugins without an exact resume path.
|
|
183
|
+
// They remain launchable, but cannot gain a releasable descriptor.
|
|
184
|
+
if (restoring || error.code !== 'EXACT_RESUME_UNSUPPORTED')
|
|
185
|
+
throw error;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
159
188
|
record.launch_submitted = true;
|
|
160
189
|
this.saveRecord(record);
|
|
161
190
|
const prepared = (binding) => { record.terminal_binding = binding; this.saveRecord(record); };
|
|
@@ -187,27 +216,74 @@ export class IntegrationSessions {
|
|
|
187
216
|
return structuredClone({ session_id: record.session_id, operation_id: record.operation_id,
|
|
188
217
|
state: record.state, error_code: record.error_code, diagnostics: record.restore_diagnostics ?? null });
|
|
189
218
|
}
|
|
190
|
-
|
|
219
|
+
assertRuntimeMessage(id) { assertMessageRuntime(this.doc.sessions.find(s => s.session_id === id)); }
|
|
220
|
+
assertMessageTarget(principal, id) {
|
|
221
|
+
const record = this.doc.sessions.find(s => s.session_id === id && s.group_id === principal.groupId);
|
|
222
|
+
assertMessageRuntime(record);
|
|
223
|
+
}
|
|
224
|
+
async mutate(principal, key, input, action) {
|
|
225
|
+
const request = this.request(principal, key, input);
|
|
226
|
+
if (request.previous?.result) {
|
|
227
|
+
this.lookup(principal, request.previous.sessionId);
|
|
228
|
+
return structuredClone(request.previous.result);
|
|
229
|
+
}
|
|
230
|
+
const active = this.mutations.get(request.scoped);
|
|
231
|
+
if (active) {
|
|
232
|
+
if (active.hash !== request.hash)
|
|
233
|
+
integrationError('IDEMPOTENCY_CONFLICT', 'Operation key parameters changed', 409);
|
|
234
|
+
return active.promise;
|
|
235
|
+
}
|
|
236
|
+
const promise = action().then(result => {
|
|
237
|
+
if (this.doc.requests[request.scoped]) {
|
|
238
|
+
const before = this.doc.requests[request.scoped];
|
|
239
|
+
this.doc.requests[request.scoped] = { ...before, result: structuredClone(result) };
|
|
240
|
+
try {
|
|
241
|
+
this.save();
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
this.doc.requests[request.scoped] = before;
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
249
|
+
}).finally(() => this.mutations.delete(request.scoped));
|
|
250
|
+
this.mutations.set(request.scoped, { hash: request.hash, promise });
|
|
251
|
+
return promise;
|
|
252
|
+
}
|
|
253
|
+
async replayRequest(principal, id, request) {
|
|
254
|
+
const record = await (this.pending.get(id) ?? this.get(principal, id));
|
|
255
|
+
if (request.operationId && (record.operation_id !== request.operationId || record.generation !== request.generation))
|
|
256
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Original operation result was not committed and a later generation exists; read session get, do not reuse this key', 409);
|
|
257
|
+
return record;
|
|
258
|
+
}
|
|
259
|
+
async restore(principal, id, key) {
|
|
260
|
+
return this.mutate(principal, key, ['restore', id], () => this.lifecycle.run(id, () => this.restoreOnce(principal, id, key)));
|
|
261
|
+
}
|
|
262
|
+
async restoreOnce(principal, id, idempotencyKey) {
|
|
191
263
|
const request = this.request(principal, idempotencyKey, ['restore', id]);
|
|
192
264
|
if (request.previous)
|
|
193
|
-
return this.
|
|
194
|
-
if (this.pending.has(id)
|
|
265
|
+
return this.replayRequest(principal, id, request.previous);
|
|
266
|
+
if (this.pending.has(id))
|
|
195
267
|
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Another session operation is in progress', 409);
|
|
196
|
-
|
|
268
|
+
let releaseNative;
|
|
197
269
|
try {
|
|
198
270
|
const record = await this.get(principal, id);
|
|
199
271
|
if (!record.agent_native_session_id)
|
|
200
272
|
integrationError('NATIVE_SESSION_ID_MISSING', 'An exact native session binding is required', 409);
|
|
201
273
|
// Recheck the current grant and actual directories; restore uses the
|
|
202
274
|
// original profile snapshot, never changed model or wrapper defaults.
|
|
203
|
-
|
|
275
|
+
const authorizedProfile = principal.launchProfiles.find(p => p.id === record.launch_profile);
|
|
276
|
+
if (!authorizedProfile)
|
|
204
277
|
integrationError('LAUNCH_PROFILE_DENIED', 'Original launch profile is no longer authorized', 403);
|
|
278
|
+
permittedCwd(authorizedProfile, record.cwd);
|
|
205
279
|
permittedCwd(record.profile, record.cwd);
|
|
280
|
+
if (record.operation_uncertain || record.state === 'releasing')
|
|
281
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Reconcile prior release using session get before restore', 409);
|
|
206
282
|
const observed = await this.adapter.inspect(record);
|
|
207
283
|
if (observed.running) {
|
|
208
284
|
if (observed.agentSlug !== record.agent_slug || observed.nativeId !== record.agent_native_session_id)
|
|
209
285
|
integrationError('SESSION_IDENTITY_MISMATCH', 'Existing process does not prove the exact native binding', 409);
|
|
210
|
-
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id };
|
|
286
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
|
|
211
287
|
try {
|
|
212
288
|
this.save();
|
|
213
289
|
}
|
|
@@ -221,16 +297,23 @@ export class IntegrationSessions {
|
|
|
221
297
|
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Prior launch outcome is not yet resolved; inspect the original session', 409);
|
|
222
298
|
if (observed.exists && !observed.shell)
|
|
223
299
|
integrationError('SESSION_TARGET_NOT_SHELL', 'Original pane is not a verified shell', 409);
|
|
300
|
+
if (record.state === 'released' && !record.resume_configuration_fingerprint)
|
|
301
|
+
integrationError('RESUME_CONFIGURATION_UNCONFIRMED', 'Administrator must explicitly confirm the current recovery configuration', 409);
|
|
302
|
+
releaseNative = this.lifecycle.reserveNative(record, this.doc.sessions);
|
|
303
|
+
if (record.resume_configuration_fingerprint && record.resume_configuration_fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
|
|
304
|
+
integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
|
|
305
|
+
record.generation = (record.generation ?? 1) + 1;
|
|
306
|
+
record.runtime_present = observed.exists;
|
|
224
307
|
record.state = 'restoring';
|
|
225
308
|
record.error_code = null;
|
|
226
309
|
record.operation_id = randomUUID();
|
|
227
310
|
record.deadline = Date.now() + this.bindingTimeout;
|
|
228
311
|
record.updated_at = Date.now();
|
|
229
312
|
record.restore_diagnostics = undefined;
|
|
230
|
-
if (!record.profile.startupInput && !record.startup_condition)
|
|
313
|
+
if (!record.startup_input_disabled && !record.profile.startupInput && !record.startup_condition)
|
|
231
314
|
record.startup_condition = structuredClone(principal.launchProfiles.find(profile => profile.id === record.launch_profile)?.startupInput);
|
|
232
315
|
record.startup_input = startupInput(record, Date.now());
|
|
233
|
-
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id };
|
|
316
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
|
|
234
317
|
try {
|
|
235
318
|
this.saveRecord(record);
|
|
236
319
|
}
|
|
@@ -241,13 +324,156 @@ export class IntegrationSessions {
|
|
|
241
324
|
return await this.launch(record, true);
|
|
242
325
|
}
|
|
243
326
|
finally {
|
|
244
|
-
|
|
327
|
+
releaseNative?.();
|
|
245
328
|
}
|
|
246
329
|
}
|
|
330
|
+
async resumeConfiguration(id) {
|
|
331
|
+
const record = this.doc.sessions.find(s => s.session_id === id);
|
|
332
|
+
if (!record)
|
|
333
|
+
return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
|
|
334
|
+
return { session_id: id, generation: record.generation ?? 1, recorded_fingerprint: record.resume_configuration_fingerprint ?? null,
|
|
335
|
+
current_fingerprint: await resumeConfigurationFingerprint(record, this.adapter), confirmation_required: !record.resume_configuration_fingerprint };
|
|
336
|
+
}
|
|
337
|
+
async clearStartupInput(id, generation) {
|
|
338
|
+
return this.lifecycle.run(id, async () => {
|
|
339
|
+
const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
|
|
340
|
+
if (!record)
|
|
341
|
+
return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
|
|
342
|
+
assertReleaseGeneration(record, generation);
|
|
343
|
+
if (this.pending.has(id) || record.operation_uncertain || ['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state)) {
|
|
344
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the current lifecycle operation before clearing startup input conditions', 409);
|
|
345
|
+
}
|
|
346
|
+
if (record.startup_input_disabled && !record.profile.startupInput && !record.startup_condition && !record.startup_input)
|
|
347
|
+
return record;
|
|
348
|
+
delete record.profile.startupInput;
|
|
349
|
+
delete record.startup_condition;
|
|
350
|
+
delete record.startup_input;
|
|
351
|
+
record.startup_input_disabled = true;
|
|
352
|
+
record.generation = generation + 1;
|
|
353
|
+
record.updated_at = Date.now();
|
|
354
|
+
this.saveRecord(record);
|
|
355
|
+
return record;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
async confirmResumeConfiguration(id, generation, fingerprint) {
|
|
359
|
+
return this.lifecycle.run(id, async () => {
|
|
360
|
+
const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
|
|
361
|
+
if (!record)
|
|
362
|
+
return integrationError('INTEGRATION_SESSION_NOT_FOUND', 'Recovery record does not exist', 404);
|
|
363
|
+
assertReleaseGeneration(record, generation);
|
|
364
|
+
if (this.pending.has(id) || ['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state))
|
|
365
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Resolve the current operation before confirming compatibility', 409);
|
|
366
|
+
if (!/^[a-f0-9]{64}$/.test(fingerprint) || fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
|
|
367
|
+
integrationError('RESUME_CONFIGURATION_CHANGED', 'Current fingerprint no longer matches the administrator confirmation', 409);
|
|
368
|
+
record.resume_configuration_fingerprint = fingerprint;
|
|
369
|
+
record.generation = generation + 1;
|
|
370
|
+
record.updated_at = Date.now();
|
|
371
|
+
this.saveRecord(record);
|
|
372
|
+
return record;
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
async release(principal, id, generation, key) {
|
|
376
|
+
return this.mutate(principal, key, ['release', id, generation], () => this.lifecycle.run(id, async () => {
|
|
377
|
+
const request = this.request(principal, key, ['release', id, generation]);
|
|
378
|
+
if (request.previous)
|
|
379
|
+
return this.replayRequest(principal, id, request.previous);
|
|
380
|
+
if (!this.adapter.release || !this.adapter.runtimePresent)
|
|
381
|
+
integrationError('SESSION_RELEASE_UNSUPPORTED', 'Runtime adapter does not support release', 409);
|
|
382
|
+
if (this.pending.has(id))
|
|
383
|
+
integrationError('SESSION_OPERATION_IN_PROGRESS', 'Launch is in progress', 409);
|
|
384
|
+
const record = await this.get(principal, id);
|
|
385
|
+
assertReleaseGeneration(record, generation);
|
|
386
|
+
if (record.state !== 'released') {
|
|
387
|
+
assertReleaseFacts(record, await this.adapter.inspect(record));
|
|
388
|
+
if (this.adapter.pendingMessages?.(id))
|
|
389
|
+
integrationError('SESSION_PENDING_MESSAGES', 'Pending messages must be resolved before release', 409);
|
|
390
|
+
if (!record.resume_configuration_fingerprint)
|
|
391
|
+
integrationError('RESUME_CONFIGURATION_UNCONFIRMED', 'Administrator must inspect and confirm the recovery configuration before release', 409);
|
|
392
|
+
if (record.resume_configuration_fingerprint !== await resumeConfigurationFingerprint(record, this.adapter))
|
|
393
|
+
integrationError('RESUME_CONFIGURATION_CHANGED', 'Recovery launcher or plugin semantics changed', 409);
|
|
394
|
+
}
|
|
395
|
+
if (record.state === 'released') {
|
|
396
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
|
|
397
|
+
try {
|
|
398
|
+
this.save();
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
delete this.doc.requests[request.scoped];
|
|
402
|
+
throw error;
|
|
403
|
+
}
|
|
404
|
+
return record;
|
|
405
|
+
}
|
|
406
|
+
if (this.adapter.prepareRelease)
|
|
407
|
+
record.group_role = (await this.adapter.prepareRelease(record)).group_role;
|
|
408
|
+
record.state = 'releasing';
|
|
409
|
+
record.operation_id = randomUUID();
|
|
410
|
+
record.generation = generation + 1;
|
|
411
|
+
record.operation_uncertain = false;
|
|
412
|
+
record.error_code = null;
|
|
413
|
+
record.updated_at = Date.now();
|
|
414
|
+
this.doc.requests[request.scoped] = { hash: request.hash, sessionId: id, operationId: record.operation_id, generation: record.generation };
|
|
415
|
+
try {
|
|
416
|
+
this.saveRecord(record);
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
delete this.doc.requests[request.scoped];
|
|
420
|
+
throw error;
|
|
421
|
+
}
|
|
422
|
+
try {
|
|
423
|
+
await this.adapter.release(record, false);
|
|
424
|
+
if (await this.adapter.runtimePresent(record))
|
|
425
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Runtime removal could not be confirmed', 409);
|
|
426
|
+
record.state = 'released';
|
|
427
|
+
record.runtime_present = false;
|
|
428
|
+
record.terminal_binding = null;
|
|
429
|
+
record.startup_input = undefined;
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
record.state = 'failed';
|
|
433
|
+
record.operation_uncertain = true;
|
|
434
|
+
record.runtime_present = null;
|
|
435
|
+
record.error_code = typeof error.code === 'string' ? error.code : 'SESSION_RELEASE_FAILED';
|
|
436
|
+
}
|
|
437
|
+
record.updated_at = Date.now();
|
|
438
|
+
this.saveRecord(record);
|
|
439
|
+
return record;
|
|
440
|
+
}));
|
|
441
|
+
}
|
|
247
442
|
async refresh(id) {
|
|
248
443
|
const record = structuredClone(this.doc.sessions.find(s => s.session_id === id));
|
|
249
444
|
if (!record)
|
|
250
445
|
return;
|
|
446
|
+
if (record.state === 'released' || record.state === 'releasing' || record.operation_uncertain) {
|
|
447
|
+
if (this.lifecycle.active(id))
|
|
448
|
+
return;
|
|
449
|
+
try {
|
|
450
|
+
if (record.state !== 'released')
|
|
451
|
+
await this.adapter.release?.(record, true);
|
|
452
|
+
const present = await this.adapter.runtimePresent?.(record);
|
|
453
|
+
if (present === undefined)
|
|
454
|
+
integrationError('SESSION_RELEASE_UNSUPPORTED', 'Runtime presence is unavailable', 409);
|
|
455
|
+
if (record.state !== 'released') {
|
|
456
|
+
if (present)
|
|
457
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'Prior release still has a runtime; explicit release retry is required', 409);
|
|
458
|
+
// Absent runtime metadata has been reconciled without killing a live runtime.
|
|
459
|
+
}
|
|
460
|
+
else if (present)
|
|
461
|
+
integrationError('OPERATION_OUTCOME_UNCONFIRMED', 'A runtime exists for a released descriptor', 409);
|
|
462
|
+
const next = { ...record, state: 'released', runtime_present: false, terminal_binding: null, startup_input: undefined, operation_uncertain: false, error_code: null };
|
|
463
|
+
if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
|
|
464
|
+
return;
|
|
465
|
+
if (JSON.stringify(next) !== JSON.stringify(record))
|
|
466
|
+
this.saveRecord({ ...next, updated_at: Date.now() });
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
if (JSON.stringify(this.doc.sessions.find(s => s.session_id === id)) !== JSON.stringify(record))
|
|
470
|
+
return;
|
|
471
|
+
const next = { ...record, state: 'failed', runtime_present: null, operation_uncertain: true, error_code: typeof error.code === 'string' ? error.code : 'OPERATION_OUTCOME_UNCONFIRMED' };
|
|
472
|
+
if (JSON.stringify(next) !== JSON.stringify(record))
|
|
473
|
+
this.saveRecord({ ...next, updated_at: Date.now() });
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
251
477
|
let observed;
|
|
252
478
|
try {
|
|
253
479
|
observed = await this.adapter.inspect(record);
|
|
@@ -261,7 +487,7 @@ export class IntegrationSessions {
|
|
|
261
487
|
this.saveRecord({ ...record, state: 'failed', error_code: typeof code === 'string' ? code : 'SESSION_OBSERVATION_FAILED', updated_at: Date.now() });
|
|
262
488
|
return;
|
|
263
489
|
}
|
|
264
|
-
const next = { ...record };
|
|
490
|
+
const next = { ...record, runtime_present: this.adapter.runtimePresent ? await this.adapter.runtimePresent(record) : observed.exists };
|
|
265
491
|
if (observed.nativeId && (typeof observed.nativeId !== 'string' || observed.nativeId.length > 256 || /[\x00-\x1f\x7f]/.test(observed.nativeId))) {
|
|
266
492
|
next.state = 'failed';
|
|
267
493
|
next.error_code = 'NATIVE_SESSION_ID_INVALID';
|
|
@@ -325,7 +551,12 @@ export class IntegrationSessions {
|
|
|
325
551
|
}
|
|
326
552
|
/** Only gates configured launches. Native UUID and Agent turn state are not input prerequisites. */
|
|
327
553
|
async deliveryReadiness(id) {
|
|
328
|
-
|
|
554
|
+
const existing = this.doc.sessions.find(record => record.session_id === id);
|
|
555
|
+
if (!existing)
|
|
556
|
+
return null;
|
|
557
|
+
if (existing.state === 'released' || existing.state === 'releasing' || existing.operation_uncertain)
|
|
558
|
+
return { allowed: false, reason: existing.state === 'released' ? 'SESSION_RELEASED' : 'SESSION_OPERATION_IN_PROGRESS' };
|
|
559
|
+
if (!existing.startup_input)
|
|
329
560
|
return null;
|
|
330
561
|
await this.refresh(id);
|
|
331
562
|
const record = this.doc.sessions.find(record => record.session_id === id);
|
|
@@ -3,7 +3,7 @@ import path from 'node:path';
|
|
|
3
3
|
import { createHash, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
4
4
|
import { CollaborationError } from './collaborationProtocol.js';
|
|
5
5
|
export const INTEGRATION_PROTOCOL = 1;
|
|
6
|
-
export const INTEGRATION_OPERATIONS = ['message.send', 'message.read', 'task.read', 'task.create', 'task.configure', 'task.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore'];
|
|
6
|
+
export const INTEGRATION_OPERATIONS = ['message.send', 'message.read', 'task.read', 'task.create', 'task.configure', 'task.assign', 'task.comment', 'task.revise', 'task.answer', 'events.read', 'session.create', 'session.read', 'session.restore', 'session.release'];
|
|
7
7
|
export function integrationError(code, message, status = 400) { throw new CollaborationError(code, message, status); }
|
|
8
8
|
export function atomicJson(file, value) {
|
|
9
9
|
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
2
|
+
const fail = (code, message) => { throw new CollaborationError(code, message, 409); };
|
|
3
|
+
/** One coordinator per service. A reservation covers validation and launch;
|
|
4
|
+
* persisted unresolved intents also block a second identity after restart.
|
|
5
|
+
* This does not promise atomic exclusion of independently launched programs. */
|
|
6
|
+
export class SessionLifecycleCoordinator {
|
|
7
|
+
operations = new Set();
|
|
8
|
+
natives = new Map();
|
|
9
|
+
async run(id, action) {
|
|
10
|
+
if (this.operations.has(id))
|
|
11
|
+
fail('SESSION_OPERATION_IN_PROGRESS', 'Another lifecycle operation is in progress');
|
|
12
|
+
this.operations.add(id);
|
|
13
|
+
try {
|
|
14
|
+
return await action();
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
this.operations.delete(id);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
active(id) { return this.operations.has(id); }
|
|
21
|
+
reserveNative(record, descriptors) {
|
|
22
|
+
const key = JSON.stringify([record.agent_slug, record.agent_native_session_id]);
|
|
23
|
+
const owner = this.natives.get(key);
|
|
24
|
+
if (owner && owner !== record.session_id || descriptors.some(other => other.session_id !== record.session_id
|
|
25
|
+
&& other.agent_slug === record.agent_slug && other.agent_native_session_id === record.agent_native_session_id
|
|
26
|
+
&& (['starting', 'restoring', 'binding_pending'].includes(other.state) || other.operation_uncertain))) {
|
|
27
|
+
fail('SESSION_NATIVE_OPERATION_IN_PROGRESS', 'Another descriptor has an unresolved operation for this native identity');
|
|
28
|
+
}
|
|
29
|
+
this.natives.set(key, record.session_id);
|
|
30
|
+
return () => { if (this.natives.get(key) === record.session_id)
|
|
31
|
+
this.natives.delete(key); };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function assertReleaseGeneration(record, generation) {
|
|
35
|
+
if (!Number.isSafeInteger(generation) || Number(generation) < 1)
|
|
36
|
+
fail('INVALID_SESSION_GENERATION', 'Operation requires the generation returned by session get');
|
|
37
|
+
if (generation !== (record.generation ?? 1))
|
|
38
|
+
fail('SESSION_GENERATION_MISMATCH', 'Session generation changed; read its current state before retrying');
|
|
39
|
+
}
|
|
40
|
+
export function assertReleaseFacts(record, facts) {
|
|
41
|
+
if (!record.agent_native_session_id)
|
|
42
|
+
fail('NATIVE_SESSION_ID_MISSING', 'Release requires a durable exact native identity');
|
|
43
|
+
if (['starting', 'restoring', 'binding_pending', 'releasing'].includes(record.state))
|
|
44
|
+
fail('SESSION_OPERATION_IN_PROGRESS', 'Prior operation is unresolved');
|
|
45
|
+
if (facts.running)
|
|
46
|
+
fail('SESSION_STILL_RUNNING', 'Agent is still running; explicitly exit it before releasing the runtime');
|
|
47
|
+
if (facts.exists && !facts.shell)
|
|
48
|
+
fail('SESSION_IDENTITY_MISMATCH', 'Runtime is not the verified original empty shell');
|
|
49
|
+
}
|
|
50
|
+
export function assertMessageRuntime(record) {
|
|
51
|
+
if (!record)
|
|
52
|
+
return;
|
|
53
|
+
if (record.state === 'released')
|
|
54
|
+
fail('SESSION_RELEASED', 'Runtime was released; explicitly restore this session before sending');
|
|
55
|
+
if (record.state === 'releasing')
|
|
56
|
+
fail('SESSION_OPERATION_IN_PROGRESS', 'Runtime release is in progress; message was not enqueued');
|
|
57
|
+
if (record.operation_uncertain)
|
|
58
|
+
fail('OPERATION_OUTCOME_UNCONFIRMED', 'Lifecycle outcome needs reconciliation before sending');
|
|
59
|
+
}
|
|
60
|
+
/** Shared release protocol. No Agent idle inference, task result, or UI
|
|
61
|
+
* visibility participates in deciding whether a process may be destroyed. */
|
|
62
|
+
export async function releaseSessionRuntime(adapter, reconcile) {
|
|
63
|
+
await adapter.barrier(async () => {
|
|
64
|
+
if (adapter.pendingMessages())
|
|
65
|
+
fail('SESSION_PENDING_MESSAGES', 'Pending messages appeared before runtime release');
|
|
66
|
+
if (await adapter.exists()) {
|
|
67
|
+
if (reconcile)
|
|
68
|
+
fail('OPERATION_OUTCOME_UNCONFIRMED', 'Original runtime still exists; restart reconciliation will not terminate it');
|
|
69
|
+
await adapter.verifyOwnership();
|
|
70
|
+
const facts = await adapter.inspect();
|
|
71
|
+
if (facts.running)
|
|
72
|
+
fail('SESSION_STILL_RUNNING', 'Agent restarted before release');
|
|
73
|
+
if (!facts.exists || !facts.shell)
|
|
74
|
+
fail('SESSION_IDENTITY_MISMATCH', 'Original runtime is not a verified foreground shell');
|
|
75
|
+
await adapter.destroy();
|
|
76
|
+
}
|
|
77
|
+
if (await adapter.exists())
|
|
78
|
+
fail('OPERATION_OUTCOME_UNCONFIRMED', 'Owned runtime still exists after release');
|
|
79
|
+
await adapter.forgetRuntime();
|
|
80
|
+
});
|
|
81
|
+
}
|