dsh-remote 0.8.29 → 0.8.31

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 (4) hide show
  1. package/README.en.md +344 -0
  2. package/README.md +220 -225
  3. package/README.zh.md +7 -268
  4. package/package.json +3 -1
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- **English** · [中文](./README.zh.md)
1
+ [English](./README.en.md) · **中文**
2
2
 
3
3
  ---
4
4
 
@@ -10,329 +10,324 @@
10
10
  [![license](https://img.shields.io/github/license/flymysql/dsh-remote)](LICENSE)
11
11
  [![dsh-plugin](https://img.shields.io/badge/topic-dsh--plugin-7a3ef3)](https://github.com/topics/dsh-plugin)
12
12
 
13
- Maintained by [@flymysql](https://github.com/flymysql) · [Homepage](https://flymysql.github.io/dsh-remote/) · [Usage stats](https://flymysql.github.io/dsh-remote/stats/) · [Blog](https://gitpull.cn) · [Discussions](https://github.com/flymysql/dsh-remote/discussions) · [Issues](https://github.com/flymysql/dsh-remote/issues) · [中文说明](./README.zh.md)
13
+ 由 [@flymysql](https://github.com/flymysql) 维护 · [主页](https://flymysql.github.io/dsh-remote/) · [用量统计](https://flymysql.github.io/dsh-remote/stats/) · [博客](https://gitpull.cn) · [讨论区](https://github.com/flymysql/dsh-remote/discussions) · [Issue](https://github.com/flymysql/dsh-remote/issues) · [English](./README.en.md)
14
14
 
15
- ![dsh-remote — make any SSH machine a real DSH workspace](docs/cover.png)
15
+ ![dsh-remote —— 把任意 SSH 机器变成真正的 DSH 工作区](docs/cover.png)
16
16
 
17
- **Remote-work assistant for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) (DSH).**
17
+ **为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)打造的远程工作助手。**
18
18
 
19
- Manage several SSH machines, then pick a **remote workspace** (or a **local** one) and let the agent operate right there without leaving the harness — listing files, reading code, running builds & commands over the remote host, and keeping that remote directory mirrored into a real local workspace object.
19
+ 维护多台 SSH 机器,然后在「选择工作区」时选一个**远程工作区**(或**本地工作区**),Agent 就能在不离开 harness 的情况下直接操作——列文件、读代码、在远程主机上跑构建/命令,并把远程目录镜像成一个真实的本地工作区对象。
20
20
 
21
- The harness Web UI intentionally binds `127.0.0.1` (the CLI rejects `--host 0.0.0.0` for safety). This plugin goes the other way: **you connect out** to the machines you maintain, pick a workspace, and work in it through the normal DSH workspace + agent fs flows — no changes to `dsh-workspace` or the harness core.
22
-
23
- ## Data collection / telemetry
24
-
25
- dsh-remote sends **one anonymous heartbeat per launch** (at most once every 6 hours) so the author can measure real usage: daily active installs, which version is actually running, and platform distribution. npm download counts cannot answer this (they are release-driven and include mirrors/crawlers), and GitHub clones include CI.
26
-
27
- **What is sent** — exactly five fields, and nothing else:
28
-
29
- | Field | Example | Purpose |
30
- |---|---|---|
31
- | `idHash` | `872bd8cf…` (32 hex) | `HMAC-SHA256('dsh-remote/telemetry/v1', installId)` — a pseudonym for this install |
32
- | `version` | `0.8.24` | which version is actually running |
33
- | `platform` | `win32` / `darwin` / `linux` | platform distribution |
34
- | `arch` | `x64` / `arm64` | architecture |
35
- | `node` | `24.14.0` | Node version |
36
-
37
- **What is never sent** — hostnames, usernames, file paths, IP addresses, SSH hosts/ports/keys, your machine list, conversation content, or anything from your remote sessions. The original `installId` **never leaves your machine**: only its HMAC is transmitted, so the server cannot correlate it with anything else and cannot reverse it.
38
-
39
- **Where the identity lives** — a random UUID in `<DSH_HOME>/.dsh-remote-install-id` (e.g. `~/.dsh/`). It is deliberately **not** stored in the plugin directory, which npm/pnpm overwrites on every upgrade; keeping it in `DSH_HOME` means an upgrade does not make you look like a new user. Delete that file to reset the identity.
40
-
41
- The heartbeat is **fire-and-forget**: it never blocks loading, never logs noise, and any failure (offline, blocked, endpoint change) is swallowed silently — it can never affect any plugin feature.
42
-
43
- <!--中文-->
21
+ DSH 的 Web 界面刻意只监听 `127.0.0.1`(CLI 为安全拒绝 `--host 0.0.0.0`)。本插件反过来:**由你主动连出**到你维护的机器,选一个工作区,然后通过 DSH 原生的工作区 + 文件流来工作——**不改动 `dsh-workspace` 核心**。
44
22
 
45
23
  ## 数据采集 / 遥测
46
24
 
47
25
  dsh-remote 每次启动会发送**一次匿名心跳**(同一安装最多每 6 小时一次),用于统计真实使用量:去重日活、实际在跑的版本、平台分布。npm 下载量回答不了这些问题(由发版驱动、且含镜像与爬虫),GitHub clone 也混有 CI。
48
26
 
49
- **发送的内容** —— 严格只有 5 个字段:
27
+ **发送的内容** —— 严格只有 5 个字段,一个都不多:
50
28
 
51
29
  | 字段 | 示例 | 用途 |
52
30
  |---|---|---|
53
31
  | `idHash` | `872bd8cf…`(32 位 hex) | `HMAC-SHA256('dsh-remote/telemetry/v1', installId)`,本安装的伪名 |
54
- | `version` | `0.8.24` | 实际在运行的版本 |
32
+ | `version` | `0.8.29` | 实际在运行的版本 |
55
33
  | `platform` | `win32` / `darwin` / `linux` | 平台分布 |
56
34
  | `arch` | `x64` / `arm64` | 架构 |
57
35
  | `node` | `24.14.0` | Node 版本 |
58
36
 
59
- **绝不发送** —— 主机名、用户名、文件路径、IP、SSH 主机/端口/密钥、你的机器列表、会话内容、任何远程工作区数据。原始 `installId` **不离开本机**:只有它的 HMAC 被传出,服务端无法与其他数据关联,也无法反推。
37
+ **绝不发送** —— 主机名、用户名、文件路径、IP、SSH 主机/端口/密钥、你的机器列表、会话内容、任何远程工作区数据。本插件恰恰能拿到这些信息,所以边界在这里划得很硬:原始 `installId` **不离开本机**,只有它的 HMAC 被传出,服务端无法与其他数据关联,也无法反推身份。
60
38
 
61
- **身份存放位置** —— `<DSH_HOME>/.dsh-remote-install-id`(如 `~/.dsh/`)中的一个随机 UUID。刻意**不放在插件目录**(npm/pnpm 每次升级都会覆盖),放 `DSH_HOME` 才能保证升级后不会把你算成新用户。删除该文件即重置身份。
39
+ **身份存放位置** —— `<DSH_HOME>/.dsh-remote-install-id`(如 `~/.dsh/`)中的一个随机 UUID。刻意**不放在插件目录**:npm/pnpm 每次升级都会覆盖插件目录,放那里会让同一台机器每次升级都换身份,把 1 个用户算成 N 个。删除该文件即重置身份。
62
40
 
63
41
  心跳是**尽力而为**的旁路:不阻塞加载、不产生日志噪音、任何失败(离线/被拦截/端点变更)都静默吞掉,绝不影响插件的任何功能。
64
42
 
65
- ## Screen previews
43
+ [实时用量看板](https://flymysql.github.io/dsh-remote/stats/) · [插件主页](https://flymysql.github.io/dsh-remote/)
44
+
45
+ ## 界面预览
66
46
 
67
- Settings → **远程工作区** — a multi-machine SSH registry (add / edit / delete / set-current, password stored locally):
47
+ 设置 → **远程工作区** —— 多机 SSH 列表(增/删/改/设为当前,密码本地保存、不回显):
68
48
 
69
- <img src="docs/ui-settings-panel.png" alt="dsh-remote settings — multi-machine registry (light theme, host scrubbed)" width="720"/>
49
+ <img src="docs/ui-settings-panel.png" alt="dsh-remote 设置页 — 多机列表(浅色主题,主机已打码)" width="720"/>
70
50
 
71
- The native **"Add workspace" / "Select workspace"** flow — a centered modal, two tabs, opens on **本机 (local)**; switch to **远程 (remote)**:
51
+ 原生 **「Add workspace / 选择工作区」** 流程 —— **居中弹窗**、两个 tab,**默认落在「本机」**;切到 **「远程」**:
72
52
 
73
- - **远程** — a **machine `<select>`**, a path field that **auto-prefills `/` and live-completes** directories (picking one immediately reveals its next level, OS/VSCode-style), plus a **浏览…** floating browser that fills the field without committing — you review, edit, then **设为远程工作区**.
53
+ - **远程** —— 一个**机器下拉**;路径输入框**自动预填 `/` 并实时补全目录**(点选一个目录后**立即列出它的下一级**,像系统/VSCode 逐级选目录);另外有**「浏览…」浮窗**,选中仅回填到输入框(不直接提交),你复核 / 修改后点「设为远程工作区」。
74
54
 
75
- Real capture (host scrubbed to a placeholder):
55
+ 真实截图(机器已打码为占位):
76
56
 
77
- <img src="docs/ui-picker-panel.png" alt="dsh-remote workspace picker — real dialog; 本机 (local) tab; 远程 machine select + prefilled root path + autocomplete" width="720"/>
57
+ <img src="docs/ui-picker-panel.png" alt="dsh-remote 工作区选择 — 真实弹窗;默认本机 tab;远程:机器下拉 + 预填根路径 + 自动补全" width="720"/>
78
58
 
79
59
  ---
80
60
 
81
- ## Features
82
-
83
- - **Multi-machine SSH** — save any number of hosts (`host`/`port`/`user` + **private key** or **password**). Passwords are stored locally and never shown back in the UI. Switch with one click in Settings. Per-machine **passphrase / host-key mode / SSH agent / keyboard-interactive (OTP) / proxy jump (bastion)** and an **optional OS-keychain password** (`加密保存密码` — macOS Keychain / Windows DPAPI / Linux secret-tool).
84
- - **`~/.ssh/config` aliases (resolved live, never copied)** — a machine can be saved as just a **Host alias** (`useSshConfig`): hostname/user/port/key/jump host are read from `~/.ssh/config` **at every connect**, so editing that file takes effect immediately and there is nothing to re-import; the registry stores **no copy** of those values (the key stays a path reference, its content is never read). Full OpenSSH semantics: multi-alias `Host a b`, `*`/`?` wildcards, `!` negation, `Include` (globbed, relative to `~/.ssh`), trailing-`\` continuations and ssh_config(5)'s *first-obtained-value-wins*. In Settings, **Import from ~/.ssh/config** saves an alias in one click (or **Copy fields** materialises a normal machine), the alias list and machine rows show **alias → what it actually resolves to**, and anything the plugin cannot honour (`ProxyJump` with several hops, `ProxyCommand`) is surfaced as a warning instead of silently degrading.
85
- - **Two-tab workspace picker** (fills the native "Add workspace" flow):
86
- - **本机 / Local** — opens the **native OS folder chooser** over the host (macOS `osascript` / Linux `zenity`→`kdialog` / **Windows `FolderBrowserDialog`**), or lets you type a local path → adopted directly as a normal DSH local workspace.
87
- - **远程 / Remote** — the picker is a **centered modal**. Pick a **machine** → on Windows hosts the root shows a **"This PC" drive view** (`C:\`, `D:\`, `E:\`… instead of the Git Bash MSYS root) and the path field live **autocompletes** directories (accepts `C:\Users\…` or `/c/Users/…` — Windows paths are rewritten to the Git Bash form underneath); selecting a directory immediately lists its next level. A **浏览…** floating browser (Windows-aware breadcrumb `此电脑 / C:\ / Users / dev`, drive rows, size + mtime, dirs first, follows symlinks) fills the field without committing; the **回上一级** button works at any depth (even when the browser was opened at the path bar's value). **最近 workspaces** quick-pick, **`~` 主目录** shortcut and **新建目录** are one click away. On confirm it creates a **real local mirror** under `$DSH_HOME/remote-workspaces/<host>-<user>-<port>/<base>` that passes `fs.realpath` → the harness adopts it as a real workspace while dsh-remote keeps it synced over SFTP.
88
- - **Git Bash default terminal (Windows remotes)** — the remote platform is auto-detected (`cmd /c ver`, plus an `uname -s` MINGW/MSYS probe as fallback); on Windows the plugin locates Git Bash (`config.shell` can pin a path or `native` disables wrapping) and pipes every command to `bash -s` over the exec channel, so quoting/backslash escaping is never an issue regardless of the SSH default shell. `rw_exec` runs with a Git Bash cwd (`/c/Users/…` form). `/dsh-remote/status`, `rw_info` and the 测试连接 button report the detected platform + shell.
89
- - **Windows path auto-conversion** — typing `C:\Users\dev\project` (or `C:/…`, `/c/…`, `/C:/…`) is normalized underneath to the Git Bash form `/c/Users/dev/project` for shell commands, while workspaces are stored and shown Windows-style (`C:\Users\dev\project`). All model tools accept and report both forms; SFTP access uses the Win32-OpenSSH `/D:/…` form (see `toSftpPath`).
90
- - **Remote `@` completion (issue #39)** — in a remote session `@` lists the **remote** tree (read live over SFTP, not the local mirror): directories drill down, a slash-free query fuzzy-matches the whole tree, and candidates are **workspace-relative paths** (`@src/main.c`) exactly like a local session. The `rw_*` tools accept those relative paths and resolve them against the remote workspace root. The index is bounded (entries/directories/deadline + cache + failure breaker) and **falls back to the local mirror when the host is unreachable** — never a silent empty list. Local sessions are untouched.
91
- - **Bidirectional SFTP sync, conflict-aware** — `rw_sync` (remote → mirror) and `rw_push` (mirror → remote) are **three-way** (remote vs local vs last-synced snapshot): files changed on both sides are **reported as conflicts and never silently overwritten** (`force=true` overrides). Defaults are **depth 8 / 2000 files**; hitting a cap is reported as **`TRUNCATED`**. Both support **dry-run**, **background tasks**, and honor **gitignore-style ignore rules**.
92
- - **Model tools** — 20 tools, all Windows/POSIX portable via SFTP: `rw_info`, `rw_connect` (with `save`), `rw_pick_workspace`, `rw_list_dir` (size+mtime), `rw_stat`, `rw_read_file` (encoding-aware: utf-8/gbk), `rw_write_file`, **`rw_edit`** (literal replace + mtime optimistic lock), `rw_append`, `rw_mkdir`, `rw_remove` (recursive, bounded), `rw_move`, `rw_exec` (pty/env), **`rw_search`** (SFTP tree walk — works on Windows too, honors ignore rules, context lines), `rw_download`/`rw_upload` (streaming fastGet/fastPut + size caps), **`rw_forward`** (SSH tunnels), `rw_sync`, `rw_push`, `rw_disconnect`.
93
- - **Port forwarding panel** — create/start/stop/remove **local** (`127.0.0.1:port → remote`) and **reverse** (`remote → local`) tunnels in the Settings page or via `rw_forward`; definitions persist, auto-restart on reconnect when enabled, all tunnels stop on disconnect.
94
- - **Sidebar remote editing** — the remote file tab is **editable**: click **编辑** → edit → **保存到远程** with an mtime optimistic lock (409 + "重新读取" on concurrent change). File ops are **session-bound** (v0.8.19): the explorer sends `sessionId` so two conversations on different hosts do not share the active-machine pool. The explorer rows show file sizes and have a **right-click menu** (下载到本地镜像 / 重命名 / 删除 / 新建目录).
95
- - **Command audit log** — every `rw_exec`/write/remove/move/forward is appended to `$DSH_HOME/remote-workspaces/audit.log` (time · user@host · op · exit code · command); the Settings page shows the last 30.
96
- - **Async long tasks** — `rw_sync`/`rw_push` with `async: true` return a `taskId`; progress/result/cancel via `/dsh-remote/task` (single-flight queue).
97
- - **Connection health** — a **「测试连接」** button validates host/user/key/password (with per-category error hints: auth / network / host key / timeout) before you save a machine; latency is cached on the machine record.
98
- - The active `user@host:/path` is injected into every system prompt (plus active forwards).
99
- - **No official `dsh-workspace` core is modified** — everything is delivered as a normal plugin (directory-flow holes filled by the client half at `priority -100`).
100
- - **Cross-platform remotes** — all file access is SFTP-protocol-level (no shell dependency), so Linux/macOS/Windows remotes all work for listing, reading, writing, searching and syncing.
101
- - **Host-key verification (TOFU)** — every SSH connect verifies the host key
102
- (`hostKeyMode: accept-new`): first connect records it, a later CHANGE is rejected
103
- as a possible man-in-the-middle. `verify` also refuses hosts never seen before;
104
- `off` disables it. Stored at `$DSH_HOME/remote-workspaces/known_hosts.json`; reset
105
- with `/remote forget-key`.
106
- - **Data lives under the harness home** — machines + mirrors follow `$DSH_HOME`; pre-0.6 data under `~/.dsh/remote-workspaces` is migrated automatically on first run.
107
-
108
- ## Install
109
-
110
- ### Official Desktop compatibility (experimental, unreleased)
111
-
112
- This branch adds a compatibility path for the **official**
113
- [DeepSeek Harness Desktop](https://github.com/deepseek-ai/deepseek-harness),
114
- tested against the `0.1.5-rc.2` Host transport. It does not replace the Harness
115
- core or require a listening Web server:
116
-
117
- - The SSH settings and directory picker use `/api/dsh-remote/*` over the
118
- Desktop's `dsh-app:` carrier. Exact Fetch routes are registered on
119
- `ctx.connection.fetch`; the carrier retains ownership of authentication.
120
- - A native **Remote Files** entry uses `sidebarRightTabs` and the keyed
121
- `sidebar.right.pane.tab` seat. It reuses the existing explorer/editor and
122
- gives remote files their own session-scoped resource addresses, rather than
123
- sending remote paths to the local Files viewer.
124
- - `dsh-better-sidebar` is not bundled. Web hosts may install it separately;
125
- official Desktop uses the native right-sidebar integration instead.
126
-
127
- Since **v0.8.19**, sidebar `/ls` `/read` `/write` `/fs` resolve the session's
128
- mirror binding (same path as `rw_*`) when the client sends `sessionId`. Two
129
- sessions on different hosts no longer share the active-machine pool for file
130
- ops. Host-side tests cover that routing plus the editor 409/re-read/save path.
131
-
132
- Official Desktop's native file-tab GUI, failed/cancelled dialogs, non-macOS
133
- hosts, and a full legacy Web UI pass are still experimental. Desktop's package
134
- installer may also require an explicit policy for the optional `ssh2` /
135
- `cpu-features` build scripts. The isolated transport test disabled those
136
- optional scripts; this change does not loosen an application's build allowlist
137
- or automatically approve dependency scripts.
138
-
139
- ### Published Web bundle
61
+ ## 功能
62
+
63
+ - **多机 SSH** —— 可存任意多台主机(host/port/user + **私钥**或**密码**)。密码只存在本地,界面不回显;在设置里一键切当前机。每机可配 passphrase / 主机指纹策略 / SSH agent / keyboard-interactive(OTP)/ 跳板机,以及可选的 **系统钥匙串加密密码**。
64
+ - **`~/.ssh/config` 别名(实时解析,不存副本)** —— 机器可以只保存一个 **Host 别名**(`useSshConfig`):主机名/用户/端口/私钥/跳板机**每次连接都从 `~/.ssh/config` 实时解析**,改配置立刻生效、无需重新导入;注册表里**不存这些值的副本**(私钥只引用路径,永不读内容)。支持 OpenSSH 语义:`Host a b` 多别名、`*`/`?` 通配、`!` 取反、`Include`(含通配、相对 `~/.ssh`)、行尾 `\` 续行、以及 ssh_config(5) 的**首个取值优先**规则。设置页「从 ~/.ssh/config 导入」列表里点别名即按别名保存(也可以「复制字段」成普通机器);列表与机器行都会显示 **别名 → 实际解析到哪台机**,`ProxyJump` 多跳、`ProxyCommand` 等插件无法照做的事会**显式告警**而不是静默降级。
65
+ - **双 tab 工作区选择器**(填充原生「Add workspace」流程):
66
+ - **本机** —— 走 **host 端原生系统文件夹对话框**选本地目录(或直接输入本地路径)→ 直接成为普通 DSH 本地工作区(与本地工作区共存)。优先用 DSH 的 `directoryPicker` 服务,服务缺失时**回退到插件自持的原生选择器**(macOS `osascript` / Linux `zenity`→`kdialog` / Windows `FolderBrowserDialog`)——桌面启动路径上框架服务不注册也能用。
67
+ - **远程** —— 选择器是**居中弹窗**(窄侧边栏也不会被挤压)。先**选机器** → Windows 主机根级显示 **「此电脑」多盘视图**(`C:\`、`D:\`、`E:\`…,而不是 Git Bash 的 MSYS 根),路径框**实时补全**目录(支持 `C:\Users\…` 或 `/c/Users/…` 任意写法,Windows 路径在底层自动改写为 Git Bash 形式);**选中一个目录立即列出它下一级**(OS/VSCode 式级联)。另有 **「浏览…」文件选择式浮层**(Windows 面包屑 `此电脑 / C:\ / Users / dev` 可点击跳级、驱动器行、大小/时间、跟随软链),选中**回填输入框不提交**,你复核/修改后再确定;「回上一级」任意深度可用(包括浮层直接打开在路径栏当前路径时)。**最近工作区**快捷入口、**`~` 主目录**、**新建目录**一键可达。确定会创建**真实本地镜像**(`$DSH_HOME/remote-workspaces/<host>-<user>-<port>/<basename>`;仅当同主机上**别的远端路径**已占用同名 basename 时才追加短路径 hash)→ harness 把它当真实工作区收养,同时 dsh-remote 通过 SFTP 保持同步。所选工作区会**持久化到该机器**,重启不丢。
68
+ - **Git Bash 默认终端(Windows 主机)** —— 自动探测远程平台(`cmd /c ver`,附 `uname -s` 的 MINGW/MSYS 探测兜底);Windows 机器自动定位 Git Bash(`config.shell` 可显式指定或 `native` 关闭),所有命令经 `bash -s` 从 SSH 通道 stdin 管道执行,不依赖 cmd/PowerShell,也不受引号/反斜杠转义困扰;`rw_exec` 默认在 Git Bash 形式的 cwd(`/c/Users/…`)下执行。`/dsh-remote/status`、`rw_info`、设置页「测试连接」都会报告检测到的平台与 shell。
69
+ - **Windows 路径自动改写** —— 用户输入 `C:\Users\dev\project`(或 `C:/…`、`/c/…`、`/C:/…`)时底层自动规范为 Git Bash 形式 `/c/Users/dev/project` 执行;工作区存储与展示为 Windows 形式 `C:\Users\dev\project`。模型工具全部接受并展示两种写法;SFTP 访问使用 Win32-OpenSSH 的 `/D:/…` 形式(见 `toSftpPath`)。
70
+ - **远程 `@` 补全(issue #39)** —— 远程会话里输入 `@` 会**列出远端目录树**(走 SFTP 实时读,不是本地镜像)。目录逐级下钻、无斜杠时在整棵树上模糊匹配,候选是**相对远程工作区根的路径**(`@src/main.c`),与本地会话的写法一致;`rw_*` 工具接受这种相对路径并自动拼到远程工作区根上。索引有预算保护(条目/目录/时限 + 缓存 + 失败熔断),**远端不可达时自动回退到本地镜像**(不会静默变成空列表)。本地会话完全不受影响。
71
+ - **双向 SFTP 同步(三路冲突检测)** —— `rw_sync`(远程→镜像)、`rw_push`(镜像→远程)。两边都改过的文件会列出冲突、绝不静默覆盖(`force=true` 覆盖)。默认 **深度 8 / 2000 文件**,触顶会标明 **`TRUNCATED`**。支持 dry-run、后台任务、gitignore 风格 ignore 规则。
72
+ - **模型工具(20 个)** —— `rw_info`、`rw_connect`(可 `save`)、`rw_pick_workspace`、`rw_list_dir`(大小+mtime)、`rw_stat`、`rw_read_file`(utf-8/gbk)、`rw_write_file`、`rw_edit`(字面替换 + mtime 乐观锁)、`rw_append`、`rw_mkdir`、`rw_remove`(递归、有上限)、`rw_move`、`rw_exec`(pty/env)、`rw_search`(**POSIX 快路径 `rg` → `grep -R -E`,远端两者都没有时回退 SFTP 遍历**,因此 Windows 主机也可用;遵守 ignore 规则、支持上下文行)、`rw_download`/`rw_upload`(流式 fastGet/fastPut + 体积上限)、`rw_forward`(SSH 隧道)、`rw_sync`、`rw_push`、`rw_disconnect`。
73
+ - **端口转发面板** —— 设置页或 `rw_forward` 创建/启停/删除**本地**(`127.0.0.1:port → 远端`)与**反向**(`远端 → 本地`)隧道;定义持久化,开启时重连自动恢复,断开时全部停止。
74
+ - **侧栏远程编辑** —— 远程文件 tab 可编辑并保存到远端(mtime 乐观锁,冲突返回 409 + 「重新读取」)。**v0.8.19** 起文件操作按会话绑定机器(前端带 `sessionId`),两台不同主机的会话不会共用当前机连接池。文件树显示文件大小,并有**右键菜单**(下载到本地镜像 / 重命名 / 删除 / 新建目录)。
75
+ - **命令审计** —— 每次 `rw_exec`/写/删/移动/转发都追加到 `$DSH_HOME/remote-workspaces/audit.log`(时间 · user@host · 操作 · 退出码 · 命令);设置页显示最近 30 条。
76
+ - **长任务异步化** —— `rw_sync`/`rw_push` 传 `async: true` 返回 `taskId`,通过 `/dsh-remote/task` 查询进度/结果/取消(单飞队列)。
77
+ - **连接体检** —— 设置页「测试连接」按类别提示(认证 / 网络 / 主机指纹 / 超时);延迟会缓存在机器记录里。
78
+ - 当前 `user@host:/path`(以及生效的转发)会注入每次系统提示,让 Agent 明确自己的工作根。
79
+ - **不改任何 `dsh-workspace` 官方代码** —— 全部作为普通插件实现(client 半以 `priority -100` 填充 directory-flow holes)。
80
+ - **远端跨平台** —— 文件访问走 SFTP 协议(不依赖 POSIX shell),Linux/macOS/Windows 远端都能列/读/写/搜索/同步。
81
+ - **主机指纹校验(TOFU)** —— 每次 SSH 连接都校验主机密钥(`hostKeyMode: accept-new`):首次连接记录,之后**密钥一旦变化立即拒绝**(防中间人)。`verify` 模式还会拒绝从未见过的机器;`off` 关闭校验。指纹存于 `$DSH_HOME/remote-workspaces/known_hosts.json`;误判可用 `/remote forget-key` 重置。
82
+ - **数据跟随 Harness 根目录** —— 机器清单与镜像放在 `$DSH_HOME/remote-workspaces`(桌面版即 `userData/harness` 下);0.6 之前落在 `~/.dsh/remote-workspaces` 的数据**首次启动自动迁移**,不丢失。
83
+
84
+ ## 安装
85
+
86
+ ### DSH 版本兼容性
87
+
88
+ `dsh-remote` **同时支持 `0.1.x` 与 `0.2.x` 两条 DSH 线**。自 **0.8.29** 起,所有
89
+ `@deepseek-ai/dsh-*` 的 peer 范围都是**跨线区间**(`>=0.1.0-rc.6 <0.3.0`,其中三个
90
+ `dsh-client-*` 保持各自原有的 `>=0.1.2-rc.1` 下界),而不再是 caret。
91
+
92
+ 这一点的必要性在于:DSH 会在导入 bundle **之前**,把每条 `@deepseek-ai/dsh` /
93
+ `@deepseek-ai/dsh-*` 的 peer 范围与运行时版本比对,**只要有一条不匹配就整包丢弃**:
94
+
95
+ ```
96
+ dsh: skipping profile bundle "dsh-remote": Error: Plugin dsh-remote@… is incompatible …
97
+ ```
98
+
99
+ 而 caret 在 `0.x` 上会锁死该小版本线,所以 `^0.1.0-rc.6` 永远无法容纳 `0.2.x` 运行时,
100
+ `^0.2.0-rc.1` 也永远无法容纳 `0.1.x`。**如果你用的版本低于 0.8.29,请升级**——若你在升级
101
+ DSH 后插件整个消失(没有设置页、没有 `rw_*` 工具),原因就是它。
102
+ `test/compat.test.js` 用 DSH 自己的判定谓词钉住这些范围,防止 caret 回潮。
103
+
104
+ ### 官方 Desktop 兼容适配(实验性,尚未发布)
105
+
106
+ 本分支增加对 [DeepSeek 官方 Desktop](https://github.com/deepseek-ai/deepseek-harness)
107
+ 的适配,以 `0.1.5-rc.2` Host 通信协议验证,不修改 Harness 核心:
108
+
109
+ - 通过 `ctx.connection.fetch` 注册 `/api/dsh-remote/*`,由 Desktop 的
110
+ `dsh-app:` 通道承载请求,鉴权仍由宿主负责,不启动 Web Server。
111
+ - 通过 `sidebarRightTabs` 和 `sidebar.right.pane.tab` 提供原生“远程文件”入口,
112
+ 复用原来的文件树与编辑器,不把远端路径传给本地文件预览器。
113
+ - `dsh-better-sidebar` 不再内置;Web 版可以单独安装,官方 Desktop 则使用
114
+ 原生右侧栏集成。
115
+
116
+ 已验证 Host 启动、IPC 请求、真实 SSH 的只读连接/目录列表/文本读取,以及设置页和
117
+ 测试 SSH 配置的导入。**v0.8.19** 起侧栏 `/ls` `/read` `/write` `/fs` 在请求带
118
+ `sessionId` 时按该会话的镜像绑定选机(与 `rw_*` 同一套),不再落到「当前机器」
119
+ 连接池;宿主侧测试覆盖双机会话路由与编辑 409/重读/保存。原生文件标签的完整 GUI、
120
+ 失败/取消交互、非 macOS 宿主以及旧 Web 版完整 UI 回归仍属实验性。
121
+
122
+ Desktop 安装器还可能要求明确配置 `ssh2` / `cpu-features` 可选构建脚本策略。
123
+ 隔离验证中禁用了这些可选脚本;本改动不放宽应用的构建白名单,也不自动批准脚本。
124
+
125
+ ### 已发布的 Web bundle
140
126
 
141
127
  ```bash
142
- dsh plugin add dsh-remote # add the bundle
128
+ dsh plugin add dsh-remote # 添加 bundle
143
129
  ```
144
130
 
145
- Since **v0.8.18**, `dsh-remote` installs and mounts only itself. The Web sidebar
146
- ([dsh-better-sidebar](https://www.npmjs.com/package/dsh-better-sidebar)) is
147
- optional and is no longer a dependency or an automatically mounted row. This
148
- keeps the SSH tools and settings UI independent from a particular sidebar
149
- implementation.
131
+ 从 **v0.8.18** 起,`dsh-remote` 只安装并挂载自身。Web 侧边栏
132
+ ([dsh-better-sidebar](https://www.npmjs.com/package/dsh-better-sidebar))
133
+ 改为可选,不再是依赖,也不会被自动挂载。这样 SSH 工具和设置页不再被某个侧边栏
134
+ 实现的版本/API 变化拖垮。
150
135
 
151
- To add the optional Web remote-file explorer/editor, install both bundles:
136
+ 如需 Web 版远程文件浏览/编辑,请显式安装两个 bundle:
152
137
 
153
138
  ```bash
154
139
  dsh plugin add dsh-remote
155
140
  dsh plugin add dsh-better-sidebar
156
141
  ```
157
142
 
158
- When the standalone sidebar service is present, `dsh-remote` discovers it
159
- dynamically and registers its remote explorer/editor tabs. Without it, all
160
- `rw_*` tools, the settings UI, sync, audit log, and port forwarding continue to
161
- work. Official Desktop uses its native right-sidebar seats and does not need
162
- `dsh-better-sidebar`.
163
-
164
- > **Upgrading from 0.7.2–0.8.17:** upgrading to 0.8.18 removes the embedded
165
- > sidebar dependency and mount. Install `dsh-better-sidebar` separately only if
166
- > you still want that Web UI. Any old profile override for
167
- > `id: dsh-remote-sidebar` can be removed because that row no longer exists.
168
-
169
- (or `npm install dsh-remote` + add `- id: dsh-remote / name: dsh-remote` in `cordis.patch.yml`).
170
-
171
- ## Quick start
172
-
173
- 1. **Add a machine** — Settings → 远程工作区 → add host/port/user + key or password → (optional) set it current.
174
- 2. **Open a workspace** — click **Add workspace** in the sidebar / conversation:
175
- - **本机** → system folder chooser (or type a local path) → local workspace.
176
- On hosts without a usable OS dialog (DSH Desktop's browse backend, headless
177
- SSH hosts without zenity/kdialog) the in-app directory browser pops up
178
- instead — breadcrumbs, Windows drive switch, new-folder, pick-and-fill.
179
- - **远程** → choose the machine → browse to a remote directory (or type `/path`) → "设为远程工作区" ⇒ a local mirror workspace is created and adopted.
180
- 3. **Work with the agent** — treat it like any workspace:
181
- - `rw_list_dir(path?)`/`rw_read_file` — inspect remote files
182
- - `rw_write_file(path, content)` / `rw_edit(path, old, new)` — create / patch a remote file directly
183
- - `rw_stat(path)` / `rw_mkdir(path)` / `rw_remove(path, recursive?)` / `rw_move(path, dest)` — manage remote paths
184
- - `rw_search(pattern, path?)` — grep remote files (SFTP walk, Windows OK)
185
- - `rw_exec(command, cwd?, pty?)` — run remote shell commands (defaults to the workspace dir)
186
- - `rw_forward(listenPort, targetHost?, targetPort?)` — open an SSH tunnel
187
- - `rw_sync(dryRun?/force?/async?)` / `rw_push(dryRun?/force?/async?)` — conflict-aware mirror pull/push
188
-
189
- ## CLI defaults (optional)
190
-
191
- Provide a default machine in `cordis.patch.yml`:
143
+ 独立侧边栏 service 存在时,`dsh-remote` 会动态发现它并注册远程文件 tab;
144
+ 不安装时,`rw_*` 工具、设置页、同步、审计日志和端口转发均照常工作。
145
+ 官方 Desktop 使用原生右侧栏,不需要安装 `dsh-better-sidebar`。
146
+
147
+ > **从 0.7.2–0.8.17 升级:** 升到 0.8.18 后,内嵌侧边栏依赖和挂载会消失。
148
+ > 只有仍需要 Web 侧边栏 UI 时才单独安装 `dsh-better-sidebar`。旧 profile 里针对
149
+ > `id: dsh-remote-sidebar` 的覆盖可以删除,因为这行已不存在。
150
+
151
+ (或 `npm install dsh-remote`,再在 `cordis.patch.yml` 加 `- id: dsh-remote / name: dsh-remote`。)
152
+
153
+ ## 快速上手
154
+
155
+ 1. **加一台机器** —— 设置 → 远程工作区 → 填 host/port/user + 密码或 key →(可选)设为当前。
156
+ > **保存 ≠ 激活(v0.8.8+)**:保存的机器只是备用连接,不会自动进入任何 session 的
157
+ > remote context。只有「设为当前」(或 Agent 显式调用 `rw_connect`)才激活当前机器;
158
+ > 「取消设为当前」可回到 `active remote = none`。
159
+ 2. **选工作区** —— 点侧边栏/会话的 **Add workspace**:
160
+ - **本机** → 系统文件夹选择(或输入本地路径)→ 本地工作区。
161
+ 宿主没有可用的系统对话框时(DSH Desktop 的 browse 后端、无 zenity/kdialog 的无头
162
+ SSH 主机),改为弹出插件内置的目录浏览器——面包屑、Windows 盘符切换、新建目录、
163
+ 选中回填。
164
+ - **远程** → 选机器 → 浏览到远程目录(或输入 `/path`)→ 「设为远程工作区」⇒ 创建并收养一个本地镜像工作区。
165
+ 3. **让 Agent 工作** —— 把它当普通工作区用:
166
+ - `rw_list_dir(path?)` / `rw_read_file` / `rw_stat` —— 查看远程文件
167
+ - `rw_write_file` / `rw_edit` / `rw_append` —— 创建、补丁、追加远程文件
168
+ - `rw_mkdir` / `rw_remove` / `rw_move` —— 管理远程路径
169
+ - `rw_search(pattern, path?)` —— 远程 grep(POSIX 走 rg/grep,否则 SFTP 遍历)
170
+ - `rw_exec(command, cwd?, pty?)` —— 在远程执行命令(默认在工作区目录)
171
+ - `rw_forward` —— SSH 隧道
172
+ - `rw_sync` / `rw_push` —— 冲突感知的镜像拉取/推送
173
+
174
+ > **Remote context 是 session 级的(v0.8.8+)**:system prompt 只会在**当前 session 的
175
+ > cwd 位于某个远程 mirror 内**(即你把远程目录选成了这个 session 的工作区)时注入
176
+ > 「Remote workspace」段落;普通本地 session 不注入、侧边栏「远程文件」也不显示任何
177
+ > 机器默认目录,模型不会主动调用 `rw_*`。混合访问(本地 + 远程同屏比较)请通过显式
178
+ > 选择远程工作区进行。
179
+
180
+ ## 可选:CLI 默认机
181
+
182
+ 可在 `cordis.patch.yml` 提供默认机:
192
183
 
193
184
  ```yaml
194
- # Example only — use values for your own machine.
185
+ # 示例:请换成你自己的机器
195
186
  - id: dsh-remote
196
187
  name: dsh-remote
197
188
  config:
198
- host: 203.0.113.10 # or your real host / hostname
189
+ host: 203.0.113.10 # 或你的真实主机 / hostname
199
190
  port: 22
200
191
  username: dev
201
192
  privateKeyPath: ~/.ssh/id_rsa
202
- # or password: '…'
193
+ # 或用密码登录:
194
+ # password: '…'
203
195
  workspace: ~/project
204
196
  ```
205
197
 
206
- If `host` is empty the plugin starts disconnected and you configure machines in the UI.
198
+ 若 `host` 为空,插件启动时处于断开状态,在 UI 里配置机器即可。
207
199
 
208
- ## CLI quick reference
200
+ ## 常用命令(安装 / 查看 / 启动)
209
201
 
210
- Installing and driving DSH may live in different shells, so both the `dsh` binary and the `npx` form are shown. Always tell DSH **which profile** to use with `--profile <name>` (usually `web`).
202
+ DSH 的 `dsh` 可能不在某些 shell 的 PATH(比如 Windows PowerShell 里在某个仓库目录下),所以同时列出 `dsh` 与 `npx` 两种写法。操作都要用 `--profile <name>` 指定 profile(一般 `web`):
211
203
 
212
204
  ```bash
213
- # install the bundle into a profile (npm is pulled by pnpm; recommended)
205
+ # 安装(从 npm 拉到 profile)
214
206
  dsh plugin --profile web add dsh-remote
215
- # same but when `dsh` is not on PATH (e.g. Windows PowerShell inside a repo)
207
+ # 同一效果:当 `dsh` 不在 PATH 时用 npx
216
208
  npx --yes @deepseek-ai/dsh plugin --profile web add dsh-remote
217
209
 
218
- # confirm it is installed wire
210
+ # 确认已装
219
211
  dsh plugin --profile web list
220
212
  npx --yes @deepseek-ai/dsh plugin --profile web list
221
213
 
222
- # start the web surface (reload profile; the plugin activates on boot)
214
+ # 启动 web 界面(重载 profile,新插件在启动时生效)
223
215
  dsh --profile web
224
- npx --yes @deepseek-ai/dsh --profile web # http://127.0.0.1:3080
216
+ npx --yes @deepseek-ai/dsh --profile web # 访问 http://127.0.0.1:3080
225
217
 
226
- # use a local checkout instead of the npm version (dev iteration)
227
- npx --yes @deepseek-ai/dsh plugin --profile web add /path/to/dsh-remote
228
- npx --yes @deepseek-ai/dsh plugin --profile web remove dsh-remote # back to release
218
+ # 迭代用本地源码替换 npm 版(便于改 dsh 插件代码后即测)
219
+ npx --yes @deepseek-ai/dsh plugin --profile web add D:/path/to/dsh-remote
220
+ npx --yes @deepseek-ai/dsh plugin --profile web remove dsh-remote # 恢复用发行版
229
221
  ```
230
222
 
231
- After a successful start, `Settings → 远程工作区` appears and the "Add workspace" flow gains the 本机 / 远程 tabs (screenshots above).
223
+ 启动成功后,设置 →「远程工作区」会出现;「Add workspace」流程会带「本机 / 远程」两个 tab(见上方效果图)。
232
224
 
233
- ## Development (sandbox, not product)
225
+ ## 开发(沙箱优先,勿改产品)
234
226
 
235
- Iterate **in the sandbox**, never by hand-editing a product profile — the
236
- product profile is re-managed by the plugin manager and reverts hand-deployed
237
- files on reinstall. Use the helper script:
227
+ 迭代**一律在沙箱**里做,绝不手工改产品 profile——产品 profile 由插件管理器重管,
228
+ 重装会把手工部署的文件还原掉。用仓库内的辅助脚本:
238
229
 
239
230
  ```bash
240
- scripts/dev-run.sh --restart # start / restart the isolated sandbox
241
- scripts/dev-run.sh --stop # stop it
242
- scripts/dev-run.sh --status # is it running?
231
+ scripts/dev-run.sh --restart # 启动 / 重启隔离沙箱
232
+ scripts/dev-run.sh --stop # 停止
233
+ scripts/dev-run.sh --status # 是否在运行
243
234
  ```
244
235
 
245
- - Runs its own DSH instance (`dev-harness/harness` inside this repo) with the
246
- plugin copied in from `lib/` — it boots through the same `bin.js web --patch`
247
- path as the desktop app, so the sandbox reproduces the product boot behavior.
248
- - The sandbox web UI serves on `http://127.0.0.1:50599` and the plugin routes
249
- are live immediately (e.g. `GET /dsh-remote/machines`).
250
- - **Host-half changes** (`lib/index.js`) need a sandbox restart (`--restart`);
251
- **client-half changes** (`lib/client.js`) need a page refresh.
252
- - Node ESM resolves dependencies from the importing file's real path, so the
253
- script **copies** `lib/` (hardlink copy, `cp -al`) into the sandbox profile
254
- instead of symlinking — a symlink breaks `@deepseek-ai/*` resolution.
255
- - Run `scripts/check.mjs` (static framework-constraint gate: command-name
256
- regex, …) before every commit; `scripts/boot-smoke.sh` boots an isolated
257
- instance to prove the plugin still starts.
258
- - Full rules live in `scripts/dev-standards.md` (command names, cordis service
259
- access via `ctx.get()` only, optional framework services may never register,
260
- verify third-party callback contracts against the real runtime, …).
261
-
262
- Deploying to a product profile is a separate, explicit action (`./sync.sh`)
263
- and should be done only when you intend to release.
264
-
265
- ## Configuration
266
-
267
- | Key | Type | Default | Meaning |
236
+ - 自带一套独立 DSH 实例(仓库内 `dev-harness/harness`),把 `lib/` 复制进沙箱
237
+ profile——与桌面 App 走同一条 `bin.js web --patch` 启动路径,沙箱即产品启动行为。
238
+ - 沙箱 web UI 在 `http://127.0.0.1:50599`,插件路由立即可见(如
239
+ `GET /dsh-remote/machines`)。
240
+ - **宿主半改动**(`lib/index.js`)需重启沙箱(`--restart`);**客户端半改动**
241
+ (`lib/client.js`)只需刷新页面。
242
+ - Node ESM 按导入文件的真实路径解析依赖,脚本用**硬链接拷贝**(`cp -al`)把
243
+ `lib/` 复制进沙箱 profile,而不是软链——软链会破坏 `@deepseek-ai/*` 的解析。
244
+ - 每次提交前跑 `node check.mjs`(静态框架约束闸门:命令名正则等);
245
+ `scripts/boot-smoke.sh` 用隔离实例证明插件仍能启动。
246
+ - 完整规则见 `scripts/dev-standards.md`(命令名、cordis 服务只许 `ctx.get()`、
247
+ 可选框架服务可能压根不注册、三方库回调契约以真实运行为准等)。
248
+
249
+ 部署到产品 profile 是单独的受控动作(`./sync.sh`),只在确定要发布时做。
250
+
251
+ ## 配置
252
+
253
+ | 键 | 类型 | 默认 | 说明 |
268
254
  | --- | --- | --- | --- |
269
- | `host` | string | `''` | default SSH host (else start disconnected) |
270
- | `port` | int | `22` | default SSH port |
271
- | `username` | string | `''` | default SSH user |
272
- | `password` | string | `''` | default SSH password (non-empty overrides key) |
273
- | `privateKeyPath` | string | `''` | private key path (used only when explicitly provided) |
274
- | `passphrase` | string | `''` | passphrase for an encrypted private key |
275
- | `workspace` | string | `''` | default remote workspace path |
276
- | `shell` | string | `''` | remote command terminal strategy: `''`=auto-detect (Git Bash on Windows remotes), `'git-bash'`=prefer Git Bash, `'native'`=never wrap, anything else=explicit bash.exe path (e.g. `C:\Program Files\Git\bin\bash.exe`) |
277
- | `commandTimeoutMs` | int | 20000 | per remote command timeout |
278
- | `connectTimeoutMs` | int | 15000 | SSH connect timeout |
279
- | `maxFileBytes` | int | 52428800 | skip mirroring/reading files larger than this (0 = no cap) |
280
- | `hostKeyMode` | string | `accept-new` | host-key policy: `accept-new` (TOFU), `verify` (reject unknown hosts), `off` (skip) |
281
- | `useAgent` | bool | `false` | authenticate via the OpenSSH agent (`SSH_AUTH_SOCK`) |
282
- | `keyboardInteractive` | bool | `false` | allow keyboard-interactive auth (OTP/MFA) with the configured password |
283
- | `proxy` | object | — | jump host: `{ host, port?, username?, password?, privateKeyPath? }` |
284
- | `autoPush` | bool | `false` | auto-push edited mirror files back to the remote (watcher, debounced) |
285
- | `auditLog` | bool | `true` | append executed commands to `$DSH_HOME/remote-workspaces/audit.log` |
286
- | `encoding` | string | `utf-8` | text encoding for remote file reads/writes (e.g. `gbk`) |
287
- | `fileReference` | bool | `true` | remote `@` completion: in a remote session `@` lists the **remote** tree over SFTP (issue #39); off → only the local mirror |
288
- | `fileReferenceMaxResults` | int | `20` | max `@` candidates rendered for one query |
289
- | `fileReferenceMaxEntries` | int | `3000` | max entries retained in one remote workspace's `@` index |
290
- | `fileReferenceExcludedDirectories` | string[] | `[.git, node_modules, dist, build, out, coverage, target, .next, .nuxt, .turbo, .venv, __pycache__, .pytest_cache, .mypy_cache, .gradle]` | directory basenames the remote `@` traversal skips |
291
- | `fileReferenceTimeoutMs` | int | `4000` | wall-clock budget for one remote `@` index pass (on expiry the partial index answers rather than making the caret wait) |
255
+ | `host` | string | `''` | 默认 SSH 主机(空=断开) |
256
+ | `port` | int | `22` | 默认 SSH 端口 |
257
+ | `username` | string | `''` | 默认 SSH 用户 |
258
+ | `password` | string | `''` | 默认 SSH 密码(非空覆盖 key) |
259
+ | `privateKeyPath` | string | `''` | 私钥路径(仅在显式提供时使用) |
260
+ | `passphrase` | string | `''` | 加密私钥的 passphrase |
261
+ | `workspace` | string | `''` | 默认远程工作区路径 |
262
+ | `shell` | string | `''` | 远程命令终端策略:`''`=自动检测(Windows 找 Git Bash)、`'git-bash'`=优先 Git Bash、`'native'`=不包装、其他=显式 bash.exe 路径(如 `C:\Program Files\Git\bin\bash.exe`) |
263
+ | `commandTimeoutMs` | int | 20000 | 单条远程命令超时 |
264
+ | `connectTimeoutMs` | int | 15000 | SSH 连接超时 |
265
+ | `maxOutputChars` | int | 200000 | 单条远程命令捕获的 stdout/stderr 上限 |
266
+ | `maxFileBytes` | int | 52428800 | 镜像同步时跳过超过该大小的文件(0=不设上限) |
267
+ | `hostKeyMode` | string | `accept-new` | 主机指纹策略:`accept-new`(首次信任)、`verify`(拒绝未知主机)、`off`(跳过校验) |
268
+ | `useAgent` | bool | `false` | 用 OpenSSH agent(`SSH_AUTH_SOCK`)认证 |
269
+ | `keyboardInteractive` | bool | `false` | 允许 keyboard-interactive 认证(OTP/MFA)并复用配置的密码 |
270
+ | `proxy` | object | — | 跳板机:`{ host, port?, username?, password?, privateKeyPath? }` |
271
+ | `autoPush` | bool | `false` | 镜像内文件被编辑后自动推回远端(watcher,带防抖) |
272
+ | `auditLog` | bool | `true` | 把执行的命令追加到 `$DSH_HOME/remote-workspaces/audit.log` |
273
+ | `encoding` | string | `utf-8` | 远程文件读写的文本编码(如 `gbk`) |
274
+ | `fileReference` | bool | `true` | 远程 `@` 补全:远程会话的 `@` 列出**远端**目录树(issue #39);关闭则只有本地镜像 |
275
+ | `fileReferenceMaxResults` | int | `20` | 一次 `@` 查询最多返回多少候选 |
276
+ | `fileReferenceMaxEntries` | int | `3000` | 一棵远程工作区索引最多保留多少条目 |
277
+ | `fileReferenceExcludedDirectories` | string[] | `[.git, node_modules, dist, build, out, coverage, target, .next, .nuxt, .turbo, .venv, __pycache__, .pytest_cache, .mypy_cache, .gradle]` | 远程 `@` 遍历跳过的目录名 |
278
+ | `fileReferenceTimeoutMs` | int | `4000` | 一次远程索引遍历的墙钟预算(超时用已扫到的部分结果,不让光标等) |
279
+ | `updateMode` | string | `auto` | 自更新模式:`auto`=加载时及每 6 小时检查并自动应用、`manual`=仅在手动检查时查、`off`=完全不查。**0.8.27 起默认 `auto`**——之所以现在才安全,是因为 0.8.24 补上了宿主半热切换 |
280
+ | `updateCheckIntervalMs` | int | 21600000(6h) | `auto` 模式检查 npm 的间隔(下限 60000) |
281
+ | `updateAutoReload` | bool | `true` | 更新落地后自动热切换宿主半;`false` 则留到下次启动,设置页会显示 `pendingReload` |
282
+
283
+ > 权威清单是 `lib/index.js` 里的 `Config` schema,本表与之一致。
284
+
285
+ ## 常见问题 / 排查
292
286
 
293
- ## FAQ / troubleshooting
287
+ **`@` 能列出远程文件,但内置的读文件工具打不开** —— harness 自带的文件工具看到的是会话的**本地镜像**(`$DSH_HOME/remote-workspaces/…`),要等 `rw_sync` 下载后才有内容。读远程文件请用 `rw_read_file` 或侧栏的远程文件 tab:远程会话里的 `@src/main.c` 指 `<远程工作区>/src/main.c`,所有 `rw_*` 工具会把这种相对路径解析到远程工作区根。完全看不到候选?远端不可达时 `@` 索引会回退到本地镜像,设置页「测试连接」会告诉你原因。
294
288
 
295
- **`@` lists remote files but the built-in read tool cannot open them** — the harness's own file tools see the session's **local mirror** (`$DSH_HOME/remote-workspaces/…`), which stays empty until `rw_sync` downloads it. Read remote files with `rw_read_file` / the sidebar remote tab: `@src/main.c` in a remote session means `<remote workspace>/src/main.c`, and every `rw_*` tool resolves such a relative path against the remote workspace root. Seeing nothing at all? The remote `@` index falls back to the mirror when the host is unreachable, and the settings page's 测试连接 shows why.
289
+ **主机指纹变了 / 提示可能中间人** —— 主机重装过或密钥更换过:`/remote-forget-key`(或设置页 → 机器 → 重新信任),下次连接重新记录。
296
290
 
297
- **Host key 变了 / 提示可能中间人** — 主机重装过或密钥更换过:`/remote-forget-key`(或设置页 → 机器 → 重新信任),下次连接重新记录。
291
+ **连接报「认证失败」** —— 检查用户名/密码/私钥路径;私钥加密了要填 Passphrase;公司机器要求 OTP/动态码时勾选 keyboard-interactive。
298
292
 
299
- **连接报"认证失败"** — 检查用户名/密码/私钥路径;私钥加密了要填 Passphrase;公司机器要求 OTP/动态码时勾选 keyboard-interactive。
293
+ **连不上内网机器** —— 走跳板机:机器表单里填「跳板机」主机(也可以先把它本身配成一台机器)。主机不可达类错误会给出分类提示。
300
294
 
301
- **连不上内网机器** — 走跳板机:机器表单里填「跳板机」主机(也可以先把它本身配成一台机器)。主机不可达类错误会给出分类提示。
295
+ **`rw_sync`/`rw_push` 报冲突** —— 远端和本地都改过同一个文件时会跳过并列出冲突(绝不静默覆盖)。处理:手动合并后重新同步,或用 `force=true` 以一边为准。
302
296
 
303
- **rw_sync/rw_push 报冲突** — 远端和本地都改过同一个文件时会跳过并列出冲突(绝不静默覆盖)。处理:手动合并后重新同步,或用 `force=true` 以一边为准。
297
+ **Windows 远程** —— 列表/读写/搜索/同步全部走 SFTP 协议,不依赖 POSIX shell;中文文件用 `encoding=gbk` 读。
304
298
 
305
- **Windows 远程** — 列表/读写/搜索/同步全部走 SFTP 协议,不依赖 POSIX shell;中文文件用 `encoding=gbk` 读。
299
+ **镜像里没有某个目录** —— 默认 ignore 规则(`.git`、`node_modules`、`target` 等)会跳过;在 `$DSH_HOME/remote-workspaces/.dsh-remote-ignore` 加 `!` 之外的条目即可调整(gitignore 语法)。
306
300
 
307
- **镜像里没有某个目录** — 默认 ignore 规则(`.git`、`node_modules`、`target` 等)会跳过;在 `$DSH_HOME/remote-workspaces/.dsh-remote-ignore` 加 `!` 之外的条目即可调整(gitignore 语法)。
301
+ **侧边栏远程文件保存失败(409)** —— 远端文件在你打开后已被改动,重新读取后再编辑(mtime 乐观锁保护)。
308
302
 
309
- **侧边栏远程文件保存失败(409)** — 远端文件在你打开后已被改动,重新读取后再编辑(mtime 乐观锁保护)。
303
+ **密码怎么加密保存** —— 机器表单勾选「加密保存密码」:macOS 用系统钥匙串(security),Windows 用 DPAPI,Linux 需要 secret-tool(libsecret);后端不可用时自动回退明文。
310
304
 
311
- **密码怎么加密保存** — 机器表单勾选「加密保存密码」:macOS 用系统钥匙串(security),Windows 用 DPAPI,Linux 需要 secret-tool(libsecret);后端不可用时自动回退明文。
305
+ **升级后插件整个不见了** —— 多半是 DSH 兼容性判定把 bundle 丢弃了(见上文「DSH 版本兼容性」)。升到 **0.8.29+** 即可同时兼容 `0.1.x` / `0.2.x`。
312
306
 
313
- ## Safety
307
+ ## 安全提醒
314
308
 
315
- Giving the plugin a machine's credentials lets the agent run **shell commands as your user** on that host. Only add machines you trust. Passwords are saved on the local machine file (or the OS keychain when enabled); treat it as sensitive (you may lock file ACLs). Every executed command is recorded in the audit log when `auditLog` is on — review it from the Settings page.
309
+ 把机器凭据交给插件,等于允许 Agent 以你的用户身份在主机上执行 **shell 命令**。只添加你可信的机器。密码保存在本机文件里(或启用后的系统钥匙串),请当作敏感数据处理(可收紧文件 ACL)。开启 `auditLog` 时每条执行的命令都会记入审计日志——可在设置页查看。
316
310
 
317
311
  ## License
318
312
 
319
313
  MIT
320
314
 
321
- ## Contributing
315
+ ## 参与贡献
322
316
 
323
- Contributions are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md). Questions, setups and "is this supported?" go to [Discussions](https://github.com/flymysql/dsh-remote/discussions); reproducible bugs go to [Issues](https://github.com/flymysql/dsh-remote/issues).
317
+ 欢迎贡献,请先阅读 [CONTRIBUTING.md](./CONTRIBUTING.md)。使用问题、环境配置、「支持 XX 吗」这类讨论请走 [讨论区](https://github.com/flymysql/dsh-remote/discussions);可复现的缺陷请提 [Issue](https://github.com/flymysql/dsh-remote/issues)。
324
318
 
325
- Thanks to everyone who has landed a change here (merged PRs in parentheses):
319
+ 感谢以下已合并 PR 的贡献者:
326
320
 
327
321
  [@dahaipeng](https://github.com/dahaipeng) (#31) ·
328
322
  [@YiHui-Liu](https://github.com/YiHui-Liu) (#28) ·
329
323
  [@nekomona](https://github.com/nekomona) (#24) ·
324
+ [@zhz1667](https://github.com/zhz1667) (#43) ·
330
325
  [FoolishWiser](https://github.com/FoolishWiser) (#17) ·
331
326
  [@jace1cch](https://github.com/jace1cch) (#16) ·
332
327
  [@Minggle](https://github.com/Minggle) (#10) ·
333
328
  [4FMTWRV](https://github.com/4FMTWRV) (#6) ·
334
- [glzhangzhi](https://github.com/glzhangzhi) (per-session SSH pool fix)
329
+ [glzhangzhi](https://github.com/glzhangzhi)(per-session SSH 连接池修复)
335
330
 
336
- ## Changelog
331
+ ## 变更记录
337
332
 
338
- See [CHANGELOG.md](./CHANGELOG.md).
333
+ 见 [CHANGELOG.md](./CHANGELOG.md)。