source-code-mgmt — DSH 源代码管理插件
English | 中文
版本:v1.25.0 | 更新日志见文末「版本历史」
界面语言跟随 DSH 设置实时切换:面板与 host 端消息自动使用 DSH 的语言(设置 → 通用 → 语言),中文 ↔ 英文即时生效,无需重启。
DSH Web GUI 源代码管理插件:一个「代码管理」面板把 ① 环境检查 → ② SSH 密钥与连接 → ③ 代码管理 → ④ 克隆仓库 → ⑤ 发布 npm 包 全流程串起来,GitHub / Gitee 双平台通用。环境检查自动检测 Git / GitHub CLI / SSH,缺工具可一键安装(按平台自适应、尽量免 sudo;gh 支持镜像下载源加速,Windows 亦可用),并带「检查更新」跟进新版本。
入口默认注册进 DSH 自带的右侧栏(不再保留单独的按钮):在侧边栏「开始」页上,「代码管理」是与 工作区文件 / 新建终端 / 浏览器 / 壁纸引擎 并列的一张卡片,点开即在该栏里以页面形式打开面板。老宿主自动降级:装了 dsh-better-sidebar 时注册成它的侧边栏 Tab;两者都没有时(旧版宿主)才回退到右上角的 header 入口(有会话时放在「Session 日志」旁;无会话/新对话时钉在右上角,与有会话时逐像素同高)。不会占用左边栏。
功能
集成入口(按可用性自动选择,无需手动切换;任何时刻只有一个入口):
-
DSH 自带右侧栏(默认,harness ≥ 0.1.5):「代码管理」作为侧边栏「开始」页(guide)上的一张卡片 + 该栏里的一个页面。点卡片即在右侧栏打开面板(官方行为:openTab(kind, { replaceTab: true }),替换掉「开始」页本身,像浏览器起始页那样)。卡片顺序排在内置三张(工作区文件 10 / 新建终端 20 / 浏览器 30)与其它插件(壁纸引擎 100)之后 = 110。
- 没有独立按钮:座位
sidebar.right.pane.tab 存在即代表宿主自带右侧栏,此时插件完全不注册 header 入口 —— 这正是「去掉单独按钮」的落点。
- 卡片标题/说明是函数(
title: () => t("代码管理")),由宿主每次渲染时求值 ⇒ 切语言无需重新注册。
- 面板用
variant: "tab":填满该栏、不画自己的 ✕(关闭由宿主的 tab 栏负责)。
-
已安装 dsh-better-sidebar(旧宿主降级):「代码管理」注册为它侧边栏的一个新 Tab 页面,点击侧边栏 Tab 直接打开面板;
-
两者都没有(旧宿主降级):入口只注册在会话 header 里(两处严格互斥,任何时刻只有一个「代码管理」可见)——
- ① 有活跃(非空白)会话时:经
conversation.session.header.utilities 槽位放在「Session 日志」旁(同款胶囊、间距 8px 一致);
- ② 空白(新对话)/无会话空态时:经常驻的
shell.overlay 槽注册一个常驻按钮,钉在右上角、且与「有会话时」那枚按钮完全同高。
- 纵向与有会话时对齐:有会话时按钮在 header 的 utilities 行里,该行几何为
.header 的 padding-top: 10px + .titleRow 的 min-height: 30px(居中)⇒ 标题行占 y=10..40、中心 25px;按钮高 32px ⇒ top = 10 + (30 - 32)/2 = 9px。空态按钮取同一个 9px,所以两种形态切换时按钮不跳位。桌面壳里再叠加 --dsh-windows-titlebar-height(普通浏览器为 0),写法是 calc(var(--dsh-windows-titlebar-height, 0px) + 9px)。
- 横向内缩
right: 48px,让开空态 header 右上角已有的「右侧栏展开按钮」:空态下 .headerBlank .headerCorner 带 margin-left: auto,把 ui-sidebar-right 的 ExpandButton(28×28 圆钮)顶到最右,其盒子位于视口右侧 12px~40px(.header 的 padding-right: 28px 减去 .headerCorner 的 margin-right: -16px 得到右缘 12px)。48 = 40 + 8(8px 与 header 内其它控件的间距一致),整个按钮落在圆钮左侧、互不相交。
降级入口点击都打开同一个 dsh-better-sidebar 外观的右侧集成面板(内容放同一面板),并把主内容区往左推挤。
座位「晚到」有宽限窗口(600ms):sidebar.right.pane.tab 座位属于另一个包(ui-sidebar-right),加载器不保证顺序。若座位比本插件晚声明,这段时间里先不挂兜底按钮 —— 否则官方宿主上会先闪一个「代码管理」按钮、随后才被侧边栏卡片取代。窗口内座位出现 → 永远不挂;窗口耗尽仍没有 → 才按老宿主降级(老宿主本来就吃这条路径,晚 0.6s 出现按钮不可见)。全程零 I/O,只是一个计时器。
关闭方式:右侧栏形态由宿主的 tab 栏负责(点 ✕ / 切页);降级面板有三种 —— 面板右上角 ✕(标题行固定不滚动,内容滚到哪都在视口内)、按 Esc、或再次点「代码管理」按钮。
降级面板顶部会让开窗口控件那条带(--dsh-frame-chrome-top):桌面壳里 ─ □ ✕ 是 OS 绘制的覆盖层,任何贴到右上角的网页按钮都会被它压住、点不到(v1.24.1 修的正是这个「打开后关不掉」),所以面板整体从该带下方开始;原生全屏时该变量归零,面板仍用满整屏。
刻意不注册进 DSH 自带左边栏(sidebar.footer.action)——那个入口会挤在左边栏「设置」上方,与侧栏自身的项混在一起。
也刻意不去抢 DSH 自己的单占位槽:conversation.session.header.corner 已被自带右侧栏开关(ui-sidebar-right 的 ExpandButton)占用,conversation.session.header.leading 被自带侧栏开关(ui-sidebar 的 HeaderLeadingControls)占用;sidebar.panellist 的每一项 id 必须是真实主面板 id(会交给 layout.selectPanel),本插件没有主面板,注册进去只会得到点不动的死按钮。
检测只是激活时两次内存读取(座位声明 + ctx.get('sidebarRightTabs')),零 I/O、零网络,不影响 DSH 启动速度。
面板分五步(①②③ 为核心三步,④⑤ 为新扩展板块,默认折叠按需展开):
① 环境检查
- 显示操作系统(美化名:
Windows / macOS / Linux,对应底层 Node 平台标识 win32 / darwin / linux)
- 自动检测 Git、GitHub CLI 是否安装及版本(如
git version 2.55.0、gh version 2.97.0)
- 检测 SSH 客户端是否可用(解析到可用
ssh 即显示「已找到」)
- 缺工具时给安装指引 + 一键安装(按平台自适应、尽量免 sudo):某工具未找到时,该行显示「❌ 未安装」+「复制安装命令」+「安装」按钮:
- GitHub CLI(Linux / macOS):免 sudo 用户级安装——从官方 GitHub Release 下载对应平台/架构的二进制包,解压后装到
~/.local/bin/gh(目录不存在会自动创建,全程不需要管理员权限);失败时自动回退系统包管理器。
- Git(Linux):apt / dnf / pacman(自动带
sudo -n;先探测 sudo 是否免密,需要密码时不再盲目执行,而是给出「请在终端手动执行」的指引 + 完整命令)。Git(macOS):brew,无 brew 时走 Xcode 命令行工具(xcode-select --install,系统自带安装,含 git/ssh)。
- SSH(Windows):内置可选功能(需管理员);SSH(Linux):apt / dnf / pacman 的 openssh-client(同上 sudo 探测);macOS 系统自带。
- 复制安装命令与「安装」按钮走同一套平台自适应逻辑(不再固定显示 Windows 的 winget 命令)。
- 安装过程实时进度弹窗:点「安装」后弹出进度窗口,逐步显示「查询最新版本 → 下载 → 解压 → 安装 → 清理」及实时输出;完成后显示成功/失败原因。
- 需要重启时提示并一键重启:检测到安装后当前 host 进程无法立即识别(如 gh 装到了不在 PATH 的
~/.local/bin)时,弹窗提示并显示「重启 DSH」按钮——点击后 host 通过 detached 辅助进程按原启动命令自动拉起新进程(机制同 dsh-update),页面短暂断开后刷新即可;若 host 已能识别(如设置了 DSH_SCM_GH 或 ~/.local/bin 在 PATH),则不打扰直接完成。
- 安装完成后自动重新检测。
② SSH 密钥与连接
- 平台选择:下拉选择代码托管平台 GitHub(默认) / Gitee,决定下面的 SSH 配置写入与连接测试目标
- 自动探测 ed25519 密钥:扫描
~/.ssh/*.pub 中已存在的 ed25519 公钥——优先用 id_ed25519;否则用找到的第一个(支持任意命名的密钥,如 github_ed25519);都没有则默认名 id_ed25519。状态行显示实际检测到的密钥文件名,SSH config 的 IdentityFile 也用它
- 一键生成 ed25519 密钥(无密码;本地已有 ed25519 密钥时复用,不会重复生成)
- 一键写入 SSH config,端口可选(见下):
- 443(穿墙,默认):GitHub 写
github.com → ssh.github.com:443;Gitee 写 gitee.com:443。用于绕过封锁 22 端口的网络。
- 22(标准):GitHub 写
github.com → github.com:22(标准端点)。家宽 / 办公室 / VPS 通常直连即可。
- 切换端口后再次点「配置 SSH(config)」会就地改写
~/.ssh/config 里对应的 Host 块(不会留下重复块,也不动其它 Host);状态行显示的是实际配置的端口(host 端解析 ~/.ssh/config 得出),不再是写死的 (443)。
- 「测试连接」也按所选端口测试(
ssh -T -p <port>),失败时提示「若是网络封锁 22 端口,请改用 443」。
- 测试连接
ssh -T -p <port> git@<host>(GitHub 走 ssh.github.com / github.com,Gitee 走 gitee.com)
- 显示公钥内容,方便复制上传到对应平台
- 检测
gh 是否已登录及账号
443 与「是否有代理」无关(常见误解)。 普通 HTTP 代理只对 git-over-HTTPS / curl 生效;OpenSSH 完全无视 HTTP_PROXY / HTTPS_PROXY(要走代理得配 ProxyCommand)。所以「我开了代理」并不等于「22 端口通了」——该不该用 443 取决于你的网络是否封锁 22 端口,因此这里交给用户自己选,而不是由插件猜测。
海外/正常网络需要 443 吗?——不需要。 「写 SSH config」本身是可选的(只有你点按钮才写 ~/.ssh/config)。GitHub 标准端点就是 git@github.com 走 22 端口,正常网络开箱即用:生成密钥 → 把公钥贴到 GitHub → 测试连接 → 推送,全程 22 端口。443(Host github.com → HostName ssh.github.com, Port 443)是 GitHub 官方支持的兜底方案,典型场景是国内网络、部分公司/校园网;只有当 443 也被封锁时才会反而不通(极少数网络)。Gitee 是国内平台,海外用户基本只会用到 GitHub。
③ 代码管理
- 跟随 ② 平台:本区所有「检测/新建/可见性」逻辑随 ② 的平台选择切换(GitHub 走
gh CLI,Gitee 走 Gitee OpenAPI)
- Gitee 令牌(仅 Gitee 模式显示):输入 Gitee 私人访问令牌(需
projects 权限)→ 保存在本机 ~/.dsh/storages/source-code-mgmt-gitee.json(0600,不写入插件目录、不回传到浏览器/日志);可一键清除;令牌无效会自动清掉
- 选择工作区:下拉选择 DSH 已登记的工作区文件夹,选中即加载
- 选择目录 →:下拉右侧按钮,可手动输入/粘贴目录绝对路径或点击「浏览…」弹出原生文件夹选择器;确认后持久化加入自定义目录列表(插件独立存储于
~/.dsh/storages/source-code-mgmt-dirs.json,不写入插件目录,开源不泄漏个人路径),下次打开无需重新选择;手动添加的目录会以自定义目录徽标显示,末端带 ✕ 可一键删除该下拉记录(只删记录,不删实际文件夹)
- 显示仓库状态:平台来源、分支、远程地址、待提交改动数、领先/落后远程、>100MB 文件
- 查看详情:有改动时「改动」行旁出现「查看」按钮 → 点击弹出窗口列出改动/新增/删除/重命名的文件或文件夹名称;所有可文本预览的文件(含新增/untracked 新文件)都可点击文件行展开查看内容——并排视图(左旧右新,删除行红底、新增行绿底),纯新增文件没有旧版本,只显示「新版本」一列;二进制文件不显示「查看」(内容无法按文本预览);本地与远程存在差异时「同步」行旁出现「查看」按钮 → 点击弹出窗口显示本地领先/落后的具体提交列表;无改动或已一致时不显示按钮
- 本地 Git 工作流(不改动远程同步逻辑):
- 「改动」弹窗里每个文件行有暂存 / 取消暂存按钮(按
git status 的 XY 状态区分 staged/unstaged),并显示「已暂存 / 未暂存」标记
- ③ 面板仓库名上方有提交信息输入框 + 「提交」按钮(仅 git 仓库且有改动时显示)——可写自定义提交信息,不再用固定 message;留空则自动生成
- 「分支」行旁有**「切换」**按钮 → 弹窗列出分支,点选即
checkout
- 「分支」行旁有**「历史」按钮 → 弹窗列出提交(hash+subject+author+date),每条可查看**(并排 diff)、revert、cherry-pick(后两者带确认框,因为会改写历史)
- 远程按平台 + 当前账号匹配:③ 的「远程 / 同步」只认「属于当前平台的远程」且 owner 等于当前登录账号(GitHub 平台 =
gh 账号如 Zhucy123 名下,Gitee 平台 = Gitee 令牌账号如 Zhucy2100 名下)。这样:
- 切到 Gitee 时不读 GitHub 的 origin,只读 gitee.com 的远程;
- github.com 上属于别人/其他组织的仓库(如
deepseek-ai/deepseek-harness)不会被当作「用户自己的远程」显示,ahead/behind 也不对它计算;
- 本地没有属于当前账号的远程时,「远程」显示「(无)」、不计算同步,只走「同名仓库检测 + 新建仓库并推送」形态。
- 创建 Git:当所选目录不是 git 仓库但远程已存在同名仓库时显示该按钮,仅执行
git init(+设默认身份),不拉取不推送,由用户自行决定下一步是拉取还是推送
- 新建仓库并推送:默认以文件夹名为仓库名(只读不可改),可选私有/公开;同名仓库已存在时按钮禁用并在下方提示「同名仓库已经创建」。若目录还是全新的(尚无任何提交),会先自动 + 生成一个初始提交再创建,避免推送时报 "no commits found"
④ 克隆仓库(Clone)
- 跟随 ② 平台:把已登录账号(GitHub 走
gh,Gitee 走 OpenAPI)名下所有远程仓库列出来,并自动标记本地是否已有同名 git 仓库(默认按「默认工作区 + 已登记工作区 + 自定义目录」判定)
- 克隆到目录:可选目标父目录(默认 = DSH 默认工作区),不选即克隆到默认位置;克隆走 SSH URL(
git@github.com:… / git@gitee.com:…),成功后自动加入自定义目录列表,③ 里可直接选中
- 列表里「本地已有」的仓库不可重复克隆,其余每个仓库一个「克隆」按钮
⑤ 发布 npm 包(npm publish)
- 五步向导(在目标目录执行):① 确认源
npm config get registry → ② 查看认证配置 npm config list(自动脱敏 token/auth/password)→ ③ 验证身份 npm whoami → ④ 预览打包 npm pack --dry-run(先看会发布哪些文件,不真正打包发布)→ ⑤ 发布 npm publish
- 登录与发布固定走官方源
https://registry.npmjs.org:即使你的全局 npm 配置是镜像源(如 registry.npmmirror.com——镜像只同步、不接受发布),whoami / npm login / npm publish 也统一带 --registry=https://registry.npmjs.org;状态区在检测到镜像配置时会黄色提示「发布将使用官方源」。
- 「打开终端执行 npm login」安全弹终端:先探测系统真实存在的终端程序(
konsole / gnome-terminal / xterm 系等),再 spawn(并挂 error 监听兜底)——修复了旧版 spawn 不存在二进制时异步 ENOENT 未捕获、直接把 dsh host 进程打崩的问题;KDE 下用 konsole --separate 开独立窗口,不与宿主所在终端实例纠缠。找不到终端时明确返回手动命令指引,不再假装成功。
- 发布前必须勾选「我已核对以上内容,确认发布到 npm registry」才可点「发布」,防止误操作
数据加载时机(打开时联网、显示刷新中)
DSH 打开时不联网同步仓库,只预取静态的环境/SSH/工作区列表。打开插件、切换工作区、刷新状态、以及推送/拉取/暂存/提交等操作后,都会联网获取对应工作区的最新仓库状态,并显示「⟳ 刷新中…」提示——避免打开/重开/切换时显示可能过期的旧数据(如旧的「无改动」)。关闭面板再打开也会重新同步,不会停留在旧状态。
安装
本插件以 Profile Bundle 形态分发:package.json 声明了 dsh.bundle(携带 cordis.patch.yml 配置层),所以 dsh plugin --profile web add 一条命令装完即自动激活——无需手动编辑任何配置文件。
方式一:从 npm 官方包安装(推荐)
最省事的方式——只需一条命令,且不用先进 profile 目录,推荐给普通使用者。
在任意目录执行:
# 用 DSH 自带的插件命令(自动定位/初始化 web profile)
dsh plugin --profile web add source-code-mgmt
该命令在 web profile 目录里执行 pnpm add,成功后对账插件层:检测到本插件声明 dsh.bundle,会自动把它追加进 dsh.profile.bundles(见 ~/.dsh/profiles/web/package.json)并注册进 Cordis loader 树,一步装完即用。
装完完全重启 dsh web(不是刷新页面,而是要停掉旧进程后重新启动),然后浏览器 F5 刷新,「代码管理」入口即出现(已装 dsh-better-sidebar 时为侧边栏 Tab,未装时为右上角 Session log 旁的「代码管理」按钮 + 右侧集成面板)。
方式二:从本地目录安装(开发/测试)
Windows (PowerShell):
# 用 DSH 插件命令安装本地源码(link: 协议,符号链接,改源码即生效)
dsh plugin --profile web add link:C:/path/to/source-code-mgmt
dsh web
Linux / macOS:
dsh plugin --profile web add link:/home/yourname/path/to/source-code-mgmt
dsh web
方式三:从 GitHub 安装(分发场景)
Windows / Linux / macOS 通用:
dsh plugin --profile web add git+https://github.com/Zhucy123/source-code-mgmt.git
dsh web
git 安装会把源码实际拷贝到 node_modules,改动源码需重新 dsh plugin --profile web add ... 拉取(不像 link: 是符号链接、改源码即生效)。
验证安装是否成功
安装并重启后,可以核对以下几点:
- 依赖已写入:
~/.dsh/profiles/web/package.json 的 dependencies 里应有 source-code-mgmt。
- 已加入配置层:
~/.dsh/profiles/web/package.json 的 dsh.profile.bundles 列表里应有 source-code-mgmt(dsh plugin add 自动写入,无需手动编辑)。
- 符号链接已建立(
link: 方式):~/.dsh/profiles/web/node_modules/source-code-mgmt 指向源码目录(Windows 显示为 Junction)。
- 重启后入口可见:已装 dsh-better-sidebar 时侧边栏出现「代码管理」Tab;未装时右上角 Session log 旁出现「代码管理」按钮,点击展开右侧集成面板。
常见排障
| 现象 | 原因 / 处理 |
|---|
已 dsh plugin add 并重启,但按钮不出现 | 最常见:装完没有完全重启(不是刷新)。停掉旧 dsh web 进程再启动(旧进程还占着 3080 端口时,新实例起不来)。 |
| 安装时提示「declares no dsh.bundle」 | 装到的版本缺少 bundle 声明(旧版或打包遗漏 cordis.patch.yml)。确认版本 ≥ 1.9.0 后重新安装/更新。 |
| 出现「Failed to load plugins」 | 插件 host 端 index.js 启动报错(多为依赖解析问题)。查看启动日志,确认 node_modules 依赖已装齐。 |
使用步骤
- 重启 dsh web 并刷新浏览器
- 点击「代码管理」入口(已装 dsh-better-sidebar 时点侧边栏 Tab,未装时点右上角 Session log 旁的「代码管理」按钮展开右侧集成面板)
- 面板打开(联网获取当前工作区最新状态,显示「⟳ 刷新中…」,拉取完成即显示内容)
- ①确认 Git / GitHub CLI 已安装 → ②生成密钥并测试连接 → ③选择工作区后推送或新建仓库
后端 API 路由
| 路由 | 方法 | 说明 |
|---|
/api/source-code-mgmt/env | GET | 环境检查(git/gh 版本) |
/api/source-code-mgmt/install-tool | POST | 一键安装缺失工具(body tool: git/gh/ssh;gh 可另传 source: official 或 gh-proxy.com/ghfast.top/ghproxy.net 等镜像 id,按平台自适应:gh 在 Linux/macOS 免 sudo 装到 ~/.local/bin;git/ssh 走系统包管理器 / brew / Xcode CLT / winget;响应为 NDJSON 事件流:step/out/result,供进度弹窗实时展示) |
/api/source-code-mgmt/install-command | POST | 按工具与下载源返回可复制的安装命令(body tool + 可选 source;与「安装」同源逻辑) |
/api/source-code-mgmt/check-update | POST | 检查 gh / git 是否有可用更新(body tool;返回 {current, latest, hasUpdate}) |
/api/source-code-mgmt/ssh | GET | SSH 密钥 / config / gh 登录状态 |
/api/source-code-mgmt/gh/login | POST | 打开终端运行 gh auth login(交互式登录需用户在弹出终端完成;找不到终端时返回手动命令指引) |
/api/source-code-mgmt/gen-key | POST | 生成 ed25519 密钥 |
/api/source-code-mgmt/write-config | POST | 写入 SSH config(body provider: github 默认 / gitee) |
/api/source-code-mgmt/ssh-test | POST | 测试 SSH 连接(body provider: github 默认 / gitee) |
/api/source-code-mgmt/default-dir | GET | 当前工作区目录 |
/api/source-code-mgmt/workspaces | GET | 列出所有工作区目录 + 自定义目录集合(DSH 工作区 + 插件自定义目录) |
/api/source-code-mgmt/pick-dir | POST | 宿主端弹出原生文件夹选择对话框,返回选中的路径 |
/api/source-code-mgmt/add-workspace | POST | 校验目录存在并持久化加入插件自定义目录列表,返回合并后的工作区列表 |
|
安全
所有路由均为 loopback-only(sec-fetch-site + Origin 校验),仅本机浏览器可访问,LAN/手机来源一律 403——与控制面板同一策略。
跨平台
- Windows / Linux / macOS 通用
- 用
process.platform 检测平台
~/.ssh 通过 homedir() 定位(Windows: C:\Users\用户名\ .ssh,Linux/macOS: /home/用户名/.ssh 或 /Users/用户名/.ssh)
- Linux/macOS 下 SSH config 自动设 0600 权限
- git / gh / ssh / ssh-keygen 二进制自动探测:启动时依次按 ①环境变量覆盖 → ②PATH 查找(Windows 加
.exe)→(仅 Windows)③Git 自带目录(usr\bin / bin)回退,最后兜底用裸命令名。因此只要装了 Git,即使 ssh 不在 PATH 里也能正常工作,换设备无需额外配置。
- 如需手动指定二进制路径,可用环境变量覆盖:
DSH_SCM_GIT / DSH_SCM_GH / DSH_SCM_SSH / DSH_SCM_SSH_KEYGEN
- SSH 传输修复:Git for Windows 自带的 MSYS
ssh.exe(usr\bin\ssh.exe)在被 detached/agent 进程调用时可能报 couldn't create signal pipe, Win32 error 5,导致 git push/git pull 失败。插件执行 git 远程命令时会自动注入 GIT_SSH 指向解析到的可用 ssh(通常为系统 OpenSSH C:\Windows\System32\OpenSSH\ssh.exe),避免该问题。
- 中文文件名兼容:git 默认
core.quotepath 会把含非 ASCII 字节的路径输出成八进制转义的引号串(显示为乱码)。插件对所有 git 调用统一注入 -c core.quotepath=false(直接输出原始 UTF-8 路径),并对仍带引号转义的路径做 parseGitPath() 反转义兜底——改动列表 / diff 里的中文文件名显示正常
- 缺工具一键安装跨平台(v1.15.0 起按平台自适应、尽量免 sudo):GitHub CLI 在 Linux/macOS 直接免 sudo 用户级安装(官方二进制 →
~/.local/bin/gh,失败回退系统包管理器);git/ssh 在 Linux 走 apt-get / dnf / pacman(先探测 sudo 免密,需要密码时给出手动命令指引而非静默失败),macOS 走 brew / Xcode 命令行工具,Windows 走 winget / 内置功能(回退 choco/scoop)。安装过程有实时进度弹窗,需要重启时提供一键重启 dsh。SSH 密钥探测、本地 Git 工作流、并排 diff 等在三个平台行为一致;「浏览目录」的原生选择器仅 Windows 可用,macOS/Linux 上请在输入框直接填路径(可手动输入粘贴)。
开发
git clone https://github.com/Zhucy123/source-code-mgmt.git
cd source-code-mgmt
# 在本地 DSH 测试(安装到 web profile,自动激活)
dsh plugin --profile web add link:$(pwd)
- 改动
lib/client.js(浏览器端)→ 刷新页面即生效
- 改动
lib/index.js(host/Node 端)→ 刷新页面即可让面板重新检测(toolInstalled 按当前 PATH / ~/.local/bin 实测);如 host 端 API 仍为旧版,重启 dsh web 进程一次
版本历史
v1.25.0(当前)
变更:入口默认注册进 DSH 自带的右侧栏,「代码管理」不再是一个单独的按钮:
- 需求:把插件默认注册进 DSH 的侧边栏,而不是保留右上角那个独立按钮。
- 背景(先说清「侧边栏」指哪个):截图里被圈掉的是右上角那枚「代码管理」按钮;红圈标出的位置是右侧栏「开始」页上 工作区文件 / 新建终端 / 浏览器 / 壁纸引擎 那张卡片列表下面的空位。那四张卡片来自 DSH 自带右侧栏(ui-sidebar-right)的 guide(
sidebar.right.pane.tab 座位 + sidebarRightTabs.register({ guide })),并不是 sidebar.footer.action(左边栏底部)——左边栏早在 v1.1.2 起就明确不碰。
- 实现:激活时
slots.inject('sidebar.right.pane.tab', …) —— 座位声明了就是这个宿主自带右侧栏,回调跑没跑即天然的能力门(不嗅探版本号,少一个漂移源)。回调里:
ctx.get('sidebarRightTabs').register({ id: 'source-code-mgmt/sidebar', kind: 'source-code-mgmt', title: () => t('代码管理'), guide: [{ id: 'panel', order: 110, title, description, icon: RepoIcon }] }) —— 这张 guide 条目就是「开始」页上的卡片;
slots.register({ name: 'sidebar.right.pane.tab', key: 'source-code-mgmt/sidebar' }, () => h(ScmPanel, { variant: 'tab' })) —— 卡片点开后在该栏里渲染的页面。key 必须等于 tab 类型的 id:宿主 TabSlot 用 definition.id 作 entryKey 派发 tab 本体,写错就会「卡片点开了但面板空白」。
- 卡片顺序
order: 110:内置三张是 10 / 20 / 30,壁纸引擎(dsh-plugin-wallpaper-engine)是 100,所以本卡片排在其后、落在列表末尾。
title / description 是函数,宿主每次渲染都求值 ⇒ 切语言不需要重新注册(旧的 better-sidebar 路径因为收的是字符串,才需要 subscribe 后重注册)。
- 「去掉单独按钮」的落点:座位存在时根本不注册 header 两处入口 —— 不是「注册了再隐藏」。分支顺序 ①官方右侧栏 → ②dsh-better-sidebar → ③header 兜底,先成立者独占入口。
- 座位晚到的宽限窗口:
sidebar.right.pane.tab 属于另一个包,可能比本插件晚声明。新增 SCM_FALLBACK_GRACE_MS = 600:这段窗口内先不挂兜底按钮(否则官方宿主上会先闪一个按钮再被卡片取代),窗口内座位出现 → 永远不挂,窗口耗尽才降级。全程零 I/O。
- 服务晚到:座位声明了 ≠
sidebarRightTabs 服务已就绪,故 ctx.get 短轮询(250ms × 40 ≈ 10s)后才放弃并回落 ③;tab 本体注册包在 ctx.effect 里,让 DSH 在插件卸载时自动注销(避免 fiber 已 dispose 时抛 INACTIVE_EFFECT)。
- 老宿主不受影响:装 dsh-better-sidebar 的走 ②(原逻辑不动);两者都没有的走 ③(header 两处入口的原几何、互斥判定全部保留)。
- 测试:开发期用 4 个针对性回归文件覆盖本条改动 ——
verify-entry-runtime.test.mjs(32 项,三段分支 + 卡片形状 + 图标尺寸 + 流体布局 + 卸载清理)、verify-sidebar-entry.test.mjs(16 项,「忠实宿主模型」:座位按宣告纪元重跑注入、keyed 座位拒绝重复 key、ctx.effect 按注入作用域回收、卸载按 LIFO / FIFO 两种顺序都验、注册抛异常路径,以及「兜底只能从路由里挂」的结构断言)、verify-sidebar-adversarial.test.mjs(16 项对抗式:能不能让官方宿主冒出独立按钮 / 能不能把用户搞成「零入口」)、verify-sidebar-postfix.test.mjs(3 项,独立复核者所写)。这些测试文件已在本版发布前删除(见下方「变更范围」),此处保留它们的结论作为改动依据的记录。
- :由一位独立验证者对照真实 逐条核对宿主契约(座位名/guide 排序//图标 size 22|26//无 也能按 kind 打开/ 确实有语义),并,随后全部修掉:
v1.24.4(历史)
修复:面板打开后,窗口顶栏仍然有一条白缝(v1.24.3 的补带只补了面板那一段):
- 现象:重启后白缝依旧 —— 顶栏在面板左缘仍是断开的。
- 根因:v1.24.3 补的那条带宽度写的是
panelW(跟面板同宽),但白缝的位置并不都在面板宽度内 —— 面板宽度随拖拽变化,补带宽度跟着变就总会留下没补到的段。
- 修复:补带改为
left: 0; right: 0 横贯整个窗口宽度。左半部分本来就是同色的原生顶栏,重复铺同色无副作用;右半部分把缺的那段补齐。不再依赖面板宽度 —— 拖拽改宽、开合面板都不会在顶栏留下任何缝。顺带删掉了为同步宽度而加的 chromeRef/getChromeEl(不再需要)。
- 回归测试:断言补带必须
left: 0; right: 0 横贯整宽(变异测试验证过:改回 panelW 宽会失败)。
- 变更范围:
lib/client.js(桌面版无需操作,自动热重载)、tools/verify-close-button.test.mjs、README.md、README.en.md、package.json。版本号 1.24.3 → 1.24.4。
v1.24.3(历史)
v1.24.3(历史)
修复:面板打开后,窗口顶栏那一段出现明显色差(白条):
- 现象:打开「代码管理」后,窗口最顶部那条原生标题栏在面板上方变成白色,左半(正常内容上方)仍是原来的浅灰
#f9fafb —— 中间在面板左缘形成一条硬缝。
- 根因(推挤的连带副作用):桌面壳的 40px 顶栏是由
.BynINW_frame(及其 :before)用 --dsw-specific-sidebar-fill(= --dsw-static-neutral-bluish-50,#f9fafb)画出来的,而 frame 在 #root 里。本插件为了让位,用 .LAYOUT_CSS 把 #root 收窄了 panelW:#root { margin-right: var(--scm-push); width: calc(100% - var(--scm-push)) }。顶栏跟着 #root 一起被收窄,于是在面板左缘戛然而止;面板上方那一段没人画,露出页面底色(--dsw-alias-bg-layer-1 = #ffffff)。不是面板盖住了顶栏,而是顶栏被推挤截断了。
- 修复:在面板 portal 里补一条与顶栏同高、同宽(面板宽)的色带,用同一个 token
var(--dsw-specific-sidebar-fill, transparent) 填充,顶栏于是横贯整个窗口宽度、看不出接缝。
pointerEvents: "none" —— 这条带只负责上色,鼠标事件照旧穿透给底下的原生拖拽区域,不改变顶栏原有的拖动行为;
- 高度用
SCM_CHROME_TOP(普通浏览器下为 0px → 高度 0、整块不可见,与改造前一致);原生全屏下同样归零,不会多出一条带;
- 拖拽改宽时它必须跟面板同步改宽(否则顶栏会随拖拽裂开一条缝):
makeResizeHandler 相应多接一个 getChromeEl,逐帧同时写面板与色带的 width。
- 回归测试(
tools/verify-close-button.test.mjs,9 项,本次新增 1 项):断言 portal 里必须存在这条带、用的是 --dsw-specific-sidebar-fill、高度等于 SCM_CHROME_TOP、pointerEvents: "none"、带 ref 且 makeResizeHandler 拿到了它(并用变异测试验证:去掉色带的背景后该断言确实失败)。
- 变更范围:
lib/client.js(刷新页面即生效)、tools/verify-close-button.test.mjs、README.md、README.en.md、package.json。版本号 1.24.2 → 1.24.3。
v1.24.2(历史)
修复:面板顶部那条灰带盖住了说明文字(v1.24.1 引入的回归):
- 现象:打开「代码管理」后,标题「源代码管理」下方压着一条灰色横带,把「按顺序完成:①环境检查 → …」那行说明文字盖住/挤掉。
- 根因:v1.24.1 为让
✕ 常驻,把标题行做成了 position: sticky 并给了它一层底色(--dsw-alias-bg-layer-1)。sticky 行是浮在内容之上的——它不会把后续内容推开,只是悬停在那里,于是底色正好压住从下面滚上来的文字;同时 padding 又让标题行多占高度,把说明文字整体下推。用「浮层 + 底色」去解决「常驻可见」,方向本身就错了。
- 修复(结构性分离,而不是再调颜色/层级):
drawer 下面板根节点改为 overflow: hidden(面板本身不滚动),标题行是普通静态行(flex: 0 0 auto,不带任何底色),只有它下面的 body 容器 overflow: auto 负责滚动。标题行在正常流里、不重叠内容,因此 ✕ 照样常驻可见,却不可能盖住任何文字——与原生侧边栏同一做法。
tab 变体保持整块滚动(滚动语义交给宿主 dsh-better-sidebar),不受影响。
- 回归测试(
tools/verify-close-button.test.mjs,8 项)相应重写:断言 ScmPanel 内不得再出现 position: "sticky"(断言范围限定在 ScmPanel——插件自己的 diff 查看器用 sticky 是正确的,不能误伤)、headStyle 不得带 background、drawer 根节点必须是 overflow: hidden、滚动 body 必须排在标题行之后、✕ 仍在且 tab 变体不渲染。
- 变更范围:
lib/client.js(刷新页面即生效)、tools/verify-close-button.test.mjs、README.md、README.en.md、package.json。版本号 1.24.1 → 1.24.2。
v1.24.1(历史)
修复:换成 DSH 官方桌面版后,右侧面板打开却关不掉:
- 现象:桌面版里点「代码管理」能打开右侧面板,但右上角没有可点的关闭按钮,面板也退不出去。
- 根因(不是按钮没渲染,是点不到):面板自带
✕ 一直都在(variant === "drawer" 时渲染),但桌面壳会把 --dsh-windows-titlebar-height: 40px 写到 documentElement 上,而窗口控件(─ □ ✕)是 OS 绘制的覆盖层——它永远画在网页之上,z-index 加到多大都没用。面板原本是 position: fixed; inset: 0,它自己的 ✕ 正好落在 y≈20..42、x≈右边 20..50 那一带,被原生 ✕ 完整压住:按钮在 DOM 里、也响应 hover,就是收不到点击。网页版没有这条 OS 带,所以旧版一直没暴露。
- 修复(四处,都写进回归测试):
- 面板整体下移:外层遮罩由
inset: 0 改为 top: var(--dsh-frame-chrome-top, var(--dsh-windows-titlebar-height, 0px)) + right/bottom/left: 0。优先用 DSH 自己发布的 --dsh-frame-chrome-top——它正是 DSH 用来「让模态遮罩不要画到 Windows 顶栏上」的量,原生全屏时自动归零,面板在全屏下仍能用满整屏;只发布旧变量的壳回退到 --dsh-windows-titlebar-height;普通浏览器两者皆无 → 0px,与改造前逐像素一致。
✕ 常驻可见:面板内容很长(①②③④⑤),普通滚动会把标题行连同 ✕ 一起带出视口,得一路滚回顶部才能关。注:本版最初用 position: sticky + 底色实现,方向是错的(sticky 是浮层,底色会盖住下面滚上来的文字)——已在 v1.24.2 改为「标题行固定 + 只有 body 滚动」的结构性分离。tab 变体保持原样——它由宿主 dsh-better-sidebar 负责关闭,本就不渲染 ✕。
- Esc 兜底:面板打开时挂
keydown 监听,按 Esc 关闭(关闭/卸载时摘除)。桌面壳里任何贴边的网页控件都可能被 OS 覆盖层压住,键盘是永远可用的退路。
- 入口按钮兼作开关:「代码管理」按钮由单向
setOpen(true) 改为 toggleOpen(再点一次收起,并加 aria-expanded)。它挂在 header 里、不在被窗口控件压住的那条带上,是最不容易失灵的关闭入口。
- 新增回归测试
tools/verify-close-button.test.mjs(7 项,v1.24.2 重写为 8 项):断言 SCM_CHROME_TOP 的变量链与回退顺序、遮罩必须用 top: SCM_CHROME_TOP、drawer 里不得再出现 inset: 0(断言范围限定在 HeaderScmAction 内——插件自己的居中弹窗用整屏 mask 是正确的,不能误伤)、标题行 sticky 且 ✕ 在滚动容器内、tab 变体不渲染 ✕、拖拽条 zIndex 必须高于当时的 sticky 标题行(标题行吸顶后会盖住拖拽条内侧 4px,造成顶部一段拖不动的盲区;已把拖拽条从 zIndex: 1 抬到 3,并用变异测试验证过该断言会失败)、Esc 监听的挂载与清理、入口按钮必须是 toggle 而非单向 open。
- 变更范围:
lib/client.js(刷新页面即生效,无需重启)、tools/verify-close-button.test.mjs(新增)、README.md、README.en.md、package.json。版本号 1.24.0 → 1.24.1。
v1.24.0(历史)
③ 加载提速:把「每个文件一次 git」改成批量调用(实测每次加载省 ~1.2s):
- 先回答「为什么开代理没变快」:push/pull/fetch 走的是 SSH(
git@github.com:...),而 OpenSSH 不使用 HTTP_PROXY/HTTPS_PROXY/git http.proxy——已实测(ssh -G 在设了这些变量后不产生任何代理指令)。代理只对 git-over-HTTPS 生效。所以对 SSH 远端,开系统代理不会改变速度;这是协议决定的,不是配置问题。要让 SSH 走代理必须显式配 ProxyCommand(需要 ncat/connect 之类的辅助程序,本机没有)。
- 实测瓶颈分布(本机真实仓库,单个工作区):
| 阶段 | 耗时 |
|---|
| 本地 git 调用(branch/status/ls-files/remote/rev-list) | ~330 ms |
其中「每个改动文件一次 git diff --numstat」 | ~1250 ms(30 个改动 → 30 次进程) |
git fetch(联网,仅 full 模式) | ~4000 ms |
| SSH 握手本身 | ~3200 ms |
根因:Windows 上每次 git 进程约 35ms,旧代码对「工作区副本不存在」的改动文件逐个跑 git diff --numstat -- <path>,是典型 N+1。 | |
- 修复:
- 新增
viewableMap():二进制判定批量做——先直接读文件头 8KB 嗅探(零进程),只有确实读不到的文件才汇总成至多一次 git diff --numstat(按暂存/未暂存分两批)。
providerRemoteInfo():用一次 git remote -v 同时拿到远程名与 URL,替换原先「git remote + 每个远程一次 get-url」以及 repoStatus 里又一次 get-url。
- 实测:1258 ms → 58 ms(30 个改动文件),加上远程查询共每次加载省约 1.24 s。
- 语义保持一致(有测试兜底):新增
tools/verify-viewable-batch.test.mjs(9 项),在真实临时仓库里同时跑「旧实现」与「新实现」,断言两者对每个文件给出完全相同的结论。这个对照测试当场抓出一个我引入的回归:已删除文件上 git diff --numstat 不输出任何行,旧逻辑判为「不可预览」(正确——没有工作区副本可看),我的批量版一开始误判为「可预览」。已修回与旧实现一致。
- 定位瓶颈与验证优化时用的是一次性计时脚本(对真实仓库逐项计时,上表数字即由此得出);脚本本身无断言、不参与回归,用完已删除,未随仓库保留。
- 结论/建议:本地加载约 0.1s 量级、已经很快;
fetch 那 4s 是网络与 SSH 握手成本,代理帮不上,只能靠「少 fetch」——插件已默认在快速模式不联网 fetch(用本地 tracking ref 算 ahead/behind),只有点「刷新状态」才联网。
- 顺带清理:删掉 4 个无断言的纯诊断脚本(
scan-keys / / 两个计时脚本)和 1 个在本机跑不起来的旧测试( 硬编码了 )。带断言的 与 i18n 工作流全部保留。
v1.23.0(历史)
SSH 端口改为用户可选(443 穿墙 / 22 标准):
- 需求:用户提出「有代理的话是不是不用配 443,可以让用户自己选」。核实后按「做成可选」实现。
- 先纠正一个前提(已实测):SSH 不使用
HTTP_PROXY / HTTPS_PROXY——用 ssh -G 验证过,设了这些环境变量后 OpenSSH 不会产生任何代理指令(要走代理必须配 ProxyCommand)。所以「有代理」≠「22 端口通」;443 的真正意义是绕过封锁 22 端口的网络。因此做成用户按网络情况自己选,而不是由插件按「有没有代理」去猜。
- host 端(
lib/index.js):
providerCfg(provider, port) 接受端口,并据此决定 Hostname——443 用 GitHub 的 ssh.github.com,22 用标准 github.com(Gitee 两种都用自身域名)。
- 新增
normalizePort():只接受 22 / 443,其余一律回退 443(避免脏输入写坏配置)。
writeSshConfig(provider, port):端口变了会就地改写 ~/.ssh/config 里对应的 Host 块(用新增的 hostBlockSpan() 定位行区间),不产生重复块、不影响其它 Host;端口未变则仍是幂等 no-op。
sshTest(provider, port):显式带 -p <port>(443 时直接指向 git@ssh.github.com),不再依赖配置文件先写好。
- 路由
/write-config、/ssh-test 透传端口;checkSsh() 新增 sshGitHubPort / sshGiteePort(从配置解析实际端口)。
- client 端(
lib/client.js):② 的「SSH 配置」行加端口下拉(443(穿墙) / 22(标准));状态文案显示实际配置的端口(不再写死 (443));首次读到已有配置时选择器跟随一次,之后以用户选择为准(不覆盖用户手动切换);「测试连接」按所选端口测,失败提示「若是网络封锁 22 端口,请改用 443」。
- 测试:新增
tools/verify-ssh-port.test.mjs(16 项:端口归一化、Host 块解析/端口读取、相邻块不误吞、github.com.evil 之类相似域名不误匹配、路由透传);新增 tools/verify-ssh-port-e2e.test.mjs(7 项:在临时目录里真实跑「写 443 → 幂等 → 切 22 就地改写 → 切回 443 → 保留其它 Host → 追加 Gitee 不干扰」,不触碰真实 ~/.ssh/config);新增 tools/verify-ssh-crlf.test.mjs(4 项)与 tools/verify-ssh-crlf-write.test.mjs(5 项);新增 tools/verify-en-dict.mjs(校验新 UI 文案的英文翻译已进内联词典,280 → 287)。
- 顺带修掉一个会被本机配置直接踩中的 bug(换行符):Windows 上
~/.ssh/config 常见是 CRLF 换行。原先的「就地改写」把文件按 \n 切开再按 \n 拼回,会让被改写的那个 Host 块退化成 LF,整个文件变成 CRLF/LF 混合(在 dotfiles 仓库里表现为整段 diff)。现已改为按文件自身的换行符切分与拼接(),新写入的块、追加的块、以及拼接缝都保持原样。这组 CRLF 用例是拿本机真实配置(确认就是 CRLF)验证出来的。
v1.22.4(历史)
空态按钮下移到与「有会话时」完全相同的高度:
- 症状:没有会话时,「代码管理」按钮比有会话时高了几个像素,切换会话时位置会跳。
- 根因:上一版把空态按钮的
top 对齐到了侧栏开关那一行(calc((var(--dsh-windows-titlebar-height, 40px) - 28px) / 2) = 6px),但「有会话时」的按钮挂在 header 的 utilities 行里,其高度由另一套几何决定:.header 的 padding-top: 10px + .titleRow 的 min-height: 30px(居中)⇒ 标题行 y=10..40、中心 25px;按钮高 32px ⇒ top = 10 + (30 - 32)/2 = 9px。两者差 3px。
- 修复:空态按钮改用 header 自身的几何表达
top = SCM_HEADER_PAD_TOP + (SCM_TITLE_ROW_H - SCM_BUTTON_H) / 2 = 9px(常量 10 / 30 / 32 分别对应 .header 的 padding-top、.titleRow 的 min-height、headerBtnStyle 的 height),桌面壳再叠加 --dsh-windows-titlebar-height(普通浏览器回退 0px,即精确 9px)。两种形态切换时按钮逐像素同位、不跳位。
- 回归测试把「纵向」断言整组换掉:新增「空态与有会话时同高(由 header 度量推导出同一个 9px)」、「按钮高度必须等于
headerBtnStyle 的 32px」、「叠加标题栏偏移」三项,并断言旧的 6px 公式不得残留(共 13 项);用变异测试验证过改回 6px 会被抓出。另抽出 stripComments() 助手,让「禁止残留」类断言只匹配代码、不被解释性注释误伤。
- 变更范围:
lib/client.js(刷新页面即生效)、README.md、package.json(版本号)。版本号 1.22.3 → 1.22.4。
本轮(v1.22.0 → v1.22.4)新增的回归测试文件(随包发布,不在 files 白名单内,仅供本地校验):
tools/verify-entry-placement.test.mjs —— 入口几何位置与槽位选择回归(13 项)。
tools/verify-entry-runtime.test.mjs —— 在模拟 cordis 运行时里真实执行 apply(ctx)、并用模拟 session 快照真实渲染每个注册项,验证两个分支与入口互斥(13 项)。
运行:node tools/verify-entry-placement.test.mjs / node tools/verify-entry-runtime.test.mjs
⚠️ 以上测试文件已在 v1.25.0 删除(用户要求清理插件目录),下面各历史版本里提到的测试文件同样已不存在;当时的结论保留作改动依据。
v1.22.3(历史)
空态入口让开右上角的「右侧栏展开按钮」(修掉横向重叠):
- 症状:上一版把空态常驻按钮设成
right: 12px 后,它与 DSH 空态 header 右上角已有的按钮叠在一起。
- 根因:那个按钮不是左侧栏开关,而是 ui-sidebar-right 的 ExpandButton(右侧栏展开按钮)。空态下
.headerBlank .headerCorner 带 margin-left: auto,把它顶到最右;按 DSH 的 CSS 算它的盒子:.header 是 padding-right: 28px、.headerCorner 是 margin-right: -16px、圆钮宽 28px ⇒ 它占据视口右侧 12px~40px。right: 12 正好把「代码管理」的右缘压在同一条线上。
- 修复:改为
right: 48px(常量 SCM_TOPRIGHT_OFFSET = 48)= 圆钮左缘 40px + 8px 间距(与 header 内其它控件的 8px 间距一致),整个按钮落在圆钮左侧、互不相交。纵向当时仍复用 DSH 自己的公式(该纵向写法已在 v1.22.4 被替换为「与有会话时同高」,见上)。
- 回归测试新增 3 项几何断言:右缘必须 ≥ 展开按钮左缘(
SCM_TOPRIGHT_OFFSET >= 40,并断言 right: 12 不得再出现)、偏移量恰为 40 + 8;并用变异测试验证过把偏移改回 12 会被抓出。(当时共 14 项;v1.22.4 重排纵向断言后为 13 项。)
- 变更范围:
lib/client.js(刷新页面即生效)、README.md。版本号 1.22.2 → 1.22.3。
v1.22.2(历史)
空态入口改回右上角、与 DSH 自带控件同一行:
- 按用户反馈调整位置:空态(无会话 / 新对话)时常驻按钮此前被钉在会话 header 下方(
top: 84px; right: 28px),用户要求改回右上角——即与 DSH 自带侧栏开关同一条水平线上。
- 用 DSH 自己的公式对齐,而不是写死像素:DSH 在 Windows 桌面壳下把侧栏开关钉成
position: fixed; top: calc((var(--dsh-windows-titlebar-height) - 28px) / 2); left: 12px(ui-sidebar 的 SidebarRoot.module.css,「新会话」按钮同一条公式、left: 48px)。本插件复用同一条公式(带 40px 回退值,取自 apps/desktop 的 WINDOWS_TITLEBAR_HEIGHT),所以标题栏高度变化时按钮自动跟着走、不会错位;普通浏览器下变量缺失时退化为 6px,与桌面壳完全一致。
- 横向不再遮挡:自带开关在左侧
left: 12px,插件按钮贴右侧 right: 12px,两者分居两端,不会叠在一起。
- 回归测试
verify-entry-placement.test.mjs 同步更新:新增「复用 DSH 的纵向公式」「公式与 DSH 逐字一致(只差回退值)」「浏览器回退与桌面壳同为 6px」「不再钉在 header 下方」四项断言(共 12 项)。
- 变更范围:
lib/client.js(刷新页面即生效)、README.md。版本号 1.22.1 → 1.22.2。
v1.22.1(历史)
修复「同时出现两个『代码管理』入口」+ 移除左边栏入口:
- 移除左边栏入口(按用户要求):不再注册进 DSH 自带侧边栏底部(
sidebar.footer.action)——那个入口会挤在左边栏「设置」上方。同时删掉只为此存在的跨 React 根订阅桥(openScmPanel()/scmOpenListeners)和 SidebarFooterScmAction 组件。入口现在只保留会话 header 里的两处。
- 修复「两个『代码管理』」根因:
sessions.list.getSnapshot() 返回的是 SessionListState,形状为 { ids, byId, phase, subagentsByParent, jobsBySession }——没有 current 字段。而空态常驻入口的显隐判断读的是 snap.current,永远拿到 undefined,于是 hasActiveSession 恒为 false:即使主视图正展示一个活跃会话,常驻按钮也照样渲染,与 header utilities 里的按钮同时出现两个。
- 修复方式:改用 DSH 自己的判定惯例
retainedBy.mainView > 0(ui-layout/DocumentTitle、ui-workspace/WorkspaceBrowser、ui-settings-general 等 5 处都这么写),新增 activeMainSession(snap) helper;并给「DSH 版本未提供 retainedBy」加了兜底(退回「非空白会话里 updatedAt 最新的一条」),拿不准时宁可认为有活跃会话,保证任何时候都不出现两个按钮。
- 两个入口改为严格互斥:header utilities 那个不再直接注册裸按钮,而是注册自门控的
HeaderScmEntry——只有主视图展示「非空白」会话时才渲染;常驻入口只在否则情况下渲染。两者由同一个信号(activeMainSession)决定,不再依赖 DSH 的 hideChrome 时机,从构造上不可能同时出现。
- 新增回归测试:
verify-entry-runtime.test.mjs 会用模拟的 session 快照真实渲染每个注册项,断言「非空白会话 / 空白会话 / 无会话 / 非主视图空白会话 / 缺 retainedBy」五种状态都恰好只显示一个 header 入口、且没有任何入口渲染在 header 之外(共 13 项);并用变异测试验证过这些断言确实能抓出该 bug。verify-entry-placement.test.mjs 增至 12 项(含「不得注册进左边栏」「恰好两个入口」)。
- 变更范围:
lib/client.js(刷新页面即生效)、README.md。版本号 1.22.0 → 1.22.1。
v1.22.0(历史)
修复「未安装 dsh-better-sidebar 时,『代码管理』按钮压住 DSH 自带侧边栏按钮」:
- 根因:空态常驻入口此前用
position: fixed; top: 8px; right: 12px(按钮高 32px → 纵向 y=8..40)。而 DSH 自带侧栏开关(ui-sidebar 的 HeaderLeadingControls,注册在 conversation.session.header.leading)位于会话 header 的 30px 标题行内——.header 是 padding: 10px 28px 0 20px,即自带按钮落在 y≈10..40;空态下 .headerCorner 还带 margin-left: auto,右侧同行同样被占。两条带直接相交,于是按钮叠在一起。
- 修复:空态常驻按钮从那条带里挪出来——改钉在会话 header 之下(
top: 84px; right: 28px,即 .header 的 76px 高度 + 8px、右缩进对齐 header 的 28px 内边距),与自带侧栏按钮在纵向和横向都不相交;有会话时的入口(Session 日志旁)本来就与自带按钮不在同一侧,保持不变。
- 刻意避开的槽位(避免与 DSH 互相顶掉或产生死按钮):
conversation.session.header.corner 是 single 槽且已被自带右侧栏开关(ui-sidebar-right 的 ExpandButton)占用;conversation.session.header.leading 是 single 槽、被自带侧栏开关占用;sidebar.panellist 的每一项 id 必须是真实主面板 id(会交给 layout.selectPanel),本插件没有主面板。
- 新增
tools/verify-entry-placement.test.mjs(几何 + 注册位回归)与 tools/verify-entry-runtime.test.mjs(在模拟 cordis 运行时里真实跑 apply(ctx),断言两个分支的注册结果)。
- 变更范围:
lib/client.js(刷新页面即生效)、README.md。host 端 /api 路由未改动。版本号 1.21.0 → 1.22.0。
- 注:本版曾同时新增「注册进 DSH 自带侧边栏底部」的入口,已在 v1.22.1 按用户要求移除。
v1.21.0(历史)
彻底移除 Gitee CLI + Gitee 私人令牌配置移到 ②:
- 背景:Gitee CLI 不是刚需——插件核心的 Gitee 功能(建仓 / push / clone / ②③)靠私人令牌(OpenAPI) 与 SSH 公钥驱动,与 CLI 二进制无关;且 Windows 上安装经常失败(无 npm.exe 的 ENOENT / npm.cmd 的 EINVAL 坑)报「未找到可用的包管理器」误导。
- host 端:删除
GITEE 常量、env.gitee 检测、installHints.gitee、checkSsh 的 giteeLoggedIn/giteeAccount、giteeUserLevelScript、checkGiteeUpdate、giteeLoginTerminal、/gitee/login 与 /gitee/import-token 路由、installCommandSystem/installCommand/installToolFlow/toolInstalled//check-update 里的 gitee 分支;installCommandSystem 对未知工具(含 gitee)直接返回 null,避免误落到 gh 的包名。
- client 端:① 环境检查固定检测 gh(不再按平台切 gitee CLI 行);② Gitee 私人令牌改在 ② 直接输入/保存/清除(
/gitee-token 存储,保存/清除后即时通知 ③④ 刷新);③ 只读显示令牌配置状态(未配置时提示去 ②),移除「从 Gitee CLI 导入」按钮。
- 保留不变:Gitee 平台本身、私人令牌存储(
~/.dsh/storages,0600)、建仓/push/clone、SSH 443 配置全部保留。
- 变更范围:
lib/index.js(重启 dsh web 生效)、lib/client.js(刷新即生效)。版本号 1.20.0 → 1.21.0。
v1.20.0(历史)
① 环境检查新增 Git 检查更新(仅 Windows)(复用 gh/gitee 的「检查更新」交互):
- 已安装的 Git 行显示「检查更新」按钮(仅 Windows 显示):host 对比本地
git --version 与 Git 官方最新稳定版本,有更新则 window.confirm 确认后一键升级(进度弹窗实时展示)。
- Git 官方版本来源不能用 GitHub Releases(
git/git 没有 release,/releases/latest 是 404),改用其 tags API 并过滤预发布/RC(v2.56.0-rc0 之类会被剔除,避免诱导更新到 RC)。
- 更新 = 系统包管理器「升级」命令:Windows
winget upgrade Git.Git;需要 root 时沿用 canSudo() 探测(Windows 走 winget 自处理提权)。
- Linux/macOS 不显示 Git 检查更新:这两个平台的 git 是发行版 / brew / Xcode CLT 管理的,版本与官方不同步(Linux 常停留在发行版固定版本),拿官方最新版对比会永远误报,且随系统更新走无需单独检查——host 端也加了防御,非 Windows 的 git 更新直接提示「Git 随系统更新」。
- SSH 不加更新(系统组件、无独立可升级版本,且没必要)。
- 新增
POST /check-update 的 tool=git 分支(返回 {ok, current, latest, hasUpdate})。
- 变更范围:
lib/index.js(刷新/重启一次生效)、lib/client.js(刷新即生效)。版本号 1.19.0 → 1.20.0。
v1.19.0(历史)
GitHub CLI 下载源选择扩展到 Windows(此前仅 Linux/macOS):
- Windows 也支持镜像下载源下拉:之前下载源选择只在 Linux/macOS 显示,Windows 走 winget 直连 GitHub Releases 导致「下载很慢 / 不走进度」。现在 ① 环境检查在 Windows 的 gh 缺失时同样显示「下载源」下拉(默认第一个镜像
gh-proxy.com)。
- Windows 镜像安装 = 下载官方 msi + msiexec 静默安装:选定镜像后,host 按「镜像前缀 + 官方 msi URL」下载
gh_<版本>_windows_<arch>.msi(实测约 5-6MB/s),再用 msiexec /qn 全静默安装——与 winget 效果一致(gh 注册进 Program Files 与 PATH),但避开了直连 GitHub 的慢速段。「一键安装」分步进度 /「复制安装命令」/「检查更新」均跟随所选镜像。msiexec 用 PowerShell -EncodedCommand(UTF-16LE base64)经 Start-Process -Verb RunAs -Wait 触发 UAC 提权安装(DSH 进程通常非提权)。
- 修复 Windows 下载报
curl: (3) URL rejected: Port number...:最初用 cmd /c 拼字符串执行 curl,Node spawn 在 Windows 会重排引号,把镜像 URL 的第二个 https:// 误判成「host:端口」导致 curl 拒绝。已改为参数数组直接调用 curl.exe(runLive(bin, args)),逐个传值,彻底绕开 shell 引号重排。
- 列举镜像:
gh-proxy.com(快,推荐)→ ghfast.top / ghproxy.net(可用,较慢)→ 官网(慢);与 Linux/macOS 共用同一份 GH_MIRRORS,仅下载资产从 .tar.gz 换成 .msi。
- 「下载源」下拉在 gh 已安装时也显示(三平台统一):此前只在 gh 缺失时渲染,导致已安装点「检查更新」时无法选镜像(更新走隐藏的
ghSource,若默认落官网则很慢)——Linux/macOS/Windows 都存在。现改为 gh 行常驻「下载源」下拉:未安装时影响一键安装,已安装时影响「检查更新」触发的覆盖安装;并修复默认镜像的竞态(改为独立 useEffect 监听 env,保证「已安装点更新」也不会竞态落到官网)。
- 回退策略:选「官网(慢)」或镜像版本解析失败时,Windows 回落系统包管理器(winget / choco / scoop),原有行为不变。
- 变更范围:
lib/index.js(刷新/重启一次生效)、lib/client.js(刷新即生效)。版本号 1.18.0 → 1.19.0。
v1.18.0(历史)
① 环境检查新增 CLI 检查更新(GitHub CLI / Gitee CLI):
- 已安装时显示「检查更新」按钮:host 对比本地版本与官方最新 release(gh 查 GitHub Releases API;gitee 查 Gitee OpenAPI 公开端点,无需令牌)。
- 有更新 → 询问 → 按安装逻辑更新:
window.confirm 展示「发现新版本:x → y,是否立即更新?」;确认后复用一键安装流程(/install-tool 带 force,跳过「已安装」短路)——gh 走用户级安装脚本 + 所选下载源(镜像),gitee 走官方安装脚本,进度弹窗实时展示,装完自动重新检测。
- 无更新提示「已是最新版本(x)」;错误(读不到版本/连不上官方)有明确提示。
- 点击「检查更新」后按钮变为「正在检查更新…」并禁用,检查完成自动恢复。
- 修复更新不生效:
/install-tool 路由最初未透传 force(更新确认后命中「已安装」短路,显示"已安装 执行成功"但版本不变),已修复——force 完整透传到安装流程,更新真正覆盖安装到最新版。
- 新增
POST /check-update 路由(body tool: gh/gitee → {ok, current, latest, hasUpdate})。
- 变更范围:
lib/index.js、lib/client.js。版本号 1.17.0 → 1.18.0。
v1.17.0(历史)
Gitee 平台改用官方 Gitee CLI 检测与登录(对齐 GitHub 的 gh 体验):
- ① 环境检查按平台自适应:切到 Gitee 后,工具行检测 Gitee CLI(
gitee,官方 oschina/gitee-cli) 而不是 GitHub CLI(gh);切回 GitHub 恢复检测 gh。Gitee CLI 未安装时可一键安装(Linux/macOS 走官方安装脚本免 sudo 装到 ~/.local/bin/gitee,gitee.com 国内直连无需镜像;Windows 走 npm install -g @gitee/gitee-cli),安装后点「重新检查」即可。
- ② Gitee 登录行用 CLI 状态:Gitee CLI 已装时显示
gitee auth status 结果——未登录提供「登录 Gitee」按钮(host 打开终端运行 gitee auth login,粘贴私人令牌即可)+「复制登录命令」+「重新检查」;CLI 未装时提示「请在 ① 环境检查安装 Gitee CLI」,并显示令牌兜底状态(Gitee API 仍需要私人令牌,可在 ③ 配置)。
- ③「从 Gitee CLI 导入」令牌:Gitee CLI 已登录时,③ 令牌配置块出现「从 Gitee CLI 导入」按钮——把 CLI 保存的令牌(
gitee auth token)一键导入插件存储(~/.dsh/storages),免去手动复制粘贴;「新建仓库并推送」等 OpenAPI 功能随即可用。
- 私人令牌仍是 Gitee API 认证方式(创建仓库等必需);SSH 公钥只管 push/pull 已有仓库。
- 变更范围:
lib/index.js、lib/client.js。版本号 1.16.0 → 1.17.0。
v1.16.0(历史)
GitHub CLI 下载提速 + gh 登录按钮 + 移除一键重启:
- GitHub CLI 下载源可选(官网 / 镜像):官网直连在国内网络经常「下载很慢 / 基本不走进度」。现 gh 未安装时,①环境检查会显示「下载源」下拉(默认第一个镜像):
gh-proxy.com(实测约 2-3.5MB/s、稳定)→ ghfast.top / ghproxy.net(可用但偏慢)→ 官网(慢)。下载 URL 由 host 按所选源构造(镜像 = 前缀 + 官方 Releases URL),「一键安装」与「复制安装命令」都跟随所选下载源。镜像列表由 /env 的 ghMirrors 下发,后续增删镜像无需改浏览器端。
- 安装完成提示改为「刷新网页即可生效」:删除「需要重启 DSH 生效」提示与「重启 DSH」按钮;成功提示统一为「安装完成…,刷新网页即可生效」。host 端删除整套一键重启机制(
/restart 路由、restartLaunch / scheduleRestart / restartNeededFor 等)。
- 缺少环境时也显示「重新检查」按钮:之前只有全部就绪才显示;现在有缺失时提示行旁同样提供「重新检查」,手动安装 / 刷新后无需重启即可重新检测。
- gh 未登录时可一键登录:② SSH 密钥与连接的「GH 登录」行在未登录时显示「登录 GitHub」按钮(host 打开终端运行
gh auth login,KDE konsole 独立窗口、找不到终端给出手动命令指引)+「复制登录命令」+「重新检查」;登录完成后点「重新检查」即可看到账号。
- 变更范围:
lib/client.js(刷新即生效)、lib/index.js(刷新/重启一次生效)。版本号 1.15.1 → 1.16.0。
v1.15.1(历史)
⑤ 发布 npm 包功能修复(v1.15.0 的补丁):
- 修复「打开终端执行 npm login」打崩 host:旧实现
spawn 不存在的终端二进制(如 SteamOS 上没有 x-terminal-emulator)时,异步 ENOENT 错误无监听器被抛成未捕获异常,直接把 dsh host 进程打崩(表现:运行 pnpm dsh web 的终端跟着结束)。现改为先 findTerminal() 探测 PATH 中真实存在的终端(konsole / gnome-terminal / xterm 系等),并对每个 child 挂 error 监听器兜底——绝不再崩宿主。
- KDE konsole 用
--separate 开独立窗口:不与宿主所在的 konsole 实例纠缠;找不到任何终端时明确返回「请手动运行:<命令>」指引,不再假装打开成功。
- npm 登录/发布固定走官方源:
npm login / npm whoami / npm publish 统一带 --registry=https://registry.npmjs.org(镜像源只同步、不接受发布);npmStatus 新增 publishRegistry / mirrorConfigured,面板在检测到镜像配置(如 npmmirror)时黄色提示「发布将使用官方源」;「复制登录命令」也同步为官方源版本。
- 变更范围:
lib/index.js(需重启 dsh web)、lib/client.js(刷新即生效)。版本号 1.15.0 → 1.15.1。
v1.15.0(历史)
一键安装全面升级:平台自适应 + 尽量免 sudo + 实时进度 + 一键重启。
- 修复「复制安装命令」在 Linux/macOS 上错误显示 winget:客户端写死的 Windows 提示改为取 host
/env 返回的 installHints(与「安装」按钮同一套平台自适应逻辑);非 Windows 且无可用方式时留空,不再误导。
- GitHub CLI(Linux/macOS)改免 sudo 用户级安装:
installCommand() 对 gh 优先返回官方二进制下载脚本(查最新版本 → 按平台/架构下载 gh_<ver>_linux_amd64 / macOS_arm64 等 → 解压 → 装到 ~/.local/bin/gh),一键安装失败自动回退系统包管理器。resolveBin 新增 ~/.local/bin 探测——插件加载时即使 PATH 里没有该目录也能找到用户级 gh(不再依赖改 PATH / 设环境变量)。
- sudo 免密探测(Linux):
canSudo() 用 sudo -n true 探测;需要密码时不再盲目执行(-n 必然失败),返回「请在终端手动执行:<命令>」指引 + 原因,配合复制按钮即可完成。
- macOS 兜底:git/ssh 无 brew 时走 Xcode 命令行工具(
xcode-select --install,系统自带安装、含 git/ssh),弹窗以「⏳ 已触发,按系统提示完成后点重新检查」呈现。
- 安装后实测复查:
toolInstalled() 改为按当前 PATH/落点直接探测(git/ssh 走 --version、gh 走 ~/.local/bin 优先),不再依赖模块加载时缓存的历史解析结果——装完立即识别,修掉「装好了还报失败」的隐患。
- 安装进度弹窗:
/install-tool 改为 NDJSON 事件流(step/out/result,runLive() spawn 流式转发),浏览器端 jpostStream() 逐行消费;弹窗逐步显示「▶ 查询最新版本 → 下载 → 解压 → 安装 → 清理」与实时输出尾部。
- 需要重启时提示 + 一键重启:
restartNeededFor() 智能判定(DSH_SCM_GH 显式指定或 ~/.local/bin 在 PATH 时无需重启);需要时弹窗显示「重启 DSH」按钮——新增 POST /restart 路由(机制同 dsh-update:detached helper 等端口释放后按原启动命令拉起新进程,POSIX detached spawn / Windows PowerShell 隐藏窗口;严格 loopback + origin 同源校验;systemd supervisor 托管或 DSH_SCM_RESTART=0 时禁用),点击后页面短暂断开、新进程自动起来。
- 变更范围:
lib/index.js(需重启 dsh web)、lib/client.js(刷新即生效)。版本号 1.14.0 → 1.15.0。
v1.14.0(历史)
按你的要求移除了 ⑤「提交 PR」功能:
- 前端:
lib/client.js 删除整个 PrSection(含 ⑤ 区块的渲染与面板导语中的「⑤提交 PR」文案),一并清理了对应的 i18n 键(EN_DICT)。
- host:
lib/index.js 删除整套 PR 实现——/pr/targets、/pr/rule、/pr/analyze、/pr/generate、/pr/execute 五个路由,prAnalyzeFlow / prGenerateFlow / prExecuteFlow,PR 目标仓库本地存储(source-code-mgmt-pr-targets.json)、PR 规则缓存读写(rules/ 下的 readPrRule/writePrRule 等)、parseRepoUrl、fillPrTemplate、安全校验(safeEntryPath / safeRegenerateCommand)、PRESET_PR_RULES 与启动时的规则预置写入。
- 保留
llmComplete() 这一通用 LLM 调用助手(与 PR 无关,属可复用能力,本轮暂未用到可直接忽略)。
- 版本号 1.13.0 → 1.14.0;
package.json 描述同步去掉「AI-assisted Pull Requests」。克隆板块的「按地址克隆」「本地已有判定」与 ⑥ npm(默认空目录)等仍保持 v1.13.0 行为不变。
- 变更范围:
lib/client.js(刷新即生效)、lib/index.js(需重启 dsh web)。
说明:rules/ 目录下旧预置文件 rules/awesome-dsh-plugin__awesome-dsh-plugin.json 已不再被任何代码引用,可自行删除或保留(不影响功能)。
v1.13.0(历史)
按第二轮反馈继续精化(④⑤⑥ 三处交互收敛):
- ④「本地已有」判定扩大到已登记的目录:此前克隆列表只在「当前选中的克隆目录」下判定本地是否已有,用户若把克隆目录切到别处(如工作区
workspace),明明已存在的仓库仍标「可克隆」。现在新增 localRepoExistsAnywhere():除了当前选中目录,还会遍历 ③ 代码管理登记过的所有工作区 / 自定义目录(~/.dsh/storages/workspace.json + source-code-mgmt-dirs.json)逐一判定——任一被登记的位置已有同名仓库即标「本地已有」;克隆成功后目标目录本就会记入自定义目录,因此「某位置已有该仓库」的知识会自动沉淀下来,下次列表直接命中。
- ④ 新增「按仓库地址克隆到任意位置」:克隆板块底部新增一行——粘贴任意 git 仓库地址(HTTPS 或 SSH,GitHub / Gitee / 自建均可)+ 选择目标目录,自动从 URL 末段推导仓库名(去掉
.git)并 POST /clone/run 克隆到所选位置;复用「选择目录…」按钮(可输入绝对路径或弹系统文件夹选择器,确认后记住到自定义目录)。目标目录默认沿用顶部下拉,未选择时给出提示。
- ⑥ 发布 npm:目标目录默认留空:此前默认回填默认工作区,用户不选就可能发布到非预期目录。现在目标目录默认留空,界面提示「当前留空,不会自动使用默认目录」;未选目录时「重新检查 / 登录 / 执行」按钮全部禁用并拦截,用户必须手动选择(同 ③/④ 的「选择目录…」按钮)。
- ⑤ 收敛为「提交 awesome-dsh-plugin 插件收录 PR」:按你要求先只保留「awesome-dsh-plugin 插件收录 PR」这最常用的一条路——隐藏「目标仓库地址」输入框、隐藏「记录到本地 / 已记录」行与标签、隐藏「分析 PR 规则 / 重新分析(忽略缓存)」按钮与手填规则面板、隐藏通用 PR 流程兜底框;目标仓库锁定为
https://github.com/awesome-dsh-plugin/awesome-dsh-plugin,挂载时只读加载一次其预置收录规则(不消耗 AI)并展示加载状态。保留下部:插件信息(名称/分类/描述 en/zh)→「生成 PR 内容(AI)」(命中预置模板可免 AI 填充、仍可编辑)→ 可编辑的标题/描述/条目文件内容 + 工作目录 →「执行 PR」(逐步日志 + PR 链接)。host 端 /pr/analyze、/pr/targets 等路由与预置规则文件保持不变,仅前端做了收敛。
v1.12.0(历史)
本次更新(新增④克隆 / ⑤提交 PR / ⑥发布 npm 三大板块):
- ④ 克隆仓库:列出已登录账号(GitHub
gh repo list / Gitee OpenAPI user/repos)名下所有远程仓库并标记本地是否已有;默认目标目录 = 用户主目录(home),不选即克隆到 home;每一行目录都有与 ③ 代码管理一致的「选择目录…」按钮(输入绝对路径或系统文件夹选择器,确认后记住到自定义目录);克隆走 SSH URL,成功后自动加入自定义目录列表。host 新增 GET /clone/default-dir、GET /clone/home、GET /clone/repos、POST /clone/run;本地已存在判定同时覆盖「目录名 == 仓库名且含 .git」的场景
- ⑤ 提交 PR:目标仓库网址记录到
~/.dsh/storages/source-code-mgmt-pr-targets.json(不写入插件目录);输入网址即自动读取该仓库的本地规则(GET /pr/rule,只读缓存、不消耗 AI):命中预置/缓存规则就显示对应专属 PR 功能,未命中则显示通用流程并提示「分析 PR 规则」;预置 rules/awesome-dsh-plugin__awesome-dsh-plugin.json——输入 https://github.com/awesome-dsh-plugin/awesome-dsh-plugin 直接给出它的收录规则(fork → clone → 在 data/plugins/<owner>__<repo>.yml 写条目 → npm ci && node scripts/generate-readme.mjs 重生成 README → commit → push → PR,规则写在插件目录);「分析 PR 规则」抓取目标仓库 README/CONTRIBUTING/PR 模板/package.json 交给 DSH 默认模型思考该仓库应如何 PR,输出结构化步骤规则并缓存到插件目录 rules/<owner>__<repo>.json(下次直接执行、不再消耗 AI;「重新分析(忽略缓存)」可强制重想);「生成 PR 内容」按规则模板产出标题/描述/条目文件内容(带预置模板时可免 AI 直接填充、仍可编辑),生成后可改、绝不直接提交;「执行 PR」按 fork→clone→加 upstream→fetch→建分支→写条目→重生成 README→commit→push→gh pr create(Gitee 走 OpenAPI) 逐步执行并显示日志与 PR 链接。host 新增 GET /pr/targets、POST /pr/targets、POST /pr/rule、POST /pr/analyze、POST /pr/generate、POST /pr/execute
- ⑥ 发布 npm 包:五步向导(
npm config get registry → npm config list(脱敏 token/auth/password)→ npm whoami → npm pack --dry-run → npm publish),发布前需勾选确认;可切换包目录(同样带「选择目录…」按钮);未登录/未配置时明确提示:显示当前 registry / whoami,未登录给出「打开终端执行 npm login」(host 弹系统终端、进入目标目录执行,POST /npm/login)与「复制登录命令」,登完回面板点「重新检查」;GET /npm/status 会报告目录是否存在、是否含 package.json 并给出对应警告。host 新增 GET /npm/status、POST /npm/step、POST /npm/login
- host 端 AI 能力:新增
llmComplete()——通过 ctx.get('llm') 调 DSH 的 LLM 运行时,默认模型取 ctx.get('settings').get('agent-default-model')(兜底解析 ~/.dsh/settings.yaml),消息格式适配 GenerateOptions(text-delta 组装)
- 维护性:新增 i18n 键通过 并入 / ,不改动原超长字典行;新增 共享输入框样式; 双端通过
v1.11.0(历史)
本次更新(改动文件查看体验 + 中文文件名兼容性):
- 新增/untracked 文件现在可以查看内容:改动列表里「新增」文件行也显示「▸ 查看」,点击展开显示完整文件内容(按「全部新增」的 diff 渲染,超大文件只显示前 2000 行并附截断提示,上限 1MB 不会整读进内存);空的新文件显示「(空文件)」。
- 新增文件不再显示「旧版本」列:并排 diff 在没有任何删除行(纯新增,如新增/untracked 文件)时只显示「新版本」一列,不再出现空白的「旧版本」表头与左列;有增有删的修改仍保留双列对比。
- 二进制文件不显示「查看」:每个改动文件按内容嗅探(NUL 字节启发式,与 git 一致)判断是否可文本预览——二进制文件(图片、exe 等)不显示「查看」按钮(悬停提示「二进制文件,无法查看文本内容」);已删除/暂存后无副本的文件用
git diff --numstat(二进制条目 -\t-)判断。
- 中文文件名兼容性修复:git 默认
core.quotepath 会把中文路径输出成八进制转义的引号串(如 "\346\270\270…md"),导致改动列表与 diff 头部的中文文件名显示成乱码。现在所有 git 调用统一注入 -c core.quotepath=false(直接输出 UTF-8 路径),并新增 parseGitPath() 反转义兜底——改动列表、ls-files -z、diff --name-only、git diff/git show 头部路径等解析点全覆盖。
- 新增
tools/verify-cn-paths.test.mjs 回归测试(中文路径解析、文本/二进制可查看性、新增文件 diff 生成、已删除文件判断)。(该文件硬编码了 C:\Users\Administrator\workspace,在本机跑不起来,已于 v1.24.0 删除;相关覆盖由 tools/verify-viewable-batch.test.mjs 承担。)
v1.10.1(历史)
本次更新(修复 better-sidebar 集成与「未安装 better-sidebar」时入口的若干问题):
- 修复:装了 dsh-better-sidebar 后,右上角「代码管理」按钮仍残留。根因:未装 better-sidebar 时的降级入口此前只给 ReactDOM 降级路径赋了
entryUnmount,slots 路径漏了——导致切到「侧边栏 Tab」形态时 header 入口没被拆掉。现已把 slots.inject 返回的 disposer 记进 entryUnmount,切到 Tab 时正确拆除(顺带修正同类清理逻辑)。
- 修复:未安装 better-sidebar 时,入口只在对话内显示,新对话/无会话空态不显示。根因:旧入口挂
conversation.session.header.utilities(scope: 'session'),而空态时整个会话 header 被 hideChrome 隐藏,按钮出不来。现在改为右上角常驻——有活跃会话时放在「Session 日志」旁(会话 header 的右对齐列表),新对话/无会话空态时改为 shell.overlay 常驻槽里 position: fixed 钉在右上角的按钮(仅空白/无会话时显示,避免与 header 按钮重复)。
- 修复:刷新瞬间「代码管理」按钮与「Session 日志」重叠。根因:
captureSessions() 延迟 1.2s 才捕获到 session 列表,捕获前误判为「无会话」而提前点亮常驻按钮。现在未捕获到 session 列表前不渲染常驻按钮,杜绝刷新闪叠。
- 判定信号修正:常驻按钮的显隐改用「当前会话是否空白」判断(
sessions.list.getSnapshot().byId[current].blank)——活跃(非空白)会话才隐藏常驻按钮(交给 header 内的按钮),空白/无会话则显示,替代原来不准确的「current === undefined」判断。
- 健壮性:better-sidebar 服务的探测由「一次性 1.5s 重试」改为有界多档重试(快节奏起步再放慢,覆盖约 44s,拿到即停、卸载即清),兜底 better-sidebar 客户端冷启动较慢导致漏切 Tab 的竞态。
以上均为客户端(lib/client.js)改动,刷新页面即生效;host 端 /api 路由与推送/忽略逻辑未改动。
v1.10.0(历史)
本次更新:
- 界面中英文实时切换(跟随 DSH 语言设置):面板全部文案(①②③ 三步、按钮、弹窗、状态/结果消息、确认框、Tab 标题)与 host 端错误/结果消息改为双语词典驱动——语言 = DSH 设置 → 通用 → 语言,切换即时生效(面板经
ctx.locale 订阅实时重渲染,Tab 标题随动),无需刷新/重启;host 端按请求的 ?lang= 经 AsyncLocalStorage 按请求返回对应语言,并发不串扰。中文界面与 v1.9.0 完全一致,英文界面为完整翻译(含 >100MB 忽略原因、Gitee 令牌提示、git 命令失败回退等全部消息)。新增 tools/ 下的 i18n 提取/应用/测试脚本便于后续维护
v1.9.0(历史)
本次更新:
- 改为 Profile Bundle 分发,安装即激活:
package.json 的 dsh.bundle 从裸字符串 "./lib/index.js" 改为对象形态 { "patch": "./cordis.patch.yml" },并新增 cordis.patch.yml(insert source-code-mgmt 行)。现在 dsh plugin --profile web add source-code-mgmt 一步装完即被自动加入 dsh.profile.bundles 并注册进 Cordis loader 树,不再需要手动编辑 cordis.patch.yml 激活;README 中「安装 ≠ 激活」说明与 PowerShell 激活脚本已删除。功能行为零变化(仍是同一份 lib/index.js host 端 + lib/client.js 浏览器端)
v1.8.0(历史)
本次更新:
- 新增「推送暂存」按钮(③面板「同步」行):当本地领先有提交或有已暂存的改动时,「同步」行「本地领先 N 提交」后面出现「推送暂存」按钮——点击后只把已暂存的内容用你填写的提交信息(没填则自动生成
chore: update <文件夹名>)提交,然后推送到远程;不会像「推送更改」那样自动暂存所有未暂存的改动。推送成功后自动刷新状态并显示「⟳ 刷新中…」,界面显示本次提交的信息与 hash。host 端新增 POST /push-staged 路由(pushStagedFlow,沿用本插件 run()/GIT_SSH)
v1.7.0(历史)
本次更新:
- 数据加载时机重做:打开时联网、显示「刷新中」:此前打开 DSH 会预取所有工作区的仓库状态并缓存,导致打开/重开/切换面板时命中旧缓存,显示可能过期的数据(如旧的「无改动」)。现在改为——打开 DSH 时不联网同步仓库(只预取静态的 env/SSH/工作区列表);打开插件、切换工作区、自定义目录点击、刷新状态、以及推送/拉取/暂存/取消暂存/提交/对齐/初始化等操作后,一律联网获取对应工作区的最新状态,并显示「⟳ 刷新中…」提示(刷新期间按钮变「刷新中…」、状态区显示同步中)。关闭面板再打开也会重新同步,不再停留在旧状态。删除了预取所有工作区的
syncAllRepos 与「命中缓存秒显」分支
v1.6.0(历史)
本次更新:
- 未安装 dsh-better-sidebar 时的入口改为「右上角 Session log 旁 + 右侧集成面板」:原「右上角浮动按钮 + 右侧抽屉」改为——「代码管理」按钮经 DSH 的
conversation.session.header.utilities 槽位注册,出现在右上角 Session log 旁边的右对齐列表里(与 Session log 同款胶囊样式、间距 8px 不挤在一起);点击后在右侧展开一个 dsh-better-sidebar 外观的集成面板(复用原话术:环境检查 / SSH / 代码管理三步),并把主内容区 #root 往左推挤(margin-right + width calc)。slots 服务不可用时降级为右上角浮动按钮 + 右侧面板。改动仅限 lib/client.js(浏览器端),刷新页面即生效,host 端 /api 路由未动
- 已安装 dsh-better-sidebar 的形态不变:仍注册为它侧边栏的「代码管理」Tab;安装/未安装两形态依旧自动检测切换
v1.5.0(历史)
本次更新:
- ① 环境检查缺工具一键安装:某个工具(git / gh / ssh)未检测到时,该行显示「❌ 未安装」+「复制安装命令」+「安装」按钮——「安装」由 host 自动选包管理器执行(Windows winget / 内置功能、macOS brew、Linux apt/dnf/pacman),安装后自动重新检测;host 新增
POST /install-tool 路由(best-effort,回传执行命令与输出)
- 修复切换平台后 ③ 未刷新:仓库状态缓存按目录 + 平台区分,② 切换 GitHub / Gitee 后 ③ 会重新拉取对应平台的检测内容,不再残留上一个平台的「远程/同步/同名校验」结果;拉取期间显示「⟳ 切换平台,正在重新检测…」提示,避免旧内容停留几秒让用户误以为没变化
- macOS / Linux 适配修复:Linux 缺工具一键安装自动带
sudo -n(已是 root 则省略,避免权限失败挂起);decodeSessionDir 按平台区分 Windows 盘符路径与 POSIX 绝对路径,macOS / Linux 下也能正确从 session 目录还原工作区
v1.4.0(历史)
本次更新:
- SSH 密钥自动探测:扫描
~/.ssh/*.pub 中的 ed25519 公钥——优先用 id_ed25519,否则用找到的第一个(支持任意命名密钥);都没有才用默认名 id_ed25519 新建。② 状态行显示实际检测到的密钥名,SSH config 的 IdentityFile 也用它,有自定义命名密钥也能正确识别、配置并推送
v1.3.0(历史)
本次更新(补齐本地 Git 工作流,未改动远程同步逻辑):
-
② SSH 默认折叠 + 标题行切平台:② SSH 部分默认折叠,标题行内嵌「平台」下拉(折叠时也能切),切换后自动展开该部分以继续配置,③ 跟随平台
-
选择性暂存 + 自定义提交信息:「改动」弹窗每个文件行新增暂存 / 取消暂存按钮(按 git status --porcelain 的 XY 状态区分 staged/unstaged),并显示「已暂存 / 未暂存」;③ 面板仓库名上方新增提交信息输入框 + 「提交」按钮,提交不再用固定 chore: update workspace via DSH...(留空则自动生成基于文件夹名的信息);沿用 pushFlow 的无身份兜底逻辑
-
分支切换 + 提交历史 + revert / cherry-pick:「分支」行旁新增「切换」(弹窗列出分支,点选 checkout)与「历史」(弹窗列出提交,每条可「查看」「revert」「cherry-pick」——后两者带确认框)
-
真·并排 diff:把 unified diff 解析成(旧行/新行)配对,左右两栏渲染(删除行红底、新增行绿底);改动文件展开与历史点某提交(/commit-diff)都用并排视图;保留纯文本 renderDiff 作降级(二进制/无法解析时)
-
「查看改动」秒出:加载仓库状态时后台并发预取所有改动文件的 diff(并发≤3、纯本地 git 读取)并缓存,点开文件行立即显示,不再等「加载中…」
-
提交历史右键菜单:「历史」弹窗每条提交可点**「⋯」或右键**打开菜单——查看提交差异、复制短哈希、复制完整哈希、复制提交信息、还原此提交(revert)、拾取此提交(cherry-pick,后两者带确认框)
-
本地 Git 功能按 better-sidebar 自适应隐藏:已安装并激活 dsh-better-sidebar 时,③ 面板隐藏本插件自带的本地 Git 工作流(暂存/取消暂存、自定义提交、分支「切换」、提交「历史」+ revert/cherry-pick、并排 diff),因为这些能力 better-sidebar 的 Git 面板已覆盖,避免重复;此情况下「查看改动」弹窗只列改动文件,并提示具体改动内容请到 dsh-better-sidebar 的「源代码管理 / Git 面板」查看;未安装时则展示完整本地 Git 功能
-
host 端新增 /stage /unstage /commit /branches /checkout /log /revert /cherrypick /commit-diff 路由(全部走 isLoopbackRequest 校验 + run()/GIT_SSH)
v1.2.0(历史)
本次更新:
- 入口自适应(不再占用左栏底部按钮):「代码管理」的入口改为自动检测——已安装 dsh-better-sidebar 时,通过其
ctx.betterSidebar.registerTab 把「代码管理」注册为该侧边栏的一个新 Tab 页面;未安装时,在 DSH 页面右上角显示一个浮动按钮,点击展开右侧栏抽屉(形态类似 dsh-better-sidebar 的右栏),内容为同一面板
- 检测零开销:激活时仅一次内存读取(
ctx.get('betterSidebar')),零 I/O、零网络,毫秒级,不影响 DSH 启动;并对激活顺序做了兜底延迟重试,保证最终落在正确的形态
- 移除原左栏底部按钮:不再通过
sidebar.footer.action 插槽注册触发按钮;ScmPanel 支持 variant(tab / drawer)以分别适配侧边栏 Tab 与右侧抽屉布局,面板 UI 与 host 端 /api 路由完全复用、未改动
- 可见性默认值更聪明:「私有 / 公开」下拉默认选当前仓库的实际可见性(已存在且可读时);当所选工作区没有远程(即将新建的仓库)时,默认选公开,而不是固定「私有」
- 每个部分可折叠:①环境检查 / ②SSH / ③代码管理 标题行右上角各加一个折叠按钮,点击收起只显示标题、再点展开;①环境检查在所有工具都就绪(git/gh/ssh 均已安装)时默认折叠并显示「✅ 均存在」提示
v1.1.2(历史)
本次更新:
- 改动详情可展开查看内容 diff:「改动」查看弹窗里,对已跟踪的改动(修改/删除/新增)点击文件行即可展开查看该文件的 diff——删除行红色带
-、新增行绿色带 +、上下文灰色;untracked 新文件无 diff 只列名称(后端对每个改动文件追加 git diff/git diff --cached 内容)
v1.1.1(历史)
本次更新:
- 远程按平台切换:切到 Gitee 时「远程 / 同步」不再读取 GitHub 的 origin,而是只匹配 gitee.com 的远程(GitHub 平台仍读 github.com 远程)——解决「切到 Gitee 却仍显示
git@github.com:...」的问题;本地没有对应平台远程时显示「(无)」且不计算同步
- 远程必须属于当前账号:此前只按
github.com/gitee.com 域名匹配远程,导致 deepseek-ai/deepseek-harness 这类别人的远程被当成用户自己的远程显示、还能点「推送更改」。现在校验远程 owner 必须等于当前登录账号(GitHub=gh 账号、Gitee=令牌账号),不属于自己的远程一律不算,界面回到「同名仓库检测 + 新建仓库」形态;同时无远程时「同步」行显示「(无)」,不再误显示「与远程一致」
- 「拉取更新」改用强制对齐的实现并直接执行:「本地干净 + 远程有更新」状态的「拉取更新」按钮不再走
git pull --ff-only,而是复用「强制对齐」的 git fetch + git reset --hard origin/<branch> 逻辑——本地干净没有可丢失的改动,reset 等同快速前进到远程最新,且不弹确认框直接执行,结果更可靠(可绕过 rebase/merge 常见失败场景)
- 操作按钮只保留当前状态对应的唯一动作:只有本地有改动(远程一致)→ 只显示「推送更改」;只有远程有更新(本地干净)→ 只显示「拉取更新」;「强制对齐」仅在「本地和远程都有更新」状态出现(该状态的唯一操作按钮),不再在推送/拉取/已同步状态里当作兜底显示
- 「本地有改动 + 远程有更新」状态只保留「强制对齐」:此前该状态显示「拉取更新并推送更改 / 强制推送 / 强制拉取」三按钮,但这些操作容易因未提交改动、分支保护等失败且行为难预测(表现为点了没反应、不显示结果),现改为只显示提示 + 「强制对齐」按钮(
git fetch + git reset --hard,本地完全重置为远程),与用户实际想要的「一键对齐」一致
- 修复「点了按钮没有任何反馈」:操作回调先
setErr 再刷新仓库状态,随后 loadRepo 把错误提示无条件清空,导致失败时结果被立即抹掉、界面像什么都没发生;现在错误/结果会保留显示(切换目录 / 切换平台 / 手动刷新状态仍会清空旧提示),对齐失败等也能看到真实原因
v1.1.0(历史)
本次更新:
- ② SSH 新增平台选择:在「② SSH 密钥与连接」顶部增加下拉选择代码托管平台 GitHub(默认) / Gitee
- 「配置 SSH(config)」按所选平台写入相应 443 端口配置(GitHub:
github.com → ssh.github.com:443;Gitee:gitee.com 443)
- 「测试连接」按所选平台执行
ssh -T git@github.com / ssh -T git@gitee.com
- 「SSH 配置」状态行按所选平台分别显示 github / gitee 的配置情况,公钥上传提示文案也随平台切换
- ③ 代码管理完整适配 Gitee:③ 全部跟随 ② 的平台选择
- Gitee 模式走 Gitee OpenAPI(REST API + 私人令牌,
curl 调用),令牌在 ③ 输入并保存到本机 ~/.dsh/storages/source-code-mgmt-gitee.json(0600),可清除
- 同名仓库检测:Gitee
GET /repos/{owner}/{name};可见性读取/修改:Gitee GET/PATCH /repos/{owner}/{name}
- 新建仓库并推送:Gitee
POST /user/repos 建仓 + SSH 远程 git@gitee.com:<owner>/<name>.git 推送
- 仓库状态行显示当前平台来源;推送/拉取/强制对齐等 git 操作平台无关照常可用
v1.0.1(历史)
本次更新:
- 「改动」详情查看:「改动 N 个文件」旁新增「查看」按钮,点击弹出窗口列出改动的文件/新增的文件/文件夹名称(含状态标签:新增/修改/删除/重命名),只显示名称不显示具体内容;无改动时不显示该按钮
- 「同步」详情查看:「同步」行在本地与远程存在差异时新增「查看」按钮,点击弹出窗口显示本地领先/落后的具体提交列表;与远程一致时无差异可看,不显示按钮
- 修复:点「查看」不再导致插件崩溃(补全弹窗挂载容器)
- 修复:推送/拉取/新建仓库等操作后,操作结果(如「已推送 已提交 …」)能正常显示并保留,不再被刷新逻辑立即清空
- 修复:切换工作区 / 选择新目录 / 手动刷新状态时,会清空上一个工作区的操作结果,避免残留
- 优化:操作后的自动刷新不再闪断信息栏(目录/分支/改动/同步等字段保持显示,仅后台更新数据)
- 操作系统显示统一为美化名
Windows / macOS / Linux(对应底层 win32 / darwin / linux)
v1.0.0(历史)
首发版本,包含以下功能与修复:
- 跨平台二进制自动探测:
git / gh / ssh / ssh-keygen 按 ①环境变量覆盖 → ②PATH 查找 → ③(仅 Windows)Git 自带目录回退 自动定位,换设备无需额外配置,即使 ssh 不在 PATH 也能工作
- 环境检查:显示操作系统、Git、GitHub CLI、SSH 是否可用
- SSH:密钥生成 / 写入 github.com 443 配置 / 测试连接 / 公钥展示 / gh 登录检测
- 代码管理:工作区选择、动态操作按钮(推送 / 拉取更新 / 合并推送 / 强制推送 / 强制拉取 / 新建仓库并推送)
- 修改已有仓库可见性(私有 ↔ 公开)
- 新建仓库并推送:对无提交的全新目录自动生成初始提交,避免
gh 报 "no commits found"
-
100MB 文件自动忽略(整目录或单文件)并显示未上传原因
- 所有 API 均为 loopback-only,仅本机浏览器可访问
License
MIT