Files
dsh_shenxian/dsh-server-docs/archive/交接单-已完成/T01-档案16阶段3-4-实例内我的技能.md
T
admin 5ad755116e chore(docs): 文档库并入代码仓(R4 选 a)+ 索引/台账跟进
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 一律写「远程服务器」。
2026-09-15 18:47:13 +08:00

153 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# T01 · 档案 16 阶段 3/4 —— 实例内「我的技能」
- 日期:2026-09-11
- 状态:✅ **已完成**(2026-09-15 执行会话 `T01我的技能-0825` 落地;档案 **100**;插件 `business-plugins` **0.3.21** 已投放并验收)
- **阶段 3**(实例内入口)= ✅ 完成,且形态按 2026-09-15 修正为**并入「功能管理」内分组**(见 §一 横幅 / §四 决策 6 / §九)
- **阶段 4**(权限收口复核)= ✅ 收口:**本单未新增 section、未改 `ensure-role-profile-patch.cjs`** ⇒ 用户可见性**不变**,不存在"新 section 的开放范围"问题(详 §八 回报)
- 来源:`04-调整方案/16-页面导航定稿-技能插件管理面三决策落地.md §六` 阶段 3 / 阶段 4
- 规划会话边界:本单由**规划会话**产出(未 ssh、未改码);执行会话按单开工
---
## 一、目标
让**普通用户在实例内**能自助管理个人技能(上传 / 启用 / 禁用 / 删除),并在 dsh 设置「**功能管理**」页内看到「**我的技能**」分组;随后完成阶段 4 的**权限收口复核**。
**做完的判定**:普通用户(guest)在实例内 dsh 设置 →「功能管理」里看到「我的技能」分组,能上传一个 zip 技能并完成 启用 → 禁用 → 删除 全流程,且共享技能行显示为锁定(不可操作)。
> ⚠️ **2026-09-15 形态修正**:原述为「新增一个「我的技能」`settings.section`」;现改为**并入既有「功能管理」section 内分组**。理由与边界见 **§四 决策 6** 与 **§九**。
## 二、只读前置(开工前先核实,别推断)
| # | 要核实的事实 | 命令 | 期望 |
|---|---|---|---|
| 1 | 后端 API **确实已就绪**(档案 41 §实现 B) | `ssh bt-server "git -C /opt/dshs ls-files \| grep -E 'routes/skills' "` 再读该文件 | 存在 `GET/POST /api/skills/mine`、`POST /:name/enable`、`POST /:name/disable`、`DELETE /:name`,全部 `requireAuth` |
| 2 | 现有 client bundle 的 section 注册写法(照抄形态,勿自创) | `ssh bt-server "ls -l /opt/dsh/artifacts/ \| grep -i biz"` + 本机 `04-调整方案/poc/business-plugins/` | 拿到 `lib/client.js` 的 `settings.section` 注册片段与 package.json 的 `dsh.client` 元数据 |
| 3 | 铺开链路入口(新用户自动 + cron) | `ssh bt-server "git -C /opt/dshs ls-files \| grep -E 'ensure-biz' "` 再读该脚本 | `scripts/ensure-biz-plugins.cjs`(幂等、版本感知、自动取最新产物) |
| 4 | 实例页注入的既有脚本仍在(别撞车) | `ssh bt-server "grep -c '__dshRecover\|__dshAssist' /opt/dshs/src/supervisor/proxy.ts"` | ≥2(档案 50 / 56 两个注入并存) |
| 5 | 本次是否需要重启实例 | 取决于 #1:#1 通过则**本单不改服务端**,改的是 bundle → 需 `POST /api/dsh/restart` 或让用户在确认弹窗触发 | — |
> **判定口径提醒(重要,来自档案 18 v3 的教训)**:`dsh --dump-config` **不反映 bundle / profile patch 层** —— 连已生效的 `portal-entry` / `business-plugins` 行都不显示。**因此不能拿 `--dump-config` 当验收口径**(档案 16 §六 阶段 4 原文的这句写法已过时)。改用「**bundle 加载标记(看 journald)+ 实例页 UI 实测**」。
## 三、范围
**要改**(路径以 `git -C /opt/dshs ls-files` 与 `04-调整方案/poc/` 实际为准):
| 区 | 文件 | 动作 |
|---|---|---|
| client bundle | `.../client.js` | 在**既有「功能管理」`settings.section`(id `business-plugins`)内新增「我的技能」分组**(**不新开 section**,见 §四 决策 6):列表(含 `source/enabled/locked`)+ 上传表单 + 启用/禁用/删除按钮 |
| bundle 版本 | `package.json` | **必须升版本号**(pnpm 同版本会命中缓存 → 改了不生效,档案 18 踩坑 1) |
| 加载标记 | `.../lib/index.js` | 加一行 `[<bundle>] loaded …`(把回归从"看 UI"变成"看日志";档案 26 §建议 / B5) |
| 网关/路由 | 无需改(除 #1 发现 bug → 另立单) | — |
**明确不动**:
- ❌ 不动 dsh 官方包与缓存(红线 2);❌ 不改 `src/web/routes/skills.ts` 的鉴权语义
- ❌ 不动门户 `#/skills`(admin 面);❌ 不动 `workspace-scoped-picker` / `portal-entry`
- ❌ 不碰 `folder_plugins` / `enablePatch` 分支(档案 16 §八 保留说明:无业务调用,勿加功能)
- ❌ **不扩大任何权限**(API 已是 `requireAuth`,本单只是加 UI)→ **不触发 R5 权限扩大门禁**
## 四、决策点
| # | 决策 | 结论 |
|---|---|---|
| 1 | **扩展 `@dsh-local/business-plugins` 注册第二个 section**,还是**新建 `@dsh-local/my-skills`** | ✅ **已定(2026-09-12 用户拍板)**:**采用 A —— 扩展 `@dsh-local/business-plugins` 注册第二个 section**(用户原话「扩展 business-plugins 不用做两套」)。不新建 `my-skills` 包。理由:复用现成的「铺开 / 探活 / 版本感知 / 新用户自动铺 + cron 巡检」链路,成本最低;代价是 `business-plugins` 职责由一个变两个(在包 README 与本档案里说明即可)。 |
| 2 | 上传是否复用门户的 P0/P1 内容扫描 | **已定(必须复用)**:上传走平台 root 链路,档案 41 的 symlink P0 就是这类。UI 只负责把 zip POST 到 `/api/skills/mine`,扫描在服务端(档案 41 §A / 档案 19 §C6) |
| 3 | admin 是否也看到这个 section | **已定(档案 16 §五)**:所有用户(含 admin)都显示 |
| 4 | 是否顺手补 B5(给两个 bundle 加加载标记) | **已定(顺手做)**:零边际成本,正是本单要改的文件(`03-路线图` B5 行已降级为"顺手做") |
| 5 | 是否同时补门户 `#/files` 下载按钮(P3) | **不在本单**(另立) |
| 6 | **「我的技能」是并进既有「功能管理」section,还是新开一个 section** | ✅ **已定(2026-09-15,自决,可推翻)**:**并入「功能管理」内分组,不新开 section**。依据 ① 用户口径「不要分开管理」② 该分区已由档案 60 命名为**「功能管理」**——另立「我的技能」= 在"功能"之外再造一个平行概念,与口径反向 ③ 素材库 **U2**「不做两套实现,能扩展就不新建」④ 两者交互形态同构(列表 + 启停)。**代价**:一个 section 承载两组内容(决策 1 已认下)。⛔ **仅入口层合并 —— 机制层保持分离**:两条后端 API、两套落盘位置、两种生效方式都不合并(见 **§九**)|
## 五、步骤(每步自带验证)
1. **核实前置**:#1–#4 逐条执行;若 #1 缺端点 → **停**,回报"后端未就绪",不要自己补后端。
2. **读规范**:`06-工作台UI规范.md`(强制基线)+ `档案 16 §七` 设计点;参考 `04-调整方案/poc/business-plugins/` 现有 section 的写法与 token 用法。
*验证*:能指出新 section 用到的设计 token 来源(`--dsw-*`)。
3. **写 UI**(决策 1 选定的载体):列表字段 = 名称 / 来源(shared·user)/ 状态(enabled·disabled)/ 锁定标记;操作 = 上传(zip)、启用、禁用、删除;共享行 `locked:true` → 按钮禁用。
*验证*:本地 `npm run build`(或该 bundle 的构建命令)通过。
4. **升版本 + 打包 + 安装**:`pnpm add file:<tgz>`,**`HOME=<ws>` 必须设**,且 **guest 的 profile 是 pnpm workspace 根需加 `-w`、admin 不需要**(档案 25 踩坑)。产物放 `/opt/dsh/artifacts/`,**不要复制进用户 ws**(档案 28 的平台污染 bug)。
*验证*:`ls` 确认 `lib/client.js` 与 `lib/index.js` **都在**(档案 18 踩坑 2:scp 静默失败);比对 md5。
5. **重启 + 探活**:走编排器 `POST /api/dsh/restart`(按 uid 选 pid);或复用 `restartAndProbe` 的探活口径(档案 34)。
*验证*:`journalctl -u dshs --since '5 min ago'` 出现 `[<bundle>] loaded`,且**无** `duplicate loader entry id`(档案 25 事故特征)。
6. **端到端实测**(用 R4 模板:注册 → admin approve → 用完 DELETE,临时会话 `poc-*` 用完即删;**禁止用真实账号做 API 登录测试**):上传 → 禁用 → 启用 → 删除。
*验证*:见 §六 B–F。
7. **阶段 4 权限收口复核**:确认新 section 对 guest 可见、且**没误伤** guest 已禁的 `ui-settings-*` 裁剪(档案 09 / 16 §六 阶段 4)。
*验证*:用**实例页实测 + 加载标记**判定(**不要用 `--dump-config`**,见 §二 提醒);输出一份"开 new section 前后 guest 可见 section 列表"对照。
8. **归档**:新建档案(**原子占号** `mkdir 04-调整方案/.lock-<NN>`;当前下一号见 `README` 的「下一号」);更新 `INDEX.md §二` 对应行状态、`03-路线图` 勾销阶段 3/4 行、`README` 下一号;`git mv` 本单到 `archive/交接单-已完成/`。
*验证*:`python3 scripts/docs-audit.py` 退出码 0。
## 六、验收标准(可被第三方复现)
| # | 命令 / 动作 | 期望 |
|---|---|---|
| A | `ssh bt-server "cd /opt/dshs && bash scripts/ci.sh"` | 退出码 **0**,单测全绿 |
| B | 硬刷新 guest 实例子域页 → dsh 设置 →「**功能管理**」 | 页内出现「**我的技能**」分组(**与「功能插件」同页**),列出共享技能(显示锁定)与个人技能 |
| C | 上传一个正常 zip 技能 | 200,列表出现该项且 `enabled`;`$DSH_HOME/skills/<name>` 存在 |
| D | 点禁用 → 点启用 → 点删除 | 禁用后文件在 `home/skills-library/<name>`;启用后回到 `skills/`;删除后两处均无 |
| E | 上传与共享技能同名的包 / 对锁定行操作 | **409**(档案 41 的守卫生效) |
| F | 实例页仍含两个注入脚本 | 页面源码含 `__dshRecover` **且** 含 `__dshAssist` |
| G | `journalctl` | 有 `[<bundle>] loaded`;无 `duplicate loader entry id`、无崩溃重启 |
## 七、回滚
- **bundle 回退**:装回上一版本 tgz(版本号必须不同,否则 pnpm 命中缓存)→ 重启实例;或让该 section 不注册。
- **本单不改服务端** → 服务端无需回滚;若 #1 阶段确实改了服务端,按 `cp <file>.bak-<ts> <file> && npm run build && systemctl restart dshs`。
- **确认无残留**:`ls <profile>/node_modules/@dsh-local/` 与 profile 的 `dsh.profile.bundles` 一致。
## 八、回报格式(执行会话填)
```
## T01 执行回报
- 决策 1 选定:A · 扩展 business-plugins(2026-09-12 用户拍板);形态修正为「并入既有「功能管理」内分组」(2026-09-15,见 §四 决策 6)
- 档案号:100
- 插件版本:`@dsh-local/business-plugins` 0.3.20 → **0.3.21**(产物 70,758 B / sha256 `77429d2c…`)
- 验收:A ✅ / B ✅ / C ✅ / D ✅ / E ✅ / F ✅ / G ✅(**逐项证据见下**)
- 偏离:① 形态由「新增 section」改为「并入既有 section 分组」(有依据,见 §四 决策 6);② **新增** `scripts/verify-my-skills.mjs` 并纳入 `npm run verify`(原单未要求,但把"不换行 / 危险操作二次确认 / 锁定行无按钮"钉成断言可防回退);③ 未改动服务端(同原单预期)
- 证据:
· A 本地 `npm run verify` 全绿(词典 zh/en 各 366 键、引用键 343 个全部已声明)
· B 两实例 profile 均 0.3.21;`msk.group` 命中 3 / `bp-skillRow` 命中 4
· C 壳页 combo 出现 `@dsh-local/business-plugins`;壳页 58,126 B → 58,724 B;`rev=533537bbc02b`
· D bundle HTTP 200 · **11,363,655 B** · 含 `msk.group` / `msk.replaceWarn` / `bp-skillRow` / 「我的技能」
· E 端到端 **19/19**(一次性用户,用完即删):上传 → 列表 enabled → 同名 `conflict`+`stagedId` → `apply` 全量替换 → 停用(文件落 `skills-library`、不在 `skills`)→ 启用(回到 `skills`)→ 删除(两处皆无、列表消失)
· F 对共享技能 `platform-capabilities` 的 disable / delete **均 409**;非 `.zip` **400**「仅支持 .zip 文件」
· G 浏览器(`agent-browser`)**全链路零缺陷**:设置→「功能管理」可见「我的技能」分组(与「功能插件」同页);共享技能行显示 `共享|🔒只读|已启用|1 个文件 · 2.9 KB` 且**无动作按钮**;展开上传面板 → 选 `poc-skill-demo.zip` → 点「上传」= `POST /api/skills/mine` **200** 且页面提示「✓ 已上传技能…」、列表新增 `我的|已启用|3 个文件 · 575 B|[停用][删除]`;点「删除」出确认弹窗(点名 + "不可恢复")→ 确认 = `DELETE` **200**、提示「✓ 已删除…」、列表回到只剩共享行。截图与逐条证据见档案 100 §8.4
- 阶段 4 复核:本单**未新增 section**(形态 = 既有 section 内分组)⇒ 段位可见性零变化;`ensure-role-profile-patch.cjs` **未改**(我的改动清单 = client.js / package.json×2 / verify-my-skills.mjs);实例侧 `cordis.patch.yml` 的禁用项(`ui-settings-models` / `-settings-plugins` / `-plugin-inventory` / `ui-cordis`)原样保留
```
---
## 九、口径依据与适用范围(2026-09-15 追加,本单形态修正的由来)
**问题**:「把 skill 和插件都看成功能(能力),统一管理」这条提法,**适用于用户管理个人 skills 吗**?
### 9.1 原文与该提法的适用范围
- **原话**(2026-09-12 09:18,会话 `ca58e09f`):「你是方案规划 不是执行,在明确一下 A 按照最佳方案处理 B 按照你的建议处理 **!业务技能能否打包到插件中 一起安装和使用,不要分开管理!** C D 按照最优方案处理」
- **它落地成什么** = **投放链路的合并**,即 `archive/交接单-已完成/T03` **§4.1「技能随插件包投放」**:整合包内置 `skills/` + `skills/.manifest.json`,插件 host 面每次实例启动跑 `ensureSkills()` 幂等对账;**与共享技能层互斥**(同名会按 rank 抢)。对应素材库 **U4「交付面越少越好」**。
- **适用对象 = 平台/业务侧投放的技能**(MCN 这类)。判据内核 = 用户心智「我装一个插件就全有了」,优于"分层更干净"。
### 9.2 对「用户管理个人 skills」——分两层,结论相反
| 层 | 是否适用 | 依据 |
|---|---|---|
| **机制 / 投放层** | ❌ **不适用** | 个人技能**不是投放物**,是用户自己上传的产物 —— 没有"第二条投放链路"可合并,也没有"插件包"能装它;硬套会把「用户自己产生的东西」塞进「平台投放的包」,方向反了。而且两向隔离**已被现决策刻意设计**:`T03 §4.1` 第 4 条明确「**不覆盖用户自建的同名技能**(目录存在但无 `.installed.json`)→ 跳过 + 日志 + UI 提示"被同名技能占用"」。⇒ 二者**必须保持两条独立通道** |
| **入口 / 心智层** | ✅ **适用** | 正是本单形态修正的原因(§四 决策 6):实例内那一栏已由档案 60 命名为**「功能管理」**,再另立一个「我的技能」section = 在"功能"之外再造平行概念,与「不要分开管理」反向 |
### 9.3 ⛔ 不能一起"统一"掉的机制差异(避免过度统一)
| 维度 | 功能插件(候选池) | 个人技能(用户自传) |
|---|---|---|
| 来源 | admin 投放(门户 → 候选池) | 用户自己上传 zip |
| 生效方式 | **需重启实例** | **watch 即时生效,无需重启** |
| 默认态 | 进池**默认禁用** | 上传即启用 |
| 平台共享层的语义 | 投放进池、用户自助启用 | **投放即生效且锁定只读**(不造开关 —— 档案 16 §2.1 决策 2「技能与插件启用语义不同」)|
| 用户可否删除 | 可禁用(包由平台管)| **可启用 / 可禁用 / 可删除**(档案 41 用户决策)|
| 落盘位置 | profile `node_modules` + `dsh.profile.bundles` | `$DSH_HOME/skills` 启用位 / `home/skills-library` 禁用位 |
| 后端 API | `/api/plugins/*` | `/api/skills/{shared,mine}/*` |
⇒ 本单"合并"的**只有可点击的那一层**;后端、落盘、生效链路一律各自独立。
### 9.4 相关档案
`04-调整方案/16`(导航定稿 + 三层归属 + §2.1 决策 2)· `04-调整方案/41`(技能上传安全加固 + 用户启停)· `04-调整方案/60`(分区改名「功能管理」)· `04-调整方案/11`(技能管理面)· `archive/交接单-已完成/T03 §4.1`(技能随包投放)· `skills/dsh-decision-method/references/素材库-U-用户决策.md`(U2 / U4 / U10)。