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,239 @@
1
+ /**
2
+ * The remote backend: one remote directory served over a {@link Transport}.
3
+ *
4
+ * This is the Tier 1 shape — no agent on the remote host, only POSIX tools the server already has
5
+ * (`sh`, `wc`, `stat`, `base64`). Directory listing is therefore one bounded shell script rather
6
+ * than a protocol method, and the script is built from a fixed template with a single quoted path,
7
+ * so there is no injection surface.
8
+ *
9
+ * Version tokens are whatever **one** transport reports for the same file state, and this backend never
10
+ * mixes two sources. On Tier 1 that is the shell `size:mtimeSeconds` shape (comparable with the ssh2
11
+ * transport); on Tier 2 it is the agent's `size:mtimeNs`, obtained from {@link Transport.statPath} so that a
12
+ * token read by `readText` and a token compared by `writeText` come from the same producer. Deriving one
13
+ * from the other is what made every read→write guard fail on Tier 2 (audit 0055).
14
+ *
15
+ * @module dsh-over-ssh/infra/backend/remote-backend
16
+ */
17
+ import type { Backend, BackendEntry, BackendRead, BackendSandboxMode, BackendStat, BackendText, BackendWrite } from '../../app/backend.ts';
18
+ import type { RemoteCapabilities } from '../../domain/capability.ts';
19
+ import { type VersionedCacheStats } from '../cache/versioned.ts';
20
+ import type { Transport } from '../transport/transport.ts';
21
+ /** Options for {@link RemoteBackend}. */
22
+ export interface RemoteBackendOptions {
23
+ /** Backend id; the router namespaces target keys with it (equals the root key). */
24
+ readonly id: string;
25
+ /** A ready transport. Mutually exclusive with {@link getTransport}. */
26
+ readonly transport?: Transport;
27
+ /**
28
+ * Lazy transport resolver, called once on first use.
29
+ *
30
+ * The root registry's backend factory is synchronous while acquiring a transport is not, so the
31
+ * connection is established when the first operation needs it and reused afterwards.
32
+ */
33
+ readonly getTransport?: () => Promise<Transport>;
34
+ /** Read cap applied by the backend itself; the router passes a smaller one when it wants to. */
35
+ readonly maxReadBytes?: number;
36
+ /**
37
+ * Freshness cache policy for the read path (FR-2.7). Omitted means {@link DEFAULT_CACHE}.
38
+ *
39
+ * This is the switch that makes "re-opening the same file within the TTL costs **zero** round trips"
40
+ * true. Before it existed, `VersionedCache` was written, benchmarked and then never constructed by
41
+ * any production code: every read went to the host, and the numbers in `baseline.md` measured a
42
+ * component nobody called (audit 0044).
43
+ */
44
+ readonly cache?: RemoteBackendCacheOptions;
45
+ }
46
+ /** Cache policy for one remote backend. */
47
+ export interface RemoteBackendCacheOptions {
48
+ /** Freshness window; the FR-2.7 budget is 5 s. `0` disables retention (every read goes remote). */
49
+ readonly ttlMs?: number;
50
+ /** Bound on retained entries. */
51
+ readonly maxEntries?: number;
52
+ /** Bound on retained payload bytes. */
53
+ readonly maxBytes?: number;
54
+ /** Injectable clock, so a TTL can be tested without sleeping. */
55
+ readonly now?: () => number;
56
+ }
57
+ /** Filesystem access for one remote root over a transport. */
58
+ export declare class RemoteBackend implements Backend {
59
+ #private;
60
+ readonly id: string;
61
+ readonly sandboxMode: BackendSandboxMode | undefined;
62
+ /**
63
+ * Whether this root has a **native** watcher: the transport is one that speaks `watch.*` *and* its handshake
64
+ * advertised `watch === "native"`. It reads `false` until the transport is known — the capability is a fact
65
+ * about the connected agent, and claiming it early would advertise a watcher for a Tier 1 host that can
66
+ * never serve one.
67
+ */
68
+ get watches(): boolean;
69
+ constructor(options: RemoteBackendOptions);
70
+ /**
71
+ * Read-path cache counters, for diagnostics and the performance baseline.
72
+ *
73
+ * Reported rather than inferred: the FR-2.7 claims ("0 round trips inside the TTL", "≈5× on
74
+ * revalidation") are only checkable if the counters behind them are reachable from outside.
75
+ */
76
+ get cacheStats(): {
77
+ readonly reads: VersionedCacheStats;
78
+ readonly listings: VersionedCacheStats;
79
+ };
80
+ /** Remote capabilities discovered so far (probe results live on the transport). */
81
+ capabilities(): Promise<RemoteCapabilities>;
82
+ /**
83
+ * Stat a path, preferring the transport's **own** token source when it has one.
84
+ *
85
+ * This is where the two tiers are reconciled, and it is the *only* place that decides. A transport that
86
+ * exposes {@link Transport.statPath} (Tier 2: the agent's `fs.stat`) reports the very same token its
87
+ * `readFile` reports (`size:mtimeNs`), so a version read by one and compared by the other is comparing
88
+ * like with like. Tier 1 has no native stat, so the shell script below runs exactly as it always has and
89
+ * its long-standing `size:mtimeSeconds` tokens are untouched.
90
+ *
91
+ * Before this, `stat` always ran the shell script: on Tier 2 it produced seconds while every read produced
92
+ * nanoseconds, so the `remote_read` → `remote_write`/`remote_edit` version guard could never pass and the
93
+ * FR-2.7 stale-revalidation branch never matched either.
94
+ */
95
+ stat(absolute: string, signal?: AbortSignal): Promise<BackendStat>;
96
+ list(absolute: string, signal?: AbortSignal): Promise<readonly BackendEntry[]>;
97
+ /**
98
+ * Read a file, going through the FR-2.7 freshness cache.
99
+ *
100
+ * The three states the budget is written in terms of:
101
+ * - **fresh** (inside the TTL): returned from memory, **zero** round trips;
102
+ * - **stale**: one cheap `stat` decides. If the version still matches, the retained body is reused and
103
+ * only the timestamp is refreshed — this is the ≈5× revalidation path;
104
+ * - **gone or changed**: re-read and replace.
105
+ *
106
+ * A read with a different cap is a different cache entry (see the field notes), so a capped probe can
107
+ * never satisfy a full read.
108
+ */
109
+ readBytes(absolute: string, maxBytes: number, signal?: AbortSignal): Promise<BackendRead>;
110
+ /**
111
+ * Atomic in-place edit through the agent's `fs.edit` (审计 0030).
112
+ *
113
+ * The caller passes the version it just read; the agent re-checks it, so a concurrent modification
114
+ * between our read and this call is refused rather than clobbered. Throws `REMOTE_UNSUPPORTED` when the
115
+ * agent predates the method, which is what makes `ctx.fs` fall back to read → replace → write.
116
+ */
117
+ editText(absolute: string, oldString: string, newString: string, replaceAll: boolean, expectedVersion: string | undefined, signal?: AbortSignal): Promise<BackendWrite & {
118
+ readonly replaced: number;
119
+ }>;
120
+ /**
121
+ * Apply a unified diff server-side: every hunk in one round trip, all-or-nothing.
122
+ *
123
+ * No client-side fallback on purpose. Reading the file, patching it here and writing it back would cost
124
+ * three round trips for two hunks — more than the `remote_edit` calls it is meant to replace — and would
125
+ * need a second implementation of the hunk-matching rules.
126
+ */
127
+ patchText(absolute: string, diff: string, expectedVersion: string | undefined, signal?: AbortSignal): Promise<BackendWrite & {
128
+ readonly hunks: number;
129
+ }>;
130
+ readText(absolute: string, maxBytes: number, signal?: AbortSignal): Promise<BackendText>;
131
+ /**
132
+ * Cache-aware wrapper around {@link readManyUncached}, so a batch sweep leaves the cache warm.
133
+ *
134
+ * Satisfying the whole batch from cache is deliberately **not** attempted: the batch exists to collapse
135
+ * N round trips into one, and splitting it into cached and uncached halves would give back the round trips
136
+ * it was built to save. Cached entries are therefore refreshed from the batch's answer, and files already
137
+ * fresh are still fetched (one shared round trip) rather than removed from the batch.
138
+ */
139
+ readMany(absolutePaths: readonly string[], options: {
140
+ readonly maxBytesEach: number;
141
+ readonly totalBytesCap?: number;
142
+ readonly signal?: AbortSignal;
143
+ }): Promise<{
144
+ readonly chunks: readonly (BackendText | undefined)[];
145
+ readonly missing: readonly string[];
146
+ }>;
147
+ /**
148
+ * Write text remotely.
149
+ *
150
+ * **The version guard is tier-aware, and that is the point.** The token a caller passes back is one this
151
+ * backend just handed out, so the guard must be verified by the *same* token source:
152
+ *
153
+ * - a transport with a native version token (Tier 2) verifies the token itself through
154
+ * {@link WriteFileRequest.expectedVersion}, which is what its own `fs.write` guard compares — no digest
155
+ * is involved and none could be, because the agent advertises no sha256 tool;
156
+ * - a Tier 1 transport verifies a content digest, so the current content is hashed and handed over as
157
+ * `expectedSha256`. That path is unchanged.
158
+ *
159
+ * A plain write is one round trip plus one `stat` for the new version.
160
+ */
161
+ writeText(absolute: string, text: string, expectedVersion: string | undefined, signal?: AbortSignal): Promise<BackendWrite>;
162
+ /**
163
+ * Watch one remote path (directory, or one file) through the agent's native watcher.
164
+ *
165
+ * The agent watches **directories**: `watch.start` registers recursively from the given roots, and each
166
+ * reported event carries the path of the changed entry. A file target therefore watches its parent directory
167
+ * and filters events by exact path — the same shape the platform's own local watcher uses, and the only way
168
+ * inotify can report changes to one file.
169
+ *
170
+ * Degradation is **not** silent:
171
+ * - `watch.start` reporting `truncated` (maxWatches / `max_user_watches` cut registration short) means the
172
+ * tree is only partially covered; the watcher is stopped and this call fails rather than promising
173
+ * notifications it cannot deliver;
174
+ * - a poll whose cumulative `dropped` grew means events were lost (queue limit / kernel overflow); the
175
+ * owner is told through `changed(error)` and the watcher stops, so the caller rescans instead of trusting
176
+ * an incomplete stream.
177
+ *
178
+ * `changed()` also covers "nothing to re-stat but something happened" — the platform's contract is
179
+ * "re-stat this path", so events are coalesced per poll.
180
+ *
181
+ * @param absolute - root-absolute remote path (directory or file).
182
+ * @param changed - platform callback: no argument = re-stat; an error = the subscription is dead.
183
+ * @param signal - cancels the subscription; stopping is immediate and idempotent.
184
+ * @returns the disposer (stops the watcher; idempotent).
185
+ * @throws SshDevError `REMOTE_UNSUPPORTED` when this connection has no native watcher, `REMOTE_NOT_FOUND`
186
+ * when a file target's parent directory does not exist, `CANCELLED` when aborted during setup.
187
+ */
188
+ watch(absolute: string, changed: (error?: Error) => void, signal: AbortSignal): Promise<() => Promise<void>>;
189
+ /**
190
+ * Stop every watcher this backend started.
191
+ *
192
+ * Not part of {@link Backend}: the wiring owns backend teardown and is outside this change's scope. It is
193
+ * public so whoever retires the backend can end the poll loops, and so tests can prove they stop.
194
+ */
195
+ dispose(): Promise<void>;
196
+ /**
197
+ * Create a directory (FR-2.2.2).
198
+ *
199
+ * Prefers the agent's `fs.mkdir` (Tier 2) and falls back to one bounded shell script. The shell path is not
200
+ * dead weight: Tier 1 has no agent, and an older agent answers `E_METHOD`, which
201
+ * {@link Transport.makeDir} reports as `REMOTE_UNSUPPORTED`.
202
+ *
203
+ * **Why the protocol path is worth preferring**: the shell version depends on the remote having a POSIX
204
+ * `mkdir`, and its refusals have to be parsed back out of the script's own `DSH_ERROR=` line. Windows
205
+ * targets have neither. The product rule — `recursive: false` refuses a name that is already taken, so a
206
+ * 「新建目录」 dialog cannot silently "succeed" into someone else's folder — is applied **here**, once, for
207
+ * both paths.
208
+ */
209
+ mkdir(absolute: string, options: {
210
+ readonly recursive: boolean;
211
+ }, signal?: AbortSignal): Promise<void>;
212
+ /**
213
+ * Rename/move a path (FR-2.2.2), refusing to clobber.
214
+ *
215
+ * The no-clobber rule lives in {@link renameCommand}'s script (see its notes): `mv` would either replace a
216
+ * file or move the source *inside* an existing directory, and both are silent.
217
+ */
218
+ rename(from: string, to: string, options: {
219
+ readonly createParents: boolean;
220
+ }, signal?: AbortSignal): Promise<void>;
221
+ /** Remove a path (FR-2.2.2); `recursive: false` refuses a directory. */
222
+ remove(absolute: string, options: {
223
+ readonly recursive: boolean;
224
+ }, signal?: AbortSignal): Promise<void>;
225
+ /**
226
+ * Change one path's permission bits.
227
+ *
228
+ * Runs through `exec` rather than a transport primitive because the agent has no `fs.chmod`; the same
229
+ * script therefore works on both tiers, which is what keeps `remote_write`'s `mode` from meaning two
230
+ * different things depending on the host (audit 0064 P4). `chmodCommand` re-validates the octal digits, so
231
+ * this is not a place where model-supplied text reaches a shell unchecked.
232
+ */
233
+ chmod(absolute: string, mode: string, signal?: AbortSignal): Promise<void>;
234
+ /** Whether this backend's root contains a path (used by the router's `contains`). */
235
+ contains(parent: string, candidate: string): boolean;
236
+ }
237
+ /** Parse `KEY=value` header lines into a map. */
238
+ export declare function parseHeaders(stdout: string): Map<string, string>;
239
+ //# sourceMappingURL=remote-backend.d.ts.map
@@ -0,0 +1,52 @@
1
+ /**
2
+ * A bounded LRU cache: at most `maxEntries` entries and at most `maxBytes` of payload.
3
+ *
4
+ * Both bounds are enforced because "number of open files" and "memory held by cached file
5
+ * bodies" fail differently: a repository of tiny files hits the entry bound, a few large
6
+ * generated files hit the byte bound.
7
+ *
8
+ * @module dsh-over-ssh/infra/cache/lru
9
+ */
10
+ /** Bounds for one cache. */
11
+ export interface LruCacheOptions {
12
+ /** Maximum number of retained entries; at least 1. */
13
+ readonly maxEntries: number;
14
+ /** Maximum total payload bytes; at least 1. */
15
+ readonly maxBytes: number;
16
+ }
17
+ /** Observable cache counters. */
18
+ export interface LruStats {
19
+ readonly entries: number;
20
+ readonly bytes: number;
21
+ readonly hits: number;
22
+ readonly misses: number;
23
+ readonly evictions: number;
24
+ /** Values rejected because a single value exceeded `maxBytes`. */
25
+ readonly rejectedOversize: number;
26
+ }
27
+ /** A size-bounded, recency-ordered map. */
28
+ export declare class LruCache<V> {
29
+ #private;
30
+ constructor(options: LruCacheOptions, sizeOf: (value: V) => number);
31
+ /** Look up a value and mark it most-recently used. */
32
+ get(key: string): V | undefined;
33
+ /** Look up a value without changing recency or counters. */
34
+ peek(key: string): V | undefined;
35
+ /** Whether the key is retained. */
36
+ has(key: string): boolean;
37
+ /**
38
+ * Insert or replace a value.
39
+ *
40
+ * @returns `false` when the value alone exceeds `maxBytes` and was therefore not stored.
41
+ */
42
+ set(key: string, value: V): boolean;
43
+ /** Remove a key, returning whether it was present. */
44
+ delete(key: string): boolean;
45
+ /** Remove everything. */
46
+ clear(): void;
47
+ /** Keys from least to most recently used. */
48
+ keys(): readonly string[];
49
+ /** Current counters. */
50
+ get stats(): LruStats;
51
+ }
52
+ //# sourceMappingURL=lru.d.ts.map
@@ -0,0 +1,77 @@
1
+ /**
2
+ * TTL + version cache used by the remote read path.
3
+ *
4
+ * The performance budget depends on this three-state behaviour:
5
+ * - within TTL: a hit with zero remote round trips;
6
+ * - past TTL: a *stale* hit whose body may still be reused if a cheap version check matches
7
+ * (this is what makes revalidation ≈5× cheaper than a first open);
8
+ * - invalidated: no usable body.
9
+ *
10
+ * @module dsh-over-ssh/infra/cache/versioned
11
+ */
12
+ /** Cache policy for one cache instance. */
13
+ export interface VersionedCacheOptions {
14
+ /** Freshness window in milliseconds. */
15
+ readonly ttlMs: number;
16
+ readonly maxEntries: number;
17
+ readonly maxBytes: number;
18
+ /** Monotonic-or-wall clock; injectable so tests are deterministic. */
19
+ readonly now?: () => number;
20
+ }
21
+ /** One lookup result. */
22
+ export interface VersionedHit<V> {
23
+ readonly value: V;
24
+ /** The version token recorded when the body was cached. */
25
+ readonly version: string;
26
+ /** Whether the entry is still inside its freshness window. */
27
+ readonly fresh: boolean;
28
+ readonly ageMs: number;
29
+ }
30
+ /** Counters reported for diagnostics and the performance baseline. */
31
+ export interface VersionedCacheStats {
32
+ readonly entries: number;
33
+ readonly bytes: number;
34
+ readonly hits: number;
35
+ readonly misses: number;
36
+ readonly freshHits: number;
37
+ readonly staleHits: number;
38
+ /** Version checks that reused an existing body. */
39
+ readonly revalidations: number;
40
+ readonly evictions: number;
41
+ }
42
+ /** Freshness-aware cache keyed by route key. */
43
+ export declare class VersionedCache<V> {
44
+ #private;
45
+ constructor(options: VersionedCacheOptions, sizeOf: (value: V) => number);
46
+ /**
47
+ * Look up an entry.
48
+ *
49
+ * @param key - route key.
50
+ * @returns the entry with its freshness, or `undefined` when nothing is retained.
51
+ */
52
+ get(key: string): VersionedHit<V> | undefined;
53
+ /** Insert or replace an entry, stamping it as just-read. */
54
+ set(key: string, value: V, version: string): boolean;
55
+ /**
56
+ * Refresh an entry's timestamp after a cheap remote version check matched.
57
+ *
58
+ * @param key - route key.
59
+ * @param version - the version observed by the check; a mismatch refuses the touch.
60
+ * @returns whether the entry was refreshed.
61
+ */
62
+ touch(key: string, version: string): boolean;
63
+ /** Drop one entry. */
64
+ invalidate(key: string): boolean;
65
+ /**
66
+ * Drop every entry whose key starts with the prefix, used when a directory epoch changes.
67
+ *
68
+ * @param prefix - key prefix.
69
+ * @returns the number of removed entries.
70
+ */
71
+ invalidatePrefix(prefix: string): number;
72
+ /** Remove everything. */
73
+ clear(): void;
74
+ /** Current counters. */
75
+ get stats(): VersionedCacheStats;
76
+ }
77
+ //# sourceMappingURL=versioned.d.ts.map
@@ -0,0 +1,19 @@
1
+ /**
2
+ * A temporary file name that is unique per **write**, not just per process.
3
+ *
4
+ * The pid alone is not enough: two concurrent writes to the same target from one process picked the same
5
+ * temp name, so A's rename could publish B's bytes while reporting A's byte count, and B's rename then
6
+ * failed because the file was already gone (audit 0097). A per-process sequence closes that window, and the
7
+ * pid keeps the name distinct from another host process writing the same file.
8
+ *
9
+ * @module dsh-over-ssh/infra/temp-name
10
+ */
11
+ /**
12
+ * Build a temp path next to `path` that no other in-flight write in this process will reuse.
13
+ *
14
+ * @param path - the target the temp file is later renamed onto.
15
+ * @returns `path` plus a `.dsh-tmp-<pid>-<n>` suffix, keeping the file in the same directory so the rename
16
+ * stays a rename and not a cross-device copy.
17
+ */
18
+ export declare function temporaryNameFor(path: string): string;
19
+ //# sourceMappingURL=temp-name.d.ts.map
@@ -0,0 +1,121 @@
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-over-ssh/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
@@ -0,0 +1,132 @@
1
+ /**
2
+ * 代理协议的**客户端层**:请求/响应配对、在飞窗口、blob 通道与关闭时清场。
3
+ *
4
+ * 线上格式的读写移到 `agent-protocol-codec.ts`,错误与截止移到 `agent-protocol-errors.ts`
5
+ * (审计 0079)。本文件只剩状态机本身:发请求、配对响应、管理 blob 通道、关闭时清场。
6
+ *
7
+ * @module dsh-over-ssh/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-over-ssh/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 the agent accepted request-level cancellation (`capabilities.cancel === true`). */
49
+ get cancelSupported(): boolean;
50
+ /**
51
+ * Record the cancellation capability the handshake reported.
52
+ *
53
+ * Called by the owning transport after it adopts `hello`/`capabilities`; a client that is never told keeps
54
+ * the old behaviour. `true` only when the agent literally sent `true` — a missing bit is not a maybe.
55
+ */
56
+ setCancelSupport(supported: boolean): void;
57
+ /** Whether {@link close} already ran. */
58
+ get closed(): boolean;
59
+ /** Feed stdout bytes; called by the transport's data handler. */
60
+ handleData(chunk: Uint8Array): void;
61
+ /**
62
+ * Handshake. The first method on a fresh session must be `hello`; a version mismatch is fatal and is
63
+ * **not** retried with a guessed version.
64
+ */
65
+ handshake(signal?: AbortSignal, clientId?: string): Promise<unknown>;
66
+ /**
67
+ * Session the agent echoed for this connection, or `undefined` when it did not send one.
68
+ *
69
+ * `undefined` is meaningful: it means the agent predates session support, so this client must not claim
70
+ * the requests are isolated from one another.
71
+ */
72
+ get negotiatedSessionId(): string | undefined;
73
+ /**
74
+ * Whether the agent reported that jobs outlive this connection (`hello.result.daemon`).
75
+ *
76
+ * Defaults to `false`, which is the honest reading of "the agent said nothing": today's agent does tie
77
+ * job lifetime to the connection, so assuming otherwise would make callers skip cleanup that is still
78
+ * required.
79
+ */
80
+ get daemon(): boolean;
81
+ /** `capabilities`. Requires a completed handshake. */
82
+ capabilities(signal?: AbortSignal): Promise<unknown>;
83
+ /**
84
+ * Send one control-frame request (plus an optional blob frame) and await its response.
85
+ *
86
+ * @throws SshDevError `CONNECTION_LOST` when the session is gone, the deadline expired mid-request, or
87
+ * the stream desynchronised; `CANCELLED` when the caller's signal aborted.
88
+ * @throws AgentProtocolError when the agent answered with a structured error.
89
+ */
90
+ request(method: string, params: Readonly<Record<string, unknown>>, options?: RequestOptions): Promise<unknown>;
91
+ /**
92
+ * The blob payload belonging to `control`.
93
+ *
94
+ * v1 semantics: a response with `result.blob === true` is followed by exactly one blob frame, so the
95
+ * blob cannot be awaited *before* the control response arrives; it is persisted on arrival instead
96
+ * (a fast agent may deliver it before this call is even entered).
97
+ *
98
+ * Under pipelining several requests may have declared a blob at once, so the channel is resolved by the
99
+ * identity of the control promise instead of from one global "current blob" slot.
100
+ *
101
+ * Bounded on purpose: the blob shares the request deadline, so an agent that declares a blob and then
102
+ * goes silent produces one failed request instead of a promise that never settles.
103
+ *
104
+ * @throws AgentProtocolError `E_PROTOCOL` when the response carried no blob within the deadline.
105
+ */
106
+ readBlob(control: Promise<unknown>, options?: {
107
+ readonly signal?: AbortSignal;
108
+ readonly timeoutMs?: number;
109
+ }): Promise<Uint8Array>;
110
+ /** Declare the connection dead: reject everything outstanding and stop accepting work. */
111
+ failConnection(error: SshDevError): void;
112
+ /** Stop the client; idempotent. */
113
+ close(): void;
114
+ }
115
+ interface RequestOptions {
116
+ readonly signal?: AbortSignal;
117
+ readonly timeoutMs?: number;
118
+ /** Blob payload to send immediately after the control frame (`fs.write`). */
119
+ readonly blob?: Uint8Array;
120
+ /**
121
+ * Session this request belongs to; overrides the client's {@link AgentProtocolClientOptions.sessionId}
122
+ * default for this one request.
123
+ *
124
+ * The agent gives each session its own share of the per-connection in-flight window, so naming the
125
+ * session is what keeps one session's long request from queueing another's short one behind it.
126
+ * Measured on a real host before the agent grew sessions (audit 0021): a `fs.stat` on session B waited
127
+ * **3502 ms** behind eight long requests on session A, and **0.6 ms** when the window was not full.
128
+ */
129
+ readonly session?: string;
130
+ }
131
+ export {};
132
+ //# sourceMappingURL=agent-protocol-client.d.ts.map