dsh-remote-vs 0.0.0-stage → 0.8.5

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 (200) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +388 -0
  3. package/README.md +349 -2
  4. package/cordis.patch.yml +259 -0
  5. package/icon.svg +7 -0
  6. package/lib/client.js +62666 -0
  7. package/lib/index.js +4287 -0
  8. package/lib/p2-bridge-refs.js +10 -0
  9. package/lib/p2-bridge.js +16 -0
  10. package/lib/p2-router-fs.js +252 -0
  11. package/lib/p2-router-refs.js +466 -0
  12. package/lib/p2-router-spill.js +62 -0
  13. package/lib/p2-router-subprocess.js +64 -0
  14. package/lib/p2-shell-bash.js +24 -0
  15. package/lib/ssh-proc-arI5lm4W.js +632 -0
  16. package/lib/types/changes/recorder.d.ts +240 -0
  17. package/lib/types/changes/status.d.ts +201 -0
  18. package/lib/types/changes/text-diff.d.ts +65 -0
  19. package/lib/types/client/CodeEditor.d.ts +125 -0
  20. package/lib/types/client/Dialog.d.ts +14 -0
  21. package/lib/types/client/DirectoryFlow.d.ts +30 -0
  22. package/lib/types/client/EditorPane.d.ts +30 -0
  23. package/lib/types/client/HostsView.d.ts +10 -0
  24. package/lib/types/client/NativeExplorer.d.ts +39 -0
  25. package/lib/types/client/RemoteDirectoryPicker.d.ts +16 -0
  26. package/lib/types/client/RemoteEditorView.d.ts +20 -0
  27. package/lib/types/client/RemoteExplorer.d.ts +18 -0
  28. package/lib/types/client/RemoteRoot.d.ts +16 -0
  29. package/lib/types/client/SearchView.d.ts +14 -0
  30. package/lib/types/client/SettingsHosts.d.ts +15 -0
  31. package/lib/types/client/TerminalView.d.ts +16 -0
  32. package/lib/types/client/WorkspaceWizard.d.ts +16 -0
  33. package/lib/types/client/WorkspacesView.d.ts +14 -0
  34. package/lib/types/client/XTerm.d.ts +15 -0
  35. package/lib/types/client/api.d.ts +162 -0
  36. package/lib/types/client/changes/ChangesDock.d.ts +35 -0
  37. package/lib/types/client/changes/model.d.ts +36 -0
  38. package/lib/types/client/changes/undo-affordance.d.ts +48 -0
  39. package/lib/types/client/changes/undo-context.d.ts +25 -0
  40. package/lib/types/client/composer.d.ts +165 -0
  41. package/lib/types/client/i18n.d.ts +17 -0
  42. package/lib/types/client/index.d.ts +14 -0
  43. package/lib/types/client/remote-file-open-target.d.ts +247 -0
  44. package/lib/types/client/remote-file-open.d.ts +35 -0
  45. package/lib/types/client/reveal.d.ts +15 -0
  46. package/lib/types/client/scm/DiffView.d.ts +24 -0
  47. package/lib/types/client/scm/HistoryPanel.d.ts +12 -0
  48. package/lib/types/client/scm/RepoPicker.d.ts +24 -0
  49. package/lib/types/client/scm/ScmPanel.d.ts +15 -0
  50. package/lib/types/client/scm/actions.d.ts +187 -0
  51. package/lib/types/client/scm/diff-model.d.ts +29 -0
  52. package/lib/types/client/scm/stage-queue.d.ts +99 -0
  53. package/lib/types/client/scm/status-map.d.ts +157 -0
  54. package/lib/types/client/scm/untracked-dirs.d.ts +72 -0
  55. package/lib/types/client/selection-popup.d.ts +61 -0
  56. package/lib/types/client/store.d.ts +437 -0
  57. package/lib/types/client/theme.d.ts +15 -0
  58. package/lib/types/client/ui.d.ts +46 -0
  59. package/lib/types/client/upload-plan.d.ts +78 -0
  60. package/lib/types/client/upload-source.d.ts +46 -0
  61. package/lib/types/client/use-remote-chat-target.d.ts +17 -0
  62. package/lib/types/client/vsc/FindWidget.d.ts +81 -0
  63. package/lib/types/client/vsc/change-marks.d.ts +150 -0
  64. package/lib/types/client/vsc/editor-langs.d.ts +5 -0
  65. package/lib/types/client/vsc/editor-palette.d.ts +37 -0
  66. package/lib/types/client/vsc/editor-theme.d.ts +32 -0
  67. package/lib/types/client/vsc/fallback-highlight.d.ts +26 -0
  68. package/lib/types/client/vsc/kit.d.ts +195 -0
  69. package/lib/types/client/vsc/markdown.d.ts +11 -0
  70. package/lib/types/client/vsc/reveal-line.d.ts +38 -0
  71. package/lib/types/client/vsc/tokenize.d.ts +104 -0
  72. package/lib/types/deploy.d.ts +94 -0
  73. package/lib/types/fs-ops.d.ts +8 -0
  74. package/lib/types/git-format.d.ts +34 -0
  75. package/lib/types/git-ops.d.ts +183 -0
  76. package/lib/types/http.d.ts +14 -0
  77. package/lib/types/importers.d.ts +20 -0
  78. package/lib/types/index.d.ts +87 -0
  79. package/lib/types/keygen.d.ts +19 -0
  80. package/lib/types/loopback.d.ts +7 -0
  81. package/lib/types/p2/bridge-refs.d.ts +21 -0
  82. package/lib/types/p2/bridge.d.ts +24 -0
  83. package/lib/types/p2/file-refs.d.ts +73 -0
  84. package/lib/types/p2/helper-rpc.d.ts +63 -0
  85. package/lib/types/p2/in-flight-gate.d.ts +25 -0
  86. package/lib/types/p2/router-fs.d.ts +92 -0
  87. package/lib/types/p2/router-refs.d.ts +45 -0
  88. package/lib/types/p2/router-spill.d.ts +11 -0
  89. package/lib/types/p2/router-subprocess.d.ts +23 -0
  90. package/lib/types/p2/shell-bash.d.ts +16 -0
  91. package/lib/types/p2/ssh-proc.d.ts +22 -0
  92. package/lib/types/p2/worlds.d.ts +179 -0
  93. package/lib/types/paths.d.ts +12 -0
  94. package/lib/types/pool.d.ts +105 -0
  95. package/lib/types/protocol.d.ts +452 -0
  96. package/lib/types/routes.d.ts +27 -0
  97. package/lib/types/search-ops.d.ts +86 -0
  98. package/lib/types/sftp.d.ts +90 -0
  99. package/lib/types/store.d.ts +66 -0
  100. package/lib/types/terminal.d.ts +51 -0
  101. package/lib/types/timeouts.d.ts +83 -0
  102. package/lib/types/tools.d.ts +41 -0
  103. package/lib/worlds-PUumRIh7.js +18765 -0
  104. package/locale/en.json +6 -0
  105. package/locale/zh.json +6 -0
  106. package/package.json +187 -4
  107. package/src/changes/recorder.ts +902 -0
  108. package/src/changes/status.ts +448 -0
  109. package/src/changes/text-diff.ts +377 -0
  110. package/src/client/CodeEditor.tsx +923 -0
  111. package/src/client/Dialog.tsx +76 -0
  112. package/src/client/DirectoryFlow.tsx +431 -0
  113. package/src/client/EditorPane.tsx +512 -0
  114. package/src/client/HostsView.tsx +312 -0
  115. package/src/client/NativeExplorer.tsx +121 -0
  116. package/src/client/RemoteDirectoryPicker.tsx +157 -0
  117. package/src/client/RemoteEditorView.tsx +54 -0
  118. package/src/client/RemoteExplorer.tsx +1398 -0
  119. package/src/client/RemoteRoot.tsx +389 -0
  120. package/src/client/SearchView.tsx +407 -0
  121. package/src/client/SettingsHosts.tsx +136 -0
  122. package/src/client/TerminalView.tsx +308 -0
  123. package/src/client/WorkspaceWizard.tsx +199 -0
  124. package/src/client/WorkspacesView.tsx +93 -0
  125. package/src/client/XTerm.tsx +308 -0
  126. package/src/client/api.ts +331 -0
  127. package/src/client/changes/ChangesDock.tsx +348 -0
  128. package/src/client/changes/changes.css +230 -0
  129. package/src/client/changes/model.ts +347 -0
  130. package/src/client/changes/undo-affordance.ts +389 -0
  131. package/src/client/changes/undo-context.ts +34 -0
  132. package/src/client/composer.ts +436 -0
  133. package/src/client/css.d.ts +7 -0
  134. package/src/client/i18n.ts +702 -0
  135. package/src/client/index.tsx +286 -0
  136. package/src/client/remote-file-open-target.ts +553 -0
  137. package/src/client/remote-file-open.ts +261 -0
  138. package/src/client/reveal.ts +33 -0
  139. package/src/client/scm/DiffView.tsx +570 -0
  140. package/src/client/scm/HistoryPanel.tsx +353 -0
  141. package/src/client/scm/RepoPicker.tsx +238 -0
  142. package/src/client/scm/ScmPanel.tsx +857 -0
  143. package/src/client/scm/actions.ts +491 -0
  144. package/src/client/scm/diff-model.ts +206 -0
  145. package/src/client/scm/stage-queue.ts +264 -0
  146. package/src/client/scm/status-map.ts +381 -0
  147. package/src/client/scm/untracked-dirs.ts +139 -0
  148. package/src/client/selection-popup.ts +157 -0
  149. package/src/client/store.tsx +1670 -0
  150. package/src/client/theme.ts +54 -0
  151. package/src/client/ui.tsx +136 -0
  152. package/src/client/upload-plan.ts +153 -0
  153. package/src/client/upload-source.ts +145 -0
  154. package/src/client/use-remote-chat-target.ts +153 -0
  155. package/src/client/vsc/FindWidget.tsx +446 -0
  156. package/src/client/vsc/change-marks.ts +586 -0
  157. package/src/client/vsc/editor-langs.ts +137 -0
  158. package/src/client/vsc/editor-palette.ts +107 -0
  159. package/src/client/vsc/editor-theme.ts +93 -0
  160. package/src/client/vsc/fallback-highlight.ts +145 -0
  161. package/src/client/vsc/kit.tsx +777 -0
  162. package/src/client/vsc/markdown.tsx +172 -0
  163. package/src/client/vsc/reveal-line.ts +142 -0
  164. package/src/client/vsc/tokenize.ts +569 -0
  165. package/src/client/vsc/vsc.css +2669 -0
  166. package/src/deploy.ts +481 -0
  167. package/src/fs-ops.ts +57 -0
  168. package/src/git-format.ts +205 -0
  169. package/src/git-ops.ts +693 -0
  170. package/src/http.ts +55 -0
  171. package/src/importers.ts +141 -0
  172. package/src/index.ts +442 -0
  173. package/src/keygen.ts +77 -0
  174. package/src/loopback.ts +47 -0
  175. package/src/p2/bridge-refs.ts +27 -0
  176. package/src/p2/bridge.ts +32 -0
  177. package/src/p2/file-refs.ts +392 -0
  178. package/src/p2/helper-rpc.ts +394 -0
  179. package/src/p2/in-flight-gate.ts +63 -0
  180. package/src/p2/router-fs.ts +327 -0
  181. package/src/p2/router-refs.ts +166 -0
  182. package/src/p2/router-spill.ts +75 -0
  183. package/src/p2/router-subprocess.ts +92 -0
  184. package/src/p2/shell-bash.ts +30 -0
  185. package/src/p2/ssh-proc.ts +802 -0
  186. package/src/p2/worlds.ts +735 -0
  187. package/src/paths.ts +50 -0
  188. package/src/pool.ts +811 -0
  189. package/src/protocol.ts +460 -0
  190. package/src/routes.ts +802 -0
  191. package/src/search-ops.ts +250 -0
  192. package/src/sftp.ts +677 -0
  193. package/src/store.ts +364 -0
  194. package/src/terminal.ts +291 -0
  195. package/src/timeouts.ts +122 -0
  196. package/src/tools.ts +320 -0
  197. package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.helper.json +16 -0
  198. package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.tar.gz +4 -0
  199. package/vendor/dsh-remote-helpers/dsh-ssh-helper-0.2.1-alpha.2-linux-x64.tar.gz.sha256 +1 -0
  200. package/vendor/dsh-remote-helpers/glibc-ceiling.patch +31 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 xiray-xia
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,388 @@
1
+ # dsh-remote-vs · VSCode-style remote development for DSH
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
4
+
5
+ [中文](./README.md) | English
6
+
7
+ SSH host management inside the DSH Host process + helper push pipeline + a **VS Code-style remote
8
+ workbench** (explorer / search / source control / commit graph / multi-tab editor / side-by-side diff /
9
+ multiple terminals). Hosts live in DSH Settings ("Remote hosts"); with `dsh-better-sidebar` installed
10
+ there is an extra standalone "Remote" tab, otherwise it falls back to a native right-Sidebar "Remote"
11
+ page (same content, self-opened from the guide, per-session instances). The editor is ALSO mounted into
12
+ the conversation view slot as a "远程编辑器" (remote editor) tab next to "对话 / 轨迹", so the code area is
13
+ full width; the sidebar copy is the narrow fallback. Since P2, native tools inside remote workspaces
14
+ transparently run remotely (see below).
15
+
16
+ ```text
17
+ ┌─ Local DSH Host ─────────────────────────┐
18
+ │ Plugin host half │ Client half │
19
+ │ hosts/keys/push/ │ settings + native │
20
+ │ routing │ entry takeover │
21
+ │ /api/dsh-remote/* │ explorer page │
22
+ └─────────────────────┴────────────────────┘
23
+ │ ssh2 (files/proc/terminal) + helper RPC (remote fs)
24
+ ▼
25
+ ┌─ Remote Linux ─────────────────────────────┐
26
+ │ ~/.dsh-helpers/<ver>/<arch>/dsh-ssh-helper│
27
+ │ user-chosen remote working directory │
28
+ └────────────────────────────────────────────┘
29
+ ```
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ dsh plugin --profile web add dsh-remote-vs
35
+ ```
36
+
37
+ (From npm, recommended; installing straight from the repository also works:
38
+ `dsh plugin --profile web add github:xiray-xia/dsh-remote-vs`.)
39
+
40
+ Requires DSH `0.2.1-alpha.3` or newer (`engines.dsh` enforces the range; **the tested
41
+ baseline is `0.2.1-rc.2`**). `--profile web` selects which profile to install into —
42
+ this is a Web plugin, so it has to go into the profile your Web GUI runs from (`web` is
43
+ the common name; use your own profile's name).
44
+
45
+ **That one command is all it takes — no build scripts have to be approved.** The package
46
+ has no `prepare`/`postinstall` and no package in the dependency tree carries an install
47
+ script (`ssh2` is **bundled into the artifact**, not depended on); the repository route
48
+ also ships the built `lib/`. The old "allow the printed build scripts in that profile's
49
+ `pnpm-workspace.yaml` and re-run" step was pnpm 12's hard policy gate for dependencies
50
+ with lifecycle scripts (`ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED` / `ERR_PNPM_IGNORED_BUILDS`);
51
+ it cannot be waived from the dependency side, only removed from the tree. Then restart
52
+ `dsh web` and open Settings → Remote hosts to configure.
53
+
54
+ > The package carries the prebuilt helper (`vendor/dsh-remote-helpers/`, ~45 MB), so the
55
+ > first install is a large download — it is the **only** channel that delivers a helper
56
+ > (the official releases ship no helper executable), and dropping it would leave new hosts
57
+ > undeployable.
58
+
59
+ ## Capabilities
60
+
61
+ ### Remote workbench (VS Code-style UI)
62
+
63
+ | Capability | Notes |
64
+ | --- | --- |
65
+ | Explorer | Lazy tree + expand/collapse + hidden-files toggle; context menu: new / rename / delete / copy / move / paste / find in folder / insert `@` reference; git status colours and folder roll-up marks |
66
+ | Search | Two modes: **file names** (remote `find`) and **contents** (remote `grep`). Content mode has a scope folder, `Aa` (case) / `ab_` (whole word) / `.*` (regex), results grouped per file with match highlights, and clicking a hit jumps to that line |
67
+ | Source control | **Repository-folder picker** (for workspaces whose git repository lives in a sub-folder: browse the remote tree, one-click jump to any repository discovered inside the workspace, type a relative or absolute path, and see which repository each folder resolves to); changes / staged / untracked / ignored buckets; **untracked directory rows expand on click**; per-file AND **per-hunk** stage / unstage / discard (behind a confirm) / commit; branches, remotes and stash panes; sync (fetch/pull/push) |
68
+ | Session change review | The **collapsible** panel above the composer: what THIS SESSION changed (one row per file, file-type icon, `+a −b` on the right); clicking a file opens it in the remote editor with **line shading plus character-level inline diff**, `n/N ↑↓` jumps between change points, and hovering a changed line offers **Undo this change** (remote `git apply -R`). See "Session change review" below |
69
+ | Side-by-side diff | Panes stay **exactly equal** + per-hunk buttons in the middle gutter + a **top horizontal scrollbar** (a very long line can no longer break the symmetry) + pinned line numbers, **rendering only the rows inside the viewport** (a 3000-line file goes from 9000 row containers to ~174, with exactly the same scroll range as a full render); switch between "before / after", and the after side is editable and savable |
70
+ | Commit graph | SVG branch/merge graph + commit details + click a commit to expand its changed files + open the diff |
71
+ | Multi-tab editor | Open/save/reload, dirty marks, several files at once; image preview, Markdown/HTML preview (source vs preview) |
72
+ | Syntax highlighting | VS Code **Dark+ / Light+** palettes; covers java/aidl/smali, c/c++, go, rust, js/ts, json, py, yaml, xml, html/css, md, sh, properties/rc/toml, diff …; a **fallback colorer** takes over when no grammar matches, so a file is never left monochrome. Honest caveat: kotlin / gradle / smali are coloured by the Java grammar (no dedicated package), which gets keywords, strings and comments right but does not split their own syntax |
73
+ | Find | In-editor `Ctrl+F` (`Aa` / `ab_` / `.*`, `3/17` count, ↑↓, `F3`/`Shift+F3`, `Esc`); **selected text is carried into the box by `Ctrl+F`**; the widget is pinned to the top-right and does not scroll away |
74
+ | Jump | Clicking a search hit opens the file at that line, centres it and **lights the landing line** (fades after ~2.6s); repeat jumps in the same file work |
75
+ | Terminals | Tabbed multi-terminal (`+` to create, `✕`/middle-click to close behind a confirm), across hosts; session ids are persisted so a page refresh reattaches to the running shells, and a session is kept for **10 minutes after its last socket goes away** (enough for a refresh or a browser-tab switch) before the remote shell is closed |
76
+ | Workspaces | Binding list + routing-mode dropdown + the dual-mode (local / remote) new-workspace wizard |
77
+
78
+ ### Hosts and transport
79
+
80
+ | Capability | Notes |
81
+ | --- | --- |
82
+ | Host management | CRUD + connectivity test; key / password auth; config in `$DSH_HOME/dsh-remote.json` (0600), independent from `dsh-ssh.json` |
83
+ | Key bootstrap | One click generates a dedicated keypair on password hosts and deploys `authorized_keys`, key auth from then on |
84
+ | Push pipeline | Remote arch/OS probe → version compare → skip-if-same / else push; the executable channel pushes a self-built helper. A failed deploy no longer retries on every remote file operation: it backs off from **5 seconds up to 5 minutes**, and the Settings "Push" button forces an immediate retry |
85
+ | Remote files | SFTP browse / read / write / create / delete / rename. Boundaries hardened: 0-byte files no longer deadlock, and every SFTP operation times out at 60s and releases its channel; **a single directory listing is capped at 20000 entries** (the first 20000 after sorting — directories first, then names — announced in the host log; the protocol carries no "truncated" field, so the UI has no marker) |
86
+ | Remote terminal | ssh2 PTY + WebSocket, xterm rendering (copy-paste/theme-follow/reconnect-on-refresh); **a reattach only takes over, it does not resize** (a page reload no longer shrinks a running shell to 80×24); an output backlog applies real backpressure to the far end; **frames are capped at 1 MiB**, and pastes past 64 Ki characters are chunked on code points by the client instead of dropping the connection |
87
+ | Concurrent channels | At most **6** remote channels per host at a time (no longer colliding with the far end's sshd `MaxSessions`); the 7th queues for up to **30 seconds** and then fails with `remote host <alias> is busy` instead of piling on connections |
88
+ | Timeouts and process cleanup | exec runs in its own session (`setsid --wait`); on timeout it sends TERM and **escalates to KILL after 3 seconds**, reaping the child processes with it; a host without `setsid --wait` is detected by a one-shot probe and falls back to the old behaviour, with a host-log line saying the timed-out remote command may still be running |
89
+ | Git graph | Remote repo status + branches + SVG commit graph + commit details (exec + frontend parsing, zero Host additions) |
90
+ | Import | One-click import from `~/.ssh/config` and from an existing `dsh-ssh.json` (read once, no coupling) |
91
+
92
+ ### Agent capabilities
93
+
94
+ | Capability | Notes |
95
+ | --- | --- |
96
+ | Agent tools | `remote_hosts` / `remote_exec` / `remote_read` / `remote_write` / `remote_ls` |
97
+ | **P2 transparent remote execution** | Native `read/write/edit/bash/glob/grep/jobs` inside remote workspaces run directly on the far end |
98
+ | **P3 remote @ references** | `@` in remote sessions completes remote files (fileReferences router: scoped live listings / bare queries via a remote rg index) |
99
+
100
+ ## Project structure
101
+
102
+ ```text
103
+ dsh_remote_vs/
104
+ ├── cordis.patch.yml # composition: plugin row + private local realm + routers + preset-remote
105
+ ├── src/
106
+ │ ├── index.ts # plugin entry: store/pool/worlds/routes/tools/preset-switch wiring
107
+ │ ├── protocol.ts # frontend/backend wire contract (route table + pure types, shared)
108
+ │ ├── paths.ts # four-piece addressing under $DSH_HOME (store/keys/build cache/alias dirs)
109
+ │ ├── store.ts # host + binding + tombstone persistence (0600 JSON, atomic writes)
110
+ │ ├── pool.ts # ssh2 long-lived pool (exec/shell/sftp/putFile, TOFU host keys)
111
+ │ ├── sftp.ts # remote file engine (SFTP windowed reads-writes/tree ops)
112
+ │ ├── keygen.ts # password-host key bootstrap (generate→deploy→flip)
113
+ │ ├── deploy.ts # push pipeline (probe/compare/ship/double-verify/manifest)
114
+ │ ├── importers.ts # ssh_config / dsh-ssh.json parsing importers
115
+ │ ├── routes.ts # /api/dsh-remote/* route table + terminal WebSocket upgrade
116
+ │ ├── terminal.ts # PTY session registry (reconnect-on-refresh/disconnect cleanup)
117
+ │ ├── tools.ts # the 5 Agent tool definitions
118
+ │ ├── http.ts / loopback.ts # JSON body IO + loopback trust fence
119
+ │ ├── git-format.ts # git command builders + pure frontend parsing (zero Host additions)
120
+ │ ├── git-ops.ts # the full remote git surface: status/diff/branch/remote/stash/commit/hunk patch builders + parsers
121
+ │ ├── fs-ops.ts # remote file-operation command builders (copy/move/remove/stat)
122
+ │ ├── search-ops.ts # remote content search (grep builder + NUL-separated parser + match splitting)
123
+ │ ├── p2/ # ── P2 transparent routing ──
124
+ │ │ ├── worlds.ts # world decisions (cwd key/alias/tombstones) + remote RPC/FS/proc contexts
125
+ │ │ ├── helper-rpc.ts # helper wire protocol over an ssh2 exec channel (handshake/heartbeat)
126
+ │ │ ├── router-fs.ts # transparent fs router (target→world map/path translation/policy rewrite)
127
+ │ │ ├── router-subprocess.ts # transparent process router (argv0 reduction/pwsh gate)
128
+ │ │ ├── ssh-proc.ts # ssh2 process runtime (setsid group kill/marker/stdin/spill)
129
+ │ │ ├── shell-bash.ts # routed bash executor (local verbatim/remote strips DSH_*)
130
+ │ │ ├── router-spill.ts # transparent spill router (session world key/remote dir)
131
+ │ │ ├── router-refs.ts # transparent @ reference router (agent world key, P3)
132
+ │ │ ├── file-refs.ts # remote @ search (live fs-ssh listings/rg index/ported stock scoring)
133
+ │ │ ├── bridge.ts # private-realm local publishes (localFs and other early services)
134
+ │ │ └── bridge-refs.ts # standalone refs bridge (local-refs needs workingDirectory: must split)
135
+ │ └── client/ # ── browser half (React, bundled into lib/client.js) ──
136
+ │ ├── index.tsx # surface decision (better-first/settle-boost/native fallback) + settings + directoryFlow takeover
137
+ │ ├── store.tsx # module-level state shared by both surfaces (useSyncExternalStore; open files/terminals/view live here)
138
+ │ ├── RemoteRoot.tsx # sidebar surface: VS Code activity rail (explorer/search/SCM/history/editor/terminal/workspaces)
139
+ │ ├── RemoteEditorView.tsx # conversation-view surface (the "remote editor" tab next to 对话/轨迹, full width)
140
+ │ ├── RemoteExplorer.tsx # explorer: tree + context menu + file ops + @ references + status marks
141
+ │ ├── SearchView.tsx # search panel: name / content modes + Aa/ab_/.* + grouped hits + jump
142
+ │ ├── EditorPane.tsx # editor pane: tabs + info bar (build stamp / tok count) + image/MD/HTML preview
143
+ │ ├── CodeEditor.tsx # CodeMirror wrapper: grammar table/theme/find widget/jump/fallback wiring
144
+ │ ├── scm/ # ── source control ──
145
+ │ │ ├── ScmPanel.tsx # changes/staged/untracked + per-file and per-hunk actions + commit box
146
+ │ │ ├── DiffView.tsx # side-by-side diff (top scrollbar/hunk buttons/editable after side)
147
+ │ │ ├── HistoryPanel.tsx # SVG commit graph + commit details + expanded changed files
148
+ │ │ ├── actions.ts # remote git action layer (over the exec channel)
149
+ │ │ ├── diff-model.ts # unified diff → side-by-side row projection (pure)
150
+ │ │ └── status-map.ts # status bucketing + tree marks (pure)
151
+ │ ├── vsc/ # ── VS Code skin and controls (zero new runtime deps) ──
152
+ │ │ ├── kit.tsx / vsc.css # icons/dialogs/context menu/segmented control + all styles (scoped to .rvs-root)
153
+ │ │ ├── editor-palette.ts # Dark+/Light+ palette + fixed class names (rvs-ck-*)
154
+ │ │ ├── editor-theme.ts # editor chrome theme (live light/dark swap)
155
+ │ │ ├── editor-langs.ts # file extension → CodeMirror grammar
156
+ │ │ ├── fallback-highlight.ts# coloring used when no grammar matched (via tokenize.ts)
157
+ │ │ ├── FindWidget.tsx # Ctrl+F find widget (pinned top-right)
158
+ │ │ ├── reveal-line.ts # jump + landing-line highlight
159
+ │ │ ├── tokenize.ts / markdown.tsx # read-only pane tokenizer / dependency-free markdown renderer
160
+ │ ├── NativeExplorer.tsx# native page type definition + seat body (no-sidebar fallback)
161
+ │ ├── composer.ts / use-remote-chat-target.ts / selection-popup.ts # remote @refs/selection-to-chat
162
+ │ ├── api.ts # typed fetch wrapper over /api/dsh-remote
163
+ │ ├── HostsView / WorkspacesView / WorkspaceWizard / TerminalView / XTerm / Dialog
164
+ │ ├── SettingsHosts.tsx # settings occupants (host mgmt/binding list, self-fetching)
165
+ │ ├── DirectoryFlow.tsx # native entry occupant (local dual-mode/remote wizard)
166
+ │ ├── RemoteDirectoryPicker.tsx# remote directory browser dialog
167
+ │ └── i18n.ts / theme.ts / ui.tsx # bilingual dictionary/theme signal/shared styles
168
+ ├── vendor/dsh-remote-helpers/ # helper seeds (shipped, seeded into cache at startup, see below)
169
+ ├── docs/ # local-only working docs (never pushed to GitHub)
170
+ │ ├── P2-PLAN.md # P2 architecture/phases/acceptance matrix (with per-item status)
171
+ │ └── MAINTENANCE.md # local-only (.gitignore), not shipped: DSH upgrade impact + helper procedure
172
+ ├── locale/ # bilingual plugin metadata
173
+ └── lib/ # build output (gitignored, restart after link install to take effect)
174
+ ```
175
+
176
+ `vendor/dsh-remote-helpers/` (committed) holds helper seeds: at startup the plugin spreads them into
177
+ `$DSH_HOME/dsh-remote-helpers/` (copy-if-absent, never overwrites), so fresh installs can push right away.
178
+ When a new helper is built, swap the 4-piece set (tarball + sha256 + helper.json + patch notes)
179
+ into vendor and commit (details live in the local-only `docs/MAINTENANCE.md` §4, which is gitignored and not distributed).
180
+
181
+ `.spike/` and `scripts/` (both gitignored, **never distributed with the repository**) hold the
182
+ development-time verification and self-check scripts: `.spike/` has the live-host ones — `p2-router.ts`
183
+ main regression (real remote + real Cordis + real router classes) and probes like `grep-probe.mjs`
184
+ (build with `tsconfig.scratch.json`), plus `model-check.mjs` / `reveal-check.mjs` /
185
+ `fallback-check.mjs` / `client-load-check.mjs` / `tool-schema-check.mjs` which need no host at all;
186
+ `scripts/` has the plain-node regression gates (`node scripts/check-*.mjs`, with `verify-lib.mjs`
187
+ checking the build artifact). Both are covered under "Development".
188
+
189
+ ## Remote workbench (VS Code style)
190
+
191
+ ![The sidebar's four views: files, search, source control, history](docs/media/sidebar-views.gif)
192
+
193
+ Both surfaces share one state module (`store.tsx`): **the sidebar "Remote" page** (a standalone tab when
194
+ `dsh-better-sidebar` is installed, otherwise the native right-Sidebar page) and **the conversation-view
195
+ "remote editor" tab** (next to 对话/轨迹, giving the code area full width).
196
+
197
+ - **Activity rail**: explorer / search / source control / commit graph / editor / terminal / workspaces.
198
+ A panel stays mounted after its first visit, so switching back neither refetches nor flashes. While it is
199
+ hidden it does **no remote work** (directory listings, the SCM stash/branch/remote queries and editor
200
+ image reads all wait for the next foreground pass), and visibility is MEASURED on the plugin root
201
+ (IntersectionObserver) instead of being taken from a host prop.
202
+ - **Editor**: tabs plus an info bar (path, language, **build stamp**, `tok N` coloured-token count,
203
+ saved state, reload/save). Those three marks exist for troubleshooting: the stamp answers "is the
204
+ browser running the bundle I just built?", `tok 0` answers "did a grammar match this extension?".
205
+ - **Find and jump**: `Ctrl+F` opens a widget pinned to the editor's top-right corner
206
+ (`position: fixed` anchored to the editor's rect, so no ancestor scroller can carry it away);
207
+ a selection is carried into it by `Ctrl+F`; a jump from the search results centres the line and
208
+ lights it up.
209
+ - **Terminals**: tabbed sessions with the session id persisted one by one, so a page refresh reattaches
210
+ to the shells that are still running; **a reattach only takes over, it does not resize** (a live shell is
211
+ never shrunk to 80×24 — the true size comes from the one tab that can measure itself, sent as a `resize`
212
+ frame); a session is kept for **10 minutes** after its last socket goes away before the remote shell is
213
+ closed (it used to be kept forever, so closing the browser left the shell holding an sshd session slot).
214
+ - **Known limits (stated honestly)**:
215
+ - There is **no public API to select the "remote editor" view**; the plugin can only click the shell's
216
+ own tab by its label and toasts a hint when that fails (`revealEditor()`).
217
+ - Untracked directories arrive as a **single `dir/` row**; clicking it **expands** the files inside
218
+ (one path-limited `git status -uall -- dir/`, so the cost tracks the directory, not the repository;
219
+ click again to collapse). An expansion is a snapshot of the moment it was opened — collapse and
220
+ re-expand to refresh it.
221
+ - Opening a file is capped at 8MiB (official helper constant); beyond that it is shown read-only.
222
+ - The find widget floats above the code (like VS Code) and takes no layout space.
223
+ - Output backpressure is **per shell**: one tab that cannot keep up pauses every tab attached to that
224
+ shell — a deliberate trade, preferring to hold the remote command back on behalf of the slowest
225
+ consumer over letting host memory grow without bound.
226
+ - A directory listing past 20000 entries is truncated, and since the protocol has no "truncated" field
227
+ the UI shows **no marker at all** — only the host log line
228
+ `listing <path> on <alias> shows 20000 of N entries`.
229
+
230
+ ## Session change review (Cursor style)
231
+
232
+ ![Session change review: the panel lists the files this session changed, opening one paints whole-line and intra-line differences, the top-right control steps through the changes, and hovering reverts a single change](docs/media/session-change-review.gif)
233
+
234
+ The **collapsible** panel above the composer (titled "本次改动") answers, per **session**, "what did this turn
235
+ change?". Its data comes from the plugin's own change ledger (`/api/dsh-remote/changes`), not from DSH's
236
+ `workspaceChanges` — the latter cannot see a routed session at all (the probe measured 58 remote sessions
237
+ with zero change events).
238
+
239
+ - **The panel**: one row per file (file-type icon + relative path + `+a −b` on the right), aligned left and
240
+ right with the composer card; click the header to collapse/expand, `⟳` forces an immediate rescan. With no
241
+ changes it says "this session has not changed any file yet"; when it cannot compute, it states the
242
+ **specific reason** (see the limits below) instead of handing you an empty list to guess about.
243
+ - **Clicking a file** opens it in the remote editor with change marks: added lines get a **green line
244
+ background**, deleted characters are drawn as an **inline struck-through chip** (a deleted character does
245
+ not exist in the new file, so it can only be an inserted widget), and the left gutter carries a change bar.
246
+ - **Jumping between change points**: the floating `n/N ↑↓` control at the editor's top-right
247
+ (`Alt+Shift+Cmd/Ctrl+↓/↑`) cycles through them; typing in the editor invalidates the marks and says
248
+ "position may be out of date".
249
+ - **Undo this change**: hover a changed line and click → remote `git apply -R`. When the context no longer
250
+ matches, the far end refuses outright (not one byte of the file changes); a tab with unsaved edits is
251
+ refused first, so a remote patch can never overwrite your buffer; on success the caret stays where it was
252
+ and the marks are re-read (they do not simply vanish because something was undone).
253
+ - **Follow the session's workspace**: the sidebar, the editor's dropdown and the file tree all follow the
254
+ workspace the **current session** lives in; after a manual switch they are not pulled back within that
255
+ session, and a new session follows again. The capability lives in the store, so it works even when **the
256
+ sidebar was never opened**; clicking a changed file is "show me this result", not a manual switch.
257
+ - **Repository directory**: the one the user picked under Source control → repository directory always wins;
258
+ when the session root is not a repository, a single candidate one level below is adopted automatically,
259
+ while several candidates produce an explicit hint to set it in Source control.
260
+
261
+ **Known limits (stated honestly)**:
262
+
263
+ - The baseline is the **session start**, not HEAD: untracked files that already existed before the session
264
+ began are not counted as this session's work.
265
+ - Tracked files refresh when a **turn ends** (the panel polls every 3 seconds as a backstop, so the numbers
266
+ usually catch up within a second or two); **new files** created in a shell are found by a **slow scan**
267
+ whose single-run budget is up to **3 minutes**, re-run at a self-tuning interval (at least 5 seconds,
268
+ naturally longer on a huge checkout). `⟳` forces an immediate rescan.
269
+ - A non-git workspace, or one holding several repositories with no repository directory set, shows the exact
270
+ reason (`no git repository at the workspace root, and N candidates below it — set the repository directory
271
+ in Source Control`) rather than an empty list.
272
+ - Binary and oversized files are marked "binary / too large" on the row and get no character-level diff.
273
+
274
+ ## P2 transparent remote execution (Windows native)
275
+
276
+ Single-Host mixed routing: native tools dispatch by which world the session cwd lands in — remote aliases go
277
+ over helper RPC (fs) / ssh2 (processes), local stays untouched; both share one Host and one GUI.
278
+
279
+ - Alias dirs: `$DSH_HOME/remote-ssh/aliases/<id>` (empty shells, routing keys only), auto-registered into the native workspace list as `<server> > <base>`
280
+ - Routing switch (dropdown at the top of Settings → Remote hosts; underneath it is profile patch `- id: remote-vs`, `config.routingMode`, volatile, no restart):
281
+ `off` (default, local passthrough) / `shadow` (logs, still local) / `enforce` (real routing)
282
+ - New workspace: the native "Add workspace…" entry is adopted (local/remote dual tab; remote leg = host→push→bind wizard)
283
+ - Presets: a `Remote` preset (bash toolset); under enforce, new remote sessions switch to it automatically (default preset only, never overrides an explicit choice)
284
+ - Ground truths (measured, not assumed):
285
+ - Remote `grep/glob` call remote `rg` through routing (both measured hosts ship rg 13; no remote rg means no hits)
286
+ - Whole reads >8MiB report `FS_TOO_LARGE` (official helper ceiling) — use streaming/paged reads; **writes are uncapped** (a 46MiB whole write passed live)
287
+ - Oversized process output lands in a Host temp spill file (`readFrom` returns tail + path, full text lives in the file — same as stock)
288
+ - After unbinding, new calls are explicitly refused (fail-closed tombstones), history stays readable
289
+ - Disconnects: in-flight calls are not replayed; new calls reconnect automatically
290
+ - Verify: `npx tsc -p tsconfig.scratch.json && node .spike/build/.spike/p2-router.js`
291
+ (real remote + real Cordis + real router classes, 61/61; P0 routing and GUI need a Host restart + smoke pass)
292
+ - Details: `docs/P2-PLAN.md` (§8 acceptance matrix with per-item status)
293
+
294
+ ## Self-built helper (P1)
295
+
296
+ Upstream never published helper binaries (all Release assets are empty), so this plugin builds its own:
297
+
298
+ - Build machine: WSL2 Ubuntu-22.04 (glibc 2.35, matching the remote targets), Node v24.21.0 + pnpm 11.28.5
299
+ - Sources: the `dsh-v0.2.1-alpha.2` tag (the **build baseline**: what must match is the wire protocol version, not the Host version — it is still 2, so a Host upgrade to alpha.4 or alpha.5 does not require a rebuild)
300
+ - Command: stock script `pnpm exec tsx scripts/build-exe-for-ssh-helper.ts --target=node24-linux-x64`
301
+ - Only deviation: no docker for the manylinux 2.28 rebuild, so a `DSH_GLIBC_CEILING`
302
+ env override was added (default stays 2.28); this build targets 2.35 (see `glibc-ceiling.patch` in the cache dir)
303
+ - Checks: stock `verify-ssh-helper-artifact.ts --sandbox=required`, all 8 green
304
+ - Cache: `$DSH_HOME/dsh-remote-helpers/` (`*.tar.gz` + `.sha256` + `.helper.json`
305
+ with binarySha256, feeding P2's helperHash)
306
+ - Full rebuild procedure for upgrades: the local-only `docs/MAINTENANCE.md` §4 (gitignored, not distributed)
307
+
308
+ ## Security model
309
+
310
+ - All `/api/dsh-remote/*` routes are loopback + same-origin only.
311
+ - Passwords / key passphrases are stored in plaintext in `dsh-remote.json` (0600) — same trust model as the existing SSH plugin.
312
+ - Pushed artifacts are sha256-verified both ways (tarball in, binary out); the helper install dir is separate from workspaces.
313
+ - Remote sessions may read explicit local absolute paths (the tool spill-file contract requires it); acceptable under the same model trust — see `docs/P2-PLAN.md` §4 note.
314
+
315
+ ## Development
316
+
317
+ ```sh
318
+ pnpm install
319
+ pnpm run typecheck
320
+ pnpm run build
321
+ dsh plugin --profile web add link:<absolute path of this directory>
322
+ ```
323
+
324
+ **How a change goes live: `pnpm run build`, then restart `dsh web` — for the host half and the client half alike.**
325
+ **`lib/` is committed.** The package has no `prepare` script (pnpm 12 refuses to run one for a git
326
+ dependency), so a `dsh plugin add github:…` user receives the repository as it is: **after editing
327
+ source you must `npm run build` and commit `lib/` with it** (`node scripts/verify-lib.mjs` checks that the
328
+ entries are present, committed and loadable; the self-check scripts live on the maintainer's machine only,
329
+ see below).
330
+ The Host reads plugin bundles into memory at startup (`initialBundleSnapshot`: it stats mtime/ctime/size into a
331
+ revision, then reads the bytes; the response carries `Cache-Control: public, max-age=31536000, immutable`), so a
332
+ **client-only** change (`src/client/**`) behaves exactly the same way: a browser refresh — `Ctrl+Shift+R`
333
+ included — only re-fetches the bytes captured at startup, under the same `rev`. How to tell:
334
+
335
+ 1. Each build injects a stamp (`MMDD-HHmm`) into `lib/client.js`, shown at the right of the editor info bar;
336
+ 2. Restart `dsh web` → refresh the page: **the stamp must change** (if it does not, you are still on the bytes
337
+ captured at startup);
338
+ 3. For colour issues, read `tok N`: `0` means no grammar matched the extension AND the fallback colorer
339
+ did not take over (the console line next to it names the file).
340
+
341
+ > The one exception is DSH's own development watcher (`pnpm run dev:web`, via `ClientModuleRegistry.rebuilt`);
342
+ > there is no watcher on a plugin user's side and `dsh web --help` has no watch option, so "restart" is the rule.
343
+
344
+ Self-checks (no host, no DSH, a few seconds each):
345
+
346
+ ```sh
347
+ node .spike/client-load-check.mjs # really evaluates the client modules: load-time errors tsc/bundler/other harnesses miss
348
+ node .spike/tool-schema-check.mjs # all 5 agent tools: run execute, then validate the result with the RUNTIME's own validator
349
+ node .spike/model-check.mjs # 55 checks: real git output → parsers, diff model, hunk patches, class↔CSS contract
350
+ node .spike/reveal-check.mjs # 5 checks: jump target, landing highlight, repeat jump in one editor
351
+ node .spike/fallback-check.mjs # 12 checks: the fallback colorer colours java/cpp/shell in a real DOM
352
+ node .spike/editor-dom-check.mjs # 12 checks: a real CodeMirror in jsdom emits colour spans for java/shell/cpp
353
+ node .spike/i18n-check.mjs # zh/en dictionary consistency: no missing key, no orphan, matching placeholders
354
+ node .spike/ref-check.mjs # 32 cases: assertRef accepts real revision suffixes, rejects every injection surface
355
+ node .spike/status-check.mjs # runs the generated git commands in a real shell to prove exit codes are no longer masked
356
+ node .spike/cache-check.mjs # incremental token cache vs whole-document tokenizing, 9 languages × 8 edit shapes
357
+ node .spike/layout-check.mjs # height chain + scrollbar contract: header rows stay pinned, the bar is wide enough to grab
358
+ node .spike/viewstate-check.mjs # scroll+caret round trip across a remount (stateful scrollTop: tells "restored" from "reset")
359
+ node .spike/tabswitch-check.mjs # real React drives three wirings: both ref-reading ones must fail, reporting must pass
360
+ node .spike/letter-check.mjs # status letters: untracked `?` no longer collides with conflict `U`, plus both render sites
361
+ ```
362
+
363
+ `.spike/layout-fixture.html` (generated by `build-fixture.mjs`) is the fixture for
364
+ measuring layout in a browser: it mechanically inlines the **host's real slot CSS**
365
+ and the **shipped vsc.css**, so after a CSS change you re-run
366
+ `node .spike/build-fixture.mjs` and can measure the height chain, the scroll
367
+ container and the scrollbar width for real. Pure-CSS defects are invisible to
368
+ tsc, to the bundler and to jsdom — this fixture is the only place they reproduce.
369
+
370
+ `markdown-check.ts` has to be compiled first (it imports a `.tsx`, which Node cannot load):
371
+ `npx tsc -p tsconfig.scratch.json && node .spike/build/.spike/markdown-check.js`.
372
+
373
+ > `.spike/` is git-ignored, so these checks are local only and run in no CI. **Run them before quoting any
374
+ > number** rather than copying a figure out of any file — one `model-check` count drifted across the README,
375
+ > the local maintenance doc and the review reports as 44 / 48 / 55, while the harness actually prints 55/55
376
+ > (its source holds 57 `check(` call sites, 2 of them inside loops and reachable only on failure, which is
377
+ > exactly why "57 call sites" and "55 checks run" differ by 2).
378
+
379
+ The live-host regression is still `npx tsc -p tsconfig.scratch.json && node .spike/build/.spike/p2-router.js`.
380
+ Before touching anything for a new DSH release, read the local-only `docs/MAINTENANCE.md` (§2 coupling points, §3 checklist) — it is gitignored, so a clone will not have it. The mandatory steps after a host-half change are `pnpm run verify:lib` plus the load self-check.
381
+
382
+ ## License
383
+
384
+ MIT © 2026 xiray-xia — see [LICENSE](./LICENSE).
385
+
386
+ The `vendor/` helper seed is built from DeepSeek Harness sources (MIT) at tag `dsh-v0.2.1-alpha.2`
387
+ with the stock build script; see "Self-built helper" above for the exact procedure and the single
388
+ documented deviation (`glibc-ceiling.patch` in the seed set).