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

16 KiB
Raw Blame History

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)。