termdock 1.4.295 → 1.4.297

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 (367) hide show
  1. package/.desktop-dist/main.js +35 -2
  2. package/README.en.md +257 -0
  3. package/README.md +284 -133
  4. package/dist/client/assets/App-BbqtNY2x.js +177 -0
  5. package/dist/client/assets/App-BbqtNY2x.js.br +0 -0
  6. package/dist/client/assets/App-BbqtNY2x.js.gz +0 -0
  7. package/dist/client/assets/{ChangeWalkthroughPanel-Dv5XRtRh.js → ChangeWalkthroughPanel-CvQJIp_C.js} +1 -1
  8. package/dist/client/assets/ChangeWalkthroughPanel-CvQJIp_C.js.br +0 -0
  9. package/dist/client/assets/ChangeWalkthroughPanel-CvQJIp_C.js.gz +0 -0
  10. package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js +1 -0
  11. package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js.br +0 -0
  12. package/dist/client/assets/CollaborationMainWorkspace-B0OgRtyR.js.gz +0 -0
  13. package/dist/client/assets/CollaborationTaskContent-C01GXOEq.js +2 -0
  14. package/dist/client/assets/ComputerControlView-CW1uA133.js +4 -0
  15. package/dist/client/assets/ComputerControlView-CW1uA133.js.br +0 -0
  16. package/dist/client/assets/ComputerControlView-CW1uA133.js.gz +0 -0
  17. package/dist/client/assets/{DagPlayground-mq69dKyr.js → DagPlayground-CdE3tQsl.js} +1 -1
  18. package/dist/client/assets/DagPlayground-CdE3tQsl.js.br +0 -0
  19. package/dist/client/assets/DagPlayground-CdE3tQsl.js.gz +0 -0
  20. package/dist/client/assets/{DiffLab-BqdHYGi5.js → DiffLab-BLGNnazd.js} +1 -1
  21. package/dist/client/assets/DiffLab-BLGNnazd.js.br +0 -0
  22. package/dist/client/assets/DiffLab-BLGNnazd.js.gz +0 -0
  23. package/dist/client/assets/{DiffReview-BvzGxpuc.js → DiffReview-CYw3ASI6.js} +1 -1
  24. package/dist/client/assets/DiffReview-CYw3ASI6.js.br +0 -0
  25. package/dist/client/assets/DiffReview-CYw3ASI6.js.gz +0 -0
  26. package/dist/client/assets/{DiffReviewLab-DrfddB7x.js → DiffReviewLab-CySIuWq_.js} +1 -1
  27. package/dist/client/assets/DiffReviewLab-CySIuWq_.js.br +0 -0
  28. package/dist/client/assets/DiffReviewLab-CySIuWq_.js.gz +0 -0
  29. package/dist/client/assets/{DiffViewer-3bJV_A5s.js → DiffViewer-WsYvN75z.js} +13 -13
  30. package/dist/client/assets/DiffViewer-WsYvN75z.js.br +0 -0
  31. package/dist/client/assets/DiffViewer-WsYvN75z.js.gz +0 -0
  32. package/dist/client/assets/{ModelPreview-CX7VtoJB.js → ModelPreview-Dnlx9ByX.js} +1 -1
  33. package/dist/client/assets/ModelPreview-Dnlx9ByX.js.br +0 -0
  34. package/dist/client/assets/ModelPreview-Dnlx9ByX.js.gz +0 -0
  35. package/dist/client/assets/RightSidebar-Cp5b_y0u.js +1678 -0
  36. package/dist/client/assets/RightSidebar-Cp5b_y0u.js.br +0 -0
  37. package/dist/client/assets/RightSidebar-Cp5b_y0u.js.gz +0 -0
  38. package/dist/client/assets/{abnfDiagram-VCTEODGH-PJwhLTPH.js → abnfDiagram-VCTEODGH-CK1mGFRQ.js} +1 -1
  39. package/dist/client/assets/abnfDiagram-VCTEODGH-CK1mGFRQ.js.br +0 -0
  40. package/dist/client/assets/abnfDiagram-VCTEODGH-CK1mGFRQ.js.gz +0 -0
  41. package/dist/client/assets/app-DfACMixk.js +35 -0
  42. package/dist/client/assets/app-DfACMixk.js.br +0 -0
  43. package/dist/client/assets/app-DfACMixk.js.gz +0 -0
  44. package/dist/client/assets/{arc-l3FXjCEl.js → arc-yZE1gqDl.js} +1 -1
  45. package/dist/client/assets/arc-yZE1gqDl.js.br +0 -0
  46. package/dist/client/assets/arc-yZE1gqDl.js.gz +0 -0
  47. package/dist/client/assets/{architectureDiagram-5GKGNRK7-DdNbKi-4.js → architectureDiagram-5GKGNRK7-U5XSC7cY.js} +1 -1
  48. package/dist/client/assets/architectureDiagram-5GKGNRK7-U5XSC7cY.js.br +0 -0
  49. package/dist/client/assets/architectureDiagram-5GKGNRK7-U5XSC7cY.js.gz +0 -0
  50. package/dist/client/assets/{blockDiagram-I7D4REHJ-Cu63oaxR.js → blockDiagram-I7D4REHJ-pVJnntSG.js} +1 -1
  51. package/dist/client/assets/blockDiagram-I7D4REHJ-pVJnntSG.js.br +0 -0
  52. package/dist/client/assets/blockDiagram-I7D4REHJ-pVJnntSG.js.gz +0 -0
  53. package/dist/client/assets/{c4Diagram-7LVT6UL2-DQiV_yk6.js → c4Diagram-7LVT6UL2-Bwz0AJJV.js} +1 -1
  54. package/dist/client/assets/c4Diagram-7LVT6UL2-Bwz0AJJV.js.br +0 -0
  55. package/dist/client/assets/c4Diagram-7LVT6UL2-Bwz0AJJV.js.gz +0 -0
  56. package/dist/client/assets/channel-qqjDJtcZ.js +1 -0
  57. package/dist/client/assets/{chunk-2Q5K7J3B-COKJ79Ld.js → chunk-2Q5K7J3B-CXJ1kP4X.js} +1 -1
  58. package/dist/client/assets/{chunk-5VM5RSS4-BfV-m7oa.js → chunk-5VM5RSS4-Dpdum_2z.js} +1 -1
  59. package/dist/client/assets/{chunk-F27PBJKO-sLDCEnU8.js → chunk-F27PBJKO-BCG5Zi_-.js} +1 -1
  60. package/dist/client/assets/chunk-F27PBJKO-BCG5Zi_-.js.br +0 -0
  61. package/dist/client/assets/chunk-F27PBJKO-BCG5Zi_-.js.gz +0 -0
  62. package/dist/client/assets/{chunk-IMKFNOWR-DYu5qS7g.js → chunk-IMKFNOWR-BM-GcNcv.js} +1 -1
  63. package/dist/client/assets/chunk-IMKFNOWR-BM-GcNcv.js.br +0 -0
  64. package/dist/client/assets/chunk-IMKFNOWR-BM-GcNcv.js.gz +0 -0
  65. package/dist/client/assets/{chunk-JWPE2WC7-DixkILcl.js → chunk-JWPE2WC7-BWiXpQT9.js} +1 -1
  66. package/dist/client/assets/{chunk-POPQ4Y6H-CfW5Vm_-.js → chunk-POPQ4Y6H-vQSslMNv.js} +1 -1
  67. package/dist/client/assets/{chunk-SVP7TREG-nJnbUFzs.js → chunk-SVP7TREG-Aqfz6nPA.js} +1 -1
  68. package/dist/client/assets/chunk-SVP7TREG-Aqfz6nPA.js.br +0 -0
  69. package/dist/client/assets/chunk-SVP7TREG-Aqfz6nPA.js.gz +0 -0
  70. package/dist/client/assets/{chunk-TICWLB2K-BwyiSQgx.js → chunk-TICWLB2K-DDjekMd0.js} +1 -1
  71. package/dist/client/assets/chunk-TICWLB2K-DDjekMd0.js.br +0 -0
  72. package/dist/client/assets/chunk-TICWLB2K-DDjekMd0.js.gz +0 -0
  73. package/dist/client/assets/{chunk-XXDRQBXY-TCk4c2XN.js → chunk-XXDRQBXY-BegIBcFg.js} +1 -1
  74. package/dist/client/assets/classDiagram-ZZMXUADV-BDuBrfNz.js +1 -0
  75. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BDuBrfNz.js +1 -0
  76. package/dist/client/assets/{cose-bilkent-JH36ORCC-rroo-EOb.js → cose-bilkent-JH36ORCC-B2izFsBY.js} +1 -1
  77. package/dist/client/assets/cose-bilkent-JH36ORCC-B2izFsBY.js.br +0 -0
  78. package/dist/client/assets/cose-bilkent-JH36ORCC-B2izFsBY.js.gz +0 -0
  79. package/dist/client/assets/{cynefin-OW5HDTMX-CkFeV85k.js → cynefin-OW5HDTMX-C7HA99Zc.js} +1 -1
  80. package/dist/client/assets/cynefin-OW5HDTMX-C7HA99Zc.js.br +0 -0
  81. package/dist/client/assets/{cynefin-OW5HDTMX-CkFeV85k.js.gz → cynefin-OW5HDTMX-C7HA99Zc.js.gz} +0 -0
  82. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-hBwpU_bW.js → cynefinDiagram-5FMLGOSQ-vNIzcCYl.js} +1 -1
  83. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-vNIzcCYl.js.br +0 -0
  84. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-vNIzcCYl.js.gz +0 -0
  85. package/dist/client/assets/{dagre-GXQ25YYZ-B321-hdp.js → dagre-GXQ25YYZ-Bv9WHNT9.js} +1 -1
  86. package/dist/client/assets/dagre-GXQ25YYZ-Bv9WHNT9.js.br +0 -0
  87. package/dist/client/assets/dagre-GXQ25YYZ-Bv9WHNT9.js.gz +0 -0
  88. package/dist/client/assets/{diagram-S7CK7UJ4-bY87_ptA.js → diagram-S7CK7UJ4-DFg5UNic.js} +1 -1
  89. package/dist/client/assets/diagram-S7CK7UJ4-DFg5UNic.js.br +0 -0
  90. package/dist/client/assets/diagram-S7CK7UJ4-DFg5UNic.js.gz +0 -0
  91. package/dist/client/assets/{diagram-UQ7AKVKN--cez0H_o.js → diagram-UQ7AKVKN-BFQsCAs7.js} +1 -1
  92. package/dist/client/assets/diagram-UQ7AKVKN-BFQsCAs7.js.br +0 -0
  93. package/dist/client/assets/diagram-UQ7AKVKN-BFQsCAs7.js.gz +0 -0
  94. package/dist/client/assets/{diagram-VSXAHHWV-BupH_VRK.js → diagram-VSXAHHWV-C4R8TKEv.js} +1 -1
  95. package/dist/client/assets/diagram-VSXAHHWV-C4R8TKEv.js.br +0 -0
  96. package/dist/client/assets/diagram-VSXAHHWV-C4R8TKEv.js.gz +0 -0
  97. package/dist/client/assets/{diagram-VX7I27RA-DewqaxvF.js → diagram-VX7I27RA-CL75HIf0.js} +1 -1
  98. package/dist/client/assets/diagram-VX7I27RA-CL75HIf0.js.br +0 -0
  99. package/dist/client/assets/diagram-VX7I27RA-CL75HIf0.js.gz +0 -0
  100. package/dist/client/assets/{diagram-Z3DM3KII-wDumcTuR.js → diagram-Z3DM3KII-CfJampuO.js} +1 -1
  101. package/dist/client/assets/diagram-Z3DM3KII-CfJampuO.js.br +0 -0
  102. package/dist/client/assets/diagram-Z3DM3KII-CfJampuO.js.gz +0 -0
  103. package/dist/client/assets/{ebnfDiagram-PWID7BFC-C0OY91We.js → ebnfDiagram-PWID7BFC-Ct53jhNd.js} +1 -1
  104. package/dist/client/assets/ebnfDiagram-PWID7BFC-Ct53jhNd.js.br +0 -0
  105. package/dist/client/assets/ebnfDiagram-PWID7BFC-Ct53jhNd.js.gz +0 -0
  106. package/dist/client/assets/{erDiagram-RLTQ6QDP-Cw9aNqvJ.js → erDiagram-RLTQ6QDP-BgL2xOOa.js} +1 -1
  107. package/dist/client/assets/erDiagram-RLTQ6QDP-BgL2xOOa.js.br +0 -0
  108. package/dist/client/assets/erDiagram-RLTQ6QDP-BgL2xOOa.js.gz +0 -0
  109. package/dist/client/assets/{flowDiagram-HODETNUW-BC7XUXeI.js → flowDiagram-HODETNUW-Cp4Nu5jY.js} +1 -1
  110. package/dist/client/assets/{flowDiagram-HODETNUW-BC7XUXeI.js.br → flowDiagram-HODETNUW-Cp4Nu5jY.js.br} +0 -0
  111. package/dist/client/assets/flowDiagram-HODETNUW-Cp4Nu5jY.js.gz +0 -0
  112. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DovhJIz5.js → ganttDiagram-EL5Y4UJY-DI0rdyPX.js} +1 -1
  113. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DI0rdyPX.js.br +0 -0
  114. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DI0rdyPX.js.gz +0 -0
  115. package/dist/client/assets/{gestureArbiter-wjVok2Lg.js → gestureArbiter-Belsq4xq.js} +1 -1
  116. package/dist/client/assets/gestureArbiter-Belsq4xq.js.br +0 -0
  117. package/dist/client/assets/gestureArbiter-Belsq4xq.js.gz +0 -0
  118. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-BmrbbyW1.js → gitGraphDiagram-WWUBYQGX-C0H4loQP.js} +1 -1
  119. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C0H4loQP.js.br +0 -0
  120. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-C0H4loQP.js.gz +0 -0
  121. package/dist/client/assets/index-CYmI1EsU.css +1 -0
  122. package/dist/client/assets/index-CYmI1EsU.css.br +0 -0
  123. package/dist/client/assets/index-CYmI1EsU.css.gz +0 -0
  124. package/dist/client/assets/{infoDiagram-27XIBGKW-CQEu4Jwy.js → infoDiagram-27XIBGKW-BN4RpQTL.js} +1 -1
  125. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-BPqibqsm.js → ishikawaDiagram-5VMMS53U-BZQ5qZB_.js} +1 -1
  126. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BZQ5qZB_.js.br +0 -0
  127. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BZQ5qZB_.js.gz +0 -0
  128. package/dist/client/assets/{journeyDiagram-3NMN7TZE-DIkhubnf.js → journeyDiagram-3NMN7TZE-Q8bx0_N4.js} +1 -1
  129. package/dist/client/assets/journeyDiagram-3NMN7TZE-Q8bx0_N4.js.br +0 -0
  130. package/dist/client/assets/journeyDiagram-3NMN7TZE-Q8bx0_N4.js.gz +0 -0
  131. package/dist/client/assets/{kanban-definition-UXKFOSKX-CBT0DIUR.js → kanban-definition-UXKFOSKX-D7JhGtpO.js} +1 -1
  132. package/dist/client/assets/kanban-definition-UXKFOSKX-D7JhGtpO.js.br +0 -0
  133. package/dist/client/assets/kanban-definition-UXKFOSKX-D7JhGtpO.js.gz +0 -0
  134. package/dist/client/assets/{layout-C1CMlOIU.js → layout-DERUg0xU.js} +1 -1
  135. package/dist/client/assets/layout-DERUg0xU.js.br +0 -0
  136. package/dist/client/assets/layout-DERUg0xU.js.gz +0 -0
  137. package/dist/client/assets/{linear-C4gyqor0.js → linear-lNTe4x00.js} +1 -1
  138. package/dist/client/assets/linear-lNTe4x00.js.br +0 -0
  139. package/dist/client/assets/linear-lNTe4x00.js.gz +0 -0
  140. package/dist/client/assets/{mermaid.core-CMfa4ncd.js → mermaid.core-WHCwLQDQ.js} +5 -5
  141. package/dist/client/assets/mermaid.core-WHCwLQDQ.js.br +0 -0
  142. package/dist/client/assets/mermaid.core-WHCwLQDQ.js.gz +0 -0
  143. package/dist/client/assets/{mindmap-definition-YA3MSWOX-C-JmgKOI.js → mindmap-definition-YA3MSWOX-BouapA4v.js} +1 -1
  144. package/dist/client/assets/mindmap-definition-YA3MSWOX-BouapA4v.js.br +0 -0
  145. package/dist/client/assets/mindmap-definition-YA3MSWOX-BouapA4v.js.gz +0 -0
  146. package/dist/client/assets/{passwordBootstrap-BmUG2IwA.js → passwordBootstrap-E0piP814.js} +1 -1
  147. package/dist/client/assets/passwordBootstrap-E0piP814.js.br +0 -0
  148. package/dist/client/assets/passwordBootstrap-E0piP814.js.gz +0 -0
  149. package/dist/client/assets/{pegDiagram-XKGWAZYB-IY-AEKQa.js → pegDiagram-XKGWAZYB-Curpo5Rq.js} +1 -1
  150. package/dist/client/assets/pegDiagram-XKGWAZYB-Curpo5Rq.js.br +0 -0
  151. package/dist/client/assets/pegDiagram-XKGWAZYB-Curpo5Rq.js.gz +0 -0
  152. package/dist/client/assets/{pieDiagram-E7YTZNPT-9Qq43qED.js → pieDiagram-E7YTZNPT-BLQCHaOG.js} +1 -1
  153. package/dist/client/assets/pieDiagram-E7YTZNPT-BLQCHaOG.js.br +0 -0
  154. package/dist/client/assets/pieDiagram-E7YTZNPT-BLQCHaOG.js.gz +0 -0
  155. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-BjQncx5i.js → quadrantDiagram-AXDQQJYC-PViFqGao.js} +1 -1
  156. package/dist/client/assets/quadrantDiagram-AXDQQJYC-PViFqGao.js.br +0 -0
  157. package/dist/client/assets/quadrantDiagram-AXDQQJYC-PViFqGao.js.gz +0 -0
  158. package/dist/client/assets/{railroadDiagram-O6MQD6OU-Cr1rL0wZ.js → railroadDiagram-O6MQD6OU-BtHo1LpT.js} +1 -1
  159. package/dist/client/assets/railroadDiagram-O6MQD6OU-BtHo1LpT.js.br +0 -0
  160. package/dist/client/assets/railroadDiagram-O6MQD6OU-BtHo1LpT.js.gz +0 -0
  161. package/dist/client/assets/rdpSession-O_f9ejOI.js +1 -0
  162. package/dist/client/assets/rdpSession-O_f9ejOI.js.br +0 -0
  163. package/dist/client/assets/rdpSession-O_f9ejOI.js.gz +0 -0
  164. package/dist/client/assets/{referenceLongPress-ConC5-ym.js → referenceLongPress-BZr7Lqzh.js} +1 -1
  165. package/dist/client/assets/referenceLongPress-BZr7Lqzh.js.br +0 -0
  166. package/dist/client/assets/referenceLongPress-BZr7Lqzh.js.gz +0 -0
  167. package/dist/client/assets/{requirementDiagram-BXWQKSXE-DU2q_iZg.js → requirementDiagram-BXWQKSXE-BUm81HUU.js} +1 -1
  168. package/dist/client/assets/requirementDiagram-BXWQKSXE-BUm81HUU.js.br +0 -0
  169. package/dist/client/assets/requirementDiagram-BXWQKSXE-BUm81HUU.js.gz +0 -0
  170. package/dist/client/assets/rfb-CxsXsMUX.js +5 -0
  171. package/dist/client/assets/rfb-CxsXsMUX.js.br +0 -0
  172. package/dist/client/assets/rfb-CxsXsMUX.js.gz +0 -0
  173. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-BYlx9kXC.js → sankeyDiagram-P5KCCOFB-B-_-aZ_y.js} +1 -1
  174. package/dist/client/assets/sankeyDiagram-P5KCCOFB-B-_-aZ_y.js.br +0 -0
  175. package/dist/client/assets/sankeyDiagram-P5KCCOFB-B-_-aZ_y.js.gz +0 -0
  176. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-D_uAKme-.js → sequenceDiagram-WJ2MYXX4-DT6LDIfx.js} +1 -1
  177. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DT6LDIfx.js.br +0 -0
  178. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DT6LDIfx.js.gz +0 -0
  179. package/dist/client/assets/{sizeCapture-INFHLROL-H_uqscf4.js → sizeCapture-INFHLROL-CTYerd9j.js} +1 -1
  180. package/dist/client/assets/sizeCapture-INFHLROL-CTYerd9j.js.br +0 -0
  181. package/dist/client/assets/sizeCapture-INFHLROL-CTYerd9j.js.gz +0 -0
  182. package/dist/client/assets/{sparkles-D2z4Ue6G.js → sparkles-CgaUBIwH.js} +1 -1
  183. package/dist/client/assets/{stateDiagram-D77RDMKH-BvsQjIIW.js → stateDiagram-D77RDMKH-CQbb5x2_.js} +1 -1
  184. package/dist/client/assets/stateDiagram-D77RDMKH-CQbb5x2_.js.br +0 -0
  185. package/dist/client/assets/stateDiagram-D77RDMKH-CQbb5x2_.js.gz +0 -0
  186. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CJAVXcx1.js +1 -0
  187. package/dist/client/assets/{swimlanes-42K2YHIH-LmUJag-p.js → swimlanes-42K2YHIH-BHZ4X2Fd.js} +1 -1
  188. package/dist/client/assets/swimlanes-42K2YHIH-BHZ4X2Fd.js.br +0 -0
  189. package/dist/client/assets/swimlanes-42K2YHIH-BHZ4X2Fd.js.gz +0 -0
  190. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-VL2nyI27.js +8 -0
  191. package/dist/client/assets/{timeline-definition-24CTP7MA-DZ5n0Pim.js → timeline-definition-24CTP7MA-BrjH6R6w.js} +1 -1
  192. package/dist/client/assets/timeline-definition-24CTP7MA-BrjH6R6w.js.br +0 -0
  193. package/dist/client/assets/timeline-definition-24CTP7MA-BrjH6R6w.js.gz +0 -0
  194. package/dist/client/assets/{triangle-alert-Ct6iIBmv.js → triangle-alert-C9IHD_eF.js} +1 -1
  195. package/dist/client/assets/{vennDiagram-4TSXK5OY-BkTA_Ciz.js → vennDiagram-4TSXK5OY-C9f5jYgk.js} +1 -1
  196. package/dist/client/assets/{vennDiagram-4TSXK5OY-BkTA_Ciz.js.br → vennDiagram-4TSXK5OY-C9f5jYgk.js.br} +0 -0
  197. package/dist/client/assets/vennDiagram-4TSXK5OY-C9f5jYgk.js.gz +0 -0
  198. package/dist/client/assets/{videoCompression-VZIoFnyJ.js → videoCompression-DJaU8exd.js} +1 -1
  199. package/dist/client/assets/videoCompression-DJaU8exd.js.br +0 -0
  200. package/dist/client/assets/videoCompression-DJaU8exd.js.gz +0 -0
  201. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-7hzU176j.js → wardleyDiagram-VM6X3IG4-6ljFvtiC.js} +1 -1
  202. package/dist/client/assets/wardleyDiagram-VM6X3IG4-6ljFvtiC.js.br +0 -0
  203. package/dist/client/assets/wardleyDiagram-VM6X3IG4-6ljFvtiC.js.gz +0 -0
  204. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-Bx4P96Bb.js → xychartDiagram-S5SC5T6Z-BaR2hm3e.js} +1 -1
  205. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BaR2hm3e.js.br +0 -0
  206. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BaR2hm3e.js.gz +0 -0
  207. package/dist/client/index.html +2 -2
  208. package/dist/client/index.html.br +0 -0
  209. package/dist/client/index.html.gz +0 -0
  210. package/dist/client/sw.js +1 -1
  211. package/dist/client/sw.js.br +0 -0
  212. package/dist/client/sw.js.gz +0 -0
  213. package/dist/server/agent/collaborationCli.js +45 -4
  214. package/dist/server/agent/collaborationDeliveryWorker.js +1 -1
  215. package/dist/server/agent/collaborationGroupRoutes.js +0 -2
  216. package/dist/server/agent/collaborationPeerTransport.js +8 -2
  217. package/dist/server/agent/collaborationResultPresentation.js +14 -0
  218. package/dist/server/agent/collaborationRoutes.js +18 -2
  219. package/dist/server/agent/collaborationService.js +46 -2
  220. package/dist/server/agent/collaborationStore.js +12 -21
  221. package/dist/server/agent/collaborationTaskBundles.js +121 -0
  222. package/dist/server/agent/collaborationTaskCli.js +54 -0
  223. package/dist/server/agent/collaborationTaskRoutes.js +58 -0
  224. package/dist/server/agent/collaborationTaskService.js +355 -0
  225. package/dist/server/agent/collaborationTaskStore.js +835 -0
  226. package/dist/server/agent/collaborationTaskTypes.js +1 -0
  227. package/dist/server/agent/collaborationTaskWorkspace.js +165 -0
  228. package/dist/server/agent/collaborationTeam.js +16 -0
  229. package/dist/server/agent/executionArchives.js +52 -0
  230. package/dist/server/entry.js +17 -2
  231. package/dist/server/federation/runtime.js +30 -7
  232. package/dist/server/routes/computer.js +198 -0
  233. package/dist/server/routes/computerTarget.js +23 -0
  234. package/dist/server/routes/guacamoleProtocol.js +55 -0
  235. package/dist/server/routes/rdp.js +162 -0
  236. package/dist/server/routes/terminal.js +441 -362
  237. package/dist/server/shared/computerPreferences.js +29 -0
  238. package/dist/server/utils/computerCredentials.js +88 -0
  239. package/dist/server/utils/sharedSampling.js +93 -0
  240. package/dist/server/utils/terminalOutputDelivery.js +8 -1
  241. package/dist/server/utils/tmuxMetadataWriter.js +61 -0
  242. package/package.json +4 -1
  243. package/runtime-manifest.json +4 -4
  244. package/dist/client/assets/App-D-cerXsD.js +0 -158
  245. package/dist/client/assets/App-D-cerXsD.js.br +0 -0
  246. package/dist/client/assets/App-D-cerXsD.js.gz +0 -0
  247. package/dist/client/assets/ChangeWalkthroughPanel-Dv5XRtRh.js.br +0 -0
  248. package/dist/client/assets/ChangeWalkthroughPanel-Dv5XRtRh.js.gz +0 -0
  249. package/dist/client/assets/DagPlayground-mq69dKyr.js.br +0 -0
  250. package/dist/client/assets/DagPlayground-mq69dKyr.js.gz +0 -0
  251. package/dist/client/assets/DiffLab-BqdHYGi5.js.br +0 -0
  252. package/dist/client/assets/DiffLab-BqdHYGi5.js.gz +0 -0
  253. package/dist/client/assets/DiffReview-BvzGxpuc.js.br +0 -0
  254. package/dist/client/assets/DiffReview-BvzGxpuc.js.gz +0 -0
  255. package/dist/client/assets/DiffReviewLab-DrfddB7x.js.br +0 -0
  256. package/dist/client/assets/DiffReviewLab-DrfddB7x.js.gz +0 -0
  257. package/dist/client/assets/DiffViewer-3bJV_A5s.js.br +0 -0
  258. package/dist/client/assets/DiffViewer-3bJV_A5s.js.gz +0 -0
  259. package/dist/client/assets/ModelPreview-CX7VtoJB.js.br +0 -0
  260. package/dist/client/assets/ModelPreview-CX7VtoJB.js.gz +0 -0
  261. package/dist/client/assets/RightSidebar-BK6vWNw3.js +0 -1678
  262. package/dist/client/assets/RightSidebar-BK6vWNw3.js.br +0 -0
  263. package/dist/client/assets/RightSidebar-BK6vWNw3.js.gz +0 -0
  264. package/dist/client/assets/abnfDiagram-VCTEODGH-PJwhLTPH.js.br +0 -0
  265. package/dist/client/assets/abnfDiagram-VCTEODGH-PJwhLTPH.js.gz +0 -0
  266. package/dist/client/assets/app-C7pXMvSE.js +0 -35
  267. package/dist/client/assets/app-C7pXMvSE.js.br +0 -0
  268. package/dist/client/assets/app-C7pXMvSE.js.gz +0 -0
  269. package/dist/client/assets/arc-l3FXjCEl.js.br +0 -0
  270. package/dist/client/assets/arc-l3FXjCEl.js.gz +0 -0
  271. package/dist/client/assets/architectureDiagram-5GKGNRK7-DdNbKi-4.js.br +0 -0
  272. package/dist/client/assets/architectureDiagram-5GKGNRK7-DdNbKi-4.js.gz +0 -0
  273. package/dist/client/assets/blockDiagram-I7D4REHJ-Cu63oaxR.js.br +0 -0
  274. package/dist/client/assets/blockDiagram-I7D4REHJ-Cu63oaxR.js.gz +0 -0
  275. package/dist/client/assets/c4Diagram-7LVT6UL2-DQiV_yk6.js.br +0 -0
  276. package/dist/client/assets/c4Diagram-7LVT6UL2-DQiV_yk6.js.gz +0 -0
  277. package/dist/client/assets/channel-CwC957gi.js +0 -1
  278. package/dist/client/assets/chunk-F27PBJKO-sLDCEnU8.js.br +0 -0
  279. package/dist/client/assets/chunk-F27PBJKO-sLDCEnU8.js.gz +0 -0
  280. package/dist/client/assets/chunk-IMKFNOWR-DYu5qS7g.js.br +0 -0
  281. package/dist/client/assets/chunk-IMKFNOWR-DYu5qS7g.js.gz +0 -0
  282. package/dist/client/assets/chunk-SVP7TREG-nJnbUFzs.js.br +0 -0
  283. package/dist/client/assets/chunk-SVP7TREG-nJnbUFzs.js.gz +0 -0
  284. package/dist/client/assets/chunk-TICWLB2K-BwyiSQgx.js.br +0 -0
  285. package/dist/client/assets/chunk-TICWLB2K-BwyiSQgx.js.gz +0 -0
  286. package/dist/client/assets/classDiagram-ZZMXUADV-CmoJ7-rE.js +0 -1
  287. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CmoJ7-rE.js +0 -1
  288. package/dist/client/assets/cose-bilkent-JH36ORCC-rroo-EOb.js.br +0 -0
  289. package/dist/client/assets/cose-bilkent-JH36ORCC-rroo-EOb.js.gz +0 -0
  290. package/dist/client/assets/cynefin-OW5HDTMX-CkFeV85k.js.br +0 -0
  291. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-hBwpU_bW.js.br +0 -0
  292. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-hBwpU_bW.js.gz +0 -0
  293. package/dist/client/assets/dagre-GXQ25YYZ-B321-hdp.js.br +0 -0
  294. package/dist/client/assets/dagre-GXQ25YYZ-B321-hdp.js.gz +0 -0
  295. package/dist/client/assets/diagram-S7CK7UJ4-bY87_ptA.js.br +0 -0
  296. package/dist/client/assets/diagram-S7CK7UJ4-bY87_ptA.js.gz +0 -0
  297. package/dist/client/assets/diagram-UQ7AKVKN--cez0H_o.js.br +0 -0
  298. package/dist/client/assets/diagram-UQ7AKVKN--cez0H_o.js.gz +0 -0
  299. package/dist/client/assets/diagram-VSXAHHWV-BupH_VRK.js.br +0 -0
  300. package/dist/client/assets/diagram-VSXAHHWV-BupH_VRK.js.gz +0 -0
  301. package/dist/client/assets/diagram-VX7I27RA-DewqaxvF.js.br +0 -0
  302. package/dist/client/assets/diagram-VX7I27RA-DewqaxvF.js.gz +0 -0
  303. package/dist/client/assets/diagram-Z3DM3KII-wDumcTuR.js.br +0 -0
  304. package/dist/client/assets/diagram-Z3DM3KII-wDumcTuR.js.gz +0 -0
  305. package/dist/client/assets/ebnfDiagram-PWID7BFC-C0OY91We.js.br +0 -0
  306. package/dist/client/assets/ebnfDiagram-PWID7BFC-C0OY91We.js.gz +0 -0
  307. package/dist/client/assets/erDiagram-RLTQ6QDP-Cw9aNqvJ.js.br +0 -0
  308. package/dist/client/assets/erDiagram-RLTQ6QDP-Cw9aNqvJ.js.gz +0 -0
  309. package/dist/client/assets/flowDiagram-HODETNUW-BC7XUXeI.js.gz +0 -0
  310. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DovhJIz5.js.br +0 -0
  311. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DovhJIz5.js.gz +0 -0
  312. package/dist/client/assets/gestureArbiter-wjVok2Lg.js.br +0 -0
  313. package/dist/client/assets/gestureArbiter-wjVok2Lg.js.gz +0 -0
  314. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BmrbbyW1.js.br +0 -0
  315. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BmrbbyW1.js.gz +0 -0
  316. package/dist/client/assets/index-BeD4iJ6L.css +0 -1
  317. package/dist/client/assets/index-BeD4iJ6L.css.br +0 -0
  318. package/dist/client/assets/index-BeD4iJ6L.css.gz +0 -0
  319. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BPqibqsm.js.br +0 -0
  320. package/dist/client/assets/ishikawaDiagram-5VMMS53U-BPqibqsm.js.gz +0 -0
  321. package/dist/client/assets/journeyDiagram-3NMN7TZE-DIkhubnf.js.br +0 -0
  322. package/dist/client/assets/journeyDiagram-3NMN7TZE-DIkhubnf.js.gz +0 -0
  323. package/dist/client/assets/kanban-definition-UXKFOSKX-CBT0DIUR.js.br +0 -0
  324. package/dist/client/assets/kanban-definition-UXKFOSKX-CBT0DIUR.js.gz +0 -0
  325. package/dist/client/assets/layout-C1CMlOIU.js.br +0 -0
  326. package/dist/client/assets/layout-C1CMlOIU.js.gz +0 -0
  327. package/dist/client/assets/linear-C4gyqor0.js.br +0 -0
  328. package/dist/client/assets/linear-C4gyqor0.js.gz +0 -0
  329. package/dist/client/assets/mermaid.core-CMfa4ncd.js.br +0 -0
  330. package/dist/client/assets/mermaid.core-CMfa4ncd.js.gz +0 -0
  331. package/dist/client/assets/mindmap-definition-YA3MSWOX-C-JmgKOI.js.br +0 -0
  332. package/dist/client/assets/mindmap-definition-YA3MSWOX-C-JmgKOI.js.gz +0 -0
  333. package/dist/client/assets/passwordBootstrap-BmUG2IwA.js.br +0 -0
  334. package/dist/client/assets/passwordBootstrap-BmUG2IwA.js.gz +0 -0
  335. package/dist/client/assets/pegDiagram-XKGWAZYB-IY-AEKQa.js.br +0 -0
  336. package/dist/client/assets/pegDiagram-XKGWAZYB-IY-AEKQa.js.gz +0 -0
  337. package/dist/client/assets/pieDiagram-E7YTZNPT-9Qq43qED.js.br +0 -0
  338. package/dist/client/assets/pieDiagram-E7YTZNPT-9Qq43qED.js.gz +0 -0
  339. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BjQncx5i.js.br +0 -0
  340. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BjQncx5i.js.gz +0 -0
  341. package/dist/client/assets/railroadDiagram-O6MQD6OU-Cr1rL0wZ.js.br +0 -0
  342. package/dist/client/assets/railroadDiagram-O6MQD6OU-Cr1rL0wZ.js.gz +0 -0
  343. package/dist/client/assets/referenceLongPress-ConC5-ym.js.br +0 -0
  344. package/dist/client/assets/referenceLongPress-ConC5-ym.js.gz +0 -0
  345. package/dist/client/assets/requirementDiagram-BXWQKSXE-DU2q_iZg.js.br +0 -0
  346. package/dist/client/assets/requirementDiagram-BXWQKSXE-DU2q_iZg.js.gz +0 -0
  347. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BYlx9kXC.js.br +0 -0
  348. package/dist/client/assets/sankeyDiagram-P5KCCOFB-BYlx9kXC.js.gz +0 -0
  349. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D_uAKme-.js.br +0 -0
  350. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-D_uAKme-.js.gz +0 -0
  351. package/dist/client/assets/sizeCapture-INFHLROL-H_uqscf4.js.br +0 -0
  352. package/dist/client/assets/sizeCapture-INFHLROL-H_uqscf4.js.gz +0 -0
  353. package/dist/client/assets/stateDiagram-D77RDMKH-BvsQjIIW.js.br +0 -0
  354. package/dist/client/assets/stateDiagram-D77RDMKH-BvsQjIIW.js.gz +0 -0
  355. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DNgoExtD.js +0 -1
  356. package/dist/client/assets/swimlanes-42K2YHIH-LmUJag-p.js.br +0 -0
  357. package/dist/client/assets/swimlanes-42K2YHIH-LmUJag-p.js.gz +0 -0
  358. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BRuArMR5.js +0 -8
  359. package/dist/client/assets/timeline-definition-24CTP7MA-DZ5n0Pim.js.br +0 -0
  360. package/dist/client/assets/timeline-definition-24CTP7MA-DZ5n0Pim.js.gz +0 -0
  361. package/dist/client/assets/vennDiagram-4TSXK5OY-BkTA_Ciz.js.gz +0 -0
  362. package/dist/client/assets/videoCompression-VZIoFnyJ.js.br +0 -0
  363. package/dist/client/assets/videoCompression-VZIoFnyJ.js.gz +0 -0
  364. package/dist/client/assets/wardleyDiagram-VM6X3IG4-7hzU176j.js.br +0 -0
  365. package/dist/client/assets/wardleyDiagram-VM6X3IG4-7hzU176j.js.gz +0 -0
  366. package/dist/client/assets/xychartDiagram-S5SC5T6Z-Bx4P96Bb.js.br +0 -0
  367. package/dist/client/assets/xychartDiagram-S5SC5T6Z-Bx4P96Bb.js.gz +0 -0
@@ -0,0 +1 @@
1
+ export function taskMemberKey(member) { return `${member.serviceId}:${member.sessionId}`; }
@@ -0,0 +1,165 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { mkdir, readFile, writeFile, rename, realpath, stat } from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ const exec = promisify(execFile);
7
+ const git = async (cwd, args) => (await exec('git', ['-C', cwd, ...args], {
8
+ timeout: 30_000, maxBuffer: 2 * 1024 * 1024,
9
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0', GIT_MERGE_AUTOEDIT: 'no' },
10
+ })).stdout.trim();
11
+ const home = path.join(os.homedir(), '.termdock', 'task-workspaces');
12
+ /** Only committed code enters task worktrees; the user's checkout is never reset, cleaned or merged. */
13
+ export async function prepareTaskWorkspace(task, sourceCwd, dependencies, importCommit) {
14
+ if (!/^[a-f0-9]{32}$/.test(task.id))
15
+ throw new Error('任务标识无效');
16
+ const generation = task.events.filter(e => e.kind === 'scheduled').at(-1)?.id;
17
+ if (!generation || !/^[a-f0-9]{32}$/.test(generation))
18
+ throw new Error('缺少执行目录准备记录');
19
+ const repository = await git(sourceCwd, ['rev-parse', '--show-toplevel']);
20
+ const rootId = task.workflow?.rootTaskId ?? task.id;
21
+ const goalDir = path.join(home, rootId);
22
+ await mkdir(goalDir, { recursive: true, mode: 0o700 });
23
+ // Pin a base per repository and goal. A moved user HEAD cannot change later tasks' starting point.
24
+ const { createHash } = await import('node:crypto');
25
+ const common = await realpath(path.resolve(repository, await git(repository, ['rev-parse', '--git-common-dir'])));
26
+ const baseFile = path.join(goalDir, `${createHash('sha256').update(common).digest('hex').slice(0, 16)}.base`);
27
+ let base;
28
+ try {
29
+ base = (await readFile(baseFile, 'utf8')).trim();
30
+ }
31
+ catch (error) {
32
+ if (error.code !== 'ENOENT')
33
+ throw error;
34
+ base = await git(repository, ['rev-parse', 'HEAD']);
35
+ try {
36
+ await writeFile(baseFile, base, { flag: 'wx', mode: 0o600 });
37
+ }
38
+ catch (error) {
39
+ if (error.code !== 'EEXIST')
40
+ throw error;
41
+ base = (await readFile(baseFile, 'utf8')).trim();
42
+ }
43
+ }
44
+ if (!/^[a-f0-9]{40,64}$/.test(base))
45
+ throw new Error('目标基线无效');
46
+ const cwd = path.join(goalDir, `${task.id}-${generation.slice(0, 8)}`);
47
+ const branch = `termdock/task/${task.id}-${generation.slice(0, 8)}`;
48
+ try {
49
+ await readFile(path.join(cwd, '.git'), 'utf8');
50
+ }
51
+ catch (error) {
52
+ if (error.code !== 'ENOENT')
53
+ throw error;
54
+ await git(repository, ['worktree', 'add', '-b', branch, cwd, base]);
55
+ }
56
+ if (await git(cwd, ['rev-parse', '--show-toplevel']) !== cwd || await git(cwd, ['branch', '--show-current']) !== branch)
57
+ throw new Error('独立目录与任务分支不匹配,保留现场等待处理');
58
+ const readyFile = path.join(goalDir, `${task.id}-${generation.slice(0, 8)}.ready`);
59
+ let ready = false;
60
+ try {
61
+ ready = (await readFile(readyFile, 'utf8')) === generation;
62
+ }
63
+ catch (error) {
64
+ if (error.code !== 'ENOENT')
65
+ throw error;
66
+ }
67
+ if (!ready) {
68
+ // A failed merge is deliberately left in this task's worktree for a member to resolve.
69
+ if (await git(cwd, ['status', '--porcelain']))
70
+ throw new Error(`依赖集成需要处理,现场保留在 ${cwd};请协调者安排解决冲突后继续`);
71
+ for (const dependency of dependencies) {
72
+ const artifact = dependency.artifacts.find(a => a.id === dependency.acceptedArtifactId);
73
+ const commit = artifact?.evidence?.commit;
74
+ if (!commit || !/^[a-f0-9]{40,64}$/.test(commit))
75
+ throw new Error(`依赖「${dependency.title}」缺少已评审提交,请补充交付`);
76
+ try {
77
+ await git(cwd, ['cat-file', '-e', `${commit}^{commit}`]);
78
+ }
79
+ catch {
80
+ if (!importCommit)
81
+ throw new Error(`依赖「${dependency.title}」的提交在此仓库不可用,请协调者先同步提交`);
82
+ await importCommit(dependency, cwd);
83
+ await git(cwd, ['cat-file', '-e', `${commit}^{commit}`]);
84
+ }
85
+ await git(cwd, ['-c', 'user.name=Termdock', '-c', 'user.email=tasks@termdock.local', 'merge', '--no-edit', commit]);
86
+ }
87
+ const temp = `${readyFile}.${process.pid}.tmp`;
88
+ await writeFile(temp, generation, { mode: 0o600 });
89
+ await rename(temp, readyFile);
90
+ }
91
+ return { cwd, repository, branch, base };
92
+ }
93
+ export async function captureTaskCommit(workspace) {
94
+ if (!(await realpath(workspace.cwd)).startsWith(`${await realpath(home)}${path.sep}`) || await git(workspace.cwd, ['branch', '--show-current']) !== workspace.branch)
95
+ throw new Error('交付目录与任务分支不匹配');
96
+ if (await git(workspace.cwd, ['status', '--porcelain']))
97
+ throw new Error('独立目录还有未提交改动,请先提交再报告完成;不会自动暂存或提交');
98
+ const commit = await git(workspace.cwd, ['rev-parse', 'HEAD']);
99
+ return { commit, branch: workspace.branch, cwd: workspace.cwd, base: workspace.base };
100
+ }
101
+ async function assertManagedWorkspace(workspace) {
102
+ const relative = path.relative(home, workspace.cwd);
103
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)
104
+ || !/^termdock\/task\/[a-f0-9]{32}-[a-f0-9]{8}$/.test(workspace.branch))
105
+ throw new Error('目录不是 Termdock 独立执行工作区');
106
+ const actual = await realpath(workspace.cwd);
107
+ if (actual !== path.resolve(workspace.cwd) || !actual.startsWith(`${await realpath(home)}${path.sep}`)
108
+ || await git(actual, ['rev-parse', '--show-toplevel']) !== actual
109
+ || await git(actual, ['branch', '--show-current']) !== workspace.branch)
110
+ throw new Error('执行目录或分支发生变化,现场已保留');
111
+ const common = async (cwd) => realpath(path.resolve(cwd, await git(cwd, ['rev-parse', '--git-common-dir'])));
112
+ if (await common(actual) !== await common(workspace.repository))
113
+ throw new Error('执行目录不属于原仓库,现场已保留');
114
+ }
115
+ /** Remove only a verified, clean worktree. Keep its branch and all Git commits. */
116
+ export async function archiveTaskWorkspace(workspace) {
117
+ try {
118
+ await assertManagedWorkspace(workspace);
119
+ const commit = await git(workspace.cwd, ['rev-parse', 'HEAD']);
120
+ if (await git(workspace.cwd, ['status', '--porcelain']) || await git(workspace.cwd, ['ls-files', '--others', '--ignored', '--exclude-standard'])) {
121
+ return { state: 'retained', reason: '目录含未提交或忽略的文件,已保留现场', commit };
122
+ }
123
+ await git(workspace.repository, ['worktree', 'remove', workspace.cwd]);
124
+ return { state: 'removed', commit };
125
+ }
126
+ catch (error) {
127
+ return { state: 'retained', reason: error instanceof Error ? error.message : String(error) };
128
+ }
129
+ }
130
+ export async function snapshotTaskWorkspace(workspace) {
131
+ await assertManagedWorkspace(workspace);
132
+ return git(workspace.cwd, ['rev-parse', 'HEAD']);
133
+ }
134
+ export async function restoreTaskWorkspace(workspace, cleanup) {
135
+ if (cleanup.state !== 'removed') {
136
+ try {
137
+ await assertManagedWorkspace(workspace);
138
+ return;
139
+ }
140
+ catch (error) {
141
+ if (cleanup.state !== 'pending' || error.code !== 'ENOENT')
142
+ throw error;
143
+ }
144
+ }
145
+ const relative = path.relative(home, workspace.cwd);
146
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)
147
+ || !/^termdock\/task\/[a-f0-9]{32}-[a-f0-9]{8}$/.test(workspace.branch) || !cleanup.commit)
148
+ throw new Error('工作区恢复记录无效');
149
+ try {
150
+ await stat(workspace.cwd);
151
+ await assertManagedWorkspace(workspace);
152
+ return;
153
+ }
154
+ catch (error) {
155
+ if (error.code !== 'ENOENT')
156
+ throw error;
157
+ }
158
+ if (await git(workspace.repository, ['rev-parse', workspace.branch]) !== cleanup.commit)
159
+ throw new Error('归档分支已有变化,请核对后再恢复;不会重置分支');
160
+ await mkdir(path.dirname(workspace.cwd), { recursive: true, mode: 0o700 });
161
+ if (!(await realpath(path.dirname(workspace.cwd))).startsWith(`${await realpath(home)}${path.sep}`))
162
+ throw new Error('工作区父目录发生变化');
163
+ await git(workspace.repository, ['worktree', 'add', workspace.cwd, workspace.branch]);
164
+ await assertManagedWorkspace(workspace);
165
+ }
@@ -0,0 +1,16 @@
1
+ /** Provision only missing roles. Persist each member before proceeding so a
2
+ * failed launch, retry or server restart cannot create another full team. */
3
+ export async function ensureTeam(options) {
4
+ const ids = [];
5
+ const labels = [`自动协调者 (${options.agentSlug})`, `自动执行与评审 (${options.agentSlug})`];
6
+ for (const role of labels) {
7
+ const members = options.members();
8
+ const reserved = members.find(member => member.role?.startsWith(role.split(' (')[0]));
9
+ if (reserved && (reserved.role !== role || reserved.cwd !== options.cwd)) {
10
+ throw new Error('本组已有自动配置的成员,请使用原 Agent 和目录,或在成员设置中移出后重新配置');
11
+ }
12
+ const existing = reserved ?? members.find(member => !ids.includes(member.id) && member.agentSlug === options.agentSlug && member.cwd === options.cwd);
13
+ ids.push(existing?.id ?? await options.spawn(role));
14
+ }
15
+ return { coordinatorSessionId: ids[0], reviewerSessionIds: [ids[1]] };
16
+ }
@@ -0,0 +1,52 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { normalizePersistedAgentResumeInfo } from './resumePersistence.js';
4
+ /** Explicit archives are durable records, independent of the recent-history cap. */
5
+ export class ExecutionArchiveStore {
6
+ file;
7
+ entries;
8
+ constructor(file) {
9
+ this.file = file;
10
+ try {
11
+ const document = JSON.parse(fs.readFileSync(file, 'utf8'));
12
+ if (document.version !== 1 || !Array.isArray(document.entries))
13
+ throw new Error('执行归档文件格式无效');
14
+ this.entries = document.entries.map((entry) => {
15
+ const agent = normalizePersistedAgentResumeInfo(entry.agent);
16
+ if (!agent || !entry.sessionId || !entry.workspace?.cwd || !Array.isArray(entry.groups) || !Array.isArray(entry.taskIds))
17
+ throw new Error('执行归档记录无效');
18
+ return { ...entry, agent };
19
+ });
20
+ }
21
+ catch (error) {
22
+ if (error.code !== 'ENOENT')
23
+ throw error;
24
+ this.entries = [];
25
+ }
26
+ }
27
+ list() { return structuredClone(this.entries); }
28
+ get(id) { return this.list().find(entry => entry.sessionId === id); }
29
+ save(entry) {
30
+ if (!normalizePersistedAgentResumeInfo(entry.agent))
31
+ throw new Error('没有可恢复的 Agent 原生会话 ID,终端已保留');
32
+ const entries = [structuredClone(entry), ...this.entries.filter(item => item.sessionId !== entry.sessionId)];
33
+ fs.mkdirSync(path.dirname(this.file), { recursive: true, mode: 0o700 });
34
+ const temporary = `${this.file}.${process.pid}.tmp`;
35
+ fs.writeFileSync(temporary, JSON.stringify({ version: 1, entries }), { mode: 0o600 });
36
+ fs.renameSync(temporary, this.file);
37
+ this.entries = entries;
38
+ return structuredClone(entry);
39
+ }
40
+ }
41
+ export function completedExecution(tasks, serviceId, sessionId) {
42
+ const matches = (member) => member?.serviceId === serviceId && member.sessionId === sessionId;
43
+ if (tasks.some(task => matches(task.coordinator) || task.workflow?.reviewers.some(matches)
44
+ || task.status === 'open' && (matches(task.attempts.find(a => a.id === task.activeAttemptId)?.assignee) || matches(task.scheduledAssignee)))) {
45
+ throw new Error('此会话仍承担协作工作,不能归档;请完成当前任务后再操作');
46
+ }
47
+ const completed = tasks.filter(task => task.status !== 'open' && task.workspace && task.workflow?.kind === 'step' && task.workflow.isolated
48
+ && matches(task.attempts.find(a => a.id === task.activeAttemptId)?.assignee));
49
+ if (!completed.length)
50
+ throw new Error('此会话没有已完成的独立执行任务,终端已保留');
51
+ return completed;
52
+ }
@@ -1,7 +1,8 @@
1
1
  import { CollaborationService } from './agent/collaborationService.js';
2
2
  import { X509Certificate } from 'node:crypto';
3
3
  import { CollaborationPeerTransport, connectCollaborationRpc } from './agent/collaborationPeerTransport.js';
4
- import { collaborationStore, deliverPeerCollaboration, collaborationLocalActivity, collaborationDirectorySessions } from './routes/terminal.js';
4
+ import { collaborationStore, collaborationTaskStore, deliverPeerCollaboration, collaborationLocalActivity, collaborationDirectorySessions, prepareCollaborationTaskWorker } from './routes/terminal.js';
5
+ import { CollaborationTaskService } from './agent/collaborationTaskService.js';
5
6
  import { setPushTargetPeerId } from './notifications/pushService.js';
6
7
  import { desktopDirectTargets } from './federation/desktopTargets.js';
7
8
  import { createOpenAccessRouter } from './federation/openAccess.js';
@@ -31,6 +32,7 @@ import { fileURLToPath } from 'url';
31
32
  import { WebSocketServer } from 'ws';
32
33
  import terminalRoutes, { handleTerminalWebSocket, handleControlWebSocket, resolveStableFederationSessionId } from './routes/terminal.js';
33
34
  import androidRoutes, { handleAndroidWebSocket } from './routes/android.js';
35
+ import computerRoutes, { handleComputerWebSocket, stopComputerBridges } from './routes/computer.js';
34
36
  import { exitWithStartupFailure } from './utils/startupFailure.js';
35
37
  import { androidRecordings } from './android/recording.js';
36
38
  import { stopAllScrcpySessions } from './android/scrcpy.js';
@@ -348,6 +350,9 @@ export function createApp(options = {}) {
348
350
  app.use('/api/android', requireAuth({ bypass: isTrustedLocalCliRequest }));
349
351
  app.use('/api/android', csrfProtection.verifyMiddleware({ bypass: isTrustedLocalCliRequest }));
350
352
  app.use('/api/android', androidRoutes);
353
+ app.use('/api/computer', requireAuth({ bypass: isTrustedLocalCliRequest }));
354
+ app.use('/api/computer', csrfProtection.verifyMiddleware({ bypass: isTrustedLocalCliRequest }));
355
+ app.use('/api/computer', computerRoutes);
351
356
  if (fs.existsSync(bundledClientIndexPath)) {
352
357
  const clientPath = pinBundledRuntimeClientDist(bundledClientDistPath);
353
358
  const compression = createStaticCompressionMiddleware(clientPath);
@@ -499,8 +504,9 @@ export function startServer(options = {}) {
499
504
  target.close(); });
500
505
  server.once('close', () => relayRouter.close());
501
506
  server.once('close', () => { void stopAllScrcpySessions(); void androidRecordings.stopAll(); });
507
+ server.once('close', stopComputerBridges);
502
508
  const federation = createFederationRuntime(app, path.join(homedir(), '.termdock', 'federation'), {
503
- terminal: handleTerminalWebSocket, control: handleControlWebSocket, android: handleAndroidWebSocket,
509
+ terminal: handleTerminalWebSocket, control: handleControlWebSocket, android: handleAndroidWebSocket, computer: handleComputerWebSocket,
504
510
  }, {
505
511
  resolveSessionId: resolveStableFederationSessionId,
506
512
  collaborationConnected: (subjectId, rpc) => {
@@ -616,11 +622,20 @@ export function startServer(options = {}) {
616
622
  collaborationService = new CollaborationService({ file: path.join(homedir(), '.termdock', 'federation', 'collaboration-services.json'),
617
623
  store: collaborationStore, transport: collaborationTransport, node: () => app.locals.collaborationNode,
618
624
  sessions: collaborationDirectorySessions, reverse: reverseCollaboration, connect: connectPeer,
625
+ tasks: {
626
+ receive: (peer, packet) => app.locals.collaborationTasks.receive(peer, packet),
627
+ heads: ids => app.locals.collaborationTasks?.heads(ids) ?? [],
628
+ sync: (peer, heads) => app.locals.collaborationTasks.sync(peer, heads),
629
+ },
619
630
  deviceScope: device => runtime.store.listEffective({ subjectId: device, serviceId: runtime.serviceId })
620
631
  .filter(grant => grant.scope.kind === 'sessions' && grant.actions.includes('session.input'))
621
632
  .flatMap(grant => grant.scope.kind === 'sessions' ? grant.scope.sessionIds : []),
622
633
  pairConnect: peer => connectCollaborationRpc(runtime.identity, peer) });
623
634
  app.locals.collaborationService = collaborationService;
635
+ const collaborationTasks = new CollaborationTaskService(collaborationTaskStore, collaborationStore, collaborationService, deliverPeerCollaboration, (task, template, dependencies) => prepareCollaborationTaskWorker(task, template, dependencies, collaborationService, (taskId, dependencyId, offset) => collaborationTasks.dependencyChunk(taskId, dependencyId, offset)));
636
+ app.locals.collaborationTasks = collaborationTasks;
637
+ collaborationTasks.start();
638
+ server.once('close', () => collaborationTasks.close());
624
639
  collaborationService.start();
625
640
  server.once('close', () => collaborationService?.close());
626
641
  collaborationTransport.start();
@@ -252,8 +252,8 @@ export async function createFederationRuntime(app, directory, handlers, options
252
252
  }
253
253
  }
254
254
  revokeTimer = setInterval(() => {
255
- for (const { socket: logical, sessionId, android } of sockets.values()) {
256
- if (android) {
255
+ for (const { socket: logical, sessionId, android, computer } of sockets.values()) {
256
+ if (android || computer) {
257
257
  if (!full(subjectId))
258
258
  logical.close(4003, 'Authorization revoked');
259
259
  continue;
@@ -297,7 +297,12 @@ export async function createFederationRuntime(app, directory, handlers, options
297
297
  else if (packet.type === 'collaboration-service') {
298
298
  if (!options.collaborationService)
299
299
  throw new Error('COLLABORATION_UPGRADE_REQUIRED');
300
- send({ type: 'result', id: packet.id, ...options.collaborationService(subjectId, packet) });
300
+ // Task preparation may use the same channel for a directory request; do not block its reader.
301
+ void Promise.resolve(options.collaborationService(subjectId, packet)).then(result => send({ type: 'result', id: packet.id, ...result }))
302
+ .catch(error => { try {
303
+ send({ type: 'error', id: packet.id, error: error instanceof Error ? error.message : 'REQUEST_FAILED' });
304
+ }
305
+ catch { /* Connection closed. */ } });
301
306
  }
302
307
  else if (packet.type === 'collaboration-scoped-peer') {
303
308
  if (!options.registerScopedCollaborationPeer)
@@ -564,18 +569,20 @@ export async function createFederationRuntime(app, directory, handlers, options
564
569
  const url = new URL(packet.path, 'http://inner');
565
570
  const match = /^\/api\/terminal\/([^/%]+)\/ws$/.exec(url.pathname);
566
571
  const androidMatch = /^\/api\/android\/([^/%]+)\/ws$/.exec(url.pathname);
572
+ const computer = url.pathname === '/api/computer/ws';
567
573
  if (match)
568
574
  check('session.view', match[1]);
569
575
  else if (url.pathname === '/api/control/ws')
570
576
  check('service.view');
571
- else if (androidMatch) {
577
+ else if (androidMatch || computer) {
572
578
  if (!full(subjectId))
573
579
  throw new Error('API_NOT_ALLOWED');
574
580
  }
575
581
  else
576
582
  throw new Error('API_NOT_ALLOWED');
577
- const logical = new LogicalSocket(channel, packet.id, () => androidMatch ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]));
578
- sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: Boolean(androidMatch) });
583
+ const logical = new LogicalSocket(channel, packet.id, () => (androidMatch || computer) ? full(subjectId) : allowed(subjectId, match ? 'session.view' : 'service.view', match?.[1]));
584
+ sockets.set(packet.id, { socket: logical, sessionId: match?.[1], android: Boolean(androidMatch), computer,
585
+ computerProtocol: computer ? url.searchParams.get('protocol') ?? 'vnc' : undefined });
579
586
  logical.once('close', () => sockets.delete(packet.id));
580
587
  send({ type: 'ws-ready', id: packet.id });
581
588
  if (match)
@@ -593,6 +600,15 @@ export async function createFederationRuntime(app, directory, handlers, options
593
600
  maxFps: Number(url.searchParams.get('max_fps')) || undefined,
594
601
  });
595
602
  }
603
+ else if (computer) {
604
+ if (!handlers.computer) {
605
+ logical.close(4403, 'API_NOT_ALLOWED');
606
+ continue;
607
+ }
608
+ handlers.computer(logical, url.searchParams.get('host') ?? '', {
609
+ protocol: url.searchParams.get('protocol') ?? undefined, port: url.searchParams.get('port') ?? undefined,
610
+ });
611
+ }
596
612
  else
597
613
  handlers.control(logical, randomUUID());
598
614
  }
@@ -601,7 +617,14 @@ export async function createFederationRuntime(app, directory, handlers, options
601
617
  if (!item || typeof packet.data !== 'string')
602
618
  throw new Error('INVALID_STREAM');
603
619
  const data = JSON.parse(packet.data);
604
- if (item.android) {
620
+ if (item.computer) {
621
+ if (!full(subjectId))
622
+ throw new Error('AUTHORIZATION_DENIED');
623
+ const types = item.computerProtocol === 'rdp' ? ['start', 'instruction', 'ack'] : ['data', 'ack'];
624
+ if (!types.includes(data?.type))
625
+ throw new Error('ACTION_NOT_ALLOWED');
626
+ }
627
+ else if (item.android) {
605
628
  if (!full(subjectId))
606
629
  throw new Error('AUTHORIZATION_DENIED');
607
630
  if (!['control', 'ack', 'ping', 'bitrate'].includes(data?.type))
@@ -0,0 +1,198 @@
1
+ import { Router } from 'express';
2
+ import { isDeepStrictEqual } from 'node:util';
3
+ import { hostname } from 'node:os';
4
+ import { Socket } from 'node:net';
5
+ import { resolveComputerHost } from './computerTarget.js';
6
+ import { handleRdpWebSocket, rdpBackendAvailable, stopRdpBridges } from './rdp.js';
7
+ import { computerLoginKey, computerPreferences } from '../shared/computerPreferences.js';
8
+ import { loadSettings, updateSettings } from '../utils/settings.js';
9
+ import { ComputerCredentialStore } from '../utils/computerCredentials.js';
10
+ export function createComputerRouter(read = loadSettings, update = updateSettings, credentials = new ComputerCredentialStore()) {
11
+ const router = Router();
12
+ router.get('/status', async (_req, res) => res.json({ platform: process.platform, hostname: hostname(), rdpAvailable: await rdpBackendAvailable() }));
13
+ router.get('/preferences', (_req, res) => {
14
+ try {
15
+ const saved = read().computerControl;
16
+ const preferences = computerPreferences(saved, process.platform);
17
+ let credentialKeys = [], credentialError = false;
18
+ try {
19
+ credentialKeys = [preferences.local, preferences.remote].filter(profile => profile.rememberLogin && credentials.has(profile)).map(computerLoginKey);
20
+ }
21
+ catch {
22
+ credentialError = true;
23
+ }
24
+ res.set('Cache-Control', 'no-store').json({ preferences, configured: Boolean(saved), credentialKeys, credentialError });
25
+ }
26
+ catch {
27
+ res.status(500).json({ error: 'COMPUTER_PREFERENCES_READ_FAILED' });
28
+ }
29
+ });
30
+ // The full-service encrypted API boundary applies to every credential route.
31
+ router.post('/credentials/:action', (req, res) => {
32
+ res.set('Cache-Control', 'no-store');
33
+ try {
34
+ const { profile, password } = req.body ?? {};
35
+ const preferences = computerPreferences(read().computerControl, process.platform);
36
+ const matched = [preferences.local, preferences.remote].find(value => isDeepStrictEqual(value, profile));
37
+ const action = req.params.action;
38
+ const allowedKeys = action === 'save' ? ['profile', 'password'] : ['profile'];
39
+ if (!matched || !['save', 'use', 'forget'].includes(action) || Object.keys(req.body).some(key => !allowedKeys.includes(key))) {
40
+ res.status(400).json({ error: 'INVALID_COMPUTER_CREDENTIALS' });
41
+ return;
42
+ }
43
+ if (action === 'forget') {
44
+ credentials.forget(matched);
45
+ res.json({ saved: false });
46
+ return;
47
+ }
48
+ if (!matched.rememberLogin) {
49
+ res.status(400).json({ error: 'COMPUTER_LOGIN_NOT_REMEMBERED' });
50
+ return;
51
+ }
52
+ if (action === 'save') {
53
+ if (typeof password !== 'string' || !password || password.length > 1024 || password.includes('\0')) {
54
+ res.status(400).json({ error: 'INVALID_COMPUTER_CREDENTIALS' });
55
+ return;
56
+ }
57
+ credentials.save(matched, password);
58
+ res.json({ saved: true });
59
+ return;
60
+ }
61
+ const secret = credentials.get(matched);
62
+ if (!secret) {
63
+ res.status(404).json({ error: 'COMPUTER_CREDENTIALS_MISSING' });
64
+ return;
65
+ }
66
+ // Only the authenticated service page receives it, in memory, over Noise.
67
+ res.json({ password: secret });
68
+ }
69
+ catch {
70
+ res.status(500).json({ error: 'COMPUTER_CREDENTIALS_FAILED' });
71
+ }
72
+ });
73
+ router.put('/preferences', (req, res) => {
74
+ const preferences = computerPreferences(req.body, process.platform);
75
+ // Strict projection prevents credentials and unknown fields entering settings.
76
+ if (!req.body || !isDeepStrictEqual(preferences, req.body)) {
77
+ res.status(400).json({ error: 'INVALID_COMPUTER_PREFERENCES' });
78
+ return;
79
+ }
80
+ try {
81
+ update((settings) => { settings.computerControl = preferences; });
82
+ res.json({ preferences });
83
+ }
84
+ catch {
85
+ res.status(500).json({ error: 'COMPUTER_PREFERENCES_WRITE_FAILED' });
86
+ }
87
+ });
88
+ return router;
89
+ }
90
+ export default createComputerRouter();
91
+ const bridges = new Set();
92
+ export function stopComputerBridges() {
93
+ stopRdpBridges();
94
+ for (const bridge of bridges)
95
+ bridge.destroy();
96
+ bridges.clear();
97
+ }
98
+ /** RFB bytes are base64 records inside the existing authenticated Noise tunnel.
99
+ * The eight-record receive window applies TCP backpressure without dropping bytes. */
100
+ export function handleComputerWebSocket(socket, host, options) {
101
+ if (options?.protocol === 'rdp') {
102
+ handleRdpWebSocket(socket, host, Number(options.port ?? 3389));
103
+ return;
104
+ }
105
+ if (options?.protocol && options.protocol !== 'vnc') {
106
+ socket.close(4400, 'COMPUTER_INVALID_PROTOCOL');
107
+ return;
108
+ }
109
+ let closed = false;
110
+ let tcp;
111
+ let sent = 0;
112
+ let acked = 0;
113
+ let lastAckAt = Date.now();
114
+ let connected = false;
115
+ const finish = (code = 1000, reason = '') => {
116
+ if (closed)
117
+ return;
118
+ closed = true;
119
+ clearTimeout(connectTimer);
120
+ clearInterval(flowTimer);
121
+ if (tcp) {
122
+ bridges.delete(tcp);
123
+ tcp.destroy();
124
+ }
125
+ socket.close(code, reason);
126
+ };
127
+ const connectTimer = setTimeout(() => finish(4408, 'COMPUTER_CONNECT_TIMEOUT'), 10_000);
128
+ const flowTimer = setInterval(() => {
129
+ if (sent > acked && Date.now() - lastAckAt > 30_000)
130
+ finish(4408, 'COMPUTER_STREAM_TIMEOUT');
131
+ }, 5000);
132
+ const pump = () => {
133
+ if (closed || !tcp)
134
+ return;
135
+ while (sent - acked < 8 && tcp.readableLength > 0) {
136
+ const bytes = tcp.read(Math.min(tcp.readableLength, 32 * 1024));
137
+ if (!bytes)
138
+ break;
139
+ if (sent === acked)
140
+ lastAckAt = Date.now();
141
+ socket.send(JSON.stringify({ type: 'data', seq: ++sent, data: bytes.toString('base64') }));
142
+ if (closed)
143
+ break;
144
+ }
145
+ };
146
+ socket.on('close', () => finish());
147
+ socket.on('error', () => finish());
148
+ socket.on('message', raw => {
149
+ if (closed)
150
+ return;
151
+ try {
152
+ const message = JSON.parse(raw.toString());
153
+ if (message.type === 'ack') {
154
+ if (!Number.isSafeInteger(message.seq) || message.seq <= acked || message.seq > sent)
155
+ throw new Error('INVALID_ACK');
156
+ acked = message.seq;
157
+ lastAckAt = Date.now();
158
+ pump();
159
+ }
160
+ else if (message.type === 'data') {
161
+ if (!connected || !tcp || typeof message.data !== 'string' || message.data.length > 88_000
162
+ || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(message.data))
163
+ throw new Error('INVALID_DATA');
164
+ const bytes = Buffer.from(message.data, 'base64');
165
+ if (!bytes.length || bytes.length > 64 * 1024 || tcp.writableLength + bytes.length > 1024 * 1024)
166
+ throw new Error('INPUT_LIMIT');
167
+ tcp.write(bytes);
168
+ }
169
+ else
170
+ throw new Error('INVALID_MESSAGE');
171
+ }
172
+ catch {
173
+ finish(4400, 'COMPUTER_INVALID_MESSAGE');
174
+ }
175
+ });
176
+ void (async () => {
177
+ try {
178
+ const address = await resolveComputerHost(host);
179
+ if (closed)
180
+ return;
181
+ tcp = new Socket();
182
+ bridges.add(tcp);
183
+ tcp.setNoDelay(true);
184
+ tcp.on('readable', pump);
185
+ tcp.on('connect', () => { connected = true; clearTimeout(connectTimer); });
186
+ tcp.on('end', () => finish(1000, 'COMPUTER_DISCONNECTED'));
187
+ tcp.on('close', () => finish(1000, 'COMPUTER_DISCONNECTED'));
188
+ tcp.on('error', error => {
189
+ const code = error.code;
190
+ finish(4502, code === 'ECONNREFUSED' ? 'COMPUTER_SCREEN_SHARING_OFF' : 'COMPUTER_UNREACHABLE');
191
+ });
192
+ tcp.connect({ host: address.address, family: address.family, port: 5900 });
193
+ }
194
+ catch (reason) {
195
+ finish(4502, reason instanceof Error && /^COMPUTER_(?:INVALID_HOST|PRIVATE_HOST_ONLY)$/.test(reason.message) ? reason.message : 'COMPUTER_UNREACHABLE');
196
+ }
197
+ })();
198
+ }
@@ -0,0 +1,23 @@
1
+ import { lookup } from 'node:dns/promises';
2
+ import { isIP } from 'node:net';
3
+ export function privateComputerAddress(address) {
4
+ if (isIP(address) === 4) {
5
+ const [a, b] = address.split('.').map(Number);
6
+ return a === 127 || a === 10 || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168);
7
+ }
8
+ const value = address.toLowerCase();
9
+ if (value.startsWith('::ffff:'))
10
+ return privateComputerAddress(value.slice(7));
11
+ return isIP(value) === 6 && (value === '::1' || value.startsWith('fc') || value.startsWith('fd'));
12
+ }
13
+ /** Resolve once, reject mixed public/private DNS answers, and dial the pinned IP. */
14
+ export async function resolveComputerHost(host) {
15
+ host = host.trim();
16
+ if (!host || host.length > 253 || (!isIP(host) && !/^[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/i.test(host))) {
17
+ throw new Error('COMPUTER_INVALID_HOST');
18
+ }
19
+ const addresses = await lookup(host, { all: true });
20
+ if (!addresses.length || addresses.some(item => !privateComputerAddress(item.address)))
21
+ throw new Error('COMPUTER_PRIVATE_HOST_ONLY');
22
+ return addresses[0];
23
+ }
@@ -0,0 +1,55 @@
1
+ export function guacamoleInstruction(...values) {
2
+ return values.map(value => `${Array.from(value).length}.${value}`).join(',') + ';';
3
+ }
4
+ /** Incremental UTF-8-decoded Guacamole parser. Lengths count Unicode code points. */
5
+ export class GuacamoleParser {
6
+ instruction;
7
+ digits = '';
8
+ remaining = 0;
9
+ value = '';
10
+ values = [];
11
+ phase = 'length';
12
+ size = 0;
13
+ constructor(instruction) {
14
+ this.instruction = instruction;
15
+ }
16
+ receive(chunk) {
17
+ for (const char of chunk) {
18
+ if (++this.size > 2 * 1024 * 1024)
19
+ throw new Error('GUAC_INSTRUCTION_LIMIT');
20
+ if (this.phase === 'length') {
21
+ if (/^[0-9]$/.test(char) && this.digits.length < 7)
22
+ this.digits += char;
23
+ else if (char === '.' && this.digits) {
24
+ this.remaining = Number(this.digits);
25
+ this.digits = '';
26
+ if (this.remaining > 1024 * 1024)
27
+ throw new Error('GUAC_ELEMENT_LIMIT');
28
+ this.phase = this.remaining ? 'value' : 'separator';
29
+ }
30
+ else
31
+ throw new Error('GUAC_INVALID_LENGTH');
32
+ }
33
+ else if (this.phase === 'value') {
34
+ this.value += char;
35
+ if (--this.remaining === 0)
36
+ this.phase = 'separator';
37
+ }
38
+ else {
39
+ if (char !== ',' && char !== ';')
40
+ throw new Error('GUAC_INVALID_SEPARATOR');
41
+ this.values.push(this.value);
42
+ this.value = '';
43
+ this.phase = 'length';
44
+ if (this.values.length > 256)
45
+ throw new Error('GUAC_ARGUMENT_LIMIT');
46
+ if (char === ';') {
47
+ const values = this.values;
48
+ this.values = [];
49
+ this.size = 0;
50
+ this.instruction(values);
51
+ }
52
+ }
53
+ }
54
+ }
55
+ }