@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.
Files changed (345) hide show
  1. package/USAGE.md +18 -0
  2. package/dist/adoption/plan.js +31 -192
  3. package/dist/adoption/quiesce.js +3 -86
  4. package/dist/adoption/runner.js +3 -140
  5. package/dist/adoption/sql.js +0 -74
  6. package/dist/adoption/wire.js +0 -80
  7. package/dist/approval-ask-machine.js +0 -75
  8. package/dist/approval-card.js +0 -323
  9. package/dist/approval-deny-reasons.js +0 -42
  10. package/dist/approval-hmac.js +0 -35
  11. package/dist/approval-reconciler.js +8 -166
  12. package/dist/approval.js +4 -67
  13. package/dist/audit.js +1 -44
  14. package/dist/auth-bridge.js +4 -47
  15. package/dist/auth-keys.js +0 -23
  16. package/dist/bake-runner/main.js +4 -65
  17. package/dist/bake-runner/protocol.js +3 -58
  18. package/dist/bake-runner/runner.js +5 -91
  19. package/dist/bench/l8/artifact.js +2 -61
  20. package/dist/bench/l8/escape.js +0 -25
  21. package/dist/bench/l8/index.js +0 -14
  22. package/dist/bench/l8/probes.js +0 -60
  23. package/dist/bench/l8/run-probes.js +2 -45
  24. package/dist/bench/s1/arms.js +14 -189
  25. package/dist/bench/s1/live-deps.js +11 -253
  26. package/dist/bench/s1/oracle.js +1 -36
  27. package/dist/bench/s1/repair-oracle-adapter.js +0 -30
  28. package/dist/bench/s1/reviewer.js +0 -34
  29. package/dist/bench/s1/row.js +0 -62
  30. package/dist/bench/s1/run-firm.js +4 -70
  31. package/dist/bench/s1/runner-ctx.js +0 -40
  32. package/dist/bench/s1/tasks.js +0 -115
  33. package/dist/boot/adoption.js +0 -20
  34. package/dist/boot/budget-tracing.js +3 -55
  35. package/dist/boot/config-center.js +43 -551
  36. package/dist/boot/coordinators.js +1 -69
  37. package/dist/boot/crash-last.js +0 -18
  38. package/dist/boot/deferred-sandbox-path-env.js +3 -119
  39. package/dist/boot/execution-env.js +5 -171
  40. package/dist/boot/governance-seams.js +0 -92
  41. package/dist/boot/leader.js +0 -69
  42. package/dist/boot/limit-sync.js +0 -2
  43. package/dist/boot/memory-boundary.js +3 -91
  44. package/dist/boot/org-memory.js +1 -25
  45. package/dist/boot/parked-revive-gate.js +0 -150
  46. package/dist/boot/permission-rules-audit.js +1 -56
  47. package/dist/boot/reapers.js +15 -273
  48. package/dist/boot/resolve-spec.js +9 -768
  49. package/dist/boot/retention-lane.js +4 -87
  50. package/dist/boot/runner-deps.js +3 -297
  51. package/dist/boot/runtime-caps.js +0 -40
  52. package/dist/boot/session-faces.js +2 -133
  53. package/dist/boot/shutdown.js +9 -83
  54. package/dist/boot/side-query-lane.js +2 -137
  55. package/dist/boot/stores.js +15 -215
  56. package/dist/boot/task-list-lane.js +0 -18
  57. package/dist/boot/webfetch-summarize-lane.js +0 -47
  58. package/dist/boot/workflow-orchestration.js +3 -99
  59. package/dist/boot-reclaim.js +0 -20
  60. package/dist/bounded-session-map.js +0 -19
  61. package/dist/brain.js +2 -139
  62. package/dist/budget.js +9 -244
  63. package/dist/capabilities/builtin-tools.js +0 -3
  64. package/dist/capabilities/center-plugins.js +6 -51
  65. package/dist/capabilities/center-prompts.js +6 -71
  66. package/dist/capabilities/code-review-council.js +7 -40
  67. package/dist/capabilities/collab-workflows.js +1 -44
  68. package/dist/capabilities/hands-lane.js +0 -65
  69. package/dist/capabilities/memory-notice.js +0 -70
  70. package/dist/capabilities/prompt.js +0 -14
  71. package/dist/capabilities/prompts/code-review.js +0 -14
  72. package/dist/capabilities/prompts/identity.js +0 -6
  73. package/dist/capabilities/prompts/team.js +0 -4
  74. package/dist/capabilities/repo-tools.js +0 -34
  75. package/dist/capabilities/sandbox-file-send.js +3 -92
  76. package/dist/capabilities/scenarios.d.ts +0 -1
  77. package/dist/capabilities/scenarios.js +18 -279
  78. package/dist/capabilities/select-environment-tool.js +0 -32
  79. package/dist/capabilities/send-user-file-tool.js +2 -66
  80. package/dist/capabilities/skills.d.ts +1 -2
  81. package/dist/capabilities/skills.js +3 -21
  82. package/dist/capabilities/team.d.ts +3 -11
  83. package/dist/capabilities/team.js +3 -51
  84. package/dist/capabilities/tool-defer.js +0 -3
  85. package/dist/config-center/apply-effective.js +15 -330
  86. package/dist/config-center/apply-ledger.js +5 -38
  87. package/dist/config-center/facade.js +0 -41
  88. package/dist/config-center/hot-keys-registry.js +0 -20
  89. package/dist/config-center/http-client.js +2 -125
  90. package/dist/config-center/mcp-revocation.js +2 -34
  91. package/dist/config-center/read-face.js +0 -59
  92. package/dist/config-center/restart-signal.js +1 -85
  93. package/dist/config-center/skills-mcp.d.ts +1 -1
  94. package/dist/config-center/skills-mcp.js +9 -76
  95. package/dist/config-center/stage-limits.js +8 -35
  96. package/dist/config-invariants.js +0 -16
  97. package/dist/config-lkg.js +0 -42
  98. package/dist/config-provider.js +3 -186
  99. package/dist/config-types.js +0 -5
  100. package/dist/config.js +107 -1145
  101. package/dist/degenerate-instrument.js +3 -67
  102. package/dist/deployment-governance.js +0 -124
  103. package/dist/digest-form.js +0 -12
  104. package/dist/elicitation.js +3 -86
  105. package/dist/env-facts.js +7 -75
  106. package/dist/fleet/fleet-bus.js +34 -507
  107. package/dist/fleet/fleet-reconciler.js +9 -149
  108. package/dist/fleet/fleet-terminal-window.js +10 -178
  109. package/dist/fleet/subagent-tail-bus.js +3 -72
  110. package/dist/fleet-client.js +10 -70
  111. package/dist/fleet-lease.js +5 -79
  112. package/dist/git-api-kind.js +0 -3
  113. package/dist/governance-ask-marks.js +2 -78
  114. package/dist/hooks/branch-transcript.js +0 -74
  115. package/dist/hooks/cc-agent-hook-prompt.js +0 -29
  116. package/dist/hooks/cc-stop-prompt.js +1 -46
  117. package/dist/hooks/hook-llm.js +1 -53
  118. package/dist/hooks/hook-runner.js +20 -414
  119. package/dist/http/active-run-conflict.js +4 -129
  120. package/dist/http/cursor-fingerprint.d.ts +5 -0
  121. package/dist/http/cursor-fingerprint.js +5 -0
  122. package/dist/http/idempotency.js +0 -37
  123. package/dist/http/principal-gate.js +3 -40
  124. package/dist/http/route-ctx.js +0 -9
  125. package/dist/http/routes/a2a-serve.js +7 -319
  126. package/dist/http/routes/admin-config-refresh.js +0 -4
  127. package/dist/http/routes/admin-drain.js +0 -10
  128. package/dist/http/routes/adoption.js +1 -29
  129. package/dist/http/routes/agents-roster.js +1 -47
  130. package/dist/http/routes/approvals-assistant.js +32 -395
  131. package/dist/http/routes/attachments.js +4 -22
  132. package/dist/http/routes/capabilities.js +3 -420
  133. package/dist/http/routes/diagnostics.js +1 -81
  134. package/dist/http/routes/fleet.js +9 -185
  135. package/dist/http/routes/images.js +14 -239
  136. package/dist/http/routes/leader.js +0 -13
  137. package/dist/http/routes/memory-bundle.js +1 -61
  138. package/dist/http/routes/memory-policy.js +9 -99
  139. package/dist/http/routes/notify-wake.js +3 -37
  140. package/dist/http/routes/observability.js +2 -19
  141. package/dist/http/routes/retention-ops.js +2 -34
  142. package/dist/http/routes/rules.js +1 -93
  143. package/dist/http/routes/runs.js +64 -873
  144. package/dist/http/routes/session-sync.js +19 -258
  145. package/dist/http/routes/sessions-list.js +17 -43
  146. package/dist/http/routes/sessions.js +34 -226
  147. package/dist/http/routes/shared-memory.js +5 -36
  148. package/dist/http/routes/side-query.js +1 -87
  149. package/dist/http/routes/tasks.js +52 -722
  150. package/dist/http/routes/trace-usage.js +32 -195
  151. package/dist/http/routes/workflows.js +19 -195
  152. package/dist/http/run-meta.js +0 -6
  153. package/dist/http/send.js +0 -32
  154. package/dist/http/server.js +92 -1597
  155. package/dist/http/sse-lifecycle.js +2 -13
  156. package/dist/http/sse-log.js +3 -48
  157. package/dist/http/tar.js +5 -21
  158. package/dist/http/verify-rounds.js +0 -5
  159. package/dist/http/wire-gate.js +0 -9
  160. package/dist/http/workspace-content.js +0 -10
  161. package/dist/images/bake-validate.js +1 -70
  162. package/dist/images/manifest.js +1 -6
  163. package/dist/index.js +0 -21
  164. package/dist/key-resolver.js +2 -17
  165. package/dist/leader/diffout.js +1 -20
  166. package/dist/leader/diffup.js +0 -47
  167. package/dist/leader/endpoint.js +2 -57
  168. package/dist/leader/fanout.js +3 -45
  169. package/dist/leader/grader-env-factory.js +3 -72
  170. package/dist/leader/leader.js +5 -156
  171. package/dist/leader/merge.js +8 -115
  172. package/dist/leader/planner.js +3 -54
  173. package/dist/leader/repair-oracle.js +1 -60
  174. package/dist/leader/repair-wire.js +2 -79
  175. package/dist/leader/wire.js +8 -307
  176. package/dist/lsp/e2b-bridge.js +4 -64
  177. package/dist/lsp/e2b-manager.js +6 -94
  178. package/dist/lsp/lsp-frames.js +0 -12
  179. package/dist/lsp/manager.js +4 -96
  180. package/dist/lsp/ws-transport.js +5 -55
  181. package/dist/lsp-evict.js +1 -15
  182. package/dist/main.js +57 -765
  183. package/dist/memory-bundle-engine.js +0 -55
  184. package/dist/memory-export.js +0 -4
  185. package/dist/memory-posture.js +1 -15
  186. package/dist/memory-scope.js +11 -148
  187. package/dist/memory-sync-client.js +2 -44
  188. package/dist/memory-sync.js +1 -80
  189. package/dist/model-select.js +3 -80
  190. package/dist/observability/cost-quota.js +1 -17
  191. package/dist/observability/cost-taxonomy.js +0 -34
  192. package/dist/observability/fail-open.js +7 -86
  193. package/dist/observability/logger.js +0 -6
  194. package/dist/observability/metrics.js +0 -94
  195. package/dist/observability/otel-exporter.js +3 -13
  196. package/dist/observability/principal-context.js +0 -9
  197. package/dist/observability/prompt-manifest.js +1 -37
  198. package/dist/observability/rate-limit.js +0 -4
  199. package/dist/observability/secret-env-scrub.js +2 -56
  200. package/dist/observability/tool-trace.js +1 -70
  201. package/dist/orchestration/hardened-vm-runner.js +4 -118
  202. package/dist/orchestration/hardened-vm-worker-runner.js +1 -26
  203. package/dist/orchestration/hardened-vm-worker.js +0 -27
  204. package/dist/orchestration/subagent-steer.js +1 -45
  205. package/dist/orchestration/workflow-agent-steer.js +1 -80
  206. package/dist/orchestration/workflow-completion-inbox.js +32 -285
  207. package/dist/orchestration/workflow-notify-journal.js +16 -259
  208. package/dist/org-memory-admission.js +3 -47
  209. package/dist/parent-watch.js +2 -48
  210. package/dist/parked-decide.js +1 -109
  211. package/dist/per-task-image.js +0 -57
  212. package/dist/plan-cache-probe.js +3 -27
  213. package/dist/plugins/adoption-log-sql.js +2 -119
  214. package/dist/plugins/approval-ask-store-memory.js +3 -38
  215. package/dist/plugins/approval-ask-store-sql.js +6 -188
  216. package/dist/plugins/approval-exemption-store.js +2 -28
  217. package/dist/plugins/background-agent-store-sql.js +4 -105
  218. package/dist/plugins/background-shell-support.js +14 -122
  219. package/dist/plugins/blob-backend.js +6 -169
  220. package/dist/plugins/breaker-state-sql.js +8 -46
  221. package/dist/plugins/caching-session-store.js +4 -106
  222. package/dist/plugins/checkpoint-store-sql.js +18 -505
  223. package/dist/plugins/e2b-orphan-reclaim.js +0 -45
  224. package/dist/plugins/file-outcome-sink.js +0 -9
  225. package/dist/plugins/file-resume-anchor-store.js +4 -43
  226. package/dist/plugins/file-run-store.js +26 -364
  227. package/dist/plugins/file-snapshot-store-sql.js +10 -181
  228. package/dist/plugins/fork-routing-session-store.js +8 -111
  229. package/dist/plugins/host-platform.js +2 -91
  230. package/dist/plugins/image-bake-store-sql.js +7 -250
  231. package/dist/plugins/image-index-sql.js +4 -123
  232. package/dist/plugins/k8s-bg-scripts.js +4 -88
  233. package/dist/plugins/k8s-exec-protocol.js +0 -41
  234. package/dist/plugins/leader-run-store-sql.js +0 -101
  235. package/dist/plugins/local-checkpoint-store.js +8 -128
  236. package/dist/plugins/local-session-store.js +34 -296
  237. package/dist/plugins/local-task-attachment-store.js +2 -16
  238. package/dist/plugins/mailbox-store-sql.js +8 -65
  239. package/dist/plugins/memory-embedder-fingerprint.js +5 -166
  240. package/dist/plugins/memory-embedder.js +4 -70
  241. package/dist/plugins/memory-engine-pg.js +6 -170
  242. package/dist/plugins/memory-engine-tidb.js +7 -155
  243. package/dist/plugins/memory-engine-vector-util.js +0 -10
  244. package/dist/plugins/memory-key-guards.js +0 -34
  245. package/dist/plugins/memory-origin-law.js +0 -187
  246. package/dist/plugins/memory-resume-anchor-store.js +0 -17
  247. package/dist/plugins/memory-run-store.js +12 -89
  248. package/dist/plugins/memory-session-policy-store.js +0 -17
  249. package/dist/plugins/memory-sync-store-pg.js +4 -49
  250. package/dist/plugins/memory-sync-store-tidb.js +3 -35
  251. package/dist/plugins/outcome-ledger-sql.js +3 -97
  252. package/dist/plugins/permission-rule-store-file.js +5 -133
  253. package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
  254. package/dist/plugins/permission-rule-store-sql.js +50 -307
  255. package/dist/plugins/pg-cost-quota.js +0 -7
  256. package/dist/plugins/pg-pool.js +0 -92
  257. package/dist/plugins/pg-rate-limiter.js +2 -13
  258. package/dist/plugins/pg-safe-json.js +4 -40
  259. package/dist/plugins/pg-session-storage.js +25 -189
  260. package/dist/plugins/posix-shell-fs.js +1 -31
  261. package/dist/plugins/remote-env-adb.js +12 -101
  262. package/dist/plugins/remote-env-e2b.js +40 -371
  263. package/dist/plugins/remote-env-file-error.js +0 -37
  264. package/dist/plugins/remote-env-host.js +63 -483
  265. package/dist/plugins/remote-env-k8s.js +32 -326
  266. package/dist/plugins/remote-env-local-docker.js +24 -157
  267. package/dist/plugins/remote-env-ssh.js +20 -128
  268. package/dist/plugins/remote-scratchpad.js +2 -32
  269. package/dist/plugins/remote-shell.js +1 -32
  270. package/dist/plugins/resume-anchor-store-sql.js +0 -11
  271. package/dist/plugins/retention-lane-store-sql.js +0 -108
  272. package/dist/plugins/retention-store-sql.js +4 -383
  273. package/dist/plugins/roster-store-sql.js +0 -55
  274. package/dist/plugins/run-store-sql.js +14 -251
  275. package/dist/plugins/s3-presign.js +2 -49
  276. package/dist/plugins/scheduler-support.js +3 -80
  277. package/dist/plugins/send-file-ledger.js +4 -53
  278. package/dist/plugins/send-user-file.js +4 -94
  279. package/dist/plugins/session-placement.js +1 -89
  280. package/dist/plugins/session-policy-store-sql.js +3 -81
  281. package/dist/plugins/session-store.js +0 -57
  282. package/dist/plugins/shared-memory-store-sql.js +4 -167
  283. package/dist/plugins/sql-driver.js +0 -17
  284. package/dist/plugins/sql-errors.js +0 -7
  285. package/dist/plugins/sql-escape.js +0 -8
  286. package/dist/plugins/sql-row-helpers.js +0 -25
  287. package/dist/plugins/store-backend.js +42 -222
  288. package/dist/plugins/store-contracts.js +2 -46
  289. package/dist/plugins/task-attachment-store.js +3 -49
  290. package/dist/plugins/task-list-store-sql.js +0 -79
  291. package/dist/plugins/tidb-cost-quota.js +1 -4
  292. package/dist/plugins/tidb-pool.js +1 -207
  293. package/dist/plugins/tidb-rate-limiter.js +3 -9
  294. package/dist/plugins/tidb-session-storage.js +4 -70
  295. package/dist/plugins/tidb-session-store.js +19 -341
  296. package/dist/plugins/tool-result-store-sql.js +4 -179
  297. package/dist/plugins/usage-window-store-sql.js +0 -8
  298. package/dist/plugins/web-search.js +10 -132
  299. package/dist/plugins/workflow-journal-store-sql.js +2 -58
  300. package/dist/plugins/workflow-run-store-sql.js +6 -91
  301. package/dist/plugins/worktree-isolation.js +6 -126
  302. package/dist/plugins/write-behind-counter.js +16 -75
  303. package/dist/principal-jwt.js +5 -60
  304. package/dist/project-memory.js +15 -146
  305. package/dist/prompts-domain-validate.js +1 -51
  306. package/dist/question.js +2 -111
  307. package/dist/resource-suspend.js +0 -18
  308. package/dist/router/route-orchestration.js +0 -77
  309. package/dist/rules-consent.d.ts +44 -5
  310. package/dist/rules-consent.js +75 -215
  311. package/dist/run-local.js +14 -381
  312. package/dist/runs.js +29 -617
  313. package/dist/runtime-caps-resolver.js +6 -132
  314. package/dist/runtime-governance.js +1 -232
  315. package/dist/sandbox-pkg-source.js +0 -37
  316. package/dist/sealed-key.js +3 -68
  317. package/dist/security.js +6 -291
  318. package/dist/session-leaf-bus.js +0 -32
  319. package/dist/session-sync-content.js +1 -69
  320. package/dist/session-sync-kernel.js +3 -60
  321. package/dist/session-sync.js +3 -66
  322. package/dist/session-titler.js +7 -41
  323. package/dist/session-watch.js +9 -73
  324. package/dist/shared-memory-scope-authorizer.js +0 -18
  325. package/dist/sighup-idle.js +1 -12
  326. package/dist/spec-fields.js +4 -143
  327. package/dist/store-live-probe.js +3 -49
  328. package/dist/task-a2a.js +1 -126
  329. package/dist/task-cwd.js +1 -103
  330. package/dist/task-mcp.js +1 -89
  331. package/dist/task-settings.js +10 -300
  332. package/dist/task-workflow.js +8 -75
  333. package/dist/tool-approval.d.ts +28 -1
  334. package/dist/tool-approval.js +70 -1430
  335. package/dist/trace/artifacts.js +5 -20
  336. package/dist/trace/engine-notice-wire.js +6 -138
  337. package/dist/trace/ledger-sink.js +7 -96
  338. package/dist/trace/project.js +4 -448
  339. package/dist/trace/redact.js +9 -77
  340. package/dist/turn-activity.js +1 -27
  341. package/dist/usage-analytics.js +4 -34
  342. package/dist/wall-clock-jump-guard.js +1 -68
  343. package/package.json +3 -3
  344. package/dist/capabilities/scenario-alias.d.ts +0 -27
  345. package/dist/capabilities/scenario-alias.js +0 -61
@@ -1,29 +1,18 @@
1
1
  import { delimitUntrusted } from "@sema-agent/core";
2
2
  import { applyAndGrade, changedFilesOutOfAllowlist } from "./grader-env-factory.js";
3
3
  import { shellQuote as Q } from "../plugins/remote-shell.js";
4
- /** Integrate completed workers' branches. Never throws — failures are typed MergeResult. */
5
4
  export async function mergeBranches(reports, baseSha, deps) {
6
5
  const repo = deps.repoDir ?? "/repo";
7
6
  const mergeable = reports.filter((r) => r.status === "completed");
8
7
  if (mergeable.length === 0) {
9
8
  return { ok: false, phase: "no-workers", reason: "no completed workers to merge", quarantine: reports.map((r) => r.workerId) };
10
9
  }
11
- // The whole pipeline is wrapped so this function honours its "never throws" contract AND never leaks the
12
- // integration sandbox: provisioning (which connects+seeds a billable VM) and pullWorkerDiff used to run
13
- // OUTSIDE the try, so a throw there (e.g. seed failure) escaped as an exception and — worse — left a
14
- // connected sandbox with no `destroy` ever called. `destroy` is captured the instant provisioning resolves,
15
- // and the finally tears it down on every path (success / typed failure / thrown error).
16
10
  let destroy;
17
- // Track the current stage so a THROWN error is attributed to the right phase (not always "provision").
18
11
  let phase = "diff-out";
19
- // [4953]② declared OUTSIDE the try (same pattern as `phase`) so the catch-all can still disclose them:
20
- // spend/repair/measure that already HAPPENED must ride the thrown-failure return too — the leader folds
21
- // conflictResolverCostUsd into the next round's budget, and a throw doesn't un-spend it.
22
- let conflictResolverCostUsd = 0; // 🔴 capture the bounded conflict-resolver agent's spend (was dropped → S1 TEAM-cost undercount)
12
+ let conflictResolverCostUsd = 0;
23
13
  let repairMeta;
24
14
  let measure;
25
15
  try {
26
- // 1. pull each worker's diff OUT of its container (M2). A failed diff quarantines that worker, aborts merge.
27
16
  const patches = [];
28
17
  for (const r of mergeable) {
29
18
  const d = await deps.pullWorkerDiff(r);
@@ -32,17 +21,11 @@ export async function mergeBranches(reports, baseSha, deps) {
32
21
  }
33
22
  patches.push({ workerId: r.workerId, patch: d.patch });
34
23
  }
35
- // 2. ephemeral no-creds integration sandbox (destroyed in finally — council#15).
36
24
  phase = "provision";
37
25
  const integ = await deps.provisionIntegrationSandbox();
38
26
  destroy = integ.destroy;
39
27
  const { env } = integ;
40
- // The sandbox's base commit ≠ the durable remote's `baseSha` (different repos). Capture the sandbox's
41
- // pre-apply HEAD so `format-patch <integBase>..HEAD` captures the integration commit even though the
42
- // commit advances the sandbox's branch (dogfood-surfaced: `main..HEAD` was empty). `baseSha` is used
43
- // ONLY for the durable push CAS below.
44
28
  const integBase = (await env.exec("git rev-parse HEAD", { cwd: repo })).stdout.trim();
45
- // 3. apply each patch --3way (council#3: keeps conflict context; capture .rej on failure, quarantine).
46
29
  phase = "apply";
47
30
  const conflictsResolved = [];
48
31
  for (const p of patches) {
@@ -51,15 +34,10 @@ export async function mergeBranches(reports, baseSha, deps) {
51
34
  const ap = await env.exec(`git apply --3way --whitespace=nowarn ${Q(patchPath)}`, { cwd: repo });
52
35
  if (ap.exitCode !== 0) {
53
36
  const rej = await env.exec(`find . -name '*.rej' -print -exec cat {} +`, { cwd: repo }).catch(() => ({ stdout: "", stderr: "", exitCode: -1 }));
54
- // Bounded conflict-resolution (search 2026-06-16): a --3way conflict left markers/.rej in the tree. Instead
55
- // of discarding ALL fan-out work + redoing single (leader collapseToSingle), run a bounded agent IN this
56
- // sandbox to integrate THIS worker's patch (siblings already applied clean), gated by a marker/.rej re-check
57
- // and the downstream test gate. Symmetric with integration-repair; OFF unless a conflictResolver is wired;
58
- // degrades to the original apply-failure (collapse) on any leftover/agent-error — never worse than baseline.
59
37
  const cr = integ.conflictResolver && integ.conflictResolver.maxRounds > 0
60
38
  ? await resolveApplyConflict(env, repo, p, rej.stdout, ap.stderr, integ.conflictResolver, deps)
61
39
  : { resolved: false, costUsd: 0 };
62
- conflictResolverCostUsd += cr.costUsd; // count the spend even when resolution failed (tokens were spent)
40
+ conflictResolverCostUsd += cr.costUsd;
63
41
  if (!cr.resolved) {
64
42
  deps.logger?.warn?.("merge_apply_failed", { workerId: p.workerId, exitCode: ap.exitCode });
65
43
  return { ok: false, phase: "apply", reason: `git apply --3way failed for ${p.workerId} (conflict)`, quarantine: [p.workerId], rej: rej.stdout, details: ap.stderr.slice(0, 800), ...(conflictResolverCostUsd > 0 ? { conflictResolverCostUsd } : {}) };
@@ -67,15 +45,8 @@ export async function mergeBranches(reports, baseSha, deps) {
67
45
  conflictsResolved.push(p.workerId);
68
46
  }
69
47
  }
70
- // 4. full test suite IN the sandbox = the merge gate (untrusted diff stays sandboxed — [26]§16).
71
- // testCmd is a caller-provided COMMAND LINE (e.g. "cd x && pytest"), run verbatim by design — NOT a single
72
- // arg, so it is intentionally not Q()-quoted (council round-2 noted the asymmetry). Blast radius = the
73
- // ephemeral no-creds sandbox; the caller owns the command (trusted control-plane config).
74
48
  phase = "test";
75
49
  let test = await env.exec(deps.testCmd, { cwd: repo });
76
- // Bounded integration-repair (search 2026-06-14): a clean-merge-but-failing-build used to be terminal.
77
- // If the sandbox supplied a `repair`, run the repair agent IN this merged tree (it edits the working tree;
78
- // the squash commit below captures worker patches + repair together), then re-run testCmd. Up to maxRounds.
79
50
  if (test.exitCode !== 0 && integ.repair && integ.repair.maxRounds > 0) {
80
51
  let cost = 0;
81
52
  let round = 0;
@@ -88,7 +59,7 @@ export async function mergeBranches(reports, baseSha, deps) {
88
59
  }
89
60
  catch (e) {
90
61
  deps.logger?.warn?.("integration_repair_error", { round: round + 1, err: e instanceof Error ? e.message : String(e) });
91
- break; // a thrown repair agent → stop repairing; fall through to the (still-failing) terminal return
62
+ break;
92
63
  }
93
64
  test = await env.exec(deps.testCmd, { cwd: repo });
94
65
  }
@@ -98,28 +69,13 @@ export async function mergeBranches(reports, baseSha, deps) {
98
69
  if (test.exitCode !== 0) {
99
70
  return { ok: false, phase: "test", reason: `integration test failed (exit ${test.exitCode})`, details: (test.stdout + "\n" + test.stderr).slice(-2000), ...(repairMeta ? { repair: repairMeta } : {}), ...(conflictResolverCostUsd > 0 ? { conflictResolverCostUsd } : {}) };
100
71
  }
101
- // 4b. truecorrect MEASURE (design/59) — run the thorough hidden oracle IN this sandbox (E2B), where the merged
102
- // code already lives, NOT on the CPU-contended control plane (fixes the frontier-multi oracle spawn-timeout
103
- // that thinned n). Pure observation: never gates `ok`, just reports into MergeResult.measure.
104
72
  if (deps.measureCmd) {
105
73
  const mr = await env.exec(deps.measureCmd, { cwd: repo });
106
74
  measure = mr.exitCode === 0 ? { pass: true } : { pass: false, reason: (mr.stdout + " " + mr.stderr).slice(-200) };
107
75
  }
108
- // 5. produce the integrated patch over base, hand to the Coordinator's credentialed push (council#1).
109
- // Check the commit's exit code: if `git add -A`/commit fails, HEAD doesn't advance and `format-patch`
110
- // still exits 0 with EMPTY output — pushing an empty patch would no-op or corrupt the merge silently.
111
76
  phase = "push";
112
- // If integration-repair ran, a repair agent may have created commits despite the instruction not to. Flatten
113
- // any such commits back to the working tree (Codex review #2) so the single `commit` below squashes worker
114
- // patches + repair edits into ONE integration commit — otherwise format-patch would emit the repair commit
115
- // PLUS an empty leader commit, and the Coordinator's `git am` can choke on the empty patch. No-op when no
116
- // repair commits exist (HEAD already == integBase).
117
77
  if (repairMeta)
118
78
  await env.exec(`git reset --soft ${Q(integBase)}`, { cwd: repo });
119
- // Cost legs that MUST ride EVERY push-phase failure return (wq64gmm5e HIGH: TEAM C1 undercount). A
120
- // conflictResolver and/or integration-repair may already have spent real tokens BEFORE any push-phase
121
- // failure; dropping these on the commit/format-patch/empty/race/push-fail returns made captureTeamC1
122
- // undercount the arm. Mirror the success/hold/test-fail returns exactly.
123
79
  const costTail = { ...(repairMeta ? { repair: repairMeta } : {}), ...(conflictResolverCostUsd > 0 ? { conflictResolverCostUsd } : {}) };
124
80
  const commit = await env.exec(`git add -A && git -c user.email=leader@local -c user.name=leader commit -m ${Q(`merge: integrate ${mergeable.map((r) => r.workerId).join("+")}`)} --allow-empty`, { cwd: repo });
125
81
  if (commit.exitCode !== 0) {
@@ -132,17 +88,9 @@ export async function mergeBranches(reports, baseSha, deps) {
132
88
  if (!integrated.stdout.trim()) {
133
89
  return { ok: false, phase: "push", reason: "integrated patch is empty (nothing applied over base) — refusing to push", quarantine: mergeable.map((r) => r.workerId), ...costTail };
134
90
  }
135
- // ── §10.6 THE PUSH CHOKEPOINT + §10.3 push-hold-on-signal + §10.4 measure-drives-repair ──
136
- // A single gate covering EVERY push path by construction (collapse `leader.ts:451` + N=1 `leader.ts:508`):
137
- // the credentialed push fires ONLY when (a) no mergeable report carries a non-auto-accepting
138
- // `repairTerminal` (§10.6 — the SAFE-tier `candidate_only` that the status collapse to "completed" would
139
- // otherwise hide), AND (b) under `LEADER_MEASURE_GATES`, every configured strong oracle signal is GREEN
140
- // (§10.3). When any of these HOLDS, the push is withheld and the held, DELIMITED candidate is surfaced for
141
- // human acceptance (a candidate is never a silent push and never a silent drop — §5 invariant). With
142
- // `measureGates` OFF and no report carrying a `repairTerminal`, this whole block is inert → byte-identical.
143
91
  const evald = await evaluatePushHold(env, repo, mergeable, measure, integrated.stdout, deps);
144
92
  if (evald.measure !== undefined)
145
- measure = evald.measure; // §10.4: the out-of-process re-measure supersedes the observe-only one
93
+ measure = evald.measure;
146
94
  if (evald.hold) {
147
95
  const h = evald.hold;
148
96
  deps.logger?.warn?.("merge_push_held", { terminal: h.terminal, reason: h.reason.slice(0, 200) });
@@ -165,9 +113,6 @@ export async function mergeBranches(reports, baseSha, deps) {
165
113
  return { ok: true, ref: push.ref, merged: mergeable.map((r) => r.workerId), ...(measure ? { measure } : {}), ...(repairMeta ? { repair: repairMeta } : {}), ...(conflictsResolved.length > 0 ? { conflictsResolved } : {}), ...(conflictResolverCostUsd > 0 ? { conflictResolverCostUsd } : {}) };
166
114
  }
167
115
  catch (e) {
168
- // Honour the "never throws" contract: a thrown pullWorkerDiff/provision/exec becomes a typed failure,
169
- // attributed to the stage it threw in (not a blanket "provision"). [4953]②: spend/repair/measure that
170
- // already happened before the throw ride this return too (same tail as every explicit failure return).
171
116
  return {
172
117
  ok: false, phase, reason: `merge aborted in ${phase}: ${e instanceof Error ? e.message : String(e)}`,
173
118
  quarantine: mergeable.map((r) => r.workerId),
@@ -177,98 +122,48 @@ export async function mergeBranches(reports, baseSha, deps) {
177
122
  };
178
123
  }
179
124
  finally {
180
- // council#15: destroy the sandbox on EVERY exit path. Guarded because we may bail (diff-out) or throw
181
- // BEFORE provisioning — `destroy` is only set once provisionIntegrationSandbox has resolved.
182
125
  if (destroy)
183
126
  await destroy().catch(() => { });
184
127
  }
185
128
  }
186
- /**
187
- * §10.6 + §10.3 + §10.4 — the push chokepoint decision (pure-ish: reads the reports + the optional measure grader;
188
- * never throws — a grader error falls back to a HOLD, never a silent push). Returns `{ hold }` to WITHHOLD the
189
- * credentialed push, surfacing the held candidate; `{}` to allow it. Three independent hold triggers:
190
- *
191
- * §10.6 — any mergeable report carries a `repairTerminal ∉ {undefined, "fixed"}`. A SAFE-tier `candidate_only`
192
- * maps to core verdict "PASS" → the report status collapses to "completed", so WITHOUT this gate the
193
- * merge would push `testCmd`-green-but-strong-oracle-UNCONFIRMED work. This fires regardless of
194
- * `measureGates` (the single-agent repair loop is itself OFF-by-default behind LEADER_REPAIR_LOOP — once
195
- * a report carries a repairTerminal, the operator opted in; the candidate MUST be held). Covers the
196
- * collapse-to-single and N=1 leader paths by construction.
197
- *
198
- * §10.3 — under `measureGates` ONLY: a configured STRONG oracle that is not GREEN holds the push. The hold keys
199
- * on the SIGNAL not a caller field: (`strongOracleSeeded`) OR (`measureCmd` present AND the measure did
200
- * not pass). Absent BOTH strong signals → no §10.3 hold (legacy compile-only auto-push). With
201
- * `measureGates` OFF this trigger is inert (today's observe-only measure).
202
- *
203
- * §10.4 — under `measureGates` AND `measureDrivesRepair`: re-seed the SINGLE out-of-process grader, mechanically
204
- * check the integrated patch's changed files against the allowlist (out-of-allowlist / build-config →
205
- * downgrade to `needs_human_oracle`), then re-measure THERE (never the repair-poisoned merge sandbox).
206
- * A still-failing re-measure holds (`candidate_only`); a passing re-measure clears the §10.3 measure
207
- * signal (so the §10.3 trigger no longer holds on it).
208
- */
209
129
  async function evaluatePushHold(env, repo, mergeable, observeMeasure, integratedPatch, deps) {
210
- // §10.7 — the integrated patch is worker-authored code reaching a human-read HTTP body / durable-approval row.
211
130
  const heldCandidate = () => delimitUntrusted("CANDIDATE PATCH (untrusted worker code)", integratedPatch);
212
- // §10.6 — a non-auto-accepting single-agent repair terminal rode in on a report (status collapsed to
213
- // "completed"). The strongest non-fixed terminal wins the label (it's the WHY surfaced to the human).
214
131
  const carried = mergeable.map((r) => r.repairTerminal).filter((t) => t !== undefined && t !== "fixed");
215
132
  if (carried.length > 0) {
216
- const terminal = carried[0]; // every mergeable solo report shares one repairTerminal (N=1 / collapse-solo)
133
+ const terminal = carried[0];
217
134
  return { hold: { reason: `single-agent repair terminal '${terminal}' — strong oracle unconfirmed, push held for human acceptance (§10.6)`, terminal, candidatePatch: heldCandidate() } };
218
135
  }
219
- // §10.3/§10.4 are OFF unless the master measure-gates flag is on → byte-identical default path.
220
136
  if (!deps.measureGates)
221
137
  return {};
222
138
  let measure = observeMeasure;
223
- // §10.4 measure-drives-repair: re-measure the integrated patch in a FRESH-seeded out-of-process grader, with the
224
- // mechanical changed-files allowlist guard. Runs only when configured AND there is a measure to re-confirm.
225
139
  if (deps.measureDrivesRepair && deps.measureCmd) {
226
140
  const mdr = deps.measureDrivesRepair;
227
141
  try {
228
- // §10.4(ii) — mechanical allowlist FIRST (a fresh sandbox is necessary-not-sufficient: poison can ride
229
- // INSIDE the integrated patch). An out-of-allowlist / build-config touch → downgrade to needs_human_oracle.
230
142
  const changed = await mdr.changedFiles(integratedPatch);
231
143
  const offending = changedFilesOutOfAllowlist(changed, mdr.allowlist);
232
144
  if (offending.length > 0) {
233
145
  deps.logger?.warn?.("merge_measure_allowlist_denied", { offending: offending.slice(0, 20) });
234
146
  return { hold: { reason: `integrated patch touches out-of-allowlist / build-config paths (${offending.slice(0, 5).join(", ")}) — needs_human_oracle (§10.4(ii))`, terminal: "needs_human_oracle", candidatePatch: heldCandidate() }, measure };
235
147
  }
236
- // Re-seed the SINGLE grader + re-measure THERE (never the repair-edited merge sandbox — §10.4(i)). The
237
- // existing in-sandbox `repair` agent already ran in `mergeBranches`; the grader re-measure is the
238
- // authoritative correctness signal. (Bounded measure-repair rounds beyond the existing integration-repair
239
- // are a future enable; v1 re-measures once.)
240
148
  const grade = await applyAndGrade(mdr.grader, integratedPatch, mdr.measureSteps);
241
149
  measure = grade.passed ? { pass: true } : { pass: false, reason: (grade.trace ?? "measure failed in out-of-process grader").slice(-200) };
242
150
  deps.logger?.info?.("merge_measure_grader", { passed: grade.passed, applied: grade.applied });
243
151
  }
244
152
  catch (e) {
245
- // Fail-CLOSED (never a silent push): a grader transport/exec error holds the push as needs_human_oracle.
246
153
  deps.logger?.warn?.("merge_measure_grader_error", { err: e instanceof Error ? e.message : String(e) });
247
- // §10.7 — surfaced reason is a trusted constant; the untrusted error detail is in the warn log above.
248
154
  return { hold: { reason: `measure grader errored — needs_human_oracle (§10.4)`, terminal: "needs_human_oracle", candidatePatch: heldCandidate() }, measure };
249
155
  }
250
156
  }
251
- // §10.3 — push-hold on the SIGNAL. Auto-push only when every configured strong signal is GREEN.
252
157
  const measureFailing = deps.measureCmd !== undefined && measure?.pass !== true;
253
- const strongOracleUnconfirmed = deps.strongOracleSeeded === true; // a hidden held-out oracle was seeded but only testCmd cleared
158
+ const strongOracleUnconfirmed = deps.strongOracleSeeded === true;
254
159
  if (measureFailing || strongOracleUnconfirmed) {
255
- // §10.7 — the surfaced `reason` reaches a human-read HTTP body / durable-approval row, so it is built from
256
- // TRUSTED constants ONLY; the untrusted measure output tail (`measure.reason`) stays in the logs, NEVER
257
- // interpolated into a human-/LLM-reachable string.
258
160
  const why = measureFailing ? "measure did not pass" : "hidden held-out oracle seeded but only the compile gate cleared";
259
161
  deps.logger?.warn?.("merge_push_held_signal", { why, measureReason: (measure?.reason ?? "").slice(0, 200) });
260
162
  return { hold: { reason: `strong oracle unconfirmed: ${why} — candidate_only, push held (§10.3)`, terminal: "candidate_only", candidatePatch: heldCandidate() }, measure };
261
163
  }
262
164
  return { measure };
263
165
  }
264
- /** Bounded resolution of a `git apply --3way` conflict (search 2026-06-16). The failed apply left conflict markers
265
- * in-tree and/or `.rej` files. Run the resolver agent up to maxRounds, then VERIFY the tree is conflict-free (no
266
- * `<<<<<<<`/`>>>>>>>` markers, no `*.rej`). Returns true iff clean → the patch is treated as applied and the merge
267
- * continues, KEEPING every other worker's clean work. Any agent error / leftover marker → false → the caller falls
268
- * back to today's apply-failure (collapse-to-single). Never worse than the baseline. */
269
166
  async function resolveApplyConflict(env, repo, p, rejText, applyStderr, resolver, deps) {
270
- // 🔴 Accumulate + RETURN the resolver's spend (it used to return only a boolean → the cost was dropped, the S1
271
- // TEAM-cost-undercount bug). Counts every round, including the round that throws (the tokens were spent).
272
167
  let costUsd = 0;
273
168
  const conflictText = `git apply --3way could not cleanly apply worker '${p.workerId}'.\n--- apply stderr ---\n${applyStderr.slice(0, 1500)}\n--- reject hunks (*.rej) ---\n${rejText.slice(0, 3000)}`;
274
169
  for (let round = 0; round < resolver.maxRounds; round++) {
@@ -279,10 +174,8 @@ async function resolveApplyConflict(env, repo, p, rejText, applyStderr, resolver
279
174
  }
280
175
  catch (e) {
281
176
  deps.logger?.warn?.("merge_conflict_repair_error", { workerId: p.workerId, err: e instanceof Error ? e.message : String(e) });
282
- return { resolved: false, costUsd }; // a thrown resolver → give up, fall back to collapse
177
+ return { resolved: false, costUsd };
283
178
  }
284
- // Resolved iff NO conflict markers and NO .rej files remain under the repo. `grep -rIlE` LEADS the command so a
285
- // mock/impl can distinguish this verify from the `.rej` CAPTURE in mergeBranches; non-empty stdout = still dirty.
286
179
  const check = await env
287
180
  .exec(`grep -rIlE '^(<<<<<<<|>>>>>>>)' . 2>/dev/null; find . -name '*.rej' 2>/dev/null | head -20`, { cwd: repo })
288
181
  .catch(() => ({ exitCode: -1, stdout: "DIRTY", stderr: "" }));
@@ -292,6 +185,6 @@ async function resolveApplyConflict(env, repo, p, rejText, applyStderr, resolver
292
185
  return { resolved: true, costUsd };
293
186
  }
294
187
  }
295
- return { resolved: false, costUsd }; // still conflicted after maxRounds
188
+ return { resolved: false, costUsd };
296
189
  }
297
190
  //# sourceMappingURL=merge.js.map
@@ -1,9 +1,3 @@
1
- /**
2
- * Deterministic economic 防灾 guard (design/68 §7 / §11 D2, search [83] ruling (b)): the asymmetry is that a
3
- * false fan-out is the -98% disaster while a false single merely runs slower — so every case decidable WITHOUT
4
- * the model resolves to single, for free. Pure function (unit-testable); the semantic judgments (sequential/
5
- * coupled/uncertain) stay with the router LLM, and "估不准回 single" is the collapse fallback (B3).
6
- */
7
1
  export function economicGuard(objective, opts = {}) {
8
2
  if (opts.fanoutEnabled === false)
9
3
  return { single: true, reason: "fan-out disabled by deployment (A6)" };
@@ -14,12 +8,9 @@ export function economicGuard(objective, opts = {}) {
14
8
  }
15
9
  return { single: false };
16
10
  }
17
- /** The deterministic SINGLE route: one sub-task owning the whole objective (the same shape the router emits
18
- * for its single decision — N=1 runs through the identical leader pipeline). */
19
11
  export function singleRoute(objective) {
20
12
  return [{ workerId: "main", branch: "worker/main", files: ["."], spec: { objective } }];
21
13
  }
22
- /** The planning prompt: asks for a strict JSON array of disjoint-file sub-tasks. */
23
14
  export function planPrompt(objective, opts = {}) {
24
15
  const max = opts.maxSubtasks ?? 6;
25
16
  return [
@@ -33,7 +24,6 @@ export function planPrompt(objective, opts = {}) {
33
24
  `Rules: workerId unique + url-safe; files non-empty + disjoint across ALL sub-tasks; objective self-contained.`,
34
25
  ].filter(Boolean).join(" ");
35
26
  }
36
- /** Extract a JSON array from a model completion (tolerates ```json fences / surrounding prose). */
37
27
  function extractJsonArray(raw) {
38
28
  const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/i);
39
29
  const candidate = (fenced ? fenced[1] : raw).trim();
@@ -48,20 +38,11 @@ function extractJsonArray(raw) {
48
38
  throw new Error("planner output is not valid JSON");
49
39
  }
50
40
  }
51
- /** Parse + VALIDATE a planner completion into SubtaskSpec[]. Throws on any violation (disjointness etc.). */
52
41
  export function parsePlan(raw, opts = {}) {
53
42
  return validateSubtasks(extractJsonArray(raw), opts);
54
43
  }
55
- /**
56
- * Deterministically validate an array of sub-tasks (the trust boundary — see file header). Used both for the
57
- * LLM planner output AND for caller-supplied `body.subtasks`, which would otherwise skip every invariant: the
58
- * `branch` is interpolated into a worker-container shell command (`git checkout -b <branch>`), so an
59
- * unvalidated branch (`x; curl …|sh`, `` `reboot` ``, `--upload-pack=…`) would inject; and the disjoint-file /
60
- * unique-id invariants are what the Coordinator's parallel-merge depends on. Throws on any violation.
61
- */
62
44
  export function validateSubtasks(arr, opts = {}) {
63
45
  const max = opts.maxSubtasks ?? 6;
64
- // "plan" not "planner" — this also validates caller-supplied body.subtasks, not just LLM output.
65
46
  if (!Array.isArray(arr) || arr.length === 0)
66
47
  throw new Error("plan must be a non-empty array of sub-tasks");
67
48
  if (arr.length > max)
@@ -89,15 +70,6 @@ export function validateSubtasks(arr, opts = {}) {
89
70
  seenFiles.add(norm);
90
71
  }
91
72
  const branch = typeof e.branch === "string" && e.branch.trim() ? e.branch.trim() : `worker/${workerId}`;
92
- // Reject a leading "-" (would be parsed as an option by `git checkout -b <branch>`), ".." (ref escape), and
93
- // an over-long ref (git rejects it anyway; cap keeps the value tidy). The char set is shell-safe.
94
- //
95
- // 🔗 近亲判据(2026-08-08,#176 二轮扫描 finding):`leader/diffout.ts` 的 `safeRev` 用**同一套**字符
96
- // 类 `[A-Za-z0-9._/-]`、**同一个** 200 长度上限、**同样**拒前导 `-`。唯一的真差异在 `..`:safeRev 只拒
97
- // `..--`(rev 要支持 `A..B` 区间形),这里全拒 `..`(branch **名**里 `..` 无合法用途,git 自己也拒)
98
- // —— 是**有意收紧**,不是抄漏。两处此前零互指、无共享基元、无跨处钉;单边加一个字符类成员时另一边
99
- // 不会有任何提示。是否抽一个共享基元(比如 `refToken(s, { allowRange })`)属结构面决定,候裁;本批
100
- // 只把「近亲关系 + 差异所在」两侧成文。
101
73
  if (branch.length > 200 || !/^[A-Za-z0-9._/-]+$/.test(branch) || branch.startsWith("-") || branch.includes("..")) {
102
74
  throw new Error(`sub-task ${workerId}: invalid branch`);
103
75
  }
@@ -106,19 +78,10 @@ export function validateSubtasks(arr, opts = {}) {
106
78
  }
107
79
  return subs;
108
80
  }
109
- /** Run the planner: one model completion → validated disjoint-file sub-tasks. */
110
81
  export async function llmPlan(objective, runPlan, opts = {}) {
111
82
  const raw = await runPlan(planPrompt(objective, opts));
112
83
  return parsePlan(raw, opts);
113
84
  }
114
- /**
115
- * The DIFFICULTY-ROUTER prompt (search [43], planner-light). Same disjoint-subtask contract as `planPrompt`,
116
- * but the model FIRST routes: default to a SINGLE agent (one sub-task owning the whole task) and only fan out
117
- * when the objective is CLEARLY + ECONOMICALLY decomposable (disjoint files, independent/non-sequential, each
118
- * part buildable without most of the same context). The campaign proved forcing fan-out on a sequential/coupled
119
- * task is a -98% disaster, so the bias is firmly toward single. The output is still a plain disjoint-file
120
- * sub-task array — a 1-element array IS the single route (the leader runs N=1 through the same pipeline).
121
- */
122
85
  export function routePrompt(objective, opts = {}) {
123
86
  const max = opts.maxSubtasks ?? 6;
124
87
  return [
@@ -141,39 +104,25 @@ export function routePrompt(objective, opts = {}) {
141
104
  `Rules: always a JSON ARRAY (SINGLE = one element, not a bare object); workerId unique + url-safe; files non-empty + DISJOINT across ALL elements; each objective self-contained.`,
142
105
  ].filter(Boolean).join(" ");
143
106
  }
144
- /** Run the difficulty router: one model completion → a routed plan (1 sub-task = single route, N = fan-out),
145
- * validated by `parsePlan` (the same disjointness/shape invariants the Coordinator depends on). */
146
107
  export async function routePlan(objective, runRoute, opts = {}) {
147
108
  if (opts.fanoutEnabled === false)
148
- return singleRoute(objective); // A6: deploy-disabled fan-out → no router call
109
+ return singleRoute(objective);
149
110
  const raw = await runRoute(routePrompt(objective, opts));
150
111
  return parsePlan(raw, opts);
151
112
  }
152
- /**
153
- * Decomposer quality gate (design/68 §5/§11 B1-B3): run the router, and on a rejected/unparseable plan retry
154
- * the LLM AT MOST ONCE, then COLLAPSE TO SINGLE (never fail the task on a bad plan — single is almost always
155
- * the right fallback, and a false fan-out is the -98% disaster; a plan failure must not be louder than that).
156
- * `collapsed` carries the reason (observability: a collapse is a router-quality signal, not silent).
157
- */
158
113
  export async function routePlanWithFallback(objective, runRoute, opts = {}) {
159
- const guard = economicGuard(objective, opts); // deterministic 防灾经济 cases never pay for a router call
114
+ const guard = economicGuard(objective, opts);
160
115
  if (guard.single)
161
116
  return { subs: singleRoute(objective) };
162
117
  let firstErr;
163
- let firstRaw = ""; // capture the raw completion of the FIRST failed attempt for diagnosis (see below)
118
+ let firstRaw = "";
164
119
  for (let attempt = 0; attempt < 2; attempt++) {
165
- // §5: no more than ONE LLM re-plan
166
120
  const raw = await runRoute(routePrompt(objective, opts));
167
121
  try {
168
122
  return { subs: parsePlan(raw, opts) };
169
123
  }
170
124
  catch (e) {
171
125
  firstErr ??= e;
172
- // Capturing the raw output on collapse is load-bearing for diagnosis: a collapse logs only "not valid
173
- // JSON", and the router runs on an in-memory (non-durable) session, so the raw completion is otherwise
174
- // GONE — task#2d's flaky collapse could not be root-caused because nothing recorded the bad output. Keep
175
- // a bounded head+tail so the next collapse is diagnosable (truncated/unterminated vs unescaped-quote vs
176
- // prose) without dumping a 32k-token runaway in full.
177
126
  if (!firstRaw)
178
127
  firstRaw = raw.length > 600 ? `${raw.slice(0, 300)}…[${raw.length}ch]…${raw.slice(-150)}` : raw;
179
128
  }
@@ -1,37 +1,7 @@
1
- /**
2
- * The PROFILE-injected `RepairOracle` closure for the leader's single-agent self-repair loop
3
- * (`runRepairLoop`, core design/78 / D1). See `sema-internal server/docs/LEADER-REPAIRLOOP-INTEGRATION.md` (§3.1 + the authoritative
4
- * §10 corrected layer). The closure GRADES a worker's candidate in an ISOLATED grader env (the reused
5
- * integration sandbox, distinct from the worker env) and maps the result to a core `OracleResult`. core only
6
- * fixes the `OracleResult` shape + the read-only/identity contract; the composition lives HERE so the core type
7
- * never grows a discriminated union.
8
- *
9
- * Hardened by the adversarial round (§10):
10
- * - §10.1 HERMETIC grade: the grader tree is reset to a clean base (`git reset --hard <integBase> && git clean
11
- * -fdx`) BEFORE applying the worker's LATEST full `base..HEAD` diff each call — so a 2nd/3rd attempt never
12
- * applies cumulatively onto an already-applied tree (the BROKEN finding). The diff is recomputed fresh every
13
- * call (never a stale cached diff — verify.ts:328 BUG5).
14
- * - flaky: the gate is re-isolated K times on a green-looking verdict; a disagreement is reported `flaky:true`
15
- * and core's `terminalForTier` provably refuses to project it to a passing terminal.
16
- * - §10.9 DE-CORRELATION: the L3 judge can only contribute a CLEARING tier when the verifier model is PROVABLY
17
- * distinct from the generator — and the closure CANNOT resolve roles→models itself (that happens inside
18
- * `Runner.runTask` after the closure returns), so the WIRE resolves both ids and passes them in; the closure
19
- * downgrades to `l3_judge_advisory` when they are equal. v1 ships L3 OFF (`cfg.l3` unset) → L2 `trusted_hidden`
20
- * is the sole clearer and `terminalForTier` caps every PASS at `candidate_only`. Wiring the actual
21
- * `verifyCompleted` invocation is a future enable: it needs the worker's `TaskResult` threaded to the oracle
22
- * (core passes only the report `evidence` today), so it is deliberately NOT invoked here.
23
- *
24
- * The grader topology (graderEnv = the reused integration sandbox, oracle seeded ONLY there, worker env distinct)
25
- * means the worker can never read the oracle; for SAFE-tier `candidate_only` the bare `graderEnv !== workerEnv`
26
- * identity check is sufficient, so the caller OMITS `immutableOraclePaths` (the structural bash write-probe is
27
- * vacuous across distinct sandboxes and matters only for the mandate-OFF auto-accept path — §10.2).
28
- */
29
1
  import { runExecGate, } from "@sema-agent/core";
30
2
  import { createHash } from "node:crypto";
31
3
  import { shellQuote as sq } from "../plugins/remote-shell.js";
32
- /** A short, stable content hash for the stale-diff guard (fork2.1) + the graded-bytes binding (fork2.2). */
33
4
  const sha = (s) => createHash("sha256").update(s).digest("hex").slice(0, 16);
34
- /** Parse the changed file paths from a unified diff — the `diff --git a/X b/X` headers (both old + new path). */
35
5
  function changedFilesOf(patch) {
36
6
  const out = new Set();
37
7
  for (const m of patch.matchAll(/^diff --git a\/(.+?) b\/(.+)$/gm)) {
@@ -41,8 +11,6 @@ function changedFilesOf(patch) {
41
11
  return [...out];
42
12
  }
43
13
  const normP = (p) => p.replace(/^\.?\/+/, "").replace(/\/+$/, "");
44
- /** fork1.2 — the first changed file that touches a protected oracle path (exact, or a subtree either way), else
45
- * undefined. A patch that edits the grader's hidden-oracle/test path is refused (test-tampering guard). */
46
14
  function touchesOraclePath(changed, oraclePaths) {
47
15
  const ops = oraclePaths.map(normP).filter((p) => p !== "");
48
16
  for (const f of changed.map(normP)) {
@@ -53,35 +21,23 @@ function touchesOraclePath(changed, oraclePaths) {
53
21
  }
54
22
  return undefined;
55
23
  }
56
- /**
57
- * Build the `RepairOracle` closure. The returned `(graderEnv, evidence) => OracleResult` is what the leader
58
- * passes as `RepairLoopConfig.oracle`. `evidence` (the worker's report text) is intentionally unused — the
59
- * closure recomputes the candidate diff from the worker env each call (§10.1).
60
- */
61
24
  export function mkRepairOracle(cfg) {
62
25
  const repo = cfg.graderRepoDir;
63
26
  const K = Math.max(1, Math.floor(cfg.flakyK ?? 2));
64
- let lastGradedHash; // fork2.1 stale-diff guard — per-loop state across the engine's attempts.
27
+ let lastGradedHash;
65
28
  return async (graderEnv, _evidence) => {
66
- // §10.1 — materialize the worker's LATEST code in a CLEAN grader tree. Pull fresh (never a stale diff).
67
29
  const diff = await cfg.pullWorkerDiff();
68
30
  if (!diff.ok) {
69
31
  cfg.log?.("repair_oracle_pull_failed", { error: diff.error });
70
32
  return { tier: cfg.passTier, passed: false, flaky: false, retries: 0, trace: `pull worker diff failed: ${diff.error}` };
71
33
  }
72
34
  const diffHash = sha(diff.patch);
73
- // fork1.2 — refuse a candidate that edits a seeded oracle/test path: applied into the grader it
74
- // could tamper the very oracle that grades it. Surface as `needs_human_oracle` (tier "none" → terminalForTier),
75
- // never a silent candidate_only. (The §10.2 factory lane OMITs the engine path-probe, so this is the fence.)
76
35
  const tampered = cfg.oraclePaths && cfg.oraclePaths.length > 0 ? touchesOraclePath(changedFilesOf(diff.patch), cfg.oraclePaths) : undefined;
77
36
  if (tampered) {
78
37
  cfg.log?.("repair_oracle_tampering_guard", { path: tampered });
79
38
  cfg.onGraded?.({ diff: diff.patch, hash: diffHash, passed: false });
80
39
  return { tier: "none", passed: false, flaky: false, retries: 0, trace: `candidate patch touches oracle/test path '${tampered}' — refusing to grade (fork1.2 test-tampering guard)` };
81
40
  }
82
- // fork2.1 — a repair attempt that produced NO new diff (byte-identical to the prior graded one)
83
- // made no progress → never grade stale bytes as a fresh PASS: report no-progress (the loop gives up rather
84
- // than re-passing a stale artifact). With fork2.2 this guarantees the SURFACED candidate == the graded bytes.
85
41
  if (lastGradedHash !== undefined && diffHash === lastGradedHash) {
86
42
  cfg.log?.("repair_oracle_stale_diff", { hash: diffHash });
87
43
  cfg.onGraded?.({ diff: diff.patch, hash: diffHash, passed: false });
@@ -90,8 +46,6 @@ export function mkRepairOracle(cfg) {
90
46
  lastGradedHash = diffHash;
91
47
  const patchPath = `/tmp/candidate-${Date.now()}.patch`;
92
48
  await cfg.graderWriteFile(patchPath, diff.patch);
93
- // Hermetic prep: reset the grader to its clean base, then apply the FULL base→latest diff. `--whitespace=nowarn`
94
- // matches the merge path (merge.ts:144). A prep failure (e.g. an apply conflict) is a non-flaky failed grade.
95
49
  const prep = await runExecGate(graderEnv, [
96
50
  { command: `git -C ${sq(repo)} reset --hard ${sq(cfg.integBase)} && git -C ${sq(repo)} clean -fdx`, label: "grader-reset" },
97
51
  { command: `git -C ${sq(repo)} apply --whitespace=nowarn ${sq(patchPath)}`, label: "grader-apply" },
@@ -102,15 +56,8 @@ export function mkRepairOracle(cfg) {
102
56
  cfg.onGraded?.({ diff: diff.patch, hash: diffHash, passed: false });
103
57
  return { tier: cfg.passTier, passed: false, flaky: false, retries: 0, trace: `grader prep failed:\n${trace}` };
104
58
  }
105
- // 🔴 council 2026-06-17 — the hermetic `clean -fdx` above DELETED the seeded hidden oracle (untracked/ignored,
106
- // never committed at integBase). RE-INJECT it AFTER applying the worker patch, so (a) the gate runs against the
107
- // REAL oracle the clean wiped (else `trusted_hidden` is a false label), and (b) any file the patch placed at an
108
- // oracle path is OVERWRITTEN by the trusted content (defence-in-depth with the fork1.2 path guard above).
109
59
  for (const f of cfg.oracleFiles ?? [])
110
60
  await cfg.graderWriteFile(f.path, f.content);
111
- // L2 mechanical gate (the trusted hidden oracle), re-isolated K times to settle flakiness. A clear FIRST
112
- // failure short-circuits (no point re-running a definite fail); a green verdict is re-run to detect a flaky
113
- // (nondeterministic) test — a disagreement is reported flaky and core refuses to project it to a pass.
114
61
  const verdicts = [];
115
62
  let lastTrace = "";
116
63
  for (let k = 0; k < K; k++) {
@@ -123,18 +70,12 @@ export function mkRepairOracle(cfg) {
123
70
  }
124
71
  const passed = verdicts.length > 0 && verdicts.every(Boolean);
125
72
  const flaky = new Set(verdicts).size > 1;
126
- // §10.9 — a self-certifying verifier (no distinct model) cannot CLEAR: a PASS downgrades to advisory. v1 has
127
- // `cfg.l3` unset (L3 off) → the L2 `passTier` stands and `terminalForTier` caps the PASS at candidate_only.
128
73
  let tier = cfg.passTier;
129
74
  if (cfg.l3 && passed && !flaky && cfg.l3.verifierModelId === cfg.l3.generatorModelId) {
130
75
  cfg.log?.("repair_oracle_l3_decorrelation_failed", { model: cfg.l3.generatorModelId });
131
76
  tier = "l3_judge_advisory";
132
77
  }
133
- // fork2.2 — bind the EXACT graded bytes (the diff that produced this verdict) so the caller surfaces the
134
- // candidate the human reviews == the bytes that were graded (not a later re-pull that may differ).
135
78
  cfg.onGraded?.({ diff: diff.patch, hash: diffHash, passed });
136
- // [4953]① `retries` = how many re-isolations actually RAN (the cross-repo contract's wording) — the
137
- // first-clear-fail short-circuit means this can be < K, and reporting the configured cap was a lie.
138
79
  cfg.log?.("repair_oracle_verdict", { tier, passed, flaky, retries: verdicts.length });
139
80
  return { tier, passed, flaky, retries: verdicts.length, ...(passed && !flaky ? {} : { trace: lastTrace || "oracle rejected the candidate" }) };
140
81
  };