termdock 1.4.173 → 1.4.175

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/.desktop-dist/main.js +204 -7
  2. package/dist/client/assets/App-BXYZyI_k.js +150 -0
  3. package/dist/client/assets/App-BXYZyI_k.js.br +0 -0
  4. package/dist/client/assets/App-BXYZyI_k.js.gz +0 -0
  5. package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js +2 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js.br +0 -0
  7. package/dist/client/assets/ChangeWalkthroughPanel-DeVtCf73.js.gz +0 -0
  8. package/dist/client/assets/DagPlayground-ZIqLZoMo.js +1 -0
  9. package/dist/client/assets/DagPlayground-ZIqLZoMo.js.br +0 -0
  10. package/dist/client/assets/DagPlayground-ZIqLZoMo.js.gz +0 -0
  11. package/dist/client/assets/{DiffLab-xuS6rtpv.js → DiffLab-CSMf8So2.js} +2 -2
  12. package/dist/client/assets/DiffLab-CSMf8So2.js.br +0 -0
  13. package/dist/client/assets/DiffLab-CSMf8So2.js.gz +0 -0
  14. package/dist/client/assets/DiffReview-CE5lW1zQ.js +1 -0
  15. package/dist/client/assets/DiffReview-CE5lW1zQ.js.br +0 -0
  16. package/dist/client/assets/DiffReview-CE5lW1zQ.js.gz +0 -0
  17. package/dist/client/assets/DiffReviewLab-CpUW75fZ.js +2 -0
  18. package/dist/client/assets/DiffReviewLab-CpUW75fZ.js.br +0 -0
  19. package/dist/client/assets/DiffReviewLab-CpUW75fZ.js.gz +0 -0
  20. package/dist/client/assets/{DiffViewer-BQGXUr8t.js → DiffViewer-BomvZ4kq.js} +5 -5
  21. package/dist/client/assets/DiffViewer-BomvZ4kq.js.br +0 -0
  22. package/dist/client/assets/DiffViewer-BomvZ4kq.js.gz +0 -0
  23. package/dist/client/assets/{ModelPreview-BfZzS1_d.js → ModelPreview-BWdZFyhh.js} +3 -3
  24. package/dist/client/assets/ModelPreview-BWdZFyhh.js.br +0 -0
  25. package/dist/client/assets/ModelPreview-BWdZFyhh.js.gz +0 -0
  26. package/dist/client/assets/RightSidebar-CYVwFbOc.js +79 -0
  27. package/dist/client/assets/RightSidebar-CYVwFbOc.js.br +0 -0
  28. package/dist/client/assets/RightSidebar-CYVwFbOc.js.gz +0 -0
  29. package/dist/client/assets/{abnfDiagram-VCTEODGH-Dn_lmPcC.js → abnfDiagram-VCTEODGH-BIPsLStE.js} +1 -1
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-BIPsLStE.js.br +0 -0
  31. package/dist/client/assets/abnfDiagram-VCTEODGH-BIPsLStE.js.gz +0 -0
  32. package/dist/client/assets/app-D6KzR8B3.js +34 -0
  33. package/dist/client/assets/app-D6KzR8B3.js.br +0 -0
  34. package/dist/client/assets/app-D6KzR8B3.js.gz +0 -0
  35. package/dist/client/assets/{arc-DUybtKGZ.js → arc-DzeT6hBj.js} +1 -1
  36. package/dist/client/assets/arc-DzeT6hBj.js.br +0 -0
  37. package/dist/client/assets/arc-DzeT6hBj.js.gz +0 -0
  38. package/dist/client/assets/{architectureDiagram-5GKGNRK7-CLEmOHBp.js → architectureDiagram-5GKGNRK7-B1qsxd_5.js} +1 -1
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-B1qsxd_5.js.br +0 -0
  40. package/dist/client/assets/architectureDiagram-5GKGNRK7-B1qsxd_5.js.gz +0 -0
  41. package/dist/client/assets/{blockDiagram-I7D4REHJ-O_XM5b9_.js → blockDiagram-I7D4REHJ-NLsa29Jp.js} +1 -1
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-NLsa29Jp.js.br +0 -0
  43. package/dist/client/assets/blockDiagram-I7D4REHJ-NLsa29Jp.js.gz +0 -0
  44. package/dist/client/assets/{c4Diagram-7LVT6UL2-DlUEslcV.js → c4Diagram-7LVT6UL2-DBAUvou3.js} +1 -1
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-DBAUvou3.js.br +0 -0
  46. package/dist/client/assets/c4Diagram-7LVT6UL2-DBAUvou3.js.gz +0 -0
  47. package/dist/client/assets/channel-BhwBAWhq.js +1 -0
  48. package/dist/client/assets/{chunk-2Q5K7J3B-DCrP6W79.js → chunk-2Q5K7J3B-BsVAXGLa.js} +1 -1
  49. package/dist/client/assets/{chunk-5VM5RSS4-Zm6_pYQv.js → chunk-5VM5RSS4-BKtILsst.js} +1 -1
  50. package/dist/client/assets/{chunk-F27PBJKO-DkyD-X9z.js → chunk-F27PBJKO-Dn9zcQZG.js} +1 -1
  51. package/dist/client/assets/chunk-F27PBJKO-Dn9zcQZG.js.br +0 -0
  52. package/dist/client/assets/chunk-F27PBJKO-Dn9zcQZG.js.gz +0 -0
  53. package/dist/client/assets/{chunk-IMKFNOWR-UvWxXAkh.js → chunk-IMKFNOWR-MVtVQeTr.js} +1 -1
  54. package/dist/client/assets/chunk-IMKFNOWR-MVtVQeTr.js.br +0 -0
  55. package/dist/client/assets/chunk-IMKFNOWR-MVtVQeTr.js.gz +0 -0
  56. package/dist/client/assets/{chunk-JWPE2WC7-BjeBAFYM.js → chunk-JWPE2WC7-DliIIMe7.js} +1 -1
  57. package/dist/client/assets/{chunk-POPQ4Y6H-CnqrQdZC.js → chunk-POPQ4Y6H-XBSus3IC.js} +1 -1
  58. package/dist/client/assets/{chunk-SVP7TREG-C4a1s0v4.js → chunk-SVP7TREG-wleSQcJR.js} +1 -1
  59. package/dist/client/assets/chunk-SVP7TREG-wleSQcJR.js.br +0 -0
  60. package/dist/client/assets/chunk-SVP7TREG-wleSQcJR.js.gz +0 -0
  61. package/dist/client/assets/{chunk-TICWLB2K-hnmjYAl_.js → chunk-TICWLB2K-Dq6LdswN.js} +1 -1
  62. package/dist/client/assets/chunk-TICWLB2K-Dq6LdswN.js.br +0 -0
  63. package/dist/client/assets/chunk-TICWLB2K-Dq6LdswN.js.gz +0 -0
  64. package/dist/client/assets/{chunk-XXDRQBXY-xR4LOz28.js → chunk-XXDRQBXY-xafyKlM-.js} +1 -1
  65. package/dist/client/assets/classDiagram-ZZMXUADV-slQB0ns2.js +1 -0
  66. package/dist/client/assets/classDiagram-v2-VYDZK3BY-slQB0ns2.js +1 -0
  67. package/dist/client/assets/{cose-bilkent-JH36ORCC-CqY84fFp.js → cose-bilkent-JH36ORCC-BR7Ohnm_.js} +1 -1
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-BR7Ohnm_.js.br +0 -0
  69. package/dist/client/assets/cose-bilkent-JH36ORCC-BR7Ohnm_.js.gz +0 -0
  70. package/dist/client/assets/{cynefin-OW5HDTMX-Bd14zuaD.js → cynefin-OW5HDTMX-CmPjQgER.js} +1 -1
  71. package/dist/client/assets/cynefin-OW5HDTMX-CmPjQgER.js.br +0 -0
  72. package/dist/client/assets/cynefin-OW5HDTMX-CmPjQgER.js.gz +0 -0
  73. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-DSHcNXDs.js → cynefinDiagram-5FMLGOSQ-CFyR9RvM.js} +1 -1
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CFyR9RvM.js.br +0 -0
  75. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CFyR9RvM.js.gz +0 -0
  76. package/dist/client/assets/{dagre-GXQ25YYZ-CIGWQYeP.js → dagre-GXQ25YYZ-D_RVMRO1.js} +1 -1
  77. package/dist/client/assets/dagre-GXQ25YYZ-D_RVMRO1.js.br +0 -0
  78. package/dist/client/assets/dagre-GXQ25YYZ-D_RVMRO1.js.gz +0 -0
  79. package/dist/client/assets/{diagram-S7CK7UJ4-C9Fg7OLN.js → diagram-S7CK7UJ4-6fr-ZoPj.js} +1 -1
  80. package/dist/client/assets/diagram-S7CK7UJ4-6fr-ZoPj.js.br +0 -0
  81. package/dist/client/assets/diagram-S7CK7UJ4-6fr-ZoPj.js.gz +0 -0
  82. package/dist/client/assets/{diagram-UQ7AKVKN-DoRhl7Fd.js → diagram-UQ7AKVKN-CgwiUtz0.js} +1 -1
  83. package/dist/client/assets/diagram-UQ7AKVKN-CgwiUtz0.js.br +0 -0
  84. package/dist/client/assets/diagram-UQ7AKVKN-CgwiUtz0.js.gz +0 -0
  85. package/dist/client/assets/{diagram-VSXAHHWV-CI83g3iM.js → diagram-VSXAHHWV-CLxzTB9k.js} +1 -1
  86. package/dist/client/assets/diagram-VSXAHHWV-CLxzTB9k.js.br +0 -0
  87. package/dist/client/assets/diagram-VSXAHHWV-CLxzTB9k.js.gz +0 -0
  88. package/dist/client/assets/{diagram-VX7I27RA-Dp2kP4PB.js → diagram-VX7I27RA-DjxDAvdZ.js} +1 -1
  89. package/dist/client/assets/diagram-VX7I27RA-DjxDAvdZ.js.br +0 -0
  90. package/dist/client/assets/diagram-VX7I27RA-DjxDAvdZ.js.gz +0 -0
  91. package/dist/client/assets/{diagram-Z3DM3KII-KtLMCM-V.js → diagram-Z3DM3KII-CDPOjiz4.js} +1 -1
  92. package/dist/client/assets/diagram-Z3DM3KII-CDPOjiz4.js.br +0 -0
  93. package/dist/client/assets/diagram-Z3DM3KII-CDPOjiz4.js.gz +0 -0
  94. package/dist/client/assets/{ebnfDiagram-PWID7BFC-D_zseu1m.js → ebnfDiagram-PWID7BFC-BLbwyF1O.js} +1 -1
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLbwyF1O.js.br +0 -0
  96. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLbwyF1O.js.gz +0 -0
  97. package/dist/client/assets/{erDiagram-RLTQ6QDP-DaWUl_Kz.js → erDiagram-RLTQ6QDP-DbjtZnJx.js} +1 -1
  98. package/dist/client/assets/{erDiagram-RLTQ6QDP-DaWUl_Kz.js.br → erDiagram-RLTQ6QDP-DbjtZnJx.js.br} +0 -0
  99. package/dist/client/assets/erDiagram-RLTQ6QDP-DbjtZnJx.js.gz +0 -0
  100. package/dist/client/assets/{flowDiagram-HODETNUW-BMoJ46zJ.js → flowDiagram-HODETNUW-BirJInAN.js} +1 -1
  101. package/dist/client/assets/flowDiagram-HODETNUW-BirJInAN.js.br +0 -0
  102. package/dist/client/assets/flowDiagram-HODETNUW-BirJInAN.js.gz +0 -0
  103. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-lV_iVmoW.js → ganttDiagram-EL5Y4UJY-d6Q9vSbv.js} +1 -1
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-d6Q9vSbv.js.br +0 -0
  105. package/dist/client/assets/ganttDiagram-EL5Y4UJY-d6Q9vSbv.js.gz +0 -0
  106. package/dist/client/assets/gestureArbiter-C2J0D0kV.css +1 -0
  107. package/dist/client/assets/gestureArbiter-C2J0D0kV.css.br +0 -0
  108. package/dist/client/assets/gestureArbiter-C2J0D0kV.css.gz +0 -0
  109. package/dist/client/assets/gestureArbiter-D1SNS_gK.js +1 -0
  110. package/dist/client/assets/gestureArbiter-D1SNS_gK.js.br +0 -0
  111. package/dist/client/assets/gestureArbiter-D1SNS_gK.js.gz +0 -0
  112. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BVCT9Btd.js → gitGraphDiagram-WWUBYQGX-BWBoIeS6.js} +1 -1
  113. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BWBoIeS6.js.br +0 -0
  114. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BWBoIeS6.js.gz +0 -0
  115. package/dist/client/assets/{index-CnKSUg74.css → index-DUI8KRf6.css} +1 -1
  116. package/dist/client/assets/index-DUI8KRf6.css.br +0 -0
  117. package/dist/client/assets/index-DUI8KRf6.css.gz +0 -0
  118. package/dist/client/assets/{infoDiagram-27XIBGKW-BVhXOx1S.js → infoDiagram-27XIBGKW-BH4AynkM.js} +1 -1
  119. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-Cyzfuoys.js → ishikawaDiagram-5VMMS53U-BQw90AKR.js} +1 -1
  120. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BQw90AKR.js.br +0 -0
  121. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BQw90AKR.js.gz +0 -0
  122. package/dist/client/assets/{journeyDiagram-3NMN7TZE-CEIPDhQF.js → journeyDiagram-3NMN7TZE-DISkQocf.js} +1 -1
  123. package/dist/client/assets/journeyDiagram-3NMN7TZE-DISkQocf.js.br +0 -0
  124. package/dist/client/assets/journeyDiagram-3NMN7TZE-DISkQocf.js.gz +0 -0
  125. package/dist/client/assets/{kanban-definition-UXKFOSKX-C8OulEOF.js → kanban-definition-UXKFOSKX-Qg2LyEVy.js} +1 -1
  126. package/dist/client/assets/kanban-definition-UXKFOSKX-Qg2LyEVy.js.br +0 -0
  127. package/dist/client/assets/kanban-definition-UXKFOSKX-Qg2LyEVy.js.gz +0 -0
  128. package/dist/client/assets/{layout-YN93qRzb.js → layout-Cd7-4_Vr.js} +1 -1
  129. package/dist/client/assets/layout-Cd7-4_Vr.js.br +0 -0
  130. package/dist/client/assets/layout-Cd7-4_Vr.js.gz +0 -0
  131. package/dist/client/assets/{linear-D9T4dYh7.js → linear-DFTi2fTn.js} +1 -1
  132. package/dist/client/assets/linear-DFTi2fTn.js.br +0 -0
  133. package/dist/client/assets/linear-DFTi2fTn.js.gz +0 -0
  134. package/dist/client/assets/{mermaid.core-BdCvvCGV.js → mermaid.core-2v726lUU.js} +5 -5
  135. package/dist/client/assets/mermaid.core-2v726lUU.js.br +0 -0
  136. package/dist/client/assets/mermaid.core-2v726lUU.js.gz +0 -0
  137. package/dist/client/assets/{mindmap-definition-YA3MSWOX-CSpES3Jh.js → mindmap-definition-YA3MSWOX-_FpwteCy.js} +4 -4
  138. package/dist/client/assets/mindmap-definition-YA3MSWOX-_FpwteCy.js.br +0 -0
  139. package/dist/client/assets/mindmap-definition-YA3MSWOX-_FpwteCy.js.gz +0 -0
  140. package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js +2 -0
  141. package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js.br +0 -0
  142. package/dist/client/assets/passwordBootstrap-D9Ffc7NY.js.gz +0 -0
  143. package/dist/client/assets/{pegDiagram-XKGWAZYB-6f4vAA1Q.js → pegDiagram-XKGWAZYB-DLFf-HmZ.js} +1 -1
  144. package/dist/client/assets/pegDiagram-XKGWAZYB-DLFf-HmZ.js.br +0 -0
  145. package/dist/client/assets/pegDiagram-XKGWAZYB-DLFf-HmZ.js.gz +0 -0
  146. package/dist/client/assets/{pieDiagram-E7YTZNPT-BC4Jd-I8.js → pieDiagram-E7YTZNPT-DqTnfXHG.js} +1 -1
  147. package/dist/client/assets/pieDiagram-E7YTZNPT-DqTnfXHG.js.br +0 -0
  148. package/dist/client/assets/pieDiagram-E7YTZNPT-DqTnfXHG.js.gz +0 -0
  149. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-B0pnPaEN.js → quadrantDiagram-AXDQQJYC-B_oBH52Q.js} +1 -1
  150. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B_oBH52Q.js.br +0 -0
  151. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B_oBH52Q.js.gz +0 -0
  152. package/dist/client/assets/{railroadDiagram-O6MQD6OU-B-oOamZT.js → railroadDiagram-O6MQD6OU-HxFQ7Pce.js} +1 -1
  153. package/dist/client/assets/railroadDiagram-O6MQD6OU-HxFQ7Pce.js.br +0 -0
  154. package/dist/client/assets/railroadDiagram-O6MQD6OU-HxFQ7Pce.js.gz +0 -0
  155. package/dist/client/assets/referenceLongPress-B3NGIjvv.js +1 -0
  156. package/dist/client/assets/referenceLongPress-B3NGIjvv.js.br +0 -0
  157. package/dist/client/assets/referenceLongPress-B3NGIjvv.js.gz +0 -0
  158. package/dist/client/assets/{requirementDiagram-BXWQKSXE-DBSyxt0k.js → requirementDiagram-BXWQKSXE-NZdfaZFk.js} +1 -1
  159. package/dist/client/assets/requirementDiagram-BXWQKSXE-NZdfaZFk.js.br +0 -0
  160. package/dist/client/assets/requirementDiagram-BXWQKSXE-NZdfaZFk.js.gz +0 -0
  161. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-CcohzEZE.js → sankeyDiagram-P5KCCOFB-BgCctv6X.js} +1 -1
  162. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BgCctv6X.js.br +0 -0
  163. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BgCctv6X.js.gz +0 -0
  164. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-D7KS6rq_.js → sequenceDiagram-WJ2MYXX4-Br42Za1R.js} +1 -1
  165. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Br42Za1R.js.br +0 -0
  166. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-Br42Za1R.js.gz +0 -0
  167. package/dist/client/assets/{sizeCapture-INFHLROL-CaRXtn4g.js → sizeCapture-INFHLROL-FT4z4S1b.js} +1 -1
  168. package/dist/client/assets/sizeCapture-INFHLROL-FT4z4S1b.js.br +0 -0
  169. package/dist/client/assets/sizeCapture-INFHLROL-FT4z4S1b.js.gz +0 -0
  170. package/dist/client/assets/sparkles-BaGaevME.js +1 -0
  171. package/dist/client/assets/{stateDiagram-D77RDMKH-Dz836LqY.js → stateDiagram-D77RDMKH-DD0t9f84.js} +1 -1
  172. package/dist/client/assets/stateDiagram-D77RDMKH-DD0t9f84.js.br +0 -0
  173. package/dist/client/assets/stateDiagram-D77RDMKH-DD0t9f84.js.gz +0 -0
  174. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BhvVVB56.js +1 -0
  175. package/dist/client/assets/{swimlanes-42K2YHIH-BK4KAY00.js → swimlanes-42K2YHIH-DcC8pGLx.js} +1 -1
  176. package/dist/client/assets/swimlanes-42K2YHIH-DcC8pGLx.js.br +0 -0
  177. package/dist/client/assets/swimlanes-42K2YHIH-DcC8pGLx.js.gz +0 -0
  178. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DSC5lz0i.js +8 -0
  179. package/dist/client/assets/{timeline-definition-24CTP7MA-DWgxTDhO.js → timeline-definition-24CTP7MA-DnwO_Pxd.js} +1 -1
  180. package/dist/client/assets/timeline-definition-24CTP7MA-DnwO_Pxd.js.br +0 -0
  181. package/dist/client/assets/timeline-definition-24CTP7MA-DnwO_Pxd.js.gz +0 -0
  182. package/dist/client/assets/triangle-alert-BCQO9Ap9.js +1 -0
  183. package/dist/client/assets/{vennDiagram-4TSXK5OY-BV1TIfOp.js → vennDiagram-4TSXK5OY-IVNSrZDM.js} +1 -1
  184. package/dist/client/assets/vennDiagram-4TSXK5OY-IVNSrZDM.js.br +0 -0
  185. package/dist/client/assets/vennDiagram-4TSXK5OY-IVNSrZDM.js.gz +0 -0
  186. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-GYClC5F0.js → wardleyDiagram-VM6X3IG4-BnGetaQL.js} +1 -1
  187. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BnGetaQL.js.br +0 -0
  188. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BnGetaQL.js.gz +0 -0
  189. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-D1oKV5W0.js → xychartDiagram-S5SC5T6Z-C01eBN-W.js} +1 -1
  190. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C01eBN-W.js.br +0 -0
  191. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C01eBN-W.js.gz +0 -0
  192. package/dist/client/index.html +2 -2
  193. package/dist/client/index.html.br +0 -0
  194. package/dist/client/index.html.gz +0 -0
  195. package/dist/client/preview-shell.html +1 -0
  196. package/dist/client/preview-shell.js +20 -0
  197. package/dist/client/preview-shell.js.br +0 -0
  198. package/dist/client/preview-shell.js.gz +0 -0
  199. package/dist/client/sw-secure-api.js +132 -0
  200. package/dist/client/sw-secure-api.js.br +0 -0
  201. package/dist/client/sw-secure-api.js.gz +0 -0
  202. package/dist/client/sw.js +1 -1
  203. package/dist/client/sw.js.br +0 -0
  204. package/dist/client/sw.js.gz +0 -0
  205. package/dist/server/agent/collaborationCli.js +298 -0
  206. package/dist/server/agent/collaborationPrompt.js +12 -3
  207. package/dist/server/agent/collaborationProtocol.js +50 -0
  208. package/dist/server/agent/collaborationRoutes.js +86 -0
  209. package/dist/server/agent/collaborationStore.js +311 -20
  210. package/dist/server/cli.js +28 -163
  211. package/dist/server/entry.js +114 -51
  212. package/dist/server/federation/accessPolicy.js +122 -0
  213. package/dist/server/federation/authorization.js +226 -0
  214. package/dist/server/federation/bootstrapCors.js +23 -0
  215. package/dist/server/federation/cli.js +185 -0
  216. package/dist/server/federation/deviceNames.js +29 -0
  217. package/dist/server/federation/directRoutes.js +72 -0
  218. package/dist/server/federation/invitations.js +123 -0
  219. package/dist/server/federation/openAccess.js +25 -0
  220. package/dist/server/federation/originPolicy.js +19 -0
  221. package/dist/server/federation/packets.js +116 -0
  222. package/dist/server/federation/passwordBootstrap.js +86 -0
  223. package/dist/server/federation/passwordLoginRoutes.js +98 -0
  224. package/dist/server/federation/relay.js +346 -0
  225. package/dist/server/federation/requestContext.js +4 -0
  226. package/dist/server/federation/routeAccess.js +124 -0
  227. package/dist/server/federation/routeInvitations.js +160 -0
  228. package/dist/server/federation/runtime.js +537 -0
  229. package/dist/server/federation/secureProtocol.js +223 -0
  230. package/dist/server/federation/socketDuplex.js +42 -0
  231. package/dist/server/federation/streamLimits.js +5 -0
  232. package/dist/server/routes/filesystem.js +2 -1
  233. package/dist/server/routes/terminal.js +89 -79
  234. package/dist/server/utils/authProtection.js +13 -0
  235. package/dist/server/utils/csrfProtection.js +3 -0
  236. package/dist/server/utils/publicSecurity.js +8 -3
  237. package/package.json +10 -3
  238. package/runtime-manifest.json +4 -4
  239. package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js +0 -2
  240. package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js.br +0 -0
  241. package/dist/client/assets/ChangeWalkthroughPanel-DKj7F0ex.js.gz +0 -0
  242. package/dist/client/assets/DagPlayground-D2Wui4jd.js +0 -1
  243. package/dist/client/assets/DagPlayground-D2Wui4jd.js.br +0 -0
  244. package/dist/client/assets/DagPlayground-D2Wui4jd.js.gz +0 -0
  245. package/dist/client/assets/DiffLab-xuS6rtpv.js.br +0 -0
  246. package/dist/client/assets/DiffLab-xuS6rtpv.js.gz +0 -0
  247. package/dist/client/assets/DiffReview-CAwpqM_w.js +0 -1
  248. package/dist/client/assets/DiffReview-CAwpqM_w.js.br +0 -0
  249. package/dist/client/assets/DiffReview-CAwpqM_w.js.gz +0 -0
  250. package/dist/client/assets/DiffReviewLab-BM_uGdyI.js +0 -2
  251. package/dist/client/assets/DiffReviewLab-BM_uGdyI.js.br +0 -0
  252. package/dist/client/assets/DiffReviewLab-BM_uGdyI.js.gz +0 -0
  253. package/dist/client/assets/DiffViewer-BQGXUr8t.js.br +0 -0
  254. package/dist/client/assets/DiffViewer-BQGXUr8t.js.gz +0 -0
  255. package/dist/client/assets/ModelPreview-BfZzS1_d.js.br +0 -0
  256. package/dist/client/assets/ModelPreview-BfZzS1_d.js.gz +0 -0
  257. package/dist/client/assets/RightSidebar-Cdqt3C7V.js +0 -55
  258. package/dist/client/assets/RightSidebar-Cdqt3C7V.js.br +0 -0
  259. package/dist/client/assets/RightSidebar-Cdqt3C7V.js.gz +0 -0
  260. package/dist/client/assets/abnfDiagram-VCTEODGH-Dn_lmPcC.js.br +0 -0
  261. package/dist/client/assets/abnfDiagram-VCTEODGH-Dn_lmPcC.js.gz +0 -0
  262. package/dist/client/assets/app-DDCkFsNe.js +0 -175
  263. package/dist/client/assets/app-DDCkFsNe.js.br +0 -0
  264. package/dist/client/assets/app-DDCkFsNe.js.gz +0 -0
  265. package/dist/client/assets/arc-DUybtKGZ.js.br +0 -0
  266. package/dist/client/assets/arc-DUybtKGZ.js.gz +0 -0
  267. package/dist/client/assets/architectureDiagram-5GKGNRK7-CLEmOHBp.js.br +0 -0
  268. package/dist/client/assets/architectureDiagram-5GKGNRK7-CLEmOHBp.js.gz +0 -0
  269. package/dist/client/assets/blockDiagram-I7D4REHJ-O_XM5b9_.js.br +0 -0
  270. package/dist/client/assets/blockDiagram-I7D4REHJ-O_XM5b9_.js.gz +0 -0
  271. package/dist/client/assets/c4Diagram-7LVT6UL2-DlUEslcV.js.br +0 -0
  272. package/dist/client/assets/c4Diagram-7LVT6UL2-DlUEslcV.js.gz +0 -0
  273. package/dist/client/assets/channel-Bk1VkYvd.js +0 -1
  274. package/dist/client/assets/chunk-F27PBJKO-DkyD-X9z.js.br +0 -0
  275. package/dist/client/assets/chunk-F27PBJKO-DkyD-X9z.js.gz +0 -0
  276. package/dist/client/assets/chunk-IMKFNOWR-UvWxXAkh.js.br +0 -0
  277. package/dist/client/assets/chunk-IMKFNOWR-UvWxXAkh.js.gz +0 -0
  278. package/dist/client/assets/chunk-SVP7TREG-C4a1s0v4.js.br +0 -0
  279. package/dist/client/assets/chunk-SVP7TREG-C4a1s0v4.js.gz +0 -0
  280. package/dist/client/assets/chunk-TICWLB2K-hnmjYAl_.js.br +0 -0
  281. package/dist/client/assets/chunk-TICWLB2K-hnmjYAl_.js.gz +0 -0
  282. package/dist/client/assets/classDiagram-ZZMXUADV-_tUz3Cxt.js +0 -1
  283. package/dist/client/assets/classDiagram-v2-VYDZK3BY-_tUz3Cxt.js +0 -1
  284. package/dist/client/assets/cose-bilkent-JH36ORCC-CqY84fFp.js.br +0 -0
  285. package/dist/client/assets/cose-bilkent-JH36ORCC-CqY84fFp.js.gz +0 -0
  286. package/dist/client/assets/cynefin-OW5HDTMX-Bd14zuaD.js.br +0 -0
  287. package/dist/client/assets/cynefin-OW5HDTMX-Bd14zuaD.js.gz +0 -0
  288. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DSHcNXDs.js.br +0 -0
  289. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-DSHcNXDs.js.gz +0 -0
  290. package/dist/client/assets/dagre-GXQ25YYZ-CIGWQYeP.js.br +0 -0
  291. package/dist/client/assets/dagre-GXQ25YYZ-CIGWQYeP.js.gz +0 -0
  292. package/dist/client/assets/diagram-S7CK7UJ4-C9Fg7OLN.js.br +0 -0
  293. package/dist/client/assets/diagram-S7CK7UJ4-C9Fg7OLN.js.gz +0 -0
  294. package/dist/client/assets/diagram-UQ7AKVKN-DoRhl7Fd.js.br +0 -0
  295. package/dist/client/assets/diagram-UQ7AKVKN-DoRhl7Fd.js.gz +0 -0
  296. package/dist/client/assets/diagram-VSXAHHWV-CI83g3iM.js.br +0 -0
  297. package/dist/client/assets/diagram-VSXAHHWV-CI83g3iM.js.gz +0 -0
  298. package/dist/client/assets/diagram-VX7I27RA-Dp2kP4PB.js.br +0 -0
  299. package/dist/client/assets/diagram-VX7I27RA-Dp2kP4PB.js.gz +0 -0
  300. package/dist/client/assets/diagram-Z3DM3KII-KtLMCM-V.js.br +0 -0
  301. package/dist/client/assets/diagram-Z3DM3KII-KtLMCM-V.js.gz +0 -0
  302. package/dist/client/assets/ebnfDiagram-PWID7BFC-D_zseu1m.js.br +0 -0
  303. package/dist/client/assets/ebnfDiagram-PWID7BFC-D_zseu1m.js.gz +0 -0
  304. package/dist/client/assets/erDiagram-RLTQ6QDP-DaWUl_Kz.js.gz +0 -0
  305. package/dist/client/assets/flowDiagram-HODETNUW-BMoJ46zJ.js.br +0 -0
  306. package/dist/client/assets/flowDiagram-HODETNUW-BMoJ46zJ.js.gz +0 -0
  307. package/dist/client/assets/ganttDiagram-EL5Y4UJY-lV_iVmoW.js.br +0 -0
  308. package/dist/client/assets/ganttDiagram-EL5Y4UJY-lV_iVmoW.js.gz +0 -0
  309. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVCT9Btd.js.br +0 -0
  310. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BVCT9Btd.js.gz +0 -0
  311. package/dist/client/assets/index-CnKSUg74.css.br +0 -0
  312. package/dist/client/assets/index-CnKSUg74.css.gz +0 -0
  313. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cyzfuoys.js.br +0 -0
  314. package/dist/client/assets/ishikawaDiagram-5VMMS53U-Cyzfuoys.js.gz +0 -0
  315. package/dist/client/assets/journeyDiagram-3NMN7TZE-CEIPDhQF.js.br +0 -0
  316. package/dist/client/assets/journeyDiagram-3NMN7TZE-CEIPDhQF.js.gz +0 -0
  317. package/dist/client/assets/kanban-definition-UXKFOSKX-C8OulEOF.js.br +0 -0
  318. package/dist/client/assets/kanban-definition-UXKFOSKX-C8OulEOF.js.gz +0 -0
  319. package/dist/client/assets/layout-YN93qRzb.js.br +0 -0
  320. package/dist/client/assets/layout-YN93qRzb.js.gz +0 -0
  321. package/dist/client/assets/linear-D9T4dYh7.js.br +0 -0
  322. package/dist/client/assets/linear-D9T4dYh7.js.gz +0 -0
  323. package/dist/client/assets/mermaid.core-BdCvvCGV.js.br +0 -0
  324. package/dist/client/assets/mermaid.core-BdCvvCGV.js.gz +0 -0
  325. package/dist/client/assets/mindmap-definition-YA3MSWOX-CSpES3Jh.js.br +0 -0
  326. package/dist/client/assets/mindmap-definition-YA3MSWOX-CSpES3Jh.js.gz +0 -0
  327. package/dist/client/assets/pegDiagram-XKGWAZYB-6f4vAA1Q.js.br +0 -0
  328. package/dist/client/assets/pegDiagram-XKGWAZYB-6f4vAA1Q.js.gz +0 -0
  329. package/dist/client/assets/pieDiagram-E7YTZNPT-BC4Jd-I8.js.br +0 -0
  330. package/dist/client/assets/pieDiagram-E7YTZNPT-BC4Jd-I8.js.gz +0 -0
  331. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0pnPaEN.js.br +0 -0
  332. package/dist/client/assets/quadrantDiagram-AXDQQJYC-B0pnPaEN.js.gz +0 -0
  333. package/dist/client/assets/railroadDiagram-O6MQD6OU-B-oOamZT.js.br +0 -0
  334. package/dist/client/assets/railroadDiagram-O6MQD6OU-B-oOamZT.js.gz +0 -0
  335. package/dist/client/assets/requirementDiagram-BXWQKSXE-DBSyxt0k.js.br +0 -0
  336. package/dist/client/assets/requirementDiagram-BXWQKSXE-DBSyxt0k.js.gz +0 -0
  337. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CcohzEZE.js.br +0 -0
  338. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CcohzEZE.js.gz +0 -0
  339. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D7KS6rq_.js.br +0 -0
  340. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D7KS6rq_.js.gz +0 -0
  341. package/dist/client/assets/sizeCapture-INFHLROL-CaRXtn4g.js.br +0 -0
  342. package/dist/client/assets/sizeCapture-INFHLROL-CaRXtn4g.js.gz +0 -0
  343. package/dist/client/assets/sparkles-CUdUt4qy.js +0 -1
  344. package/dist/client/assets/stateDiagram-D77RDMKH-Dz836LqY.js.br +0 -0
  345. package/dist/client/assets/stateDiagram-D77RDMKH-Dz836LqY.js.gz +0 -0
  346. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-5V5Y3wwn.js +0 -1
  347. package/dist/client/assets/swimlanes-42K2YHIH-BK4KAY00.js.br +0 -0
  348. package/dist/client/assets/swimlanes-42K2YHIH-BK4KAY00.js.gz +0 -0
  349. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Sj-7YK2P.js +0 -8
  350. package/dist/client/assets/timeline-definition-24CTP7MA-DWgxTDhO.js.br +0 -0
  351. package/dist/client/assets/timeline-definition-24CTP7MA-DWgxTDhO.js.gz +0 -0
  352. package/dist/client/assets/vennDiagram-4TSXK5OY-BV1TIfOp.js.br +0 -0
  353. package/dist/client/assets/vennDiagram-4TSXK5OY-BV1TIfOp.js.gz +0 -0
  354. package/dist/client/assets/wardleyDiagram-VM6X3IG4-GYClC5F0.js.br +0 -0
  355. package/dist/client/assets/wardleyDiagram-VM6X3IG4-GYClC5F0.js.gz +0 -0
  356. package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1oKV5W0.js.br +0 -0
  357. package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1oKV5W0.js.gz +0 -0
@@ -0,0 +1,223 @@
1
+ /** End-to-end byte transport. Cryptographic handshake and records are owned by noise-libp2p. */
2
+ import { noise, pureJsCrypto } from '@chainsafe/libp2p-noise';
3
+ import { generateKeyPair, privateKeyFromProtobuf, privateKeyToProtobuf } from '@libp2p/crypto/keys';
4
+ import { peerIdFromPublicKey, peerIdFromString } from '@libp2p/peer-id';
5
+ import { AbstractMessageStream } from '@libp2p/utils';
6
+ export const SECURE_PROTOCOL = 'termdock-e2ee-v1';
7
+ export const MAX_SECURE_CONNECTION_AGE_MS = 60 * 60 * 1000;
8
+ export const MAX_SECURE_CONNECTION_BYTES_PER_DIRECTION = 512 * 1024 * 1024;
9
+ const MAX_BUFFER_BYTES = 4 * 1024 * 1024;
10
+ const noop = () => { };
11
+ const silentLogger = Object.assign(noop, {
12
+ enabled: false, error: noop, trace: noop, newScope: () => silentLogger,
13
+ });
14
+ Object.freeze(silentLogger);
15
+ export async function createIdentity() {
16
+ const privateKey = await generateKeyPair('Ed25519');
17
+ return { privateKey, peerId: peerIdFromPublicKey(privateKey.publicKey).toString() };
18
+ }
19
+ /** Contains a private key. Store with device-private permissions, never include in pairing URLs. */
20
+ export function exportIdentity(identity) {
21
+ return btoa(String.fromCharCode(...privateKeyToProtobuf(identity.privateKey)));
22
+ }
23
+ export function importIdentity(serialized) {
24
+ if (serialized.length > 512 || !/^[A-Za-z0-9+/]+={0,2}$/.test(serialized))
25
+ throw new Error('Invalid identity encoding');
26
+ const privateKey = privateKeyFromProtobuf(Uint8Array.from(atob(serialized), c => c.charCodeAt(0)));
27
+ if (privateKey.type !== 'Ed25519')
28
+ throw new Error('Only Ed25519 device identities are supported');
29
+ return { privateKey, peerId: peerIdFromPublicKey(privateKey.publicKey).toString() };
30
+ }
31
+ /** Bounded bridge, not a cryptographic record layer. */
32
+ class ByteQueue {
33
+ consumed;
34
+ chunks = [];
35
+ size = 0;
36
+ ended = false;
37
+ error;
38
+ wake;
39
+ constructor(consumed = noop) {
40
+ this.consumed = consumed;
41
+ }
42
+ push(bytes) {
43
+ if (this.ended)
44
+ throw this.error ?? new Error('Byte stream closed');
45
+ if (this.size + bytes.byteLength > MAX_BUFFER_BYTES)
46
+ throw new Error('Secure stream buffer limit exceeded');
47
+ this.chunks.push(bytes.slice());
48
+ this.size += bytes.byteLength;
49
+ this.wake?.();
50
+ }
51
+ end(error) { this.ended = true; this.error ??= error; this.wake?.(); }
52
+ async *[Symbol.asyncIterator]() {
53
+ while (true) {
54
+ if (this.error)
55
+ throw this.error;
56
+ const chunk = this.chunks.shift();
57
+ if (chunk) {
58
+ this.size -= chunk.byteLength;
59
+ yield chunk;
60
+ this.consumed();
61
+ }
62
+ else if (this.ended)
63
+ return;
64
+ else
65
+ await new Promise(resolve => { this.wake = resolve; });
66
+ }
67
+ }
68
+ }
69
+ class TransportStream extends AbstractMessageStream {
70
+ duplex;
71
+ outgoing = new ByteQueue(() => this.onMuxerDrain());
72
+ finished;
73
+ constructor(duplex, initiator) {
74
+ super({ log: silentLogger, direction: initiator ? 'outbound' : 'inbound', maxReadBufferLength: MAX_BUFFER_BYTES, maxWriteBufferLength: MAX_BUFFER_BYTES });
75
+ this.duplex = duplex;
76
+ this.finished = duplex.sink(this.outgoing).catch(error => { this.abort(asError(error)); });
77
+ void this.readTransport();
78
+ }
79
+ async readTransport() {
80
+ try {
81
+ for await (const bytes of this.duplex.source)
82
+ this.onData(bytes.slice());
83
+ this.onTransportClosed();
84
+ }
85
+ catch (error) {
86
+ this.onTransportClosed(asError(error));
87
+ }
88
+ }
89
+ sendData(data) {
90
+ this.outgoing.push(data.subarray());
91
+ return { sentBytes: data.byteLength, canSendMore: false };
92
+ }
93
+ sendReset(error) { this.outgoing.end(error); this.duplex.close?.(error); }
94
+ sendPause() { }
95
+ sendResume() { }
96
+ async close() {
97
+ this.outgoing.end();
98
+ this.duplex.close?.();
99
+ this.onTransportClosed();
100
+ await this.finished;
101
+ }
102
+ }
103
+ function asError(value) { return value instanceof Error ? value : new Error('Secure transport failed'); }
104
+ export class SecureConnectionExpiredError extends Error {
105
+ code = 'E2EE_REHANDSHAKE_REQUIRED';
106
+ constructor() { super('Encrypted connection lifetime reached; establish a new handshake without replaying pending input'); }
107
+ }
108
+ function connectionLimits(limits) {
109
+ const maxAgeMs = limits?.maxAgeMs ?? MAX_SECURE_CONNECTION_AGE_MS;
110
+ const maxBytesPerDirection = limits?.maxBytesPerDirection ?? MAX_SECURE_CONNECTION_BYTES_PER_DIRECTION;
111
+ if (!Number.isSafeInteger(maxAgeMs) || maxAgeMs <= 0 || maxAgeMs > MAX_SECURE_CONNECTION_AGE_MS
112
+ || !Number.isSafeInteger(maxBytesPerDirection) || maxBytesPerDirection <= 0 || maxBytesPerDirection > MAX_SECURE_CONNECTION_BYTES_PER_DIRECTION) {
113
+ throw new Error('Encrypted connection limits cannot exceed the production maximum');
114
+ }
115
+ return { maxAgeMs, maxBytesPerDirection };
116
+ }
117
+ export async function secureConnection(options) {
118
+ const { identity, duplex, initiator, targetPinnedPeerId, signal } = options;
119
+ const limits = connectionLimits(options.limits);
120
+ if (typeof Reflect.get(Promise, 'withResolvers') !== 'function' || typeof globalThis.crypto?.getRandomValues !== 'function') {
121
+ throw new Error('End-to-end encryption requires Node.js 22+ or a modern browser with Promise.withResolvers and secure random support');
122
+ }
123
+ if (initiator && !targetPinnedPeerId)
124
+ throw new Error('A pinned target identity is required');
125
+ const remotePeer = targetPinnedPeerId ? peerIdFromString(targetPinnedPeerId) : undefined;
126
+ if (remotePeer && remotePeer.type !== 'Ed25519')
127
+ throw new Error('Only Ed25519 target identities are supported');
128
+ if (identity.peerId !== peerIdFromPublicKey(identity.privateKey.publicKey).toString())
129
+ throw new Error('Local identity mismatch');
130
+ if (signal?.aborted)
131
+ throw signal.reason ?? new Error('Connection aborted');
132
+ const transport = new TransportStream(duplex, initiator);
133
+ const handshake = new AbortController();
134
+ const timeout = setTimeout(() => {
135
+ const error = new Error('Encrypted handshake timed out');
136
+ handshake.abort(error);
137
+ transport.abort(error);
138
+ }, 15_000);
139
+ const abort = () => { handshake.abort(signal?.reason); transport.abort(asError(signal?.reason)); };
140
+ signal?.addEventListener('abort', abort, { once: true });
141
+ transport.addEventListener('close', () => signal?.removeEventListener('abort', abort), { once: true });
142
+ const encrypter = noise({ crypto: pureJsCrypto, prologueBytes: new TextEncoder().encode(SECURE_PROTOCOL) })({
143
+ privateKey: identity.privateKey,
144
+ peerId: peerIdFromPublicKey(identity.privateKey.publicKey),
145
+ logger: { forComponent: () => silentLogger },
146
+ // Only getStreamMuxers is needed, and negotiation is explicitly disabled.
147
+ upgrader: { getStreamMuxers: () => new Map() },
148
+ });
149
+ try {
150
+ const secured = await (initiator ? encrypter.secureOutbound(transport, { remotePeer, signal: handshake.signal, skipStreamMuxerNegotiation: true })
151
+ : encrypter.secureInbound(transport, { remotePeer, signal: handshake.signal, skipStreamMuxerNegotiation: true }));
152
+ if (secured.remotePeer.type !== 'Ed25519')
153
+ throw new Error('Unsupported remote identity');
154
+ return { authenticatedPeerId: secured.remotePeer.toString(), duplex: plaintextDuplex(secured.connection, limits) };
155
+ }
156
+ catch (error) {
157
+ transport.abort(asError(error));
158
+ throw error;
159
+ }
160
+ finally {
161
+ clearTimeout(timeout);
162
+ signal?.removeEventListener('abort', abort);
163
+ }
164
+ }
165
+ function plaintextDuplex(stream, limits) {
166
+ const incoming = new ByteQueue();
167
+ const establishedAt = performance.now(), establishedWallTime = Date.now();
168
+ let sentBytes = 0, receivedBytes = 0, expiredError;
169
+ const expire = () => {
170
+ expiredError ??= new SecureConnectionExpiredError();
171
+ incoming.end(expiredError);
172
+ stream.abort(expiredError);
173
+ return expiredError;
174
+ };
175
+ const lifetime = setTimeout(expire, limits.maxAgeMs);
176
+ if (typeof lifetime === 'object')
177
+ lifetime.unref?.();
178
+ const checkLimit = (alreadyTransferred, nextBytes) => {
179
+ if (expiredError)
180
+ throw expiredError;
181
+ // Check on traffic as well as the timer: a suspended PWA must not resume
182
+ // using an expired session key before its delayed timer gets a turn.
183
+ const age = Math.max(performance.now() - establishedAt, Date.now() - establishedWallTime);
184
+ if (age >= limits.maxAgeMs || alreadyTransferred + nextBytes >= limits.maxBytesPerDirection)
185
+ throw expire();
186
+ };
187
+ stream.addEventListener('message', event => {
188
+ try {
189
+ checkLimit(receivedBytes, event.data.byteLength);
190
+ receivedBytes += event.data.byteLength;
191
+ incoming.push(event.data.subarray());
192
+ }
193
+ catch (error) {
194
+ stream.abort(asError(error));
195
+ }
196
+ });
197
+ stream.addEventListener('close', event => { clearTimeout(lifetime); incoming.end(event.error); }, { once: true });
198
+ let writing = false;
199
+ return {
200
+ source: incoming,
201
+ async sink(source) {
202
+ if (writing)
203
+ throw new Error('Secure sink already consumed');
204
+ writing = true;
205
+ try {
206
+ for await (const chunk of source) {
207
+ checkLimit(sentBytes, chunk.byteLength);
208
+ sentBytes += chunk.byteLength;
209
+ if (!stream.send(chunk))
210
+ await stream.onDrain();
211
+ }
212
+ }
213
+ catch (error) {
214
+ stream.abort(asError(error));
215
+ throw error;
216
+ }
217
+ },
218
+ close(error) { if (error)
219
+ stream.abort(error);
220
+ else
221
+ void stream.close(); },
222
+ };
223
+ }
@@ -0,0 +1,42 @@
1
+ import { AsyncQueue } from './packets.js';
2
+ export function socketDuplex(socket) {
3
+ const input = new AsyncQueue(64);
4
+ socket.binaryType = 'arraybuffer';
5
+ const close = (error) => { input.end(error); socket.close(1000); };
6
+ socket.addEventListener('message', event => {
7
+ try {
8
+ if (!(event.data instanceof ArrayBuffer) || event.data.byteLength > 1024 * 1024)
9
+ throw new Error('Invalid encrypted frame');
10
+ input.push(new Uint8Array(event.data));
11
+ }
12
+ catch {
13
+ close(new Error('Encrypted transport rejected frame'));
14
+ }
15
+ });
16
+ socket.addEventListener('close', () => input.end());
17
+ socket.addEventListener('error', () => input.end(new Error('Encrypted transport disconnected')));
18
+ return {
19
+ source: input, close,
20
+ async sink(source) {
21
+ try {
22
+ for await (const chunk of source) {
23
+ for (let offset = 0; offset < chunk.length; offset += 64 * 1024) {
24
+ const started = Date.now();
25
+ while (socket.bufferedAmount > 256 * 1024) {
26
+ if (socket.readyState !== 1 || Date.now() - started > 15000)
27
+ throw new Error('Encrypted transport congested');
28
+ await new Promise(resolve => setTimeout(resolve, 10));
29
+ }
30
+ if (socket.readyState !== 1)
31
+ throw new Error('Encrypted transport closed');
32
+ socket.send(chunk.subarray(offset, offset + 64 * 1024));
33
+ }
34
+ }
35
+ }
36
+ catch (error) {
37
+ close(error instanceof Error ? error : new Error('Encrypted transport failed'));
38
+ throw error;
39
+ }
40
+ },
41
+ };
42
+ }
@@ -0,0 +1,5 @@
1
+ // Long-lived terminal subscriptions have an independent budget from short HTTP
2
+ // operations. Normal browser requests queue below the server's defensive ceiling.
3
+ export const MAX_ACTIVE_HTTP_REQUESTS = 8;
4
+ export const MAX_SERVER_HTTP_REQUESTS = 32;
5
+ export const MAX_OPEN_SECURE_SOCKETS = 128;
@@ -9,6 +9,7 @@ import busboy from 'busboy';
9
9
  import { validatePreview } from '../utils/previewValidation.js';
10
10
  import { pathValidator } from '../utils/pathValidator.js';
11
11
  import { isAuthEnabled, isRequestAuthenticated } from '../utils/authProtection.js';
12
+ import { isEncryptedRequest } from '../federation/requestContext.js';
12
13
  import { getImageDimensions, parseImageDimensions } from '../utils/imageDimensions.js';
13
14
  import { writeDiffTraceLog, writeErrorLog, writeJsonLog } from '../utils/serverLogger.js';
14
15
  import { clearBranchAuditRecords, clearChangeAuditRecords, listBranchAuditRecords, listChangeAuditRecords, buildChangeAuditFingerprint } from '../utils/changeAuditStore.js';
@@ -3656,7 +3657,7 @@ router.get('/preview/*path', async (req, res) => {
3656
3657
  // previewed file's directory, so the sandboxed iframe's subresources can
3657
3658
  // load without cookies. The token segment in the URL is inherited by every
3658
3659
  // relative reference the document makes.
3659
- if (authEnabled && !maybeToken) {
3660
+ if ((authEnabled || isEncryptedRequest(req)) && !maybeToken) {
3660
3661
  const root = stat.isDirectory() ? validatedPath : path.dirname(validatedPath);
3661
3662
  const tokenized = tokenizePreviewUrl(previewBaseUrl, mintPreviewToken(root, req.cookies?.[AUTH_COOKIE]));
3662
3663
  redirectUrl = stat.isDirectory() && !previewBaseUrl.endsWith('/') ? `${tokenized}/` : tokenized;
@@ -33,6 +33,8 @@ import { AgentResumeHistoryStore } from '../agent/resumeHistory.js';
33
33
  import { isTmuxRecoveryCandidate } from '../utils/tmuxRecoveryCandidate.js';
34
34
  import { AutomationStore, normalizeAutomationSchedule } from '../agent/automationStore.js';
35
35
  import { buildBracketedSubmitBytes, canDeliverPromptToAgent } from '../agent/promptDelivery.js';
36
+ import { collaborationRoutes } from '../agent/collaborationRoutes.js';
37
+ import { COLLAB_LIMITS, extrasFromBody } from '../agent/collaborationProtocol.js';
36
38
  import { CollaborationStore } from '../agent/collaborationStore.js';
37
39
  import { formatCollaborationDelivery } from '../agent/collaborationPrompt.js';
38
40
  import { resolveCollaborationSpawnMode } from '../agent/collaborationSpawn.js';
@@ -1266,6 +1268,7 @@ function orchestrationSessionSnapshot(record) {
1266
1268
  const agent = backend?.agent ?? (record.agentResume?.slug ? agentBySlug(record.agentResume.slug) : null);
1267
1269
  const latestPrompt = backend?.autoTitlePromptPayloads.at(-1)?.trim() ?? '';
1268
1270
  return {
1271
+ ...collaborationStore.sessionFacts(record.sessionId, Boolean(backend), backend?.agentSession?.status),
1269
1272
  sessionId: record.sessionId,
1270
1273
  agentNativeSessionId: backend?.agentSession?.sessionId ?? record.agentResume?.sessionId ?? null,
1271
1274
  backendSessionId: record.backendSessionId ?? null,
@@ -1365,7 +1368,19 @@ function resolveFrontendSessionId(input) {
1365
1368
  const tmuxSessionName = typeof input.tmuxSessionName === 'string' ? input.tmuxSessionName.trim() : '';
1366
1369
  return globalSessionState.sessions.find((record) => record.tmuxSessionName === tmuxSessionName)?.sessionId ?? null;
1367
1370
  }
1371
+ function collaborationRemoteSessions() {
1372
+ return Array.from(new Map(collaborationStore.list().flatMap((group) => group.remoteSessions ?? [])
1373
+ .map((session) => [session.sessionId, { ...session, ...collaborationStore.sessionFacts(session.sessionId, session.serviceConnected === true && Date.now() - (session.serviceCheckedAt ?? 0) <= 15_000, session.status), status: session.serviceConnected !== true || Date.now() - (session.serviceCheckedAt ?? 0) > 15_000 ? 'service-unreachable' : session.status, name: `${session.name} · ${session.serviceLabel}` }])).values());
1374
+ }
1368
1375
  function tryDeliverCollaborationInbox(frontendSessionId) {
1376
+ const remote = collaborationRemoteSessions().find((session) => session.sessionId === frontendSessionId);
1377
+ if (remote) {
1378
+ const unavailable = remote.serviceConnected !== true || Date.now() - (remote.serviceCheckedAt ?? 0) > 15_000;
1379
+ return { delivered: [], pending: collaborationStore.pendingCount(frontendSessionId),
1380
+ serviceUnavailable: unavailable, reason: unavailable
1381
+ ? `服务 ${remote.serviceLabel} 不可达:消息尚未送达,仅保存在待发送队列;请勿等待对方已收到的回复,服务重连且 Mac 客户端运行后重试投递。`
1382
+ : '跨服务消息尚未确认送达,正在等待 Mac 客户端转发。' };
1383
+ }
1369
1384
  const record = globalSessionState.sessions.find((candidate) => candidate.sessionId === frontendSessionId);
1370
1385
  const session = record?.backendSessionId ? terminalSessions.get(record.backendSessionId) : null;
1371
1386
  const pending = collaborationStore.inbox(frontendSessionId, { pendingOnly: true, limit: 10 });
@@ -1378,19 +1393,33 @@ function tryDeliverCollaborationInbox(frontendSessionId) {
1378
1393
  targetSessionId: frontendSessionId,
1379
1394
  messages: pending,
1380
1395
  groups: collaborationStore.groupsForSession(frontendSessionId),
1381
- sessions: globalSessionState.sessions.map((candidate) => {
1382
- const snapshot = orchestrationSessionSnapshot(candidate);
1383
- return {
1384
- sessionId: snapshot.sessionId,
1385
- agentNativeSessionId: snapshot.agentNativeSessionId,
1386
- name: snapshot.name,
1387
- status: snapshot.status,
1388
- };
1389
- }),
1396
+ sessions: [...collaborationRemoteSessions(), ...globalSessionState.sessions.map((candidate) => {
1397
+ const snapshot = orchestrationSessionSnapshot(candidate);
1398
+ return {
1399
+ sessionId: snapshot.sessionId,
1400
+ agentNativeSessionId: snapshot.agentNativeSessionId,
1401
+ name: snapshot.name,
1402
+ status: snapshot.status,
1403
+ };
1404
+ })],
1390
1405
  });
1391
- session.ptyProcess.write(buildBracketedSubmitBytes(prompt));
1406
+ try {
1407
+ session.ptyProcess.write(buildBracketedSubmitBytes(prompt));
1408
+ }
1409
+ catch (error) {
1410
+ for (const message of pending)
1411
+ collaborationStore.recordTransport(message.id, {
1412
+ relay_online: null, peer_reachable: true, attempt_count: (collaborationStore.diagnostic(message.id)?.attempt_count ?? 0) + 1,
1413
+ next_retry_at: Date.now() + 2_000, last_error: `PTY_WRITE_FAILED: ${getErrorMessage(error)}`, checked_at: Date.now(),
1414
+ });
1415
+ setTimeout(() => tryDeliverCollaborationInbox(frontendSessionId), 2_000).unref?.();
1416
+ return { delivered: [], pending: collaborationStore.pendingCount(frontendSessionId), reason: '终端写入失败,消息已保留并等待重试' };
1417
+ }
1392
1418
  collaborationStore.markDelivered(pending.map((message) => message.id));
1393
- return { delivered: pending.map((message) => message.id), pending: 0 };
1419
+ const remaining = collaborationStore.pendingCount(frontendSessionId);
1420
+ if (remaining)
1421
+ setTimeout(() => tryDeliverCollaborationInbox(frontendSessionId), 300).unref?.();
1422
+ return { delivered: pending.map((message) => message.id), pending: remaining };
1394
1423
  }
1395
1424
  async function refreshCollaborationAgentIdentity(backendSessionId, session) {
1396
1425
  let activeProgram = null;
@@ -5188,8 +5217,47 @@ router.delete('/operations/automations/:automationId', (req, res) => {
5188
5217
  return res.status(404).json({ error: '自动任务不存在' });
5189
5218
  res.status(204).send();
5190
5219
  });
5220
+ // Authenticated desktop connections exchange only collaboration data; no peer credentials
5221
+ // or arbitrary remote URLs are accepted by the server.
5222
+ router.get('/operations/collaboration-federation', (_req, res) => {
5223
+ res.json({ protocolVersion: 2, limits: COLLAB_LIMITS, ...collaborationStore.federationSnapshot(),
5224
+ sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot) });
5225
+ });
5226
+ router.post('/operations/collaboration-federation', (req, res) => {
5227
+ try {
5228
+ const group = req.body?.group;
5229
+ if (!group || !Array.isArray(group.remoteSessions))
5230
+ throw new Error('跨服务工作组无效');
5231
+ const remoteIds = new Set(group.remoteSessions.map((session) => session.sessionId));
5232
+ const localIds = new Set(globalSessionState.sessions.map((session) => session.sessionId));
5233
+ if (!group.deleted && group.sessionIds.some((id) => !localIds.has(id) && !remoteIds.has(id))) {
5234
+ throw new Error('工作组包含不存在的本服务会话,请刷新成员列表');
5235
+ }
5236
+ collaborationStore.mergeFederatedGroup(group);
5237
+ collaborationStore.mergeFederatedMessages(Array.isArray(req.body.messages) ? req.body.messages : []);
5238
+ const fragments = Array.isArray(req.body.fragments) ? req.body.fragments : [];
5239
+ if (fragments.length > 25 || fragments.some((fragment) => fragment.group_id !== group.id))
5240
+ throw new Error('无效消息分片');
5241
+ const fragmentReceipts = fragments.map((fragment) => collaborationStore.acceptFragment(fragment));
5242
+ for (const entry of Array.isArray(req.body.transport) ? req.body.transport : []) {
5243
+ 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)
5244
+ continue;
5245
+ collaborationStore.recordTransport(entry.message_id, entry.diagnostic);
5246
+ if (entry.failure_reason)
5247
+ collaborationStore.fail(entry.message_id, entry.failure_reason);
5248
+ }
5249
+ for (const id of group.sessionIds)
5250
+ if (localIds.has(id))
5251
+ tryDeliverCollaborationInbox(id);
5252
+ res.json({ protocolVersion: 2, ...collaborationStore.federationSnapshot(), fragmentReceipts });
5253
+ }
5254
+ catch (error) {
5255
+ res.status(400).json({ error: getErrorMessage(error) });
5256
+ }
5257
+ });
5191
5258
  router.get('/operations/collaboration-groups', (_req, res) => {
5192
5259
  res.json({
5260
+ federationVersion: 1,
5193
5261
  groups: collaborationStore.list(),
5194
5262
  sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot),
5195
5263
  });
@@ -5199,7 +5267,9 @@ router.post('/operations/collaboration-groups', (req, res) => {
5199
5267
  const sessionIds = Array.isArray(req.body?.sessionIds)
5200
5268
  ? req.body.sessionIds.filter((id) => typeof id === 'string')
5201
5269
  : [];
5202
- const knownIds = new Set(globalSessionState.sessions.map((session) => session.sessionId));
5270
+ const existingGroup = typeof req.body?.id === 'string' ? collaborationStore.getGroup(req.body.id) : null;
5271
+ const knownIds = new Set([...globalSessionState.sessions.map((session) => session.sessionId),
5272
+ ...(existingGroup?.remoteSessions?.map((session) => session.sessionId) ?? [])]);
5203
5273
  const normalizedIds = Array.from(new Set(sessionIds.filter((id) => knownIds.has(id))));
5204
5274
  if (!name || normalizedIds.length < 2)
5205
5275
  return res.status(400).json({ error: '协作组至少需要两个有效会话' });
@@ -5241,7 +5311,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
5241
5311
  return res.status(404).json({ error: '协作组不存在' });
5242
5312
  const fromSessionId = resolveFrontendSessionId(req.body ?? {});
5243
5313
  const kind = typeof req.body?.kind === 'string' ? req.body.kind : 'message';
5244
- const content = typeof req.body?.content === 'string' ? req.body.content.slice(0, 20_000) : '';
5314
+ const content = typeof req.body?.content === 'string' ? req.body.content : '';
5245
5315
  const requestedTargets = Array.isArray(req.body?.toSessionIds)
5246
5316
  ? req.body.toSessionIds.filter((id) => typeof id === 'string')
5247
5317
  : [];
@@ -5249,6 +5319,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
5249
5319
  ? requestedTargets
5250
5320
  : group.sessionIds.filter((id) => id !== fromSessionId);
5251
5321
  const messages = collaborationStore.send({
5322
+ ...extrasFromBody(req.body),
5252
5323
  groupId: group.id,
5253
5324
  fromSessionId,
5254
5325
  toSessionIds,
@@ -5261,7 +5332,7 @@ router.post('/operations/collaboration-groups/:groupId/messages', (req, res) =>
5261
5332
  sessionId,
5262
5333
  ...tryDeliverCollaborationInbox(sessionId),
5263
5334
  }));
5264
- res.json({ messages, deliveries });
5335
+ res.json({ messages: messages.map((message) => collaborationStore.getMessage(message.id)), deliveries });
5265
5336
  }
5266
5337
  catch (error) {
5267
5338
  res.status(400).json({ error: getErrorMessage(error) });
@@ -5277,7 +5348,7 @@ router.get('/operations/orchestration/peers', async (req, res) => {
5277
5348
  res.json({
5278
5349
  source: orchestrationSessionSnapshot(source),
5279
5350
  groups,
5280
- peers: globalSessionState.sessions.filter((record) => peerIds.has(record.sessionId)).map(orchestrationSessionSnapshot),
5351
+ peers: [...globalSessionState.sessions.filter((record) => peerIds.has(record.sessionId)).map(orchestrationSessionSnapshot), ...collaborationRemoteSessions().filter((record) => peerIds.has(record.sessionId))],
5281
5352
  sessions: globalSessionState.sessions.map(orchestrationSessionSnapshot),
5282
5353
  agents: await listDetectedAgentLaunchers(),
5283
5354
  });
@@ -5322,70 +5393,7 @@ router.post('/operations/orchestration/spawn', async (req, res) => {
5322
5393
  res.status(400).json({ error: getErrorMessage(error) });
5323
5394
  }
5324
5395
  });
5325
- router.post('/operations/orchestration/send', (req, res) => {
5326
- const sourceSessionId = resolveFrontendSessionId(req.body ?? {});
5327
- const targetSessionId = typeof req.body?.targetSessionId === 'string' ? req.body.targetSessionId.trim() : '';
5328
- const content = typeof req.body?.message === 'string' ? req.body.message.trim().slice(0, 20_000) : '';
5329
- if (!sourceSessionId)
5330
- return res.status(400).json({ error: '无法识别发送会话;请从 Termdock 会话内运行 td collab' });
5331
- const group = collaborationStore.groupsForSession(sourceSessionId).find((candidate) => candidate.sessionIds.includes(targetSessionId));
5332
- if (!group)
5333
- return res.status(400).json({ error: '发送方和接收方不在同一协作组' });
5334
- try {
5335
- const messages = collaborationStore.send({
5336
- groupId: group.id, fromSessionId: sourceSessionId, toSessionIds: [targetSessionId],
5337
- kind: typeof req.body?.kind === 'string' ? req.body.kind : 'message',
5338
- content,
5339
- });
5340
- res.json({ ok: true, messages, delivery: tryDeliverCollaborationInbox(targetSessionId) });
5341
- }
5342
- catch (error) {
5343
- res.status(400).json({ error: getErrorMessage(error) });
5344
- }
5345
- });
5346
- router.get('/operations/orchestration/inbox', (req, res) => {
5347
- const sessionId = resolveFrontendSessionId(req.query);
5348
- if (!sessionId)
5349
- return res.status(404).json({ error: '会话不存在' });
5350
- const messages = collaborationStore.inbox(sessionId, { limit: Number(req.query.limit) || 50 });
5351
- if (req.query.markRead === 'true')
5352
- collaborationStore.markRead(messages.map((message) => message.id));
5353
- const groups = collaborationStore.groupsForSession(sessionId);
5354
- const peerIds = new Set(groups.flatMap((group) => group.sessionIds).filter((id) => id !== sessionId));
5355
- res.json({
5356
- session: orchestrationSessionSnapshot(globalSessionState.sessions.find((record) => record.sessionId === sessionId)),
5357
- groups,
5358
- peers: globalSessionState.sessions.filter((record) => peerIds.has(record.sessionId)).map(orchestrationSessionSnapshot),
5359
- messages,
5360
- });
5361
- });
5362
- router.post('/operations/orchestration/reply', (req, res) => {
5363
- const sourceSessionId = resolveFrontendSessionId(req.body ?? {});
5364
- const messageId = typeof req.body?.messageId === 'string' ? req.body.messageId.trim() : '';
5365
- const content = typeof req.body?.content === 'string' ? req.body.content.trim().slice(0, 20_000) : '';
5366
- const original = collaborationStore.getMessage(messageId);
5367
- if (!sourceSessionId || !original || original.toSessionId !== sourceSessionId) {
5368
- return res.status(404).json({ error: '协作消息不存在或不属于当前会话' });
5369
- }
5370
- if (!original.fromSessionId)
5371
- return res.status(400).json({ error: '这条用户消息无需回复到其他会话' });
5372
- try {
5373
- collaborationStore.markRead([original.id]);
5374
- const messages = collaborationStore.send({
5375
- groupId: original.groupId,
5376
- fromSessionId: sourceSessionId,
5377
- toSessionIds: [original.fromSessionId],
5378
- kind: 'reply',
5379
- content,
5380
- threadId: original.threadId,
5381
- replyTo: original.id,
5382
- });
5383
- res.json({ ok: true, messages, delivery: tryDeliverCollaborationInbox(original.fromSessionId) });
5384
- }
5385
- catch (error) {
5386
- res.status(400).json({ error: getErrorMessage(error) });
5387
- }
5388
- });
5396
+ router.use('/operations/orchestration', collaborationRoutes({ store: collaborationStore, resolveSession: resolveFrontendSessionId, deliver: tryDeliverCollaborationInbox }));
5389
5397
  router.get('/operations/session-search', (req, res) => {
5390
5398
  const query = typeof req.query.q === 'string' ? req.query.q : '';
5391
5399
  const results = sessionSearchStore.search(query, Number(req.query.limit) || 30).map((result) => {
@@ -7158,6 +7166,8 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}, i
7158
7166
  type: 'connected',
7159
7167
  streamEpoch: TERMINAL_STREAM_EPOCH,
7160
7168
  outputProtocol: options.flowControl ? 2 : undefined,
7169
+ cols: session.cols,
7170
+ rows: session.rows,
7161
7171
  runtime,
7162
7172
  ptyBackend: session.ptyBackend || 'unknown',
7163
7173
  mode: session.mode,
@@ -1,4 +1,5 @@
1
1
  import crypto from 'crypto';
2
+ import { isEncryptedRequest } from '../federation/requestContext.js';
2
3
  import { EventEmitter } from 'events';
3
4
  import fs from 'fs';
4
5
  import os from 'os';
@@ -123,6 +124,14 @@ function credentialFingerprint() {
123
124
  export function isEnvPasswordSet() {
124
125
  return getEnvPassword() !== null;
125
126
  }
127
+ /** Server-only input for password-authenticated encrypted bootstrap. Never serialize this value. */
128
+ export function getPasswordVerifier() {
129
+ return getEnvPasswordHash() ?? readAuthFile()?.passwordHash ?? null;
130
+ }
131
+ export function getPasswordCredentialFingerprint() {
132
+ const verifier = getPasswordVerifier();
133
+ return verifier ? crypto.createHash('sha256').update(verifier).digest('hex') : null;
134
+ }
126
135
  export function isAuthEnabled() {
127
136
  if (getEnvPassword())
128
137
  return true;
@@ -317,6 +326,8 @@ export function recordLoginSuccess(ip) {
317
326
  // ── Express middleware ──
318
327
  // Returns true when the incoming request bears a valid session cookie.
319
328
  export function isRequestAuthenticated(req) {
329
+ if (isEncryptedRequest(req))
330
+ return true;
320
331
  pruneExpiredSessions();
321
332
  const token = req.cookies?.[AUTH_COOKIE];
322
333
  return isSessionValid(typeof token === 'string' ? token : undefined);
@@ -338,6 +349,8 @@ export function renewSessionMiddleware(req, res, next) {
338
349
  // Bypass paths are public (login/status/health/static).
339
350
  export function requireAuth(options) {
340
351
  return (req, res, next) => {
352
+ if (isEncryptedRequest(req))
353
+ return next();
341
354
  if (!isAuthEnabled())
342
355
  return next();
343
356
  if (options?.bypass?.(req))
@@ -1,4 +1,5 @@
1
1
  import crypto from 'crypto';
2
+ import { isEncryptedRequest } from '../federation/requestContext.js';
2
3
  import { getCookieSecurityOptions } from './cookieSecurity.js';
3
4
  /**
4
5
  * CSRF令牌管理器
@@ -65,6 +66,8 @@ export class CsrfProtection {
65
66
  */
66
67
  verifyMiddleware(options) {
67
68
  return (req, res, next) => {
69
+ if (isEncryptedRequest(req))
70
+ return next();
68
71
  if (options?.bypass?.(req)) {
69
72
  return next();
70
73
  }
@@ -30,9 +30,14 @@ export const securityHeaders = (req, res, next) => {
30
30
  // Enforce sandbox even for top-level navigation to user-controlled HTML/SVG.
31
31
  // allow-same-origin must NEVER be added: that would expose the terminal API.
32
32
  const preview = /^\/api\/terminal\/fs(?:\/|$)/i.test(req.path);
33
- res.setHeader('Content-Security-Policy', preview
34
- ? "sandbox allow-scripts; frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
35
- : "script-src 'self' 'wasm-unsafe-eval'; script-src-attr 'none'; worker-src 'self' blob:; frame-ancestors 'self'; object-src 'none'; base-uri 'self'");
33
+ const host = req.get('host') ?? '';
34
+ const shellSource = /^[a-z0-9.\-:\[\]]+$/i.test(host) ? `http://${host}/preview-shell.html https://${host}/preview-shell.html` : "'none'";
35
+ const shell = req.path === '/preview-shell.html';
36
+ res.setHeader('Content-Security-Policy', shell
37
+ ? "sandbox allow-scripts; default-src 'none'; script-src 'unsafe-inline' blob: https: http:; style-src 'unsafe-inline' blob: https:; img-src blob: data: https:; font-src blob: data: https:; media-src blob: data: https:; connect-src https:; frame-src 'none'; frame-ancestors 'self'; object-src 'none'; base-uri https://termdock-preview.invalid; form-action 'none'"
38
+ : preview
39
+ ? "sandbox allow-scripts; frame-ancestors 'self'; object-src 'none'; base-uri 'self'"
40
+ : `script-src 'self' 'wasm-unsafe-eval'; script-src-attr 'none'; worker-src 'self' blob:; frame-src ${shellSource}; frame-ancestors 'self'; object-src 'none'; base-uri 'self'`);
36
41
  if (getPublicOrigin()) {
37
42
  res.setHeader('Strict-Transport-Security', 'max-age=31536000');
38
43
  if (!isAuthEnabled()) {