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,407 +0,0 @@
1
- /**
2
- * The same-origin HTTP route a client panel uses to **edit** roots and connections.
3
- *
4
- * Why a plain HTTP route instead of a Typert `@Remote` service (the shape audit 0021 used for reads):
5
- *
6
- * - every action here carries **parameters**, and a Client Remote descriptor with parameters requires a
7
- * generated strict codec that this repository cannot produce (`doc/02-platform/client-plugin.md` §4);
8
- * - the panel is served from the GUI's own origin, so a relative `fetch` is same-origin: no CORS
9
- * concession, no code generation, no `@Remote` registration — exactly the shape the third-party
10
- * `dsh-plugin-stock-market` uses (`ctx.webServer.register({ kind: 'exact', path, handler })`).
11
- *
12
- * This module is a **handler factory decoupled from cordis**: everything it needs arrives through
13
- * {@link RootsApiDeps}, so it can be unit-tested with fake dependencies and fake request/response objects.
14
- * The Lead performs the actual registration:
15
- *
16
- * ```ts
17
- * const api = createRootsApi({ ... })
18
- * disposers.push(webServer.register({ kind: 'exact', path: ROOTS_API_PATH, handler: api.handle }))
19
- * ```
20
- *
21
- * Contract:
22
- *
23
- * | Request | Answer |
24
- * |---|---|
25
- * | `GET` | `200 { ok: true, roots, connections, userRoots, warnings }` |
26
- * | `POST` `{ action: 'add-root' \| 'remove-root' \| 'add-connection' \| 'update-connection' \| 'remove-connection', ... }` | `200 { ok: true, action, state }` |
27
- * | `POST` `{ action: 'add-root', location, path, connectionId?, workspaces?, reuseExisting? }` | `200 { ok: true, action, state, anchorPath? }` — `reuseExisting: true` makes an already-persisted root idempotent and answers its anchor instead of `400 该根已存在` |
28
- * | `POST` `{ action: 'remove-root', key, workspace? }` | `200 { ok: true, action, state }` — `workspace` removes the root from that workspace only; the root disappears when the last one drops it |
29
- * | `POST` `{ action: 'update-connection', id, newId?, host?, user?, port?, authKind?, keyPath?, credentialKey?, transport?, password? }` | `200 { ok: true, action, state }` — absent fields keep their value; `newId` renames and migrates the connection's roots (keeping their anchors) |
30
- * | `POST` `{ action: 'list-remote', connectionId, path }` | `200 { ok: true, action, connectionId, path, entries: [{ name, kind }], truncated }` |
31
- * | `POST` `{ action: 'list-local', path? }` | `200 { ok: true, action, path, entries: [{ name, kind }], crumbs, truncated }` |
32
- * | `POST` `{ action: 'list-ssh-config' }` | `200 { ok: true, action, candidates: [{ alias, host, user?, port?, hasIdentityFile, hasProxyJump, suggestedId }], skipped: [{ pattern, reason, line }] }` (FR-2.1.4) |
33
- * | `POST` `{ action: 'import-ssh-config', aliases: [alias, …] }` | `200 { ok: true, action, imported: [id, …], refused: [{ alias, reason }], state }` — the body names **aliases**, never ids/hosts/paths: every field is re-derived from the config |
34
- * | any validation / trust / body failure | `400`/`403` `{ ok: false, error, reason? }` |
35
- * | any other method | `405` `{ ok: false, error }` + `Allow: GET, HEAD, POST` |
36
- *
37
- * Two invariants this layer owns:
38
- *
39
- * 1. **Credentials never come back out.** `connections` (live and persisted), the `state` echoed by
40
- * `POST` and every directory row (`list-remote` and `list-local`) are whitelist projections:
41
- * `{ id, host, user, port, authKind, transport? }` and `{ name, kind }` respectively. `keyPath`, `password`,
42
- * `passphrase`, `credentialKey`, `alias`, `proxyJump`, sizes, times, modes and symlink targets are
43
- * never serialised, and no file content is readable through this route at all.
44
- * 2. **Parameters are taken from a whitelist, one field at a time** — the body is never spread into a
45
- * call. A field the UI does not own cannot reach the seam, however the body is shaped.
46
- *
47
- * @module dsh-remote-workspace/integration/roots-api
48
- */
49
- import { type UserConnectionRecord, type UserRootEntry, type UserRootsState } from './user-roots.ts';
50
- /** The one path this plugin claims on the host HTTP carrier. */
51
- export declare const ROOTS_API_PATH = "/api/remote-workspace/roots";
52
- /** Largest accepted request body. A root/connection edit is a few hundred bytes; 64 KiB is a ceiling, not a budget. */
53
- export declare const ROOTS_API_MAX_BODY_BYTES: number;
54
- /** Entries one `list-remote` answer carries at most; the tail is dropped and `truncated` says so. */
55
- export declare const REMOTE_LIST_MAX_ENTRIES = 2000;
56
- /** Entries one `list-local` answer carries at most (the same budget as `list-remote`). */
57
- export declare const LOCAL_LIST_MAX_ENTRIES = 2000;
58
- /**
59
- * One remote directory row as the wire may carry it.
60
- *
61
- * **Names and kinds only.** A browsing client needs to render a list and decide what is enterable, and
62
- * nothing else: no sizes, no times, no symlink targets, no file contents, no credentials. The projection
63
- * below is what enforces that, field by field, exactly like `publicConnection` does for connections.
64
- */
65
- export interface RemoteDirectoryEntry {
66
- /** Base name of the entry. */
67
- readonly name: string;
68
- /** What the row is; anything the listing cannot classify is reported as `file`. */
69
- readonly kind: 'directory' | 'file' | 'symlink';
70
- }
71
- /** Where one `list-remote` request points. */
72
- export interface RemoteListRequest {
73
- /** Connection the directory lives behind. */
74
- readonly connectionId: string;
75
- /** Absolute POSIX path on that host. */
76
- readonly path: string;
77
- }
78
- /**
79
- * One **local** directory row as the wire may carry it.
80
- *
81
- * The same discipline as {@link RemoteDirectoryEntry} with a narrower vocabulary: a local `readdir` can
82
- * report sizes, modes, owners and times without a second syscall, and it can only tell a link from a
83
- * directory by following it — which this route never does. So anything that is neither a plain file nor a
84
- * plain directory is answered as `other`, and no field but the name and the kind ever leaves the host.
85
- */
86
- export interface LocalDirectoryEntry {
87
- /** Base name of the entry. */
88
- readonly name: string;
89
- /** What the row is: a plain file, a plain directory, or something else (a symlink, a socket, a device). */
90
- readonly kind: 'file' | 'directory' | 'other';
91
- }
92
- /** Where one `list-local` request points. */
93
- export interface LocalListRequest {
94
- /** Absolute, `..`-free path on **this** machine, already defaulted and canonicalised by the handler. */
95
- readonly path: string;
96
- }
97
- /** One action `POST` accepts; anything else is refused with the list. */
98
- export declare const ROOTS_API_ACTIONS: readonly ["add-root", "remove-root", "add-connection", "update-connection", "test-connection", "remove-connection", "list-remote", "list-local", "list-ssh-config", "import-ssh-config", "mkdir-remote", "rename-remote", "remove-remote", "list-transfers", "clear-transfers"];
99
- /** One accepted action. */
100
- export type RootsApiAction = (typeof ROOTS_API_ACTIONS)[number];
101
- /**
102
- * The request surface this handler consumes: a structural subset of `node:http`'s `IncomingMessage`, so a
103
- * test can pass a `Readable` with `method`/`url`/`headers` and no cast, while a real `IncomingMessage`
104
- * still satisfies it (asserted at compile time in `tests/roots-api.test.ts`).
105
- */
106
- export interface RootsApiRequest {
107
- readonly method?: string | undefined;
108
- readonly url?: string | undefined;
109
- readonly headers: Readonly<Record<string, string | readonly string[] | undefined>>;
110
- [Symbol.asyncIterator](): AsyncIterator<unknown>;
111
- }
112
- /** The response surface this handler consumes: a structural subset of `ServerResponse`. */
113
- export interface RootsApiResponse {
114
- statusCode: number;
115
- setHeader(name: string, value: string): void;
116
- end(chunk?: string): void;
117
- }
118
- /** The seams the handler needs. All of them are the Lead's wiring; none of them is imported from cordis. */
119
- export interface RootsApiDeps {
120
- /** Live roots (the registry snapshot), as roots — credentials are stripped here anyway. */
121
- listRoots(): Promise<readonly unknown[]> | readonly unknown[];
122
- /** Live connections (profiles), which **do** carry credentials; only the whitelist is answered. */
123
- listConnections(): Promise<readonly unknown[]> | readonly unknown[];
124
- /** The persisted panel-owned state plus its warnings (see `createUserRootsStore().load`). */
125
- loadUserState(): Promise<{
126
- readonly state: UserRootsState;
127
- readonly warnings: readonly string[];
128
- }> | {
129
- readonly state: UserRootsState;
130
- readonly warnings: readonly string[];
131
- };
132
- /** Persist a whole state; returns what was written (normalised). */
133
- saveUserState(next: UserRootsState): Promise<UserRootsState> | UserRootsState;
134
- /** Make a root live **and** remember it. The handler reconciles the file afterwards, so persisting here is expected but not required (a second write is skipped when the content already matches). */
135
- registerRoot(entry: UserRootEntry): Promise<unknown> | unknown;
136
- /** Drop a root from the live side (and, if the wiring persists there, from the file). */
137
- unregisterRoot(key: string): Promise<unknown> | unknown;
138
- /** Remember a connection. Applying it to a live profile is a reload concern, not this handler's. */
139
- addConnection(entry: UserConnectionRecord): Promise<unknown> | unknown;
140
- /**
141
- * Optional: apply a patched connection to the **live** profile map (and, if the wiring chooses, forget what
142
- * was cached for it: the agent installation state and the probed remote home are properties of the account,
143
- * so a changed host must not keep the previous host's answers).
144
- *
145
- * The handler already made the rename coherent for the *roots* (see {@link updateConnectionInState}); this
146
- * seam is what makes it coherent for the live transports. Absent means the change is persisted only, and
147
- * takes effect after a restart — which is why the wiring provides it.
148
- */
149
- applyConnection?: ((entry: UserConnectionRecord, previousId: string) => Promise<unknown> | unknown) | undefined;
150
- /**
151
- * Optional: forget a connection on the **live** side after it was removed.
152
- *
153
- * The mirror image of {@link RootsApiDeps.applyConnection}, and it exists for a reason that is easy to miss:
154
- * the `GET` answer lists connections from the **live** profile map, not from the file. Dropping the record
155
- * from `user-roots.json` alone therefore leaves the connection on screen — the panel would show a row that
156
- * no longer exists in any file and whose 「删除」 button reports success forever. Everything the plugin caches
157
- * **per connection** belongs to this call too: the live transport (a real SSH master or agent child process),
158
- * the remote index, the installed-agent bookkeeping, the stored askpass secret and its temp directory, the
159
- * probed remote home. A connection the user deleted must stop costing memory and processes.
160
- *
161
- * Absent means the removal is persisted only and the connection stays visible until a restart, which is why
162
- * the wiring provides it.
163
- */
164
- removeConnection?: ((id: string) => Promise<unknown> | unknown) | undefined;
165
- /**
166
- * Optional: hands back the credential store for **this** request; it writes and drops, never reads.
167
- *
168
- * A getter rather than a value on purpose: a service read while the entry activates can still be absent —
169
- * `ctx.get('webServer')` behaved exactly that way on the packaged runtime — and capturing `undefined` once
170
- * would disable password connections for the whole process. A composition that never provides one refuses
171
- * `authKind: 'password'` with an explicit message instead of storing a password in a file the user was told
172
- * holds no secrets.
173
- */
174
- credentials?: (() => CredentialsSeam | undefined) | undefined;
175
- /**
176
- * Optional: really connect to one saved connection and run one command, for the panel's 「测试」 button.
177
- *
178
- * Optional for the same reason as every other capability here: a composition without a transport service
179
- * answers the action with an explicit failure instead of pretending the connection is fine. The result is
180
- * projected field by field, so a wiring that returns extra facts cannot leak them.
181
- */
182
- testConnection?: ((id: string) => Promise<unknown> | unknown) | undefined;
183
- /**
184
- * Optional: list one directory on a connection, for the browsing action (`list-remote`).
185
- *
186
- * The wiring implements it with the transport's own directory primitive; a composition that does not
187
- * provide it answers `list-remote` with an explicit failure instead of failing to activate. Whatever it
188
- * returns is projected to `{ name, kind }` before it reaches the wire.
189
- */
190
- listRemoteDirectory?: ((input: RemoteListRequest) => Promise<readonly unknown[]> | readonly unknown[]) | undefined;
191
- /**
192
- * Optional: the directory a remote browse starts at when the client names none.
193
- *
194
- * A getter rather than a value for the same reason as {@link credentials}: resolving it may need a live
195
- * transport, which can appear after this entry activates. When absent, an empty `path` degrades to `/`.
196
- */
197
- remoteHomeDirectory?: ((connectionId: string) => Promise<string> | string) | undefined;
198
- /**
199
- * Optional: list one directory **on this machine**, for the browsing action (`list-local`).
200
- *
201
- * Same contract as {@link RootsApiDeps.listRemoteDirectory} — rows in, `{ name, kind }` out — and injected
202
- * for the same two reasons: the domain layer imports no `@deepseek-ai/*` (and no `node:fs`), and a test can
203
- * assert what the seam was asked for without touching the disk. The wiring implements it with `node:fs`,
204
- * so a machine whose platform directory picker has no `browse` capability still has a working browser.
205
- *
206
- * Whatever it throws must carry the filesystem's own `errno` code (`ENOENT`, `ENOTDIR`, `EACCES`, …): that
207
- * is how the handler turns "there is no such directory" into a machine-readable `400 reason` instead of an
208
- * empty listing. A row without a usable name is dropped before it reaches the wire.
209
- */
210
- listLocalDirectory?: ((input: LocalListRequest) => Promise<readonly unknown[]> | readonly unknown[]) | undefined;
211
- /**
212
- * Optional: read the user's own `~/.ssh/config` for the import action (FR-2.1.4).
213
- *
214
- * Injected for the same two reasons as the directory seams: the domain layer reads no files, and a test can
215
- * answer with a fixture instead of depending on the developer's home directory. It returns the **text**, not
216
- * parsed entries, so the parsing (`domain/ssh-config.ts`) stays a pure function under test.
217
- *
218
- * Absent means the action answers an explicit "this composition cannot read the file" failure rather than
219
- * silently importing nothing.
220
- */
221
- readSshConfig?: (() => Promise<string | undefined> | string | undefined) | undefined;
222
- /**
223
- * Optional: the remote-file operations the file tree needs (FR-2.2.2).
224
- *
225
- * One seam rather than three, because all three are the same capability from this route's point of view
226
- * ("change the remote filesystem") and a composition either has it or does not. `rename` must refuse to
227
- * clobber and `remove` must refuse a directory without `recursive` — the contract is in `app/backend.ts`.
228
- */
229
- remoteFiles?: RemoteFileOperations | undefined;
230
- /**
231
- * Optional: the in-flight and recent transfers (FR-2.6.4).
232
- *
233
- * A **pull** seam, not a push one: the client half has exactly one HTTP route, so "what is happening" is a
234
- * question it asks, not a stream it subscribes to. Absent means the two transfer actions answer an explicit
235
- * refusal instead of an empty list — "nothing is running" and "this composition cannot tell you" are
236
- * different answers, and only one of them is honest.
237
- */
238
- transfers?: TransferReport | undefined;
239
- }
240
- /** What the route needs from the tracker, without importing it (the handler stays decoupled). */
241
- export interface TransferReport {
242
- list(): readonly unknown[];
243
- clearFinished(): number;
244
- }
245
- /** The three mutating remote-file operations (FR-2.2.2). */
246
- export interface RemoteFileOperations {
247
- mkdir(input: {
248
- readonly connectionId: string;
249
- readonly path: string;
250
- readonly recursive: boolean;
251
- }): Promise<void> | void;
252
- rename(input: {
253
- readonly connectionId: string;
254
- readonly from: string;
255
- readonly to: string;
256
- readonly createParents: boolean;
257
- }): Promise<void> | void;
258
- remove(input: {
259
- readonly connectionId: string;
260
- readonly path: string;
261
- readonly recursive: boolean;
262
- }): Promise<void> | void;
263
- }
264
- /**
265
- * The credential store as this route needs it: **write, drop, and nothing else**.
266
- *
267
- * There is deliberately no read here. The route must not be able to answer with a stored secret, so the only
268
- * operations it can ask for are the two that leave the value in the store.
269
- */
270
- export interface CredentialsSeam {
271
- /** Store one value under a reference name (the platform rejects an empty one). */
272
- set(ref: string, value: string): Promise<void> | void;
273
- /** Drop one stored value; a no-op when the name is not present. */
274
- unset(ref: string): Promise<void> | void;
275
- }
276
- /** The handler pair the Lead registers. */
277
- export interface RootsApi {
278
- /**
279
- * Serve one request. Never throws: a handler that rejects would leave the socket without a status.
280
- *
281
- * Bound as an arrow property so `handler: api.handle` can be passed directly.
282
- */
283
- readonly handle: (req: RootsApiRequest, res: RootsApiResponse) => Promise<void>;
284
- /** No resources are held; the route registration belongs to the caller. */
285
- dispose(): void;
286
- }
287
- /** Reasons a request is refused before any action runs. */
288
- export type TrustVerdict = 'ok' | 'no-host' | 'bad-host' | 'cross-site' | 'bad-origin';
289
- /**
290
- * Decide whether a request is same-origin enough to serve.
291
- *
292
- * The rule, and why it is not stricter:
293
- *
294
- * - **A missing `Origin` is allowed.** Non-browser clients do not send it: `curl`, a Node-side `fetch`, and
295
- * the DSH desktop shell's own calls. Browsers always send `Origin` on a `POST` (including a cross-site
296
- * `no-cors` form post), which is the case this fence exists for, so "absent" cannot be produced by a
297
- * cross-site page.
298
- * - **`Origin: null` is refused** (sandboxed iframe, `file:` document).
299
- * - **`Origin` must match `Host`.** A different port or hostname is a different origin.
300
- * - **A port-less loopback spelling is accepted** when the `Origin` carried no port at all: some Chromium
301
- * builds serialise a loopback page as `http://127.0.0.1` even when the page is on a non-default port
302
- * (observed in this workspace by the `dsh-remote-ssh` plugin, which fences the same way). A *different*
303
- * explicit port still fails, because that path compares full `host`.
304
- * - **`Sec-Fetch-Site: cross-site` is refused** even if the two headers were somehow consistent.
305
- *
306
- * The host allowlist itself (loopback / trusted hosts) is **not** re-implemented here: the carrier already
307
- * decides what it serves. See the audit's unverified list.
308
- *
309
- * @param req - incoming request.
310
- * @returns `'ok'`, or the reason to refuse.
311
- */
312
- export declare function trustVerdict(req: RootsApiRequest): TrustVerdict;
313
- /** User-facing text for one {@link TrustVerdict}. */
314
- export declare function trustMessage(verdict: TrustVerdict): string;
315
- /**
316
- * Build the handler pair.
317
- *
318
- * @param deps - the injected seams (live registry, persisted store, connection writer).
319
- * @returns `handle` plus a no-op `dispose`.
320
- */
321
- export declare function createRootsApi(deps: RootsApiDeps): RootsApi;
322
- /** One root as the panel may see it: no credential can appear in a root, but the shape is still fixed. */
323
- export interface PublicRoot {
324
- readonly key?: string;
325
- readonly location: string;
326
- readonly connectionId?: string;
327
- readonly path: string;
328
- readonly label?: string;
329
- readonly writable?: boolean;
330
- }
331
- /**
332
- * One connection as the panel may see it. **Deliberately without `keyPath`.**
333
- *
334
- * `transport` **is** projected, and it has to be: the edit form is filled from this answer and submits what it
335
- * was filled with, so omitting the field silently rewrote every `alias`/`ssh2` connection to the default
336
- * (`agent`) on the next save - even when the operator only changed the host.
337
- */
338
- export interface PublicConnection {
339
- readonly id: string;
340
- readonly host: string;
341
- readonly user: string;
342
- readonly port?: number;
343
- readonly authKind: string;
344
- /** The transport this connection asks for; absent only for a record that names none. */
345
- readonly transport?: string;
346
- }
347
- /**
348
- * The absolute chain from the filesystem root down to `absolute`, inclusive.
349
- *
350
- * The client renders "up" and "home" from this instead of composing a parent path itself: drive letters, UNC
351
- * shares and `/` are exactly the dialects a web client gets wrong, and one wrong guess sends `list-local` to a
352
- * directory the user never asked for. Every element is an ancestor of the path the answer already carries, so
353
- * the field discloses nothing the `path` field did not.
354
- *
355
- * @param absolute - an absolute, resolved path (what the handler answers with).
356
- * @returns the chain, root first; empty when the platform reports no root (impossible for a resolved path).
357
- */
358
- export declare function localPathCrumbs(absolute: string): readonly string[];
359
- /**
360
- * Importable `~/.ssh/config` aliases, and what the flow refuses.
361
- *
362
- * The refusal list is the point, not a nicety: a wildcard `Host *` or an alias already imported is skipped
363
- * **with its reason and line number**, so the dialog can say why a host the user can see in the file is not on
364
- * the list. Silently importing a subset is how a user concludes the feature is broken.
365
- */
366
- export interface SshConfigCandidates {
367
- /** Aliases the user may import, already filtered. */
368
- readonly candidates: readonly SshConfigCandidate[];
369
- /** Patterns that were refused, each with its reason. */
370
- readonly skipped: readonly {
371
- readonly pattern: string;
372
- readonly reason: string;
373
- readonly line: number;
374
- }[];
375
- /** Absolute path the text was read from, when the wiring knows it. */
376
- readonly path?: string;
377
- }
378
- /** One importable alias, projected to what the dialog renders. */
379
- export interface SshConfigCandidate {
380
- /** The literal alias; also the `ssh` target. */
381
- readonly alias: string;
382
- /** Resolved host name (falls back to the alias). */
383
- readonly host: string;
384
- /** Login user when the block named one. */
385
- readonly user?: string;
386
- /** Port when the block named a numeric one. */
387
- readonly port?: number;
388
- /** Whether the block named an identity file (the path itself is not sent: see the route's invariant 1). */
389
- readonly hasIdentityFile: boolean;
390
- /** Whether the block named a ProxyJump. */
391
- readonly hasProxyJump: boolean;
392
- /** The id this import would create, so the dialog can show and edit it before committing. */
393
- readonly suggestedId: string;
394
- }
395
- /**
396
- * Turn an alias into a connection id that passes {@link isValidConnectionId}.
397
- *
398
- * `~/.ssh/config` aliases routinely contain characters this plugin's ids reject (`@`, `:`, `/`, spaces) —
399
- * `deploy@bastion` is a perfectly normal alias. Importing one verbatim would be refused by
400
- * {@link normalizeConnectionEntry} with a message about id grammar, which reads as "the import is broken".
401
- * The alias is therefore **kept as the ssh target** (`alias`), and only the *id* is sanitised.
402
- *
403
- * @param alias - the literal alias from the config.
404
- * @returns a valid id, or `undefined` when sanitising leaves nothing usable.
405
- */
406
- export declare function connectionIdFromAlias(alias: string): string | undefined;
407
- //# sourceMappingURL=roots-api.d.ts.map
@@ -1,211 +0,0 @@
1
- /**
2
- * Wire the panel's HTTP route to the live roots, the live connections and the persisted panel state.
3
- *
4
- * This module is the seam the API handler and the registry never see each other through: the handler is pure
5
- * request/response (see {@link createRootsApi}), the registry owns live routing, and this file is the only
6
- * place that knows both. It exists as its own module so the wiring is one small, reviewable call in the entry
7
- * rather than twenty lines inside it.
8
- *
9
- * Two behaviours worth stating:
10
- *
11
- * - **Startup merge**: roots and connections the panel wrote survive a restart because they are read back
12
- * from `user-roots.json` and applied to the live side at load. A file that cannot be applied is reported as
13
- * a warning, never as a load failure: losing the panel's extra roots must not stop the plugin.
14
- * - **Same-origin only**: the route is registered on the browser carrier (`ctx.webServer`). When that service
15
- * is absent (a headless profile), the plugin still works and simply has no panel data channel — the reason
16
- * is logged instead of thrown.
17
- *
18
- * @module dsh-remote-workspace/host/integration/roots-wiring
19
- */
20
- import { type CredentialsSeam, type RemoteFileOperations, type TransferReport, type RootsApi } from '../app/roots-api.ts';
21
- import { type UserConnectionRecord, type UserRootEntry, type UserRootsIo, type UserRootsStore } from '../app/user-roots.ts';
22
- /** The browser HTTP carrier the host exposes; only the one method this wiring needs. */
23
- export interface RootsRouteHost {
24
- register(route: {
25
- readonly kind: 'exact';
26
- readonly path: string;
27
- readonly handler: (req: never, res: never) => Promise<void>;
28
- }): () => void;
29
- }
30
- /**
31
- * Deferred carrier: run `callback` once `webServer` is **available**, and again whenever it comes back.
32
- *
33
- * `ctx.inject` is the platform's documented way to depend on a service that a later composition layer
34
- * provides (`dsh-client-connection` does exactly this for the `/api` route). A bare `ctx.get('webServer')`
35
- * at activation time is **not** equivalent: measured on the packaged 0.2.0-rc.2 runtime
36
- * (`dsh --profile web`, `ctx.get('webServer') === undefined`, `'webServer' in ctx === false`) while
37
- * `ctx.get('tools')` was already an object — the carrier row is constructed after the plugin entries
38
- * activate. With `ctx.get` the route was never registered, the shell's `dsh-app://app/api/…` proxy fell
39
- * through to the SPA fallback, and the panel reported `http-404` while the plugin looked healthy.
40
- *
41
- * `scoped.get` is typed `unknown` and narrowed by {@link asRootsRouteHost} rather than declared as the
42
- * carrier type: this module stays clear of `@deepseek-ai/*` types (红线 2), and a cordis `Context` is
43
- * structurally richer than this one-method seam, so the narrower declaration would reject the real `ctx`.
44
- */
45
- export interface RootsRouteHostProvider {
46
- inject(names: readonly string[], callback: (scoped: {
47
- get(name: string): unknown;
48
- }) => void | (() => void)): unknown;
49
- }
50
- /** Why a route could not be registered; both variants are warnings, never activation failures. */
51
- export type RootsRouteWarning =
52
- /** No `webServer` service ever appeared (a headless composition): the panel has no data channel. */
53
- {
54
- readonly kind: 'no-carrier';
55
- }
56
- /** The carrier refused the registration, e.g. a duplicate `(kind, path)` pair. */
57
- | {
58
- readonly kind: 'refused';
59
- readonly reason: string;
60
- };
61
- /** Everything the wiring needs from the entry. */
62
- export interface RootsWiringDeps {
63
- /** Plugin state directory; `user-roots.json` lives inside it. */
64
- readonly stateDir: string;
65
- /** Live roots (the registry's records). */
66
- readonly listRoots: () => readonly unknown[];
67
- /** Live connections (the entry's profiles). */
68
- readonly listConnections: () => readonly unknown[];
69
- /** Make a root live and routable. */
70
- /**
71
- * Make a root live. **Awaitable**: registering a remote root also creates its local anchor directory, and the
72
- * panel hands that path straight to the platform's `createWorkspace`, which resolves it with `realpath`.
73
- */
74
- readonly registerRoot: (entry: UserRootEntry) => Promise<void> | void;
75
- /** Drop a root from the live side. */
76
- readonly unregisterRoot: (key: string) => void;
77
- /** Make a connection usable without a restart. */
78
- readonly applyConnection: (entry: UserConnectionRecord, previousId: string) => void;
79
- /**
80
- * Forget a deleted connection on the live side: its profile, and everything cached per connection.
81
- *
82
- * Required, not optional: the `GET` answer lists connections from the live side, so a wiring that omitted this
83
- * would keep answering with a connection the user deleted (see `RootsApiDeps.removeConnection`). Making it
84
- * required means the compiler — not a reviewer — notices a wiring that forgot it.
85
- */
86
- readonly removeConnection: (id: string) => void;
87
- /**
88
- * Optional: list one directory on a connection (names and kinds only), for the `list-remote` action.
89
- *
90
- * Passed through to the handler untouched: this wiring knows nothing about transports, and a composition
91
- * without it simply answers that action with an explicit failure.
92
- */
93
- readonly listRemoteDirectory?: ((input: {
94
- readonly connectionId: string;
95
- readonly path: string;
96
- }) => Promise<readonly unknown[]> | readonly unknown[]) | undefined;
97
- /**
98
- * Optional: the directory a remote browse starts at when the client names no path.
99
- *
100
- * The same pass-through as {@link RootsWiringDeps.listRemoteDirectory}, and the same reason to have it: the
101
- * start directory is the remote login user's home, which only the transport can answer (an account may live
102
- * under `/home`, `/home1`, …). A composition without it degrades to `/`, the pre-existing behaviour.
103
- */
104
- readonly remoteHomeDirectory?: ((connectionId: string) => Promise<string> | string) | undefined;
105
- /**
106
- * Optional: list one directory **on this machine** (names and kinds only), for the `list-local` action.
107
- *
108
- * The same pass-through as {@link RootsWiringDeps.listRemoteDirectory}, and the same reason to have it: a
109
- * profile whose platform directory picker offers only the native dialog (no `browse` capability) still gets
110
- * a working browser from the host, which always has `node:fs`. A composition that does not provide it
111
- * answers that action with an explicit failure rather than pretending the directory is empty.
112
- */
113
- readonly listLocalDirectory?: ((input: {
114
- readonly path: string;
115
- }) => Promise<readonly unknown[]> | readonly unknown[]) | undefined;
116
- /**
117
- * Optional: the text of the user's own `~/.ssh/config`, for the import flow (FR-2.1.4).
118
- *
119
- * The same pass-through as the directory seams, and the same reason: this wiring reads no files, and the
120
- * handler wants **text** so the parsing stays a pure function under test. Absent means the import actions
121
- * answer an explicit failure.
122
- */
123
- readonly readSshConfig?: (() => Promise<string | undefined> | string | undefined) | undefined;
124
- /**
125
- * Optional: the mutating remote-file operations the file tree needs (FR-2.2.2).
126
- *
127
- * The same pass-through as the other seams: this wiring resolves nothing itself. Absent means those actions
128
- * answer an explicit refusal.
129
- */
130
- readonly remoteFiles?: RemoteFileOperations | undefined;
131
- /**
132
- * Optional: the in-flight and recent transfers (FR-2.6.4).
133
- *
134
- * The same pass-through as the other seams. Absent means the transfer actions answer an explicit refusal.
135
- */
136
- readonly transfers?: TransferReport | undefined;
137
- /**
138
- * Optional: the credential store, read **per request** (a getter, not a value — see `RootsApiDeps`).
139
- *
140
- * Absent in a composition with no credentials service: password connections are then refused with an
141
- * explicit 400 instead of being written into a file that is documented to hold no secrets.
142
- */
143
- readonly readCredentials?: (() => CredentialsSeam | undefined) | undefined;
144
- /** Optional: really connect to one saved connection (the panel's 「测试」 button). */
145
- readonly testConnection?: ((id: string) => Promise<unknown> | unknown) | undefined;
146
- /** Optional: the deferred `ctx.webServer` carrier. Absent in a headless profile. */
147
- readonly webServerProvider?: RootsRouteHostProvider | undefined;
148
- /** Optional file-system binding for the store; tests inject an in-memory one (红线 3). */
149
- readonly io?: UserRootsIo | undefined;
150
- }
151
- /** What the entry holds on to. */
152
- export interface RootsWiring {
153
- readonly api: RootsApi;
154
- readonly store: UserRootsStore;
155
- /** Apply everything the panel persisted, so UI edits survive a restart. */
156
- mergeUserRoots(): Promise<{
157
- readonly roots: number;
158
- readonly connections: number;
159
- readonly warnings: readonly string[];
160
- }>;
161
- /**
162
- * Human-readable warnings collected while trying to own the route: an absent carrier, or one that
163
- * refused the registration. Exposed so the entry can log them **after** activation, when `ctx.logger`
164
- * itself resolves (it is undefined at activation time, the same ordering fact this file works around).
165
- */
166
- readonly routeWarnings: () => readonly string[];
167
- /**
168
- * The route's own facts, for the entry's one-line diagnostic.
169
- *
170
- * `injectRan` and `routeRegistered` are the two facts nothing else can answer: the first says whether the
171
- * deferred carrier callback ever executed (a callback that never ran means `webServer` never appeared),
172
- * the second whether the carrier actually accepted the route. Both are read **after** activation, on the
173
- * next tick, because at activation time neither can be true yet on the packaged runtime.
174
- */
175
- readonly routeFacts: () => RootsRouteFacts;
176
- readonly dispose: () => void;
177
- }
178
- /** What the wiring knows about its own route attempt; the rest of the diagnostic comes from the entry. */
179
- export interface RootsRouteFacts {
180
- /** True once the deferred carrier callback ran at least once. */
181
- readonly injectRan: boolean;
182
- /** True when the route is currently registered on a carrier. */
183
- readonly routeRegistered: boolean;
184
- /** The warnings collected so far, in order. */
185
- readonly warnings: readonly string[];
186
- }
187
- /**
188
- * The one-line diagnostic the entry logs when the route is still missing after the next tick.
189
- *
190
- * **Why this exists**: the panel's failure mode is `http-404`, and a 404 alone cannot tell "the carrier
191
- * refused the route" from "the carrier never appeared" from "the plugin never asked". Each of those has a
192
- * different fix, so the line carries the four facts that separate them - whether the `ctx.inject`
193
- * callback ran, whether `'webServer' in ctx` is true on the entry's own context, what `ctx.get('tools')`
194
- * resolved to (a non-undefined service proves the context itself is live), and the warnings registered so
195
- * far - with the warnings inline, so a single log line is enough to act on.
196
- *
197
- * @param facts - the wiring's own facts plus the two the entry samples from its context.
198
- * @returns one structured line, safe for any logger.
199
- */
200
- export declare function rootsRouteDiagnostic(facts: RootsRouteFacts & {
201
- readonly hasWebServerProperty: boolean;
202
- readonly toolsType: string;
203
- }): string;
204
- /**
205
- * Build the wiring and register the route when a carrier exists.
206
- *
207
- * @param deps - live roots, live connections and the persist location.
208
- * @returns the wiring; call {@link RootsWiring.dispose} on unload to release the route.
209
- */
210
- export declare function createRootsWiring(deps: RootsWiringDeps): RootsWiring;
211
- //# sourceMappingURL=roots-wiring.d.ts.map