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,418 +0,0 @@
1
- /**
2
- * 代理传输:把协议客户端适配成 {@link Transport}。
3
- *
4
- * 从原 3,388 行文件拆出(审计 0077):协议层(帧 / 类型 / 客户端)已移到
5
- * `agent-protocol-{frames,types,client}.ts`,本文件只剩**传输适配**——连接状态机、
6
- * 能力探测、各方法的 RPC 调用、以及真实启动器。
7
- *
8
- * 兼容性:下面 re-export 三个协议模块,既有调用方(integration / wiring / tests)
9
- * 从 `agent-transport.ts` 取符号的路径不变。
10
- *
11
- * @module dsh-over-ssh/infra/transport/agent-transport
12
- */
13
- export * from './agent-protocol-frames.ts';
14
- export * from './agent-protocol-types.ts';
15
- export * from './agent-protocol-codec.ts';
16
- export * from './agent-protocol-errors.ts';
17
- export * from './agent-protocol-client.ts';
18
- import { type RemoteCapabilities } from '../../../domain/capability.ts';
19
- import { type ExecRequest, type ExecResult, type CopyFileRequest, type CopyTreeRequest, type CopyTreeOutcome, type PatchFileRequest, type PatchFileResult, type RemoteEnvResult, type ReadFileRangeRequest, type ReadFileRequest, type RemoteFileChunk, type RemoteStatResult, type RemoteWriteOutcome, type RemoveRequest, type RenameRequest, type Transport, type WriteFileRangeRequest, type WriteFileRequest } from '../transport.ts';
20
- import type { EditFileRequest, EditFileResult, HashFileRequest, HashFileResult, JobStdinRequest, JobStdinResult, MakeDirRequest, MakeDirResult, GlobPathsRequest, GlobPathsResult, RemovePathRequest, RenamePathRequest } from '../transport.ts';
21
- import { TransportBase } from '../transport-base.ts';
22
- import type { AgentLauncher, AgentTransportOptions, AgentIndexScanResult, AgentProcStartRequest, AgentJobStatus, AgentJobKill, AgentJobOutput, AgentLspStartRequest, AgentLspStatus, AgentLspRead, AgentIndexScanRequest, AgentSearchResult, AgentSearchGrepRequest, AgentWatchStartResult, AgentWatchStartRequest, AgentWatchPollResult, AgentWatchPollRequest, AgentWatchStopResult, AgentGitStatusResult, AgentCapabilities } from './agent-protocol-types.ts';
23
- export declare class AgentTransport extends TransportBase implements Transport {
24
- #private;
25
- readonly id: string;
26
- constructor(options: AgentTransportOptions);
27
- /**
28
- * Session the agent echoed at handshake (`hello.result.sessionId`), or `undefined` when it sent none.
29
- *
30
- * `undefined` is the honest answer for an agent that predates sessions, and callers must not treat it as
31
- * `"default"`: doing so would claim per-session isolation the agent never agreed to.
32
- */
33
- get negotiatedSessionId(): string | undefined;
34
- /**
35
- * Whether the agent said jobs outlive this connection (`hello.result.daemon`).
36
- *
37
- * `false` until a handshake says otherwise, because today's agent does tie job lifetime to the
38
- * connection; assuming `true` would make callers skip the cleanup that is still required.
39
- */
40
- get daemon(): boolean;
41
- /**
42
- * The agent's own capability record (Tier 2 facts).
43
- *
44
- * `capabilities` is deliberately the Tier 1 shape, so the agent's extra bits (`watch`, `index`,
45
- * `atomicWrite`, `landlock`, `exec`) would otherwise be dropped on the floor. Tier 2 callers must gate
46
- * on **this** record: `watch !== 'native'` means no `watch.*`, and `exec !== true` means no `proc.exec`.
47
- * Empty until the first handshake.
48
- */
49
- get agentCapabilities(): AgentCapabilities;
50
- /** `uname` derived from the agent's `os`, or `undefined` when the agent reported nothing usable. */
51
- get agentUname(): string | undefined;
52
- /**
53
- * Open the session and handshake. Idempotent and single-flight: concurrent callers share one attempt.
54
- *
55
- * @throws SshDevError `CONNECTION_LOST` when the handshake fails or the protocol version mismatches
56
- * (the only correct reaction is falling back to Tier 1), `CONNECTION_UNREACHABLE` when the session
57
- * cannot be started at all.
58
- */
59
- connect(signal?: AbortSignal): Promise<void>;
60
- /**
61
- * Close the session. Idempotent, and safe to call while connecting (the attempt is abandoned).
62
- *
63
- * A later `connect` opens a fresh session, so the transport is reusable after a disconnect.
64
- */
65
- dispose(): Promise<void>;
66
- /**
67
- * Run one command through the agent.
68
- *
69
- * Protocol v1 has no arbitrary-command method, so there are exactly two paths:
70
- *
71
- * - an injected {@link AgentExecCommand} (a deployment whose agent build does add one);
72
- * - otherwise the client asks `proc.exec` and maps `E_METHOD` onto `REMOTE_UNSUPPORTED` — an honest
73
- * "this agent version cannot run commands", never a silent fallback to the Tier 1 transport.
74
- */
75
- exec(request: ExecRequest, signal?: AbortSignal): Promise<ExecResult>;
76
- /**
77
- * Stat a path with the agent's own `fs.stat`, so the token is the **same one `fs.read` reports**.
78
- *
79
- * This is the member that closes the read→write token gap on Tier 2. `fs.stat` returns the agent's
80
- * `Version{Size, MtimeNs}.String()` (`size:mtimeNs`), which is exactly what `fs.read` returns and exactly
81
- * what `fs.write`'s `expectedVersion` guard compares against. Deriving a token here instead — or letting
82
- * the caller fall back to a shell `stat -c '%Y'` — would produce seconds and make every guard mismatch.
83
- *
84
- * The agent's `version` string is passed through **untouched** rather than re-formatted locally: there is
85
- * one token producer (the agent) and this is not a second one.
86
- */
87
- statPath(path: string, signal?: AbortSignal): Promise<RemoteStatResult>;
88
- /**
89
- * Read a remote file: `fs.read` (control frame) followed by its blob frame.
90
- *
91
- * The version token is the string the **agent** returned (`size:mtimeNs`), passed through untouched:
92
- * re-deriving it locally would produce a token that no longer matches the guard the agent applies on
93
- * the next write. Note this is *not* byte-identical to the Tier 1 `size:mtimeSeconds` token, because
94
- * the agent reports nanoseconds (verified in its `Version.String()`); {@link statPath} exists so a caller
95
- * can obtain the *same* Tier 2 token from a stat instead of comparing the two shapes.
96
- */
97
- readFile(request: ReadFileRequest, signal?: AbortSignal): Promise<RemoteFileChunk>;
98
- /**
99
- * Write a remote file: `fs.write` (control frame, then one blob frame with the content).
100
- *
101
- * **The guard here is a version comparison, not a digest comparison.** Protocol v1's `fs.write` accepts
102
- * `expectedVersion` and nothing else, so the guard is implemented as: `fs.stat` the target, refuse when the
103
- * observed version differs from the caller's token, and otherwise pass that same version to the agent as
104
- * `expectedVersion` so the agent re-checks it at write time (that is the compare-and-swap closing the race
105
- * between our stat and our write). When the target does not exist, no version token can match, so the write
106
- * is refused with `TRANSFER_CONFLICT` rather than overwriting blind.
107
- *
108
- * **Which field carries the token.** {@link WriteFileRequest.expectedVersion} is the explicit channel and
109
- * wins when present. {@link WriteFileRequest.expectedSha256} is still accepted as a *version* token for
110
- * callers written before the explicit field existed, and that legacy overloading is exactly what this
111
- * transport used to do implicitly; the error details say `guard: 'version'` in both cases so a caller is
112
- * never told a digest was verified when a version was.
113
- *
114
- * The alternative — refusing every guarded write with `REMOTE_UNSUPPORTED` — was rejected because the
115
- * version round trip is available, cheap, and strictly safer than an unguarded overwrite.
116
- */
117
- writeFile(request: WriteFileRequest, signal?: AbortSignal): Promise<RemoteWriteOutcome>;
118
- /**
119
- * Offset read: the agent's own `fs.read` with `offset`.
120
- *
121
- * This used to borrow `proc.exec` (`tail -c +N | head -c M | base64`) because protocol v1's `fs.read` had no
122
- * offset. The agent now serves `offset` natively (audit 0006), so the shell hop is gone: no `tail`/`head`/
123
- * `base64` on the remote, no shell quoting, and the bytes are framed instead of base64-inflated (the old path
124
- * asked for `2 * cap + 4096` bytes of headroom precisely because of that inflation).
125
- *
126
- * The agent's `truncated` already means "there is more after this window", so it is passed through rather
127
- * than recomputed from `size - offset`.
128
- */
129
- readFileRange(request: ReadFileRangeRequest, signal?: AbortSignal): Promise<RemoteFileChunk>;
130
- /**
131
- * Positioned write: `fs.write` with `offset` (the agent's `WriteAt`).
132
- *
133
- * This used to shell out (`dd of=… seek=… conv=notrunc`) because protocol v1's `fs.write` only replaced a
134
- * whole file. The agent serves `offset` natively now (audit 0006), so the payload travels as a real blob
135
- * frame instead of base64-on-stdin: no `dd`, no shell quoting, and no 4/3 size inflation. The agent owns the
136
- * POSIX rules (offset 0 may create and keeps the tail, offset > 0 refuses a missing file) — see
137
- * `WriteAt` in the agent, which is the single place those rules are written down.
138
- */
139
- writeFileRange(request: WriteFileRangeRequest, signal?: AbortSignal): Promise<RemoteWriteOutcome>;
140
- /**
141
- * {@inheritDoc Transport.copyFile}
142
- *
143
- * Throws `REMOTE_UNSUPPORTED` on an agent that predates `fs.copy`; the caller is expected to fall back to a
144
- * relay it reports honestly, not to pretend the bytes stayed on the remote host.
145
- */
146
- copyFile(request: CopyFileRequest, signal?: AbortSignal): Promise<RemoteWriteOutcome>;
147
- /**
148
- * {@inheritDoc Transport.copyTree}
149
- *
150
- * One round trip for a whole tree. An agent that predates `recursive` answers E_PARAMS, which
151
- * `#optionalRequest` surfaces as REMOTE_UNSUPPORTED so the caller can say "this agent is too old" instead
152
- * of relaying the tree.
153
- */
154
- copyTree(request: CopyTreeRequest, signal?: AbortSignal): Promise<CopyTreeOutcome>;
155
- /**
156
- * `proc.forget`: drop a settled job from the connection's job list.
157
- *
158
- * The list otherwise only grows — a long session that starts dozens of jobs keeps every one of them
159
- * forever, and `remote_job { action: "list" }` becomes noise. The agent refuses to forget a job that is
160
- * still running, so this cannot lose track of live work.
161
- */
162
- procForget(id: string, signal?: AbortSignal): Promise<void>;
163
- /** {@inheritDoc Transport.patchFile} — one round trip for every hunk, all-or-nothing. */
164
- patchFile(request: PatchFileRequest, signal?: AbortSignal): Promise<PatchFileResult>;
165
- /** {@inheritDoc Transport.probeEnv} — one round trip, cached on the agent side until `refresh`. */
166
- probeEnv(refresh: boolean, signal?: AbortSignal): Promise<RemoteEnvResult>;
167
- /** Move a path; a cross-device move fails instead of silently copying. */
168
- /** {@inheritDoc Transport.editFile} — atomic in-place edit: one round trip, no file transfer. */
169
- editFile(request: EditFileRequest, signal?: AbortSignal): Promise<EditFileResult>;
170
- /** {@inheritDoc Transport.removePath} */
171
- removePath(request: RemovePathRequest, signal?: AbortSignal): Promise<void>;
172
- /** {@inheritDoc Transport.renamePath} */
173
- renamePath(request: RenamePathRequest, signal?: AbortSignal): Promise<void>;
174
- /** {@inheritDoc Transport.hashFile} */
175
- hashFile(request: HashFileRequest, signal?: AbortSignal): Promise<HashFileResult>;
176
- /** {@inheritDoc Transport.makeDir} */
177
- makeDir(request: MakeDirRequest, signal?: AbortSignal): Promise<MakeDirResult>;
178
- /** {@inheritDoc Transport.globPaths} */
179
- globPaths(request: GlobPathsRequest, signal?: AbortSignal): Promise<GlobPathsResult>;
180
- /** `proc.stdin` for the job layer: same call, job-shaped arguments (审计 0030). */
181
- procStdin(id: string, data: string, close: boolean, signal?: AbortSignal): Promise<JobStdinResult>;
182
- /** {@inheritDoc Transport.jobStdin} */
183
- jobStdin(request: JobStdinRequest, signal?: AbortSignal): Promise<JobStdinResult>;
184
- rename(request: RenameRequest, signal?: AbortSignal): Promise<void>;
185
- /** Remove a file or (with `recursive`) a directory tree. */
186
- remove(request: RemoveRequest, signal?: AbortSignal): Promise<void>;
187
- /**
188
- * Enumerate remote files for `@` completion: `index.scan`.
189
- *
190
- * Gated on `agentCapabilities.index === true` **before** anything is written; when the agent did not
191
- * advertise indexing this throws `REMOTE_UNSUPPORTED` and leaves `agent.methods()` untouched, so a
192
- * caller can fall back to Tier 1 without a wasted round trip.
193
- *
194
- * The returned numbers are **the agent's own report** (entry count, strategy, truncation); this client
195
- * does not re-validate them, and an unrecognised `strategy` stays unrecognised (`"unknown"`).
196
- *
197
- * This is the **full** scan. When the agent has a snapshot from a previous scan it can refresh
198
- * incrementally instead — see {@link scanIndexRefresh}, which is what a caller polling for `@` completion
199
- * should use after the first scan.
200
- *
201
- * The agent's per-scan deadline is the request's `timeoutMs` (it has its own 5 s default); the *client's*
202
- * deadline is the transport's `requestTimeoutMs`.
203
- *
204
- * @throws SshDevError `REMOTE_UNSUPPORTED` when the capability is absent, `REMOTE_COMMAND_FAILED`
205
- * (`E_PARAMS`) for an empty `roots`, `CANCELLED`/`CONNECTION_LOST` per the shared request rules.
206
- */
207
- scanIndex(request: AgentIndexScanRequest, signal?: AbortSignal): Promise<AgentIndexScanResult>;
208
- /**
209
- * Refresh the remote index **incrementally**: `index.refresh`.
210
- *
211
- * The agent keeps a bounded snapshot per (roots, caps) identity from the last scan and reuses the file
212
- * listing of every directory whose mtime did not move, so a poll after the first scan costs
213
- * `O(directories)` instead of `O(files)`. Measured on a synthetic 13-directory / 52-file tree:
214
- * 65 syscalls → 13.
215
- *
216
- * **The agent's own report is what the caller gets.** When it cannot refresh safely (no snapshot yet,
217
- * different roots, expired entry) it answers with a full scan and says so in `strategy`/`warnings` — this
218
- * method never pretends an incremental answer was incremental.
219
- *
220
- * ## The boundary a caller must know
221
- *
222
- * A reused directory keeps the `size`/`mtimeNs` recorded at the last scan: a directory's mtime does not
223
- * change when a file's **content** changes, only when its set of entries does. So this is right for
224
- * `@` completion (which needs paths and kinds, and where added/removed/renamed files *are* picked up), and
225
- * wrong for anything that needs current file sizes. Use {@link scanIndex} for that.
226
- *
227
- * @throws SshDevError same vocabulary as {@link scanIndex}. An agent predating `index.refresh` answers
228
- * `E_METHOD`, which maps to `REMOTE_UNSUPPORTED` — callers should treat that as "fall back to scanIndex".
229
- */
230
- scanIndexRefresh(request: AgentIndexScanRequest, signal?: AbortSignal): Promise<AgentIndexScanResult>;
231
- /**
232
- * Start a background command and return immediately: `proc.start`.
233
- *
234
- * The agent runs it in a job table and answers with an id; progress and output come from
235
- * {@link procStatus} / {@link procOutput}. **Nothing is pushed** - v1's frame discipline (a control
236
- * frame plus an optional blob frame, strictly paired, written by one goroutine) has no room for
237
- * server-initiated frames, so the caller polls.
238
- *
239
- * Jobs die with the connection: the agent kills them all when the ssh channel closes. This is
240
- * "reconnectable within one connection", not a durable task queue.
241
- *
242
- * @throws SshDevError `REMOTE_UNSUPPORTED` when the agent predates `proc.start` (`E_METHOD`).
243
- */
244
- procStart(request: AgentProcStartRequest, signal?: AbortSignal): Promise<AgentJobStatus>;
245
- /**
246
- * One job's state: `proc.status`.
247
- *
248
- * @throws SshDevError `REMOTE_NOT_FOUND` for an unknown (or already forgotten) id.
249
- */
250
- procStatus(id: string, signal?: AbortSignal, waitMs?: number): Promise<AgentJobStatus>;
251
- /**
252
- * Incremental output read: `proc.output`.
253
- *
254
- * `offset` is a **position, not a cursor**: pass `nextOffset` from the previous call to get only what is
255
- * new. Re-reading an older offset re-sends that span (nothing is remembered as already read).
256
- *
257
- * The agent keeps a bounded ring buffer that drops the **oldest** bytes (errors tend to be at the end),
258
- * so a result with `lost: true` means the requested span was already discarded - the caller must not
259
- * treat it as continuous output.
260
- */
261
- procOutput(id: string, offset?: number, signal?: AbortSignal, waitMs?: number): Promise<AgentJobOutput>;
262
- /** Terminate a job: `proc.kill`. Idempotent - an already-finished job answers `killed: false`. */
263
- procKill(id: string, signal?: AbortSignal): Promise<AgentJobKill>;
264
- /**
265
- * End one session explicitly: kill its running jobs and drop its finished ones (`session.end`).
266
- *
267
- * With `--shared` (the default) a daemon keeps jobs alive across connections, so a session that
268
- * simply goes away leaves its long-running commands behind — "survives reconnects" would silently
269
- * become "leaks forever" (protocol.md §9.4). Callers end the session when it is genuinely over.
270
- *
271
- * Idempotent: an unknown or already-clean session reports `killed: 0, forgotten: 0`.
272
- */
273
- endSession(session?: string, signal?: AbortSignal): Promise<{
274
- readonly session: string;
275
- readonly killed: number;
276
- readonly forgotten: number;
277
- readonly lspStopped: number;
278
- }>;
279
- /** Every job this connection has started: `proc.list`. */
280
- procList(signal?: AbortSignal): Promise<readonly AgentJobStatus[]>;
281
- /**
282
- * Host a language server: `lsp.start`.
283
- *
284
- * The agent is a **stdio pipe**, not an LSP implementation: it starts the process and moves bytes.
285
- * `lspWrite` takes bytes (base64 on the wire) and `lspRead` pages the server's output by offset - the
286
- * caller owns all JSON-RPC framing and semantics.
287
- *
288
- * @throws SshDevError `REMOTE_UNSUPPORTED` when the agent predates `lsp.start`.
289
- */
290
- lspStart(request: AgentLspStartRequest, signal?: AbortSignal): Promise<AgentLspStatus>;
291
- /**
292
- * Send bytes to a language server's stdin: `lsp.write`.
293
- *
294
- * 走**原生 blob 帧**(`blob: true`),不再把 LSP 帧 base64 塞进 `params.data`:base64 会让负载
295
- * 膨胀 4/3,还要在两端各做一次编解码,而 didOpen/didChange 每次都要走这条路。代理侧
296
- * `expectsBlob` 早就支持这个可选形态了,只是插件一直没发。
297
- */
298
- lspWrite(id: string, bytes: Uint8Array, signal?: AbortSignal): Promise<{
299
- readonly id: string;
300
- readonly written: number;
301
- readonly state: string;
302
- }>;
303
- /** Read a language server's output from `offset`: `lsp.read`. Same offset semantics as {@link procOutput}. */
304
- lspRead(id: string, offset?: number, signal?: AbortSignal): Promise<AgentLspRead>;
305
- /** One language-server session's state: `lsp.status`. */
306
- lspStatus(id: string, signal?: AbortSignal): Promise<AgentLspStatus>;
307
- /** Terminate a language-server session: `lsp.stop` (closes stdin, then kills if it will not exit). */
308
- lspStop(id: string, signal?: AbortSignal): Promise<AgentLspStatus>;
309
- /**
310
- * Release a **finished** language-server session's record and output buffer: `lsp.forget`.
311
- *
312
- * `lsp.stop` only ends the process; the agent keeps the session row and its output ring (up to 4 MiB) so a
313
- * caller can still read the tail. Without this call that memory lives until the agent's own finish-cap
314
- * evicts it (`MaxFinishedSessions` = 16 / 32 MiB), which on a long-lived connection is a lot of resident
315
- * memory for sessions nobody will read again (audit 2026-11-01: `lsp.forget` had zero callers).
316
- *
317
- * @param id - the session id `lsp.start` returned.
318
- * @throws SshDevError `REMOTE_UNSUPPORTED` when the agent predates the method (`E_METHOD`).
319
- */
320
- lspForget(id: string, signal?: AbortSignal): Promise<void>;
321
- /**
322
- * Enumerate the language-server sessions **the agent** is holding: `lsp.list`.
323
- *
324
- * Distinct from the registry's list, which answers "which servers do we have a client for". This one answers
325
- * "what processes exist on the remote right now", including ones left by an earlier connection.
326
- */
327
- lspList(signal?: AbortSignal): Promise<readonly Record<string, unknown>[]>;
328
- /**
329
- * Bounded content search: `search.grep`, always available in protocol v1 (no capability bit gates it).
330
- *
331
- * `literal` defaults to the wire default (`true`); `ignoreCase` defaults to `true` and is written as
332
- * the agent's inverted `caseSensitive` flag. Everything the caller omits is omitted from the frame, so
333
- * the agent's own default applies — this client does not pre-fill values it cannot see the effect of.
334
- *
335
- * A regex that the *agent* cannot compile comes back as `E_PARAMS` → `REMOTE_COMMAND_FAILED`; v1 has no
336
- * cancel frame, so a long search is bounded only by `timeoutMs` and the workload caps.
337
- *
338
- * @throws SshDevError `REMOTE_COMMAND_FAILED` for an empty `paths`/`query` (refused locally),
339
- * `REMOTE_UNSUPPORTED` when the agent answers `E_METHOD` (plugin/agent version mismatch).
340
- */
341
- searchGrep(request: AgentSearchGrepRequest, signal?: AbortSignal): Promise<AgentSearchResult>;
342
- /**
343
- * Start (or replace) the remote native watcher: `watch.start`.
344
- *
345
- * Requires `agentCapabilities.watch === 'native'`. `'poll'` (darwin/windows builds) and `'none'` are
346
- * **not** enough: on those platforms the protocol answers `E_UNSUPPORTED`, and the intended Tier 1
347
- * behaviour is polling by the plugin — so this method refuses locally with `REMOTE_UNSUPPORTED`.
348
- *
349
- * `watched` is the list of **roots** that were really registered — not every subdirectory — and
350
- * `truncated` (not a thrown error) is how `maxWatches` / the kernel's `max_user_watches` report that
351
- * only part of a tree is covered. Calling `start` while a watcher runs replaces it; a failed
352
- * replacement leaves no watcher behind, because the old one is stopped first.
353
- */
354
- startWatch(request: AgentWatchStartRequest, signal?: AbortSignal): Promise<AgentWatchStartResult>;
355
- /**
356
- * Collect queued watch events: `watch.poll`.
357
- *
358
- * Since ADR-0009 the agent dispatches requests concurrently, so a poll no longer blocks the connection:
359
- * a concurrent `fs.read`/`search.grep` proceeds and is answered as soon as the agent gets to it. The poll
360
- * still *occupies one in-flight slot* for up to `timeoutMs`, so a loop of long polls can crowd the
361
- * {@link DEFAULT_AGENT_MAX_IN_FLIGHT}-slot window; keep `timeoutMs` small and poll in a loop, or use a
362
- * second connection when you need a dedicated watcher.
363
- *
364
- * Keep `timeoutMs` below the transport's `requestTimeoutMs` (default 15 s): the client deadline is a
365
- * separate clock, and when it fires first the poll fails with `CONNECTION_LOST` even though the agent
366
- * is healthy. `timeout: true` with few or no events is the normal "nothing happened" outcome, not an
367
- * error; when `truncated` is true the remaining events are still queued, so poll again.
368
- *
369
- * `dropped` is **cumulative since `watch.start`**; a growing value (or `truncated`) means the event
370
- * stream is incomplete and the honest reaction is a full `scanIndex` rescan.
371
- */
372
- pollWatch(request?: AgentWatchPollRequest, signal?: AbortSignal): Promise<AgentWatchPollResult>;
373
- /**
374
- * Stop one remote watcher: `watch.stop`. Idempotent, and stopping an absent watcher is **not** an
375
- * error — the agent answers `{ "stopped": false }`, which is returned as-is.
376
- *
377
- * `watcherId` selects which watcher to stop; omit it for the agent's `"default"` watcher, which is what
378
- * a caller that never sets one has always stopped. Stopping one id never touches another, so two
379
- * sessions sharing a connection cannot stop each other's listener.
380
- *
381
- * The first parameter also still accepts an `AbortSignal` (the pre-`watcherId` signature), so a caller
382
- * written against the old positional form keeps working instead of having its signal silently read as a
383
- * watcher name. Prefer the explicit two-argument form.
384
- *
385
- * Unpolled events are discarded by the agent, so a caller that needs a consistent view must rescan
386
- * rather than assume the queue survives the stop.
387
- */
388
- stopWatch(watcherId?: string | AbortSignal, signal?: AbortSignal): Promise<AgentWatchStopResult>;
389
- /**
390
- * Read one remote work tree's status: `git.status { root }` (task-20).
391
- *
392
- * Read-only and bounded on the agent side (5000 entries / 1 MiB of paths / 4 MiB of output / 10 s);
393
- * `truncated` travels with the result instead of being a failure. An agent that predates this method answers
394
- * `E_METHOD`, which {@link #rpc} translates to `REMOTE_UNSUPPORTED` — callers treat that as "no badges", not
395
- * as an error.
396
- *
397
- * @param root - absolute remote path (a work tree root, or any path inside it).
398
- * @param signal - caller cancellation (the agent kills the git child with the request context).
399
- * @returns the normalized status.
400
- */
401
- gitStatus(root: string, signal?: AbortSignal): Promise<AgentGitStatusResult>;
402
- /**
403
- * Discover capabilities from the agent, cached like the other transports.
404
- *
405
- * The handshake already carries the capability record, so a fresh connection answers from cache;
406
- * `maxAgeMs` (default 5 minutes) decides when to re-ask.
407
- */
408
- probeCapabilities(signal?: AbortSignal, maxAgeMs?: number): Promise<RemoteCapabilities>;
409
- }
410
- /**
411
- * The real launcher: `ssh <alias> <agent> --stdio` through `node:child_process`.
412
- *
413
- * Only this function touches the process API; every test injects its own launcher. stdout carries
414
- * protocol frames only (the agent writes diagnostics to stderr), so stderr is inherited for the
415
- * operator and stdin is the pipe this client owns.
416
- */
417
- export declare const defaultLauncher: AgentLauncher;
418
- //# sourceMappingURL=agent-transport.d.ts.map
@@ -1,121 +0,0 @@
1
- /**
2
- * The Tier 2 install ladder: probe → reuse-or-upload → verify → execute-or-fall-back.
3
- *
4
- * Tier 1 is the floor, so **nothing in here throws for a remote-side problem**: every failure becomes a
5
- * {@link AgentInstallation} with `kind: 'tier1'` plus a reason and an actionable hint. Callers then keep
6
- * using the zero-install transport, and the diagnostic survives to the UI.
7
- *
8
- * The whole ladder runs through injected I/O ({@link AgentInstallIo}) because it must be testable without a
9
- * server and, more importantly, because it has to run over the **Tier 1 transport** — the agent cannot be
10
- * used to install itself.
11
- *
12
- * Integrity rules taken from ADR-0005:
13
- *
14
- * - the artifact's SHA-256 comes from the plugin's shipped manifest, never from the remote host;
15
- * - the digest is verified **after** the upload and again after it lands at its final path;
16
- * - a file that is group- or world-writable is never executed;
17
- * - the last step is an actual execution (`--version`), because `noexec` cannot be detected reliably by
18
- * reading mount options; "it ran" is the only honest proof.
19
- *
20
- * @module dsh-remote-workspace/infra/transport/agent-installer
21
- */
22
- /** One binary shipped with the plugin, as listed in the SHA-256 manifest. */
23
- export interface AgentArtifact {
24
- /** Manifest key: `linux-amd64`, `linux-arm64`, … */
25
- readonly platform: string;
26
- /** Absolute path inside the plugin package. */
27
- readonly path: string;
28
- /** Lowercase hex SHA-256 from the manifest (trusted, local). */
29
- readonly sha256: string;
30
- readonly bytes: number;
31
- }
32
- /** Where the agent should end up, and where it is staged first. */
33
- export interface AgentInstallTarget {
34
- /** Final remote path of the binary. */
35
- readonly remotePath: string;
36
- /**
37
- * Remote directory used for staging. Must share a filesystem with {@link remotePath} so the final
38
- * `mv` is an atomic rename rather than a copy.
39
- */
40
- readonly stagingDir: string;
41
- }
42
- /** Every effect the ladder needs; all of it must work **before** Tier 2 exists. */
43
- export interface AgentInstallIo {
44
- /** Run one shell command over the Tier 1 transport. */
45
- exec(command: string, signal?: AbortSignal): Promise<{
46
- readonly exitCode: number;
47
- readonly stdout: string;
48
- readonly stderr: string;
49
- }>;
50
- /** Upload the local artifact, then verify; the caller decides sftp vs base64. */
51
- upload(localPath: string, remotePath: string, signal?: AbortSignal): Promise<void>;
52
- /** Remote `sha256sum`-equivalent, or `undefined` when the host has no usable digest tool. */
53
- digestTool(signal?: AbortSignal): Promise<'sha256sum' | 'shasum' | undefined>;
54
- /** Hex digest of a remote file using the tool reported by {@link digestTool}. */
55
- remoteSha256(path: string, tool: string, signal?: AbortSignal): Promise<string | undefined>;
56
- }
57
- /** Why the plugin stayed on Tier 1. One value per distinct operator action. */
58
- export type AgentFallbackReason = 'probe-failed' | 'platform-unsupported' | 'no-digest-tool' | 'staging-unwritable' | 'upload-failed' | 'upload-mismatch' | 'not-executable' | 'too-large';
59
- /** Outcome of the ladder. */
60
- export type AgentInstallation = {
61
- readonly kind: 'installed' | 'reused';
62
- readonly platform: string;
63
- readonly remotePath: string;
64
- readonly sha256: string;
65
- } | {
66
- readonly kind: 'tier1';
67
- readonly reason: AgentFallbackReason;
68
- readonly hint: string;
69
- readonly details: Readonly<Record<string, unknown>>;
70
- };
71
- /** Options for {@link ensureAgent}. */
72
- export interface EnsureAgentOptions {
73
- /** Every shipped artifact, normally read from the manifest. */
74
- readonly artifacts: readonly AgentArtifact[];
75
- /** Missing artifacts are the caller's problem, not the remote host's. */
76
- readonly target: AgentInstallTarget;
77
- readonly io: AgentInstallIo;
78
- readonly signal?: AbortSignal;
79
- /** Refuse to upload anything larger than this (guards against a corrupt manifest). */
80
- readonly maxBytes?: number;
81
- }
82
- /**
83
- * Map `uname` output onto a manifest platform key.
84
- *
85
- * @param system - `uname -s` (e.g. `Linux`, `Darwin`).
86
- * @param machine - `uname -m` (e.g. `x86_64`, `aarch64`).
87
- * @returns the manifest key, or `undefined` when nothing is shipped for that pair.
88
- */
89
- export declare function platformKey(system: string, machine: string): string | undefined;
90
- /**
91
- * Run the ladder: reuse an already-correct binary, otherwise install one, otherwise stay on Tier 1.
92
- *
93
- * @param options - artifacts, target, injected I/O and cancellation.
94
- * @returns the installation, or a Tier 1 fallback carrying a reason and a hint. Never throws for a
95
- * remote-side problem; a cancelled/aborted call propagates instead.
96
- */
97
- export declare function ensureAgent(options: EnsureAgentOptions): Promise<AgentInstallation>;
98
- /** Manifest schema this loader understands (written by `tools/agent-manifest.mjs`). */
99
- export declare const AGENT_MANIFEST_VERSION = 1;
100
- /**
101
- * Read the shipped manifest into install artifacts.
102
- *
103
- * Every field is validated rather than trusted: a manifest with a bad digest, a missing path or an unknown
104
- * schema version must fail **before** anything is uploaded, because this file is the root of the trust
105
- * chain (ADR-0005). `agentVersion`/`sourceCommit` are optional provenance (written by
106
- * `tools/agent-manifest.mjs generate` from the binaries themselves); a manifest without them still loads. Artifact paths are resolved relative to the manifest's own directory, so the packaged
107
- * tree can be moved as a unit.
108
- *
109
- * @param manifestPath - path to `agent-manifest.json` inside the plugin package.
110
- * @returns the parsed manifest with absolute artifact paths.
111
- * @throws Error describing the first structural problem (unreadable, unknown version, empty, malformed).
112
- */
113
- export declare function loadAgentManifest(manifestPath: string): Promise<{
114
- readonly version: number;
115
- readonly artifacts: readonly AgentArtifact[];
116
- /** Version the artifacts report (`hello.result.agent.version`), when the manifest records it. */
117
- readonly agentVersion?: string;
118
- /** Commit the artifacts were built from, when the manifest records it. */
119
- readonly sourceCommit?: string;
120
- }>;
121
- //# sourceMappingURL=agent-installer.d.ts.map
@@ -1,123 +0,0 @@
1
- /**
2
- * 代理协议的**客户端层**:请求/响应配对、在飞窗口、blob 通道与关闭时清场。
3
- *
4
- * 线上格式的读写移到 `agent-protocol-codec.ts`,错误与截止移到 `agent-protocol-errors.ts`
5
- * (审计 0079)。本文件只剩状态机本身:发请求、配对响应、管理 blob 通道、关闭时清场。
6
- *
7
- * @module dsh-remote-workspace/infra/transport/agent-protocol-client
8
- */
9
- import { SshDevError } from '../../domain/errors.ts';
10
- import type { AgentSession } from './agent-protocol-types.ts';
11
- /**
12
- * 代理协议的**客户端层**:请求/响应配对、在飞窗口、blob 通道、截止与取消、参数压缩与结果读取。
13
- *
14
- * 从 `agent-transport.ts` 拆出(审计 0077)。它只依赖帧层与类型层,**不依赖**任何 Transport 实现,
15
- * 因此可以脱离传输单独测试(既有测试正是这么用的)。
16
- *
17
- * @module dsh-remote-workspace/infra/transport/agent-protocol-client
18
- */
19
- export declare class AgentProtocolClient {
20
- #private;
21
- constructor(session: AgentSession, options?: {
22
- readonly requestTimeoutMs?: number;
23
- readonly maxInFlight?: number;
24
- readonly now?: () => number;
25
- readonly epoch?: number;
26
- readonly epochNow?: () => number;
27
- /**
28
- * Called once when the connection is declared dead.
29
- *
30
- * Without it a client-side death is invisible to the owner: a per-request deadline that expires
31
- * poisons this client permanently (`#lost` is never cleared), yet the transport still reports
32
- * `state === 'ready'`, so the transport service keeps handing the same dead client to every later
33
- * call. Every one of them then fails instantly with the *first* timeout's message and numbers.
34
- */
35
- readonly onFatal?: () => void;
36
- /**
37
- * Default session for every request on this connection, sent as `hello.params.sessionId`.
38
- *
39
- * Omitted (the default) means the field is not sent at all — byte-for-byte the pre-session
40
- * handshake, which the agent answers with `sessionId: "default"`.
41
- */
42
- readonly sessionId?: string;
43
- });
44
- /** Whether the `hello` handshake already succeeded. */
45
- get handshaked(): boolean;
46
- /** Whether the connection has been declared dead. */
47
- get lost(): boolean;
48
- /** Whether {@link close} already ran. */
49
- get closed(): boolean;
50
- /** Feed stdout bytes; called by the transport's data handler. */
51
- handleData(chunk: Uint8Array): void;
52
- /**
53
- * Handshake. The first method on a fresh session must be `hello`; a version mismatch is fatal and is
54
- * **not** retried with a guessed version.
55
- */
56
- handshake(signal?: AbortSignal, clientId?: string): Promise<unknown>;
57
- /**
58
- * Session the agent echoed for this connection, or `undefined` when it did not send one.
59
- *
60
- * `undefined` is meaningful: it means the agent predates session support, so this client must not claim
61
- * the requests are isolated from one another.
62
- */
63
- get negotiatedSessionId(): string | undefined;
64
- /**
65
- * Whether the agent reported that jobs outlive this connection (`hello.result.daemon`).
66
- *
67
- * Defaults to `false`, which is the honest reading of "the agent said nothing": today's agent does tie
68
- * job lifetime to the connection, so assuming otherwise would make callers skip cleanup that is still
69
- * required.
70
- */
71
- get daemon(): boolean;
72
- /** `capabilities`. Requires a completed handshake. */
73
- capabilities(signal?: AbortSignal): Promise<unknown>;
74
- /**
75
- * Send one control-frame request (plus an optional blob frame) and await its response.
76
- *
77
- * @throws SshDevError `CONNECTION_LOST` when the session is gone, the deadline expired mid-request, or
78
- * the stream desynchronised; `CANCELLED` when the caller's signal aborted.
79
- * @throws AgentProtocolError when the agent answered with a structured error.
80
- */
81
- request(method: string, params: Readonly<Record<string, unknown>>, options?: RequestOptions): Promise<unknown>;
82
- /**
83
- * The blob payload belonging to `control`.
84
- *
85
- * v1 semantics: a response with `result.blob === true` is followed by exactly one blob frame, so the
86
- * blob cannot be awaited *before* the control response arrives; it is persisted on arrival instead
87
- * (a fast agent may deliver it before this call is even entered).
88
- *
89
- * Under pipelining several requests may have declared a blob at once, so the channel is resolved by the
90
- * identity of the control promise instead of from one global "current blob" slot.
91
- *
92
- * Bounded on purpose: the blob shares the request deadline, so an agent that declares a blob and then
93
- * goes silent produces one failed request instead of a promise that never settles.
94
- *
95
- * @throws AgentProtocolError `E_PROTOCOL` when the response carried no blob within the deadline.
96
- */
97
- readBlob(control: Promise<unknown>, options?: {
98
- readonly signal?: AbortSignal;
99
- readonly timeoutMs?: number;
100
- }): Promise<Uint8Array>;
101
- /** Declare the connection dead: reject everything outstanding and stop accepting work. */
102
- failConnection(error: SshDevError): void;
103
- /** Stop the client; idempotent. */
104
- close(): void;
105
- }
106
- interface RequestOptions {
107
- readonly signal?: AbortSignal;
108
- readonly timeoutMs?: number;
109
- /** Blob payload to send immediately after the control frame (`fs.write`). */
110
- readonly blob?: Uint8Array;
111
- /**
112
- * Session this request belongs to; overrides the client's {@link AgentProtocolClientOptions.sessionId}
113
- * default for this one request.
114
- *
115
- * The agent gives each session its own share of the per-connection in-flight window, so naming the
116
- * session is what keeps one session's long request from queueing another's short one behind it.
117
- * Measured on a real host before the agent grew sessions (audit 0021): a `fs.stat` on session B waited
118
- * **3502 ms** behind eight long requests on session A, and **0.6 ms** when the window was not full.
119
- */
120
- readonly session?: string;
121
- }
122
- export {};
123
- //# sourceMappingURL=agent-protocol-client.d.ts.map