dsh-connect-modelscope-token-plan 0.3.0 → 0.4.0
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/CHANGELOG.md +94 -82
- package/README.md +5 -0
- package/client.js +150 -36
- package/cordis.patch.yml +4 -0
- package/lib/host-config.js +136 -44
- package/lib/index.js +922 -357
- package/lib/llm-adapter.js +1 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,105 +1,117 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
3
|
+
> 本文件只记公开行为变化(新增能力、破坏性改动、重要修复)。实现细节、重构与测试加固请直接看 git log。
|
|
4
|
+
>
|
|
5
|
+
> **判断标准**:写下一条之前问一句——**用户能观察到它吗?** 能(面板变了字、流程多了步、
|
|
6
|
+
> 数据不再丢)才进本文件;不能(改了哪个文件、加了几条测试、抽了哪个函数)进 commit message
|
|
7
|
+
> 或 `docs/archive/`。**本文件是给用户看的,不是给维护者看的。**
|
|
8
|
+
>
|
|
9
|
+
> **写法**
|
|
10
|
+
>
|
|
11
|
+
> - **主题化要点**,不是 `Features/Fixes` 分组。一个主题一个粗体小节,下面 1–4 条要点。
|
|
12
|
+
> **一条 ≤ 120 字,讲清「变了什么 + 用户看到什么」**;病灶取证、判据推导、阴性对照一律不写。
|
|
13
|
+
> - **大小是信号**:一节 20–30 行是正常体量,超过就说明**混进了实施过程**——把它搬走。
|
|
14
|
+
> 既有的长节(如 `[0.2.0]`)是历史债,**别再照着写**。
|
|
15
|
+
> - **摘要必须自含主语与后果**:`snapshot-aggregate.ts` 把空清单当不过滤 ≠
|
|
16
|
+
> 「面板说 allowed:"all" 却按旧清单注册」。写**后者**——前者是病因,读者要的是症状。
|
|
17
|
+
> - **不许沉默**:修好一个从未生效的分支、或改掉一个会误导人的读数,也算行为变化,
|
|
18
|
+
> 照实上报(例:`[0.3.0]` 的「读不到」不再被画成 0)。**不许拿「内部修复」当不写进
|
|
19
|
+
> 本文件的理由**。
|
|
20
|
+
> - **测试/门禁事项不单独成节**:`npm test` 多几个套件、CI 换了工具链版本,用户看不见。
|
|
21
|
+
> 确有影响就并在「工程」一行里,细节留在 commit 或归档。
|
|
22
|
+
>
|
|
23
|
+
> **过程材料去哪**:`docs/archive/YYYY-MM-DD-X.Y.Z-process.md`。发版时新开一节,条目里
|
|
24
|
+
> 带一句外链(见各节末)。**不要为了少写一节而把过程删掉**——那是丢证据,不是精简。
|
|
25
|
+
>
|
|
26
|
+
> **发版时**:顶部开 `## [X.Y.Z] — YYYY-MM-DD`,完整流程见
|
|
27
|
+
> [RELEASING.md](RELEASING.md) §3(本文开头这段是**写作者规范**,那是**发布操作流程**)。
|
|
28
|
+
|
|
29
|
+
## [0.4.0] — 2026-10-17
|
|
30
|
+
|
|
31
|
+
**面板保存不再被瞬时网络故障打断**
|
|
32
|
+
|
|
33
|
+
- 在面板里切换 provider 开关、保存模型清单或上下文窗口时,若恰逢魔搭目录拉取失败,此前的实现会把失败降级成「空目录」并照样发布——已注册的模型会从选择器里全部消失,直到下一轮轮询才恢复。现在这种故障只保留当前已服务的模型并记录原因,下一次收敛自然重试。
|
|
34
|
+
- 保存动作与后台轮询此前的两套互不知情的发布逻辑,现已合并为单一的收敛流程:保存后立即生效,不再出现「保存了却要等下一轮轮询才兑现」、或生效值被旧轮询覆盖回去一个周期的现象。
|
|
35
|
+
|
|
36
|
+
**出图工具不再让整轮对话干等**
|
|
37
|
+
|
|
38
|
+
- 出图工具此前会一直 `await` 到出图完成才返回(最长 15 分钟),期间这轮对话什么都做不了。现在一次调用只等约 90 秒(新增 `drawWaitMs` 可调):图好了直接返回图片,没好就返回「仍在生成中」加一个 `jobId`,稍后带上它再调用即可继续查询——只查询,不会重复出图。
|
|
39
|
+
- 15 分钟总预算仍然是「这张图值不值得继续等」的上限,但改为**从提交时刻起累计**(跨多次续查),超过才放弃;放弃时依旧会说明上游任务可能仍在生成,而不是报「出图失败」。
|
|
40
|
+
|
|
41
|
+
**出图设置的保存不再丢,失败消息里的密钥不再漏**
|
|
42
|
+
|
|
43
|
+
- 出图开关与出图模型两个设置此前各存各的、互不排队:快速改两项时,后写的那次会盖掉前一次——界面报成功,其中一项却没落盘。现在两次写入串行,两个都能存住。
|
|
44
|
+
- 出图设置文件**存在却读不懂**时(更新的构建写的、或文件损坏),此前会被当成「没设过」并照写照存,把盘上的设置覆盖掉。现在与 provider/用量两处同纪律:读不懂就拒写并说明原因;读侧把「保存的值被忽略」明说成一条警告,面板不再把它呈现成「从未保存过」;`npm run doctor` 也终于会盘点这个文件。
|
|
45
|
+
- 出图失败消息的脱敏此前用的是本模块私有的一份正则副本,比插件其余部分少两组规则(`sk-` 密钥、`password=…` 形式的键值对)——上游把请求体原样回显时,凭据可能一路进面板与日志。现在全插件共用同一份。
|
|
46
|
+
|
|
47
|
+
**provider 状态读不到时,面板不再拿编出来的值顶上**
|
|
48
|
+
|
|
49
|
+
- 此前「接入为 DSH 模型」那一块读不到时,插件会塞一份填满默认值的块(开关关、0 个模型、允许清单「不过滤」)。那个形状看起来就是一个答案,面板因此不再去单独查询 provider 状态,只能按它渲染——而真相是「读不到」。现在这一块会被整个省略,面板随即改用独立的状态查询取真值,两次都取不到才显示「未接入」。
|
|
50
|
+
- 省掉的块不再把原因一起带走:这类失败会作为一条警告列出,操作者能区分「没接入」与「插件读不到自己的状态」。
|
|
51
|
+
|
|
52
|
+
**「全部隐藏」的口径统一**
|
|
53
|
+
|
|
54
|
+
- 允许清单里同时含「全部隐藏」哨兵和真实模型 id 时(手工编辑 `provider.json` 会出现),现在与面板显示一致地按「什么都不提供」处理;此前个别路径会把其中的真实 id 放出来,面板计数与实际提供的模型不符。
|
|
55
|
+
|
|
56
|
+
> **实施过程(病灶、取证、判据)在
|
|
57
|
+
> [docs/archive/2026-10-17-0.4.0-process.md](docs/archive/2026-10-17-0.4.0-process.md)**——那几轮里
|
|
58
|
+
> 不少是踩过的坑,照着做能绕开。
|
|
22
59
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- **npm 10(Node 22 自带)装不上依赖**:`npm error Cannot read properties of null (reading 'edgesOut')`,十几秒就死在 `npm install`。根因链条实测为:tsdown 声明可选 peer `@vitejs/devtools: "*"` → 该包 `0.8.0`(2026-10-14 发布)的 peer 里新增 `@vitejs/devtools-vitest` → vitest 的可选 peer 是 jsdom / happy-dom / canvas,**npm 10 的 peer 解析在那里崩**,npm 11 正常。`overrides` 回退 rolldown / cac / rolldown-plugin-dts 都无效、`--omit=optional` 无效,而 `--legacy-peer-deps` 立即成功——所以崩点在 peer 解析而非某个包。修法:项目根加 `.npmrc`(`legacy-peer-deps=true`),CI 与本地开发者(npm 10)一起修好,不必改 workflow。本仓库的 peerDependencies **全部**标了 optional,peer 校验本就没有可执行对象,关掉自动 peer 解析无实际损失。
|
|
26
|
-
- **build-gate 报「client.js 与当前 src 不同源」**:lockfile 有意不入库,于是 CI 全新安装解析到 **tsdown 0.23.1 + rolldown 1.2.14**,打出的字节与入库产物不同——门禁红在一个「看着像改了 src 忘 build」的地方,真因是工具链漂了。修法:把工具链精确钉在**产出当前入库产物**的那一组(`tsdown: 0.23.0` + `overrides: rolldown 1.2.12 / rolldown-plugin-dts 0.28.6`),并在 `tsdown.config.mjs` 头部写下升级流程(改 pin 必须把新产物与 pin 放进同一个提交)。验证方式:在没有 lockfile 的干净 worktree 上全新安装,钉住后 build-gate 逐字节绿、不钉则与 CI 同样红。
|
|
27
|
-
|
|
28
|
-
**门禁:新增真 DOM 渲染/交互套件 `test/dom-render.test.mjs`**(devDependencies 加 `jsdom`,是本仓库第一个测试用第三方包;仍然离线,不联网)
|
|
29
|
-
|
|
30
|
-
- 补的缺口:client 半边此前只由 `test/panel.test.mjs` 的**替身 React**钉着——`createElement` 返回一个普通对象,`onClick` 只是对象上一个属性值,于是事件绑定、受控输入、busy 期间该不该 `disabled`、effect 与卸载的时序全都不可观察;更糟的是替身把 children 放在 `props` **旁边**,而 `SectionCard` 从 `props` 里解构 `children`——折叠体里的卡片在那个套件里**从来没被渲染过**。新套件把**构建产物 `client.js`** 挂进 jsdom 的真 document,配一套最小替身 React(hook 槽位按「路径 + 组件名」、setState 触发真重渲染、effect 在 commit 之后跑、卸载跑 cleanup),钉住:九条面板路由各自被真实点击驱动过且 payload 正确(打字经 state 回流进 `/token` 的 body、哨兵独占态、`已启用 N / M` 与勾选态一致)、`disabled` 的控件点不动、挂载即首轮轮询与 locale 订阅、切后台 `clearInterval` / 回前台补一发 / 失败退避不低于 60s、顶替与卸载真的 abort 在途请求、慢响应不覆盖快响应、写失败与契约漂移那两句话真的上屏。
|
|
31
|
-
- 阴性对照:同一批 16 条 src 变异(拆 handler、把开关值取反、混排哨兵、不 `clearInterval`、不记 `inFlight`、`ok:false` 判成功、缺键不报漂移……),`panel.test.mjs` 抓住 **0** 条,新套件抓住 **16** 条。跑法与「为什么必须带 `DSH_DOM_TARGET=src`」(产物入口看不见 src 变异,首轮曾得到 24/24 全绿的假象)见 `tools/dev/README.md`;spec 是 `tools/dev/mutate-dom-render.json`。
|
|
32
|
-
- 这一档没覆盖的部分见下一条(真 React 那一档),以及 `docs/ROADMAP.md` 的 Backlog:并发与错误边界、fake timers 下跑满 cadence 的长周期行为。
|
|
33
|
-
|
|
34
|
-
**再加一档:真 React 渲染门禁 `test/dom-render-react.test.mjs`**(devDependencies 精确钉 `react@18.3.1` + `react-dom@18.3.1`)
|
|
60
|
+
## [0.3.0] — 2026-10-16
|
|
35
61
|
|
|
36
|
-
|
|
37
|
-
- 阴性对照:同一批 3 条变异(把 `tabActive` 压平、给令牌区块塞一个不稳定 key、删掉 roster 行的 `key`),真 React 档抓 **3** 条,mini 档抓 **0** 条;其中「删 key」是被**零告警**那条抓住的,不是被顺序断言抓住(checkbox 全受控,删 key 不串位)——凡「React 自己的规矩」类缺陷都归零告警那条。spec:`tools/dev/mutate-dom-render-react.json`。
|
|
38
|
-
- **代价是版本耦合**:pin 的是宿主浏览器实际注入的那一份 React,用 `tools/dev/probe-host-react.mjs` 从 `app.asar` 读出来(2026-10-15 实测 18.3.1),套件启动时把 `React.version` 与期望值硬比,漂了就红并让人先去核对——不这么办的话,门禁会静默测着一个旧的 reconciler。产物里仍然永远没有 React(`tsdown` 的 `neverBundle` 不变),被钉的只是测试副本;这与 `types/dsh-peer-modules.d.ts`「不声明 React 形状」的立场不冲突。
|
|
39
|
-
- 两个必踩的坑写进了套件文件头(别删注释):`react-dom/client` 必须在 `window`/`document` 就位之后再 import,否则它按「没有 DOM」求值,input 事件被整条吞掉、payload 变空串,看着像被测代码坏了;受控 input 要用原型上的原生 setter 改值,否则被 React 的值追踪器判成「没有变化」。写第一版时当场踩了前者。
|
|
62
|
+
**新增「上下文窗口」全局档位**
|
|
40
63
|
|
|
41
|
-
|
|
64
|
+
- 魔搭目录 `/v1/models` 不返回任何窗口字段,此前每个模型一律按 128K 兜底声明。新增面板「上下文窗口」选择器:默认 / 128K / 256K / 512K / 1M,落盘 `provider.json`。窗口取值按三级真值链——目录真声明过就用目录值,其次是用户档位,再次是内置策展表(models.dev 交叉核对,当前目录 19 个 id 有数据),都没有才落 128K。
|
|
65
|
+
- roster 每行标出当前生效窗口(128000 诚实显示 125K,不美化);策展表来的值带 `*` 号与悬停说明(平台未亲口声明),与目录声明、用户覆盖区分开。
|
|
66
|
+
- **修复**:保存窗口覆盖后,每 `pollSeconds` 轮询都会用未打标条目重新 publish,覆盖被静默回退、适配器每轮重建。现在轮询读落盘覆盖传入;fetch 期间覆盖被改则跳过本轮。
|
|
42
67
|
|
|
43
|
-
|
|
44
|
-
- **版本守卫 fail-open**:`readStateVersion` 把 EACCES/EBUSY/EIO(以及路径被目录占住)与「文件不存在」一起折成 `null`,而写守卫只把 `null` 读作「没有东西要保护」——ADR-006「绝不覆盖读不懂的文件」在自己那一侧漏了:**存在却读不出**的文件被判为可写。现在只有 ENOENT 返回 `null`(写是恢复手段),其余 fs 错误抛给写守卫;`provider-store` / `usage-store` 据此拒写并说明理由(usage 侧复用既有的只读闩锁,文件恢复可读即复位)。钉子:`state-store.test.mjs` §6、`provider.test.mjs` §10(阴性对照:`notStrictEqual` 红)。
|
|
45
|
-
- **回滚路径漏 release**:`restorePreviousPair` 的 catch 只清 `built`/`registered`,而 `registerProviderPair` 是**先 registerAdapter 再挂目录行**——后者抛错时 `state.releaseAdapter` 已经握着一次成功的注册,于是「面板说不注册、Host 里却还在路由」。现在那条 catch 先 `release()`。钉子:`provider-publish.test.mjs` §12。
|
|
46
|
-
- **回滚成功后 `error` 不清,空转守卫形同不存在**:注册失败回滚恢复旧对之后,若恢复出来的 offer 与本次请求**相同**,`error !== null` 会让两处空转守卫永远跳过不了——每个轮询周期把同一对摘下再挂上(fix B 明确要避免的「期间在途请求可能被打断」),面板还挂着一个已不成立的错误。现在只有「请求的 offer 确实没兑现」才留 `error`。钉子:`provider-publish.test.mjs` §11(阴性对照:`actual 'transient registration failure'` / `expected null`)。
|
|
47
|
-
- **成功的 probe 被报成失败**:成功分支里 `await tokenStore.state()` 住在 try 内,凭据服务读状态一抛错就落进 catch——一次已成功的验令牌回报 `ok:false`,还往面板事件流写一条假 `error`(事件流是该面板的独家数据源)。补 `.catch(() => null)`,与失败分支同形。钉子:`routes.test.mjs` 的 probe 节(阴性对照:`actual false` / `expected true`)。
|
|
48
|
-
- **「忘掉令牌」整条丢掉 body.ok**:`postJson` 只把 HTTP/解析失败折成 `null`,域失败是 `{ok:false, error}`;`saveToken` 与 provider 写入都检查了,唯独「忘掉令牌」直接往下走。而 Host 侧 `ms-auth.forget` 的失败是**刻意传播**的(凭据文件只读时抛错,其文件头写着「谎报成功比失败更糟——用户以为凭据已删干净」),于是界面上表现为「什么都没发生」。三个写回调收敛到 `http.ts#writeFailure` 一份判据。钉子:`panel.test.mjs` §8/§8b(阴性对照:§8b 的 `match` 红)。
|
|
68
|
+
**新增对话框用量胶囊**
|
|
49
69
|
|
|
50
|
-
|
|
70
|
+
- 会话当前选中的 provider 是 `modelscope-token-plan` 时,输入框工具栏出现胶囊,正文两个读数、各标各的来源:`魔搭 · 143 魔粒 · 今日本地 7 次`。前半是官方魔粒余额,后半是本地日桶——「本地」二字是承重的,官方接口没有「今日」这一项(口径见 README「诚实声明」)。
|
|
71
|
+
- **刻意没有百分比,也没有黄色档**:魔粒余额是存量,官方未发布任何上限或低额阈值;画进度条或定阈值都是发明一个没人公布过的分母。三档只剩绿(读数是正数)、红(官方读数确实是 0)、灰(读不到)。
|
|
72
|
+
- **读不到渲染「—」,绝不渲染 0**;读失败时保留上一次数值并标「上次数据」(陈旧阈值 = `pollSeconds` × 3,默认 90s)。
|
|
73
|
+
- 非归属会话一个请求都不发;切模型不会踩 hook 顺序。
|
|
51
74
|
|
|
52
|
-
|
|
53
|
-
- 令牌输入框缺 `aria-label`、ToggleSwitch 缺 `busyLabel`:屏幕阅读器读不出「正在切换」。补两条 i18n 键(zh/en 各 +1)。
|
|
54
|
-
- `modelscopeModelUrl` 用 `encodeURI` 不转义 `#`/`?`,模型 id 含这些字符时 URL 语义被破坏。改为分段 `encodeURIComponent`。
|
|
55
|
-
- `modelCount`/`enabledCount` 的 `typeof === "number"` 对 NaN 也返回 true,面板会显示「NaN 个模型」。补 `Number.isFinite`。
|
|
56
|
-
- 嵌套降层:`swapRegistration` 的 rollback 路径从 4 层 try/catch 降到 2 层(提取 `restorePreviousPair`);`publishProviderOnce` 两处空转守卫的重复签名比对提取为 `offerUnchanged()`;`runPoll` 的 fallback 逻辑提取为 `currentEnabledIds()`。
|
|
57
|
-
- 新增 `docs.test.mjs` §11 引用有效性门禁:扫描 `src/` 与 `test/` 里所有 `test/*.test.mjs` 引用,文件不存在即失败。补 HIDE_ALL_MODELS 双端交叉断言与 locale key 集合一致性断言。三处恒真/死断言换成真断言。
|
|
58
|
-
- 六处 `test/peer-contract.test.mjs` 虚假引用全删(文件从未存在,但注释声称 peer 契约被它钉死),改为诚实的缺口声明并记入 ROADMAP backlog。`find-fake-gates.mjs` 误报率从 200+ 降到 3 条。
|
|
75
|
+
**六处写守卫与注册状态机缺陷**(每处都有用户可见后果)
|
|
59
76
|
|
|
60
|
-
|
|
77
|
+
- **面板与 Host 对「正在提供什么」各说一套**:操作者按提示删掉 `provider.json`(或清单被保存为空)后,面板说 `allowed:"all"` 且数上全部模型,轮询却继续按内存里的旧清单注册。
|
|
78
|
+
- **版本守卫 fail-open**:**存在却读不出的** `provider.json` / `usage.json` 被判为可写,违反 ADR-006「绝不覆盖读不懂的文件」;现在只有文件不存在才返回 `null`,其余 fs 错误一律拒写并说明理由。
|
|
79
|
+
- **回滚路径漏 release**:注册回滚时适配器已注册成功,「面板说不注册、Host 里却还在路由」。
|
|
80
|
+
- **回滚成功后 `error` 不清**:每个轮询周期把同一对摘下再挂上(期间在途请求可能被打断),面板还挂着一个已不成立的错误。
|
|
81
|
+
- **成功的验令牌被报成失败**:凭据服务读状态一抛错就落进 catch,回报 `ok:false` 并往事件流写一条假 `error`。
|
|
82
|
+
- **「忘掉令牌」整条丢掉 `body.ok`**:凭据文件只读时删除失败,界面上却表现为「什么都没发生」——而该失败是刻意传播的(谎报成功比失败更糟)。
|
|
61
83
|
|
|
62
|
-
|
|
63
|
-
- `!llmAvailable` 分支漏清 `state.built`:残留 `built` 成为**下一次** publish 的回滚目标,注册失败时回滚会把一只 release 已调用过的适配器重新挂上 Host——回滚只在别处已出错时才跑,是最坏的发现时机。改为统一路由 `unregister`(与 `dsh-connect-agnes-token-plan` 对齐)。钉子:`provider-publish.test.mjs` §10(阴性对照实测:旧代码 `state.built` 仍是 `{adapter…}`)。
|
|
84
|
+
**修复发布状态机的两处自伤**
|
|
64
85
|
|
|
65
|
-
|
|
86
|
+
- 工厂加载的 rejection 被 memo 死:一次失败的 `import()` 把错误钉在槽里直到进程结束,此后 provider 再也注册不上,**照面板给的补救提示修好依然无效、必须重启 Host**。现在只 memo 成功,失败清槽、下一次 publish 真重试。
|
|
87
|
+
- `!llmAvailable` 分支漏清 `state.built`:残留 `built` 成为下一次 publish 的回滚目标,会把一只已 release 的适配器重新挂上 Host。
|
|
66
88
|
|
|
67
|
-
|
|
68
|
-
- **同类漂移的实证**:同一个坑在 `doctor.test.mjs` 里被手工 `delete` 处理过,`routes.test.mjs` 没有——「每个测试记得清环境」这条纪律在同一个仓库里已经出现了两种下场。修法因此不选"补一句 delete",而是给 `HostDeps` 加 `env` **注入缝**(与既有 `fetchImpl` / `getLlm` / `emit` 同列的既有标准测试缝):产品侧不传、落回 `process.env` 零漂移,测试侧传 `{}` 则宿主环境彻底进不了判定。
|
|
69
|
-
- **门禁**:新增 `test/env-hermetic.test.mjs`(已进 `npm test`),两层:①运行时对照——断言「默认 env 源确实能读到宿主令牌」(把泄漏路径的存在钉成事实,删掉它就没人知道第二条在防什么)与「注入 env 后看不见宿主令牌」「注入的 env 仍可用」(缝不是恒空的桩);②源码扫描——任何 import `apply()` 的测试必须隔离 env,忘了就在这里红并指明文件。**守纪律的是文件,不是记性。**
|
|
70
|
-
- 阴性对照:①临时造一个忘了隔离的测试文件 → 门禁列出它(`zz-negative-probe.test.mjs`);②删掉 `index.ts` 的 `env: deps.env` 转发 → §1 立刻红在「注入 env 缝后,宿主令牌不得可见」。两次都还原后全绿。
|
|
89
|
+
**「读不到」不再被画成 0**
|
|
71
90
|
|
|
72
|
-
|
|
91
|
+
- `usage.json` 不可读时,面板「额度」头条从「今日 0 次」变成「—」,并在面板上方给出原因。此前「读不到 ≠ 0」的语义在三个文件里写了三遍,一次都没兑现:真实的读失败被吞成空载荷再折成 0,而「读不到画成 0」会让操作者以为今天没调用过。
|
|
92
|
+
- 判据改为「文件在不在」:文件**不存在** = 真实的 0;**在却读不出来**(损坏 / 形状不对 / 未知版本 / 不可读)= `null`。
|
|
73
93
|
|
|
74
|
-
|
|
75
|
-
- **修法**:判据换成「**文件在不在**」。`usage.json` **不存在** = 真实的 0(这台 Host 从没调用过,那是已知答案);**文件在却读不出来**(损坏 / 形状不对 / 更新构建写的版本 / 存在却不可读)= `null`;存在与否由 `stat` 判定,且这发 IO 只落在异常路径上(正常循环被读缓存短路,零额外开销)。
|
|
76
|
-
- **用户可见**:`usage.json` 不可读时,面板「额度」头条从「今日 0 次」变成「—」,并在面板上方 `shapeWarnings` 里给出原因(此前至少还有一行警告,现在数字本身也不再伪造)。**把「读不到」画成 0 会让操作者以为今天没调用过——这正是那份注释说的错误判断。**
|
|
77
|
-
- **既有的旧期望随之更正**:`usage-store.test.mjs` 曾把「损坏 → 0」「形状坏 → 0」「未知版本 → 0」当成"不炸"的证据——它们证明的是没崩,不是答案是 0。现在三处都断言 `null`,并新增反向对照(文件不存在仍是真实的 0,且不带任何痕迹)。
|
|
78
|
-
- **端到端钉子**:`routes.test.mjs` 新增一节——用**真实挂载的插件** + 磁盘上一份 `version: 99` 的 `usage.json`(里面写着今日 7 次),断言快照头条是 `null` 而不是那 7 次、也不是 0,且 `usage-state:` 警告在场。
|
|
79
|
-
- 阴性对照:把 `daily()` 退回恒 0 后,`usage-store.test.mjs` 与 `routes.test.mjs` 的新断言双双红(actual 0 / expected null),还原后全绿。
|
|
80
|
-
- **留给 backlog**(本次有意不改,避免膨胀):`perModelToday()` 与 `trend()` 还没跟上同一套语义——它们仍把读不到渲染成空数组 / 全零桶。补齐要动 `wire.ts` 的 `TrendBucket` 形状与 client 渲染,属于契约变更,另起一轮。
|
|
94
|
+
**令牌状态补上真实结论**
|
|
81
95
|
|
|
82
|
-
|
|
96
|
+
- `valid` / `checkedAt` 此前在 Host 侧硬编码为 `null`,从无生产者,面板只能一直显示「未校验」。现在由零额度探针产出:200/400 → 有效,401/403 → 无效,**429 / 额度耗尽 / 上游 5xx 一律不写**(它们回答的是「上游忙不忙」,不是「令牌好不好」)。
|
|
97
|
+
- 换令牌时清掉旧结论;结论只活在本进程,重启回到「未校验」。
|
|
98
|
+
- 新增「无效」态:`valid: false` 此前会渲染成「未校验」——把已知的坏说成不知道。探针出结论后面板立即刷新,不再等下一个 30s 轮询。
|
|
83
99
|
|
|
84
|
-
|
|
85
|
-
- **修法**:三个接上(不传 = 落回 `ctx`,产品行为零漂移);`pollMs` **删除**(留着只会让人以为它能用——与当年移除 `dailyQuotaTotal` 同一条纪律)。反向也补:`loadToolsModule` / `drawFetch` 实际在用却从未声明(靠 `deps: any` 绕过类型),现已写进接口。
|
|
86
|
-
- **门禁**:新增 `test/deps-wiring.test.mjs`(已进 `npm test`)——①双向扫描:`HostDeps` 每个字段都必须在 `src/` 里被 `deps.<name>` 读到,反向(读了却没声明)同样红;②运行时:注入的 `getLlm` / `credentials` 必须真的生效。**两层各有各的阴性对照**:拆掉接线 → §1 红在「声明了没人读」;读到却不用(只当布尔判断)→ §2 红在「注入的 getLlm 必须真的被调用」——只有两层都在,才同时堵住「没接线」与「接了没用」。
|
|
87
|
-
- **顺带修掉 `docs.test.mjs` §10 的一处误报**:反重复规则的 needle 是通用词「静默忽略」,任何注释一用这四个字就被判成在重述 `reasoning_effort` 那条事实(实测误伤 `index.ts` 里一句关于 deps 缝的注释)。收窄为归属文件原文的指纹「无法区分『参数生效』与『参数被静默忽略』」——中途试过「无法区分」,它同样是通用词(误伤 `docs/SPIKE-image-gen.md`)。阴性对照:插入一句真重述,门禁仍能抓住。
|
|
100
|
+
**可用性与无障碍**
|
|
88
101
|
|
|
89
|
-
|
|
102
|
+
- tab 键盘焦点不可见:内联 `outline: "none"` 压掉了外壳的焦点环,改为 `outlineOffset: -2`。
|
|
103
|
+
- 令牌输入框补 `aria-label`、ToggleSwitch 补 `busyLabel`(屏幕阅读器此前读不出「正在切换」)。
|
|
104
|
+
- 模型 id 含 `#` / `?` 时详情页外链 URL 语义被破坏,改为分段 `encodeURIComponent`。
|
|
105
|
+
- 面板会显示「NaN 个模型」,补 `Number.isFinite`。
|
|
90
106
|
|
|
91
|
-
|
|
92
|
-
- **修法**:唯一的生产者是零额度探针(`noteValidity`)。判据按证据分,不按成败分:200/400 → `true`(鉴权过了);401/403 → `false`(令牌坏的证据);**429 / 额度耗尽 / 上游 5xx 一律不写**——它们回答的是"上游现在忙不忙",不是"这把令牌好不好",记成 false 就是把限频说成令牌坏(429 分诊那条修正的同一个误判换个地方)。这几种留着上一次的结论,那是已知的最好答案。
|
|
93
|
-
- **失效时机**:`save` / `forget` 换令牌时清掉旧结论——一个关于**别的值**的判断不许继续替新令牌说话(与 `ephemeral` 那条修正同一条纪律)。只活在本进程:重启即回到"未校验",那正是 `null` 的语义。
|
|
94
|
-
- **三态补齐**:`valid: false` 此前会渲染成「未校验」——把已知的坏说成不知道,与"读不到画成 0"同一类谎。新增 `validity.bad`(zh「无效」/ en "invalid"),`validity.no`(「未校验」)从此只服务 `null`。
|
|
95
|
-
- **面板**:探针结论写进快照后立刻 `load()` 一次,否则那行「状态:…(校验 …)」要等下一个 30s 轮询才从"未校验"变成答案,而按钮下方明明已经写着结论了。
|
|
96
|
-
- **钉子**:`credentials.test.mjs` §6(三态与换手失效)、`routes.test.mjs` 新增两节——①错误码映射(401 写 false;429/额度/5xx **一个都不写**,阴性对照:去掉错误码判断后 `actual [false] / expected []`);②**端到端**:真实挂载的插件,存令牌 → 桩 200 探针 → 快照 `token.valid === true`,桩 401 → `false`,再存一次别的令牌 → 回到 `null`。
|
|
97
|
-
- 阴性对照:把 `state()` 退回硬编码 null → 两处新断言双双红(actual null / expected true);还原后全绿。
|
|
107
|
+
**工程**
|
|
98
108
|
|
|
99
|
-
|
|
109
|
+
- 依赖安装不再被 npm 10 的自动 peer 解析卡死;构建工具链精确钉住版本(build-gate 逐字节比对产物)。
|
|
110
|
+
- CI 门禁补真 DOM 与真 React 两档渲染套件;测试环境隔离宿主令牌,测试缝接线双向扫描。细节与取证见下方指针。
|
|
100
111
|
|
|
101
|
-
|
|
102
|
-
|
|
112
|
+
> **实施过程(病灶、取证、判据、阴性对照)全部在
|
|
113
|
+
> [docs/archive/2026-10-16-0.3.0-process.md](docs/archive/2026-10-16-0.3.0-process.md)**——那几十轮里
|
|
114
|
+
> 不少是踩过的坑,照着做能绕开。
|
|
103
115
|
|
|
104
116
|
## [0.2.0] — 2026-10-05
|
|
105
117
|
|
package/README.md
CHANGED
|
@@ -24,6 +24,11 @@
|
|
|
24
24
|
tools registry 没有 unregister)。实测协议(异步两段式、必需头、提交态不可信、
|
|
25
25
|
计费与耗时口径)见 [docs/SPIKE-image-gen.md](docs/SPIKE-image-gen.md)。
|
|
26
26
|
|
|
27
|
+
工具本身也是**两段式**的(v0.4 起):一次调用只等 `drawWaitMs`(默认 90 秒)——
|
|
28
|
+
图好了直接返回,没好就返回 `status: pending` 与一个 `jobId`,**带上它再调用即可
|
|
29
|
+
继续查询**(只查询、不重复出图)。上游排队实测 1–15 分钟,此前一次调用会一直
|
|
30
|
+
await 到出图为止,整轮对话都得陪着等。
|
|
31
|
+
|
|
27
32
|
同一 tab 下方另有**「上下文窗口」**选择器(默认/128K/256K/512K/1M):魔搭目录
|
|
28
33
|
`/v1/models` 不返回窗口字段,Host 侧按三级真值链给每个模型定窗口——目录真声明了
|
|
29
34
|
就用目录值(不受任何下层影响);用户在这里选一个档位即对**目录没声明窗口的模型**
|
package/client.js
CHANGED
|
@@ -11,6 +11,30 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
11
11
|
};
|
|
12
12
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
13
13
|
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/shared/wire.ts
|
|
16
|
+
var HIDE_ALL_MODELS, SNAPSHOT_REQUIRED_KEYS;
|
|
17
|
+
var init_wire = __esmMin((() => {
|
|
18
|
+
HIDE_ALL_MODELS = "__hide_all__";
|
|
19
|
+
SNAPSHOT_REQUIRED_KEYS = Object.freeze([
|
|
20
|
+
"ok",
|
|
21
|
+
"name",
|
|
22
|
+
"version",
|
|
23
|
+
"now",
|
|
24
|
+
"pollSeconds",
|
|
25
|
+
"cacheSeconds",
|
|
26
|
+
"token",
|
|
27
|
+
"balance",
|
|
28
|
+
"quota",
|
|
29
|
+
"events",
|
|
30
|
+
"trend",
|
|
31
|
+
"models",
|
|
32
|
+
"provider",
|
|
33
|
+
"shapeWarnings",
|
|
34
|
+
"quotaError"
|
|
35
|
+
]);
|
|
36
|
+
}));
|
|
37
|
+
|
|
14
38
|
//#endregion
|
|
15
39
|
//#region src/client/const.ts
|
|
16
40
|
function modelscopeModelUrl(id) {
|
|
@@ -39,8 +63,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
39
63
|
if (tokens >= 1024) return `${Math.round(tokens / 1024)}K`;
|
|
40
64
|
return String(tokens);
|
|
41
65
|
}
|
|
42
|
-
var NS, PANEL_ID, SNAPSHOT_PATH, MODELS_PATH, TOKEN_PATH, TOKEN_FORGET_PATH, PROBE_PATH, PROVIDER_PATH, PROVIDER_ROSTER_PATH, PROVIDER_CONTEXT_PATH, PROVIDER_RESET_PATH, DRAW_PATH, DRAW_RESET_PATH,
|
|
66
|
+
var NS, PANEL_ID, SNAPSHOT_PATH, MODELS_PATH, TOKEN_PATH, TOKEN_FORGET_PATH, PROBE_PATH, PROVIDER_PATH, PROVIDER_ROSTER_PATH, PROVIDER_CONTEXT_PATH, PROVIDER_RESET_PATH, DRAW_PATH, DRAW_RESET_PATH, MODELSCOPE_TOKEN_URL, MODELSCOPE_USAGE_URL, MODELSCOPE_MODEL_URL_BASE, FEATURED_OWNERS;
|
|
43
67
|
var init_const = __esmMin((() => {
|
|
68
|
+
init_wire();
|
|
44
69
|
NS = "dsh-connect-modelscope-token-plan";
|
|
45
70
|
PANEL_ID = NS;
|
|
46
71
|
SNAPSHOT_PATH = `/api/${NS}/snapshot`;
|
|
@@ -54,7 +79,6 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
54
79
|
PROVIDER_RESET_PATH = `/api/${NS}/provider/reset`;
|
|
55
80
|
DRAW_PATH = `/api/${NS}/draw`;
|
|
56
81
|
DRAW_RESET_PATH = `/api/${NS}/draw/reset`;
|
|
57
|
-
HIDE_ALL_MODELS = "__hide_all__";
|
|
58
82
|
MODELSCOPE_TOKEN_URL = "https://modelscope.cn/my/myaccesstoken";
|
|
59
83
|
MODELSCOPE_USAGE_URL = "https://modelscope.cn/magicube/usage?tab=consume";
|
|
60
84
|
MODELSCOPE_MODEL_URL_BASE = "https://www.modelscope.cn/models";
|
|
@@ -315,7 +339,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
315
339
|
|
|
316
340
|
//#endregion
|
|
317
341
|
//#region src/client/format.ts
|
|
318
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* Time and number formatters(与 sensenova 的 format.ts 同源、按需裁剪——去注释后
|
|
344
|
+
* 代码行重叠 73%,与 agnes 那份已分家(45%)。2026-10-16 实测,复核
|
|
345
|
+
* `node tools/dev/cross-repo-drift.mjs`)。
|
|
346
|
+
*/
|
|
319
347
|
/**
|
|
320
348
|
* Host 声明的 cadence(秒)转毫秒;无可用数字时回退。Host 在源头已 clamp,
|
|
321
349
|
* 面板不做第二次 opinion;亚秒值向上取整到 1s(setInterval 的 0 = 尽快)。
|
|
@@ -768,33 +796,46 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
768
796
|
}));
|
|
769
797
|
|
|
770
798
|
//#endregion
|
|
771
|
-
//#region src/shared/
|
|
772
|
-
var
|
|
773
|
-
var
|
|
774
|
-
|
|
775
|
-
"
|
|
776
|
-
"
|
|
777
|
-
"
|
|
778
|
-
"
|
|
779
|
-
"
|
|
780
|
-
"
|
|
781
|
-
"
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
799
|
+
//#region src/shared/codes.ts
|
|
800
|
+
var CODE;
|
|
801
|
+
var init_codes = __esmMin((() => {
|
|
802
|
+
CODE = Object.freeze({
|
|
803
|
+
CONFIG_ERROR: "config_error",
|
|
804
|
+
NETWORK_ERROR: "network_error",
|
|
805
|
+
TIMEOUT_ERROR: "timeout_error",
|
|
806
|
+
AUTH_ERROR: "auth_error",
|
|
807
|
+
RATE_LIMITED: "rate_limited",
|
|
808
|
+
QUOTA_EXCEEDED: "quota_exceeded",
|
|
809
|
+
UPSTREAM_ERROR: "upstream_error",
|
|
810
|
+
/**
|
|
811
|
+
* 插件自身抛错(聚合器、状态层、路由之外的意外)。
|
|
812
|
+
*
|
|
813
|
+
* 曾经只作为 `soft()` / `failureCode()` 的兜底字符串出现在快照里,没进这张
|
|
814
|
+
* 表——于是 Client 的 `GUIDANCE_BY_CODE` 也没法映射它(那份映射被测试钉住
|
|
815
|
+
* 「每个键必须在 CODE 里」),最需要人看的内部错误反而没有引导文案。
|
|
816
|
+
* 正式收进码表,两端从此同一份真源。
|
|
817
|
+
*/
|
|
818
|
+
INTERNAL_ERROR: "internal_error",
|
|
819
|
+
/**
|
|
820
|
+
* Client 自产码:Host 响应无法解读成快照(非 JSON / 缺 ok 字段)时,
|
|
821
|
+
* 面板用它走引导文案,而不是把裸英文串("unexpected payload")上屏。
|
|
822
|
+
*
|
|
823
|
+
* 本表是 `GUIDANCE_BY_CODE` 的键集真源——新增 client 码必须同时进
|
|
824
|
+
* 这张表,两侧才同步。
|
|
825
|
+
*/
|
|
826
|
+
PAYLOAD_ERROR: "payload_error"
|
|
827
|
+
});
|
|
791
828
|
}));
|
|
792
829
|
|
|
793
830
|
//#endregion
|
|
794
831
|
//#region src/client/snapshot.ts
|
|
795
832
|
/**
|
|
796
|
-
* 缺失块的空形状,**逐字对齐 Host 的降级实现**(`snapshot-aggregate.ts`
|
|
797
|
-
* `
|
|
833
|
+
* 缺失块的空形状,**逐字对齐 Host 的降级实现**(`snapshot-aggregate.ts` 各
|
|
834
|
+
* `soft()` 失败分支、TokenStatus 的失败分支)。
|
|
835
|
+
*
|
|
836
|
+
* `provider` 块现在是**省略**而不是由 Host 塞一个降级块(2026-10-17):Host 那份
|
|
837
|
+
* 降级形状「看起来就是一个答案」,面板据此认为块在、不再走 `GET /provider` 兜底,
|
|
838
|
+
* 于是只能拿编出来的值渲染。省略之后,兜底真的跑起来,降级形状只剩这里一份。
|
|
798
839
|
*
|
|
799
840
|
* 这里的每一条都是「Host 那一路失败时真的产出的形状」,不是随手捏的:面板渲染
|
|
800
841
|
* 的每一个字段都必须有一个与 Host 同构的空值,否则补出来的空块自己就会炸——
|
|
@@ -862,7 +903,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
862
903
|
function interpretSnapshot(body) {
|
|
863
904
|
const payload = body;
|
|
864
905
|
if (payload && payload.ok === false) {
|
|
865
|
-
const code = typeof payload.code === "string" && payload.code !== "" ? payload.code :
|
|
906
|
+
const code = typeof payload.code === "string" && payload.code !== "" ? payload.code : CODE.PAYLOAD_ERROR;
|
|
866
907
|
return {
|
|
867
908
|
data: null,
|
|
868
909
|
error: {
|
|
@@ -875,7 +916,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
875
916
|
data: null,
|
|
876
917
|
error: {
|
|
877
918
|
message: null,
|
|
878
|
-
code:
|
|
919
|
+
code: CODE.PAYLOAD_ERROR
|
|
879
920
|
}
|
|
880
921
|
};
|
|
881
922
|
const raw = payload;
|
|
@@ -984,11 +1025,11 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
984
1025
|
shapeWarnings
|
|
985
1026
|
};
|
|
986
1027
|
}
|
|
987
|
-
var
|
|
1028
|
+
var DEGRADED_PROVIDER, DEGRADED_DRAW, GUIDANCE_BY_CODE, FORM_EXCLUDED_CODES;
|
|
988
1029
|
var init_snapshot = __esmMin((() => {
|
|
989
1030
|
init_format();
|
|
990
1031
|
init_wire();
|
|
991
|
-
|
|
1032
|
+
init_codes();
|
|
992
1033
|
DEGRADED_PROVIDER = Object.freeze({
|
|
993
1034
|
enabled: false,
|
|
994
1035
|
source: "config",
|
|
@@ -1113,6 +1154,73 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1113
1154
|
TRAVEL = 13;
|
|
1114
1155
|
}));
|
|
1115
1156
|
|
|
1157
|
+
//#endregion
|
|
1158
|
+
//#region src/shared/allow-list.ts
|
|
1159
|
+
/**
|
|
1160
|
+
* 把线编码的 `enabledIds` 解析成联合——**全插件只有这一处解释**。
|
|
1161
|
+
*
|
|
1162
|
+
* 判据(与旧 `resolveAllowedList` 的承诺一致,且现在 `filterByEnabled` 等
|
|
1163
|
+
* 消费者也真的按它做):
|
|
1164
|
+
* 1. 非数组或空数组 → `all`(新装没做过选择,仍提供全部);
|
|
1165
|
+
* 2. 含哨兵(哪怕还混着别的 id)→ `none`(哨兵是独占态,优先于任何真实 id);
|
|
1166
|
+
* 3. 其余非空清单 → `list`(严格白名单;条目滤掉非字符串与空串、
|
|
1167
|
+
* 去重保序——落盘前 `provider-store` 已归一遍,这里是解释不是归一)。
|
|
1168
|
+
*
|
|
1169
|
+
* 注意 `[""]` 这类「滤完什么都不剩」的脏清单归 `list` 且 `ids: []`:过滤
|
|
1170
|
+
* 结果为空,与旧行为一致(它匹配不到任何真实 id)。
|
|
1171
|
+
* @param {unknown} enabledIds - 线编码的允许清单。
|
|
1172
|
+
* @returns {AllowedList} 联合。
|
|
1173
|
+
*/
|
|
1174
|
+
function parseAllowedList(enabledIds) {
|
|
1175
|
+
if (!Array.isArray(enabledIds) || enabledIds.length === 0) return { mode: "all" };
|
|
1176
|
+
if (enabledIds.includes("__hide_all__")) return { mode: "none" };
|
|
1177
|
+
const ids = [];
|
|
1178
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1179
|
+
for (const entry of enabledIds) {
|
|
1180
|
+
const id = typeof entry === "string" ? entry : "";
|
|
1181
|
+
if (id === "" || seen.has(id)) continue;
|
|
1182
|
+
seen.add(id);
|
|
1183
|
+
ids.push(id);
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
mode: "list",
|
|
1187
|
+
ids
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* 面板草稿 → 落盘清单的归一:哨兵独占态。
|
|
1192
|
+
*
|
|
1193
|
+
* 草稿含哨兵(「全部隐藏」)时落盘值只保留哨兵——不能让它与真实 id 混排:
|
|
1194
|
+
* Host 把混排判 `none`,而面板会把混排里的真实 id 画成勾中,「已启用 N」
|
|
1195
|
+
* 与实际提供 0 个撒谎(踩过,见 cards.ts 的注释)。其余情况剥掉哨兵、
|
|
1196
|
+
* 滤掉空串后原样落盘。
|
|
1197
|
+
* @param {readonly unknown[]} draft - 面板勾选的草稿。
|
|
1198
|
+
* @returns {string[]} 落盘的 `enabledIds`。
|
|
1199
|
+
*/
|
|
1200
|
+
function normalizeRosterSave(draft) {
|
|
1201
|
+
return rosterSaveOf(parseAllowedList(draft));
|
|
1202
|
+
}
|
|
1203
|
+
/**
|
|
1204
|
+
* 联合 → 落盘清单({@link parseAllowedList} 的逆运算):`none` 序列化成
|
|
1205
|
+
* 哨兵清单,`all` 序列化成空清单(「不过滤」的拼写),`list` 原样带出 id。
|
|
1206
|
+
* @param {AllowedList} list - 联合。
|
|
1207
|
+
* @returns {string[]} 线编码的 `enabledIds`。
|
|
1208
|
+
*/
|
|
1209
|
+
function rosterSaveOf(list) {
|
|
1210
|
+
switch (list.mode) {
|
|
1211
|
+
case "all": return [];
|
|
1212
|
+
case "none": return [HIDE_ALL_MODELS];
|
|
1213
|
+
case "list": return [...list.ids];
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
/** 「全部隐藏」的落盘清单({@link rosterSaveOf} 的 `none` 特例;每次新数组)。 */
|
|
1217
|
+
function hideAllRoster() {
|
|
1218
|
+
return rosterSaveOf({ mode: "none" });
|
|
1219
|
+
}
|
|
1220
|
+
var init_allow_list = __esmMin((() => {
|
|
1221
|
+
init_wire();
|
|
1222
|
+
}));
|
|
1223
|
+
|
|
1116
1224
|
//#endregion
|
|
1117
1225
|
//#region src/client/cards.ts
|
|
1118
1226
|
/** 可折叠 section 卡:全宽头部按钮 + 旋转 chevron;open/onToggle 由 props 进。 */
|
|
@@ -1324,13 +1432,17 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1324
1432
|
const hostIds = status.enabledIds;
|
|
1325
1433
|
const hostKey = useMemo(() => JSON.stringify(hostIds), [hostIds]);
|
|
1326
1434
|
const [draft, setDraft] = useState(() => hostIds.slice());
|
|
1327
|
-
const hideAllMode = draft.
|
|
1435
|
+
const hideAllMode = parseAllowedList(draft).mode === "none";
|
|
1328
1436
|
useEffect(() => {
|
|
1329
|
-
setDraft(
|
|
1437
|
+
setDraft(normalizeRosterSave(hostIds));
|
|
1330
1438
|
}, [hostKey]);
|
|
1331
1439
|
const roster = status.roster;
|
|
1332
1440
|
const rosterIds = roster.map((row) => row.id);
|
|
1333
|
-
const toggleOne = (id) => setDraft((current) =>
|
|
1441
|
+
const toggleOne = (id) => setDraft((current) => {
|
|
1442
|
+
const ids = parseAllowedList(current);
|
|
1443
|
+
const kept = ids.mode === "list" ? [...ids.ids] : [];
|
|
1444
|
+
return kept.includes(id) ? kept.filter((x) => x !== id) : [...kept, id];
|
|
1445
|
+
});
|
|
1334
1446
|
const ticked = hideAllMode ? 0 : roster.filter((row) => draft.includes(row.id)).length;
|
|
1335
1447
|
const degraded = status.error === "unavailable";
|
|
1336
1448
|
let statusNode;
|
|
@@ -1459,8 +1571,9 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1459
1571
|
style: S.rosterBulk,
|
|
1460
1572
|
disabled: busy,
|
|
1461
1573
|
onClick: () => {
|
|
1462
|
-
|
|
1463
|
-
|
|
1574
|
+
const none = hideAllRoster();
|
|
1575
|
+
setDraft(none);
|
|
1576
|
+
onSaveList(none);
|
|
1464
1577
|
}
|
|
1465
1578
|
}, tt("provider.hideAll")), h("button", {
|
|
1466
1579
|
type: "button",
|
|
@@ -1648,6 +1761,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1648
1761
|
init_const();
|
|
1649
1762
|
init_snapshot();
|
|
1650
1763
|
init_toggle_switch();
|
|
1764
|
+
init_allow_list();
|
|
1651
1765
|
EVENT_KIND_KEY = {
|
|
1652
1766
|
quota: "events.quota",
|
|
1653
1767
|
rate_limit: "events.rate_limit",
|
|
@@ -1960,8 +2074,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
1960
2074
|
]);
|
|
1961
2075
|
const toggleProvider = useCallback((enabled) => void runProviderWrite(PROVIDER_PATH, { enabled }), [runProviderWrite]);
|
|
1962
2076
|
const saveRoster = useCallback((enabledIds) => {
|
|
1963
|
-
|
|
1964
|
-
runProviderWrite(PROVIDER_ROSTER_PATH, { enabledIds: normalized });
|
|
2077
|
+
runProviderWrite(PROVIDER_ROSTER_PATH, { enabledIds: normalizeRosterSave(enabledIds) });
|
|
1965
2078
|
}, [runProviderWrite]);
|
|
1966
2079
|
const resetProvider = useCallback(() => void runProviderWrite(PROVIDER_RESET_PATH, {}), [runProviderWrite]);
|
|
1967
2080
|
const saveContextWindow = useCallback((contextWindow) => {
|
|
@@ -2186,6 +2299,7 @@ var dsh_connect_modelscope_token_plan_client = (function() {
|
|
|
2186
2299
|
init_use_snapshot_polling();
|
|
2187
2300
|
init_runtime();
|
|
2188
2301
|
init_styles();
|
|
2302
|
+
init_allow_list();
|
|
2189
2303
|
}));
|
|
2190
2304
|
|
|
2191
2305
|
//#endregion
|
package/cordis.patch.yml
CHANGED
|
@@ -81,5 +81,9 @@
|
|
|
81
81
|
# 别调小:实测最慢一次 859s 才成功,而超时放弃**不等于**任务死亡
|
|
82
82
|
# (上游会继续跑),把预算压到 5 分钟曾实测击穿。
|
|
83
83
|
drawTimeoutMs: 900000
|
|
84
|
+
# 单次出图**工具调用**愿意等的时间(毫秒)。与上面那条是两件事:那是
|
|
85
|
+
# 「这张图值不值得继续等」的总放弃线,这是「一轮对话被占住多久」。
|
|
86
|
+
# 等不到就返回 pending + jobId,agent 带 jobId 再调用即可续查。
|
|
87
|
+
drawWaitMs: 90000
|
|
84
88
|
# 追加可信 Host 名(默认 localhost / 127.0.0.1 / ::1,只增不替)。
|
|
85
89
|
# allowedHosts: []
|