@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/diffup.js
CHANGED
|
@@ -1,32 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* design/68 §8 F2(a) — worker SELF-UPLOAD diff-out (the factory-env counterpart of diffout.ts's pullDiff).
|
|
3
|
-
*
|
|
4
|
-
* With a factory env the runner owns the container lifecycle (create at task start, destroy at finish — and
|
|
5
|
-
* durable suspend only produces a complete ownedEnv on this path), so the leader can no longer pull the diff
|
|
6
|
-
* from a still-alive env after the task. Instead (the gate#2-proven MinIO presigned pattern):
|
|
7
|
-
* 1. the env factory STAGES each fresh container (seed repo + write an upload script holding a presigned
|
|
8
|
-
* PUT URL) — adapter-side, so the URL/bulk never enters the model's CONTEXT by construction (it does sit
|
|
9
|
-
* on the sandbox filesystem, readable if the agent goes looking — the objective forbids inspecting it;
|
|
10
|
-
* the URL is single-object, PUT-only, short-TTL, so that residual face is small);
|
|
11
|
-
* 2. the worker's objective ends with a MANDATORY final step: run the upload script (`git format-patch` →
|
|
12
|
-
* `curl -T <file>` — a real file, so Content-Length is set; MinIO 411-rejects chunked presigned PUTs);
|
|
13
|
-
* 3. after fan-out the leader fetches the object over a presigned GET on the CONTROL PLANE (fetchUploadedDiff).
|
|
14
|
-
* A worker that skips the upload surfaces as `{ok:false}` → merge quarantines it (honest failure, no hang).
|
|
15
|
-
* pullDiff stays as the compat path for static (leader-owned) envs — M2 降兼容.
|
|
16
|
-
*/
|
|
17
|
-
// 字节帽与 `safeRev` 同源取自 pull 腿(理由见 diffout.ts 的 DEFAULT_MAX_DIFF_BYTES 顶注:同值双抄一旦
|
|
18
|
-
// 单边改值,上行/pull 两腿对同一个 diff 的过/拒判定就分家,且两边都「按自己的常量」静默正确)。
|
|
19
1
|
import { safeRev, DEFAULT_MAX_DIFF_BYTES } from "./diffout.js";
|
|
20
2
|
import { OBJECT_STORE_IO_TIMEOUT_MS } from "../plugins/blob-backend.js";
|
|
21
|
-
/** The staged upload script. `repoDir`/`workDir` are deployment config; `baseSha` is interpolated into a
|
|
22
|
-
* shell line, so it is re-validated HERE (fail-closed) — callers should already have applied diffout's
|
|
23
|
-
* safeRev rules, but this function is the last line before the shell. The patch is written OUTSIDE the repo
|
|
24
|
-
* dir so it never shows up as untracked noise in the worker's `git status`. */
|
|
25
3
|
export function buildUploadScript(p) {
|
|
26
4
|
if (!safeRev(p.baseSha))
|
|
27
5
|
throw new Error(`buildUploadScript: unsafe baseSha ${JSON.stringify(p.baseSha)}`);
|
|
28
|
-
// presignS3Url percent-encodes all specials today, so a "'" can't occur — escape anyway (belt-and-suspenders;
|
|
29
|
-
// the URL sits inside single quotes in the script below).
|
|
30
6
|
const putUrl = p.putUrl.replaceAll("'", "'\\''");
|
|
31
7
|
return [
|
|
32
8
|
"#!/bin/sh",
|
|
@@ -34,35 +10,25 @@ export function buildUploadScript(p) {
|
|
|
34
10
|
"set -e",
|
|
35
11
|
`cd ${p.repoDir}`,
|
|
36
12
|
`git format-patch --stdout ${p.baseSha}..HEAD > ${p.workDir}/.leader-out.patch`,
|
|
37
|
-
// -T <file> (not stdin): sets Content-Length — MinIO returns 411 on chunked presigned PUTs (gate#3 council#3).
|
|
38
13
|
`curl -fsS -X PUT -T ${p.workDir}/.leader-out.patch '${putUrl}'`,
|
|
39
14
|
`echo leader-diff-uploaded`,
|
|
40
15
|
"",
|
|
41
16
|
].join("\n");
|
|
42
17
|
}
|
|
43
|
-
/** The objective suffix that makes the upload the worker's last task step (no URL through the model). */
|
|
44
18
|
export function uploadStepSuffix(workDir) {
|
|
45
19
|
return `\n\nMANDATORY FINAL STEP: after your final commit, run exactly: sh ${workDir}/.leader-upload.sh — it exports your branch diff for integration. Do not modify, inspect, or skip it; if it fails, fix your git state (commit your work) and run it again.`;
|
|
46
20
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Fetch the worker-uploaded patch over a presigned GET (control plane; creds stay adapter-held). Returns the
|
|
49
|
-
* same result shape as pullDiff so merge consumes either path. 🔴 The presigned URL is a short-lived bearer
|
|
50
|
-
* credential — it must never appear in returned errors (gate#3 council#5 redaction rule).
|
|
51
|
-
*/
|
|
52
21
|
export async function fetchUploadedDiff(getUrl, opts = {}) {
|
|
53
22
|
const maxBytes = opts.maxBytes ?? DEFAULT_MAX_DIFF_BYTES;
|
|
54
23
|
const f = opts.fetchImpl ?? fetch;
|
|
55
24
|
let res;
|
|
56
25
|
try {
|
|
57
|
-
// C6(2026-08-03):abort 上界(半开 MinIO 此前把 leader merge 阶段挂满 undici ~300s,钉死整个
|
|
58
|
-
// fanout)。maxBytes 检查仍在 text() 缓冲之后=已知残留(流式读上界另案),本修只收超时轴。
|
|
59
26
|
res = await f(getUrl, { signal: AbortSignal.timeout(OBJECT_STORE_IO_TIMEOUT_MS) });
|
|
60
27
|
}
|
|
61
28
|
catch (e) {
|
|
62
29
|
return { ok: false, error: `diff fetch failed: ${e instanceof Error ? e.message.replaceAll(getUrl, "<presigned-url>") : "network error"}` };
|
|
63
30
|
}
|
|
64
31
|
if (res.status === 404 || res.status === 403) {
|
|
65
|
-
// MinIO answers 403 (signature scope) for a missing key on some configs — both mean "no upload landed".
|
|
66
32
|
return { ok: false, error: `no uploaded diff (worker skipped the mandatory upload step, or upload failed) [${res.status}]` };
|
|
67
33
|
}
|
|
68
34
|
if (!res.ok)
|
|
@@ -74,14 +40,7 @@ export async function fetchUploadedDiff(getUrl, opts = {}) {
|
|
|
74
40
|
return { ok: false, error: "uploaded diff is empty (worker committed nothing past base)" };
|
|
75
41
|
return { ok: true, patch, bytes: patch.length };
|
|
76
42
|
}
|
|
77
|
-
/** The minimal surface staging must run before (the agent reaches the workspace through these). */
|
|
78
43
|
const STAGED_METHODS = new Set(["exec", "execStream", "writeFile", "readFile"]);
|
|
79
|
-
/**
|
|
80
|
-
* Wrap an ExecutionEnv so a one-shot `stage(env)` (seed repo + write the upload script) runs before the FIRST
|
|
81
|
-
* workspace-touching call — the adapters are lazy-connect, so there is no explicit connect() hook to ride; this
|
|
82
|
-
* guarantees seed-before-agent on whichever call comes first. A failed staging rejects that call (task fails
|
|
83
|
-
* honestly) and RESETS, so a retried call re-attempts staging instead of replaying a cached rejection.
|
|
84
|
-
*/
|
|
85
44
|
export function withStaging(env, stage, finalize) {
|
|
86
45
|
let staged;
|
|
87
46
|
const ensure = () => {
|
|
@@ -98,11 +57,6 @@ export function withStaging(env, stage, finalize) {
|
|
|
98
57
|
return v;
|
|
99
58
|
const fn = v.bind(t);
|
|
100
59
|
if (prop === "destroy" && finalize) {
|
|
101
|
-
// Belt for the MANDATORY upload step (found live: task02b run4 collapse-solo): a worker that did the
|
|
102
|
-
// porting but FORGOT `sh .leader-upload.sh` loses its work when the runner destroys the env — diff-out
|
|
103
|
-
// 404s and merge quarantines a worker that actually finished. Run `finalize` best-effort before the
|
|
104
|
-
// teardown, only if the env was ever staged (never spawn a pod just to upload nothing). Idempotent:
|
|
105
|
-
// re-running the upload script after a model-run upload overwrites the same object with the same diff.
|
|
106
60
|
return async (...args) => {
|
|
107
61
|
if (staged)
|
|
108
62
|
await Promise.resolve(staged).then(() => finalize(env)).catch(() => { });
|
|
@@ -112,7 +66,6 @@ export function withStaging(env, stage, finalize) {
|
|
|
112
66
|
if (!STAGED_METHODS.has(prop))
|
|
113
67
|
return fn;
|
|
114
68
|
if (prop === "execStream") {
|
|
115
|
-
// async-iterable return — staging must complete before the underlying stream starts.
|
|
116
69
|
return (...args) => (async function* () {
|
|
117
70
|
await ensure();
|
|
118
71
|
yield* fn(...args);
|
package/dist/leader/endpoint.js
CHANGED
|
@@ -1,31 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v2 MVP M4 — leader HTTP endpoint (V2-MVP-PLAN.md). Exposes `runLeaderTask` over HTTP:
|
|
3
|
-
* POST /v1/leader → 202 { leaderRunId, status:"running" }, runs in the background
|
|
4
|
-
* GET /v1/leader/:id → { id, status, result?, error? }
|
|
5
|
-
* A leader run is long (minutes: provision N E2B workers → real model coding → merge → push), so it is async
|
|
6
|
-
* (background + poll), mirroring /v1/runs. `runLeader` is injected (the real wiring lives in wire.ts /
|
|
7
|
-
* main.ts) so this handler is pure + mock-tested.
|
|
8
|
-
*
|
|
9
|
-
* ── 登记表的两条腿(#193 车4 件1,台账 P1-9)────────────────────────────────────────────────────────
|
|
10
|
-
* **默认腿 = durable SQL 店**(`opts.store`,`plugins/leader-run-store-sql.ts` 的双方言孪生)。它一次解掉
|
|
11
|
-
* 三条同源缺口:跨副本 GET(行在库里)、POST 提交幂等(`idem_key` UNIQUE 的原子赢或观察)、以及
|
|
12
|
-
* 「重复 spawn」这件事本身(准入处只放一条进去,worker 层就不需要那道由构造即无效的并发去重面 ——
|
|
13
|
-
* 它的删除理由见 `fanout.ts` 头注)。
|
|
14
|
-
*
|
|
15
|
-
* **降级腿 = 进程内 `Map`**(`opts.store` 缺席时)。它是**显式的单副本限定**形,不是「以后再补」:
|
|
16
|
-
* · 跨副本 GET 404、进程重启即失忆、幂等作用域只到本进程;
|
|
17
|
-
* · 生产部署不会落到这条腿上 —— config 层已把「`LEADER_ENABLED=true` 且无外部 SQL 店」整个组合拒启
|
|
18
|
-
* (#193 车4 件2 门②),所以这条腿的真实用户只有单元测试与手工试跑。
|
|
19
|
-
* 两腿的**可观察行为**除上述三条外逐位一致(同一套 4xx 码、同一套属主门、同一份 202/200 体)。
|
|
20
|
-
*/
|
|
21
1
|
import { uuidv7 } from "@sema-agent/core";
|
|
22
2
|
import { leaderIdemKey } from "../plugins/leader-run-store-sql.js";
|
|
23
|
-
/**
|
|
24
|
-
* LEADER-REPAIRLOOP-INTEGRATION §5/§10.6 — map a finished `LeaderResult` to the run status. The repair terminal
|
|
25
|
-
* (set only when LEADER_REPAIR_LOOP/LEADER_MEASURE_GATES drove a single-agent path or the merge push chokepoint
|
|
26
|
-
* held) refines the legacy `ok ? completed : failed` into a THIRD `needs_human` for an ABSTAIN (a held candidate
|
|
27
|
-
* awaiting a human), keeping `oracle.unprotected`/`gave_up` as `failed` (carry the terminal as the WHY —
|
|
28
|
-
* no-silent-failures). Undefined `repairTerminal` ⇒ the exact legacy mapping (byte-identical default path). */
|
|
29
3
|
function statusForLeaderResult(result) {
|
|
30
4
|
switch (result.repairTerminal) {
|
|
31
5
|
case "candidate_only":
|
|
@@ -36,20 +10,13 @@ function statusForLeaderResult(result) {
|
|
|
36
10
|
case "gave_up":
|
|
37
11
|
return "failed";
|
|
38
12
|
default:
|
|
39
|
-
// `fixed` (not reachable SAFE-tier) or undefined → the legacy ok-based mapping.
|
|
40
13
|
return result.ok ? "completed" : "failed";
|
|
41
14
|
}
|
|
42
15
|
}
|
|
43
16
|
const LEADER_ID_RE = /^\/v1\/leader\/([^/]+)$/;
|
|
44
|
-
/** How long a FINISHED leader run stays pollable before it's evicted from the in-memory registry.
|
|
45
|
-
* Bounds PROCESS MEMORY on the degraded arm — it is not a retention policy (the durable table keeps its rows;
|
|
46
|
-
* see the store's header for why it has no reaper leg). */
|
|
47
17
|
const RUN_RETENTION_MS = 60 * 60_000;
|
|
48
|
-
/** 终局写库的有界重试(codex R1-high③):一次瞬断不该永久丢掉一条真 run 的终局。 */
|
|
49
18
|
const TERMINAL_WRITE_ATTEMPTS = 3;
|
|
50
19
|
const TERMINAL_WRITE_RETRY_MS = 200;
|
|
51
|
-
/** `Idempotency-Key` 的形:1–255 且无首尾空白(与 `/v1/runs` 的 zod `.max(255)` + 店门口
|
|
52
|
-
* `assertIdempotencyKeyShape` 同规)。坏形 ⇒ 400,不静默忽略 —— 忽略一个去重键 = 悄悄放行第二次真 push。 */
|
|
53
20
|
function idemShapeError(raw) {
|
|
54
21
|
if (raw.length === 0 || raw.length > 255 || raw.trim() !== raw) {
|
|
55
22
|
return `invalid Idempotency-Key: must be 1-255 chars with no leading/trailing whitespace (got length ${raw.length})`;
|
|
@@ -58,10 +25,7 @@ function idemShapeError(raw) {
|
|
|
58
25
|
}
|
|
59
26
|
export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
60
27
|
const runs = new Map();
|
|
61
|
-
/** 降级臂的进程内幂等索引(scope 过的键 → run id)。store 在场时不使用(库上的 UNIQUE 才是执法者)。 */
|
|
62
28
|
const memIdem = new Map();
|
|
63
|
-
// Bound the in-memory registry: a finished leader run stays pollable for RUN_RETENTION_MS, then is evicted.
|
|
64
|
-
// Swept on each POST (amortized, no timer) so the map can't grow without limit over a long-lived process.
|
|
65
29
|
function reap() {
|
|
66
30
|
const cutoff = Date.now() - RUN_RETENTION_MS;
|
|
67
31
|
for (const [id, run] of runs) {
|
|
@@ -73,13 +37,12 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
73
37
|
}
|
|
74
38
|
}
|
|
75
39
|
}
|
|
76
|
-
/** 后台驱动一条已准入的 run。两腿共用:终局既写本地 Map(drain 门 + 降级臂的读面),又写库(若在场)。 */
|
|
77
40
|
function drive(id, body) {
|
|
78
41
|
void (async () => {
|
|
79
42
|
let terminal;
|
|
80
43
|
try {
|
|
81
44
|
const result = await runLeader(body);
|
|
82
|
-
const status = statusForLeaderResult(result);
|
|
45
|
+
const status = statusForLeaderResult(result);
|
|
83
46
|
terminal = { status: status, result };
|
|
84
47
|
opts.logger?.info?.("leader_run_done", { id, ok: result.ok, status, ...(result.repairTerminal ? { repairTerminal: result.repairTerminal } : {}), merged: result.merge && result.merge.ok ? result.merge.merged : undefined });
|
|
85
48
|
}
|
|
@@ -87,13 +50,7 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
87
50
|
terminal = { status: "failed", error: e instanceof Error ? e.message : String(e) };
|
|
88
51
|
opts.logger?.error?.("leader_run_error", { id, err: terminal.error });
|
|
89
52
|
}
|
|
90
|
-
// 🔴 codex R1-high③(验真后修):**先落库,后改本地记录**。`inflight()` 数的是本地 `running`,
|
|
91
|
-
// 而 drain 门读的正是它 —— 反过来写(先改本地再 await 落库)会让 SIGTERM 在终局 SQL 还在飞时看到 0,
|
|
92
|
-
// hardShutdown 关掉连接池 ⇒ 终局写丢 ⇒ 别的副本永远读到 `running`,而代码可能已经真 push 过了。
|
|
93
|
-
// 顺序反过来之后,这条 run 直到库写**尘埃落定**(成功或彻底失败)才退出 inflight 集。
|
|
94
53
|
if (opts.store) {
|
|
95
|
-
// 有界重试:一次瞬断(连接被回收/主从切换)不该让一条真 run 的终局永久丢失。重试是**有界**的
|
|
96
|
-
// ——停机窗里无限重试只会把 drain 拖死,而那时该做的是响亮记一笔让运维手工对账。
|
|
97
54
|
for (let attempt = 1;; attempt += 1) {
|
|
98
55
|
try {
|
|
99
56
|
const moved = await opts.store.finishRun(id, terminal);
|
|
@@ -121,7 +78,6 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
121
78
|
}
|
|
122
79
|
})();
|
|
123
80
|
}
|
|
124
|
-
/** GET 的公共出口形(两腿同一份体)。 */
|
|
125
81
|
function getBody(run) {
|
|
126
82
|
return {
|
|
127
83
|
status: 200,
|
|
@@ -152,19 +108,12 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
152
108
|
const bad = idemShapeError(idempotencyKey);
|
|
153
109
|
if (bad)
|
|
154
110
|
return { status: 400, body: { error: bad, errorCode: "request.body_shape" } };
|
|
155
|
-
// 🔴 按提交者 scope:两个 principal 用了同一个字面键,不该撞进同一条 run —— 那既是一次别人的
|
|
156
|
-
// run id 泄漏(存在性预言机),也会让 B 的提交被 A 的 run 静默吞掉。
|
|
157
111
|
scopedIdem = leaderIdemKey(idempotencyKey, owner);
|
|
158
112
|
}
|
|
159
113
|
reap();
|
|
160
114
|
if (opts.store) {
|
|
161
115
|
const { created, run } = await opts.store.createRun({ objective: b.objective, owner, idemKey: scopedIdem });
|
|
162
|
-
// 幂等重投:回既有行,**不**起第二条后台腿(第二次 = 第二次真 git push)。
|
|
163
116
|
if (!created) {
|
|
164
|
-
// 🔴 codex R1-high④(验真后修):重放**必须校属主**。`idem_key` 编码的是提交时的 owner,而
|
|
165
|
-
// `owner` 列会被身份收编(design/183)重绑 —— 于是旧身份拿旧键重投,可以命中一条**现属他人**的行。
|
|
166
|
-
// 直接回显 = 把别人的 leaderRunId 与状态泄漏出去。姿势照 `/v1/runs` 的 clientTaskId 重放先例:
|
|
167
|
-
// 属主不符 ⇒ 409 冲突,不回显任何标识(不给跨租户预言机)。
|
|
168
117
|
if (run.owner != null && run.owner !== owner) {
|
|
169
118
|
return { status: 409, body: { error: "idempotency key already used by another principal", errorCode: "conflict.leader_run_exists" } };
|
|
170
119
|
}
|
|
@@ -174,7 +123,6 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
174
123
|
drive(run.id, b);
|
|
175
124
|
return { status: 202, body: { leaderRunId: run.id, status: "running" } };
|
|
176
125
|
}
|
|
177
|
-
// 降级臂(无 store):作用域=本进程的同一套语义。
|
|
178
126
|
if (scopedIdem) {
|
|
179
127
|
const prior = memIdem.get(scopedIdem);
|
|
180
128
|
const priorRun = prior ? runs.get(prior) : undefined;
|
|
@@ -182,7 +130,7 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
182
130
|
return { status: 202, body: { leaderRunId: priorRun.id, status: priorRun.status } };
|
|
183
131
|
}
|
|
184
132
|
const id = uuidv7();
|
|
185
|
-
runs.set(id, { id, status: "running", startedAt: Date.now(), owner });
|
|
133
|
+
runs.set(id, { id, status: "running", startedAt: Date.now(), owner });
|
|
186
134
|
if (scopedIdem)
|
|
187
135
|
memIdem.set(scopedIdem, id);
|
|
188
136
|
drive(id, b);
|
|
@@ -191,9 +139,6 @@ export function createLeaderEndpoint(runLeader, opts = {}) {
|
|
|
191
139
|
const m = method === "GET" ? LEADER_ID_RE.exec(url) : null;
|
|
192
140
|
if (m) {
|
|
193
141
|
const id = m[1];
|
|
194
|
-
// BL-4 owner check: an owned run is visible ONLY to its owner — 404 (not 403) to anyone else, so a
|
|
195
|
-
// non-owner can't even confirm the id exists. owner null/undefined = anonymous/single-tenant run,
|
|
196
|
-
// visible to any authenticated caller (back-compat; mirrors GET /v1/runs/:id's runOwnerOk).
|
|
197
142
|
const ownerOk = (owner) => owner == null || owner === (requester ?? null);
|
|
198
143
|
if (opts.store) {
|
|
199
144
|
const run = await opts.store.getRun(id);
|
package/dist/leader/fanout.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/** A tiny counting semaphore (FIFO-ish) bounding concurrent worker execution. */
|
|
2
1
|
class Semaphore {
|
|
3
2
|
max;
|
|
4
3
|
active = 0;
|
|
@@ -19,23 +18,6 @@ class Semaphore {
|
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
20
|
}
|
|
22
|
-
/**
|
|
23
|
-
* 「这条 worker 的 TaskResult 停在门上(durable park)了吗」——`TaskStatus` 的穷举判据。
|
|
24
|
-
*
|
|
25
|
-
* 🔴 park 是**两个词**(扫描P2,status/kind 同名词混淆病族):`suspended`(工具审批 / human /
|
|
26
|
-
* resource_limit 门)与 `needs_review`(`plan_review` 与 dry-run 拦截门)。真源 = core
|
|
27
|
-
* `agents/suspend-guard.js` 的 `isDurablePause`(逐字 `status === "suspended" || status === "needs_review"`),
|
|
28
|
-
* `mapNestedSuspend` / `verify.js` 的 `unverifiedReason` 两处都按这张表分词。
|
|
29
|
-
*
|
|
30
|
-
* 只认 `suspended` 的后果不是「少一个标签」:一条 plan_review 停下的 worker 会被报成 `failed` ⇒
|
|
31
|
-
* fanOut 的 C4 抢占({@link FanOutOptions.cancelOnSuspend})不触发(其余 worker 继续烧钱)、leader 的
|
|
32
|
-
* replan「suspended 波」看不见它、`suspendedSessions` 的豁免名单漏掉它 ⇒ 它那张还能被决议的 checkpoint
|
|
33
|
-
* 与暂停中的环境会被当失败清掉。带验证器的那条腿早已改用「还带着 checkpointToken 就是 park」的判据
|
|
34
|
-
* (leader.ts round-3 review BLOCKER),这里是它当时漏掉的另外半场。
|
|
35
|
-
*
|
|
36
|
-
* `never` 收敛 = core `TaskStatus` 增删成员的**编译期**执行点(同 `store-contracts.ts` 两条判据的姿势);
|
|
37
|
-
* 运行时未知词保守判**非 park**(与既有 `else → failed` 同行为,不静默扩大 park 面)。
|
|
38
|
-
*/
|
|
39
21
|
export function isDurablePauseStatus(s) {
|
|
40
22
|
switch (s) {
|
|
41
23
|
case "suspended":
|
|
@@ -55,30 +37,17 @@ export function isDurablePauseStatus(s) {
|
|
|
55
37
|
function mapStatus(s) {
|
|
56
38
|
return s === "completed" ? "completed" : isDurablePauseStatus(s) ? "suspended" : "failed";
|
|
57
39
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Fan out `subtasks` in parallel. Never throws — every worker resolves to a WorkerReport (failures captured).
|
|
60
|
-
* The caller (Coordinator) merges only `status==="completed"` reports.
|
|
61
|
-
*/
|
|
62
40
|
export async function fanOut(subtasks, opts) {
|
|
63
41
|
const sharedAC = new AbortController();
|
|
64
42
|
const streams = new Map();
|
|
65
43
|
let cancelled = false;
|
|
66
44
|
const cancelAll = async () => {
|
|
67
45
|
cancelled = true;
|
|
68
|
-
// Abort FIRST (council round-2): one signal both aborts RUNNING workers (their shared spec.signal) AND
|
|
69
|
-
// stops NOT-YET-STARTED ones — runOne's `sharedAC.signal.aborted` guard returns before provisioning a VM.
|
|
70
|
-
// Closes the race where a worker registering AFTER the snapshot would miss it. Then destroy() each registered
|
|
71
|
-
// stream (core 1.71): abort + await settle + CAS-expire the checkpoint (fences any concurrent resume) + reap
|
|
72
|
-
// a SUSPENDED worker's paused container — so a cancel that hits a suspended worker leaks neither a paused
|
|
73
|
-
// container nor an orphan checkpoint. The leader (leader.ts) still destroys each worker's static env in its
|
|
74
|
-
// own finally; destroy() here reaps the (durable) checkpoint — the two are disjoint (core [R47]).
|
|
75
46
|
sharedAC.abort();
|
|
76
47
|
await Promise.allSettled([...streams.values()].map((s) => s.destroy()));
|
|
77
48
|
};
|
|
78
49
|
const sem = new Semaphore(opts.maxConcurrency);
|
|
79
50
|
let pendingCancel;
|
|
80
|
-
// C4: a SUSPENDED settle preempts the remaining fan-out (the settled worker is already out of `streams`,
|
|
81
|
-
// so cancelAll's destroy sweep is exempt of it by construction — its checkpoint/paused env survive).
|
|
82
51
|
const maybeCancelOnSuspend = (r) => {
|
|
83
52
|
if (opts.cancelOnSuspend && r.status === "suspended" && !cancelled) {
|
|
84
53
|
opts.logger?.info?.("fanout_suspend_cancel", { suspendedWorker: r.workerId });
|
|
@@ -91,8 +60,6 @@ export async function fanOut(subtasks, opts) {
|
|
|
91
60
|
return sem.run(async () => {
|
|
92
61
|
if (sharedAC.signal.aborted)
|
|
93
62
|
return { ...base, status: "timeout", error: "cancelled before start" };
|
|
94
|
-
// Per-worker run override (verify path): reuse the semaphore + shared abort signal; map throws like the
|
|
95
|
-
// default path so runOne still never rejects (Promise.all stays safe).
|
|
96
63
|
if (opts.runWorker) {
|
|
97
64
|
try {
|
|
98
65
|
return maybeCancelOnSuspend(await opts.runWorker(st, sharedAC.signal));
|
|
@@ -115,32 +82,24 @@ export async function fanOut(subtasks, opts) {
|
|
|
115
82
|
report = { ...base, status: mapStatus(result.status), stats: result.stats, ...(result.errorMessage ? { error: result.errorMessage } : {}) };
|
|
116
83
|
}
|
|
117
84
|
catch (e) {
|
|
118
|
-
// interrupted/aborted workers may reject here — report, don't propagate.
|
|
119
85
|
report = { ...base, status: cancelled ? "timeout" : "failed", error: e instanceof Error ? e.message : String(e) };
|
|
120
86
|
}
|
|
121
87
|
finally {
|
|
122
88
|
streams.delete(st.workerId);
|
|
123
89
|
}
|
|
124
|
-
// AFTER deregistration — so a suspend-triggered cancelAll can never destroy THIS worker's own stream
|
|
125
|
-
// (destroying a suspended stream = CAS-expiring its checkpoint, the exact thing C4 forbids).
|
|
126
90
|
return maybeCancelOnSuspend(report);
|
|
127
91
|
});
|
|
128
92
|
};
|
|
129
|
-
// Record each worker's report as it settles so the overall deadline can HARD-bound the wait: if a worker's
|
|
130
|
-
// stream.result() ignores the abort, `Promise.all(work)` would never resolve and `await work` below would hang
|
|
131
|
-
// PAST the deadline (the overall timeout would be only "soft"). On the grace expiry we return whatever settled
|
|
132
|
-
// + a synthesized `timeout` for the rest, so fanOut always returns within timeoutMs + the cancel grace.
|
|
133
93
|
const results = new Map();
|
|
134
94
|
const work = Promise.all(subtasks.map((st) => runOne(st).then((r) => { results.set(st.workerId, r); return r; })));
|
|
135
95
|
const timer = new Promise((resolve) => {
|
|
136
96
|
const id = setTimeout(() => resolve("timeout"), opts.timeoutMs);
|
|
137
|
-
void work.then(() => clearTimeout(id));
|
|
97
|
+
void work.then(() => clearTimeout(id));
|
|
138
98
|
});
|
|
139
99
|
const raced = await Promise.race([work.then(() => "done"), timer]);
|
|
140
100
|
if (raced === "timeout") {
|
|
141
101
|
opts.logger?.warn?.("fanout_timeout", { timeoutMs: opts.timeoutMs, workers: subtasks.length });
|
|
142
102
|
await cancelAll();
|
|
143
|
-
// Hard bound: give cancelAll a grace window to settle the running workers, then stop waiting regardless.
|
|
144
103
|
const graceMs = opts.cancelGraceMs ?? CANCEL_GRACE_MS;
|
|
145
104
|
const grace = new Promise((resolve) => {
|
|
146
105
|
const id = setTimeout(() => resolve("grace"), graceMs);
|
|
@@ -154,11 +113,10 @@ export async function fanOut(subtasks, opts) {
|
|
|
154
113
|
};
|
|
155
114
|
}
|
|
156
115
|
}
|
|
157
|
-
const reports = await work;
|
|
116
|
+
const reports = await work;
|
|
158
117
|
if (pendingCancel)
|
|
159
|
-
await pendingCancel.catch(() => { });
|
|
118
|
+
await pendingCancel.catch(() => { });
|
|
160
119
|
return { reports, cancelled };
|
|
161
120
|
}
|
|
162
|
-
/** Grace window after `cancelAll` before fanOut returns regardless — bounds a worker that ignores the abort. */
|
|
163
121
|
const CANCEL_GRACE_MS = 30_000;
|
|
164
122
|
//# sourceMappingURL=fanout.js.map
|
|
@@ -1,48 +1,9 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The OUT-OF-PROCESS grader plumbing for the leader × `runRepairLoop` integration
|
|
3
|
-
* (`sema-internal server/docs/LEADER-REPAIRLOOP-INTEGRATION.md` §3.2 + the authoritative §10 layer). Two distinct consumers:
|
|
4
|
-
*
|
|
5
|
-
* - **R1 (single-agent oracle, §10.1)** — the `RepairOracle` closure (`repair-oracle.ts`) reuses the EXISTING
|
|
6
|
-
* integration sandbox as its grader at ZERO extra sandbox cost. It needs only a `GraderHandle` (a writeFile +
|
|
7
|
-
* exec bound to that sandbox via the wire's `asIntegrationEnv`/`sh` pattern) and the worker-diff source — it
|
|
8
|
-
* grades through `runExecGate(graderEnv, steps)` itself, so it does NOT use `applyAndGrade` (the closure owns
|
|
9
|
-
* its hermetic reset+apply inline; `repair-oracle.ts:89`). This file gives the merge path the SAME apply-and-
|
|
10
|
-
* grade primitive so the two stay byte-consistent.
|
|
11
|
-
*
|
|
12
|
-
* - **R2 (measure-drives-repair, §10.4)** — the merge path (`merge.ts`, Stage-2) RE-SEEDS a single minted
|
|
13
|
-
* grader per measure round (NOT a new microVM): `git reset --hard <integBase> && git clean -fdx`, re-apply the
|
|
14
|
-
* INTEGRATED patch, then run the trusted `measureCmd` there. The repair agent edits the merged sandbox, so the
|
|
15
|
-
* measure must NOT run in that (possibly poisoned) sandbox — `applyAndGrade` runs it in a fresh-seeded grader.
|
|
16
|
-
* Before re-measure, the caller mechanically checks the patch's changed-file list against an allowlist
|
|
17
|
-
* (`changedFilesWithinAllowlist`, §10.4(ii)) — an out-of-allowlist / build-config touch denies the candidate.
|
|
18
|
-
*
|
|
19
|
-
* A standalone, mint-a-distinct-sandbox grader belongs to the AUTO-ACCEPT slice, which has NOT been designed or
|
|
20
|
-
* built. v1 reuses the integration sandbox (R1) + re-seeds it (R2) instead. **There is no config key that turns
|
|
21
|
-
* such a factory on** — an earlier revision of this header named one and shipped a `throw`-only stub beside it,
|
|
22
|
-
* but no such key ever existed in `config.ts`, so an operator following that text set an env var that did
|
|
23
|
-
* nothing. Both the stub and the promise were removed (#193 车4 件3 / staleness ledger P2-9); the auto-accept
|
|
24
|
-
* slice, when it happens, is a new design件 that lands its factory, its wiring and its knob in one batch.
|
|
25
|
-
*
|
|
26
|
-
* Everything here is additive + OFF by default: nothing in this file runs unless the Stage-2 seams call it behind
|
|
27
|
-
* `LEADER_REPAIR_LOOP` / `LEADER_MEASURE_GATES`.
|
|
28
|
-
*/
|
|
29
1
|
import { runExecGate } from "@sema-agent/core";
|
|
30
2
|
import { shellQuote as sq } from "../plugins/remote-shell.js";
|
|
31
|
-
/**
|
|
32
|
-
* §10.1/§10.4 — HERMETIC apply-and-grade. Reset the grader to its clean `integBase` (`git reset --hard` +
|
|
33
|
-
* `git clean -fdx`), apply the FULL `base→latest` `patch` (never cumulative onto an already-applied tree), then
|
|
34
|
-
* run the TRUSTED, spec-derived `steps` (`testCmd` / `measureCmd`) there. `steps` MUST NOT be worker-authored
|
|
35
|
-
* (the `runExecGate` security contract — exec-gate.ts header). Used by the R2 measure grader; the R1 oracle
|
|
36
|
-
* closure performs the equivalent inline (`repair-oracle.ts`) so the single-agent and merge paths grade
|
|
37
|
-
* identically. Never throws — an apply conflict or a step failure is a non-passing `GradeOutcome`.
|
|
38
|
-
*/
|
|
39
3
|
export async function applyAndGrade(handle, patch, steps) {
|
|
40
4
|
const repo = handle.repoDir;
|
|
41
5
|
const patchPath = `/tmp/grade-${Date.now()}.patch`;
|
|
42
6
|
await handle.writeFile(patchPath, patch);
|
|
43
|
-
// Hermetic prep: clean base, then apply the full base→latest diff. `--whitespace=nowarn` matches `merge.ts:144`
|
|
44
|
-
// and `repair-oracle.ts:93`. An apply failure is a non-flaky, non-applied failure (a candidate that no longer
|
|
45
|
-
// applies onto the clean base — e.g. it raced the integration head).
|
|
46
7
|
const prep = await runExecGate(handle.env, [
|
|
47
8
|
{ command: `git -C ${sq(repo)} reset --hard ${sq(handle.integBase)} && git -C ${sq(repo)} clean -fdx`, label: "grader-reset" },
|
|
48
9
|
{ command: `git -C ${sq(repo)} apply --whitespace=nowarn ${sq(patchPath)}`, label: "grader-apply" },
|
|
@@ -57,21 +18,6 @@ export async function applyAndGrade(handle, patch, steps) {
|
|
|
57
18
|
const trace = gate.steps.map((s) => `${s.label}\n${s.stdout}\n${s.stderr}`).join("\n").slice(-4000);
|
|
58
19
|
return { passed: false, applied: true, trace };
|
|
59
20
|
}
|
|
60
|
-
/**
|
|
61
|
-
* §10.4(ii) — the mechanical CHANGED-FILES ALLOWLIST guard. A "fresh sandbox" is necessary-NOT-sufficient: poison
|
|
62
|
-
* can ride INSIDE the integrated patch (a malicious `build.gradle` / git hook / test shim). Before re-measuring,
|
|
63
|
-
* diff the patch's changed-file list against the allowlist = the workers' planned `files` ∪ trusted spec dirs.
|
|
64
|
-
* Any out-of-allowlist path, or a build-config touch (gradle / settings / dependency manifests / git hooks), is a
|
|
65
|
-
* DENY — the caller downgrades the candidate to `needs_human_oracle` (the prompt-only "do NOT modify build config"
|
|
66
|
-
* `wire.ts:269` is unreliable; this makes it mechanical). Returns the offending paths (empty ⇒ within allowlist).
|
|
67
|
-
*
|
|
68
|
-
* `changedFiles` is parsed by the caller from the patch (`git apply --numstat` / `git diff --name-only`); this is
|
|
69
|
-
* the PURE policy over that list so it is unit-testable without a sandbox. Matching is path-prefix on normalized
|
|
70
|
-
* POSIX paths: an allowlist entry `"."` or `""` allows everything UNDER it (a worker that legitimately owns the
|
|
71
|
-
* whole tree); a file allow-entry matches exactly; a dir allow-entry (trailing `/` or a real dir) matches its
|
|
72
|
-
* subtree. Build-config patterns are denied even when nominally within the allowlist (a worker must not be able
|
|
73
|
-
* to launder a `build.gradle` edit through an over-broad `files:["."]`).
|
|
74
|
-
*/
|
|
75
21
|
const BUILD_CONFIG_DENY = [
|
|
76
22
|
/(^|\/)build\.gradle(\.kts)?$/,
|
|
77
23
|
/(^|\/)settings\.gradle(\.kts)?$/,
|
|
@@ -96,12 +42,11 @@ const norm = (p) => p.replace(/^\.\/+/, "").replace(/\/+$/, "");
|
|
|
96
42
|
function withinAllowEntry(file, entry) {
|
|
97
43
|
const e = norm(entry);
|
|
98
44
|
if (e === "" || e === ".")
|
|
99
|
-
return true;
|
|
45
|
+
return true;
|
|
100
46
|
if (file === e)
|
|
101
|
-
return true;
|
|
102
|
-
return file.startsWith(`${e}/`);
|
|
47
|
+
return true;
|
|
48
|
+
return file.startsWith(`${e}/`);
|
|
103
49
|
}
|
|
104
|
-
/** The §10.4(ii) decision: the subset of `changedFiles` that is OUT of the allowlist or hits a build-config deny. */
|
|
105
50
|
export function changedFilesOutOfAllowlist(changedFiles, allowlist) {
|
|
106
51
|
const files = changedFiles.map(norm).filter((f) => f !== "");
|
|
107
52
|
const offending = [];
|
|
@@ -115,21 +60,7 @@ export function changedFilesOutOfAllowlist(changedFiles, allowlist) {
|
|
|
115
60
|
}
|
|
116
61
|
return offending;
|
|
117
62
|
}
|
|
118
|
-
/** §10.4(ii): true iff EVERY changed file is within the allowlist and none is a denied build-config path. */
|
|
119
63
|
export function changedFilesWithinAllowlist(changedFiles, allowlist) {
|
|
120
64
|
return changedFilesOutOfAllowlist(changedFiles, allowlist).length === 0;
|
|
121
65
|
}
|
|
122
|
-
/*
|
|
123
|
-
* §3.2 的「standalone 铸新沙箱 grader 工厂」曾以 `GraderEnvFactoryCfg` / `MintedGrader` / `createGraderEnvFactory`
|
|
124
|
-
* 三件的形式停在这里:一个固定好的签名 + 一个必抛的 not-implemented 实现,理由写的是「签名先定好,将来
|
|
125
|
-
* 自动接受切片直接接线,不改契约」。#193 车4(台账 P2-9)裁定删除,理由三条:
|
|
126
|
-
* ① 它零调用零测试,而它承诺的开关(见文件头注)在 `config.ts` 里从来不存在 —— 「签名已定」这句话
|
|
127
|
-
* 本身在骗读者:没有任何消费方按它写过一行码,所谓的契约稳定性没有被任何东西验证过;
|
|
128
|
-
* ② 一个 `throw` 工厂在 grep 结果里长得像「这条路已经有了」,恰恰是本仓 doc-rot 战役反复清理的那一类
|
|
129
|
-
* 「过期的在哪里比缺失的在哪里更危险」;
|
|
130
|
-
* ③ 真需求出现时,铸沙箱 + 播种 + oracle 隔离本来就要跟着 `assertOracleIsolation` 一起重新设计
|
|
131
|
-
* (§10.2 明说 v1 OMITS immutableOraclePaths),旧签名大概率也不会被原样采用。
|
|
132
|
-
* 复原坐标(需要时按此重建):sema-internal `server/docs/LEADER-REPAIRLOOP-INTEGRATION.md` §3.2/§7,
|
|
133
|
-
* 以及本文件的 git 历史(#193 车4 之前的任一版本)。
|
|
134
|
-
*/
|
|
135
66
|
//# sourceMappingURL=grader-env-factory.js.map
|