dsh-tree-task-flow 0.3.4 → 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 CHANGED
@@ -22,46 +22,73 @@
22
22
  | 过程 | 读文件、试错、命令输出 | 节点完成时**整段折叠掉** |
23
23
  | 结果 | 模型在 `tree_task_done` 里提交的 `result` | 留下来,供后续节点引用 |
24
24
 
25
- ## 上下文怎么长
25
+ ## 折叠按什么切
26
+
27
+ 规则只有一条,**只看表层,与计划树无关**:
26
28
 
27
29
  ```
28
- [节点 0] 系统提示词(身份 / 工具 schema / 工具说明) ← 不随计划变化
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
- 检查点:任务「重构解析器」(t-3) 的子节点已全部结束。
60
- 此刻轮到你的判断:用 tree_task_plan 继续给 t-3 拆子节点,
61
- 或者用 tree_task_done 提交这个任务的结果并完成它。
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
- **检查点只是提示,不是闸门。** 它出现在 `tree_task_done` 的回执、树上和汇总消息里,
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` | 三层的折叠游标 `{ key, cursor }` |
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 把"上下文被替换过"记成一次请求序列的边界,界面在 `reason` 不是 `change`
247
- 的请求上都会渲染这张卡片,而复用的是同一个锚点,所以卡片不会变多,位置也不动。
248
- 这是 DSH 的固有语义,插件只能把替换次数压到最低——**一次折叠只替换真正变化的
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.plan.goal;
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.plan?.goal?.id;
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
- * 「能不能丢弃」(整个目标不能丢,plan.js 会拒绝)。
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 goal = data.plan.goal;
666
- const stats = countNodes(goal);
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: goal.title }, goal.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
- h(NodeRow, {
739
- node: goal,
740
- depth: 0,
741
- activeIds: currentIds(data),
742
- openIds: defaultOpenIds(data),
743
- checkpointId: data.checkpoint ? data.checkpoint.id : null,
744
- // 整个目标不能丢弃:plan.js 会拒绝,这里也不给按钮。
745
- canDrop: false,
746
- sessionId,
747
- reload,
748
- t,
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}「${checkpoint.node.title}」(${checkpoint.node.id}) 的子节点已全部结束。`,
38
- `此刻轮到你的判断:用 tree_task_plan 继续给 ${checkpoint.node.id} 拆子节点,` +
39
- `或者用 tree_task_done(id="${checkpoint.node.id}", result="…") 提交这个${label}的结果并完成它。`,
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
  * 单独回传。