dsh-over-ssh 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +119 -2
  3. package/agents/agent-manifest.json +20 -0
  4. package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
  5. package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
  6. package/cordis.patch.yml +84 -0
  7. package/lib/client.js +6611 -0
  8. package/lib/file-references.js +366 -0
  9. package/lib/fs.js +1368 -0
  10. package/lib/index.js +19034 -0
  11. package/lib/local-bridge-refs.js +16 -0
  12. package/lib/local-bridge.js +16 -0
  13. package/lib/terminal-wrapper.js +294 -0
  14. package/lib/types/src/client/controls.d.ts +131 -0
  15. package/lib/types/src/client/core/card-focus-trap.d.ts +45 -0
  16. package/lib/types/src/client/core/controls.d.ts +164 -0
  17. package/lib/types/src/client/core/latest.d.ts +48 -0
  18. package/lib/types/src/client/core/locale.d.ts +38 -0
  19. package/lib/types/src/client/core/message.d.ts +37 -0
  20. package/lib/types/src/client/core/panel-model.d.ts +134 -0
  21. package/lib/types/src/client/core/portal.d.ts +266 -0
  22. package/lib/types/src/client/core/remote.d.ts +84 -0
  23. package/lib/types/src/client/core/styles.d.ts +458 -0
  24. package/lib/types/src/client/file-tree-io.d.ts +19 -0
  25. package/lib/types/src/client/file-tree.d.ts +271 -0
  26. package/lib/types/src/client/flows/directory-browser.d.ts +587 -0
  27. package/lib/types/src/client/flows/session-directory.d.ts +415 -0
  28. package/lib/types/src/client/flows/workspace-directory-flow.d.ts +480 -0
  29. package/lib/types/src/client/index.d.ts +49 -0
  30. package/lib/types/src/client/locale.d.ts +31 -0
  31. package/lib/types/src/client/panel-model.d.ts +37 -0
  32. package/lib/types/src/client/panel.d.ts +77 -0
  33. package/lib/types/src/client/roots/connection-test.d.ts +69 -0
  34. package/lib/types/src/client/roots/roots-panel-state.d.ts +307 -0
  35. package/lib/types/src/client/roots/roots-panel-view.d.ts +280 -0
  36. package/lib/types/src/client/roots/roots-panel.d.ts +364 -0
  37. package/lib/types/src/client/roots/transfer-panel-view.d.ts +35 -0
  38. package/lib/types/src/client/roots/transfer-panel.d.ts +241 -0
  39. package/lib/types/src/client/roots/transfer-progress.d.ts +67 -0
  40. package/lib/types/src/client/roots-panel-view.d.ts +236 -0
  41. package/lib/types/src/client/roots-panel.d.ts +347 -0
  42. package/lib/types/src/client/session/session-panel-view.d.ts +336 -0
  43. package/lib/types/src/client/session/session-panel.d.ts +322 -0
  44. package/lib/types/src/client/session/session-remote-mark.d.ts +110 -0
  45. package/lib/types/src/client/session/session-row-actions.d.ts +398 -0
  46. package/lib/types/src/client/session-panel.d.ts +152 -0
  47. package/lib/types/src/client/styles.d.ts +252 -0
  48. package/lib/types/src/client/workspace-directory-flow.d.ts +342 -0
  49. package/lib/types/src/config.d.ts +312 -0
  50. package/lib/types/src/file-references.d.ts +25 -0
  51. package/lib/types/src/fs.d.ts +46 -0
  52. package/lib/types/src/host/app/backend.d.ts +170 -0
  53. package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +247 -0
  54. package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +18 -0
  55. package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +45 -0
  56. package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +38 -0
  57. package/lib/types/src/host/app/recycle-bin-contract.d.ts +215 -0
  58. package/lib/types/src/host/app/recycle-bin-move.d.ts +18 -0
  59. package/lib/types/src/host/app/recycle-bin-service.d.ts +45 -0
  60. package/lib/types/src/host/app/recycle-bin-world.d.ts +35 -0
  61. package/lib/types/src/host/app/roots/roots-api-connections.d.ts +49 -0
  62. package/lib/types/src/host/app/roots/roots-api-contract.d.ts +369 -0
  63. package/lib/types/src/host/app/roots/roots-api-directories.d.ts +13 -0
  64. package/lib/types/src/host/app/roots/roots-api-http.d.ts +77 -0
  65. package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +130 -0
  66. package/lib/types/src/host/app/roots/roots-api-projections.d.ts +102 -0
  67. package/lib/types/src/host/app/roots/roots-api-roots.d.ts +13 -0
  68. package/lib/types/src/host/app/roots/roots-api-state.d.ts +22 -0
  69. package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +26 -0
  70. package/lib/types/src/host/app/roots/roots-api.d.ts +55 -0
  71. package/lib/types/src/host/app/roots/user-roots.d.ts +552 -0
  72. package/lib/types/src/host/app/roots-api-connections.d.ts +49 -0
  73. package/lib/types/src/host/app/roots-api-contract.d.ts +339 -0
  74. package/lib/types/src/host/app/roots-api-directories.d.ts +13 -0
  75. package/lib/types/src/host/app/roots-api-http.d.ts +77 -0
  76. package/lib/types/src/host/app/roots-api-projections.d.ts +102 -0
  77. package/lib/types/src/host/app/roots-api-roots.d.ts +13 -0
  78. package/lib/types/src/host/app/roots-api-state.d.ts +22 -0
  79. package/lib/types/src/host/app/roots-api-support.d.ts +182 -0
  80. package/lib/types/src/host/app/roots-api-transfers.d.ts +13 -0
  81. package/lib/types/src/host/app/roots-api.d.ts +55 -0
  82. package/lib/types/src/host/app/routed-fs.d.ts +239 -0
  83. package/lib/types/src/host/app/serialize.d.ts +37 -0
  84. package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +36 -0
  85. package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +221 -0
  86. package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +34 -0
  87. package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +40 -0
  88. package/lib/types/src/host/app/session-admin-context.d.ts +36 -0
  89. package/lib/types/src/host/app/session-admin-contract.d.ts +214 -0
  90. package/lib/types/src/host/app/session-admin-delete.d.ts +34 -0
  91. package/lib/types/src/host/app/session-admin-service.d.ts +38 -0
  92. package/lib/types/src/host/app/takeover/takeover-plan.d.ts +227 -0
  93. package/lib/types/src/host/app/takeover/takeover-rows.d.ts +55 -0
  94. package/lib/types/src/host/app/transport/transport-selector.d.ts +98 -0
  95. package/lib/types/src/host/app/transport/transport-service.d.ts +82 -0
  96. package/lib/types/src/host/app/transport-selector.d.ts +98 -0
  97. package/lib/types/src/host/app/transport-service.d.ts +75 -0
  98. package/lib/types/src/host/app/user-roots.d.ts +461 -0
  99. package/lib/types/src/host/domain/bin-location.d.ts +79 -0
  100. package/lib/types/src/host/domain/capability.d.ts +78 -0
  101. package/lib/types/src/host/domain/connection.d.ts +95 -0
  102. package/lib/types/src/host/domain/errors.d.ts +55 -0
  103. package/lib/types/src/host/domain/glob-pattern.d.ts +62 -0
  104. package/lib/types/src/host/domain/literal-text.d.ts +63 -0
  105. package/lib/types/src/host/domain/path-grammar.d.ts +84 -0
  106. package/lib/types/src/host/domain/recycle-bin.d.ts +337 -0
  107. package/lib/types/src/host/domain/root.d.ts +74 -0
  108. package/lib/types/src/host/domain/roots/bin-location.d.ts +91 -0
  109. package/lib/types/src/host/domain/roots/connection.d.ts +106 -0
  110. package/lib/types/src/host/domain/roots/root.d.ts +108 -0
  111. package/lib/types/src/host/domain/roots/route-table.d.ts +102 -0
  112. package/lib/types/src/host/domain/roots/session-roots.d.ts +151 -0
  113. package/lib/types/src/host/domain/roots/ssh-config.d.ts +73 -0
  114. package/lib/types/src/host/domain/route-table.d.ts +80 -0
  115. package/lib/types/src/host/domain/session/session-admin.d.ts +345 -0
  116. package/lib/types/src/host/domain/session/terminal-target.d.ts +136 -0
  117. package/lib/types/src/host/domain/session-admin.d.ts +330 -0
  118. package/lib/types/src/host/domain/shell.d.ts +47 -0
  119. package/lib/types/src/host/domain/ssh-config.d.ts +73 -0
  120. package/lib/types/src/host/domain/terminal-target.d.ts +132 -0
  121. package/lib/types/src/host/domain/tool-requests.d.ts +369 -0
  122. package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +337 -0
  123. package/lib/types/src/host/domain/transfer/transfer.d.ts +260 -0
  124. package/lib/types/src/host/domain/transfer.d.ts +260 -0
  125. package/lib/types/src/host/infra/backend/local-backend.d.ts +84 -0
  126. package/lib/types/src/host/infra/backend/local-version.d.ts +43 -0
  127. package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +89 -0
  128. package/lib/types/src/host/infra/backend/remote-backend.d.ts +239 -0
  129. package/lib/types/src/host/infra/cache/lru.d.ts +52 -0
  130. package/lib/types/src/host/infra/cache/versioned.d.ts +77 -0
  131. package/lib/types/src/host/infra/temp-name.d.ts +19 -0
  132. package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +121 -0
  133. package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +132 -0
  134. package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +140 -0
  135. package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +41 -0
  136. package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +197 -0
  137. package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +564 -0
  138. package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +418 -0
  139. package/lib/types/src/host/infra/transport/agent-installer.d.ts +121 -0
  140. package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +123 -0
  141. package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +131 -0
  142. package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +41 -0
  143. package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +197 -0
  144. package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +509 -0
  145. package/lib/types/src/host/infra/transport/agent-transport.d.ts +353 -0
  146. package/lib/types/src/host/infra/transport/alias-transport.d.ts +206 -0
  147. package/lib/types/src/host/infra/transport/askpass-fs.d.ts +20 -0
  148. package/lib/types/src/host/infra/transport/askpass.d.ts +133 -0
  149. package/lib/types/src/host/infra/transport/command-pool.d.ts +61 -0
  150. package/lib/types/src/host/infra/transport/fs-commands.d.ts +140 -0
  151. package/lib/types/src/host/infra/transport/git-status-route.d.ts +63 -0
  152. package/lib/types/src/host/infra/transport/git-status.d.ts +101 -0
  153. package/lib/types/src/host/infra/transport/node-spawn.d.ts +26 -0
  154. package/lib/types/src/host/infra/transport/session-window.d.ts +51 -0
  155. package/lib/types/src/host/infra/transport/shell-quote.d.ts +59 -0
  156. package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +206 -0
  157. package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +20 -0
  158. package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +133 -0
  159. package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +62 -0
  160. package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +43 -0
  161. package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +328 -0
  162. package/lib/types/src/host/infra/transport/ssh-argv.d.ts +43 -0
  163. package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +302 -0
  164. package/lib/types/src/host/infra/transport/transport-base.d.ts +34 -0
  165. package/lib/types/src/host/infra/transport/transport.d.ts +527 -0
  166. package/lib/types/src/host/integration/agent/agent-install-io.d.ts +33 -0
  167. package/lib/types/src/host/integration/agent/agent-preflight.d.ts +56 -0
  168. package/lib/types/src/host/integration/agent/askpass-registry.d.ts +66 -0
  169. package/lib/types/src/host/integration/agent-install-io.d.ts +33 -0
  170. package/lib/types/src/host/integration/agent-preflight.d.ts +56 -0
  171. package/lib/types/src/host/integration/anchor-directory.d.ts +56 -0
  172. package/lib/types/src/host/integration/askpass-registry.d.ts +59 -0
  173. package/lib/types/src/host/integration/bin-root-resolver.d.ts +81 -0
  174. package/lib/types/src/host/integration/connection-test.d.ts +73 -0
  175. package/lib/types/src/host/integration/file-reference-provider.d.ts +115 -0
  176. package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +142 -0
  177. package/lib/types/src/host/integration/fs/fs-provider.d.ts +368 -0
  178. package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +21 -0
  179. package/lib/types/src/host/integration/fs/platform-errors.d.ts +94 -0
  180. package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +369 -0
  181. package/lib/types/src/host/integration/fs/remote-index.d.ts +177 -0
  182. package/lib/types/src/host/integration/fs-provider.d.ts +226 -0
  183. package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +38 -0
  184. package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +67 -0
  185. package/lib/types/src/host/integration/lsp/lsp-session.d.ts +130 -0
  186. package/lib/types/src/host/integration/management-tools.d.ts +64 -0
  187. package/lib/types/src/host/integration/recycle-bin-service.d.ts +247 -0
  188. package/lib/types/src/host/integration/remote/connection-test.d.ts +73 -0
  189. package/lib/types/src/host/integration/remote/remote-face.d.ts +127 -0
  190. package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +108 -0
  191. package/lib/types/src/host/integration/remote-face.d.ts +127 -0
  192. package/lib/types/src/host/integration/remote-file-tools.d.ts +241 -0
  193. package/lib/types/src/host/integration/remote-home.d.ts +69 -0
  194. package/lib/types/src/host/integration/remote-index.d.ts +165 -0
  195. package/lib/types/src/host/integration/remote-job-tools.d.ts +295 -0
  196. package/lib/types/src/host/integration/remote-tool-backend.d.ts +97 -0
  197. package/lib/types/src/host/integration/remote-tools.d.ts +206 -0
  198. package/lib/types/src/host/integration/root-registry.d.ts +115 -0
  199. package/lib/types/src/host/integration/roots/anchor-directory.d.ts +56 -0
  200. package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +81 -0
  201. package/lib/types/src/host/integration/roots/remote-home.d.ts +69 -0
  202. package/lib/types/src/host/integration/roots/root-registry.d.ts +144 -0
  203. package/lib/types/src/host/integration/roots/roots-wiring.d.ts +219 -0
  204. package/lib/types/src/host/integration/roots/session-root-index.d.ts +52 -0
  205. package/lib/types/src/host/integration/roots-api.d.ts +407 -0
  206. package/lib/types/src/host/integration/roots-wiring.d.ts +211 -0
  207. package/lib/types/src/host/integration/session/session-api.d.ts +88 -0
  208. package/lib/types/src/host/integration/session/session-bin-lock.d.ts +80 -0
  209. package/lib/types/src/host/integration/session/session-header.d.ts +151 -0
  210. package/lib/types/src/host/integration/session/session-labels.d.ts +56 -0
  211. package/lib/types/src/host/integration/session/session-route-wiring.d.ts +50 -0
  212. package/lib/types/src/host/integration/session-admin-service.d.ts +236 -0
  213. package/lib/types/src/host/integration/session-api.d.ts +81 -0
  214. package/lib/types/src/host/integration/session-bin-lock.d.ts +80 -0
  215. package/lib/types/src/host/integration/session-header.d.ts +118 -0
  216. package/lib/types/src/host/integration/session-labels.d.ts +56 -0
  217. package/lib/types/src/host/integration/session-route-wiring.d.ts +50 -0
  218. package/lib/types/src/host/integration/session-scope.d.ts +113 -0
  219. package/lib/types/src/host/integration/session-tools.d.ts +44 -0
  220. package/lib/types/src/host/integration/strict-arguments.d.ts +79 -0
  221. package/lib/types/src/host/integration/takeover/takeover-api.d.ts +62 -0
  222. package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +60 -0
  223. package/lib/types/src/host/integration/takeover/takeover-service.d.ts +167 -0
  224. package/lib/types/src/host/integration/terminal-manifest.d.ts +94 -0
  225. package/lib/types/src/host/integration/tools/management-tools.d.ts +245 -0
  226. package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +316 -0
  227. package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +43 -0
  228. package/lib/types/src/host/integration/tools/remote-tools.d.ts +236 -0
  229. package/lib/types/src/host/integration/tools/session-target.d.ts +52 -0
  230. package/lib/types/src/host/integration/tools/session-tools.d.ts +46 -0
  231. package/lib/types/src/host/integration/tools/strict-arguments.d.ts +79 -0
  232. package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +94 -0
  233. package/lib/types/src/host/integration/transfer/remote-copy.d.ts +66 -0
  234. package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +48 -0
  235. package/lib/types/src/host/integration/transfer/transfer-service.d.ts +212 -0
  236. package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +106 -0
  237. package/lib/types/src/host/integration/transfer-adapters.d.ts +44 -0
  238. package/lib/types/src/host/integration/transfer-service.d.ts +164 -0
  239. package/lib/types/src/host/integration/transfer-tracker.d.ts +106 -0
  240. package/lib/types/src/host/integration/user-roots.d.ts +461 -0
  241. package/lib/types/src/index.d.ts +66 -0
  242. package/lib/types/src/local-bridge-refs.d.ts +26 -0
  243. package/lib/types/src/local-bridge.d.ts +36 -0
  244. package/lib/types/src/shared/format.d.ts +13 -0
  245. package/lib/types/src/wiring/connections.d.ts +189 -0
  246. package/lib/types/src/wiring/fs.d.ts +13 -0
  247. package/lib/types/src/wiring/helpers.d.ts +144 -0
  248. package/lib/types/src/wiring/install-memo.d.ts +32 -0
  249. package/lib/types/src/wiring/legacy-dirs.d.ts +62 -0
  250. package/lib/types/src/wiring/package-paths.d.ts +35 -0
  251. package/lib/types/src/wiring/roots.d.ts +21 -0
  252. package/lib/types/src/wiring/state-dir.d.ts +29 -0
  253. package/lib/types/src/wiring/tools.d.ts +35 -0
  254. package/package.json +120 -4
@@ -0,0 +1,345 @@
1
+ /**
2
+ * Session administration: inventory, hierarchy classification, delete plans and restore plans — pure logic only.
3
+ *
4
+ * Requirement FR-3.3–3.10 asks for a *session* recycle bin, which is a different thing from the existing
5
+ * *file-level* recycle bin in [`recycle-bin.ts`](./recycle-bin.ts) (see ADR-0009): that one moves one path
6
+ * inside a root, this one moves a whole session directory out of the platform's session store.
7
+ *
8
+ * ## The two facts everything here is built on
9
+ *
10
+ * 1. **`projectKey` is lossy.** The platform folds separators and truncates at 251 chars, so a directory
11
+ * name **cannot** be decoded back into a `cwd`. Every classification therefore reads the session
12
+ * **header**, never the directory name. (The encoder is reproduced in {@link projectKey} and was checked
13
+ * against the real store 6/6 — see ADR-0009.)
14
+ * 2. **A bin entry has two directory names.** `segment` is where it lives *inside the bin*; `originalSegment` A top-level session id carries a `session-` prefix while a
15
+ * sub-session id is a bare uuid, but the requirement is explicit that this must **not** be used to infer
16
+ * hierarchy: `origin`/`parentSession` from the header is the only authority.
17
+ *
18
+ * Everything in this module is a pure function over already-read data: no filesystem, no clock, no I/O.
19
+ *
20
+ * @module dsh-over-ssh/domain/session-admin
21
+ */
22
+ /**
23
+ * Reproduce the platform's `projectKey(cwd)` (source: `dsh-session-persistence-jsonl` `projectKey`).
24
+ *
25
+ * Rules: runs of `/`, `\` and `:` collapse to one `-`; `[A-Za-z0-9._-]` (except `~`) stay; everything else
26
+ * becomes `~XXXX` (4 uppercase hex digits of the UTF-16 code unit); leading `-` are dropped; empty becomes
27
+ * `root`; the body is truncated to 251 chars; the result is wrapped in `--…--`.
28
+ *
29
+ * **Lossy on purpose** (the platform says so too): two different `cwd` values can share one key. This
30
+ * function exists so the plugin can *compute* the directory a session lives in, never to decode one.
31
+ *
32
+ * @param cwd - the session's project directory (an absolute path).
33
+ * @returns the project directory name.
34
+ * @throws SshDevError `ROOT_INVALID` for an empty path (the platform refuses it too).
35
+ */
36
+ export declare function projectKey(cwd: string): string;
37
+ /**
38
+ * Reproduce the platform's `encodeSegment(id)` (source: `dsh-session-persistence-jsonl`).
39
+ *
40
+ * Unlike {@link projectKey} this one is **injective**: `.` becomes `~002E` (so `.`/`..` cannot escape the
41
+ * directory), safe characters pass through, everything else becomes `~XXXX`. A `session-` prefix is
42
+ * therefore preserved verbatim — which is exactly why hierarchy must not be read from it.
43
+ *
44
+ * @param id - the session id.
45
+ * @returns the directory name.
46
+ */
47
+ export declare function encodeSegment(id: string): string;
48
+ /**
49
+ * The directory one session lives in, relative to the sessions root.
50
+ *
51
+ * `cwd === undefined` selects the platform's `_no-cwd` bucket (the platform's own convention).
52
+ *
53
+ * @param cwd - the session's project directory, or `undefined` for the no-cwd bucket.
54
+ * @param id - the session id.
55
+ * @returns the path segments, root-relative.
56
+ */
57
+ export declare function sessionSegments(cwd: string | undefined, id: string): readonly string[];
58
+ /**
59
+ * Classify one inventory record.
60
+ *
61
+ * The only authority is the header: `parentSessionId` present ⇒ sub-session. A record whose header could not
62
+ * be read is `unreadable`. The directory name is deliberately **not** consulted — `session-`-prefixed ids are
63
+ * top-level sessions, but the requirement forbids inferring hierarchy from that, and a future id scheme would
64
+ * silently break such an inference.
65
+ *
66
+ * @param record - a record without its `classification` field.
67
+ * @returns the class.
68
+ */
69
+ export declare function classifySession(record: Omit<SessionRecord, 'classification'> & {
70
+ readonly readable?: boolean;
71
+ }): SessionClass;
72
+ /** How one session relates to the rest of the store. */
73
+ export type SessionClass =
74
+ /** A normal, independently-visible session. */
75
+ 'normal'
76
+ /** A sub-session: its lifecycle follows its parent, so it must not be deleted on its own. */
77
+ | 'subsession'
78
+ /** A directory whose header could not be read (corrupt or unsupported format). */
79
+ | 'unreadable';
80
+ /** One session as the inventory sees it. */
81
+ export interface SessionRecord {
82
+ readonly id: string;
83
+ /** Project directory the log was found under, as spelled on disk. */
84
+ readonly projectDir: string;
85
+ /** Directory name (`encodeSegment(id)`) the log was found under. */
86
+ readonly segment: string;
87
+ /** `cwd` from the header; `undefined` for the no-cwd bucket. */
88
+ readonly cwd: string | undefined;
89
+ /** The session's own title (the platform's `title` projection), when one was ever generated. */
90
+ readonly title: string | undefined;
91
+ /**
92
+ * Display title of the workspace owning {@link cwd}, resolved from the platform's workspace registry.
93
+ *
94
+ * `undefined` when the caller supplied no resolver or no registered workspace owns the directory. The
95
+ * directory itself is still on the wire (`cwd`), so a reader falls back to its own leaf name.
96
+ */
97
+ readonly workspaceTitle: string | undefined;
98
+ /** Parent session id from the header, when this is a sub-session. */
99
+ readonly parentSessionId: string | undefined;
100
+ /** Total bytes of the log files in the session directory (0 when unknown). */
101
+ readonly bytes: number;
102
+ /** Modification time of the newest log file, in ms since the epoch. */
103
+ readonly updatedAtMs: number;
104
+ /**
105
+ * True when the host holds this session in its store — it was opened in this DSH process.
106
+ *
107
+ * Loaded is **not** running: the platform keeps an agent for every session opened since the process
108
+ * started, and an idle agent is doing nothing. The panel renders this as 已打开, and an idle loaded
109
+ * session stays deletable.
110
+ */
111
+ readonly live: boolean;
112
+ /**
113
+ * True when this session's agent is running a turn (`agents.get(id)?.status === 'running'`).
114
+ *
115
+ * This is the platform's own "运行中" signal — the one the session list uses for a row's status — and the
116
+ * only state that must refuse deletion (FR-3.6).
117
+ */
118
+ readonly running: boolean;
119
+ /** How this session relates to the store (see {@link classifySession}). */
120
+ readonly classification: SessionClass;
121
+ }
122
+ /** Why one session may not be deleted. */
123
+ export type DeleteRefusal =
124
+ /** Its agent is running a turn: the platform has no public way to unload one (see FR-3.6). */
125
+ 'running'
126
+ /** It is a sub-session: delete its parent instead. */
127
+ | 'subsession'
128
+ /** Its log could not be read, so deleting it would be guesswork. */
129
+ | 'unreadable'
130
+ /** The caller asked to keep a session that this operation would have removed. */
131
+ | 'protected';
132
+ /** The pure decision for one deletion. */
133
+ export interface DeleteDecision {
134
+ /** Ids to delete, **children before parents** (so a parent never outlives its children). */
135
+ readonly delete: readonly string[];
136
+ /** One refusal per skipped id, in the order the ids were considered. */
137
+ readonly refusals: readonly {
138
+ readonly id: string;
139
+ readonly reason: DeleteRefusal;
140
+ }[];
141
+ /** Total bytes the deletion will free, counting only the ids in `delete`. */
142
+ readonly bytes: number;
143
+ }
144
+ /**
145
+ * Decide what a deletion request would actually do, without touching anything.
146
+ *
147
+ * The rules, in order:
148
+ *
149
+ * 1. an id that is not in the inventory is refused as `unreadable` (it was never a session here);
150
+ * 2. a **running** session is refused (`running`) — FR-3.6 requires blocking rather than killing, because the
151
+ * platform exposes no way to unload a live agent, and the requirement is explicit that
152
+ * "stop and delete" is *not* offered this round. Loaded-but-idle is **not** refused: the host keeps an
153
+ * agent for every session opened since it started, so refusing those would make nearly every session
154
+ * undeletable, and a session that is doing nothing cannot be interrupted;
155
+ * 3. an `unreadable` record is refused (`unreadable`) — deleting a directory we could not identify is
156
+ * exactly the kind of irreversible guess this feature must not make;
157
+ * 4. a sub-session is refused (`subsession`) when it is selected **on its own** (FR-3.3: 子会话随父会话管理,
158
+ * 默认不可被单独删除);
159
+ * 5. selecting a parent **always** takes its whole descendant subtree along, ordered **children first**.
160
+ *
161
+ * Rule 5 is unconditional on purpose. It used to be an opt-in `includeSubsessions` switch, and that was the
162
+ * wrong shape: leaving a parent's children behind orphans their logs — the parent is gone, so nothing
163
+ * navigates to them, and the platform's listing hides them by `parentId` nesting, so the bytes stay on disk
164
+ * and invisible forever. FR-3.3 asks for sub-sessions to be "随父会话管理" and the requirement's own §7
165
+ * recommendation is "删除父会话时提示将一并处理 N 个子会话": always together, with the count shown.
166
+ *
167
+ * A sub-session that was explicitly requested *and* pulled in by its parent is deleted and **not** reported
168
+ * as a refusal — otherwise the caller would see a row refused and deleted in the same answer.
169
+ *
170
+ * **A subtree is atomic.** If any descendant of a requested session is running or protected, the whole
171
+ * request for that session is refused instead of deleting the ancestor and orphaning the descendant: a
172
+ * running child whose parent is gone can never be reached again (the listing nests children under their
173
+ * parent), and a protected one was explicitly asked to be spared. Refusing the parent is the only outcome
174
+ * that keeps both promises (FR-3.6 never interrupts a running agent; `keep` is honoured).
175
+ *
176
+ * `keep` wins over everything: an id listed there is refused as `protected` even if it is an idle normal
177
+ * session. It exists so a UI can show a preview and let the user uncheck rows.
178
+ *
179
+ * @param records - the full inventory.
180
+ * @param request - ids to delete, plus the optional keep list.
181
+ * @returns the decision; `delete` is empty when everything was refused.
182
+ */
183
+ export declare function planDelete(records: readonly SessionRecord[], request: {
184
+ readonly ids: readonly string[];
185
+ readonly keep?: readonly string[];
186
+ }): DeleteDecision;
187
+ /** Index file name inside the session bin root. */
188
+ export declare const SESSION_TRASH_INDEX_FILE = "index.json";
189
+ /** Version stamped into the session bin index; a different version is a hard parse failure. */
190
+ export declare const SESSION_TRASH_INDEX_VERSION = 1;
191
+ /** Opaque identity of one session-bin entry. */
192
+ export type SessionTrashEntryId = string & {
193
+ readonly __sessionTrashEntryId: 'SessionTrashEntryId';
194
+ };
195
+ /** One entry in the session recycle bin: everything a restore needs. */
196
+ export interface SessionTrashEntry {
197
+ readonly id: SessionTrashEntryId;
198
+ /** The deleted session's id. */
199
+ readonly sessionId: string;
200
+ /**
201
+ * The session's `cwd` as recorded in its header, or `undefined` for the no-cwd bucket.
202
+ *
203
+ * Kept **verbatim** rather than as a `projectKey`: the key is lossy, so restoring from it could land the
204
+ * session under a different project directory than it came from.
205
+ */
206
+ readonly cwd: string | undefined;
207
+ /** Project directory name the session was moved out of. */
208
+ readonly projectDir: string;
209
+ /**
210
+ * Directory name **inside the bin**; the session directory is moved here wholesale.
211
+ *
212
+ * Equals the entry id in practice, and is **not** the store-side name — see `originalSegment`. Keeping the
213
+ * two apart is what makes a restore land back where it came from: using one for both would make the
214
+ * restored session appear under the entry id.
215
+ */
216
+ readonly segment: string;
217
+ /**
218
+ * The session's directory name **in the store** (`encodeSegment(sessionId)`), captured before the move.
219
+ *
220
+ * Stored rather than recomputed: `encodeSegment` is injective so recomputing would work today, but recording
221
+ * what was actually observed means a restore never depends on the encoder staying byte-identical.
222
+ */
223
+ readonly originalSegment: string;
224
+ readonly title: string | undefined;
225
+ /** Total bytes of the moved log files, for the retention policy. */
226
+ readonly bytes: number;
227
+ readonly deletedAtMs: number;
228
+ /** How the bytes were moved. `copy` means the move crossed a filesystem boundary. */
229
+ readonly movedBy: 'rename' | 'copy';
230
+ /** Who confirmed the deletion, when the caller supplied it (FR-3.10 auditability). */
231
+ readonly confirmedBy: string | undefined;
232
+ }
233
+ /**
234
+ * Derive a stable, filesystem-safe entry id.
235
+ *
236
+ * Deterministic on `(sessionId, deletedAtMs)` so a retried deletion of the same session at the same instant
237
+ * lands on the same entry instead of leaving two copies; {@link allocateSessionTrashEntryId} breaks genuine
238
+ * collisions.
239
+ *
240
+ * @param input - identity inputs.
241
+ * @returns the entry id.
242
+ */
243
+ export declare function makeSessionTrashEntryId(input: {
244
+ readonly sessionId: string;
245
+ readonly deletedAtMs: number;
246
+ }): SessionTrashEntryId;
247
+ /**
248
+ * Allocate an id that is not already taken, by appending a counter.
249
+ *
250
+ * @param existing - ids already in the bin.
251
+ * @param input - identity inputs.
252
+ * @returns a free id.
253
+ * @throws SshDevError `TRASH_NOT_FOUND` when the counter space is exhausted (never returns a taken id).
254
+ */
255
+ export declare function allocateSessionTrashEntryId(existing: Iterable<string>, input: {
256
+ readonly sessionId: string;
257
+ readonly deletedAtMs: number;
258
+ }): SessionTrashEntryId;
259
+ /** The persisted session-bin index document. */
260
+ export interface SessionTrashIndex {
261
+ readonly version: number;
262
+ readonly entries: readonly SessionTrashEntry[];
263
+ }
264
+ /** One index record the parser refused. */
265
+ export interface SkippedSessionTrashRecord {
266
+ readonly index: number;
267
+ readonly reason: string;
268
+ }
269
+ /** Result of {@link parseSessionTrashIndex}. */
270
+ export interface ParsedSessionTrashIndex {
271
+ readonly entries: readonly SessionTrashEntry[];
272
+ readonly skipped: readonly SkippedSessionTrashRecord[];
273
+ }
274
+ /**
275
+ * Serialize the session-bin index.
276
+ *
277
+ * Entries are sorted by id so the same set is byte-identical every time (diffable, testable).
278
+ *
279
+ * @param entries - entries to persist.
280
+ * @returns JSON text.
281
+ */
282
+ export declare function serializeSessionTrashIndex(entries: readonly SessionTrashEntry[]): string;
283
+ /**
284
+ * Parse the session-bin index.
285
+ *
286
+ * A malformed document or unknown version is a **hard failure** (the bin cannot be trusted, and guessing
287
+ * could delete the wrong directory); a single unusable record is skipped and reported, so one corrupt entry
288
+ * never hides the rest.
289
+ *
290
+ * @param text - index file content.
291
+ * @returns usable entries plus skipped records.
292
+ * @throws SshDevError `TRASH_NOT_FOUND` when the document cannot be read as an index.
293
+ */
294
+ export declare function parseSessionTrashIndex(text: string): ParsedSessionTrashIndex;
295
+ /** The pure decision for restoring one entry. */
296
+ export type RestoreSessionPlan = {
297
+ readonly kind: 'restore';
298
+ readonly entryId: SessionTrashEntryId;
299
+ readonly sessionId: string;
300
+ readonly projectDir: string;
301
+ /** Where it goes back to in the store (the original directory name). */
302
+ readonly originalSegment: string;
303
+ } | {
304
+ readonly kind: 'refuse';
305
+ readonly reason: 'occupied';
306
+ readonly entryId: SessionTrashEntryId;
307
+ readonly suggestedId: string;
308
+ };
309
+ /**
310
+ * Decide how to restore one entry.
311
+ *
312
+ * Restoring onto an occupied path is **refused** with a suggestion rather than silently restored under a
313
+ * different name: the platform keys a session by `(id, cwd)`, so a renamed directory would either be
314
+ * invisible or (worse) look like a second copy of a session that still exists.
315
+ *
316
+ * @param entry - the bin entry.
317
+ * @param options - whether the original path is currently occupied.
318
+ * @returns the plan.
319
+ */
320
+ export declare function planRestoreSession(entry: SessionTrashEntry, options: {
321
+ readonly occupied: boolean;
322
+ }): RestoreSessionPlan;
323
+ /**
324
+ * Build one entry from a deletion that has already been performed.
325
+ *
326
+ * Pure: the caller has moved the bytes and passes the observed facts. Keeping this separate from the move
327
+ * means the recorded `bytes`/`movedBy` are what actually happened, not what was planned.
328
+ *
329
+ * @param input - observed facts.
330
+ * @returns the entry.
331
+ */
332
+ export declare function makeSessionTrashEntry(input: {
333
+ readonly id: SessionTrashEntryId;
334
+ readonly sessionId: string;
335
+ readonly cwd: string | undefined;
336
+ readonly projectDir: string;
337
+ readonly segment: string;
338
+ readonly originalSegment: string;
339
+ readonly title: string | undefined;
340
+ readonly bytes: number;
341
+ readonly deletedAtMs: number;
342
+ readonly movedBy: 'rename' | 'copy';
343
+ readonly confirmedBy?: string | undefined;
344
+ }): SessionTrashEntry;
345
+ //# sourceMappingURL=session-admin.d.ts.map
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Which terminal a session gets: the remote host, or the local machine.
3
+ *
4
+ * The platform's only official extension point for the Web terminal is `terminalController.Config.shell`
5
+ * (`{ path, name, args }`), so this plugin ships a **wrapper** as that shell: the wrapper decides at launch
6
+ * time whether to `exec ssh` or the local login shell. See `doc/02-platform/terminal.md` §5 for the evidence
7
+ * and the accepted costs.
8
+ *
9
+ * This module is the decision itself, kept pure (no filesystem, no spawn) so it can be tested in
10
+ * milliseconds — the wrapper script is a thin `spawn` around it.
11
+ *
12
+ * Two inputs decide it, in this order:
13
+ *
14
+ * 1. **the session's recorded target** (`state/<sessionId>.json`): the host writes it when it resolves a
15
+ * remote root for that session, so it works even when the core hands us an unrelated cwd;
16
+ * 2. **the root manifest** (`roots.json`): a directory→connection map, used when the wrapper has no session
17
+ * id (a terminal opened by hand) — a cwd under a remote root's anchor means the user is working remotely.
18
+ *
19
+ * @module dsh-over-ssh/domain/terminal-target
20
+ */
21
+ import { quote as posixQuote } from '../shell.ts';
22
+ /** How the wrapper reaches a connection's host: pre-built ssh arguments the host already resolved. */
23
+ export interface SshInvocation {
24
+ /** `user@host` or a `~/.ssh/config` alias. */
25
+ readonly destination: string;
26
+ /** Extra arguments (`-i`, `-o UserKnownHostsFile=…`, `-p`, jump options…), already credential-resolved. */
27
+ readonly args: readonly string[];
28
+ }
29
+ /** One remote root as the wrapper needs it. */
30
+ export interface ManifestRoot {
31
+ readonly connectionId: string;
32
+ /** Backend-absolute remote path (POSIX). */
33
+ readonly remoteRoot: string;
34
+ /** Local anchor directory that maps to this remote root. */
35
+ readonly anchorPath: string;
36
+ /** How to reach the host. */
37
+ readonly ssh: SshInvocation;
38
+ }
39
+ /** The root manifest the host writes for the wrapper. */
40
+ export interface RootManifest {
41
+ /** Manifest format version; a wrapper that does not know the version falls back to local. */
42
+ readonly version: number;
43
+ readonly roots: readonly ManifestRoot[];
44
+ }
45
+ /** One session's recorded remote target. */
46
+ export interface SessionState {
47
+ readonly sessionId: string;
48
+ readonly connectionId: string;
49
+ readonly remoteRoot: string;
50
+ readonly ssh: SshInvocation;
51
+ /** Epoch milliseconds; the wrapper ignores entries older than {@link SESSION_STATE_MAX_AGE_MS}. */
52
+ readonly recordedAt: number;
53
+ }
54
+ /** Current manifest version. */
55
+ export declare const MANIFEST_VERSION = 1;
56
+ /** A session state older than this is ignored (a stale file must not send the user to the wrong host). */
57
+ export declare const SESSION_STATE_MAX_AGE_MS: number;
58
+ /** The decision the wrapper acts on. */
59
+ export type TerminalDecision = {
60
+ readonly kind: 'remote';
61
+ readonly connectionId: string;
62
+ readonly remoteRoot: string;
63
+ /** Full argv to spawn: `[ssh, -tt, …args, destination, --, command]`. */
64
+ readonly argv: readonly string[];
65
+ /** Remote command: `cd <root> && exec <login shell> -l`. */
66
+ readonly remoteCommand: string;
67
+ /** Why this decision was taken (for diagnostics and tests). */
68
+ readonly reason: 'session' | 'anchor';
69
+ } | {
70
+ readonly kind: 'local';
71
+ /** Full argv to spawn: the local login shell. */
72
+ readonly argv: readonly string[];
73
+ readonly reason: 'no-target' | 'state-unusable' | 'manifest-unusable';
74
+ };
75
+ /** Options for {@link decideTerminal}. */
76
+ export interface DecideTerminalOptions {
77
+ /** Working directory the terminal was launched in. */
78
+ readonly cwd: string;
79
+ /** Manifest, when readable and valid. */
80
+ readonly manifest?: RootManifest;
81
+ /** This session's state, when readable and valid. */
82
+ readonly sessionState?: SessionState;
83
+ /** Local login shell to fall back to (platform default resolved by the caller). */
84
+ readonly localShell: string;
85
+ /** Remote login shell; `$SHELL` expands remotely, which is the right default. */
86
+ readonly remoteShell?: string;
87
+ /** Clock, injected for the staleness check. */
88
+ readonly now?: number;
89
+ }
90
+ /**
91
+ * Quote one word for a POSIX shell.
92
+ *
93
+ * Re-exported from {@link ../shell.ts}: this module builds `cd … && exec …`, which is a remote shell command,
94
+ * and the shared implementation is the one that refuses NUL bytes.
95
+ */
96
+ export { posixQuote };
97
+ /**
98
+ * Parse a root manifest.
99
+ *
100
+ * @param raw - file contents.
101
+ * @returns the manifest, or `undefined` when it is unusable (wrong version, malformed, empty).
102
+ */
103
+ export declare function parseRootManifest(raw: string): RootManifest | undefined;
104
+ /**
105
+ * Parse one session state file.
106
+ *
107
+ * @param raw - file contents.
108
+ * @returns the state, or `undefined` when malformed.
109
+ */
110
+ export declare function parseSessionState(raw: string): SessionState | undefined;
111
+ /** Serialize a manifest for the wrapper (stable key order, so the file diff is readable). */
112
+ export declare function serializeRootManifest(manifest: RootManifest): string;
113
+ /** Serialize one session state. */
114
+ export declare function serializeSessionState(state: SessionState): string;
115
+ /** Build the command the remote shell runs: enter the workspace and be a login shell. */
116
+ export declare function remoteCommandFor(remoteRoot: string, remoteShell?: string): string;
117
+ /**
118
+ * Build the ssh argv for one remote target.
119
+ *
120
+ * `-tt` is what makes an interactive terminal work through the wrapper (the child needs a TTY on both
121
+ * sides). `--` ends option parsing before the destination, and the remote command is a single argument.
122
+ *
123
+ * @param ssh - resolved destination and credential arguments.
124
+ * @param remoteCommand - command to run remotely.
125
+ * @returns the argv to spawn.
126
+ */
127
+ export declare function sshArgvFor(ssh: SshInvocation, remoteCommand: string): readonly string[];
128
+ /**
129
+ * Decide which terminal to open.
130
+ *
131
+ * @param options - cwd, manifest, session state, local shell, clock.
132
+ * @returns the decision, always total (never throws): an unusable input degrades to the local shell, with
133
+ * `reason` naming why.
134
+ */
135
+ export declare function decideTerminal(options: DecideTerminalOptions): TerminalDecision;
136
+ //# sourceMappingURL=terminal-target.d.ts.map