@zhushanwen/pi-subagent-workflow 8.11.0 → 8.13.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.
- package/package.json +6 -6
- package/src/host/__tests__/inflight-reporter.test.ts +18 -12
- package/src/host/inflight-reporter.ts +60 -28
- package/src/host/pi-host.ts +1 -1
- package/src/index.ts +32 -13
- package/src/injectors/__tests__/engine-awareness.test.ts +1 -1
- package/src/injectors/__tests__/engine-section-stability.test.ts +1 -4
- package/src/injectors/engine-awareness.ts +1 -1
- package/src/injectors/subagent-list-injector.ts +1 -1
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +1 -1
- package/src/interface/commands.ts +14 -3
- package/src/interface/format.ts +29 -5
- package/src/interface/gui-mappers.ts +6 -2
- package/src/interface/list-component.ts +9 -3
- package/src/interface/list-view.ts +1 -1
- package/src/interface/subagent-actions.ts +1 -1
- package/src/interface/subagent-tool-schema.ts +21 -9
- package/src/interface/subagent-tool.ts +5 -5
- package/src/interface/subagents.ts +1 -1
- package/src/interface/tool-workflow.ts +5 -3
- package/src/interface/views/WorkflowsView.ts +128 -48
- package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +82 -95
- package/src/interface/views/__tests__/record-progress.test.ts +235 -0
- package/src/interface/views/detail-content.ts +72 -26
- package/src/jsonl-run-store.ts +1 -1
- package/src/session-lifecycle.ts +23 -7
|
@@ -12,8 +12,9 @@
|
|
|
12
12
|
|
|
13
13
|
import { Key, matchesKey } from "@earendil-works/pi-tui";
|
|
14
14
|
|
|
15
|
-
import {
|
|
15
|
+
import { computeElapsedSeconds } from "@zhushanwen/subagent-core";
|
|
16
16
|
import type { AgentEventLogEntry } from "@zhushanwen/subagent-core";
|
|
17
|
+
import type { SubagentRecord } from "@zhushanwen/subagent-core";
|
|
17
18
|
import type { ExecutionTraceNode } from "@zhushanwen/subagent-core";
|
|
18
19
|
import type { WorkflowRun } from "@zhushanwen/subagent-core";
|
|
19
20
|
import {
|
|
@@ -41,6 +42,49 @@ import {
|
|
|
41
42
|
/** 探测宽度:足够大避免截断折行影响行数统计(对齐 subagents DETAIL_LEN_PROBE_WIDTH)。 */
|
|
42
43
|
const DETAIL_LEN_PROBE_WIDTH = 9999;
|
|
43
44
|
|
|
45
|
+
// ── [H2 W3] store record 的 live 进度投影(设计 D2:进度源从 node.live 切 store)──
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* running record 的实时进度投影——消费面七字段口径与旧 projectLiveProgress(node.live)
|
|
49
|
+
* 逐字段对齐(totalTokens / 工具计数 / elapsedSeconds / turns / eventLog /
|
|
50
|
+
* currentActivity / lastError)。
|
|
51
|
+
*/
|
|
52
|
+
export interface LiveProgressView {
|
|
53
|
+
totalTokens: number;
|
|
54
|
+
toolCallCount: number;
|
|
55
|
+
elapsedSeconds: number;
|
|
56
|
+
turns: number;
|
|
57
|
+
eventLog: AgentEventLogEntry[];
|
|
58
|
+
currentActivity?: { type: "tool" | "text" | "thinking"; label: string };
|
|
59
|
+
lastError?: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* SubagentRecord(store 查询投影)→ LiveProgressView。与旧路径
|
|
64
|
+
* projectLiveProgress(node.live) 的字段等价性(构造性论证,S1 等价表依据):
|
|
65
|
+
* - totalTokens / turns:recordToSubagent 直读 record.totalTokens / turnCount,同源;
|
|
66
|
+
* - toolCallCount:eventLog 里 tool_start 计数——getEventLog 对每个 toolCall 恰产
|
|
67
|
+
* 一条 tool_start,与 getAllToolCalls(record).length 恒等;
|
|
68
|
+
* - elapsedSeconds:computeElapsedSeconds 同一函数(startedAt/endedAt 同字段);
|
|
69
|
+
* - eventLog / currentActivity:recordToSubagent 内部即调 getEventLog /
|
|
70
|
+
* getCurrentActivity(同源 API,仅内存 running record 有 currentActivity);
|
|
71
|
+
* - lastError:eventLog 末条 type==="error" 的 label——getEventLog 仅当
|
|
72
|
+
* record.lastError 非空时追加该条且必在末尾,存在性 ⟺ 非空,label 即原文。
|
|
73
|
+
*/
|
|
74
|
+
export function projectRecordProgress(rec: SubagentRecord): LiveProgressView {
|
|
75
|
+
const eventLog = rec.eventLog;
|
|
76
|
+
const last = eventLog[eventLog.length - 1];
|
|
77
|
+
return {
|
|
78
|
+
totalTokens: rec.totalTokens,
|
|
79
|
+
toolCallCount: eventLog.filter((e) => e.type === "tool_start").length,
|
|
80
|
+
elapsedSeconds: computeElapsedSeconds(rec),
|
|
81
|
+
turns: rec.turns,
|
|
82
|
+
eventLog,
|
|
83
|
+
currentActivity: rec.currentActivity,
|
|
84
|
+
lastError: last !== undefined && last.type === "error" ? last.label : undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
44
88
|
/** status → 语义色标签(L2 detail 头用)。 */
|
|
45
89
|
export function statusLabel(status: string, theme: ThemeLike): string {
|
|
46
90
|
switch (status) {
|
|
@@ -57,7 +101,9 @@ export function statusLabel(status: string, theme: ThemeLike): string {
|
|
|
57
101
|
* 构建 L2 右侧详情的完整内容行。
|
|
58
102
|
*
|
|
59
103
|
* 纯函数:无 Pi runtime、无副作用,可单测。入参 promptExpanded 用结构化类型,
|
|
60
|
-
* 不依赖完整 ViewState(便于测试构造)。
|
|
104
|
+
* 不依赖完整 ViewState(便于测试构造)。liveView = 该节点配对的 running record 投影
|
|
105
|
+
* ([H2 W3] 从 trace.live 切 store 订阅,经 WorkflowsView.collectNodeLiveProgress
|
|
106
|
+
* 配对注入;缺省走终态 result 路径)。
|
|
61
107
|
*/
|
|
62
108
|
export function buildDetailContent(
|
|
63
109
|
node: ExecutionTraceNode,
|
|
@@ -66,6 +112,7 @@ export function buildDetailContent(
|
|
|
66
112
|
theme: ThemeLike,
|
|
67
113
|
mainWidth: number,
|
|
68
114
|
now: number,
|
|
115
|
+
liveView?: LiveProgressView,
|
|
69
116
|
): string[] {
|
|
70
117
|
const rightLines: string[] = [];
|
|
71
118
|
const elapsed = formatElapsed(
|
|
@@ -75,20 +122,18 @@ export function buildDetailContent(
|
|
|
75
122
|
rightLines.push(theme.fg("muted", "Detail"));
|
|
76
123
|
rightLines.push("─".repeat(mainWidth));
|
|
77
124
|
rightLines.push(`${statusDotStr(node.status, theme)} ${statusLabel(node.status, theme)} · ${node.model}`);
|
|
78
|
-
// Live 路径优先:运行中用
|
|
79
|
-
if (
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
const tcCount = getAllToolCalls(node.live).length;
|
|
83
|
-
rightLines.push(theme.fg("dim", `${tokK} · ${tcCount} tool calls · ${formatElapsedSeconds(live.elapsedSeconds)}`));
|
|
125
|
+
// Live 路径优先:运行中用 store record 投影的实时 usage/toolCalls/elapsed;否则用终态 result。
|
|
126
|
+
if (liveView) {
|
|
127
|
+
const tokK = liveView.totalTokens > 0 ? `${Math.round(liveView.totalTokens / BUDGET_TOKENS_DIVISOR)}k tok` : "0 tok";
|
|
128
|
+
rightLines.push(theme.fg("dim", `${tokK} · ${liveView.toolCallCount} tool calls · ${formatElapsedSeconds(liveView.elapsedSeconds)}`));
|
|
84
129
|
} else {
|
|
85
130
|
rightLines.push(theme.fg("dim", formatTokenStat(node.result?.usage, node.result?.toolCalls, elapsed)));
|
|
86
131
|
}
|
|
87
132
|
rightLines.push("");
|
|
88
133
|
renderWorkerLogSection(rightLines, run, mainWidth, theme);
|
|
89
134
|
renderPromptSection(rightLines, node, state, theme);
|
|
90
|
-
renderActivitySection(rightLines, node, mainWidth, theme);
|
|
91
|
-
renderOutcomeSection(rightLines, node, mainWidth, theme);
|
|
135
|
+
renderActivitySection(rightLines, node, liveView, mainWidth, theme);
|
|
136
|
+
renderOutcomeSection(rightLines, node, liveView, mainWidth, theme);
|
|
92
137
|
renderSessionSection(rightLines, node, mainWidth, theme);
|
|
93
138
|
return rightLines;
|
|
94
139
|
}
|
|
@@ -99,8 +144,9 @@ export function detailContentLength(
|
|
|
99
144
|
state: { promptExpanded: boolean },
|
|
100
145
|
run: WorkflowRun,
|
|
101
146
|
theme: ThemeLike,
|
|
147
|
+
live?: LiveProgressView,
|
|
102
148
|
): number {
|
|
103
|
-
return buildDetailContent(node, state, run, theme, DETAIL_LEN_PROBE_WIDTH, Date.now()).length;
|
|
149
|
+
return buildDetailContent(node, state, run, theme, DETAIL_LEN_PROBE_WIDTH, Date.now(), live).length;
|
|
104
150
|
}
|
|
105
151
|
|
|
106
152
|
// ── L2 详情滚动按键(纯函数,对齐 subagents processKey)─────────
|
|
@@ -214,20 +260,20 @@ function renderPromptSection(
|
|
|
214
260
|
function renderActivitySection(
|
|
215
261
|
rightLines: string[],
|
|
216
262
|
node: ExecutionTraceNode,
|
|
263
|
+
liveView: LiveProgressView | undefined,
|
|
217
264
|
mainWidth: number,
|
|
218
265
|
theme: ThemeLike,
|
|
219
266
|
): void {
|
|
220
|
-
// Live 路径:agent 运行中,从
|
|
267
|
+
// Live 路径:agent 运行中,从 store record 投影派生实时 eventLog + currentActivity。
|
|
221
268
|
// 与 subagents TUI 一致:当前活动行 + 最近 N 条离散事件(tool/turn_end/error)。
|
|
222
|
-
if (
|
|
223
|
-
const
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
const label = `Activity · ${totalCount} tool call${totalCount !== 1 ? "s" : ""} · ${live.turns} turn${live.turns !== 1 ? "s" : ""}`;
|
|
269
|
+
if (liveView) {
|
|
270
|
+
const eventLog = liveView.eventLog.filter((e) => e.type !== "turn_end");
|
|
271
|
+
const totalCount = liveView.toolCallCount;
|
|
272
|
+
const label = `Activity · ${totalCount} tool call${totalCount !== 1 ? "s" : ""} · ${liveView.turns} turn${liveView.turns !== 1 ? "s" : ""}`;
|
|
227
273
|
rightLines.push(theme.fg("muted", label));
|
|
228
274
|
// 当前活动行(running tool / thinking / text)
|
|
229
|
-
if (
|
|
230
|
-
rightLines.push(theme.fg("accent", ` ⎿ ${
|
|
275
|
+
if (liveView.currentActivity) {
|
|
276
|
+
rightLines.push(theme.fg("accent", ` ⎿ ${liveView.currentActivity.type}: ${liveView.currentActivity.label}`.slice(0, mainWidth - BOX_BORDER_CHARS)));
|
|
231
277
|
}
|
|
232
278
|
// 最近 N 条事件
|
|
233
279
|
const showCount = Math.min(MAX_TOOL_CALLS_DISPLAY, eventLog.length);
|
|
@@ -236,7 +282,7 @@ function renderActivitySection(
|
|
|
236
282
|
const entry = eventLog[i] as AgentEventLogEntry;
|
|
237
283
|
rightLines.push(theme.fg("dim", ` ${formatTraceEventLine(entry, theme)}`.slice(0, mainWidth - BOX_BORDER_CHARS)));
|
|
238
284
|
}
|
|
239
|
-
if (totalCount === 0 && !
|
|
285
|
+
if (totalCount === 0 && !liveView.currentActivity) {
|
|
240
286
|
rightLines.push(theme.fg("dim", " (starting...)"));
|
|
241
287
|
}
|
|
242
288
|
rightLines.push("");
|
|
@@ -267,17 +313,17 @@ function renderActivitySection(
|
|
|
267
313
|
function renderOutcomeSection(
|
|
268
314
|
rightLines: string[],
|
|
269
315
|
node: ExecutionTraceNode,
|
|
316
|
+
liveView: LiveProgressView | undefined,
|
|
270
317
|
mainWidth: number,
|
|
271
318
|
theme: ThemeLike,
|
|
272
319
|
): void {
|
|
273
320
|
rightLines.push(theme.fg("muted", "Outcome"));
|
|
274
|
-
if (node.status === "running" &&
|
|
321
|
+
if (node.status === "running" && liveView) {
|
|
275
322
|
// 运行中:显示实时指标(elapsed/tokens/turns)替代空荡的 "Still running..."
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
rightLines.push(theme.fg("warning", ` ⚠ ${live.lastError.slice(0, mainWidth - BOX_BORDER_CHARS)}`));
|
|
323
|
+
const tokK = liveView.totalTokens > 0 ? `${Math.round(liveView.totalTokens / BUDGET_TOKENS_DIVISOR)}k tok` : "0 tok";
|
|
324
|
+
rightLines.push(theme.fg("dim", ` Running · ${formatElapsedSeconds(liveView.elapsedSeconds)} · ${tokK} · ${liveView.turns} turn${liveView.turns !== 1 ? "s" : ""}`));
|
|
325
|
+
if (liveView.lastError) {
|
|
326
|
+
rightLines.push(theme.fg("warning", ` ⚠ ${liveView.lastError.slice(0, mainWidth - BOX_BORDER_CHARS)}`));
|
|
281
327
|
}
|
|
282
328
|
} else if (node.status === "running") {
|
|
283
329
|
rightLines.push(theme.fg("dim", " Still running..."));
|
package/src/jsonl-run-store.ts
CHANGED
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
* - pi 侧版本不匹配静默跳过语义保持(D-5);「缺 v 宽容」是 core FileRunStore
|
|
51
51
|
* 侧的存量预处理职责,不内聚进 codec(D4 裁决②),故本侧零改动即保持。
|
|
52
52
|
*
|
|
53
|
-
* [S3 查证结论] pi 0.84.
|
|
53
|
+
* [S3 查证结论] pi 0.84.4 实装(node_modules/@earendil-works/pi-coding-agent/dist,
|
|
54
54
|
* core/session-manager.js,PS-19)的 session 生命周期管理不含自动 GC:
|
|
55
55
|
* listSessionsFromDir 只做只读扫描(readdir + `.jsonl` 过滤 + header 解析,:548-571,
|
|
56
56
|
* 非递归——`<sessionDir>/workflow-state/` 子目录完全不在 pi 的任何扫描/清理范围内),
|
package/src/session-lifecycle.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* 1. identity env→appendEntry 重建(类型 13 字段含 1 个 @deprecated,写入 12)
|
|
9
9
|
* 2. notify ledger host 装配 + 重启恢复
|
|
10
10
|
* 3. 双 Service 装配 + initSession(createOrReuseServices 封装,单例语义 D8)
|
|
11
|
-
* 4. GC / manifest tmp / worktree 恢复
|
|
11
|
+
* 4. GC / manifest tmp 清扫([U4c/D6] promote 退役)/ 索引重建([U4c/G1] boot 全量腿)/ worktree 恢复
|
|
12
12
|
* 5. per-session run store + kill-9 恢复循环 + evictDoneRunsBeyondCap
|
|
13
13
|
* 6. SAR + engine 基线(经 SessionLifecycleResult 返回,sessionState 写入留在组合根)
|
|
14
14
|
*
|
|
@@ -408,17 +408,33 @@ async function runProcessLevelMaintenance(
|
|
|
408
408
|
});
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
// ADR-035
|
|
411
|
+
// ADR-035 启动清扫:manifest tmp 残留(崩溃打断的 writeManifest 留下)。
|
|
412
|
+
// [U4c / D6] tmp 恢复退役为静默删除——manifest 已是可丢可重建缓存(权威 =
|
|
413
|
+
// `.state`,重建走下方 rebuildIndexes 钩子),promote 半写 tmp 的恢复语义失效。
|
|
412
414
|
// 扫描属进程级维护——oncePerProcess 守卫防双跑(u-audit-fix);第二派发重放首次
|
|
413
|
-
// Promise(结果缓存语义),
|
|
415
|
+
// Promise(结果缓存语义),deleted 计数日志可能重打,无文件副作用。
|
|
414
416
|
try {
|
|
415
|
-
const
|
|
417
|
+
const swept = await oncePerProcess("subagent-workflow:sweep-manifest-tmp-files", () =>
|
|
416
418
|
service.recoverManifestTmpFiles());
|
|
417
|
-
if (
|
|
418
|
-
logger.warn(`[subagents] manifest tmp
|
|
419
|
+
if (swept.deleted > 0) {
|
|
420
|
+
logger.warn(`[subagents] manifest tmp sweep: ${swept.deleted} stale tmp file(s) removed (manifest is rebuildable cache)`);
|
|
419
421
|
}
|
|
420
422
|
} catch (err) {
|
|
421
|
-
logger.warn("[subagents] manifest tmp
|
|
423
|
+
logger.warn("[subagents] manifest tmp sweep failed", {
|
|
424
|
+
reason: toErrorMessage(err),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// [U4c / G1] 缓存降级重建(boot 全量腿):boot revive 已完成(createOrReuseServices
|
|
429
|
+
// 内 initSession 的孤儿恢复/可重连 entry 重物化先于本 helper),此处全量重建可丢
|
|
430
|
+
// 缓存——manifest 幂等补缺 + sessions-index 经首扫自愈重写。进程级维护(磁盘域
|
|
431
|
+
// 全量),oncePerProcess 守卫防双跑;/resume /fork 同进程复用实例时跳过(查询面
|
|
432
|
+
// 惰性通道兜底,且终态写点本身维持 manifest 就位)。
|
|
433
|
+
try {
|
|
434
|
+
oncePerProcess("subagent-workflow:rebuild-record-indexes", () =>
|
|
435
|
+
service.rebuildIndexes());
|
|
436
|
+
} catch (err) {
|
|
437
|
+
logger.warn("[subagents] record index rebuild failed", {
|
|
422
438
|
reason: toErrorMessage(err),
|
|
423
439
|
});
|
|
424
440
|
}
|