Files
workbuddy_skills/dsh-workflow/references/dsh-change-workflow/03-沙箱与技能机制.md
T
admin e03465c398 按用户令提交:把此前未纳管的 9 个技能目录一并入库
用户令逐字:「E:/ProgramData/.workbuddy/skills  提交仓库是指的这里」——
即本目录就是仓库(2026-10-07 已在本目录建仓,见当日日志 §22),本轮把余下未纳管的 9 个技能一并提交。

本次入库(9 个技能,46 个文件):
1、`AI HOT`
2、`draw-ui`
3、`dsh-diagnose`
4、`dsh-knowledge`
5、`dsh-local-env`
6、`dsh-opensource-release`
7、`dsh-workflow`
8、`oil-motion`
9、`skills-security-check`

提交前核对:
· **凭据类扫描**(`*.env` / `*token*` / `*.key` / `*secret*` / `*.pem`)⇒ **零命中** ✓;
· 体积合计约 20 MB(`draw-ui` 12M + `oil-motion` 6.5M 是大头,形态为配图/素材 ——
  仓库 `.gitignore` 里明写「`assets/*.png` 是内容不是产物」⇒ 属刻意入库);
· 运行产物仍按既定规则排除(`__pycache__` / `logs/` / `tmp/` / `.venv/` / `*.egg-info` / `uv.lock` / `.workbuddy/`)。
2026-10-08 22:29:08 +08:00

8.3 KiB
Raw Blame History

机制速查(一)· Profile 层 cordis patch / Skill 装载机制 / Skill 管理面

归属:技能 dsh-change-workflow 的详情档(按需读,不是每次都要读)。 本档覆盖:Profile 层 cordis patch 机制 · Skill 装载机制 · Skill 管理面 · dsh 沙箱与权限预设机制(原行 L481–L504 + L555–L600)。 主文件 / 判据与流程主干 = ../SKILL.md(§1 六阶段流程 · §2 红线 R1–R8 原文速查 · §3 本机 Git Bash 环境坑 · §4 并行调度结论)。 来源:2026-09-22「技能重组线」把 ../SKILL.md 的 L481–L504 + L555–L600 段逐行原样下沉到本文件,未改一字。 跨档引用:正文里的「§N / 见 §8 坑 N / 见下表」等编号,用 ../SKILL.md 末节「详情索引」的原章节列定位。 维护:本文件与 ../SKILL.md 的指针行成对;改内容时同时核对主文件的指针描述是否仍准确。


Profile 层 cordis patch 机制(角色化 UI 裁剪,档案 09)

  • client 插件行 id = 短 id(ui-settings-models,非包名),见 dsh --profile web --dump-config。
  • 禁用官方 client 插件(如设置面板「模型」分区对普通用户):profile cordis.patch.yml 写 - id: <短id>\n name: "@deepseek-ai/<包名>"\n disabled: true(dsh-app-boot applyEntryPatches:非 insert patch 按 id 合入 overrides)。--dump-config 验证:目标行出现 disabled: true + # == ... patched by <path> 注释。
  • 生效必须重启实例(client bundle 启动时打包);patchReload: live 对 client 插件增减不生效(实测 5 轮)。
  • 当前生产 spawn 不带 --patch overlay(enablePatch=false),disable 只能写 profile 层 cordis.patch.yml。
  • 幂等工具:/opt/dshs/ensure-role-profile-patch.cjs [--restart] <username>(全量=非 admin 用户;含管理标记头则跳过;非默认空内容不覆盖)。admin profile 不动 = admin 保留该分区。

Skill 装载机制(全员共享只读技能 = bundledSkillDir,档案 10)

  • 技能由 agent preset 注册:@deepseek-ai/dsh-agent-presets/presets/standard/agent.cordis.yml L83-88(skill-filesystem + tool-skill),preset 无 config 块 → provider 配置走 env。
  • 技能分层 rank(dsh-skill-filesystem/lib/index.js roots()):project-dsh 100(<项目>/.dsh/skills)/ project-agents 200 / custom 300 / user-dsh 400($DSH_HOME/skills,每用户独立)/ user-agents 500 / bundled 600($DSH_BUNDLED_SKILL_DIR,trustedHost: true 只读共享)。
  • 扫描粒度:discoverRoot 只扫 1 层(root 下每目录须含 SKILL.md;或直接 .md 文件);root/主技能/subskills/子技能/SKILL.md 不会被发现 → subskill 要独立可调需平铺到技能根。
  • 编排器 env 白名单(关键卡点):/opt/dshs/lib/supervisor/spawn.js ALLOWED_ENV 仅 PATH/HOME/USER/TMP/LANG 等基础变量;baseEnv() = {...scrubEnv(process.env), HOME, DSH_HOME, DEEPSEEK_API_KEY} → 新 env 变量必须同时加白名单 + baseEnv 显式注入(src/*.ts 同步),否则被 scrub 丢弃。这是改编排器(自研,非红线 2 对象)。
  • MCN V1.0 技能跨平台部署注意:subskills/browser-harness/envs/ 为 Windows venv(Scripts/*.exe),Linux 服务器不可用需重建;海外节点直连抖音风控风险高,数据类优先 RedFox API。

Skill 管理面(编排器 API + 静态页,档案 11)

  • dsh 技能名硬规则:/^[a-z0-9]+(?:-[a-z0-9]+)*$/(@deepseek-ai/dsh-skill/lib/index.js SKILL_NAME)—— 中文名技能 dsh 静默丢弃。MCN 等含中文名技能需先改 SKILL.md frontmatter name 为 kebab(如 mcn-workstation)。
  • API 形态:/api/skills/shared(admin 三件套 GET/POST/DELETE)+ /api/skills/mine(任何登录用户);上传 base64 body { file, filename, force? };后端走系统 tar/unzip 解压、路径穿越校验、合法名校验。包结构:单顶层目录 + 含 SKILL.md。
  • 落盘属主:shared → root:root 0755(OS 权限兜底只读);mine → home 属主用户 uid/gid(确保用户可改自己的技能)。踩坑:chownTree 须 chown 顶层目录自身(首次实现只 chown 子项,目录保留 tar 包内 owner)。
  • watch 即时生效:skill-filesystem 对共享根 watchManager 监听 addDir/unlinkDir/SKILL.md → 自动 invalidate registry(无需重启实例,源码已核实)。
  • 编排器 env 注入 DSH_BUNDLED_SKILL_DIR:spawn.ts ALLOWED_ENV 加变量 + orchestrator.ts baseEnv 显式注入(config.bundledSkillDir,默认 <dataRoot>/bundled-skills);这是上一节「全员共享只读技能层」的实例侧落地。

dsh 沙箱与权限预设机制(2026-09-11 源码核实,改默认值必看)

两层隔离,别混淆:

层 谁提供 说明
内层 dsh 自带沙箱 read-only / workspace-write / danger-full-access 靠 Landlock 或 bubblewrap 后端。本机不可用:内核 5.10(Landlock 需 5.13+,LSM 无 landlock),bwrap 嵌套探测失败 → fail-closed 拒绝执行任何 shell(@deepseek-ai/dsh-sandbox/lib/index.js:185)
外层 平台 systemd-run --scope + bwrap + setpriv 真正的边界:mount 路径隔离 + uid 隔离 + cgroup(基础 448M → 上界 1024M:MemoryHigh=MIN 软限 + MemoryMax=MAX 硬限;与插件开关无关(档案 96)/150%/128)。内层失效不影响它

权限预设插件(row @deepseek-ai/dsh-permission-presets,短 id 一般即 permission-presets):

  • 内置预设表(sandbox 与 approval 成对绑定,这是关键坑):
    'workspace-write':    { sandbox: 'workspace-write',    approval: 'ask'   }
    'danger-full-access': { sandbox: 'danger-full-access', approval: 'never' }  // ← 选它=同时摘掉审批
    
  • defaultPreset = config.defaultPreset ?? inferredDefault;默认落 workspace-write(→ 本机即「shell 全废」)。
  • 设置持久化命名空间 = permission(settings key permission.defaultPreset,值必须是 presets 里的键名)。

改默认值的官方做法(不碰官方主程序,R2 合规):

⭐ 首选:DSH_PERMISSION_MODE 环境变量(2026-09-11 定案,档案 33)。 官方 @deepseek-ai/dsh-base/cordis.patch.yml:217/233 直接读它:

mode:   !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
policy: !!js "(process.env.DSH_PERMISSION_MODE ?? 'workspace-write') === 'danger-full-access' ? 'never' : 'ask'"

在编排器 spawn 时注入 DSH_PERMISSION_MODE=danger-full-access 即可 全局默认「完全权限」+ 免审批,且: ① 不必铺 profile(env 随 spawn,新用户自动生效);② 完全复用官方逻辑(预设表 / 设置 UI 都不动)。 落地两处:spawn.ts 的 ALLOWED_ENV 加该键 + orchestrator.ts 的 baseEnv() 注入(值取 process.env.X ?? 'danger-full-access',运维可覆盖)。 ⚠️ 副作用:权限档位是会话级播种(会话创建时写 permission/preset / sandbox/mode / approval/policy 三条种子事件)→ 旧会话不跟随新默认,必须新建会话才生效。给用户的话术是「新开一个会话」,不是「刷新页面」。

其余做法(需要更细粒度时才用):

  1. profile 层 cordis.patch.yml 改该行 config —— 已核实 applyEntryPatches 里 const { id, insert, name, ...overrides } = patch → patch 的其它键会作为 overrides 合入目标行,所以 config: 可覆盖。 注意 config 是整体替换不是深合并 → 必须把内置的两个预设一并写全。
  2. 或改设置:$DSH_HOME/settings.yaml 加
    permission:
      defaultPreset: <presets 里的键名>
    
  3. profile 是每用户一份 → 要像 ensure-role-profile-patch.cjs 那样给每个用户(含新用户)铺;改完必须重启该用户实例才生效。
  4. 用户仍可在实例「设置 → 权限」自助切换(ui-permission),这是设计内的。

推荐形态:不要直接用 danger-full-access(会连审批一起摘掉)。在 patch 里新增一个自定义预设: sandbox: danger-full-access + approval: ask,语义 = 「隔离由平台容器提供,高风险操作仍需确认」。