(
mgr: AgentManager,
options: RunLoopOptions = {}
)
| 30 | * recovery path; use `kill` explicitly if you want to converge a broken tree. |
| 31 | */ |
| 32 | export async function runOrchestrateLoop( |
| 33 | mgr: AgentManager, |
| 34 | options: RunLoopOptions = {} |
| 35 | ): Promise<number> { |
| 36 | const running: Promise<void>[] = []; |
| 37 | const maxRuntimeSec = options.maxRuntimeSec ?? DEFAULT_MAX_RUNTIME_SEC; |
| 38 | const rootMode = options.rootMode ?? false; |
| 39 | const exitOnError = options.exitOnError ?? true; |
| 40 | const now = options.now ?? Date.now; |
| 41 | const sleepFn = options.sleep ?? sleep; |
| 42 | const startedAt = now(); |
| 43 | // Pre-existing errors (respawned-but-still-failed, pruned-siblings) |
| 44 | // must not short-circuit the first sweep; only new transitions do. |
| 45 | const preExistingErrors = new Set( |
| 46 | mgr.tasks.filter(s => s.status === "error").map(s => s.name) |
| 47 | ); |
| 48 | |
| 49 | for (const s of mgr.tasks) { |
| 50 | if (s.status === "running") { |
| 51 | const rec = await mgr.recoverRunning(s); |
| 52 | if (rec) running.push(mgr.waitAndHandoff(rec)); |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | if (exitOnError && hasNewTerminalError(mgr, preExistingErrors)) { |
| 57 | return exitOnErrorReturn(mgr, preExistingErrors); |
| 58 | } |
| 59 | |
| 60 | while (true) { |
| 61 | await spawnReadyPending(mgr, running); |
| 62 | printSweepHeartbeat(mgr); |
| 63 | if (exitOnError && hasNewTerminalError(mgr, preExistingErrors)) { |
| 64 | return exitOnErrorReturn(mgr, preExistingErrors); |
| 65 | } |
| 66 | const progressPossible = canMakeProgress(mgr); |
| 67 | const elapsedMs = now() - startedAt; |
| 68 | if (progressPossible && elapsedMs >= maxRuntimeSec * 1000) { |
| 69 | const elapsedSec = Math.floor(elapsedMs / 1000); |
| 70 | return plannedCheckpointRestart(mgr, elapsedSec, rootMode); |
| 71 | } |
| 72 | if (!progressPossible) break; |
| 73 | const remainingRuntimeMs = maxRuntimeSec * 1000 - elapsedMs; |
| 74 | await sleepFn( |
| 75 | Math.min(SPAWN_SWEEP_INTERVAL_MS, Math.max(1, remainingRuntimeMs)) |
| 76 | ); |
| 77 | } |
| 78 | await Promise.allSettled(running); |
| 79 | |
| 80 | flagUnreachablePending(mgr); |
| 81 | printLoopSummary(mgr); |
| 82 | return computeLoopExitCode(mgr); |
| 83 | } |
| 84 | |
| 85 | function plannedCheckpointRestart( |
| 86 | mgr: AgentManager, |
no test coverage detected