Skip to content

Releases

v1.0.2

Choose a tag to compare

@github-actions github-actions released this 20 Sep 06:30

1.0.2 (2026-09-20)

CI 修复:2 条用例依赖运行机器时区 —— test workflow 自 2026-09-08 起
6/6 次全失败,根因即此。无生产逻辑改动(仅测试与 CI;lib/index.js 与
client.js 的改动全部是文件头注释与版本号,无行为变化)。

发布后补丁(同日):外部审核发现 assets/DSH-model-router-v1.0.2.zip 为过期产物
—— 包在 11:20 打出,而 CHANGELOG 在 14:28 被改写为扁平标准后包未重打,
于是包内 CHANGELOG 自述「904.6 KB / 34 文件 / 顶层单一目录 / 含 scripts/」,
与实际内容「251.0 KB / 29 条目 / 扁平 / 不含」自相矛盾(详见下「发布后补丁」节)。

缺陷

2 条用例用硬编码时刻断言时段命中,却没给 Router 指定 timeZone
(缺省 null = 系统时区):

// unit.test.mjs —— 注释写「上海 00:30」,但 config.timeZone 是 null
router._now = new Date('2026-09-04T16:30:00Z');
assert.deepEqual(router.candidates(...), [{ provider: 'q-off', model: 'n-off' }]);

2026-09-04T16:30:00Z 在 +8 机器上是本地 00:30(命中谷时窗 ✓),
在 UTC 机器上是本地 16:30(不命中 ✗)→ 同一断言结果相反。

症状极具误导性:本地(+8)全绿,CI(UTC)全红,
且 6 个矩阵 job 全失败(含与本地配置相同的 macos-latest, 22)。

修复

  1. makeRouter() 新增可选 opts.timeZone(不传则维持原行为 null)
  2. 2 条用例显式传入 timeZone: 'Asia/Shanghai'(或 new Router({ timeZone }))
  3. CI 的测试步骤显式声明 env: TZ: UTC —— 把「跨时区可复现」钉成持续约束,
    而非依赖 runner 镜像的默认时区

多时区实测(6 个时区全部通过):

TZ 结果
UTC ✓
Asia/Shanghai ✓
Asia/Tokyo ✓
America/New_York ✓
Europe/London ✓
Pacific/Auckland ✓

同时修复:1 条用例在 Windows 上必然失败

修完时区后 CI 变为 5 绿 2 红 —— 仅剩 windows-latest(Node 22/24)。
根因是同一测试里的场景 3:

chmodSync(roDir, 0o555);                    // 造只读目录
assert.ok(thrown, '只读目录写入应抛错');      // 期望 EACCES

Windows 的 chmod 是空操作(无 Unix 权限位语义)→ 目录仍可写 →
writeAtomic 成功 → 断言崩。

修复:把场景 3 拆成独立测试并加 { skip: process.platform === 'win32' },
场景 1/2(跨平台)留在原测试中继续在 Windows 上跑。
刻意跳过而非改成假断言 —— 权限语义只在类 Unix 上可测,保 coverage 诚实。

测试数 290 → 291(拆分后新增 1 条)。

新增 · v1.0.2 安装包(assets/DSH-model-router-v1.0.2.zip)

打 tag 发 Release 时由 release.yml 上传。251.0 KB / 29 条目,
按项目既有打包标准(扁平布局,无顶层目录前缀)。

package.json  client.js  cordis.patch.yml  screenshots.json
README.md  README.zh.md  CHANGELOG.md  LICENSE
lib/(19 个 js,含 lib/wrapper/)

排除:node_modules / .git / test/ / docs/ / scripts/ / assets/ /
SUBMISSION.md / .github/ / 其他 *.zip / .DS_Store / *.bak。

验证(解压后实测,非只看列表):

  • package.json version = 1.0.2
  • 27 个文件与仓库当前状态逐字节一致
  • 不含 node_modules|test/|docs/|scripts/|assets/(计数 = 0)
  • 20 个 js 文件语法检查通过
  • 无 zi?????? / XX?????? 临时文件残留

说明(第一版走了弯路):本版首次打包时按「自包含」思路加入了
scripts/ + assets/
(904.6 KB / 34 文件),理由是 README「方式二」的
deploy.sh 需要 scripts。但用户指出应沿用既有标准 —— 该标准面向
「拷贝进 node_modules」的场景,脚本与截图均不需要。已改回标准形态。
「方式二」的脚本按 README 原文取自本仓库,不由 Release 资产提供。

修复 · 仓库内版本声明不统一(7 处)

统一前,同一仓库并存 0.8 / 0.9.5 / 1.0.2 三种版本口径:

位置 原状 现状
README.md / README.zh.md ## v0.8 范围声明(含「86 项测试」「五页签」「F 段计划 v0.9.0」) ## v1.0.2 范围声明(291 项测试 / 8 页签 / F 段已实现)
README.md / README.zh.md ## 阶段交付速览(v0.9.10–v0.9.21) (v0.9.10–v1.0.2),补 v0.9.20–v1.0.2 三行
client.js 头注释 插件浏览器半(client)v0.8.0 + 「五页签」 v1.0.2 + 「8 页签」(L1/L2/L3 结构按实际重写)
client.js 页签定义注释 「七页签」 「八页签」(+ 测试档案)
README.zh.md 状态接口示例 端口 3080 3081(与 README.md 及实机一致)
SUBMISSION.md 多处 v1.0.1 / 8775a3b / 「lib/index.js 注释已滞后」 v1.0.2 / 3ebd7d6 / 「已更新」
assets/ 部署包仅 v0.8.0 补 v1.0.2

⚠ 未改动:根 commit 43460d6「v0.9.5 实机状态基线锚点」的提交说明。
那是历史事实记录(记录仓库建立时的实机状态),不是版本声明;
改写它需要重写 git 历史,风险远大于收益。
同理 CHANGELOG.md 中历史版本条目里的「五页签」等表述保持原样。

修复 · release.yml 按 tag 精确选包(原为 assets/*.zip)

原逻辑上传 assets/ 下所有 zip。现在该目录长期存有历史版本的包
(v0.8.0 + v1.0.2),打 v1.0.2 tag 会把 v0.8.0 的包也附上 —— 用户可能下错版本。

files: assets/DSH-model-router-${{ steps.tag.outputs.value }}.zip

tag v1.0.2 → 只上传 assets/DSH-model-router-v1.0.2.zip ✓
缺包时不失败(fail_on_unmatched_files: false),仅告警 —— 便于先发版后补包。

更新 · lib/index.js 顶部能力注释(滞后于实现)

原注释写「能力(v0.2.0)」且有多处与实际不符:

原注释 实际
能力(v0.2.0) v1.0.2
rules/propose 支持面板热更新 propose 已是 v0.9.1 前的遗留兼容字段,新客户端不再提交、不参与路由
commit-on-first-chunk v0.6.0 已改为 commit-on-substantive
详见 lib/wrapper.js 实现已迁至 lib/wrapper/index.js(wrapper.js 仅 20 行 re-export 薄桥)

补齐 v0.9/v1.0 的能力:纯选择驱动、每日报告、模型测试与档案、峰谷段场景、
窗口限额与重置规则、free-tier 节流、8 页签面板。
「四种候选扩展策略」经核实仍成立,保留。

同时:CI 失败可诊断化

job 日志需仓库 admin 权限才能通过 API 下载
(GET .../actions/jobs/{id}/logs → 403 Must have admin rights),
在无权限环境下无法定位失败。现改为:测试失败时把「测试名 + 错误详情」
发成 GitHub annotation(::error::),公共仓库的 check-run annotations
可匿名读取(GET .../check-runs/{id}/annotations)。

同时:CI 增强

  • concurrency —— 同分支新推送取消旧运行(tag 运行不取消),省 CI 分钟
  • timeout-minutes —— 防挂死任务烧分钟
  • workflow_dispatch —— 手动触发便于排查
  • changelog-guard job —— 校验「package.json 版本须在 CHANGELOG 有对应段落」。
    release.yml 依赖 CHANGELOG 段落,缺段会在打完 tag 后才失败;提前到 PR 阶段拦截。
  • npm test 由硬编码文件名改为 glob test/*.test.mjs
    —— 此前新增测试文件会静默漏跑;manual-suite-automated.mjs(需实机实例)
    由命名约定自然排除。

排查过程中的弯路(如实记录)

首轮误判为「CI 缺 @deepseek-ai/dsh-llm(peerDependency 不在公共源)」,
据此加了 test/stubs/dsh-llm/ 测试替身 + CI 安装步骤。

该诊断是错的:我的复现脚本跳过了 CI 的 npm install 步骤,
所以复现出的是个假故障。实际 npm install 能正常从公共 npm 源装上
@deepseek-ai/dsh-llm@0.0.1-rc.1(38 个导出)。

教训:复现 CI 失败时,必须完整复现 CI 的每一步(含依赖安装),
否则会对着假故障修出真复杂度。替身已删除。

发布后补丁 · 产物与源码不一致(外部审核发现,同日修复)

缺陷:assets/DSH-model-router-v1.0.2.zip 是过期产物 —— 包在 11:20 打出,
CHANGELOG 在 14:28 被改写为扁平标准后包未重打。于是包内 CHANGELOG 自述:

包内 CHANGELOG 自述 包的实际内容
904.6 KB / 34 文件 251.0 KB / 29 条目
「顶层单一目录 DSH-model-router-v1.0.2/」 扁平(package.json 在根)
「含 scripts/」 不含
「含 assets/(2 张截图)」 不含

下载者读包内 CHANGELOG 会以为「应该有 34 个文件、应该有 scripts」,实际没有 →
怀疑下载损坏或版本错误,产生无效排查。

为什么能静默通过发布:release.yml 只校验「assets/ 里的包存在」,
不校验「内容来自本 tag 的源码」;且 fail_on_unmatched_files: false
让「包缺失」也只告警不失败。

修复(三层):

  1. 重打并覆盖:assets/DSH-model-router-v1.0.2.zip 按当前标准重建;
    Release 上的资产同步替换。
  2. 打包逻辑收进 scripts/pack.sh(单一来源)—— 人工与 CI 共用同一份,
    避免「人工打包 / CHANGELOG 规格描述 / CI 上传」三处独立维护再次分叉。
    脚本内含自检:版本一致、扁平、不含排除目录、条目名全 ASCII。
  3. release.yml 加硬门禁:checkout(已带 ref: tag)后用当次源码重建包,
    与 assets/ 里那个做条目级 + 内容级比对,不一致即 ::error:: + fail。
    原则:assets/ 只作留档,Release 资产必须来自当次源码。

验证:用修复前的过期包试跑门禁 → 条目级与内容级均报红(差异正是上述规格矛盾),
证明该门禁确实能拦住此类缺陷。

门禁自身的假阳性(2026-09-21 首次上线即踩到,共 2 处):

  1. 日期列 = mtime —— 条目级比对最初用 unzip -l 的 大小 + 日期 + 名称 三列,
    其中日期列是源文件 mtime:CI 检出时全变成检出时刻,本地打包用编辑时刻,必然不一致。
    → 已去掉日期列,只比「未压缩大小 + 条目名」。
  2. 条目顺序 = 文件系统顺序 —— zip -r lib 按文件系统顺序写入条目
    (macOS/APFS 与 Linux/ext4 不同),不排序则 diff 必然不一致。
    → 已在比对前 sort。

两次都导致 tag v1.0.2 的发布被误判为「条目清单不一致」而失败。

修正后双向验证:① mtime 不同 / 条目顺序不同但内容相同 → pass;
② 注入一处内容差异 → fail(未丧失判别力)。

与本项目审核报告 §7.2 记录的两处「校验器假阳性」同源:未经校验的校验器会制造假缺陷,
比不校验更危险
。判据必须避开与被测对象无关的变量 —— 此处是 mtime 与条目顺序。

发布后补丁 · 上架材料与安装指引(外部审核发现,同日修复)

  • SUBMISSION.md 测试计数过期:写「290/290(单测 277)」→ 实测 291(单测 278 + 契约 13)。
    根因是一致性检查清单未覆盖 SUBMISSION.md(已补进 skill §I 第 ⑧ 项)。
  • README 安装指引:README.md / README.zh.md 的「方式一:插件市场」原先
    被列为推荐,但 npm 包尚未发布、awesome-dsh-plugin 条目尚未提交 ——
    按指引操作的用户第一步就会失败。现标注「⚠️ 上架中,暂不可用」并把
    「方式二:本仓库脚本」提为当前推荐。

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 15:44

0.8.0 (2026-09-07)

  • G1 每日 API 报告:日账本 NDJSON 按天追加 + 每日凌晨 1 点调度生成昨日报告(可配 reports.hour)
    • 稳定性评级 S/A/B/C/D/N/A;按供应商×模型聚合(calls/失败/切换/token/时延/错误码分布)
    • 三渠道交付:文件(~/.deepseek-harness/home/model-router-reports/)+ 面板「每日报告」页签 + API
    • 接口:GET /reports、GET /reports?l1=1(面板轻量摘要)、GET /reports?day=YYYY-MM-DD、POST /reports/generate
    • 默认 reports.enabled=false,零记账零调度,独立可回退
  • G2 面板信息分级:五页签三级结构(概览摘要卡 → 明细页签 → 工具折叠区)
    • 概览 5 张摘要卡:健康总览 / 今日运行 / 昨日报告 / 熔断速览 / 规则速览
    • 轮询分级:概览态 5s 轮询 ?l1=1 轻量接口,明细页才全量拉取
  • G3 Windows 适配:deploy.ps1 / undeploy.ps1(PS5.1 兼容、无 BOM UTF-8、id: model-router 幂等)+ CI 三平台 matrix
  • A-1:failoverSignals 基于实际 10 项末尾追加 INVALID_REQUEST(DSH httpErrorCode(400, 非 quota/context)),严禁整段替换
  • A-2:新增 providerMeta 配置域(quotaGroup/tier,自家配置域不跨插件读 settings);route hop 内联优先
  • A-3:maxRetries 自动调优——未显式声明时回填 max(quotaGroupCount×2, 5);显式声明(含显式 2)一律尊重
  • B-1/B-2:按需负载测试 lib/loadtest.js(4-phase:probe / rpm / context / quota-group),复用 probe 原语
    • 端点 GET/POST/DELETE /api/model-router/loadtest;默认只测 free tier;rpm 触发限流后自动等待恢复
    • 所有探针请求带 PROBE_MARK 直透——跑完 cooldown/metrics/quota/daily 零污染
  • C-1:router.candidates() 按 quotaGroup 去重(同组保留链序第一个)
  • C-2:registry registeredPairs()/metaSnapshot() 携带 quotaGroup/tier 元数据
  • 附带修复:wrapper.js sessionId 未赋值 bug;透传路径 try/finally 兜底记账;status version 去硬编码
  • 测试:86 项全过(基线 61 + 新增 25:A-2/A-3/C-1/C-2 8 项、G1 12 项、B-1/B-2 5 项)