DeepSeek Harness 的权限预设命名:README 与源码三处都不一样
翻 deepseek-harness 的 packages/interaction/permission-presets/ 这个包时,会碰到一件挺容易踩的事:同一个东西,包 README 里叫 permissionPresets,源码里叫 permission。而且不是一处,是三处——事件名、斜杠命令名、settings 命名空间,三个都对不上。
先把话说在前面:本文只陈述差异、标明两边各在哪个文件哪一行,不推断哪个是对的、也不推断为什么会这样。我们没有安装、没有运行过这个项目,下面所有内容都是在仓库快照 47f9438(核对日 2026-08-16)上读文件读出来的。另外这个仓库建立于 2026-08-13,版本是 0.1.0-rc.5,一个 GitHub Release 都没有,README 自述处于开发者预览阶段并明确写了未来会有破坏兼容性的变更——所以下面提到的任何名字,都可能在你读到这篇时已经变了,请以仓库当前内容为准。
三处差异一览
| 差异点 | 包 README 里的写法 | 源码里的写法 |
|---|---|---|
| 会话事件名 | permissionPresets/preset | permission/preset |
| 斜杠命令名 | /permissionPresets | name: 'permission' |
| settings 命名空间 | permissionPresets | settingsNamespace('permission') |
三处的具体位置在下面各自展开。
差异一:事件名
包 README(packages/interaction/permission-presets/README.md)里,permissionPresets/preset 这个名字出现了三次:
:7写set(session, name)会「records a changed selection in a log-onlypermissionPresets/presetevent」;:9写新会话创建时会 pin 下permissionPresets/preset、sandbox/mode、approval/policy三件事;:17又写「permissionPresets/presetitself is log-only」。
源码这边,实际 append 出去的事件名是 permission/preset。位置有三个:packages/interaction/permission-presets/src/index.ts:383(apply() 里在选择发生变化时追加)、:409 与 :422(新会话初始化 pinInitialPermission() 的两条路径,该函数的范围是 :400-430)。事件的类型声明也在同文件的 :50。
同一个名字在仓库里还有三处旁证,写的都是带斜杠短名的那个:
docs/subsystems/permission-presets.md:66-68;docs/subsystems/README.md:45;packages/core/session/src/known-event-types.ts:39——这是会话已知事件类型的登记清单,登记的是permission/preset。顺带一提,这份清单里以'...',形式列出的事件类型我们数出来共 44 个,approval/asked、approval/decided、approval/policy、command/run、command/done、tool/call、tool/result这些都在里面。
也就是说,README 的三处是一个口径,源码 + 子系统文档 + 事件登记表是另一个口径。以我们实读的仓库状态为准,源码追加的是 permission/preset。
差异二:斜杠命令名
包 README 的 :13 提到的是「the /permissionPresets command」。
源码里注册这个命令的位置是 packages/interaction/permission-presets/src/index.ts:258-261,注册的 name 是 'permission',description 原文是 Switch the permission preset (sandbox mode + approval policy),input 的 hint 是 <preset>。handler 的两条分支文案也在源码里(:266-271):参数为空时返回 current preset <名> (available: <逗号分隔>),名字不认识时返回 unknown preset "<名>" (available: ...)。
同一个仓库里还有第三处提到这个命令:packages/client/ui-permission-presets/README.md:13,那边写的是 /permission,和源码一致。
命令注册这块还有一条相关的口径可以对照:docs/subsystems/commands.md:27-42 描述的 CommandDefinition 里,name 字段要求是小写、不含前导斜杠的;同页 :140-158 写了命令的生命周期日志——command/run 在 handler 之前 append,command/done 在结算之后,抛错或被 abort 都结算为 kind: 'error',而语法错误或未知命令什么都不记。这一条对排查有用:如果你想验证某个命令名是否存在,不能指望它在会话事件里留下痕迹。
差异三:settings 命名空间
README 的 :9 写「The service owns the permissionPresets Settings namespace」,:28(Known Limitations 那节)也写「the permissionPresets section in settings.yaml」。
源码里导出的常量是 export const PERMISSION_SETTINGS_NAMESPACE = settingsNamespace('permission'),位置在 packages/interaction/permission-presets/src/index.ts:73。
同样,只陈述到这里为止。
自己复核的动作
这三处都不需要跑任何东西,读文件就能确认,方向也很明确:README 一侧、源码一侧各看一眼,再看第三方位置(子系统文档、事件登记表、另一个包的 README)站在哪边。
在你 clone 下来的仓库根目录,用通用的文本检索命令扫这个包就行——注意下面两条是操作系统自带的检索命令,不是该项目提供的命令:
# Linux / macOS / Git Bash
grep -rn "permissionPresets" packages/interaction/permission-presets/
grep -rn "permission/preset" packages/
# Windows PowerShell
Get-ChildItem -Recurse packages/interaction/permission-presets | Select-String -Pattern "permissionPresets"
对照着看三件事:README 里出现 permissionPresets 的行、src/index.ts 里 append('permission/preset', ...) 与 commands.register({ name: 'permission', ... }) 的行、以及 packages/core/session/src/known-event-types.ts 里登记的那一条。三处看完,你手上就有和本文一样的原始依据,不必信我转述。
为什么这三个名字值得逐个核
因为它们各自落在不同的消费面上,谁也不能替谁:
事件名是会写进会话日志的。 这个包做的事是把两个旋钮打包:一个预设同时决定 sandbox/mode 与 approval/policy。apply() 的顺序是先在「当前预设 ≠ 目标预设」时追加 permission/preset 事件(src/index.ts:382-384),再仅在该旋钮的有效值确实变化时分别调 setSandboxMode 与审批写入器(:386-391)。所以选择事件在前、旋钮事件在后;如果你要写工具去解析会话日志、统计权限变更,事件名写错就一条都匹配不到。
命令名是用户要敲进去的。 源码注册的是 permission。至于按 README 那个名字敲会发生什么,我们没有运行过、不做断言——但要确认一个命令到底叫什么,去 commands.register 的注册处读 name 字段,比照 README 更直接。
顺带说一句「当前预设」这个名字是怎么算出来的(apply() 里判断要不要 append 事件时比对的也是它,见 src/index.ts:382-384 的 current(...) !== name):derive() 的兜底链在 src/index.ts:310-311——sandbox 取 state.sandbox ?? this.ctx.shell.sandboxMode,approval 取 state.approval ?? this.ctx.approval.config.policy ?? 'ask';匹配顺序则是先看「仍然匹配的上次选择」,再按表的声明顺序取第一个匹配,两者都不中就返回 CUSTOM_PRESET(:314-320)。所以报出来的名字既取决于会话里已有的事件,也取决于组合进来的默认值,这两层都能在源码里逐行核到。
settings 命名空间名决定你在 settings.yaml 里写哪个段。 README 的 Known Limitations(:24-28)里列了四条标题,其中两条与这个段名直接相关:「Stored defaults must remain in the preset table」与「The preset table is process-level」——后者按该节的说法,改预设需要重载插件。另外两条是「Only two mechanism knobs are bundled」(agent / profile 的选择还不在 PresetSpec 里)与「custom is derived-only」。这四条都只是 README 的原文标题,具体到你的部署里会怎样,我们没有运行过,不做断言。
顺带补一句这个包的默认形态,方便你对着看:源码 static Config 里 presets 的 .default({...})(src/index.ts:161-178)只有两档——workspace-write(sandbox: 'workspace-write' + approval: 'ask')与 danger-full-access(sandbox: 'danger-full-access' + approval: 'never'),docs/subsystems/permission-presets.md:11 是同一口径。还有个 custom:它是派生态而不是预设,常量在 :70,表里如果出现一条叫 custom 的条目会在构造时直接抛错,错误原文是 permission: "custom" is reserved for the derived not-a-preset state and cannot name a table entry(:189-191);文档 :48 也写明「custom is derived-only: clients may display it as the current value, but it is never a switch target or an event payload」。关于这张预设表在已发布组合里是几档,我们另有一篇专门讲,这里不展开。
边界
最后把口径收一下。这三处差异,我们能确认的只有「A 处写的是 X,B 处写的是 Y」这一层,以及第三方位置各自站在哪边。为什么不一致、哪一个是团队打算保留的、会不会某次提交后就统一了,我们一概不知道,也不打算猜。 更不能拿这三处去评价这个项目、这个包或写它的人——一个建仓三天、版本还停在 0.1.0-rc.5、README 自述开发者预览的仓库,文档与代码之间出现命名口径差,本身只是一条需要你在阅读时注意的事实,不是别的什么。
对读者最实用的一条结论其实很朴素:在这个仓库里查名字,以源码里那个字符串字面量为准去核,README 与子系统文档当作交叉验证的第二、第三个位置。这三处都是几秒钟就能查完的事。
延伸阅读
- 从头读起:DeepSeek Harness 是什么:建仓三天、13 万 star 的 Agent 框架
- 本专题共 45 篇,完整分组目录见专题页
- DeepSeek Harness 的权限预设有几档:源码两档、出厂组合三档
- DeepSeek Harness 的 bash 工具参数:目录五个,源码再展开两个
本文依据 DeepSeek Harness 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的
架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。
本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目,
因此不涉及界面外观、操作手感与运行速度的任何描述。
该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更,
文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。
安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。