@zhushanwen/pi-subagent-workflow 8.14.7 → 9.0.1

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 (58) hide show
  1. package/README.md +6 -5
  2. package/package.json +14 -9
  3. package/relay/relay.mjs +45 -2
  4. package/skills/workflow-script-format/SKILL.md +2 -56
  5. package/src/host/__tests__/inflight-reporter.test.ts +26 -1
  6. package/src/host/__tests__/pi-host.test.ts +418 -4
  7. package/src/host/inflight-reporter.ts +13 -10
  8. package/src/host/pi-host.ts +205 -17
  9. package/src/index.ts +21 -72
  10. package/src/injectors/__tests__/engine-awareness.test.ts +236 -33
  11. package/src/injectors/__tests__/helpers/injector-test-mocks.ts +15 -7
  12. package/src/injectors/__tests__/workflow-list-injector.test.ts +447 -40
  13. package/src/injectors/resource-list-injector.ts +103 -32
  14. package/src/injectors/subagent-list-injector.ts +4 -28
  15. package/src/injectors/workflow-list-injector.ts +8 -4
  16. package/src/interface/__tests__/capture-tool.ts +56 -0
  17. package/src/interface/__tests__/commands-resume.test.ts +103 -0
  18. package/src/interface/__tests__/tool-prompt-contract.test.ts +361 -0
  19. package/src/interface/__tests__/tool-render.test.ts +105 -2
  20. package/src/interface/__tests__/tool-subagents.test.ts +24 -26
  21. package/src/interface/__tests__/tool-workflow-resume.test.ts +227 -0
  22. package/src/interface/__tests__/tool-workflow-script.test.ts +557 -0
  23. package/src/interface/__tests__/tool-workflow.test.ts +884 -0
  24. package/src/interface/bg-notify-render.ts +18 -9
  25. package/src/interface/command-actions.ts +22 -17
  26. package/src/interface/commands.ts +58 -12
  27. package/src/interface/format.ts +50 -16
  28. package/src/interface/id-preview.ts +4 -4
  29. package/src/interface/subagent-actions.ts +18 -50
  30. package/src/interface/subagent-tool.ts +26 -18
  31. package/src/interface/subagents.ts +5 -11
  32. package/src/interface/tool-render.ts +14 -30
  33. package/src/interface/tool-shared.ts +55 -12
  34. package/src/interface/tool-subagents.ts +21 -11
  35. package/src/interface/tool-workflow-script.ts +52 -72
  36. package/src/interface/tool-workflow.ts +345 -139
  37. package/src/interface/views/WorkflowsView.ts +10 -7
  38. package/src/interface/views/__tests__/{WorkflowsView-signature.test.ts → WorkflowsView.test.ts} +155 -11
  39. package/src/interface/views/detail-content.ts +3 -5
  40. package/src/jsonl-run-store.ts +656 -544
  41. package/src/model-events.ts +35 -0
  42. package/src/session-lifecycle.ts +349 -86
  43. package/src/subagents-events.ts +56 -0
  44. package/src/workflow-events.ts +285 -200
  45. package/src/workflow-notify.ts +456 -0
  46. package/src/injectors/__tests__/engine-section-stability.test.ts +0 -353
  47. package/src/injectors/__tests__/model-list-injector.test.ts +0 -136
  48. package/src/injectors/__tests__/subagent-list-injector.test.ts +0 -376
  49. package/src/interface/__tests__/detectors.test.ts +0 -161
  50. package/src/interface/__tests__/subagent-tool-prompt.test.ts +0 -183
  51. package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +0 -219
  52. package/src/interface/__tests__/tool-workflow-script-generate.test.ts +0 -251
  53. package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +0 -215
  54. package/src/interface/__tests__/workflow-state-file-exposure.test.ts +0 -38
  55. package/src/interface/__tests__/workflow-tool-prompt.test.ts +0 -129
  56. package/src/interface/helpers.ts +0 -272
  57. package/src/interface/views/__tests__/detail-content-session-file.test.ts +0 -73
  58. package/src/interface/views/__tests__/record-progress.test.ts +0 -235
@@ -48,6 +48,9 @@ import {
48
48
  statusDotStr,
49
49
  type ThemeLike,
50
50
  } from "../format.ts";
51
+ // [W2/V1 D1 分流表第 9 行] CLI 视图显示换源:投影二值 status(混合判源收拢在
52
+ // displayStatusOf 单点——判源 = core runSummary 投影)。
53
+ import { displayStatusOf } from "../tool-workflow.ts";
51
54
  import {
52
55
  b,
53
56
  dashes,
@@ -186,7 +189,7 @@ export function collectNodeLiveProgress(
186
189
  *
187
190
  * | 签名字段 | 消费点 |
188
191
  * |---|---|
189
- * | run.state.status | WorkflowsView.ts renderHeader(statusDotStr+statusLabel 同串源)/ detail-content.ts statusLabel(statusDotStr 调用处)/ renderFooter |
192
+ * | displayStatusOf(run) 投影二值([W2/V1 D1 第 6 行] 混合判源单点——v2 fold/注册表投影 ∨ v1 聚合读面)| WorkflowsView.ts renderHeader(statusDotStr+statusLabel 同串源)/ detail-content.ts statusLabel(statusDotStr 调用处)/ renderFooter |
190
193
  * | 秒桶 Math.floor(now/1000) | renderHeader formatElapsed(现算 elapsed)+ detail-content.ts(now 参与未完成节点 elapsed)|
191
194
  * | completed/total(节点 status 推导)| renderHeader |
192
195
  * | budget 量化值(tokens round-k + cost toFixed(4)=BUDGET_COST_DECIMALS,与 renderHeader budgetStr 同精度——第 3-4 位小数变化是可见变化)| renderHeader / saveTraceToFile |
@@ -230,7 +233,7 @@ export function computeRenderSignature(
230
233
  const l = liveProgress.get(n.stepIndex);
231
234
  return `${n.stepIndex}:${n.status}:${n.sessionFile ?? "-"}:${l?.totalTokens ?? -1}:${l?.toolCallCount ?? -1}:${l?.elapsedSeconds ?? -1}:${l?.turns ?? -1}:${l?.eventLog.length ?? -1}:${l?.currentActivity ? `${l.currentActivity.type}:${l.currentActivity.label}` : "-"}:${l?.lastError ?? "-"}`;
232
235
  });
233
- return [run.state.status, Math.floor(now / SECOND_MS), `${completed}/${traceArr.length}`, budgetPart, errorLogsPart, ...nodeParts].join("|");
236
+ return [displayStatusOf(run), Math.floor(now / SECOND_MS), `${completed}/${traceArr.length}`, budgetPart, errorLogsPart, ...nodeParts].join("|");
234
237
  }
235
238
 
236
239
  // ── View state ────────────────────────────────────────────────
@@ -281,7 +284,7 @@ function createInitialState(): ViewState {
281
284
  /**
282
285
  * 创建 workflow fullscreen view。
283
286
  *
284
- * @param run WorkflowRun 聚合根(读 state.status/spec/trace/meta)
287
+ * @param run WorkflowRun 聚合根(读 displayStatusOf 投影二值/spec/trace/meta)
285
288
  * @param theme ThemeLike(避免直接 import Pi runtime)
286
289
  * @param ctx ExtensionContext(调 ui.custom 渲染 + ui.notify 错误反馈)
287
290
  * @param actions lifecycle 操作(abort),由调用方注入
@@ -496,7 +499,7 @@ export function createWorkflowsView(
496
499
  function handleShortcutKeys(data: string): void {
497
500
  // ── Lifecycle shortcuts (no restart per D-9; no pause/resume — one-shot) ──
498
501
  if (data === "a") {
499
- if (run.state.status === "running") {
502
+ if (displayStatusOf(run) === "running") {
500
503
  void actions.abort(run.runId)
501
504
  .then(() => { cache.key = undefined; requestRender(); })
502
505
  .catch((err: Error) => ctx.ui.notify(`Abort failed: ${err.message}`, "error"));
@@ -703,7 +706,7 @@ function renderHeader(
703
706
  const completed = traceArr.filter((n) => n.status === "completed").length;
704
707
  const total = traceArr.length;
705
708
  const elapsed = formatElapsed(run.meta.startedAt);
706
- const headerRight = `${formatStatusBadge(run.state.status, theme)} · ${completed}/${total} agents · ${elapsed}`;
709
+ const headerRight = `${formatStatusBadge(displayStatusOf(run), theme)} · ${completed}/${total} agents · ${elapsed}`;
707
710
  const budget = run.state.budget;
708
711
  const budgetStr = `${Math.round(budget.usedTokens / BUDGET_TOKENS_DIVISOR)}k/${budget.maxTokens ? `${Math.round(budget.maxTokens / BUDGET_TOKENS_DIVISOR)}k` : "∞"} tok · $${budget.usedCost.toFixed(BUDGET_COST_DECIMALS)}`;
709
712
 
@@ -746,7 +749,7 @@ function renderFooter(
746
749
  ? "↑↓ agent · ⏎ detail"
747
750
  : "↑↓ agent · ⏎ prompt · PgUp/PgDn scroll";
748
751
  const actionParts: string[] = [];
749
- if (run.state.status === "running") {
752
+ if (displayStatusOf(run) === "running") {
750
753
  actionParts.push("a abort");
751
754
  }
752
755
  actionParts.push("s save");
@@ -1055,7 +1058,7 @@ function saveTraceToFile(run: WorkflowRun, ctx: ExtensionContext): void {
1055
1058
  function traceHeaderLines(run: WorkflowRun): string[] {
1056
1059
  const lines: string[] = [];
1057
1060
  lines.push(`# Workflow Trace: ${run.spec.scriptName} (${run.runId})`, "");
1058
- lines.push(`Status: ${run.state.status} | Started: ${run.meta.startedAt ?? "-"} | Duration: ${formatElapsed(run.meta.startedAt)}`);
1061
+ lines.push(`Status: ${displayStatusOf(run)} | Started: ${run.meta.startedAt ?? "-"} | Duration: ${formatElapsed(run.meta.startedAt)}`);
1059
1062
  const budget = run.state.budget;
1060
1063
  lines.push(`Budget: ${budget.usedTokens}/${budget.maxTokens ?? "unlimited"} tokens, $${budget.usedCost.toFixed(BUDGET_COST_DECIMALS)}`, "");
1061
1064
  return lines;
@@ -1,5 +1,8 @@
1
1
  /**
2
- * computeRenderSignature(IF11/TC7/DM6)— 渲染签名单测(now 参数化)。
2
+ * WorkflowsView 纯函数三块合一(同被测文件 views/WorkflowsView.ts 及其 companion
3
+ * detail-content.ts):渲染签名 / live 配对 / detail session 路径行,fixture 同族共享。
4
+ *
5
+ * ── computeRenderSignature(IF11/TC7/DM6)— 渲染签名单测(now 参数化) ──
3
6
  *
4
7
  * 契约:tick 条件失效的判据——签名字段集覆盖 header(renderHeader)/节点行
5
8
  * (renderLevel1 agent list)/L2 detail(buildDetailContent)当前消费的全部动态
@@ -10,8 +13,7 @@
10
13
  * (Map<stepIndex, LiveProgressView>,经 collectNodeLiveProgress 配对注入)——
11
14
  * 字段口径不变(totalTokens/toolCallCount/elapsedSeconds/turns/eventLog.length/
12
15
  * currentActivity/lastError),本文件用 makeLiveView 直接构造投影驱动「已入字段
13
- * 变 → 签名变」;投影与旧 projectLiveProgress 的逐字段等价性见
14
- * record-progress.test.ts(双路径对齐断言)。
16
+ * 变 → 签名变」。
15
17
  *
16
18
  * 确定性说明:签名非完全纯——live 投影的 elapsedSeconds 由 projectRecordProgress
17
19
  * 内 computeElapsedSeconds 现算(record.endedAt ?? Date.now())。fake timers 控制
@@ -19,13 +21,16 @@
19
21
  */
20
22
  import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
21
23
 
22
- import { computeRenderSignature } from "../WorkflowsView.ts";
23
24
  import type { LiveProgressView } from "../detail-content.ts";
25
+ import { buildDetailContent } from "../detail-content.ts";
26
+ import type { ThemeLike } from "../../format.ts";
27
+ import { collectNodeLiveProgress, computeRenderSignature } from "../WorkflowsView.ts";
24
28
  import type { AgentEventLogEntry } from "@zhushanwen/subagent-core";
25
29
  import type { ExecutionTraceNode, WorkerLogEntry } from "@zhushanwen/subagent-core";
30
+ import type { SubagentRecord } from "@zhushanwen/subagent-core";
26
31
  import type { WorkflowRun } from "@zhushanwen/subagent-core";
27
32
 
28
- // ── Fixtures(duck typing,对齐 detail-content-session-file.test.ts 先例)──
33
+ // ── Fixtures(duck typing)──
29
34
 
30
35
  const T0 = 1_700_000_000_000; // 固定 epoch ms
31
36
 
@@ -57,7 +62,7 @@ function makeNode(overrides: Partial<ExecutionTraceNode> = {}): ExecutionTraceNo
57
62
  };
58
63
  }
59
64
 
60
- interface RunShape {
65
+ type RunShape = {
61
66
  status?: string;
62
67
  budget?: { usedTokens: number; maxTokens?: number; usedCost: number };
63
68
  nodes?: ExecutionTraceNode[];
@@ -65,7 +70,17 @@ interface RunShape {
65
70
  }
66
71
 
67
72
  function makeRun(shape: RunShape = {}): WorkflowRun {
73
+ return makeRunWithId(`wf-sig-${signatureRunSeq++}`, shape);
74
+ }
75
+
76
+ /** [W2/V1] runId 唯一化 + spec 最小面(displayStatusOf → core runSummary 投影
77
+ * 读 runId/spec.scriptName——签名测试的 mock 需携带该最小字段集)。 */
78
+ let signatureRunSeq = 0;
79
+ function makeRunWithId(runId: string, shape: RunShape): WorkflowRun {
68
80
  return {
81
+ runId,
82
+ spec: { scriptName: "sig-wf" },
83
+ meta: { startedAt: "2026-09-27T00:00:00.000Z" },
69
84
  state: {
70
85
  status: shape.status ?? "running",
71
86
  budget: shape.budget ?? { usedTokens: 0, maxTokens: 200_000, usedCost: 0 },
@@ -84,6 +99,35 @@ function runWithLive(nodeOverrides: Partial<ExecutionTraceNode>, live: LiveProgr
84
99
  };
85
100
  }
86
101
 
102
+ /** plain theme: 着色方法直接返回原文,无 ANSI 色码——测试断言纯文本。
103
+ * bg/underline 为宽 ThemeLike 接口的 no-op 实现(workflow 渲染面只用 fg/bold)。 */
104
+ const plainTheme: ThemeLike = {
105
+ bg: (_color: string, text: string) => text,
106
+ fg: (_tag: string, text: string) => text,
107
+ bold: (text: string) => text,
108
+ underline: (text: string) => text,
109
+ };
110
+
111
+ /** collectNodeLiveProgress 配对用 run:只含 trace 节点面(task/status/startedAt)。 */
112
+ function makeRunShape(nodes: { stepIndex: number; task: string; status: string; startedAt: string }[]): WorkflowRun {
113
+ return {
114
+ state: { trace: { toArray: () => nodes } },
115
+ } as unknown as WorkflowRun;
116
+ }
117
+
118
+ function makeSub(over: Partial<SubagentRecord> = {}): SubagentRecord {
119
+ return {
120
+ id: "sa-match",
121
+ task: "调研 A",
122
+ status: "running",
123
+ startedAt: T0,
124
+ turns: 0,
125
+ totalTokens: 0,
126
+ eventLog: [],
127
+ ...over,
128
+ } as SubagentRecord;
129
+ }
130
+
87
131
  beforeEach(() => {
88
132
  vi.useFakeTimers();
89
133
  vi.setSystemTime(T0);
@@ -220,17 +264,35 @@ describe("computeRenderSignature — 节点级字段(store record 投影)",
220
264
  });
221
265
 
222
266
  describe("computeRenderSignature — 多节点与无 live 终态", () => {
223
- it("多节点逐一拼接(节点序参与签名)", () => {
267
+ it("多节点:live 投影按 stepIndex 配对生效(live 值出现 / 消失 / 挂到另一节点 → 签名变)", () => {
268
+ // 对比式断言:同一 run 换 live 投影驱动签名变化——词法格式串是内部表示非契约,
269
+ // 不作为主证明(结构快照见下一条)。
224
270
  const run = makeRun({
225
271
  nodes: [
226
272
  makeNode({ stepIndex: 0, status: "completed" }),
227
273
  makeNode({ stepIndex: 1, status: "running" }),
228
274
  ],
229
275
  });
230
- const live = new Map([[1, makeLiveView({ totalTokens: 500 })]]);
231
- const sig = computeRenderSignature(run, live, T0);
232
- expect(sig).toContain("0:completed:-:-1:-1:-1:-1:-1:-:-");
233
- expect(sig).toContain("1:running:-:500:0:5:0:0:-:-");
276
+ const noLive = new Map<number, LiveProgressView>();
277
+ const liveOnNode1 = new Map<number, LiveProgressView>([[1, makeLiveView({ totalTokens: 500 })]]);
278
+ const liveOnNode0 = new Map<number, LiveProgressView>([[0, makeLiveView({ totalTokens: 500 })]]);
279
+
280
+ const base = computeRenderSignature(run, noLive, T0);
281
+ // live 值出现(挂到节点 1)→ 签名变
282
+ expect(computeRenderSignature(run, liveOnNode1, T0)).not.toBe(base);
283
+ // 同值 live 挂到另一节点 → 签名也变(配对按 stepIndex 生效,非全局均摊)
284
+ expect(computeRenderSignature(run, liveOnNode0, T0)).not.toBe(computeRenderSignature(run, liveOnNode1, T0));
285
+ // live 值消失 → 签名回落基线
286
+ expect(computeRenderSignature(run, noLive, T0)).toBe(base);
287
+ });
288
+
289
+ // 结构快照(不承担主证明):签名段集 = run 级五段 + 每节点一段;
290
+ // 段内词法编码是内部表示,不锁定。
291
+ it("多节点结构快照:两节点 run 签名共 7 段(run 级 5 + 节点 2)", () => {
292
+ const run = makeRun({
293
+ nodes: [makeNode({ stepIndex: 0 }), makeNode({ stepIndex: 1 })],
294
+ });
295
+ expect(computeRenderSignature(run, new Map(), T0).split("|")).toHaveLength(7);
234
296
  });
235
297
 
236
298
  it("节点重排(trace 数组序对调,stepIndex 维度)→ 签名变", () => {
@@ -249,3 +311,85 @@ describe("computeRenderSignature — 多节点与无 live 终态", () => {
249
311
  expect(sig.split("|")).toHaveLength(5);
250
312
  });
251
313
  });
314
+
315
+ // ── collectNodeLiveProgress 配对([H2 W3] store record live 进度配对) ──
316
+ //
317
+ // 配对规则:task 标签匹配 + startedAt 最近邻 + 贪心唯一;终态 node 不配;
318
+ // 无匹配 record(重试间隙)返回空。
319
+ // SubagentRecord 投影面(eventLog/turns/totalTokens 等)的投影恒等由 core 侧
320
+ // record-store 测试锁定,此处消费投影面。
321
+
322
+ describe("collectNodeLiveProgress 配对", () => {
323
+ it("task 全等匹配:running node ↔ running record,投影挂到 stepIndex", () => {
324
+ const run = makeRunShape([{ stepIndex: 0, task: "调研 A", status: "running", startedAt: new Date(T0).toISOString() }]);
325
+ const live = collectNodeLiveProgress(run, [makeSub({ totalTokens: 42 })]);
326
+ expect(live.get(0)?.totalTokens).toBe(42);
327
+ });
328
+
329
+ it("多候选(parallel 同 prompt)取 startedAt 最近邻,贪心一一不重复消费", () => {
330
+ const run = makeRunShape([
331
+ { stepIndex: 0, task: "调研 A", status: "running", startedAt: new Date(T0).toISOString() },
332
+ { stepIndex: 1, task: "调研 A", status: "running", startedAt: new Date(T0 + 5000).toISOString() },
333
+ ]);
334
+ const near0 = makeSub({ id: "sa-near0", startedAt: T0 + 10, totalTokens: 100 });
335
+ const near1 = makeSub({ id: "sa-near1", startedAt: T0 + 5010, totalTokens: 200 });
336
+ const live = collectNodeLiveProgress(run, [near1, near0]);
337
+ expect(live.get(0)?.totalTokens).toBe(100); // node0(startedAt=T0) 最近 = near0
338
+ expect(live.get(1)?.totalTokens).toBe(200); // node1(T0+5s) 最近 = near1
339
+ });
340
+
341
+ it("终态 node 不配对;task 不匹配(不同 prompt)不配对", () => {
342
+ const run = makeRunShape([
343
+ { stepIndex: 0, task: "调研 A", status: "completed", startedAt: new Date(T0).toISOString() },
344
+ { stepIndex: 1, task: "写总结", status: "running", startedAt: new Date(T0).toISOString() },
345
+ ]);
346
+ const live = collectNodeLiveProgress(run, [makeSub({ task: "调研 A", totalTokens: 99 })]);
347
+ expect(live.size).toBe(0); // node0 终态跳过;node1 task 不匹配
348
+ });
349
+
350
+ it("重试间隙(无 running record)→ 空 map(views 走终态 fallback 渲染)", () => {
351
+ const run = makeRunShape([{ stepIndex: 0, task: "调研 A", status: "running", startedAt: new Date(T0).toISOString() }]);
352
+ const live = collectNodeLiveProgress(run, [makeSub({ status: "closed" })]);
353
+ expect(live.size).toBe(0);
354
+ });
355
+ });
356
+
357
+ // ── buildDetailContent session 路径行渲染 ──
358
+ //
359
+ // TUI 渲染测试:buildDetailContent 在 agent 终态时渲染 session: 路径行。
360
+ //
361
+ // 防的 bug:node.sessionFile 有值但 TUI detail 不显示——用户在交互面板里
362
+ // 看不到 agent 的 session jsonl 路径,无法定位文件做后续查看。
363
+
364
+ describe("buildDetailContent session 路径行渲染", () => {
365
+ it("node.sessionFile 有值 → 详情末尾渲染 session: <路径>", () => {
366
+ const sessionPath = "/abs/.pi/agent/subagents/enc/sessions/2026-07-15T_session-abc.jsonl";
367
+ const node = makeNode({
368
+ status: "completed",
369
+ sessionFile: sessionPath,
370
+ result: { content: "done" },
371
+ });
372
+ const lines = buildDetailContent(node, { promptExpanded: false }, makeRun(), plainTheme, 120, Date.now());
373
+ const sessionLine = lines.find((l) => l.includes("session:"));
374
+ expect(sessionLine).toBeDefined();
375
+ expect(sessionLine).toContain(sessionPath);
376
+ });
377
+
378
+ it("node.sessionFile undefined(窗口期)→ 不渲染 session: 行", () => {
379
+ const node = makeNode({ status: "completed", sessionFile: undefined, result: { content: "done" } });
380
+ const lines = buildDetailContent(node, { promptExpanded: false }, makeRun(), plainTheme, 120, Date.now());
381
+ const sessionLine = lines.find((l) => l.includes("session:"));
382
+ expect(sessionLine).toBeUndefined();
383
+ });
384
+
385
+ it("长路径截断到 mainWidth(防溢出)", () => {
386
+ const longPath = "/very/long/path/" + "x".repeat(200) + "/session.jsonl";
387
+ const node = makeNode({ status: "completed", sessionFile: longPath, result: { content: "done" } });
388
+ const narrowWidth = 60;
389
+ const lines = buildDetailContent(node, { promptExpanded: false }, makeRun(), plainTheme, narrowWidth, Date.now());
390
+ const sessionLine = lines.find((l) => l.includes("session:"));
391
+ expect(sessionLine).toBeDefined();
392
+ // 截断后行宽不超过 mainWidth + 边框余量
393
+ expect(sessionLine!.length).toBeLessThanOrEqual(narrowWidth);
394
+ });
395
+ });
@@ -45,9 +45,8 @@ const DETAIL_LEN_PROBE_WIDTH = 9999;
45
45
  // ── [H2 W3] store record 的 live 进度投影(设计 D2:进度源从 node.live 切 store)──
46
46
 
47
47
  /**
48
- * running record 的实时进度投影——消费面七字段口径与旧 projectLiveProgress(node.live)
49
- * 逐字段对齐(totalTokens / 工具计数 / elapsedSeconds / turns / eventLog /
50
- * currentActivity / lastError)。
48
+ * running record 的实时进度投影(totalTokens / 工具计数 / elapsedSeconds / turns /
49
+ * eventLog / currentActivity / lastError)。
51
50
  */
52
51
  export interface LiveProgressView {
53
52
  totalTokens: number;
@@ -60,8 +59,7 @@ export interface LiveProgressView {
60
59
  }
61
60
 
62
61
  /**
63
- * SubagentRecord(store 查询投影)→ LiveProgressView。与旧路径
64
- * projectLiveProgress(node.live) 的字段等价性(构造性论证,S1 等价表依据):
62
+ * SubagentRecord(store 查询投影)→ LiveProgressView。字段口径(S1 等价表):
65
63
  * - totalTokens / turns:recordToSubagent 直读 record.totalTokens / turnCount,同源;
66
64
  * - toolCallCount:eventLog 里 tool_start 计数——getEventLog 对每个 toolCall 恰产
67
65
  * 一条 tool_start,与 getAllToolCalls(record).length 恒等;