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
@@ -27,6 +27,7 @@ import type { HostKeyPolicy } from '../../../domain/roots/connection.ts';
27
27
  import { SshDevError } from '../../../domain/errors.ts';
28
28
  import { type ExecRequest, type ExecResult, type ReadFileRangeRequest, type ReadFileRequest, type RemoteFileChunk, type RemoteWriteOutcome, type RemoveRequest, type RenameRequest, type Transport, type WriteFileRangeRequest, type WriteFileRequest } from '../transport.ts';
29
29
  import { TransportBase } from '../transport-base.ts';
30
+ import type { ForwardHandle, ForwardRequest } from '../forward.ts';
30
31
  /** Authentication material for one hop. */
31
32
  export interface Ssh2Auth {
32
33
  readonly password?: string;
@@ -227,6 +228,10 @@ export declare class Ssh2Transport extends TransportBase implements Transport {
227
228
  */
228
229
  connect(signal?: AbortSignal): Promise<void>;
229
230
  /** Close the session. Idempotent. */
231
+ /** {@inheritDoc Transport.forward} */
232
+ forward(request: ForwardRequest, signal?: AbortSignal): Promise<ForwardHandle>;
233
+ /** {@inheritDoc Transport.closeForwards} */
234
+ closeForwards(): Promise<void>;
230
235
  dispose(): Promise<void>;
231
236
  /** Run one remote command over a channel of the shared session. */
232
237
  exec(request: ExecRequest, signal?: AbortSignal): Promise<ExecResult>;
@@ -325,4 +330,3 @@ export declare function sftpError(operation: string, path: string, error?: Error
325
330
  * session.
326
331
  */
327
332
  export declare const defaultConnector: Ssh2Connector;
328
- //# sourceMappingURL=ssh2-transport.d.ts.map
@@ -31,4 +31,3 @@ export declare abstract class TransportBase {
31
31
  /** Forget the probe (a reconnect must not keep a stale timestamp). */
32
32
  protected clearProbed(): void;
33
33
  }
34
- //# sourceMappingURL=transport-base.d.ts.map
@@ -7,6 +7,7 @@
7
7
  *
8
8
  * @module dsh-over-ssh/infra/transport/transport
9
9
  */
10
+ import type { ForwardHandle, ForwardRequest } from './forward.ts';
10
11
  import type { RemoteCapabilities } from '../../domain/capability.ts';
11
12
  /** Lifecycle of one transport instance. */
12
13
  export type TransportState = 'idle' | 'connecting' | 'ready' | 'lost';
@@ -35,6 +36,15 @@ export interface ExecRequest {
35
36
  * *encoded* payload into the file, i.e. silent corruption).
36
37
  */
37
38
  readonly stdinBase64?: string;
39
+ /**
40
+ * Landlock write fence: when non-empty, the command may only write under these roots.
41
+ *
42
+ * **Tier 2 only.** The agent builds the fence before `exec` and refuses with `REMOTE_UNSUPPORTED` when it
43
+ * cannot (no Landlock, missing root); a Tier 1 transport has no fence at all and must refuse the field
44
+ * rather than silently run unfenced — "the fence was requested but skipped" is a security lie, not a
45
+ * degradation. Surfaced through `limits.execWriteFence`.
46
+ */
47
+ readonly writeRoots?: readonly string[];
38
48
  }
39
49
  /** One finished remote command. */
40
50
  export interface ExecResult {
@@ -458,6 +468,22 @@ export interface Transport {
458
468
  * hunk matching rules, which is where a patch applier goes wrong.
459
469
  */
460
470
  patchFile?(request: PatchFileRequest, signal?: AbortSignal): Promise<PatchFileResult>;
471
+ /**
472
+ * 建立一条本地 → 远端的 TCP 转发(端口转发)。
473
+ *
474
+ * 可选:**系统 ssh 的两条路径(Tier 1 与默认 Tier 2)都实现了它**,`ssh2` 传输没有——那一条会诚实
475
+ * 地不声明这个方法,调用方据此报"该连接不支持端口转发",而不是给一个连不上的端口。
476
+ *
477
+ * 只绑 `127.0.0.1`:转发出来的端口是给本机浏览器/客户端用的,绑 `0.0.0.0` 等于把一个远端服务暴露给
478
+ * 整个局域网。要给别人用请走 SSH 自己的 `GatewayPorts` 配置,而不是让插件默认放宽。
479
+ *
480
+ * @param request - 远端目标与可选本地端口。
481
+ * @param signal - 取消建立过程(已建立的转发不会因此关闭)。
482
+ * @returns 句柄,`localPort` 一定是真实端口(自动选择时也回填)。
483
+ */
484
+ forward?(request: ForwardRequest, signal?: AbortSignal): Promise<ForwardHandle>;
485
+ /** 关掉这条传输上的所有转发(dispose 时调用);幂等。 */
486
+ closeForwards?(): Promise<void>;
461
487
  /** Native delete (`fs.remove`); absent on Tier 1, which uses its shell primitive instead. */
462
488
  removePath?(request: RemovePathRequest, signal?: AbortSignal): Promise<void>;
463
489
  /** Native rename/move (`fs.rename`); absent on Tier 1, which uses its shell primitive. */
@@ -522,6 +548,20 @@ export interface Transport {
522
548
  }
523
549
  /** Encode bytes as base64 without line wrapping. */
524
550
  export declare function encodeBase64(bytes: Uint8Array): string;
525
- /** Decode base64, ignoring line breaks produced by remote tooling. */
551
+ /**
552
+ * Decode base64, ignoring line breaks produced by remote tooling.
553
+ *
554
+ * **两次多余的整串处理被去掉了**(审计 0112 P3-2):
555
+ *
556
+ * 1. `text.replace(/\s+/g, '')` 先把整串扫一遍去掉空白。Node 的 base64 解码**本来就忽略空白**
557
+ * (`\n` / `\r\n` / 空格 / 制表符都已实测逐字节等价,见本函数下方的测试),所以这一遍纯属浪费:
558
+ * 9 MiB 负载(12.7 MB base64)实测 17.3 ms → 7.1 ms。
559
+ * 2. `new Uint8Array(...)` 把解码结果再拷一遍。4 KiB 以上的解码本来就不走 Buffer 的共享池,
560
+ * `Buffer.from(str, 'base64')` 的返回对象已经是独占、正好大小的 `Uint8Array`,直接交出去即可
561
+ * (9 MiB 的一次拷贝实测 1.4 ms,而它正是这条读路径上最大的一块)。
562
+ *
563
+ * **小结果仍然拷一次,这是有意的**:`Buffer.from(string)` 在 4 KiB 以下会落进 Node 的 64 KiB 共享池,
564
+ * 原样返回会让一个几十字节的缓存值长期钉住 64 KiB(读缓存按 `byteLength` 记账,看不见这块)。
565
+ * 那次拷贝是几十字节,与热路径无关。
566
+ */
526
567
  export declare function decodeBase64(text: string): Uint8Array;
527
- //# sourceMappingURL=transport.d.ts.map
@@ -53,4 +53,3 @@ export interface AgentPreflight {
53
53
  * @returns the preflight, an installation lookup, and a way to forget a connection.
54
54
  */
55
55
  export declare function createAgentPreflight(options: AgentPreflightOptions): AgentPreflight;
56
- //# sourceMappingURL=agent-preflight.d.ts.map
@@ -139,4 +139,3 @@ export declare class RemoteFileReferenceService extends FileReferenceService {
139
139
  list(agent: Agent, query: string, signal: AbortSignal): Promise<FileReferenceCandidate[]>;
140
140
  }
141
141
  export default RemoteFileReferenceService;
142
- //# sourceMappingURL=file-reference-provider.d.ts.map
@@ -101,7 +101,6 @@ export interface RoutedFsProviderOptions {
101
101
  * model's file tools follow the router (see `cordis.patch.yml`).
102
102
  */
103
103
  export declare class RoutedFsProvider extends FileSystem {
104
- #private;
105
104
  static inject: string[];
106
105
  protected readonly rangeReader: BackendRangeReader | undefined;
107
106
  protected readonly rangeChunkBytes: number;
@@ -285,7 +284,24 @@ export declare class RoutedFsProvider extends FileSystem {
285
284
  */
286
285
  private syncSessionRoots;
287
286
  private routedTarget;
288
- /** Enforce the per-call policy and the root's own writability. */
287
+ /**
288
+ * Enforce the per-call policy and the root's own writability.
289
+ *
290
+ * Three gates, in order:
291
+ * 1. `read-only` denies every mutation (the platform's own contract);
292
+ * 2. a root registered `writable: false` denies;
293
+ * 3. under **`workspace-write`**, the target must be inside the session's workspace — where "workspace"
294
+ * includes the directories **this session added** (product decision 2026-11-04: 「添加到会话的目录」=
295
+ * 该目录临时成为 workspace 的一部分). Before that decision the plugin admitted every writable root it
296
+ * could route, which made the mode wider than the platform's own `fs-sandbox` fence
297
+ * (`workspace-write` = "canonicalizes under `workspaceRoot` or the platform temp area").
298
+ *
299
+ * A call without a per-call policy keeps the historical behaviour (the mode comes from
300
+ * {@link sandboxMode}); the area rule needs `workspaceRoot`, which only the policy carries.
301
+ *
302
+ * @param routed - the resolved target.
303
+ * @param policy - the per-call sandbox policy the tool layer resolved.
304
+ */
289
305
  private assertWritable;
290
306
  /**
291
307
  * Previous text content, when it is readable text inside the cap; `undefined` otherwise.
@@ -365,4 +381,3 @@ export declare function createBackendRangeReader(store: (backend: Backend, offse
365
381
  export declare function toFsError(error: SshDevError): FsError;
366
382
  /** Codes this provider can surface, exported for tests that assert the mapping table. */
367
383
  export declare const MAPPED_DOMAIN_CODES: readonly SshDevErrorCode[];
368
- //# sourceMappingURL=fs-provider.d.ts.map
@@ -209,4 +209,3 @@ export declare const RESUME_VERIFY_BYTES: number;
209
209
  * @returns the upload/download operations; no state is kept between calls.
210
210
  */
211
211
  export declare function createTransferService(dependencies: TransferDependencies): TransferService;
212
- //# sourceMappingURL=transfer-service.d.ts.map
@@ -1,3 +1,18 @@
1
+ /**
2
+ * The host plugin entry: configuration, services and the object graph behind this plugin.
3
+ *
4
+ * It provides two services and nothing else, deliberately:
5
+ *
6
+ * - `sshDevConnections` — the validated connection profiles (secrets referenced by credential key).
7
+ * - `sshDevRoots` — the {@link RootRegistry} the `ctx.fs` provider (`dsh-over-ssh/fs`) injects.
8
+ *
9
+ * Transports are constructed lazily and owned by a {@link TransportService}, so a client half (or a
10
+ * root added at runtime) simply calls into the registry and everything else follows. Nothing here
11
+ * reads a secret eagerly: passwords live in `ctx.credentials` and are resolved when a connection is
12
+ * actually opened.
13
+ *
14
+ * @module dsh-over-ssh
15
+ */
1
16
  import { Context } from '@deepseek-ai/cordis';
2
17
  import { TransportService } from './host/app/transport/transport-service.ts';
3
18
  import { Config } from './config.ts';
@@ -54,6 +69,14 @@ declare module '@deepseek-ai/cordis' {
54
69
  sshDevSessionAdmin: SessionAdminService;
55
70
  }
56
71
  }
72
+ /**
73
+ * Read a session log's header line.
74
+ *
75
+ * The bounded reader lives in {@link createDiskSessionHeaderReader} rather than here: a composition root
76
+ * cannot be exercised by a test (审计 0046 记录了同款教训), and a reader whose *memory bound* is its whole
77
+ * contract must have one. The previous inline version read the entire log before slicing 256 KiB off it —
78
+ * measured at 105.3 MiB of reads for this machine's 73 sessions, worst single log 10.9 MiB (审计 0050).
79
+ */
57
80
  /**
58
81
  * Install the plugin.
59
82
  *
@@ -63,4 +86,3 @@ declare module '@deepseek-ai/cordis' {
63
86
  */
64
87
  export { Config };
65
88
  export declare function apply(ctx: Context, config: ResolvedConfig): void;
66
- //# sourceMappingURL=index.d.ts.map
@@ -23,4 +23,3 @@ export default class LocalFileReferencesBridge {
23
23
  static inject: readonly ["fileReferences"];
24
24
  constructor(ctx: Context);
25
25
  }
26
- //# sourceMappingURL=local-bridge-refs.d.ts.map
@@ -33,4 +33,3 @@ export default class LocalFsBridge {
33
33
  static inject: readonly ["fs"];
34
34
  constructor(ctx: Context);
35
35
  }
36
- //# sourceMappingURL=local-bridge.d.ts.map
@@ -0,0 +1,120 @@
1
+ /**
2
+ * 根路径与根键的**唯一拼写规则**:client 与 host 两侧都 import 这一份。
3
+ *
4
+ * 为什么在 `shared/`:这三条规则此前各有两份实现,而每一对都已经分叉过一次(审计 0112 P2-11):
5
+ *
6
+ * - **根键**:客户端拼 `local:<原样路径>`,宿主拼 `local:<规范化路径>`(Windows 上还折叠大小写)。
7
+ * 0024 §8.3 的"删除按钮发出宿主匹配不上的键"就是这个分叉的直接后果——面板把那行显示出来,
8
+ * 点删除却什么也没发生,而两边各自的用例都是绿的;
9
+ * - **绝对本地路径**:宿主不认 `\\server\share`(它先规范化再判断,所以只认 `//server/share`),
10
+ * 客户端认。同一个 UNC 路径在两半得到相反的答案;
11
+ * - **远端父目录**:客户端自己 `lastIndexOf('/')`,宿主的 `remoteDirname` 先规范化(`/a//b/` 与
12
+ * `/a/b` 因此是不同的答案)。
13
+ *
14
+ * `shared/` 是两侧唯一都能 import 的位置:`tests/architecture.test.ts` 规定 client 只能依赖
15
+ * `client` 与 `shared`,而 `shared` 只能依赖自己。这也是 `routes.ts` / `format.ts` 住在这里的理由。
16
+ *
17
+ * 本模块是**纯函数**,且**不读 `process`**:它会被打进浏览器 bundle(`platform: 'browser'`),
18
+ * 那里没有 `process`。平台相关的决定由调用方作为参数传进来——见 {@link rootKeySpelling} 的
19
+ * `caseInsensitive`:宿主的 `domain/roots/root.ts` 传 `process.platform === 'win32'`,客户端不猜。
20
+ *
21
+ * 三个函数都是**全函数**(不抛错):宿主那一侧的错误策略(`SshDevError('ROOT_INVALID')`)留在
22
+ * `domain/` 里,因为客户端在 reducer 里抛错等于把界面炸掉。所以这里能共享的是**规则**,不是
23
+ * "遇到坏输入怎么办"。
24
+ *
25
+ * @module dsh-over-ssh/shared/root-keys
26
+ */
27
+ /** Where a root lives: on this machine, or behind a connection. */
28
+ export type RootLocation = 'local' | 'remote';
29
+ /**
30
+ * One option bag for every spelling rule here.
31
+ *
32
+ * Only `caseInsensitive` exists today, and only for **local** paths: a Windows host folds case in the
33
+ * registry, a POSIX host does not, and a browser cannot know which. So the decision travels as an argument
34
+ * instead of being read from `process` inside a function that both halves call.
35
+ */
36
+ export interface RootKeyOptions {
37
+ /** Fold case in a local path/key. Defaults to `false` — "keep the caller's case". */
38
+ readonly caseInsensitive?: boolean;
39
+ }
40
+ /**
41
+ * Whether a host path is absolute in either the Windows or the POSIX spelling.
42
+ *
43
+ * Both UNC spellings are accepted: `\\server\share` (what a Windows operator types) and `//server/share`
44
+ * (what {@link normalizeLocalPath} produces). The host used to accept only the second, the client only the
45
+ * first — so the panel and `makeLocalRoot` disagreed about the same string.
46
+ *
47
+ * @param path - candidate host path.
48
+ * @returns whether it names an absolute host path.
49
+ */
50
+ export declare function isAbsoluteLocalPath(path: string): boolean;
51
+ /**
52
+ * Normalize a host path for comparison: separators to `/`, `.`/`..` resolved, trailing separators dropped,
53
+ * and (optionally) the drive letter and case folded.
54
+ *
55
+ * Total: a relative spelling comes back as a relative spelling (`src` → `src`). The caller decides whether
56
+ * that is an error (the host's `makeLocalRoot`/`validateRoot` check {@link isAbsoluteLocalPath} after this).
57
+ *
58
+ * @param path - host path in either separator spelling.
59
+ * @param options - `caseInsensitive` folds case for comparison keys.
60
+ * @returns the normalized path.
61
+ */
62
+ export declare function normalizeLocalPath(path: string, options?: RootKeyOptions): string;
63
+ /**
64
+ * Normalize an absolute POSIX path: repeated separators collapsed, `.`/`..` resolved, trailing separators
65
+ * dropped (except for the root).
66
+ *
67
+ * Total: `undefined` means "not an absolute POSIX spelling". The host's `normalizeRemotePath` is the same
68
+ * rule plus the `SshDevError('ROOT_INVALID')` policy on top.
69
+ *
70
+ * @param path - candidate absolute POSIX path.
71
+ * @returns the normalized path, or `undefined` when the spelling is not absolute.
72
+ */
73
+ export declare function normalizePosixPath(path: string): string | undefined;
74
+ /**
75
+ * The parent directory of a **normalized** absolute POSIX path; the root's parent is `/`.
76
+ *
77
+ * One rule, two policies on top: the host's `remoteDirname` answers `/` for the root (a root's parent is
78
+ * itself, which is what its callers assume), while the directory browser's `parentRemotePath` answers
79
+ * `undefined` there (the "up" button must be disabled). The separator arithmetic is not repeated.
80
+ *
81
+ * @param normalized - an absolute POSIX path already passed through {@link normalizePosixPath}.
82
+ * @returns the parent directory.
83
+ */
84
+ export declare function posixDirname(normalized: string): string;
85
+ /**
86
+ * The key one root answers to, spelled the way **both halves** must spell it:
87
+ * `local:<normalized local path>` or `remote:<connectionId>:<normalized POSIX path>`.
88
+ *
89
+ * This is the string a client sends back to remove a root and the string the registry stores, so the two
90
+ * have to be the same function — the earlier client spelling (`local:-:<path>`, then `local:<raw path>`)
91
+ * produced keys the host could not match (audit 0024 §8.3, §8.5).
92
+ *
93
+ * Case is **not** folded here unless the caller asks: the host passes `{ caseInsensitive: process.platform
94
+ * === 'win32' }` (that is where its registry folds), and the client passes nothing because it cannot know
95
+ * the host's platform. Case-differing keys are therefore matched by {@link rootKeyIdentity} (the client's
96
+ * join) and by the host's own `sameRootKey`/`liveKeyFor` comparison — never by string equality on the key.
97
+ *
98
+ * @param location - local or remote.
99
+ * @param path - host path (either separator spelling) or absolute POSIX path.
100
+ * @param connectionId - the connection for a remote root; absent spells `remote::<path>` (the client's
101
+ * fallback for a malformed answer — the host rejects such a root before it ever gets here).
102
+ * @param options - `caseInsensitive` folds case in a local key.
103
+ * @returns the key.
104
+ */
105
+ export declare function rootKeySpelling(location: RootLocation, path: string, connectionId?: string, options?: RootKeyOptions): string;
106
+ /**
107
+ * The identity two root keys are joined by: `local:` keys compare **case-insensitively**, remote keys do not.
108
+ *
109
+ * Why unconditional for local: the host's registry folds a Windows local path to lower case while the panel's
110
+ * own file keeps the caller's case, so "the same root" routinely arrives with two spellings. The host has
111
+ * always compared this way (`sameRootKey` in `app/roots/user-roots.ts`, out of this change's write scope);
112
+ * this is the client's half of the same rule, and without it a live root reads as "not in the file", which
113
+ * silently removes its delete button (audit 0024 §8.5).
114
+ *
115
+ * Remote paths are POSIX and therefore case-sensitive, so their keys are compared verbatim.
116
+ *
117
+ * @param key - one root key (or anything shaped like one).
118
+ * @returns the join identity.
119
+ */
120
+ export declare function rootKeyIdentity(key: string): string;
@@ -141,4 +141,3 @@ export interface CredentialsLike {
141
141
  }
142
142
  /** 会话日志头读取器(原先在 index.ts 底部,被 tools 段的会话管理使用)。 */
143
143
  export declare const readSessionHeader: (dir: string) => Promise<import("../host/app/session-admin/session-admin-contract.ts").SessionHeader | undefined>;
144
- //# sourceMappingURL=helpers.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-over-ssh",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "SSH remote development for DeepSeek Harness: connections (key/password/ProxyJump/ssh config), remote files without sync, remote terminal, remote workspaces, remote @-completion, upload/download.",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -56,12 +56,14 @@
56
56
  "typecheck:client": "tsc -p tsconfig.client.json --pretty false",
57
57
  "build:client": "node tools/build-client.mjs --check",
58
58
  "build:host": "node tools/build-host.mjs",
59
- "build": "npm run build:host && npm run build:terminal-wrapper && npm run build:client && npm run build:types:client",
59
+ "build": "npm run build:host && npm run build:types && npm run build:terminal-wrapper && npm run build:client",
60
60
  "agents": "node tools/agent-manifest.mjs generate --from ../dsh-over-ssh-agent/dist --out agents/agent-manifest.json --copy-to agents --platforms linux-amd64,linux-arm64 --require-provenance",
61
61
  "build:types:client": "node ./node_modules/typescript/bin/tsc -p tsconfig.client.types.json --pretty false",
62
62
  "prepack": "npm run build && node tools/prepack-agents.mjs && node tools/release-note.mjs --check",
63
63
  "build:terminal-wrapper": "node tools/build-terminal-wrapper.mjs",
64
- "release:note": "node tools/release-note.mjs"
64
+ "release:note": "node tools/release-note.mjs",
65
+ "build:types:host": "node ./node_modules/typescript/bin/tsc -p tsconfig.host.types.json",
66
+ "build:types": "node tools/clean-types.mjs && npm run build:types:host && npm run build:types:client && node tools/prune-types.mjs"
65
67
  },
66
68
  "peerDependencies": {
67
69
  "@deepseek-ai/cordis": "^4.0.4",
@@ -1,131 +0,0 @@
1
- /**
2
- * The controls the three surfaces share, as plain `createElement` templates.
3
- *
4
- * **No hooks, on purpose.** `:hover`, `:active`, `:focus-visible` and `:disabled` come from the stylesheet in
5
- * [`styles.ts`](./styles.ts) via class names, because a component holding `useState` cannot be rendered by
6
- * this repository's tests: they call the section functions **directly**, outside any React render, where a
7
- * hook throws `Invalid hook call`. Classes keep the templates pure element trees — a test walks the returned
8
- * tree and reads the same `type` / `name` / `disabled` / `role` it always did — while the browser still gets
9
- * real interaction states.
10
- *
11
- * These are deliberately **not** the platform's `dsh-client-ui-primitives` atoms: those factories import
12
- * per-component CSS modules, which a third-party bundle cannot supply (the loader materialises only
13
- * registered factories), and the package is not a dependency of this repository. `styles.ts` carries the
14
- * tokens those atoms are built from.
15
- *
16
- * @module dsh-remote-workspace/client/controls
17
- */
18
- import { type ChangeEvent, type ReactNode } from 'react';
19
- /** The sizes a control may have. */
20
- export type ControlSize = 'normal' | 'compact';
21
- /** The visual weight of a button. */
22
- export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'row' | 'chip';
23
- /** Props every control shares: extra classes from the caller. */
24
- export interface ControlClassProps {
25
- /** Extra classes from the caller; appended after the vocabulary's own. */
26
- readonly className?: string;
27
- }
28
- /**
29
- * The part of a keyboard event a card's Esc handler reads.
30
- *
31
- * Structural, so the pure templates need no DOM types and a test drives the handler with a plain object. It is
32
- * assignable from React's `KeyboardEvent` (which carries both members), and it is the whole reason a dialog
33
- * closes on Esc: the shell focuses into the card when it opens, so the event reaches the card's own handler.
34
- */
35
- export interface KeyEventLike {
36
- /** The key name, e.g. `'Escape'`. */
37
- readonly key?: string;
38
- /** Stop the platform's own Esc handling from also firing while the dialog is up. */
39
- stopPropagation?(): void;
40
- }
41
- /** True when the event is the Esc key. */
42
- export declare function isEscape(event: KeyEventLike): boolean;
43
- /**
44
- * One button.
45
- *
46
- * @param props - the button's own props plus the variants this plugin uses.
47
- * @returns the button element.
48
- */
49
- export declare function Button(props: ControlClassProps & {
50
- readonly variant: ButtonVariant;
51
- readonly size?: ControlSize;
52
- readonly type?: 'button' | 'submit';
53
- readonly disabled?: boolean;
54
- readonly name?: string;
55
- readonly title?: string;
56
- readonly onClick?: () => void;
57
- readonly children?: ReactNode;
58
- }): ReactNode;
59
- /** Props of a text input, narrowed to what the templates pass. */
60
- export interface TextInputProps extends ControlClassProps {
61
- readonly id: string;
62
- readonly name: string;
63
- readonly value: string;
64
- readonly disabled?: boolean;
65
- readonly placeholder?: string;
66
- readonly inputMode?: 'numeric';
67
- /** `password` for a secret field: the browser masks it and no autofill offers to save it. */
68
- readonly type?: 'text' | 'password';
69
- readonly autoComplete?: 'off' | 'new-password';
70
- readonly size?: ControlSize;
71
- readonly onChange: (event: ChangeEvent<HTMLInputElement>) => void;
72
- }
73
- /**
74
- * One text input, with the platform's border, inset surface and focus ring.
75
- *
76
- * @param props - the input's own props.
77
- * @returns the input element.
78
- */
79
- export declare function TextInput(props: TextInputProps): ReactNode;
80
- /** One option of a {@link Select}. */
81
- export interface SelectOption {
82
- readonly value: string;
83
- readonly label: string;
84
- }
85
- /**
86
- * A `<select>` over a fixed list of options.
87
- *
88
- * @param props - id, name, the current value, the options and the change handler.
89
- * @returns the select element.
90
- */
91
- export declare function Select(props: ControlClassProps & {
92
- readonly id: string;
93
- readonly name: string;
94
- readonly value: string;
95
- readonly options: readonly SelectOption[];
96
- readonly disabled?: boolean;
97
- readonly size?: ControlSize;
98
- readonly onChange: (value: string) => void;
99
- }): ReactNode;
100
- /**
101
- * A labelled field: the caption above its control, so the column and its label move together.
102
- *
103
- * @param props - the label text, the control's id and the control itself.
104
- * @returns the labelled column.
105
- */
106
- export declare function Field(props: {
107
- readonly label: string;
108
- readonly htmlFor: string;
109
- readonly children: ReactNode;
110
- }): ReactNode;
111
- /**
112
- * An inline notice block: the surface for a refusal, a host warning or "no host data".
113
- *
114
- * @param props - `warn` for something the operator may need to act on, `info` for a neutral status.
115
- * @returns the notice element carrying the role its caller needs (`alert` / `note` / `status`).
116
- */
117
- export declare function Notice(props: {
118
- readonly role: 'alert' | 'note' | 'status';
119
- readonly tone?: 'warn' | 'info';
120
- readonly children: ReactNode;
121
- }): ReactNode;
122
- /**
123
- * The status dot of a connection row, so the row's state is not carried by colour alone.
124
- *
125
- * @param props - the dot's accessible description.
126
- * @returns the dot element.
127
- */
128
- export declare function StatusDot(props: {
129
- readonly label: string;
130
- }): ReactNode;
131
- //# sourceMappingURL=controls.d.ts.map
@@ -1,45 +0,0 @@
1
- /**
2
- * The **one** Tab trap every card in this plugin installs.
3
- *
4
- * Five surfaces are dialogs and every one of them has to keep Tab inside itself, for two different reasons:
5
- *
6
- * - the settings-hosted cards sit **outside** the platform settings modal, whose own focus trap listens on
7
- * `document` in the bubble phase and pulls focus back into its panel — a bubble-phase listener of ours would
8
- * run after it (registration order) and could no longer tell which field Tab came from, so the trap has to
9
- * be in the **capture** phase (audit 0090);
10
- * - the overlay and composer cards are simply modals: Tab walking into the page behind them is a dialog the
11
- * keyboard user leaves without dismissing.
12
- *
13
- * Both cases want the same mechanics, so they live here instead of being spelled out per surface (the two that
14
- * were first — the connection editor and the session-panel confirmation — each carried their own copy of the
15
- * same eight lines, and the two newer ones had none at all; audit 2026-10-31 `client.md` §2.3).
16
- *
17
- * The split is the same one the rest of `core/` uses: {@link installCardTabTrap} is pure and takes the document
18
- * as an argument (so an offline test drives it with a fake), and {@link useCardTabTrap} is the two-line React
19
- * binding.
20
- *
21
- * @module dsh-over-ssh/client/card-focus-trap
22
- */
23
- import { type PortalDocument } from './portal.ts';
24
- /**
25
- * Install the card's Tab trap on one document.
26
- *
27
- * Capture phase on purpose: see the module note. The listener is removed with the same `options` value, so a
28
- * runtime that keys listeners by phase cannot leak one.
29
- *
30
- * @param doc - the page's document, or `undefined` off-browser (where this is a no-op).
31
- * @param container - the card's body-level container; `undefined` while it is not attached.
32
- * @returns the remover; safe to call more than once.
33
- */
34
- export declare function installCardTabTrap(doc: PortalDocument | undefined, container: unknown): () => void;
35
- /**
36
- * Trap Tab while a card is open.
37
- *
38
- * `open` is the caller's own condition (`editingId !== undefined`, `pending !== undefined`, `request !==
39
- * undefined`); the container comes from that surface's portal. Nothing is installed while the card is closed, so
40
- * the settings panel keeps its own Tab behaviour the rest of the time.
41
- *
42
- * @param open - whether the card is up.
43
- * @param container - the card's container from its portal.
44
- */
45
- export declare function useCardTabTrap(open: boolean, container: unknown): void;