@tangle-network/agent-bench 0.3.6 → 0.3.7
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/CHANGELOG.md +7 -0
- package/dist/adapters.js +2 -2
- package/dist/benchmarks/humaneval.d.ts +10 -1
- package/dist/benchmarks/humaneval.js +5 -3
- package/dist/{chunk-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
- package/dist/chunk-5H5XV76F.js.map +1 -0
- package/dist/{chunk-5SBJCB6W.js → chunk-PWQVGAJB.js} +2 -2
- package/dist/index.js +2 -2
- package/package.json +5 -4
- package/scripts/run-package-tests.mjs +30 -8
- package/scripts/verify-pier-agent.mts +1 -0
- package/src/benchmarks/humaneval.test.mts +122 -0
- package/src/benchmarks/humaneval.ts +100 -27
- package/src/david-attribution.mts +78 -0
- package/src/david-goliath.mts +149 -0
- package/src/hev-improve.mts +25 -6
- package/src/humaneval-object-ablation.mts +201 -0
- package/src/live-improve-campaign-mbpp.mts +641 -0
- package/src/live-improve-campaign.mts +500 -0
- package/src/mbpp-structural.mts +12 -7
- package/src/stream-observe.py +45 -0
- package/src/stream-observe.tpl.html +247 -0
- package/src/supervisor-arena.mts +816 -0
- package/src/swe-arena/analyze.ts +211 -0
- package/src/swe-arena/arms.ts +788 -0
- package/src/swe-arena/bootstrap-meta.mts +188 -0
- package/src/swe-arena/bootstrap-meta.test.mts +51 -0
- package/src/swe-arena/calibrate.ts +116 -0
- package/src/swe-arena/capabilities.mts +76 -0
- package/src/swe-arena/capabilities.test.mts +57 -0
- package/src/swe-arena/capacity.ts +194 -0
- package/src/swe-arena/cell-evidence.mts +405 -0
- package/src/swe-arena/cell-evidence.test.mts +248 -0
- package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
- package/src/swe-arena/diagnosis-ensemble.ts +520 -0
- package/src/swe-arena/execution.test.mts +1170 -0
- package/src/swe-arena/fixtures/analyze.py +80 -0
- package/src/swe-arena/fixtures/excludes.txt +8 -0
- package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
- package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
- package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
- package/src/swe-arena/fixtures/holdout.json +44 -0
- package/src/swe-arena/fixtures/instances.json +146 -0
- package/src/swe-arena/fixtures/ledger.jsonl +12 -0
- package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
- package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
- package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
- package/src/swe-arena/fixtures/rematch.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
- package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
- package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
- package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
- package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
- package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
- package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
- package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
- package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
- package/src/swe-arena/fixtures/worker-tokens.json +42 -0
- package/src/swe-arena/fixtures.ts +104 -0
- package/src/swe-arena/holdout-certify.mts +408 -0
- package/src/swe-arena/holdout-certify.test.mts +160 -0
- package/src/swe-arena/judge-child.mts +37 -0
- package/src/swe-arena/manifest.mts +293 -0
- package/src/swe-arena/manifest.test.mts +169 -0
- package/src/swe-arena/materialize.ts +142 -0
- package/src/swe-arena/outer-loop.mts +2145 -0
- package/src/swe-arena/outer-loop.test.mts +696 -0
- package/src/swe-arena/parity.test.mts +87 -0
- package/src/swe-arena/proc.test.mts +174 -0
- package/src/swe-arena/proc.ts +260 -0
- package/src/swe-arena/profiles/default-author.profile.json +4 -0
- package/src/swe-arena/proposer-fanout.mts +489 -0
- package/src/swe-arena/proposer-fanout.test.mts +372 -0
- package/src/swe-arena/reconcile.ts +0 -0
- package/src/swe-arena/replay.mts +183 -0
- package/src/swe-arena/replay.test.mts +300 -0
- package/src/swe-arena/run-experiment.mts +361 -0
- package/src/swe-arena/run-supervisor.mjs +297 -0
- package/src/swe-arena/run-supervisor.test.mts +498 -0
- package/src/swe-arena/serialized-judge.ts +414 -0
- package/src/swe-arena/types.ts +166 -0
- package/src/swe-code-improve.mts +328 -0
- package/src/swe-emit-patch.mts +104 -0
- package/src/swe-improve.mts +232 -0
- package/src/swe-jail.ts +2 -2
- package/src/swe-local-proof.mts +169 -0
- package/src/swe-repro-calibrate.mts +446 -0
- package/src/swe-stream.mts +1497 -0
- package/dist/chunk-PPYSEKFM.js.map +0 -1
- /package/dist/{chunk-5SBJCB6W.js.map → chunk-PWQVGAJB.js.map} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.7
|
|
4
|
+
|
|
5
|
+
- Declare the public executable search path for Pier candidate entrypoints so the exact process contract can replay them against `@tangle-network/agent-runtime@0.102.0`.
|
|
6
|
+
- Align the portable agent contract with `@tangle-network/agent-interface@0.32.0`.
|
|
7
|
+
- Add the trace-driven SWE supervisor improvement loop with measured candidate worktrees and official Docker judging.
|
|
8
|
+
- Settle cancelled worker trees, isolate each cell's state, serialize judges with a kernel lock, and reject unfinished processes before patch extraction or scoring.
|
|
9
|
+
|
|
3
10
|
## 0.3.6
|
|
4
11
|
|
|
5
12
|
- Ship compiled ESM and declarations so Node can import the installed package without a TypeScript runtime.
|
package/dist/adapters.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ADAPTERS,
|
|
3
3
|
resolveAdapter
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PWQVGAJB.js";
|
|
5
5
|
import "./chunk-2PVVP7GN.js";
|
|
6
6
|
import "./chunk-VQRS7VUC.js";
|
|
7
7
|
import "./chunk-Z7ML6L77.js";
|
|
@@ -15,7 +15,7 @@ import "./chunk-R36V2VP7.js";
|
|
|
15
15
|
import "./chunk-JRWWGMK7.js";
|
|
16
16
|
import "./chunk-INNOYXCP.js";
|
|
17
17
|
import "./chunk-PA2ZKHJC.js";
|
|
18
|
-
import "./chunk-
|
|
18
|
+
import "./chunk-5H5XV76F.js";
|
|
19
19
|
import "./chunk-TBKU5XQI.js";
|
|
20
20
|
import "./chunk-TSWPNOYM.js";
|
|
21
21
|
import "./chunk-KDIKRJGB.js";
|
|
@@ -45,9 +45,18 @@ interface CheckResult {
|
|
|
45
45
|
* execution-grounded feedback a self-repair loop steers on; ignored by selection. */
|
|
46
46
|
detail?: string;
|
|
47
47
|
}
|
|
48
|
+
interface PythonProgramResult {
|
|
49
|
+
exitCode: number;
|
|
50
|
+
stdout: string;
|
|
51
|
+
stderr: string;
|
|
52
|
+
}
|
|
53
|
+
/** Run arbitrary Python in the same isolated container used by the HumanEval
|
|
54
|
+
* checker. This is also the execution-tool primitive for experiments that let
|
|
55
|
+
* a model test its own snippets; model-written code must never run on the host. */
|
|
56
|
+
declare function runPythonProgram(program: string, timeoutMs?: number): Promise<PythonProgramResult>;
|
|
48
57
|
declare function runChecker(task: HumanEvalTask, candidate: string): Promise<CheckResult>;
|
|
49
58
|
/** The HumanEval `BenchmarkAdapter`. OFFSET (env) selects the correctable middle
|
|
50
59
|
* band; loadTasks honors `limit`/`ids`. The judge is the Docker deployable checker. */
|
|
51
60
|
declare function createHumanEvalAdapter(): BenchmarkAdapter;
|
|
52
61
|
|
|
53
|
-
export { type CheckResult, type HumanEvalTask, basePrompt, createHumanEvalAdapter, extractCode, loadHumanEval, runChecker };
|
|
62
|
+
export { type CheckResult, type HumanEvalTask, type PythonProgramResult, basePrompt, createHumanEvalAdapter, extractCode, loadHumanEval, runChecker, runPythonProgram };
|
|
@@ -3,13 +3,15 @@ import {
|
|
|
3
3
|
createHumanEvalAdapter,
|
|
4
4
|
extractCode,
|
|
5
5
|
loadHumanEval,
|
|
6
|
-
runChecker
|
|
7
|
-
|
|
6
|
+
runChecker,
|
|
7
|
+
runPythonProgram
|
|
8
|
+
} from "../chunk-5H5XV76F.js";
|
|
8
9
|
export {
|
|
9
10
|
basePrompt,
|
|
10
11
|
createHumanEvalAdapter,
|
|
11
12
|
extractCode,
|
|
12
13
|
loadHumanEval,
|
|
13
|
-
runChecker
|
|
14
|
+
runChecker,
|
|
15
|
+
runPythonProgram
|
|
14
16
|
};
|
|
15
17
|
//# sourceMappingURL=humaneval.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/benchmarks/humaneval.ts
|
|
2
2
|
import { execFile } from "child_process";
|
|
3
|
-
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
4
4
|
import { tmpdir } from "os";
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { gunzipSync } from "zlib";
|
|
@@ -59,10 +59,12 @@ check(${task.entryPoint})
|
|
|
59
59
|
`;
|
|
60
60
|
}
|
|
61
61
|
var dockerRunSeq = 0;
|
|
62
|
-
function
|
|
62
|
+
function runPythonProgram(program, timeoutMs = dockerTimeoutMs) {
|
|
63
63
|
const dir = mkdtempSync(join(tmpdir(), "hev-"));
|
|
64
|
-
|
|
64
|
+
chmodSync(dir, 493);
|
|
65
|
+
writeFileSync(join(dir, "p.py"), program, { mode: 420 });
|
|
65
66
|
const name = `hev-${process.pid}-${dockerRunSeq++}`;
|
|
67
|
+
const startMarker = `__AGENT_RUNTIME_CANDIDATE_STARTED_${name}__`;
|
|
66
68
|
return new Promise((resolvePromise, reject) => {
|
|
67
69
|
let settled = false;
|
|
68
70
|
const cleanup = () => {
|
|
@@ -84,49 +86,98 @@ function runChecker(task, candidate) {
|
|
|
84
86
|
cleanup();
|
|
85
87
|
reject(e);
|
|
86
88
|
};
|
|
87
|
-
const
|
|
89
|
+
const outerTimeoutMs = timeoutMs + 3e3;
|
|
90
|
+
const backstop = setTimeout(
|
|
91
|
+
() => fail(new Error(`docker checker did not return within ${outerTimeoutMs + 3e3}ms`)),
|
|
92
|
+
outerTimeoutMs + 3e3
|
|
93
|
+
);
|
|
94
|
+
const inContainerSeconds = Math.max(1, Math.ceil(timeoutMs / 1e3));
|
|
88
95
|
execFile(
|
|
89
96
|
"docker",
|
|
90
97
|
[
|
|
91
98
|
"run",
|
|
92
99
|
"--rm",
|
|
100
|
+
"--pull=never",
|
|
93
101
|
"--name",
|
|
94
102
|
name,
|
|
95
103
|
"--network=none",
|
|
96
104
|
"--cpus=1",
|
|
97
105
|
"--memory=512m",
|
|
106
|
+
"--pids-limit=64",
|
|
107
|
+
"--cap-drop=ALL",
|
|
108
|
+
"--security-opt=no-new-privileges",
|
|
109
|
+
"--read-only",
|
|
110
|
+
"--tmpfs",
|
|
111
|
+
"/tmp:rw,nosuid,nodev,noexec,size=64m,mode=1777",
|
|
112
|
+
"--user",
|
|
113
|
+
"65534:65534",
|
|
98
114
|
"-v",
|
|
99
115
|
`${dir}:/w:ro`,
|
|
100
116
|
"-w",
|
|
101
117
|
"/w",
|
|
102
118
|
dockerImage,
|
|
103
|
-
"
|
|
104
|
-
"
|
|
119
|
+
"sh",
|
|
120
|
+
"-c",
|
|
121
|
+
'command -v timeout >/dev/null 2>&1 && command -v python >/dev/null 2>&1 && [ -r /w/p.py ] || exit 126; printf "%s\\n" "$1"; exec timeout -s KILL "$2" python /w/p.py',
|
|
122
|
+
"agent-runtime-checker",
|
|
123
|
+
startMarker,
|
|
124
|
+
`${inContainerSeconds}s`
|
|
105
125
|
],
|
|
106
|
-
{ timeout:
|
|
107
|
-
(err,
|
|
126
|
+
{ timeout: outerTimeoutMs, killSignal: "SIGKILL", maxBuffer: 4 * 1024 * 1024 },
|
|
127
|
+
(err, stdout, stderr) => {
|
|
128
|
+
const markerLine = `${startMarker}
|
|
129
|
+
`;
|
|
130
|
+
const candidateStarted = stdout?.startsWith(markerLine) === true;
|
|
131
|
+
const candidateStdout = candidateStarted ? stdout.slice(markerLine.length) : stdout ?? "";
|
|
108
132
|
if (err) {
|
|
109
133
|
const e = err;
|
|
110
134
|
if (e.code === "ENOENT") {
|
|
111
135
|
fail(new Error("docker binary not found on PATH \u2014 cannot run the deployable checker"));
|
|
112
136
|
return;
|
|
113
137
|
}
|
|
114
|
-
if (
|
|
115
|
-
fail(new Error(`docker
|
|
138
|
+
if (e.killed) {
|
|
139
|
+
fail(new Error(`docker checker exceeded its ${outerTimeoutMs}ms outer timeout`));
|
|
116
140
|
return;
|
|
117
141
|
}
|
|
118
|
-
if (
|
|
119
|
-
|
|
142
|
+
if (!candidateStarted) {
|
|
143
|
+
if (/cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(
|
|
144
|
+
stderr
|
|
145
|
+
)) {
|
|
146
|
+
fail(new Error(`docker daemon unreachable: ${stderr.slice(0, 200)}`));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (/unable to find image|no such image|pull access denied|manifest unknown/i.test(stderr)) {
|
|
150
|
+
fail(new Error(`docker image ${dockerImage} unavailable: ${stderr.slice(0, 200)}`));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
fail(
|
|
154
|
+
new Error(
|
|
155
|
+
`docker checker did not start the candidate: ${stderr.slice(0, 200) || e.message}`
|
|
156
|
+
)
|
|
157
|
+
);
|
|
120
158
|
return;
|
|
121
159
|
}
|
|
122
|
-
|
|
160
|
+
const exitCode = typeof e.code === "number" ? e.code : 1;
|
|
161
|
+
finish({
|
|
162
|
+
exitCode,
|
|
163
|
+
stdout: candidateStdout,
|
|
164
|
+
stderr: stderr ?? ""
|
|
165
|
+
});
|
|
123
166
|
return;
|
|
124
167
|
}
|
|
125
|
-
|
|
168
|
+
if (!candidateStarted) {
|
|
169
|
+
fail(new Error("docker checker exited without starting the candidate"));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
finish({ exitCode: 0, stdout: candidateStdout, stderr: stderr ?? "" });
|
|
126
173
|
}
|
|
127
174
|
);
|
|
128
175
|
});
|
|
129
176
|
}
|
|
177
|
+
async function runChecker(task, candidate) {
|
|
178
|
+
const result = await runPythonProgram(buildProgram(task, candidate));
|
|
179
|
+
return result.exitCode === 0 ? { pass: 1 } : { pass: 0, detail: result.stderr.slice(-600) || "timed out (no output)" };
|
|
180
|
+
}
|
|
130
181
|
function toBenchTask(t) {
|
|
131
182
|
const metadata = {
|
|
132
183
|
promptHeader: t.prompt,
|
|
@@ -153,6 +204,12 @@ function createHumanEvalAdapter() {
|
|
|
153
204
|
else resolve();
|
|
154
205
|
});
|
|
155
206
|
});
|
|
207
|
+
await new Promise((resolve, reject) => {
|
|
208
|
+
execFile("docker", ["image", "inspect", dockerImage], (err) => {
|
|
209
|
+
if (err) reject(new Error(`HumanEval judge needs the cached Docker image ${dockerImage}`));
|
|
210
|
+
else resolve();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
156
213
|
},
|
|
157
214
|
async loadTasks(opts) {
|
|
158
215
|
const offset = Number(process.env.OFFSET ?? 0);
|
|
@@ -176,7 +233,8 @@ export {
|
|
|
176
233
|
loadHumanEval,
|
|
177
234
|
basePrompt,
|
|
178
235
|
extractCode,
|
|
236
|
+
runPythonProgram,
|
|
179
237
|
runChecker,
|
|
180
238
|
createHumanEvalAdapter
|
|
181
239
|
};
|
|
182
|
-
//# sourceMappingURL=chunk-
|
|
240
|
+
//# sourceMappingURL=chunk-5H5XV76F.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/benchmarks/humaneval.ts"],"sourcesContent":["/**\n * HumanEval adapter — the deployable-checker domain as a `BenchmarkAdapter`, so the\n * gate runner (`runGate`) can A/B the STEERING regime on it: a real rollout\n * through the `Supervisor` that self-corrects across rounds, vs blind\n * random@k. This is the experiment `humaneval-gate.mts` names as \"the next one\" —\n * the gate measures SELECTION over stateless single completions; this measures\n * whether observe→steer (self-correction) beats blind compute at equal k.\n *\n * Worker artifact = the model's reply (a Python function, fenced or raw). The\n * DETERMINISTIC judge runs the candidate against the task's own `test` in an\n * isolated `--network=none` python container — exit 0 = pass. No gold\n * `canonical_solution` is ever shown to the model; `goldArtifact` returns it only\n * to self-verify the judge before spending tokens.\n *\n * The primitives (loader / extractor / Docker checker) live here and are reused by\n * `humaneval-gate.mts` — one home, no duplication.\n */\n\nimport { execFile } from 'node:child_process'\nimport { chmodSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { gunzipSync } from 'node:zlib'\nimport type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'\n\nconst humanevalUrl = 'https://github.com/openai/human-eval/raw/master/data/HumanEval.jsonl.gz'\nconst dockerImage = 'python:3.12-slim'\nconst dockerTimeoutMs = Number(process.env.DOCKER_TIMEOUT_MS ?? 20000)\n\nexport interface HumanEvalTask {\n taskId: string\n prompt: string\n test: string\n entryPoint: string\n /** Reference solution body — used ONLY to self-verify the judge, never shown to the worker. */\n canonicalSolution?: string\n}\n\n/** Pull the 164-task HumanEval JSONL.gz and parse it. Fail loud on a non-OK fetch\n * or a malformed line — a silently-short task set would poison the gate. `offset`\n * selects a deeper slice (the later tasks are harder) so the worker has a\n * correctable middle band rather than a saturated easy prefix. */\nexport async function loadHumanEval(limit: number, offset = 0): Promise<HumanEvalTask[]> {\n // Prefer a locally-cached .jsonl.gz (HUMANEVAL_GZ) — the GitHub raw URL rate-limits\n // (429) under repeated runs. Fall back to the network fetch when unset.\n const localGz = process.env.HUMANEVAL_GZ\n let gz: Buffer\n if (localGz) {\n gz = readFileSync(localGz)\n } else {\n const res = await fetch(humanevalUrl)\n if (!res.ok) throw new Error(`HumanEval fetch HTTP ${res.status}: ${humanevalUrl}`)\n gz = Buffer.from(await res.arrayBuffer())\n }\n const text = gunzipSync(gz).toString('utf8')\n const tasks: HumanEvalTask[] = []\n for (const line of text.split('\\n')) {\n if (line.trim() === '') continue\n const d = JSON.parse(line) as {\n task_id?: string\n prompt?: string\n test?: string\n entry_point?: string\n canonical_solution?: string\n }\n if (!d.task_id || !d.prompt || !d.test || !d.entry_point) {\n throw new Error(`malformed HumanEval record: ${line.slice(0, 120)}`)\n }\n tasks.push({\n taskId: d.task_id,\n prompt: d.prompt,\n test: d.test,\n entryPoint: d.entry_point,\n ...(d.canonical_solution ? { canonicalSolution: d.canonical_solution } : {}),\n })\n }\n if (tasks.length === 0) throw new Error('HumanEval parsed to 0 tasks')\n if (offset >= tasks.length) throw new Error(`OFFSET ${offset} >= dataset size ${tasks.length}`)\n return tasks.slice(offset, offset + limit)\n}\n\nconst solveInstruction =\n 'Complete the following Python function. Output the COMPLETE function definition (signature, docstring optional, body) inside a single ```python code block. Include any imports the function needs. Do not write tests or example calls.'\n\nexport function basePrompt(task: HumanEvalTask): string {\n return `${solveInstruction}\\n\\n\\`\\`\\`python\\n${task.prompt}\\`\\`\\``\n}\n\n/** Extract the function source from a model reply: prefer a fenced ```python (or\n * bare ```) block, else fall back to the raw text. The deployable program adds the\n * prompt header (imports + signature context), so a candidate that returns only a\n * body still runs; a candidate that re-defines the function shadows the header. */\nexport function extractCode(reply: string): string {\n const fenced = reply.match(/```(?:python|py)?\\s*\\n([\\s\\S]*?)```/i)\n if (fenced && typeof fenced[1] === 'string') return fenced[1].trim()\n return reply.trim()\n}\n\n/** The deployable test program: the prompt header (imports + signature/docstring the\n * model was given), then the candidate (its def shadows the header's stub), then the\n * task's own check() suite and the call. No gold solution anywhere. */\nfunction buildProgram(task: HumanEvalTask, candidate: string): string {\n return `${task.prompt}\\n${candidate}\\n\\n${task.test}\\n\\ncheck(${task.entryPoint})\\n`\n}\n\nexport interface CheckResult {\n /** {0,1} pass-count for this candidate (1 = the check() suite passed). */\n pass: number\n /** On failure: the interpreter stderr tail (traceback / failing assertion). The\n * execution-grounded feedback a self-repair loop steers on; ignored by selection. */\n detail?: string\n}\n\nexport interface PythonProgramResult {\n exitCode: number\n stdout: string\n stderr: string\n}\n\n/** Run one candidate's deployable test program in an isolated container:\n * read-only root, bounded writable `/tmp`, no network, one CPU, and 512 MiB.\n * Exit 0 → pass. A docker invocation error (binary missing, daemon down, image\n * unavailable) is NOT a test failure — it throws so the harness fails loud rather\n * than scoring every candidate 0 from a broken checker. */\nlet dockerRunSeq = 0\n\n/** Run arbitrary Python in the same isolated container used by the HumanEval\n * checker. This is also the execution-tool primitive for experiments that let\n * a model test its own snippets; model-written code must never run on the host. */\nexport function runPythonProgram(\n program: string,\n timeoutMs = dockerTimeoutMs,\n): Promise<PythonProgramResult> {\n const dir = mkdtempSync(join(tmpdir(), 'hev-'))\n // Rootless Docker maps container root to a subordinate host uid that cannot\n // traverse mkdtemp's default 0700 directory. The directory contains only the\n // disposable candidate script and is mounted read-only into the container.\n chmodSync(dir, 0o755)\n writeFileSync(join(dir, 'p.py'), program, { mode: 0o644 })\n // Unique container name so we can force-reap it regardless of the docker client's state.\n const name = `hev-${process.pid}-${dockerRunSeq++}`\n const startMarker = `__AGENT_RUNTIME_CANDIDATE_STARTED_${name}__`\n return new Promise<PythonProgramResult>((resolvePromise, reject) => {\n let settled = false\n const cleanup = () => {\n rmSync(dir, { recursive: true, force: true })\n // `execFile`'s `timeout` kills the docker CLIENT, not the container — a hung\n // `python` would otherwise pin a CPU forever. Force-reap by name (fire-and-forget;\n // the name is unique, so no reuse race).\n execFile('docker', ['rm', '-f', name], () => {})\n }\n const finish = (res: PythonProgramResult) => {\n if (settled) return\n settled = true\n clearTimeout(backstop)\n cleanup()\n resolvePromise(res)\n }\n const fail = (e: Error) => {\n if (settled) return\n settled = true\n clearTimeout(backstop)\n cleanup()\n reject(e)\n }\n // Candidate timeouts are enforced inside the container. If the outer Docker\n // client or daemon misses this larger deadline, that is infrastructure failure,\n // not a wrong answer.\n const outerTimeoutMs = timeoutMs + 3_000\n const backstop = setTimeout(\n () => fail(new Error(`docker checker did not return within ${outerTimeoutMs + 3_000}ms`)),\n outerTimeoutMs + 3_000,\n )\n const inContainerSeconds = Math.max(1, Math.ceil(timeoutMs / 1_000))\n execFile(\n 'docker',\n [\n 'run',\n '--rm',\n '--pull=never',\n '--name',\n name,\n '--network=none',\n '--cpus=1',\n '--memory=512m',\n '--pids-limit=64',\n '--cap-drop=ALL',\n '--security-opt=no-new-privileges',\n '--read-only',\n '--tmpfs',\n '/tmp:rw,nosuid,nodev,noexec,size=64m,mode=1777',\n '--user',\n '65534:65534',\n '-v',\n `${dir}:/w:ro`,\n '-w',\n '/w',\n dockerImage,\n 'sh',\n '-c',\n 'command -v timeout >/dev/null 2>&1 && command -v python >/dev/null 2>&1 && [ -r /w/p.py ] || exit 126; printf \"%s\\\\n\" \"$1\"; exec timeout -s KILL \"$2\" python /w/p.py',\n 'agent-runtime-checker',\n startMarker,\n `${inContainerSeconds}s`,\n ],\n { timeout: outerTimeoutMs, killSignal: 'SIGKILL', maxBuffer: 4 * 1024 * 1024 },\n (err, stdout, stderr) => {\n const markerLine = `${startMarker}\\n`\n const candidateStarted = stdout?.startsWith(markerLine) === true\n const candidateStdout = candidateStarted ? stdout.slice(markerLine.length) : (stdout ?? '')\n if (err) {\n const e = err as Error & { killed?: boolean; code?: number | string }\n if (e.code === 'ENOENT') {\n fail(new Error('docker binary not found on PATH — cannot run the deployable checker'))\n return\n }\n if (e.killed) {\n fail(new Error(`docker checker exceeded its ${outerTimeoutMs}ms outer timeout`))\n return\n }\n if (!candidateStarted) {\n if (\n /cannot connect to the docker daemon|is the docker daemon running|permission denied while trying to connect/i.test(\n stderr,\n )\n ) {\n fail(new Error(`docker daemon unreachable: ${stderr.slice(0, 200)}`))\n return\n }\n if (/unable to find image|no such image|pull access denied|manifest unknown/i.test(stderr)) {\n fail(new Error(`docker image ${dockerImage} unavailable: ${stderr.slice(0, 200)}`))\n return\n }\n fail(\n new Error(\n `docker checker did not start the candidate: ${stderr.slice(0, 200) || e.message}`,\n ),\n )\n return\n }\n const exitCode = typeof e.code === 'number' ? e.code : 1\n finish({\n exitCode,\n stdout: candidateStdout,\n stderr: stderr ?? '',\n })\n return\n }\n if (!candidateStarted) {\n fail(new Error('docker checker exited without starting the candidate'))\n return\n }\n finish({ exitCode: 0, stdout: candidateStdout, stderr: stderr ?? '' })\n },\n )\n })\n}\n\nexport async function runChecker(task: HumanEvalTask, candidate: string): Promise<CheckResult> {\n const result = await runPythonProgram(buildProgram(task, candidate))\n return result.exitCode === 0\n ? { pass: 1 }\n : { pass: 0, detail: result.stderr.slice(-600) || 'timed out (no output)' }\n}\n\n/** A HumanEval task carries its checker inputs in metadata so the deterministic\n * judge can rebuild the deployable program from a `BenchTask` alone. */\ninterface HumanEvalMeta extends Record<string, unknown> {\n promptHeader: string\n test: string\n entryPoint: string\n canonicalSolution?: string\n}\n\nfunction toBenchTask(t: HumanEvalTask): BenchTask {\n const metadata: HumanEvalMeta = {\n promptHeader: t.prompt,\n test: t.test,\n entryPoint: t.entryPoint,\n ...(t.canonicalSolution ? { canonicalSolution: t.canonicalSolution } : {}),\n }\n return { id: t.taskId, prompt: basePrompt(t), metadata }\n}\n\nfunction taskFromMeta(task: BenchTask): HumanEvalTask {\n const m = task.metadata as HumanEvalMeta | undefined\n if (!m?.promptHeader || !m.test || !m.entryPoint) {\n throw new Error(`HumanEval judge: task ${task.id} missing checker metadata`)\n }\n return { taskId: task.id, prompt: m.promptHeader, test: m.test, entryPoint: m.entryPoint }\n}\n\n/** The HumanEval `BenchmarkAdapter`. OFFSET (env) selects the correctable middle\n * band; loadTasks honors `limit`/`ids`. The judge is the Docker deployable checker. */\nexport function createHumanEvalAdapter(): BenchmarkAdapter {\n return {\n name: 'humaneval',\n async preflight() {\n // Prove the daemon and exact local image before any model call. Scoring uses\n // --pull=never so a registry or cold pull cannot become a candidate outcome.\n await new Promise<void>((resolve, reject) => {\n execFile('docker', ['version', '--format', '{{.Server.Version}}'], (err) => {\n if (err) reject(new Error('HumanEval judge needs a running Docker daemon (python:3.12-slim, --network=none)'))\n else resolve()\n })\n })\n await new Promise<void>((resolve, reject) => {\n execFile('docker', ['image', 'inspect', dockerImage], (err) => {\n if (err) reject(new Error(`HumanEval judge needs the cached Docker image ${dockerImage}`))\n else resolve()\n })\n })\n },\n async loadTasks(opts?: LoadOptions) {\n const offset = Number(process.env.OFFSET ?? 0)\n // Pull a generous window when filtering by id, else exactly `limit` from offset.\n const all = await loadHumanEval(opts?.ids ? 164 : (opts?.limit ?? 8), offset)\n const picked = opts?.ids ? all.filter((t) => opts.ids?.includes(t.taskId)) : all\n return picked.map(toBenchTask)\n },\n async judge(task: BenchTask, artifact: string): Promise<BenchScore> {\n const { pass } = await runChecker(taskFromMeta(task), extractCode(artifact))\n return { resolved: pass === 1, score: pass, detail: pass === 1 ? 'tests passed' : 'tests failed' }\n },\n async goldArtifact(task: BenchTask) {\n const m = task.metadata as HumanEvalMeta | undefined\n const sol = m?.canonicalSolution\n // Return the COMPLETE function (signature header + canonical body), i.e. what a\n // real worker emits — NOT the body alone. The judge runs `extractCode`, whose\n // unfenced fallback is `reply.trim()`; trimming a body-only string strips its\n // leading indent and breaks it, so a body-only gold fails its own judge. A full\n // def starts at column 0, trims safely, and self-verifies.\n return sol ? `${m!.promptHeader}${sol}` : undefined\n },\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
} from "./chunk-PA2ZKHJC.js";
|
|
37
37
|
import {
|
|
38
38
|
createHumanEvalAdapter
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-5H5XV76F.js";
|
|
40
40
|
import {
|
|
41
41
|
createMind2WebAdapter
|
|
42
42
|
} from "./chunk-TBKU5XQI.js";
|
|
@@ -141,4 +141,4 @@ export {
|
|
|
141
141
|
ADAPTERS,
|
|
142
142
|
resolveAdapter
|
|
143
143
|
};
|
|
144
|
-
//# sourceMappingURL=chunk-
|
|
144
|
+
//# sourceMappingURL=chunk-PWQVGAJB.js.map
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ADAPTERS,
|
|
3
3
|
resolveAdapter
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PWQVGAJB.js";
|
|
5
5
|
import "./chunk-2PVVP7GN.js";
|
|
6
6
|
import "./chunk-VQRS7VUC.js";
|
|
7
7
|
import "./chunk-Z7ML6L77.js";
|
|
@@ -24,7 +24,7 @@ import "./chunk-R36V2VP7.js";
|
|
|
24
24
|
import "./chunk-JRWWGMK7.js";
|
|
25
25
|
import "./chunk-INNOYXCP.js";
|
|
26
26
|
import "./chunk-PA2ZKHJC.js";
|
|
27
|
-
import "./chunk-
|
|
27
|
+
import "./chunk-5H5XV76F.js";
|
|
28
28
|
import "./chunk-TBKU5XQI.js";
|
|
29
29
|
import {
|
|
30
30
|
createNoMiraclAdapter
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/agent-bench",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The unified benchmark suite for agent-runtime agents: 31 adapters (commit0, enterpriseops-gym, ragbench, crag, nomiracl, open-rag-bench, t2-ragbench, tau3-banking, bfcl, finresearchbench, …) behind one resolveAdapter registry, each with a real judge or fail-loud unsupported scorer. Score any profile/skill/prompt change against them. Map: bench/HARNESS.md.",
|
|
6
6
|
"repository": {
|
|
@@ -25,10 +25,11 @@
|
|
|
25
25
|
}
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@tangle-network/agent-eval": "0.
|
|
29
|
-
"@tangle-network/agent-interface": "0.
|
|
28
|
+
"@tangle-network/agent-eval": "0.123.0",
|
|
29
|
+
"@tangle-network/agent-interface": "0.32.0",
|
|
30
|
+
"@tangle-network/agent-knowledge": "^4.1.0",
|
|
30
31
|
"@tangle-network/sandbox": "^0.11.1",
|
|
31
|
-
"@tangle-network/agent-runtime": "0.
|
|
32
|
+
"@tangle-network/agent-runtime": "0.102.1"
|
|
32
33
|
},
|
|
33
34
|
"devDependencies": {
|
|
34
35
|
"@types/node": "^25.9.3",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process'
|
|
2
|
-
import { access, readdir } from 'node:fs/promises'
|
|
2
|
+
import { access, readdir, readFile } from 'node:fs/promises'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
5
|
import { promisify } from 'node:util'
|
|
@@ -43,14 +43,36 @@ try {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const tests = await collectTests(sourceDir)
|
|
46
|
-
|
|
47
|
-
if (relativeTests.length === 0) throw new Error('no package tests found under src/')
|
|
46
|
+
if (tests.length === 0) throw new Error('no package tests found under src/')
|
|
48
47
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
// Two test runtimes coexist under src/: node:test files run under `node --test`;
|
|
49
|
+
// vitest files (the swe-arena suite) crash there (`vitest` APIs need the vitest
|
|
50
|
+
// worker), so partition by the framework each file actually imports.
|
|
51
|
+
const nodeTests = []
|
|
52
|
+
const vitestTests = []
|
|
53
|
+
for (const file of tests) {
|
|
54
|
+
const body = await readFile(file, 'utf8')
|
|
55
|
+
if (/from\s+['"]vitest['"]/.test(body)) vitestTests.push(file)
|
|
56
|
+
else nodeTests.push(file)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (nodeTests.length > 0) {
|
|
60
|
+
await run(
|
|
61
|
+
process.execPath,
|
|
62
|
+
['--test', '--import', 'tsx', ...nodeTests.map((file) => path.relative(benchDir, file))],
|
|
63
|
+
{
|
|
64
|
+
...process.env,
|
|
65
|
+
TSX_TSCONFIG_PATH: 'tsconfig.public.json',
|
|
66
|
+
},
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (vitestTests.length > 0) {
|
|
71
|
+
await run('npx', ['vitest', 'run', ...vitestTests.map((file) => path.relative(benchDir, file))])
|
|
72
|
+
}
|
|
53
73
|
|
|
54
74
|
await run(python, ['-m', 'unittest', 'discover', '-s', 'pier_agents', '-p', '*_test.py'])
|
|
55
75
|
|
|
56
|
-
console.log(
|
|
76
|
+
console.log(
|
|
77
|
+
`package tests passed: ${tests.length}/${tests.length} TypeScript files (${nodeTests.length} node:test + ${vitestTests.length} vitest) + Pier bridge`,
|
|
78
|
+
)
|
|
@@ -370,6 +370,7 @@ ${proofArm === 'success' ? "(task / 'src/status.txt').write_text('ready\\nowner=
|
|
|
370
370
|
},
|
|
371
371
|
instructionDelivery: { kind: 'argv-append' as const },
|
|
372
372
|
cwd: { workspace: 'task' as const, path: '.' },
|
|
373
|
+
env: { PATH: { kind: 'public' as const, value: '/usr/local/bin:/usr/bin:/bin' } },
|
|
373
374
|
environment: { kind: 'evaluator-task-container' as const },
|
|
374
375
|
workspace: candidateWorkspace,
|
|
375
376
|
isolation: {
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import assert from 'node:assert/strict'
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
import { describe, it } from 'node:test'
|
|
6
|
+
import { runPythonProgram } from './humaneval'
|
|
7
|
+
|
|
8
|
+
describe('HumanEval Python isolation', () => {
|
|
9
|
+
it('runs the exact program through the resource-capped networkless container', async () => {
|
|
10
|
+
const dir = mkdtempSync(join(tmpdir(), 'humaneval-docker-test-'))
|
|
11
|
+
const fakeDocker = join(dir, 'docker')
|
|
12
|
+
const capture = join(dir, 'capture.jsonl')
|
|
13
|
+
writeFileSync(
|
|
14
|
+
fakeDocker,
|
|
15
|
+
`#!/usr/bin/env node
|
|
16
|
+
const fs = require('node:fs')
|
|
17
|
+
const path = require('node:path')
|
|
18
|
+
const args = process.argv.slice(2)
|
|
19
|
+
if (args[0] === 'rm') process.exit(0)
|
|
20
|
+
if (process.env.FAKE_DOCKER_MISSING === '1') {
|
|
21
|
+
process.stderr.write('docker: Error response from daemon: No such image: python:3.12-slim')
|
|
22
|
+
process.exit(125)
|
|
23
|
+
}
|
|
24
|
+
const mount = args[args.indexOf('-v') + 1]
|
|
25
|
+
const hostDir = mount.slice(0, -':/w:ro'.length)
|
|
26
|
+
fs.appendFileSync(process.env.FAKE_DOCKER_CAPTURE, JSON.stringify({
|
|
27
|
+
args,
|
|
28
|
+
program: fs.readFileSync(path.join(hostDir, 'p.py'), 'utf8'),
|
|
29
|
+
}) + '\\n')
|
|
30
|
+
if (process.env.FAKE_DOCKER_NO_START === '1') {
|
|
31
|
+
process.stderr.write('docker: Error response from daemon: unable to start container process')
|
|
32
|
+
process.exit(125)
|
|
33
|
+
}
|
|
34
|
+
process.stdout.write(args[args.length - 2] + '\\n')
|
|
35
|
+
if (process.env.FAKE_DOCKER_CANDIDATE === 'daemon-text') {
|
|
36
|
+
process.stderr.write('Cannot connect to the Docker daemon')
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
if (process.env.FAKE_DOCKER_CANDIDATE === 'exit-125') process.exit(125)
|
|
40
|
+
process.stdout.write('CONTAINER_OK\\n')
|
|
41
|
+
`,
|
|
42
|
+
{ mode: 0o755 },
|
|
43
|
+
)
|
|
44
|
+
const originalPath = process.env.PATH
|
|
45
|
+
const originalCapture = process.env.FAKE_DOCKER_CAPTURE
|
|
46
|
+
const originalMissing = process.env.FAKE_DOCKER_MISSING
|
|
47
|
+
const originalCandidate = process.env.FAKE_DOCKER_CANDIDATE
|
|
48
|
+
const originalNoStart = process.env.FAKE_DOCKER_NO_START
|
|
49
|
+
process.env.PATH = `${dir}:${originalPath ?? ''}`
|
|
50
|
+
process.env.FAKE_DOCKER_CAPTURE = capture
|
|
51
|
+
try {
|
|
52
|
+
const program = 'print("exact bytes")\n'
|
|
53
|
+
const result = await runPythonProgram(program, 2_000)
|
|
54
|
+
assert.equal(result.exitCode, 0)
|
|
55
|
+
assert.match(result.stdout, /CONTAINER_OK/)
|
|
56
|
+
|
|
57
|
+
const firstCall = JSON.parse(readFileSync(capture, 'utf8').trim().split('\n')[0]!) as {
|
|
58
|
+
args: string[]
|
|
59
|
+
program: string
|
|
60
|
+
}
|
|
61
|
+
assert.equal(firstCall.program, program)
|
|
62
|
+
assert.deepEqual(firstCall.args.slice(0, 2), ['run', '--rm'])
|
|
63
|
+
assert.ok(firstCall.args.includes('--network=none'))
|
|
64
|
+
assert.ok(firstCall.args.includes('--cpus=1'))
|
|
65
|
+
assert.ok(firstCall.args.includes('--memory=512m'))
|
|
66
|
+
assert.ok(firstCall.args.includes('--pids-limit=64'))
|
|
67
|
+
assert.ok(firstCall.args.includes('--cap-drop=ALL'))
|
|
68
|
+
assert.ok(firstCall.args.includes('--security-opt=no-new-privileges'))
|
|
69
|
+
assert.ok(firstCall.args.includes('--pull=never'))
|
|
70
|
+
assert.ok(firstCall.args.includes('--read-only'))
|
|
71
|
+
assert.equal(
|
|
72
|
+
firstCall.args[firstCall.args.indexOf('--tmpfs') + 1],
|
|
73
|
+
'/tmp:rw,nosuid,nodev,noexec,size=64m,mode=1777',
|
|
74
|
+
)
|
|
75
|
+
assert.equal(firstCall.args[firstCall.args.indexOf('--user') + 1], '65534:65534')
|
|
76
|
+
assert.match(firstCall.args[firstCall.args.indexOf('-v') + 1] ?? '', /:\/w:ro$/)
|
|
77
|
+
const command = firstCall.args.slice(firstCall.args.indexOf('python:3.12-slim'))
|
|
78
|
+
assert.equal(command[0], 'python:3.12-slim')
|
|
79
|
+
assert.equal(command[1], 'sh')
|
|
80
|
+
assert.equal(command[2], '-c')
|
|
81
|
+
assert.match(command[3] ?? '', /command -v timeout/)
|
|
82
|
+
assert.equal(command[4], 'agent-runtime-checker')
|
|
83
|
+
assert.match(command[5] ?? '', /^__AGENT_RUNTIME_CANDIDATE_STARTED_hev-/)
|
|
84
|
+
assert.equal(command[6], '2s')
|
|
85
|
+
|
|
86
|
+
process.env.FAKE_DOCKER_MISSING = '1'
|
|
87
|
+
await assert.rejects(
|
|
88
|
+
runPythonProgram('print("must not score")\n', 2_000),
|
|
89
|
+
/docker image python:3\.12-slim unavailable/,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
delete process.env.FAKE_DOCKER_MISSING
|
|
93
|
+
process.env.FAKE_DOCKER_CANDIDATE = 'daemon-text'
|
|
94
|
+
const daemonText = await runPythonProgram('raise SystemExit(1)\n', 2_000)
|
|
95
|
+
assert.equal(daemonText.exitCode, 1)
|
|
96
|
+
assert.match(daemonText.stderr, /Cannot connect to the Docker daemon/)
|
|
97
|
+
|
|
98
|
+
process.env.FAKE_DOCKER_CANDIDATE = 'exit-125'
|
|
99
|
+
const exit125 = await runPythonProgram('raise SystemExit(125)\n', 2_000)
|
|
100
|
+
assert.equal(exit125.exitCode, 125)
|
|
101
|
+
|
|
102
|
+
delete process.env.FAKE_DOCKER_CANDIDATE
|
|
103
|
+
process.env.FAKE_DOCKER_NO_START = '1'
|
|
104
|
+
await assert.rejects(
|
|
105
|
+
runPythonProgram('print("never started")\n', 2_000),
|
|
106
|
+
/did not start the candidate/,
|
|
107
|
+
)
|
|
108
|
+
} finally {
|
|
109
|
+
if (originalPath === undefined) delete process.env.PATH
|
|
110
|
+
else process.env.PATH = originalPath
|
|
111
|
+
if (originalCapture === undefined) delete process.env.FAKE_DOCKER_CAPTURE
|
|
112
|
+
else process.env.FAKE_DOCKER_CAPTURE = originalCapture
|
|
113
|
+
if (originalMissing === undefined) delete process.env.FAKE_DOCKER_MISSING
|
|
114
|
+
else process.env.FAKE_DOCKER_MISSING = originalMissing
|
|
115
|
+
if (originalCandidate === undefined) delete process.env.FAKE_DOCKER_CANDIDATE
|
|
116
|
+
else process.env.FAKE_DOCKER_CANDIDATE = originalCandidate
|
|
117
|
+
if (originalNoStart === undefined) delete process.env.FAKE_DOCKER_NO_START
|
|
118
|
+
else process.env.FAKE_DOCKER_NO_START = originalNoStart
|
|
119
|
+
rmSync(dir, { recursive: true, force: true })
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
})
|