dsh-tree-task-flow 0.3.8 → 0.3.10

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
@@ -117,7 +117,7 @@ tree_task消息:接下来需要进行步骤:「步骤二」(s-4)。
117
117
  | 层 | 是什么 | 怎么算分对了 |
118
118
  |---|---|---|
119
119
  | **L1 目标** | 最终要交付的东西 | 一个会话可以有多个,按创建顺序排;每个都写清要交付什么 |
120
- | **L2 任务** | **交付节点** | 一件能独立验收的交付物(一个文件、一条能跑的命令、一份结论);下面覆盖多个步骤,别拆到只剩一两个 |
120
+ | **L2 任务** | **交付节点** | 一件能独立验收的交付内容——一个文件、一条能跑的命令、一份结论,也可以是件需要完成或验证的事、一个检查点;下面覆盖多个步骤,别拆到只剩一两个 |
121
121
  | **L3 子任务** | **步骤** | 任务内部一两次工具调用能做完的事 |
122
122
 
123
123
  **折叠次数跟着 `tree_task_done` 的次数走**:每完成一个节点就折一次,界面也会因此多一张
@@ -132,7 +132,7 @@ tree_task消息:接下来需要进行步骤:「步骤二」(s-4)。
132
132
 
133
133
  **从 npm 安装**:
134
134
 
135
- ```powershell
135
+ ```bash
136
136
  dsh plugin --profile web add dsh-tree-task-flow
137
137
  ```
138
138
 
@@ -311,6 +311,12 @@ dsh --profile web --dump-config # 输出里应出现 task-tree 这一
311
311
  之类的提示;继续之后也不会有"继续"消息进上下文。想让模型知道中间发生过什么,
312
312
  得自己发消息说。
313
313
 
314
+ 7. **内置压缩之后会有一条提醒。** DSH 自己的压缩把一大段历史收成一条检查点之后,
315
+ 如果这个会话里有计划树,插件会注入一条
316
+ `tree_task消息:上下文刚被压缩过,计划树没有被压缩。用 tree_task_status 取回当前进度…`。
317
+ 计划树本身不在压缩范围内——它存在磁盘上;但模型会忘掉它,所以补这一句。
318
+ 同一个压缩点只提醒一次,之后再被压缩会再提醒一次。
319
+
314
320
  ## 不做的事
315
321
 
316
322
  | 不做 | 原因 |
@@ -0,0 +1,137 @@
1
+ /**
2
+ * 压缩后提示:内置压缩把一大段历史收成一条检查点之后,提醒模型
3
+ * 计划树没有被压缩,可以用 `tree_task_status` 取回来。
4
+ *
5
+ * 为什么需要它:内置压缩(`@deepseek-ai/dsh-compaction-basic`)在上下文用到
6
+ * 窗口 `thresholdRatio`(默认 0.8)时触发,只保留最近 `retainRatio`
7
+ * (默认 0.16)的原文,其余整段换成一条结构化的检查点消息。计划树的三层结构、
8
+ * 各节点已提交的 result、当前推进到哪一步,只能指望摘要恰好把它们写进了
9
+ * Pending Jobs / Current Work / Next Step——不保证写全。而压缩的摘要指令
10
+ * 又明确要求"不要提及这次压缩",模型不会自己想到去查。
11
+ *
12
+ * 怎么认:压缩留下的表层节点是一条 `user/message`,它的 `source` 由
13
+ * `compactCheckpointSource()` 构造,标记固定为 `plugin: "compact"`
14
+ * (见 `@deepseek-ai/dsh-compaction` 的 `isCompactCheckpointSource`)。
15
+ *
16
+ * **不要认 `compaction/summary` 事件**:那是压缩自己的记录,不带 `surfaceOp`,
17
+ * 因此根本不在表层里;真正替代被收走那段的是紧随其后那条带
18
+ * `surfaceOp: { op: "replace", … }` 的消息。
19
+ *
20
+ * 时机与顺序:本模块在 `lib/index.js` 里比 `region` **先**注册,因此它是外层
21
+ * 监听器——注入放在 `await next()` **之后**,那时暂停闸门与折叠都已经做完:
22
+ * 被按住的会话收不到提示,这条提示自己也不会被同一轮折叠收走。
23
+ *
24
+ * 检出则**两头都看**,因为本行相对压缩实现坐在链的哪一层不是本插件能决定的:
25
+ *
26
+ * - 坐在压缩外层:前置那次还没有新检查点,只有后置能看到;
27
+ * - 坐在压缩内层:前置那次就看到了,而后置时它可能已被这一轮折叠收走
28
+ * (实机复测里真出现过:三次压缩只提示了两次)。
29
+ *
30
+ * 取后置看到的优先——那是更新的一次压缩。
31
+ *
32
+ * @module dsh-tree-task-flow/compaction
33
+ */
34
+
35
+ import { summaryMessage } from "./region.js";
36
+
37
+ /**
38
+ * 压缩检查点的插件标记。
39
+ *
40
+ * 与 `@deepseek-ai/dsh-compaction` 的 `COMPACT_CHECKPOINT_MARKER` 保持一致的
41
+ * 字面量。这里按形状判断而不是 import 那个包:插件不该为了认一个标记,
42
+ * 就把一个 DSH 内部包写进自己的依赖里。
43
+ */
44
+ const COMPACTION_PLUGIN = "compact";
45
+
46
+ /**
47
+ * 提示正文。
48
+ *
49
+ * 与折叠通告同形:`tree_task消息:` 开头,走 plugin/notice 形态落进表层,
50
+ * 界面上显示成一条可展开的通知,而不是冒充用户说的话。
51
+ */
52
+ const NOTICE_TEXT =
53
+ "tree_task消息:上下文刚被压缩过,计划树没有被压缩。用 tree_task_status 取回当前进度、" +
54
+ "各节点已提交的 result,以及下一步该推进哪个节点。";
55
+
56
+ /** 界面上的一行摘要。 */
57
+ const NOTICE_LINE = "树形任务流 · 上下文被压缩,计划树仍在";
58
+
59
+ /** 提示消息的编号,保证同一毫秒内连发两条也不撞 id。 */
60
+ let noticeSeq = 0;
61
+
62
+ function noticeId() {
63
+ noticeSeq += 1;
64
+ return `tt-compaction-${Date.now().toString(36)}-${noticeSeq.toString(36)}`;
65
+ }
66
+
67
+ /** 这条表层节点是不是压缩落下的检查点。 */
68
+ function isCompactionCheckpoint(event) {
69
+ if (event?.type !== "user/message") return false;
70
+ const source = event.data?.source;
71
+ return source?.kind === "plugin" && source.plugin === COMPACTION_PLUGIN;
72
+ }
73
+
74
+ /**
75
+ * 表层上最后一个压缩检查点的 seq;这个会话还没被压缩过就返回 null。
76
+ *
77
+ * @param session - 当前会话。
78
+ * @returns 检查点节点的 seq,或 null。
79
+ */
80
+ export function latestCompactionSeq(session) {
81
+ const nodes = session?.surface?.nodes ?? [];
82
+ if (typeof session?.eventAt !== "function") return null;
83
+ for (let index = nodes.length - 1; index >= 0; index -= 1) {
84
+ if (isCompactionCheckpoint(session.eventAt(nodes[index]))) return nodes[index];
85
+ }
86
+ return null;
87
+ }
88
+
89
+ /**
90
+ * 注册压缩后提示。
91
+ *
92
+ * @param ctx - 挂载本行的 Cordis 上下文。
93
+ * @param options - `{ store }`。
94
+ */
95
+ export function registerCompactionNotice(ctx, { store }) {
96
+ ctx.on("agent/pre-step", async (payload, next) => {
97
+ const session = payload?.agent?.session;
98
+ // 前置那次先记下来:如果本行坐在压缩内层,这就是唯一能看到新检查点的时机。
99
+ const seqBefore = session === undefined ? null : latestCompactionSeq(session);
100
+
101
+ // 注入在后:暂停闸门与折叠都已经做完。
102
+ const decision = await next();
103
+ if (decision?.kind === "reject" || payload?.signal?.aborted) return decision;
104
+ if (session === undefined) return decision;
105
+
106
+ // 后置看到的优先(那是更新的一次压缩),看不到再退回前置那次。
107
+ const seq = latestCompactionSeq(session) ?? seqBefore;
108
+ if (seq === null) return decision;
109
+
110
+ const sessionId = session.id;
111
+ // 没有计划树就没有可查的状态,一条都不注入。
112
+ if (store.readPlan(sessionId) === null) return decision;
113
+
114
+ const cursors = store.readSurfaces(sessionId);
115
+ // 同一个检查点只提示一次;之后再被压缩,才会再提示一次。
116
+ if (cursors.compactionNoticeAt === seq) return decision;
117
+
118
+ try {
119
+ // surface 事件必须显式声明 surfaceOp,否则 append 会直接抛错。
120
+ session.append("user/message", summaryMessage(noticeId(), NOTICE_TEXT, NOTICE_LINE), {
121
+ surfaceOp: "append",
122
+ });
123
+ } catch (error) {
124
+ // 注入失败不改去重位置,下一轮还有机会。
125
+ ctx.logger?.warn?.(
126
+ `dsh-tree-task-flow: 压缩提示注入失败,下一轮再试:${String(error?.message ?? error)}`,
127
+ );
128
+ return decision;
129
+ }
130
+
131
+ cursors.compactionNoticeAt = seq;
132
+ cursors.sessionId = sessionId;
133
+ cursors.updatedAt = Date.now();
134
+ store.writeSurfaces(sessionId, cursors);
135
+ return decision;
136
+ });
137
+ }
package/lib/index.js CHANGED
@@ -19,6 +19,7 @@
19
19
 
20
20
  import { registerAutoContinue } from "./auto-continue.js";
21
21
  import { registerCommands } from "./commands.js";
22
+ import { registerCompactionNotice } from "./compaction.js";
22
23
  import { registerHttp } from "./http.js";
23
24
  import { registerPause } from "./pause.js";
24
25
  import { DEFAULT_ORDER, registerPromptSection } from "./prompt.js";
@@ -97,6 +98,12 @@ export function apply(ctx, config) {
97
98
  // 六个工具。tools 已在 inject 里声明,这里必定就绪。
98
99
  registerTools(ctx, { store, cfg });
99
100
 
101
+ // 内置压缩之后提醒模型去看计划树。
102
+ //
103
+ // ★ **必须在 registerRegion 之前注册**:先注册的是外层,它的后置代码才会
104
+ // 排在折叠之后——否则这条提示会被同一轮折叠顺手收走。
105
+ registerCompactionNotice(ctx, { store });
106
+
100
107
  // 节点完成时把它的执行过程折叠成一条携带结果的汇总消息。
101
108
  registerRegion(ctx, { store });
102
109
 
package/lib/prompt.js CHANGED
@@ -23,7 +23,7 @@ export const SECTION_NAME = "dsh-tree-task-flow";
23
23
  export const DEFAULT_ORDER = 1800;
24
24
 
25
25
  /** 段落正文。 */
26
- export const SECTION_TEXT = "`tree_task_*` 工具适用长线任务,当目标和资料分析完成后,如果用户要求或判断任务较为复杂,可以将任务执行计划分为目标、任务交付节点、任务执行步骤三级写入任务树,借助工具来推进项目,并自动收缩上下文;完成步骤或任务时提交后续步骤需要的 result,内容可以是:产物/结论/问题/有哪些需要注意的坑,过程上下文将会被隐藏。";
26
+ export const SECTION_TEXT = "`tree_task_*` 工具适用长线任务,当目标和资料分析完成后,如果用户要求或判断任务较为复杂,可以将任务执行计划分为目标、任务、任务执行步骤三级写入任务树,借助工具来推进项目;将目标按照可独立交付的节点分为多个任务,任务节点需要准确表述清楚需求;将任务按照“需一次性处理并交付产物”分为多个步骤;完成步骤或任务时提交后续步骤需要的 result,提交内容可以是:产物/结论/问题/有哪些需要注意的坑;执行过程上下文将会在完成时被隐藏。";
27
27
 
28
28
  /**
29
29
  * 注册段落。调用方必须包在 `ctx.effect()` 里,否则热更新后会重复注册而抛错。
package/lib/store.js CHANGED
@@ -149,8 +149,12 @@ export function createStore(root) {
149
149
  * 它**不参与折叠**:折叠区间由 `region.foldRange()` 在表层上现算(边界调用之间那
150
150
  * 一段过程)。这里记的只是"这一层走到哪儿了",给界面和排查用。
151
151
  *
152
+ * 另有一个与游标同住一处的去重位置:`compactionNoticeAt`,记着"压缩提示
153
+ * 已经提示到哪个 `compaction/summary` 节点了"(见 `lib/compaction.js`)。
154
+ * 它同样不参与折叠,只是让同一段压缩不会被反复提示。
155
+ *
152
156
  * @param sessionId - 会话 id。
153
- * @returns `{ sessionId, goal, task, step, updatedAt }`。
157
+ * @returns `{ sessionId, goal, task, step, compactionNoticeAt, updatedAt }`。
154
158
  */
155
159
  readSurfaces(sessionId) {
156
160
  return readJson(file("surfaces", `${sessionId}.json`), {
@@ -158,6 +162,7 @@ export function createStore(root) {
158
162
  goal: null,
159
163
  task: null,
160
164
  step: null,
165
+ compactionNoticeAt: null,
161
166
  updatedAt: null,
162
167
  });
163
168
  },
package/lib/tools.js CHANGED
@@ -78,9 +78,10 @@ export function registerTools(ctx, { store, cfg }) {
78
78
  ctx.tools.register({
79
79
  name: "tree_task_create",
80
80
  description:
81
- "新增一个目标:这个目标 + 它下面的若干任务。目标是要交付的东西,一个会话可以有多个目标," +
81
+ "新增一个目标:这个目标 + 它下面的若干任务。一个会话可以有多个目标," +
82
82
  "按调用顺序排在计划里——已经建过的目标和它们的历史都留着,不会被这次调用替换掉。" +
83
- "这里的任务是交付节点——一件能独立验收的交付物,不是步骤本身。" +
83
+ "这里的任务是交付节点——一件能独立验收的交付内容,可以是需要完成或验证的某件事," +
84
+ "也可以是一个检查点,不是步骤本身。" +
84
85
  "建完记得用 tree_task_plan 给这个目标下的第一个任务拆子任务。",
85
86
  parameters: {
86
87
  type: "object",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-tree-task-flow",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "树形任务流:把多步任务组织成目标→任务→子任务三级树,节点完成时由 AI 提交结果,插件随即把该节点的执行过程从上下文里折叠掉,只留下结果。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",