@eddyskywalker/dsh-chatgpt-subscription 0.9.9 → 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.
- package/CHANGELOG.md +72 -0
- package/README.md +80 -2
- package/lib/client.js +1596 -207
- package/lib/client.js.map +1 -1
- package/lib/index.js +4834 -1019
- package/lib/types/client/ProviderHubSection.d.ts.map +1 -1
- package/lib/types/client/claude/locales.d.ts +2 -2
- package/lib/types/client/command-code/locales.d.ts +2 -2
- package/lib/types/client/index.d.ts +2 -0
- package/lib/types/client/index.d.ts.map +1 -1
- package/lib/types/client/kimi-code/locales.d.ts +2 -2
- package/lib/types/client/minimax-code/MinimaxCodeComposerQuota.d.ts +27 -0
- package/lib/types/client/minimax-code/MinimaxCodeComposerQuota.d.ts.map +1 -0
- package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts +49 -0
- package/lib/types/client/minimax-code/MinimaxCodeSection.d.ts.map +1 -0
- package/lib/types/client/minimax-code/api.d.ts +5 -0
- package/lib/types/client/minimax-code/api.d.ts.map +1 -0
- package/lib/types/client/minimax-code/locales.d.ts +341 -0
- package/lib/types/client/minimax-code/locales.d.ts.map +1 -0
- package/lib/types/client/minimax-code/styles.d.ts +12 -0
- package/lib/types/client/minimax-code/styles.d.ts.map +1 -0
- package/lib/types/client/styles.d.ts +19 -0
- package/lib/types/client/styles.d.ts.map +1 -1
- package/lib/types/compat.d.ts +1 -0
- package/lib/types/compat.d.ts.map +1 -1
- package/lib/types/host/agent-preset.d.ts.map +1 -1
- package/lib/types/host/claude/mapper.d.ts +11 -2
- package/lib/types/host/claude/mapper.d.ts.map +1 -1
- package/lib/types/host/minimax-code/account-pool.d.ts +219 -0
- package/lib/types/host/minimax-code/account-pool.d.ts.map +1 -0
- package/lib/types/host/minimax-code/adapter.d.ts +168 -0
- package/lib/types/host/minimax-code/adapter.d.ts.map +1 -0
- package/lib/types/host/minimax-code/client.d.ts +184 -0
- package/lib/types/host/minimax-code/client.d.ts.map +1 -0
- package/lib/types/host/minimax-code/mapper.d.ts +143 -0
- package/lib/types/host/minimax-code/mapper.d.ts.map +1 -0
- package/lib/types/host/minimax-code/model-catalog.d.ts +146 -0
- package/lib/types/host/minimax-code/model-catalog.d.ts.map +1 -0
- package/lib/types/host/minimax-code/oauth.d.ts +204 -0
- package/lib/types/host/minimax-code/oauth.d.ts.map +1 -0
- package/lib/types/host/minimax-code/routes.d.ts +79 -0
- package/lib/types/host/minimax-code/routes.d.ts.map +1 -0
- package/lib/types/host/minimax-code/token-store.d.ts +238 -0
- package/lib/types/host/minimax-code/token-store.d.ts.map +1 -0
- package/lib/types/host/minimax-code/types.d.ts +206 -0
- package/lib/types/host/minimax-code/types.d.ts.map +1 -0
- package/lib/types/host/subagent-model-authorization.d.ts +7 -0
- package/lib/types/host/subagent-model-authorization.d.ts.map +1 -1
- package/lib/types/index.d.ts +10 -0
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/shared/minimax-code-contracts.d.ts +166 -0
- package/lib/types/shared/minimax-code-contracts.d.ts.map +1 -0
- package/package.json +1 -1
- package/presets/dispatch/agent.cordis.yml +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,78 @@
|
|
|
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 条全绿**,证明它们确实钉住的是本次修掉的缺陷而非同义反复。
|
|
50
|
+
- **适配 DSH 的 Agent Teams:`dispatch` 自适应协作模式,守卫把 `spawn_teammate` 纳入 inherit 模式**。
|
|
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。
|
|
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 带证据重派在这类部署上不可用。
|
|
53
|
+
- **守卫改动(按既有结论落地)**:`DEFAULT_SUBAGENT_INHERIT_TOOLS` 由 `['subagent_fork']` 改为 `['subagent_fork', 'spawn_teammate']`。理由:Agent Teams 经 `ctx.subagents.startContinuable` 创建成员时**完全不传 `agentOptions``**,成员必然继承 Lead 路由,且工具没有任何路由参数——「工具不能选」不等于「这个选择被授权」,所以它属于 inherit 语义(同 `subagent_fork`),而非 explicit。**绝不能**把它加进 `subagentModelTools`:那里要求成对 `provider`+`model`,该工具没有这两个字段,会让每次 teammate 创建被硬拒且模型无法自救(为此在守卫测试里加了专门断言)。
|
|
54
|
+
- **persona 改动(自适应,保持精简)**:新增 **R0.5 协作模式侦测**(判据:`spawn_teammate`/`wait_agent` 是否存在;一句「分界不是能不能派,而是派完能不能追问」)与 **R-T teammate 模式**(协调走成员词汇;`spawn_teammate` 无路由参数、不写 route 行;选型与任务板落点),R8 补一行 teammate 边界,反模式补 2 条;【角色识别】扩为「子代理或 teammate」。全部按精简原则写:R0.5 由 7 行压到 4 行、R-T 由 7 条压到 4 条、反模式由 3 条并为 2 条,并删去可由工具描述本身传达的细节(`wait_agent` 的语义、`scope` 辅助判据等)。
|
|
55
|
+
- **验证**:`npx tsc -b --force` 与 `npx tsc -p test/tsconfig.json` **均 0 错误**;`npm run build` 成功且 `lib/index.js` 内确认带有 R0.5/R-T 文本;全量 `npx vitest run` **1615 passed / 7 skipped**(112 文件,1 skipped);新增守卫测试(teammate 归 inherit、越权拒绝文案是 inherit 版而非 explicit 版、授权路由放行、`[]` 退出、显式命名路由被 override 分支拒绝)与 preset 测试(两种形态 parity 逐字节 + R0.5/R-T/反模式文本钉住);**变异验证**:只改 YAML / 只改 TS / 两份同时改同一措辞(parity 仍成立时行为测试单独失败)三种情况均被捕获。
|
|
56
|
+
- **已记录的上游限制**:`spawn_teammate` 无法指定 `provider`/`model`——三层已核对(工具 schema 无此参数;`SpawnTeammateRequest.provider` 是 subagent 后端名而非 LLM provider 且无 `model`;底层 `ContinuableStartSpec.request.agentOptions` 本可携带,但 Team 服务构造 request 时只放 `prompt`+`parent` 整个省略)。插件侧无干净绕法(pre-execute 明确排除输入改写;`agentTeams` 服务内部丢弃后包装不到;自定义 provider 拿不到已在 continuation manager 解析完的路由;`selectForNextRequest` 只作用于下一个请求且与 teammate 立即开跑构成竞态)。README 新增「已知上游限制」一节,并写明**上游接出 `agentOptions` 后**应把 `spawn_teammate` 从 `subagentModelInheritTools` 移到 `subagentModelTools`。
|
|
57
|
+
- **未验证项(如实说明)**:本次是**提示词 + 守卫默认值 + 测试**改动,验证的是「两种形态一致 + 守卫判定符合预期」,**不是**「真实会话里模型一定先侦测再动手」。另需注意一个**已实测的副作用**:本机 web profile 的 `agent-default-model` 是 `workbuddy-subscription/deepseek-v4.1-flash`,而 `subagent-model-selection-settings.allowedModels` 只勾了 `command-code/deepseek/deepseek-v4.1-flash`——两条路由不同,因此在该 profile 下开启允许清单的新会话里,`spawn_teammate` 与 `subagent_fork` 都会被视为「继承路由不在清单内」而被拒绝。要恢复 Agent Teams,需把当前会话路由切到清单内,或把该路由补进勾选清单;拒绝文案已包含这条恢复路径。
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
- **修掉「设置 UI 部分情况下丢失样式表」**(用户报告:设置页偶尔整体裸渲染,重启应用才恢复)。ChatGPT 页样式表(`.dsh-codex-*`)是全部六份客户端样式表里**唯一在卸载时删除元素**的:`installStyles()` 检测到同 id 标签已存在时返回**空 disposer**(放弃所有权),而 cordis 的插件 fiber 重启(改配置、依赖服务重启、HMR 热重载)允许新 fiber 的 `apply()` 在旧 fiber 异步排空 disposer **之前**运行(`runtime.fibers` 明确支持同插件多 fiber 并存)。这个顺序一旦发生:新 fiber 看到旧标签 → 返回空 disposer;旧 fiber 随后 `element.remove()` 删掉唯一一份;此后每次重启都命中同一去重分支,样式表**永不恢复**——正是「部分情况下」才触发的竞态。其余五份样式表(antigravity / claude / kimi / workbuddy / pool)的安装器本来就是「存在即跳过 + 空 disposer」的纯增量模式,天然不受影响;本修复把主样式表改成同一模式:**标签归文档所有(missing 则创建、存在则刷新 `textContent`、disposer 恒为空)**。刷新内容顺带修掉 HMR 场景的另一个小缺陷——重建 bundle 后旧标签的旧 CSS 一直残留(dev 环境改 CSS 不生效)。
|
|
61
|
+
- **验证**:`npm run typecheck` 0 错误;新增 `test/client-styles.test.ts`(7 条)锁定单标签不变量、disposer 不删节点、外部删除后可重建、旧 bundle 内容刷新,以及**真实 Cordis** 上的两条生命周期用例(fiber 重启 + 旧 fiber 迟到销毁、历史「fiber 持有删除」形状的恢复);**红绿验证**——把修复 `git stash` 后在同一测试上复跑,恰好那 2 条生命周期用例失败、恢复修复后 7 条全绿,证明测试真的覆盖该路径。合并 `upstream/master`(0.9.8 之后共 16 个提交,含 zhipu 线路换代为 claude)后复跑:`npx tsc -b --force` + `tsc -p test/tsconfig.json` 0 错误、`npm run build` 通过、`npx vitest run` **110 文件通过 / 1 跳过(112)、1600 条通过 / 9 跳过**;唯一失败的 `test/quota-ui.test.tsx` 2 条是**存量**问题与本修复无关(用例固定 UTC 边界日期 `2030-01-01`,在 UTC+8 机器上落到 `2029-12-31 19:00`,带不带本修复、合并前后同样失败)。
|
|
62
|
+
- **遗留说明**:其余安装器「存在即跳过」不去刷新 `textContent`,HMR 改它们的 CSS 仍需整页刷新才生效——纯 dev 体验问题,与丢失无关,未在本次改动。
|
|
63
|
+
- **修复用户实测缺陷:Claude 线路凡是有内容的回复必定失败(`Assistant stream chunk must be losslessly JSON-serializable`)**。现象是**每一轮**只要模型输出了文字/思考/工具调用,整轮就以这个错误告终,UI 显示"本轮运行失败";而传输失败、凭据失败这类**没有内容**的轮次反而正常,所以看起来像偶发。**这是本插件引入的缺陷,与用户环境无关。**
|
|
64
|
+
- **根因:`claude/mapper.ts` 把 `undefined` 写进了 finish 块的 `replayState.blocks`**。DSH 在会话日志接受每个 stream chunk 之前会做**无损 JSON 校验**,只要包含 JSON 无法承载的值(`undefined`、函数、symbol、非有限数、`-0`、数组空洞等)就**整块拒收**——代价是用户**整轮**对话,而不是一条元数据。三处写坏值:
|
|
65
|
+
- `closeTextBlock`:`state.replayBlocks[open.index] = undefined`(预留给文本块;文本本来就靠 DSH 自己持有的 block 回放,无需逐字条目);
|
|
66
|
+
- `content_block_start` 的 `tool_use` 分支:`state.replayBlocks[index] = undefined`(先占位、等 `content_block_stop` 填入);
|
|
67
|
+
- `closeToolCall`:`input: parsed`,而 `safeJsonParse` 在**缺参数或 JSON 解析失败**时返回 `undefined`。
|
|
68
|
+
- **`JSON.parse` 本身也不是安全来源**(这一条超出了原始报告):它接受线上真会出现的 `-0` 与越界字面量,`{"n":-0}` 解析出负零、`{"n":1e999}` 解析出 `Infinity`,两者都不能无损往返。所以只把 `undefined` 换成 `null` 并不够。
|
|
69
|
+
- **修法(四件事)**:① 两处占位改写成 `null`(合法的 JSON 值,语义正是"此处无逐字条目");② 新增 `jsonSafeValue()`,把值收敛进可无损往返的子集——有限数字通过、`-0` 归一为 `0`、数组变稠密、普通对象只保留可枚举字符串键并丢弃无 JSON 形式的字段(函数/symbol/bigint/undefined)、带环则判为无形式;③ `input` 经 `jsonSafeValue(parsed) ?? {}` 规范化;④ `closeStream` 出口再整体过一遍 `jsonSafeValue`,**未来若有新槽位忘记初始化,代价是一条回放条目而不是用户整轮**。
|
|
70
|
+
- **类型收紧,让同类缺陷编不过**:`ClaudeStreamState.replayBlocks` 由 `unknown[]` 改为 `(Record<string, unknown> | null)[]`。原先那两处 `= undefined` 从此是**编译错误**——这是本次唯一能在编译期拦截该类缺陷的手段。
|
|
71
|
+
- **回放语义不变**:读取端 `replayBlockFor` 用 `isRecord(entry) ? entry : undefined`,对它而言 `null` 与 `undefined` **无法区分**,因此带签名的 thinking 块依旧逐字回放(这是多轮工具调用的前提),文本块照旧由 DSH 自己的 block 重建。已用 `buildClaudeRequestBody`(真实读取路径,而非内部函数)专门断言这一点。
|
|
72
|
+
- **独立变异验证(不只是"跑绿了")**:把三处一起还原成原缺陷 → 新增测试 **5 条失败**;只还原出口守卫(三处仍是 `null`)→ **恰好 1 条**(守卫那条)失败,证明它防的是"未来漏写"而非重复覆盖;全部修复 → 7 条全绿。三次篡改均被捕获。
|
|
73
|
+
- **验证**:`npx tsc -b --force`(host + client)与 `npx tsc -p test/tsconfig.json`(测试树)**均 0 错误**;`npm run build` 成功;`test/claude-mapper.test.ts` **70 项通过**(原 63 + 新 7);全量 `npx vitest run` **1599 通过 / 3 失败 / 9 跳过**,3 个失败全部是 `test/web-provider-lifecycle.test.ts` 的 `URL hostname "example.com" resolves to a non-public IP address`——**与本次改动无关**:本机 DNS 把公网域名解析到 `198.18.0.0/15`(透明代理的 fake-IP),该文件在**未改动的干净工作树上单跑同样 3 失败**(已用 `git stash` 实证)。
|
|
74
|
+
- **未验证项(如实说明)**:本次**没有向真实 Anthropic 端点发过请求**。结论依托两点:一是缺陷链在本地用**真实插件代码 + 从 `app.asar` 抽出的 DSH 真实校验函数**复现(修复前 8 种流式形态 7 种被拒,修复后 8/8 通过),二是用用户会话日志里**两次真实失败轮次**的原始数据回放,修复后 `2/2` 产出被接受的 chunk。
|
|
75
|
+
- **影响面**:只有 `claude-subscription` 线路受影响(`antigravity` 的 `replayBlocks` 只经 `push()` 写入、从不留空位,实测无此问题);该文件其余适配器未受影响。
|
|
76
|
+
|
|
5
77
|
- **修复用户实测缺陷:调度模式一旦进入 R8 兜底就"卡死",任务已经成功完成,下一个任务仍不再派发子代理**。
|
|
6
78
|
- **根因不是状态泄漏,而是提示词缺了两件事**。全仓库检索确认:插件**没有任何**失败计数器、熔断标志或持久化状态(`src/` 里出现"连续失败"字样的唯一位置就是 R8 那句话本身),委派守卫 `subagent-model-authorization` 是**纯函数**(读设置/会话快照→判定→返回理由或 `undefined`),DSH 内核的子代理失败也是**一次性**的(失败即 `throw` 成 `isError`,不重试、不入状态)。所以"兜底状态"只存在于**对话历史**里:旧 R8 把「子代理连续失败」列为无阈值、无失效条件、无解除规则的许可,模型写下"进入兜底"之后,后续每轮都会把它当成仍然成立的既有结论——R0 要求每次重新分诊,R8 没有对应要求,R6 的"不通过就重新派发"也没有兜底之后的**恢复**路径。
|
|
7
79
|
- **修法:把兜底从"会话级口头结论"改成"任务级、有阈值、可解除"的规则**,四条同批落地:① R8 首句明确**任务级、非会话级**,只覆盖触发它的那一个任务;② 触发条件收紧为「同一任务书按 R6 **重新派发两次**仍不通过」(R6 同步补上这个上限),取代不可判定的"连续失败";③ 新增**解除规则**——下一个任务一律回到 R0 重新分诊,条件已解除就必须恢复派发,不得沿用或预设兜底;④ 新增两条反模式(把一次兜底沿用成整个会话默认;拿工具硬拒当当兜底理由而不修触发条件),并针对两类**会话内不会自愈**的硬拒绝给出修条件路径(`active child limit` → 等子代理结算;`subagent model selection` → 按错误里列出的授权路由写明 `provider`/`model`)。
|
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、也不按量计费**;
|
|
@@ -245,6 +262,43 @@ DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。GPT-6 系列
|
|
|
245
262
|
|
|
246
263
|
> 路径解析不写死相对路径:`src/host/preset-sync.ts` 从模块位置向上查找最近的 `package.json` 作为包根,因此 `src/` 布局、打包后的 `lib/` 布局,以及通过 pnpm symlink / Windows junction 安装都能正确解析。注意 `fs.cpSync({ recursive: true })` 在 Node 22 + Windows 上遇到含非 ASCII 的源路径会直接崩进程(nodejs/node#54476),所以复制是逐条目实现的。
|
|
247
264
|
|
|
265
|
+
### 协作模式自适应(teammate / subagent)
|
|
266
|
+
|
|
267
|
+
DSH 的 Agent Teams(bundle `@deepseek-ai/dsh-experimental-agent-team-profile`)会在**每个会话自己的作用域**里注册一套与内置**同名**的协调工具。工具注册表按作用域链解析、**近的层遮蔽远的层**,所以一旦该 bundle 被组合进来,会话里的 `send_message`、`list_agents`、`interrupt_agent` 就都换成了 teammate 版:
|
|
268
|
+
|
|
269
|
+
- `list_agents()` 只列 Team 成员(Lead 自己显示为 `lead`),**看不到 `subagent` 派出去的子代理**;
|
|
270
|
+
- `send_message` 的 `target` 只接受成员名,把子代理的 session id 传进去会抛 `active teammate "…" not found`(实测);
|
|
271
|
+
- 于是「派得出去、回访不了」——`subagent` 的孩子只能等完成通知,无法追问。
|
|
272
|
+
|
|
273
|
+
`dispatch` preset 现在**每个任务开始时先侦测一次协作模式**(persona 的 R0.5),再按模式选择协调词汇(R-T):
|
|
274
|
+
|
|
275
|
+
| 侦测为 | 判据 | 协调词汇 | 派发 |
|
|
276
|
+
|---|---|---|---|
|
|
277
|
+
| teammate | `spawn_teammate` / `wait_agent` 存在 | `list_agents()`、`send_message({ target: <成员名> })`、`wait_agent()`、`team_task_*` | `spawn_teammate`(无路由参数,继承主代理路由)或 `subagent`(按 R2 显式路由) |
|
|
278
|
+
| subagent | 二者都不存在 | `list_agents()`、`send_message({ agent_id })`、`interrupt_agent` | `subagent` / `subagent_fork` |
|
|
279
|
+
|
|
280
|
+
两种模式下 R0 分诊、R1 职责边界、R3 自治、R6 验收都照常执行;**模型路由守卫(`subagentModelAuthorization`)不变**——它默认把 `spawn_teammate` 与 `subagent_fork` 同为 **inherit 模式**(见下),校验的是成员实际继承到的路由,而不是要求它给出并不存在的路由参数。
|
|
281
|
+
|
|
282
|
+
> **不要**把 `spawn_teammate` 加进 `subagentModelTools`。该列表是 **explicit** 模式:要求每次调用成对给出 `provider` + `model`,而 `spawn_teammate` 的参数里根本没有这两个字段(DSH 设计如此:成员一律继承 Lead 的路由),结果是每一次 teammate 创建都会被硬拒,且模型无法通过补参数自救。它的正确归属是 `subagentModelInheritTools`(默认已包含)。
|
|
283
|
+
|
|
284
|
+
### 已知上游限制:teammate 不能选模型
|
|
285
|
+
|
|
286
|
+
`spawn_teammate` 目前**无法**指定 `provider`/`model`,这不是插件的问题,而是 Agent Teams 有意不做 per-teammate 路由。三层都已核对:
|
|
287
|
+
|
|
288
|
+
| 层 | 能否带路由 | 证据 |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| 工具入参 | ❌ | `spawn_teammate` 的 schema 只有 `name`/`description`/`prompt`/`context` |
|
|
291
|
+
| Team 服务 | ❌ | `SpawnTeammateRequest.provider` 是 **subagent 后端名**(`spawn`/`fork`),不是 LLM provider;且无 `model` 字段 |
|
|
292
|
+
| 底层 API | ✅ | `ContinuableStartSpec.request` 可带 `agentOptions`(含 provider/model/reasoningEffort),但 Team 服务构造 request 时**只放 prompt 与 parent**,整个省略 |
|
|
293
|
+
|
|
294
|
+
因此成员必然跑在 Lead 自己的路由上——这正是守卫把它归 inherit 模式的原因,也是**不应**把它加进 explicit 的 `subagentModelTools` 的原因。
|
|
295
|
+
|
|
296
|
+
**插件侧无法绕过**,四条路都已验证死:pre-execute 明确排除输入改写(参数已被记录与呈现,`PreToolDecision` 只有 allow/deny/cancel/ask);`agentTeams` 服务在内部丢弃后包装不到;注册自定义 subagent provider 也拿不到路由(`agentOptions` 在 continuation manager 就已解析完,provider 只返回 `seed`);唯一能改子会话路由的 `selectForNextRequest` 只作用于「下一个请求」,而 teammate 创建后立刻开跑,抢不进去。
|
|
297
|
+
|
|
298
|
+
**上游正路**:在 `packages/experimental/agent-team/src/roster.ts` 构造 `startContinuable` 的 `request` 处补上 `agentOptions`,并给 `SpawnTeammateRequest` 加字段、给工具 schema 加参数、创建前接 `assertAllowedModelSelection`。**该改动落地后**,`spawn_teammate` 就应从 `subagentModelInheritTools` 移到 `subagentModelTools`(explicit)——否则它会变成「能选模型却没人管」。
|
|
299
|
+
|
|
300
|
+
> 实践提醒:inherit 模式校验的是**当前会话路由**。若当前会话跑在允许清单之外的路由上(例如默认模型与勾选清单不是同一条),teammate 与 `subagent_fork` 都会被拒绝——拒绝文案会指出恢复路径(改用显式路由的 `subagent`,或先把会话切到清单内的模型)。`subagent` 的显式路由不受影响。
|
|
301
|
+
|
|
248
302
|
### 配置项
|
|
249
303
|
|
|
250
304
|
| 字段 | 默认 | 含义 |
|
|
@@ -266,6 +320,8 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
|
|
|
266
320
|
- **子代理永远和主代理同一个模型**的历史行为由此消失:模型必须先用 `list_subagent_models` 查出已授权路由,再按拒绝理由里列出的路由(例如 `antigravity/gemini-3.8-flash`)重试;该工具也只展示已授权路由;
|
|
267
321
|
- 未记录允许列表的会话(例如恢复的旧会话、未启用该设置的会话)保持 DSH 原有行为(继承父级模型)。
|
|
268
322
|
|
|
323
|
+
**inherit 模式(`subagentModelInheritTools`,默认 `['subagent_fork', 'spawn_teammate']`)**:有些委派工具**按设计**就没有路由参数,子代理必然跑在调用者自己的路由上——`subagent_fork`(复用对话与 KV Cache)与 Agent Teams 的 `spawn_teammate`(成员一律继承 Lead 的路由)都是这一类。「工具不能选」不等于「这个选择被授权」,所以守卫改为校验它**实际继承到的路由**:允许清单内有该路由就放行,没有就拒绝,并说明该工具无法改路由、要么改用能显式指定路由的 `subagent`、要么先把本会话切到清单内的模型。设为 `[]` 可关闭 inherit 模式的全部校验。
|
|
324
|
+
|
|
269
325
|
守卫装在 DSH 工具注册表的调度入口上,因此 **`run_code`(代码模式 / PTC)里通过 SDK 调用的 `tools["subagent"]` 同样被拦截**:该子调用走的是与直接调用相同的 `prepare → guard → dispatch` 流水线,拒绝理由以 `ToolCallError` 抛回程序。也就是说模型无法靠把委派写进代码里绕过白名单(`test/subagent-model-authorization-ptc.test.ts` 用真实 `ToolRuntime` + 假 code runtime 验证了放行、缺省拒绝与越权拒绝三条路径)。
|
|
270
326
|
|
|
271
327
|
配置项(插件行 `config`,全部可省略):
|
|
@@ -273,7 +329,8 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
|
|
|
273
329
|
| 字段 | 默认 | 含义 |
|
|
274
330
|
|---|---|---|
|
|
275
331
|
| `subagentModelAuthorization` | `true` | 是否启用上述授权守卫;设为 `false` 回到 DSH 原有行为 |
|
|
276
|
-
| `subagentModelTools` | `['subagent']` |
|
|
332
|
+
| `subagentModelTools` | `['subagent']` | 需要授权的委派工具名(explicit:必须成对给出 `provider`+`model`);preset 里自定义了 `toolName` 时在此列出 |
|
|
333
|
+
| `subagentModelInheritTools` | `['subagent_fork', 'spawn_teammate']` | 按设计继承调用者路由的委派工具(校验实际继承到的路由);设 `[]` 关闭 |
|
|
277
334
|
| `subagentModelScope` | `session` | `session` 只约束记录了允许列表的会话;`preference` 额外用当前设置卡列表约束未记录的会话 |
|
|
278
335
|
|
|
279
336
|
改动只在设置卡片里保存过的勾选生效:设置改动只影响之后新建的会话(DSH 的会话快照语义),已运行的会话继续使用它自己记录的那份列表。
|
|
@@ -472,8 +529,27 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
|
|
|
472
529
|
| POST | `/accounts` | 号池动作:`set-primary` / `set-alias` / `delete` / `clear-cooldown` / `clear-auth-failed` / `strategy` |
|
|
473
530
|
| POST | `/logout` | 注销(号池感知) |
|
|
474
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
|
+
|
|
475
549
|
与其它线路一样,所有修改状态的路由只接受同源 JSON POST,并校验 `Origin` 与 `Host`。**响应里永远不含
|
|
476
|
-
access token / refresh token / 授权码 / PKCE verifier
|
|
550
|
+
access token / refresh token / 授权码 / PKCE verifier**;诊断里提到某个令牌时只输出它的
|
|
551
|
+
**SHA-256 指纹前缀**(`sha256:.../len:...`),**不是**令牌的任何一段原文——早前版本会打印前 6 个字符,
|
|
552
|
+
那本身就是一次凭据泄露,因为同一个字符串会被渲染到设置卡片并写进 Host 日志(有测试锁定)。
|
|
477
553
|
|
|
478
554
|
## 安全边界
|
|
479
555
|
|
|
@@ -481,6 +557,8 @@ Antigravity 的 access token / refresh token 使用独立的系统凭据存储
|
|
|
481
557
|
|
|
482
558
|
**WorkBuddy token 仅在 Host 内处理,从不进入浏览器**(`/workbuddy/api` 响应不含 `accessToken` / `refreshToken`,有测试锁定)。桌面扫描账号仍使用 CodeBuddy 自己的登录态文件:续期只原子写回其 `auth` 块;从本插件删除时只隐藏/恢复,绝不删除原文件。通过浏览器授权添加的账号归本插件所有,保存在独立系统凭据存储中:Windows CurrentUser DPAPI(`$DSH_HOME/storages/workbuddy-accounts.json.dpapi`)、macOS 登录钥匙串、Linux Secret Service;这类账号可在设置页真正删除。
|
|
483
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
|
+
|
|
484
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` 与只增不换的别名集共同表达,**任何路由键都不是令牌或其摘要**(令牌会轮换,用它做键会产生幽灵账号)。每次写入都**先读回校验再落盘**,校验失败会抛错且不破坏既有数据。
|
|
485
563
|
|
|
486
564
|
**收编(adopt)是只读的**:本插件从不创建、修改、移动或删除 Claude Code 的任何文件;它只读取。收编得到的快照**永不由本插件刷新**(原因见上文)。
|