termdock 1.4.233 → 1.4.234
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.desktop-dist/main.js +1 -39
- package/dist/client/assets/{App-CBhXXSRZ.js → App-3pt5-ilC.js} +3 -3
- package/dist/client/assets/App-3pt5-ilC.js.br +0 -0
- package/dist/client/assets/App-3pt5-ilC.js.gz +0 -0
- package/dist/client/assets/{ChangeWalkthroughPanel-CeOq4iWF.js → ChangeWalkthroughPanel-BV8mOdXi.js} +1 -1
- package/dist/client/assets/ChangeWalkthroughPanel-BV8mOdXi.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-BV8mOdXi.js.gz +0 -0
- package/dist/client/assets/{DagPlayground-nzwoofiL.js → DagPlayground-gJS2zbWl.js} +1 -1
- package/dist/client/assets/DagPlayground-gJS2zbWl.js.br +0 -0
- package/dist/client/assets/DagPlayground-gJS2zbWl.js.gz +0 -0
- package/dist/client/assets/{DiffLab-BA5JJf_P.js → DiffLab-DW2zymWD.js} +1 -1
- package/dist/client/assets/DiffLab-DW2zymWD.js.br +0 -0
- package/dist/client/assets/DiffLab-DW2zymWD.js.gz +0 -0
- package/dist/client/assets/{DiffReview-CNnJIPtm.js → DiffReview-9KJ9EdtF.js} +1 -1
- package/dist/client/assets/DiffReview-9KJ9EdtF.js.br +0 -0
- package/dist/client/assets/DiffReview-9KJ9EdtF.js.gz +0 -0
- package/dist/client/assets/{DiffReviewLab-CH9k1x7c.js → DiffReviewLab-C1jzKsUR.js} +1 -1
- package/dist/client/assets/DiffReviewLab-C1jzKsUR.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-C1jzKsUR.js.gz +0 -0
- package/dist/client/assets/{DiffViewer-D17tBMWn.js → DiffViewer-QEeO1VeZ.js} +1 -1
- package/dist/client/assets/DiffViewer-QEeO1VeZ.js.br +0 -0
- package/dist/client/assets/DiffViewer-QEeO1VeZ.js.gz +0 -0
- package/dist/client/assets/{ModelPreview-eUZEjzgg.js → ModelPreview-CWgs8gwV.js} +1 -1
- package/dist/client/assets/ModelPreview-CWgs8gwV.js.br +0 -0
- package/dist/client/assets/ModelPreview-CWgs8gwV.js.gz +0 -0
- package/dist/client/assets/{RightSidebar-DQSjBvAA.js → RightSidebar-D4iQycSs.js} +4 -4
- package/dist/client/assets/RightSidebar-D4iQycSs.js.br +0 -0
- package/dist/client/assets/RightSidebar-D4iQycSs.js.gz +0 -0
- package/dist/client/assets/{abnfDiagram-VCTEODGH-CeIsieUL.js → abnfDiagram-VCTEODGH-C68PyHq_.js} +1 -1
- package/dist/client/assets/abnfDiagram-VCTEODGH-C68PyHq_.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-C68PyHq_.js.gz +0 -0
- package/dist/client/assets/app-BU7kQEH9.js +35 -0
- package/dist/client/assets/app-BU7kQEH9.js.br +0 -0
- package/dist/client/assets/app-BU7kQEH9.js.gz +0 -0
- package/dist/client/assets/{arc-B0iWWfl8.js → arc-BFWEvChC.js} +1 -1
- package/dist/client/assets/arc-BFWEvChC.js.br +0 -0
- package/dist/client/assets/arc-BFWEvChC.js.gz +0 -0
- package/dist/client/assets/{architectureDiagram-5GKGNRK7-IV8_iAgW.js → architectureDiagram-5GKGNRK7-CW9uKAFu.js} +1 -1
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CW9uKAFu.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-CW9uKAFu.js.gz +0 -0
- package/dist/client/assets/{blockDiagram-I7D4REHJ-DWdbwev4.js → blockDiagram-I7D4REHJ-C7Toh4ra.js} +1 -1
- package/dist/client/assets/blockDiagram-I7D4REHJ-C7Toh4ra.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-C7Toh4ra.js.gz +0 -0
- package/dist/client/assets/{c4Diagram-7LVT6UL2-DL6qM9qX.js → c4Diagram-7LVT6UL2-C0qVAmVY.js} +1 -1
- package/dist/client/assets/c4Diagram-7LVT6UL2-C0qVAmVY.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-C0qVAmVY.js.gz +0 -0
- package/dist/client/assets/channel-C_cSN9aY.js +1 -0
- package/dist/client/assets/{chunk-2Q5K7J3B-CADcnv26.js → chunk-2Q5K7J3B-C6Nq29YU.js} +1 -1
- package/dist/client/assets/{chunk-5VM5RSS4-Dy92AQjN.js → chunk-5VM5RSS4-CtbqynTp.js} +1 -1
- package/dist/client/assets/{chunk-F27PBJKO-CXMkCSL3.js → chunk-F27PBJKO-BpeUMCig.js} +1 -1
- package/dist/client/assets/chunk-F27PBJKO-BpeUMCig.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-BpeUMCig.js.gz +0 -0
- package/dist/client/assets/{chunk-IMKFNOWR-BHb_PEkZ.js → chunk-IMKFNOWR-CrP5u-oM.js} +1 -1
- package/dist/client/assets/chunk-IMKFNOWR-CrP5u-oM.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-CrP5u-oM.js.gz +0 -0
- package/dist/client/assets/{chunk-JWPE2WC7-B4h9BTig.js → chunk-JWPE2WC7-BKyQo8Ez.js} +1 -1
- package/dist/client/assets/{chunk-POPQ4Y6H-DMc2ZxT_.js → chunk-POPQ4Y6H-C-rOKTS_.js} +1 -1
- package/dist/client/assets/{chunk-SVP7TREG-D2bHA6KO.js → chunk-SVP7TREG-D_el56_y.js} +1 -1
- package/dist/client/assets/chunk-SVP7TREG-D_el56_y.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D_el56_y.js.gz +0 -0
- package/dist/client/assets/{chunk-TICWLB2K-CSV5x8Yz.js → chunk-TICWLB2K-BW4G8nfd.js} +1 -1
- package/dist/client/assets/chunk-TICWLB2K-BW4G8nfd.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-BW4G8nfd.js.gz +0 -0
- package/dist/client/assets/{chunk-XXDRQBXY-CEkyVIOo.js → chunk-XXDRQBXY-Bp0To9IO.js} +1 -1
- package/dist/client/assets/classDiagram-ZZMXUADV-g86YsEDj.js +1 -0
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-g86YsEDj.js +1 -0
- package/dist/client/assets/{cose-bilkent-JH36ORCC-CUiXAbSC.js → cose-bilkent-JH36ORCC-BZopJ5Vk.js} +1 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-BZopJ5Vk.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-BZopJ5Vk.js.gz +0 -0
- package/dist/client/assets/{cynefin-OW5HDTMX-BgqqNrml.js → cynefin-OW5HDTMX-DodGcb-y.js} +1 -1
- package/dist/client/assets/cynefin-OW5HDTMX-DodGcb-y.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-DodGcb-y.js.gz +0 -0
- package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CHJg73sm.js → cynefinDiagram-5FMLGOSQ-BrfmChC2.js} +1 -1
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BrfmChC2.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BrfmChC2.js.gz +0 -0
- package/dist/client/assets/{dagre-GXQ25YYZ-Du6cYIDp.js → dagre-GXQ25YYZ-DDaQVt3_.js} +1 -1
- package/dist/client/assets/dagre-GXQ25YYZ-DDaQVt3_.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-DDaQVt3_.js.gz +0 -0
- package/dist/client/assets/{diagram-S7CK7UJ4-CNHI3hSO.js → diagram-S7CK7UJ4-BdTpDUnW.js} +1 -1
- package/dist/client/assets/diagram-S7CK7UJ4-BdTpDUnW.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-BdTpDUnW.js.gz +0 -0
- package/dist/client/assets/{diagram-UQ7AKVKN-BP2TAlt5.js → diagram-UQ7AKVKN-DCrCcCSc.js} +1 -1
- package/dist/client/assets/diagram-UQ7AKVKN-DCrCcCSc.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-DCrCcCSc.js.gz +0 -0
- package/dist/client/assets/{diagram-VSXAHHWV-BiNbCPl8.js → diagram-VSXAHHWV-CsF1Qn8l.js} +1 -1
- package/dist/client/assets/diagram-VSXAHHWV-CsF1Qn8l.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-CsF1Qn8l.js.gz +0 -0
- package/dist/client/assets/{diagram-VX7I27RA-Doe3bOA3.js → diagram-VX7I27RA-BjGnkc9i.js} +1 -1
- package/dist/client/assets/diagram-VX7I27RA-BjGnkc9i.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-BjGnkc9i.js.gz +0 -0
- package/dist/client/assets/{diagram-Z3DM3KII-CRWmdH0s.js → diagram-Z3DM3KII-DwYFrSyB.js} +1 -1
- package/dist/client/assets/diagram-Z3DM3KII-DwYFrSyB.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-DwYFrSyB.js.gz +0 -0
- package/dist/client/assets/{ebnfDiagram-PWID7BFC-CwItyAmD.js → ebnfDiagram-PWID7BFC-DL_jC7vl.js} +1 -1
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DL_jC7vl.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-DL_jC7vl.js.gz +0 -0
- package/dist/client/assets/{erDiagram-RLTQ6QDP-BWF0eELE.js → erDiagram-RLTQ6QDP-BJRsIcZN.js} +1 -1
- package/dist/client/assets/erDiagram-RLTQ6QDP-BJRsIcZN.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BJRsIcZN.js.gz +0 -0
- package/dist/client/assets/{flowDiagram-HODETNUW-Dz_4CTgZ.js → flowDiagram-HODETNUW-DtVbuyGA.js} +1 -1
- package/dist/client/assets/flowDiagram-HODETNUW-DtVbuyGA.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-DtVbuyGA.js.gz +0 -0
- package/dist/client/assets/{ganttDiagram-EL5Y4UJY-V27-kSI4.js → ganttDiagram-EL5Y4UJY-Cz8ZjyFr.js} +1 -1
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Cz8ZjyFr.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-Cz8ZjyFr.js.gz +0 -0
- package/dist/client/assets/{gestureArbiter-RzRq7gG2.js → gestureArbiter-CYnutT7u.js} +1 -1
- package/dist/client/assets/gestureArbiter-CYnutT7u.js.br +0 -0
- package/dist/client/assets/gestureArbiter-CYnutT7u.js.gz +0 -0
- package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-C9WTgqiD.js → gitGraphDiagram-WWUBYQGX-CW73vifz.js} +1 -1
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CW73vifz.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CW73vifz.js.gz +0 -0
- package/dist/client/assets/{infoDiagram-27XIBGKW-DKp6kmDi.js → infoDiagram-27XIBGKW-DCaqT0R9.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-5VMMS53U-B0UG9H7W.js → ishikawaDiagram-5VMMS53U-DEloHV3_.js} +1 -1
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DEloHV3_.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-DEloHV3_.js.gz +0 -0
- package/dist/client/assets/{journeyDiagram-3NMN7TZE-Cgj9wfeA.js → journeyDiagram-3NMN7TZE-BJ5Ymi6P.js} +1 -1
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BJ5Ymi6P.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-BJ5Ymi6P.js.gz +0 -0
- package/dist/client/assets/{kanban-definition-UXKFOSKX-B0Su1S1B.js → kanban-definition-UXKFOSKX-BA1Md7zM.js} +1 -1
- package/dist/client/assets/kanban-definition-UXKFOSKX-BA1Md7zM.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-BA1Md7zM.js.gz +0 -0
- package/dist/client/assets/{layout-Cte2rCfs.js → layout-BLPB7zap.js} +1 -1
- package/dist/client/assets/layout-BLPB7zap.js.br +0 -0
- package/dist/client/assets/layout-BLPB7zap.js.gz +0 -0
- package/dist/client/assets/{linear-CC59HR-0.js → linear-DQnd3pCW.js} +1 -1
- package/dist/client/assets/linear-DQnd3pCW.js.br +0 -0
- package/dist/client/assets/linear-DQnd3pCW.js.gz +0 -0
- package/dist/client/assets/{mermaid.core-DNvgq7UV.js → mermaid.core-CC0si1rU.js} +5 -5
- package/dist/client/assets/mermaid.core-CC0si1rU.js.br +0 -0
- package/dist/client/assets/mermaid.core-CC0si1rU.js.gz +0 -0
- package/dist/client/assets/{mindmap-definition-YA3MSWOX-CL-Too_c.js → mindmap-definition-YA3MSWOX-xj-LqgQH.js} +1 -1
- package/dist/client/assets/mindmap-definition-YA3MSWOX-xj-LqgQH.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-xj-LqgQH.js.gz +0 -0
- package/dist/client/assets/{passwordBootstrap-DHkPEJte.js → passwordBootstrap-WkTOy597.js} +1 -1
- package/dist/client/assets/passwordBootstrap-WkTOy597.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-WkTOy597.js.gz +0 -0
- package/dist/client/assets/{pegDiagram-XKGWAZYB-DqHkIEjl.js → pegDiagram-XKGWAZYB-C_tvk3BX.js} +1 -1
- package/dist/client/assets/pegDiagram-XKGWAZYB-C_tvk3BX.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-C_tvk3BX.js.gz +0 -0
- package/dist/client/assets/{pieDiagram-E7YTZNPT-6G1ciW1i.js → pieDiagram-E7YTZNPT-DavzgCyE.js} +1 -1
- package/dist/client/assets/pieDiagram-E7YTZNPT-DavzgCyE.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-DavzgCyE.js.gz +0 -0
- package/dist/client/assets/{quadrantDiagram-AXDQQJYC-C08ycYeq.js → quadrantDiagram-AXDQQJYC-OQcqoEdY.js} +1 -1
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-OQcqoEdY.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-OQcqoEdY.js.gz +0 -0
- package/dist/client/assets/{railroadDiagram-O6MQD6OU-BsmprHau.js → railroadDiagram-O6MQD6OU-CKbyYF7J.js} +1 -1
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CKbyYF7J.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-CKbyYF7J.js.gz +0 -0
- package/dist/client/assets/{referenceLongPress-D5zkcISp.js → referenceLongPress-B7xcIddc.js} +1 -1
- package/dist/client/assets/referenceLongPress-B7xcIddc.js.br +0 -0
- package/dist/client/assets/referenceLongPress-B7xcIddc.js.gz +0 -0
- package/dist/client/assets/{requirementDiagram-BXWQKSXE-Ct7OZ0sX.js → requirementDiagram-BXWQKSXE-C8jPUps0.js} +1 -1
- package/dist/client/assets/requirementDiagram-BXWQKSXE-C8jPUps0.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-C8jPUps0.js.gz +0 -0
- package/dist/client/assets/{sankeyDiagram-P5KCCOFB-BcGS-zY5.js → sankeyDiagram-P5KCCOFB-B1XbZMfI.js} +1 -1
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B1XbZMfI.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-B1XbZMfI.js.gz +0 -0
- package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CzGOOMf7.js → sequenceDiagram-WJ2MYXX4-TKmv1K4M.js} +1 -1
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-TKmv1K4M.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-TKmv1K4M.js.gz +0 -0
- package/dist/client/assets/{sizeCapture-INFHLROL-B4nglf-i.js → sizeCapture-INFHLROL-Ca_kB42l.js} +1 -1
- package/dist/client/assets/sizeCapture-INFHLROL-Ca_kB42l.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-Ca_kB42l.js.gz +0 -0
- package/dist/client/assets/{sparkles-D2dbUVEe.js → sparkles-UE5-cQCr.js} +1 -1
- package/dist/client/assets/{stateDiagram-D77RDMKH-DTid36j9.js → stateDiagram-D77RDMKH-dvSeMJMI.js} +1 -1
- package/dist/client/assets/stateDiagram-D77RDMKH-dvSeMJMI.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-dvSeMJMI.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BgD6bkF_.js +1 -0
- package/dist/client/assets/{swimlanes-42K2YHIH-CfBzltgL.js → swimlanes-42K2YHIH-Csz9F-Em.js} +1 -1
- package/dist/client/assets/swimlanes-42K2YHIH-Csz9F-Em.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-Csz9F-Em.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CtJ_HpaR.js +8 -0
- package/dist/client/assets/{timeline-definition-24CTP7MA-ChGIsqiN.js → timeline-definition-24CTP7MA-D_ixo9p2.js} +1 -1
- package/dist/client/assets/timeline-definition-24CTP7MA-D_ixo9p2.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-D_ixo9p2.js.gz +0 -0
- package/dist/client/assets/{triangle-alert-BVxZtbSu.js → triangle-alert-C9EZlPNN.js} +1 -1
- package/dist/client/assets/{vennDiagram-4TSXK5OY-KovgHKV7.js → vennDiagram-4TSXK5OY-Bt3wQ7L4.js} +1 -1
- package/dist/client/assets/vennDiagram-4TSXK5OY-Bt3wQ7L4.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-Bt3wQ7L4.js.gz +0 -0
- package/dist/client/assets/{wardleyDiagram-VM6X3IG4-Br0xO1Ao.js → wardleyDiagram-VM6X3IG4-D-5G4QL_.js} +1 -1
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-D-5G4QL_.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-D-5G4QL_.js.gz +0 -0
- package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BzbDfcqJ.js → xychartDiagram-S5SC5T6Z-BcXJGPmd.js} +1 -1
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BcXJGPmd.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BcXJGPmd.js.gz +0 -0
- package/dist/client/index.html +1 -1
- 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 +36 -7
- package/dist/server/agent/collaborationGroupRoutes.js +6 -2
- package/dist/server/agent/collaborationPeerTransport.js +5 -2
- package/dist/server/agent/collaborationRoutes.js +30 -0
- package/dist/server/agent/collaborationService.js +275 -0
- package/dist/server/agent/collaborationStore.js +10 -0
- package/dist/server/entry.js +14 -1
- package/dist/server/federation/runtime.js +6 -1
- package/dist/server/routes/terminal.js +18 -56
- package/package.json +1 -1
- package/runtime-manifest.json +3 -3
- package/dist/client/assets/App-CBhXXSRZ.js.br +0 -0
- package/dist/client/assets/App-CBhXXSRZ.js.gz +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-CeOq4iWF.js.br +0 -0
- package/dist/client/assets/ChangeWalkthroughPanel-CeOq4iWF.js.gz +0 -0
- package/dist/client/assets/DagPlayground-nzwoofiL.js.br +0 -0
- package/dist/client/assets/DagPlayground-nzwoofiL.js.gz +0 -0
- package/dist/client/assets/DiffLab-BA5JJf_P.js.br +0 -0
- package/dist/client/assets/DiffLab-BA5JJf_P.js.gz +0 -0
- package/dist/client/assets/DiffReview-CNnJIPtm.js.br +0 -0
- package/dist/client/assets/DiffReview-CNnJIPtm.js.gz +0 -0
- package/dist/client/assets/DiffReviewLab-CH9k1x7c.js.br +0 -0
- package/dist/client/assets/DiffReviewLab-CH9k1x7c.js.gz +0 -0
- package/dist/client/assets/DiffViewer-D17tBMWn.js.br +0 -0
- package/dist/client/assets/DiffViewer-D17tBMWn.js.gz +0 -0
- package/dist/client/assets/ModelPreview-eUZEjzgg.js.br +0 -0
- package/dist/client/assets/ModelPreview-eUZEjzgg.js.gz +0 -0
- package/dist/client/assets/RightSidebar-DQSjBvAA.js.br +0 -0
- package/dist/client/assets/RightSidebar-DQSjBvAA.js.gz +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CeIsieUL.js.br +0 -0
- package/dist/client/assets/abnfDiagram-VCTEODGH-CeIsieUL.js.gz +0 -0
- package/dist/client/assets/app-CM_Chr9o.js +0 -35
- package/dist/client/assets/app-CM_Chr9o.js.br +0 -0
- package/dist/client/assets/app-CM_Chr9o.js.gz +0 -0
- package/dist/client/assets/arc-B0iWWfl8.js.br +0 -0
- package/dist/client/assets/arc-B0iWWfl8.js.gz +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-IV8_iAgW.js.br +0 -0
- package/dist/client/assets/architectureDiagram-5GKGNRK7-IV8_iAgW.js.gz +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DWdbwev4.js.br +0 -0
- package/dist/client/assets/blockDiagram-I7D4REHJ-DWdbwev4.js.gz +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DL6qM9qX.js.br +0 -0
- package/dist/client/assets/c4Diagram-7LVT6UL2-DL6qM9qX.js.gz +0 -0
- package/dist/client/assets/channel-CanBPcbJ.js +0 -1
- package/dist/client/assets/chunk-F27PBJKO-CXMkCSL3.js.br +0 -0
- package/dist/client/assets/chunk-F27PBJKO-CXMkCSL3.js.gz +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BHb_PEkZ.js.br +0 -0
- package/dist/client/assets/chunk-IMKFNOWR-BHb_PEkZ.js.gz +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D2bHA6KO.js.br +0 -0
- package/dist/client/assets/chunk-SVP7TREG-D2bHA6KO.js.gz +0 -0
- package/dist/client/assets/chunk-TICWLB2K-CSV5x8Yz.js.br +0 -0
- package/dist/client/assets/chunk-TICWLB2K-CSV5x8Yz.js.gz +0 -0
- package/dist/client/assets/classDiagram-ZZMXUADV-Dbb9V726.js +0 -1
- package/dist/client/assets/classDiagram-v2-VYDZK3BY-Dbb9V726.js +0 -1
- package/dist/client/assets/cose-bilkent-JH36ORCC-CUiXAbSC.js.br +0 -0
- package/dist/client/assets/cose-bilkent-JH36ORCC-CUiXAbSC.js.gz +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BgqqNrml.js.br +0 -0
- package/dist/client/assets/cynefin-OW5HDTMX-BgqqNrml.js.gz +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CHJg73sm.js.br +0 -0
- package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CHJg73sm.js.gz +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Du6cYIDp.js.br +0 -0
- package/dist/client/assets/dagre-GXQ25YYZ-Du6cYIDp.js.gz +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CNHI3hSO.js.br +0 -0
- package/dist/client/assets/diagram-S7CK7UJ4-CNHI3hSO.js.gz +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BP2TAlt5.js.br +0 -0
- package/dist/client/assets/diagram-UQ7AKVKN-BP2TAlt5.js.gz +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BiNbCPl8.js.br +0 -0
- package/dist/client/assets/diagram-VSXAHHWV-BiNbCPl8.js.gz +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Doe3bOA3.js.br +0 -0
- package/dist/client/assets/diagram-VX7I27RA-Doe3bOA3.js.gz +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CRWmdH0s.js.br +0 -0
- package/dist/client/assets/diagram-Z3DM3KII-CRWmdH0s.js.gz +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-CwItyAmD.js.br +0 -0
- package/dist/client/assets/ebnfDiagram-PWID7BFC-CwItyAmD.js.gz +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BWF0eELE.js.br +0 -0
- package/dist/client/assets/erDiagram-RLTQ6QDP-BWF0eELE.js.gz +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Dz_4CTgZ.js.br +0 -0
- package/dist/client/assets/flowDiagram-HODETNUW-Dz_4CTgZ.js.gz +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-V27-kSI4.js.br +0 -0
- package/dist/client/assets/ganttDiagram-EL5Y4UJY-V27-kSI4.js.gz +0 -0
- package/dist/client/assets/gestureArbiter-RzRq7gG2.js.br +0 -0
- package/dist/client/assets/gestureArbiter-RzRq7gG2.js.gz +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C9WTgqiD.js.br +0 -0
- package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C9WTgqiD.js.gz +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-B0UG9H7W.js.br +0 -0
- package/dist/client/assets/ishikawaDiagram-5VMMS53U-B0UG9H7W.js.gz +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Cgj9wfeA.js.br +0 -0
- package/dist/client/assets/journeyDiagram-3NMN7TZE-Cgj9wfeA.js.gz +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-B0Su1S1B.js.br +0 -0
- package/dist/client/assets/kanban-definition-UXKFOSKX-B0Su1S1B.js.gz +0 -0
- package/dist/client/assets/layout-Cte2rCfs.js.br +0 -0
- package/dist/client/assets/layout-Cte2rCfs.js.gz +0 -0
- package/dist/client/assets/linear-CC59HR-0.js.br +0 -0
- package/dist/client/assets/linear-CC59HR-0.js.gz +0 -0
- package/dist/client/assets/mermaid.core-DNvgq7UV.js.br +0 -0
- package/dist/client/assets/mermaid.core-DNvgq7UV.js.gz +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CL-Too_c.js.br +0 -0
- package/dist/client/assets/mindmap-definition-YA3MSWOX-CL-Too_c.js.gz +0 -0
- package/dist/client/assets/passwordBootstrap-DHkPEJte.js.br +0 -0
- package/dist/client/assets/passwordBootstrap-DHkPEJte.js.gz +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DqHkIEjl.js.br +0 -0
- package/dist/client/assets/pegDiagram-XKGWAZYB-DqHkIEjl.js.gz +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-6G1ciW1i.js.br +0 -0
- package/dist/client/assets/pieDiagram-E7YTZNPT-6G1ciW1i.js.gz +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-C08ycYeq.js.br +0 -0
- package/dist/client/assets/quadrantDiagram-AXDQQJYC-C08ycYeq.js.gz +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BsmprHau.js.br +0 -0
- package/dist/client/assets/railroadDiagram-O6MQD6OU-BsmprHau.js.gz +0 -0
- package/dist/client/assets/referenceLongPress-D5zkcISp.js.br +0 -0
- package/dist/client/assets/referenceLongPress-D5zkcISp.js.gz +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Ct7OZ0sX.js.br +0 -0
- package/dist/client/assets/requirementDiagram-BXWQKSXE-Ct7OZ0sX.js.gz +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BcGS-zY5.js.br +0 -0
- package/dist/client/assets/sankeyDiagram-P5KCCOFB-BcGS-zY5.js.gz +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CzGOOMf7.js.br +0 -0
- package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CzGOOMf7.js.gz +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B4nglf-i.js.br +0 -0
- package/dist/client/assets/sizeCapture-INFHLROL-B4nglf-i.js.gz +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DTid36j9.js.br +0 -0
- package/dist/client/assets/stateDiagram-D77RDMKH-DTid36j9.js.gz +0 -0
- package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CivC-WBT.js +0 -1
- package/dist/client/assets/swimlanes-42K2YHIH-CfBzltgL.js.br +0 -0
- package/dist/client/assets/swimlanes-42K2YHIH-CfBzltgL.js.gz +0 -0
- package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DNedYJ8P.js +0 -8
- package/dist/client/assets/timeline-definition-24CTP7MA-ChGIsqiN.js.br +0 -0
- package/dist/client/assets/timeline-definition-24CTP7MA-ChGIsqiN.js.gz +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-KovgHKV7.js.br +0 -0
- package/dist/client/assets/vennDiagram-4TSXK5OY-KovgHKV7.js.gz +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Br0xO1Ao.js.br +0 -0
- package/dist/client/assets/wardleyDiagram-VM6X3IG4-Br0xO1Ao.js.gz +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BzbDfcqJ.js.br +0 -0
- package/dist/client/assets/xychartDiagram-S5SC5T6Z-BzbDfcqJ.js.gz +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname } from 'node:path';
|
|
4
|
+
import { CollaborationError } from './collaborationProtocol.js';
|
|
5
|
+
import { COLLAB_NAME_FORBIDDEN } from './collaborationPrompt.js';
|
|
6
|
+
import { remoteSession, validateCollaborationNode } from './collaborationPeerTransport.js';
|
|
7
|
+
const hash = (value) => createHash('sha256').update(value).digest('hex');
|
|
8
|
+
const address = (id) => { if (!id.startsWith('remote:'))
|
|
9
|
+
return null; const parts = id.slice(7).split(':'); if (parts.length !== 2)
|
|
10
|
+
throw new Error('INVALID_SESSION'); return { origin: decodeURIComponent(parts[0]), id: decodeURIComponent(parts[1]) }; };
|
|
11
|
+
const local = (origin, id) => { const a = address(id); return a?.origin === origin ? a.id : id; };
|
|
12
|
+
const mapRoles = (value, map) => value && Object.fromEntries(Object.entries(value).map(([id, item]) => [map(id), item]));
|
|
13
|
+
function mapGroup(group, map) {
|
|
14
|
+
return { ...group, sessionIds: group.sessionIds.map(map), roles: mapRoles(group.roles, map), roleVersions: mapRoles(group.roleVersions, map),
|
|
15
|
+
instructions: group.instructions && { ...group.instructions, updatedBy: map(group.instructions.updatedBy) } };
|
|
16
|
+
}
|
|
17
|
+
/** Sole owner of peer discovery and group replication. UI and CLI call the same
|
|
18
|
+
* local API. Pairing grants collaboration directory/group management only, never
|
|
19
|
+
* generic HTTP, filesystem access or an Agent's credentials. */
|
|
20
|
+
export class CollaborationService {
|
|
21
|
+
options;
|
|
22
|
+
document = { version: 1, peers: [], offers: [] };
|
|
23
|
+
clients = new Map();
|
|
24
|
+
observations = new Map();
|
|
25
|
+
inFlight;
|
|
26
|
+
timer;
|
|
27
|
+
stopped = false;
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
try {
|
|
31
|
+
const data = JSON.parse(readFileSync(options.file, 'utf8'));
|
|
32
|
+
if (data.version !== 1 || !Array.isArray(data.peers) || data.peers.length > 64 || !Array.isArray(data.offers))
|
|
33
|
+
throw new Error('INVALID_DIRECTORY');
|
|
34
|
+
for (const peer of data.peers)
|
|
35
|
+
validateCollaborationNode(peer);
|
|
36
|
+
if (data.origin)
|
|
37
|
+
validateCollaborationNode({ ...options.node(), origin: data.origin });
|
|
38
|
+
this.document = data;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
if (error.code !== 'ENOENT')
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
persist() {
|
|
46
|
+
mkdirSync(dirname(this.options.file), { recursive: true, mode: 0o700 });
|
|
47
|
+
const temp = `${this.options.file}.${process.pid}.tmp`;
|
|
48
|
+
writeFileSync(temp, JSON.stringify(this.document), { mode: 0o600 });
|
|
49
|
+
renameSync(temp, this.options.file);
|
|
50
|
+
}
|
|
51
|
+
node(origin = this.document.origin) { if (!origin)
|
|
52
|
+
throw new Error('COLLABORATION_ORIGIN_REQUIRED'); const node = { ...this.options.node(), origin }; validateCollaborationNode(node); return node; }
|
|
53
|
+
setOrigin(origin) { this.node(origin); if (this.document.origin && this.document.origin !== origin)
|
|
54
|
+
throw new Error('COLLABORATION_ORIGIN_MISMATCH'); this.document.origin = origin; }
|
|
55
|
+
remember(node) {
|
|
56
|
+
validateCollaborationNode(node);
|
|
57
|
+
if (node.serviceId === this.options.node().serviceId || node.origin === this.document.origin)
|
|
58
|
+
throw new Error('CANNOT_PAIR_SELF');
|
|
59
|
+
const old = this.document.peers.find(peer => peer.serviceId === node.serviceId || peer.origin === node.origin);
|
|
60
|
+
if (old && (old.serviceId !== node.serviceId || old.origin !== node.origin || old.caFingerprint256 !== node.caFingerprint256))
|
|
61
|
+
throw new Error('PEER_IDENTITY_CHANGED');
|
|
62
|
+
if (!old) {
|
|
63
|
+
if (this.document.peers.length >= 63)
|
|
64
|
+
throw new Error('PEER_LIMIT');
|
|
65
|
+
this.document.peers.push(node);
|
|
66
|
+
}
|
|
67
|
+
this.persist();
|
|
68
|
+
}
|
|
69
|
+
invite(origin) {
|
|
70
|
+
this.setOrigin(origin);
|
|
71
|
+
const code = randomBytes(32).toString('base64url'), expiresAt = Date.now() + 10 * 60_000;
|
|
72
|
+
this.document.offers = this.document.offers.filter(offer => offer.expiresAt > Date.now()).slice(-15);
|
|
73
|
+
this.document.offers.push({ hash: hash(code), expiresAt });
|
|
74
|
+
this.persist();
|
|
75
|
+
return { version: 1, node: this.node(), code, expiresAt, scope: 'collaboration-directory-and-groups' };
|
|
76
|
+
}
|
|
77
|
+
async accept(origin, invitation) {
|
|
78
|
+
if (invitation?.version !== 1 || !/^[A-Za-z0-9_-]{43}$/.test(invitation.code) || !Number.isFinite(invitation.expiresAt) || invitation.expiresAt <= Date.now())
|
|
79
|
+
throw new Error('PAIRING_EXPIRED_OR_INVALID');
|
|
80
|
+
validateCollaborationNode(invitation.node);
|
|
81
|
+
this.setOrigin(origin);
|
|
82
|
+
const rpc = await this.options.connect(invitation.node);
|
|
83
|
+
try {
|
|
84
|
+
await rpc.request({ type: 'collaboration-service', action: 'pair', code: invitation.code, node: this.node() });
|
|
85
|
+
this.remember(invitation.node);
|
|
86
|
+
void this.refresh();
|
|
87
|
+
return { ok: true, peer: invitation.node };
|
|
88
|
+
}
|
|
89
|
+
finally {
|
|
90
|
+
rpc.close();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
receive(subject, packet) {
|
|
94
|
+
if (packet.action === 'pair') {
|
|
95
|
+
if (typeof packet.code !== 'string' || !/^[A-Za-z0-9_-]{43}$/.test(packet.code))
|
|
96
|
+
throw new Error('PAIRING_EXPIRED_OR_INVALID');
|
|
97
|
+
const codeHash = hash(packet.code);
|
|
98
|
+
const offer = this.document.offers.find(item => item.hash === codeHash && item.expiresAt > Date.now());
|
|
99
|
+
const node = packet.node;
|
|
100
|
+
validateCollaborationNode(node);
|
|
101
|
+
if (!offer || node.serviceId !== subject || offer.acceptedBy && offer.acceptedBy !== subject)
|
|
102
|
+
throw new Error('PAIRING_EXPIRED_OR_INVALID');
|
|
103
|
+
if (offer.acceptedBy && !this.document.peers.some(peer => peer.serviceId === subject))
|
|
104
|
+
throw new Error('PAIRING_REVOKED');
|
|
105
|
+
offer.acceptedBy = subject;
|
|
106
|
+
this.remember(node);
|
|
107
|
+
void this.refresh();
|
|
108
|
+
return { ok: true };
|
|
109
|
+
}
|
|
110
|
+
const peer = this.document.peers.find(node => node.serviceId === subject);
|
|
111
|
+
if (!peer)
|
|
112
|
+
throw new Error('COLLABORATION_PAIRING_REQUIRED');
|
|
113
|
+
if (packet.action === 'directory')
|
|
114
|
+
return this.snapshot(peer.origin);
|
|
115
|
+
if (packet.action === 'group') {
|
|
116
|
+
const canonical = packet.group;
|
|
117
|
+
if (!canonical?.sessionIds?.some(id => address(id)?.origin === peer.origin) && !this.document.replicas?.[canonical?.id]?.includes(peer.origin))
|
|
118
|
+
throw new Error('GROUP_SENDER_NOT_MEMBER');
|
|
119
|
+
this.merge(canonical, packet.nodes);
|
|
120
|
+
return { ok: true };
|
|
121
|
+
}
|
|
122
|
+
throw new Error('COLLABORATION_OPERATION_UNSUPPORTED');
|
|
123
|
+
}
|
|
124
|
+
nodes() { return [...new Map([...this.options.transport.registeredNodes(), ...this.document.peers, this.node()].map(node => [node.origin, node])).values()]; }
|
|
125
|
+
snapshot(peerOrigin) {
|
|
126
|
+
const origin = this.node().origin;
|
|
127
|
+
return { sessions: this.options.sessions(), groups: this.options.store.federationSnapshot().groups
|
|
128
|
+
.filter(group => group.federated && (group.sessionIds.some(id => address(id)?.origin === peerOrigin) || this.document.replicas?.[group.id]?.includes(peerOrigin)))
|
|
129
|
+
.map(group => mapGroup(group, id => address(id) ? id : remoteSession(origin, id))), nodes: this.nodes() };
|
|
130
|
+
}
|
|
131
|
+
merge(canonical, nodes) {
|
|
132
|
+
if (!canonical || typeof canonical.id !== 'string' || !canonical.id.startsWith('cross-') || !Array.isArray(canonical.sessionIds)
|
|
133
|
+
|| canonical.sessionIds.length < 2 || canonical.sessionIds.length > 500 || typeof canonical.name !== 'string' || !canonical.name.trim() || COLLAB_NAME_FORBIDDEN.test(canonical.name)
|
|
134
|
+
|| !Number.isFinite(canonical.updatedAt) || canonical.updatedAt > Date.now() + 60_000 || !Array.isArray(nodes) || nodes.length > 65)
|
|
135
|
+
throw new Error('INVALID_GROUP');
|
|
136
|
+
for (const node of nodes) {
|
|
137
|
+
validateCollaborationNode(node);
|
|
138
|
+
const trusted = this.document.peers.find(peer => peer.serviceId === node.serviceId || peer.origin === node.origin);
|
|
139
|
+
if (trusted && (trusted.serviceId !== node.serviceId || trusted.origin !== node.origin || trusted.caFingerprint256 !== node.caFingerprint256))
|
|
140
|
+
throw new Error('PEER_IDENTITY_CHANGED');
|
|
141
|
+
}
|
|
142
|
+
const self = this.node(), own = nodes.find(node => node.serviceId === self.serviceId);
|
|
143
|
+
if (!own || own.origin !== self.origin || own.caFingerprint256 !== self.caFingerprint256)
|
|
144
|
+
throw new Error('LOCAL_NODE_MISMATCH');
|
|
145
|
+
const existing = this.options.store.getGroup(canonical.id);
|
|
146
|
+
if ((this.document.departed?.[canonical.id] ?? -1) >= canonical.updatedAt)
|
|
147
|
+
return;
|
|
148
|
+
if (!canonical.sessionIds.some(id => address(id)?.origin === self.origin)) {
|
|
149
|
+
(this.document.departed ??= {})[canonical.id] = canonical.updatedAt;
|
|
150
|
+
this.options.store.dropFederatedReplica(canonical.id, canonical.updatedAt);
|
|
151
|
+
this.persist();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
const origins = [...new Set([...(this.document.replicas?.[canonical.id] ?? []), ...canonical.sessionIds.map(id => address(id)?.origin).filter((origin) => !!origin)])];
|
|
155
|
+
if (JSON.stringify(origins) !== JSON.stringify(this.document.replicas?.[canonical.id])) {
|
|
156
|
+
(this.document.replicas ??= {})[canonical.id] = origins;
|
|
157
|
+
this.persist();
|
|
158
|
+
}
|
|
159
|
+
const localIds = new Set(this.options.sessions().map(s => s.sessionId));
|
|
160
|
+
const incoming = { ...mapGroup(canonical, id => local(self.origin, id)), federated: true };
|
|
161
|
+
for (const id of incoming.sessionIds) {
|
|
162
|
+
const remote = address(id);
|
|
163
|
+
if (remote ? !nodes.some(node => node.origin === remote.origin) : !localIds.has(id) && !existing?.sessionIds.includes(id))
|
|
164
|
+
throw new Error('GROUP_MEMBER_UNAVAILABLE');
|
|
165
|
+
}
|
|
166
|
+
incoming.remoteSessions = incoming.sessionIds.filter(id => address(id)).map(id => {
|
|
167
|
+
const a = address(id);
|
|
168
|
+
const observed = this.observations.get(nodes.find(node => node.origin === a.origin).serviceId)?.sessions.find(s => s.sessionId === a.id);
|
|
169
|
+
return { ...observed, sessionId: id, name: observed?.name ?? a.id, cwd: observed?.cwd ?? '', status: 'offline', currentTask: '', capability: observed?.capability ?? '', updatedAt: observed?.updatedAt ?? 0,
|
|
170
|
+
serviceOrigin: a.origin, serviceLabel: a.origin, backendSessionId: null, agentNativeSessionId: null, agent: observed?.agent ?? null };
|
|
171
|
+
});
|
|
172
|
+
this.options.store.mergeFederatedGroup(incoming);
|
|
173
|
+
const saved = this.options.store.getGroup(incoming.id);
|
|
174
|
+
if (saved && !saved.deleted && saved.updatedAt === incoming.updatedAt)
|
|
175
|
+
this.options.transport.configure(saved.id, self.origin, nodes);
|
|
176
|
+
}
|
|
177
|
+
start() { this.timer = setInterval(() => { void this.refresh(); }, 2000); this.timer.unref(); void this.refresh(); }
|
|
178
|
+
close() { this.stopped = true; clearInterval(this.timer); for (const rpc of this.clients.values())
|
|
179
|
+
void rpc.then(client => client.close()).catch(() => { }); this.clients.clear(); }
|
|
180
|
+
async client(peer) {
|
|
181
|
+
let pending = this.clients.get(peer.serviceId);
|
|
182
|
+
if (pending && (await pending).closed) {
|
|
183
|
+
this.clients.delete(peer.serviceId);
|
|
184
|
+
pending = undefined;
|
|
185
|
+
}
|
|
186
|
+
if (!pending) {
|
|
187
|
+
pending = this.options.connect(peer);
|
|
188
|
+
this.clients.set(peer.serviceId, pending);
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
return await pending;
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
this.clients.delete(peer.serviceId);
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
refresh() {
|
|
199
|
+
if (this.stopped || !this.document.origin)
|
|
200
|
+
return Promise.resolve();
|
|
201
|
+
return this.inFlight ??= Promise.all(this.document.peers.map(async (peer) => {
|
|
202
|
+
try {
|
|
203
|
+
const client = await this.client(peer);
|
|
204
|
+
const data = await client.request({ type: 'collaboration-service', action: 'directory' });
|
|
205
|
+
if (!Array.isArray(data.sessions) || data.sessions.length > 5000 || !Array.isArray(data.groups))
|
|
206
|
+
throw new Error('INVALID_DIRECTORY');
|
|
207
|
+
const sessions = data.sessions.filter((s) => s && typeof s.sessionId === 'string' && !s.sessionId.startsWith('remote:'));
|
|
208
|
+
this.observations.set(peer.serviceId, { sessions, checkedAt: Date.now() });
|
|
209
|
+
for (const group of data.groups)
|
|
210
|
+
this.merge(group, data.nodes);
|
|
211
|
+
for (const group of this.options.store.federationSnapshot().groups.filter(g => g.federated && (g.sessionIds.some(id => address(id)?.origin === peer.origin) || this.document.replicas?.[g.id]?.includes(peer.origin)))) {
|
|
212
|
+
const canonical = mapGroup(group, id => address(id) ? id : remoteSession(this.node().origin, id));
|
|
213
|
+
const nodes = this.nodes();
|
|
214
|
+
if (canonical.sessionIds.some(id => !nodes.some(node => node.origin === address(id)?.origin)))
|
|
215
|
+
continue;
|
|
216
|
+
this.merge(canonical, nodes);
|
|
217
|
+
await client.request({ type: 'collaboration-service', action: 'group', group: canonical, nodes });
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
catch (error) {
|
|
221
|
+
this.observations.set(peer.serviceId, { sessions: this.observations.get(peer.serviceId)?.sessions ?? [], checkedAt: Date.now(), error: error instanceof Error ? error.message.slice(0, 240) : 'PEER_UNAVAILABLE' });
|
|
222
|
+
}
|
|
223
|
+
})).then(() => { }).finally(() => { this.inFlight = undefined; });
|
|
224
|
+
}
|
|
225
|
+
directory() {
|
|
226
|
+
const known = new Set(this.document.peers.map(peer => peer.origin));
|
|
227
|
+
const missing = [...new Set(this.options.store.list().flatMap(group => group.sessionIds.map(id => address(id)?.origin).filter((origin) => !!origin && !known.has(origin))))];
|
|
228
|
+
const services = this.document.peers.map(peer => {
|
|
229
|
+
const observed = this.observations.get(peer.serviceId);
|
|
230
|
+
return { origin: peer.origin, serviceId: peer.serviceId, label: peer.origin,
|
|
231
|
+
connected: !!observed && !observed.error && Date.now() - observed.checkedAt < 15_000, error: observed?.error ?? (!observed ? 'CONNECTING' : undefined) };
|
|
232
|
+
});
|
|
233
|
+
return { protocolVersion: 2, sessions: this.document.peers.flatMap(peer => (this.observations.get(peer.serviceId)?.sessions ?? []).map(session => ({ ...session,
|
|
234
|
+
sessionId: remoteSession(peer.origin, session.sessionId), backendSessionId: null, agentNativeSessionId: null, serviceOrigin: peer.origin, serviceLabel: peer.origin,
|
|
235
|
+
serviceConnected: services.find(s => s.origin === peer.origin).connected, serviceCheckedAt: this.observations.get(peer.serviceId)?.checkedAt }))),
|
|
236
|
+
services: [...services, ...missing.map(origin => ({ origin, label: origin, connected: false, error: '两端服务尚未配对;请使用 td collab transport invite / accept 完成一次授权' }))] };
|
|
237
|
+
}
|
|
238
|
+
async save(input) {
|
|
239
|
+
const existing = input.id ? this.options.store.getGroup(input.id) : null;
|
|
240
|
+
if (input.id && (!existing || existing.deleted))
|
|
241
|
+
throw new Error('GROUP_NOT_FOUND');
|
|
242
|
+
if (existing && input.expectedUpdatedAt !== existing.updatedAt)
|
|
243
|
+
throw new CollaborationError('GROUP_CHANGED', '协作组已变化,请刷新后重试', 409);
|
|
244
|
+
if (typeof input.name !== 'string' || !input.name.trim() || COLLAB_NAME_FORBIDDEN.test(input.name) || !Array.isArray(input.sessionIds) || input.sessionIds.some(id => typeof id !== 'string') || new Set(input.sessionIds).size < 2)
|
|
245
|
+
throw new Error('INVALID_GROUP');
|
|
246
|
+
if (!input.sessionIds.some(id => address(id)) && !existing?.federated) {
|
|
247
|
+
if (input.sessionIds.some(id => !this.options.sessions().some(s => s.sessionId === id) && !existing?.sessionIds.includes(id)))
|
|
248
|
+
throw new Error('GROUP_MEMBER_UNAVAILABLE');
|
|
249
|
+
return { group: this.options.store.save(input) };
|
|
250
|
+
}
|
|
251
|
+
if (!this.document.origin)
|
|
252
|
+
throw new Error('COLLABORATION_PAIRING_REQUIRED');
|
|
253
|
+
await this.refresh();
|
|
254
|
+
if (existing && this.options.store.getGroup(existing.id)?.updatedAt !== existing.updatedAt)
|
|
255
|
+
throw new Error('GROUP_CHANGED');
|
|
256
|
+
if (!input.sessionIds.some(id => !address(id)))
|
|
257
|
+
throw new Error('GROUP_MUST_INCLUDE_LOCAL_MEMBER');
|
|
258
|
+
const available = new Set([...this.options.sessions(), ...this.directory().sessions.filter(s => s.serviceConnected)].map(s => s.sessionId));
|
|
259
|
+
if (!Array.isArray(input.sessionIds) || input.sessionIds.some(id => !available.has(id) && !existing?.sessionIds.includes(id)))
|
|
260
|
+
throw new Error('GROUP_MEMBER_UNAVAILABLE');
|
|
261
|
+
const group = { ...existing, id: existing?.federated ? existing.id : `cross-${randomBytes(16).toString('hex')}`, name: input.name,
|
|
262
|
+
sessionIds: [...new Set(input.sessionIds)], createdAt: existing?.createdAt ?? Date.now(), updatedAt: Math.max(Date.now(), (existing?.updatedAt ?? 0) + 1), federated: true };
|
|
263
|
+
const canonical = mapGroup(group, id => address(id) ? id : remoteSession(this.node().origin, id));
|
|
264
|
+
if (canonical.sessionIds.some(id => !this.nodes().some(node => node.origin === address(id)?.origin)))
|
|
265
|
+
throw new Error('COLLABORATION_PAIRING_REQUIRED');
|
|
266
|
+
// Promotion transfers history atomically to the federated group id.
|
|
267
|
+
if (existing && !existing.federated) {
|
|
268
|
+
const promoted = this.options.store.promoteGroup(existing.id, { ...group, remoteSessions: [] }, existing.updatedAt);
|
|
269
|
+
canonical.updatedAt = promoted.updatedAt;
|
|
270
|
+
}
|
|
271
|
+
this.merge(canonical, this.nodes());
|
|
272
|
+
void this.refresh();
|
|
273
|
+
return { group: this.options.store.getGroup(group.id), synchronization: 'background' };
|
|
274
|
+
}
|
|
275
|
+
}
|
|
@@ -404,6 +404,16 @@ export class CollaborationStore {
|
|
|
404
404
|
const messages = this.document.messages.filter((message) => ids.has(message.groupId));
|
|
405
405
|
return { groups, messages, transportDiagnostics: Object.fromEntries(messages.flatMap((message) => this.document.transport?.[message.id] ? [[message.id, this.document.transport[message.id]]] : [])) };
|
|
406
406
|
}
|
|
407
|
+
/** Drop only this replica after its last local member leaves; never broadcast
|
|
408
|
+
* a global deletion of the group that still exists on other services. */
|
|
409
|
+
dropFederatedReplica(id, updatedAt) {
|
|
410
|
+
const group = this.getGroup(id);
|
|
411
|
+
if (!group?.federated || group.updatedAt > updatedAt)
|
|
412
|
+
return;
|
|
413
|
+
this.document.groups = this.document.groups.filter(item => item.id !== id);
|
|
414
|
+
this.document.messages = this.document.messages.filter(item => item.groupId !== id);
|
|
415
|
+
this.persist();
|
|
416
|
+
}
|
|
407
417
|
mergeFederatedGroup(group) {
|
|
408
418
|
validateFederatedGroup(group);
|
|
409
419
|
const existing = this.getGroup(group.id);
|
package/dist/server/entry.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { CollaborationService } from './agent/collaborationService.js';
|
|
1
2
|
import { X509Certificate } from 'node:crypto';
|
|
2
3
|
import { CollaborationPeerTransport, connectCollaborationRpc } from './agent/collaborationPeerTransport.js';
|
|
3
|
-
import { collaborationStore, deliverPeerCollaboration, collaborationLocalActivity } from './routes/terminal.js';
|
|
4
|
+
import { collaborationStore, deliverPeerCollaboration, collaborationLocalActivity, collaborationDirectorySessions } from './routes/terminal.js';
|
|
4
5
|
import { setPushTargetPeerId } from './notifications/pushService.js';
|
|
5
6
|
import { desktopDirectTargets } from './federation/desktopTargets.js';
|
|
6
7
|
import { createOpenAccessRouter } from './federation/openAccess.js';
|
|
@@ -407,6 +408,7 @@ export function startServer(options = {}) {
|
|
|
407
408
|
runtimeMonitor,
|
|
408
409
|
});
|
|
409
410
|
const { server, scheme } = createServerForApp(app, options);
|
|
411
|
+
let collaborationService;
|
|
410
412
|
let collaborationTransport;
|
|
411
413
|
let entrySubjectAllowed = (_subjectId) => false;
|
|
412
414
|
let routeInvitations;
|
|
@@ -445,6 +447,11 @@ export function startServer(options = {}) {
|
|
|
445
447
|
const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
|
|
446
448
|
terminal: handleTerminalWebSocket, control: handleControlWebSocket,
|
|
447
449
|
}, {
|
|
450
|
+
collaborationService: (subjectId, packet) => {
|
|
451
|
+
if (!collaborationService)
|
|
452
|
+
throw new Error('COLLABORATION_UNAVAILABLE');
|
|
453
|
+
return collaborationService.receive(subjectId, packet);
|
|
454
|
+
},
|
|
448
455
|
collaborationExchange: (subjectId, packet) => {
|
|
449
456
|
if (!collaborationTransport)
|
|
450
457
|
throw new Error('COLLABORATION_UNAVAILABLE');
|
|
@@ -516,6 +523,12 @@ export function startServer(options = {}) {
|
|
|
516
523
|
}
|
|
517
524
|
catch { /* System TLS trust remains required. */ }
|
|
518
525
|
app.locals.collaborationNode = { serviceId: runtime.serviceId, ...(caFingerprint256 ? { caFingerprint256 } : {}) };
|
|
526
|
+
collaborationService = new CollaborationService({ file: path.join(homedir(), '.termdock', 'federation', 'collaboration-services.json'),
|
|
527
|
+
store: collaborationStore, transport: collaborationTransport, node: () => app.locals.collaborationNode,
|
|
528
|
+
sessions: collaborationDirectorySessions, connect: peer => connectCollaborationRpc(runtime.identity, peer) });
|
|
529
|
+
app.locals.collaborationService = collaborationService;
|
|
530
|
+
collaborationService.start();
|
|
531
|
+
server.once('close', () => collaborationService?.close());
|
|
519
532
|
collaborationTransport.start();
|
|
520
533
|
server.once('close', () => collaborationTransport?.close());
|
|
521
534
|
entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
|
|
@@ -251,7 +251,12 @@ export async function createFederationRuntime(app, directory, handlers, options
|
|
|
251
251
|
try {
|
|
252
252
|
if (packet.id.length > 128)
|
|
253
253
|
throw new Error('INVALID_ID');
|
|
254
|
-
if (packet.type === 'collaboration-
|
|
254
|
+
if (packet.type === 'collaboration-service') {
|
|
255
|
+
if (!options.collaborationService)
|
|
256
|
+
throw new Error('COLLABORATION_UPGRADE_REQUIRED');
|
|
257
|
+
send({ type: 'result', id: packet.id, ...options.collaborationService(subjectId, packet) });
|
|
258
|
+
}
|
|
259
|
+
else if (packet.type === 'collaboration-exchange') {
|
|
255
260
|
if (!options.collaborationExchange)
|
|
256
261
|
throw new Error('COLLABORATION_UNAVAILABLE');
|
|
257
262
|
send({ type: 'result', id: packet.id, ...options.collaborationExchange(subjectId, packet) });
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { assertPeerRegistrationAuthority } from '../agent/collaborationPeerTransport.js';
|
|
2
1
|
import { progressRoutes } from '../notifications/progressRoutes.js';
|
|
3
2
|
import { ensureNodePty } from '../utils/ensureNodePty.js';
|
|
4
3
|
import { PtySpawnBackoff, PtySpawnDeferredError } from '../utils/ptySpawnBackoff.js';
|
|
@@ -39,7 +38,7 @@ import { isTmuxRecoveryCandidate } from '../utils/tmuxRecoveryCandidate.js';
|
|
|
39
38
|
import { AutomationStore, normalizeAutomationSchedule } from '../agent/automationStore.js';
|
|
40
39
|
import { buildBracketedSubmitBytes, canDeliverPromptToAgent } from '../agent/promptDelivery.js';
|
|
41
40
|
import { collaborationRoutes } from '../agent/collaborationRoutes.js';
|
|
42
|
-
import {
|
|
41
|
+
import { extrasFromBody } from '../agent/collaborationProtocol.js';
|
|
43
42
|
import { CollaborationStore } from '../agent/collaborationStore.js';
|
|
44
43
|
import { COLLAB_NAME_FORBIDDEN, formatCollaborationDelivery } from '../agent/collaborationPrompt.js';
|
|
45
44
|
import { ambiguousIdMessage } from '../agent/collaborationProtocol.js';
|
|
@@ -5476,24 +5475,19 @@ router.delete('/operations/automations/:automationId', (req, res) => {
|
|
|
5476
5475
|
return res.status(404).json({ error: '自动任务不存在' });
|
|
5477
5476
|
res.status(204).send();
|
|
5478
5477
|
});
|
|
5479
|
-
//
|
|
5480
|
-
//
|
|
5481
|
-
router.
|
|
5482
|
-
res.json({
|
|
5483
|
-
sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot) });
|
|
5478
|
+
// The old native/browser relay may still poll after a CLI-only upgrade. It
|
|
5479
|
+
// receives a fast, explicit refusal and cannot duplicate the server worker.
|
|
5480
|
+
router.all(['/operations/collaboration-federation', '/operations/collaboration-peers'], (_req, res) => {
|
|
5481
|
+
res.status(410).json({ code: 'CLIENT_RELAY_REMOVED', error: '协作由服务端处理,请重新加载服务页面' });
|
|
5484
5482
|
});
|
|
5485
|
-
router.
|
|
5486
|
-
|
|
5487
|
-
|
|
5488
|
-
|
|
5489
|
-
|
|
5490
|
-
|
|
5491
|
-
res.json({ ok: true });
|
|
5492
|
-
}
|
|
5493
|
-
catch (error) {
|
|
5494
|
-
res.status(400).json({ error: getErrorMessage(error) });
|
|
5495
|
-
}
|
|
5483
|
+
router.get('/operations/collaboration-directory', (req, res) => {
|
|
5484
|
+
const service = req.app.locals.collaborationService;
|
|
5485
|
+
if (!service)
|
|
5486
|
+
return res.status(503).json({ error: '协作服务正在启动,请稍后重试' });
|
|
5487
|
+
void service.refresh();
|
|
5488
|
+
res.json(service.directory());
|
|
5496
5489
|
});
|
|
5490
|
+
export function collaborationDirectorySessions() { return globalSessionState.sessions.map(orchestrationSessionSnapshot); }
|
|
5497
5491
|
export function collaborationLocalActivity() {
|
|
5498
5492
|
return globalSessionState.sessions.map(record => {
|
|
5499
5493
|
const backendId = collaborationRouting.get(record.sessionId)?.backendSessionId ?? record.backendSessionId;
|
|
@@ -5502,45 +5496,13 @@ export function collaborationLocalActivity() {
|
|
|
5502
5496
|
});
|
|
5503
5497
|
}
|
|
5504
5498
|
export function deliverPeerCollaboration(sessionId) { void tryDeliverCollaborationInbox(sessionId); }
|
|
5505
|
-
router.post('/operations/collaboration-federation', (req, res) => {
|
|
5506
|
-
try {
|
|
5507
|
-
const group = req.body?.group;
|
|
5508
|
-
if (!group || !Array.isArray(group.remoteSessions))
|
|
5509
|
-
throw new Error('跨服务工作组无效');
|
|
5510
|
-
const remoteIds = new Set(group.remoteSessions.map((session) => session.sessionId));
|
|
5511
|
-
const localIds = new Set(globalSessionState.sessions.map((session) => session.sessionId));
|
|
5512
|
-
const existing = collaborationStore.getGroup(group.id);
|
|
5513
|
-
if (req.body.expectedUpdatedAt !== undefined && req.body.expectedUpdatedAt !== existing?.updatedAt) {
|
|
5514
|
-
return res.status(409).json({ code: 'GROUP_CHANGED', error: '协作组已被修改,请重新打开成员管理后再保存' });
|
|
5515
|
-
}
|
|
5516
|
-
const retainedIds = new Set(existing?.sessionIds ?? []);
|
|
5517
|
-
if (!group.deleted && group.sessionIds.some((id) => !localIds.has(id) && !remoteIds.has(id) && !retainedIds.has(id))) {
|
|
5518
|
-
throw new Error('工作组包含不存在的本服务会话,请刷新成员列表');
|
|
5519
|
-
}
|
|
5520
|
-
collaborationStore.mergeFederatedGroup(group);
|
|
5521
|
-
collaborationStore.mergeFederatedMessages(Array.isArray(req.body.messages) ? req.body.messages : []);
|
|
5522
|
-
const fragments = Array.isArray(req.body.fragments) ? req.body.fragments : [];
|
|
5523
|
-
if (fragments.length > 25 || fragments.some((fragment) => fragment.group_id !== group.id))
|
|
5524
|
-
throw new Error('无效消息分片');
|
|
5525
|
-
const fragmentReceipts = fragments.map((fragment) => collaborationStore.acceptFragment(fragment));
|
|
5526
|
-
for (const entry of Array.isArray(req.body.transport) ? req.body.transport : []) {
|
|
5527
|
-
if (collaborationStore.getMessage(entry.message_id)?.groupId !== group.id || !entry.diagnostic || !Number.isFinite(entry.diagnostic.checked_at) || !Number.isInteger(entry.diagnostic.attempt_count) || entry.diagnostic.attempt_count < 0)
|
|
5528
|
-
continue;
|
|
5529
|
-
collaborationStore.recordTransport(entry.message_id, entry.diagnostic);
|
|
5530
|
-
if (entry.failure_reason)
|
|
5531
|
-
collaborationStore.fail(entry.message_id, entry.failure_reason);
|
|
5532
|
-
}
|
|
5533
|
-
for (const id of group.sessionIds)
|
|
5534
|
-
if (localIds.has(id))
|
|
5535
|
-
tryDeliverCollaborationInbox(id);
|
|
5536
|
-
res.json({ protocolVersion: 2, ...collaborationStore.federationSnapshot(), fragmentReceipts });
|
|
5537
|
-
}
|
|
5538
|
-
catch (error) {
|
|
5539
|
-
res.status(400).json({ error: getErrorMessage(error) });
|
|
5540
|
-
}
|
|
5541
|
-
});
|
|
5542
5499
|
router.use('/operations', collaborationGroupRoutes({ store: collaborationStore,
|
|
5543
|
-
sessions: () => globalSessionState.sessions.map(orchestrationSessionSnapshot)
|
|
5500
|
+
sessions: () => globalSessionState.sessions.map(orchestrationSessionSnapshot),
|
|
5501
|
+
save: (req, input) => {
|
|
5502
|
+
if (!req.app.locals.collaborationService)
|
|
5503
|
+
throw new Error('COLLABORATION_UNAVAILABLE');
|
|
5504
|
+
return req.app.locals.collaborationService.save(input);
|
|
5505
|
+
} }));
|
|
5544
5506
|
/** Group id from a URL param: the short id a terminal shows resolves to
|
|
5545
5507
|
* its full id, an ambiguous prefix is refused with 409, and anything else is
|
|
5546
5508
|
* simply "no such group" — the same 404 each route already answers. */
|
package/package.json
CHANGED
package/runtime-manifest.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
3
|
"packageName": "termdock",
|
|
4
|
-
"version": "1.4.
|
|
4
|
+
"version": "1.4.234",
|
|
5
5
|
"runtimeProtocolVersion": 1,
|
|
6
6
|
"minimumDesktopVersion": "1.4.46",
|
|
7
7
|
"nodeMajor": 22,
|
|
8
8
|
"dependencyHash": "sha256-K4KyYJuVoQDpZsBM8ZX1Rw3MddOxAzVdp/cPm0iGZz0=",
|
|
9
|
-
"serverBundleHash": "sha256-
|
|
10
|
-
"clientBundleHash": "sha256-
|
|
9
|
+
"serverBundleHash": "sha256-nhvsmvXOZsSAZPmQTztOUo074PRH4RrLqcMhe5r4kKk=",
|
|
10
|
+
"clientBundleHash": "sha256-NULxnbzNlpIXlrOpTKR4KD9KY3y5ekC2/dn3VWl6AcE=",
|
|
11
11
|
"entrypoint": "dist/server/cli.js",
|
|
12
12
|
"clientEntrypoint": "dist/client/index.html"
|
|
13
13
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|