termdock 1.4.261 → 1.4.263

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 (333) hide show
  1. package/dist/client/assets/App-Be0tsVAS.js +156 -0
  2. package/dist/client/assets/App-Be0tsVAS.js.br +0 -0
  3. package/dist/client/assets/App-Be0tsVAS.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-DCDXuo9g.js → ChangeWalkthroughPanel-CzrJ6gJP.js} +2 -2
  5. package/dist/client/assets/ChangeWalkthroughPanel-CzrJ6gJP.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-CzrJ6gJP.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-DQ-f4yDq.js → DagPlayground-C7RaD0hD.js} +1 -1
  8. package/dist/client/assets/DagPlayground-C7RaD0hD.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-C7RaD0hD.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-2IOw8MAu.js → DiffLab-Cl8rs4sk.js} +2 -2
  11. package/dist/client/assets/DiffLab-Cl8rs4sk.js.br +0 -0
  12. package/dist/client/assets/DiffLab-Cl8rs4sk.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-CVVPwpq8.js → DiffReview-D4715gv-.js} +1 -1
  14. package/dist/client/assets/DiffReview-D4715gv-.js.br +0 -0
  15. package/dist/client/assets/DiffReview-D4715gv-.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-D_ApcQkp.js → DiffReviewLab-DelgnRri.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-DelgnRri.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-DelgnRri.js.gz +0 -0
  19. package/dist/client/assets/DiffViewer-DxGkdCpG.js +36 -0
  20. package/dist/client/assets/DiffViewer-DxGkdCpG.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-DxGkdCpG.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-B7LjjdM1.js → ModelPreview-COGEMw6A.js} +1 -1
  23. package/dist/client/assets/ModelPreview-COGEMw6A.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-COGEMw6A.js.gz +0 -0
  25. package/dist/client/assets/RightSidebar-B4lwUjHl.js +1678 -0
  26. package/dist/client/assets/RightSidebar-B4lwUjHl.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-B4lwUjHl.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-mrLkUgFf.js → abnfDiagram-VCTEODGH-COWpaiS-.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-COWpaiS-.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-COWpaiS-.js.gz +0 -0
  31. package/dist/client/assets/app-DHnc6XU3.js +35 -0
  32. package/dist/client/assets/app-DHnc6XU3.js.br +0 -0
  33. package/dist/client/assets/app-DHnc6XU3.js.gz +0 -0
  34. package/dist/client/assets/{arc-DmupYkTe.js → arc-B5ZIGNkR.js} +1 -1
  35. package/dist/client/assets/arc-B5ZIGNkR.js.br +0 -0
  36. package/dist/client/assets/arc-B5ZIGNkR.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-BsHtkmDE.js → architectureDiagram-5GKGNRK7-D9KCt4gq.js} +1 -1
  38. package/dist/client/assets/architectureDiagram-5GKGNRK7-D9KCt4gq.js.br +0 -0
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-D9KCt4gq.js.gz +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-ncLrg2Xh.js → blockDiagram-I7D4REHJ-wwBKtSqS.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-wwBKtSqS.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-wwBKtSqS.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-EPHQSBvq.js → c4Diagram-7LVT6UL2-BSWUY2m8.js} +1 -1
  44. package/dist/client/assets/c4Diagram-7LVT6UL2-BSWUY2m8.js.br +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-BSWUY2m8.js.gz +0 -0
  46. package/dist/client/assets/channel-TGPTnJaC.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-xUusOn_a.js → chunk-2Q5K7J3B-C6tBCDpG.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-cYhK2MoD.js → chunk-5VM5RSS4-BboWsUGn.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-BgMxwAfS.js → chunk-F27PBJKO-t8LC2p6f.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-t8LC2p6f.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-t8LC2p6f.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-B6CRxrMW.js → chunk-IMKFNOWR-BLpilR76.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-BLpilR76.js.br +0 -0
  54. package/dist/client/assets/chunk-IMKFNOWR-BLpilR76.js.gz +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-DZn1jA44.js → chunk-JWPE2WC7-UAJgqWvI.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-DQ3eNn4I.js → chunk-POPQ4Y6H-CpVzK8jo.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-BmxWA3QV.js → chunk-SVP7TREG-irhVxs6O.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-irhVxs6O.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-irhVxs6O.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-CspXvKlS.js → chunk-TICWLB2K-Au4c5Tnt.js} +1 -1
  61. package/dist/client/assets/chunk-TICWLB2K-Au4c5Tnt.js.br +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-Au4c5Tnt.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-BqApWArB.js → chunk-XXDRQBXY-NlJK-TD4.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-ECZW8RQZ.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-ECZW8RQZ.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-CoFCEHUR.js → cose-bilkent-JH36ORCC-CBucN9Xa.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-CBucN9Xa.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-CBucN9Xa.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-DR69rOWi.js → cynefin-OW5HDTMX-DBkqdsPg.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-DBkqdsPg.js.br +0 -0
  71. package/dist/client/assets/cynefin-OW5HDTMX-DBkqdsPg.js.gz +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-Bx3WQxdI.js → cynefinDiagram-5FMLGOSQ-g-mNhqWP.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-g-mNhqWP.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-g-mNhqWP.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-D3fm-UnU.js → dagre-GXQ25YYZ-aMhTipKP.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-aMhTipKP.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-aMhTipKP.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-BaDXNCkJ.js → diagram-S7CK7UJ4-FTXlqV5C.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-FTXlqV5C.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-FTXlqV5C.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-CRm0G3Vr.js → diagram-UQ7AKVKN-CWKlCxDh.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-CWKlCxDh.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-CWKlCxDh.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-w6yyxMEN.js → diagram-VSXAHHWV-DuCN1zfS.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-DuCN1zfS.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-DuCN1zfS.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-C7AvCpG2.js → diagram-VX7I27RA-BYYvrnH1.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-BYYvrnH1.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-BYYvrnH1.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-Do48vc7u.js → diagram-Z3DM3KII-BJ_UXuWt.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-BJ_UXuWt.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-BJ_UXuWt.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-CtqbLVix.js → ebnfDiagram-PWID7BFC-BLb31-CG.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLb31-CG.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-BLb31-CG.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-GREU7AG0.js → erDiagram-RLTQ6QDP-Dt5T_zZr.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-Dt5T_zZr.js.br +0 -0
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-Dt5T_zZr.js.gz +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-AF48X1A6.js → flowDiagram-HODETNUW-CC8Rn9fH.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-CC8Rn9fH.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-CC8Rn9fH.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-DXN1ogyz.js → ganttDiagram-EL5Y4UJY-Bki2eBkC.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bki2eBkC.js.br +0 -0
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-Bki2eBkC.js.gz +0 -0
  105. package/dist/client/assets/{gestureArbiter-CGvVFAKz.js → gestureArbiter-BX-zjr4n.js} +1 -1
  106. package/dist/client/assets/gestureArbiter-BX-zjr4n.js.br +0 -0
  107. package/dist/client/assets/gestureArbiter-BX-zjr4n.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CvYbDGx_.js → gitGraphDiagram-WWUBYQGX-DlR3hAyj.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DlR3hAyj.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DlR3hAyj.js.gz +0 -0
  111. package/dist/client/assets/{index-mZxreyvP.css → index-ddQQwU7M.css} +1 -1
  112. package/dist/client/assets/index-ddQQwU7M.css.br +0 -0
  113. package/dist/client/assets/index-ddQQwU7M.css.gz +0 -0
  114. package/dist/client/assets/{infoDiagram-27XIBGKW-DjL3Ebik.js → infoDiagram-27XIBGKW-C-5zXbqy.js} +1 -1
  115. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-CPM34-nt.js → ishikawaDiagram-5VMMS53U-D_KAjhux.js} +1 -1
  116. package/dist/client/assets/ishikawaDiagram-5VMMS53U-D_KAjhux.js.br +0 -0
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-D_KAjhux.js.gz +0 -0
  118. package/dist/client/assets/{journeyDiagram-3NMN7TZE-lQ_yAhk1.js → journeyDiagram-3NMN7TZE-DasVMhbH.js} +1 -1
  119. package/dist/client/assets/journeyDiagram-3NMN7TZE-DasVMhbH.js.br +0 -0
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-DasVMhbH.js.gz +0 -0
  121. package/dist/client/assets/{kanban-definition-UXKFOSKX-C1uwQkzK.js → kanban-definition-UXKFOSKX-BeDZ9x71.js} +1 -1
  122. package/dist/client/assets/kanban-definition-UXKFOSKX-BeDZ9x71.js.br +0 -0
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-BeDZ9x71.js.gz +0 -0
  124. package/dist/client/assets/{layout-CoSxduiU.js → layout-BonFkvNe.js} +1 -1
  125. package/dist/client/assets/layout-BonFkvNe.js.br +0 -0
  126. package/dist/client/assets/layout-BonFkvNe.js.gz +0 -0
  127. package/dist/client/assets/{linear-BL_xULdD.js → linear-amKhUz-E.js} +1 -1
  128. package/dist/client/assets/linear-amKhUz-E.js.br +0 -0
  129. package/dist/client/assets/linear-amKhUz-E.js.gz +0 -0
  130. package/dist/client/assets/{mermaid.core-JMckmwXW.js → mermaid.core-C743W_cw.js} +5 -5
  131. package/dist/client/assets/mermaid.core-C743W_cw.js.br +0 -0
  132. package/dist/client/assets/mermaid.core-C743W_cw.js.gz +0 -0
  133. package/dist/client/assets/{mindmap-definition-YA3MSWOX-cvRobstd.js → mindmap-definition-YA3MSWOX-dnSMIb-2.js} +1 -1
  134. package/dist/client/assets/mindmap-definition-YA3MSWOX-dnSMIb-2.js.br +0 -0
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-dnSMIb-2.js.gz +0 -0
  136. package/dist/client/assets/{passwordBootstrap-pL3w9QFh.js → passwordBootstrap-Z-zMfeRE.js} +2 -2
  137. package/dist/client/assets/passwordBootstrap-Z-zMfeRE.js.br +0 -0
  138. package/dist/client/assets/passwordBootstrap-Z-zMfeRE.js.gz +0 -0
  139. package/dist/client/assets/{pegDiagram-XKGWAZYB-BBw52-pE.js → pegDiagram-XKGWAZYB-BMMrDjH3.js} +1 -1
  140. package/dist/client/assets/pegDiagram-XKGWAZYB-BMMrDjH3.js.br +0 -0
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-BMMrDjH3.js.gz +0 -0
  142. package/dist/client/assets/{pieDiagram-E7YTZNPT-DkpRcJ2F.js → pieDiagram-E7YTZNPT-CGZ4yhWL.js} +1 -1
  143. package/dist/client/assets/pieDiagram-E7YTZNPT-CGZ4yhWL.js.br +0 -0
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-CGZ4yhWL.js.gz +0 -0
  145. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-DXniqIK0.js → quadrantDiagram-AXDQQJYC-BfUxBRZx.js} +1 -1
  146. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BfUxBRZx.js.br +0 -0
  147. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BfUxBRZx.js.gz +0 -0
  148. package/dist/client/assets/{railroadDiagram-O6MQD6OU-CeTDIXQh.js → railroadDiagram-O6MQD6OU-DRtstXqY.js} +1 -1
  149. package/dist/client/assets/railroadDiagram-O6MQD6OU-DRtstXqY.js.br +0 -0
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-DRtstXqY.js.gz +0 -0
  151. package/dist/client/assets/{referenceLongPress-BD3NoqKH.js → referenceLongPress-OgQ-br0J.js} +1 -1
  152. package/dist/client/assets/referenceLongPress-OgQ-br0J.js.br +0 -0
  153. package/dist/client/assets/referenceLongPress-OgQ-br0J.js.gz +0 -0
  154. package/dist/client/assets/{requirementDiagram-BXWQKSXE-CIU0yJaX.js → requirementDiagram-BXWQKSXE-jIWvwxYZ.js} +1 -1
  155. package/dist/client/assets/requirementDiagram-BXWQKSXE-jIWvwxYZ.js.br +0 -0
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-jIWvwxYZ.js.gz +0 -0
  157. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-dDnMB9nm.js → sankeyDiagram-P5KCCOFB-CmV9o38-.js} +1 -1
  158. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CmV9o38-.js.br +0 -0
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-CmV9o38-.js.gz +0 -0
  160. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-DzaYOfu7.js → sequenceDiagram-WJ2MYXX4-C37VhGXR.js} +1 -1
  161. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C37VhGXR.js.br +0 -0
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-C37VhGXR.js.gz +0 -0
  163. package/dist/client/assets/{sizeCapture-INFHLROL-CueSAYic.js → sizeCapture-INFHLROL-D3aZUvMq.js} +1 -1
  164. package/dist/client/assets/sizeCapture-INFHLROL-D3aZUvMq.js.br +0 -0
  165. package/dist/client/assets/sizeCapture-INFHLROL-D3aZUvMq.js.gz +0 -0
  166. package/dist/client/assets/{sparkles-56xYHAnJ.js → sparkles-BwJHncXI.js} +1 -1
  167. package/dist/client/assets/{stateDiagram-D77RDMKH-im-7B3pn.js → stateDiagram-D77RDMKH-B3W9KWRf.js} +1 -1
  168. package/dist/client/assets/stateDiagram-D77RDMKH-B3W9KWRf.js.br +0 -0
  169. package/dist/client/assets/stateDiagram-D77RDMKH-B3W9KWRf.js.gz +0 -0
  170. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-D9yMXUEN.js +1 -0
  171. package/dist/client/assets/{swimlanes-42K2YHIH-bNyXNqi0.js → swimlanes-42K2YHIH-CgFI-TZF.js} +1 -1
  172. package/dist/client/assets/swimlanes-42K2YHIH-CgFI-TZF.js.br +0 -0
  173. package/dist/client/assets/swimlanes-42K2YHIH-CgFI-TZF.js.gz +0 -0
  174. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DfgFmAr4.js +8 -0
  175. package/dist/client/assets/{timeline-definition-24CTP7MA-BB9HIAin.js → timeline-definition-24CTP7MA-Ad00a495.js} +1 -1
  176. package/dist/client/assets/timeline-definition-24CTP7MA-Ad00a495.js.br +0 -0
  177. package/dist/client/assets/timeline-definition-24CTP7MA-Ad00a495.js.gz +0 -0
  178. package/dist/client/assets/{triangle-alert-_k_kGfRA.js → triangle-alert-DDQ67k7N.js} +1 -1
  179. package/dist/client/assets/{vennDiagram-4TSXK5OY-CjRz0A2w.js → vennDiagram-4TSXK5OY-CSawzFTi.js} +1 -1
  180. package/dist/client/assets/vennDiagram-4TSXK5OY-CSawzFTi.js.br +0 -0
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-CSawzFTi.js.gz +0 -0
  182. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DUp5G-FF.js → wardleyDiagram-VM6X3IG4-Emoh-n_o.js} +1 -1
  183. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Emoh-n_o.js.br +0 -0
  184. package/dist/client/assets/wardleyDiagram-VM6X3IG4-Emoh-n_o.js.gz +0 -0
  185. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DJj6fGG6.js → xychartDiagram-S5SC5T6Z-BPgImmcN.js} +1 -1
  186. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BPgImmcN.js.br +0 -0
  187. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-DJj6fGG6.js.gz → xychartDiagram-S5SC5T6Z-BPgImmcN.js.gz} +0 -0
  188. package/dist/client/index.html +2 -2
  189. package/dist/client/index.html.br +0 -0
  190. package/dist/client/index.html.gz +0 -0
  191. package/dist/client/sw.js +1 -1
  192. package/dist/client/sw.js.br +0 -0
  193. package/dist/client/sw.js.gz +0 -0
  194. package/dist/server/cli.js +336 -99
  195. package/dist/server/entry.js +11 -0
  196. package/dist/server/routes/terminal.js +80 -4
  197. package/dist/server/supervisor.js +525 -0
  198. package/dist/server/utils/backoff.js +15 -0
  199. package/dist/server/utils/crashForensics.js +227 -0
  200. package/dist/server/utils/healthProbe.js +92 -0
  201. package/dist/server/utils/ptySpawnBackoff.js +2 -1
  202. package/dist/server/utils/serverHealth.js +126 -0
  203. package/dist/server/utils/serverLogger.js +27 -4
  204. package/dist/server/utils/settings.js +38 -0
  205. package/dist/server/utils/supervisorClient.js +151 -0
  206. package/dist/server/utils/supervisorProtocol.js +159 -0
  207. package/dist/server/utils/termdockState.js +62 -0
  208. package/package.json +1 -1
  209. package/runtime-manifest.json +3 -3
  210. package/dist/client/assets/App-DvHlDcFI.js +0 -156
  211. package/dist/client/assets/App-DvHlDcFI.js.br +0 -0
  212. package/dist/client/assets/App-DvHlDcFI.js.gz +0 -0
  213. package/dist/client/assets/ChangeWalkthroughPanel-DCDXuo9g.js.br +0 -0
  214. package/dist/client/assets/ChangeWalkthroughPanel-DCDXuo9g.js.gz +0 -0
  215. package/dist/client/assets/DagPlayground-DQ-f4yDq.js.br +0 -0
  216. package/dist/client/assets/DagPlayground-DQ-f4yDq.js.gz +0 -0
  217. package/dist/client/assets/DiffLab-2IOw8MAu.js.br +0 -0
  218. package/dist/client/assets/DiffLab-2IOw8MAu.js.gz +0 -0
  219. package/dist/client/assets/DiffReview-CVVPwpq8.js.br +0 -0
  220. package/dist/client/assets/DiffReview-CVVPwpq8.js.gz +0 -0
  221. package/dist/client/assets/DiffReviewLab-D_ApcQkp.js.br +0 -0
  222. package/dist/client/assets/DiffReviewLab-D_ApcQkp.js.gz +0 -0
  223. package/dist/client/assets/DiffViewer-JbnGZ1in.js +0 -35
  224. package/dist/client/assets/DiffViewer-JbnGZ1in.js.br +0 -0
  225. package/dist/client/assets/DiffViewer-JbnGZ1in.js.gz +0 -0
  226. package/dist/client/assets/ModelPreview-B7LjjdM1.js.br +0 -0
  227. package/dist/client/assets/ModelPreview-B7LjjdM1.js.gz +0 -0
  228. package/dist/client/assets/RightSidebar-5-LgWsUf.js +0 -1678
  229. package/dist/client/assets/RightSidebar-5-LgWsUf.js.br +0 -0
  230. package/dist/client/assets/RightSidebar-5-LgWsUf.js.gz +0 -0
  231. package/dist/client/assets/abnfDiagram-VCTEODGH-mrLkUgFf.js.br +0 -0
  232. package/dist/client/assets/abnfDiagram-VCTEODGH-mrLkUgFf.js.gz +0 -0
  233. package/dist/client/assets/app-Cvxoycn7.js +0 -35
  234. package/dist/client/assets/app-Cvxoycn7.js.br +0 -0
  235. package/dist/client/assets/app-Cvxoycn7.js.gz +0 -0
  236. package/dist/client/assets/arc-DmupYkTe.js.br +0 -0
  237. package/dist/client/assets/arc-DmupYkTe.js.gz +0 -0
  238. package/dist/client/assets/architectureDiagram-5GKGNRK7-BsHtkmDE.js.br +0 -0
  239. package/dist/client/assets/architectureDiagram-5GKGNRK7-BsHtkmDE.js.gz +0 -0
  240. package/dist/client/assets/blockDiagram-I7D4REHJ-ncLrg2Xh.js.br +0 -0
  241. package/dist/client/assets/blockDiagram-I7D4REHJ-ncLrg2Xh.js.gz +0 -0
  242. package/dist/client/assets/c4Diagram-7LVT6UL2-EPHQSBvq.js.br +0 -0
  243. package/dist/client/assets/c4Diagram-7LVT6UL2-EPHQSBvq.js.gz +0 -0
  244. package/dist/client/assets/channel-D1PE0_Ej.js +0 -1
  245. package/dist/client/assets/chunk-F27PBJKO-BgMxwAfS.js.br +0 -0
  246. package/dist/client/assets/chunk-F27PBJKO-BgMxwAfS.js.gz +0 -0
  247. package/dist/client/assets/chunk-IMKFNOWR-B6CRxrMW.js.br +0 -0
  248. package/dist/client/assets/chunk-IMKFNOWR-B6CRxrMW.js.gz +0 -0
  249. package/dist/client/assets/chunk-SVP7TREG-BmxWA3QV.js.br +0 -0
  250. package/dist/client/assets/chunk-SVP7TREG-BmxWA3QV.js.gz +0 -0
  251. package/dist/client/assets/chunk-TICWLB2K-CspXvKlS.js.br +0 -0
  252. package/dist/client/assets/chunk-TICWLB2K-CspXvKlS.js.gz +0 -0
  253. package/dist/client/assets/classDiagram-ZZMXUADV-C6cf2s8I.js +0 -1
  254. package/dist/client/assets/classDiagram-v2-VYDZK3BY-C6cf2s8I.js +0 -1
  255. package/dist/client/assets/cose-bilkent-JH36ORCC-CoFCEHUR.js.br +0 -0
  256. package/dist/client/assets/cose-bilkent-JH36ORCC-CoFCEHUR.js.gz +0 -0
  257. package/dist/client/assets/cynefin-OW5HDTMX-DR69rOWi.js.br +0 -0
  258. package/dist/client/assets/cynefin-OW5HDTMX-DR69rOWi.js.gz +0 -0
  259. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Bx3WQxdI.js.br +0 -0
  260. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Bx3WQxdI.js.gz +0 -0
  261. package/dist/client/assets/dagre-GXQ25YYZ-D3fm-UnU.js.br +0 -0
  262. package/dist/client/assets/dagre-GXQ25YYZ-D3fm-UnU.js.gz +0 -0
  263. package/dist/client/assets/diagram-S7CK7UJ4-BaDXNCkJ.js.br +0 -0
  264. package/dist/client/assets/diagram-S7CK7UJ4-BaDXNCkJ.js.gz +0 -0
  265. package/dist/client/assets/diagram-UQ7AKVKN-CRm0G3Vr.js.br +0 -0
  266. package/dist/client/assets/diagram-UQ7AKVKN-CRm0G3Vr.js.gz +0 -0
  267. package/dist/client/assets/diagram-VSXAHHWV-w6yyxMEN.js.br +0 -0
  268. package/dist/client/assets/diagram-VSXAHHWV-w6yyxMEN.js.gz +0 -0
  269. package/dist/client/assets/diagram-VX7I27RA-C7AvCpG2.js.br +0 -0
  270. package/dist/client/assets/diagram-VX7I27RA-C7AvCpG2.js.gz +0 -0
  271. package/dist/client/assets/diagram-Z3DM3KII-Do48vc7u.js.br +0 -0
  272. package/dist/client/assets/diagram-Z3DM3KII-Do48vc7u.js.gz +0 -0
  273. package/dist/client/assets/ebnfDiagram-PWID7BFC-CtqbLVix.js.br +0 -0
  274. package/dist/client/assets/ebnfDiagram-PWID7BFC-CtqbLVix.js.gz +0 -0
  275. package/dist/client/assets/erDiagram-RLTQ6QDP-GREU7AG0.js.br +0 -0
  276. package/dist/client/assets/erDiagram-RLTQ6QDP-GREU7AG0.js.gz +0 -0
  277. package/dist/client/assets/flowDiagram-HODETNUW-AF48X1A6.js.br +0 -0
  278. package/dist/client/assets/flowDiagram-HODETNUW-AF48X1A6.js.gz +0 -0
  279. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DXN1ogyz.js.br +0 -0
  280. package/dist/client/assets/ganttDiagram-EL5Y4UJY-DXN1ogyz.js.gz +0 -0
  281. package/dist/client/assets/gestureArbiter-CGvVFAKz.js.br +0 -0
  282. package/dist/client/assets/gestureArbiter-CGvVFAKz.js.gz +0 -0
  283. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CvYbDGx_.js.br +0 -0
  284. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-CvYbDGx_.js.gz +0 -0
  285. package/dist/client/assets/index-mZxreyvP.css.br +0 -0
  286. package/dist/client/assets/index-mZxreyvP.css.gz +0 -0
  287. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CPM34-nt.js.br +0 -0
  288. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CPM34-nt.js.gz +0 -0
  289. package/dist/client/assets/journeyDiagram-3NMN7TZE-lQ_yAhk1.js.br +0 -0
  290. package/dist/client/assets/journeyDiagram-3NMN7TZE-lQ_yAhk1.js.gz +0 -0
  291. package/dist/client/assets/kanban-definition-UXKFOSKX-C1uwQkzK.js.br +0 -0
  292. package/dist/client/assets/kanban-definition-UXKFOSKX-C1uwQkzK.js.gz +0 -0
  293. package/dist/client/assets/layout-CoSxduiU.js.br +0 -0
  294. package/dist/client/assets/layout-CoSxduiU.js.gz +0 -0
  295. package/dist/client/assets/linear-BL_xULdD.js.br +0 -0
  296. package/dist/client/assets/linear-BL_xULdD.js.gz +0 -0
  297. package/dist/client/assets/mermaid.core-JMckmwXW.js.br +0 -0
  298. package/dist/client/assets/mermaid.core-JMckmwXW.js.gz +0 -0
  299. package/dist/client/assets/mindmap-definition-YA3MSWOX-cvRobstd.js.br +0 -0
  300. package/dist/client/assets/mindmap-definition-YA3MSWOX-cvRobstd.js.gz +0 -0
  301. package/dist/client/assets/passwordBootstrap-pL3w9QFh.js.br +0 -0
  302. package/dist/client/assets/passwordBootstrap-pL3w9QFh.js.gz +0 -0
  303. package/dist/client/assets/pegDiagram-XKGWAZYB-BBw52-pE.js.br +0 -0
  304. package/dist/client/assets/pegDiagram-XKGWAZYB-BBw52-pE.js.gz +0 -0
  305. package/dist/client/assets/pieDiagram-E7YTZNPT-DkpRcJ2F.js.br +0 -0
  306. package/dist/client/assets/pieDiagram-E7YTZNPT-DkpRcJ2F.js.gz +0 -0
  307. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DXniqIK0.js.br +0 -0
  308. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DXniqIK0.js.gz +0 -0
  309. package/dist/client/assets/railroadDiagram-O6MQD6OU-CeTDIXQh.js.br +0 -0
  310. package/dist/client/assets/railroadDiagram-O6MQD6OU-CeTDIXQh.js.gz +0 -0
  311. package/dist/client/assets/referenceLongPress-BD3NoqKH.js.br +0 -0
  312. package/dist/client/assets/referenceLongPress-BD3NoqKH.js.gz +0 -0
  313. package/dist/client/assets/requirementDiagram-BXWQKSXE-CIU0yJaX.js.br +0 -0
  314. package/dist/client/assets/requirementDiagram-BXWQKSXE-CIU0yJaX.js.gz +0 -0
  315. package/dist/client/assets/sankeyDiagram-P5KCCOFB-dDnMB9nm.js.br +0 -0
  316. package/dist/client/assets/sankeyDiagram-P5KCCOFB-dDnMB9nm.js.gz +0 -0
  317. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DzaYOfu7.js.br +0 -0
  318. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-DzaYOfu7.js.gz +0 -0
  319. package/dist/client/assets/sizeCapture-INFHLROL-CueSAYic.js.br +0 -0
  320. package/dist/client/assets/sizeCapture-INFHLROL-CueSAYic.js.gz +0 -0
  321. package/dist/client/assets/stateDiagram-D77RDMKH-im-7B3pn.js.br +0 -0
  322. package/dist/client/assets/stateDiagram-D77RDMKH-im-7B3pn.js.gz +0 -0
  323. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-f2rjjFm2.js +0 -1
  324. package/dist/client/assets/swimlanes-42K2YHIH-bNyXNqi0.js.br +0 -0
  325. package/dist/client/assets/swimlanes-42K2YHIH-bNyXNqi0.js.gz +0 -0
  326. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BFOOCrnj.js +0 -8
  327. package/dist/client/assets/timeline-definition-24CTP7MA-BB9HIAin.js.br +0 -0
  328. package/dist/client/assets/timeline-definition-24CTP7MA-BB9HIAin.js.gz +0 -0
  329. package/dist/client/assets/vennDiagram-4TSXK5OY-CjRz0A2w.js.br +0 -0
  330. package/dist/client/assets/vennDiagram-4TSXK5OY-CjRz0A2w.js.gz +0 -0
  331. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DUp5G-FF.js.br +0 -0
  332. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DUp5G-FF.js.gz +0 -0
  333. package/dist/client/assets/xychartDiagram-S5SC5T6Z-DJj6fGG6.js.br +0 -0
@@ -24,7 +24,7 @@ import { getPtyHostManager } from '../ptyhost/manager.js';
24
24
  import { pathValidator } from '../utils/pathValidator.js';
25
25
  import { TERMINAL, TMUX } from '../config.js';
26
26
  import { localAccessManager } from '../utils/localAccess.js';
27
- import { normalizeLocalAccessName, getLocaleSetting, setLocaleSetting, getContextDraftHeightSetting, setContextDraftHeightSetting, getAutoRenameAgentsSetting, setAutoRenameAgentsSetting, getAutoRenameNamerSetting, setAutoRenameNamerSetting, getAutoRenameModelsSetting, setAutoRenameModelsSetting, getAutoRenameIntervalMinutesSetting, setAutoRenameIntervalMinutesSetting, getAutoRenamePromptPreferenceSetting, setAutoRenamePromptPreferenceSetting, getAutoRenamePromptPayloadCharsSetting, setAutoRenamePromptPayloadCharsSetting, getNewSessionAgentSlugSetting, setNewSessionAgentSlugSetting, getRunningSessionButtonEnabledSetting, getAttentionButtonEnabledSetting, getCollaborationPanelsSetting, setCollaborationPanelSetting, getAndroidPanelSetting, setAndroidPanelSetting, normalizeAndroidPanel, getCollaborationFloatingGroupIdSetting, setCollaborationFloatingGroupIdSetting, getServiceSwitcherExpandedSetting, setServiceSwitcherExpandedSetting, setRunningSessionButtonEnabledSetting, setAttentionButtonEnabledSetting, getFileSortModesSetting, setFileSortModesSetting, setFileSortModeSetting, getNestedGitScanRootsSetting, setNestedGitScanRootSetting, getPinnedExplorerRootsSetting, setPinnedExplorerRootsSetting, setPinnedExplorerRootSetting, watchPinnedExplorerRootsSetting, } from '../utils/settings.js';
27
+ import { normalizeLocalAccessName, getLocaleSetting, setLocaleSetting, getContextDraftHeightSetting, setContextDraftHeightSetting, getAutoRenameAgentsSetting, setAutoRenameAgentsSetting, getAutoRenameNamerSetting, setAutoRenameNamerSetting, getAutoRenameModelsSetting, setAutoRenameModelsSetting, getAutoRenameIntervalMinutesSetting, setAutoRenameIntervalMinutesSetting, getAutoRenamePromptPreferenceSetting, setAutoRenamePromptPreferenceSetting, getAutoRenamePromptPayloadCharsSetting, setAutoRenamePromptPayloadCharsSetting, getNewSessionAgentSlugSetting, setNewSessionAgentSlugSetting, getRunningSessionButtonEnabledSetting, getAttentionButtonEnabledSetting, getCollaborationPanelsSetting, setCollaborationPanelSetting, getAndroidPanelSetting, setAndroidPanelSetting, normalizeAndroidPanel, getCollaborationFloatingGroupIdSetting, setCollaborationFloatingGroupIdSetting, getServiceSwitcherExpandedSetting, setServiceSwitcherExpandedSetting, setRunningSessionButtonEnabledSetting, setAttentionButtonEnabledSetting, getFileSortModesSetting, setFileSortModesSetting, setFileSortModeSetting, getNestedGitScanRootsSetting, setNestedGitScanRootSetting, getPinnedExplorerRootsSetting, setPinnedExplorerRootsSetting, setPinnedExplorerRootSetting, watchPinnedExplorerRootsSetting, getActiveGitReposSetting, setActiveGitRepoSetting, } from '../utils/settings.js';
28
28
  import { loadContextDraft, saveContextDraft } from '../utils/contextDraft.js';
29
29
  import { getOnboardingServerUrl } from '../onboardingServer.js';
30
30
  import { inspectLinuxInotifyUsage, isWatchResourceExhaustion } from '../utils/fileWatchPolicy.js';
@@ -38,6 +38,7 @@ import { AgentResumeHistoryStore } from '../agent/resumeHistory.js';
38
38
  import { isTmuxRecoveryCandidate } from '../utils/tmuxRecoveryCandidate.js';
39
39
  import { AutomationStore, normalizeAutomationSchedule } from '../agent/automationStore.js';
40
40
  import { buildBracketedSubmitBytes, canDeliverPromptToAgent } from '../agent/promptDelivery.js';
41
+ import { buildServerHealthState, dismissServerHealth } from '../utils/serverHealth.js';
41
42
  import { collaborationRoutes } from '../agent/collaborationRoutes.js';
42
43
  import { extrasFromBody } from '../agent/collaborationProtocol.js';
43
44
  import { CollaborationStore } from '../agent/collaborationStore.js';
@@ -292,10 +293,43 @@ watchPinnedExplorerRootsSetting((pinnedExplorerRoots) => {
292
293
  });
293
294
  });
294
295
  let serverRestartScheduled = false;
296
+ /**
297
+ * supervision 通道是否真的还在。
298
+ *
299
+ * `TERMDOCK_SUPERVISED` 只说明"出生时有人管",`process.connected` 才是"此刻还管得着"——
300
+ * supervisor 自己崩掉之后子进程是被故意留下来继续服务的(丢监督好过丢服务),
301
+ * 那种情况下如果还按受管路径走,就会变成"报告完意图就自杀,而没人接盘",
302
+ * 比旧 bridge 还糟。所以两个条件必须同时成立。
303
+ */
304
+ function supervisionChannelOpen() {
305
+ return process.env.TERMDOCK_SUPERVISED === '1' && process.connected === true;
306
+ }
295
307
  function requestServerRestartAfterUpdate() {
296
308
  if (serverRestartScheduled)
297
309
  return;
298
310
  serverRestartScheduled = true;
311
+ if (supervisionChannelOpen()) {
312
+ // 只说"我要重启",不说"怎么重启":重启用哪条命令、PATH 里是谁、要验证什么,
313
+ // 全是 supervisor 的事,而它每次都用 realpath 后的 entry 重新读盘。
314
+ // 用 IPC 而不是标记文件,是因为意图会随进程死亡一起消失,不可能过期后被误读。
315
+ try {
316
+ process.send?.({ type: 'termdock-intent', intent: 'restart-after-update' });
317
+ }
318
+ catch {
319
+ // 通道刚好断了:照样退,supervisor 会把这次退出当成崩溃重启,
320
+ // 结果同样是"服务带着新版本回来",只是 crash.log 里的事件名不同。
321
+ }
322
+ const exitTimer = setTimeout(() => {
323
+ process.kill(process.pid, 'SIGTERM');
324
+ }, 750);
325
+ exitTimer.unref?.();
326
+ return;
327
+ }
328
+ // —— 以下为未受管路径(`--foreground` / Electron / 外部进程管理器)——
329
+ // 发射后不管:不验证新进程是否起来、不重试、失败也不回滚,
330
+ // 「更新后服务没起来」正是这个形状造成的。没有监督者时别无选择,
331
+ // 但它不再是默认路径——受管启动会走上面的分支。
332
+ //
299
333
  // The bridge is detached before this process exits. It waits for the old
300
334
  // PID to release the port, then resolves `termdock` from PATH again so an
301
335
  // npm-replaced global binary is used instead of the old loaded file.
@@ -6170,6 +6204,7 @@ async function getSettingsPayload() {
6170
6204
  serviceSwitcherExpanded: getServiceSwitcherExpandedSetting(),
6171
6205
  fileSortModes: getFileSortModesSetting(),
6172
6206
  nestedGitScanRoots: getNestedGitScanRootsSetting(),
6207
+ activeGitRepos: getActiveGitReposSetting(),
6173
6208
  pinnedExplorerRoots: getPinnedExplorerRootsSetting(),
6174
6209
  localAccess: {
6175
6210
  ...localAccess,
@@ -6191,9 +6226,17 @@ router.get('/update', (_req, res) => {
6191
6226
  });
6192
6227
  router.post('/update/check', async (_req, res) => {
6193
6228
  try {
6194
- res.json(desktopRuntimeOwner
6195
- ? await desktopRuntimeOwner.checkForUpdates()
6196
- : await npmAutoUpdateManager.checkNow());
6229
+ if (desktopRuntimeOwner) {
6230
+ res.json(await desktopRuntimeOwner.checkForUpdates());
6231
+ return;
6232
+ }
6233
+ // npm 查询加全局安装动辄二三十秒,而加密传输对 30s 内收不到任何回包就判超时,
6234
+ // 客户端会弹「服务响应超时」——更新其实装完了。检查照常跑,进度与结果都由控制
6235
+ // WS 的 update-state 广播推送,这里立刻回当前状态(通常是 checking)即可。
6236
+ void npmAutoUpdateManager.checkNow().catch((error) => {
6237
+ console.warn(`[auto-update] ${error instanceof Error ? error.message : String(error)}`);
6238
+ });
6239
+ res.status(202).json(npmAutoUpdateManager.getState());
6197
6240
  }
6198
6241
  catch (error) {
6199
6242
  res.status(503).json({ error: error instanceof Error ? error.message : String(error) });
@@ -6210,6 +6253,14 @@ router.post('/update/restart', async (_req, res) => {
6210
6253
  res.status(409).json({ error: error instanceof Error ? error.message : String(error) });
6211
6254
  }
6212
6255
  });
6256
+ // 「服务最近出过什么事」:红点与设置面板的唯一数据源。
6257
+ // 只读快照,不含任何可变更操作——所以不需要 CSRF,和 GET /update 同档。
6258
+ router.get('/server-health', (_req, res) => {
6259
+ res.json(buildServerHealthState());
6260
+ });
6261
+ router.post('/server-health/dismiss', (_req, res) => {
6262
+ res.status(202).json(dismissServerHealth());
6263
+ });
6213
6264
  router.put('/settings', async (req, res) => {
6214
6265
  const body = req.body ?? {};
6215
6266
  let pinnedExplorerRootsChanged = false;
@@ -6343,6 +6394,28 @@ router.put('/settings', async (req, res) => {
6343
6394
  }
6344
6395
  setNestedGitScanRootSetting(preference.rootPath, preference.enabled);
6345
6396
  }
6397
+ if (body.activeGitRepo && typeof body.activeGitRepo === 'object') {
6398
+ const preference = body.activeGitRepo;
6399
+ const contextKey = preference.contextKey;
6400
+ const repoRoot = preference.repoRoot;
6401
+ // The context key is the client's layout — session id plus workspace root,
6402
+ // or a split-workspace id — so it is validated by shape only. `null` is the
6403
+ // explicit "every repository", which clears the entry.
6404
+ const validContextKey = typeof contextKey === 'string'
6405
+ && contextKey.length > 0
6406
+ && contextKey.length <= 4300
6407
+ && !/[\u0000-\u001f\u007f]/.test(contextKey.split('\u0000').join(''));
6408
+ const validRepoRoot = repoRoot === null
6409
+ || (typeof repoRoot === 'string'
6410
+ && repoRoot.length > 0
6411
+ && repoRoot.length <= 4096
6412
+ && (repoRoot.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(repoRoot)));
6413
+ if (!validContextKey || !validRepoRoot) {
6414
+ res.status(400).json({ error: 'Invalid active git repository preference', code: 'ACTIVE_GIT_REPO_INVALID' });
6415
+ return;
6416
+ }
6417
+ setActiveGitRepoSetting(contextKey, repoRoot ?? null);
6418
+ }
6346
6419
  if (body.pinnedExplorerRoots && typeof body.pinnedExplorerRoots === 'object' && !Array.isArray(body.pinnedExplorerRoots)) {
6347
6420
  setPinnedExplorerRootsSetting(body.pinnedExplorerRoots);
6348
6421
  pinnedExplorerRootsChanged = true;
@@ -8180,6 +8253,9 @@ export function handleControlWebSocket(ws, clientId) {
8180
8253
  type: 'update-state',
8181
8254
  state: desktopRuntimeOwner?.getState() ?? npmAutoUpdateManager.getState(),
8182
8255
  }));
8256
+ // 服务健康快照:崩溃时服务自己已经死了、推不出任何东西,所以"红点"这件事
8257
+ // 只能靠客户端重连时的这一发。客户端断线重连的既有逻辑正好覆盖了那个时刻。
8258
+ ws.send(JSON.stringify({ type: 'server-health', state: buildServerHealthState() }));
8183
8259
  }
8184
8260
  catch {
8185
8261
  controlClients.delete(clientId);
@@ -0,0 +1,525 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * termdock supervisor — 盯住服务子进程,挂了就重启,并把原因记下来。
4
+ *
5
+ * 它解决的是两件今天无人负责的事:
6
+ * 1. 自动更新后新进程没起来(旧实现是发射后不管的 `node -e` bridge,不验证、不重试);
7
+ * 2. 运行中崩溃(全仓没有 uncaughtException 处理,server.log 连时间戳都没有)。
8
+ *
9
+ * 形态:一个**独立进程**,不是服务内的自重启。自重启活不过 SIGKILL/OOM,
10
+ * 而那两种恰恰是最需要被兜住的。
11
+ *
12
+ * 架构:
13
+ * ```
14
+ * td / setsid (launcher,短命)
15
+ * └─ supervisor (detached 或前台,stdio 指向 server.log + ipc)
16
+ * └─ server (同进程组,stdio 同样指向 server.log + ipc)
17
+ * ```
18
+ * 子进程刻意**不 detached**:同进程组便于整组 kill。但 supervisor 自己死掉时
19
+ * 不连坐杀服务——丢监督好过丢服务。
20
+ *
21
+ * Standalone entry: 只依赖 node stdlib + utils/ 下几个轻量模块,
22
+ * **绝不 import cli.ts / entry.ts**(那会拖进整个服务运行时)。
23
+ */
24
+ import { spawn } from 'node:child_process';
25
+ import fs from 'node:fs';
26
+ import path from 'node:path';
27
+ import { fileURLToPath } from 'node:url';
28
+ import { backoffDelayMs } from './utils/backoff.js';
29
+ import { installCrashForensics, isOomSuspect, readRuntimeTail, readServerLogTail } from './utils/crashForensics.js';
30
+ import { probeHealthOnce, HEALTH_PROBE_TIMEOUT_MS } from './utils/healthProbe.js';
31
+ import { writeJsonLogSync } from './utils/serverLogger.js';
32
+ import { SUPERVISOR_STATE_VERSION, classifyChildExit, isReadyMessage, isServerIntentMessage, shouldGiveUp, } from './utils/supervisorProtocol.js';
33
+ import { SUPERVISOR_STATE_PATH, STOP_GRACE_MS, isProcessRunning, removeJsonState, writeJsonStateAtomic, } from './utils/termdockState.js';
34
+ export const DEFAULT_SUPERVISOR_TIMING = {
35
+ readyTimeoutMs: 30_000,
36
+ healthIntervalMs: 30_000,
37
+ healthProbeTimeoutMs: HEALTH_PROBE_TIMEOUT_MS,
38
+ wedgeFailures: 3,
39
+ killGraceMs: STOP_GRACE_MS,
40
+ restartDelayMs: 250,
41
+ crashResetMs: 60_000,
42
+ maxConsecutiveCrashes: 5,
43
+ backoffBaseMs: 1_000,
44
+ backoffMaxMs: 30_000,
45
+ };
46
+ /**
47
+ * 节奏可以由 `TERMDOCK_SUPERVISOR_TIMING`(JSON)覆盖。
48
+ *
49
+ * 存在的理由之一是测试:崩溃循环用例要么等 5 次真实退避(1+2+4+8+16 = 31s),
50
+ * 要么把节奏变成可注入的。顺带它也是生产上的调参口子(探测密度、退避上限)。
51
+ * 只接受**正数**:0 会让 setInterval 空转。
52
+ */
53
+ export function resolveTimingFromEnv(env = process.env) {
54
+ const raw = env.TERMDOCK_SUPERVISOR_TIMING;
55
+ if (!raw)
56
+ return {};
57
+ let parsed;
58
+ try {
59
+ parsed = JSON.parse(raw);
60
+ }
61
+ catch {
62
+ process.stderr.write('[supervisor] TERMDOCK_SUPERVISOR_TIMING is not valid JSON; using defaults\n');
63
+ return {};
64
+ }
65
+ if (!parsed || typeof parsed !== 'object')
66
+ return {};
67
+ const source = parsed;
68
+ const overrides = {};
69
+ for (const key of Object.keys(DEFAULT_SUPERVISOR_TIMING)) {
70
+ const value = source[key];
71
+ if (typeof value === 'number' && Number.isFinite(value) && value > 0)
72
+ overrides[key] = value;
73
+ }
74
+ return overrides;
75
+ }
76
+ export function defaultSpawnChild(config) {
77
+ const child = spawn(process.execPath, [config.childEntry, '--foreground', ...config.childArgs], {
78
+ detached: false,
79
+ stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
80
+ env: {
81
+ ...process.env,
82
+ TERMDOCK_SUPERVISED: '1',
83
+ // 给子进程一个能指名道姓的 pid:它的 disconnect 处理器要靠这个说清
84
+ // "是谁丢下了我",`ps` 里也能直接认出来。
85
+ TERMDOCK_SUPERVISOR_PID: String(process.pid),
86
+ },
87
+ });
88
+ // spawn 失败(ENOENT / EACCES)只会在 'error' 里出现,不会走 'exit'。
89
+ // 旧实现两个 spawn 都没挂 error 处理,于是"启动失败"表现为"什么都没发生"。
90
+ return child;
91
+ }
92
+ export function defaultNotifyLauncher(message) {
93
+ if (process.connected)
94
+ process.send?.(message);
95
+ }
96
+ export function defaultLog(message) {
97
+ // 走 stderr:它和子进程的 stdout/stderr 是同一个 fd(server.log),
98
+ // 于是 `tail -f ~/.termdock/server.log` 能同时看到服务和监督者的叙述。
99
+ process.stderr.write(`${new Date().toISOString()} ${message}\n`);
100
+ }
101
+ /**
102
+ * 监督一台服务。返回对象由入口按需驱动;测试直接调 handlers。
103
+ *
104
+ * 这里不装全局进程处理器——入口(main)负责那些,本函数只管状态机。
105
+ */
106
+ export function createSupervisor(config, deps) {
107
+ const timing = { ...DEFAULT_SUPERVISOR_TIMING, ...(config.timing ?? {}) };
108
+ const now = deps.now ?? (() => Date.now());
109
+ const supervisorStartedAt = now();
110
+ let child = null;
111
+ let childPid = null;
112
+ let childStartedAt = 0;
113
+ let becameReady = false;
114
+ /** 本世代的 IPC 意图,随每次 spawn 重置 */
115
+ let intent = null;
116
+ let portConflict = false;
117
+ let wedgeKill = false;
118
+ let stopRequested = false;
119
+ let restartRequested = false;
120
+ let consecutiveCrashes = 0;
121
+ let restarts = 0;
122
+ let lastIncident = null;
123
+ let lastHealthOkAt = null;
124
+ let healthFailures = 0;
125
+ let phase = 'starting';
126
+ let readyTimer = null;
127
+ let healthTimer = null;
128
+ let crashResetTimer = null;
129
+ let restartTimer = null;
130
+ let stopping = false;
131
+ const clearTimers = () => {
132
+ for (const timer of [readyTimer, healthTimer, crashResetTimer, restartTimer]) {
133
+ if (timer)
134
+ clearTimeout(timer);
135
+ }
136
+ readyTimer = null;
137
+ healthTimer = null;
138
+ crashResetTimer = null;
139
+ restartTimer = null;
140
+ };
141
+ const persistState = () => {
142
+ deps.writeState({
143
+ version: SUPERVISOR_STATE_VERSION,
144
+ pid: process.pid,
145
+ serverPid: childPid,
146
+ phase,
147
+ startedAt: supervisorStartedAt,
148
+ restarts,
149
+ consecutiveCrashes,
150
+ lastIncident,
151
+ });
152
+ };
153
+ const setPhase = (next) => {
154
+ phase = next;
155
+ persistState();
156
+ };
157
+ function startChild() {
158
+ intent = null;
159
+ portConflict = false;
160
+ wedgeKill = false;
161
+ restartRequested = false;
162
+ becameReady = false;
163
+ healthFailures = 0;
164
+ childStartedAt = now();
165
+ setPhase('starting');
166
+ let errored = null;
167
+ const spawned = deps.spawnChild(config);
168
+ child = spawned;
169
+ childPid = spawned.pid ?? null;
170
+ spawned.on('error', (error) => {
171
+ errored = error;
172
+ deps.log(`[supervisor] failed to spawn child: ${error.message}`);
173
+ // 抢在 'exit' 之前记下原因;真正的出口统一走 handleExit。
174
+ portConflict = false;
175
+ handleExit(null, null, error);
176
+ });
177
+ spawned.on('message', (message) => {
178
+ if (isReadyMessage(message)) {
179
+ becameReady = true;
180
+ lastHealthOkAt = now();
181
+ if (readyTimer) {
182
+ clearTimeout(readyTimer);
183
+ readyTimer = null;
184
+ }
185
+ setPhase('running');
186
+ armCrashReset();
187
+ deps.log(`[supervisor] server ready (pid ${childPid ?? 'unknown'}, restarts ${restarts})`);
188
+ // 透传给 launcher:`td` 后台模式等的就是这个,而不是自己盲等 10 秒。
189
+ deps.notifyLauncher({ type: 'termdock-ready' });
190
+ return;
191
+ }
192
+ if (isServerIntentMessage(message)) {
193
+ intent = message.intent;
194
+ deps.log(`[supervisor] server declared intent: ${message.intent}`);
195
+ }
196
+ });
197
+ spawned.on('exit', (code, signal) => {
198
+ if (errored)
199
+ return;
200
+ handleExit(code, signal, null);
201
+ });
202
+ readyTimer = setTimeout(() => {
203
+ if (becameReady || stopping)
204
+ return;
205
+ deps.log(`[supervisor] server did not become ready within ${timing.readyTimeoutMs}ms; restarting`);
206
+ wedgeKill = false;
207
+ killChild('SIGTERM');
208
+ }, timing.readyTimeoutMs);
209
+ armHealthProbe();
210
+ }
211
+ /** 连续健康运行满 crashResetMs 就把崩溃计数清零——否则一天里零星崩 5 次也会被判"放弃"。 */
212
+ function armCrashReset() {
213
+ if (crashResetTimer)
214
+ clearTimeout(crashResetTimer);
215
+ const generation = childPid;
216
+ crashResetTimer = setTimeout(() => {
217
+ if (childPid !== generation || !becameReady)
218
+ return;
219
+ if (consecutiveCrashes !== 0) {
220
+ deps.log(`[supervisor] server stayed healthy for ${timing.crashResetMs}ms; clearing crash counter`);
221
+ }
222
+ consecutiveCrashes = 0;
223
+ persistState();
224
+ }, timing.crashResetMs);
225
+ }
226
+ /**
227
+ * 健康探测。卡死的事件循环不会应答任何请求,所以"超时"就是"卡死",
228
+ * 不需要额外的心跳协议。
229
+ */
230
+ function armHealthProbe() {
231
+ if (healthTimer)
232
+ clearInterval(healthTimer);
233
+ healthTimer = setInterval(() => {
234
+ if (stopping || !becameReady || !child)
235
+ return;
236
+ void deps.probe(config.healthUrl, config.healthCaPath, timing.healthProbeTimeoutMs).then((result) => {
237
+ if (stopping || !child)
238
+ return;
239
+ if (result.ok) {
240
+ healthFailures = 0;
241
+ lastHealthOkAt = now();
242
+ return;
243
+ }
244
+ healthFailures += 1;
245
+ deps.log(`[supervisor] health probe failed (${healthFailures}/${timing.wedgeFailures}): ${result.failure ?? 'unknown'}`);
246
+ if (healthFailures < timing.wedgeFailures)
247
+ return;
248
+ healthFailures = 0;
249
+ wedgeKill = true;
250
+ deps.log('[supervisor] server looks wedged; killing it so it can be restarted');
251
+ killChild('SIGTERM');
252
+ });
253
+ }, timing.healthIntervalMs);
254
+ }
255
+ function killChild(signal) {
256
+ if (!child)
257
+ return;
258
+ try {
259
+ child.kill(signal);
260
+ }
261
+ catch {
262
+ // 进程可能刚退出,'exit' 会跟上。
263
+ return;
264
+ }
265
+ if (signal !== 'SIGTERM' || !childPid)
266
+ return;
267
+ const targetPid = childPid;
268
+ const deadline = now() + timing.killGraceMs;
269
+ const escalate = setInterval(() => {
270
+ if (childPid !== targetPid || !isProcessRunning(targetPid)) {
271
+ clearInterval(escalate);
272
+ return;
273
+ }
274
+ if (now() >= deadline) {
275
+ clearInterval(escalate);
276
+ deps.log(`[supervisor] child ${targetPid} ignored SIGTERM; sending SIGKILL`);
277
+ try {
278
+ child?.kill('SIGKILL');
279
+ }
280
+ catch { /* already gone */ }
281
+ }
282
+ }, Math.max(100, Math.min(1_000, timing.killGraceMs / 4)));
283
+ // 该 interval 不该拖住 supervisor 的退出。
284
+ escalate.unref?.();
285
+ }
286
+ function handleExit(code, signal, spawnError) {
287
+ // 停止请求不做提前返回:它要走的是分类表里的 `stop` 分支(记录 + 删状态 + 退出 0)。
288
+ // 这里若直接 return,`--stop` 就会在 crash.log 里留下一段无法解释的空白。
289
+ if (healthTimer) {
290
+ clearInterval(healthTimer);
291
+ healthTimer = null;
292
+ }
293
+ if (readyTimer) {
294
+ clearTimeout(readyTimer);
295
+ readyTimer = null;
296
+ }
297
+ if (crashResetTimer) {
298
+ clearTimeout(crashResetTimer);
299
+ crashResetTimer = null;
300
+ }
301
+ const uptimeMs = now() - childStartedAt;
302
+ const exitedPid = childPid;
303
+ child = null;
304
+ childPid = null;
305
+ const runtimeTail = readRuntimeTail(childStartedAt);
306
+ const oomSuspect = isOomSuspect(runtimeTail);
307
+ const observation = {
308
+ intent,
309
+ exitCode: code,
310
+ signal,
311
+ becameReady,
312
+ supervisorStopRequested: stopRequested,
313
+ restartRequested,
314
+ wedge: wedgeKill,
315
+ portConflict,
316
+ oomSuspect,
317
+ };
318
+ const decision = classifyChildExit(observation);
319
+ // 计数先于记录:crash.log 里的 `consecutiveCrashes` 应当读作
320
+ // "算上这一次的第几次",否则五连崩会记成 0/1/2/3/4,而 gave-up 那条写 5。
321
+ if (decision.resetBackoff)
322
+ consecutiveCrashes = 0;
323
+ else if (decision.countAsCrash)
324
+ consecutiveCrashes += 1;
325
+ lastIncident = {
326
+ at: now(),
327
+ event: decision.event,
328
+ detail: spawnError ? `failed to spawn: ${spawnError.message}` : decision.detail,
329
+ exitCode: code,
330
+ signal,
331
+ uptimeMs,
332
+ version: config.version ?? null,
333
+ oomSuspect,
334
+ restartCount: restarts,
335
+ };
336
+ recordIncident(decision, observation, {
337
+ serverPid: exitedPid,
338
+ uptimeMs,
339
+ runtimeTail,
340
+ spawnError: spawnError?.message ?? null,
341
+ });
342
+ // 第一世代就没起来:立刻告诉 launcher,别让它空等到超时。
343
+ // 之后 supervisor 仍会按退避继续重试,所以只说"这次没起来",不说"放弃了"。
344
+ if (restarts === 0 && !becameReady && !stopRequested) {
345
+ deps.notifyLauncher({
346
+ type: 'termdock-startup-failed',
347
+ reason: spawnError ? 'spawn-failed' : decision.event,
348
+ detail: spawnError ? spawnError.message : decision.detail,
349
+ });
350
+ }
351
+ if (!decision.restart) {
352
+ deps.log(`[supervisor] server ${decision.event} (${decision.detail}); not restarting`);
353
+ setPhase('stopped');
354
+ deps.clearState();
355
+ deps.exit(0);
356
+ return;
357
+ }
358
+ if (shouldGiveUp(consecutiveCrashes, timing.maxConsecutiveCrashes)) {
359
+ lastIncident.detail = `${lastIncident.detail} — gave up after ${consecutiveCrashes} consecutive crashes`;
360
+ deps.log(`[supervisor] ${consecutiveCrashes} consecutive crashes; giving up. Service is NOT running.`);
361
+ recordApplicationGaveUp();
362
+ setPhase('gave-up');
363
+ deps.exit(1);
364
+ return;
365
+ }
366
+ restarts += 1;
367
+ const delayMs = decision.event === 'update-restart' || decision.event === 'manual-restart'
368
+ ? timing.restartDelayMs
369
+ : backoffDelayMs(consecutiveCrashes, { baseMs: timing.backoffBaseMs, maxMs: timing.backoffMaxMs });
370
+ setPhase('restarting');
371
+ deps.log(`[supervisor] restarting server in ${delayMs}ms (event=${decision.event}, restarts=${restarts})`);
372
+ restartTimer = setTimeout(() => {
373
+ restartTimer = null;
374
+ if (stopping)
375
+ return;
376
+ startChild();
377
+ }, delayMs);
378
+ }
379
+ function recordApplicationGaveUp() {
380
+ deps.recordIncident({
381
+ role: 'supervisor',
382
+ event: 'gave-up',
383
+ detail: `gave up after ${consecutiveCrashes} consecutive crashes`,
384
+ version: config.version ?? null,
385
+ restarts,
386
+ lastIncident,
387
+ restartCommand: config.version ? `npm i -g termdock@${config.version}` : null,
388
+ });
389
+ }
390
+ function recordIncident(decision, observation, extra) {
391
+ deps.recordIncident({
392
+ role: 'supervisor',
393
+ event: decision.event,
394
+ detail: decision.detail,
395
+ exitCode: observation.exitCode,
396
+ signal: observation.signal,
397
+ version: config.version ?? null,
398
+ supervisorPid: process.pid,
399
+ restartCount: restarts,
400
+ consecutiveCrashes,
401
+ lastHealthOkAt,
402
+ ...extra,
403
+ logTail: readServerLogTail(),
404
+ });
405
+ }
406
+ /** launcher 发的停止请求:杀子进程、删状态、退出。收尾统一走 handleExit 的 `stop` 分支。 */
407
+ function stop() {
408
+ if (stopping)
409
+ return;
410
+ stopping = true;
411
+ stopRequested = true;
412
+ clearTimers();
413
+ if (!child) {
414
+ deps.clearState();
415
+ deps.exit(0);
416
+ return;
417
+ }
418
+ killChild('SIGTERM');
419
+ // 兜底:子进程迟迟不退(僵尸、收不到信号)也不能让 --stop 挂住。
420
+ setTimeout(() => {
421
+ deps.clearState();
422
+ deps.exit(0);
423
+ }, timing.killGraceMs + 1_000).unref?.();
424
+ }
425
+ /** `td --restart`:不发停止请求,只让子进程重启一次。 */
426
+ function restart() {
427
+ if (stopping || !child)
428
+ return;
429
+ restartRequested = true;
430
+ deps.log('[supervisor] manual restart requested');
431
+ killChild('SIGTERM');
432
+ }
433
+ return {
434
+ phase: () => phase,
435
+ start: () => { startChild(); },
436
+ stop,
437
+ restart,
438
+ };
439
+ }
440
+ /** 供 CLI 复用的 supervisor 入口地址(与 cli.js 同目录)。 */
441
+ export function resolveSupervisorEntry() {
442
+ return fileURLToPath(new URL('./supervisor.js', import.meta.url));
443
+ }
444
+ function parseArgs(argv) {
445
+ const separator = argv.indexOf('--');
446
+ const own = separator >= 0 ? argv.slice(0, separator) : argv;
447
+ const childArgs = separator >= 0 ? argv.slice(separator + 1) : [];
448
+ const value = (flag) => {
449
+ const index = own.indexOf(flag);
450
+ return index >= 0 ? own[index + 1] : undefined;
451
+ };
452
+ const healthUrl = value('--health-url');
453
+ const childEntry = value('--child');
454
+ if (!healthUrl || !childEntry)
455
+ return null;
456
+ return {
457
+ healthUrl,
458
+ healthCaPath: value('--health-ca'),
459
+ version: value('--version'),
460
+ childEntry,
461
+ childArgs,
462
+ };
463
+ }
464
+ /**
465
+ * 以**当前进程**作为 supervisor 启动监督(装全局处理器 + 信号处理 + 开跑)。
466
+ *
467
+ * 两条入口共用:`supervisor.js` 独立入口(`td` 后台模式 spawn 它),
468
+ * 以及 `td --supervise`(cli.ts 进程自己当 supervisor,给 setsid/systemd 用)。
469
+ */
470
+ export function runSupervisor(config, overrides = {}) {
471
+ const forensics = installCrashForensics({ role: 'supervisor', version: config.version ?? null });
472
+ const handle = createSupervisor(config, {
473
+ spawnChild: defaultSpawnChild,
474
+ probe: probeHealthOnce,
475
+ recordIncident: (entry) => writeJsonLogSync('crash.log', entry),
476
+ log: defaultLog,
477
+ exit: (code) => {
478
+ forensics.dispose();
479
+ process.exit(code);
480
+ },
481
+ now: () => Date.now(),
482
+ writeState: (state) => writeJsonStateAtomic(SUPERVISOR_STATE_PATH, state),
483
+ clearState: () => removeJsonState(SUPERVISOR_STATE_PATH),
484
+ notifyLauncher: defaultNotifyLauncher,
485
+ ...overrides,
486
+ });
487
+ const stop = () => { handle.stop(); };
488
+ process.on('SIGTERM', stop);
489
+ process.on('SIGINT', stop);
490
+ process.on('SIGHUP', () => handle.restart());
491
+ // launcher 断了不该影响监督:它已经拿到了 ready/失败,之后 supervisor 自己活着。
492
+ process.on('disconnect', () => { });
493
+ handle.start();
494
+ return handle;
495
+ }
496
+ function main() {
497
+ const parsed = parseArgs(process.argv.slice(2));
498
+ if (!parsed) {
499
+ process.stderr.write('Usage: supervisor --health-url <url> --child <entry> [--health-ca <path>] [--version <v>] -- [child args]\n');
500
+ process.exit(2);
501
+ }
502
+ // realpath 一次,之后每次重启都用这个绝对路径:既绕开 `npm i -g` 删建符号链接
503
+ // 窗口里的 ENOENT,也消除"重启落到了另一个安装"的歧义(PATH 按名字找会)。
504
+ let childEntry;
505
+ try {
506
+ childEntry = fs.realpathSync(parsed.childEntry);
507
+ }
508
+ catch (error) {
509
+ process.stderr.write(`[supervisor] child entry is not readable: ${parsed.childEntry} (${error.message})\n`);
510
+ process.exit(2);
511
+ return;
512
+ }
513
+ runSupervisor({
514
+ childEntry,
515
+ childArgs: parsed.childArgs,
516
+ healthUrl: parsed.healthUrl,
517
+ healthCaPath: parsed.healthCaPath,
518
+ version: parsed.version,
519
+ timing: resolveTimingFromEnv(),
520
+ });
521
+ }
522
+ const isDirectExecution = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
523
+ if (isDirectExecution) {
524
+ main();
525
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * 指数退避。PTY 创建冷却与 supervisor 的崩溃重启共用同一套曲线,
3
+ * 免得两处各写一遍 `min(30_000, 1000 * 2 ** (n - 1))` 再各自跑偏。
4
+ */
5
+ /**
6
+ * 第 `failures` 次失败后应等待多久。failures 从 1 起算
7
+ * (0 或负数按第 1 次处理),因此返回 baseMs 而不是它的一半。
8
+ */
9
+ export function backoffDelayMs(failures, options = {}) {
10
+ const baseMs = options.baseMs ?? 1_000;
11
+ const maxMs = options.maxMs ?? 30_000;
12
+ const cap = options.cap ?? 6;
13
+ const attempt = Math.max(1, Math.min(failures, cap));
14
+ return Math.min(maxMs, baseMs * 2 ** (attempt - 1));
15
+ }