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
@@ -1,302 +0,0 @@
1
- /**
2
- * The `ssh2` transport: a pure-JS SSH client.
3
- *
4
- * Why a second implementation:
5
- *
6
- * - **Password authentication** cannot be done by a non-interactive system `ssh` (it hardcodes
7
- * `BatchMode`-style behaviour and would block on a prompt).
8
- * - **Dynamic ProxyJump** chains (per-connection host/port/credentials) cannot be expressed
9
- * through the system client's argv without writing a temporary ssh config.
10
- * - **Connection reuse on Windows**: the host platform's OpenSSH does not support ControlMaster
11
- * (measured: `getsockname failed: Not a socket`), so the only way to reuse one connection for
12
- * many commands there is a client we own. See `doc/03-decisions/0006-windows-host-no-controlmaster.md`.
13
- *
14
- * Everything the plugin needs from the remote host is expressed with the *same* helpers as the
15
- * alias transport — the same probe script, the same shell quoting, the same `size:mtimeSeconds`
16
- * version token, the same failure classification — so behaviour does not change when a connection
17
- * switches transports. File bytes travel over the SFTP subsystem (part of OpenSSH, still zero
18
- * install) instead of base64 over the exec channel.
19
- *
20
- * The client itself is injected (`Ssh2Connector`), so this module never imports the `ssh2`
21
- * package: unit tests run without it and without a network.
22
- *
23
- * @module dsh-remote-workspace/infra/transport/ssh2-transport
24
- */
25
- import { type RemoteCapabilities } from '../../domain/capability.ts';
26
- import type { HostKeyPolicy } from '../../domain/connection.ts';
27
- import { SshDevError } from '../../domain/errors.ts';
28
- import { type ExecRequest, type ExecResult, type ReadFileRangeRequest, type ReadFileRequest, type RemoteFileChunk, type RemoteWriteOutcome, type RemoveRequest, type RenameRequest, type Transport, type WriteFileRangeRequest, type WriteFileRequest } from './transport.ts';
29
- import { TransportBase } from './transport-base.ts';
30
- /** Authentication material for one hop. */
31
- export interface Ssh2Auth {
32
- readonly password?: string;
33
- readonly privateKey?: string | Uint8Array;
34
- readonly passphrase?: string;
35
- /** ssh-agent socket path, or `pageant` on Windows. */
36
- readonly agent?: string;
37
- }
38
- /** One host to connect to. */
39
- export interface Ssh2Target {
40
- readonly host: string;
41
- readonly port?: number;
42
- readonly username: string;
43
- readonly auth: Ssh2Auth;
44
- }
45
- /** Everything needed to open a session. */
46
- export interface Ssh2ConnectConfig {
47
- /** Final target. */
48
- readonly target: Ssh2Target;
49
- /** Ordered jump hosts: hop 0 is reached directly, each next hop through the previous one. */
50
- readonly jump?: readonly Ssh2Target[];
51
- readonly hostKeyPolicy?: HostKeyPolicy;
52
- /** Accepted host key fingerprints (`SHA256:…`), required when the policy is `strict`. */
53
- readonly trustedHostKeys?: readonly string[];
54
- readonly connectTimeoutMs?: number;
55
- readonly keepAliveMs?: number;
56
- /** Cancellation for the connect attempt. */
57
- readonly signal?: AbortSignal;
58
- }
59
- /**
60
- * An SFTP file handle.
61
- *
62
- * Opaque on purpose: `ssh2` hands back a raw `Buffer` handle that is only meaningful to
63
- * `sftp.read` / `sftp.close` (verified against the real library — there is **no**
64
- * `handle.read` method, which is exactly the mistake this shape prevents).
65
- */
66
- export type Ssh2FileHandle = unknown;
67
- /** SFTP statistics as far as this transport needs them. */
68
- export interface Ssh2Stats {
69
- /** Base name of a `readdir` entry; absent on objects returned by `stat`. */
70
- readonly filename?: string;
71
- readonly size: number;
72
- /**
73
- * Modification time in **seconds** since the epoch.
74
- *
75
- * SFTP v3 attributes carry seconds, and `ssh2` passes them through unchanged (verified on a
76
- * real host: `mtime = 1789217901`). The alias transport's `stat -c %Y` also reports seconds, so
77
- * keeping the same unit is what makes version tokens comparable across transports.
78
- */
79
- readonly mtime: number;
80
- /**
81
- * SFTP file mode bits (`stats.mode & 0o170000`).
82
- *
83
- * Optional because a caller may hand over a `Stats`-shaped object without it, but the real
84
- * library always sets it. It is the only reliable way to detect a **symbolic link** during a
85
- * recursive removal: SFTP v3 `readdir` reports the link itself and never follows it.
86
- */
87
- readonly mode?: number;
88
- /** `readdir` long name; starts with `l` for a symbolic link (`-` file, `d` directory). */
89
- readonly longname?: string;
90
- isDirectory?(): boolean;
91
- isFile?(): boolean;
92
- isSymbolicLink?(): boolean;
93
- }
94
- /**
95
- * The narrow SFTP surface this transport uses, mirroring the real `ssh2` API:
96
- * `open` yields a handle, and reads go through `read(handle, …)`, not through the handle.
97
- *
98
- * `readdir` / `rmdir` are optional for the same reason `ext_openssh_rename` is: a fake or a
99
- * non-OpenSSH server may not provide them, and {@link Ssh2Transport.remove} must then report
100
- * `REMOTE_UNSUPPORTED` instead of pretending a tree went away.
101
- */
102
- export interface Ssh2Sftp {
103
- stat(path: string, callback: (error?: Error, stats?: Ssh2Stats) => void): void;
104
- open(path: string, flags: string, callback: (error?: Error, handle?: Ssh2FileHandle) => void): void;
105
- /** Read into a real `Buffer` (the library rejects plain `Uint8Array` views). */
106
- read(handle: Ssh2FileHandle, buffer: Buffer, offset: number, length: number, position: number, callback: (error?: Error, bytesRead?: number) => void): void;
107
- /**
108
- * Positioned write into an open handle. `position` is the **file** offset; `offset` is the
109
- * offset inside `buffer` (the same parameter order as the real library).
110
- */
111
- write(handle: Ssh2FileHandle, buffer: Buffer, offset: number, length: number, position: number, callback: (error?: Error) => void): void;
112
- close(handle: Ssh2FileHandle, callback: (error?: Error) => void): void;
113
- /** Statistics for an **open handle**, used to size a positioned write before truncating. */
114
- fstat?(handle: Ssh2FileHandle, callback: (error?: Error, stats?: Ssh2Stats) => void): void;
115
- /** Truncate an open handle to `length` bytes. */
116
- ftruncate?(handle: Ssh2FileHandle, length: number, callback: (error?: Error) => void): void;
117
- writeFile(path: string, data: Uint8Array, callback: (error?: Error) => void): void;
118
- mkdir(path: string, attributes: {
119
- readonly mode: number;
120
- }, callback: (error?: Error) => void): void;
121
- /** List a directory. The v3 protocol returns `.` and `..` too, so callers must skip them. */
122
- readdir?(path: string, callback: (error?: Error, entries?: readonly Ssh2Stats[]) => void): void;
123
- /** Remove an **empty** directory. */
124
- rmdir?(path: string, callback: (error?: Error) => void): void;
125
- /**
126
- * Plain SFTP rename.
127
- *
128
- * Measured on a real OpenSSH server: this **fails with SSH_FX_FAILURE when the destination
129
- * already exists**, so it cannot be the only path for an overwriting write.
130
- */
131
- rename(from: string, to: string, callback: (error?: Error) => void): void;
132
- /**
133
- * `posix-rename@openssh.com`: atomic overwrite. Present on OpenSSH servers/clients, absent
134
- * elsewhere — which is why {@link writeFile} keeps a fallback ladder.
135
- */
136
- ext_openssh_rename?: ((from: string, to: string, callback: (error?: Error) => void) => void) | undefined;
137
- unlink(path: string, callback: (error?: Error) => void): void;
138
- end(): void;
139
- }
140
- /** A command channel as far as this transport needs it. */
141
- export interface Ssh2Channel {
142
- on(event: 'data', listener: (chunk: Buffer) => void): this;
143
- on(event: 'close', listener: (code: number | null, signal: string | null) => void): this;
144
- on(event: string, listener: (...args: never[]) => void): this;
145
- stderr: {
146
- on(event: 'data', listener: (chunk: Buffer) => void): unknown;
147
- };
148
- write(data: string | Uint8Array): boolean;
149
- end(data?: string | Uint8Array): void;
150
- close(): void;
151
- }
152
- /** An open SSH session: many channels, one connection. */
153
- export interface Ssh2Session {
154
- exec(command: string, options: {
155
- readonly pty?: boolean;
156
- }, callback: (error?: Error, channel?: Ssh2Channel) => void): void;
157
- sftp(callback: (error?: Error, sftp?: Ssh2Sftp) => void): void;
158
- /** Open a TCP channel through this session, used to chain jump hosts. */
159
- forwardOut(sourceAddress: string, sourcePort: number, destinationAddress: string, destinationPort: number, callback: (error?: Error, stream?: unknown) => void): void;
160
- end(): void;
161
- destroy(): void;
162
- /**
163
- * Intermediate jump-host sessions this session rides on, so `dispose()` can close them too.
164
- *
165
- * **Deliberately per-session, not a module-level slot.** The first version of this fix kept them in
166
- * a module-level `lastHops`, which is wrong as soon as one process has two connections
167
- * (`TransportService` caches by `profile.id`, so multiple profiles are normal):
168
- * - the second `connect` cleared the array, so the first connection's hops were **leaked**;
169
- * - the first `dispose()` then took the *second* connection's hops and **ended a live connection
170
- * another transport was still using**.
171
- * Hanging them on the session they belong to makes ownership structural instead of positional.
172
- * `ssh2`'s real `Client` has no such property, so it is optional and set only by
173
- * {@link defaultConnector}.
174
- */
175
- readonly hops?: readonly Ssh2Session[];
176
- }
177
- /** Opens a session; injected so tests never load the real client. */
178
- export type Ssh2Connector = (config: Ssh2ConnectConfig) => Promise<Ssh2Session>;
179
- /** Construction options. */
180
- export interface Ssh2TransportOptions {
181
- readonly connectionId: string;
182
- /** Session factory. Defaults to the real `ssh2` client (loaded lazily). */
183
- readonly connector?: Ssh2Connector;
184
- readonly connect?: Ssh2ConnectConfig;
185
- readonly commandTimeoutMs?: number;
186
- readonly now?: () => number;
187
- }
188
- /**
189
- * Bytes of a command's **stderr** that are retained.
190
- *
191
- * `maxBytes` bounds stdout only, and stderr used to be accumulated without any bound at all: a command
192
- * that writes diagnostics in a loop (`ssh -v`, a verbose build, a remote program that logs errors) grew
193
- * this process's heap until the channel closed. Measured before the fix: a peer emitting 64 MiB of
194
- * stderr retained **all 64 MiB** while stdout was correctly capped at 1 KiB (审计 0050).
195
- *
196
- * The **tail** is kept, not the head: every consumer of this string wants the last thing the command
197
- * said. `domain/errors.ts` reduces it with `tail(stderr, 2000)` for classification, and a failing
198
- * command's useful diagnostic (the final error, the `DSH_ERROR=` refusal) is at the end.
199
- */
200
- export declare const SSH2_STDERR_MAX_BYTES: number;
201
- /**
202
- * First line prepended to a truncated stderr capture.
203
- *
204
- * The truncation is announced **in the string itself** because `ExecResult.truncated` is stdout's flag
205
- * (see its JSDoc) and must keep that meaning: `remote-backend` treats a truncated batch read as "these
206
- * paths were not read", so setting it for a chatty command's stderr would report readable files as
207
- * missing. Announcing it here keeps the diagnostic honest without overloading the flag.
208
- */
209
- export declare const STDERR_TRUNCATION_NOTICE = "\uFF08stderr \u8FC7\u957F\uFF0C\u4EC5\u4FDD\u7559\u672B\u5C3E\u7247\u6BB5\uFF09";
210
- /**
211
- * The pure-JS transport.
212
- *
213
- * One session serves every command and every file transfer, which is what gives Windows hosts
214
- * the connection reuse their OpenSSH client cannot provide.
215
- */
216
- export declare class Ssh2Transport extends TransportBase implements Transport {
217
- #private;
218
- readonly id: string;
219
- constructor(options: Ssh2TransportOptions);
220
- get sessionCount(): number;
221
- /** Open the session; concurrent calls share one attempt. */
222
- connect(signal?: AbortSignal): Promise<void>;
223
- /** Close the session. Idempotent. */
224
- dispose(): Promise<void>;
225
- /** Run one remote command over a channel of the shared session. */
226
- exec(request: ExecRequest, signal?: AbortSignal): Promise<ExecResult>;
227
- /**
228
- * Read a remote file over SFTP.
229
- *
230
- * The version token is `size:mtimeSeconds` — the same shape the alias transport produces, so
231
- * caches keyed on version tokens keep working when a connection changes transport.
232
- */
233
- readFile(request: ReadFileRequest, signal?: AbortSignal): Promise<RemoteFileChunk>;
234
- /**
235
- * Read `maxBytes` from `offset` over SFTP, seeking with a real `position`.
236
- *
237
- * Why the seek matters: `readFile` always starts at 0, so paging through a large file by
238
- * reading `[0, offset+len)` and slicing transfers every earlier byte on every page. Here the
239
- * server only sends the requested window, and only `maxBytes` is ever allocated locally.
240
- */
241
- readFileRange(request: ReadFileRangeRequest, signal?: AbortSignal): Promise<RemoteFileChunk>;
242
- /**
243
- * Write at `offset` over SFTP, leaving the bytes before it untouched.
244
- *
245
- * Flags are chosen by offset, and the semantics are deliberately narrow:
246
- *
247
- * - `offset === 0` uses `'w'`: the file is **created or truncated**, so a chunked upload that
248
- * restarts from 0 overwrites a longer previous file instead of leaving a stale tail.
249
- * - `offset > 0` uses `'r+'`: the file **must already exist** (there is no other way to preserve
250
- * the earlier bytes), otherwise `REMOTE_NOT_FOUND`. After the positioned write the file is
251
- * truncated to `offset + bytes.byteLength` — a shorter final chunk must shorten the file, or
252
- * the tail of a previous, longer upload would survive and corrupt the result.
253
- */
254
- writeFileRange(request: WriteFileRangeRequest, signal?: AbortSignal): Promise<RemoteWriteOutcome>;
255
- /**
256
- * Move a remote path with a plain SFTP `rename`.
257
- *
258
- * A cross-device move makes the server fail (`SSH_FX_FAILURE` / `EXDEV`) and that failure is
259
- * **propagated as-is**: copying would silently turn a move into a copy, which breaks the
260
- * "move, then delete the source only after confirmation" semantics callers rely on.
261
- */
262
- rename(request: RenameRequest, signal?: AbortSignal): Promise<void>;
263
- /**
264
- * Remove a file, or a directory tree when `recursive` is set.
265
- *
266
- * A directory without `recursive` is an explicit error, never a silent success: a caller that
267
- * hears "removed" must be able to trust that the path is gone.
268
- *
269
- * Symbolic links are never followed. SFTP v3 `readdir` reports a link as an entry with
270
- * `mode & 0o170000 === 0o120000`; treating it as a directory would either recurse forever on a
271
- * self-referential link or descend into a tree that was never requested for deletion.
272
- */
273
- remove(request: RemoveRequest, signal?: AbortSignal): Promise<void>;
274
- /**
275
- * Write a remote file over SFTP: temp file in the target directory, then a rename.
276
- *
277
- * The optional `expectedSha256` guard runs the same remote digest tool the alias transport
278
- * uses, so a guarded write means the same thing on both transports.
279
- */
280
- writeFile(request: WriteFileRequest, signal?: AbortSignal): Promise<RemoteWriteOutcome>;
281
- /**
282
- * Probe the remote host once and remember the result.
283
- *
284
- * @param signal - cancellation.
285
- * @param maxAgeMs - reuse a previous probe inside this window (default 5 minutes).
286
- * @returns the discovered capabilities.
287
- */
288
- probeCapabilities(signal?: AbortSignal, maxAgeMs?: number): Promise<RemoteCapabilities>;
289
- }
290
- /** Version token shared by every transport: `size:mtimeSeconds`. */
291
- export declare function versionToken(size: number, mtimeSeconds: number): string;
292
- /** Map an SFTP failure onto a plugin error code. */
293
- export declare function sftpError(operation: string, path: string, error?: Error): SshDevError;
294
- /**
295
- * The real connector: wraps the `ssh2` package.
296
- *
297
- * Loaded lazily so this module (and its unit tests) never require the dependency, and required
298
- * for dynamic jump chains: each hop is reached by forwarding a TCP channel through the previous
299
- * session.
300
- */
301
- export declare const defaultConnector: Ssh2Connector;
302
- //# sourceMappingURL=ssh2-transport.d.ts.map
@@ -1,33 +0,0 @@
1
- /**
2
- * The install ladder's I/O, built on the connection's **Tier 1 transport**.
3
- *
4
- * The agent cannot install itself, so every step of the ladder runs through the transport that already
5
- * works: `exec` for the probe, `writeFile` for the upload (the Tier 1 transports already implement bounded,
6
- * atomic remote writes), and one more `exec` for the digest. Nothing here opens a second authentication
7
- * path, which is what keeps "upload" as trustworthy as "run a command".
8
- *
9
- * @module dsh-over-ssh/integration/agent-install-io
10
- */
11
- import type { AgentInstallIo } from '../../infra/transport/agent/agent-installer.ts';
12
- import type { Transport } from '../../infra/transport/transport.ts';
13
- /** Options for {@link createAgentInstallIo}. */
14
- export interface AgentInstallIoOptions {
15
- /** The Tier 1 transport of the connection being installed onto. */
16
- readonly transport: Transport;
17
- /** Refuse to upload anything larger than this (a corrupt manifest must not fill the host's disk). */
18
- readonly maxUploadBytes?: number;
19
- /** Local file reader, injected so tests never touch a real file. */
20
- readonly readLocalFile?: (path: string) => Promise<Uint8Array>;
21
- /** Local size lookup, so an oversized artifact is rejected before it is read into memory. */
22
- readonly localSize?: (path: string) => Promise<number>;
23
- /** Cap for exec output while probing; the ladder only needs tiny answers. */
24
- readonly probeMaxBytes?: number;
25
- }
26
- /**
27
- * Build the ladder's I/O from an existing transport.
28
- *
29
- * @param options - transport plus optional readers and caps.
30
- * @returns an {@link AgentInstallIo} that never opens a second connection.
31
- */
32
- export declare function createAgentInstallIo(options: AgentInstallIoOptions): AgentInstallIo;
33
- //# sourceMappingURL=agent-install-io.d.ts.map
@@ -1,66 +0,0 @@
1
- /**
2
- * Per-connection store of `SSH_ASKPASS` helpers.
3
- *
4
- * Why this exists as a module: preparing a helper is **async** (it reads the secret from the credential
5
- * service and writes two files), but transport factories are **synchronous** and are also reached from
6
- * `createAlias` during the install ladder. The two are joined here: something async calls
7
- * {@link AskpassRegistry.prepare} first, and the factory then reads {@link AskpassRegistry.envFor}
8
- * without awaiting.
9
- *
10
- * Three rules:
11
- *
12
- * 1. **One helper per connection.** Preparing twice returns the same handle, so a reconnect does not
13
- * accumulate temp directories (and the secret is read once).
14
- * 2. **Only password connections get one.** Key and agent connections never prompt; handing them
15
- * `BatchMode=no` would let a misconfigured host block on an interactive prompt forever.
16
- * 3. **A failure is a degradation, not a throw.** If the secret cannot be resolved the connection still
17
- * works through the transports that do not need it; {@link AskpassRegistry.prepare} reports `undefined`
18
- * and the caller keeps its previous behaviour.
19
- *
20
- * @module dsh-over-ssh/integration/askpass-registry
21
- */
22
- import { type AskpassHandle } from '../../infra/transport/ssh/askpass.ts';
23
- import type { ConnectionProfile } from '../../domain/roots/connection.ts';
24
- /** Options for {@link createAskpassRegistry}. */
25
- export interface AskpassRegistryOptions {
26
- /** Resolve the connection's password. Throws when it is missing or the credential service is absent. */
27
- readonly readSecret: (profile: ConnectionProfile) => Promise<string>;
28
- /** Materialise one helper for a secret. */
29
- readonly create: (secret: string) => Promise<AskpassHandle>;
30
- /** Optional sink for "we could not prepare one" so the reason is not swallowed. */
31
- readonly onUnavailable?: (connectionId: string, reason: string) => void;
32
- }
33
- /** The registry surface the composition root needs. */
34
- export interface AskpassRegistry {
35
- /**
36
- * Ensure a helper exists for this connection.
37
- *
38
- * @param profile - the connection.
39
- * @returns the environment to hand `ssh`, or `undefined` when this connection needs none or preparation
40
- * failed (in which case the caller must keep its non-prompting behaviour).
41
- */
42
- prepare(profile: ConnectionProfile): Promise<Record<string, string> | undefined>;
43
- /** The environment prepared for this connection, if any. Synchronous, for transport factories. */
44
- envFor(profile: ConnectionProfile): Record<string, string> | undefined;
45
- /** Whether a connection is currently using a password helper. */
46
- has(connectionId: string): boolean;
47
- /** Drop one connection's helper (its transport was released, or the connection changed). */
48
- forget(connectionId: string): Promise<void>;
49
- /**
50
- * Allow a retired connection id to prepare a helper again.
51
- *
52
- * Call this only when the connection is deliberately re-established. Without it, a re-saved connection
53
- * could never get a password helper, because `forget` leaves a tombstone that `prepare` honours.
54
- */
55
- revive(connectionId: string): void;
56
- /** Drop every helper (plugin unload). */
57
- disposeAll(): Promise<void>;
58
- }
59
- /**
60
- * Build the registry.
61
- *
62
- * @param options - injected secret reader, helper factory and optional sink.
63
- * @returns the registry.
64
- */
65
- export declare function createAskpassRegistry(options: AskpassRegistryOptions): AskpassRegistry;
66
- //# sourceMappingURL=askpass-registry.d.ts.map
@@ -1,33 +0,0 @@
1
- /**
2
- * The install ladder's I/O, built on the connection's **Tier 1 transport**.
3
- *
4
- * The agent cannot install itself, so every step of the ladder runs through the transport that already
5
- * works: `exec` for the probe, `writeFile` for the upload (the Tier 1 transports already implement bounded,
6
- * atomic remote writes), and one more `exec` for the digest. Nothing here opens a second authentication
7
- * path, which is what keeps "upload" as trustworthy as "run a command".
8
- *
9
- * @module dsh-remote-workspace/integration/agent-install-io
10
- */
11
- import type { AgentInstallIo } from '../infra/transport/agent-installer.ts';
12
- import type { Transport } from '../infra/transport/transport.ts';
13
- /** Options for {@link createAgentInstallIo}. */
14
- export interface AgentInstallIoOptions {
15
- /** The Tier 1 transport of the connection being installed onto. */
16
- readonly transport: Transport;
17
- /** Refuse to upload anything larger than this (a corrupt manifest must not fill the host's disk). */
18
- readonly maxUploadBytes?: number;
19
- /** Local file reader, injected so tests never touch a real file. */
20
- readonly readLocalFile?: (path: string) => Promise<Uint8Array>;
21
- /** Local size lookup, so an oversized artifact is rejected before it is read into memory. */
22
- readonly localSize?: (path: string) => Promise<number>;
23
- /** Cap for exec output while probing; the ladder only needs tiny answers. */
24
- readonly probeMaxBytes?: number;
25
- }
26
- /**
27
- * Build the ladder's I/O from an existing transport.
28
- *
29
- * @param options - transport plus optional readers and caps.
30
- * @returns an {@link AgentInstallIo} that never opens a second connection.
31
- */
32
- export declare function createAgentInstallIo(options: AgentInstallIoOptions): AgentInstallIo;
33
- //# sourceMappingURL=agent-install-io.d.ts.map
@@ -1,56 +0,0 @@
1
- /**
2
- * The bridge between the install ladder and transport selection.
3
- *
4
- * Selection is synchronous (it may only consult cached facts) while the ladder must talk to the host, so
5
- * the two are joined by an **async preflight**: the selector's first answer is revised before any transport
6
- * is built. That is what makes "agent unavailable ⇒ Tier 1" true without making the selector depend on I/O.
7
- *
8
- * Two rules this module exists to enforce:
9
- *
10
- * 1. **No host traffic unless the connection asked for the agent.** A non-agent decision returns
11
- * immediately, so an ordinary alias connection never probes or uploads anything.
12
- * 2. **A failed ladder is a degradation, never an exception.** `ensureAgent` already turns every remote
13
- * problem into a `tier1` outcome; this module forwards that as a revised decision plus a hint.
14
- *
15
- * @module dsh-remote-workspace/integration/agent-preflight
16
- */
17
- import type { TransportPreflight } from '../app/transport-service.ts';
18
- import type { ConnectionProfile } from '../domain/connection.ts';
19
- import type { AgentInstallation } from '../infra/transport/agent-installer.ts';
20
- /** What the preflight learned about one connection. */
21
- export type AgentPreflightOutcome = {
22
- readonly kind: 'agent';
23
- readonly remotePath: string;
24
- readonly platform: string;
25
- readonly reused: boolean;
26
- } | {
27
- readonly kind: 'tier1';
28
- readonly reason: string;
29
- readonly hint: string;
30
- };
31
- /** Options for {@link createAgentPreflight}. */
32
- export interface AgentPreflightOptions {
33
- /** Run (or reuse a memoized) ladder for one connection. Injected so this module stays offline-testable. */
34
- readonly ensure: (profile: ConnectionProfile, signal?: AbortSignal) => Promise<AgentInstallation>;
35
- /** Optional sink so the connection UI can show what happened. */
36
- readonly onOutcome?: (connectionId: string, outcome: AgentPreflightOutcome) => void;
37
- /** Optional logger for the "we are not even trying" path; defaults to silence. */
38
- readonly onSkipped?: (profile: ConnectionProfile) => void;
39
- }
40
- /** The preflight plus the installation facts the agent factory needs. */
41
- export interface AgentPreflight {
42
- /** Pass this to {@link TransportServiceOptions.preflight}. */
43
- readonly preflight: TransportPreflight;
44
- /** The installation for a connection, once the ladder approved one. */
45
- installationFor(connectionId: string): AgentPreflightOutcome | undefined;
46
- /** Forget one connection (its transport was released, or the user changed the connection). */
47
- forget(connectionId: string): void;
48
- }
49
- /**
50
- * Build the preflight.
51
- *
52
- * @param options - ladder plus optional sinks.
53
- * @returns the preflight, an installation lookup, and a way to forget a connection.
54
- */
55
- export declare function createAgentPreflight(options: AgentPreflightOptions): AgentPreflight;
56
- //# sourceMappingURL=agent-preflight.d.ts.map
@@ -1,56 +0,0 @@
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\\ssh-dev\\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-remote-workspace/integration/anchor-directory
15
- */
16
- import type { RootRef } from '../domain/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
@@ -1,59 +0,0 @@
1
- /**
2
- * Per-connection store of `SSH_ASKPASS` helpers.
3
- *
4
- * Why this exists as a module: preparing a helper is **async** (it reads the secret from the credential
5
- * service and writes two files), but transport factories are **synchronous** and are also reached from
6
- * `createAlias` during the install ladder. The two are joined here: something async calls
7
- * {@link AskpassRegistry.prepare} first, and the factory then reads {@link AskpassRegistry.envFor}
8
- * without awaiting.
9
- *
10
- * Three rules:
11
- *
12
- * 1. **One helper per connection.** Preparing twice returns the same handle, so a reconnect does not
13
- * accumulate temp directories (and the secret is read once).
14
- * 2. **Only password connections get one.** Key and agent connections never prompt; handing them
15
- * `BatchMode=no` would let a misconfigured host block on an interactive prompt forever.
16
- * 3. **A failure is a degradation, not a throw.** If the secret cannot be resolved the connection still
17
- * works through the transports that do not need it; {@link AskpassRegistry.prepare} reports `undefined`
18
- * and the caller keeps its previous behaviour.
19
- *
20
- * @module dsh-remote-workspace/integration/askpass-registry
21
- */
22
- import { type AskpassHandle } from '../infra/transport/askpass.ts';
23
- import type { ConnectionProfile } from '../domain/connection.ts';
24
- /** Options for {@link createAskpassRegistry}. */
25
- export interface AskpassRegistryOptions {
26
- /** Resolve the connection's password. Throws when it is missing or the credential service is absent. */
27
- readonly readSecret: (profile: ConnectionProfile) => Promise<string>;
28
- /** Materialise one helper for a secret. */
29
- readonly create: (secret: string) => Promise<AskpassHandle>;
30
- /** Optional sink for "we could not prepare one" so the reason is not swallowed. */
31
- readonly onUnavailable?: (connectionId: string, reason: string) => void;
32
- }
33
- /** The registry surface the composition root needs. */
34
- export interface AskpassRegistry {
35
- /**
36
- * Ensure a helper exists for this connection.
37
- *
38
- * @param profile - the connection.
39
- * @returns the environment to hand `ssh`, or `undefined` when this connection needs none or preparation
40
- * failed (in which case the caller must keep its non-prompting behaviour).
41
- */
42
- prepare(profile: ConnectionProfile): Promise<Record<string, string> | undefined>;
43
- /** The environment prepared for this connection, if any. Synchronous, for transport factories. */
44
- envFor(profile: ConnectionProfile): Record<string, string> | undefined;
45
- /** Whether a connection is currently using a password helper. */
46
- has(connectionId: string): boolean;
47
- /** Drop one connection's helper (its transport was released, or the connection changed). */
48
- forget(connectionId: string): Promise<void>;
49
- /** Drop every helper (plugin unload). */
50
- disposeAll(): Promise<void>;
51
- }
52
- /**
53
- * Build the registry.
54
- *
55
- * @param options - injected secret reader, helper factory and optional sink.
56
- * @returns the registry.
57
- */
58
- export declare function createAskpassRegistry(options: AskpassRegistryOptions): AskpassRegistry;
59
- //# sourceMappingURL=askpass-registry.d.ts.map