Files
dsh_shenxian/dsh-server-docs/04-调整方案/10-共享技能只读部署-bundledSkillDir.md
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

129 lines
9.3 KiB
Markdown
Raw Permalink 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.
# 10-共享技能只读部署(bundledSkillDir 层)
> 状态:**已实施(机制生效);管理面见档案 11**|2026-09-09|对象:dshs + dsh 0.1.2-rc.1
> 需求来源:能否上传 skill 让**所有用户可用、只允许使用不允许修改**?用 MCN V1.0 短视频脚本技能(290MB)做可行性分析 + 嵌套 subskill(browser-harness)能否在服务器运行。
> **TL;DR**|**结论**:共享只读技能层:dsh **原生支持** `bundledSkillDir`(全员可用、不可修改)。
> **关键**:实测结论:主技能放 bundled 只注册 1 个;**6 个 subskill 要各自独立目录平铺**才能成为可调技能。
> **状态**:✅ 机制已实施(管理面见档案 11)
## 一句话结论
**支持,且 dsh 原生设计了这一层**:`skill-filesystem` 的 **bundledSkillDir**(env `DSH_BUNDLED_SKILL_DIR`,rank 600)就是"系统标准技能层"——所有实例共享、只读、不可改(`trustedHost: true` 直读 + root 属主 OS 权限兜底)。**唯一卡点**:dshs 编排器 spawn 子进程时 env 走**白名单过滤**(`ALLOWED_ENV`),新变量不会自动透传,需改编排器 2 处(自研代码,**不触碰红线 2** 的官方 dsh 主程序)。
## 需求判定
| 子问题 | 结论 |
|--------|------|
| dsh 是否支持上传 skill 全员共享 | ✅ 支持,但**无"上传"界面**——机制是文件系统扫描 + env 注入指向共享只读目录 |
| 只允许使用、不允许修改 | ✅ bundled 层设计即此语义(详见机制表) |
| MCN V1.0(290MB)能否直接放 | ⚠️ 需裁剪(290MB 中 238MB 是 browser-harness 的 **Windows venv**,服务器 Linux 不可用)+ 拍平(见 subskill 结论) |
| 嵌套 subskill(browser-harness 等)能否被识别 | ⚠️ discoverRoot **只扫 root/<技能名>/SKILL.md 一层**,subskills/ 内技能不会自动注册为独立技能;需**拍平**到 bundled 根目录(每个子技能目录含自己的 SKILL.md 即可独立注册) |
| browser-harness 能否在服务器运行 | ⚠️ 机制上可行但工程量大:自带 envs 是 **Windows** 构建(`Scripts/browser-harness.exe`),Linux 需重建 venv + 装 headless Chrome + 依赖;且服务器为**海外节点**,直连抖音有风控/地区限制风险 |
## 机制调研表(源码级证据)
### 1. skill 装配链(谁在装载技能)
| 环节 | 位置 | 证据 |
|------|------|------|
| 注册插件 | `@deepseek-ai/dsh-agent-presets/presets/standard/agent.cordis.yml` L83-88 | `skill-filesystem`(dsh-skill-filesystem)+ `tool-skill`(dsh-tool-skill,提供技能目录/加载给 agent) |
| provider 实现 | `@deepseek-ai/dsh-skill-filesystem/lib/index.js` | `apply(ctx, config)` → `ctx.skills.registerProvider(...)` |
| env 来源 | 同文件 L84 | `config.bundledSkillDir ?? (includeDefaultRoots ? process.env.DSH_BUNDLED_SKILL_DIR : void 0)` → **preset 里无 config 块 → 走 env** |
| 只读语义 | 同文件 L142 `get()` | `parseSkillFile(locator.path, ctx, signal, candidate.source === "bundled")` —— bundled 走 trustedHost 直读 |
### 2. 技能分层 rank(roots 构建,L166-190)
| root | 路径 | source | rank | 修改权限 |
|------|------|--------|------|---------|
| project-dsh | `<项目根>/.dsh/skills` | project-dsh | 100 | 项目内 |
| project-agents | `<项目根>/.agents/skills` | project-agents | 200 | 项目内 |
| custom | config.customSkillDirs | custom | 300 | 配置方 |
| user-dsh | `$DSH_HOME/skills` | user-dsh | 400 | **用户自己**(每人独立,改自己的) |
| user-agents | `$DSH_AGENTS_HOME 或 ~/.agents/skills` | user-agents | 500 | 用户自己 |
| **bundled** | **`$DSH_BUNDLED_SKILL_DIR`** | bundled | **600** | **只读共享(trustedHost=true,无修改面)** |
> rank 600 注释 `trustedHost: true`:读取绕过沙箱 fs 服务走 Node 原生直读;技能源为系统标准层,模型侧无可写入口。
### 3. 扫描粒度(决定 subskill 命运)
- `discoverRoot(root)` = `readdir(root.path)` 单层 → 每个 entry:
- 目录 → 要求 `<entry>/SKILL.md` 存在
- `.md` 文件 → 直接视为技能
- `isPotentialSkillPath`:相对 root 最多 **2 段**(`root/<技能名>/SKILL.md`),更深(如 `root/主技能/subskills/子技能/SKILL.md`)**不被发现**。
- 结论:V1.0 主技能放 bundled 只注册「短视频工作台」一个;6 个 subskill 要成为独立可调技能必须**各自独立目录平铺**在 bundled 根。
### 4. 编排器 env 白名单(实施唯一卡点)
`/opt/dshs/lib/supervisor/spawn.js` L11-27:
```js
const ALLOWED_ENV = new Set(['PATH','HOME','USER','TMP','TEMP','TMPDIR','SYSTEMROOT','SystemRoot',
'PATHEXT','ProgramFiles','ProgramFiles(x86)','LANG','LC_ALL']);
```
`orchestrator.js baseEnv()`(L189-205)= `{...scrubEnv(process.env), HOME: ws, DSH_HOME: home, DEEPSEEK_API_KEY}`。
→ `DSH_BUNDLED_SKILL_DIR` 不在白名单 → 即使 systemd 里设了也会被 scrub 掉。**必须**:
1. `spawn.js` ALLOWED_ENV 加 `'DSH_BUNDLED_SKILL_DIR'`
2. `orchestrator.js` baseEnv 显式注入(建议读自身 config,如 `DSH_BUNDLED_SKILL_DIR ?? '/opt/dshs/bundled-skills'`)
3. src/*.ts 同步修改(保持与 lib 编译产物一致)
改的是**自研编排器**,非官方 dsh 主程序 → 不违反红线 2。
## 推荐实现方案(获批后执行)
| 步骤 | 操作 | 说明 |
|------|------|------|
| 1 | 服务器建共享目录 `/opt/dshs/bundled-skills/`(root:root 755) | 用户进程只读,OS 权限兜底不可改 |
| 2 | 改编排器 2 文件(spawn.js 白名单 + orchestrator.js baseEnv)+ src 同步 | 重启 `systemctl restart dshs` |
| 3 | 裁剪 V1.0 → 部署包(见下) | 290MB → ~30MB |
| 4 | 平铺部署:bundled 根下放 `mcn-workstation/`(主技能)+ 各 subskill 独立目录 | 主技能内 `subskills/` 相对引用保留(V1.0 内目录不动);独立注册的子技能解决"按名调用" |
| 5 | 重启 admin/guest 实例(client 面技能目录即时可见) | 实例重启走主域 Host `/api/dsh/restart` |
| 6 | guest 浏览器实测:技能目录可见 → 发起会话让模型列出技能 → 确认只读 | 见档案 09 同款验证栈 |
### V1.0 裁剪清单(290MB → 预计 <30MB)
| 内容 | 大小 | 处置 |
|------|------|------|
| `subskills/browser-harness/envs/` | 238MB | **删除**(Windows venv,Linux 不可用;服务器按需重建) |
| `mcn-work-shop/*.db.bak-*` | ~16MB | **删除**(4 份调试备份) |
| `mcn-work-shop/` 其余(server.js/tmp/png/.mjs) | ~4MB | 视需求——调试工作台,建议**不随技能分发**(保留 SKILL.md 引用则需评估) |
| `SKILL.md` + `references/` + `references-add/` + `scripts/` | ~1.3MB | **保留**(核心) |
| 6 个 subskill(除 browser-harness envs 外) | ~25MB | **保留 + 拍平** |
## subskill / browser-harness 服务器可行性
### 识别(注册)
- 每个 subskill 目录已含独立 `SKILL.md`(已逐一确认 6/6)→ 平铺到 bundled 根后**可独立注册**。
- 主技能 SKILL.md 的「子技能索引」用相对路径 `subskills/<名>/SKILL.md` 引用 → 主技能加载后模型可读文件;但要"作为技能被调用"需独立注册(dsh 无宿主递归扫描机制,WorkBuddy 的软链/递归扫描不适用)。
### 运行(browser-harness)
| 检查项 | 服务器现状 | 结论 |
|--------|-----------|------|
| 自带环境 | `envs/browser-harness/Scripts/browser-harness.exe`(**Windows**) | ❌ 不可直接用 |
| Chrome | 未安装(`which` 空) | 需装 headless chromium + 系统库(libnss3 等) |
| Node | v22.23.2 ✅ | 可跑 node 型脚本 |
| 网络 | 47.77.182.89 海外节点 | 抖音直连风控/地区限制风险高(MCN 数据抓取核心场景) |
| RedFox API 路径 | `REDFOX_API_KEY` 已具备 | ✅ API 型数据(榜单/诊断)不受浏览器影响,**优先走此路** |
→ 结论:browser-harness 服务器**机制可行**(Linux headless Chrome + 重建 venv),但投入大且抖音网页抓取在海外 IP 收益不确定;建议第一阶段**不启用** browser-harness,抖音类技能优先走 RedFox API 子技能;网页交互类需求待业务验证后再评估。
## 方案对比
| 方案 | 机制 | 全员可用 | 只读 | 改动面 | 结论 |
|------|------|:---:|:---:|------|------|
| **A. bundledSkillDir(推荐)** | env 注入共享只读目录 | ✅ | ✅(原生 trustedHost + OS 权限) | 编排器 2 文件 + 共享目录 | 正解,语义即"系统标准技能层" |
| B. 每用户 `$DSH_HOME/skills` | 编排器建号时播种 | ✅(复制分发) | ❌ 用户可改自己副本 | 播种逻辑 + 同步机制 | 不合"不可改" |
| C. repository 插件全局注册 | preset 注释提到"deployment registered globally" | 未验证 | 未验证 | 机制不透明 | 不做 |
## 验证记录
- 源码级证据全部取自行机服务器(2026-09-09 18:xx):装配链 / rank 常量 / discoverRoot 单层 / scrubEnv 白名单 / browser-harness envs 为 Windows venv。
- **已实施(2026-09-09)**:spawn.ts ALLOWED_ENV 加变量 + orchestrator.ts baseEnv 注入 + 建 /var/lib/dshs/bundled-skills;admin dsh 实例 `/proc/<pid>/environ` 已含 `DSH_BUNDLED_SKILL_DIR=/var/lib/dshs/bundled-skills`。
- 后续管理面(API+UI+个人技能)见档案 11。
## 回滚
- 编排器改动 revert 2 文件 + `systemctl restart dshs` 即可(bundled 目录删除即技能消失;实例无需逐个回滚)。