# 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: - 目录 → 要求 `/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//environ` 已含 `DSH_BUNDLED_SKILL_DIR=/var/lib/dshs/bundled-skills`。 - 后续管理面(API+UI+个人技能)见档案 11。 ## 回滚 - 编排器改动 revert 2 文件 + `systemctl restart dshs` 即可(bundled 目录删除即技能消失;实例无需逐个回滚)。