@sema-agent/server 7.43.0 → 7.44.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/USAGE.md +18 -0
- package/dist/adoption/plan.js +31 -192
- package/dist/adoption/quiesce.js +3 -86
- package/dist/adoption/runner.js +3 -140
- package/dist/adoption/sql.js +0 -74
- package/dist/adoption/wire.js +0 -80
- package/dist/approval-ask-machine.js +0 -75
- package/dist/approval-card.js +0 -323
- package/dist/approval-deny-reasons.js +0 -42
- package/dist/approval-hmac.js +0 -35
- package/dist/approval-reconciler.js +8 -166
- package/dist/approval.js +4 -67
- package/dist/audit.js +1 -44
- package/dist/auth-bridge.js +4 -47
- package/dist/auth-keys.js +0 -23
- package/dist/bake-runner/main.js +4 -65
- package/dist/bake-runner/protocol.js +3 -58
- package/dist/bake-runner/runner.js +5 -91
- package/dist/bench/l8/artifact.js +2 -61
- package/dist/bench/l8/escape.js +0 -25
- package/dist/bench/l8/index.js +0 -14
- package/dist/bench/l8/probes.js +0 -60
- package/dist/bench/l8/run-probes.js +2 -45
- package/dist/bench/s1/arms.js +14 -189
- package/dist/bench/s1/live-deps.js +11 -253
- package/dist/bench/s1/oracle.js +1 -36
- package/dist/bench/s1/repair-oracle-adapter.js +0 -30
- package/dist/bench/s1/reviewer.js +0 -34
- package/dist/bench/s1/row.js +0 -62
- package/dist/bench/s1/run-firm.js +4 -70
- package/dist/bench/s1/runner-ctx.js +0 -40
- package/dist/bench/s1/tasks.js +0 -115
- package/dist/boot/adoption.js +0 -20
- package/dist/boot/budget-tracing.js +3 -55
- package/dist/boot/config-center.js +43 -551
- package/dist/boot/coordinators.js +1 -69
- package/dist/boot/crash-last.js +0 -18
- package/dist/boot/deferred-sandbox-path-env.js +3 -119
- package/dist/boot/execution-env.js +5 -171
- package/dist/boot/governance-seams.js +0 -92
- package/dist/boot/leader.js +0 -69
- package/dist/boot/limit-sync.js +0 -2
- package/dist/boot/memory-boundary.js +3 -91
- package/dist/boot/org-memory.js +1 -25
- package/dist/boot/parked-revive-gate.js +0 -150
- package/dist/boot/permission-rules-audit.js +1 -56
- package/dist/boot/reapers.js +15 -273
- package/dist/boot/resolve-spec.js +9 -768
- package/dist/boot/retention-lane.js +4 -87
- package/dist/boot/runner-deps.js +3 -297
- package/dist/boot/runtime-caps.js +0 -40
- package/dist/boot/session-faces.js +2 -133
- package/dist/boot/shutdown.js +9 -83
- package/dist/boot/side-query-lane.js +2 -137
- package/dist/boot/stores.js +15 -215
- package/dist/boot/task-list-lane.js +0 -18
- package/dist/boot/webfetch-summarize-lane.js +0 -47
- package/dist/boot/workflow-orchestration.js +3 -99
- package/dist/boot-reclaim.js +0 -20
- package/dist/bounded-session-map.js +0 -19
- package/dist/brain.js +2 -139
- package/dist/budget.js +9 -244
- package/dist/capabilities/builtin-tools.js +0 -3
- package/dist/capabilities/center-plugins.js +6 -51
- package/dist/capabilities/center-prompts.js +6 -71
- package/dist/capabilities/code-review-council.js +7 -40
- package/dist/capabilities/collab-workflows.js +1 -44
- package/dist/capabilities/hands-lane.js +0 -65
- package/dist/capabilities/memory-notice.js +0 -70
- package/dist/capabilities/prompt.js +0 -14
- package/dist/capabilities/prompts/code-review.js +0 -14
- package/dist/capabilities/prompts/identity.js +0 -6
- package/dist/capabilities/prompts/team.js +0 -4
- package/dist/capabilities/repo-tools.js +0 -34
- package/dist/capabilities/sandbox-file-send.js +3 -92
- package/dist/capabilities/scenarios.d.ts +0 -1
- package/dist/capabilities/scenarios.js +18 -279
- package/dist/capabilities/select-environment-tool.js +0 -32
- package/dist/capabilities/send-user-file-tool.js +2 -66
- package/dist/capabilities/skills.d.ts +1 -2
- package/dist/capabilities/skills.js +3 -21
- package/dist/capabilities/team.d.ts +3 -11
- package/dist/capabilities/team.js +3 -51
- package/dist/capabilities/tool-defer.js +0 -3
- package/dist/config-center/apply-effective.js +15 -330
- package/dist/config-center/apply-ledger.js +5 -38
- package/dist/config-center/facade.js +0 -41
- package/dist/config-center/hot-keys-registry.js +0 -20
- package/dist/config-center/http-client.js +2 -125
- package/dist/config-center/mcp-revocation.js +2 -34
- package/dist/config-center/read-face.js +0 -59
- package/dist/config-center/restart-signal.js +1 -85
- package/dist/config-center/skills-mcp.d.ts +1 -1
- package/dist/config-center/skills-mcp.js +9 -76
- package/dist/config-center/stage-limits.js +8 -35
- package/dist/config-invariants.js +0 -16
- package/dist/config-lkg.js +0 -42
- package/dist/config-provider.js +3 -186
- package/dist/config-types.js +0 -5
- package/dist/config.js +107 -1145
- package/dist/degenerate-instrument.js +3 -67
- package/dist/deployment-governance.js +0 -124
- package/dist/digest-form.js +0 -12
- package/dist/elicitation.js +3 -86
- package/dist/env-facts.js +7 -75
- package/dist/fleet/fleet-bus.js +34 -507
- package/dist/fleet/fleet-reconciler.js +9 -149
- package/dist/fleet/fleet-terminal-window.js +10 -178
- package/dist/fleet/subagent-tail-bus.js +3 -72
- package/dist/fleet-client.js +10 -70
- package/dist/fleet-lease.js +5 -79
- package/dist/git-api-kind.js +0 -3
- package/dist/governance-ask-marks.js +2 -78
- package/dist/hooks/branch-transcript.js +0 -74
- package/dist/hooks/cc-agent-hook-prompt.js +0 -29
- package/dist/hooks/cc-stop-prompt.js +1 -46
- package/dist/hooks/hook-llm.js +1 -53
- package/dist/hooks/hook-runner.js +20 -414
- package/dist/http/active-run-conflict.js +4 -129
- package/dist/http/cursor-fingerprint.d.ts +5 -0
- package/dist/http/cursor-fingerprint.js +5 -0
- package/dist/http/idempotency.js +0 -37
- package/dist/http/principal-gate.js +3 -40
- package/dist/http/route-ctx.js +0 -9
- package/dist/http/routes/a2a-serve.js +7 -319
- package/dist/http/routes/admin-config-refresh.js +0 -4
- package/dist/http/routes/admin-drain.js +0 -10
- package/dist/http/routes/adoption.js +1 -29
- package/dist/http/routes/agents-roster.js +1 -47
- package/dist/http/routes/approvals-assistant.js +32 -395
- package/dist/http/routes/attachments.js +4 -22
- package/dist/http/routes/capabilities.js +3 -420
- package/dist/http/routes/diagnostics.js +1 -81
- package/dist/http/routes/fleet.js +9 -185
- package/dist/http/routes/images.js +14 -239
- package/dist/http/routes/leader.js +0 -13
- package/dist/http/routes/memory-bundle.js +1 -61
- package/dist/http/routes/memory-policy.js +9 -99
- package/dist/http/routes/notify-wake.js +3 -37
- package/dist/http/routes/observability.js +2 -19
- package/dist/http/routes/retention-ops.js +2 -34
- package/dist/http/routes/rules.js +1 -93
- package/dist/http/routes/runs.js +64 -873
- package/dist/http/routes/session-sync.js +19 -258
- package/dist/http/routes/sessions-list.js +17 -43
- package/dist/http/routes/sessions.js +34 -226
- package/dist/http/routes/shared-memory.js +5 -36
- package/dist/http/routes/side-query.js +1 -87
- package/dist/http/routes/tasks.js +52 -722
- package/dist/http/routes/trace-usage.js +32 -195
- package/dist/http/routes/workflows.js +19 -195
- package/dist/http/run-meta.js +0 -6
- package/dist/http/send.js +0 -32
- package/dist/http/server.js +92 -1597
- package/dist/http/sse-lifecycle.js +2 -13
- package/dist/http/sse-log.js +3 -48
- package/dist/http/tar.js +5 -21
- package/dist/http/verify-rounds.js +0 -5
- package/dist/http/wire-gate.js +0 -9
- package/dist/http/workspace-content.js +0 -10
- package/dist/images/bake-validate.js +1 -70
- package/dist/images/manifest.js +1 -6
- package/dist/index.js +0 -21
- package/dist/key-resolver.js +2 -17
- package/dist/leader/diffout.js +1 -20
- package/dist/leader/diffup.js +0 -47
- package/dist/leader/endpoint.js +2 -57
- package/dist/leader/fanout.js +3 -45
- package/dist/leader/grader-env-factory.js +3 -72
- package/dist/leader/leader.js +5 -156
- package/dist/leader/merge.js +8 -115
- package/dist/leader/planner.js +3 -54
- package/dist/leader/repair-oracle.js +1 -60
- package/dist/leader/repair-wire.js +2 -79
- package/dist/leader/wire.js +8 -307
- package/dist/lsp/e2b-bridge.js +4 -64
- package/dist/lsp/e2b-manager.js +6 -94
- package/dist/lsp/lsp-frames.js +0 -12
- package/dist/lsp/manager.js +4 -96
- package/dist/lsp/ws-transport.js +5 -55
- package/dist/lsp-evict.js +1 -15
- package/dist/main.js +57 -765
- package/dist/memory-bundle-engine.js +0 -55
- package/dist/memory-export.js +0 -4
- package/dist/memory-posture.js +1 -15
- package/dist/memory-scope.js +11 -148
- package/dist/memory-sync-client.js +2 -44
- package/dist/memory-sync.js +1 -80
- package/dist/model-select.js +3 -80
- package/dist/observability/cost-quota.js +1 -17
- package/dist/observability/cost-taxonomy.js +0 -34
- package/dist/observability/fail-open.js +7 -86
- package/dist/observability/logger.js +0 -6
- package/dist/observability/metrics.js +0 -94
- package/dist/observability/otel-exporter.js +3 -13
- package/dist/observability/principal-context.js +0 -9
- package/dist/observability/prompt-manifest.js +1 -37
- package/dist/observability/rate-limit.js +0 -4
- package/dist/observability/secret-env-scrub.js +2 -56
- package/dist/observability/tool-trace.js +1 -70
- package/dist/orchestration/hardened-vm-runner.js +4 -118
- package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
- package/dist/orchestration/hardened-vm-worker.js +0 -27
- package/dist/orchestration/subagent-steer.js +1 -45
- package/dist/orchestration/workflow-agent-steer.js +1 -80
- package/dist/orchestration/workflow-completion-inbox.js +32 -285
- package/dist/orchestration/workflow-notify-journal.js +16 -259
- package/dist/org-memory-admission.js +3 -47
- package/dist/parent-watch.js +2 -48
- package/dist/parked-decide.js +1 -109
- package/dist/per-task-image.js +0 -57
- package/dist/plan-cache-probe.js +3 -27
- package/dist/plugins/adoption-log-sql.js +2 -119
- package/dist/plugins/approval-ask-store-memory.js +3 -38
- package/dist/plugins/approval-ask-store-sql.js +6 -188
- package/dist/plugins/approval-exemption-store.js +2 -28
- package/dist/plugins/background-agent-store-sql.js +4 -105
- package/dist/plugins/background-shell-support.js +14 -122
- package/dist/plugins/blob-backend.js +6 -169
- package/dist/plugins/breaker-state-sql.js +8 -46
- package/dist/plugins/caching-session-store.js +4 -106
- package/dist/plugins/checkpoint-store-sql.js +18 -505
- package/dist/plugins/e2b-orphan-reclaim.js +0 -45
- package/dist/plugins/file-outcome-sink.js +0 -9
- package/dist/plugins/file-resume-anchor-store.js +4 -43
- package/dist/plugins/file-run-store.js +26 -364
- package/dist/plugins/file-snapshot-store-sql.js +10 -181
- package/dist/plugins/fork-routing-session-store.js +8 -111
- package/dist/plugins/host-platform.js +2 -91
- package/dist/plugins/image-bake-store-sql.js +7 -250
- package/dist/plugins/image-index-sql.js +4 -123
- package/dist/plugins/k8s-bg-scripts.js +4 -88
- package/dist/plugins/k8s-exec-protocol.js +0 -41
- package/dist/plugins/leader-run-store-sql.js +0 -101
- package/dist/plugins/local-checkpoint-store.js +8 -128
- package/dist/plugins/local-session-store.js +34 -296
- package/dist/plugins/local-task-attachment-store.js +2 -16
- package/dist/plugins/mailbox-store-sql.js +8 -65
- package/dist/plugins/memory-embedder-fingerprint.js +5 -166
- package/dist/plugins/memory-embedder.js +4 -70
- package/dist/plugins/memory-engine-pg.js +6 -170
- package/dist/plugins/memory-engine-tidb.js +7 -155
- package/dist/plugins/memory-engine-vector-util.js +0 -10
- package/dist/plugins/memory-key-guards.js +0 -34
- package/dist/plugins/memory-origin-law.js +0 -187
- package/dist/plugins/memory-resume-anchor-store.js +0 -17
- package/dist/plugins/memory-run-store.js +12 -89
- package/dist/plugins/memory-session-policy-store.js +0 -17
- package/dist/plugins/memory-sync-store-pg.js +4 -49
- package/dist/plugins/memory-sync-store-tidb.js +3 -35
- package/dist/plugins/outcome-ledger-sql.js +3 -97
- package/dist/plugins/permission-rule-store-file.js +5 -133
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +50 -307
- package/dist/plugins/pg-cost-quota.js +0 -7
- package/dist/plugins/pg-pool.js +0 -92
- package/dist/plugins/pg-rate-limiter.js +2 -13
- package/dist/plugins/pg-safe-json.js +4 -40
- package/dist/plugins/pg-session-storage.js +25 -189
- package/dist/plugins/posix-shell-fs.js +1 -31
- package/dist/plugins/remote-env-adb.js +12 -101
- package/dist/plugins/remote-env-e2b.js +40 -371
- package/dist/plugins/remote-env-file-error.js +0 -37
- package/dist/plugins/remote-env-host.js +63 -483
- package/dist/plugins/remote-env-k8s.js +32 -326
- package/dist/plugins/remote-env-local-docker.js +24 -157
- package/dist/plugins/remote-env-ssh.js +20 -128
- package/dist/plugins/remote-scratchpad.js +2 -32
- package/dist/plugins/remote-shell.js +1 -32
- package/dist/plugins/resume-anchor-store-sql.js +0 -11
- package/dist/plugins/retention-lane-store-sql.js +0 -108
- package/dist/plugins/retention-store-sql.js +4 -383
- package/dist/plugins/roster-store-sql.js +0 -55
- package/dist/plugins/run-store-sql.js +14 -251
- package/dist/plugins/s3-presign.js +2 -49
- package/dist/plugins/scheduler-support.js +3 -80
- package/dist/plugins/send-file-ledger.js +4 -53
- package/dist/plugins/send-user-file.js +4 -94
- package/dist/plugins/session-placement.js +1 -89
- package/dist/plugins/session-policy-store-sql.js +3 -81
- package/dist/plugins/session-store.js +0 -57
- package/dist/plugins/shared-memory-store-sql.js +4 -167
- package/dist/plugins/sql-driver.js +0 -17
- package/dist/plugins/sql-errors.js +0 -7
- package/dist/plugins/sql-escape.js +0 -8
- package/dist/plugins/sql-row-helpers.js +0 -25
- package/dist/plugins/store-backend.js +42 -222
- package/dist/plugins/store-contracts.js +2 -46
- package/dist/plugins/task-attachment-store.js +3 -49
- package/dist/plugins/task-list-store-sql.js +0 -79
- package/dist/plugins/tidb-cost-quota.js +1 -4
- package/dist/plugins/tidb-pool.js +1 -207
- package/dist/plugins/tidb-rate-limiter.js +3 -9
- package/dist/plugins/tidb-session-storage.js +4 -70
- package/dist/plugins/tidb-session-store.js +19 -341
- package/dist/plugins/tool-result-store-sql.js +4 -179
- package/dist/plugins/usage-window-store-sql.js +0 -8
- package/dist/plugins/web-search.js +10 -132
- package/dist/plugins/workflow-journal-store-sql.js +2 -58
- package/dist/plugins/workflow-run-store-sql.js +6 -91
- package/dist/plugins/worktree-isolation.js +6 -126
- package/dist/plugins/write-behind-counter.js +16 -75
- package/dist/principal-jwt.js +5 -60
- package/dist/project-memory.js +15 -146
- package/dist/prompts-domain-validate.js +1 -51
- package/dist/question.js +2 -111
- package/dist/resource-suspend.js +0 -18
- package/dist/router/route-orchestration.js +0 -77
- package/dist/rules-consent.d.ts +44 -5
- package/dist/rules-consent.js +75 -215
- package/dist/run-local.js +14 -381
- package/dist/runs.js +29 -617
- package/dist/runtime-caps-resolver.js +6 -132
- package/dist/runtime-governance.js +1 -232
- package/dist/sandbox-pkg-source.js +0 -37
- package/dist/sealed-key.js +3 -68
- package/dist/security.js +6 -291
- package/dist/session-leaf-bus.js +0 -32
- package/dist/session-sync-content.js +1 -69
- package/dist/session-sync-kernel.js +3 -60
- package/dist/session-sync.js +3 -66
- package/dist/session-titler.js +7 -41
- package/dist/session-watch.js +9 -73
- package/dist/shared-memory-scope-authorizer.js +0 -18
- package/dist/sighup-idle.js +1 -12
- package/dist/spec-fields.js +4 -143
- package/dist/store-live-probe.js +3 -49
- package/dist/task-a2a.js +1 -126
- package/dist/task-cwd.js +1 -103
- package/dist/task-mcp.js +1 -89
- package/dist/task-settings.js +10 -300
- package/dist/task-workflow.js +8 -75
- package/dist/tool-approval.d.ts +28 -1
- package/dist/tool-approval.js +70 -1430
- package/dist/trace/artifacts.js +5 -20
- package/dist/trace/engine-notice-wire.js +6 -138
- package/dist/trace/ledger-sink.js +7 -96
- package/dist/trace/project.js +4 -448
- package/dist/trace/redact.js +9 -77
- package/dist/turn-activity.js +1 -27
- package/dist/usage-analytics.js +4 -34
- package/dist/wall-clock-jump-guard.js +1 -68
- package/package.json +3 -3
- package/dist/capabilities/scenario-alias.d.ts +0 -27
- package/dist/capabilities/scenario-alias.js +0 -61
package/dist/leader/wire.js
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v2 MVP M4 — leader endpoint wiring (V2-MVP-PLAN.md). `createLeaderRunner` turns a LeaderRequestBody into a
|
|
3
|
-
* `runLeaderTask` invocation with REAL deps: per-worker E2B envs (leader-owned, static so they outlive the
|
|
4
|
-
* task for diff-out) + a real ephemeral E2B integration sandbox + the Coordinator's credentialed
|
|
5
|
-
* `git push --force-with-lease`. This is the productization of the joint dogfood (which validated the exact
|
|
6
|
-
* logic on real infra) — gated behind LEADER_ENABLED (default off).
|
|
7
|
-
*
|
|
8
|
-
* 🔴 DEPLOY REQUIREMENTS (document — not satisfied by the default bun-slim image): the control plane (service
|
|
9
|
-
* host) needs **git** on PATH for the Coordinator clone/apply/push, plus a reachable durable remote +
|
|
10
|
-
* write creds (LEADER_GIT_TOKEN). E2B workers need outbound net only if the base repo is cloned over the
|
|
11
|
-
* network; the MVP seeds the base from the Coordinator (no worker→remote net). MVP Planner = caller-provided
|
|
12
|
-
* `subtasks` (an LLM DAG-planner is a follow-up).
|
|
13
|
-
*/
|
|
14
1
|
import { parseNumOrFail } from "../config.js";
|
|
15
2
|
import { execFileSync } from "node:child_process";
|
|
16
3
|
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
@@ -26,12 +13,6 @@ import { runLeaderTask } from "./leader.js";
|
|
|
26
13
|
import { attachRepairLoopDeps } from "./repair-wire.js";
|
|
27
14
|
import { routePlanWithFallback, validateSubtasks } from "./planner.js";
|
|
28
15
|
import { createEngineNoticeSeat } from "../boot/runner-deps.js";
|
|
29
|
-
/** The single-agent `runRepairLoop` is now WIRED LIVE (the canary-enable slice): when `LEADER_REPAIR_LOOP=true`
|
|
30
|
-
* the wire provisions a DEDICATED oracle-seeded grader sandbox per solo worker and attaches a real async
|
|
31
|
-
* `resolve` to `attachRepairLoopDeps` (below). With this `true`, flipping `LEADER_REPAIR_LOOP` engages BOTH the
|
|
32
|
-
* repair loop AND the §10.5 resource-suspend mutual exclusion (a repair-owned worker never resource-suspends).
|
|
33
|
-
* Still OFF-by-default — with the flag off, `resolve` is undefined → `attachRepairLoopDeps` is a no-op → the
|
|
34
|
-
* default single-agent path is byte-identical to today. */
|
|
35
16
|
const REPAIR_LOOP_WIRED = true;
|
|
36
17
|
const sh = (env) => async (cmd) => {
|
|
37
18
|
const r = await env.exec(`bash -lc ${JSON.stringify(cmd)}`);
|
|
@@ -49,95 +30,36 @@ const asIntegrationEnv = (env) => ({
|
|
|
49
30
|
writeFile: async (p, c) => { const r = await env.writeFile(p, c); if (!r.ok)
|
|
50
31
|
throw r.error; },
|
|
51
32
|
});
|
|
52
|
-
/**
|
|
53
|
-
* Resume-safe worker staging (the design/68 F2(a) factory-env path). Runs adapter-side before the agent's
|
|
54
|
-
* first workspace-touching call: seed the base repo at the worker branch + plant the upload script (so the
|
|
55
|
-
* presigned URL never passes through the model).
|
|
56
|
-
*
|
|
57
|
-
* 🔴 BL-1 fix (design/74 Slice 6): on a RESOURCE-SUSPEND RESUME core re-invokes the `executionEnvFactory`,
|
|
58
|
-
* producing a FRESH `withStaging` wrapper (`staged=undefined`) whose `stage()` runs AGAIN — AFTER core's
|
|
59
|
-
* `resumeVM()` has already untar'd the snapshot onto the fresh pod (`resumeVM` is not a STAGED_METHOD, so it
|
|
60
|
-
* is NOT intercepted by the staging proxy; the agent's first `exec` is what triggers `stage()`, lazily, and
|
|
61
|
-
* by then the snapshot is restored). The old `stage()` ran the seed UNCONDITIONALLY:
|
|
62
|
-
* `${seedCmd}; cd ${repo}; git checkout -q -b ${branch}`
|
|
63
|
-
* and `seedCmd` begins with `rm -rf /workspace/repo` — so it WIPED the just-restored snapshot and re-seeded at
|
|
64
|
-
* `baseSha`, then created the worker branch at base. The forensic proof (leaderRunId 019ecda7): the suspend
|
|
65
|
-
* snapshot object DID contain the worker's commit (`git log` in the S3 tarball = the real progress), yet every
|
|
66
|
-
* resumed slice's `git log` showed only the base commit — the worker rebuilt destroyed work for ~47 min. THIS
|
|
67
|
-
* (a staging re-seed clobbering a correctly-restored snapshot) is the dominant cause of failed make-real runs.
|
|
68
|
-
*
|
|
69
|
-
* The fix = a RESTORED-SNAPSHOT SENTINEL that no-ops staging on resume. The robust signal is the upload-script
|
|
70
|
-
* MARKER FILE (`uploadScriptPath`): staging itself plants it, so it is present in EVERY restored snapshot
|
|
71
|
-
* (verified: it survives in both a base-state in-progress snapshot AND a post-commit one) and ABSENT on a
|
|
72
|
-
* genuinely fresh pod — and it is INDEPENDENT of git/commit state. That last point matters: a worker can
|
|
73
|
-
* suspend with work in progress but no commit yet (HEAD still == baseSha), so a "HEAD advanced past baseSha"
|
|
74
|
-
* probe alone would WRONGLY re-seed+clobber that valid snapshot. The marker captures "staging already ran here"
|
|
75
|
-
* regardless of whether a commit landed. We additionally accept the worker branch already existing as a
|
|
76
|
-
* secondary restored-snapshot signal (belt-and-suspenders for the same git-state-independent reason).
|
|
77
|
-
*
|
|
78
|
-
* A FRESH pod has neither marker nor branch → full staging runs. A probe transport failure falls through to
|
|
79
|
-
* full staging too (fails honestly via `sh()`, never silently skips). `branch`/paths are `validateSubtasks`-
|
|
80
|
-
* clean / deployment config and are JSON-quoted under `bash -lc` regardless. Exported for the regression test.
|
|
81
|
-
*/
|
|
82
33
|
export async function stageWorkerEnv(env, opts) {
|
|
83
34
|
const { seedCmd, repo, branch, uploadScriptPath, uploadScript } = opts;
|
|
84
|
-
// Restored-snapshot sentinel (non-throwing exec — we need the exit code, not an exception). Two independent
|
|
85
|
-
// signals, EITHER of which proves "staging already ran here, this is a resumed snapshot, do NOT re-seed":
|
|
86
|
-
// (1) the upload-script marker file is present — the definitive signal (planted by staging, git-state
|
|
87
|
-
// independent, so it covers an in-progress-but-uncommitted snapshot where HEAD is still at base); and
|
|
88
|
-
// (2) the worker branch already exists — secondary, also git-state independent.
|
|
89
|
-
// Both probed in ONE round-trip; exit 0 ⇒ restored snapshot ⇒ NO-OP (never `rm -rf` the restored workspace).
|
|
90
|
-
// A transport failure ({ok:false}) falls through to full staging (honest fail via sh()), never silent-skips.
|
|
91
35
|
const probe = await env.exec(`bash -lc ${JSON.stringify(`if [ -f ${uploadScriptPath} ]; then exit 0; fi; ` +
|
|
92
36
|
`cd ${repo} 2>/dev/null && git rev-parse --verify --quiet refs/heads/${branch} >/dev/null && exit 0; ` +
|
|
93
37
|
`exit 1`)}`);
|
|
94
38
|
if (probe.ok && probe.value.exitCode === 0)
|
|
95
|
-
return;
|
|
39
|
+
return;
|
|
96
40
|
await sh(env)(`${seedCmd}; cd ${repo}; git checkout -q -b ${branch}`);
|
|
97
41
|
const wr = await env.writeFile(uploadScriptPath, uploadScript);
|
|
98
42
|
if (!wr.ok)
|
|
99
43
|
throw new Error(`upload-script staging failed: ${wr.error.message}`);
|
|
100
44
|
}
|
|
101
|
-
/** Build the `runLeader(body)` the HTTP endpoint invokes. Constructs real per-task deps + runs the leader. */
|
|
102
|
-
// 🔴 review-batch(重构池②,2026-08-01):`parseNumOrFail` alone still lets a NEGATIVE value through — a negative
|
|
103
|
-
// number is JS-truthy, so every `parseNumOrFail(...) || default` chain in this file passed it straight through
|
|
104
|
-
// instead of falling back (LEADER_WORKER_MAX_TURNS=-5 ⇒ workerMaxTurns=-5, unchanged by any later `|| default`).
|
|
105
|
-
// This is the one extra domain bound (A2: illegal input INCLUDING negatives must fail loud) layered on the same
|
|
106
|
-
// shared parse primitive — not a second parser. `undefined` (unset) stays NaN (`NaN < 0` is false) so the
|
|
107
|
-
// existing `|| default` fallback chains are untouched; only "non-numeric" and "negative" newly throw.
|
|
108
45
|
function parseNumOrFailNonNegative(name, raw) {
|
|
109
46
|
const n = parseNumOrFail(name, raw);
|
|
110
47
|
if (n < 0)
|
|
111
48
|
throw new Error(`env ${name}=${n} must not be negative`);
|
|
112
49
|
return n;
|
|
113
50
|
}
|
|
114
|
-
/**
|
|
115
|
-
* Per-leader-run resource bounds derived from env (2026-06-14). Exported for unit tests. The worker sandbox
|
|
116
|
-
* lifetime (BUG2), the worker's diff-upload presigned-URL TTL (BUG1), and the worker spec's triple bound
|
|
117
|
-
* (maxTurns/timeoutSec/maxCostUsd, #15) ALL track one `leaderTimeoutMs` — a hardcoded 10min (sandbox) or 1h
|
|
118
|
-
* (URL) silently killed long autonomous builds or expired the diff upload mid-run. core's turn cap is now a
|
|
119
|
-
* high net (10000), so the leader gives each worker an explicit triple bound; a sub-task's own limits override.
|
|
120
|
-
*/
|
|
121
|
-
// 🔴 2026-08-01 gap-sweep:四个旋钮原为 `Number(x) || default` —— 非法值(`"20usd"`/`"1800000ms"`)
|
|
122
|
-
// 得 NaN 后**静默落到慷慨的默认值**,方向是**松绑**:运维想把 worker 预算从 $100 收到 $20,实际拿到
|
|
123
|
-
// $100(5 倍);想把寿命从 24h 收到 30min,实际拿到 24h,而 leaderTimeoutMs 还级联 presignTtlSec
|
|
124
|
-
// ⇒ diff 上传预签名 URL 的暴露窗口一起放大。改走与 numEnv 同源的 parseNumOrFail:非法值当场报错。
|
|
125
|
-
// 刻意**零行为变更**:`|| default` 保留,所以未设与显式 0 的既有语义一个没动。
|
|
126
51
|
export function leaderResourceConfig(env = process.env) {
|
|
127
|
-
const leaderTimeoutMs = Math.max(600_000, Math.floor(parseNumOrFailNonNegative("LEADER_TIMEOUT_MS", env.LEADER_TIMEOUT_MS) || 0) || 86_400_000);
|
|
52
|
+
const leaderTimeoutMs = Math.max(600_000, Math.floor(parseNumOrFailNonNegative("LEADER_TIMEOUT_MS", env.LEADER_TIMEOUT_MS) || 0) || 86_400_000);
|
|
128
53
|
const workerMaxTurns = Math.floor(parseNumOrFailNonNegative("LEADER_WORKER_MAX_TURNS", env.LEADER_WORKER_MAX_TURNS) || 0) || 10_000;
|
|
129
54
|
const workerBudgetUsd = (parseNumOrFailNonNegative("LEADER_WORKER_BUDGET_USD", env.LEADER_WORKER_BUDGET_USD) || 0) || 100;
|
|
130
55
|
const resourceSuspendOn = String(env.LEADER_RESOURCE_SUSPEND ?? "").toLowerCase() === "true";
|
|
131
|
-
// Per-slice window: a fraction of the total so a worker suspends+resumes several times across its budget
|
|
132
|
-
// (default = total/4, min $1). Smaller window ⇒ more, shorter slices ⇒ progress saved more often.
|
|
133
56
|
const sliceMaxCostUsd = (parseNumOrFailNonNegative("LEADER_WORKER_SLICE_BUDGET_USD", env.LEADER_WORKER_SLICE_BUDGET_USD) || 0) || Math.max(1, workerBudgetUsd / 4);
|
|
134
57
|
return {
|
|
135
58
|
leaderTimeoutMs,
|
|
136
59
|
workerMaxTurns,
|
|
137
60
|
workerBudgetUsd,
|
|
138
|
-
// core 5.8.0:预算四键全入 limits(顶层 maxCostUsd 删);walltime 原生毫秒不再除
|
|
139
61
|
workerLimits: { limits: { maxTurns: workerMaxTurns, maxWalltimeMs: leaderTimeoutMs, maxCostUsd: workerBudgetUsd } },
|
|
140
|
-
presignTtlSec: Math.ceil(leaderTimeoutMs / 1000) + 600,
|
|
62
|
+
presignTtlSec: Math.ceil(leaderTimeoutMs / 1000) + 600,
|
|
141
63
|
...(resourceSuspendOn
|
|
142
64
|
? {
|
|
143
65
|
resourceSuspend: {
|
|
@@ -149,12 +71,6 @@ export function leaderResourceConfig(env = process.env) {
|
|
|
149
71
|
: {}),
|
|
150
72
|
};
|
|
151
73
|
}
|
|
152
|
-
// 🔴 review-batch(重构池②,2026-08-01): every knob below used to be a bare `Number(process.env.X ?? default)`
|
|
153
|
-
// with ZERO fail-loud guard — worst case was `LEADER_BUDGET_USD="20usd"` silently becoming `budgetUsd: NaN`,
|
|
154
|
-
// which makes the replan-lite budget gate's `spent > budgetUsd` comparison permanently `false` (the whole
|
|
155
|
-
// gate goes dark, not "loose" — `NaN` compares false in every direction). Converged onto the same
|
|
156
|
-
// `parseNumOrFailNonNegative` primitive leaderResourceConfig uses: non-numeric and negative now throw at
|
|
157
|
-
// startup; `undefined`/explicit-`0` fallback semantics are byte-identical to before.
|
|
158
74
|
export function leaderLoopConfig(cfg, env = process.env) {
|
|
159
75
|
const repairRounds = Math.max(0, Math.floor(env.LEADER_REPAIR_ROUNDS !== undefined
|
|
160
76
|
? parseNumOrFailNonNegative("LEADER_REPAIR_ROUNDS", env.LEADER_REPAIR_ROUNDS)
|
|
@@ -163,12 +79,6 @@ export function leaderLoopConfig(cfg, env = process.env) {
|
|
|
163
79
|
const conflictRounds = Math.max(0, Math.floor(env.LEADER_CONFLICT_ROUNDS !== undefined
|
|
164
80
|
? parseNumOrFailNonNegative("LEADER_CONFLICT_ROUNDS", env.LEADER_CONFLICT_ROUNDS)
|
|
165
81
|
: (cfg.conflictRounds ?? 0)) || 0);
|
|
166
|
-
// ── LEADER-REPAIRLOOP-INTEGRATION §10 env knobs (FRESH hunk, §10.8 — mirrors LEADER_REPAIR_ROUNDS above) ──
|
|
167
|
-
// Every flag default-OFF so the whole feature is inert until flipped on a canary (the default path is
|
|
168
|
-
// byte-identical to today). `LEADER_REPAIR_LOOP` is the MASTER (gates the single-agent `runRepairLoop` dep);
|
|
169
|
-
// `LEADER_MEASURE_GATES` gates the merge push-hold-on-signal + measure-drives-repair (§10.3/§10.4);
|
|
170
|
-
// `LEADER_REPAIR_LOOP_ATTEMPTS` is the in-loop attempt ceiling (validated 2-3, default 2 — design/78 §5);
|
|
171
|
-
// `LEADER_ORACLE_FLAKY_K` is the flaky-settle re-isolation count (default 2 — §10.1 / repair-oracle.ts).
|
|
172
82
|
const repairLoopOn = String(env.LEADER_REPAIR_LOOP ?? "").toLowerCase() === "true";
|
|
173
83
|
const measureGatesOn = String(env.LEADER_MEASURE_GATES ?? "").toLowerCase() === "true";
|
|
174
84
|
const repairLoopAttempts = Math.min(3, Math.max(2, Math.floor(parseNumOrFailNonNegative("LEADER_REPAIR_LOOP_ATTEMPTS", env.LEADER_REPAIR_LOOP_ATTEMPTS ?? "2")) || 2));
|
|
@@ -178,26 +88,11 @@ export function leaderLoopConfig(cfg, env = process.env) {
|
|
|
178
88
|
...(env.LEADER_BUDGET_USD ? { replanBudgetUsd: parseNumOrFailNonNegative("LEADER_BUDGET_USD", env.LEADER_BUDGET_USD) } : {}),
|
|
179
89
|
};
|
|
180
90
|
}
|
|
181
|
-
// 鲁棒性批5 A4(2026-08-05):Coordinator 的六条 execFileSync(clone/config×2/am/ls-remote/push×2)此前零超时。
|
|
182
|
-
// `execFileSync` 是**同步**调用——它挂起的是整个 Node 事件循环,不只是这一次 leader 跑;`durableRemote`
|
|
183
|
-
// 是运维配的、可能半开/不可达的远端(DNS 黑洞、TCP SYN 丢、对端 git-upload-pack 卡死),一旦挂住,这一个
|
|
184
|
-
// 副本上**所有并发请求**一起冻结,直到操作系统层 TCP 超时(可能几十分钟)才会松绑。
|
|
185
|
-
// 上界两档:clone/push/ls-remote 是网络往返(仓可能较大,给 120s);config/am 是纯本地操作(不该合法挂起,
|
|
186
|
-
// 但损坏的 object store / 文件锁可能卡住 `git am`),给 30s——同 fleet-client.ts `FLEET_CLIENT_FETCH_TIMEOUT_MS`
|
|
187
|
-
// 判据的普适形(操作分类决定上界,不是一刀切)。
|
|
188
91
|
export const LEADER_PUSH_NETWORK_TIMEOUT_MS = 120_000;
|
|
189
92
|
export const LEADER_PUSH_LOCAL_TIMEOUT_MS = 30_000;
|
|
190
|
-
/** Coordinator(control plane,持有推送凭据)的 git push 闭包——从 `createLeaderRunner` 抽出为独立工厂,
|
|
191
|
-
* 仅依赖 durableRemote/targetRef/git 身份(不依赖 wire 内其余状态),因此可以脱离完整的
|
|
192
|
-
* plan→provision→fan-out→merge 管线单测(那条管线需要真 brain + E2B/k8s env)。行为与抽出前逐字相同,
|
|
193
|
-
* 唯一新增是六条 execFileSync 各自的 `timeout`。 */
|
|
194
93
|
export function createCoordinatorPush(durableRemote, targetRef, ident) {
|
|
195
94
|
return async (integratedPatch, baseSha) => {
|
|
196
|
-
// Coordinator (control plane, creds): clone durable remote, apply integrated series, force-with-lease push.
|
|
197
|
-
// 🔴 requires `git` on the service host PATH (deploy req).
|
|
198
95
|
const dir = mkdtempSync(join(tmpdir(), "leader-coord-"));
|
|
199
|
-
// Force C locale so the `raced` regex below matches git's (English) push-rejection messages regardless of
|
|
200
|
-
// the host locale — otherwise a translated message → regex miss → a real race mislabeled non-retryable (council #5).
|
|
201
96
|
const gitEnv = { ...process.env, LC_ALL: "C" };
|
|
202
97
|
try {
|
|
203
98
|
execFileSync("git", ["clone", "-q", durableRemote, dir], { env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
|
|
@@ -207,26 +102,12 @@ export function createCoordinatorPush(durableRemote, targetRef, ident) {
|
|
|
207
102
|
execFileSync("git", ["-C", dir, "am", "--3way", ".leader.patch"], { env: gitEnv, timeout: LEADER_PUSH_LOCAL_TIMEOUT_MS });
|
|
208
103
|
rmSync(join(dir, ".leader.patch"));
|
|
209
104
|
const ref = targetRef ?? "refs/heads/main";
|
|
210
|
-
// CAS against the TARGET ref's ACTUAL current tip (not baseSha). The old `=ref:baseSha` lease assumed
|
|
211
|
-
// the target ref already pointed at baseSha — but a FRESH ref (or one at any other commit) is never at
|
|
212
|
-
// baseSha, so the lease rejected EVERY such push and mislabeled it "raced" (found live: auto1 reached
|
|
213
|
-
// push after a clean 6-worker fan-out + green gradle, and failed only here). ls-remote gives the ref's
|
|
214
|
-
// current oid (empty = it doesn't exist yet) → lease against THAT, which still detects a real concurrent
|
|
215
|
-
// merge race (the ref moved since we read it) but lets a normal create/update through.
|
|
216
|
-
// Does the target ref already exist? (exact full-ref match; --refs drops peeled annotated-tag lines.)
|
|
217
105
|
const ls = execFileSync("git", ["-C", dir, "ls-remote", "--refs", "origin", ref], { encoding: "utf8", env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
|
|
218
106
|
const exists = ls.split("\n").some((l) => l.split("\t")[1] === ref);
|
|
219
107
|
if (exists) {
|
|
220
|
-
// Existing ref → keep the original CAS against baseSha (detects a concurrent merge that moved the ref
|
|
221
|
-
// off the base this run started from — council#2). NOT against a just-read end-of-run tip: that would
|
|
222
|
-
// let leader B force-push over leader A's concurrent merge (Codex review #1). A ref that exists but
|
|
223
|
-
// isn't at baseSha → refuse (use a fresh per-run targetRef to avoid that — the proving-ground does).
|
|
224
108
|
execFileSync("git", ["-C", dir, "push", `--force-with-lease=${ref}:${baseSha}`, "origin", `HEAD:${ref}`], { env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
|
|
225
109
|
}
|
|
226
110
|
else {
|
|
227
|
-
// Absent → create with a "must not exist" lease (empty expected oid): a concurrent create races safely
|
|
228
|
-
// (rejected as stale info) instead of silently fast-forwarding. The original `:baseSha` lease wrongly
|
|
229
|
-
// rejected a fresh ref (it's never at baseSha) — that was auto1's only failure after a green build.
|
|
230
111
|
execFileSync("git", ["-C", dir, "push", `--force-with-lease=${ref}:`, "origin", `HEAD:${ref}`], { env: gitEnv, timeout: LEADER_PUSH_NETWORK_TIMEOUT_MS });
|
|
231
112
|
}
|
|
232
113
|
return { ok: true, ref };
|
|
@@ -246,49 +127,21 @@ export function createLeaderRunner(cfg) {
|
|
|
246
127
|
const ident = cfg.git ?? { name: "leader", email: "leader@local" };
|
|
247
128
|
const { leaderTimeoutMs, workerLimits, presignTtlSec, resourceSuspend: resourceCfg } = leaderResourceConfig();
|
|
248
129
|
const { repairRounds, repairBudgetUsd, conflictRounds, repairLoopOn, measureGatesOn, repairLoopAttempts, oracleFlakyK, replanBudgetUsd, } = leaderLoopConfig({ repairRounds: cfg.repairRounds, conflictRounds: cfg.conflictRounds });
|
|
249
|
-
// #240② onNotice 席(合并码重扫):本车道的每一只 Runner 都要带,否则 core 的引擎通告在这条腿上落回
|
|
250
|
-
// 裸 `console.warn`(主车道进结构化 `engine_notice` 行)—— 同一部署两条通告通道,按 code 采集的运维面
|
|
251
|
-
// 只看得见一半。转发体取共享基座的**那一个**具名导出,禁在这里另写一份(两份必漂)。
|
|
252
|
-
// ⚠️ 判据是 `warn` **在场**,不是 `cfg.logger` 在场:leader 的 logger 两格都可选,只装了 `info` 的部署
|
|
253
|
-
// 铸这一席等于把通告吞掉 —— 那比不铸还坏(不铸时 core 至少还打自己的 `console.warn`)。
|
|
254
|
-
// 🔴 这条判据现在**整条住在** `createEngineNoticeSeat` 里(codex 复审 R1-[medium],验真后修):此前它是
|
|
255
|
-
// 这里的一行三元,于是只能靠「扫这段源码文本里有没有 `warn`」去守,而 `cfg.logger ? ((m,x)=>cfg.logger?.warn?.(m,x)) : undefined`
|
|
256
|
-
// 这一族变异**文本上照样有 `warn`**、值上却是 truthy 闭包(info-only 部署照铸席、通告被可选链吞掉)。
|
|
257
|
-
// 搬进函数之后判据可被**行为**测,门不再是关键字搜索。
|
|
258
130
|
const onNoticeSeat = createEngineNoticeSeat(cfg.logger);
|
|
259
|
-
// design/170 件B/C/D(#252,codex 对抗复审 R2-F1 验真后修):**leader 车道也是一个执行面**。
|
|
260
|
-
// 修前这五处 Runner 一个治理座席都没有 —— 开了 leader 端点的部署因此有一条在部署治理**之外**的执行
|
|
261
|
-
// 面:合规档位说禁 web_fetch,leader 的 worker 照挂;锁与留存声明同样够不着。与 `onNotice` 席当年的
|
|
262
|
-
// 病同族(那次的修法也是「判据收进单一属主 + 五处同席」)。
|
|
263
|
-
// 座席由 cfg 递进来(leader 绝不自己造一份治理判据 —— 一个进程里治理只能有一个属主);缺席 ⇒ 展开空
|
|
264
|
-
// 对象 = 与修前逐字相同,不铸死键。
|
|
265
131
|
const governanceSeat = cfg.governance ?? {};
|
|
266
|
-
// worker resource bounds (limits/maxCostUsd) + presign ttl are derived in leaderResourceConfig(), above.
|
|
267
|
-
// Bounded integration-repair (search 2026-06-14): when the merged tree compiles-clean-but-fails, run a strong
|
|
268
|
-
// agent WITH HANDS in the live merged sandbox to fix the cross-worker integration, then mergeBranches re-runs
|
|
269
|
-
// testCmd. OFF unless LEADER_REPAIR_ROUNDS>0 — closes over the RAW env (hands need the real RemoteExecutionEnv,
|
|
270
|
-
// not the exec/writeFile-only IntegrationEnv wrapper). Strong model (default role); no durable wiring (short).
|
|
271
132
|
const mkRepair = (rawEnv, repoDir, oracleFiles) => {
|
|
272
133
|
if (repairRounds <= 0)
|
|
273
134
|
return undefined;
|
|
274
135
|
return {
|
|
275
136
|
maxRounds: repairRounds,
|
|
276
137
|
run: async ({ round, errorText, testCmd }) => {
|
|
277
|
-
// 🔴 Measurement integrity (council blocker + partner-Codex gotcha): HIDE the hidden oracle so the repair
|
|
278
|
-
// agent (rooted at the repo) can't `cat` the test it's graded on and cheat; RE-INJECT it in `finally` so
|
|
279
|
-
// merge's re-test gate verifies the fix against the real oracle (and it's hidden again next round).
|
|
280
138
|
for (const f of oracleFiles) {
|
|
281
|
-
await rawEnv.remove(f.path).catch(() => { });
|
|
282
|
-
// FAIL-CLOSED (partner-Codex): remove() returns a Result that does NOT throw on failure, so a silent
|
|
283
|
-
// {ok:false} would leave the oracle readable and the repair model could `cat` it. VERIFY it's gone.
|
|
139
|
+
await rawEnv.remove(f.path).catch(() => { });
|
|
284
140
|
const ex = await rawEnv.exists(f.path);
|
|
285
141
|
if (ex.ok && ex.value)
|
|
286
142
|
throw new Error(`oracle still present after remove (${f.path}) — refusing repair (measurement integrity)`);
|
|
287
143
|
}
|
|
288
144
|
try {
|
|
289
|
-
// rootPath: repoDir (Codex review #4) — without it core roots the file tools at executionEnv.cwd
|
|
290
|
-
// (/workspace on Kata), one level above the repo (/workspace/repo). bash can cd, but the dedicated
|
|
291
|
-
// read/edit tools would start too high + lose containment.
|
|
292
145
|
const runner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat, executionEnv: rawEnv, rootPath: repoDir });
|
|
293
146
|
const objective = [
|
|
294
147
|
`The integrated project at ${repoDir} fails its build/test. Make the MINIMAL change to the working tree so this command exits 0 (cd into the repo and run it yourself to confirm):`,
|
|
@@ -296,8 +149,6 @@ export function createLeaderRunner(cfg) {
|
|
|
296
149
|
`Several agents each ported a DISJOINT module group; the failure is almost certainly a cross-module integration mismatch (a shared interface, a missing/renamed symbol, a type/signature disagreement between two groups). Read the failing files, fix the smallest set of files, and re-run the command to verify it passes.`,
|
|
297
150
|
`HARD RULES: do NOT git commit or git push; do NOT modify build configuration (gradle.properties / AGP / dependency versions / settings); do NOT add build-tooling files; do NOT call AskUserQuestion — decide autonomously. Preserve the ported semantics — fix the integration, never delete behavior just to make it pass.`,
|
|
298
151
|
``,
|
|
299
|
-
// BL-32 — the failing output is worker/build-authored; delimit it so an injected instruction in a stack
|
|
300
|
-
// trace / test message can't steer the repair agent (design/53 trifecta).
|
|
301
152
|
delimitUntrusted("FAILING OUTPUT (untrusted)", errorText),
|
|
302
153
|
].join("\n");
|
|
303
154
|
const res = await runner
|
|
@@ -307,16 +158,11 @@ export function createLeaderRunner(cfg) {
|
|
|
307
158
|
limits: { maxCostUsd: repairBudgetUsd, maxWalltimeMs: Math.max(300_000, Math.floor(leaderTimeoutMs / 2)) },
|
|
308
159
|
})
|
|
309
160
|
.result();
|
|
310
|
-
// A non-completed repair (timeout/abort) may leave a process alive in the SAME Kata pod that would race
|
|
311
|
-
// the merge re-test (Codex review #1) — surface it as a throw so mergeBranches does NOT re-test: it keeps
|
|
312
|
-
// the original failure and the `finally` destroys the sandbox, killing any orphan.
|
|
313
161
|
if (res.status !== "completed")
|
|
314
162
|
throw new Error(`repair round ${round} did not complete (status=${res.status})`);
|
|
315
163
|
return { costUsd: res.stats?.costMicroUsd !== undefined ? res.stats.costMicroUsd / 1e6 : undefined, note: res.result?.slice(0, 200) };
|
|
316
164
|
}
|
|
317
165
|
finally {
|
|
318
|
-
// Re-inject the oracle so merge's re-test gate (run right after) verifies the fix against the REAL hidden
|
|
319
|
-
// test — even if the repair model threw. It's hidden again at the top of the next round.
|
|
320
166
|
for (const f of oracleFiles) {
|
|
321
167
|
const wr = await rawEnv.writeFile(f.path, f.content);
|
|
322
168
|
if (!wr.ok)
|
|
@@ -326,12 +172,6 @@ export function createLeaderRunner(cfg) {
|
|
|
326
172
|
},
|
|
327
173
|
};
|
|
328
174
|
};
|
|
329
|
-
// Bounded merge-conflict resolution (search 2026-06-16): a `git apply --3way` conflict means two workers touched
|
|
330
|
-
// overlapping lines. Instead of the leader discarding ALL fan-out work + redoing single (collapseToSingle), run a
|
|
331
|
-
// strong agent WITH HANDS in the conflicted sandbox to integrate the conflicting worker's patch (the others
|
|
332
|
-
// already applied clean). OFF unless LEADER_CONFLICT_ROUNDS>0. Mirrors mkRepair: raw env for hands; hide the
|
|
333
|
-
// hidden oracle so the resolver can't grade-peek; restore in `finally` so the downstream test gate sees the real
|
|
334
|
-
// oracle. mergeBranches re-checks for leftover markers/.rej and degrades to collapse if the resolution is unclean.
|
|
335
175
|
const mkConflictResolver = (rawEnv, repoDir, oracleFiles) => {
|
|
336
176
|
if (conflictRounds <= 0)
|
|
337
177
|
return undefined;
|
|
@@ -352,8 +192,6 @@ export function createLeaderRunner(cfg) {
|
|
|
352
192
|
`When done, NO conflict markers and NO *.rej files may remain under ${repoDir}. Verify yourself before finishing: grep -rIlE '^(<<<<<<<|>>>>>>>)' . ; find . -name '*.rej' — BOTH must print nothing.`,
|
|
353
193
|
`HARD RULES: do NOT git commit / git add / git push (the coordinator squashes at the end); do NOT modify build configuration; do NOT call AskUserQuestion — decide autonomously. Preserve both sides' ported semantics.`,
|
|
354
194
|
``,
|
|
355
|
-
// BL-32 — the worker patch + conflict text are untrusted worker-adjacent content; delimit both so an
|
|
356
|
-
// injected instruction can't steer the conflict-resolver agent (design/53 trifecta).
|
|
357
195
|
delimitUntrusted("CONFLICTING WORKER PATCH (untrusted)", patch.slice(0, 6000)),
|
|
358
196
|
delimitUntrusted("CONFLICT DETAILS (untrusted)", conflictText),
|
|
359
197
|
].join("\n");
|
|
@@ -383,22 +221,12 @@ export function createLeaderRunner(cfg) {
|
|
|
383
221
|
if (!body.objective || !body.durableRemote || !body.testCmd || !body.seedCmd || !body.baseSha) {
|
|
384
222
|
return { ok: false, reports: [], cancelled: false, error: "request must provide objective, durableRemote, baseSha, testCmd, seedCmd" };
|
|
385
223
|
}
|
|
386
|
-
// `durableRemote` is handed to `git clone` on the CONTROL PLANE (with the Coordinator's creds). git executes
|
|
387
|
-
// transport-helper URLs (`ext::sh -c '…'`, `file://…`) as commands, so allow only a normal http(s)/git/ssh
|
|
388
|
-
// URL or an scp-style user@host:path — reject the transport-helper class even though the caller is trusted.
|
|
389
224
|
if (!/^(?:https?|git|ssh):\/\/[^\s]+$/i.test(body.durableRemote) && !/^[\w.-]+@[\w.-]+:[^\s]+$/.test(body.durableRemote)) {
|
|
390
225
|
return { ok: false, reports: [], cancelled: false, error: "durableRemote must be an http(s)/git/ssh URL or user@host:path (no ext::/file:// transport helpers)" };
|
|
391
226
|
}
|
|
392
|
-
// baseSha is interpolated into worker-shell lines on BOTH env paths (static: format-patch via pullDiff,
|
|
393
|
-
// which re-validates; factory: the staged upload script) — gate it here so the factory path is covered too.
|
|
394
227
|
if (!safeRev(body.baseSha)) {
|
|
395
228
|
return { ok: false, reports: [], cancelled: false, error: "baseSha must be a plain git rev (sha/branch token)" };
|
|
396
229
|
}
|
|
397
|
-
// B-2: write the hidden oracle into the integration sandbox (workers never see it) + local-exclude it so the
|
|
398
|
-
// squash `git add -A` does NOT commit/push it into the durable integration branch (Codex review #1: else the
|
|
399
|
-
// hidden test leaks into the repo, visible to any future worker seeded from it). gradle compiles src/test
|
|
400
|
-
// regardless of git status, so the correctness gate still runs. NOTE: if integration-repair is enabled, the
|
|
401
|
-
// repair agent (rooted at the repo) could still READ the on-disk oracle — delete it before repair runs then.
|
|
402
230
|
const injectOracles = async (env) => {
|
|
403
231
|
const ofs = body.oracleFiles ?? [];
|
|
404
232
|
if (ofs.length === 0)
|
|
@@ -408,9 +236,6 @@ export function createLeaderRunner(cfg) {
|
|
|
408
236
|
if (!wr.ok)
|
|
409
237
|
throw wr.error;
|
|
410
238
|
}
|
|
411
|
-
// Local-exclude each oracle so `git add -A` never commits it (B-2 sandbox-only). Root-anchor with a leading
|
|
412
|
-
// "/" + escape a leading "#"/"!" so the path is a literal exclude, not a gitignore comment/negation (council
|
|
413
|
-
// #4). APPEND to (don't clobber) any seed-configured excludes — read the current file first (council #3).
|
|
414
239
|
const anchor = (p) => "/" + (/^[#!]/.test(p) ? "\\" + p : p);
|
|
415
240
|
const rel = ofs.map((f) => anchor(f.path.startsWith(`${repo}/`) ? f.path.slice(repo.length + 1) : f.path)).join("\n");
|
|
416
241
|
const cur = await env.exec(`bash -lc ${JSON.stringify(`cat ${repo}/.git/info/exclude 2>/dev/null || true`)}`);
|
|
@@ -419,18 +244,8 @@ export function createLeaderRunner(cfg) {
|
|
|
419
244
|
if (!ex.ok)
|
|
420
245
|
throw ex.error;
|
|
421
246
|
};
|
|
422
|
-
// design/158 A13: the four LeaderDeps methods below (plan/provisionWorker/provisionIntegrationSandbox/push)
|
|
423
|
-
// are named local functions — same closure scope, same capture, same execution order as when they were
|
|
424
|
-
// inline object-literal properties; only the "anonymous inline closure" form changed (behavior identical).
|
|
425
|
-
// caller-provided sub-tasks = an explicit FAN-OUT override; else the difficulty ROUTER decides (one model
|
|
426
|
-
// completion): default single (a 1-subtask plan) and only fan out when clearly+economically decomposable
|
|
427
|
-
// Either way → VALIDATED disjoint-file sub-tasks (planner.ts enforces the Coordinator's invariant).
|
|
428
247
|
const plan = async () => {
|
|
429
248
|
if (Array.isArray(body.subtasks) && body.subtasks.length > 0) {
|
|
430
|
-
// Validate caller-supplied sub-tasks through the SAME deterministic checks as an LLM plan — the
|
|
431
|
-
// branch is interpolated into a worker-container shell command (planner.validateSubtasks rejects a
|
|
432
|
-
// shell-unsafe branch / leading "-" / "..") and the disjoint-file + unique-id invariants are what
|
|
433
|
-
// the Coordinator's parallel merge relies on. Was a straight map() that skipped all of it.
|
|
434
249
|
return validateSubtasks(body.subtasks);
|
|
435
250
|
}
|
|
436
251
|
const planRunner = new Runner({ brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat });
|
|
@@ -438,14 +253,10 @@ export function createLeaderRunner(cfg) {
|
|
|
438
253
|
const res = await planRunner.runTaskStream({
|
|
439
254
|
objective: prompt,
|
|
440
255
|
sessionId: `leader-route-${Date.now()}`,
|
|
441
|
-
// Pin the route to a cheap classifier model (see `routerModel` doc): the heavy reasoning model
|
|
442
|
-
// returns an empty completion ~2/3 of the time on this prompt, collapsing every fan-out to single.
|
|
443
256
|
...(cfg.routerModel ? { model: cfg.routerModel } : {}),
|
|
444
257
|
}).result();
|
|
445
258
|
return res.result ?? "";
|
|
446
259
|
};
|
|
447
|
-
// §5 quality gate: a rejected/unparseable plan retries the LLM ≤1, then collapses to single (B1-B3) —
|
|
448
|
-
// never plan-fails the task; the collapse reason is logged as a router-quality signal.
|
|
449
260
|
const { subs, collapsed, rawSample } = await routePlanWithFallback(body.objective, runRoute, {
|
|
450
261
|
...(body.planContext ? { context: body.planContext } : {}),
|
|
451
262
|
...(cfg.fanoutEnabled === false ? { fanoutEnabled: false } : {}),
|
|
@@ -456,17 +267,10 @@ export function createLeaderRunner(cfg) {
|
|
|
456
267
|
};
|
|
457
268
|
const provisionWorker = async (sub) => {
|
|
458
269
|
const sessionId = `leader-${sub.workerId}-${Date.now()}`;
|
|
459
|
-
// design/68 C4: durable suspend bits per worker task (mirrors main.ts's per-task wiring) — a gated
|
|
460
|
-
// ask/AskUserQuestion suspends the worker; ctx is seeded so the surfaced token resumes on the
|
|
461
|
-
// single-agent path (the resume body re-runs the sub-objective on this worker).
|
|
462
270
|
let durableSpec = {};
|
|
463
271
|
if (cfg.durable) {
|
|
464
272
|
const d = cfg.durable;
|
|
465
273
|
durableSpec = {
|
|
466
|
-
// AskUserQuestion 判决:本腿是 resolve-spec `createDurableQuestionGate` 单一属主声明的**登记豁免**
|
|
467
|
-
// (复审车B 上抛/属主裁定 2026-08-05)——leader worker 无活体问答面可装(不是 HTTP 活流腿,
|
|
468
|
-
// QuestionCoordinator 的 ALS 永不覆盖到这里),core 原形+下方 sentinel 即完整正确语义,且 leader
|
|
469
|
-
// 子系统不 import boot 装配层(分层)。⚠️ 若 leader 腿将来接活体面,必须改调该工厂并撤本注。
|
|
470
274
|
toolPolicy: combinePolicies(createDurableQuestionPolicy(), createDurableAskPolicy({ requireApproval: d.requireApproval, ...(d.deny ? { deny: d.deny } : {}), ...(d.autoBudget ? { autoBudget: d.autoBudget } : {}), ...(d.neverAuto ? { neverAuto: d.neverAuto } : {}) })),
|
|
471
275
|
checkpointStore: d.checkpointStore,
|
|
472
276
|
durableApproval: { scope: "_leader", ...(d.ttlMs ? { ttlMs: d.ttlMs } : {}) },
|
|
@@ -476,27 +280,7 @@ export function createLeaderRunner(cfg) {
|
|
|
476
280
|
cfg.logger?.warn?.("leader_putctx_failed", { workerId: sub.workerId, err: String(e) });
|
|
477
281
|
});
|
|
478
282
|
}
|
|
479
|
-
// design/74 Slice 6: opt-in resource-slice suspend. A worker that overruns its per-slice budget window
|
|
480
|
-
// SUSPENDS (resumable) instead of failing; the leader's runWorker auto-resumes until the TOTAL is spent.
|
|
481
|
-
// Reuses the durable checkpoint store (so requires `cfg.durable` for the store + durable tool-results +
|
|
482
|
-
// session — the same durable-resume prerequisites). The per-slice window OVERRIDES workerLimits.maxCostUsd
|
|
483
|
-
// (spread last); the total + cumulative spend ride on the checkpoint's resource ledger (core debits it).
|
|
484
283
|
let resourceSpec = {};
|
|
485
|
-
// Slice-6 review BLOCKER: core's resource-suspend needs the FULL durable-resume prerequisites —
|
|
486
|
-
// a checkpoint store (cfg.durable), a durable tool-result store (cfg.toolResultStore; an in-memory
|
|
487
|
-
// offload makes core REFUSE to suspend), AND a durable session store (cfg.sessionStore; without it a
|
|
488
|
-
// resumed slice dies with SessionError "Entry <leafId> not found" — the worker's progress is lost,
|
|
489
|
-
// wire.ts:52-55). Require ALL THREE before applying the per-slice window: otherwise the worker's
|
|
490
|
-
// maxCostUsd would be cut to a quarter WITHOUT the ability to suspend/resume → it fails at 1/4 its
|
|
491
|
-
// budget with no warning. When opted in (resourceCfg) but a prerequisite is missing, leave the budget
|
|
492
|
-
// intact + warn (which one).
|
|
493
|
-
// §10.5 ENFORCED mutual exclusion: when the single-agent repair loop actually OWNS a worker, that worker
|
|
494
|
-
// must NOT also resource-suspend — the repair loop owns its attempt budget, and a resource-suspend mid-loop
|
|
495
|
-
// risks `attemptCount` corruption + budget overrun (§10.5: a doc aspiration is not enough, this drops
|
|
496
|
-
// `resourceSpec` by construction so the two are NEVER on the same spec). Gated on the loop being WIRED
|
|
497
|
-
// (`REPAIR_LOOP_WIRED=true` — the canary-enable slice attached a LIVE `resolveRepair`), so flipping
|
|
498
|
-
// `LEADER_REPAIR_LOOP` now engages BOTH the single-agent repair loop AND this resource-suspend exclusion for
|
|
499
|
-
// the worker the loop owns. (`REPAIR_LOOP_WIRED` is the live/inert switch — it shipped false pre-activation.)
|
|
500
284
|
const repairLoopActive = repairLoopOn && REPAIR_LOOP_WIRED;
|
|
501
285
|
if (repairLoopActive && resourceCfg) {
|
|
502
286
|
cfg.logger?.warn?.("leader_resource_suspend_disabled_for_repair_loop", { workerId: sub.workerId, reason: "LEADER_REPAIR_LOOP active — resource-suspend mutually exclusive with the repair loop (§10.5)" });
|
|
@@ -505,9 +289,6 @@ export function createLeaderRunner(cfg) {
|
|
|
505
289
|
resourceSpec = {
|
|
506
290
|
checkpointStore: cfg.durable.checkpointStore,
|
|
507
291
|
resourceSuspend: { scope: `_leader-resource-${sub.workerId}`, totalBudgetUsd: resourceCfg.totalBudgetUsd },
|
|
508
|
-
// core 5.8.0:maxCostUsd 入 limits 后,本 spread(spec: {...workerLimits, ..., ...resourceSpec})的
|
|
509
|
-
// limits 键是**整体覆盖**——必须显式并回 workerLimits.limits(maxTurns/maxWalltimeMs),否则
|
|
510
|
-
// per-slice 覆盖会顺手把 worker 的轮数/墙钟界丢掉(5.7 时代顶层键覆盖顶层键,无此陷阱)。
|
|
511
292
|
limits: { ...workerLimits.limits, maxCostUsd: resourceCfg.sliceMaxCostUsd },
|
|
512
293
|
maxSuspends: resourceCfg.maxSuspends,
|
|
513
294
|
};
|
|
@@ -522,12 +303,6 @@ export function createLeaderRunner(cfg) {
|
|
|
522
303
|
: "no durable session store",
|
|
523
304
|
});
|
|
524
305
|
}
|
|
525
|
-
// Keep the resume ctx warm for the WHOLE first leg (found live: task02b run1). putCtx stamps
|
|
526
|
-
// `updated_at` once at provision, but reapCtx GC's any ctx older than runStaleSec that has no pending
|
|
527
|
-
// checkpoint and no task_active claim — sub-workers hold no task_active row (that lock belongs to the
|
|
528
|
-
// HTTP submit path), so a leg longer than the reap cutoff lost its ctx and the post-suspend /decide
|
|
529
|
-
// died 409 "resume context missing". Refresh inside the run; once suspended, the pending checkpoint
|
|
530
|
-
// itself shields the ctx from the reaper, and after a terminal result the ctx ages out as designed.
|
|
531
306
|
const keepCtxWarm = (runner) => {
|
|
532
307
|
const d = cfg.durable;
|
|
533
308
|
if (!d)
|
|
@@ -544,53 +319,29 @@ export function createLeaderRunner(cfg) {
|
|
|
544
319
|
};
|
|
545
320
|
return runner;
|
|
546
321
|
};
|
|
547
|
-
// design/68 §8 F2(a) — factory-env worker + self-upload diff-out (preferred; static mode = compat below).
|
|
548
322
|
if (cfg.s3 && cfg.envFactory) {
|
|
549
323
|
const s3 = cfg.s3;
|
|
550
324
|
const envFactory = cfg.envFactory;
|
|
551
|
-
const baseSha = body.baseSha;
|
|
325
|
+
const baseSha = body.baseSha;
|
|
552
326
|
const seedCmd = body.seedCmd;
|
|
553
|
-
// workerId is validateSubtasks-clean ([A-Za-z0-9._-]+) → safe as a key segment; the presigner
|
|
554
|
-
// backstop-rejects traversal anyway. One object per worker per leader run; a repair re-run overwrites.
|
|
555
327
|
const key = `${s3.keyPrefix ?? "leader-diff"}/${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}/${sub.workerId}.patch`;
|
|
556
|
-
// The presigned URL must outlive the worker: it self-uploads its diff at the END of its run, which
|
|
557
|
-
// can be the full leader deadline (24h). The old `Math.min(3600, …)` clamp expired mid-run for any
|
|
558
|
-
// worker > 1h → the upload 403'd and the diff (progress) was lost. Track the worker deadline + slack.
|
|
559
|
-
// (Codex: instant-signing at upload time, from the control plane, is the cleaner long-term fix.)
|
|
560
328
|
const ttl = presignTtlSec;
|
|
561
329
|
const base = { endpoint: s3.endpoint, bucket: s3.bucket, accessKey: s3.accessKey, secretKey: s3.secretKey, ...(s3.region ? { region: s3.region } : {}), key, expiresSec: ttl };
|
|
562
330
|
const putUrl = presignS3Url({ ...base, method: "PUT" });
|
|
563
331
|
const getUrl = presignS3Url({ ...base, method: "GET" });
|
|
564
332
|
const script = buildUploadScript({ repoDir: repo, baseSha, putUrl, workDir: W });
|
|
565
|
-
// Staging runs adapter-side before the agent's first workspace call: seed the base repo at the worker
|
|
566
|
-
// branch + plant the upload script (the presigned URL never passes through the model).
|
|
567
|
-
// Resume-safe (BL-1): stageWorkerEnv no-ops on a resumed snapshot (worker branch already present)
|
|
568
|
-
// instead of re-running `git checkout -b` (which would fail "branch exists" and kill the resumed
|
|
569
|
-
// worker). Now load-bearing because the prod auto-resume landed (leader.ts non-verify path, 9a91c60).
|
|
570
333
|
const stage = async (env) => {
|
|
571
334
|
await stageWorkerEnv(env, { seedCmd, repo, branch: sub.branch, uploadScriptPath: `${W}/.leader-upload.sh`, uploadScript: script });
|
|
572
|
-
// #16 tail (search 2026-06-17): INCREMENTAL progress upload. The upload script is READ-ONLY git
|
|
573
|
-
// (format-patch base..HEAD — no add/commit/index write), so a background loop re-running it keeps the
|
|
574
|
-
// worker's fixed diff key at its latest COMMITTED progress. If the env HARD-CRASHES before its final /
|
|
575
|
-
// belt upload, salvage (fetchUploadedDiff) then recovers up to the last commit instead of losing all
|
|
576
|
-
// (run 019eca71's "4 commits vaporized" was exactly a failed worker that never uploaded). No git
|
|
577
|
-
// concurrency (read-only); the teardown belt upload stays authoritative (same key, complete diff).
|
|
578
|
-
// Fire-and-forget + NEVER awaited: the loop never exits, so staging must not block on it — whether the
|
|
579
|
-
// env's exec detaches or blocks, the loop RUNS either way and the dangling promise is reaped when the
|
|
580
|
-
// pod dies. Best-effort: a launch failure (no nohup, etc.) just falls back to today's end-only upload.
|
|
581
335
|
void sh(env)(`nohup sh -c 'while true; do sleep 90; sh ${W}/.leader-upload.sh >/dev/null 2>&1 || true; done' </dev/null >/dev/null 2>&1 &`).catch(() => { });
|
|
582
336
|
};
|
|
583
337
|
return {
|
|
584
338
|
workerId: sub.workerId, sessionId, branch: sub.branch,
|
|
585
|
-
baseSha,
|
|
339
|
+
baseSha,
|
|
586
340
|
runner: keepCtxWarm(new Runner({
|
|
587
341
|
brain: cfg.brain, models: cfg.models, roles: cfg.roles, pricing: cfg.pricing, ...onNoticeSeat, ...governanceSeat,
|
|
588
342
|
...(cfg.toolResultStore ? { toolResultStore: cfg.toolResultStore } : {}),
|
|
589
343
|
...(cfg.sessionStore ? { sessionStore: cfg.sessionStore } : {}),
|
|
590
344
|
executionEnvFactory: (ctx) => withStaging(envFactory(ctx), stage, async (e) => {
|
|
591
|
-
// Belt (task02b run6): workers that did the work but never `git commit`ed upload an EMPTY diff
|
|
592
|
-
// (diff-out is committed-state vs base) — auto-commit any uncommitted work first. No-op for a
|
|
593
|
-
// worker that committed properly; the commit identity marks belt-captured work for the reviewer.
|
|
594
345
|
await sh(e)(`cd ${repo} && git add -A && git -c user.name='leader-belt' -c user.email='belt@leader' commit -qm 'belt: auto-commit uncommitted worker output' || true; sh ${W}/.leader-upload.sh`);
|
|
595
346
|
}),
|
|
596
347
|
})),
|
|
@@ -614,8 +365,6 @@ export function createLeaderRunner(cfg) {
|
|
|
614
365
|
};
|
|
615
366
|
};
|
|
616
367
|
const provisionIntegrationSandbox = async () => {
|
|
617
|
-
// Factory lane (k8s or e2b): one fresh env from the service factory; lazy-connect — first exec
|
|
618
|
-
// provisions the pod/VM, destroy() reaps it. Static lane: the original explicit E2B env.
|
|
619
368
|
if (cfg.envFactory) {
|
|
620
369
|
const env = cfg.envFactory({ sessionId: `leader-integ-${Date.now()}` });
|
|
621
370
|
try {
|
|
@@ -631,8 +380,6 @@ export function createLeaderRunner(cfg) {
|
|
|
631
380
|
if (!cfg.e2bApiKey)
|
|
632
381
|
throw new Error("leader wire: no envFactory and no e2bApiKey");
|
|
633
382
|
const env = new RemoteContainerExecutionEnv({ apiKey: cfg.e2bApiKey, timeoutMs: leaderTimeoutMs });
|
|
634
|
-
// Once connected, the VM is billable — if seeding throws, destroy it HERE before propagating, otherwise
|
|
635
|
-
// mergeBranches never receives the `destroy` closure and the sandbox leaks until E2B's timeout reaps it.
|
|
636
383
|
if (!(await env.connect()).ok) {
|
|
637
384
|
await env.destroy().catch(() => { });
|
|
638
385
|
throw new Error("integ connect failed");
|
|
@@ -655,40 +402,14 @@ export function createLeaderRunner(cfg) {
|
|
|
655
402
|
push,
|
|
656
403
|
testCmd: body.testCmd,
|
|
657
404
|
repoDir: repo, workerRepoDir: repo,
|
|
658
|
-
|
|
659
|
-
// found live: task02b run5, porting workers + collapse-solo all 'run aborted' at the 600s default while
|
|
660
|
-
// mid-gradle; the per-worker presign ttl below tracks the same knob).
|
|
661
|
-
timeoutMs: leaderTimeoutMs, maxConcurrency: 4, // Semaphore cap; the planner emits ≤6 sub-tasks
|
|
662
|
-
// replan-lite (design/68 §6) always on in the wired leader; LEADER_BUDGET_USD bounds the fan-out spend
|
|
663
|
-
// (parsed + fail-loud-guarded in leaderLoopConfig(), above — see LEADER_BUDGET_USD review-batch note).
|
|
405
|
+
timeoutMs: leaderTimeoutMs, maxConcurrency: 4,
|
|
664
406
|
replan: { ...(replanBudgetUsd !== undefined ? { budgetUsd: replanBudgetUsd } : {}) },
|
|
665
|
-
// LEADER-REPAIRLOOP-INTEGRATION §10.3 — the merge push-hold-on-signal (OFF unless LEADER_MEASURE_GATES). It
|
|
666
|
-
// needs NO extra sandbox: `strongOracleSeeded` keys on whether a hidden held-out oracle was injected into the
|
|
667
|
-
// integration sandbox (`injectOracles` runs `body.oracleFiles`), and the §10.3 hold combines it with the
|
|
668
|
-
// existing observe-only `measureCmd` result inside `mergeBranches`. §10.4's out-of-process measure grader
|
|
669
|
-
// (`measureDrivesRepair`) is a separate re-seedable sandbox — the canary-enable add-on (§6 step 5); it is
|
|
670
|
-
// intentionally NOT wired here in v1 (a half-mint would be a stopgap), so `measureGates` ships the §10.3
|
|
671
|
-
// signal-hold only. With LEADER_MEASURE_GATES off this whole block is absent → byte-identical merge path.
|
|
672
407
|
...(measureGatesOn
|
|
673
408
|
? { mergeGates: { measureGates: true, ...((body.oracleFiles?.length ?? 0) > 0 ? { strongOracleSeeded: true } : {}) } }
|
|
674
409
|
: {}),
|
|
675
410
|
...(cfg.logger ? { logger: cfg.logger } : {}),
|
|
676
411
|
};
|
|
677
|
-
// §10.8 — the SINGLE wire-call helper for the opt-in single-agent repair loop (LEADER_REPAIR_LOOP). The
|
|
678
|
-
// canary-enable slice attaches a LIVE async `resolve(solo, sub)` that, per solo worker, provisions a DEDICATED
|
|
679
|
-
// oracle-seeded GRADER sandbox (a SEPARATE sandbox from the worker + the integration sandbox — distinct object/
|
|
680
|
-
// sandboxId by construction, §10.2 structural isolation) and binds the per-worker diff source + the concrete
|
|
681
|
-
// Runner. OFF-by-default: `resolve` is attached ONLY when `repairLoopOn` (LEADER_REPAIR_LOOP=true); with the
|
|
682
|
-
// flag off `resolve` is undefined → `attachRepairLoopDeps` is a NO-OP → `deps.repairLoop` stays unset → the
|
|
683
|
-
// single-agent default path is byte-identical to today. The §10.6 push chokepoint (merge.ts) is LIVE
|
|
684
|
-
// regardless: once any report carries a `repairTerminal`, it holds.
|
|
685
|
-
//
|
|
686
|
-
// The grader is provisioned the SAME way as the integration sandbox (provisionIntegrationSandbox: raw env →
|
|
687
|
-
// seed → injectOracles → integBase), then exposed as a `GraderHandle` (the raw RemoteExecutionEnv IS an
|
|
688
|
-
// ExecutionEnv). `graderDestroy` reaps it in `runRepairLoopForLeader`'s `finally`. On a seed/inject throw the
|
|
689
|
-
// env is destroyed before propagating (mirrors provisionIntegrationSandbox :587-590 / :604-606).
|
|
690
412
|
const resolveRepair = async (solo, _sub) => {
|
|
691
|
-
// Provision a DEDICATED grader: factory lane mints a fresh env (lazy-connect); static lane connects an E2B VM.
|
|
692
413
|
let env;
|
|
693
414
|
if (cfg.envFactory) {
|
|
694
415
|
env = cfg.envFactory({ sessionId: `leader-grader-${Date.now()}` });
|
|
@@ -703,8 +424,6 @@ export function createLeaderRunner(cfg) {
|
|
|
703
424
|
}
|
|
704
425
|
env = e2b;
|
|
705
426
|
}
|
|
706
|
-
// Seed the base repo + inject the hidden oracle ONLY in this grader (never the worker env). On a throw,
|
|
707
|
-
// destroy the billable env before propagating (else it leaks until the adapter's timeout reaps it).
|
|
708
427
|
let integBase;
|
|
709
428
|
try {
|
|
710
429
|
await sh(env)(body.seedCmd);
|
|
@@ -722,33 +441,20 @@ export function createLeaderRunner(cfg) {
|
|
|
722
441
|
repoDir: repo,
|
|
723
442
|
integBase,
|
|
724
443
|
};
|
|
725
|
-
// The worker diff source: factory lane = the worker's self-upload (`solo.fetchDiff`); static lane = pull the
|
|
726
|
-
// committed `base..HEAD` series straight out of the worker's still-alive env (the same merge `pullDiff` path).
|
|
727
444
|
const workerRepoDir = repo;
|
|
728
445
|
const pullWorkerDiff = solo.fetchDiff
|
|
729
|
-
?
|
|
730
|
-
// repairObjective carries no upload mandate, so an un-uploaded fix is missed. A deterministic belt needs
|
|
731
|
-
// a worker-env exec handle the factory lane does not surface (env-recording factory = follow-up). The
|
|
732
|
-
// fork2.1 stale-diff guard keeps this SAFE (no false candidate) until then.
|
|
446
|
+
?
|
|
733
447
|
solo.fetchDiff
|
|
734
448
|
: async () => {
|
|
735
|
-
// 🔴 BL-33 — the STATIC lane EXPOSES the worker env (`solo.diffEnv`), so deterministically belt-commit
|
|
736
|
-
// the worker's UNCOMMITTED fix-turn work BEFORE the committed-only `format-patch base..HEAD` pull —
|
|
737
|
-
// else attempts ≥2 grade a STALE diff (the fix never observed). Best-effort + only when there is a
|
|
738
|
-
// change to commit (no empty commits polluting the series). Mirrors the teardown belt, run per-grade.
|
|
739
449
|
const wenv = solo.diffEnv;
|
|
740
450
|
await wenv
|
|
741
451
|
.exec(`bash -lc ${JSON.stringify(`cd ${repo} && git add -A && (git diff --cached --quiet || git -c user.email=w@w.co -c user.name=worker commit -q -m repair-wip)`)}`)
|
|
742
452
|
.catch(() => { });
|
|
743
453
|
return pullDiff(solo.diffEnv, solo.baseSha, { ref: solo.branch, ...(workerRepoDir ? { cwd: workerRepoDir } : {}) });
|
|
744
454
|
};
|
|
745
|
-
// The trusted, spec-derived oracle steps (the integration testCmd) run in the grader; NEVER worker-authored.
|
|
746
455
|
const oracleSteps = [{ command: body.testCmd, cwd: repo, label: "oracle-test" }];
|
|
747
|
-
// tier: a seeded hidden held-out oracle clears to `trusted_hidden`; a compile-only gate is `property_harness_weak`.
|
|
748
456
|
const oracleFiles = body.oracleFiles ?? [];
|
|
749
457
|
const passTier = oracleFiles.length > 0 ? "trusted_hidden" : "property_harness_weak";
|
|
750
|
-
// fork1.1 — make the INTENTIONAL factory-lane identity-check skip observable (distinct from an
|
|
751
|
-
// accidental workerEnv omission, which onWarn's generic MINOR-2 warning can't tell apart).
|
|
752
458
|
if (solo.fetchDiff)
|
|
753
459
|
cfg.logger?.info?.("repair_loop_factory_lane_isolation", { workerId: solo.workerId, reason: "identity check intentionally skipped — isolation by independent grader sandbox construction (§10.2)" });
|
|
754
460
|
return {
|
|
@@ -758,14 +464,9 @@ export function createLeaderRunner(cfg) {
|
|
|
758
464
|
pullWorkerDiff,
|
|
759
465
|
oracleSteps,
|
|
760
466
|
passTier,
|
|
761
|
-
// council 2026-06-17 — re-inject the seeded oracle after each hermetic reset (clean -fdx wipes it);
|
|
762
|
-
// fork1.2 — the paths a candidate patch must not touch (the oracle files' own paths).
|
|
763
467
|
...(oracleFiles.length > 0 ? { oracleFiles, oraclePaths: oracleFiles.map((f) => f.path) } : {}),
|
|
764
468
|
maxAttempts: repairLoopAttempts,
|
|
765
469
|
flakyK: oracleFlakyK,
|
|
766
|
-
// §10.2 — on the factory lane the worker's RAW env is not surfaced (only fetchDiff), so OMIT workerEnv
|
|
767
|
-
// (runRepairLoop skips the identity check; the grader is a separate sandbox by construction). On the static
|
|
768
|
-
// lane the leader owns the worker's RemoteExecutionEnv (`solo.diffEnv` is that env) → pass it so the check runs.
|
|
769
470
|
...(!solo.fetchDiff && solo.diffEnv ? { workerEnv: solo.diffEnv } : {}),
|
|
770
471
|
log: (event, x) => event.includes("fail") || event.includes("warn") || event.includes("threw")
|
|
771
472
|
? cfg.logger?.warn?.(event, x)
|