# 更新日志 版本号遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/);每一版都有对应的 `v<版本号>` 标签,可在 [Releases](https://github.com/Yara2090/dsh-update-center/releases) 查看。 ### 1.6.4 - **新增文档一致性检查**(`test/docs.test.js`,用例数 95 → 100)。这一轮里「文档里的数字过期」 是反复出现的问题——光「用例数」一项就手工改过四次(84 → 87 → 89 → 91 → 95)。现在四条 不变量由用例守着:目录里每条锚点都要跳到真实标题且文案一致、每个章节(除目录自身)都要 写进目录且顺序一致、「目录结构」树与实际文件双向对齐、文中写的用例数与实际数量一致。 - 写完当场抓到一处真漏:新加的 `docs.test.js` 自己忘了写进 README 的目录结构树。 - **修正「亮点」里一处说得太满的话**:「下载字节与速率」只在 npm 下提供;pnpm 的缓存布局 不同,代码刻意不显示(宁可不给,也不给一个错的数字)。原文没写这个限定。 - **接口表下面补了一句**:只有「无错误」槽位始终以 `null` 出现,其余字段在尚未发生时会整个 不出现在 JSON 里(`latestVersion` 要检测过一次才有),取值前要先判断。 ### 1.6.3 - **改掉一处用词错误:中文把 npm「注册表」写成了「仓库」。** 插件查的是 **npm 注册表** (`registry`),不是代码仓库;英文侧一直写的是 `registry`,中文侧却在 5 个文件里混用了 「仓库」,其中**两处是页面上能看见的**:设置页副标题「检查**仓库**中的新版本」、检查失败 时的提示「无法读取版本**仓库**」。README 开头那句「自动检测**仓库**上的新版本」同样错。 现在统一成「注册表」。 - 保留了三处**正确**的「仓库」:`.gitattributes` 的「仓库内存 LF」、README 与 integrity 用例里「只能重新克隆仓库」——那三处指的确实是代码仓库。 - 纯文案改动,行为零变化;用例仍为 95。 ### 1.6.2 - **新增前后端接口一致性检查**(`test/endpoints.test.js`,用例 91 → 95)。做法学自同类插件 `dsh-whale-widget` 的 `tools/ci-audit.mjs`——它给的理由是「前端与后端是两个各 1.5 万行的 单文件,各改一半就会在运行时静默 404,用户只看到『点了没反应』」。本插件是同样的架构、 同样的风险。三条不变量:前端调的每个动作后端都认、**后端认的每个动作前端都在用**、 两边路由前缀是同一个字符串。 - 第二条正是冲着 1.6.0 那件事去的:那条「半撤」的重启路由如果早有这样的检查,第一天就 会被标红。验证方式是把它故意加回后端(`if (action === 'stop' || action === 'restart')`), 检查确实报 `后端有路由但前端从不调用:restart —— 半撤的残留就是这样来的`。 - 检查自身带非空断言,防止「门禁腐烂」:一个什么都抽不到、于是永远通过的检查比没有更糟。 - **`package.json` 补上标准字段**:`repository` / `homepage` / `bugs` / `main`。两个同类插件 都有前三个,本插件此前一个都没有。 - **没有加 `peerDependencies: { react }`**(同类插件那样写了):本插件的浏览器半边是从 Harness 的**浏览器模块表**取 react(`require('react')`),并不从 npm 解析它,而宿主用的 是哪个版本在本机也查不到——签一个版本范围只会换来装插件时的告警,所以不签。 ### 1.6.1 - **修掉 1.6.0 删重启时留下的一处显示错误。** 旧偏好文件里可能还躺着一条 `lastLifecycle.action === "restart"` 的记录,它的字段形状和停止完全一样(同样有 `ok` / `pid`),照单全收会让「运行控制」卡片把一次**重启**说成一次**停止**——而 界面上的名字已经写死成「停止 Harness」了。现在读偏好时只收没有 `action` 的记录 (1.6.0 之后写的)或 `action === "stop"` 的(更早的停止记录),其余丢掉;同一份 文件里其它偏好照常读入,不会因为一条坏记录把整份设置丢了。 - 这是被问「删掉重启有没有影响别的功能」时查出来的,查法是把 v1.5.2 与 v1.6.0 放在一起做 A/B:状态快照**只有** `lifecycle` 的键不同(`canRestart`/`launcher` 消失),12 条路由请求**只有** `/restart` 不同(旧版 200 / 409 → 新版 404), 其余全部逐字一致——包括「脚本齐全时 `/stop` 仍返回 200 scheduled」。 - 用例 89 → 91(新增「旧的重启记录不显示」「旧的停止记录仍读入」)。 ### 1.6.0 - **删掉「重启」的全部残留。** 1.5.0 只把界面上的按钮撤了,实现、`POST /restart` 路由 和一组用例都还留着——于是它成了一条**谁都不走、也从没被真跑过**的路径。实测触发它 (走的就是那条还活着的路由)会让服务停掉、却拉不起来:启动器每次启动前会清日志, 所以连失败现场都没留下,只能靠「服务没自己回来」这件事反推。 - `lib/lifecycle.js` 只剩停止:`planLifecycle`/`scheduleLifecycle` → `planStop`/`scheduleStop`, 去掉 action 参数、`-ForceRestart`、`-Workspace` 与 `LAUNCH_SCRIPT`。 - `resolveLifecycle` 不再返回 `canRestart` / `launcher`;状态快照里的 `lifecycle` 从 `{canStop, canRestart, stopper, launcher}` 收窄为 `{canStop, stopper}`。 - 路由表里没有 `/restart` 了,访问它会得到 `404`(**这条有专门的用例守着,防止它 悄悄长回来**)。 - 界面(`client.js`)本来就没在用这些字段,只是清掉了三句提到「重启」的过时注释。 - **这一版真正想记下的教训不是「重启坏了」,而是「半撤」的代价。** 入口撤掉之后, 剩下那半看起来是活的(有路由、有实现、有绿色用例),实际却从没被端到端验证过; 而它的失败方式是把服务停掉。要撤就撤干净,或者留一条真的有测试走通的路径。 - 用例数不变(89):删掉一条重启用例,补上一条「`/restart` 必须 404」的守门用例。 ### 1.5.2 - **修复「一键修复」可能删掉一个真实目录**(数据安全)。判据 `looksLikeLink` 此前拿 `realpath(target)` 和 `target` 直接比字符串,而 realpath 会把**整条路径**规范化—— Windows 上包括大小写与 8.3 短名,任何平台都包括路径中间的任何一层链接。于是只要 target 的**前缀**写法不唯一,一个货真价实的普通目录就会被判成「指向别处的链接」, 而 profile-link 的修复分支正是据此决定要不要删掉它重建。现在基准改成**父目录解析后的 写法**:两边前缀一致,剩下的差异只可能来自 target 自己这一层,那才是「它是不是链接」 的定义。实测(空目录,前缀用别名路径):修复前 `repairedCount` 为 1、目录被换成链接; 修复后为 0、目录原样保留。 - **修复 profile 判定在同类情形下退化成「无法确定」**:`discoverProfileDir` 里最硬的那份 证据(磁盘上的链接确实指向本插件,score 3)同样在拿一个 realpath 结果比一个未规范化的 写法,因此永远拿不到,只能退回清单去猜——而清单恰恰是坏掉时才需要自检的那部分。 现在两边都先 realpath 再比。 - 这两个问题**不是新代码引入的**,而是加上 CI 之后第一次在 GitHub 的 Windows runner 上 跑就暴露出来的:那里的临时目录带 8.3 短名,正好命中「路径写法不唯一」。7 条用例因此 变红,其中 `遇到真实目录时不硬来,如实报失败` 期望「不产生任何修复动作」却得到了 1—— 那就是上面那次删除。 - 新增两条回归用例:用「中间层是链接」的别名路径让同一个目录拥有第二种写法,从而在任何 机器上都能确定地复现这两种情形;其中一条会真的跑一次修复,并断言那个目录还在。 - 用例 87 → 89。 ### 1.5.1 - **安全:版本号必须真的是版本号,才会进安装命令。** `npm install --global` 在 Windows 上 要经 shell 执行(npm/pnpm 是 `.cmd` 包装脚本,`shell: true` 的参数不会被转义),而注册表 返回的 dist-tag 此前只检查「是不是字符串」就被拼进了参数。现在有两道闸:读取注册表时 (`applyDistTags`)和**安装出口本身**(`runUpdate`)。第二道才是贴着危险动作的那道—— 安全闸不能只建在调用方,而此前唯一挡住它的是浏览器半边把按钮置灰。 - **顺带收窄同一条路上的另一个入口**:配置里的 `registry` 也会以 `--registry=<地址>` 进入 命令行,现在只接受能被 URL 解析、且协议为 `http`/`https` 的地址,其余退回官方源。 - **新增 GitHub Actions**:Ubuntu(Node 20 / 22)与 Windows(Node 22)跑同一套用例。 Windows 那一条会让 `test/lifecycle.test.js` 里的 PowerShell 冒烟测试真的执行, 其余平台上它自动跳过——那条用例正是「detached 让 PowerShell 静默退出」的产物, 只在 Windows 上跑才有意义。 - 用例 84 → 87:新增「注册表返回非版本号」「安装出口拒绝非法版本号」「注册表地址回退」。 - 拆出本文件,README 从 478 行压到 360 行;README 顶部补徽章与截图位。 ### 1.5.0 - **只保留「停止」,去掉「重启」**。两个按钮带来的是持续困惑(「我点的是哪个、接下来会发生 什么、为什么页面回不来」),而「停止 + 用桌面快捷方式启动」本来就是同一条路径。现在界面上 只有一个动作,少一个概念就少一处误解。重启的 Host 路由保留在 API 里,界面不再暴露。 - **停止后自动尝试关闭标签页,关不掉就如实说**。页面会盖一层浮层说明「服务即将关闭、这个 标签页可以关掉了」,并调用 `window.close()`;随后等一小段时间,如果代码还在跑就说明浏览器 拒绝了,于是提示按 Ctrl+W。实测 Chromium 拒绝的理由是:`history.length = 2` 且 `window.opener = false`,即外部打开的标签页不允许被脚本关闭——**这件事做不到就不能假装做到**。 - **移除整套「旧页面自动恢复」机制**(`resumeAt` / `loginUrl` / 轮询 / 手动重开按钮)。 它成立的前提是「服务会自己回来」,而那个前提只属于已经去掉的「重启」;连同一起移除的还有 给页面暴露带令牌地址的那条路径。 - 安装卡片上的「需要重启才生效」改为明确指引:点「停止 Harness」,再用桌面快捷方式启动。 - README 的安装说明按要求压成**两种方式**:纯手动(下载 → 装依赖 → 写 bundle 列表 → 重启), 或把 GitHub 地址交给 DSH 让它装。原先那种「先下载、再分两种装法、最后重启」的三段式里, 其实只有这两条路。 - 用例相应调整(84 个,去掉两个只服务于已删机制的用例)。 ### 1.4.1 - **修复「闪一下刷新,然后照样卡在重新连接中」**。上一版让旧页面自己恢复,但**排定动作的 那个进程也会立刻交出它自己的登录地址**——而它几秒后就被杀掉,那个令牌已经作废。页面拿到 就跳过去,于是表现为:先有一次刷新感,接着又回到「重新连接中」,而且新页面里没有待办动作, 轮询不会再启动,只能手动开着。 现在**排定动作的进程绝不交出自己的地址**,只有从磁盘读到待恢复窗口的新进程才发;页面因此 会一直等到真正可用的那一刻才跳。 - 浮层上新增「**立即重新打开**」按钮:自动恢复之外的第二条路,用户点一下才发请求,不受任何 定时器或浏览器策略影响;服务还没起来时如实说「还没起来」,而不是装作在忙。 - 新增用例专门守住这条:排定动作的进程返回 `loginUrl: undefined`,后来起来的进程才返回地址 (用例总数 84 → 85)。 ### 1.4.0 - **停止/重启之后,旧标签页会自己恢复**。此前它只会永远停在「重新连接中」:重启换了进程, 旧页面的登录凭证随之失效,而页面自己没有任何办法回来——用户既看不到新标签页,也只好手动 关掉旧页面。现在 Host 在动作排定后的 10 分钟窗口内,会在状态里带上由 `connection` 服务 签发的带令牌地址(启动时打印 `dsh web: …?token=…` 用的就是它),旧页面每 2 秒问一次, 拿到就自己跳过去完成重新登录。**不需要新标签页,也不需要手动关闭。** - 带令牌的地址只在那个时间窗内提供;`connection` 取不到时退回手动提示,因此本插件在不带 Web 连接服务的组合里照常工作。 - 新增用例覆盖「排定动作后把恢复地址交给页面」(用例总数 83 → 84)。 ### 1.3.3 - **停止/重启后,旧页面会直接告诉你接下来去哪**。实测确认动作本身一直是好的:重启会拉起新 进程并**自动打开带新令牌的标签页**,停止则按预期把服务关掉。但旧页面会永远停在 「重新连接中」,而那时页面上没有任何提示——用户只能靠猜。现在动作排定后立刻盖一层浮层: 重启 → 「启动器已在新标签页打开登录链接,请切过去」;停止 → 「服务已关掉,双击桌面 『DeepSeek Harness』重新打开」。浮层可关闭。 - 顺带把「旧页面为什么回不来」说清楚:重启换了进程,旧页面的登录凭证随之失效,属于正常。 ### 1.3.2 - **把「停止」和「重启」的差别写到点之前**。用户反馈「两个按钮看起来一样,点完都是页面 连接中」——反馈是对的:功能没问题,但后果没写清楚。现在卡片常驻两行说明,第一次点击 进入确认状态时还会再显示一遍对应后果,并明说「停止后页面一直显示连接中属于正常」。 - **两个按钮在视觉上分开**:停止用错误色描边(不可逆),重启用主色。 - **「上次动作」跨重启保留**:结果写进偏好文件。停止/重启会把进程换掉,只有落盘才能在新 进程里回答「我刚刚点的那个按钮到底生效没有」。 ### 1.3.1 - **修复「停止/重启点了没反应」**:这两个动作在 Windows 上完全没生效。根因是 `spawn(..., { detached: true })` 会带来 `DETACHED_PROCESS`(新进程没有控制台), 而 Windows PowerShell 在这种状态下会**以退出码 0 静默退出、脚本一行都不执行**—— 既不报错也不留痕,所以页面看着像什么都没发生。 - 新增「上次动作」一行:子进程是否真的起来由 `spawn` / `error` 事件上报并记进状态, 失败时页面直说原因,不再出现「点了没反应、也不知道为什么」。 - 补一条 Windows 冒烟用例:**真的拉起 PowerShell 写一个文件**来验证脚本被执行—— 只断言「spawn 被调用过」是抓不到这个 bug 的(用例总数 80 → 83)。 ### 1.3.0 - **新增「运行控制」**:设置页里可以直接停止或重启 Harness,不用再去桌面找快捷方式。 起因是个很常见的误会——「关掉浏览器就是停止服务」,其实服务一直在后台跑,于是 「改了代码但页面没变」这种事就没人说得清。 - 停止/重启**不自己实现杀进程与拉起**,而是调用本机原有的 `launch-deepseek-harness.ps1` / `stop-deepseek-harness.ps1`,行为与桌面快捷方式完全一致;脚本不存在时按钮禁用并写明缺了什么。 - 动作延后 1.5 秒、并以脱离本进程的子进程执行:要被停掉的正是这个进程,回包必须先发出去。 - 重启沿用当前端口与工作区(读 `run/web-server.json`),不会重启到别的地方。 - 安装进行中拒绝停止/重启(`409`)。 - **修复 profile 判定**:`DSH_PROFILE_DIR` / `DSH_PROFILE` 是 Harness 注入给**工具子进程**的, 宿主进程自己并没有,导致「profile 注册 / profile 链接」两项在真实环境里永远显示 「无法确定」、一键修复也跟着失效。现在改为从磁盘反推(谁的 `node_modules` 指向本插件, 或谁的清单里声明了本插件),证据不足时仍如实说「无法判定」而不是乱猜。 - 新增 `lib/lifecycle.js`、`test/lifecycle.test.js`(用例总数 65 → 80)。 ### 1.2.0 - **新增「完整性检查」**:10 项自检覆盖插件文件、清单、源码依赖、浏览器半边、 profile 注册与链接、偏好文件、DSH 主目录可写性、Node 版本与 Harness 安装。 进入设置页自动跑一次,也可随时点「检查完整性」重跑。 - **新增「一键修复」**:补回 profile 清单里缺失的依赖项与 bundle 条目、重建插件链接、 重写损坏的偏好文件、创建缺失的 DSH 主目录。每项先备份(`.bak`),只补不删,且幂等。 - **修复的安全边界写进代码**:链接位置若是普通目录,区分「拷贝进来的一份插件」(警告)与 「别的东西占了名字」(报错),两种情况都不自动删除;重建链接时只删链接本身, 绝不触碰它指向的真实目录(有专门的用例守着这条)。 - 链接判定改用 `realpath`:Windows 的目录联接在 Node 里 `isSymbolicLink()` 为假、 `isDirectory()` 为真,只看该标志会把 pnpm 的联接与手工修复的结果误判成普通目录。 - README 的安装说明重写为三步,并补了「出问题时」对照表。 - 新增 `lib/integrity.js` 与 `test/integrity.test.js`(用例总数 43 → 65)。 ### 1.1.0 - **新增安装进度反馈**:不确定进度条 + 已用时长、已下载字节、下载速率、已取包数。 这些数字由 1 秒心跳刷新,因此 npm 完全静默时页面依然在动,不再出现「点了立即更新 之后一片安静」。 - **新增静默提示**:活动(输出或字节增长)中断超过 90 秒时给出明确说明,而不是让 用户对着一个不动的按钮猜是卡了还是慢。 - **安装命令带 `--loglevel=http`**(pnpm 用 `--reporter=append-only`),让安装在管道里 也有输出;同时加上 `--no-audit --no-fund` 减少无关网络往返。 - **安装与检测共用配置里的 `registry`**:安装命令现在会带上 `--registry=<地址>`。 此前它只影响检测,配了镜像的用户装的时候仍走官方源。 - **安装结束后保留总耗时**,便于判断某次升级是否异常。 - 修掉一处子进程 `error` 事件被后续 `close` 覆盖的问题(启动失败的原因不再丢失)。 - 新增 `lib/progress.js` 与 `test/progress.test.js`(用例总数 25 → 41)。 ### 1.0.0 - 首个版本:版本信息卡片、通道切换、自动检测、自动安装开关、一键更新与安装输出回显。