dsh-tree-task-flow 0.3.3 → 0.3.5
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 +76 -35
- package/client.js +52 -19
- package/lib/commands.js +6 -4
- package/lib/http.js +1 -1
- package/lib/plan.js +215 -89
- package/lib/prompt.js +1 -12
- package/lib/region.js +220 -212
- package/lib/store.js +21 -7
- package/lib/tools.js +41 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,46 +22,73 @@
|
|
|
22
22
|
| 过程 | 读文件、试错、命令输出 | 节点完成时**整段折叠掉** |
|
|
23
23
|
| 结果 | 模型在 `tree_task_done` 里提交的 `result` | 留下来,供后续节点引用 |
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## 折叠按什么切
|
|
26
|
+
|
|
27
|
+
规则只有一条,**只看表层,与计划树无关**:
|
|
26
28
|
|
|
27
29
|
```
|
|
28
|
-
|
|
29
|
-
[用户消息]
|
|
30
|
-
[目标级汇总] ← 换目标时逐条累积
|
|
31
|
-
[任务级汇总] ← 换任务时逐条累积
|
|
32
|
-
[子任务汇总] [子任务汇总] … ← 同级逐条累积,互不卷走
|
|
33
|
-
[当前子任务的执行过程] ← 下一次折叠的对象
|
|
30
|
+
从上一个边界调用之后 → 到下一个 tree_task_done 之前,这一段收走
|
|
34
31
|
```
|
|
35
32
|
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
边界调用是 `tree_task_create` / `tree_task_plan` / `tree_task_done` 这三个工具,
|
|
34
|
+
它们的调用**与返回**都留在上下文里——模型靠它们看"计划是什么、下一步干什么"。
|
|
35
|
+
折叠只收它们**之间**的过程,换成一条计数通告:
|
|
38
36
|
|
|
39
37
|
```
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
38
|
+
[tree_task_plan 调用] [返回:已添加 2 个子任务 + 整棵树]
|
|
39
|
+
[tree_task消息:隐藏了 12 条过程上下文。读取:… 写入:…]
|
|
40
|
+
[tree_task_done 调用] [返回:接下来需要进行步骤:「二」(s-4)。]
|
|
43
41
|
```
|
|
44
42
|
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
两条硬约束:
|
|
44
|
+
|
|
45
|
+
- 两次边界调用之间**夹着你的消息就不折**——往前折丢了需求,往后折丢了回应,
|
|
46
|
+
这一段整段跳过,一个节点都不动;
|
|
47
|
+
- 两次调用**挨着**(中间没有过程)也什么都不注入,不追加、不留空消息。
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
|
|
49
|
+
每一次 `tree_task_done` 的调用与它的返回都**留在上下文里**,一轮轮累积;
|
|
50
|
+
模型下一轮还看得到"我刚做完了什么、接下来干什么"。
|
|
50
51
|
|
|
51
52
|
折叠发生在 `agent/pre-step`——上一轮已结束、下一个请求还没构造的那一刻。
|
|
52
53
|
工具执行中途不能折叠,那时 surface 还在增长,替换会把自己卷进去。
|
|
53
54
|
|
|
54
|
-
##
|
|
55
|
+
## 注入消息与 tree_task_done 的返回
|
|
56
|
+
|
|
57
|
+
一次折叠在上下文里留下**两样**东西:
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
**一、折叠通告——哪一段被收走了**
|
|
57
60
|
|
|
58
61
|
```
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
+
tree_task消息:隐藏了12条过程上下文。
|
|
63
|
+
读取:C:\apps\x\a.js、C:\apps\x\b.js
|
|
64
|
+
写入:C:\apps\x\c.js
|
|
62
65
|
```
|
|
63
66
|
|
|
64
|
-
|
|
67
|
+
只报条数与**读写了哪些文件**:过程收走了,但"这活碰过哪些文件"得留下,
|
|
68
|
+
否则模型要从头再找一遍。路径取自工具调用的 `file_path` / `path` 参数——
|
|
69
|
+
界面列出文件变更用的是同一个来源;`pwsh` / `bash` 里碰过什么文件看不出来,不计入。
|
|
70
|
+
|
|
71
|
+
**二、`tree_task_done` 的返回——下一步干什么**
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
tree_task消息:接下来需要进行步骤:「步骤二」(s-4)。
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
按情形是三种之一:
|
|
78
|
+
|
|
79
|
+
| 情形 | 返回的是 |
|
|
80
|
+
|---|---|
|
|
81
|
+
| 同层还有下一个 | `tree_task消息:接下来需要进行步骤/任务:「标题」(id)。` |
|
|
82
|
+
| 同层到头、上一层还没交 | `tree_task消息:检查点:任务/目标<id> 的步骤/任务已全部结束,请确认<id> 是否完成,如果完成请调用tree_task_done,如果还需继续,可使用 tree_task_plan 新增步骤/任务继续。` |
|
|
83
|
+
| 整棵树都完了 | `tree_task消息:请汇报最终结果。` |
|
|
84
|
+
|
|
85
|
+
两样都**不重复提交内容**(`result` 就在这次调用的参数里)。折叠时,**这次
|
|
86
|
+
`tree_task_done` 的调用连同它的返回原样留在上下文里**,被收走的只是它**前面**那段过程。
|
|
87
|
+
|
|
88
|
+
**子任务全部结束,不会让父节点自动完成。** 那一刻树进入**检查点**,
|
|
89
|
+
也就是上表第二种返回。
|
|
90
|
+
|
|
91
|
+
**检查点只是提示,不是闸门。** 它出现在树上,也出现在 `tree_task_done` 的返回里,
|
|
65
92
|
但插件**不会拦住任何工具**——"这一层到底做完了没有"由模型判断,插件既不替它定死,
|
|
66
93
|
也不靠拒绝别的工具把它逼到墙角。
|
|
67
94
|
|
|
@@ -70,14 +97,28 @@
|
|
|
70
97
|
| 工具 | 用途 |
|
|
71
98
|
|---|---|
|
|
72
99
|
| `tree_task_status` | 查看整棵树、各节点状态与已提交的结果,以及现在该推进哪个节点 |
|
|
73
|
-
| `tree_task_create` |
|
|
100
|
+
| `tree_task_create` | 新增一个目标:这个目标 + 它下面的若干任务。可多次调用,多个目标并存,不替换已有计划 |
|
|
74
101
|
| `tree_task_plan` | 给一个节点追加子节点:给目标追加任务,给任务追加子任务 |
|
|
75
|
-
| `tree_task_done` | 完成一个节点并提交它的结果,**`result`
|
|
102
|
+
| `tree_task_done` | 完成一个节点并提交它的结果,**`result` 必填**;返回的就是"下一步干什么",这次调用连同返回留在上下文里,被收走的只是它前面的执行过程 |
|
|
76
103
|
| `tree_task_update` | 修改一个节点的标题或说明 |
|
|
77
|
-
| `tree_task_drop` |
|
|
104
|
+
| `tree_task_drop` | 丢弃一个节点;丢弃目标会连同它下面的任务与子任务一起走,丢弃任务带上它的子任务 |
|
|
78
105
|
|
|
79
106
|
层级固定三级,子任务是最低一级,不能再往下拆。
|
|
80
107
|
|
|
108
|
+
### 三层怎么分
|
|
109
|
+
|
|
110
|
+
| 层 | 是什么 | 怎么算分对了 |
|
|
111
|
+
|---|---|---|
|
|
112
|
+
| **L1 目标** | 最终要交付的东西 | 一个会话可以有多个,按创建顺序排;每个都写清要交付什么 |
|
|
113
|
+
| **L2 任务** | **交付节点** | 一件能独立验收的交付物(一个文件、一条能跑的命令、一份结论);下面覆盖多个步骤,别拆到只剩一两个 |
|
|
114
|
+
| **L3 子任务** | **步骤** | 任务内部一两次工具调用能做完的事 |
|
|
115
|
+
|
|
116
|
+
**折叠次数跟着 `tree_task_done` 的次数走**:每完成一个节点就折一次,界面也会因此多一张
|
|
117
|
+
系统提示词卡片。所以任务别拆得太碎——够厚,done 的次数少,上下文前缀变动的次数也少。
|
|
118
|
+
|
|
119
|
+
`result` 分两层写:任务的 result 写**交付物 + 怎么验收**(文件在哪、怎么跑、验出什么、
|
|
120
|
+
已知限制),子任务的 result 一句话说清这步做了什么就够。
|
|
121
|
+
|
|
81
122
|
## 安装
|
|
82
123
|
|
|
83
124
|
需要 Node.js 22.19 以上,以及能运行 `dsh`。
|
|
@@ -219,7 +260,7 @@ dsh --profile web --dump-config # 输出里应出现 task-tree 这一
|
|
|
219
260
|
| 文件 | 内容 |
|
|
220
261
|
|---|---|
|
|
221
262
|
| `plans/<sessionId>.json` | 计划树(每个会话一棵) |
|
|
222
|
-
| `surfaces/<sessionId>.json` |
|
|
263
|
+
| `surfaces/<sessionId>.json` | 三层的进度游标 `{ key, cursor }`(只给界面看,不参与折叠) |
|
|
223
264
|
|
|
224
265
|
> 目录名沿用插件改名前的标识,为的是不让既有的计划文件失联;插件名与它无关。
|
|
225
266
|
|
|
@@ -235,21 +276,21 @@ dsh --profile web --dump-config # 输出里应出现 task-tree 这一
|
|
|
235
276
|
空 `result` 会被拒绝。所以要写清"产出了什么",写成能独立看懂的样子——
|
|
236
277
|
过程会丢,别把过程写进去。
|
|
237
278
|
|
|
238
|
-
2.
|
|
239
|
-
|
|
279
|
+
2. **折叠只看表层。** 两次边界调用之间没有过程时,什么都不注入;中间夹着你的消息时
|
|
280
|
+
整段不折——那一段原样留着(往前折丢需求、往后折丢回应),代价是上下文不收缩。
|
|
240
281
|
|
|
241
282
|
3. **模型看到的工具说明不随计划变化,也不复述工具清单。** 那一段只讲什么时候用
|
|
242
283
|
这组工具、完成时守什么规矩;每个工具的用法由它自己的 `description` 承担。
|
|
243
284
|
要了解计划就让模型调 `tree_task_status`。
|
|
244
285
|
|
|
245
|
-
4.
|
|
246
|
-
DSH 把"上下文被替换过"
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
286
|
+
4. **折叠几次,界面就多几张「系统提示词」卡片,但上下文里的系统提示词始终只有一条。**
|
|
287
|
+
DSH 把"上下文被替换过"记成一次请求序列的边界(下一次请求带 `reason=series`),
|
|
288
|
+
界面在 `reason` 不是 `change` 的请求上都会画一张系统提示词卡片;锚点跟着那一轮 step 走,
|
|
289
|
+
卡片于是一张张堆起来。**上下文里 `system/message` 自始至终只有一条**,折叠没有伪造
|
|
290
|
+
任何系统消息。已实测:3 次折叠 → 3 次 `series` → 界面 3 张卡片。
|
|
250
291
|
|
|
251
292
|
5. **改需求用「丢弃 + 追加」,不要重建整棵树。** 丢掉的节点留在树里可查,
|
|
252
|
-
|
|
293
|
+
它和完成的节点会一起被那一段折叠收走(通告只报条数,不区分完成还是丢弃)。
|
|
253
294
|
|
|
254
295
|
6. **暂停不留痕迹。** 暂停后模型看到的是"没有下一条请求",看不到任何"你被暂停了"
|
|
255
296
|
之类的提示;继续之后也不会有"继续"消息进上下文。想让模型知道中间发生过什么,
|
|
@@ -261,7 +302,7 @@ dsh --profile web --dump-config # 输出里应出现 task-tree 这一
|
|
|
261
302
|
|---|---|
|
|
262
303
|
| 资料 / 文件快照 / 记忆 | 上下文里值得留下的只有结果,其余靠模型自己重读 |
|
|
263
304
|
| 文件回退 / 撤销 | 改需求用「丢弃 + 追加」 |
|
|
264
|
-
|
|
|
305
|
+
| 并行推进多个目标 | 结构上支持一个会话有多个目标(目标层就是兄弟列表),但同一时刻只沿一条活动路径走 |
|
|
265
306
|
| 把计划内容注入提示词 | 计划状态由 `tree_task_status` 现取,提示词里只放不随计划变化的说明 |
|
|
266
307
|
|
|
267
308
|
## 自检
|
package/client.js
CHANGED
|
@@ -166,11 +166,29 @@ window.__ModuleLoader__.load({
|
|
|
166
166
|
return { total, done };
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
/** 计划里的全部目标。一个会话可以有多个目标,它们是最外层的兄弟。 */
|
|
170
|
+
function goalsOf(data) {
|
|
171
|
+
return data?.plan?.goals || [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** 当前该盯着哪个目标:活动路径上的那个;没有就取第一个还没结束的,再退到最后一个。 */
|
|
175
|
+
function focusGoal(data) {
|
|
176
|
+
const goals = goalsOf(data);
|
|
177
|
+
if (goals.length === 0) return null;
|
|
178
|
+
const here = goals.find((each) => each.id === data?.active?.goalId);
|
|
179
|
+
if (here) return here;
|
|
180
|
+
return (
|
|
181
|
+
goals.find((each) => each.status !== "done" && each.status !== "dropped") ||
|
|
182
|
+
goals[goals.length - 1]
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
169
186
|
/** 当前活动节点的标题:子任务优先,其次任务,最后目标。 */
|
|
170
187
|
function currentLabel(data) {
|
|
171
188
|
const active = data.active;
|
|
172
189
|
if (!active) return "";
|
|
173
|
-
const goal = data
|
|
190
|
+
const goal = focusGoal(data);
|
|
191
|
+
if (!goal) return "";
|
|
174
192
|
const task = (goal.tasks || []).find((each) => each.id === active.taskId);
|
|
175
193
|
if (!task) return goal.title;
|
|
176
194
|
const step = (task.steps || []).find((each) => each.id === active.stepId);
|
|
@@ -198,7 +216,7 @@ window.__ModuleLoader__.load({
|
|
|
198
216
|
const checkpoint = data.checkpoint;
|
|
199
217
|
if (checkpoint !== null && checkpoint !== undefined && typeof checkpoint.id === "string") {
|
|
200
218
|
ids.add(checkpoint.id);
|
|
201
|
-
const goalId = data
|
|
219
|
+
const goalId = focusGoal(data)?.id;
|
|
202
220
|
if (checkpoint.level !== "goal" && typeof goalId === "string") ids.add(goalId);
|
|
203
221
|
}
|
|
204
222
|
return ids;
|
|
@@ -229,6 +247,7 @@ window.__ModuleLoader__.load({
|
|
|
229
247
|
"label.noResult": "(还没提交结果)",
|
|
230
248
|
"label.current": "当前",
|
|
231
249
|
"label.checkpointHere": "检查点",
|
|
250
|
+
"label.goals": "{count} 个目标",
|
|
232
251
|
"placeholder.result":
|
|
233
252
|
"写清这个节点产出了什么。它整段执行过程会被这条结果顶替,所以要能独立看懂。",
|
|
234
253
|
"hint.noChildren": "(还没有子节点 → 让模型调用 tree_task_plan)",
|
|
@@ -261,6 +280,7 @@ window.__ModuleLoader__.load({
|
|
|
261
280
|
"label.noResult": "(no result submitted yet)",
|
|
262
281
|
"label.current": "current",
|
|
263
282
|
"label.checkpointHere": "checkpoint",
|
|
283
|
+
"label.goals": "{count} goals",
|
|
264
284
|
"placeholder.result":
|
|
265
285
|
"State what this node produced. Its whole execution range is replaced by this result, so it must stand on its own.",
|
|
266
286
|
"hint.noChildren": "(no children yet → ask the model to call tree_task_plan)",
|
|
@@ -457,8 +477,8 @@ window.__ModuleLoader__.load({
|
|
|
457
477
|
/**
|
|
458
478
|
* 一个节点:一行标题 + 可展开的详情。
|
|
459
479
|
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
480
|
+
* 三层共用同一个组件——它们的字段与动作本来就一样,差别只有缩进,
|
|
481
|
+
* 以及"这一层要不要给丢弃按钮"(目标、任务、子任务都能丢)。
|
|
462
482
|
*/
|
|
463
483
|
function NodeRow({ node, depth, activeIds, openIds, checkpointId, canDrop, sessionId, reload, t }) {
|
|
464
484
|
const children = node.tasks || node.steps || [];
|
|
@@ -662,8 +682,18 @@ window.__ModuleLoader__.load({
|
|
|
662
682
|
if (state.loading || state.empty || !state.data) return null;
|
|
663
683
|
|
|
664
684
|
const data = state.data;
|
|
665
|
-
const
|
|
666
|
-
|
|
685
|
+
const goals = goalsOf(data);
|
|
686
|
+
if (goals.length === 0) return null;
|
|
687
|
+
// 一个会话可能有多个目标:条上给总数与合计进度,正文里把它们都列出来,
|
|
688
|
+
// 展开状态仍旧只摊开正在走的那条路径。
|
|
689
|
+
const stats = goals.reduce(
|
|
690
|
+
(acc, each) => {
|
|
691
|
+
const one = countNodes(each);
|
|
692
|
+
return { total: acc.total + one.total, done: acc.done + one.done };
|
|
693
|
+
},
|
|
694
|
+
{ total: 0, done: 0 },
|
|
695
|
+
);
|
|
696
|
+
const heading = goals.length === 1 ? goals[0].title : t("label.goals", { count: goals.length });
|
|
667
697
|
const current = currentLabel(data);
|
|
668
698
|
// 「被按住」有两条来源:插件闸门(paused)与人按过界面停止(stopped)。
|
|
669
699
|
// 对用户来说都是"停着呢,点继续就能接着跑",所以合成一个状态展示。
|
|
@@ -687,7 +717,7 @@ window.__ModuleLoader__.load({
|
|
|
687
717
|
{ className: "dsh-ttf-bar" },
|
|
688
718
|
h("span", { className: "dsh-ttf-glyph", "aria-hidden": true }, icon("IconChecklistOutline14", 14)),
|
|
689
719
|
h("span", { className: "dsh-ttf-label" + (held ? " dsh-ttf-labelHeld" : "") }, stateLabel),
|
|
690
|
-
h("span", { className: "dsh-ttf-title", title:
|
|
720
|
+
h("span", { className: "dsh-ttf-title", title: heading }, heading),
|
|
691
721
|
h(
|
|
692
722
|
"span",
|
|
693
723
|
{ className: "dsh-ttf-progress" },
|
|
@@ -735,18 +765,21 @@ window.__ModuleLoader__.load({
|
|
|
735
765
|
)
|
|
736
766
|
: null,
|
|
737
767
|
held ? h("div", { className: "dsh-ttf-note" }, t("note.paused")) : null,
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
768
|
+
goals.map((each) =>
|
|
769
|
+
h(NodeRow, {
|
|
770
|
+
key: each.id,
|
|
771
|
+
node: each,
|
|
772
|
+
depth: 0,
|
|
773
|
+
activeIds: currentIds(data),
|
|
774
|
+
openIds: defaultOpenIds(data),
|
|
775
|
+
checkpointId: data.checkpoint ? data.checkpoint.id : null,
|
|
776
|
+
// 目标也能丢:丢弃会连同它下面的任务与子任务一起走。
|
|
777
|
+
canDrop: true,
|
|
778
|
+
sessionId,
|
|
779
|
+
reload,
|
|
780
|
+
t,
|
|
781
|
+
}),
|
|
782
|
+
),
|
|
750
783
|
)
|
|
751
784
|
: null,
|
|
752
785
|
),
|
package/lib/commands.js
CHANGED
|
@@ -26,17 +26,19 @@ const DISABLED_NOTE =
|
|
|
26
26
|
"也不会折叠任何上下文。要打开请在 profile 的 cordis.patch.yml 里把 enabled 设为 true。";
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* 把检查点那句话拼出来。措辞与 `plan.js`
|
|
29
|
+
* 把检查点那句话拼出来。措辞与 `plan.js` 的 `renderContinue` 一致。
|
|
30
30
|
*
|
|
31
31
|
* @param checkpoint - `checkpointOf(plan)` 的结果。
|
|
32
32
|
* @returns 两行文本。
|
|
33
33
|
*/
|
|
34
34
|
function checkpointLines(checkpoint) {
|
|
35
35
|
const label = levelLabel(checkpoint.level);
|
|
36
|
+
const id = checkpoint.node.id;
|
|
37
|
+
const title = checkpoint.node.title;
|
|
36
38
|
return [
|
|
37
|
-
`检查点:${label}「${
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
`检查点:${label}「${title}」(${id}) 的子节点已全部结束。`,
|
|
40
|
+
`结果已经满足「${title}」,就用 tree_task_done(id="${id}", result="…") 提交这个${label}的结果并完成它;` +
|
|
41
|
+
`还缺哪一步,再用 tree_task_plan 补上。`,
|
|
40
42
|
];
|
|
41
43
|
}
|
|
42
44
|
|
package/lib/http.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* - **每层的结果**:节点完成时提交的文本。条目不自己去挖计划树的深层结构,
|
|
11
11
|
* 接口直接把三层各自的结果摆出来。
|
|
12
12
|
* - **检查点状态**:一个节点自己的子节点全部结束、而它自己还开着的那一刻。
|
|
13
|
-
*
|
|
13
|
+
* 折叠出来的汇总消息会直接写明这一层够不够收尾。
|
|
14
14
|
* - **暂停状态**:条目上的暂停/继续按钮写的就是这个。暂停挂的是
|
|
15
15
|
* `agent/pre-step`(见 `pause.js`),不插消息、不改计划,所以它不属于计划树,
|
|
16
16
|
* 单独回传。
|