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,44 @@
1
+ /**
2
+ * Adapters that turn the plugin's routing + transports into the injected dependencies the transfer and
3
+ * recycle-bin services expect.
4
+ *
5
+ * Two rules shape everything here:
6
+ *
7
+ * - **Remote work uses the real primitives.** Chunked reads go through `Transport.readFileRange` (verified on
8
+ * a real host: exact window, no whole-file read) and positioned writes through `writeFileRange` (POSIX
9
+ * `pwrite` semantics, shared by all three transports). Where a transport lacks the method the fallback is
10
+ * written down instead of hidden.
11
+ * - **Local work never borrows the remote path.** `node:fs` is opened with `position` arguments so a local
12
+ * chunk does not read the whole file either; the recycle bin's local mutators are a separate object, so a
13
+ * local target can never end up being "moved" by an ssh command.
14
+ *
15
+ * @module dsh-remote-workspace/integration/transfer-adapters
16
+ */
17
+ import type { RootRef } from '../domain/root.ts';
18
+ import type { RoutedFileSystem } from '../app/routed-fs.ts';
19
+ import type { Transport } from '../infra/transport/transport.ts';
20
+ import type { RecycleBinServiceDeps } from '../app/recycle-bin-service.ts';
21
+ import type { TransferDependencies } from './transfer-service.ts';
22
+ /** What both adapters need from the plugin. */
23
+ export interface WorldAdapterDeps {
24
+ /** Path routing: turns a path in any root's spelling into `{root, backend, absolute}`. */
25
+ readonly routed: RoutedFileSystem;
26
+ /** The transport of one connection (the caller already owns the profile lookup). */
27
+ readonly transportFor: (connectionId: string) => Promise<Transport>;
28
+ /** Bin directory **inside one root** (same device, so the move stays a rename). */
29
+ readonly binRootFor: (root: RootRef) => string;
30
+ /** Chunk ceiling passed to the transfer service. */
31
+ readonly chunkBytes: number;
32
+ /** Upper bound for one text read (the index file). */
33
+ readonly maxReadBytes: number;
34
+ /** Clock, injected for tests. */
35
+ readonly now?: () => number;
36
+ }
37
+ /** The transfer service's dependencies. */
38
+ export declare function createTransferDeps(deps: WorldAdapterDeps): TransferDependencies;
39
+ /** The recycle bin's dependencies: remote primitives plus an explicit local world. */
40
+ export declare function createRecycleBinDeps(deps: WorldAdapterDeps & {
41
+ readonly knownRoots: () => readonly RootRef[];
42
+ readonly undoDepth: number;
43
+ }): RecycleBinServiceDeps;
44
+ //# sourceMappingURL=transfer-adapters.d.ts.map
@@ -0,0 +1,164 @@
1
+ /**
2
+ * The remote-file transfer seam: move one file in one direction, in bounded chunks, with progress.
3
+ *
4
+ * This module owns the *policy* half of a transfer — offsets, resume decisions, progress callbacks,
5
+ * cancellation and failure classification — and none of the I/O. Every side effect arrives as an
6
+ * injected dependency (`readLocal`, `writeRemote`, `readRemote`, `writeLocal`, `statLocal`,
7
+ * `statRemote`, optional `sha256Local`, optional clock), so the whole flow can be exercised offline
8
+ * against substitute I/O (AGENTS.md 红线 3), and the same code runs on a real host by wiring the real
9
+ * implementations in [`src/index.ts`] (left to the plugin entry, which this change does not touch).
10
+ *
11
+ * Contract notes that callers and implementers both rely on:
12
+ *
13
+ * - **Memory is bounded by `chunkBytes`**: a chunk is read, written and dropped; the file is never
14
+ * assembled in memory. A test asserts this by recording every `readLocal` request, not by claiming it.
15
+ * - **`offset === undefined` means "replace the target's content with exactly these bytes"**;
16
+ * `offset === n` means "place these bytes at byte n, extending the target". The first write of a
17
+ * fresh transfer therefore carries no offset, and every later chunk carries its absolute offset.
18
+ * - **Progress fires once per completed write** and never decreases: each snapshot is computed from
19
+ * a running byte counter, and the clock is injected so rates are reproducible in tests.
20
+ * - **Cancellation is reported, never swallowed**: an aborted transfer fails with `CANCELLED` and
21
+ * details carrying the bytes that are known to have landed, how many chunks were written, and
22
+ * whether the chunk in flight may have landed partially.
23
+ * - **Failure is classified**: `details.kind` is the precise {@link TransferFailureKind}; the error
24
+ * code stays inside the existing `SSH_DEV_ERROR_CODES` table (see `domain/transfer.ts`).
25
+ *
26
+ * @module dsh-remote-workspace/integration/transfer-service
27
+ */
28
+ import { type TransferDegradation, type TransferProgress } from '../domain/transfer.ts';
29
+ import type { BackendStat } from '../app/backend.ts';
30
+ import type { RemoteWriteOutcome } from '../infra/transport/transport.ts';
31
+ /** Default cap on one read/write step; overridable per call and per service instance. */
32
+ export declare const DEFAULT_TRANSFER_CHUNK_BYTES: number;
33
+ /** One bounded source read. */
34
+ export interface SourceReadOptions {
35
+ /** Byte offset to start at. */
36
+ readonly offset: number;
37
+ /** Maximum bytes to return; a shorter answer means the source ended early. */
38
+ readonly length: number;
39
+ readonly signal?: AbortSignal;
40
+ }
41
+ /** One bounded remote read. */
42
+ export interface RemoteReadOptions {
43
+ /** Byte offset to start at; omitted means 0. */
44
+ readonly offset?: number;
45
+ /** Maximum bytes to return *from `offset`*. */
46
+ readonly maxBytes: number;
47
+ readonly signal?: AbortSignal;
48
+ }
49
+ /** One remote write. */
50
+ export interface RemoteWriteOptions {
51
+ /** `undefined` replaces the file's content; `n` places the bytes at byte `n`. */
52
+ readonly offset?: number;
53
+ /** Refuse the write unless the target's current content has this digest (optimistic guard). */
54
+ readonly expectedSha256?: string;
55
+ readonly signal?: AbortSignal;
56
+ }
57
+ /** One local write (the download side). */
58
+ export interface LocalWriteOptions {
59
+ /** `undefined` replaces the file's content; `n` places the bytes at byte `n`. */
60
+ readonly offset?: number;
61
+ }
62
+ /** Everything a transfer needs from the outside world. */
63
+ export interface TransferDependencies {
64
+ /** Bounded local read. Must not return more than `length` bytes. */
65
+ readonly readLocal: (path: string, options: SourceReadOptions) => Promise<Uint8Array>;
66
+ /**
67
+ * Remote write. `offset === undefined` replaces the target; `offset === n` writes at `n`.
68
+ * Returning {@link RemoteWriteOutcome} is how a remote digest (if any) reaches the caller.
69
+ */
70
+ readonly writeRemote: (absolute: string, bytes: Uint8Array, options: RemoteWriteOptions) => Promise<RemoteWriteOutcome>;
71
+ /**
72
+ * Remote read, bounded by `maxBytes` and optionally starting at `offset`.
73
+ *
74
+ * The sketched Tier 1 transport takes only `maxBytes`; the optional `offset` is what makes a
75
+ * chunked download possible, so an implementation that cannot honour it must say so rather than
76
+ * silently returning the head of the file (a wrong answer is worse than a refused one).
77
+ */
78
+ readonly readRemote: (absolute: string, options: RemoteReadOptions) => Promise<Uint8Array>;
79
+ /** Local write; same offset convention as {@link TransferDependencies.writeRemote}. */
80
+ readonly writeLocal: (path: string, bytes: Uint8Array, options: LocalWriteOptions) => Promise<void>;
81
+ /** Local metadata. Must report `exists: false` for a missing path instead of throwing. */
82
+ readonly statLocal: (path: string) => Promise<BackendStat>;
83
+ /** Remote metadata. Same `exists` rule as {@link TransferDependencies.statLocal}. */
84
+ readonly statRemote: (absolute: string) => Promise<BackendStat>;
85
+ /** Optional digest of a local file; when absent the result reports `sha256: null`. */
86
+ readonly sha256Local?: (path: string) => Promise<string>;
87
+ /**
88
+ * Whether `writeRemote` may continue an existing partial remote file (`resume: true` requests).
89
+ * Defaults to `false`: a requested resume then degrades to a full rewrite **and says so**.
90
+ */
91
+ readonly supportsResume?: boolean;
92
+ /** Default per-chunk cap; a request may override it. */
93
+ readonly chunkBytes?: number;
94
+ /** Clock, injected so rate/remaining estimates are reproducible in tests. */
95
+ readonly nowMs?: () => number;
96
+ }
97
+ /** Common request fields. */
98
+ export interface TransferRequestBase {
99
+ /** Connection the remote path belongs to; recorded in the result and in failures. */
100
+ readonly connectionId: string;
101
+ /** Absolute POSIX root the remote path must stay inside. */
102
+ readonly root: string;
103
+ /** Remote path relative to the root; `..` is folded and can never escape. */
104
+ readonly remotePath: string;
105
+ /** Per-chunk cap for this call; defaults to the service's `chunkBytes`. */
106
+ readonly chunkBytes?: number;
107
+ /** Keep bytes already present on the target when possible; defaults to `false`. */
108
+ readonly resume?: boolean;
109
+ readonly onProgress?: (progress: TransferProgress) => void;
110
+ readonly signal?: AbortSignal;
111
+ }
112
+ /** Upload one local file to the remote root. */
113
+ export interface UploadRequest extends TransferRequestBase {
114
+ /** Local source file. */
115
+ readonly localPath: string;
116
+ /**
117
+ * Digest the remote target must currently have, forwarded to the first `writeRemote` call as its
118
+ * optimistic guard. A dependency that refuses the write surfaces as `TRANSFER_CONFLICT`.
119
+ */
120
+ readonly expectedSha256?: string;
121
+ }
122
+ /** Download one remote file into a local path. */
123
+ export interface DownloadRequest extends TransferRequestBase {
124
+ /** Local destination *file* path (its base name is validated for the host platform). */
125
+ readonly localPath: string;
126
+ }
127
+ /** What one transfer did. */
128
+ export interface TransferResult {
129
+ readonly direction: 'upload' | 'download';
130
+ readonly connectionId: string;
131
+ readonly root: string;
132
+ readonly localPath: string;
133
+ /** Root-absolute remote path the transfer actually used (after `..` folding). */
134
+ readonly remotePath: string;
135
+ readonly totalBytes: number;
136
+ /** Bytes this transfer wrote, excluding any prefix it resumed from. */
137
+ readonly bytesTransferred: number;
138
+ /** Writes performed by this transfer (0 for a resumed transfer with nothing left; 1 for an empty file). */
139
+ readonly chunks: number;
140
+ readonly resumed: boolean;
141
+ /** Offset the transfer started at. */
142
+ readonly startOffset: number;
143
+ readonly durationMs: number;
144
+ /**
145
+ * Digest of the transferred content, or `null` when no digest was obtainable:
146
+ * the remote's own digest for a single non-resumed write, else `sha256Local`'s answer.
147
+ */
148
+ readonly sha256: string | null;
149
+ /** Why something was not done as asked (resume refused, digest unavailable); empty when clean. */
150
+ readonly degradations: readonly TransferDegradation[];
151
+ }
152
+ /** The two operations this seam exposes. */
153
+ export interface TransferService {
154
+ upload(request: UploadRequest): Promise<TransferResult>;
155
+ download(request: DownloadRequest): Promise<TransferResult>;
156
+ }
157
+ /**
158
+ * Build the transfer service.
159
+ *
160
+ * @param dependencies - all local/remote I/O plus the optional clock, digest and resume capability.
161
+ * @returns the upload/download operations; no state is kept between calls.
162
+ */
163
+ export declare function createTransferService(dependencies: TransferDependencies): TransferService;
164
+ //# sourceMappingURL=transfer-service.d.ts.map
@@ -0,0 +1,106 @@
1
+ /**
2
+ * A bounded registry of in-flight and recently finished transfers (FR-2.6.4).
3
+ *
4
+ * Why a registry and not a callback straight into the client: the UI asks "what is happening" whenever it
5
+ * renders, which is a *pull*. Pushing would need a channel this plugin does not have (its client half talks
6
+ * over one HTTP route), so the honest shape is a small table the UI polls.
7
+ *
8
+ * Three rules, each of which is a way a progress UI usually lies:
9
+ *
10
+ * 1. **A finished transfer is kept, not deleted.** A row that vanishes the instant it completes leaves the
11
+ * user unable to tell "done" from "failed and cleared"; the failure list FR-2.6.4 asks for is exactly
12
+ * the recent history.
13
+ * 2. **The table is bounded** (`maxEntries`, oldest finished first). An unbounded map keyed by transfer id is
14
+ * a leak that only shows up after a long session.
15
+ * 3. **`percent` is computed from the bytes that landed, never extrapolated.** A progress bar that jumps to
16
+ * the end because a chunk was *planned* is worse than no bar.
17
+ *
18
+ * The module imports no cordis and no `node:fs`: it is a table plus transitions, so it is testable offline.
19
+ *
20
+ * @module dsh-remote-workspace/integration/transfer-tracker
21
+ */
22
+ import { type TransferProgress } from '../domain/transfer.ts';
23
+ /** Lifecycle of one tracked transfer. */
24
+ export type TrackedTransferStatus = 'running' | 'done' | 'failed' | 'cancelled';
25
+ /** One tracked transfer, as the UI sees it. */
26
+ export interface TrackedTransfer {
27
+ readonly id: string;
28
+ readonly direction: 'upload' | 'download';
29
+ readonly connectionId: string;
30
+ /** Remote path (root-absolute). */
31
+ readonly remotePath: string;
32
+ /** Local path; for a download this is the destination, for an upload the source. */
33
+ readonly localPath: string;
34
+ readonly status: TrackedTransferStatus;
35
+ readonly progress: TransferProgress;
36
+ /** Message of the failure, when `status === 'failed'`. */
37
+ readonly error?: string;
38
+ /** Machine-readable code of the failure, when the host named one. */
39
+ readonly errorCode?: string;
40
+ readonly startedAt: number;
41
+ /** When it stopped running; absent while it is still running. */
42
+ readonly finishedAt?: number;
43
+ }
44
+ /** What a caller supplies to open a transfer. */
45
+ export interface TransferStart {
46
+ readonly direction: 'upload' | 'download';
47
+ readonly connectionId: string;
48
+ readonly remotePath: string;
49
+ readonly localPath: string;
50
+ readonly totalBytes: number;
51
+ }
52
+ /** The tracker's injected facts. */
53
+ export interface TransferTrackerOptions {
54
+ /** How many entries to retain, finished ones evicted oldest-first. At least 1. */
55
+ readonly maxEntries: number;
56
+ /** Injectable clock. */
57
+ readonly now?: () => number;
58
+ }
59
+ /** The table. */
60
+ export interface TransferTracker {
61
+ /** Open a transfer and return its id. */
62
+ start(input: TransferStart): string;
63
+ /**
64
+ * Record progress; unknown ids are ignored (a late callback after eviction must not throw).
65
+ *
66
+ * `totalBytes` is carried on every report because the tracker does **not** know the size up front: the plan is
67
+ * computed inside the transfer service, so the first honest total arrives with the first progress callback.
68
+ * Guessing a total at `start` would show 100% for a transfer that has barely begun.
69
+ */
70
+ progress(id: string, transferredBytes: number, totalBytes?: number): void;
71
+ /** Mark it finished. */
72
+ done(id: string, transferredBytes?: number): void;
73
+ /** Mark it failed, with whatever code the host produced. */
74
+ fail(id: string, error: string, errorCode?: string): void;
75
+ /** Mark it cancelled (the user's own action, which is not a failure). */
76
+ cancel(id: string): void;
77
+ /**
78
+ * Close a transfer from the error it threw, choosing between `cancelled` and `failed`.
79
+ *
80
+ * The policy lives here rather than at the call site because it is a **domain** rule, not plumbing: a
81
+ * cancelled transfer is the user's own action, so listing it among failures would bury the real ones — and
82
+ * the requirement's failure list is the thing that has to stay readable. It sat in the composition root
83
+ * first, where no test could reach it (audit 0046).
84
+ *
85
+ * @param id - the transfer.
86
+ * @param failure - the thrown value and the code the host produced, if any.
87
+ */
88
+ settle(id: string, failure: {
89
+ readonly code?: string | undefined;
90
+ readonly message: string;
91
+ }): void;
92
+ /** Everything retained, newest first. */
93
+ list(): readonly TrackedTransfer[];
94
+ /** One entry, if it is still retained. */
95
+ get(id: string): TrackedTransfer | undefined;
96
+ /** Drop every finished entry (the UI's "clear" button). */
97
+ clearFinished(): number;
98
+ }
99
+ /**
100
+ * Build a tracker.
101
+ *
102
+ * @param options - bounds and clock.
103
+ * @returns the tracker.
104
+ */
105
+ export declare function createTransferTracker(options: TransferTrackerOptions): TransferTracker;
106
+ //# sourceMappingURL=transfer-tracker.d.ts.map