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,34 @@
1
+ /**
2
+ * 两个传输(AgentTransport / Ssh2Transport)共用的**状态机与能力记录**。
3
+ *
4
+ * 为什么需要:这两个类各自持有一份一模一样的 `#state / #capabilities / #probedAt` 字段、
5
+ * 一对同样的 getter,以及同样"探测后记录时间戳"的写法。抽出来之后:
6
+ *
7
+ * - 状态迁移只有一个入口(`markState`),不会出现"一个类记得置 lost、另一个忘了";
8
+ * - `capabilities` 的写入口只有 `setCapabilities`,探测时间戳不可能忘记同步;
9
+ * - 子类不再声明同名字段,`Transport` 接口里的 `state`/`capabilities` 由基类满足。
10
+ *
11
+ * **不抽的部分(有意)**:`exec / readFile / writeFile / rename / remove …` 在两个类里是
12
+ * **同名但不同实现**——一个走代理 RPC,一个走 SFTP/系统 ssh。它们是"实现同一个端口",
13
+ * 不是重复代码;强行拉一个公共基类只会把两套完全不同的机制耦在一起。
14
+ *
15
+ * @module dsh-over-ssh/infra/transport/transport-base
16
+ */
17
+ import { type RemoteCapabilities } from '../../domain/capability.ts';
18
+ import type { TransportState } from './transport.ts';
19
+ export declare abstract class TransportBase {
20
+ #private;
21
+ get state(): TransportState;
22
+ get capabilities(): RemoteCapabilities;
23
+ /** When `capabilities` was last probed (undefined before the first probe). */
24
+ get probedAt(): number | undefined;
25
+ /** The single write path for the state machine. */
26
+ protected markState(state: TransportState): void;
27
+ /** Record a probe result. `probedAt` is optional so a caller that has its own clock can pass it. */
28
+ protected setCapabilities(capabilities: RemoteCapabilities, probedAt?: number): void;
29
+ /** Mark the probe timestamp (used right after {@link setCapabilities} by callers with their own clock). */
30
+ protected markProbed(at: number): void;
31
+ /** Forget the probe (a reconnect must not keep a stale timestamp). */
32
+ protected clearProbed(): void;
33
+ }
34
+ //# sourceMappingURL=transport-base.d.ts.map
@@ -0,0 +1,527 @@
1
+ /**
2
+ * The transport contract: one pluggable remote execution world.
3
+ *
4
+ * Implementations are `alias` (system `ssh` + ControlMaster, inheriting `~/.ssh/config`) and
5
+ * `ssh2` (pure-JS client, needed for password auth and dynamic ProxyJump). Keeping the
6
+ * contract here lets the application layer and every test run without either implementation.
7
+ *
8
+ * @module dsh-over-ssh/infra/transport/transport
9
+ */
10
+ import type { RemoteCapabilities } from '../../domain/capability.ts';
11
+ /** Lifecycle of one transport instance. */
12
+ export type TransportState = 'idle' | 'connecting' | 'ready' | 'lost';
13
+ /** A remote command expressed as argv (preferred) or as an explicit script. */
14
+ export interface ExecRequest {
15
+ /** Executable and arguments; quoted and joined for the remote shell. */
16
+ readonly argv?: readonly string[];
17
+ /** Raw script body; used only when `argv` is absent. */
18
+ readonly script?: string;
19
+ /** Working directory; omitted leaves the transport default. */
20
+ readonly cwd?: string;
21
+ /** Environment assignments for the command. */
22
+ readonly env?: Readonly<Record<string, string>>;
23
+ /** Command deadline; falls back to the connection's `commandTimeoutMs`. */
24
+ readonly timeoutMs?: number;
25
+ /** Cap on retained stdout bytes; output beyond it is dropped and flagged. */
26
+ readonly maxBytes?: number;
27
+ /** Bytes fed to the command's stdin. */
28
+ readonly stdin?: string;
29
+ /**
30
+ * Bytes fed to the command's stdin, base64-encoded for the wire.
31
+ *
32
+ * Separate from {@link stdin} because the two have different meanings — `stdin` is **text** (written
33
+ * verbatim), this is **bytes**. A transport that only understands text must refuse this field rather
34
+ * than send the base64 text to the command (found on the real host: a positioned write wrote the
35
+ * *encoded* payload into the file, i.e. silent corruption).
36
+ */
37
+ readonly stdinBase64?: string;
38
+ }
39
+ /** One finished remote command. */
40
+ export interface ExecResult {
41
+ readonly exitCode: number;
42
+ /**
43
+ * The signal that ended the command, or `null` when it exited on its own.
44
+ *
45
+ * `-1` + this pair is what separates the two ways a command can fail without an exit code: a **timeout**
46
+ * (below) and a **signal** (a crash, an OOM kill, a `kill -TERM`). Reporting only the exit code made those
47
+ * indistinguishable, and a signal was read as "the timeout fired" (audit 0064 P4).
48
+ */
49
+ readonly signal: string | null;
50
+ /**
51
+ * Whether this transport's own deadline ended the command.
52
+ *
53
+ * `true` means the budget ran out and the transport stopped waiting; it does **not** claim the remote
54
+ * process is dead. An agent reports this explicitly. The `ssh2` transport answers a deadline by throwing
55
+ * `REMOTE_COMMAND_FAILED` naming the timeout, so every result it *returns* is truthfully `false`.
56
+ */
57
+ readonly timedOut: boolean;
58
+ readonly stdout: string;
59
+ readonly stderr: string;
60
+ readonly durationMs: number;
61
+ /**
62
+ * Whether **stdout** was cut at `maxBytes`.
63
+ *
64
+ * This keeps its long-standing meaning and must not be widened: `remote-backend` reads it as "this batch
65
+ * read lost paths", so folding stderr truncation in would report readable files as missing (see
66
+ * `ssh2-transport.ts`'s `STDERR_TRUNCATION_NOTICE`). A transport whose backend reports one combined flag
67
+ * passes that value through unchanged for compatibility; the per-stream fields below are what callers must
68
+ * read when they need to know **which** stream was cut.
69
+ */
70
+ readonly truncated: boolean;
71
+ /**
72
+ * Whether stdout was cut — the authoritative stdout flag (审计 0052 §8).
73
+ *
74
+ * Distinct from {@link truncated} only for a backend that reports one combined flag; every transport here
75
+ * states it explicitly so a caller never has to infer it.
76
+ */
77
+ readonly stdoutTruncated: boolean;
78
+ /**
79
+ * Whether stderr was cut.
80
+ *
81
+ * **A transport must report only what it actually knows.** Claiming `true` here when the stream was
82
+ * complete is worse than claiming nothing: the reader is told to distrust compiler diagnostics that are in
83
+ * fact intact, and re-runs an expensive build for nothing. A transport that cannot observe stderr
84
+ * truncation reports `false` and says so in its own documentation.
85
+ */
86
+ readonly stderrTruncated: boolean;
87
+ /**
88
+ * Remote path holding the **full** stdout, present only when stdout was cut **and** the backend spilled it.
89
+ *
90
+ * `undefined` means "no such file was produced", never "the path is unknown".
91
+ */
92
+ readonly stdoutSpillPath: string | undefined;
93
+ /** Remote path holding the **full** stderr; same rules as {@link stdoutSpillPath}. */
94
+ readonly stderrSpillPath: string | undefined;
95
+ /**
96
+ * **Tier 1 only**: this command was stopped locally (deadline or cancellation), but the host has no agent,
97
+ * so there is no protocol channel that could confirm the remote process group died.
98
+ *
99
+ * `true` is an honest limitation, not an error: the result/exit code is unchanged, and the caller must not
100
+ * read it as "the command is still running" either — it means **unknown**. Tier 2 does not set it (a
101
+ * cancelled `proc.exec` is killed by the agent and reported through the protocol).
102
+ */
103
+ readonly remoteMayStillRun?: boolean;
104
+ /** Human-readable explanation for {@link remoteMayStillRun}; model-facing when present. */
105
+ readonly hint?: string;
106
+ }
107
+ /**
108
+ * The hint a Tier 1 transport attaches when it stops waiting on a command: the local `ssh` was killed, but
109
+ * without an agent there is no way to confirm the remote process group is gone.
110
+ *
111
+ * Deliberately worded as "may still be running … cannot confirm": claiming it died would be a lie on a host
112
+ * where `sshd` does not signal the child on channel close, and claiming it lives would be a lie on hosts that
113
+ * do. The error code is untouched; this travels in `details.hint` (throw path) or `ExecResult.hint` (result
114
+ * path) so the model can tell the user to look before re-running a build.
115
+ */
116
+ export declare const TIER1_REMOTE_MAY_STILL_RUN_HINT = "\u8FDC\u7AEF\u547D\u4EE4\u53EF\u80FD\u4ECD\u5728\u8FD0\u884C\uFF08Tier 1 \u65E0\u4EE3\u7406\uFF0C\u65E0\u6CD5\u786E\u8BA4\uFF09\uFF1A\u672C\u5730\u5DF2\u505C\u6B62\u7B49\u5F85\u5E76\u7EC8\u6B62\u4E86\u672C\u5730 ssh\uFF0C\u4F46\u8FDC\u7AEF\u8FDB\u7A0B\u7EC4\u6CA1\u6709\u534F\u8BAE\u901A\u9053\u53BB\u786E\u8BA4\u6216\u6740\u6B7B\u3002";
117
+ /** One remote file read. */
118
+ export interface ReadFileRequest {
119
+ /** Absolute remote path. */
120
+ readonly path: string;
121
+ /** Inclusive byte cap for the retained content. */
122
+ readonly maxBytes: number;
123
+ }
124
+ /**
125
+ * One **offset** read: the primitive a chunked download needs.
126
+ *
127
+ * `readFile` always starts at 0, so a caller paging through a large file had to read `[0, offset+len)` and
128
+ * slice — which defeats the point of a byte cap on a big file. Optional: a transport that cannot seek must
129
+ * simply not implement it, and the transfer layer degrades instead of silently reading the whole file.
130
+ */
131
+ export interface ReadFileRangeRequest {
132
+ /** Absolute remote path. */
133
+ readonly path: string;
134
+ /** Byte offset to start at (0-based). */
135
+ readonly offset: number;
136
+ /** Maximum bytes to read from `offset`. */
137
+ readonly maxBytes: number;
138
+ }
139
+ /**
140
+ * Metadata for one remote path, as the **transport itself** observes it.
141
+ *
142
+ * This exists because a version token is only meaningful next to the token source that produced it. The
143
+ * Tier 1 shell `stat` (`RemoteBackend.stat`) can only run `stat -c '%Y'` and therefore reports seconds,
144
+ * while the Tier 2 agent's `fs.read` reports `size:mtimeNs` (`dsh-over-ssh-agent/internal/agent/fileops.go`,
145
+ * `Version.String()`). Comparing the two is comparing two different quantities, which made every
146
+ * read→write version guard fail on Tier 2.
147
+ *
148
+ * @see Transport.statPath - the member that produces this, and the one place that reconciles the two.
149
+ */
150
+ export interface RemoteStatResult {
151
+ readonly exists: boolean;
152
+ readonly isDirectory: boolean;
153
+ readonly isFile: boolean;
154
+ /** Size in bytes; 0 when the path does not exist. */
155
+ readonly size: number;
156
+ /** Milliseconds since the epoch; 0 when unknown. */
157
+ readonly mtimeMs: number;
158
+ /**
159
+ * The transport's own version token for this path, or `''` when it has none.
160
+ *
161
+ * It must be the **same token the same transport's `readFile` reports for the same state** — that
162
+ * identity is the whole point of this member.
163
+ */
164
+ readonly version: string;
165
+ }
166
+ /** Raw bytes read from a remote file. */
167
+ export interface RemoteFileChunk {
168
+ readonly bytes: Uint8Array;
169
+ /** Full size reported by the remote when known. */
170
+ readonly size: number;
171
+ /** Whether `bytes` is shorter than the full file. */
172
+ readonly truncated: boolean;
173
+ /** The version token observed by the same round trip, when the host can provide one. */
174
+ readonly version?: string;
175
+ }
176
+ /** One remote file write. */
177
+ export interface WriteFileRequest {
178
+ /** Absolute remote path. */
179
+ readonly path: string;
180
+ readonly bytes: Uint8Array;
181
+ /** Refuse the write when the current content digest differs. */
182
+ readonly expectedSha256?: string;
183
+ /**
184
+ * Refuse the write unless the target still carries this **version token**.
185
+ *
186
+ * Distinct from {@link expectedSha256}, and the distinction is the point: a digest and a version token
187
+ * are different quantities, and overloading one field with both meanings is what broke the guarded
188
+ * read→write flow on Tier 2 (the agent's `fs.write` guard is a version comparison — see
189
+ * `agent-transport.ts`'s `writeFile` notes).
190
+ *
191
+ * A transport that guards on version tokens honours this field; a transport that guards on digests
192
+ * ignores it. A caller that wants a guard on **either** tier must therefore pass whichever one that
193
+ * tier actually verifies, which is what `RemoteBackend` does.
194
+ */
195
+ readonly expectedVersion?: string;
196
+ /** Create missing parent directories. */
197
+ readonly createParents?: boolean;
198
+ }
199
+ /**
200
+ * One **positioned** write: the primitive a chunked upload needs.
201
+ *
202
+ * `writeFile` replaces the whole file atomically, so putting a 1 GiB file in place required holding it in
203
+ * memory first. A positioned write follows **POSIX `pwrite` semantics**, and every transport must agree on
204
+ * them — they did not at first (the ssh2 path truncated the tail and required the file to exist at offset 0
205
+ * while the shell path did the opposite), which is exactly the kind of difference nobody notices until a
206
+ * resumed upload corrupts a file:
207
+ *
208
+ * - bytes outside `[offset, offset + bytes.length)` are **left untouched** — never truncated;
209
+ * - `offset === 0` creates the file when it does not exist, otherwise writes from 0 and keeps the tail;
210
+ * - `offset > 0` requires the file to exist (a hole is not conjured up);
211
+ * - the length grows when needed, but a caller that wants a shorter file must say so (`writeFile`, or an
212
+ * explicit truncate) — silently shortening on every chunk would break resumable uploads.
213
+ */
214
+ export interface WriteFileRangeRequest {
215
+ /** Absolute remote path. */
216
+ readonly path: string;
217
+ /** Byte offset to write at (0-based); the file must already be at least this long, or be created by offset 0. */
218
+ readonly offset: number;
219
+ readonly bytes: Uint8Array;
220
+ /** Create missing parent directories. */
221
+ readonly createParents?: boolean;
222
+ }
223
+ /** A server-side copy: one remote path to another, bytes never crossing this process. */
224
+ export interface CopyFileRequest {
225
+ /** Absolute remote source path. */
226
+ readonly from: string;
227
+ /** Absolute remote destination path. */
228
+ readonly to: string;
229
+ /** Create missing parent directories of `to`. */
230
+ readonly createParents?: boolean;
231
+ /** Replace an existing destination. When absent the copy refuses to clobber. */
232
+ readonly overwrite?: boolean;
233
+ /** Require the destination to still have exactly this version before replacing it. */
234
+ readonly expectedVersion?: string;
235
+ }
236
+ /** Outcome of a remote write. */
237
+ /** What the remote host reported about itself. Every field is best-effort; absent means "not known". */
238
+ export interface RemoteEnvResult {
239
+ readonly os: string;
240
+ readonly arch: string;
241
+ readonly kernel?: string | undefined;
242
+ readonly distro?: string | undefined;
243
+ readonly shell?: string | undefined;
244
+ readonly cpus: number;
245
+ readonly memTotalBytes?: number | undefined;
246
+ readonly memAvailableBytes?: number | undefined;
247
+ readonly diskFreeBytes?: number | undefined;
248
+ readonly diskTotalBytes?: number | undefined;
249
+ readonly user: string;
250
+ readonly uid: number;
251
+ readonly home: string;
252
+ readonly path: string;
253
+ /** Commands that exist, mapped to their resolved absolute path. */
254
+ readonly tools: Readonly<Record<string, string>>;
255
+ /** Commands from the probe list that were not found. */
256
+ readonly missing: readonly string[];
257
+ /** Unix seconds of the sample this answer came from (not of the call). */
258
+ readonly probedAt: number;
259
+ }
260
+ /** One patch request: a unified diff applied to one file. */
261
+ export interface PatchFileRequest {
262
+ readonly path: string;
263
+ /** Unified diff text. Line numbers are used for error messages only; hunks are located by context. */
264
+ readonly diff: string;
265
+ readonly expectedVersion?: string;
266
+ }
267
+ /** What a patch did. */
268
+ export interface PatchFileResult {
269
+ readonly hunks: number;
270
+ readonly bytes: number;
271
+ readonly version: string;
272
+ }
273
+ /** One recursive copy request; both paths are absolute on the remote host. */
274
+ export interface CopyTreeRequest {
275
+ readonly from: string;
276
+ readonly to: string;
277
+ }
278
+ /** What a recursive server-side copy did. */
279
+ export interface CopyTreeOutcome {
280
+ readonly files: number;
281
+ readonly dirs: number;
282
+ readonly symlinks: number;
283
+ readonly bytes: number;
284
+ }
285
+ export interface RemoteWriteOutcome {
286
+ readonly bytes: number;
287
+ /** Digest of the written content when the host can compute it. */
288
+ readonly sha256?: string;
289
+ /**
290
+ * Opaque version token of the written content when the host reports one.
291
+ *
292
+ * The two ssh transports report a digest instead; the Tier 2 agent reports `size:mtimeNs`, which is
293
+ * the token its own `fs.write` guard expects — so a caller that wants to chain another guarded write
294
+ * must use this value rather than a locally derived digest.
295
+ */
296
+ readonly version?: string;
297
+ }
298
+ /** One rename/move inside the remote file system. */
299
+ export interface RenameRequest {
300
+ readonly from: string;
301
+ readonly to: string;
302
+ /** Create the destination's parent directories. Implementations must not silently copy across devices. */
303
+ readonly createParents?: boolean;
304
+ }
305
+ /** One removal. */
306
+ export interface RemoveRequest {
307
+ /** Absolute remote path. */
308
+ readonly path: string;
309
+ /** Remove directories and their contents; a directory without this flag must fail, not half-succeed. */
310
+ readonly recursive?: boolean;
311
+ }
312
+ /** Retained stdout cap applied when the caller does not choose one. */
313
+ export declare const DEFAULT_MAX_OUTPUT_BYTES: number;
314
+ /** Default file read cap; larger files must be paged by the caller. */
315
+ export declare const DEFAULT_MAX_READ_BYTES: number;
316
+ /** Request for {@link Transport.editFile}: an atomic in-place edit done **by the agent** (审计 0030). */
317
+ export interface EditFileRequest {
318
+ readonly path: string;
319
+ readonly oldString: string;
320
+ readonly newString: string;
321
+ readonly replaceAll?: boolean;
322
+ readonly expectedVersion?: string;
323
+ }
324
+ /** Result of {@link Transport.editFile}. */
325
+ export interface EditFileResult {
326
+ readonly bytes: number;
327
+ readonly version: string;
328
+ readonly replaced: number;
329
+ }
330
+ /** Request for {@link Transport.removePath}. */
331
+ export interface RemovePathRequest {
332
+ readonly path: string;
333
+ readonly recursive?: boolean;
334
+ readonly expectedVersion?: string;
335
+ }
336
+ /** Request for {@link Transport.renamePath}. */
337
+ export interface RenamePathRequest {
338
+ readonly from: string;
339
+ readonly to: string;
340
+ readonly createParents?: boolean;
341
+ readonly expectedVersion?: string;
342
+ }
343
+ /** Request for {@link Transport.hashFile}: sha256 of a whole file or a byte range. */
344
+ export interface HashFileRequest {
345
+ readonly path: string;
346
+ readonly offset?: number;
347
+ readonly length?: number;
348
+ }
349
+ /** Result of {@link Transport.hashFile}. */
350
+ export interface HashFileResult {
351
+ readonly sha256: string;
352
+ readonly size: number;
353
+ readonly bytes: number;
354
+ readonly version: string;
355
+ }
356
+ /** Request for {@link Transport.makeDir}. */
357
+ export interface MakeDirRequest {
358
+ readonly path: string;
359
+ readonly mode?: number;
360
+ readonly recursive?: boolean;
361
+ }
362
+ /**
363
+ * Outcome of {@link Transport.makeDir}.
364
+ *
365
+ * `created` exists so the **policy** can live in one place. The agent treats `fs.mkdir` as an idempotent
366
+ * primitive (a repeat answers `created: false`, and its e2e pins that), while the product rule for
367
+ * `recursive: false` is "refuse a name that is already taken" — that rule belongs to the caller, which
368
+ * applies it identically on the shell path. Reporting the fact and deciding elsewhere is what keeps the two
369
+ * tiers from drifting.
370
+ */
371
+ export interface MakeDirResult {
372
+ /** `true` when this call created the directory, `false` when it already existed as a directory. */
373
+ readonly created: boolean;
374
+ }
375
+ /** Request for {@link Transport.globPaths}. */
376
+ export interface GlobPathsRequest {
377
+ readonly root: string;
378
+ readonly pattern: string;
379
+ readonly maxResults?: number;
380
+ readonly includeDirs?: boolean;
381
+ readonly timeoutMs?: number;
382
+ }
383
+ /** Result of {@link Transport.globPaths}. */
384
+ export interface GlobPathsResult {
385
+ readonly paths: readonly string[];
386
+ readonly truncated: boolean;
387
+ readonly scanned: number;
388
+ }
389
+ /** Request for {@link Transport.jobStdin}. */
390
+ export interface JobStdinRequest {
391
+ readonly id: string;
392
+ readonly data?: string;
393
+ readonly dataBase64?: string;
394
+ readonly close?: boolean;
395
+ }
396
+ /** Result of {@link Transport.jobStdin}. */
397
+ export interface JobStdinResult {
398
+ readonly id: string;
399
+ readonly bytes: number;
400
+ readonly closed: boolean;
401
+ }
402
+ /**
403
+ * 系统 ssh 多路复用(ControlMaster)的失败缓存。
404
+ *
405
+ * 放在**端口模块**而不是 `alias-transport.ts`:`app/transport-selector.ts` 需要这个类型
406
+ * 来做传输选择,而 app 层只允许依赖端口、不允许依赖具体适配器(架构测试会断言)。
407
+ * 适配器模块继续 re-export,既有调用方无需改动。
408
+ */
409
+ export interface MultiplexingCache {
410
+ /** Whether this client+alias pair is already known to lack multiplexing. */
411
+ isUnsupported(key: string): boolean;
412
+ /** Record a mux failure so later connects skip the master attempt. */
413
+ markUnsupported(key: string, reason: string): void;
414
+ /** The recorded reason, for diagnostics and the UI. */
415
+ reasonFor(key: string): string | undefined;
416
+ }
417
+ /** One remote execution world. */
418
+ export interface Transport {
419
+ /** Connection id this transport serves. */
420
+ readonly id: string;
421
+ readonly state: TransportState;
422
+ /** Remote tool availability discovered by {@link probeCapabilities} (or the conservative default). */
423
+ readonly capabilities: RemoteCapabilities;
424
+ /** Establish the connection; idempotent. */
425
+ connect(signal?: AbortSignal): Promise<void>;
426
+ /** Release the connection and any cached sessions. */
427
+ dispose(): Promise<void>;
428
+ /** Run one command to completion. */
429
+ exec(request: ExecRequest, signal?: AbortSignal): Promise<ExecResult>;
430
+ /** Read a remote file as bytes. */
431
+ readFile(request: ReadFileRequest, signal?: AbortSignal): Promise<RemoteFileChunk>;
432
+ /**
433
+ * Stat a path using **this transport's own** token source, so the answer is comparable with its
434
+ * {@link readFile}.
435
+ *
436
+ * Optional, and its absence is meaningful: Tier 1's shell transports have no native stat, so
437
+ * `RemoteBackend` keeps using its `stat -c '%Y'` script for them and their long-standing
438
+ * `size:mtimeSeconds` tokens are untouched. The Tier 2 agent implements it from `fs.stat`, whose
439
+ * `version` is the very string its `fs.write` guard expects.
440
+ *
441
+ * A transport that implements this must report the same token for the same file state as its
442
+ * `readFile`; a test pins that identity because it is the contract's entire purpose.
443
+ */
444
+ statPath?(path: string, signal?: AbortSignal): Promise<RemoteStatResult>;
445
+ /**
446
+ * Atomic in-place edit performed by the agent itself (`fs.edit`).
447
+ *
448
+ * Optional and its absence is meaningful: Tier 1 has no such primitive, so callers fall back to
449
+ * read → replace → write. Present, it is one round trip with no file transfer and a version guard
450
+ * that holds across the whole read-modify-write (审计 0030).
451
+ */
452
+ editFile?(request: EditFileRequest, signal?: AbortSignal): Promise<EditFileResult>;
453
+ /**
454
+ * Apply a unified diff to one file, atomically.
455
+ *
456
+ * Absent on transports without a server-side patcher. The caller must **not** silently fall back to a
457
+ * client-side apply: the whole point is one round trip, and a fallback would also have to re-implement the
458
+ * hunk matching rules, which is where a patch applier goes wrong.
459
+ */
460
+ patchFile?(request: PatchFileRequest, signal?: AbortSignal): Promise<PatchFileResult>;
461
+ /** Native delete (`fs.remove`); absent on Tier 1, which uses its shell primitive instead. */
462
+ removePath?(request: RemovePathRequest, signal?: AbortSignal): Promise<void>;
463
+ /** Native rename/move (`fs.rename`); absent on Tier 1, which uses its shell primitive. */
464
+ renamePath?(request: RenamePathRequest, signal?: AbortSignal): Promise<void>;
465
+ /** Content digest (`fs.hash`): the primitive that makes `expectedSha256` a digest guard. */
466
+ hashFile?(request: HashFileRequest, signal?: AbortSignal): Promise<HashFileResult>;
467
+ /** Create a directory (`fs.mkdir`); reports whether it was created rather than deciding the policy. */
468
+ makeDir?(request: MakeDirRequest, signal?: AbortSignal): Promise<MakeDirResult>;
469
+ /** Server-side glob (`fs.glob`): enumerate by pattern without shipping the whole index. */
470
+ globPaths?(request: GlobPathsRequest, signal?: AbortSignal): Promise<GlobPathsResult>;
471
+ /** Feed a running interactive job's stdin (`proc.stdin`). */
472
+ jobStdin?(request: JobStdinRequest, signal?: AbortSignal): Promise<JobStdinResult>;
473
+ /** Write a remote file atomically from the caller's bytes. */
474
+ writeFile(request: WriteFileRequest, signal?: AbortSignal): Promise<RemoteWriteOutcome>;
475
+ /** Discover remote tool availability; tolerant of missing commands. */
476
+ probeCapabilities(signal?: AbortSignal): Promise<RemoteCapabilities>;
477
+ /**
478
+ * Read from `offset` without reading the earlier bytes.
479
+ *
480
+ * Absent when the transport cannot seek: callers must treat "no method" as "no chunked download" rather
481
+ * than falling back to a whole-file read (see {@link ReadFileRangeRequest}).
482
+ */
483
+ readFileRange?(request: ReadFileRangeRequest, signal?: AbortSignal): Promise<RemoteFileChunk>;
484
+ /**
485
+ * Write at `offset`, leaving the bytes before it untouched.
486
+ *
487
+ * Absent when the transport can only replace whole files: a chunked upload must then either use one
488
+ * whole-file write or refuse to resume, never pretend.
489
+ */
490
+ writeFileRange?(request: WriteFileRangeRequest, signal?: AbortSignal): Promise<RemoteWriteOutcome>;
491
+ /**
492
+ * Copy one remote file to another path **on the remote side**.
493
+ *
494
+ * Absent when the transport has no server-side copy: the caller must then relay the bytes itself and say
495
+ * that it did, rather than quietly streaming a large file through the local machine while claiming a copy.
496
+ */
497
+ copyFile?(request: CopyFileRequest, signal?: AbortSignal): Promise<RemoteWriteOutcome>;
498
+ /**
499
+ * Copy a whole **directory tree** on the remote host.
500
+ *
501
+ * Absent when the transport cannot do it server-side. The caller must **not** fall back to relaying a tree
502
+ * file by file: that is exactly the cost this method exists to remove, and a silent fallback would turn a
503
+ * one-round-trip operation into thousands.
504
+ */
505
+ copyTree?(request: CopyTreeRequest, signal?: AbortSignal): Promise<CopyTreeOutcome>;
506
+ /**
507
+ * Sample the remote host's environment: OS, resources, and which commands exist.
508
+ *
509
+ * Absent on transports that cannot probe; the caller then simply has no environment answer rather than a
510
+ * guessed one.
511
+ */
512
+ probeEnv?(refresh: boolean, signal?: AbortSignal): Promise<RemoteEnvResult>;
513
+ /**
514
+ * Move a remote path.
515
+ *
516
+ * Contract: create the destination's parents when asked, and **fail** when the move cannot be done as a
517
+ * rename (e.g. across devices) — a silent copy would break "move then confirm" delete semantics.
518
+ */
519
+ rename?(request: RenameRequest, signal?: AbortSignal): Promise<void>;
520
+ /** Remove a file, or a directory tree when `recursive` is set. */
521
+ remove?(request: RemoveRequest, signal?: AbortSignal): Promise<void>;
522
+ }
523
+ /** Encode bytes as base64 without line wrapping. */
524
+ export declare function encodeBase64(bytes: Uint8Array): string;
525
+ /** Decode base64, ignoring line breaks produced by remote tooling. */
526
+ export declare function decodeBase64(text: string): Uint8Array;
527
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,56 @@
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-over-ssh/integration/agent-preflight
16
+ */
17
+ import type { TransportPreflight } from '../../app/transport/transport-service.ts';
18
+ import type { ConnectionProfile } from '../../domain/roots/connection.ts';
19
+ import type { AgentInstallation } from '../../infra/transport/agent/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