dsh-tree-task-flow 0.3.2 → 0.3.3
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 -5
- package/client.js +52 -15
- package/lib/auto-continue.js +5 -11
- package/package.json +1 -1
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.
|
|
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
|
-
{
|
|
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
|
|
432
|
-
const
|
|
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,
|
package/lib/auto-continue.js
CHANGED
|
@@ -7,12 +7,10 @@
|
|
|
7
7
|
* 这里兜的是"模型仍然停下来"的情况:本轮结束、agent 回到 `idle` 时,
|
|
8
8
|
* 如果计划还在进行中,就推一条续行消息把下一轮唤醒。
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* 两条安全阀缺一不可(方案 §7.1):
|
|
11
11
|
*
|
|
12
|
-
* 1.
|
|
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
|
|
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
|
-
// 安全阀
|
|
84
|
+
// 安全阀 2:轮次上限。
|
|
91
85
|
const used = rounds.get(sessionId) ?? 0;
|
|
92
86
|
if (used >= cfg.maxAutoRounds) return;
|
|
93
87
|
|