1) dsh-server-docs/ 从工作区(原 E:\...\aliyun-dsh-server\dsh-server-docs)**整体并入本仓**,
保留目录名 ⇒ 仓库内 dsh-server-docs/... 的相对引用天然继续有效;旧目录(含其 .git)已归档到
工作区 _中间产物_待清理/,未随本提交带入。
2) .gitattributes:新增 `dsh-server-docs/** -text` —— 原文档库是 `* -text` + autocrlf=false,
必须保持纯 LF,否则会被本仓的 CRLF 规则翻掉。
3) 活引用里的绝对路径已全部改到新位置(docs 的 INDEX / README / scripts / skills + 用户级 skills
+ ~/.workbuddy/settings.json 的 hooks);历史档案(04-调整方案/、archive/)按「只增不改」未动。
⚠️ hooks 路径改动需「完全重启会话」才生效(配置是会话启动快照)。
4) 交接单/T08:新增 §16「生产整体切换执行记录」(形态 / 落地动作 / **4 个只有真上线才暴露的真 bug** /
验收证据 / 回滚命令 / 残留项);台账 T08 行 → 已完成并归档;03-路线图 §二 登记 T08 收尾项。
5) 统一称谓:**「本机」只指跑 WorkBuddy 的开发机**,47 / 106 一律写「远程服务器」。
8.4 KiB
T06 · 平台自建「模型设置」页 —— 多厂家条目自助配置(各自开关、可同时启用)
- 日期:2026-09-13
- 状态:✅ 已完成并归档(后端已部署 + 插件 0.3.11 已铺发两实例 + 角色补丁已刷新;验收四件事见 §6)
- 执行会话:
craft-session-T06(原 lane 会话rebuild-my-keys失联,经用户明确授权解锁后接手) - 触发(用户 09-13 22:5x 三轮口径):官方「设置 → 模型」页在平台环境必然报错 ⇒ 改走平台自建页;条目要能各自开关、平台共享模型也要列入且可开关;具体用哪个模型在 dsh 对话框里选。
- 档案:
04-调整方案/87-模型设置页-用户自配厂家与共享开关.md(本轮全部技术结论的单一来源)
1. 目标(已达成)
让每个用户在实例内「设置 → 模型设置」自助管理多条模型条目(内置 DeepSeek + 自定义 OpenAI 兼容厂家),每条可独立开关、可同时启用;平台按「已启用」把配置写进实例(
$DSH_HOME/.credentials.yaml的refs:+$DSH_HOME/settings.yaml的llm-pi-ai.providers.<route>),用户再到 dsh 对话框的模型选择器里挑具体模型。
2. 只读前置(执行时的核实结果)
| # | 核实项 | 结果 |
|---|---|---|
| 1 | 工作树里哪些是本轮改动 | src/db/*、src/web/{server.ts,routes/auth.ts,model-landing.ts}、ensure-role-profile-patch.cjs、poc/business-plugins/** = 本轮;admin-user-ops.ts / dsh.ts / portal.html / client.js 的改名段 = 档案 86 |
| 2 | npx tsc --noEmit 应报「类未实现接口」 |
✅ 如预期(sqlite.ts/pg.ts 各缺 5 项)→ 已补齐归零 |
| 3 | 「官方页为何不可用」的结论在哪 | ⚠️ 不在档案 86(86 = admin 跨用户实例管理 + 两处改名,全文仅 §一–§七);真结论原只在代码注释里。本轮已挖到权威出处(官方 dsh-client-ui-settings/README.md:97)并落进档案 87 §一 —— 同时辨清了与 03-路线图 决策 3 的「两层」关系(见档案 87 §1.1) |
| 4 | 线上状态 | 后端未构建未部署、插件 0.3.10 ✅(但角色补丁其实已被上一会话刷过——见 §7 教训) |
| 5 | 0.3.11 这个版本号没被用过 |
✅(本机 / /opt/dsh/artifacts / 两实例 profile 三方核对) |
3. 范围
改了(详见档案 87 §四):src/db/{schema,types,repo,adapter,sqlite,pg}.ts · 新文件 src/web/model-landing.ts · src/web/server.ts · src/web/routes/auth.ts · poc/business-plugins/lib/client.js + package.json(→0.3.11) · ensure-role-profile-patch.cjs · test/db.test.mjs · scripts/verify-{model-landing,platform-admin-section,mem-model}.mjs · package.json(注册新验证脚本)
没动(守住了边界):官方 dsh 主程序与缓存 · web/portal.html 的「模型管理」页 · 04-调整方案/86 · /var/lib/** 之外的系统面 · systemd 单元 · nginx/nft · 内存配额档位。
4. 决策点
- 用户已定三条口径(各自开关可同时启用 / 共享模型也列入且可开关 / 模型在 dsh 对话框选)—— 全程未再上抛。
- 技术自决(可推翻):共享开关走单列查询不动
USER_COLS;getEnabledCredentialKeyRef重定义为「已启用的内置 DeepSeek 条目」;selectCredentialKey降级为非互斥同义实现;落地用成对标记夹住自己写的 route 块。 - 开跑前唯一的未取证项(自定义厂家的 settings 字段名)= 已取证,且救回一个必须纠正的字段名:协议字段是
api,不是protocol(§5)。
5. 步骤与验证(全部执行完毕)
- 抢锁 + 占号 → ✅(原 lane 会话失联;用户明确授权解锁,随后自持
craft-session-T06) - 补
repo.ts两个函数 + 重定义getEnabledCredentialKeyRef→ ✅ - 取证官方 schema → ✅ 读
[email protected]:分区名llm-pi-ai、凭据字段apiKeyEnv、endpointbaseURL、协议api(三枚举)、ref 名须匹配^[A-Za-z_][A-Za-z0-9_]*$、provider/maxRetries会直接抛错 - 补
sqlite.ts/pg.ts各 5 项 → ✅tsc全绿 - 写落地层
model-landing.ts+ 13 组回归 → ✅(回归当场抓出一个真 bug:providers:被写重复 —— 第一版按"只看顶层行"找链,而providers:本身就是缩进 2 的子键) - 改
server.ts(逐条落地 + 托管清单 + 一次性交接)→ ✅ 见 §6③ - 改
auth.ts4 条路由 → ✅ 含 4 类非法入参被拒(400/409) - 前端「模型设置」分区(插件 0.3.11)→ ✅ 无浏览器 harness 全绿(新增 8 条断言)
- 角色补丁
--force --restart→ ✅ 先留回滚点再跑,跑完 diff 验证 - 收尾四件套 + 台账 → ✅
6. 验收(用户点名的四件事 —— 全部有证据)
| # | 验收项 | 证据 |
|---|---|---|
| ① | 官方「模型」栏两账号都不再出现 | 两账号 cordis.patch.yml 各 1 处 - id: ui-settings-models + disabled: true;--restart 已生效(客户端 bundle 于实例启动时重打)。页面实看留用户确认(本机 agent-browser daemon 起不来,沿用档案 86 §七-3 的做法) |
| ② | 新页能配 / 能开关 / 能删 | HTTP 实测(临时 session 直插,R4):新增内置条目 → effective: shared→own;新增自定义厂家 → route/base_url/api/models 全部落库;toggle 关/开 → enabled 0/1;非法入参:非 http baseUrl→400、缺 models→400、route 撞车→409、非法协议→400;前端 harness 全绿 |
| ③ | 共享模型开关真的影响实例落地 | 关共享 + 重启 → 实例 .credentials.yaml 里 DEEPSEEK_API_KEY 消失(老实现写下的那行被"一次性交接"正确认领并撤掉),实例进程 env 里该变量 = 0 条;再打开 → 重启 → 该行回来且与自定义厂家的 ref 并存 |
| ④ | 自定义厂家被 dsh 模型选择器认到 | 实例 settings.yaml 落地为官方 schema 原文(llm-pi-ai.providers.acctest-gw: {apiKeyEnv, baseURL, api: openai-completions, models: [...]});实例启动成功、journalctl 无 llm-pi-ai / settings-rejected 报错;删除该条目后重启 → 整块消失 |
本地全套(Node 22):单测 49 项 0 失败 + verify-inject / verify-static / verify-platform-admin-section / verify-mem-model / verify-model-landing 全绿。
7. 回滚
见档案 87 §七。要点:后端 npm run build && systemctl restart dshs;插件回落 0.3.10;角色补丁回滚件 = /opt/dsh/backups/patch-20260913-234511/(⚠️ 回滚它 = 把必然报错的官方页放回给用户,除非接受该副作用)。
8. 回报格式(已回填)
- 每步命令与输出:见档案 87 §五/§六;
- 取证结论:档案 87 §三(
api不是protocol,是本轮最重要的一条外部事实); - 产物:插件 0.3.11 / 48,590 B / md5
15c8083f5db85b3fce6a8a15db133319,两实例bundles=6且含@dsh-local/business-plugins; - 台账:本表 +
交接单/README.md §一+INDEX.md; - commit:未提交(用户未要求;且本机代码仓含别人在途的档案 86 改动,不能一起提)。
附:本轮暴露的三个「顺便修 / 已上报」项(都属本 lane)
- 🔴
ensure-role-profile-patch.cjs的整文件覆盖是颗雷(已修):它的--force升级分支原本writeFileSync(patchPath, block),而 admin 的cordis.patch.yml里同时住着三个平台块(disable-hmr/workspace-scoped-picker/ role patch)⇒ 一旦触发升级就会静默抹掉另两个(HMR 重新连、目录选择器退回无限制版)。修法 = 新增stripManagedBlock()只替换自己那段;实机 diff 验证另两块完整保留。 - ⚠️
verify-mem-model.mjs陈旧断言(已修):档案 86 重构后request.user!.id→user.id,该断言长期红着。 - ⚠️ 上一会话自述与线上不符:它称「未构建未部署」,但角色补丁实际已被它刷到线上(两账号的 patch 里已有
ADMIN_MODELS_BLOCK的内容)。⚠️ 教训:别拿会话自述当线上事实,一律看服务器实际文件 —— 这正是本次「先看服务器再动手」救回两颗雷的原因。