@tea-agent/loop-agent 0.35.4-beta.0 → 0.36.0

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.
Files changed (55) hide show
  1. package/AGENTS.md +4 -2
  2. package/CHANGELOG.md +55 -7
  3. package/dist/application/task-lifecycle/advance.js +1 -0
  4. package/dist/application/task-lifecycle/observe.js +15 -0
  5. package/dist/application/task-lifecycle/plan-transitions.js +15 -0
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/commands/init-upgrade.js +351 -19
  8. package/dist/commands/init.js +14 -67
  9. package/dist/commands/run-dag-progress.js +14 -0
  10. package/dist/commands/task-advance.js +33 -3
  11. package/dist/executors/dag-pi-executor.js +3 -1
  12. package/dist/shared/operator/capabilities.js +125 -11
  13. package/dist/task/source-prepare/completeness.js +17 -0
  14. package/dist/task/source-prepare/parse-intent.js +15 -1
  15. package/dist/worker/console/chat/artifact-card.js +8 -1
  16. package/dist/worker/console/chat/chat-event-store.js +61 -0
  17. package/dist/worker/console/chat/human-gate-card.js +9 -1
  18. package/dist/worker/console/chat/operation-card.js +71 -2
  19. package/dist/worker/console/chat/pi-runtime.js +69 -30
  20. package/dist/worker/console/chat/routes.js +27 -4
  21. package/dist/worker/console/chat/semantic-activity.js +465 -0
  22. package/dist/worker/console/chat/turn-process.js +31 -12
  23. package/dist/worker/console/operation-run-facts.js +190 -0
  24. package/dist/worker/console/operation-runner.js +107 -6
  25. package/dist/worker/console/operation-wait.js +314 -0
  26. package/dist/worker/console/operator-actions.js +153 -2
  27. package/dist/worker/console/static/assets/index-2OeZODxk.js +57 -0
  28. package/dist/worker/console/static/assets/index-DVJlUL8X.css +1 -0
  29. package/dist/worker/console/static/index.html +2 -2
  30. package/dist/worker/console/static-src/operator-chat/activity-journey.js +125 -0
  31. package/dist/worker/console/static-src/operator-chat/activity-rail-presentation.js +73 -0
  32. package/dist/worker/console/static-src/operator-chat/activity-references.js +20 -0
  33. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +69 -31
  34. package/dist/worker/console/static-src/operator-chat/slash-palette-layout.js +24 -0
  35. package/dist/worker/console/static-src/operator-chat/slash-palette-nav.js +141 -0
  36. package/dist/worker/console/static-src/operator-chat/spatial-overlay.js +2 -1
  37. package/dist/worker/console/static-src/operator-chat/useActivityRailTransition.js +59 -0
  38. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +16 -5
  39. package/dist/worker/console/static-src/operator-chat/useComposer.js +30 -7
  40. package/dist/worker/console/static-src/operator-chat/workspace-layout-mode.js +7 -3
  41. package/dist/worker/observe/static/operator-chrome.js +1 -1
  42. package/dist/workflows/dag/failure-routing.js +4 -9
  43. package/dist/workflows/dag/init-hybrid.js +74 -2
  44. package/dist/workflows/dag/lifecycle.js +0 -4
  45. package/dist/workflows/dag/report.js +0 -6
  46. package/docs/README.md +3 -3
  47. package/docs/architecture/evolution.md +102 -67
  48. package/docs/templates/frontend-task-constraints.md +7 -13
  49. package/docs/templates/init-managed-agents.md +5 -2
  50. package/harness.json +2 -2
  51. package/package.json +1 -1
  52. package/skills/loop-agent/SKILL.md +1 -0
  53. package/skills/loop-agent/references/command-reference.md +2 -0
  54. package/dist/worker/console/static/assets/index-DRqZiQ7J.css +0 -1
  55. package/dist/worker/console/static/assets/index-DuVLjCIT.js +0 -57
@@ -0,0 +1,465 @@
1
+ const TECHNICAL_ONLY_TOOLS = new Set([
2
+ "operatorCapabilities",
3
+ "operationList",
4
+ "operationGet",
5
+ "operationEventSummary",
6
+ "statsContext",
7
+ "sourceInstructions",
8
+ "spineCheck",
9
+ "inspect",
10
+ "observeLink",
11
+ ]);
12
+ export function semanticToolPresentation(toolName) {
13
+ if (TECHNICAL_ONLY_TOOLS.has(toolName))
14
+ return "technical-only";
15
+ if (toolName === "doctor")
16
+ return "inline-detail";
17
+ return "activity-detail";
18
+ }
19
+ const OPERATION_TITLES = {
20
+ newTask: "创建任务",
21
+ importPrd: "任务需求",
22
+ contractShow: "任务边界",
23
+ contractApply: "更新任务契约",
24
+ contractDiff: "任务契约差异",
25
+ contractDraftSave: "任务契约草稿",
26
+ contractValidate: "检查任务契约",
27
+ taskAdvance: "推进任务准备",
28
+ dagRunTask: "执行流程",
29
+ dagValidate: "校验执行流程",
30
+ prepareDagConfirmation: "执行确认",
31
+ confirmDagConfirmation: "确认执行边界",
32
+ prepareDagExecution: "执行边界",
33
+ runDag: "任务执行",
34
+ operationWait: "执行监督",
35
+ operationGet: "任务活动详情",
36
+ operationList: "会话活动",
37
+ operationEventSummary: "执行进展摘要",
38
+ dagStatus: "执行状态",
39
+ dagDoctor: "执行诊断",
40
+ dagReport: "执行结果",
41
+ dagRerunPlan: "恢复评估",
42
+ standaloneTaskRerun: "恢复执行",
43
+ workerTaskRetry: "重试任务",
44
+ promoteRun: "任务推进",
45
+ status: "任务状态",
46
+ };
47
+ const TOOL_SEMANTICS = {
48
+ newTask: {
49
+ running: "正在创建任务",
50
+ success: "任务已经创建",
51
+ error: "创建任务时遇到问题",
52
+ },
53
+ importPrd: {
54
+ running: "正在整理并导入任务需求",
55
+ success: "任务需求已经导入",
56
+ error: "导入任务需求时遇到问题",
57
+ },
58
+ contractShow: {
59
+ running: "正在读取任务目标和执行边界",
60
+ success: "任务目标与执行边界已经读取",
61
+ error: "读取任务边界时遇到问题",
62
+ },
63
+ taskAdvance: {
64
+ running: "正在推进任务契约并准备执行流程",
65
+ success: "任务已经推进到下一安全阶段",
66
+ error: "任务推进被契约或执行边界阻塞",
67
+ },
68
+ dagRunTask: {
69
+ running: "正在生成并校验执行流程",
70
+ success: "执行流程已经准备好",
71
+ error: "准备执行流程时遇到问题",
72
+ },
73
+ prepareDagExecution: {
74
+ running: "正在确认任务可以安全执行",
75
+ success: "执行边界已经确认",
76
+ error: "执行边界尚未通过确认",
77
+ },
78
+ runDag: {
79
+ running: "正在启动任务执行",
80
+ success: "任务执行已经启动",
81
+ error: "启动任务执行时遇到问题",
82
+ },
83
+ dagReport: {
84
+ running: "正在读取本次执行结果",
85
+ success: "本次执行结果已经读取",
86
+ error: "读取执行结果时遇到问题",
87
+ },
88
+ dagRerunPlan: {
89
+ running: "正在评估能否从失败位置继续",
90
+ success: "恢复条件评估已经完成",
91
+ error: "暂时无法确定安全的恢复方式",
92
+ },
93
+ standaloneTaskRerun: {
94
+ running: "正在从失败位置恢复执行",
95
+ success: "已经从失败位置继续执行",
96
+ error: "从失败位置恢复执行时遇到问题",
97
+ },
98
+ promoteRun: {
99
+ running: "正在推进任务状态",
100
+ success: "任务状态已经更新",
101
+ error: "推进任务状态时遇到问题",
102
+ },
103
+ status: {
104
+ running: "正在读取任务状态",
105
+ success: "任务状态已经更新",
106
+ error: "读取任务状态时遇到问题",
107
+ },
108
+ contractApply: {
109
+ running: "正在应用任务契约变更",
110
+ success: "任务契约已经更新",
111
+ error: "应用任务契约变更时遇到问题",
112
+ },
113
+ contractDiff: {
114
+ running: "正在比较任务契约版本",
115
+ success: "任务契约差异已经读取",
116
+ error: "比较任务契约时遇到问题",
117
+ },
118
+ contractDraftSave: {
119
+ running: "正在保存任务契约草稿",
120
+ success: "任务契约草稿已经保存",
121
+ error: "保存任务契约草稿时遇到问题",
122
+ },
123
+ contractValidate: {
124
+ running: "正在检查任务契约",
125
+ success: "任务契约检查已经完成",
126
+ error: "任务契约检查没有通过",
127
+ },
128
+ dagValidate: {
129
+ running: "正在校验执行流程",
130
+ success: "执行流程校验已经通过",
131
+ error: "执行流程校验没有通过",
132
+ },
133
+ prepareDagConfirmation: {
134
+ running: "正在准备执行确认",
135
+ success: "执行确认已经准备好",
136
+ error: "准备执行确认时遇到问题",
137
+ },
138
+ confirmDagConfirmation: {
139
+ running: "正在确认执行边界",
140
+ success: "执行边界已经确认",
141
+ error: "执行边界尚未通过确认",
142
+ },
143
+ operationGet: {
144
+ running: "正在读取任务活动详情",
145
+ success: "任务活动详情已经读取",
146
+ error: "读取任务活动详情时遇到问题",
147
+ },
148
+ operationList: {
149
+ running: "正在读取本次对话的活动",
150
+ success: "本次对话的活动已经读取",
151
+ error: "读取对话活动时遇到问题",
152
+ },
153
+ operationEventSummary: {
154
+ running: "正在汇总执行进展",
155
+ success: "执行进展已经汇总",
156
+ error: "汇总执行进展时遇到问题",
157
+ },
158
+ dagStatus: {
159
+ running: "正在读取执行状态",
160
+ success: "执行状态已经更新",
161
+ error: "读取执行状态时遇到问题",
162
+ },
163
+ dagDoctor: {
164
+ running: "正在诊断本次任务执行",
165
+ success: "任务执行诊断已经完成",
166
+ error: "诊断任务执行时遇到问题",
167
+ },
168
+ workerTaskRetry: {
169
+ running: "正在重试失败的任务",
170
+ success: "失败任务已经重新开始",
171
+ error: "重试失败任务时遇到问题",
172
+ },
173
+ doctor: {
174
+ running: "正在检查运行环境",
175
+ success: "运行环境检查已经完成",
176
+ error: "运行环境检查发现问题",
177
+ },
178
+ inspect: {
179
+ running: "正在读取详细运行信息",
180
+ success: "详细运行信息已经读取",
181
+ error: "读取详细运行信息时遇到问题",
182
+ },
183
+ observeLink: {
184
+ running: "正在准备运行看板入口",
185
+ success: "运行看板入口已经准备好",
186
+ error: "准备运行看板入口时遇到问题",
187
+ },
188
+ operatorCapabilities: {
189
+ running: "正在读取可用操作能力",
190
+ success: "可用操作能力已经读取",
191
+ error: "读取可用操作能力时遇到问题",
192
+ },
193
+ sourceInstructions: {
194
+ running: "正在读取任务来源说明",
195
+ success: "任务来源说明已经读取",
196
+ error: "读取任务来源说明时遇到问题",
197
+ },
198
+ spineCheck: {
199
+ running: "正在检查计划执行步骤",
200
+ success: "计划执行步骤检查已经完成",
201
+ error: "计划执行步骤检查发现问题",
202
+ },
203
+ statsContext: {
204
+ running: "正在统计上下文使用情况",
205
+ success: "上下文使用情况已经统计完成",
206
+ error: "统计上下文使用情况时遇到问题",
207
+ },
208
+ };
209
+ const NODE_LABELS = [
210
+ {
211
+ pattern: /(?:^|-)contract(?:-pi)?$/i,
212
+ running: "正在核对任务目标和执行边界",
213
+ finished: "任务目标与执行边界已确认",
214
+ error: "任务边界存在冲突,需要先处理",
215
+ },
216
+ {
217
+ pattern: /scout-src/i,
218
+ running: "正在梳理与任务相关的代码",
219
+ finished: "相关代码范围已梳理",
220
+ error: "梳理相关代码时遇到问题",
221
+ },
222
+ {
223
+ pattern: /scout-tests/i,
224
+ running: "正在检查现有测试和验证方式",
225
+ finished: "现有测试与验证方式已确认",
226
+ error: "检查现有测试时遇到问题",
227
+ },
228
+ {
229
+ pattern: /plan/i,
230
+ running: "正在整理具体实施步骤",
231
+ finished: "实施步骤已经准备好",
232
+ error: "实施方案还需要进一步完善",
233
+ },
234
+ {
235
+ pattern: /implement|writer/i,
236
+ running: "正在修改并保存相关文件",
237
+ finished: "文件修改已经完成",
238
+ error: "修改文件时遇到问题",
239
+ },
240
+ {
241
+ pattern: /verify|reverify/i,
242
+ running: "正在验证本次修改",
243
+ finished: "本次修改已通过验证",
244
+ error: "验证没有通过,正在检查原因",
245
+ },
246
+ {
247
+ pattern: /review/i,
248
+ running: "正在检查改动是否完整",
249
+ finished: "改动完整性检查已通过",
250
+ error: "发现需要补充或修正的内容",
251
+ },
252
+ {
253
+ pattern: /closeout/i,
254
+ running: "正在整理交付结果",
255
+ finished: "任务已经完成并形成交付记录",
256
+ error: "交付结果尚未整理完整",
257
+ },
258
+ ];
259
+ function asRecord(value) {
260
+ if (!value || typeof value !== "object")
261
+ return undefined;
262
+ return value;
263
+ }
264
+ function parseJsonRecord(value) {
265
+ if (value && typeof value === "object")
266
+ return value;
267
+ if (typeof value !== "string")
268
+ return undefined;
269
+ try {
270
+ return asRecord(JSON.parse(value));
271
+ }
272
+ catch {
273
+ return undefined;
274
+ }
275
+ }
276
+ function operationIdFromArgs(args) {
277
+ const record = parseJsonRecord(args);
278
+ const operationId = record?.operationId;
279
+ return typeof operationId === "string" && operationId ? operationId : undefined;
280
+ }
281
+ function maxWaitSeconds(args) {
282
+ const record = parseJsonRecord(args);
283
+ const raw = record?.maxWaitMs;
284
+ if (typeof raw !== "number" || !Number.isFinite(raw) || raw <= 0)
285
+ return undefined;
286
+ return Math.max(1, Math.round(raw / 1000));
287
+ }
288
+ function activityRefForTool(tool) {
289
+ const operationId = operationIdFromArgs(tool.args);
290
+ return operationId ? `operation:${operationId}` : `tool:${tool.id}`;
291
+ }
292
+ function semanticKindForStatus(status) {
293
+ if (status === "running")
294
+ return "progress";
295
+ if (status === "error")
296
+ return "error";
297
+ return "success";
298
+ }
299
+ function resultRecord(tool) {
300
+ return parseJsonRecord(tool.resultText ?? tool.result);
301
+ }
302
+ function eventMessages(record) {
303
+ if (!Array.isArray(record.events))
304
+ return [];
305
+ return record.events.flatMap((value) => {
306
+ const event = asRecord(value);
307
+ if (!event)
308
+ return [];
309
+ const message = event.messagePreview ?? event.message;
310
+ return typeof message === "string" ? [message] : [];
311
+ });
312
+ }
313
+ function nodeActivityFromMessages(messages, id, activityRef) {
314
+ const facts = [];
315
+ for (const message of messages) {
316
+ for (const match of message.matchAll(/node-(started|finished)[^\n]*?node=([^\s,]+)/gi)) {
317
+ const lifecycle = match[1]?.toLowerCase();
318
+ const node = match[2] ?? "";
319
+ const statusMatch = message.match(/status=([^\s,]+)/i)?.[1]?.toUpperCase();
320
+ let status = "finished";
321
+ if (statusMatch === "ERROR" || statusMatch === "FAILED")
322
+ status = "error";
323
+ else if (lifecycle === "started")
324
+ status = "running";
325
+ facts.push({ node, status });
326
+ }
327
+ }
328
+ const latest = facts.at(-1);
329
+ if (!latest)
330
+ return undefined;
331
+ const scoutSrcFinished = facts.some((fact) => /scout-src/i.test(fact.node) && fact.status === "finished");
332
+ const scoutTestsFinished = facts.some((fact) => /scout-tests/i.test(fact.node) && fact.status === "finished");
333
+ if (scoutSrcFinished && scoutTestsFinished) {
334
+ return {
335
+ id,
336
+ kind: "success",
337
+ title: "代码与测试的影响范围已确认",
338
+ activityRef,
339
+ };
340
+ }
341
+ const label = NODE_LABELS.find((candidate) => candidate.pattern.test(latest.node));
342
+ if (!label) {
343
+ return { id, kind: "success", title: "执行状态已经更新", activityRef };
344
+ }
345
+ if (latest.status === "running") {
346
+ return { id, kind: "progress", title: label.running, activityRef };
347
+ }
348
+ if (latest.status === "error") {
349
+ return { id, kind: "error", title: label.error, activityRef };
350
+ }
351
+ return { id, kind: "success", title: label.finished, activityRef };
352
+ }
353
+ function operationWaitActivity(tool) {
354
+ const activityRef = activityRefForTool(tool);
355
+ const id = `activity:${tool.id}`;
356
+ const seconds = maxWaitSeconds(tool.args);
357
+ if (tool.status === "running") {
358
+ return {
359
+ id,
360
+ kind: "progress",
361
+ title: seconds
362
+ ? `正在等待新的执行进展,最长等待时间 ${seconds} 秒`
363
+ : "正在等待新的执行进展",
364
+ activityRef,
365
+ };
366
+ }
367
+ if (tool.status === "error") {
368
+ return {
369
+ id,
370
+ kind: "error",
371
+ title: "等待执行进展时遇到问题",
372
+ activityRef,
373
+ };
374
+ }
375
+ let rawResult = "";
376
+ if (typeof tool.resultText === "string")
377
+ rawResult = tool.resultText;
378
+ else if (typeof tool.result === "string")
379
+ rawResult = tool.result;
380
+ const record = resultRecord(tool);
381
+ const nodeActivity = nodeActivityFromMessages([rawResult, ...(record ? eventMessages(record) : [])], id, activityRef);
382
+ if (nodeActivity)
383
+ return nodeActivity;
384
+ if (record) {
385
+ if (record.timedOut === true || record.changed === false) {
386
+ return {
387
+ id,
388
+ kind: "warning",
389
+ title: "等待期间没有新的执行进展",
390
+ activityRef,
391
+ };
392
+ }
393
+ if (record.changed === true) {
394
+ return {
395
+ id,
396
+ kind: "success",
397
+ title: "已经收到新的执行进展",
398
+ activityRef,
399
+ };
400
+ }
401
+ }
402
+ return {
403
+ id,
404
+ kind: "success",
405
+ title: "执行状态已经更新",
406
+ activityRef,
407
+ };
408
+ }
409
+ /**
410
+ * Return a deterministic, human-written semantic event for Operator tools.
411
+ * Ordinary read/bash/edit tools intentionally return undefined and retain the
412
+ * existing technical ToolCard representation.
413
+ */
414
+ export function semanticActivityForTool(tool) {
415
+ const presentation = semanticToolPresentation(tool.toolName);
416
+ if (presentation === "technical-only" || presentation === "inline-detail") {
417
+ return undefined;
418
+ }
419
+ if (tool.toolName === "operationWait")
420
+ return operationWaitActivity(tool);
421
+ const semantic = TOOL_SEMANTICS[tool.toolName];
422
+ if (!semantic)
423
+ return undefined;
424
+ let title = semantic.success;
425
+ if (tool.status === "running")
426
+ title = semantic.running;
427
+ else if (tool.status === "error")
428
+ title = semantic.error;
429
+ return {
430
+ id: `activity:${tool.id}`,
431
+ kind: semanticKindForStatus(tool.status),
432
+ title,
433
+ activityRef: activityRefForTool(tool),
434
+ };
435
+ }
436
+ export function semanticOperationTitle(action) {
437
+ return OPERATION_TITLES[action] ?? "任务活动";
438
+ }
439
+ export function semanticOperationState(state) {
440
+ switch (state) {
441
+ case "starting":
442
+ return { label: "正在启动", kind: "progress" };
443
+ case "running":
444
+ return { label: "运行中", kind: "progress" };
445
+ case "succeeded":
446
+ case "finished":
447
+ case "FINISHED":
448
+ case "pass":
449
+ return { label: "已完成", kind: "success" };
450
+ case "failed":
451
+ case "partial_failed":
452
+ case "ERROR":
453
+ case "FAILED":
454
+ case "timed-out":
455
+ return { label: "未完成", kind: "error" };
456
+ case "needs-reconcile":
457
+ return { label: "需要同步", kind: "warning" };
458
+ case "cancelled":
459
+ case "abandoned":
460
+ case "superseded":
461
+ return { label: "已停止", kind: "warning" };
462
+ default:
463
+ return { label: "状态已更新", kind: "success" };
464
+ }
465
+ }
@@ -2,6 +2,7 @@
2
2
  * Turn grouping for Operator Chat inline process UX (pi-web ProcessDetails mind).
3
3
  */
4
4
  import { getDisplayableAssistantBlocks, joinAssistantText, parseAssistantContent, splitFinalAssistantBlocks, } from "./assistant-content.js";
5
+ import { semanticActivityForTool, } from "./semantic-activity.js";
5
6
  function emptyGroup(key) {
6
7
  return {
7
8
  key,
@@ -76,6 +77,13 @@ export function hydrateSessionThread(raw) {
76
77
  }
77
78
  return { messages, toolCalls };
78
79
  }
80
+ function pushToolProcessItem(processItems, tool) {
81
+ const activity = semanticActivityForTool(tool);
82
+ if (activity)
83
+ processItems.push({ kind: "activity", activity });
84
+ else
85
+ processItems.push({ kind: "tool", tool });
86
+ }
79
87
  function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
80
88
  for (const block of blocks) {
81
89
  if (block.type === "thinking") {
@@ -89,7 +97,7 @@ function emitProcessBlocks(blocks, group, processItems, usedToolIds) {
89
97
  }
90
98
  const tool = group.tools.find((entry) => entry.id === block.toolCallId);
91
99
  if (tool) {
92
- processItems.push({ kind: "tool", tool });
100
+ pushToolProcessItem(processItems, tool);
93
101
  usedToolIds.add(tool.id);
94
102
  }
95
103
  }
@@ -98,7 +106,7 @@ function fillProcessDetails(group) {
98
106
  const usedToolIds = new Set();
99
107
  const processItems = [];
100
108
  const prior = group.assistants.slice(0, -1);
101
- const finalAssistant = group.assistants[group.assistants.length - 1];
109
+ const finalAssistant = group.assistants.at(-1);
102
110
  for (const assistant of prior) {
103
111
  emitProcessBlocks(getDisplayableAssistantBlocks(assistant.content ?? []), group, processItems, usedToolIds);
104
112
  }
@@ -107,7 +115,7 @@ function fillProcessDetails(group) {
107
115
  emitProcessBlocks(split.processBlocks, group, processItems, usedToolIds);
108
116
  for (const tool of group.tools) {
109
117
  if (!usedToolIds.has(tool.id))
110
- processItems.push({ kind: "tool", tool });
118
+ pushToolProcessItem(processItems, tool);
111
119
  }
112
120
  group.processItems = processItems;
113
121
  if (!finalAssistant)
@@ -161,7 +169,6 @@ export function buildTurnProcessGroups(input) {
161
169
  if (group) {
162
170
  group.tools.push(tool);
163
171
  assigned.add(tool.id);
164
- continue;
165
172
  }
166
173
  }
167
174
  }
@@ -183,7 +190,6 @@ export function buildTurnProcessGroups(input) {
183
190
  if (restored) {
184
191
  restored.tools.push(tool);
185
192
  assigned.add(tool.id);
186
- continue;
187
193
  }
188
194
  }
189
195
  }
@@ -200,13 +206,21 @@ export function buildTurnProcessGroups(input) {
200
206
  windowGroup.tools.push(tool);
201
207
  assigned.add(tool.id);
202
208
  }
203
- else if (groups.length > 0) {
204
- groups[groups.length - 1].tools.push(tool);
205
- assigned.add(tool.id);
209
+ else {
210
+ const lastGroup = groups.at(-1);
211
+ if (lastGroup) {
212
+ lastGroup.tools.push(tool);
213
+ assigned.add(tool.id);
214
+ }
206
215
  }
207
216
  }
208
217
  for (const group of groups) {
209
- group.tools.sort((a, b) => a.createdAt - b.createdAt);
218
+ group.tools.sort((a, b) => {
219
+ if (a.eventSeq !== undefined && b.eventSeq !== undefined) {
220
+ return a.eventSeq - b.eventSeq;
221
+ }
222
+ return a.createdAt - b.createdAt;
223
+ });
210
224
  group.live =
211
225
  input.streaming &&
212
226
  input.activeAssistantId !== null &&
@@ -219,12 +233,17 @@ export function processGroupSummary(tools, options) {
219
233
  const count = tools.length;
220
234
  const thinkingCount = options?.thinkingCount ?? 0;
221
235
  const textCount = options?.textCount ?? 0;
222
- const stepCount = thinkingCount + textCount + count;
223
- const errorCount = tools.filter((t) => t.status === "error").length;
224
- const runningCount = tools.filter((t) => t.status === "running").length;
236
+ const activityCount = options?.activityCount ?? 0;
237
+ const stepCount = thinkingCount + textCount + activityCount + count;
238
+ const errorCount = tools.filter((t) => t.status === "error").length +
239
+ (options?.activityErrorCount ?? 0);
240
+ const runningCount = tools.filter((t) => t.status === "running").length +
241
+ (options?.activityRunningCount ?? 0);
225
242
  const parts = [`处理详情 · ${stepCount} 步`];
226
243
  if (count > 0)
227
244
  parts.push(`${count} 工具`);
245
+ if (activityCount > 0)
246
+ parts.push(`${activityCount} 进展`);
228
247
  if (errorCount > 0)
229
248
  parts.push(`${errorCount} 出错`);
230
249
  else if (runningCount > 0)