termdock 1.4.174 → 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 (355) hide show
  1. package/.desktop-dist/main.js +154 -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-CdbPsjKS.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-hdxcM9yF.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-BZ454pgM.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-C185Omck.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-DNt-DTCl.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-C5Nwm_Qz.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-C5aR_7V9.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-DjV6ZNwd.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-Ct7nd4cL.js → chunk-2Q5K7J3B-BsVAXGLa.js} +1 -1
  49. package/dist/client/assets/{chunk-5VM5RSS4-CXem4UYP.js → chunk-5VM5RSS4-BKtILsst.js} +1 -1
  50. package/dist/client/assets/{chunk-F27PBJKO-DyMNEawE.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-BsW7iIrh.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-BsW7iIrh.js.gz → chunk-IMKFNOWR-MVtVQeTr.js.gz} +0 -0
  56. package/dist/client/assets/{chunk-JWPE2WC7-EO8E1q1J.js → chunk-JWPE2WC7-DliIIMe7.js} +1 -1
  57. package/dist/client/assets/{chunk-POPQ4Y6H-0One8T9x.js → chunk-POPQ4Y6H-XBSus3IC.js} +1 -1
  58. package/dist/client/assets/{chunk-SVP7TREG-CDu41dkj.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-BACjgX9v.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-BJWowF6E.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-WTwHOCdm.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-D4y8TkaO.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-BzN8Ggk_.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-CwMYtt5E.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-BnTeHiAb.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-BNlh251g.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-B5FftXkQ.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-C51ZNXR2.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-CXoZH2qf.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-C7YFLlx4.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-BNQlS7-g.js → erDiagram-RLTQ6QDP-DbjtZnJx.js} +1 -1
  98. package/dist/client/assets/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-DIQXZ06I.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-BaJQfOqD.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-tQ4E6pwN.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-DlxiaPv1.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-DTOUeQrN.js → infoDiagram-27XIBGKW-BH4AynkM.js} +1 -1
  119. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-8sBi-gla.js → ishikawaDiagram-5VMMS53U-BQw90AKR.js} +1 -1
  120. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-8sBi-gla.js.br → 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-ZwikziWh.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-ZwikziWh.js.gz → journeyDiagram-3NMN7TZE-DISkQocf.js.gz} +0 -0
  125. package/dist/client/assets/{kanban-definition-UXKFOSKX-wjuw2jQ4.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-nASMIj-E.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-pGaQeqW5.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-CKEBWbQz.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-CBjYcnwd.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-DJqvclIp.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-DLV4WPwc.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-BN7Zpsb9.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-DfHX6MUm.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-tQVSFg_k.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-C7JW1-Wh.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-BVeDiFDI.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-CBRaCpeP.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-CiLpGRuf.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-BkzVtkVY.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-DKyfGITP.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-CRgkkrgx.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-Cr7yS4CZ.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-DkPYl6E0.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 +10 -2
  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 +249 -21
  210. package/dist/server/cli.js +28 -168
  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 +40 -72
  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-hj8I8Cwy.js +0 -2
  240. package/dist/client/assets/ChangeWalkthroughPanel-hj8I8Cwy.js.br +0 -0
  241. package/dist/client/assets/ChangeWalkthroughPanel-hj8I8Cwy.js.gz +0 -0
  242. package/dist/client/assets/DagPlayground-BRoB8JX5.js +0 -1
  243. package/dist/client/assets/DagPlayground-BRoB8JX5.js.br +0 -0
  244. package/dist/client/assets/DagPlayground-BRoB8JX5.js.gz +0 -0
  245. package/dist/client/assets/DiffLab-CdbPsjKS.js.br +0 -0
  246. package/dist/client/assets/DiffLab-CdbPsjKS.js.gz +0 -0
  247. package/dist/client/assets/DiffReview-MQXjrRsW.js +0 -1
  248. package/dist/client/assets/DiffReview-MQXjrRsW.js.br +0 -0
  249. package/dist/client/assets/DiffReview-MQXjrRsW.js.gz +0 -0
  250. package/dist/client/assets/DiffReviewLab-C47CNvDM.js +0 -2
  251. package/dist/client/assets/DiffReviewLab-C47CNvDM.js.br +0 -0
  252. package/dist/client/assets/DiffReviewLab-C47CNvDM.js.gz +0 -0
  253. package/dist/client/assets/DiffViewer-hdxcM9yF.js.br +0 -0
  254. package/dist/client/assets/DiffViewer-hdxcM9yF.js.gz +0 -0
  255. package/dist/client/assets/ModelPreview-BZ454pgM.js.br +0 -0
  256. package/dist/client/assets/ModelPreview-BZ454pgM.js.gz +0 -0
  257. package/dist/client/assets/RightSidebar-i0Oq68YI.js +0 -55
  258. package/dist/client/assets/RightSidebar-i0Oq68YI.js.br +0 -0
  259. package/dist/client/assets/RightSidebar-i0Oq68YI.js.gz +0 -0
  260. package/dist/client/assets/abnfDiagram-VCTEODGH-C185Omck.js.br +0 -0
  261. package/dist/client/assets/abnfDiagram-VCTEODGH-C185Omck.js.gz +0 -0
  262. package/dist/client/assets/app-DAp8rVMc.js +0 -175
  263. package/dist/client/assets/app-DAp8rVMc.js.br +0 -0
  264. package/dist/client/assets/app-DAp8rVMc.js.gz +0 -0
  265. package/dist/client/assets/arc-DNt-DTCl.js.br +0 -0
  266. package/dist/client/assets/arc-DNt-DTCl.js.gz +0 -0
  267. package/dist/client/assets/architectureDiagram-5GKGNRK7-C5Nwm_Qz.js.br +0 -0
  268. package/dist/client/assets/architectureDiagram-5GKGNRK7-C5Nwm_Qz.js.gz +0 -0
  269. package/dist/client/assets/blockDiagram-I7D4REHJ-C5aR_7V9.js.br +0 -0
  270. package/dist/client/assets/blockDiagram-I7D4REHJ-C5aR_7V9.js.gz +0 -0
  271. package/dist/client/assets/c4Diagram-7LVT6UL2-DjV6ZNwd.js.br +0 -0
  272. package/dist/client/assets/c4Diagram-7LVT6UL2-DjV6ZNwd.js.gz +0 -0
  273. package/dist/client/assets/channel-uhLrATdy.js +0 -1
  274. package/dist/client/assets/chunk-F27PBJKO-DyMNEawE.js.br +0 -0
  275. package/dist/client/assets/chunk-F27PBJKO-DyMNEawE.js.gz +0 -0
  276. package/dist/client/assets/chunk-IMKFNOWR-BsW7iIrh.js.br +0 -0
  277. package/dist/client/assets/chunk-SVP7TREG-CDu41dkj.js.br +0 -0
  278. package/dist/client/assets/chunk-SVP7TREG-CDu41dkj.js.gz +0 -0
  279. package/dist/client/assets/chunk-TICWLB2K-BACjgX9v.js.br +0 -0
  280. package/dist/client/assets/chunk-TICWLB2K-BACjgX9v.js.gz +0 -0
  281. package/dist/client/assets/classDiagram-ZZMXUADV-CnxFfzm9.js +0 -1
  282. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CnxFfzm9.js +0 -1
  283. package/dist/client/assets/cose-bilkent-JH36ORCC-WTwHOCdm.js.br +0 -0
  284. package/dist/client/assets/cose-bilkent-JH36ORCC-WTwHOCdm.js.gz +0 -0
  285. package/dist/client/assets/cynefin-OW5HDTMX-D4y8TkaO.js.br +0 -0
  286. package/dist/client/assets/cynefin-OW5HDTMX-D4y8TkaO.js.gz +0 -0
  287. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BzN8Ggk_.js.br +0 -0
  288. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-BzN8Ggk_.js.gz +0 -0
  289. package/dist/client/assets/dagre-GXQ25YYZ-CwMYtt5E.js.br +0 -0
  290. package/dist/client/assets/dagre-GXQ25YYZ-CwMYtt5E.js.gz +0 -0
  291. package/dist/client/assets/diagram-S7CK7UJ4-BnTeHiAb.js.br +0 -0
  292. package/dist/client/assets/diagram-S7CK7UJ4-BnTeHiAb.js.gz +0 -0
  293. package/dist/client/assets/diagram-UQ7AKVKN-BNlh251g.js.br +0 -0
  294. package/dist/client/assets/diagram-UQ7AKVKN-BNlh251g.js.gz +0 -0
  295. package/dist/client/assets/diagram-VSXAHHWV-B5FftXkQ.js.br +0 -0
  296. package/dist/client/assets/diagram-VSXAHHWV-B5FftXkQ.js.gz +0 -0
  297. package/dist/client/assets/diagram-VX7I27RA-C51ZNXR2.js.br +0 -0
  298. package/dist/client/assets/diagram-VX7I27RA-C51ZNXR2.js.gz +0 -0
  299. package/dist/client/assets/diagram-Z3DM3KII-CXoZH2qf.js.br +0 -0
  300. package/dist/client/assets/diagram-Z3DM3KII-CXoZH2qf.js.gz +0 -0
  301. package/dist/client/assets/ebnfDiagram-PWID7BFC-C7YFLlx4.js.br +0 -0
  302. package/dist/client/assets/ebnfDiagram-PWID7BFC-C7YFLlx4.js.gz +0 -0
  303. package/dist/client/assets/erDiagram-RLTQ6QDP-BNQlS7-g.js.br +0 -0
  304. package/dist/client/assets/erDiagram-RLTQ6QDP-BNQlS7-g.js.gz +0 -0
  305. package/dist/client/assets/flowDiagram-HODETNUW-DIQXZ06I.js.br +0 -0
  306. package/dist/client/assets/flowDiagram-HODETNUW-DIQXZ06I.js.gz +0 -0
  307. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BaJQfOqD.js.br +0 -0
  308. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BaJQfOqD.js.gz +0 -0
  309. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tQ4E6pwN.js.br +0 -0
  310. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-tQ4E6pwN.js.gz +0 -0
  311. package/dist/client/assets/index-DlxiaPv1.css.br +0 -0
  312. package/dist/client/assets/index-DlxiaPv1.css.gz +0 -0
  313. package/dist/client/assets/ishikawaDiagram-5VMMS53U-8sBi-gla.js.gz +0 -0
  314. package/dist/client/assets/journeyDiagram-3NMN7TZE-ZwikziWh.js.br +0 -0
  315. package/dist/client/assets/kanban-definition-UXKFOSKX-wjuw2jQ4.js.br +0 -0
  316. package/dist/client/assets/kanban-definition-UXKFOSKX-wjuw2jQ4.js.gz +0 -0
  317. package/dist/client/assets/layout-nASMIj-E.js.br +0 -0
  318. package/dist/client/assets/layout-nASMIj-E.js.gz +0 -0
  319. package/dist/client/assets/linear-pGaQeqW5.js.br +0 -0
  320. package/dist/client/assets/linear-pGaQeqW5.js.gz +0 -0
  321. package/dist/client/assets/mermaid.core-CKEBWbQz.js.br +0 -0
  322. package/dist/client/assets/mermaid.core-CKEBWbQz.js.gz +0 -0
  323. package/dist/client/assets/mindmap-definition-YA3MSWOX-CBjYcnwd.js.br +0 -0
  324. package/dist/client/assets/mindmap-definition-YA3MSWOX-CBjYcnwd.js.gz +0 -0
  325. package/dist/client/assets/pegDiagram-XKGWAZYB-DJqvclIp.js.br +0 -0
  326. package/dist/client/assets/pegDiagram-XKGWAZYB-DJqvclIp.js.gz +0 -0
  327. package/dist/client/assets/pieDiagram-E7YTZNPT-DLV4WPwc.js.br +0 -0
  328. package/dist/client/assets/pieDiagram-E7YTZNPT-DLV4WPwc.js.gz +0 -0
  329. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BN7Zpsb9.js.br +0 -0
  330. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BN7Zpsb9.js.gz +0 -0
  331. package/dist/client/assets/railroadDiagram-O6MQD6OU-DfHX6MUm.js.br +0 -0
  332. package/dist/client/assets/railroadDiagram-O6MQD6OU-DfHX6MUm.js.gz +0 -0
  333. package/dist/client/assets/requirementDiagram-BXWQKSXE-tQVSFg_k.js.br +0 -0
  334. package/dist/client/assets/requirementDiagram-BXWQKSXE-tQVSFg_k.js.gz +0 -0
  335. package/dist/client/assets/sankeyDiagram-P5KCCOFB-C7JW1-Wh.js.br +0 -0
  336. package/dist/client/assets/sankeyDiagram-P5KCCOFB-C7JW1-Wh.js.gz +0 -0
  337. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BVeDiFDI.js.br +0 -0
  338. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BVeDiFDI.js.gz +0 -0
  339. package/dist/client/assets/sizeCapture-INFHLROL-CBRaCpeP.js.br +0 -0
  340. package/dist/client/assets/sizeCapture-INFHLROL-CBRaCpeP.js.gz +0 -0
  341. package/dist/client/assets/sparkles-D0cwIvB0.js +0 -1
  342. package/dist/client/assets/stateDiagram-D77RDMKH-CiLpGRuf.js.br +0 -0
  343. package/dist/client/assets/stateDiagram-D77RDMKH-CiLpGRuf.js.gz +0 -0
  344. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BRbS0rv2.js +0 -1
  345. package/dist/client/assets/swimlanes-42K2YHIH-BkzVtkVY.js.br +0 -0
  346. package/dist/client/assets/swimlanes-42K2YHIH-BkzVtkVY.js.gz +0 -0
  347. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CeWFezkt.js +0 -8
  348. package/dist/client/assets/timeline-definition-24CTP7MA-DKyfGITP.js.br +0 -0
  349. package/dist/client/assets/timeline-definition-24CTP7MA-DKyfGITP.js.gz +0 -0
  350. package/dist/client/assets/vennDiagram-4TSXK5OY-CRgkkrgx.js.br +0 -0
  351. package/dist/client/assets/vennDiagram-4TSXK5OY-CRgkkrgx.js.gz +0 -0
  352. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cr7yS4CZ.js.br +0 -0
  353. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Cr7yS4CZ.js.gz +0 -0
  354. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DkPYl6E0.js.br +0 -0
  355. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DkPYl6E0.js.gz +0 -0
@@ -1,7 +1,14 @@
1
+ import { createOpenAccessRouter } from './federation/openAccess.js';
2
+ import { createPasswordLoginRouter } from './federation/passwordLoginRoutes.js';
1
3
  import { apiAccessGate, isTrustedLocalRequest } from './utils/apiAccess.js';
4
+ import { isEncryptedRequest } from './federation/requestContext.js';
5
+ import { createFederationRuntime } from './federation/runtime.js';
6
+ import { RouteAccess } from './federation/routeAccess.js';
7
+ import { RouteInvitationStore } from './federation/routeInvitations.js';
8
+ import { attachRegisteredDirectTargets, verifyDirectTarget } from './federation/directRoutes.js';
9
+ import { secureChannelOriginAllowed } from './federation/originPolicy.js';
10
+ import { RelayRouter } from './federation/relay.js';
2
11
  import { assertPublicSecurity, securityHeaders } from './utils/publicSecurity.js';
3
- import { guardWebSocketSession } from './utils/webSocketSecurity.js';
4
- import { readTerminalHandshakeDimensions } from './utils/terminalHandshakeDimensions.js';
5
12
  import { apiCachePolicy } from './utils/apiCachePolicy.js';
6
13
  import 'dotenv/config';
7
14
  import express from 'express';
@@ -23,7 +30,7 @@ import { createOnboardingRouter } from './routes/onboarding.js';
23
30
  import { createLocalRouter } from './routes/local.js';
24
31
  import { csrfProtection } from './utils/csrfProtection.js';
25
32
  import { pathValidator } from './utils/pathValidator.js';
26
- import { isUpgradeRequestAuthenticated, requireAuth, renewSessionMiddleware } from './utils/authProtection.js';
33
+ import { requireAuth, renewSessionMiddleware } from './utils/authProtection.js';
27
34
  import { localAccessManager } from './utils/localAccess.js';
28
35
  import { isAllowedHost, isUpgradeOriginAllowed, validateHostMiddleware, validateOriginMiddleware, } from './utils/requestSecurity.js';
29
36
  import { getCookieSecurityOptions, setSecureCookieMode } from './utils/cookieSecurity.js';
@@ -37,22 +44,6 @@ import { getTermdockVersion, TERMDOCK_CAPABILITIES, TERMDOCK_PROTOCOL_VERSION, }
37
44
  import { PORT, DEFAULT_HOST } from './config.js';
38
45
  const CLIENT_STATE_COOKIE = 'termdock-client';
39
46
  export const DEFAULT_PORT = PORT.backend;
40
- /** Read one cookie from a raw Cookie header (upgrade requests bypass the
41
- * cookie-parser middleware). Values are percent-encoded. */
42
- function getCookieValue(cookieHeader, name) {
43
- if (!cookieHeader)
44
- return undefined;
45
- const pattern = new RegExp(`(?:^|;\\s*)${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}=([^;]*)`);
46
- const match = cookieHeader.match(pattern);
47
- if (!match)
48
- return undefined;
49
- try {
50
- return decodeURIComponent(match[1]);
51
- }
52
- catch {
53
- return match[1];
54
- }
55
- }
56
47
  const CLIENT_LOG_DEDUP_WINDOW_MS = 5_000;
57
48
  const CLIENT_LOG_RATE_WINDOW_MS = 10_000;
58
49
  const CLIENT_LOG_RATE_LIMIT = 120;
@@ -146,6 +137,17 @@ export function createApp(options = {}) {
146
137
  app.disable('x-powered-by');
147
138
  app.use(securityHeaders);
148
139
  app.use(validateHostMiddleware);
140
+ if (options.secureRequired)
141
+ app.use('/api/auth/open', createOpenAccessRouter(() => app.locals.passwordRuntime?.serviceId));
142
+ if (options.secureRequired)
143
+ app.use('/api/auth/password', createPasswordLoginRouter(() => app.locals.passwordRuntime));
144
+ app.use('/api', (req, res, next) => {
145
+ if (!options.secureRequired || isEncryptedRequest(req) || isTrustedLocalRequest(req, options.localApiToken))
146
+ return next();
147
+ if (req.method === 'GET' && ['/meta', '/auth/status'].includes(req.path))
148
+ return next();
149
+ res.status(426).json({ error: 'Pair this device to use an encrypted connection', code: 'E2EE_REQUIRED' });
150
+ });
149
151
  // Authenticate before allocating request bodies, including future API routes.
150
152
  app.use(cookieParser());
151
153
  app.use('/api', renewSessionMiddleware);
@@ -393,12 +395,78 @@ export function startServer(options = {}) {
393
395
  });
394
396
  runtimeMonitor.start();
395
397
  const app = createApp({
398
+ secureRequired: true,
396
399
  port: options.onboardingPort ?? port,
397
400
  httpsCaPath: options.httpsCaPath,
398
401
  localApiToken: options.localApiToken,
399
402
  runtimeMonitor,
400
403
  });
401
404
  const { server, scheme } = createServerForApp(app, options);
405
+ let entrySubjectAllowed = (_subjectId) => false;
406
+ let routeInvitations;
407
+ const routeAccess = new RouteAccess(path.join(homedir(), '.termdock', 'federation', 'routes.json'), Date.now, (subjectId, targetServiceId) => routeInvitations?.allows(subjectId, targetServiceId) === true);
408
+ const relayRouter = new RelayRouter({
409
+ authenticate: context => context,
410
+ allowRegister: (principal, serviceId) => routeAccess.allowRegister(principal, serviceId),
411
+ allowRoute: (principal, serviceId) => routeAccess.allowRoute(principal, serviceId),
412
+ });
413
+ const dynamicDirectTargets = new Map();
414
+ server.once('close', () => { for (const close of dynamicDirectTargets.values())
415
+ close(); });
416
+ try {
417
+ const detachDirectTargets = attachRegisteredDirectTargets(relayRouter, routeAccess.configuredDirectTargets(), path.join(homedir(), '.termdock', 'federation'));
418
+ server.once('close', detachDirectTargets);
419
+ }
420
+ catch {
421
+ console.error('Configured direct routes unavailable; target access remains closed.');
422
+ }
423
+ server.once('close', () => relayRouter.close());
424
+ const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
425
+ terminal: handleTerminalWebSocket, control: handleControlWebSocket,
426
+ }, {
427
+ listRouteAccess: () => routeInvitations?.list() || [],
428
+ grantRouteAccess: async (issuerId, targetServiceId, subjectId, url) => {
429
+ if (!routeInvitations)
430
+ throw new Error('ROUTE_NOT_AVAILABLE');
431
+ if (!routeAccess.hasConfiguredTarget(targetServiceId)) {
432
+ if (!url)
433
+ throw new Error('ROUTE_TARGET_NOT_CONFIGURED');
434
+ const target = { serviceId: targetServiceId, url };
435
+ await verifyDirectTarget(target);
436
+ const runtime = await federation;
437
+ if (!runtime.store.authorize({ subjectId: issuerId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed)
438
+ throw new Error('AUTHORIZATION_DENIED');
439
+ routeAccess.addDirectTarget(target);
440
+ if (!dynamicDirectTargets.has(targetServiceId))
441
+ dynamicDirectTargets.set(targetServiceId, attachRegisteredDirectTargets(relayRouter, [target]));
442
+ }
443
+ return routeInvitations.grant(issuerId, targetServiceId, subjectId);
444
+ },
445
+ revokeRouteAccess: id => routeInvitations?.revoke(id) || false,
446
+ issueRouteTicket: (subjectId, serviceId) => routeAccess.issueRouteTicket(subjectId, serviceId),
447
+ hasRouteGrant: (subjectId, serviceId) => routeInvitations?.allows(subjectId, serviceId) === true,
448
+ createRouteInvitation: (issuerId, serviceId) => {
449
+ if (!routeInvitations)
450
+ throw new Error('ROUTE_NOT_AVAILABLE');
451
+ return routeInvitations.create(issuerId, serviceId);
452
+ },
453
+ consumeRouteInvitation: (code, subjectId) => {
454
+ if (!routeInvitations)
455
+ throw new Error('ROUTE_NOT_AVAILABLE');
456
+ return routeInvitations.consume(code, subjectId);
457
+ },
458
+ });
459
+ void federation.then(runtime => {
460
+ app.locals.passwordRuntime = runtime;
461
+ entrySubjectAllowed = subjectId => runtime.store.authorize({ subjectId, serviceId: runtime.serviceId, action: 'authorization.manage' }).allowed;
462
+ routeInvitations = new RouteInvitationStore({
463
+ filePath: path.join(homedir(), '.termdock', 'federation', 'route-invitations.json'), serviceId: runtime.serviceId,
464
+ issuerAllowed: subjectId => entrySubjectAllowed(subjectId),
465
+ targetAvailable: serviceId => routeAccess.hasConfiguredTarget(serviceId) && relayRouter.hasRoute(serviceId),
466
+ });
467
+ }).catch(() => console.error('Route invitations unavailable; uninitialized route grants remain denied.'));
468
+ void federation.catch(() => console.error('Encrypted access initialization failed; business access remains closed.'));
469
+ server.once('close', () => { void federation.then(runtime => runtime.close()).catch(() => { }); });
402
470
  server.headersTimeout = 10_000;
403
471
  server.requestTimeout = 120_000;
404
472
  server.keepAliveTimeout = 5_000;
@@ -464,14 +532,12 @@ export function startServer(options = {}) {
464
532
  zlibDeflateOptions: { level: 6, memLevel: 7 },
465
533
  },
466
534
  });
467
- const WS_PATH_RE = /^\/api\/terminal\/([^/]+)\/ws$/;
468
- const CONTROL_WS_PATH = '/api/control/ws';
469
535
  server.on('upgrade', (request, socket, head) => {
470
536
  if (wss.clients.size >= 256) {
471
537
  socket.end('HTTP/1.1 503 Service Unavailable\r\nConnection: close\r\n\r\n');
472
538
  return;
473
539
  }
474
- if (!isAllowedHost(request.headers.host) || !isUpgradeOriginAllowed(request.headers.origin, request.headers.host)) {
540
+ if (!isAllowedHost(request.headers.host)) {
475
541
  socket.write('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
476
542
  socket.destroy();
477
543
  return;
@@ -485,41 +551,38 @@ export function startServer(options = {}) {
485
551
  return;
486
552
  }
487
553
  const pathname = url.pathname;
488
- // Reject the upgrade before the WebSocket handshake completes if auth
489
- // is enabled and the client cookie is missing/expired. We respond with
490
- // a real HTTP 401 so the browser surfaces a useful error. Applied to
491
- // both per-terminal and control paths.
492
- const cookieHeader = request.headers.cookie;
493
- if (!isUpgradeRequestAuthenticated(typeof cookieHeader === 'string' ? cookieHeader : undefined)) {
494
- socket.write('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
495
- socket.destroy();
554
+ if (pathname === '/api/federation/relay') {
555
+ // Native relay peers have no Origin. Authenticate explicitly before upgrade.
556
+ if (scheme !== 'https' || (request.headers.origin && !isUpgradeOriginAllowed(request.headers.origin, request.headers.host))) {
557
+ socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
558
+ return;
559
+ }
560
+ const principal = routeAccess.authenticate(request.headers.authorization, url.searchParams.get('routeToken'));
561
+ if (!principal) {
562
+ socket.end('HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n');
563
+ return;
564
+ }
565
+ wss.handleUpgrade(request, socket, head, ws => {
566
+ void relayRouter.attach(ws, principal).then(attached => { if (attached)
567
+ ws.send(JSON.stringify({ type: 'ready' })); });
568
+ });
496
569
  return;
497
570
  }
498
- // Control WS: server-pushed global client-state events. One per browser.
499
- if (pathname === CONTROL_WS_PATH) {
500
- const clientId = crypto.randomUUID();
571
+ if (pathname === '/api/federation/secure') {
572
+ // This exception applies only to the cookie-independent Noise handshake.
573
+ // Target pinning and device authorization remain mandatory inside it.
574
+ if (!secureChannelOriginAllowed(request.headers.origin, () => isUpgradeOriginAllowed(request.headers.origin, request.headers.host))) {
575
+ socket.end('HTTP/1.1 403 Forbidden\r\nConnection: close\r\n\r\n');
576
+ return;
577
+ }
501
578
  wss.handleUpgrade(request, socket, head, (ws) => {
502
- handleControlWebSocket(ws, clientId);
503
- guardWebSocketSession(ws, cookieHeader);
579
+ void federation.then(runtime => runtime.accept(ws, { allowOpenAccess: !!request.headers.origin && isUpgradeOriginAllowed(request.headers.origin, request.headers.host) })).catch(() => ws.close(1011, 'Encrypted access unavailable'));
504
580
  });
505
581
  return;
506
582
  }
507
- // Per-terminal WS: bidirectional I/O for a single terminal session.
508
- const match = pathname.match(WS_PATH_RE);
509
- if (!match) {
510
- socket.destroy();
511
- return;
512
- }
513
- const sessionId = match[1];
514
- const clientId = crypto.randomUUID();
515
- const pushClientId = getCookieValue(cookieHeader, CLIENT_STATE_COOKIE);
516
- // 短线重连时客户端会带上 ?since=<lastSeq>,让服务端只补发增量。
517
- const sinceParam = url.searchParams.get('since');
518
- const sinceSeq = sinceParam ? Math.max(0, Number.parseInt(sinceParam, 10) || 0) : 0;
519
- wss.handleUpgrade(request, socket, head, (ws) => {
520
- handleTerminalWebSocket(ws, sessionId, clientId, { sinceSeq, pushClientId, streamEpoch: url.searchParams.get('epoch') ?? undefined, flowControl: url.searchParams.get('flow') === '2', independentTmux: url.searchParams.get('transport') === 'tmux-client', outputActive: url.searchParams.get('active') !== '0' }, readTerminalHandshakeDimensions(url.searchParams));
521
- guardWebSocketSession(ws, cookieHeader);
522
- });
583
+ // Business WebSockets have no plaintext fallback after pairing migration.
584
+ socket.end('HTTP/1.1 426 Upgrade Required\r\nConnection: close\r\n\r\n');
585
+ return;
523
586
  });
524
587
  server.listen(port, host, () => {
525
588
  const displayHost = host === '0.0.0.0' ? 'localhost' : host;
@@ -0,0 +1,122 @@
1
+ const METHODS = new Set(['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE']);
2
+ export function canonicalApiPath(path) {
3
+ if (typeof path !== 'string' || !path.startsWith('/api/') || /[\\\x00-\x20\x7f#]/.test(path))
4
+ return null;
5
+ const pathname = path.split('?')[0];
6
+ if (pathname.includes('//'))
7
+ return null;
8
+ try {
9
+ const segments = pathname.split('/').map((segment, index) => {
10
+ if (index === 0 || (segment === '' && index === pathname.split('/').length - 1))
11
+ return segment;
12
+ const value = decodeURIComponent(segment);
13
+ // Decode exactly once. Never allow an encoded separator, traversal segment,
14
+ // control byte, invalid UTF-8 or a second escape layer to change routing.
15
+ if (!value || value === '.' || value === '..' || /[\\/\x00-\x1f\x7f]/.test(value) || /%[a-f0-9]{2}/i.test(value))
16
+ throw new Error('Ambiguous API path');
17
+ return value;
18
+ });
19
+ const canonical = segments.join('/');
20
+ if (canonical.endsWith('/') && !canonical.startsWith('/api/terminal/fs/preview/'))
21
+ return null;
22
+ return canonical;
23
+ }
24
+ catch {
25
+ return null;
26
+ }
27
+ }
28
+ const pathOnly = canonicalApiPath;
29
+ /** Restricts a full-service tunnel to business APIs, excluding authentication,
30
+ * local-only operations and recursive federation/bootstrap endpoints. */
31
+ export function isBusinessApiPath(method, path) {
32
+ if (!METHODS.has(method))
33
+ return false;
34
+ const pathname = pathOnly(path);
35
+ if (!pathname)
36
+ return false;
37
+ return !/^\/api\/(?:auth|local|federation|onboarding|csrf)(?:\/|$)/i.test(pathname);
38
+ }
39
+ const fileReads = new Set(['list', 'watch', 'search', 'read', 'git-blob', 'eda-preview', 'eda-inspect', 'blob', 'video', 'download', 'diff', 'diff-files', 'untracked-files', 'git-context', 'git-bundle', 'change-audit', 'branch-diff', 'commit-diff', 'git-recent-commits', 'branch-audit', 'git-action/status', 'local-open-availability']);
40
+ const filePosts = new Set(['git-action', 'apply-hunk', 'upload', 'open-in-file-browser']);
41
+ const settings = new Set(['settings', 'toolbar-presets', 'program-detection', 'context-draft']);
42
+ const serviceReads = new Set(['update', 'auto-title/catalog', 'agent-hooks', 'agent-launchers', 'agent-plugins', 'quota', 'tmux/status', 'tmux/sessions', 'agent-resume-history']);
43
+ const reservedSessionIds = new Set(['fs', 'settings', 'client-state', 'session-inventory', 'operations', 'tmux', 'create', 'force-kill', 'serialize-state', 'agent-resume-history', 'toolbar-presets', 'context-draft', 'program-detection', 'update', 'quota', 'agent-hooks', 'agent-launchers', 'agent-plugins', 'agent-plugin-icon', 'auto-title', 'directory-suggestions']);
44
+ export function resolveAccessRequest(method, path) {
45
+ if (!isBusinessApiPath(method, path))
46
+ return null;
47
+ const pathname = pathOnly(path);
48
+ const read = method === 'GET' || method === 'HEAD';
49
+ if (/^\/api\/notifications\/(status|subscribe|preferences|unsubscribe)$/.test(pathname)) {
50
+ if (read && pathname.endsWith('/status'))
51
+ return { action: 'service.view' };
52
+ if (method === 'POST' && !pathname.endsWith('/status'))
53
+ return { action: 'service.configure' };
54
+ return null;
55
+ }
56
+ const prefix = '/api/terminal/';
57
+ if (!pathname.startsWith(prefix))
58
+ return null;
59
+ const route = pathname.slice(prefix.length);
60
+ if (route.startsWith('fs/')) {
61
+ const op = route.slice(3);
62
+ if (read && (fileReads.has(op) || /^preview\/.+/.test(op)))
63
+ return { action: 'file.read' };
64
+ if ((method === 'POST' && filePosts.has(op)) || (method === 'DELETE' && ['file', 'change-audit', 'branch-audit'].includes(op)) || (method === 'GET' && op === 'cancel-slot'))
65
+ return { action: 'file.write' };
66
+ return null;
67
+ }
68
+ if (read && route === 'session-inventory')
69
+ return { action: 'service.view', metadata: 'session-inventory' };
70
+ if (read && route === 'client-state')
71
+ return { action: 'service.view', metadata: 'global-client-state' };
72
+ if (settings.has(route)) {
73
+ if (read)
74
+ return { action: 'service.view' };
75
+ if (method === 'PUT' || (method === 'DELETE' && route === 'program-detection'))
76
+ return { action: 'service.configure' };
77
+ return null;
78
+ }
79
+ if (read && serviceReads.has(route))
80
+ return { action: 'service.view' };
81
+ if (read && route === 'directory-suggestions')
82
+ return { action: 'file.read' };
83
+ if (method === 'POST' && route === 'create')
84
+ return { action: 'service.create-session' };
85
+ const session = /^([^/]+)(?:\/(health|attach|stream|input|resize|tmux|restart|agent-resume))?$/.exec(route);
86
+ if (session && !reservedSessionIds.has(session[1].toLowerCase())) {
87
+ const sessionId = session[1];
88
+ const op = session[2];
89
+ if (read && ['health', 'attach', 'stream', 'agent-resume'].includes(op))
90
+ return { action: 'session.view', sessionId };
91
+ if (method === 'POST' && op === 'input')
92
+ return { action: 'session.input', sessionId };
93
+ if (method === 'POST' && op === 'resize')
94
+ return { action: 'session.resize', sessionId };
95
+ if (method === 'DELETE' && !op)
96
+ return { action: 'session.terminate', sessionId };
97
+ // tmux switch-session can reach other backends; restart/agent-resume accept
98
+ // launch configuration. Their unrestricted bodies require service authority.
99
+ if (method === 'POST' && ['tmux', 'restart', 'agent-resume'].includes(op))
100
+ return { action: 'service.configure' };
101
+ }
102
+ // Complex operations manipulate global state or accept additional resource IDs.
103
+ // An unregistered operation intentionally receives no custom-grant mapping.
104
+ return null;
105
+ }
106
+ /** WS control mapping; caller must bind sessionId to the opened channel. */
107
+ export function terminalMessageAction(type) {
108
+ switch (type) {
109
+ case 'input':
110
+ case 'focus':
111
+ case 'agent-review-ack': return 'session.input';
112
+ case 'resize': return 'session.resize';
113
+ case 'tmux': return 'service.configure';
114
+ case 'ping':
115
+ case 'viewing':
116
+ case 'output-subscription':
117
+ case 'output-ack': return 'session.view';
118
+ // Legacy flow-control can pause the shared PTY; grant only to controllers.
119
+ case 'flow-control': return 'session.input';
120
+ default: return null;
121
+ }
122
+ }
@@ -0,0 +1,226 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdirSync, readFileSync, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync } from 'node:fs';
3
+ import { dirname } from 'node:path';
4
+ const id = (v) => typeof v === 'string' && v.length > 0 && v.length <= 512 && !/[\s\x00-\x1f]/.test(v);
5
+ const action = (v) => typeof v === 'string' && (v === 'service:*' || /^[a-z][a-z0-9_-]*\.[a-z][a-z0-9_-]*$/.test(v));
6
+ const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
7
+ function assert(value, message) { if (!value)
8
+ throw new Error(`Invalid authorization: ${message}`); }
9
+ function keys(v, allowed) { assert(Object.keys(v).every(k => allowed.includes(k)), 'unknown field'); }
10
+ function ids(v) { return Array.isArray(v) && v.length > 0 && v.length <= 10000 && v.every(id) && new Set(v).size === v.length; }
11
+ function validateInput(v) {
12
+ assert(object(v), 'grant object');
13
+ assert(id(v.subjectId), 'subject identity');
14
+ assert(object(v.scope), 'scope');
15
+ if (v.scope.kind === 'service')
16
+ keys(v.scope, ['kind']);
17
+ else {
18
+ assert(v.scope.kind === 'sessions' && ids(v.scope.sessionIds), 'session scope');
19
+ keys(v.scope, ['kind', 'sessionIds']);
20
+ }
21
+ 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
+ if (v.scope.kind === 'sessions')
23
+ assert(v.actions.every(a => a.startsWith('session.')), 'session scope cannot grant service or file capabilities');
24
+ assert(v.expiresAt === undefined || (Number.isSafeInteger(v.expiresAt) && Number(v.expiresAt) > 0), 'expiry');
25
+ assert(v.canDelegate === undefined || typeof v.canDelegate === 'boolean', 'delegation flag');
26
+ if (v.conditions !== undefined) {
27
+ assert(object(v.conditions), 'conditions');
28
+ keys(v.conditions, ['entryServiceIds', 'relayIds']);
29
+ for (const list of Object.values(v.conditions))
30
+ assert(ids(list), 'condition identities');
31
+ }
32
+ }
33
+ function isSubset(child, parent) {
34
+ if (child.serviceId !== parent.serviceId || !parent.canDelegate)
35
+ return false;
36
+ if (parent.expiresAt !== undefined && (child.expiresAt === undefined || child.expiresAt > parent.expiresAt))
37
+ return false;
38
+ if (parent.scope.kind === 'sessions' && (child.scope.kind !== 'sessions' || !child.scope.sessionIds.every(s => parent.scope.kind === 'sessions' && parent.scope.sessionIds.includes(s))))
39
+ return false;
40
+ if (!parent.actions.includes('service:*') && !child.actions.every(a => parent.actions.includes(a)))
41
+ return false;
42
+ for (const key of ['entryServiceIds', 'relayIds']) {
43
+ const limit = parent.conditions?.[key];
44
+ if (limit && (!child.conditions?.[key] || !child.conditions[key].every(x => limit.includes(x))))
45
+ return false;
46
+ }
47
+ return true;
48
+ }
49
+ function active(g, grants, now, seen = new Set(), passwordCredential) {
50
+ if (seen.has(g.id) || g.createdAt > now || g.revokedAt !== undefined || (g.expiresAt !== undefined && g.expiresAt <= now))
51
+ return false;
52
+ if (g.passwordCredential && g.passwordCredential !== passwordCredential)
53
+ return false;
54
+ seen.add(g.id);
55
+ if (!g.parentGrantId)
56
+ return true;
57
+ const parent = grants.find(p => p.id === g.parentGrantId);
58
+ return !!parent && isSubset(g, parent) && active(parent, grants, now, seen, passwordCredential);
59
+ }
60
+ /** Target-service authority. Root mutations must only be exposed to authenticated administrators.
61
+ * Every operation reloads disk; synchronous atomic writes support one owning server process.
62
+ * Do not share this file between concurrently writing processes. */
63
+ export class AuthorizationStore {
64
+ options;
65
+ now;
66
+ constructor(options) {
67
+ this.options = options;
68
+ assert(id(options.serviceId), 'service identity');
69
+ this.now = options.now ?? Date.now;
70
+ this.read();
71
+ }
72
+ read() {
73
+ let raw;
74
+ try {
75
+ raw = readFileSync(this.options.filePath, 'utf8');
76
+ }
77
+ catch (error) {
78
+ if (error.code === 'ENOENT')
79
+ return [];
80
+ throw error;
81
+ }
82
+ const data = JSON.parse(raw);
83
+ assert(object(data) && data.version === 1 && data.serviceId === this.options.serviceId && Array.isArray(data.grants), 'store header');
84
+ keys(data, ['version', 'serviceId', 'grants']);
85
+ const grants = [];
86
+ for (const value of data.grants) {
87
+ validateInput(value);
88
+ const g = value;
89
+ keys(g, ['id', 'serviceId', 'createdAt', 'parentGrantId', 'revokedAt', 'subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions', 'passwordCredential']);
90
+ assert(id(g.id) && g.serviceId === this.options.serviceId && Number.isSafeInteger(g.createdAt) && g.createdAt >= 0, 'grant metadata');
91
+ assert(g.parentGrantId === undefined || id(g.parentGrantId), 'parent identity');
92
+ assert(g.revokedAt === undefined || (Number.isSafeInteger(g.revokedAt) && g.revokedAt >= 0), 'revocation');
93
+ assert(g.passwordCredential === undefined || /^[a-f0-9]{64}$/.test(g.passwordCredential), 'password credential');
94
+ assert(!grants.some(p => p.id === g.id), 'duplicate grant');
95
+ grants.push(g);
96
+ }
97
+ return grants;
98
+ }
99
+ save(grants) {
100
+ mkdirSync(dirname(this.options.filePath), { recursive: true, mode: 0o700 });
101
+ const temp = `${this.options.filePath}.${randomUUID()}.tmp`;
102
+ let fd;
103
+ try {
104
+ fd = openSync(temp, 'wx', 0o600);
105
+ writeFileSync(fd, JSON.stringify({ version: 1, serviceId: this.options.serviceId, grants }, null, 2));
106
+ fsyncSync(fd);
107
+ closeSync(fd);
108
+ fd = undefined;
109
+ renameSync(temp, this.options.filePath);
110
+ }
111
+ finally {
112
+ if (fd !== undefined)
113
+ closeSync(fd);
114
+ try {
115
+ unlinkSync(temp);
116
+ }
117
+ catch (error) {
118
+ if (error.code !== 'ENOENT')
119
+ throw error;
120
+ }
121
+ }
122
+ }
123
+ create(input) {
124
+ validateInput(input);
125
+ keys(input, ['subjectId', 'scope', 'actions', 'expiresAt', 'canDelegate', 'conditions']);
126
+ const now = this.now();
127
+ assert(input.expiresAt === undefined || input.expiresAt > now, 'already expired');
128
+ return { ...structuredClone(input), id: randomUUID(), serviceId: this.options.serviceId, createdAt: now };
129
+ }
130
+ /** Trusted target administrator only; never authorize this from a relay's identity. */
131
+ grant(input) {
132
+ const grants = this.read();
133
+ const grant = this.create(input);
134
+ grants.push(grant);
135
+ this.save(grants);
136
+ return structuredClone(grant);
137
+ }
138
+ /** Only call after a successful mutually authenticated password proof. */
139
+ grantPassword(subjectId) {
140
+ const passwordCredential = this.options.passwordCredential?.();
141
+ assert(passwordCredential && /^[a-f0-9]{64}$/.test(passwordCredential), 'password unavailable');
142
+ const grants = this.read();
143
+ for (const grant of grants)
144
+ if (grant.subjectId === subjectId && grant.passwordCredential)
145
+ grant.revokedAt = this.now();
146
+ const grant = { ...this.create({ subjectId, scope: { kind: 'service' }, actions: ['service:*'], canDelegate: true, expiresAt: this.now() + 30 * 24 * 60 * 60 * 1000 }), passwordCredential };
147
+ grants.push(grant);
148
+ this.save(grants);
149
+ return structuredClone(grant);
150
+ }
151
+ delegate(parentGrantId, authenticatedSubjectId, input) {
152
+ const grants = this.read();
153
+ const parent = grants.find(g => g.id === parentGrantId);
154
+ assert(parent && parent.subjectId === authenticatedSubjectId && active(parent, grants, this.now(), new Set(), this.options.passwordCredential?.()), 'inactive or unowned parent');
155
+ const child = { ...this.create(input), parentGrantId };
156
+ assert(isSubset(child, parent), 'delegation exceeds parent');
157
+ grants.push(child);
158
+ this.save(grants);
159
+ return structuredClone(child);
160
+ }
161
+ /** Trusted target administrator only. Descendants become ineffective immediately on next check. */
162
+ revoke(grantId) {
163
+ const grants = this.read();
164
+ const grant = grants.find(g => g.id === grantId);
165
+ if (!grant || grant.revokedAt !== undefined)
166
+ return false;
167
+ grant.revokedAt = this.now();
168
+ this.save(grants);
169
+ return true;
170
+ }
171
+ list() { return this.read(); }
172
+ listEffective(request) {
173
+ if (!request || !id(request.subjectId) || request.serviceId !== this.options.serviceId)
174
+ return [];
175
+ let grants;
176
+ try {
177
+ grants = this.read();
178
+ }
179
+ catch {
180
+ return [];
181
+ }
182
+ const now = this.now();
183
+ return grants.filter(g => g.subjectId === request.subjectId && active(g, grants, now, new Set(), this.options.passwordCredential?.())
184
+ && (!g.conditions?.entryServiceIds || (!!request.entryServiceId && g.conditions.entryServiceIds.includes(request.entryServiceId)))
185
+ && (!g.conditions?.relayIds || (!!request.relayId && g.conditions.relayIds.includes(request.relayId))));
186
+ }
187
+ /** Tests the actual wildcard, not an invented action that custom grants could allow. */
188
+ hasFullServiceAccess(request) {
189
+ return this.listEffective(request).some(g => g.scope.kind === 'service' && g.actions.includes('service:*'));
190
+ }
191
+ authorize(request) {
192
+ const deny = { allowed: false, grantIds: [] };
193
+ if (!request || !id(request.subjectId) || request.serviceId !== this.options.serviceId || !action(request.action) || request.action === 'service:*')
194
+ return deny;
195
+ if (request.action.startsWith('session.') && !id(request.sessionId))
196
+ return deny;
197
+ if (request.sessionId !== undefined && !id(request.sessionId))
198
+ return deny;
199
+ if (request.entryServiceId !== undefined && !id(request.entryServiceId))
200
+ return deny;
201
+ if (request.relayId !== undefined && !id(request.relayId))
202
+ return deny;
203
+ let grants;
204
+ try {
205
+ grants = this.read();
206
+ }
207
+ catch {
208
+ return deny;
209
+ }
210
+ const now = this.now();
211
+ const grantIds = grants.filter(g => {
212
+ if (g.subjectId !== request.subjectId || !active(g, grants, now, new Set(), this.options.passwordCredential?.()))
213
+ return false;
214
+ if (!g.actions.includes('service:*') && !g.actions.includes(request.action))
215
+ return false;
216
+ if (g.scope.kind === 'sessions' && (!request.action.startsWith('session.') || !request.sessionId || !g.scope.sessionIds.includes(request.sessionId)))
217
+ return false;
218
+ if (g.conditions?.entryServiceIds && (!request.entryServiceId || !g.conditions.entryServiceIds.includes(request.entryServiceId)))
219
+ return false;
220
+ if (g.conditions?.relayIds && (!request.relayId || !g.conditions.relayIds.includes(request.relayId)))
221
+ return false;
222
+ return true;
223
+ }).map(g => g.id);
224
+ return { allowed: grantIds.length > 0, grantIds };
225
+ }
226
+ }
@@ -0,0 +1,23 @@
1
+ import { isAllowedOrigin } from '../utils/requestSecurity.js';
2
+ import { secureChannelOriginAllowed } from './originPolicy.js';
3
+ /** These endpoints exchange explicit password proofs, never cookies or grants
4
+ * from the caller's browser session. Business routes retain their strict policy. */
5
+ export const bootstrapCors = (req, res, next) => {
6
+ const origin = req.headers.origin;
7
+ if (!secureChannelOriginAllowed(origin, () => isAllowedOrigin(origin, req.headers.host))) {
8
+ res.status(403).json({ error: 'Origin is not allowed', code: 'ORIGIN_NOT_ALLOWED' });
9
+ return;
10
+ }
11
+ res.setHeader('Cache-Control', 'no-store');
12
+ if (origin) {
13
+ res.setHeader('Access-Control-Allow-Origin', origin);
14
+ res.vary('Origin');
15
+ }
16
+ if (req.method === 'OPTIONS') {
17
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
18
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
19
+ res.status(204).end();
20
+ return;
21
+ }
22
+ next();
23
+ };