@zhushanwen/pi-subagent-workflow 7.3.4 → 8.0.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/README.md +39 -12
- package/agents/analyst.md +61 -0
- package/agents/coder.md +70 -0
- package/agents/debugger.md +67 -0
- package/agents/doc-reviewer.md +3 -3
- package/agents/explorer.md +50 -18
- package/agents/general-purpose.md +19 -8
- package/agents/orchestrator.md +37 -32
- package/agents/planner.md +45 -11
- package/agents/researcher.md +53 -11
- package/agents/reviewer.md +74 -0
- package/package.json +1 -1
- package/skills/workflow-script-format/SKILL.md +1 -1
- package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
- package/src/execution/__tests__/agent-registry.test.ts +13 -11
- package/src/execution/__tests__/ask-user-transit-e2e.test.ts +10 -4
- package/src/execution/__tests__/before-agent-start-injection.test.ts +132 -0
- package/src/execution/__tests__/bg-notify-render.test.ts +15 -15
- package/src/execution/__tests__/chatmode-first-round-closure-service.test.ts +365 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
- package/src/execution/__tests__/chatmode-round-notify-real-chain.test.ts +215 -0
- package/src/execution/__tests__/conversation-wiring.test.ts +198 -0
- package/src/execution/__tests__/crash-recovery.test.ts +8 -2
- package/src/execution/__tests__/delivery-methods.test.ts +385 -0
- package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
- package/src/execution/__tests__/execute-and-await-worktree.test.ts +49 -2
- package/src/execution/__tests__/execute-nesting.test.ts +20 -72
- package/src/execution/__tests__/execution-record.test.ts +199 -0
- package/src/execution/__tests__/finalize-record.test.ts +170 -14
- package/src/execution/__tests__/format.test.ts +131 -7
- package/src/execution/__tests__/gc-timer.test.ts +184 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
- package/src/execution/__tests__/helpers/spawn-mock.ts +25 -7
- package/src/execution/__tests__/index-session-start-identity.test.ts +371 -0
- package/src/execution/__tests__/index-session-start.test.ts +257 -5
- package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +337 -0
- package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
- package/src/execution/__tests__/list-component.test.ts +59 -5
- package/src/execution/__tests__/list-fields.test.ts +109 -0
- package/src/execution/__tests__/model-resolver.test.ts +38 -1
- package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
- package/src/execution/__tests__/nested-visibility.test.ts +325 -0
- package/src/execution/__tests__/notifier-flush.test.ts +209 -7
- package/src/execution/__tests__/one-shot-upgrade.test.ts +205 -0
- package/src/execution/__tests__/parent-child-matrix.test.ts +336 -0
- package/src/execution/__tests__/record-store.test.ts +158 -52
- package/src/execution/__tests__/recursive-visibility-baseline.test.ts +11 -12
- package/src/execution/__tests__/recursive-visibility-env.test.ts +18 -20
- package/src/execution/__tests__/resource-policy.test.ts +109 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +18 -25
- package/src/execution/__tests__/run-spawn-integration.test.ts +29 -25
- package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
- package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +14 -11
- package/src/execution/__tests__/session-pending.test.ts +61 -2
- package/src/execution/__tests__/session-reconstructor.test.ts +4 -4
- package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
- package/src/execution/__tests__/session-runner-schema-env.test.ts +15 -21
- package/src/execution/__tests__/session-start-reaper.test.ts +10 -8
- package/src/execution/__tests__/spawn-args.test.ts +127 -49
- package/src/execution/__tests__/spawn-worktree-guidance.test.ts +1 -0
- package/src/execution/__tests__/spawned-children.test.ts +92 -0
- package/src/execution/__tests__/status-refactor.test.ts +345 -0
- package/src/execution/__tests__/stdin-writer.test.ts +97 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +598 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
- package/src/execution/__tests__/subagent-service.test.ts +49 -11
- package/src/execution/__tests__/timeout-integration.test.ts +27 -13
- package/src/execution/__tests__/tool-action.test.ts +12 -10
- package/src/execution/__tests__/truncline-snapshot.test.ts +81 -0
- package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
- package/src/execution/__tests__/worktree-manager.test.ts +292 -89
- package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +13 -12
- package/src/execution/argv-mirror.ts +21 -2
- package/src/execution/execution-record.ts +126 -9
- package/src/execution/finalize-record.ts +90 -13
- package/src/execution/host-mode.ts +1 -1
- package/src/execution/lifecycle-manager.ts +484 -0
- package/src/execution/lifecycle-predicates.ts +65 -0
- package/src/execution/manifest-store.ts +61 -16
- package/src/execution/model-resolver.ts +26 -5
- package/src/execution/notifier.ts +69 -12
- package/src/execution/pi-invocation.ts +21 -1
- package/src/execution/record-store.ts +554 -107
- package/src/execution/session-pending.ts +116 -45
- package/src/execution/session-reconstructor.ts +224 -7
- package/src/execution/session-runner.ts +290 -75
- package/src/execution/sessions-index.ts +304 -0
- package/src/execution/stdin-writer.ts +93 -7
- package/src/execution/stream-sink.ts +20 -3
- package/src/execution/subagent-service.ts +867 -138
- package/src/execution/turn-limiter.ts +14 -0
- package/src/execution/types.ts +204 -22
- package/src/execution/worktree-manager.ts +128 -49
- package/src/execution/worktree-registry.ts +13 -2
- package/src/index.ts +277 -19
- package/src/injectors/subagent-list-injector.ts +26 -8
- package/src/injectors/workflow-list-injector.ts +25 -8
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +18 -5
- package/src/interface/__tests__/tool-render.test.ts +15 -13
- package/src/interface/bg-notify-render.ts +32 -8
- package/src/interface/command-actions.ts +26 -7
- package/src/interface/commands.ts +21 -22
- package/src/interface/format.ts +44 -17
- package/src/interface/gui-mappers.ts +6 -8
- package/src/interface/helpers.ts +170 -10
- package/src/interface/list-component.ts +53 -14
- package/src/interface/subagent-actions.ts +235 -17
- package/src/interface/subagent-tool.ts +82 -17
- package/src/interface/subagents.ts +2 -1
- package/src/interface/tool-render.ts +11 -24
- package/src/interface/tool-workflow.ts +20 -35
- package/src/interface/views/WorkflowsView.ts +89 -32
- package/src/interface/views/__tests__/WorkflowsView-signature.test.ts +264 -0
- package/src/interface/views/__tests__/detail-content-session-file.test.ts +1 -1
- package/src/interface/views/format.ts +3 -3
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +325 -0
- package/src/orchestration/__tests__/args-validator.test.ts +1 -1
- package/src/orchestration/__tests__/config-loader.test.ts +38 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +394 -4
- package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +4 -4
- package/src/orchestration/__tests__/execute-agent-call.test.ts +95 -0
- package/src/orchestration/__tests__/jsonl-run-store-session-file.test.ts +657 -18
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +0 -2
- package/src/orchestration/__tests__/lifecycle.test.ts +332 -149
- package/src/orchestration/__tests__/skill-discovery.test.ts +157 -0
- package/src/orchestration/__tests__/test-mocks.ts +191 -0
- package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +98 -0
- package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +0 -2
- package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
- package/src/orchestration/__tests__/workflows-e2e.test.ts +1 -1
- package/src/orchestration/agent-opts-resolver.ts +4 -1
- package/src/orchestration/args-validator.ts +2 -2
- package/src/orchestration/config-loader.ts +30 -1
- package/src/orchestration/error-recovery.ts +133 -29
- package/src/orchestration/execute-agent-call.ts +31 -7
- package/src/orchestration/jsonl-run-store.ts +287 -40
- package/src/orchestration/launcher.ts +7 -1
- package/src/orchestration/lifecycle.ts +135 -132
- package/src/orchestration/models/__tests__/trace.test.ts +408 -0
- package/src/orchestration/models/budget.ts +1 -1
- package/src/orchestration/models/run-runtime.ts +15 -17
- package/src/orchestration/models/run-spec.ts +2 -2
- package/src/orchestration/models/run-state.ts +3 -3
- package/src/orchestration/models/trace.ts +95 -15
- package/src/orchestration/models/types.ts +8 -9
- package/src/orchestration/models/workflow-run.ts +50 -71
- package/src/orchestration/models/workflow-script.ts +32 -1
- package/src/orchestration/skill-discovery.ts +30 -0
- package/src/orchestration/worker-handle.ts +1 -1
- package/src/orchestration/worker-host.ts +1 -1
- package/src/orchestration/worker-script-builder.ts +29 -10
- package/src/shared/__tests__/agent-ref.test.ts +34 -0
- package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +280 -0
- package/src/shared/__tests__/resource-discovery.test.ts +55 -0
- package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
- package/src/shared/agent-ref.ts +16 -0
- package/src/shared/resource-discovery.ts +147 -58
- package/src/shared/schema-jsonify.ts +53 -0
- package/workflows/README.md +4 -4
- package/agents/code-reviewer.md +0 -47
- package/agents/context-builder.md +0 -21
- package/agents/oracle.md +0 -34
- package/agents/worker.md +0 -20
|
@@ -109,6 +109,25 @@ function isTerminal(run: WorkflowRun): boolean {
|
|
|
109
109
|
return run.state.status === "done";
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 孤儿 call 判定:dispatch 时捕获的 call 实例是否已不是 calls Map 中该 callId
|
|
114
|
+
* 的当前条目。
|
|
115
|
+
*
|
|
116
|
+
* 为何需要实例级比对(而非只查 run 状态):rebuildRuntime 不改 status(全程
|
|
117
|
+
* running),既有的终态 stale 守卫拦不住旧 runtime 代际的迟到 completion。只有
|
|
118
|
+
* discardInFlightCalls(delete 条目)与新一代 dispatch(set 新实例)会改变
|
|
119
|
+
* 「callId → 实例」映射,故实例不等 ⟺ 本 completion 属于被丢弃/被替换的旧代际
|
|
120
|
+
* (S7-second 竞态:旧失败结果经 postAgentResult 投给新 worker 的同 callId
|
|
121
|
+
* pending,劫持重跑调用为假失败/空串假成功)。
|
|
122
|
+
*
|
|
123
|
+
* 运行期 calls Map 写点仅 discard 的 delete 与 dispatchAgentCall 的 set 两族
|
|
124
|
+
* (jsonl-run-store 的 set 在离线重水合路径,无在飞 promise),正常(非孤儿)
|
|
125
|
+
* 路径下实例恒等,无误判。
|
|
126
|
+
*/
|
|
127
|
+
function isOrphanedCall(run: WorkflowRun, callId: number, call: AgentCall): boolean {
|
|
128
|
+
return run.state.calls.get(callId) !== call;
|
|
129
|
+
}
|
|
130
|
+
|
|
112
131
|
/** 计算第 n 次重试前的退避时间(ms):1s, 2s, 4s 指数。 */
|
|
113
132
|
function backoffDelay(retryIndex: number): number {
|
|
114
133
|
return RETRY_BACKOFF_BASE_MS * Math.pow(EXPONENTIAL_BACKOFF_BASE, retryIndex - 1);
|
|
@@ -123,6 +142,29 @@ function delay(ms: number): Promise<void> {
|
|
|
123
142
|
|
|
124
143
|
// ── rebuildRuntime(G3-001 整重建) ─────────────────────────
|
|
125
144
|
|
|
145
|
+
/**
|
|
146
|
+
* 移除 run 中未真正完成的在飞 call(status !== "done")及其 trace 节点。
|
|
147
|
+
*
|
|
148
|
+
* 仅 rebuildRuntime 调用——清理被旧 runtime abort 的在飞 call,避免重跑时
|
|
149
|
+
* cached replay 把 abort 产生的 failed 结果当作已完成结果回放(原 MUST_FIX
|
|
150
|
+
* round-4 #1,自 pause 路径移入崩溃重建路径)。genuinely-done 的 call(成功或
|
|
151
|
+
* 失败均 "done")保留,重跑时按原语义 replay(不重复耗 token)。
|
|
152
|
+
*
|
|
153
|
+
* 返回被丢弃的 callId 数组(升序——Map 迭代按插入序,排序保证返回值与
|
|
154
|
+
* rebuildRuntime 的 L3 日志 payload 形态稳定),供调用方记日志。
|
|
155
|
+
*/
|
|
156
|
+
function discardInFlightCalls(run: WorkflowRun): number[] {
|
|
157
|
+
const inFlight: number[] = [];
|
|
158
|
+
for (const [callId, call] of run.state.calls) {
|
|
159
|
+
if (call.status !== "done") inFlight.push(callId);
|
|
160
|
+
}
|
|
161
|
+
for (const callId of inFlight) {
|
|
162
|
+
run.state.calls.delete(callId);
|
|
163
|
+
run.state.trace.removeByStepIndex(callId);
|
|
164
|
+
}
|
|
165
|
+
return inFlight.sort((a, b) => a - b);
|
|
166
|
+
}
|
|
167
|
+
|
|
126
168
|
/**
|
|
127
169
|
* 重建整个 RunRuntime:新 controller + 新 gate + 新 worker。
|
|
128
170
|
*
|
|
@@ -142,19 +184,56 @@ export function rebuildRuntime(
|
|
|
142
184
|
deps: LifecycleDeps,
|
|
143
185
|
handlers: WorkerHandlers,
|
|
144
186
|
): void {
|
|
187
|
+
// OB3(可观察性):rebuild 关键节点 debug 日志——此前函数体 0 处 deps.log,
|
|
188
|
+
// 崩溃自愈只能靠行为证据诊断(L1 入口 / L2 重排 / L3 discard / L4 完成)。
|
|
189
|
+
deps.log?.("debug", "workflow:error-recovery", "runtime rebuild start", {
|
|
190
|
+
runId: run.runId,
|
|
191
|
+
budgetTimeMs: run.spec.budgetTimeMs,
|
|
192
|
+
});
|
|
145
193
|
const controller = new AbortController();
|
|
146
194
|
const gate = new ConcurrencyGate({ maxConcurrency: DEFAULT_CONCURRENCY });
|
|
147
195
|
const worker = deps.workerHost.start(run.spec, run.spec.args, handlers);
|
|
148
196
|
// D-12 regression fix (round-2 #2):重新调度 run 级墙钟预算计时器。
|
|
149
197
|
// replaceRuntime 释放旧 runtime 时 clearTimeout 了旧计时器(run-runtime.release),
|
|
150
198
|
// 新 runtime 必须重排,否则带 budgetTimeMs 的 run 命中一次 worker/script 错误重试后
|
|
151
|
-
// 时间预算静默失效(直到
|
|
152
|
-
//
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
199
|
+
// 时间预算静默失效(直到 rebuildRuntime 才重排——本函数即唯一重排点)。
|
|
200
|
+
// deps.scheduleTimeBudget 由 Interface 层注入;未注入时(旧测试)跳过重排(兼容,
|
|
201
|
+
// 不影响无时间预算的 run)。
|
|
202
|
+
// 重排分支改为 if——语义与原三元一致(同一条件调 scheduleTimeBudget),仅为在
|
|
203
|
+
// 分支内记 L2 日志,控制流/异常语义零变化。
|
|
204
|
+
let timeBudgetTimer: ReturnType<typeof setTimeout> | undefined;
|
|
205
|
+
if (run.spec.budgetTimeMs && run.spec.budgetTimeMs > 0 && deps.scheduleTimeBudget) {
|
|
206
|
+
timeBudgetTimer = deps.scheduleTimeBudget(run.runId, run.spec.budgetTimeMs);
|
|
207
|
+
deps.log?.("debug", "workflow:error-recovery", "time budget rescheduled", {
|
|
208
|
+
runId: run.runId,
|
|
209
|
+
budgetTimeMs: run.spec.budgetTimeMs,
|
|
210
|
+
});
|
|
211
|
+
}
|
|
157
212
|
run.replaceRuntime(new RunRuntime(worker, gate, controller, timeBudgetTimer));
|
|
213
|
+
// 清除被旧 runtime abort 的在飞 call——必须在 replaceRuntime 之后同步执行(无
|
|
214
|
+
// await 间隔):replaceRuntime 同步 abort 旧 controller + terminate 旧 worker,
|
|
215
|
+
// 在飞 executeAgentCall 的 finalize 发生在 `await runner.run` resolve 后的
|
|
216
|
+
// microtask,此刻在飞 call 仍为 "running"/"pending"(status !== "done")可精确
|
|
217
|
+
// 清理;genuinely-done 的 call 保留(重跑 replay)。放 delay 退避之前会误删退避
|
|
218
|
+
// 期间自然完成的真结果(重跑重复耗 token);放任何 await 之后,假失败已 finalize
|
|
219
|
+
// 为 "done" 挡不住——重跑 replay 会把 abort 错误当真结果回放,静默污染输出。
|
|
220
|
+
// 注意:discard 只清 Map/trace 条目,旧 executeAgentCall 的 promise 链仍会醒来
|
|
221
|
+
// finalize。markDone 在孤儿实例上无害,但后续投递并非 no-op——postAgentResult
|
|
222
|
+
// 会投给 run.runtime(已是新 worker)的同 callId pending,劫持重跑调用(实测
|
|
223
|
+
// S7-second 竞态:旧失败结果被 worker 侧 resolve 为空串 → 脚本假成功);
|
|
224
|
+
// finalizeCall 的 trace.update 在重跑已 append 同 stepIndex 新节点时命中新节点
|
|
225
|
+
// (瞬时污染,由重跑完成时的 update 覆盖)。该投递由 dispatchAgentCall 的
|
|
226
|
+
// 孤儿守卫(isOrphanedCall)拦截,trace.update 的瞬时污染由 executeAgentCall
|
|
227
|
+
// 的 isOrphaned 谓词(OB2)拦截,此处不重复设防。
|
|
228
|
+
const discardedCallIds = discardInFlightCalls(run);
|
|
229
|
+
deps.log?.("debug", "workflow:error-recovery", "in-flight calls discarded", {
|
|
230
|
+
runId: run.runId,
|
|
231
|
+
callIds: discardedCallIds,
|
|
232
|
+
count: discardedCallIds.length,
|
|
233
|
+
});
|
|
234
|
+
deps.log?.("debug", "workflow:error-recovery", "runtime rebuild complete", {
|
|
235
|
+
runId: run.runId,
|
|
236
|
+
});
|
|
158
237
|
}
|
|
159
238
|
|
|
160
239
|
// ── handleWorkerMessage(消息路由) ──────────────────────────
|
|
@@ -166,7 +245,7 @@ export function rebuildRuntime(
|
|
|
166
245
|
* return → transition done,completed(脚本正常返回)
|
|
167
246
|
* error → handleScriptError(脚本主动抛错)
|
|
168
247
|
*
|
|
169
|
-
*
|
|
248
|
+
* 终态(done)下的 stale 消息丢弃(P0-1)。
|
|
170
249
|
*/
|
|
171
250
|
export async function handleWorkerMessage(
|
|
172
251
|
run: WorkflowRun,
|
|
@@ -174,8 +253,8 @@ export async function handleWorkerMessage(
|
|
|
174
253
|
deps: LifecycleDeps,
|
|
175
254
|
handlers: WorkerHandlers,
|
|
176
255
|
): Promise<void> {
|
|
177
|
-
//
|
|
178
|
-
if (isTerminal(run)
|
|
256
|
+
// 终态(done)丢弃 stale 消息(P0-1)
|
|
257
|
+
if (isTerminal(run)) return;
|
|
179
258
|
|
|
180
259
|
// M7: 形状校验——防畸形 IPC 消息(worker 崩溃/发非对象)导致下游 TypeError
|
|
181
260
|
if (typeof raw !== "object" || raw === null) return;
|
|
@@ -215,8 +294,12 @@ export async function handleWorkerMessage(
|
|
|
215
294
|
* **C-2 修复**:call 完成后检查 `budget.isExceeded` → abortRun(budget_limited),
|
|
216
295
|
* 终止整个 run(避免烧光预算后继续 spawn 新 call)。
|
|
217
296
|
*
|
|
218
|
-
* **stale
|
|
219
|
-
*
|
|
297
|
+
* **stale 完成守卫(两层)**:completion 到达时——
|
|
298
|
+
* 1. `run.state.status === "running"` recheck:run 终止(abort/terminate)后到达的
|
|
299
|
+
* call 完成不写 run.state.calls / 不 postAgentResult(终态快照不被迟到结果污染);
|
|
300
|
+
* 2. 孤儿 call 实例比对(isOrphanedCall):rebuildRuntime 后旧代际 dispatch 的
|
|
301
|
+
* completion 不投递——rebuild 不改 status,第 1 层拦不住跨 runtime 代际的迟到
|
|
302
|
+
* 结果(S7-second 竞态:旧失败结果投给新 worker 劫持重跑 pending → 假成功)。
|
|
220
303
|
*/
|
|
221
304
|
function dispatchAgentCall(
|
|
222
305
|
run: WorkflowRun,
|
|
@@ -233,7 +316,7 @@ function dispatchAgentCall(
|
|
|
233
316
|
return;
|
|
234
317
|
}
|
|
235
318
|
|
|
236
|
-
// 已缓存的调用直接 replay(跨
|
|
319
|
+
// 已缓存的调用直接 replay(跨 rebuild——崩溃重建后重跑脚本,已完成调用按 callId 命中缓存)
|
|
237
320
|
const cached = run.state.calls.get(msg.callId);
|
|
238
321
|
if (cached && cached.status === "done") {
|
|
239
322
|
postAgentResult(run, msg.callId, cached.result!, true);
|
|
@@ -313,7 +396,7 @@ function dispatchAgentCall(
|
|
|
313
396
|
// runner(runner.run)管 spawn pi 子进程。
|
|
314
397
|
// assignRuntime/replaceRuntime 保证 status==="running" ⟺ runtime defined,
|
|
315
398
|
// 故 run.runtime 在此必存在(dispatchAgentCall 仅从 handleWorkerMessage 调用,
|
|
316
|
-
// 后者已守
|
|
399
|
+
// 后者已守 terminal(isTerminal)早期 return)。fallback new AbortController 已移除。
|
|
317
400
|
const runtime = run.runtime!;
|
|
318
401
|
const signal = runtime.controller.signal;
|
|
319
402
|
// D-005: onEvent 签名升级——executeAndAwait 直接出 AgentEvent(强类型,
|
|
@@ -333,7 +416,10 @@ function dispatchAgentCall(
|
|
|
333
416
|
.withSlot(
|
|
334
417
|
async () => {
|
|
335
418
|
try {
|
|
336
|
-
|
|
419
|
+
// OB2(S7 残留):isOrphaned 谓词注入——旧代际 finalize 在 trace.update 前被
|
|
420
|
+
// 拦截(判定语义与下方 .then/.catch 守卫同一 isOrphanedCall,详见
|
|
421
|
+
// execute-agent-call.ts finalizeCall 文档注释)。
|
|
422
|
+
await executeAgentCall(call, deps.runner, run.state.budget, signal, run.state.trace, onEvent, stream, () => isOrphanedCall(run, msg.callId, call));
|
|
337
423
|
} finally {
|
|
338
424
|
stream?.dispose();
|
|
339
425
|
}
|
|
@@ -343,10 +429,20 @@ function dispatchAgentCall(
|
|
|
343
429
|
.then(() => {
|
|
344
430
|
// 清除 live record:终态已由 executeAgentCall → finalizeCall 写入 node.result,
|
|
345
431
|
// live 不再需要(且含可变状态,不保留)。无论 stale 与否都清,避免内存泄漏。
|
|
346
|
-
// M4: 必须在 stale guard
|
|
432
|
+
// M4: 必须在 stale guard 之前清,否则跨 rebuild 的迟到 completion 会累积 live record。
|
|
347
433
|
node.live = undefined;
|
|
348
|
-
//
|
|
434
|
+
// run 终止(终态)后到达的 stale completion 不写 state
|
|
349
435
|
if (run.state.status !== "running") return;
|
|
436
|
+
// 孤儿 call 守卫(S7-second 竞态):rebuild 的 discardInFlightCalls 已移除本
|
|
437
|
+
// call、或重跑 dispatch 已用新实例替换同 callId 条目时,本 completion 属于旧
|
|
438
|
+
// runtime 代际。postAgentResult 的投递目标是 run.runtime(已是新 worker),
|
|
439
|
+
// 迟到结果会劫持新 worker 内重跑 agent() 的 pending Promise——跳过投递 /
|
|
440
|
+
// budget 同步 / 持久化,仅留日志。executeAgentCall 内 finalizeCall 的
|
|
441
|
+
// trace.update 若已命中重跑新节点(瞬时污染),由重跑完成时的 update 覆盖。
|
|
442
|
+
if (isOrphanedCall(run, msg.callId, call)) {
|
|
443
|
+
deps.log?.("debug", "workflow:error-recovery", "orphan agent call completion dropped", { runId: run.runId, callId: msg.callId });
|
|
444
|
+
return;
|
|
445
|
+
}
|
|
350
446
|
if (call.result) postAgentResult(run, msg.callId, call.result, false);
|
|
351
447
|
// D-12 regression fix (round-2 #1):executeAgentCall 内 consume/incrementCallCount
|
|
352
448
|
// 后同步 worker $BUDGET(否则 $BUDGET.spent()/remaining() 恒为 0)
|
|
@@ -401,6 +497,15 @@ function dispatchAgentCall(
|
|
|
401
497
|
// Promise 永不 resolve → agent() 永久 await → worker 脚本挂死。构造 failed AgentResult
|
|
402
498
|
//(与 resolveAgentOpts 失败路径 L262-275 一致的模式)postAgentResult 回 worker,
|
|
403
499
|
// 让 pending Promise resolve(结果为 error),脚本可继续或失败退出。
|
|
500
|
+
// 孤儿 call 守卫(与 .then 对称,S7-second 竞态):rebuild 后本 call 已被 discard
|
|
501
|
+
// 移除/替换——markDone 虽在孤儿实例上无害,但 trace.update 会污染重跑新建的同
|
|
502
|
+
// stepIndex 节点、postAgentResult 会劫持新 worker 的同 callId pending。孤儿时只
|
|
503
|
+
// 留日志,全部跳过。node.live 无条件先清(旧节点已脱离 trace,防御性统一)。
|
|
504
|
+
node.live = undefined;
|
|
505
|
+
if (isOrphanedCall(run, msg.callId, call)) {
|
|
506
|
+
deps.log?.("debug", "workflow:error-recovery", "orphan agent call failure dropped", { runId: run.runId, callId: msg.callId });
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
404
509
|
const errorResult: AgentResult = { content: "", error: message };
|
|
405
510
|
// call 已 done(executeAgentCall 内 finalizeCall 已 markDone)时跳过,避免重复 markDone。
|
|
406
511
|
// status 理论上必为 running(executeAgentCall L130 markRunning 先于 reject),pending
|
|
@@ -411,9 +516,8 @@ function dispatchAgentCall(
|
|
|
411
516
|
}
|
|
412
517
|
// state 一致性三件套(与 resolveAgentOpts 失败 L268-276 / .then L319-325 对等):
|
|
413
518
|
// trace 标 failed + 清 live record(防泄漏)+ 持久化(catch 恰是最需留证的场景)。
|
|
414
|
-
// stale 终态(run 已
|
|
415
|
-
// optional chaining 跳过 worker 回发;trace/state
|
|
416
|
-
node.live = undefined;
|
|
519
|
+
// stale 终态(run 已 done)时 run.runtime 为 undefined,postAgentResult 用
|
|
520
|
+
// optional chaining 跳过 worker 回发;trace/state 写入仍执行(无害,终态快照已存)。
|
|
417
521
|
run.state.trace.update(msg.callId, {
|
|
418
522
|
status: "failed",
|
|
419
523
|
result: errorResult,
|
|
@@ -450,7 +554,7 @@ export function makeSerializeFailedResult(
|
|
|
450
554
|
* 异步 postMessage(workflow-result) 回 worker。
|
|
451
555
|
*
|
|
452
556
|
* onWorkflowCall 未注入时(向后兼容),返回 error result 让脚本 soft-fail。
|
|
453
|
-
* 与 dispatchAgentCall 对称:异步触发(不 await),stale
|
|
557
|
+
* 与 dispatchAgentCall 对称:异步触发(不 await),stale 完成守卫(终态不发)。
|
|
454
558
|
*/
|
|
455
559
|
function dispatchWorkflowCall(
|
|
456
560
|
run: WorkflowRun,
|
|
@@ -619,9 +723,9 @@ export async function handleWorkerError(
|
|
|
619
723
|
deps: LifecycleDeps,
|
|
620
724
|
handlers: WorkerHandlers,
|
|
621
725
|
): Promise<void> {
|
|
622
|
-
// 与 handleWorkerMessage
|
|
623
|
-
//
|
|
624
|
-
if (isTerminal(run)
|
|
726
|
+
// 与 handleWorkerMessage 对称——终态(done)丢弃 stale error。
|
|
727
|
+
// 否则终态后到达的 worker error 仍会 workerErrorCount++(污染跨 runtime 计数)。
|
|
728
|
+
if (isTerminal(run)) return;
|
|
625
729
|
|
|
626
730
|
const count = (run.meta.workerErrorCount ?? 0) + 1;
|
|
627
731
|
run.meta.workerErrorCount = count;
|
|
@@ -664,7 +768,7 @@ export async function handleWorkerExit(
|
|
|
664
768
|
): Promise<void> {
|
|
665
769
|
// G-025: stale exit 事件丢弃(handle 已不是当前 runtime 的 worker)
|
|
666
770
|
if (!handle.isCurrent) return;
|
|
667
|
-
if (isTerminal(run)
|
|
771
|
+
if (isTerminal(run)) return;
|
|
668
772
|
|
|
669
773
|
if (code === 0) return; // 正常退出,no-op
|
|
670
774
|
|
|
@@ -695,8 +799,8 @@ export async function handleScriptError(
|
|
|
695
799
|
deps: LifecycleDeps,
|
|
696
800
|
handlers: WorkerHandlers,
|
|
697
801
|
): Promise<void> {
|
|
698
|
-
// 与 handleWorkerMessage/handleWorkerError
|
|
699
|
-
if (isTerminal(run)
|
|
802
|
+
// 与 handleWorkerMessage/handleWorkerError 对称——终态守卫前置。
|
|
803
|
+
if (isTerminal(run)) return;
|
|
700
804
|
|
|
701
805
|
// P2-2: 捕获 worker 诊断日志
|
|
702
806
|
// L9: 追加而非覆盖
|
|
@@ -733,8 +837,8 @@ export async function handleScriptError(
|
|
|
733
837
|
/**
|
|
734
838
|
* 退避后重建 RunRuntime(G3-001 整重建)。
|
|
735
839
|
*
|
|
736
|
-
* 退避期间 run 可能被
|
|
737
|
-
*
|
|
840
|
+
* 退避期间 run 可能被 abort(转终态 done)——rebuildRuntime 前重检状态,终态时
|
|
841
|
+
* 跳过重建(避免给已终止的 run 启新 worker)。
|
|
738
842
|
*/
|
|
739
843
|
async function scheduleRebuild(
|
|
740
844
|
run: WorkflowRun,
|
|
@@ -749,7 +853,7 @@ async function scheduleRebuild(
|
|
|
749
853
|
await delay(backoffDelay(retryIndex));
|
|
750
854
|
|
|
751
855
|
// 退避期间状态可能变化——重检
|
|
752
|
-
if (isTerminal(run)
|
|
856
|
+
if (isTerminal(run)) return;
|
|
753
857
|
|
|
754
858
|
rebuildRuntime(run, deps, handlers);
|
|
755
859
|
}
|
|
@@ -78,13 +78,31 @@ function backoffDelay(retryIndex: number): number {
|
|
|
78
78
|
*
|
|
79
79
|
* 成功:status="completed";失败:status="failed"。
|
|
80
80
|
* traceNode.stepIndex === call.id(D-10 单源,调用方保证)。
|
|
81
|
+
*
|
|
82
|
+
* 孤儿守卫(OB2,S7 残留):isOrphaned 谓词为 true 时跳过 trace.update——
|
|
83
|
+
* rebuild 竞态窗口中,重跑 dispatch 已 append 同 stepIndex 新节点,旧代际
|
|
84
|
+
* finalize 的 update 会命中新节点,TUI/中间快照短暂可见错误终态。正确性论证:
|
|
85
|
+
* 运行期 calls Map 写点仅 discardInFlightCalls 的 delete 与 dispatchAgentCall 的
|
|
86
|
+
* set 两族(error-recovery.ts isOrphanedCall 文档注释既定),故实例不等 ⟺ 本
|
|
87
|
+
* finalize 属于被丢弃/被替换的旧代际——与 dispatch 层 .then/.catch 守卫
|
|
88
|
+
* (S7-second 修复,8353f6b60)同一判定语义,本守卫只是把它前移到 trace.update
|
|
89
|
+
* 之前。markDone 与 sessionId/sessionFile 同步保留(markDone 在孤儿实例上无害,
|
|
90
|
+
* dispatch 层 catch 路径依赖 call.status 语义)。跳过时不记日志——本文件是纯
|
|
91
|
+
* 函数层无日志通道,dispatch 层 .then 守卫的 orphan completion dropped 日志已
|
|
92
|
+
* 覆盖同一事件的可观察性。
|
|
81
93
|
*/
|
|
82
|
-
function finalizeCall(
|
|
94
|
+
function finalizeCall(
|
|
95
|
+
call: AgentCall,
|
|
96
|
+
result: AgentResult,
|
|
97
|
+
trace: Trace,
|
|
98
|
+
isOrphaned?: () => boolean,
|
|
99
|
+
): void {
|
|
83
100
|
call.markDone(result);
|
|
84
101
|
const status = result.error === undefined ? "completed" : "failed";
|
|
85
102
|
// 同步 AgentCall 的 sessionId/sessionFile(对齐 trace 节点,持久化 + reset 用)
|
|
86
103
|
if (result.sessionId !== undefined) call.setSessionId(result.sessionId);
|
|
87
104
|
if (result.sessionFile !== undefined) call.setSessionFile(result.sessionFile);
|
|
105
|
+
if (isOrphaned?.()) return;
|
|
88
106
|
trace.update(call.id, {
|
|
89
107
|
status,
|
|
90
108
|
result,
|
|
@@ -124,6 +142,11 @@ function delay(ms: number): Promise<void> {
|
|
|
124
142
|
* @param budget Budget 值对象(consume + isExceeded 检查)
|
|
125
143
|
* @param signal AbortSignal(runner.run 传播;abort 后不重试)
|
|
126
144
|
* @param trace Trace 值对象(finalizeCall 时 update)
|
|
145
|
+
* @param onEvent 实时事件回调(live record 更新用)
|
|
146
|
+
* @param stream streaming sink(透传 runner.run)
|
|
147
|
+
* @param isOrphaned 孤儿判定谓词(OB2,可选,默认恒 false):true 时 finalizeCall
|
|
148
|
+
* 跳过 trace.update(判定语义与正确性论证见 finalizeCall 文档注释)。递归重试
|
|
149
|
+
* 透传本谓词。
|
|
127
150
|
*/
|
|
128
151
|
export async function executeAgentCall(
|
|
129
152
|
call: AgentCall,
|
|
@@ -133,6 +156,7 @@ export async function executeAgentCall(
|
|
|
133
156
|
trace: Trace,
|
|
134
157
|
onEvent?: (event: AgentEvent) => void,
|
|
135
158
|
stream?: SubagentStream,
|
|
159
|
+
isOrphaned?: () => boolean,
|
|
136
160
|
): Promise<void> {
|
|
137
161
|
call.markRunning();
|
|
138
162
|
|
|
@@ -145,21 +169,21 @@ export async function executeAgentCall(
|
|
|
145
169
|
|
|
146
170
|
// stale-context:不重试(P1-5)
|
|
147
171
|
if (result.error !== undefined && isStaleContextErrorMsg(result.error)) {
|
|
148
|
-
finalizeCall(call, result, trace);
|
|
172
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
149
173
|
budget.incrementCallCount();
|
|
150
174
|
return;
|
|
151
175
|
}
|
|
152
176
|
|
|
153
177
|
// signal 已 abort:调用方终止,不重试(避免无意义的递归)
|
|
154
178
|
if (signal.aborted) {
|
|
155
|
-
finalizeCall(call, result, trace);
|
|
179
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
156
180
|
budget.incrementCallCount();
|
|
157
181
|
return;
|
|
158
182
|
}
|
|
159
183
|
|
|
160
184
|
// 预算超限:不重试(重试只会突破预算且无意义)
|
|
161
185
|
if (result.error !== undefined && budget.isExceeded()) {
|
|
162
|
-
finalizeCall(call, result, trace);
|
|
186
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
163
187
|
budget.incrementCallCount();
|
|
164
188
|
return;
|
|
165
189
|
}
|
|
@@ -169,15 +193,15 @@ export async function executeAgentCall(
|
|
|
169
193
|
await delay(backoffDelay(call.attempts));
|
|
170
194
|
// 退避期间 signal 可能 abort
|
|
171
195
|
if (signal.aborted) {
|
|
172
|
-
finalizeCall(call, result, trace);
|
|
196
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
173
197
|
budget.incrementCallCount();
|
|
174
198
|
return;
|
|
175
199
|
}
|
|
176
|
-
await executeAgentCall(call, runner, budget, signal, trace, onEvent, stream);
|
|
200
|
+
await executeAgentCall(call, runner, budget, signal, trace, onEvent, stream, isOrphaned);
|
|
177
201
|
return;
|
|
178
202
|
}
|
|
179
203
|
|
|
180
204
|
// 终态(成功或达到重试上限的失败)
|
|
181
|
-
finalizeCall(call, result, trace);
|
|
205
|
+
finalizeCall(call, result, trace, isOrphaned);
|
|
182
206
|
budget.incrementCallCount();
|
|
183
207
|
}
|