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,94 @@
|
|
|
1
|
+
# Task construction and validation
|
|
2
|
+
|
|
3
|
+
SelfBench creates easy, medium, and hard Harbor evaluations from completed pull requests. This document defines the task boundary, validation gates, repair behavior, and export contents.
|
|
4
|
+
|
|
5
|
+
## Terms
|
|
6
|
+
|
|
7
|
+
- A **candidate** is one possible source pull request.
|
|
8
|
+
- The **base snapshot** is the repository state before the change.
|
|
9
|
+
- The **reference patch** or **gold** is the known-good non-test implementation.
|
|
10
|
+
- The **held-out test patch** is grader code hidden from evaluated agents.
|
|
11
|
+
- A **fail-to-pass** test fails on the base snapshot and passes after a correct implementation.
|
|
12
|
+
- A **pass-to-pass** test already passes at the base and protects existing behavior.
|
|
13
|
+
- A Harbor **nop** run applies no solution; an **oracle** run applies the saved reference patch.
|
|
14
|
+
- **Test-to-gold coupling** means a test passes only because it knows the saved implementation's private structure rather than the requested behavior.
|
|
15
|
+
|
|
16
|
+
## Provenance
|
|
17
|
+
|
|
18
|
+
Each task is grounded in one retained human request. SelfBench prefers exact user messages from local Pi, Claude Code, or Codex sessions associated with the repository or its worktrees. For third-party repositories, an exact GitHub pull-request title and optional body from a non-bot author is also valid.
|
|
19
|
+
|
|
20
|
+
GitHub records are stored with a distinct `github-pull-request` source type, canonical URL, and PR number. Discovery cannot pair the request from one PR with another PR. A model-authored benchmark instruction may restate the retained request but may not add behavior inferred only from the implementation or tests.
|
|
21
|
+
|
|
22
|
+
## Difficulty gates
|
|
23
|
+
|
|
24
|
+
Profiles are eligibility rules, not empirical claims about model success:
|
|
25
|
+
|
|
26
|
+
| Difficulty | Reference patch | Fail-to-pass | Pass-to-pass |
|
|
27
|
+
| --- | --- | --- | --- |
|
|
28
|
+
| easy | at least 20 changed lines across 1 implementation path | at least 1 | no minimum |
|
|
29
|
+
| medium | at least 50 changed lines across 2 implementation paths | at least 1 | at least 1 |
|
|
30
|
+
| hard | at least 100 changed lines across 3 implementation paths | at least 1 | at least 2 |
|
|
31
|
+
|
|
32
|
+
Every accepted task also requires a held-out test patch with no file overlap with the reference patch, deterministic repository-native setup and tests, a passing nop/oracle split, and independent anti-coupling review.
|
|
33
|
+
|
|
34
|
+
The size gate is mechanical. Generated or vendored code suitability remains a review judgment. Git LFS, submodules, generated changes, and service-heavy integration suites receive no special path and may be rejected during authoring or validation.
|
|
35
|
+
|
|
36
|
+
## Agent-visible boundary
|
|
37
|
+
|
|
38
|
+
An evaluated coding agent receives:
|
|
39
|
+
|
|
40
|
+
- the base repository snapshot;
|
|
41
|
+
- a standalone instruction preserving the human request;
|
|
42
|
+
- the task's declared environment.
|
|
43
|
+
|
|
44
|
+
It does not receive the held-out test patch or reference solution. Harbor mounts `solution/` only for the explicit oracle agent.
|
|
45
|
+
|
|
46
|
+
Held-out tests must exercise an existing public API, command, persistence boundary, or extension seam. They may not import gold-specific private helpers or prescribe exact internal SQL, query counts, private schemas, object identity, telemetry layout, incidental error wording, or UI composition unless the source request explicitly makes that artifact public.
|
|
47
|
+
|
|
48
|
+
## Validation and repair
|
|
49
|
+
|
|
50
|
+
Static audit runs before sandbox validation. Harbor then proves:
|
|
51
|
+
|
|
52
|
+
1. `nop`: selected new tests fail while selected regressions pass;
|
|
53
|
+
2. `oracle`: the reference patch applies and every selected test passes;
|
|
54
|
+
3. determinism: the fail-to-pass selection passes a second time with the oracle.
|
|
55
|
+
|
|
56
|
+
A fresh model session reviews the prompt and tests without inheriting the authoring conversation. When it finds repairable test-to-gold coupling, Temporal schedules one constrained repair in another sandbox. Repair may modify only paths already changed by the held-out test patch. It cannot change the request, base snapshot, or reference implementation.
|
|
57
|
+
|
|
58
|
+
After repair, the task repeats static audit, nop, oracle, and independent review from the beginning. A second failure rejects the candidate.
|
|
59
|
+
|
|
60
|
+
## Toolchains
|
|
61
|
+
|
|
62
|
+
The task compiler has setup layers for Node.js, Bun, Python, Go, and Rust. The author supplies fixed repository-native setup and test commands; compatibility is validated per candidate instead of promised for every repository.
|
|
63
|
+
|
|
64
|
+
When the reference patch changes a recognized dependency manifest or lockfile, the hidden verifier image repeats setup with the trusted reference state and then resets source files to the base snapshot. This prevents stale base dependencies from invalidating the oracle without exposing the reference patch to the coding-agent environment.
|
|
65
|
+
|
|
66
|
+
A test selector is one repository-native identifier substituted into the task's `{tests}` command template. For example, `bun test {tests}` may receive one new test path and two existing regression paths. Pytest, Go, Rust, and custom runners use their own selectors.
|
|
67
|
+
|
|
68
|
+
## Export
|
|
69
|
+
|
|
70
|
+
The run export is a gzip-compressed tar archive:
|
|
71
|
+
|
|
72
|
+
```text
|
|
73
|
+
manifest.json
|
|
74
|
+
tasks/
|
|
75
|
+
├── task-one.tar.gz
|
|
76
|
+
└── task-two.tar.gz
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`manifest.json` pins the source repository commit, SelfBench build identity, execution backend, task IDs, and archive SHA-256 values. Each task archive is a native Harbor task:
|
|
80
|
+
|
|
81
|
+
```text
|
|
82
|
+
harbor-task/
|
|
83
|
+
├── task.toml
|
|
84
|
+
├── instruction.md
|
|
85
|
+
├── environment/
|
|
86
|
+
├── tests/
|
|
87
|
+
└── solution/
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The export includes the repository snapshot at each selected base commit, held-out tests, and reference solutions. It excludes Git history, local provenance/session records, and author/reviewer transcripts.
|
|
91
|
+
|
|
92
|
+
The manifest digest detects accidental corruption but is not a signature because it sits inside the same archive. Extract only exports from a trusted SelfBench deployment and store them as private benchmark material.
|
|
93
|
+
|
|
94
|
+
The complete model-facing authoring and anti-coupling rubric is in [`src/skills/selfbench/SKILL.md`](../src/skills/selfbench/SKILL.md).
|
package/package.json
CHANGED
|
@@ -1,25 +1,38 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "self-bench",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Turn completed repository changes into durable, private Harbor evaluations",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "https://github.com/mupt-ai/self-bench.git"
|
|
9
9
|
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public",
|
|
12
|
+
"registry": "https://registry.npmjs.org/"
|
|
13
|
+
},
|
|
10
14
|
"type": "module",
|
|
11
15
|
"bin": {
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
16
|
+
"self-bench": "dist/cli.js",
|
|
17
|
+
"self-bench-agent-smoke": "dist/agent-smoke-main.js",
|
|
18
|
+
"self-bench-eval": "dist/eval-main.js",
|
|
19
|
+
"self-bench-reaudit": "dist/reaudit-main.js",
|
|
20
|
+
"self-bench-repair": "dist/repair-main.js",
|
|
21
|
+
"self-bench-validate": "dist/validate-main.js"
|
|
18
22
|
},
|
|
19
23
|
"files": [
|
|
20
24
|
"dist",
|
|
21
|
-
"src
|
|
22
|
-
"
|
|
25
|
+
"src",
|
|
26
|
+
"docs",
|
|
27
|
+
"compose.yaml",
|
|
28
|
+
"Dockerfile",
|
|
29
|
+
"Dockerfile.sandbox",
|
|
30
|
+
"bun.lock",
|
|
31
|
+
"scripts",
|
|
32
|
+
"biome.json",
|
|
33
|
+
"tsconfig.json",
|
|
34
|
+
"tsconfig.build.json",
|
|
35
|
+
".dockerignore"
|
|
23
36
|
],
|
|
24
37
|
"engines": {
|
|
25
38
|
"node": ">=22"
|
|
@@ -29,44 +42,44 @@
|
|
|
29
42
|
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
30
43
|
"cli": "bun run src/cli.ts",
|
|
31
44
|
"build:review": "vite build --config review/vite.config.ts",
|
|
32
|
-
"build:server": "tsc -p tsconfig.build.json && bun build src/sandbox-author.ts --target=node --outfile=dist/sandbox-author.bundle.js && bun build src/sandbox-review.ts --target=node --outfile=dist/sandbox-review.bundle.js && bun build src/sandbox-repair.ts --target=node --outfile=dist/sandbox-repair.bundle.js && bun build src/sandbox-validation-repair.ts --target=node --outfile=dist/sandbox-validation-repair.bundle.js",
|
|
45
|
+
"build:server": "tsc -p tsconfig.build.json && bun build src/sandbox-author.ts --target=node --outfile=dist/sandbox-author.bundle.js && bun build src/sandbox-review.ts --target=node --outfile=dist/sandbox-review.bundle.js && bun build src/sandbox-repair.ts --target=node --outfile=dist/sandbox-repair.bundle.js && bun build src/sandbox-validation-repair.ts --target=node --outfile=dist/sandbox-validation-repair.bundle.js && bun run build:metadata",
|
|
46
|
+
"build:metadata": "bun scripts/write-build-metadata.ts",
|
|
47
|
+
"prepack": "bun run build",
|
|
48
|
+
"prepublishOnly": "bun run validate",
|
|
49
|
+
"verify:package": "bun scripts/verify-package.ts",
|
|
33
50
|
"check": "biome check . && tsc --noEmit -p tsconfig.json && bun run typecheck:review",
|
|
34
51
|
"dev:api": "tsx src/api-main.ts",
|
|
35
52
|
"dev:review": "vite --config review/vite.config.ts",
|
|
36
|
-
"dev:worker": "tsx src/worker-main.ts",
|
|
53
|
+
"dev:worker": "tsx src/temporal/worker-main.ts",
|
|
37
54
|
"format": "biome format --write .",
|
|
38
|
-
"prepack": "bun run build",
|
|
39
55
|
"start:api": "node dist/api-main.js",
|
|
40
|
-
"start:worker": "node dist/worker-main.js",
|
|
56
|
+
"start:worker": "node dist/temporal/worker-main.js",
|
|
41
57
|
"test": "bun test tests review/src",
|
|
42
58
|
"typecheck:review": "tsc --noEmit -p review/tsconfig.json",
|
|
43
|
-
"validate": "bun run check && bun run test && bun run build"
|
|
44
|
-
},
|
|
45
|
-
"publishConfig": {
|
|
46
|
-
"access": "public"
|
|
59
|
+
"validate": "bun run check && bun run test && bun run build && bun run verify:package"
|
|
47
60
|
},
|
|
48
61
|
"dependencies": {
|
|
49
62
|
"@google-cloud/storage": "^7.17.0",
|
|
50
|
-
"@pierre/diffs": "1.2.12",
|
|
51
63
|
"@temporalio/activity": "^1.13.2",
|
|
52
64
|
"@temporalio/client": "^1.13.2",
|
|
53
65
|
"@temporalio/common": "^1.13.2",
|
|
54
66
|
"@temporalio/worker": "^1.13.2",
|
|
55
67
|
"@temporalio/workflow": "^1.13.2",
|
|
56
68
|
"modal": "^0.9.0",
|
|
57
|
-
"react": "19.2.7",
|
|
58
|
-
"react-dom": "19.2.7",
|
|
59
69
|
"zod": "^4.1.12"
|
|
60
70
|
},
|
|
61
71
|
"devDependencies": {
|
|
62
72
|
"@biomejs/biome": "^2.2.6",
|
|
63
73
|
"@earendil-works/pi-coding-agent": "0.84.0",
|
|
74
|
+
"@pierre/diffs": "1.2.12",
|
|
64
75
|
"@sinclair/typebox": "0.34.41",
|
|
65
76
|
"@types/bun": "^1.3.14",
|
|
66
77
|
"@types/node": "24.10.13",
|
|
67
78
|
"@types/react": "19.2.14",
|
|
68
79
|
"@types/react-dom": "19.2.3",
|
|
69
80
|
"@vitejs/plugin-react": "4.7.0",
|
|
81
|
+
"react": "19.2.7",
|
|
82
|
+
"react-dom": "19.2.7",
|
|
70
83
|
"tsx": "^4.20.6",
|
|
71
84
|
"typescript": "5.9.3",
|
|
72
85
|
"vite": "7.3.1"
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { mkdtemp, readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
const root = resolve(import.meta.dir, "..");
|
|
6
|
+
const temporary = await mkdtemp(join(tmpdir(), "self-bench-package-"));
|
|
7
|
+
async function run(command: string, args: string[], cwd = root): Promise<string> {
|
|
8
|
+
const child = Bun.spawn([command, ...args], {
|
|
9
|
+
cwd,
|
|
10
|
+
stdout: "pipe",
|
|
11
|
+
stderr: "pipe",
|
|
12
|
+
});
|
|
13
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
14
|
+
child.exited,
|
|
15
|
+
new Response(child.stdout).text(),
|
|
16
|
+
new Response(child.stderr).text(),
|
|
17
|
+
]);
|
|
18
|
+
if (exitCode !== 0) {
|
|
19
|
+
throw new Error(`${command} ${args.join(" ")} failed:\n${stderr || stdout}`);
|
|
20
|
+
}
|
|
21
|
+
return stdout;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
await run("bun", ["pm", "pack", "--destination", temporary]);
|
|
26
|
+
const tarballs = (await Array.fromAsync(new Bun.Glob("*.tgz").scan({ cwd: temporary }))).map(
|
|
27
|
+
(name) => join(temporary, name),
|
|
28
|
+
);
|
|
29
|
+
const tarball = tarballs[0] ?? (() => {
|
|
30
|
+
throw new Error("bun pm pack did not create a tarball");
|
|
31
|
+
})();
|
|
32
|
+
const packageJson = JSON.parse(await readFile(join(root, "package.json"), "utf8")) as {
|
|
33
|
+
name: string;
|
|
34
|
+
version: string;
|
|
35
|
+
};
|
|
36
|
+
const installRoot = join(temporary, "install");
|
|
37
|
+
await run("mkdir", ["-p", installRoot]);
|
|
38
|
+
await run("bun", ["init", "--yes"], installRoot);
|
|
39
|
+
await run("bun", ["add", "--no-save", tarball], installRoot);
|
|
40
|
+
const executable = join(installRoot, "node_modules", ".bin", "self-bench");
|
|
41
|
+
const help = await run(executable, ["--help"], installRoot);
|
|
42
|
+
if (!help.includes("self-bench up")) {
|
|
43
|
+
throw new Error("installed self-bench did not print the expected CLI help");
|
|
44
|
+
}
|
|
45
|
+
for (const asset of ["compose.yaml", "Dockerfile", "Dockerfile.sandbox", "src/skills/selfbench/SKILL.md"]) {
|
|
46
|
+
await readFile(join(installRoot, "node_modules", packageJson.name, asset));
|
|
47
|
+
}
|
|
48
|
+
const installedPackage = JSON.parse(
|
|
49
|
+
await readFile(join(installRoot, "node_modules", packageJson.name, "package.json"), "utf8"),
|
|
50
|
+
) as { name: string; version: string };
|
|
51
|
+
if (installedPackage.name !== packageJson.name || installedPackage.version !== packageJson.version) {
|
|
52
|
+
throw new Error("installed package metadata does not match the workspace package");
|
|
53
|
+
}
|
|
54
|
+
console.log(`verified ${installedPackage.name}@${installedPackage.version}`);
|
|
55
|
+
} finally {
|
|
56
|
+
await rm(temporary, { recursive: true, force: true });
|
|
57
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
|
|
5
|
+
const execFileAsync = promisify(execFile);
|
|
6
|
+
const root = new URL("../", import.meta.url);
|
|
7
|
+
const output = new URL("../dist/build-metadata.js", import.meta.url);
|
|
8
|
+
const configuredCommit = process.env.SELFBENCH_BUILD_COMMIT;
|
|
9
|
+
|
|
10
|
+
let commit = configuredCommit;
|
|
11
|
+
if (!commit) {
|
|
12
|
+
try {
|
|
13
|
+
const result = await execFileAsync("git", ["rev-parse", "HEAD"], { cwd: root });
|
|
14
|
+
commit = result.stdout.trim();
|
|
15
|
+
} catch {
|
|
16
|
+
commit = "0".repeat(40);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (!/^[0-9a-f]{40}$/i.test(commit)) {
|
|
20
|
+
throw new Error("SELFBENCH_BUILD_COMMIT must be a full commit SHA");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await mkdir(new URL("../dist/", import.meta.url), { recursive: true });
|
|
24
|
+
await writeFile(
|
|
25
|
+
output,
|
|
26
|
+
`// Generated during the build.\nexport const buildCommit = ${JSON.stringify(commit.toLowerCase())};\n`,
|
|
27
|
+
);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
import { smokeAllAdapters } from "./agent-smoke.js";
|
|
5
|
+
|
|
6
|
+
const parsed = parseArgs({
|
|
7
|
+
options: {
|
|
8
|
+
task: { type: "string" },
|
|
9
|
+
jobs: { type: "string" },
|
|
10
|
+
harbor: { type: "string" },
|
|
11
|
+
environment: { type: "string", default: "modal" },
|
|
12
|
+
concurrency: { type: "string", default: "4" },
|
|
13
|
+
help: { type: "boolean", short: "h" },
|
|
14
|
+
},
|
|
15
|
+
strict: true,
|
|
16
|
+
});
|
|
17
|
+
if (parsed.values.help) {
|
|
18
|
+
console.log(`Attempt installation and instantiation of every pinned Harbor adapter.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
self-bench-agent-smoke --task DIRECTORY --jobs DIRECTORY [options]
|
|
22
|
+
|
|
23
|
+
Options:
|
|
24
|
+
--harbor PATH Harbor executable (default: harbor)
|
|
25
|
+
--environment docker|modal Execution environment (default: modal)
|
|
26
|
+
--concurrency N Concurrent adapter checks (default: 4)
|
|
27
|
+
-h, --help Show this help`);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
const environment = parsed.values.environment;
|
|
31
|
+
if (environment !== "docker" && environment !== "modal") {
|
|
32
|
+
throw new Error("--environment must be docker or modal");
|
|
33
|
+
}
|
|
34
|
+
const results = await smokeAllAdapters({
|
|
35
|
+
taskDirectory: parsed.values.task ?? fail("--task is required"),
|
|
36
|
+
jobsDirectory: parsed.values.jobs ?? fail("--jobs is required"),
|
|
37
|
+
environment,
|
|
38
|
+
concurrency: positiveInteger(parsed.values.concurrency, "--concurrency"),
|
|
39
|
+
...(parsed.values.harbor ? { harborPath: parsed.values.harbor } : {}),
|
|
40
|
+
});
|
|
41
|
+
console.log(
|
|
42
|
+
JSON.stringify(
|
|
43
|
+
{
|
|
44
|
+
adapters: results.length,
|
|
45
|
+
installed: results.filter((result) => result.installed).length,
|
|
46
|
+
failed: results.filter((result) => !result.installed).length,
|
|
47
|
+
},
|
|
48
|
+
null,
|
|
49
|
+
2,
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
function fail(message: string): never {
|
|
54
|
+
throw new Error(message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function positiveInteger(value: string | undefined, label: string): number {
|
|
58
|
+
const parsed = Number(value);
|
|
59
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
60
|
+
throw new Error(`${label} must be a positive integer`);
|
|
61
|
+
}
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { parallelMap } from "./parallel.js";
|
|
4
|
+
import { runCommand } from "./process.js";
|
|
5
|
+
|
|
6
|
+
export const HARBOR_AGENT_ADAPTERS = [
|
|
7
|
+
"oracle",
|
|
8
|
+
"nop",
|
|
9
|
+
"acp",
|
|
10
|
+
"terminus-2",
|
|
11
|
+
"claude-code",
|
|
12
|
+
"copilot-cli",
|
|
13
|
+
"aider",
|
|
14
|
+
"cline-cli",
|
|
15
|
+
"codex",
|
|
16
|
+
"cortex-code",
|
|
17
|
+
"cursor-cli",
|
|
18
|
+
"gemini-cli",
|
|
19
|
+
"antigravity-cli",
|
|
20
|
+
"antigravity-sdk",
|
|
21
|
+
"rovodev-cli",
|
|
22
|
+
"goose",
|
|
23
|
+
"grok-build",
|
|
24
|
+
"hermes",
|
|
25
|
+
"kimi-code",
|
|
26
|
+
"kimi-cli",
|
|
27
|
+
"langgraph",
|
|
28
|
+
"deerflow",
|
|
29
|
+
"mini-swe-agent",
|
|
30
|
+
"nemo-agent",
|
|
31
|
+
"swe-agent",
|
|
32
|
+
"opencode",
|
|
33
|
+
"mimo",
|
|
34
|
+
"openclaw",
|
|
35
|
+
"openhands",
|
|
36
|
+
"openhands-sdk",
|
|
37
|
+
"pi",
|
|
38
|
+
"qwen-coder",
|
|
39
|
+
"devin",
|
|
40
|
+
"trae-agent",
|
|
41
|
+
"computer-1",
|
|
42
|
+
"eve",
|
|
43
|
+
"dspy-rlm",
|
|
44
|
+
"vibe",
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
export interface AdapterSmokeOptions {
|
|
48
|
+
readonly taskDirectory: string;
|
|
49
|
+
readonly jobsDirectory: string;
|
|
50
|
+
readonly harborPath?: string;
|
|
51
|
+
readonly environment?: "docker" | "modal";
|
|
52
|
+
readonly concurrency?: number;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface AdapterSmokeResult {
|
|
56
|
+
readonly agent: (typeof HARBOR_AGENT_ADAPTERS)[number];
|
|
57
|
+
readonly installed: boolean;
|
|
58
|
+
readonly exitCode: number;
|
|
59
|
+
readonly jobName: string;
|
|
60
|
+
readonly outputTail: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export async function smokeAllAdapters(
|
|
64
|
+
options: AdapterSmokeOptions,
|
|
65
|
+
): Promise<readonly AdapterSmokeResult[]> {
|
|
66
|
+
const jobsDirectory = resolve(options.jobsDirectory);
|
|
67
|
+
const reportsDirectory = join(jobsDirectory, "adapter-smoke");
|
|
68
|
+
await mkdir(reportsDirectory, { recursive: true });
|
|
69
|
+
const results = await parallelMap(
|
|
70
|
+
HARBOR_AGENT_ADAPTERS,
|
|
71
|
+
options.concurrency ?? 4,
|
|
72
|
+
async (agent) => {
|
|
73
|
+
const reportPath = join(reportsDirectory, `${agent}.json`);
|
|
74
|
+
const existing = await readFile(reportPath, "utf8").catch(() => undefined);
|
|
75
|
+
if (existing) {
|
|
76
|
+
return JSON.parse(existing) as AdapterSmokeResult;
|
|
77
|
+
}
|
|
78
|
+
const jobName = `install-${agent}-${crypto.randomUUID().slice(0, 8)}`;
|
|
79
|
+
const result = await runCommand(
|
|
80
|
+
options.harborPath ?? "harbor",
|
|
81
|
+
[
|
|
82
|
+
"run",
|
|
83
|
+
"--path",
|
|
84
|
+
resolve(options.taskDirectory),
|
|
85
|
+
"--agent",
|
|
86
|
+
agent,
|
|
87
|
+
"--env",
|
|
88
|
+
options.environment ?? "modal",
|
|
89
|
+
"--job-name",
|
|
90
|
+
jobName,
|
|
91
|
+
"--jobs-dir",
|
|
92
|
+
jobsDirectory,
|
|
93
|
+
"--install-only",
|
|
94
|
+
"--n-concurrent",
|
|
95
|
+
"1",
|
|
96
|
+
"--max-retries",
|
|
97
|
+
"0",
|
|
98
|
+
"--delete",
|
|
99
|
+
"--yes",
|
|
100
|
+
"--quiet",
|
|
101
|
+
],
|
|
102
|
+
{ allowFailure: true, timeoutMs: 60 * 60 * 1000 },
|
|
103
|
+
);
|
|
104
|
+
const summary: AdapterSmokeResult = {
|
|
105
|
+
agent,
|
|
106
|
+
installed: result.exitCode === 0,
|
|
107
|
+
exitCode: result.exitCode,
|
|
108
|
+
jobName,
|
|
109
|
+
outputTail: `${result.stdout}\n${result.stderr}`.trim().slice(-2_000),
|
|
110
|
+
};
|
|
111
|
+
await writeFile(reportPath, `${JSON.stringify(summary, null, 2)}\n`, { flag: "wx" });
|
|
112
|
+
return summary;
|
|
113
|
+
},
|
|
114
|
+
);
|
|
115
|
+
await writeFile(
|
|
116
|
+
join(reportsDirectory, "summary.json"),
|
|
117
|
+
`${JSON.stringify(
|
|
118
|
+
{
|
|
119
|
+
schemaVersion: 1,
|
|
120
|
+
mode: "install-only",
|
|
121
|
+
environment: options.environment ?? "modal",
|
|
122
|
+
adapterCount: results.length,
|
|
123
|
+
installed: results.filter((result) => result.installed).length,
|
|
124
|
+
failed: results.filter((result) => !result.installed).length,
|
|
125
|
+
results,
|
|
126
|
+
},
|
|
127
|
+
null,
|
|
128
|
+
2,
|
|
129
|
+
)}\n`,
|
|
130
|
+
);
|
|
131
|
+
return results;
|
|
132
|
+
}
|
package/src/api-main.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { startApi } from "./api.js";
|
|
2
|
+
import { loadConfig } from "./config.js";
|
|
3
|
+
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
const stop = await startApi(config);
|
|
6
|
+
console.log(`SelfBench API listening on http://${config.apiHost}:${config.apiPort}`);
|
|
7
|
+
|
|
8
|
+
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
|
9
|
+
process.once(signal, () => {
|
|
10
|
+
void stop().finally(() => process.exit(0));
|
|
11
|
+
});
|
|
12
|
+
}
|