/** * v2 MVP M4 — the leader orchestrator ([ref] §3, V2-MVP-PLAN.md). Composes the proven pieces into one * callable API: Planner → fanOut (M1) → diff-out (M2) → merge(b) (M3) → Coordinator push. * * Worker-env model ([ref] §8 F2(a)): preferred = FACTORY env (runner-owned lifecycle; durable suspend only * yields a complete ownedEnv on this path) + diff-out via the worker's mandatory SELF-UPLOAD last step * (`fetchDiff`, diffup.ts). Compat = the original static env the leader owns: provision up-front, pull each * branch's diff OUT of the still-alive env (pullDiff), destroy every env in a `finally`. * fanOut's runner just routes a sessionId to that worker's env-bound runner. This is the leaderless-leader * pattern (service[19]): authority = owning the integration+verification point + disjoint file-ownership. * * Deps injected → mock-tested; the real-infra wiring (E2B envs + deepseek runners + git push) lives in the * joint dogfood, which this generalizes. */ import { type RunnerLike, type WorkerReport } from "./fanout.js"; import { type ExecStreamLike } from "./diffout.js"; import { type MergeDeps, type MergeResult } from "./merge.js"; import { type TaskSpec, type VerifyConfig, type CheckpointToken, type TerminalCause, type ToolSpec, type RepairTerminal, type OracleTier, type RepairResult } from "@sema-agent/core"; /** [ref] Slice 6: drive a worker's resource-suspend auto-resume to a terminal (or bound) state. Exported * for unit tests. Loops `resume(token, slice)` WHILE the result's terminal cause is a park on a * `resource_limit` gate. Exits on: a terminal cause that is not a park, a park on any OTHER gate * (a human approval / a review — never auto-resumed), an exhausted budget (core fails the resume fast, so * the cause is `failed` with no nested park), `signal.aborted`, or `maxSlices` (a belt; core's ledger * fail-fast + per-task suspend-loop cap are the primary bounds). Returns the final result. * * 🔴 S-136(core 7.6.0 D-8):判据从「两个平面键合读」(`checkpointToken` 在场 ∧ `checkpointGate.kind`) * 换成**一条因由**({@link pausedCauseOf}) —— 这条循环从一开始就刻意**不按 status 判**(顶注原话: * 「the verify wrapper may surface a suspend as failed-with-token」),因为嵌套边界会把 park 报成 failed; * 现在那件事是形状:两条路径同一个读法,而 token 与 gate 在同一个 `PausedCause` 上,不可能只有一半。 */ export declare function autoResumeOnResource(initial: T, signal: { readonly aborted: boolean; }, resume: (token: CheckpointToken, slice: number) => Promise, maxSlices?: number): Promise; /** A planned sub-task: a worker id, its branch, disjoint file-ownership, and the agent objective. */ export interface SubtaskSpec { workerId: string; branch: string; files: string[]; spec: Omit; } /** A leader-OWNED worker: its env-bound runner + the env handle to pull its diff out + lifecycle. */ export interface ProvisionedWorker { workerId: string; sessionId: string; branch: string; /** base commit of THIS worker's env (drives `git format-patch ..` diff-out). */ baseSha: string; /** runs the worker agent on this worker's env (static `executionEnv` or a factory — [ref] F2(a)). */ runner: RunnerLike; /** Static-env mode (M2 compat): pull the worker's branch diff OUT of its (post-task, still-alive) env. */ diffEnv?: ExecStreamLike; /** Factory-env mode ([ref] F2(a), preferred when set): fetch the diff the worker SELF-UPLOADED as its * mandatory last task step (presigned MinIO object) — the env is runner-owned and already gone. */ fetchDiff?: () => Promise; /** Static-env mode only: the leader owns + destroys the env. Factory envs omit this — the runner's factory * lifecycle destroys them, and a SUSPENDED worker's paused env is thereby exempt from the leader's reap * ([ref] row4/C4: destroying a suspended worker's env = expiring its resume token). */ destroy?: () => Promise; spec: Omit; /** Optional read-effect PROBE tools for the L3 verifier (verifyWorker path). The default verifierTools = the * impl's tools filtered to effect:"read", which DROPS the env's write-effect `bash` → the verifier can't run * python3/tests → PARTIAL. Provision a read-effect exec/probe tool here so the verifier can actually verify. */ verifierTools?: ToolSpec[]; } export interface LeaderDeps { /** Plan a task into N disjoint-file sub-tasks (MVP: simple/hand-authored; adaptive replan = scale). */ plan: (task: string) => Promise; /** Provision a leader-owned worker (its env seeded with base at `branch` + an env-bound runner). */ provisionWorker: (sub: SubtaskSpec) => Promise; /** Ephemeral no-creds integration sandbox + the credentialed Coordinator push (merge(b) deps). */ provisionIntegrationSandbox: MergeDeps["provisionIntegrationSandbox"]; push: MergeDeps["push"]; /** Integration test command (the merge gate) + repo dir inside the sandbox. */ testCmd: string; /** Optional truecorrect MEASURE ([ref]): a thorough hidden-oracle command run IN the integration sandbox * after the gate passes (NOT a gate) — offloads the measure from the control plane. Result in result.merge.measure. */ measureCmd?: string; repoDir?: string; /** Diff-out repo dir inside each WORKER env (default = repoDir). */ workerRepoDir?: string; /** fan-out deadline + concurrency cap. */ timeoutMs: number; maxConcurrency: number; /** [ref] L3a — optional per-module sub-gate + bounded repair, run AFTER fan-out, BEFORE merge. For each * completed worker: judge its module (diff) vs its sub-objective; on fail, re-run that ONE worker with the * judge's feedback (bounded by maxRepairs, default 1) on its own still-alive env, then re-judge. A module that * still fails is marked `failed` (merge quarantines it). Catches per-module escapes the end-integration test * misses + restores cheap-team correctness by local repair (search [38] / N=6 all-or-nothing). Omit = off. */ moduleGate?: { judge: (sub: SubtaskSpec, diff: string) => Promise<{ ok: boolean; reason?: string; }>; repairObjective: (sub: SubtaskSpec, reason: string) => string; maxRepairs?: number; }; /** core 1.72 native L3a ([ref] §4): when set, each worker runs via `runWithVerification` (run the module + * an INDEPENDENT DECORRELATED verifier tries to break it reading the diff/workspace + a bounded fix-loop) * instead of plain fan-out. The worker runner's `models` catalog MUST carry the heterogeneous `verifierModel` * (decorrelation = deployment contract). Mutually exclusive with `moduleGate` (this IS the native version). */ verifyWorker?: VerifyConfig; /** Replan-lite ([ref] §6): bounded, DETERMINISTIC reactions — no runtime DAG surgery. When set: * worker failed → redispatch ×1 on a FRESH env (clean base; a crashed worker's half-written workspace is * never reused) → still failed = collapse-remainder-to-single; verify FAIL (repair already bounded inside * the verify loop) → collapse; budgetUsd exceeded → stop fan-out, single continues. Multi-trigger * arbitration priority (C6): suspended > budget > verify-fail > worker-failed — only the highest-priority * reaction runs. (The suspended reaction itself — cancel the others, exempt the suspended worker, surface * its token — is unconditional, not gated here.) * * 🔴 S-113 / clay [ref] ④(7.83.0):**merge-conflict 不再是一条 replan 臂**。worker 分支合不上 ⇒ 不起 * collapse 模型,`conflict` 段(分支 + 冲突文件)交给**用户**,见 {@link LeaderResult.conflict}。 */ replan?: { /** Fan-out budget (USD, sum of worker costs). Exceeded → stop fan-out, collapse to single (C5). */ budgetUsd?: number; }; /** LEADER-REPAIRLOOP-INTEGRATION §3/§10 — the OPT-IN single-agent self-repair loop (`runRepairLoop`). When * set (LEADER_REPAIR_LOOP on; the wire attaches it via `attachRepairLoopDeps`), the single-agent paths * (`collapseToSingle` `:409` and the N=1 `route==="single"` path) route their solo worker through this * instead of the plain fan-out map: GENERATE == one `runner.runTask`, GRADED by an ISOLATED grader (the * reused integration sandbox), capped at `candidate_only` (SAFE-tier never auto-accepts). Stage-2 wires the * call-sites; this is the additive dep slot. `run` returns the worker's `WorkerReport` (carrying * `repairTerminal` — §10.6) so the merge push chokepoint can hold an unconfirmed candidate. Unset ⇒ the * default path is byte-identical to today. */ repairLoop?: { /** Run the bounded repair loop over a single provisioned worker; the returned report carries `repairTerminal` * (and the candidate patch is surfaced on LeaderResult). The grader env / oracle steps / model ids are * captured by the wire (`runRepairLoopForLeader`). */ run: (solo: ProvisionedWorker, sub: SubtaskSpec) => Promise<{ report: WorkerReport; result: RepairResult; candidatePatch?: { patch: string; tier: OracleTier; reason: string; gradedHash?: string; }; }>; }; /** LEADER-REPAIRLOOP-INTEGRATION §10.3/§10.4 — the merge-path push-gate extras (the wire sets them from * `LEADER_MEASURE_GATES`, default OFF). Threaded straight into `MergeDeps` (`measureGates` / `strongOracleSeeded` * / `measureDrivesRepair`). Unset ⇒ the merge push path is byte-identical to today (the §10.6 repairTerminal * chokepoint still fires independently — it does NOT need these). */ mergeGates?: Pick; logger?: { warn?: (m: string, x?: Record) => void; info?: (m: string, x?: Record) => void; }; } export interface LeaderResult { ok: boolean; reports: WorkerReport[]; merge?: MergeResult; cancelled: boolean; /** Which route the difficulty router picked (search [43]): "single" = a 1-subtask plan, "fanout" = N. */ route?: "single" | "fanout"; /** Set when planning/provisioning failed before fan-out. */ error?: string; /** Replan-lite ([ref] §6): which trigger won arbitration + what was done. * 🔴 S-113 / [ref] ④(7.83.0):闭集删掉 `"merge-conflict"`(硬 breaking,零别名——消费方的 tsc 红即通知)。 */ replan?: { trigger: "suspended" | "budget" | "verify-fail" | "worker-failed"; redispatched?: string[]; collapsed?: string; }; /** * 🔴 S-113 / clay [ref] ④(7.83.0)——**合不上 = 交给用户**(对齐 CC 形)。merge 的 `phase:"apply"` 冲突 * 不再起 collapse 模型:`ok=false` + 本段,用户拿 `workers[].branch` + `files` 自己 `git merge` / cherry-pick * (worker 分支本就留在集成沙箱 / 远端)。run 状态走既有第三终局 `needs_human`(endpoint 的状态映射),不加 * 新状态词。**缺席何义** = 这一趟没有冲突,或对面是老 server。 */ conflict?: { /** 用户侧的合并基点 = leader 这一趟的 durable base。 */ baseSha: string; /** 有冲突标记 / `.rej` 的路径(**不含内容**)。 */ files: string[]; /** 清单被截断(探测到的冲突路径多于上限)时在场;**缺席 = 这就是全部**(对抗复审 F5)。 */ filesTruncated?: true; /** 全部 completed worker 的分支;`applied:false` = 这只 worker 的补丁没进树(冲突者本人,或排在它后面 * 还没轮到 apply 的——顺序 apply,首次失败即停)。 */ workers: Array<{ workerId: string; branch: string; applied: boolean; }>; /** `.rej` 头(经既有脱敏面,≤ 2 KiB)——给人看冲突长什么样。 */ rejHead?: string; }; /** C4: suspended workers' resume tokens, surfaced to the caller (their checkpoints + paused envs are intact — * the single-agent resume path owns them from here; orphan protection = the existing deadline reap). */ suspended?: Array<{ workerId: string; sessionId: string; }>; /** [ref] Slice 6 / task#16: the diff (uncommitted work auto-committed by the belt, then diff-out) of * workers that did NOT merge (failed / still-suspended). Their progress is otherwise lost — the merge only * integrates COMPLETED workers — so surface it here for review/cherry-pick instead of silently dropping it * (the migration-run loss: a network-failed worker's 4 commits vanished). Best-effort + non-empty only. */ salvaged?: Array<{ workerId: string; sessionId: string; patch: string; }>; /** LEADER-REPAIRLOOP-INTEGRATION §5 — the single-agent repair loop's terminal (when `LeaderDeps.repairLoop` * ran on a single-agent path). Surfaced so the HTTP status map (`endpoint.ts:75`) can distinguish "abstained, * awaiting a human" (`candidate_only`/`needs_human_oracle`/`conflict` → `needs_human`) from "broke" * (`oracle.unprotected`/`gave_up` → `failed`). Undefined ⇒ the repair loop did not run. */ repairTerminal?: RepairTerminal; /** LEADER-REPAIRLOOP-INTEGRATION §5/§10.7 — the held candidate when the repair loop resolved to a * non-auto-accepting terminal (`candidate_only` etc.). `patch` is the worker-authored diff, DELIMITED as * untrusted at the wire surface (`delimitUntrusted("CANDIDATE PATCH (untrusted worker code)", …)` — §10.7) so * it can safely reach the HTTP body + a durable-approval row a human reads; `reason` is sourced ONLY from the * core-sanitized `RepairResult.verification.findings[0]`, never the raw oracle trace. The push is HELD — a * human `/decide` accept fires it, reject discards (an unattended `candidate_only` is never a silent push and * never a silent drop — §5 invariant). */ candidatePatch?: { patch: string; tier: OracleTier; reason: string; gradedHash?: string; }; } /** * Run a task to an integrated, pushed result. Never throws — failures (plan, provision, fan-out, merge) are * captured in LeaderResult. ALWAYS destroys every provisioned worker env (leader owns the lifecycle). */ export declare function runLeaderTask(task: string, durableBaseSha: string, deps: LeaderDeps): Promise; //# sourceMappingURL=leader.d.ts.map