@zhushanwen/pi-subagent-workflow 9.4.0 → 9.4.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.
- package/package.json +10 -10
- package/src/host/pi-host.ts +9 -6
- package/src/interface/command/commands.ts +126 -66
- package/src/jsonl-run-store.ts +126 -48
- package/src/session-lifecycle.ts +11 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zhushanwen/pi-subagent-workflow",
|
|
3
|
-
"version": "9.4.
|
|
3
|
+
"version": "9.4.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"description": "Unified subagent execution and multi-agent workflow orchestration for Pi — spawned-process agent runtime with sync/background modes, stateful workflow management with persistence, state machine, and execution tracing.",
|
|
@@ -58,22 +58,22 @@
|
|
|
58
58
|
]
|
|
59
59
|
},
|
|
60
60
|
"dependencies": {
|
|
61
|
-
"@zhushanwen/extension-protocol": "0.
|
|
61
|
+
"@zhushanwen/extension-protocol": "0.20.0",
|
|
62
62
|
"@zhushanwen/session-delivery": "1.1.0",
|
|
63
|
-
"@zhushanwen/pi-ext-guards": "0.4.
|
|
63
|
+
"@zhushanwen/pi-ext-guards": "0.4.4",
|
|
64
64
|
"@zhushanwen/pi-extension-logger": "0.6.2",
|
|
65
|
-
"@zhushanwen/pi-notify-ledger-host": "0.3.
|
|
66
|
-
"@zhushanwen/
|
|
67
|
-
"@zhushanwen/
|
|
68
|
-
"@zhushanwen/subagent-
|
|
65
|
+
"@zhushanwen/pi-notify-ledger-host": "0.3.2",
|
|
66
|
+
"@zhushanwen/subagent-core": "1.5.0",
|
|
67
|
+
"@zhushanwen/pi-subagent-cli": "0.7.1",
|
|
68
|
+
"@zhushanwen/zcode-subagent-cli": "0.7.0"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@earendil-works/pi-ai": "^1.0.0",
|
|
72
72
|
"@earendil-works/pi-coding-agent": "^1.0.0",
|
|
73
73
|
"@earendil-works/pi-tui": "^1.0.0",
|
|
74
74
|
"typebox": "1.3.27",
|
|
75
|
-
"@zhushanwen/pi-
|
|
76
|
-
"@zhushanwen/pi-
|
|
75
|
+
"@zhushanwen/pi-pending-notifications": "0.8.4",
|
|
76
|
+
"@zhushanwen/pi-structured-output": "6.1.1"
|
|
77
77
|
},
|
|
78
78
|
"peerDependenciesMeta": {
|
|
79
79
|
"@earendil-works/pi-coding-agent": {
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"@types/node": "^24.0.0",
|
|
88
88
|
"@vitest/coverage-v8": "^4.1.9",
|
|
89
89
|
"vitest": "^4.1.8",
|
|
90
|
-
"@zhushanwen/subagent-engine-sdk": "0.10.
|
|
90
|
+
"@zhushanwen/subagent-engine-sdk": "0.10.1"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
93
|
"typecheck": "npx tsc --noEmit",
|
package/src/host/pi-host.ts
CHANGED
|
@@ -107,12 +107,15 @@ export function corePackageNpmRoot(moduleUrl: string = import.meta.url): string
|
|
|
107
107
|
* `<Resources>/extensions/@zhushanwen/pi-subagent-workflow/index.js`),其**父目录**
|
|
108
108
|
* 正是 `@zhushanwen` scope 目录——npm 槽语义("一级子项 = 包目录")恰好成立,
|
|
109
109
|
* 于是可直接当 npm 槽根用:约定目录扫描即命中
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
110
|
+
* `pi-subagent-workflow/{workflows,agents}`(两目录由 `scripts/bundle-extensions.mjs`
|
|
111
|
+
* 随包拷贝;`pi.agents`/`pi.workflows` manifest 约定及其"声明路径不存在 → 整包失败
|
|
112
|
+
* 占位"语义是 subagent-core resource-discovery 的 taiji 自建规则,权威源 =
|
|
113
|
+
* resource-discovery.ts processPackage——pi 上游 manifest 只有
|
|
114
|
+
* extensions/skills/prompts/themes 四字段(锚 = pi-coding-agent
|
|
115
|
+
* dist/core/pi-manifest.js:3 RESOURCE_FIELDS,pi@1.0.0 已核;同源登记见
|
|
116
|
+
* docs/extensions/extension-conventions.md「readPiManifest」条),无 agents/workflows
|
|
117
|
+
* 概念。已核仓内无任何包声明该字段,故扫描面内所有包都走约定目录分支,不进
|
|
118
|
+
* manifest 失败分支)。
|
|
116
119
|
*
|
|
117
120
|
* 与 dev/npm 形态的既有语义一致:npm 槽根扫的就是 scope 下全部包目录(因此本包自带
|
|
118
121
|
* 的 workflows/agents 在 dev/npm 形态同样入发现面),非新增语义。
|
|
@@ -51,6 +51,13 @@ const STATUS_ORDER: Record<string, number> = {
|
|
|
51
51
|
/** 未知 status 的默认排序权重(排在已知 status 之后)。 */
|
|
52
52
|
const UNKNOWN_STATUS_WEIGHT = 9;
|
|
53
53
|
|
|
54
|
+
/** RPC 非 lifecycle 通知 action 词表(abort/resume 之外的三个纯 notify 分支——
|
|
55
|
+
* rpcSideNotify 的穷举域;abort/resume 在 handleRpcMode 内 if 早退)。 */
|
|
56
|
+
type WorkflowRpcSideAction = Extract<
|
|
57
|
+
WorkflowRpcAction,
|
|
58
|
+
{ action: "lifecycle-removed" | "lifecycle-missing-id" | "noop" }
|
|
59
|
+
>;
|
|
60
|
+
|
|
54
61
|
// ── /workflows command ───────────────────────────────────────
|
|
55
62
|
|
|
56
63
|
/**
|
|
@@ -129,41 +136,7 @@ export function registerWorkflowsCommand(
|
|
|
129
136
|
return;
|
|
130
137
|
}
|
|
131
138
|
|
|
132
|
-
|
|
133
|
-
// (abort 动词在 TUI 侧无既有通道,不在此顺手扩——resume 是本批接入面)
|
|
134
|
-
const trimmedArgs = args.trim();
|
|
135
|
-
const [verb, ...rest] = trimmedArgs.split(/\s+/).filter(Boolean);
|
|
136
|
-
if (verb === "resume") {
|
|
137
|
-
const runId = rest[0];
|
|
138
|
-
if (!runId) {
|
|
139
|
-
ctx.ui.notify("Usage: /workflows resume <runId> [model]", "warning");
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
// 可选第二参数 = 显式模型 canonical ref('provider/modelId[:thinkingLevel]')——
|
|
143
|
-
// 落统一覆盖记账(F1-26 后续项:与 setModel 补切同通道,resume 合一步表达)。
|
|
144
|
-
const model = rest[1];
|
|
145
|
-
try {
|
|
146
|
-
if (model !== undefined) assertResumeModelInCatalog(model);
|
|
147
|
-
await resumeRun(runId, deps, ...(model !== undefined ? [{ model }] : []));
|
|
148
|
-
ctx.ui.notify(
|
|
149
|
-
`Workflow ${runId}: resuming${model !== undefined ? ` with model ${model}` : ""} — completed calls replay at zero token cost, unfinished calls re-dispatched`,
|
|
150
|
-
"info",
|
|
151
|
-
);
|
|
152
|
-
} catch (err) {
|
|
153
|
-
ctx.ui.notify(`Failed to resume workflow ${runId}: ${toErrorMessage(err)}`, "warning");
|
|
154
|
-
}
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// 直接按 runId / 前缀匹配打开
|
|
159
|
-
const directRunId = trimmedArgs;
|
|
160
|
-
if (directRunId) {
|
|
161
|
-
await openByRunId(directRunId, getRuns, ctx, deps);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 无参——列表选择
|
|
166
|
-
await openFromList(getRuns, ctx, deps);
|
|
139
|
+
await handleTuiCommand(args, ctx, getRuns, deps);
|
|
167
140
|
},
|
|
168
141
|
});
|
|
169
142
|
}
|
|
@@ -182,38 +155,36 @@ export function registerWorkflowsCommand(
|
|
|
182
155
|
* (F3 定稿——提示语义优先于 missing-id:run 一次性生命周期后不可挂起)
|
|
183
156
|
* - lifecycle-missing-id → Usage 提示
|
|
184
157
|
* - noop → 无 action 或未知 action:GUI 端已屏蔽此 command 入口,此处兜底
|
|
158
|
+
*
|
|
159
|
+
* 分派形态:两个 lifecycle 动作 if 早退(控制流收窄,零断言),其余三个纯
|
|
160
|
+
* notify action 收敛进 rpcSideNotify 的穷举 switch。
|
|
185
161
|
*/
|
|
186
162
|
async function handleRpcMode(
|
|
187
163
|
parsed: WorkflowRpcAction,
|
|
188
164
|
ctx: ExtensionCommandContext,
|
|
189
165
|
deps: LauncherDeps,
|
|
190
166
|
): Promise<void> {
|
|
167
|
+
if (parsed.action === "abort") {
|
|
168
|
+
await abortWithNotify(parsed.runId, deps, ctx);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (parsed.action === "resume") {
|
|
172
|
+
await resumeWithNotify(parsed.runId, parsed.model, deps, ctx);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
rpcSideNotify(parsed, ctx);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* 非 lifecycle RPC action(lifecycle-removed / lifecycle-missing-id / noop)的
|
|
180
|
+
* 穷举 notify 分派。default 保留 exhaustiveness 断言:未来新增 action verb 忘加
|
|
181
|
+
* case 时 tsc 报错。
|
|
182
|
+
*/
|
|
183
|
+
function rpcSideNotify(
|
|
184
|
+
parsed: WorkflowRpcSideAction,
|
|
185
|
+
ctx: ExtensionCommandContext,
|
|
186
|
+
): void {
|
|
191
187
|
switch (parsed.action) {
|
|
192
|
-
case "abort": {
|
|
193
|
-
try {
|
|
194
|
-
await abortRun(parsed.runId, deps);
|
|
195
|
-
// abort 单态后 pastTense 固定(原三态拼接随 pause/resume 删除)
|
|
196
|
-
ctx.ui.notify(`Workflow ${parsed.runId}: aborted`, "info");
|
|
197
|
-
} catch (err) {
|
|
198
|
-
const msg = toErrorMessage(err);
|
|
199
|
-
ctx.ui.notify(`Failed to abort workflow ${parsed.runId}: ${msg}`, "warning");
|
|
200
|
-
}
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
case "resume": {
|
|
204
|
-
try {
|
|
205
|
-
if (parsed.model !== undefined) assertResumeModelInCatalog(parsed.model);
|
|
206
|
-
await resumeRun(parsed.runId, deps, ...(parsed.model !== undefined ? [{ model: parsed.model }] : []));
|
|
207
|
-
ctx.ui.notify(
|
|
208
|
-
`Workflow ${parsed.runId}: resuming${parsed.model !== undefined ? ` with model ${parsed.model}` : ""} — completed calls replay at zero token cost, unfinished calls re-dispatched`,
|
|
209
|
-
"info",
|
|
210
|
-
);
|
|
211
|
-
} catch (err) {
|
|
212
|
-
const msg = toErrorMessage(err);
|
|
213
|
-
ctx.ui.notify(`Failed to resume workflow ${parsed.runId}: ${msg}`, "warning");
|
|
214
|
-
}
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
188
|
case "lifecycle-removed":
|
|
218
189
|
ctx.ui.notify(
|
|
219
190
|
`Workflow ${parsed.verb} has been removed — runs are one-shot. To stop a run early: /workflows abort <runId>`,
|
|
@@ -223,12 +194,7 @@ async function handleRpcMode(
|
|
|
223
194
|
case "lifecycle-missing-id":
|
|
224
195
|
// Usage 按 verb 区分(与 TUI verb 分支同文案——两模式统一):resume 的可选
|
|
225
196
|
// [model] 参数要出现在提示里,GUI 侧缺 runId 时得到的指引不指向旧用法。
|
|
226
|
-
ctx.ui.notify(
|
|
227
|
-
parsed.verb === "resume"
|
|
228
|
-
? "Usage: /workflows resume <runId> [model]"
|
|
229
|
-
: `Usage: /workflows ${parsed.verb} <runId>`,
|
|
230
|
-
"warning",
|
|
231
|
-
);
|
|
197
|
+
ctx.ui.notify(missingIdUsageText(parsed.verb), "warning");
|
|
232
198
|
return;
|
|
233
199
|
case "noop":
|
|
234
200
|
ctx.ui.notify("View workflows in the composer task tray", "info");
|
|
@@ -241,6 +207,100 @@ async function handleRpcMode(
|
|
|
241
207
|
}
|
|
242
208
|
}
|
|
243
209
|
|
|
210
|
+
/** lifecycle-missing-id 的 Usage 文案:resume 带可选 [model] 参数,其余 verb 仅 runId。 */
|
|
211
|
+
function missingIdUsageText(verb: "abort" | "resume"): string {
|
|
212
|
+
return verb === "resume" ? "Usage: /workflows resume <runId> [model]" : `Usage: /workflows ${verb} <runId>`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** abort 执行 + 结果通知(成功/失败均 notify,不向上抛——RPC 通道唯一 abort 入口)。 */
|
|
216
|
+
async function abortWithNotify(
|
|
217
|
+
runId: string,
|
|
218
|
+
deps: LauncherDeps,
|
|
219
|
+
ctx: ExtensionCommandContext,
|
|
220
|
+
): Promise<void> {
|
|
221
|
+
try {
|
|
222
|
+
await abortRun(runId, deps);
|
|
223
|
+
// abort 单态后 pastTense 固定(原三态拼接随 pause/resume 删除)
|
|
224
|
+
ctx.ui.notify(`Workflow ${runId}: aborted`, "info");
|
|
225
|
+
} catch (err) {
|
|
226
|
+
ctx.ui.notify(`Failed to abort workflow ${runId}: ${toErrorMessage(err)}`, "warning");
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* resume 执行 + 结果通知(TUI verb / RPC verb 两通道共享——同语义同文案,防
|
|
232
|
+
* 双处文案漂移)。显式 model 先做目录预检(assertResumeModelInCatalog),成功/
|
|
233
|
+
* 失败均 notify(不向上抛)。
|
|
234
|
+
*/
|
|
235
|
+
async function resumeWithNotify(
|
|
236
|
+
runId: string,
|
|
237
|
+
model: string | undefined,
|
|
238
|
+
deps: LauncherDeps,
|
|
239
|
+
ctx: ExtensionCommandContext,
|
|
240
|
+
): Promise<void> {
|
|
241
|
+
try {
|
|
242
|
+
if (model !== undefined) assertResumeModelInCatalog(model);
|
|
243
|
+
await resumeRun(runId, deps, ...resumeModelArgs(model));
|
|
244
|
+
ctx.ui.notify(resumeStartedText(runId, model), "info");
|
|
245
|
+
} catch (err) {
|
|
246
|
+
ctx.ui.notify(`Failed to resume workflow ${runId}: ${toErrorMessage(err)}`, "warning");
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** 可选 model → resumeRun 变参(无覆盖 = 空变参,不造覆盖记账)。 */
|
|
251
|
+
function resumeModelArgs(model: string | undefined): [] | [{ model: string }] {
|
|
252
|
+
return model !== undefined ? [{ model }] : [];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** resume 启动通知文案(带显式 model 时回显模型,用户可确认覆盖生效)。 */
|
|
256
|
+
function resumeStartedText(runId: string, model: string | undefined): string {
|
|
257
|
+
return `Workflow ${runId}: resuming${model !== undefined ? ` with model ${model}` : ""} — completed calls replay at zero token cost, unfinished calls re-dispatched`;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* TUI 模式命令分派:resume verb → 断点续跑后 notify(不打开面板);带参 → 按
|
|
262
|
+
* runId/前缀匹配打开;无参 → 列表选择。(abort 动词在 TUI 侧无既有通道,不在此
|
|
263
|
+
* 顺手扩——resume 是本批接入面)
|
|
264
|
+
*/
|
|
265
|
+
async function handleTuiCommand(
|
|
266
|
+
args: string,
|
|
267
|
+
ctx: ExtensionCommandContext,
|
|
268
|
+
getRuns: () => Map<string, WorkflowRun>,
|
|
269
|
+
deps: LauncherDeps,
|
|
270
|
+
): Promise<void> {
|
|
271
|
+
const trimmedArgs = args.trim();
|
|
272
|
+
const [verb, ...rest] = trimmedArgs.split(/\s+/).filter(Boolean);
|
|
273
|
+
if (verb === "resume") {
|
|
274
|
+
await resumeTuiVerb(rest, ctx, deps);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// 直接按 runId / 前缀匹配打开
|
|
278
|
+
if (trimmedArgs) {
|
|
279
|
+
await openByRunId(trimmedArgs, getRuns, ctx, deps);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// 无参——列表选择
|
|
283
|
+
await openFromList(getRuns, ctx, deps);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* TUI resume verb:缺 runId 给 Usage 提示。可选第二参数 = 显式模型 canonical ref
|
|
288
|
+
* ('provider/modelId[:thinkingLevel]')——落统一覆盖记账(F1-26 后续项:与
|
|
289
|
+
* setModel 补切同通道,resume 合一步表达)。执行与通知复用 RPC 同款 resumeWithNotify。
|
|
290
|
+
*/
|
|
291
|
+
async function resumeTuiVerb(
|
|
292
|
+
rest: string[],
|
|
293
|
+
ctx: ExtensionCommandContext,
|
|
294
|
+
deps: LauncherDeps,
|
|
295
|
+
): Promise<void> {
|
|
296
|
+
const runId = rest[0];
|
|
297
|
+
if (!runId) {
|
|
298
|
+
ctx.ui.notify("Usage: /workflows resume <runId> [model]", "warning");
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
await resumeWithNotify(runId, rest[1], deps, ctx);
|
|
302
|
+
}
|
|
303
|
+
|
|
244
304
|
/**
|
|
245
305
|
* 命令通道显式 model 的目录预检(D8 拒单语义,与 tool 通道 run/resume action 同款
|
|
246
306
|
* ——isModelRef 语法闸在 core resumeRun,此处补目录存在性:语法合法但目录查无
|
package/src/jsonl-run-store.ts
CHANGED
|
@@ -67,13 +67,17 @@ import {
|
|
|
67
67
|
noteRebuiltSettlement,
|
|
68
68
|
settlementRecordOfRunSettledFrame,
|
|
69
69
|
type AgentResult,
|
|
70
|
+
type Budget,
|
|
70
71
|
type ExecutionTraceNode,
|
|
71
72
|
type RunEventLineIssue,
|
|
72
73
|
type RunOutcome,
|
|
74
|
+
type RunSpec,
|
|
73
75
|
type RunStore,
|
|
76
|
+
type WorkflowModelOverride,
|
|
74
77
|
type WorkflowRecordRegisteredEntryData,
|
|
75
78
|
type WorkflowRecordSettledEntryData,
|
|
76
79
|
type WorkflowRunEvent,
|
|
80
|
+
type WorkerLogEntry,
|
|
77
81
|
} from "@zhushanwen/subagent-core";
|
|
78
82
|
import { WorkflowRun } from "@zhushanwen/subagent-core";
|
|
79
83
|
import { errorLogsFromEvents, latestModelOverride, rebuildBudget, runAccountingFromEvents } from "@zhushanwen/subagent-core";
|
|
@@ -478,26 +482,32 @@ function rebuildRunSpecFromEntries(
|
|
|
478
482
|
};
|
|
479
483
|
}
|
|
480
484
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
485
|
+
/**
|
|
486
|
+
* [foldRecordStreamToRun 拆分] 两分支 reconstruct 共用的 fold 产物(spec 之外的
|
|
487
|
+
* 快照/元数据公共字段)。
|
|
488
|
+
*/
|
|
489
|
+
interface FoldCommonParts {
|
|
490
|
+
budget: Budget;
|
|
491
|
+
errorLogs: WorkerLogEntry[];
|
|
492
|
+
startedAt: string;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* [foldRecordStreamToRun 拆分] 两分支 reconstruct 共用的聚合重建块(trace + calls +
|
|
497
|
+
* 覆盖记账——原 fold 主干的公共块提取)。
|
|
498
|
+
*/
|
|
499
|
+
interface RebuiltRunAggregate {
|
|
500
|
+
calls: Map<number, AgentCall>;
|
|
501
|
+
trace: Trace;
|
|
502
|
+
/** 生效模型覆盖记账(无覆盖 undefined 不造键——语义见 rebuildRunAggregate 内注释)。 */
|
|
503
|
+
modelOverride: WorkflowModelOverride | undefined;
|
|
504
|
+
}
|
|
500
505
|
|
|
506
|
+
/**
|
|
507
|
+
* 聚合重建块:drafts → trace 节点 → Trace 副本 → call 集合(D-10 引用共享链)+
|
|
508
|
+
* 覆盖记账折叠。纯函数,零 IO。
|
|
509
|
+
*/
|
|
510
|
+
function rebuildRunAggregate(events: readonly WorkflowRunEvent[]): RebuiltRunAggregate {
|
|
501
511
|
const drafts = collectRunCallDrafts(events);
|
|
502
512
|
|
|
503
513
|
// Trace 先重建:call 的 traceNode 回链到 Trace 副本(D-10 引用共享;匹配不到
|
|
@@ -508,55 +518,123 @@ function foldRecordStreamToRun(
|
|
|
508
518
|
|
|
509
519
|
const calls = draftsToAgentCalls(drafts, sharedNodes, nodes);
|
|
510
520
|
|
|
511
|
-
const settledEvent = lastRunSettledEvent(events);
|
|
512
521
|
// [subagent-model-switch §6.6①/§7.4] 覆盖记账折叠(派生视图半边):生效覆盖值 =
|
|
513
522
|
// 最新一条(latestModelOverride core 单点——覆盖旧覆盖值不叠加,不变量 2);
|
|
514
523
|
// 无覆盖 undefined 不造键。fold 未跟时本行缺失 = 覆盖状态静默丢行——
|
|
515
524
|
// jsonl-run-store-loadall 的 fold 消费用例是此丢行风险的机器防线(P8 第五道)。
|
|
516
525
|
const modelOverride = latestModelOverride(events);
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
526
|
+
return { calls, trace, modelOverride };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* 非终局分支 reconstruct:无 run-settled 帧 = running(交恢复链收编)+ 中断标记
|
|
531
|
+
* 投影([D2])。
|
|
532
|
+
*/
|
|
533
|
+
function reconstructUnsettledRun(
|
|
534
|
+
runId: string,
|
|
535
|
+
spec: RunSpec,
|
|
536
|
+
aggregate: RebuiltRunAggregate,
|
|
537
|
+
parts: FoldCommonParts,
|
|
538
|
+
events: readonly WorkflowRunEvent[],
|
|
539
|
+
): WorkflowRun {
|
|
540
|
+
const interruptedAt = lastInterruptedAt(events);
|
|
541
|
+
return WorkflowRun.reconstruct(
|
|
542
|
+
runId,
|
|
543
|
+
spec,
|
|
544
|
+
{
|
|
545
|
+
budget: parts.budget,
|
|
546
|
+
calls: aggregate.calls,
|
|
547
|
+
trace: aggregate.trace,
|
|
548
|
+
errorLogs: parts.errorLogs,
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
startedAt: parts.startedAt,
|
|
552
|
+
// 中断标记([D2] 中断态经 meta 投影表达):末次 run-interrupted 后无
|
|
553
|
+
// run-resumed 复活 → meta.interruptedAt 置位,runSummary 投影 'interrupted'
|
|
554
|
+
//(CLI/TUI 不显示僵尸「运行中」;resume 资格判据在 core fold lifecycle,
|
|
555
|
+
// 不受本投影影响)。
|
|
556
|
+
...(interruptedAt !== undefined ? { interruptedAt } : {}),
|
|
557
|
+
...(aggregate.modelOverride !== undefined ? { modelOverride: aggregate.modelOverride } : {}),
|
|
558
|
+
},
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* 终局分支 reconstruct:run-settled 帧派生 DoneReason + completedAt;失败终局带
|
|
564
|
+
* 帧 reason 文本(不落 generic 恢复文案)。
|
|
565
|
+
*/
|
|
566
|
+
function reconstructSettledRun(
|
|
567
|
+
runId: string,
|
|
568
|
+
spec: RunSpec,
|
|
569
|
+
aggregate: RebuiltRunAggregate,
|
|
570
|
+
parts: FoldCommonParts,
|
|
571
|
+
settledEvent: RunSettledFrame,
|
|
572
|
+
): WorkflowRun {
|
|
539
573
|
const reason = runSettledOutcomeToDoneReason(settledEvent.outcome, settledEvent.errorCode);
|
|
540
574
|
return WorkflowRun.reconstruct(
|
|
541
575
|
runId,
|
|
542
576
|
spec,
|
|
543
577
|
{
|
|
544
578
|
reason,
|
|
545
|
-
budget,
|
|
546
|
-
calls,
|
|
547
|
-
trace,
|
|
548
|
-
errorLogs,
|
|
579
|
+
budget: parts.budget,
|
|
580
|
+
calls: aggregate.calls,
|
|
581
|
+
trace: aggregate.trace,
|
|
582
|
+
errorLogs: parts.errorLogs,
|
|
549
583
|
// 成功终局无 error;失败终局带帧 reason 文本(不落 generic 恢复文案)
|
|
550
584
|
...(reason !== "completed" && settledEvent.reason !== undefined ? { error: settledEvent.reason } : {}),
|
|
551
585
|
},
|
|
552
586
|
{
|
|
553
|
-
startedAt:
|
|
587
|
+
startedAt: parts.startedAt,
|
|
554
588
|
completedAt: new Date(settledEvent.ts).toISOString(),
|
|
555
|
-
...(modelOverride !== undefined ? { modelOverride } : {}),
|
|
589
|
+
...(aggregate.modelOverride !== undefined ? { modelOverride: aggregate.modelOverride } : {}),
|
|
556
590
|
},
|
|
557
591
|
);
|
|
558
592
|
}
|
|
559
593
|
|
|
594
|
+
/**
|
|
595
|
+
* startedAt 毫秒锚:run-created 帧 ts 优先,回落注册条目 startedAt(非有限数值 =
|
|
596
|
+
* 兜底当前时刻,保 startedAt 恒有值)。
|
|
597
|
+
*/
|
|
598
|
+
function resolveStartedAtMs(
|
|
599
|
+
created: Extract<WorkflowRunEvent, { type: "run-created" }> | undefined,
|
|
600
|
+
reg: WorkflowRecordRegisteredEntryData,
|
|
601
|
+
): number {
|
|
602
|
+
return created?.ts ?? (Number.isFinite(reg.startedAt) ? reg.startedAt : Date.now());
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* record 事件流 → WorkflowRun 聚合(纯函数,零 IO;fold 语义词表说明见文件头
|
|
607
|
+
* 「record 流 fold 重建」节)。主干只做产物编排:公共 fold 产物(budget/errorLogs/
|
|
608
|
+
* startedAt/spec)+ 聚合重建块(rebuildRunAggregate)+ 按终局帧有无分流到
|
|
609
|
+
* reconstructUnsettledRun / reconstructSettledRun。
|
|
610
|
+
*/
|
|
611
|
+
function foldRecordStreamToRun(
|
|
612
|
+
runId: string,
|
|
613
|
+
reg: WorkflowRecordRegisteredEntryData,
|
|
614
|
+
events: readonly WorkflowRunEvent[],
|
|
615
|
+
settled?: WorkflowRecordSettledEntryData,
|
|
616
|
+
): WorkflowRun {
|
|
617
|
+
const created = events.find(
|
|
618
|
+
(e): e is Extract<WorkflowRunEvent, { type: "run-created" }> => e.type === "run-created",
|
|
619
|
+
);
|
|
620
|
+
const spec = rebuildRunSpecFromEntries(created, reg, resolveSpecBudget(created, events));
|
|
621
|
+
// [§2.1b] 会计重建:v2 终态条目带活体口径 usedTokens/callCount → 真值优先;条目缺席
|
|
622
|
+
// 回落 agent-settled.result.usage 的同一加权口径(下界近似,含 usedCost)。
|
|
623
|
+
// [§2.1 errorLogs 持久化 / ADR-0093] 诊断日志重建:worker-log 帧 → errorLogs(与活体
|
|
624
|
+
// 写入同语义:按序 + 尾部上限裁剪)。此前无持久面、重启即空。
|
|
625
|
+
const parts: FoldCommonParts = {
|
|
626
|
+
budget: rebuildBudget(settled, events),
|
|
627
|
+
errorLogs: errorLogsFromEvents(events),
|
|
628
|
+
startedAt: new Date(resolveStartedAtMs(created, reg)).toISOString(),
|
|
629
|
+
};
|
|
630
|
+
const aggregate = rebuildRunAggregate(events);
|
|
631
|
+
const settledEvent = lastRunSettledEvent(events);
|
|
632
|
+
if (settledEvent === undefined) {
|
|
633
|
+
return reconstructUnsettledRun(runId, spec, aggregate, parts, events);
|
|
634
|
+
}
|
|
635
|
+
return reconstructSettledRun(runId, spec, aggregate, parts, settledEvent);
|
|
636
|
+
}
|
|
637
|
+
|
|
560
638
|
// ── JsonlRunStore ────────────────────────────────────────────
|
|
561
639
|
|
|
562
640
|
const logger = getLogger("subagents");
|
package/src/session-lifecycle.ts
CHANGED
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
import { bindNotifyLedgerHost, type NotifyLedgerHost } from "@zhushanwen/subagent-core";
|
|
43
43
|
import { IDENTITY_CUSTOM_TYPE, type SubagentIdentityData } from "@zhushanwen/subagent-core";
|
|
44
44
|
import type { ExecutionMode } from "@zhushanwen/subagent-core";
|
|
45
|
-
import {
|
|
45
|
+
import { startEngineJournalGcTimer } from "@zhushanwen/subagent-core";
|
|
46
46
|
import { resolvePiSessionScopedDir } from "@zhushanwen/subagent-core";
|
|
47
47
|
// [W1 / D5] session_start 兜底触发点的统一保留维护轮:入口 + record 域目录锚
|
|
48
48
|
// (getSubagentRecordsDir 布局 + ENV_ROOT_CWD 贯穿 env 名单源)+ state 目录分量
|
|
@@ -557,7 +557,7 @@ export function bindLedgerHostAndRecover(pi: ExtensionAPI, ctx: ExtensionContext
|
|
|
557
557
|
|
|
558
558
|
/**
|
|
559
559
|
* 随迁块 4 的进程级维护三连(各 try-catch「失败记日志不阻断」,设计 §3.4):
|
|
560
|
-
*
|
|
560
|
+
* 引擎 journal TTL 回收 / ADR-035 manifest tmp 恢复 / ADR-035 worktree reaper 扫描。
|
|
561
561
|
* ([modeless 波5] 原 [E1] sync 批崩溃恢复接线已摘除;[collect 退役] core 侧
|
|
562
562
|
* recoverSyncCollectBatch 方法本体已删——sync 批机制不存在,无恢复面可接线。)
|
|
563
563
|
*/
|
|
@@ -568,12 +568,16 @@ async function runProcessLevelMaintenance(
|
|
|
568
568
|
deps: SessionLifecycleDeps,
|
|
569
569
|
): Promise<void> {
|
|
570
570
|
try {
|
|
571
|
-
//
|
|
572
|
-
//
|
|
573
|
-
oncePerProcess
|
|
574
|
-
|
|
571
|
+
// 引擎 journal TTL 回收(30 天 mtime)+ worktree .patch 孤儿清扫(同 TTL,仅
|
|
572
|
+
// *.patch 两层枚举——按 branch 命名无会话锚,级联引用并集无锚可达,唯一 mtime
|
|
573
|
+
// 兜底通道)属进程级维护——oncePerProcess 守卫防双跑(u-audit-fix)。接线即扫
|
|
574
|
+
// 一次 + 24h 定时复扫(unref 不 hold 进程退出,对齐 main 侧 log-retention 形态)。
|
|
575
|
+
// 子代理会话文件(*.jsonl / sidecar / manifest / *.events)不在本维护面——回收归
|
|
576
|
+
// 删主会话级联(runtime session-content-cascade,2026-10-12 裁决)。
|
|
577
|
+
oncePerProcess("subagent-workflow:engine-journal-gc", () =>
|
|
578
|
+
startEngineJournalGcTimer(agentDir));
|
|
575
579
|
} catch (err) {
|
|
576
|
-
logger.warn("[subagents]
|
|
580
|
+
logger.warn("[subagents] engine journal gc failed", {
|
|
577
581
|
reason: toErrorMessage(err),
|
|
578
582
|
});
|
|
579
583
|
}
|