dsh-over-ssh 0.1.0 → 0.2.0

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 (250) hide show
  1. package/README.md +19 -17
  2. package/agents/agent-manifest.json +6 -6
  3. package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
  4. package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
  5. package/lib/client.js +422 -337
  6. package/lib/file-references.js +92 -35
  7. package/lib/fs.js +295 -113
  8. package/lib/index.js +6912 -4330
  9. package/lib/terminal-wrapper.js +9 -4
  10. package/lib/types/src/config.d.ts +156 -9
  11. package/lib/types/src/file-references.d.ts +0 -1
  12. package/lib/types/src/host/app/backend.d.ts +44 -3
  13. package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +0 -1
  14. package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +0 -1
  15. package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +12 -1
  16. package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +0 -1
  17. package/lib/types/src/host/app/transport/transport-selector.d.ts +0 -1
  18. package/lib/types/src/host/app/transport/transport-service.d.ts +0 -1
  19. package/lib/types/src/host/domain/capability.d.ts +0 -1
  20. package/lib/types/src/host/domain/errors.d.ts +0 -1
  21. package/lib/types/src/host/domain/roots/connection.d.ts +0 -1
  22. package/lib/types/src/host/domain/roots/root.d.ts +24 -9
  23. package/lib/types/src/host/domain/roots/session-roots.d.ts +23 -1
  24. package/lib/types/src/host/domain/roots/ssh-config.d.ts +0 -1
  25. package/lib/types/src/host/domain/session/session-admin.d.ts +0 -1
  26. package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +0 -1
  27. package/lib/types/src/host/domain/transfer/transfer.d.ts +0 -1
  28. package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +0 -1
  29. package/lib/types/src/host/infra/transport/forward.d.ts +75 -0
  30. package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +5 -1
  31. package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
  32. package/lib/types/src/host/infra/transport/transport.d.ts +42 -2
  33. package/lib/types/src/host/integration/agent/agent-preflight.d.ts +0 -1
  34. package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +0 -1
  35. package/lib/types/src/host/integration/fs/fs-provider.d.ts +18 -3
  36. package/lib/types/src/host/integration/transfer/transfer-service.d.ts +0 -1
  37. package/lib/types/src/index.d.ts +23 -1
  38. package/lib/types/src/local-bridge-refs.d.ts +0 -1
  39. package/lib/types/src/local-bridge.d.ts +0 -1
  40. package/lib/types/src/shared/root-keys.d.ts +120 -0
  41. package/lib/types/src/wiring/helpers.d.ts +0 -1
  42. package/package.json +5 -3
  43. package/lib/types/src/client/controls.d.ts +0 -131
  44. package/lib/types/src/client/core/card-focus-trap.d.ts +0 -45
  45. package/lib/types/src/client/core/controls.d.ts +0 -164
  46. package/lib/types/src/client/core/latest.d.ts +0 -48
  47. package/lib/types/src/client/core/locale.d.ts +0 -38
  48. package/lib/types/src/client/core/message.d.ts +0 -37
  49. package/lib/types/src/client/core/panel-model.d.ts +0 -134
  50. package/lib/types/src/client/core/portal.d.ts +0 -266
  51. package/lib/types/src/client/core/remote.d.ts +0 -84
  52. package/lib/types/src/client/core/styles.d.ts +0 -458
  53. package/lib/types/src/client/file-tree-io.d.ts +0 -19
  54. package/lib/types/src/client/file-tree.d.ts +0 -271
  55. package/lib/types/src/client/flows/directory-browser.d.ts +0 -587
  56. package/lib/types/src/client/flows/session-directory.d.ts +0 -415
  57. package/lib/types/src/client/flows/workspace-directory-flow.d.ts +0 -480
  58. package/lib/types/src/client/locale.d.ts +0 -31
  59. package/lib/types/src/client/panel-model.d.ts +0 -37
  60. package/lib/types/src/client/panel.d.ts +0 -77
  61. package/lib/types/src/client/roots/connection-test.d.ts +0 -69
  62. package/lib/types/src/client/roots/roots-panel-state.d.ts +0 -307
  63. package/lib/types/src/client/roots/roots-panel-view.d.ts +0 -280
  64. package/lib/types/src/client/roots/roots-panel.d.ts +0 -364
  65. package/lib/types/src/client/roots/transfer-panel-view.d.ts +0 -35
  66. package/lib/types/src/client/roots/transfer-panel.d.ts +0 -241
  67. package/lib/types/src/client/roots/transfer-progress.d.ts +0 -67
  68. package/lib/types/src/client/roots-panel-view.d.ts +0 -236
  69. package/lib/types/src/client/roots-panel.d.ts +0 -347
  70. package/lib/types/src/client/session/session-panel-view.d.ts +0 -336
  71. package/lib/types/src/client/session/session-panel.d.ts +0 -322
  72. package/lib/types/src/client/session/session-remote-mark.d.ts +0 -110
  73. package/lib/types/src/client/session/session-row-actions.d.ts +0 -398
  74. package/lib/types/src/client/session-panel.d.ts +0 -152
  75. package/lib/types/src/client/styles.d.ts +0 -252
  76. package/lib/types/src/client/workspace-directory-flow.d.ts +0 -342
  77. package/lib/types/src/fs.d.ts +0 -46
  78. package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +0 -18
  79. package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +0 -38
  80. package/lib/types/src/host/app/recycle-bin-contract.d.ts +0 -215
  81. package/lib/types/src/host/app/recycle-bin-move.d.ts +0 -18
  82. package/lib/types/src/host/app/recycle-bin-service.d.ts +0 -45
  83. package/lib/types/src/host/app/recycle-bin-world.d.ts +0 -35
  84. package/lib/types/src/host/app/roots/roots-api-connections.d.ts +0 -49
  85. package/lib/types/src/host/app/roots/roots-api-contract.d.ts +0 -369
  86. package/lib/types/src/host/app/roots/roots-api-directories.d.ts +0 -13
  87. package/lib/types/src/host/app/roots/roots-api-http.d.ts +0 -77
  88. package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +0 -130
  89. package/lib/types/src/host/app/roots/roots-api-projections.d.ts +0 -102
  90. package/lib/types/src/host/app/roots/roots-api-roots.d.ts +0 -13
  91. package/lib/types/src/host/app/roots/roots-api-state.d.ts +0 -22
  92. package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +0 -26
  93. package/lib/types/src/host/app/roots/roots-api.d.ts +0 -55
  94. package/lib/types/src/host/app/roots/user-roots.d.ts +0 -552
  95. package/lib/types/src/host/app/roots-api-connections.d.ts +0 -49
  96. package/lib/types/src/host/app/roots-api-contract.d.ts +0 -339
  97. package/lib/types/src/host/app/roots-api-directories.d.ts +0 -13
  98. package/lib/types/src/host/app/roots-api-http.d.ts +0 -77
  99. package/lib/types/src/host/app/roots-api-projections.d.ts +0 -102
  100. package/lib/types/src/host/app/roots-api-roots.d.ts +0 -13
  101. package/lib/types/src/host/app/roots-api-state.d.ts +0 -22
  102. package/lib/types/src/host/app/roots-api-support.d.ts +0 -182
  103. package/lib/types/src/host/app/roots-api-transfers.d.ts +0 -13
  104. package/lib/types/src/host/app/roots-api.d.ts +0 -55
  105. package/lib/types/src/host/app/routed-fs.d.ts +0 -239
  106. package/lib/types/src/host/app/serialize.d.ts +0 -37
  107. package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +0 -36
  108. package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +0 -34
  109. package/lib/types/src/host/app/session-admin-context.d.ts +0 -36
  110. package/lib/types/src/host/app/session-admin-contract.d.ts +0 -214
  111. package/lib/types/src/host/app/session-admin-delete.d.ts +0 -34
  112. package/lib/types/src/host/app/session-admin-service.d.ts +0 -38
  113. package/lib/types/src/host/app/takeover/takeover-plan.d.ts +0 -227
  114. package/lib/types/src/host/app/takeover/takeover-rows.d.ts +0 -55
  115. package/lib/types/src/host/app/transport-selector.d.ts +0 -98
  116. package/lib/types/src/host/app/transport-service.d.ts +0 -75
  117. package/lib/types/src/host/app/user-roots.d.ts +0 -461
  118. package/lib/types/src/host/domain/bin-location.d.ts +0 -79
  119. package/lib/types/src/host/domain/connection.d.ts +0 -95
  120. package/lib/types/src/host/domain/glob-pattern.d.ts +0 -62
  121. package/lib/types/src/host/domain/literal-text.d.ts +0 -63
  122. package/lib/types/src/host/domain/path-grammar.d.ts +0 -84
  123. package/lib/types/src/host/domain/recycle-bin.d.ts +0 -337
  124. package/lib/types/src/host/domain/root.d.ts +0 -74
  125. package/lib/types/src/host/domain/roots/bin-location.d.ts +0 -91
  126. package/lib/types/src/host/domain/roots/route-table.d.ts +0 -102
  127. package/lib/types/src/host/domain/route-table.d.ts +0 -80
  128. package/lib/types/src/host/domain/session/terminal-target.d.ts +0 -136
  129. package/lib/types/src/host/domain/session-admin.d.ts +0 -330
  130. package/lib/types/src/host/domain/shell.d.ts +0 -47
  131. package/lib/types/src/host/domain/ssh-config.d.ts +0 -73
  132. package/lib/types/src/host/domain/terminal-target.d.ts +0 -132
  133. package/lib/types/src/host/domain/tool-requests.d.ts +0 -369
  134. package/lib/types/src/host/domain/transfer.d.ts +0 -260
  135. package/lib/types/src/host/infra/backend/local-backend.d.ts +0 -84
  136. package/lib/types/src/host/infra/backend/local-version.d.ts +0 -43
  137. package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +0 -89
  138. package/lib/types/src/host/infra/backend/remote-backend.d.ts +0 -239
  139. package/lib/types/src/host/infra/cache/lru.d.ts +0 -52
  140. package/lib/types/src/host/infra/cache/versioned.d.ts +0 -77
  141. package/lib/types/src/host/infra/temp-name.d.ts +0 -19
  142. package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +0 -132
  143. package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +0 -140
  144. package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +0 -41
  145. package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +0 -197
  146. package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +0 -564
  147. package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +0 -418
  148. package/lib/types/src/host/infra/transport/agent-installer.d.ts +0 -121
  149. package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +0 -123
  150. package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +0 -131
  151. package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +0 -41
  152. package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +0 -197
  153. package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +0 -509
  154. package/lib/types/src/host/infra/transport/agent-transport.d.ts +0 -353
  155. package/lib/types/src/host/infra/transport/alias-transport.d.ts +0 -206
  156. package/lib/types/src/host/infra/transport/askpass-fs.d.ts +0 -20
  157. package/lib/types/src/host/infra/transport/askpass.d.ts +0 -133
  158. package/lib/types/src/host/infra/transport/command-pool.d.ts +0 -61
  159. package/lib/types/src/host/infra/transport/fs-commands.d.ts +0 -140
  160. package/lib/types/src/host/infra/transport/git-status-route.d.ts +0 -63
  161. package/lib/types/src/host/infra/transport/git-status.d.ts +0 -101
  162. package/lib/types/src/host/infra/transport/node-spawn.d.ts +0 -26
  163. package/lib/types/src/host/infra/transport/session-window.d.ts +0 -51
  164. package/lib/types/src/host/infra/transport/shell-quote.d.ts +0 -59
  165. package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +0 -206
  166. package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +0 -20
  167. package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +0 -133
  168. package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +0 -62
  169. package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +0 -43
  170. package/lib/types/src/host/infra/transport/ssh-argv.d.ts +0 -43
  171. package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +0 -302
  172. package/lib/types/src/host/integration/agent/agent-install-io.d.ts +0 -33
  173. package/lib/types/src/host/integration/agent/askpass-registry.d.ts +0 -66
  174. package/lib/types/src/host/integration/agent-install-io.d.ts +0 -33
  175. package/lib/types/src/host/integration/agent-preflight.d.ts +0 -56
  176. package/lib/types/src/host/integration/anchor-directory.d.ts +0 -56
  177. package/lib/types/src/host/integration/askpass-registry.d.ts +0 -59
  178. package/lib/types/src/host/integration/bin-root-resolver.d.ts +0 -81
  179. package/lib/types/src/host/integration/connection-test.d.ts +0 -73
  180. package/lib/types/src/host/integration/file-reference-provider.d.ts +0 -115
  181. package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +0 -21
  182. package/lib/types/src/host/integration/fs/platform-errors.d.ts +0 -94
  183. package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +0 -369
  184. package/lib/types/src/host/integration/fs/remote-index.d.ts +0 -177
  185. package/lib/types/src/host/integration/fs-provider.d.ts +0 -226
  186. package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +0 -38
  187. package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +0 -67
  188. package/lib/types/src/host/integration/lsp/lsp-session.d.ts +0 -130
  189. package/lib/types/src/host/integration/management-tools.d.ts +0 -64
  190. package/lib/types/src/host/integration/recycle-bin-service.d.ts +0 -247
  191. package/lib/types/src/host/integration/remote/connection-test.d.ts +0 -73
  192. package/lib/types/src/host/integration/remote/remote-face.d.ts +0 -127
  193. package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +0 -108
  194. package/lib/types/src/host/integration/remote-face.d.ts +0 -127
  195. package/lib/types/src/host/integration/remote-file-tools.d.ts +0 -241
  196. package/lib/types/src/host/integration/remote-home.d.ts +0 -69
  197. package/lib/types/src/host/integration/remote-index.d.ts +0 -165
  198. package/lib/types/src/host/integration/remote-job-tools.d.ts +0 -295
  199. package/lib/types/src/host/integration/remote-tool-backend.d.ts +0 -97
  200. package/lib/types/src/host/integration/remote-tools.d.ts +0 -206
  201. package/lib/types/src/host/integration/root-registry.d.ts +0 -115
  202. package/lib/types/src/host/integration/roots/anchor-directory.d.ts +0 -56
  203. package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +0 -81
  204. package/lib/types/src/host/integration/roots/remote-home.d.ts +0 -69
  205. package/lib/types/src/host/integration/roots/root-registry.d.ts +0 -144
  206. package/lib/types/src/host/integration/roots/roots-wiring.d.ts +0 -219
  207. package/lib/types/src/host/integration/roots/session-root-index.d.ts +0 -52
  208. package/lib/types/src/host/integration/roots-api.d.ts +0 -407
  209. package/lib/types/src/host/integration/roots-wiring.d.ts +0 -211
  210. package/lib/types/src/host/integration/session/session-api.d.ts +0 -88
  211. package/lib/types/src/host/integration/session/session-bin-lock.d.ts +0 -80
  212. package/lib/types/src/host/integration/session/session-header.d.ts +0 -151
  213. package/lib/types/src/host/integration/session/session-labels.d.ts +0 -56
  214. package/lib/types/src/host/integration/session/session-route-wiring.d.ts +0 -50
  215. package/lib/types/src/host/integration/session-admin-service.d.ts +0 -236
  216. package/lib/types/src/host/integration/session-api.d.ts +0 -81
  217. package/lib/types/src/host/integration/session-bin-lock.d.ts +0 -80
  218. package/lib/types/src/host/integration/session-header.d.ts +0 -118
  219. package/lib/types/src/host/integration/session-labels.d.ts +0 -56
  220. package/lib/types/src/host/integration/session-route-wiring.d.ts +0 -50
  221. package/lib/types/src/host/integration/session-scope.d.ts +0 -113
  222. package/lib/types/src/host/integration/session-tools.d.ts +0 -44
  223. package/lib/types/src/host/integration/strict-arguments.d.ts +0 -79
  224. package/lib/types/src/host/integration/takeover/takeover-api.d.ts +0 -62
  225. package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +0 -60
  226. package/lib/types/src/host/integration/takeover/takeover-service.d.ts +0 -167
  227. package/lib/types/src/host/integration/terminal-manifest.d.ts +0 -94
  228. package/lib/types/src/host/integration/tools/management-tools.d.ts +0 -245
  229. package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +0 -316
  230. package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +0 -43
  231. package/lib/types/src/host/integration/tools/remote-tools.d.ts +0 -236
  232. package/lib/types/src/host/integration/tools/session-target.d.ts +0 -52
  233. package/lib/types/src/host/integration/tools/session-tools.d.ts +0 -46
  234. package/lib/types/src/host/integration/tools/strict-arguments.d.ts +0 -79
  235. package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +0 -94
  236. package/lib/types/src/host/integration/transfer/remote-copy.d.ts +0 -66
  237. package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +0 -48
  238. package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +0 -106
  239. package/lib/types/src/host/integration/transfer-adapters.d.ts +0 -44
  240. package/lib/types/src/host/integration/transfer-service.d.ts +0 -164
  241. package/lib/types/src/host/integration/transfer-tracker.d.ts +0 -106
  242. package/lib/types/src/host/integration/user-roots.d.ts +0 -461
  243. package/lib/types/src/wiring/connections.d.ts +0 -189
  244. package/lib/types/src/wiring/fs.d.ts +0 -13
  245. package/lib/types/src/wiring/install-memo.d.ts +0 -32
  246. package/lib/types/src/wiring/legacy-dirs.d.ts +0 -62
  247. package/lib/types/src/wiring/package-paths.d.ts +0 -35
  248. package/lib/types/src/wiring/roots.d.ts +0 -21
  249. package/lib/types/src/wiring/state-dir.d.ts +0 -29
  250. package/lib/types/src/wiring/tools.d.ts +0 -35
package/lib/client.js CHANGED
@@ -43,9 +43,10 @@ var OVER_SSH_DICTS = {
43
43
  rootsTitle: "\u8FDC\u7A0B\u5DE5\u4F5C\u533A",
44
44
  // 本页只列插件已登记的根、并可移除:「增」在审计 0027 已移到工作区目录流,旧文案的
45
45
  // 「在这里增删…」是错的。作用域那句(不会自动出现在会话的目录列表里)是审计 0056 的护栏,保留。
46
- rootsDescription: "\u8FD9\u91CC\u53EA\u5217\u51FA\u63D2\u4EF6\u5DF2\u767B\u8BB0\u7684\u6839\uFF0C\u5E76\u53EF\u4EE5\u79FB\u9664\uFF1B\u8981\u6DFB\u52A0\u76EE\u5F55\u8BF7\u5728\u5DE5\u4F5C\u533A\u7684\u76EE\u5F55\u6D41\u91CC\u505A\u3002\u8FD9\u91CC\u767B\u8BB0\u7684\u6839\u4E0D\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728\u4F1A\u8BDD\u7684\u76EE\u5F55\u5217\u8868\u91CC\uFF1A\u4F1A\u8BDD\u8981\u7528\u5B83\uFF0C\u9700\u5728\u4F1A\u8BDD\u91CC\u70B9\u300C\u6DFB\u52A0\u76EE\u5F55\u300D\u3002profile \u91CC\u58F0\u660E\u7684\u6839\u53EA\u8BFB\u663E\u793A\uFF1A\u5220\u6389\u5B83\u91CD\u542F\u540E\u8FD8\u4F1A\u56DE\u6765\u3002",
46
+ rootsDescription: "\u8FD9\u91CC\u53EA\u5217\u51FA\u63D2\u4EF6\u5DF2\u767B\u8BB0\u7684\u6839\uFF0C\u5E76\u53EF\u4EE5\u79FB\u9664\uFF1B\u8981\u6DFB\u52A0\u76EE\u5F55\u8BF7\u5728\u5DE5\u4F5C\u533A\u7684\u76EE\u5F55\u6D41\u91CC\u505A\u3002\u8FD9\u91CC\u767B\u8BB0\u7684\u6839\u4E0D\u4F1A\u81EA\u52A8\u51FA\u73B0\u5728\u4F1A\u8BDD\u7684\u76EE\u5F55\u5217\u8868\u91CC\uFF1A\u4F1A\u8BDD\u8981\u7528\u5B83\uFF0C\u9700\u5728\u4F1A\u8BDD\u91CC\u70B9\u300C\u6DFB\u52A0\u76EE\u5F55\u300D\u3002\u6A21\u578B\u5DE5\u5177\u53EA\u8BA4\u300C\u843D\u5728\u672C\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u5185\u7684\u6839\u300D\u4E0E\u300C\u951A\u70B9\u5C31\u662F\u672C\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u7684\u8FDC\u7AEF\u6839\u300D\uFF1B\u5176\u4F59\u6839\u5728\u88C5\u5165\u4F1A\u8BDD\u4E4B\u524D\u4F1A\u62A5\u300C\u4F1A\u8BDD\u9694\u79BB\u300D\u3002profile \u91CC\u58F0\u660E\u7684\u6839\u53EA\u8BFB\u663E\u793A\uFF1A\u5220\u6389\u5B83\u91CD\u542F\u540E\u8FD8\u4F1A\u56DE\u6765\u3002",
47
47
  rootsAdvancedSummary: "\u9AD8\u7EA7\uFF1A\u6839\u8868\uFF08\u8BCA\u65AD\uFF09",
48
48
  rootsAdvancedNote: "\u8FD9\u662F\u63D2\u4EF6\u7684\u6839\u8DEF\u7531\u8868\uFF1A\u6BCF\u6761 = \u4E00\u4E2A\u8FDC\u7AEF\u76EE\u5F55 + \u63D2\u4EF6\u4E3A\u5B83\u5EFA\u7684\u672C\u5730\u951A\u70B9\u76EE\u5F55\u3002\u5220\u6389\u67D0\u6761\u540E\uFF0C\u6B63\u5728\u4F7F\u7528\u8BE5\u951A\u70B9\u7684\u5DE5\u4F5C\u533A\u91CC remote_* \u5DE5\u5177\u4F1A\u7ACB\u523B\u62A5\u300C\u4E0D\u5728\u4EFB\u4F55\u8FDC\u7AEF\u6839\u5185\u300D\uFF1B\u5DE5\u4F5C\u533A\u672C\u8EAB\u4ECD\u5728\u4FA7\u680F\u3002",
49
+ rootsScopeNoSession: "\u6CA1\u6709\u4F1A\u8BDD\u4F5C\u7528\u57DF\uFF1A\u6A21\u578B\u53EA\u5BF9\u843D\u5728\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u5185\u7684\u6839\u53EF\u8FBE\uFF1B\u5176\u4F59\u9700\u5728\u4F1A\u8BDD\u91CC\u70B9\u300C\u6DFB\u52A0\u76EE\u5F55\u300D",
49
50
  rootsEmpty: "\u8FD8\u6CA1\u6709\u914D\u7F6E\u4EFB\u4F55\u6839\u3002",
50
51
  rootsPending: "\u6B63\u5728\u4E0E\u5BBF\u4E3B\u901A\u4FE1\u2026",
51
52
  rootsAuthPassword: "\u7528\u6237\u540D + \u5BC6\u7801",
@@ -167,7 +168,6 @@ var OVER_SSH_DICTS = {
167
168
  rootsImportFailed: "\u5BFC\u5165\u5931\u8D25\uFF1A{message}",
168
169
  transferTitle: "\u4F20\u8F93\u8FDB\u5EA6",
169
170
  transferDescription: "\u5F53\u524D\u4E0E\u6700\u8FD1\u7684\u4F20\u8F93\uFF1A\u901F\u7387\u3001\u5DF2\u5B8C\u6210\u5B57\u8282\u3001\u5269\u4F59\u65F6\u95F4\u3002\u5931\u8D25\u9879\u7559\u5728\u4E0B\u9762\u3002",
170
- remoteFilesHint: "\u8FDC\u7AEF\u6587\u4EF6\u901A\u8FC7\u5BF9\u8BDD\u8BBF\u95EE\uFF1A@ \u8DEF\u5F84\uFF0C\u6216\u8BA9\u6211\u5217\u76EE\u5F55\u3002",
171
171
  transferEmpty: "\u5F53\u524D\u6CA1\u6709\u4F20\u8F93\u3002",
172
172
  transferLoading: "\u6B63\u5728\u8BFB\u53D6\u4F20\u8F93\u72B6\u6001\u2026",
173
173
  transferRefresh: "\u5237\u65B0",
@@ -283,9 +283,10 @@ var OVER_SSH_DICTS = {
283
283
  rootsTitle: "Remote workspaces",
284
284
  // Same correction as the Chinese copy: this page can only remove rows (adding moved to the workspace
285
285
  // directory flow, audit 0027). The scope sentence is the audit-0056 guard and stays.
286
- rootsDescription: "This page only lists the roots the plugin has registered, and lets you remove them; to add a directory, use the workspace directory flow. A root registered here does not appear in a session's directory list automatically: to use it, add it from the session first. Roots declared in the profile are read-only here: deleting one would bring it back after a restart.",
286
+ rootsDescription: "This page only lists the roots the plugin has registered, and lets you remove them; to add a directory, use the workspace directory flow. A root registered here does not appear in a session's directory list automatically: to use it, add it from the session first. The model reaches only roots that fall inside the session workspace (or a remote root whose anchor is that workspace); every other root reports session isolation until a session takes it in. Roots declared in the profile are read-only here: deleting one would bring it back after a restart.",
287
287
  rootsAdvancedSummary: "Advanced: root table (diagnostic)",
288
288
  rootsAdvancedNote: "This is the plugin's root routing table: each row is a remote directory plus the local anchor directory the plugin created for it. Deleting a row makes the remote_* tools fail immediately in any workspace that already uses that anchor; the workspace itself stays in the sidebar.",
289
+ rootsScopeNoSession: "No session scope: the model reaches only roots inside the session workspace; add it from the session otherwise",
289
290
  rootsEmpty: "No root is configured yet.",
290
291
  rootsPending: "Talking to the host\u2026",
291
292
  rootsAuthPassword: "Username + password",
@@ -402,7 +403,6 @@ var OVER_SSH_DICTS = {
402
403
  rootsImportFailed: "Import failed: {message}",
403
404
  transferTitle: "Transfers",
404
405
  transferDescription: "Current and recent transfers: rate, bytes done, time left. Failures stay listed below.",
405
- remoteFilesHint: "Remote files are reached through the conversation: @ a path, or ask me to list a directory.",
406
406
  transferEmpty: "Nothing is transferring.",
407
407
  transferLoading: "Reading transfer state\u2026",
408
408
  transferRefresh: "Refresh",
@@ -510,7 +510,7 @@ var OVER_SSH_DICTS = {
510
510
  };
511
511
 
512
512
  // src/client/panel.ts
513
- var import_react7 = require("react");
513
+ var import_react6 = require("react");
514
514
 
515
515
  // src/client/core/controls.ts
516
516
  var import_react = require("react");
@@ -1356,29 +1356,7 @@ function Checkbox(props) {
1356
1356
  }
1357
1357
 
1358
1358
  // src/client/roots/roots-panel-view.ts
1359
- var import_react6 = require("react");
1360
-
1361
- // src/shared/format.ts
1362
- var BYTE_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
1363
- function formatBytes(bytes) {
1364
- if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
1365
- let value = bytes;
1366
- let unit = 0;
1367
- while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
1368
- value /= 1024;
1369
- unit += 1;
1370
- }
1371
- const label = BYTE_UNITS[unit] ?? "B";
1372
- return unit === 0 ? `${Math.round(value)} ${label}` : `${value.toFixed(1)} ${label}`;
1373
- }
1374
- function formatDuration(ms) {
1375
- if (!Number.isFinite(ms) || ms < 0) return "\u672A\u77E5";
1376
- if (ms < 1e3) return `${Math.round(ms)}ms`;
1377
- const seconds = ms / 1e3;
1378
- if (seconds < 60) return `${seconds.toFixed(1)}s`;
1379
- const minutes = Math.floor(seconds / 60);
1380
- return `${minutes}m${Math.round(seconds - minutes * 60)}s`;
1381
- }
1359
+ var import_react5 = require("react");
1382
1360
 
1383
1361
  // src/client/core/card-focus-trap.ts
1384
1362
  var import_react2 = require("react");
@@ -1526,8 +1504,67 @@ function useCardTabTrap(open, container) {
1526
1504
  }, [open, container]);
1527
1505
  }
1528
1506
 
1529
- // src/client/roots/roots-panel.ts
1507
+ // src/shared/routes.ts
1530
1508
  var ROOTS_API_PATH = "/api/over-ssh/roots";
1509
+ var SESSION_API_PATH = "/api/over-ssh/sessions";
1510
+
1511
+ // src/shared/root-keys.ts
1512
+ function isAbsoluteLocalPath(path) {
1513
+ return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith("/") || path.startsWith("\\\\");
1514
+ }
1515
+ function normalizeLocalPath(path, options = {}) {
1516
+ const caseInsensitive = options.caseInsensitive ?? false;
1517
+ const unified = path.replace(/\\/g, "/");
1518
+ const unc = unified.startsWith("//");
1519
+ const posixAbsolute = unified.startsWith("/");
1520
+ const driveMatch = /^([A-Za-z]):/.exec(unified);
1521
+ const driveLetter = driveMatch?.[1];
1522
+ const drive = driveLetter === void 0 ? "" : `${driveLetter.toUpperCase()}:`;
1523
+ const rest = unc ? unified.slice(2) : drive === "" ? unified : unified.slice(2);
1524
+ const segments = [];
1525
+ for (const segment of rest.split("/")) {
1526
+ if (segment === "" || segment === ".") continue;
1527
+ if (segment === "..") {
1528
+ segments.pop();
1529
+ continue;
1530
+ }
1531
+ segments.push(segment);
1532
+ }
1533
+ const body = segments.join("/");
1534
+ let normalized;
1535
+ if (unc) normalized = `//${body}`;
1536
+ else if (drive !== "") normalized = body === "" ? `${drive}/` : `${drive}/${body}`;
1537
+ else if (posixAbsolute) normalized = body === "" ? "/" : `/${body}`;
1538
+ else normalized = body === "" ? "." : body;
1539
+ return caseInsensitive ? normalized.toLowerCase() : normalized;
1540
+ }
1541
+ function normalizePosixPath(path) {
1542
+ if (!path.startsWith("/")) return void 0;
1543
+ const segments = [];
1544
+ for (const segment of path.split("/")) {
1545
+ if (segment === "" || segment === ".") continue;
1546
+ if (segment === "..") {
1547
+ segments.pop();
1548
+ continue;
1549
+ }
1550
+ segments.push(segment);
1551
+ }
1552
+ return segments.length === 0 ? "/" : `/${segments.join("/")}`;
1553
+ }
1554
+ function posixDirname(normalized) {
1555
+ const index = normalized.lastIndexOf("/");
1556
+ return index <= 0 ? "/" : normalized.slice(0, index);
1557
+ }
1558
+ function rootKeySpelling(location, path, connectionId, options = {}) {
1559
+ if (location === "remote") return `remote:${connectionId ?? ""}:${normalizePosixPath(path) ?? path}`;
1560
+ return `local:${normalizeLocalPath(path, options)}`;
1561
+ }
1562
+ function rootKeyIdentity(key) {
1563
+ const lowered = key.toLowerCase();
1564
+ return lowered.startsWith("local:") ? lowered : key;
1565
+ }
1566
+
1567
+ // src/client/roots/roots-panel.ts
1531
1568
  function readString(source, key) {
1532
1569
  const value = source[key];
1533
1570
  return typeof value === "string" && value !== "" ? value : void 0;
@@ -1549,11 +1586,7 @@ function readBoolean(source, key) {
1549
1586
  return typeof value === "boolean" ? value : void 0;
1550
1587
  }
1551
1588
  function rootKey(location, path, connectionId) {
1552
- return location === "remote" ? `remote:${connectionId ?? ""}:${path}` : `local:${path}`;
1553
- }
1554
- function rootKeyIdentity(key) {
1555
- const lowered = key.toLowerCase();
1556
- return lowered.startsWith("local:") ? lowered : key;
1589
+ return rootKeySpelling(location, path, connectionId);
1557
1590
  }
1558
1591
  function toRootRow(value, source, inUserFile) {
1559
1592
  if (typeof value !== "object" || value === null) return void 0;
@@ -1654,9 +1687,6 @@ function parseRootsSnapshot(value) {
1654
1687
  const anchorRoot = readString(payload, "anchorRoot");
1655
1688
  return { roots, connections, warnings, ...anchorRoot === void 0 ? {} : { anchorRoot } };
1656
1689
  }
1657
- function isAbsoluteLocalPath(path) {
1658
- return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith("/") || path.startsWith("\\\\");
1659
- }
1660
1690
  function isAbsoluteRemotePath(path) {
1661
1691
  if (!path.startsWith("/")) return false;
1662
1692
  return !path.split("/").includes("..");
@@ -1671,7 +1701,8 @@ function validateRootDraft(draft, existing = []) {
1671
1701
  return { ok: false, message: { key: "rootsErrorLocalPath" } };
1672
1702
  }
1673
1703
  const key = rootKey(draft.location, path, draft.location === "remote" ? draft.connectionId : void 0);
1674
- if (existing.some((row) => row.key === key)) return { ok: false, message: { key: "rootsErrorDuplicate" } };
1704
+ const identity = rootKeyIdentity(key);
1705
+ if (existing.some((row) => rootKeyIdentity(row.key) === identity)) return { ok: false, message: { key: "rootsErrorDuplicate" } };
1675
1706
  const label = draft.label?.trim();
1676
1707
  return {
1677
1708
  ok: true,
@@ -1754,6 +1785,9 @@ function buildRootsView(input) {
1754
1785
  title: row.label ?? row.path,
1755
1786
  subtitle: row.location === "remote" ? `${row.connectionId ?? "?"}:${row.path}` : row.path,
1756
1787
  badgeKey: row.location === "remote" ? "rootsBadgeRemote" : "rootsBadgeLocal",
1788
+ // 只有"用户在设置页登记、且没有任何会话作用域"的行才对 AI 不可用;profile 里的根是部署级共享根,
1789
+ // 不属于这个提示的适用范围。
1790
+ ...row.source === "user" && (row.workspaces?.length ?? 0) === 0 && (row.sessions?.length ?? 0) === 0 ? { scopeKey: "rootsScopeNoSession" } : {},
1757
1791
  // Only a root the panel owns can be deleted: a config root comes back after a restart, so offering a
1758
1792
  // delete button for it would be a lie the user cannot see. A root that is both declared and added is
1759
1793
  // deletable — the file entry goes away, which is what the user asked for.
@@ -1878,7 +1912,6 @@ function resolveMessage(copy, message) {
1878
1912
  }
1879
1913
 
1880
1914
  // src/client/roots/roots-panel-state.ts
1881
- var import_react3 = require("react");
1882
1915
  function emptyRootsPanelEditDrafts() {
1883
1916
  return { id: "", host: "", user: "", authKind: "key", keyPath: "", password: "", port: "" };
1884
1917
  }
@@ -2051,10 +2084,32 @@ function resolveMessage2(copy, message) {
2051
2084
  }
2052
2085
 
2053
2086
  // src/client/roots/transfer-panel-view.ts
2054
- var import_react5 = require("react");
2087
+ var import_react4 = require("react");
2088
+
2089
+ // src/shared/format.ts
2090
+ var BYTE_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
2091
+ function formatBytes(bytes) {
2092
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
2093
+ let value = bytes;
2094
+ let unit = 0;
2095
+ while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
2096
+ value /= 1024;
2097
+ unit += 1;
2098
+ }
2099
+ const label = BYTE_UNITS[unit] ?? "B";
2100
+ return unit === 0 ? `${Math.round(value)} ${label}` : `${value.toFixed(1)} ${label}`;
2101
+ }
2102
+ function formatDuration(ms) {
2103
+ if (!Number.isFinite(ms) || ms < 0) return "\u672A\u77E5";
2104
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
2105
+ const seconds = ms / 1e3;
2106
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
2107
+ const minutes = Math.floor(seconds / 60);
2108
+ return `${minutes}m${Math.round(seconds - minutes * 60)}s`;
2109
+ }
2055
2110
 
2056
2111
  // src/client/roots/transfer-panel.ts
2057
- var import_react4 = require("react");
2112
+ var import_react3 = require("react");
2058
2113
  function parseTransferRows(value) {
2059
2114
  const document2 = typeof value === "object" && value !== null ? value : {};
2060
2115
  const rows = [];
@@ -2206,11 +2261,11 @@ function TransferPanelSection(props) {
2206
2261
  const { copy, state, actions, format } = props;
2207
2262
  const rows = visibleTransfers(state.rows);
2208
2263
  const failures = rows.filter((row) => row.status === "failed");
2209
- return (0, import_react4.createElement)(
2264
+ return (0, import_react3.createElement)(
2210
2265
  "section",
2211
2266
  { className: CLASS.stack, "aria-label": copy.title },
2212
- (0, import_react4.createElement)("h3", { className: CLASS.sectionTitle }, copy.title),
2213
- (0, import_react4.createElement)("p", { className: CLASS.description }, copy.description),
2267
+ (0, import_react3.createElement)("h3", { className: CLASS.sectionTitle }, copy.title),
2268
+ (0, import_react3.createElement)("p", { className: CLASS.description }, copy.description),
2214
2269
  state.error === void 0 ? null : Notice({ role: "alert", tone: "error", children: copy.resolve(state.error) }),
2215
2270
  state.clearError === void 0 ? null : Notice({ role: "alert", tone: "error", children: copy.resolve(state.clearError) }),
2216
2271
  state.loading ? Notice({ role: "status", tone: "info", children: copy.loading }) : null,
@@ -2221,17 +2276,17 @@ function TransferPanelSection(props) {
2221
2276
  * *and* the empty line tells the user both "we are reading" and "there is nothing" at once. `loading`
2222
2277
  * therefore suppresses the empty state.
2223
2278
  */
2224
- rows.length > 0 ? (0, import_react4.createElement)(
2279
+ rows.length > 0 ? (0, import_react3.createElement)(
2225
2280
  "ul",
2226
2281
  { className: CLASS.list },
2227
2282
  ...rows.map((row) => {
2228
2283
  const label = row.direction === "download" ? copy.downloadLabel : copy.uploadLabel;
2229
2284
  const summary = transferSummary(row, { bytes: format.bytes, duration: format.duration, label }, copy);
2230
- return (0, import_react4.createElement)(
2285
+ return (0, import_react3.createElement)(
2231
2286
  "li",
2232
2287
  { key: row.id, className: CLASS.row },
2233
- (0, import_react4.createElement)("span", { className: CLASS.rowTitle }, summary),
2234
- (0, import_react4.createElement)("span", { className: CLASS.rowMeta }, `${row.connectionId}:${row.remotePath}`),
2288
+ (0, import_react3.createElement)("span", { className: CLASS.rowTitle }, summary),
2289
+ (0, import_react3.createElement)("span", { className: CLASS.rowMeta }, `${row.connectionId}:${row.remotePath}`),
2235
2290
  /**
2236
2291
  * A native `<progress>` — the platform's own primitive, announced by a screen reader, and a
2237
2292
  * progress bar even if this stylesheet never loads — with its own class for geometry and accent.
@@ -2239,7 +2294,7 @@ function TransferPanelSection(props) {
2239
2294
  * The `aria-label` names **this** transfer (see {@link transferProgressLabel}): every row used to
2240
2295
  * carry `copy.title`, so a screen reader read N identical bars with no way to tell them apart.
2241
2296
  */
2242
- (0, import_react4.createElement)("progress", {
2297
+ (0, import_react3.createElement)("progress", {
2243
2298
  className: CLASS.progress,
2244
2299
  value: Math.round(row.percent * 10),
2245
2300
  max: 1e3,
@@ -2249,29 +2304,29 @@ function TransferPanelSection(props) {
2249
2304
  copy
2250
2305
  )
2251
2306
  }),
2252
- row.status === "running" ? null : (0, import_react4.createElement)(
2307
+ row.status === "running" ? null : (0, import_react3.createElement)(
2253
2308
  "code",
2254
2309
  { className: classes(CLASS.badge, row.status === "failed" ? CLASS.badgeError : row.status === "done" ? CLASS.badgeSuccess : void 0) },
2255
2310
  row.status === "done" ? copy.done : row.status === "cancelled" ? copy.cancelled : copy.failedTitle
2256
2311
  )
2257
2312
  );
2258
2313
  })
2259
- ) : state.loading ? null : (0, import_react4.createElement)("p", { className: CLASS.empty }, copy.empty),
2260
- failures.length === 0 ? null : (0, import_react4.createElement)(
2314
+ ) : state.loading ? null : (0, import_react3.createElement)("p", { className: CLASS.empty }, copy.empty),
2315
+ failures.length === 0 ? null : (0, import_react3.createElement)(
2261
2316
  "div",
2262
2317
  { className: CLASS.block },
2263
- (0, import_react4.createElement)("p", { className: CLASS.hint }, copy.failedTitle),
2264
- (0, import_react4.createElement)(
2318
+ (0, import_react3.createElement)("p", { className: CLASS.hint }, copy.failedTitle),
2319
+ (0, import_react3.createElement)(
2265
2320
  "ul",
2266
2321
  { className: CLASS.list },
2267
2322
  ...failures.map(
2268
- (row) => (0, import_react4.createElement)(
2323
+ (row) => (0, import_react3.createElement)(
2269
2324
  "li",
2270
2325
  { key: `${row.id}-error`, className: CLASS.row },
2271
- (0, import_react4.createElement)("code", { className: classes(CLASS.badge, CLASS.badgeError) }, row.errorCode ?? copy.errorCodeUnknown),
2326
+ (0, import_react3.createElement)("code", { className: classes(CLASS.badge, CLASS.badgeError) }, row.errorCode ?? copy.errorCodeUnknown),
2272
2327
  // The separator is the dictionary's business: a hard-coded full-width colon put CJK punctuation
2273
2328
  // into the English UI (audit 2026-11-01b ui P3-3).
2274
- (0, import_react4.createElement)(
2329
+ (0, import_react3.createElement)(
2275
2330
  "span",
2276
2331
  { className: CLASS.rowMeta },
2277
2332
  resolveTemplate(copy.failureLine, { path: row.remotePath, reason: row.error ?? "" })
@@ -2280,7 +2335,7 @@ function TransferPanelSection(props) {
2280
2335
  )
2281
2336
  )
2282
2337
  ),
2283
- (0, import_react4.createElement)(
2338
+ (0, import_react3.createElement)(
2284
2339
  "div",
2285
2340
  { className: CLASS.form },
2286
2341
  Button({ variant: "secondary", size: "compact", disabled: state.clearing, onClick: () => actions.onRefresh(), children: copy.refresh }),
@@ -2394,20 +2449,32 @@ function createTransferProgress(options) {
2394
2449
  listeners.clear();
2395
2450
  void running;
2396
2451
  },
2452
+ /**
2453
+ * Re-arm after a `dispose()`.
2454
+ *
2455
+ * React's development replay runs an effect's cleanup and then the effect again **on the same controller**
2456
+ * (the instance is kept by `useMemo`). Without `activate()` the second mount's `start()` was silently
2457
+ * ignored — `dispose()` set `disposed = true` and nothing cleared it — so the transfer panel never polled
2458
+ * again for the rest of the session. Every other controller in this half already has this method.
2459
+ */
2460
+ activate: () => {
2461
+ disposed = false;
2462
+ },
2397
2463
  requests: () => issued
2398
2464
  };
2399
2465
  }
2400
2466
 
2401
2467
  // src/client/roots/transfer-panel-view.ts
2402
2468
  function TransferProgressSection({ copy }) {
2403
- const io = (0, import_react5.useMemo)(() => createTransferPanelIo((input, init) => globalThis.fetch(input, init)), []);
2404
- const progress = (0, import_react5.useMemo)(() => createTransferProgress({ io }), [io]);
2405
- const panel = (0, import_react5.useSyncExternalStore)(progress.subscribe, progress.getState);
2406
- (0, import_react5.useEffect)(() => {
2469
+ const io = (0, import_react4.useMemo)(() => createTransferPanelIo((input, init) => globalThis.fetch(input, init)), []);
2470
+ const progress = (0, import_react4.useMemo)(() => createTransferProgress({ io }), [io]);
2471
+ const panel = (0, import_react4.useSyncExternalStore)(progress.subscribe, progress.getState);
2472
+ (0, import_react4.useEffect)(() => {
2473
+ progress.activate();
2407
2474
  progress.start();
2408
2475
  return () => progress.dispose();
2409
2476
  }, [progress]);
2410
- (0, import_react5.useEffect)(injectControlStylesInBrowser, []);
2477
+ (0, import_react4.useEffect)(injectControlStylesInBrowser, []);
2411
2478
  const panelCopy = transferPanelCopy(copy);
2412
2479
  return TransferPanelSection({
2413
2480
  copy: panelCopy,
@@ -2708,15 +2775,16 @@ function renderFormSuccess(copy, state, scope) {
2708
2775
  return Notice({ role: "status", tone: "info", children: resolveMessage2(copy, saved.message) });
2709
2776
  }
2710
2777
  function renderRow(row, copy, editable, onRemove) {
2711
- return (0, import_react6.createElement)(
2778
+ return (0, import_react5.createElement)(
2712
2779
  "li",
2713
2780
  { key: row.key, className: CLASS.row },
2714
- (0, import_react6.createElement)("span", { className: CLASS.rowTitle }, row.title),
2715
- (0, import_react6.createElement)("code", { className: CLASS.badge }, resolveMessage2(copy, { key: row.badgeKey })),
2716
- (0, import_react6.createElement)("span", { className: CLASS.rowMeta }, row.subtitle),
2781
+ (0, import_react5.createElement)("span", { className: CLASS.rowTitle }, row.title),
2782
+ (0, import_react5.createElement)("code", { className: CLASS.badge }, resolveMessage2(copy, { key: row.badgeKey })),
2783
+ (0, import_react5.createElement)("span", { className: CLASS.rowMeta }, row.subtitle),
2784
+ row.scopeKey === void 0 ? null : (0, import_react5.createElement)("span", { className: CLASS.hint }, resolveMessage2(copy, { key: row.scopeKey })),
2717
2785
  // The one and only source of "may this be deleted": the view model's `removable`. A per-row destructive
2718
2786
  // action is the `ghost` variant, so the row stays quiet until the operator means it.
2719
- row.removable ? (0, import_react6.createElement)(
2787
+ row.removable ? (0, import_react5.createElement)(
2720
2788
  "span",
2721
2789
  { className: CLASS.rowActions },
2722
2790
  Button({ variant: "ghost", size: "compact", disabled: !editable, onClick: () => onRemove(row.key), children: copy.rootsRemove })
@@ -2733,7 +2801,7 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2733
2801
  const candidates = listing?.candidates ?? [];
2734
2802
  const showCandidates = candidates.length > 0;
2735
2803
  const showEmpty = listing !== void 0 && candidates.length === 0 && !busy && state.importError === void 0;
2736
- return (0, import_react6.createElement)(
2804
+ return (0, import_react5.createElement)(
2737
2805
  "section",
2738
2806
  {
2739
2807
  className: CLASS.block,
@@ -2750,8 +2818,8 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2750
2818
  actions.onImportClose();
2751
2819
  }
2752
2820
  },
2753
- (0, import_react6.createElement)("h4", { id: `${idPrefix}-import-title`, className: CLASS.blockTitle }, copy.rootsImportTitle),
2754
- (0, import_react6.createElement)("p", { className: CLASS.description }, copy.rootsImportDescription),
2821
+ (0, import_react5.createElement)("h4", { id: `${idPrefix}-import-title`, className: CLASS.blockTitle }, copy.rootsImportTitle),
2822
+ (0, import_react5.createElement)("p", { className: CLASS.description }, copy.rootsImportDescription),
2755
2823
  state.importError === void 0 ? null : Notice({ role: "alert", tone: "error", children: resolveMessage2(copy, { key: "rootsImportFailed", params: { message: state.importError } }) }),
2756
2824
  // A clean import says what it imported; a **partial** one says both numbers and then lists what was refused.
2757
2825
  //
@@ -2762,7 +2830,7 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2762
2830
  report === void 0 ? null : report.refused.length === 0 ? Notice({
2763
2831
  role: "status",
2764
2832
  children: resolveMessage2(copy, { key: "rootsImportDone", params: { count: report.imported.length, ids: report.imported.join(", ") } })
2765
- }) : (0, import_react6.createElement)(
2833
+ }) : (0, import_react5.createElement)(
2766
2834
  "div",
2767
2835
  { className: CLASS.block },
2768
2836
  Notice({
@@ -2772,15 +2840,15 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2772
2840
  params: { imported: report.imported.length, refused: report.refused.length }
2773
2841
  })
2774
2842
  }),
2775
- (0, import_react6.createElement)("p", { className: CLASS.hint }, copy.rootsImportRefusedTitle),
2776
- (0, import_react6.createElement)(
2843
+ (0, import_react5.createElement)("p", { className: CLASS.hint }, copy.rootsImportRefusedTitle),
2844
+ (0, import_react5.createElement)(
2777
2845
  "ul",
2778
2846
  { className: CLASS.list },
2779
2847
  ...report.refused.map(
2780
- (entry) => (0, import_react6.createElement)(
2848
+ (entry) => (0, import_react5.createElement)(
2781
2849
  "li",
2782
2850
  { key: entry.alias, className: CLASS.row },
2783
- (0, import_react6.createElement)(
2851
+ (0, import_react5.createElement)(
2784
2852
  "span",
2785
2853
  { className: CLASS.rowMeta },
2786
2854
  resolveMessage2(copy, { key: "rootsImportRefusedLine", params: { alias: entry.alias, reason: entry.reason } })
@@ -2792,11 +2860,11 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2792
2860
  busy ? Notice({ role: "status", tone: "info", children: copy.rootsImportBusy }) : null,
2793
2861
  // The candidate list. A checkbox per alias, with the id it will become spelled out. While a read is in flight
2794
2862
  // (or after it failed) the notice above is the whole story — see `showEmpty`.
2795
- showCandidates ? (0, import_react6.createElement)(
2863
+ showCandidates ? (0, import_react5.createElement)(
2796
2864
  "ul",
2797
2865
  { className: CLASS.list },
2798
2866
  ...candidates.map(
2799
- (candidate) => (0, import_react6.createElement)(
2867
+ (candidate) => (0, import_react5.createElement)(
2800
2868
  "li",
2801
2869
  { key: candidate.alias, className: CLASS.row },
2802
2870
  Checkbox({
@@ -2806,38 +2874,38 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2806
2874
  disabled: !editable || busy,
2807
2875
  onChange: () => actions.onImportToggle(candidate.alias)
2808
2876
  }),
2809
- (0, import_react6.createElement)(
2877
+ (0, import_react5.createElement)(
2810
2878
  "label",
2811
2879
  { htmlFor: `${idPrefix}-import-${candidate.alias}`, className: CLASS.rowTitle },
2812
2880
  candidate.alias
2813
2881
  ),
2814
- (0, import_react6.createElement)("span", { className: CLASS.rowMeta }, sshConfigCandidateSummary(candidate)),
2815
- (0, import_react6.createElement)("code", { className: CLASS.badge }, candidate.suggestedId),
2882
+ (0, import_react5.createElement)("span", { className: CLASS.rowMeta }, sshConfigCandidateSummary(candidate)),
2883
+ (0, import_react5.createElement)("code", { className: CLASS.badge }, candidate.suggestedId),
2816
2884
  // Flags, not values: the presence of a key or a jump is what changes the resulting connection.
2817
- candidate.hasIdentityFile ? (0, import_react6.createElement)("code", { className: CLASS.badge }, copy.rootsImportHasKey) : null,
2818
- candidate.hasProxyJump ? (0, import_react6.createElement)("code", { className: CLASS.badge }, copy.rootsImportHasJump) : null
2885
+ candidate.hasIdentityFile ? (0, import_react5.createElement)("code", { className: CLASS.badge }, copy.rootsImportHasKey) : null,
2886
+ candidate.hasProxyJump ? (0, import_react5.createElement)("code", { className: CLASS.badge }, copy.rootsImportHasJump) : null
2819
2887
  )
2820
2888
  )
2821
- ) : showEmpty ? (0, import_react6.createElement)("p", { className: CLASS.empty }, copy.rootsImportEmpty) : null,
2822
- (0, import_react6.createElement)("p", { className: CLASS.hint }, copy.rootsImportIdHint),
2889
+ ) : showEmpty ? (0, import_react5.createElement)("p", { className: CLASS.empty }, copy.rootsImportEmpty) : null,
2890
+ (0, import_react5.createElement)("p", { className: CLASS.hint }, copy.rootsImportIdHint),
2823
2891
  // The refusals, with the reason for each.
2824
- listing !== void 0 && listing.skipped.length > 0 ? (0, import_react6.createElement)(
2892
+ listing !== void 0 && listing.skipped.length > 0 ? (0, import_react5.createElement)(
2825
2893
  "div",
2826
2894
  { className: CLASS.block },
2827
- (0, import_react6.createElement)(
2895
+ (0, import_react5.createElement)(
2828
2896
  "p",
2829
2897
  { className: CLASS.hint },
2830
2898
  resolveMessage2(copy, { key: "rootsImportSkippedTitle", params: { count: listing.skipped.length } })
2831
2899
  ),
2832
- (0, import_react6.createElement)(
2900
+ (0, import_react5.createElement)(
2833
2901
  "ul",
2834
2902
  { className: CLASS.list },
2835
2903
  ...listing.skipped.map(
2836
- (entry) => (0, import_react6.createElement)(
2904
+ (entry) => (0, import_react5.createElement)(
2837
2905
  "li",
2838
2906
  { key: `${entry.pattern}-${entry.reason}`, className: CLASS.row },
2839
- (0, import_react6.createElement)("code", { className: CLASS.badge }, entry.pattern),
2840
- (0, import_react6.createElement)(
2907
+ (0, import_react5.createElement)("code", { className: CLASS.badge }, entry.pattern),
2908
+ (0, import_react5.createElement)(
2841
2909
  "span",
2842
2910
  { className: CLASS.rowMeta },
2843
2911
  resolveMessage2(copy, { key: sshConfigSkipKey(entry.reason) })
@@ -2846,7 +2914,7 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2846
2914
  )
2847
2915
  )
2848
2916
  ) : null,
2849
- (0, import_react6.createElement)(
2917
+ (0, import_react5.createElement)(
2850
2918
  "div",
2851
2919
  { className: CLASS.form },
2852
2920
  Button({ variant: "secondary", size: "compact", disabled: !editable || busy, onClick: () => actions.onImportAll(), children: copy.rootsImportSelectAll }),
@@ -2869,7 +2937,7 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
2869
2937
  const editable = canEdit(state);
2870
2938
  const drafts = state.editDrafts;
2871
2939
  const renamed = drafts.id.trim() !== editingId;
2872
- return (0, import_react6.createElement)(
2940
+ return (0, import_react5.createElement)(
2873
2941
  "form",
2874
2942
  {
2875
2943
  onSubmit: (event) => {
@@ -2895,20 +2963,20 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
2895
2963
  actions.onCancelEdit();
2896
2964
  }
2897
2965
  },
2898
- (0, import_react6.createElement)(
2966
+ (0, import_react5.createElement)(
2899
2967
  "div",
2900
2968
  { className: CLASS.dialogHeader },
2901
- (0, import_react6.createElement)("h3", { className: CLASS.sectionTitle }, `${copy.rootsEditTitle}: ${editingId}`)
2969
+ (0, import_react5.createElement)("h3", { className: CLASS.sectionTitle }, `${copy.rootsEditTitle}: ${editingId}`)
2902
2970
  ),
2903
- (0, import_react6.createElement)(
2971
+ (0, import_react5.createElement)(
2904
2972
  "div",
2905
2973
  { className: CLASS.dialogBody },
2906
- (0, import_react6.createElement)(
2974
+ (0, import_react5.createElement)(
2907
2975
  "div",
2908
2976
  { className: CLASS.form },
2909
2977
  // The id is the rename field and carries the note below it, so it spans the whole row instead of
2910
2978
  // pairing with a neighbour that would then sit next to a paragraph.
2911
- (0, import_react6.createElement)(
2979
+ (0, import_react5.createElement)(
2912
2980
  "div",
2913
2981
  { className: CLASS.fieldSpan },
2914
2982
  Field({
@@ -2925,7 +2993,7 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
2925
2993
  ),
2926
2994
  // Only when it is actually being renamed: the note is about a change the user is making, and a
2927
2995
  // permanent paragraph would be noise on every edit.
2928
- renamed ? (0, import_react6.createElement)(
2996
+ renamed ? (0, import_react5.createElement)(
2929
2997
  "p",
2930
2998
  { className: classes(CLASS.hint, CLASS.fieldSpan) },
2931
2999
  resolveMessage2(copy, { key: "rootsEditRenameHint", params: { id: editingId } })
@@ -2995,7 +3063,7 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
2995
3063
  onChange: (event) => actions.onEditDraftChange({ password: fieldValue(event) })
2996
3064
  })
2997
3065
  }) : null,
2998
- drafts.authKind === "password" ? (0, import_react6.createElement)("p", { className: classes(CLASS.hint, CLASS.fieldSpan) }, copy.rootsEditPasswordHint) : null,
3066
+ drafts.authKind === "password" ? (0, import_react5.createElement)("p", { className: classes(CLASS.hint, CLASS.fieldSpan) }, copy.rootsEditPasswordHint) : null,
2999
3067
  Field({
3000
3068
  htmlFor: `${idPrefix}-edit-port`,
3001
3069
  label: copy.rootsPortLabel,
@@ -3012,7 +3080,7 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
3012
3080
  ),
3013
3081
  // The action row is a **sibling of the grid**, not a cell inside it: as a grid item it occupied a single
3014
3082
  // column, which is why the two buttons came out small and jammed under the first field (audit 0090).
3015
- (0, import_react6.createElement)(
3083
+ (0, import_react5.createElement)(
3016
3084
  "div",
3017
3085
  { className: CLASS.actions },
3018
3086
  Button({ variant: "primary", type: "submit", disabled: !editable, children: copy.rootsEditSave }),
@@ -3037,11 +3105,11 @@ function RootsPanelSection({
3037
3105
  const editable = view.canEdit;
3038
3106
  const { drafts } = state;
3039
3107
  const notice = resolveMessage2(copy, view.notice);
3040
- return (0, import_react6.createElement)(
3108
+ return (0, import_react5.createElement)(
3041
3109
  "section",
3042
3110
  { "aria-label": copy.rootsTitle, className: CLASS.stack },
3043
- (0, import_react6.createElement)("h3", { className: CLASS.sectionTitle }, copy.rootsTitle),
3044
- (0, import_react6.createElement)("p", { className: CLASS.description }, copy.rootsDescription),
3111
+ (0, import_react5.createElement)("h3", { className: CLASS.sectionTitle }, copy.rootsTitle),
3112
+ (0, import_react5.createElement)("p", { className: CLASS.description }, copy.rootsDescription),
3045
3113
  // "No host data" / "the host reported warnings": a block, never bare text.
3046
3114
  notice === "" ? null : Notice({ role: "note", children: notice }),
3047
3115
  state.pending ? Notice({ role: "status", tone: "info", children: copy.rootsPending }) : null,
@@ -3051,7 +3119,7 @@ function RootsPanelSection({
3051
3119
  // collapsed diagnostic block at the bottom of this section (see below): it is still the only place a
3052
3120
  // root can be removed from, so it is demoted, never dropped.
3053
3121
  // -- add a connection ----------------------------------------------------
3054
- (0, import_react6.createElement)(
3122
+ (0, import_react5.createElement)(
3055
3123
  "form",
3056
3124
  {
3057
3125
  onSubmit: (event) => {
@@ -3060,8 +3128,8 @@ function RootsPanelSection({
3060
3128
  },
3061
3129
  className: CLASS.block
3062
3130
  },
3063
- (0, import_react6.createElement)("h4", { className: CLASS.blockTitle }, copy.rootsAddConnection),
3064
- (0, import_react6.createElement)(
3131
+ (0, import_react5.createElement)("h4", { className: CLASS.blockTitle }, copy.rootsAddConnection),
3132
+ (0, import_react5.createElement)(
3065
3133
  "div",
3066
3134
  { className: CLASS.form },
3067
3135
  Field({
@@ -3161,18 +3229,16 @@ function RootsPanelSection({
3161
3229
  // -- import from ~/.ssh/config (FR-2.1.4) --------------------------------
3162
3230
  // The button sits next to the add form because it is an alternative way to *create* a connection, and it
3163
3231
  // is disabled for the same reason every other control is (`canEdit`).
3164
- (0, import_react6.createElement)(
3232
+ (0, import_react5.createElement)(
3165
3233
  "div",
3166
3234
  { className: CLASS.block },
3167
3235
  Button({ variant: "secondary", disabled: !editable, onClick: () => actions.onImportOpen(), children: copy.rootsImportOpen })
3168
3236
  ),
3169
3237
  SshConfigImportDialog({ copy, state, actions, idPrefix }),
3170
- // -- remote files are reached through the conversation (audit 0085) -------
3171
- // There is no tree here, and no button that opens one. A file manager inside settings was the wrong
3172
- // artifact: it duplicated the agent's own access, wrote to the remote machine with no audit trail, and
3173
- // competed with the conversation as a second source of truth. The sentence says where the capability
3174
- // actually lives; the tools behind it (`@` a path, or asking for a listing) are the agent's.
3175
- (0, import_react6.createElement)("p", { className: CLASS.description }, copy.remoteFilesHint),
3238
+ // -- no file tree, no entry point (audit 0085) ---------------------------
3239
+ // Settings used to carry a sentence pointing at the conversation. It was a signpost for a capability
3240
+ // that deliberately has no UI; the decision still stands (remote files are the agent's), but the
3241
+ // signpost is gone — the settings section is not the place to teach it.
3176
3242
  // -- transfer progress (FR-2.6.4) ----------------------------------------
3177
3243
  transfers ?? null,
3178
3244
  // -- edit one saved connection (a dialog, not a block in this page) -------
@@ -3185,27 +3251,27 @@ function RootsPanelSection({
3185
3251
  // be removed from: dropping the rows would remove the only cleanup entry point, not just declutter.
3186
3252
  //
3187
3253
  // No `open` prop on purpose: the native default is collapsed, which is exactly the demotion asked for.
3188
- (0, import_react6.createElement)(
3254
+ (0, import_react5.createElement)(
3189
3255
  // The card surface is an **outer div**, not the `<details>` element: `.dsh-rw-block` is `display: flex`, and
3190
3256
  // that changes the disclosure's box model (the marker/`open` rendering is a UA-internal slot) — audit
3191
3257
  // 2026-11-01b ui P3-11. The disclosure keeps its native display and only the card around it is a flex column.
3192
3258
  "div",
3193
3259
  { className: CLASS.block },
3194
- (0, import_react6.createElement)(
3260
+ (0, import_react5.createElement)(
3195
3261
  "details",
3196
3262
  void 0,
3197
- (0, import_react6.createElement)("summary", { className: CLASS.blockTitle }, copy.rootsAdvancedSummary),
3198
- (0, import_react6.createElement)("p", { className: CLASS.description }, copy.rootsAdvancedNote),
3199
- view.rows.length === 0 ? (0, import_react6.createElement)("p", { className: CLASS.empty }, copy.rootsEmpty) : (0, import_react6.createElement)("ul", { className: CLASS.list }, ...view.rows.map((row) => renderRow(row, copy, editable, actions.onRemove)))
3263
+ (0, import_react5.createElement)("summary", { className: CLASS.blockTitle }, copy.rootsAdvancedSummary),
3264
+ (0, import_react5.createElement)("p", { className: CLASS.description }, copy.rootsAdvancedNote),
3265
+ view.rows.length === 0 ? (0, import_react5.createElement)("p", { className: CLASS.empty }, copy.rootsEmpty) : (0, import_react5.createElement)("ul", { className: CLASS.list }, ...view.rows.map((row) => renderRow(row, copy, editable, actions.onRemove)))
3200
3266
  )
3201
3267
  )
3202
3268
  );
3203
3269
  }
3204
3270
  function RootsPanelContainer({ copy, controller }) {
3205
- const state = (0, import_react6.useSyncExternalStore)(controller.subscribe, controller.getState);
3206
- const uid = (0, import_react6.useId)();
3271
+ const state = (0, import_react5.useSyncExternalStore)(controller.subscribe, controller.getState);
3272
+ const uid = (0, import_react5.useId)();
3207
3273
  const idPrefix = `dsh-rw-${uid.replace(/[^A-Za-z0-9_-]/g, "")}`;
3208
- const [portal] = (0, import_react6.useState)(
3274
+ const [portal] = (0, import_react5.useState)(
3209
3275
  () => createCardPortal({
3210
3276
  document: browserPortalDocument(),
3211
3277
  className: ROOTS_EDIT_PORTAL_CLASS,
@@ -3213,16 +3279,16 @@ function RootsPanelContainer({ copy, controller }) {
3213
3279
  render: renderInPortal
3214
3280
  })
3215
3281
  );
3216
- (0, import_react6.useEffect)(() => {
3282
+ (0, import_react5.useEffect)(() => {
3217
3283
  controller.start();
3218
3284
  }, [controller]);
3219
- (0, import_react6.useEffect)(injectControlStylesInBrowser, []);
3220
- (0, import_react6.useEffect)(() => {
3285
+ (0, import_react5.useEffect)(injectControlStylesInBrowser, []);
3286
+ (0, import_react5.useEffect)(() => {
3221
3287
  controller.surfaceAttached();
3222
3288
  return controller.surfaceDetached;
3223
3289
  }, [controller]);
3224
3290
  useCardTabTrap(state.editingId !== void 0, portal.container);
3225
- (0, import_react6.useLayoutEffect)(() => {
3291
+ (0, import_react5.useLayoutEffect)(() => {
3226
3292
  if (state.editingId === void 0) return;
3227
3293
  portal.mount();
3228
3294
  portal.focusCard();
@@ -3230,14 +3296,14 @@ function RootsPanelContainer({ copy, controller }) {
3230
3296
  portal.unmount();
3231
3297
  };
3232
3298
  }, [portal, state.editingId]);
3233
- const sectionActions = (0, import_react6.useMemo)(() => rootsPanelSectionActions(controller), [controller]);
3299
+ const sectionActions = (0, import_react5.useMemo)(() => rootsPanelSectionActions(controller), [controller]);
3234
3300
  return RootsPanelSection({
3235
3301
  copy,
3236
3302
  state,
3237
3303
  idPrefix,
3238
3304
  // No `actions`: the transfer panel owns its own polling and its own buttons (the prop that used to be passed
3239
3305
  // here was typed `unknown` and never read — audit 2026-10-31 client.md §2.4).
3240
- transfers: (0, import_react6.createElement)(TransferProgressSection, { copy, state }),
3306
+ transfers: (0, import_react5.createElement)(TransferProgressSection, { copy, state }),
3241
3307
  // The panel needs the connection list, which this section already read; it does not re-fetch it.
3242
3308
  actions: sectionActions,
3243
3309
  portal
@@ -3247,19 +3313,19 @@ function RootsPanelContainer({ copy, controller }) {
3247
3313
  // src/client/panel.ts
3248
3314
  function renderConnectionRow(row, actions) {
3249
3315
  const { onTest, onEdit, onRemove, disabled } = actions;
3250
- return (0, import_react7.createElement)(
3316
+ return (0, import_react6.createElement)(
3251
3317
  "li",
3252
3318
  { key: row.id, className: CLASS.row },
3253
- (0, import_react7.createElement)("code", { className: CLASS.badge }, row.transportLabel ?? row.transport),
3254
- (0, import_react7.createElement)("span", { className: CLASS.rowTitle }, `${row.id} \xB7 ${row.host}`),
3255
- row.degraded ? (0, import_react7.createElement)("span", { className: CLASS.warnGlyph, "aria-hidden": true }, "(!)") : null,
3256
- row.note === "" ? null : (0, import_react7.createElement)("span", { className: CLASS.rowNote }, row.note),
3319
+ (0, import_react6.createElement)("code", { className: CLASS.badge }, row.transportLabel ?? row.transport),
3320
+ (0, import_react6.createElement)("span", { className: CLASS.rowTitle }, `${row.id} \xB7 ${row.host}`),
3321
+ row.degraded ? (0, import_react6.createElement)("span", { className: CLASS.warnGlyph, "aria-hidden": true }, "(!)") : null,
3322
+ row.note === "" ? null : (0, import_react6.createElement)("span", { className: CLASS.rowNote }, row.note),
3257
3323
  // The probe's result sits beside the row it belongs to: "is this server reachable right now" is a fact
3258
3324
  // about that connection, not a page-level notice.
3259
- row.test === void 0 || row.test.detail === "" ? null : (0, import_react7.createElement)("span", { className: CLASS.description }, row.test.detail),
3325
+ row.test === void 0 || row.test.detail === "" ? null : (0, import_react6.createElement)("span", { className: CLASS.description }, row.test.detail),
3260
3326
  // Everything after this point is an action, so it is pushed to the far edge as one group: a row whose
3261
3327
  // buttons drifted apart as the note grew would be unreadable.
3262
- (0, import_react7.createElement)(
3328
+ (0, import_react6.createElement)(
3263
3329
  "span",
3264
3330
  { className: CLASS.rowActions },
3265
3331
  row.test === void 0 || onTest === void 0 ? null : Button({
@@ -3276,12 +3342,12 @@ function renderConnectionRow(row, actions) {
3276
3342
  );
3277
3343
  }
3278
3344
  function renderRemoveConfirmation(copy, row, remoteRoots, disabled, onCancel, onAccept) {
3279
- return (0, import_react7.createElement)(
3345
+ return (0, import_react6.createElement)(
3280
3346
  "div",
3281
3347
  { role: "alertdialog", "aria-label": copy.title, className: CLASS.block },
3282
- (0, import_react7.createElement)("h4", { className: CLASS.blockTitle }, copy.title),
3283
- (0, import_react7.createElement)("p", { className: CLASS.description }, copy.body(row, remoteRoots)),
3284
- (0, import_react7.createElement)(
3348
+ (0, import_react6.createElement)("h4", { className: CLASS.blockTitle }, copy.title),
3349
+ (0, import_react6.createElement)("p", { className: CLASS.description }, copy.body(row, remoteRoots)),
3350
+ (0, import_react6.createElement)(
3285
3351
  "div",
3286
3352
  { className: CLASS.actions },
3287
3353
  Button({ variant: "primary", size: "compact", disabled, onClick: onAccept, children: copy.acceptLabel }),
@@ -3291,29 +3357,29 @@ function renderRemoveConfirmation(copy, row, remoteRoots, disabled, onCancel, on
3291
3357
  }
3292
3358
  function SshDevPanel({ model, close, roots, onTestConnection, rowState }) {
3293
3359
  const confirming = rowState?.confirmingId === void 0 ? void 0 : model.connections.find((row) => row.id === rowState.confirmingId);
3294
- return (0, import_react7.createElement)(
3360
+ return (0, import_react6.createElement)(
3295
3361
  "section",
3296
3362
  { "aria-label": model.title, className: CLASS.stack },
3297
- (0, import_react7.createElement)(
3363
+ (0, import_react6.createElement)(
3298
3364
  "header",
3299
3365
  { className: CLASS.header },
3300
- (0, import_react7.createElement)("h2", { className: CLASS.sectionTitle }, model.title),
3366
+ (0, import_react6.createElement)("h2", { className: CLASS.sectionTitle }, model.title),
3301
3367
  Button({ variant: "secondary", size: "compact", onClick: close, children: model.closeLabel })
3302
3368
  ),
3303
- (0, import_react7.createElement)("p", { className: CLASS.description }, model.description),
3369
+ (0, import_react6.createElement)("p", { className: CLASS.description }, model.description),
3304
3370
  // The page-level notice belongs **above** the content it explains: it used to be rendered after the whole
3305
3371
  // editor (several screens of forms), so "this page currently shows static copy only" sat at the very bottom,
3306
3372
  // below everything it was about (audit 2026-11-01b ui P2-7).
3307
3373
  model.notice === "" ? null : Notice({ role: "note", children: model.notice }),
3308
- (0, import_react7.createElement)(
3374
+ (0, import_react6.createElement)(
3309
3375
  "div",
3310
3376
  { className: CLASS.block },
3311
- (0, import_react7.createElement)("h3", { className: CLASS.blockTitle }, model.connectionsHeading),
3377
+ (0, import_react6.createElement)("h3", { className: CLASS.blockTitle }, model.connectionsHeading),
3312
3378
  // The list has three states, and they used to be two: rows, or nothing at all. An answered-but-empty
3313
3379
  // list now says so (the block used to vanish, leaving the page silent about whether there were no
3314
3380
  // connections or the section had simply not rendered), and a channel that has not answered keeps the
3315
3381
  // sentence at the top instead of claiming "none yet" at the bottom (audit 2026-11-01b ui P2-7).
3316
- model.connections.length > 0 ? (0, import_react7.createElement)(
3382
+ model.connections.length > 0 ? (0, import_react6.createElement)(
3317
3383
  "ul",
3318
3384
  { className: CLASS.list },
3319
3385
  ...model.connections.map(
@@ -3322,7 +3388,7 @@ function SshDevPanel({ model, close, roots, onTestConnection, rowState }) {
3322
3388
  ...rowState === void 0 ? {} : { onEdit: rowState.onEdit, onRemove: rowState.onRemove, disabled: rowState.disabled }
3323
3389
  })
3324
3390
  )
3325
- ) : model.notice === "" ? (0, import_react7.createElement)("p", { className: CLASS.empty }, model.connectionsEmpty) : null
3391
+ ) : model.notice === "" ? (0, import_react6.createElement)("p", { className: CLASS.empty }, model.connectionsEmpty) : null
3326
3392
  ),
3327
3393
  // The confirmation sits directly under the row list it belongs to, above everything else: it is a
3328
3394
  // question about a row the user just clicked, and it must not be pushed off-screen by the editor below.
@@ -3336,7 +3402,7 @@ function SshDevPanel({ model, close, roots, onTestConnection, rowState }) {
3336
3402
  ),
3337
3403
  // The read-only explanation stays above the editor: it answers "which transport is in force", which the
3338
3404
  // editor's forms do not touch (a new connection only becomes live after a reload).
3339
- roots === void 0 ? null : (0, import_react7.createElement)(RootsPanelContainer, { copy: roots.copy, controller: roots.controller })
3405
+ roots === void 0 ? null : (0, import_react6.createElement)(RootsPanelContainer, { copy: roots.copy, controller: roots.controller })
3340
3406
  );
3341
3407
  }
3342
3408
 
@@ -3440,14 +3506,13 @@ async function openConnectionChannel(ctx, observer) {
3440
3506
  }
3441
3507
 
3442
3508
  // src/client/roots/connection-test.ts
3443
- var DEFAULT_PATH = "/api/over-ssh/roots";
3444
3509
  var CONNECTION_TEST_TIMEOUT_MS = 15e3;
3445
3510
  function isTimeout(error) {
3446
3511
  const name = typeof error === "object" && error !== null ? error.name : void 0;
3447
3512
  return name === "TimeoutError" || name === "AbortError";
3448
3513
  }
3449
3514
  function createConnectionTester(options) {
3450
- const path = options.path ?? DEFAULT_PATH;
3515
+ const path = options.path ?? ROOTS_API_PATH;
3451
3516
  const timeoutMs = options.timeoutMs ?? CONNECTION_TEST_TIMEOUT_MS;
3452
3517
  const listeners = /* @__PURE__ */ new Set();
3453
3518
  let state = /* @__PURE__ */ new Map();
@@ -3530,7 +3595,7 @@ function createConnectionTester(options) {
3530
3595
  }
3531
3596
 
3532
3597
  // src/client/session/session-panel-view.ts
3533
- var import_react8 = require("react");
3598
+ var import_react7 = require("react");
3534
3599
 
3535
3600
  // src/client/core/latest.ts
3536
3601
  function createLatestOnly() {
@@ -3547,7 +3612,6 @@ function createLatestOnly() {
3547
3612
  }
3548
3613
 
3549
3614
  // src/client/session/session-panel.ts
3550
- var SESSION_API_PATH = "/api/over-ssh/sessions";
3551
3615
  function toSessionRowView(raw) {
3552
3616
  const id = raw["id"];
3553
3617
  if (typeof id !== "string" || id === "") return void 0;
@@ -3792,7 +3856,7 @@ function sessionRowTemplate(props) {
3792
3856
  const { row, copy, checked, onToggle, pickIdPrefix } = props;
3793
3857
  const badge = badgeFor(row, copy);
3794
3858
  const stamp = formatStamp(row.updatedAtMs);
3795
- return (0, import_react8.createElement)(
3859
+ return (0, import_react7.createElement)(
3796
3860
  "li",
3797
3861
  { key: row.id, className: CLASS.sessionRow },
3798
3862
  /**
@@ -3800,7 +3864,7 @@ function sessionRowTemplate(props) {
3800
3864
  * Before this the box stood alone (only `aria-label` on the input): clicking the session
3801
3865
  * title did nothing, and a 16px square was the entire target for a two-line row.
3802
3866
  */
3803
- (0, import_react8.createElement)(
3867
+ (0, import_react7.createElement)(
3804
3868
  "label",
3805
3869
  { className: CLASS.checkboxLabel, htmlFor: `${pickIdPrefix}${row.id}` },
3806
3870
  Checkbox({
@@ -3810,44 +3874,44 @@ function sessionRowTemplate(props) {
3810
3874
  onChange: () => onToggle(row.id),
3811
3875
  ariaLabel: row.id
3812
3876
  }),
3813
- (0, import_react8.createElement)(
3877
+ (0, import_react7.createElement)(
3814
3878
  "div",
3815
3879
  { className: CLASS.sessionBody },
3816
- (0, import_react8.createElement)(
3880
+ (0, import_react7.createElement)(
3817
3881
  "div",
3818
3882
  { className: CLASS.sessionHead },
3819
- (0, import_react8.createElement)("span", { className: CLASS.rowTitle, title: row.id }, rowLabel(row, copy)),
3820
- badge === void 0 ? null : (0, import_react8.createElement)(
3883
+ (0, import_react7.createElement)("span", { className: CLASS.rowTitle, title: row.id }, rowLabel(row, copy)),
3884
+ badge === void 0 ? null : (0, import_react7.createElement)(
3821
3885
  "span",
3822
3886
  { className: classes(CLASS.badge, badge.success ? CLASS.badgeSuccess : void 0) },
3823
3887
  badge.text
3824
3888
  )
3825
3889
  ),
3826
- (0, import_react8.createElement)(
3890
+ (0, import_react7.createElement)(
3827
3891
  "div",
3828
3892
  { className: CLASS.sessionMeta },
3829
- (0, import_react8.createElement)("code", { className: CLASS.idChip, title: row.id }, shortId(row.id)),
3830
- stamp === "" ? null : (0, import_react8.createElement)("span", void 0, stamp),
3831
- (0, import_react8.createElement)("span", void 0, formatBytes(row.bytes))
3893
+ (0, import_react7.createElement)("code", { className: CLASS.idChip, title: row.id }, shortId(row.id)),
3894
+ stamp === "" ? null : (0, import_react7.createElement)("span", void 0, stamp),
3895
+ (0, import_react7.createElement)("span", void 0, formatBytes(row.bytes))
3832
3896
  )
3833
3897
  )
3834
3898
  )
3835
3899
  );
3836
3900
  }
3837
- var SessionRow = (0, import_react8.memo)(function SessionRow2(props) {
3901
+ var SessionRow = (0, import_react7.memo)(function SessionRow2(props) {
3838
3902
  return sessionRowTemplate(props);
3839
3903
  });
3840
3904
  function renderBinRow(props) {
3841
3905
  const { row, copy, busy } = props;
3842
- return (0, import_react8.createElement)(
3906
+ return (0, import_react7.createElement)(
3843
3907
  "li",
3844
3908
  { key: row.entryId },
3845
- (0, import_react8.createElement)(
3909
+ (0, import_react7.createElement)(
3846
3910
  "div",
3847
3911
  { className: CLASS.row },
3848
- (0, import_react8.createElement)("code", { className: CLASS.badge }, row.sessionId),
3849
- (0, import_react8.createElement)("span", { className: CLASS.rowMeta }, formatBytes(row.bytes)),
3850
- (0, import_react8.createElement)(
3912
+ (0, import_react7.createElement)("code", { className: CLASS.badge }, row.sessionId),
3913
+ (0, import_react7.createElement)("span", { className: CLASS.rowMeta }, formatBytes(row.bytes)),
3914
+ (0, import_react7.createElement)(
3851
3915
  "span",
3852
3916
  { className: CLASS.rowActions },
3853
3917
  Button({
@@ -3872,14 +3936,14 @@ function renderWorkspaceGroup(props) {
3872
3936
  const { group, copy, selected, busy } = props;
3873
3937
  const remove = workspaceDeleteAction(group);
3874
3938
  const selectedSet = new Set(selected);
3875
- return (0, import_react8.createElement)(
3939
+ return (0, import_react7.createElement)(
3876
3940
  "section",
3877
3941
  { key: group.key, className: CLASS.group },
3878
- (0, import_react8.createElement)(
3942
+ (0, import_react7.createElement)(
3879
3943
  "div",
3880
3944
  { className: CLASS.groupHead },
3881
- (0, import_react8.createElement)("span", { className: CLASS.rowTitle }, group.label === "" ? copy.workspaceUnknown : group.label),
3882
- (0, import_react8.createElement)("span", { className: CLASS.badge }, String(group.sessions.length)),
3945
+ (0, import_react7.createElement)("span", { className: CLASS.rowTitle }, group.label === "" ? copy.workspaceUnknown : group.label),
3946
+ (0, import_react7.createElement)("span", { className: CLASS.badge }, String(group.sessions.length)),
3883
3947
  // A workspace with no directory (the platform's no-cwd bucket) has nothing to scope a delete to, so it
3884
3948
  // gets no button rather than a button that guesses.
3885
3949
  remove === void 0 ? null : Button({
@@ -3890,12 +3954,12 @@ function renderWorkspaceGroup(props) {
3890
3954
  children: copy.deleteWorkspace
3891
3955
  })
3892
3956
  ),
3893
- group.cwd === void 0 ? null : (0, import_react8.createElement)("p", { className: CLASS.hint, title: group.cwd }, group.cwd),
3894
- (0, import_react8.createElement)(
3957
+ group.cwd === void 0 ? null : (0, import_react7.createElement)("p", { className: CLASS.hint, title: group.cwd }, group.cwd),
3958
+ (0, import_react7.createElement)(
3895
3959
  "ul",
3896
3960
  { className: CLASS.list },
3897
3961
  ...group.sessions.map(
3898
- (row) => (0, import_react8.createElement)(SessionRow, {
3962
+ (row) => (0, import_react7.createElement)(SessionRow, {
3899
3963
  // The key belongs to the element the group creates: the boundary is per row, so React has to match "this
3900
3964
  // is the same row as last render" before it compares props.
3901
3965
  key: row.id,
@@ -3922,24 +3986,24 @@ function sessionPanelStatus(phase, copy) {
3922
3986
  function sessionInventoryEmpty(phase, copy) {
3923
3987
  if (phase.kind !== "ready") return null;
3924
3988
  if (phase.snapshot.sessions.length > 0) return null;
3925
- return (0, import_react8.createElement)("p", { className: CLASS.empty }, copy.sessionsEmpty);
3989
+ return (0, import_react7.createElement)("p", { className: CLASS.empty }, copy.sessionsEmpty);
3926
3990
  }
3927
3991
  function SessionPanel({ copy, io, close }) {
3928
- const [phase, setPhase] = (0, import_react8.useState)({ kind: "loading" });
3929
- const [selected, setSelected] = (0, import_react8.useState)([]);
3930
- const [outcome, setOutcome] = (0, import_react8.useState)(void 0);
3931
- const [busy, setBusy] = (0, import_react8.useState)(false);
3932
- const [pending, setPending] = (0, import_react8.useState)(void 0);
3992
+ const [phase, setPhase] = (0, import_react7.useState)({ kind: "loading" });
3993
+ const [selected, setSelected] = (0, import_react7.useState)([]);
3994
+ const [outcome, setOutcome] = (0, import_react7.useState)(void 0);
3995
+ const [busy, setBusy] = (0, import_react7.useState)(false);
3996
+ const [pending, setPending] = (0, import_react7.useState)(void 0);
3933
3997
  const snapshot = phase.kind === "ready" ? phase.snapshot : EMPTY_SNAPSHOT;
3934
- const [reads] = (0, import_react8.useState)(() => createLatestOnly());
3935
- const alive = (0, import_react8.useRef)(true);
3936
- (0, import_react8.useEffect)(() => {
3998
+ const [reads] = (0, import_react7.useState)(() => createLatestOnly());
3999
+ const alive = (0, import_react7.useRef)(true);
4000
+ (0, import_react7.useEffect)(() => {
3937
4001
  alive.current = true;
3938
4002
  return () => {
3939
4003
  alive.current = false;
3940
4004
  };
3941
4005
  }, []);
3942
- const refresh = (0, import_react8.useCallback)(async () => {
4006
+ const refresh = (0, import_react7.useCallback)(async () => {
3943
4007
  const mine = reads.begin();
3944
4008
  setBusy(true);
3945
4009
  try {
@@ -3950,10 +4014,10 @@ function SessionPanel({ copy, io, close }) {
3950
4014
  if (alive.current && reads.isCurrent(mine)) setBusy(false);
3951
4015
  }
3952
4016
  }, [io, reads]);
3953
- (0, import_react8.useEffect)(() => {
4017
+ (0, import_react7.useEffect)(() => {
3954
4018
  void refresh();
3955
4019
  }, [refresh]);
3956
- const run = (0, import_react8.useCallback)(
4020
+ const run = (0, import_react7.useCallback)(
3957
4021
  async (action) => {
3958
4022
  const mine = reads.begin();
3959
4023
  setBusy(true);
@@ -3972,14 +4036,14 @@ function SessionPanel({ copy, io, close }) {
3972
4036
  },
3973
4037
  [io, reads]
3974
4038
  );
3975
- const visible = (0, import_react8.useMemo)(() => visibleSessions(snapshot.sessions), [snapshot.sessions]);
4039
+ const visible = (0, import_react7.useMemo)(() => visibleSessions(snapshot.sessions), [snapshot.sessions]);
3976
4040
  const hidden = hiddenSubsessionCount(snapshot.sessions);
3977
- const deletable = (0, import_react8.useMemo)(
4041
+ const deletable = (0, import_react7.useMemo)(
3978
4042
  () => visible.filter((row) => row.deletable && selected.includes(row.id)),
3979
4043
  [visible, selected]
3980
4044
  );
3981
- const groups = (0, import_react8.useMemo)(() => groupByWorkspace(visible), [visible]);
3982
- const toggle = (0, import_react8.useCallback)((id) => {
4045
+ const groups = (0, import_react7.useMemo)(() => groupByWorkspace(visible), [visible]);
4046
+ const toggle = (0, import_react7.useCallback)((id) => {
3983
4047
  setSelected((current) => current.includes(id) ? current.filter((value) => value !== id) : [...current, id]);
3984
4048
  }, []);
3985
4049
  const acceptPending = () => {
@@ -3993,9 +4057,9 @@ function SessionPanel({ copy, io, close }) {
3993
4057
  });
3994
4058
  };
3995
4059
  const cancelPending = () => setPending(void 0);
3996
- const confirmTitleId = (0, import_react8.useId)();
3997
- const pickIdPrefix = `session-pick-${(0, import_react8.useId)().replace(/[^A-Za-z0-9_-]/g, "")}-`;
3998
- const [portal] = (0, import_react8.useState)(
4060
+ const confirmTitleId = (0, import_react7.useId)();
4061
+ const pickIdPrefix = `session-pick-${(0, import_react7.useId)().replace(/[^A-Za-z0-9_-]/g, "")}-`;
4062
+ const [portal] = (0, import_react7.useState)(
3999
4063
  () => createCardPortal({
4000
4064
  document: browserPortalDocument(),
4001
4065
  className: CONFIRM_PORTAL_CLASS,
@@ -4003,10 +4067,10 @@ function SessionPanel({ copy, io, close }) {
4003
4067
  render: renderInPortal
4004
4068
  })
4005
4069
  );
4006
- const focusOrigin = (0, import_react8.useRef)(void 0);
4070
+ const focusOrigin = (0, import_react7.useRef)(void 0);
4007
4071
  const confirmationOpen = pending !== void 0;
4008
- (0, import_react8.useEffect)(injectControlStylesInBrowser, []);
4009
- (0, import_react8.useLayoutEffect)(() => {
4072
+ (0, import_react7.useEffect)(injectControlStylesInBrowser, []);
4073
+ (0, import_react7.useLayoutEffect)(() => {
4010
4074
  if (!confirmationOpen) return;
4011
4075
  focusOrigin.current = captureFocusOrigin(browserPortalDocument());
4012
4076
  portal.mount();
@@ -4029,21 +4093,21 @@ function SessionPanel({ copy, io, close }) {
4029
4093
  );
4030
4094
  return portal.container === void 0 ? card : portal.render(card, portal.container);
4031
4095
  };
4032
- return (0, import_react8.createElement)(
4096
+ return (0, import_react7.createElement)(
4033
4097
  "section",
4034
4098
  { "aria-label": copy.title, className: CLASS.stack },
4035
- (0, import_react8.createElement)(
4099
+ (0, import_react7.createElement)(
4036
4100
  "header",
4037
4101
  { className: CLASS.header },
4038
- (0, import_react8.createElement)("h2", { className: CLASS.sectionTitle }, copy.title),
4039
- (0, import_react8.createElement)(
4102
+ (0, import_react7.createElement)("h2", { className: CLASS.sectionTitle }, copy.title),
4103
+ (0, import_react7.createElement)(
4040
4104
  "div",
4041
4105
  { className: CLASS.headerActions },
4042
4106
  Button({ variant: "secondary", size: "compact", onClick: () => void refresh(), children: copy.refresh }),
4043
4107
  Button({ variant: "secondary", size: "compact", onClick: close, children: copy.close })
4044
4108
  )
4045
4109
  ),
4046
- (0, import_react8.createElement)("p", { className: CLASS.description }, copy.description),
4110
+ (0, import_react7.createElement)("p", { className: CLASS.description }, copy.description),
4047
4111
  sessionPanelStatus(phase, copy),
4048
4112
  outcome === void 0 ? null : Notice({
4049
4113
  role: outcome.kind === "ok" ? "status" : "alert",
@@ -4051,12 +4115,12 @@ function SessionPanel({ copy, io, close }) {
4051
4115
  children: copy.resolve(outcome.message)
4052
4116
  }),
4053
4117
  // --- the inventory ---------------------------------------------------------------------------
4054
- (0, import_react8.createElement)("h3", { className: CLASS.blockTitle }, copy.sessionsHeading),
4118
+ (0, import_react7.createElement)("h3", { className: CLASS.blockTitle }, copy.sessionsHeading),
4055
4119
  // No totals from an empty snapshot: 「0 个会话…共 0 B」 next to 「正在读取会话…」 was half of the first-paint lie.
4056
- phase.kind === "ready" ? (0, import_react8.createElement)("p", { className: CLASS.description }, copy.totals(snapshot)) : null,
4057
- hidden === 0 ? null : (0, import_react8.createElement)("p", { className: CLASS.hint }, copy.subsessionsHidden(hidden)),
4120
+ phase.kind === "ready" ? (0, import_react7.createElement)("p", { className: CLASS.description }, copy.totals(snapshot)) : null,
4121
+ hidden === 0 ? null : (0, import_react7.createElement)("p", { className: CLASS.hint }, copy.subsessionsHidden(hidden)),
4058
4122
  sessionInventoryEmpty(phase, copy),
4059
- snapshot.sessions.length === 0 ? null : (0, import_react8.createElement)(
4123
+ snapshot.sessions.length === 0 ? null : (0, import_react7.createElement)(
4060
4124
  "div",
4061
4125
  { className: CLASS.block },
4062
4126
  /**
@@ -4065,7 +4129,7 @@ function SessionPanel({ copy, io, close }) {
4065
4129
  * to scroll back up for every action. `position: sticky` keeps it inside the panel's own scroll box
4066
4130
  * (the card is the scrolling ancestor, not the window), so it follows the list without covering it.
4067
4131
  */
4068
- (0, import_react8.createElement)(
4132
+ (0, import_react7.createElement)(
4069
4133
  "div",
4070
4134
  { className: CLASS.actionBar },
4071
4135
  Button({
@@ -4090,7 +4154,7 @@ function SessionPanel({ copy, io, close }) {
4090
4154
  children: copy.deletePermanent
4091
4155
  })
4092
4156
  ),
4093
- (0, import_react8.createElement)(
4157
+ (0, import_react7.createElement)(
4094
4158
  "div",
4095
4159
  { className: CLASS.list },
4096
4160
  ...groups.map(
@@ -4107,13 +4171,13 @@ function SessionPanel({ copy, io, close }) {
4107
4171
  )
4108
4172
  ),
4109
4173
  // --- the bin --------------------------------------------------------------------------------
4110
- (0, import_react8.createElement)("h3", { className: CLASS.blockTitle }, copy.binHeading),
4174
+ (0, import_react7.createElement)("h3", { className: CLASS.blockTitle }, copy.binHeading),
4111
4175
  // Same rule as the inventory: 「回收站为空」 is a claim about an **answer**. While nothing has been read (or the
4112
4176
  // read failed) the status line above says what is happening (audit 2026-11-01b ui P1-3).
4113
- phase.kind !== "ready" ? null : snapshot.bin.length === 0 ? (0, import_react8.createElement)("p", { className: CLASS.empty }, copy.binEmpty) : (0, import_react8.createElement)(
4177
+ phase.kind !== "ready" ? null : snapshot.bin.length === 0 ? (0, import_react7.createElement)("p", { className: CLASS.empty }, copy.binEmpty) : (0, import_react7.createElement)(
4114
4178
  "div",
4115
4179
  { className: CLASS.block },
4116
- (0, import_react8.createElement)(
4180
+ (0, import_react7.createElement)(
4117
4181
  "ul",
4118
4182
  { className: CLASS.list },
4119
4183
  ...snapshot.bin.map(
@@ -4139,7 +4203,7 @@ function SessionPanel({ copy, io, close }) {
4139
4203
  );
4140
4204
  }
4141
4205
  function renderConfirmationDialog(pending, copy, handlers, titleId, loadedIdle = 0) {
4142
- return (0, import_react8.createElement)(
4206
+ return (0, import_react7.createElement)(
4143
4207
  "div",
4144
4208
  {
4145
4209
  className: CLASS.dialog,
@@ -4152,9 +4216,9 @@ function renderConfirmationDialog(pending, copy, handlers, titleId, loadedIdle =
4152
4216
  handlers.cancel();
4153
4217
  }
4154
4218
  },
4155
- (0, import_react8.createElement)("h3", { id: titleId, className: CLASS.dialogHeader }, copy.confirmTitle),
4156
- (0, import_react8.createElement)("p", { className: CLASS.dialogBody }, confirmationBody(pending, copy, loadedIdle)),
4157
- (0, import_react8.createElement)(
4219
+ (0, import_react7.createElement)("h3", { id: titleId, className: CLASS.dialogHeader }, copy.confirmTitle),
4220
+ (0, import_react7.createElement)("p", { className: CLASS.dialogBody }, confirmationBody(pending, copy, loadedIdle)),
4221
+ (0, import_react7.createElement)(
4158
4222
  "div",
4159
4223
  { className: CLASS.actions },
4160
4224
  Button({ variant: "primary", size: "compact", onClick: handlers.accept, children: copy.confirmAccept }),
@@ -4180,7 +4244,7 @@ function loadedIdleIds(pending, rows) {
4180
4244
  }
4181
4245
 
4182
4246
  // src/client/session/session-row-actions.ts
4183
- var import_react9 = require("react");
4247
+ var import_react8 = require("react");
4184
4248
  var SESSION_MENU_SLOT = "sidebar.workspaces.session.menu.item";
4185
4249
  var SESSION_ROW_RECYCLE_ID = "over-ssh-session-recycle";
4186
4250
  var SESSION_ROW_RECYCLE_ORDER = 500;
@@ -4296,7 +4360,7 @@ function sessionRowMenuEntry(kind) {
4296
4360
  return kind === "recycle" ? { itemId: SESSION_ROW_RECYCLE_ID, danger: false } : { itemId: SESSION_ROW_DELETE_ID, danger: true };
4297
4361
  }
4298
4362
  function SessionRowMenuItem(props) {
4299
- return (0, import_react9.createElement)(
4363
+ return (0, import_react8.createElement)(
4300
4364
  "button",
4301
4365
  {
4302
4366
  type: "button",
@@ -4364,7 +4428,7 @@ function SessionRowActionDialog({
4364
4428
  onClose,
4365
4429
  titleId = SESSION_ROW_ACTIONS_TITLE_ID
4366
4430
  }) {
4367
- return (0, import_react9.createElement)(
4431
+ return (0, import_react8.createElement)(
4368
4432
  "div",
4369
4433
  {
4370
4434
  // The shared confirmation shell: the same surface and radius as the settings panel's confirmation, instead
@@ -4379,8 +4443,8 @@ function SessionRowActionDialog({
4379
4443
  onClose();
4380
4444
  }
4381
4445
  },
4382
- (0, import_react9.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, view.title),
4383
- view.body === "" ? null : (0, import_react9.createElement)("p", { className: CLASS.description }, view.body),
4446
+ (0, import_react8.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, view.title),
4447
+ view.body === "" ? null : (0, import_react8.createElement)("p", { className: CLASS.description }, view.body),
4384
4448
  view.warning === "" ? null : Notice({ role: "note", tone: "warn", children: view.warning }),
4385
4449
  view.blocked === "" ? null : Notice({ role: "alert", tone: "error", children: view.blocked }),
4386
4450
  view.outcome === "" ? null : Notice({
@@ -4388,7 +4452,7 @@ function SessionRowActionDialog({
4388
4452
  tone: view.outcomeTone,
4389
4453
  children: view.outcome
4390
4454
  }),
4391
- (0, import_react9.createElement)(
4455
+ (0, import_react8.createElement)(
4392
4456
  "div",
4393
4457
  { className: CLASS.actions },
4394
4458
  view.buttons === "confirm" ? Button({
@@ -4404,11 +4468,11 @@ function SessionRowActionDialog({
4404
4468
  }
4405
4469
  function SessionRowActionsOverlay(props) {
4406
4470
  const { copy, store } = props;
4407
- const state = (0, import_react9.useSyncExternalStore)(store.subscribe, store.getState);
4471
+ const state = (0, import_react8.useSyncExternalStore)(store.subscribe, store.getState);
4408
4472
  const view = describeSessionRowAction(state, copy);
4409
4473
  const open = view !== void 0;
4410
- const titleId = (0, import_react9.useId)();
4411
- const [portal] = (0, import_react9.useState)(
4474
+ const titleId = (0, import_react8.useId)();
4475
+ const [portal] = (0, import_react8.useState)(
4412
4476
  () => createCardPortal({
4413
4477
  document: browserPortalDocument(),
4414
4478
  className: CONFIRM_PORTAL_CLASS,
@@ -4416,18 +4480,18 @@ function SessionRowActionsOverlay(props) {
4416
4480
  render: renderInPortal
4417
4481
  })
4418
4482
  );
4419
- const focusOrigin = (0, import_react9.useRef)(void 0);
4420
- (0, import_react9.useEffect)(injectControlStylesInBrowser, []);
4483
+ const focusOrigin = (0, import_react8.useRef)(void 0);
4484
+ (0, import_react8.useEffect)(injectControlStylesInBrowser, []);
4421
4485
  const queryKey = actionQueryKey(state);
4422
- (0, import_react9.useEffect)(() => {
4486
+ (0, import_react8.useEffect)(() => {
4423
4487
  if (queryKey === "closed") return;
4424
4488
  void store.load();
4425
4489
  }, [store, queryKey]);
4426
- (0, import_react9.useEffect)(() => {
4490
+ (0, import_react8.useEffect)(() => {
4427
4491
  store.activate();
4428
4492
  return () => store.dispose();
4429
4493
  }, [store]);
4430
- (0, import_react9.useLayoutEffect)(() => {
4494
+ (0, import_react8.useLayoutEffect)(() => {
4431
4495
  if (!open) return;
4432
4496
  focusOrigin.current = captureFocusOrigin(browserPortalDocument());
4433
4497
  portal.mount();
@@ -4450,7 +4514,7 @@ function SessionRowActionsOverlay(props) {
4450
4514
  }
4451
4515
 
4452
4516
  // src/client/session/session-remote-mark.ts
4453
- var import_react10 = require("react");
4517
+ var import_react9 = require("react");
4454
4518
  var REMOTE_MARK_SLOT = "sidebar.session.row.leading";
4455
4519
  var REMOTE_MARK_ID = "over-ssh-remote-mark";
4456
4520
  var REMOTE_MARK_ORDER = 5;
@@ -4477,7 +4541,7 @@ function RemoteSessionMark(props) {
4477
4541
  if (sessionId === void 0) return null;
4478
4542
  if (props.isRemoteSession?.(sessionId) !== true) return null;
4479
4543
  const label = props.label ?? "";
4480
- return (0, import_react10.createElement)(
4544
+ return (0, import_react9.createElement)(
4481
4545
  "span",
4482
4546
  {
4483
4547
  className: CLASS.remoteMark,
@@ -4488,25 +4552,25 @@ function RemoteSessionMark(props) {
4488
4552
  // marks without depending on the icon's geometry.
4489
4553
  "data-dsh-rw-remote-mark": sessionId
4490
4554
  },
4491
- (0, import_react10.createElement)(
4555
+ (0, import_react9.createElement)(
4492
4556
  "svg",
4493
4557
  { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", focusable: "false" },
4494
4558
  // A globe in three strokes: outline, equator, and the two meridian arcs. Same geometry the reference
4495
4559
  // third-party plugin draws, minus its five-arc fidelity; `currentColor` is what keeps it legible in
4496
4560
  // both themes (the reference implementation shipped a hardcoded white stroke and was invisible in the
4497
4561
  // light theme — see its CHANGELOG).
4498
- (0, import_react10.createElement)("circle", { cx: "8", cy: "8", r: "6", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
4499
- (0, import_react10.createElement)("path", { d: "M2 8h12", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
4500
- (0, import_react10.createElement)("path", { d: "M8 2c2.4 2.4 2.4 9.6 0 12M8 2C5.6 4.4 5.6 11.6 8 14", fill: "none", stroke: "currentColor", strokeWidth: "1.2" })
4562
+ (0, import_react9.createElement)("circle", { cx: "8", cy: "8", r: "6", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
4563
+ (0, import_react9.createElement)("path", { d: "M2 8h12", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
4564
+ (0, import_react9.createElement)("path", { d: "M8 2c2.4 2.4 2.4 9.6 0 12M8 2C5.6 4.4 5.6 11.6 8 14", fill: "none", stroke: "currentColor", strokeWidth: "1.2" })
4501
4565
  )
4502
4566
  );
4503
4567
  }
4504
4568
 
4505
4569
  // src/client/flows/session-directory.ts
4506
- var import_react12 = require("react");
4570
+ var import_react11 = require("react");
4507
4571
 
4508
4572
  // src/client/flows/directory-browser.ts
4509
- var import_react11 = require("react");
4573
+ var import_react10 = require("react");
4510
4574
  var GIT_STATUS_API_PATH = "/api/over-ssh/git-status";
4511
4575
  var GIT_STATUS_BADGE = {
4512
4576
  modified: "M",
@@ -4548,11 +4612,12 @@ function postFailureReason(parsed, status, path) {
4548
4612
  const error = parsed?.["error"];
4549
4613
  return typeof error === "string" ? error : transportFailureReason(status, path);
4550
4614
  }
4551
- async function postRootsJson(fetchImpl, path, body) {
4615
+ async function postRootsJson(fetchImpl, path, body, signal) {
4552
4616
  try {
4553
4617
  const response = await fetchImpl(path, {
4554
4618
  method: "POST",
4555
4619
  headers: { "content-type": "application/json" },
4620
+ ...signal === void 0 ? {} : { signal },
4556
4621
  body: JSON.stringify(body)
4557
4622
  });
4558
4623
  let parsed;
@@ -4576,10 +4641,10 @@ function createDirectoryBrowserIo(options) {
4576
4641
  const result = await rootsApi.load();
4577
4642
  return result.ok ? { ok: true, value: result.value.connections } : { ok: false, error: result.error };
4578
4643
  };
4579
- const listLocal = async (target) => {
4644
+ const listLocal = async (target, signal) => {
4580
4645
  const body = { action: "list-local" };
4581
4646
  if (target !== void 0 && target !== "") body["path"] = target;
4582
- const result = await postRootsJson(options.fetchImpl, path, body);
4647
+ const result = await postRootsJson(options.fetchImpl, path, body, signal);
4583
4648
  if (!result.ok) return result;
4584
4649
  const answer = result.value;
4585
4650
  const answerPath = answer["path"];
@@ -4600,9 +4665,9 @@ function createDirectoryBrowserIo(options) {
4600
4665
  }
4601
4666
  return { ok: true, value: { path: answerPath, crumbs, entries, truncated: answer["truncated"] === true } };
4602
4667
  };
4603
- const listRemote = async (input) => {
4668
+ const listRemote = async (input, signal) => {
4604
4669
  if (input.connectionId === "") return { ok: false, error: NO_CONNECTION_ERROR };
4605
- const result = await postRootsJson(options.fetchImpl, path, { action: "list-remote", connectionId: input.connectionId, path: input.path });
4670
+ const result = await postRootsJson(options.fetchImpl, path, { action: "list-remote", connectionId: input.connectionId, path: input.path }, signal);
4606
4671
  if (!result.ok) return result;
4607
4672
  const raw = result.value["entries"];
4608
4673
  const entries = [];
@@ -4616,9 +4681,9 @@ function createDirectoryBrowserIo(options) {
4616
4681
  const answerPath = typeof result.value["path"] === "string" && result.value["path"] !== "" ? result.value["path"] : input.path;
4617
4682
  return { ok: true, value: { path: answerPath, entries, truncated: result.value["truncated"] === true } };
4618
4683
  };
4619
- const loadGitStatus = async (input) => {
4684
+ const loadGitStatus = async (input, signal) => {
4620
4685
  if (input.connectionId === "" || input.path === "") return { ok: false, error: NO_CONNECTION_ERROR };
4621
- const result = await postRootsJson(options.fetchImpl, GIT_STATUS_API_PATH, { connectionId: input.connectionId, path: input.path });
4686
+ const result = await postRootsJson(options.fetchImpl, GIT_STATUS_API_PATH, { connectionId: input.connectionId, path: input.path }, signal);
4622
4687
  if (!result.ok) return result;
4623
4688
  return { ok: true, value: parseGitStatusView(result.value) };
4624
4689
  };
@@ -4700,11 +4765,14 @@ function directoryBrowserReducer(state, action) {
4700
4765
  }))
4701
4766
  };
4702
4767
  case "git-loaded": {
4703
- if (action.view.available !== true) return { ...withoutOptional(state) };
4768
+ if (action.view.available !== true) {
4769
+ const { git: _drop, ...rest } = state;
4770
+ return rest;
4771
+ }
4704
4772
  const changes = {};
4705
4773
  for (const change of action.view.changes) changes[change.path] = change.status;
4706
4774
  return {
4707
- ...withoutOptional(state),
4775
+ ...state,
4708
4776
  git: {
4709
4777
  root: action.view.root ?? "",
4710
4778
  directory: action.view.directory ?? "",
@@ -4721,10 +4789,9 @@ function directoryBrowserReducer(state, action) {
4721
4789
  }
4722
4790
  }
4723
4791
  function parentRemotePath(path) {
4724
- const trimmed = path.replace(/\/+$/, "");
4725
- if (trimmed === "") return void 0;
4726
- const at = trimmed.lastIndexOf("/");
4727
- return at <= 0 ? "/" : trimmed.slice(0, at);
4792
+ const normalized = normalizePosixPath(path);
4793
+ if (normalized === void 0 || normalized === "/") return void 0;
4794
+ return posixDirname(normalized);
4728
4795
  }
4729
4796
  function remoteChildPath(base, name) {
4730
4797
  const trimmed = base.replace(/\/+$/, "");
@@ -4778,6 +4845,13 @@ function createDirectoryBrowser(deps) {
4778
4845
  };
4779
4846
  const listeners = /* @__PURE__ */ new Set();
4780
4847
  let token = 0;
4848
+ let inFlight;
4849
+ const beginRequest = () => {
4850
+ inFlight?.abort();
4851
+ const controller = new AbortController();
4852
+ inFlight = controller;
4853
+ return controller.signal;
4854
+ };
4781
4855
  let gitToken = 0;
4782
4856
  let disposed = false;
4783
4857
  const dispatch = (action) => {
@@ -4788,20 +4862,21 @@ function createDirectoryBrowser(deps) {
4788
4862
  };
4789
4863
  const current = (issued) => !disposed && issued === token;
4790
4864
  const currentGit = (issued) => !disposed && issued === gitToken;
4791
- const loadGit = async (connectionId, path) => {
4865
+ const loadGit = async (connectionId, path, signal) => {
4792
4866
  const load = deps.io.loadGitStatus;
4793
4867
  if (load === void 0 || connectionId === "" || path === "") return;
4794
4868
  const issued = ++gitToken;
4795
- const result = await load({ connectionId, path });
4869
+ const result = await load({ connectionId, path }, signal);
4796
4870
  if (!currentGit(issued)) return;
4797
4871
  if (!result.ok) return;
4798
4872
  dispatch({ type: "git-loaded", view: result.value });
4799
4873
  };
4800
4874
  const readLocal = async (path) => {
4875
+ const signal = beginRequest();
4801
4876
  const issued = ++token;
4802
4877
  gitToken += 1;
4803
4878
  dispatch({ type: "target", path: path ?? "" });
4804
- const result = await deps.io.listLocal(path === void 0 || path === "" ? void 0 : path);
4879
+ const result = await deps.io.listLocal(path === void 0 || path === "" ? void 0 : path, signal);
4805
4880
  if (!current(issued)) return;
4806
4881
  if (!result.ok) {
4807
4882
  dispatch({ type: "failed", error: result.error });
@@ -4810,17 +4885,18 @@ function createDirectoryBrowser(deps) {
4810
4885
  dispatch({ type: "local-loaded", listing: result.value });
4811
4886
  };
4812
4887
  const readRemote = async (connectionId, path) => {
4888
+ const signal = beginRequest();
4813
4889
  const issued = ++token;
4814
4890
  gitToken += 1;
4815
4891
  dispatch({ type: "target", path });
4816
- const result = await deps.io.listRemote({ connectionId, path });
4892
+ const result = await deps.io.listRemote({ connectionId, path }, signal);
4817
4893
  if (!current(issued)) return;
4818
4894
  if (!result.ok) {
4819
4895
  dispatch({ type: "failed", error: result.error });
4820
4896
  return;
4821
4897
  }
4822
4898
  dispatch({ type: "remote-loaded", path, listing: result.value });
4823
- void loadGit(connectionId, result.value.path);
4899
+ void loadGit(connectionId, result.value.path, signal);
4824
4900
  };
4825
4901
  const ensureConnections = async () => {
4826
4902
  if (state.connections.length > 0) return true;
@@ -4893,6 +4969,8 @@ function createDirectoryBrowser(deps) {
4893
4969
  },
4894
4970
  dispose: () => {
4895
4971
  disposed = true;
4972
+ inFlight?.abort();
4973
+ inFlight = void 0;
4896
4974
  listeners.clear();
4897
4975
  }
4898
4976
  };
@@ -4901,7 +4979,7 @@ var DEFAULT_DIRECTORY_BROWSER_ID_PREFIX = "dsh-rw-browse";
4901
4979
  function gitStatusBadgeElement(row, view) {
4902
4980
  const status = gitBadgeForRow(view, row);
4903
4981
  if (status === void 0) return null;
4904
- return (0, import_react11.createElement)("span", { className: CLASS.badge, title: status }, GIT_STATUS_BADGE[status]);
4982
+ return (0, import_react10.createElement)("span", { className: CLASS.badge, title: status }, GIT_STATUS_BADGE[status]);
4905
4983
  }
4906
4984
  function failureText(copy, reason) {
4907
4985
  if (reason === NO_LOCAL_CAPABILITY_REASON) return resolveMessage2(copy, { key: "browseUnavailable" });
@@ -4913,7 +4991,7 @@ function failureText(copy, reason) {
4913
4991
  }
4914
4992
  function connectionListNotice(copy, view, gate, actions) {
4915
4993
  if (view.connectionsError !== void 0) {
4916
- return (0, import_react11.createElement)(
4994
+ return (0, import_react10.createElement)(
4917
4995
  "div",
4918
4996
  { className: CLASS.block },
4919
4997
  Notice({
@@ -4950,10 +5028,10 @@ function DirectoryBrowserSection({
4950
5028
  onClick: () => actions.onModeChange(mode),
4951
5029
  children: label
4952
5030
  });
4953
- return (0, import_react11.createElement)(
5031
+ return (0, import_react10.createElement)(
4954
5032
  "div",
4955
5033
  { className: CLASS.browser, role: "group", "aria-label": copy.browseHeading },
4956
- (0, import_react11.createElement)(
5034
+ (0, import_react10.createElement)(
4957
5035
  "div",
4958
5036
  { className: CLASS.actions },
4959
5037
  fixedMode ? null : modeButton("local", copy.browseLocalMode),
@@ -4965,7 +5043,7 @@ function DirectoryBrowserSection({
4965
5043
  Button({ variant: "secondary", size: "compact", disabled: gate, onClick: actions.onHome, children: copy.browseHome })
4966
5044
  ),
4967
5045
  // -- the remote world needs a connection before anything can be listed -----
4968
- view.needsConnection && view.connections.length > 0 ? (0, import_react11.createElement)(
5046
+ view.needsConnection && view.connections.length > 0 ? (0, import_react10.createElement)(
4969
5047
  "div",
4970
5048
  { className: CLASS.form },
4971
5049
  Field({
@@ -4995,10 +5073,10 @@ function DirectoryBrowserSection({
4995
5073
  // 「浏览…」跟当前位置放在一起,而不是跟模式切换挤在一行:它是**对当前目录**的动作,
4996
5074
  // 放在本机/远端旁边会被读成第三种模式,还会把「主目录」挤到第二行。
4997
5075
  // 远端不画——远端目录在这台机器上没有系统框可选。
4998
- (0, import_react11.createElement)(
5076
+ (0, import_react10.createElement)(
4999
5077
  "div",
5000
5078
  { className: CLASS.actions },
5001
- (0, import_react11.createElement)("p", { className: CLASS.browserPath }, resolveMessage2(copy, { key: "browseCurrent", params: { path: view.path } })),
5079
+ (0, import_react10.createElement)("p", { className: CLASS.browserPath }, resolveMessage2(copy, { key: "browseCurrent", params: { path: view.path } })),
5002
5080
  state.mode !== "local" || actions.onPickNative === void 0 ? null : Button({
5003
5081
  variant: "secondary",
5004
5082
  size: "compact",
@@ -5009,21 +5087,21 @@ function DirectoryBrowserSection({
5009
5087
  ),
5010
5088
  // Branch line: only when the host could name one (detached HEAD / no git / read failure → nothing is shown,
5011
5089
  // never a guessed name). The glyph is language-neutral; the branch name is the host's own string.
5012
- view.gitBranch === void 0 ? null : (0, import_react11.createElement)(
5090
+ view.gitBranch === void 0 ? null : (0, import_react10.createElement)(
5013
5091
  "p",
5014
5092
  { className: CLASS.browserPath, "aria-label": `git branch ${view.gitBranch}` },
5015
5093
  `\u2387 ${view.gitBranch}${view.gitTruncated ? " \xB7 \u2026" : ""}`
5016
5094
  ),
5017
5095
  view.error === void 0 ? null : Notice({ role: "alert", tone: "error", children: failureText(copy, view.error) }),
5018
5096
  view.pending ? Notice({ role: "status", tone: "info", children: copy.browseLoading }) : null,
5019
- view.empty ? (0, import_react11.createElement)("p", { className: CLASS.empty }, copy.browseEmpty) : null,
5020
- (0, import_react11.createElement)(
5097
+ view.empty ? (0, import_react10.createElement)("p", { className: CLASS.empty }, copy.browseEmpty) : null,
5098
+ (0, import_react10.createElement)(
5021
5099
  "ul",
5022
5100
  // `tabIndex: -1` makes the list a programmatic focus target (not a Tab stop): it is where focus goes after a
5023
5101
  // navigation removes the row that had it.
5024
5102
  { className: CLASS.browserList, ref: listRef, tabIndex: -1 },
5025
5103
  ...view.canGoUp ? [
5026
- (0, import_react11.createElement)(
5104
+ (0, import_react10.createElement)(
5027
5105
  "li",
5028
5106
  { key: "..", className: CLASS.browserItem },
5029
5107
  Button({
@@ -5041,7 +5119,7 @@ function DirectoryBrowserSection({
5041
5119
  ] : [],
5042
5120
  ...view.rows.map((row) => {
5043
5121
  const badge = gitStatusBadgeElement(row, view);
5044
- return (0, import_react11.createElement)(
5122
+ return (0, import_react10.createElement)(
5045
5123
  "li",
5046
5124
  { key: row.key, className: CLASS.browserItem },
5047
5125
  row.kind === "directory" ? Button({
@@ -5052,11 +5130,11 @@ function DirectoryBrowserSection({
5052
5130
  disabled: gate,
5053
5131
  onClick: () => actions.onEnter(row.path),
5054
5132
  children: `${row.name}/`
5055
- }) : (0, import_react11.createElement)(
5133
+ }) : (0, import_react10.createElement)(
5056
5134
  "span",
5057
5135
  { className: CLASS.browserQuiet },
5058
5136
  row.name,
5059
- (0, import_react11.createElement)(
5137
+ (0, import_react10.createElement)(
5060
5138
  "span",
5061
5139
  { className: CLASS.badge },
5062
5140
  row.kind === "symlink" ? copy.browseKindSymlink : row.kind === "other" ? copy.browseKindOther : copy.browseKindFile
@@ -5067,7 +5145,7 @@ function DirectoryBrowserSection({
5067
5145
  })
5068
5146
  ),
5069
5147
  view.truncated ? Notice({ role: "note", tone: "info", children: resolveMessage2(copy, { key: "browseTruncated", params: { count: view.rows.length } }) }) : null,
5070
- (0, import_react11.createElement)(
5148
+ (0, import_react10.createElement)(
5071
5149
  "div",
5072
5150
  { className: CLASS.actions },
5073
5151
  Button({ variant: "primary", disabled: gate || !view.canCommit, onClick: actions.onCommit, children: commitLabel, className: CLASS.browserCommit }),
@@ -5077,10 +5155,10 @@ function DirectoryBrowserSection({
5077
5155
  }
5078
5156
  function DirectoryBrowserContainer(props) {
5079
5157
  const { copy, io, mode, seedPath, initialConnectionId, initialConnections, disabled, fixedMode, idPrefix, commitLabel, onCommit, pickNative } = props;
5080
- const copyRef = (0, import_react11.useRef)(copy);
5158
+ const copyRef = (0, import_react10.useRef)(copy);
5081
5159
  copyRef.current = copy;
5082
- const listRef = (0, import_react11.useRef)(null);
5083
- const [controller] = (0, import_react11.useState)(
5160
+ const listRef = (0, import_react10.useRef)(null);
5161
+ const [controller] = (0, import_react10.useState)(
5084
5162
  () => createDirectoryBrowser({
5085
5163
  io,
5086
5164
  copy: () => copyRef.current,
@@ -5089,17 +5167,17 @@ function DirectoryBrowserContainer(props) {
5089
5167
  ...initialConnections === void 0 ? {} : { connections: initialConnections }
5090
5168
  })
5091
5169
  );
5092
- const state = (0, import_react11.useSyncExternalStore)(controller.subscribe, controller.getState);
5093
- (0, import_react11.useEffect)(injectControlStylesInBrowser, []);
5094
- (0, import_react11.useEffect)(() => {
5170
+ const state = (0, import_react10.useSyncExternalStore)(controller.subscribe, controller.getState);
5171
+ (0, import_react10.useEffect)(injectControlStylesInBrowser, []);
5172
+ (0, import_react10.useEffect)(() => {
5095
5173
  controller.activate();
5096
5174
  controller.start(mode === "local" ? seedPath : initialConnectionId === void 0 ? void 0 : "");
5097
5175
  return () => {
5098
5176
  controller.dispose();
5099
5177
  };
5100
5178
  }, [controller]);
5101
- const lastPath = (0, import_react11.useRef)(state.path);
5102
- (0, import_react11.useLayoutEffect)(() => {
5179
+ const lastPath = (0, import_react10.useRef)(state.path);
5180
+ (0, import_react10.useLayoutEffect)(() => {
5103
5181
  if (lastPath.current === state.path) return;
5104
5182
  lastPath.current = state.path;
5105
5183
  listRef.current?.focus();
@@ -5163,6 +5241,9 @@ function createSessionDirectoryIo(options) {
5163
5241
  const loadRoots = async (workspacePath, sessionId) => {
5164
5242
  const result = await rootsApi.load();
5165
5243
  if (!result.ok) return result;
5244
+ if (result.value.warnings.includes("roots-invalid-answer")) {
5245
+ return { ok: false, error: "roots-invalid-answer" };
5246
+ }
5166
5247
  const all = result.value.roots;
5167
5248
  if (workspacePath === void 0 || workspacePath === "") return { ok: true, value: [] };
5168
5249
  return {
@@ -5341,6 +5422,10 @@ function createSessionDirectory(deps) {
5341
5422
  dispatch({ type: "roots-failed", reason: result.error });
5342
5423
  return;
5343
5424
  }
5425
+ if (state.workspacePath !== workspacePath || state.sessionId !== sessionId) {
5426
+ await refreshRoots();
5427
+ return;
5428
+ }
5344
5429
  dispatch({ type: "root-removed", key });
5345
5430
  };
5346
5431
  const commit = async (path, connectionId) => {
@@ -5421,7 +5506,7 @@ function SessionDirectorySection({
5421
5506
  titleId
5422
5507
  }) {
5423
5508
  const view = selectSessionDirectoryView(state, copy);
5424
- const card = view.visible === false ? null : (0, import_react12.createElement)(
5509
+ const card = view.visible === false ? null : (0, import_react11.createElement)(
5425
5510
  "div",
5426
5511
  {
5427
5512
  className: CLASS.floating,
@@ -5437,43 +5522,43 @@ function SessionDirectorySection({
5437
5522
  actions.onClose();
5438
5523
  }
5439
5524
  },
5440
- (0, import_react12.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, copy.sessionAddTitle),
5441
- (0, import_react12.createElement)("p", { className: CLASS.hint }, copy.sessionAddHint),
5525
+ (0, import_react11.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, copy.sessionAddTitle),
5526
+ (0, import_react11.createElement)("p", { className: CLASS.hint }, copy.sessionAddHint),
5442
5527
  // Which workspace this lands in, or - when the framework seats could not answer - the fallback the
5443
5528
  // copy states explicitly. Both are one line, so a narrow panel never has to truncate it.
5444
- (0, import_react12.createElement)("p", { className: CLASS.browserPath }, view.workspaceLine),
5529
+ (0, import_react11.createElement)("p", { className: CLASS.browserPath }, view.workspaceLine),
5445
5530
  // The card no longer *is* a browser: it lists what this session has and offers one way in. The picker is a
5446
5531
  // step behind 「浏览…」, which also means the card no longer opens with a full directory listing (that was
5447
5532
  // the complaint: the panel showed the current directory's contents before the user asked for anything).
5448
- view.browsing ? (0, import_react12.createElement)(
5533
+ view.browsing ? (0, import_react11.createElement)(
5449
5534
  "div",
5450
5535
  { className: CLASS.block },
5451
5536
  browser,
5452
- (0, import_react12.createElement)(
5537
+ (0, import_react11.createElement)(
5453
5538
  "div",
5454
5539
  { className: CLASS.actions },
5455
5540
  Button({ variant: "ghost", size: "compact", onClick: actions.onBrowse, children: copy.sessionBrowseBack })
5456
5541
  )
5457
- ) : (0, import_react12.createElement)(
5542
+ ) : (0, import_react11.createElement)(
5458
5543
  "div",
5459
5544
  { className: CLASS.block },
5460
- (0, import_react12.createElement)("h4", { className: CLASS.blockTitle }, copy.sessionRootsTitle),
5461
- (0, import_react12.createElement)("p", { className: CLASS.hint }, copy.sessionRootsHint),
5545
+ (0, import_react11.createElement)("h4", { className: CLASS.blockTitle }, copy.sessionRootsTitle),
5546
+ (0, import_react11.createElement)("p", { className: CLASS.hint }, copy.sessionRootsHint),
5462
5547
  view.roots.length === 0 ? view.rootsReading ? Notice({ role: "status", tone: "info", children: copy.sessionRootsLoading }) : (
5463
5548
  // A failed read is not an empty list: the error notice below is the whole truth there, and
5464
5549
  // 「还没有添加目录」 next to it would contradict it (same rule as the import dialog, ui P2-2/P2-3).
5465
- view.notice === "" ? (0, import_react12.createElement)("p", { className: CLASS.empty }, copy.sessionRootsEmpty) : null
5466
- ) : (0, import_react12.createElement)(
5550
+ view.notice === "" ? (0, import_react11.createElement)("p", { className: CLASS.empty }, copy.sessionRootsEmpty) : null
5551
+ ) : (0, import_react11.createElement)(
5467
5552
  "ul",
5468
5553
  { className: CLASS.list },
5469
5554
  ...view.roots.map(
5470
- (row) => (0, import_react12.createElement)(
5555
+ (row) => (0, import_react11.createElement)(
5471
5556
  "li",
5472
5557
  { key: row.key, className: CLASS.row },
5473
- (0, import_react12.createElement)("span", { className: CLASS.rowTitle }, row.title),
5474
- (0, import_react12.createElement)("code", { className: CLASS.badge }, row.badge),
5475
- row.subtitle === "" ? null : (0, import_react12.createElement)("span", { className: CLASS.rowMeta }, row.subtitle),
5476
- row.removable ? (0, import_react12.createElement)(
5558
+ (0, import_react11.createElement)("span", { className: CLASS.rowTitle }, row.title),
5559
+ (0, import_react11.createElement)("code", { className: CLASS.badge }, row.badge),
5560
+ row.subtitle === "" ? null : (0, import_react11.createElement)("span", { className: CLASS.rowMeta }, row.subtitle),
5561
+ row.removable ? (0, import_react11.createElement)(
5477
5562
  "span",
5478
5563
  { className: CLASS.rowActions },
5479
5564
  Button({
@@ -5487,7 +5572,7 @@ function SessionDirectorySection({
5487
5572
  )
5488
5573
  )
5489
5574
  ),
5490
- (0, import_react12.createElement)(
5575
+ (0, import_react11.createElement)(
5491
5576
  "div",
5492
5577
  { className: CLASS.actions },
5493
5578
  Button({ variant: "secondary", size: "compact", onClick: actions.onBrowse, children: copy.sessionBrowse })
@@ -5496,9 +5581,9 @@ function SessionDirectorySection({
5496
5581
  view.added === void 0 ? null : Notice({ role: "status", tone: "info", children: resolveMessage2(copy, { key: "sessionAddDone", params: { path: view.added } }) }),
5497
5582
  view.pending ? Notice({ role: "status", tone: "info", children: copy.flowPending }) : null,
5498
5583
  view.notice === "" ? null : Notice({ role: "alert", tone: "error", children: view.notice }),
5499
- (0, import_react12.createElement)("div", { className: CLASS.actions }, Button({ variant: "secondary", size: "compact", onClick: actions.onClose, children: copy.close }))
5584
+ (0, import_react11.createElement)("div", { className: CLASS.actions }, Button({ variant: "secondary", size: "compact", onClick: actions.onClose, children: copy.close }))
5500
5585
  );
5501
- return (0, import_react12.createElement)(
5586
+ return (0, import_react11.createElement)(
5502
5587
  "div",
5503
5588
  { className: CLASS.sessionEntry },
5504
5589
  Button({
@@ -5537,13 +5622,13 @@ function rootsQueryKey(state) {
5537
5622
  }
5538
5623
  function SessionDirectoryContainer(props) {
5539
5624
  const { copy, io } = props;
5540
- const copyRef = (0, import_react12.useRef)(copy);
5625
+ const copyRef = (0, import_react11.useRef)(copy);
5541
5626
  copyRef.current = copy;
5542
- const [controller] = (0, import_react12.useState)(() => createSessionDirectory({ io, copy: () => copyRef.current }));
5543
- const state = (0, import_react12.useSyncExternalStore)(controller.subscribe, controller.getState);
5627
+ const [controller] = (0, import_react11.useState)(() => createSessionDirectory({ io, copy: () => copyRef.current }));
5628
+ const state = (0, import_react11.useSyncExternalStore)(controller.subscribe, controller.getState);
5544
5629
  const workspacePath = readWorkspacePath(props);
5545
- const titleId = (0, import_react12.useId)();
5546
- const [portal] = (0, import_react12.useState)(
5630
+ const titleId = (0, import_react11.useId)();
5631
+ const [portal] = (0, import_react11.useState)(
5547
5632
  () => createCardPortal({
5548
5633
  document: browserPortalDocument(),
5549
5634
  className: SESSION_DIRECTORY_PORTAL_CLASS,
@@ -5551,22 +5636,22 @@ function SessionDirectoryContainer(props) {
5551
5636
  render: renderInPortal
5552
5637
  })
5553
5638
  );
5554
- const focusOrigin = (0, import_react12.useRef)(void 0);
5555
- (0, import_react12.useEffect)(injectControlStylesInBrowser, []);
5556
- (0, import_react12.useEffect)(() => controller.setWorkspacePath(workspacePath), [controller, workspacePath]);
5639
+ const focusOrigin = (0, import_react11.useRef)(void 0);
5640
+ (0, import_react11.useEffect)(injectControlStylesInBrowser, []);
5641
+ (0, import_react11.useEffect)(() => controller.setWorkspacePath(workspacePath), [controller, workspacePath]);
5557
5642
  const sessionId = props.sessionId;
5558
- (0, import_react12.useEffect)(() => controller.setSessionId(sessionId), [controller, sessionId]);
5643
+ (0, import_react11.useEffect)(() => controller.setSessionId(sessionId), [controller, sessionId]);
5559
5644
  const queryKey = rootsQueryKey(state);
5560
- (0, import_react12.useEffect)(() => {
5645
+ (0, import_react11.useEffect)(() => {
5561
5646
  if (queryKey === "closed") return;
5562
5647
  void controller.refreshRoots();
5563
5648
  }, [controller, queryKey]);
5564
5649
  useCardTabTrap(state.open, portal.container);
5565
- (0, import_react12.useEffect)(() => {
5650
+ (0, import_react11.useEffect)(() => {
5566
5651
  controller.activate();
5567
5652
  return () => controller.dispose();
5568
5653
  }, [controller]);
5569
- (0, import_react12.useLayoutEffect)(() => {
5654
+ (0, import_react11.useLayoutEffect)(() => {
5570
5655
  if (!state.open) return;
5571
5656
  focusOrigin.current = captureFocusOrigin(browserPortalDocument());
5572
5657
  portal.mount();
@@ -5577,7 +5662,7 @@ function SessionDirectoryContainer(props) {
5577
5662
  focusOrigin.current = void 0;
5578
5663
  };
5579
5664
  }, [portal, state.open]);
5580
- const browser = state.open === false || state.browsing === false ? null : (0, import_react12.createElement)(DirectoryBrowserContainer, {
5665
+ const browser = state.open === false || state.browsing === false ? null : (0, import_react11.createElement)(DirectoryBrowserContainer, {
5581
5666
  // One occurrence per open: a remount is how the browser is reset (its `mode`/seed are mount-time).
5582
5667
  key: "session-browser",
5583
5668
  copy,
@@ -5612,7 +5697,7 @@ function messageOf(error) {
5612
5697
  }
5613
5698
 
5614
5699
  // src/client/flows/workspace-directory-flow.ts
5615
- var import_react13 = require("react");
5700
+ var import_react12 = require("react");
5616
5701
  var WORKSPACE_DIRECTORY_FLOW_SLOTS = ["sidebar.workspaces.directoryFlow", "conversation.hero.workspace.directoryFlow"];
5617
5702
  var WORKSPACE_DIRECTORY_FLOW_PRIORITY = -1;
5618
5703
  var NO_ANCHOR_ERROR = "no-anchor-path";
@@ -5938,7 +6023,7 @@ function WorkspaceDirectoryFlowSection({
5938
6023
  const canSubmitRemote2 = view.canSubmitRemote;
5939
6024
  const connectionFieldId = `${idPrefix}-remote-connection`;
5940
6025
  const pathFieldId = `${idPrefix}-remote-path`;
5941
- const worldSwitch = (0, import_react13.createElement)(
6026
+ const worldSwitch = (0, import_react12.createElement)(
5942
6027
  "div",
5943
6028
  { className: CLASS.actions, role: "group", "aria-label": copy.flowTitle },
5944
6029
  Button({
@@ -5956,7 +6041,7 @@ function WorkspaceDirectoryFlowSection({
5956
6041
  children: copy.flowRemoteHeading
5957
6042
  })
5958
6043
  );
5959
- const card = (0, import_react13.createElement)(
6044
+ const card = (0, import_react12.createElement)(
5960
6045
  "section",
5961
6046
  {
5962
6047
  // The dialog's accessible name is its visible heading: the shell gives the heading a per-mount id, so
@@ -5975,21 +6060,21 @@ function WorkspaceDirectoryFlowSection({
5975
6060
  // wide two-column shape is queried against (see `styles.ts`).
5976
6061
  className: classes(CLASS.stack, CLASS.dialog)
5977
6062
  },
5978
- (0, import_react13.createElement)("div", { className: CLASS.dialogHeader }, (0, import_react13.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, copy.flowTitle)),
5979
- (0, import_react13.createElement)(
6063
+ (0, import_react12.createElement)("div", { className: CLASS.dialogHeader }, (0, import_react12.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, copy.flowTitle)),
6064
+ (0, import_react12.createElement)(
5980
6065
  "div",
5981
6066
  { className: CLASS.dialogBody },
5982
- (0, import_react13.createElement)("p", { className: CLASS.description }, copy.flowDescription),
6067
+ (0, import_react12.createElement)("p", { className: CLASS.description }, copy.flowDescription),
5983
6068
  notice === "" ? null : Notice({ role: "alert", tone: "error", children: notice }),
5984
6069
  // -- ask first: local or remote; only the selected world's block is rendered below -----------------
5985
6070
  worldSwitch,
5986
6071
  // -- local: the OS folder dialog, opened by one button ------------------------------------------------
5987
6072
  // 「浏览目录」means the platform's native chooser (users can reach any drive there); the embedded browser is
5988
6073
  // the **remote** block's business only. One button, one commit path: the chooser's answer is adopted.
5989
- view.mode === "local" ? (0, import_react13.createElement)(
6074
+ view.mode === "local" ? (0, import_react12.createElement)(
5990
6075
  "div",
5991
6076
  { key: "flow-local", className: CLASS.block },
5992
- (0, import_react13.createElement)("p", { className: CLASS.hint }, copy.flowLocalHint),
6077
+ (0, import_react12.createElement)("p", { className: CLASS.hint }, copy.flowLocalHint),
5993
6078
  Button({
5994
6079
  variant: "primary",
5995
6080
  disabled: !canPickLocal2,
@@ -6000,7 +6085,7 @@ function WorkspaceDirectoryFlowSection({
6000
6085
  view.pending ? Notice({ role: "status", tone: "info", children: copy.flowPending }) : null
6001
6086
  ) : null,
6002
6087
  // -- the remote block: connection + path + one submit; browsing merely fills the path ------------
6003
- view.mode === "remote" ? (0, import_react13.createElement)(
6088
+ view.mode === "remote" ? (0, import_react12.createElement)(
6004
6089
  "form",
6005
6090
  {
6006
6091
  key: "flow-remote",
@@ -6010,10 +6095,10 @@ function WorkspaceDirectoryFlowSection({
6010
6095
  },
6011
6096
  className: CLASS.block
6012
6097
  },
6013
- (0, import_react13.createElement)("p", { className: CLASS.hint }, copy.flowRemoteHint),
6098
+ (0, import_react12.createElement)("p", { className: CLASS.hint }, copy.flowRemoteHint),
6014
6099
  // The connection list's three states are inline, in this block: a failure (with its own retry), a read
6015
6100
  // in progress, and "no connection at all". None of them closes the dialog — the local entry still works.
6016
- view.connectionsError !== void 0 ? (0, import_react13.createElement)(
6101
+ view.connectionsError !== void 0 ? (0, import_react12.createElement)(
6017
6102
  "div",
6018
6103
  { className: CLASS.block },
6019
6104
  Notice({ role: "alert", tone: "error", children: resolveMessage2(copy, { key: "flowUnavailable", params: { reason: view.connectionsError } }) }),
@@ -6022,10 +6107,10 @@ function WorkspaceDirectoryFlowSection({
6022
6107
  // One column by default; two once `@container dsh-rw-flow (min-width: 22rem)` matches. Each field is
6023
6108
  // wrapped so a future sibling (a hint, a row of buttons) stays in the same cell instead of becoming a
6024
6109
  // column of its own.
6025
- (0, import_react13.createElement)(
6110
+ (0, import_react12.createElement)(
6026
6111
  "div",
6027
6112
  { className: CLASS.form },
6028
- (0, import_react13.createElement)(
6113
+ (0, import_react12.createElement)(
6029
6114
  "div",
6030
6115
  { className: CLASS.fieldSpan },
6031
6116
  Field({
@@ -6047,7 +6132,7 @@ function WorkspaceDirectoryFlowSection({
6047
6132
  })
6048
6133
  })
6049
6134
  ),
6050
- (0, import_react13.createElement)(
6135
+ (0, import_react12.createElement)(
6051
6136
  "div",
6052
6137
  { className: CLASS.fieldSpan },
6053
6138
  Field({
@@ -6055,7 +6140,7 @@ function WorkspaceDirectoryFlowSection({
6055
6140
  label: copy.rootsRemotePathLabel,
6056
6141
  // The path and its browse button share one line: typing a path by hand stays possible, and the
6057
6142
  // button that finds it sits exactly where the user looks for it instead of under the form.
6058
- children: (0, import_react13.createElement)(
6143
+ children: (0, import_react12.createElement)(
6059
6144
  "div",
6060
6145
  { className: CLASS.inputRow },
6061
6146
  TextInput({
@@ -6084,7 +6169,7 @@ function WorkspaceDirectoryFlowSection({
6084
6169
  // Browsing the connection's directories: the host answers names and kinds only. Picking one commits
6085
6170
  // through the browser's own "用这个目录" (see browseRemote); the form's "添加远端目录" is hidden while
6086
6171
  // this panel is open, so the dialog never shows two buttons for one action.
6087
- view.browse === "remote" ? (0, import_react13.createElement)(DirectoryBrowserContainer, {
6172
+ view.browse === "remote" ? (0, import_react12.createElement)(DirectoryBrowserContainer, {
6088
6173
  key: "flow-browse-remote",
6089
6174
  copy,
6090
6175
  io: browseIo,
@@ -6112,13 +6197,13 @@ function WorkspaceDirectoryFlowSection({
6112
6197
  }
6113
6198
  function WorkspaceDirectoryFlowContainer(props) {
6114
6199
  const { copy, io, open, busy, onPicked, onCancel, onError } = props;
6115
- const copyRef = (0, import_react13.useRef)(copy);
6200
+ const copyRef = (0, import_react12.useRef)(copy);
6116
6201
  copyRef.current = copy;
6117
- const [controller] = (0, import_react13.useState)(() => createWorkspaceDirectoryFlow({ io, copy: () => copyRef.current }));
6118
- const state = (0, import_react13.useSyncExternalStore)(controller.subscribe, controller.getState);
6119
- const titleId = (0, import_react13.useId)();
6202
+ const [controller] = (0, import_react12.useState)(() => createWorkspaceDirectoryFlow({ io, copy: () => copyRef.current }));
6203
+ const state = (0, import_react12.useSyncExternalStore)(controller.subscribe, controller.getState);
6204
+ const titleId = (0, import_react12.useId)();
6120
6205
  const idPrefix = `dsh-rw-${titleId.replace(/[^A-Za-z0-9_-]/g, "")}`;
6121
- const [portal] = (0, import_react13.useState)(
6206
+ const [portal] = (0, import_react12.useState)(
6122
6207
  () => createCardPortal({
6123
6208
  document: browserPortalDocument(),
6124
6209
  className: WORKSPACE_DIRECTORY_FLOW_PORTAL_CLASS,
@@ -6126,16 +6211,16 @@ function WorkspaceDirectoryFlowContainer(props) {
6126
6211
  render: renderInPortal
6127
6212
  })
6128
6213
  );
6129
- const focusOrigin = (0, import_react13.useRef)(void 0);
6130
- (0, import_react13.useEffect)(() => {
6214
+ const focusOrigin = (0, import_react12.useRef)(void 0);
6215
+ (0, import_react12.useEffect)(() => {
6131
6216
  controller.activate();
6132
6217
  return () => controller.dispose();
6133
6218
  }, [controller]);
6134
- (0, import_react13.useEffect)(injectControlStylesInBrowser, []);
6135
- (0, import_react13.useEffect)(() => {
6219
+ (0, import_react12.useEffect)(injectControlStylesInBrowser, []);
6220
+ (0, import_react12.useEffect)(() => {
6136
6221
  controller.syncOwner({ open, busy, onPicked, onCancel, onError });
6137
6222
  });
6138
- (0, import_react13.useLayoutEffect)(() => {
6223
+ (0, import_react12.useLayoutEffect)(() => {
6139
6224
  if (!state.open || portal.container === void 0) return;
6140
6225
  focusOrigin.current = captureFocusOrigin(browserPortalDocument());
6141
6226
  portal.mount();
@@ -6146,7 +6231,7 @@ function WorkspaceDirectoryFlowContainer(props) {
6146
6231
  focusOrigin.current = void 0;
6147
6232
  };
6148
6233
  }, [portal, state.open]);
6149
- (0, import_react13.useEffect)(() => {
6234
+ (0, import_react12.useEffect)(() => {
6150
6235
  if (!state.open) return;
6151
6236
  const doc = browserPortalDocument();
6152
6237
  if (doc?.addEventListener === void 0) return;