Repository navigation
Releases
Releases · btsd321/dsh-remote-explorer
v0.8.10:插件市场假插件清理(manifest 自愈只收编真插件)/ Clean Fake Plugin Entries in the Marketplace (Profile Manifest Self-Heal)
v0.8.10:插件市场假插件清理(manifest 自愈只收编真插件)/ Clean Fake Plugin Entries in the Marketplace (Profile Manifest Self-Heal)
Latest
中文
修复
- 插件市场「已安装插件」里混入大量非插件(如
@standard-schema/spec、@codemirror/*):profile 的nodeLinker: hoisted把所有传递依赖摊到顶层node_modules,而旧的 manifest 自愈把「有 package.json 且 manifest 未声明」的包一律按"*"收编成直接依赖;插件市场的已安装列表直接读package.json.dependencies,于是这些包被当成插件显示。现在插件判定改为解析包自己的dsh元数据(用 node 读 JSON,grep 会漏掉跨行字段),非插件直接依赖一律清理——包括后来被 dshmarket update 改写成具体版本的残留(@lydell/node-pty: 1.2.0-beta.15、undici: ^8.11.2这类)。 - 真插件不再被写成
*:历史上被收编成*的真插件改钉^<已装版本>;node_modules里存在但 manifest 未声明的真插件也按已装版本收编,避免版本随pnpm update漂移。 - manifest 变化后同步收敛
pnpm-lock.yaml(pnpm install --lockfile-only --prefer-offline):manifest 与锁文件的 specifier 对不上时,pnpm 的--frozen-lockfile修复路径会直接拒绝(dshmarket #808)。收敛失败会回滚 manifest、下次连接重试,绝不留下不一致状态。 - 自愈不再可能误删全部依赖:扫描命令非零退出或输出不完整(缺分段标记)时直接放弃本次自愈;manifest 缺失/损坏也不再猜内容。
- 自愈异常不再阻断会话:整个自愈用 try/catch 兜底,传输/IO 异常降级为 warn。
- 版本号不是 semver 的真插件保留原 spec(不猜 git/file 规格);
*改钉只接受严格x.y.z[-pre][+build]。
升级须知
- 已有 profile 在升级后第一次连接时自动清理,插件市场的已安装列表随之恢复为真正的插件。
- 清理只改 manifest 与锁文件;被清理的包仍作为真插件的传递依赖留在
node_modules里,不会卸载任何插件。 - 若远端离线导致锁文件收敛失败,manifest 会回滚,下次连接再试(插件市场列表在清理成功前保持原样)。
- 建议升级后强制重启一次远端会话:旧版本进程仍会按旧逻辑重新收编,必须先让新版本接管。
文档
- lessons 新增 15b:hoisted 布局下 manifest 自愈的边界(只收编真插件)、锁文件收敛与回滚纪律、扫描失败的安全线。
- 新增
tests/unit/plugin-store.test.ts(12 个用例):扫描结果解析(插件判定、回退链接/handoff 排除)与依赖计划(回收、清理非插件、*改钉、插件收编、不猜非 semver)。
验证
pnpm run typecheck零错误;scripts/check-plugin.ts全通过;单元测试 676 个用例 673 通过 / 3 skipped(既有 POSIX-only)/ 0 失败。- 真机拷贝冒烟(WSL profile 的真实副本,manifest/lock 为真拷贝、node_modules 只读):注入 372 条
*收编污染后运行自愈——依赖从 377 清理到 5 个真插件,manifest 与锁文件中的非插件条目归零,5 个插件依赖(dshmarket / dsh-better-sidebar / dsh-oh-my-terminal / skill-explorer / memory-plugin)全部保留;pnpm install --lockfile-only --frozen-lockfile退出 0(锁文件一致);第二次调用返回未变更(幂等)。 build:plugin重建后lib/可复现(工作区干净),产物内联版本0.8.10。
English
Fixes
- The marketplace "installed plugins" list was flooded with non-plugins (
@standard-schema/spec,@codemirror/*, ...): the profile usesnodeLinker: hoisted, so every transitive dependency sits at the top level ofnode_modules; the old manifest self-heal adopted any undeclared package as a direct dependency with spec"*", and the marketplace list readspackage.json.dependenciesdirectly. Plugin detection now parses the package's owndshmetadata with node (grep misses fields spread across lines), and every non-plugin direct dependency is removed — including leftovers that dshmarket updates had rewritten into concrete versions (@lydell/node-pty: 1.2.0-beta.15,undici: ^8.11.2, ...). - Real plugins are no longer pinned as
*: entries historically adopted as*are rewritten to^<installed version>, and plugins present innode_modulesbut missing from the manifest are adopted at their installed version, so versions cannot drift onpnpm update. - Manifest changes now realign
pnpm-lock.yaml(pnpm install --lockfile-only --prefer-offline): when the lockfile specifiers disagree with the manifest, pnpm's--frozen-lockfilerepair path refuses to run (dshmarket #808). If realignment fails the manifest is rolled back and retried on the next connection — an inconsistent state is never left behind. - Self-heal can no longer wipe all dependencies by accident: a non-zero scan exit or incomplete output (missing section markers) aborts this round; a missing/corrupt manifest is no longer guessed at.
- Self-heal failures no longer block the session: the whole routine is wrapped in try/catch and degrades to a warning.
- Plugins whose version is not semver keep their original spec (git/file specs are never invented);
*pinning only accepts strictx.y.z[-pre][+build].
Upgrade Notes
- Existing profiles are cleaned automatically on the first connection after upgrading; the marketplace list then shows only real plugins.
- Cleanup only touches the manifest and lockfile; removed packages stay in
node_modulesas transitive dependencies of real plugins, and no plugin is uninstalled. - If the remote is offline and lockfile realignment fails, the manifest is rolled back and retried on the next connection (the marketplace list stays as-is until cleanup succeeds).
- After upgrading, force-restart one remote session: the old process would re-adopt with the old logic, so the new version has to take over first.
Documentation
- lessons gains 15b: manifest self-heal boundaries under the hoisted layout (adopt real plugins only), lockfile realignment and rollback discipline, and the scan-failure safety line.
- New
tests/unit/plugin-store.test.ts(12 cases): scan parsing (plugin detection, fallback-link/handoff exclusion) and the dependency plan (reclaim, non-plugin cleanup,*pinning, plugin adoption, non-semver versions left alone).
Verification
pnpm run typecheckclean;scripts/check-plugin.tsfully passing; 676 unit tests — 673 passed / 3 skipped (pre-existing POSIX-only) / 0 failed.- Real-host copy smoke (a true copy of the WSL profile: manifest/lockfile copied,
node_modulesread-only): after injecting 372*adoption entries, the self-heal reduced 377 dependencies to 5 real plugins, removed every non-plugin entry from both the manifest and the lockfile, kept all 5 plugin dependencies (dshmarket / dsh-better-sidebar / dsh-oh-my-terminal / skill-explorer / memory-plugin),pnpm install --lockfile-only --frozen-lockfileexited 0 (consistent lockfile), and a second call reported no changes (idempotent). build:pluginreproduceslib/exactly (clean working tree); the bundle inlines version0.8.10.
Full Changelog: v0.8.9...v0.8.10
v0.8.9:技能根指向账号真实的 ~/.agents(自动迁移旧版私有根)/ Skill Root Moves to the Account's Real ~/.agents
中文
新功能
- 🟢 技能与全局指令改指账号真实的
~/.agents:DSH_AGENTS_HOME从本工具私有的btsd321/.agents改为远端账号的~/.agents(dsh 的默认值)。用户在同一台机器上早已维护的技能与~/.agents/AGENTS.md全局指令,现在在远端会话里直接生效;远端窗口里装的技能,原生 dsh 也共享同一份。同一远端账号下换工作目录开会话,技能依然在。 - 旧版私有根自动迁移:首次连接把
btsd321/.agents里的技能与AGENTS.md并入~/.agents——同名冲突项不迁移(保留新版,旧副本原地留下),第三方安装器元数据.skill-lock.json不搬运(dsh 的技能发现只扫目录,本工具不伪造来源/hash)。迁移幂等、失败只跳过该阶段、不影响会话;写入经base/tmp/agents.lock的 flock 串行化,多个会话同时连接不会互相踩。 - 迁移后旧版空目录与本工具旧
.lock自动收尾(rmdir只删空目录,有冲突项或第三方元数据时保留)。
修复
- 代码审查发现:迁移「无可迁项」分支的旧根收尾(
rm .lock/rmdir)此前绕过 flock,与「所有写~/.agents的动作共用同一把锁」不一致;现在收尾与搬运走同一临界区。 AGENTS.md的搬运结果不再复用技能名输出通道(技能根里若存在同名平铺技能AGENTS.md会被误记成全局指令),改用独立标签区分。- 预建
~/.agents/skills改为失败容忍:该根现在归用户(可能是只读目录、软链或普通文件),预建只是 watcher 优化,失败交由 dsh 自带的轮询兜底,不该连会话启动一起挂掉。 clean不再删除任何 agent 目录:~/.agents(技能与全局AGENTS.md)属于用户数据,与 plugin store 同一纪律;旧btsd321/.agents也保留,避免丢掉未迁移的同名冲突项。- 锁文件从
.agents/.lock迁到base/tmp/agents.lock,不再往用户目录写本工具的内部文件。
文档
- README(中/英)、usage(中/英)更新:技能根位置、本工具对
~/.agents的两处写操作(预建空skills/+ 迁移)、clean语义、迁移规则,以及「被复用的旧远端进程仍带旧DSH_AGENTS_HOME,需重启远端会话才读到新根」。 - lessons 第 11/22 条重写:记录技能根从「会话级 → 工具机器级 → 账号级」三代演进与判据(dsh 的
agent-instructions与skill-filesystem只认这个根)。 - 单测同步:
remote-paths/agents-lock断言新归属;新增迁移计划用例(冲突不迁移、平铺.md、AGENTS.md冲突);删除clean-agents旧用例。
验证
pnpm run typecheck零错误;scripts/check-plugin.ts全通过;单元测试 664 个用例 661 通过 / 3 skipped(既有 POSIX-only)/ 0 失败。- 依赖真实 POSIX shell 的 WSL 冒烟:在
/tmp沙箱跑完整迁移——非冲突技能迁走、同名冲突保留、AGENTS.md首次迁移且二次冲突跳过、旧.lock清理、空旧根移除、二次调用幂等,全部符合预期;锁文件落在base/tmp/agents.lock。 build:plugin重建后lib/可复现(工作区干净),产物内联版本0.8.9。
English
Features
- 🟢 Skills and global instructions now use the account's real
~/.agents:DSH_AGENTS_HOMEmoves from this tool's privatebtsd321/.agentsto the remote account's~/.agents(dsh's default). Skills and the~/.agents/AGENTS.mdglobal instructions you already maintain on that machine now apply directly in remote sessions, and skills installed from the remote window are shared with native dsh. Opening a session in a different working directory on the same account keeps your skills. - Automatic migration of the old private root: on first connect, skills and
AGENTS.mdunderbtsd321/.agentsare merged into~/.agents— same-name entries are not migrated (the new copy wins and the old one stays in place), and external installer metadata (.skill-lock.json) is not carried over (dsh's skill discovery only scans directories; this tool does not fabricate source/hash records). The migration is idempotent, a failure only skips the stage and never blocks the session, and writes are serialized by a flock atbase/tmp/agents.lockso concurrent sessions cannot step on each other. - Empty legacy directories and the tool's old
.lockare cleaned up afterwards (rmdironly removes empty directories; conflicts or third-party metadata are kept).
Fixes
- Found in code review: the no-op branch's legacy cleanup (
rm .lock/rmdir) bypassed the flock, contradicting the rule that every write to~/.agentsshares one lock; cleanup and moves now run in the same critical section. - The
AGENTS.mdmove result no longer reuses the skill-name output channel (a flat skill namedAGENTS.mdwould have been misreported as the global instruction file); it now uses its own tags. - Pre-creating
~/.agents/skillsis now failure-tolerant: the root belongs to the user (it may be read-only, a symlink, or a regular file), pre-creation is only a watcher optimization, and dsh's own polling is the fallback — it must not take session startup down with it. cleanno longer deletes any agent directory:~/.agents(skills and globalAGENTS.md) is user data, the same discipline as the plugin store; the oldbtsd321/.agentsis kept too so unmigrated same-name conflicts are not lost.- The lock file moves from
.agents/.locktobase/tmp/agents.lock, so this tool no longer writes its internal lock into the user's home.
Documentation
- README (zh/en) and usage (zh/en): skill root location, the two places this tool writes into
~/.agents(emptyskills/pre-create + migration),cleansemantics, migration rules, and the note that a reused remote process still carries the oldDSH_AGENTS_HOMEand needs a restart to read the migrated skills. - lessons #11/#22 rewritten: the three-generation evolution of the skill root (session-level → tool machine-level → account-level) and the evidence behind it (dsh's
agent-instructionsandskill-filesystemonly read that root). - Tests updated:
remote-paths/agents-lockassert the new ownership; new migration-plan cases (conflict skip, flat.md,AGENTS.mdconflict); the obsoleteclean-agentscases are removed.
Verification
pnpm run typecheckclean;scripts/check-plugin.tsfully passing; 664 unit tests — 661 passed / 3 skipped (pre-existing POSIX-only) / 0 failed.- WSL smoke test driven by a real POSIX shell: a complete migration in a
/tmpsandbox — non-conflicting skills moved, same-name conflicts kept,AGENTS.mdmigrated once and skipped on the second run, old.lockremoved, empty legacy root removed, second call idempotent; all as expected, with the lock atbase/tmp/agents.lock. build:pluginreproduceslib/exactly (clean working tree); the bundle inlines version0.8.9.
Full Changelog: v0.8.8...v0.8.9
v0.8.8:local_resource_download(远端无网时经本机网络代理下载)/ Local Resource Download via This Machine
中文
新功能
- 🟢 local_resource_download——远端无网时经本机网络完成引导期下载:新增 SSH 高级设置
local_resource_download,类似 VS Code 的Remote.SSH: Local Server Download。三档取值:auto(默认,先探测远端网络,不可达才建隧道)/always(始终走本机代理)/off(远端直连,原行为)。实现为本机纯 JS HTTP CONNECT 代理 + SSH 反向转发:远端的 curl 与 npm install 收到http_proxy=http://127.0.0.1:<远端端口>,出站流量经隧道回到本机网络——远端下载命令本身不改,npm 依赖树照常解析,解决了「只下载 tarball 仍需远端拉依赖」的固有局限。引导期全部网络调用(镜像测速、Node 发行包下载、npm 版本解析、dsh 与 pnpm 安装)都走这条隧道。 - 反向端口动态分配:隧道用的远端端口从与其他反向隧道同一套候选区间分配(不再固定值),与凭据代理共用同一套端口纪律;绑定冲突时换端口重试一次,两次都失败则降级为远端直连,不让引导因此中断。
- 两个入口:面板「高级选项」新增下拉框(与代理/环境变量/跳板机共用
~/.dsh/remote-advanced.json的 SSH 域持久化),CLI 新增--local-resource-download <auto|always|off>。仅对 SSH 连接生效(WSL 有本地文件系统访问,无需此选项)。 - 代理环境变量的八个键清单下沉到基础层
util/proxy-env.ts,供编排层与能力层共用单一实现。
修复
- 镜像测速漏注入代理(阻塞级):
selectMirror此前裸curl探测候选镜像,远端无网时全部不可达即抛MIRROR_ALL_UNREACHABLE——本已注入代理的下载步骤根本执行不到,特性在主场景下完全不生效。现在测速的每次 curl 都带-x。 - 反向转发的监听器泄漏:
ssh-transport.forwardIn的'tcp connection'handler 在绑定前挂上,失败路径只 reject 不卸载。上一项修复引入的换端口重试让这条路径成为常态;累积的孤儿 handler 在该端口被释放又被重新绑定时会与正常 handler 同时accept()同一条连接。现在失败先卸载再抛。 - 隧道被空闲超时打断:
net.Socket.setTimeout计的是空闲时长而非建连时长。原实现建连时设了 30 秒超时却从不解除,隧道建立后任何 30 秒静默(npm keep-alive 间隔、慢速服务器、aarch64 上耗时两分钟的安装)都会打断正在传输的连接,客户端只看到 EOF 并重连——表现为连接反复重连、任务迟迟不结束。现在握手成功即解除超时。 - 代理失败路径泄漏本机端口:本机代理一启动就占端口,而 provision 的
try/finally在其返回之后才建立。forwardIn失败时代理永不停止;现在失败路径就地收尾并降级。 selectMirror之外的可达性探测:auto模式改为逐候选顺序尝试全部 npm 镜像,单点被墙不再误判「远端无网」而白建一条隧道。- 代理服务器收尾挂起:
stop()此前依赖server.close()等连接自然结束,而注入的 SSH 流不归本机管——provision 的finally会永久挂起。现在显式销毁上游与注入流,并closeAllConnections()兜底。 - 日志量级:成功的 CONNECT 不再逐条打印(一次 npm install 会产生上百条,刷屏看起来像死循环),改为收尾汇总「共转发 N 条隧道」;建连失败仍逐条留痕。CONNECT 目标解析同时加固:含空白一律拒绝、IPv6 方括号剥离后再交给 DNS。
- 其余整理:删除
provision拆分后遗留的死变量与不可达分支、消除六处重复的'auto' | 'always' | 'off'字面量联合、runProvisionStages去掉与上下文重复的参数。
文档
- README(中/英)新增「本地资源下载」章节:三档取值、工作原理、CLI 与面板两种入口。
- README 删除「模块职责」表格(面向开发者的模块清单,使用者不需要)。
- 新增 5 个测试文件、共 20 个用例,其中 4 条专为上述缺陷的回归线(换端口重试、失败不泄漏、空闲不打断、测速带代理)。
验证
pnpm run typecheck零错误;scripts/check-plugin.ts全通过;单元测试 663 个用例 660 通过 / 3 skipped(既有 POSIX-only)/ 0 失败。- 真机端到端(OrangePI,aarch64,Node 与 dsh 均未安装):
always模式下引导全流程成功——镜像测速、Node v24.22.1 下载、dsh 0.2.1-rc.4 安装(137.7s)、pnpm 11.7.0 安装全部经隧道完成;auto模式在有公网主机上正确判定「有网」不建隧道。
English
Features
- 🟢 local_resource_download — bootstrap downloads through this machine when the remote has no network: New SSH advanced option
local_resource_download, similar to VS Code'sRemote.SSH: Local Server Download. Three values:auto(default — probe the remote network first, build the tunnel only when unreachable) /always(always use this machine as proxy) /off(remote direct, previous behavior). Implemented as a pure-JS HTTP CONNECT proxy in the local process plus an SSH reverse forward: the remotecurlandnpm installreceivehttp_proxy=http://127.0.0.1:<remote-port>and their outbound traffic returns through the tunnel to the local network — the remote download commands themselves are unchanged and npm dependency trees resolve normally, which fixes the inherent limitation of "downloading the tarball still needs the remote to fetch its dependencies." Every network call during bootstrapping (mirror latency probing, Node distribution download, npm version resolution, dsh and pnpm installs) goes through this tunnel. - Dynamic reverse port allocation: the tunnel's remote port comes from the same candidate range as the other reverse tunnels (no fixed value — the documented
DSH_REMOTE_PROXY=18890example would otherwise collide) and shares the same port discipline as the credential proxy. On bind conflicts it retries with a fresh port, and degrades to remote direct if both attempts fail rather than aborting the bootstrap. - Two entry points: a new dropdown in the panel's advanced options (persisted in the SSH domain of
~/.dsh/remote-advanced.json, shared with proxy/env/jump-host) and a new CLI flag--local-resource-download <auto|always|off>. SSH only — WSL has direct filesystem access and does not need it. - The eight proxy environment variable keys moved down to the foundation layer
util/proxy-env.ts, giving the orchestration and capability layers a single implementation.
Fixes
- Mirror probing missed the proxy (blocking):
selectMirrorprobed candidate mirrors with a barecurl, so on an offline remote every candidate was unreachable and it threwMIRROR_ALL_UNREACHABLE— the later, proxy-injected download steps never ran at all, making the feature useless in its primary scenario. Every probe curl now carries-x. - Reverse-forward listener leak:
ssh-transport.forwardInregistered its'tcp connection'handler before binding and never removed it on the failure path. The port-retry introduced above turns that path into a common one; accumulated orphan handlers wouldaccept()the same connection as the legitimate handler once the port is released and rebound. The handler is now removed before rethrowing. - Tunnels killed by an idle timeout:
net.Socket.setTimeoutmeasures inactivity, not connect time. The original code armed a 30-second timeout at connect and never cleared it, so any 30-second silence after the tunnel was established (npm keep-alive gaps, slow servers, a two-minute install on aarch64) tore down an in-flight transfer; the client saw EOF and reconnected — showing up as connection churn while the task never finished. The timeout is now cleared as soon as the handshake succeeds. - Local port leaked on proxy failure: the local proxy occupies a port as soon as it starts, while provision's
try/finallyis only established after it returns. A failingforwardInleft the proxy running forever; the failure path now cleans up in place and degrades. - Reachability probe:
automode now tries every npm mirror in order, so a single blocked endpoint no longer makes it conclude "no network" and build a tunnel needlessly. - Proxy shutdown hang:
stop()relied onserver.close()waiting for connections to end, but injected SSH streams are not driven locally — provision'sfinallywould hang forever. It now explicitly destroys upstream and injected streams, withcloseAllConnections()as a backstop. - Log volume: successful CONNECTs are no longer logged one by one (a single npm install produces hundreds, which looks like an infinite loop); a single summary line reports the tunnel count at shutdown, while failures are still traced. CONNECT target parsing was hardened too: whitespace is rejected outright and IPv6 brackets are stripped before handing the host to DNS.
- Cleanups: removed the dead variable and unreachable branch left behind by the
provisionsplit, dropped six duplicated'auto' | 'always' | 'off'literal unions, and trimmedrunProvisionStagesparameters that duplicated its context.
Documentation
- README (zh/en) gains a "Local resource download" section covering the three modes, how it works, and both entry points.
- README drops the developer-facing "Module Responsibility" table, which users do not need.
- Five new test files with 20 cases, four of them regression lines for the defects above (port retry, no leak on failure, no idle interruption, proxy-aware probing).
Verification
pnpm run typecheckclean;scripts/check-plugin.tsfully passing; 663 unit tests — 660 passed / 3 skipped (pre-existing POSIX-only) / 0 failed.- Real-host end-to-end (OrangePI, aarch64, with neither Node nor dsh installed): a full bootstrap in
alwaysmode succeeded — mirror probing, Node v24.22.1 download, dsh 0.2.1-rc.4 install (137.7s) and pnpm 11.7.0 install all completed through the tunnel; on a host with internet access,automode correctly detected the network and built no tunnel.
Full Changelog: v0.8.7...v0.8.8
v0.8.7:dsh 升级管理 + Node 24 动态解析 / dsh Upgrade Management + Node 24 Dynamic Resolution
中文
新功能
- 🟢 dsh 升级管理——稳定版优先 + 候选链降级:默认版本策略改为优先最大稳定版(无稳定版才取最新 rc,alpha/beta 一律不选)。首选版本 npm install 失败或装后版本不符时,自动沿候选链退到次新版本,最多尝试 3 个——避免「最新发布但装不上」直接卡死引导。损坏/版本不符时先清空安装树再重装,绝不删 base 根目录。
- 🟢 引导默认 Node 改为解析 Node 24 最新稳定版:在线引导不再 pin
v24.21.0,改为在引导期经发行站index.json解析 Node 24 系最新稳定版(排除预发布),复用面板版本探测的镜像竞速与缓存。解析失败或严格离线回退兜底地板,不中断引导。显式--node-version优先。
修复
- 修复 dsh 版本探测:dsh 是
base/node_modules下的单一安装点,旧脚本 glob 的versions/dsh-<ver>/旧布局恒不命中。改为标记DSHBIN=后跑dsh --version,doctor 能区分「未安装」与「存在但不可运行」。
文档
- 记录特性分支不 push 到远程的规则:特性分支是临时工作区,只在本地流转,远程只保留 develop/master/tag,避免分支堆积与幽灵分支。
English
Features
- 🟢 dsh upgrade management — stable-first + candidate chain fallback: The default version strategy now prefers the largest stable version (falls back to latest rc only if no stable exists; alpha/beta are never selected). When the preferred version fails to install or the installed version doesn't match, the installer automatically walks down the candidate chain to the next-newest version, trying up to 3 — preventing the bootstrap from getting stuck when "the latest published version won't install." On corruption/version mismatch, the install tree is cleared before reinstalling, never deleting the base root directory.
- 🟢 Default Node now resolves the latest Node 24 stable release: Online bootstrapping no longer pins
v24.21.0; instead it resolves the latest Node 24 stable version (excluding pre-releases) via the distribution site'sindex.jsonduring the bootstrap, reusing the version-probe mirror racing and caching. Falls back to a floor version on resolution failure or strict-offline mode without breaking the bootstrap. Explicit--node-versionstill takes precedence.
Fixes
- Fix dsh version detection: dsh is a single install point under
base/node_modules; the old script's glob ofversions/dsh-<ver>/(legacy layout) never matched. Now marksDSHBIN=and runsdsh --version, lettingdoctordistinguish "not installed" from "installed but not runnable."
Documentation
- Document the feature-branch-no-push rule: feature branches are temporary workspaces that live locally only; remotes keep only develop/master/tags, avoiding branch proliferation and ghost branches.
Full Changelog: v0.8.6...v0.8.7
v0.8.6:SSH 连接失败崩溃修复 / SSH Connection Failure Crash Fix
中文
修复
- 🔴 修复 SSH 连接失败导致桌面端崩溃:连接网络不可达的主机(如 ProxyJump 配错)时,底层 socket 的
error事件在connectClientreject 之后仍可能异步触发;若无监听器则未捕获异常直接掀翻进程。在connect()和buildJumpChain()中提前注册防御性error监听器,捕获并记录这些异步错误。 - 修复连接失败后会话状态不正确:首次连接失败时状态机通过
transition(state, { type: 'fail' })进入reconnect-exhausted终态,会话记录保留让前端能看到错误日志与状态,不再被误标为"已连接"。 - 修复前端活跃会话判定:
isActiveSession将reconnect-exhausted视为终态,连接失败的主机不再在连接表单中显示"已连接"标记。 - 修复
connectError字段回归:补回 catch 块中record.connectError赋值,恢复前端红色错误消息显示与use-session-polling失败检测回调。 - 简化
connectClient内部结构:删除与外层调用方重复注册的空函数defenseHandler及其try/finally包裹,恢复线性代码结构。
English
Fixes
- 🔴 Fix desktop crash on SSH connection failure: When connecting to an unreachable host (e.g. misconfigured ProxyJump), the underlying socket
errorevent may fire asynchronously afterconnectClientrejects; without a listener, the uncaught exception crashes the process. Defensiveerrorlisteners are now registered early inconnect()andbuildJumpChain()to catch and log these async errors. - Fix incorrect session state after connection failure: On first connection failure, the state machine transitions to
reconnect-exhaustedterminal state viatransition(state, { type: 'fail' }). The session record is retained so the frontend can display error logs and status, instead of being incorrectly marked as "connected". - Fix frontend active session detection:
isActiveSessionnow treatsreconnect-exhaustedas a terminal state; a host whose connection failed no longer shows an "connected" badge in the connect form. - Fix
connectErrorfield regression: Restoredrecord.connectErrorassignment in the catch block, recovering the frontend red error message display and theuse-session-pollingfailure detection callback. - Simplify
connectClientinternal structure: Removed the redundant emptydefenseHandlerfunction (duplicated with the caller's listener) and itstry/finallywrapper, restoring the linear code structure.
Full Changelog: v0.8.5...v0.8.6