@arcaneorion/dsh-model-channel-manager 0.3.14 → 0.3.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -306,6 +306,32 @@ react 经 `require('react')`;样式用 `ctx.effect` 自管理;`dsh.client: {
306
306
  - 回归:`tests/timeout-abort.test.cjs`(契约断言 + 挂起流行为级验证——3s 关闭预算内
307
307
  完成,不等满 5s 挂起)
308
308
 
309
+ ## 引擎集成测试(0.3.15:真正跑一遍 apply → adapter → 引擎)
310
+
311
+ 此前所有引擎测试都是**源级断言或逻辑复刻**,抓不到「作用域/引用」类缺陷:0.3.5 的 F13
312
+ 重构把 `let cursor = …` 改成两个分支内赋值却丢了声明,ESM 严格模式下每次走虚拟路由都抛
313
+ `ReferenceError: cursor is not defined`(用户看到的「本轮运行失败」),而当时 32 个测试全绿。
314
+
315
+ `tests/engine-integration.test.cjs` 用最小 Cordis ctx 替身(只实现插件用到的 8 个面,
316
+ `ctx.timeout` 按 `cordis-plugin-timer` 的真实双形态实现)真正执行:
317
+
318
+ ```
319
+ apply(ctx, Config({...})) → 捕获 llm.registerAdapter 的 adapter
320
+ → adapter.stream({provider: 'roundrobin/<组>'}) → 断言真实 llm.stream 调用序列
321
+ ```
322
+
323
+ 覆盖:路由分发、round-robin 指针原子预留(F13 并发均分)、单候选重试与 failover 顺序、
324
+ `NO_ADAPTER` 终止块、`prepareCall` 快照路径、effect disposer 可执行(卸载不留悬挂 interval)。
325
+ **验证过它抓得住原 bug**:临时删掉 `let cursor = 0;` → 7 项里 5 项立即失败。
326
+
327
+ 另加静态守卫 `tests/no-undeclared-assignment.test.cjs`:用 acorn 解析两个源文件,
328
+ 找出所有「赋值给从未声明标识符」的目标(F18 的 `idx`、这次的 `cursor` 都是这一类)。
329
+ 这是文件级 no-undef(比词法作用域宽松,宁可漏报不误报),恰好覆盖最危险的形态。
330
+
331
+ > 顺带观察(非缺陷):`speedTest.enabled` 默认开启,所以**组内第一次请求会后台触发一次
332
+ > 全候选测速**(每个候选一次真实请求)。UI 文案是「开启自动测速」,语义一致;不想付这份
333
+ > 额度就在组设置里取消勾选。
334
+
309
335
  ## 踩坑速记(本项目,按严重程度)
310
336
 
311
337
  1. **settings 服务异步初始化**:host 插件 `ctx.get('settings')` 在 apply 时为 undefined → 整个引擎静默不生效(无报错、无命名空间)。必须 `ctx.inject(['settings'], ...)`。
@@ -345,6 +371,8 @@ react 经 `require('react')`;样式用 `ctx.effect` 自管理;`dsh.client: {
345
371
 
346
372
  33. **settings 的 revision 是「raw config 的 JSON 指纹」,会被插件自己的健康写入推高(0.3.14)**:`describe()` 里 `revision += raw !== previous.raw`,`raw = JSON.stringify([fiber.uid, schema.toJSON(), entry.options.config])`。本插件的行同时承载健康投影(digest 每 5s、runtime、testResults),所以 client 手里的 revision 几秒内必然过期,保存被 `settings/conflict` 拒绝,提示却是「已被其他页面修改」——**其实「其他页面」就是插件自己**;providers 在另一个行(llm-pi-ai)没有自噪声,于是表现为「部分保存:提供商成功、轮询组失败,点多次才成功」。两条教训:① **带 revision 的写入必须「写入前重读」**,加载时记下的 revision 只在「该行没有其他写入者」时才有效;② 冲突要**分类**——比较远端值与加载基线(键序无关的规范 JSON),远端没变就是自噪声(重试),真的变了才是冲突(报错)。根治方向是把高频数据搬出该行(Remote/storageDomain)。回归:`tests/revision-conflict.test.cjs`。
347
373
 
374
+ 34. **重构时把「声明 + 赋值」拆成分支内赋值,却丢了声明(0.3.15,用户看到的「本轮运行失败 cursor is not defined」)**:F13 把 `let cursor = strategy === 'primary' ? 0 : …` 改成 `if (round-robin) { cursor = …; } else { cursor = …; }` —— 两处赋值都在,**声明没了**。ESM 恒为严格模式,赋值未声明标识符直接抛 `ReferenceError`,于是**每一次走虚拟路由的请求都失败**;更糟的是这个错误只有真正跑到那一行才暴露:注册、目录、模型菜单、源级测试全部正常(32 项全绿)。教训:① **局部变量改写分支结构时,声明必须留在分支之外**(`let x;` + 分支内只赋值);② **引擎需要「真跑一遍」的集成测试**——源级断言/逻辑复刻挡不住这类缺陷,见 `tests/engine-integration.test.cjs`;③ 加一道静态守卫:acorn 扫「赋值给未声明标识符」(`tests/no-undeclared-assignment.test.cjs`),同一类缺陷的 F18(`idx`)也会被它抓住。
375
+
348
376
  ## 0.3.11 修复后的自愈路径(无需手工清库)
349
377
 
350
378
  重启后:schema 接受历史字符串 nonce → `records` 重新可见 → `migrateFrom` 把旧流水**合并去重**进 domain(`my-opencode-go` 桶已有新事件,按 `ts|provider|model` 去重后并入)→ path-ops 真删除 settings 里的 `records` → digest 重算包含历史。日志会打印 `legacy health imported into domain and cleared from settings`。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcaneorion/dsh-model-channel-manager",
3
- "version": "0.3.14",
3
+ "version": "0.3.15",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -37,7 +37,8 @@
37
37
  "access": "public"
38
38
  },
39
39
  "devDependencies": {
40
- "yaml": "^2.9.0"
40
+ "yaml": "^2.9.0",
41
+ "acorn": "^8.18.0"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@deepseek-ai/cordis": "^4.0.4",
package/src/index.js CHANGED
@@ -628,6 +628,10 @@ export function apply(ctx, config) {
628
628
  // 推进 rt.currentIndex,两个并发请求都从 first 开始(并发不均分)。
629
629
  // 现在:选定时立即占位推进(同步读改写,JS 单线程内原子),失败不回滚
630
630
  // (失败反馈走冷却,指针保持前进语义——轮询分布比严格顺序更重要)。
631
+ // 0.3.15:`cursor` 必须先声明——0.3.5 重构时把原来的 `let cursor = …`
632
+ // 改成了分支内赋值却没保留声明,ESM 严格模式下每次走虚拟路由都抛
633
+ // `ReferenceError: cursor is not defined`(本轮运行失败)。
634
+ let cursor = 0;
631
635
  if (strategy === 'round-robin') {
632
636
  cursor = rt.currentIndex % order.length;
633
637
  rt.currentIndex = (cursor + 1) % order.length; // 本次请求已占用 cursor 槽位