@eddyskywalker/dsh-chatgpt-subscription 0.24.3 → 0.24.5

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 CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.24.5 - 2026-10-16
6
+ - **[推理塌缩守卫] Agent 先于守卫创建时注册表两条读取路径同时失效:熔断生效但续写从未排队,回合被截断后静默结束**
7
+ - **现象**:对话在思考中途**没有任何报错地突然结束**,界面上什么都不显示。
8
+ - **触发条件(判别实验实测,非推断)**:Agent 注册表由**兄弟 fiber** `@deepseek-ai/dsh-agent` 提供,插件 `inject` 未声明 `agents`,Cordis 对未声明的服务属性读取**直接抛** `cannot get property "agents" without inject`。守卫有两条读取路径(`ctx.agents` 属性、`agent/created` 映射表),而**当 Agent 在守卫装载之前就已创建**时——插件 HMR 重载、或激活晚于会话建立,本 profile 的 `hmr` 是启用的——映射表为空,属性读取又抛异常,**两条路同时断**:`resolveAgent` 返回 `undefined`,`breakOff` 在**第一行** `if (agent === undefined) return` 就返回,**熔断(`return` 截断流)已发生,而 `cancel` 与 `steer` 都没发生**。截断的流没有 finish chunk,harness 的 `assembler.finish` 把「没有 finish」默认为 `{kind:'stop'}`,`agent-loop` 因此**当成正常说完**(既不报错也不重试),从用户视角就是「无预兆、无反馈地停了」。
9
+ - **为什么之前没被发现**:守卫自己的测试假 ctx **直接给 `agents` 属性**,声明与否都能过;且**另一条时序是好的**——判别实验证实,Agent 在守卫装载**之后**才创建时,`agent/created` 映射表正常兜住(原代码 `cancels=1, steers=1`)。所以故障只在「先建 Agent、后装守卫」这个窗口出现,正是插件一旦重载就必然落入的那一个。
10
+ - **改法(`reasoning-collapse-guard/index.ts`,一处修复全线路受益)**:读注册表改为**先走 `ctx.get('agents')`**(直读服务存储,无需 `inject` 声明,因此不受时序影响),属性读取保留为回退,另加服务形状校验 `asRegistry`。**没有采用给 `inject` 加 `agents`**——那会让插件在**不提供该服务**的宿主上永不激活(本仓库明令「旧代际是要求而非回退」),而 `ctx.get` 在服务缺失时返回 `undefined`,由属性回退与 `agent/created` 映射表兜住,两个方向都不牺牲。
11
+ - **验证**:`npx tsc -b --force` 与测试树 tsc 均 0 错误;全量 **226 passed | 1 skipped(227 文件)、3745 passed | 7 skipped、0 failed**。新增 `test/reasoning-collapse-guard-registry.test.ts` 以**真实 Cordis 边界**建模(`agents` 属性按 Cordis 语义抛错、`ctx.get` 作答):断言熔断同时**真的 cancel + steer 各一次**。**承重已双向检查(负对照)**:在「先建 Agent、后装守卫」时序下,还原 `ctx.get` 那一路立刻复现 `cancels=0, steers=0` 并使新用例失败;恢复后重新全绿。
12
+ - **附(两处经实测否证的判断,故未改动)**:① 社区 PR 称 `z.union([z.object({}), z.const(false)])` 会 strip 掉 `maxBreaksPerTurn`/`cooldownMs`;以本机 schemastery 3.18.4 实测这些键**被完整保留**(`{"maxBreaksPerTurn":5,"cooldownMs":1000}`),`Config` 无需改动。② 曾怀疑 `preparedCall.stream()` 会绕过 `llm/stream` 瀑布使守卫失位;实测 `prepareCall.stream` 内部同样走该瀑布,**不成立**。
13
+ - **未解决的**:用户报告 MiniMax 静默、而 workbuddy 的 deepseek 能正常 steer,本机全部可读会话存档(8 个)中**未出现任何守卫熔断痕迹**(`interrupted` 与续写 notice 均为 0),因此该差异**尚无证据定性**;本次修的是「时序窗口下熔断不留续写且完全静默」这条已复现的缺陷,不等同于已解释该差异。
14
+
15
+ - **[测试隔离] `workbuddy-routes.test.ts` 的目录快照会跨用例串味:fire-and-forget 的写入在用例结束后才落盘,被下一个用例 rehydrate 回来,导致 CI ubuntu 上偶发失败**
16
+ - **现象**:v0.24.4 的 release bump 提交在 CI 上 ubuntu 失败而 windows 通过,唯一失败断言是 `test/workbuddy-routes.test.ts` 的 `expected [ { id: 'glm-5.3', …(10) }, …(6) ] to deeply equal []`,报错落在 `never serves one region the catalog snapshot read for another`。同一份代码在前一个提交(纯功能改动)上通过 CI,说明是**时序敏感**而非确定性失败;本地单独跑该文件 10 次也不复现。
17
+ - **根因(用插桩实测定位,不是推断)**:`loadConfigCatalog` 取目录成功后调用 `writeCatalogSnapshot`,而它是 **fire-and-forget**(调用方不 await);关键在于 `catalogSnapshotPath` 是在**写入真正执行时**才读 `DSH_HOME` 拼路径,不是入队时。给写路径插桩后抓到一条**没有任何用例栈帧**的写入(`processTicksAndRejections`,count=31),即一个脱离用例的孤儿写入在 afterEach 清理**之后**才落盘,于是 cn/intl 快照被重新创建、并被下一个用例(`intl` + `offline`)在首轮 cache miss 时 rehydrate 读回——正是该用例断言必须为 `[]` 的场景。**先前的排查方向(跨文件污染)已被证伪**:实测每个测试文件拥有各自的 `DSH_HOME` 与各自进程,文件之间不可能互污。
18
+ - **改法**:给**每个用例**分配私有 `DSH_HOME`(`mkdtemp`,`beforeEach` 写入、`afterEach` 还原),把竞态**消除**而不是收窄——孤儿写入只能落到上一个用例那个已无人读取的目录里。另在 `afterEach` 保留 `await flushCatalogSnapshots()`,让常规写入在目录被删前落定。(`dshHomeDir()` 每次调用都读环境变量,因此改环境变量即可生效。)
19
+ - **为什么不是"先 flush 再删文件"**:那是第一版修法,本地 10 次全绿、CI 仍然失败——它只能收窄窗口,无法阻止一个在清理之后才执行的写入。真正的修复必须是让"迟到的那一次写入"写进一个不会再被读的位置。
20
+ - **验证**:`npm run typecheck` 0 错误;全量**连续 3 次** `3743 passed | 7 skipped、0 failed`;目标文件连跑 6 次全绿。**承重已双向检查(负对照)**:在同一个测试文件里构造「用例 A 排队一次孤儿写入、不 await → 用例 B 读 `intl` + `offline`」,**共享 home 时读回 4 个模型(含注入的 `LEAKED-MODEL`)并使断言失败;改为每用例私有 home 后读回 0 个、通过**——同场景、同一迟到写入,唯一变量是 home 策略。
21
+
22
+ ## 0.24.4 - 2026-10-15
23
+ - **[Kimi Code] 额度倍率与缓存时长档位写进设置页说明:倍率此前只在官方英文 description 里隐含提及,缓存档位说明则完全没给选择依据**
24
+ - **现象**:模型能力表里 `k3` 的说明是官方表格的英文原文,倍率只隐含在 "consumes about twice the quota of k3-256k" 一句里;`kimi-for-coding-highspeed` 的 3× 与 `kimi-for-coding` 的 1× 则一个字都没有。官方模型表实际声明的是 k3 = 2×、k3-256k = 1×、kimi-for-coding = 1×、highspeed = 3×,而**设置页从头到尾没有出现过这组数字**。缓存时长那一栏只说了「1h 写入更贵」,没给任何选择依据——用户无从判断该选哪个档。
25
+ - **改法(结构化接入,非改文案)**:`KimiCodeModelOption` 新增 `quotaMultiplier: number | null`,由 `buildModelOptions` 从**静态注册表**填充(实时目录不发布倍率,因此是唯一来源),能力表「说明」列首行渲染为「额度倍率 2x」并进入悬停 facts。**注册表没有该 id 时填 `null` 而不是默认 1×** —— 把未知模型显示成 1× 会在一个专门用来说明「谁更费额度」的页面上低报一个 2×/3× 模型。顺带复用了一直定义却全项目无人使用的 `quotaCost` 文案键(与 `speedRegular`/`speedHighspeed` 同批预留)。
26
+ - **缓存说明改写**:`cacheTtlHint`(中英双份)补齐两点——1h 写入**约为 5m 的两倍价**、留空时**实测有效期约 5–30 分钟**,并给出判据:回合间隔通常短于约 30 分钟就留空或选 5 分钟,调度/等子代理这类长空闲会话选 1 小时。
27
+ - **验证**:`npm run typecheck` 与 `npm run build` 均 0 错误;全量 **225 passed | 1 skipped(226 文件)、3743 passed | 7 skipped、0 failed**(基线 3739,新增 4 例:倍率渲染、悬停 facts、未知 id 不渲染、DTO 四模型逐值 + 未知 id 为 null)。**承重已双向检查**:把 `client.ts` 的填充单独还原为硬编码 `1`,2 条新用例立刻失败(`expected { k3: 1, … } to deeply equal { k3: 2, … }`)。
28
+
5
29
  ## 0.24.3 - 2026-10-15
6
30
  - **[Linux 凭据存储] 账号池撞上 secret-tool 的 8192 字节 stdin 上限:第 3 个账号添加后凭据写入成功、池写入失败,账号在 UI 闪现后消失(issue #54)**
7
31
  - **现象**:Linux(gnome-keyring)下添加第 3 个 WorkBuddy 账号后账号区闪现即消失——凭据条目(7934B)惊险通过,但池条目(约 8960B)≥ 8192 被 `SecretServiceCredentialStore.save` 拒绝,`status.accounts`(池)永远少一个。该上限对齐的是 libsecret `secret-tool` 的 stdin 固定缓冲(超限静默截断仍 exit 0),且对**所有线路**(antigravity / claude / command-code / glm-coding-plan / kimi-code / minimax-code / ollama / step-code / workbuddy 的池与凭据共 25 处调用点)一视同仁,账号一多都会撞。
package/README.md CHANGED
@@ -560,6 +560,8 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
560
560
 
561
561
  `kimi-code` 会像其他 Provider 一样出现在 DSH 模型选择器中。**上下文窗口**默认取模型目录值,可逐模型覆盖(用于 DSH 的压缩与溢出判断,支持 `1M` / `256K` / `200000` 等写法)——注意 `k3` 的 1M 上下文需要 Allegretto 及以上套餐,因此默认按 256K 计算,升级后再在此覆盖为 1M。**默认思考档位**只提供 `low` / `high` / `max` 三档与「关闭思考」(服务对这三档以外的取值直接报 400);切换模型或切换档位都会使上下文缓存失效,建议在同一会话内保持一致。
562
562
 
563
+ **额度倍率**取自官方模型表并显示在设置页模型能力表的「说明」列(悬停可一并看到):`k3` = **2×**、`k3-256k` = **1×**、`kimi-for-coding` = **1×**、`kimi-for-coding-highspeed` = **3×**。倍率绑定**模型 id**,与上下文长度、缓存命中率都无关——选 `k3` 就是 2×,哪怕只用几千 token。注册表不认识的新模型 id 显示为**未知**而不是猜成 1×。
564
+
563
565
  **区域**:默认使用中国大陆主机(`auth.kimi.com` / `api.kimi.com`)。国际账号可设置 `DSH_KIMI_CODE_OAUTH_HOST=https://auth.kimi.ai` 与 `DSH_KIMI_CODE_BASE_URL=https://api.kimi.ai/coding` 后重新登录;环境变量同时会钉住区域,设置页会显示当前解析到的主机。
564
566
 
565
567
  ## Command Code 线路
package/lib/client.js CHANGED
@@ -4200,7 +4200,7 @@ window.__ModuleLoader__.load({
4200
4200
  defaultReasoningEffort: "默认思考档位",
4201
4201
  defaultReasoningEffortHint: "Kimi Code 只接受 low / high / max 三档;none 表示关闭思考。切换档位会使上下文缓存失效,建议在同一会话中保持一致。",
4202
4202
  cacheTtl: "上下文缓存时长",
4203
- cacheTtlHint: "Kimi 按前缀自动缓存,这里选择写入时长。1h 写入更贵,只有当同一前缀会在一小时内被反复读取才划算;档位在首次写入时锁定,之后无法改写。留空表示不指定,由 Kimi 自己决定。",
4203
+ cacheTtlHint: "Kimi 按前缀自动缓存,这里选择写入时长。1h 写入约为 5m 的两倍价,只有当同一前缀会在一小时内被反复读取才划算;档位在首次写入时锁定,之后无法改写。留空表示不指定,由 Kimi 自己决定(实测有效期约 5–30 分钟)。依据回合间隔选:间隔通常短于约 30 分钟就留空或选 5 分钟;调度、等子代理这类会出现长空闲的会话选 1 小时。",
4204
4204
  cacheTtlDefault: "不指定(Kimi 默认)",
4205
4205
  cacheTtl5m: "5 分钟",
4206
4206
  cacheTtl1h: "1 小时",
@@ -4323,7 +4323,7 @@ window.__ModuleLoader__.load({
4323
4323
  defaultReasoningEffort: "Default thinking level",
4324
4324
  defaultReasoningEffortHint: "Kimi Code accepts only low / high / max; none disables thinking. Switching the level invalidates the context cache, so keep it steady within a session.",
4325
4325
  cacheTtl: "Context cache duration",
4326
- cacheTtlHint: "Kimi caches prefixes automatically; this chooses the write duration. A 1h write costs more and only pays off when the same prefix is re-read within the hour. The tier is locked when first written and cannot be changed afterwards. Leave unset to send no cache field and let Kimi decide.",
4326
+ cacheTtlHint: "Kimi caches prefixes automatically; this chooses the write duration. A 1h write costs about twice a 5m one and only pays off when the same prefix is re-read within the hour. The tier is locked when first written and cannot be changed afterwards. Leave unset to send no cache field and let Kimi decide (measured lifetime is roughly 5-30 minutes). Choose by the gap between turns: leave unset or use 5 minutes when turns are normally less than about 30 minutes apart, and pick 1 hour for a session that idles for long stretches, such as dispatch work waiting on subagents.",
4327
4327
  cacheTtlDefault: "Unset (Kimi default)",
4328
4328
  cacheTtl5m: "5 minutes",
4329
4329
  cacheTtl1h: "1 hour",
@@ -4397,6 +4397,15 @@ window.__ModuleLoader__.load({
4397
4397
  * detail kept in a hover title.
4398
4398
  */
4399
4399
  const t = zh$7;
4400
+ /**
4401
+ * Render one quota multiplier the way the official table states it: "2x", "3x".
4402
+ *
4403
+ * The value is transcribed, never computed — the service is the only party that
4404
+ * knows how a request is billed, so this shows what the model table declares.
4405
+ */
4406
+ function formatMultiplier(value) {
4407
+ return `${value}x`;
4408
+ }
4400
4409
  function KimiModelCapabilities({ models }) {
4401
4410
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4402
4411
  className: "dsha-cap-table",
@@ -4413,7 +4422,8 @@ window.__ModuleLoader__.load({
4413
4422
  model.wire === "anthropic" ? t.wireAnthropic : t.wireOpenai,
4414
4423
  ...model.defaultReasoningEffort ? [t.defaultReasoningEffort + ": " + model.defaultReasoningEffort] : [],
4415
4424
  ...model.maxInputTokens === void 0 ? [] : [t.capInputCap.replace("{tokens}", String(model.maxInputTokens))],
4416
- ...model.minimumPlan ? [t.capPlan.replace("{plan}", model.minimumPlan)] : []
4425
+ ...model.minimumPlan ? [t.capPlan.replace("{plan}", model.minimumPlan)] : [],
4426
+ ...model.quotaMultiplier === null ? [] : [`${t.quotaCost}: ${formatMultiplier(model.quotaMultiplier)}`]
4417
4427
  ].join(" · "),
4418
4428
  children: model.name
4419
4429
  }),
@@ -4431,9 +4441,16 @@ window.__ModuleLoader__.load({
4431
4441
  className: "dsha-cap-off",
4432
4442
  children: t.capNoTools
4433
4443
  }) }),
4434
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("td", {
4444
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("td", {
4435
4445
  className: "dsha-cap-notes",
4436
- children: model.description ?? ""
4446
+ children: [model.quotaMultiplier === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4447
+ className: "dsha-cap-cost",
4448
+ children: [
4449
+ t.quotaCost,
4450
+ " ",
4451
+ formatMultiplier(model.quotaMultiplier)
4452
+ ]
4453
+ }), model.description ?? ""]
4437
4454
  })
4438
4455
  ] }, model.id);
4439
4456
  }) })] }), models.some((model) => model.supportsVideo) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -14053,6 +14070,7 @@ window.__ModuleLoader__.load({
14053
14070
  .dsha-cap-on{color:var(--dsw-alias-label-primary);font-weight:500}
14054
14071
  .dsha-cap-off{color:var(--dsw-alias-label-tertiary)}
14055
14072
  .dsha-cap-notes{line-height:1.5;min-width:200px}
14073
+ .dsha-cap-cost{color:var(--dsw-alias-label-primary);display:block;font-weight:500;margin-bottom:3px}
14056
14074
  .dsha-cap-footnote{font-size:11px;line-height:1.55;margin:8px 0 0}
14057
14075
  .dsha-warn{border:0.5px solid var(--dsw-alias-border-l2);border-radius:6px;line-height:1.55;margin:0 0 10px;padding:8px 10px}
14058
14076
  `;