dsh-ssh-tunnel 1.0.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,305 @@
1
+ # Changelog
2
+
3
+ 本项目的更改记录在此文件。
4
+
5
+ All notable changes to this project are documented in this file.
6
+
7
+ 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.1.0/);
8
+ 版本号遵循 [语义化版本](https://semver.org/lang/zh-CN/)。
9
+
10
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
11
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
12
+
13
+ ## [1.0.0] - 2026-09-28
14
+
15
+ ### 变更
16
+
17
+ - **声明 DSH 兼容性元数据。** `package.json` 现携带 `dsh.manifestVersion: 1`、`engines.dsh: ">=0.1.7-rc.2 <0.3.0-0"`(作者声明的兼容 DSH 范围,与 `engines.node` 并列;后者随当前生态主流提到 `>=20`),以及覆盖同一范围的 `@deepseek-ai/dsh-client-locale` peer——它同时覆盖 0.1.7 与 0.2 两条线(0.1.7-rc.2、0.2.0-rc.1、0.2.0 及后续 0.2.x),上界写作 `<0.3.0-0` 以免放行 0.3 的预发布。自 DSH 0.1.7-rc.1 起,插件闸门会把 `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` 的 peer 范围与运行时版本比对(未声明 DSH peer 则不施加约束)——此前本插件没有任何 DSH peer,在任何宿主上都被静默放行。`dsh.manifestVersion` 与 `engines.dsh` 按清单规范仍只是声明字段。
18
+
19
+ ### 修复
20
+
21
+ - **安装脚本装的是本仓库,且 node 前置检查与 `engines.node` 一致。** `scripts/install.sh` / `scripts/install.ps1` 的 `GITHUB_REPO` 默认值此前仍指向上游仓库 `thirsty5034/dsh-ssh-tunnel`,按脚本安装拿到的是上游最后发布的 v0.4.6(无 `manifestVersion`、无 DSH 兼容声明);现在默认本仓库。两处 node 检查此前只判断「命令存在」并提示 `>= 18`,现在按 `engines.node` 真正校验主版本(`>= 20`)。
22
+ - **本地同步脚本改走官方入口。** `scripts/sync-to-dsh.sh` 此前把文件复制进 `$DSH_HOME/local-plugins/dsh-ssh-tunnel` 并就地 `npm install`,而当前运行时不读取该目录(对整棵运行时搜索 `local-plugins` 零命中),等于静默无效;现在改为 `dsh plugin --profile <profile> add "link:<repo>"`,依赖解析与 `dsh.profile.bundles` 登记都交给 DSH。
23
+ - **开发期探针不再写死 Linux 路径、也不再必然失败。** `scripts/portal-probe.mjs` 此前用 `createRequire("/usr/local/lib/node_modules/@deepseek-ai/dsh/…")` 取 react,在任何未把 react 作为可解析包发布的 DSH 安装上都会直接抛错(本机 profile 与整棵运行时里都没有 react 包,前端自带打包副本);现在按候选根目录(`DSH_RUNTIME_ROOT`、profile 目录、常见全局安装路径)逐个尝试,都取不到时打印跳过说明并以 0 退出;标记断言也改为当前 UI 真实渲染的 `ssh-t-root` / `ssh-t-head`。
24
+ - **`@deepseek-ai/cordis` peer 对齐到验证过的版本。** 从 `^4.0.1` 改为 `^4.0.4`(运行时实装 4.0.4,0.2.0-rc.1 的 locale 包亦声明 `~4.0.4`),使声明不再早于实测版本。
25
+ - **`scripts/smoke-test.mjs` 现在会打进安装包。** `npm test` / `npm run check` 都会运行它,但 `files` 白名单把它漏掉了,于是任何从打包产物安装的副本都以 `MODULE_NOT_FOUND` 失败,声明的自检根本跑不起来。`scripts/portal-probe.mjs` 与 `scripts/sync-to-dsh.sh` 一并补齐,与 `dsh-git-forge` 采用同一打包策略(`scripts/` 下的脚本全部进包)。
26
+ - **本地路径守卫在工作区不是 `/workspace` 的宿主上恢复正常。** `isPathInsideRoots` 用 `root + '/'` 前缀对比平台原生形态的键(分隔符与大小写都敏感),于是 Windows 上连 `C:\ws\child` 在 `C:\ws` 内都被判为外部;`constrainToWorkspace` 还把 `/workspace` 写死,凡由它把守的地方(SFTP 的 `local_path` 与本地文件 API)都会拒绝真实工作区路径。现在包含判断在「统一分隔符、Win32 折叠大小写」的比较形态上进行,且守卫以宿主已知的项目工作区为根,于是 SFTP 上传/下载与本地文件 API(列目录、建目录、删除、重命名)都能接受真实工作区路径,相对本地路径也以该根为基准解析;`DSH_SSH_TUNNEL_WORKSPACE_ROOT` 作为运维覆盖保留,`/workspace` 仅作最后兜底。
27
+ - **自检用例可移植且自洽。** 夹具硬编码 POSIX 路径——装好依赖的 Windows 检出上固定挂 9 个用例,覆盖路径包含、会话与项目的匹配(夹具键是裸字符串而生产侧会过 `normalizeProjectKey`)、以及符号链接逃逸用例(其夹具目录根本不存在)。现在套件经 `normalizeProjectKey` 推导期望值、把路径守卫指向一次性根目录,并在无法创建符号链接的环境里优雅跳过该用例。
28
+
29
+ ### Changed
30
+
31
+ - **Declare DSH compatibility metadata.** `package.json` now carries `dsh.manifestVersion: 1`, `engines.dsh: ">=0.1.7-rc.2 <0.3.0-0"` (the author-declared compatible DSH range, sitting beside `engines.node`, which is raised to `>=20` like current ecosystem plugins), and a `@deepseek-ai/dsh-client-locale` peer over the same range — it covers the 0.1.7 and 0.2 lines alike (0.1.7-rc.2, 0.2.0-rc.1, 0.2.0 and later 0.2.x), with `<0.3.0-0` as the upper bound so that 0.3 prereleases stay out. Since DSH 0.1.7-rc.1 the plugin gate compares `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` peer ranges against the running runtime (missing peers impose no constraint) — without a DSH peer this plugin passed every host silently. `dsh.manifestVersion` and `engines.dsh` stay declarative, as the manifest spec defines them.
32
+
33
+ ### Fixed
34
+
35
+ - **The install scripts install this repository, and the node precheck matches `engines.node`.** Their `GITHUB_REPO` default still pointed at the upstream `thirsty5034/dsh-ssh-tunnel`, so running them fetched the upstream v0.4.6 — no `manifestVersion`, no DSH compatibility declarations; they now default to this repository. Both node checks only tested that the command exists and said `>= 18`; they now verify the major version against `engines.node` (`>= 20`).
36
+ - **The local sync script uses the official entry point.** `scripts/sync-to-dsh.sh` copied files into `$DSH_HOME/local-plugins/dsh-ssh-tunnel` and ran `npm install` there, but the current runtime never reads that directory (searching the whole runtime for `local-plugins` returns nothing), so it was silently a no-op; it now runs `dsh plugin --profile <profile> add "link:<repo>"`, leaving dependency resolution and `dsh.profile.bundles` registration to DSH.
37
+ - **The dev probe no longer hard-codes a Linux path or fails by construction.** `scripts/portal-probe.mjs` resolved react through `createRequire("/usr/local/lib/node_modules/@deepseek-ai/dsh/…")`, which throws on any DSH install that does not ship react as a resolvable package (this machine included — the web frontend bundles its own copy); it now tries a list of candidate roots (`DSH_RUNTIME_ROOT`, the profile dir, the usual global install roots) and, when none has react, prints a skip notice and exits 0. Its markup assertions now target the classes the tab tree actually renders (`ssh-t-root` / `ssh-t-head`).
38
+ - **The `@deepseek-ai/cordis` peer is aligned with the tested version.** It moves from `^4.0.1` to `^4.0.4` (the runtime ships 4.0.4 and the 0.2.0-rc.1 locale package declares `~4.0.4`), so the declaration no longer reaches back before what has been verified.
39
+ - **`scripts/smoke-test.mjs` now ships in the packed install.** `npm test` / `npm run check` run it, but the `files` allowlist omitted it, so any install from the packed artifact failed with `MODULE_NOT_FOUND` and the declared self-check could not run at all. `scripts/portal-probe.mjs` and `scripts/sync-to-dsh.sh` are shipped alongside it, following the same packaging strategy as `dsh-git-forge` (every script under `scripts/` goes into the tarball).
40
+ - **The local-path guard now works on hosts whose workspace is not `/workspace`.** `isPathInsideRoots` compared a `root + '/'` prefix against platform-native keys (separator- and case-sensitive), so on Windows even `C:\ws\child` inside `C:\ws` was judged outside; `constrainToWorkspace` additionally hard-coded `/workspace`, so real workspace paths were rejected wherever it guards (SFTP `local_path` handling and the local file APIs). Containment is now judged on a separator-unified, Win32-case-folded comparison form, and the guard takes the project workspace the host knows as its root, so SFTP upload/download and the local file APIs (list, mkdir, delete, rename) accept real workspace paths, with relative local paths resolving against that root; `DSH_SSH_TUNNEL_WORKSPACE_ROOT` remains an operator override and `/workspace` the last-resort fallback.
41
+ - **Smoke tests are platform-portable and hermetic.** The fixtures hard-coded POSIX paths — 9 tests fail on a Windows checkout with dependencies installed, covering path containment, session-to-project matching (raw fixture keys vs `normalizeProjectKey` on the production side), and the symlink escape case (its fixture directory did not exist). The suite now derives expectations through `normalizeProjectKey`, runs the path guard against a throwaway root, and skips the symlink case gracefully where symlink creation is unavailable.
42
+
43
+ ## [0.4.6] - 2026-09-15
44
+
45
+ ### 修复
46
+
47
+ - **终端不再报「无法加载 xterm」。** `require("@xterm/xterm")` 在 DSH 浏览器模块表里永远解析不到(只回答平台种子与已注册插件 row),而 0.4.4 移除 jsDelivr 回退后没有可用路径。插件现把 `@xterm/xterm@5.5.0` 与 `@xterm/addon-fit@0.11.0` 钉为运行时依赖,并将其 UMD/CSS 从同源白名单路由(`/dsh-ssh-tunnel/vendor/*`)下发;客户端用普通 `<script>`/`<link>` 标签加载,原来的模块表 require 路径仅作降级保留。永不访问第三方 CDN。
48
+
49
+ ### 安全
50
+
51
+ - **Vendor 路由白名单化。** `/dsh-ssh-tunnel/vendor/*` 只解析三个固定资源名;其它任何请求(含路径穿越)一律 404,且不会触碰文件系统。
52
+
53
+ ### Fixed
54
+
55
+ - **Terminal no longer fails with "无法加载 xterm".** `require("@xterm/xterm")` is unresolvable in the DSH browser module table (only platform seeds and registered plugin rows answer), and the 0.4.4 removal of the jsDelivr fallback left no working path. The plugin now pins `@xterm/xterm@5.5.0` + `@xterm/addon-fit@0.11.0` as runtime dependencies and serves their UMD/CSS from a same-origin allowlisted route (`/dsh-ssh-tunnel/vendor/*`); the client loads them with plain `<script>`/`<link>` tags, with the old module-table path kept only as a fallback. No third-party CDN is ever fetched.
56
+
57
+ ### Security
58
+
59
+ - **Vendor route is allowlisted.** `/dsh-ssh-tunnel/vendor/*` resolves exactly three asset names; anything else (including path traversal) is a 404 sent without touching the filesystem path.
60
+
61
+ ## [0.4.5] - 2026-09-09
62
+
63
+ ### 移除
64
+
65
+ - **不再提供键盘交互登录。** 只能在堡垒机网页/客户端里用的主机无法绑定到本插件。侧栏认证方式只保留密码与私钥。已保存的 `keyboardInteractive` 主机记录仍留在磁盘,但连接、存成 KBI、重连和 `SSHManager` 都会失败,并提示改为密码或私钥(`credential=unsupported`)。
66
+
67
+ ### Removed
68
+
69
+ - **Keyboard-interactive auth is no longer offered.** Bastion/MFA hosts that only work inside a vendor web/client cannot be bound here. The sidebar auth dropdown is password or private key only. Existing `keyboardInteractive` host records are kept on disk but Connect, save-as-KBI, reconnect, and `SSHManager` all fail with a message to switch the host to password or private key (`credential=unsupported`).
70
+
71
+ ## [0.4.4] - 2026-09-09
72
+
73
+ ### 安全
74
+
75
+ - **本地路径在解析符号链接后仍必须落在 `/workspace`。** 上传/下载/列举/建目录/删除/重命名会同时拒绝词法逃逸和指向工作区外的符号链接。删除符号链接只 unlink 链接本身,不跟随目标递归。
76
+ - **HTTP 会话 API 必须带 `projectPathKey`。** 省略该字段不再跳过项目检查。跨站 `Origin` 会被拒绝;`0.0.0.0` 不再当作回环。
77
+ - **损坏的 `secrets.json` 失败关闭。** 解析失败不再当成空库被下一次 `saveHost` 写回。JSON 采用临时文件 + rename 原子写入。
78
+ - **旧稿迁移幂等。** 标记文件 + host+port+username 去重,避免每次启动复制主机。
79
+ - **结果门禁覆盖写操作与 HTTP。** 写文件/上传/mkdir/重命名/删除/`send_input` 以及对应侧栏 API 在回报成功前复检授权。缺少授权谓词会抛错,不再静默放行。
80
+ - **拒绝键盘交互空应答。** 侧栏在没有应答 UI 时不会拨号;KBI 意外断开立即墓碑,不再空转重连。
81
+ - **xterm 仅使用打包模块。** 去掉 jsDelivr CSS/JS 回退。
82
+
83
+ ### 修复
84
+
85
+ - 重连与 `close` 竞态时会 `end()` 新客户端,不再挂到已删除记录上。
86
+ - `openSftp` 与 `ensureShell` 遵守与 exec 相同的超时/取消合同。
87
+
88
+ ### Security
89
+
90
+ - **Local paths stay under `/workspace` after symlink resolution.** Upload/download/list/mkdir/delete/rename now reject lexical escapes *and* outbound symlinks (for example `ln -s $DSH_HOME /workspace/.escape`). Deleting a symlink unlinks the link only; it does not walk the target.
91
+ - **HTTP session APIs require `projectPathKey`.** Omitting it no longer skips the project check on PTY/SFTP/`disconnect`. Cross-origin `Origin` headers are rejected; `0.0.0.0` is not treated as loopback.
92
+ - **Corrupt `secrets.json` fails closed.** Parse errors no longer become an empty store that the next `saveHost` would write back. JSON files are written atomically (`tmp` + rename).
93
+ - **Draft migration is idempotent.** A marker file plus host+port+username matching prevent duplicate hosts on every restart.
94
+ - **Result gates cover writes and HTTP.** `sftp_write_text` / upload / mkdir / rename / delete / `send_input` and the matching sidebar APIs re-check grants before reporting success. A missing authorization predicate throws instead of no-op.
95
+ - **keyboard-interactive empty answers are refused.** The sidebar will not dial KBI with no prompt UI; unexpected KBI drops tombstone immediately instead of a 25s reconnect loop.
96
+ - **xterm is bundled-only.** The previous jsDelivr CSS/JS fallback is removed.
97
+
98
+ ### Fixed
99
+
100
+ - Reconnect that wins a race against `close` now `end()`s the new client instead of attaching it to a deleted record.
101
+ - `openSftp` and `ensureShell` honor the same timeout/abort contract as exec.
102
+
103
+ ## [0.4.3] - 2026-09-08
104
+
105
+ ### 安全
106
+
107
+ - **在飞操作的结果门禁**。若 `SSHManager` 操作(exec / read_session / sftp_list / sftp_stat / sftp_read_text / sftp_download)执行期间该主机的项目授权被撤销,结果在交付前会复检:来自已撤销主机的输出被丢弃,调用以 `not authorized` 失败。
108
+ - **撤销授权后 `sftp_download` 不留任何本地文件**。若传输期间或之后授权被撤销,已写入的本地文件会被删除(含中途失败的部分写入),并拒绝交付结果。
109
+
110
+ ### 修复
111
+
112
+ - **终端面板不再静默轮询已死会话**。会话被关闭或主机授权在终端打开期间被撤销时,客户端停止轮询并显示「会话已断开」,而不再每 120ms 无反馈地空转请求。
113
+
114
+ ### Security
115
+
116
+ - **Result gate for in-flight operations.** If a host's project grant is revoked while an `SSHManager` operation (exec / read_session / sftp_list / sftp_stat / sftp_read_text / sftp_download) is running, the result is re-validated right before delivery: output from the revoked host is discarded and the call fails with `not authorized`.
117
+ - **`sftp_download` never leaves data behind after a revocation.** If the grant is revoked during or after a pull, the locally written file is removed (even a partially written one on a mid-transfer failure) and the result is refused.
118
+
119
+ ### Fixed
120
+
121
+ - **Terminal panel no longer silently polls a dead session.** When a session is closed or its host grant revoked while the terminal is open, the client stops polling and shows `Session disconnected` instead of hammering the server every 120ms with no feedback.
122
+
123
+ ## [0.4.2] - 2026-09-08
124
+
125
+ ### 安全
126
+
127
+ - **撤销某主机的项目授权后,已建立的隧道会话立即失效。** 此前只有新建连接和 `SSHManager` 工具会复检授权;已打开会话上的 shell / SFTP 操作、以及重连(含自动重连)在撤销授权后仍然可用。现在每次会话使用都会按该会话所属项目的授权重新校验(`requireLiveSession`),重连 / 自动重连拒绝复活已撤销主机,`setGrants` 与删除主机时还会主动关闭受影响的存活会话。
128
+
129
+ ### Security
130
+
131
+ - **Revoking a host's project grant now takes effect on established tunnel sessions.** Previously only new connections and the `SSHManager` tool re-checked grants; shell / SFTP operations over an already-open session and reconnection (including auto-reconnect) kept working after the grant was revoked. Now every session use re-validates the host against the session's project grants (`requireLiveSession`), reconnect / auto-reconnect refuse to resurrect a revoked host, and `setGrants` / host deletion actively close the affected live sessions.
132
+
133
+ ## [0.4.1] - 2026-09-08
134
+
135
+ ### 修复
136
+
137
+ - 从 `dsh.client.inject` 移除 **`@deepseek-ai/dsh-client-runtime`**。该包在 DSH 0.1.2 已删除(社区升级卡 `DSH-0.1.2-A1-25`);保留该幻影依赖可能让 client 装配行在 0.1.2 宿主上 pending。保留 `@deepseek-ai/dsh-client-locale`(提供 `ctx.locale`)。
138
+
139
+ ### Fixed
140
+
141
+ - Remove **`@deepseek-ai/dsh-client-runtime`** from `dsh.client.inject`. The package was deleted in DSH 0.1.2 (community upgrade card `DSH-0.1.2-A1-25`); keeping the phantom in `inject` could leave the client assembly row pending on 0.1.2 hosts. `@deepseek-ai/dsh-client-locale` is retained (provides `ctx.locale`).
142
+
143
+ ## [0.4.0] - 2026-09-01
144
+
145
+ ### 新增
146
+
147
+ - 会话断开后保留**墓碑**(重连 + 关闭),无 TTL。意外断开自动重连最多 3 次,同一 `session_id`。
148
+ - ssh2 **keepalive** 30s × 3,半开连接会变成真正断开,而不再假活。
149
+ - `SSHManager` 支持 `timeout_ms`(1s–300s),覆盖 exec / SFTP / shell。默认:exec 与 SFTP 元数据 30s,SFTP 传输 120s;整次调用天花板 300s。Stop / `exec.signal` 会中止并关掉 channel。
150
+
151
+ ### 变更
152
+
153
+ - `reuse_or_create` **复活**同主机最老墓碑,不再另开一条活会话。重连失败则保持断线并让这次工具失败。
154
+ - 显式已断开的 `session_id` 立即失败(不再卡在 ssh2 上)。重连中的 id 会等到活过来。
155
+ - 键盘交互主机不能原地重连,需关闭后在 UI 重新 Connect。
156
+
157
+ ### Added
158
+
159
+ - Disconnected sessions stay as **tombstones** (Reconnect + Close). No TTL. Unexpected drops auto-reconnect up to 3 times on the same `session_id`.
160
+ - ssh2 **keepalive** 30s × 3 misses so half-open TCP becomes a real disconnect instead of a false-alive row.
161
+ - `SSHManager` `timeout_ms` (1s–300s) for exec, SFTP, and shell. Defaults: exec/SFTP metadata 30s, SFTP transfer 120s. Host ceiling 300s. Stop/`exec.signal` aborts in-flight work and destroys the channel.
162
+
163
+ ### Changed
164
+
165
+ - `reuse_or_create` **revives** the oldest disconnected session for that host instead of opening a parallel live row. Reconnect failure leaves the tombstone and fails the tool call.
166
+ - Explicit `session_id` that is disconnected fails immediately (no ssh2 hang). Reconnecting ids wait for live.
167
+ - keyboard-interactive hosts cannot reconnect in place; close and Connect from the UI.
168
+
169
+ ## [0.3.11] - 2026-08-26
170
+
171
+ ### 修复
172
+
173
+ - **适配 dsh-better-sidebar ≥ 0.16**:终端 / SFTP 中央面板重新全屏居中。0.16.x 给侧栏面板容器加了 `contain: layout`(并引入统一面板宿主 / 自由窗口),按 CSS Containment 规范该容器成为**后代 `position:fixed` 元素的包含块**——此前内联渲染的全屏覆盖层因此被「关进」右侧窄条。现改为通过 React portal 挂载到 `document.body`,不再依赖任何祖先的定位假设(右键菜单、对话框随覆盖层一起迁移);react-dom 缺失时回退旧内联渲染。
174
+
175
+ ### Fixed
176
+
177
+ - **Compatibility with dsh-better-sidebar ≥ 0.16**: the terminal / SFTP center panel is full-screen centered again. 0.16.x adds `contain: layout` to the sidebar panel containers (plus the unified panel host / free windows); per CSS Containment such a container becomes the **containing block for descendant `position:fixed` elements**, which trapped the previously inline-rendered viewport overlay inside the narrow right rail. The overlay now mounts through a React portal into `document.body` and no longer depends on ancestor positioning assumptions (context menu and dialogs travel with it); falls back to the legacy inline render if react-dom is unavailable.
178
+
179
+ ## [0.3.10] - 2026-08-18
180
+
181
+ ### 修复
182
+
183
+ - 主机库 / 隧道列表:过长 endpoint(如 IPv6)不再把 **编辑/删除** 等按钮挤出可视区——文案可换行,操作区始终保留
184
+
185
+ ### Fixed
186
+
187
+ - Host library / tunnel rows: long endpoints (IPv6) no longer push **Edit/Delete** (and other actions) off-screen — text wraps, action cluster stays visible
188
+
189
+ ## [0.3.9] - 2026-08-18
190
+
191
+ ### 修复
192
+
193
+ - **SSHManager 作为全局模型工具正确注册**(不再依赖 out-of-tree 解析 `@deepseek-ai/dsh-tools` 的 `defineTool`;改用与 modlens 相同的裸 JSON Schema 定义)。此前标准对话工具列表中看不到该工具。
194
+
195
+ ### Fixed
196
+
197
+ - **SSHManager registers as a global model tool** without importing `@deepseek-ai/dsh-tools` (raw JSON-Schema definition, same pattern as modlens). Out-of-tree `defineTool` resolution left the tool missing from standard chat catalogs.
198
+
199
+ ## [0.3.8] - 2026-08-18
200
+
201
+ ### 修复
202
+
203
+ - Host key 固定:使用 ssh2 `hostHash: sha256` 并以 hex 摘要入库(旧版 `String(Buffer)` 条目作废并重新提示信任)
204
+ - 信任 UI 展示 SHA256 指纹;`health.version` 与 `package.json` 对齐
205
+ - **Connect 不再静默写入项目授权**;须先在「项目授权」勾选主机
206
+
207
+ ### Fixed
208
+
209
+ - Host key pinning: use ssh2 `hostHash: sha256` and store hex digests (legacy `String(Buffer)` entries are ignored and re-prompted)
210
+ - Trust UI shows the SHA256 fingerprint; `health.version` tracks `package.json`
211
+ - **Connect no longer auto-writes project grants**; host must already be authorized under Project access
212
+
213
+ ## [0.3.7] - 2026-08-18
214
+
215
+ ### 变更
216
+
217
+ - README(中/英):明确 **致谢 / 参考来源** — 产品形态参考开源 [LiveAgent](https://github.com/thirsty5034/LiveAgent);DSH 原生实现,非 fork
218
+
219
+ ### Changed
220
+
221
+ - README (EN/ZH): explicit **Credits / prior art** — product shape informed by open-source [LiveAgent](https://github.com/thirsty5034/LiveAgent); DSH-native reimplementation, not a fork
222
+
223
+ ## [0.3.6] - 2026-08-16
224
+
225
+ ### 变更
226
+
227
+ - 对齐 dsh-better-sidebar 的公开仓库结构
228
+ - 增加 `scripts/install.sh` / `install.ps1`(默认 GitHub;`--from npm` 预留)
229
+ - package.json:`repository` / `homepage` / `bugs` / `publishConfig`
230
+ - 增加简体中文 README / CHANGELOG,与英文版并列入口
231
+
232
+ ### Changed
233
+
234
+ - Public GitHub repo layout aligned with dsh-better-sidebar
235
+ - `scripts/install.sh` / `install.ps1` (GitHub default; `--from npm` ready)
236
+ - package.json: `repository` / `homepage` / `bugs` / `publishConfig`
237
+ - Add Simplified Chinese README / CHANGELOG alongside English
238
+
239
+ ## [0.3.5] - 2026-08-16
240
+
241
+ ### 新增
242
+
243
+ - 客户端 i18n(对齐 dsh-better-sidebar):`ctx.locale.register("sshTunnel", zh|en)`、`t(key)`、语言切换实时刷新
244
+
245
+ ### 修复
246
+
247
+ - `zh.footerHint` 误调用 `t()` 导致插件加载 TDZ 报错
248
+
249
+ ### Added
250
+
251
+ - Client i18n following dsh-better-sidebar: `ctx.locale.register("sshTunnel", zh|en)`, `t(key)`, live switch via `useSyncExternalStore`
252
+
253
+ ### Fixed
254
+
255
+ - Plugin load TDZ when `zh.footerHint` incorrectly called `t()` during dictionary init
256
+
257
+ ## [0.3.4] - 2026-08-16
258
+
259
+ ### 变更
260
+
261
+ - 发布前规范化:包元数据、MIT LICENSE、README、CHANGELOG
262
+ - 抽取 `lib/shared/*` 纯函数与离线冒烟测试
263
+ - `shellRead` 默认只返回增量 `chunk`(`full: true` 才给全量)
264
+ - 主题 token 配色;SFTP 面板内对话框(0.3.x)
265
+ - 包描述改为中立表述,不再带第三方产品的营销文案
266
+
267
+ ### 修复
268
+
269
+ - 主机库删除恢复确认
270
+ - 样式表缓存刷新
271
+
272
+ ### Changed
273
+
274
+ - Publish prep: package metadata, MIT LICENSE, README, CHANGELOG
275
+ - Extract shared pure modules (`lib/shared/*`) with offline smoke tests
276
+ - `shellRead` returns incremental `chunk` by default (omit full buffer unless `full: true`)
277
+ - Theme-safe UI tokens; in-panel dialogs for SFTP prompts (0.3.x)
278
+ - Neutral package description (no third-party product marketing)
279
+
280
+ ### Fixed
281
+
282
+ - Host library delete asks for confirmation again
283
+ - Style sheet cache bust when CSS updates
284
+
285
+ ## [0.3.0] - 2026-08-16
286
+
287
+ ### 新增
288
+
289
+ - 中央终端(xterm)与双栏 SFTP
290
+ - 项目级授权、SSHManager、OpenSSH 扫描
291
+
292
+ ### Added
293
+
294
+ - Center overlay terminal (xterm) and dual-pane SFTP
295
+ - Project-scoped grants, SSHManager tool, OpenSSH scan
296
+
297
+ ## [0.1.0] - 2026-08-16
298
+
299
+ ### 新增
300
+
301
+ - 初始永久插件骨架
302
+
303
+ ### Added
304
+
305
+ - Initial permanent plugin scaffold
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-ssh-tunnel contributors
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.md ADDED
@@ -0,0 +1,173 @@
1
+ <p align="center"><strong>简体中文</strong> | <a href="README_en.md">English</a></p>
2
+
3
+ <div align="center">
4
+ <h1>dsh-ssh-tunnel</h1>
5
+ <p>DeepSeek Harness 的多机 SSH 工作台:主机库、按项目授权、右侧栏里的终端与双栏 SFTP。</p>
6
+ <p>模型用 <strong>SSHManager</strong> 工具执行命令、传文件;你在侧栏管主机与授权。<strong>密钥不进模型上下文</strong>。</p>
7
+
8
+ <p>
9
+ <a href="https://github.com/OMSociety/dsh-ssh-tunnel/releases"><img src="https://img.shields.io/badge/version-1.0.0-4f6ef7" alt="Version"></a>
10
+ <a href="https://github.com/deepseek-ai/dsh"><img src="https://img.shields.io/badge/DSH-%3E%3D0.1.7--rc.2_%3C0.3.0--0-4f6ef7" alt="DSH"></a>
11
+ <a href="LICENSE"><img src="https://img.shields.io/github/license/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="License"></a>
12
+ <a href="https://github.com/OMSociety/dsh-ssh-tunnel/stargazers"><img src="https://img.shields.io/github/stars/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Stars"></a>
13
+ <a href="https://github.com/OMSociety/dsh-ssh-tunnel/issues"><img src="https://img.shields.io/github/issues/OMSociety/dsh-ssh-tunnel?color=4f6ef7" alt="Issues"></a>
14
+ </p>
15
+
16
+ <a href="#这是什么">这是什么</a> • <a href="#核心特性">核心特性</a> • <a href="#快速开始">快速开始</a> • <a href="#侧栏">侧栏</a> • <a href="#模型工具">模型工具</a> • <a href="#安全">安全</a> • <a href="#开发">开发</a> • <a href="#许可证与作者">许可证与作者</a>
17
+ </div>
18
+
19
+ > **提示:**本仓库是 `dsh-ssh-tunnel` 的维护主线,在 [OMSociety 仓库](https://github.com/OMSociety/dsh-ssh-tunnel) 独立延续(2026-09-28 起脱离 fork 网络)。上游项目与代码作者是 [thirsty5034/dsh-ssh-tunnel](https://github.com/thirsty5034/dsh-ssh-tunnel)(MIT,见 [LICENSE](LICENSE))。修复与问题反馈都在本仓库处理。
20
+
21
+ ## 这是什么
22
+
23
+ **dsh-ssh-tunnel** 是 [DeepSeek Harness](https://github.com/deepseek-ai/dsh) 的社区插件,挂在右侧栏宿主 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 上:把多台 SSH 主机收进一个**主机库**,按项目授权,然后在中央面板里开**交互式终端**(xterm)或**双栏 SFTP**。
24
+
25
+ 它**不会**把全局 `fs` / `subprocess` 换成一个远程盘:远端操作只发生在你显式调用的 `SSHManager` 工具与面板里,本地文件与远端文件始终是两侧分明的东西。
26
+
27
+ 配套插件是 [dsh-git-forge](https://github.com/OMSociety/dsh-git-forge)(Git 凭据与 push 策略)。
28
+
29
+ ## 参考来源
30
+
31
+ **产品形态与部分 UX 参考开源项目 [LiveAgent](https://github.com/thirsty5034/LiveAgent)**(多机 SSH 主机库、按项目授权、侧栏隧道管理、中央终端 / SFTP 等)。
32
+
33
+ 本仓库是 **DSH 原生实现**(Cordis host/client、`dsh-better-sidebar` Tab、`SSHManager` 工具、DSH 本地密钥布局),**不是** LiveAgent 的 git fork,也**不**内嵌 LiveAgent 源码。对照设计时请遵守 LiveAgent 自身许可证。
34
+
35
+ ## 核心特性
36
+
37
+ | 特性 | 说明 |
38
+ |---|---|
39
+ | **主机库** | 主机增删改、支持导入 OpenSSH 配置;密码 / 私钥(含口令),密钥只存宿主侧 |
40
+ | **按项目授权** | 授权 key 是 **DSH 会话工作区**(`projectPathKey`);**连接前必须先授权**,Connect 不会自动写入授权 |
41
+ | **`SSHManager` 工具** | 远端 exec、SFTP 列举/读写/增删改、上传下载、交互式会话读写;`reuse_or_create` / `new` / `require_existing` 会话策略 |
42
+ | **中央面板** | 交互式终端(xterm)与双栏 SFTP:左侧是项目工作区、右侧是远端,可直接上传下载 |
43
+ | **断线可续** | 掉线会话留作墓碑,可**重连**(同一 id)或关闭;意外断开自动重连最多 3 次,ssh2 keepalive 30s × 3 |
44
+ | **主机密钥指纹** | 以 SHA256 hex 存入 `known_hosts.json`,首次连接或指纹变更时在侧栏确认并展示指纹 |
45
+ | **本地路径守卫** | 上传 / 下载 / 列举 / 删除的本机路径限制在**项目工作区根**内,词法 **与** realpath 双重校验 |
46
+ | **界面双语** | 侧栏与面板随 DSH 界面语言在中文 / 英文间即时切换 |
47
+
48
+ ## 快速开始
49
+
50
+ **方式一:命令行直装(推荐)**
51
+
52
+ ```powershell
53
+ # 1) 先停掉 dsh web(运行中的服务会锁住依赖,装完再起)
54
+ dsh plugin --profile web add "github:OMSociety/dsh-ssh-tunnel"
55
+ # 2) 重新启动 dsh web
56
+ ```
57
+
58
+ 想复现某次安装就钉住 ref:在仓库地址后加 `#<tag 或提交 sha>`。
59
+
60
+ **方式二:一键脚本**
61
+
62
+ ```sh
63
+ curl -fsSL https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.sh | bash
64
+ ```
65
+
66
+ ```powershell
67
+ irm https://raw.githubusercontent.com/OMSociety/dsh-ssh-tunnel/main/scripts/install.ps1 | iex
68
+ ```
69
+
70
+ 脚本除安装外还会把 profile 的 `minimumReleaseAgeExclude` 补上本插件、校验 `dsh.profile.bundles` 确实写入、清掉旧版手写在 profile `cordis.patch.yml` 里的挂载(先加 `--dry-run` 可只看计划不动手)。
71
+
72
+ > **提示:**装好后**刷新一下浏览器页面**,右侧栏才会出现「SSH 隧道」入口——只重启宿主不够,客户端产物是页面加载时取的。
73
+
74
+ **装完怎么用**
75
+
76
+ 1. 打开右侧栏「SSH 隧道」→ **主机库** 添加一台主机(密码或私钥)
77
+ 2. 切到 **项目授权**,给当前项目勾上这台主机并保存(**不授权连不上**,这是有意为之)
78
+ 3. 回 **隧道会话** 点 Connect;首次连接会提示确认主机密钥指纹
79
+ 4. 点 **终端** 得到一个交互式 shell,或点 **SFTP** 打开双栏面板
80
+ 5. 让模型用 `SSHManager action=exec` 跑条命令、用 `sftp_list` 看目录、`sftp_upload` / `sftp_download` 传文件,验证整条链路
81
+
82
+ ## 侧栏
83
+
84
+ 侧栏只有一个 Tab,内部三页:
85
+
86
+ | 页面 | 作用 |
87
+ |---|---|
88
+ | **项目授权** | 当前项目允许使用的主机 |
89
+ | **主机库** | 主机增删改、OpenSSH 配置扫描导入 |
90
+ | **隧道会话** | 连接 / 断开,打开终端或 SFTP;掉线会话留墓碑,可重连或关闭 |
91
+
92
+ ## 模型工具
93
+
94
+ `SSHManager` 的动作按用途分组:
95
+
96
+ | 动作 | 作用 |
97
+ |---|---|
98
+ | `list_hosts` / `list_sessions` | 列出主机库、列出当前会话 |
99
+ | `create_session` / `close_session` | 建立或关闭会话(配合 `session_strategy`) |
100
+ | `exec` | 远端执行命令(`command`、`cwd`、`timeout_ms`) |
101
+ | `sftp_list` / `sftp_stat` / `sftp_read_text` / `sftp_write_text` | 远端目录、元数据与文本读写 |
102
+ | `sftp_mkdir` / `sftp_rename` / `sftp_delete` | 远端建目录、重命名、删除 |
103
+ | `sftp_upload` / `sftp_download` | 在项目工作区与远端之间传文件 |
104
+ | `read_session` / `send_input` / `resize_session` | 交互式会话的读取、输入与窗口尺寸 |
105
+
106
+ - 会话策略:`reuse_or_create`(默认,会复活同主机已断开的会话)、`new`、`require_existing`,或显式传 `session_id`
107
+ - `timeout_ms` 取值 1000–300000,作用于 exec / SFTP / shell;默认 exec 与 SFTP 元数据 30s,SFTP 传输 120s
108
+ - 认证方式只有**密码**或**私钥**;键盘交互式认证(含堡垒机网页 MFA)不支持
109
+
110
+ ## 安全
111
+
112
+ - 工具结果与列表 API 不返回 password / PEM / 口令
113
+ - 本地上传、下载、列举、删除的路径限制在**项目工作区根**内(由宿主按当前会话工作区解析,不假设固定挂载点):词法 **与** realpath 双重校验,越界路径与指向工作区外的符号链接一律拒绝
114
+ - 主机密钥以 **SHA256 hex** 存入 `known_hosts.json`;首次连接或指纹变更时在侧栏确认(展示指纹)
115
+ - HTTP API 限制在 loopback / trusted hosts;浏览器 `Origin` 必须匹配;会话 API 必须带 `projectPathKey`
116
+ - 优先使用密钥登录;若 `secrets.json` 可能泄露请立即轮换凭据
117
+ - 键盘交互式认证已下线:请把这类主机改为密码或私钥
118
+
119
+ ## 数据放在哪
120
+
121
+ `$DSH_HOME/ssh-tunnel/`(目录 `0700`):
122
+
123
+ | 文件 | 内容 |
124
+ |---|---|
125
+ | `hosts.json` | 主机元数据(不含密钥明文) |
126
+ | `secrets.json` | 密码 / PEM / 口令(`0600`) |
127
+ | `grants.json` | `projectPathKey → hostIds[]` |
128
+ | `known_hosts.json` | 已信任的主机密钥指纹 |
129
+
130
+ ## 界面国际化
131
+
132
+ - 命名空间:`sshTunnel`;字典 `zh` / `en` 注册到 `ctx.locale`
133
+ - Tab 标题与面板随 DSH 界面语言即时切换
134
+ - 宿主侧 `SSHManager` 的描述保持英文(面向模型)
135
+
136
+ ## 开发
137
+
138
+ ```powershell
139
+ npm test # node --test:自检脚本全量回归
140
+ npm run check # 语法 + 自检
141
+ bash scripts/sync-to-dsh.sh # 以 link: 方式把本仓库接入 web profile(开发用)
142
+ bash scripts/install.sh --dry-run # 只看安装计划,不动 profile
143
+ node scripts/portal-probe.mjs # 客户端 Tab 渲染探针(能解析到 react 时生效,否则明确跳过)
144
+ ```
145
+
146
+ 目录与「改东西去哪」:
147
+
148
+ ```text
149
+ lib/index.js 宿主入口:工具注册、/dsh-ssh-tunnel/api 路由、授权与路径守卫、xterm 资产下发
150
+ lib/client.js 客户端 bundle(已入库;dsh plugin add 不做构建)
151
+ lib/session.js 会话生命周期:连接、keepalive、掉线墓碑与自动重连
152
+ lib/shared/ 宿主与客户端共用纯函数:path / host-key / host-summary / http-trust / persist /
153
+ session-auth / session-policy / shell-buffer / vendor
154
+ scripts/ install.sh · install.ps1 · sync-to-dsh.sh · smoke-test.mjs · portal-probe.mjs
155
+ cordis.patch.yml 包内 bundle patch,CLI 据此写入 dsh.profile.bundles
156
+ ```
157
+
158
+ ### xterm 加载
159
+
160
+ `@xterm/xterm@5.5.0` 与 `@xterm/addon-fit@0.11.0` 是钉死的运行时依赖。宿主把它们的 UMD 与 CSS 从同源白名单路由下发(`/dsh-ssh-tunnel/vendor/xterm.js|addon-fit.js|xterm.css`),客户端用普通 `<script>` / `<link>` 加载。没有 CDN 回退,也不依赖模块表。
161
+
162
+ ## 支持与致谢
163
+
164
+ - 如果这个插件对你有帮助,欢迎点亮 Star;有问题或建议请提 [Issue](https://github.com/OMSociety/dsh-ssh-tunnel/issues) 或 [Pull Request](https://github.com/OMSociety/dsh-ssh-tunnel/pulls)。
165
+ - 变更记录见 [CHANGELOG](CHANGELOG.md)。
166
+ - [LiveAgent](https://github.com/thirsty5034/LiveAgent):产品形态与部分 UX 的参考来源(见上文「参考来源」)
167
+ - [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar):右侧栏宿主与 Tab 契约
168
+ - [dsh-git-forge](https://github.com/OMSociety/dsh-git-forge):同门插件,Git 凭据与 push 策略
169
+ - [DeepSeek Harness](https://github.com/deepseek-ai/dsh):插件、工具与 agent shell 的宿主
170
+
171
+ ## 许可证与作者
172
+
173
+ [MIT](LICENSE)。上游项目与代码作者 [@thirsty5034](https://github.com/thirsty5034);本仓库的维护与新增部分 © 2026 [@OMSociety](https://github.com/OMSociety)。