@xth26/dsh-plan-build-mode
v0.6.10
Published
OpenCode-style Plan / Build hard permission model for DSH
Readme
dsh-plan-build-mode
OpenCode 风格的 Plan / Build 硬权限模型插件(DeepSeek Harness)。
本插件为 DSH 增加一个独立的 Plan/Build 模式切换,并用 OpenCode 风格权限引擎
(allow | ask | deny)对工具访问做门禁,不影响 DSH 原生的 /plan 软提示模式:
- Plan 模式按策略只读:所有写类工具都被拒绝,唯一例外是写入配置的
plans 目录(
.opencode/plans/)——这样 agent 在其余位置保持只读时,仍能持久化 计划文件。 - Build 模式可写:可以执行已批准的计划。
- 两种模式下读取都不受限,包括工作区之外的文件——与 DSH 一致,读取在每种
沙箱模式下都放行。只有修改会被门禁(对应 OpenCode 的
external_directory)。 - DSH 自带的
/plan软提示模式保持原样。
插件的内置默认值把两个沙箱都配置为 workspace-write;Plan 模式的“只读”由权限
门禁而不是沙箱强制——这正是计划文件能写到 .opencode/plans/、其余位置保持只读
的原因。随附的 cordis.patch.yml 是纯 insert(只含 id 与 name),因此可以直接
从 DSH 插件市场启用(热挂载),无需重启。
版本兼容性
| 插件版本 | DSH 版本 | 说明 |
|---|---|---|
| 0.6.3+ | >= 0.2.1-rc.3 | 模式改由官方 permissionPresets 服务承载(oc-plan 预设 = Plan 模式;其它预设 = Build)。bundle patch 覆盖共享的 permission 行,把 oc-plan / oc-build 预设加进原生下拉。命令改名 /oc-permission(原 /permission,避免与官方命令重名)。模式状态持久化在插件专属的 storageDomain KV 域(内存兜底)。 |
| 0.6.6+ | >= 0.2.1-rc.3 | Plan 模式 bash/pwsh 写守卫:命中写习语(重定向、Set-Content、open(...,"w")、setx、reg add、git push/commit、包安装、网络写、kill 等)的命令直接拒绝——workspace-write 沙箱无法为命令兜底 plan 门禁。 |
| 0.6.9+ | >= 0.2.1-rc.3 | OpenCode 风格 shell scanner:复合命令拆成子命令(引号/heredoc 感知)逐条检查;动态求值($()、反引号、${…}、未加引号的 $var)、引号不闭合、heredoc 未终止一律 fail closed。 |
| 0.5.1 | >= 0.1.5-rc.1 | bundle patch 改为纯 insert;OpenCode 对齐默认值内置到插件代码,插件市场可直接启用,无需重启。 |
| 0.5.x | >= 0.1.5-rc.1 | 新增 OpenCode 权限门禁与 /permission;模式由 plan-build/mode 会话事件承载(旧会话从 sandbox/mode 推断)。 |
| 0.4.x | >= 0.1.5-rc.1 | 使用 session.snapshotEvents();DSH 0.1.5 移除了 session.events 快照 getter。 |
| <= 0.3.1 | 0.1.0-rc.x | 旧版本;在 DSH 0.1.5+ 上因上述 API 变更无法工作。 |
安装
DSH 插件通过 DSH profile 加载。把它装到你实际使用的 profile 里:
# 装到 web profile
dsh plugin --profile web add @xth26/dsh-plan-build-mode
# 装到 TUI profile
dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode插件自带的 cordis.patch.yml 会在 DSH profile 加载时自动注入;它是纯 insert,
因此从 DSH 插件市场启用也可以直接生效,无需重启。
让 Web 和 TUI 共享同一个 Plan/Build 模式
Plan/Build 模式状态持久化在插件专属的 storageDomain KV 域(进程内 SQLite,
无持久层时内存兜底)。同一 profile 下多个入口共享同一插件实例,因此模式切换
天然同步;permissionPresets 服务挂载时以官方预设为权威来源。
- 同一个 profile:比如
dsh web --profile web和dsh --profile web启动的两个入口,会共享 session 事件,因此模式切换会同步生效。 - 不同 profile:默认
web和dsh-tui是两个独立 profile,会话目录也分开。在一处切换不会影响另一处。
想让 web 和 TUI 共享模式状态,最简单的方法是两个入口都使用同一个 profile:
# 两个入口都用 web profile
dsh web --profile web
dsh --profile web如果你确实想用不同 profile,但只想共享会话存储,可以在每个 profile 的 cordis.patch.yml 里把 session root 指向同一个目录:
- id: session-root
config:
root: /path/to/shared/sessions注意:跨 profile 共享会话目录要求两个 profile 挂载的服务组合兼容,否则事件解释可能出现差异。
本地开发 / link 试用
想在发布前从本地源码试用:
# 在插件目录
cd /path/to/dsh-plan-build-mode
pnpm link --global
# 在你的 DSH profile 目录
pnpm link --global @xth26/dsh-plan-build-mode然后启动 DSH。插件 package.json 里的 dsh.bundle.patch 会自动生效。
更新
对于 0.x 版本,semver 范围 ^0.1.0 只会匹配 0.1.x,永远不会自动更新到 0.2.0。升级时必须显式提升声明并重启 DSH 进程:
# 在目标 profile 中提升已安装版本
# (这条命令需要在非沙箱的终端里执行;DSH agent 无法写入 profile 目录)
dsh plugin --profile dsh-tui add @xth26/[email protected]
# 重启使用该 profile 的 DSH 进程
# (旧的 Node 进程仍缓存着旧插件代码)
Stop-Process -Name dsh -Force # PowerShell
# 或: taskkill /IM dsh.exe /F # CMD然后重新启动 dsh web 或 dsh tui。如果想始终安装最新发布版本:
dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode@latest升级说明(0.5.0 → 0.5.1)
cordis.patch.yml改为纯 insert(只含id与name,不带config),插件市场 可直接启用。原来写在 patch 里的 OpenCode 对齐值现在内置为插件默认值;已按旧配置 固定值的 profile 无需改动仍可工作。
升级说明(0.4.x → 0.5.x)
planSandbox与buildSandbox现在可以相等(随附默认两者都用workspace-write);Plan 模式的只读策略由权限门禁强制。旧的“必须不同”限制已移除。- 模式状态迁移到插件专属的
plan-build/mode事件。只有sandbox/mode事件的旧 会话会在加载时被推断并在会话开始时固定为真实的plan-build/mode事件,历史不丢失。 - 旧的
denyWriteTools选项仍然有效:设为false允许 agent 在 Plan 模式写入 (等价于覆盖modes.plan.edit = allow)。
使用
| 命令 | 效果 |
|---|---|
| /plan-build | 在 Plan 与 Build 模式之间切换 |
| /plan-build status | 显示当前模式 |
| /plan-build switch plan | 进入 Plan 模式(策略只读) |
| /plan-build switch build | 进入 Build 模式(可写) |
| /plan-build switch | 同 /plan-build,切换当前模式 |
| /oc-permission status | 显示外部路径默认值、规则与本次会话的 allowlist |
| /oc-permission allow <pattern> | 把模式加入本次会话 allowlist(OpenCode 的 “always”) |
| /oc-permission deny <pattern> | 从本次会话 allowlist 移除该模式 |
当官方 permissionPresets 服务挂载时(DSH 桌面 profile 的默认形态),模式切换
走 presets.set()——原生权限下拉会出现 oc-plan / oc-build 预设,选择任何
预设都等于退出 Plan 模式。插件自有的模式 store 只作为没有该服务的组合的后备。
配置
- id: plan-build-mode
name: '@xth26/dsh-plan-build-mode'
config:
planSandbox: workspace-write
buildSandbox: workspace-write
denyWriteTools: true
section: true
plansDirectory: .opencode/plans
externalDirectory:
default: allow # allow | ask | deny —— 工作区之外的读取上面示例中的所有值都是插件内置默认值,因此随附的纯 insert patch(只有 id 与
name)与示例行为完全一致。planSandbox 和 buildSandbox 可以相等,也可以不同。
不同时,模式切换还会联动沙箱(read-only ↔ workspace-write 等);相等时
(随附默认),Plan 模式的策略由权限门禁强制。
权限模型
每个工具映射到一个类别:
| 类别 | 工具 |
|---|---|
| read | read、read_image、pdf_read、docx_read、pptx_read、xlsx_read |
| edit | write、edit、pdf_create、docx_create、pptx_create、xlsx_write、xlsx_edit |
| glob | glob |
| grep | grep |
| bash | bash |
| pwsh | pwsh |
| webfetch | web_fetch |
| websearch | web_search |
类别值可以是标量(allow | ask | deny),也可以是带顺序的对象规则,和 OpenCode 一致:
permission:
read:
'*': allow
'**/.env': deny
'**/.env.*': deny
'**/.env.example': allow # 最后匹配的规则生效
bash: ask
edit: deny模式支持 OpenCode 通配符(* 匹配任意字符、含 /;? 恰好匹配一个字符;
** 与 * 相同)以及 ~/$HOME 展开。规则按“最后匹配者胜出”求值。默认规则对齐
OpenCode:大多数类别 allow;.env* 读取被拒绝(.env.example 恢复为允许)。
Plan 模式覆盖默认值(除非被 modes.plan 或 denyWriteTools: false 覆盖):
edit: deny——对应 OpenCode plan agent 的默认值。
modes.plan / modes.build 按模式覆盖全局权限,类似 OpenCode 的 agent 级覆盖。
modes:
plan:
bash: deny # 替代默认值(scanner 门控放行)
otherTools: deny # 默认;设为 'allow' 可解除对未归类状态变更工具的拒绝
build:
read: allowPlan 模式:shell scanner
Plan 模式的 bash / pwsh 命令按 OpenCode shell scanner 的方式扫描:
- 命令文本在
&&、||、;、|、&和换行处拆成子命令——引号与 heredoc 感知。git status && rm -rf src是两条命令,即使文本以安全前缀 开头,rm也会被检查。 - 每条子命令过写习语判定:重定向(
> file、>>)、PowerShell 写命令 (Set-Content、New-Item等)、解释器写法(open(...,"w")、write_text、writeFileSync,含 heredoc 喂入的脚本体内)、文件变更 (rm、mv、cp、mkdir等)、git 状态变更(git push|commit|reset等)、 包安装/发布(npm install、pip install等)、网络写(curl -X POST、wget -O、scp)、进程/权限变更(kill、chmod、icacls等)、 持久系统状态(setx、SetEnvironmentVariable、reg add、schtasks、sc config)。 - Fail closed:任何无法静态分析的结构——命令替换
$(…)、反引号、${…}或未加引号的$var展开(bash)、$(…)子表达式与反引号转义 (PowerShell)、引号不闭合、heredoc 未终止——都以plan-mode scanner error拒绝,不会执行。
只读复合命令(git status && ls、cat a | grep x | head、git diff 换行)
照常放行。这让 Plan 模式成为确定性门禁——强于原生 Claude Code(仅提示词),
理论上精度略低于 OpenCode 基于 unbash 的完整 parser。
未归类工具
已知的无权限类别状态变更工具在 Plan 模式默认拒绝(modes.plan.otherTools:
'allow' 可解除):compress、interrupt_agent、send_message、job_kill、
schedule_create、schedule_edit、office_open/edit/save/close、
browser_* 自动化全家、create_goal / update_goal。未知/MCP 工具不设门
(沙箱仍是底线)。
Plans 目录例外
在 Plan 模式下,写入 plansDirectory(默认 .opencode/plans/,相对会话工作区)
下的 edit 类工具始终允许,保证 /plan-build 与计划文件正常工作。Plan 模式下
其它所有写入都会被拒绝,理由中包含 forbidden in Plan Mode。
外部路径
- 读取工作区之外的文件在两种模式下默认都允许(
externalDirectory.default: allow)。设为ask可获得完整的 OpenCode 行为:对外部读取发起审批请求。 - 写入工作区之外由沙箱决定。在
workspace-write下无法放行,因此插件不会 弹审批(那也会在沙箱层失败),而是直接拒绝并给出升级提示——用sandbox_permissions+justification重试同一个工具。 /oc-permission allow <pattern>会加入一个会话级 allowlist 条目(OpenCode “always”),仅在该会话内存中保留。
Plan 模式:命令行 Python 做数据检查
Plan 模式拒绝 write/edit 工具,但鼓励在命令行里跑简短、只读的 Python
片段来读数据和验证假设。例如:
python -B -c "import json, sys; data = json.load(open('data/sample.json')); print(len(data))"
python -B -c "import pandas as pd; print(pd.read_csv('data.csv').describe())"
pytest -p no:cacheprovider -q tests/test_sanity.py使用 -B(或设置 PYTHONDONTWRITEBYTECODE=1)以及 pytest -p no:cacheprovider,
避免写入 __pycache__ 或 .pytest_cache。权限门禁与沙箱是最终防线:任何试图写文件的
操作都会在门禁(Plan 模式)或沙箱层(Build 模式)被拦截。
本地集成验证
安装或 link 插件到 DSH profile 后:
- 启动 DSH。
- 输入
/plan-build,应提示进入 Plan 模式(策略只读)。 - 让 agent 对
src/x.ts调用write,应被拒绝,理由包含forbidden in Plan Mode。 - 让 agent 对
.opencode/plans/x.md调用write,应允许(plans 目录例外)。 - 再次输入
/plan-build,应提示进入 Build 模式。 - 让 agent 对
src/x.ts调用write,应允许执行。 - 让 agent 读取工作区之外的文件,应无需提示直接放行(默认
external_directory: allow)。 - 输入
/oc-permission status查看门禁状态;可尝试/oc-permission allow ~/projects/**添加会话 allowlist。 - 输入
/plan-build status,可只查看当前模式而不切换。 - Plan 模式下让 agent 跑
git status && rm -rf src,应被拒绝(scanner 拆出写子命令);跑echo "$(/tmp/evil)",应被拒绝(fail closed)。
客户端模式标签
包附带 web 客户端模块(./client),在输入框 dock 注册常驻模式标签:
Plan 模式显示 Plan 只读模式(灰点),Build 模式显示 Build 模式(蓝点)。
标签读取实时权限投影,跟随下拉切换即时变化。
许可证
MIT
