self-bench 0.3.2 → 0.3.4

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 (203) hide show
  1. package/.dockerignore +0 -1
  2. package/Dockerfile +2 -1
  3. package/Dockerfile.sandbox +1 -1
  4. package/README.md +114 -170
  5. package/bun.lock +60 -12
  6. package/compose.yaml +11 -5
  7. package/dist/agent-smoke-main.js +3 -2
  8. package/dist/agent-smoke-main.js.map +1 -1
  9. package/dist/agent-smoke.d.ts +2 -1
  10. package/dist/agent-smoke.d.ts.map +1 -1
  11. package/dist/agent-smoke.js +6 -1
  12. package/dist/agent-smoke.js.map +1 -1
  13. package/dist/api.d.ts +24 -0
  14. package/dist/api.d.ts.map +1 -1
  15. package/dist/api.js +65 -20
  16. package/dist/api.js.map +1 -1
  17. package/dist/artifacts.d.ts +0 -1
  18. package/dist/artifacts.d.ts.map +1 -1
  19. package/dist/artifacts.js +0 -3
  20. package/dist/artifacts.js.map +1 -1
  21. package/dist/build-metadata.js +1 -1
  22. package/dist/cli.js +75 -11
  23. package/dist/cli.js.map +1 -1
  24. package/dist/codex-review.d.ts +2 -1
  25. package/dist/codex-review.d.ts.map +1 -1
  26. package/dist/codex-review.js +26 -22
  27. package/dist/codex-review.js.map +1 -1
  28. package/dist/config.d.ts +36 -11
  29. package/dist/config.d.ts.map +1 -1
  30. package/dist/config.js +89 -17
  31. package/dist/config.js.map +1 -1
  32. package/dist/contracts.d.ts +23 -3
  33. package/dist/contracts.d.ts.map +1 -1
  34. package/dist/contracts.js +31 -6
  35. package/dist/contracts.js.map +1 -1
  36. package/dist/eval-main.js +5 -4
  37. package/dist/eval-main.js.map +1 -1
  38. package/dist/evaluate.d.ts +2 -1
  39. package/dist/evaluate.d.ts.map +1 -1
  40. package/dist/evaluate.js +14 -8
  41. package/dist/evaluate.js.map +1 -1
  42. package/dist/harbor-environment.d.ts +2 -0
  43. package/dist/harbor-environment.d.ts.map +1 -0
  44. package/dist/harbor-environment.js +15 -0
  45. package/dist/harbor-environment.js.map +1 -0
  46. package/dist/harbor-results.d.ts.map +1 -1
  47. package/dist/harbor-results.js +2 -0
  48. package/dist/harbor-results.js.map +1 -1
  49. package/dist/process.d.ts +2 -1
  50. package/dist/process.d.ts.map +1 -1
  51. package/dist/process.js.map +1 -1
  52. package/dist/provenance.d.ts +0 -1
  53. package/dist/provenance.d.ts.map +1 -1
  54. package/dist/provenance.js.map +1 -1
  55. package/dist/providers.d.ts +8 -0
  56. package/dist/providers.d.ts.map +1 -0
  57. package/dist/providers.js +14 -0
  58. package/dist/providers.js.map +1 -0
  59. package/dist/reaudit-main.js +14 -11
  60. package/dist/reaudit-main.js.map +1 -1
  61. package/dist/repair-main.js +12 -9
  62. package/dist/repair-main.js.map +1 -1
  63. package/dist/review/assets/index-CZ3G759O.js +1568 -0
  64. package/dist/review/assets/index-DgSLzX8l.css +1 -0
  65. package/dist/review/assets/{mojo-Blby8dQK.js → mojo-DJz3ZmWd.js} +1 -1
  66. package/dist/review/assets/typst-BUadGCkm.js +1 -0
  67. package/dist/review/index.html +2 -2
  68. package/dist/sandbox-author.bundle.js +40 -6
  69. package/dist/sandbox-repair.bundle.js +8 -7
  70. package/dist/sandbox-repair.js +7 -7
  71. package/dist/sandbox-repair.js.map +1 -1
  72. package/dist/sandbox-review.bundle.js +29 -25
  73. package/dist/sandbox-review.js +5 -3
  74. package/dist/sandbox-review.js.map +1 -1
  75. package/dist/sandbox-timeout.d.ts +11 -0
  76. package/dist/sandbox-timeout.d.ts.map +1 -0
  77. package/dist/sandbox-timeout.js +41 -0
  78. package/dist/sandbox-timeout.js.map +1 -0
  79. package/dist/sandbox-validation-repair.bundle.js +49 -15
  80. package/dist/sandbox-validation-repair.js +11 -9
  81. package/dist/sandbox-validation-repair.js.map +1 -1
  82. package/dist/sandbox.d.ts +2 -2
  83. package/dist/sandbox.d.ts.map +1 -1
  84. package/dist/sandbox.js +10 -3
  85. package/dist/sandbox.js.map +1 -1
  86. package/dist/subscription-auth.d.ts +12 -1
  87. package/dist/subscription-auth.d.ts.map +1 -1
  88. package/dist/subscription-auth.js +16 -16
  89. package/dist/subscription-auth.js.map +1 -1
  90. package/dist/{activities.d.ts → temporal/activities.d.ts} +3 -4
  91. package/dist/temporal/activities.d.ts.map +1 -0
  92. package/dist/{activities.js → temporal/activities.js} +51 -34
  93. package/dist/temporal/activities.js.map +1 -0
  94. package/dist/{temporal.d.ts → temporal/connection.d.ts} +2 -2
  95. package/dist/temporal/connection.d.ts.map +1 -0
  96. package/dist/{temporal.js → temporal/connection.js} +1 -1
  97. package/dist/temporal/connection.js.map +1 -0
  98. package/dist/{worker-main.d.ts.map → temporal/worker-main.d.ts.map} +1 -1
  99. package/dist/{worker-main.js → temporal/worker-main.js} +6 -10
  100. package/dist/temporal/worker-main.js.map +1 -0
  101. package/dist/{workflow.d.ts → temporal/workflow.d.ts} +1 -1
  102. package/dist/temporal/workflow.d.ts.map +1 -0
  103. package/dist/temporal/workflow.js.map +1 -0
  104. package/dist/terminal-prompts.d.ts +26 -0
  105. package/dist/terminal-prompts.d.ts.map +1 -0
  106. package/dist/terminal-prompts.js +137 -0
  107. package/dist/terminal-prompts.js.map +1 -0
  108. package/dist/terminal-reporter.d.ts +30 -0
  109. package/dist/terminal-reporter.d.ts.map +1 -0
  110. package/dist/terminal-reporter.js +90 -0
  111. package/dist/terminal-reporter.js.map +1 -0
  112. package/dist/validate-main.js +8 -2
  113. package/dist/validate-main.js.map +1 -1
  114. package/dist/vercel-cli.d.ts +64 -0
  115. package/dist/vercel-cli.d.ts.map +1 -0
  116. package/dist/vercel-cli.js +303 -0
  117. package/dist/vercel-cli.js.map +1 -0
  118. package/dist/vercel-command.d.ts +14 -0
  119. package/dist/vercel-command.d.ts.map +1 -0
  120. package/dist/vercel-command.js +93 -0
  121. package/dist/vercel-command.js.map +1 -0
  122. package/dist/vercel-executor.d.ts +14 -0
  123. package/dist/vercel-executor.d.ts.map +1 -0
  124. package/dist/vercel-executor.js +356 -0
  125. package/dist/vercel-executor.js.map +1 -0
  126. package/dist/vercel-fetch.d.ts +6 -0
  127. package/dist/vercel-fetch.d.ts.map +1 -0
  128. package/dist/vercel-fetch.js +39 -0
  129. package/dist/vercel-fetch.js.map +1 -0
  130. package/dist/vercel-profile.d.ts +34 -0
  131. package/dist/vercel-profile.d.ts.map +1 -0
  132. package/dist/vercel-profile.js +244 -0
  133. package/dist/vercel-profile.js.map +1 -0
  134. package/dist/vercel-runtime-image.d.ts +51 -0
  135. package/dist/vercel-runtime-image.d.ts.map +1 -0
  136. package/dist/vercel-runtime-image.js +123 -0
  137. package/dist/vercel-runtime-image.js.map +1 -0
  138. package/dist/vercel-setup-probe.d.ts +49 -0
  139. package/dist/vercel-setup-probe.d.ts.map +1 -0
  140. package/dist/vercel-setup-probe.js +151 -0
  141. package/dist/vercel-setup-probe.js.map +1 -0
  142. package/dist/vercel-setup.d.ts +42 -0
  143. package/dist/vercel-setup.d.ts.map +1 -0
  144. package/dist/vercel-setup.js +280 -0
  145. package/dist/vercel-setup.js.map +1 -0
  146. package/docs/evaluations.md +15 -12
  147. package/docs/operations.md +134 -21
  148. package/package.json +8 -6
  149. package/src/agent-smoke-main.ts +3 -2
  150. package/src/agent-smoke.ts +8 -2
  151. package/src/api.ts +71 -20
  152. package/src/artifacts.ts +0 -4
  153. package/src/cli.ts +81 -11
  154. package/src/codex-review.ts +63 -54
  155. package/src/config.ts +150 -28
  156. package/src/contracts.ts +33 -11
  157. package/src/eval-main.ts +5 -4
  158. package/src/evaluate.ts +18 -11
  159. package/src/harbor-environment.ts +17 -0
  160. package/src/harbor-results.ts +2 -0
  161. package/src/process.ts +3 -1
  162. package/src/provenance.ts +0 -1
  163. package/src/providers.ts +22 -0
  164. package/src/reaudit-main.ts +17 -11
  165. package/src/repair-main.ts +15 -9
  166. package/src/sandbox-repair.ts +7 -11
  167. package/src/sandbox-review.ts +5 -3
  168. package/src/sandbox-timeout.ts +56 -0
  169. package/src/sandbox-validation-repair.ts +13 -10
  170. package/src/sandbox.ts +15 -5
  171. package/src/subscription-auth.ts +30 -17
  172. package/src/{activities.ts → temporal/activities.ts} +53 -42
  173. package/src/{temporal.ts → temporal/connection.ts} +1 -1
  174. package/src/{worker-main.ts → temporal/worker-main.ts} +6 -10
  175. package/src/{workflow.ts → temporal/workflow.ts} +10 -10
  176. package/src/terminal-prompts.ts +197 -0
  177. package/src/terminal-reporter.ts +126 -0
  178. package/src/validate-main.ts +9 -3
  179. package/src/vercel-cli.ts +403 -0
  180. package/src/vercel-command.ts +127 -0
  181. package/src/vercel-executor.ts +448 -0
  182. package/src/vercel-fetch.ts +45 -0
  183. package/src/vercel-profile.ts +319 -0
  184. package/src/vercel-runtime-image.ts +204 -0
  185. package/src/vercel-setup-probe.ts +229 -0
  186. package/src/vercel-setup.ts +419 -0
  187. package/dist/activities.d.ts.map +0 -1
  188. package/dist/activities.js.map +0 -1
  189. package/dist/extensions/review.d.ts +0 -3
  190. package/dist/extensions/review.d.ts.map +0 -1
  191. package/dist/extensions/review.js +0 -44
  192. package/dist/extensions/review.js.map +0 -1
  193. package/dist/review/assets/index-Dq-6DFzI.css +0 -1
  194. package/dist/review/assets/index-DurW_RD7.js +0 -1567
  195. package/dist/review/assets/typst-DHCkPAjA.js +0 -1
  196. package/dist/temporal.d.ts.map +0 -1
  197. package/dist/temporal.js.map +0 -1
  198. package/dist/worker-main.js.map +0 -1
  199. package/dist/workflow.d.ts.map +0 -1
  200. package/dist/workflow.js.map +0 -1
  201. package/src/extensions/review.ts +0 -54
  202. /package/dist/{worker-main.d.ts → temporal/worker-main.d.ts} +0 -0
  203. /package/dist/{workflow.js → temporal/workflow.js} +0 -0
@@ -10,7 +10,7 @@ import {
10
10
  couplingReviewInput,
11
11
  couplingReviewSchema,
12
12
  } from "./codex-review.js";
13
- import { loadConfig } from "./config.js";
13
+ import { defaultStandaloneConcurrency, loadWorkerConfig } from "./config.js";
14
14
  import {
15
15
  buildCouplingEvidence,
16
16
  discoverContractArtifacts,
@@ -20,14 +20,14 @@ import {
20
20
  import { parallelMap } from "./parallel.js";
21
21
  import { runCommand } from "./process.js";
22
22
  import { createSandboxExecutor, type SandboxExecutor } from "./sandbox.js";
23
- import { loadPiSubscriptionAuth } from "./subscription-auth.js";
23
+ import { loadPiModelAuth } from "./subscription-auth.js";
24
24
 
25
25
  const parsed = parseArgs({
26
26
  options: {
27
27
  tasks: { type: "string" },
28
28
  task: { type: "string" },
29
29
  output: { type: "string" },
30
- concurrency: { type: "string", default: "10" },
30
+ concurrency: { type: "string" },
31
31
  help: { type: "boolean", short: "h" },
32
32
  },
33
33
  strict: true,
@@ -39,7 +39,7 @@ Usage:
39
39
  self-bench-reaudit --tasks DIRECTORY --output REPORT.json [options]
40
40
 
41
41
  Options:
42
- --concurrency N Concurrent Sol reviews (default: 10)
42
+ --concurrency N Concurrent Sol reviews (default: 10; Vercel: worker setting, default 4)
43
43
  --task ID Review only one task ID
44
44
  -h, --help Show this help`);
45
45
  process.exit(0);
@@ -47,12 +47,15 @@ Options:
47
47
 
48
48
  const tasksDirectory = resolve(parsed.values.tasks ?? fail("--tasks is required"));
49
49
  const outputPath = resolve(parsed.values.output ?? fail("--output is required"));
50
- const concurrency = positiveInteger(parsed.values.concurrency, "--concurrency");
51
- const config = loadConfig();
50
+ const config = loadWorkerConfig();
51
+ const concurrency = positiveInteger(
52
+ parsed.values.concurrency ?? String(defaultStandaloneConcurrency(config, 10)),
53
+ "--concurrency",
54
+ );
52
55
  const sandbox = createSandboxExecutor(config.execution);
53
- const [taskIds, piAuth, reviewer] = await Promise.all([
56
+ const [taskIds, modelAuth, reviewer] = await Promise.all([
54
57
  readdir(tasksDirectory),
55
- loadPiSubscriptionAuth(),
58
+ loadPiModelAuth(),
56
59
  readFile(join(assetRoot(), "dist/sandbox-review.bundle.js")),
57
60
  ]);
58
61
  const directories = (
@@ -79,7 +82,7 @@ const tasks = await parallelMap(directories, concurrency, async (taskDirectory)
79
82
  const taskId = basename(taskDirectory);
80
83
  console.error(`reviewing ${taskId}`);
81
84
  try {
82
- const report = await auditTask(taskDirectory, taskId, piAuth, reviewer, sandbox);
85
+ const report = await auditTask(taskDirectory, taskId, modelAuth, reviewer, sandbox);
83
86
  console.error(`${taskId}: ${report.resolution.verdict}`);
84
87
  return report;
85
88
  } catch (error) {
@@ -113,7 +116,7 @@ console.log(JSON.stringify({ output: outputPath, ...report.summary }, null, 2));
113
116
  async function auditTask(
114
117
  taskDirectory: string,
115
118
  taskId: string,
116
- piAuth: string,
119
+ modelAuth: { apiKey?: string; authJson?: string },
117
120
  reviewer: Uint8Array,
118
121
  sandbox: SandboxExecutor,
119
122
  ) {
@@ -151,7 +154,10 @@ async function auditTask(
151
154
  },
152
155
  ],
153
156
  outputPaths: ["/work/review.json"],
154
- secrets: { SELFBENCH_PI_AUTH_JSON: piAuth },
157
+ secrets: {
158
+ ...(modelAuth.apiKey ? { OPENAI_API_KEY: modelAuth.apiKey } : {}),
159
+ ...(modelAuth.authJson ? { SELFBENCH_PI_AUTH_JSON: modelAuth.authJson } : {}),
160
+ },
155
161
  environment: { SELFBENCH_REVIEW_OUTPUT: "/work/review.json" },
156
162
  command: ["node", "/work/sandbox-review.js"],
157
163
  });
@@ -5,11 +5,11 @@ import { tmpdir } from "node:os";
5
5
  import { basename, dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { parseArgs } from "node:util";
8
- import { loadConfig } from "./config.js";
8
+ import { defaultStandaloneConcurrency, loadWorkerConfig } from "./config.js";
9
9
  import { parallelMap } from "./parallel.js";
10
10
  import { runCommand } from "./process.js";
11
11
  import { createSandboxExecutor } from "./sandbox.js";
12
- import { loadCodexSubscriptionAuth } from "./subscription-auth.js";
12
+ import { loadCodexModelAuth } from "./subscription-auth.js";
13
13
 
14
14
  const parsed = parseArgs({
15
15
  options: {
@@ -17,7 +17,7 @@ const parsed = parseArgs({
17
17
  review: { type: "string" },
18
18
  output: { type: "string" },
19
19
  task: { type: "string" },
20
- concurrency: { type: "string", default: "9" },
20
+ concurrency: { type: "string" },
21
21
  model: { type: "string", default: "gpt-5.6-sol" },
22
22
  help: { type: "boolean", short: "h" },
23
23
  },
@@ -30,7 +30,7 @@ Usage:
30
30
  self-bench-repair --tasks DIRECTORY --review REPORT.json --output DIRECTORY [options]
31
31
 
32
32
  Options:
33
- --concurrency N Concurrent repair sandboxes (default: 9)
33
+ --concurrency N Concurrent repair sandboxes (default: 9; Vercel: worker setting, default 4)
34
34
  --model MODEL Codex subscription model (default: gpt-5.6-sol)
35
35
  --task ID Repair only one coupled task
36
36
  -h, --help Show this help`);
@@ -43,7 +43,11 @@ const outputDirectory = resolve(parsed.values.output ?? fail("--output is requir
43
43
  if (outputDirectory === tasksDirectory || outputDirectory.startsWith(`${tasksDirectory}/`)) {
44
44
  throw new Error("--output must be outside --tasks");
45
45
  }
46
- const concurrency = positiveInteger(parsed.values.concurrency, "--concurrency");
46
+ const config = loadWorkerConfig();
47
+ const concurrency = positiveInteger(
48
+ parsed.values.concurrency ?? String(defaultStandaloneConcurrency(config, 9)),
49
+ "--concurrency",
50
+ );
47
51
  const rawReview = JSON.parse(await readFile(reviewPath, "utf8")) as unknown;
48
52
  const review = reviewReport(rawReview);
49
53
  const coupled = new Map(
@@ -77,10 +81,9 @@ for (const source of sourceTasks) {
77
81
  }
78
82
  }
79
83
 
80
- const config = loadConfig();
81
84
  const sandbox = createSandboxExecutor(config.execution);
82
- const [authJson, repairer] = await Promise.all([
83
- loadCodexSubscriptionAuth(),
85
+ const [modelAuth, repairer] = await Promise.all([
86
+ loadCodexModelAuth(),
84
87
  readFile(join(assetRoot(), "dist/sandbox-repair.bundle.js")),
85
88
  ]);
86
89
  const results = await parallelMap([...coupled.values()], concurrency, async (taskReview) => {
@@ -103,7 +106,10 @@ const results = await parallelMap([...coupled.values()], concurrency, async (tas
103
106
  { path: "/work/sandbox-repair.js", contents: repairer },
104
107
  ],
105
108
  outputPaths: ["/work/repaired-task.tar.gz", "/work/repair-report.json"],
106
- secrets: { SELFBENCH_CODEX_AUTH_JSON: authJson },
109
+ secrets: {
110
+ ...(modelAuth.apiKey ? { OPENAI_API_KEY: modelAuth.apiKey } : {}),
111
+ ...(modelAuth.authJson ? { SELFBENCH_CODEX_AUTH_JSON: modelAuth.authJson } : {}),
112
+ },
107
113
  environment: { SELFBENCH_REPAIR_MODEL: parsed.values.model ?? "gpt-5.6-sol" },
108
114
  command: [
109
115
  "node",
@@ -50,13 +50,15 @@ await runCommand("git", [
50
50
  ]);
51
51
  await runCommand("git", ["-C", repositoryDirectory, "add", "-N", "--all"]);
52
52
 
53
+ const codexAuth = process.env.SELFBENCH_CODEX_AUTH_JSON;
54
+ const apiKey = process.env.OPENAI_API_KEY;
55
+ if (!apiKey && !codexAuth) {
56
+ fail("OPENAI_API_KEY or SELFBENCH_CODEX_AUTH_JSON is required");
57
+ }
53
58
  const codexHome = join(homedir(), ".codex");
54
59
  const authPath = join(codexHome, "auth.json");
55
60
  await mkdir(codexHome, { recursive: true });
56
- await writeFile(
57
- authPath,
58
- process.env.SELFBENCH_CODEX_AUTH_JSON ?? fail("SELFBENCH_CODEX_AUTH_JSON is required"),
59
- );
61
+ await writeFile(authPath, codexAuth ?? `${JSON.stringify({ OPENAI_API_KEY: apiKey })}\n`);
60
62
  await chmod(authPath, 0o600);
61
63
  const promptPath = join(tmpdir(), `selfbench-repair-${taskId}.md`);
62
64
  await writeFile(
@@ -86,7 +88,7 @@ const codex = await runCommand(
86
88
  {
87
89
  allowFailure: true,
88
90
  timeoutMs: 90 * 60 * 1000,
89
- env: withoutApiKey(process.env),
91
+ env: process.env,
90
92
  input: await readFile(promptPath, "utf8"),
91
93
  onOutput: (stream, chunk) => {
92
94
  (stream === "stdout" ? process.stdout : process.stderr).write(chunk);
@@ -149,12 +151,6 @@ await writeFile(
149
151
  )}\n`,
150
152
  );
151
153
 
152
- function withoutApiKey(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
153
- const output = { ...environment };
154
- delete output.OPENAI_API_KEY;
155
- return output;
156
- }
157
-
158
154
  function fail(message: string): never {
159
155
  throw new Error(message);
160
156
  }
@@ -1,17 +1,19 @@
1
1
  import { readFile, writeFile } from "node:fs/promises";
2
2
  import { reviewCouplingWithCodex } from "./codex-review.js";
3
3
 
4
+ const apiKey = process.env.OPENAI_API_KEY;
4
5
  const authJson = process.env.SELFBENCH_PI_AUTH_JSON;
5
6
  const outputPath = process.env.SELFBENCH_REVIEW_OUTPUT;
6
- if (!authJson) {
7
- throw new Error("SELFBENCH_PI_AUTH_JSON is required");
7
+ if (!apiKey && !authJson) {
8
+ throw new Error("OPENAI_API_KEY or SELFBENCH_PI_AUTH_JSON is required");
8
9
  }
9
10
  if (!outputPath) {
10
11
  throw new Error("SELFBENCH_REVIEW_OUTPUT is required");
11
12
  }
12
13
 
13
14
  const review = await reviewCouplingWithCodex({
14
- authJson,
15
+ ...(apiKey ? { apiKey } : {}),
16
+ ...(authJson ? { authJson } : {}),
15
17
  prompt: await readFile("/work/review-input.md", "utf8"),
16
18
  });
17
19
  await writeFile(outputPath, `${JSON.stringify(review, null, 2)}\n`, { flag: "wx" });
@@ -0,0 +1,56 @@
1
+ import type {
2
+ SandboxExecutor,
3
+ SandboxRequest,
4
+ SandboxResult,
5
+ SandboxRunOptions,
6
+ } from "./sandbox.js";
7
+
8
+ export const STANDARD_VERCEL_TIMEOUT_CAP_MS = 2 * 60 * 60 * 1_000;
9
+ export const HOBBY_VERCEL_TIMEOUT_CAP_MS = 45 * 60 * 1_000;
10
+
11
+ export function parseSandboxTimeoutCapText(value: string): number | undefined {
12
+ const normalized = value.trim().toLowerCase();
13
+ if (normalized === "") {
14
+ return undefined;
15
+ }
16
+ const match = /^(\d+)(ms|s|m|h)?$/.exec(normalized);
17
+ if (!match?.[1]) {
18
+ return Number.NaN;
19
+ }
20
+ const amount = Number(match[1]);
21
+ const multiplier =
22
+ match[2] === "h"
23
+ ? 60 * 60 * 1_000
24
+ : match[2] === "m"
25
+ ? 60 * 1_000
26
+ : match[2] === "s"
27
+ ? 1_000
28
+ : 1;
29
+ return amount * multiplier;
30
+ }
31
+
32
+ export class TimeoutCappedSandboxExecutor implements SandboxExecutor {
33
+ readonly #delegate: SandboxExecutor;
34
+ readonly #timeoutCapMs: number;
35
+
36
+ constructor(delegate: SandboxExecutor, timeoutCapMs: number) {
37
+ if (!Number.isInteger(timeoutCapMs) || timeoutCapMs < 100) {
38
+ throw new Error("sandbox timeout cap must be an integer of at least 100ms");
39
+ }
40
+ this.#delegate = delegate;
41
+ this.#timeoutCapMs = timeoutCapMs;
42
+ }
43
+
44
+ async run(request: SandboxRequest, options?: SandboxRunOptions): Promise<SandboxResult> {
45
+ return await this.#delegate.run(
46
+ request.timeoutMs > this.#timeoutCapMs
47
+ ? { ...request, timeoutMs: this.#timeoutCapMs }
48
+ : request,
49
+ options,
50
+ );
51
+ }
52
+
53
+ close(): void {
54
+ this.#delegate.close();
55
+ }
56
+ }
@@ -68,10 +68,19 @@ await Promise.all([
68
68
  ]);
69
69
 
70
70
  const piHome = join(homedir(), ".pi/agent");
71
- const piAuth = process.env.SELFBENCH_PI_AUTH_JSON ?? fail("SELFBENCH_PI_AUTH_JSON is required");
71
+ const piAuth = process.env.SELFBENCH_PI_AUTH_JSON;
72
+ if (!process.env.OPENAI_API_KEY && !piAuth) {
73
+ fail("OPENAI_API_KEY or SELFBENCH_PI_AUTH_JSON is required");
74
+ }
72
75
  await mkdir(piHome, { recursive: true });
73
76
  await Promise.all([
74
- writeFile(join(piHome, "auth.json"), piAuth).then(() => chmod(join(piHome, "auth.json"), 0o600)),
77
+ ...(piAuth
78
+ ? [
79
+ writeFile(join(piHome, "auth.json"), piAuth).then(() =>
80
+ chmod(join(piHome, "auth.json"), 0o600),
81
+ ),
82
+ ]
83
+ : []),
75
84
  writeFile(join(piHome, "settings.json"), `${JSON.stringify({ transport: "auto" })}\n`),
76
85
  ]);
77
86
  const promptPath = join(tmpdir(), `selfbench-validation-repair-${originalDefinition.taskId}.md`);
@@ -98,7 +107,7 @@ const pi = await runCommand(
98
107
  "--no-context-files",
99
108
  "--no-extensions",
100
109
  "--provider",
101
- "openai-codex",
110
+ process.env.OPENAI_API_KEY ? "openai" : "openai-codex",
102
111
  "--model",
103
112
  process.env.SELFBENCH_REPAIR_MODEL ?? "gpt-5.6-sol",
104
113
  "--thinking",
@@ -111,7 +120,7 @@ const pi = await runCommand(
111
120
  allowFailure: true,
112
121
  timeoutMs: 90 * 60 * 1000,
113
122
  cwd: repositoryDirectory,
114
- env: withoutApiKey(process.env),
123
+ env: process.env,
115
124
  onOutput: (stream, chunk) => {
116
125
  (stream === "stdout" ? process.stdout : process.stderr).write(chunk);
117
126
  },
@@ -163,12 +172,6 @@ await Promise.all([
163
172
  ),
164
173
  ]);
165
174
 
166
- function withoutApiKey(environment: NodeJS.ProcessEnv): NodeJS.ProcessEnv {
167
- const output = { ...environment };
168
- delete output.OPENAI_API_KEY;
169
- return output;
170
- }
171
-
172
175
  function fail(message: string): never {
173
176
  throw new Error(message);
174
177
  }
package/src/sandbox.ts CHANGED
@@ -1,6 +1,8 @@
1
- import type { SelfBenchConfig } from "./config.js";
1
+ import type { SelfBenchWorkerConfig } from "./config.js";
2
2
  import { DockerSandboxExecutor } from "./docker-executor.js";
3
3
  import { ModalSandboxExecutor } from "./modal-executor.js";
4
+ import { TimeoutCappedSandboxExecutor } from "./sandbox-timeout.js";
5
+ import { VercelSandboxExecutor } from "./vercel-executor.js";
4
6
 
5
7
  export interface SandboxFile {
6
8
  readonly path: string;
@@ -44,8 +46,16 @@ export interface SandboxExecutor {
44
46
  close(): void;
45
47
  }
46
48
 
47
- export function createSandboxExecutor(config: SelfBenchConfig["execution"]): SandboxExecutor {
48
- return config.kind === "modal"
49
- ? new ModalSandboxExecutor(config)
50
- : new DockerSandboxExecutor(config);
49
+ export function createSandboxExecutor(config: SelfBenchWorkerConfig["execution"]): SandboxExecutor {
50
+ switch (config.kind) {
51
+ case "docker":
52
+ return new DockerSandboxExecutor(config);
53
+ case "modal":
54
+ return new ModalSandboxExecutor(config);
55
+ case "vercel":
56
+ return new TimeoutCappedSandboxExecutor(
57
+ new VercelSandboxExecutor(config),
58
+ config.timeoutCapMs,
59
+ );
60
+ }
51
61
  }
@@ -3,6 +3,34 @@ import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { runCommand } from "./process.js";
5
5
 
6
+ export interface PiModelAuth {
7
+ readonly provider: "openai" | "openai-codex";
8
+ readonly apiKey?: string;
9
+ readonly authJson?: string;
10
+ }
11
+
12
+ export interface CodexModelAuth {
13
+ readonly apiKey?: string;
14
+ readonly authJson?: string;
15
+ }
16
+
17
+ export function openAiApiKey(): string | undefined {
18
+ const key = process.env.OPENAI_API_KEY?.trim();
19
+ return key || undefined;
20
+ }
21
+
22
+ export async function loadPiModelAuth(): Promise<PiModelAuth> {
23
+ const apiKey = openAiApiKey();
24
+ return apiKey
25
+ ? { provider: "openai", apiKey }
26
+ : { provider: "openai-codex", authJson: await loadPiSubscriptionAuth() };
27
+ }
28
+
29
+ export async function loadCodexModelAuth(): Promise<CodexModelAuth> {
30
+ const apiKey = openAiApiKey();
31
+ return apiKey ? { apiKey } : { authJson: await loadCodexSubscriptionAuth() };
32
+ }
33
+
6
34
  export async function loadPiSubscriptionAuth(): Promise<string> {
7
35
  const raw =
8
36
  process.env.SELFBENCH_PI_AUTH_JSON ??
@@ -28,7 +56,8 @@ export function assertCodexSubscriptionAuth(value: unknown, source = "Codex auth
28
56
 
29
57
  export async function loadCodexSubscriptionAuth(): Promise<string> {
30
58
  const path = process.env.CODEX_AUTH_JSON_PATH ?? join(homedir(), ".codex/auth.json");
31
- const parsed = JSON.parse(await readFile(path, "utf8")) as unknown;
59
+ const raw = process.env.SELFBENCH_CODEX_AUTH_JSON ?? (await readFile(path, "utf8"));
60
+ const parsed = JSON.parse(raw) as unknown;
32
61
  assertCodexSubscriptionAuth(parsed, path);
33
62
  const auth = parsed as Record<string, unknown>;
34
63
  return JSON.stringify({
@@ -38,22 +67,6 @@ export async function loadCodexSubscriptionAuth(): Promise<string> {
38
67
  });
39
68
  }
40
69
 
41
- export async function subscriptionBearerToken(model: string): Promise<string> {
42
- const result = await runCommand("pi", [
43
- "auth",
44
- "print-bearer-token",
45
- "--provider",
46
- "openai-codex",
47
- "--model",
48
- model,
49
- ]);
50
- const token = result.stdout.trim();
51
- if (!token) {
52
- throw new Error(`Pi returned no openai-codex subscription bearer token for ${model}`);
53
- }
54
- return token;
55
- }
56
-
57
70
  export async function githubToken(): Promise<string | undefined> {
58
71
  if (process.env.GH_TOKEN) {
59
72
  return process.env.GH_TOKEN;
@@ -5,20 +5,19 @@ import { fileURLToPath } from "node:url";
5
5
  import { CancelledFailure, Context } from "@temporalio/activity";
6
6
  import { ApplicationFailure } from "@temporalio/common";
7
7
  import { z } from "zod";
8
- import { type ArtifactStore, createArtifactStore } from "./artifacts.js";
9
- import { auditTaskDefinition } from "./audit.js";
8
+ import { type ArtifactStore, createArtifactStore } from "../artifacts.js";
9
+ import { auditTaskDefinition } from "../audit.js";
10
10
  import {
11
11
  COUPLING_REVIEW_MODEL,
12
12
  couplingReviewInput,
13
13
  couplingReviewSchema,
14
- } from "./codex-review.js";
15
- import type { SelfBenchConfig } from "./config.js";
14
+ } from "../codex-review.js";
15
+ import type { SelfBenchConfig, SelfBenchWorkerConfig } from "../config.js";
16
16
  import {
17
17
  type ArtifactRef,
18
18
  type AuditResult,
19
19
  type AuthoredTask,
20
20
  type AuthorOutcome,
21
- artifactRefSchema,
22
21
  type Candidate,
23
22
  candidateSchema,
24
23
  type Difficulty,
@@ -27,29 +26,26 @@ import {
27
26
  type RunRequest,
28
27
  taskDefinitionSchema,
29
28
  type ValidationResult,
30
- } from "./contracts.js";
29
+ } from "../contracts.js";
31
30
  import {
32
31
  buildCouplingEvidence,
33
32
  discoverContractArtifacts,
34
33
  resolveCouplingReview,
35
34
  scanBaseContractArtifacts,
36
- } from "./coupling.js";
37
- import { assertPullRequestBelongsToRepository } from "./github.js";
35
+ } from "../coupling.js";
36
+ import { assertPullRequestBelongsToRepository } from "../github.js";
37
+ import { harborChildEnvironment } from "../harbor-environment.js";
38
38
  import {
39
39
  type HarborJobResult,
40
40
  harborInfrastructureError,
41
41
  readHarborJobResult,
42
- } from "./harbor-results.js";
43
- import { refreshHarborTask } from "./harbor-task.js";
44
- import { sha256 } from "./hash.js";
45
- import { runCommand } from "./process.js";
46
- import { assertProvenanceMatchesPullRequest, type ProvenanceMessage } from "./provenance.js";
47
- import { createSandboxExecutor, type SandboxExecutor, type SandboxRunOptions } from "./sandbox.js";
48
- import {
49
- githubToken,
50
- loadCodexSubscriptionAuth,
51
- loadPiSubscriptionAuth,
52
- } from "./subscription-auth.js";
42
+ } from "../harbor-results.js";
43
+ import { refreshHarborTask } from "../harbor-task.js";
44
+ import { sha256 } from "../hash.js";
45
+ import { runCommand } from "../process.js";
46
+ import { assertProvenanceMatchesPullRequest, type ProvenanceMessage } from "../provenance.js";
47
+ import { createSandboxExecutor, type SandboxExecutor, type SandboxRunOptions } from "../sandbox.js";
48
+ import { githubToken, loadCodexModelAuth, loadPiModelAuth } from "../subscription-auth.js";
53
49
 
54
50
  const HARBOR_INFRASTRUCTURE_FAILURE_TYPE = "HarborInfrastructureFailure";
55
51
  const DISCOVERY_TIMEOUT_MS = 45 * 60 * 1000;
@@ -119,7 +115,7 @@ export interface SelfBenchActivities {
119
115
  buildExport(input: ExportInput): Promise<ArtifactRef>;
120
116
  }
121
117
 
122
- export function createActivities(config: SelfBenchConfig): SelfBenchActivities {
118
+ export function createActivities(config: SelfBenchWorkerConfig): SelfBenchActivities {
123
119
  const store = createArtifactStore(config.artifact);
124
120
  const sandbox = createSandboxExecutor(config.execution);
125
121
  return {
@@ -178,7 +174,7 @@ async function discoverCandidateShard(
178
174
  } else {
179
175
  const [extension, piAuth, ghToken] = await Promise.all([
180
176
  readAsset("src/extensions/discovery.ts"),
181
- loadPiSubscriptionAuth(),
177
+ loadPiModelAuth(),
182
178
  githubToken(),
183
179
  ]);
184
180
  Context.current().heartbeat(
@@ -200,7 +196,8 @@ async function discoverCandidateShard(
200
196
  ],
201
197
  outputPaths: ["/work/discovery.json"],
202
198
  secrets: {
203
- SELFBENCH_PI_AUTH_JSON: piAuth,
199
+ ...(piAuth.apiKey ? { OPENAI_API_KEY: piAuth.apiKey } : {}),
200
+ ...(piAuth.authJson ? { SELFBENCH_PI_AUTH_JSON: piAuth.authJson } : {}),
204
201
  ...(ghToken ? { GH_TOKEN: ghToken } : {}),
205
202
  },
206
203
  environment: {
@@ -356,7 +353,7 @@ async function authorCandidate(
356
353
  readAsset("src/extensions/authoring.ts"),
357
354
  readAsset("src/skills/selfbench/SKILL.md"),
358
355
  readAsset("dist/sandbox-author.bundle.js"),
359
- loadPiSubscriptionAuth(),
356
+ loadPiModelAuth(),
360
357
  githubToken(),
361
358
  ]);
362
359
  const prompt = authoringPrompt(run, candidate);
@@ -378,7 +375,8 @@ async function authorCandidate(
378
375
  ],
379
376
  outputPaths: ["/work/task.tar.gz", "/work/definition.json"],
380
377
  secrets: {
381
- SELFBENCH_PI_AUTH_JSON: piAuth,
378
+ ...(piAuth.apiKey ? { OPENAI_API_KEY: piAuth.apiKey } : {}),
379
+ ...(piAuth.authJson ? { SELFBENCH_PI_AUTH_JSON: piAuth.authJson } : {}),
382
380
  ...(ghToken ? { GH_TOKEN: ghToken } : {}),
383
381
  },
384
382
  environment: {
@@ -591,9 +589,8 @@ async function repairValidationTask(
591
589
  store.get(input.task.bundle),
592
590
  store.get(input.task.definition),
593
591
  readAsset("dist/sandbox-validation-repair.bundle.js"),
594
- loadPiSubscriptionAuth(),
595
- ]);
596
- // Full verifier logs remain durable artifacts. The validation reason already carries bounded
592
+ loadPiModelAuth(),
593
+ ]); // Full verifier logs remain durable artifacts. The validation reason already carries bounded
597
594
  // tails for failed gates, which keeps the repair prompt within the model context window.
598
595
  const diagnostics = Buffer.from(input.validation.reason ?? "validation failed without a reason");
599
596
  const result = await withActivityHeartbeats(
@@ -616,7 +613,10 @@ async function repairValidationTask(
616
613
  "/work/repaired-test.patch",
617
614
  "/work/repair-report.json",
618
615
  ],
619
- secrets: { SELFBENCH_PI_AUTH_JSON: authJson },
616
+ secrets: {
617
+ ...(authJson.apiKey ? { OPENAI_API_KEY: authJson.apiKey } : {}),
618
+ ...(authJson.authJson ? { SELFBENCH_PI_AUTH_JSON: authJson.authJson } : {}),
619
+ },
620
620
  environment: { SELFBENCH_REPAIR_MODEL: input.run.authoring.model },
621
621
  command: [
622
622
  "node",
@@ -704,7 +704,7 @@ async function reviewTask(
704
704
  });
705
705
  const [reviewer, authJson] = await Promise.all([
706
706
  readAsset("dist/sandbox-review.bundle.js"),
707
- loadPiSubscriptionAuth(),
707
+ loadPiModelAuth(),
708
708
  ]);
709
709
  const result = await withActivityHeartbeats(
710
710
  `running sandboxed coupling review for ${input.task.taskId}`,
@@ -728,7 +728,10 @@ async function reviewTask(
728
728
  },
729
729
  ],
730
730
  outputPaths: ["/work/review.json"],
731
- secrets: { SELFBENCH_PI_AUTH_JSON: authJson },
731
+ secrets: {
732
+ ...(authJson.apiKey ? { OPENAI_API_KEY: authJson.apiKey } : {}),
733
+ ...(authJson.authJson ? { SELFBENCH_PI_AUTH_JSON: authJson.authJson } : {}),
734
+ },
732
735
  environment: { SELFBENCH_REVIEW_OUTPUT: "/work/review.json" },
733
736
  command: ["node", "/work/sandbox-review.js"],
734
737
  },
@@ -796,7 +799,7 @@ async function repairTask(
796
799
  store.get(input.task.bundle),
797
800
  store.get(input.review),
798
801
  readAsset("dist/sandbox-repair.bundle.js"),
799
- loadCodexSubscriptionAuth(),
802
+ loadCodexModelAuth(),
800
803
  ]);
801
804
  const result = await withActivityHeartbeats(
802
805
  `running test repair sandbox for ${input.task.taskId}`,
@@ -813,7 +816,10 @@ async function repairTask(
813
816
  { path: "/work/sandbox-repair.js", contents: repairer },
814
817
  ],
815
818
  outputPaths: ["/work/repaired-task.tar.gz", "/work/repair-report.json"],
816
- secrets: { SELFBENCH_CODEX_AUTH_JSON: authJson },
819
+ secrets: {
820
+ ...(authJson.apiKey ? { OPENAI_API_KEY: authJson.apiKey } : {}),
821
+ ...(authJson.authJson ? { SELFBENCH_CODEX_AUTH_JSON: authJson.authJson } : {}),
822
+ },
817
823
  environment: { SELFBENCH_REPAIR_MODEL: input.run.authoring.model },
818
824
  command: [
819
825
  "node",
@@ -980,7 +986,7 @@ function modalAgentScript(extension: string, tool: string): string {
980
986
  clone_source
981
987
  cd /work/repo
982
988
  pi --print --mode json --no-session --no-approve --no-skills --no-prompt-templates --no-context-files --no-extensions \\
983
- --extension /work/${extension} --provider openai-codex --model "$AUTHOR_MODEL" --thinking high \\
989
+ --extension /work/${extension} --provider "$(model_provider)" --model "$AUTHOR_MODEL" --thinking high \\
984
990
  --tools read,bash,grep,find,ls,${tool} "$(cat /work/prompt.txt)" 2>&1`;
985
991
  }
986
992
 
@@ -991,7 +997,7 @@ mkdir -p /work/tasks
991
997
  cd /work/repo
992
998
  pi --print --mode json --no-session --no-approve --no-prompt-templates --no-context-files --no-extensions \\
993
999
  --skill /work/selfbench-skill --extension /work/authoring.ts \\
994
- --provider openai-codex --model "$AUTHOR_MODEL" --thinking high \\
1000
+ --provider "$(model_provider)" --model "$AUTHOR_MODEL" --thinking high \\
995
1001
  --tools read,bash,grep,find,ls,submit_task "$(cat /work/prompt.txt)" 2>&1
996
1002
  node /work/sandbox-author.js /work/tasks /work/repo /work/harbor-task
997
1003
  cp /work/tasks/*/definition.json /work/definition.json
@@ -1001,9 +1007,13 @@ tar -czf /work/task.tar.gz -C /work harbor-task`;
1001
1007
  function sandboxBootstrap(): string {
1002
1008
  return `set -euo pipefail
1003
1009
  mkdir -p "$HOME/.pi/agent"
1004
- printf '%s' "$SELFBENCH_PI_AUTH_JSON" > "$HOME/.pi/agent/auth.json"
1010
+ if [ -n "\${SELFBENCH_PI_AUTH_JSON:-}" ]; then
1011
+ printf '%s' "$SELFBENCH_PI_AUTH_JSON" > "$HOME/.pi/agent/auth.json"
1012
+ chmod 600 "$HOME/.pi/agent/auth.json"
1013
+ fi
1005
1014
  printf '%s\n' '{"transport":"auto"}' > "$HOME/.pi/agent/settings.json"
1006
- chmod 600 "$HOME/.pi/agent/auth.json" "$HOME/.pi/agent/settings.json"
1015
+ chmod 600 "$HOME/.pi/agent/settings.json"
1016
+ model_provider() { [ -n "\${OPENAI_API_KEY:-}" ] && printf openai || printf openai-codex; }
1007
1017
  cleanup() { rm -f "$HOME/.pi/agent/auth.json" "$HOME/.pi/agent/settings.json" "$HOME/.git-credentials"; }
1008
1018
  trap cleanup EXIT
1009
1019
  clone_source() {
@@ -1045,7 +1055,12 @@ async function runHarborGate(
1045
1055
  "--yes",
1046
1056
  "--quiet",
1047
1057
  ],
1048
- { allowFailure: true, timeoutMs: 3 * 60 * 60 * 1000, signal },
1058
+ {
1059
+ allowFailure: true,
1060
+ env: harborChildEnvironment(),
1061
+ timeoutMs: 3 * 60 * 60 * 1000,
1062
+ signal,
1063
+ },
1049
1064
  );
1050
1065
  if (result.exitCode !== 0) {
1051
1066
  throw ApplicationFailure.create({
@@ -1204,7 +1219,7 @@ function parseProvenance(value: Uint8Array): ProvenanceMessage[] {
1204
1219
  }
1205
1220
 
1206
1221
  function readAsset(relativePath: string): Promise<Buffer> {
1207
- const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
1222
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
1208
1223
  return readFile(join(root, relativePath));
1209
1224
  }
1210
1225
 
@@ -1230,7 +1245,3 @@ function numberValue(value: unknown): number {
1230
1245
  function isRecord(value: unknown): value is Record<string, unknown> {
1231
1246
  return typeof value === "object" && value !== null && !Array.isArray(value);
1232
1247
  }
1233
-
1234
- export function parseArtifactReference(value: unknown): ArtifactRef {
1235
- return artifactRefSchema.parse(value);
1236
- }
@@ -1,6 +1,6 @@
1
1
  import { Connection } from "@temporalio/client";
2
2
  import { NativeConnection } from "@temporalio/worker";
3
- import type { SelfBenchConfig } from "./config.js";
3
+ import type { SelfBenchConfig } from "../config.js";
4
4
 
5
5
  export async function connectTemporalClient(
6
6
  config: SelfBenchConfig["temporal"],