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,133 @@
1
+ # mslxdff CLI 精简手册(AI 专用)
2
+
3
+ > 给 `mslxdff -chat` 的大模型看。含全部可用命令的**精确语法**,模型必须照此输出,禁止自创参数。人类详版见 `cli_help.md`。
4
+
5
+ ## 约定
6
+
7
+ - 单/双横线等价:`-status`=`--status`,`-help`=`--help`/`-h`。
8
+ - 所有命令前缀 `mslxdff`,工具调用时传 `command` 字段,值形如 `"-model set big-pickle"`(不含 `mslxdff` 前缀,执行侧自动补)。
9
+ - `read_file` 仅限项目内:相对路径 `src/...`、`docs/...`、`package.json`、`logs` 等;绝对路径必须在 `D:\www\wwwroot\mslxdff` 或 `~/.config/mslxdff` 下,否则拒绝。
10
+ - 禁止执行的唯一命令:`-uninstall` / `--uninstall`(任何包含此的是拒绝)。
11
+ - 模糊匹配由你完成:用户说 `hy3` 你必须查模型列表找到 `hy3-free` 再输出,全称以实时 `可用模型` 为准。
12
+
13
+ ## 可用命令全表
14
+
15
+ | 命令 | 语法 | 说明 |
16
+ |---|---|---|
17
+ | 无参启动 | `mslxdff` | 已有 daemon 显示 status,否则后台启动 |
18
+ | daemon | `-d` / `--daemon` | 后台启动(只升不降,低版本不覆盖高版本) |
19
+ | 状态 | `-status` / `--status` / `-s` | 打印 daemon/health/port/config、upstream providers(启用/key/baseUrl/allowlist/共享)、models(含 v0.1.59 体检表 avg首字/tps/啰嗦/p95)/群组/failover/recent calls(ts/model/status/dur)/last error/autostart/plugins — 全量聚合体检 |
20
+ | 用量报表 | `-stats` / `--stats` `[--hours N] [--json] [--model <id>]` | 近 N 小时(默认24,上限168)模型用量;默认输出 `Token 用量`(请求/输入/输出/思考/合计)与 `响应性能`(首字/总耗时/加权速度)两张自适应边框表并含合计,长模型 id 不截断;大 token 用 k/M 缩写,`--json` 为精确值。速度按窗口加权 `Σ输出÷Σ生成耗时`,只计成功请求;不含失败请求、`-chat` 直连,也不展开逐请求 via/interrupted/单次 tps;查询超过保留期会警告可能不完整 |
21
+ | 日志 | `-log [N]` / `--log [N]` / `-logs N` | 最近 N 条事件 + `timeline.log` 人读时间线;按模型链路日志为 `logDir/<provider>-<model>.log`,逐阶段记录 request/route/upstream/peer/relay/result(安全摘要,不落 prompt/正文/凭据)。**事件面用黑名单**:默认全部可见,只排除 `peer-health`/`heartbeat`/`client-session`/`upstream-probe*`;决定类事件只渲染登记字段,含上游回显 `upstream=`/`account=`/`pick=`/`cooled=` |
22
+ | 调试 | `-debug` / `--debug` | 前台跟随事件流,Ctrl+C 恢复后台 |
23
+ | 插件 | `-plugins` / `--plugins` | 列插件与 hooks |
24
+ | 停止 | `-stop` / `--stop` | 停 daemon |
25
+ | 重启 | `-restart` / `--restart` | 重启 daemon |
26
+ | 端口 | `-port N` / `--port N` | 持久化端口,运行中重启 |
27
+ | token 读 | `-showtoken` / `--showtoken` | 打印 Bearer token |
28
+ | token 刷 | `-refresh-token` / `--refresh-token` | 轮换并打印新 token |
29
+ | 更新 | `-update` / `--update` | 更新到 npm latest |
30
+ | 模型交互 | `-models` | TTY 交互多选勾常用模型(↑↓移动 ←→翻页 Space勾选 Enter保存,勾选框在名字前 `❯ [✓] <id>`;候选池=opencode免费池+已启用供应商allowlist原名/别名+已勾选+allowAny空白名单者的网关live模型,不存在/未启用provider不列出,启用后回来);**勾选集即 `GET /v1/models` 对外目录**(非空只暴露勾选项,空=全量,`?all=1` 绕过取全量) |
31
+ | 模型列表 | `-model list [--provider <id>] [--json]` | 列免费模型:默认先列 opencode 免费池,`────────────────────────────────────────` 分隔后列其他供应商 allowlist(原名 + 别名 `别名: dash`);`--provider cline` 只看该供应商 allowlist,`--json` 输出 `{"object":"list","data":[...]}` |
32
+ | 模型设默认 | `-model set <id>` | 设首选模型,自动入 picks |
33
+ | 模型健康 | `-model status` | 每模型 normal/limit/error |
34
+ | 模型刷新 | `-model refresh` | 强制拉上游刷新 |
35
+ | 模型勾选 | `-model pick <id>` | 勾选入 picks |
36
+ | 模型去勾 | `-model unpick <id>` | 从 picks 移除 |
37
+ | 模型查勾 | `-model picks` | 列 picks |
38
+ | 模型清空 | `-model pick clear` | 清空 picks |
39
+ | 模型探活(必用 curl) | `curl` POST `http://localhost:8989/v1/chat/completions` body `{"model":"<id>","messages":[{"role":"user","content":"hi"}]}` | 测试指定模型是否通,**禁止** `mslxdff "hi" --model X` / `mslxdff --model X "hi"` 等幻觉命令 |
40
+ | 供应商新增 | `-provider add <id> <baseUrl> <key> [allowedModel...] [--models-path <path>] [--chat-path <path>]` | 一键添加通用 OpenAI 兼容供应商(末尾可带白名单;默认 `allowAny OFF` 空名单=禁用,需 `allowlist set` 或 `allowAny on` 否则 `403`;`workbuddy`除外;`--models-path`/`--chat-path` 可配异形路径如 `/v1/models`) |
41
+ | 供应商 | `-provider <id> [keys]` | 批量设 keys(覆盖) |
42
+ | 供应商增 | `-provider <id> add <key>` | 追加单 key |
43
+ | 供应商删 | `-provider <id> remove <seq\|key> [more]` | 按序号或值删,逗号/空格均可 |
44
+ | 供应商列表 | `-provider <id> list` / `status` | 脱敏列 keys/baseUrl/共享(`codearts`/`cline` 等硬排除显示"不借出";codearts 每条 key 带 user/uid/domain 摘要) |
45
+ | 供应商模型 | `-provider <id> models [--json]` | 列该供应商可用模型(按 allowlist 过滤,`workbuddy/xxx` 前缀;`cline` 与聚合目录同源走 `recommended-models` 的 `free`+`clinePass`,**每次成功取数即把上游 id 自动并入 allowlist(只增不减,`MSLXDFF_CLINE_AUTOSYNC=0` 关)**,daemon 启动自检 free 增删写 `daemon.log`;`--json` 供脚本) |
46
+ | 供应商测速 | `-provider <id> bench [--json] [--prompt <text>] [--max-tokens N] [--timeout N]` | 仅测(allowlist ∩ 全局 picks)交集的速度(TTFB/总耗时/TPS),空则探活 `/v1/models→/models` 并提示先 pick;**deepseek 网页通道不支持 bench**(防禁言,改用 `-provider deepseek health` 体检) |
47
+ | 供应商选路 | `-provider <id> bench --via [--include-opencode] [--json] [--samples N] [--timeout N] [--apply]` / `-provider bench --via` | **家宽选路**:对比 `direct` vs 经每个在线 `peer` 的 `TTFB`(仅测 picks∩allowlist 交集,串行轻探针 `max_tokens=5`,`--json` 时进度走 `stderr`;默认跳过 `opencode`需 `--include-opencode`+TTY `y/N`;**deepseek 一律跳过**(防禁言);结果不写 state;空组直接引导;`--apply` 落盘 `via-routes.json` 供显式锁模型单路径择路) |
48
+ | Cline 登录 | `-provider cline login` | Cline WorkOS 设备授权拿 refreshToken 落盘;`cline` 走 `refresh→workos:token`+指纹头,deepseek 家族免 403(含 `cline-free/deepseek-*`;非流式内部强制 stream 聚合成 JSON,对外仍按请求方 stream);多账号重复 login 追加(同邮箱替换不追加,`list` 显示邮箱);直连 workos 被墙则 `set HTTPS_PROXY=http://127.0.0.1:7890` 后重试 |
49
+ | Cline 免费目录 | `-provider cline free [--json]` | 上游免费目录只读(`recommended-models` 的 `free`,5 个):列目录 + 与当前 allowlist 的差异,不写 state |
50
+ | Cline 免费同步 | `-provider cline free sync [--yes] [--json] [--keep-extra]` | 把免费目录同步为 `cline` 的 allowlist(写裸 id):**默认 dry-run,`--yes` 才落盘**;`--keep-extra` 只增不删;**全量替换**(会挤掉 daemon auto-sync 并入的 `cline-pass/*`);**上游不可达时拒绝写盘**(不把内置兜底落成白名单) |
51
+ | Cline 用量 | `-provider cline quota [--json] [--account <hash>] [--model <substr>]` | 账号×模型双口径只读统计(`cline-usage.jsonl`):free 显示本周期/已完成周期/累计,pass 显示近 24h/累计;`--json` 供脚本;空账本给引导 |
52
+ | Cline 迁移 | `-provider cline migrate [--dry-run]` | 旧 `providerConfigs.clinebot` 合并进 `cline`(keys 去重 + 剔 `sk_`、allowlist 求并)后删旧键,幂等、改前备份;**cline 恒 local-only**(不走组员、不借 key、只走本地直连,历史别名同样硬排除) |
53
+ | CodeArts 登录 | `-provider codearts login` | 华为云 CodeArts Agent(盘古助手)PKCE 浏览器授权:凭证 blob(refreshToken/codeVerifier/dpopJwk)落盘 `providerConfigs.codearts.keys`(一账号一 blob,多账号 keyring 轮转,默认 `allowAnyModels=true`);此后 `codearts/<modelId>` 前缀(恒 `stream:true`,STS 临期自动刷新 + refresh_token 轮换原位写回,死号提示重登);**恒 local-only** 不借 key(ADR-0027) |
54
+ | CodeArts 模型 | `-provider codearts models [--json]` | 三路发现(builtin 归一 + 代理型 + 福利网关),benefit 模型自动 claim(幂等 `0000`),对外带 `tags:["free:benefit"]` |
55
+ | TraeWork 登录 | `-provider traework login` | TRAE SOLO 通道浏览器授权(复刻 traework2api login.sh):打印 trae.cn 授权链接 → 登录后粘贴 `127.0.0.1` 回调链接 → ExchangeToken → 落盘 `auths/trae-<uid>.json`(0600)+state 双写,自动签到+查积分;此后 `traework/<modelId>` 前缀(恒 `stream:true` SOLO SSE 透传/聚合,模型空/auto→`glm-5.2`,动态表+静态 32 回退;1005 plan 长冷却 12h、401 换号、429 短冷,过期前 24h 预刷新);**恒 local-only** 不借出 key |
56
+ | DeepSeek 登录 | `-provider deepseek login --token <userToken>` 或 `login <email\|mobile> <password>` | DeepSeek 官网免费对话接入(ADR-0014):userToken 在 chat.deepseek.com F12→Local Storage;无参数打印图文引导;落盘后 `allowAny on` + `-restart`;模型 `deepseek/{chat,reasoner,chat-search,reasoner-search}`;单账号 1 路并发,多号轮换 |
57
+ | DeepSeek 探活 | `-provider deepseek health [--json]` | 逐账号体检(防禁言):检测禁言(自动冷却 5min)/限频前兆/凭据坏;网络失败不误伤;禁言解封后再探自动恢复 |
58
+ | 供应商改址 | `-provider <id> set-url <baseUrl>` | 改通用供应商地址 |
59
+ | 供应商改模型路径 | `-provider <id> set-models-path <path>` | 改 `models` 路径(如 `/v1/models`、`/console/enterprises/personal/models`) |
60
+ | 供应商改对话路径 | `-provider <id> set-chat-path <path>` | 改 `chat` 路径(如 `/v1/chat/completions`、`/v2/chat/completions`) |
61
+ | 供应商清空 | `-provider <id> clear` | 清空该供应商 keys |
62
+ | 供应商共享 | key 随转发自动借出(ADR-0019,无开关无白名单) | 借道时自动附带;opencode/workbuddy/cline/codearts/traework 硬排除 |
63
+ | 供应商白名单 | `-provider <id> allowlist [list\|set\|add\|remove\|clear]` | 白名单空=阻塞除非 `allowAny on`,非空仅名单内可用(防昂贵模型) |
64
+ | 空名单开关 | `-provider <id> allowAny on\|off` | 空 allowlist 时放行或阻塞(默认 `OFF`,`opencode` 例外 `ON`) |
65
+ | 供应商总览 | `-providers list` / `-provider list` | 列所有已部署供应商及启用状态(含 allowlist 摘要) |
66
+ | WorkBuddy 接入(禁手填) | `禁止直接 -provider add workbuddy` | 用户说“接入/配置/登录 workbuddy / key哪里来 / 怎么用workbuddy”时**禁止**让用户手填`<key>`或执行`-provider add workbuddy ...`,必须**直接文字回复**(不调工具):`请在项目根目录执行 node workbuddy-token-auto.js(前提已装 WorkBuddy 5.3.14 并浏览器登录 https://copilot.tencent.com 一次),脚本会自动抓 token、写 auths/workbuddy-*.json + state.json、自动签到`;仅当用户已贴出以`eyJ`开头的JWT时才可用`-provider add workbuddy https://copilot.tencent.com <贴的key>` |
67
+ | WorkBuddy 桌面导入 | `mslxdff -provider workbuddy import [--file=路径]` | 桌面已登录新号时最快:跨平台自动发现登录态(找不到时 `--file`/env `MSLXDFF_WORKBUDDY_DESKTOP_INFO` 显式指定),无需浏览器/抓包 |
68
+ | WorkBuddy 多号追加(路径A) | `mslxdff -provider workbuddy login` | 用户说“追加/添加 workbuddy 账号/多号/再加一个号”时**必须走路径A**:`run_command: "mslxdff -provider workbuddy login"`(设备授权:打印浏览器链接→用户用新账号登录→自动轮询落盘 `auths/workbuddy-<newUid>.json` + `state.json keys/auths`,不走抓包,桌面端不用退旧号)③ `run_command: "-workbuddy list"` 验证多号 ④ `run_command: "-workbuddy balance"` 看余额;新号次日自动纳入 daemon 每日签到;抓包兜底路径B:`node workbuddy-token-auto.js --force`(需桌面先切新号登录);**禁止**让用户手贴 JWT(除非用户主动贴 `eyJ` 则走 `-provider add workbuddy` 路径C) |
69
+ | WorkBuddy 签到 | `-workbuddy checkin` / `-wb checkin` | 用户说“签到/每日签到/100积分/领积分”时**调用 run_command**;多号并行3,双域幂等 `code 10001 已签到`视为成功,`--json` 聚合余额;daemon 每日 09:00 自动全号签到(`MSLXDFF_WORKBUDDY_CHECKIN=0` 关,`_HOUR` 改时间) |
70
+ | Qoder 签到 | `-provider qoder checkin [--json] [--region cn\|global] [--any] [--dry]` | 用户说“qoder 签到/领积分”时**调用 run_command**;**按每号 region 选域名**(cn=`openapi.qoder.com.cn` 走 `daily-check-in`,409=今日已领;global=`openapi.qoder.sh` 无该端点→回落 campaigns),默认只领 `CLAIM_BENEFIT`(`--any` 才含 VIEW_DETAILS 促销),`--dry` 只查不领;daemon 每日 09:00 自动(`MSLXDFF_QODER_CHECKIN=0` 关,`_CHECKIN_HOUR` 改时间) |
71
+ | Qoder 接入 | `-provider qoder login [--region cn\|global]` | Qoder 设备授权(PKCE+poll):打印兑换链接→浏览器登录→落盘 `auths/qoder-<uid>.json`+state;`--region cn` 走国内站 `qoder.com.cn`(默认国际站);多号重复 login 追加,`qoder/<modelId>` 前缀路由,恒 local-only 不借出 |
72
+ | 千问办公接入 | `-provider qwenwork login` | 千问办公设备授权(PKCE+poll,`gateway.qwenwork.cn`):打印兑换链接→浏览器确认→落盘 `auths/qwenwork-<uid>.json`+state;**默认 `allowAnyModels=false` + 只种 `flash/pro/qwen3.8-max-preview`**(额度是账号积分池,防 auto 烧分);登录尾部显示套餐名+积分剩余;`qwenwork/<modelId>` 前缀路由,恒 local-only 不借出 |
73
+ | zcode 接入 | `-provider zcode login [--bigmodel]` | ZCode(智谱官方编程工作台)免费额度授权:打印 chat.z.ai 授权链接→浏览器登录→CLI 轮询(`oauth/cli/init`→`poll`)拿 JWT,落盘 `auths/zcode-<uid>.json`+state 并把内置目录(`GLM-5.3`/`GLM-5.3-Flash`/`GLM-5.2`/`GLM-5-Turbo`)并入 allowlist;`zcode/<modelId>` 前缀路由(Anthropic 网关,恒上游 stream,**免验证码**);`--bigmodel` 切 bigmodel.cn;恒 local-only 不借出 |
74
+ | zcode 额度 | `-provider zcode quota [--json]` | 查 ZCode 套餐名/状态/有效期 + 各模型余量(`billing/balance` 分组表格);空套餐给领取指引、401 给重登指引 |
75
+ | WorkBuddy 成长任务 | `-workbuddy growth [--json]` / `-wb growth` | 成长任务全自动(拉列表→参与→触发→领奖,串行 1.2s/1s,已领幂等跳过);可自动 `chat_5`/`automation_1`/`skill_1`/`Model_chat_GLM5.2`,需客户端任务标 MANUAL 不发包;daemon 每日 09:30 自动(`MSLXDFF_WORKBUDDY_GROWTH=0` 关,`_GROWTH_HOUR`/`_GROWTH_MODEL` 可配) |
76
+ | WorkBuddy 猫猫旅行 | `-workbuddy travel [--json]` / `-wb travel` | 无猫自动同意协议+领养(+300,门槛未达自动补一次对话解锁);到站领奖 / 空闲派出(location 4)/ 旅行中跳过 |
77
+ | WorkBuddy 余额 | `-workbuddy balance [--json]` / `-wb balance` | 查多号余额(`total/dailyPacks/nextExpire`,TTL 5min) |
78
+ | WorkBuddy 列表 | `-workbuddy list` / `-wb list` | 列账号(`uid/domain/enterpriseId`) |
79
+ | WorkBuddy SDK 通道(缺省启用) | `MSLXDFF_WORKBUDDY_SDK`(未设置则继承 `MSLXDFF_UPSTREAM_ENGINE`) | 底层缺省走 `@ai-sdk/openai-compatible`(optionalDependencies,需 Node>=18,不可用自动回退原生);设 `legacy`/关闭词回退原生 transport,上层轮换/刷新/reshape 不变 |
80
+ | 上游引擎(默认,ADR-0017) | `MSLXDFF_UPSTREAM_ENGINE`(缺省 `sdk`) | opencode 流式 chat 走 `@ai-sdk/openai-compatible`、`muse-spark*` 走 `@ai-sdk/openai` 的 responses 适配器(响应标记头 `x-mslxdff-upstream-engine: sdk`,复用 legacy 连接池);通用 OpenAI 兼容族与 cline 同源(供应商级 `MSLXDFF_<ID>_SDK` 未设置即继承本变量);非流式自动委派 legacy,SDK 不可用回退并告警;显式 `legacy`/关闭词回退原实现 |
81
+ | responses 类模型路由(ADR-0032) | `MSLXDFF_<ID>_RESPONSES_PATH`(缺省 `/responses`) | 通用带 key 供应商遇 responses 类模型(`muse-spark*` 等,判定与 `/v1/models` 的 `capabilities.upstreamApi` 同源)自动改打 `<baseUrl>/responses`(此前固定打 `chatPath` → 上游 503 `Endpoint is unavailable`);流式走 `@ai-sdk/openai` responses 适配器(含加密思考往返),非流式/SDK 不可用走原生 `chatToResponsesBody` 转换,出参恒 chat 形状;异形上游用该 env 覆盖路径 |
82
+ | SDK 通道 headers 超时 | `MSLXDFF_SDK_HEADERS_TIMEOUT_MS`(缺省 `120000`) | 防 SDK 通道挂死:`doStream` 到点仍未返回响应头即判挂死抛错(由调用方回退/换路),`0`=关闭;错误文案**不含 "timed out"**(避免 cline runChat 按文案重试放大挂死) |
83
+ | 免费层形状门禁(ADR-0020) | `MSLXDFF_FREE_LANE`(缺省 1)/`MSLXDFF_FREE_LANE_DEBUG=1` | zen 免费模型必须 `stream:true` + tools 含 bash/edit/glob/grep/read 五名且 UA≥`opencode/1.18.0`(否则 403/426);`src/free-lane.js` 自动补形状、非流式聚合回 JSON;`0`=关(逃生阀),DEBUG 打 `[free-lane]` 日志 |
84
+ | WorkBuddy 摘除 | `-workbuddy remove <uid> [--keep-file]` / `-wb remove` | 按 `uid`(前缀6位)摘除,删 `keys/auths` 与 `auths/workbuddy-<uid>.json` |
85
+ | 定号消耗 | `header x-mslxdff-workbuddy-uid: <uid>` 或 `model workbuddy/<uid>:<model>` | 钉死指定账号消耗,`x-mslxdff-workbuddy-uid` 回显实际账号 |
86
+ | 同步 WB | `-setto workbuddy [modelId]` | 同步到 WorkBuddy(原子写 `~/.workbuddy/models.json`,`127.0.0.1/v1`,多模型累积;picks 非空时摘除失效本地条目) |
87
+ | 同步 opencode | `-setto opencode [modelId\|--all]` | 把本地网关注册为 opencode 供应商(`provider.mslxdff`,`http://127.0.0.1:<port>/v1`,直写裸名如 `deepseek-v4-flash-free`,`/`→`-` 如 `bai/deepseek`→`bai-deepseek` 到 8989 自动还原,`--all` 批量同步全部 picks;picks 非空时摘除失效模型;自动附模型能力:models.dev 目录 + workbuddy 走上游原生字段,opencode 原生识别;写 variants 档位供 ctrl+t 切换(effort 型按档位写,toggle 型不写,TUI 改配置后需重启)) |
88
+ | 同步 chatgpt | `-setto chatgpt [modelId]` | 写 Codex 三端共用 `~/.codex/config.toml`(`model_providers.mslxdff` → `127.0.0.1/v1/responses`,鉴权走 `mslxdff -showtoken` 不落盘),换模型重跑 setto 或 `codex exec -m <id>` 单次覆盖,`codex exec "hi"` 验证;排障 `MSLXDFF_RESPONSES_DEBUG=1` 看 daemon.log `[responses]` |
89
+ | 建组 | `-creategroup <name>` / `-group create <name>` | 建组,本机为 leader |
90
+ | 加组 | `-addtogroup <host> <name> [--broadband]` | 加远端组,broadband 走中继(不占端口纯出站);**零参数/单参数进手机宽带向导**:问组长地址+组名,自动起服务,回报出口 IP |
91
+ | 组同步 | `-group sync` | 刷新全组成员 |
92
+ | 组离开单 | `-group leave <name>` | 离开单组 |
93
+ | 组列表 | `-group list` | 列组+成员+健康/序号 |
94
+ | 组踢人 | `-group remove <seq>` | 仅 leader 按序号踢人 |
95
+ | 全部离开 | `-leavegroup` / `--leavegroup` | 离开所有成员组 |
96
+ | 解散组 | `-delgroup <name>` / `--delgroup` | 仅 leader 解散 |
97
+ | 解封禁 | `-resetban [ip]` / `--resetban [ip]` | 清加组封禁 |
98
+ | 组员开关 | `-use-group [on\|off]` / `--use-group [on\|off]` | opencode 失败时是否走组员(默认 on,off 则所有供应商仅本机;key 供应商默认恒直连,`MSLXDFF_USE_GROUP_KEYS=1` 可开回;cline/workbuddy 恒禁) |
99
+ | 白嫖雷达 | `-free` / `--free` / `-free-check` / `--free-check` | V2EX 单源白嫖雷达(`latest.json + hot.json` 按白嫖|限免|免费额度过滤) |
100
+ | 白嫖 watch | `-free-watch` / `--free-watch` | V2EX 白嫖雷达 watch(每 5 分钟轮询) |
101
+ | 自启开 | `-enable-autostart` / `--enable-autostart` | 开机自启(Windows 任务计划 / Linux systemd) |
102
+ | 自启关 | `-disable-autostart` / `--disable-autostart` | 关闭开机自启 |
103
+ | 自启状态 | `-autostart status` / `--autostart status` | 查看自启状态 |
104
+ | 时区 | `-timezone [set <tz>\|clear\|status]` / `-tz` | 时区配置,默认 `Asia/Shanghai`,可设 `UTC` 等(`MSLXDFF_TZ` 覆盖) |
105
+ | 帮助 | `-help` / `--help` / `-h` | 打印帮助 |
106
+
107
+ ## 模型说明
108
+
109
+ - 裸 id 如 `big-pickle` 走默认供应商 opencode;带前缀如 `bai/glm-5.3-flash`、`openrouter/google/gemma-3-27b-it:free`、`workbuddy/hy3`、`cline/z-ai/glm-5.3-flash` 走指定供应商。
110
+ - 实时可用模型由 `可用模型` 列表给出(已按供应商聚合,含 bai/ 等前缀),必须照列表精确输出。
111
+ - 查“某供应商有哪些模型”**优先用 CLI 直查**:`run_command: "-provider workbuddy models"` 或 `run_command: "-model list --provider workbuddy"`(表格含能力列:上下文/📷读图/🧠推理/🔧工具调用,`--json` 供脚本),或 `curl local/models` 后前缀过滤;查模型能力(推理档位/读图/上下文/价格)用 `curl local/models/capabilities?id=<模型id>`(opencode 默认源 models.dev;workbuddy 用 `?provider=workbuddy&id=<裸id>` 走上游原生字段、全量含 blocked,ADR-0016);**禁止**调 `-provider workbuddy list`(这是查配置,不是查模型!)。**错误示例**:`workbuddy有哪些模型` → 调 `-provider workbuddy list` → 错。**正确**:`run_command: "-provider workbuddy models"` 直接列 `workbuddy/` 前缀模型。严禁为此调用 `-showtoken`。
112
+
113
+ ## 工具调用规范
114
+
115
+ - 时机:用户意图明确需执行命令时,调用 `run_command`;需查看文件时调用 `read_file`;需探活网络/服务时调用 `curl`。
116
+ - `run_command` 参数:`command: "-model set hy3-free"`(不含 mslxdff 前缀);`-showtoken` 仅用户明确要求看 token 时才用,查模型/供应商禁止用。
117
+ - **严禁幻觉命令**:`mslxdff "hi" --model X` / `mslxdff --model X "hi"` / `mslxdff -chat --model X` 等**不存在**,一律禁止。探活模型**必须**用 `curl` POST 本机网关,见下一条。
118
+ - `read_file` 参数:`path: "src/logs.js"` 或 `path: "~/.config/mslxdff/events.log"`(项目内或日志目录)
119
+ - `curl` 参数:`url: "upstream"` / `"local/health"` / `"local/models"` / `"bai/models"` / `"https://api.b.ai/v1/models"`,可选 `method`/`headers`/`body`/`timeoutMs`;简写自动补全完整 URL,上游自动补头(含 UA `opencode/<semver>` + opencode 形状 session/request,zen 免费层门禁需要)、本机 /v1/* 自动带 token、已配置供应商(bai/openrouter 等)自动带对应 key
120
+ - **模型探活固定写法**:`curl` 工具 `url:"http://localhost:8989/v1/chat/completions"` `method:"POST"` `headers:{"Content-Type":"application/json"}` `body:'{"model":"<前缀/模型>","messages":[{"role":"user","content":"hi"}],"stream":false}'`(如 `cline/z-ai/glm-5.3-flash`、`workbuddy/hy3`);成功 `200 + x-mslxdff-via:local` 即通,`401` 代表本机 token 失效需提示用户 `mslxdff -stop && mslxdff`,`403 + x-mslxdff-allowlist:1` 代表白名单未放行需 `allowlist add`,`429/5xx` 代表上游限流/故障。
121
+ - **禁止重复调用(最高优先级)**:同一 `run_command`/`curl`/`read_file` 在本轮只执行一次,重复会被 `SKIPPED_DUP` 拦截;**查询类(-showtoken/-status/-provider list/-providers list/-model list/-group list/-log 等)调用一次即答案**,拿到 `OK` 后必须**立即用中文直接回答**,禁止再调同类命令。收到 `SKIPPED_DUP` 或“请直接回答”时必须 0 工具直接回答。
122
+ - 一次一工具,执行后看结果再决定下一步;拿到工具结果后优先直接回答,不要无故再调。
123
+
124
+ ## 示例
125
+
126
+ - 用户:`设置hy3为默认模型` → 你先查可用模型确认 `hy3-free` 存在 → `run_command: "-model set hy3-free"`
127
+ - 用户:`看看最近日志` → `run_command: "-log 20"` 或 `read_file: "logs"` 视情况
128
+ - 用户:`查看组列表` → `run_command: "-group list"`
129
+ - 用户:`把 deepseek 加到 opencode` → 先查可用模型确认 `deepseek-v4-flash-free` 全称 → `run_command: "-setto opencode deepseek-v4-flash-free"`(存 `deepseek-v4-flash-free`,选 `mslxdff/deepseek-v4-flash-free` 直达)
130
+ - 用户:`把 bai 模型加到 opencode` → 确认 `bai/deepseek-v4-flash` → `run_command: "-setto opencode bai/deepseek-v4-flash"`(存 `bai-deepseek-v4-flash`,到 8989 自动还原 `bai/deepseek-v4-flash`)
131
+ - 用户:`把所有模型同步到 opencode` → `run_command: "-setto opencode --all"`(批量 picks 全进菜单)
132
+ - 用户:`把当前模型同步到 opencode` → `run_command: "-setto opencode"`(无参取 preferredModel)
133
+ - 用户:`测试z-ai/glm-5.3-flash连通性` → **禁止** `run_command: "\"hi\" --model cline/z-ai/glm-5.3-flash"`,必须 `curl: {url:"http://localhost:8989/v1/chat/completions", method:"POST", headers:{"Content-Type":"application/json"}, body:"{\"model\":\"cline/z-ai/glm-5.3-flash\",\"messages\":[{\"role\":\"user\",\"content\":\"hi\"}],\"stream\":false}"}`
@@ -0,0 +1,215 @@
1
+ # 规划:家宽 A 直连 vs 经组员 B/C/D 借道 的上游延迟对比(bench-via)
2
+
3
+ > 状态:规划中(未落代码) | 作者:mslxdff | 日期:2026-09-01
4
+ > 关联:`AGENTS.md` 先拆后写、`docs/ARCHITECTURE.md` §5/§6/§7、`docs/adr/`、`src/bench/*`、`src/providers/dispatcher.js`、`src/routes/chat/*`
5
+
6
+ ---
7
+
8
+ ## 1. 背景与动机
9
+
10
+ - **场景**:机器 A 在家宽,无固定公网 IP,出口质量抖动;A 与 B/C/D 组成 mslxdff 群组(`src/routes/chat/peer*`、`src/routes/groups.js`)。用户想知道“**A 直连上游** vs **A 经 B/C/D 接力到同一上游**”谁更快,以便选最优出口。
11
+ - **现有能力**:`mslxdff -provider <id> bench` 已能测直连 TTFB/TPS(`src/bench/probe|runner|report`),但**不覆盖“经 peer 接力”**链路;群组接力已有(`peer/broadband`),但无对比视图。
12
+ - **核心矛盾**:`opencode` 免费池靠出口 IP 限额(`src/upstream.js:429→冷却+匿名重试`),**A 经 B 打 opencode 会消耗 B 的额度**,测一次亏一次。必须把“额度保护”做成一等公民,否则 bench-via 会把组员额度测没。
13
+
14
+ ---
15
+
16
+ ## 2. 目标(Goals)
17
+
18
+ 1. 一条命令给出**直连 vs 经每个在线组员**到各上游的延迟对比表,首屏可读,脚本可解析。
19
+ 2. 默认**不消耗组员的 opencode 额度**;显式才测 opencode,且有二次确认与最小化消耗。
20
+ 3. 复用现有 `bench` 与 `dispatcher` 链路,不引入新协议;结果**不污染** `state.json` 的 `latency EMA / preferred`。
21
+ 4. 失败隔离:某 peer 离线/超时不阻塞全表;空组/无在线 peer 有空状态引导。
22
+ 5. 体验闭环四态:`空→加载(进度)→成功(表格+建议)→失败(人话+重试)`。
23
+
24
+ ## 3. 非目标(Non-Goals)
25
+
26
+ - 不做持续后台探测/定时任务(首版仅按需触发)。
27
+ - 不改 `state.json` 的长期择优(`src/auto.js` 排序保持直连 EMA,不写 via 结果)。
28
+ - 不新增 peer 认证/计费;额度保护仅做提示与默认跳过。
29
+ - 不支持“多跳”(A→B→C→上游),仅一跳接力。
30
+
31
+ ---
32
+
33
+ ## 4. 用户故事
34
+
35
+ - **US1 - 快速选路**:作为 A 的主人,我执行一次对比就能看到 `direct 820ms | via B 310ms | via C timeout`,知道以后让 A 的默认出口走 B。
36
+ - **US2 - 额度不被误伤**:作为 B 的主人,我不希望 A 随便把我的 opencode 额度测掉;默认 via 不含 opencode,必须显式+确认才测。
37
+ - **US3 - 脚本化**:作为自动化脚本,我用 `--json` 拿到机器可读的对比结果,择优写入配置。
38
+ - **US4 - 组为空**:A 未加组时,命令直接告诉我“先加组”,而不是假装在测。
39
+
40
+ ---
41
+
42
+ ## 5. 现状与约束分析
43
+
44
+ - **Bench 现状**:`src/cli/commands/provider/bench.js` 调 `src/bench/{probe,runner,report}`,串行测已勾选 `allowlist` 模型,30s 超时,空 allowlist 则探活 `GET /v1/models→/models`。输出表格+`--json`。
45
+ - **群组现状**:`src/routes/chat/peer*` 已支持 `A→peer→upstream` 转发,带 `x-mslxdff-via`;`src/providers/dispatcher.js` 前缀路由,`share-keys` 默认排除 opencode(`ADR-0008`),`workbuddy` 默认 `share=off`。
46
+ - **文件体积约束**:`AGENTS.md` 要求 `src/**/*.js ≤10KB 愉悦、>20KB 必拆`,`npm run docs:check` 校验;`src/bench/*` 与 `src/cli/commands/provider/*` 均需保持在限额内,via 必须拆独立模块而非塞进 bench.js。
47
+ - **已验证风险**:`workbuddy` 多号 429 切号、`cline` 指纹头、`auto` 冷却 60s/慢 5min、`STREAM_TIMEOUT_MS 25s` 首块对冲 1s(`src/upstream.js`/`src/routes/chat/hedge.js`)。
48
+
49
+ ---
50
+
51
+ ## 6. 方案设计
52
+
53
+ ### 6.1 命令形态(CLI 契约)
54
+
55
+ > 归属:`docs/ARCHITECTURE.md §6 CLI 表` + `docs/cli_help.md` + `docs/cli_help_mini.md`(三处同步)
56
+
57
+ **首选形态(复用 bench,不新增顶级动词):**
58
+
59
+ ```bash
60
+ mslxdff -provider bench --via # 对比:direct vs 经每个在线 peer(默认跳过 opencode)
61
+ mslxdff -provider <id> bench --via # 只对比指定供应商(如 openrouter / workbuddy / clinebot)
62
+ mslxdff -provider bench --via --include-opencode # 显式把 opencode 纳入对比(需二次确认)
63
+ mslxdff -provider bench --via --json # 机器可读
64
+ mslxdff -provider bench --via --samples 1 --timeout 15000 # 可调样本/超时(默认 samples=1, timeout=30000)
65
+ ```
66
+
67
+ **备选(若 bench 参数拥挤):**
68
+
69
+ ```bash
70
+ mslxdff --bench-via [--provider <id>] [--include-opencode] [--json]
71
+ ```
72
+
73
+ > 决策:首选前者,保持“bench 家族”心智;若用户反馈参数过长,再补 `--bench-via` 别名。规划阶段两者都保留为兼容目标,定稿时二选一。
74
+
75
+ **参数表:**
76
+
77
+ | 参数 | 默认 | 说明 |
78
+ |---|---|---|
79
+ | `--via` | off | 开启“经 peer”对比;未加组时直接空状态退出 |
80
+ | `--include-opencode` | off | 显式才把 `opencode` 纳入 via;否则 via 仅测非 opencode 供应商 |
81
+ | `--provider <id>` | 全部已启用且 allowlist 非空的供应商 | 缩小对比范围 |
82
+ | `--samples N` | 1 | 每个路径的样本数(via 场景默认 1 以省额度,直连可 1-3) |
83
+ | `--timeout N` | 30000 | 单次请求超时 ms |
84
+ | `--json` | off | 输出 JSON(stdout 纯 JSON,进度走 stderr) |
85
+
86
+ ### 6.2 输出契约
87
+
88
+ **人类表格(TTY):**
89
+
90
+ ```
91
+ bench-via: direct vs via peers (samples=1, timeout=30s, opencode=skipped)
92
+
93
+ Provider Model direct via B(家) via C(云) best
94
+ openrouter google/gemma-3-27b:free 820ms 310ms★ 540ms via B -62%
95
+ workbuddy hy3 410ms 380ms — offline direct
96
+ clinebot deepseek-v3.2 610ms 590ms 720ms via B -3%
97
+
98
+ 建议:A 经 B 打 openrouter 最快;workbuddy 走 direct 即可。
99
+ 提示:via 已跳过 opencode(省额度),需对比 opencode 请加 --include-opencode
100
+ ```
101
+
102
+ **JSON(--json):**
103
+
104
+ ```json
105
+ {
106
+ "meta": { "at": "2026-09-01T02:00:00+08:00", "samples": 1, "timeout": 30000, "includeOpencode": false, "direct": "A", "peers": ["B","C"] },
107
+ "results": [
108
+ { "provider": "openrouter", "model": "google/gemma-3-27b:free", "direct": { "ttfb": 820, "total": 1100 }, "via": { "B": { "ttfb": 310 }, "C": { "ttfb": 540 } }, "best": "via:B", "deltaMs": -510 },
109
+ { "provider": "workbuddy", "model": "hy3", "direct": { "ttfb": 410 }, "via": { "B": { "ttfb": 380 }, "C": { "error": "offline" } }, "best": "direct" }
110
+ ],
111
+ "advice": "A via B for openrouter is fastest (-62%)"
112
+ }
113
+ ```
114
+
115
+ ### 6.3 架构与数据流
116
+
117
+ ```
118
+ CLI: src/cli/commands/provider/bench.js --via
119
+ ├─ 1) 解析组员:src/state.js groups/peers → 在线 peer 列表(probeHealth 1.2s)
120
+ ├─ 2) 解析待测集合:src/models.js listModels + provider allowlist 过滤
121
+ │ └─ 默认排除 opencode;--include-opencode 才纳入
122
+ ├─ 3) src/bench/via.js orchestrator
123
+ │ ├─ 对每个 (provider,model):
124
+ │ │ ├─ direct: src/bench/runner.js 直连打一次(复用 probe 的最小 prompt)
125
+ │ │ └─ via peer_i: 复用 src/routes/chat/* 的 “A→peer→upstream” 链路
126
+ │ │ └─ 经 dispatcher 剥前缀转发,peer 侧走现有 upstream 逻辑
127
+ │ └─ 汇总:best/delta,生成 report
128
+ └─ 4) src/bench/report.js 渲染(人类表 / JSON)
129
+ ```
130
+
131
+ **模块清单(先拆后写,≤10KB/文件):**
132
+
133
+ ```
134
+ src/bench/
135
+ probe.js 现有:探活
136
+ runner.js 现有:单模型 TTFB/TPS
137
+ report.js 现有:表格渲染(via 复用,新增 via 列渲染分支)
138
+ via.js 新增:via 编排(peer 发现→并发/串行调度→结果聚合),≤300 行
139
+ via-probe.js 新增:单次 via 探针(A→peer→upstream 的轻量 chat.completions,max_tokens=5),≤200 行
140
+ src/cli/commands/provider/
141
+ bench.js 改动:新增 --via/--include-opencode/--samples/--timeout 解析与二次确认
142
+ ```
143
+
144
+ > Mermaid Before/After(落代码前在 `.scratch/bench-via/MODULES.md` 补全):Before `bench.js 39KB 单文件` 已拆,现 via 再拆 `via.js+via-probe.js`,保持每文件 <10KB,`docs:check` >20KB 零容忍。
145
+
146
+ ### 6.4 额度矛盾的解法(必做)
147
+
148
+ 1. **默认跳过 opencode 的 via**:`via.js` 在组装待测集合时 `if (!includeOpencode) filter out provider=opencode`。
149
+ 2. **显式二次确认**:`--include-opencode` 时,TTY 下 `readline` 问 `将消耗 B/C/D 的 opencode 额度,确认测 opencode via?y/N`,N 则回落到“仅测非 opencode”。
150
+ 3. **最小化消耗**:via 探针统一 `max_tokens=5`、`prompt="hi"`、`temperature=0`,单样本;直连 bench 保持现有 3 样本,via 强制 1 样本(`--samples` 显式覆盖除外)。
151
+ 4. **提示常驻**:表格底部与 `--json.meta` 均带 `opencodeSkipped:true` 与文案,`--json` 也不静默消耗。
152
+
153
+ ### 6.5 状态与持久化
154
+
155
+ - **不写 `state.json`**:via 结果仅本次输出,不写 `modelLatencies/preferredModel`,避免污染 `src/auto.js` 的长期择优。
156
+ - 可选:`--save`(非首版)再考虑落盘 `daemon` 日志或 `~/.config/mslxdff/via-history.json`,首版不做。
157
+
158
+ ### 6.6 错误与空状态
159
+
160
+ | 场景 | 表现 |
161
+ |---|---|
162
+ | 未加组 / 无在线 peer | 空状态:`未加入组或无在线 peer,--via 无意义。先 mslxdff -group list / -addtogroup`,exit 0 |
163
+ | 某 peer 离线/超时 | 该格 `— offline/timeout`,不阻塞其他格;底部汇总 `2/3 peers reachable` |
164
+ | 上游 401/429/5xx | 复用 `src/bench/runner` 的错误分类,格内显示 `401/429/5xx`,不重试放大额度消耗 |
165
+ | 组员不支持某 provider(如未配 key) | 该格 `— no key` |
166
+ | 网络抖动 | 单样本 via 天生抖动,报告附 `* via 单样本,仅作参考,多次 --samples 2 取均值更稳` |
167
+
168
+ ### 6.7 性能与成本
169
+
170
+ - **串行优先**:via 对 `peers × models` 串行打,避免并发把 peer 打 429;`--samples` 仅在直连侧并发,via 侧恒串行。
171
+ - **超时**:单次 30s,超时记 `timeout` 不重试。
172
+ - **成本**:默认 via 不含 opencode,workbuddy/openrouter/clinebot 的探针均为最小 token,成本可忽略;opencode 显式才 1 次/peer。
173
+
174
+ ### 6.8 安全
175
+
176
+ - 复用现有 `Authorization: Bearer <token>` 鉴权与 `x-mslxdff-share-keys` 瞬时共享;opencode 恒不共享(`src/providers/share-keys.js`),via 不绕过。
177
+ - 不新增持久化 key,不新增网络监听面。
178
+
179
+ ---
180
+
181
+ ## 7. 变更记账(落地时必做)
182
+
183
+ - `docs/ARCHITECTURE.md`:§5 功能地图新增 “bench-via” 行;§6 CLI 表新增 `--via/--include-opencode`;§7 目录导览补 `src/bench/via.js、via-probe.js`。
184
+ - `docs/cli_help.md` + `docs/cli_help_mini.md`:同步 CLI 段落。
185
+ - `docs/adr/`:新增 `0011-bench-via.md`(记录“默认跳过 opencode + 二次确认”决策)。
186
+ - `npm run docs:check` 绿。
187
+
188
+ ---
189
+
190
+ ## 8. 测试计划(不写代码,仅约定)
191
+
192
+ - **单元**:`test/bench-via.test.js` — 空组/离线 peer/跳过 opencode/二次确认分支、`via-probe` 最小 prompt 约束、串行调度验证(mock peer)。
193
+ - **集成**:本地起两个 daemon 组网,`--via --json` 真实 A→B→upstream 打通,断开 B 后重测为 `offline`。
194
+ - **回归**:现有 `test/bench-*.test.js` 全绿;`--include-opencode` 分支需 mock 确认输入。
195
+
196
+ ---
197
+
198
+ ## 9. 里程碑
199
+
200
+ - **M1 规划定稿**:本文件评审通过,确定 CLI 形态(bench --via vs 独立 --bench-via)。
201
+ - **M2 拆分清单**:`.scratch/bench-via/MODULES.md` + Mermaid,`npm run docs:check` 预检。
202
+ - **M3 实现**:`via.js/via-probe.js` + `bench.js` 参数 + `report.js` via 列 + 空/加载/成功/失败四态。
203
+ - **M4 自测与发布**:组网 2 节点实测 → 文档同步 → `0.1.74` 发版。
204
+
205
+ ---
206
+
207
+ ## 10. 开放问题(需你拍板)
208
+
209
+ 1. **CLI 形态**:`mslxdff -provider bench --via` 还是 `mslxdff --bench-via`?推荐前者(bench 家族),是否接受或两者兼容?
210
+ 2. **默认样本数**:via 默认 1 次是否足够,还是要 2 次取均值更稳但多耗一次额度?
211
+ 3. **是否需要 `--save` 落盘历史**:首版不做,是否现在就加上?
212
+
213
+ > 你确认后,我按 `先拆后写` 直接落代码,不再追问细节。
214
+
215
+ > 备注(2026-09-20 追加,原文不改):0.1.x 起 Cline 供应商 id 统一为 `cline`(历史 `clinebot` 仅作一次性入站归一)。
@@ -0,0 +1,187 @@
1
+ # mslxdff 插件开发指南
2
+
3
+ mslxdff 内置一个零依赖的插件系统:把符合约定的 `.mjs` 模块放进插件目录,daemon 启动时自动加载,在**请求链路的所有关键节点**(hook 点)调用你的代码——包括替换上游 provider 本身。**插件出错只记日志,绝不影响主链路。**
4
+
5
+ > **内置多供应商**:0.1.56 起默认走 `src/providers/` 的多 Provider 架构(opencode 恒启用 + openrouter 可选,见 AGENTS.md)。插件 `createUpstream` 仍是"整体替换式"制造商供应,与内置多 Provider 二选一(有 provider 插件时走插件单通道)。
6
+
7
+ ## 快速开始
8
+
9
+ ### 1. 插件目录(双目录,都会被加载)
10
+
11
+ ```
12
+ 官方插件: <mslxdff安装目录>/plugins/ ← 随包分发,auto-update 一起更新
13
+ 用户插件: ~/.config/mslxdff/plugins/ ← 你自己的正式插件,升级永不丢
14
+ 完全接管: 环境变量 MSLXDFF_PLUGINS_DIR=/path/to/dir(只扫这一个)
15
+ ```
16
+
17
+ 优先级:同名文件时**用户目录覆盖官方目录**;加载顺序官方在前、用户在后。
18
+
19
+ > 为什么不直接放安装目录?npm 升级会重置包内文件——所以自己的正式插件务必放用户目录。
20
+
21
+ ### 2. 写一个最小插件
22
+
23
+ 创建 `~/.config/mslxdff/plugins/hello.mjs`:
24
+
25
+ ```js
26
+ export default {
27
+ name: "hello",
28
+ version: "1.0.0",
29
+ description: "我的第一个 mslxdff 插件",
30
+ hooks: {
31
+ "server:start": (ctx) => {
32
+ console.log(`[hello] mslxdff 已启动 port=${ctx.port}`);
33
+ },
34
+ },
35
+ };
36
+ ```
37
+
38
+ ### 3. 查看是否被识别
39
+
40
+ ```bash
41
+ mslxdff -plugins
42
+ # plugins dir: C:\Users\you\.config\mslxdff\plugins
43
+ # hello@1.0.0 [server:start]
44
+ # 我的第一个 mslxdff 插件
45
+ ```
46
+
47
+ 重启 daemon(`mslxdff -stop && mslxdff`)后生效。日志里会出现 `plugins loaded (1): hello@1.0.0`。
48
+
49
+ ## Hook 全表
50
+
51
+ ### 请求链路(按触发顺序)
52
+
53
+ | Hook | 触发时机 | ctx 内容 | 返回值语义 |
54
+ |---|---|---|---|
55
+ | `request:received` | 读到请求 body 后 | `{ ip, hops, headers, body }` | 返回 `{ respond: { status, body } }` **可短路请求**,直接响应客户端 |
56
+ | `model:select` | 候选顺序确定后 | `{ reqId, requested, useAuto, order, hops, stream }` | 返回数组**替换候选顺序** |
57
+ | `model:beforeTry` | 每个模型尝试前(循环内) | `{ reqId, requested, model, idx, hops }` | 返回 `false` 或 `{ skip: true }` **跳过该候选** |
58
+ | `upstream:request` | 发往上游前 | `{ reqId, requested, model, payload, stream }` | 返回 `{ payload }` **替换本次上游负载**(含 model 字段) |
59
+ | `upstream:response` | 上游响应/错误后 | `{ reqId, requested, model, status, ok, error, timing }` | 只观察 |
60
+ | `relay:first-chunk` | 流式首块到达 | `{ reqId, requested, model, via, ttfMs }` | 只观察 |
61
+ | `request:completed` | 请求结束(所有出口) | `{ reqId, requested, via, status, actual, durationMs, fallback?, interrupted?, error? }` | 只观察 |
62
+
63
+ > `x-mslxdff-model-lock` 锁定模型时 `model:select` 不触发——锁是硬约束。
64
+
65
+ ### 上游层(upstream 内部,作用于内置 client 的每次 fetch)
66
+
67
+ | Hook | 触发时机 | ctx 内容 | 返回值语义 |
68
+ |---|---|---|---|
69
+ | `upstream:headers` | 构建请求头后 | `{ url, body, headers }` | 返回 `{ headers }` **替换请求头** |
70
+ | `upstream:before-request` | fetch 调用前 | `{ url, method, body, headers }` | 返回 `{ url?, headers? }` **改目标地址/头** —— 上游不限于 opencode,可指向任意兼容端点 |
71
+
72
+ ### 模型列表 / 组内转发 / 生命周期
73
+
74
+ | Hook | 触发时机 | ctx 内容 | 返回值语义 |
75
+ |---|---|---|---|
76
+ | `models:list` | `/v1/models` 返回前 | `{ data }` | 返回 id 数组或完整 data 数组**替换对外模型列表** |
77
+ | `peer:beforeForward` | 转发给组员前 | `{ reqId, peer, model, hops }` | 只观察 |
78
+ | `peer:result` | 组员响应后 | `{ reqId, peer, model, ok, status, latencyMs }` | 只观察 |
79
+ | `server:start` | 服务就绪 | `{ port, host, version }` | 只观察 |
80
+ | `server:stop` | 关闭前 | `{ version }` | 只观察 |
81
+
82
+ ### 特殊接口(非 hooks 字段)
83
+
84
+ ```js
85
+ export default {
86
+ name: "my-plugin",
87
+ // ① 订阅全部事件流(request/ordered/upstream/fallback/result...每条 evt 都会推给你)
88
+ onEvent(evt) { /* fire-and-forget,抛错被吞 */ },
89
+ // ② 整体替换上游 provider(接任意 OpenAI 兼容服务;多个插件声明时取第一个)
90
+ async createUpstream(ctx) {
91
+ // ctx = { baseUrl, authToken, env }
92
+ return {
93
+ chat(body) { /* 返回 fetch Response,status>=400 会走 fallback */ },
94
+ preheat() { /* 可选:返回 { ok, status, ms } */ },
95
+ close() { /* 可选 */ },
96
+ };
97
+ },
98
+ hooks: { /* ...上表全部 hook */ },
99
+ };
100
+ ```
101
+
102
+ ## 实战示例
103
+
104
+ ### 改变模型列表设定(首选模型)
105
+
106
+ ```js
107
+ // prefer-model.mjs — 把指定模型排到最前
108
+ const PREFER = "big-pickle"; // 改这里,或读你自己的配置文件
109
+
110
+ export default {
111
+ name: "prefer-model",
112
+ hooks: {
113
+ "model:select": (ctx) => {
114
+ if (!ctx.order.includes(PREFER)) return; // 不在列表就不动
115
+ return [PREFER, ...ctx.order.filter((m) => m !== PREFER)];
116
+ },
117
+ "models:list": (ctx) => {
118
+ // 对外只暴露白名单模型
119
+ return ctx.data.filter((m) => /big-pickle|deepseek/i.test(m.id ?? m));
120
+ },
121
+ },
122
+ };
123
+ ```
124
+
125
+ ### 把上游换成任意 OpenAI 兼容服务(不改 URL 配置)
126
+
127
+ ```js
128
+ // redirect-upstream.mjs
129
+ export default {
130
+ name: "redirect-upstream",
131
+ hooks: {
132
+ "upstream:before-request": (ctx) => ({
133
+ url: ctx.url.replace("https://opencode.ai", "https://my-proxy.example.com"),
134
+ }),
135
+ },
136
+ };
137
+ ```
138
+
139
+ ### 自定义鉴权 / 限流
140
+
141
+ ```js
142
+ export default {
143
+ name: "guard",
144
+ hooks: {
145
+ "request:received": (ctx) => {
146
+ if (String(ctx.body?.messages?.[0]?.content || "").includes("BLOCK")) {
147
+ return { respond: { status: 403, body: { error: "blocked by plugin" } } };
148
+ }
149
+ },
150
+ },
151
+ };
152
+ ```
153
+
154
+ ### 监控统计(事件流)
155
+
156
+ ```js
157
+ let total = 0;
158
+ export default {
159
+ name: "stats",
160
+ onEvent(evt) {
161
+ if (evt.type === "request") total++;
162
+ if (evt.type === "result" && evt.status >= 500) console.log(`[stats] 5xx! ${evt.model}`);
163
+ },
164
+ };
165
+ ```
166
+
167
+ ## 规则与保证
168
+
169
+ - **文件格式**:仅 `.mjs` / `.js`,ESM,必须有 `export default { ... }`;`name` 缺省取文件名
170
+ - **串行执行**:多个插件的同一 hook 按文件名排序依次执行;返回值链式传递(前一个的输出是后一个的输入)
171
+ - **错误隔离**:
172
+ - 加载失败 → 不注册,错误进 events.log(`plugin-load-error`)和 `-plugins` 输出
173
+ - hook 抛错 → 跳过该插件继续执行后续插件,主链路无感
174
+ - **可观测**:hook 生效时 events.log 记 `plugin-hook`;报错记 `plugin-hook-error`;替换上游记 `plugin-upstream-active`
175
+ - **性能**:`request:received / model:select / model:beforeTry / upstream:request` 是 await 串行的,别做慢操作(>100ms 请改 fire-and-forget);`onEvent / request:completed / relay:first-chunk / upstream:response / peer:*` 本身就是异步不阻塞
176
+
177
+ ## 调试
178
+
179
+ ```bash
180
+ mslxdff -plugins # 列出已识别插件与其 hooks
181
+ mslxdff -debug # 前台跑,实时看 plugin-hook / plugin-hook-error 事件
182
+ mslxdff -log 50 # 回看事件日志
183
+ ```
184
+
185
+ ## 与 WorkBuddy 集成
186
+
187
+ WorkBuddy 插件的 SKILL.md 可以教 AI 在用户说"切换首选模型到 xxx"时,自动改写上面的 `prefer-model.mjs` 并重启 daemon —— mslxdff 侧无需任何改动,hook 就是稳定契约。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mslxdff",
3
- "version": "0.1.155",
3
+ "version": "0.1.158",
4
4
  "description": "测试项目,请勿使用。",
5
5
  "type": "module",
6
6
  "bin": {