@eddyskywalker/dsh-chatgpt-subscription 0.43.0 → 0.44.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/cordis.patch.yml +5 -0
  3. package/lib/client.js +157 -46
  4. package/lib/client.js.map +1 -1
  5. package/lib/entry-progress-updates-CkGJr_fe.js +1004 -0
  6. package/lib/index.js +237 -991
  7. package/lib/progress-updates.js +2 -0
  8. package/lib/types/client/locales-ru/qoder.d.ts.map +1 -1
  9. package/lib/types/client/progress-updates/host-source.d.ts +16 -0
  10. package/lib/types/client/progress-updates/host-source.d.ts.map +1 -1
  11. package/lib/types/client/progress-updates/index.d.ts.map +1 -1
  12. package/lib/types/client/progress-updates/settings-binding.d.ts +5 -0
  13. package/lib/types/client/progress-updates/settings-binding.d.ts.map +1 -1
  14. package/lib/types/client/qoder/QoderSection.d.ts.map +1 -1
  15. package/lib/types/client/qoder/locales.d.ts +9 -1
  16. package/lib/types/client/qoder/locales.d.ts.map +1 -1
  17. package/lib/types/client/qoder/styles.d.ts.map +1 -1
  18. package/lib/types/compat.d.ts +1 -1
  19. package/lib/types/entry-progress-updates.d.ts +43 -0
  20. package/lib/types/entry-progress-updates.d.ts.map +1 -0
  21. package/lib/types/host/proxy-manager.d.ts +6 -0
  22. package/lib/types/host/proxy-manager.d.ts.map +1 -1
  23. package/lib/types/host/qoder/client.d.ts.map +1 -1
  24. package/lib/types/host/qoder/mapper.d.ts +74 -2
  25. package/lib/types/host/qoder/mapper.d.ts.map +1 -1
  26. package/lib/types/index.d.ts +21 -25
  27. package/lib/types/index.d.ts.map +1 -1
  28. package/lib/types/shared/qoder-contracts.d.ts +10 -0
  29. package/lib/types/shared/qoder-contracts.d.ts.map +1 -1
  30. package/locale/progress-updates/en.json +6 -0
  31. package/locale/progress-updates/zh.json +6 -0
  32. package/package.json +13 -3
  33. package/progress-updates.svg +9 -0
package/CHANGELOG.md CHANGED
@@ -2,6 +2,30 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.44.0 - 2026-10-27
6
+
7
+ - **[packaging] 「进展更新」拆成独立组件:同包第二行,可在插件管理页单独关闭**
8
+ - **机制**:`cordis.patch.yml` 现在插两行 loader 条目;新增 `src/entry-progress-updates.ts` 作为第二个宿主入口(自带 `Config`、`inject` 与 `apply`),`package.json` 增加 `exports["./progress-updates"]` 与子路径行的 `locale/*.json` / `icon`,构建多入口产出 `lib/progress-updates.js`。管理页的「包含的组件」因此从 1 变成 2。
9
+ - **设置字段留在核心行**:三个 `volatile` 字段仍声明在 `src/index.ts` 的 `Config`,由核心在 `apply` 时通过 `attachProgressSettings()` 把读取器交给新行(两行的挂载顺序两种都兜住)。不能搬走的原因很实在:profile 的 patch 层按 id 定位**替换整行 config**,而用户 profile 早就在核心行写了 `progressUpdates: true`——字段搬去新行,那条 patch 会把新行配置整体抹掉、schema 默认值填回 false,于是「设置显示打开、实际是关的」。
10
+ - **前端自隐**:同包多行只拆宿主——浏览器半边仍按包加载。客户端挂载时探一次 `GET api/dsh-progress-updates/updates`:**只有路由自己回的 JSON 404/405** 才说明那一行没装(网关兜底的纯文本 `not found` 也是 404,第一版据此把装着它的实例的 UI 也撤了),网络错误按「装着」处理。
11
+ - **回归**:`progress-updates-live-ref-and-route` 改为读新入口的 `Config`;新增 3 例(宿主不在时撤注册、宿主在时不动、探测失败按装着);`minimax-media-tool-registration` 改为断言 `progress_update` 不再属于核心行。
12
+ - **验证**:`npm run typecheck` 0 错误;全量 Vitest **4906 passed / 10 skipped**(唯一失败仍是 GLM adopt 那个环境用例);`npm run build` 产出 `lib/index.js` + `lib/progress-updates.js`;`npm pack --dry-run` 含 `lib/progress-updates.js`、`locale/progress-updates/{zh,en}.json`、`progress-updates.svg`;直接 import 构建产物确认 `name / apply / Config` 三项齐全、默认值解析为 `true / 8 / auto`。
13
+ - **未验证**:插件管理页上真的出现两个开关需要重启 DSH(bundle patch 在启动时应用),本轮没有重启你的实例。
14
+
15
+ - **[proxy] 代理连接池被销毁不再让回答卡死:死连接自动换新,卸载时优雅关闭**
16
+ - **现象**:长思考进行到一半,界面出现「已重试模型请求(1/3)」与 `terminated: The client is destroyed`,随后「已停止」。与 0.43.0 修的 180 秒超时是两个不同的故障。
17
+ - **为什么**:`terminated` 是 undici 的包装错误,`The client is destroyed` 是它的 `ClientDestroyedError`——请求还在流式读取时,脚下的连接池已经不存在了。这条线还有两处会把「一次事故」放大成「持续故障」:`ProxyManager.dispose()` 用 `destroy()` 立刻拆连接(任何插件卸载都会掐断在途回答),以及被销毁的 agent 仍留在按 URL 缓存的表里,之后每个请求都复用它并在瞬间失败。
18
+ - **改法**:卸载改为 `close()`——等在建请求跑完再释放 socket;代理请求遇到 destroyed-client 家族的错误时,把该 URL 的缓存 agent 摘掉、换一个新连接**重试一次**;错误判定只认这一族,普通的连接失败仍然不重放(那是另一种决定)。
19
+ - **测试**:`test/proxy-manager.test.ts` 新增两例——销毁后换新连接并成功(断言第二次的 dispatcher 不是第一次那个)、普通传输失败只跑一次。
20
+
21
+ ## 0.43.1 - 2026-10-27
22
+
23
+ - **[qoder] 上游排队不再被说成「API 密钥无效」;额度用尽与排队分成两条路**
24
+ - **现象**:Qoder 线路发消息立刻失败,界面只显示「本轮运行失败 · API 密钥无效 · AUTH」,于是去重建 PAT、重新登录、换区域——而凭据一直是好的(同一把 token 调 `GET /api/v1/userinfo` 返回 200)。另一条路同样的话:额度真的用尽时(上游 code 112 + pricingUrl)显示的也是「API 密钥无效」,两条完全不同的失败在用户眼里是同一句。
25
+ - **为什么**:上游把「排队中」装在 **HTTP 200 的 SSE 错误信封**里(`statusCodeValue: 403`,body 带 `code 10605` / `subCode: RESOURCE_LIMIT` / `queueCount 3239` / `retryAfterSeconds 30` / `serviceAvailable false`)。插件在 mapper 里解出 `statusCodeValue` 后交给 `classifyQoderFailure`,而它把任何 403 一刀切成 AUTH;DSH 收到 AUTH 就显示通用文案「API 密钥无效」(`message.failure.auth`)。插件也不认识 `RESOURCE_LIMIT` / `10605` / `isQueued` / `retryAfterSeconds` 这几个字段,信息在整条链路上被丢弃。额度快照同样只映射三只桶,`isQuotaExceeded` 与 `upgradeUrl` 一次都没出现过——于是三只桶全是 0 时卡片显示「还有额度」,而上游其实已经把请求拒了。
26
+ - **改法**:(1) 分类器改为**按上游事实**判定:新增一份逐层扫描的事实集合(信封外层 + body + body 里的 `error` 对象),排队/资源不足(`RESOURCE_LIMIT`、`10605`,或 403 配容量信号)归 `SERVER`(上游容量,换账号与重新登录都救不了,只有退避重试有意义——与 kimi-code 的「引擎过载」同一判断),额度用尽(code `112`,或 403 配 `isQuotaExceeded` / `pricingUrl`)归 `QUOTA`(DSH 显示「当前请求的额度已用尽」),401 与 code `105` 仍是 `AUTH`,认不出原因的 403 仍保守归 AUTH;上游给了 `retryAfterSeconds` 就照它说(`providerRetryAfterMs`)。(2) 信封路径把**信封层与 body 一起**送进分类器——上游把事实放在哪一层并不确定,只读 body 会把「排队」读成「凭据被拒」。(3) 额度快照补上 `quotaExceeded` / `upgradeUrl`,设置页额度卡在上游说用尽时先说这件事并给出升级页(zh/en/ru 三语)。
27
+ - **测试**:`test/qoder-mapper.test.ts` 新增 4 例(排队归 SERVER 且带 `providerRetryAfterMs`、额度用尽归 QUOTA、认不出的 403 仍是 AUTH、429 的含义不被弱信号改写)与 4 例信封路径;`test/qoder-adapter.test.ts` 新增 1 例端到端(HTTP 200 + 错误信封 → `SERVER` 而非 `AUTH`,且账号没有被判成「要重新登录」);新增 `test/qoder-quota.test.ts`(4 例)钉住两个新字段的归一化;`test/qoder-ui.test.tsx` 新增 2 例(说用尽时额度区给出升级页,没说时不出现)。
28
+
5
29
  ## 0.43.0 - 2026-10-27
6
30
 
7
31
  - **[opendesign] MiMo V2.6 与 Qwen3.7 Max 拿回思考深度选择**
package/cordis.patch.yml CHANGED
@@ -4,3 +4,8 @@
4
4
  - insert:
5
5
  - id: dsh-chatgpt-subscription
6
6
  name: '@eddyskywalker/dsh-chatgpt-subscription'
7
+ # 第二个组件:「进展更新」。同包多行 = 两个独立的 loader 条目,管理页各有一
8
+ # 个开关;浏览器半边仍只有一份 bundle(`dsh.client` 按包声明),所以它自己
9
+ # 探测这一行的路由在不在,不在就不注册面板与设置分区。
10
+ - id: dsh-chatgpt-subscription-progress-updates
11
+ name: '@eddyskywalker/dsh-chatgpt-subscription/progress-updates'
package/lib/client.js CHANGED
@@ -15965,6 +15965,10 @@ window.__ModuleLoader__.load({
15965
15965
  quotaSignedOut: "登录后这里显示账号的额度。",
15966
15966
  quotaErrorLabel: "额度读取失败",
15967
15967
  quotaNoUnit: "",
15968
+ /** 上游明说额度用尽:它是账号层面的事实,三只桶全是 0 时只有它能说清为什么被拒。 */
15969
+ quotaExceeded: "额度已用尽",
15970
+ quotaExceededHint: "上游说这个账号的额度已经用尽,请求会被拒。额度恢复之前发消息会失败:可以等周期重置,或在额度页升级套餐。",
15971
+ quotaUpgradeLink: "打开升级页面",
15968
15972
  cacheSection: "缓存用量",
15969
15973
  cacheStatScope: "本机统计(token 维度,自本进程启动累计):它回答「缓存帮了多少忙」,不是上游账单。",
15970
15974
  cacheDesc: "Qoder 服务端自动做前缀缓存;命中量从响应的 usage.prompt_tokens_details.cached_tokens 读出(缺失时读顶层 cached_tokens)。本线路订阅没有公开的 token 单价,整页只显示 token 数字,不显示金额。",
@@ -16085,6 +16089,9 @@ window.__ModuleLoader__.load({
16085
16089
  quotaSignedOut: "Sign in to see the account's quota here.",
16086
16090
  quotaErrorLabel: "Quota read failed",
16087
16091
  quotaNoUnit: "",
16092
+ quotaExceeded: "Quota exhausted",
16093
+ quotaExceededHint: "Upstream says this account's quota is exhausted, so requests get rejected. Sending a message fails until the quota resets: wait for the period to reset, or upgrade the plan on the quota page.",
16094
+ quotaUpgradeLink: "Open the upgrade page",
16088
16095
  cacheSection: "Cache usage",
16089
16096
  cacheStatScope: "Local statistics (tokens, accumulated since this process started): it answers \"how much the cache helped\", not the upstream bill.",
16090
16097
  cacheDesc: "Qoder caches prefixes automatically server-side; hits are read from usage.prompt_tokens_details.cached_tokens (falling back to a top-level cached_tokens). There is no public per-token price for the subscription, so this page shows token counts only, never money.",
@@ -17198,49 +17205,71 @@ window.__ModuleLoader__.load({
17198
17205
  children: busy === "quota" ? t.quotaRefreshing : t.quotaEmpty
17199
17206
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17200
17207
  className: "dsha-quota-card",
17201
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17202
- className: "dshq-fact-grid",
17203
- children: [
17204
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17205
- className: "dshq-fact",
17206
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.quotaExpiresAt }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: quota.expiresAt !== null ? formatDate$1(quota.expiresAt) : "—" })]
17207
- }),
17208
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17209
- className: "dshq-fact",
17210
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.quotaReadAt }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatDate$1(quota.fetchedAt) })]
17211
- }),
17212
- /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17213
- className: "dshq-fact",
17214
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.quotaRegion }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: regionText(quota.region, t) })]
17215
- })
17216
- ]
17217
- }), quota.buckets.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
17218
- className: "dsha-empty",
17219
- children: t.quotaEmpty
17220
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
17221
- className: "dshq-quota-list",
17222
- children: quota.buckets.map((bucket, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17223
- className: "dshq-quota-row",
17208
+ children: [
17209
+ quota.quotaExceeded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17210
+ className: "dshq-quota-exhausted",
17224
17211
  children: [
17225
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
17226
- className: "dshq-quota-name",
17227
- children: quotaBucketText(bucket.kind, t)
17212
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
17213
+ className: "dshq-quota-exhausted-title",
17214
+ children: t.quotaExceeded
17228
17215
  }),
17229
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
17230
- className: "dshq-quota-value",
17231
- children: bucketAmountText(bucket, t)
17216
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
17217
+ className: "dshq-quota-exhausted-hint",
17218
+ children: t.quotaExceededHint
17232
17219
  }),
17233
- bucket.used !== null && bucket.total !== null && bucket.total > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
17234
- className: "dshq-quota-meta",
17235
- children: [
17236
- t.quotaUsed,
17237
- " ",
17238
- percentText(bucket.used / bucket.total)
17239
- ]
17220
+ quota.upgradeUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
17221
+ href: quota.upgradeUrl,
17222
+ target: "_blank",
17223
+ rel: "noreferrer noopener",
17224
+ children: t.quotaUpgradeLink
17240
17225
  })
17241
17226
  ]
17242
- }, bucket.kind + ":" + index))
17243
- })]
17227
+ }),
17228
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17229
+ className: "dshq-fact-grid",
17230
+ children: [
17231
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17232
+ className: "dshq-fact",
17233
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.quotaExpiresAt }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: quota.expiresAt !== null ? formatDate$1(quota.expiresAt) : "—" })]
17234
+ }),
17235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17236
+ className: "dshq-fact",
17237
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.quotaReadAt }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: formatDate$1(quota.fetchedAt) })]
17238
+ }),
17239
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17240
+ className: "dshq-fact",
17241
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t.quotaRegion }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: regionText(quota.region, t) })]
17242
+ })
17243
+ ]
17244
+ }),
17245
+ quota.buckets.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
17246
+ className: "dsha-empty",
17247
+ children: t.quotaEmpty
17248
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
17249
+ className: "dshq-quota-list",
17250
+ children: quota.buckets.map((bucket, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
17251
+ className: "dshq-quota-row",
17252
+ children: [
17253
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
17254
+ className: "dshq-quota-name",
17255
+ children: quotaBucketText(bucket.kind, t)
17256
+ }),
17257
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
17258
+ className: "dshq-quota-value",
17259
+ children: bucketAmountText(bucket, t)
17260
+ }),
17261
+ bucket.used !== null && bucket.total !== null && bucket.total > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
17262
+ className: "dshq-quota-meta",
17263
+ children: [
17264
+ t.quotaUsed,
17265
+ " ",
17266
+ percentText(bucket.used / bucket.total)
17267
+ ]
17268
+ })
17269
+ ]
17270
+ }, bucket.kind + ":" + index))
17271
+ })
17272
+ ]
17244
17273
  }),
17245
17274
  moreNotes(t.moreNotes, [t.quotaDesc])
17246
17275
  ]
@@ -21098,6 +21127,9 @@ window.__ModuleLoader__.load({
21098
21127
  quotaSignedOut: "Войдите, чтобы увидеть здесь квоту аккаунта.",
21099
21128
  quotaErrorLabel: "Не удалось прочитать квоту",
21100
21129
  quotaNoUnit: "",
21130
+ quotaExceeded: "Квота исчерпана",
21131
+ quotaExceededHint: "Апстрим сообщает, что квота этого аккаунта исчерпана, поэтому запросы отклоняются. Отправка сообщения не удастся, пока квота не восстановится: дождитесь сброса периода или смените тариф на странице квоты.",
21132
+ quotaUpgradeLink: "Открыть страницу тарифа",
21101
21133
  cacheSection: "Расход кеша",
21102
21134
  cacheStatScope: "Локальная статистика (в токенах, с начала работы процесса): она отвечает, насколько помог кеш, и не является счётом апстрима.",
21103
21135
  cacheDesc: "Qoder кеширует префиксы на сервере автоматически; попадания читаются из usage.prompt_tokens_details.cached_tokens (при отсутствии — из верхнеуровневого cached_tokens). У подписки нет публичной цены за токен, поэтому страница показывает только токены, без денег.",
@@ -23383,6 +23415,12 @@ window.__ModuleLoader__.load({
23383
23415
  .dshq-quota-name{color:var(--dsw-alias-label-primary);font-size:12px;min-width:104px}
23384
23416
  .dshq-quota-value{color:var(--dsw-alias-label-secondary);font-size:12px}
23385
23417
  .dshq-quota-meta{color:var(--dsw-alias-label-tertiary);font-size:11px}
23418
+ /* 额度用尽:账号层面的一条事实(上游明说),与三只桶的数字并排给出。 */
23419
+ .dshq-quota-exhausted{margin-top:11px}
23420
+ .dshq-quota-exhausted-title{color:var(--dsw-alias-label-primary);font-size:12px;font-weight:600}
23421
+ .dshq-quota-exhausted-hint{color:var(--dsw-alias-label-secondary);font-size:12px;line-height:1.5;margin-top:4px}
23422
+ .dshq-quota-exhausted a{color:var(--dsw-alias-label-link,#397ee8);font-size:12px;text-decoration:none}
23423
+ .dshq-quota-exhausted a:hover{text-decoration:underline}
23386
23424
  /* 快照卡里的几行事实:紧凑网格,一格一件事,行高一致。 */
23387
23425
  .dshq-fact-grid{display:grid;gap:9px 20px;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));margin-top:11px}
23388
23426
  .dshq-fact{display:flex;flex-direction:column;gap:3px;min-width:0}
@@ -53385,6 +53423,44 @@ window.__ModuleLoader__.load({
53385
53423
  * (同 dsh-usage / dsh-git-graph 的处理)。
53386
53424
  */
53387
53425
  const PROGRESS_UPDATES_PATH = "api/dsh-progress-updates/updates";
53426
+ /**
53427
+ * 宿主那一行今天装没装。
53428
+ *
53429
+ * 这个包的浏览器半边按**包**加载:即使「进展更新」那一行被关掉,本 bundle 照样
53430
+ * 会跑,面板与设置分区就会变成两个永远空的壳。所以挂载前问一次宿主的只读路由。
53431
+ *
53432
+ * **只在路由自己说不存在时判「没装」**:本路由的 403(非 loopback)与 200 都说明
53433
+ * 它在;而网关对一个不认识的路径回的那句 `not found`(纯文本或 HTML)也常常是
53434
+ * 404,把「任何 404」当成没装会让装着它的实例把 UI 自己撤掉——第一版就是这么写的,
53435
+ * 实测踩到了。
53436
+ *
53437
+ * 探测失败(网络错误、超时、无法判断)一律按「装着」处理:宁可多一格外壳,也不能
53438
+ * 因为一次抖动把装着的功能判成没装。
53439
+ * @returns 宿主半边是否在线。
53440
+ */
53441
+ async function isProgressUpdatesHostPresent() {
53442
+ if (typeof fetch !== "function") return true;
53443
+ let response;
53444
+ try {
53445
+ response = await fetch(PROGRESS_UPDATES_PATH, {
53446
+ method: "GET",
53447
+ headers: { accept: "application/json" }
53448
+ });
53449
+ } catch {
53450
+ return true;
53451
+ }
53452
+ if (response.status !== 404 && response.status !== 405) return true;
53453
+ if (!(response.headers.get("content-type") ?? "").toLowerCase().includes("json")) return true;
53454
+ let body;
53455
+ try {
53456
+ body = await response.json();
53457
+ } catch {
53458
+ return true;
53459
+ }
53460
+ if (body === null || typeof body !== "object") return true;
53461
+ const ok = body.ok;
53462
+ return typeof ok === "boolean" ? ok : true;
53463
+ }
53388
53464
  /** 默认定时器:直接转发到 `globalThis`(浏览器与 Node 都能跑)。 */
53389
53465
  const DEFAULT_TIMERS = {
53390
53466
  setInterval: (handler, ms) => globalThis.setInterval(handler, ms),
@@ -53606,6 +53682,11 @@ window.__ModuleLoader__.load({
53606
53682
  * 第一项来自 `cordis.patch.yml` 的行 id(独立安装的唯一形态);后两项是聚合安装
53607
53683
  * 可能套的前缀。镜像答上来之后不再用这张表的顺序,改用"值里带 `progressUpdates`
53608
53684
  * 的那一条"。
53685
+ *
53686
+ * 这三个字段挂在**核心那一行**(`src/index.ts` 的 Config)而不是「进展更新」那一行:
53687
+ * 用户/profile 早就在核心行的 config 里写过 `progressUpdates`,而 loader 的 profile
53688
+ * 层是按 id 替换整行 config 的——字段若搬去新行,profile 一写就等于把那行的开关
53689
+ * 抹成默认值,于是「设置里显示打开」和「实际是关的」同时成立。
53609
53690
  */
53610
53691
  const PROGRESS_SETTINGS_ENTRY_IDS = [
53611
53692
  "dsh-chatgpt-subscription",
@@ -53646,6 +53727,10 @@ window.__ModuleLoader__.load({
53646
53727
  }
53647
53728
  /**
53648
53729
  * 判断一个条目的解析值是不是本插件的 Config 切片。
53730
+ *
53731
+ * 按**键**认领而不是按行 id:聚合安装会给条目套家族前缀,而字段名是唯一稳定的。
53732
+ * 认领对象是核心行(进度更新的实现行现在没有这些字段了),所以「值里带
53733
+ * `progressUpdates` 的那一条」就正好是设置该写进去的那一条。
53649
53734
  * @param value - describe 交来的解析值。
53650
53735
  * @returns 是否带 `progressUpdates` 这个键。
53651
53736
  */
@@ -53819,6 +53904,28 @@ window.__ModuleLoader__.load({
53819
53904
  */
53820
53905
  const explicitSource = options.source;
53821
53906
  /**
53907
+ * 宿主那一行是不是装着。
53908
+ *
53909
+ * 同包多组件之后,关掉「进展更新」只会卸掉宿主那一行:浏览器半边仍随本包的
53910
+ * bundle 加载,面板与设置分区就会变成永远空的壳。启动时问一次宿主的只读路由,
53911
+ * 缺了就撤掉已经注册的槽位、并且不再响应后续出现的槽位。
53912
+ *
53913
+ * 显式给了数据源(测试与将来替换实现)时不探测:那时宿主根本不在链路里。
53914
+ */
53915
+ let hostPresent = true;
53916
+ const registrations = [];
53917
+ const keep = (dispose) => {
53918
+ registrations.push(dispose);
53919
+ return dispose;
53920
+ };
53921
+ if (explicitSource === void 0) isProgressUpdatesHostPresent().then((present) => {
53922
+ if (present) return;
53923
+ hostPresent = false;
53924
+ for (const dispose of registrations.splice(0)) try {
53925
+ dispose();
53926
+ } catch {}
53927
+ });
53928
+ /**
53822
53929
  * 默认数据源:每个会话一份轮询源。
53823
53930
  *
53824
53931
  * 槽位是 session 作用域的,同一个页签在会话切换时会被重新注入一次;按会话 id
@@ -53943,37 +54050,40 @@ window.__ModuleLoader__.load({
53943
54050
  * 这是本特写的主形态——不依赖侧栏,也不依赖宿主路由,只看工具调用的参数。
53944
54051
  */
53945
54052
  ctx.slots.inject("tool.call.toolview", () => {
54053
+ if (!hostPresent) return () => {};
53946
54054
  try {
53947
- return ctx.slots.register({
54055
+ return keep(ctx.slots.register({
53948
54056
  name: "tool.call.toolview",
53949
54057
  key: PROGRESS_UPDATE_TOOL_NAME,
53950
54058
  locale: NS_PROGRESS_UPDATES
53951
- }, ProgressRow);
54059
+ }, ProgressRow));
53952
54060
  } catch {
53953
54061
  return () => {};
53954
54062
  }
53955
54063
  });
53956
54064
  /** 阶段二:同一个 id 下渲染页签正文。 */
53957
54065
  ctx.slots.inject("sidebar.right.pane.tab", () => {
54066
+ if (!hostPresent) return () => {};
53958
54067
  try {
53959
- return ctx.slots.register({
54068
+ return keep(ctx.slots.register({
53960
54069
  name: "sidebar.right.pane.tab",
53961
54070
  key: PANEL_TAB_ID,
53962
54071
  locale: NS_PROGRESS_UPDATES,
53963
54072
  inject: (sessionId) => ({ source: explicitSource ?? hostSourceOf(sessionId) })
53964
- }, ProgressUpdatesTab);
54073
+ }, ProgressUpdatesTab));
53965
54074
  } catch {
53966
54075
  return () => {};
53967
54076
  }
53968
54077
  });
53969
54078
  /** 阶段二:同一个 id 下渲染页签条上的标题。 */
53970
54079
  ctx.slots.inject("sidebar.right.pane.tab.title", () => {
54080
+ if (!hostPresent) return () => {};
53971
54081
  try {
53972
- return ctx.slots.register({
54082
+ return keep(ctx.slots.register({
53973
54083
  name: "sidebar.right.pane.tab.title",
53974
54084
  key: PANEL_TAB_ID,
53975
54085
  locale: NS_PROGRESS_UPDATES
53976
- }, ProgressUpdatesTabTitle);
54086
+ }, ProgressUpdatesTabTitle));
53977
54087
  } catch {
53978
54088
  return () => {};
53979
54089
  }
@@ -53984,9 +54094,10 @@ window.__ModuleLoader__.load({
53984
54094
  * 没有这一格,面板就是注册好了也永远点不开——类型和正文都不会自己长出入口。
53985
54095
  */
53986
54096
  ctx.slots.inject("settings.section", () => {
54097
+ if (!hostPresent) return () => {};
53987
54098
  try {
53988
54099
  const openPanel = openPanelAction(ctx, options.openPanel);
53989
- return ctx.slots.register({
54100
+ return keep(ctx.slots.register({
53990
54101
  name: "settings.section",
53991
54102
  id: SETTINGS_SECTION_ID,
53992
54103
  order: 150,
@@ -54005,7 +54116,7 @@ window.__ModuleLoader__.load({
54005
54116
  settings: currentSettings
54006
54117
  };
54007
54118
  }
54008
- }, ProgressUpdatesSettingsSection);
54119
+ }, ProgressUpdatesSettingsSection));
54009
54120
  } catch {
54010
54121
  return () => {};
54011
54122
  }