@eddyskywalker/dsh-chatgpt-subscription 0.10.0 → 0.10.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 (44) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/README.md +39 -1
  3. package/lib/client.js +1569 -205
  4. package/lib/client.js.map +1 -1
  5. package/lib/index.js +4775 -1030
  6. package/lib/types/client/ProviderHubSection.d.ts.map +1 -1
  7. package/lib/types/client/index.d.ts +2 -0
  8. package/lib/types/client/index.d.ts.map +1 -1
  9. package/lib/types/client/minimax-code/MinimaxCodeComposerQuota.d.ts +27 -0
  10. package/lib/types/client/minimax-code/MinimaxCodeComposerQuota.d.ts.map +1 -0
  11. package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts +49 -0
  12. package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts.map +1 -0
  13. package/lib/types/client/minimax-code/api.d.ts +5 -0
  14. package/lib/types/client/minimax-code/api.d.ts.map +1 -0
  15. package/lib/types/client/minimax-code/locales.d.ts +341 -0
  16. package/lib/types/client/minimax-code/locales.d.ts.map +1 -0
  17. package/lib/types/client/minimax-code/styles.d.ts +12 -0
  18. package/lib/types/client/minimax-code/styles.d.ts.map +1 -0
  19. package/lib/types/client/styles.d.ts.map +1 -1
  20. package/lib/types/compat.d.ts +1 -0
  21. package/lib/types/compat.d.ts.map +1 -1
  22. package/lib/types/host/minimax-code/account-pool.d.ts +219 -0
  23. package/lib/types/host/minimax-code/account-pool.d.ts.map +1 -0
  24. package/lib/types/host/minimax-code/adapter.d.ts +168 -0
  25. package/lib/types/host/minimax-code/adapter.d.ts.map +1 -0
  26. package/lib/types/host/minimax-code/client.d.ts +184 -0
  27. package/lib/types/host/minimax-code/client.d.ts.map +1 -0
  28. package/lib/types/host/minimax-code/mapper.d.ts +143 -0
  29. package/lib/types/host/minimax-code/mapper.d.ts.map +1 -0
  30. package/lib/types/host/minimax-code/model-catalog.d.ts +146 -0
  31. package/lib/types/host/minimax-code/model-catalog.d.ts.map +1 -0
  32. package/lib/types/host/minimax-code/oauth.d.ts +204 -0
  33. package/lib/types/host/minimax-code/oauth.d.ts.map +1 -0
  34. package/lib/types/host/minimax-code/routes.d.ts +79 -0
  35. package/lib/types/host/minimax-code/routes.d.ts.map +1 -0
  36. package/lib/types/host/minimax-code/token-store.d.ts +238 -0
  37. package/lib/types/host/minimax-code/token-store.d.ts.map +1 -0
  38. package/lib/types/host/minimax-code/types.d.ts +206 -0
  39. package/lib/types/host/minimax-code/types.d.ts.map +1 -0
  40. package/lib/types/index.d.ts +10 -0
  41. package/lib/types/index.d.ts.map +1 -1
  42. package/lib/types/shared/minimax-code-contracts.d.ts +166 -0
  43. package/lib/types/shared/minimax-code-contracts.d.ts.map +1 -0
  44. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,51 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ - **找到并接上 mcode 自己的用量端点(用户要求「在我们这查看」)**。上一轮打的 `/v1/token_plan/remains` 是**平台**端点(只认平台 API 密钥),所以必然被拒。真正的端点在**官方客户端源码**里:`packages/tui/src/account/matrix-account-client.ts` 用 `GET https://agent.minimaxi.com/v1/api/openplatform/coding_plan/remains`(国际 `agent.minimax.io`)——**路径与平台端点完全不同**。现已改为该路径,主机按「本线路自己的 `agent.minimax.cn` 优先、官方主机兜底」探测并记住命中者。
6
+ - ⚠️ **该请求带四个「官方客户端标识头」**:`yy` / `x-timestamp` / `x-signature` / `User-Agent: MiniMaxCode`。官方源码的注释自己写明这些字面量是「把请求标记为来自 MiniMax 第一方客户端」(同时注明它们并非凭据、也非安全边界,鉴权仍是 Bearer 令牌)。本插件 `types.ts` 已明确决定**不伪造官方客户端身份**,因此**默认一律不发**这些头——宁可拿到一个拒绝让卡片如实说明,也不冒充官方应用;确实要数字的用户可设 `DSH_MINIMAX_CODE_QUOTA_ATTRIBUTION=1` 显式选择该取舍。新增测试锁定该默认:未开启时四个头一个都不出现、`user-agent` 不是 `MiniMaxCode`;开启后恰好出现该组。
7
+ - **诚实失败的三条保留**:`base_resp` 非 0 视为凭据被拒、只试一台就停、记住 30 分钟;其它失败按 `unreachable` 区分、记住 10 分钟;`/status` 只读缓存、绝不在轮询里发网络请求。
8
+ - **验证**:`npm run typecheck` 0 错误、`npm run build` 成功、测试 **1685 passed / 7 skipped**(配额测试 20 条),仍只剩 6 条既有的 Windows Antigravity 回调端口失败。⚠️ **端到端仍未验证**:探测当下本机凭据文件已被登出清掉,所以「默认不带标识头能否通过」还需用户重新登录后实测。
9
+
10
+ - **实测确认:MiniMax Code 的登录态读不到用量,卡片改为说明真实原因(用户在国内、配额条未出现)**。上一轮接上的 `/v1/token_plan/remains` 在生产凭据下**读不出来**,本轮用本机真实凭据(国内区)把它查清了:
11
+ - **端点确实存在**:`api.minimax.cn` / `api.minimaxi.com` / `api.minimax.io` 三个主机都返回 JSON,**不是 404**。
12
+ - **但它只接受平台 API 密钥**:本线路持有的 `mcode-public`(MiniMax Code)登录态在**四种认证写法**下全部被拒——`Authorization: Bearer`、原始令牌、`x-api-key`、以及两者同时携带——而且**是 HTTP 200 + `base_resp.status_code: 1004`**(`"login fail: Please carry the API secret key in the 'Authorization' field"`)。**关键教训**:只看 `response.ok` 会把这次拒绝当成成功。
13
+ - **官方 CLI 能用是因为它的 OAuth 是「平台」登录**(`account.minimax.io` / `account.minimaxi.com`),与本线路的 `account.minimax.cn` mcode 登录是**两套不同的身份**;`mmx quota show` 的凭据分流(OAuth → 配额端点)只对平台 OAuth 成立。
14
+ - **mavis 后端没有用量路径**:探测了 `agent.minimax.cn/mavis/api/v1/` 下的 `token_plan/remains`、`usage`、`quota`、`user/quota`、`user/usage`、`account/quota`、`coding_plan/remains`、`subscription` 等,**全部 404**(而 `llm/v1/token_plan/remains` 返回的是 JSON 404,说明该前缀存在但无此路由)。
15
+ - **因此重构为「诚实的失败」**:新增 `quotaUnavailable` 状态字段与 `credential-not-accepted` / `unreachable` 两种原因,卡片区分显示——前者直接告诉用户"该登录态读不到用量、请在控制台或 MiniMax Code 应用查看",不再含糊地说"暂无数据"。三个行为修正:① **识别 `base_resp` 拒绝后只试一台就停**(这个结论与主机无关,试遍候选纯属浪费);② 此类拒绝**记住 30 分钟**(不是成功的 60 秒——重新登录会清掉快照,因为那是新凭据);③ 失败原因不再被误当作"主机不对"。
16
+ - **新增 2 条测试**(共 19 条):`base_resp` 拒绝被识别为 `credential-not-accepted` 且只发一台;`unreachable` 与前者区分。
17
+ - **验证**:`npm run typecheck` 0 错误、`npm run build` 成功、测试 **1684 passed / 7 skipped**,仍只剩 6 条既有的 Windows Antigravity 回调端口失败。
18
+
19
+ - **MiniMax Code 接上用量的配额条(用户追问「用量和配额查不到吗」)**。原卡片断言「MiniMax Code 未提供用量查询端点」,**这句话是错的**,会把读者挡在一个确实存在的东西之外。端点不在 API 文档里——文档只说"用量显示在控制台的用量条上"——它存在于**官方 CLI 的源码**中:`mmx quota show`("Display Token Plan usage and remaining quotas")读 `GET {baseUrl}/v1/token_plan/remains`,且**凭据分流**是 OAuth 令牌走配额端点、只有 `sk-api-` 密钥才走 `/account/query_balance`——前者正是本线路实现的那套 RFC 8628 + PKCE。现已接入:
20
+ - **两个窗口分开显示**:返回的每个 `model_remains` 行同时带 5 小时滚动窗口与每周窗口,卡片渲染两根条,各自显示剩余百分比、重置时刻与"已用/总量"。
21
+ - **照抄官方实现的三处细节,缺一处就会显示错数**:① **`*_usage_count` 的语义是模糊的**——官方注释写明老响应把该字段当「剩余」、新响应当「已用」,必须用显式百分比消歧(偏差 >1% 时干脆放弃计数、只显示百分比),照抄否则**进度条会反过来**(红绿验证:改成朴素读法后 3 条测试失败,750 vs 250);② **周窗口带显示倍率** `weekly_boost_permille`(渲染值 = 百分比 × 倍率/1000,可超过 100%,所以上限取 200 而非 100),5 小时窗口没有这个字段;③ `status: 3` 通常是「不限量」,但**两个总量都为 0 时**它表示「当前套餐不含该模型」——渲染成不限量会凭空许诺额度,这种行直接跳过。
22
+ - **主机是唯一未实测的部分,因此做了候选探测**:CLI 用 `api.minimax.io`(国际)/ `api.minimaxi.com`(国内),而本线路的 agent 主机是 `agent.minimax.cn`,两套域名不一致且无法在本地实测,所以按候选顺序探测并**记住命中的那台**,可用 `DSH_MINIMAX_CODE_QUOTA_HOST` 固定。
23
+ - **读用量永远不可能影响对话**:这是独立的只读路径,失败只让卡片显示"暂无数据"(`quota` 本是可选字段)。三个防护:`/status` **只读缓存、绝不在轮询里发网络请求**(用量刷新在应答之后进行,否则主机不可达时每次轮询都会卡一个超时);**失败会被记住 10 分钟**(成功 60 秒),避免主机不对时每次轮询都白探两遍;`/quota` 的失败也返回 200。
24
+ - **新增 17 条测试**(`test/minimax-code-quota.test.ts`):双窗口映射、两种计数字段编码落到同一结果、计数不自洽时放弃、不限量与"不在套餐内"的区分、周窗口倍率、`general` 桶优先、候选回退与命中记忆、Bearer + JSON 头、TTL 与失败退避、无凭据不读、`/status` 不碰网络、跨域拒绝。
25
+ - **验证**:`npm run typecheck` 0 错误、`npm run build` 成功、测试 **1682 passed / 7 skipped**(较上轮 +17),仍只剩 6 条既有的 Windows Antigravity 回调端口失败。
26
+
27
+ - **MiniMax Code 线路补齐与兄弟线路对等的功能面(用户指出「没有和图 2 的线路保持一样的功能」)**。上一轮只修了缺陷,但这条线路**缺了兄弟线路都有的五块功能**,这是功能缺失而非风格问题,现全部补上:
28
+ - **① 启用供应商总开关**。新增 `MinimaxCodeModelSettings.enabled`,卡片在「连接」组首行渲染它。关闭时适配器 `listModels()` 返回**空数组**——「关掉」必须意味着不提供任何模型,包括已勾选的:如果关掉还能用,这个开关就没有意义。`/status` 仍返回全部目录行(每行自带 `enabled`),否则卡片无法显示「哪些可以重新打开」。
29
+ - **② 模型勾选(含全选 / 全不选)**。`MinimaxCodeModelOption` 取代了原先的 `models: readonly string[]`:卡片现在能渲染带勾选框的模型网格,而不是一串只读的 `<code>` 标签。适配器的 `listModels()` 只暴露被勾选的子集,所以勾选状态真的决定对话页模型选择器里出现什么。
30
+ - **③ 模型上下文窗口覆盖**。每个已勾选模型可填自定义容量(`1M` / `512K` / `200000`),host 侧用共享的 `mergeContextWindowOverrides` 合并;「恢复默认」以 `null` 语义**删除**该键而不是写入 0(写 0 会让压缩与溢出判断以为窗口为零)。生效窗口**同一条路径**同时决定卡片显示值与请求的输出上限,避免两处各算一个数。
31
+ - **④ 默认思考深度**。全局档位只在模型确实列了该档时生效,否则回落到该模型自己的默认档——**广告一个模型会拒绝的档位只会让服务端拒掉整个请求**;「关闭思考」也只对**可关闭**(非 `always-on`)的模型生效,因为 M2.7 系列根本没有关闭这个状态可选。
32
+ - **⑤ 号池管理与多账号登录**。新增 `src/host/minimax-code/account-pool.ts`,复用共享内核 `AccountPoolCore` 与共享账号卡片,具备顺序耗尽 / 轮询 / 粘性调度、429 冷却换号、设为主账号、别名、`removable` 语义。**身份键不是令牌**:桌面端凭据用 `recordKey`(同一记录槽每次轮换都是同一账号 → 原地更新),插件自持凭据用 `loginEpoch`(每次设备码登录是一次独立会话);用刷新令牌做键会让同一账号每次轮换后都变成「新账号」。
33
+ - **⑥ 桌面端登录态可显式导入号池**(`POST /accounts {action:'adopt'}`):只读、只新增一行,不改动官方客户端任何文件;重复导入按记录槽原地更新。**并且在设置页新发起的设备码登录会自动加入号池**——这需要给 `pollWebLogin` 加一个 `onSave` 钩子,否则新登录只写进镜像文件而号池看不见,这正是「多账号登录没配好」的根因。该钩子失败**不会**让登录失败,因为凭据此时已经落盘。
34
+ - **桌面端账号依旧不可删除**:沿用 WorkBuddy 的成例,`deleteAccount` 对 `minimax-native` 账号直接拒绝,卡片据 `removable: false` 不提供删除按钮。**删除路径已有测试逐条证明** `~/.minimax/auth/<env>/<region>/mcode-public/auth.json` 逐字节不变(含 `schemaVersion` 与记录键);桌面端账号的**续期仍会原子写回**该文件——刷新令牌会轮换,不写回官方客户端手里会留着一枚已被本插件用掉的令牌,下一次任一侧刷新都会失败。
35
+ - **附带修掉的两个真实缺陷**(由实现者发现并修复):`extendSummary` 最初**重建**摘要对象而不是展开 `base`,会静默丢掉内核推导出的 `authStatus`/`expired` 标记,让一个已失效凭据继续留在轮换里;`parseMinimaxCodePoolData` 最初让一行坏数据**抛错**,而 `AccountPoolCore.read` 会把它当成整个池读取失败(整池退回单凭据投影)——现在坏行被跳过、其余照常存活。
36
+ - **验证**:`npm run typecheck`(含 test 项目)0 错误;`npm run build` 成功;测试 **1657 passed / 7 skipped**(新增 31 条:号池 18、设置与号池集成 9、卡片 UI 4),只剩 6 条**既有的** Antigravity 回调端口失败(本机 Windows 保留端口段所致,与本次改动无关)。号池测试做过**变异验证**:把 `removable` 强制为 true → 3 条失败;允许删除原生账号 → 2 条失败;把刷新区域硬编码为 cn → 1 条失败。
37
+ - **新增 MiniMax Code(编程订阅)线路,并在合并前修正该 PR 自带的缺陷**(PR #23,原作者 Anuii)。本线路注册 `minimax-code` Provider,凭据**只读复用** MiniMax Code 桌面端的 `~/.minimax/auth/.../auth.json`(无桌面端凭据时可用 RFC 8628 设备码流程另存到插件自己的 `storages`),模型目录硬编码(该端点 `/v1/models` 未对订阅流量开放),请求走 Anthropic Messages 协议且**只用 Bearer 认证**。审查发现并逐条修掉的问题按影响排序:
38
+ - **① 设置卡片被灌进 ChatGPT 的文案(UI 一致性,用户可见)**。`ProviderHubSection` 把**自己那份绑到 `dsh-chatgpt-subscription` 命名空间的 `t`** 通过 `{...runtime}` 传给了 `MinimaxCodeSection`,而该卡片的键集并不是那个命名空间的子集。DSH 的 locale 解析在**本命名空间查不到时回落 `common`、再查不到就把键名原样返回**,于是实测该标签页渲染出:**35 个键显示为字面键名**(`pageDesc`、`accountLabel`、`region`、`quotaSection`、`storagePath` …),另有 **8 个键显示为 ChatGPT 的措辞**——最刺眼的是登录按钮变成「**使用 ChatGPT 登录**」,「注销」「已连接/未测试」「正在读取状态…」也全部串味。**修法**:让该卡片与**其余每个 provider 标签页一致**——自己渲染自己的字典,不接注入的 `t`(KimiCodeSection / WorkBuddySection / CommandCodeSection 都没有 locale prop,这也正是它们的做法),并把 `{...runtime}` 从 hub 去掉。**验证**:新增测试断言该标签页的可见文案不含任何字面键名、也不含 ChatGPT 措辞。
39
+ - **② 「登出」会撤销并删除桌面端自己的登录态(安全/可用性)**。原实现无条件 `revokeToken(refreshToken)` 后删文件——当凭据来自桌面端 `auth.json` 时,这等于**撤销用户正在运行的 MiniMax Code 的刷新令牌并删掉它的凭据文件**,直接把人从官方客户端踢下线。**修法**:登出**只作用于本插件自己持有的凭据**;桌面端的登录态返回 `native: true` 并说明原因,卡片据此禁用按钮并给出 tooltip(这正是 WorkBuddy 对「桌面账号归 IDE 所有、只隐藏不删除」的既有做法)。`/status` 新增 `ownedByPlugin` 供卡片判断。**验证**:新增两条测试分别锁定「桌面端凭据的登出不撤销、不删文件」与「插件自持凭据的登出会撤销并删除」。
40
+ - **③ 路由前缀违背全仓库约定(功能/一致性)**。该 PR 把设置路由挂到 `/api/dsh-chatgpt-subscription/minimax-code`——而 `/api/dsh-chatgpt-subscription` 是 **Codex 自己那条线路的**前缀,其余五条订阅线路一律挂在各自的 `/<line>/api` 下;为了自圆其说,`compat.ts` 还导出了 `MINIMAX_CODE_SIBLING_ROUTE_PREFIX`、`MINIMAX_CODE_ANTHROPIC_VERSION`、`MINIMAX_CODE_CLIENT_ID/SCOPE/AUDIENCE` 五个**只被定义、从未被引用**的重复常量。**修法**:前缀改为 `/minimax-code/api`,删掉「挂两次」与那五个死常量。**验证**:新增测试锁定前缀常量与真实挂载点,客户端注册测试同步改到新前缀。
41
+ - **④ 401 之后永远不恢复(功能)**。`ensureAccessToken` 的 `force` 选项注释写着「used after a 401」,但**全仓库没有任何调用点传它**:本地时钟看着还有效、服务端已拒收的令牌(在桌面端被撤销、时钟偏移、桌面端已轮换)会让该轮**永久失败**。**修法**:第一次 401 强制续期一次并原样重试同一请求体,重试再被拒才是终局。**验证**:新增测试断言「一次 401 → 一次 refresh → 一次重试成功」,且失败路径在修复前会抛 `INVALID_CREDENTIAL`。
42
+ - **⑤ 国际区账号永远「未登录」(功能)**。凭据读取只探测构造时的默认区域目录,而桌面端**按区域分目录存放**,国际区用户没有国区文件;同时 `parseMinimaxCodeCredentials` **丢弃了记录里存的区域**、一律套用调用方的默认值,于是即使读到也会被改判成 `cn` 并打到国区端点。**修法**:两个区域目录都探测(先常用后另一个),并以**记录里的区域**为准决定所有后续 host 与写回路径。**验证**:新增两条测试——国际区登录能被找到、读回的区域仍是 `global`。
43
+ - **⑥ 桌面端目录里被写入明文旁路凭据(安全)**。`writeFileAtomic` 每次写都会 `copyFile(filePath, filePath + '.dsh-bak')`,而目标可能就是 **MiniMax Code 自己的目录**:等于在官方客户端的数据目录里留下一份**明文**、固定名的凭据副本。**修法**:删掉该副本;原子 `rename` 本身已保证「替换完成前原文件逐字节不变」,那正是备份想提供的能力。**验证**:新增测试断言一次续期写回后目录里既无 `dsh-bak` 也无残留 `dsh-tmp-`,且文档的 `schemaVersion` 与其他记录原样保留。
44
+ - **⑦ 诊断泄露真实令牌前缀(安全)**。`redactToken` 输出「前 6 个字符 + 长度」,而它的产物会被渲染进设置页错误条并写进 Host 日志——6 个字符的 bearer 令牌本身就是可被利用的泄露。**修法**:改为 **SHA-256 指纹前缀**(`sha256:.../len:...`),仍然能区分两枚令牌、指认哪一枚被拒(它的诊断目的),但不可用作凭据。**验证**:新增测试断言渲染结果不含令牌任意一段原文,同时仍能区分两枚不同令牌。
45
+ - **⑧ 声明了做不到的视频能力(功能)**。模型表给 M3 / M3.1 标了视频输入,`inputModalities` 也照抄了 `'video'`,但 DSH 附件服务只存图片、本线路没有视频读取器,映射器只能替换成说明文本。**声明一个做不到的能力比不声明更糟**:DSH 的能力闸门、模型选择器与子代理委派都会当它成立。**修法**:只声明 `text`/`image`,并在类型注释里写明「加 `video` 必须先有真正的读取器」。**验证**:新增测试断言目录不含 `video`,且即便传入 video 块,序列化请求体里也不出现任何线上视频字段。
46
+ - **⑨ 输出上限的收敛是空操作**。`clampOutputToContext(requestedMax, contextWindow)` 少传了第三个参数 `estimatedInputTokens`,因此**永远提前返回**、从不收敛——上游会拒绝,而不是本插件先收敛。**修法**:接上共享的 `estimatedInputTokens`(与 Kimi 线路同一实现)。另修掉 `thinkingFieldFor` 里两分支返回同一值的死三元。
47
+ - **⑩ 空转的额度轮询(性能)**。composer 徽标为一条**没有配额接口**的线路每 60 秒读一次 `/status`(每次都要读并解析凭据文件),而该徽标按契约**永远不渲染**(`selectBadgeFacts` 恒返回 null)。**修法**:去掉定时器,只在选中该线路时读一次。同时把 `/status` 的凭据读取从**三次**(`read` + `activeSource` + `activePath`,每次都要重读重解析原生文档)合并为一次 `readWithProvenance()`。
48
+ - **遗漏补齐**:README 增补该线路的功能/路由/安全边界三节(新增六条路由的表格与「诊断只输出指纹」的说明),CHANGELOG 即本条目;新增 `test/minimax-code-review-fixes.test.ts`(15 条)。
49
+ - **验证**:`npm run typecheck` 三个项目 0 错误;`npm run build` 成功;新增测试**红绿双向验证**——把 `src` 改动 `git stash` 后同一套测试 **9 条失败 / 5 条通过**,恢复后 **15 条全绿**,证明它们确实钉住的是本次修掉的缺陷而非同义反复。
5
50
  - **适配 DSH 的 Agent Teams:`dispatch` 自适应协作模式,守卫把 `spawn_teammate` 纳入 inherit 模式**。
6
51
  - **根因(实测,非推测)**:Agent Teams bundle 在**每个会话自己的作用域**里注册了一套与内置**同名**的协调工具,而工具注册表按作用域链解析、**近层遮蔽远层**(`packages/core/tools/src/index.ts:1185-1208`;`tool-agent-team` 的安装点是 `agent.ctx`,见其 `src/index.ts:402-421`)。因此在装了该 bundle 的部署上,内置 `tool-subagent-control` 的 `send_message` / `list_agents` / `interrupt_agent` 被 teammate 版顶掉;`roster.membership()` 对任何无 `parentSession` 的顶层会话都返回「自己是 lead」,于是**每个顶层会话都被替换**。本机 web profile 的 `dsh.profile.bundles` 恰好包含该 bundle。
7
52
  - **现场证据**:`list_agents({})` 只返回 `[{ target: "lead", role: "lead", status: "running" }]`,当轮真正派出的 continuable 子代理不在其中;`send_message({ target: "<该子代理 session id>" })` 抛 `active teammate "…" not found`(文案出自 `agent-team/src/roster.ts:52`)。后果是「派得出去、回访不了」:R5 等证据与 R6 带证据重派在这类部署上不可用。
package/README.md CHANGED
@@ -84,6 +84,23 @@
84
84
  - **流中断即失败**:上游必然以 `finish_reason` 或 `data: [DONE]` 结束,两者都缺失说明连接中途断开,此时抛出错误而不是把半截文本当成完整回答;
85
85
  - 额度来自 `/billing/meter/get-user-resource`,卡片展示套餐名、本周期已用/上限、剩余额度与重置时间;设置页为「设置 → 订阅服务 → WorkBuddy」标签页,对话输入框右侧有该线路的额度胶囊。
86
86
 
87
+
88
+ **MiniMax Code(编程订阅)线路**
89
+
90
+ - 注册 `minimax-code` Provider,接入 **MiniMax Code 编程订阅**。它与 MiniMax 开放平台(按量计费的 API Key)是两套互不通用的系统:订阅的模型接口是 Anthropic Messages 协议(`https://agent.minimax.cn/mavis/api/v1/llm/v1/messages`),且**只用 `authorization: Bearer <accessToken>` 认证**——实测 `x-api-key` 一律返回 401 `{"code":401,"message":"token is required"}`,因此本线路**没有** x-api-key 回退分支(回退只会在每次请求上白花一个往返);
91
+ - **复用桌面端登录态,而不是让你再登录一次**:凭据就是 MiniMax Code 自己写的 `~/.minimax/auth/<buildEnv>/<region>/mcode-public/auth.json`。**只读优先**:仍在有效期内的令牌原样使用,绝不无谓轮换;需要续期时走**原子替换**(临时文件 + `rename`),失败或中断都让原文件保持逐字节不变,**不创建、不删除、不等待** `auth.lock`(那是桌面端自己的刷新锁,第二方碰它就可能打断官方客户端的刷新);
92
+ - **两种登录来源互不覆盖**:桌面端的 `auth.json` 归桌面端所有,本插件只读;若本机没有它,才在设置页用 **RFC 8628 设备码流程**(PKCE S256)登录,凭据存到本插件自己的 `$DSH_HOME/storages/minimax-code-credentials.json`。**登出只作用于本插件自己那份凭据**:桌面端的登录态会被续期写回(只读优先),但**绝不撤销、绝不删除**,登出请求对它会被拒绝并说明原因(撤销它等于把你从正在跑的 MiniMax Code 里踢下线),这也是 WorkBuddy 对桌面账号的既有做法;
93
+ - **区域是凭据属性**:`cn` 用 `account.minimax.cn` / `agent.minimax.cn`,`global` 用对应 `.io` 域名。两个区域的目录都会被探测,因此国际区账号不会因为没有国区文件而「未登录」;读回凭据时**以记录里存的区域为准**,而不是拿默认区域覆盖它;
94
+ - **模型目录是硬编码的**(`src/host/minimax-code/model-catalog.ts`):该端点的 `GET /v1/models` **未对订阅流量开放**(503 `{"errorCode":50115,"errorReason":"direct_route_not_configured"}`),所以任何「实时目录」都只会是一个必然失败的请求。四款模型(M2.7 / M2.7 HighSpeed / M3 / M3.1 Flash Preview)的上下文、输出上限与思考形态都取自本机 `~/.minimax/config.yaml` 的模型表,**不从不存在的接口推断**;
95
+ - **思考形态按模型表三态实现**:M2.7 系列**恒定开启且没有档位**(不发送任何字段);M3 是**开关二态**(`{type:'enabled'|'disabled'}`);M3.1 Flash Preview **强制开启并可指定档位**(`{type:'enabled',effort:...}`)。未知或越档的取值一律回落到该模型目录声明的默认档,不会把模型没有声明的档位发出去;
96
+ - **视频不在能力声明里**:模型表虽然给 M3 / M3.1 标了视频输入,但 DSH 的附件服务只存图片、本线路也没有安装视频字节读取器,映射器只能把它替换成一条说明文本。**声明一个做不到的能力比不声明更糟**(DSH 的能力闸门、模型选择器与子代理委派都会当它成立),所以这里只声明 `text` 与 `image`;要加 `video` 必须先有真正的读取器(对照 Kimi Code 线路的 `video-store.ts`);
97
+ - **瞬时失败按错误类别重试**(`src/host/minimax-code/adapter.ts`):上游模型供应商临时不可用(5xx)归为 `SERVER`、429 归为 `RATE_LIMIT`、连接未产出响应归为 `TRANSPORT`,走 DSH retry policy 的有界退避(最多 3 次,1.5s 起步、15s 上限、0.2 抖动);**但 429 的正文若说的是余额/额度耗尽,则判为终局**——重试只会推迟用户真正需要看到的提示;
98
+ - **401 会强制续期一次再重试**:本地时钟看着还有效、服务端却拒收的令牌(在桌面端被撤销、时钟偏移、桌面端已轮换)与「真的需要重新登录」在状态码上无法区分,所以第一次 401 会**强制刷新一次并原样重试同一个请求体**;重试再被拒才是终局;
99
+ - **请求体超过 2 MB 本地即拒绝**(与 Kimi 线路同一个守卫与上限,因为两条线路上游都是同一族端点);额度面板**有配额条了**:Token Plan 的用量端点**不在 MiniMax 的 API 文档里**(文档只说"用量显示在控制台的用量条上",这大概就是最初判定"没有端点"的原因),它来自官方 CLI——`mmx quota show` 读 `GET {apiHost}/v1/token_plan/remains`。返回的每个 `model_remains` 行带**两个窗口**(5 小时滚动 + 每周),因此卡片两根条分别显示,还能看到每根条的重置时刻与"已用/总量"。三个必须照抄官方实现的细节:① **`*_usage_count` 的语义是模糊的**(老响应是"剩余"、新响应是"已用"),官方用显式百分比消歧——照抄否则进度条会**反过来**;② **周窗口带显示倍率**(返回的是百分比 × `weekly_boost_permille`,可超过 100%);③ `status: 3` 通常表示"不限量",但**两个总量都为 0 时**它表示"当前套餐不含该模型",渲染成不限量会凭空许诺额度。**有两套用量端点,第一轮打错了那一套**。① **平台端点** `/v1/token_plan/remains`(`api.minimax.cn` / `api.minimaxi.com` / `api.minimax.io`,实测都返回 JSON)只接受**平台 API 密钥**:`mcode-public` 登录态在四种认证写法(`Bearer` / 原始 / `x-api-key` / 两者都带)下全部被拒,且是 **HTTP 200 + `base_resp.status_code: 1004`**。官方 `mmx` CLI 能用,是因为它的 OAuth 是**平台**登录,与本线路的 mcode 登录是两套身份。② **mcode 自己的端点在官方客户端源码里**(`packages/tui/src/account/matrix-account-client.ts`):`GET https://agent.minimaxi.com/v1/api/openplatform/coding_plan/remains`(国际 `agent.minimax.io`),路径与平台端点**完全不同**——本插件现在用的就是这条(本线路自己的 `agent.minimax.cn` 优先、官方主机兜底)。⚠️ **但它带 `yy` / `x-timestamp` / `x-signature` / `User-Agent: MiniMaxCode` 四个"官方客户端标识头"**:官方注释自己写明这些是"把请求标记为来自 MiniMax 第一方客户端"的字面量。本插件 `types.ts` 已明确决定**不伪造官方客户端身份**(伪造既失信,也可能是封号理由),所以**默认不发**,宁可拿到一个拒绝让卡片如实说明;确实要数字的用户可以设 `DSH_MINIMAX_CODE_QUOTA_ATTRIBUTION=1` 显式选择该取舍。三处防护:① `base_resp` 非 0 视为凭据被拒,**只试一台就停**并记住 30 分钟;② 其它失败按 `unreachable` 区分、记住 10 分钟;③ `/status` **只读缓存、绝不在轮询里发网络请求**。读用量**永远不影响对话**:独立只读路径,失败只让卡片换一句话;
100
+ - **设置面与其余各线路逐项对齐**(该 PR 最初缺失这一整块,用户明确指出):① **「启用此供应商」总开关**——关闭后适配器**不暴露任何模型**(包括已勾选的),因为「关掉」如果还能用就等于没关;② **模型勾选**——只有勾选的模型进入对话页的模型选择列表,另有「全选 / 全不选」;③ **模型上下文窗口覆盖**——每个已勾选模型可填自定义容量(接受 `1M` / `512K` / `200000`),「恢复默认」以 `null` 语义在 host 侧**删除**该键而不是写入 0;④ **默认思考深度**——全局档位只在模型确实列了该档时生效,否则回落到该模型自己的默认档(广告一个模型会拒绝的档位只会让服务端拒掉请求),且「关闭思考」只对**可关闭**的模型生效;⑤ **号池管理**(见下条)。目录仍然硬编码,但「这一安装当前怎么用它」是用户设置:两者分开存储(目录随代码发布、设置随用户数据保存),因此一份用户数据不会看起来像一条已发布的目录项;
101
+ - **号池与多账号登录**:复用与其它线路**同一个**号池内核(`src/host/common/account-pool.ts`)与**同一张**共享账号卡片,具备顺序耗尽 / 轮询 / 粘性调度、429 冷却换号、设为主账号与备注。**身份键不是令牌**:桌面端凭据用它自己的 `recordKey`(同一个记录槽每次轮换都是同一账号,因此在池里原地更新),插件自持凭据用 `loginEpoch`(每次设备码登录就是一次独立会话)——用刷新令牌做键在每次轮换后都会把同一账号看成新账号;
102
+ - **桌面端登录态可被显式「导入」为号池账号**(`POST /accounts {action:'adopt'}`):只读取、只新增一行,不改动官方客户端的任何文件;重复导入按记录槽身份原地更新,不会产生重复账号。**在设置页新发起的设备码登录会自动加入号池**(`pollWebLogin` 的 `onSave` 钩子)——没有这个钩子,新登录只会写进镜像文件而号池看不见,这正是「多账号登录没配好」的根因;该钩子的失败**不会**让登录失败,因为凭据此时已经落盘。
103
+
87
104
  **Claude(订阅)线路**
88
105
 
89
106
  - 注册 `claude-subscription` Provider,以 **Claude Pro / Max 订阅的 OAuth 登录态**访问 Claude 模型(Anthropic Messages 接口),**不使用 API Key、也不按量计费**;
@@ -512,8 +529,27 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
512
529
  | POST | `/accounts` | 号池动作:`set-primary` / `set-alias` / `delete` / `clear-cooldown` / `clear-auth-failed` / `strategy` |
513
530
  | POST | `/logout` | 注销(号池感知) |
514
531
 
532
+ ### 插件路由(MiniMax Code)
533
+
534
+ 所有路由都挂在本线路自己的 `/minimax-code/api` 前缀下(与 antigravity / claude / command-code /
535
+ kimi-code / workbuddy 一致),**不**占用 Codex 那条线路的 `/api/dsh-chatgpt-subscription` 前缀。
536
+
537
+ | 方法 | 路径 | 说明 |
538
+ | --- | --- | --- |
539
+ | GET | `/status` | 账号、区域、凭据来源与路径、硬编码模型目录、路由归属(`serving` / `conflict`)与凭据归属(`ownedByPlugin`) |
540
+ | POST | `/login/start` | 开始设备码授权(可带 `region`) |
541
+ | POST | `/login/poll` | 轮询一次授权结果 |
542
+ | POST | `/login/cancel` | 取消授权 |
543
+ | POST | `/logout` | 注销**本插件自己那份**凭据;桌面端的登录态会拒绝并说明原因 |
544
+ | POST | `/test` | 向上游发一次最小 Messages 请求测试连接 |
545
+ | GET / POST | `/models`、`/settings` | 读取或更新启用开关、勾选模型、上下文窗口覆盖与默认思考深度;两个路径等价(kimi/claude 叫 `/models`,workbuddy/command-code 叫 `/settings`) |
546
+ | GET / POST | `/accounts` | 号池动作:`set-primary` / `set-alias` / `delete` / `clear-cooldown` / `strategy` / `relogin` / `adopt`(`adopt` = 把桌面端当前登录态导入号池,只读、不改动官方客户端文件) |
547
+ | GET / POST | `/quota` | 读取用量快照(POST 强制刷新):Token Plan 的 5 小时与每周窗口。**上游读失败不算请求失败**——`quota` 是可选字段,失败时卡片显示“暂无数据”,对话不受任何影响 |
548
+
515
549
  与其它线路一样,所有修改状态的路由只接受同源 JSON POST,并校验 `Origin` 与 `Host`。**响应里永远不含
516
- access token / refresh token / 授权码 / PKCE verifier**(有测试锁定)。
550
+ access token / refresh token / 授权码 / PKCE verifier**;诊断里提到某个令牌时只输出它的
551
+ **SHA-256 指纹前缀**(`sha256:.../len:...`),**不是**令牌的任何一段原文——早前版本会打印前 6 个字符,
552
+ 那本身就是一次凭据泄露,因为同一个字符串会被渲染到设置卡片并写进 Host 日志(有测试锁定)。
517
553
 
518
554
  ## 安全边界
519
555
 
@@ -521,6 +557,8 @@ Antigravity 的 access token / refresh token 使用独立的系统凭据存储
521
557
 
522
558
  **WorkBuddy token 仅在 Host 内处理,从不进入浏览器**(`/workbuddy/api` 响应不含 `accessToken` / `refreshToken`,有测试锁定)。桌面扫描账号仍使用 CodeBuddy 自己的登录态文件:续期只原子写回其 `auth` 块;从本插件删除时只隐藏/恢复,绝不删除原文件。通过浏览器授权添加的账号归本插件所有,保存在独立系统凭据存储中:Windows CurrentUser DPAPI(`$DSH_HOME/storages/workbuddy-accounts.json.dpapi`)、macOS 登录钥匙串、Linux Secret Service;这类账号可在设置页真正删除。
523
559
 
560
+ **MiniMax Code 的 access token / refresh token 同样只在 Host 内处理,从不进入浏览器**(`/minimax-code/api` 的响应只含非机密事实,有测试锁定)。号池的**身份键也刻意不含机密**:桌面端凭据用 `recordKey`(路径摘要)、插件凭据用 `loginEpoch`,**不是**刷新令牌的哈希——用令牌做键在每次轮换后都会把同一账号看成新账号。它的凭据**不是本插件自持的**:正常路径是**只读复用** MiniMax Code 桌面端自己的 `~/.minimax/auth/<buildEnv>/<region>/mcode-public/auth.json`,续期时通过「同目录临时文件 + `rename`」原子写回,**不创建、不删除、不等待**桌面端的 `auth.lock`,也**不留任何旁路副本**——不会在桌面端的目录里写出明文 `.dsh-bak` 之类的第二份凭据。只有本机没有桌面端凭据时,本插件才把设备码登录得到的凭据存到自己的 `$DSH_HOME/storages/minimax-code-credentials.json`;**登出只删这一份**,桌面端的登录态绝不撤销、绝不删除(撤销它等于把用户从正在运行的官方客户端踢下线)。诊断中提及令牌时只输出 SHA-256 指纹前缀,不含令牌原文。
561
+
524
562
  **Claude 订阅的 access token / refresh token 同样只在 Host 内处理,从不进入浏览器**(`/claude/api` 的响应只含非机密事实,有测试锁定)。它保存在独立系统凭据存储中:Windows CurrentUser DPAPI(`$DSH_HOME/storages/claude-credentials.json.dpapi`)、macOS 登录钥匙串、Linux Secret Service;号池另用一份同样加密的文件 `$DSH_HOME/storages/claude-pool.json`。凭据文档是**多账号**结构,账号身份由不可变的 `internalId` 与只增不换的别名集共同表达,**任何路由键都不是令牌或其摘要**(令牌会轮换,用它做键会产生幽灵账号)。每次写入都**先读回校验再落盘**,校验失败会抛错且不破坏既有数据。
525
563
 
526
564
  **收编(adopt)是只读的**:本插件从不创建、修改、移动或删除 Claude Code 的任何文件;它只读取。收编得到的快照**永不由本插件刷新**(原因见上文)。