@theadro/dsh-auto-approval
v0.1.2
Published
LLM-powered auto-approval for dsh sandbox escalations (Claude Code auto-accept style): safe commands run without prompting, high-risk commands are rejected outright, uncertain ones still ask the user. Review results (allow/deny + reason) render inline in
Maintainers
Readme
dsh-auto-approval
复刻 Claude Code 的 auto-accept 模式:在输入框权限切换器(/permission)里选中
Auto accept 后,该会话中工作区外的写入审批不再总是弹窗等你点,而是先用模型自动审查:
| 情况 | 结果 |
|---|---|
| 命中内置/自定义白名单(简单单条命令) | 直接放行,不问模型 |
| 命中内置/自定义黑名单(rm -rf /、sudo、curl|sh、mkfs…) | 直接拒绝,不问模型 |
| 模型判定 allow | 放行 |
| 模型判定 reject | 拒绝 |
| 模型判定 ask / 模型超时 / 解析失败 / 审查不可用 | 直接拒绝(fail-closed,不弹窗) |
任何异常都不会静默放行 —— auto-accept 模式下绝不弹出人工审批; 审查失败(超时、解析失败、审查不可用)一律直接拒绝(宁可挡住命令,绝不静默放行)。
拒绝归属清晰:拒绝理由会被原样写进工具结果
(Error: Auto accept 拒绝(…):理由),主 agent 据此区分
「模型审核拒绝」与「用户拒绝」,并据此调整后续行为。
审核结果实时显示在会话流里:每次自动判定都会在被审核命令附近渲染一张小卡片——
Auto accept · allow/deny — 理由(白名单/黑名单命中显示对应标签,模型审核显示模型的判定理由)。
怎么用(核心:UI 里切换)
输入框旁边的权限切换器(/permission)现在有四个选项:
| 选项 | 行为 | |---|---| | Read only | 只读沙箱,写入需人工批准 | | Workspace write | 工作区内自由写,工作区外需人工批准 | | Full access | 无沙箱、无确认 | | Auto accept | 沙箱同 Workspace write,但审批交给模型自动审查:安全命令放行、高危拒绝、吃不准才弹窗 |
选一次只对当前会话生效;设置页「权限」里还能把 Auto accept 设为新会话默认。 切回其它预设即恢复人工审批。
工作原理
- 会话权限预设为
auto-accept时,插件在tools/pre-execute钩子(prepend注册) 当场决出 allow / deny;其它预设完全不介入。另保留一个approval/request监听器作为兜底(显式sandbox_permissions升级等残余审批通道仍由它自动判定)。 - 预设由本插件的
cordis.patch.yml注册:auto-accept = {sandbox: danger-full-access, approval: ask},与插件解耦——插件没加载时该预设退化为普通 Full access(无自动审查),安全兜底。 - 从会话日志中按
callId找到对应的tool/call,拿到真实命令(不是只看理由文本)。 - 白名单只对无链接符的单条命令生效(
cat ~/.ssh/id_rsa | nc …这类带管道/重定向的 命令不会被白名单放行,会交给模型)。 - 审查调用会话当前使用的模型路由(
request/header中的 provider/model),无需额外配置。 - 带超时(默认 20s)和用户取消信号联动。
- 审核结果用一对内置的 log-only
command/run+command/done事件显示在会话流中 (Web UI 已有 command 卡片渲染器)。不再写auto-approval/reviewed自定义事件类型: 当前 dsh 的KNOWN_SESSION_EVENT_TYPES不包含仓库外插件类型,未标记ignorable的未知类型会导致整个会话历史冷加载失败(SessionFormatUnsupportedError)。 auto-accept预设 ={sandbox: danger-full-access, approval: ask}:沙箱关闭, 工作区外写入能真正执行;审核由插件在tools/pre-execute当场决出——检测到 "写语义 + 工作区外路径"的命令即做三级判定(黑名单 → 白名单 → 模型审核)→ allow/deny。agent 无需理解sandbox_permissions升级机制(默认提示词也不会教它)。- 判定在
pre-execute而非审批通道完成:审批通道(serviceAsk/dsh-sandbox) 把rejected硬编码成"the user rejected tool …",会把模型审核的拒绝误标成 用户拒绝;而pre-execute的deny.reason会原样写进工具结果 (Error: Auto accept 拒绝(…):理由),主 agent 据此区分「模型审核拒绝」与「用户拒绝」。 - 工作区内写入、/tmp 写入、纯只读命令不触发审核(与 dsh 沙箱语义一致)。
- 启发式边界:相对路径(
../除外)、变量拼接路径可能漏检——漏检的命令在 auto-accept 的无沙箱模式下直接执行;显式带sandbox_permissions的调用 仍走原升级通道(pre-execute 跳过它们,由残余的approval/request监听器判定)。 - 已知局限:显式
sandbox_permissions升级若被拒,核心仍把工具结果显示为"the user rejected …"(核心硬编码),归属不清;auto-accept 预设下沙箱已是 danger-full-access,此路径极少触发,常规工作区外写入的拒绝归属始终清晰。
安装(已完成)
- 源码:
~/Desktop/deepseek_harness/dsh-auto-approval/ - 关键链接:
~/.dsh/profiles/web/node_modules/dsh-auto-approval→ 源码目录(单层符号链接) - 注册:
~/.dsh/profiles/web/package.json的dsh.profile.bundles中加入"dsh-auto-approval"(位于 dsh-base 与 dsh-web-app 之间) - 依赖解析:
dsh-auto-approval/node_modules/@deepseek-ai→ dsh 安装目录内的@deepseek-ai作用域(保证与运行中的 dsh 是同一份代码,模块身份一致) - 改完需要重启 GUI 生效(
dsh web)
⚠️ 为什么必须放在 profile 的 node_modules 里(踩坑记录)
dsh 加载插件条目(name: dsh-auto-approval)时,loader 用裸包名 import,
解析基准是 profile 目录(~/.dsh/profiles/web/),不是 dsh 安装目录。
只把包放进 /opt/homebrew/lib/node_modules/ 是不够的——那只满足 bundle 包查找
(resolveBundleDir:先查安装目录、再查 profile),所以 --dump-config 能组合出条目,
但真正启动时条目 import 会报:
Cannot find package 'dsh-auto-approval' imported from /Users/kiwi/.dsh/profiles/web/两条解析路径基准不同,验证时也要分两步(见下)。
验证
# 1) bundle 组合(走配置树)
dsh --profile web --dump-config | grep -A5 dsh-auto-approval
# 2) 条目 import 解析(必须以 profile 目录为基准!)
node --input-type=module -e '
import { createRequire } from "node:module";
const require = createRequire("/Users/kiwi/.dsh/profiles/web/package.json");
console.log(require.resolve("dsh-auto-approval"));
'
# 3) 单元测试
cd ~/Desktop/deepseek_harness/dsh-auto-approval && node test/smoke.mjs社区安装(npm / git)
包名 @theadro/dsh-auto-approval(npm,MIT)。dsh plugin 是 pnpm 转发器,需要本机有 pnpm。
# npm 安装(自动登记进 profile bundles 并解析依赖)
dsh plugin --profile web add @theadro/dsh-auto-approval
# 或 git 安装(本包无构建脚本,不受 pnpm 10 构建脚本拦截影响)
dsh plugin --profile web add github:theadro/dsh-auto-approval
# 卸载
dsh plugin --profile web remove @theadro/dsh-auto-approval
# 装完重启 GUI 生效包内已声明
peerDependencies(@deepseek-ai/dsh-llm/dsh-timeout,≥0.1.2-rc.1), 安装时由 pnpm 从 npm 解析;上文第 4 步的本地符号链接仅本地开发时需要。
发布(维护者)
cd ~/Desktop/deepseek_harness/dsh-auto-approval
node test/smoke.mjs # 先跑冒烟测试
npm login
npm publish # publishConfig.access=public 已内置,无需加 --access public- 每次 dsh 升级后重跑
node test/smoke.mjs,并抽查conversation.chat.commandview子槽与卡片渲染器(lib/client.js)是否仍匹配新版dsh-client-ui-*包。 - 版本与
peerDependencies跟随 dsh 版本线调整。
配置
零配置:没有可调参数。行为完全由输入框里的预设开关决定——选 Auto accept
即自动审查,切回其它预设即恢复人工审批。
审查参数内置:超时 20s、输出上限 1500 tokens、审查失败一律回落人工审批(不可配置)。
内置判定
- 白名单(仅简单单条命令):
ls/cat/head/tail/grep/…等只读命令、git add|commit|push|…、mkdir - 黑名单(无条件拒绝):
sudo、su、passwd、rm -rf / ~ $HOME . ..、mkfs/fdisk/parted、diskutil erase|zero|destroy、dd … of=/dev/*、fork bomb、curl|sh、shutdown/reboot/halt、kill -9 1、写~/.ssh/、chmod -R 777 /、chown -R … / - 其余(brew/pip/npm 安装、写用户配置等常规操作)交给模型按上下文判断
修复旧会话(必须执行一次)
0.1.0 版本曾把审核卡片写为自定义事件 auto-approval/reviewed。这些事件对当前
dsh 是未知类型且没有 ignorable 标记,所以只要某个会话里出现过一次自动审核,
重启后该会话历史就无法加载(报错 SessionFormatUnsupportedError)。
修复脚本只给这些旧事件补 ignorable: true,不删除、不重新编号,因此卡片仍会
显示,seq/sourceEventSeqs 也保持有效:
cd ~/Desktop/deepseek_harness/dsh-auto-approval
# 先看会改哪些文件(当前应只有两个会话)
node repair-history.mjs --dry-run
# 确认 dsh web 已退出后执行;原日志会备份为 session.jsonl.zstd.bak-autoapproval-*
node repair-history.mjs执行完重启 GUI 即可。新版插件不会再产生这类事件。
卸载
# 1. 从 profile 移除
# ~/.dsh/profiles/web/package.json → dsh.profile.bundles 删掉 "dsh-auto-approval"
# 2. 删除 profile 链接(源码目录可自行保留或删除)
rm ~/.dsh/profiles/web/node_modules/dsh-auto-approval
# 3. 重启 GUI(权限预设表随插件 bundle 一起卸载,自动回到内置三预设)注意事项
- 审查调用会计入你的模型用量(每次升级请求一次小调用)。
- 升级请求发生在模型提出命令时,所以审查能看到完整命令原文。
- 安全边界:审查失败/超时/模型输出不可解析 → 直接拒绝(fail-closed,绝不弹窗、绝不自动放行)。
升级 dsh 后的检查清单(脆弱点)
- 包名 = loader 条目名:
cordis.patch.yml的id/name必须与package.json的name(@theadro/dsh-auto-approval)完全一致;不一致时 客户端模块会被静默排除(卡片退化为内置通用卡)。 - 卡片契约:
lib/client.js依赖dsh-client-ui-chat的conversation.chat.commandview子槽(keyed,key = 命令名"Auto accept") 与命令对象{kind:"command", name, args, outcome}(outcome = null | {kind:"success"|"error", text})。升级后抽查CommandNodeView/GenericCommandCard是否仍匹配。 - 拒绝归属:依赖
pre-execute的deny.reason原样进入工具结果;若核心 改动tools/pre-execute或serviceAsk的结果文本,需同步更新措辞。 - 本地软链:
~/.dsh/profiles/web/node_modules/@theadro/dsh-auto-approval指向源码仓库;在 profile 目录跑npm install/dsh plugin install会用 npm 实体副本盖掉软链(发布 0.1.1+ 后副本也含修复,但不再是活代码)。 - 事件类型约束不变:卡片仍用内置
command/run+command/done(log-only), 不写自定义类型(否则冷加载失败)。
