dsh-over-ssh 0.0.0-stage → 0.1.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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -2
  3. package/agents/agent-manifest.json +20 -0
  4. package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
  5. package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
  6. package/cordis.patch.yml +84 -0
  7. package/lib/client.js +6611 -0
  8. package/lib/file-references.js +366 -0
  9. package/lib/fs.js +1368 -0
  10. package/lib/index.js +19034 -0
  11. package/lib/local-bridge-refs.js +16 -0
  12. package/lib/local-bridge.js +16 -0
  13. package/lib/terminal-wrapper.js +294 -0
  14. package/lib/types/src/client/controls.d.ts +131 -0
  15. package/lib/types/src/client/core/card-focus-trap.d.ts +45 -0
  16. package/lib/types/src/client/core/controls.d.ts +164 -0
  17. package/lib/types/src/client/core/latest.d.ts +48 -0
  18. package/lib/types/src/client/core/locale.d.ts +38 -0
  19. package/lib/types/src/client/core/message.d.ts +37 -0
  20. package/lib/types/src/client/core/panel-model.d.ts +134 -0
  21. package/lib/types/src/client/core/portal.d.ts +266 -0
  22. package/lib/types/src/client/core/remote.d.ts +84 -0
  23. package/lib/types/src/client/core/styles.d.ts +458 -0
  24. package/lib/types/src/client/file-tree-io.d.ts +19 -0
  25. package/lib/types/src/client/file-tree.d.ts +271 -0
  26. package/lib/types/src/client/flows/directory-browser.d.ts +587 -0
  27. package/lib/types/src/client/flows/session-directory.d.ts +415 -0
  28. package/lib/types/src/client/flows/workspace-directory-flow.d.ts +480 -0
  29. package/lib/types/src/client/index.d.ts +49 -0
  30. package/lib/types/src/client/locale.d.ts +31 -0
  31. package/lib/types/src/client/panel-model.d.ts +37 -0
  32. package/lib/types/src/client/panel.d.ts +77 -0
  33. package/lib/types/src/client/roots/connection-test.d.ts +69 -0
  34. package/lib/types/src/client/roots/roots-panel-state.d.ts +307 -0
  35. package/lib/types/src/client/roots/roots-panel-view.d.ts +280 -0
  36. package/lib/types/src/client/roots/roots-panel.d.ts +364 -0
  37. package/lib/types/src/client/roots/transfer-panel-view.d.ts +35 -0
  38. package/lib/types/src/client/roots/transfer-panel.d.ts +241 -0
  39. package/lib/types/src/client/roots/transfer-progress.d.ts +67 -0
  40. package/lib/types/src/client/roots-panel-view.d.ts +236 -0
  41. package/lib/types/src/client/roots-panel.d.ts +347 -0
  42. package/lib/types/src/client/session/session-panel-view.d.ts +336 -0
  43. package/lib/types/src/client/session/session-panel.d.ts +322 -0
  44. package/lib/types/src/client/session/session-remote-mark.d.ts +110 -0
  45. package/lib/types/src/client/session/session-row-actions.d.ts +398 -0
  46. package/lib/types/src/client/session-panel.d.ts +152 -0
  47. package/lib/types/src/client/styles.d.ts +252 -0
  48. package/lib/types/src/client/workspace-directory-flow.d.ts +342 -0
  49. package/lib/types/src/config.d.ts +312 -0
  50. package/lib/types/src/file-references.d.ts +25 -0
  51. package/lib/types/src/fs.d.ts +46 -0
  52. package/lib/types/src/host/app/backend.d.ts +170 -0
  53. package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +247 -0
  54. package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +18 -0
  55. package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +45 -0
  56. package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +38 -0
  57. package/lib/types/src/host/app/recycle-bin-contract.d.ts +215 -0
  58. package/lib/types/src/host/app/recycle-bin-move.d.ts +18 -0
  59. package/lib/types/src/host/app/recycle-bin-service.d.ts +45 -0
  60. package/lib/types/src/host/app/recycle-bin-world.d.ts +35 -0
  61. package/lib/types/src/host/app/roots/roots-api-connections.d.ts +49 -0
  62. package/lib/types/src/host/app/roots/roots-api-contract.d.ts +369 -0
  63. package/lib/types/src/host/app/roots/roots-api-directories.d.ts +13 -0
  64. package/lib/types/src/host/app/roots/roots-api-http.d.ts +77 -0
  65. package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +130 -0
  66. package/lib/types/src/host/app/roots/roots-api-projections.d.ts +102 -0
  67. package/lib/types/src/host/app/roots/roots-api-roots.d.ts +13 -0
  68. package/lib/types/src/host/app/roots/roots-api-state.d.ts +22 -0
  69. package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +26 -0
  70. package/lib/types/src/host/app/roots/roots-api.d.ts +55 -0
  71. package/lib/types/src/host/app/roots/user-roots.d.ts +552 -0
  72. package/lib/types/src/host/app/roots-api-connections.d.ts +49 -0
  73. package/lib/types/src/host/app/roots-api-contract.d.ts +339 -0
  74. package/lib/types/src/host/app/roots-api-directories.d.ts +13 -0
  75. package/lib/types/src/host/app/roots-api-http.d.ts +77 -0
  76. package/lib/types/src/host/app/roots-api-projections.d.ts +102 -0
  77. package/lib/types/src/host/app/roots-api-roots.d.ts +13 -0
  78. package/lib/types/src/host/app/roots-api-state.d.ts +22 -0
  79. package/lib/types/src/host/app/roots-api-support.d.ts +182 -0
  80. package/lib/types/src/host/app/roots-api-transfers.d.ts +13 -0
  81. package/lib/types/src/host/app/roots-api.d.ts +55 -0
  82. package/lib/types/src/host/app/routed-fs.d.ts +239 -0
  83. package/lib/types/src/host/app/serialize.d.ts +37 -0
  84. package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +36 -0
  85. package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +221 -0
  86. package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +34 -0
  87. package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +40 -0
  88. package/lib/types/src/host/app/session-admin-context.d.ts +36 -0
  89. package/lib/types/src/host/app/session-admin-contract.d.ts +214 -0
  90. package/lib/types/src/host/app/session-admin-delete.d.ts +34 -0
  91. package/lib/types/src/host/app/session-admin-service.d.ts +38 -0
  92. package/lib/types/src/host/app/takeover/takeover-plan.d.ts +227 -0
  93. package/lib/types/src/host/app/takeover/takeover-rows.d.ts +55 -0
  94. package/lib/types/src/host/app/transport/transport-selector.d.ts +98 -0
  95. package/lib/types/src/host/app/transport/transport-service.d.ts +82 -0
  96. package/lib/types/src/host/app/transport-selector.d.ts +98 -0
  97. package/lib/types/src/host/app/transport-service.d.ts +75 -0
  98. package/lib/types/src/host/app/user-roots.d.ts +461 -0
  99. package/lib/types/src/host/domain/bin-location.d.ts +79 -0
  100. package/lib/types/src/host/domain/capability.d.ts +78 -0
  101. package/lib/types/src/host/domain/connection.d.ts +95 -0
  102. package/lib/types/src/host/domain/errors.d.ts +55 -0
  103. package/lib/types/src/host/domain/glob-pattern.d.ts +62 -0
  104. package/lib/types/src/host/domain/literal-text.d.ts +63 -0
  105. package/lib/types/src/host/domain/path-grammar.d.ts +84 -0
  106. package/lib/types/src/host/domain/recycle-bin.d.ts +337 -0
  107. package/lib/types/src/host/domain/root.d.ts +74 -0
  108. package/lib/types/src/host/domain/roots/bin-location.d.ts +91 -0
  109. package/lib/types/src/host/domain/roots/connection.d.ts +106 -0
  110. package/lib/types/src/host/domain/roots/root.d.ts +108 -0
  111. package/lib/types/src/host/domain/roots/route-table.d.ts +102 -0
  112. package/lib/types/src/host/domain/roots/session-roots.d.ts +151 -0
  113. package/lib/types/src/host/domain/roots/ssh-config.d.ts +73 -0
  114. package/lib/types/src/host/domain/route-table.d.ts +80 -0
  115. package/lib/types/src/host/domain/session/session-admin.d.ts +345 -0
  116. package/lib/types/src/host/domain/session/terminal-target.d.ts +136 -0
  117. package/lib/types/src/host/domain/session-admin.d.ts +330 -0
  118. package/lib/types/src/host/domain/shell.d.ts +47 -0
  119. package/lib/types/src/host/domain/ssh-config.d.ts +73 -0
  120. package/lib/types/src/host/domain/terminal-target.d.ts +132 -0
  121. package/lib/types/src/host/domain/tool-requests.d.ts +369 -0
  122. package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +337 -0
  123. package/lib/types/src/host/domain/transfer/transfer.d.ts +260 -0
  124. package/lib/types/src/host/domain/transfer.d.ts +260 -0
  125. package/lib/types/src/host/infra/backend/local-backend.d.ts +84 -0
  126. package/lib/types/src/host/infra/backend/local-version.d.ts +43 -0
  127. package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +89 -0
  128. package/lib/types/src/host/infra/backend/remote-backend.d.ts +239 -0
  129. package/lib/types/src/host/infra/cache/lru.d.ts +52 -0
  130. package/lib/types/src/host/infra/cache/versioned.d.ts +77 -0
  131. package/lib/types/src/host/infra/temp-name.d.ts +19 -0
  132. package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +121 -0
  133. package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +132 -0
  134. package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +140 -0
  135. package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +41 -0
  136. package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +197 -0
  137. package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +564 -0
  138. package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +418 -0
  139. package/lib/types/src/host/infra/transport/agent-installer.d.ts +121 -0
  140. package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +123 -0
  141. package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +131 -0
  142. package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +41 -0
  143. package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +197 -0
  144. package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +509 -0
  145. package/lib/types/src/host/infra/transport/agent-transport.d.ts +353 -0
  146. package/lib/types/src/host/infra/transport/alias-transport.d.ts +206 -0
  147. package/lib/types/src/host/infra/transport/askpass-fs.d.ts +20 -0
  148. package/lib/types/src/host/infra/transport/askpass.d.ts +133 -0
  149. package/lib/types/src/host/infra/transport/command-pool.d.ts +61 -0
  150. package/lib/types/src/host/infra/transport/fs-commands.d.ts +140 -0
  151. package/lib/types/src/host/infra/transport/git-status-route.d.ts +63 -0
  152. package/lib/types/src/host/infra/transport/git-status.d.ts +101 -0
  153. package/lib/types/src/host/infra/transport/node-spawn.d.ts +26 -0
  154. package/lib/types/src/host/infra/transport/session-window.d.ts +51 -0
  155. package/lib/types/src/host/infra/transport/shell-quote.d.ts +59 -0
  156. package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +206 -0
  157. package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +20 -0
  158. package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +133 -0
  159. package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +62 -0
  160. package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +43 -0
  161. package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +328 -0
  162. package/lib/types/src/host/infra/transport/ssh-argv.d.ts +43 -0
  163. package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +302 -0
  164. package/lib/types/src/host/infra/transport/transport-base.d.ts +34 -0
  165. package/lib/types/src/host/infra/transport/transport.d.ts +527 -0
  166. package/lib/types/src/host/integration/agent/agent-install-io.d.ts +33 -0
  167. package/lib/types/src/host/integration/agent/agent-preflight.d.ts +56 -0
  168. package/lib/types/src/host/integration/agent/askpass-registry.d.ts +66 -0
  169. package/lib/types/src/host/integration/agent-install-io.d.ts +33 -0
  170. package/lib/types/src/host/integration/agent-preflight.d.ts +56 -0
  171. package/lib/types/src/host/integration/anchor-directory.d.ts +56 -0
  172. package/lib/types/src/host/integration/askpass-registry.d.ts +59 -0
  173. package/lib/types/src/host/integration/bin-root-resolver.d.ts +81 -0
  174. package/lib/types/src/host/integration/connection-test.d.ts +73 -0
  175. package/lib/types/src/host/integration/file-reference-provider.d.ts +115 -0
  176. package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +142 -0
  177. package/lib/types/src/host/integration/fs/fs-provider.d.ts +368 -0
  178. package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +21 -0
  179. package/lib/types/src/host/integration/fs/platform-errors.d.ts +94 -0
  180. package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +369 -0
  181. package/lib/types/src/host/integration/fs/remote-index.d.ts +177 -0
  182. package/lib/types/src/host/integration/fs-provider.d.ts +226 -0
  183. package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +38 -0
  184. package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +67 -0
  185. package/lib/types/src/host/integration/lsp/lsp-session.d.ts +130 -0
  186. package/lib/types/src/host/integration/management-tools.d.ts +64 -0
  187. package/lib/types/src/host/integration/recycle-bin-service.d.ts +247 -0
  188. package/lib/types/src/host/integration/remote/connection-test.d.ts +73 -0
  189. package/lib/types/src/host/integration/remote/remote-face.d.ts +127 -0
  190. package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +108 -0
  191. package/lib/types/src/host/integration/remote-face.d.ts +127 -0
  192. package/lib/types/src/host/integration/remote-file-tools.d.ts +241 -0
  193. package/lib/types/src/host/integration/remote-home.d.ts +69 -0
  194. package/lib/types/src/host/integration/remote-index.d.ts +165 -0
  195. package/lib/types/src/host/integration/remote-job-tools.d.ts +295 -0
  196. package/lib/types/src/host/integration/remote-tool-backend.d.ts +97 -0
  197. package/lib/types/src/host/integration/remote-tools.d.ts +206 -0
  198. package/lib/types/src/host/integration/root-registry.d.ts +115 -0
  199. package/lib/types/src/host/integration/roots/anchor-directory.d.ts +56 -0
  200. package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +81 -0
  201. package/lib/types/src/host/integration/roots/remote-home.d.ts +69 -0
  202. package/lib/types/src/host/integration/roots/root-registry.d.ts +144 -0
  203. package/lib/types/src/host/integration/roots/roots-wiring.d.ts +219 -0
  204. package/lib/types/src/host/integration/roots/session-root-index.d.ts +52 -0
  205. package/lib/types/src/host/integration/roots-api.d.ts +407 -0
  206. package/lib/types/src/host/integration/roots-wiring.d.ts +211 -0
  207. package/lib/types/src/host/integration/session/session-api.d.ts +88 -0
  208. package/lib/types/src/host/integration/session/session-bin-lock.d.ts +80 -0
  209. package/lib/types/src/host/integration/session/session-header.d.ts +151 -0
  210. package/lib/types/src/host/integration/session/session-labels.d.ts +56 -0
  211. package/lib/types/src/host/integration/session/session-route-wiring.d.ts +50 -0
  212. package/lib/types/src/host/integration/session-admin-service.d.ts +236 -0
  213. package/lib/types/src/host/integration/session-api.d.ts +81 -0
  214. package/lib/types/src/host/integration/session-bin-lock.d.ts +80 -0
  215. package/lib/types/src/host/integration/session-header.d.ts +118 -0
  216. package/lib/types/src/host/integration/session-labels.d.ts +56 -0
  217. package/lib/types/src/host/integration/session-route-wiring.d.ts +50 -0
  218. package/lib/types/src/host/integration/session-scope.d.ts +113 -0
  219. package/lib/types/src/host/integration/session-tools.d.ts +44 -0
  220. package/lib/types/src/host/integration/strict-arguments.d.ts +79 -0
  221. package/lib/types/src/host/integration/takeover/takeover-api.d.ts +62 -0
  222. package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +60 -0
  223. package/lib/types/src/host/integration/takeover/takeover-service.d.ts +167 -0
  224. package/lib/types/src/host/integration/terminal-manifest.d.ts +94 -0
  225. package/lib/types/src/host/integration/tools/management-tools.d.ts +245 -0
  226. package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +316 -0
  227. package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +43 -0
  228. package/lib/types/src/host/integration/tools/remote-tools.d.ts +236 -0
  229. package/lib/types/src/host/integration/tools/session-target.d.ts +52 -0
  230. package/lib/types/src/host/integration/tools/session-tools.d.ts +46 -0
  231. package/lib/types/src/host/integration/tools/strict-arguments.d.ts +79 -0
  232. package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +94 -0
  233. package/lib/types/src/host/integration/transfer/remote-copy.d.ts +66 -0
  234. package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +48 -0
  235. package/lib/types/src/host/integration/transfer/transfer-service.d.ts +212 -0
  236. package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +106 -0
  237. package/lib/types/src/host/integration/transfer-adapters.d.ts +44 -0
  238. package/lib/types/src/host/integration/transfer-service.d.ts +164 -0
  239. package/lib/types/src/host/integration/transfer-tracker.d.ts +106 -0
  240. package/lib/types/src/host/integration/user-roots.d.ts +461 -0
  241. package/lib/types/src/index.d.ts +66 -0
  242. package/lib/types/src/local-bridge-refs.d.ts +26 -0
  243. package/lib/types/src/local-bridge.d.ts +36 -0
  244. package/lib/types/src/shared/format.d.ts +13 -0
  245. package/lib/types/src/wiring/connections.d.ts +189 -0
  246. package/lib/types/src/wiring/fs.d.ts +13 -0
  247. package/lib/types/src/wiring/helpers.d.ts +144 -0
  248. package/lib/types/src/wiring/install-memo.d.ts +32 -0
  249. package/lib/types/src/wiring/legacy-dirs.d.ts +62 -0
  250. package/lib/types/src/wiring/package-paths.d.ts +35 -0
  251. package/lib/types/src/wiring/roots.d.ts +21 -0
  252. package/lib/types/src/wiring/state-dir.d.ts +29 -0
  253. package/lib/types/src/wiring/tools.d.ts +35 -0
  254. package/package.json +120 -4
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The root registry: which directories a workspace has, and which backend serves each one.
3
+ *
4
+ * It owns exactly one {@link RoutedFileSystem} and mutates it in place, so the object handed to the
5
+ * core `ctx.fs` provider keeps its identity across root changes (a rebuilt router would silently
6
+ * invalidate every target key a caller is holding).
7
+ *
8
+ * Backend construction is injected: the local and remote backends need `node:fs` and a transport
9
+ * respectively, and keeping those out of this module is what makes the registry testable without a
10
+ * filesystem or a network.
11
+ *
12
+ * @module dsh-remote-workspace/integration/root-registry
13
+ */
14
+ import type { Backend } from '../app/backend.ts';
15
+ import { RoutedFileSystem, type RoutedTarget } from '../app/routed-fs.ts';
16
+ import { type RootOptions, type RootRef } from '../domain/root.ts';
17
+ /** One registered root together with its backend. */
18
+ export interface RootRecord {
19
+ readonly key: string;
20
+ readonly root: RootRef;
21
+ readonly backend: Backend;
22
+ }
23
+ /** Options for {@link RootRegistry}. */
24
+ export interface RootRegistryOptions {
25
+ /** Builds the backend for a local root. */
26
+ readonly createLocalBackend: (root: RootRef) => Backend;
27
+ /** Builds the backend for a remote root (one per connection in this design). */
28
+ readonly createRemoteBackend: (root: RootRef) => Backend;
29
+ readonly caseInsensitive?: boolean;
30
+ readonly maxReadBytes?: number;
31
+ /** Called after every mutation, so a client half or a status view can refresh. */
32
+ readonly onChanged?: (records: readonly RootRecord[]) => void;
33
+ }
34
+ /** Remote-root options. */
35
+ export interface RemoteRootOptions extends RootOptions {
36
+ /** Local empty anchor directory; DSH requires a workspace path that exists on the host. */
37
+ readonly anchorPath?: string;
38
+ }
39
+ /** The set of roots of one workspace. */
40
+ export declare class RootRegistry {
41
+ #private;
42
+ constructor(options: RootRegistryOptions);
43
+ /** The router every consumer reads through (`ctx.fs` wraps this). */
44
+ get routed(): RoutedFileSystem;
45
+ /** Registered roots in registration order. **Hidden roots are not here** (see {@link addHidden}). */
46
+ get records(): readonly RootRecord[];
47
+ /**
48
+ * Register a root that only the router knows about.
49
+ *
50
+ * Used for the file-level recycle bin, which has to live outside the caller's repository and therefore
51
+ * outside every visible root (ADR-0011). The backend is built exactly as for a visible root, so routing,
52
+ * `stat`, `list`, `move` and `remove` all behave normally — what changes is only that nothing lists it.
53
+ *
54
+ * Idempotent: registering the same key twice returns the existing record instead of throwing, because the
55
+ * caller (a bin resolver) may be asked for the same root repeatedly and a duplicate is not an error there.
56
+ *
57
+ * @param root - the hidden root to register.
58
+ * @returns the record, newly created or already present.
59
+ */
60
+ addHidden(root: RootRef): RootRecord;
61
+ /** Hidden roots registered so far (for tests and diagnostics — never for a user-facing list). */
62
+ get hiddenRecords(): readonly RootRecord[];
63
+ /** Look up one hidden root by key. */
64
+ getHidden(key: string): RootRecord | undefined;
65
+ /** Remove one hidden root by key (used when a connection's bin can no longer be resolved). */
66
+ removeHidden(key: string): boolean;
67
+ /** Register a local directory root. */
68
+ addLocal(path: string, options?: RootOptions): RootRecord;
69
+ /** Register a remote directory root on one connection. */
70
+ addRemote(connectionId: string, path: string, options?: RemoteRootOptions): RootRecord;
71
+ /** Register an already-built root reference. */
72
+ add(root: RootRef): RootRecord;
73
+ /** Remove one root by key. */
74
+ remove(key: string): boolean;
75
+ /**
76
+ * Remove one root by a key that may differ in case from the registered one.
77
+ *
78
+ * Windows local keys are folded by {@link rootKey}, while the file and the client keep the caller's case, so
79
+ * a removal that only matched verbatim removed the file entry and left the root live. The exact match is
80
+ * tried first; the fallback folds **only local keys**, where case is not significant on Windows and where
81
+ * the mismatch can only come from that spelling difference.
82
+ *
83
+ * @param key - the key the caller holds.
84
+ * @returns the key the root was registered under, so related caches can forget the same spelling; `undefined`
85
+ * when nothing matched.
86
+ */
87
+ removeByLooseKey(key: string): string | undefined;
88
+ /** Replace every root (workspace switch, profile reload). Hidden roots go too: they are derived from these. */
89
+ reset(roots: readonly RootRef[]): void;
90
+ /** Roots belonging to one connection, for connection teardown and status. */
91
+ forConnection(connectionId: string): readonly RootRecord[];
92
+ /** Look one root up by key. */
93
+ get(key: string): RootRecord | undefined;
94
+ /**
95
+ * Remove every root of one connection (the connection was deleted or its transport is gone).
96
+ *
97
+ * @param connectionId - connection whose roots should disappear.
98
+ * @returns the removed records, so a caller can report what was dropped.
99
+ */
100
+ removeConnection(connectionId: string): readonly RootRecord[];
101
+ }
102
+ /** Declaration merging so the provider can read the registry from `ctx` with a typed property. */
103
+ declare module '@deepseek-ai/cordis' {
104
+ interface Context {
105
+ /** Root registry provided by this plugin. */
106
+ sshDevRoots: RootRegistry;
107
+ }
108
+ }
109
+ /** Narrow view used by the `ctx.fs` provider: it only needs the router. */
110
+ export interface RoutedTargetOwner {
111
+ readonly routed: RoutedFileSystem;
112
+ }
113
+ /** Re-exported for consumers that only need the target type. */
114
+ export type { RoutedTarget };
115
+ //# sourceMappingURL=root-registry.d.ts.map
@@ -0,0 +1,56 @@
1
+ /**
2
+ * The local anchor directory of a remote root.
3
+ *
4
+ * A remote root needs a **local** path to stand in for it, because a workspace path must exist on this machine
5
+ * (`root.ts` keeps that anchor on the `RootRef`). The platform then resolves that path with `realpath` when it
6
+ * opens a workspace on it, so the directory has to be **created**, not just computed:
7
+ *
8
+ * workspace create failed: workspace/invalid-path: cannot create a Workspace at
9
+ * "C:\\Users\\…\\.dsh\\over-ssh\\roots\\腾讯云\\39d1e90c": ENOENT …
10
+ *
11
+ * That failure is what this module exists to prevent. It lives in its own file so the rule ("a remote root's
12
+ * anchor must exist") is in one place and testable without a real home directory (红线 3: 外部依赖走注入).
13
+ *
14
+ * @module dsh-over-ssh/integration/anchor-directory
15
+ */
16
+ import type { RootRef } from '../../domain/roots/root.ts';
17
+ /** The one filesystem call this module needs. */
18
+ export interface AnchorDirectoryIo {
19
+ /**
20
+ * Create a directory and its parents; a no-op when it already exists.
21
+ *
22
+ * The return value is ignored on purpose (`node:fs` answers with the first created path): this module only
23
+ * cares that the directory exists afterwards, so the seam stays as narrow as that fact.
24
+ */
25
+ mkdir(path: string, options: {
26
+ readonly recursive: true;
27
+ }): Promise<unknown> | unknown;
28
+ }
29
+ /**
30
+ * Make one root's local anchor exist.
31
+ *
32
+ * Local roots are their own anchor (the directory is the user's own path, which the platform checks itself), and
33
+ * a remote root without an anchor simply has nothing to create - both cases return without touching the disk.
34
+ *
35
+ * @param root - the root that is about to become live.
36
+ * @param io - the injected filesystem.
37
+ * @returns nothing; a failure propagates so the caller decides whether it is fatal.
38
+ */
39
+ export declare function ensureAnchorDirectory(root: Pick<RootRef, 'location' | 'anchorPath'>, io: AnchorDirectoryIo): Promise<void>;
40
+ /**
41
+ * The last path segment of a remote root's anchor: **the remote folder's own name**.
42
+ *
43
+ * Why it matters: the platform titles a new workspace with `defaultWorkspaceTitle(path)` =
44
+ * `basename(path)` (`dsh-workspace/lib/types/paths.js:26-29`), and the directory-flow contract only carries a
45
+ * `path` - there is no title parameter to pass (`DirectoryFlowOwnerProps`). So the segment this function
46
+ * returns **is** the name the user sees in the sidebar. A digest-only anchor made every remote workspace show
47
+ * up as `daf8c293`; this is what turns it into `stress_10`.
48
+ *
49
+ * Identity is *not* this function's job: the caller keeps a digest directory above it, so two remote paths with
50
+ * the same folder name still get two anchors (one canonical path per workspace).
51
+ *
52
+ * @param remotePath - absolute POSIX path on the remote host.
53
+ * @returns a segment that is safe to create on this machine and readable as a workspace name.
54
+ */
55
+ export declare function anchorLeafName(remotePath: string): string;
56
+ //# sourceMappingURL=anchor-directory.d.ts.map
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Decide, per root, where its file-level recycle bin lives — and make that place resolvable.
3
+ *
4
+ * The bin must sit **outside** the caller's repository (a "safe delete" that pollutes `git status` is not safe),
5
+ * yet it must be reachable by the router, which only resolves paths inside a registered root (ADR-0011). So the
6
+ * bin directory is registered as a **hidden root**: routable, but absent from `RootRegistry.records` and
7
+ * therefore from every user-facing list.
8
+ *
9
+ * This module owns the one decision that cannot be pure — *is the chosen directory usable?* — and it answers by
10
+ * measuring rather than assuming: the bin's parent must exist and must be on the **same device** as the root,
11
+ * because a cross-device `rename` is refused outright and the file-level bin has no copy fallback
12
+ * (`fs-commands.ts`, `recycle-bin-service.ts`). If a measurement fails, the bin stays inside the root — the old
13
+ * behaviour — and the reason is recorded, so a fallback is **visible** rather than silent.
14
+ *
15
+ * @module dsh-over-ssh/integration/bin-root-resolver
16
+ */
17
+ import { type RootRef } from '../../domain/roots/root.ts';
18
+ import type { RootRecord } from './root-registry.ts';
19
+ /** Where one root's bin ended up, and why. */
20
+ export interface BinRootChoice {
21
+ /** Absolute bin directory. */
22
+ readonly binRoot: string;
23
+ /** `beside` = outside the repository (the goal); `inside` = the documented fallback. */
24
+ readonly placement: 'beside' | 'inside';
25
+ /** Whether a hidden root was registered for it (only for `beside`). */
26
+ readonly hidden: boolean;
27
+ /** Human-readable reason for a fallback, or for an inconclusive measurement. */
28
+ note?: string;
29
+ }
30
+ /** What the resolver needs from its owner. */
31
+ export interface BinRootResolverDeps {
32
+ /** Register a hidden root; returns the record. */
33
+ readonly addHiddenRoot: (root: RootRef) => RootRecord;
34
+ /** Drop a hidden root by key; used when a migration fails and the new location is abandoned. */
35
+ readonly removeHiddenRoot?: (key: string) => void;
36
+ /** The configured bin directory name (`limits.binDir`). */
37
+ readonly binDir: string;
38
+ /**
39
+ * Whether two paths are on the same device.
40
+ *
41
+ * `undefined` means **unknown**, and unknown is treated as *usable*: refusing to move a file into the bin is
42
+ * worse than putting the bin in an untidy place, and a transport without a working `stat` cannot answer.
43
+ */
44
+ readonly sameDevice: (root: RootRef, left: string, right: string) => Promise<boolean | undefined>;
45
+ /** Whether a directory exists. */
46
+ readonly exists: (root: RootRef, path: string) => Promise<boolean>;
47
+ /**
48
+ * Move a directory, creating the destination's parents.
49
+ *
50
+ * Only ever called for the **old bin → new bin** migration, where the two are on one device (already
51
+ * measured), so a plain `rename` is the whole implementation.
52
+ */
53
+ readonly moveDirectory: (root: RootRef, from: string, to: string) => Promise<void>;
54
+ }
55
+ /** The resolver handed to the recycle-bin wiring. */
56
+ export interface BinRootResolver {
57
+ /**
58
+ * The bin directory for one root, registering a hidden root on first use.
59
+ *
60
+ * Synchronous by contract: the bin service asks repeatedly and cannot await (`binRootFor`). The measurement
61
+ * therefore happens in {@link BinRootResolver.prepare}, which the composition calls when a root is registered.
62
+ *
63
+ * @param root - the root being served.
64
+ * @returns its bin directory.
65
+ */
66
+ binRootFor: (root: RootRef) => string;
67
+ /** Measure and register a root's bin placement; call this when the root is added. */
68
+ prepare: (root: RootRef) => Promise<BinRootChoice>;
69
+ /** The choice made for one root, if any (for diagnostics). */
70
+ choiceFor: (root: RootRef) => BinRootChoice | undefined;
71
+ /** Drop one root's cached choice (its root was removed, or its connection changed). */
72
+ forget: (key: string) => void;
73
+ }
74
+ /**
75
+ * Build the resolver.
76
+ *
77
+ * @param deps - injected registration, configuration and measurement seams.
78
+ * @returns the resolver.
79
+ */
80
+ export declare function createBinRootResolver(deps: BinRootResolverDeps): BinRootResolver;
81
+ //# sourceMappingURL=bin-root-resolver.d.ts.map
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Resolve a connection's **home directory** on the remote host.
3
+ *
4
+ * Why this exists: the directory browser used to start remote listings at `/` (the filesystem root), which is
5
+ * almost never what someone adding a remote folder wants — and on a shared server it shows every other
6
+ * account's home. The obvious "fix" of spelling `home/<user>` is **wrong**, and this host proves it: the login
7
+ * user's home is `/home1/yezhiyuan` while `/home/yezhiyuan` does not exist, because the site spreads accounts
8
+ * across `/home`, `/home1`, `/home2`, … (an NFS/automount convention). Only the host can answer the question,
9
+ * so the answer is **probed, never guessed**.
10
+ *
11
+ * Precedence, highest first:
12
+ *
13
+ * 1. `remoteDefaultRoot` from the connection's own configuration — an explicit choice always wins.
14
+ * 2. A probe of the login user's home (`printf %s "$HOME"`), run over the connection's transport.
15
+ * 3. `/`, the old behaviour, when the probe cannot answer (a transport without command execution, a shell
16
+ * that prints nothing, a path that is not absolute). Degrading to the previous behaviour keeps a browse
17
+ * surface usable instead of empty.
18
+ *
19
+ * Results are cached per connection: the answer is a property of the account, not of the moment, and browsing
20
+ * re-asks on every level change. {@link RemoteHomeResolver.forget} drops one entry when a connection changes.
21
+ *
22
+ * @module dsh-over-ssh/integration/remote-home
23
+ */
24
+ /** The fallback every path of last resort uses. */
25
+ export declare const REMOTE_ROOT_FALLBACK = "/";
26
+ /** Options for {@link createRemoteHomeResolver}. */
27
+ export interface RemoteHomeResolverOptions {
28
+ /**
29
+ * Probe the login user's home over the connection's transport.
30
+ *
31
+ * @returns the absolute path, or `undefined` when the host could not answer.
32
+ */
33
+ readonly probe: (connectionId: string) => Promise<string | undefined>;
34
+ /** The connection's configured `remoteDefaultRoot`, when it has one. */
35
+ readonly configuredRoot?: (connectionId: string) => string | undefined;
36
+ }
37
+ /** The resolver the route calls. */
38
+ export interface RemoteHomeResolver {
39
+ /**
40
+ * The directory a remote browse should start at.
41
+ *
42
+ * @param connectionId - the connection to resolve for.
43
+ * @returns an absolute path; never empty, and never throws (failures degrade to `/`).
44
+ */
45
+ resolve(connectionId: string): Promise<string>;
46
+ /** Drop one connection's cached answer (its profile changed, or it was removed). */
47
+ forget(connectionId: string): void;
48
+ /** Drop every cached answer. */
49
+ disposeAll(): void;
50
+ }
51
+ /**
52
+ * Whether a string is usable as a remote directory to list.
53
+ *
54
+ * The same fence the route applies to client-supplied paths: absolute, and no `..` segment. A probe that
55
+ * answers something else (a shell banner, a relative path, a trailing newline artefact) must not become the
56
+ * listing target.
57
+ *
58
+ * @param value - candidate path.
59
+ * @returns the normalised path, or `undefined` when it is unusable.
60
+ */
61
+ export declare function normalizeRemoteDir(value: string | undefined): string | undefined;
62
+ /**
63
+ * Build the resolver.
64
+ *
65
+ * @param options - injected probe and configuration reader.
66
+ * @returns the resolver.
67
+ */
68
+ export declare function createRemoteHomeResolver(options: RemoteHomeResolverOptions): RemoteHomeResolver;
69
+ //# sourceMappingURL=remote-home.d.ts.map
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The root registry: which directories a workspace has, and which backend serves each one.
3
+ *
4
+ * It owns exactly one {@link RoutedFileSystem} and mutates it in place, so the object handed to the
5
+ * core `ctx.fs` provider keeps its identity across root changes (a rebuilt router would silently
6
+ * invalidate every target key a caller is holding).
7
+ *
8
+ * Backend construction is injected: the local and remote backends need `node:fs` and a transport
9
+ * respectively, and keeping those out of this module is what makes the registry testable without a
10
+ * filesystem or a network.
11
+ *
12
+ * The registry also owns the **session workspace roots** ({@link RootRegistry.syncSessionWorkspaces}): one
13
+ * hidden, session-scoped local root per live session cwd, so a session's own working directory is always
14
+ * resolvable — including by absolute path without a cwd, which is how the platform reads the workspace while a
15
+ * turn starts.
16
+ *
17
+ * @module dsh-over-ssh/integration/root-registry
18
+ */
19
+ import type { Backend } from '../../app/backend.ts';
20
+ import { RoutedFileSystem, type RoutedTarget } from '../../app/routed-fs.ts';
21
+ import { type RootOptions, type RootRef } from '../../domain/roots/root.ts';
22
+ /** One registered root together with its backend. */
23
+ export interface RootRecord {
24
+ readonly key: string;
25
+ readonly root: RootRef;
26
+ readonly backend: Backend;
27
+ }
28
+ /** Options for {@link RootRegistry}. */
29
+ export interface RootRegistryOptions {
30
+ /** Builds the backend for a local root. */
31
+ readonly createLocalBackend: (root: RootRef) => Backend;
32
+ /** Builds the backend for a remote root (one per connection in this design). */
33
+ readonly createRemoteBackend: (root: RootRef) => Backend;
34
+ readonly caseInsensitive?: boolean;
35
+ readonly maxReadBytes?: number;
36
+ /** Called after every mutation, so a client half or a status view can refresh. */
37
+ readonly onChanged?: (records: readonly RootRecord[]) => void;
38
+ }
39
+ /** Remote-root options. */
40
+ export interface RemoteRootOptions extends RootOptions {
41
+ /** Local empty anchor directory; DSH requires a workspace path that exists on the host. */
42
+ readonly anchorPath?: string;
43
+ }
44
+ /** The set of roots of one workspace. */
45
+ export declare class RootRegistry {
46
+ #private;
47
+ constructor(options: RootRegistryOptions);
48
+ /** The router every consumer reads through (`ctx.fs` wraps this). */
49
+ get routed(): RoutedFileSystem;
50
+ /** Registered roots in registration order. **Hidden roots are not here** (see {@link addHidden}). */
51
+ get records(): readonly RootRecord[];
52
+ /**
53
+ * Register a root that only the router knows about.
54
+ *
55
+ * Used for the file-level recycle bin, which has to live outside the caller's repository and therefore
56
+ * outside every visible root (ADR-0011). The backend is built exactly as for a visible root, so routing,
57
+ * `stat`, `list`, `move` and `remove` all behave normally — what changes is only that nothing lists it.
58
+ *
59
+ * Idempotent: registering the same key twice returns the existing record instead of throwing, because the
60
+ * caller (a bin resolver) may be asked for the same root repeatedly and a duplicate is not an error there.
61
+ *
62
+ * @param root - the hidden root to register.
63
+ * @returns the record, newly created or already present.
64
+ */
65
+ addHidden(root: RootRef): RootRecord;
66
+ /** Hidden roots registered so far (for tests and diagnostics — never for a user-facing list). */
67
+ get hiddenRecords(): readonly RootRecord[];
68
+ /** Look up one hidden root by key. */
69
+ getHidden(key: string): RootRecord | undefined;
70
+ /** Remove one hidden root by key (used when a connection's bin can no longer be resolved). */
71
+ removeHidden(key: string): boolean;
72
+ /** Session-workspace roots added by {@link syncSessionWorkspaces} — diagnostics and tests, never a list. */
73
+ get sessionWorkspaceRecords(): readonly RootRecord[];
74
+ /**
75
+ * Register the **live sessions' working directories** as session-scoped local roots.
76
+ *
77
+ * The failure this fixes is turn-level, not tool-level: DSH reads the workspace (`AGENTS.md` and friends)
78
+ * through `ctx.fs` while a turn is starting, and `@deepseek-ai/dsh-agent-instructions` resolves that file by
79
+ * **absolute path without a cwd** (`fileSystem.resolve(join(current, marker), { signal })`). A cwd no
80
+ * registered root covers therefore failed the whole turn before the model was called — and a fallback keyed on
81
+ * `cwd` cannot help, because that call carries none. So every live session's working directory becomes a real
82
+ * local root: **in memory only** (never written to `user-roots.json`), scoped to its session
83
+ * (`workspaces: [cwd]`, which {@link rootVisibleToSession} already reads), and hidden
84
+ * ({@link addHidden}) so no surface built from `records` shows a directory the user never added.
85
+ *
86
+ * Idempotent and cheap: a cwd already covered by a local root is skipped, so a registered root keeps its
87
+ * priority and its backend; a cwd that already has its session root is reused. Roots whose session is gone are
88
+ * pruned — an access grant must not outlive the session it was created for — but only when the caller reports
89
+ * sessions at all: an empty list means "the session service was not readable", not "no session is live", and
90
+ * dropping every grant on a transient read failure would break live turns.
91
+ *
92
+ * @param cwds - working directories of the live sessions.
93
+ * @returns the session roots this call added (empty when every cwd was already covered).
94
+ */
95
+ syncSessionWorkspaces(cwds: readonly string[]): readonly RootRecord[];
96
+ /** Register a local directory root. */
97
+ addLocal(path: string, options?: RootOptions): RootRecord;
98
+ /** Register a remote directory root on one connection. */
99
+ addRemote(connectionId: string, path: string, options?: RemoteRootOptions): RootRecord;
100
+ /** Register an already-built root reference. */
101
+ add(root: RootRef): RootRecord;
102
+ /** Remove one root by key. */
103
+ remove(key: string): boolean;
104
+ /**
105
+ * Remove one root by a key that may differ in case from the registered one.
106
+ *
107
+ * Windows local keys are folded by {@link rootKey}, while the file and the client keep the caller's case, so
108
+ * a removal that only matched verbatim removed the file entry and left the root live. The exact match is
109
+ * tried first; the fallback folds **only local keys**, where case is not significant on Windows and where
110
+ * the mismatch can only come from that spelling difference.
111
+ *
112
+ * @param key - the key the caller holds.
113
+ * @returns the key the root was registered under, so related caches can forget the same spelling; `undefined`
114
+ * when nothing matched.
115
+ */
116
+ removeByLooseKey(key: string): string | undefined;
117
+ /** Replace every root (workspace switch, profile reload). Hidden roots go too: they are derived from these. */
118
+ reset(roots: readonly RootRef[]): void;
119
+ /** Roots belonging to one connection, for connection teardown and status. */
120
+ forConnection(connectionId: string): readonly RootRecord[];
121
+ /** Look one root up by key. */
122
+ get(key: string): RootRecord | undefined;
123
+ /**
124
+ * Remove every root of one connection (the connection was deleted or its transport is gone).
125
+ *
126
+ * @param connectionId - connection whose roots should disappear.
127
+ * @returns the removed records, so a caller can report what was dropped.
128
+ */
129
+ removeConnection(connectionId: string): readonly RootRecord[];
130
+ }
131
+ /** Declaration merging so the provider can read the registry from `ctx` with a typed property. */
132
+ declare module '@deepseek-ai/cordis' {
133
+ interface Context {
134
+ /** Root registry provided by this plugin. */
135
+ sshDevRoots: RootRegistry;
136
+ }
137
+ }
138
+ /** Narrow view used by the `ctx.fs` provider: it only needs the router. */
139
+ export interface RoutedTargetOwner {
140
+ readonly routed: RoutedFileSystem;
141
+ }
142
+ /** Re-exported for consumers that only need the target type. */
143
+ export type { RoutedTarget };
144
+ //# sourceMappingURL=root-registry.d.ts.map