termdock 1.4.283 → 1.4.285

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 (337) hide show
  1. package/dist/client/assets/App--hZmWk-u.js +157 -0
  2. package/dist/client/assets/App--hZmWk-u.js.br +0 -0
  3. package/dist/client/assets/App--hZmWk-u.js.gz +0 -0
  4. package/dist/client/assets/{ChangeWalkthroughPanel-D3P0LWfU.js → ChangeWalkthroughPanel-BQ1hdwzt.js} +1 -1
  5. package/dist/client/assets/ChangeWalkthroughPanel-BQ1hdwzt.js.br +0 -0
  6. package/dist/client/assets/ChangeWalkthroughPanel-BQ1hdwzt.js.gz +0 -0
  7. package/dist/client/assets/{DagPlayground-DHLTKfYe.js → DagPlayground-BERXHwib.js} +1 -1
  8. package/dist/client/assets/DagPlayground-BERXHwib.js.br +0 -0
  9. package/dist/client/assets/DagPlayground-BERXHwib.js.gz +0 -0
  10. package/dist/client/assets/{DiffLab-DyapQhFp.js → DiffLab-DF14_TAz.js} +1 -1
  11. package/dist/client/assets/DiffLab-DF14_TAz.js.br +0 -0
  12. package/dist/client/assets/DiffLab-DF14_TAz.js.gz +0 -0
  13. package/dist/client/assets/{DiffReview-CtlVXUuM.js → DiffReview-BQcIn0zl.js} +1 -1
  14. package/dist/client/assets/DiffReview-BQcIn0zl.js.br +0 -0
  15. package/dist/client/assets/DiffReview-BQcIn0zl.js.gz +0 -0
  16. package/dist/client/assets/{DiffReviewLab-CcATFm0V.js → DiffReviewLab-CgChwzQr.js} +1 -1
  17. package/dist/client/assets/DiffReviewLab-CgChwzQr.js.br +0 -0
  18. package/dist/client/assets/DiffReviewLab-CgChwzQr.js.gz +0 -0
  19. package/dist/client/assets/DiffViewer-hDpe-wvG.js +37 -0
  20. package/dist/client/assets/DiffViewer-hDpe-wvG.js.br +0 -0
  21. package/dist/client/assets/DiffViewer-hDpe-wvG.js.gz +0 -0
  22. package/dist/client/assets/{ModelPreview-DFsugreu.js → ModelPreview-vg3vRIhx.js} +1 -1
  23. package/dist/client/assets/ModelPreview-vg3vRIhx.js.br +0 -0
  24. package/dist/client/assets/ModelPreview-vg3vRIhx.js.gz +0 -0
  25. package/dist/client/assets/RightSidebar-5H4vhn3k.js +1678 -0
  26. package/dist/client/assets/RightSidebar-5H4vhn3k.js.br +0 -0
  27. package/dist/client/assets/RightSidebar-5H4vhn3k.js.gz +0 -0
  28. package/dist/client/assets/{abnfDiagram-VCTEODGH-C6Qn78Kl.js → abnfDiagram-VCTEODGH-DI7938HB.js} +1 -1
  29. package/dist/client/assets/abnfDiagram-VCTEODGH-DI7938HB.js.br +0 -0
  30. package/dist/client/assets/abnfDiagram-VCTEODGH-DI7938HB.js.gz +0 -0
  31. package/dist/client/assets/app-BeVXUQb-.js +35 -0
  32. package/dist/client/assets/app-BeVXUQb-.js.br +0 -0
  33. package/dist/client/assets/app-BeVXUQb-.js.gz +0 -0
  34. package/dist/client/assets/{arc-DlrdXzuL.js → arc-D4qDm5Yn.js} +1 -1
  35. package/dist/client/assets/arc-D4qDm5Yn.js.br +0 -0
  36. package/dist/client/assets/arc-D4qDm5Yn.js.gz +0 -0
  37. package/dist/client/assets/{architectureDiagram-5GKGNRK7-yDm6ZlLB.js → architectureDiagram-5GKGNRK7-C4IZCNC9.js} +1 -1
  38. package/dist/client/assets/architectureDiagram-5GKGNRK7-C4IZCNC9.js.br +0 -0
  39. package/dist/client/assets/architectureDiagram-5GKGNRK7-C4IZCNC9.js.gz +0 -0
  40. package/dist/client/assets/{blockDiagram-I7D4REHJ-CQ_ev6HF.js → blockDiagram-I7D4REHJ-DHVOr_jQ.js} +1 -1
  41. package/dist/client/assets/blockDiagram-I7D4REHJ-DHVOr_jQ.js.br +0 -0
  42. package/dist/client/assets/blockDiagram-I7D4REHJ-DHVOr_jQ.js.gz +0 -0
  43. package/dist/client/assets/{c4Diagram-7LVT6UL2-Xc0eRnWn.js → c4Diagram-7LVT6UL2-kQqkOkIe.js} +1 -1
  44. package/dist/client/assets/c4Diagram-7LVT6UL2-kQqkOkIe.js.br +0 -0
  45. package/dist/client/assets/c4Diagram-7LVT6UL2-kQqkOkIe.js.gz +0 -0
  46. package/dist/client/assets/channel-DNlwrmA4.js +1 -0
  47. package/dist/client/assets/{chunk-2Q5K7J3B-DOea--M6.js → chunk-2Q5K7J3B-D2kOR60I.js} +1 -1
  48. package/dist/client/assets/{chunk-5VM5RSS4-5BTB5aOY.js → chunk-5VM5RSS4-CxaDg0nL.js} +1 -1
  49. package/dist/client/assets/{chunk-F27PBJKO-D4bPuUCL.js → chunk-F27PBJKO-Bxf7XPNj.js} +1 -1
  50. package/dist/client/assets/chunk-F27PBJKO-Bxf7XPNj.js.br +0 -0
  51. package/dist/client/assets/chunk-F27PBJKO-Bxf7XPNj.js.gz +0 -0
  52. package/dist/client/assets/{chunk-IMKFNOWR-CcpsYCGR.js → chunk-IMKFNOWR-BTifpaB5.js} +1 -1
  53. package/dist/client/assets/chunk-IMKFNOWR-BTifpaB5.js.br +0 -0
  54. package/dist/client/assets/chunk-IMKFNOWR-BTifpaB5.js.gz +0 -0
  55. package/dist/client/assets/{chunk-JWPE2WC7-DrVzbPfl.js → chunk-JWPE2WC7-Deieql_u.js} +1 -1
  56. package/dist/client/assets/{chunk-POPQ4Y6H-D2vQ6I_d.js → chunk-POPQ4Y6H-CqZwUpjK.js} +1 -1
  57. package/dist/client/assets/{chunk-SVP7TREG-BK9PycPu.js → chunk-SVP7TREG-Bm1spPMb.js} +1 -1
  58. package/dist/client/assets/chunk-SVP7TREG-Bm1spPMb.js.br +0 -0
  59. package/dist/client/assets/chunk-SVP7TREG-Bm1spPMb.js.gz +0 -0
  60. package/dist/client/assets/{chunk-TICWLB2K-LPhjhi7S.js → chunk-TICWLB2K-BGLj-zGP.js} +1 -1
  61. package/dist/client/assets/chunk-TICWLB2K-BGLj-zGP.js.br +0 -0
  62. package/dist/client/assets/chunk-TICWLB2K-BGLj-zGP.js.gz +0 -0
  63. package/dist/client/assets/{chunk-XXDRQBXY-qujR76f5.js → chunk-XXDRQBXY-CR77bUfc.js} +1 -1
  64. package/dist/client/assets/classDiagram-ZZMXUADV-CUt4JCwJ.js +1 -0
  65. package/dist/client/assets/classDiagram-v2-VYDZK3BY-CUt4JCwJ.js +1 -0
  66. package/dist/client/assets/{cose-bilkent-JH36ORCC-BArSIiIO.js → cose-bilkent-JH36ORCC-GTawhnoV.js} +1 -1
  67. package/dist/client/assets/cose-bilkent-JH36ORCC-GTawhnoV.js.br +0 -0
  68. package/dist/client/assets/cose-bilkent-JH36ORCC-GTawhnoV.js.gz +0 -0
  69. package/dist/client/assets/{cynefin-OW5HDTMX-CCs1or7w.js → cynefin-OW5HDTMX-DHdRkQdW.js} +1 -1
  70. package/dist/client/assets/cynefin-OW5HDTMX-DHdRkQdW.js.br +0 -0
  71. package/dist/client/assets/cynefin-OW5HDTMX-DHdRkQdW.js.gz +0 -0
  72. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CZtiCwpH.js → cynefinDiagram-5FMLGOSQ-Cj2ytLun.js} +1 -1
  73. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Cj2ytLun.js.br +0 -0
  74. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-Cj2ytLun.js.gz +0 -0
  75. package/dist/client/assets/{dagre-GXQ25YYZ-sxi6dz48.js → dagre-GXQ25YYZ-BdFZmtIj.js} +1 -1
  76. package/dist/client/assets/dagre-GXQ25YYZ-BdFZmtIj.js.br +0 -0
  77. package/dist/client/assets/dagre-GXQ25YYZ-BdFZmtIj.js.gz +0 -0
  78. package/dist/client/assets/{diagram-S7CK7UJ4-CqA9RxCi.js → diagram-S7CK7UJ4-D5WQNuKI.js} +1 -1
  79. package/dist/client/assets/diagram-S7CK7UJ4-D5WQNuKI.js.br +0 -0
  80. package/dist/client/assets/diagram-S7CK7UJ4-D5WQNuKI.js.gz +0 -0
  81. package/dist/client/assets/{diagram-UQ7AKVKN-DI6HGWgF.js → diagram-UQ7AKVKN-ChwjfMce.js} +1 -1
  82. package/dist/client/assets/diagram-UQ7AKVKN-ChwjfMce.js.br +0 -0
  83. package/dist/client/assets/diagram-UQ7AKVKN-ChwjfMce.js.gz +0 -0
  84. package/dist/client/assets/{diagram-VSXAHHWV-CMZi8lNh.js → diagram-VSXAHHWV-DT4FET5f.js} +1 -1
  85. package/dist/client/assets/diagram-VSXAHHWV-DT4FET5f.js.br +0 -0
  86. package/dist/client/assets/diagram-VSXAHHWV-DT4FET5f.js.gz +0 -0
  87. package/dist/client/assets/{diagram-VX7I27RA-C7C5Wauj.js → diagram-VX7I27RA-BLJsAGx6.js} +1 -1
  88. package/dist/client/assets/diagram-VX7I27RA-BLJsAGx6.js.br +0 -0
  89. package/dist/client/assets/diagram-VX7I27RA-BLJsAGx6.js.gz +0 -0
  90. package/dist/client/assets/{diagram-Z3DM3KII-5gZWUza2.js → diagram-Z3DM3KII-C1ofLllK.js} +1 -1
  91. package/dist/client/assets/diagram-Z3DM3KII-C1ofLllK.js.br +0 -0
  92. package/dist/client/assets/diagram-Z3DM3KII-C1ofLllK.js.gz +0 -0
  93. package/dist/client/assets/{ebnfDiagram-PWID7BFC-_UeIqZQ9.js → ebnfDiagram-PWID7BFC-apxDEeqe.js} +1 -1
  94. package/dist/client/assets/ebnfDiagram-PWID7BFC-apxDEeqe.js.br +0 -0
  95. package/dist/client/assets/ebnfDiagram-PWID7BFC-apxDEeqe.js.gz +0 -0
  96. package/dist/client/assets/{erDiagram-RLTQ6QDP-Xmw2qbA7.js → erDiagram-RLTQ6QDP-Dq9hBYpH.js} +1 -1
  97. package/dist/client/assets/erDiagram-RLTQ6QDP-Dq9hBYpH.js.br +0 -0
  98. package/dist/client/assets/erDiagram-RLTQ6QDP-Dq9hBYpH.js.gz +0 -0
  99. package/dist/client/assets/{flowDiagram-HODETNUW-KRYKHFDk.js → flowDiagram-HODETNUW-Juk-Qv9f.js} +1 -1
  100. package/dist/client/assets/flowDiagram-HODETNUW-Juk-Qv9f.js.br +0 -0
  101. package/dist/client/assets/flowDiagram-HODETNUW-Juk-Qv9f.js.gz +0 -0
  102. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-UcoHnpCx.js → ganttDiagram-EL5Y4UJY-mmwd0mxs.js} +1 -1
  103. package/dist/client/assets/ganttDiagram-EL5Y4UJY-mmwd0mxs.js.br +0 -0
  104. package/dist/client/assets/ganttDiagram-EL5Y4UJY-mmwd0mxs.js.gz +0 -0
  105. package/dist/client/assets/{gestureArbiter-B6OZhQjV.js → gestureArbiter-CM_xJnCS.js} +1 -1
  106. package/dist/client/assets/gestureArbiter-CM_xJnCS.js.br +0 -0
  107. package/dist/client/assets/gestureArbiter-CM_xJnCS.js.gz +0 -0
  108. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-Evwvg64n.js → gitGraphDiagram-WWUBYQGX--UL7HV4P.js} +1 -1
  109. package/dist/client/assets/gitGraphDiagram-WWUBYQGX--UL7HV4P.js.br +0 -0
  110. package/dist/client/assets/gitGraphDiagram-WWUBYQGX--UL7HV4P.js.gz +0 -0
  111. package/dist/client/assets/index-pZsldTFS.css +1 -0
  112. package/dist/client/assets/index-pZsldTFS.css.br +0 -0
  113. package/dist/client/assets/index-pZsldTFS.css.gz +0 -0
  114. package/dist/client/assets/{infoDiagram-27XIBGKW-CdTYa6sH.js → infoDiagram-27XIBGKW-CcsxyqYJ.js} +1 -1
  115. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-D1-1rWNL.js → ishikawaDiagram-5VMMS53U-CNVFPx1_.js} +1 -1
  116. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CNVFPx1_.js.br +0 -0
  117. package/dist/client/assets/ishikawaDiagram-5VMMS53U-CNVFPx1_.js.gz +0 -0
  118. package/dist/client/assets/{journeyDiagram-3NMN7TZE-DNF2Xeph.js → journeyDiagram-3NMN7TZE-BtIuJLUp.js} +1 -1
  119. package/dist/client/assets/journeyDiagram-3NMN7TZE-BtIuJLUp.js.br +0 -0
  120. package/dist/client/assets/journeyDiagram-3NMN7TZE-BtIuJLUp.js.gz +0 -0
  121. package/dist/client/assets/{kanban-definition-UXKFOSKX-nNy4MKl1.js → kanban-definition-UXKFOSKX-gOFjuFZV.js} +1 -1
  122. package/dist/client/assets/kanban-definition-UXKFOSKX-gOFjuFZV.js.br +0 -0
  123. package/dist/client/assets/kanban-definition-UXKFOSKX-gOFjuFZV.js.gz +0 -0
  124. package/dist/client/assets/{layout-eOsGqyyy.js → layout-KEP24zEE.js} +1 -1
  125. package/dist/client/assets/layout-KEP24zEE.js.br +0 -0
  126. package/dist/client/assets/layout-KEP24zEE.js.gz +0 -0
  127. package/dist/client/assets/{linear-BvPFVZ5r.js → linear-BwUXWDLn.js} +1 -1
  128. package/dist/client/assets/linear-BwUXWDLn.js.br +0 -0
  129. package/dist/client/assets/linear-BwUXWDLn.js.gz +0 -0
  130. package/dist/client/assets/{mermaid.core-xgi3W3HJ.js → mermaid.core-DkzJYwWa.js} +5 -5
  131. package/dist/client/assets/mermaid.core-DkzJYwWa.js.br +0 -0
  132. package/dist/client/assets/mermaid.core-DkzJYwWa.js.gz +0 -0
  133. package/dist/client/assets/{mindmap-definition-YA3MSWOX-C0Y8K-f6.js → mindmap-definition-YA3MSWOX-BE1H8WPd.js} +1 -1
  134. package/dist/client/assets/mindmap-definition-YA3MSWOX-BE1H8WPd.js.br +0 -0
  135. package/dist/client/assets/mindmap-definition-YA3MSWOX-BE1H8WPd.js.gz +0 -0
  136. package/dist/client/assets/{passwordBootstrap-dH2p1UXM.js → passwordBootstrap-Dhzj9-U2.js} +1 -1
  137. package/dist/client/assets/passwordBootstrap-Dhzj9-U2.js.br +0 -0
  138. package/dist/client/assets/passwordBootstrap-Dhzj9-U2.js.gz +0 -0
  139. package/dist/client/assets/{pegDiagram-XKGWAZYB-DDphzW5Y.js → pegDiagram-XKGWAZYB-CorBwvfP.js} +1 -1
  140. package/dist/client/assets/pegDiagram-XKGWAZYB-CorBwvfP.js.br +0 -0
  141. package/dist/client/assets/pegDiagram-XKGWAZYB-CorBwvfP.js.gz +0 -0
  142. package/dist/client/assets/{pieDiagram-E7YTZNPT-GDu--HqE.js → pieDiagram-E7YTZNPT-A017Uk-W.js} +1 -1
  143. package/dist/client/assets/pieDiagram-E7YTZNPT-A017Uk-W.js.br +0 -0
  144. package/dist/client/assets/pieDiagram-E7YTZNPT-A017Uk-W.js.gz +0 -0
  145. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-NSIlEYZd.js → quadrantDiagram-AXDQQJYC-CNNywvhG.js} +1 -1
  146. package/dist/client/assets/quadrantDiagram-AXDQQJYC-CNNywvhG.js.br +0 -0
  147. package/dist/client/assets/quadrantDiagram-AXDQQJYC-CNNywvhG.js.gz +0 -0
  148. package/dist/client/assets/{railroadDiagram-O6MQD6OU-BMr8_470.js → railroadDiagram-O6MQD6OU-DIxiWn5A.js} +1 -1
  149. package/dist/client/assets/railroadDiagram-O6MQD6OU-DIxiWn5A.js.br +0 -0
  150. package/dist/client/assets/railroadDiagram-O6MQD6OU-DIxiWn5A.js.gz +0 -0
  151. package/dist/client/assets/{referenceLongPress-5KOTIE_2.js → referenceLongPress-Bp8q50sv.js} +1 -1
  152. package/dist/client/assets/referenceLongPress-Bp8q50sv.js.br +0 -0
  153. package/dist/client/assets/referenceLongPress-Bp8q50sv.js.gz +0 -0
  154. package/dist/client/assets/{requirementDiagram-BXWQKSXE-BYfx-BnY.js → requirementDiagram-BXWQKSXE-Ob4lS23U.js} +1 -1
  155. package/dist/client/assets/requirementDiagram-BXWQKSXE-Ob4lS23U.js.br +0 -0
  156. package/dist/client/assets/requirementDiagram-BXWQKSXE-Ob4lS23U.js.gz +0 -0
  157. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-Sjw1moBs.js → sankeyDiagram-P5KCCOFB-Cc5e7umb.js} +1 -1
  158. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Cc5e7umb.js.br +0 -0
  159. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Cc5e7umb.js.gz +0 -0
  160. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-BdB4bAuv.js → sequenceDiagram-WJ2MYXX4-CLTRKFht.js} +1 -1
  161. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CLTRKFht.js.br +0 -0
  162. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-CLTRKFht.js.gz +0 -0
  163. package/dist/client/assets/{sizeCapture-INFHLROL-Bqjs5SmV.js → sizeCapture-INFHLROL-h6hFhQw6.js} +1 -1
  164. package/dist/client/assets/sizeCapture-INFHLROL-h6hFhQw6.js.br +0 -0
  165. package/dist/client/assets/sizeCapture-INFHLROL-h6hFhQw6.js.gz +0 -0
  166. package/dist/client/assets/{sparkles-BqYxpm2T.js → sparkles-9OJtJfyK.js} +1 -1
  167. package/dist/client/assets/{stateDiagram-D77RDMKH-CeGiiS-A.js → stateDiagram-D77RDMKH-DSaJxX_r.js} +1 -1
  168. package/dist/client/assets/stateDiagram-D77RDMKH-DSaJxX_r.js.br +0 -0
  169. package/dist/client/assets/stateDiagram-D77RDMKH-DSaJxX_r.js.gz +0 -0
  170. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CQ9iS9Nl.js +1 -0
  171. package/dist/client/assets/{swimlanes-42K2YHIH-CfKdzOcE.js → swimlanes-42K2YHIH-CP7ABWyE.js} +1 -1
  172. package/dist/client/assets/swimlanes-42K2YHIH-CP7ABWyE.js.br +0 -0
  173. package/dist/client/assets/{swimlanes-42K2YHIH-CfKdzOcE.js.gz → swimlanes-42K2YHIH-CP7ABWyE.js.gz} +0 -0
  174. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BbfCRvRt.js +8 -0
  175. package/dist/client/assets/{timeline-definition-24CTP7MA-BQHm94LP.js → timeline-definition-24CTP7MA-CdwVIVF6.js} +1 -1
  176. package/dist/client/assets/timeline-definition-24CTP7MA-CdwVIVF6.js.br +0 -0
  177. package/dist/client/assets/timeline-definition-24CTP7MA-CdwVIVF6.js.gz +0 -0
  178. package/dist/client/assets/{triangle-alert-9JebA9Zi.js → triangle-alert-BvT4KSYf.js} +1 -1
  179. package/dist/client/assets/{vennDiagram-4TSXK5OY-CiDKvpY7.js → vennDiagram-4TSXK5OY-BXGgQFVY.js} +1 -1
  180. package/dist/client/assets/vennDiagram-4TSXK5OY-BXGgQFVY.js.br +0 -0
  181. package/dist/client/assets/vennDiagram-4TSXK5OY-BXGgQFVY.js.gz +0 -0
  182. package/dist/client/assets/{videoCompression-BZCAJl_H.js → videoCompression-BFXJyhpz.js} +1 -1
  183. package/dist/client/assets/videoCompression-BFXJyhpz.js.br +0 -0
  184. package/dist/client/assets/videoCompression-BFXJyhpz.js.gz +0 -0
  185. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-DOUAhlES.js → wardleyDiagram-VM6X3IG4-CtLophqU.js} +1 -1
  186. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CtLophqU.js.br +0 -0
  187. package/dist/client/assets/wardleyDiagram-VM6X3IG4-CtLophqU.js.gz +0 -0
  188. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-BV7ihxFK.js → xychartDiagram-S5SC5T6Z-YZ9bnI1e.js} +1 -1
  189. package/dist/client/assets/xychartDiagram-S5SC5T6Z-YZ9bnI1e.js.br +0 -0
  190. package/dist/client/assets/xychartDiagram-S5SC5T6Z-YZ9bnI1e.js.gz +0 -0
  191. package/dist/client/index.html +2 -2
  192. package/dist/client/index.html.br +0 -0
  193. package/dist/client/index.html.gz +0 -0
  194. package/dist/client/sw.js +1 -1
  195. package/dist/client/sw.js.br +0 -0
  196. package/dist/client/sw.js.gz +0 -0
  197. package/dist/server/agent/collaborationCli.js +11 -3
  198. package/dist/server/agent/collaborationGroupRoutes.js +18 -0
  199. package/dist/server/agent/collaborationPeerTransport.js +19 -1
  200. package/dist/server/agent/collaborationRoutes.js +12 -0
  201. package/dist/server/agent/collaborationService.js +83 -8
  202. package/dist/server/agent/nativeSessionSearch.js +84 -0
  203. package/dist/server/agent/sessionSearchStore.js +2 -1
  204. package/dist/server/federation/trustedCa.js +9 -2
  205. package/dist/server/routes/filesystem.js +43 -1
  206. package/dist/server/routes/terminal.js +148 -19
  207. package/dist/server/utils/settings.js +14 -0
  208. package/dist/server/workers/nativeSessionSearchWorker.js +416 -0
  209. package/package.json +1 -1
  210. package/runtime-manifest.json +3 -3
  211. package/dist/client/assets/App-DIYNv8nX.js +0 -157
  212. package/dist/client/assets/App-DIYNv8nX.js.br +0 -0
  213. package/dist/client/assets/App-DIYNv8nX.js.gz +0 -0
  214. package/dist/client/assets/ChangeWalkthroughPanel-D3P0LWfU.js.br +0 -0
  215. package/dist/client/assets/ChangeWalkthroughPanel-D3P0LWfU.js.gz +0 -0
  216. package/dist/client/assets/DagPlayground-DHLTKfYe.js.br +0 -0
  217. package/dist/client/assets/DagPlayground-DHLTKfYe.js.gz +0 -0
  218. package/dist/client/assets/DiffLab-DyapQhFp.js.br +0 -0
  219. package/dist/client/assets/DiffLab-DyapQhFp.js.gz +0 -0
  220. package/dist/client/assets/DiffReview-CtlVXUuM.js.br +0 -0
  221. package/dist/client/assets/DiffReview-CtlVXUuM.js.gz +0 -0
  222. package/dist/client/assets/DiffReviewLab-CcATFm0V.js.br +0 -0
  223. package/dist/client/assets/DiffReviewLab-CcATFm0V.js.gz +0 -0
  224. package/dist/client/assets/DiffViewer-DbAcE-rO.js +0 -37
  225. package/dist/client/assets/DiffViewer-DbAcE-rO.js.br +0 -0
  226. package/dist/client/assets/DiffViewer-DbAcE-rO.js.gz +0 -0
  227. package/dist/client/assets/ModelPreview-DFsugreu.js.br +0 -0
  228. package/dist/client/assets/ModelPreview-DFsugreu.js.gz +0 -0
  229. package/dist/client/assets/RightSidebar-BljTZjyo.js +0 -1678
  230. package/dist/client/assets/RightSidebar-BljTZjyo.js.br +0 -0
  231. package/dist/client/assets/RightSidebar-BljTZjyo.js.gz +0 -0
  232. package/dist/client/assets/abnfDiagram-VCTEODGH-C6Qn78Kl.js.br +0 -0
  233. package/dist/client/assets/abnfDiagram-VCTEODGH-C6Qn78Kl.js.gz +0 -0
  234. package/dist/client/assets/app-BT_37rUb.js +0 -35
  235. package/dist/client/assets/app-BT_37rUb.js.br +0 -0
  236. package/dist/client/assets/app-BT_37rUb.js.gz +0 -0
  237. package/dist/client/assets/arc-DlrdXzuL.js.br +0 -0
  238. package/dist/client/assets/arc-DlrdXzuL.js.gz +0 -0
  239. package/dist/client/assets/architectureDiagram-5GKGNRK7-yDm6ZlLB.js.br +0 -0
  240. package/dist/client/assets/architectureDiagram-5GKGNRK7-yDm6ZlLB.js.gz +0 -0
  241. package/dist/client/assets/blockDiagram-I7D4REHJ-CQ_ev6HF.js.br +0 -0
  242. package/dist/client/assets/blockDiagram-I7D4REHJ-CQ_ev6HF.js.gz +0 -0
  243. package/dist/client/assets/c4Diagram-7LVT6UL2-Xc0eRnWn.js.br +0 -0
  244. package/dist/client/assets/c4Diagram-7LVT6UL2-Xc0eRnWn.js.gz +0 -0
  245. package/dist/client/assets/channel-JBVMoCs6.js +0 -1
  246. package/dist/client/assets/chunk-F27PBJKO-D4bPuUCL.js.br +0 -0
  247. package/dist/client/assets/chunk-F27PBJKO-D4bPuUCL.js.gz +0 -0
  248. package/dist/client/assets/chunk-IMKFNOWR-CcpsYCGR.js.br +0 -0
  249. package/dist/client/assets/chunk-IMKFNOWR-CcpsYCGR.js.gz +0 -0
  250. package/dist/client/assets/chunk-SVP7TREG-BK9PycPu.js.br +0 -0
  251. package/dist/client/assets/chunk-SVP7TREG-BK9PycPu.js.gz +0 -0
  252. package/dist/client/assets/chunk-TICWLB2K-LPhjhi7S.js.br +0 -0
  253. package/dist/client/assets/chunk-TICWLB2K-LPhjhi7S.js.gz +0 -0
  254. package/dist/client/assets/classDiagram-ZZMXUADV-BkraiclO.js +0 -1
  255. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BkraiclO.js +0 -1
  256. package/dist/client/assets/cose-bilkent-JH36ORCC-BArSIiIO.js.br +0 -0
  257. package/dist/client/assets/cose-bilkent-JH36ORCC-BArSIiIO.js.gz +0 -0
  258. package/dist/client/assets/cynefin-OW5HDTMX-CCs1or7w.js.br +0 -0
  259. package/dist/client/assets/cynefin-OW5HDTMX-CCs1or7w.js.gz +0 -0
  260. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CZtiCwpH.js.br +0 -0
  261. package/dist/client/assets/cynefinDiagram-5FMLGOSQ-CZtiCwpH.js.gz +0 -0
  262. package/dist/client/assets/dagre-GXQ25YYZ-sxi6dz48.js.br +0 -0
  263. package/dist/client/assets/dagre-GXQ25YYZ-sxi6dz48.js.gz +0 -0
  264. package/dist/client/assets/diagram-S7CK7UJ4-CqA9RxCi.js.br +0 -0
  265. package/dist/client/assets/diagram-S7CK7UJ4-CqA9RxCi.js.gz +0 -0
  266. package/dist/client/assets/diagram-UQ7AKVKN-DI6HGWgF.js.br +0 -0
  267. package/dist/client/assets/diagram-UQ7AKVKN-DI6HGWgF.js.gz +0 -0
  268. package/dist/client/assets/diagram-VSXAHHWV-CMZi8lNh.js.br +0 -0
  269. package/dist/client/assets/diagram-VSXAHHWV-CMZi8lNh.js.gz +0 -0
  270. package/dist/client/assets/diagram-VX7I27RA-C7C5Wauj.js.br +0 -0
  271. package/dist/client/assets/diagram-VX7I27RA-C7C5Wauj.js.gz +0 -0
  272. package/dist/client/assets/diagram-Z3DM3KII-5gZWUza2.js.br +0 -0
  273. package/dist/client/assets/diagram-Z3DM3KII-5gZWUza2.js.gz +0 -0
  274. package/dist/client/assets/ebnfDiagram-PWID7BFC-_UeIqZQ9.js.br +0 -0
  275. package/dist/client/assets/ebnfDiagram-PWID7BFC-_UeIqZQ9.js.gz +0 -0
  276. package/dist/client/assets/erDiagram-RLTQ6QDP-Xmw2qbA7.js.br +0 -0
  277. package/dist/client/assets/erDiagram-RLTQ6QDP-Xmw2qbA7.js.gz +0 -0
  278. package/dist/client/assets/flowDiagram-HODETNUW-KRYKHFDk.js.br +0 -0
  279. package/dist/client/assets/flowDiagram-HODETNUW-KRYKHFDk.js.gz +0 -0
  280. package/dist/client/assets/ganttDiagram-EL5Y4UJY-UcoHnpCx.js.br +0 -0
  281. package/dist/client/assets/ganttDiagram-EL5Y4UJY-UcoHnpCx.js.gz +0 -0
  282. package/dist/client/assets/gestureArbiter-B6OZhQjV.js.br +0 -0
  283. package/dist/client/assets/gestureArbiter-B6OZhQjV.js.gz +0 -0
  284. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Evwvg64n.js.br +0 -0
  285. package/dist/client/assets/gitGraphDiagram-WWUBYQGX-Evwvg64n.js.gz +0 -0
  286. package/dist/client/assets/index-IknuJMxJ.css +0 -1
  287. package/dist/client/assets/index-IknuJMxJ.css.br +0 -0
  288. package/dist/client/assets/index-IknuJMxJ.css.gz +0 -0
  289. package/dist/client/assets/ishikawaDiagram-5VMMS53U-D1-1rWNL.js.br +0 -0
  290. package/dist/client/assets/ishikawaDiagram-5VMMS53U-D1-1rWNL.js.gz +0 -0
  291. package/dist/client/assets/journeyDiagram-3NMN7TZE-DNF2Xeph.js.br +0 -0
  292. package/dist/client/assets/journeyDiagram-3NMN7TZE-DNF2Xeph.js.gz +0 -0
  293. package/dist/client/assets/kanban-definition-UXKFOSKX-nNy4MKl1.js.br +0 -0
  294. package/dist/client/assets/kanban-definition-UXKFOSKX-nNy4MKl1.js.gz +0 -0
  295. package/dist/client/assets/layout-eOsGqyyy.js.br +0 -0
  296. package/dist/client/assets/layout-eOsGqyyy.js.gz +0 -0
  297. package/dist/client/assets/linear-BvPFVZ5r.js.br +0 -0
  298. package/dist/client/assets/linear-BvPFVZ5r.js.gz +0 -0
  299. package/dist/client/assets/mermaid.core-xgi3W3HJ.js.br +0 -0
  300. package/dist/client/assets/mermaid.core-xgi3W3HJ.js.gz +0 -0
  301. package/dist/client/assets/mindmap-definition-YA3MSWOX-C0Y8K-f6.js.br +0 -0
  302. package/dist/client/assets/mindmap-definition-YA3MSWOX-C0Y8K-f6.js.gz +0 -0
  303. package/dist/client/assets/passwordBootstrap-dH2p1UXM.js.br +0 -0
  304. package/dist/client/assets/passwordBootstrap-dH2p1UXM.js.gz +0 -0
  305. package/dist/client/assets/pegDiagram-XKGWAZYB-DDphzW5Y.js.br +0 -0
  306. package/dist/client/assets/pegDiagram-XKGWAZYB-DDphzW5Y.js.gz +0 -0
  307. package/dist/client/assets/pieDiagram-E7YTZNPT-GDu--HqE.js.br +0 -0
  308. package/dist/client/assets/pieDiagram-E7YTZNPT-GDu--HqE.js.gz +0 -0
  309. package/dist/client/assets/quadrantDiagram-AXDQQJYC-NSIlEYZd.js.br +0 -0
  310. package/dist/client/assets/quadrantDiagram-AXDQQJYC-NSIlEYZd.js.gz +0 -0
  311. package/dist/client/assets/railroadDiagram-O6MQD6OU-BMr8_470.js.br +0 -0
  312. package/dist/client/assets/railroadDiagram-O6MQD6OU-BMr8_470.js.gz +0 -0
  313. package/dist/client/assets/referenceLongPress-5KOTIE_2.js.br +0 -0
  314. package/dist/client/assets/referenceLongPress-5KOTIE_2.js.gz +0 -0
  315. package/dist/client/assets/requirementDiagram-BXWQKSXE-BYfx-BnY.js.br +0 -0
  316. package/dist/client/assets/requirementDiagram-BXWQKSXE-BYfx-BnY.js.gz +0 -0
  317. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Sjw1moBs.js.br +0 -0
  318. package/dist/client/assets/sankeyDiagram-P5KCCOFB-Sjw1moBs.js.gz +0 -0
  319. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BdB4bAuv.js.br +0 -0
  320. package/dist/client/assets/sequenceDiagram-WJ2MYXX4-BdB4bAuv.js.gz +0 -0
  321. package/dist/client/assets/sizeCapture-INFHLROL-Bqjs5SmV.js.br +0 -0
  322. package/dist/client/assets/sizeCapture-INFHLROL-Bqjs5SmV.js.gz +0 -0
  323. package/dist/client/assets/stateDiagram-D77RDMKH-CeGiiS-A.js.br +0 -0
  324. package/dist/client/assets/stateDiagram-D77RDMKH-CeGiiS-A.js.gz +0 -0
  325. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-DAcs8J97.js +0 -1
  326. package/dist/client/assets/swimlanes-42K2YHIH-CfKdzOcE.js.br +0 -0
  327. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-ByPh86Cc.js +0 -8
  328. package/dist/client/assets/timeline-definition-24CTP7MA-BQHm94LP.js.br +0 -0
  329. package/dist/client/assets/timeline-definition-24CTP7MA-BQHm94LP.js.gz +0 -0
  330. package/dist/client/assets/vennDiagram-4TSXK5OY-CiDKvpY7.js.br +0 -0
  331. package/dist/client/assets/vennDiagram-4TSXK5OY-CiDKvpY7.js.gz +0 -0
  332. package/dist/client/assets/videoCompression-BZCAJl_H.js.br +0 -0
  333. package/dist/client/assets/videoCompression-BZCAJl_H.js.gz +0 -0
  334. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DOUAhlES.js.br +0 -0
  335. package/dist/client/assets/wardleyDiagram-VM6X3IG4-DOUAhlES.js.gz +0 -0
  336. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BV7ihxFK.js.br +0 -0
  337. package/dist/client/assets/xychartDiagram-S5SC5T6Z-BV7ihxFK.js.gz +0 -0
@@ -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);
@@ -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)
@@ -25,7 +25,7 @@ import { getPtyHostManager } from '../ptyhost/manager.js';
25
25
  import { pathValidator } from '../utils/pathValidator.js';
26
26
  import { TERMINAL, TMUX } from '../config.js';
27
27
  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';
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, getHideGitIgnoredRootsSetting, setHideGitIgnoredRootSetting, setFileSortModesSetting, setFileSortModeSetting, getNestedGitScanRootsSetting, setNestedGitScanRootSetting, getPinnedExplorerRootsSetting, setPinnedExplorerRootsSetting, setPinnedExplorerRootSetting, watchPinnedExplorerRootsSetting, getActiveGitReposSetting, setActiveGitRepoSetting, } from '../utils/settings.js';
29
29
  import { loadContextDraft, saveContextDraft } from '../utils/contextDraft.js';
30
30
  import { getOnboardingServerUrl } from '../onboardingServer.js';
31
31
  import { inspectLinuxInotifyUsage, isWatchResourceExhaustion } from '../utils/fileWatchPolicy.js';
@@ -48,6 +48,8 @@ import { COLLAB_NAME_FORBIDDEN, formatCollaborationDelivery } from '../agent/col
48
48
  import { ambiguousIdMessage } from '../agent/collaborationProtocol.js';
49
49
  import { buildCollaborationSpawnCommand, resolveCollaborationSpawnMode } from '../agent/collaborationSpawn.js';
50
50
  import { SessionSearchStore } from '../agent/sessionSearchStore.js';
51
+ import { NativeSessionSearch } from '../agent/nativeSessionSearch.js';
52
+ import { CollaborationError } from '../agent/collaborationProtocol.js';
51
53
  import { resolveCollaborationBackend, resolveCollaborationSessionId } from '../agent/sessionBindingRecovery.js';
52
54
  import { CollaborationRoutingStore, selectCollaborationPane, selectDrivePane } from '../agent/collaborationRouting.js';
53
55
  import { CollaborationDeliveryWorker } from '../agent/collaborationDeliveryWorker.js';
@@ -82,6 +84,7 @@ const collaborationDeliveryWorker = new CollaborationDeliveryWorker({
82
84
  onError: (error) => console.warn('[collaboration] background delivery failed:', getErrorMessage(error)),
83
85
  });
84
86
  const sessionSearchStore = new SessionSearchStore(`${TERMDOCK_DIR}/session-search`);
87
+ const nativeSessionSearch = new NativeSessionSearch(`${TERMDOCK_DIR}/session-search/native`);
85
88
  let atomicJsonWriteSequence = 0;
86
89
  async function readJsonFileIfExists(filePath) {
87
90
  try {
@@ -244,6 +247,9 @@ function getClientStateSemanticSignature(state, inventory) {
244
247
  connected: session.connected,
245
248
  live: session.live,
246
249
  restorable: session.restorable,
250
+ activeProgram: session.activeProgram ?? null,
251
+ cwd: session.cwd ?? null,
252
+ shellTitle: session.shellTitle ?? null,
247
253
  })),
248
254
  tmuxSessions: inventory.tmuxSessions.map((session) => ({
249
255
  name: session.name,
@@ -936,6 +942,9 @@ function normalizePersistedClientSession(input) {
936
942
  activeProgram: typeof candidate.activeProgram === 'string' && candidate.activeProgram.trim().length > 0
937
943
  ? candidate.activeProgram
938
944
  : null,
945
+ shellTitle: typeof candidate.shellTitle === 'string' && candidate.shellTitle.trim().length > 0
946
+ ? candidate.shellTitle
947
+ : null,
939
948
  agentResume: normalizePersistedAgentResumeInfo(candidate.agentResume),
940
949
  };
941
950
  }
@@ -2383,6 +2392,7 @@ async function buildSessionInventory() {
2383
2392
  return {
2384
2393
  ...tmux,
2385
2394
  program: metadata.program,
2395
+ shellTitle: metadata.shellTitle,
2386
2396
  cwd: metadata.cwd,
2387
2397
  label: metadata.label,
2388
2398
  lastActiveAt: Date.now(),
@@ -2405,13 +2415,14 @@ async function buildSessionInventory() {
2405
2415
  synchronizedPersistedFields = true;
2406
2416
  }
2407
2417
  }
2408
- // 展示名数据(activeProgram / cwd)回写:last-known 持久化,server 重启 /
2409
- // backend 掉线后 inventory hint 仍能给出上次的程序名与目录。
2418
+ // 展示名数据(程序 / 目录 / 终端标题)回写:last-known 持久化,
2419
+ // server 重启 / backend 掉线后 inventory hint 仍能给出上次的值。
2410
2420
  // live 值只以非空覆盖——检测空窗(null)不抹掉已存结果。
2411
2421
  const backend = next.backendSessionId ? terminalSessions.get(next.backendSessionId) : undefined;
2412
2422
  const tmuxMeta = next.tmuxSessionName ? liveTmuxByName.get(next.tmuxSessionName) : undefined;
2413
2423
  const liveProgram = backend?.activeProgram?.command ?? tmuxMeta?.program ?? null;
2414
2424
  const liveCwd = backend?.cwd ?? tmuxMeta?.cwd ?? null;
2425
+ const liveShellTitle = tmuxMeta?.shellTitle ?? backend?.lastOscTitle ?? null;
2415
2426
  if (liveProgram && liveProgram !== (next.activeProgram ?? null)) {
2416
2427
  next = { ...next, activeProgram: liveProgram };
2417
2428
  synchronizedPersistedFields = true;
@@ -2420,6 +2431,10 @@ async function buildSessionInventory() {
2420
2431
  next = { ...next, cwd: liveCwd };
2421
2432
  synchronizedPersistedFields = true;
2422
2433
  }
2434
+ if (liveShellTitle && liveShellTitle !== (next.shellTitle ?? null)) {
2435
+ next = { ...next, shellTitle: liveShellTitle };
2436
+ synchronizedPersistedFields = true;
2437
+ }
2423
2438
  return next;
2424
2439
  }),
2425
2440
  updatedAt: synchronizedPersistedFields ? Date.now() : globalSessionState.updatedAt,
@@ -2439,6 +2454,7 @@ async function buildSessionInventory() {
2439
2454
  const tmuxMeta = session.tmuxSessionName ? liveTmuxByName.get(session.tmuxSessionName) : undefined;
2440
2455
  const activeProgram = backend?.activeProgram?.command ?? tmuxMeta?.program ?? session.activeProgram ?? null;
2441
2456
  const cwd = backend?.cwd ?? tmuxMeta?.cwd ?? session.cwd ?? null;
2457
+ const shellTitle = tmuxMeta?.shellTitle ?? backend?.lastOscTitle ?? session.shellTitle ?? null;
2442
2458
  return {
2443
2459
  ...session,
2444
2460
  frontendSessionId: session.sessionId,
@@ -2452,6 +2468,7 @@ async function buildSessionInventory() {
2452
2468
  || (session.mode === 'shell' && canRestoreDeadAgentShell(session))),
2453
2469
  activeProgram,
2454
2470
  cwd,
2471
+ shellTitle,
2455
2472
  };
2456
2473
  });
2457
2474
  const clientByTmux = new Map();
@@ -3401,8 +3418,8 @@ async function findCodexSessionFile(nativeSessionId) {
3401
3418
  return cachedPath;
3402
3419
  if (!/^[a-zA-Z0-9._-]{8,160}$/.test(nativeSessionId))
3403
3420
  return null;
3404
- const root = path.join(os.homedir(), '.codex', 'sessions');
3405
- const pending = [root];
3421
+ const codexHome = process.env.CODEX_HOME || path.join(os.homedir(), '.codex');
3422
+ const pending = [path.join(codexHome, 'sessions'), path.join(codexHome, 'archived_sessions')];
3406
3423
  let visited = 0;
3407
3424
  while (pending.length > 0 && visited < 20_000) {
3408
3425
  const directory = pending.pop();
@@ -3434,7 +3451,9 @@ async function findExternalCodexWriter(target) {
3434
3451
  return null;
3435
3452
  try {
3436
3453
  const { stdout } = await execFileAsync('lsof', ['-t', '--', sessionFile], { timeout: 2500, maxBuffer: 64 * 1024 });
3437
- const pid = stdout.split(/\s+/).map(Number).find((value) => Number.isInteger(value) && value > 1);
3454
+ // Our search worker reads these files in this process; that read-only
3455
+ // descriptor must not be mistaken for an external Codex conversation owner.
3456
+ const pid = stdout.split(/\s+/).map(Number).find((value) => Number.isInteger(value) && value > 1 && value !== process.pid);
3438
3457
  return pid ?? null;
3439
3458
  }
3440
3459
  catch {
@@ -3670,12 +3689,15 @@ async function resolveLiveTmuxMetadata(tmuxSessionName) {
3670
3689
  const fallback = getActiveProgramFromTmuxLayout(layout);
3671
3690
  const program = resolved?.command ?? fallback?.command ?? null;
3672
3691
  const cwd = getCwdFromTmuxLayout(layout);
3673
- return buildRuntimeTmuxMetadata({
3674
- tmuxSessionName,
3675
- program,
3676
- cwd,
3677
- rawArgs: resolved?.rawArgs ?? null,
3678
- });
3692
+ return {
3693
+ ...buildRuntimeTmuxMetadata({
3694
+ tmuxSessionName,
3695
+ program,
3696
+ cwd,
3697
+ rawArgs: resolved?.rawArgs ?? null,
3698
+ }),
3699
+ shellTitle: activePane.title || null,
3700
+ };
3679
3701
  }
3680
3702
  // ── label builder (mirrors the frontend `getSessionDisplayLines` semantics) ──
3681
3703
  //
@@ -5571,6 +5593,31 @@ router.get('/operations/collaboration-directory', (req, res) => {
5571
5593
  void service.refresh();
5572
5594
  res.json(service.directory());
5573
5595
  });
5596
+ router.delete('/operations/collaboration-connections/:serviceId', (req, res) => {
5597
+ try {
5598
+ assertPeerRegistrationAuthority(req);
5599
+ const service = req.app.locals.collaborationService;
5600
+ if (!service)
5601
+ return res.status(503).json({ error: '协作服务正在启动' });
5602
+ res.json(service.removePeer(req.params.serviceId));
5603
+ }
5604
+ catch (error) {
5605
+ res.status(error instanceof CollaborationError ? error.httpStatus : getErrorMessage(error) === 'AUTHORIZATION_DENIED' ? 403 : 400)
5606
+ .json({ error: getErrorMessage(error), ...(error instanceof CollaborationError ? { code: error.code, ...error.details } : {}) });
5607
+ }
5608
+ });
5609
+ router.post('/operations/collaboration-connections/:serviceId/restore', (req, res) => {
5610
+ try {
5611
+ assertPeerRegistrationAuthority(req);
5612
+ const service = req.app.locals.collaborationService;
5613
+ if (!service)
5614
+ return res.status(503).json({ error: '协作服务正在启动' });
5615
+ res.json(service.restorePeer(req.params.serviceId));
5616
+ }
5617
+ catch (error) {
5618
+ res.status(error instanceof CollaborationError ? error.httpStatus : getErrorMessage(error) === 'AUTHORIZATION_DENIED' ? 403 : 400).json({ error: getErrorMessage(error) });
5619
+ }
5620
+ });
5574
5621
  export function collaborationDirectorySessions() { return globalSessionState.sessions.map(orchestrationSessionSnapshot); }
5575
5622
  export function collaborationLocalActivity() {
5576
5623
  return globalSessionState.sessions.map(record => {
@@ -5970,16 +6017,81 @@ router.use('/operations/orchestration', collaborationRoutes({ store: collaborati
5970
6017
  broadcastClientState();
5971
6018
  return { ok: true };
5972
6019
  } }));
5973
- router.get('/operations/session-search', (req, res) => {
5974
- const query = typeof req.query.q === 'string' ? req.query.q : '';
5975
- const results = sessionSearchStore.search(query, Number(req.query.limit) || 30).map((result) => {
6020
+ router.get('/operations/session-search', async (req, res) => {
6021
+ const query = typeof req.query.q === 'string' ? req.query.q.trim().slice(0, 512) : '';
6022
+ const limit = Math.max(1, Math.min(5000, Math.floor(Number(req.query.limit)) || 50));
6023
+ const controller = new AbortController();
6024
+ const disconnected = () => { if (!res.writableEnded)
6025
+ controller.abort(); };
6026
+ res.once('close', disconnected);
6027
+ const terminalResults = sessionSearchStore.search(query, 100).map((result) => {
5976
6028
  const live = globalSessionState.sessions.some((candidate) => candidate.sessionId === result.sessionId);
5977
6029
  const resumeEntry = result.agentNativeSessionId
5978
- ? agentResumeHistory.list().find((entry) => entry.agent.sessionId === result.agentNativeSessionId)
6030
+ ? agentResumeHistory.list().find((entry) => entry.agent.slug === result.agentSlug && entry.agent.sessionId === result.agentNativeSessionId)
5979
6031
  : null;
5980
- return { ...result, live, resumeHistoryId: resumeEntry?.id ?? null };
6032
+ return { ...result, source: 'terminal', nativeKey: null, live, resumeHistoryId: resumeEntry?.id ?? null };
5981
6033
  });
5982
- res.json({ query, results });
6034
+ try {
6035
+ // Preserve the names users see in Termdock as searchable aliases, including
6036
+ // custom titles for conversations that have already been closed.
6037
+ const titles = [
6038
+ ...sessionSearchStore.search('', 100).flatMap(entry => entry.agentSlug && entry.agentNativeSessionId ? [{ agentSlug: entry.agentSlug, agentNativeSessionId: entry.agentNativeSessionId, title: entry.title }] : []),
6039
+ ...agentResumeHistory.list().reverse().map(entry => ({ agentSlug: entry.agent.slug, agentNativeSessionId: entry.agent.sessionId, title: entry.title })),
6040
+ ...globalSessionState.sessions.flatMap(entry => entry.customName && entry.agentResume?.sessionId ? [{ agentSlug: entry.agentResume.slug, agentNativeSessionId: entry.agentResume.sessionId, title: entry.name }] : []),
6041
+ ];
6042
+ const native = await nativeSessionSearch.search(query, limit + terminalResults.length, titles, controller.signal);
6043
+ if (controller.signal.aborted)
6044
+ return;
6045
+ const matched = new Set(native.matchedSessions);
6046
+ const additional = terminalResults.filter(result => !result.agentNativeSessionId || !matched.has(`${result.agentSlug}:${result.agentNativeSessionId}`));
6047
+ const results = native.results.map(result => {
6048
+ const owner = findActiveAgentResumeOwner('', { slug: result.agentSlug, nativeSessionId: result.agentNativeSessionId, command: '' });
6049
+ const liveRecord = owner ? globalSessionState.sessions.find(candidate => candidate.backendSessionId === owner) : null;
6050
+ return { ...result, source: result.agentSlug, nativeKey: result.key, sessionId: liveRecord?.sessionId || `native:${result.key}`,
6051
+ live: Boolean(liveRecord), resumeHistoryId: null };
6052
+ });
6053
+ res.json({ query, results: [...results, ...additional].sort((a, b) => b.updatedAt - a.updatedAt).slice(0, limit), total: native.total + additional.length, index: native.index });
6054
+ }
6055
+ catch (error) {
6056
+ if (controller.signal.aborted)
6057
+ return;
6058
+ res.json({ query, results: terminalResults.slice(0, limit), total: terminalResults.length,
6059
+ index: { total: 0, indexed: 0, building: false, failed: 0, warning: `历史会话暂时无法搜索:${getErrorMessage(error)}` } });
6060
+ }
6061
+ finally {
6062
+ res.off('close', disconnected);
6063
+ }
6064
+ });
6065
+ router.post('/operations/session-search/prepare', async (req, res) => {
6066
+ const key = typeof req.body?.nativeKey === 'string' ? req.body.nativeKey : '';
6067
+ if (!/^[a-f0-9]{64}$/.test(key))
6068
+ return res.status(400).json({ error: '无效的历史会话' });
6069
+ try {
6070
+ // Resolve the original file and command on the server; never accept a
6071
+ // transcript path, working directory or shell command from search clients.
6072
+ const entry = await nativeSessionSearch.get(key);
6073
+ if (!entry)
6074
+ return res.status(404).json({ error: '这条历史会话已不存在,请重新搜索' });
6075
+ const agent = agentBySlug(entry.agentSlug);
6076
+ const history = agentResumeHistory.list().find(candidate => candidate.agent.slug === entry.agentSlug && candidate.agent.sessionId === entry.agentNativeSessionId);
6077
+ const command = agent ? buildResumeCommand(agent, entry.agentNativeSessionId, history?.agent.launchArgv) : null;
6078
+ if (!command)
6079
+ return res.status(410).json({ error: '当前 Agent 不支持恢复这条历史会话' });
6080
+ const target = { slug: entry.agentSlug, nativeSessionId: entry.agentNativeSessionId, command };
6081
+ const owner = findActiveAgentResumeOwner('', target);
6082
+ const liveRecord = owner ? globalSessionState.sessions.find(candidate => candidate.backendSessionId === owner) : null;
6083
+ if (liveRecord)
6084
+ return res.json({ sessionId: liveRecord.sessionId });
6085
+ if (owner || await findExternalCodexWriter(target))
6086
+ return res.status(409).json({ error: '这条会话已在其他终端运行,请先切换到原终端或关闭后恢复', code: 'AGENT_SESSION_ACTIVE_ELSEWHERE' });
6087
+ if (!entry.cwd)
6088
+ return res.status(409).json({ error: '尚未找到这条会话的项目目录,请等待索引完成后重试' });
6089
+ const cwd = await resolveWorkingDirectory(req, entry.cwd);
6090
+ res.json({ command, cwd, title: entry.title, agent });
6091
+ }
6092
+ catch (error) {
6093
+ res.status(400).json({ error: `无法恢复会话:${getErrorMessage(error)}` });
6094
+ }
5983
6095
  });
5984
6096
  router.get('/agent-resume-history', (_req, res) => {
5985
6097
  const entries = agentResumeHistory.list().flatMap((entry) => {
@@ -6221,6 +6333,7 @@ async function getSettingsPayload() {
6221
6333
  collaborationFloatingGroupId: getCollaborationFloatingGroupIdSetting(),
6222
6334
  serviceSwitcherExpanded: getServiceSwitcherExpandedSetting(),
6223
6335
  fileSortModes: getFileSortModesSetting(),
6336
+ hideGitIgnoredRoots: getHideGitIgnoredRootsSetting(),
6224
6337
  nestedGitScanRoots: getNestedGitScanRootsSetting(),
6225
6338
  activeGitRepos: getActiveGitReposSetting(),
6226
6339
  pinnedExplorerRoots: getPinnedExplorerRootsSetting(),
@@ -6410,6 +6523,18 @@ router.put('/settings', async (req, res) => {
6410
6523
  }
6411
6524
  setFileSortModeSetting(preference.path, preference.mode);
6412
6525
  }
6526
+ if (body.hideGitIgnoredRoot && typeof body.hideGitIgnoredRoot === 'object') {
6527
+ const preference = body.hideGitIgnoredRoot;
6528
+ const validPath = typeof preference.rootPath === 'string'
6529
+ && preference.rootPath.length > 0
6530
+ && preference.rootPath.length <= 4096
6531
+ && (preference.rootPath.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(preference.rootPath));
6532
+ if (!validPath || typeof preference.enabled !== 'boolean') {
6533
+ res.status(400).json({ error: 'Invalid Git ignore visibility preference', code: 'GIT_IGNORE_VISIBILITY_INVALID' });
6534
+ return;
6535
+ }
6536
+ setHideGitIgnoredRootSetting(preference.rootPath, preference.enabled);
6537
+ }
6413
6538
  if (body.nestedGitScanRoot && typeof body.nestedGitScanRoot === 'object') {
6414
6539
  const preference = body.nestedGitScanRoot;
6415
6540
  const isAbsoluteRootPath = (value) => typeof value === 'string'
@@ -8396,8 +8521,12 @@ async function reconcileClientState() {
8396
8521
  }
8397
8522
  }
8398
8523
  }
8399
- if (toRemove.length === 0 && toDetach.length === 0)
8524
+ if (toRemove.length === 0 && toDetach.length === 0) {
8525
+ // The refreshed inventory may contain new titles for unopened panes.
8526
+ // Semantic deduplication suppresses broadcasts when nothing changed.
8527
+ broadcastClientState();
8400
8528
  return;
8529
+ }
8401
8530
  const detached = new Set(toDetach);
8402
8531
  globalSessionState = {
8403
8532
  sessions: globalSessionState.sessions