dsh-tree-task-flow 0.3.9 → 0.3.11
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 +7 -1
- package/client.js +49 -18
- package/lib/compaction.js +137 -0
- package/lib/index.js +7 -0
- package/lib/store.js +6 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -132,7 +132,7 @@ tree_task消息:接下来需要进行步骤:「步骤二」(s-4)。
|
|
|
132
132
|
|
|
133
133
|
**从 npm 安装**:
|
|
134
134
|
|
|
135
|
-
```
|
|
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
|
| 不做 | 原因 |
|
package/client.js
CHANGED
|
@@ -61,10 +61,27 @@ window.__ModuleLoader__.load({
|
|
|
61
61
|
primitives = {};
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* 内置图标名有两套写法,这里两套都认。
|
|
66
|
+
*
|
|
67
|
+
* 旧外壳把尺寸写进名字:`IconCheckOutline16`、`IconChevronDownOutline14`;
|
|
68
|
+
* 升级后的外壳把名字里的尺寸换成笔画粗细:`IconCheckOutlineRegular`、
|
|
69
|
+
* `IconCheckOutlineMedium`,尺寸改由 `size` 属性给。按「尺寸后缀 → 粗细后缀
|
|
70
|
+
* → 裸名」的顺序找,两套外壳都取得回图标,不会平白降级成文字。
|
|
71
|
+
*/
|
|
72
|
+
function findIcon(base, size) {
|
|
73
|
+
const candidates = [base + size, base + "14", base + "16", base + "Regular", base + "Medium", base];
|
|
74
|
+
for (const candidate of candidates) {
|
|
75
|
+
const component = primitives[candidate];
|
|
76
|
+
if (typeof component === "function") return component;
|
|
77
|
+
}
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
64
81
|
/** 取一个内置图标;这版外壳没有就返回 null,由调用方决定怎么退。 */
|
|
65
|
-
function icon(
|
|
66
|
-
const component =
|
|
67
|
-
if (
|
|
82
|
+
function icon(base, size) {
|
|
83
|
+
const component = findIcon(base, size);
|
|
84
|
+
if (component === null) return null;
|
|
68
85
|
return h(component, { size });
|
|
69
86
|
}
|
|
70
87
|
|
|
@@ -75,16 +92,21 @@ window.__ModuleLoader__.load({
|
|
|
75
92
|
return h(Tooltip, { label, side: "bottom", delayMs: 500 }, node);
|
|
76
93
|
}
|
|
77
94
|
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
/**
|
|
96
|
+
* 图标按钮:有图标用图标,没图标退回短文字,保证永远点得到。
|
|
97
|
+
*
|
|
98
|
+
* 退回文字时换成 `dsh-ttf-iconBtnText`:按钮按文字宽度自己撑开、文字长了
|
|
99
|
+
* 用省略号收住,所以并排的几个按钮不会挤成一团互相压。
|
|
100
|
+
*/
|
|
101
|
+
function IconButton({ iconBase, label, size, disabled, onClick }) {
|
|
102
|
+
const glyph = icon(iconBase, typeof size === "number" ? size : 16);
|
|
81
103
|
return withTooltip(
|
|
82
104
|
label,
|
|
83
105
|
h(
|
|
84
106
|
"button",
|
|
85
107
|
{
|
|
86
108
|
type: "button",
|
|
87
|
-
className: "dsh-ttf-iconBtn",
|
|
109
|
+
className: glyph === null ? "dsh-ttf-iconBtn dsh-ttf-iconBtnText" : "dsh-ttf-iconBtn",
|
|
88
110
|
disabled: disabled === true,
|
|
89
111
|
"aria-label": label,
|
|
90
112
|
onClick,
|
|
@@ -307,16 +329,21 @@ window.__ModuleLoader__.load({
|
|
|
307
329
|
'.dsh-ttf-panel:after{border:.5px solid var(--dsw-alias-border-l1);border-radius:inherit;content:"";pointer-events:none;position:absolute;inset:0}',
|
|
308
330
|
".dsh-ttf-bar{box-sizing:border-box;width:100%;height:36px;display:flex;align-items:center;gap:10px;padding:4px 5px 4px 12px}",
|
|
309
331
|
".dsh-ttf-glyph{color:var(--dsw-alias-label-tertiary);flex:none;display:inline-flex;align-items:center}",
|
|
332
|
+
".dsh-ttf-glyphFallback{font-size:13px;line-height:16px}",
|
|
310
333
|
".dsh-ttf-label{color:var(--dsw-alias-label-primary);flex:none;font-size:13px;font-weight:500;line-height:24px;white-space:nowrap}",
|
|
311
334
|
".dsh-ttf-labelHeld{color:var(--dsw-alias-state-warning-primary,#a06800)}",
|
|
312
335
|
".dsh-ttf-title{min-width:0;flex:1;color:var(--dsw-alias-label-primary-dimmed);font-size:13px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
313
336
|
".dsh-ttf-progress{flex:none;max-width:45%;min-width:0;color:var(--dsw-alias-label-tertiary);font-size:12px;line-height:20px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
314
337
|
".dsh-ttf-actions{flex:none;display:flex;align-items:center;gap:2px}",
|
|
315
|
-
".dsh-ttf-iconBtn{corner-shape:round;width:28px;height:28px;border:none;background:0 0;border-radius:999px;color:var(--dsw-alias-label-tertiary);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0;font:inherit}",
|
|
338
|
+
".dsh-ttf-iconBtn{corner-shape:round;width:28px;height:28px;border:none;background:0 0;border-radius:999px;color:var(--dsw-alias-label-tertiary);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0;font:inherit;flex:none}",
|
|
316
339
|
".dsh-ttf-iconBtn:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-secondary)}",
|
|
317
340
|
".dsh-ttf-iconBtn:disabled{opacity:.4;cursor:default}",
|
|
318
341
|
".dsh-ttf-iconBtn:disabled:hover{background:0 0;color:var(--dsw-alias-label-tertiary)}",
|
|
319
|
-
|
|
342
|
+
// 取不到内置图标时退回文字:固定 28px 宽装不下几个字,文字会溢出到相邻
|
|
343
|
+
// 按钮上,看着就是几个按钮叠在一起。这里按文字宽度撑开、留出边距,
|
|
344
|
+
// 并给一个上限,长了用省略号收住——几个按钮始终各占各的位置。
|
|
345
|
+
".dsh-ttf-iconBtnText{width:auto;min-width:28px;max-width:112px;height:24px;padding:0 8px}",
|
|
346
|
+
".dsh-ttf-iconText{min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;font-size:12px;line-height:16px;white-space:nowrap}",
|
|
320
347
|
// 展开体:限高滚动——这是输入框旁边的位置,不能让它把会话挤没了。
|
|
321
348
|
".dsh-ttf-body{max-height:264px;overflow-y:auto;padding:2px 8px 8px}",
|
|
322
349
|
".dsh-ttf-note{margin:4px 4px 6px;padding:6px 8px;border-radius:8px;font-size:12px;line-height:18px;color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-bg-module-platform)}",
|
|
@@ -388,11 +415,11 @@ window.__ModuleLoader__.load({
|
|
|
388
415
|
*/
|
|
389
416
|
function StatusMark({ node, isCurrent }) {
|
|
390
417
|
if (node.status === "done") {
|
|
391
|
-
const check = icon("
|
|
418
|
+
const check = icon("IconCheckOutline", 14);
|
|
392
419
|
return h("span", { className: "dsh-ttf-markDone" }, check === null ? "✓" : check);
|
|
393
420
|
}
|
|
394
421
|
if (node.status === "dropped") {
|
|
395
|
-
const close = icon("
|
|
422
|
+
const close = icon("IconCloseOutline", 14);
|
|
396
423
|
return h("span", { className: "dsh-ttf-markMuted" }, close === null ? "×" : close);
|
|
397
424
|
}
|
|
398
425
|
const circle = { cx: 7, cy: 7, r: 5.2, stroke: "currentColor", strokeWidth: 1.2 };
|
|
@@ -536,7 +563,7 @@ window.__ModuleLoader__.load({
|
|
|
536
563
|
{ className: "dsh-ttf-rowActions" },
|
|
537
564
|
canAct
|
|
538
565
|
? h(IconButton, {
|
|
539
|
-
|
|
566
|
+
iconBase: "IconCheckOutline",
|
|
540
567
|
label: t("action.done"),
|
|
541
568
|
disabled: busy,
|
|
542
569
|
onClick: () => {
|
|
@@ -547,7 +574,7 @@ window.__ModuleLoader__.load({
|
|
|
547
574
|
: null,
|
|
548
575
|
canAct && canDrop
|
|
549
576
|
? h(IconButton, {
|
|
550
|
-
|
|
577
|
+
iconBase: "IconTrashOutline",
|
|
551
578
|
label: t("action.drop"),
|
|
552
579
|
disabled: busy,
|
|
553
580
|
onClick: () => {
|
|
@@ -559,7 +586,7 @@ window.__ModuleLoader__.load({
|
|
|
559
586
|
: null,
|
|
560
587
|
expandable
|
|
561
588
|
? h(IconButton, {
|
|
562
|
-
|
|
589
|
+
iconBase: open ? "IconChevronDownOutline" : "IconChevronRightOutline",
|
|
563
590
|
label: open ? t("action.collapse") : t("action.expand"),
|
|
564
591
|
onClick: () => setOpen(!open),
|
|
565
592
|
})
|
|
@@ -718,6 +745,10 @@ window.__ModuleLoader__.load({
|
|
|
718
745
|
: data.active === null
|
|
719
746
|
? t("state.done")
|
|
720
747
|
: t("state.running");
|
|
748
|
+
// 条首的清单图标;外壳连这个图标都没有时退回一个同类字符,别留空白。
|
|
749
|
+
const listIcon = icon("IconChecklistOutline", 14);
|
|
750
|
+
const listGlyph =
|
|
751
|
+
listIcon === null ? h("span", { className: "dsh-ttf-glyphFallback" }, "☰") : listIcon;
|
|
721
752
|
|
|
722
753
|
return h(
|
|
723
754
|
"div",
|
|
@@ -728,7 +759,7 @@ window.__ModuleLoader__.load({
|
|
|
728
759
|
h(
|
|
729
760
|
"div",
|
|
730
761
|
{ className: "dsh-ttf-bar" },
|
|
731
|
-
h("span", { className: "dsh-ttf-glyph", "aria-hidden": true },
|
|
762
|
+
h("span", { className: "dsh-ttf-glyph", "aria-hidden": true }, listGlyph),
|
|
732
763
|
h("span", { className: "dsh-ttf-label" + (held ? " dsh-ttf-labelHeld" : "") }, stateLabel),
|
|
733
764
|
h("span", { className: "dsh-ttf-title", title: heading }, heading),
|
|
734
765
|
h(
|
|
@@ -740,13 +771,13 @@ window.__ModuleLoader__.load({
|
|
|
740
771
|
"div",
|
|
741
772
|
{ className: "dsh-ttf-actions" },
|
|
742
773
|
h(IconButton, {
|
|
743
|
-
|
|
774
|
+
iconBase: held ? "IconPlayOutline" : "IconPauseOutline",
|
|
744
775
|
label: held ? t("action.resume") : t("action.pause"),
|
|
745
776
|
disabled: busy,
|
|
746
777
|
onClick: () => act({ action: held ? "resume" : "pause" }),
|
|
747
778
|
}),
|
|
748
779
|
h(IconButton, {
|
|
749
|
-
|
|
780
|
+
iconBase: "IconTrashOutline",
|
|
750
781
|
label: t("action.reset"),
|
|
751
782
|
disabled: busy,
|
|
752
783
|
onClick: () => {
|
|
@@ -754,7 +785,7 @@ window.__ModuleLoader__.load({
|
|
|
754
785
|
},
|
|
755
786
|
}),
|
|
756
787
|
h(IconButton, {
|
|
757
|
-
|
|
788
|
+
iconBase: open ? "IconChevronDownOutline" : "IconChevronUpOutline",
|
|
758
789
|
label: open ? t("action.collapse") : t("action.expand"),
|
|
759
790
|
onClick: () => setOpen(!open),
|
|
760
791
|
}),
|
|
@@ -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/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
|
},
|