DeepSeek Harness 的权限预设命名:README 与源码三处都不一样

2026-08-16

deepseek-harnesspackages/interaction/permission-presets/ 这个包时,会碰到一件挺容易踩的事:同一个东西,包 README 里叫 permissionPresets,源码里叫 permission。而且不是一处,是三处——事件名、斜杠命令名、settings 命名空间,三个都对不上。

先把话说在前面:本文只陈述差异、标明两边各在哪个文件哪一行,不推断哪个是对的、也不推断为什么会这样。我们没有安装、没有运行过这个项目,下面所有内容都是在仓库快照 47f9438(核对日 2026-08-16)上读文件读出来的。另外这个仓库建立于 2026-08-13,版本是 0.1.0-rc.5,一个 GitHub Release 都没有,README 自述处于开发者预览阶段并明确写了未来会有破坏兼容性的变更——所以下面提到的任何名字,都可能在你读到这篇时已经变了,请以仓库当前内容为准。

三处差异一览

差异点包 README 里的写法源码里的写法
会话事件名permissionPresets/presetpermission/preset
斜杠命令名/permissionPresetsname: 'permission'
settings 命名空间permissionPresetssettingsNamespace('permission')

三处的具体位置在下面各自展开。

差异一:事件名

包 README(packages/interaction/permission-presets/README.md)里,permissionPresets/preset 这个名字出现了三次:

  • :7set(session, name) 会「records a changed selection in a log-only permissionPresets/preset event」;
  • :9 写新会话创建时会 pin 下 permissionPresets/presetsandbox/modeapproval/policy 三件事;
  • :17 又写「permissionPresets/preset itself is log-only」。

源码这边,实际 append 出去的事件名是 permission/preset。位置有三个:packages/interaction/permission-presets/src/index.ts:383apply() 里在选择发生变化时追加)、: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/askedapproval/decidedapproval/policycommand/runcommand/donetool/calltool/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.tsappend('permission/preset', ...)commands.register({ name: 'permission', ... }) 的行、以及 packages/core/session/src/known-event-types.ts 里登记的那一条。三处看完,你手上就有和本文一样的原始依据,不必信我转述。

为什么这三个名字值得逐个核

因为它们各自落在不同的消费面上,谁也不能替谁:

事件名是会写进会话日志的。 这个包做的事是把两个旋钮打包:一个预设同时决定 sandbox/modeapproval/policyapply() 的顺序是先在「当前预设 ≠ 目标预设」时追加 permission/preset 事件(src/index.ts:382-384),再仅在该旋钮的有效值确实变化时分别调 setSandboxMode 与审批写入器(:386-391)。所以选择事件在前、旋钮事件在后;如果你要写工具去解析会话日志、统计权限变更,事件名写错就一条都匹配不到。

命令名是用户要敲进去的。 源码注册的是 permission。至于按 README 那个名字敲会发生什么,我们没有运行过、不做断言——但要确认一个命令到底叫什么,去 commands.register 的注册处读 name 字段,比照 README 更直接。

顺带说一句「当前预设」这个名字是怎么算出来的(apply() 里判断要不要 append 事件时比对的也是它,见 src/index.ts:382-384current(...) !== 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 Configpresets.default({...})src/index.ts:161-178)只有两档——workspace-writesandbox: 'workspace-write' + approval: 'ask')与 danger-full-accesssandbox: '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 官方仓库(github.com/deepseek-ai/deepseek-harness)的 README、docs/ 下的 架构与子系统文档、以及 packages/ 下的源码整理,核对日 2026-08-16,对应仓库快照 47f9438(版本 0.1.0-rc.5)。 本文内容为仓库源码与文档口径,我们没有安装、也没有运行过这个项目, 因此不涉及界面外观、操作手感与运行速度的任何描述。 该仓库建立于 2026-08-13,README 自述处于开发者预览阶段并明确说明未来会有破坏兼容性的变更, 文中出现的命令、配置与默认值随时可能变动,请以仓库最新内容为准。 安全与合规相关做法请结合自身环境评估,本文不构成安全方案建议。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。