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.
- package/bin/mslxdff.js +4 -4
- package/docs/ARCHITECTURE.md +426 -0
- package/docs/FEATURE_TREE.md +164 -0
- package/docs/MOBILE.md +82 -0
- package/docs/adr/0001-reasoning-content-injection.md +14 -0
- package/docs/adr/0002-models-free-filter.md +12 -0
- package/docs/adr/0003-zero-state-no-auth.md +10 -0
- package/docs/adr/0004-bearer-token.md +18 -0
- package/docs/adr/0005-peer-mesh.md +53 -0
- package/docs/adr/0006-broadband-member.md +103 -0
- package/docs/adr/0007-multi-provider-prefix.md +25 -0
- package/docs/adr/0008-share-keys-to-peers.md +52 -0
- package/docs/adr/0009-chat-repl.md +37 -0
- package/docs/adr/0010-allowlist.md +36 -0
- package/docs/adr/0011-broadband-stream.md +30 -0
- package/docs/adr/0012-responses-endpoint-codex-sync.md +48 -0
- package/docs/adr/0013-node16-compat.md +41 -0
- package/docs/adr/0014-deepseek-provider.md +52 -0
- package/docs/adr/0015-upstream-probe-routing.md +50 -0
- package/docs/adr/0016-model-capabilities.md +27 -0
- package/docs/adr/0017-ai-sdk-upstream-engine.md +59 -0
- package/docs/adr/0018-zen-client-identity.md +52 -0
- package/docs/adr/0019-share-keys-always-lend.md +59 -0
- package/docs/adr/0020-zen-free-lane-agent-shape.md +52 -0
- package/docs/adr/0021-usage-report-jsonl.md +47 -0
- package/docs/adr/0022-models-capability-merge.md +61 -0
- package/docs/adr/0023-key-provider-default-direct.md +58 -0
- package/docs/adr/0024-node18-baseline.md +63 -0
- package/docs/adr/0025-workbuddy-authdir-follows-state.md +71 -0
- package/docs/adr/0026-cline-provider-id-unify.md +67 -0
- package/docs/adr/0027-codearts-provider.md +48 -0
- package/docs/adr/0028-traework-provider.md +34 -0
- package/docs/adr/0029-qoder-native-provider.md +60 -0
- package/docs/adr/0030-models-list-scoped-by-picks.md +53 -0
- package/docs/adr/0031-qoder-true-streaming.md +50 -0
- package/docs/adr/0032-generic-responses-channel.md +72 -0
- package/docs/adr/0033-cline-allowlist-auto-sync.md +75 -0
- package/docs/adr/0034-request-level-human-readable-observability.md +60 -0
- package/docs/adr/0035-sdk-channel-headers-timeout.md +49 -0
- package/docs/adr/0036-qoder-per-request-sticky-account.md +82 -0
- package/docs/adr/0037-qwenwork-independent-provider.md +82 -0
- package/docs/adr/0038-zcode-provider.md +140 -0
- package/docs/agents/domain.md +51 -0
- package/docs/agents/issue-tracker.md +30 -0
- package/docs/agents/triage-labels.md +15 -0
- package/docs/cli_help.md +1391 -0
- package/docs/cli_help_mini.md +133 -0
- package/docs/plans/bench-via-latency-2026-09-01.md +215 -0
- package/docs/plugins.md +187 -0
- package/package.json +1 -1
- package/src/auto.js +254 -254
- package/src/bench/cline-bench.js +42 -42
- package/src/bench/probe.js +70 -70
- package/src/bench/report.js +162 -162
- package/src/bench/runner.js +77 -77
- package/src/bench/via-probe.js +124 -124
- package/src/bench/via-routes.js +87 -87
- package/src/bench/workbuddy-bench.js +54 -54
- package/src/chat/engine.js +160 -160
- package/src/chat/gateway.js +163 -163
- package/src/chat/orchestrator.js +234 -234
- package/src/chat/prompt.js +70 -70
- package/src/chat/repl.js +88 -88
- package/src/chat/terminal.js +135 -135
- package/src/chat/tools.js +306 -306
- package/src/chat-pipeline/index.js +123 -123
- package/src/chat-pipeline/policy.js +76 -76
- package/src/chat-pipeline/serial-trial.js +210 -210
- package/src/cli/commands/group.js +249 -249
- package/src/cli/commands/model/list-providers.js +4 -3
- package/src/cli/commands/model/list-render.js +7 -9
- package/src/cli/commands/model/list-sort.js +52 -0
- package/src/cli/commands/model/list-tty.js +76 -0
- package/src/cli/commands/model/list.js +5 -62
- package/src/cli/commands/model/picks.js +50 -50
- package/src/cli/commands/provider/bench-via.js +247 -247
- package/src/cli/commands/provider/bench.js +141 -141
- package/src/cli/commands/provider/index.js +124 -116
- package/src/cli/commands/provider/models.js +139 -128
- package/src/cli/commands/provider/qwenwork-login.js +119 -0
- package/src/cli/commands/provider/zcode-login.js +77 -0
- package/src/cli/commands/provider/zcode-quota.js +55 -0
- package/src/cli/commands/sync.js +232 -232
- package/src/cli/provider-row.js +2 -2
- package/src/cli/status.js +279 -279
- package/src/daemon.js +96 -96
- package/src/model-capabilities/enrich.js +86 -86
- package/src/model-capabilities/index.js +183 -183
- package/src/model-capabilities/parse.js +70 -70
- package/src/model-trace.js +1 -0
- package/src/models.js +225 -225
- package/src/providers/classify.js +1 -1
- package/src/providers/cline/auth.js +228 -228
- package/src/providers/cline/chat.js +307 -307
- package/src/providers/cline.js +2 -2
- package/src/providers/keyring.js +60 -56
- package/src/providers/qoder/chat.js +183 -100
- package/src/providers/qoder/index.js +230 -184
- package/src/providers/qoder/sse.js +103 -46
- package/src/providers/qoder/sticky.js +1 -0
- package/src/providers/qoder/stream.js +32 -9
- package/src/providers/qwenwork/account-store.js +133 -0
- package/src/providers/qwenwork/constants.js +67 -0
- package/src/providers/qwenwork/cosy.js +120 -0
- package/src/providers/qwenwork/crypto.js +218 -0
- package/src/providers/qwenwork/http.js +20 -0
- package/src/providers/qwenwork/index.js +327 -0
- package/src/providers/qwenwork/payload.js +142 -0
- package/src/providers/qwenwork/rsa.js +54 -0
- package/src/providers/qwenwork/sse.js +268 -0
- package/src/providers/qwenwork/stream.js +130 -0
- package/src/providers/qwenwork/upstream.js +120 -0
- package/src/providers/qwenwork.js +1 -0
- package/src/providers/registry.js +66 -56
- package/src/providers/share-keys.js +2 -2
- package/src/providers/workbuddy/chat.js +248 -248
- package/src/providers/workbuddy/reshape.js +152 -152
- package/src/providers/workbuddy.js +2 -2
- package/src/providers/zcode/account-store.js +129 -0
- package/src/providers/zcode/auth.js +28 -0
- package/src/providers/zcode/chat.js +171 -0
- package/src/providers/zcode/const.js +54 -0
- package/src/providers/zcode/headers.js +55 -0
- package/src/providers/zcode/index.js +124 -0
- package/src/providers/zcode/models.js +65 -0
- package/src/providers/zcode/oauth.js +120 -0
- package/src/providers/zcode/quota.js +176 -0
- package/src/providers/zcode/sse.js +179 -0
- package/src/reasoning.js +32 -32
- package/src/routes/chat/gateway.js +46 -46
- package/src/routes/chat/relay-pipeline.js +250 -246
- package/src/routes/chat/via-route-handler.js +144 -144
- package/src/routes/hedge.js +255 -255
- package/src/routes/models-route.js +167 -167
- package/src/routes/peers.js +273 -273
- package/src/routes/stream.js +438 -399
- package/src/runtime/bootstrap.js +45 -45
- package/src/runtime/provider-gate.js +33 -30
- package/src/runtime/providers-setup.js +165 -165
- package/src/server.js +64 -64
- package/src/state/schemas/allowlist.js +92 -92
- package/src/sync-opencode.js +280 -280
- package/src/transport/index.js +244 -244
- package/src/transport/pool.js +56 -56
- package/src/transport/retry.js +24 -24
- package/src/transport/sse.js +93 -93
- package/src/upstream-probe/display.js +52 -52
- package/src/upstream-probe/probe.js +49 -49
- package/src/upstream-probe/rotate.js +110 -110
- package/src/upstream-probe/start.js +45 -45
- package/src/upstream.js +289 -289
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# mslxdff 功能树(AI 导航索引)
|
|
2
|
+
|
|
3
|
+
> 目的:给 AI(及人类)一张**分层的功能全图**,改功能/修 bug 前先定位改动落在哪棵枝干、会波及哪些兄弟功能。
|
|
4
|
+
> 本文件是**纯导航索引**,不重复实现细节——细节请看每叶标注的**归属模块源码**与 `docs/ARCHITECTURE.md`(权威变更记账门)。
|
|
5
|
+
|
|
6
|
+
## 如何使用(AI 改代码前必读)
|
|
7
|
+
|
|
8
|
+
1. **定位改动枝干**:动到某个 `src/**` 文件时,先在本树反查它属于哪片叶子 → 叶子的父枝 = 功能域。
|
|
9
|
+
2. **评估波及面**:看该叶是否标注 `共用:`(与它共用底层模块的其它叶子都会受波及)。
|
|
10
|
+
3. **是否涉契约**:改动改了 CLI / Env / HTTP 路由 / state schema → 必须同步 `docs/ARCHITECTURE.md` 对应表(§1 变更契约门);新增源文件要补 ARCHITECTURE §7。
|
|
11
|
+
4. **改完跑检查**:`npm run docs:check`(第 5 步 `scripts/check-feature-tree.js` 做功能树↔src 双向锁定)。
|
|
12
|
+
|
|
13
|
+
树每叶格式:`功能名 — 一句话 · 归属模块/入口`。树是**能力入口级**——只列能向用户述说的能力代表文件;支撑/实现文件(如 `transport/sse.js`)不逐条列,靠**目录锚定**自动归属所在能力目录(落进已被树引用的目录 = 自动进树,无需登记)。
|
|
14
|
+
|
|
15
|
+
**什么情况必须补叶**(不补会被 docs:check 拦截):① 新增了树里完全没锚定的**新能力目录**(该目录及全部祖先都无任何树引用);② 新增**顶层能力入口文件**(顶层一个 `.js` 本身就是可述说能力,如 `sync-opencode.js`)。普通新增文件落进既有目录即自动吸收,不用动树。
|
|
16
|
+
|
|
17
|
+
> 让树长期不腐化的机制不是"自觉",是 `docs:check` 第 5 步的 CI 强制双向校验:树引用了不存在的文件会红,真实 src 出现树未锚定的新目录也会红——两头都锁死。
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## 0. 代理核心(Gateway)
|
|
22
|
+
客户端把请求打到 `http://127.0.0.1:<port>/v1/*`,这里负责认证、路由分发、链路编排。
|
|
23
|
+
|
|
24
|
+
- **0.1 认证与令牌**
|
|
25
|
+
- Bearer token 鉴权(`/health` 公开、`/v1/*` 恒时比较 401)· `src/state/schemas/token.js`, `src/server.js`;ADR-0004
|
|
26
|
+
- token 生成/轮换(首跑生成、`-refresh-token` 轮换,落 state 文件)· `bin/mslxdff.js`, `src/state.js`
|
|
27
|
+
- **0.2 服务器与端口**
|
|
28
|
+
- 端口持久化(state `port` > env `MSLXDFF_PORT` > 默认 8989,忽略裸 `PORT`)· `src/runtime/bootstrap.js`, `src/state/schemas/port.js`
|
|
29
|
+
- `EADDRINUSE` 自愈(自动杀旧 daemon 重试 ready)· `src/runtime/bootstrap.js`
|
|
30
|
+
- **0.3 HTTP 路由(请求链路门面)**
|
|
31
|
+
- `POST /v1/chat/completions` 主链路门面 → ChatPipeline · `src/routes/chat.js`, `src/routes/chat/index.js`
|
|
32
|
+
- `POST /v1/responses` Responses⇄Chat 翻译复用 ChatPipeline(stateless,ADR-0012)· `src/routes/responses-route.js`, `src/responses/translate.js`, `src/upstream-responses.js`
|
|
33
|
+
- `GET /v1/models` 免费模型列表(10min 缓存,whitelist+`-free` 后缀过滤)+ **按勾选集裁剪对外目录**(`modelPicks` 非空只回勾选项、空=不裁剪、`?all=1` 绕过,ADR-0030)+ 每条默认内联 `capabilities`(推理档位/模态/toolCall/上下文/最大输出/价格/endpoints/upstreamApi;`?raw=1` 回原始形状,codex 不富化)· `src/routes/models-route.js`, `src/models.js`, `src/model-capabilities/merge.js`;ADR-0002/0022/0030
|
|
34
|
+
- `GET /v1/models/capabilities` 模型能力元数据(reasoning 档位 effort/toggle/budget_tokens、图片输入、tool_call、上下文/价格;源=opencode 官方 models.dev 目录 24h 磁盘缓存,fetch 失败回退旧缓存)+ `-setto opencode` 条目自动注入同源能力(opencode Model 形状)· `src/routes/models-route.js`, `src/model-capabilities/{index,parse,enrich}.js`, `src/sync-opencode.js`;ADR-0016
|
|
35
|
+
- `GET /health` 公开探活 · `src/server.js`
|
|
36
|
+
- 中间件注入(reasoning_content 占位,DeepSeek all / kimi tool_calls)· `src/upstream.js`, `src/reasoning.js`;ADR-0001
|
|
37
|
+
- **0.5 ChatPipeline(对话主引擎子系统,0.1.89 起独立)**
|
|
38
|
+
- 编排 runTurn/serialize/auto-race/serial-trial · `src/chat-pipeline/{index,engine,auto-race,serial-trial,policy}.js`
|
|
39
|
+
- 请求策略解析(alias / auto / lock / share-keys / hops)· `src/chat-pipeline/policy.js`
|
|
40
|
+
- **0.6 上游传输层(Transport,网关薄化委托)**
|
|
41
|
+
- streaming 传输池/重试/SSE · `src/transport/{index,pool,retry,sse}.js`
|
|
42
|
+
- 网关侧 upstream 委托 Transport 薄化 · `src/upstream.js`
|
|
43
|
+
- 上游引擎 AI SDK(wire 层,ADR-0017):缺省 `sdk`,opencode 流式 chat 走 `@ai-sdk/openai-compatible`、`muse-spark*` 走 `@ai-sdk/openai` 的 responses 适配器、通用 OpenAI 兼容族(generic/openrouter)与 cline 的流式 chat 经 `dispatch.js` 同源(响应标记 `x-mslxdff-upstream-engine: sdk`,复用 legacy keep-alive 连接池),共享请求转换/SSE 序列化/执行器,非流式与异形 chatPath 回退 legacy,不可用自动回退;显式 `legacy`/关闭词回退原实现。**headers 闸门**:`attempt.js` 的 `doStream` 裸 await 会被半死连接挂死(ADR-0035),现由 `MSLXDFF_SDK_HEADERS_TIMEOUT_MS`(缺省 120s,0=关)+ `AbortController` 兜住;`responses.js` 尚未接入 · `src/upstream-engine/index.js`, `src/upstream-engine/mode.js`, `src/upstream-engine/sdk/{convert,sse,attempt,chat,responses,dispatch}.js`
|
|
44
|
+
- **0.4 请求分发策略**(同属一条链路、按序尝试,互有共用)
|
|
45
|
+
- via-route 单路径直达(显式锁模型按 `via-routes.json:best`,失败秒切直连)· `src/routes/chat/via-route-handler.js`
|
|
46
|
+
- 本地处理(model 解析 auto/normalize → upstream → 拆供应商转发)· `src/routes/chat/index.js`
|
|
47
|
+
- 组员接力 peer(本机超时让组员处理,带 `x-mslxdff-via`)· `src/routes/chat/peer-handler.js`
|
|
48
|
+
- 首块对冲 hedge(1s 未回并发竞速,谁快用谁)· `src/routes/hedge.js`, `src/routes/chat/hedge-handler.js`;`共用:0.4 全部分发策略`
|
|
49
|
+
- 模型无服务 → 自动选健康模型 · `src/routes/chat/exhausted-handler.js`, `src/auto.js`
|
|
50
|
+
- **0.7 状态迁移(state migrations,幂等 + 备份)**
|
|
51
|
+
- 迁移运行器与注册(bootstrap / CLI 单点触发,汇总 applied/skipped,先备份 state)· `src/state/migrations.js`
|
|
52
|
+
- cline 供应商 id 统一(`providerConfigs.clinebot` → `cline`,剔 `sk_` 旧 key,allowlist 求并后删旧键)· `src/state/migrations/cline-unify.js`;ADR-0026(`共用:1.5 cline`)
|
|
53
|
+
|
|
54
|
+
## 1. 上游供应商体系(Provider)
|
|
55
|
+
请求经 `dispatcher` 按 `<provider>/<raw>` 前缀路由到具体供应商,转发前剥前缀。裸 id 恒指默认 opencode。
|
|
56
|
+
|
|
57
|
+
- **1.0 供应商调度与公共机制**(所有 provider 共用)
|
|
58
|
+
- 前缀路由 dispatcher(split/join model id、剥前缀转发;启动预热仅默认供应商 opencode)· `src/providers/dispatcher.js`, `src/providers/model-id.js`;ADR-0007
|
|
59
|
+
- 多 key 轮转 keyring(round-robin + 30s 冷却隔离 401/403/429/5xx)· `src/providers/keyring.js`
|
|
60
|
+
- 瞬时 key 共享(outgoing 带 `x-mslxdff-share-keys` 借给组员一次,opencode 恒排除)· `src/providers/share-keys.js`;ADR-0008
|
|
61
|
+
- 供应商模型白名单 allowlist(`allowedModels` + `allowAnyModels`,空=403 除非 on;`/models` 亦过滤)· `src/state.js`, `src/providers/dispatcher.js`
|
|
62
|
+
- 插件系统(.mjs hook 可替换上游,失败仅记日志)· `src/plugins.js`;`docs/plugins.md`
|
|
63
|
+
- **1.1 opencode(默认供应商,恒启用)**
|
|
64
|
+
- zen gateway 免费池转发(`x-opencode-client:desktop` + `Bearer public` + 客户端身份规格:UA `opencode/<semver>` + 26 位 opencode 形状 session/request,2026-09-17 门禁,ADR-0018)· `src/providers/opencode.js`, `src/upstream.js`, `src/opencode-identity.js`
|
|
65
|
+
- 免费额度匿名兜底(`public` 429 + free → 空 Authorization 重试 hermes)· `src/upstream.js`
|
|
66
|
+
- 模型自动选择 auto(冷却>首选>延迟EMA>错误时间排序)· `src/auto.js`;`共用:0.4 自动选健康`
|
|
67
|
+
- 模型勾选集 picks(state `modelPicks`:既限制 auto 候选池,也是 `GET /v1/models` 对外目录白名单,ADR-0030)· `src/auto.js`, `src/routes/models-route.js`;`共用:0.3 HTTP 路由`
|
|
68
|
+
- 交互式模型多选 `-models` 渲染(`\x1b[2J\x1b[H` 兼容 PowerShell;按终端行高**开窗分页**:页头 `第 i/n 页 · 共 N 项`,翻页键 `←/→`·`h/l`·`PgUp/PgDn`,`↑/↓` 跨页自动跟随;多选行 `❯ [✓] <id>` 勾选框在名字前,不传 size 时全量渲染)· `src/chooser.js`;ADR-0030(`共用:1.0 公共机制` live 取数)
|
|
69
|
+
- 免费模型过滤(只暴露 ~7 个 free,`big-pickle` 特例)· `src/models.js`;ADR-0002
|
|
70
|
+
- **1.2 openrouter(配任一 key 自动启用)**
|
|
71
|
+
- OpenAI 兼容转发 + keyring · `src/providers/openrouter.js`
|
|
72
|
+
- **1.3 通用 OpenAI 兼容供应商(generic,`-provider add` 一键接入)**
|
|
73
|
+
- `providerConfigs.<id>={baseUrl,keys,allowAny,allowlist}` 前缀路由 · `src/providers/generic.js`
|
|
74
|
+
- 复用 keyring/retry/allowlist · `共用:1.0 公共机制`
|
|
75
|
+
- **1.4 WorkBuddy(copilot.tencent.com)**
|
|
76
|
+
- 多号接入与鉴权(`auths` 与 `keys` 一一对应;401/403 自动 refresh 回写 + 切号重放)· `src/providers/workbuddy.js`, `src/providers/workbuddy/auth.js`
|
|
77
|
+
- 强制 `stream:true` 的 `/v2/chat/completions` + 环形切号(402/insufficient)· `src/providers/workbuddy/chat.js`
|
|
78
|
+
- 余额查询(`get-user-resource` TTL 5min)· `src/providers/workbuddy-balance.js`, `src/providers/workbuddy/balance.js`
|
|
79
|
+
- 每日签到 100 credits(多号并行、双域幂等)· `src/providers/workbuddy/checkin.js`, `bin/mslxdff.js`
|
|
80
|
+
- token 自动化采集(`codebuddy -p auto hi` MITM 抓 refreshToken)· `workbuddy-token-auto.js`
|
|
81
|
+
- 桌面登录态跨平台发现(显式 `--file`/env > 后缀快查 > BFS)· `src/providers/workbuddy/desktop-info.js`
|
|
82
|
+
- 桶/子模块聚合 + 环形切号与轮转日志 · `src/providers/workbuddy/index.js`, `src/providers/workbuddy/rotation-log.js`
|
|
83
|
+
- **1.5 cline(Cline API)**
|
|
84
|
+
- 桶/子模块聚合(顶层 `cline.js` → `cline/` 目录)· `src/providers/cline.js`, `src/providers/cline/index.js`
|
|
85
|
+
- WorkOS 设备授权流 login(浏览器授权 → refreshToken 落盘)· `src/providers/cline/auth.js`
|
|
86
|
+
- 对话链(`/api/v1/auth/refresh` → `Bearer workos:<token>` + Cline 指纹头 UA)· `src/providers/cline/chat.js`
|
|
87
|
+
- 指纹头/请求头构造 · `src/providers/cline/headers.js`;模型目录(`recommended-models` 的 free+clinePass 为单一源,CLI 直查与聚合目录同源;daemon 启动自检增删留痕)**白名单自动同步 (2026-09-24)**:每次成功读取后把上游 id 并入 `allowedModels`(只增不减、幂等零写盘;兜底路径不写盘;异常吞掉留痕)· `src/providers/cline/models.js`, `src/providers/cline/allowlist-sync.js`;ADR-0033;取舍见 `.agents/notes/implemented/feature/2026-09-24-cline-auto-sync-free-catalog.md`
|
|
88
|
+
- deepseek 免费通道强制 `stream:true` + SSE 聚合 · `共用:1.5 cline`
|
|
89
|
+
- **1.6 codearts(华为云 CodeArts Agent 盘古助手,免费福利模型)**
|
|
90
|
+
- 桶/子模块聚合(顶层 shim → `codearts/` 目录:auth-pool/sts/sign/dpop/models/sse/stream/chat)· `src/providers/codearts.js`, `src/providers/codearts/index.js`
|
|
91
|
+
- 华为账号凭证 blob 池(refreshToken+codeVerifier+DPoP JWK 三重绑定;STS 临期 30min 单飞刷新 + refresh_token 单次轮换原位写回;多账号 keyring 轮转;终态死号提示重登)· `src/providers/codearts/auth-pool.js`;ADR-0027(`共用:1.0 公共机制`)
|
|
92
|
+
- PKCE 浏览器授权 login(凭证 blob 落盘 `providerConfigs.codearts.keys`,默认 `allowAnyModels=true`)· `src/cli/commands/provider/codearts-login.js`
|
|
93
|
+
- 三路模型发现 + benefit claim 幂等(`error_code:"0000"`);对话恒 `stream:true`(v2 SSE `text` 全文快照→delta/聚合 + HTTP 200 内嵌错误映射 429/400/502)· `共用:1.6 codearts`
|
|
94
|
+
- **1.7 traework(TRAE SOLO 通道,免费 SOLO 模型)**
|
|
95
|
+
- 桶/子模块聚合(顶层 shim → `traework/` 目录:constants/headers/payload/errors/sse/token/account-store/chat/models/checkin/index)· `src/providers/traework.js`, `src/providers/traework/index.js`
|
|
96
|
+
- trae.cn 浏览器授权 login(随机 machine/device id → 回调解析 → ExchangeToken → `auths/trae-<uid>.json` 0600 + state 双写,自动签到+查积分)· `src/cli/commands/provider/traework-login.js`
|
|
97
|
+
- 对话恒 `stream:true`(SOLO SSE output/done/error/token_usage → OpenAI SSE 透传/聚合;1005 plan 长冷却/401 换号/429 短冷)+ 模型映射(空/auto→`glm-5.2`,去 `__` 后缀,下划线宽松匹配)· `共用:1.7 traework`(`共用:1.0 公共机制` transport/keyring/account-store 范式)
|
|
98
|
+
## 1.8 qoder(Qoder 上游,免费模型池)
|
|
99
|
+
- 原生直连深模块(顶层 shim → `qoder/` 目录:constants/oauth/encode/fingerprint/session/payload/sse/chat/models/account-store/checkin/index + baseprompt 模板)· `src/providers/qoder.js`, `src/providers/qoder/index.js`
|
|
100
|
+
- 对话真流式(上游恒 `stream:true`:客户端 `stream:true` 上游帧到达即转 OpenAI chunk 边收边吐,`false` 才聚合回 JSON;**同请求粘号**:一次客户端请求内复用同一个号,仅 401/403/429/5xx 冷却才换,选号决定经 `x-mslxdff-qoder-account` 回显;流式非 200 整形成 200+流内 error,真实状态码经内部头 `x-mslxdff-qoder-upstream-status` 带出门面判冷却——**三条判决面**:①首包 HTTP 非 200;②HTTP 200 但 SSE 信封内 `statusCodeValue` 非 200(限流裹成 200+`serviceAvailable:false`);③**额度耗尽**——判定用 `findQuotaSignal` 限深扫描信封各层显式信号(`code:"110"` 或 `Billing…exceeded` 措辞),实测三层嵌套(外层 403 → `code:10605` → 内层 110)也能命中;**排队 10605 无 110 信号 → 天然不判额度**(不得误报没额度)。命中 `kind:"quota"`(内部头 `x-mslxdff-qoder-quota`)→ **在 provider 内直接换号重发**(不依赖外层空转重试:该链路在 `stream:false` 不触发),该号走长冷却 `MSLXDFF_QODER_QUOTA_COOLDOWN_MS`(默认 1h,普通错 30s);**收口按真实全景分档**:仅当每个号都被上游明确判过额度才 429 `quota_exhausted`("所有账号额度已用完"),有上游响应则如实透出(含上游真错),全在冷却但非额度则 429 `all_cooling`("暂不可用,请稍后重试")——绝不用非额度错误谎称额度耗尽。冷却过期自动回池。②③需在返回 Response 前预读上游首帧取判决(`PEEK_MAX_FRAMES=8`),预读字节原样回灌给整形器)· `src/providers/qoder/{chat,index,sticky,request,stream,aggregate,sse}.js`;ADR-0031/0036(`共用:1.0 公共机制`)— 见 .agents/notes/implemented/bug-fix/2026-09-28-qoder-quota-cooldown-switch.md
|
|
101
|
+
- 每日签到领积分(**按每号 region 选域名**:cn=`daily-check-in/status→claim`(409=今日已领),global=`campaigns→/{id}/claim`;默认只领 `CLAIM_BENEFIT`,`--any` 才含 VIEW_DETAILS 促销)+ 额度查询 `/api/v2/quota/usage` · `src/providers/qoder/checkin.js`, `src/cli/commands/provider/qoder-checkin.js`, `src/runtime/qoder-checkin.js`(`共用:1.7 traework` 签到范式、`共用:1.0 公共机制`)
|
|
102
|
+
- OAuth PKCE 设备授权 login(授权链接 → 轮询 dt-/drt- → userinfo → `auths/qoder-<uid>.json` 0600 + state 双写,默认 `allowAnyModels=true`)· `src/cli/commands/provider/qoder-login.js`;ADR-0029(`共用:1.0 公共机制`)
|
|
103
|
+
## 1.9 qwenwork(千问办公,账号积分池供应商)
|
|
104
|
+
- 独立 provider(vendor 原作者 `qwenwork2api` 协议:`qwenwork/` 目录 constants/crypto/rsa/cosy/payload/sse/stream/upstream/index/account-store + 顶层 shim;**不与 qoder 共用任何常量或账号存储**——同 OAuth client_id 不代表同租户:RSA 模数、cosyVersion/clienttype/scene、请求体编码、无交集模型池、四条边界见 ADR-0037)· `src/providers/qwenwork.js`, `src/providers/qwenwork/index.js`
|
|
105
|
+
- 设备授权 login(PKCE + 轮询 → userinfo → `auths/qwenwork-<uid>.json` 0600 + state 双写,默认 `allowAnyModels=false` + 只种 3 个实测模型,防 auto 烧分;手动配 key 无身份时首轮自动补 userinfo,否则上游判 101 Signature invalid)· `src/cli/commands/provider/qwenwork-login.js`;ADR-0037(`共用:1.0 公共机制`)
|
|
106
|
+
- 对话恒上游 `stream:true`(无 `Encode=1`,纯 JSON;`qwork` 切片仅 3 模型:`flash`(price 0.1)/`pro`(1)/`qwen3.8-max-preview`(1.8),均 reasoning+VL;流式 `transformStream` 边收边吐 + model 名回写,非流式 `collectSync` 聚合;额度走账号积分池,额度错长冷却 1h 换号,收口全号额度确认才 429 `quota_exhausted`)· `src/providers/qwenwork/{payload,sse,stream,upstream}.js`(`共用:1.0 公共机制`)
|
|
107
|
+
## 1.10 zcode(智谱 ZCode 官方工作台,绑定账号的免费额度)
|
|
108
|
+
- 供应商门面(JWT keys→keyring 多号轮转 + 差异化冷却:auth/限流/服务端/网络=短冷却 30s、1005 额度=长冷却 1h、3007/参数=不冷却直透;无账号→空目录 + 401 login 指引)· `src/providers/zcode/index.js`
|
|
109
|
+
- Anthropic 网关转发(OpenAI→Anthropic messages 请求转换 + Anthropic SSE→OpenAI chunk 整形;上游恒 `stream:true`,非流式本地聚合;thinking 块丢弃不入历史)· `src/providers/zcode/{chat,sse}.js`
|
|
110
|
+
- 客户端同形头 + 账号身份(12 项 ZCode source headers;per-account 持久 deviceMid UUID 随账号文档走,不进 providerConfigs)· `src/providers/zcode/{headers,const,auth,account-store}.js`
|
|
111
|
+
- CLI 轮询登录(`oauth/cli/init` → 浏览器授权 → `oauth/cli/poll` 拿 zcode JWT,zai/bigmodel 双入口;**全程免验证码**;落 `auths/zcode-<uid>.json` 0600 + state keys,内置目录只增不减并入 allowlist 保证开箱可用)· `src/providers/zcode/oauth.js`, `src/cli/commands/provider/zcode-login.js`;ADR-0038
|
|
112
|
+
- 模型目录与额度(内置 Start/Coding Plan 目录 + balance capabilities 探测过滤 + 价格 0/可用 `*` 标注;`billing/balance` → plans/balances 分组表格,空套餐给领取指引、401 给重登指引)· `src/providers/zcode/{models,quota}.js`, `src/cli/commands/provider/zcode-quota.js`
|
|
113
|
+
让代理"选对、选快、不白烧额度"。
|
|
114
|
+
|
|
115
|
+
- **2.1 模型选择与排序**(`共用:0.1 自动选健康`)
|
|
116
|
+
- 排序:冷却 > 首选 `PREFERRED_MODEL` > 延迟 EMA > 错误时间 · `src/auto.js`
|
|
117
|
+
- 勾选集 picks 筛选;勾选全失效回退全量;真实上游模型自动入 picks · `src/auto.js`
|
|
118
|
+
- **2.2 健康 / 冷却 / 降权**
|
|
119
|
+
- 模型出错冷却(普通 60s / slow 5min,`SLOW_TOTAL_MS=20s` 标慢)· `src/upstream.js`
|
|
120
|
+
- 首次块 25s 内到即可用、stall 15s 仅作质量分 · `src/upstream.js`
|
|
121
|
+
- **2.3 上游测速 bench**
|
|
122
|
+
- `-provider <id> bench` 只测 allowlist∩picks 的 TTFB/TPS · `src/bench/{probe,runner,report}.js`
|
|
123
|
+
- bench-via 直连 vs 经 peer 延迟对比(串行省额度;默认跳过 opencode)· `src/bench/via.js`, `src/bench/via-probe.js`
|
|
124
|
+
- via-routes 动态择路表(探针自动保鲜 + `--apply` 手动校准,`provider:<id>` 级回退,TTL 默认 5min)· `src/bench/via-routes.js`
|
|
125
|
+
- 后台上游探针与三类路由(60s 轮转 direct+relay 代发 EMA 落盘;workbuddy 不转/opencode 直连先行/其余比延迟;零新增命令)· `src/upstream-probe/{probe,rotate,start,display}.js`, `src/providers/classify.js`;ADR-0015
|
|
126
|
+
- **2.4 模型孤儿治理**
|
|
127
|
+
- modelErrors TTL 90d 过期;picks 孤儿仅标不删、`prune --orphans` 显式回收 · `src/auto.js`
|
|
128
|
+
|
|
129
|
+
## 3. 网络与群组(Group / Peer / 宽带中继)
|
|
130
|
+
赶 IP 级限流、组员接力、宽带直推。
|
|
131
|
+
|
|
132
|
+
- **3.1 群组管理**(`-group` 命令)
|
|
133
|
+
- 建/加/删组、成员序(leader 过滤固定)、`-delgroup` vs `-leavegroup` 语义 · `src/groups.js`, `src/routes/groups.js`
|
|
134
|
+
- 手机宽带接入向导(`-addtogroup` 零/单参数:问组长地址+组名 → 宽带入组 → 出口 IP → 自动起服务 → Termux 保活提示)· `src/cli/join-wizard.js`, `src/cli/join-core.js`
|
|
135
|
+
- **3.2 peer 接力与组员**
|
|
136
|
+
- 本机超时组员处理、瞬时 key 共享透传 · `src/routes/chat/peer-handler.js`, `src/providers/share-keys.js`
|
|
137
|
+
- peer 冷却/跳数/宽带过期常量(DEFAULT_PEER_COOLDOWN_MS / MAX_HOPS / BROADBAND_STALE_MS)· `src/peers.js`
|
|
138
|
+
- group sync 刷新成员 · `src/groups.js`
|
|
139
|
+
- **3.3 宽带中继长连**(relay 成员)
|
|
140
|
+
- `GET /v1/groups/relay/stream` SSE 单长连 + 25s ping 直推(空闲 -96%)· `src/routes/groups-relay.js`, `src/routes/relay-queue.js`
|
|
141
|
+
- `MSLXDFF_BROADBAND_STREAM=0` 回退 1s poll · `src/runtime/bootstrap.js`
|
|
142
|
+
|
|
143
|
+
## 4. 客户端接入同步(对终端工具暴露)
|
|
144
|
+
- **4.1 opencode 同步**(`-setto opencode`)原子写 `opencode.json` 的 `provider.mslxdff` · `src/sync-opencode.js`
|
|
145
|
+
- **4.2 WorkBuddy 同步**(`-setto workbuddy`)原子写 `~/.workbuddy/models.json` · `src/sync-workbuddy.js`
|
|
146
|
+
- **4.3 Codex 同步 + Responses**(`-setto chatgpt`)写 `~/.codex/config.toml`(`wire_api="responses"`)+ `/v1/models` 对 codex 追加空 `models:[]` · `src/sync-codex.js`, `src/routes/models-route.js`
|
|
147
|
+
- **4.4 模型别名还原**(对外 `/`→`-`,经 `model-aliases.json` 还原)· `src/routes/chat/gateway.js`
|
|
148
|
+
|
|
149
|
+
## 5. 运维 / CLI / 后台
|
|
150
|
+
- **5.1 Daemon**(后台守护、pid/日志/事件流、auto-update)· `src/daemon.js`
|
|
151
|
+
- **5.2 自启与保活**(systemd --user / `loginctl enable-linger`)· `src/autostart.js`
|
|
152
|
+
- **5.3 状态聚合 `-status`**(daemon/health/providers/models+体检表/groups/recent calls)· `bin/mslxdff.js`, `src/chat/stats.js`
|
|
153
|
+
- **5.4 对话 CLI `-chat`**(交互 repl + `curl` 工具 + engine 纯逻辑拆分)· `src/chat/{repl,terminal,engine,upstream,tools}.js`
|
|
154
|
+
- **5.5 日志与排障**(时间统一时区;`-log`/`-debug`;`timeline.log` 每请求一行人读汇总(直连/组员逐个胜负/重试/最终结果/总耗时);按模型链路日志:每个模型一个 `<provider>-<model>.log`,记录 request/route/upstream/peer/relay/result/client-response 阶段与安全摘要;**事件面用黑名单**(默认全部可见,只排除 `peer-health`/`heartbeat`/`client-session`/`upstream-probe*`),决定类事件只渲染登记字段(含上游回显 `upstream=`/`account=`/`pick=`/`cooled=`),payload 与正文不落)· `src/timeline.js`, `src/logs.js`, `src/model-trace.js`, `src/time.js`(ADR-0034)
|
|
155
|
+
- **5.6 时区**(state `timezone` 默认 `Asia/Shanghai`)· `src/cli/commands/timezone.js`
|
|
156
|
+
- **5.7 白嫖雷达 free-watcher**(V2EX 单源关键词过滤,5min 轮询)· `src/free-watcher.js`, `bin/mslxdff.js`
|
|
157
|
+
- **5.8 Node16 兼容层**(undici 锁 5.28 + compatFetch/timeoutSignal/clone/uuid 单一出口)· `src/compat.js`;ADR-0013(`共用:所有走网络的模块`)
|
|
158
|
+
- **5.9 模型 CLI 命令族 `-model`**(pick/unpick/clear/picks/status/list 勾选管理 + 供应商模型直查)· `src/cli/commands/model/{picks,list,status}.js`, `src/cli/commands/provider/models.js`(`共用:2.1 模型选择、2.4 孤儿治理`)
|
|
159
|
+
- **5.10 模型用量报表 `-stats`**(近 N 小时每模型 token 消耗 + 首字/总耗时/加权速度;逐请求 JSONL 按日分片 + 保留期删旧,区别于 calls/events 的 1MB 环形截断;只计经 8989 的成功请求)· `src/usage/{record,report}.js`, `src/cli/commands/stats.js`(`共用:5.5 日志与排障 的 logDir`)
|
|
160
|
+
|
|
161
|
+
## 6. 全局横切关注点
|
|
162
|
+
- **6.1 零依赖 + ≤10KB/20KB 拆分纪律**(`src/**/*.js` ≤10KB 好、>20KB 强拆,entry 聚合)· `scripts/check-file-size.js`
|
|
163
|
+
- **6.2 变更记账门**(ARCHITECTURE.md §1 + ADR + docs:check)· `docs/ARCHITECTURE.md`, `scripts/docs-check.js`
|
|
164
|
+
- **6.3 功能树同步门**(本文件):新增功能/模块 → 在对应主干/枝干下补叶(格式 `功能名 — 一句话 · 归属模块`),叶子漏登记 = 树失准;改功能波及兄弟功能时在受影响叶补 `共用:` 标注
|
package/docs/MOBILE.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# 手机接入指南(Android / Termux)
|
|
2
|
+
|
|
3
|
+
把手机变成组内出口:**不占端口、不需公网 IP**,只主动连组长,用手机自己的网络访问上游。
|
|
4
|
+
适合"再要一个独立出口 IP、给组内分担上游限流"的场景。
|
|
5
|
+
|
|
6
|
+
## 3 分钟上手
|
|
7
|
+
|
|
8
|
+
### 1. 装 Termux(一次性)
|
|
9
|
+
从 F-Droid 安装:<https://f-droid.org/packages/com.termux/>(Play 商店版已停止维护,勿用)。
|
|
10
|
+
|
|
11
|
+
### 2. 装 Node(一次性)
|
|
12
|
+
```bash
|
|
13
|
+
pkg update && pkg install -y nodejs-lts
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### 3. 装 mslxdff
|
|
17
|
+
```bash
|
|
18
|
+
npm i -g mslxdff
|
|
19
|
+
```
|
|
20
|
+
(也可以不装,直接用 `npx -y mslxdff@latest -addtogroup`,首次会下载。)
|
|
21
|
+
|
|
22
|
+
### 4. 一键入组(向导)
|
|
23
|
+
```bash
|
|
24
|
+
mslxdff -addtogroup
|
|
25
|
+
```
|
|
26
|
+
按提示回答两个问题即可:
|
|
27
|
+
```
|
|
28
|
+
手机宽带接入 — 让这台设备成为组内出口(不占端口,只出站)
|
|
29
|
+
|
|
30
|
+
1/2 组长地址(ip:端口,如 149.13.91.10:8989): 149.13.91.10:8989
|
|
31
|
+
2/2 组名: my@mslxd
|
|
32
|
+
→ 正在连接组长 http://149.13.91.10:8989 ...
|
|
33
|
+
✓ 已加入组「my@mslxd」(宽带成员 relay://08cb0592)
|
|
34
|
+
→ 正在确认出口 IP ...
|
|
35
|
+
✓ 出口 IP: 203.0.113.7(组长视角,上游分流按这个 IP)
|
|
36
|
+
→ 正在启动后台服务 ...
|
|
37
|
+
✓ 后台服务已启动(pid 1234)
|
|
38
|
+
```
|
|
39
|
+
看到三个 ✓(已加入 / 出口 IP / 服务已启动)即完成——**此时手机已在为组内分担请求**。
|
|
40
|
+
|
|
41
|
+
### 5. 防休眠(重要)
|
|
42
|
+
```bash
|
|
43
|
+
termux-wake-lock
|
|
44
|
+
```
|
|
45
|
+
再补一刀:**下拉通知栏 → Termux 常驻通知 → 设置 → 电池优化 → 不优化**(各品牌路径略有差异)。
|
|
46
|
+
|
|
47
|
+
## 验证
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
mslxdff -status # 组与出口状态
|
|
51
|
+
mslxdff -group list # 组内成员(自己带 [broadband] 标注)
|
|
52
|
+
tail -f ~/.config/mslxdff/daemon.log # 看到 "connected <组> via <组长> (SSE)" = 长连已建立
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## 停止 / 离开
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
mslxdff -stop # 停后台服务(不再贡献,组内身份保留)
|
|
59
|
+
mslxdff -leavegroup # 离开所有组(组长侧同步移除)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 排错
|
|
63
|
+
|
|
64
|
+
| 现象 | 原因 | 处理 |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| `npx: command not found` | 没装 Node | `pkg install nodejs-lts` |
|
|
67
|
+
| `join failed: fetch failed` | 组长地址/端口错、组长没起、网络不通 | 先 `curl <组长>/health` 看是否 `{"status":"ok"}`;手机切 WiFi/流量再试 |
|
|
68
|
+
| `出口 IP: 待确认` | 组长暂时不可达 | 通常自动恢复;`mslxdff -log 20` 看错误 |
|
|
69
|
+
| 跑一会就断 | Android 省电杀后台 | `termux-wake-lock` + 关电池优化;确认通知栏 Termux 常驻 |
|
|
70
|
+
| 手机流量消耗大 | 每次转发=上下行各一份 | 正常现象;不想贡献时 `mslxdff -stop` |
|
|
71
|
+
| 切了 WiFi / 4G | 出口 IP 变了(**这其实是好事:新 IP = 新额度**) | 无需操作,长连自动重连 |
|
|
72
|
+
| `· 已取消(输入结束)` | 输入被打断(Ctrl+D / 管道结束) | 重跑 `mslxdff -addtogroup` |
|
|
73
|
+
|
|
74
|
+
## 一句话原理
|
|
75
|
+
|
|
76
|
+
组员请求 → 组长经 SSE 长连推给手机 → **手机用自己的网络访问上游** → 结果回传组长 → 请求方。
|
|
77
|
+
出口 IP 是组长视角看到的手机公网 IP,用于上游 IP 级限流分散;broadband 成员只监听 `127.0.0.1`,不对外开放端口。
|
|
78
|
+
|
|
79
|
+
## 相关
|
|
80
|
+
|
|
81
|
+
- CLI 全量手册:`cli_help.md`(含「手机宽带接入向导」一节的完整语法与失败分支)
|
|
82
|
+
- 设计取舍:`.agents/notes/implemented/feature/2026-09-16-mobile-broadband-join-wizard.md`
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# ADR-0001: Inject a reasoning_content placeholder on outbound assistant messages
|
|
2
|
+
|
|
3
|
+
The Zen upstream's thinking-mode models (deepseek-family at minimum) return
|
|
4
|
+
`400 "The reasoning_content in the thinking mode must be passed back"` when a
|
|
5
|
+
multi-turn request echoes an assistant message without its `reasoning_content`.
|
|
6
|
+
Clients speaking plain OpenAI format never send that field, so the proxy writes
|
|
7
|
+
a `" "` placeholder into assistant messages before forwarding. Scope is `all`
|
|
8
|
+
for deepseek-family models and `tool_calls` for kimi-family models; messages
|
|
9
|
+
that already carry non-empty `reasoning_content` are left untouched.
|
|
10
|
+
|
|
11
|
+
The alternative — telling clients to manage `reasoning_content` themselves —
|
|
12
|
+
would break standard OpenAI-compatible clients, so the proxy eats this
|
|
13
|
+
compatibility cost instead. Matches `/root/9router` v0.5.45
|
|
14
|
+
`open-sse/utils/reasoningContentInjector.js`.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# ADR-0002: /v1/models exposes only free models, matched by suffix or whitelist
|
|
2
|
+
|
|
3
|
+
The upstream `/zen/v1/models` list contains ~60 models; exposing them all would
|
|
4
|
+
pollute clients with paid models this proxy can't serve for free. `/v1/models`
|
|
5
|
+
therefore filters to: `id` ending in `-free`, OR the explicit whitelist entry
|
|
6
|
+
`big-pickle`. The whitelist exists because `big-pickle` is a free model without
|
|
7
|
+
the `-free` suffix, and a suffix-only filter would silently drop it.
|
|
8
|
+
|
|
9
|
+
A plain `endsWith("-free")` filter was considered and rejected for exactly that
|
|
10
|
+
reason. Matches `/root/9router` v0.5.45
|
|
11
|
+
`src/app/api/providers/suggested-models/filters.js`
|
|
12
|
+
(`KNOWN_FREE_OPENCODE_MODELS = ["big-pickle"]`).
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# ADR-0003: Zero-state, no-DB, no-account local proxy
|
|
2
|
+
|
|
3
|
+
Status: partially superseded by [ADR-0004](./0004-bearer-token.md) — the
|
|
4
|
+
no-auth clause below is replaced; the zero-DB / no-account / no-cloud principles
|
|
5
|
+
stand.
|
|
6
|
+
|
|
7
|
+
By design this proxy holds no database, no token store, no account rotation,
|
|
8
|
+
and no cloud sync. A single static bearer token is the only credential, kept
|
|
9
|
+
in a 0600 state file (see ADR-0004); everything else is stateless per-process
|
|
10
|
+
memory at most.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# ADR-0004: Single static bearer token, persisted in a 0600 state file
|
|
2
|
+
|
|
3
|
+
The proxy requires a bearer token on `/v1/*` so an accidentally-exposed port
|
|
4
|
+
isn't an open relay. There is no account system: the token is a random
|
|
5
|
+
`crypto` 32-byte value (hex), generated once on first run, persisted to a
|
|
6
|
+
state file (default `~/.config/mslxdff/state.json`, `0600`, path overridable
|
|
7
|
+
via `MSLXDFF_STATE_FILE`), and printed to stdout on creation. Rotate with
|
|
8
|
+
`mslxdff -refresh-token`, which regenerates, rewrites the file, prints the
|
|
9
|
+
new token, and exits (does not start the server).
|
|
10
|
+
|
|
11
|
+
Auth is enforced with a constant-time string compare on
|
|
12
|
+
`Authorization: Bearer <token>`; mismatches get `401` with `WWW-Authenticate`.
|
|
13
|
+
`/health` stays public (no token). Tokens never appear in logs.
|
|
14
|
+
|
|
15
|
+
Alternatives rejected: a fixed default token (same key on every install),
|
|
16
|
+
per-user accounts (needs a DB — that's the 9Router provisioning surface we
|
|
17
|
+
rejected in ADR-0003), and unauthenticated local-only binding (fragile;
|
|
18
|
+
a proxy relay deserves an explicit secret even on localhost).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ADR-0005: Peer mesh for same-model failover across machines
|
|
2
|
+
|
|
3
|
+
> **Update (group model):** peers are now configured through named groups, and the
|
|
4
|
+
> group name doubles as the join password (supersedes the random join key). CLI:
|
|
5
|
+
> `-creategroup <name>` on the leader (no address needed — the first joiner seeds
|
|
6
|
+
> the leader's own entry from the address it connects from), `-addtogroup <leader-host> <name>`
|
|
7
|
+
> elsewhere. Every member (leader included) re-registers with the leader on a timer
|
|
8
|
+
> (`MSLXDFF_GROUP_SYNC_MS`, default 60s) and rebuilds its local peer list from the
|
|
9
|
+
> freshest member map, so membership changes propagate to all nodes automatically.
|
|
10
|
+
> The `-peer` commands are removed; peers are an internal mechanism. Wrong group
|
|
11
|
+
> names/tokens are counted per source IP: `MSLXDFF_BAN_THRESHOLD` (default 5)
|
|
12
|
+
> failures ban the IP for `MSLXDFF_BAN_WINDOW_MS` (default 48h); `-resetban [ip]`
|
|
13
|
+
> clears bans.
|
|
14
|
+
|
|
15
|
+
A single mslxdff instance depends on one upstream quota; when that upstream
|
|
16
|
+
starts rate-limiting or failing for a model, the only local fallback is
|
|
17
|
+
switching to a *different* model. That changes the model out from under the
|
|
18
|
+
client. To keep the requested model working while the local path recovers,
|
|
19
|
+
multiple instances can be joined into a group: when the local upstream
|
|
20
|
+
fails for model X, the instance forwards the request (still model X) to a
|
|
21
|
+
group member that runs its own mslxdff and has its own upstream quota.
|
|
22
|
+
|
|
23
|
+
## Design
|
|
24
|
+
|
|
25
|
+
- **Peers are plain mslxdff instances.** Each peer is identified by
|
|
26
|
+
`{ url, token }` (its bearer token, per ADR-0004). Configured with
|
|
27
|
+
`mslxdff -peer add <token> <url> [name]`, removed with `-peer remove`,
|
|
28
|
+
listed with `-peer list`; persisted in the state file. No new protocol —
|
|
29
|
+
forwarding is a normal authenticated `POST /v1/chat/completions` to the
|
|
30
|
+
peer, reusing the existing API surface.
|
|
31
|
+
- **Local-first routing.** A request always tries the local upstream first.
|
|
32
|
+
Only on local failure (network error or HTTP ≥ 400) does it iterate peers
|
|
33
|
+
for the *same model*, round-robin over currently-available peers.
|
|
34
|
+
- **Model lock.** Forwarded requests carry `x-mslxdff-model-lock: <model>`
|
|
35
|
+
so the receiving peer uses exactly that model — it must not re-select or
|
|
36
|
+
fall back to another model, keeping "same model, different machine" true.
|
|
37
|
+
- **Hop bound.** Forwarded requests carry `x-mslxdff-hops` (incremented each
|
|
38
|
+
hop). A peer receiving hops ≥ `maxHops` (default 3, `MSLXDFF_MAX_HOPS`)
|
|
39
|
+
stops forwarding further, bounding mesh depth and preventing loops.
|
|
40
|
+
- **Peer cooldown.** A peer that fails a request enters a cooldown window
|
|
41
|
+
(default 30s, `MSLXDFF_PEER_COOLDOWN_MS`), during which it is skipped by
|
|
42
|
+
the round-robin, so the mesh rotates to healthy peers instead of
|
|
43
|
+
hammering a down one. Mirrors the model cooldown in ADR-0001.
|
|
44
|
+
|
|
45
|
+
## Why not alternatives
|
|
46
|
+
|
|
47
|
+
- **Point the whole proxy at another machine** (client-side failover): the
|
|
48
|
+
client can't detect per-model upstream failure, and every request pays the
|
|
49
|
+
cross-machine latency even when local is healthy.
|
|
50
|
+
- **Different model per machine**: violates the requirement of keeping the
|
|
51
|
+
same model, and hides the model-change from the client.
|
|
52
|
+
- **Central coordinator / service discovery**: overkill for a handful of
|
|
53
|
+
private instances; static peer config is zero-config and debuggable.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# ADR-0006: 宽带动态IP成员(broadband)经 Leader 中继共享配额
|
|
2
|
+
|
|
3
|
+
> **状态**:规划完成,待实现(基于 v0.1.33)。用户确认:`--broadband` 为唯一语义,不设 `--relay` 别名;默认 `127.0.0.1` 监听。
|
|
4
|
+
|
|
5
|
+
## 1. 背景与问题
|
|
6
|
+
|
|
7
|
+
现有组模型(ADR-0005)假设组员均为公网 VPS,`A->D` 直连 `http://D公网IP:8989/v1/chat/completions` 用 `D` 的出口 IP 打 `opencode.ai`,实现 `IP级免费池` 分散。家庭宽带 `D` 有公网 IP 但:
|
|
8
|
+
|
|
9
|
+
1. **入站不可达**:无端口映射/CGNAT,`probeHealth GET http://D:8989/health` 恒 `fail`,`peer-race` 直接跳过,`D` 的配额永不被用。
|
|
10
|
+
2. **IP 动态**:`refreshGroupMembers` 透传旧 `myUrl`,IP 变更后 60s 同步期内全组仍用旧 IP。
|
|
11
|
+
3. **语义缺失**:组内无法区分 `static(VPS直连)` 与 `broadband(家庭中继)`,`-group list` 无标识。
|
|
12
|
+
|
|
13
|
+
需求:家庭 `D` 以 `broadband` 类型加入,无需公网入站,经 `Leader` 中继仍用 `D` 的家庭出口打上游,且全程可观测。
|
|
14
|
+
|
|
15
|
+
## 2. 决策
|
|
16
|
+
|
|
17
|
+
新增成员类型 `kind: "broadband"`(默认 `kind: "static"` 兼容老数据),`broadband` 隐含 `relay` 中继:
|
|
18
|
+
|
|
19
|
+
* **加入**:`mslxdff -addtogroup <leader-host> <group> --broadband`(唯一旗标,不设 `--relay`)。
|
|
20
|
+
* **监听**:`--broadband` 下默认 `listen 127.0.0.1:8989`,仅本机 `WorkBuddy` 可用;不加该旗标的为 `static` 走 `0.0.0.0`。
|
|
21
|
+
* **共享**:`D --WS--> Leader` 常驻出站,`A(429) -> Leader -> D -> opencode.ai -> D -> Leader -> A`,上游仍见 `D` 家庭 IP。
|
|
22
|
+
* **展示**:`-help` 新增用法行,`-group list / -status / -log` 标注 `[broadband] via leader Xs ago ip=...`。
|
|
23
|
+
|
|
24
|
+
## 3. 设计
|
|
25
|
+
|
|
26
|
+
### 3.1 成员模型
|
|
27
|
+
|
|
28
|
+
`state.json groups[name].members[id]` 扩展:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
"home-D": {
|
|
32
|
+
"url": "relay://home-D",
|
|
33
|
+
"token": "...",
|
|
34
|
+
"kind": "broadband",
|
|
35
|
+
"publicIp": "183.14.22.78",
|
|
36
|
+
"lastSeen": 1724212345678,
|
|
37
|
+
"status": {"upstreamOk": true, "latencyMs": 4200}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
* `static`:`url: http://IP:8989`,参与 `probeHealth`。
|
|
42
|
+
* `broadband`:`url: relay://id`,不探活,只看 `lastSeen`(`>90s` 标 `cooling`)+ `status`,`rankModels` 同 `slow` 5m 冷却共用。
|
|
43
|
+
|
|
44
|
+
### 3.2 连接与心跳
|
|
45
|
+
|
|
46
|
+
**D 端(家庭)**
|
|
47
|
+
* 解析 `--broadband`,建 `WS wss://Leader/v1/groups/relay/connect?group=my@mslxd`,`Authorization: Bearer <token>`。
|
|
48
|
+
* `hello {group, token, kind:"broadband", version}` → Leader 回 `welcome {memberId}`。
|
|
49
|
+
* `heartbeat 30s {status:{upstreamOk, models, load}}`,`publicIp` 由 Leader 的 `clientIp(req)` 填,不靠 `ifconfig.me`。
|
|
50
|
+
* 断线指数退避 `1s/2s/4s...` 重连,IP 变更导致 `TCP RST` 自动重建即完成 IP 更新。
|
|
51
|
+
|
|
52
|
+
**Leader 端(VPS)**
|
|
53
|
+
* `GET /v1/groups/relay/connect` 升级 WS,鉴权 `membersForToken`,存 `relayConns[group][id]=ws`,`ws.remoteIp=clientIp`。
|
|
54
|
+
* 每条 `heartbeat` 对比 `remoteIp` vs `members[id].publicIp`,变则 `saveGroups` + `evt relay-ip-change` 入 `events.log`。
|
|
55
|
+
* `lastSeen >90s` 或 `WS断开` 立即标 `cooling`,`A` 下次 `candidatesFor` 自动避开。
|
|
56
|
+
|
|
57
|
+
### 3.3 转发路径
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
用户 -> A: POST /v1/chat/completions {model: deepseek, stream:true}
|
|
61
|
+
A -> opencode.ai (用A IP) 429
|
|
62
|
+
A选 candidatesFor -> [B(static), home-D(broadband via Leader), ...] 按 latency EMA
|
|
63
|
+
A -> Leader: POST /v1/groups/relay/forward {target: home-D, body, hops:1, reqId}
|
|
64
|
+
Leader -> D (WS): {reqId, body:{model,messages}}
|
|
65
|
+
D -> opencode.ai (用D家庭IP) 200 SSE chunk*
|
|
66
|
+
D --WS {reqId, data:chunk}--> Leader --HTTP chunk--> A --SSE--> 用户
|
|
67
|
+
A断开 -> Leader --WS {abort reqId}--> D controller.abort()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
* `hops` 每跳 +1,`MAX_HOPS=3` 防环;`broadband` 节点自身 `429` 时可直接 `fetch` 到 VPS `static` 节点(出站直连,无需中继)。
|
|
71
|
+
* 多请求复用一条 WS,`reqId` 复用现有 `relay` 日志的 `reqId/detail` 串联。
|
|
72
|
+
|
|
73
|
+
### 3.4 可观测
|
|
74
|
+
|
|
75
|
+
* `-help`:` -addtogroup <host> <name> [--broadband] 宽带动态IP成员(经Leader中继,无需公网入站,默认127.0.0.1)`
|
|
76
|
+
* `-group list`:`3. relay://home-D [broadband] ok via leader 12s ago ip=183.14.22.78`
|
|
77
|
+
* `-log [N]`:`relay-ip-change / relay-forward / relay-heartbeat / client-abort` 均带 `reqId/detail`。
|
|
78
|
+
|
|
79
|
+
## 4. 实现清单
|
|
80
|
+
|
|
81
|
+
| 文件 | 改动 |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `bin/mslxdff.js` | `-addtogroup` 解析 `--broadband`,`WS` 客户端+心跳+重连,`listen` 在 `broadband` 下绑 `127.0.0.1`,`printHelp` 新增行,`groupSyncTimer` 对 `broadband` 组 30s |
|
|
84
|
+
| `src/groups.js` | 成员结构 `kind/publicIp/lastSeen/status`,`addGroupMember/upsertMember/syncPeersFromMembers` 支持 `broadband`,`refreshGroupMembers` 透传 `kind` |
|
|
85
|
+
| `src/peers.js` | 新增 `relayVia` 字段,`add({relayVia, kind}) / isRelay`,`broadband` 不进 `ordered()` 直连池 |
|
|
86
|
+
| `src/routes.js` | 新增 `WS /v1/groups/relay/connect` + `POST /v1/groups/relay/forward`,`forwardToPeer` 中继分支,`hops` 处理 |
|
|
87
|
+
| `src/state.js` | 持久化 `kind/publicIp/lastSeen`,新增 `load/save` 兼容 |
|
|
88
|
+
| `src/logs.js` | `relay-*` 事件类型 |
|
|
89
|
+
|
|
90
|
+
依赖:`ws`(或 Node 22 原生 `WebSocket`,服务端需 `upgrade` 处理)。
|
|
91
|
+
|
|
92
|
+
## 5. 验证
|
|
93
|
+
|
|
94
|
+
1. **IP变更**:`D` 拨号重拨,`WS` 重连,`events.log` 出现 `relay-ip-change old->new`,`-group list` IP 更新,`A` 经 `Leader` 仍命中 `D`。
|
|
95
|
+
2. **配额共享**:`A` 指定 `deepseek 429`,`-log` 显示 `peer-race via=relay-leader target=home-D`,`D` 的 `upstream-done 200` 且 `detail.exitReason:normal`,回包完整。
|
|
96
|
+
3. **本地可用**:`D` 本机 `curl http://127.0.0.1:8989/v1/chat/completions` 200,外网 `curl http://家庭公网IP:8989` 超时(符合预期)。
|
|
97
|
+
4. **`-help` / `-group list`** 均含 `broadband` 标识。
|
|
98
|
+
|
|
99
|
+
## 6. 风险与取舍
|
|
100
|
+
|
|
101
|
+
* `Leader` 单点中继增加 `10-30ms` 延迟,可接受;`Leader` 宕则 `broadband` 配额暂不可用(`static` 组员仍直连可用)。
|
|
102
|
+
* 多并发复用单 `WS` 需 `reqId` 复用与背压控制,首版可限并发 3(复用 `PEER_RACE_LIMIT`)。
|
|
103
|
+
* 不设 `--relay` 别名,术语统一为 `broadband`,内部变量 `relay` 仅作实现名。
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# ADR-0007:多供应商前缀路由
|
|
2
|
+
|
|
3
|
+
- 状态:**已采纳**(0.1.56)
|
|
4
|
+
- 日期:2026-08-27
|
|
5
|
+
- 关联:`docs/ARCHITECTURE.md` §3、`src/providers/`
|
|
6
|
+
|
|
7
|
+
## 背景
|
|
8
|
+
|
|
9
|
+
opencode.ai 的免费池会 429 限流,且模型种类有限。需要引入 OpenRouter 的免费模型池(`pricing.prompt/completion` 全 0,实测 20 个)作为第二通道。上游供应商各有自己的模型 id 空间、鉴权方式、品牌头要求,如果全部裸 id 混在一个 namespace 会冲突(同名 id 歧义)。
|
|
10
|
+
|
|
11
|
+
## 决策
|
|
12
|
+
|
|
13
|
+
采用**前缀路由**:
|
|
14
|
+
|
|
15
|
+
- 模型 id 对外形态:`<provider>/<raw-id>`;裸 id(无前缀)恒指默认供应商 **opencode**,向后兼容所有存量配置。
|
|
16
|
+
- 新增 `src/providers/`:每供应商一个文件,统一接口 `{ id, chat(body), listModels(), preheat, close }`。
|
|
17
|
+
- `dispatcher.js` 用 `splitModelId` 拆前缀路由到对应 provider,**转发前把 `body.model` 剥回原始 id**,上游看到的是未污染的 id。
|
|
18
|
+
- 未识别前缀回退:整个 model 当裸 id 交给默认供应商。
|
|
19
|
+
- 供应商发布模型清单时用 `joinModelId` 前缀化,客户端看到的完整 id 即为最终路由键。
|
|
20
|
+
|
|
21
|
+
## 后果
|
|
22
|
+
|
|
23
|
+
- 模型选择器(auto、modelPicks)工作在**完整带前缀 id** 上,天然区分供应商。
|
|
24
|
+
- 多 key 轮转(keyring)在 provider 内部,对路由透明。
|
|
25
|
+
- 默认供应商无 key 不需要;非主供应商(如 openrouter)无 key 时**不加载**,不报错。
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# ADR-0008:供应商 key 瞬时共享(peer 转发时附带)
|
|
2
|
+
|
|
3
|
+
- 状态:**已采纳**(0.1.57)
|
|
4
|
+
- 日期:2026-08-27
|
|
5
|
+
- 关联:`docs/ARCHITECTURE.md` §3/§5、`src/routes/peers.js`、`src/providers/dispatcher.js`
|
|
6
|
+
|
|
7
|
+
## 背景
|
|
8
|
+
|
|
9
|
+
多供应商架构(ADR-0007)下,组内接力对 model 字符串透明转发(peers.js `forwardToPeer`)。当 A 请求 `openrouter/xxx` 本地失败而转发给组员 B,若 B 未配置 openrouter key,B 的 dispatcher 找不到该前缀 → 回退 opencode 兜底 → 把 `openrouter/xxx` 原样发给 opencode 上游 → 失败。
|
|
10
|
+
|
|
11
|
+
同时存在分散 IP 级限流的机会:若 B 能用 A 的 key + B 自己的出口 IP 访问 openrouter,则组间接力既借力 B 的带宽/IP,又复用 A 的配额。
|
|
12
|
+
|
|
13
|
+
但 key 长期分发给组员有失控风险(明文泄漏、风控、配额被滥用),且上游限流维度不同(opencode 以 IP 为主、OpenRouter 以账户 key 为主)。
|
|
14
|
+
|
|
15
|
+
## 决策
|
|
16
|
+
|
|
17
|
+
增加**供应商级开关 `shareKeysToPeers`(默认 false)**:
|
|
18
|
+
|
|
19
|
+
- `false`(默认):现状,不发生任何 key 共享。
|
|
20
|
+
- `true`:本节点在 **outgoing 转发**(peer 接力或 broadband 接力)给组员时,若请求模型命中该供应商前缀,把本节点该供应商的 key 列表放私有请求头 `x-mslxdff-share-keys` 附带过去。组员**仅在该次请求内**用这份 key 访问上游,处理完即弃——**不落盘、不广播、不持久同步**。
|
|
21
|
+
|
|
22
|
+
锁定语义:
|
|
23
|
+
- **瞬时借用**:key 生命周期 = 一次转发请求。组员绝不把共享 key 写入本地 state,也不为其它请求复用。
|
|
24
|
+
- **仅转发时附带**:谁开启共享,谁在 outgoing 转发时带自己的 key;不向全组广播。本节点自己直接用,也带。
|
|
25
|
+
|
|
26
|
+
### 状态与配置
|
|
27
|
+
|
|
28
|
+
- state:`providerShareKeys: { <providerId>: boolean }`(空/缺失 = 默认 false)。
|
|
29
|
+
- env 覆盖:`MSLXDFF_<ID>_SHARE_KEYS`(`1/true/on` 视为 true)。
|
|
30
|
+
- CLI:`mslxdff -provider openrouter share on|off` 切换;`-provider openrouter status` 一并显示。
|
|
31
|
+
|
|
32
|
+
### 转发协议
|
|
33
|
+
|
|
34
|
+
出站转发(peer / broadband)时组装私有头:
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
x-mslxdff-share-keys: openrouter=sk-1,sk-2;other=sk-a
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
组员端收到:解析头 → 对该供应商 id **临时创建** provider(用头上 key)注入本次请求的 dispatcher 解析路径 → chat 完成后丢弃。
|
|
41
|
+
|
|
42
|
+
### 安全边界
|
|
43
|
+
|
|
44
|
+
- 只对**开启 share 的供应商**生效;key 总量被网关(本节点)控制。
|
|
45
|
+
- 组员只需能执行"本次请求用这个 key 调上游",拿不到长期凭证。
|
|
46
|
+
- 自定义其值约定触发风控:多个组员同时高频用同一 key 请求时,仍受上游账户级限制(不规避 key 级配额),仅叠加 IP 分散价值——这是决策接受的范围。
|
|
47
|
+
|
|
48
|
+
## 后果
|
|
49
|
+
|
|
50
|
+
- 组内接力对配了 key 的供应商可用率提升(不再无谓落到 opencode 兜底失败)。
|
|
51
|
+
- 蓝领风险可控:瞬时借用,default off,显式开启才生效。
|
|
52
|
+
- 迁移:无破坏。旧 state 无 `providerShareKeys` 字段 → 默认 false,行为等同现状。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# ADR-0009: 对话终端 mslxdff -chat(mimo 优先,独立进程,历史压缩)
|
|
2
|
+
|
|
3
|
+
- 状态:已接受
|
|
4
|
+
- 日期:2026-08-27
|
|
5
|
+
- 版本:0.1.58
|
|
6
|
+
|
|
7
|
+
## 背景
|
|
8
|
+
|
|
9
|
+
`mslxdff` 的 CLI 参数已达 30+,用户常说简称(`hy3`→`hy3-free`)且希望自然语言直接执行。需要一个常驻终端把自然语言翻译成精确命令,保持与 daemon 生命周期解耦,且不引入额外运维负担。
|
|
10
|
+
|
|
11
|
+
## 决策
|
|
12
|
+
|
|
13
|
+
- 新增 `mslxdff -chat ["prompt"]`:
|
|
14
|
+
- 无 prompt → 常驻 REPL(`mimo> `,readline,上下历史,`/help /clear /history /exit`)
|
|
15
|
+
- 有 prompt → 单次执行后退出
|
|
16
|
+
- 模型:**优先 `mimo-v2.5-free`,失败自动降级 `big-pickle`**(`chatWithFallback`),与上游共用 `createUpstreamClient`(`x-opencode-client: desktop` + `Bearer public`)。
|
|
17
|
+
- 工具仅 2 个:`run_command`(执行 `cli_help.md` 所列任意命令,**仅拦截 `-uninstall`**,`-stop`/`-port` 可执行,模糊匹配与精确性由大模型负责)与 `read_file`(限项目根 `pkgRoot` + `logDir()` + `stateDir`,超限截断)。
|
|
18
|
+
- 提示词:`docs/cli_help_mini.md`(AI 精简版,4.3KB)+ 实时可用模型列表(`models.json` 缓存或硬编码兜底)注入 system。
|
|
19
|
+
- 历史:`~/.config/mslxdff/chat-history.json`(`MSLXDFF_CHAT_HISTORY` 可覆盖),存最近 60 条;REPL 为前台独立进程(`bin` 中 `await startChat()` 直接跑,不经 `startDaemon`),daemon 重启不影响。
|
|
20
|
+
- 压缩:当 `estimateChars(messages) > 18000` 时触发——保留 `system + 最近 8 条`,将其余旧消息让**大模型自己摘要**(`summarizeHistory` 发一次 mimo/big-pickle 调用,约束“300 字内中文摘要,保留关键操作与偏好”),成功用 `【历史摘要】` 替代,失败则截断。原理是**用模型压缩模型**,避免固定截断丢上下文。
|
|
21
|
+
|
|
22
|
+
## 后果
|
|
23
|
+
|
|
24
|
+
- 正面:自然语言可用,模糊匹配由模型完成,无需代码归一;历史持久化且长期对话不爆上下文;独立进程语义清晰。
|
|
25
|
+
- 负面:每轮多一次上游调用(工具循环最多 6 次),且压缩时额外一次摘要调用。
|
|
26
|
+
- 兼容:不影响 daemon、group、provider 等现有链路;`printHelp` 新增 `-chat` 行。
|
|
27
|
+
|
|
28
|
+
## 备选
|
|
29
|
+
|
|
30
|
+
- 代码侧做 `hy3`→`hy3-free` 归一:被否,用户明确要求由大模型自行查精确名称。
|
|
31
|
+
- 把 chat 做成 daemon 子进程:被否,用户要求 daemon 重启不影响 chat。
|
|
32
|
+
|
|
33
|
+
## 关联
|
|
34
|
+
|
|
35
|
+
- `docs/cli_help_mini.md`(AI 专用手册)
|
|
36
|
+
- `src/chat/*`(config/store/tools/prompt/upstream/repl/index)
|
|
37
|
+
- `ARCHITECTURE.md §6/§7` 与 `cli_help.md §9`
|