termdock 1.4.284 → 1.4.286

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 (344) hide show
  1. package/.desktop-dist/main.js +10 -553
  2. package/README.md +2 -4
  3. package/dist/client/assets/App-qs0jye8B.js +158 -0
  4. package/dist/client/assets/App-qs0jye8B.js.br +0 -0
  5. package/dist/client/assets/App-qs0jye8B.js.gz +0 -0
  6. package/dist/client/assets/{ChangeWalkthroughPanel-Bvg7jZNw.js → ChangeWalkthroughPanel-X-M6nxKJ.js} +1 -1
  7. package/dist/client/assets/ChangeWalkthroughPanel-X-M6nxKJ.js.br +0 -0
  8. package/dist/client/assets/ChangeWalkthroughPanel-X-M6nxKJ.js.gz +0 -0
  9. package/dist/client/assets/{DagPlayground-DSM5oYq3.js → DagPlayground-DgiUaUkH.js} +1 -1
  10. package/dist/client/assets/DagPlayground-DgiUaUkH.js.br +0 -0
  11. package/dist/client/assets/DagPlayground-DgiUaUkH.js.gz +0 -0
  12. package/dist/client/assets/{DiffLab-CgI11xY5.js → DiffLab-u8njwj0d.js} +1 -1
  13. package/dist/client/assets/DiffLab-u8njwj0d.js.br +0 -0
  14. package/dist/client/assets/DiffLab-u8njwj0d.js.gz +0 -0
  15. package/dist/client/assets/{DiffReview-DOmoMEgC.js → DiffReview-DoQY-klj.js} +1 -1
  16. package/dist/client/assets/DiffReview-DoQY-klj.js.br +0 -0
  17. package/dist/client/assets/DiffReview-DoQY-klj.js.gz +0 -0
  18. package/dist/client/assets/{DiffReviewLab-CEdM0rim.js → DiffReviewLab-DlKbMbfq.js} +1 -1
  19. package/dist/client/assets/DiffReviewLab-DlKbMbfq.js.br +0 -0
  20. package/dist/client/assets/DiffReviewLab-DlKbMbfq.js.gz +0 -0
  21. package/dist/client/assets/DiffViewer-nlvDqGPJ.js +37 -0
  22. package/dist/client/assets/DiffViewer-nlvDqGPJ.js.br +0 -0
  23. package/dist/client/assets/DiffViewer-nlvDqGPJ.js.gz +0 -0
  24. package/dist/client/assets/{ModelPreview-DNiQACGL.js → ModelPreview-C3beTH3N.js} +1 -1
  25. package/dist/client/assets/ModelPreview-C3beTH3N.js.br +0 -0
  26. package/dist/client/assets/ModelPreview-C3beTH3N.js.gz +0 -0
  27. package/dist/client/assets/RightSidebar-BtktcUdh.js +1678 -0
  28. package/dist/client/assets/RightSidebar-BtktcUdh.js.br +0 -0
  29. package/dist/client/assets/RightSidebar-BtktcUdh.js.gz +0 -0
  30. package/dist/client/assets/{abnfDiagram-VCTEODGH-ByazGh0D.js → abnfDiagram-VCTEODGH-7J_hDjG2.js} +1 -1
  31. package/dist/client/assets/abnfDiagram-VCTEODGH-7J_hDjG2.js.br +0 -0
  32. package/dist/client/assets/abnfDiagram-VCTEODGH-7J_hDjG2.js.gz +0 -0
  33. package/dist/client/assets/app-CHKqaWIE.js +35 -0
  34. package/dist/client/assets/app-CHKqaWIE.js.br +0 -0
  35. package/dist/client/assets/app-CHKqaWIE.js.gz +0 -0
  36. package/dist/client/assets/{arc-DMO5fEDY.js → arc-Bg_CuCZm.js} +1 -1
  37. package/dist/client/assets/arc-Bg_CuCZm.js.br +0 -0
  38. package/dist/client/assets/arc-Bg_CuCZm.js.gz +0 -0
  39. package/dist/client/assets/{architectureDiagram-5GKGNRK7-c6WktFDt.js → architectureDiagram-5GKGNRK7-CsSHwySz.js} +1 -1
  40. package/dist/client/assets/architectureDiagram-5GKGNRK7-CsSHwySz.js.br +0 -0
  41. package/dist/client/assets/architectureDiagram-5GKGNRK7-CsSHwySz.js.gz +0 -0
  42. package/dist/client/assets/{blockDiagram-I7D4REHJ-CN0lQ7zu.js → blockDiagram-I7D4REHJ-DePM_hkV.js} +1 -1
  43. package/dist/client/assets/blockDiagram-I7D4REHJ-DePM_hkV.js.br +0 -0
  44. package/dist/client/assets/blockDiagram-I7D4REHJ-DePM_hkV.js.gz +0 -0
  45. package/dist/client/assets/{c4Diagram-7LVT6UL2-CALO2K48.js → c4Diagram-7LVT6UL2-BQ8B9h53.js} +1 -1
  46. package/dist/client/assets/c4Diagram-7LVT6UL2-BQ8B9h53.js.br +0 -0
  47. package/dist/client/assets/c4Diagram-7LVT6UL2-BQ8B9h53.js.gz +0 -0
  48. package/dist/client/assets/channel-8p7Zocq1.js +1 -0
  49. package/dist/client/assets/{chunk-2Q5K7J3B-CcQPyBbp.js → chunk-2Q5K7J3B-Bo9Nc-d4.js} +1 -1
  50. package/dist/client/assets/{chunk-5VM5RSS4-CEp2aHuN.js → chunk-5VM5RSS4-D_qQp7an.js} +1 -1
  51. package/dist/client/assets/{chunk-F27PBJKO-gRP8AiSl.js → chunk-F27PBJKO-CJvGLWuF.js} +1 -1
  52. package/dist/client/assets/chunk-F27PBJKO-CJvGLWuF.js.br +0 -0
  53. package/dist/client/assets/chunk-F27PBJKO-CJvGLWuF.js.gz +0 -0
  54. package/dist/client/assets/{chunk-IMKFNOWR-BZewY7aF.js → chunk-IMKFNOWR-e4fa23mF.js} +1 -1
  55. package/dist/client/assets/chunk-IMKFNOWR-e4fa23mF.js.br +0 -0
  56. package/dist/client/assets/chunk-IMKFNOWR-e4fa23mF.js.gz +0 -0
  57. package/dist/client/assets/{chunk-JWPE2WC7-C4S_lg0B.js → chunk-JWPE2WC7-D9qVRvQs.js} +1 -1
  58. package/dist/client/assets/{chunk-POPQ4Y6H-BgYxXL0V.js → chunk-POPQ4Y6H-7eOizLll.js} +1 -1
  59. package/dist/client/assets/{chunk-SVP7TREG-rjbdtq2C.js → chunk-SVP7TREG-Co1wDBYI.js} +1 -1
  60. package/dist/client/assets/chunk-SVP7TREG-Co1wDBYI.js.br +0 -0
  61. package/dist/client/assets/chunk-SVP7TREG-Co1wDBYI.js.gz +0 -0
  62. package/dist/client/assets/{chunk-TICWLB2K-CxU1tNuL.js → chunk-TICWLB2K-yKjTtLNf.js} +1 -1
  63. package/dist/client/assets/chunk-TICWLB2K-yKjTtLNf.js.br +0 -0
  64. package/dist/client/assets/chunk-TICWLB2K-yKjTtLNf.js.gz +0 -0
  65. package/dist/client/assets/{chunk-XXDRQBXY-CSSO11_4.js → chunk-XXDRQBXY-DDFq5Eoi.js} +1 -1
  66. package/dist/client/assets/classDiagram-ZZMXUADV-DM8VyUpP.js +1 -0
  67. package/dist/client/assets/classDiagram-v2-VYDZK3BY-DM8VyUpP.js +1 -0
  68. package/dist/client/assets/{cose-bilkent-JH36ORCC-BiO-E-0q.js → cose-bilkent-JH36ORCC-Ng2b-szu.js} +1 -1
  69. package/dist/client/assets/cose-bilkent-JH36ORCC-Ng2b-szu.js.br +0 -0
  70. package/dist/client/assets/cose-bilkent-JH36ORCC-Ng2b-szu.js.gz +0 -0
  71. package/dist/client/assets/{cynefin-OW5HDTMX-DUC3zzns.js → cynefin-OW5HDTMX-BUzoxsoE.js} +1 -1
  72. package/dist/client/assets/cynefin-OW5HDTMX-BUzoxsoE.js.br +0 -0
  73. package/dist/client/assets/cynefin-OW5HDTMX-BUzoxsoE.js.gz +0 -0
  74. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CR1RnhbU.js → cynefinDiagram-5FMLGOSQ-9sKimYPk.js} +1 -1
  75. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-9sKimYPk.js.br +0 -0
  76. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-9sKimYPk.js.gz +0 -0
  77. package/dist/client/assets/{dagre-GXQ25YYZ-d1joyB5v.js → dagre-GXQ25YYZ-C6GZ4pYU.js} +1 -1
  78. package/dist/client/assets/dagre-GXQ25YYZ-C6GZ4pYU.js.br +0 -0
  79. package/dist/client/assets/dagre-GXQ25YYZ-C6GZ4pYU.js.gz +0 -0
  80. package/dist/client/assets/{diagram-S7CK7UJ4-izL3GdCu.js → diagram-S7CK7UJ4-DHewWm_2.js} +1 -1
  81. package/dist/client/assets/diagram-S7CK7UJ4-DHewWm_2.js.br +0 -0
  82. package/dist/client/assets/diagram-S7CK7UJ4-DHewWm_2.js.gz +0 -0
  83. package/dist/client/assets/{diagram-UQ7AKVKN-CjoFV2sD.js → diagram-UQ7AKVKN-BU-Qm_iS.js} +1 -1
  84. package/dist/client/assets/diagram-UQ7AKVKN-BU-Qm_iS.js.br +0 -0
  85. package/dist/client/assets/diagram-UQ7AKVKN-BU-Qm_iS.js.gz +0 -0
  86. package/dist/client/assets/{diagram-VSXAHHWV-BgHmsnTZ.js → diagram-VSXAHHWV-DEvxj92u.js} +1 -1
  87. package/dist/client/assets/diagram-VSXAHHWV-DEvxj92u.js.br +0 -0
  88. package/dist/client/assets/diagram-VSXAHHWV-DEvxj92u.js.gz +0 -0
  89. package/dist/client/assets/{diagram-VX7I27RA-DMzRLNbg.js → diagram-VX7I27RA-DwwnVInb.js} +1 -1
  90. package/dist/client/assets/diagram-VX7I27RA-DwwnVInb.js.br +0 -0
  91. package/dist/client/assets/diagram-VX7I27RA-DwwnVInb.js.gz +0 -0
  92. package/dist/client/assets/{diagram-Z3DM3KII-kgWacykR.js → diagram-Z3DM3KII-DDhqqeca.js} +1 -1
  93. package/dist/client/assets/diagram-Z3DM3KII-DDhqqeca.js.br +0 -0
  94. package/dist/client/assets/diagram-Z3DM3KII-DDhqqeca.js.gz +0 -0
  95. package/dist/client/assets/{ebnfDiagram-PWID7BFC-XzSXtFGS.js → ebnfDiagram-PWID7BFC-CQz2BRgT.js} +1 -1
  96. package/dist/client/assets/ebnfDiagram-PWID7BFC-CQz2BRgT.js.br +0 -0
  97. package/dist/client/assets/ebnfDiagram-PWID7BFC-CQz2BRgT.js.gz +0 -0
  98. package/dist/client/assets/{erDiagram-RLTQ6QDP-Bu67_rUZ.js → erDiagram-RLTQ6QDP-D2eLjKEA.js} +1 -1
  99. package/dist/client/assets/erDiagram-RLTQ6QDP-D2eLjKEA.js.br +0 -0
  100. package/dist/client/assets/erDiagram-RLTQ6QDP-D2eLjKEA.js.gz +0 -0
  101. package/dist/client/assets/{flowDiagram-HODETNUW-BsK_b_G_.js → flowDiagram-HODETNUW-B7oJq5P3.js} +1 -1
  102. package/dist/client/assets/flowDiagram-HODETNUW-B7oJq5P3.js.br +0 -0
  103. package/dist/client/assets/flowDiagram-HODETNUW-B7oJq5P3.js.gz +0 -0
  104. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-gCSBItav.js → ganttDiagram-EL5Y4UJY-BUA_LsMw.js} +1 -1
  105. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BUA_LsMw.js.br +0 -0
  106. package/dist/client/assets/ganttDiagram-EL5Y4UJY-BUA_LsMw.js.gz +0 -0
  107. package/dist/client/assets/{gestureArbiter-BOikQWZ8.js → gestureArbiter-8VtxF9dg.js} +1 -1
  108. package/dist/client/assets/gestureArbiter-8VtxF9dg.js.br +0 -0
  109. package/dist/client/assets/gestureArbiter-8VtxF9dg.js.gz +0 -0
  110. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-DL0NSPPn.js → gitGraphDiagram-WWUBYQGX-BlbsPdki.js} +1 -1
  111. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BlbsPdki.js.br +0 -0
  112. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-BlbsPdki.js.gz +0 -0
  113. package/dist/client/assets/{index-Bv-r5mB7.css → index-DUCUcxTy.css} +1 -1
  114. package/dist/client/assets/index-DUCUcxTy.css.br +0 -0
  115. package/dist/client/assets/index-DUCUcxTy.css.gz +0 -0
  116. package/dist/client/assets/{infoDiagram-27XIBGKW-Cwevdubr.js → infoDiagram-27XIBGKW-DdYvRIDS.js} +1 -1
  117. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-pz1lzDd4.js → ishikawaDiagram-5VMMS53U-CZbBbDjz.js} +1 -1
  118. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CZbBbDjz.js.br +0 -0
  119. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CZbBbDjz.js.gz +0 -0
  120. package/dist/client/assets/{journeyDiagram-3NMN7TZE-BrUnjTGd.js → journeyDiagram-3NMN7TZE-DtxlqtaG.js} +1 -1
  121. package/dist/client/assets/journeyDiagram-3NMN7TZE-DtxlqtaG.js.br +0 -0
  122. package/dist/client/assets/journeyDiagram-3NMN7TZE-DtxlqtaG.js.gz +0 -0
  123. package/dist/client/assets/{kanban-definition-UXKFOSKX-BKf4UYyB.js → kanban-definition-UXKFOSKX-UMDV9UCG.js} +1 -1
  124. package/dist/client/assets/kanban-definition-UXKFOSKX-UMDV9UCG.js.br +0 -0
  125. package/dist/client/assets/kanban-definition-UXKFOSKX-UMDV9UCG.js.gz +0 -0
  126. package/dist/client/assets/{layout-Dd_yzBVh.js → layout-CAAXci79.js} +1 -1
  127. package/dist/client/assets/layout-CAAXci79.js.br +0 -0
  128. package/dist/client/assets/layout-CAAXci79.js.gz +0 -0
  129. package/dist/client/assets/{linear-iY7daKXS.js → linear-BGBP_FE_.js} +1 -1
  130. package/dist/client/assets/linear-BGBP_FE_.js.br +0 -0
  131. package/dist/client/assets/linear-BGBP_FE_.js.gz +0 -0
  132. package/dist/client/assets/{mermaid.core-CYNb258G.js → mermaid.core-BH_WDKpZ.js} +5 -5
  133. package/dist/client/assets/mermaid.core-BH_WDKpZ.js.br +0 -0
  134. package/dist/client/assets/mermaid.core-BH_WDKpZ.js.gz +0 -0
  135. package/dist/client/assets/{mindmap-definition-YA3MSWOX-ctnc7JBK.js → mindmap-definition-YA3MSWOX-7yyx6kg5.js} +1 -1
  136. package/dist/client/assets/mindmap-definition-YA3MSWOX-7yyx6kg5.js.br +0 -0
  137. package/dist/client/assets/mindmap-definition-YA3MSWOX-7yyx6kg5.js.gz +0 -0
  138. package/dist/client/assets/{passwordBootstrap-Bth6QB9C.js → passwordBootstrap-Dt3dN800.js} +1 -1
  139. package/dist/client/assets/passwordBootstrap-Dt3dN800.js.br +0 -0
  140. package/dist/client/assets/passwordBootstrap-Dt3dN800.js.gz +0 -0
  141. package/dist/client/assets/{pegDiagram-XKGWAZYB-DVKAZXxP.js → pegDiagram-XKGWAZYB-D254I9Yl.js} +1 -1
  142. package/dist/client/assets/pegDiagram-XKGWAZYB-D254I9Yl.js.br +0 -0
  143. package/dist/client/assets/pegDiagram-XKGWAZYB-D254I9Yl.js.gz +0 -0
  144. package/dist/client/assets/{pieDiagram-E7YTZNPT-C0u6lfoS.js → pieDiagram-E7YTZNPT-DTy-4KXi.js} +1 -1
  145. package/dist/client/assets/pieDiagram-E7YTZNPT-DTy-4KXi.js.br +0 -0
  146. package/dist/client/assets/pieDiagram-E7YTZNPT-DTy-4KXi.js.gz +0 -0
  147. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-DXFDUoYJ.js → quadrantDiagram-AXDQQJYC-BMsaHxl0.js} +1 -1
  148. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-DXFDUoYJ.js.br → quadrantDiagram-AXDQQJYC-BMsaHxl0.js.br} +0 -0
  149. package/dist/client/assets/quadrantDiagram-AXDQQJYC-BMsaHxl0.js.gz +0 -0
  150. package/dist/client/assets/{railroadDiagram-O6MQD6OU-Cz-wrQ4E.js → railroadDiagram-O6MQD6OU-D4c39AAQ.js} +1 -1
  151. package/dist/client/assets/railroadDiagram-O6MQD6OU-D4c39AAQ.js.br +0 -0
  152. package/dist/client/assets/railroadDiagram-O6MQD6OU-D4c39AAQ.js.gz +0 -0
  153. package/dist/client/assets/{referenceLongPress-BMcpaynk.js → referenceLongPress-TTIbwCKV.js} +1 -1
  154. package/dist/client/assets/referenceLongPress-TTIbwCKV.js.br +0 -0
  155. package/dist/client/assets/referenceLongPress-TTIbwCKV.js.gz +0 -0
  156. package/dist/client/assets/{requirementDiagram-BXWQKSXE-DdFhwbqd.js → requirementDiagram-BXWQKSXE-CwJjBKnR.js} +1 -1
  157. package/dist/client/assets/requirementDiagram-BXWQKSXE-CwJjBKnR.js.br +0 -0
  158. package/dist/client/assets/requirementDiagram-BXWQKSXE-CwJjBKnR.js.gz +0 -0
  159. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-DIEO7iin.js → sankeyDiagram-P5KCCOFB-DVlRBDMK.js} +1 -1
  160. package/dist/client/assets/sankeyDiagram-P5KCCOFB-DVlRBDMK.js.br +0 -0
  161. package/dist/client/assets/sankeyDiagram-P5KCCOFB-DVlRBDMK.js.gz +0 -0
  162. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-0Jq8ZpjE.js → sequenceDiagram-WJ2MYXX4-BsWqKeoD.js} +1 -1
  163. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BsWqKeoD.js.br +0 -0
  164. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BsWqKeoD.js.gz +0 -0
  165. package/dist/client/assets/{sizeCapture-INFHLROL-CzkB3_L-.js → sizeCapture-INFHLROL-1FEOsRFT.js} +1 -1
  166. package/dist/client/assets/sizeCapture-INFHLROL-1FEOsRFT.js.br +0 -0
  167. package/dist/client/assets/sizeCapture-INFHLROL-1FEOsRFT.js.gz +0 -0
  168. package/dist/client/assets/{sparkles-OssC7CZI.js → sparkles-hN4VSTDx.js} +1 -1
  169. package/dist/client/assets/{stateDiagram-D77RDMKH-B1UZvmmJ.js → stateDiagram-D77RDMKH-DWNE48Sc.js} +1 -1
  170. package/dist/client/assets/stateDiagram-D77RDMKH-DWNE48Sc.js.br +0 -0
  171. package/dist/client/assets/stateDiagram-D77RDMKH-DWNE48Sc.js.gz +0 -0
  172. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-BMprFuVB.js +1 -0
  173. package/dist/client/assets/{swimlanes-42K2YHIH-CwBFrD7Q.js → swimlanes-42K2YHIH-ghhxa8QM.js} +1 -1
  174. package/dist/client/assets/swimlanes-42K2YHIH-ghhxa8QM.js.br +0 -0
  175. package/dist/client/assets/swimlanes-42K2YHIH-ghhxa8QM.js.gz +0 -0
  176. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-Dg4tYXGL.js +8 -0
  177. package/dist/client/assets/{timeline-definition-24CTP7MA-Cgrwm9y7.js → timeline-definition-24CTP7MA-B0eZfCze.js} +1 -1
  178. package/dist/client/assets/timeline-definition-24CTP7MA-B0eZfCze.js.br +0 -0
  179. package/dist/client/assets/timeline-definition-24CTP7MA-B0eZfCze.js.gz +0 -0
  180. package/dist/client/assets/{triangle-alert-gfowObfL.js → triangle-alert-zb36qQ-j.js} +1 -1
  181. package/dist/client/assets/{vennDiagram-4TSXK5OY-JVQ9D_hy.js → vennDiagram-4TSXK5OY-DCdV0A4l.js} +1 -1
  182. package/dist/client/assets/vennDiagram-4TSXK5OY-DCdV0A4l.js.br +0 -0
  183. package/dist/client/assets/vennDiagram-4TSXK5OY-DCdV0A4l.js.gz +0 -0
  184. package/dist/client/assets/{videoCompression-D1SrM0Eo.js → videoCompression-BzT-9eWg.js} +1 -1
  185. package/dist/client/assets/videoCompression-BzT-9eWg.js.br +0 -0
  186. package/dist/client/assets/videoCompression-BzT-9eWg.js.gz +0 -0
  187. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-BsmpGt9d.js → wardleyDiagram-VM6X3IG4-BQvk-8b6.js} +1 -1
  188. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BQvk-8b6.js.br +0 -0
  189. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BQvk-8b6.js.gz +0 -0
  190. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-C33-nORD.js → xychartDiagram-S5SC5T6Z-D1VdK6g5.js} +1 -1
  191. package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1VdK6g5.js.br +0 -0
  192. package/dist/client/assets/xychartDiagram-S5SC5T6Z-D1VdK6g5.js.gz +0 -0
  193. package/dist/client/index.html +2 -2
  194. package/dist/client/index.html.br +0 -0
  195. package/dist/client/index.html.gz +0 -0
  196. package/dist/client/sw.js +1 -1
  197. package/dist/client/sw.js.br +0 -0
  198. package/dist/client/sw.js.gz +0 -0
  199. package/dist/server/agent/collaborationCli.js +11 -3
  200. package/dist/server/agent/collaborationGroupRoutes.js +18 -0
  201. package/dist/server/agent/collaborationPeerTransport.js +19 -1
  202. package/dist/server/agent/collaborationRoutes.js +12 -0
  203. package/dist/server/agent/collaborationService.js +83 -8
  204. package/dist/server/agent/nativeSessionSearch.js +84 -0
  205. package/dist/server/agent/sessionSearchStore.js +2 -1
  206. package/dist/server/entry.js +2 -6
  207. package/dist/server/federation/trustedCa.js +9 -2
  208. package/dist/server/routes/filesystem.js +43 -1
  209. package/dist/server/routes/terminal.js +122 -32
  210. package/dist/server/utils/enableSupervision.js +2 -3
  211. package/dist/server/utils/runtimeClient.js +0 -32
  212. package/dist/server/utils/settings.js +14 -0
  213. package/dist/server/workers/nativeSessionSearchWorker.js +416 -0
  214. package/package.json +3 -3
  215. package/runtime-manifest.json +3 -3
  216. package/scripts/classify-release.mjs +0 -1
  217. package/dist/client/assets/App-BgEYOR8Z.js +0 -157
  218. package/dist/client/assets/App-BgEYOR8Z.js.br +0 -0
  219. package/dist/client/assets/App-BgEYOR8Z.js.gz +0 -0
  220. package/dist/client/assets/ChangeWalkthroughPanel-Bvg7jZNw.js.br +0 -0
  221. package/dist/client/assets/ChangeWalkthroughPanel-Bvg7jZNw.js.gz +0 -0
  222. package/dist/client/assets/DagPlayground-DSM5oYq3.js.br +0 -0
  223. package/dist/client/assets/DagPlayground-DSM5oYq3.js.gz +0 -0
  224. package/dist/client/assets/DiffLab-CgI11xY5.js.br +0 -0
  225. package/dist/client/assets/DiffLab-CgI11xY5.js.gz +0 -0
  226. package/dist/client/assets/DiffReview-DOmoMEgC.js.br +0 -0
  227. package/dist/client/assets/DiffReview-DOmoMEgC.js.gz +0 -0
  228. package/dist/client/assets/DiffReviewLab-CEdM0rim.js.br +0 -0
  229. package/dist/client/assets/DiffReviewLab-CEdM0rim.js.gz +0 -0
  230. package/dist/client/assets/DiffViewer-CI3JmNyq.js +0 -37
  231. package/dist/client/assets/DiffViewer-CI3JmNyq.js.br +0 -0
  232. package/dist/client/assets/DiffViewer-CI3JmNyq.js.gz +0 -0
  233. package/dist/client/assets/ModelPreview-DNiQACGL.js.br +0 -0
  234. package/dist/client/assets/ModelPreview-DNiQACGL.js.gz +0 -0
  235. package/dist/client/assets/RightSidebar-CpPgHZEU.js +0 -1678
  236. package/dist/client/assets/RightSidebar-CpPgHZEU.js.br +0 -0
  237. package/dist/client/assets/RightSidebar-CpPgHZEU.js.gz +0 -0
  238. package/dist/client/assets/abnfDiagram-VCTEODGH-ByazGh0D.js.br +0 -0
  239. package/dist/client/assets/abnfDiagram-VCTEODGH-ByazGh0D.js.gz +0 -0
  240. package/dist/client/assets/app-UpvbJ3fT.js +0 -35
  241. package/dist/client/assets/app-UpvbJ3fT.js.br +0 -0
  242. package/dist/client/assets/app-UpvbJ3fT.js.gz +0 -0
  243. package/dist/client/assets/arc-DMO5fEDY.js.br +0 -0
  244. package/dist/client/assets/arc-DMO5fEDY.js.gz +0 -0
  245. package/dist/client/assets/architectureDiagram-5GKGNRK7-c6WktFDt.js.br +0 -0
  246. package/dist/client/assets/architectureDiagram-5GKGNRK7-c6WktFDt.js.gz +0 -0
  247. package/dist/client/assets/blockDiagram-I7D4REHJ-CN0lQ7zu.js.br +0 -0
  248. package/dist/client/assets/blockDiagram-I7D4REHJ-CN0lQ7zu.js.gz +0 -0
  249. package/dist/client/assets/c4Diagram-7LVT6UL2-CALO2K48.js.br +0 -0
  250. package/dist/client/assets/c4Diagram-7LVT6UL2-CALO2K48.js.gz +0 -0
  251. package/dist/client/assets/channel-DcmoMLL-.js +0 -1
  252. package/dist/client/assets/chunk-F27PBJKO-gRP8AiSl.js.br +0 -0
  253. package/dist/client/assets/chunk-F27PBJKO-gRP8AiSl.js.gz +0 -0
  254. package/dist/client/assets/chunk-IMKFNOWR-BZewY7aF.js.br +0 -0
  255. package/dist/client/assets/chunk-IMKFNOWR-BZewY7aF.js.gz +0 -0
  256. package/dist/client/assets/chunk-SVP7TREG-rjbdtq2C.js.br +0 -0
  257. package/dist/client/assets/chunk-SVP7TREG-rjbdtq2C.js.gz +0 -0
  258. package/dist/client/assets/chunk-TICWLB2K-CxU1tNuL.js.br +0 -0
  259. package/dist/client/assets/chunk-TICWLB2K-CxU1tNuL.js.gz +0 -0
  260. package/dist/client/assets/classDiagram-ZZMXUADV-VSirHVcP.js +0 -1
  261. package/dist/client/assets/classDiagram-v2-VYDZK3BY-VSirHVcP.js +0 -1
  262. package/dist/client/assets/cose-bilkent-JH36ORCC-BiO-E-0q.js.br +0 -0
  263. package/dist/client/assets/cose-bilkent-JH36ORCC-BiO-E-0q.js.gz +0 -0
  264. package/dist/client/assets/cynefin-OW5HDTMX-DUC3zzns.js.br +0 -0
  265. package/dist/client/assets/cynefin-OW5HDTMX-DUC3zzns.js.gz +0 -0
  266. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CR1RnhbU.js.br +0 -0
  267. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CR1RnhbU.js.gz +0 -0
  268. package/dist/client/assets/dagre-GXQ25YYZ-d1joyB5v.js.br +0 -0
  269. package/dist/client/assets/dagre-GXQ25YYZ-d1joyB5v.js.gz +0 -0
  270. package/dist/client/assets/diagram-S7CK7UJ4-izL3GdCu.js.br +0 -0
  271. package/dist/client/assets/diagram-S7CK7UJ4-izL3GdCu.js.gz +0 -0
  272. package/dist/client/assets/diagram-UQ7AKVKN-CjoFV2sD.js.br +0 -0
  273. package/dist/client/assets/diagram-UQ7AKVKN-CjoFV2sD.js.gz +0 -0
  274. package/dist/client/assets/diagram-VSXAHHWV-BgHmsnTZ.js.br +0 -0
  275. package/dist/client/assets/diagram-VSXAHHWV-BgHmsnTZ.js.gz +0 -0
  276. package/dist/client/assets/diagram-VX7I27RA-DMzRLNbg.js.br +0 -0
  277. package/dist/client/assets/diagram-VX7I27RA-DMzRLNbg.js.gz +0 -0
  278. package/dist/client/assets/diagram-Z3DM3KII-kgWacykR.js.br +0 -0
  279. package/dist/client/assets/diagram-Z3DM3KII-kgWacykR.js.gz +0 -0
  280. package/dist/client/assets/ebnfDiagram-PWID7BFC-XzSXtFGS.js.br +0 -0
  281. package/dist/client/assets/ebnfDiagram-PWID7BFC-XzSXtFGS.js.gz +0 -0
  282. package/dist/client/assets/erDiagram-RLTQ6QDP-Bu67_rUZ.js.br +0 -0
  283. package/dist/client/assets/erDiagram-RLTQ6QDP-Bu67_rUZ.js.gz +0 -0
  284. package/dist/client/assets/flowDiagram-HODETNUW-BsK_b_G_.js.br +0 -0
  285. package/dist/client/assets/flowDiagram-HODETNUW-BsK_b_G_.js.gz +0 -0
  286. package/dist/client/assets/ganttDiagram-EL5Y4UJY-gCSBItav.js.br +0 -0
  287. package/dist/client/assets/ganttDiagram-EL5Y4UJY-gCSBItav.js.gz +0 -0
  288. package/dist/client/assets/gestureArbiter-BOikQWZ8.js.br +0 -0
  289. package/dist/client/assets/gestureArbiter-BOikQWZ8.js.gz +0 -0
  290. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DL0NSPPn.js.br +0 -0
  291. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-DL0NSPPn.js.gz +0 -0
  292. package/dist/client/assets/index-Bv-r5mB7.css.br +0 -0
  293. package/dist/client/assets/index-Bv-r5mB7.css.gz +0 -0
  294. package/dist/client/assets/ishikawaDiagram-5VMMS53U-pz1lzDd4.js.br +0 -0
  295. package/dist/client/assets/ishikawaDiagram-5VMMS53U-pz1lzDd4.js.gz +0 -0
  296. package/dist/client/assets/journeyDiagram-3NMN7TZE-BrUnjTGd.js.br +0 -0
  297. package/dist/client/assets/journeyDiagram-3NMN7TZE-BrUnjTGd.js.gz +0 -0
  298. package/dist/client/assets/kanban-definition-UXKFOSKX-BKf4UYyB.js.br +0 -0
  299. package/dist/client/assets/kanban-definition-UXKFOSKX-BKf4UYyB.js.gz +0 -0
  300. package/dist/client/assets/layout-Dd_yzBVh.js.br +0 -0
  301. package/dist/client/assets/layout-Dd_yzBVh.js.gz +0 -0
  302. package/dist/client/assets/linear-iY7daKXS.js.br +0 -0
  303. package/dist/client/assets/linear-iY7daKXS.js.gz +0 -0
  304. package/dist/client/assets/mermaid.core-CYNb258G.js.br +0 -0
  305. package/dist/client/assets/mermaid.core-CYNb258G.js.gz +0 -0
  306. package/dist/client/assets/mindmap-definition-YA3MSWOX-ctnc7JBK.js.br +0 -0
  307. package/dist/client/assets/mindmap-definition-YA3MSWOX-ctnc7JBK.js.gz +0 -0
  308. package/dist/client/assets/passwordBootstrap-Bth6QB9C.js.br +0 -0
  309. package/dist/client/assets/passwordBootstrap-Bth6QB9C.js.gz +0 -0
  310. package/dist/client/assets/pegDiagram-XKGWAZYB-DVKAZXxP.js.br +0 -0
  311. package/dist/client/assets/pegDiagram-XKGWAZYB-DVKAZXxP.js.gz +0 -0
  312. package/dist/client/assets/pieDiagram-E7YTZNPT-C0u6lfoS.js.br +0 -0
  313. package/dist/client/assets/pieDiagram-E7YTZNPT-C0u6lfoS.js.gz +0 -0
  314. package/dist/client/assets/quadrantDiagram-AXDQQJYC-DXFDUoYJ.js.gz +0 -0
  315. package/dist/client/assets/railroadDiagram-O6MQD6OU-Cz-wrQ4E.js.br +0 -0
  316. package/dist/client/assets/railroadDiagram-O6MQD6OU-Cz-wrQ4E.js.gz +0 -0
  317. package/dist/client/assets/referenceLongPress-BMcpaynk.js.br +0 -0
  318. package/dist/client/assets/referenceLongPress-BMcpaynk.js.gz +0 -0
  319. package/dist/client/assets/requirementDiagram-BXWQKSXE-DdFhwbqd.js.br +0 -0
  320. package/dist/client/assets/requirementDiagram-BXWQKSXE-DdFhwbqd.js.gz +0 -0
  321. package/dist/client/assets/sankeyDiagram-P5KCCOFB-DIEO7iin.js.br +0 -0
  322. package/dist/client/assets/sankeyDiagram-P5KCCOFB-DIEO7iin.js.gz +0 -0
  323. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-0Jq8ZpjE.js.br +0 -0
  324. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-0Jq8ZpjE.js.gz +0 -0
  325. package/dist/client/assets/sizeCapture-INFHLROL-CzkB3_L-.js.br +0 -0
  326. package/dist/client/assets/sizeCapture-INFHLROL-CzkB3_L-.js.gz +0 -0
  327. package/dist/client/assets/stateDiagram-D77RDMKH-B1UZvmmJ.js.br +0 -0
  328. package/dist/client/assets/stateDiagram-D77RDMKH-B1UZvmmJ.js.gz +0 -0
  329. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-Cmg877fp.js +0 -1
  330. package/dist/client/assets/swimlanes-42K2YHIH-CwBFrD7Q.js.br +0 -0
  331. package/dist/client/assets/swimlanes-42K2YHIH-CwBFrD7Q.js.gz +0 -0
  332. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-CjR47hdK.js +0 -8
  333. package/dist/client/assets/timeline-definition-24CTP7MA-Cgrwm9y7.js.br +0 -0
  334. package/dist/client/assets/timeline-definition-24CTP7MA-Cgrwm9y7.js.gz +0 -0
  335. package/dist/client/assets/vennDiagram-4TSXK5OY-JVQ9D_hy.js.br +0 -0
  336. package/dist/client/assets/vennDiagram-4TSXK5OY-JVQ9D_hy.js.gz +0 -0
  337. package/dist/client/assets/videoCompression-D1SrM0Eo.js.br +0 -0
  338. package/dist/client/assets/videoCompression-D1SrM0Eo.js.gz +0 -0
  339. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BsmpGt9d.js.br +0 -0
  340. package/dist/client/assets/wardleyDiagram-VM6X3IG4-BsmpGt9d.js.gz +0 -0
  341. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C33-nORD.js.br +0 -0
  342. package/dist/client/assets/xychartDiagram-S5SC5T6Z-C33-nORD.js.gz +0 -0
  343. package/dist/server/utils/desktopRuntimeOwner.js +0 -105
  344. package/scripts/prepare-desktop-runtime.mjs +0 -228
@@ -33,6 +33,15 @@ export class CollaborationService {
33
33
  throw new Error('INVALID_DIRECTORY');
34
34
  for (const peer of data.peers)
35
35
  validateCollaborationNode(peer);
36
+ if (data.removedPeers !== undefined) {
37
+ if (!Array.isArray(data.removedPeers) || data.removedPeers.length > 768)
38
+ throw new Error('INVALID_DIRECTORY');
39
+ for (const peer of data.removedPeers) {
40
+ validateCollaborationNode(peer);
41
+ if (peer.scope && !/^12D3KooW[a-zA-Z0-9]{30,60}$/.test(peer.scope.device))
42
+ throw new Error('INVALID_DIRECTORY');
43
+ }
44
+ }
36
45
  if (data.scoped !== undefined) {
37
46
  if (!data.scoped || typeof data.scoped !== 'object' || Array.isArray(data.scoped))
38
47
  throw new Error('INVALID_DIRECTORY');
@@ -61,10 +70,12 @@ export class CollaborationService {
61
70
  throw new Error('COLLABORATION_ORIGIN_REQUIRED'); const node = { ...this.options.node(), origin }; validateCollaborationNode(node); return node; }
62
71
  setOrigin(origin) { this.node(origin); if (this.document.origin && this.document.origin !== origin)
63
72
  throw new Error('COLLABORATION_ORIGIN_MISMATCH'); this.document.origin = origin; }
64
- remember(node) {
73
+ remember(node, explicitPair = false) {
65
74
  validateCollaborationNode(node);
66
75
  if (node.serviceId === this.options.node().serviceId || node.origin === this.document.origin)
67
76
  throw new Error('CANNOT_PAIR_SELF');
77
+ if (this.document.removedPeers?.some(peer => peer.serviceId === node.serviceId) && !explicitPair)
78
+ throw new Error('COLLABORATION_PEER_REMOVED');
68
79
  const old = this.document.peers.find(peer => peer.serviceId === node.serviceId || peer.origin === node.origin);
69
80
  if (old && (old.serviceId !== node.serviceId || old.origin !== node.origin || old.caFingerprint256 !== node.caFingerprint256))
70
81
  throw new Error('PEER_IDENTITY_CHANGED');
@@ -73,6 +84,8 @@ export class CollaborationService {
73
84
  throw new Error('PEER_LIMIT');
74
85
  this.document.peers.push(node);
75
86
  }
87
+ if (explicitPair)
88
+ this.document.removedPeers = this.document.removedPeers?.filter(peer => peer.serviceId !== node.serviceId);
76
89
  this.persist();
77
90
  }
78
91
  descriptor() { return { ...this.options.node(), ...(this.document.origin ? { origin: this.document.origin } : {}) }; }
@@ -96,14 +109,72 @@ export class CollaborationService {
96
109
  if (old && (old.serviceId !== node.serviceId || old.origin !== node.origin || old.caFingerprint256 !== node.caFingerprint256))
97
110
  throw new Error('PEER_IDENTITY_CHANGED');
98
111
  }
99
- if (new Set([...this.document.peers.map(peer => peer.serviceId), ...nodes.filter(node => node.serviceId !== self.serviceId).map(node => node.serviceId)]).size > 63)
112
+ const removed = new Set(this.document.removedPeers?.map(peer => peer.serviceId));
113
+ const eligible = nodes.filter(node => node.serviceId !== self.serviceId && !removed.has(node.serviceId));
114
+ if (new Set([...this.document.peers.map(peer => peer.serviceId), ...eligible.map(node => node.serviceId)]).size > 63)
100
115
  throw new Error('PEER_LIMIT');
101
116
  this.setOrigin(origin);
102
- this.document.peers = [...new Map([...this.document.peers, ...nodes.filter(node => node.serviceId !== self.serviceId)].map(node => [node.serviceId, node])).values()];
117
+ this.document.peers = [...new Map([...this.document.peers, ...eligible].map(node => [node.serviceId, node])).values()];
103
118
  this.persist();
104
119
  void this.refresh();
105
120
  return { ok: true, registered: this.document.peers.map(peer => peer.serviceId) };
106
121
  }
122
+ groupsUsing(peer) {
123
+ return this.options.store.list().filter(group => group.sessionIds.some(id => address(id)?.origin === peer.origin)
124
+ || group.federated && this.document.replicas?.[group.id]?.includes(peer.origin)).map(group => ({ id: group.id, name: group.name }));
125
+ }
126
+ /** Explicit removal is durable: other open clients may still enroll their
127
+ * stale saved catalogs, but only a deliberate restore or pairing re-adds it. */
128
+ removePeer(serviceId) {
129
+ const peer = this.document.peers.find(item => item.serviceId === serviceId);
130
+ if (!peer)
131
+ throw new CollaborationError('PEER_NOT_FOUND', '该协作服务已移除,请刷新列表', 404);
132
+ const groups = this.groupsUsing(peer);
133
+ if (groups.length)
134
+ throw new CollaborationError('PEER_IN_USE', `该服务仍用于协作组「${groups.map(group => group.name).join('」「')}」,请先移出对应成员或删除协作组`, 409, { groups });
135
+ this.options.transport.forgetUnusedPeer(serviceId);
136
+ const previous = this.document;
137
+ const scope = previous.scoped?.[serviceId];
138
+ this.document = { ...previous, peers: previous.peers.filter(item => item.serviceId !== serviceId),
139
+ scoped: Object.fromEntries(Object.entries(previous.scoped ?? {}).filter(([id]) => id !== serviceId)),
140
+ removedPeers: [...(previous.removedPeers ?? []).filter(item => item.serviceId !== serviceId), { ...peer, ...(scope ? { scope } : {}) }].slice(-768) };
141
+ try {
142
+ this.persist();
143
+ }
144
+ catch (error) {
145
+ this.document = previous;
146
+ throw error;
147
+ }
148
+ const client = this.clients.get(serviceId);
149
+ this.clients.delete(serviceId);
150
+ this.observations.delete(serviceId);
151
+ if (client)
152
+ void client.then(rpc => rpc.close()).catch(() => { });
153
+ return { ok: true, origin: peer.origin };
154
+ }
155
+ restorePeer(serviceId) {
156
+ const removed = this.document.removedPeers?.find(item => item.serviceId === serviceId);
157
+ if (!removed)
158
+ throw new CollaborationError('PEER_NOT_FOUND', '找不到这条已移除的协作连接', 404);
159
+ const { scope, ...node } = removed;
160
+ validateCollaborationNode(node);
161
+ if (this.document.peers.some(peer => peer.serviceId === node.serviceId || peer.origin === node.origin))
162
+ throw new CollaborationError('PEER_IDENTITY_CHANGED', '该地址已属于另一条服务连接,请重新配对', 409);
163
+ if (this.document.peers.length >= 63)
164
+ throw new CollaborationError('PEER_LIMIT', '协作服务数量已达上限', 400);
165
+ const previous = this.document;
166
+ this.document = { ...previous, peers: [...previous.peers, node], removedPeers: previous.removedPeers?.filter(item => item.serviceId !== serviceId),
167
+ scoped: { ...previous.scoped, ...(scope ? { [serviceId]: scope } : {}) } };
168
+ try {
169
+ this.persist();
170
+ }
171
+ catch (error) {
172
+ this.document = previous;
173
+ throw error;
174
+ }
175
+ void this.refresh();
176
+ return { ok: true, origin: node.origin };
177
+ }
107
178
  /** Effective local sessions a peer may touch. `undefined` means a fully
108
179
  * authorized directory peer; an array (possibly empty) means a peer that
109
180
  * exists only because a device shared a write invitation for those sessions. */
@@ -157,7 +228,7 @@ export class CollaborationService {
157
228
  const rpc = await (this.options.pairConnect ?? this.options.connect)(invitation.node);
158
229
  try {
159
230
  await rpc.request({ type: 'collaboration-service', action: 'pair', code: invitation.code, node: this.node() });
160
- this.remember(invitation.node);
231
+ this.remember(invitation.node, true);
161
232
  void this.refresh();
162
233
  return { ok: true, peer: invitation.node };
163
234
  }
@@ -178,7 +249,7 @@ export class CollaborationService {
178
249
  if (offer.acceptedBy && !this.document.peers.some(peer => peer.serviceId === subject))
179
250
  throw new Error('PAIRING_REVOKED');
180
251
  offer.acceptedBy = subject;
181
- this.remember(node);
252
+ this.remember(node, true);
182
253
  void this.refresh();
183
254
  return { ok: true };
184
255
  }
@@ -298,6 +369,8 @@ export class CollaborationService {
298
369
  try {
299
370
  const client = await this.client(peer);
300
371
  const data = await client.request({ type: 'collaboration-service', action: 'directory' });
372
+ if (!this.document.peers.some(item => item.serviceId === peer.serviceId))
373
+ return;
301
374
  if (!Array.isArray(data.sessions) || data.sessions.length > 5000 || !Array.isArray(data.groups))
302
375
  throw new Error('INVALID_DIRECTORY');
303
376
  const sessions = data.sessions.filter((s) => s && typeof s.sessionId === 'string' && !s.sessionId.startsWith('remote:'));
@@ -318,7 +391,8 @@ export class CollaborationService {
318
391
  }
319
392
  }
320
393
  catch (error) {
321
- this.observations.set(peer.serviceId, { sessions: this.observations.get(peer.serviceId)?.sessions ?? [], checkedAt: Date.now(), error: error instanceof Error ? error.message.slice(0, 240) : 'PEER_UNAVAILABLE' });
394
+ if (this.document.peers.some(item => item.serviceId === peer.serviceId))
395
+ this.observations.set(peer.serviceId, { sessions: this.observations.get(peer.serviceId)?.sessions ?? [], checkedAt: Date.now(), error: error instanceof Error ? error.message.slice(0, 240) : 'PEER_UNAVAILABLE' });
322
396
  }
323
397
  })).then(() => { }).finally(() => { this.inFlight = undefined; });
324
398
  }
@@ -328,12 +402,13 @@ export class CollaborationService {
328
402
  const services = this.document.peers.map(peer => {
329
403
  const observed = this.observations.get(peer.serviceId);
330
404
  return { origin: peer.origin, serviceId: peer.serviceId, label: peer.origin,
331
- connected: !!observed && !observed.error && Date.now() - observed.checkedAt < 15_000, error: observed?.error ?? (!observed ? 'CONNECTING' : undefined) };
405
+ groups: this.groupsUsing(peer), connected: !!observed && !observed.error && Date.now() - observed.checkedAt < 15_000, error: observed?.error ?? (!observed ? 'CONNECTING' : undefined) };
332
406
  });
333
407
  return { protocolVersion: 2, sessions: this.document.peers.flatMap(peer => this.document.scoped?.[peer.serviceId] ? [] : (this.observations.get(peer.serviceId)?.sessions ?? []).map(session => ({ ...session,
334
408
  sessionId: remoteSession(peer.origin, session.sessionId), backendSessionId: null, agentNativeSessionId: null, serviceOrigin: peer.origin, serviceLabel: peer.origin,
335
409
  serviceConnected: services.find(s => s.origin === peer.origin).connected, serviceCheckedAt: this.observations.get(peer.serviceId)?.checkedAt }))),
336
- services: [...services, ...missing.map(origin => ({ origin, label: origin, connected: false, error: '服务连接授权尚未同步;在已授权服务页面连接后会自动完成' }))] };
410
+ services: [...services, ...missing.map(origin => ({ origin, label: origin, connected: false, error: '服务连接授权尚未同步;在已授权服务页面连接后会自动完成' }))],
411
+ removedServices: this.document.removedPeers?.map(peer => ({ serviceId: peer.serviceId, origin: peer.origin, label: peer.origin, connected: false })) ?? [] };
337
412
  }
338
413
  async save(input) {
339
414
  const existing = input.id ? this.options.store.getGroup(input.id) : null;
@@ -0,0 +1,84 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import fs from 'node:fs';
4
+ import { Worker } from 'node:worker_threads';
5
+ /** Keep transcript parsing and text scans off the terminal server's event loop.
6
+ * The worker and its private, incremental cache start only when search is used.
7
+ */
8
+ export class NativeSessionSearch {
9
+ directory;
10
+ worker = null;
11
+ sequence = 0;
12
+ pending = new Map();
13
+ constructor(directory) {
14
+ this.directory = directory;
15
+ }
16
+ search(query, limit, titles, signal) {
17
+ return this.request({ type: 'search', query, limit, titles }, signal);
18
+ }
19
+ get(key) {
20
+ return this.request({ type: 'get', key });
21
+ }
22
+ request(input, signal) {
23
+ if (signal?.aborted)
24
+ return Promise.reject(new Error('搜索已取消'));
25
+ if (!this.worker) {
26
+ const compiled = new URL('../workers/nativeSessionSearchWorker.js', import.meta.url);
27
+ // tsx development runs source files; production always uses compiled JS.
28
+ const url = fs.existsSync(compiled) ? compiled : new URL('../workers/nativeSessionSearchWorker.ts', import.meta.url);
29
+ this.worker = new Worker(url, { workerData: {
30
+ directory: this.directory,
31
+ codexHome: process.env.CODEX_HOME || path.join(os.homedir(), '.codex'),
32
+ claudeHome: process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'),
33
+ } });
34
+ const worker = this.worker;
35
+ worker.unref();
36
+ worker.on('message', (message) => {
37
+ const entry = this.pending.get(message.id);
38
+ if (!entry)
39
+ return;
40
+ clearTimeout(entry.timer);
41
+ entry.cleanup();
42
+ this.pending.delete(message.id);
43
+ if (message.error)
44
+ entry.reject(new Error(message.error));
45
+ else
46
+ entry.resolve(message.value);
47
+ });
48
+ const fail = (error) => {
49
+ if (this.worker !== worker)
50
+ return;
51
+ this.worker = null;
52
+ for (const entry of this.pending.values()) {
53
+ clearTimeout(entry.timer);
54
+ entry.cleanup();
55
+ entry.reject(error);
56
+ }
57
+ this.pending.clear();
58
+ void worker.terminate();
59
+ };
60
+ worker.on('error', fail);
61
+ worker.on('exit', () => fail(new Error('历史会话索引服务已退出,请重新搜索')));
62
+ }
63
+ return new Promise((resolve, reject) => {
64
+ const id = ++this.sequence;
65
+ const worker = this.worker;
66
+ const cancel = () => {
67
+ clearTimeout(timer);
68
+ this.pending.delete(id);
69
+ signal?.removeEventListener('abort', cancel);
70
+ worker.postMessage({ id, type: 'cancel' });
71
+ reject(new Error('搜索已取消'));
72
+ };
73
+ const timer = setTimeout(() => {
74
+ this.pending.delete(id);
75
+ signal?.removeEventListener('abort', cancel);
76
+ worker.postMessage({ id, type: 'cancel' });
77
+ reject(new Error('历史会话搜索超时,请缩短关键词后重试'));
78
+ }, 60_000);
79
+ this.pending.set(id, { resolve, reject, timer, cleanup: () => signal?.removeEventListener('abort', cancel) });
80
+ signal?.addEventListener('abort', cancel, { once: true });
81
+ worker.postMessage({ id, ...input });
82
+ });
83
+ }
84
+ }
@@ -53,7 +53,8 @@ export class SessionSearchStore {
53
53
  this.flush();
54
54
  const needle = query.trim().toLocaleLowerCase();
55
55
  if (!needle)
56
- return [];
56
+ return [...this.metadata.values()].sort((a, b) => b.updatedAt - a.updatedAt)
57
+ .slice(0, Math.max(1, Math.min(limit, 100))).map(metadata => ({ ...metadata, snippet: '', matchCount: 0 }));
57
58
  const results = [];
58
59
  for (const metadata of this.metadata.values()) {
59
60
  const text = this.readLog(metadata.sessionId);
@@ -50,7 +50,7 @@ import { RuntimeMonitor } from './utils/runtimeMonitor.js';
50
50
  import { startOnboardingServer, stopOnboardingServer, getOnboardingServerUrl } from './onboardingServer.js';
51
51
  import { CertificateWatcher } from './certificateWatcher.js';
52
52
  import { startTermdockLogMaintenance, writeDiffTraceLog, writeErrorLog, writeJsonLog, writeTextLog, } from './utils/serverLogger.js';
53
- import { indexRetainedClientModules, pinBundledRuntimeClientDist, resolveRuntimeClientDist } from './utils/runtimeClient.js';
53
+ import { indexRetainedClientModules, pinBundledRuntimeClientDist } from './utils/runtimeClient.js';
54
54
  import { installCrashForensics } from './utils/crashForensics.js';
55
55
  import { getTermdockVersion, TERMDOCK_CAPABILITIES, TERMDOCK_PROTOCOL_VERSION, } from './utils/version.js';
56
56
  import { PORT, DEFAULT_HOST } from './config.js';
@@ -265,7 +265,6 @@ export function createApp(options = {}) {
265
265
  version: getTermdockVersion(),
266
266
  protocolVersion: TERMDOCK_PROTOCOL_VERSION,
267
267
  capabilities: TERMDOCK_CAPABILITIES,
268
- desktopManaged: process.env.TERMDOCK_DESKTOP === '1',
269
268
  });
270
269
  });
271
270
  // Client-side log relay — enables collecting browser/device logs
@@ -350,10 +349,7 @@ export function createApp(options = {}) {
350
349
  app.use('/api/android', csrfProtection.verifyMiddleware({ bypass: isTrustedLocalCliRequest }));
351
350
  app.use('/api/android', androidRoutes);
352
351
  if (fs.existsSync(bundledClientIndexPath)) {
353
- const selectedClientPath = resolveRuntimeClientDist(bundledClientDistPath);
354
- const clientPath = selectedClientPath === bundledClientDistPath
355
- ? pinBundledRuntimeClientDist(bundledClientDistPath)
356
- : selectedClientPath;
352
+ const clientPath = pinBundledRuntimeClientDist(bundledClientDistPath);
357
353
  const compression = createStaticCompressionMiddleware(clientPath);
358
354
  const staticFiles = express.static(clientPath, {
359
355
  setHeaders: (res, filePath, stat) => {
@@ -14,7 +14,8 @@ export function readPinnedCertificateAuthority(origin, fingerprint) {
14
14
  if (cached && cached.expires > Date.now())
15
15
  return cached.value;
16
16
  const value = new Promise((resolve, reject) => {
17
- const request = get(url, { agent: false, rejectUnauthorized: false, signal: AbortSignal.timeout(5000) }, response => {
17
+ const signal = AbortSignal.timeout(5000);
18
+ const request = get(url, { agent: false, rejectUnauthorized: false, signal }, response => {
18
19
  if (response.statusCode !== 200) {
19
20
  response.resume();
20
21
  reject(new Error('Pinned certificate authority unavailable'));
@@ -44,7 +45,13 @@ export function readPinnedCertificateAuthority(origin, fingerprint) {
44
45
  }
45
46
  });
46
47
  });
47
- request.on('error', reject);
48
+ request.on('error', error => {
49
+ if (signal.aborted && signal.reason?.name === 'TimeoutError') {
50
+ reject(new Error(`连接 ${url.origin} 超时(5 秒):未能读取远端服务证书,请确认远端服务已启动且网络可达`, { cause: error }));
51
+ }
52
+ else
53
+ reject(error);
54
+ });
48
55
  });
49
56
  if (cache.size >= 64)
50
57
  cache.delete(cache.keys().next().value);
@@ -1,3 +1,4 @@
1
+ import { getHideGitIgnoredRootsSetting } from '../utils/settings.js';
1
2
  import { readRecentCommitHistory } from '../utils/recentCommitHistory.js';
2
3
  import { AUTH_COOKIE, isSessionValid } from '../utils/authProtection.js';
3
4
  import { Router } from 'express';
@@ -2703,6 +2704,35 @@ async function getCachedNestedGitRoots(workspaceRoot, options = {}) {
2703
2704
  }
2704
2705
  return promise;
2705
2706
  }
2707
+ // Ask Git in one batch so nested .gitignore, .git/info/exclude and global
2708
+ // exclusions follow Git semantics; NUL separators preserve unusual filenames.
2709
+ function getGitIgnoredPaths(directory, entries, signal) {
2710
+ if (entries.length === 0)
2711
+ return Promise.resolve(new Set());
2712
+ return new Promise((resolve, reject) => {
2713
+ const proc = execFile('git', ['check-ignore', '-z', '--stdin'], {
2714
+ cwd: directory,
2715
+ timeout: GIT_TIMEOUT_MS,
2716
+ maxBuffer: 16 * 1024 * 1024,
2717
+ signal,
2718
+ }, (error, stdout, stderr) => {
2719
+ if (error && error.code !== 1) {
2720
+ // Ordinary non-repository directories remain browsable with the filter.
2721
+ if (error.code === 'ENOENT' || /not a git repository/i.test(stderr)) {
2722
+ resolve(new Set());
2723
+ }
2724
+ else {
2725
+ reject(error);
2726
+ }
2727
+ return;
2728
+ }
2729
+ resolve(new Set(stdout.split('\0').filter(Boolean)));
2730
+ });
2731
+ // Git may exit before consuming stdin (e.g. outside a repository).
2732
+ proc.stdin?.on('error', () => undefined);
2733
+ proc.stdin?.end(entries.map((entry) => path.join(directory, entry.name)).join('\0') + '\0');
2734
+ });
2735
+ }
2706
2736
  // Directory listing
2707
2737
  router.get('/list', async (req, res) => {
2708
2738
  const requestId = ++fsIoRequestSeq;
@@ -2735,7 +2765,19 @@ router.get('/list', async (req, res) => {
2735
2765
  const sortMode = req.query.sort === 'modified' ? 'modified' : 'name';
2736
2766
  const allEntries = await fs.promises.readdir(resolvedPath, { withFileTypes: true });
2737
2767
  throwIfAborted(controller.signal, 'fs.list');
2738
- const visibleDirents = allEntries.filter(dirent => showHidden || !dirent.name.startsWith('.'));
2768
+ let visibleDirents = allEntries.filter(dirent => showHidden || !dirent.name.startsWith('.'));
2769
+ const gitIgnoreRoot = typeof req.query.gitIgnoreRoot === 'string' ? req.query.gitIgnoreRoot : null;
2770
+ if (gitIgnoreRoot && getHideGitIgnoredRootsSetting()[gitIgnoreRoot]) {
2771
+ const resolvedRoot = await pathValidator.validatePathAsync(gitIgnoreRoot);
2772
+ const relative = path.relative(resolvedRoot, resolvedPath);
2773
+ if (relative === '' || (!relative.startsWith(`..${path.sep}`) && relative !== '..' && !path.isAbsolute(relative))) {
2774
+ const ignored = await getGitIgnoredPaths(resolvedPath, visibleDirents, controller.signal);
2775
+ throwIfAborted(controller.signal, 'fs.list');
2776
+ // Filter before sorting and capping so ignored folders don't crowd
2777
+ // useful files out of the first page of a large directory.
2778
+ visibleDirents = visibleDirents.filter((entry) => entry.name !== '.git' && !ignored.has(path.join(resolvedPath, entry.name)));
2779
+ }
2780
+ }
2739
2781
  const entries = sortMode === 'modified'
2740
2782
  ? (await loadDirectoryEntriesWithModified(resolvedPath, visibleDirents, controller.signal))
2741
2783
  .sort(compareDirectoryEntriesByModified)
@@ -19,13 +19,12 @@ import { execFile, spawn } from 'child_process';
19
19
  import { createHash, randomBytes, randomUUID } from 'crypto';
20
20
  import { promisify } from 'util';
21
21
  import { caffeinateManager } from '../utils/caffeinate.js';
22
- import { DesktopRuntimeOwnerClient } from '../utils/desktopRuntimeOwner.js';
23
22
  import { gitStatusCache } from '../utils/gitStatus.js';
24
23
  import { getPtyHostManager } from '../ptyhost/manager.js';
25
24
  import { pathValidator } from '../utils/pathValidator.js';
26
25
  import { TERMINAL, TMUX } from '../config.js';
27
26
  import { localAccessManager } from '../utils/localAccess.js';
28
- 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';
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, getHideGitIgnoredRootsSetting, setHideGitIgnoredRootSetting, setFileSortModesSetting, setFileSortModeSetting, getNestedGitScanRootsSetting, setNestedGitScanRootSetting, getPinnedExplorerRootsSetting, setPinnedExplorerRootsSetting, setPinnedExplorerRootSetting, watchPinnedExplorerRootsSetting, getActiveGitReposSetting, setActiveGitRepoSetting, } from '../utils/settings.js';
29
28
  import { loadContextDraft, saveContextDraft } from '../utils/contextDraft.js';
30
29
  import { getOnboardingServerUrl } from '../onboardingServer.js';
31
30
  import { inspectLinuxInotifyUsage, isWatchResourceExhaustion } from '../utils/fileWatchPolicy.js';
@@ -48,6 +47,8 @@ import { COLLAB_NAME_FORBIDDEN, formatCollaborationDelivery } from '../agent/col
48
47
  import { ambiguousIdMessage } from '../agent/collaborationProtocol.js';
49
48
  import { buildCollaborationSpawnCommand, resolveCollaborationSpawnMode } from '../agent/collaborationSpawn.js';
50
49
  import { SessionSearchStore } from '../agent/sessionSearchStore.js';
50
+ import { NativeSessionSearch } from '../agent/nativeSessionSearch.js';
51
+ import { CollaborationError } from '../agent/collaborationProtocol.js';
51
52
  import { resolveCollaborationBackend, resolveCollaborationSessionId } from '../agent/sessionBindingRecovery.js';
52
53
  import { CollaborationRoutingStore, selectCollaborationPane, selectDrivePane } from '../agent/collaborationRouting.js';
53
54
  import { CollaborationDeliveryWorker } from '../agent/collaborationDeliveryWorker.js';
@@ -82,6 +83,7 @@ const collaborationDeliveryWorker = new CollaborationDeliveryWorker({
82
83
  onError: (error) => console.warn('[collaboration] background delivery failed:', getErrorMessage(error)),
83
84
  });
84
85
  const sessionSearchStore = new SessionSearchStore(`${TERMDOCK_DIR}/session-search`);
86
+ const nativeSessionSearch = new NativeSessionSearch(`${TERMDOCK_DIR}/session-search/native`);
85
87
  let atomicJsonWriteSequence = 0;
86
88
  async function readJsonFileIfExists(filePath) {
87
89
  try {
@@ -385,18 +387,7 @@ const npmAutoUpdateManager = new TermdockAutoUpdateManager({
385
387
  requestRestart: requestServerRestartAfterUpdate,
386
388
  log: (message) => console.warn(message),
387
389
  });
388
- const desktopRuntimeOwner = process.env.TERMDOCK_DESKTOP_OWNER_SOCKET
389
- ? new DesktopRuntimeOwnerClient(process.env.TERMDOCK_DESKTOP_OWNER_SOCKET, TERMDOCK_VERSION, (state) => broadcastControlEvent({ type: 'update-state', state }))
390
- : null;
391
- // Packaged desktop builds already have a signed app/runtime updater. The npm
392
- // updater only owns standalone CLI services, otherwise both mechanisms could
393
- // race to replace the runtime.
394
- if (desktopRuntimeOwner) {
395
- desktopRuntimeOwner.start();
396
- }
397
- else {
398
- npmAutoUpdateManager.start();
399
- }
390
+ npmAutoUpdateManager.start();
400
391
  async function flushClientStateBroadcast() {
401
392
  if (broadcastClientStateInFlight) {
402
393
  broadcastClientStateNeedsRerun = true;
@@ -3415,8 +3406,8 @@ async function findCodexSessionFile(nativeSessionId) {
3415
3406
  return cachedPath;
3416
3407
  if (!/^[a-zA-Z0-9._-]{8,160}$/.test(nativeSessionId))
3417
3408
  return null;
3418
- const root = path.join(os.homedir(), '.codex', 'sessions');
3419
- const pending = [root];
3409
+ const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
3410
+ const pending = [path.join(codexHome, 'sessions'), path.join(codexHome, 'archived_sessions')];
3420
3411
  let visited = 0;
3421
3412
  while (pending.length > 0 && visited < 20_000) {
3422
3413
  const directory = pending.pop();
@@ -3448,7 +3439,9 @@ async function findExternalCodexWriter(target) {
3448
3439
  return null;
3449
3440
  try {
3450
3441
  const { stdout } = await execFileAsync('lsof', ['-t', '--', sessionFile], { timeout: 2500, maxBuffer: 64 * 1024 });
3451
- const pid = stdout.split(/\s+/).map(Number).find((value) => Number.isInteger(value) && value > 1);
3442
+ // Our search worker reads these files in this process; that read-only
3443
+ // descriptor must not be mistaken for an external Codex conversation owner.
3444
+ const pid = stdout.split(/\s+/).map(Number).find((value) => Number.isInteger(value) && value > 1 && value !== process.pid);
3452
3445
  return pid ?? null;
3453
3446
  }
3454
3447
  catch {
@@ -5588,6 +5581,31 @@ router.get('/operations/collaboration-directory', (req, res) => {
5588
5581
  void service.refresh();
5589
5582
  res.json(service.directory());
5590
5583
  });
5584
+ router.delete('/operations/collaboration-connections/:serviceId', (req, res) => {
5585
+ try {
5586
+ assertPeerRegistrationAuthority(req);
5587
+ const service = req.app.locals.collaborationService;
5588
+ if (!service)
5589
+ return res.status(503).json({ error: '协作服务正在启动' });
5590
+ res.json(service.removePeer(req.params.serviceId));
5591
+ }
5592
+ catch (error) {
5593
+ res.status(error instanceof CollaborationError ? error.httpStatus : getErrorMessage(error) === 'AUTHORIZATION_DENIED' ? 403 : 400)
5594
+ .json({ error: getErrorMessage(error), ...(error instanceof CollaborationError ? { code: error.code, ...error.details } : {}) });
5595
+ }
5596
+ });
5597
+ router.post('/operations/collaboration-connections/:serviceId/restore', (req, res) => {
5598
+ try {
5599
+ assertPeerRegistrationAuthority(req);
5600
+ const service = req.app.locals.collaborationService;
5601
+ if (!service)
5602
+ return res.status(503).json({ error: '协作服务正在启动' });
5603
+ res.json(service.restorePeer(req.params.serviceId));
5604
+ }
5605
+ catch (error) {
5606
+ res.status(error instanceof CollaborationError ? error.httpStatus : getErrorMessage(error) === 'AUTHORIZATION_DENIED' ? 403 : 400).json({ error: getErrorMessage(error) });
5607
+ }
5608
+ });
5591
5609
  export function collaborationDirectorySessions() { return globalSessionState.sessions.map(orchestrationSessionSnapshot); }
5592
5610
  export function collaborationLocalActivity() {
5593
5611
  return globalSessionState.sessions.map(record => {
@@ -5987,16 +6005,81 @@ router.use('/operations/orchestration', collaborationRoutes({ store: collaborati
5987
6005
  broadcastClientState();
5988
6006
  return { ok: true };
5989
6007
  } }));
5990
- router.get('/operations/session-search', (req, res) => {
5991
- const query = typeof req.query.q === 'string' ? req.query.q : '';
5992
- const results = sessionSearchStore.search(query, Number(req.query.limit) || 30).map((result) => {
6008
+ router.get('/operations/session-search', async (req, res) => {
6009
+ const query = typeof req.query.q === 'string' ? req.query.q.trim().slice(0, 512) : '';
6010
+ const limit = Math.max(1, Math.min(5000, Math.floor(Number(req.query.limit)) || 50));
6011
+ const controller = new AbortController();
6012
+ const disconnected = () => { if (!res.writableEnded)
6013
+ controller.abort(); };
6014
+ res.once('close', disconnected);
6015
+ const terminalResults = sessionSearchStore.search(query, 100).map((result) => {
5993
6016
  const live = globalSessionState.sessions.some((candidate) => candidate.sessionId === result.sessionId);
5994
6017
  const resumeEntry = result.agentNativeSessionId
5995
- ? agentResumeHistory.list().find((entry) => entry.agent.sessionId === result.agentNativeSessionId)
6018
+ ? agentResumeHistory.list().find((entry) => entry.agent.slug === result.agentSlug && entry.agent.sessionId === result.agentNativeSessionId)
5996
6019
  : null;
5997
- return { ...result, live, resumeHistoryId: resumeEntry?.id ?? null };
6020
+ return { ...result, source: 'terminal', nativeKey: null, live, resumeHistoryId: resumeEntry?.id ?? null };
5998
6021
  });
5999
- res.json({ query, results });
6022
+ try {
6023
+ // Preserve the names users see in Termdock as searchable aliases, including
6024
+ // custom titles for conversations that have already been closed.
6025
+ const titles = [
6026
+ ...sessionSearchStore.search('', 100).flatMap(entry => entry.agentSlug && entry.agentNativeSessionId ? [{ agentSlug: entry.agentSlug, agentNativeSessionId: entry.agentNativeSessionId, title: entry.title }] : []),
6027
+ ...agentResumeHistory.list().reverse().map(entry => ({ agentSlug: entry.agent.slug, agentNativeSessionId: entry.agent.sessionId, title: entry.title })),
6028
+ ...globalSessionState.sessions.flatMap(entry => entry.customName && entry.agentResume?.sessionId ? [{ agentSlug: entry.agentResume.slug, agentNativeSessionId: entry.agentResume.sessionId, title: entry.name }] : []),
6029
+ ];
6030
+ const native = await nativeSessionSearch.search(query, limit + terminalResults.length, titles, controller.signal);
6031
+ if (controller.signal.aborted)
6032
+ return;
6033
+ const matched = new Set(native.matchedSessions);
6034
+ const additional = terminalResults.filter(result => !result.agentNativeSessionId || !matched.has(`${result.agentSlug}:${result.agentNativeSessionId}`));
6035
+ const results = native.results.map(result => {
6036
+ const owner = findActiveAgentResumeOwner('', { slug: result.agentSlug, nativeSessionId: result.agentNativeSessionId, command: '' });
6037
+ const liveRecord = owner ? globalSessionState.sessions.find(candidate => candidate.backendSessionId === owner) : null;
6038
+ return { ...result, source: result.agentSlug, nativeKey: result.key, sessionId: liveRecord?.sessionId || `native:${result.key}`,
6039
+ live: Boolean(liveRecord), resumeHistoryId: null };
6040
+ });
6041
+ res.json({ query, results: [...results, ...additional].sort((a, b) => b.updatedAt - a.updatedAt).slice(0, limit), total: native.total + additional.length, index: native.index });
6042
+ }
6043
+ catch (error) {
6044
+ if (controller.signal.aborted)
6045
+ return;
6046
+ res.json({ query, results: terminalResults.slice(0, limit), total: terminalResults.length,
6047
+ index: { total: 0, indexed: 0, building: false, failed: 0, warning: `历史会话暂时无法搜索:${getErrorMessage(error)}` } });
6048
+ }
6049
+ finally {
6050
+ res.off('close', disconnected);
6051
+ }
6052
+ });
6053
+ router.post('/operations/session-search/prepare', async (req, res) => {
6054
+ const key = typeof req.body?.nativeKey === 'string' ? req.body.nativeKey : '';
6055
+ if (!/^[a-f0-9]{64}$/.test(key))
6056
+ return res.status(400).json({ error: '无效的历史会话' });
6057
+ try {
6058
+ // Resolve the original file and command on the server; never accept a
6059
+ // transcript path, working directory or shell command from search clients.
6060
+ const entry = await nativeSessionSearch.get(key);
6061
+ if (!entry)
6062
+ return res.status(404).json({ error: '这条历史会话已不存在,请重新搜索' });
6063
+ const agent = agentBySlug(entry.agentSlug);
6064
+ const history = agentResumeHistory.list().find(candidate => candidate.agent.slug === entry.agentSlug && candidate.agent.sessionId === entry.agentNativeSessionId);
6065
+ const command = agent ? buildResumeCommand(agent, entry.agentNativeSessionId, history?.agent.launchArgv) : null;
6066
+ if (!command)
6067
+ return res.status(410).json({ error: '当前 Agent 不支持恢复这条历史会话' });
6068
+ const target = { slug: entry.agentSlug, nativeSessionId: entry.agentNativeSessionId, command };
6069
+ const owner = findActiveAgentResumeOwner('', target);
6070
+ const liveRecord = owner ? globalSessionState.sessions.find(candidate => candidate.backendSessionId === owner) : null;
6071
+ if (liveRecord)
6072
+ return res.json({ sessionId: liveRecord.sessionId });
6073
+ if (owner || await findExternalCodexWriter(target))
6074
+ return res.status(409).json({ error: '这条会话已在其他终端运行,请先切换到原终端或关闭后恢复', code: 'AGENT_SESSION_ACTIVE_ELSEWHERE' });
6075
+ if (!entry.cwd)
6076
+ return res.status(409).json({ error: '尚未找到这条会话的项目目录,请等待索引完成后重试' });
6077
+ const cwd = await resolveWorkingDirectory(req, entry.cwd);
6078
+ res.json({ command, cwd, title: entry.title, agent });
6079
+ }
6080
+ catch (error) {
6081
+ res.status(400).json({ error: `无法恢复会话:${getErrorMessage(error)}` });
6082
+ }
6000
6083
  });
6001
6084
  router.get('/agent-resume-history', (_req, res) => {
6002
6085
  const entries = agentResumeHistory.list().flatMap((entry) => {
@@ -6238,6 +6321,7 @@ async function getSettingsPayload() {
6238
6321
  collaborationFloatingGroupId: getCollaborationFloatingGroupIdSetting(),
6239
6322
  serviceSwitcherExpanded: getServiceSwitcherExpandedSetting(),
6240
6323
  fileSortModes: getFileSortModesSetting(),
6324
+ hideGitIgnoredRoots: getHideGitIgnoredRootsSetting(),
6241
6325
  nestedGitScanRoots: getNestedGitScanRootsSetting(),
6242
6326
  activeGitRepos: getActiveGitReposSetting(),
6243
6327
  pinnedExplorerRoots: getPinnedExplorerRootsSetting(),
@@ -6257,14 +6341,10 @@ router.get('/auto-title/catalog', async (req, res) => {
6257
6341
  res.json({ namers: await getTitleNamerCatalog(force) });
6258
6342
  });
6259
6343
  router.get('/update', (_req, res) => {
6260
- res.json(desktopRuntimeOwner?.getState() ?? npmAutoUpdateManager.getState());
6344
+ res.json(npmAutoUpdateManager.getState());
6261
6345
  });
6262
6346
  router.post('/update/check', async (_req, res) => {
6263
6347
  try {
6264
- if (desktopRuntimeOwner) {
6265
- res.json(await desktopRuntimeOwner.checkForUpdates());
6266
- return;
6267
- }
6268
6348
  // npm 查询加全局安装动辄二三十秒,而加密传输对 30s 内收不到任何回包就判超时,
6269
6349
  // 客户端会弹「服务响应超时」——更新其实装完了。检查照常跑,进度与结果都由控制
6270
6350
  // WS 的 update-state 广播推送,这里立刻回当前状态(通常是 checking)即可。
@@ -6279,9 +6359,7 @@ router.post('/update/check', async (_req, res) => {
6279
6359
  });
6280
6360
  router.post('/update/restart', async (_req, res) => {
6281
6361
  try {
6282
- const state = desktopRuntimeOwner
6283
- ? await desktopRuntimeOwner.restart()
6284
- : npmAutoUpdateManager.confirmRestart();
6362
+ const state = npmAutoUpdateManager.confirmRestart();
6285
6363
  res.status(202).json(state);
6286
6364
  }
6287
6365
  catch (error) {
@@ -6427,6 +6505,18 @@ router.put('/settings', async (req, res) => {
6427
6505
  }
6428
6506
  setFileSortModeSetting(preference.path, preference.mode);
6429
6507
  }
6508
+ if (body.hideGitIgnoredRoot && typeof body.hideGitIgnoredRoot === 'object') {
6509
+ const preference = body.hideGitIgnoredRoot;
6510
+ const validPath = typeof preference.rootPath === 'string'
6511
+ && preference.rootPath.length > 0
6512
+ && preference.rootPath.length <= 4096
6513
+ && (preference.rootPath.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(preference.rootPath));
6514
+ if (!validPath || typeof preference.enabled !== 'boolean') {
6515
+ res.status(400).json({ error: 'Invalid Git ignore visibility preference', code: 'GIT_IGNORE_VISIBILITY_INVALID' });
6516
+ return;
6517
+ }
6518
+ setHideGitIgnoredRootSetting(preference.rootPath, preference.enabled);
6519
+ }
6430
6520
  if (body.nestedGitScanRoot && typeof body.nestedGitScanRoot === 'object') {
6431
6521
  const preference = body.nestedGitScanRoot;
6432
6522
  const isAbsoluteRootPath = (value) => typeof value === 'string'
@@ -8297,7 +8387,7 @@ export function handleControlWebSocket(ws, clientId) {
8297
8387
  ws.send(JSON.stringify({ type: 'client-state', seq: initialSeq, state: globalSessionState, inventory: inventory ?? latestSessionInventory }));
8298
8388
  ws.send(JSON.stringify({
8299
8389
  type: 'update-state',
8300
- state: desktopRuntimeOwner?.getState() ?? npmAutoUpdateManager.getState(),
8390
+ state: npmAutoUpdateManager.getState(),
8301
8391
  }));
8302
8392
  // 服务健康快照:崩溃时服务自己已经死了、推不出任何东西,所以"红点"这件事
8303
8393
  // 只能靠客户端重连时的这一发。客户端断线重连的既有逻辑正好覆盖了那个时刻。