@arcaneorion/dsh-model-channel-manager 0.3.1 → 0.3.12

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
@@ -31,6 +31,13 @@ dsh plugin --profile web add @arcaneorion/dsh-model-channel-manager
31
31
  - `dsh.profile.bundles` 加 `"@arcaneorion/dsh-model-channel-manager"`
32
32
  - `pnpm install` 后重启 `dsh --profile web`
33
33
 
34
+ > **link: 方式的模块解析坑(0.3.8 实测)**:pnpm 对 link: 包不安装其依赖;且 Node ESM
35
+ > import 会把 symlink **realpath 化**——host 从 profile 路径加载插件时,`import 'zod'`
36
+ > 实际从**工作区真实路径**向上解析,工作区没有 node_modules 就报
37
+ > `Cannot find package 'zod'`。解法:工作区 `node_modules/` 里软链宿主侧已有实体
38
+ > (`zod` ← profile 顶层;`@deepseek-ai/dsh-storage-domain` ← pnpm `.pnpm` 实体;
39
+ > `.gitignore` 已含 `node_modules/`)。npm 安装方式(dependencies 正常解析)无此问题。
40
+
34
41
  验证:
35
42
  - host 日志出现 `[model-channel-manager] booted, groups: ...`
36
43
  - `llm.providers` 出现 `roundrobin/<组id>`
@@ -88,7 +95,12 @@ dsh plugin --profile web add @arcaneorion/dsh-model-channel-manager
88
95
  - **降级**:storageDomain 缺席的 profile 退化为纯内存(不持久化流水),不拒绝启动。
89
96
  - **client 兼容**:旧 host(无 digest 字段)自动回落原始 records 路径,升级窗口不断供。
90
97
  - **已知近似**:30m/24h 视图按「最近活跃渠道」过滤,数值仍是 7 天累计(UI 已标注);
91
- 精确分窗口需 host 出多份 digest,后续增强。
98
+ 精确分窗口需 host 出多份 digest,后续增强。健康页默认 7d,避免近 30m 冷窗让用户
99
+ 误以为历史数据丢失。
100
+ - **混部 nonce 兼容**:测试/测速 nonce 使用随机安全整数(host schema 为 number),
101
+ 避免 UUID client 遇到未重启旧 host 时被旧版 `typeof nonce === 'number'` 静默忽略;
102
+ 新 host 同时兼容旧数字与字符串 nonce。使用 `crypto.getRandomValues`,fallback 为
103
+ 时间戳×1000 + 同毫秒计数。
92
104
 
93
105
  ## 响应信封(重要)
94
106
 
@@ -118,7 +130,33 @@ dsh plugin --profile web add @arcaneorion/dsh-model-channel-manager
118
130
  - 结果:`status:'ok'`(ttftMs/latencyMs/text)或 `status:'error'`(code/error)
119
131
  - 模型行内显示 ⏳→✓/✗ 状态标签(hover 见详情)
120
132
 
121
- > 注意:此通道依赖 host 半新代码。**旧 host(未重启)无 testRequest 处理器**,测试会一直「请求中」——client 现在约 66s 后超时报 `POLL_TIMEOUT` 并提示 host 未处理(不再无限轮询)。
133
+ > 注意:此通道依赖 host 半新代码。**旧 host(未重启)无 testRequest 处理器**,测试会一直「请求中」——client 现在约 66s 后超时,并按现场区分提示:`POLL_TIMEOUT_RUNNING`(host 已收到、仍在执行:渠道慢或上游挂起)或 `POLL_TIMEOUT`(host 未写入结果:多为旧 host 未重启或写入失败)。
134
+ >
135
+ > 0.3.12 起终态判定同时接受 `finishedAt`:即使条目的 `status` 被并发写坏成 `running`,只要带 `finishedAt`/`code`/`error` 就按终态显示真实上游错误,不再误报「host 未处理」。
136
+
137
+ ## 任务通道完整性(0.3.12 重构:去重与写入语义)
138
+
139
+ ## 任务通道完整性(0.3.12 重构:去重与写入语义)
140
+
141
+ 0.3.11 线上症状「等待测试结果超时:host 可能未处理该请求」的两个真实成因,均在 0.3.12 修掉:
142
+
143
+ - **去重不许做数值推断**:`handleTestRequest` / `reloadFromConfig`(测速)改用纯函数
144
+ `shouldConsumeNonce()`(导出,可在测试里直接断言判定表)——严格等值 + 已消费集合 +
145
+ 已结算值 + 已有终态结果,四者任一命中即不消费。旧实现 `last = Math.max(lastTestHandledNonce,
146
+ claimedNonce)` 对随机 53-bit nonce 有 ≈50% 失效率(「上一轮 > 本轮」即失效),
147
+ 于是同一请求在每次 `loader/volatile-update`(含宿主自己每 5s 的 digest 回写)都被重新消费:
148
+ 重复上游请求(真实计费)+ 多次执行结果写进同一条目(线上物证:同 nonce 同时带
149
+ `code TIMEOUT / 60000ms` 与 `ok:true / text`,单次执行不可能)。
150
+ - **health 子树一律叶写**:`writeHealth` 由「读整树 → 合并 → 整树写回」改为对 patch 命中的
151
+ 顶层键逐个 `set`(path-ops),单键结果用 `writeHealthLeaf(['testResults', nonce], value)`;
152
+ client 侧 shim 同样不再 `describe` 快照 + 整树回写,直接下推 path-ops。整树回写的 payload 是
153
+ 「调用时刻的快照」,晚于并发的终态落盘时会把 `status` 改回 `running`,而 `update` 的深合并
154
+ 保留后写入的 `finishedAt/code/error` → 僵尸条目 → 客户端永远等不到终态。
155
+ - client 终态判定加 `finishedAt`(写入后不可抹掉),并把 `ok` 归一成 `status` 显示真实错误。
156
+ - 降级 nonce 路径改用安全整数随机数(旧 `Date.now()*1000+counter ≈ 1.8e18` 超出
157
+ `MAX_SAFE_INTEGER`,同毫秒可撞值;strict consume-once 下撞值 = 测试被静默丢弃)。
158
+
159
+ 回归:`tests/task-dedup.test.cjs`(判定表用真实导出,不复制实现)、`tests/testresult-fallback.test.cjs`(叶写语义 + 事故复现:终态后并发叶写不得改回 running)。
122
160
 
123
161
  ## 拉取上游(模型选择)
124
162
 
@@ -134,11 +172,21 @@ dsh plugin --profile web add @arcaneorion/dsh-model-channel-manager
134
172
 
135
173
  供应商卡片头部「改名」按钮可重命名 Provider ID(约束:小写字母开头,仅小写字母/数字/连字符):
136
174
 
137
- - 轮询组候选池中引用该 ID 的 candidate 会自动同步为新 ID
175
+ - 轮询组候选池中引用该 ID 的 candidate 会自动同步为新 ID——**主 candidates 与全部 presets 都同步**(0.3.6 修复审计 F17/C05:宿主优先使用 `activePreset.candidates`,漏改它 = 删旧 provider 后组悬空)
138
176
  - `apiKeyEnv` 凭据引用**保持不变**——凭据是 write-only 无法搬移,保持引用名原地不动即可让已存储 Key 继续生效
139
177
  - 历史健康流水保留在原 ID 名下(历史存档不受影响)
140
178
  - 改名后仍需点击右上「保存全部变更」落盘
141
179
 
180
+ ## 组配置保存校验(0.3.6,审计 F16/H08)
181
+
182
+ 保存前 client 预检,以下问题**直接拒绝提交**并列出:
183
+
184
+ - 组 ID 非法(仅小写字母/数字/连字符,字母或数字开头)
185
+ - 组 ID 重复
186
+ - 组无可用候选(候选需同时选 Provider 和模型)
187
+
188
+ host 侧兜底:`rewireRoutes` 发现配置组数 > 实际路由数时 `console.warn` 列出被丢弃的组 ID(此前是静默丢弃——H08:3 组保存、路由只有 1 条,界面仍显示「已保存」)。允许清空全部组(空列表合法)。
189
+
142
190
  ## 密钥写入(凭据引用虚拟化)
143
191
 
144
192
  - 面板主视图只出现「API Key」输入框:**粘贴或输入后失焦即自动写入** DSH 凭据存储(`~/.dsh/.credentials.yaml`,0600,write-only 读不回),无手动按钮;清空输入框不会删除已存 key。上游 llm-pi-ai 的供应商 profile 只有 `apiKeyEnv` 一个密钥字段(凭据引用名),不存在内联 key 的选项——secrets 不进 settings.yaml、不随 `settings.describe` 下发,是有意的安全设计。
@@ -151,7 +199,8 @@ dsh plugin --profile web add @arcaneorion/dsh-model-channel-manager
151
199
 
152
200
  - settings 接入用 **`ctx.inject(['settings'], (sctx) => {...})`**(settings 服务异步初始化,apply 时 `ctx.get('settings')` 为 undefined——曾经整个引擎静默失效,命名空间从未注册)
153
201
  - 配置 schema(schemastery):`model-channels` 的虚模型/candidates/strategy/timeoutMs/cooldownMs/maxRetriesPerCandidate/speedTest;`model-channel-health` 的 runtime/speedRequest+lastHandledNonce/testRequest+testResults+lastTestHandledNonce/digest 小投影(records/speedResults 仅作 0.3.1 迁移的读取源,权威在 storageDomain)
154
- - 引擎:sticky/round-robin/primary 三策略;首响应超时 + 流中空闲超时(动态 = max(timeoutMs, min(120s, ttft×2)));单候选原地重试(指数退避)耗尽才换;全炸清冷却重试一轮;测速 ttft/latency/hybrid/smart 四键(smart = 0.5×ttft_norm + 0.3×(1−reliability) + 0.2×latency_norm,reliability 贝叶斯平滑 `(success+2.5)/(total+5)`);测速失败进冷却;请求隔离按组
202
+ - 引擎:sticky/round-robin/primary 三策略;**round-robin 选择时原子预留**(0.3.5:指针选定即推进,并发请求均分;旧实现成功后才推进,并发全打同一候选——审计 F13/H13);首响应超时 + 流中空闲超时(动态 = max(timeoutMs, min(120s, ttft×2)));单候选原地重试(指数退避)耗尽才换;全炸清冷却重试一轮;组级总预算 `totalBudgetMs`(默认 10 分钟,0.3.3);测速 ttft/latency/hybrid/smart 四键(smart = 0.5×ttft_norm + 0.3×(1−reliability) + 0.2×latency_norm,reliability 贝叶斯平滑 `(success+2.5)/(total+5)`);测速失败进冷却;请求隔离按组
203
+ - 自动测速(0.3.5,审计 F14/H07):`speedTest.enabled` 且组无测速结果时,**首次真实使用触发一次后台测速**(不阻塞请求);此前只有 boot 时的 `onFirstUse` 分支,常规路径无入口——开启开关后从未生效
155
204
  - 虚拟模型元数据:`reasoning.efforts` 七档(off…max)、**defaultEffort=max**——原生 `/model` 弹窗对新模型的自动填档与展示跟随该声明;会话内显式档位的跨会话恢复由 selector 插件的档位记忆层负责(`modelDirectories` 拦截,存 `model-channels.effortMemory`)
156
205
  - 迁移:startup 时从工作区 `.channel-manager/config.json` 一次性迁入 `model-channels`(无遗留则忽略);完成后写 `legacyMigrated` 哨兵防止「清空组后重启复活」;fs 未就绪时 5s×6 重试
157
206
  - 遗留 `.channel-manager/` 目录不再使用
@@ -165,6 +214,11 @@ react 经 `require('react')`;样式用 `ctx.effect` 自管理;`dsh.client: {
165
214
 
166
215
  ## 已知限制
167
216
 
217
+ - **虚拟模型能力声明与候选实际能力无联动(审计 F08,0.3.4 已做最小切片)**:请求含图
218
+ 或带 effort 时,宿主 `resolveModelInfo` 明确声明不支持的候选会被过滤(60s TTL 缓存;
219
+ `inputModalities` 缺失=未知不过滤,保持 failover;全滤退回原列表报真实错误)——
220
+ 防住 H14(图片静默替换后假成功)与 H06(effort 强塞被拒)。**仍未做**:虚拟模型声明
221
+ 元数据(vision/efforts/窗口)与候选能力的联动聚合,属后续专项
168
222
  - 动态超时实现了首响应 + 流中空闲;全炸后「清冷却重试一轮」回溯,未实现「等待最早冷却」的睡眠分支
169
223
  - 测速结果不入健康流水(pi 记);smart 键只统计真实请求
170
224
  - **Token 字段只在新记录上出现**:host 升级重启前的存量健康记录无 token 字段,7 天视图对重启前的调用会低估 token(请求数/可用率不受影响);数据自重启后开始累积
@@ -172,6 +226,45 @@ react 经 `require('react')`;样式用 `ctx.effect` 自管理;`dsh.client: {
172
226
  - 轮询渠道/健康统计面板需要 host 新代码(重启后生效);健康流水的数据在**实际请求过轮询组**后才出现
173
227
  - `reasoningEfforts` 缺失(undefined)的 model 正确渲染(`|| {}` 兜底)
174
228
  - 会话模型选择器搜索版已拆出为独立插件 `@arcaneorion/dsh-model-selector-search`(原生座位遮蔽、搜索、向上展开菜单、effort 档位未实现等边界见该仓 README);本插件不再注册任何座位
229
+ - `makeLegacyApi` 0.1 兼容门面仍保留(94 行、11 调用点):拆除要动 6 个功能路径的双层
230
+ 信封,待 0.3.2 真实环境验证后再决定
231
+ - 30m/24h 健康视图是近似口径(按最近活跃过滤,数值为 7 天累计,UI 已标注);精确分窗口
232
+ 需 host 出多份 digest
233
+
234
+ ### 0.3.12 审计已确认、尚未修(按严重度)
235
+
236
+ 均为 2026-10-01 只读审计结论(13 项),0.3.12 先修了其中最要命的两条(去重 guard、整树回写):
237
+
238
+ - **volatile 提交静默失败仍是放大器**:loader 的 `_commitVolatile` 在 `resolveConfig` 抛错时
239
+ 只 `logger.warn` 后返回——document 已落盘、fiber 引用不更新、**不发 volatile-update**,
240
+ 而宿主只在 volatile-update 里消费请求 → 请求被彻底忽略且无任何可见报错。当前 payload
241
+ 校验通过,属潜在复发路径。建议:宿主加「未结算 nonce」兜底轮询 + 未消费显式日志。
242
+ - **remount/dispose 会打断在飞的测试**:超时守卫挂在 `ctx.effect` 上,dispose 只 `clearTimeout`,
243
+ promise 永不 reject → `runModelTest` 挂死 → 终态永不写入(旧闭包的排队写入只进
244
+ `console.error`)。建议:守卫不挂 fiber effect,dispose 时补写 ABORTED 终态。
245
+ - **60s(host)/66s(client)预算错配**:跑满 60s 时终态写入稍晚于 client giveUp。建议 host 收到
246
+ 45s 或 client 放弃前比较 `lastTestHandledNonce` 再补一轮。
247
+ - **health 写不带 `expectedRevision`**(配置域已带):叶写后危害大幅降低(不同键互不覆盖),
248
+ 但同一叶子仍无冲突检测。
249
+ - 低危:`digest: []` 空数组会遮蔽 settings 里的旧 `records`;`migrateLegacyConfig` 直写未走
250
+ `outsideTransaction`(在事务内会抛 "cannot be nested" 并被空 catch 吞掉);health-store 的
251
+ `eventKey = ts|provider|model` 会把同毫秒同渠道两笔当重复合并。
252
+
253
+ ## 引擎超时与生命周期(0.3.3 重构:审计 F01/F02/F15 已修)
254
+
255
+ - **per-attempt AbortController**:每次候选尝试独立 signal,用户取消转发(`relayAbort`,
256
+ finally 移除防泄漏)+ 超时 abort(`attemptController.abort(raceErr)`)。pi-ai 适配器把
257
+ `options.signal` 经 `AbortSignal.any` 融进 watchdog 并传给上游 HTTP——abort 即真正
258
+ 取消网络请求,不再有「return() 排在挂起的 next() 之后拖住 failover」(H02 复现的根因)
259
+ - **统一 finally 有界关闭**:streamAttempt / measureCandidate / runModelTest 三处流消费
260
+ 路径,成功 return / 失败 / 消费者提前退出都走 `closeInner`(closed 防重入 + 3s 关闭
261
+ 预算 race,预算超时补一发 abort)
262
+ - **组级总预算 `totalBudgetMs`**(默认 10 分钟,组配置可调):超预算不开新尝试,以
263
+ `CHANNEL_BUDGET_EXCEEDED` 终结——旧实现最坏 `2×N×(R+1)` 次尝试(默认 R=2 → 6N)
264
+ - **测速/测试改总时限**(F15):旧实现每 chunk 重置 guard(H12:timeoutMs=40 流每
265
+ 20ms 输出,84ms 后仍成功),现在 `deadline` 固定总时限,超时 abort
266
+ - 回归:`tests/timeout-abort.test.cjs`(契约断言 + 挂起流行为级验证——3s 关闭预算内
267
+ 完成,不等满 5s 挂起)
175
268
 
176
269
  ## 踩坑速记(本项目,按严重程度)
177
270
 
@@ -200,3 +293,13 @@ react 经 `require('react')`;样式用 `ctx.effect` 自管理;`dsh.client: {
200
293
  23. **整字段落盘 + 内存态被配置快照覆盖**(0.3.1 已根治):`settings.update` 是整字段替换;旧实现 `reloadFromConfig()` 每次 volatile-update 都用配置快照整体覆盖 `state.records`,而健康 flush 有 2s 防抖 → **刚记下的一笔在落盘前就被内存覆盖**(症状:面板“测试”成功不入账,失败反被全局拦截器的 catch 记上)。当时的修法是 `pendingRecords` 缓冲补账;0.3.1 起权威数据搬入 storageDomain(见「健康数据存储」),settings 只存 digest 小投影,此竞态从数据模型层消除。
201
294
  24. **nonce 落盘时机与启动竞态**:`lastTestHandledNonce` / `lastHandledNonce` 必须在**得出结果之后**写(提前写会让“未就绪”的重试被自己的持久值挡掉);启动瞬间凭据服务尚未就绪时测试/测速会以 `MISSING_CREDENTIAL` 失败(凭据其实已在 `.credentials.yaml` 里),应识别为「还没就绪」→ 释放认领 + 5s 延时重试(上限 24 次),**不要**写成渠道故障;测速还必须在整组候选都因未就绪失败时**不落盘、不冷却**,否则一次启动重放就把所有渠道误判成故障。
202
295
  25. **domain 写入的并发丢失(0.3.1 review 挽救)**:`KvTable.put` 是整 record 覆盖,`get→filter→put` 的读-改-写在 put 的 IO 延迟窗口内并发调用会互相覆盖(后写盖先写,先记的账丢失)——恰好复刻了要消灭的丢账问题。**并发追加必须走原子链**:`update(key, fn)` 的 fn 在写链队列槽位看到当前值;桶不存在时 update 报 `missing-key`,先 put 初始化。另外在 promise 链里 `ctx.effect` 注册 disposer 前必须先验 fiber 活性——对 inactive fiber 注册会抛 `INACTIVE_EFFECT`,若被外层 catch 吞掉则 domain 永不 close,facility 名字被占 → HMR 重载后 `already-open` 静默降级。回归:`tests/health-domain-sync.test.cjs`。
296
+ 26. **`ctx.get(name)` 是宽松读,不代表可以访问服务(0.3.10 线上事故)**:Cordis 的服务属性访问经 Proxy 校验 inject——`ctx.get('storageDomain')` 直接查 store 返回裸服务(不校验 state/inject),但拿着这个未声明的 ctx 做 `ctx.storageDomain.open(...)` 会抛 `cannot get property "storageDomain" without inject`;若被 promise 链的 catch 吞掉,表现为「插件启动正常、domain 永远不打开、健康流水静默降级为内存」。**可选服务必须用响应式 inject**:`ctx.inject(['storageDomain'], (dctx) => { ... dctx.storageDomain ... })`——回调只在服务可用(state=2)时触发,缺席时插件照常工作;同时把插件主启动(`reloadFromConfig` + `boot`)放在 inject **之外**先跑,避免服务缺席时路由不注册。回归:`tests/domain-wiring.test.cjs`。
297
+ 27. **`settings.update` 的深合并清不掉旧键(0.3.10 修)**:`update` 走 `mergeLayers`,`{records: {}}` 覆盖已有对象是**深合并**——旧键原样保留。0.3.8 的迁移「清空 settings 旧流水」实际没删,`records` 一直留在 profile 里,每次 `describe`(client 5s 轮询全量命名空间)都要带着它。要真删除必须走 path-ops:`settings.mutate(ns, [{op:'unset', path:['health','records']}, ...])`(`isVolatilePath` 对 volatile 子树的后代返回 true,允许操作)。回归:`tests/domain-wiring.test.cjs`。
298
+ 28. **schema 类型漂移会静默清空整个 volatile 子树(0.3.11 线上事故,自己埋的)**:0.3.2 把 client nonce 改成 UUID 字符串、host 消费代码也兼容了字符串,**但没同步改 schema**——`lastTestHandledNonce: z.number()`。字符串一落盘,`HEALTH_SCHEMA` 解析时子字段抛错;而它是 `.loose(true)`,宽松兜底把**整个 health 子树换成默认空对象**(实测 `healthOf()` 返回 `{}`,不报错、不告警)。后果:`records`/`digest` 在运行时全部消失 → 存量迁移遍历空对象(什么都不导入)→ 清理逻辑以为「无残留」(旧键删不掉)→ 表现为「健康页没有历史数据」且旧 `records` 永久占据 profile。三条教训:① **写侧放宽类型时,schema 必须同步放宽**(用 `z.union([z.number(), z.string()])` 兼容历史值);② **`.loose(true)` 的兜底是静默的**,volatile 子树里一个字段失配 = 整树数据不可见;③ **迁移标记不可信**——`migrateFrom` 改为「桶已存在时合并去重」而非整桶跳过,且「有存量必须先导入再清理」,这样即使 marker 被误写也能把旧账救回来。回归:`tests/health-schema-tolerance.test.cjs`。
299
+
300
+ 29. **去重不能依赖 nonce 的数值单调性(0.3.12)**:`last = Math.max(lastTestHandledNonce, claimedNonce)` 看似「取最新」,实则假设了 nonce 单调递增——而 0.3.2 起 nonce 是随机 53-bit,**「上一轮 > 本轮」时(≈50%)判等失效**,同一 testRequest 在每次 `loader/volatile-update`(包括宿主自己每 5s 的 digest 回写触发的那次)都被重新消费。症状是两级:① 上游被真实重复调用(计费);② 多次执行的结果写进同一条目(线上物证:同 nonce 一条记录同时带 `code TIMEOUT / 60000ms` 与 `ok:true / text/ttftMs`,单次执行不可能)。**规则:去重只做严格等值 + 已消费集合 + 已结算值 + 已有终态,任何「大小推断」都是错的**。释放认领(notReady 重放)时必须同时释放已消费集合,否则重放被自己的去重挡住。回归:`tests/task-dedup.test.cjs`(判定表直接调用生产导出 `shouldConsumeNonce`)。
301
+ 30. **整树读-改-写会把并发终态「回灌」成旧值(0.3.12 线上事故)**:health 子树的每次写入都曾是 `cur = healthOf(); update({health:{...cur, patch}})`——payload 是调用时刻的**整树快照**。只要它晚于并发的终态写落盘,`status` 就被改回 `running`;而 `settings.update` 是深合并(只覆盖出现的键、不删键),后写入的 `finishedAt/code/error` 反而被保留 → 条目变成「running + 终态字段」的僵尸,客户端只认 `status==='ok'|'error'` 就永远等不到终态,66s 后误报「host 可能未处理该请求」。**规则:settings 里的共享子树一律叶写**(patch 命中的键逐个 `set`;单键结果用 `writeHealthLeaf(['testResults', nonce], value)`;修剪用 `unset` 而非整字典覆盖),client 侧同样不得「describe 快照 + 整树回写」。另:客户端终态判定要接受 `finishedAt`——状态位可能被写坏,但已经发生的终态字段不会消失。回归:`tests/testresult-fallback.test.cjs`(F4 直接复刻事故时序)。
302
+
303
+ ## 0.3.11 修复后的自愈路径(无需手工清库)
304
+
305
+ 重启后: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,8 +1,11 @@
1
1
  {
2
2
  "name": "@arcaneorion/dsh-model-channel-manager",
3
- "version": "0.3.1",
3
+ "version": "0.3.12",
4
4
  "type": "module",
5
5
  "main": "src/index.js",
6
+ "scripts": {
7
+ "test": "node --test \"tests/*.test.cjs\""
8
+ },
6
9
  "exports": {
7
10
  ".": "./src/index.js",
8
11
  "./client": "./src/client.js",
@@ -33,6 +36,9 @@
33
36
  "publishConfig": {
34
37
  "access": "public"
35
38
  },
39
+ "devDependencies": {
40
+ "yaml": "^2.9.0"
41
+ },
36
42
  "peerDependencies": {
37
43
  "@deepseek-ai/cordis": "^4.0.4",
38
44
  "@deepseek-ai/dsh-llm": "0.2.0-rc.1",
@@ -50,7 +56,9 @@
50
56
  "patch": "./cordis.patch.yml"
51
57
  },
52
58
  "client": {
53
- "inject": ["slots"],
59
+ "inject": [
60
+ "slots"
61
+ ],
54
62
  "platform": "web"
55
63
  }
56
64
  }