@zhangfengshun/dsh-remote-ssh 2.4.4 → 2.4.6
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 +21 -0
- package/README.md +6 -4
- package/README_EN.md +6 -4
- package/lib/client.js +84 -4
- package/lib/index.js +16 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
本文件的版本号与 `package.json` 的 `version` 保持一致。每个版本对应一个 Cordis Package 快照(`pkg-N`)。
|
|
4
4
|
|
|
5
|
+
## [2.4.6] — 「文件」页签树根显示远程目录名(issue #9)
|
|
6
|
+
### 修复
|
|
7
|
+
- **树根标签不再显示本地镜像目录 ID**(感谢 @Linhaojing 的链路定位):better-sidebar「文件」页签的树根标签取自会话 cwd(= 本地镜像路径)的 basename,于是显示成 `wmu3sxe24jpvg` 之类的镜像目录名。这条链路**不经过任何 `fs.*` 路由**,且 Host 侧 `session.cwd` 虽已返回 `root` 字段但客户端只消费 `cwd`(`api.sessionCwd(...).then(r => setFetchedCwd(r.cwd))`,另有 `useSessionCwd` 直接读客户端会话列表的 `cwd`),因此只能在渲染层替换文本。现由客户端取 `listWorkspaces` 的 `mirrorPath`/`remotePath` 建立「镜像 basename → 远程 basename」映射,把树根那一行换成**远程目录名**(如 `IB_Robot`),并给该行加 `title` 显示完整远程路径(如 `~/lhj/IB_Robot`)。
|
|
8
|
+
- **定位不依赖构建期哈希类名**:根行是树里唯一带内联 `padding-left:6px` 的行,仅当该行内纯文本节点恰好等于某远程工作区的镜像 basename 时才替换——同名子条目、本地工作区、无根行标志(上游改版)时一律保守不动;
|
|
9
|
+
- **同步替换 + 幂等自愈**:与设置导航图标同样在 MutationObserver 回调(微任务、绘制前)内完成,不会先闪一下镜像 ID;React 重渲染还原文本时由 1s 复检与 30s 工作区轮询恢复。
|
|
10
|
+
|
|
11
|
+
### 测试
|
|
12
|
+
- 新增 `tests/tree-root-label.test.mjs`:从 `lib/client.js` 提取**真实**实现配极简 DOM 桩执行,覆盖 7 组场景共 17 条断言(替换、假阳性防护、幂等、React 还原自愈、本地工作区、同名边界、样式容错、接线检查)。
|
|
13
|
+
|
|
14
|
+
## [2.4.5] — 集成终端落在工作区远程目录(issue #7)+ 密码认证回退提示
|
|
15
|
+
### 修复
|
|
16
|
+
- **「终端」页签现在落在工作区对应的远程目录**(感谢 @Linhaojing 的完整定位与验证矩阵):此前生成的 `dsh-remote-shell.js` 只拼 host/user/port/keyPath/proxyJump,`ssh -tt` 不带远程命令,交互式 shell 落在远程 `$HOME`;同一会话里出现「文件树在工作区目录、终端在 $HOME」的割裂。现在 wrapper 读取标记文件里**已有的** `remotePath` 并追加远程命令:
|
|
17
|
+
- `~`/`~/x` 的展开交给**远端** shell(`$HOME/...`)——本地 homedir 与远程不同,本地展开必错;
|
|
18
|
+
- `cd … 2>/dev/null || cd "$HOME"`:目标目录被删/改名时回退,终端仍可打开;
|
|
19
|
+
- `exec "${SHELL:-/bin/bash}" -l`:尊重远端登录 shell(zsh/fish 等),保持单进程与退出码语义;
|
|
20
|
+
- 无 `remotePath` 的旧标记文件不追加命令,行为与 2.4.5 之前完全一致(向后兼容)。
|
|
21
|
+
- **密码认证的远程工作区现在会明确提示**:集成终端仅支持密钥认证,此前静默回退为本地 shell,用户会误以为已连上远程;现在向 stderr 打印中英双语提示(本地工作区与密钥认证路径不受影响)。
|
|
22
|
+
|
|
23
|
+
### 测试
|
|
24
|
+
- 新增 `tests/wrapper-terminal.test.mjs`:从 `lib/index.js` 提取**真实**的 wrapper 生成文本,用 CJS runner 桩掉 `child_process` 后捕获实际 spawn argv,覆盖 5 组场景(`~/子路径`、`~`、绝对路径、无 remotePath、非密钥认证)共 16 条断言。
|
|
25
|
+
|
|
5
26
|
## [2.4.4] — 文档结构补全(README 代码示例 / 兼容性矩阵 / 故障排查)
|
|
6
27
|
### 文档
|
|
7
28
|
- **新增「代码示例」章节**(中英双语):6 组可直接照抄的示例——远程命令(含 `timeoutMs`)、文件读写、长时任务与挂起恢复(`timeoutMs: 0` / `remote_ssh_kill`)、远程工作区内的免参调用、`~/.ssh/config` 导入、镜像 sync/push,并给出真实返回值形状。
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
| --- | --- |
|
|
13
13
|
| 🔌 SSH 连接 | 密钥 / 密码认证,ProxyJump 跳板机,`~/.ssh/config` 一键导入 |
|
|
14
14
|
| 📂 远程文件 | 内置「文件」页签直接 SSH 读写远程文件,无需同步 |
|
|
15
|
-
| 💻 远程终端 | 内置「终端」页签自动检测远程工作区,SSH
|
|
15
|
+
| 💻 远程终端 | 内置「终端」页签自动检测远程工作区,SSH 交互式终端,**落在工作区对应的远程目录**(与 VSCode Remote-SSH 一致) |
|
|
16
16
|
| 🌐 远程工作区 | 选择远程目录创建原生工作区,一键进入远程环境 |
|
|
17
17
|
| 🤖 模型工具 | 13 个 `remote_ssh_*` 工具,会话感知免填连接参数;命令级超时 + `remote_ssh_kill` 兜底恢复 |
|
|
18
18
|
| ⚡ 打开提速 | 单往返合并读 + raw 文本快路径 + 结果缓存(LRU + 5s TTL):首开 ≈**1.31×**,TTL 内重复打开 **0 往返**,过期复验 **≈5×**(真实超算实测);`remote_ssh_exec` 连接复用 **≈15×** |
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
**一条命令安装**(无需 token、API Key 或额外配置):
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
dsh plugin --profile <name> add @zhangfengshun/dsh-remote-ssh@2.4.
|
|
48
|
+
dsh plugin --profile <name> add @zhangfengshun/dsh-remote-ssh@2.4.6
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
安装后**重启 DSH**。`@zhangfengshun/dsh-remote-ssh` 必须在 bundles 列表中排在 `dsh-better-sidebar` **之后**。
|
|
@@ -64,7 +64,7 @@ dsh plugin --profile <name> remove @zhangfengshun/dsh-remote-ssh
|
|
|
64
64
|
|
|
65
65
|
1. **设置 → 远程连接** → 添加连接(主机 / 端口 / 用户 / 密钥)→ 点「测试连接」验证;已有 `~/.ssh/config` 可直接一键导入
|
|
66
66
|
2. **添加工作区** → 选「选择远程目录…」→ 选连接 → 浏览并选择远程目录(该目录会成为原生 DSH 工作区)
|
|
67
|
-
3. 进入该工作区会话:内置「文件」页签直接显示远程文件(编辑保存直写远程),「终端」页签自动 SSH
|
|
67
|
+
3. 进入该工作区会话:内置「文件」页签直接显示远程文件(编辑保存直写远程),「终端」页签自动 SSH 到该工作区的**远程目录**(仅密钥认证)
|
|
68
68
|
|
|
69
69
|
**会话内直接对模型说**(远程工作区会话中免填连接参数):
|
|
70
70
|
|
|
@@ -185,7 +185,9 @@ dsh plugin --profile <name> remove @zhangfengshun/dsh-remote-ssh
|
|
|
185
185
|
| 「测试连接」报 `Permission denied (publickey)` | ① 私钥**带口令**:插件以批处理模式运行(`BatchMode=yes`),无法交互输口令——先用 `ssh-add` 加载,或去掉密钥口令;② Windows host 且用户在 Administrators 组时,公钥须写入 `C:\ProgramData\ssh\administrators_authorized_keys`;③ 用户名的写法(`user` / `.\user` / `user@domain`)要与手动连接一致 |
|
|
186
186
|
| 从 git-bash 启动 `dsh web` 后密钥认证失败 | 2.3.9 起已修复:Windows 下 ssh 解析固定为系统 OpenSSH 绝对路径(此前会误用 Git 自带的 MSYS2 ssh) |
|
|
187
187
|
| 侧边栏文件页签显示「这类内容还没有可用的查看方式。」 | `dsh-better-sidebar` 主机半边未加载:0.18.1 / 0.19.0 / 0.19.1 在 DSH Desktop 上会因 `SessionLogOffset` 运行时导入失败——降到 0.18.0 或使用修复版(上游 PR [#641](https://github.com/omdsh-dev/DSH-better-sidebar/pull/641)) |
|
|
188
|
-
| 内置「终端」页签连不上 | 终端为 `ssh -tt`
|
|
188
|
+
| 内置「终端」页签连不上 | 终端为 `ssh -tt` 交互式通道,**仅支持密钥认证**;密码认证的连接会回退为本地 shell 并打印一行提示(避免把本地 shell 误认为已连上远程),密码认证请改用「文件」页签与模型工具 |
|
|
189
|
+
| 终端落在远程 `$HOME` 而不是工作区目录 | 2.4.5 起已修复(wrapper 会 `cd` 到工作区 `remotePath`,目录不存在时回退 `$HOME`);若仍停在 `$HOME`,确认 2.4.5 已装入并重启 DSH |
|
|
190
|
+
| 「文件」页签树根显示镜像目录 ID(如 `wmu3sxe24jpvg`) | 2.4.6 起已修复:树根改为显示**远程目录名**(如 `IB_Robot`),悬停可见完整远程路径;该标签不经过 `fs.*` 路由,由客户端渲染层替换 |
|
|
189
191
|
| 安装时提示 `minimumReleaseAge` 或「No matching version」(刚发布) | npm 供应链新鲜度策略,等 1–5 分钟后重试即可 |
|
|
190
192
|
| 命令卡住不返回 | 默认 120s 超时后自动丢弃会话;长时任务用 `timeoutMs: 0`,随时可用 `remote_ssh_kill` 强杀 |
|
|
191
193
|
| 大文件读取被截断 | 单文件读取上限 4MB、下载池化路径约 6.29MB(更大自动回落一次性连接);用 `remote_ssh_exec` + `head`/`tail` 分段处理 |
|
package/README_EN.md
CHANGED
|
@@ -12,7 +12,7 @@ A **DSH** plugin like **VSCode Remote-SSH**: connect to remote HPC / servers via
|
|
|
12
12
|
| --- | --- |
|
|
13
13
|
| 🔌 SSH Connection | Key / password auth, ProxyJump bastion, one-click import from `~/.ssh/config` |
|
|
14
14
|
| 📂 Remote Files | Built-in **Files** tab reads/writes remote files directly via SSH — no sync needed |
|
|
15
|
-
| 💻 Remote Terminal | Built-in **Terminal** tab auto-detects remote workspaces, opens SSH interactive shell |
|
|
15
|
+
| 💻 Remote Terminal | Built-in **Terminal** tab auto-detects remote workspaces, opens an SSH interactive shell **in the workspace's remote directory** (like VSCode Remote-SSH) |
|
|
16
16
|
| 🌐 Remote Workspace | Select a remote directory to create a native workspace, one-click enter |
|
|
17
17
|
| 🤖 Model Tools | 13 `remote_ssh_*` tools, session-aware with auto-filled connection params; command-level timeout + `remote_ssh_kill` recovery |
|
|
18
18
|
| ⚡ Faster Opens | Single-roundtrip merged reads + raw text fast path + result cache (LRU + 5s TTL): first open ≈**1.31×**, repeat opens within TTL **0 round-trips**, expired revalidation **≈5×** (measured on a real HPC); `remote_ssh_exec` connection reuse **≈15×** |
|
|
@@ -45,7 +45,7 @@ A **DSH** plugin like **VSCode Remote-SSH**: connect to remote HPC / servers via
|
|
|
45
45
|
**One command** (no token, API key or extra configuration needed):
|
|
46
46
|
|
|
47
47
|
```bash
|
|
48
|
-
dsh plugin --profile <name> add @zhangfengshun/dsh-remote-ssh@2.4.
|
|
48
|
+
dsh plugin --profile <name> add @zhangfengshun/dsh-remote-ssh@2.4.6
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
**Restart DSH** after installation. `@zhangfengshun/dsh-remote-ssh` must come **after** `dsh-better-sidebar` in the bundles list.
|
|
@@ -64,7 +64,7 @@ dsh plugin --profile <name> remove @zhangfengshun/dsh-remote-ssh
|
|
|
64
64
|
|
|
65
65
|
1. **Settings → Remote SSH** → Add a connection (host / port / user / key) → Click "Test Connection"; an existing `~/.ssh/config` can be imported in one click
|
|
66
66
|
2. **Add Workspace** → Choose "Select Remote Directory…" → Pick a connection → Browse and select a remote directory (it becomes a native DSH workspace)
|
|
67
|
-
3. Inside that workspace session: the built-in **Files** tab shows remote files (edits save straight back to remote), and the **Terminal** tab auto-SSHes
|
|
67
|
+
3. Inside that workspace session: the built-in **Files** tab shows remote files (edits save straight back to remote), and the **Terminal** tab auto-SSHes **into the workspace's remote directory** (key auth only)
|
|
68
68
|
|
|
69
69
|
**Just ask the model** (connection params are auto-filled inside a remote-workspace session):
|
|
70
70
|
|
|
@@ -185,7 +185,9 @@ The plugin never patches DSH sources or injects into the profile dependency tree
|
|
|
185
185
|
| "Test Connection" reports `Permission denied (publickey)` | ① key has a **passphrase**: the plugin runs in batch mode (`BatchMode=yes`) and cannot prompt — load it with `ssh-add` first, or strip the passphrase; ② on a Windows host where the user is in Administrators, the public key must go to `C:\ProgramData\ssh\administrators_authorized_keys`; ③ the username spelling (`user` / `.\user` / `user@domain`) must match a manual connection |
|
|
186
186
|
| Key auth fails after launching `dsh web` from git-bash | Fixed in 2.3.9: on Windows the ssh binary is pinned to the system OpenSSH absolute path (previously Git's MSYS2 ssh was picked up) |
|
|
187
187
|
| Sidebar Files tab says "Nothing here can view this kind of content yet." | `dsh-better-sidebar` host half failed to load: 0.18.1 / 0.19.0 / 0.19.1 hit the `SessionLogOffset` runtime import on DSH Desktop — downgrade to 0.18.0 or use a fixed build (upstream PR [#641](https://github.com/omdsh-dev/DSH-better-sidebar/pull/641)) |
|
|
188
|
-
| Built-in Terminal tab cannot connect | The terminal is an `ssh -tt` interactive channel and supports **key auth only**; use the Files tab and the model tools for password auth |
|
|
188
|
+
| Built-in Terminal tab cannot connect | The terminal is an `ssh -tt` interactive channel and supports **key auth only**; password-auth profiles fall back to a local shell and now print a one-line notice (so a local shell is not mistaken for a remote one) — use the Files tab and the model tools for password auth |
|
|
189
|
+
| Terminal opens in the remote `$HOME` instead of the workspace directory | Fixed in 2.4.5 (the wrapper `cd`s into the workspace `remotePath`, falling back to `$HOME` when it no longer exists); if it still starts in `$HOME`, make sure 2.4.5 is installed and DSH restarted |
|
|
190
|
+
| Files tab tree root shows the mirror directory id (e.g. `wmu3sxe24jpvg`) | Fixed in 2.4.6: the root row now shows the **remote directory name** (e.g. `IB_Robot`) with the full remote path on hover; that label never passes through the `fs.*` routes, so the client renders the replacement |
|
|
189
191
|
| Install fails with `minimumReleaseAge` or "No matching version" right after a release | npm supply-chain freshness policy — retry after 1–5 minutes |
|
|
190
192
|
| A command hangs forever | The 120s timeout discards the pooled session automatically; use `timeoutMs: 0` for long jobs and `remote_ssh_kill` at any time |
|
|
191
193
|
| Large files are truncated | 4MB per read, ≈6.29MB on the pooled download path (larger files fall back to a one-shot connection); use `remote_ssh_exec` with `head`/`tail` to page through |
|
package/lib/client.js
CHANGED
|
@@ -1167,6 +1167,8 @@ window.__ModuleLoader__.load({
|
|
|
1167
1167
|
// 追加地球经纬线组 —— 文件夹本体保持壳层默认原语几何,零改写零对账风险。
|
|
1168
1168
|
var remoteTitles = new Set();
|
|
1169
1169
|
var watchedTitles = []; // 已匹配的标题元素:定时幂等复检,抵消 React 重渲染对图标的还原
|
|
1170
|
+
/** 镜像目录 basename → { label: 远程目录 basename, remotePath }(issue #9)。 */
|
|
1171
|
+
var rootLabels = {};
|
|
1170
1172
|
function refreshRemoteTitles() {
|
|
1171
1173
|
api("listWorkspaces").then(function (r) {
|
|
1172
1174
|
if (!(r && r.ok && Array.isArray(r.workspaces))) return;
|
|
@@ -1176,7 +1178,9 @@ window.__ModuleLoader__.load({
|
|
|
1176
1178
|
if (t) { next.add(t); next.add("🌐 " + t); } // 兼容自愈前的旧前缀标题
|
|
1177
1179
|
}
|
|
1178
1180
|
remoteTitles = next;
|
|
1181
|
+
refreshRootLabels(r.workspaces);
|
|
1179
1182
|
swapWorkspaceIconsIn(document.body); // 标题集就绪后补一次全量替换
|
|
1183
|
+
applyRootLabelsIn(document.body); // 树根标签同样补一次全量替换
|
|
1180
1184
|
}).catch(function () {});
|
|
1181
1185
|
}
|
|
1182
1186
|
/** 地球经纬线组(右下角、无圆盘底色):线为白色、其余全透明;文件夹本体完全
|
|
@@ -1247,12 +1251,83 @@ window.__ModuleLoader__.load({
|
|
|
1247
1251
|
}
|
|
1248
1252
|
} catch (e) {}
|
|
1249
1253
|
}
|
|
1254
|
+
|
|
1255
|
+
// ---- 文件树根标签:显示远程目录名,而不是本地镜像目录 ID(issue #9)----
|
|
1256
|
+
// better-sidebar「文件」页签的树根标签取自会话 cwd(= 本地镜像路径)的 basename,
|
|
1257
|
+
// 于是显示成镜像目录 ID(如 wmu3sxe24jpvg)。这条链路不经过任何 fs.* 路由,
|
|
1258
|
+
// 而 Host 侧 session.cwd 虽然已返回 root 字段,客户端只消费 cwd
|
|
1259
|
+
// (client-registry 的 api.sessionCwd(...).then(r => setFetchedCwd(r.cwd)),
|
|
1260
|
+
// 另有 useSessionCwd 直接读客户端会话列表的 cwd),因此只能在渲染层替换文本。
|
|
1261
|
+
// 定位不依赖构建期哈希类名:根行是树里唯一带内联 padding-left:6px 的行,
|
|
1262
|
+
// 仅当该行内的纯文本节点 === 某远程工作区的镜像 basename 时才替换(幂等、可自愈)。
|
|
1263
|
+
var watchedRootLabels = []; // 已替换的标签元素:1s 幂等复检,抵消 React 重渲染
|
|
1264
|
+
function refreshRootLabels(workspaces) {
|
|
1265
|
+
var next = {};
|
|
1266
|
+
for (var i = 0; i < (workspaces || []).length; i++) {
|
|
1267
|
+
var w = workspaces[i] || {};
|
|
1268
|
+
var mirror = String(w.mirrorPath || "").replace(/[\\/]+$/, "");
|
|
1269
|
+
var remote = String(w.remotePath || "").replace(/[\\/]+$/, "");
|
|
1270
|
+
if (!mirror || !remote) continue;
|
|
1271
|
+
var mb = mirror.split(/[\\/]/).pop();
|
|
1272
|
+
var rb = remote.split(/[\\/]/).pop();
|
|
1273
|
+
if (mb && rb && mb !== rb) next[mb] = { label: rb, remotePath: remote };
|
|
1274
|
+
}
|
|
1275
|
+
rootLabels = next;
|
|
1276
|
+
}
|
|
1277
|
+
/** 根行判据:自身或 ≤3 层祖先带内联 padding-left:6px(better-sidebar 根行样式)。 */
|
|
1278
|
+
function isRootRow(el) {
|
|
1279
|
+
var node = el;
|
|
1280
|
+
for (var hop = 0; hop < 3 && node; hop++) {
|
|
1281
|
+
var style = node.getAttribute ? node.getAttribute("style") : null;
|
|
1282
|
+
if (style && /padding-left:\s*6px/i.test(String(style))) return true;
|
|
1283
|
+
node = node.parentElement;
|
|
1284
|
+
}
|
|
1285
|
+
return false;
|
|
1286
|
+
}
|
|
1287
|
+
/** 单个标签替换:纯文本 + 命中镜像 basename + 位于根行 → 换成远程目录名。 */
|
|
1288
|
+
function fixRootLabel(el) {
|
|
1289
|
+
var text = (el.textContent || "").trim();
|
|
1290
|
+
if (!text) return false;
|
|
1291
|
+
var hit = rootLabels[text];
|
|
1292
|
+
if (hit === undefined) {
|
|
1293
|
+
// 文本已是远程目录名(上一轮替换过)→ 幂等成功;否则与本插件无关
|
|
1294
|
+
return el.getAttribute && el.getAttribute("data-rssh-root-label") === "1";
|
|
1295
|
+
}
|
|
1296
|
+
if (el.childElementCount > 0) return false; // 只改纯文本节点
|
|
1297
|
+
if (!isRootRow(el)) return false; // 非根行(例如同名子条目)不动
|
|
1298
|
+
el.textContent = hit.label;
|
|
1299
|
+
if (el.setAttribute) {
|
|
1300
|
+
el.setAttribute("data-rssh-root-label", "1");
|
|
1301
|
+
el.setAttribute("title", hit.remotePath); // 悬停可见完整远程路径
|
|
1302
|
+
}
|
|
1303
|
+
watchRootLabel(el);
|
|
1304
|
+
return true;
|
|
1305
|
+
}
|
|
1306
|
+
function watchRootLabel(el) {
|
|
1307
|
+
for (var i = 0; i < watchedRootLabels.length; i++) { if (watchedRootLabels[i] === el) return; }
|
|
1308
|
+
watchedRootLabels.push(el);
|
|
1309
|
+
}
|
|
1310
|
+
function recheckRootLabels() {
|
|
1311
|
+
for (var i = watchedRootLabels.length - 1; i >= 0; i--) {
|
|
1312
|
+
var el = watchedRootLabels[i];
|
|
1313
|
+
if (!el || !el.isConnected) { watchedRootLabels.splice(i, 1); continue; }
|
|
1314
|
+
fixRootLabel(el);
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
function applyRootLabelsIn(root) {
|
|
1318
|
+
if (!root || !root.querySelectorAll) return;
|
|
1319
|
+
try {
|
|
1320
|
+
var nodes = root.querySelectorAll("span,div");
|
|
1321
|
+
for (var i = 0; i < nodes.length; i++) fixRootLabel(nodes[i]);
|
|
1322
|
+
if (root.nodeType === 1) fixRootLabel(root);
|
|
1323
|
+
} catch (e) {}
|
|
1324
|
+
}
|
|
1250
1325
|
ctx.effect(function () {
|
|
1251
1326
|
if (typeof document === "undefined") return;
|
|
1252
1327
|
applyRemoteNavIcon();
|
|
1253
1328
|
refreshRemoteTitles();
|
|
1254
1329
|
var titleTimer = setInterval(refreshRemoteTitles, 30000);
|
|
1255
|
-
var recheckTimer = setInterval(recheckWatched, 1000); // 抵消 React 属性还原
|
|
1330
|
+
var recheckTimer = setInterval(function () { recheckWatched(); recheckRootLabels(); }, 1000); // 抵消 React 属性还原
|
|
1256
1331
|
var timer = null;
|
|
1257
1332
|
var pendingRoots = null;
|
|
1258
1333
|
var schedule = function (root) {
|
|
@@ -1265,13 +1340,14 @@ window.__ModuleLoader__.load({
|
|
|
1265
1340
|
if (document.hidden) return; // 后台页签零开销
|
|
1266
1341
|
if (roots && roots.length) {
|
|
1267
1342
|
// 只扫新增子树:流式聊天期间不做全文档扫描(稳态成本≈0)
|
|
1268
|
-
//
|
|
1343
|
+
// (设置导航图标与树根标签已在 MutationObserver 回调里同步替换,这里只补工作区角标)
|
|
1269
1344
|
for (var i = 0; i < roots.length; i++) {
|
|
1270
1345
|
if (roots[i].isConnected) swapWorkspaceIconsIn(roots[i]);
|
|
1271
1346
|
}
|
|
1272
1347
|
} else {
|
|
1273
1348
|
applyRemoteNavIcon(); // 语言切换等无根场景全量扫一次
|
|
1274
1349
|
swapWorkspaceIconsIn(document.body);
|
|
1350
|
+
applyRootLabelsIn(document.body);
|
|
1275
1351
|
}
|
|
1276
1352
|
}, 500);
|
|
1277
1353
|
};
|
|
@@ -1283,7 +1359,11 @@ window.__ModuleLoader__.load({
|
|
|
1283
1359
|
// 设置导航图标:同步原位替换。MutationObserver 回调是微任务,
|
|
1284
1360
|
// 在浏览器绘制前执行 —— 齿轮刚插入 DOM 就被换成显示器图标,
|
|
1285
1361
|
// 用户看到的第一帧就是最终图标(2.4.2 修复齿轮闪现)。
|
|
1286
|
-
if (node && node.nodeType === 1 && node.isConnected)
|
|
1362
|
+
if (node && node.nodeType === 1 && node.isConnected) {
|
|
1363
|
+
checkCellIn(node);
|
|
1364
|
+
// 树根标签同样同步替换:微任务内、绘制前完成,避免先闪一下镜像目录 ID
|
|
1365
|
+
applyRootLabelsIn(node);
|
|
1366
|
+
}
|
|
1287
1367
|
// 工作区地球角标依赖异步 remoteTitles,仍走防抖批量路径
|
|
1288
1368
|
schedule(node);
|
|
1289
1369
|
}
|
|
@@ -1298,7 +1378,7 @@ window.__ModuleLoader__.load({
|
|
|
1298
1378
|
clearInterval(titleTimer);
|
|
1299
1379
|
clearInterval(recheckTimer);
|
|
1300
1380
|
};
|
|
1301
|
-
}, "dsh-remote-ssh: settings nav monitor icon + remote workspace folder-globe icon");
|
|
1381
|
+
}, "dsh-remote-ssh: settings nav monitor icon + tree root label + remote workspace folder-globe icon");
|
|
1302
1382
|
}
|
|
1303
1383
|
|
|
1304
1384
|
// better-sidebar 0.15+ 的文件上传 UI 把原始字节流 POST 到 /sidebar/upload。
|
package/lib/index.js
CHANGED
|
@@ -1947,8 +1947,9 @@ function apply(ctx, config) {
|
|
|
1947
1947
|
});
|
|
1948
1948
|
|
|
1949
1949
|
// ---- 创建 shell wrapper(供 better-sidebar 的 shell 配置指向它,实现远程终端透明接入)----
|
|
1950
|
-
// wrapper 逻辑:读取当前工作目录下的 .remote-ssh.json,若存在且含 keyPath → exec ssh -tt
|
|
1951
|
-
//
|
|
1950
|
+
// wrapper 逻辑:读取当前工作目录下的 .remote-ssh.json,若存在且含 keyPath → exec ssh -tt
|
|
1951
|
+
// (并把终端 cd 到该工作区的 remotePath,与 VSCode Remote-SSH 行为一致);
|
|
1952
|
+
// 否则降级到平台默认 shell(bash -l / powershell)——远程工作区但非密钥认证时打一行提示。
|
|
1952
1953
|
// 实现方式:跨平台 Node.js 脚本 + 平台特定的薄壳调用它。
|
|
1953
1954
|
const wrapperDir = join(homedir(), ".dsh", "remote-ssh");
|
|
1954
1955
|
const isWin = process.platform === "win32";
|
|
@@ -1971,10 +1972,23 @@ function apply(ctx, config) {
|
|
|
1971
1972
|
" // ExitOnForwardFailure=no:ssh config 里的 RemoteForward 端口被占时终端仍能打开",
|
|
1972
1973
|
" const args = ['-tt', '-o', 'StrictHostKeyChecking=no', '-o', 'ExitOnForwardFailure=no', '-p', String(port), '-i', j.keyPath, j.user + '@' + j.host];",
|
|
1973
1974
|
" if (j.proxyJump) { args.splice(2, 0, '-J', j.proxyJump); }",
|
|
1975
|
+
" // 终端落在工作区对应的远程目录(issue #7:此前不带远程命令,落点是远程 $HOME)。",
|
|
1976
|
+
" // remotePath 的 ~ 必须交给【远端】shell 展开——本地 homedir 与远程不同,本地展开必错;",
|
|
1977
|
+
" // cd 失败(目录被删/改名)回退 $HOME,保证终端仍能打开;",
|
|
1978
|
+
" // exec 让远端只留一个交互式登录 shell,退出码语义与之前一致。",
|
|
1979
|
+
" const remotePath = typeof j.remotePath === 'string' ? j.remotePath.trim() : '';",
|
|
1980
|
+
" if (remotePath !== '') {",
|
|
1981
|
+
" const expanded = remotePath === '~' ? '$HOME' : (remotePath.indexOf('~/') === 0 ? '$HOME/' + remotePath.slice(2) : remotePath);",
|
|
1982
|
+
" args.push('cd \"' + expanded + '\" 2>/dev/null || cd \"$HOME\"; exec \"${SHELL:-/bin/bash}\" -l');",
|
|
1983
|
+
" }",
|
|
1974
1984
|
" const ssh = spawn(sshBin, args, { stdio: 'inherit' });",
|
|
1975
1985
|
" ssh.on('exit', function(c) { process.exit(c == null ? 0 : c); });",
|
|
1976
1986
|
" return;",
|
|
1977
1987
|
" }",
|
|
1988
|
+
" // 远程工作区但该连接不是密钥认证(如密码认证,keyPath 为空):集成终端仅支持",
|
|
1989
|
+
" // 密钥认证,这里显式提示一行,避免用户把本地 shell 误认为已连上远程。",
|
|
1990
|
+
" process.stderr.write('[dsh-remote-ssh] 检测到远程工作区,但该连接不是密钥认证:集成终端仅支持密钥认证,已回退为本地 shell。\\n');",
|
|
1991
|
+
" process.stderr.write('[dsh-remote-ssh] remote workspace detected, but this profile is not key-authenticated; the integrated terminal only supports key auth and fell back to a local shell.\\n');",
|
|
1978
1992
|
" }",
|
|
1979
1993
|
"} catch (e) {}",
|
|
1980
1994
|
"// 默认本地 shell",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhangfengshun/dsh-remote-ssh",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.6",
|
|
4
4
|
"description": "DSH web plugin: VSCode Remote-SSH-like remote development (SSH to supercomputers/servers, remote workspace, file explorer, integrated terminal), integrated with dsh-better-sidebar and DSH settings.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"dsh",
|