self-bench 0.3.0 → 0.3.3
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/.dockerignore +9 -0
- package/Dockerfile +38 -0
- package/Dockerfile.sandbox +24 -0
- package/README.md +103 -171
- package/biome.json +18 -0
- package/bun.lock +1182 -0
- package/compose.yaml +85 -0
- package/dist/agent-smoke-main.js +1 -1
- package/dist/api.d.ts.map +1 -1
- package/dist/api.js +40 -2
- package/dist/api.js.map +1 -1
- package/dist/artifacts.d.ts +0 -1
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +0 -3
- package/dist/artifacts.js.map +1 -1
- package/dist/build-metadata.d.ts +2 -0
- package/dist/build-metadata.d.ts.map +1 -0
- package/dist/build-metadata.js +2 -0
- package/dist/build-metadata.js.map +1 -0
- package/dist/cli.js +26 -8
- package/dist/cli.js.map +1 -1
- package/dist/codex-review.d.ts +2 -1
- package/dist/codex-review.d.ts.map +1 -1
- package/dist/codex-review.js +26 -22
- package/dist/codex-review.js.map +1 -1
- package/dist/config.d.ts +0 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +0 -2
- package/dist/config.js.map +1 -1
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.d.ts.map +1 -1
- package/dist/eval-main.js +3 -3
- package/dist/evaluate.d.ts.map +1 -1
- package/dist/evaluate.js +12 -7
- package/dist/evaluate.js.map +1 -1
- package/dist/provenance.d.ts +0 -1
- package/dist/provenance.d.ts.map +1 -1
- package/dist/provenance.js.map +1 -1
- package/dist/reaudit-main.js +10 -7
- package/dist/reaudit-main.js.map +1 -1
- package/dist/repair-main.js +8 -5
- package/dist/repair-main.js.map +1 -1
- package/dist/review/assets/index-CZ3G759O.js +1568 -0
- package/dist/review/assets/index-DgSLzX8l.css +1 -0
- package/dist/review/assets/{mojo-Blby8dQK.js → mojo-DJz3ZmWd.js} +1 -1
- package/dist/review/assets/typst-BUadGCkm.js +1 -0
- package/dist/review/index.html +2 -2
- package/dist/sandbox-repair.bundle.js +8 -7
- package/dist/sandbox-repair.js +7 -7
- package/dist/sandbox-repair.js.map +1 -1
- package/dist/sandbox-review.bundle.js +29 -25
- package/dist/sandbox-review.js +5 -3
- package/dist/sandbox-review.js.map +1 -1
- package/dist/sandbox-validation-repair.bundle.js +9 -9
- package/dist/sandbox-validation-repair.js +11 -9
- package/dist/sandbox-validation-repair.js.map +1 -1
- package/dist/subscription-auth.d.ts +12 -1
- package/dist/subscription-auth.d.ts.map +1 -1
- package/dist/subscription-auth.js +16 -16
- package/dist/subscription-auth.js.map +1 -1
- package/dist/{activities.d.ts → temporal/activities.d.ts} +2 -3
- package/dist/temporal/activities.d.ts.map +1 -0
- package/dist/{activities.js → temporal/activities.js} +44 -33
- package/dist/temporal/activities.js.map +1 -0
- package/dist/{temporal.d.ts → temporal/connection.d.ts} +2 -2
- package/dist/temporal/connection.d.ts.map +1 -0
- package/dist/{temporal.js → temporal/connection.js} +1 -1
- package/dist/temporal/connection.js.map +1 -0
- package/dist/{worker-main.d.ts.map → temporal/worker-main.d.ts.map} +1 -1
- package/dist/{worker-main.js → temporal/worker-main.js} +5 -9
- package/dist/temporal/worker-main.js.map +1 -0
- package/dist/{workflow.d.ts → temporal/workflow.d.ts} +1 -1
- package/dist/temporal/workflow.d.ts.map +1 -0
- package/dist/temporal/workflow.js.map +1 -0
- package/dist/validate-main.js +1 -1
- package/docs/evaluations.md +68 -0
- package/docs/operations.md +169 -0
- package/docs/task-construction.md +94 -0
- package/package.json +33 -20
- package/scripts/verify-package.ts +57 -0
- package/scripts/write-build-metadata.ts +27 -0
- package/src/agent-smoke-main.ts +63 -0
- package/src/agent-smoke.ts +132 -0
- package/src/api-main.ts +12 -0
- package/src/api.ts +277 -0
- package/src/artifacts.ts +357 -0
- package/src/audit.ts +106 -0
- package/src/build-metadata.ts +3 -0
- package/src/cli.ts +359 -0
- package/src/codex-review.ts +229 -0
- package/src/config.ts +114 -0
- package/src/contracts.ts +204 -0
- package/src/coupling.ts +259 -0
- package/src/docker-executor.ts +115 -0
- package/src/eval-main.ts +92 -0
- package/src/evaluate.ts +298 -0
- package/src/github.ts +26 -0
- package/src/harbor-results.ts +142 -0
- package/src/harbor-task.ts +528 -0
- package/src/hash.ts +5 -0
- package/src/modal-auth.ts +11 -0
- package/src/modal-executor.ts +176 -0
- package/src/parallel.ts +24 -0
- package/src/process.ts +165 -0
- package/src/provenance.ts +457 -0
- package/src/reaudit-main.ts +195 -0
- package/src/repair-main.ts +206 -0
- package/src/repair.ts +55 -0
- package/src/run-wait.ts +40 -0
- package/src/sandbox-author.ts +19 -0
- package/src/sandbox-repair.ts +156 -0
- package/src/sandbox-review.ts +19 -0
- package/src/sandbox-validation-repair.ts +177 -0
- package/src/sandbox.ts +51 -0
- package/src/subscription-auth.ts +80 -0
- package/src/temporal/activities.ts +1241 -0
- package/src/temporal/connection.ts +23 -0
- package/src/temporal/worker-main.ts +28 -0
- package/src/temporal/workflow.ts +519 -0
- package/src/validate-main.ts +171 -0
- package/src/validation-repair.ts +94 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +21 -0
- package/dist/activities.d.ts.map +0 -1
- package/dist/activities.js.map +0 -1
- package/dist/extensions/review.d.ts +0 -3
- package/dist/extensions/review.d.ts.map +0 -1
- package/dist/extensions/review.js +0 -44
- package/dist/extensions/review.js.map +0 -1
- package/dist/review/assets/index-Dq-6DFzI.css +0 -1
- package/dist/review/assets/index-DurW_RD7.js +0 -1567
- package/dist/review/assets/typst-DHCkPAjA.js +0 -1
- package/dist/temporal.d.ts.map +0 -1
- package/dist/temporal.js.map +0 -1
- package/dist/worker-main.js.map +0 -1
- package/dist/workflow.d.ts.map +0 -1
- package/dist/workflow.js.map +0 -1
- package/src/extensions/review.ts +0 -54
- /package/dist/{worker-main.d.ts → temporal/worker-main.d.ts} +0 -0
- /package/dist/{workflow.js → temporal/workflow.js} +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Connection } from "@temporalio/client";
|
|
2
|
+
import { NativeConnection } from "@temporalio/worker";
|
|
3
|
+
import type { SelfBenchConfig } from "../config.js";
|
|
4
|
+
|
|
5
|
+
export async function connectTemporalClient(
|
|
6
|
+
config: SelfBenchConfig["temporal"],
|
|
7
|
+
): Promise<Connection> {
|
|
8
|
+
return await Connection.connect({
|
|
9
|
+
address: config.address,
|
|
10
|
+
tls: config.tls,
|
|
11
|
+
...(config.apiKey ? { apiKey: config.apiKey } : {}),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function connectTemporalWorker(
|
|
16
|
+
config: SelfBenchConfig["temporal"],
|
|
17
|
+
): Promise<NativeConnection> {
|
|
18
|
+
return await NativeConnection.connect({
|
|
19
|
+
address: config.address,
|
|
20
|
+
tls: config.tls,
|
|
21
|
+
...(config.apiKey ? { apiKey: config.apiKey } : {}),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { fileURLToPath } from "node:url";
|
|
2
|
+
import { Worker } from "@temporalio/worker";
|
|
3
|
+
import { loadConfig } from "../config.js";
|
|
4
|
+
import { removeEmptyModalCredentialOverrides } from "../modal-auth.js";
|
|
5
|
+
import { runCommand } from "../process.js";
|
|
6
|
+
import { createActivities } from "./activities.js";
|
|
7
|
+
import { connectTemporalWorker } from "./connection.js";
|
|
8
|
+
|
|
9
|
+
removeEmptyModalCredentialOverrides();
|
|
10
|
+
const config = loadConfig();
|
|
11
|
+
if (config.execution.kind === "docker" || config.harborEnvironment === "docker") {
|
|
12
|
+
await runCommand("docker", ["info"], { timeoutMs: 30_000 });
|
|
13
|
+
await runCommand("docker", ["compose", "version"], { timeoutMs: 30_000 });
|
|
14
|
+
}
|
|
15
|
+
const connection = await connectTemporalWorker(config.temporal);
|
|
16
|
+
const workflowsPath = fileURLToPath(new URL("./workflow.js", import.meta.url));
|
|
17
|
+
const worker = await Worker.create({
|
|
18
|
+
connection,
|
|
19
|
+
namespace: config.temporal.namespace,
|
|
20
|
+
taskQueue: config.temporal.taskQueue,
|
|
21
|
+
workflowsPath,
|
|
22
|
+
activities: createActivities(config),
|
|
23
|
+
maxConcurrentActivityTaskExecutions: config.activityConcurrency,
|
|
24
|
+
});
|
|
25
|
+
console.log(
|
|
26
|
+
`SelfBench worker polling ${config.temporal.namespace}/${config.temporal.taskQueue} with activity concurrency ${config.activityConcurrency}`,
|
|
27
|
+
);
|
|
28
|
+
await worker.run();
|
|
@@ -0,0 +1,519 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ActivityFailure,
|
|
3
|
+
ApplicationFailure,
|
|
4
|
+
defineQuery,
|
|
5
|
+
isCancellation,
|
|
6
|
+
proxyActivities,
|
|
7
|
+
setHandler,
|
|
8
|
+
} from "@temporalio/workflow";
|
|
9
|
+
import type {
|
|
10
|
+
AuthoredTask,
|
|
11
|
+
AuthorOutcome,
|
|
12
|
+
Candidate,
|
|
13
|
+
Difficulty,
|
|
14
|
+
DiscoveryProgress,
|
|
15
|
+
RunRequest,
|
|
16
|
+
RunResult,
|
|
17
|
+
RunStatus,
|
|
18
|
+
TaskProgress,
|
|
19
|
+
} from "../contracts.js";
|
|
20
|
+
import type {
|
|
21
|
+
AuthorCandidateInput,
|
|
22
|
+
DiscoveryShardInput,
|
|
23
|
+
ExportInput,
|
|
24
|
+
RepairTaskInput,
|
|
25
|
+
SelfBenchActivities,
|
|
26
|
+
TaskStageInput,
|
|
27
|
+
ValidationRepairTaskInput,
|
|
28
|
+
} from "./activities.js";
|
|
29
|
+
|
|
30
|
+
export const statusQuery = defineQuery<RunStatus>("status");
|
|
31
|
+
|
|
32
|
+
const taskActivities = proxyActivities<
|
|
33
|
+
Omit<SelfBenchActivities, "discoverCandidateShard" | "repairValidationTask">
|
|
34
|
+
>({
|
|
35
|
+
startToCloseTimeout: "7 hours",
|
|
36
|
+
heartbeatTimeout: "10 minutes",
|
|
37
|
+
cancellationType: "WAIT_CANCELLATION_COMPLETED",
|
|
38
|
+
retry: {
|
|
39
|
+
initialInterval: "5 seconds",
|
|
40
|
+
backoffCoefficient: 2,
|
|
41
|
+
maximumInterval: "2 minutes",
|
|
42
|
+
maximumAttempts: 4,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const validationRepairActivity = proxyActivities<Pick<SelfBenchActivities, "repairValidationTask">>(
|
|
47
|
+
{
|
|
48
|
+
startToCloseTimeout: "2 hours",
|
|
49
|
+
heartbeatTimeout: "10 minutes",
|
|
50
|
+
cancellationType: "WAIT_CANCELLATION_COMPLETED",
|
|
51
|
+
retry: { maximumAttempts: 1 },
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const discoveryActivities = proxyActivities<Pick<SelfBenchActivities, "discoverCandidateShard">>({
|
|
56
|
+
startToCloseTimeout: "1 hour",
|
|
57
|
+
heartbeatTimeout: "10 minutes",
|
|
58
|
+
cancellationType: "WAIT_CANCELLATION_COMPLETED",
|
|
59
|
+
retry: {
|
|
60
|
+
initialInterval: "5 seconds",
|
|
61
|
+
backoffCoefficient: 2,
|
|
62
|
+
maximumInterval: "1 minute",
|
|
63
|
+
maximumAttempts: 3,
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const activities: SelfBenchActivities = {
|
|
68
|
+
discoverCandidateShard: discoveryActivities.discoverCandidateShard,
|
|
69
|
+
authorCandidate: taskActivities.authorCandidate,
|
|
70
|
+
validateTask: taskActivities.validateTask,
|
|
71
|
+
repairValidationTask: validationRepairActivity.repairValidationTask,
|
|
72
|
+
reviewTask: taskActivities.reviewTask,
|
|
73
|
+
repairTask: taskActivities.repairTask,
|
|
74
|
+
auditTask: taskActivities.auditTask,
|
|
75
|
+
buildExport: taskActivities.buildExport,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const DISCOVERY_SHARD_COUNT = 8;
|
|
79
|
+
const DISCOVERY_SHARD_OVERFETCH = 3;
|
|
80
|
+
const MAX_CANDIDATES_PER_TIER_PER_SHARD = 8;
|
|
81
|
+
const MAX_DISCOVERED_CANDIDATES = 300;
|
|
82
|
+
|
|
83
|
+
export async function selfBenchRunWorkflow(input: RunRequest): Promise<RunResult> {
|
|
84
|
+
return await executeRun(input, activities, (status) => setHandler(statusQuery, () => status()));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function executeRun(
|
|
88
|
+
input: RunRequest,
|
|
89
|
+
activitySet: SelfBenchActivities,
|
|
90
|
+
installStatusQuery: (status: () => RunStatus) => void = () => undefined,
|
|
91
|
+
): Promise<RunResult> {
|
|
92
|
+
const requested = Object.values(input.candidateCounts).reduce((sum, count) => sum + count, 0);
|
|
93
|
+
let status: RunStatus = {
|
|
94
|
+
runId: input.runId,
|
|
95
|
+
phase: "queued",
|
|
96
|
+
requested,
|
|
97
|
+
requestedByDifficulty: input.candidateCounts,
|
|
98
|
+
discovered: 0,
|
|
99
|
+
accepted: 0,
|
|
100
|
+
rejected: 0,
|
|
101
|
+
tasks: [],
|
|
102
|
+
};
|
|
103
|
+
installStatusQuery(() => status);
|
|
104
|
+
const setPhase = (phase: RunStatus["phase"]): void => {
|
|
105
|
+
status = { ...status, phase };
|
|
106
|
+
};
|
|
107
|
+
const setTasks = (tasks: readonly TaskProgress[]): void => {
|
|
108
|
+
status = {
|
|
109
|
+
...status,
|
|
110
|
+
tasks,
|
|
111
|
+
accepted: tasks.filter((task) => task.status === "accepted").length,
|
|
112
|
+
rejected: tasks.filter((task) => task.status === "rejected").length,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
const setDiscovered = (discovered: number): void => {
|
|
116
|
+
status = {
|
|
117
|
+
...status,
|
|
118
|
+
discovered,
|
|
119
|
+
...(status.discovery ? { discovery: { ...status.discovery, candidates: discovered } } : {}),
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
setPhase("discovering");
|
|
125
|
+
const updateDiscovery = (progress: DiscoveryProgress): void => {
|
|
126
|
+
status = { ...status, discovery: progress };
|
|
127
|
+
};
|
|
128
|
+
const candidates: Candidate[] = [];
|
|
129
|
+
let discoveryWave = 0;
|
|
130
|
+
while (true) {
|
|
131
|
+
const selected = selectCandidates(candidates, input.candidateCounts);
|
|
132
|
+
const missing = missingCandidateCounts(selected, input.candidateCounts);
|
|
133
|
+
if (Object.values(missing).every((count) => count === 0)) {
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
const capacity = MAX_DISCOVERED_CANDIDATES - candidates.length;
|
|
137
|
+
if (capacity <= 0) {
|
|
138
|
+
setPhase("blocked");
|
|
139
|
+
throw candidatePoolExhausted(selected, input.candidateCounts);
|
|
140
|
+
}
|
|
141
|
+
const additions = await discoverWave(activitySet, input, {
|
|
142
|
+
wave: discoveryWave,
|
|
143
|
+
targetCounts: missing,
|
|
144
|
+
excludedSourcePrs: candidates.map((candidate) => candidate.sourcePr),
|
|
145
|
+
onProgress: updateDiscovery,
|
|
146
|
+
});
|
|
147
|
+
const knownPrs = new Set(candidates.map((candidate) => candidate.sourcePr));
|
|
148
|
+
const uniqueAdditions = additions.filter((candidate) => !knownPrs.has(candidate.sourcePr));
|
|
149
|
+
if (uniqueAdditions.length === 0) {
|
|
150
|
+
setPhase("blocked");
|
|
151
|
+
throw candidatePoolExhausted(selected, input.candidateCounts);
|
|
152
|
+
}
|
|
153
|
+
candidates.push(...uniqueAdditions.slice(0, capacity));
|
|
154
|
+
setDiscovered(candidates.length);
|
|
155
|
+
discoveryWave += 1;
|
|
156
|
+
}
|
|
157
|
+
const selectedCandidates = selectCandidates(candidates, input.candidateCounts);
|
|
158
|
+
const taskProgress: TaskProgress[] = [];
|
|
159
|
+
const acceptedTasks: AuthoredTask[] = [];
|
|
160
|
+
const taskIds = new Set<string>();
|
|
161
|
+
|
|
162
|
+
const rejectProgress = (progress: TaskProgress, reason: string): void => {
|
|
163
|
+
progress.status = "rejected";
|
|
164
|
+
progress.reason = reason;
|
|
165
|
+
setTasks(taskProgress);
|
|
166
|
+
};
|
|
167
|
+
const processCandidate = async (candidate: Candidate): Promise<void> => {
|
|
168
|
+
const progress: TaskProgress = {
|
|
169
|
+
candidateId: candidate.candidateId,
|
|
170
|
+
taskId: candidate.candidateId,
|
|
171
|
+
difficulty: candidate.difficulty,
|
|
172
|
+
status: "authoring",
|
|
173
|
+
};
|
|
174
|
+
taskProgress.push(progress);
|
|
175
|
+
setTasks(taskProgress);
|
|
176
|
+
try {
|
|
177
|
+
const authored = await activitySet.authorCandidate({
|
|
178
|
+
run: input,
|
|
179
|
+
candidate,
|
|
180
|
+
} satisfies AuthorCandidateInput);
|
|
181
|
+
if (authored.kind === "rejected") {
|
|
182
|
+
rejectProgress(progress, authored.reason);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
let task = authored.task;
|
|
186
|
+
if (taskIds.has(task.taskId)) {
|
|
187
|
+
rejectProgress(progress, `authoring repeated task ID ${task.taskId}`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
taskIds.add(task.taskId);
|
|
191
|
+
progress.taskId = task.taskId;
|
|
192
|
+
|
|
193
|
+
progress.status = "auditing";
|
|
194
|
+
setTasks(taskProgress);
|
|
195
|
+
const audit = await activitySet.auditTask({ run: input, task } satisfies TaskStageInput);
|
|
196
|
+
if (!audit.accepted) {
|
|
197
|
+
rejectProgress(progress, audit.reason ?? "audit rejected task");
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
progress.status = "validating";
|
|
202
|
+
setTasks(taskProgress);
|
|
203
|
+
let validation = await activitySet.validateTask({
|
|
204
|
+
run: input,
|
|
205
|
+
task,
|
|
206
|
+
} satisfies TaskStageInput);
|
|
207
|
+
if (!validation.accepted) {
|
|
208
|
+
progress.status = "repairing";
|
|
209
|
+
setTasks(taskProgress);
|
|
210
|
+
let repaired: AuthorOutcome;
|
|
211
|
+
try {
|
|
212
|
+
repaired = await activitySet.repairValidationTask({
|
|
213
|
+
run: input,
|
|
214
|
+
task,
|
|
215
|
+
validation,
|
|
216
|
+
} satisfies ValidationRepairTaskInput);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (isCancellation(error)) {
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
rejectProgress(progress, "validation repair failed after its single activity attempt");
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
if (repaired.kind === "rejected") {
|
|
225
|
+
rejectProgress(progress, repaired.reason);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
task = repaired.task;
|
|
229
|
+
|
|
230
|
+
progress.status = "auditing";
|
|
231
|
+
setTasks(taskProgress);
|
|
232
|
+
const repairAudit = await activitySet.auditTask({
|
|
233
|
+
run: input,
|
|
234
|
+
task,
|
|
235
|
+
} satisfies TaskStageInput);
|
|
236
|
+
if (!repairAudit.accepted) {
|
|
237
|
+
rejectProgress(progress, repairAudit.reason ?? "audit rejected validation repair");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
progress.status = "validating";
|
|
242
|
+
setTasks(taskProgress);
|
|
243
|
+
validation = await activitySet.validateTask({
|
|
244
|
+
run: input,
|
|
245
|
+
task,
|
|
246
|
+
} satisfies TaskStageInput);
|
|
247
|
+
if (!validation.accepted) {
|
|
248
|
+
rejectProgress(progress, validation.reason ?? "validation rejected repaired harness");
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
progress.status = "reviewing";
|
|
254
|
+
setTasks(taskProgress);
|
|
255
|
+
let review = await activitySet.reviewTask({ run: input, task } satisfies TaskStageInput);
|
|
256
|
+
if (!review.accepted) {
|
|
257
|
+
progress.status = "repairing";
|
|
258
|
+
setTasks(taskProgress);
|
|
259
|
+
let repaired: AuthorOutcome;
|
|
260
|
+
try {
|
|
261
|
+
repaired = await activitySet.repairTask({
|
|
262
|
+
run: input,
|
|
263
|
+
task,
|
|
264
|
+
review: review.report,
|
|
265
|
+
} satisfies RepairTaskInput);
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (isCancellation(error)) {
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
rejectProgress(progress, "test repair failed after activity retries");
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (repaired.kind === "rejected") {
|
|
274
|
+
rejectProgress(progress, repaired.reason);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
task = repaired.task;
|
|
278
|
+
|
|
279
|
+
progress.status = "auditing";
|
|
280
|
+
setTasks(taskProgress);
|
|
281
|
+
const repairAudit = await activitySet.auditTask({
|
|
282
|
+
run: input,
|
|
283
|
+
task,
|
|
284
|
+
} satisfies TaskStageInput);
|
|
285
|
+
if (!repairAudit.accepted) {
|
|
286
|
+
rejectProgress(progress, repairAudit.reason ?? "audit rejected repaired task");
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
progress.status = "validating";
|
|
291
|
+
setTasks(taskProgress);
|
|
292
|
+
const repairValidation = await activitySet.validateTask({
|
|
293
|
+
run: input,
|
|
294
|
+
task,
|
|
295
|
+
} satisfies TaskStageInput);
|
|
296
|
+
if (!repairValidation.accepted) {
|
|
297
|
+
rejectProgress(
|
|
298
|
+
progress,
|
|
299
|
+
repairValidation.reason ?? "validation rejected repaired task",
|
|
300
|
+
);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
progress.status = "reviewing";
|
|
305
|
+
setTasks(taskProgress);
|
|
306
|
+
review = await activitySet.reviewTask({ run: input, task } satisfies TaskStageInput);
|
|
307
|
+
if (!review.accepted) {
|
|
308
|
+
rejectProgress(progress, review.reason ?? "review rejected repaired task");
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
progress.status = "accepted";
|
|
314
|
+
acceptedTasks.push(task);
|
|
315
|
+
setTasks(taskProgress);
|
|
316
|
+
} catch (error) {
|
|
317
|
+
if (!isHarborInfrastructureFailure(error)) {
|
|
318
|
+
throw error;
|
|
319
|
+
}
|
|
320
|
+
progress.status = "infrastructure_failed";
|
|
321
|
+
progress.reason = infrastructureFailureMessage(error);
|
|
322
|
+
setTasks(taskProgress);
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
setPhase("authoring");
|
|
327
|
+
await Promise.all(selectedCandidates.map(processCandidate));
|
|
328
|
+
|
|
329
|
+
setPhase("exporting");
|
|
330
|
+
const exportRef = await activitySet.buildExport({
|
|
331
|
+
run: input,
|
|
332
|
+
tasks: acceptedTasks,
|
|
333
|
+
} satisfies ExportInput);
|
|
334
|
+
status = { ...status, phase: "complete", export: exportRef };
|
|
335
|
+
return {
|
|
336
|
+
runId: input.runId,
|
|
337
|
+
export: exportRef,
|
|
338
|
+
acceptedTaskIds: acceptedTasks.map((task) => task.taskId),
|
|
339
|
+
};
|
|
340
|
+
} catch (error) {
|
|
341
|
+
if (isCancellation(error)) {
|
|
342
|
+
status = { ...status, phase: "cancelled" };
|
|
343
|
+
} else if (status.phase !== "blocked") {
|
|
344
|
+
status = {
|
|
345
|
+
...status,
|
|
346
|
+
phase: "failed",
|
|
347
|
+
error: error instanceof Error ? error.message : String(error),
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
throw error;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
interface DiscoveryWaveOptions {
|
|
355
|
+
readonly wave: number;
|
|
356
|
+
readonly targetCounts: RunRequest["candidateCounts"];
|
|
357
|
+
readonly excludedSourcePrs: readonly number[];
|
|
358
|
+
readonly onProgress: (progress: DiscoveryProgress) => void;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function discoverWave(
|
|
362
|
+
activitySet: SelfBenchActivities,
|
|
363
|
+
run: RunRequest,
|
|
364
|
+
options: DiscoveryWaveOptions,
|
|
365
|
+
): Promise<Candidate[]> {
|
|
366
|
+
const targetCounts = Object.fromEntries(
|
|
367
|
+
(["easy", "medium", "hard"] as const).map((difficulty) => [
|
|
368
|
+
difficulty,
|
|
369
|
+
options.targetCounts[difficulty] === 0
|
|
370
|
+
? 0
|
|
371
|
+
: Math.min(
|
|
372
|
+
MAX_CANDIDATES_PER_TIER_PER_SHARD,
|
|
373
|
+
Math.ceil(options.targetCounts[difficulty] / DISCOVERY_SHARD_COUNT) +
|
|
374
|
+
DISCOVERY_SHARD_OVERFETCH,
|
|
375
|
+
),
|
|
376
|
+
]),
|
|
377
|
+
) as Record<Difficulty, number>;
|
|
378
|
+
let completedShards = 0;
|
|
379
|
+
let failedShards = 0;
|
|
380
|
+
let candidateCount = 0;
|
|
381
|
+
const reportProgress = (): void =>
|
|
382
|
+
options.onProgress({
|
|
383
|
+
wave: options.wave,
|
|
384
|
+
totalShards: DISCOVERY_SHARD_COUNT,
|
|
385
|
+
completedShards,
|
|
386
|
+
failedShards,
|
|
387
|
+
candidates: candidateCount,
|
|
388
|
+
});
|
|
389
|
+
reportProgress();
|
|
390
|
+
const shards = await Promise.all(
|
|
391
|
+
Array.from({ length: DISCOVERY_SHARD_COUNT }, async (_unused, shardIndex) => {
|
|
392
|
+
try {
|
|
393
|
+
const result = await activitySet.discoverCandidateShard({
|
|
394
|
+
run,
|
|
395
|
+
wave: options.wave,
|
|
396
|
+
shardIndex,
|
|
397
|
+
shardCount: DISCOVERY_SHARD_COUNT,
|
|
398
|
+
targetCounts,
|
|
399
|
+
excludedSourcePrs: options.excludedSourcePrs,
|
|
400
|
+
} satisfies DiscoveryShardInput);
|
|
401
|
+
completedShards += 1;
|
|
402
|
+
candidateCount += result.candidates.length;
|
|
403
|
+
reportProgress();
|
|
404
|
+
return result;
|
|
405
|
+
} catch (error) {
|
|
406
|
+
if (isCancellation(error)) {
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
if (isNonRetryableActivityFailure(error)) {
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
412
|
+
failedShards += 1;
|
|
413
|
+
reportProgress();
|
|
414
|
+
return { candidates: [], report: undefined };
|
|
415
|
+
}
|
|
416
|
+
}),
|
|
417
|
+
);
|
|
418
|
+
const ranked = interleave(shards.map((shard) => shard.candidates));
|
|
419
|
+
const seenPrs = new Set<number>();
|
|
420
|
+
return ranked.filter((candidate) => {
|
|
421
|
+
if (seenPrs.has(candidate.sourcePr)) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
seenPrs.add(candidate.sourcePr);
|
|
425
|
+
return true;
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function selectCandidates(
|
|
430
|
+
candidates: readonly Candidate[],
|
|
431
|
+
counts: RunRequest["candidateCounts"],
|
|
432
|
+
): Candidate[] {
|
|
433
|
+
const selectedCounts: Record<Difficulty, number> = { easy: 0, medium: 0, hard: 0 };
|
|
434
|
+
return candidates.filter((candidate) => {
|
|
435
|
+
if (selectedCounts[candidate.difficulty] >= counts[candidate.difficulty]) {
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
selectedCounts[candidate.difficulty] += 1;
|
|
439
|
+
return true;
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function missingCandidateCounts(
|
|
444
|
+
selected: readonly Candidate[],
|
|
445
|
+
requested: RunRequest["candidateCounts"],
|
|
446
|
+
): Record<Difficulty, number> {
|
|
447
|
+
const actual: Record<Difficulty, number> = { easy: 0, medium: 0, hard: 0 };
|
|
448
|
+
for (const candidate of selected) {
|
|
449
|
+
actual[candidate.difficulty] += 1;
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
easy: requested.easy - actual.easy,
|
|
453
|
+
medium: requested.medium - actual.medium,
|
|
454
|
+
hard: requested.hard - actual.hard,
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function candidatePoolExhausted(
|
|
459
|
+
selected: readonly Candidate[],
|
|
460
|
+
requested: RunRequest["candidateCounts"],
|
|
461
|
+
): ApplicationFailure {
|
|
462
|
+
const missing = missingCandidateCounts(selected, requested);
|
|
463
|
+
return ApplicationFailure.nonRetryable(
|
|
464
|
+
`candidate pool exhausted; missing easy=${missing.easy}, medium=${missing.medium}, hard=${missing.hard}`,
|
|
465
|
+
"CandidatePoolExhausted",
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function infrastructureFailureMessage(error: unknown): string {
|
|
470
|
+
let cause = error;
|
|
471
|
+
let message = error instanceof Error ? error.message : String(error);
|
|
472
|
+
while (cause instanceof Error) {
|
|
473
|
+
if (cause instanceof ApplicationFailure && cause.type === "HarborInfrastructureFailure") {
|
|
474
|
+
return cause.message;
|
|
475
|
+
}
|
|
476
|
+
message = cause.message;
|
|
477
|
+
cause = cause.cause;
|
|
478
|
+
}
|
|
479
|
+
return message;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function isHarborInfrastructureFailure(error: unknown): boolean {
|
|
483
|
+
let cause = error;
|
|
484
|
+
while (cause instanceof Error) {
|
|
485
|
+
if (cause instanceof ApplicationFailure && cause.type === "HarborInfrastructureFailure") {
|
|
486
|
+
return true;
|
|
487
|
+
}
|
|
488
|
+
cause = cause.cause;
|
|
489
|
+
}
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function isNonRetryableActivityFailure(error: unknown): boolean {
|
|
494
|
+
if (!(error instanceof ActivityFailure)) {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
let cause: unknown = error.cause;
|
|
498
|
+
while (cause instanceof Error) {
|
|
499
|
+
if (cause instanceof ApplicationFailure) {
|
|
500
|
+
return cause.nonRetryable === true;
|
|
501
|
+
}
|
|
502
|
+
cause = cause.cause;
|
|
503
|
+
}
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function interleave<T>(groups: readonly (readonly T[])[]): T[] {
|
|
508
|
+
const output: T[] = [];
|
|
509
|
+
const maxLength = Math.max(0, ...groups.map((group) => group.length));
|
|
510
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
511
|
+
for (const group of groups) {
|
|
512
|
+
const value = group[index];
|
|
513
|
+
if (value !== undefined) {
|
|
514
|
+
output.push(value);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return output;
|
|
519
|
+
}
|