@eddyskywalker/dsh-chatgpt-subscription 0.11.5 → 0.12.1

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 (48) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +36 -2
  3. package/lib/client.js +182 -59
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +465 -25
  6. package/lib/types/client/CodexSubscriptionSection.d.ts +18 -0
  7. package/lib/types/client/CodexSubscriptionSection.d.ts.map +1 -1
  8. package/lib/types/client/antigravity/styles.d.ts.map +1 -1
  9. package/lib/types/client/claude/styles.d.ts.map +1 -1
  10. package/lib/types/client/common/plugin-style.d.ts +23 -0
  11. package/lib/types/client/common/plugin-style.d.ts.map +1 -0
  12. package/lib/types/client/common/styles.d.ts.map +1 -1
  13. package/lib/types/client/kimi-code/styles.d.ts.map +1 -1
  14. package/lib/types/client/locales.d.ts +45 -5
  15. package/lib/types/client/locales.d.ts.map +1 -1
  16. package/lib/types/client/mermaid/styles.d.ts.map +1 -1
  17. package/lib/types/client/minimax-code/styles.d.ts.map +1 -1
  18. package/lib/types/client/styles.d.ts +5 -0
  19. package/lib/types/client/styles.d.ts.map +1 -1
  20. package/lib/types/client/workbuddy/styles.d.ts.map +1 -1
  21. package/lib/types/host/antigravity/mapper.d.ts +3 -2
  22. package/lib/types/host/antigravity/mapper.d.ts.map +1 -1
  23. package/lib/types/host/antigravity/tool-schema.d.ts +13 -1
  24. package/lib/types/host/antigravity/tool-schema.d.ts.map +1 -1
  25. package/lib/types/host/claude/adapter.d.ts.map +1 -1
  26. package/lib/types/host/claude/mapper.d.ts +57 -3
  27. package/lib/types/host/claude/mapper.d.ts.map +1 -1
  28. package/lib/types/host/codex-fetch.d.ts.map +1 -1
  29. package/lib/types/host/command-code/adapter.d.ts.map +1 -1
  30. package/lib/types/host/common/context-budget-fetch.d.ts +7 -0
  31. package/lib/types/host/common/context-budget-fetch.d.ts.map +1 -0
  32. package/lib/types/host/fetch-address-policy.d.ts +2 -1
  33. package/lib/types/host/fetch-address-policy.d.ts.map +1 -1
  34. package/lib/types/host/fetch-configuration.d.ts +6 -0
  35. package/lib/types/host/fetch-configuration.d.ts.map +1 -0
  36. package/lib/types/host/kimi-code/adapter.d.ts.map +1 -1
  37. package/lib/types/host/kimi-code/mapper.d.ts.map +1 -1
  38. package/lib/types/host/minimax-code/adapter.d.ts.map +1 -1
  39. package/lib/types/host/routes.d.ts +2 -2
  40. package/lib/types/host/routes.d.ts.map +1 -1
  41. package/lib/types/host/search-provider-switcher.d.ts +2 -0
  42. package/lib/types/host/search-provider-switcher.d.ts.map +1 -1
  43. package/lib/types/host/workbuddy/adapter.d.ts.map +1 -1
  44. package/lib/types/index.d.ts +7 -0
  45. package/lib/types/index.d.ts.map +1 -1
  46. package/lib/types/shared/contracts.d.ts +11 -0
  47. package/lib/types/shared/contracts.d.ts.map +1 -1
  48. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - **Antigravity Claude 工具 schema 兼容(#39)**:仅对 Claude 目标在出站边界折叠 `anyOf` / `oneOf` / `allOf` 与类型联合,优先保留可填写的对象形状,把原始备选形式与约束摘要放入描述;同类型枚举合并保留空字符串重置值。Gemini 继续沿用原转换路径,原工具 schema 不被修改。覆盖调度参数、权限枚举、引用、数组、根联合、可空和单分支。折叠是网关兼容降级,并非等价 JSON Schema 转换:声明形式可能收窄,工具端仍校验参数。验证使用离线序列化请求;禁用折叠后 5 条回归立即失败,恢复后通过。两项 issue 修复组合的强制源码/测试类型检查、构建通过,全量测试 2456 passed / 7 skipped。未使用真实 Antigravity 账号复测上游,也未部署到当前运行中的 GUI。
6
+
7
+ - **网页抓取选择与上限可配置(#40)**:插件 Config 新增 `fetchProvider: auto | plugin | dsh`(默认 auto 保持既有行为)、`fetchMaxBodyChars`(默认 100000)和 `fetchMaxResponseBytes`(默认 2097152),上限须为正安全整数。显式 dsh 优先于代理检测和 Codex 搜索选择,plugin 则独立启用插件抓取。状态接口、设置页及不含代理凭据的切换日志展示配置与上限;README 说明如何修改插件 Config 并重载。回归覆盖真实插件注册、provider 重载、超过十万字符、多字节文本的字节/字符截断、显式模式优先级与诊断信息。临时恢复原注册接线后 3 条上限测试均复现 100000 字符截断,恢复原选择逻辑后显式 DSH 测试复现错误接管;修复恢复后全部通过。生命周期测试固定离线 DNS,不再依赖本机代理。DSH 抓取模式不使用插件上限;工具层另有输出限制,不承诺放大插件上限后可拿到整页。
8
+
9
+ - **修复 Claude(订阅)会话因一张宽图被永久卡死**:`messages.N.content.M.image.source.base64.data: At least one of the image dimensions exceed max allowed size for many-image requests: 2000 pixels`。
10
+ - **根因**:Messages 接口限制单张图片最长边为 8000 px;一次请求的图片**超过 20 张**时,每张图的最长边还不能超过 **2000 px**。DSH 存图时按总像素 2048×2048、最长边 8192 归一化,所以 2904×1272 这类并排对比截图会原样存下来;本线路随后按原样发送。会话历史不会丢掉旧图,图片只会越积越多。一旦超过 20 张、其中有一张宽于 2000 px,整次请求就会返回 400,之后每一轮(包括 fork 出来的会话)都会带上这张图,同样失败,无法自行恢复。
11
+ - **修法**:`resolveRequestImages` 先统计本次请求的图片块数(同一附件重复出现按多次计,tool-result 里嵌套的图片也计入),据此确定最长边上限(超过 20 张取 2000,否则取 8000)。只有超出上限的图片才通过 harness 的 `attachments.readImageRequest` 取一份按最长边缩小、结果确定的请求版本;已在上限内的图片仍按原字节发送,提示缓存前缀保持不变。存储的原图和会话历史都不改动。取不到缩小版本(后端不支持派生,或返回的版本仍然超限)时,这张图改成可见的「image unavailable」占位文本,而不是把原图发出去,导致整次请求失败。取消(abort)照常向上抛出。缩小版本的字节目标为原始 3.5 MiB(base64 约 4.9 MB),低于单图 5 MB 的上限。peer 范围内的每一代 harness(0.1.2-alpha.5 起)都声明了 `readImageRequest`,但旧代使用 `maxPixels` 而新版使用宽高目标;请求同时携带两套参数,旧代像素预算按未取整宽高比计算,避免窄图取整后越过最长边上限。新增 4 条旧代投影算法回归(横图、竖图和极窄图),无需升级 harness。
12
+ - **测试**:`test/claude-mapper.test.ts` 新增「Request image edge limits」7 条:20 张图以内宽图原样发送;超过 20 张时只缩小那张 2904×1272 的图(目标为 2000×876),线上的 tool_result 里携带的是缩小后的字节,其余 20 张原样发送;计数包括重复附件和嵌套图片;单张 4000×8192 的图会被压到 8000 上限;竖图按最长边保持宽高比,缺少尺寸的引用保持不动;无法派生或派生结果仍超限时改为占位文本;取消会原样传播。**已实测承重**:让 `resolveRequestImages` 恢复为按原样发送后,其中 4 条立即失败。
13
+ - **验证**:`npm run typecheck` 0 错误;`npm run build` 通过;全量 `npm test` 2405 passed / 9 skipped,只有 `test/web-provider-lifecycle.test.ts` 的 3 条失败。这 3 条与本次无关:本机代理把 `example.com` 解析成假 IP(198.18.0.206 / fc00::ce),在未改动的 master 上同样失败。另外用一个真实卡死会话的历史(34 张图,其中三张为 2904×1272)离线回放:只有那三张被缩小到 2000×876,其余 31 张原样发送。
14
+ - **边界**:未覆盖调用方直接内联 base64、不带附件引用的图片;这类图片没有尺寸信息,无法派生缩小版本,按原样发送。
15
+
16
+ - **插件内上下文预算恢复**:Kimi Code、MiniMax Code、Command Code、Claude、WorkBuddy 的模型请求在 HTTP 400/422 明确报告完整且一致的窗口、输入和输出 token 计数时,若输入仍能放下、上游输出预留等于实际发送的上限,会保留完整消息和工具,只降低输出上限并重试一次(预留 1024 tokens 余量,至少保留 1024 输出 tokens)。普通请求与摘要请求共用此路径,不修改 DSH、不截断历史。显式 thinking budget 不满足时不强行重试;鉴权、限流、字节限制、模糊计数、服务端忽略参数和流内错误保持原处理,输出截断也仍报告失败。Kimi/MiniMax 共用的输入估算补计推理文本和工具调用参数,避免将这些实际输入计为零。验证:截图预算的三种输出字段回归、MiniMax 普通/摘要真实 LLM 服务调用、Kimi Chat Completions 与 Command Code Responses 适配器回归通过;全量插件测试、强制源码及测试类型检查、构建通过。Codex 不支持输出上限字段,未套用此恢复;未进行真实账号端到端验证,不能据此认定截图中未知线路的所有压缩故障已解决。
17
+
18
+ - **修复设置页「订阅服务」整页样式丢失(只剩标签栏有样式)**。
19
+ - **根因**:客户端插件系统按属性记账样式归属。模块工厂 materialize 时会把文档里**所有未打标**的 `<style>` 认领给当时正在 materialize 的那个包(`style:not([data-plugin])` 被写上该包 id),并在该包重载 / 被新 revision 替换 / 被从依赖图剪除时执行 `removeOwnedStyles(id)`,按 owner 删掉这些标签。本插件的 7 张表(antigravity / claude / kimi-code / minimax-code / workbuddy / pool / mermaid)此前只用 `style.id = …` 注入、**不带 `data-plugin`**,于是先被别的包认领,再随那个包的重载被一起删除;而聊天区那张已按约定打了 `data-plugin` + `data-plugin-css` 的 `…/main` 表存活下来。设置页标签栏的样式来自 `…/main`(`.dsh-codex-segments`、`.dsh-hub-tabs`),正文控件来自被删掉的共享 `.dsha-*` 基础表,所以呈现为「标签栏正常、整页正文裸奔」。
20
+ - **修法**:新增 `src/client/common/plugin-style.ts` 的 `installPluginStyle(name, css, legacyId?)`,把原来的「`createElement` + `style.id` + 一次性 append」统一换成:按 `…/<name>` 的 `data-plugin-css` 定位、缺失时创建、**创建/命中时都盖上本包的 `data-plugin` 与唯一的 `data-plugin-css`**、内容变化时刷新 `textContent`、disposer 恒为空操作(标签归文档所有)。八个安装器全部改走它,原来判断存在即 return 的分支一并去掉,因此旧 bundle 在页面里留下的未打标元素会被**就地接管**(不会叠出第二份 CSS),`…/main` 的行为保持不变。
21
+ - **测试**:新增 `test/client-style-ownership.test.ts`(5 条)——八个安装器跑完后断言文档里不剩任何 `style:not([data-plugin])`(不给别的包留可认领对象)、每张表的 `data-plugin-css` 都指向本包;ChatGPT 默认页依赖的 `.dsha-*` 基础表(Kimi / Antigravity)确实带本包归属;模拟 `removeOwnedStyles` 删光后重新安装能全部恢复;模拟「旧 bundle 的未打标表已被别的包认领」时必须接管同一元素并改回本包;旧 bundle 的过期 CSS 必须被刷新而不是 dedupe 成空操作。
22
+ - **验证**:`npm run typecheck` 0 错误;全量 **2314 passed** / 9 skipped,仅 `test/quota-ui.test.tsx` 2 条与本次无关、且在本机时区(Etc/GMT+7)下必然失败的历史用例(把 UTC 的 2030-01-01 渲染成 2029-12-31)未过。
23
+
5
24
  - **修复 Ollama「同步模型列表」报 `Failed to execute 'json' on 'Response': Unexpected end of JSON input`**([issue #36](https://github.com/Aa728848/dsh-chatgpt-subscription/issues/36))。
6
25
  - **根因不在 Ollama Cloud**:issue 里推测的端点与鉴权都没问题——`CLOUD_BASE_URL` 一直就是 `https://ollama.com`,`/api/tags` 也一直带着 `Authorization: Bearer <key>`,只是从来没有任何一条断言钉住这两点(本次补上)。真正的原因是**本插件自己的路由**:`/ollama/api` 是全插件唯一没有用 `try/catch` 包起来的设置路由处理器,一旦它抛错,DSH 的 web server(`packages/host/webserver`)只会回一个**空的 400**(`res.writeHead(400); res.end()`)。卡片再把这个空响应体交给 `response.json()`,浏览器抛出的解析异常就成了用户看到的全部错误——既没有状态码,也没有请求信息,真实原因只留在主机日志里。同步路径上 `storeCatalog()` 当时是唯一没有保护的 `await`,设置文件写不进去就会走到这里。
7
26
  - **修法(主机侧)**:路由处理器整体包进 `try/catch`,兜底用与 Claude / Kimi / WorkBuddy 等同级线路相同的 `{ok:false,error}` 500 信封,并补上它们都有、本线路唯独没有的 404 兜底;`storeCatalog()` 单独捕获,区分「列表读到了但本地存不下」与「读不到」。
package/README.md CHANGED
@@ -375,9 +375,41 @@ DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。GPT-6 系列
375
375
 
376
376
  额度卡片展示:**套餐名**(由订阅返回的机器 id 查表得出,表在 `src/host/command-code/plans.ts`,按最长前缀匹配)、**订阅状态与续费日期**、**滚动窗口用量**(`windowLimits` 的 `fiveHour` / `weekly`,按官方 CLI 同款标签 `5-hour` / `Weekly` 显示,并各自带重置倒计时),以及**余额明细**(月度额度 / 已购额度 / 赠送额度,另附合计)。服务只回报数字不回报名称的字段一律补上可读标签——窗口按 key 命名,余额按池命名,实在没有名字的兜底为 `Extra allowance` 并注明来源,不会渲染成 `meter-1` 这类无意义编号。
377
377
 
378
- **搜索与抓取来源** 切换 DSH 的搜索后端;网页抓取后端在有可用代理时自动改用本插件。选择 ChatGPT 来源后,网页由本插件在 Host 抓取,并使用上述网络代理设置;纯 TUN 模式可使用直连,流量由虚拟网卡接管。来源切换即时生效,DSH web 服务重载后会重新注册插件后端,并保留切回 DSH 默认来源所需的配置。
378
+ **搜索来源** 切换 DSH 的**搜索**后端;网页**抓取**来源由下面的**抓取模式**决定。模式为 `auto`(默认)时跟随此处的搜索选择:选择 ChatGPT 来源后,网页也改由本插件在 Host 抓取,并使用上述网络代理设置;模式为 `dsh` 时即使选了 ChatGPT 搜索,抓取也仍由 DSH 原有来源完成。纯 TUN 模式可使用直连,流量由虚拟网卡接管。来源切换即时生效,DSH web 服务重载后会重新注册插件后端,并保留切回 DSH 默认来源所需的配置。设置页在该选择器下方**只读**展示 Host 上报的抓取来源、切换器状态、抓取模式与插件上限;Host 未提供这些字段(较旧的 Host)时整个区块不显示。
379
379
 
380
- **网页抓取后端(web_fetch)** DSH 内置抓取 provider 会先解析域名、校验并固定解析结果,而且只在进程环境变量里读到代理时才走代理——系统代理对它不可见。代理工具(Clash/Mihomo 等)常把域名解析成自己的 fake-ip 地址(默认 `198.18.0.0/15`),于是内置 provider 直接以 `WEB_BLOCKED_URL`(resolves to a non-public IP address)拒绝,代理根本没被用上。因此只要插件配置了可用代理(**网络代理** 选系统代理且检测到,或填写自定义代理),`web_fetch` 就改用本插件的 provider:由代理解析源站,与 DSH 对“走代理的请求”采用的语义一致;未配置代理时仍由 DSH 内置 provider 抓取,保留其解析与固定策略。若在纯 TUN 模式下把代理设为**直连**,内置 provider 会重新接管,此时可改回系统代理让插件接管抓取。代理如果在 DSH 启动之后才可用(代理工具后启动,或首次探测失败),插件会在下一次探测到代理时重新选择抓取后端,不必重启或改设置。
380
+ **网页抓取后端(web_fetch)** DSH 内置抓取 provider 会先解析域名、校验并固定解析结果,而且只在进程环境变量里读到代理时才走代理——系统代理对它不可见。代理工具(Clash/Mihomo 等)常把域名解析成自己的 fake-ip 地址(默认 `198.18.0.0/15`),于是内置 provider 直接以 `WEB_BLOCKED_URL`(resolves to a non-public IP address)拒绝,代理根本没被用上。因此在 `auto` 模式下,只要插件配置了可用代理(**网络代理** 选系统代理且检测到,或填写自定义代理),`web_fetch` 就改用本插件的 provider:由代理解析源站,与 DSH 对“走代理的请求”采用的语义一致;未配置代理时仍由 DSH 内置 provider 抓取,保留其解析与固定策略。若在纯 TUN 模式下把代理设为**直连**,内置 provider 会重新接管(`auto` 模式下),此时可改回系统代理让插件接管抓取。代理如果在 DSH 启动之后才可用(代理工具后启动,或首次探测失败),插件会在下一次探测到代理时重新选择抓取后端,不必重启或改设置。
381
+
382
+ #### 抓取模式与上限(插件 Config)
383
+
384
+ 抓取**模式**与**上限**属于插件部署配置,不在设置页的偏好里:设置页只读展示当前值,要改写在 profile 的 `cordis.patch.yml` 里,**重载插件或重启 DSH 后生效**。
385
+
386
+ 写在**安装时已有的那一条插件行**上(`id: dsh-chatgpt-subscription`,包名 `@eddyskywalker/dsh-chatgpt-subscription`)的 `config` 片段:
387
+
388
+ ```yaml
389
+ # $DSH_HOME/profiles/<profile>/cordis.patch.yml
390
+ - id: dsh-chatgpt-subscription
391
+ config:
392
+ fetchProvider: auto # auto | plugin | dsh
393
+ fetchMaxBodyChars: 100000 # 解码后正文保留的字符数
394
+ fetchMaxResponseBytes: 2097152 # 响应体保留的字节数(2 MiB)
395
+ ```
396
+
397
+ > patch 按 id 定位条目并**整体替换**它的 `config`,所以在这一行上追加字段时请保留原有的其它字段。
398
+
399
+ | 字段 | 默认 | 含义 |
400
+ |---|---|---|
401
+ | `fetchProvider` | `auto` | `auto` 跟随搜索来源(有可用代理或选了 Codex 搜索时用插件抓取);`plugin` 始终用本插件抓取;`dsh` **强制保留 DSH 原有的抓取来源**,即使有代理或选了 Codex 搜索也不接管 |
402
+ | `fetchMaxBodyChars` | `100000` | 解码后交给工具的正文上限(字符) |
403
+ | `fetchMaxResponseBytes` | `2097152` | 进入解码前的响应体上限(字节) |
404
+
405
+ 两者都必须是**正安全整数**,否则该配置不被接受(DSH 不会把非法值当成 0 使用)。
406
+
407
+ 必须说清的四条边界:
408
+
409
+ - **`fetchMaxResponseBytes` 是“读进来之后再截断”,不是下载量或内存的硬上限。** 实现仍是 `response.arrayBuffer()` 先把整个响应体读进内存,超出部分才被切掉;调小它省不下下载流量和峰值内存,只能限制进入解码与工具结果的内容量。
410
+ - **工具层另有上限。** 这两个数字只管本插件 provider 交给 `web_fetch` 的内容,DSH 工具结果本身还有自己的裁剪预算,因此**调大它们并不保证取回完整页面**。
411
+ - **两个上限只在实际使用插件抓取时生效**;走 DSH 原有抓取来源(`dsh` 模式,或 `auto` 模式下本次抓取没落到插件)时不适用。设置页会直接写明这一点。
412
+ - **plugin 抓取不做 DNS 固定(pinning)。** 内置 provider 会解析并固定解析结果,而代理的 fake-ip DNS 让这一步无法照搬;本插件沿用既有安全模型——只做 `http/https` 与**目标地址不得落在私网/回环**的校验,域名交给解析它的代理解析,解析失败也放行。抬高上限不改变这条边界。
381
413
 
382
414
  卸载前建议先在设置页点击 **“注销”**,它会删除当前平台的凭据和 Host 内存中的额度缓存。
383
415
 
@@ -766,6 +798,8 @@ npm pack --dry-run
766
798
 
767
799
  ### 压缩失败
768
800
 
801
+ Kimi Code、MiniMax Code、Command Code、Claude、WorkBuddy 遇到“输入加输出预留超限”时,插件可在 HTTP 400/422 的完整 token 计数证明输入仍能放下、且上游确实采用了请求输出上限的情况下,仅降低输出上限重试一次。消息、工具和历史保持不变,摘要请求也适用。降低后的输出仍可能截断,截断不会冒充完整摘要。Codex 不支持该输出参数,不走此恢复;输入本身超限、模糊错误或流内错误仍交给 Harness 处理。这不是对所有压缩故障的通用修复。
802
+
769
803
  MiniMax、Kimi、Codex、Claude、Command Code、WorkBuddy 和 Antigravity 的错误处理会将明确的上下文超限错误交给 Harness 的溢出恢复机制。该机制需要宿主启用压缩后端;它不是无条件重发同一个超限请求。认证、配额、请求体字节限制和输出截断不会因此被当作上下文超限。手动压缩仍需生成完整摘要;如果摘要请求本身超限、被截断或缺少正文,修正错误分类也不能保证它成功。请保留失败会话中的 `compaction/end` 错误、provider/model、宿主及插件版本,以便定位。
770
804
 
771
805
  | 现象 | 处理 |
package/lib/client.js CHANGED
@@ -1345,6 +1345,10 @@ window.__ModuleLoader__.load({
1345
1345
  })]
1346
1346
  })]
1347
1347
  }),
1348
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FetchProviderStatusCard, {
1349
+ status,
1350
+ t
1351
+ }),
1348
1352
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1349
1353
  className: "dsha-pref-row",
1350
1354
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("outputVerbosity") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -1624,6 +1628,83 @@ window.__ModuleLoader__.load({
1624
1628
  })]
1625
1629
  });
1626
1630
  }
1631
+ const SWITCHER_STATE_LABELS = {
1632
+ idle: "switcherStateIdle",
1633
+ applying: "switcherStateApplying",
1634
+ applied: "switcherStateApplied",
1635
+ missing: "switcherStateMissing",
1636
+ failed: "switcherStateFailed"
1637
+ };
1638
+ const FETCH_MODE_LABELS = {
1639
+ auto: "fetchModeAuto",
1640
+ plugin: "fetchModePlugin",
1641
+ dsh: "fetchModeDsh"
1642
+ };
1643
+ /**
1644
+ * Read-only view of the fetch selection, shown under the search source.
1645
+ *
1646
+ * Both blocks are optional on the status DTO, so every row is rendered only from
1647
+ * a value the host actually reported. That distinction is load-bearing: a
1648
+ * missing `switcher` means the host tells us nothing and the row stays hidden,
1649
+ * while a reported `configuredFetchProvider: null` is a real answer — the host
1650
+ * pinned no provider, which is the DSH default. Guessing an id there would claim
1651
+ * a provider the settings page never observed.
1652
+ *
1653
+ * Nothing here is editable: the mode and the limits live in the plugin Config,
1654
+ * not in the preferences this card sits in, so the numbers cannot be mistaken for
1655
+ * a setting that this page can change.
1656
+ */
1657
+ function FetchProviderStatusCard({ status, t }) {
1658
+ const switcher = status?.switcher;
1659
+ const configuration = status?.fetchConfiguration;
1660
+ if (switcher === void 0 && configuration === void 0) return null;
1661
+ const state = switcher !== null && switcher !== void 0 && switcher.state in SWITCHER_STATE_LABELS ? switcher.state : null;
1662
+ const mode = configuration !== void 0 && configuration.fetchProvider in FETCH_MODE_LABELS ? configuration.fetchProvider : void 0;
1663
+ const maxBodyChars = fetchLimit(configuration?.fetchMaxBodyChars);
1664
+ const maxResponseBytes = fetchLimit(configuration?.fetchMaxResponseBytes);
1665
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1666
+ className: "dsha-context-settings",
1667
+ "data-fetch-status": state ?? "none",
1668
+ children: [
1669
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: t("fetchStatus") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1670
+ className: "dsha-muted",
1671
+ children: t("fetchStatusHint")
1672
+ })] }),
1673
+ switcher !== null && switcher !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
1674
+ label: t("fetchConfiguredProvider"),
1675
+ value: switcher.configuredFetchProvider || t("fetchProviderDshDefault")
1676
+ }) : null,
1677
+ state !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
1678
+ label: t("switcherState"),
1679
+ value: t(SWITCHER_STATE_LABELS[state])
1680
+ }) : null,
1681
+ mode !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
1682
+ label: t("fetchMode"),
1683
+ value: t(FETCH_MODE_LABELS[mode])
1684
+ }) : null,
1685
+ maxBodyChars !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
1686
+ label: t("fetchMaxBodyChars"),
1687
+ value: `${maxBodyChars} ${t("characters")}`
1688
+ }) : null,
1689
+ maxResponseBytes !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InfoRow, {
1690
+ label: t("fetchMaxResponseBytes"),
1691
+ value: `${maxResponseBytes} ${t("bytes")}`
1692
+ }) : null,
1693
+ mode !== void 0 && mode !== "plugin" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1694
+ className: "dsha-muted",
1695
+ children: t("fetchLimitsInactive")
1696
+ }) : null,
1697
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
1698
+ className: "dsha-muted",
1699
+ children: t("fetchConfigSource")
1700
+ })
1701
+ ]
1702
+ });
1703
+ }
1704
+ /** A limit is shown only once it is the positive safe integer the Config promises. */
1705
+ function fetchLimit(value) {
1706
+ return value !== void 0 && Number.isSafeInteger(value) && value >= 1 ? value : null;
1707
+ }
1627
1708
  function storageLabel$1(storage, t) {
1628
1709
  if (storage === void 0 || !storage.available) return t("storageUnavailable");
1629
1710
  if (storage.kind === "windows-dpapi") return t("storageWindows");
@@ -9527,10 +9608,30 @@ window.__ModuleLoader__.load({
9527
9608
  summaryConcise: "简洁(显示简要推理摘要)",
9528
9609
  summaryDetailed: "详细(显示更详细的推理摘要)",
9529
9610
  summaryNone: "无(不显示推理摘要)",
9530
- searchProvider: "搜索与抓取来源",
9531
- searchProviderHint: "选择 DSH 联网工具使用默认来源,或改用当前 ChatGPT 订阅的 Codex 来源(自动走代理隧道)。",
9611
+ searchProvider: "搜索来源",
9612
+ searchProviderHint: "选择 DSH 联网搜索使用默认来源,或改用当前 ChatGPT 订阅的 Codex 来源(自动走代理隧道)。抓取模式为 auto 时,抓取来源跟随此选择。",
9532
9613
  searchProviderDsh: "DSH 默认",
9533
9614
  searchProviderCodex: "ChatGPT(代理隧道)",
9615
+ fetchStatus: "抓取来源(只读)",
9616
+ fetchStatusHint: "以下内容由 Host 上报,只读展示。",
9617
+ fetchConfiguredProvider: "配置的抓取 Provider",
9618
+ fetchProviderDshDefault: "DSH 默认(Host 未固定抓取 provider)",
9619
+ switcherState: "抓取选择器状态",
9620
+ switcherStateIdle: "空闲(尚未应用选择)",
9621
+ switcherStateApplying: "应用中(正在切换 Host 的 web provider)",
9622
+ switcherStateApplied: "已应用",
9623
+ switcherStateMissing: "未找到可切换的 web 服务",
9624
+ switcherStateFailed: "切换失败,请检查 Host 日志",
9625
+ fetchMode: "抓取模式",
9626
+ fetchModeAuto: "auto(有可用代理或选了 ChatGPT 搜索时用插件抓取)",
9627
+ fetchModePlugin: "plugin(始终用本插件抓取)",
9628
+ fetchModeDsh: "dsh(保留 DSH 原有抓取来源)",
9629
+ fetchMaxBodyChars: "插件正文上限",
9630
+ fetchMaxResponseBytes: "插件响应体上限",
9631
+ characters: "字符",
9632
+ bytes: "字节",
9633
+ fetchLimitsInactive: "仅在实际使用插件抓取时生效;走 DSH 原有抓取来源时不适用这两个上限。",
9634
+ fetchConfigSource: "模式与上限在插件 Config 中修改(不是本页面偏好),修改后重载插件或重启 DSH 生效。",
9534
9635
  providerDefault: "使用 Provider 默认值",
9535
9636
  contextWindows: "模型上下文窗口",
9536
9637
  contextWindowsHint: "只列出已勾选「可用模型」的条目。默认取模型目录值(GPT-6 系列 384K,最高 872K;其余最高 1M),该值用于 DSH 的压缩与溢出判断,可输入 872K、512K 等容量;改过的模型可单独或一键恢复默认。",
@@ -9660,10 +9761,30 @@ window.__ModuleLoader__.load({
9660
9761
  summaryConcise: "Concise (brief summary)",
9661
9762
  summaryDetailed: "Detailed (in-depth summary)",
9662
9763
  summaryNone: "None (hide reasoning summary)",
9663
- searchProvider: "Search & Fetch provider",
9664
- searchProviderHint: "Choose whether DSH web tools use the default provider or the Codex proxy-backed source from this ChatGPT subscription.",
9764
+ searchProvider: "Search provider",
9765
+ searchProviderHint: "Choose whether DSH web search uses the default provider or the Codex proxy-backed source from this ChatGPT subscription. Fetch mode \"auto\" follows this choice.",
9665
9766
  searchProviderDsh: "DSH default",
9666
9767
  searchProviderCodex: "ChatGPT (Proxy tunnel)",
9768
+ fetchStatus: "Fetch source (read-only)",
9769
+ fetchStatusHint: "Reported by the host. Read-only.",
9770
+ fetchConfiguredProvider: "Configured fetch provider",
9771
+ fetchProviderDshDefault: "DSH default (the host pins no fetch provider)",
9772
+ switcherState: "Fetch switcher state",
9773
+ switcherStateIdle: "Idle (no selection applied yet)",
9774
+ switcherStateApplying: "Applying (host web provider switch in progress)",
9775
+ switcherStateApplied: "Applied",
9776
+ switcherStateMissing: "No web service found to switch",
9777
+ switcherStateFailed: "Switch failed, check the host log",
9778
+ fetchMode: "Fetch mode",
9779
+ fetchModeAuto: "auto (fetches with the plugin when a proxy is available or the ChatGPT search source is selected)",
9780
+ fetchModePlugin: "plugin (always fetch with this plugin)",
9781
+ fetchModeDsh: "dsh (keep the DSH default fetch source)",
9782
+ fetchMaxBodyChars: "Plugin body limit",
9783
+ fetchMaxResponseBytes: "Plugin response limit",
9784
+ characters: "chars",
9785
+ bytes: "bytes",
9786
+ fetchLimitsInactive: "Only applied when the plugin actually fetches; the DSH default fetch source does not use these two limits.",
9787
+ fetchConfigSource: "Mode and limits are changed in the plugin Config, not in these settings; they take effect after a plugin reload or a DSH restart.",
9667
9788
  providerDefault: "Use provider default",
9668
9789
  contextWindows: "Model context windows",
9669
9790
  contextWindowsHint: "Lists the models checked under Available models. Each starts at its catalog value (384K for the GPT-6 family, up to 872K; up to 1M otherwise); DSH uses it for compaction and overflow detection. Values such as 872K and 512K are accepted, and a changed model can be restored one by one or all at once.",
@@ -9723,9 +9844,45 @@ window.__ModuleLoader__.load({
9723
9844
  }
9724
9845
  };
9725
9846
  //#endregion
9726
- //#region src/client/styles.ts
9847
+ //#region src/client/common/plugin-style.ts
9848
+ /**
9849
+ * Shared installer for every stylesheet this plugin injects.
9850
+ *
9851
+ * The DSH client module system books stylesheet ownership by attribute. When a
9852
+ * package's factory materializes it claims every untagged `<style>` in the
9853
+ * document for itself (`style:not([data-plugin])` gains that package's id); when
9854
+ * a package reloads, is replaced at a new revision, or is pruned from the graph,
9855
+ * it removes every tag carrying its id. A sheet injected as a plain
9856
+ * `<style id=…>` therefore gets claimed by whichever sibling package
9857
+ * materializes next and is then deleted as that sibling's bookkeeping — which is
9858
+ * how the whole shared provider design system (`.dsha-*`) disappeared from the
9859
+ * settings page while the already-tagged chat-only `…/main` sheet survived.
9860
+ *
9861
+ * Tagging every sheet with this package's own `data-plugin` and a unique
9862
+ * `data-plugin-css` keeps the module system from attributing it to another
9863
+ * package, and re-running the installer on every `apply()` restores it after one
9864
+ * of our own reloads. The tag is owned by the document: the installer refreshes
9865
+ * stale text but never removes the element, so a stale fiber's teardown can
9866
+ * never take the styles away.
9867
+ */
9727
9868
  const PLUGIN_ID = "@eddyskywalker/dsh-chatgpt-subscription";
9728
- const STYLE_ID$5 = `${PLUGIN_ID}/main`;
9869
+ function installPluginStyle(name, css, legacyId) {
9870
+ if (typeof document === "undefined") return () => void 0;
9871
+ const pluginCss = `${PLUGIN_ID}/${name}`;
9872
+ let element = document.querySelector(`style[data-plugin-css="${pluginCss}"]`);
9873
+ element ??= legacyId === void 0 ? null : document.getElementById(legacyId);
9874
+ if (element === null) {
9875
+ element = document.createElement("style");
9876
+ if (legacyId !== void 0) element.id = legacyId;
9877
+ document.head.appendChild(element);
9878
+ }
9879
+ element.dataset.plugin = PLUGIN_ID;
9880
+ element.dataset.pluginCss = pluginCss;
9881
+ if (element.textContent !== css) element.textContent = css;
9882
+ return () => void 0;
9883
+ }
9884
+ //#endregion
9885
+ //#region src/client/styles.ts
9729
9886
  const CSS = `
9730
9887
  .dsh-codex-page{box-sizing:border-box;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:20px;max-width:780px;min-width:0;padding:2px 0 30px}
9731
9888
  .dsh-codex-page *{box-sizing:border-box}
@@ -9873,29 +10030,20 @@ window.__ModuleLoader__.load({
9873
10030
  * create it when missing, refresh `textContent` when a rebuilt bundle ships
9874
10031
  * different CSS (HMR), and return a no-op disposer unconditionally, matching
9875
10032
  * the additive installers.
10033
+ *
10034
+ * Every other sheet this plugin injects now goes through the same installer
10035
+ * (`installPluginStyle`), which additionally stamps the ownership attributes
10036
+ * the client module system needs so the tag cannot be claimed by, and later
10037
+ * removed with, some other package.
9876
10038
  */
9877
10039
  function installStyles() {
9878
- if (typeof document === "undefined") return () => void 0;
9879
- const existing = document.querySelector(`style[data-plugin-css="${STYLE_ID$5}"]`);
9880
- if (existing !== null) {
9881
- if (existing.textContent !== CSS) existing.textContent = CSS;
9882
- return () => void 0;
9883
- }
9884
- const element = document.createElement("style");
9885
- element.dataset.plugin = PLUGIN_ID;
9886
- element.dataset.pluginCss = STYLE_ID$5;
9887
- element.textContent = CSS;
9888
- document.head.appendChild(element);
9889
- return () => void 0;
10040
+ return installPluginStyle("main", CSS);
9890
10041
  }
9891
10042
  //#endregion
9892
10043
  //#region src/client/antigravity/styles.ts
9893
10044
  const STYLE_ID$4 = "dsh-antigravity-settings-style";
9894
10045
  function installAntigravityStyles() {
9895
- if (typeof document === "undefined" || document.getElementById("dsh-antigravity-settings-style")) return;
9896
- const style = document.createElement("style");
9897
- style.id = STYLE_ID$4;
9898
- style.textContent = `
10046
+ installPluginStyle("antigravity", `
9899
10047
  .dsha-page{box-sizing:border-box;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:20px;max-width:780px;min-width:0;padding:2px 0 30px}
9900
10048
  .dsha-page *{box-sizing:border-box}
9901
10049
  .dsha-group{border-top:0.5px solid var(--dsw-alias-border-l2);min-width:0}
@@ -9969,8 +10117,7 @@ window.__ModuleLoader__.load({
9969
10117
  .dsha-account-details{display:flex;flex-wrap:wrap;gap:14px;font-size:12px;color:var(--dsw-alias-label-secondary)}
9970
10118
  .dsha-account-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:2px}
9971
10119
  .dsha-account-actions .dsha-btn{padding:5px 10px;font-size:12px}
9972
- `;
9973
- document.head.append(style);
10120
+ `, STYLE_ID$4);
9974
10121
  }
9975
10122
  //#endregion
9976
10123
  //#region src/client/common/styles.ts
@@ -9984,14 +10131,10 @@ window.__ModuleLoader__.load({
9984
10131
  * them, so nothing that already rendered changes appearance.
9985
10132
  */
9986
10133
  function installPoolStyles() {
9987
- if (typeof document === "undefined" || document.getElementById("dsh-provider-pool-style")) return;
9988
- const style = document.createElement("style");
9989
- style.id = POOL_STYLE_ID;
9990
- style.textContent = `
10134
+ installPluginStyle("pool", `
9991
10135
  .dsha-badge.danger{background:color-mix(in srgb,var(--dsw-alias-label-danger,#d94b4b) 18%,var(--dsw-alias-bg-layer-1));color:var(--dsw-alias-label-danger,#d94b4b);border:0.5px solid color-mix(in srgb,var(--dsw-alias-label-danger,#d94b4b) 40%,transparent)}
9992
10136
  .dsha-account-card .dsha-btn.danger{color:var(--dsw-alias-label-danger,#d94b4b)}
9993
- `;
9994
- document.head.append(style);
10137
+ `, POOL_STYLE_ID);
9995
10138
  }
9996
10139
  //#endregion
9997
10140
  //#region src/client/antigravity/AntigravityComposerQuota.tsx
@@ -10227,10 +10370,7 @@ window.__ModuleLoader__.load({
10227
10370
  * markers, neither of which a sibling line renders.
10228
10371
  */
10229
10372
  function installClaudeStyles() {
10230
- if (typeof document === "undefined" || document.getElementById("dsh-claude-settings-style")) return;
10231
- const style = document.createElement("style");
10232
- style.id = STYLE_ID$3;
10233
- style.textContent = `
10373
+ installPluginStyle("claude", `
10234
10374
  .dsha-badge.claude-adopted{background:color-mix(in srgb,var(--dsw-alias-button-info-fill,#397ee8) 16%,var(--dsw-alias-bg-layer-1));border-color:color-mix(in srgb,var(--dsw-alias-button-info-fill,#397ee8) 45%,var(--dsw-alias-border-l2));color:var(--dsw-alias-label-primary)}
10235
10375
  .dshcl-flow{border-bottom:0.5px solid var(--dsw-alias-border-l2);display:flex;flex-direction:column;gap:10px;padding:12px 0}
10236
10376
  .dshcl-flow-url{background:var(--dsw-alias-bg-layer-1,rgba(127,127,127,.14));border:0.5px solid var(--dsw-alias-border-l2);border-radius:7px;color:var(--dsw-alias-label-primary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;overflow-wrap:anywhere;padding:8px 10px;user-select:all}
@@ -10240,8 +10380,7 @@ window.__ModuleLoader__.load({
10240
10380
  .dshcl-mono{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;overflow-wrap:anywhere}
10241
10381
  .dshcl-model-meta{color:var(--dsw-alias-label-tertiary);font-size:11px;margin-left:5px}
10242
10382
  .dshcl-empty-inline{color:var(--dsw-alias-label-tertiary);font-size:12px}
10243
- `;
10244
- document.head.append(style);
10383
+ `, STYLE_ID$3);
10245
10384
  }
10246
10385
  //#endregion
10247
10386
  //#region src/client/command-code/CommandCodeComposerQuota.tsx
@@ -10560,11 +10699,7 @@ window.__ModuleLoader__.load({
10560
10699
  `;
10561
10700
  /** Kimi-specific rules layered on top of the shared control styles. */
10562
10701
  function installKimiCodeStyles() {
10563
- if (typeof document === "undefined" || document.getElementById("dsh-kimi-code-settings-style")) return;
10564
- const style = document.createElement("style");
10565
- style.id = STYLE_ID$2;
10566
- style.textContent = BASE;
10567
- document.head.appendChild(style);
10702
+ installPluginStyle("kimi-code", BASE, STYLE_ID$2);
10568
10703
  }
10569
10704
  //#endregion
10570
10705
  //#region src/client/minimax-code/MinimaxCodeComposerQuota.tsx
@@ -10662,16 +10797,12 @@ window.__ModuleLoader__.load({
10662
10797
  * credential path and the model-name list.
10663
10798
  */
10664
10799
  function installMinimaxCodeStyles() {
10665
- if (typeof document === "undefined" || document.getElementById("dsh-minimax-code-settings-style")) return;
10666
- const style = document.createElement("style");
10667
- style.id = STYLE_ID$1;
10668
- style.textContent = `
10800
+ installPluginStyle("minimax-code", `
10669
10801
  .dshm-mono{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}
10670
10802
  .dshm-model-list{display:flex;flex-wrap:wrap;gap:7px;padding-top:8px}
10671
10803
  .dshm-model-list code{background:var(--dsw-alias-bg-layer-2);border:0.5px solid var(--dsw-alias-border-l2);border-radius:6px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;padding:5px 8px;white-space:nowrap}
10672
10804
  .dshm-quota-note{margin-top:12px}
10673
- `;
10674
- document.head.append(style);
10805
+ `, STYLE_ID$1);
10675
10806
  }
10676
10807
  //#endregion
10677
10808
  //#region src/client/workbuddy/WorkBuddyComposerQuota.tsx
@@ -10794,16 +10925,12 @@ window.__ModuleLoader__.load({
10794
10925
  * every sibling tab, and the account card is the shared one.
10795
10926
  */
10796
10927
  function installWorkBuddyStyles() {
10797
- if (typeof document === "undefined" || document.getElementById("dsh-workbuddy-settings-style")) return;
10798
- const style = document.createElement("style");
10799
- style.id = STYLE_ID;
10800
- style.textContent = `
10928
+ installPluginStyle("workbuddy", `
10801
10929
  .dshwb-mono{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px}
10802
10930
  .dsha-account-add-actions{display:flex;flex-wrap:wrap;gap:8px}
10803
10931
  .dsha-account-add-actions .dsha-btn{background:var(--dsw-alias-button-info-fill,#397ee8);border-color:transparent;color:var(--dsw-alias-button-info-label,#fff)}
10804
10932
  .dsha-account-add-actions .dsha-btn:hover:not(:disabled){opacity:.9;background:var(--dsw-alias-button-info-fill,#397ee8)}
10805
- `;
10806
- document.head.append(style);
10933
+ `, STYLE_ID);
10807
10934
  }
10808
10935
  //#endregion
10809
10936
  //#region src/client/mermaid/sanitize.ts
@@ -10854,10 +10981,7 @@ window.__ModuleLoader__.load({
10854
10981
  //#region src/client/mermaid/styles.ts
10855
10982
  const MERMAID_STYLE_ID = "dsh-mermaid-renderer-style";
10856
10983
  function installMermaidStyles() {
10857
- if (typeof document === "undefined" || document.getElementById("dsh-mermaid-renderer-style")) return;
10858
- const style = document.createElement("style");
10859
- style.id = MERMAID_STYLE_ID;
10860
- style.textContent = `
10984
+ installPluginStyle("mermaid", `
10861
10985
  .dsh-mermaid-wrapper{margin:12px 0;border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,0.12));border-radius:8px;overflow:hidden;background:var(--dsw-alias-bg-layer-2,#1a1a1a);box-shadow:0 2px 8px rgba(0,0,0,0.15)}
10862
10986
  .dsh-mermaid-header{display:flex;align-items:center;justify-content:space-between;padding:6px 12px;background:var(--dsw-alias-bg-layer-1,rgba(0,0,0,0.25));border-bottom:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,0.08));font-size:12px;color:var(--dsw-alias-label-secondary,#888)}
10863
10987
  .dsh-mermaid-title{font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:11px;text-transform:uppercase;letter-spacing:0.5px;color:var(--dsw-alias-label-tertiary,#aaa);font-weight:600}
@@ -10876,8 +11000,7 @@ window.__ModuleLoader__.load({
10876
11000
  .dsh-mermaid-modal-btn:hover{background:rgba(255,255,255,0.15)}
10877
11001
  .dsh-mermaid-modal-stage{flex:1;width:100%;height:100%;overflow:auto;display:flex;justify-content:center;align-items:center;padding:40px}
10878
11002
  .dsh-mermaid-modal-stage svg{max-width:90vw;max-height:85vh;height:auto}
10879
- `;
10880
- document.head.append(style);
11003
+ `, MERMAID_STYLE_ID);
10881
11004
  }
10882
11005
  //#endregion
10883
11006
  //#region src/client/mermaid/renderer.ts