@sema-agent/server 7.82.1 → 7.83.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/dist/bench/s1/arms.d.ts +4 -4
- package/dist/bench/s1/arms.js +1 -2
- package/dist/bench/s1/live-deps.js +0 -1
- package/dist/bench/s1/runner-ctx.d.ts +3 -3
- package/dist/bench/s1/tasks.d.ts +8 -4
- package/dist/config-catalog.js +0 -1
- package/dist/config.js +1 -0
- package/dist/leader/endpoint.js +2 -0
- package/dist/leader/leader.d.ts +33 -6
- package/dist/leader/leader.js +54 -45
- package/dist/leader/merge.d.ts +38 -29
- package/dist/leader/merge.js +45 -38
- package/dist/leader/wire.d.ts +2 -17
- package/dist/leader/wire.js +5 -58
- package/dist/observability/fail-open.d.ts +5 -5
- package/dist/observability/fail-open.js +5 -2
- package/dist/observability/run-terminal-log.d.ts +0 -7
- package/dist/observability/run-terminal-log.js +9 -20
- package/dist/plugins/adoption-log-sql.d.ts +3 -0
- package/dist/plugins/adoption-log-sql.js +5 -1
- package/dist/plugins/approval-ask-store-sql.d.ts +3 -0
- package/dist/plugins/approval-ask-store-sql.js +9 -5
- package/dist/plugins/background-agent-store-sql.d.ts +3 -0
- package/dist/plugins/background-agent-store-sql.js +7 -2
- package/dist/plugins/device-store-sql.d.ts +3 -0
- package/dist/plugins/device-store-sql.js +9 -4
- package/dist/plugins/ensure-index.d.ts +42 -0
- package/dist/plugins/ensure-index.js +81 -0
- package/dist/plugins/ensure-schema-lock-policy.d.ts +29 -0
- package/dist/plugins/ensure-schema-lock-policy.js +31 -0
- package/dist/plugins/image-bake-store-sql.d.ts +3 -0
- package/dist/plugins/image-bake-store-sql.js +6 -2
- package/dist/plugins/image-index-sql.d.ts +3 -0
- package/dist/plugins/image-index-sql.js +8 -4
- package/dist/plugins/leader-run-store-sql.d.ts +3 -0
- package/dist/plugins/leader-run-store-sql.js +6 -2
- package/dist/plugins/mailbox-store-sql.d.ts +3 -0
- package/dist/plugins/mailbox-store-sql.js +6 -1
- package/dist/plugins/memory-engine-pg.d.ts +4 -0
- package/dist/plugins/memory-engine-pg.js +6 -6
- package/dist/plugins/memory-engine-tidb.js +5 -0
- package/dist/plugins/memory-sync-store-pg.d.ts +5 -0
- package/dist/plugins/memory-sync-store-pg.js +9 -2
- package/dist/plugins/memory-sync-store-tidb.js +6 -0
- package/dist/plugins/one-time-migrations.d.ts +1 -1
- package/dist/plugins/one-time-migrations.js +7 -14
- package/dist/plugins/outcome-ledger-sql.d.ts +3 -0
- package/dist/plugins/outcome-ledger-sql.js +7 -3
- package/dist/plugins/permission-rule-store-sql.d.ts +3 -0
- package/dist/plugins/permission-rule-store-sql.js +9 -5
- package/dist/plugins/pg-cost-quota.d.ts +3 -0
- package/dist/plugins/pg-cost-quota.js +5 -1
- package/dist/plugins/pg-pool.d.ts +23 -2
- package/dist/plugins/pg-pool.js +22 -37
- package/dist/plugins/pg-rate-limiter.d.ts +3 -0
- package/dist/plugins/pg-rate-limiter.js +5 -1
- package/dist/plugins/pg-session-storage.d.ts +3 -0
- package/dist/plugins/pg-session-storage.js +8 -4
- package/dist/plugins/retention-store-sql.d.ts +3 -0
- package/dist/plugins/retention-store-sql.js +7 -3
- package/dist/plugins/roster-store-sql.d.ts +3 -0
- package/dist/plugins/roster-store-sql.js +6 -1
- package/dist/plugins/schema-indexes.d.ts +21 -0
- package/dist/plugins/schema-indexes.js +25 -0
- package/dist/plugins/task-attachment-store.d.ts +3 -0
- package/dist/plugins/task-attachment-store.js +7 -2
- package/dist/plugins/tidb-pool.d.ts +15 -3
- package/dist/plugins/tidb-pool.js +38 -15
- package/dist/plugins/tool-result-store-sql.d.ts +3 -0
- package/dist/plugins/tool-result-store-sql.js +5 -1
- package/dist/trace/project.d.ts +2 -1
- package/dist/trace/redact.d.ts +6 -0
- package/package.json +1 -1
package/dist/bench/s1/arms.d.ts
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* 3. WORKER-ENV: the repair leg ALWAYS passes `workerEnv` so runRepairLoop's §5.1 isolation identity check is LIVE.
|
|
23
23
|
* 4. PAIRED-SEED: `seed` is best-effort N-repeat replicate, NEVER a GO criterion; rows are distributional.
|
|
24
24
|
* 5. COMPLETE TEAM COST: TEAM stats = Σ(worker stats.costBreakdown) + (merge.repair?.costUsd) +
|
|
25
|
-
* (
|
|
25
|
+
* 两条腿(7.83.0 起:冲突解决模型已随 [ref] ④ 删除,不再有第三条 resolver 腿)。
|
|
26
26
|
*
|
|
27
27
|
* 🔴 BLOCKED-but-honest structure: the REAL firm run is clay-gated (expensive E2B+DeepSeek). This file builds the
|
|
28
28
|
* REAL arm logic against INJECTABLE profile seams (`ProfileDeps`) so the deterministic smoke drives it with a MOCK
|
|
@@ -143,9 +143,9 @@ export declare function classifyRunStatus(input: {
|
|
|
143
143
|
*/
|
|
144
144
|
export declare function captureStats(stats: TaskResult["stats"] | undefined): EmittedStats;
|
|
145
145
|
/**
|
|
146
|
-
* 🔴 Corr #5 — the COMPLETE TEAM `stats`. Σ(worker stats.costBreakdown + humanReview) PLUS the merge
|
|
147
|
-
* `repair?.costUsd
|
|
148
|
-
*
|
|
146
|
+
* 🔴 Corr #5 — the COMPLETE TEAM `stats`. Σ(worker stats.costBreakdown + humanReview) PLUS the merge leg
|
|
147
|
+
* `repair?.costUsd`. Both legs, or TEAM C1 is undercounted ([ref]'s #1 confound). costUsd (float USD) →
|
|
148
|
+
* µUSD into llmRoot. (7.83.0:第三条腿 `conflictResolverCostUsd` 随冲突解决模型删除,见下。)
|
|
149
149
|
*/
|
|
150
150
|
export declare function captureTeamStats(reports: WorkerReport[], merge: MergeResult | undefined): EmittedStats;
|
|
151
151
|
/**
|
package/dist/bench/s1/arms.js
CHANGED
|
@@ -60,8 +60,7 @@ export function captureTeamStats(reports, merge) {
|
|
|
60
60
|
acc.humanReview.gates.push(...s.humanReview.gates);
|
|
61
61
|
}
|
|
62
62
|
const repairUsd = merge?.repair?.costUsd ?? 0;
|
|
63
|
-
|
|
64
|
-
acc.costBreakdown.llmRootMicroUsd += Math.round((repairUsd + resolverUsd) * 1e6);
|
|
63
|
+
acc.costBreakdown.llmRootMicroUsd += Math.round(repairUsd * 1e6);
|
|
65
64
|
return acc;
|
|
66
65
|
}
|
|
67
66
|
export async function driveSupSuspendResume(initial, deps, implSpec, trap, advanceClock) {
|
|
@@ -362,7 +362,6 @@ async function runLeaderForTrap(rt, brain, models, roles, pricing, trap, cellId,
|
|
|
362
362
|
const targetRef = "refs/heads/main";
|
|
363
363
|
process.env.LEADER_ENABLED = "true";
|
|
364
364
|
process.env.LEADER_WORKER_BUDGET_USD = String((rt.maxCostUsd ?? 1.0) / 2);
|
|
365
|
-
process.env.LEADER_CONFLICT_ROUNDS = process.env.LEADER_CONFLICT_ROUNDS ?? "2";
|
|
366
365
|
const run = createLeaderRunner({
|
|
367
366
|
brain, models, roles, pricing, e2bApiKey: rt.e2bApiKey, workspace: W,
|
|
368
367
|
...(s1DebugGit() ? { logger: { warn: (m, x) => console.error(`[S1_DEBUG_LEADER warn] ${m}`, x ? JSON.stringify(x).slice(0, 300) : ""), info: (m, x) => console.error(`[S1_DEBUG_LEADER info] ${m}`, x ? JSON.stringify(x).slice(0, 200) : "") } } : {}),
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* `maxTokens`/`maxCostUsd` is a silently-unread dead key), and TEAM's per-worker budgets sum to ≤ the solo budget — so no
|
|
9
9
|
* arm can win on a bigger pie. `assertBudgetMatch` (below) is the build-time guard the shape test runs.
|
|
10
10
|
*
|
|
11
|
-
* 🔴 BUDGET-MATCH for TEAM is NEW WIRING
|
|
12
|
-
*
|
|
13
|
-
* live test verbatim would yield an unbounded TEAM (the confound) and an empty `humanReview` (C2 = undefined).
|
|
11
|
+
* 🔴 BUDGET-MATCH for TEAM is NEW WIRING no leader live test does: the (now-deleted, 7.83.0 / [ref] ④) conflict
|
|
12
|
+
* live test set NO budget on `SubtaskSpec.spec` and NO `now` on any Runner. The harness STAMPS both (arms.ts) —
|
|
13
|
+
* copying a live test verbatim would yield an unbounded TEAM (the confound) and an empty `humanReview` (C2 = undefined).
|
|
14
14
|
*
|
|
15
15
|
* The real-infra construction (E2B worker/grader envs + a live brain) is deferred to the live make-real proof
|
|
16
16
|
* (`test/s1-value-live.test.ts`, gated on E2B+DEEPSEEK). This module only fixes the SHAPE + the budget invariant
|
package/dist/bench/s1/tasks.d.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* 🔴 FIRM vs DIRECTIONAL (a HIGH review correction — do NOT reproduce the false "5 LIVE-VERIFIED" claim):
|
|
5
5
|
* Only C1 / C2 / C4 have a real E2B+DeepSeek live make-real test in `test/` today
|
|
6
|
-
* (supervisor-verify-fix-live / supervisor-honest-bound-live
|
|
6
|
+
* (supervisor-verify-fix-live / supervisor-honest-bound-live;C4 的那条 `leader-conflict-resolver-live`
|
|
7
|
+
* 已随 7.83.0 / clay [ref] ④ **删除** —— 它证的是「冲突解决模型合两边」,而那条通道不存在了;C4 今天
|
|
8
|
+
* 没有 live 证明位,按 DIRECTIONAL 计,不得再算作 FIRM).
|
|
7
9
|
* C3 (honest-PARTIAL) has NO dedicated live test; C5 (safety-escalate) is ONLY a unit test
|
|
8
10
|
* (`supervisor-safety-escalate.test.ts` header: "no E2B / no DeepSeek / no DB" — it only exercises
|
|
9
11
|
* `createDurableAskPolicy` in-memory, NEVER a real boundary). Counting either as a measured datapoint is the
|
|
@@ -84,9 +86,11 @@ export declare const C1_VERIFY_FIX: TrapSpec;
|
|
|
84
86
|
*/
|
|
85
87
|
export declare const C2_HONEST_BOUND: TrapSpec;
|
|
86
88
|
/**
|
|
87
|
-
* C4 — fan-out CONFLICT (two workers overlap one file → merge conflict
|
|
88
|
-
*
|
|
89
|
-
*
|
|
89
|
+
* C4 — fan-out CONFLICT (two workers overlap one file → merge conflict).
|
|
90
|
+
* 🔴 7.83.0 / clay [ref] ④:合不上不再起解决模型,leader 把 `conflict` 段(分支 + 冲突文件)交给用户、
|
|
91
|
+
* run 状态 `needs_human` ⇒ 这一格的 TEAM 臂在冲突形上**不会自己合好**。原 FIRM 证明位
|
|
92
|
+
* (`leader-conflict-resolver-live` live 测试)随通道删除,C4 退回 DIRECTIONAL。TEAM-primary。
|
|
93
|
+
* Objective COMMAND oracle = both functions exist AND compute correctly.
|
|
90
94
|
*/
|
|
91
95
|
export declare const C4_PARALLEL: TrapSpec;
|
|
92
96
|
/**
|
package/dist/config-catalog.js
CHANGED
|
@@ -184,7 +184,6 @@ export const CONFIG_CATALOG = [
|
|
|
184
184
|
r("KUBERNETES_SERVICE_HOST", "orchestration", "string", "平台注入的 in-cluster k8s API host(K8S_API_URL 缺席时用)"),
|
|
185
185
|
r("KUBERNETES_SERVICE_PORT", "orchestration", "string", "平台注入的 in-cluster k8s API 端口(缺省 443)"),
|
|
186
186
|
r("LEADER_BUDGET_USD", "orchestration", "number", "leader replan-lite 扇出花费帽(USD)", { danger: BILL, derivedDefaultNote: "缺席 ⇒ replan-lite 自默认" }),
|
|
187
|
-
r("LEADER_CONFLICT_ROUNDS", "orchestration", "number", "leader 合并冲突修复轮数", { danger: BILL, derivedDefaultNote: "缺席 ⇒ 场景配置 conflictRounds(默认 0)" }),
|
|
188
187
|
r("LEADER_ENABLED", "orchestration", "boolean", "leader 端点(POST/GET /v1/leader;需隔离执行后端 + durable leader_run 店,缺则拒启)", { staticDefault: "false", configKey: "leaderEnabled" }),
|
|
189
188
|
r("LEADER_FANOUT_ENABLED", "orchestration", "boolean", "leader 扇出许可(false=钉死单路由零 router 成本)", { staticDefault: "true", danger: BILL, configKey: "leaderFanoutEnabled" }),
|
|
190
189
|
r("LEADER_MEASURE_GATES", "orchestration", "boolean", "leader 合并 push-hold-on-signal + measure-drives-repair 门(严格 'true' 才开)", { staticDefault: "false" }),
|
package/dist/config.js
CHANGED
|
@@ -119,6 +119,7 @@ const RETIRED_ENVS = [
|
|
|
119
119
|
["OA_ISSUE_REPO", "the OA scenario was removed in 5.0.0", "removed"],
|
|
120
120
|
["OA_ISSUE_LABELS", "the OA scenario was removed in 5.0.0", "removed"],
|
|
121
121
|
["APPROVAL_POLL_MS", "the OA poll-approval lane was removed in 5.0.0; the durable checkpoint lane is the only gate", "removed"],
|
|
122
|
+
["LEADER_CONFLICT_ROUNDS", "the bounded merge-conflict-resolution agent was deleted in 7.83.0 按 C-R63 删除,合不上交用户:leader 交回 result.conflict(分支 + 冲突文件),run 状态 needs_human", "removed"],
|
|
122
123
|
];
|
|
123
124
|
function rejectRetiredEnvs() {
|
|
124
125
|
const hit = RETIRED_ENVS.filter(([o]) => process.env[o] !== undefined && process.env[o] !== "");
|
package/dist/leader/endpoint.js
CHANGED
|
@@ -3,6 +3,8 @@ import { redactErrorMessage, redactHead, redactLeaderResult, redactLeaderResultW
|
|
|
3
3
|
import { withPrincipal } from "../observability/principal-context.js";
|
|
4
4
|
import { leaderIdemKey } from "../plugins/leader-run-store-sql.js";
|
|
5
5
|
function statusForLeaderResult(result) {
|
|
6
|
+
if (result.conflict)
|
|
7
|
+
return "needs_human";
|
|
6
8
|
switch (result.repairTerminal) {
|
|
7
9
|
case "candidate_only":
|
|
8
10
|
case "needs_human_oracle":
|
package/dist/leader/leader.d.ts
CHANGED
|
@@ -104,10 +104,13 @@ export interface LeaderDeps {
|
|
|
104
104
|
/** Replan-lite ([ref] §6): bounded, DETERMINISTIC reactions — no runtime DAG surgery. When set:
|
|
105
105
|
* worker failed → redispatch ×1 on a FRESH env (clean base; a crashed worker's half-written workspace is
|
|
106
106
|
* never reused) → still failed = collapse-remainder-to-single; verify FAIL (repair already bounded inside
|
|
107
|
-
* the verify loop) → collapse;
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
107
|
+
* the verify loop) → collapse; budgetUsd exceeded → stop fan-out, single continues. Multi-trigger
|
|
108
|
+
* arbitration priority (C6): suspended > budget > verify-fail > worker-failed — only the highest-priority
|
|
109
|
+
* reaction runs. (The suspended reaction itself — cancel the others, exempt the suspended worker, surface
|
|
110
|
+
* its token — is unconditional, not gated here.)
|
|
111
|
+
*
|
|
112
|
+
* 🔴 S-113 / clay [ref] ④(7.83.0):**merge-conflict 不再是一条 replan 臂**。worker 分支合不上 ⇒ 不起
|
|
113
|
+
* collapse 模型,`conflict` 段(分支 + 冲突文件)交给**用户**,见 {@link LeaderResult.conflict}。 */
|
|
111
114
|
replan?: {
|
|
112
115
|
/** Fan-out budget (USD, sum of worker costs). Exceeded → stop fan-out, collapse to single (C5). */
|
|
113
116
|
budgetUsd?: number;
|
|
@@ -154,12 +157,36 @@ export interface LeaderResult {
|
|
|
154
157
|
route?: "single" | "fanout";
|
|
155
158
|
/** Set when planning/provisioning failed before fan-out. */
|
|
156
159
|
error?: string;
|
|
157
|
-
/** Replan-lite ([ref] §6): which trigger won arbitration + what was done.
|
|
160
|
+
/** Replan-lite ([ref] §6): which trigger won arbitration + what was done.
|
|
161
|
+
* 🔴 S-113 / [ref] ④(7.83.0):闭集删掉 `"merge-conflict"`(硬 breaking,零别名——消费方的 tsc 红即通知)。 */
|
|
158
162
|
replan?: {
|
|
159
|
-
trigger: "suspended" | "budget" | "
|
|
163
|
+
trigger: "suspended" | "budget" | "verify-fail" | "worker-failed";
|
|
160
164
|
redispatched?: string[];
|
|
161
165
|
collapsed?: string;
|
|
162
166
|
};
|
|
167
|
+
/**
|
|
168
|
+
* 🔴 S-113 / clay [ref] ④(7.83.0)——**合不上 = 交给用户**(对齐 CC 形)。merge 的 `phase:"apply"` 冲突
|
|
169
|
+
* 不再起 collapse 模型:`ok=false` + 本段,用户拿 `workers[].branch` + `files` 自己 `git merge` / cherry-pick
|
|
170
|
+
* (worker 分支本就留在集成沙箱 / 远端)。run 状态走既有第三终局 `needs_human`(endpoint 的状态映射),不加
|
|
171
|
+
* 新状态词。**缺席何义** = 这一趟没有冲突,或对面是老 server。
|
|
172
|
+
*/
|
|
173
|
+
conflict?: {
|
|
174
|
+
/** 用户侧的合并基点 = leader 这一趟的 durable base。 */
|
|
175
|
+
baseSha: string;
|
|
176
|
+
/** 有冲突标记 / `.rej` 的路径(**不含内容**)。 */
|
|
177
|
+
files: string[];
|
|
178
|
+
/** 清单被截断(探测到的冲突路径多于上限)时在场;**缺席 = 这就是全部**(对抗复审 F5)。 */
|
|
179
|
+
filesTruncated?: true;
|
|
180
|
+
/** 全部 completed worker 的分支;`applied:false` = 这只 worker 的补丁没进树(冲突者本人,或排在它后面
|
|
181
|
+
* 还没轮到 apply 的——顺序 apply,首次失败即停)。 */
|
|
182
|
+
workers: Array<{
|
|
183
|
+
workerId: string;
|
|
184
|
+
branch: string;
|
|
185
|
+
applied: boolean;
|
|
186
|
+
}>;
|
|
187
|
+
/** `.rej` 头(经既有脱敏面,≤ 2 KiB)——给人看冲突长什么样。 */
|
|
188
|
+
rejHead?: string;
|
|
189
|
+
};
|
|
163
190
|
/** C4: suspended workers' resume tokens, surfaced to the caller (their checkpoints + paused envs are intact —
|
|
164
191
|
* the single-agent resume path owns them from here; orphan protection = the existing deadline reap). */
|
|
165
192
|
suspended?: Array<{
|
package/dist/leader/leader.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { fanOut } from "./fanout.js";
|
|
2
|
-
import { redactErrorMessage, redactHead
|
|
2
|
+
import { redactErrorMessage, redactHead } from "../observability/run-terminal-log.js";
|
|
3
3
|
import { pullDiff } from "./diffout.js";
|
|
4
4
|
import { mergeBranches } from "./merge.js";
|
|
5
|
-
import { Runner, runWithVerification, resumeWithVerification,
|
|
5
|
+
import { Runner, runWithVerification, resumeWithVerification, terminalProjection } from "@sema-agent/core";
|
|
6
6
|
import { pausedCauseOf } from "../terminal.js";
|
|
7
7
|
const MAX_RESUME_SLICES = 200;
|
|
8
8
|
export async function autoResumeOnResource(initial, signal, resume, maxSlices = MAX_RESUME_SLICES) {
|
|
@@ -152,20 +152,25 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
152
152
|
: w.diffEnv
|
|
153
153
|
? pullDiff(w.diffEnv, w.baseSha, { ref: w.branch, ...(workerRepoDir ? { cwd: workerRepoDir } : {}) })
|
|
154
154
|
: Promise.resolve({ ok: false, error: `worker ${w.workerId} has neither fetchDiff nor diffEnv` });
|
|
155
|
-
const
|
|
155
|
+
const notCompleted = (r) => r.status !== "completed";
|
|
156
|
+
const salvagePatches = async (rs) => {
|
|
156
157
|
const out = [];
|
|
158
|
+
const skipped = (workerId, why, detail) => deps.logger?.warn?.("salvage_skipped", { workerId, why, ...(detail ? { detail: redactHead(detail, 200) } : {}) });
|
|
157
159
|
await Promise.all(rs
|
|
158
|
-
.filter((r) => r.status !== "completed")
|
|
159
160
|
.map(async (r) => {
|
|
160
161
|
const w = byId.get(r.workerId);
|
|
161
162
|
if (!w)
|
|
162
|
-
return;
|
|
163
|
+
return skipped(r.workerId, "unknown_worker");
|
|
163
164
|
try {
|
|
164
165
|
const d = await workerDiff(w);
|
|
165
|
-
if (d.ok
|
|
166
|
-
|
|
166
|
+
if (!d.ok)
|
|
167
|
+
return skipped(r.workerId, "diff_failed", d.error);
|
|
168
|
+
if (d.patch.trim().length === 0)
|
|
169
|
+
return skipped(r.workerId, "empty_diff");
|
|
170
|
+
out.push({ workerId: r.workerId, sessionId: r.sessionId, patch: d.patch });
|
|
167
171
|
}
|
|
168
|
-
catch {
|
|
172
|
+
catch (e) {
|
|
173
|
+
skipped(r.workerId, "diff_threw", e instanceof Error ? e.message : String(e));
|
|
169
174
|
}
|
|
170
175
|
}));
|
|
171
176
|
return out;
|
|
@@ -226,7 +231,7 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
226
231
|
if (sus.length === 0)
|
|
227
232
|
return undefined;
|
|
228
233
|
deps.logger?.info?.("replan_suspended", { workers: sus.map((r) => r.workerId) });
|
|
229
|
-
const salvaged = await
|
|
234
|
+
const salvaged = await salvagePatches(rs.filter(notCompleted));
|
|
230
235
|
return {
|
|
231
236
|
ok: false, reports: rs, cancelled: true, route,
|
|
232
237
|
error: "worker suspended — fan-out cancelled (suspended worker exempt), resume token surfaced",
|
|
@@ -235,41 +240,26 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
235
240
|
...(salvaged.length > 0 ? { salvaged } : {}),
|
|
236
241
|
};
|
|
237
242
|
};
|
|
238
|
-
const
|
|
243
|
+
const conflictSegment = (c, rs) => ({
|
|
244
|
+
baseSha: durableBaseSha,
|
|
245
|
+
files: c.files,
|
|
246
|
+
...(c.filesTruncated ? { filesTruncated: true } : {}),
|
|
247
|
+
workers: rs
|
|
248
|
+
.filter((r) => r.status === "completed")
|
|
249
|
+
.map((r) => ({ workerId: r.workerId, branch: byId.get(r.workerId).branch, applied: c.applied.includes(r.workerId) })),
|
|
250
|
+
...(c.rejHead ? { rejHead: c.rejHead } : {}),
|
|
251
|
+
});
|
|
252
|
+
const collapseToSingle = async (trigger, reason) => {
|
|
239
253
|
const safeReason = redactHead(reason, 300);
|
|
240
254
|
deps.logger?.warn?.("replan_collapse_to_single", { trigger, reason: safeReason });
|
|
241
255
|
const replan = { trigger, collapsed: safeReason, ...(redispatched ? { redispatched } : {}) };
|
|
242
|
-
const
|
|
243
|
-
await Promise.all(reports.map(async (r) => {
|
|
244
|
-
const w = byId.get(r.workerId);
|
|
245
|
-
if (!w)
|
|
246
|
-
return;
|
|
247
|
-
try {
|
|
248
|
-
const d = await workerDiff(w);
|
|
249
|
-
if (d.ok && d.patch.trim().length > 0)
|
|
250
|
-
priorDiffs.push({ workerId: r.workerId, status: r.status, patch: d.patch });
|
|
251
|
-
}
|
|
252
|
-
catch {
|
|
253
|
-
}
|
|
254
|
-
}));
|
|
255
|
-
if (priorDiffs.length > 0)
|
|
256
|
-
deps.logger?.info?.("collapse_reuse_priors", { workers: priorDiffs.map((d) => d.workerId) });
|
|
257
|
-
const soloObjective = priorDiffs.length === 0
|
|
258
|
-
? task
|
|
259
|
-
: [
|
|
260
|
-
task,
|
|
261
|
-
``,
|
|
262
|
-
`--- PRIOR PARALLEL WORK — REUSE IT, do not re-derive from scratch ---`,
|
|
263
|
-
`${priorDiffs.length} worker(s) attempted this task in parallel; their changes could NOT be integrated cleanly (${safeReason.slice(0, 150)}). Each worker's diff over the base is below. COPY the correct, non-overlapping parts verbatim and produce ONE coherent change for the WHOLE task, resolving only the overlaps. Treat these as a strong starting point — fix anything wrong, but do not throw the work away.`,
|
|
264
|
-
...priorDiffs.map((d) => delimitUntrusted(`PRIOR WORKER '${d.workerId}' (${d.status}) DIFF (untrusted)`, redactPatchHead(d.patch, 8000))),
|
|
265
|
-
].join("\n");
|
|
266
|
-
const soloSub = { workerId: "collapse-solo", branch: "worker/collapse-solo", files: ["."], spec: { objective: soloObjective } };
|
|
256
|
+
const soloSub = { workerId: "collapse-solo", branch: "worker/collapse-solo", files: ["."], spec: { objective: task } };
|
|
267
257
|
let solo;
|
|
268
258
|
try {
|
|
269
259
|
solo = await deps.provisionWorker(soloSub);
|
|
270
260
|
}
|
|
271
261
|
catch (e) {
|
|
272
|
-
const salvaged = await
|
|
262
|
+
const salvaged = await salvagePatches(finalReports.filter(notCompleted));
|
|
273
263
|
return { ok: false, reports: finalReports, cancelled, route, error: `collapse provision failed: ${e instanceof Error ? e.message : String(e)}`, replan, ...(salvaged.length > 0 ? { salvaged } : {}) };
|
|
274
264
|
}
|
|
275
265
|
workers.push(solo);
|
|
@@ -280,10 +270,20 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
280
270
|
const sus = await surfaceSuspended(finalReports, fo.reports);
|
|
281
271
|
if (sus)
|
|
282
272
|
return { ...sus, replan: { ...replan, trigger: "suspended" }, ...(repairTerminal ? { repairTerminal } : {}), ...(candidatePatch ? { candidatePatch } : {}) };
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
273
|
+
const m = await mergeBranches(fo.reports, durableBaseSha, mergeDeps);
|
|
274
|
+
if (!m.ok && m.conflict) {
|
|
275
|
+
const cc = m.conflict;
|
|
276
|
+
deps.logger?.warn?.("merge_conflict_to_user", { conflicted: cc.conflicted, applied: cc.applied, files: cc.files.slice(0, 20) });
|
|
277
|
+
const salvagedAll = await salvagePatches(finalReports);
|
|
278
|
+
return {
|
|
279
|
+
ok: false, reports: finalReports, merge: m, cancelled: fo.cancelled, route, replan,
|
|
280
|
+
conflict: conflictSegment(cc, fo.reports),
|
|
281
|
+
...(salvagedAll.length > 0 ? { salvaged: salvagedAll } : {}),
|
|
282
|
+
...(repairTerminal ? { repairTerminal } : {}),
|
|
283
|
+
...(candidatePatch ? { candidatePatch } : {}),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
const salvaged = await salvagePatches(finalReports.filter(notCompleted));
|
|
287
287
|
return {
|
|
288
288
|
ok: m.ok, reports: finalReports, merge: m, cancelled: fo.cancelled, route, replan,
|
|
289
289
|
...(salvaged.length > 0 ? { salvaged } : {}),
|
|
@@ -336,18 +336,27 @@ export async function runLeaderTask(task, durableBaseSha, deps) {
|
|
|
336
336
|
const sus2 = await surfaceSuspended(reports, fo2.reports);
|
|
337
337
|
if (sus2)
|
|
338
338
|
return sus2;
|
|
339
|
-
const stillFailed = fo2.reports.filter(
|
|
339
|
+
const stillFailed = fo2.reports.filter(notCompleted).map((r) => r.workerId);
|
|
340
340
|
if (stillFailed.length > 0) {
|
|
341
341
|
return collapseToSingle("worker-failed", `redispatch failed again: ${stillFailed.join(", ")}`);
|
|
342
342
|
}
|
|
343
343
|
}
|
|
344
344
|
}
|
|
345
345
|
const merge = await mergeBranches(reports, durableBaseSha, mergeDeps);
|
|
346
|
-
if (!merge.ok && merge.
|
|
347
|
-
|
|
348
|
-
|
|
346
|
+
if (!merge.ok && merge.conflict) {
|
|
347
|
+
const c = merge.conflict;
|
|
348
|
+
deps.logger?.warn?.("merge_conflict_to_user", { conflicted: c.conflicted, applied: c.applied, files: c.files.slice(0, 20) });
|
|
349
|
+
const salvagedC = await salvagePatches(reports);
|
|
350
|
+
return {
|
|
351
|
+
ok: false, reports, merge, cancelled, route,
|
|
352
|
+
conflict: conflictSegment(c, reports),
|
|
353
|
+
...(redispatched ? { replan: { trigger: "worker-failed", redispatched } } : {}),
|
|
354
|
+
...(salvagedC.length > 0 ? { salvaged: salvagedC } : {}),
|
|
355
|
+
...(repairTerminal ? { repairTerminal } : {}),
|
|
356
|
+
...(candidatePatch ? { candidatePatch } : {}),
|
|
357
|
+
};
|
|
349
358
|
}
|
|
350
|
-
const salvaged = await
|
|
359
|
+
const salvaged = await salvagePatches(reports.filter(notCompleted));
|
|
351
360
|
return {
|
|
352
361
|
ok: merge.ok, reports, merge, cancelled, route,
|
|
353
362
|
...(redispatched ? { replan: { trigger: "worker-failed", redispatched } } : {}),
|
package/dist/leader/merge.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
* The deterministic Coordinator integrates N completed workers' branches under FULL isolation:
|
|
5
5
|
* 1. pull each worker's patch OUT of its container (M2 `pullDiff` — no creds go in).
|
|
6
6
|
* 2. provision an EPHEMERAL no-net/no-creds integration sandbox (base pre-provisioned by the dep).
|
|
7
|
-
* 3. `git apply --3way` each patch in sequence (
|
|
7
|
+
* 3. `git apply --3way` each patch in sequence (conflict ⇒ TERMINAL: capture `.rej` + the conflict PATH list and
|
|
8
|
+
* hand them to the USER as {@link MergeConflict} — S-113 / clay [ref] ④; council#3's quarantine unchanged).
|
|
8
9
|
* 4. run the full test suite IN the sandbox (the gate; untrusted diff never touches the control plane — [26]§16).
|
|
9
10
|
* 5. green → the **Coordinator** (trusted control plane, holds creds — NOT the sandbox, council#1) takes the
|
|
10
11
|
* integrated patch and pushes with CAS `--force-with-lease=<ref>:<baseSha>` (push race → retry — council#2).
|
|
@@ -57,31 +58,25 @@ export interface IntegrationRepair {
|
|
|
57
58
|
}>;
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* then VERIFIES no marker/.rej remains before continuing, and the downstream test gate still guards correctness.
|
|
66
|
-
* Wired by the caller (provisionIntegrationSandbox); when unset, an apply conflict stays terminal exactly as before
|
|
67
|
-
* (zero behaviour change). Reuses the IntegrationRepair sandbox + hands pattern → no new core/env seam (the seam
|
|
68
|
-
* [ref] §8 feared for "adopting N half-done workspaces" is unnecessary for the conflict case).
|
|
61
|
+
* S-113 / clay [ref] ④ — **合不上 = 交给用户**(对齐 CC 形)。`git apply --3way` 冲突时的产出:冲突**路径**
|
|
62
|
+
* (不含内容)+ 哪几只 worker 的补丁已经进树,由 leader 铸成 `LeaderResult.conflict` 段交到用户手上。
|
|
63
|
+
* 本段刻意**只有路径与身份**:CC 259 语料对 collapse / conflict resolution 零命中,CC 的并行是同机 teammates
|
|
64
|
+
* 各 worktree、结束由 git 重放到用户 HEAD、冲突列给用户(cli259.js @2303113)——没有任何模型去读别的 worker
|
|
65
|
+
* 的 diff。7.83.0 删掉的那条「冲突解决模型」通道(它的类型面与 `LEADER_CONFLICT_ROUNDS` 旋钮)正是此意。
|
|
69
66
|
*/
|
|
70
|
-
export interface
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
*
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
note?: string;
|
|
84
|
-
}>;
|
|
67
|
+
export interface MergeConflict {
|
|
68
|
+
/** 有冲突标记或 `.rej` 的路径(**不含内容**;`.rej` 后缀已剥,指向源文件)。 */
|
|
69
|
+
files: string[];
|
|
70
|
+
/** 对抗复审 F5 [medium]:清单**被截断**(命中 {@link CONFLICT_FILES_MAX})时在场 —— 缺席 = 这就是全部。
|
|
71
|
+
* 「完整清单」是本终局对用户的补偿,一次静默的 200 封顶会让用户以为冲突只有 200 处。 */
|
|
72
|
+
filesTruncated?: true;
|
|
73
|
+
/** 冲突发生**之前**已经干净 apply 进树的 workerId(顺序 apply,失败即停)。 */
|
|
74
|
+
applied: string[];
|
|
75
|
+
/** 补丁没能 apply 的那只 worker。 */
|
|
76
|
+
conflicted: string;
|
|
77
|
+
/** `.rej` 头(经既有**工件**脱敏面 `redactArtifactText` —— 与 `rej` 同一批 diff 字节同一口 —— 再切 ≤ 2 KiB)
|
|
78
|
+
* ——给用户看「冲突长什么样」,不是给模型的输入。 */
|
|
79
|
+
rejHead?: string;
|
|
85
80
|
}
|
|
86
81
|
export interface MergeDeps {
|
|
87
82
|
/** Pull one worker's patch from ITS container (M2 pullDiff bound to the worker's env). */
|
|
@@ -93,7 +88,6 @@ export interface MergeDeps {
|
|
|
93
88
|
env: IntegrationEnv;
|
|
94
89
|
destroy: () => Promise<void>;
|
|
95
90
|
repair?: IntegrationRepair;
|
|
96
|
-
conflictResolver?: ConflictResolver;
|
|
97
91
|
}>;
|
|
98
92
|
/**
|
|
99
93
|
* The CREDENTIALED push, done by the Coordinator (control plane), NOT the sandbox (council#1). Takes the
|
|
@@ -177,8 +171,6 @@ export type MergeResult = {
|
|
|
177
171
|
reason?: string;
|
|
178
172
|
};
|
|
179
173
|
repair?: RepairMeta;
|
|
180
|
-
conflictsResolved?: string[];
|
|
181
|
-
conflictResolverCostUsd?: number;
|
|
182
174
|
terminal?: RepairTerminal;
|
|
183
175
|
candidatePatch?: string;
|
|
184
176
|
} | {
|
|
@@ -187,10 +179,10 @@ export type MergeResult = {
|
|
|
187
179
|
phase: "no-workers" | "diff-out" | "provision" | "apply" | "test" | "push";
|
|
188
180
|
quarantine?: string[];
|
|
189
181
|
rej?: string;
|
|
182
|
+
conflict?: MergeConflict;
|
|
190
183
|
retry?: boolean;
|
|
191
184
|
details?: string;
|
|
192
185
|
repair?: RepairMeta;
|
|
193
|
-
conflictResolverCostUsd?: number;
|
|
194
186
|
terminal?: RepairTerminal;
|
|
195
187
|
candidatePatch?: string;
|
|
196
188
|
measure?: {
|
|
@@ -200,4 +192,21 @@ export type MergeResult = {
|
|
|
200
192
|
};
|
|
201
193
|
/** Integrate completed workers' branches. Never throws — failures are typed MergeResult. */
|
|
202
194
|
export declare function mergeBranches(reports: WorkerReport[], baseSha: string, deps: MergeDeps): Promise<MergeResult>;
|
|
195
|
+
/** 探测脚本里某一半失败时打到 stdout 的标记前缀(`grep` / `find` 各一);只由 {@link conflictFilesOf} 认。
|
|
196
|
+
* ⚠️ 判序承重:`conflictFilesOf` **先**测本前缀、**后**剥 `./` —— `grep -rl` / `find .` 输出的真路径恒带 `./`,
|
|
197
|
+
* 所以一个真叫 `__PROBE_FAIL_grep` 的文件到达时是 `./__PROBE_FAIL_grep`,不与标记二义;把剥 `./` 挪到前面就静默破功。 */
|
|
198
|
+
export declare const PROBE_FAIL_PREFIX = "__PROBE_FAIL_";
|
|
199
|
+
/**
|
|
200
|
+
* S-113 / [ref] ④ — 把 `grep -rIl` + `find` 的输出变成**冲突路径清单**(给用户,不含内容)。
|
|
201
|
+
* 逐行:去掉 `./` 前缀、剥掉 `.rej` 后缀(`pkg/a.ts.rej` 与 `pkg/a.ts` 是同一处冲突的两种痕迹)、去重、
|
|
202
|
+
* 按 {@link CONFLICT_FILES_MAX} 封顶并**交回是否截断**(F5:静默封顶会让用户把 200 当成全部)。两条命令合成
|
|
203
|
+
* 一次 exec,所以 stdout 里两族路径混在一起 —— 本函数是它唯一的解释者。任一半探测失败 ⇒ 那一半打出
|
|
204
|
+
* {@link PROBE_FAIL_PREFIX} 标记、另一半的清单照常回来 ⇒ `probeFailed` 点名失败的那半、`files` 只是**不完整**而不是
|
|
205
|
+
* 「没有冲突」:`conflict` 段仍在场(用户仍拿得到分支与冲突者身份),调用点另记一行响亮日志(合并复审 车HB H-7)。
|
|
206
|
+
*/
|
|
207
|
+
export declare function conflictFilesOf(stdout: string): {
|
|
208
|
+
files: string[];
|
|
209
|
+
truncated: boolean;
|
|
210
|
+
probeFailed: string[];
|
|
211
|
+
};
|
|
203
212
|
//# sourceMappingURL=merge.d.ts.map
|
package/dist/leader/merge.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { delimitUntrusted } from "@sema-agent/core";
|
|
2
|
-
import { OVERSIZED_DIAG_PLACEHOLDER, redactErrorMessage, redactHead, redactTail } from "../observability/run-terminal-log.js";
|
|
2
|
+
import { OVERSIZED_DIAG_PLACEHOLDER, redactArtifactText, redactErrorMessage, redactHead, redactTail } from "../observability/run-terminal-log.js";
|
|
3
3
|
import { applyAndGrade, changedFilesOutOfAllowlist } from "./grader-env-factory.js";
|
|
4
4
|
import { shellQuote as Q } from "../plugins/remote-shell.js";
|
|
5
|
+
const CONFLICT_FILES_MAX = 200;
|
|
6
|
+
const CONFLICT_REJ_HEAD_MAX = 2048;
|
|
5
7
|
export async function mergeBranches(reports, baseSha, deps) {
|
|
6
8
|
const repo = deps.repoDir ?? "/repo";
|
|
7
9
|
const mergeable = reports.filter((r) => r.status === "completed");
|
|
@@ -10,7 +12,6 @@ export async function mergeBranches(reports, baseSha, deps) {
|
|
|
10
12
|
}
|
|
11
13
|
let destroy;
|
|
12
14
|
let phase = "diff-out";
|
|
13
|
-
let conflictResolverCostUsd = 0;
|
|
14
15
|
let repairMeta;
|
|
15
16
|
let measure;
|
|
16
17
|
try {
|
|
@@ -28,23 +29,29 @@ export async function mergeBranches(reports, baseSha, deps) {
|
|
|
28
29
|
const { env } = integ;
|
|
29
30
|
const integBase = (await env.exec("git rev-parse HEAD", { cwd: repo })).stdout.trim();
|
|
30
31
|
phase = "apply";
|
|
31
|
-
const
|
|
32
|
+
const applied = [];
|
|
32
33
|
for (const p of patches) {
|
|
33
34
|
const patchPath = `/tmp/${p.workerId}.patch`;
|
|
34
35
|
await env.writeFile(patchPath, p.patch);
|
|
35
36
|
const ap = await env.exec(`git apply --3way --whitespace=nowarn ${Q(patchPath)}`, { cwd: repo });
|
|
36
37
|
if (ap.exitCode !== 0) {
|
|
37
38
|
const rej = await env.exec(`find . -name '*.rej' -print -exec cat {} +`, { cwd: repo }).catch(() => ({ stdout: "", stderr: "", exitCode: -1 }));
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
39
|
+
const listing = await env
|
|
40
|
+
.exec(`grep -rIlE --exclude-dir=.git '^(<<<<<<<|>>>>>>>)' . 2>/dev/null; [ $? -le 1 ] || echo ${PROBE_FAIL_PREFIX}grep; ` +
|
|
41
|
+
`find . -path ./.git -prune -o -name '*.rej' -print 2>/dev/null || echo ${PROBE_FAIL_PREFIX}find`, { cwd: repo })
|
|
42
|
+
.catch(() => ({ exitCode: -1, stdout: "", stderr: "" }));
|
|
43
|
+
const { files, truncated, probeFailed } = conflictFilesOf(listing.stdout);
|
|
44
|
+
if (listing.exitCode !== 0 || probeFailed.length > 0)
|
|
45
|
+
deps.logger?.warn?.("merge_conflict_files_probe_failed", { workerId: p.workerId, exitCode: listing.exitCode, probeFailed });
|
|
46
|
+
deps.logger?.warn?.("merge_apply_failed", { workerId: p.workerId, exitCode: ap.exitCode, files: files.slice(0, 20) });
|
|
47
|
+
const rejHead = redactArtifactText(rej.stdout).slice(0, CONFLICT_REJ_HEAD_MAX);
|
|
48
|
+
return {
|
|
49
|
+
ok: false, phase: "apply", reason: `git apply --3way failed for ${p.workerId} (conflict)`,
|
|
50
|
+
quarantine: [p.workerId], rej: rej.stdout, details: redactHead(ap.stderr, 800),
|
|
51
|
+
conflict: { files, ...(truncated ? { filesTruncated: true } : {}), applied: [...applied], conflicted: p.workerId, ...(rejHead ? { rejHead } : {}) },
|
|
52
|
+
};
|
|
47
53
|
}
|
|
54
|
+
applied.push(p.workerId);
|
|
48
55
|
}
|
|
49
56
|
phase = "test";
|
|
50
57
|
let test = await env.exec(deps.testCmd, { cwd: repo });
|
|
@@ -74,7 +81,7 @@ export async function mergeBranches(reports, baseSha, deps) {
|
|
|
74
81
|
deps.logger?.info?.("integration_repair_done", { ...repairMeta });
|
|
75
82
|
}
|
|
76
83
|
if (test.exitCode !== 0) {
|
|
77
|
-
return { ok: false, phase: "test", reason: `integration test failed (exit ${test.exitCode})`, details: redactTail(test.stdout + "\n" + test.stderr, 2000), ...(repairMeta ? { repair: repairMeta } : {})
|
|
84
|
+
return { ok: false, phase: "test", reason: `integration test failed (exit ${test.exitCode})`, details: redactTail(test.stdout + "\n" + test.stderr, 2000), ...(repairMeta ? { repair: repairMeta } : {}) };
|
|
78
85
|
}
|
|
79
86
|
if (deps.measureCmd) {
|
|
80
87
|
const mr = await env.exec(deps.measureCmd, { cwd: repo });
|
|
@@ -83,7 +90,7 @@ export async function mergeBranches(reports, baseSha, deps) {
|
|
|
83
90
|
phase = "push";
|
|
84
91
|
if (repairMeta)
|
|
85
92
|
await env.exec(`git reset --soft ${Q(integBase)}`, { cwd: repo });
|
|
86
|
-
const costTail = { ...(repairMeta ? { repair: repairMeta } : {})
|
|
93
|
+
const costTail = { ...(repairMeta ? { repair: repairMeta } : {}) };
|
|
87
94
|
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 });
|
|
88
95
|
if (commit.exitCode !== 0) {
|
|
89
96
|
return { ok: false, phase: "push", reason: `failed to create integration commit (exit ${commit.exitCode})`, details: redactHead(commit.stdout + "\n" + commit.stderr, 800), ...costTail };
|
|
@@ -107,7 +114,6 @@ export async function mergeBranches(reports, baseSha, deps) {
|
|
|
107
114
|
candidatePatch: h.candidatePatch,
|
|
108
115
|
...(measure ? { measure } : {}),
|
|
109
116
|
...(repairMeta ? { repair: repairMeta } : {}),
|
|
110
|
-
...(conflictResolverCostUsd > 0 ? { conflictResolverCostUsd } : {}),
|
|
111
117
|
};
|
|
112
118
|
}
|
|
113
119
|
const push = await deps.push(integrated.stdout, baseSha);
|
|
@@ -117,7 +123,7 @@ export async function mergeBranches(reports, baseSha, deps) {
|
|
|
117
123
|
return { ok: false, phase: "push", reason: `push failed: ${push.error}`, ...costTail };
|
|
118
124
|
}
|
|
119
125
|
deps.logger?.info?.("merge_ok", { ref: push.ref, merged: mergeable.map((r) => r.workerId), ...(repairMeta ? { repairRounds: repairMeta.rounds } : {}) });
|
|
120
|
-
return { ok: true, ref: push.ref, merged: mergeable.map((r) => r.workerId), ...(measure ? { measure } : {}), ...(repairMeta ? { repair: repairMeta } : {})
|
|
126
|
+
return { ok: true, ref: push.ref, merged: mergeable.map((r) => r.workerId), ...(measure ? { measure } : {}), ...(repairMeta ? { repair: repairMeta } : {}) };
|
|
121
127
|
}
|
|
122
128
|
catch (e) {
|
|
123
129
|
return {
|
|
@@ -125,7 +131,6 @@ export async function mergeBranches(reports, baseSha, deps) {
|
|
|
125
131
|
quarantine: mergeable.map((r) => r.workerId),
|
|
126
132
|
...(measure ? { measure } : {}),
|
|
127
133
|
...(repairMeta ? { repair: repairMeta } : {}),
|
|
128
|
-
...(conflictResolverCostUsd > 0 ? { conflictResolverCostUsd } : {}),
|
|
129
134
|
};
|
|
130
135
|
}
|
|
131
136
|
finally {
|
|
@@ -170,28 +175,30 @@ async function evaluatePushHold(mergeable, observeMeasure, integratedPatch, deps
|
|
|
170
175
|
}
|
|
171
176
|
return { measure };
|
|
172
177
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
const check = await env
|
|
187
|
-
.exec(`grep -rIlE '^(<<<<<<<|>>>>>>>)' . 2>/dev/null; find . -name '*.rej' 2>/dev/null | head -20`, { cwd: repo })
|
|
188
|
-
.catch(() => ({ exitCode: -1, stdout: "DIRTY", stderr: "" }));
|
|
189
|
-
if (!check.stdout.trim()) {
|
|
190
|
-
await env.exec(`find . -name '*.orig' -delete 2>/dev/null || true`, { cwd: repo }).catch(() => { });
|
|
191
|
-
deps.logger?.info?.("merge_conflict_resolved", { workerId: p.workerId, rounds: round + 1 });
|
|
192
|
-
return { resolved: true, costUsd };
|
|
178
|
+
export const PROBE_FAIL_PREFIX = "__PROBE_FAIL_";
|
|
179
|
+
export function conflictFilesOf(stdout) {
|
|
180
|
+
const out = [];
|
|
181
|
+
const probeFailed = [];
|
|
182
|
+
const seen = new Set();
|
|
183
|
+
for (const raw of stdout.split("\n")) {
|
|
184
|
+
let f = raw.trim();
|
|
185
|
+
if (f === "")
|
|
186
|
+
continue;
|
|
187
|
+
if (f.startsWith(PROBE_FAIL_PREFIX)) {
|
|
188
|
+
probeFailed.push(f.slice(PROBE_FAIL_PREFIX.length));
|
|
189
|
+
continue;
|
|
193
190
|
}
|
|
191
|
+
if (f.startsWith("./"))
|
|
192
|
+
f = f.slice(2);
|
|
193
|
+
if (f.endsWith(".rej"))
|
|
194
|
+
f = f.slice(0, -4);
|
|
195
|
+
if (f === "" || seen.has(f))
|
|
196
|
+
continue;
|
|
197
|
+
seen.add(f);
|
|
198
|
+
if (out.length >= CONFLICT_FILES_MAX)
|
|
199
|
+
return { files: out, truncated: true, probeFailed };
|
|
200
|
+
out.push(f);
|
|
194
201
|
}
|
|
195
|
-
return {
|
|
202
|
+
return { files: out, truncated: false, probeFailed };
|
|
196
203
|
}
|
|
197
204
|
//# sourceMappingURL=merge.js.map
|