Skip to content

Releases

dsh-restart 1.0.2 — 重启判据改为按新实例判定

Choose a tag to compare

@csuwzw csuwzw released this 19 Oct 00:09

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 连试五次,现象是:

    尝试 进程存活 netstat LISTENING 行 Get-NetTCPConnection -OwningProcess
    1 ✓ 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 30s
    2026-10-19 54205 / 57441 仍为 False
    2026-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」错误说法

Choose a tag to compare

@csuwzw csuwzw released this 17 Oct 01:39

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 找不到 git
    github: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

Choose a tag to compare

@csuwzw csuwzw released this 17 Oct 01:14

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,插件无法往里加项。