termdock 1.4.351 → 1.4.353

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.
Files changed (444) hide show
  1. package/dist/client/assets/AndroidMirrorView-C39BgFa_.js +3 -0
  2. package/dist/client/assets/AndroidMirrorView-C39BgFa_.js.br +0 -0
  3. package/dist/client/assets/AndroidMirrorView-C39BgFa_.js.gz +0 -0
  4. package/dist/client/assets/App-AjjtXg93.js +178 -0
  5. package/dist/client/assets/App-AjjtXg93.js.br +0 -0
  6. package/dist/client/assets/App-AjjtXg93.js.gz +0 -0
  7. package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js +9 -0
  8. package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js.br +0 -0
  9. package/dist/client/assets/ArchitecturePanel-Xi_R4Tso.js.gz +0 -0
  10. package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js +2 -0
  11. package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js.br +0 -0
  12. package/dist/client/assets/ChangeWalkthroughPanel-BH1Una8m.js.gz +0 -0
  13. package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js +1 -0
  14. package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js.br +0 -0
  15. package/dist/client/assets/CollaborationMainWorkspace-CA9ZGT5N.js.gz +0 -0
  16. package/dist/client/assets/CollaborationTaskContent-D_NDzA-t.js +2 -0
  17. package/dist/client/assets/ComputerControlView-BGvF5NeQ.js +6 -0
  18. package/dist/client/assets/ComputerControlView-BGvF5NeQ.js.br +0 -0
  19. package/dist/client/assets/ComputerControlView-BGvF5NeQ.js.gz +0 -0
  20. package/dist/client/assets/DagPlayground-CKqB6zap.js +1 -0
  21. package/dist/client/assets/DagPlayground-CKqB6zap.js.br +0 -0
  22. package/dist/client/assets/DagPlayground-CKqB6zap.js.gz +0 -0
  23. package/dist/client/assets/{DiffLab-DFut8ZHi.js → DiffLab-CDt8EP6I.js} +2 -2
  24. package/dist/client/assets/DiffLab-CDt8EP6I.js.br +0 -0
  25. package/dist/client/assets/DiffLab-CDt8EP6I.js.gz +0 -0
  26. package/dist/client/assets/DiffReview-YADkwVd6.js +1 -0
  27. package/dist/client/assets/DiffReview-YADkwVd6.js.br +0 -0
  28. package/dist/client/assets/DiffReview-YADkwVd6.js.gz +0 -0
  29. package/dist/client/assets/{DiffReviewLab-Y9JlI5DQ.js → DiffReviewLab-YqgsRn_K.js} +2 -2
  30. package/dist/client/assets/DiffReviewLab-YqgsRn_K.js.br +0 -0
  31. package/dist/client/assets/DiffReviewLab-YqgsRn_K.js.gz +0 -0
  32. package/dist/client/assets/DiffViewer-CT5_YwM2.js +34 -0
  33. package/dist/client/assets/DiffViewer-CT5_YwM2.js.br +0 -0
  34. package/dist/client/assets/DiffViewer-CT5_YwM2.js.gz +0 -0
  35. package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js +1 -0
  36. package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js.br +0 -0
  37. package/dist/client/assets/GitLoadingSkeleton-DfWzAnA2.js.gz +0 -0
  38. package/dist/client/assets/MarkdownPreview-DHLp5cqt.js +19 -0
  39. package/dist/client/assets/MarkdownPreview-DHLp5cqt.js.br +0 -0
  40. package/dist/client/assets/MarkdownPreview-DHLp5cqt.js.gz +0 -0
  41. package/dist/client/assets/{ModelPreview-Cv6JgunE.js → ModelPreview-DymFzc55.js} +166 -166
  42. package/dist/client/assets/ModelPreview-DymFzc55.js.br +0 -0
  43. package/dist/client/assets/ModelPreview-DymFzc55.js.gz +0 -0
  44. package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js +1 -0
  45. package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js.br +0 -0
  46. package/dist/client/assets/RecordingSaveDialog-CxSN76bp.js.gz +0 -0
  47. package/dist/client/assets/RightSidebar-eIuDnhJn.js +1662 -0
  48. package/dist/client/assets/RightSidebar-eIuDnhJn.js.br +0 -0
  49. package/dist/client/assets/RightSidebar-eIuDnhJn.js.gz +0 -0
  50. package/dist/client/assets/SessionChatView-C5wXAt7h.js +13 -0
  51. package/dist/client/assets/SessionChatView-C5wXAt7h.js.br +0 -0
  52. package/dist/client/assets/SessionChatView-C5wXAt7h.js.gz +0 -0
  53. package/dist/client/assets/SessionChatView-D8GoESjb.css +1 -0
  54. package/dist/client/assets/SessionChatView-D8GoESjb.css.br +0 -0
  55. package/dist/client/assets/SessionChatView-D8GoESjb.css.gz +0 -0
  56. package/dist/client/assets/SessionResourceSidebar-Czai5Bt3.js +2 -0
  57. package/dist/client/assets/SessionResourceSidebar-Czai5Bt3.js.br +0 -0
  58. package/dist/client/assets/SessionResourceSidebar-Czai5Bt3.js.gz +0 -0
  59. package/dist/client/assets/TodoWorkspace-BftnnEU0.js +1 -0
  60. package/dist/client/assets/TodoWorkspace-BftnnEU0.js.br +0 -0
  61. package/dist/client/assets/TodoWorkspace-BftnnEU0.js.gz +0 -0
  62. package/dist/client/assets/{abnfDiagram-VCTEODGH-CcCffdpj.js → abnfDiagram-VCTEODGH-DI1Y-QAP.js} +1 -1
  63. package/dist/client/assets/abnfDiagram-VCTEODGH-DI1Y-QAP.js.br +0 -0
  64. package/dist/client/assets/abnfDiagram-VCTEODGH-DI1Y-QAP.js.gz +0 -0
  65. package/dist/client/assets/app-DvMXSNN7.js +36 -0
  66. package/dist/client/assets/app-DvMXSNN7.js.br +0 -0
  67. package/dist/client/assets/app-DvMXSNN7.js.gz +0 -0
  68. package/dist/client/assets/{arc-CXcCuzb3.js → arc-QdJAJx_D.js} +1 -1
  69. package/dist/client/assets/arc-QdJAJx_D.js.br +0 -0
  70. package/dist/client/assets/arc-QdJAJx_D.js.gz +0 -0
  71. package/dist/client/assets/{architectureDiagram-5GKGNRK7-CmR7whZz.js → architectureDiagram-5GKGNRK7-Bw2CvoGg.js} +1 -1
  72. package/dist/client/assets/architectureDiagram-5GKGNRK7-Bw2CvoGg.js.br +0 -0
  73. package/dist/client/assets/architectureDiagram-5GKGNRK7-Bw2CvoGg.js.gz +0 -0
  74. package/dist/client/assets/arrow-down-CZKuaGwN.js +1 -0
  75. package/dist/client/assets/arrow-up-CYLC6_to.js +1 -0
  76. package/dist/client/assets/{blockDiagram-I7D4REHJ-DODhUpHm.js → blockDiagram-I7D4REHJ-CM2GI6sC.js} +1 -1
  77. package/dist/client/assets/blockDiagram-I7D4REHJ-CM2GI6sC.js.br +0 -0
  78. package/dist/client/assets/blockDiagram-I7D4REHJ-CM2GI6sC.js.gz +0 -0
  79. package/dist/client/assets/{c4Diagram-7LVT6UL2-BF9eEb2Y.js → c4Diagram-7LVT6UL2-BAB2TKr4.js} +1 -1
  80. package/dist/client/assets/c4Diagram-7LVT6UL2-BAB2TKr4.js.br +0 -0
  81. package/dist/client/assets/c4Diagram-7LVT6UL2-BAB2TKr4.js.gz +0 -0
  82. package/dist/client/assets/channel-CcDRt0AM.js +1 -0
  83. package/dist/client/assets/chevron-left-BNby2AjR.js +1 -0
  84. package/dist/client/assets/{chunk-2Q5K7J3B-CB7-8Zex.js → chunk-2Q5K7J3B-DI2pKKMQ.js} +1 -1
  85. package/dist/client/assets/{chunk-5VM5RSS4-8Uxe2lki.js → chunk-5VM5RSS4-D5l5zyYl.js} +1 -1
  86. package/dist/client/assets/{chunk-F27PBJKO-Bv45U-az.js → chunk-F27PBJKO-Japl432v.js} +1 -1
  87. package/dist/client/assets/{chunk-F27PBJKO-Bv45U-az.js.br → chunk-F27PBJKO-Japl432v.js.br} +0 -0
  88. package/dist/client/assets/chunk-F27PBJKO-Japl432v.js.gz +0 -0
  89. package/dist/client/assets/{chunk-IMKFNOWR-CfEErbEd.js → chunk-IMKFNOWR-CDJoLgpw.js} +1 -1
  90. package/dist/client/assets/chunk-IMKFNOWR-CDJoLgpw.js.br +0 -0
  91. package/dist/client/assets/chunk-IMKFNOWR-CDJoLgpw.js.gz +0 -0
  92. package/dist/client/assets/{chunk-JWPE2WC7-CjUqcCSL.js → chunk-JWPE2WC7-BQ2cpPDE.js} +1 -1
  93. package/dist/client/assets/{chunk-POPQ4Y6H-BHbP3O75.js → chunk-POPQ4Y6H-CWjI0yRx.js} +1 -1
  94. package/dist/client/assets/{chunk-SVP7TREG-CbyJW-OT.js → chunk-SVP7TREG-BzEphMpl.js} +1 -1
  95. package/dist/client/assets/chunk-SVP7TREG-BzEphMpl.js.br +0 -0
  96. package/dist/client/assets/chunk-SVP7TREG-BzEphMpl.js.gz +0 -0
  97. package/dist/client/assets/{chunk-TICWLB2K-t93EivmS.js → chunk-TICWLB2K-B2hsw7jN.js} +1 -1
  98. package/dist/client/assets/chunk-TICWLB2K-B2hsw7jN.js.br +0 -0
  99. package/dist/client/assets/chunk-TICWLB2K-B2hsw7jN.js.gz +0 -0
  100. package/dist/client/assets/{chunk-XXDRQBXY-C_Pe8Osb.js → chunk-XXDRQBXY-BEWjHo7s.js} +1 -1
  101. package/dist/client/assets/classDiagram-ZZMXUADV-Dz0qH9Hg.js +1 -0
  102. package/dist/client/assets/classDiagram-v2-VYDZK3BY-Dz0qH9Hg.js +1 -0
  103. package/dist/client/assets/clipboard-6kE6vzDi.js +1 -0
  104. package/dist/client/assets/clipboardText-B7202UNa.js +1 -0
  105. package/dist/client/assets/clipboardText-B7202UNa.js.br +0 -0
  106. package/dist/client/assets/clipboardText-B7202UNa.js.gz +0 -0
  107. package/dist/client/assets/{cose-bilkent-JH36ORCC-CksG0bvP.js → cose-bilkent-JH36ORCC-DpS77pec.js} +1 -1
  108. package/dist/client/assets/cose-bilkent-JH36ORCC-DpS77pec.js.br +0 -0
  109. package/dist/client/assets/cose-bilkent-JH36ORCC-DpS77pec.js.gz +0 -0
  110. package/dist/client/assets/{cynefin-OW5HDTMX-CV3T078G.js → cynefin-OW5HDTMX-D1y5kNwR.js} +1 -1
  111. package/dist/client/assets/cynefin-OW5HDTMX-D1y5kNwR.js.br +0 -0
  112. package/dist/client/assets/cynefin-OW5HDTMX-D1y5kNwR.js.gz +0 -0
  113. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-BQnJabEs.js → cynefinDiagram-5FMLGOSQ-CqTCLyMC.js} +1 -1
  114. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CqTCLyMC.js.br +0 -0
  115. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CqTCLyMC.js.gz +0 -0
  116. package/dist/client/assets/{dagre-GXQ25YYZ-BeIQp5Pv.js → dagre-GXQ25YYZ-DkJ-kRn6.js} +1 -1
  117. package/dist/client/assets/dagre-GXQ25YYZ-DkJ-kRn6.js.br +0 -0
  118. package/dist/client/assets/dagre-GXQ25YYZ-DkJ-kRn6.js.gz +0 -0
  119. package/dist/client/assets/{diagram-S7CK7UJ4-D-Mibxiq.js → diagram-S7CK7UJ4-DenrrE_C.js} +1 -1
  120. package/dist/client/assets/diagram-S7CK7UJ4-DenrrE_C.js.br +0 -0
  121. package/dist/client/assets/diagram-S7CK7UJ4-DenrrE_C.js.gz +0 -0
  122. package/dist/client/assets/{diagram-UQ7AKVKN-eiIMWp9t.js → diagram-UQ7AKVKN-B2ikSD8K.js} +1 -1
  123. package/dist/client/assets/diagram-UQ7AKVKN-B2ikSD8K.js.br +0 -0
  124. package/dist/client/assets/diagram-UQ7AKVKN-B2ikSD8K.js.gz +0 -0
  125. package/dist/client/assets/{diagram-VSXAHHWV-CTVOWtNL.js → diagram-VSXAHHWV-CQcNoZDC.js} +1 -1
  126. package/dist/client/assets/diagram-VSXAHHWV-CQcNoZDC.js.br +0 -0
  127. package/dist/client/assets/diagram-VSXAHHWV-CQcNoZDC.js.gz +0 -0
  128. package/dist/client/assets/{diagram-VX7I27RA-NKKk6uh7.js → diagram-VX7I27RA-Cq3FaI-1.js} +1 -1
  129. package/dist/client/assets/diagram-VX7I27RA-Cq3FaI-1.js.br +0 -0
  130. package/dist/client/assets/diagram-VX7I27RA-Cq3FaI-1.js.gz +0 -0
  131. package/dist/client/assets/{diagram-Z3DM3KII-DKYdBuNs.js → diagram-Z3DM3KII-BcWReLM8.js} +1 -1
  132. package/dist/client/assets/diagram-Z3DM3KII-BcWReLM8.js.br +0 -0
  133. package/dist/client/assets/diagram-Z3DM3KII-BcWReLM8.js.gz +0 -0
  134. package/dist/client/assets/{ebnfDiagram-PWID7BFC-DaM3D5Ed.js → ebnfDiagram-PWID7BFC-DwwFkjaL.js} +1 -1
  135. package/dist/client/assets/ebnfDiagram-PWID7BFC-DwwFkjaL.js.br +0 -0
  136. package/dist/client/assets/ebnfDiagram-PWID7BFC-DwwFkjaL.js.gz +0 -0
  137. package/dist/client/assets/{erDiagram-RLTQ6QDP-Df9kQwWg.js → erDiagram-RLTQ6QDP-B1MNvouo.js} +1 -1
  138. package/dist/client/assets/erDiagram-RLTQ6QDP-B1MNvouo.js.br +0 -0
  139. package/dist/client/assets/erDiagram-RLTQ6QDP-B1MNvouo.js.gz +0 -0
  140. package/dist/client/assets/{flowDiagram-HODETNUW-CWjDvpsN.js → flowDiagram-HODETNUW-B_tIl0Vb.js} +1 -1
  141. package/dist/client/assets/flowDiagram-HODETNUW-B_tIl0Vb.js.br +0 -0
  142. package/dist/client/assets/flowDiagram-HODETNUW-B_tIl0Vb.js.gz +0 -0
  143. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-mKt-CgrH.js → ganttDiagram-EL5Y4UJY-CV0YyZHk.js} +1 -1
  144. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CV0YyZHk.js.br +0 -0
  145. package/dist/client/assets/ganttDiagram-EL5Y4UJY-CV0YyZHk.js.gz +0 -0
  146. package/dist/client/assets/gestureArbiter-DszagC-d.js +1 -0
  147. package/dist/client/assets/gestureArbiter-DszagC-d.js.br +0 -0
  148. package/dist/client/assets/gestureArbiter-DszagC-d.js.gz +0 -0
  149. package/dist/client/assets/git-compare-CGiJqTYP.js +1 -0
  150. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js → gitGraphDiagram-WWUBYQGX-DZvnSoyE.js} +1 -1
  151. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DZvnSoyE.js.br +0 -0
  152. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DZvnSoyE.js.gz +0 -0
  153. package/dist/client/assets/index-CA1fR_jI.css +1 -0
  154. package/dist/client/assets/index-CA1fR_jI.css.br +0 -0
  155. package/dist/client/assets/index-CA1fR_jI.css.gz +0 -0
  156. package/dist/client/assets/{infoDiagram-27XIBGKW-CkniyUX9.js → infoDiagram-27XIBGKW-G1OS50nR.js} +1 -1
  157. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CKo9urjS.js → ishikawaDiagram-5VMMS53U-C0-s6pfy.js} +1 -1
  158. package/dist/client/assets/ishikawaDiagram-5VMMS53U-C0-s6pfy.js.br +0 -0
  159. package/dist/client/assets/ishikawaDiagram-5VMMS53U-C0-s6pfy.js.gz +0 -0
  160. package/dist/client/assets/{journeyDiagram-3NMN7TZE-Cd4jK-oI.js → journeyDiagram-3NMN7TZE-B-9Omdit.js} +1 -1
  161. package/dist/client/assets/journeyDiagram-3NMN7TZE-B-9Omdit.js.br +0 -0
  162. package/dist/client/assets/journeyDiagram-3NMN7TZE-B-9Omdit.js.gz +0 -0
  163. package/dist/client/assets/{kanban-definition-UXKFOSKX-BuulZGO-.js → kanban-definition-UXKFOSKX-BS_qXegp.js} +1 -1
  164. package/dist/client/assets/kanban-definition-UXKFOSKX-BS_qXegp.js.br +0 -0
  165. package/dist/client/assets/kanban-definition-UXKFOSKX-BS_qXegp.js.gz +0 -0
  166. package/dist/client/assets/{layout-BpJ2ofrz.js → layout-0NYin1Qz.js} +1 -1
  167. package/dist/client/assets/{layout-BpJ2ofrz.js.br → layout-0NYin1Qz.js.br} +0 -0
  168. package/dist/client/assets/layout-0NYin1Qz.js.gz +0 -0
  169. package/dist/client/assets/{linear-CaSbGLIA.js → linear-CTVKrsxK.js} +1 -1
  170. package/dist/client/assets/linear-CTVKrsxK.js.br +0 -0
  171. package/dist/client/assets/linear-CTVKrsxK.js.gz +0 -0
  172. package/dist/client/assets/link-C5oPMHMJ.js +8 -0
  173. package/dist/client/assets/link-C5oPMHMJ.js.br +0 -0
  174. package/dist/client/assets/link-C5oPMHMJ.js.gz +0 -0
  175. package/dist/client/assets/list-tree-BPg9QdiM.js +1 -0
  176. package/dist/client/assets/loader-circle-CTtHKNWd.js +1 -0
  177. package/dist/client/assets/maximize-2-32OfQ0kG.js +1 -0
  178. package/dist/client/assets/{mermaid.core-BlkorXd6.js → mermaid.core-BgCZP9ke.js} +5 -5
  179. package/dist/client/assets/mermaid.core-BgCZP9ke.js.br +0 -0
  180. package/dist/client/assets/mermaid.core-BgCZP9ke.js.gz +0 -0
  181. package/dist/client/assets/{mindmap-definition-YA3MSWOX-BqKhK1NJ.js → mindmap-definition-YA3MSWOX-BjSZRjkg.js} +2 -2
  182. package/dist/client/assets/mindmap-definition-YA3MSWOX-BjSZRjkg.js.br +0 -0
  183. package/dist/client/assets/mindmap-definition-YA3MSWOX-BjSZRjkg.js.gz +0 -0
  184. package/dist/client/assets/passwordBootstrap-CXQgj5nw.js +2 -0
  185. package/dist/client/assets/passwordBootstrap-CXQgj5nw.js.br +0 -0
  186. package/dist/client/assets/passwordBootstrap-CXQgj5nw.js.gz +0 -0
  187. package/dist/client/assets/{pegDiagram-XKGWAZYB-BlWgUklx.js → pegDiagram-XKGWAZYB-qDU0sU6m.js} +1 -1
  188. package/dist/client/assets/pegDiagram-XKGWAZYB-qDU0sU6m.js.br +0 -0
  189. package/dist/client/assets/pegDiagram-XKGWAZYB-qDU0sU6m.js.gz +0 -0
  190. package/dist/client/assets/{pieDiagram-E7YTZNPT-Bp32kHHQ.js → pieDiagram-E7YTZNPT-CZF3MEOJ.js} +1 -1
  191. package/dist/client/assets/pieDiagram-E7YTZNPT-CZF3MEOJ.js.br +0 -0
  192. package/dist/client/assets/pieDiagram-E7YTZNPT-CZF3MEOJ.js.gz +0 -0
  193. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-0qeSh9-W.js → quadrantDiagram-AXDQQJYC-DcZW4RMG.js} +1 -1
  194. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DcZW4RMG.js.br +0 -0
  195. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DcZW4RMG.js.gz +0 -0
  196. package/dist/client/assets/quote-CuTMveNr.js +1 -0
  197. package/dist/client/assets/{railroadDiagram-O6MQD6OU-COmWghVD.js → railroadDiagram-O6MQD6OU-DtQROK31.js} +1 -1
  198. package/dist/client/assets/railroadDiagram-O6MQD6OU-DtQROK31.js.br +0 -0
  199. package/dist/client/assets/railroadDiagram-O6MQD6OU-DtQROK31.js.gz +0 -0
  200. package/dist/client/assets/rdpSession-a1ZvAyiW.js +1 -0
  201. package/dist/client/assets/rdpSession-a1ZvAyiW.js.br +0 -0
  202. package/dist/client/assets/rdpSession-a1ZvAyiW.js.gz +0 -0
  203. package/dist/client/assets/{requirementDiagram-BXWQKSXE-CeohDJAC.js → requirementDiagram-BXWQKSXE-B8yR39lm.js} +1 -1
  204. package/dist/client/assets/requirementDiagram-BXWQKSXE-B8yR39lm.js.br +0 -0
  205. package/dist/client/assets/requirementDiagram-BXWQKSXE-B8yR39lm.js.gz +0 -0
  206. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-rA5x9iOw.js → sankeyDiagram-P5KCCOFB-D27zuIFg.js} +1 -1
  207. package/dist/client/assets/sankeyDiagram-P5KCCOFB-D27zuIFg.js.br +0 -0
  208. package/dist/client/assets/sankeyDiagram-P5KCCOFB-D27zuIFg.js.gz +0 -0
  209. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js → sequenceDiagram-WJ2MYXX4-BI3gSsUa.js} +1 -1
  210. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BI3gSsUa.js.br +0 -0
  211. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BI3gSsUa.js.gz +0 -0
  212. package/dist/client/assets/settings-2-GFFo5UwO.js +1 -0
  213. package/dist/client/assets/{sizeCapture-INFHLROL-Dz1ykd8a.js → sizeCapture-INFHLROL-NBzIIRER.js} +1 -1
  214. package/dist/client/assets/sizeCapture-INFHLROL-NBzIIRER.js.br +0 -0
  215. package/dist/client/assets/sizeCapture-INFHLROL-NBzIIRER.js.gz +0 -0
  216. package/dist/client/assets/{sparkles-0HeBLLwx.js → sparkles-ZXBp-KvJ.js} +1 -1
  217. package/dist/client/assets/{stateDiagram-D77RDMKH-DHfjTfLj.js → stateDiagram-D77RDMKH-D-D0FB4D.js} +1 -1
  218. package/dist/client/assets/stateDiagram-D77RDMKH-D-D0FB4D.js.br +0 -0
  219. package/dist/client/assets/stateDiagram-D77RDMKH-D-D0FB4D.js.gz +0 -0
  220. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-Dk-QpCnJ.js +1 -0
  221. package/dist/client/assets/sun-CO5F9zS4.js +1 -0
  222. package/dist/client/assets/{swimlanes-42K2YHIH-CAJkSUVz.js → swimlanes-42K2YHIH-CH_jxbV3.js} +1 -1
  223. package/dist/client/assets/swimlanes-42K2YHIH-CH_jxbV3.js.br +0 -0
  224. package/dist/client/assets/swimlanes-42K2YHIH-CH_jxbV3.js.gz +0 -0
  225. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-C4E5FOdv.js +8 -0
  226. package/dist/client/assets/swiper-DJjq1TYT.js +1 -0
  227. package/dist/client/assets/swiper-DJjq1TYT.js.br +0 -0
  228. package/dist/client/assets/swiper-DJjq1TYT.js.gz +0 -0
  229. package/dist/client/assets/syntaxHighlight-C9PKWUqs.js +4 -0
  230. package/dist/client/assets/syntaxHighlight-C9PKWUqs.js.br +0 -0
  231. package/dist/client/assets/syntaxHighlight-C9PKWUqs.js.gz +0 -0
  232. package/dist/client/assets/{timeline-definition-24CTP7MA-plr9yv12.js → timeline-definition-24CTP7MA-DuiaCBON.js} +1 -1
  233. package/dist/client/assets/timeline-definition-24CTP7MA-DuiaCBON.js.br +0 -0
  234. package/dist/client/assets/timeline-definition-24CTP7MA-DuiaCBON.js.gz +0 -0
  235. package/dist/client/assets/triangle-alert-CuGUScPU.js +1 -0
  236. package/dist/client/assets/unplug-C0pb-BKU.js +1 -0
  237. package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js +1 -0
  238. package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js.br +0 -0
  239. package/dist/client/assets/use-gesture-react.esm-DYpdiE_k.js.gz +0 -0
  240. package/dist/client/assets/v4-EwEgHOG0.js +1 -0
  241. package/dist/client/assets/{vennDiagram-4TSXK5OY-Cz04de3S.js → vennDiagram-4TSXK5OY-DUEZFEzo.js} +1 -1
  242. package/dist/client/assets/vennDiagram-4TSXK5OY-DUEZFEzo.js.br +0 -0
  243. package/dist/client/assets/vennDiagram-4TSXK5OY-DUEZFEzo.js.gz +0 -0
  244. package/dist/client/assets/videoCompression-JAcp0atl.js +3 -0
  245. package/dist/client/assets/videoCompression-JAcp0atl.js.br +0 -0
  246. package/dist/client/assets/videoCompression-JAcp0atl.js.gz +0 -0
  247. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DnT-q6At.js → wardleyDiagram-VM6X3IG4-CnHmIbot.js} +1 -1
  248. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CnHmIbot.js.br +0 -0
  249. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CnHmIbot.js.gz +0 -0
  250. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-C0y08MEX.js → xychartDiagram-S5SC5T6Z-CDAoHQTr.js} +1 -1
  251. package/dist/client/assets/xychartDiagram-S5SC5T6Z-CDAoHQTr.js.br +0 -0
  252. package/dist/client/assets/xychartDiagram-S5SC5T6Z-CDAoHQTr.js.gz +0 -0
  253. package/dist/client/index.html +2 -2
  254. package/dist/client/index.html.br +0 -0
  255. package/dist/client/index.html.gz +0 -0
  256. package/dist/client/sw.js +1 -1
  257. package/dist/client/sw.js.br +0 -0
  258. package/dist/client/sw.js.gz +0 -0
  259. package/dist/server/agent/collaborationDeliveryWorker.js +11 -0
  260. package/dist/server/agent/collaborationPrompt.js +4 -2
  261. package/dist/server/agent/collaborationStore.js +7 -1
  262. package/dist/server/agent/sessionAttachmentRoutes.js +39 -0
  263. package/dist/server/agent/sessionChatRoutes.js +64 -0
  264. package/dist/server/agent/sessionViewModeRoutes.js +72 -0
  265. package/dist/server/android/recording.js +19 -17
  266. package/dist/server/cli.js +34 -0
  267. package/dist/server/entry.js +25 -17
  268. package/dist/server/federation/accessPolicy.js +8 -3
  269. package/dist/server/federation/authorization.js +26 -3
  270. package/dist/server/federation/invitations.js +27 -6
  271. package/dist/server/federation/requestContext.js +5 -1
  272. package/dist/server/federation/resourceAccess.js +63 -0
  273. package/dist/server/federation/runtime.js +60 -17
  274. package/dist/server/federation/sessionSharing.js +39 -0
  275. package/dist/server/routes/android.js +36 -7
  276. package/dist/server/routes/computer.js +14 -4
  277. package/dist/server/routes/filesystem.js +6 -3
  278. package/dist/server/routes/rdp.js +4 -3
  279. package/dist/server/routes/sharedFiles.js +51 -0
  280. package/dist/server/routes/terminal.js +83 -14
  281. package/dist/server/routes/vncReadOnly.js +134 -0
  282. package/dist/server/shared/shareResources.js +37 -0
  283. package/dist/server/todos/cli.js +91 -0
  284. package/dist/server/todos/routes.js +73 -0
  285. package/dist/server/todos/store.js +240 -0
  286. package/dist/server/todos/types.js +1 -0
  287. package/dist/server/utils/autoUpdate.js +7 -2
  288. package/dist/server/utils/clientSnapshotMaintenance.js +200 -0
  289. package/dist/server/utils/runtimeClient.js +79 -0
  290. package/dist/server/utils/serverCleanup.js +24 -0
  291. package/dist/server/utils/storageMaintenance.js +60 -0
  292. package/dist/server/utils/workspaceCacheMaintenance.js +158 -0
  293. package/package.json +1 -1
  294. package/runtime-manifest.json +3 -3
  295. package/dist/client/assets/App-BiDKeTpx.js +0 -187
  296. package/dist/client/assets/App-BiDKeTpx.js.br +0 -0
  297. package/dist/client/assets/App-BiDKeTpx.js.gz +0 -0
  298. package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js +0 -9
  299. package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js.br +0 -0
  300. package/dist/client/assets/ArchitecturePanel-DfRsqSBx.js.gz +0 -0
  301. package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js +0 -2
  302. package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js.br +0 -0
  303. package/dist/client/assets/ChangeWalkthroughPanel-DksecDp5.js.gz +0 -0
  304. package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js +0 -1
  305. package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js.br +0 -0
  306. package/dist/client/assets/CollaborationMainWorkspace-D-TgDrxX.js.gz +0 -0
  307. package/dist/client/assets/CollaborationTaskContent-Ce6gFw26.js +0 -2
  308. package/dist/client/assets/ComputerControlView-BKwuceeG.js +0 -6
  309. package/dist/client/assets/ComputerControlView-BKwuceeG.js.br +0 -0
  310. package/dist/client/assets/ComputerControlView-BKwuceeG.js.gz +0 -0
  311. package/dist/client/assets/DagPlayground-Cshoqo5l.js +0 -1
  312. package/dist/client/assets/DagPlayground-Cshoqo5l.js.br +0 -0
  313. package/dist/client/assets/DagPlayground-Cshoqo5l.js.gz +0 -0
  314. package/dist/client/assets/DiffLab-DFut8ZHi.js.br +0 -0
  315. package/dist/client/assets/DiffLab-DFut8ZHi.js.gz +0 -0
  316. package/dist/client/assets/DiffReview-DkEi5c3c.js +0 -1
  317. package/dist/client/assets/DiffReview-DkEi5c3c.js.br +0 -0
  318. package/dist/client/assets/DiffReview-DkEi5c3c.js.gz +0 -0
  319. package/dist/client/assets/DiffReviewLab-Y9JlI5DQ.js.br +0 -0
  320. package/dist/client/assets/DiffReviewLab-Y9JlI5DQ.js.gz +0 -0
  321. package/dist/client/assets/DiffViewer-3JGF6U0m.js +0 -37
  322. package/dist/client/assets/DiffViewer-3JGF6U0m.js.br +0 -0
  323. package/dist/client/assets/DiffViewer-3JGF6U0m.js.gz +0 -0
  324. package/dist/client/assets/ModelPreview-Cv6JgunE.js.br +0 -0
  325. package/dist/client/assets/ModelPreview-Cv6JgunE.js.gz +0 -0
  326. package/dist/client/assets/RightSidebar-akYx_nRq.js +0 -1679
  327. package/dist/client/assets/RightSidebar-akYx_nRq.js.br +0 -0
  328. package/dist/client/assets/RightSidebar-akYx_nRq.js.gz +0 -0
  329. package/dist/client/assets/abnfDiagram-VCTEODGH-CcCffdpj.js.br +0 -0
  330. package/dist/client/assets/abnfDiagram-VCTEODGH-CcCffdpj.js.gz +0 -0
  331. package/dist/client/assets/app-BB0CAQ8D.js +0 -35
  332. package/dist/client/assets/app-BB0CAQ8D.js.br +0 -0
  333. package/dist/client/assets/app-BB0CAQ8D.js.gz +0 -0
  334. package/dist/client/assets/arc-CXcCuzb3.js.br +0 -0
  335. package/dist/client/assets/arc-CXcCuzb3.js.gz +0 -0
  336. package/dist/client/assets/architectureDiagram-5GKGNRK7-CmR7whZz.js.br +0 -0
  337. package/dist/client/assets/architectureDiagram-5GKGNRK7-CmR7whZz.js.gz +0 -0
  338. package/dist/client/assets/blockDiagram-I7D4REHJ-DODhUpHm.js.br +0 -0
  339. package/dist/client/assets/blockDiagram-I7D4REHJ-DODhUpHm.js.gz +0 -0
  340. package/dist/client/assets/c4Diagram-7LVT6UL2-BF9eEb2Y.js.br +0 -0
  341. package/dist/client/assets/c4Diagram-7LVT6UL2-BF9eEb2Y.js.gz +0 -0
  342. package/dist/client/assets/channel-BKkTA-X_.js +0 -1
  343. package/dist/client/assets/chunk-F27PBJKO-Bv45U-az.js.gz +0 -0
  344. package/dist/client/assets/chunk-IMKFNOWR-CfEErbEd.js.br +0 -0
  345. package/dist/client/assets/chunk-IMKFNOWR-CfEErbEd.js.gz +0 -0
  346. package/dist/client/assets/chunk-SVP7TREG-CbyJW-OT.js.br +0 -0
  347. package/dist/client/assets/chunk-SVP7TREG-CbyJW-OT.js.gz +0 -0
  348. package/dist/client/assets/chunk-TICWLB2K-t93EivmS.js.br +0 -0
  349. package/dist/client/assets/chunk-TICWLB2K-t93EivmS.js.gz +0 -0
  350. package/dist/client/assets/classDiagram-ZZMXUADV-D9nwA51-.js +0 -1
  351. package/dist/client/assets/classDiagram-v2-VYDZK3BY-D9nwA51-.js +0 -1
  352. package/dist/client/assets/cose-bilkent-JH36ORCC-CksG0bvP.js.br +0 -0
  353. package/dist/client/assets/cose-bilkent-JH36ORCC-CksG0bvP.js.gz +0 -0
  354. package/dist/client/assets/cynefin-OW5HDTMX-CV3T078G.js.br +0 -0
  355. package/dist/client/assets/cynefin-OW5HDTMX-CV3T078G.js.gz +0 -0
  356. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BQnJabEs.js.br +0 -0
  357. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BQnJabEs.js.gz +0 -0
  358. package/dist/client/assets/dagre-GXQ25YYZ-BeIQp5Pv.js.br +0 -0
  359. package/dist/client/assets/dagre-GXQ25YYZ-BeIQp5Pv.js.gz +0 -0
  360. package/dist/client/assets/diagram-S7CK7UJ4-D-Mibxiq.js.br +0 -0
  361. package/dist/client/assets/diagram-S7CK7UJ4-D-Mibxiq.js.gz +0 -0
  362. package/dist/client/assets/diagram-UQ7AKVKN-eiIMWp9t.js.br +0 -0
  363. package/dist/client/assets/diagram-UQ7AKVKN-eiIMWp9t.js.gz +0 -0
  364. package/dist/client/assets/diagram-VSXAHHWV-CTVOWtNL.js.br +0 -0
  365. package/dist/client/assets/diagram-VSXAHHWV-CTVOWtNL.js.gz +0 -0
  366. package/dist/client/assets/diagram-VX7I27RA-NKKk6uh7.js.br +0 -0
  367. package/dist/client/assets/diagram-VX7I27RA-NKKk6uh7.js.gz +0 -0
  368. package/dist/client/assets/diagram-Z3DM3KII-DKYdBuNs.js.br +0 -0
  369. package/dist/client/assets/diagram-Z3DM3KII-DKYdBuNs.js.gz +0 -0
  370. package/dist/client/assets/ebnfDiagram-PWID7BFC-DaM3D5Ed.js.br +0 -0
  371. package/dist/client/assets/ebnfDiagram-PWID7BFC-DaM3D5Ed.js.gz +0 -0
  372. package/dist/client/assets/erDiagram-RLTQ6QDP-Df9kQwWg.js.br +0 -0
  373. package/dist/client/assets/erDiagram-RLTQ6QDP-Df9kQwWg.js.gz +0 -0
  374. package/dist/client/assets/flowDiagram-HODETNUW-CWjDvpsN.js.br +0 -0
  375. package/dist/client/assets/flowDiagram-HODETNUW-CWjDvpsN.js.gz +0 -0
  376. package/dist/client/assets/ganttDiagram-EL5Y4UJY-mKt-CgrH.js.br +0 -0
  377. package/dist/client/assets/ganttDiagram-EL5Y4UJY-mKt-CgrH.js.gz +0 -0
  378. package/dist/client/assets/gestureArbiter-C8TBuoU3.js +0 -1
  379. package/dist/client/assets/gestureArbiter-C8TBuoU3.js.br +0 -0
  380. package/dist/client/assets/gestureArbiter-C8TBuoU3.js.gz +0 -0
  381. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js.br +0 -0
  382. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BqnaVVZ0.js.gz +0 -0
  383. package/dist/client/assets/index-3lPto_1X.css +0 -1
  384. package/dist/client/assets/index-3lPto_1X.css.br +0 -0
  385. package/dist/client/assets/index-3lPto_1X.css.gz +0 -0
  386. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CKo9urjS.js.br +0 -0
  387. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CKo9urjS.js.gz +0 -0
  388. package/dist/client/assets/journeyDiagram-3NMN7TZE-Cd4jK-oI.js.br +0 -0
  389. package/dist/client/assets/journeyDiagram-3NMN7TZE-Cd4jK-oI.js.gz +0 -0
  390. package/dist/client/assets/kanban-definition-UXKFOSKX-BuulZGO-.js.br +0 -0
  391. package/dist/client/assets/kanban-definition-UXKFOSKX-BuulZGO-.js.gz +0 -0
  392. package/dist/client/assets/layout-BpJ2ofrz.js.gz +0 -0
  393. package/dist/client/assets/linear-CaSbGLIA.js.br +0 -0
  394. package/dist/client/assets/linear-CaSbGLIA.js.gz +0 -0
  395. package/dist/client/assets/mermaid.core-BlkorXd6.js.br +0 -0
  396. package/dist/client/assets/mermaid.core-BlkorXd6.js.gz +0 -0
  397. package/dist/client/assets/mindmap-definition-YA3MSWOX-BqKhK1NJ.js.br +0 -0
  398. package/dist/client/assets/mindmap-definition-YA3MSWOX-BqKhK1NJ.js.gz +0 -0
  399. package/dist/client/assets/passwordBootstrap-DLUamTjd.js +0 -2
  400. package/dist/client/assets/passwordBootstrap-DLUamTjd.js.br +0 -0
  401. package/dist/client/assets/passwordBootstrap-DLUamTjd.js.gz +0 -0
  402. package/dist/client/assets/pegDiagram-XKGWAZYB-BlWgUklx.js.br +0 -0
  403. package/dist/client/assets/pegDiagram-XKGWAZYB-BlWgUklx.js.gz +0 -0
  404. package/dist/client/assets/pieDiagram-E7YTZNPT-Bp32kHHQ.js.br +0 -0
  405. package/dist/client/assets/pieDiagram-E7YTZNPT-Bp32kHHQ.js.gz +0 -0
  406. package/dist/client/assets/quadrantDiagram-AXDQQJYC-0qeSh9-W.js.br +0 -0
  407. package/dist/client/assets/quadrantDiagram-AXDQQJYC-0qeSh9-W.js.gz +0 -0
  408. package/dist/client/assets/railroadDiagram-O6MQD6OU-COmWghVD.js.br +0 -0
  409. package/dist/client/assets/railroadDiagram-O6MQD6OU-COmWghVD.js.gz +0 -0
  410. package/dist/client/assets/rdpSession-Bbycnmly.js +0 -1
  411. package/dist/client/assets/rdpSession-Bbycnmly.js.br +0 -0
  412. package/dist/client/assets/rdpSession-Bbycnmly.js.gz +0 -0
  413. package/dist/client/assets/referenceLongPress-CQ9ote5C.js +0 -1
  414. package/dist/client/assets/referenceLongPress-CQ9ote5C.js.br +0 -0
  415. package/dist/client/assets/referenceLongPress-CQ9ote5C.js.gz +0 -0
  416. package/dist/client/assets/requirementDiagram-BXWQKSXE-CeohDJAC.js.br +0 -0
  417. package/dist/client/assets/requirementDiagram-BXWQKSXE-CeohDJAC.js.gz +0 -0
  418. package/dist/client/assets/sankeyDiagram-P5KCCOFB-rA5x9iOw.js.br +0 -0
  419. package/dist/client/assets/sankeyDiagram-P5KCCOFB-rA5x9iOw.js.gz +0 -0
  420. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js.br +0 -0
  421. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Cw2BZ0po.js.gz +0 -0
  422. package/dist/client/assets/sizeCapture-INFHLROL-Dz1ykd8a.js.br +0 -0
  423. package/dist/client/assets/sizeCapture-INFHLROL-Dz1ykd8a.js.gz +0 -0
  424. package/dist/client/assets/stateDiagram-D77RDMKH-DHfjTfLj.js.br +0 -0
  425. package/dist/client/assets/stateDiagram-D77RDMKH-DHfjTfLj.js.gz +0 -0
  426. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-B9AqPaxQ.js +0 -1
  427. package/dist/client/assets/swimlanes-42K2YHIH-CAJkSUVz.js.br +0 -0
  428. package/dist/client/assets/swimlanes-42K2YHIH-CAJkSUVz.js.gz +0 -0
  429. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DPbqRuWV.js +0 -8
  430. package/dist/client/assets/timeline-definition-24CTP7MA-plr9yv12.js.br +0 -0
  431. package/dist/client/assets/timeline-definition-24CTP7MA-plr9yv12.js.gz +0 -0
  432. package/dist/client/assets/triangle-alert-ibl6FV9I.js +0 -1
  433. package/dist/client/assets/vennDiagram-4TSXK5OY-Cz04de3S.js.br +0 -0
  434. package/dist/client/assets/vennDiagram-4TSXK5OY-Cz04de3S.js.gz +0 -0
  435. package/dist/client/assets/videoCompression-DCsUD6X6.js +0 -3
  436. package/dist/client/assets/videoCompression-DCsUD6X6.js.br +0 -0
  437. package/dist/client/assets/videoCompression-DCsUD6X6.js.gz +0 -0
  438. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DnT-q6At.js.br +0 -0
  439. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DnT-q6At.js.gz +0 -0
  440. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C0y08MEX.js.br +0 -0
  441. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C0y08MEX.js.gz +0 -0
  442. /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css → swiper-C2J0D0kV.css} +0 -0
  443. /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css.br → swiper-C2J0D0kV.css.br} +0 -0
  444. /package/dist/client/assets/{gestureArbiter-C2J0D0kV.css.gz → swiper-C2J0D0kV.css.gz} +0 -0
@@ -52,10 +52,12 @@ import { isAllowedHost, isUpgradeOriginAllowed, validateHostMiddleware, validate
52
52
  import { getCookieSecurityOptions, setSecureCookieMode } from './utils/cookieSecurity.js';
53
53
  import { requestDeadlineMiddleware } from './utils/requestDeadline.js';
54
54
  import { RuntimeMonitor } from './utils/runtimeMonitor.js';
55
+ import { serverCleanup } from './utils/serverCleanup.js';
55
56
  import { startOnboardingServer, stopOnboardingServer, getOnboardingServerUrl } from './onboardingServer.js';
56
57
  import { CertificateWatcher } from './certificateWatcher.js';
57
58
  import { startTermdockLogMaintenance, writeDiffTraceLog, writeErrorLog, writeJsonLog, writeTextLog, } from './utils/serverLogger.js';
58
- import { indexRetainedClientModules, pinBundledRuntimeClientDist } from './utils/runtimeClient.js';
59
+ import { indexRetainedClientModules, pinBundledRuntimeClientDist, resolveRetainedClientModule } from './utils/runtimeClient.js';
60
+ import { startGeneratedStorageMaintenance } from './utils/storageMaintenance.js';
59
61
  import { installCrashForensics } from './utils/crashForensics.js';
60
62
  import { getTermdockVersion, TERMDOCK_CAPABILITIES, TERMDOCK_PROTOCOL_VERSION, } from './utils/version.js';
61
63
  import { PORT, DEFAULT_HOST } from './config.js';
@@ -363,6 +365,7 @@ export function createApp(options = {}) {
363
365
  app.use('/api/computer', computerRoutes);
364
366
  if (fs.existsSync(bundledClientIndexPath)) {
365
367
  const clientPath = pinBundledRuntimeClientDist(bundledClientDistPath);
368
+ app.locals.runtimeClientPath = clientPath;
366
369
  const compression = createStaticCompressionMiddleware(clientPath);
367
370
  const staticFiles = express.static(clientPath, {
368
371
  setHeaders: (res, filePath, stat) => {
@@ -395,7 +398,7 @@ export function createApp(options = {}) {
395
398
  });
396
399
  const retainedModules = indexRetainedClientModules();
397
400
  app.get(/^\/assets\//, (req, res, next) => {
398
- const retainedPath = retainedModules.get(req.path);
401
+ const retainedPath = resolveRetainedClientModule(retainedModules, req.path);
399
402
  if (!retainedPath) {
400
403
  // Never cache a missing module or return the SPA HTML as JavaScript.
401
404
  res.setHeader('Cache-Control', 'no-store');
@@ -403,7 +406,7 @@ export function createApp(options = {}) {
403
406
  return;
404
407
  }
405
408
  setStaticCacheHeaders(req, res);
406
- res.sendFile(retainedPath, error => {
409
+ res.sendFile(path.basename(retainedPath), { root: path.dirname(retainedPath) }, error => {
407
410
  if (!error)
408
411
  return;
409
412
  if (res.headersSent)
@@ -414,7 +417,7 @@ export function createApp(options = {}) {
414
417
  });
415
418
  app.get(/^(?!\/api(?:\/|$)|\/health$|\/onboarding(?:\/|$)|\/ca(?:\/|$)).*/, (req, res) => {
416
419
  setStaticCacheHeaders(req, res);
417
- res.sendFile(path.join(clientPath, 'index.html'));
420
+ res.sendFile('index.html', { root: clientPath });
418
421
  });
419
422
  }
420
423
  // Never expose parser errors, submitted passwords, or stack traces to callers.
@@ -471,9 +474,14 @@ export function startServer(options = {}) {
471
474
  runtimeMonitor,
472
475
  });
473
476
  const { server, scheme } = createServerForApp(app, options);
477
+ const onServerClose = serverCleanup(server);
478
+ if (typeof app.locals.runtimeClientPath === 'string')
479
+ onServerClose(startGeneratedStorageMaintenance({
480
+ stateDirectory: path.join(homedir(), '.termdock'), clientDirectory: app.locals.runtimeClientPath,
481
+ }));
474
482
  let webTransport;
475
483
  const webTransportDescriptor = () => webTransport?.descriptor();
476
- server.once('close', () => webTransport?.close());
484
+ onServerClose(() => webTransport?.close());
477
485
  const collaborationChannels = new Map();
478
486
  const reverseCollaboration = (id) => [...(collaborationChannels.get(id) ?? [])].find(rpc => !rpc.closed);
479
487
  let collaborationService;
@@ -512,11 +520,11 @@ export function startServer(options = {}) {
512
520
  refreshDirectTargets();
513
521
  const desktopRoutesTimer = setInterval(refreshDirectTargets, 2000);
514
522
  desktopRoutesTimer.unref();
515
- server.once('close', () => { clearInterval(desktopRoutesTimer); for (const target of dynamicDirectTargets.values())
523
+ onServerClose(() => { clearInterval(desktopRoutesTimer); for (const target of dynamicDirectTargets.values())
516
524
  target.close(); });
517
- server.once('close', () => relayRouter.close());
518
- server.once('close', () => { void stopAllScrcpySessions(); void androidRecordings.stopAll(); });
519
- server.once('close', stopComputerBridges);
525
+ onServerClose(() => relayRouter.close());
526
+ onServerClose(() => { void stopAllScrcpySessions(); void androidRecordings.stopAll(); });
527
+ onServerClose(stopComputerBridges);
520
528
  const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
521
529
  terminal: handleTerminalWebSocket, control: handleControlWebSocket, android: handleAndroidWebSocket, computer: handleComputerWebSocket,
522
530
  }, {
@@ -655,7 +663,7 @@ export function startServer(options = {}) {
655
663
  messages: collaborationStore, taskStore: collaborationTaskStore, tasks: collaborationTasks, peers: collaborationService, sessions: integrationSessionAdapter });
656
664
  await integration.listen();
657
665
  app.locals.integrationRuntime = integration;
658
- server.once('close', () => { void integration?.close(); });
666
+ onServerClose(() => { void integration?.close(); });
659
667
  }
660
668
  catch {
661
669
  await integration?.close();
@@ -663,11 +671,11 @@ export function startServer(options = {}) {
663
671
  }
664
672
  }
665
673
  collaborationTasks.start();
666
- server.once('close', () => collaborationTasks.close());
674
+ onServerClose(() => collaborationTasks.close());
667
675
  collaborationService.start();
668
- server.once('close', () => collaborationService?.close());
676
+ onServerClose(() => collaborationService?.close());
669
677
  collaborationTransport.start();
670
- server.once('close', () => collaborationTransport?.close());
678
+ onServerClose(() => collaborationTransport?.close());
671
679
  entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
672
680
  routeInvitations = new RouteInvitationStore({
673
681
  filePath: path.join(homedir(), '.termdock', 'federation', 'route-invitations.json'), serviceId: runtime.serviceId,
@@ -688,10 +696,10 @@ export function startServer(options = {}) {
688
696
  localOrigin: `${scheme}://${localAuthority}:${port}`, ...(localCa ? { localCa } : {}),
689
697
  });
690
698
  selfRelay.start();
691
- server.once('close', () => selfRelay.close());
699
+ onServerClose(() => selfRelay.close());
692
700
  }).catch(() => console.error('Route invitations unavailable; uninitialized route grants remain denied.'));
693
701
  void federation.catch(() => console.error('Encrypted access initialization failed; business access remains closed.'));
694
- server.once('close', () => { void federation.then(runtime => runtime.close()).catch(() => { }); });
702
+ onServerClose(() => { void federation.then(runtime => runtime.close()).catch(() => { }); });
695
703
  server.headersTimeout = 10_000;
696
704
  server.requestTimeout = 120_000;
697
705
  server.keepAliveTimeout = 5_000;
@@ -699,7 +707,7 @@ export function startServer(options = {}) {
699
707
  server.maxRequestsPerSocket = 1000;
700
708
  server.maxConnections = 1024;
701
709
  server.on('connection', (socket) => runtimeMonitor.trackSocket(socket));
702
- server.once('close', () => runtimeMonitor.stop());
710
+ onServerClose(() => runtimeMonitor.stop());
703
711
  setSecureCookieMode(scheme === 'https');
704
712
  const certWatcher = new CertificateWatcher({
705
713
  enabled: scheme === 'https' && Boolean(options.httpsCertPath && options.httpsKeyPath),
@@ -865,7 +873,7 @@ export function startServer(options = {}) {
865
873
  console.error('Server error:', error);
866
874
  process.exit(1);
867
875
  });
868
- server.on('close', () => {
876
+ onServerClose(() => {
869
877
  stopLogMaintenance();
870
878
  crashForensics.dispose();
871
879
  certWatcher.stop();
@@ -90,13 +90,18 @@ export function resolveAccessRequest(method, path) {
90
90
  return { action: 'file.read' };
91
91
  if (method === 'POST' && route === 'create')
92
92
  return { action: 'service.create-session' };
93
- const session = /^([^/]+)(?:\/(health|attach|stream|input|resize|tmux|restart|agent-resume))?$/.exec(route);
93
+ const attachment = /^([^/]+)\/chat\/attachments$/.exec(route);
94
+ if (method === 'POST' && attachment && !reservedSessionIds.has(attachment[1].toLowerCase()))
95
+ return { action: 'session.input', sessionId: attachment[1] };
96
+ const session = /^([^/]+)(?:\/(health|attach|stream|input|resize|tmux|restart|agent-resume|chat|view-mode))?$/.exec(route);
94
97
  if (session && !reservedSessionIds.has(session[1].toLowerCase())) {
95
98
  const sessionId = session[1];
96
99
  const op = session[2];
97
- if (read && ['health', 'attach', 'stream', 'agent-resume'].includes(op))
100
+ if (read && ['health', 'attach', 'stream', 'agent-resume', 'chat', 'view-mode'].includes(op))
98
101
  return { action: 'session.view', sessionId };
99
- if (method === 'POST' && op === 'input')
102
+ if (method === 'PUT' && op === 'view-mode')
103
+ return { action: 'session.view', sessionId };
104
+ if (method === 'POST' && (op === 'input' || op === 'chat'))
100
105
  return { action: 'session.input', sessionId };
101
106
  if (method === 'POST' && op === 'resize')
102
107
  return { action: 'session.resize', sessionId };
@@ -1,3 +1,4 @@
1
+ import { validShareResources, resourceSubset } from '../shared/shareResources.js';
1
2
  import { randomUUID } from 'node:crypto';
2
3
  import { mkdirSync, readFileSync, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync } from 'node:fs';
3
4
  import { dirname } from 'node:path';
@@ -18,6 +19,7 @@ function validateInput(v) {
18
19
  assert(v.scope.kind === 'sessions' && ids(v.scope.sessionIds), 'session scope');
19
20
  keys(v.scope, ['kind', 'sessionIds']);
20
21
  }
22
+ assert(v.resources === undefined || validShareResources(v.resources), 'shared resources');
21
23
  assert(Array.isArray(v.actions) && v.actions.length > 0 && v.actions.length <= 256 && v.actions.every(action) && new Set(v.actions).size === v.actions.length, 'actions');
22
24
  if (v.scope.kind === 'sessions')
23
25
  assert(v.actions.every(a => a.startsWith('session.')), 'session scope cannot grant service or file capabilities');
@@ -37,7 +39,7 @@ function isSubset(child, parent) {
37
39
  return false;
38
40
  if (parent.scope.kind === 'sessions' && (child.scope.kind !== 'sessions' || !child.scope.sessionIds.every(s => parent.scope.kind === 'sessions' && parent.scope.sessionIds.includes(s))))
39
41
  return false;
40
- if (!parent.actions.includes('service:*') && !child.actions.every(a => parent.actions.includes(a)))
42
+ if (!parent.actions.includes('service:*') && (!child.actions.every(a => parent.actions.includes(a)) || !resourceSubset(child.resources, parent.resources)))
41
43
  return false;
42
44
  for (const key of ['entryServiceIds', 'relayIds']) {
43
45
  const limit = parent.conditions?.[key];
@@ -86,7 +88,7 @@ export class AuthorizationStore {
86
88
  for (const value of data.grants) {
87
89
  validateInput(value);
88
90
  const g = value;
89
- keys(g, ['id', 'serviceId', 'createdAt', 'parentGrantId', 'revokedAt', 'subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'passwordCredential']);
91
+ keys(g, ['id', 'serviceId', 'createdAt', 'parentGrantId', 'revokedAt', 'subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'passwordCredential', 'resources']);
90
92
  assert(id(g.id) && g.serviceId === this.options.serviceId && Number.isSafeInteger(g.createdAt) && g.createdAt >= 0, 'grant metadata');
91
93
  assert(g.parentGrantId === undefined || id(g.parentGrantId), 'parent identity');
92
94
  assert(g.revokedAt === undefined || (Number.isSafeInteger(g.revokedAt) && g.revokedAt >= 0), 'revocation');
@@ -122,7 +124,7 @@ export class AuthorizationStore {
122
124
  }
123
125
  create(input) {
124
126
  validateInput(input);
125
- keys(input, ['subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions']);
127
+ keys(input, ['subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'resources']);
126
128
  const now = this.now();
127
129
  assert(input.expiresAt === undefined || input.expiresAt > now, 'already expired');
128
130
  return { ...structuredClone(input), id: randomUUID(), serviceId: this.options.serviceId, createdAt: now };
@@ -169,6 +171,27 @@ export class AuthorizationStore {
169
171
  return true;
170
172
  }
171
173
  list() { return this.read(); }
174
+ listActive() { const grants = this.read(); return grants.filter(grant => active(grant, grants, this.now(), new Set(), this.options.passwordCredential?.())); }
175
+ /** Narrow only this Session, preserving sibling Session grants and descendant checks. */
176
+ revokeSession(sessionId, normalize = id => id) {
177
+ const grants = this.read();
178
+ let changed = 0;
179
+ for (const grant of grants) {
180
+ if (grant.revokedAt !== undefined || grant.scope.kind !== 'sessions')
181
+ continue;
182
+ const remaining = grant.scope.sessionIds.filter(id => normalize(id) !== sessionId);
183
+ if (remaining.length === grant.scope.sessionIds.length)
184
+ continue;
185
+ changed++;
186
+ if (!remaining.length)
187
+ grant.revokedAt = this.now();
188
+ else
189
+ grant.scope = { kind: 'sessions', sessionIds: remaining };
190
+ }
191
+ if (changed)
192
+ this.save(grants);
193
+ return changed;
194
+ }
172
195
  listEffective(request) {
173
196
  if (!request || !id(request.subjectId) || request.serviceId !== this.options.serviceId)
174
197
  return [];
@@ -1,3 +1,4 @@
1
+ import { validShareResources } from '../shared/shareResources.js';
1
2
  import { createHash, randomBytes, randomUUID } from 'node:crypto';
2
3
  import { mkdirSync, readFileSync, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync, chmodSync } from 'node:fs';
3
4
  import { dirname } from 'node:path';
@@ -8,8 +9,9 @@ const record = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
8
9
  function requireValid(ok) { if (!ok)
9
10
  throw new Error('INVALID_INVITATION'); }
10
11
  export function validateInvitation(input) {
11
- requireValid(record(input) && Object.keys(input).every(k => ['scope', 'actions', 'label', 'expiresAt'].includes(k)));
12
+ requireValid(record(input) && Object.keys(input).every(k => ['scope', 'actions', 'label', 'expiresAt', 'resources'].includes(k)));
12
13
  requireValid(record(input.scope) && Array.isArray(input.actions) && input.actions.length > 0 && new Set(input.actions).size === input.actions.length);
14
+ requireValid(input.resources === undefined || validShareResources(input.resources));
13
15
  const scope = input.scope;
14
16
  if (scope.kind === 'service') {
15
17
  requireValid(Object.keys(scope).length === 1 && ((input.actions.length === 1 && input.actions[0] === 'service:*') || input.actions.every(a => typeof a === 'string' && actions.has(a))));
@@ -50,9 +52,9 @@ export class InvitationStore {
50
52
  requireValid(record(data) && data.version === 1 && data.serviceId === this.options.serviceId && Array.isArray(data.invitations) && data.invitations.length <= 2048);
51
53
  const seen = new Set();
52
54
  for (const item of data.invitations) {
53
- requireValid(record(item) && Object.keys(item).every(k => ['scope', 'actions', 'label', 'expiresAt', 'hash', 'issuerId', 'createdAt', 'invitationExpiresAt', 'consumedAt', 'consumedBy'].includes(k)));
54
- const { scope, actions, label, expiresAt, hash, issuerId, createdAt, invitationExpiresAt, consumedAt, consumedBy } = item;
55
- validateInvitation({ scope, actions, ...(label === undefined ? {} : { label }), ...(expiresAt === undefined ? {} : { expiresAt }) });
55
+ requireValid(record(item) && Object.keys(item).every(k => ['scope', 'actions', 'label', 'expiresAt', 'hash', 'issuerId', 'createdAt', 'invitationExpiresAt', 'consumedAt', 'consumedBy', 'resources'].includes(k)));
56
+ const { scope, actions, resources, label, expiresAt, hash, issuerId, createdAt, invitationExpiresAt, consumedAt, consumedBy } = item;
57
+ validateInvitation({ scope, actions, ...(resources === undefined ? {} : { resources }), ...(label === undefined ? {} : { label }), ...(expiresAt === undefined ? {} : { expiresAt }) });
56
58
  requireValid(typeof hash === 'string' && /^[a-f0-9]{64}$/.test(hash) && !seen.has(hash) && identity(issuerId));
57
59
  seen.add(hash);
58
60
  requireValid(Number.isSafeInteger(createdAt) && Number.isSafeInteger(invitationExpiresAt) && Number(invitationExpiresAt) === Number(createdAt) + TTL);
@@ -87,7 +89,7 @@ export class InvitationStore {
87
89
  listPending() {
88
90
  const now = this.now();
89
91
  return this.read().filter(i => i.consumedAt === undefined && i.invitationExpiresAt > now && (i.expiresAt === undefined || i.expiresAt > now))
90
- .map(({ hash, scope, actions, label, expiresAt, createdAt, invitationExpiresAt }) => ({ id: hash, scope, actions, ...(label ? { label } : {}), ...(expiresAt === undefined ? {} : { expiresAt }), createdAt, invitationExpiresAt }));
92
+ .map(({ hash, scope, actions, resources, label, expiresAt, createdAt, invitationExpiresAt }) => ({ id: hash, scope, actions, ...(resources === undefined ? {} : { resources }), ...(label ? { label } : {}), ...(expiresAt === undefined ? {} : { expiresAt }), createdAt, invitationExpiresAt }));
91
93
  }
92
94
  revoke(id) {
93
95
  const current = this.read();
@@ -98,6 +100,25 @@ export class InvitationStore {
98
100
  this.save(current);
99
101
  return true;
100
102
  }
103
+ revokeSession(sessionId, normalize = id => id) {
104
+ const current = this.read();
105
+ let changed = 0;
106
+ const next = current.filter(invitation => {
107
+ if (invitation.consumedAt !== undefined || invitation.scope.kind !== 'sessions')
108
+ return true;
109
+ const remaining = invitation.scope.sessionIds.filter(id => normalize(id) !== sessionId);
110
+ if (remaining.length === invitation.scope.sessionIds.length)
111
+ return true;
112
+ changed++;
113
+ if (!remaining.length)
114
+ return false;
115
+ invitation.scope = { kind: 'sessions', sessionIds: remaining };
116
+ return true;
117
+ });
118
+ if (changed)
119
+ this.save(next);
120
+ return changed;
121
+ }
101
122
  create(issuerId, input) {
102
123
  requireValid(identity(issuerId));
103
124
  validateInvitation(input);
@@ -129,7 +150,7 @@ export class InvitationStore {
129
150
  invite.consumedAt = now;
130
151
  invite.consumedBy = subjectId;
131
152
  this.save(current);
132
- return { subjectId, scope: structuredClone(invite.scope), actions: [...invite.actions], ...(invite.expiresAt === undefined ? {} : { expiresAt: invite.expiresAt }), canDelegate: false };
153
+ return { subjectId, ...(invite.resources ? { resources: structuredClone(invite.resources) } : {}), scope: structuredClone(invite.scope), actions: [...invite.actions], ...(invite.expiresAt === undefined ? {} : { expiresAt: invite.expiresAt }), canDelegate: false };
133
154
  }
134
155
  subjectLabels() {
135
156
  return Object.fromEntries(this.read().filter(i => i.consumedBy && i.label).map(i => [i.consumedBy, i.label]));
@@ -1,9 +1,13 @@
1
1
  // Object identity is deliberately used instead of a remotely forgeable header.
2
+ const resources = new WeakMap();
3
+ export function encryptedRequestResources(request) { return resources.get(request); }
2
4
  const verified = new WeakSet();
3
5
  const subjects = new WeakMap();
4
6
  const serviceAccess = new WeakSet();
5
- export function markEncryptedRequest(request, subjectId, fullService = false) {
7
+ export function markEncryptedRequest(request, subjectId, fullService = false, shared) {
6
8
  verified.add(request);
9
+ if (shared)
10
+ resources.set(request, shared);
7
11
  if (subjectId)
8
12
  subjects.set(request, subjectId);
9
13
  if (fullService)
@@ -0,0 +1,63 @@
1
+ import { realpathSync, statSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { canonicalApiPath } from './accessPolicy.js';
4
+ export function computerResource(resources, host, protocol = 'vnc', port = protocol === 'rdp' ? 3389 : 5900) {
5
+ const matches = resources.computers?.filter(c => c.host.toLowerCase() === host.toLowerCase() && c.protocol === protocol && c.port === port);
6
+ return matches?.find(c => c.access === 'control') ?? matches?.[0];
7
+ }
8
+ /** Check both the lexical and real path. Symlinks cannot widen a shared root. */
9
+ export function directoryResource(resources, requested, write = false) {
10
+ if (!path.isAbsolute(requested) || requested.includes('\0'))
11
+ return false;
12
+ try {
13
+ const lexical = path.resolve(requested), real = realpathSync(lexical);
14
+ const inside = (root, file) => { const rel = path.relative(root, file); return rel === '' || (!rel.startsWith(`..${path.sep}`) && rel !== '..' && !path.isAbsolute(rel)); };
15
+ return resources.directories?.some(root => (!write || root.access === 'write') && inside(path.resolve(root.path), lexical) && inside(realpathSync(root.path), real)) === true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ export function canonicalShareDirectories(resources) {
22
+ return { ...resources, ...(resources.directories ? { directories: resources.directories.map(root => {
23
+ const canonical = realpathSync(root.path);
24
+ if (!statSync(canonical).isDirectory())
25
+ throw new Error('SHARE_DIRECTORY_UNAVAILABLE');
26
+ return { ...root, path: canonical };
27
+ }) } : {}) };
28
+ }
29
+ export function resourceHttpAllowed(resources, method, requestPath) {
30
+ const pathname = canonicalApiPath(requestPath);
31
+ if (!pathname)
32
+ return false;
33
+ if (method === 'GET' && pathname === '/api/android/devices')
34
+ return !!resources.android?.length;
35
+ if (resources.android?.length && ((['GET', 'POST'].includes(method) && pathname === '/api/android/recordings') || (method === 'POST' && /^\/api\/android\/recordings\/[a-f0-9-]{36}\/(?:stop|save)$/.test(pathname)) || (method === 'DELETE' && /^\/api\/android\/recordings\/[a-f0-9-]{36}$/.test(pathname))))
36
+ return true;
37
+ if (method === 'GET' && pathname === '/api/computer/status')
38
+ return !!resources.computers?.length;
39
+ const url = new URL(requestPath, 'http://inner');
40
+ const op = pathname.slice('/api/terminal/fs/'.length);
41
+ if (['GET', 'HEAD'].includes(method) && op.startsWith('preview/')) {
42
+ const parts = op.slice(8).split('/');
43
+ if (/^[0-9a-f]{32}$/.test(parts[0]))
44
+ parts.shift();
45
+ return directoryResource(resources, '/' + parts.join('/'));
46
+ }
47
+ if (!pathname.startsWith('/api/terminal/fs/'))
48
+ return false;
49
+ // Unregistered Git, shell, native-open and recursive APIs remain unavailable.
50
+ const read = ['GET', 'HEAD'].includes(method) && ['list', 'read', 'blob', 'video', 'download', 'eda-preview', 'eda-inspect'].includes(op);
51
+ const write = (method === 'POST' && op === 'upload') || (method === 'PUT' && op === 'write') || (method === 'DELETE' && op === 'file');
52
+ if (!read && !write)
53
+ return false;
54
+ const key = op === 'upload' ? 'dir' : 'path';
55
+ if (url.searchParams.getAll(key).length !== 1 || !directoryResource(resources, url.searchParams.get(key) ?? '', write))
56
+ return false;
57
+ // Listing can run git check-ignore; its repository is another filesystem input.
58
+ for (const extra of ['gitIgnoreRoot', 'cwd', 'root', 'repoRoot']) {
59
+ if (url.searchParams.has(extra) && (url.searchParams.getAll(extra).length !== 1 || !directoryResource(resources, url.searchParams.get(extra) ?? '')))
60
+ return false;
61
+ }
62
+ return true;
63
+ }
@@ -1,3 +1,7 @@
1
+ import { SessionSharingPresence } from './sessionSharing.js';
2
+ import { collaborationSessionDisplayName } from '../utils/sessionDisplayName.js';
3
+ import { mergeShareResources } from '../shared/shareResources.js';
4
+ import { canonicalShareDirectories, computerResource, resourceHttpAllowed } from './resourceAccess.js';
1
5
  import { CollaborationRpc } from '../agent/collaborationPeerTransport.js';
2
6
  import { PasswordBootstrapServer } from './passwordBootstrap.js';
3
7
  import { DeviceProfiles } from './deviceProfiles.js';
@@ -92,6 +96,7 @@ export async function createFederationRuntime(app, directory, handlers, options
92
96
  const passwordStarts = [];
93
97
  const passwordAttempts = new Map();
94
98
  const store = new AuthorizationStore({ serviceId, filePath: join(directory, 'grants.json'), passwordCredential: getPasswordCredentialFingerprint });
99
+ const sharingPresence = new SessionSharingPresence();
95
100
  const deviceProfiles = new DeviceProfiles(join(directory, 'device-profiles.json'));
96
101
  const deviceNames = new DeviceNames(join(directory, 'device-names.json'));
97
102
  const invitations = new InvitationStore({ serviceId, filePath: join(directory, 'invitations.json') });
@@ -113,7 +118,9 @@ export async function createFederationRuntime(app, directory, handlers, options
113
118
  delete request.headers['x-termdock-device'];
114
119
  const fullService = request.headers['x-termdock-full-service'] === '1';
115
120
  delete request.headers['x-termdock-full-service'];
116
- markEncryptedRequest(request, typeof subject === 'string' ? subject : undefined, fullService);
121
+ const resourcesHeader = request.headers['x-termdock-resources'];
122
+ delete request.headers['x-termdock-resources'];
123
+ markEncryptedRequest(request, typeof subject === 'string' ? subject : undefined, fullService, typeof resourcesHeader === 'string' ? JSON.parse(resourcesHeader) : undefined);
117
124
  app(request, response);
118
125
  });
119
126
  // The outer encrypted consumer can apply backpressure long after a local
@@ -163,10 +170,14 @@ export async function createFederationRuntime(app, directory, handlers, options
163
170
  const stable = stableSessionId(sessionId);
164
171
  return stable !== undefined && store.authorize({ subjectId, serviceId, action, sessionId: stable }).allowed;
165
172
  };
173
+ const resources = (subjectId) => mergeShareResources(store.listEffective({ subjectId, serviceId }).map(g => g.resources));
174
+ const androidAllowed = (subjectId, serial, control = false) => full(subjectId) || resources(subjectId).android?.some(d => d.serial === serial && (!control || d.access === 'control')) === true;
175
+ const computerAllowed = (subjectId, target) => full(subjectId) || !!computerResource(resources(subjectId), target.host, target.protocol, target.port);
166
176
  const full = (subjectId) => open() || store.hasFullServiceAccess({ subjectId, serviceId });
167
177
  let channel;
168
178
  const operations = new Map();
169
179
  const sockets = new Map();
180
+ const presenceKey = randomUUID();
170
181
  let revokeTimer;
171
182
  try {
172
183
  const secured = await secureConnection({ identity, duplex, initiator: false, signal: AbortSignal.timeout(15000) });
@@ -180,23 +191,26 @@ export async function createFederationRuntime(app, directory, handlers, options
180
191
  if (typeof head.method !== 'string' || typeof head.path !== 'string' || !isBusinessApiPath(head.method, head.path))
181
192
  throw new Error('API_NOT_ALLOWED');
182
193
  const policy = resolveAccessRequest(head.method, head.path);
183
- if (!full(subjectId)) {
194
+ if (!full(subjectId) && !resourceHttpAllowed(resources(subjectId), head.method, head.path)) {
184
195
  if (!policy)
185
196
  throw new Error('API_NOT_ALLOWED');
186
197
  check(policy.action, policy.sessionId);
187
198
  }
188
199
  };
189
200
  async function runHttp(id, operation) {
201
+ const scopeStamp = JSON.stringify([full(subjectId), resources(subjectId)]);
190
202
  const sendHttp = (packet) => {
191
203
  if (operations.get(id) !== operation || operation.abort.signal.aborted)
192
204
  throw new Error('CANCELLED');
205
+ if (scopeStamp !== JSON.stringify([full(subjectId), resources(subjectId)]))
206
+ throw new Error('AUTHORIZATION_CHANGED');
193
207
  httpAllowed(operation.head);
194
208
  send(packet);
195
209
  };
196
210
  try {
197
211
  httpAllowed(operation.head);
198
212
  const headers = new Headers({ 'x-termdock-inner': internalToken, 'x-termdock-device': subjectId,
199
- 'x-termdock-full-service': full(subjectId) ? '1' : '0' });
213
+ 'x-termdock-full-service': full(subjectId) ? '1' : '0', 'x-termdock-resources': JSON.stringify(resources(subjectId)) });
200
214
  const supplied = operation.head.headers;
201
215
  if (supplied && typeof supplied === 'object')
202
216
  for (const [name, value] of Object.entries(supplied)) {
@@ -259,9 +273,9 @@ export async function createFederationRuntime(app, directory, handlers, options
259
273
  }
260
274
  }
261
275
  revokeTimer = setInterval(() => {
262
- for (const { socket: logical, sessionId, android, computer } of sockets.values()) {
276
+ for (const { socket: logical, sessionId, android, computer, computerTarget, computerControl } of sockets.values()) {
263
277
  if (android || computer) {
264
- if (!full(subjectId))
278
+ if (android ? !androidAllowed(subjectId, android) : !computerTarget || !computerAllowed(subjectId, computerTarget) || (computerControl && !full(subjectId) && !resources(subjectId).computers?.some(c => c.host.toLowerCase() === computerTarget.host.toLowerCase() && c.protocol === computerTarget.protocol && c.port === computerTarget.port && c.access === 'control')))
265
279
  logical.close(4003, 'Authorization revoked');
266
280
  continue;
267
281
  }
@@ -400,11 +414,26 @@ export async function createFederationRuntime(app, directory, handlers, options
400
414
  return [];
401
415
  seen.add(sessionId);
402
416
  return [{ sessionId, ...(typeof session.sessionId === 'string' ? { sourceSessionId: session.sessionId } : {}),
403
- name: typeof session.name === 'string' ? session.name : sessionId, live: session.live === true,
417
+ name: collaborationSessionDisplayName({ ...session, name: typeof session.name === 'string' ? session.name : sessionId }), live: session.live === true,
404
418
  canWrite: allowed(subjectId, 'session.input', sessionId), canResize: allowed(subjectId, 'session.resize', sessionId) }];
405
419
  });
406
420
  send({ type: 'result', id: packet.id, items });
407
421
  }
422
+ else if (packet.type === 'session-sharing-status') {
423
+ check('authorization.manage');
424
+ const normalize = (id) => options.resolveSessionId?.(id) || id;
425
+ send({ type: 'result', id: packet.id, items: sharingPresence.summary(store.listActive(), invitations.listPending(), normalize) });
426
+ }
427
+ else if (packet.type === 'session-sharing-stop') {
428
+ check('authorization.manage');
429
+ if (typeof packet.sessionId !== 'string' || !packet.sessionId)
430
+ throw new Error('INVALID_SESSION');
431
+ const normalize = (id) => options.resolveSessionId?.(id) || id;
432
+ const sessionId = normalize(packet.sessionId);
433
+ const revoked = store.revokeSession(sessionId, normalize);
434
+ invitations.revokeSession(sessionId, normalize);
435
+ send({ type: 'result', id: packet.id, revoked });
436
+ }
408
437
  else if (packet.type === 'self-relay-ticket') {
409
438
  if (!options.issueSelfRelayTicket)
410
439
  throw new Error('SELF_RELAY_UNAVAILABLE');
@@ -461,10 +490,12 @@ export async function createFederationRuntime(app, directory, handlers, options
461
490
  }
462
491
  else if (packet.type === 'invite-create') {
463
492
  check('authorization.manage');
464
- const invitation = { scope: packet.scope, actions: packet.actions,
493
+ const invitation = { scope: packet.scope, actions: packet.actions, ...(packet.resources === undefined ? {} : { resources: packet.resources }),
465
494
  ...(packet.label === undefined ? {} : { label: packet.label }),
466
495
  ...(packet.expiresAt === undefined ? {} : { expiresAt: packet.expiresAt }) };
467
496
  validateInvitation(invitation);
497
+ if (invitation.resources)
498
+ invitation.resources = canonicalShareDirectories(invitation.resources);
468
499
  if (invitation.scope.kind === 'sessions') {
469
500
  // Bind to the stable client session id, never the transient backend
470
501
  // PTY id: backends are replaced on restart, tmux recovery or
@@ -524,6 +555,9 @@ export async function createFederationRuntime(app, directory, handlers, options
524
555
  if ((context.singleOperation ? operations.size + sockets.size >= 1 : operations.size >= MAX_SERVER_HTTP_REQUESTS) || operations.has(packet.id) || sockets.has(packet.id))
525
556
  throw new Error('STREAM_LIMIT');
526
557
  httpAllowed(packet);
558
+ const requestedSession = resolveAccessRequest(String(packet.method), String(packet.path))?.sessionId;
559
+ if (requestedSession && !full(subjectId))
560
+ sharingPresence.visit(presenceKey, subjectId, stableSessionId(requestedSession) || requestedSession);
527
561
  const upload = !['GET', 'HEAD'].includes(String(packet.method));
528
562
  if (upload && uploadSlots >= 8)
529
563
  throw new Error('UPLOAD_CONCURRENCY_LIMIT');
@@ -587,26 +621,34 @@ export async function createFederationRuntime(app, directory, handlers, options
587
621
  const match = /^\/api\/terminal\/([^/%]+)\/ws$/.exec(url.pathname);
588
622
  const androidMatch = /^\/api\/android\/([^/%]+)\/ws$/.exec(url.pathname);
589
623
  const computer = url.pathname === '/api/computer/ws';
590
- if (match)
624
+ const computerTarget = { host: url.searchParams.get('host') ?? '', protocol: url.searchParams.get('protocol') ?? 'vnc', port: Number(url.searchParams.get('port') ?? (url.searchParams.get('protocol') === 'rdp' ? '3389' : '5900')) };
625
+ const canView = () => androidMatch ? androidAllowed(subjectId, androidMatch[1]) : computer ? computerAllowed(subjectId, computerTarget) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]);
626
+ if (match) {
591
627
  check('session.view', match[1]);
628
+ if (!full(subjectId))
629
+ sharingPresence.visit(presenceKey, subjectId, stableSessionId(match[1]) || match[1]);
630
+ }
592
631
  else if (url.pathname === '/api/control/ws')
593
632
  check('service.view');
594
633
  else if (androidMatch || computer) {
595
- if (!full(subjectId))
634
+ if (!canView())
596
635
  throw new Error('API_NOT_ALLOWED');
597
636
  }
598
637
  else
599
638
  throw new Error('API_NOT_ALLOWED');
600
- const logical = new LogicalSocket(channel, packet.id, () => (androidMatch || computer) ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]), match ? 1024 * 1024 : undefined, !!match && packet.compression === 'gzip');
601
- sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: Boolean(androidMatch), computer,
602
- computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
603
- logical.once('close', () => sockets.delete(packet.id));
639
+ const logical = new LogicalSocket(channel, packet.id, canView, match ? 1024 * 1024 : undefined, !!match && packet.compression === 'gzip');
640
+ sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: androidMatch?.[1], computer, computerTarget: computer ? computerTarget : undefined,
641
+ computerControl: computer && (full(subjectId) || resources(subjectId).computers?.some(c => c.host.toLowerCase() === computerTarget.host.toLowerCase() && c.protocol === computerTarget.protocol && c.port === computerTarget.port && c.access === 'control')), computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
642
+ const shareStreamKey = `${presenceKey}:${packet.id}`;
643
+ if (match && !full(subjectId))
644
+ sharingPresence.openStream(shareStreamKey, presenceKey, subjectId, stableSessionId(match[1]) || match[1]);
645
+ logical.once('close', () => { sockets.delete(packet.id); sharingPresence.closeStream(shareStreamKey); });
604
646
  send({ type: 'ws-ready', id: packet.id });
605
647
  if (match)
606
648
  handlers.terminal(logical, match[1], randomUUID(), {
607
649
  pushClientId: `device:${subjectId}`, sinceSeq: Math.max(0, Number(url.searchParams.get('since')) || 0), streamEpoch: url.searchParams.get('epoch') ?? undefined,
608
650
  flowControl: url.searchParams.get('flow') === '2', outputActive: url.searchParams.get('active') !== '0',
609
- independentTmux: full(subjectId) && url.searchParams.get('transport') === 'tmux-client',
651
+ independentTmux: allowed(subjectId, 'session.resize', match[1]) && url.searchParams.get('transport') === 'tmux-client',
610
652
  }, allowed(subjectId, 'session.resize', match[1]) ? readTerminalHandshakeDimensions(url.searchParams) : undefined);
611
653
  else if (androidMatch) {
612
654
  if (!handlers.android)
@@ -623,7 +665,7 @@ export async function createFederationRuntime(app, directory, handlers, options
623
665
  continue;
624
666
  }
625
667
  handlers.computer(logical, url.searchParams.get('host') ?? '', {
626
- protocol: url.searchParams.get('protocol') ?? undefined, port: url.searchParams.get('port') ?? undefined,
668
+ protocol: computerTarget.protocol, port: String(computerTarget.port), readOnly: !full(subjectId) && computerResource(resources(subjectId), computerTarget.host, computerTarget.protocol, computerTarget.port)?.access !== 'control',
627
669
  });
628
670
  }
629
671
  else
@@ -635,14 +677,14 @@ export async function createFederationRuntime(app, directory, handlers, options
635
677
  throw new Error('INVALID_STREAM');
636
678
  const data = JSON.parse(packet.data);
637
679
  if (item.computer) {
638
- if (!full(subjectId))
680
+ if (!item.computerTarget || !computerAllowed(subjectId, item.computerTarget) || (item.computerControl && !full(subjectId) && !resources(subjectId).computers?.some(c => c.host.toLowerCase() === item.computerTarget.host.toLowerCase() && c.protocol === item.computerTarget.protocol && c.port === item.computerTarget.port && c.access === 'control')))
639
681
  throw new Error('AUTHORIZATION_DENIED');
640
682
  const types = item.computerProtocol === 'rdp' ? ['start', 'instruction', 'ack'] : ['data', 'ack'];
641
683
  if (!types.includes(data?.type))
642
684
  throw new Error('ACTION_NOT_ALLOWED');
643
685
  }
644
686
  else if (item.android) {
645
- if (!full(subjectId))
687
+ if (!androidAllowed(subjectId, item.android, data?.type === 'control' && data?.data !== 'EQ=='))
646
688
  throw new Error('AUTHORIZATION_DENIED');
647
689
  if (!['control', 'ack', 'ping', 'bitrate'].includes(data?.type))
648
690
  throw new Error('ACTION_NOT_ALLOWED');
@@ -678,6 +720,7 @@ export async function createFederationRuntime(app, directory, handlers, options
678
720
  duplex.close?.(new Error('Secure channel closed'));
679
721
  }
680
722
  finally {
723
+ sharingPresence.close(presenceKey);
681
724
  unregisterCollaboration?.();
682
725
  collaborationRpc?.close();
683
726
  clearInterval(revokeTimer);
@@ -0,0 +1,39 @@
1
+ export class SessionSharingPresence {
2
+ connections = new Map();
3
+ visit(connection, device, session) {
4
+ const current = this.connections.get(connection) || { device, sessions: new Map() };
5
+ current.sessions.set(session, Date.now());
6
+ this.connections.set(connection, current);
7
+ }
8
+ streams = new Map();
9
+ openStream(key, connection, device, session) { this.streams.set(key, { connection, device, session }); }
10
+ closeStream(key) { this.streams.delete(key); }
11
+ close(connection) { this.connections.delete(connection); for (const [key, stream] of this.streams)
12
+ if (stream.connection === connection)
13
+ this.streams.delete(key); }
14
+ summary(grants, invitations, normalize) {
15
+ const result = new Map();
16
+ const record = (id) => { const key = normalize(id); let item = result.get(key); if (!item) {
17
+ item = { sessionId: key, devices: new Set(), invitations: 0 };
18
+ result.set(key, item);
19
+ } return item; };
20
+ for (const grant of grants)
21
+ if (grant.scope.kind === 'sessions')
22
+ for (const id of grant.scope.sessionIds)
23
+ record(id).devices.add(grant.subjectId);
24
+ for (const invite of invitations)
25
+ if (invite.scope.kind === 'sessions')
26
+ for (const id of invite.scope.sessionIds)
27
+ record(id).invitations++;
28
+ return [...result.values()].map(item => {
29
+ const online = new Set();
30
+ for (const connection of this.connections.values())
31
+ if (Date.now() - (connection.sessions.get(item.sessionId) || 0) < 15_000 && item.devices.has(connection.device))
32
+ online.add(connection.device);
33
+ for (const stream of this.streams.values())
34
+ if (stream.session === item.sessionId && item.devices.has(stream.device))
35
+ online.add(stream.device);
36
+ return { sessionId: item.sessionId, devices: item.devices.size, connectedDevices: online.size, invitations: item.invitations };
37
+ });
38
+ }
39
+ }