self-bench 0.3.5 → 0.3.6

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 (198) hide show
  1. package/README.md +16 -14
  2. package/biome.json +7 -1
  3. package/dist/api.d.ts.map +1 -1
  4. package/dist/api.js +7 -7
  5. package/dist/api.js.map +1 -1
  6. package/dist/build-metadata.js +1 -1
  7. package/dist/cli.js +75 -15
  8. package/dist/cli.js.map +1 -1
  9. package/dist/config.js +1 -1
  10. package/dist/contracts.d.ts +1 -0
  11. package/dist/contracts.d.ts.map +1 -1
  12. package/dist/contracts.js +6 -5
  13. package/dist/contracts.js.map +1 -1
  14. package/dist/extensions/discovery.d.ts.map +1 -1
  15. package/dist/extensions/discovery.js +12 -5
  16. package/dist/extensions/discovery.js.map +1 -1
  17. package/dist/harbor-task.d.ts.map +1 -1
  18. package/dist/harbor-task.js +3 -1
  19. package/dist/harbor-task.js.map +1 -1
  20. package/dist/project-paths.d.ts +2 -0
  21. package/dist/project-paths.d.ts.map +1 -0
  22. package/dist/project-paths.js +15 -0
  23. package/dist/project-paths.js.map +1 -0
  24. package/dist/provenance-associations.d.ts +49 -0
  25. package/dist/provenance-associations.d.ts.map +1 -0
  26. package/dist/provenance-associations.js +220 -0
  27. package/dist/provenance-associations.js.map +1 -0
  28. package/dist/provenance.d.ts +34 -13
  29. package/dist/provenance.d.ts.map +1 -1
  30. package/dist/provenance.js +81 -9
  31. package/dist/provenance.js.map +1 -1
  32. package/dist/reaudit-main.js +3 -3
  33. package/dist/reaudit-main.js.map +1 -1
  34. package/dist/repair-main.js +4 -4
  35. package/dist/repair-main.js.map +1 -1
  36. package/dist/{sandbox.d.ts → sandbox/contracts.d.ts} +5 -3
  37. package/dist/sandbox/contracts.d.ts.map +1 -0
  38. package/dist/sandbox/contracts.js +9 -0
  39. package/dist/sandbox/contracts.js.map +1 -0
  40. package/dist/sandbox/index.d.ts +5 -0
  41. package/dist/sandbox/index.d.ts.map +1 -0
  42. package/dist/{sandbox.js → sandbox/index.js} +6 -5
  43. package/dist/sandbox/index.js.map +1 -0
  44. package/dist/sandbox/programs/author.d.ts +3 -0
  45. package/dist/sandbox/programs/author.d.ts.map +1 -0
  46. package/dist/{sandbox-author.js → sandbox/programs/author.js} +2 -2
  47. package/dist/sandbox/programs/author.js.map +1 -0
  48. package/dist/sandbox/programs/prepare-repair.d.ts +7 -0
  49. package/dist/sandbox/programs/prepare-repair.d.ts.map +1 -0
  50. package/dist/sandbox/programs/prepare-repair.js +30 -0
  51. package/dist/sandbox/programs/prepare-repair.js.map +1 -0
  52. package/dist/sandbox/programs/repair.d.ts +3 -0
  53. package/dist/sandbox/programs/repair.d.ts.map +1 -0
  54. package/dist/{sandbox-repair.js → sandbox/programs/repair.js} +7 -28
  55. package/dist/sandbox/programs/repair.js.map +1 -0
  56. package/dist/sandbox/programs/review.d.ts +2 -0
  57. package/dist/sandbox/programs/review.d.ts.map +1 -0
  58. package/dist/{sandbox-review.js → sandbox/programs/review.js} +2 -2
  59. package/dist/sandbox/programs/review.js.map +1 -0
  60. package/dist/sandbox/programs/validation-repair.d.ts +3 -0
  61. package/dist/sandbox/programs/validation-repair.d.ts.map +1 -0
  62. package/dist/{sandbox-validation-repair.js → sandbox/programs/validation-repair.js} +7 -28
  63. package/dist/sandbox/programs/validation-repair.js.map +1 -0
  64. package/dist/{docker-executor.d.ts → sandbox/providers/docker/executor.d.ts} +3 -3
  65. package/dist/sandbox/providers/docker/executor.d.ts.map +1 -0
  66. package/dist/{docker-executor.js → sandbox/providers/docker/executor.js} +2 -2
  67. package/dist/sandbox/providers/docker/executor.js.map +1 -0
  68. package/dist/{modal-executor.d.ts → sandbox/providers/modal/executor.d.ts} +3 -3
  69. package/dist/sandbox/providers/modal/executor.d.ts.map +1 -0
  70. package/dist/{modal-executor.js → sandbox/providers/modal/executor.js} +50 -19
  71. package/dist/sandbox/providers/modal/executor.js.map +1 -0
  72. package/dist/{vercel-command.d.ts → sandbox/providers/vercel/command.d.ts} +3 -3
  73. package/dist/sandbox/providers/vercel/command.d.ts.map +1 -0
  74. package/dist/{vercel-command.js → sandbox/providers/vercel/command.js} +3 -3
  75. package/dist/sandbox/providers/vercel/command.js.map +1 -0
  76. package/dist/{vercel-executor.d.ts → sandbox/providers/vercel/executor.d.ts} +3 -3
  77. package/dist/sandbox/providers/vercel/executor.d.ts.map +1 -0
  78. package/dist/{vercel-executor.js → sandbox/providers/vercel/executor.js} +5 -5
  79. package/dist/sandbox/providers/vercel/executor.js.map +1 -0
  80. package/dist/{vercel-fetch.d.ts → sandbox/providers/vercel/fetch.d.ts} +1 -1
  81. package/dist/sandbox/providers/vercel/fetch.d.ts.map +1 -0
  82. package/dist/{vercel-fetch.js → sandbox/providers/vercel/fetch.js} +1 -1
  83. package/dist/sandbox/providers/vercel/fetch.js.map +1 -0
  84. package/dist/{sandbox-timeout.d.ts → sandbox/timeout.d.ts} +2 -2
  85. package/dist/sandbox/timeout.d.ts.map +1 -0
  86. package/dist/{sandbox-timeout.js → sandbox/timeout.js} +1 -1
  87. package/dist/sandbox/timeout.js.map +1 -0
  88. package/dist/sandbox-author.bundle.js +11 -8
  89. package/dist/sandbox-repair.bundle.js +43 -35
  90. package/dist/sandbox-review.bundle.js +2 -2
  91. package/dist/sandbox-validation-repair.bundle.js +50 -41
  92. package/dist/{vercel-cli.d.ts → setup/vercel/cli.d.ts} +2 -2
  93. package/dist/setup/vercel/cli.d.ts.map +1 -0
  94. package/dist/{vercel-cli.js → setup/vercel/cli.js} +2 -2
  95. package/dist/setup/vercel/cli.js.map +1 -0
  96. package/dist/setup/vercel/index.d.ts +4 -0
  97. package/dist/setup/vercel/index.d.ts.map +1 -0
  98. package/dist/setup/vercel/index.js +3 -0
  99. package/dist/setup/vercel/index.js.map +1 -0
  100. package/dist/{vercel-setup-probe.d.ts → setup/vercel/probe.d.ts} +2 -2
  101. package/dist/setup/vercel/probe.d.ts.map +1 -0
  102. package/dist/{vercel-setup-probe.js → setup/vercel/probe.js} +2 -2
  103. package/dist/setup/vercel/probe.js.map +1 -0
  104. package/dist/{vercel-profile.d.ts → setup/vercel/profile.d.ts} +1 -1
  105. package/dist/setup/vercel/profile.d.ts.map +1 -0
  106. package/dist/{vercel-profile.js → setup/vercel/profile.js} +2 -2
  107. package/dist/setup/vercel/profile.js.map +1 -0
  108. package/dist/{vercel-runtime-image.d.ts → setup/vercel/runtime-image.d.ts} +5 -5
  109. package/dist/setup/vercel/runtime-image.d.ts.map +1 -0
  110. package/dist/{vercel-runtime-image.js → setup/vercel/runtime-image.js} +2 -2
  111. package/dist/setup/vercel/runtime-image.js.map +1 -0
  112. package/dist/{vercel-setup.d.ts → setup/vercel/setup.d.ts} +8 -8
  113. package/dist/setup/vercel/setup.d.ts.map +1 -0
  114. package/dist/{vercel-setup.js → setup/vercel/setup.js} +8 -8
  115. package/dist/setup/vercel/setup.js.map +1 -0
  116. package/dist/temporal/activities.d.ts +12 -0
  117. package/dist/temporal/activities.d.ts.map +1 -1
  118. package/dist/temporal/activities.js +80 -33
  119. package/dist/temporal/activities.js.map +1 -1
  120. package/dist/temporal/workflow.d.ts +1 -1
  121. package/dist/temporal/workflow.d.ts.map +1 -1
  122. package/dist/temporal/workflow.js +90 -65
  123. package/dist/temporal/workflow.js.map +1 -1
  124. package/docs/operations.md +39 -5
  125. package/docs/task-construction.md +3 -1
  126. package/package.json +5 -5
  127. package/scripts/build-sandbox-programs.ts +22 -0
  128. package/scripts/verify-package.ts +25 -4
  129. package/scripts/write-build-metadata.ts +1 -1
  130. package/src/api.ts +7 -6
  131. package/src/cli.ts +99 -15
  132. package/src/config.ts +1 -1
  133. package/src/contracts.ts +7 -5
  134. package/src/extensions/discovery.ts +14 -5
  135. package/src/harbor-task.ts +6 -1
  136. package/src/project-paths.ts +15 -0
  137. package/src/provenance-associations.ts +311 -0
  138. package/src/provenance.ts +132 -28
  139. package/src/reaudit-main.ts +3 -3
  140. package/src/repair-main.ts +4 -4
  141. package/src/{sandbox.ts → sandbox/contracts.ts} +11 -20
  142. package/src/sandbox/index.ts +23 -0
  143. package/src/{sandbox-author.ts → sandbox/programs/author.ts} +1 -1
  144. package/src/sandbox/programs/prepare-repair.ts +44 -0
  145. package/src/{sandbox-repair.ts → sandbox/programs/repair.ts} +7 -31
  146. package/src/{sandbox-review.ts → sandbox/programs/review.ts} +1 -1
  147. package/src/{sandbox-validation-repair.ts → sandbox/programs/validation-repair.ts} +6 -30
  148. package/src/{docker-executor.ts → sandbox/providers/docker/executor.ts} +3 -3
  149. package/src/{modal-executor.ts → sandbox/providers/modal/executor.ts} +66 -25
  150. package/src/{vercel-command.ts → sandbox/providers/vercel/command.ts} +3 -3
  151. package/src/{vercel-executor.ts → sandbox/providers/vercel/executor.ts} +5 -5
  152. package/src/{sandbox-timeout.ts → sandbox/timeout.ts} +1 -1
  153. package/src/{vercel-cli.ts → setup/vercel/cli.ts} +1 -1
  154. package/src/setup/vercel/index.ts +3 -0
  155. package/src/{vercel-setup-probe.ts → setup/vercel/probe.ts} +5 -2
  156. package/src/{vercel-profile.ts → setup/vercel/profile.ts} +4 -1
  157. package/src/{vercel-runtime-image.ts → setup/vercel/runtime-image.ts} +5 -5
  158. package/src/{vercel-setup.ts → setup/vercel/setup.ts} +8 -8
  159. package/src/temporal/activities.ts +164 -63
  160. package/src/temporal/workflow.ts +120 -71
  161. package/tsconfig.json +1 -1
  162. package/dist/docker-executor.d.ts.map +0 -1
  163. package/dist/docker-executor.js.map +0 -1
  164. package/dist/modal-executor.d.ts.map +0 -1
  165. package/dist/modal-executor.js.map +0 -1
  166. package/dist/sandbox-author.d.ts +0 -3
  167. package/dist/sandbox-author.d.ts.map +0 -1
  168. package/dist/sandbox-author.js.map +0 -1
  169. package/dist/sandbox-repair.d.ts +0 -3
  170. package/dist/sandbox-repair.d.ts.map +0 -1
  171. package/dist/sandbox-repair.js.map +0 -1
  172. package/dist/sandbox-review.d.ts +0 -2
  173. package/dist/sandbox-review.d.ts.map +0 -1
  174. package/dist/sandbox-review.js.map +0 -1
  175. package/dist/sandbox-timeout.d.ts.map +0 -1
  176. package/dist/sandbox-timeout.js.map +0 -1
  177. package/dist/sandbox-validation-repair.d.ts +0 -3
  178. package/dist/sandbox-validation-repair.d.ts.map +0 -1
  179. package/dist/sandbox-validation-repair.js.map +0 -1
  180. package/dist/sandbox.d.ts.map +0 -1
  181. package/dist/sandbox.js.map +0 -1
  182. package/dist/vercel-cli.d.ts.map +0 -1
  183. package/dist/vercel-cli.js.map +0 -1
  184. package/dist/vercel-command.d.ts.map +0 -1
  185. package/dist/vercel-command.js.map +0 -1
  186. package/dist/vercel-executor.d.ts.map +0 -1
  187. package/dist/vercel-executor.js.map +0 -1
  188. package/dist/vercel-fetch.d.ts.map +0 -1
  189. package/dist/vercel-fetch.js.map +0 -1
  190. package/dist/vercel-profile.d.ts.map +0 -1
  191. package/dist/vercel-profile.js.map +0 -1
  192. package/dist/vercel-runtime-image.d.ts.map +0 -1
  193. package/dist/vercel-runtime-image.js.map +0 -1
  194. package/dist/vercel-setup-probe.d.ts.map +0 -1
  195. package/dist/vercel-setup-probe.js.map +0 -1
  196. package/dist/vercel-setup.d.ts.map +0 -1
  197. package/dist/vercel-setup.js.map +0 -1
  198. /package/src/{vercel-fetch.ts → sandbox/providers/vercel/fetch.ts} +0 -0
@@ -1,7 +1,6 @@
1
1
  import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
2
  import { tmpdir } from "node:os";
3
- import { dirname, join, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
3
+ import { join } from "node:path";
5
4
  import { CancelledFailure, Context } from "@temporalio/activity";
6
5
  import { ApplicationFailure } from "@temporalio/common";
7
6
  import { z } from "zod";
@@ -43,8 +42,21 @@ import {
43
42
  import { refreshHarborTask } from "../harbor-task.js";
44
43
  import { sha256 } from "../hash.js";
45
44
  import { runCommand } from "../process.js";
46
- import { assertProvenanceMatchesPullRequest, type ProvenanceMessage } from "../provenance.js";
47
- import { createSandboxExecutor, type SandboxExecutor, type SandboxRunOptions } from "../sandbox.js";
45
+ import { projectRoot } from "../project-paths.js";
46
+ import {
47
+ assertProvenanceMatchesPullRequest,
48
+ collectGitHubPullRequestProvenance,
49
+ combineRunProvenance,
50
+ type ProvenanceMessage,
51
+ provenanceMessageSchema,
52
+ } from "../provenance.js";
53
+ import {
54
+ createSandboxExecutor,
55
+ SandboxExecutionError,
56
+ type SandboxExecutor,
57
+ type SandboxResult,
58
+ type SandboxRunOptions,
59
+ } from "../sandbox/index.js";
48
60
  import { githubToken, loadCodexModelAuth, loadPiModelAuth } from "../subscription-auth.js";
49
61
 
50
62
  const HARBOR_INFRASTRUCTURE_FAILURE_TYPE = "HarborInfrastructureFailure";
@@ -62,7 +74,6 @@ const discoveryPlanSchema = z.object({
62
74
  sourceUrl: z.string().url(),
63
75
  baseCommit: z.string().regex(/^[0-9a-f]{40}$/i),
64
76
  completedCommit: z.string().regex(/^[0-9a-f]{40}$/i),
65
- request: z.string().min(1),
66
77
  provenance: z.object({
67
78
  sourceType: z.enum(["pi", "claude-code", "codex", "generic", "github-pull-request"]),
68
79
  sessionId: z.string().min(1),
@@ -105,6 +116,7 @@ export interface ExportInput {
105
116
  }
106
117
 
107
118
  export interface SelfBenchActivities {
119
+ collectRunProvenance(run: RunRequest): Promise<ArtifactRef>;
108
120
  discoverCandidateShard(input: DiscoveryShardInput): Promise<DiscoveryResult>;
109
121
  authorCandidate(input: AuthorCandidateInput): Promise<AuthorOutcome>;
110
122
  validateTask(input: TaskStageInput): Promise<ValidationResult>;
@@ -119,6 +131,7 @@ export function createActivities(config: SelfBenchWorkerConfig): SelfBenchActivi
119
131
  const store = createArtifactStore(config.artifact);
120
132
  const sandbox = createSandboxExecutor(config.execution);
121
133
  return {
134
+ collectRunProvenance: (run) => collectRunProvenance(store, run),
122
135
  discoverCandidateShard: (input) => discoverCandidateShard(store, sandbox, input),
123
136
  authorCandidate: (input) => authorCandidate(store, sandbox, input),
124
137
  validateTask: (input) => validateTask(store, config.harborEnvironment, input),
@@ -130,6 +143,29 @@ export function createActivities(config: SelfBenchWorkerConfig): SelfBenchActivi
130
143
  };
131
144
  }
132
145
 
146
+ async function collectRunProvenance(store: ArtifactStore, run: RunRequest): Promise<ArtifactRef> {
147
+ return await withActivityHeartbeats(
148
+ "collecting merged GitHub pull requests",
149
+ async ({ signal }) => {
150
+ const [localBytes, token] = await Promise.all([store.get(run.provenance), githubToken()]);
151
+ const local = parseProvenance(localBytes);
152
+ const github = await collectGitHubPullRequestProvenance(run.repository.url, token, signal);
153
+ const messages = combineRunProvenance(run.repository.url, local, github);
154
+ if (messages.length === 0) {
155
+ throw ApplicationFailure.nonRetryable(
156
+ "no sanitized local-session or GitHub pull-request provenance was found",
157
+ "NoProvenance",
158
+ );
159
+ }
160
+ return await store.put(
161
+ `runs/${run.runId}/input/combined-provenance-attempt-${Context.current().info.attempt}.jsonl`,
162
+ Buffer.from(`${messages.map((message) => JSON.stringify(message)).join("\n")}\n`),
163
+ "application/x-ndjson",
164
+ );
165
+ },
166
+ );
167
+ }
168
+
133
169
  async function discoverCandidateShard(
134
170
  store: ArtifactStore,
135
171
  sandbox: SandboxExecutor,
@@ -180,36 +216,43 @@ async function discoverCandidateShard(
180
216
  Context.current().heartbeat(
181
217
  `starting discovery wave ${input.wave} shard ${input.shardIndex} over ${shard.length} messages`,
182
218
  );
183
- const result = await withActivityHeartbeats(
184
- `running discovery wave ${input.wave} shard ${input.shardIndex}`,
185
- (options) =>
186
- sandbox.run(
187
- {
188
- runId: run.runId,
189
- stage: `discover-${input.wave}-${input.shardIndex}`,
190
- timeoutMs: DISCOVERY_TIMEOUT_MS,
191
- inactivityTimeoutMs: AGENT_INACTIVITY_TIMEOUT_MS,
192
- files: [
193
- { path: "/work/discovery.ts", contents: extension },
194
- { path: "/work/provenance.jsonl", contents: shardBytes },
195
- { path: "/work/prompt.txt", contents: discoveryShardPrompt(input, shard.length) },
196
- ],
197
- outputPaths: ["/work/discovery.json"],
198
- secrets: {
199
- ...(piAuth.apiKey ? { OPENAI_API_KEY: piAuth.apiKey } : {}),
200
- ...(piAuth.authJson ? { SELFBENCH_PI_AUTH_JSON: piAuth.authJson } : {}),
201
- ...(ghToken ? { GH_TOKEN: ghToken } : {}),
202
- },
203
- environment: {
204
- SOURCE_REPO_URL: run.repository.url,
205
- SOURCE_COMMIT: run.repository.commit,
206
- AUTHOR_MODEL: run.authoring.model,
207
- SELFBENCH_DISCOVERY_OUTPUT: "/work/discovery.json",
219
+ const result = await runSandboxWithFailureLog(store, `${attemptPrefix}/modal.log`, () =>
220
+ withActivityHeartbeats(
221
+ `running discovery wave ${input.wave} shard ${input.shardIndex}`,
222
+ (options) =>
223
+ sandbox.run(
224
+ {
225
+ runId: run.runId,
226
+ stage: `discover-${input.wave}-${input.shardIndex}`,
227
+ timeoutMs: DISCOVERY_TIMEOUT_MS,
228
+ inactivityTimeoutMs: AGENT_INACTIVITY_TIMEOUT_MS,
229
+ files: [
230
+ { path: "/work/discovery.ts", contents: extension },
231
+ {
232
+ path: "/work/excluded-source-prs.json",
233
+ contents: JSON.stringify(input.excludedSourcePrs),
234
+ },
235
+ { path: "/work/provenance.jsonl", contents: shardBytes },
236
+ { path: "/work/prompt.txt", contents: discoveryShardPrompt(input, shard.length) },
237
+ ],
238
+ outputPaths: ["/work/discovery.json"],
239
+ secrets: {
240
+ ...(piAuth.apiKey ? { OPENAI_API_KEY: piAuth.apiKey } : {}),
241
+ ...(piAuth.authJson ? { SELFBENCH_PI_AUTH_JSON: piAuth.authJson } : {}),
242
+ ...(ghToken ? { GH_TOKEN: ghToken } : {}),
243
+ },
244
+ environment: {
245
+ SOURCE_REPO_URL: run.repository.url,
246
+ SOURCE_COMMIT: run.repository.commit,
247
+ AUTHOR_MODEL: run.authoring.model,
248
+ SELFBENCH_DISCOVERY_EXCLUSIONS: "/work/excluded-source-prs.json",
249
+ SELFBENCH_DISCOVERY_OUTPUT: "/work/discovery.json",
250
+ },
251
+ command: ["bash", "-lc", modalAgentScript("discovery.ts", "submit_discovery")],
208
252
  },
209
- command: ["bash", "-lc", modalAgentScript("discovery.ts", "submit_discovery")],
210
- },
211
- options,
212
- ),
253
+ options,
254
+ ),
255
+ ),
213
256
  );
214
257
  planBytes = result.outputs["/work/discovery.json"];
215
258
  logs = await store.put(
@@ -227,6 +270,7 @@ async function discoverCandidateShard(
227
270
  store,
228
271
  run,
229
272
  shard,
273
+ provenance,
230
274
  planBytes,
231
275
  logs,
232
276
  input.targetCounts,
@@ -240,6 +284,7 @@ async function materializeDiscovery(
240
284
  store: ArtifactStore,
241
285
  run: RunRequest,
242
286
  provenance: readonly ProvenanceMessage[],
287
+ allProvenance: readonly ProvenanceMessage[],
243
288
  planBytes: Uint8Array,
244
289
  logs: ArtifactRef,
245
290
  maxCandidates: Readonly<Record<Difficulty, number>>,
@@ -251,16 +296,7 @@ async function materializeDiscovery(
251
296
 
252
297
  const candidates: Candidate[] = [];
253
298
  for (const raw of plan.candidates) {
254
- const message = provenance.find(
255
- (item) =>
256
- item.sourceType === raw.provenance.sourceType &&
257
- item.sessionId === raw.provenance.sessionId &&
258
- item.messageIndex === raw.provenance.messageIndex,
259
- );
260
- if (!message) {
261
- throw new Error(`candidate ${raw.candidateId} references unknown provenance`);
262
- }
263
- assertProvenanceMatchesPullRequest(message, raw.sourcePr, raw.sourceUrl);
299
+ const message = selectCandidateProvenance(provenance, allProvenance, raw);
264
300
  const staged = Buffer.from(
265
301
  `${JSON.stringify({ source: message, messages: [{ role: "user", content: message.content }] })}\n`,
266
302
  );
@@ -287,6 +323,38 @@ async function materializeDiscovery(
287
323
  return { candidates, report };
288
324
  }
289
325
 
326
+ export function selectCandidateProvenance(
327
+ available: readonly ProvenanceMessage[],
328
+ allProvenance: readonly ProvenanceMessage[],
329
+ candidate: {
330
+ readonly candidateId: string;
331
+ readonly sourcePr: number;
332
+ readonly sourceUrl: string;
333
+ readonly provenance: {
334
+ readonly sourceType: ProvenanceMessage["sourceType"];
335
+ readonly sessionId: string;
336
+ readonly messageIndex: number;
337
+ };
338
+ },
339
+ ): ProvenanceMessage {
340
+ const message = available.find(
341
+ (item) =>
342
+ item.sourceType === candidate.provenance.sourceType &&
343
+ item.sessionId === candidate.provenance.sessionId &&
344
+ item.messageIndex === candidate.provenance.messageIndex,
345
+ );
346
+ if (!message) {
347
+ throw new Error(`candidate ${candidate.candidateId} references unknown provenance`);
348
+ }
349
+ assertProvenanceMatchesPullRequest(
350
+ message,
351
+ candidate.sourcePr,
352
+ candidate.sourceUrl,
353
+ allProvenance,
354
+ );
355
+ return message;
356
+ }
357
+
290
358
  function parseDiscoveryPlan(
291
359
  planBytes: Uint8Array,
292
360
  maxCandidates: Readonly<Record<Difficulty, number>>,
@@ -357,9 +425,9 @@ async function authorCandidate(
357
425
  githubToken(),
358
426
  ]);
359
427
  const prompt = authoringPrompt(run, candidate);
360
- const result = await withActivityHeartbeats(
361
- `running author sandbox for ${candidate.candidateId}`,
362
- (options) =>
428
+ const attemptLogKey = `runs/${run.runId}/authoring/${candidate.candidateId}/attempt-${Context.current().info.attempt}/modal.log`;
429
+ const result = await runSandboxWithFailureLog(store, attemptLogKey, () =>
430
+ withActivityHeartbeats(`running author sandbox for ${candidate.candidateId}`, (options) =>
363
431
  sandbox.run(
364
432
  {
365
433
  runId: run.runId,
@@ -389,9 +457,10 @@ async function authorCandidate(
389
457
  },
390
458
  options,
391
459
  ),
460
+ ),
392
461
  );
393
462
  const log = await store.put(
394
- `runs/${run.runId}/authoring/${candidate.candidateId}/attempt-${Context.current().info.attempt}/modal.log`,
463
+ attemptLogKey,
395
464
  Buffer.from(`${result.stdout}\n${result.stderr}`),
396
465
  "text/plain",
397
466
  );
@@ -928,13 +997,7 @@ async function buildExport(store: ArtifactStore, input: ExportInput): Promise<Ar
928
997
  }
929
998
 
930
999
  function discoveryShardPrompt(input: DiscoveryShardInput, provenanceCount: number): string {
931
- const exclusions =
932
- input.excludedSourcePrs.length > 0
933
- ? `Do not return any of these already-considered pull requests: ${input.excludedSourcePrs.join(", ")}.`
934
- : "No pull requests have been considered yet.";
935
- return `Discover and rank SelfBench candidates from this assigned provenance shard. Return at most easy=${input.targetCounts.easy}, medium=${input.targetCounts.medium}, hard=${input.targetCounts.hard}.
936
-
937
- ${exclusions}
1000
+ return `Discover and rank SelfBench candidates from this assigned provenance shard. Return at most easy=${input.targetCounts.easy}, medium=${input.targetCounts.medium}, hard=${input.targetCounts.hard}. Before selecting a pull request, query its number against /work/excluded-source-prs.json with jq; do not print or read the full exclusion list into context. The submit_discovery tool also removes any already-considered pull requests as a final safeguard.
938
1001
 
939
1002
  Assign each candidate exactly one difficulty using the separable implementation core, excluding tests, generated code, formatting churn, and unrelated cleanup:
940
1003
  - easy: at least 20 changed implementation lines across at least 1 implementation file, with at least 1 viable fail-to-pass test;
@@ -944,7 +1007,7 @@ Choose the highest tier whose thresholds the candidate honestly meets. Every tie
944
1007
 
945
1008
  Every candidate must be a pull request from SOURCE_REPO_URL. Repository names or pull requests mentioned inside provenance messages are context only; never follow them into another repository. sourceUrl must be the canonical GitHub pull-request URL for SOURCE_REPO_URL and sourcePr must match its number.
946
1009
 
947
- The sanitized corpus at /work/provenance.jsonl contains ${provenanceCount} human requests. Local Pi, Claude Code, and Codex requests are preferred when they clearly correspond to the same change. Records with sourceType github-pull-request contain the non-bot PR author's exact title and optional body and are valid fallback provenance. A github-pull-request record may be used only for its own sourcePr and sourceUrl. Select provenance only by an exact sourceType, sessionId, and messageIndex present in the corpus. Never invent or reconstruct request text from implementation or tests. Inspect merged PR metadata and diffs with gh and git. Resolve the exact base and completed 40-character commits. Do not modify the repository.
1010
+ The sanitized corpus at /work/provenance.jsonl contains ${provenanceCount} human requests. Local Pi, Claude Code, and Codex requests are preferred when they clearly correspond to the same change. A local record with sourcePr and sourceUrl has an explicit user-supplied association and may be used only for that PR. Records with sourceType github-pull-request contain the non-bot PR author's exact title and optional body and are valid fallback provenance; they too may be used only for their own sourcePr and sourceUrl. Select provenance only by an exact sourceType, sessionId, and messageIndex present in the corpus. Never invent or reconstruct request text from implementation or tests. Inspect merged PR metadata and diffs with gh and git. Resolve the exact base and completed 40-character commits. Do not modify the repository.
948
1011
 
949
1012
  Return fewer candidates when the shard does not contain enough valid requests; an empty candidate list is valid. Call submit_discovery exactly once. Do not return prose after the tool call.`;
950
1013
  }
@@ -985,9 +1048,9 @@ function modalAgentScript(extension: string, tool: string): string {
985
1048
  return `${sandboxBootstrap()}
986
1049
  clone_source
987
1050
  cd /work/repo
988
- pi --print --mode json --no-session --no-approve --no-skills --no-prompt-templates --no-context-files --no-extensions \\
1051
+ run_with_heartbeat pi --print --mode json --no-session --no-approve --no-skills --no-prompt-templates --no-context-files --no-extensions \\
989
1052
  --extension /work/${extension} --provider "$(model_provider)" --model "$AUTHOR_MODEL" --thinking high \\
990
- --tools read,bash,grep,find,ls,${tool} "$(cat /work/prompt.txt)" 2>&1`;
1053
+ --tools read,bash,grep,find,ls,${tool} "$(cat /work/prompt.txt)"`;
991
1054
  }
992
1055
 
993
1056
  function authoringScript(): string {
@@ -995,10 +1058,10 @@ function authoringScript(): string {
995
1058
  clone_source
996
1059
  mkdir -p /work/tasks
997
1060
  cd /work/repo
998
- pi --print --mode json --no-session --no-approve --no-prompt-templates --no-context-files --no-extensions \\
1061
+ run_with_heartbeat pi --print --mode json --no-session --no-approve --no-prompt-templates --no-context-files --no-extensions \\
999
1062
  --skill /work/selfbench-skill --extension /work/authoring.ts \\
1000
1063
  --provider "$(model_provider)" --model "$AUTHOR_MODEL" --thinking high \\
1001
- --tools read,bash,grep,find,ls,submit_task "$(cat /work/prompt.txt)" 2>&1
1064
+ --tools read,bash,grep,find,ls,submit_task "$(cat /work/prompt.txt)"
1002
1065
  node /work/sandbox-author.js /work/tasks /work/repo /work/harbor-task
1003
1066
  cp /work/tasks/*/definition.json /work/definition.json
1004
1067
  tar -czf /work/task.tar.gz -C /work harbor-task`;
@@ -1014,6 +1077,25 @@ fi
1014
1077
  printf '%s\n' '{"transport":"auto"}' > "$HOME/.pi/agent/settings.json"
1015
1078
  chmod 600 "$HOME/.pi/agent/settings.json"
1016
1079
  model_provider() { [ -n "\${OPENAI_API_KEY:-}" ] && printf openai || printf openai-codex; }
1080
+ run_with_heartbeat() {
1081
+ "$@" 2>&1 &
1082
+ local command_pid=$!
1083
+ (
1084
+ while sleep 60; do
1085
+ printf '[selfbench] agent process %s still running at %s\n' "$command_pid" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >&2
1086
+ done
1087
+ ) &
1088
+ local heartbeat_pid=$!
1089
+ trap 'kill "$command_pid" "$heartbeat_pid" 2>/dev/null || true' TERM INT
1090
+ set +e
1091
+ wait "$command_pid"
1092
+ local command_status=$?
1093
+ set -e
1094
+ kill "$heartbeat_pid" 2>/dev/null || true
1095
+ wait "$heartbeat_pid" 2>/dev/null || true
1096
+ trap - TERM INT
1097
+ return "$command_status"
1098
+ }
1017
1099
  cleanup() { rm -f "$HOME/.pi/agent/auth.json" "$HOME/.pi/agent/settings.json" "$HOME/.git-credentials"; }
1018
1100
  trap cleanup EXIT
1019
1101
  clone_source() {
@@ -1137,6 +1219,26 @@ function boundedTail(value: string, maxBytes = 8_000): string {
1137
1219
  : `[truncated ${buffer.length - maxBytes} bytes]\n${buffer.subarray(-maxBytes).toString("utf8").trimEnd()}`;
1138
1220
  }
1139
1221
 
1222
+ async function runSandboxWithFailureLog(
1223
+ store: ArtifactStore,
1224
+ logKey: string,
1225
+ action: () => Promise<SandboxResult>,
1226
+ ): Promise<SandboxResult> {
1227
+ try {
1228
+ return await action();
1229
+ } catch (error) {
1230
+ if (!(error instanceof SandboxExecutionError)) {
1231
+ throw error;
1232
+ }
1233
+ const log = await store.put(
1234
+ logKey,
1235
+ Buffer.from(`${error.result.stdout}\n${error.result.stderr}`),
1236
+ "text/plain",
1237
+ );
1238
+ throw new Error(`${error.message}; partial log: ${log.uri}`, { cause: error });
1239
+ }
1240
+ }
1241
+
1140
1242
  async function withActivityHeartbeats<T>(
1141
1243
  detail: string,
1142
1244
  action: (options: SandboxRunOptions & { readonly signal: AbortSignal }) => Promise<T>,
@@ -1215,12 +1317,11 @@ function parseProvenance(value: Uint8Array): ProvenanceMessage[] {
1215
1317
  .toString("utf8")
1216
1318
  .split("\n")
1217
1319
  .filter((line) => line.trim())
1218
- .map((line) => JSON.parse(line) as ProvenanceMessage);
1320
+ .map((line) => provenanceMessageSchema.parse(JSON.parse(line)));
1219
1321
  }
1220
1322
 
1221
1323
  function readAsset(relativePath: string): Promise<Buffer> {
1222
- const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
1223
- return readFile(join(root, relativePath));
1324
+ return readFile(join(projectRoot(import.meta.url), relativePath));
1224
1325
  }
1225
1326
 
1226
1327
  function rewards(trial: unknown): Record<string, unknown> {