v0.3.5 合并发布:v0.3.2 / v0.3.3 / v0.3.4 未单独发 Release,此处一并列出(对应 tag 均已在仓库中)。
0.3.5 · 修复:vendored 内嵌把范围声明当版本,上游全被误判「消失」(2026-09-28)
导出 fluent-deepseek-harness-1.0.0.dspack 时,8 个依赖全部被内嵌为 -local.1 且 reason: upstream-missing,但上游全都活着。追查下来是两个正交问题:
1. version 解析 bug(src/core/vendored.js)
listProfileDependencies 把 package.json 里的依赖声明原文直接当版本(version: spec,即 ^2.0.2)。registry 的 tarball / 元数据 URL 不接受 ^:
GET /dsh-opencode-palette/^2.0.2→ 404(精确版本2.0.2才是 200)…/dsh-opencode-palette-^2.0.2.tgz→ 404
于是取件失败 → 回落本地重打包 → 乱加 -local.1。修复:读 node_modules/<pkg>/package.json 的真实安装版本折算(spec 保留原文),取件与「三处一致」都用精确版本。
顺带修了 fetchRegistryTarball 的 URL:DEFAULT_REGISTRY 带尾斜杠再拼 / 产生双斜杠(npm 容忍但部分 registry 会 404),现归一为单斜杠。
2. 删掉「死上游探测」(probeNpmAlive)
auto 档默认会对每个 npm 依赖联网探测上游存活、404 就擅自补选内嵌。这次就是它:探测用带 ^ 的版本必 404 → 8 个包全部误判「上游消失」→ 自动内嵌 + -local.1。与其「替用户检测」翻车,不如不检测:
auto档改为手动勾选 + 已内嵌 round-trip,打包全程不联网;probeNpmAlive及其 UI 文案(vendor.mode.auto)一并清理;full档行为不变(全部直接依赖 + 传递闭包)。
新增/调整用例:npm 范围 spec 折算为精确版本(^ 不进 tarball URL)、expandVendorSelection 的 auto 档不再探测。全量 161 通过。
0.3.4 · 修复:悬浮球打开设置后停在别人分区的成因(2026-09-28)
0.3.3 实测(desktop profile 装的就是 0.3.3 的包):点球后设置弹窗打开了,仍停在「费用估算」。
先把根因钉死:SettingsPanel 的当前分区是
const active = rows.find((r) => r.id === activeId)?.id ?? rows[0]?.id;—— 一旦我们这一节此刻不在 rows 里,shell 会把选择回退到第一行(别的插件 order 更小,占着「费用估算」)。所以「点了我们那一行」并不等于「切过去了」,光看一眼点击事件是不够的。
- 切分区改成闭环:点 → 下一拍读 shell 的
aria-current(它标的就是当前选中行)→ 不是我们就再点(maxClicks上限 3 次)→ 2s 内始终不是我们才如实报失败。弹窗已开着的情形也走同一条循环(旧实现那条路径只点一次、不验收)。 - 旗标握手:球在点官方入口之前先立
window.__dspackWantSection;客户端插件在同一个 MutationObserver 里看到旗标,会在弹窗挂上的第一时间把导航切过去 —— 比球自己轮询更早,而且它认识自己的 label。 - 标签通道
window.__dspackSectionLabel:球拿不到钩子时也能按文案认行(归一化空白 + 容忍包含关系);导航图标标记仍作最后兜底。 - 失败要能自证:提示里带上「我看到的导航」,例如
设置已打开,请在左侧选「整合包」;我看到的导航:通用 / 费用估算 / 整合包,
同时console.info一份诊断;window.__dspackBall.diag()可拿到完整快照(钩子在不在、标记在不在、我方 label、当前选中行、各行文案)。
顺带确认了一件事:profiles/desktop 是 better-deepseek-harness 的 Junction,里面装的正是 17:49 落地的 0.3.3。若点球时页面没做过硬刷新,跑的还是旧球脚本(旧脚本既没有 2s 重试,也没有失败提示)。
新增 4 个用例(aria-current 验收、点了没落上要再点、下一拍才表态不许重复点、标签通道),全量 160 通过。
0.3.3 · 修复:悬浮球打开设置后停在别人的分区(2026-09-28)
0.3.2 实测现象:点球之后官方设置弹窗正常打开了,但停在「费用估算」那一节。
根因不在「找入口」,而在「切分区」:别的插件注册的 settings.section order 比我们小(我们 order 20),占了导航第一行,而 SettingsRoot 的 activeId 初值就是 rows[0] —— 于是弹窗一开就落在别人的分区上。0.3.2 的切分区实现有两个短板:只认导航图标标记 [data-dspack-nav-icon](由 MutationObserver + label 文本匹配打上,可能晚一两帧、也可能压根没打上),而且只轮询 300ms(6 × 50ms)就放弃。
- 改为调用客户端插件提供的钩子
window.__dspackFocusSection(settings-nav-icon.js的installSectionFocusHook):按当前 locale 的分区标签找到本插件那一行并点击,locale 切换后立刻反映,不再依赖标记时机;导航图标标记仍作兜底。 - 轮询窗口 300ms → 2s(40 × 50ms),期间持续尝试切到本插件分区。
- 切不过去也要指路:新增
onFocusFail回调,球提示「设置已打开:请在左侧选『整合包』」,不把用户晾在别人的分区上。 - 顺带的防御性加固(不是本次现象的根因):找官方设置入口不再「取第一个
aria-haspopup="dialog"匹配」——那个属性同类插件的费用气泡(.dsh-cost-pill等)也有,且createPortal到 body,一旦设置触发按钮当时不在 DOM 里就可能点错。改为正向打分:设置区祖先_settingsArea+5、aria-label命中设置/偏好/Settings +4、自身类名_trigger+2、带aria-haspopup="dialog"+1;命中cost/usage/billing/pet/whale/meter/stats特征或身处已开弹窗 → 出局。低于阈值(4)一律不点,返回no-trigger并如实提示。另外点完会轮询校验设置弹窗真的出现,没出现就把误点的开关收回去(设置区里的官方入口绝不收回 —— 它expanded=true说明弹窗正在开,可能只是慢)。
新增 11 个用例(切分区钩子优先、慢弹窗、切不过去的指路回调、打分边界、费用气泡与设置入口并列时的回归等),全量 154 通过。
0.3.2 · 悬浮球:绕开侧边栏 / 设置页的独立入口(2026-09-28)
整合包会装进各种 client 插件,有的会把侧边栏或设置页搞坏 —— 那时用户进不去「整合包」设置界面,连换包、卸载都做不到。新增一个悬浮球,由宿主注入 index.html(不经过任何前端插件,别人把 UI 拆了它还在),点击直接打开原来的设置页并切到本插件分区。
- 不另造页面:官方设置界面本身就是完整弹窗,诉求是「进不去」而不是「没有这个页面」。
SettingsRoot(dsh-client-ui-settings-general)的开合是组件内部 state、官方无对外 API(ctx.remote.settings.openSettingsDocument()是「在本地打开设置文档」,不是打开这个弹窗),唯一的路是点它注册到settings.trigger的按钮 —— 好在属性是语义化的button[aria-haspopup="dialog"][aria-expanded](无构建哈希类名),而合成click()不要求元素可见:侧边栏被主题藏起来、被挤扁、变轨道态,只要弹窗组件还挂着就点得动。点开后再把导航切到本插件分区(认data-dspack-nav-icon标记,不依赖中英文文案)。 - 注入两条通道:
webserver/index-inject结构化行(桌面壳 index.html 从静态 dist 直出、函数变换过不去,这是桌面端唯一生效通道)+webServer.tapIndex(web 形态兜底),靠路径标记互相去重。行内容用内联 script 而非script-src行:官方页面解释器对两类行处理不对称 —— 内联那条是 createElement + textContent + append、没有 await,不可能「加载失败」;script-src那条会await loadScript(src),失败即 reject 成致命错误把整个页面带下水。内联 loader 自己建<script src>并吞掉onerror:路由在就有球,不在也只是没球。 - 球体行为:
position:fixed自建 DOM、纯尺寸驱动(不量 DOM → 无测量竞态);document 级 pointer 拖拽(down 挂、up/cancel 摘)+ 3px 点击阈值;松手按四分之一区域吸附、横纵轴独立(可组合出四角);位置只存「吸附边 + 离边偏移」,resize 后仍贴边、脏数据夹回可视区(对应「启动闪一下 → 滑出屏幕 → 消失」那类坑);贴左整体镜像、图标反向镜像保持可读;关键样式!important顶回宿主皮肤按几何特征(position:fixed + 高 z-index)的拦截;touch-action:none让移动端可拖。 - 资源路由:
GET /dsh-pack/ball.js(cache-control: no-store)复用既有/dsh-packprefix 路由,不新增路由以免撞重复路径检查;缺lib/ball.js时回 404 而非抛错。 - 第二道保险:客户端插件也调一次
mountBall()(window.__dspackBall幂等守卫,不会挂出两个球)。window.__dspackBall暴露openSettings()/setAction(fn)/showTip()/settle()/destroy(),要接别的动作直接换。 - 已知边界(不粉饰):若坏插件让侧边栏那棵 slot 子树整个崩掉(React 抛错),设置弹窗与触发按钮一起不存在,此时谁也开不了官方设置页 —— 球会如实提示「没找到官方设置入口」,不假装成功。那种场景需要的是急救(禁用坏插件)或自建面板。
新增测试 21 个(几何 / 锚点往返 / 脏数据钳制 / 注入行去重 / 资源下发分支 / 幂等挂载 / 官方设置入口识别与打开时序),全量 149 通过。