@zhushanwen/subagent-core 0.2.0 → 0.4.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 +45 -2
- package/agents/analyst.md +60 -0
- package/agents/coder.md +69 -0
- package/agents/debugger.md +66 -0
- package/agents/doc-reviewer.md +49 -0
- package/agents/explorer.md +63 -0
- package/agents/general-purpose.md +32 -0
- package/agents/orchestrator.md +61 -0
- package/agents/planner.md +53 -0
- package/agents/researcher.md +67 -0
- package/agents/reviewer.md +73 -0
- package/dist/chunk-4KN4TTG7.js +240 -0
- package/dist/chunk-APZY4IME.js +27 -0
- package/dist/chunk-X7SZ5HLQ.js +66 -0
- package/dist/execution/engine/engines/zcode/constants.cjs +65 -0
- package/dist/execution/engine/engines/zcode/constants.d.cts +93 -1
- package/dist/execution/engine/engines/zcode/constants.d.ts +93 -1
- package/dist/execution/engine/engines/zcode/constants.js +43 -1
- package/dist/execution/engine/engines/zcode/reader.d.cts +1 -1
- package/dist/execution/engine/engines/zcode/reader.d.ts +1 -1
- package/dist/execution/engine/engines/zcode/reader.js +4 -234
- package/dist/execution/engine/paths.js +7 -19
- package/dist/index.cjs +14709 -100
- package/dist/index.d.cts +4053 -200
- package/dist/index.d.ts +4053 -200
- package/dist/index.js +14358 -102
- package/dist/{types-BxyAidGf.d.cts → types-DpUO16pj.d.cts} +464 -1
- package/dist/{types-BxyAidGf.d.ts → types-DpUO16pj.d.ts} +464 -1
- package/package.json +6 -3
- package/src/__tests__/review-fix-loop-script.test.ts +90 -2
- package/src/__tests__/review-fix-loop-utils.test.ts +21 -0
- package/src/__tests__/smoke.test.ts +9 -2
- package/src/execution/__tests__/agent-profile.test.ts +232 -0
- package/src/execution/__tests__/agents-assembly.test.ts +219 -0
- package/src/execution/__tests__/chat-engine-routing.test.ts +71 -0
- package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +1 -0
- package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
- package/src/execution/__tests__/delivery-methods.test.ts +2 -0
- package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
- package/src/execution/__tests__/dialog-queue.test.ts +199 -1
- package/src/execution/__tests__/epipe-fallback.test.ts +2 -0
- package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
- package/src/execution/__tests__/finalize-record.test.ts +106 -0
- package/src/execution/__tests__/gc-timer.test.ts +2 -0
- package/src/execution/__tests__/get-record-for-action-restart.test.ts +2 -0
- package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
- package/src/execution/__tests__/keep-alive-no-progress.test.ts +378 -0
- package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
- package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
- package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
- package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
- package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
- package/src/execution/__tests__/notify-ledger.test.ts +294 -0
- package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
- package/src/execution/__tests__/record-store.test.ts +83 -0
- package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +4 -0
- package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +1 -0
- package/src/execution/__tests__/run-spawn-edges.test.ts +407 -17
- package/src/execution/__tests__/run-spawn-integration.test.ts +1 -1
- package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +12 -4
- package/src/execution/__tests__/service-kill-escalation.test.ts +89 -0
- package/src/execution/__tests__/session-pending.test.ts +228 -0
- package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
- package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
- package/src/execution/__tests__/session-runner-epipe.test.ts +1 -0
- package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +226 -0
- package/src/execution/__tests__/settled-watchdog.test.ts +289 -0
- package/src/execution/__tests__/spawned-children.test.ts +153 -10
- package/src/execution/__tests__/subagent-actions-core.test.ts +997 -0
- package/src/execution/__tests__/subagent-service-message-close.test.ts +71 -10
- package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
- package/src/execution/__tests__/subagent-service-notify-gate.test.ts +266 -0
- package/src/execution/__tests__/subagent-service-parent-guard.test.ts +3 -0
- package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +316 -0
- package/src/execution/__tests__/timeout-integration.test.ts +7 -7
- package/src/execution/__tests__/ui-request-handler-factory.test.ts +95 -1
- package/src/execution/__tests__/ui-request-queue.test.ts +25 -0
- package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
- package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
- package/src/execution/agent-registry.ts +225 -1
- package/src/execution/agents-assembly.ts +88 -0
- package/src/execution/concurrency-pool.ts +71 -9
- package/src/execution/dialog-queue.ts +101 -3
- package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +49 -2
- package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +87 -1
- package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +26 -0
- package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +83 -1
- package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +131 -0
- package/src/execution/engine/__tests__/registry.test.ts +90 -1
- package/src/execution/engine/engine-discovery.ts +12 -16
- package/src/execution/engine/engines/pi/task-spec-mapper.ts +3 -3
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +274 -0
- package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
- package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +472 -0
- package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +387 -0
- package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +780 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +815 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +462 -0
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +119 -1
- package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +136 -6
- package/src/execution/engine/engines/zcode/appserver-home.ts +442 -0
- package/src/execution/engine/engines/zcode/appserver-probe.ts +141 -0
- package/src/execution/engine/engines/zcode/connection.ts +585 -0
- package/src/execution/engine/engines/zcode/constants.ts +129 -0
- package/src/execution/engine/engines/zcode/golden-sample.ts +54 -8
- package/src/execution/engine/engines/zcode/preparer.ts +25 -22
- package/src/execution/engine/engines/zcode/session-channel.ts +656 -0
- package/src/execution/engine/engines/zcode/zcode-engine.ts +909 -41
- package/src/execution/engine/host-task-spec.ts +3 -3
- package/src/execution/engine/port.ts +41 -1
- package/src/execution/engine/registry.ts +53 -0
- package/src/execution/finalize-record.ts +63 -5
- package/src/execution/get-state-handshake.ts +85 -10
- package/src/execution/lifecycle-manager.ts +27 -3
- package/src/execution/manifest-store.ts +53 -62
- package/src/execution/notifier.ts +10 -10
- package/src/execution/notify-ledger.ts +117 -16
- package/src/execution/record-entry.ts +8 -2
- package/src/execution/record-store.ts +29 -15
- package/src/execution/session-pending.ts +213 -62
- package/src/execution/session-runner.ts +974 -128
- package/src/execution/sessions-index.ts +10 -55
- package/src/execution/settled-watchdog.ts +99 -0
- package/src/execution/subagent-actions-core.ts +686 -0
- package/src/execution/subagent-service.ts +396 -49
- package/src/execution/ui-request-handler-factory.ts +27 -4
- package/src/execution/worktree-git-ops.ts +397 -0
- package/src/execution/worktree-manager.ts +72 -10
- package/src/execution/worktree-registry.ts +6 -12
- package/src/index.ts +413 -6
- package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
- package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -4
- package/src/orchestration/__tests__/args-meta.test.ts +358 -0
- package/src/orchestration/__tests__/error-recovery-handlers.test.ts +1 -8
- package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
- package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +382 -0
- package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
- package/src/orchestration/__tests__/file-run-store.test.ts +389 -0
- package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
- package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
- package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
- package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
- package/src/orchestration/__tests__/lifecycle.test.ts +77 -0
- package/src/orchestration/__tests__/run-snapshot.test.ts +353 -0
- package/src/orchestration/__tests__/script-generate.test.ts +314 -0
- package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
- package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
- package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
- package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
- package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
- package/src/orchestration/agent-opts-resolver.ts +7 -7
- package/src/orchestration/args-meta.ts +198 -0
- package/src/orchestration/error-recovery.ts +416 -146
- package/src/orchestration/execute-agent-call.ts +9 -9
- package/src/orchestration/file-run-store.ts +327 -0
- package/src/orchestration/launcher.ts +35 -26
- package/src/orchestration/lifecycle.ts +355 -79
- package/src/orchestration/models/agent-call.ts +7 -7
- package/src/orchestration/models/budget.ts +5 -5
- package/src/orchestration/models/run-runtime.ts +13 -13
- package/src/orchestration/models/trace.ts +8 -8
- package/src/orchestration/models/workflow-run.ts +27 -27
- package/src/orchestration/models/workflow-script.ts +4 -4
- package/src/orchestration/run-snapshot.ts +266 -0
- package/src/orchestration/script-generate.ts +154 -0
- package/src/orchestration/script-lint.ts +33 -33
- package/src/orchestration/worker-handle.ts +10 -10
- package/src/orchestration/worker-host.ts +10 -10
- package/src/orchestration/worker-script-builder.ts +401 -346
- package/src/orchestration/workflow-files.ts +37 -11
- package/src/orchestration/workflow-run-summary.ts +69 -0
- package/src/orchestration/workflow-script-registry-impl.ts +31 -9
- package/src/shared/__tests__/agent-ref.test.ts +209 -2
- package/src/shared/__tests__/atomic-write.test.ts +267 -0
- package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
- package/src/shared/__tests__/injection-render.test.ts +518 -0
- package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
- package/src/shared/__tests__/resource-discovery.test.ts +3 -2
- package/src/shared/agent-ref.ts +143 -2
- package/src/shared/atomic-write.ts +320 -0
- package/src/shared/bounded-serialize.ts +154 -0
- package/src/shared/injection-render.ts +279 -0
- package/src/shared/meta-parser.ts +41 -1
- package/src/shared/resource-discovery.ts +104 -37
- package/src/shared/resource-meta.ts +14 -0
- package/src/shared/xml-injection.ts +9 -9
- package/workflows/README.md +9 -9
- package/workflows/chain.js +4 -2
- package/workflows/map-reduce.js +5 -3
- package/workflows/parallel.js +5 -3
- package/workflows/review-fix-loop-utils.cjs +14 -3
- package/workflows/review-fix-loop.js +18 -7
- package/workflows/scatter-gather.js +4 -2
- package/dist/chunk-3VOERJPJ.js +0 -22
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* - evictDoneRunsBeyondCap(runs, keepDone) → number(done run 内存淘汰)
|
|
11
11
|
* - scheduleTimeBudget(runId, deps, budgetTimeMs) → timer(C.7 时间预算)
|
|
12
12
|
*
|
|
13
|
+
* 第 6 个导出:recoverCrashedRuns(store, runs, reason, hooks?) —— 崩溃恢复四步
|
|
14
|
+
* 装配(loadAll→failed→save→evict,D8/B1),宿主专属事件经 hooks 外置。
|
|
15
|
+
*
|
|
13
16
|
* 私有 makeHandlers(run, deps) → WorkerHandlers:
|
|
14
17
|
* - onMessage → handleWorkerMessage(run, raw, deps, handlers)
|
|
15
18
|
* - onError → handleWorkerError(run, err, deps, handlers) + workerErrorCount++
|
|
@@ -38,12 +41,15 @@ import { getLogger } from "../core/logger.ts";
|
|
|
38
41
|
import { assertSafeTimerDelay } from "../shared/timer-delay.ts";
|
|
39
42
|
import { validateRunArgs } from "./args-validator.ts";
|
|
40
43
|
import {
|
|
44
|
+
closeOutInFlightCalls,
|
|
45
|
+
emitPendingUnregister,
|
|
46
|
+
emitTerminalSideEffects,
|
|
41
47
|
handleWorkerError,
|
|
42
48
|
handleWorkerExit,
|
|
43
49
|
handleWorkerMessage,
|
|
44
50
|
} from "./error-recovery.ts";
|
|
45
51
|
import { Budget } from "./models/budget.ts";
|
|
46
|
-
import type { LifecycleDeps, WorkerHandlers } from "./models/ports.ts";
|
|
52
|
+
import type { LifecycleDeps, RunStore, WorkerHandlers } from "./models/ports.ts";
|
|
47
53
|
import { RunRuntime } from "./models/run-runtime.ts";
|
|
48
54
|
import type { RunSpec } from "./models/run-spec.ts";
|
|
49
55
|
import { Trace } from "./models/trace.ts";
|
|
@@ -73,6 +79,52 @@ function generateRunId(): string {
|
|
|
73
79
|
return `wf-${Date.now()}-${Math.random().toString(RUNID_RADIX).slice(RUNID_SLICE_START, RUNID_SLICE_END)}`;
|
|
74
80
|
}
|
|
75
81
|
|
|
82
|
+
// ── OR-7:signal abort listener 终态移除 ─────────────────────
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* run → signal abort listener 收口句柄注册表。
|
|
86
|
+
*
|
|
87
|
+
* [OR-7] 旧实现 `{ once: true }` 只在 abort 触发时自清——run 经 return / error /
|
|
88
|
+
* abortRun / terminate 等任何终态路径收敛后,listener 仍挂在(可能长生命周期的)
|
|
89
|
+
* 外部 signal 上,同 signal 连续跑多 run 时每次泄漏一个(超 11 个触发
|
|
90
|
+
* MaxListeners 告警;tool-workflow 的 run action 传的是 pi 会话级 signal,泄漏
|
|
91
|
+
* 真实可达)。对齐 launcher.ts L-2 修复形态:命名 handler + run 终态
|
|
92
|
+
* removeEventListener。
|
|
93
|
+
*
|
|
94
|
+
* WeakMap:run 被 GC 时条目随之消亡——即使未来新增终态路径漏调 dispose,
|
|
95
|
+
* 注册表本身也不成为新的无界泄漏面。
|
|
96
|
+
*/
|
|
97
|
+
const signalAbortDisposers = new WeakMap<WorkflowRun, () => void>();
|
|
98
|
+
|
|
99
|
+
/** 移除 run 关联的 signal abort listener(幂等;未注册 run 为 no-op)。 */
|
|
100
|
+
function disposeSignalAbortListener(run: WorkflowRun): void {
|
|
101
|
+
const dispose = signalAbortDisposers.get(run);
|
|
102
|
+
if (!dispose) return;
|
|
103
|
+
signalAbortDisposers.delete(run);
|
|
104
|
+
dispose();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* [OR-3] 向 worker 广播 {type:"abort"}——终止 run 时先于 transition(= releaseRuntime
|
|
109
|
+
* → worker.terminate)通知 worker 优雅解阻:worker 侧 pending Map 全部 reject
|
|
110
|
+
* (WorkflowAbortedError,worker-script-builder 既有 abort 分支),脚本 catch 后自然
|
|
111
|
+
* 收尾退出,不再依赖 terminate 硬杀。
|
|
112
|
+
*
|
|
113
|
+
* P-T3 语义:广播与 worker 自然退出交错不产 unhandledRejection——postMessage 同步抛错
|
|
114
|
+
* (worker 已退出/通道关闭)只 debug 留痕不中断 abort 主流程(pendings 随 worker 死亡
|
|
115
|
+
* 清理;WorkerHandle.postMessage 对已 terminate handle 本身 no-op);transition 必须继续。
|
|
116
|
+
*/
|
|
117
|
+
function broadcastAbortToWorker(run: WorkflowRun, reason: string): void {
|
|
118
|
+
try {
|
|
119
|
+
run.runtime?.worker.postMessage({ type: "abort", reason });
|
|
120
|
+
} catch (err) {
|
|
121
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
122
|
+
logger.debug(
|
|
123
|
+
`[workflow] abort broadcast to worker failed (runId=${run.runId}, worker likely already exited): ${msg}`,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
76
128
|
// ── makeHandlers(路由 worker 事件到 error-recovery handle* 函数) ──────
|
|
77
129
|
|
|
78
130
|
/**
|
|
@@ -91,20 +143,32 @@ function generateRunId(): string {
|
|
|
91
143
|
* 实际 handleWorkerError 会做最终计数(含重试上限判断),onError 不重复递增。
|
|
92
144
|
*/
|
|
93
145
|
function makeHandlers(run: WorkflowRun, deps: LifecycleDeps): WorkerHandlers {
|
|
94
|
-
|
|
146
|
+
// [OR-7] per-run deps 视图:包装 onRunDone,把「run 终态」转译为「移除 signal abort
|
|
147
|
+
// listener」。error-recovery 全部终态路径(handleReturn / handleWorkerError /
|
|
148
|
+
// handleScriptError / handleWorkerExit / 预算终止 / time_limited / [OR-2] rebuild
|
|
149
|
+
// 失败收敛)都经 handlers 调用链消费本视图——消息面终态在此统一收口;
|
|
150
|
+
// abortRun / terminateRunningRuns 两个 lifecycle 自有终态路径另行显式 dispose。
|
|
151
|
+
const depsWithTerminalCleanup: LifecycleDeps = {
|
|
152
|
+
...deps,
|
|
153
|
+
onRunDone: (doneRun: WorkflowRun): void => {
|
|
154
|
+
disposeSignalAbortListener(run);
|
|
155
|
+
deps.onRunDone?.(doneRun);
|
|
156
|
+
},
|
|
157
|
+
};
|
|
158
|
+
// 自引用——error-recovery rebuildRuntime 需要 handlers 参数(handlers 引用自身)
|
|
95
159
|
const handlers: WorkerHandlers = {
|
|
96
160
|
async onMessage(raw: unknown): Promise<void> {
|
|
97
|
-
await handleWorkerMessage(run, raw,
|
|
161
|
+
await handleWorkerMessage(run, raw, depsWithTerminalCleanup, handlers);
|
|
98
162
|
},
|
|
99
163
|
async onError(err: Error): Promise<void> {
|
|
100
|
-
await handleWorkerError(run, err,
|
|
164
|
+
await handleWorkerError(run, err, depsWithTerminalCleanup, handlers);
|
|
101
165
|
},
|
|
102
166
|
async onExit(code: number, handle: WorkerHandle): Promise<void> {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
await handleWorkerExit(run, code, handle,
|
|
167
|
+
// H-2:用 worker-host 传入的 handle(即真正触发 exit 的那个 handle),而非
|
|
168
|
+
// run.runtime?.worker——重试竞态下 runtime.worker 可能已被 replaceRuntime 替换
|
|
169
|
+
// 为新 handle,导致 handleWorkerExit 内的 isCurrent 检查误判(漏判 stale exit 或
|
|
170
|
+
// 误杀新 worker)。G-025 检查仍在 handleWorkerExit 内(handle.isCurrent)。
|
|
171
|
+
await handleWorkerExit(run, code, handle, depsWithTerminalCleanup, handlers);
|
|
108
172
|
},
|
|
109
173
|
};
|
|
110
174
|
return handlers;
|
|
@@ -139,13 +203,127 @@ export function scheduleTimeBudget(
|
|
|
139
203
|
},
|
|
140
204
|
);
|
|
141
205
|
}, budgetTimeMs);
|
|
142
|
-
|
|
206
|
+
// unref:不阻止 Node 退出(workflow 是后台任务,不应因计时器持有事件循环)。
|
|
143
207
|
timer.unref();
|
|
144
208
|
return timer;
|
|
145
209
|
}
|
|
146
210
|
|
|
147
211
|
// ── runWorkflow ──────────────────────────────────────────────
|
|
148
212
|
|
|
213
|
+
/**
|
|
214
|
+
* rfl 仪表(tier-1 §7.1):向 spec.args 原地注入稳定 _runId。runAndWait 与
|
|
215
|
+
* executeNestedWorkflow 两个 args 入口都经 runWorkflow 这一 choke point;
|
|
216
|
+
* rebuildRuntime 复用 run.spec.args 同一对象(error-recovery.ts),worker rebuild
|
|
217
|
+
* 后脚本侧 $ARGS._runId 不漂移——修复「rebuild 回退 run-<Date.now()> 导致同一
|
|
218
|
+
* 逻辑 run 碎裂到多个 state 目录」。注入在 validateRunArgs 之后,不参与脚本
|
|
219
|
+
* 参数 schema 校验(引擎内部字段)。
|
|
220
|
+
*/
|
|
221
|
+
function injectRunId(spec: RunSpec, runId: string): void {
|
|
222
|
+
if (spec.args && typeof spec.args === "object") {
|
|
223
|
+
spec.args._runId = runId;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** P1-2: pre-aborted signal → fail fast(run 创建前抛,zero side effects)。 */
|
|
228
|
+
function assertSignalNotAborted(signal: AbortSignal | undefined): void {
|
|
229
|
+
if (signal?.aborted) {
|
|
230
|
+
throw new Error("Workflow run aborted before start");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** 创建 running 态 WorkflowRun(budget 共享引用优先,否则按 spec 上限新建)。 */
|
|
235
|
+
function createRunningRun(runId: string, spec: RunSpec): WorkflowRun {
|
|
236
|
+
return new WorkflowRun(
|
|
237
|
+
runId,
|
|
238
|
+
spec,
|
|
239
|
+
{
|
|
240
|
+
status: "running",
|
|
241
|
+
budget: spec.budgetRef ?? new Budget({
|
|
242
|
+
maxTokens: spec.budgetTokens,
|
|
243
|
+
maxTimeMs: spec.budgetTimeMs,
|
|
244
|
+
}),
|
|
245
|
+
calls: new Map(),
|
|
246
|
+
trace: new Trace(),
|
|
247
|
+
errorLogs: [],
|
|
248
|
+
},
|
|
249
|
+
{ startedAt: new Date().toISOString() },
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* C.7:run 级时间预算计时器(spec.budgetTimeMs > 0 时启用,到期 abortRun
|
|
255
|
+
* time_limited)。OR-1 顺序修复(unbounded-wait-audit §4.1):调度先于
|
|
256
|
+
* workerHost.start——budgetTimeMs 越界(>2^31-1)的 fail-fast throw
|
|
257
|
+
* (assertSafeTimerDelay)发生在任何副作用(worker 启动、runs 注册)之前,
|
|
258
|
+
* 杜绝「worker 已跑、run 永不可达」的孤儿窗口(此前 runs.set 在最末,
|
|
259
|
+
* fail-fast 后连 abortRun 都抛 not found)。
|
|
260
|
+
*/
|
|
261
|
+
function armTimeBudgetTimer(
|
|
262
|
+
runId: string,
|
|
263
|
+
deps: LifecycleDeps,
|
|
264
|
+
spec: RunSpec,
|
|
265
|
+
): ReturnType<typeof setTimeout> | undefined {
|
|
266
|
+
return spec.budgetTimeMs && spec.budgetTimeMs > 0
|
|
267
|
+
? scheduleTimeBudget(runId, deps, spec.budgetTimeMs)
|
|
268
|
+
: undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 启动 worker;失败时清理已挂的 timeBudget timer——前移的代价:start 抛错时
|
|
273
|
+
* 已挂的 timer 会残留,到期对从未注册的 runId 触发 abortRun(必抛 not found)
|
|
274
|
+
* ——该路径 runs.set 未到,须就地清理。
|
|
275
|
+
*/
|
|
276
|
+
function startWorkerGuarded(
|
|
277
|
+
spec: RunSpec,
|
|
278
|
+
deps: LifecycleDeps,
|
|
279
|
+
handlers: WorkerHandlers,
|
|
280
|
+
timeBudgetTimer: ReturnType<typeof setTimeout> | undefined,
|
|
281
|
+
): WorkerHandle {
|
|
282
|
+
try {
|
|
283
|
+
return deps.workerHost.start(spec, spec.args, handlers);
|
|
284
|
+
} catch (err) {
|
|
285
|
+
if (timeBudgetTimer) clearTimeout(timeBudgetTimer);
|
|
286
|
+
throw err;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* signal abort → abortRun([OR-7] 命名 listener + run 终态移除,注册表
|
|
292
|
+
* signalAbortDisposers;触发时自移除 = 显式 once 语义)。
|
|
293
|
+
* [B-2] 注册点后移到全部 fail-fast throw(assertSafeTimerDelay / workerHost.start)
|
|
294
|
+
* 成功之后——旧实现先注册 listener,scheduleTimeBudget/start 两条 throw 路径 run
|
|
295
|
+
* 永不到终态,disposeSignalAbortListener 永不被调,同 signal 重试每次泄漏一个
|
|
296
|
+
* listener(MaxListeners 告警面)。移后 throw 路径天然未注册,零泄漏;首个真正
|
|
297
|
+
* 副作用前完成校验的语义保持(addEventListener 本身也是设计意义上的副作用)。
|
|
298
|
+
* 注册点到 store.save 之间无 await(同步段),signal 在此窗口 abort 不可能丢失。
|
|
299
|
+
*/
|
|
300
|
+
function registerSignalAbortListener(
|
|
301
|
+
run: WorkflowRun,
|
|
302
|
+
runId: string,
|
|
303
|
+
deps: LifecycleDeps,
|
|
304
|
+
signal: AbortSignal | undefined,
|
|
305
|
+
): void {
|
|
306
|
+
if (!signal) return;
|
|
307
|
+
const onAbort = (): void => {
|
|
308
|
+
disposeSignalAbortListener(run);
|
|
309
|
+
void abortRun(runId, deps, "External signal aborted").catch((err: unknown) => {
|
|
310
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
311
|
+
logger.error(`[workflow] abortRun on signal failed: ${msg}`);
|
|
312
|
+
});
|
|
313
|
+
};
|
|
314
|
+
signal.addEventListener("abort", onAbort);
|
|
315
|
+
signalAbortDisposers.set(run, () => signal.removeEventListener("abort", onAbort));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/** pending-notifications: run 启动 → 注册(所有 workflow 启动路径的单一汇聚点)。 */
|
|
319
|
+
function emitPendingRegister(runId: string, deps: LifecycleDeps, spec: RunSpec): void {
|
|
320
|
+
deps.eventBus?.emit("pending:register", {
|
|
321
|
+
id: runId,
|
|
322
|
+
type: "workflow",
|
|
323
|
+
name: spec.slug || spec.scriptName || runId,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
149
327
|
/**
|
|
150
328
|
* 启动一个 workflow run。
|
|
151
329
|
*
|
|
@@ -172,81 +350,37 @@ export async function runWorkflow(
|
|
|
172
350
|
validateRunArgs(spec);
|
|
173
351
|
|
|
174
352
|
const runId = generateRunId();
|
|
175
|
-
|
|
176
|
-
// 两个 args 入口都经本 choke point;rebuildRuntime 复用 run.spec.args 同一对象
|
|
177
|
-
// (error-recovery.ts),worker rebuild 后脚本侧 $ARGS._runId 不漂移——修复
|
|
178
|
-
// 「rebuild 回退 run-<Date.now()> 导致同一逻辑 run 碎裂到多个 state 目录」。
|
|
179
|
-
// 注入在 validateRunArgs 之后,不参与脚本参数 schema 校验(引擎内部字段)。
|
|
180
|
-
if (spec.args && typeof spec.args === "object") {
|
|
181
|
-
spec.args._runId = runId;
|
|
182
|
-
}
|
|
353
|
+
injectRunId(spec, runId);
|
|
183
354
|
deps.log?.("debug", "workflow:lifecycle", "runWorkflow start", { runId, scriptName: spec.scriptName });
|
|
184
355
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
throw new Error("Workflow run aborted before start");
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const run = new WorkflowRun(
|
|
191
|
-
runId,
|
|
192
|
-
spec,
|
|
193
|
-
{
|
|
194
|
-
status: "running",
|
|
195
|
-
budget: spec.budgetRef ?? new Budget({
|
|
196
|
-
maxTokens: spec.budgetTokens,
|
|
197
|
-
maxTimeMs: spec.budgetTimeMs,
|
|
198
|
-
}),
|
|
199
|
-
calls: new Map(),
|
|
200
|
-
trace: new Trace(),
|
|
201
|
-
errorLogs: [],
|
|
202
|
-
},
|
|
203
|
-
{ startedAt: new Date().toISOString() },
|
|
204
|
-
);
|
|
356
|
+
// P1-2: pre-aborted signal → fail fast
|
|
357
|
+
assertSignalNotAborted(signal);
|
|
205
358
|
|
|
206
|
-
|
|
207
|
-
if (signal) {
|
|
208
|
-
signal.addEventListener(
|
|
209
|
-
"abort",
|
|
210
|
-
() => {
|
|
211
|
-
void abortRun(runId, deps, "External signal aborted").catch((err: unknown) => {
|
|
212
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
213
|
-
logger.error(`[workflow] abortRun on signal failed: ${msg}`);
|
|
214
|
-
});
|
|
215
|
-
},
|
|
216
|
-
{ once: true },
|
|
217
|
-
);
|
|
218
|
-
}
|
|
359
|
+
const run = createRunningRun(runId, spec);
|
|
219
360
|
|
|
220
|
-
|
|
361
|
+
// 构造 handlers + runtime(worker + controller)
|
|
221
362
|
const handlers = makeHandlers(run, deps);
|
|
222
363
|
const controller = new AbortController();
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
const timeBudgetTimer =
|
|
226
|
-
spec.budgetTimeMs && spec.budgetTimeMs > 0
|
|
227
|
-
? scheduleTimeBudget(runId, deps, spec.budgetTimeMs)
|
|
228
|
-
: undefined;
|
|
364
|
+
const timeBudgetTimer = armTimeBudgetTimer(runId, deps, spec);
|
|
365
|
+
const worker = startWorkerGuarded(spec, deps, handlers, timeBudgetTimer);
|
|
229
366
|
const runtime = new RunRuntime(worker, controller, timeBudgetTimer);
|
|
230
367
|
|
|
231
|
-
|
|
368
|
+
// assignRuntime(注入 runtime,恢复 I1:running ⟺ runtime!==undefined)
|
|
232
369
|
run.assignRuntime(runtime);
|
|
233
370
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
371
|
+
// 注册到 deps.runs(assignRuntime 之后——构造到 assignRuntime 之间 run 处于
|
|
372
|
+
// I1 跳过窗口(running 而 runtime undefined),后移保证窗口对外不可见;
|
|
373
|
+
// scheduleTimeBudget/worker.start 抛错时 run 未注册,无孤儿 run 残留)
|
|
237
374
|
deps.runs.set(runId, run);
|
|
238
375
|
|
|
376
|
+
registerSignalAbortListener(run, runId, deps, signal);
|
|
377
|
+
|
|
239
378
|
await deps.store.save(run);
|
|
240
379
|
deps.log?.("debug", "workflow:lifecycle", "run saved", { runId, status: run.state.status });
|
|
241
380
|
|
|
242
|
-
|
|
243
|
-
// runAndWait / actionRun / 未来入口全覆盖)
|
|
381
|
+
// pending-notifications: run 启动 → 注册(runAndWait / actionRun / 未来入口全覆盖)
|
|
244
382
|
deps.log?.("debug", "workflow:lifecycle", "emit pending:register", { runId });
|
|
245
|
-
|
|
246
|
-
id: runId,
|
|
247
|
-
type: "workflow",
|
|
248
|
-
name: spec.slug || spec.scriptName || runId,
|
|
249
|
-
});
|
|
383
|
+
emitPendingRegister(runId, deps, spec);
|
|
250
384
|
deps.log?.("debug", "workflow:lifecycle", "emit pending:register done", { runId });
|
|
251
385
|
|
|
252
386
|
return runId;
|
|
@@ -279,25 +413,33 @@ export async function abortRun(
|
|
|
279
413
|
|
|
280
414
|
deps.log?.("debug", "workflow:lifecycle", "abortRun", { runId, status: run.state.status, reason, doneReason });
|
|
281
415
|
|
|
282
|
-
|
|
416
|
+
// done 状态 no-op
|
|
283
417
|
if (run.state.status === "done") {
|
|
284
418
|
deps.log?.("debug", "workflow:lifecycle", "abortRun no-op: already done", { runId });
|
|
285
419
|
return;
|
|
286
420
|
}
|
|
287
421
|
|
|
288
|
-
|
|
422
|
+
// 记录中止原因
|
|
289
423
|
if (reason) {
|
|
290
424
|
run.state.error = reason;
|
|
291
425
|
}
|
|
292
|
-
|
|
426
|
+
// [OR-3] 先广播 abort 再 transition(transition 内 releaseRuntime→terminate,
|
|
427
|
+
// terminate 之后广播发不进去)——worker 侧 pending 优雅解阻
|
|
428
|
+
broadcastAbortToWorker(run, reason ?? `Workflow aborted (${doneReason})`);
|
|
429
|
+
// A4: transition 内部 releaseRuntime(cleanup before mutate)
|
|
293
430
|
run.transition("done", doneReason);
|
|
431
|
+
// [OR-7] run 终态:移除 signal abort listener(幂等;abort 由 signal 触发时
|
|
432
|
+
// listener 已在 onAbort 内自移除,此处为其余 abort 入口的收口)
|
|
433
|
+
disposeSignalAbortListener(run);
|
|
434
|
+
// [OR-8] 终态收口残留 in-flight call(先收口再落盘——快照不再含 running 节点)
|
|
435
|
+
closeOutInFlightCalls(run);
|
|
294
436
|
await deps.store.save(run);
|
|
295
437
|
deps.log?.("debug", "workflow:lifecycle", "abortRun transition done", { runId, reason: run.state.reason });
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
deps
|
|
438
|
+
// C-4: run 到达 done 终态 → 注销 pending-notification + 通知 Interface 层
|
|
439
|
+
// [OR-4][B-4] M12 同款围栏(emit pending:unregister + onRunDone 各自独立 try)——
|
|
440
|
+
// listener 同步抛错不再跳过 onRunDone 或把错误抛给 abort 调用方;错误 error 留痕后
|
|
441
|
+
// 本函数仍按 abort 成功语义正常返回(内存态已终态)。
|
|
442
|
+
emitTerminalSideEffects(run, deps, `abortRun (done,${doneReason})`);
|
|
301
443
|
}
|
|
302
444
|
|
|
303
445
|
// ── terminateRunningRuns(session 切换/关闭:终止全部 running run) ────────
|
|
@@ -336,11 +478,20 @@ export async function terminateRunningRuns(
|
|
|
336
478
|
try {
|
|
337
479
|
deps.log?.("debug", "workflow:lifecycle", "terminateRunningRuns", { runId: run.runId, reason });
|
|
338
480
|
run.state.error = reason;
|
|
481
|
+
// [OR-3] 同 abortRun:先广播 abort 再 transition(worker 侧 pending 优雅解阻)
|
|
482
|
+
broadcastAbortToWorker(run, reason);
|
|
339
483
|
// A4: transition 内部先 releaseRuntime(cleanup before mutate)
|
|
340
484
|
run.transition("done", "failed");
|
|
485
|
+
// [OR-7] run 终态:移除 signal abort listener(terminateRunningRuns 不走
|
|
486
|
+
// onRunDone——本路径显式收口)
|
|
487
|
+
disposeSignalAbortListener(run);
|
|
488
|
+
// [OR-8] 终态收口残留 in-flight call(先收口再落盘)
|
|
489
|
+
closeOutInFlightCalls(run);
|
|
341
490
|
await deps.store.save(run);
|
|
342
|
-
// C-4: run 到达 done 终态 → 注销 pending-notification(reason 固定 "failed"
|
|
343
|
-
|
|
491
|
+
// C-4: run 到达 done 终态 → 注销 pending-notification(reason 固定 "failed")。
|
|
492
|
+
// [B-4] M12 同款围栏:emit 单独 try(listener 同步抛错 error 留痕不中断本 run
|
|
493
|
+
// 收尾日志与其余 run 的终止;本路径不调 onRunDone——session 离开语境不发完成通知)。
|
|
494
|
+
emitPendingUnregister(run, deps, "terminateRunningRuns");
|
|
344
495
|
deps.log?.("debug", "workflow:lifecycle", "run terminated", { runId: run.runId, reason: run.state.reason });
|
|
345
496
|
} catch (err) {
|
|
346
497
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -397,3 +548,128 @@ export function evictDoneRunsBeyondCap(
|
|
|
397
548
|
}
|
|
398
549
|
return excess;
|
|
399
550
|
}
|
|
551
|
+
|
|
552
|
+
// ── recoverCrashedRuns(kill-9/崩溃恢复四步装配,D8/B1) ────────────────
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* 崩溃恢复循环的宿主事件外置 hooks(core 平台中立,宿主注入专属行为)。
|
|
556
|
+
*
|
|
557
|
+
* pi 宿主在 onRunRecovered 中发射 `pending:unregister` 事件(pending-notifications
|
|
558
|
+
* 扩展的注销信号灯);zsw/第三宿主可接自己的通知通道。不注入 = 无宿主事件,
|
|
559
|
+
* 恢复语义(failed 转换 + 落盘 + 淘汰)不受影响。
|
|
560
|
+
*/
|
|
561
|
+
export interface RecoverCrashedRunsHooks {
|
|
562
|
+
/**
|
|
563
|
+
* 每个 running → done,failed 转换的 run 恰好调用一次。
|
|
564
|
+
*
|
|
565
|
+
* payload 形状对齐 pi `pending:unregister` 事件:`{ id: runId, reason: "failed" }`
|
|
566
|
+
* ——宿主可直接把 payload 转发到自己的事件总线。
|
|
567
|
+
*
|
|
568
|
+
* 错误围栏:回调同步 throw 经 core logger facade warn 留痕后被吞掉,恢复循环
|
|
569
|
+
* 继续其余 run(转换/落盘不受影响)——与 save 步骤「单 run 失败不中断其余」
|
|
570
|
+
* 容错口径对称。
|
|
571
|
+
*/
|
|
572
|
+
onRunRecovered?: (payload: { id: string; reason: string }) => void;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* recoverCrashedRuns 的计数结果(宿主启动日志/健康面用)。
|
|
577
|
+
*
|
|
578
|
+
* `recovered` 只计 running 遗留被转换为 done,failed 的条数;`loaded` 是 loadAll
|
|
579
|
+
* 重水合的全量数(含 done 历史快照)——两者分开口径,避免宿主把「重水合 N 条」
|
|
580
|
+
* 误报为「恢复 N 条」。
|
|
581
|
+
*/
|
|
582
|
+
export interface RecoverCrashedRunsResult {
|
|
583
|
+
/** loadAll 重水合的 run 总数(含 done 历史快照)。 */
|
|
584
|
+
loaded: number;
|
|
585
|
+
/** running 遗留被转换为 done,failed 的条数(0 = 无崩溃遗留)。 */
|
|
586
|
+
recovered: number;
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/**
|
|
590
|
+
* 崩溃恢复四步装配(设计 D8/B1):loadAll → failed → save → evict。
|
|
591
|
+
*
|
|
592
|
+
* 平移自 pi 壳 session_start 恢复循环(subagent-workflow index.ts:578-627)与
|
|
593
|
+
* zsw orchestration-host recoverOrphans(逐行同构,pending:unregister 为唯一
|
|
594
|
+
* 宿主差异点——经 {@link RecoverCrashedRunsHooks} 外置)。
|
|
595
|
+
*
|
|
596
|
+
* 步骤语义:
|
|
597
|
+
* 1. **loadAll**:从 store 全量重水合。失败向上抛——fail-fast 策略(pi 的
|
|
598
|
+
* storeHealthy=false 停初始化)是宿主职责,调用方 catch 后自行决定;
|
|
599
|
+
* 2. **failed**:残留 running run(进程被杀,worker 必死)逐个
|
|
600
|
+
* `state.error = reason` → `transition("done","failed")`(A4:transition 内部
|
|
601
|
+
* 先 releaseRuntime),并发宿主事件(hooks);done run 原样保留;
|
|
602
|
+
* 3. **save**:转换后的 run 落盘(恢复终态必须持久化,不 save 则下次启动重水合
|
|
603
|
+
* 仍见 running,侧栏永久卡 running)。单 run save 失败仅记日志不中断其余
|
|
604
|
+
* run——恢复天然幂等,下次启动重开重试;
|
|
605
|
+
* 4. **evict**:全量重水合后立即 `evictDoneRunsBeyondCap(runs,
|
|
606
|
+
* MAX_RETAINED_DONE_RUNS)`(K=20)——done run 内存有界性;只 delete 内存
|
|
607
|
+
* Map 条目,磁盘 state 文件不动(对齐 pi,历史审计保留)。
|
|
608
|
+
*
|
|
609
|
+
* 所有 loaded run(含 done)都注册进 runs Map(runId → WorkflowRun)——与 pi
|
|
610
|
+
* 一致,done run 也入 Map 供列表/查询消费。
|
|
611
|
+
*
|
|
612
|
+
* @param store RunStore port(loadAll/save)
|
|
613
|
+
* @param runs per-session 的 run 注册表(原地写入 + 淘汰)
|
|
614
|
+
* @param reason 恢复原因(写入 running run 的 state.error,如
|
|
615
|
+
* "Process killed (kill-9 or crash recovery)")
|
|
616
|
+
* @param hooks 宿主事件外置(可选)
|
|
617
|
+
* @returns 计数 `{ loaded, recovered }`——recovered 只计 running→failed 转换条数
|
|
618
|
+
* @throws store.loadAll 失败时原样抛出(步骤 1)
|
|
619
|
+
*/
|
|
620
|
+
export async function recoverCrashedRuns(
|
|
621
|
+
store: RunStore,
|
|
622
|
+
runs: Map<string, WorkflowRun>,
|
|
623
|
+
reason: string,
|
|
624
|
+
hooks?: RecoverCrashedRunsHooks,
|
|
625
|
+
): Promise<RecoverCrashedRunsResult> {
|
|
626
|
+
// 步骤 1:loadAll(失败上抛,宿主决定 fail-fast 策略)
|
|
627
|
+
const loaded = await store.loadAll();
|
|
628
|
+
let recovered = 0;
|
|
629
|
+
|
|
630
|
+
for (const run of loaded) {
|
|
631
|
+
if (run.state.status === "running") {
|
|
632
|
+
// 步骤 2:running → done,failed(顺序对齐 pi:set error → transition → 宿主事件)
|
|
633
|
+
run.state.error = reason;
|
|
634
|
+
run.transition("done", "failed");
|
|
635
|
+
// [OR-8] 重水合 running 快照可携带 in-flight call(崩溃瞬间的 running 节点)——
|
|
636
|
+
// 终态收口为 cancelled,先收口再落盘
|
|
637
|
+
closeOutInFlightCalls(run);
|
|
638
|
+
recovered += 1;
|
|
639
|
+
// 宿主事件外置点:pi 发 pending:unregister,位置在 transition 后、save 前
|
|
640
|
+
// (对齐 pi emit 位置——先解除挂起通知再落盘,事件观察者不依赖落盘完成)。
|
|
641
|
+
// 错误围栏:宿主回调同步 throw 只 warn 不中断循环——与步骤 3 save 的
|
|
642
|
+
// 「单 run 失败不中断其余」容错口径对称(通知通道故障不等价于恢复失败;
|
|
643
|
+
// 恢复天然幂等,未送达的通知随下次启动重试,见步骤 3 注释)。
|
|
644
|
+
try {
|
|
645
|
+
hooks?.onRunRecovered?.({ id: run.runId, reason: "failed" });
|
|
646
|
+
} catch (err) {
|
|
647
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
648
|
+
logger.warn(
|
|
649
|
+
`[workflow] recoverCrashedRuns onRunRecovered hook failed for run ${run.runId} (recovery continues): ${msg}`,
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
// 步骤 3:恢复终态落盘(单 run 失败不中断其余 run——幂等恢复,下次重试)
|
|
653
|
+
try {
|
|
654
|
+
await store.save(run);
|
|
655
|
+
} catch (err) {
|
|
656
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
657
|
+
logger.error(
|
|
658
|
+
`[workflow] recoverCrashedRuns store.save failed for run ${run.runId}: ${msg} (reason: ${reason})`,
|
|
659
|
+
);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
runs.set(run.runId, run);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// 步骤 4:done run 内存有界性(K=20)。kill-9 恢复转换出的 failed run
|
|
666
|
+
// completedAt 为 transition 时刻(全局最新)必在保留端;多条恢复 run 同 ms
|
|
667
|
+
// completedAt → tie 稳定排序(evictDoneRunsBeyondCap 契约)。
|
|
668
|
+
const evicted = evictDoneRunsBeyondCap(runs, MAX_RETAINED_DONE_RUNS);
|
|
669
|
+
if (evicted > 0) {
|
|
670
|
+
logger.debug(
|
|
671
|
+
`[workflow] recoverCrashedRuns evicted ${evicted} done runs beyond cap (keep=${MAX_RETAINED_DONE_RUNS})`,
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
return { loaded: loaded.length, recovered };
|
|
675
|
+
}
|
|
@@ -34,11 +34,11 @@ export class AgentCall {
|
|
|
34
34
|
status: AgentCallStatus = "pending";
|
|
35
35
|
attempts = 0;
|
|
36
36
|
result?: AgentResult;
|
|
37
|
-
|
|
37
|
+
/** Pi subprocess session ID(uuidv7,G-017 归此)。 */
|
|
38
38
|
sessionId?: string;
|
|
39
|
-
|
|
39
|
+
/** Session JSONL 绝对路径(finalizeCall 后从 result.sessionFile 填入,对齐 sessionId 模式)。 */
|
|
40
40
|
sessionFile?: string;
|
|
41
|
-
|
|
41
|
+
/** 与 Trace 共享的节点引用(D-10 单源)。AgentCall 不直接改其字段。 */
|
|
42
42
|
readonly traceNode: ExecutionTraceNode;
|
|
43
43
|
|
|
44
44
|
constructor(id: number, opts: AgentCallOpts, traceNode: ExecutionTraceNode) {
|
|
@@ -47,7 +47,7 @@ export class AgentCall {
|
|
|
47
47
|
this.traceNode = traceNode;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
/**
|
|
51
51
|
* 标记进入 running 状态(dispatch 前)。attempts++(含首次)。
|
|
52
52
|
* @throws 若已 done(不可重启)
|
|
53
53
|
*/
|
|
@@ -59,7 +59,7 @@ export class AgentCall {
|
|
|
59
59
|
this.attempts += 1;
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
63
|
* 标记完成(成功或失败均调用——result.error 区分)。
|
|
64
64
|
* @throws 若当前非 running(pending 不能直接跳 done,必须先 markRunning)
|
|
65
65
|
*/
|
|
@@ -71,12 +71,12 @@ export class AgentCall {
|
|
|
71
71
|
this.status = "done";
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
/** 记录 pi subprocess session ID(dispatch 成功后)。 */
|
|
75
75
|
setSessionId(sessionId: string): void {
|
|
76
76
|
this.sessionId = sessionId;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
/** 记录 session JSONL 绝对路径(finalizeCall 后,对齐 setSessionId 模式)。 */
|
|
80
80
|
setSessionFile(sessionFile: string): void {
|
|
81
81
|
this.sessionFile = sessionFile;
|
|
82
82
|
}
|
|
@@ -46,7 +46,7 @@ export class Budget {
|
|
|
46
46
|
readonly maxTimeMs?: number;
|
|
47
47
|
usedTokens = 0;
|
|
48
48
|
usedCost = 0;
|
|
49
|
-
|
|
49
|
+
/** 总调用计数(持久化/诊断用;execute-agent-call 每次 dispatch 后 increment)。 */
|
|
50
50
|
totalCallCount = 0;
|
|
51
51
|
|
|
52
52
|
constructor(opts: {
|
|
@@ -65,7 +65,7 @@ export class Budget {
|
|
|
65
65
|
this.totalCallCount = opts.totalCallCount ?? 0;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
/**
|
|
69
69
|
* 累加一次 agent 调用的 usage(加权口径)。
|
|
70
70
|
*
|
|
71
71
|
* 四项 token 按各自权重(INPUT/CACHE_READ/CACHE_WRITE/OUTPUT_WEIGHT)折算后求和,
|
|
@@ -84,12 +84,12 @@ export class Budget {
|
|
|
84
84
|
this.usedCost += numOrZero(usage.cost);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
|
|
87
|
+
/** 累加调用计数(每次 agent dispatch 后调用;持久化快照同步)。 */
|
|
88
88
|
incrementCallCount(): void {
|
|
89
89
|
this.totalCallCount += 1;
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
93
|
* 是否超 token / cost 预算(FR-3)。
|
|
94
94
|
*
|
|
95
95
|
* maxTokens===0 或 undefined 视为不限制(守卫);
|
|
@@ -105,7 +105,7 @@ export class Budget {
|
|
|
105
105
|
return this.maxCost !== undefined && this.maxCost > 0 && this.usedCost >= this.maxCost;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
108
|
+
/**
|
|
109
109
|
* 剩余 token 预算。maxTokens 未设或 ≤0 时返回 undefined(视为不限制)。
|
|
110
110
|
*
|
|
111
111
|
* 嵌套 workflow() 调用时由 executeNestedWorkflow 消费:子 run 的 budgetTokens
|