dsh-remote 0.8.33 → 0.8.35

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/README.en.md CHANGED
@@ -22,169 +22,98 @@ The harness Web UI intentionally binds `127.0.0.1` (the CLI rejects `--host 0.0.
22
22
 
23
23
  ## Data collection / telemetry
24
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.
25
+ One anonymous heartbeat per launch (at least 6 hours apart), used only to measure usage: **de-duplicated daily active installs, the version actually running, and platform distribution**. npm download counts are release-driven and include mirrors/crawlers, and GitHub clones include CI, so neither can answer that.
26
26
 
27
- **What is sent** — exactly five fields, and nothing else:
27
+ Exactly five fields are sent: `idHash` (a pseudonym, `HMAC-SHA256('dsh-remote/telemetry/v1', installId)`), `version`, `platform`, `arch`, `node`. **Not sent:** hostnames, usernames, paths, IPs, SSH hosts/ports/keys, your machine list, session content. The raw `installId` (`<DSH_HOME>/.dsh-remote-install-id`) never leaves your machine — only its HMAC is transmitted.
28
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.
29
+ The heartbeat is fire-and-forget: it never blocks loading and failures are ignored.
42
30
 
43
31
  [live usage stats](https://flymysql.github.io/dsh-remote/stats/) · [plugin homepage](https://flymysql.github.io/dsh-remote/)
44
32
 
45
33
  ## Screen previews
46
34
 
47
- Settings → **远程工作区** — a multi-machine SSH registry (add / edit / delete / set-current, password stored locally):
35
+ **Settings → 远程工作区** — machine list, advanced config (key / jump host / agent), connection check, port forwarding, audit log, update:
48
36
 
49
- <img src="docs/ui-settings-panel.png" alt="dsh-remote settings — multi-machine registry (light theme, host scrubbed)" width="720"/>
37
+ <img src="docs/shots/settings-panel.png" alt="dsh-remote settings: machine list, advanced config, port forwarding, audit log, update" width="640"/>
50
38
 
51
- The native **"Add workspace" / "Select workspace"** flow — a centered modal, two tabs, opens on **本机 (local)**; switch to **远程 (remote)**:
39
+ The native **"Add workspace"** flow — a centered modal with two tabs, opening on Local; here switched to **Remote**:
52
40
 
53
- - **远程** — 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 **设为远程工作区**.
41
+ <img src="docs/shots/picker-dialog.png" alt="The 远程 (remote) tab of the workspace picker: machine select, recent workspaces, browse, set-as-remote-workspace" width="632"/>
54
42
 
55
- Real capture (host scrubbed to a placeholder):
56
-
57
- <img src="docs/ui-picker-panel.png" alt="dsh-remote workspace picker — real dialog; 本机 (local) tab; 远程 machine select + prefilled root path + autocomplete" width="720"/>
43
+ - The path field autocompletes live; on Windows hosts the root shows a multi-drive view; the floating browser fills the field without committing.
44
+ - On confirm a **real local mirror** is created and adopted by the harness, kept in sync over SFTP; the choice persists on the machine.
58
45
 
59
46
  ---
60
47
 
61
48
  ## Features
62
49
 
63
- - **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).
64
- - **`~/.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.
65
- - **Two-tab workspace picker** (fills the native "Add workspace" flow):
66
- - **本机 / 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.
67
- - **远程 / 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.
68
- - **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.
69
- - **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`).
70
- - **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.
71
- - **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**.
72
- - **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`** (POSIX fast path `rg` → `grep -R -E`, falling back to an SFTP tree walk when the host has neither — so it works on Windows remotes too; honors ignore rules and context lines), `rw_download`/`rw_upload` (streaming fastGet/fastPut + size caps), **`rw_forward`** (SSH tunnels), `rw_sync`, `rw_push`, `rw_disconnect`.
73
- - **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.
74
- - **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** (下载到本地镜像 / 重命名 / 删除 / 新建目录).
75
- - **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.
76
- - **Async long tasks** — `rw_sync`/`rw_push` with `async: true` return a `taskId`; progress/result/cancel via `/dsh-remote/task` (single-flight queue).
77
- - **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.
78
- - The active `user@host:/path` is injected into every system prompt (plus active forwards).
79
- - **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`).
80
- - **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.
81
- - **Host-key verification (TOFU)** — every SSH connect verifies the host key
82
- (`hostKeyMode: accept-new`): first connect records it, a later CHANGE is rejected
83
- as a possible man-in-the-middle. `verify` also refuses hosts never seen before;
84
- `off` disables it. Stored at `$DSH_HOME/remote-workspaces/known_hosts.json`; reset
85
- with `/remote forget-key`.
86
- - **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.
50
+ ![Capabilities at a glance — multi-machine SSH, live alias resolution, the two-tab picker, three-way sync, remote @ completion, audit, port forwarding, sidebar editing, self-update, and the 20 rw_* tools](docs/shots/features.png)
51
+
52
+ The image above is the overview. What follows is only what the image does not make obvious.
53
+
54
+ **Workspace picker** (fills the native "Add workspace" flow) — Local uses the system folder chooser; Remote browses inside the modal:
55
+
56
+ <img src="docs/shots/picker-dialog.png" alt="The 远程 (remote) tab of the workspace picker: machine select, recent workspaces, browse, set-as-remote-workspace" width="632"/>
57
+
58
+ - The path field autocompletes live; on Windows hosts the root shows a multi-drive view; the floating browser fills the field without committing.
59
+ - On confirm a **real local mirror** is created and adopted by the harness, kept in sync over SFTP; the choice persists on the machine.
60
+
61
+ **Settings** (machine list, connection check, port forwarding, audit log, update mode):
62
+
63
+ <img src="docs/shots/settings-panel.png" alt="dsh-remote settings: machine list, advanced config, port forwarding, audit log, update" width="640"/>
64
+
65
+ The rest:
66
+
67
+ - **20 model tools** (listed so they can be copied or searched): `rw_info`, `rw_connect`, `rw_pick_workspace`, `rw_list_dir`, `rw_stat`, `rw_read_file`, `rw_write_file`, `rw_edit`, `rw_append`, `rw_mkdir`, `rw_remove`, `rw_move`, `rw_exec`, `rw_search`, `rw_download`, `rw_upload`, `rw_sync`, `rw_push`, `rw_forward`, `rw_disconnect`.
68
+ - **Cross-platform remotes** — all file access is SFTP-protocol-level (no POSIX shell), so Linux/macOS/Windows remotes all work.
69
+ - **Windows remotes** — the platform is auto-detected and commands go through `bash -s` over stdin, so quoting and backslash escaping are never an issue (`config.shell` can pin a path or `native` disables wrapping); `C:\Users\dev` and `/c/Users/dev` are both accepted.
70
+ - **Async long tasks** — `rw_sync`/`rw_push` with `async: true` return a `taskId` with progress/result/cancel.
71
+ - **Data lives under the harness home** — machines and mirrors follow `$DSH_HOME`; pre-0.6 data migrates automatically on first run.
72
+ - **No `dsh-workspace` core changes** — everything ships as a normal plugin.
87
73
 
88
74
  ## Install
89
75
 
90
76
  ### DSH version compatibility
91
77
 
92
- `dsh-remote` runs on **both** the `0.1.x` and `0.2.x` DSH lines. Since **0.8.29** every
93
- `@deepseek-ai/dsh-*` peer range is a cross-line interval (`>=0.1.0-rc.6 <0.3.0`, with the
94
- three `dsh-client-*` peers keeping their `>=0.1.2-rc.1` lower bound) instead of a caret.
95
-
96
- This matters because DSH validates each `@deepseek-ai/dsh` / `@deepseek-ai/dsh-*` peer
97
- range against the single runtime version *before* importing a bundle, and **drops the whole
98
- bundle** when any range does not match:
78
+ Runs on **both** the `0.1.x` and `0.2.x` DSH lines. DSH validates every `@deepseek-ai/dsh-*` peer range *before* importing a bundle and **drops the whole bundle** when any range does not match (no settings panel, no `rw_*` tools):
99
79
 
100
80
  ```
101
81
  dsh: skipping profile bundle "dsh-remote": Error: Plugin dsh-remote@… is incompatible …
102
82
  ```
103
83
 
104
- A caret on a `0.x` version is locked to that minor line, so `^0.1.0-rc.6` can never admit a
105
- `0.2.x` runtime — and `^0.2.0-rc.1` can never admit a `0.1.x` one. If you are on a version
106
- **older than 0.8.29**, upgrade; if your plugin disappeared entirely after a DSH upgrade,
107
- this is why. `test/compat.test.js` pins the ranges against DSH's own predicate so a caret
108
- cannot creep back in.
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.
84
+ A caret on `0.x` is locked to that minor line (`^0.1.x` cannot admit `0.2.x`, and vice versa), so since **0.8.29** the ranges are cross-line intervals: `>=0.1.0-rc.6 <0.3.0`. **If you are below 0.8.29, upgrade the plugin before you upgrade DSH.**
138
85
 
139
- ### Published Web bundle
86
+ ### Official Desktop compatibility (experimental)
140
87
 
141
- ```bash
142
- dsh plugin add dsh-remote # add the bundle
143
- ```
88
+ A compatibility path for the [official DeepSeek Harness Desktop](https://github.com/deepseek-ai/deepseek-harness), tested against the `0.1.5-rc.2` Host transport; it does not replace the harness core or require a listening Web server:
144
89
 
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.
90
+ - SSH settings and the directory picker use `/api/dsh-remote/*` over the Desktop's `dsh-app:` carrier (registered on `ctx.connection.fetch`).
91
+ - A native Remote Files entry uses `sidebarRightTabs`, giving remote files session-scoped resource addresses instead of sending remote paths to the local Files viewer.
92
+ - `dsh-better-sidebar` is not bundled; official Desktop uses the native right sidebar.
150
93
 
151
- To add the optional Web remote-file explorer/editor, install both bundles:
94
+ Verified: host startup, IPC requests, real SSH read-only connect/list/read, and per-session routing (sidebar `/ls` `/read` `/write` `/fs` with `sessionId`). The native file-tab GUI, failed/cancelled dialogs and non-macOS hosts remain experimental. The Desktop installer may need an explicit policy for the optional `ssh2` / `cpu-features` build scripts.
95
+
96
+ ### Published Web bundle
152
97
 
153
98
  ```bash
154
99
  dsh plugin add dsh-remote
155
- dsh plugin add dsh-better-sidebar
156
100
  ```
157
101
 
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`.
102
+ Since **v0.8.18** it installs and mounts only itself; the Web sidebar ([dsh-better-sidebar](https://www.npmjs.com/package/dsh-better-sidebar)) is optional. Install it separately if you want the Web remote file explorer/editor — without it the `rw_*` tools, settings UI, sync, audit log and port forwarding all still work.
163
103
 
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.
104
+ > **Upgrading from 0.7.2–0.8.17:** the embedded sidebar goes away; any old profile override for `id: dsh-remote-sidebar` can be removed.
168
105
 
169
106
  (or `npm install dsh-remote` + add `- id: dsh-remote / name: dsh-remote` in `cordis.patch.yml`).
170
107
 
171
108
  ## Quick start
172
109
 
173
- 1. **Add a machine** — Settings → 远程工作区 → add host/port/user + key or password → (optional) set it current.
110
+ 1. **Add a machine** — Settings → 远程工作区 → host/port/user + key or password → set it current.
174
111
  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
112
+ - **Local** → system folder chooser (or type a path) → local workspace. Falls back to the in-app browser when no OS dialog exists.
113
+ - **Remote** → choose the machine → browse to a remote directory (or type `/path`) → "设为远程工作区" ⇒ a local mirror workspace is created and adopted.
114
+ 3. **Work with the agent** — treat it like any workspace: `rw_read_file` / `rw_write_file` / `rw_edit` / `rw_exec` / `rw_search` / `rw_sync` / `rw_push` / `rw_forward` (full list above).
115
+
116
+ > **Remote context is session-scoped:** the "Remote workspace" system-prompt section appears only when the current session's workspace is a remote mirror; local sessions are unaffected and the model will not call `rw_*` on its own.
188
117
 
189
118
  ## CLI defaults (optional)
190
119
 
@@ -232,9 +161,7 @@ After a successful start, `Settings → 远程工作区` appears and the "Add wo
232
161
 
233
162
  ## Development (sandbox, not product)
234
163
 
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:
164
+ Iterate in the sandbox — hand-editing a product profile is reverted by the plugin manager on reinstall:
238
165
 
239
166
  ```bash
240
167
  scripts/dev-run.sh --restart # start / restart the isolated sandbox
@@ -242,25 +169,13 @@ scripts/dev-run.sh --stop # stop it
242
169
  scripts/dev-run.sh --status # is it running?
243
170
  ```
244
171
 
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.
172
+ - The sandbox runs its own DSH instance (`dev-harness/harness`), serving on `http://127.0.0.1:50599`.
173
+ - **Host-half** (`lib/index.js`) changes need `--restart`; **client-half** (`lib/client.js`) changes need only a page refresh.
174
+ - The script hardlink-copies `lib/` into the sandbox rather than symlinking — a symlink breaks `@deepseek-ai/*` resolution.
175
+ - Before committing: `node check.mjs` (framework-constraint gate) and `npm test`; `scripts/boot-smoke.sh` proves the plugin still starts.
176
+ - Full rules live in `scripts/dev-standards.md`.
177
+
178
+ Deploying to a product profile is a separate, explicit action (`./sync.sh`) for releases only.
264
179
 
265
180
  ## Configuration
266
181
 
@@ -290,6 +205,8 @@ and should be done only when you intend to release.
290
205
  | `fileReferenceMaxEntries` | int | `3000` | max entries retained in one remote workspace's `@` index |
291
206
  | `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 |
292
207
  | `fileReferenceTimeoutMs` | int | `4000` | wall-clock budget for one remote `@` index pass (on expiry the partial index answers rather than making the caret wait) |
208
+ | `searchTimeoutMs` | int | `60000` | Cooperative budget (ms) for `rw_search`: declared as the tool's `timeoutMs` for DSH's timeout policy and used as the search's own wall-clock cap; on expiry it returns partial results marked `TRUNCATED` (issue #44). |
209
+ | `searchMaxEntries` | int | `50000` | Max files `rw_search` scans before returning partial results. |
293
210
  | `updateMode` | string | `auto` | self-update behaviour: `auto` checks npm on load and every 6h and applies a newer release; `manual` only checks when asked; `off` disables checks. **Default changed to `auto` in 0.8.27** — safe because 0.8.24 added the host-half hot swap |
294
211
  | `updateCheckIntervalMs` | int | 21600000 (6h) | how often `auto` mode checks npm (floor 60000) |
295
212
  | `updateAutoReload` | bool | `true` | hot-swap the host half after an update lands; `false` defers it to the next process start and the panel reports `pendingReload` |
@@ -298,27 +215,29 @@ and should be done only when you intend to release.
298
215
 
299
216
  ## FAQ / troubleshooting
300
217
 
301
- **`@` 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.
218
+ **`@` lists remote files but the built-in read tool cannot open them** — the harness's own file tools see the **local mirror**, which stays empty until `rw_sync` downloads it. Read remote files with `rw_read_file` or the sidebar remote tab.
219
+
220
+ **Host key changed** — `/remote forget-key` (or Settings → machine → trust again).
302
221
 
303
- **Host key 变了 / 提示可能中间人** — 主机重装过或密钥更换过:`/remote-forget-key`(或设置页 → 机器 → 重新信任),下次连接重新记录。
222
+ **"Authentication failed"** — check the username/password/key path; fill in the passphrase for an encrypted key; enable keyboard-interactive when the host requires OTP.
304
223
 
305
- **连接报"认证失败"** — 检查用户名/密码/私钥路径;私钥加密了要填 Passphrase;公司机器要求 OTP/动态码时勾选 keyboard-interactive。
224
+ **Cannot reach an internal machine** — set a jump host (or add the bastion as its own machine first).
306
225
 
307
- **连不上内网机器** — 走跳板机:机器表单里填「跳板机」主机(也可以先把它本身配成一台机器)。主机不可达类错误会给出分类提示。
226
+ **`rw_sync`/`rw_push` reports conflicts** — files changed on both sides are skipped and listed (never silently overwritten); merge manually and retry, or pass `force=true`.
308
227
 
309
- **rw_sync/rw_push 报冲突** — 远端和本地都改过同一个文件时会跳过并列出冲突(绝不静默覆盖)。处理:手动合并后重新同步,或用 `force=true` 以一边为准。
228
+ **Windows remotes** — everything goes over SFTP, no POSIX shell needed; read Chinese files with `encoding=gbk`.
310
229
 
311
- **Windows 远程** — 列表/读写/搜索/同步全部走 SFTP 协议,不依赖 POSIX shell;中文文件用 `encoding=gbk` 读。
230
+ **A directory is missing from the mirror** — the default ignore rules skip `.git`/`node_modules` and similar; adjust `$DSH_HOME/remote-workspaces/.dsh-remote-ignore` (gitignore syntax).
312
231
 
313
- **镜像里没有某个目录** — 默认 ignore 规则(`.git`、`node_modules`、`target` 等)会跳过;在 `$DSH_HOME/remote-workspaces/.dsh-remote-ignore` 加 `!` 之外的条目即可调整(gitignore 语法)。
232
+ **Saving a remote file returns 409** — the remote file changed after you opened it; re-read and edit again.
314
233
 
315
- **侧边栏远程文件保存失败(409)** — 远端文件在你打开后已被改动,重新读取后再编辑(mtime 乐观锁保护)。
234
+ **How are passwords stored?** — tick "encrypt password": macOS Keychain / Windows DPAPI / Linux secret-tool (libsecret); falls back to plaintext when unavailable.
316
235
 
317
- **密码怎么加密保存** — 机器表单勾选「加密保存密码」:macOS 用系统钥匙串(security),Windows 用 DPAPI,Linux 需要 secret-tool(libsecret);后端不可用时自动回退明文。
236
+ **The plugin vanished after a DSH upgrade** — DSH's compatibility check dropped the bundle; upgrade to **0.8.29+** (see "DSH version compatibility" above).
318
237
 
319
238
  ## Safety
320
239
 
321
- 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.
240
+ 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 live in a local file (or the OS keychain); treat them as sensitive. With `auditLog` on, every command is recorded.
322
241
 
323
242
  ## License
324
243