Repository navigation
Releases: xiray-xia/dsh-remote-vs
Release list
v0.8.9
修复
- 「在终端中打开」曾在错误的远程主机上开终端(面板按上一次使用过的宿主打开),现在按搜索所属工作区的主机打开,并把顶部主机下拉同步过去。
优化点
- 文件名搜索结果新增右键菜单:复制绝对路径、复制相对路径、打开文件所在位置、在终端中打开。
- 打开文件所在位置会逐级展开到该文件所在目录并滚动到对应行;在终端中打开会新开标签并自动
cd到该目录。
安装
dsh plugin --profile web add dsh-remote-vs
安装后重启 dsh web。
Fixes
- "Open in terminal" could open the shell on the WRONG remote host (the panel reused whatever host was last used); it now opens on the host the searched workspace belongs to and moves the host picker with it.
Improvements
- File-name results gained a right-click menu: copy absolute path, copy relative path, open containing folder, open in terminal.
- "Open containing folder" expands every level down to the file's directory and scrolls its row into view; "open in terminal" opens a new tab that
cds into that directory.
Install
dsh plugin --profile web add dsh-remote-vs
Restart dsh web after installing.
v0.8.8
修复
- 无。
优化点
- 并排差异的工具栏新增「上一处 / 下一处改动」按钮与
n/N计数:跳到该处改动第一行,两端环绕;手动滚动时计数同步。 - 工作区在侧栏的显示名改为新建时填写的名称(
别名 > 名称),路径末段只作为没有名称时的回退。
安装
dsh plugin --profile web add dsh-remote-vs
安装后重启 dsh web。
Fixes
- None.
Improvements
- The side-by-side diff toolbar gained previous/next change buttons with an
n / Ncounter: jumps to that change's first row, wraps at both ends, and stays in step when you scroll by hand. - A workspace now shows in the sidebar under the name you gave it when creating it (
alias > name); the last path segment is only a fallback when no name was given.
Install
dsh plugin --profile web add dsh-remote-vs
Restart dsh web after installing.
v0.8.7
修复
- 搜索结果点击后打开的是文件本身(内容命中会定位到该行),不再打开一个可能显示"没有差异"的空白 diff;双击同样有效。
- 并排差异遇到很长的行时,现在可以左右滚动。
优化点
- 顶部横向滚动条只在真的有行超出窗口时出现,加高、提高对比度并带说明提示;两个面板各自也保留可见的横向滚动条。
安装
dsh plugin --profile web add dsh-remote-vs
安装后重启 dsh web。
Fixes
- Clicking a search result now opens the file itself (positioned at the match line for content hits) instead of a diff that could render as "no differences"; double-click works the same way.
- Very long lines in the side-by-side diff can now be scrolled horizontally.
Improvements
- The top horizontal scrollbar appears only when a line really overflows the view, is taller, has a stronger thumb and explains itself; each pane also keeps its own visible horizontal scrollbar.
Install
dsh plugin --profile web add dsh-remote-vs
Restart dsh web after installing.
v0.8.6
修复
- 无。
优化点
- 源码注释与界面占位符改为通用描述,不再出现任何具体主机、工作区或项目名。
安装
dsh plugin --profile web add dsh-remote-vs
安装后重启 dsh web。
Fixes
- None.
Improvements
- Source comments and the directory-picker placeholder now use generic wording; no specific host, workspace or project name appears anywhere in the published package.
Install
dsh plugin --profile web add dsh-remote-vs
Restart dsh web after installing.
v0.8.5
修复
- 文件名搜索在深层目录里找不到文件(旧实现给搜索加了深度上限)。
优化点
- 搜索优先使用 ripgrep,未安装时自动回退到 find / grep。
- 开发期自检脚本不再随仓库分发。
安装
dsh plugin --profile web add dsh-remote-vs
安装后重启 dsh web。
Fixes
- File-name search could not find files in deep directories (the previous implementation capped the search depth).
Improvements
- Search prefers ripgrep and falls back to find / grep when it is not installed.
- Development self-check scripts are no longer distributed with the repository.
Install
dsh plugin --profile web add dsh-remote-vs
Restart dsh web after installing.
v0.8.3 — 上架 npm / published to npm
0.8.3 — 上架 npm:dsh plugin --profile web add dsh-remote-vs
现在有两条安装路径,都不需要放行任何构建脚本:
dsh plugin --profile web add dsh-remote-vs(npm,推荐;也可以直接跟仓库装:dsh plugin --profile web add github:xiray-xia/dsh-remote-vs。)
npm 这条路连 clone 都省了,所以更推荐。两条路能免掉"放行构建脚本"的原因是同一个:安装树里
没有任何带生命周期脚本的包 —— 包本身没有 prepare/postinstall,ssh2 是打进产物的而不是依赖。
实测(一次性 profile,跑完即删):dsh plugin --profile npmprobe add dsh-remote-vs → exit 0,
自动记入依赖并加入该 profile 的 bundles,allowBuilds 一个条目都不需要。
注意下载量:包里带着预编译 helper(vendor/dsh-remote-helpers/,约 45 MB),所以首次安装约 48 MB。
它是唯一的 helper 投放渠道(官方 Release 不带 helper 可执行文件),去掉就没法给新主机部署。
安装 / 升级
dsh plugin --profile web add dsh-remote-vs
装完重启 dsh web。
0.8.3 — on npm: dsh plugin --profile web add dsh-remote-vs
There are now two install routes, and neither needs build-script approval:
dsh plugin --profile web add dsh-remote-vs(from npm, recommended; installing straight from the repository also works:
dsh plugin --profile web add github:xiray-xia/dsh-remote-vs.)
The npm route skips the clone entirely, so it is the one to prefer. Both routes avoid the "allow the
printed build scripts" step for the same reason: nothing in the installed tree carries a lifecycle
script — the package has no prepare/postinstall, and ssh2 is bundled into the artifact rather
than depended on.
Measured on a throwaway profile (deleted afterwards): dsh plugin --profile npmprobe add dsh-remote-vs exits 0, records the dependency and adds the plugin to that profile's bundles, with no
allowBuilds entry at all.
Mind the download size: the package carries the prebuilt helper (vendor/dsh-remote-helpers/,
~45 MB), so the first install is about 48 MB. It is the only channel that delivers a helper (the
official releases ship no helper executable), and dropping it would leave new hosts undeployable.
Install / upgrade
dsh plugin --profile web add dsh-remote-vs
Restart dsh web afterwards.
v0.8.2 — 一条命令装完(不再放行构建脚本)/ one-command install
0.8.2 — 一条命令装完,不再放行构建脚本
以前 dsh plugin --profile web add github:xiray-xia/dsh-remote-vs 在全新 profile 上会先失败,
提示你去该 profile 的 pnpm-workspace.yaml 里放行构建脚本再重跑。根因是 pnpm 12 的硬策略门:
任何带生命周期脚本的包都要求安装方先表态,未表态直接报错 —— 我们自己的 git 依赖 prepare 报
ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED,传递依赖 ssh2 / cpu-features 报 ERR_PNPM_IGNORED_BUILDS。
而策略只从安装方(workspace 根)读,依赖侧无法豁免(实测无效:dependenciesMeta: {built:false}、
--allow-build 的四种 key 形态、PNPM_CONFIG_STRICT_DEP_BUILDS=false)。
所以这一版把安装树里的构建脚本清零:
lib/入库(构建产物随仓库发布),去掉prepare——github:安装拿到的就是可直接用的插件;ssh2从依赖变成产物的一部分(降为 devDependency,用noExternal打进宿主构建)——
它带install: node install.js,还拖着可选原生件cpu-features。两者都是可选加速,
上游失败时自己exit 0,所以走纯 JS 加密路径。
实测(临时 profile,跑完即删):只做第一条仍被 ssh2/cpu-features 拦住;两条都做后,
同一条命令 exit 0,allowBuilds 一个条目都不需要,装出来的 lib/ 与仓库逐文件一致(98/98)。
顺带修掉一个"加载即死":把 ssh2 打进 ESM 产物后,宿主半 import 时直接
ReferenceError: __dirname is not defined(ssh2 的 CJS 在模块顶层读 __dirname),
靠 tsdown 的 shims: true 修好。这类问题 tsc、打包器、文件存在性检查都看不见,因此补了两张回归网:
scripts/verify-lib.mjs(pnpm check 第一步:入口齐全 / 已入库 / 真的能加载)与
.spike/ssh2-smoke.mjs(真机四步:连接+认证 / exec / PTY / SFTP 往返)。
安装 / 升级
dsh plugin --profile web add github:xiray-xia/dsh-remote-vs
装完重启 dsh web。
0.8.2 — one command, no build-script approval
On a fresh profile the install used to fail first and ask you to allow build scripts in that profile's
pnpm-workspace.yaml before re-running. The cause is pnpm 12's hard policy gate: any package carrying a
lifecycle script needs the installer's explicit decision, and without one the run fails — our own git
dependency prepare produced ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED, and the transitive ssh2 /
cpu-features produced ERR_PNPM_IGNORED_BUILDS. That policy is read from the installing workspace
only, so nothing on the dependency side can waive it (measured and rejected:
dependenciesMeta: {built:false}, four key forms of --allow-build,
PNPM_CONFIG_STRICT_DEP_BUILDS=false).
So this version removes every build script from the installed tree:
lib/is committed (the built artifact ships with the repository) andprepareis gone — a
github:install receives a plugin that is ready to load;ssh2is bundled into the artifact instead of being a dependency (it moves to devDependencies and
goes throughnoExternal) — it carriesinstall: node install.jsplus the optional native
cpu-features. Both are optional accelerators whose upstream script exits 0 when the native build
fails, so the pure-JS crypto path is what ships.
Measured on a scratch profile (deleted afterwards): with only the first change it still failed on
ssh2/cpu-features; with both, the same command exits 0 with no allowBuilds entry at all, and the
installed lib/ matches the repository file for file (98/98).
Also fixed: a load-time crash. Bundling ssh2 into the ESM output made the host half throw
ReferenceError: __dirname is not defined on import (ssh2's CJS reads __dirname at module scope);
tsdown's shims: true fixes it. tsc, the bundler and a file-existence check all miss that class of
failure, so it now comes with two regression nets: scripts/verify-lib.mjs (first step of pnpm check:
entries present / committed / actually loadable) and .spike/ssh2-smoke.mjs (four real-host
steps: connect + auth / exec / PTY / SFTP round trip).
Install / upgrade
dsh plugin --profile web add github:xiray-xia/dsh-remote-vs
Restart dsh web afterwards.
v0.8.1 — 修复取消暂存没反应 / inverted stage verb fix
0.8.1 — 修复「取消暂存点了没反应」
0.8.0 引入了一个方向反转的回归:源代码管理面板里逐文件的按钮发的是相反的 git 命令 ——
暂存区的行点 × 跑的是 git add,更改区的行点 + 跑的是 git restore --staged。
两个方向都是静默的(本地那一行没有可移动的状态,随后那条限定路径校验又"同意"这个它从没动过的行),
所以既不报成功也不报失败:表现为「取消暂存点了没反应,全部取消暂存可以」以及「添加到暂存区没起作用」。
区级按钮(全部暂存 / 全部取消暂存)用的是字面量,方向一直是对的。
现在方向由具名 verb 决定(rowVerb,applyStage 收 'stage' | 'unstage' 而不是 boolean),
两条门禁断言盯着它(纯不变量 + 端到端真命令行),红证:把 rowVerb 反过来 → 2 条变红,
行按钮改回裸 boolean → 1 条变红。
同一版还包含
- 暂存点击不再全量刷新:一次点击从 4 条仓库级命令(
git add+ 全量git status+ stash/branch/remote
三个 extras)降到 1 个 verb + 1 条限定路径的git status(250ms 窗口内合并,成本随被点路径而不随仓库
大小)。实测 P710(.git/index655MB):全量status -uno1.75s、限定路径 1.2s。 - 未跟踪目录行可点击展开:
-u normal把未跟踪目录压成一行dir/,现在点它发一条
git status -uall -- dir/把里面的文件列出来(再点收起;展开态在下一次刷新里按该行是否仍存在保留/清理)。 - 两条校验提示双语化:
scm.stageMismatch/scm.stageUnverified,另有目录行的scm.dirToggle。
安装 / 升级
dsh plugin --profile web add github:xiray-xia/dsh-remote-vs
改完客户端与宿主两半都要重启 dsh web(客户端包在启动时被抓进内存)。
0.8.1 — "Unstage does nothing" fixed
0.8.0 shipped a direction inversion: the per-file buttons in the Source Control panel issued the
opposite git command — × on a staged row ran git add, + on a changed row ran
git restore --staged. Both directions were silent (the local row has no state to move, and the
path-limited probe then agreed with the row it never changed), so a click reported neither success nor
failure: "unstage does nothing, unstage-all works" and "staging a file has no effect". The section-level
buttons pass literals, so they stayed correct.
The direction now comes from a named verb (rowVerb; applyStage takes 'stage' | 'unstage'
instead of a boolean) and two gate cases pin it — a pure invariant plus an end-to-end assertion on the
real command line. Red proofs: inverting rowVerb turns 2 red, a bare boolean turns 1 red.
Also in this version
- A stage click no longer refreshes the repository: one click went from 4 repository-wide commands
(git add+ a fullgit status+ the stash/branch/remote extras) to 1 verb + ONE path-limited
git status, coalesced inside a 250 ms window, so the cost tracks the clicked paths instead of the
repository size. Measured on P710 (.git/index655MB): fullstatus -uno1.75s, path-limited 1.2s. - Untracked directory rows expand on click:
-u normalcollapses an untracked directory into one
dir/row; clicking it now issues onegit status -uall -- dir/to list the files inside (click
again to collapse; an expansion survives a refresh while its row does, and is pruned when it does not). - The two verification notices are bilingual:
scm.stageMismatch/scm.stageUnverified, plus
scm.dirTogglefor the new row hint.
Install / upgrade
dsh plugin --profile web add github:xiray-xia/dsh-remote-vs
Restart dsh web after upgrading: the client bundle is snapshotted at startup.
v0.8.0 — 会话改动审阅 / Session change review
会话改动审阅(Cursor 式)
- 输入框上方可收纳面板:按会话统计改动,一行一个文件、文件类型图标、右侧
+a −b,与输入框左右对齐;⟳强制重扫 - 点文件开在远程编辑器:整行着色 + 行内字符级差异(新增字符绿色背景,删除内容用插入式删除线 chip),左侧 gutter 标记
- 改动点跳转:编辑器右上角悬浮
n/N ↑ ↓,快捷键Alt+Shift+Cmd/Ctrl+↓/↑ - 悬浮撤销:鼠标停在改动行 → 「撤销这处改动」(远端
git apply -R;上下文不匹配自动拒绝,脏标签页拒绝;撤销后光标留在原处、标记自动刷新) - 跟随会话工作区:跟随能力放在 store 层,侧栏没打开过也跟随;手动切换不被拉回;点改动文件不算手动
- 仓库目录:沿用「源代码管理 → 仓库目录」的显式设置;根目录不是仓库时只在唯一候选下自动采用,多候选会明确提示去设置
改动采集改为纯只读
不再使用 git add 拍快照(在 AOSP 单体仓库上跑不完)。现在只做只读检查:git status 找改动 → 只读这些文件的内容 → 行级 diff 在本地算。同一仓库实测从"跑不完"降到 约 1.7 秒。
修复
- 看门狗:host 调用不返回不再把面板永久卡在「正在统计」
- 自锁:送错仓库目录曾导致
cd失败 → 无摘要 → 每 3 秒循环;现在判不了就不发,host 也会退回会话根 - 串台:面板摘要带上会话 id,切会话不再显示上一会话的状态
- 同 alias 多工作区:按远程路径而不是 alias 第一项解析绑定
已知边界
- 基线是会话起点;已跟踪文件的数字随轮次结束刷新(面板 3 秒轮询兜底)
- 新文件(shell 创建的)靠慢扫描,单次预算最长约 3 分钟、间隔自适应,
⟳可强制 - 非 git 工作区 / 多仓库未指定仓库目录时,面板显示具体原因而不是空白
- 编辑器里一敲字,改动标记即作废并提示「位置已过期」
安装 / 升级
dsh plugin --profile web add github:xiray-xia/dsh-remote-vs
升级后需重启 dsh web(host 与客户端两半都会重新加载)。
Session change review (Cursor style)
- Collapsible panel above the composer: per-session changed files, one row per file, type icon,
+a −bon the right, aligned with the input card;⟳forces a rescan - Click a file to open it in the remote editor: whole-line colouring plus intra-line character diff (added characters get a green background, removed text an inline struck-through chip) and gutter markers
- Change navigation: floating
n/N ↑ ↓at the editor's top right,Alt+Shift+Cmd/Ctrl+↓/↑ - Hover to revert: hover a changed line for "revert this change" (remote
git apply -R; a context mismatch is refused, a dirty tab is refused; the caret stays put and the marks refresh) - Follows the session's workspace: the capability lives in the store, so it works with the sidebar never opened; a manual switch is never yanked back, and clicking a changed file does not count as manual
- Repository directory: the explicit setting in Source Control wins; when the root is not a repository, a single candidate is adopted automatically and several candidates are reported instead of guessed
Change collection is now read-only
No more git add snapshots (they never finished on an AOSP monorepo). It now runs read-only checks only: git status to name the files, reads of just those files, and a line diff computed locally. Measured on the same repository: from "never finished" to about 1.7 s.
Fixes
- Watchdog: a host call that never returns no longer freezes the panel at "computing…"
- Self-lock: a wrong repository directory made
cdfail, left no summary and repeated every three seconds; the client now refuses to guess and the host falls back to the session root - Cross-talk: the summary is tagged with its session, so switching sessions no longer shows the previous one's state
- Same alias, several workspaces: bindings resolve by remote path instead of the first alias match
Known limits
- The baseline is the session start; tracked files refresh as turns close (the panel's three-second poll is the backstop)
- New files (created by a shell command) come from the slow scan: up to about three minutes, self-tuning cadence,
⟳forces it - A non-git workspace or several repositories without a configured repository directory shows the specific reason instead of an empty panel
- Typing in the editor invalidates the marks and says so ("positions stale")
Install / upgrade
dsh plugin --profile web add github:xiray-xia/dsh-remote-vs
Restart dsh web after upgrading — both halves reload.
v0.7.5 — 拖拽上传(含文件夹)+ 取消不再留下半个文件
中文
新功能
- 拖拽上传 —— 从资源管理器把文件/文件夹拖到远端文件树上即可上传:
- 拖到文件夹行 = 进该目录;拖到文件行 = 进它所在目录;拖到空白 = 工作区根;
- 支持文件夹递归(含空子目录),同样建出远端目录结构;
- 不依赖
event.target:DSH 拖动时会在整窗口盖一层「文件或图片拖动到此处即可添加」遮罩,真正的drop目标是那层遮罩。现在按指针坐标在 window 捕获阶段接管,事件不会被输入框抢走(文件不会跑进聊天框)。
- 右键菜单 / 标题栏上传 —— 「上传文件…」「上传文件夹…」,落到右键的那个目录。
修复
- 取消上传不再留下半个文件 —— 改为原子上传:先写同目录
.part-<id>侧文件,传输正常结束才rename到最终名;取消/失败/超限一律删掉侧文件,远端目录与操作前逐字节一致。覆盖时先删旧文件再 rename(SFTP v3 不允许 rename 覆盖)。 - 取消后主机不再"变慢/卡住" —— 取消的请求此前不会结束 Host 侧的上传 Promise,会一直占着 SFTP 槽位,导致该主机后续
stat/list全部排队。现在取消即结束并释放。 - 上传条按阶段说话 —— 「正在读取拖放内容…」→「等待确认覆盖 N」→「上传 2/5 · 43% · 1.3/3.0MB · 文件名」;同名文件冲突只问一次;✕ 可取消整批(含弹窗等待阶段)。
- 上传条先消失再刷新 —— 之前要等上传后的 git status(巨型仓库可达 60 秒)跑完才消失,看起来像卡死。
兼容
要求 DSH 0.2.1-alpha.3 及以上(engines.dsh 门到底),实测基线 0.2.1-rc.2;helper 二进制不变。上传走新路由 POST /api/dsh-remote/upload(原始 body 流式写 SFTP,不缓冲整个文件、不受 10 MiB JSON 上限),单文件上限 2 GiB。
升级
host 半有改动(新路由 + 原子上传 + 取消释放):重启 Host,浏览器硬刷新(Ctrl+F5)。
在 Tauri 里用 DSH 的注意
应用内(WebView2)还要关掉 Tauri 的拖放拦截,否则系统拖放根本到不了页面:
WebviewBuilder/WebviewWindowBuilder::new(...).disable_drag_drop_handler()(已在 Android ADB Tool 2.0.52 中应用)。
English
Features
- Drag & drop upload — drop files or folders from Explorer onto the remote file tree:
- onto a folder row it lands inside that folder, onto a file row beside it, onto empty space in the workspace root;
- folders recurse (empty subdirectories included) and reproduce the same structure remotely;
- it does not trust
event.target: the DSH shell paints a full-screen "drop files here" overlay during a drag, so the realdroptarget is that overlay. The zone claims the event on window capture by POINTER POSITION, so the composer cannot swallow it (files never end up attached to the chat).
- Context menu / header upload — "Upload File…" and "Upload Folder…", targeting the directory that was right-clicked.
Fixes
- Cancelling no longer leaves a half file — uploads are atomic: bytes stream into a
.part-<id>side file next to the target and are renamed into place only after the body ends; cancellation, failure or the size ceiling removes the side file, so the remote folder ends up byte-for-byte unchanged. Overwrite deletes the old target first (SFTP v3renamerefuses to clobber). - A cancelled upload no longer wedges the host — the aborted request used to leave the Host's upload promise pending, holding an SFTP slot so later
stat/listcalls queued behind it. It now settles and releases immediately. - The upload bar names its phase — "reading dropped items" → "waiting for the overwrite confirmation" → "uploading 2/5 · 43% · 1.3/3.0MB · name"; the overwrite prompt appears once per batch; ✕ cancels the whole batch including the prompt.
- The bar clears before the refresh — it used to stay up through the post-upload
git status(up to 60s on a huge tree), which read as a hang.
Compatibility
Requires DSH 0.2.1-alpha.3 or newer (engines.dsh enforces the range); the tested baseline is 0.2.1-rc.2; the helper binary is unchanged. Uploads use the new POST /api/dsh-remote/upload route (raw body streamed into SFTP — no whole-file buffering, no 10 MiB JSON cap); one file may be up to 2 GiB.
Upgrading
The host half changed (new route, atomic upload, cancellation release): restart the Host, then hard-refresh the page (Ctrl+F5).
When DSH runs inside a Tauri app
Disable Tauri's drag-drop interception, or OS drops never reach the page:
WebviewBuilder/WebviewWindowBuilder::new(...).disable_drag_drop_handler() (applied in Android ADB Tool 2.0.52).