Repository navigation
Releases
dsh-restart 1.0.2 — 重启判据改为按新实例判定
dsh-restart 1.0.2
修正「重启成功」判据:不再等旧实例的端口,改为按这次拉起的新实例判定。升级前你会看到
listening=False 而误以为重启失败——其实每次都成功了。
本版发布后追加过一次修正(版本号不变)。 第一版把「端口枚举非空」当成成功条件,
真机实测证明那个条件本身不可靠——重启成功也会读到空端口。现在判定改成看 Host 进程是否
存活,端口只作附加信息。下面「本次更新」一节已按最终行为重写;新增的
二之二节 记录了这次翻案的完整取证。
本次更新
-
判据重写。 旧判据等的是旧实例的端口
$Port重新进入 Listen,而桌面版 Host 固定以
--port 0启动、端口每次由系统分配(dsh-desktop-host/lib/index.js的
args: ["--no-open", "--host", "127.0.0.1", "--port", "0"])。新实例几乎不可能拿到同一个
端口,所以旧判据结构上就是错的:重启成功它也报listening=False。新判据走两步:先找这次拉起的主进程(新进程树里唯一「父进程不在树内」的那个),再在它下面
找命令行匹配dsh-desktop-host[\\/]lib[\\/]index\.js的 Host 子进程。找到 Host 就算成功
(verdict=ok)。Host 与dsh-subprocess-local的 runner 子进程进程名相同,只能靠
命令行区分。 -
端口只作附加信息,不再决定成败。 这是本版最重要的一次修正。第一版把「端口非空」当成功
条件,结果真按一次按钮就露底:日志报verify: main=61696 host=60140 listening=[] after 31s
——pid 全选对了、重启也确实成功了(GUI 一直在127.0.0.1:52368正常服务),可端口枚举返回空。对同一个 pid 连试五次,现象是:
尝试 进程存活 netstatLISTENING 行Get-NetTCPConnection -OwningProcess1 ✓ 1 行 抛异常 2 ✓ 0 行 抛异常 3 ✓ 1 行 1 行 4 ✓ 0 行 抛异常 5 ✓ 1 行 1 行 进程存活判据 5/5 全对,两种端口枚举都会间歇性失效(CIM 抛
No matching MSFT_NetTCPConnection objects found,还会冒出并不存在的端口3082)。
所以现在的判定是verdict = hostPid > 0 ? ok : fail,listening=[]留着如实反映
「这次没读到」,不影响结论。 -
日志格式随之改变。
旧:verify: port 19387 listening=False after 30s 新:verify: main=65108 host=66888 listening=[63845] verdict=ok after 9s看
verdict=那一项,别看方括号。方括号里是新实例实际监听的端口(可能多个),与旧实例的
port=不同属正常;读不到时会是空[]。 -
这个误报是真的、且长期存在。 本插件仓库维护者机器上的真实日志:
日期 端口 判据输出 2026-10-17(固定端口时期) 19387 listening=True after 9s(4 次里 3 次 True)2026-10-17 14:50 19387 listening=False after 30s(首次误报)2026-10-18 起(端口随机化后) 54852 / 58311 / 54467 / 54079 全部 listening=False after 30s2026-10-19 54205 / 57441 仍为 False2026-10-19 04:14 51847 listening=True after 8s(偶然:新实例复用了刚释放的旧端口)而日志里的
oldPid链条46676 → 49372 → 50340 → 50444 → 57124每次的下一个oldPid
就是上一次拉起的新实例 pid——进程一直在正常换代,每次重启都真的成功了,False是误报。10-17 还能偶然报
True,是因为那几天端口固定为 19387;10-18 起端口变成随机的 5xxxx,
判据立刻全变False。 -
更正 1.0.1 发布说明里的一句错话。 当时「已知边界」写着「真实桌面版始终使用固定端口」——
错的,桌面版一直用--port 0。已就地更正,并同步修正 1.0.0 的发布说明与验收记录。 -
同步整改文档。 README.md / README_EN.md 的亮点、日志示例、判读说明全部改为按新实例
判定;docs/releases/v1.0.0.md、docs/releases/v1.0.1.md、
docs/verification/2026-10-17-v1.0.0.md里残留的旧判据文案与错误论断一并更正。
安装
set ELECTRON_RUN_AS_NODE=1
"C:\D\software\DeepSeek Harness\DeepSeek Harness.exe" --expose-internals ^
"C:\D\software\DeepSeek Harness\resources\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\cli.js" ^
plugin --profile desktop add https://codeload.github.com/csuwzw/dsh-restart/tar.gz/refs/tags/v1.0.2
把路径换成自己的安装目录。其余安装方式(应用内插件管理器、手工放置、离线 tgz)见
README。
首次安装必须手动重启一次 DSH——按钮要等重启后才存在。之后就用按钮本身。
升级与重启
从任何旧版本升上来,只需重新执行上面的安装命令,把 tag 换成 v1.0.2。
本版只改 restart.ps1 与文档,index.js、client.js、cordis.patch.yml 与 1.0.1 逐字节相同,
所以升级后不需要为这次改动单独重启:restart.ps1 是每次点击按钮时按路径现场调用的
(index.js:21 的 SCRIPT_PATH 拼接 → conhost -File),不是启动时加载的。
同一个 tag 被追加修正过:如果你已经装过第一版 v1.0.2
本版没有升版本号,v1.0.2 这个 tag 是指向「追加修正之后」那次提交的。第一版 v1.0.2
(把「端口非空」当成功条件的那版)只在仓库里存在了很短时间。已装第一版的人重跑安装命令即可,
但 pnpm 有两道缓存会挡住内容变化,按需处理:
| 现象 | 原因 | 处理 |
|---|---|---|
装完 restart.ps1 还是旧的(117 行但没有 verdict=) |
内容寻址存储里已有同 URL 的旧包 | pnpm store prune 后重装;或直接删 node_modules/dsh-restart 再装 |
报 ERR_PNPM_TARBALL_INTEGRITY |
该项目 pnpm-lock.yaml 里锁了旧 tarball 的 integrity |
加 --update-checksums,或删掉 lockfile 里该条目 |
两种情况都不会静默拿错的包——pnpm 要么给旧的、要么明确报错。新装(这两个缓存都空)不会遇到。
改动 tag 指向是有意的,不是重新发布:这次修的仍是同一个 1.0.2 的功能缺陷,只是第一版
判据在真机上被证伪。发 1.0.3 反而会让已经装了第一版 1.0.2 的人以为自己是最新的。
回退
回到 1.0.1:
plugin --profile desktop add https://codeload.github.com/csuwzw/dsh-restart/tar.gz/refs/tags/v1.0.1
或者移除 dsh.profile.bundles 里的 dsh-restart、从 dependencies 移除同名条目、删除
node_modules/dsh-restart/,然后重启应用一次。
回退只影响日志判读——1.0.1 的实际重启功能是正常的,只是它会把成功的重启报成
listening=False。
验收
| 项目 | 结果 |
|---|---|
| 新判据选靶(真实进程树) | main=65108 ✓、host=66888 ✓(与真值全对) |
| 真实端到端重启(06:23 那次) | 新格式生效,main=61696 / host=60140 均选对;listening=[] 是枚举失灵,重启实际成功 |
| 修正后对同一棵树的复跑 | verify: main=61696 host=60140 listening=[] verdict=ok ✓ |
| 端口枚举稳定性(同一 pid 连试 5 次) | 进程存活 5/5 正确;netstat 两次返回 0 行;CIM 三次抛异常 |
| 旧判据在同一验证场景 | 无信息量(未重启时新旧「实例」是同一进程);其失效由真实日志证明(见上表) |
restart.ps1 首三字节 |
EF BB BF(BOM 完好) |
restart.ps1 语法 |
0 处错误 |
| 宿主端与客户端语法 | node --check index.js / client.js / client.test.cjs 全部 exit=0 |
| 客户端离线自测 | 25 项全部通过(ALL PASS) |
package.json 声明 |
dsh.bundle.patch / dsh.client.platform / exports["./client"] / main 齐全 |
| 推送前源码目录与 main 比对 | 23 个文件全部核对 |
index.js / client.js / cordis.patch.yml 与 v1.0.1 比对 |
逐字节相同(下载 v1.0.1 tag 的 tarball 实比,非推断) |
| tag v1.0.2 的 tarball 与本地源码 | 逐字节一致(restart.ps1 5757 字节、含 verdict=、旧成功条件已消失) |
完整取证过程见 2026-10-19 验收记录。
端到端重启实测已做。 2026-10-19 06:22 维护者真按了一次按钮,日志见上;正是这次运行暴露了
第一版把「端口非空」当成功条件的错误,才有本版最终的行为。
已知边界
- 仅 Windows。 未在 macOS / Linux 上测试,
restart.ps1依赖的 WMI、conhost.exe、taskkill
在那两个平台上都不存在。 - 判定窗口 30 秒。 新实例启动超过 30 秒时脚本只写日志、不重试。有意不做重试:旧实例已被
终止时盲目重试会造出重复实例。 verdict=ok只说明 Host 进程起来了,不保证 GUI 已经能打开页面。 Host 从进程启动到真正
bind 端口之间还有一段初始化;本版不探测 HTTP 可用性(那要么引入依赖,要么自己写 socket
探测,都超出「一行日志」的范围)。要确认能打开,请直接看浏览器。- 端口列可能为空。 这是设计上的取舍,不是故障——见上文「端口只作附加信息」。若需要端口,
重启后在浏览器地址栏看即可。 - 未测过非默认 profile 名。 参考实机为
desktop。 - 客户端产物改动需重启才生效。 本 profile 未启用
@deepseek-ai/dsh-client-hmr;本版没有
客户端改动,这一条不影响。 github:简写依赖 git 这件事没有做代码层面的规避。 本插件不改 pnpm 行为,只在文档里
给出可用写法。
dsh-restart 1.0.1 — 修正安装文档里的「不需要 git」错误说法
dsh-restart 1.0.1
修正 1.0.0 文档里一处会让人装不上插件的错误说法。插件代码本身一字未改。
本次更新
-
删掉「安装不需要 git」这句错话。 1.0.0 的 README 与 PACKAGING 都写着「pnpm 解析
github:owner/repo时直接下载 codeload 的源码 tarball,不跑git clone」,据此推荐的安装命令是
add github:csuwzw/dsh-restart。实测不成立:在没有任何 git 的 Windows 上,这条命令必然失败。实测命令与输出(桌面版 CLI,空 profile,本机
Get-Command git为空):dsh: initialized profile gh-test at C:\Users\Richard\.dsh\profiles\gh-test [ERROR] Command failed with exit code 1: git ls-remote "https://github.com/csuwzw/dsh-restart.git" HEAD "HEAD^{}" 'git' 不是内部或外部命令,也不是可运行的程序pnpm 对任何 GitHub 简写(裸
github:o/r、#main、#v1.0.0)都会先跑git ls-remote解析
ref,所以「不需要 git」只对两种写法成立:commit sha 形式github:o/r#<40 位 sha>,以及直接的
codeload tarball URL。原先那句错的来源是 dshmarket 的lib/accelerate.js——那里确实写着 pnpm 不
跑git clone(accelerate.js:5-8),但它紧接着在accelerate.js:20-33把裸github:重写成
github:o/r#<sha>来绕开ls-remote,而那条重写只在市场自己的安装通道里生效。看到前一句就下结论,
漏掉了后一句。 -
安装命令改用 codeload tarball URL。 四处文档(README、README_EN、PACKAGING、docs/releases/v1.0.0.md)
的命令统一改成:plugin --profile desktop add https://codeload.github.com/csuwzw/dsh-restart/tar.gz/refs/tags/v1.0.1这个地址就是一个普通压缩包,不经过 git。想固定版本用
refs/tags/<tag>,想跟最新用refs/heads/main
(代价是版本不可复现)。 -
补上实测矩阵。 README 与 PACKAGING 新增一张五种写法的对照表,都是本机实测结果:
写法 结果 github:csuwzw/dsh-restart失败, git ls-remote找不到 gitgithub:csuwzw/dsh-restart#main/#v1.0.0失败,同上 github:csuwzw/dsh-restart#87a3945764a30d07298b627a93eefdcc56aee1bb成功 https://codeload.github.com/csuwzw/dsh-restart/tar.gz/refs/tags/v1.0.0成功 https://codeload.github.com/csuwzw/dsh-restart/tar.gz/refs/heads/main成功 顺带确认了 tag 本身没问题:
codeload .../refs/tags/v1.0.0返回 200、git 协议 advertisement 里
有refs/tags/v1.0.0,所以#v1.0.0的失败纯粹是缺 git,不是 tag 不存在。
安装
set ELECTRON_RUN_AS_NODE=1
"C:\D\software\DeepSeek Harness\DeepSeek Harness.exe" --expose-internals ^
"C:\D\software\DeepSeek Harness\resources\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\cli.js" ^
plugin --profile desktop add https://codeload.github.com/csuwzw/dsh-restart/tar.gz/refs/tags/v1.0.1
把路径换成自己的安装目录。其余安装方式(应用内插件管理器、手工放置、离线 tgz)见
README。
首次安装必须手动重启一次 DSH——按钮要等重启后才存在。之后就用按钮本身。
升级与重启
从 1.0.0 升上来只需重新执行上面的安装命令,把 tag 换成 v1.0.1。这次改动全在文档里,index.js、
client.js、restart.ps1 与 1.0.0 逐字节相同(下面「验收」一节有比对结果),所以升级后行为不变,
重启与否只影响你看到的文档。
回退
回到 1.0.0:
plugin --profile desktop add https://codeload.github.com/csuwzw/dsh-restart/tar.gz/refs/tags/v1.0.0
或者移除 dsh.profile.bundles 里的 dsh-restart、从 dependencies 移除同名条目、删除
node_modules/dsh-restart/,然后重启应用一次。
验收
| 项目 | 结果 |
|---|---|
无 git 环境下 github: 简写 |
失败(复现 1.0.0 文档的错误前提) |
| 无 git 环境下 codeload tag URL | 成功,Done in 1.3s,exit=0 |
| 装出副本与源码逐文件 | 9 个文件全部 sameHash |
| 装出副本的版本与声明 | version: 1.0.1、dsh.bundle.patch: ./cordis.patch.yml、profile 的 bundles 含 dsh-restart |
| 装出副本的文档 | README 已含 refs/tags/v1.0.1,旧错话「不需要 git」已消失 |
| 插件代码与 1.0.0 tag 比对 | 两个 tag 的 codeload 包逐字节比对:13 个文件相同,8 处变化全在文档与版本号 |
| 推送前源码目录与 main 比对 | 21 个文件全部核对:14 个未变、7 个待更新(即本次改动) |
| 客户端离线自测 | 25 项全部通过 |
restart.ps1 首三字节 |
EF BB BF(BOM 完好) |
「插件代码与 1.0.0 相同」不是推断,是把两个 tag 的 codeload 包解开逐字节比过的(安装通道用的就是这个包)。
v1.0.0 20 个文件、v1.0.1 21 个,其中 13 个逐字节相同,包含全部插件代码与资源:index.js、
client.js、restart.ps1、cordis.patch.yml、icon.svg、LICENSE、client.test.cjs、
docs/images/*、docs/verification/*、.gitattributes、.gitignore、.github/workflows/ci.yml。
8 处变化全在文档与版本号:README.md、README_EN.md、PACKAGING.md、docs/RELEASING.md、
docs/releases/v1.0.0.md、catalog/dsh-plugin.json、package.json,以及新增的 docs/releases/v1.0.1.md。
已知边界
- 仅 Windows。 未在 macOS / Linux 上测试,
restart.ps1依赖的 WMI、conhost.exe、taskkill
在那两个平台上都不存在。 - 本次未重新做端到端重启实测。 1.0.1 只改文档,插件代码与 1.0.0 逐字节相同,因此沿用
1.0.0 的验收记录(四步日志齐全、9 秒恢复)。 - 未测过 webServer 端口配成 0 的部署。 端口取自
req.socket.localPort的推理链已由桩服务验证
(随机端口 58670 被正确取到)。此处原文写「真实桌面版始终使用固定端口」,该说法是错的:
桌面版 Host 固定以--port 0启动,端口一直由系统分配(见 1.0.0 验收记录第七节)。 - 未测过非默认 profile 名。 参考实机为
desktop。 - 未做重启失败自动重试。 脚本只记录结果——旧实例已被终止时盲目重试会造出重复实例。
- 客户端产物改动需重启才生效。 本 profile 未启用
@deepseek-ai/dsh-client-hmr。 github:简写依赖 git 这件事没有做代码层面的规避。 本插件不改 pnpm 行为,只在文档里给出可用写法。
dsh-restart 1.0.0
dsh-restart 1.0.0
首个发布版本。
本次更新
- 标题栏「重启」按钮。 装在「应用 / 编辑」右侧,样式由 preload 造菜单条时留下的 open shadow
root 里的裸button规则提供,因此高度、内边距、hover 背景与相邻菜单项天然一致;文案跟随
<html lang>切换中英。 - 两次点击才动手。 第一次点进「确认重启?」态,4 秒内不点自动撤销;第二次点才发请求。按钮
位置太顺手,而重启会终止正在运行的任务,所以加了这一层。 - 不弹控制台窗口。 重启脚本整条命令包在
conhost.exe --headless里。默认终端为 Windows
Terminal 时它会忽略-WindowStyle Hidden,脚本跑在可见窗口里,新实例继承该控制台后窗口被日志
一直占着不会消失,手动关掉还会连带杀掉新实例——实测三种启动方式的控制台窗口数为 2 / 0 / 0,
取 0 的那个。 - 绕过 job object。 从插件里普通 spawn 的进程与 Host 同属一个 job,旧实例一退出就被一并带走。
重启脚本改由 WMI(Win32_Process.Create)创建,父链是WmiPrvSE.exe → svchost.exe → services.exe,已脱离该 job,且仍在 explorer 的交互会话里,因此能启动带界面的新实例。 - 以端口监听作为成功判据。 日志四步齐全(
armed→taskkill→launched→verify),
最后一步等新实例的 Host 子进程重新监听,比数进程名可靠——@deepseek-ai/dsh-subprocess-local
的 runner 子进程带同样的进程名,数进程会误报。
(2026-10-19 更正:原文写的是「等 Host 重新监听端口」,那其实是等旧实例的端口,而桌面版
固定以--port 0启动、端口由系统分配,所以该判据必然报listening=False。判据已改为按新实例
判定,详见 验收记录第七节。) - 端口不硬编码。 脚本的
-Port取自请求连接的本端端口(req.socket.localPort),因为
webServer 的端口可以配成 0 由系统分配。 - 仅回环可达的路由。 以对端 socket 地址判定回环(Host 头是调用方可控的,只作附加检查),并
要求自定义动作头x-dsh-restart-action——这会把请求变成非简单请求,外部页面无法在不触发 CORS
预检的情况下构造它。 - 零依赖、无构建步骤。 宿主端只 import
node:内置模块;浏览器端是手写的__ModuleLoader__
bundle,不经打包器。不改内核,不留补丁。
安装
需要 DeepSeek Harness 桌面版(Windows),内核 0.2.0-rc.2 上验证通过。
桌面版自带一个能管理 desktop profile 的 CLI——@deepseek-ai/dsh-desktop-host/lib/cli.js 以
manageDesktopProfile: true 启动内核启动器,绕过了「desktop profile 由 Electron 独占管理」这道
闸门:
set ELECTRON_RUN_AS_NODE=1
"C:\D\software\DeepSeek Harness\DeepSeek Harness.exe" --expose-internals ^
"C:\D\software\DeepSeek Harness\resources\app.asar\dsh\node_modules\@deepseek-ai\dsh-desktop-host\lib\cli.js" ^
plugin --profile desktop add https://codeload.github.com/csuwzw/dsh-restart/tar.gz/refs/tags/v1.0.0
把路径换成自己的安装目录。用 codeload tarball URL 而不是 github:csuwzw/dsh-restart——后者会让 pnpm 跑
git ls-remote 解析 ref,机器上没有 git 就失败(详见 PACKAGING.md 第四节)。其余安装方式
(应用内插件管理器、手工放置、离线 tgz)见 README。
首次安装必须手动重启一次 DSH——按钮要等重启后才存在。之后就用按钮本身。
升级与重启
升级即重新执行上面的安装命令(或走应用内插件管理器)。装完点标题栏的「重启」按钮;按钮不存在时
从托盘退出应用再双击图标。
宿主端与客户端产物的改动都要重启才生效:宿主端本身是启动时挂载的;客户端 bundle 由
@deepseek-ai/dsh-client-modules 在启动 compose 时一次性读入并缓存,只有启用
@deepseek-ai/dsh-client-hmr 时才有 watcher 刷新,而本插件不引入 HMR。
回退
移除 dsh.profile.bundles 里的 dsh-restart,再从 dependencies 移除同名条目,删除
node_modules/dsh-restart/,然后重启应用一次。
插件不留补丁:它不写 profile 的 cordis.patch.yml / cordis.yml,只声明自己的 patch 层;也不
写注册表、不装服务、不建计划任务。日志文件 $DSH_HOME/logs/dsh-restart-plugin.log 为纯文本,
可直接删除。
验收
逐项取证见 验收记录。要点:
| 项目 | 结果 |
|---|---|
| 端到端重启 | 四步日志齐全,新实例的 Host 重新监听,9 秒恢复 |
| 控制台窗口 | conhost --headless 下全程 0 个(原实现 2 个) |
| 客户端离线自测 | 25 项检查全部通过 |
| 路由信任围栏 | GET→405、无动作头→403、错 content-type→403、正确→200 |
| 干净环境安装 | 空 profile 装 tgz 成功,逐文件 sameHash |
| 卸载残留 | profile patch 层、注册表、服务、计划任务均无残留 |
已知边界
- 仅 Windows。 重启脚本依赖 WMI、
conhost.exe与taskkill,其它平台都没有。 - 未测过 webServer 端口配成 0 的部署,也未测过非
desktop的 profile 名。 - 没有重启失败的自动重试:旧实例已被终止的情况下盲目重试会造出重复实例,因此有意不做。
- 按钮位于标题栏菜单条而非原生菜单内。 那条原生菜单由 Electron 主进程用
applicationItems()
构建,IPC handler 强制校验 sender,插件无法往里加项。