dsh-tree-task-flow 0.3.2 → 0.3.4

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
@@ -135,7 +135,10 @@ dsh --profile web --dump-config # 输出里应出现 task-tree 这一
135
135
  阶段标签四种:进行中 / 已暂停 / 检查点 / 已全部完成。
136
136
  - **展开后**是待办清单那样的卡片:三级树之间有竖线与拐角连线,层级一眼看得出来;
137
137
  同一层的节点之间、节点内部的各个内容块之间,各有一条分界线。
138
- 当前节点用虚线圈标出,已完成打勾、丢弃的划掉。
138
+ **默认只摊开正在走的那条路径**——目标一路展开到当前子任务,其余节点收起,
139
+ 一打开就能看见正在跑的那一个,不用在一堆已经收尾的节点里翻;检查点所在的节点
140
+ 也一样默认摊开。想细看哪个就点它行尾的箭头,逐层摊开或收起。
141
+ 当前节点的虚线圈会匀速转,已完成打勾、丢弃的划掉。
139
142
  - 节点标题**不缩略**:有多长写多长,多了自动换行。
140
143
  - 提交的结果**默认只占一行**,点一下摊开看全文。
141
144
  - 节点上可以直接完成、丢弃,不用敲命令。
@@ -192,18 +195,17 @@ dsh --profile web --dump-config # 输出里应出现 task-tree 这一
192
195
  暂停期间人按界面上的停止(中断本轮)也不会卡住:闸门盯着本轮的取消信号,
193
196
  信号一 abort 就立刻放行。
194
197
 
195
- ## 自动续行的四道闸门
198
+ ## 自动续行的三道闸门
196
199
 
197
200
  `autoContinue` 默认关闭,开着也只是**兜底**:`tree_task_done` 的返回文本里已经带了
198
201
  "请继续执行下一个子任务",模型通常会在同一轮里接着做。这里兜的是模型仍然停下来的情况。
199
202
 
200
- 开启后仍有四道闸门,缺一不可:
203
+ 开启后仍有三道闸门,缺一不可:
201
204
 
202
205
  1. **默认关**——`autoContinue: false` 时不注册续行逻辑。
203
206
  2. **中断即停**——用户按下停止(`agent/turn-stopping` 且 `signal.aborted`),
204
207
  这个会话就被标记为不再自动续行,直到真人再发一条消息。
205
- 3. **内置 goal 活跃时不插手**——`goals` 服务里有活跃 goal 就完全让路。
206
- 4. **轮次上限**——连续续行达到 `maxAutoRounds` 就停;只有真人发的消息才重置配额。
208
+ 3. **轮次上限**——连续续行达到 `maxAutoRounds` 就停;只有真人发的消息才重置配额。
207
209
 
208
210
  队列里还有排队输入时也不抢,让用户先说话。
209
211
 
package/client.js CHANGED
@@ -187,6 +187,23 @@ window.__ModuleLoader__.load({
187
187
  );
188
188
  }
189
189
 
190
+ /**
191
+ * 面板一打开该摊开哪些节点:活动路径上的三层,加上检查点节点以及它的祖先。
192
+ *
193
+ * 其余节点一律收起——正在走的那条路径本来就窄,摊平整棵树只会让人翻半天。
194
+ * 检查点落在某个任务上时,目标也得一起摊开,否则连那一行都看不见。
195
+ */
196
+ function defaultOpenIds(data) {
197
+ const ids = currentIds(data);
198
+ const checkpoint = data.checkpoint;
199
+ if (checkpoint !== null && checkpoint !== undefined && typeof checkpoint.id === "string") {
200
+ ids.add(checkpoint.id);
201
+ const goalId = data.plan?.goal?.id;
202
+ if (checkpoint.level !== "goal" && typeof goalId === "string") ids.add(goalId);
203
+ }
204
+ return ids;
205
+ }
206
+
190
207
  // ---------------------------------------------------------------- 文案
191
208
 
192
209
  const zh = {
@@ -297,6 +314,10 @@ window.__ModuleLoader__.load({
297
314
  ".dsh-ttf-markCell{flex:none;display:inline-flex;align-items:center;margin-top:3px;color:var(--dsw-alias-label-tertiary)}",
298
315
  ".dsh-ttf-markDone{flex:none;display:inline-flex;align-items:center;margin-top:3px;color:var(--dsw-alias-state-success-primary,#1a7f37)}",
299
316
  ".dsh-ttf-markMuted{flex:none;display:inline-flex;align-items:center;margin-top:3px;color:var(--dsw-alias-label-caption,#00000066)}",
317
+ // 正在运行的标记:就是原来那个虚线圆,让它匀速转起来。1s 一圈、线性、
318
+ // 无限,与内置待办面板 in_progress 的节奏一致。
319
+ "@keyframes dsh-ttf-spin{to{transform:rotate(360deg)}}",
320
+ ".dsh-ttf-markSpin{animation:dsh-ttf-spin 1s linear infinite;transform-origin:50% 50%}",
300
321
  ".dsh-ttf-done{color:var(--dsw-alias-label-tertiary);text-decoration:line-through}",
301
322
  // 三层缩进。
302
323
  // 子节点:一条竖线 + 每个子节点一个拐角,把三层画成看得见的树。
@@ -340,7 +361,11 @@ window.__ModuleLoader__.load({
340
361
 
341
362
  // ---------------------------------------------------------------- 零件
342
363
 
343
- /** 节点状态标记:完成打勾、丢弃划掉、未完成画圈(当前节点用虚线圈,和内置待办一致)。 */
364
+ /**
365
+ * 节点状态标记:完成打勾、丢弃划掉、未完成画圈(当前节点用虚线圈,和内置待办一致)。
366
+ *
367
+ * 正在运行的那个圈还会匀速转起来——虚线一转就能看出这条路径还在推进。
368
+ */
344
369
  function StatusMark({ node, isCurrent }) {
345
370
  if (node.status === "done") {
346
371
  const check = icon("IconCheckOutline14", 14);
@@ -357,7 +382,14 @@ window.__ModuleLoader__.load({
357
382
  { className: "dsh-ttf-markCell" },
358
383
  h(
359
384
  "svg",
360
- { width: 14, height: 14, viewBox: "0 0 14 14", fill: "none", "aria-hidden": true },
385
+ {
386
+ width: 14,
387
+ height: 14,
388
+ viewBox: "0 0 14 14",
389
+ fill: "none",
390
+ "aria-hidden": true,
391
+ className: isCurrent ? "dsh-ttf-markSpin" : undefined,
392
+ },
361
393
  h("circle", circle),
362
394
  ),
363
395
  );
@@ -428,12 +460,26 @@ window.__ModuleLoader__.load({
428
460
  * 三层共用同一个组件——它们的字段与动作本来就一样,差别只有缩进和
429
461
  * 「能不能丢弃」(整个目标不能丢,plan.js 会拒绝)。
430
462
  */
431
- function NodeRow({ node, depth, activeIds, checkpointId, canDrop, sessionId, reload, t, defaultOpen }) {
432
- const [open, setOpen] = useState(defaultOpen === true);
463
+ function NodeRow({ node, depth, activeIds, openIds, checkpointId, canDrop, sessionId, reload, t }) {
464
+ const children = node.tasks || node.steps || [];
465
+ const canAct = node.status === "pending";
466
+ const isCurrent = activeIds.has(node.id);
467
+ const isCheckpoint = checkpointId === node.id;
468
+ const shouldOpen = openIds.has(node.id);
469
+ const expandable = children.length > 0 || node.detail !== undefined || node.result !== undefined;
470
+
471
+ // 默认只摊开"正在走的那条路径"(见 defaultOpenIds):目标一路展开到当前子任务,
472
+ // 其余节点收起。否则一打开面板就是一整棵摊平的树,想找正在跑的那个得翻半天。
473
+ const [open, setOpen] = useState(shouldOpen);
433
474
  const [composing, setComposing] = useState(false);
434
475
  const [busy, setBusy] = useState(false);
435
476
  const [error, setError] = useState(null);
436
477
 
478
+ // 活动节点在会话里往前走了,就把它摊开:当前任务不该埋在收起里。
479
+ useEffect(() => {
480
+ if (shouldOpen) setOpen(true);
481
+ }, [shouldOpen]);
482
+
437
483
  const act = useCallback(
438
484
  async (body) => {
439
485
  setBusy(true);
@@ -451,12 +497,6 @@ window.__ModuleLoader__.load({
451
497
  [sessionId, reload],
452
498
  );
453
499
 
454
- const children = node.tasks || node.steps || [];
455
- const canAct = node.status === "pending";
456
- const isCurrent = activeIds.has(node.id);
457
- const isCheckpoint = checkpointId === node.id;
458
- const expandable = children.length > 0 || node.detail !== undefined || node.result !== undefined;
459
-
460
500
  return h(
461
501
  "div",
462
502
  { className: "dsh-ttf-node dsh-ttf-depth" + depth },
@@ -545,14 +585,12 @@ window.__ModuleLoader__.load({
545
585
  node: child,
546
586
  depth: depth + 1,
547
587
  activeIds,
588
+ openIds,
548
589
  checkpointId,
549
590
  canDrop: true,
550
591
  sessionId,
551
592
  reload,
552
593
  t,
553
- // 条目一展开就该看见整棵树,所以每一层都摊开;
554
- // 嫌长的用上面的「收起」逐层折。
555
- defaultOpen: true,
556
594
  }),
557
595
  ),
558
596
  children.length === 0 && depth < 2
@@ -701,14 +739,13 @@ window.__ModuleLoader__.load({
701
739
  node: goal,
702
740
  depth: 0,
703
741
  activeIds: currentIds(data),
742
+ openIds: defaultOpenIds(data),
704
743
  checkpointId: data.checkpoint ? data.checkpoint.id : null,
705
744
  // 整个目标不能丢弃:plan.js 会拒绝,这里也不给按钮。
706
745
  canDrop: false,
707
746
  sessionId,
708
747
  reload,
709
748
  t,
710
- // 展开条目就是为了看树,所以第一层默认摊开。
711
- defaultOpen: true,
712
749
  }),
713
750
  )
714
751
  : null,
@@ -7,12 +7,10 @@
7
7
  * 这里兜的是"模型仍然停下来"的情况:本轮结束、agent 回到 `idle` 时,
8
8
  * 如果计划还在进行中,就推一条续行消息把下一轮唤醒。
9
9
  *
10
- * 三条安全阀缺一不可(方案 §7.1):
10
+ * 两条安全阀缺一不可(方案 §7.1):
11
11
  *
12
- * 1. **有活跃的内置 goal 时完全不插手**——内置 goal 由 `dsh-goal-round-driver`
13
- * 自己推进轮次,插件再续一次就成了两套机制同时推同一个会话。
14
- * 2. **队列里还有人在等的时候绝不抢**:`inbox.nextTurn` / `nextStep` 非空就退出。
15
- * 3. **连续续行轮次有上限**,达到就停,避免把 token 烧在一个卡住的任务上。
12
+ * 1. **队列里还有人在等的时候绝不抢**:`inbox.nextTurn` / `nextStep` 非空就退出。
13
+ * 2. **连续续行轮次有上限**,达到就停,避免把 token 烧在一个卡住的任务上。
16
14
  *
17
15
  * @module dsh-tree-task-flow/auto-continue
18
16
  */
@@ -79,15 +77,11 @@ export function registerAutoContinue(ctx, { store, cfg }) {
79
77
  // ★ 安全阀 0:这一轮是被用户**中止**的 → 绝不能把它拉起来。
80
78
  if (stopped.has(sessionId)) return;
81
79
 
82
- // 安全阀 1:内置 goal 活跃时完全不插手。
83
- const goal = ctx.get?.("goals")?.get?.(agent);
84
- if (goal !== undefined && goal !== null && goal.phase === "active") return;
85
-
86
- // 安全阀 2:已经有排队输入,别抢在用户前面。
80
+ // 安全阀 1:已经有排队输入,别抢在用户前面。
87
81
  const inbox = agent.inbox;
88
82
  if ((inbox?.nextTurn?.length ?? 0) > 0 || (inbox?.nextStep?.length ?? 0) > 0) return;
89
83
 
90
- // 安全阀 3:轮次上限。
84
+ // 安全阀 2:轮次上限。
91
85
  const used = rounds.get(sessionId) ?? 0;
92
86
  if (used >= cfg.maxAutoRounds) return;
93
87
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-tree-task-flow",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "树形任务流:把多步任务组织成目标→任务→子任务三级树,节点完成时由 AI 提交结果,插件随即把该节点的执行过程从上下文里折叠掉,只留下结果。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",