dsh-over-ssh 0.1.0 → 0.1.1

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 (247) hide show
  1. package/README.md +10 -10
  2. package/agents/agent-manifest.json +1 -1
  3. package/lib/client.js +363 -314
  4. package/lib/file-references.js +80 -35
  5. package/lib/fs.js +158 -75
  6. package/lib/index.js +4103 -3397
  7. package/lib/terminal-wrapper.js +9 -4
  8. package/lib/types/src/config.d.ts +80 -9
  9. package/lib/types/src/file-references.d.ts +0 -1
  10. package/lib/types/src/host/app/backend.d.ts +9 -1
  11. package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +0 -1
  12. package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +0 -1
  13. package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +12 -1
  14. package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +0 -1
  15. package/lib/types/src/host/app/transport/transport-selector.d.ts +0 -1
  16. package/lib/types/src/host/app/transport/transport-service.d.ts +0 -1
  17. package/lib/types/src/host/domain/capability.d.ts +0 -1
  18. package/lib/types/src/host/domain/errors.d.ts +0 -1
  19. package/lib/types/src/host/domain/roots/connection.d.ts +0 -1
  20. package/lib/types/src/host/domain/roots/root.d.ts +24 -9
  21. package/lib/types/src/host/domain/roots/session-roots.d.ts +0 -1
  22. package/lib/types/src/host/domain/roots/ssh-config.d.ts +0 -1
  23. package/lib/types/src/host/domain/session/session-admin.d.ts +0 -1
  24. package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +0 -1
  25. package/lib/types/src/host/domain/transfer/transfer.d.ts +0 -1
  26. package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +0 -1
  27. package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +0 -1
  28. package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
  29. package/lib/types/src/host/infra/transport/transport.d.ts +16 -2
  30. package/lib/types/src/host/integration/agent/agent-preflight.d.ts +0 -1
  31. package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +0 -1
  32. package/lib/types/src/host/integration/fs/fs-provider.d.ts +0 -2
  33. package/lib/types/src/host/integration/transfer/transfer-service.d.ts +0 -1
  34. package/lib/types/src/index.d.ts +23 -1
  35. package/lib/types/src/local-bridge-refs.d.ts +0 -1
  36. package/lib/types/src/local-bridge.d.ts +0 -1
  37. package/lib/types/src/shared/root-keys.d.ts +120 -0
  38. package/lib/types/src/wiring/helpers.d.ts +0 -1
  39. package/package.json +5 -3
  40. package/lib/types/src/client/controls.d.ts +0 -131
  41. package/lib/types/src/client/core/card-focus-trap.d.ts +0 -45
  42. package/lib/types/src/client/core/controls.d.ts +0 -164
  43. package/lib/types/src/client/core/latest.d.ts +0 -48
  44. package/lib/types/src/client/core/locale.d.ts +0 -38
  45. package/lib/types/src/client/core/message.d.ts +0 -37
  46. package/lib/types/src/client/core/panel-model.d.ts +0 -134
  47. package/lib/types/src/client/core/portal.d.ts +0 -266
  48. package/lib/types/src/client/core/remote.d.ts +0 -84
  49. package/lib/types/src/client/core/styles.d.ts +0 -458
  50. package/lib/types/src/client/file-tree-io.d.ts +0 -19
  51. package/lib/types/src/client/file-tree.d.ts +0 -271
  52. package/lib/types/src/client/flows/directory-browser.d.ts +0 -587
  53. package/lib/types/src/client/flows/session-directory.d.ts +0 -415
  54. package/lib/types/src/client/flows/workspace-directory-flow.d.ts +0 -480
  55. package/lib/types/src/client/locale.d.ts +0 -31
  56. package/lib/types/src/client/panel-model.d.ts +0 -37
  57. package/lib/types/src/client/panel.d.ts +0 -77
  58. package/lib/types/src/client/roots/connection-test.d.ts +0 -69
  59. package/lib/types/src/client/roots/roots-panel-state.d.ts +0 -307
  60. package/lib/types/src/client/roots/roots-panel-view.d.ts +0 -280
  61. package/lib/types/src/client/roots/roots-panel.d.ts +0 -364
  62. package/lib/types/src/client/roots/transfer-panel-view.d.ts +0 -35
  63. package/lib/types/src/client/roots/transfer-panel.d.ts +0 -241
  64. package/lib/types/src/client/roots/transfer-progress.d.ts +0 -67
  65. package/lib/types/src/client/roots-panel-view.d.ts +0 -236
  66. package/lib/types/src/client/roots-panel.d.ts +0 -347
  67. package/lib/types/src/client/session/session-panel-view.d.ts +0 -336
  68. package/lib/types/src/client/session/session-panel.d.ts +0 -322
  69. package/lib/types/src/client/session/session-remote-mark.d.ts +0 -110
  70. package/lib/types/src/client/session/session-row-actions.d.ts +0 -398
  71. package/lib/types/src/client/session-panel.d.ts +0 -152
  72. package/lib/types/src/client/styles.d.ts +0 -252
  73. package/lib/types/src/client/workspace-directory-flow.d.ts +0 -342
  74. package/lib/types/src/fs.d.ts +0 -46
  75. package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +0 -18
  76. package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +0 -38
  77. package/lib/types/src/host/app/recycle-bin-contract.d.ts +0 -215
  78. package/lib/types/src/host/app/recycle-bin-move.d.ts +0 -18
  79. package/lib/types/src/host/app/recycle-bin-service.d.ts +0 -45
  80. package/lib/types/src/host/app/recycle-bin-world.d.ts +0 -35
  81. package/lib/types/src/host/app/roots/roots-api-connections.d.ts +0 -49
  82. package/lib/types/src/host/app/roots/roots-api-contract.d.ts +0 -369
  83. package/lib/types/src/host/app/roots/roots-api-directories.d.ts +0 -13
  84. package/lib/types/src/host/app/roots/roots-api-http.d.ts +0 -77
  85. package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +0 -130
  86. package/lib/types/src/host/app/roots/roots-api-projections.d.ts +0 -102
  87. package/lib/types/src/host/app/roots/roots-api-roots.d.ts +0 -13
  88. package/lib/types/src/host/app/roots/roots-api-state.d.ts +0 -22
  89. package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +0 -26
  90. package/lib/types/src/host/app/roots/roots-api.d.ts +0 -55
  91. package/lib/types/src/host/app/roots/user-roots.d.ts +0 -552
  92. package/lib/types/src/host/app/roots-api-connections.d.ts +0 -49
  93. package/lib/types/src/host/app/roots-api-contract.d.ts +0 -339
  94. package/lib/types/src/host/app/roots-api-directories.d.ts +0 -13
  95. package/lib/types/src/host/app/roots-api-http.d.ts +0 -77
  96. package/lib/types/src/host/app/roots-api-projections.d.ts +0 -102
  97. package/lib/types/src/host/app/roots-api-roots.d.ts +0 -13
  98. package/lib/types/src/host/app/roots-api-state.d.ts +0 -22
  99. package/lib/types/src/host/app/roots-api-support.d.ts +0 -182
  100. package/lib/types/src/host/app/roots-api-transfers.d.ts +0 -13
  101. package/lib/types/src/host/app/roots-api.d.ts +0 -55
  102. package/lib/types/src/host/app/routed-fs.d.ts +0 -239
  103. package/lib/types/src/host/app/serialize.d.ts +0 -37
  104. package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +0 -36
  105. package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +0 -34
  106. package/lib/types/src/host/app/session-admin-context.d.ts +0 -36
  107. package/lib/types/src/host/app/session-admin-contract.d.ts +0 -214
  108. package/lib/types/src/host/app/session-admin-delete.d.ts +0 -34
  109. package/lib/types/src/host/app/session-admin-service.d.ts +0 -38
  110. package/lib/types/src/host/app/takeover/takeover-plan.d.ts +0 -227
  111. package/lib/types/src/host/app/takeover/takeover-rows.d.ts +0 -55
  112. package/lib/types/src/host/app/transport-selector.d.ts +0 -98
  113. package/lib/types/src/host/app/transport-service.d.ts +0 -75
  114. package/lib/types/src/host/app/user-roots.d.ts +0 -461
  115. package/lib/types/src/host/domain/bin-location.d.ts +0 -79
  116. package/lib/types/src/host/domain/connection.d.ts +0 -95
  117. package/lib/types/src/host/domain/glob-pattern.d.ts +0 -62
  118. package/lib/types/src/host/domain/literal-text.d.ts +0 -63
  119. package/lib/types/src/host/domain/path-grammar.d.ts +0 -84
  120. package/lib/types/src/host/domain/recycle-bin.d.ts +0 -337
  121. package/lib/types/src/host/domain/root.d.ts +0 -74
  122. package/lib/types/src/host/domain/roots/bin-location.d.ts +0 -91
  123. package/lib/types/src/host/domain/roots/route-table.d.ts +0 -102
  124. package/lib/types/src/host/domain/route-table.d.ts +0 -80
  125. package/lib/types/src/host/domain/session/terminal-target.d.ts +0 -136
  126. package/lib/types/src/host/domain/session-admin.d.ts +0 -330
  127. package/lib/types/src/host/domain/shell.d.ts +0 -47
  128. package/lib/types/src/host/domain/ssh-config.d.ts +0 -73
  129. package/lib/types/src/host/domain/terminal-target.d.ts +0 -132
  130. package/lib/types/src/host/domain/tool-requests.d.ts +0 -369
  131. package/lib/types/src/host/domain/transfer.d.ts +0 -260
  132. package/lib/types/src/host/infra/backend/local-backend.d.ts +0 -84
  133. package/lib/types/src/host/infra/backend/local-version.d.ts +0 -43
  134. package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +0 -89
  135. package/lib/types/src/host/infra/backend/remote-backend.d.ts +0 -239
  136. package/lib/types/src/host/infra/cache/lru.d.ts +0 -52
  137. package/lib/types/src/host/infra/cache/versioned.d.ts +0 -77
  138. package/lib/types/src/host/infra/temp-name.d.ts +0 -19
  139. package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +0 -132
  140. package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +0 -140
  141. package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +0 -41
  142. package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +0 -197
  143. package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +0 -564
  144. package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +0 -418
  145. package/lib/types/src/host/infra/transport/agent-installer.d.ts +0 -121
  146. package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +0 -123
  147. package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +0 -131
  148. package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +0 -41
  149. package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +0 -197
  150. package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +0 -509
  151. package/lib/types/src/host/infra/transport/agent-transport.d.ts +0 -353
  152. package/lib/types/src/host/infra/transport/alias-transport.d.ts +0 -206
  153. package/lib/types/src/host/infra/transport/askpass-fs.d.ts +0 -20
  154. package/lib/types/src/host/infra/transport/askpass.d.ts +0 -133
  155. package/lib/types/src/host/infra/transport/command-pool.d.ts +0 -61
  156. package/lib/types/src/host/infra/transport/fs-commands.d.ts +0 -140
  157. package/lib/types/src/host/infra/transport/git-status-route.d.ts +0 -63
  158. package/lib/types/src/host/infra/transport/git-status.d.ts +0 -101
  159. package/lib/types/src/host/infra/transport/node-spawn.d.ts +0 -26
  160. package/lib/types/src/host/infra/transport/session-window.d.ts +0 -51
  161. package/lib/types/src/host/infra/transport/shell-quote.d.ts +0 -59
  162. package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +0 -206
  163. package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +0 -20
  164. package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +0 -133
  165. package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +0 -62
  166. package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +0 -43
  167. package/lib/types/src/host/infra/transport/ssh-argv.d.ts +0 -43
  168. package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +0 -302
  169. package/lib/types/src/host/integration/agent/agent-install-io.d.ts +0 -33
  170. package/lib/types/src/host/integration/agent/askpass-registry.d.ts +0 -66
  171. package/lib/types/src/host/integration/agent-install-io.d.ts +0 -33
  172. package/lib/types/src/host/integration/agent-preflight.d.ts +0 -56
  173. package/lib/types/src/host/integration/anchor-directory.d.ts +0 -56
  174. package/lib/types/src/host/integration/askpass-registry.d.ts +0 -59
  175. package/lib/types/src/host/integration/bin-root-resolver.d.ts +0 -81
  176. package/lib/types/src/host/integration/connection-test.d.ts +0 -73
  177. package/lib/types/src/host/integration/file-reference-provider.d.ts +0 -115
  178. package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +0 -21
  179. package/lib/types/src/host/integration/fs/platform-errors.d.ts +0 -94
  180. package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +0 -369
  181. package/lib/types/src/host/integration/fs/remote-index.d.ts +0 -177
  182. package/lib/types/src/host/integration/fs-provider.d.ts +0 -226
  183. package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +0 -38
  184. package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +0 -67
  185. package/lib/types/src/host/integration/lsp/lsp-session.d.ts +0 -130
  186. package/lib/types/src/host/integration/management-tools.d.ts +0 -64
  187. package/lib/types/src/host/integration/recycle-bin-service.d.ts +0 -247
  188. package/lib/types/src/host/integration/remote/connection-test.d.ts +0 -73
  189. package/lib/types/src/host/integration/remote/remote-face.d.ts +0 -127
  190. package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +0 -108
  191. package/lib/types/src/host/integration/remote-face.d.ts +0 -127
  192. package/lib/types/src/host/integration/remote-file-tools.d.ts +0 -241
  193. package/lib/types/src/host/integration/remote-home.d.ts +0 -69
  194. package/lib/types/src/host/integration/remote-index.d.ts +0 -165
  195. package/lib/types/src/host/integration/remote-job-tools.d.ts +0 -295
  196. package/lib/types/src/host/integration/remote-tool-backend.d.ts +0 -97
  197. package/lib/types/src/host/integration/remote-tools.d.ts +0 -206
  198. package/lib/types/src/host/integration/root-registry.d.ts +0 -115
  199. package/lib/types/src/host/integration/roots/anchor-directory.d.ts +0 -56
  200. package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +0 -81
  201. package/lib/types/src/host/integration/roots/remote-home.d.ts +0 -69
  202. package/lib/types/src/host/integration/roots/root-registry.d.ts +0 -144
  203. package/lib/types/src/host/integration/roots/roots-wiring.d.ts +0 -219
  204. package/lib/types/src/host/integration/roots/session-root-index.d.ts +0 -52
  205. package/lib/types/src/host/integration/roots-api.d.ts +0 -407
  206. package/lib/types/src/host/integration/roots-wiring.d.ts +0 -211
  207. package/lib/types/src/host/integration/session/session-api.d.ts +0 -88
  208. package/lib/types/src/host/integration/session/session-bin-lock.d.ts +0 -80
  209. package/lib/types/src/host/integration/session/session-header.d.ts +0 -151
  210. package/lib/types/src/host/integration/session/session-labels.d.ts +0 -56
  211. package/lib/types/src/host/integration/session/session-route-wiring.d.ts +0 -50
  212. package/lib/types/src/host/integration/session-admin-service.d.ts +0 -236
  213. package/lib/types/src/host/integration/session-api.d.ts +0 -81
  214. package/lib/types/src/host/integration/session-bin-lock.d.ts +0 -80
  215. package/lib/types/src/host/integration/session-header.d.ts +0 -118
  216. package/lib/types/src/host/integration/session-labels.d.ts +0 -56
  217. package/lib/types/src/host/integration/session-route-wiring.d.ts +0 -50
  218. package/lib/types/src/host/integration/session-scope.d.ts +0 -113
  219. package/lib/types/src/host/integration/session-tools.d.ts +0 -44
  220. package/lib/types/src/host/integration/strict-arguments.d.ts +0 -79
  221. package/lib/types/src/host/integration/takeover/takeover-api.d.ts +0 -62
  222. package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +0 -60
  223. package/lib/types/src/host/integration/takeover/takeover-service.d.ts +0 -167
  224. package/lib/types/src/host/integration/terminal-manifest.d.ts +0 -94
  225. package/lib/types/src/host/integration/tools/management-tools.d.ts +0 -245
  226. package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +0 -316
  227. package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +0 -43
  228. package/lib/types/src/host/integration/tools/remote-tools.d.ts +0 -236
  229. package/lib/types/src/host/integration/tools/session-target.d.ts +0 -52
  230. package/lib/types/src/host/integration/tools/session-tools.d.ts +0 -46
  231. package/lib/types/src/host/integration/tools/strict-arguments.d.ts +0 -79
  232. package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +0 -94
  233. package/lib/types/src/host/integration/transfer/remote-copy.d.ts +0 -66
  234. package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +0 -48
  235. package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +0 -106
  236. package/lib/types/src/host/integration/transfer-adapters.d.ts +0 -44
  237. package/lib/types/src/host/integration/transfer-service.d.ts +0 -164
  238. package/lib/types/src/host/integration/transfer-tracker.d.ts +0 -106
  239. package/lib/types/src/host/integration/user-roots.d.ts +0 -461
  240. package/lib/types/src/wiring/connections.d.ts +0 -189
  241. package/lib/types/src/wiring/fs.d.ts +0 -13
  242. package/lib/types/src/wiring/install-memo.d.ts +0 -32
  243. package/lib/types/src/wiring/legacy-dirs.d.ts +0 -62
  244. package/lib/types/src/wiring/package-paths.d.ts +0 -35
  245. package/lib/types/src/wiring/roots.d.ts +0 -21
  246. package/lib/types/src/wiring/state-dir.d.ts +0 -29
  247. package/lib/types/src/wiring/tools.d.ts +0 -35
@@ -49,7 +49,14 @@ var DEFAULTS = {
49
49
  function rankMatches(paths, query, limit) {
50
50
  const needle = query.trim().toLowerCase();
51
51
  if (needle === "") {
52
- return paths.map((path) => ({ path, depth: depth(path) })).sort((left, right) => left.depth - right.depth || (left.path < right.path ? -1 : 1)).slice(0, limit).map((entry) => entry.path);
52
+ const order = [];
53
+ for (let index = 0; index < paths.length; index += 1) order.push(index);
54
+ const depths = new Int32Array(paths.length);
55
+ for (let index = 0; index < paths.length; index += 1) depths[index] = depth(paths[index]);
56
+ order.sort(
57
+ (left, right) => depths[left] - depths[right] || (paths[left] < paths[right] ? -1 : 1)
58
+ );
59
+ return order.slice(0, limit).map((index) => paths[index]);
53
60
  }
54
61
  const scored = [];
55
62
  for (const path of paths) {
@@ -69,30 +76,27 @@ function rankMatches(paths, query, limit) {
69
76
  return scored.slice(0, limit).map((entry) => entry.path);
70
77
  }
71
78
  function depth(path) {
72
- return path.split("/").filter((segment) => segment !== "").length;
73
- }
74
-
75
- // src/host/domain/roots/root.ts
76
- function makeLocalRoot(path, options = {}) {
77
- const normalized = normalizeLocalPath(path, { caseInsensitive: false });
78
- if (!isAbsoluteLocalPath(normalized)) {
79
- throw new SshDevError("ROOT_INVALID", `\u672C\u5730\u6839\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${path}`, { path });
79
+ let segments = 0;
80
+ let inSegment = false;
81
+ for (let index = 0; index < path.length; index += 1) {
82
+ if (path.charCodeAt(index) === 47) {
83
+ inSegment = false;
84
+ continue;
85
+ }
86
+ if (!inSegment) {
87
+ segments += 1;
88
+ inSegment = true;
89
+ }
80
90
  }
81
- return {
82
- location: "local",
83
- path: normalized,
84
- writable: options.writable ?? true,
85
- ...options.label === void 0 ? {} : { label: options.label },
86
- ...options.workspaces === void 0 ? {} : { workspaces: options.workspaces },
87
- ...options.sessions === void 0 ? {} : { sessions: options.sessions },
88
- ...options.shared === void 0 ? {} : { shared: options.shared }
89
- };
91
+ return segments;
90
92
  }
93
+
94
+ // src/shared/root-keys.ts
91
95
  function isAbsoluteLocalPath(path) {
92
- return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith("//") || path.startsWith("/");
96
+ return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith("/") || path.startsWith("\\\\");
93
97
  }
94
98
  function normalizeLocalPath(path, options = {}) {
95
- const caseInsensitive = options.caseInsensitive ?? process.platform === "win32";
99
+ const caseInsensitive = options.caseInsensitive ?? false;
96
100
  const unified = path.replace(/\\/g, "/");
97
101
  const unc = unified.startsWith("//");
98
102
  const posixAbsolute = unified.startsWith("/");
@@ -118,15 +122,46 @@ function normalizeLocalPath(path, options = {}) {
118
122
  return caseInsensitive ? normalized.toLowerCase() : normalized;
119
123
  }
120
124
 
125
+ // src/host/domain/roots/root.ts
126
+ function makeLocalRoot(path, options = {}) {
127
+ const normalized = normalizeLocalPath2(path, { caseInsensitive: false });
128
+ if (!isAbsoluteLocalPath(normalized)) {
129
+ throw new SshDevError("ROOT_INVALID", `\u672C\u5730\u6839\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${path}`, { path });
130
+ }
131
+ return {
132
+ location: "local",
133
+ path: normalized,
134
+ writable: options.writable ?? true,
135
+ ...options.label === void 0 ? {} : { label: options.label },
136
+ ...options.workspaces === void 0 ? {} : { workspaces: options.workspaces },
137
+ ...options.sessions === void 0 ? {} : { sessions: options.sessions },
138
+ ...options.shared === void 0 ? {} : { shared: options.shared }
139
+ };
140
+ }
141
+ function normalizeLocalPath2(path, options = {}) {
142
+ return normalizeLocalPath(path, { caseInsensitive: options.caseInsensitive ?? process.platform === "win32" });
143
+ }
144
+
121
145
  // src/host/integration/session-scope.ts
122
146
  var MAX_FORK_DEPTH = 32;
123
147
  var ancestorCache = /* @__PURE__ */ new Map();
124
148
  var ANCESTOR_CACHE_MAX = 256;
125
149
  function readSessionAncestors(ctx, sessionId) {
126
150
  const cached = ancestorCache.get(sessionId);
127
- if (cached !== void 0) return cached;
151
+ if (cached !== void 0) {
152
+ ancestorCache.delete(sessionId);
153
+ ancestorCache.set(sessionId, cached);
154
+ return cached;
155
+ }
128
156
  const walked = walkAncestors(ctx, sessionId);
129
- if (walked.settled && ancestorCache.size < ANCESTOR_CACHE_MAX) ancestorCache.set(sessionId, walked.ancestors);
157
+ if (walked.settled) {
158
+ ancestorCache.set(sessionId, walked.ancestors);
159
+ while (ancestorCache.size > ANCESTOR_CACHE_MAX) {
160
+ const oldest = ancestorCache.keys().next();
161
+ if (oldest.done === true) break;
162
+ ancestorCache.delete(oldest.value);
163
+ }
164
+ }
130
165
  return walked.ancestors;
131
166
  }
132
167
  function walkAncestors(ctx, sessionId) {
@@ -220,16 +255,18 @@ var RemoteFileReferenceService = class extends FileReferenceService {
220
255
  if (registry === void 0) return [];
221
256
  const identity = sessionIdentityOf(this.ctx, agent.session.header.id);
222
257
  const gated = identity.sessionId === void 0 ? registry.routed.tryResolve(cwd) : registry.routed.tryResolve(cwd, { session: identity });
223
- const added = await this.#addedRemoteCandidates(cwd, identity, query, signal);
224
- if (gated.ok) {
225
- const root = gated.target.root;
226
- const named = root.location === "remote" && root.connectionId !== void 0 ? await this.#remoteCandidates(root.connectionId, root.path, query, signal) : await this.#localCandidates(root.path, query, signal);
227
- return this.#merge(added, named);
228
- }
229
- if (registry.routed.tryResolve(cwd).ok) return added ?? [];
230
- const implicit = sessionLocalRoot(cwd);
231
- const local = implicit === void 0 ? [] : await this.#localCandidates(implicit.path, query, signal);
232
- return this.#merge(added, local);
258
+ const addedTask = this.#addedRemoteCandidates(cwd, identity, query, signal);
259
+ const ungated = gated.ok ? void 0 : registry.routed.tryResolve(cwd);
260
+ const implicit = gated.ok || ungated?.ok === true ? void 0 : sessionLocalRoot(cwd);
261
+ const namedTask = gated.ok ? this.#namedCandidates(gated.target.root, query, signal) : implicit === void 0 ? void 0 : this.#localCandidates(implicit.path, query, signal);
262
+ const [added, named] = await Promise.all([addedTask, namedTask]);
263
+ if (gated.ok) return this.#merge(added, named ?? []);
264
+ if (ungated?.ok === true) return added ?? [];
265
+ return this.#merge(added, named ?? []);
266
+ }
267
+ /** cwd 命名的那个根自己的候选:远端走索引,本地走有界走查。 */
268
+ #namedCandidates(root, query, signal) {
269
+ return root.location === "remote" && root.connectionId !== void 0 ? this.#remoteCandidates(root.connectionId, root.path, query, signal) : this.#localCandidates(root.path, query, signal);
233
270
  }
234
271
  /** Remote candidates for one root, or `[]` when the composition has no index. */
235
272
  async #remoteCandidates(connectionId, root, query, signal) {
@@ -241,12 +278,20 @@ var RemoteFileReferenceService = class extends FileReferenceService {
241
278
  async #localCandidates(rootPath, query, signal) {
242
279
  const walk = this.#options.walk ?? ((root, walkSignal) => this.#walkLocal(root, walkSignal));
243
280
  const entries = await this.#walkCached(rootPath, walk, signal);
244
- const kinds = new Map(entries.map((entry) => [entry.path, entry.kind]));
245
- return rankMatches(
281
+ const ranked = rankMatches(
246
282
  entries.map((entry) => entry.path),
247
283
  query,
248
284
  this.#options.maxResults ?? DEFAULT_MAX_RESULTS
249
- ).map((path) => ({ path, kind: kinds.get(path) ?? "file" }));
285
+ );
286
+ if (ranked.length === 0) return [];
287
+ const wanted = new Set(ranked);
288
+ const kinds = /* @__PURE__ */ new Map();
289
+ for (const entry of entries) {
290
+ if (!wanted.has(entry.path)) continue;
291
+ kinds.set(entry.path, entry.kind);
292
+ if (kinds.size === wanted.size) break;
293
+ }
294
+ return ranked.map((path) => ({ path, kind: kinds.get(path) ?? "file" }));
250
295
  }
251
296
  /**
252
297
  * Candidates from the remote directory this conversation added with 「添加目录」, when there is one.
package/lib/fs.js CHANGED
@@ -128,6 +128,33 @@ var LruCache = class {
128
128
  this.#bytes -= entry.bytes;
129
129
  return true;
130
130
  }
131
+ /**
132
+ * Remove every entry whose key starts with `prefix`, in one pass over the internal map.
133
+ *
134
+ * 存在的理由:调用方(`VersionedCache.invalidatePrefix`)在**每次写**之后都要按前缀失效,而在此之前
135
+ * 它只有 `keys()` 可用——`keys()` 会把整张表**物化**成一个数组,随后又用 `delete(key)` 逐个做哈希
136
+ * 查找。这里直接在 `#entries` 上过一遍:不建中间数组,字节记账就地扣减,边迭代边删除(Map 迭代器
137
+ * 规范保证删除当前条目后仍会访问后续条目)。
138
+ *
139
+ * **为什么是"前缀"而不是更通用的"谓词"**:谓词要为每条 key 付一次 JS 回调,而一次 `keys()` 物化只是
140
+ * 一次指针批量拷贝。实测(20 000 条 × 2 000 次,只命中 1 条)旧写法 1 289 ms、前缀版 962 ms、
141
+ * 谓词版 953 ms——两者同量级;但在**生产上限**(读缓存 256 条 / 目录缓存 512 条)下谓词版反而比旧
142
+ * 写法慢(20 000 次:160 ms vs 148 ms),前缀版打平(147 ms)。也就是说:谓词版只在大表上不亏,
143
+ * 在小表上亏;前缀版两边都不亏。热路径上的方法按真实规模选形状。
144
+ *
145
+ * @param prefix - key prefix.
146
+ * @returns the number of removed entries.
147
+ */
148
+ deleteByPrefix(prefix) {
149
+ let removed = 0;
150
+ for (const [key, entry] of this.#entries) {
151
+ if (!key.startsWith(prefix)) continue;
152
+ this.#entries.delete(key);
153
+ this.#bytes -= entry.bytes;
154
+ removed++;
155
+ }
156
+ return removed;
157
+ }
131
158
  /** Remove everything. */
132
159
  clear() {
133
160
  this.#entries.clear();
@@ -148,16 +175,38 @@ var LruCache = class {
148
175
  rejectedOversize: this.#rejectedOversize
149
176
  };
150
177
  }
178
+ /**
179
+ * Drop the least-recently-used entry.
180
+ *
181
+ * Exposed so a shared {@link CacheBudget} can make this cache give bytes back; `#evictToBounds` is the
182
+ * same operation driven by this cache's own limits.
183
+ *
184
+ * @returns `false` when the cache is empty (the caller must not retry in a loop).
185
+ */
186
+ evictOldest() {
187
+ const oldest = this.#entries.keys().next();
188
+ if (oldest.done === true) return false;
189
+ const entry = this.#entries.get(oldest.value);
190
+ if (entry === void 0) return false;
191
+ this.#entries.delete(oldest.value);
192
+ this.#bytes -= entry.bytes;
193
+ this.#evictions++;
194
+ return true;
195
+ }
196
+ /**
197
+ * Come down to `maxBytes` by dropping the oldest entries.
198
+ *
199
+ * @param maxBytes - the ceiling to reach.
200
+ * @returns how many entries were dropped.
201
+ */
202
+ trimTo(maxBytes) {
203
+ let dropped = 0;
204
+ while (this.#bytes > maxBytes && this.evictOldest()) dropped++;
205
+ return dropped;
206
+ }
151
207
  #evictToBounds() {
152
208
  while (this.#entries.size > this.#maxEntries || this.#bytes > this.#maxBytes) {
153
- const oldest = this.#entries.keys().next();
154
- if (oldest.done === true) return;
155
- const key = oldest.value;
156
- const entry = this.#entries.get(key);
157
- if (entry === void 0) return;
158
- this.#entries.delete(key);
159
- this.#bytes -= entry.bytes;
160
- this.#evictions++;
209
+ if (!this.evictOldest()) return;
161
210
  }
162
211
  }
163
212
  };
@@ -167,6 +216,7 @@ var VersionedCache = class {
167
216
  #ttlMs;
168
217
  #now;
169
218
  #lru;
219
+ #budget;
170
220
  #hits = 0;
171
221
  #misses = 0;
172
222
  #freshHits = 0;
@@ -177,6 +227,29 @@ var VersionedCache = class {
177
227
  this.#ttlMs = options.ttlMs;
178
228
  this.#now = options.now ?? Date.now;
179
229
  this.#lru = new LruCache({ maxEntries: options.maxEntries, maxBytes: options.maxBytes }, (entry) => sizeOf(entry.value));
230
+ this.#budget = options.budget;
231
+ this.#budget?.join(this);
232
+ }
233
+ /** Bytes this cache holds (the budget member contract). */
234
+ get bytes() {
235
+ return this.#lru.stats.bytes;
236
+ }
237
+ /** Drop oldest entries down to `maxBytes` (the budget member contract). */
238
+ trimTo(maxBytes) {
239
+ return this.#lru.trimTo(maxBytes);
240
+ }
241
+ /** Drop the oldest entry (the budget member contract). */
242
+ evictOldest() {
243
+ return this.#lru.evictOldest();
244
+ }
245
+ /**
246
+ * Leave the shared budget, so a retired cache stops counting against the total.
247
+ *
248
+ * Called by whoever owns the cache when its root goes away. Without it a removed root's cache stayed in the
249
+ * accounting forever, which is the leak the budget exists to prevent.
250
+ */
251
+ leaveBudget() {
252
+ this.#budget?.leave(this);
180
253
  }
181
254
  /**
182
255
  * Look up an entry.
@@ -199,7 +272,9 @@ var VersionedCache = class {
199
272
  }
200
273
  /** Insert or replace an entry, stamping it as just-read. */
201
274
  set(key, value, version) {
202
- return this.#lru.set(key, { value, version, storedAt: this.#now() });
275
+ const stored = this.#lru.set(key, { value, version, storedAt: this.#now() });
276
+ if (stored) this.#budget?.enforce(this);
277
+ return stored;
203
278
  }
204
279
  /**
205
280
  * Refresh an entry's timestamp after a cheap remote version check matched.
@@ -226,11 +301,7 @@ var VersionedCache = class {
226
301
  * @returns the number of removed entries.
227
302
  */
228
303
  invalidatePrefix(prefix) {
229
- let removed = 0;
230
- for (const key of this.#lru.keys()) {
231
- if (key.startsWith(prefix) && this.#lru.delete(key)) removed++;
232
- }
233
- return removed;
304
+ return this.#lru.deleteByPrefix(prefix);
234
305
  }
235
306
  /** Remove everything. */
236
307
  clear() {
@@ -267,12 +338,35 @@ function isSshDevError(value) {
267
338
  return value instanceof SshDevError;
268
339
  }
269
340
 
270
- // src/host/domain/path-grammar.ts
271
- function normalizeRemotePath(path) {
272
- if (path === "") throw new SshDevError("ROOT_INVALID", "\u8FDC\u7A0B\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A", {});
273
- if (!path.startsWith("/")) {
274
- throw new SshDevError("ROOT_INVALID", `\u8FDC\u7A0B\u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${path}`, { path });
341
+ // src/shared/root-keys.ts
342
+ function normalizeLocalPath(path, options = {}) {
343
+ const caseInsensitive = options.caseInsensitive ?? false;
344
+ const unified = path.replace(/\\/g, "/");
345
+ const unc = unified.startsWith("//");
346
+ const posixAbsolute = unified.startsWith("/");
347
+ const driveMatch = /^([A-Za-z]):/.exec(unified);
348
+ const driveLetter = driveMatch?.[1];
349
+ const drive = driveLetter === void 0 ? "" : `${driveLetter.toUpperCase()}:`;
350
+ const rest = unc ? unified.slice(2) : drive === "" ? unified : unified.slice(2);
351
+ const segments = [];
352
+ for (const segment of rest.split("/")) {
353
+ if (segment === "" || segment === ".") continue;
354
+ if (segment === "..") {
355
+ segments.pop();
356
+ continue;
357
+ }
358
+ segments.push(segment);
275
359
  }
360
+ const body = segments.join("/");
361
+ let normalized;
362
+ if (unc) normalized = `//${body}`;
363
+ else if (drive !== "") normalized = body === "" ? `${drive}/` : `${drive}/${body}`;
364
+ else if (posixAbsolute) normalized = body === "" ? "/" : `/${body}`;
365
+ else normalized = body === "" ? "." : body;
366
+ return caseInsensitive ? normalized.toLowerCase() : normalized;
367
+ }
368
+ function normalizePosixPath(path) {
369
+ if (!path.startsWith("/")) return void 0;
276
370
  const segments = [];
277
371
  for (const segment of path.split("/")) {
278
372
  if (segment === "" || segment === ".") continue;
@@ -284,12 +378,28 @@ function normalizeRemotePath(path) {
284
378
  }
285
379
  return segments.length === 0 ? "/" : `/${segments.join("/")}`;
286
380
  }
381
+
382
+ // src/host/domain/path-grammar.ts
383
+ function normalizeRemotePath(path) {
384
+ const normalized = normalizePosixPath(path);
385
+ if (normalized !== void 0) return normalized;
386
+ if (path === "") throw new SshDevError("ROOT_INVALID", "\u8FDC\u7A0B\u8DEF\u5F84\u4E0D\u80FD\u4E3A\u7A7A", {});
387
+ throw new SshDevError("ROOT_INVALID", `\u8FDC\u7A0B\u8DEF\u5F84\u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84\uFF1A${path}`, { path });
388
+ }
287
389
  function remotePathContains(root, candidate) {
288
390
  const normalizedRoot = normalizeRemotePath(root);
289
391
  const normalizedCandidate = normalizeRemotePath(candidate);
290
392
  if (normalizedRoot === "/") return true;
291
393
  return normalizedCandidate === normalizedRoot || normalizedCandidate.startsWith(`${normalizedRoot}/`);
292
394
  }
395
+ function longestContainingRoot(roots, candidate) {
396
+ let best;
397
+ for (const root of roots) {
398
+ if (!remotePathContains(root, candidate)) continue;
399
+ if (best === void 0 || normalizeRemotePath(root).length > normalizeRemotePath(best).length) best = root;
400
+ }
401
+ return best;
402
+ }
293
403
 
294
404
  // src/host/infra/transport/git-status.ts
295
405
  function isGitStatusTransport(value) {
@@ -429,31 +539,15 @@ function relativeDirectory(root, path) {
429
539
  }
430
540
 
431
541
  // src/host/domain/roots/root.ts
432
- function normalizeLocalPath(path, options = {}) {
433
- const caseInsensitive = options.caseInsensitive ?? process.platform === "win32";
434
- const unified = path.replace(/\\/g, "/");
435
- const unc = unified.startsWith("//");
436
- const posixAbsolute = unified.startsWith("/");
437
- const driveMatch = /^([A-Za-z]):/.exec(unified);
438
- const driveLetter = driveMatch?.[1];
439
- const drive = driveLetter === void 0 ? "" : `${driveLetter.toUpperCase()}:`;
440
- const rest = unc ? unified.slice(2) : drive === "" ? unified : unified.slice(2);
441
- const segments = [];
442
- for (const segment of rest.split("/")) {
443
- if (segment === "" || segment === ".") continue;
444
- if (segment === "..") {
445
- segments.pop();
446
- continue;
447
- }
448
- segments.push(segment);
449
- }
450
- const body = segments.join("/");
451
- let normalized;
452
- if (unc) normalized = `//${body}`;
453
- else if (drive !== "") normalized = body === "" ? `${drive}/` : `${drive}/${body}`;
454
- else if (posixAbsolute) normalized = body === "" ? "/" : `/${body}`;
455
- else normalized = body === "" ? "." : body;
456
- return caseInsensitive ? normalized.toLowerCase() : normalized;
542
+ function normalizeLocalPath2(path, options = {}) {
543
+ return normalizeLocalPath(path, { caseInsensitive: options.caseInsensitive ?? process.platform === "win32" });
544
+ }
545
+
546
+ // src/shared/routes.ts
547
+ function asRouteHost(value) {
548
+ if (typeof value !== "object" || value === null) return void 0;
549
+ const register = value.register;
550
+ return typeof register === "function" ? value : void 0;
457
551
  }
458
552
 
459
553
  // src/host/app/roots/roots-api-contract.ts
@@ -507,14 +601,14 @@ function parseAuthority(authority) {
507
601
  }
508
602
 
509
603
  // src/host/app/roots/roots-api-http.ts
510
- async function readBody(req) {
604
+ async function readBody(req, maxBytes) {
511
605
  const chunks = [];
512
606
  let total = 0;
513
607
  for await (const chunk of req) {
514
608
  const buffer = toBytes(chunk);
515
609
  total += buffer.length;
516
- if (total > ROOTS_API_MAX_BODY_BYTES) {
517
- throw new Error(`\u8BF7\u6C42\u4F53\u8D85\u8FC7\u4E0A\u9650 ${ROOTS_API_MAX_BODY_BYTES} \u5B57\u8282\uFF0C\u62D2\u7EDD`);
610
+ if (total > maxBytes) {
611
+ throw new SshDevError("ROOT_INVALID", `\u8BF7\u6C42\u4F53\u8D85\u8FC7\u4E0A\u9650 ${maxBytes} \u5B57\u8282\uFF0C\u62D2\u7EDD`);
518
612
  }
519
613
  chunks.push(buffer);
520
614
  }
@@ -543,11 +637,6 @@ function messageOf(error) {
543
637
 
544
638
  // src/host/infra/transport/git-status-route.ts
545
639
  var GIT_STATUS_API_PATH = "/api/over-ssh/git-status";
546
- function asRouteHost(value) {
547
- if (typeof value !== "object" || value === null) return void 0;
548
- const register = value.register;
549
- return typeof register === "function" ? value : void 0;
550
- }
551
640
  function toPublicAnswer(snapshot) {
552
641
  return {
553
642
  ok: true,
@@ -579,7 +668,7 @@ function createGitStatusApi(deps) {
579
668
  return;
580
669
  }
581
670
  try {
582
- const body = asRecord(JSON.parse(await readBody(req)));
671
+ const body = asRecord(JSON.parse(await readBody(req, ROOTS_API_MAX_BODY_BYTES)));
583
672
  const connectionId = body?.["connectionId"];
584
673
  const path = body?.["path"];
585
674
  if (typeof connectionId !== "string" || connectionId === "") {
@@ -641,15 +730,8 @@ function wireGitStatus(ctx) {
641
730
  resolveRoot: (connectionId, path) => {
642
731
  const registry = ctx.get("sshDevRoots");
643
732
  if (registry === void 0) return void 0;
644
- const normalized = normalizeRemotePath(path);
645
- let best;
646
- for (const record of registry.records) {
647
- const root = record.root;
648
- if (root.location !== "remote" || root.connectionId !== connectionId) continue;
649
- if (!remotePathContains(root.path, normalized)) continue;
650
- if (best === void 0 || root.path.length > best.length) best = root.path;
651
- }
652
- return best;
733
+ const roots = registry.records.map((record) => record.root).filter((root) => root.location === "remote" && root.connectionId === connectionId).map((root) => root.path);
734
+ return longestContainingRoot(roots, normalizeRemotePath(path));
653
735
  }
654
736
  });
655
737
  registerGitStatusRoute(ctx, { service });
@@ -699,9 +781,20 @@ var ancestorCache = /* @__PURE__ */ new Map();
699
781
  var ANCESTOR_CACHE_MAX = 256;
700
782
  function readSessionAncestors(ctx, sessionId) {
701
783
  const cached = ancestorCache.get(sessionId);
702
- if (cached !== void 0) return cached;
784
+ if (cached !== void 0) {
785
+ ancestorCache.delete(sessionId);
786
+ ancestorCache.set(sessionId, cached);
787
+ return cached;
788
+ }
703
789
  const walked = walkAncestors(ctx, sessionId);
704
- if (walked.settled && ancestorCache.size < ANCESTOR_CACHE_MAX) ancestorCache.set(sessionId, walked.ancestors);
790
+ if (walked.settled) {
791
+ ancestorCache.set(sessionId, walked.ancestors);
792
+ while (ancestorCache.size > ANCESTOR_CACHE_MAX) {
793
+ const oldest = ancestorCache.keys().next();
794
+ if (oldest.done === true) break;
795
+ ancestorCache.delete(oldest.value);
796
+ }
797
+ }
705
798
  return walked.ancestors;
706
799
  }
707
800
  function walkAncestors(ctx, sessionId) {
@@ -798,7 +891,7 @@ var RoutedFsProvider = class extends FileSystem {
798
891
  */
799
892
  processPathFromHostPath(hostPath) {
800
893
  if (!isAbsoluteHostPath(hostPath)) return void 0;
801
- return normalizeLocalPath(hostPath, { caseInsensitive: false });
894
+ return normalizeLocalPath2(hostPath, { caseInsensitive: false });
802
895
  }
803
896
  /** Canonical `file:` URI in the target's world. */
804
897
  fileUrl(target) {
@@ -932,16 +1025,6 @@ var RoutedFsProvider = class extends FileSystem {
932
1025
  const chunk = await this.call(() => routed.backend.readBytes(routed.absolute, end, signal));
933
1026
  return chunk.bytes.subarray(start, Math.min(end, chunk.bytes.byteLength));
934
1027
  }
935
- /**
936
- * Read a window through the injected offset reader.
937
- *
938
- * Kept as a thin method so the instance is the receiver here (a direct call is fine); the work happens in
939
- * the free {@link readChunkedWindow}, because the public read path invokes it from inside `this.call(...)`,
940
- * where `this` is the Cordis service Proxy and instance state would not be reachable.
941
- */
942
- #readChunkedWindow(routed, offset, length, signal) {
943
- return readChunkedWindow(this.rangeReader, this.rangeChunkBytes, routed, offset, length, signal);
944
- }
945
1028
  /** Direct children, in stable name order, with resolved child targets. */
946
1029
  async listDir(target, signal) {
947
1030
  const routed = this.routedTarget(target);