dsh-remote 0.8.36 → 0.8.37

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
@@ -20,21 +20,13 @@ Manage several SSH machines, then pick a **remote workspace** (or a **local** on
20
20
 
21
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
22
 
23
- ## Data collection / telemetry
24
-
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
-
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
-
29
- The heartbeat is fire-and-forget: it never blocks loading and failures are ignored.
30
-
31
- [live usage stats](https://flymysql.github.io/dsh-remote/stats/) · [plugin homepage](https://flymysql.github.io/dsh-remote/)
23
+ It works the other way too: **you can open the remote machine's own DSH Web UI as a page in your local browser** — again without exposing any port on the remote. And when a connection will not come up, a **one-click health check & deploy** repairs and verifies the remote environment for you.
32
24
 
33
25
  ## Screen previews
34
26
 
35
27
  **Settings → 远程工作区** — machine list, advanced config (key / jump host / agent), connection check, port forwarding, audit log, update:
36
28
 
37
- <img src="docs/shots/settings-panel.png" alt="dsh-remote settings: machine list, advanced config, port forwarding, audit log, update" width="640"/>
29
+ <img src="docs/shots/settings-panel.png" alt="dsh-remote settings: machine list, advanced config, remote DSH Web UI, health check &amp; deploy, port forwarding, audit log, update" width="612"/>
38
30
 
39
31
  The native **"Add workspace"** flow — a centered modal with two tabs, opening on Local; here switched to **Remote**:
40
32
 
@@ -47,7 +39,7 @@ The native **"Add workspace"** flow — a centered modal with two tabs, opening
47
39
 
48
40
  ## Features
49
41
 
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 21 rw_* tools](docs/shots/features.png)
42
+ ![Capabilities at a glance — multi-machine SSH, live alias resolution, the two-tab picker, three-way sync, remote @ completion, a remote DSH Web UI in your local browser, health check &amp; one-click deploy, audit, port forwarding, sidebar editing, self-update, and the 21 rw_* tools](docs/shots/features.png)
51
43
 
52
44
  The image above is the overview. What follows is only what the image does not make obvious.
53
45
 
@@ -60,11 +52,13 @@ The image above is the overview. What follows is only what the image does not ma
60
52
 
61
53
  **Settings** (machine list, connection check, port forwarding, audit log, update mode):
62
54
 
63
- <img src="docs/shots/settings-panel.png" alt="dsh-remote settings: machine list, advanced config, port forwarding, audit log, update" width="640"/>
55
+ <img src="docs/shots/settings-panel.png" alt="dsh-remote settings: machine list, advanced config, remote DSH Web UI, health check &amp; deploy, port forwarding, audit log, update" width="612"/>
64
56
 
65
57
  The rest:
66
58
 
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`.
59
+ - **21 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`, `rw_deploy_probe`.
60
+ - **Open a remote machine's DSH Web UI locally** (`0.8.36+`) — nothing has to be exposed on the remote: the plugin SSHes out, starts a `dsh web` there that binds **`127.0.0.1` only**, and tunnels its port back to a loopback port on this machine. The URL carries a one-time login token, consumed once by a redirect.
61
+ - **One-click health check & deploy** (`0.8.36+`) — **a failed connection is usually the remote dsh version or environment, not the plugin.** The read-only check reports platform / node / npm / dsh version / **whether the native module can load** / proxy and npm registry, with a suggested fix; "Deploy & verify" installs into a remote **private directory** (no system directory, no PATH change, no replacing the version you use — deleting it is a complete rollback) and verifies step by step. If that still fails, the built-in `dsh-remote-deploy` skill takes over.
68
62
  - **Cross-platform remotes** — all file access is SFTP-protocol-level (no POSIX shell), so Linux/macOS/Windows remotes all work.
69
63
  - **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
64
  - **Async long tasks** — `rw_sync`/`rw_push` with `async: true` return a `taskId` with progress/result/cancel.
package/README.md CHANGED
@@ -20,21 +20,13 @@
20
20
 
21
21
  DSH 的 Web 界面刻意只监听 `127.0.0.1`(CLI 为安全拒绝 `--host 0.0.0.0`)。本插件反过来:**由你主动连出**到你维护的机器,选一个工作区,然后通过 DSH 原生的工作区 + 文件流来工作——**不改动 `dsh-workspace` 核心**。
22
22
 
23
- ## 数据采集 / 遥测
24
-
25
- 每次启动发送一次匿名心跳(同一安装最少间隔 6 小时),只用于统计用量:**去重日活、实际在跑的版本、平台分布**。npm 下载量由发版驱动、且含镜像与爬虫,GitHub clone 混有 CI,都回答不了这些。
26
-
27
- 只发送 5 个字段:`idHash`(`HMAC-SHA256('dsh-remote/telemetry/v1', installId)` 的伪名)、`version`、`platform`、`arch`、`node`。**不发送**主机名、用户名、路径、IP、SSH 主机/端口/密钥、机器列表、会话内容。原始 `installId`(`<DSH_HOME>/.dsh-remote-install-id`)不离开本机,只传它的 HMAC。
28
-
29
- 心跳是尽力而为的旁路:不阻塞加载,失败静默忽略。
30
-
31
- [实时用量看板](https://flymysql.github.io/dsh-remote/stats/) · [插件主页](https://flymysql.github.io/dsh-remote/)
23
+ 反过来也成立:**你可以把远端那台机器的 DSH 网页界面,当成本机浏览器里的一个页面打开**——同样不需要在远端开放任何端口(详见 [在本地打开远程机器上的 DSH 界面](#在本地打开远程机器上的-dsh-界面))。连不上时还有**一键体检与部署**,它会替你把远端环境修好并验证。
32
24
 
33
25
  ## 界面预览
34
26
 
35
- **设置 → 远程工作区** —— 多机列表、高级配置(私钥/跳板机/agent)、连接体检、端口转发、审计日志、更新:
27
+ **设置 → 远程工作区** —— 多机列表、高级配置(私钥/跳板机/agent)、连接体检、远程 DSH 界面、端口转发、审计日志、更新:
36
28
 
37
- <img src="docs/shots/settings-panel.png" alt="dsh-remote 设置页:机器列表、高级配置、端口转发、审计日志、更新" width="640"/>
29
+ <img src="docs/shots/settings-panel.png" alt="dsh-remote 设置页:机器列表、高级配置、远程 DSH 界面、体检与部署、端口转发、审计日志、更新" width="612"/>
38
30
 
39
31
  原生 **「Add workspace / 选择工作区」** 流程 —— 居中弹窗、两个 tab,默认落在「本机」;切到**「远程」**:
40
32
 
@@ -47,13 +39,15 @@ DSH 的 Web 界面刻意只监听 `127.0.0.1`(CLI 为安全拒绝 `--host 0.0.
47
39
 
48
40
  ## 功能
49
41
 
50
- ![核心能力总览 — 多机 SSH / 别名实时解析 / 双 tab 选择器 / 三路同步 / 远程 @ 补全 / 安全审计 / 端口转发 / 侧栏编辑 / 自动更新,以及 21 个 rw_* 工具](docs/shots/features.png)
42
+ ![核心能力总览 — 多机 SSH / 别名实时解析 / 双 tab 选择器 / 三路同步 / 远程 @ 补全 / 远端 DSH 界面挂到本机 / 体检与一键部署 / 安全审计 / 端口转发 / 侧栏编辑 / 自动更新,以及 21 个 rw_* 工具](docs/shots/features.png)
51
43
 
52
44
  上图是能力总览;下面只列**上图没说清、但用起来需要知道**的部分。
53
45
 
54
46
  其余要点:
55
47
 
56
- - **20 个模型工具**(便于复制/检索):`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`。
48
+ - **21 个模型工具**(便于复制/检索):`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`、`rw_deploy_probe`。
49
+ - **把远端机器的 DSH 界面挂到本机**(`0.8.36+`)—— 不用在远端开任何端口:插件主动 SSH 连过去,在远端**只监听 `127.0.0.1`** 地起一个 `dsh web`,再把端口经隧道搬回本机的一个 loopback 端口。地址带一次性登录令牌,只在本机这次跳转里用一次。
50
+ - **连不上时一键体检与部署**(`0.8.36+`)—— **连不上大多不是插件的问题,而是远端 dsh 的版本或环境不对**。体检(只读)会报出平台/node/npm/dsh 版本、**原生模块能否启动**、代理与 npm 源,并给出修复建议;「部署并验证」把它装到远端**私有目录**(不写系统目录、不改 PATH、不覆盖你在用的版本,删目录即回滚)并逐步校验。失败还能交给内置的 `dsh-remote-deploy` 技能排查。
57
51
  - **远端跨平台** —— 文件访问走 SFTP 协议层(不依赖 POSIX shell),Linux/macOS/Windows 远端都能列/读/写/搜索/同步。
58
52
  - **Windows 主机** —— 自动探测平台并定位 Git Bash,命令经 `bash -s` 走 stdin 执行,不受引号/反斜杠转义困扰(`config.shell` 可指定或设 `native` 关闭);`C:\Users\dev` 与 `/c/Users/dev` 两种写法都接受。
59
53
  - **长任务异步化** —— `rw_sync`/`rw_push` 传 `async: true` 返回 `taskId`,可查询进度/结果/取消。
package/lib/index.js CHANGED
@@ -47,7 +47,7 @@ import {
47
47
  resolvePrefix, stepResult, DEFAULT_INSTALL_VERSION,
48
48
  } from './web-deploy.js'
49
49
  import { registerDeploySkill, buildInvestigatePrompt, SKILL_NAME } from './web-deploy-skill.js'
50
- import { selfDir, diskVersion, LOADED_VERSION, gtVersion, fetchLatestVersion, applyUpdate, persistUpdateMode, readUpdateMode, reloadSelf, scheduleSelfReload, isInstalledCopy } from './update.js'
50
+ import { selfDir, diskVersion, LOADED_VERSION, gtVersion, fetchLatestVersion, applyUpdate, persistUpdateMode, readUpdateMode, reloadSelf, scheduleSelfReload, lastSelfReload, isInstalledCopy } from './update.js'
51
51
  import { sendHeartbeat, pseudonym, heartbeatUrl, hasPersistedId, installIdPath } from './telemetry.js'
52
52
  import { loadMachines as _loadMachines, saveMachines as _saveMachines, sanitizeMachine as _sanitizeMachine, applyMachine as _applyMachine, machineId as _machineId } from './registry.js'
53
53
  import { registerHttpTransports } from './http-transport.js'
@@ -3261,6 +3261,10 @@ export async function apply(ctx, config) {
3261
3261
  // Exposed so the settings page can explain why `auto` may not act:
3262
3262
  // a link:/dev install must not be overwritten by an npm publish.
3263
3263
  selfUpdateAllowed: isInstalledCopy(),
3264
+ // Outcome of the last hot-swap attempt (null if never tried). Without
3265
+ // this the swap could fail forever while the panel kept saying a
3266
+ // reload was merely pending — which is exactly what happened.
3267
+ lastReload: lastSelfReload(),
3264
3268
  }
3265
3269
  const probe = await fetchLatestVersion()
3266
3270
  // 即使查不到最新版,也把**本地状态**照常返回(ok:true),只把 registry
package/lib/update.js CHANGED
@@ -380,8 +380,24 @@ export async function reloadSelf(loader) {
380
380
  if (!entry) return { ok: false, reason: 'own loader entry not found' }
381
381
  try {
382
382
  const cleared = clearSelfModuleCache(loader)
383
+ // ★ 卸载必须走 entry.fiber.dispose()。
384
+ //
385
+ // 原实现找 `entry._dispose` / `entry.dispose`,但 cordis 的 Entry **两个都没有**
386
+ // (0.2.0 实测方法集只有 constructor/disabledOf/evaluate/_patchContext/refresh/
387
+ // update/_commitVolatile/init/_init),所以这里每次都直接返回
388
+ // "entry exposes no dispose hook" —— 热切换从未真正执行过,而
389
+ // scheduleSelfReload 里的 `.catch(() => {})` 把它彻底吞掉,
390
+ // UI 于是永远停在"已落盘但未切换"(用户报的 0.8.35 -> 0.8.36)。
391
+ //
392
+ // 也不能用 entry.refresh():它是 `if (this.fiber) return; await this.init()`,
393
+ // fiber 还活着时直接返回,等于没重启。真正卸载 fiber 的地方在 Entry.update()
394
+ // 里(`this.fiber?.dispose()`),这里照它做。
395
+ //
396
+ // 保留对 _dispose/dispose 的探测:那是宿主可能提供的扩展钩子,有则优先用,
397
+ // 没有就用 fiber.dispose()——两条路都必须能真正卸载,否则应如实报错。
383
398
  if (typeof entry._dispose === 'function') await entry._dispose()
384
399
  else if (typeof entry.dispose === 'function') await entry.dispose()
400
+ else if (entry.fiber && typeof entry.fiber.dispose === 'function') await entry.fiber.dispose()
385
401
  else return { ok: false, reason: 'entry exposes no dispose hook' }
386
402
  await entry.init()
387
403
  return { ok: true, cleared }
@@ -393,18 +409,38 @@ export async function reloadSelf(loader) {
393
409
  /**
394
410
  * Fire-and-forget {@link reloadSelf} after a short delay, so the caller can
395
411
  * finish answering an HTTP request (or an update check) before its own module is
396
- * torn down. Failures are swallowed: a failed swap leaves the running code in
397
- * place, which is the pre-hot-update behaviour, not a crash.
412
+ * torn down. A failed swap leaves the running code in place, which is the
413
+ * pre-hot-update behaviour, not a crash — so we do not throw.
414
+ *
415
+ * We DO record the outcome, though. Silently discarding it is precisely how the
416
+ * "entry exposes no dispose hook" defect stayed invisible: the panel reported a
417
+ * scheduled swap, the swap never ran, and nothing anywhere said so.
418
+ * {@link lastSelfReload} exposes the result for diagnostics (the settings panel
419
+ * and `/dsh-remote/update-check` surface it), and a non-ok result is logged once.
420
+ *
398
421
  * @param loader - the cordis Loader service.
399
422
  * @param delayMs - grace period before the swap (default 300 ms).
400
423
  * @returns true when the swap was scheduled.
401
424
  */
402
425
  export function scheduleSelfReload(loader, delayMs = 300) {
403
- const timer = setTimeout(() => { void reloadSelf(loader).catch(() => {}) }, Math.max(0, delayMs))
426
+ const timer = setTimeout(() => {
427
+ void reloadSelf(loader).then(
428
+ (res) => { lastReloadResult = res; if (res && res.ok === false) console.warn('[dsh-remote] self-update swap failed:', res.reason) },
429
+ (err) => { lastReloadResult = { ok: false, reason: String((err && err.message) || err) }; console.warn('[dsh-remote] self-update swap threw:', lastReloadResult.reason) },
430
+ )
431
+ }, Math.max(0, delayMs))
404
432
  if (typeof timer.unref === 'function') timer.unref()
405
433
  return true
406
434
  }
407
435
 
436
+ /** Result of the most recent {@link scheduleSelfReload} attempt, for diagnostics. */
437
+ let lastReloadResult = null
438
+
439
+ /** @returns the last self-reload outcome (`{ok,reason?}`, or null if never attempted). */
440
+ export function lastSelfReload() {
441
+ return lastReloadResult
442
+ }
443
+
408
444
  /** Write the persisted update-mode override (settings UI). */
409
445
  export function persistUpdateMode(mode) {
410
446
  if (!['manual', 'auto', 'off'].includes(mode)) return false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-remote",
3
- "version": "0.8.36",
3
+ "version": "0.8.37",
4
4
  "description": "Remote-work assistant for DeepSeek Harness: connect SSH (password/key/agent/keyboard-interactive/proxy jump), pick a remote workspace, operate on it with 20 rw_* tools (incl. rw_edit/rw_stat/rw_mkdir/rw_remove/rw_move/rw_forward), conflict-aware mirror sync, port forwarding, optional side-bar remote file editing, audit log, keychain passwords.",
5
5
  "license": "MIT",
6
6
  "author": "flymysql <flyphp@outlook.com>",