@xth26/dsh-plan-build-mode 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +255 -146
- package/README.zh.md +237 -143
- package/cordis.patch.yml +13 -1
- package/lib/helpers.d.ts +21 -5
- package/lib/helpers.d.ts.map +1 -1
- package/lib/helpers.js +38 -6
- package/lib/helpers.js.map +1 -1
- package/lib/index.d.ts +14 -17
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +186 -33
- package/lib/index.js.map +1 -1
- package/lib/invariant.d.ts +4 -4
- package/lib/invariant.d.ts.map +1 -1
- package/lib/invariant.js +31 -8
- package/lib/invariant.js.map +1 -1
- package/lib/permissions.d.ts +164 -0
- package/lib/permissions.d.ts.map +1 -0
- package/lib/permissions.js +351 -0
- package/lib/permissions.js.map +1 -0
- package/lib/types.d.ts +44 -1
- package/lib/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.zh.md
CHANGED
|
@@ -1,143 +1,237 @@
|
|
|
1
|
-
# dsh-plan-build-mode
|
|
2
|
-
|
|
3
|
-
OpenCode 风格的 Plan / Build
|
|
4
|
-
|
|
5
|
-
本插件为 DSH 增加一个独立的 Plan/Build
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
- **
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
dsh --profile
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode@
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
1
|
+
# dsh-plan-build-mode
|
|
2
|
+
|
|
3
|
+
OpenCode 风格的 Plan / Build 硬权限模型插件(DeepSeek Harness)。
|
|
4
|
+
|
|
5
|
+
本插件为 DSH 增加一个独立的 Plan/Build 模式切换,并用 OpenCode 风格权限引擎
|
|
6
|
+
(`allow | ask | deny`)对工具访问做门禁,不影响 DSH 原生的 `/plan` 软提示模式:
|
|
7
|
+
|
|
8
|
+
- **Plan 模式按策略只读**:所有写类工具都被拒绝,唯一例外是写入配置的
|
|
9
|
+
plans 目录(`.opencode/plans/`)——这样 agent 在其余位置保持只读时,仍能持久化
|
|
10
|
+
计划文件。
|
|
11
|
+
- **Build 模式可写**:可以执行已批准的计划。
|
|
12
|
+
- **两种模式下读取都不受限**,包括工作区之外的文件——与 DSH 一致,读取在每种
|
|
13
|
+
沙箱模式下都放行。只有**修改**会被门禁(对应 OpenCode 的 `external_directory`)。
|
|
14
|
+
- DSH 自带的 `/plan` 软提示模式保持原样。
|
|
15
|
+
|
|
16
|
+
随附的 `cordis.patch.yml` 把两个沙箱都配置为 `workspace-write`;Plan 模式的
|
|
17
|
+
“只读”由权限门禁而不是沙箱强制——这正是计划文件能写到 `.opencode/plans/`、
|
|
18
|
+
其余位置保持只读的原因。
|
|
19
|
+
|
|
20
|
+
## 版本兼容性
|
|
21
|
+
|
|
22
|
+
| 插件版本 | DSH 版本 | 说明 |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `0.5.x` | `>= 0.1.5-rc.1` | 新增 OpenCode 权限门禁与 `/permission`;模式由 `plan-build/mode` 会话事件承载(旧会话从 `sandbox/mode` 推断)。 |
|
|
25
|
+
| `0.4.x` | `>= 0.1.5-rc.1` | 使用 `session.snapshotEvents()`;DSH 0.1.5 移除了 `session.events` 快照 getter。 |
|
|
26
|
+
| `<= 0.3.1` | `0.1.0-rc.x` | 旧版本;在 DSH 0.1.5+ 上因上述 API 变更无法工作。 |
|
|
27
|
+
|
|
28
|
+
## 安装
|
|
29
|
+
|
|
30
|
+
DSH 插件通过 DSH profile 加载。把它装到你实际使用的 profile 里:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# 装到 web profile
|
|
34
|
+
dsh plugin --profile web add @xth26/dsh-plan-build-mode
|
|
35
|
+
|
|
36
|
+
# 装到 TUI profile
|
|
37
|
+
dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
插件自带的 `cordis.patch.yml` 会在 DSH profile 加载时自动注入。
|
|
41
|
+
|
|
42
|
+
## 让 Web 和 TUI 共享同一个 Plan/Build 模式
|
|
43
|
+
|
|
44
|
+
Plan/Build 模式状态存在会话事件日志里(插件专属的 `plan-build/mode` 事件,
|
|
45
|
+
以及用于沙箱执行的 `sandbox/mode` 事件)。`dsh web` 和 `dsh --profile <名称>`
|
|
46
|
+
只有在共用同一个 profile、同一个 session ID 时,才会读写同一个会话事件流。
|
|
47
|
+
|
|
48
|
+
- **同一个 profile**:比如 `dsh web --profile web` 和 `dsh --profile web` 启动的两个入口,会共享 session 事件,因此模式切换会同步生效。
|
|
49
|
+
- **不同 profile**:默认 `web` 和 `dsh-tui` 是两个独立 profile,会话目录也分开。在一处切换不会影响另一处。
|
|
50
|
+
|
|
51
|
+
想让 web 和 TUI 共享模式状态,最简单的方法是**两个入口都使用同一个 profile**:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 两个入口都用 web profile
|
|
55
|
+
dsh web --profile web
|
|
56
|
+
dsh --profile web
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
如果你确实想用不同 profile,但只想共享会话存储,可以在每个 profile 的 `cordis.patch.yml` 里把 session root 指向同一个目录:
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
- id: session-root
|
|
63
|
+
config:
|
|
64
|
+
root: /path/to/shared/sessions
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
注意:跨 profile 共享会话目录要求两个 profile 挂载的服务组合兼容,否则事件解释可能出现差异。
|
|
68
|
+
|
|
69
|
+
## 本地开发 / link 试用
|
|
70
|
+
|
|
71
|
+
想在发布前从本地源码试用:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
# 在插件目录
|
|
75
|
+
cd /path/to/dsh-plan-build-mode
|
|
76
|
+
pnpm link --global
|
|
77
|
+
|
|
78
|
+
# 在你的 DSH profile 目录
|
|
79
|
+
pnpm link --global @xth26/dsh-plan-build-mode
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
然后启动 DSH。插件 `package.json` 里的 `dsh.bundle.patch` 会自动生效。
|
|
83
|
+
|
|
84
|
+
## 更新
|
|
85
|
+
|
|
86
|
+
对于 `0.x` 版本,semver 范围 `^0.1.0` 只会匹配 `0.1.x`,**永远不会**自动更新到 `0.2.0`。升级时必须显式提升声明并重启 DSH 进程:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 在目标 profile 中提升已安装版本
|
|
90
|
+
# (这条命令需要在非沙箱的终端里执行;DSH agent 无法写入 profile 目录)
|
|
91
|
+
dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode@0.5.0
|
|
92
|
+
|
|
93
|
+
# 重启使用该 profile 的 DSH 进程
|
|
94
|
+
# (旧的 Node 进程仍缓存着旧插件代码)
|
|
95
|
+
Stop-Process -Name dsh -Force # PowerShell
|
|
96
|
+
# 或: taskkill /IM dsh.exe /F # CMD
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
然后重新启动 `dsh web` 或 `dsh tui`。如果想始终安装最新发布版本:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
dsh plugin --profile dsh-tui add @xth26/dsh-plan-build-mode@latest
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 升级说明(0.4.x → 0.5.x)
|
|
106
|
+
|
|
107
|
+
- `planSandbox` 与 `buildSandbox` **现在可以相等**(随附补丁两者都用
|
|
108
|
+
`workspace-write`);Plan 模式的只读策略由权限门禁强制。旧的“必须不同”限制已移除。
|
|
109
|
+
- 模式状态迁移到插件专属的 `plan-build/mode` 事件。只有 `sandbox/mode` 事件的旧
|
|
110
|
+
会话会在加载时被推断并在会话开始时固定为真实的 `plan-build/mode` 事件,历史不丢失。
|
|
111
|
+
- 旧的 `denyWriteTools` 选项仍然有效:设为 `false` 允许 agent 在 Plan 模式写入
|
|
112
|
+
(等价于覆盖 `modes.plan.edit = allow`)。
|
|
113
|
+
|
|
114
|
+
## 使用
|
|
115
|
+
|
|
116
|
+
| 命令 | 效果 |
|
|
117
|
+
|---|---|
|
|
118
|
+
| `/plan-build` | 在 Plan 与 Build 模式之间切换 |
|
|
119
|
+
| `/plan-build status` | 显示当前模式 |
|
|
120
|
+
| `/plan-build switch plan` | 进入 Plan 模式(策略只读) |
|
|
121
|
+
| `/plan-build switch build` | 进入 Build 模式(可写) |
|
|
122
|
+
| `/plan-build switch` | 同 `/plan-build`,切换当前模式 |
|
|
123
|
+
| `/permission status` | 显示外部路径默认值、规则与本次会话的 allowlist |
|
|
124
|
+
| `/permission allow <pattern>` | 把模式加入本次会话 allowlist(OpenCode 的 “always”) |
|
|
125
|
+
| `/permission deny <pattern>` | 从本次会话 allowlist 移除该模式 |
|
|
126
|
+
|
|
127
|
+
## 配置
|
|
128
|
+
|
|
129
|
+
```yaml
|
|
130
|
+
- id: plan-build-mode
|
|
131
|
+
name: '@xth26/dsh-plan-build-mode'
|
|
132
|
+
config:
|
|
133
|
+
planSandbox: workspace-write
|
|
134
|
+
buildSandbox: workspace-write
|
|
135
|
+
denyWriteTools: true
|
|
136
|
+
section: true
|
|
137
|
+
plansDirectory: .opencode/plans
|
|
138
|
+
externalDirectory:
|
|
139
|
+
default: allow # allow | ask | deny —— 工作区之外的读取
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`planSandbox` 和 `buildSandbox` 可以相等,也可以不同。不同时,模式切换还会联动
|
|
143
|
+
沙箱(`read-only` ↔ `workspace-write` 等);相等时(随附默认),Plan 模式的策略由
|
|
144
|
+
权限门禁强制。
|
|
145
|
+
|
|
146
|
+
### 权限模型
|
|
147
|
+
|
|
148
|
+
每个工具映射到一个类别:
|
|
149
|
+
|
|
150
|
+
| 类别 | 工具 |
|
|
151
|
+
|---|---|
|
|
152
|
+
| `read` | `read`、`read_image`、`pdf_read`、`docx_read`、`pptx_read`、`xlsx_read` |
|
|
153
|
+
| `edit` | `write`、`edit`、`pdf_create`、`docx_create`、`pptx_create`、`xlsx_write`、`xlsx_edit` |
|
|
154
|
+
| `glob` | `glob` |
|
|
155
|
+
| `grep` | `grep` |
|
|
156
|
+
| `bash` | `bash` |
|
|
157
|
+
| `pwsh` | `pwsh` |
|
|
158
|
+
| `webfetch` | `web_fetch` |
|
|
159
|
+
| `websearch` | `web_search` |
|
|
160
|
+
|
|
161
|
+
类别值可以是标量(`allow | ask | deny`),也可以是带顺序的对象规则,和 OpenCode 一致:
|
|
162
|
+
|
|
163
|
+
```yaml
|
|
164
|
+
permission:
|
|
165
|
+
read:
|
|
166
|
+
'*': allow
|
|
167
|
+
'**/.env': deny
|
|
168
|
+
'**/.env.*': deny
|
|
169
|
+
'**/.env.example': allow # 最后匹配的规则生效
|
|
170
|
+
bash: ask
|
|
171
|
+
edit: deny
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
模式支持 OpenCode 通配符(`*` 匹配任意字符、含 `/`;`?` 恰好匹配一个字符;
|
|
175
|
+
`**` 与 `*` 相同)以及 `~`/`$HOME` 展开。规则按“最后匹配者胜出”求值。默认规则对齐
|
|
176
|
+
OpenCode:大多数类别 `allow`;`.env*` 读取被拒绝(`.env.example` 恢复为允许)。
|
|
177
|
+
|
|
178
|
+
Plan 模式覆盖默认值(除非被 `modes.plan` 或 `denyWriteTools: false` 覆盖):
|
|
179
|
+
`edit: deny`、`bash: ask`、`pwsh: ask`——对应 OpenCode plan agent 的默认值。
|
|
180
|
+
`modes.plan` / `modes.build` 按模式覆盖全局权限,类似 OpenCode 的 agent 级覆盖。
|
|
181
|
+
|
|
182
|
+
```yaml
|
|
183
|
+
modes:
|
|
184
|
+
plan:
|
|
185
|
+
bash: deny # 替代默认的 ask
|
|
186
|
+
build:
|
|
187
|
+
read: allow
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Plans 目录例外
|
|
191
|
+
|
|
192
|
+
在 Plan 模式下,写入 `plansDirectory`(默认 `.opencode/plans/`,相对会话工作区)
|
|
193
|
+
下的 `edit` 类工具**始终允许**,保证 `/plan-build` 与计划文件正常工作。Plan 模式下
|
|
194
|
+
其它所有写入都会被拒绝,理由中包含 `forbidden in Plan Mode`。
|
|
195
|
+
|
|
196
|
+
### 外部路径
|
|
197
|
+
|
|
198
|
+
- **读取**工作区之外的文件在两种模式下默认都允许(`externalDirectory.default:
|
|
199
|
+
allow`)。设为 `ask` 可获得完整的 OpenCode 行为:对外部读取发起审批请求。
|
|
200
|
+
- **写入**工作区之外由沙箱决定。在 `workspace-write` 下无法放行,因此插件不会
|
|
201
|
+
弹审批(那也会在沙箱层失败),而是直接拒绝并给出升级提示——用
|
|
202
|
+
`sandbox_permissions` + `justification` 重试同一个工具。
|
|
203
|
+
- `/permission allow <pattern>` 会加入一个会话级 allowlist 条目(OpenCode
|
|
204
|
+
“always”),仅在该会话内存中保留。
|
|
205
|
+
|
|
206
|
+
## Plan 模式:命令行 Python 做数据检查
|
|
207
|
+
|
|
208
|
+
Plan 模式拒绝 `write`/`edit` 工具,但**鼓励**在命令行里跑简短、只读的 Python
|
|
209
|
+
片段来读数据和验证假设。例如:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
python -B -c "import json, sys; data = json.load(open('data/sample.json')); print(len(data))"
|
|
213
|
+
python -B -c "import pandas as pd; print(pd.read_csv('data.csv').describe())"
|
|
214
|
+
pytest -p no:cacheprovider -q tests/test_sanity.py
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
使用 `-B`(或设置 `PYTHONDONTWRITEBYTECODE=1`)以及 `pytest -p no:cacheprovider`,
|
|
218
|
+
避免写入 `__pycache__` 或 `.pytest_cache`。权限门禁与沙箱是最终防线:任何试图写文件的
|
|
219
|
+
操作都会在门禁(Plan 模式)或沙箱层(Build 模式)被拦截。
|
|
220
|
+
|
|
221
|
+
## 本地集成验证
|
|
222
|
+
|
|
223
|
+
安装或 link 插件到 DSH profile 后:
|
|
224
|
+
|
|
225
|
+
1. 启动 DSH。
|
|
226
|
+
2. 输入 `/plan-build`,应提示进入 Plan 模式(策略只读)。
|
|
227
|
+
3. 让 agent 对 `src/x.ts` 调用 `write`,应被拒绝,理由包含 `forbidden in Plan Mode`。
|
|
228
|
+
4. 让 agent 对 `.opencode/plans/x.md` 调用 `write`,应**允许**(plans 目录例外)。
|
|
229
|
+
5. 再次输入 `/plan-build`,应提示进入 Build 模式。
|
|
230
|
+
6. 让 agent 对 `src/x.ts` 调用 `write`,应允许执行。
|
|
231
|
+
7. 让 agent 读取工作区之外的文件,应无需提示直接放行(默认 `external_directory: allow`)。
|
|
232
|
+
8. 输入 `/permission status` 查看门禁状态;可尝试 `/permission allow ~/projects/**` 添加会话 allowlist。
|
|
233
|
+
9. 输入 `/plan-build status`,可只查看当前模式而不切换。
|
|
234
|
+
|
|
235
|
+
## 许可证
|
|
236
|
+
|
|
237
|
+
MIT
|
package/cordis.patch.yml
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
# Profile patch for dsh-plan-build-mode.
|
|
2
2
|
# Loaded automatically when the package is installed in a DSH profile.
|
|
3
|
+
#
|
|
4
|
+
# OpenCode-aligned defaults (v0.5.0):
|
|
5
|
+
# - planSandbox == buildSandbox == workspace-write: "read-only" in Plan mode is
|
|
6
|
+
# enforced by the permission gate (deny every write except the plans
|
|
7
|
+
# directory), not by the sandbox — so plan files can be persisted under
|
|
8
|
+
# .opencode/plans/.
|
|
9
|
+
# - externalDirectory.default: allow — reading files outside the workspace
|
|
10
|
+
# stays permitted in both modes; only writes are gated. Set to 'ask' for
|
|
11
|
+
# full OpenCode behavior (external access prompts).
|
|
3
12
|
- insert:
|
|
4
13
|
- id: plan-build-mode
|
|
5
14
|
name: '@xth26/dsh-plan-build-mode'
|
|
6
15
|
config:
|
|
7
|
-
planSandbox:
|
|
16
|
+
planSandbox: workspace-write
|
|
8
17
|
buildSandbox: workspace-write
|
|
9
18
|
denyWriteTools: true
|
|
10
19
|
section: true
|
|
20
|
+
plansDirectory: .opencode/plans
|
|
21
|
+
externalDirectory:
|
|
22
|
+
default: allow
|
package/lib/helpers.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export declare const DEFAULT_PLAN_SANDBOX: SandboxMode;
|
|
|
12
12
|
export declare const DEFAULT_BUILD_SANDBOX: SandboxMode;
|
|
13
13
|
/** Tools forbidden while in Plan mode. */
|
|
14
14
|
export declare const WRITE_TOOLS: Set<string>;
|
|
15
|
+
/** The durable Plan/Build mode event owned by this plugin. */
|
|
16
|
+
export declare const MODE_EVENT: "plan-build/mode";
|
|
17
|
+
/** A session's Plan/Build mode. */
|
|
18
|
+
export type PlanBuildMode = 'plan' | 'build';
|
|
15
19
|
/**
|
|
16
20
|
* Return the most recently written sandbox mode for a session, or undefined
|
|
17
21
|
* when the session has never switched.
|
|
@@ -22,15 +26,24 @@ export declare const WRITE_TOOLS: Set<string>;
|
|
|
22
26
|
*/
|
|
23
27
|
export declare function currentSandboxMode(session: Session): SandboxMode | undefined;
|
|
24
28
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
29
|
+
* The session's durable Plan/Build mode: the last `plan-build/mode` event, or
|
|
30
|
+
* an inference from the last `sandbox/mode` event against `planSandbox` for
|
|
31
|
+
* sessions created before the mode event existed. Returns undefined only when
|
|
32
|
+
* the session log carries neither.
|
|
33
|
+
*/
|
|
34
|
+
export declare function currentPlanBuildMode(session: Session, planSandbox: SandboxMode): PlanBuildMode | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Whether the session is currently in OpenCode Plan mode. The plugin's own
|
|
37
|
+
* `plan-build/mode` event is authoritative; legacy sessions (only
|
|
38
|
+
* `sandbox/mode` events) fall back to "current sandbox equals planSandbox".
|
|
28
39
|
*/
|
|
29
40
|
export declare function isPlanModeActive(session: Session, planSandbox: SandboxMode): boolean;
|
|
30
41
|
/**
|
|
31
|
-
* Append
|
|
42
|
+
* Append the durable `plan-build/mode` event to switch between Plan and Build
|
|
43
|
+
* mode, and — only when the target sandbox actually differs from the current
|
|
44
|
+
* one — the `sandbox/mode` event for sandbox enforcement.
|
|
32
45
|
*/
|
|
33
|
-
export declare function setPlanBuildMode(agent: Agent, mode:
|
|
46
|
+
export declare function setPlanBuildMode(agent: Agent, mode: PlanBuildMode, planSandbox: SandboxMode, buildSandbox: SandboxMode): void;
|
|
34
47
|
/**
|
|
35
48
|
* Compute a denial reason if a tool call should be blocked in Plan mode.
|
|
36
49
|
* Returns `undefined` when the call is allowed.
|
|
@@ -39,6 +52,9 @@ export declare function setPlanBuildMode(agent: Agent, mode: 'plan' | 'build', p
|
|
|
39
52
|
* configured as read-only. If Plan mode is configured to a writable sandbox,
|
|
40
53
|
* the plugin still tracks the mode but does not block tools — the real
|
|
41
54
|
* protection is the read-only sandbox.
|
|
55
|
+
*
|
|
56
|
+
* Kept for backward compatibility; the plugin's full permission gate
|
|
57
|
+
* (`permissions.ts`) supersedes this check.
|
|
42
58
|
*/
|
|
43
59
|
export declare function planModeDenial(exec: ToolExecution, planSandbox: SandboxMode, writeTools?: ReadonlySet<string>): string | undefined;
|
|
44
60
|
//# sourceMappingURL=helpers.d.ts.map
|
package/lib/helpers.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAG3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,wBAAwB,CAAA;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAA;AAG3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAA;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAA;AAI3D,sDAAsD;AACtD,eAAO,MAAM,oBAAoB,EAAE,WAAyB,CAAA;AAE5D,uDAAuD;AACvD,eAAO,MAAM,qBAAqB,EAAE,WAA+B,CAAA;AAEnE,0CAA0C;AAC1C,eAAO,MAAM,WAAW,aAA6B,CAAA;AAErD,8DAA8D;AAC9D,eAAO,MAAM,UAAU,EAAG,iBAA0B,CAAA;AAEpD,mCAAmC;AACnC,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,OAAO,CAAA;AAE5C;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,WAAW,GAAG,SAAS,CAS5E;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,GAAG,aAAa,GAAG,SAAS,CAe1G;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,GAAG,OAAO,CAEpF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,WAAW,GACxB,IAAI,CAMN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAC5B,IAAI,EAAE,aAAa,EACnB,WAAW,EAAE,WAAW,EACxB,UAAU,GAAE,WAAW,CAAC,MAAM,CAAe,GAC5C,MAAM,GAAG,SAAS,CAQpB"}
|
package/lib/helpers.js
CHANGED
|
@@ -8,6 +8,8 @@ export const DEFAULT_PLAN_SANDBOX = 'read-only';
|
|
|
8
8
|
export const DEFAULT_BUILD_SANDBOX = 'workspace-write';
|
|
9
9
|
/** Tools forbidden while in Plan mode. */
|
|
10
10
|
export const WRITE_TOOLS = new Set(['write', 'edit']);
|
|
11
|
+
/** The durable Plan/Build mode event owned by this plugin. */
|
|
12
|
+
export const MODE_EVENT = 'plan-build/mode';
|
|
11
13
|
/**
|
|
12
14
|
* Return the most recently written sandbox mode for a session, or undefined
|
|
13
15
|
* when the session has never switched.
|
|
@@ -27,19 +29,46 @@ export function currentSandboxMode(session) {
|
|
|
27
29
|
return undefined;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
32
|
+
* The session's durable Plan/Build mode: the last `plan-build/mode` event, or
|
|
33
|
+
* an inference from the last `sandbox/mode` event against `planSandbox` for
|
|
34
|
+
* sessions created before the mode event existed. Returns undefined only when
|
|
35
|
+
* the session log carries neither.
|
|
36
|
+
*/
|
|
37
|
+
export function currentPlanBuildMode(session, planSandbox) {
|
|
38
|
+
const events = session.snapshotEvents();
|
|
39
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
40
|
+
const event = events[i];
|
|
41
|
+
if (event.type === MODE_EVENT) {
|
|
42
|
+
return event.data.mode;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
for (let i = events.length - 1; i >= 0; i -= 1) {
|
|
46
|
+
const event = events[i];
|
|
47
|
+
if (event.type === 'sandbox/mode') {
|
|
48
|
+
return event.data.mode === planSandbox ? 'plan' : 'build';
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether the session is currently in OpenCode Plan mode. The plugin's own
|
|
55
|
+
* `plan-build/mode` event is authoritative; legacy sessions (only
|
|
56
|
+
* `sandbox/mode` events) fall back to "current sandbox equals planSandbox".
|
|
33
57
|
*/
|
|
34
58
|
export function isPlanModeActive(session, planSandbox) {
|
|
35
|
-
return
|
|
59
|
+
return currentPlanBuildMode(session, planSandbox) === 'plan';
|
|
36
60
|
}
|
|
37
61
|
/**
|
|
38
|
-
* Append
|
|
62
|
+
* Append the durable `plan-build/mode` event to switch between Plan and Build
|
|
63
|
+
* mode, and — only when the target sandbox actually differs from the current
|
|
64
|
+
* one — the `sandbox/mode` event for sandbox enforcement.
|
|
39
65
|
*/
|
|
40
66
|
export function setPlanBuildMode(agent, mode, planSandbox, buildSandbox) {
|
|
67
|
+
agent.session.append(MODE_EVENT, { mode });
|
|
41
68
|
const target = mode === 'plan' ? planSandbox : buildSandbox;
|
|
42
|
-
agent.session
|
|
69
|
+
if (currentSandboxMode(agent.session) !== target) {
|
|
70
|
+
agent.session.append('sandbox/mode', { mode: target });
|
|
71
|
+
}
|
|
43
72
|
}
|
|
44
73
|
/**
|
|
45
74
|
* Compute a denial reason if a tool call should be blocked in Plan mode.
|
|
@@ -49,6 +78,9 @@ export function setPlanBuildMode(agent, mode, planSandbox, buildSandbox) {
|
|
|
49
78
|
* configured as read-only. If Plan mode is configured to a writable sandbox,
|
|
50
79
|
* the plugin still tracks the mode but does not block tools — the real
|
|
51
80
|
* protection is the read-only sandbox.
|
|
81
|
+
*
|
|
82
|
+
* Kept for backward compatibility; the plugin's full permission gate
|
|
83
|
+
* (`permissions.ts`) supersedes this check.
|
|
52
84
|
*/
|
|
53
85
|
export function planModeDenial(exec, planSandbox, writeTools = WRITE_TOOLS) {
|
|
54
86
|
if (exec.agent === undefined)
|
package/lib/helpers.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"helpers.js","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"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"}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Host half of `dsh-plan-build-mode`.
|
|
3
3
|
*
|
|
4
|
-
* Implements an OpenCode-style Plan / Build mode
|
|
5
|
-
* DSH's built-in `/plan` soft-guidance mode:
|
|
4
|
+
* Implements an OpenCode-style Plan / Build mode with a tool permission gate:
|
|
6
5
|
*
|
|
7
6
|
* - `/plan-build` -> Toggle between Plan and Build modes
|
|
8
7
|
* - `/plan-build status` -> Show the current mode
|
|
9
|
-
* - `/plan-build switch plan` -> Plan mode
|
|
10
|
-
* - `/plan-build switch build` -> Build mode
|
|
11
|
-
* -
|
|
8
|
+
* - `/plan-build switch plan` -> Plan mode (read-only by policy)
|
|
9
|
+
* - `/plan-build switch build` -> Build mode (writable)
|
|
10
|
+
* - `/permission status|allow|deny` -> Inspect / adjust the permission gate
|
|
11
|
+
* - Denies write-category tools while in Plan mode (OpenCode `edit` permission)
|
|
12
|
+
* - Permits reads outside the workspace (DSH reads pass through every mode);
|
|
13
|
+
* external writes stay gated by the sandbox unless `external_directory` is
|
|
14
|
+
* configured to `ask`
|
|
12
15
|
* - Injects a `plan-build:policy` system prompt section
|
|
13
16
|
*
|
|
17
|
+
* Mode state lives in the plugin-owned `plan-build/mode` session event; the
|
|
18
|
+
* `sandbox/mode` event is still written for sandbox enforcement whenever the
|
|
19
|
+
* target sandbox actually differs.
|
|
20
|
+
*
|
|
14
21
|
* @module dsh-plan-build-mode
|
|
15
22
|
*/
|
|
16
23
|
import type { Context } from '@deepseek-ai/cordis';
|
|
@@ -21,21 +28,11 @@ export declare const name = "dsh-plan-build-mode";
|
|
|
21
28
|
/**
|
|
22
29
|
* Service dependencies for the host half. `commands` is injected optionally at
|
|
23
30
|
* runtime via ctx.inject(['commands'], ...) so headless assemblies stay
|
|
24
|
-
* unaffected;
|
|
31
|
+
* unaffected; `approval` is read optionally via ctx.get('approval').
|
|
25
32
|
*/
|
|
26
33
|
export declare const inject: readonly ["tools", "systemPrompt"];
|
|
27
34
|
/** Configuration schema. */
|
|
28
|
-
export declare const Config: z
|
|
29
|
-
planSandbox: z<"read-only" | "workspace-write" | "danger-full-access", "read-only" | "workspace-write" | "danger-full-access">;
|
|
30
|
-
buildSandbox: z<"read-only" | "workspace-write" | "danger-full-access", "read-only" | "workspace-write" | "danger-full-access">;
|
|
31
|
-
denyWriteTools: z<boolean, boolean>;
|
|
32
|
-
section: z<boolean, boolean>;
|
|
33
|
-
}>, Schemastery.ObjectT<{
|
|
34
|
-
planSandbox: z<"read-only" | "workspace-write" | "danger-full-access", "read-only" | "workspace-write" | "danger-full-access">;
|
|
35
|
-
buildSandbox: z<"read-only" | "workspace-write" | "danger-full-access", "read-only" | "workspace-write" | "danger-full-access">;
|
|
36
|
-
denyWriteTools: z<boolean, boolean>;
|
|
37
|
-
section: z<boolean, boolean>;
|
|
38
|
-
}>>;
|
|
35
|
+
export declare const Config: z;
|
|
39
36
|
/**
|
|
40
37
|
* Plugin entry point.
|
|
41
38
|
* @param ctx - Cordis context carrying the injected services.
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AA0CxC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAErD,0BAA0B;AAC1B,eAAO,MAAM,IAAI,wBAAwB,CAAA;AACzC;;;;GAIG;AACH,eAAO,MAAM,MAAM,oCAAqC,CAAA;AAOxD,4BAA4B;AAC5B,eAAO,MAAM,MAAM,EAeF,CAAC,CAAA;AAElB;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,GAAE,mBAAwB,GAAG,IAAI,CAyK1E"}
|