dsh-root-planner-preset 0.0.0-stage → 1.0.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/.gitattributes +10 -0
- package/INSTALL.md +125 -0
- package/LICENSE +21 -0
- package/README.md +81 -2
- package/build-patch.mjs +266 -0
- package/cordis.patch.yml +255 -0
- package/docs/DEVELOPMENT.md +209 -0
- package/lib-yaml.mjs +73 -0
- package/package.json +28 -4
- package/persona.txt +54 -0
- package/verify-depth-override.mjs +96 -0
- package/verify-patch.mjs +124 -0
package/.gitattributes
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# This bundle is verified by BYTE identity: verify-patch.mjs asserts that the
|
|
2
|
+
# persona parsed out of cordis.patch.yml equals persona.txt exactly. Git's
|
|
3
|
+
# autocrlf would rewrite both files on checkout and silently break that check on
|
|
4
|
+
# every Windows clone, so line endings are pinned to LF for the whole repository.
|
|
5
|
+
* text=auto eol=lf
|
|
6
|
+
|
|
7
|
+
# Binary if anyone ever adds artwork.
|
|
8
|
+
*.png binary
|
|
9
|
+
*.jpg binary
|
|
10
|
+
*.webp binary
|
package/INSTALL.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# 安装说明
|
|
2
|
+
|
|
3
|
+
面向**接收方**。本包是一个 DSH agent 预设(bundle),不改动 DSH 本体。
|
|
4
|
+
|
|
5
|
+
[返回中文说明](README.md) | [维护者文档](docs/DEVELOPMENT.md)
|
|
6
|
+
|
|
7
|
+
## 一、前置条件
|
|
8
|
+
|
|
9
|
+
装之前请对照确认,缺哪条后面都会报错:
|
|
10
|
+
|
|
11
|
+
| # | 条件 | 为什么 | 怎么确认 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| 1 | **DSH 版本足够新**,预设中带有本包引用的那些工具行 | 本补丁**镜像了随包 `standard` 预设的工具行**;行被改名或删除会导致该预设激活失败 | 看 DSH 里是否存在 `standard` / `ptc` / `cordis` 预设;能正常用 `subagent` 工具即可 |
|
|
14
|
+
| 2 | **本机已注册这些全局工具名** | 子代理白名单是 **allow 列表**,而 `tools.restrict()` 遇到**未知全局工具名会直接抛错、导致子代理创建失败** | 见下方清单 |
|
|
15
|
+
| 3 | **git** 在 PATH 上 | 从 GitHub 安装时 pnpm 需要 git 拉源码 | `git --version` |
|
|
16
|
+
| 4 | 能访问 GitHub | 拉取仓库 | 浏览器能打开本仓库 |
|
|
17
|
+
| 5 | Node(**仅**跑校验脚本时需要) | 两个校验脚本用 | `node -v` |
|
|
18
|
+
|
|
19
|
+
**第 2 条的必需工具名**(缺任何一个都要改白名单,见故障排查):
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
read glob grep read_image
|
|
23
|
+
ops_inspect_local_file ops_glob ops_grep
|
|
24
|
+
skill todo_write ask_user_question
|
|
25
|
+
get_goal create_goal update_goal
|
|
26
|
+
list_agents send_message interrupt_agent
|
|
27
|
+
subagent subagent_fork
|
|
28
|
+
web_search web_fetch
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> 其中 `ops_*` 三个来自社区包 `dsh-ops`。如果你的环境没有它们,**必须**把它们从白名单里删掉——白名单在 `cordis.patch.yml` 里两个 `toolFilter.allow` 处,改完跑一次 `node verify-patch.mjs`。
|
|
32
|
+
|
|
33
|
+
## 二、安装
|
|
34
|
+
|
|
35
|
+
三种方式任选,推荐第 1 种。
|
|
36
|
+
|
|
37
|
+
### 方式 1:命令行(推荐)
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
dsh plugin --profile web add github:XiaoOu2024/dsh-root-planner-preset
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
装完重启 `dsh web`。
|
|
44
|
+
|
|
45
|
+
> 也可以指定版本或分支:`github:XiaoOu2024/dsh-root-planner-preset#v1.0.0`
|
|
46
|
+
|
|
47
|
+
### 方式 2:让 DSH 里的 agent 装
|
|
48
|
+
|
|
49
|
+
对 agent 说:
|
|
50
|
+
|
|
51
|
+
> 用 `plugin_manager` 的 `install_bundle`,target 填 `github:XiaoOu2024/dsh-root-planner-preset`
|
|
52
|
+
|
|
53
|
+
注意:这个工具调用需要 **Full access** 权限或当次授权。
|
|
54
|
+
|
|
55
|
+
### 方式 3:先克隆再按路径装
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
git clone https://github.com/XiaoOu2024/dsh-root-planner-preset
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
然后让 agent 用 `install_bundle`,target 填**克隆下来的绝对路径**。
|
|
62
|
+
|
|
63
|
+
### ❌ 不适用:插件市场界面
|
|
64
|
+
|
|
65
|
+
**「设置 → 插件市场」搜不到本包,属正常。** dsh-market 只允许安装 `awesome-dsh-plugin` 精选列表内的来源,本包不在其中。请用上面三种方式。
|
|
66
|
+
|
|
67
|
+
## 三、装完必做:设置深度
|
|
68
|
+
|
|
69
|
+
进入 **设置 → 子智能体 → 深度上限**,填 **`2`**。
|
|
70
|
+
|
|
71
|
+
- **为什么必须手动做**:`maxDepth` 是 Host 设置,**不属于本 bundle**——本包故意不在工具行上写死它,否则会把你以后调高的值悄悄压回去。
|
|
72
|
+
- **数值含义**:`2` = 根(0) → 子代理(1) → 孙代理(2),只有 depth 1 能再派,depth 2 是叶子。
|
|
73
|
+
- **不要填 `0`**:那会禁掉全部委派,本预设的设计直接失效。
|
|
74
|
+
- 填 `1` 也能跑,但子代理就不能再派了,中间层失去作用。
|
|
75
|
+
|
|
76
|
+
## 四、启用与验证
|
|
77
|
+
|
|
78
|
+
1. **开一个新会话**(已有会话和它的子代理会保留启动时的插件版本,不会变)。
|
|
79
|
+
2. 在预设选择器里选 **Root Planner**。
|
|
80
|
+
3. 派一个研究任务,确认两件事:
|
|
81
|
+
|
|
82
|
+
- 子代理的工具列表里**没有** `write`、`edit`、`ops_replace`、`ops_bash`、`pwsh`;
|
|
83
|
+
- 子代理**能**再派一层,而它的子代理**不能**(会返回深度错误)。
|
|
84
|
+
|
|
85
|
+
4. (可选)在克隆下来的目录里跑校验:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
node verify-patch.mjs
|
|
89
|
+
node verify-depth-override.mjs
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
两条都应输出 `ALL CHECKS PASSED`。第二条在没配深度覆盖的机器上会打印 `SKIP` 并说明原因——那是正常提示,不是失败。
|
|
93
|
+
|
|
94
|
+
## 五、故障排查
|
|
95
|
+
|
|
96
|
+
| 现象 | 原因 | 怎么办 |
|
|
97
|
+
|---|---|---|
|
|
98
|
+
| 预设列表里**没有 Root Planner** | bundle 没装上,或预设行激活失败(会带诊断留在名册上) | 看 DSH 的插件/加载诊断;多半是第 1 条前置不满足——本包的补丁引用了随包预设里不存在的工具行 |
|
|
99
|
+
| DSH **启动失败**(进程直接退出) | DSH 启动是全有全无,一个插件加载失败整个进程就退出 | 用插件市场的「**调整插件**」恢复界面:DSH 点名的插件会标红并默认取消勾选,勾好再启动。找不到该界面时,手工编辑 profile 的 `cordis.patch.yml` 把对应行设为 `disabled: true` |
|
|
100
|
+
| 子代理创建失败,报 **`tools.restrict() names unknown global tool …`** | 白名单里有你的环境**没注册**的工具名 | 按报错里点出的名字,从 `cordis.patch.yml` 两个 `toolFilter.allow` 里删掉该行,重装或重开会话 |
|
|
101
|
+
| 委派立刻返回**深度错误** | `maxDepth` 没设或设得太小 | 见第三节,设为 `2` |
|
|
102
|
+
| 子代理**什么都不能读** | 白名单过窄(比如你删过头了) | 至少保留 `read`、`glob`、`grep`、`ops_inspect_local_file`、`ops_glob`、`ops_grep` |
|
|
103
|
+
| 子代理**还是能改文件** | 白名单被改坏了 | 确认 `cordis.patch.yml` 里两处 `toolFilter.allow` 都**不含** `write`、`edit`、`ops_replace`、`ops_bash`、`pwsh`;改完必须重开会话 |
|
|
104
|
+
| 改了 `persona.txt` 但没生效 | 会话与子代理都锁定启动时的插件版本 | 开**新会话**;必要时把 `preset-root-planner` 在插件列表里关掉再打开 |
|
|
105
|
+
| Windows 上校验报**人设不一致** | 换行符被 Git 改写了 | 确认 `.gitattributes` 存在且含 `* text=auto eol=lf`,重新克隆一次 |
|
|
106
|
+
|
|
107
|
+
## 六、卸载
|
|
108
|
+
|
|
109
|
+
**推荐**:让 agent 调 `plugin_manager` 的 `remove_bundle`,target 填 `dsh-root-planner-preset`。它会同时处理包和 profile 清单里的登记,最干净。
|
|
110
|
+
|
|
111
|
+
**命令行备选**:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
dsh plugin --profile web remove dsh-root-planner-preset
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
⚠️ 但注意:命令行的 `remove` 是**直接透传给 pnpm** 的(插件管理器只接管 `add`/`install`/`i`),所以它**只删包,不会清理** profile 的 `dsh.profile.bundles` 里的登记。用命令行卸载后,建议检查一下 `~/.dsh/profiles/<你的 profile>/package.json` 的 `dsh.profile.bundles` 数组,手工删掉残留条目,否则下次启动可能因找不到包而报错。
|
|
118
|
+
|
|
119
|
+
卸载后**不影响**你已经设过的深度上限——那是 Host 设置,不是本包带来的。
|
|
120
|
+
|
|
121
|
+
## 七、一个诚实的提醒
|
|
122
|
+
|
|
123
|
+
本包**尚未在作者环境之外实测过子代理运行**——白名单里的工具名是按作者环境的实际工具清单交叉核对出来的。所以第 2 条前置条件务必先对照一遍,尤其是 `ops_*` 那几个(它们来自社区包 `dsh-ops`,不是 DSH 自带)。
|
|
124
|
+
|
|
125
|
+
如果你撞上了没列在上面的错误,麻烦带上报错原文开个 issue。
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 小欧
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,82 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-root-planner-preset
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
一个面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness)(DSH)的 agent 预设:**把「读书」和「决策」分开**。
|
|
4
|
+
|
|
5
|
+
根 agent 的上下文只装任务分析、规划与决策;文件阅读、信息查询交给子代理,子代理只回摘要。而**创建、修改、删除文件只能由根 agent 执行**——子代理连工具都没有。
|
|
6
|
+
|
|
7
|
+
> 一句话:让整棵树里只有一个 agent 有手,其余都只有眼睛。
|
|
8
|
+
|
|
9
|
+
[安装步骤](INSTALL.md) | [维护者文档](docs/DEVELOPMENT.md)
|
|
10
|
+
|
|
11
|
+
## 它解决什么问题
|
|
12
|
+
|
|
13
|
+
用 coding agent 干长任务,上下文很快被原始文件内容塞满,于是忘了目标、丢了计划、开始重复劳动。常见对策是「让 agent 多派子代理」,但光靠提示词约束不住——子代理照样会顺手改文件,任务也会被无限往下转派。
|
|
14
|
+
|
|
15
|
+
这个预设把两件事做成了结构约束,而不是靠自觉:
|
|
16
|
+
|
|
17
|
+
| 要求 | 机制 | 强度 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 子代理**不能**创建/修改/删除文件 | 两个委派工具上的 `toolFilter.allow` 白名单,不含 `write`、`edit`、`ops_replace` | **结构性**:工具不存在,调不到 |
|
|
20
|
+
| 子代理**没有** shell 可绕过 | 同一白名单,不含 `ops_bash`、`pwsh` | **结构性** |
|
|
21
|
+
| 任务不会被无限转派 | 运行时深度上限(`subagent.maxDepth`) | **结构性**:超过上限直接抛 `SubagentDepthError` |
|
|
22
|
+
| 根 agent 独揽所有修改 | 人设规则(白名单只作用于子级) | 提示词级 |
|
|
23
|
+
| 根 agent 默认委派阅读 | 人设规则 | 提示词级 |
|
|
24
|
+
|
|
25
|
+
白名单是 **allow 而非 deny**:将来某个插件新增的工具,对子代理默认不可见。
|
|
26
|
+
|
|
27
|
+
## 工作方式
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
根 agent (depth 0) ← 只有它有 write / edit / shell
|
|
31
|
+
├── 子代理 (depth 1) ← 只读;可以再派一层
|
|
32
|
+
│ └── 孙代理 (depth 2) ← 只读;叶子
|
|
33
|
+
└── 子代理 (depth 1)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **根 agent**:先立计划(目标、成功判据、约束、任务清单),把阅读/搜索/定位默认派出去,自己只在「单个已知文件、一小段范围、或核实子代理的某个断言」时才亲自读。
|
|
37
|
+
- **子代理**:只读、只定位、只总结。发现**需要改文件**时不动手,而是回报精确的改动方案——目标路径、位置(file:line)、当前内容、建议替换、理由。
|
|
38
|
+
- **根 agent 收到方案后决策**,然后亲自落盘。
|
|
39
|
+
|
|
40
|
+
子代理怎么知道自己是子代理?DSH 会在每个子代理的提示词里强制插入一句 `You are a delegated subagent: …`,运行时注入、无法移除,而预设人设**没有** `{{role}}` 这类变量可用。所以人设里写的是一条**判定指令**:读自己的提示词里有没有那句话。这让人设只需要一份,父与子读到的是同一份文本,因此不会出现「父以为子被这么约束、子其实没读到」的漂移。
|
|
41
|
+
|
|
42
|
+
## 快速开始
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
dsh plugin --profile web add github:XiaoOu2024/dsh-root-planner-preset
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
装完**必须**再做一步:**设置 → 子智能体 → 深度上限 = `2`**。(这是 Host 的 `subagent` 设置节,**全局生效、不属于** bundle;设成 `0` 会禁掉全部委派,设计就废了。)
|
|
49
|
+
|
|
50
|
+
然后**开新会话**,在预设选择器里选 **Root Planner**。完整步骤与故障排查见 [INSTALL.md](INSTALL.md)。
|
|
51
|
+
|
|
52
|
+
## 适合 / 不适合
|
|
53
|
+
|
|
54
|
+
**适合**:需要跨很多文件调研的长任务、逆向分析、代码审查,以及任何「读得多、改得少」或「改动必须被仔细决策」的工作。
|
|
55
|
+
|
|
56
|
+
**不适合**:
|
|
57
|
+
|
|
58
|
+
- 只想快速改一个文件——多一层委派是纯粹的开销;
|
|
59
|
+
- 需要子代理**并行写代码**——这是本预设**故意**禁止的,请改用 `standard` 预设;
|
|
60
|
+
- 环境缺少 DSH 的工具基础(见 [INSTALL.md](INSTALL.md) 的前置条件)。
|
|
61
|
+
|
|
62
|
+
## 仓库内容
|
|
63
|
+
|
|
64
|
+
| 文件 | 说明 |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `cordis.patch.yml` | 预设声明(**由脚本生成,不要手改**) |
|
|
67
|
+
| `persona.txt` | 人设正文,**要改就改这个** |
|
|
68
|
+
| `build-patch.mjs` | 从 `persona.txt` 生成 `cordis.patch.yml` |
|
|
69
|
+
| `verify-patch.mjs` | 校验补丁结构、工具白名单,并断言人设逐字节一致 |
|
|
70
|
+
| `verify-depth-override.mjs` | 校验深度覆盖是否落到合成配置 |
|
|
71
|
+
| `lib-yaml.mjs` | 从本地 DSH 安装里解析 YAML,因此本包**零依赖** |
|
|
72
|
+
| `docs/DEVELOPMENT.md` | 维护者文档:设计取舍、机制细节、已验证/未验证边界 |
|
|
73
|
+
|
|
74
|
+
## 已验证 / 未验证
|
|
75
|
+
|
|
76
|
+
**已验证**:补丁可被 loader 解析;解析出的人设与 `persona.txt` **逐字节相同**;两个委派工具的白名单排除了所有写入入口、且只包含本机实际注册的工具名;干净检出后校验通过(`.gitattributes` 已钉死 LF,否则 Windows 克隆会破坏逐字节校验);深度覆盖已落到合成配置。
|
|
77
|
+
|
|
78
|
+
**未验证**:**尚未在 `root-planner` 预设下真正派出过一个子代理。** 白名单里的工具名是按作者环境的实际工具清单交叉核对出来的,不是运行时实测。若你的环境缺少其中某个全局工具名,子代理创建会失败并报 `tools.restrict() names unknown global tool …`。详见 [INSTALL.md](INSTALL.md) 的故障排查。
|
|
79
|
+
|
|
80
|
+
## 许可
|
|
81
|
+
|
|
82
|
+
[MIT](LICENSE)
|
package/build-patch.mjs
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
// Generates root-planner-preset/cordis.patch.yml from persona.txt.
|
|
2
|
+
//
|
|
3
|
+
// The persona is a long multi-paragraph text, and hand-indenting it into a YAML
|
|
4
|
+
// block scalar is exactly the kind of edit that silently produces a truncated or
|
|
5
|
+
// mis-indented prompt. This generator owns that indentation instead: edit
|
|
6
|
+
// persona.txt, re-run this script, and the patch stays valid.
|
|
7
|
+
//
|
|
8
|
+
// Usage: node build-patch.mjs
|
|
9
|
+
|
|
10
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { dirname, join } from 'node:path';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
|
|
14
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
/** Read persona.txt, normalize line endings, and require content. */
|
|
17
|
+
function readPersona() {
|
|
18
|
+
const text = readFileSync(join(here, 'persona.txt'), 'utf8').replace(/\r\n/g, '\n').trimEnd();
|
|
19
|
+
if (text.length === 0) throw new Error('persona.txt is empty');
|
|
20
|
+
if (text.includes('\t')) throw new Error('persona.txt contains a tab: YAML block scalars forbid tab indentation');
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Indent a block scalar body for `prefix: |-`. Every line gets `depth` spaces,
|
|
26
|
+
* because the first line of a literal block scalar sits on its own line at the
|
|
27
|
+
* same indentation as the rest; blank lines stay empty so no trailing
|
|
28
|
+
* whitespace reaches the prompt.
|
|
29
|
+
*/
|
|
30
|
+
function blockScalar(text, depth) {
|
|
31
|
+
const pad = ' '.repeat(depth);
|
|
32
|
+
return text
|
|
33
|
+
.split('\n')
|
|
34
|
+
.map((line) => line.replace(/\s+$/, ''))
|
|
35
|
+
.map((line) => (line.length === 0 ? '' : pad + line))
|
|
36
|
+
.join('\n');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Render one `allow:` key and its entry list at the given indentation. */
|
|
40
|
+
function allowList(names, keyDepth) {
|
|
41
|
+
const keyPad = ' '.repeat(keyDepth);
|
|
42
|
+
const itemPad = ' '.repeat(keyDepth + 2);
|
|
43
|
+
return [`${keyPad}allow:`, ...names.map((name) => `${itemPad}- ${name}`)].join('\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The child mask: the only global tools a subagent may call. Everything absent
|
|
48
|
+
* is invisible to the child, including every mutation entry point.
|
|
49
|
+
*/
|
|
50
|
+
const CHILD_TOOLS = [
|
|
51
|
+
'read',
|
|
52
|
+
'glob',
|
|
53
|
+
'grep',
|
|
54
|
+
'ops_inspect_local_file',
|
|
55
|
+
'ops_glob',
|
|
56
|
+
'ops_grep',
|
|
57
|
+
'web_search',
|
|
58
|
+
'web_fetch',
|
|
59
|
+
'read_image',
|
|
60
|
+
'skill',
|
|
61
|
+
'todo_write',
|
|
62
|
+
'ask_user_question',
|
|
63
|
+
'get_goal',
|
|
64
|
+
'create_goal',
|
|
65
|
+
'update_goal',
|
|
66
|
+
'list_agents',
|
|
67
|
+
'send_message',
|
|
68
|
+
'interrupt_agent',
|
|
69
|
+
'subagent',
|
|
70
|
+
'subagent_fork',
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const persona = readPersona();
|
|
74
|
+
|
|
75
|
+
/** The delegation group is shared by both shapes of child, so the mask is declared once. */
|
|
76
|
+
const patch = `# Generated by build-patch.mjs from persona.txt. Do not hand-edit:
|
|
77
|
+
# edit persona.txt and re-run the generator.
|
|
78
|
+
|
|
79
|
+
# The root-planner preset. It mirrors the shipped standard preset's tool list
|
|
80
|
+
# so behavior differs only where this design intends it to, and it declares no
|
|
81
|
+
# persona line on a delegation tool: the child must inherit this same
|
|
82
|
+
# persona, because the persona itself carries the subagent rules.
|
|
83
|
+
- insert:
|
|
84
|
+
- id: preset-root-planner
|
|
85
|
+
name: '@deepseek-ai/dsh-agent-preset'
|
|
86
|
+
config:
|
|
87
|
+
id: root-planner
|
|
88
|
+
name: Root Planner
|
|
89
|
+
description: Plans in a lean context, delegates reading to strictly read-only subagents, and owns every file mutation.
|
|
90
|
+
order: 5
|
|
91
|
+
plugins:
|
|
92
|
+
- id: persona
|
|
93
|
+
name: '@deepseek-ai/dsh-persona'
|
|
94
|
+
config:
|
|
95
|
+
suffix: Your working directory is {{cwd}}.
|
|
96
|
+
prefix: |-
|
|
97
|
+
<PERSONA>
|
|
98
|
+
- id: agent-instructions
|
|
99
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
100
|
+
config:
|
|
101
|
+
maxBytes: 65536
|
|
102
|
+
- id: tool-bash
|
|
103
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
104
|
+
disabled: !!js process.platform === 'win32'
|
|
105
|
+
- id: tool-pwsh
|
|
106
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
107
|
+
disabled: !!js process.platform !== 'win32'
|
|
108
|
+
- id: tool-fs
|
|
109
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
110
|
+
- id: tool-fs-search
|
|
111
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
112
|
+
config:
|
|
113
|
+
sampleOverCapGlobResults: false
|
|
114
|
+
- id: tool-jobs
|
|
115
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
116
|
+
- id: skill-filesystem
|
|
117
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
118
|
+
- id: tool-skill
|
|
119
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
120
|
+
- id: command-goal
|
|
121
|
+
name: '@deepseek-ai/dsh-command-goal'
|
|
122
|
+
- id: tool-goal
|
|
123
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
124
|
+
- id: planning
|
|
125
|
+
name: cordis:group
|
|
126
|
+
group: true
|
|
127
|
+
isolate:
|
|
128
|
+
planMode: true
|
|
129
|
+
config:
|
|
130
|
+
- id: plan-mode
|
|
131
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
132
|
+
config:
|
|
133
|
+
section: |
|
|
134
|
+
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement, including an answer confirming something you asked, approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
135
|
+
|
|
136
|
+
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
137
|
+
|
|
138
|
+
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed only to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
|
139
|
+
|
|
140
|
+
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
141
|
+
|
|
142
|
+
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
143
|
+
|
|
144
|
+
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
145
|
+
- id: compaction
|
|
146
|
+
name: cordis:group
|
|
147
|
+
group: true
|
|
148
|
+
isolate:
|
|
149
|
+
compaction: true
|
|
150
|
+
toolResultPruner: true
|
|
151
|
+
config:
|
|
152
|
+
- id: compaction-basic
|
|
153
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
154
|
+
- id: command-compact
|
|
155
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
156
|
+
- id: tool-result-pruner
|
|
157
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
158
|
+
config:
|
|
159
|
+
thresholdChars: 8192
|
|
160
|
+
headChars: 4096
|
|
161
|
+
tailChars: 1024
|
|
162
|
+
# Delegation. Both tools share ONE readonly mask, so every child in the
|
|
163
|
+
# tree is read-only no matter which tool produced it. maxDepth is left
|
|
164
|
+
# unset on purpose: it resolves from the Host subagent.maxDepth setting
|
|
165
|
+
# (default 1), and this preset needs unbounded depth for read-only
|
|
166
|
+
# recursion. Do not raise that Host setting to 0 while using this preset.
|
|
167
|
+
- id: delegation
|
|
168
|
+
name: cordis:group
|
|
169
|
+
group: true
|
|
170
|
+
isolate:
|
|
171
|
+
workflowEngine: true
|
|
172
|
+
config:
|
|
173
|
+
- id: tool-subagent-control
|
|
174
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
175
|
+
- id: tool-subagent-list-agents
|
|
176
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
177
|
+
- id: tool-subagent
|
|
178
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
179
|
+
config:
|
|
180
|
+
provider: spawn
|
|
181
|
+
toolName: subagent
|
|
182
|
+
modelSelectionSettings: true
|
|
183
|
+
backgroundMode: continuable
|
|
184
|
+
toolFilter:
|
|
185
|
+
<MASK>
|
|
186
|
+
- id: tool-subagent-fork
|
|
187
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
188
|
+
config:
|
|
189
|
+
provider: fork
|
|
190
|
+
toolName: subagent_fork
|
|
191
|
+
backgroundMode: continuable
|
|
192
|
+
toolFilter:
|
|
193
|
+
<MASK>
|
|
194
|
+
- id: tool-subagent-codex
|
|
195
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
196
|
+
disabled: true
|
|
197
|
+
config:
|
|
198
|
+
provider: codex
|
|
199
|
+
toolName: subagent_codex
|
|
200
|
+
backgroundMode: one-shot
|
|
201
|
+
maxDepth: provider-managed
|
|
202
|
+
- id: tool-subagent-claude-code
|
|
203
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
204
|
+
disabled: true
|
|
205
|
+
config:
|
|
206
|
+
provider: claude-code
|
|
207
|
+
toolName: subagent_claude_code
|
|
208
|
+
backgroundMode: one-shot
|
|
209
|
+
maxDepth: provider-managed
|
|
210
|
+
- id: workflow-ptc
|
|
211
|
+
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
212
|
+
config:
|
|
213
|
+
provider: spawn
|
|
214
|
+
- id: tool-workflow
|
|
215
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
216
|
+
- id: tool-ralph
|
|
217
|
+
name: '@deepseek-ai/dsh-tool-ralph'
|
|
218
|
+
disabled: true
|
|
219
|
+
config:
|
|
220
|
+
subagentProvider: spawn
|
|
221
|
+
maxRounds: 64
|
|
222
|
+
- id: tool-ask-user
|
|
223
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
224
|
+
- id: tool-todo
|
|
225
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
226
|
+
config:
|
|
227
|
+
allowParallelInProgress: true
|
|
228
|
+
- id: tool-web
|
|
229
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
230
|
+
config:
|
|
231
|
+
fetch: true
|
|
232
|
+
searchTimeoutMs: 60000
|
|
233
|
+
- id: present
|
|
234
|
+
name: '@deepseek-ai/dsh-tool-present'
|
|
235
|
+
- id: tool-plugin-manager
|
|
236
|
+
name: '@deepseek-ai/dsh-plugin-manager/tools'
|
|
237
|
+
disabled: true
|
|
238
|
+
`;
|
|
239
|
+
|
|
240
|
+
// Substitution happens after the template so the emitted indentation is owned by
|
|
241
|
+
// blockScalar/allowList rather than by the whitespace in this file.
|
|
242
|
+
const rendered = patch
|
|
243
|
+
.replace('<PERSONA>', blockScalar(persona, 16))
|
|
244
|
+
.replaceAll('<MASK>', allowList(CHILD_TOOLS, 20));
|
|
245
|
+
|
|
246
|
+
if (rendered.includes('<PERSONA>') || rendered.includes('<MASK>')) {
|
|
247
|
+
throw new Error('a placeholder survived substitution');
|
|
248
|
+
}
|
|
249
|
+
// Structural checks on the emitted text. A literal "N spaces" assertion is
|
|
250
|
+
// fragile against editor whitespace handling, so indentation is measured.
|
|
251
|
+
const maskBlocks = [...rendered.matchAll(/^([ ]+)allow:\n([ ]+)- read\n/gm)];
|
|
252
|
+
if (maskBlocks.length !== 2) {
|
|
253
|
+
throw new Error(`expected 2 toolFilter allow blocks, found ${maskBlocks.length}`);
|
|
254
|
+
}
|
|
255
|
+
for (const [, keyIndent, itemIndent] of maskBlocks) {
|
|
256
|
+
if (keyIndent.length !== 20 || itemIndent.length !== 22) {
|
|
257
|
+
throw new Error(`mask indentation is ${keyIndent.length}/${itemIndent.length}, expected 20/22`);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
const personaBlock = rendered.match(/^([ ]+)prefix: \|-\n/gm);
|
|
261
|
+
if (personaBlock === null || personaBlock.length !== 1) {
|
|
262
|
+
throw new Error(`expected 1 persona block scalar, found ${personaBlock?.length ?? 0}`);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
writeFileSync(join(here, 'cordis.patch.yml'), rendered, 'utf8');
|
|
266
|
+
process.stdout.write(`wrote cordis.patch.yml (persona ${persona.length} chars, ${persona.split('\n').length} lines)\n`);
|