mslxdff 0.1.155 → 0.1.158

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 (151) hide show
  1. package/bin/mslxdff.js +4 -4
  2. package/docs/ARCHITECTURE.md +426 -0
  3. package/docs/FEATURE_TREE.md +164 -0
  4. package/docs/MOBILE.md +82 -0
  5. package/docs/adr/0001-reasoning-content-injection.md +14 -0
  6. package/docs/adr/0002-models-free-filter.md +12 -0
  7. package/docs/adr/0003-zero-state-no-auth.md +10 -0
  8. package/docs/adr/0004-bearer-token.md +18 -0
  9. package/docs/adr/0005-peer-mesh.md +53 -0
  10. package/docs/adr/0006-broadband-member.md +103 -0
  11. package/docs/adr/0007-multi-provider-prefix.md +25 -0
  12. package/docs/adr/0008-share-keys-to-peers.md +52 -0
  13. package/docs/adr/0009-chat-repl.md +37 -0
  14. package/docs/adr/0010-allowlist.md +36 -0
  15. package/docs/adr/0011-broadband-stream.md +30 -0
  16. package/docs/adr/0012-responses-endpoint-codex-sync.md +48 -0
  17. package/docs/adr/0013-node16-compat.md +41 -0
  18. package/docs/adr/0014-deepseek-provider.md +52 -0
  19. package/docs/adr/0015-upstream-probe-routing.md +50 -0
  20. package/docs/adr/0016-model-capabilities.md +27 -0
  21. package/docs/adr/0017-ai-sdk-upstream-engine.md +59 -0
  22. package/docs/adr/0018-zen-client-identity.md +52 -0
  23. package/docs/adr/0019-share-keys-always-lend.md +59 -0
  24. package/docs/adr/0020-zen-free-lane-agent-shape.md +52 -0
  25. package/docs/adr/0021-usage-report-jsonl.md +47 -0
  26. package/docs/adr/0022-models-capability-merge.md +61 -0
  27. package/docs/adr/0023-key-provider-default-direct.md +58 -0
  28. package/docs/adr/0024-node18-baseline.md +63 -0
  29. package/docs/adr/0025-workbuddy-authdir-follows-state.md +71 -0
  30. package/docs/adr/0026-cline-provider-id-unify.md +67 -0
  31. package/docs/adr/0027-codearts-provider.md +48 -0
  32. package/docs/adr/0028-traework-provider.md +34 -0
  33. package/docs/adr/0029-qoder-native-provider.md +60 -0
  34. package/docs/adr/0030-models-list-scoped-by-picks.md +53 -0
  35. package/docs/adr/0031-qoder-true-streaming.md +50 -0
  36. package/docs/adr/0032-generic-responses-channel.md +72 -0
  37. package/docs/adr/0033-cline-allowlist-auto-sync.md +75 -0
  38. package/docs/adr/0034-request-level-human-readable-observability.md +60 -0
  39. package/docs/adr/0035-sdk-channel-headers-timeout.md +49 -0
  40. package/docs/adr/0036-qoder-per-request-sticky-account.md +82 -0
  41. package/docs/adr/0037-qwenwork-independent-provider.md +82 -0
  42. package/docs/adr/0038-zcode-provider.md +140 -0
  43. package/docs/agents/domain.md +51 -0
  44. package/docs/agents/issue-tracker.md +30 -0
  45. package/docs/agents/triage-labels.md +15 -0
  46. package/docs/cli_help.md +1391 -0
  47. package/docs/cli_help_mini.md +133 -0
  48. package/docs/plans/bench-via-latency-2026-09-01.md +215 -0
  49. package/docs/plugins.md +187 -0
  50. package/package.json +1 -1
  51. package/src/auto.js +254 -254
  52. package/src/bench/cline-bench.js +42 -42
  53. package/src/bench/probe.js +70 -70
  54. package/src/bench/report.js +162 -162
  55. package/src/bench/runner.js +77 -77
  56. package/src/bench/via-probe.js +124 -124
  57. package/src/bench/via-routes.js +87 -87
  58. package/src/bench/workbuddy-bench.js +54 -54
  59. package/src/chat/engine.js +160 -160
  60. package/src/chat/gateway.js +163 -163
  61. package/src/chat/orchestrator.js +234 -234
  62. package/src/chat/prompt.js +70 -70
  63. package/src/chat/repl.js +88 -88
  64. package/src/chat/terminal.js +135 -135
  65. package/src/chat/tools.js +306 -306
  66. package/src/chat-pipeline/index.js +123 -123
  67. package/src/chat-pipeline/policy.js +76 -76
  68. package/src/chat-pipeline/serial-trial.js +210 -210
  69. package/src/cli/commands/group.js +249 -249
  70. package/src/cli/commands/model/list-providers.js +4 -3
  71. package/src/cli/commands/model/list-render.js +7 -9
  72. package/src/cli/commands/model/list-sort.js +52 -0
  73. package/src/cli/commands/model/list-tty.js +76 -0
  74. package/src/cli/commands/model/list.js +5 -62
  75. package/src/cli/commands/model/picks.js +50 -50
  76. package/src/cli/commands/provider/bench-via.js +247 -247
  77. package/src/cli/commands/provider/bench.js +141 -141
  78. package/src/cli/commands/provider/index.js +124 -116
  79. package/src/cli/commands/provider/models.js +139 -128
  80. package/src/cli/commands/provider/qwenwork-login.js +119 -0
  81. package/src/cli/commands/provider/zcode-login.js +77 -0
  82. package/src/cli/commands/provider/zcode-quota.js +55 -0
  83. package/src/cli/commands/sync.js +232 -232
  84. package/src/cli/provider-row.js +2 -2
  85. package/src/cli/status.js +279 -279
  86. package/src/daemon.js +96 -96
  87. package/src/model-capabilities/enrich.js +86 -86
  88. package/src/model-capabilities/index.js +183 -183
  89. package/src/model-capabilities/parse.js +70 -70
  90. package/src/model-trace.js +1 -0
  91. package/src/models.js +225 -225
  92. package/src/providers/classify.js +1 -1
  93. package/src/providers/cline/auth.js +228 -228
  94. package/src/providers/cline/chat.js +307 -307
  95. package/src/providers/cline.js +2 -2
  96. package/src/providers/keyring.js +60 -56
  97. package/src/providers/qoder/chat.js +183 -100
  98. package/src/providers/qoder/index.js +230 -184
  99. package/src/providers/qoder/sse.js +103 -46
  100. package/src/providers/qoder/sticky.js +1 -0
  101. package/src/providers/qoder/stream.js +32 -9
  102. package/src/providers/qwenwork/account-store.js +133 -0
  103. package/src/providers/qwenwork/constants.js +67 -0
  104. package/src/providers/qwenwork/cosy.js +120 -0
  105. package/src/providers/qwenwork/crypto.js +218 -0
  106. package/src/providers/qwenwork/http.js +20 -0
  107. package/src/providers/qwenwork/index.js +327 -0
  108. package/src/providers/qwenwork/payload.js +142 -0
  109. package/src/providers/qwenwork/rsa.js +54 -0
  110. package/src/providers/qwenwork/sse.js +268 -0
  111. package/src/providers/qwenwork/stream.js +130 -0
  112. package/src/providers/qwenwork/upstream.js +120 -0
  113. package/src/providers/qwenwork.js +1 -0
  114. package/src/providers/registry.js +66 -56
  115. package/src/providers/share-keys.js +2 -2
  116. package/src/providers/workbuddy/chat.js +248 -248
  117. package/src/providers/workbuddy/reshape.js +152 -152
  118. package/src/providers/workbuddy.js +2 -2
  119. package/src/providers/zcode/account-store.js +129 -0
  120. package/src/providers/zcode/auth.js +28 -0
  121. package/src/providers/zcode/chat.js +171 -0
  122. package/src/providers/zcode/const.js +54 -0
  123. package/src/providers/zcode/headers.js +55 -0
  124. package/src/providers/zcode/index.js +124 -0
  125. package/src/providers/zcode/models.js +65 -0
  126. package/src/providers/zcode/oauth.js +120 -0
  127. package/src/providers/zcode/quota.js +176 -0
  128. package/src/providers/zcode/sse.js +179 -0
  129. package/src/reasoning.js +32 -32
  130. package/src/routes/chat/gateway.js +46 -46
  131. package/src/routes/chat/relay-pipeline.js +250 -246
  132. package/src/routes/chat/via-route-handler.js +144 -144
  133. package/src/routes/hedge.js +255 -255
  134. package/src/routes/models-route.js +167 -167
  135. package/src/routes/peers.js +273 -273
  136. package/src/routes/stream.js +438 -399
  137. package/src/runtime/bootstrap.js +45 -45
  138. package/src/runtime/provider-gate.js +33 -30
  139. package/src/runtime/providers-setup.js +165 -165
  140. package/src/server.js +64 -64
  141. package/src/state/schemas/allowlist.js +92 -92
  142. package/src/sync-opencode.js +280 -280
  143. package/src/transport/index.js +244 -244
  144. package/src/transport/pool.js +56 -56
  145. package/src/transport/retry.js +24 -24
  146. package/src/transport/sse.js +93 -93
  147. package/src/upstream-probe/display.js +52 -52
  148. package/src/upstream-probe/probe.js +49 -49
  149. package/src/upstream-probe/rotate.js +110 -110
  150. package/src/upstream-probe/start.js +45 -45
  151. package/src/upstream.js +289 -289
@@ -0,0 +1,426 @@
1
+ # mslxdff 架构与功能说明
2
+
3
+ > 本文档是 mslxdff 的**长效架构视图**,描述"当前系统长什么样、为什么长这样、改哪里必须同步这里"。
4
+ >
5
+ > **变更契约(强制)**:每新增或改动一个功能,都必须同步更新本文档的对应章节(`## 5 功能地图`、`## 6 契约与配置`、`## 7 目录导览` 中受影响的部分)。
6
+ > 结构性决策(引入新模块、改变请求链路、改存储 schema)**还额外要求**在 `docs/adr/` 追加以 ADR-NNN 编号的决策记录,并在 `## 8 决策索引` 登记。
7
+ > 详见 `## 1 变更契约`。检查工具:`npm run docs:check`。
8
+
9
+ ## 1. 变更契约(本文件是唯一权威的"变更-记账"门)
10
+
11
+ 新增或改动任何功能时,必须满足下面的矩阵——**不满足则视为未完成**:
12
+
13
+ | 变更类型 | 必须更新 | 附加要求 |
14
+ |---|---|---|
15
+ | 新增/修改 CLI 命令或 `-参数` | `## 6 契约与配置` 的 CLI 表 **+ `docs/cli_help.md`** | 详细参数手册与 CLI 表保持一致 |
16
+ | 新增/修改环境变量 | 同上 的 Env 表 **+ `docs/cli_help.md` 附录 A** | 命名遵循 `MSLXDFF_*`/`UPSTREAM_*` 前缀 |
17
+ | 新增/修改 HTTP 路由 | `## 4 请求链路` 路由图 + `## 6` 契约 | 鉴权规则按 ADR-0004 |
18
+ | 新增/修改 State 持久化字段 | `## 6` `## 7` | 兼容旧值或写明迁移,记 ADR |
19
+ | 新增/修改供应商 Provider | `## 3 多供应商` + `## 5` 功能地图 | 默认 opencode,其余加 `<id>/` 前缀(见 ADR-0007) |
20
+ | 新增/修改插件 Hook 点 | `docs/plugins.md` + `## 5` | 保持"插件失败只记日志"原则 |
21
+ | 新增/修改上游行为(限流、重试、时延) | `## 5` 质量机制 | 常量须在文件顶部定义(见 AGENTS.md 教训) |
22
+ | 引入新模块 / 改变模块边界 | `## 7 目录导览`(含文件树) | 单文件保持 ≤10KB,>20KB 拆分(见 AGENTS.md) |
23
+ | 影响请求成败语义(超时/降级/冷却) | `## 5` 质量机制 + `## 4` | 记 ADR 说明取舍 |
24
+ | 纯重构(行为不变) | `## 7` 文件树即可,`## 5/6` 若提及接口则同步 | 跑全量测试证明行为等价 |
25
+
26
+ > 写文档时:优先改**语义**而非字面——架构文档描述"设计意图",若实现与文档冲突,以代码为准并反查是否该更新本文档或引入 ADR 修正。
27
+
28
+ ## 2. 一句话说明
29
+
30
+ mslxdff 是把 opencode.ai 的免费模型池(以及可选的 OpenRouter 免费模型)包装成**本地 OpenAI 兼容代理**的零依赖 Node 服务:客户端(OpenCode / Claude Code / WorkBuddy 等)把请求打到 `http://127.0.0.1:8989/v1/*`,mslxdff 负责模型排序、自动选择、慢模型降权、多账户轮转、群组接力,最后转发到上游。
31
+
32
+ ## 3. 多供应商架构(0.1.56 + 通用 OpenAI 兼容 0.1.59 + WorkBuddy 0.1.60)
33
+
34
+ ```
35
+ ┌─ 客户端 ─────────────────────────────┐
36
+ │ model = "big-pickle" │ ← 裸 id:默认供应商(opencode)
37
+ │ model = "openrouter/google/gemma:free"│ ← 带前缀:路由到指定供应商
38
+ │ model = "myapi/gpt-4" │ ← 通用 OpenAI 兼容供应商
39
+ │ model = "workbuddy/hy3" │ ← WorkBuddy(copilot.tencent.com)
40
+ └──────────────┬───────────────────────┘
41
+ ▼
42
+ POST /v1/chat/completions
43
+ ▼
44
+ ┌─ src/providers/dispatcher.js ──────────┐
45
+ │ splitModelId(): 按 '<provider>/<raw>' │
46
+ │ 拆前缀;转发前剥掉前缀只发原始 id │
47
+ └────────┬──────────────┬───────────────┬───────────────┬───────────────┘
48
+ ▼ ▼ ▼ ▼
49
+ ┌─ opencode.js ──┐ ┌─ openrouter.js ──┐ ┌─ generic.js ──┐ ┌─ workbuddy.js ──┐ ┌─ codearts/ ─────┐
50
+ │ createUpstream │ │ createOpenRouter │ │ createGeneric │ │ createWorkbuddy│ │ createCodearts │
51
+ │ (upstream.js) │ │ Provider │ │ Provider │ │ Provider │ │ Provider │
52
+ └────────────────┘ └──────────────────┘ └───────────────┘ └────────────────┘ └────────────────┘
53
+ │ apiKeys: [...] │ baseUrl + keys │ keys+auths │
54
+ │ keyring.js: │ keyring.js │ keyring + │
55
+ └────────────────── └────────────── │ refresh │
56
+ ▼ ▼ ▼
57
+ openrouter.ai/api/v1 <custom baseUrl>/v1 copilot.tencent.com/v2
58
+ │
59
+ ```
60
+
61
+ - **前缀规则**:`<provider>/<raw-id>`。裸 id 恒指默认供应商(opencode),向后兼容。模型对外 id 由各 provider 用 `joinModelId` 前缀化,客户端看到的就是带前缀的完整 id;转发时 dispatcher 剥前缀。(ADR-0007)
62
+ openrouter.ai/api/v1 <custom baseUrl>/v1 copilot.tencent.com/v2 snap-access.cn-north-4
63
+ - **默认供应商 opencode 恒启用**;`openrouter` 在配了任一 key 时自动启用(env `MSLXDFF_OPENROUTER_KEY` 或 state `providerKeys.openrouter`);**通用供应商**在 `state.json providerConfigs.<id> = { baseUrl, keys }` 均非空时启用(`mslxdff -provider add <id> <baseUrl> <key>`),`baseUrl` 为 OpenAI 兼容根(如 `https://api.example.com/v1`),`model` 形如 `myapi/gpt-4`;**WorkBuddy** 在 `providerConfigs.workbuddy={baseUrl,keys,auths}` 的 `keys` 非空时启用(`mslxdff -provider add workbuddy https://copilot.tencent.com <key>` 或 `node workbuddy-token-auto.js` 自动落盘),`model` 形如 `workbuddy/hy3` 或 `workbuddy/<uid>:hy3`(后者钉死指定账号,`header x-mslxdff-workbuddy-uid` 亦可),`auths` 与 `keys` 一一对应(`{uid,domain,enterpriseId,refreshToken}`),`baseUrl` 默认 `https://copilot.tencent.com`(`MSLXDFF_WORKBUDDY_BASE_URL` 可覆盖),多号下 `POST /v2/chat/completions` 遇 `402/insufficient` 自动环形切号,`GET /v2/billing/meter/get-user-resource` 按 `uid` 查余额(`workbuddy-balance.js` TTL 5min)。
64
+ - **codearts(华为云 CodeArts Agent,ADR-0027)**在 `providerConfigs.codearts.keys` 非空时启用(`mslxdff -provider codearts login` PKCE 授权落盘,一账号一 blob JSON:refreshToken/codeVerifier/dpopJwk/AK/SK/securityToken),`model` 形如 `codearts/glm-5.3-flash`;上游恒 `stream:true`(v2 SSE 全文快照→delta/聚合),STS 凭证临期自动单飞刷新、refresh_token 轮换原位写回,多账号 keyring 轮转;恒 local-only 不借出 key。
65
+ - **未识别前缀**回退:整体当裸 id 交给默认供应商处理(例如用户传 `claude/sonnet` 想走 opencode,兼容)。
66
+ - **瞬时 key 共享(ADR-0019,取代 ADR-0008 的开关)**:默认借出——本节点在 outgoing 转发(peer/组员接力/via-route)时把命中供应商的 key 列表放 `x-mslxdff-share-keys` 头附带;组员仅本次请求借用,用完即弃不落盘。组内互信是前提,无开关、无白名单。**硬排除**:`opencode`(无 key)、`workbuddy`(local-only 不走组员)、`cline`(refresh-token 型,并发刷新会互踢下线)、`codearts`(refreshToken+DPoP 绑定型 blob,同 cline;ADR-0027)。
67
+ ## 4. 请求链路
68
+
69
+ ```
70
+ 客户端
71
+ │ Authorization: Bearer <token> (ADR-0004,恒定时间比较,/health 除外)
72
+ ▼
73
+ POST /v1/chat/completions | POST /v1/responses (/v1/* 需认证)
74
+ │ plugins: request:received → model:select → model:beforeTry
75
+ │ /v1/responses:Responses⇄Chat 形状翻译后进同一 ChatPipeline(ADR-0012,stateless)
76
+ ▼
77
+ src/routes/chat.js (门面) ──▶ src/routes/chat/{index,hedge,local,peer,broadband,exhausted,via-route}-handler.js
78
+ │
79
+ ├─ via-route:显式锁模型 `provider/model` 按 `via-routes.json:best` 单路径直达(`via:host:port` 单 peer 借 `x-mslxdff-share-keys`,`direct` 本机),不并发,失败秒切直连
80
+ ├─ 本地处理:model 解析 (auto/normalize) ──▶ upstream ──▶ 拆供应商转发
81
+ ├─ 组员接力 (peer):本机超时可让组员处理,结果带 x-mslxdff-via
82
+ ├─ 对冲 (hedge):流式 / 非流式 首块延迟对冲,谁快用谁 (MSLXDFF_HEDGE_DELAY_MS)
83
+ └─ 模型无服务 → 自动选健康模型 (src/auto.js)
84
+ ▼
85
+ 上游 (Dispatcher → Provider)
86
+ ▼
87
+ SSE 流式转发逐 chunk / 非流式透传 JSON
88
+ ```
89
+
90
+ - 认证:`POST /v1/chat/completions`、`GET /v1/models` 需 `Authorization: Bearer <token>`;`GET /health` 公开。
91
+ - 中间件注入:DeepSeek 思考模式在 assistant 消息注入 `reasoning_content` 占位(ADR-0001)。
92
+ - 模型列表:`/v1/models` 只暴露免费模型(whitelist + `-free` 后缀,`big-pickle` 特例,ADR-0002),10 分钟缓存;每条默认内联 `capabilities` 子对象(推理档位/模态/toolCall/上下文/最大输出/价格/endpoints/upstreamApi,ADR-0022),`?raw=1` 回原始透传形状,codex 调用者不富化(ADR-0012 `models:[]` 不变)。
93
+
94
+ ## 5. 功能地图(现状 + 归属模块)
95
+
96
+ | 功能 | 说明 | 主要模块 | 关键常量/配置 |
97
+ |---|---|---|---|
98
+ | 认证 | Bearer token,state 生成/轮换,恒时比较 | `src/state.js`, `src/server.js` | `-showtoken` / `-refresh-token` |
99
+ | 模型自动选择 | auto: 冷却>首选>延迟EMA>错误时间排序 | `src/auto.js` | `MSLXDFF_PREFERRED_MODEL`(默认 big-pickle) |
100
+ | 模型勾选集 | state `modelPicks` 限制候选池;显式指定真实上游模型自动勾选 | `src/auto.js` | `-model pick/unpick/…` |
101
+ | 多账户 key 轮转 | 每供应商多 key round-robin + 30s 冷却隔离(401/403/429/5xx),全冷却即报失效 | `src/providers/keyring.js` | `MSLXDFF_OPENROUTER_COOLDOWN_MS`、`-provider` |
102
+ | 瞬时 key 共享 | 转发(peer/组员/via-route)时自动附带命中供应商的 key 给组员借用一次(不落盘);硬排除 opencode/workbuddy/cline;无开关无白名单 | `src/providers/share-keys.js`, `src/routes/peers.js`, `src/routes/chat/via-route-handler.js` | — |
103
+ | 冷却/慢模型 | 模型出错 60s 冷却(慢 5min),`slow:true` 排最后 | `src/upstream.js`, `src/auto.js` | `SLOW_TOTAL_MS`(20s)、`MSLXDFF_MODEL_COOLDOWN_MS`、`STREAM_TIMEOUT_MS`(25s)、`MAX_STREAM_MS`(120s) |
104
+ | 首块对冲 (hedge) | 流式 1s 未回发并发组员/备用竞速 | `src/routes/chat/*` + `hedge.js` | `MSLXDFF_HEDGE_DELAY_MS`(1000) |
105
+ | 首块闸门 | 上游 200+SSE 头后首块未到即**真掐上游**(自持 reader.cancel,锁定流的 body.cancel 是空操作)换候选;keepalive 注释帧不算首块(不解除闸门/不触发救回,仅透传);未写真实数据帧才算可换(已写注释帧仍可换);非末位 25s 快速档、末位/唯一与**借道 via-route** 走耐心档 120s;断下游即掐上游 | `src/routes/stream.js`, `src/routes/chat/relay-pipeline.js` | `MSLXDFF_STREAM_TIMEOUT_MS`(25000)、`MSLXDFF_LAST_CANDIDATE_TIMEOUT_MS`(120000,0=不限) |
106
+ | 空转重试 | 流式 200 但零正文零工具调用(`EMPTY_MODEL_RESPONSE`)同模型暂停 1s 后重拉,最多 2 次;`empty-turn-retry` 事件可观测;429/403/500 与 fetch 异常不重试(走原有切号/failover)。**错误包络暂扣**:本轮未写出真实输出前,网关 200 包错误帧(无 content/tool_calls 的 `.error`/纯 `finish_reason=error` 帧)不写下游——下游流式 UI 不再展示瞬时错误(如 Vertex 503 + google fallback 400),摘要记 `detail.upstreamErrorText` 并带入最终 502;已开转后错误帧照常透传,解析失败默认透传 | `src/chat-pipeline/serial-trial.js`, `src/routes/chat/relay-pipeline.js`(`isEmptyTurnError`), `src/routes/stream.js`(`holdableChunk`) | `MSLXDFF_EMPTY_TURN_RETRIES`(2,0=关)、`MSLXDFF_EMPTY_TURN_RETRY_DELAY_MS`(1000) |
107
+ | 请求时间线 | 每个请求在 `timeline.log` 追加一条人读汇总:时间、reqId、model、直连结果、组员逐个胜负/耗时、空转重试次数、最终 status/finish_reason/tools/chars、总耗时;`events.log` 仍保留完整 JSON 事件;不记录 prompt/响应正文/凭据 | `src/timeline.js`, `src/chat-pipeline/index.js`, `src/logs.js` | `timeline.log`、`-log` |
108
+ | 按模型链路日志 | 每个模型一个 `logDir/<provider>-<model>.log`(`/` 等非法字符安全化为 `-`),按请求阶段追加:request/ordered/route、upstream-try(安全 payload 摘要:stream/messages/roles/tools/maxTokens)、upstream-done/error(带 `via=<host> account=<region|uid>` 回显)、empty-turn-retry(`retry N/M delay=Xms after=<host> account=<...> pick=<...>`,切号换 URL 的真因)、peer(组员地址+payload 摘要)、relay-done(status/finish/chunks/bytes/tools/chars/usage)、result/client-response。**事件面用黑名单**(`TRACE_DENY`:默认**全部可见**,只排除噪声 `peer-health`/`heartbeat` 与敏感面 `client-session`/`upstream-probe*`)——关键决定不许再被静默吞掉(`empty-turn-retry` 曾被白名单吞过);决定类事件只渲染 `DECISION_FIELDS` 登记的标量(payload/detail/正文一律不落,"加日志"≠"倒数据");`relay-done`/`result` 亦带回显(`upstream=`/`account=`/`pick=`),`pick=` = 选号决定(`new|sticky|switch|rr|forced`)、`cooled=` = 冷却状态码;`upstreamEcho(res)` 是取回显头的唯一入口;不落 prompt/响应正文/headers/凭据;日志失败不影响请求 | `src/model-trace.js`, `src/chat-pipeline/index.js`, `src/chat-pipeline/serial-trial.js`, `src/routes/chat/relay-pipeline.js`, `src/routes/peers.js`, `src/runtime/bootstrap.js` | `logDir/*.log`、`-log` |
109
+ | SDK 通道 headers 超时 | SDK 通道 `doStream` 到点仍未返回响应头即判挂死并抛错(交调用方回退/换路),默认 120s 与末位耐心档对齐;`AbortController` 一并中止底层请求;错误文案避开 "timed out"(cline 客户端按该文案重试会放大挂死) | `src/upstream-engine/sdk/attempt.js`(`withHeadersTimeout`/`headersTimeoutMs`), `src/providers/base.js` | `MSLXDFF_SDK_HEADERS_TIMEOUT_MS`(120000, 0=关)、`test/sdk-headers-timeout.test.js` |
110
+ | 失败收尾对账 | 失败路径(exhausted-local / exhausted-all 的有/无 upstream 四分支)在 `result` 后补 `client-response`,与成功路径口径一致(模型日志 `result` 条数 == `client-response` 条数) | `src/routes/chat/exhausted-handler.js` | `test/exhausted-client-response.test.js` |
111
+ | 群组接力 | 组员/组长网格,赶 IP 级限流,宽带成员经 Leader 中继;`-addtogroup` 零/单参数进**手机宽带接入向导**(问组长地址+组名 → 自动加组 → 自动起服务 → 回报出口 IP) | `src/routes/chat/peer*`, `src/cli/join-wizard.js` | `-creategroup/-addtogroup/-group…` |
112
+ | 组员竞速与错误详情 | 首批并发竞速,**首个成功立即返回**(慢/黑洞不阻塞,连接级 3s 超时),winner 确定即取消其余 in-flight(不产生迟到成功、不记错误、不留僵尸连接);同一请求内同一 peer 不重复试;失败详情聚合回传调用者;中继剥离 caller 绑定的 reasoning 加密态(`encrypted_content` 出口绑定,防 400);中继 SSE 数据间隔超时 120s(与应用层 `MAX_STREAM_MS` 对齐,undici 只兜底不掐思考流——旧 30s 曾把 muse-spark 首块后 ~31s 的活流掐成 `terminated`) | `src/routes/peers.js`, `src/routes/chat/peer-handler.js` | `MSLXDFF_PEER_CONNECT_TIMEOUT_MS`(3000)/`MSLXDFF_PEER_RACE_LIMIT` |
113
+ | 宽带中继长连 | broadband 成员 `GET /v1/groups/relay/stream` SSE 长连 + 25s ping,`enqueue` 直推 `event: relay`,空闲从 1/s 轮询降至 1/25s,断线指数退避重连,`poll` 保留降级;**空组不早退**(启动 0 组打 `idle (0 group)`,加组后 ≤10s 由 ensure 自动接上并打 `connected <组> via <leader> (SSE)`,无需重启 daemon) | `src/routes/groups-relay.js:streamHandler`, `src/routes/relay-queue.js:subscribeStream/pushToStream`, `src/runtime/{broadband,broadband-stream,bootstrap}.js` | `MSLXDFF_BROADBAND_STREAM`(默认开,0关)、`MSLXDFF_BROADBAND_PING_MS`(25000)、`MSLXDFF_BROADBAND_STALE_MS`(90000) |
114
+ | 组员中继总开关 | state.json: useGroup(默认 on)控制 opencode 失败时是否走 via-route/hedge/peer/broadband 组员中继;off 时所有供应商仅本机;带 key 上游默认恒直连(ADR-0023,仅 opencode 走组员;MSLXDFF_USE_GROUP_KEYS=1 可把 key 供应商开回,cline/workbuddy 仍硬禁)| src/state/schemas/use-group.js, src/chat-pipeline/serial-trial.js, src/chat-pipeline/index.js, src/cli/commands/use-group.js | -use-group [on|off]、MSLXDFF_USE_GROUP、MSLXDFF_USE_GROUP_KEYS |
115
+ | auto 供应商限定 | 请求头 `x-mslxdff-auto-provider: <id>` 把 auto 候选限定到单供应商(`opencode`=裸 id 免费池过滤 `isFreeModel`,其他=前缀匹配),`auto-scope` 事件记 before/after;`-chat` 的 gateway auto 默认带 `opencode`(-chat 只支持 opencode 上游) | `src/chat-pipeline/policy.js`, `src/chat-pipeline/index.js`, `src/chat/gateway.js` | 请求头 `x-mslxdff-auto-provider`(HTTP 面) |
116
+ | 免费额度匿名兜底 | `public` 429 且 free 模型 → 空 `Authorization` 重试(hermes 通道) | `src/upstream.js` | `MSLXDFF_FREE_ANON_DELAY_MS`(1000)/`_RETRIES`(3)/`MSLXDFF_FREE_ANON=0` 关 |
117
+ | 免费层 agent 形状门禁(ADR-0020) | zen 2026-09-18 起再收紧:免费模型必须 `stream:true` **且** `tools` 含 bash/edit/glob/grep/read 五名(0-4 个或换名一律 403 FreeTierError),UA ≥ `opencode/1.18.0`(低版本 426 UpgradeRequired)。`src/free-lane.js` 单一来源:缺的名字补最小 `{type:object}` 定义(幂等)、强制流式;非流式调用者拿到的 SSE 由 `aggregateChatSse` 聚合回 JSON。SDK 流式通道不经 legacy,由 `upstream-engine/index.js` 在派发前统一注入 | `src/free-lane.js`, `src/upstream.js`, `src/upstream-engine/index.js`, `src/opencode-identity.js` | `MSLXDFF_FREE_LANE=0` 关(回退旧行为), `MSLXDFF_FREE_LANE_DEBUG=1` 打 `[free-lane]` 日志 |
118
+ | Keep-Alive + 预热 | undici keepAlive 30/60s,`srv.ready` 预拉模型(仅默认供应商 opencode;cline free 自检独立于预热) | `src/upstream.js`, `src/providers/dispatcher.js` | `MSLXDFF_UPSTREAM_KEEPALIVE_*`、`MSLXDFF_PREHEAT=0` |
119
+ | 通用 OpenAI 兼容供应商 | `providerConfigs.<id>={baseUrl,keys,allowAnyModels,allowedModels}`,`mslxdff -provider add <id> <baseUrl> <key>` 一键添加(默认 `allowAny OFF` 空名单=禁用,需 `allowlist set` 或 `allowAny on` 否则 `403`),`set-url` 改地址,`list` 看 `baseUrl+keys`,`generic.js` 复用 keyring/retry(流式 chat 缺省经 AI SDK `upstream-engine/sdk/dispatch.js`,非流式回退原生),前缀路由 `myapi/gpt-4;ocgo(opencode.ai 域名)自动补 Console Go 身份头(x-opencode-session/request + opencode UA,缺失会 400 MissingSessionID;session 由 opts.sessionId 摘要派生保持稳定)` | `src/providers/generic.js`, `src/state.js`, `bin/mslxdff.js` | `-provider add/set-url/list/share/allowlist/allowAny`, `MSLXDFF_<ID>_BASE_URL/_KEY` |
120
+ | WorkBuddy 供应商 | `providerConfigs.workbuddy={baseUrl,keys,auths}`(`auths` 与 `keys` 一一对应,`workbuddy-token-auto.js` 自动落盘 `auths/workbuddy-*.json` + `state.json`;`baseUrl` 默认 `https://copilot.tencent.com`),`POST /v2/chat/completions`(强制 `stream:true`,多号环形:`402/insufficient` 自动切号 + `balanceCache` TTL 5min,`x-mslxdff-workbuddy-uid` 定号,`model workbuddy/<uid>:<id>` 亦可)+ `GET /console/enterprises/personal/models`(`credits xN.NN` 升序前缀 `workbuddy/`)+ `POST /v2/billing/meter/get-user-resource` 查余额(`workbuddy-balance.js`),**401/403 或 body 含 `token expired/invalid/unauthorized` 自动 `POST /v2/plugin/auth/token/refresh` 回写 `state.json`+`auths/` 并 `ring.replace` 重放(并发去重、JWT 5min 主动续期、`listModels` 401 同步刷新,刷新后仍 401 则切下一号)**,`daily-checkin` 双域幂等 100 credits/30d(并行 3,`--json` 聚合),出口 SSE 经 `reshape.js` 整形(碎片 reasoning 按 150 字符分片聚合,`pull` 循环读源永不空返回——防 WHATWG 流"pull 空转后不再调度"停摆,逐帧到达的思考流不再首帧后卡死),**SDK 通道(缺省启用)**(底层 `fetchOnce` 缺省走 `@ai-sdk/openai-compatible`,转调共用 `upstream-engine/sdk/attempt.js`(OpenAI 请求→SDK 入参 `convert.js`、parts→OpenAI SSE 帧 `sse.js`、HTTP 错误映射为带状态码 Response);局部 `MSLXDFF_WORKBUDDY_SDK` 显式设 `legacy`/关闭词、或未设置时继承全局 `MSLXDFF_UPSTREAM_ENGINE` 设 `legacy`/关闭词即回退原生 transport,不可用自动回退并告警一次;轮换/刷新/reshape 全链复用;上游非标字段不依赖 SDK 白名单:**出站 payload 前置改写**(`payload.js`,逆向官方客户端行为:developer→system、tool_choice 对象→字符串、DeepSeek `thinking.type=enabled`+缺档补 `high`、reasoning_content 回填 requiresReasoningContentOnAssistantMessages)+ **providerOptions 透传**(`thinking` 等非白名单 key 由 `convert.js toProviderExtras` 收进 `providerOptions.<provider>`,AI SDK getArgs 原样 spread 进请求体;空串 reasoning_content 由 convert.js 以 `" "` 占位——AI SDK 以 length>0 判定输出);**tool 序列三重防御清洗**(`sanitize-tools.js`:实测上游三条规则——①按 id 成对 ②结果必须紧跟对应 assistant(配对被 user/assistant 消息打断即 400)③首条不得为带工具链的 assistant/tool(compaction 裁剪点最易触发)——清洗把结果前移到调用后、剔无法配对项、首条非 user/system 时注入占位 user;防 `tool_call_sequence_broken` 11148 整单 400;SDK 4xx 时 `upstream-engine/sdk/diagnose.js` 打印 head/tail/issues 诊断;上游 4xx body 经 `rewrapBody` 重建保留——修"读后即毁"致下游 `Body is unusable`,同时 body 摘要打 daemon.log)) | `src/providers/workbuddy.js`, `src/providers/workbuddy/reshape.js`, `src/providers/workbuddy/sanitize-tools.js`, `src/providers/workbuddy/payload.js`, `src/providers/workbuddy/sdk-chat.js`, `src/upstream-engine/sdk/{convert,sse,attempt,chat}.js`, `src/providers/workbuddy-balance.js`, `src/state.js`, `bin/mslxdff.js`, `workbuddy-token-auto.js`, `workbuddy-checkin.js` | `-provider add workbuddy ...`, `-workbuddy checkin/growth/travel/balance/list/remove`, `MSLXDFF_WORKBUDDY_*` |
121
+ | WorkBuddy 增分自动化 | 签到之外的积分来源两条:**成长任务**(`GET /v2/activity/growth/tasks` 列表 → `POST /v2/activity/growth/tasks/accept` 参与 → `POST /v2/chat/completions` 带 `extra_vars.growthEvent`(JSON 字符串)触发 → `POST /activity/growth/tasks/{code}/claim` 领奖,**claim 路径无 /v2**;状态机 not_accepted→accepted→completed→claimed,未 accept 进度不累计、completed≠claimed;参与与进度**异步落库**,用 0.5s×8s 轮询等待而非固定 sleep;串行硬要求:任务间 1.2s、账号间 1s、单任务 ≤10 次;策略表 `growth-plans.js` 只对上游实测命中的任务发包,未登记默认 MANUAL)+ **猫猫旅行**(`/activity/growth/buddy/{info,agreement,first,travel/status,travel/depart,travel/claim}`:无猫领养 +300,门槛 400 `first_buddy task not completed yet` 自动补一次 `chat_request_send` 解锁,到站领奖);daemon 每日 09:30 自动(与 09:00 签到错峰,`MSLXDFF_WORKBUDDY_GROWTH=0` 关),结果落 `state.workbuddyGrowth`,CLI `-workbuddy growth/travel` 手动触发 | `src/providers/workbuddy/{growth,growth-plans,growth-api,cat-travel}.js`, `src/runtime/workbuddy-growth.js`, `src/cli/commands/workbuddy.js` | `-workbuddy growth/travel`, `MSLXDFF_WORKBUDDY_GROWTH*` |
122
+ | codearts 供应商(华为云 CodeArts Agent,ADR-0027) | `providerConfigs.codearts.keys` = 一华为账号一 blob JSON(refreshToken/codeVerifier/dpopJwk/AK/SK/securityToken,多账号 keyring 轮转);三路发现(builtin 归一/代理型/福利网关 + `benefit claim` 幂等 `0000`);对话恒 `stream:true` 走 v2 SSE(`text` 全文快照→流式 delta / 非流式聚合 `chat.completion`);SDK-HMAC-SHA256 签名(canonical URI 尾补 `/`、`maas_type: benefit` 签名前写入、Chat-Id/Session-Id 签名后追加)+ DPoP ES256 低 S(`dsaEncoding:"ieee-p1363"`);STS 临期(提前 30min)单飞刷新 + refresh_token 单次轮换**原位写回**(`invalid_grant` 终态死号提示重登);HTTP 200 内嵌错误映射(`tm.00001041`/tpm/并发→429,未注册/benefit not found→400,其余→502);恒 local-only 不借出 key | `src/providers/codearts/{index,auth-pool,sts,sign,dpop,models,sse,stream,chat,const,login}.js`, `src/cli/commands/provider/codearts-login.js` | `-provider codearts login/models`, `MSLXDFF_CODEARTS_*` |
123
+ | traework 供应商(TRAE SOLO 通道) | `providerConfigs.traework={baseUrl,keys,auths}`(`auths` 与 `keys` 一一对应,`mslxdff -provider traework login` 复刻 traework2api login.sh:随机 hex16 machine/device id→trae.cn 授权链接→粘贴回调→ExchangeToken→落盘 `auths/trae-<uid>.json`+state,自动签到+查积分;`baseUrl` 默认 `https://trae-api-cn.mchost.guru`),`POST /api/agent/v3/llm_utils_chat`(恒 `stream:true`,SOLO 头 `Cloud-IDE-JWT`+IDE 指纹,payload 改写 function/content/tool_choice/tools,模型空/auto→`glm-5.2`),SOLO SSE(output/done/error/token_usage)→OpenAI SSE 透传/聚合;1005 plan 长冷却12h、401 session_dead 禁用换号、429 60s;模型表 `POST /api/ide/v1/get_detail_param` 动态(10min 缓存)+静态 32 个回退;恒 local-only 不借出 key | `src/providers/traework/{index,constants,headers,payload,errors,sse,token,account-store,chat,models,checkin}.js`, `src/cli/commands/provider/traework-login.js` | `-provider traework login/models`, `MSLXDFF_TRAEWORK_*` |
124
+ | qoder 供应商(Qoder 免费池原生直连,ADR-0029) | `providerConfigs.qoder={baseUrl,keys,auths}`(一账号一 `dt-…` device-token blob,`auths/qoder-<uid>.json` 0600 为单一源,多号 keyring 轮转;**同请求粘号**:一次客户端请求内复用同一号,仅 401/403/429/5xx 冷却才换号,ADR-0036);自研 COSY 栈零桥依赖(自定义 base64 + RSA PKCS1v15/AES-CBC 会话 + md5 五段签名 + `{statusCodeValue,body}` 信封 SSE);`algo/api/v2/service/pro/sse/agent_chat_generation?Encode=1` 恒上游 `stream:true`,15 模型(auto/空→`qfmodel`);**选区签到**(`checkin.js`/`qoder-checkin.js`):每号 region 决定域名(cn=`openapi.qoder.com.cn` 走 `daily-check-in/status→claim`,409=今日已领;global=`openapi.qoder.sh` 无该端点(404)→ 回落 `/sash/api/v1/me/campaigns→/{id}/claim`),默认只领 `CLAIM_BENEFIT`(`--any` 才碰 VIEW_DETAILS 促销条目),额度读 `GET /api/v2/quota/usage`;**坏号冷却可观测**:流式契约把上游非 200 整形成「200 + 流内 error」,真实状态码经内部头 `x-mslxdff-qoder-upstream-status` 带回门面(fetch 异常折 502),401/403/429/5xx 才 `ring.onError` 并回显 `x-mslxdff-qoder-cooldown=<status>`(模型日志 `cooled=`),选号决定回显 `x-mslxdff-qoder-account=new\\|sticky\\|switch\\|forced`;恒 local-only 不借出 | `src/providers/qoder/{index,constants,sticky,checkin,chat,models,sse,payload,session,encode,fingerprint,oauth,account-store}.js`, `src/cli/commands/provider/{qoder-login,qoder-checkin}.js`, `src/runtime/qoder-checkin.js` | `-provider qoder login/checkin/models`, `MSLXDFF_QODER_*` |
125
+ | qwenwork 供应商(千问办公账号积分池,ADR-0037) | `providerConfigs.qwenwork={keys,auths}`(token blob + `auths/qwenwork-<uid>.json` 0600;vendor 原作者 `qwenwork2api` 协议,与 qoder 同 client_id 但不同租户:RSA 模数/cosyVersion 1.1.18/clienttype 6/scene qwork/纯 JSON 请求体全不同;无身份签名会被判 101 Signature invalid,故首轮自动补 userinfo 落盘);恒上游 `stream:true`(`qwork` 切片 3 模型:`flash`/price 0.1、`pro`/1、`qwen3.8-max-preview`/1.8;401/403 refresh 回写轮换 refreshToken 再重试;额度错(code 14018/中英措辞)长冷却 1h 换号,全号额度确认才 429 `quota_exhausted`);**登录默认 `allowAnyModels=false` + 只种 3 实测模型**(与 qoder 惯例故意不同:qwenwork 每日回血未实测,防 auto 烧分) | `src/providers/qwenwork/{index,constants,crypto,rsa,cosy,payload,sse,stream,upstream,http,account-store}.js`, `src/cli/commands/provider/qwenwork-login.js` | `-provider qwenwork login/models`, `MSLXDFF_QWENWORK_*` |
126
+ | zcode 供应商(智谱 ZCode 官方工作台免费额度,ADR-0038) | `providerConfigs.zcode={keys,allowedModels}`(一账号一 zcode JWT,多号 keyring 轮转;`auths/zcode-<uid>.json` 0600 为 deviceMid 单一源——per-account 持久 UUID 随账号文档走,规避 providerConfig 重建丢字段);出站 **Anthropic 协议** `POST /api/v1/zcode-plan/anthropic/v1/messages`,带 12 项 ZCode 客户端同形头(UA `ZCode/<ver>`、`X-Title: Z Code@electron`、`X-Device-Mid`、`x-request-id` 等),上游恒 `stream:true`(非流式本地聚合回 OpenAI JSON,thinking 块丢弃不入历史);业务码分级:401/1006→短冷 30s + 401 重登指引、1005→长冷 1h + 429 `quota_exhausted`、3002/3008/3009/3010→短冷 429、3007→不冷却 403 直透(`x-mslxdff-zcode-kind` 内部头供工厂判冷却);额度 `GET /api/v1/zcode-plan/billing/balance?app_version=`;登录=CLI 轮询(`oauth/cli/init`→浏览器授权→`oauth/cli/poll`,zai/bigmodel 双入口,免验证码);恒 local-only 不借出 | `src/providers/zcode/{index,chat,sse,headers,auth,oauth,const,models,quota,account-store}.js`, `src/cli/commands/provider/{zcode-login,zcode-quota}.js` | `-provider zcode login/quota/models`, `MSLXDFF_ZCODE_*` |
127
+ | 供应商模型白名单 | `providerConfigs.<id>.{allowedModels: string[], allowAnyModels: boolean}` 空=阻塞除非 `allowAnyModels:true`(默认 `false`,`opencode:true` 例外),非空仅名单内可用;接入时可带 `... <key> [model...]` 直接定白名单,`allowlist [list\|set\|add\|remove\|clear]` 增量管理,`allowAny on\|off` 控空名单放行/阻塞;拦截在 `dispatcher`(`403 + x-mslxdff-allowlist:1`),`/models` 亦按白名单过滤(防昂贵/奇怪模型) | `src/state.js`, `src/providers/dispatcher.js`, `bin/mslxdff.js` | `-provider <id> allowlist [list\|set\|add\|remove\|clear]`, `-provider <id> allowAny on\|off`, `-provider add <id> <baseUrl> <key> [model...]` |
128
+ | 上游测速 | `provider <id> bench` 只测已勾选 `allowlist` 模型的 `TTFB/总耗时/TPS`(串行 30s 超时),空则探活 `GET /v1/models→/models` 并提示先 `allowlist set`,`--json` 供脚本,防全量误扣费 | `src/bench/probe.js`, `src/bench/runner.js`, `src/bench/report.js`, `src/cli/commands/provider/bench.js`, `bin/mslxdff.js` | `-provider <id> bench [--json] [--prompt <text>] [--max-tokens N] [--timeout N]` |
129
+ | bench-via 直连 vs 经 peer 延迟对比 | ` -provider bench --via` / ` -provider <id> bench --via` 对比 `direct` vs 经每个在线 `peer` 到同一上游的 `TTFB`,串行省额度,默认跳过 `opencode`(需 `--include-opencode` 二次确认 `y/N`,非 TTY 自动跳过),探针 `max_tokens=5 prompt=hi`,`--json` 时进度走 `stderr`,结果不写 `state.json`,空组/全离线空状态引导,`--apply` 落盘 `via-routes.json` 供网关择路 | `src/bench/via.js`, `src/bench/via-probe.js`, `src/bench/report.js`, `src/bench/via-routes.js`, `src/cli/commands/provider/bench.js` | `-provider bench --via` / `-provider <id> bench --via [--include-opencode] [--json] [--samples N] [--timeout N] [--apply]` |
130
+ | via-routes 动态择路 | 后台探针自动保鲜(ADR-0015,`MSLXDFF_UPSTREAM_PROBE_MS` 默认 60s:每 tick 探 1 家 key/token 类供应商,direct GET models + 逐 peer `/v1/relay` 代发,EMA 合并落 `provider:<id>` 键)+ `bench --via --apply` 手动校准双来源落盘 `via-routes.json`(`{model: {best, direct, via, at}}`),网关对显式锁模型 `provider/model` 按 `best` 单路径直达(`via:host:port` 则单 peer 借 `x-mslxdff-share-keys` 透传,`direct` 则本机),不并发,失败秒切次优直连,`MSLXDFF_VIA_ROUTE_TTL_MS` 默认 5min 过期(0=不过期);供应商三态路由(ADR-0015):`workbuddy` local-only 恒不走组员、`opencode` quota-pool 永不走 via-route(429 后 peer 兜底)、其余 latency-compare 读表择路 | `src/upstream-probe/{probe,rotate,start,display}.js`, `src/providers/classify.js`, `src/bench/via-routes.js`, `src/routes/chat/via-route-handler.js`, `src/routes/chat/gateway.js` | `MSLXDFF_UPSTREAM_PROBE_MS`/`MSLXDFF_VIA_ROUTES_FILE`/`MSLXDFF_VIA_ROUTE_TTL_MS` |
131
+ | 时区 | 统一时间展示(logs/rotation/banned/token/free/status)所用时区,`state.json: timezone` 持久化(默认 `Asia/Shanghai`),`MSLXDFF_TZ`/`MSLXDFF_TIMEZONE`/`TZ` env 临时覆盖 | `src/state/schemas/timezone.js`, `src/time.js`, `src/cli/commands/timezone.js`, `src/logs.js`, `src/providers/workbuddy/rotation-log.js`, `src/routes/groups.js`, `src/state/schemas/token.js`, `src/cli/commands/system.js` | `-timezone [set <tz>\|clear\|status]`, `MSLXDFF_TZ` |
132
+ | 插件系统 | 可替换上游/.mjs hook,失败仅记日志 | `src/plugins.js` + `docs/plugins.md` | `MSLXDFF_PLUGINS_DIR` |
133
+ | WorkBuddy 同步 | `-setto workbuddy` 原子写 `~/.workbuddy/models.json`(`picks` 非空时摘除失效本地条目,非本地永不动) | `src/sync-workbuddy.js` | 仅认 127.0.0.1/v1 |
134
+ | opencode 同步 | `-setto opencode [--all]` 原子写 `~/.config/opencode/opencode.json` 的 `provider.mslxdff`(`http://127.0.0.1:<port>/v1`,裸名直写如 `deepseek-v4-flash-free`,`/`→`-` 如 `bai/deepseek`→`bai-deepseek` 到 8989 经 `model-aliases.json` 自动还原;`--all` 批量同步全部 `modelPicks`;`picks` 非空时摘除失效模型)+ 能力注入(models.dev 目录;`workbuddy/` 模型走上游原生字段兜底,旧格式/缺 variants 条目自动升级)+ 写 `variants` 档位供 opencode `ctrl+t` 切换(effort 型按档位写 `reasoningEffort`,toggle 型不写;TUI 改配置后需重启) | `src/sync-opencode.js`, `src/model-capabilities/enrich.js`, `src/routes/chat/gateway.js` | `OPENCODE_CONFIG`, `model-aliases.json` |
135
+ | Codex 同步 + Responses 端点 | `POST /v1/responses` 复用 ChatPipeline(翻译层 `src/responses/translate.js`,stateless);`-setto chatgpt` 写 `~/.codex/config.toml`(`model_providers.mslxdff`,`wire_api="responses"`,鉴权绝对路径 `mslxdff -showtoken`);Codex 调用者 `GET /v1/models` 追加顶层 `models: []`(必须空) | `src/routes/responses-route.js`, `src/responses/translate.js`, `src/sync-codex.js`, `src/routes/models-route.js` | `MSLXDFF_RESPONSES_DEBUG=1` 排障日志 |
136
+ | 模型能力元数据 | `GET /v1/models/capabilities[?provider=&id=]`(ADR-0016):`opencode`(默认)走 models.dev 目录(24h 缓存+staleness 降级);`workbuddy` 走上游原生字段(`maxInputTokens/supportsImages/supportsReasoning/supportsToolCall`,first-party 全量 29 个含 blocked,独立 provider 单例 10min 缓存),统一映射 caps 形状;`-provider <id> models` 表格带能力列(上下文/📷🧠🔧)。ADR-0022 起 `/v1/models` 每条默认内联 `capabilities`(`src/model-capabilities/merge.js`:裸 id→剥 `-free`→二级厂商前缀→workbuddy 原生兜底;`readyWarm` 零阻塞;`?raw=1`/codex 不富化) | `src/model-capabilities/`, `src/routes/models-route.js`, `src/cli/commands/provider/models.js` | `?provider=workbuddy&id=glm-5.3-flash`、`?raw=1` |
137
+ | 上游引擎(wire 层,ADR-0017) | 缺省 `sdk`:opencode 流式 chat 走 `@ai-sdk/openai-compatible`,responses 类(判定=models.dev 模型级 `provider.npm=@ai-sdk/openai`,启动注入 npm 索引+每小时重查,未就绪回退 `muse-spark` 前缀)走 `@ai-sdk/openai` 的 responses 适配器(共用库 `src/upstream-engine/sdk/`:convert/SSE 序列化/attempt/chat/responses/dispatch;`x-mslxdff-upstream-engine: sdk` 标记;复用 legacy keep-alive 连接池;非流式委派 legacy;SDK 不可用自动回退并告警一次);**加密思考跨 caller 400 自动降级**(responses 类遇 `encrypted_content was not issued to this caller`——客户端持有的加密块绑定签发出口,跨出口/经组员切换后回传被上游拒——则剥掉加密态重试一次(留摘要文本、无摘要交明文 reasoning_content 兜底,`toModelPrompt({dropEncrypted:true})`),正常路径零变化(首次即成功不触发),重试生效打 `_t.encRetry` 供 events 观测;仍失败原样 400 交上层转组员接力或换模型);通用 OpenAI 兼容族(`generic.js`/`openrouter.js`)与 `cline` 的流式 chat 同源(`dispatch.js` 分派,非流式/异形 chatPath 回退原生;供应商级 `MSLXDFF_<ID>_SDK` 未设置则继承全局);显式 `legacy`/关闭词回退原实现;身份头单一来源 `createOpencodeHeaderBuilder` | `src/upstream-engine/`, `src/runtime/providers-setup.js`, `src/upstream.js` | `MSLXDFF_UPSTREAM_ENGINE` |
138
+ | V2EX 白嫖雷达 | 仅 V2EX 单源:`GET /api/topics/latest.json + hot.json`,关键词 `白嫖\|限免\|免费额度\|注册送\|羊毛` 过滤,`EXCLUDE` 排除代充/倍率 | `src/free-watcher.js`, `bin/mslxdff.js` | `-free` / `-free-check` / `-free-watch`(5min 轮询) |
139
+ | Daemon | 后台守护,pid/日志/事件流,auto-update(`writePid` 带 version,`isPidAlive` 探活;**升级后重启委托 CLI `-restart`**:daemon 内直调 `stopDaemon()` 是自杀式 SIGTERM,会打断紧随的 `startDaemon()/waitForHealth()`——2026-09-15 实测 0.1.124→0.1.125 升级后 daemon 反复抢占/静默消失 7 分钟——现改 spawn 剔除 `MSLXDFF_DAEMON` 的子进程执行 `-restart`(成熟路径:杀旧+起新+health 二次确认),失败则保留旧进程下轮检查重试);**生命周期留痕**(`lifecycle-log.js`:启动行含 pid/ppid/version/node/cwd、30m 心跳含 rss(死亡时刻=最后心跳、内存曲线看 OOM)、顶层 uncaughtException/unhandledRejection 记录不退出、exit 码;`stopDaemon()` 由**杀者**写 `stopDaemon called by pid=X`——Windows 的 SIGTERM 是 TerminateProcess 强杀,被杀进程 JS 层收不到事件,只能由杀者留痕;没有该行=外部所杀) | `src/daemon.js`, `src/runtime/{auto-update,lifecycle-log}.js`, `src/server.js`, `bin/mslxdff.js` | `-d/-status/-debug/-log/-update`、`MSLXDFF_HEARTBEAT_MS` |
140
+ | 状态聚合 `-status` | 全量聚合体检:daemon/health/port/config、upstream providers(opencode/workbuddy/通用,key/baseUrl/allowlist/share)、models(free/preferred/picks + 体检表 avg首字/tps/啰嗦/p95)、autostart/plugins、groups/peers、recent calls(ts/model/status/dur)、last error;空状态/测试桩有明确提示(游戏化反馈) | `bin/mslxdff.js:printStatus`, `src/state.js:loadProvider*`, `src/chat/stats.js`, `src/cli/status.js`, `src/autostart.js`, `src/plugins.js` | `-status` / `-s`(只读,health 1.2s 超时) |
141
+ | 用量报表 `-stats` | 近 N 小时(默认 24)每模型 token 消耗 + 平均首字/总耗时/速度;逐请求 JSONL 按日落盘 + 保留期删旧(区别于 logs 的 1MB 环形截断),聚合为纯函数;默认渲染 `Token 用量`/`响应性能` 两张自适应边框表,展示当前聚合层全部字段(长模型 id 不截断),普通表格 token 用 k/M、精确值走 `--json`;查询超过保留期给出不完整警告。速度按窗口加权,只计经 8989 的成功请求(`-chat` 直连不计),不展开逐请求 via/interrupted/单次 tps | `src/usage/record.js`, `src/usage/report.js`, `src/cli/commands/stats.js` | `-stats [--hours N] [--json] [--model <id>]`、`MSLXDFF_USAGE_LOG=0`、`MSLXDFF_USAGE_KEEP_DAYS` |
142
+ | 对话探活 curl | `-chat` 新增 `curl` 工具:简写 `upstream`/`local/health`/`local/models` 自动补全,上游自动补头、本机 /v1/* 自动带 token,返回状态/耗时/头/前 6KB body | `src/chat/tools.js`, `src/chat/repl.js` | `curl url [method headers body timeoutMs]` |
143
+ | 供应/模型状态与 Cline 额度周期统计 | 三类只读观测事件落 `events.log`(`-log` 已可读):`cline-account-state`(Cline 账号池 `selected`/`switch`/`cooldown`/`pool-exhausted`/`dead`/`refresh-failed`/`force-retry`,带 `model` + `limitedModel`(从 429 报文 `...reached on model <X>` 提取的上游权威模型名)+ `accountId` + `reason`(daily_limit/empty_response/invalid_grant) + `cooldownMs` + `pool{total,cooling,dead,ready}`)、`provider-model-state`(dispatcher 每次请求后记 `provider`/`model`/`rawModel`/`status`/`state`(ok/limited/blocked/upstream-error)/`durationMs`)、`provider-state`(启动时逐供应商快照 `keys`/`accounts`/`baseUrl`)、`cline-model-usage`(按账号哈希×模型记录当前额度周期的 `outputTokens`/`cycleOutputTokens`;429 封存本周期,恢复后开新周期,JSONL 落 `cline-usage.jsonl`)。**免费额度按「账号 × 模型」计**,故日志一律点名模型——否则 `acct-ready=0/4` 会被误读成整池报废(实测同一批号 muse-spark 全 429 时 deepseek 仍 200)。**只记状态、计数与哈希,绝不落 token/refreshToken/邮箱/prompt/响应正文**;统计旁路读取成功 Response,不改请求、重试、切号与冷却语义 | `src/providers/cline/auth.js`, `src/providers/cline/chat.js`, `src/providers/cline/usage.js`, `src/providers/dispatcher.js`, `src/runtime/providers-setup.js`, `src/cli/format.js` | `cline-usage.jsonl`、`-log` |
144
+ | **白名单自动同步** | 上游 `recommended-models` 每次成功读取后,把 free+clinePass 一并并入 `allowedModels`(只增不减、幂等零写盘;兜底不写盘、异常吞掉留日志)。实测:新上架免费模型 (gemini-3.8-flash/space-bunny-alpha/mimo-v2.6-flash) 不再被误拦 → ✓ | `src/providers/cline/allowlist-sync.js`, `src/providers/cline/models.js` | `MSLXDFF_CLINE_AUTOSYNC=0`(关) |
145
+ | **Cline 用量双口径统计 (2026-09-24)** | free 模型按「额度周期」统计(限额恢复→再次限额之间累计,429 封存周期),pass 模型按「最近 24h 滚动窗口」统计(last24hTokens)。数据源 `cline-usage.jsonl`,查询纯函数聚合无 IO;渲染入口 `-provider cline quota`(按账号哈希分组,`--json/--account/--model`,空账本引导) | `src/providers/cline/usage.js`, `src/cli/commands/provider/cline-quota.js` | `cline-usage.jsonl`、`isFreeModel`、`aggregateUsage`、`loadAndAggregate` |
146
+ ## 6. 契约与配置
147
+
148
+ ### CLI(bin/mslxdff.js)
149
+
150
+ > 完整参数手册见 **[`docs/cli_help.md`](./cli_help.md)**(与本表同源,改 CLI 时两处必同步)。
151
+
152
+ | 命令 | 作用 |
153
+ |---|---|
154
+ | `mslxdff` / `-d` | 启动为后台 daemon |
155
+ | `-status` | 全量聚合体检:daemon/health/port/config、upstream providers、models+metrics(体检表)、autostart/plugins、groups/peers、recent calls(ts/model/status/dur)、last error(v0.1.60 补齐上游;token/速度改看 `-stats`) |
156
+ | `-stats [--hours N] [--json] [--model <id>]` | 模型用量报表:近 N 小时(默认 24,上限 168)以 `Token 用量`/`响应性能` 两张自适应边框表展示每模型输入/输出/思考/合计、首字/总耗时/加权速度与总计;长模型 id 不截断。数据源 `<logDir>/usage/YYYY-MM-DD.jsonl`(逐请求 JSONL,默认保留 2 天,`MSLXDFF_USAGE_LOG=0` 关;超保留期警告);普通表格 token 用 k/M、`--json` 精确输出;只计成功请求,不含失败/`-chat` 直连及逐请求 via/interrupted/单次 tps 明细 |
157
+ | `-log [N]` / `-debug` | 最近事件 / 实时事件流 |
158
+ | `-stop` / `-uninstall` | 停止 / 停止并删状态日志 |
159
+ | `-restart` | 重启 daemon |
160
+ | `-port N` | 持久化端口(写 state,重启 daemon) |
161
+ | `-model list/set/status/refresh/pick/unpick/…` | 模型查看/默认/健康/勾选集管理 |
162
+ | `-models` | 交互式模型多选 |
163
+ | `-provider add <id> <baseUrl> <key>` | 一键添加通用 OpenAI 兼容供应商(`providerConfigs`,`myapi/gpt-4` 前缀路由;默认 `allowAny OFF` 空名单=`403` 禁用,需 `allowlist set` 或 `allowAny on` 否则无法使用);`workbuddy` 同法 `mslxdff -provider add workbuddy https://copilot.tencent.com <key> [allow...]`(自动解析 `uid` 建 `auths` 一一对应并写 `auths/workbuddy-<uid>.json`) |
164
+ | `-provider <id> [key...|add|remove|list|clear|set-url]` | 配置供应商 key/地址(多 key 轮转,remove 支持序号;list 1 起编;set-url 改 baseUrl)。key 随转发自动借出(ADR-0019),无 share 子命令 |
165
+ | `-provider <id> allowlist [list\|set\|add\|remove\|clear]` | 管理供应商模型白名单(空=阻塞除非 `allowAny on`,非空仅名单内可用,`403` 拦截,`/models` 过滤;`set <m1> <m2>` 覆盖、`add <m>` 追加、`remove <m>` 移除、`clear` 清空、`list` 查看) |
166
+ | `-provider <id> allowAny on\|off` | 空 allowlist 时放行或阻塞(默认 `OFF`,`opencode` 例外 `ON`) |
167
+ | `-provider <id> bench [--json]` | 评估已勾选 allowlist 模型的响应速度/TTFB/吐字速度(串行 30s 超时,仅测勾选;空则探活 `GET /v1/models→/models` 并提示先 `allowlist set`) |
168
+ | `-provider <id> bench --via [--include-opencode] [--json] [--apply]` | 家宽选路:`direct` vs 经每个在线 `peer` 的 `TTFB` 对比,默认跳过 `opencode`(省组员额度,需 `--include-opencode` 且 TTY `y/N` 确认)`--json` 时 `stdout` 纯 JSON(`meta/results/advice`),进度走 `stderr`,探针 `max_tokens=5` 串行不并发,空组直接引导 ` -group list`,`--apply` 落盘 `via-routes.json` 供显式模型单路径择路(不并发) |
169
+ | `-provider cline login` | Cline WorkOS 设备授权流:浏览器打开链接 → 授权 → 自动拿 `refreshToken` 落盘 `providerConfigs.cline.keys`。此后 `cline` 供应商走 `refreshToken → POST /api/v1/auth/refresh → workos:token` + Cline 指纹头(UA `Cline/3.0.47`、`X-CLIENT-TYPE: cline-sdk` 等),绕 `403 only available via Cline product surfaces`;deepseek 家族(含 `cline-free/deepseek-*`)非流式请求内部强制 `stream:true` 并聚合返回(避免 `500 empty response content`),对外仍按请求方 `stream` 标志。多账号重复 login 追加(同邮箱替换不追加:`auths` 存 `uid=email` 映射,老 keys 无映射时用 refresh 反查兜底;`list` 显示邮箱;轮换回写同步 `auths`),默认 round-robin + 429/空响应切号 |
170
+ | `-provider cline free [--json]` | **只读**直查上游免费目录(`GET /api/v1/ai/cline/recommended-models` 的 `free` 数组,5 个)并列出与当前 `allowlist` 的差异,不写 state(`src/providers/cline/free-catalog.js` + `src/cli/commands/provider/cline-free.js`) |
171
+ | `-provider cline free sync [--yes] [--json] [--keep-extra]` | 把免费目录同步为 `cline` 的 `allowlist`(写裸 id 如 `z-ai/glm-5.3-flash`;对外 id 仍是 `cline/z-ai/glm-5.3-flash`):**默认 dry-run 预览**,`--yes` 才落盘,`--keep-extra` 只增不删。注意与 daemon auto-sync(ADR-0033)两处差异:① 本命令**全量替换**(会挤掉 auto-sync 并入的 `cline-pass/*`);② **上游不可达时拒绝写盘**(不把内置兜底落成白名单,同一"兜底非上游真相"口径) |
172
+ | `-provider cline quota [--json] [--account <hash>] [--model <substr>]` | **只读**聚合 `cline-usage.jsonl` 的账号×模型双口径统计并分组打印(free 本周期/已完成周期/累计,pass 近 24h/累计;`--json` 供脚本;空账本引导),不写 state(`src/providers/cline/usage.js` 的 `loadAndAggregate` + `src/cli/commands/provider/cline-quota.js`) |
173
+ | `-provider codearts login` | 华为云 CodeArts Agent(盘古助手)PKCE 授权:浏览器登录 → 自动拿 `refreshToken/codeVerifier/dpopJwk` 组凭证 blob 落盘 `providerConfigs.codearts.keys`(一账号一 blob,默认 `allowAnyModels=true`,`--no-allow-any` 关)。此后 `codearts/<modelId>` 前缀路由(STS 临时凭证自动单飞刷新、refresh_token 轮换原位写回、多账号重复 login 追加 = keyring 轮转);恒 local-only 不借出 key(ADR-0027) |
174
+ | `-provider traework login` | TRAE SOLO 通道授权:随机 hex16 machine/device id 构造 trae.cn 授权链接 → 浏览器登录 → 粘贴回调链接 → ExchangeToken → 落盘 `auths/trae-<uid>.json`+state,自动签到+查积分。此后 `traework/<modelId>` 前缀路由(恒 `stream:true` SOLO SSE 透传/聚合,1005 plan 长冷却12h、401 换号);恒 local-only 不借出 key |
175
+ | `-provider qoder login [--region cn\|global]` | Qoder 设备授权(PKCE + poll):打印 `qoder.com/device/selectAccounts` 兑换链接 → 浏览器登录 → 落盘 `auths/qoder-<uid>.json`(0600)+ state 双写,默认 `allowAnyModels=true`。此后 `qoder/<modelId>` 前缀路由(15 模型,恒上游 `stream:true`,多号 keyring 轮转);恒 local-only 不借出 key(ADR-0029)。`--region cn` 走国内站 `qoder.com.cn`(端点为 `*.qoder.com.cn` 镜像) |
176
+ | `-provider qoder checkin [--json] [--region cn\|global] [--any] [--dry]` | 每日签到领积分(按每号 region 选域名:cn 走 `daily-check-in`,global 走 campaigns);`--any` 连 VIEW_DETAILS 促销条目一起领,`--dry` 只查不领,`--json` 输出 `{ok,total,claimed,results[]}`(每号带 region/status/amount/streak/quota);daemon 每日 09:00 自动(`MSLXDFF_QODER_CHECKIN=0` 关) |
177
+ | `-provider zcode login [--bigmodel]` | ZCode 授权(智谱免费额度,Start/Coding Plan):打印 chat.z.ai 授权链接 → 浏览器登录 → **CLI 轮询**(`oauth/cli/init`→`oauth/cli/poll`,无回调服务器、全程免验证码)拿 zcode JWT,落盘 `auths/zcode-<uid>.json`(0600)+ state keys,并把内置目录(`GLM-5.3`/`GLM-5.3-Flash`/`GLM-5.2`/`GLM-5-Turbo`)只增不减并入 allowlist。此后 `zcode/<modelId>` 前缀路由(Anthropic 网关,恒上游 `stream:true`);`--bigmodel` 切 bigmodel.cn 入口;恒 local-only 不借出 key |
178
+ | `-provider zcode quota [--json]` | 查 ZCode 套餐/余量(`GET /api/v1/zcode-plan/billing/balance?app_version=` 按 plans/balances 分组表格:套餐名/状态/有效期 + 各模型 entitlement 余量);空套餐给领取指引,401 给重登指引;`--json` 供脚本 |
179
+ | `-provider codearts models [--json]` | 列 CodeArts 免费福利模型(三路发现:`builtin` 归一 + 代理型 + 福利网关目录;benefit 模型自动 `claim`(幂等),对外带 `tags:["free:benefit"]`;id 大小写归一,如 `GLM-5.2`→`glm-5.2`) |
180
+ | `-timezone [set <tz>\|clear\|status]` | 时区配置(默认 `Asia/Shanghai`,`MSLXDFF_TZ` env 覆盖,`state.json: timezone` 持久化) |
181
+ | `-workbuddy checkin` | WorkBuddy 每日签到 100 credits(多号并行 3,双域幂等,`code 10001 已签到` 视为成功,`--json` 聚合 `total/dailyPacks/nextExpire`,`workbuddy-checkin.js` 代理,`node workbuddy-token-auto.js` 已自动触发) |
182
+ | `-workbuddy growth [--json] [--codes a,b] [--account <uid>]` | WorkBuddy 成长任务全自动:拉列表 → 参与(accept) → 触发(带 `extra_vars.growthEvent` 的免费模型请求) → 领奖(claim),串行(任务间 1.2s、账号间 1s),已 claimed 幂等跳过;可自动 = `chat_5`/`automation_1`/`skill_1`/`Model_chat_GLM5.2`,需客户端 = `expert_5` 等标 MANUAL 不发包(`growth-plans.js` 未登记默认 MANUAL);`--codes` 只做指定任务(调试) |
183
+ | `-workbuddy travel [--json]` | WorkBuddy 猫猫旅行:无猫自动同意协议 + 领养(+300,门槛未达自动补一次对话解锁);到站领奖 / 空闲派出(location 4)/ 旅行中跳过;`--json` 输出分步 steps 与 `credits` |
184
+ | `-workbuddy balance [--json]` | WorkBuddy 多号余额总览(`total/dailyPacks/nextExpire/fetchedAt`,TTL 5min,`workbuddy-balance.js`) |
185
+ | `-workbuddy list` | 列出已接入 `workbuddy` 账号(`uid/domain/enterpriseId`,与 `auths/` 一致性) |
186
+ | `-workbuddy remove <uid> [--keep-file]` | 按 `uid`(全等或前缀 6 位)摘除账号(同索引删 `keys/auths`,删 `auths/workbuddy-<uid>.json`,清 `balanceCache`) |
187
+ | `-providers list` | 列出所有已部署上游供应商(opencode/openrouter/通用/workbuddy)及启用状态(含 allowlist 摘要) |
188
+ | `-setto workbuddy [modelId]` | 同步默认模型到 WorkBuddy(`~/.workbuddy/models.json`,`127.0.0.1/v1`;`picks` 非空时摘除失效本地条目) |
189
+ | `-setto opencode [modelId\|--all]` | 同步到 opencode(`~/.config/opencode/opencode.json` 的 `provider.mslxdff`,`http://127.0.0.1:<port>/v1`,裸名如 `deepseek-v4-flash-free`,`/`→`-` 如 `bai/deepseek`→`bai-deepseek` 自动还原;`--all` 同步全部 `modelPicks`;`picks` 非空时摘除失效模型) |
190
+ | `-setto chatgpt [modelId]` | 同步到 Codex 三端共用 `~/.codex/config.toml`(`model_providers.mslxdff` → `127.0.0.1/v1/responses`,`wire_api="responses"`,鉴权绝对路径 `mslxdff -showtoken` 不落盘;换模型重跑 setto 或 `codex exec -m`) |
191
+ | `-free` / `-free-check` / `-free-watch` | V2EX 白嫖雷达(`latest.json + hot.json`,`白嫖\|限免\|免费额度\|注册送\|羊毛` 过滤,5min 轮询 `watch`) |
192
+ | `-creategroup` / `-addtogroup` / `-group …` / `-leavegroup` / `-delgroup` | 群组生命周期(组员用 `-group leave`,组长用 `-delgroup`,ADR-0005/0006;`-addtogroup` 零/单参数 → 手机宽带接入向导,默认 broadband) |
193
+ | -use-group [on\\|off] | opencode 失败时是否走组员网络(默认 on;off 则所有供应商仅本机;key 供应商默认恒直连) |
194
+ | `-showtoken` / `-refresh-token` | 读 / 轮换 auth token |
195
+ | `-chat ["prompt"]` | 对话式终端(**只支持 opencode 上游**:mimo-v2.5-free 优先/big-pickle 兜底,第三级 gateway auto 带 `x-mslxdff-auto-provider: opencode` 限定候选也在 opencode 免费池;模糊匹配由模型完成,历史持久化超长压缩,仅拦 `-uninstall`,`curl` 探活上游/本机,独立进程 daemon 重启不影响);`/model <id>` 锁定会话模型(仅 opencode free 池 8 个、拒供应商前缀,锁定后**严格单模型不降级**,失败即报错;`/model auto` 解除回三级链,会话态不持久化) |
196
+ | `-update` | 更新到最新已发布版本 |
197
+
198
+ ### Env
199
+
200
+ | 变量 | 默认 | 说明 |
201
+ |---|---|---|
202
+ | `MSLXDFF_PORT` | 8989 | 监听端口(裸 `PORT` 忽略,见 AGENTS.md) |
203
+ | `MSLXDFF_STATE_FILE` | `~/.config/mslxdff/state.json` | token/port/timezone/modelPicks/providerKeys/providerConfigs 等持久化 |
204
+ | `MSLXDFF_TZ` / `MSLXDFF_TIMEZONE` / `TZ` | `Asia/Shanghai` | 时区(`state.json: timezone` 持久化,env 临时覆盖,`Intl` 校验) |
205
+ | `MSLXDFF_DAEMON_DIR` | 随 state 派生 | daemon pid/log/models 目录 |
206
+ | `MSLXDFF_OPENROUTER_KEY` / `_COOLDOWN_MS` / `_BASE_URL` / `_TIMEOUT_MS` | — / 30000 / 官方 / 30000 | openrouter 供应商 key 与行为 |
207
+ | `MSLXDFF_WORKBUDDY_KEY` / `_BASE_URL` / `_COOLDOWN_MS` / `_TIMEOUT_MS` / `_SHARE_KEYS` / `_SDK` | — / `https://copilot.tencent.com` / 30000 / 30000 / off / 继承 `MSLXDFF_UPSTREAM_ENGINE`(缺省 sdk) | WorkBuddy 供应商 key/地址/冷却/超时/共享(`_SHARE_KEYS=1` 显式开启外借)/SDK 通道(缺省走 `@ai-sdk/openai-compatible`,需 Node>=18;`_SDK` 设 `legacy`/关闭词回退原生 transport,未设置则继承全局;不可用自动回退原生并告警一次) |
208
+ | `MSLXDFF_<ID>_BASE_URL` / `MSLXDFF_<ID>_KEY` | — | 通用供应商 env 覆盖(`providerConfigs.<id>.baseUrl/keys`) |
209
+ | `WORKBUDDY_AUTH_DIR` | `<state 目录>/auths`(默认 `~/.config/mslxdff/auths`) | WorkBuddy token 落盘目录(`workbuddy-*.json`,`0600`)。跟随 state 文件走,不再用 cwd 兜底;旧 `./auths` 仅只读兜底(ADR-0025) |
210
+ | `MSLXDFF_WORKBUDDY_GROWTH` / `_GROWTH_HOUR` / `_GROWTH_MODEL` | 开 / 9 / `hy3` | 成长任务 + 猫猫旅行每日调度(默认每日 `HH:30`,与 09:00 签到错峰;`=0` 关)、触发事件用的免费模型(`_GROWTH_MODEL` 可换) |
211
+ | `UPSTREAM_BASE_URL` | `https://opencode.ai` | 默认供应商上游 |
212
+ | `UPSTREAM_AUTH_TOKEN` | `public` | 上游鉴权值 |
213
+ | `MSLXDFF_UPSTREAM_ENGINE` | `sdk` | 上游 wire 层引擎(ADR-0017):缺省即 AI SDK(opencode 流式 chat 走 `@ai-sdk/openai-compatible`,responses 判定=models.dev provider.npm(未就绪回退 `muse-spark` 前缀)的模型走 `@ai-sdk/openai` 的 responses 适配器;`x-mslxdff-upstream-engine: sdk` 标记,复用 legacy keep-alive 连接池;非流式自动委派 legacy;SDK 不可用回退 legacy);通用 OpenAI 兼容族与 cline 的流式 chat 同源(供应商级 `MSLXDFF_<ID>_SDK` 显式设置即局部生效,未设置继承本变量);显式 `legacy`/关闭词=原实现 |
214
+ | `MODELS_REFRESH_MS` | 7200000 | 模型后台刷新间隔 |
215
+ | `MSLXDFF_MODEL_COOLDOWN_MS` / `MSLXDFF_PEER_COOLDOWN_MS` | 60000 / 30000 | 模型 / 组员冷却 |
216
+ | `MSLXDFF_PEER_LIMIT_COOLDOWN_MS` | `300000` (5m) | 组员连续 429 后的长冷却(第 2 次起算,期间不再试它;普通失败仍走 `MSLXDFF_PEER_COOLDOWN_MS`) |
217
+ | `MSLXDFF_PEER_CONNECT_TIMEOUT_MS` | `3000` | 组员转发连接级超时(DNS+TCP/TLS 握手);黑洞节点快速失败(undici connect.timeout) |
218
+ | `MSLXDFF_STREAM_TIMEOUT_MS` | `25000` | 非末位候选流式首块闸门(0=关;到点真掐上游换候选) |
219
+ | `MSLXDFF_LAST_CANDIDATE_TIMEOUT_MS` | `120000` | 末位/唯一候选与借道(via-route)首块耐心档(0=不限) |
220
+ | `MSLXDFF_SDK_HEADERS_TIMEOUT_MS` | `120000` (2m) | SDK 通道 `doStream` headers 超时(防上游连接半死导致请求永不返回;`0`=关;错误文案避开 "timed out" 以免 cline 客户端按文案重试放大挂死) |
221
+ | `MSLXDFF_EMPTY_TURN_RETRIES` / `MSLXDFF_EMPTY_TURN_RETRY_DELAY_MS` | `2` / `1000` | 空转 200 同模型暂停重试(仅 `EMPTY_MODEL_RESPONSE`;`0`=关;`empty-turn-retry` 事件可观测) |
222
+ | `MSLXDFF_FREE_ANON*` | 见上 | 免费匿名兜底开关/参数 |
223
+ | `MSLXDFF_HEDGE_DELAY_MS` | 1000 | 对冲等待(0/off 关) |
224
+ | `MSLXDFF_VIA_ROUTES_FILE` | `~/.config/mslxdff/via-routes.json` | via-routes 落盘路径(随 `MSLXDFF_STATE_FILE` 派生) |
225
+ | `MSLXDFF_VIA_ROUTE_TTL_MS` | `300000` (5m) | via-routes 条目 TTL(探针自动保鲜下 5min 防陈旧择路;0=不过期) |
226
+ | `MSLXDFF_UPSTREAM_PROBE_MS` | `60000` (1m,0=关) | 后台上游探针间隔(ADR-0015):每 tick 探 1 家 key/token 类供应商(direct + 逐 peer relay,EMA 落盘),`workbuddy`/`opencode` 不参与 |
227
+ | `MSLXDFF_MODELS_DEV_URL` | `https://models.opencode.ai/api.json` | 模型能力目录源(ADR-0016,opencode 官方同源;备选 `https://models.dev/api.json`) |
228
+ | `MSLXDFF_MODELS_DEV_TTL_MS` | `86400000` (24h) | 能力目录缓存 TTL(过期重拉;`0`=每请求拉) |
229
+ | `MSLXDFF_MODELS_DEV_CACHE` | `~/.config/mslxdff/models-dev.json` | 能力目录磁盘缓存路径(fetch 失败回退旧缓存) |
230
+ | `MSLXDFF_BENCH_DELAY_MS` | 120 | bench --via 串行间隔 ms |
231
+ | `MSLXDFF_BROADBAND_STREAM` | 1 | 宽带中继长连总开关(0 则回退 1s poll) |
232
+ | `MSLXDFF_BROADBAND_PING_MS` | 25000 | 宽带 SSE ping 间隔 ms |
233
+ | `MSLXDFF_BROADBAND_STALE_MS` | 90000 | 宽带成员失联阈值 ms(超则 forward 判 stale) |
234
+ | MSLXDFF_USE_GROUP | 1 (on) | 组员中继总开关(0/off 关闭后所有供应商仅本机;on 时也仅 opencode 走组员,key 供应商默认直连) |
235
+ | `MSLXDFF_PREHEAT` | 1 | 上游预热(0 关;仅 opencode,其他供应商不预热) |
236
+ | `MSLXDFF_OPENCODE_UA` | `opencode/1.18.31` | opencode 上游身份 UA(zen 免费层门禁要求 `opencode/<semver>`;2026-09-18 起还要求 ≥ `1.18.0`,低版本 `426 UpgradeRequired`;ADR-0018/0020) |
237
+ | `MSLXDFF_FREE_LANE` | `1` | 免费层 agent 形状注入(ADR-0020):强制流式 + 补核心五工具(bash/edit/glob/grep/read);`0`/关闭词=完全不动(上游撤门禁时的逃生阀) |
238
+ | `MSLXDFF_FREE_LANE_DEBUG` | — | `1` 时 `daemon.log` 打 `[free-lane]` 发送/响应摘要(模型/URL/stream/tools 数/UA/状态),排障 403/426 用 |
239
+ | `MSLXDFF_PLUGINS_DIR` | 见 plugins.md | 插件目录覆盖 |
240
+ | `MSLXDFF_CODEARTS_TIMEOUT_MS` / `_COOLDOWN_MS` | 30000 / 30000 | codearts 上游请求超时 / key 冷却(多账号轮转间隔) |
241
+ | `MSLXDFF_CODEARTS_REFRESH_SKEW_MS` | 1800000 | STS 临时凭证提前刷新窗口(默认提前 30min) |
242
+ | `MSLXDFF_CODEARTS_AUTO_CLAIM` | 1 | 福利模型自动领取(`benefit claim`,幂等;`0` 关) |
243
+ | `MSLXDFF_CLINE_AUTOSYNC` | 1 | Cline 白名单自动同步(上游列表=真相):每次成功读取 `recommended-models` 后把 free+clinePass 并入 `allowedModels`(只增不减、幂等零写盘;兜底不写盘、异常吞掉留日志);禁闭用 `MSLXDFF_CLINE_AUTOSYNC=0`(退回纯静态白名单) |
244
+ | `MSLXDFF_CODEARTS_BASE_URL` | 官方 snap-access | codearts 上游地址覆盖(state `providerConfigs.codearts.baseUrl` 优先) |
245
+ | `MSLXDFF_QODER_COOLDOWN_MS` / `_TIMEOUT_MS` / `_STICKY_MS` | 30000 / 120000 / 600000 | qoder 供应商账号冷却 / 上游连接超时 / 同请求粘号 TTL(同请求内不换号;`0`=关,退回每次调用 round-robin) |
246
+ | `MSLXDFF_QODER_CHECKIN` / `_CHECKIN_HOUR` | 开 / 9 | qoder 每日自动签到(按每号 region 选域名;`=0` 关,`_HOUR` 改点) |
247
+ | `MSLXDFF_ZCODE_APP_VERSION` / `_COOLDOWN_MS` / `_QUOTA_COOLDOWN_MS` / `_TIMEOUT_MS` | 3.11.2 / 30000 / 3600000 / 120000 | zcode 客户端版本(headers 与 balance 查询参数)/ 短冷却(auth/限流/服务端/网络)/ 1005 额度长冷却 / 上游超时 |
248
+ | `MSLXDFF_ZCODE_AUTH_DIR` | `<stateDir>/auths` | zcode 账号文档目录覆盖(默认跟随 state 文件所在目录,0600;读取时兼容 `cwd/auths` 兜底) |
249
+ | `MSLXDFF_<ID>_RESPONSES_PATH` | `/responses` | 通用供应商 responses 端点路径覆盖(ADR-0032):responses 类模型(`muse-spark*` 等,判定与 `/v1/models` 的 `capabilities.upstreamApi` 同源)自动改打该路径(缺省 `<baseUrl>/responses`),不再走 `chatPath`;异形上游可覆盖 |
250
+
251
+ ### 运行契约(对外不可破坏)
252
+
253
+ 1. `POST /v1/chat/completions` 转发 body 上游不改(仅中间件注入),模型 id 透传。
254
+ 2. 恒带 `x-opencode-client: desktop` + `Authorization: Bearer`(上游值,默认 public) + `User-Agent: opencode/<semver>` + opencode 形状 `x-opencode-session`/`x-opencode-request`(26 位:12 hex 时间戳+14 base62,ADR-0018)+ 流式 `Accept: text/event-stream`。
255
+ 3. DeepSeek 思考模型:assistant 消息必须带 `reasoning_content` 占位再转发(ADR-0001)。
256
+ 4. 流式按 SSE chunk 转发;非流式非 JSON 上游响应透传。
257
+ 5. `/models` 只返回免费模型 filter(whitelist + `-free`,`big-pickle` 特例,ADR-0002),10-min 缓存;**`modelPicks` 非空时同时按勾选集裁剪对外目录**(空勾选=不裁剪保留全量,`?all=1` 绕过,ADR-0030)。
258
+ 6. `/v1/*` 需 `Authorization: Bearer <token>`(恒时比较,401 否则);`/health` 公开。
259
+ 7. key 类 state(providerKeys)只存用户主目录 `~/.config/mslxdff/state.json`,绝不进 repo(见 AGENTS.md + .gitignore 加固)。
260
+
261
+ ## 7. 目录导览(当前结构)
262
+
263
+ ```
264
+ mslxdff/
265
+ ├── bin/mslxdff.js 薄适配器(4 行):仅 `import { run } from "../src/cli/index.js"` + `await run(argv)`,全部逻辑下沉至 src/cli 深模块
266
+ ├── src/
267
+ │ ├── cli/ 深模块 CLI(单一入口 `run(args)`,厚重、单一 interface;`bin` 仅适配)
268
+ │ │ ├── index.js 门面:`run(args, VERSION)` 单一 inlet,按原 bin 顺序分派至子命令
269
+ │ │ ├── policy.js DaemonPolicy:`compareSemver`/`waitForHealth`/`effectivePort|Host`/`stopDaemonIfOutdated` + 冷却/刷新等 env 读值
270
+ │ │ ├── format.js 展示层:`fmtStatus`/`fmtUptime`/`fmtEvent`/`fmtTs`
271
+ │ │ ├── help.js `printHelp(VERSION)`(原 bin 1760 行中的 70 行帮助文案)
272
+ │ │ ├── status.js `printStatus(VERSION)`(原 300 行聚合体检,拆出为独立文件,<20KB)
273
+ │ │ ├── interactive.js `pickInteractive`/`pickInteractiveMulti`(TTY 交互选择器,原地重绘)
274
+ │ │ ├── util.js `errMsg`/`readModelsCache`/`npmCmd`/`run`(通用工具)
275
+ │ │ ├── group-helpers.js `probeHealth`/`syncAllJoinedGroups`/`markJoined`/`groupIs`(群组辅助)
276
+ │ │ ├── bootstrap.js Daemon 运行时:server/providers/auto/peers/groups/broadband/auto-update(原 bin 1000 行 daemon 体,原样下沉)
277
+ │ │ ├── lifecycle-log.js daemon 生命周期留痕:启动(pid/ppid/版本/node/cwd)/信号/顶层异常/退出/30m 心跳(rss)——"神秘消失"时区分被信号杀/崩溃/外部强杀,定位死亡时刻与 OOM 曲线
278
+ │ │ ├── qoder-checkin.js qoder 每日签到调度(09:00 + 启动补签;按每号 region 选域名;`MSLXDFF_QODER_CHECKIN=0` 关,结果落 `state.qoderCheckin`)
279
+ │ │ ├── provider-gate.js 定制 provider 启用门禁:auth 号型(workbuddy/traework/qoder)无 keys 但 auth 目录有号也启用、baseUrl 可空——旧门禁曾要求 baseUrl 导致 qoder 被静默跳过(整供应商从 /v1/models 与 -models 消失)
280
+ │ │ └── commands/
281
+ │ │ ├── daemon.js Daemon 生命周期:`-stop`/`-restart`/`-port`/`-d`/bare-run/`-debug`
282
+ │ │ ├── system.js 系统探活:`-help`/`-update`/`-refresh-token`/`-showtoken`/`-uninstall`/`-log`/`-plugins`/`-chat`/`-status`/`-free`/`-autostart`
283
+ │ │ ├── model.js 模型:`-model`/`-models`(refresh/status/pick/list 含 provider 过滤与交互)
284
+ │ │ ├── model/list-live.js `-models` 交互候选并入网关 live(allowAny 空白名单供应商的模型只在 /v1/models)+ `model/live-models.js` 取数
285
+ │ │ ├── sync.js 同步:`-setto workbuddy|opencode|chatgpt`
286
+ │ │ ├── workbuddy.js WorkBuddy:`-workbuddy checkin|growth|travel|balance|list|remove`
287
+ │ │ ├── timezone.js 时区:`-timezone [set <tz>|clear|status]`(默认 Asia/Shanghai,MSLXDFF_TZ 覆盖)
288
+ │ │ ├── group.js 群组:`-creategroup`/`-group`/`-addtogroup`/`-resetban`/`-leavegroup`/`-delgroup`
289
+ │ │ ├── join-core.js 加组核心:`normalizeLeaderUrl`(归一化+人话校验)/`joinGroupCore`(CLI 与向导共用,不打印不退出)
290
+ │ │ ├── join-wizard.js 手机宽带接入向导:两问入组 → 出口 IP → 自动起服务 → 保活指引(依赖全注入可测)
291
+ │ │ ├── use-group.js 组员开关:`-use-group [on|off]`(全局:off 则所有供应商仅本机,默认 on,MSLXDFF_USE_GROUP 覆盖)
292
+ │ │ └── provider/ Provider 深子模块(原 39KB 单文件按域拆,全部 <20KB)
293
+ │ │ ├── index.js 调度:`-providers list` + `-provider` 路由至子域
294
+ │ │ ├── index.js 调度:`-providers list` + `-provider` 路由至子域
295
+ │ │ ├── cline-login.js `cline login` WorkOS 设备授权流:拿 refreshToken 落盘 state(直接复用,免 Worker 部署)
296
+ │ │ ├── cline-free.js `cline free [--json]` / `free sync [--yes] [--keep-extra]`(ADR-0026:只读列上游 free 目录 + dry-run 预览后写 allowlist)
297
+ │ │ ├── cline-quota.js `cline quota [--json/--account/--model]`(只读聚合 `cline-usage.jsonl` 双口径:free 本周期/pass 24h,按账号哈希分组,空账本引导)
298
+ │ │ ├── usage.js Cline 账号×模型双口径统计:free→额度周期(限额恢复后重新计),pass→24h 滚动窗口;isFreeModel/aggregateUsage/loadAndAggregate;数据源 `cline-usage.jsonl`(`type`:output|limit,字段含 `modelType`:free|pass)
299
+ │ │ ├── codearts-login.js `codearts login` 华为云 CodeArts PKCE 授权:浏览器回调/ticket 双通道拿 refreshToken,凭证 blob(含 DPoP JWK)落盘 state(ADR-0027)
300
+ │ │ ├── add.js `mslxdff -provider add <id> <baseUrl> <key>`(通用/WorkBuddy 分支)
301
+ │ │ ├── config.js `set-models-path`/`set-chat-path`/`clear`/`set-url`/`share`
302
+ │ │ ├── allowlist.js `allowlist`/`allowAny`(白名单增量管理,空即阻塞)
303
+ │ │ ├── models.js `models`(live 拉取 + allowlist 标注 + 价格对齐)
304
+ │ │ ├── bench.js `bench`/`bench --via`(仅测 allowlist 模型 TTFB/TPS,空则探活;`--via` 时直连 vs 经在线 peer 对比 串行+额度保护+四态)
305
+ │ │ └── keys.js `list`/`add`/`remove`/多 key `set`/交互式隐藏输入
306
+ │ ├── server.js HTTP server、路由装配、认证、resolvePort
307
+ │ ├── routes.js 路由门面(63KB 超标,待拆——计划见 AGENTS.md)
308
+ │ ├── bench/ 上游测速:probe(/v1/models→/models 回退)、runner(单模型 TTFB/TPS)、report(排序/表格 via列扩展)、via(peer发现+串行编排 默认跳过opencode 额度保护)、via-probe(轻量探针 max_tokens=5 测 TTFB)、via-routes(via-routes.json 产表/读表/TTL + provider:<id> 级回退)
309
+ │ ├── upstream-probe/ 后台上游探针(ADR-0015):probe(direct GET + /v1/relay 代发)、rotate(每 tick 一家 EMA 落盘 provider:<id>)、start(daemon 装配 MSLXDFF_UPSTREAM_PROBE_MS=0 关)、display(-group list 行缀 / -status 汇总渲染)
310
+ │ ├── upstream-engine/ 上游引擎选择(ADR-0017):index(sdk/legacy 开关 + 委派矩阵 + 装载失败回退)、mode(引擎模式解析纯函数:缺省 sdk,供应商级开关继承全局总闸)、sdk/(convert OpenAI 请求→SDK 入参、sse parts→OpenAI SSE 帧、attempt 执行器/错误映射/连接池注入、chat 适配器工厂、responses 适配器、dispatch 供应商级分派;workbuddy/opencode/cline/generic 共用)
311
+ │ ├── model-capabilities/ 模型能力元数据(ADR-0016/0022):parse(models.dev 模型对象→能力形状纯函数 + workbuddy 原生字段→统一形状)、index(fetch models.opencode.ai + 磁盘缓存 24h + staleness 降级 + 全局单例 + readyWarm 零网络热身 + workbuddy 动态源 helper)、enrich(-setto opencode 条目注入 opencode Model 形状 + 人话摘要)、merge(/v1/models 条目内联 capabilities:裸 id→剥 -free→二级厂商前缀→workbuddy 兜底,?raw=1/codex 逃生)、npm 索引(opencode 裸 id→provider.npm,启动注入 responses 判定,每小时重查)
312
+ │ ├── routes/chat/ chat 门面 + 7 handler(index/hedge/local/peer/broadband/exhausted/via-route,均 <10KB;via-route 单路径读 via-routes.json 择 peer,不并发)
313
+ │ ├── upstream.js 上游客户端:keepalive、重试、超时、匿名兜底
314
+ │ ├── free-lane.js zen 免费层 agent 形状门禁(ADR-0020):核心五工具注入 + 强制流式 + SSE→JSON 聚合(-chat 直连/daemon 裸客户端共用)
315
+ │ ├── opencode-identity.js opencode 客户端身份单一来源(ADR-0018):26 位 id 生成(12hex+14base62)/sha1 摘要派生/opencodeUa/三件套(-chat curl 与 bench 直连共用)
316
+ │ ├── models.js 模型服务:刷新、到期、cacheFile、providers 聚合
317
+ │ ├── auto.js 自动模型:排序、冷却自愈、勾选集
318
+ │ ├── reasoning.js 思考模式 reasoning_content 注入
319
+ │ ├── readline-compat.js readline 兼容层(回调版包 question,老 Node 不崩;-chat 版本门 <18 给升级提示)
320
+ │ ├── compat.js Node 18+ 运行时兼容层:compatFetch(undici Agent 池,源码勿用裸 fetch)/timeoutSignal/clone/uuid/getUndici + MIN_NODE_MAJOR/assertMinNode 入口版本门(ADR-0024)
321
+ │ ├── time.js 时区格式化(默认 Asia/Shanghai,可配 MSLXDFF_TZ/state timezone,fmtShanghai/YMDHM/HMS 统一走 getTimezone())
322
+ │ ├── timeline.js 人读请求时间线(每请求一行汇总 direct/peer/retry/result/total,纯函数)· `src/chat-pipeline/index.js` 投影落 `timeline.log`
323
+ │ ├── model-trace.js 按模型链路日志:模型 id → `<provider>-<model>.log`(安全化),request/ordered/upstream/peer/relay/result 阶段与安全摘要;**事件面用黑名单**(默认全部可见,只排除 peer-health/heartbeat/client-session/upstream-probe*),决定类事件只渲染 DECISION_FIELDS 登记的标量;`upstreamEcho(res)` = provider 回显头(upstream/account/pick/cooled)转日志字段的单一来源;不落 prompt/正文/凭据
324
+ │ ├── state.js state 持久化(缓存层 + token/port/timezone/modelPicks/providerKeys/providerConfigs/auths/allowedModels/useGroup,workbuddy `auths` 与 `keys` 一一对应)
325
+ │ ├── state/schemas/use-group.js 组员中继总开关 + 供应商路由(ADR-0015/0023:local-only 恒 false,key 供应商默认直连仅 opencode 走组员,off 则全禁)
326
+ │ ├── state/migrations.js 状态迁移运行器(`runStateMigrations`:按序执行已注册迁移并汇总 applied/skipped,daemon bootstrap 与 CLI 单点调用)
327
+ │ ├── state/migrations/cline-unify.js Cline id 统一迁移(ADR-0026:合并 `providerConfigs.clinebot`→`cline`,keys 去重 + 剔 `sk_` 形态、allowlist 求并、备份 `state.json.bak-<TS>` + `cline-unify-migrated` 留痕,幂等)
328
+ │ ├── daemon.js 后台守护
329
+ │ ├── plugins.js 插件加载/执行,失败隔离
330
+ │ ├── sync-workbuddy.js WorkBuddy models.json 同步(WorkBuddy 外部同步;picks 非空时剪枝失效本地条目)
331
+ │ ├── sync-opencode.js opencode opencode.json 同步(provider.mslxdff,alias mslxdff-<id> 防重名,原名兼容,重复幂等;`OPENCODE_CONFIG` 可注入;picks 非空时剪枝失效模型)
332
+ │ ├── sync-codex.js Codex config.toml 同步(model_providers.mslxdff,wire_api=responses,绝对路径鉴权,单键覆盖无需剪枝)
333
+ │ ├── responses/ Responses 端点翻译层:translate.js(responsesToChatBody/chatJsonToResponse/createChunkTranslator/toResponsesUsage,纯函数)
334
+ │ ├── routes/responses-route.js POST /v1/responses(capture 垫片+二进制解码+debug 日志,进 ChatPipeline)
335
+ │ ├── free-watcher.js V2EX 白嫖雷达(仅 V2EX 单源,latest+hot,关键词过滤,白嫖|限免|免费额度|注册送|羊毛)
336
+ │ ├── chat/ 对话终端 -chat(mimo 优先/big-pickle 兜底、历史持久化、超长压缩、run_command/read_file/curl、仅拦 uninstall)
337
+ │ │ ├── index.js 入口
338
+ │ │ ├── repl.js REPL 循环与 slash 命令 + 美化 banner/统计
339
+ │ │ ├── repl.js REPL 循环与 slash 命令 + spinner 状态
340
+ │ │ ├── spinner.js 加载动画:已发送/等待/已回复(TTY 逐帧,非 TTY 静态)
341
+ │ │ ├── stats.js 统计聚合:模型延迟/网关请求/勾选集/最近错误
342
+ │ │ ├── upstream.js 上游调用与 fallback/压缩摘要
343
+ │ │ ├── prompt.js 系统提示词(mini + 实时模型列表)
344
+ │ │ ├── tools.js run_command/read_file 工具与校验(仅项目/日志/state,curl 探活)
345
+ │ │ ├── store.js 历史持久化与压缩判定
346
+ │ │ └── config.js 模型与阈值常量
347
+ │ ├── usage/ 模型用量报表数据层(-stats 的数据源,与 state 的 modelStats 终生 EMA 分开)
348
+ │ │ ├── record.js 逐请求 usage 落盘:按日 JSONL + 保留期删旧文件(不走 1MB 环形截断)
349
+ │ │ └── report.js 窗口聚合:加权速度 Σ输出÷Σ生成耗时(纯函数 + 薄 IO 分离)
350
+ │ └── providers/
351
+ │ ├── dispatcher.js 前缀路由、聚合 listModels、剥前缀(`workbuddy` 支持 `uid:raw` 钉死 + allowlist 403 直通,`workbuddyUid` 透传)
352
+ │ ├── model-id.js splitModelId/joinModelId/normalizeProviderId
353
+ │ ├── opencode.js opencode 上游 provider
354
+ │ ├── openrouter.js OpenRouter provider(keyring + 品牌头 + 免费 filter + chatWithKeys 瞬时共享)
355
+ │ ├── generic.js 通用 OpenAI 兼容 provider(baseUrl + keyring + 前缀化,不过滤 pricing)
356
+ │ ├── responses-channel.js 通用供应商 responses 通道(ADR-0032):responses 类模型(`muse-spark*`)自动改打 `/responses`(旧行为固定 `chatPath` → 上游 `503 Endpoint is unavailable`);`resolveResponsesPath`/`envSlug` 路径派生 + `createResponsesChannel`(流式优先 `@ai-sdk/openai` responses 适配器含加密思考往返,非流式/SDK 不可用走原生 `chatToResponsesBody` 正转换 + 反向整形,出参恒 chat 形状;key 轮换/退避重试/`_t` 与 `createChatRunner` 同语义)
357
+ │ ├── workbuddy.js WorkBuddy provider(`POST /v2/chat/completions` 强制 stream + `GET /console/…/models` 升序前缀 + `POST /v2/billing/meter/get-user-resource` 查余额,`balanceCache` 5min,环形 `402/insufficient` 自动切号,`x-mslxdff-workbuddy-uid` 钉死,`workbuddy-rotation.log`,`keyring` + `share` 默认 off,401/403 refresh 回写)
358
+ │ ├── workbuddy-balance.js WorkBuddy 余额查询与缓存(`fetchBalance/getCachedBalance`,TTL 5min)
359
+ │ ├── cline.js Cline 定制 provider(shim,下沉至 cline/;`api.cline.bot` 仅 `free` 为免费,单独文件隔离,域名匹配自动启用)
360
+ │ ├── cline/ Cline 深模块:`index.js` 门面 / `auth.js` 多账号 refreshToken 池(`POST /api/v1/auth/refresh` round-robin + 冷却 + 800ms 队列;429 按「账号×模型」记限流 `limits[model]`,不冻结整号)/ `headers.js` 指纹头(UA `Cline/3.0.47` + `X-CLIENT-TYPE: cline-sdk` + `Authorization: Bearer workos:<token>`,绕 `403 only available via Cline product surfaces`)/ `chat.js` deepseek 家族(含 `cline-free/deepseek-*`)非流式内部强制 stream + SSE 聚合(对外仍按请求方 stream 标志)/ `models.js` 聚合目录 = `free` + `clinePass`(`recommended`/`clineCloud` 不收;对外 `cline/<id>`,`allowlist` 存裸 id,`-models` 勾选即 `/v1/models` 目录)+ 启动自检快照(daemon 每次重启由 `server-lifecycle` 显式调 `checkFreeUpdates()` 只对比 free 增删,变化写 `daemon.log` + 更新 `logDir/cline-free.json`,**不写合并缓存**——否则 free-only 会把 pass 挤掉 10 分钟;不经 `dispatcher.preheat`;`free-catalog.js` 免费目录深模块(ADR-0026:拉 `recommended-models` 只抽 `free[].id`,供 CLI `-provider cline free [sync]` 与启动自检共用))/ `allowlist-sync.js` 白名单自动同步(上游列表=真相:`models.js` 每次成功取数后把 free+clinePass 裸 id 并入 `providerConfigs.cline.allowedModels`,只增不减、无新增不写盘、兜底路径不写盘、异常吞掉留痕;`MSLXDFF_CLINE_AUTOSYNC=0` 关)/ `usage.js` 账号×模型额度周期统计
361
+ │ ├── codearts.js 华为云 CodeArts 供应商(shim,下沉至 codearts/;免费福利模型,登录态凭据)
362
+ │ ├── codearts/ CodeArts 深模块(ADR-0027):`index.js` 门面(blob→auth-pool+catalog+chatSvc,轮转写回 saveFn)/ `auth-pool.js` 账号池(STS 临期 30min 单飞刷新、refresh_token 单次轮换原位换 blob 写回、终态死号)/ `sts.js` STS 刷新(DPoP proof + code_verifier)/ `sign.js` SDK-HMAC-SHA256 签名(canonical URI 尾补 `/`、maas_type 签名前写入、Chat-Id/Session-Id 签名后追加)/ `dpop.js` ES256 P-256 低 S(`dsaEncoding:"ieee-p1363"`)/ `models.js` 三路发现 + benefit claim(幂等 0000)/ `sse.js`+`stream.js` SSE 帧(text 全文快照→delta/聚合 + 200 内嵌错误映射 429/400/502)/ `chat.js` 恒 stream:true 对话(chat_id 32hex、reasoning 占位 deepseek-all/kimi-tool)/ `login.js`+`const.js` PKCE 授权与常量
363
+ │ ├── traework.js TRAE SOLO 供应商(shim,下沉至 traework/;TRAE SOLO 通道,登录态凭据)
364
+ │ ├── traework/ traework 深模块:`index.js` 门面(keys/auths+auth 目录→keyring+chatSvc+modelsSvc)/ `constants.js` 上游常量 / `headers.js` 三类头(solo/ug/oauth 纯函数)/ `payload.js` OpenAI→SOLO 改写 / `errors.js` 错误分类 / `sse.js` SOLO SSE→OpenAI(解析/聚合/流转换纯函数)/ `token.js` ExchangeToken 刷新 / `account-store.js` 账号落盘(auths/trae-*.json 0600+state 双写)/ `chat.js` 恒 stream:true 对话(transport 直调+预刷新+轮转3号)/ `models.js` 动态模型表+静态 32 回退+映射 / `checkin.js` 签到/积分
365
+ │ ├── qoder.js Qoder 供应商(shim,下沉至 qoder/;Qoder 免费池原生直连,登录态凭据)
366
+ │ ├── qoder/ Qoder 深模块(ADR-0029):`index.js` 门面(device-token blob→keyring+session+chatSvc+modelsSvc,每号 region 从 auths 文件读;坏号冷却只看 401/403/429/5xx——流式路径的真实状态码从内部头 `x-mslxdff-qoder-upstream-status` 取,fetch 异常折 502)/ `sticky.js` 同请求粘号选择器(同请求复用同一号,仅冷却才换,ADR-0036)/ `constants.js` 区域端点表 + 签到/额度路径 / `oauth.js` PKCE 设备授权 / `encode.js` 自定义 base64 / `fingerprint.js` 设备指纹 / `session.js` RSA+AES 会话与五段签名 / `payload.js` 消息与模板 / `sse.js` 信封 SSE 解析 + 错误四分类(content_policy→400 / auth→401·403 / 其余 502)/ `chat.js` 对话薄门面(上游恒 `stream:true`;客户端 `stream:true` 真流式边收边吐、`false` 聚合 JSON,ADR-0031;所有出口回显 `x-mslxdff-upstream`/`x-mslxdff-qoder-region`/`x-mslxdff-qoder-account`,非 200 或 fetch 异常另带内部头 `x-mslxdff-qoder-upstream-status` 供门面判定冷却)/ `request.js` COSY 请求装配(两条管线共享单一签名口)/ `stream.js` 真流式转发(reader 循环即时 enqueue,不攒数组)/ `aggregate.js` 非流式聚合 + `toCompletionJson` / `models.js` 模型表 / `account-store.js` 账号落盘(auths/qoder-<uid>.json 0600+state 双写)/ `checkin.js` 选区签到+额度 / `baseprompt.json` 上游系统提示词
367
+ │ ├── qwenwork.js 千问办公供应商(shim,下沉至 qwenwork/;账号积分池,登录态凭据)
368
+ │ ├── qwenwork/ qwenwork 深模块(ADR-0037):`index.js` 门面(token blob→keyring;401/403 refresh 回写轮换再重试;额度错长冷却 1h 换号;无身份首轮自动补 userinfo,否则上游判 101)/ `constants.js` 端点+版本快照(env 可覆盖)/ `crypto.js`+`rsa.js` vendor 原作者手写 AES/RSA/MD5(勿换 node:crypto,曾换出 101)/ `cosy.js` 会话+五段签名头 / `payload.js` OpenAI→agent_chat 组装 / `sse.js` 信封解帧+聚合+额度措辞(code 14018) / `stream.js` 真流式(model 名回写请求名)/ `upstream.js` 签名请求+OAuth 设备流+额度 / `http.js` 文本清洗 / `account-store.js` 账号落盘(auths/qwenwork-<uid>.json 0600+state 双写)
369
+ │ ├── zcode/ zcode 深模块(ADR-0038):`index.js` 门面(JWT keys→keyring + 差异化冷却 + 未登录空目录守卫)/ `chat.js` OpenAI→Anthropic messages 转换 + 转发 + 业务码分类 / `sse.js` Anthropic SSE→OpenAI chunk(pull 循环保证每轮有产出,防首片落在事件中间时挂起)/ `headers.js` 12 项 source headers / `auth.js` JWT 过期判断与指纹 / `oauth.js` CLI 轮询登录(cli/init+poll)/ `account-store.js` auths/zcode-<uid>.json 0600 + deviceMid 复用 / `models.js` 内置目录 + allowlist 只增不减补齐 + balance capabilities 过滤 / `quota.js` balance 解析与表格渲染 / `const.js` 端点·版本·错误码·目录
370
+ │ ├── registry.js 可扩展注册表(customProviders/customNormalizers,新增供应商仅注册+新增文件)
371
+ │ ├── keyring.js 多 key 轮转 + 冷却隔离
372
+ │ ├── share-keys.js ADR-0019 瞬时共享:header 组装/解析、转发自动借出、opencode/workbuddy/cline/traework/qoder 硬排除(后四者为本机账号绑定/登录态型)
373
+ │ └── index.js 导出
374
+ ├── docs/
375
+ │ ├── ARCHITECTURE.md ← 本文件(总览 + 变更契约)
376
+ │ ├── cli_help.md CLI 完整参数手册(与 §6 同源,改 CLI 必同步)
377
+ │ ├── cli_help_mini.md AI 精简手册(仅 -chat 用,改 CLI 必同步精简版)
378
+ │ ├── plugins.md 插件开发指南
379
+ │ ├── adr/ 架构决策记录(0001..0012,见 §8 索引)
380
+ │ └── agents/ agent 工作流用领域文档
381
+ ├── cli_help.md 根级同文件(`docs/cli_help.md` 的镜像)
382
+ ├── cli_help_mini.md 根级精简镜像(`docs/cli_help_mini.md` 的镜像)
383
+ ├── test/ 单元+集成测试(node --test,全量 250+)
384
+ └── scripts/docs-check.js npm run docs:check 文档就绪检查
385
+ ```
386
+
387
+ ## 8. 决策索引(ADR 目录)
388
+
389
+ | # | 主题 | 一句话 |
390
+ |---|---|---|
391
+ | 0001 | reasoning_content 注入 | DeepSeek 思考模式回传占位,防 400 |
392
+ | 0002 | 免费模型 filter | whitelist + `-free`,`big-pickle` 特例 |
393
+ | 0003 | 零状态无鉴权 | 早期无账号阶段的设计 |
394
+ | 0004 | Bearer token 鉴权 | `/v1/*` 恒时比较,`/health` 公开,token 轮换 |
395
+ | 0005 | 组网 mesh | 组员/组长接力,IP 级限流分散 |
396
+ | 0006 | 宽带成员 | 动态 IP 成员经 Leader 中继 |
397
+ | 0007 | 多供应商前缀 | `<provider>/<id>` 前缀路由,默认 opencode 裸 id(0.1.56 新增) |
398
+ | 0008 | 瞬时 key 共享 | shareKeysToPeers 开关(默认关),转发时附带 key 给组员借用一次,opencode 恒排除(0.1.57)。**开关部分已被 0019 取代** |
399
+ | 0009 | 对话终端 -chat | mimo-v2.5-free 优先/big-pickle 兜底,自然语言转精确命令,模糊匹配由模型完成,仅拦 -uninstall,历史持久化超长压缩,read_file 限项目内 + curl 探活,美化 banner/统计(0.1.58.2) |
400
+ | 0010 | 供应商模型白名单 | `providerConfigs.<id>.allowedModels` 空=不限,非空仅名单内可用;接入时可带白名单,`allowlist [list\|set\|add\|remove\|clear]` 管理;拦截在 dispatcher(403),`/models` 过滤(防昂贵模型) |
401
+ | 0011 | 宽带中继 SSE 长连 | broadband 1s poll 改 `GET /v1/groups/relay/stream` SSE 单长连推送 + 25s ping,空闲流量 -96%,断线指数退避,poll 保留降级 |
402
+ | 0012 | Responses 端点与 Codex 同步 | `POST /v1/responses` 复用 ChatPipeline(stateless,reasoning 暂不透传);Codex 三特例(`models:[]` 空顶层、Responses 口径 usage、done 带全文);`-setto chatgpt` 写三端共用 config.toml(绝对路径鉴权);workbuddy/opencode 同步加 picks 剪枝 |
403
+ | 0013 | Node 16 兼容层(**已被 0024 撤销**:Node 16 缺 `Response`/`Headers`/`ReadableStream`/`TransformStream`,实测必 `ReferenceError`,运行底线改回 18+) | undici 降级 5.x + engines>=16;`src/compat.js` 单一出口(compatFetch/timeoutSignal/clone/uuid/getUndici),全仓 34 文件替换;`-chat` 版本门降 16 |
404
+ | 0014 | DeepSeek Web 逆向供应商(**已废弃**:0.1.106 撤销该供应商——chat.deepseek.com 风控封号严重,代码已删,本文档保留历史) | chat.deepseek.com Android 协议(免浏览器指纹);PoW DeepSeekHashV1 纯 JS(OmniRoute MIT port + 官方 wasm 互证);无痕会话;多 token 池串行;模型 `deepseek/{chat,reasoner,chat-search,reasoner-search}`,安全默认全 blocked |
405
+ | 0015 | 上游探针与三类路由 | 供应商三态:`workbuddy` local-only(本机账号绑定,恒不走组员)、`opencode` quota-pool(图额度不图速度,永不走 via-route,429 后 peer 兜底)、其余 latency-compare(读 `via-routes.json` 择路);后台探针 `src/upstream-probe/`(`MSLXDFF_UPSTREAM_PROBE_MS` 默认 60s,每 tick 一家:direct GET models + 逐 peer `/v1/relay` 代发,EMA α=0.3 落 `provider:<id>` 键);`getViaRoute` 精确键优先 provider 级回退,TTL 默认 5min;展示并入 `-group list` 行缀与 `-status` 汇总,零新增命令 |
406
+ | 0016 | 模型能力元数据 | zen `/models` 零能力字段(实测),改与 opencode 官方同源拉 `models.opencode.ai/api.json`(4.5MB,opencode 条目覆盖 zen 70/70);新端点 `GET /v1/models/capabilities[?provider=&id=]`(reasoning 档位 effort/toggle/budget_tokens、imageInput、toolCall、context/价格),`/v1/models` 形状不动(Codex 红线);`src/model-capabilities/` 纯函数解析 + 磁盘缓存 24h + staleness 降级(fetch 失败回退旧缓存)。0.1.110 后扩展:`provider=workbuddy` 走上游原生字段(`/console/.../models` 自带 `maxInputTokens/supportsImages/supportsReasoning/supportsToolCall/disabledMultimodal`,29 模型全覆盖,first-party 全量含 blocked),`normalizeWorkbuddyCaps` 映射统一形状,`-provider workbuddy models` 表格加能力列 |
407
+ | 0017 | 上游引擎迁移到 AI SDK | 上游 wire 层以 `@ai-sdk/*` 为唯一实现(P0–P4 分阶段):引擎开关 `MSLXDFF_UPSTREAM_ENGINE`(缺省 `sdk`,显式 `legacy`/关闭词回退原实现);共用库 `src/upstream-engine/sdk/{convert,sse,attempt,chat,responses}.js`(workbuddy 原型提升,Authorization 随 headers 透传);opencode 流式 chat 走 `@ai-sdk/openai-compatible`、responses 类(muse-spark*)走 `@ai-sdk/openai` 的 responses 适配器,均复用 legacy keep-alive 连接池;非流式/anon 重试显式委派 legacy;SDK 不可用(Node16)自动回退;P2 最小一致性夹具(双引擎语义帧 diff)已落地,P3 逐供应商消除委派(workbuddy 缺省 sdk、供应商级开关复用同一语义并继承全局总闸),P4 后续删 legacy |
408
+ | 0018 | zen 免费层客户端身份规格 | 2026-09-17 起 zen 免费层只放行「像官方 OpenCode 客户端」的请求:`User-Agent` 必须 `opencode/<semver>`,`x-opencode-session`/`x-opencode-request` 必须 26 位 opencode Identifier(`<prefix>_` + 12 hex = `timestamp*4096+同毫秒计数` 截 48bit + 14 base62 随机);冒充/缺版本 → `403 FreeTierError`。`src/upstream.js` 单一来源:`opencodeIdTail`/`sessionFromMessages`(sha1 摘要确定性派生、保持会话亲和)/`opencodeUa()`/`opencodeClientIdentity()`(-chat curl 与 bench 直连共用),`MSLXDFF_OPENCODE_UA` 可覆盖 |
409
+ | 0019 | key 随转发自动借出 | 删除 ADR-0008 的 `shareKeysToPeers` 开关(默认借出):借道 = 用你的 key,组内互信是前提。转发(peer/via-route)时命中本机有 key 的供应商即自动附带 `x-mslxdff-share-keys`,组员仅本次请求借用。硬排除 opencode(无 key)/workbuddy(local-only)/cline(refresh-token 型,并发刷新互踢);无开关、无白名单。同步删 `providerShareKeys` state/CLI/status 列与 `MSLXDFF_SHARE_PROVIDERS` |
410
+ | 0020 | zen 免费层 agent 形状门禁 | 2026-09-18 起免费层要求"agent 形状":`stream:true` + `tools` 含 bash/edit/glob/grep/read 五名,UA 版本 ≥ `1.18.0`(bisect 实测:4 个或换名 403,五名 200;1.17.x 五名 426);新增 `src/free-lane.js`(幂等补最小工具定义、强制流式、`aggregateChatSse` 把 SSE 聚合回非流式 JSON);`src/upstream.js`(legacy)与 `src/upstream-engine/index.js`(SDK 流式绕过 legacy)双点注入;默认 UA 跟 npm latest,`MSLXDFF_FREE_LANE=0` 逃生阀 |
411
+ | 0021 | 模型用量报表数据层 | `-stats`(近 N 小时每模型 token+速度):逐请求 usage 落 `<logDir>/usage/YYYY-MM-DD.jsonl`(按日分片+保留期删旧,区别于 logs 1MB 环形截断),聚合纯函数窗口加权 `Σ输出÷Σ生成耗时`;写入点唯一(relay 200 记账块,canonical 名单记防双计);与 `modelStats` 终生 EMA 双事实源并存(EMA 服务排序、JSONL 服务窗口);`MSLXDFF_USAGE_LOG=0` 关、`MSLXDFF_USAGE_KEEP_DAYS=2`;顺带收口 `-status` recent calls 幻读 ttfb/tps/usage 死字段 | `src/usage/{record,report}.js`, `src/cli/commands/stats.js` |
412
+ | 0022 | /v1/models 能力富化 | 修订 ADR-0016 的"/v1/models 形状不动":每条 data 默认内联 `capabilities` 子对象(reasoning/effort 档位/模态/toolCall/context/maxOutput/cost + `endpoints:["chat","responses"]` + `upstreamApi`——muse-spark* 与 npm `@ai-sdk/openai` 上游走 responses);`src/model-capabilities/merge.js` 匹配链裸 id→剥 `-free`→二级厂商前缀→workbuddy 原生兜底;`readyWarm` 零网络热身(冷缓存后台拉新本请求原样);`?raw=1` 逃生门;codex 调用者不富化(ADR-0012 `models:[]` 红线保持);best-effort 目录不可用逐条降级 | `src/model-capabilities/{merge,index}.js`, `src/routes/models-route.js`, `test/models-capabilities-merge.test.js` | `?raw=1` |
413
+ | 0024 | 运行底线 Node 18+(撤销 0013) | Node 16 缺 `Response`/`Headers`/`ReadableStream`/`TransformStream` 四个 Web 全局(真 Node 16.20.2 实测全 `undefined`),而全仓 29 处裸 `new Response/Headers/ReadableStream/TransformStream(...)`(`free-lane.js` 的 `aggregateChatSse` 是「非流式+免费模型」必经路径)→ 实测 `ReferenceError: Response is not defined`,ADR-0013「Node 16 全功能可用」证伪。`engines` 改 `>=18`;`src/compat.js` 新增 `MIN_NODE_MAJOR`+`assertMinNode()` 由 `src/cli/index.js` 的 `run()` 入口硬拦(人话+exit 1);`compat.js` 保留为 fetch 单一出口(undici Agent 池仍用),18+ 的 Web 全局可直接用;`-chat` 版本门跟 `MIN_NODE_MAJOR` | `src/compat.js`, `src/cli/index.js`, `src/readline-compat.js`, `package.json` | `engines >=18` |
414
+ | 0025 | WorkBuddy 凭据目录跟随 state | `resolveAuthDir()` 旧实现末条兜底 `join(process.cwd(),"auths")` 才是生产实际默认(`mslxdff-` 那条恒不命中、且无生产代码设 `MSLXDFF_STATE_FILE`)→ 企业长效 refreshToken 落在「你当时所在目录」(实测两个账号分居 `~/.config/mslxdff/auths` 与 `项目根/auths`)。改为跟随账本:`authDirFor({explicit,testEnv,stateFile})` 纯函数,`WORKBUDDY_AUTH_DIR` > 测试隔离 > `dirname(state)/auths`;读取保留只读迁移兜底 `authDirCandidates()` = `[主位置, cwd/auths]`(显式/测试不兜底);读取侧收敛为单一出口 `listAccountDocs()`(provider 构造 / CLI 加载与摘除 / `-provider add` / `workbuddy-token-auto.js` 四处复制品合一,同 uid 以主位置为准);`-workbuddy remove` 扫全部候选目录防旧副本"复活" | `src/providers/workbuddy/account-store.js`, `src/providers/workbuddy/index.js`, `src/cli/commands/workbuddy.js`, `src/cli/commands/provider/add.js`, `workbuddy-token-auto.js` | `WORKBUDDY_AUTH_DIR`(默认 `~/.config/mslxdff/auths`) |
415
+ | 0026 | Cline 供应商 id 统一为 cline(+ 免费目录即清单) | 同一供应商历史挂了两个 id(`cline`/`clinebot`):login 双写 + 每次 refreshToken 轮换回写另一 id,使 `clinebot` 永不消亡(daemon 内双实例、`/v1/models` 双份前缀)。收敛为**对外只产出 `cline`**(login 只写、refresh 只回写、日志只报 `cline`),入站把 `clinebot`/`cline-bot` 一次性归一(`normalizeProviderId`,`registry.js` 删 id 分支但保留 `baseUrl.includes("cline.bot")` 兜底);`providerConfigs.clinebot` 由幂等迁移(真改动前备份 `state.json.bak-<TS>` + `cline-unify-migrated` 事件留痕,keys 去重并剔 `sk_` 形态)合并进 `cline` 后删键,双实例消失。免费额度即清单:`-provider cline free [--json]`(只读,`GET /api/v1/ai/cline/recommended-models` 的 `free`)+ `free sync [--yes] [--json] [--keep-extra]`(默认 dry-run 预览后写裸 id 到 allowlist,`free-catalog.js` 与 daemon 启动自检同源)+ `-provider cline migrate [--dry-run]`。**`cline` 恒为 local-only(硬约束,不可回退)**:不经组员转发、不借出 key、只走本地直连,历史别名同样硬排除(延续 0015 三态路由 / 0019 share-keys 硬排除 / 0022 能力合并的既有语义) | `src/cli/commands/provider/cline-login.js`, `src/cli/commands/provider/cline-free.js`, `src/providers/cline/free-catalog.js`, `src/providers/cline/index.js`, `src/state/migrations/cline-unify.js`, `src/state/migrations.js`, `src/providers/share-keys.js`, `src/state/schemas/use-group.js` | `-provider cline free/migrate` |
416
+ | 0027 | codearts 供应商(华为云 CodeArts Agent) | 华为云盘古助手免费福利模型反代:`codearts/<modelId>` 前缀(local-only 不借出 key);一华为账号一凭证 blob(refreshToken+codeVerifier+DPoP JWK 三重绑定,多账号 keyring 轮转);SDK-HMAC-SHA256 签名 + DPoP ES256 低 S + STS 临期单飞刷新与 refresh_token 单次轮换原位写回(终态死号提示重登);三路模型发现 + benefit claim 幂等(`error_code:"0000"`);对话恒 `stream:true`(v2 SSE `text` 全文快照→delta/聚合,HTTP 200 内嵌错误映射 429/400/502);login 走 PKCE 浏览器授权(`-provider codearts login`,blob 落 `providerConfigs.codearts.keys`) | `src/providers/codearts/`, `src/cli/commands/provider/codearts-login.js`, `src/providers/{registry,classify,share-keys}.js` | `-provider codearts login/models`, `MSLXDFF_CODEARTS_*` |
417
+ | 0028 | traework 供应商(TRAE SOLO CN 免费对话通道) | `traework/<modelId>` 前缀(local-only 不借出 key,NEVER_SHARE_IDS + classify 双保险);IDE 登录态凭据:`auths/trae-<uid>.json`(0600 tmp+rename,machine/device id 随账号)+ state `providerConfigs.traework={baseUrl,keys,auths}` 平行数组双写;对话恒 `stream:true` 走 `llm_utils_chat`(SOLO 头 Cloud-IDE-JWT + 15 指纹头,payload 改写 function/config_name/tool_choice/tools),SOLO SSE → OpenAI SSE 透传/聚合;错误分级 1005 plan 长冷却 12h / 401 session_dead 禁用换号 / 429 短冷(同请求轮转 3 号);过期前 24h 预刷新(ExchangeToken 毫秒→秒归一,refreshToken 单次轮换);模型 `get_detail_param` 动态(10min 缓存)+ 静态 32 回退,映射空/auto→`glm-5.2`;login 复刻 traework2api login.sh(`-provider traework login`:授权链接→粘贴回调→ExchangeToken→落盘+自动签到+查积分) | `src/providers/traework/`, `src/cli/commands/provider/traework-login.js`, `src/providers/{registry,classify,share-keys}.js`, `src/runtime/providers-setup.js` | `-provider traework login/models`, `MSLXDFF_TRAEWORK_*`, `TRAWEWORK_AUTH_DIR` |
418
+ | 0029 | qoder 供应商(Qoder 上游原生直连) | Qoder 免费模型池反代(15 模型,qfmodel=Qwen3.8-Flash 等):`qoder/<key>` 前缀(local-only 不借出 key);自研 COSY 栈(自定义 base64 编码 + RSA PKCS1v15/AES-CBC 会话 + md5 五段签名 + 信封 SSE 解析),零桥依赖;OAuth PKCE 设备授权 login(`auths/qoder-<uid>.json` 0600 + state 双写,多号 keyring 轮转,默认 `allowAnyModels=true`);对话恒上游 `stream:true`(客户端真流式边收边吐,ADR-0031);模型映射 `mapModel`(auto/空→`qfmodel`) | `src/providers/qoder/`, `src/cli/commands/provider/qoder-login.js`, `src/providers/{registry,classify,share-keys}.js` | `-provider qoder login/models`, `MSLXDFF_QODER_*` |
419
+ | 0030 | `/v1/models` 目录按勾选集裁剪 | `modelPicks` 非空即作为 `GET /v1/models` 对外白名单(勾选即目录),裁剪点在能力富化(ADR-0022)之后、`models:list` 插件 hook 之前,纯函数 `filterByPicks`;空勾选=不裁剪(否则 `pick clear` 会让下游零模型可用)、`?all=1` 绕过(`-models` 交互候选取数恒走此口,防取消勾选成单向棘轮)、不看健康状态(目录=授权面,`modelErrors` 不参与其中);不写进 `providerConfigs.<id>.allowedModels`——后者是供应商准入安全阀(空即 `403`),与用户偏好是两个所有权 | `src/routes/models-route.js`, `src/models.js`, `src/cli/commands/model/{live-models,list-live}.js` | `GET /v1/models[?all=1][&raw=1]`, `-models`, `-model pick/unpick/picks/clear` |
420
+ | 0031 | qoder 对话改真流式 + auth 错误透传 | `stream:true` 走上游帧到达即转发的真流式管线(`stream.js` 的 `reshapeQoderStream`:`reader.read()` 循环 + 即时 `enqueue`,不再"读完攒 `deltas[]` 再回放";上游 usage 只在尾帧,仍附 finish chunk 一次发出),`stream:false` 才聚合(`aggregate.js` 的 `aggregateQoderStream` + `toCompletionJson`);两条管线共享 `request.js` 的 COSY 装配口(单一签名来源,防两处漂移)与 `sse.js` 的 `extractDelta`;顺带补 `errorStatus` 的 `auth` 支——上游 401/403 此前被标 `kind:"auth"` 却落兜底 502,客户端会把"device token 失效"误判成上游故障无限重试,现透传 401/403(对齐 `qoder/index.js` 无账号返 401 `auth_error`) | `src/providers/qoder/{chat,request,stream,aggregate,sse}.js`, `test/qoder-provider.test.js` | `-provider qoder login`, `MSLXDFF_QODER_TIMEOUT_MS` |
421
+ | 0032 | 通用供应商 responses 通道 | 通用带 key 供应商(ocgo/bai/...)此前一律打 `chatPath`(`/chat/completions`),而 `muse-spark*` 这类 responses 类模型在同 host 只挂 `/responses` → `503 {"error":{"type":"server_error","message":"Upstream request failed: Endpoint is unavailable."}}`(ocgo 实测 2026-09-22;同 key 的 `mimo-v2.5` 在 `/chat` 正常,证明 key 有效)。判定复用既有单一来源 `isResponsesModel`(models.dev 模型级 `provider.npm` + `muse-spark` 前缀兜底,与 `/v1/models` 的 `capabilities.upstreamApi` 同源),新模型无需改码;命中即改打 `<baseUrl>/responses`(`MSLXDFF_<ID>_RESPONSES_PATH` 可覆盖)。通道出参恒为 chat 形状:流式优先 `@ai-sdk/openai` 的 responses 适配器(复用 ADR-0017,含加密思考往返与 `_sdkLoadFailed` 回退),非流式与 SDK 不可用时走原生 `chatToResponsesBody` 正转换 + `toChatResponse`/`reshapeResponsesSse` 反向整形;key 轮换、退避重试、全 key 冷却短路与 `_t` 计时对齐 `createChatRunner`,避免两条通道行为漂移。`src/providers/generic.js` 因此瘦回 <10KB(先拆后写:`responses-channel.js` 独立深模块) | `src/providers/{generic,responses-channel}.js`, `test/generic-responses.test.js` | `MSLXDFF_<ID>_RESPONSES_PATH`, `GET /v1/models` 的 `capabilities.upstreamApi` |
422
+ | 0036 | qoder 同请求粘号(切号只由冷却触发) | qoder 两个账号分属 cn/global 两区(region 决定 URL),而 `pickSession()` 每次上游调用都 `ring.next()` → 空转重试(`EMPTY_MODEL_RESPONSE` 同模型重试)会把同一客户端请求甩到另一个区,日志表现为"URL 莫名在切"、排障无法归因。改为**同请求粘号**:新模块 `sticky.js` 按 scope(管线传入的 `reqId`)复用上次选中的号,仅当该号处于冷却(401/403/429/5xx → `ring.onError`)才换下一个;无 scope 时退回原 round-robin,旧行为不变。`keyring.js` 对外暴露 `isCooling` 供选择器判定;管线在 `chatOpts.reqId` 传请求身份。同轮修**流式坏号冷却**(流式把上游非 200 整形成「200 + 流内 error」→ 门面看不到真实状态码,坏号永不冷却,而粘号又会把重试继续粘在它上面;改由内部头 `x-mslxdff-qoder-upstream-status` 带出,fetch 异常折 502) | `src/providers/qoder/sticky.js`, `src/providers/qoder/index.js`, `src/providers/qoder/chat.js`, `src/providers/keyring.js`, `src/chat-pipeline/serial-trial.js` | `MSLXDFF_QODER_STICKY_MS`(600000, 0=关)、`test/qoder-sticky-account.test.js`、`test/qoder-cooldown-stream.test.js` |
423
+ | 0037 | qwenwork(千问办公)作独立 provider,而非 qoder 的新 region | 初判「像 qoder 的另一个域名」(同 OAuth client_id、同 COSY 算法、同路径形状),现网取证推翻:RSA 模数不同、cosyVersion/clienttype/scene 不同、请求体纯 JSON(无 `Encode=1`)、模型池零交集(`flash/pro/qwen3.8-max-preview` vs `gmodel/qfmodel`)、官方口径「两条产品线 Credits 相互独立不互通」。判据:**region 承载「同一租户不同站点」,租户换了必须换 provider id**。隔离落四处:账号池/allowlist/CLI 语义/额度渲染。登录默认 `allowAnyModels=false` + 只种 3 实测模型(与 qoder 惯例故意不同:每日回血未实测,防 auto 烧分) | `src/providers/qwenwork/`, `src/cli/commands/provider/qwenwork-login.js`, `src/providers/{registry}.js`, `src/runtime/provider-gate.js` | `-provider qwenwork login/models`, `MSLXDFF_QWENWORK_*` |
424
+ | 0034 | 请求级人读可观测性(timeline.log + 按模型链路日志) | 排障证据原本散在 `events.log`(JSON 按时间混排)与 `calls/errors.log`(成败计数),无法直接看某模型的完整链路。新增两个互补人读面并同源于 `ChatPipeline` evt 流投影:`timeline.log` 每请求一行汇总(直连/组员/重试/结果/总耗时)、`logDir/<provider>-<model>.log` 按阶段记录链路;不落 prompt/正文/headers/凭据,同步 append 保证行序,单文件 1MB 保留最近 100 行。同轮让四个失败收尾分支补 `client-response`,使 `result` 与 `client-response` 条数可对账 | `src/timeline.js`, `src/model-trace.js`, `src/logs.js`, `src/chat-pipeline/index.js`, `src/routes/chat/exhausted-handler.js` | `mslxdff -log`(同时打印时间线与模型日志路径)、`-uninstall` 清理 `timeline.log` |
425
+ | 0035 | SDK 通道 headers 超时(防挂死) | `attemptOnceSdk` 对 `doStream` 是裸 `await`,上游连接半死时该 promise 永不 resolve,请求静默悬空且无任何事件记录(实测 cline muse-spark 悬空 27min+)。加 headers 阶段显式闸门:默认 120s(与末位候选耐心档同量级),`MSLXDFF_SDK_HEADERS_TIMEOUT_MS=0` 关;`AbortController` 中止底层连接;错误文案刻意避开 "timed out"(cline runChat 按该子串重试会放大挂死 3 倍) | `src/upstream-engine/sdk/attempt.js`(`headersTimeoutMs`/`withHeadersTimeout`), `test/sdk-headers-timeout.test.js` | `MSLXDFF_SDK_HEADERS_TIMEOUT_MS`;`sdk/responses.js` 未覆盖,待评估 |
426
+ | 0038 | zcode 供应商(智谱 ZCode 官方工作台免费额度) | 新上游:ZCode(zai-org/ZCode 开源客户端)绑定智谱账号的 Start/Coding Plan 免费额度,经官方 `zcode.z.ai` 网关接出。**出站选中 Anthropic 协议** `POST /api/v1/zcode-plan/anthropic/v1/messages`(OpenAI 形 base 在官方源码只有 URL 定义、无调用方,社区实现亦全走 Anthropic 形)→ 新增 OpenAI→Anthropic 请求转换 + Anthropic SSE→OpenAI chunk 整形(上游恒 `stream:true`,非流式本地聚合;`pull` 必须循环读到有产出,否则首个分片落在事件中间时消费端 `read()` 永不 settle——实测已复现并修复)。**登录用 CLI 轮询**(`oauth/cli/init` → 浏览器授权 → `oauth/cli/poll` 拿 JWT;zai/bigmodel 双入口;无需本地回调服务器/协议注册)。**免验证码边界**:官方客户端 7060 文件全量 grep 无验证码代码,登录/模型调用/额度查询均无阿里云验证码,验证码仅出现在 `billing/claim`(领取套餐)——本期明确不做领取、激活上报与闲时任务。**deviceMid 策略**:per-account 持久 UUID 存 `auths/zcode-<uid>.json`(0600),不进 providerConfigs(规避重建丢字段的既有故障类)。**冷却分级**:401/1006 短冷 30s + 401 重登指引、1005 长冷 1h + 429 `quota_exhausted`、3002/3008/3009/3010 短冷 429、3007 不冷却 403 直透;失败响应带 `x-mslxdff-zcode-kind` 内部头供工厂决策。**allowlist 开箱**:登录成功后内置目录 canonical id 只增不减并入 `allowedModels`(对齐 cline allowlist-sync 先例,防「登录成功却处处 403」)。版本常量 `MSLXDFF_ZCODE_APP_VERSION`(默认 3.11.2)可覆盖(UA 门禁教训)。**恒 local-only**:本机账号绑定型(JWT + deviceMid),加入 `NEVER_SHARE_IDS` 与 `classify.js` local-only,不借出 key、不走组员转发 | `src/providers/zcode/{index,chat,sse,headers,auth,oauth,const,models,quota,account-store}.js`, `src/cli/commands/provider/{zcode-login,zcode-quota}.js`, `src/providers/{registry,classify,share-keys}.js` | `-provider zcode login/quota/models`, `MSLXDFF_ZCODE_*` |