@tangle-network/agent-bench 0.1.0 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/HARNESS.md +302 -0
- package/README.md +39 -2
- package/dist/adapters.d.ts +15 -0
- package/dist/adapters.js +43 -0
- package/dist/adapters.js.map +1 -0
- package/dist/benchmarks/_harness.d.ts +125 -0
- package/dist/benchmarks/_harness.js +33 -0
- package/dist/benchmarks/_harness.js.map +1 -0
- package/dist/benchmarks/aec-bench.d.ts +27 -0
- package/dist/benchmarks/aec-bench.js +8 -0
- package/dist/benchmarks/aec-bench.js.map +1 -0
- package/dist/benchmarks/agentbench.d.ts +16 -0
- package/dist/benchmarks/agentbench.js +10 -0
- package/dist/benchmarks/agentbench.js.map +1 -0
- package/dist/benchmarks/appworld.d.ts +37 -0
- package/dist/benchmarks/appworld.js +14 -0
- package/dist/benchmarks/appworld.js.map +1 -0
- package/dist/benchmarks/bfcl.d.ts +18 -0
- package/dist/benchmarks/bfcl.js +10 -0
- package/dist/benchmarks/bfcl.js.map +1 -0
- package/dist/benchmarks/cad-design.d.ts +45 -0
- package/dist/benchmarks/cad-design.js +7 -0
- package/dist/benchmarks/cad-design.js.map +1 -0
- package/dist/benchmarks/cadbench.d.ts +19 -0
- package/dist/benchmarks/cadbench.js +8 -0
- package/dist/benchmarks/cadbench.js.map +1 -0
- package/dist/benchmarks/cadgenbench.d.ts +22 -0
- package/dist/benchmarks/cadgenbench.js +8 -0
- package/dist/benchmarks/cadgenbench.js.map +1 -0
- package/dist/benchmarks/commit0.d.ts +31 -0
- package/dist/benchmarks/commit0.js +10 -0
- package/dist/benchmarks/commit0.js.map +1 -0
- package/dist/benchmarks/crag.d.ts +14 -0
- package/dist/benchmarks/crag.js +9 -0
- package/dist/benchmarks/crag.js.map +1 -0
- package/dist/benchmarks/dabstep.d.ts +18 -0
- package/dist/benchmarks/dabstep.js +10 -0
- package/dist/benchmarks/dabstep.js.map +1 -0
- package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
- package/dist/benchmarks/enterpriseops-gym.js +10 -0
- package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
- package/dist/benchmarks/finresearchbench.d.ts +15 -0
- package/dist/benchmarks/finresearchbench.js +8 -0
- package/dist/benchmarks/finresearchbench.js.map +1 -0
- package/dist/benchmarks/finsearchcomp.d.ts +49 -0
- package/dist/benchmarks/finsearchcomp.js +7 -0
- package/dist/benchmarks/finsearchcomp.js.map +1 -0
- package/dist/benchmarks/frames.d.ts +59 -0
- package/dist/benchmarks/frames.js +13 -0
- package/dist/benchmarks/frames.js.map +1 -0
- package/dist/benchmarks/hotpotqa.d.ts +48 -0
- package/dist/benchmarks/hotpotqa.js +15 -0
- package/dist/benchmarks/hotpotqa.js.map +1 -0
- package/dist/benchmarks/humaneval.d.ts +53 -0
- package/dist/benchmarks/humaneval.js +15 -0
- package/dist/benchmarks/humaneval.js.map +1 -0
- package/dist/benchmarks/mind2web.d.ts +41 -0
- package/dist/benchmarks/mind2web.js +9 -0
- package/dist/benchmarks/mind2web.js.map +1 -0
- package/dist/benchmarks/nomiracl.d.ts +15 -0
- package/dist/benchmarks/nomiracl.js +9 -0
- package/dist/benchmarks/nomiracl.js.map +1 -0
- package/dist/benchmarks/open-rag-bench.d.ts +14 -0
- package/dist/benchmarks/open-rag-bench.js +9 -0
- package/dist/benchmarks/open-rag-bench.js.map +1 -0
- package/dist/benchmarks/programbench.d.ts +38 -0
- package/dist/benchmarks/programbench.js +10 -0
- package/dist/benchmarks/programbench.js.map +1 -0
- package/dist/benchmarks/rag-shared.d.ts +42 -0
- package/dist/benchmarks/rag-shared.js +39 -0
- package/dist/benchmarks/rag-shared.js.map +1 -0
- package/dist/benchmarks/ragbench.d.ts +16 -0
- package/dist/benchmarks/ragbench.js +9 -0
- package/dist/benchmarks/ragbench.js.map +1 -0
- package/dist/benchmarks/simpleqa.d.ts +64 -0
- package/dist/benchmarks/simpleqa.js +11 -0
- package/dist/benchmarks/simpleqa.js.map +1 -0
- package/dist/benchmarks/swe-bench.d.ts +56 -0
- package/dist/benchmarks/swe-bench.js +14 -0
- package/dist/benchmarks/swe-bench.js.map +1 -0
- package/dist/benchmarks/t2-ragbench.d.ts +14 -0
- package/dist/benchmarks/t2-ragbench.js +9 -0
- package/dist/benchmarks/t2-ragbench.js.map +1 -0
- package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
- package/dist/benchmarks/tau-bench-shared.js +10 -0
- package/dist/benchmarks/tau-bench-shared.js.map +1 -0
- package/dist/benchmarks/tau2-bench.d.ts +7 -0
- package/dist/benchmarks/tau2-bench.js +11 -0
- package/dist/benchmarks/tau2-bench.js.map +1 -0
- package/dist/benchmarks/tau3-banking.d.ts +15 -0
- package/dist/benchmarks/tau3-banking.js +9 -0
- package/dist/benchmarks/tau3-banking.js.map +1 -0
- package/dist/benchmarks/terminal-bench.d.ts +24 -0
- package/dist/benchmarks/terminal-bench.js +8 -0
- package/dist/benchmarks/terminal-bench.js.map +1 -0
- package/dist/benchmarks/toollm.d.ts +16 -0
- package/dist/benchmarks/toollm.js +10 -0
- package/dist/benchmarks/toollm.js.map +1 -0
- package/dist/benchmarks/trata-hedge.d.ts +32 -0
- package/dist/benchmarks/trata-hedge.js +7 -0
- package/dist/benchmarks/trata-hedge.js.map +1 -0
- package/dist/benchmarks/types.d.ts +107 -0
- package/dist/benchmarks/types.js +1 -0
- package/dist/benchmarks/types.js.map +1 -0
- package/dist/benchmarks/webarena-verified.d.ts +16 -0
- package/dist/benchmarks/webarena-verified.js +10 -0
- package/dist/benchmarks/webarena-verified.js.map +1 -0
- package/dist/chunk-2PVVP7GN.js +197 -0
- package/dist/chunk-2PVVP7GN.js.map +1 -0
- package/dist/chunk-2XU6OGEN.js +170 -0
- package/dist/chunk-2XU6OGEN.js.map +1 -0
- package/dist/chunk-53UPUNBZ.js +325 -0
- package/dist/chunk-53UPUNBZ.js.map +1 -0
- package/dist/chunk-5SBJCB6W.js +144 -0
- package/dist/chunk-5SBJCB6W.js.map +1 -0
- package/dist/chunk-7WSD27QQ.js +118 -0
- package/dist/chunk-7WSD27QQ.js.map +1 -0
- package/dist/chunk-C7T7WEK2.js +103 -0
- package/dist/chunk-C7T7WEK2.js.map +1 -0
- package/dist/chunk-CKUVRZ2T.js +251 -0
- package/dist/chunk-CKUVRZ2T.js.map +1 -0
- package/dist/chunk-HBSWHQNJ.js +30 -0
- package/dist/chunk-HBSWHQNJ.js.map +1 -0
- package/dist/chunk-HHXFIHXC.js +116 -0
- package/dist/chunk-HHXFIHXC.js.map +1 -0
- package/dist/chunk-IFAV6KEM.js +276 -0
- package/dist/chunk-IFAV6KEM.js.map +1 -0
- package/dist/chunk-INNOYXCP.js +387 -0
- package/dist/chunk-INNOYXCP.js.map +1 -0
- package/dist/chunk-J3KDJNX2.js +182 -0
- package/dist/chunk-J3KDJNX2.js.map +1 -0
- package/dist/chunk-JRWWGMK7.js +148 -0
- package/dist/chunk-JRWWGMK7.js.map +1 -0
- package/dist/chunk-JTHWEDEW.js +32 -0
- package/dist/chunk-JTHWEDEW.js.map +1 -0
- package/dist/chunk-KDIKRJGB.js +120 -0
- package/dist/chunk-KDIKRJGB.js.map +1 -0
- package/dist/chunk-LRRD7NAG.js +301 -0
- package/dist/chunk-LRRD7NAG.js.map +1 -0
- package/dist/chunk-ODT47UAY.js +221 -0
- package/dist/chunk-ODT47UAY.js.map +1 -0
- package/dist/chunk-PA2ZKHJC.js +230 -0
- package/dist/chunk-PA2ZKHJC.js.map +1 -0
- package/dist/chunk-PPYSEKFM.js +182 -0
- package/dist/chunk-PPYSEKFM.js.map +1 -0
- package/dist/chunk-PUIRNYI7.js +189 -0
- package/dist/chunk-PUIRNYI7.js.map +1 -0
- package/dist/chunk-R36V2VP7.js +169 -0
- package/dist/chunk-R36V2VP7.js.map +1 -0
- package/dist/chunk-R67DFVLO.js +142 -0
- package/dist/chunk-R67DFVLO.js.map +1 -0
- package/dist/chunk-SEVJPLZC.js +260 -0
- package/dist/chunk-SEVJPLZC.js.map +1 -0
- package/dist/chunk-SYDW647C.js +318 -0
- package/dist/chunk-SYDW647C.js.map +1 -0
- package/dist/chunk-TBKU5XQI.js +228 -0
- package/dist/chunk-TBKU5XQI.js.map +1 -0
- package/dist/chunk-TSWPNOYM.js +147 -0
- package/dist/chunk-TSWPNOYM.js.map +1 -0
- package/dist/chunk-UAIOHCUK.js +27 -0
- package/dist/chunk-UAIOHCUK.js.map +1 -0
- package/dist/chunk-UPAMRDX4.js +233 -0
- package/dist/chunk-UPAMRDX4.js.map +1 -0
- package/dist/chunk-VQRS7VUC.js +342 -0
- package/dist/chunk-VQRS7VUC.js.map +1 -0
- package/dist/chunk-X3BTXCJ4.js +262 -0
- package/dist/chunk-X3BTXCJ4.js.map +1 -0
- package/dist/chunk-X5YKXC6V.js +211 -0
- package/dist/chunk-X5YKXC6V.js.map +1 -0
- package/dist/chunk-Y6O2OCUO.js +130 -0
- package/dist/chunk-Y6O2OCUO.js.map +1 -0
- package/dist/chunk-YCGY7UIZ.js +208 -0
- package/dist/chunk-YCGY7UIZ.js.map +1 -0
- package/dist/chunk-Z7ML6L77.js +162 -0
- package/dist/chunk-Z7ML6L77.js.map +1 -0
- package/dist/chunk-ZEWMTR5M.js +136 -0
- package/dist/chunk-ZEWMTR5M.js.map +1 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +1908 -0
- package/dist/index.js.map +1 -0
- package/fixtures/aec-bench.json +18 -0
- package/fixtures/agentbench-dbbench.json +22 -0
- package/fixtures/bfcl.json +45 -0
- package/fixtures/commit0.json +72 -0
- package/fixtures/crag.json +10 -0
- package/fixtures/dabstep.json +22 -0
- package/fixtures/enterpriseops-gym.json +103 -0
- package/fixtures/finresearchbench.json +21 -0
- package/fixtures/finsearchcomp.json +66 -0
- package/fixtures/frames.json +26 -0
- package/fixtures/hotpotqa.json +182 -0
- package/fixtures/nomiracl.json +26 -0
- package/fixtures/open-rag-bench.json +16 -0
- package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
- package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
- package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
- package/fixtures/pier-agent/no-model-task/task.toml +35 -0
- package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
- package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
- package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
- package/fixtures/programbench.json +17 -0
- package/fixtures/ragbench.json +21 -0
- package/fixtures/simpleqa.json +121 -0
- package/fixtures/t2-ragbench.json +13 -0
- package/fixtures/tau2-bench.json +16 -0
- package/fixtures/tau3-banking.json +16 -0
- package/fixtures/toollm.json +28 -0
- package/fixtures/webarena-verified.json +20 -0
- package/package.json +60 -20
- package/pier_agents/__init__.py +18 -0
- package/pier_agents/candidate_contract.py +755 -0
- package/pier_agents/process_boundary.py +321 -0
- package/pier_agents/tangle_candidate.py +907 -0
- package/pier_agents/workspace_boundary.py +368 -0
- package/scripts/appworld_driver.py +359 -0
- package/scripts/cadbench_prepare.py +22 -0
- package/scripts/cadgenbench_hard_parts.py +48 -0
- package/scripts/clbench_codebase_judge.py +73 -0
- package/scripts/commit0_judge.py +170 -0
- package/scripts/dabstep_judge.py +42 -0
- package/scripts/enterpriseops_gym_judge.py +281 -0
- package/scripts/programbench_judge.py +120 -0
- package/scripts/render-gate-chart.mjs +176 -0
- package/scripts/run-package-tests.mjs +56 -0
- package/scripts/terminate-pier-trial.mts +66 -0
- package/scripts/trata-hedge/README.md +56 -0
- package/scripts/trata-hedge/run.sh +60 -0
- package/scripts/trata-hedge/solve.py +83 -0
- package/scripts/verify-packed-consumer.mjs +235 -0
- package/scripts/verify-pier-agent.mts +715 -0
- package/scripts/verify-pier-pair.mts +74 -0
- package/scripts/verify-pier-recovery.mts +139 -0
- package/src/adapters.ts +26 -0
- package/src/benchmarks/_harness.test.mts +178 -0
- package/src/benchmarks/_harness.ts +239 -16
- package/src/benchmarks/agentbench.ts +163 -0
- package/src/benchmarks/appworld.test.mts +15 -9
- package/src/benchmarks/bfcl.ts +346 -0
- package/src/benchmarks/crag.ts +137 -0
- package/src/benchmarks/dabstep.test.mts +70 -0
- package/src/benchmarks/dabstep.ts +212 -0
- package/src/benchmarks/external-adapters.test.mts +150 -0
- package/src/benchmarks/finresearchbench.ts +269 -0
- package/src/benchmarks/humaneval.ts +20 -8
- package/src/benchmarks/nomiracl.ts +180 -0
- package/src/benchmarks/open-rag-bench.ts +153 -0
- package/src/benchmarks/rag-benchmarks.test.mts +138 -0
- package/src/benchmarks/rag-shared.ts +327 -0
- package/src/benchmarks/ragbench.ts +171 -0
- package/src/benchmarks/swe-bench.test.mts +61 -0
- package/src/benchmarks/swe-bench.ts +201 -19
- package/src/benchmarks/t2-ragbench.ts +166 -0
- package/src/benchmarks/tau-bench-shared.ts +214 -0
- package/src/benchmarks/tau2-bench.ts +30 -0
- package/src/benchmarks/tau3-banking.ts +29 -0
- package/src/benchmarks/terminal-bench.test.mts +33 -0
- package/src/benchmarks/terminal-bench.ts +23 -8
- package/src/benchmarks/toollm.ts +254 -0
- package/src/benchmarks/types.ts +42 -0
- package/src/benchmarks/webarena-verified.ts +200 -0
- package/src/commit0-prereqs.sh +0 -0
- package/src/coordination-mcp-container-reach.mts +181 -0
- package/src/decoder-live.mts +1 -1
- package/src/examples/README.md +103 -39
- package/src/examples/benchmark-matrix.mts +101 -0
- package/src/examples/lean-proof-gate.README.md +77 -0
- package/src/examples/lean-proof-gate.mts +162 -0
- package/src/examples/lean-verify.ts +95 -0
- package/src/examples/lean.Dockerfile +12 -0
- package/src/examples/math-demo.mts +9 -7
- package/src/examples/strategy-demo.mts +10 -12
- package/src/gate.ts +3 -2
- package/src/hev-eval.mts +69 -0
- package/src/hev-improve.mts +169 -0
- package/src/hev-structural.mts +688 -0
- package/src/index.ts +73 -0
- package/src/mbpp-structural.mts +662 -0
- package/src/pier-agent.test-fixtures.mts +19 -0
- package/src/pier-agent.test.mts +363 -0
- package/src/pier-agent.ts +657 -0
- package/src/pier-result-grader.mjs +30 -0
- package/src/pier-result-grader.test.mts +62 -0
- package/src/pier-result-grader.ts +108 -0
- package/src/pier-task-outcome.test.mts +117 -0
- package/src/pier-task-outcome.ts +240 -0
- package/src/pier-trial-controller.test.mts +412 -0
- package/src/pier-trial-controller.ts +858 -0
- package/src/pier-trial-supervisor.mjs +352 -0
- package/src/resolve-client.ts +25 -2
- package/src/run-benchmarks-cli.mts +72 -0
- package/src/run-benchmarks-report.ts +66 -0
- package/src/run-benchmarks.test.mts +231 -0
- package/src/run-benchmarks.ts +589 -0
- package/src/smoke-structural-rollout.mts +393 -0
- package/src/swe-bench-env.test.ts +207 -0
- package/src/swe-bench-env.ts +554 -0
- package/src/swe-jail.ts +293 -0
- package/src/swe-self-improve.mts +84 -0
- package/src/swe-structural-judge-policy.test.ts +117 -0
- package/src/swe-structural-judge-policy.ts +133 -0
- package/src/swe-structural-policy.test.ts +124 -0
- package/src/swe-structural-policy.ts +132 -0
- package/src/swe-structural-provenance.test.ts +93 -0
- package/src/swe-structural-provenance.ts +138 -0
- package/src/swe-structural.mts +1260 -0
- package/src/swe-temp.ts +14 -0
- package/src/tb-container-executor.mts +234 -0
- package/src/tb-container-executor.test.mts +99 -0
- package/src/tb-supervisor-sidecar.mts +222 -0
- package/src/trata-gepa.mts +1 -1
- package/steerers/eops-itsm-population.json +1 -0
- package/tb_agents/opencode_refine_agent.py +117 -0
- package/tb_agents/opencode_router_agent.py +406 -0
- package/tb_agents/opencode_supervisor_agent.py +239 -0
- package/tb_agents/script_agent.py +66 -0
|
@@ -0,0 +1,554 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SWE-bench Verified as an `AgenticSurface` — the PROPER, no-cheating way to run a coding agent on real
|
|
3
|
+
* GitHub bugs through the substrate (`runAgentic`/`runBenchmark`/`runStrategyEvolution` drive the loop;
|
|
4
|
+
* we only provide tools + a deployable score). The agent clones the repo at base_commit, explores +
|
|
5
|
+
* edits SOURCE via tools (never tests — path-jailed), and `score()` grades the resulting `git diff`
|
|
6
|
+
* with the OFFICIAL swebench Docker harness (apply patch → FAIL_TO_PASS + PASS_TO_PASS → resolved).
|
|
7
|
+
*
|
|
8
|
+
* No cheating by construction: the agent never sees the hidden tests or the gold patch (the adapter's
|
|
9
|
+
* prompt is the issue only); `edit_file` refuses test files; the score is a real test run, not a judge.
|
|
10
|
+
*
|
|
11
|
+
* CONTAMINATION CAVEAT: SWE-bench bugs are public GitHub fixes a frontier model may have MEMORIZED.
|
|
12
|
+
* A clean train→holdout split (disjoint instances) rules out adaptive-reuse, but NOT training-data
|
|
13
|
+
* memorization. Always report this; never claim a "clean" frontier number from this arena alone.
|
|
14
|
+
*/
|
|
15
|
+
import { execFile } from 'node:child_process'
|
|
16
|
+
import { cpSync, existsSync, lstatSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
|
|
17
|
+
import { join, sep } from 'node:path'
|
|
18
|
+
import { promisify } from 'node:util'
|
|
19
|
+
import type { AgenticSurface, AgenticTask, AgenticTool, ArtifactHandle, SurfaceScore } from '@tangle-network/agent-runtime/loops'
|
|
20
|
+
import { runVenvPython } from './benchmarks/_harness'
|
|
21
|
+
import { createSweBenchAdapter, type SweBenchAdapterOptions } from './benchmarks/swe-bench'
|
|
22
|
+
import type { BenchTask } from './benchmarks/types'
|
|
23
|
+
import { absoluteSweTempDir } from './swe-temp'
|
|
24
|
+
|
|
25
|
+
const exec = promisify(execFile)
|
|
26
|
+
export const isTestPath = (p: string) => /(^|\/)(tests?)\//.test(p) || /test_.*\.py$|_test\.py$|conftest\.py$/.test(p)
|
|
27
|
+
|
|
28
|
+
/** Copy a cached git checkout without rewriting repository-relative symlinks, then prove that the
|
|
29
|
+
* copy is byte-for-byte clean from git's perspective before a worker can observe it. */
|
|
30
|
+
export async function copyPristineGitCheckout(sourceDir: string, destinationDir: string): Promise<void> {
|
|
31
|
+
cpSync(sourceDir, destinationDir, { recursive: true, verbatimSymlinks: true })
|
|
32
|
+
let status: string
|
|
33
|
+
try {
|
|
34
|
+
const result = await exec('git', ['-C', destinationDir, 'status', '--porcelain'], {
|
|
35
|
+
timeout: 60_000,
|
|
36
|
+
maxBuffer: 20_000_000,
|
|
37
|
+
})
|
|
38
|
+
status = result.stdout
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw new Error(`could not verify cached checkout copy: ${(error as Error).message}`)
|
|
41
|
+
}
|
|
42
|
+
if (status.length > 0) {
|
|
43
|
+
throw new Error(`cached checkout copy is not pristine:\n${status.slice(0, 4_000)}`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The read/edit-only SWE agent system prompt — the ESTABLISHED baseline surface (glm-5.2 raw = 7/12,
|
|
49
|
+
* glm-4.6 = 3/12). Exported as the single source of truth so `tasks()` here and the improve() seed in
|
|
50
|
+
* swe-improve.mts stay byte-identical (the baseline denominator depends on this — no drift). Do NOT
|
|
51
|
+
* edit this constant to add run-tool guidance; that lives in SWE_SEED_PROMPT_WITH_RUN so the read/edit
|
|
52
|
+
* baseline arm is reproducible unchanged.
|
|
53
|
+
*/
|
|
54
|
+
export const SWE_SEED_PROMPT =
|
|
55
|
+
'You are a senior engineer fixing a real bug in the checked-out repository. Work PERSISTENTLY and do not ' +
|
|
56
|
+
'stop early: use list_files + read_file to explore BROADLY (read many candidate files — the bug is rarely in ' +
|
|
57
|
+
'the first file you open), trace the issue to its root cause, then fix it with edit_file. You MUST make at ' +
|
|
58
|
+
'least one edit_file call — never finish with prose alone or without attempting a fix. Make a MINIMAL surgical ' +
|
|
59
|
+
'change (a few lines, like a real PR), source only (test files are rejected). If an edit_file fails (old_string ' +
|
|
60
|
+
'not unique/found), read the file again and retry with exact text. Keep going until you have made your best fix.'
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The WITH-TOOLS arm: the baseline prompt PLUS the run-tool workflow (write a failing repro, edit, re-run
|
|
64
|
+
* until it passes). Only used when the environment is built with `enableRun`. Kept as a separate constant
|
|
65
|
+
* so enabling the run tool never mutates the read/edit-only baseline. Also reconciles the now-active
|
|
66
|
+
* confusion from loadTasks' userPrompt (which says the repo is "at /work, cd /work" — the container path
|
|
67
|
+
* from the sandbox path; here the run tool's cwd is the repo root and there is nothing to cd into).
|
|
68
|
+
*/
|
|
69
|
+
// NOTE (WAF): the router sits behind a Cloudflare WAF whose RCE ruleset 403s any request body that
|
|
70
|
+
// contains BACKTICK-wrapped command-like text (backticks are shell command-substitution syntax). The
|
|
71
|
+
// SAME commands without backticks pass. So this prompt and the run tool description deliberately write
|
|
72
|
+
// example commands WITHOUT backticks. Verified: backtick form -> 403, plain form -> 200.
|
|
73
|
+
export const SWE_SEED_PROMPT_WITH_RUN =
|
|
74
|
+
`${SWE_SEED_PROMPT} ` +
|
|
75
|
+
'You ALSO have a run tool: execute a shell command in the repo checkout (cwd is already the repo root — do ' +
|
|
76
|
+
'NOT cd, and ignore any instruction that says the repo is at /work). Use run to VERIFY your fix, not to ' +
|
|
77
|
+
'stall. DISCIPLINE: read a few files to locate the bug, then MAKE YOUR EDIT with edit_file EARLY — do not ' +
|
|
78
|
+
'spend many turns running commands before your first edit. After you edit, use run to check the fix: a ' +
|
|
79
|
+
'one-line python -c inline check, or python -m pytest on the nearest existing test file (use -k to select ' +
|
|
80
|
+
'the case, plus -rA and -p no:cacheprovider). If it still fails, read the output, refine the edit, and ' +
|
|
81
|
+
're-run — iterate EDIT then run until it passes, and run the nearest existing tests to catch regressions. ' +
|
|
82
|
+
'The network is DISABLED and the hidden grading tests are NOT present, so verify with local, network-free ' +
|
|
83
|
+
'checks. You MUST make at least one edit_file — a turn budget spent running with no edit is a failure.'
|
|
84
|
+
|
|
85
|
+
/** Hard wall-clock cap (seconds) for a single `run` command, enforced BOTH in-container (`timeout`) and
|
|
86
|
+
* on the host (execFile timeout + SIGKILL). Env-overridable for slow suites; default fail-closed at 120s. */
|
|
87
|
+
const RUN_TIMEOUT_S = Number(process.env.SWE_RUN_TIMEOUT ?? 120)
|
|
88
|
+
/** Combined stdout+stderr budget returned to the model: head + tail so the failure summary (which pytest
|
|
89
|
+
* prints at the tail) survives truncation. */
|
|
90
|
+
const RUN_OUTPUT_LIMIT = Number(process.env.SWE_RUN_OUTPUT_LIMIT ?? 10_000)
|
|
91
|
+
/** Exact run-tool settings stamped into structural-experiment receipts. */
|
|
92
|
+
export const SWE_RUN_TOOL_CONFIG = Object.freeze({
|
|
93
|
+
timeoutS: RUN_TIMEOUT_S,
|
|
94
|
+
outputLimit: RUN_OUTPUT_LIMIT,
|
|
95
|
+
})
|
|
96
|
+
/** Monotonic suffix so concurrent `run` calls get distinct container names (for reap-on-timeout). */
|
|
97
|
+
let runNameCounter = 0
|
|
98
|
+
|
|
99
|
+
/** Truncate a long string keeping a head and a (larger) tail, with a marker between — the tail carries the
|
|
100
|
+
* test-failure summary, so it gets the bigger share. Pure. */
|
|
101
|
+
function truncateHeadTail(s: string, limit: number): string {
|
|
102
|
+
if (s.length <= limit) return s
|
|
103
|
+
const head = Math.floor(limit * 0.35)
|
|
104
|
+
const tail = limit - head
|
|
105
|
+
return `${s.slice(0, head)}\n...[truncated ${s.length - limit} chars]...\n${s.slice(s.length - tail)}`
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Inline venv script: given the instance metadata row (argv[1] JSON), print the candidate Docker image
|
|
109
|
+
* tags swebench itself would use — the remote/namespaced tag (what a `swebench` pull caches, e.g.
|
|
110
|
+
* `swebench/sweb.eval.x86_64.psf_1776_requests-1142:latest`) FIRST, then the local-build tag
|
|
111
|
+
* (`sweb.eval.x86_64.psf__requests-1142:latest`). Delegated to `make_test_spec` so the naming stays
|
|
112
|
+
* correct across swebench versions instead of being hand-built. No network (it reads a dict, not the HF split). */
|
|
113
|
+
const IMAGE_KEY_SCRIPT = `
|
|
114
|
+
import json, sys
|
|
115
|
+
from swebench.harness.test_spec.test_spec import make_test_spec
|
|
116
|
+
row = json.loads(sys.argv[1])
|
|
117
|
+
candidates = []
|
|
118
|
+
for namespace in ("swebench", None):
|
|
119
|
+
try:
|
|
120
|
+
candidates.append({
|
|
121
|
+
"tag": make_test_spec(row, namespace=namespace).instance_image_key,
|
|
122
|
+
"namespace": "swebench" if namespace == "swebench" else "none",
|
|
123
|
+
})
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
print(json.dumps(candidates))
|
|
127
|
+
`
|
|
128
|
+
|
|
129
|
+
const SWEBENCH_VERSION_SCRIPT = `
|
|
130
|
+
import importlib.metadata
|
|
131
|
+
print(importlib.metadata.version("swebench"))
|
|
132
|
+
`
|
|
133
|
+
|
|
134
|
+
/** Installed official scorer package stamped into experiment execution receipts. */
|
|
135
|
+
export async function resolveSweBenchScorerVersion(): Promise<string> {
|
|
136
|
+
const output = await runVenvPython(SWEBENCH_VERSION_SCRIPT, [], 60_000)
|
|
137
|
+
const version = output.trim().split('\n').filter(Boolean).pop() ?? ''
|
|
138
|
+
if (!version) throw new Error('could not resolve installed swebench scorer version')
|
|
139
|
+
return version
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Cheap string pre-filter for an agent-supplied repo-relative path, applied before the path is
|
|
144
|
+
* joined to a workspace root: rejects absolute paths and any `..` segment, strips a leading `./`.
|
|
145
|
+
* Returns the cleaned relative path, or `null` if it must be refused. Pure and side-effect-free —
|
|
146
|
+
* `root` is unused here (the symlink-following boundary is the realpath jail, not this filter) but
|
|
147
|
+
* is taken so call sites read symmetrically with the realpath check.
|
|
148
|
+
*/
|
|
149
|
+
export const jailPath = (_root: string, p: string): string | null => {
|
|
150
|
+
if (p.startsWith('/') || p.includes('..')) return null
|
|
151
|
+
return p.replace(/^\.\//, '')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Containment predicate for the realpath jail: true iff `real` (an already-resolved absolute path)
|
|
156
|
+
* is `jailRoot` itself or lies strictly inside it. The `+ sep` guard stops a sibling like
|
|
157
|
+
* `/tmp/swe-x-evil` from matching the root `/tmp/swe-x`. Pure and side-effect-free.
|
|
158
|
+
*/
|
|
159
|
+
export const isInsideJail = (jailRoot: string, real: string): boolean => real === jailRoot || real.startsWith(jailRoot + sep)
|
|
160
|
+
|
|
161
|
+
export interface SweImageIdentity {
|
|
162
|
+
id: string
|
|
163
|
+
repoDigests: string[]
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface SweImageCandidate {
|
|
167
|
+
tag: string
|
|
168
|
+
namespace: 'swebench' | 'none'
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export type SweImageResolution =
|
|
172
|
+
| { ok: true; tag: string; namespace: SweImageCandidate['namespace']; identity: SweImageIdentity }
|
|
173
|
+
| { ok: false; reason: string }
|
|
174
|
+
|
|
175
|
+
export function parseSweImageCandidates(stdout: string): SweImageCandidate[] {
|
|
176
|
+
const parsed = JSON.parse(stdout) as Array<{ tag?: unknown; namespace?: unknown }>
|
|
177
|
+
return parsed.map((candidate, index) => {
|
|
178
|
+
if (
|
|
179
|
+
typeof candidate.tag !== 'string' ||
|
|
180
|
+
(candidate.namespace !== 'swebench' && candidate.namespace !== 'none')
|
|
181
|
+
) {
|
|
182
|
+
throw new Error(`invalid SWE image candidate at index ${index}`)
|
|
183
|
+
}
|
|
184
|
+
return { tag: candidate.tag, namespace: candidate.namespace }
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function firstAvailableSweImageCandidate(
|
|
189
|
+
candidates: readonly SweImageCandidate[],
|
|
190
|
+
identities: ReadonlyMap<string, SweImageIdentity>,
|
|
191
|
+
): { candidate: SweImageCandidate; identity: SweImageIdentity } | null {
|
|
192
|
+
for (const candidate of candidates) {
|
|
193
|
+
const identity = identities.get(candidate.tag)
|
|
194
|
+
if (identity) return { candidate, identity }
|
|
195
|
+
}
|
|
196
|
+
return null
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Parse the immutable image identity returned by `docker image inspect`. */
|
|
200
|
+
export function parseSweImageIdentity(stdout: string): SweImageIdentity {
|
|
201
|
+
const rows = JSON.parse(stdout) as Array<{ Id?: unknown; RepoDigests?: unknown }>
|
|
202
|
+
const row = rows[0]
|
|
203
|
+
const id = typeof row?.Id === 'string' ? row.Id : ''
|
|
204
|
+
if (!id) throw new Error('docker image inspect returned no image ID')
|
|
205
|
+
const repoDigests = Array.isArray(row?.RepoDigests)
|
|
206
|
+
? row.RepoDigests.filter((value): value is string => typeof value === 'string').sort()
|
|
207
|
+
: []
|
|
208
|
+
return { id, repoDigests }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
interface Ws {
|
|
212
|
+
dir: string
|
|
213
|
+
task: BenchTask
|
|
214
|
+
/** Memoized `run`-tool image resolution: the local Docker tag to exec in, or a fail-closed reason. */
|
|
215
|
+
image?: SweImageResolution
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Resolve the locally-present Docker image for an instance's METADATA row (no workspace needed).
|
|
219
|
+
* Asks swebench for the candidate tags, then picks the first that `docker image inspect` finds locally.
|
|
220
|
+
* Fail-closed: docker down / no cached image / resolver error → `{ ok:false }`. Exported so script-side
|
|
221
|
+
* calibrators (swe-repro-calibrate) hard-assert image presence through the SAME resolution the `run`
|
|
222
|
+
* tool uses instead of hand-building tags. */
|
|
223
|
+
export async function resolveImageForMetadata(
|
|
224
|
+
metadata: Record<string, unknown>,
|
|
225
|
+
): Promise<SweImageResolution> {
|
|
226
|
+
let candidates: SweImageCandidate[]
|
|
227
|
+
try {
|
|
228
|
+
const out = await runVenvPython(IMAGE_KEY_SCRIPT, [JSON.stringify(metadata)], 60_000)
|
|
229
|
+
const lastLine = out.trim().split('\n').filter(Boolean).pop() ?? '[]'
|
|
230
|
+
candidates = parseSweImageCandidates(lastLine)
|
|
231
|
+
} catch (e) {
|
|
232
|
+
return { ok: false, reason: `image-key resolution failed: ${(e as Error).message.slice(0, 160)}` }
|
|
233
|
+
}
|
|
234
|
+
if (!candidates.length) return { ok: false, reason: 'swebench produced no image key for this instance' }
|
|
235
|
+
const identities = new Map<string, SweImageIdentity>()
|
|
236
|
+
for (const { tag } of candidates) {
|
|
237
|
+
try {
|
|
238
|
+
const inspected = await exec('docker', ['image', 'inspect', tag], { timeout: 20_000 })
|
|
239
|
+
identities.set(tag, parseSweImageIdentity(inspected.stdout))
|
|
240
|
+
} catch (e) {
|
|
241
|
+
const m = (e as { stderr?: string }).stderr ?? (e as Error).message ?? ''
|
|
242
|
+
if (/Cannot connect to the Docker daemon|Is the docker daemon running/i.test(m)) {
|
|
243
|
+
return { ok: false, reason: 'docker daemon unavailable' }
|
|
244
|
+
}
|
|
245
|
+
// otherwise: this tag is just not cached locally — try the next candidate
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
const selected = firstAvailableSweImageCandidate(candidates, identities)
|
|
249
|
+
if (selected) {
|
|
250
|
+
return {
|
|
251
|
+
ok: true,
|
|
252
|
+
tag: selected.candidate.tag,
|
|
253
|
+
namespace: selected.candidate.namespace,
|
|
254
|
+
identity: selected.identity,
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return { ok: false, reason: `no cached image (tried: ${candidates.map(({ tag }) => tag).join(', ')})` }
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Per-workspace memoization of `resolveImageForMetadata` for the `run` tool (degrades to an
|
|
261
|
+
* `ERROR:` string so the agent falls back to read/edit-only instead of crashing). */
|
|
262
|
+
async function resolveInstanceImage(ws: Ws, expected?: SweImageIdentity): Promise<SweImageResolution> {
|
|
263
|
+
if (ws.image) {
|
|
264
|
+
if (ws.image.ok && expected && ws.image.identity.id !== expected.id) {
|
|
265
|
+
return { ok: false, reason: `image identity changed (${expected.id} -> ${ws.image.identity.id})` }
|
|
266
|
+
}
|
|
267
|
+
return ws.image
|
|
268
|
+
}
|
|
269
|
+
const resolved = await resolveImageForMetadata(ws.task.metadata ?? {})
|
|
270
|
+
if (resolved.ok && expected && resolved.identity.id !== expected.id) {
|
|
271
|
+
return (ws.image = {
|
|
272
|
+
ok: false,
|
|
273
|
+
reason: `image identity changed (${expected.id} -> ${resolved.identity.id})`,
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
return (ws.image = resolved)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Build the SWE-bench Environment + a DISJOINT-slice task supplier over the Verified split. The
|
|
280
|
+
* supplier keys tasks by dataset offset so `runStrategyEvolution`'s train [0,trainN) and holdout
|
|
281
|
+
* [trainN+off,…) never overlap. Verified is loaded once; instances carry their repo/base_commit. */
|
|
282
|
+
export async function createSweBenchEnvironment(
|
|
283
|
+
poolN = 80,
|
|
284
|
+
opts: {
|
|
285
|
+
ids?: readonly string[]
|
|
286
|
+
enableRun?: boolean
|
|
287
|
+
cloneCache?: boolean
|
|
288
|
+
expectedImageIdentities?: ReadonlyMap<string, SweImageIdentity>
|
|
289
|
+
adapterOptions?: SweBenchAdapterOptions
|
|
290
|
+
} = {},
|
|
291
|
+
): Promise<{
|
|
292
|
+
environment: AgenticSurface
|
|
293
|
+
tasks: (offset: number, n: number) => Promise<AgenticTask[]>
|
|
294
|
+
adapter: ReturnType<typeof createSweBenchAdapter>
|
|
295
|
+
}> {
|
|
296
|
+
const adapter = createSweBenchAdapter(opts.adapterOptions)
|
|
297
|
+
// WITH-TOOLS arm: expose the jailed `run` tool + use the run-aware seed prompt. Default OFF keeps
|
|
298
|
+
// the established read/edit-only baseline byte-identical.
|
|
299
|
+
const enableRun = opts.enableRun ?? false
|
|
300
|
+
const pool = opts.ids?.length
|
|
301
|
+
? await adapter.loadTasks({ ids: [...opts.ids], split: 'test' })
|
|
302
|
+
: await adapter.loadTasks({ limit: poolN, split: 'test' })
|
|
303
|
+
const byId = new Map(pool.map((t) => [t.id, t]))
|
|
304
|
+
// Each environment owns its workspace registry so concurrent environments don't share state.
|
|
305
|
+
const workspaces = new Map<string, Ws>()
|
|
306
|
+
// Opt-in per-instance clone cache: clone each instance from GitHub once, then copy the pristine
|
|
307
|
+
// checkout for later sessions. This removes a mid-run network dependency without sharing edits.
|
|
308
|
+
const cloneCache = opts.cloneCache ?? false
|
|
309
|
+
const pristine = new Map<string, Promise<string>>()
|
|
310
|
+
const clonedAt = async (md: Record<string, string>, dir: string): Promise<void> => {
|
|
311
|
+
await exec('git', ['clone', '--filter=blob:none', '--no-checkout', '--quiet', `https://github.com/${md.repo}.git`, dir], { timeout: 420_000 })
|
|
312
|
+
await exec('git', ['-C', dir, 'checkout', '--quiet', md.base_commit], { timeout: 300_000 })
|
|
313
|
+
}
|
|
314
|
+
const pristineClone = (id: string, md: Record<string, string>): Promise<string> => {
|
|
315
|
+
let pending = pristine.get(id)
|
|
316
|
+
if (!pending) {
|
|
317
|
+
pending = (async () => {
|
|
318
|
+
const dir = mkdtempSync(join(absoluteSweTempDir(), 'swe-cache-'))
|
|
319
|
+
try {
|
|
320
|
+
await clonedAt(md, dir)
|
|
321
|
+
return dir
|
|
322
|
+
} catch (error) {
|
|
323
|
+
rmSync(dir, { recursive: true, force: true })
|
|
324
|
+
pristine.delete(id)
|
|
325
|
+
throw error
|
|
326
|
+
}
|
|
327
|
+
})()
|
|
328
|
+
pristine.set(id, pending)
|
|
329
|
+
}
|
|
330
|
+
return pending
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const environment: AgenticSurface = {
|
|
334
|
+
name: 'swe-bench-verified',
|
|
335
|
+
async open(task) {
|
|
336
|
+
const bt = byId.get(task.id)
|
|
337
|
+
if (!bt) throw new Error(`swe-bench-env: unknown task ${task.id}`)
|
|
338
|
+
const md = bt.metadata as Record<string, string>
|
|
339
|
+
const dir = mkdtempSync(join(absoluteSweTempDir(), 'swe-'))
|
|
340
|
+
try {
|
|
341
|
+
if (cloneCache) await copyPristineGitCheckout(await pristineClone(task.id, md), dir)
|
|
342
|
+
else await clonedAt(md, dir)
|
|
343
|
+
const handle: ArtifactHandle = { id: dir, surface: 'swe-bench-verified' }
|
|
344
|
+
workspaces.set(dir, { dir, task: bt })
|
|
345
|
+
return handle
|
|
346
|
+
} catch (error) {
|
|
347
|
+
rmSync(dir, { recursive: true, force: true })
|
|
348
|
+
throw error
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
async tools() {
|
|
352
|
+
const tools: AgenticTool[] = [
|
|
353
|
+
{ type: 'function', function: { name: 'list_files', description: 'List source files under a repo subdirectory (recursive, bounded). "" = repo root.', parameters: { type: 'object', properties: { dir: { type: 'string' } }, required: ['dir'] } } },
|
|
354
|
+
{ type: 'function', function: { name: 'read_file', description: 'Read a repo file by path.', parameters: { type: 'object', properties: { path: { type: 'string' } }, required: ['path'] } } },
|
|
355
|
+
{ type: 'function', function: { name: 'edit_file', description: 'Surgical fix: replace the EXACT old_string (must occur once — copy whitespace precisely) with new_string in a SOURCE file. Minimal changes, never whole-file rewrites. Test files are rejected.', parameters: { type: 'object', properties: { path: { type: 'string' }, old_string: { type: 'string' }, new_string: { type: 'string' } }, required: ['path', 'old_string', 'new_string'] } } },
|
|
356
|
+
]
|
|
357
|
+
if (enableRun) {
|
|
358
|
+
tools.push({
|
|
359
|
+
type: 'function',
|
|
360
|
+
function: {
|
|
361
|
+
name: 'run',
|
|
362
|
+
description:
|
|
363
|
+
'Run a shell command in the repo checkout to REPRODUCE the bug and VERIFY your fix. cwd is the ' +
|
|
364
|
+
'repo root (do NOT cd). Use a one-line python -c inline check for a quick offline reproduction, ' +
|
|
365
|
+
'or python -m pytest on an existing test file (add -k to select a case, plus -rA and ' +
|
|
366
|
+
'-p no:cacheprovider) to run tests near your change. Returns the exit code then the combined ' +
|
|
367
|
+
'stdout+stderr. NETWORK IS DISABLED and hidden grading tests are absent. A non-zero exit is a ' +
|
|
368
|
+
'normal failing-test signal, not a tool error.',
|
|
369
|
+
parameters: { type: 'object', properties: { cmd: { type: 'string' } }, required: ['cmd'] },
|
|
370
|
+
},
|
|
371
|
+
})
|
|
372
|
+
}
|
|
373
|
+
return tools
|
|
374
|
+
},
|
|
375
|
+
async call(handle, name, args) {
|
|
376
|
+
const ws = workspaces.get(handle.id)
|
|
377
|
+
if (!ws) return 'ERROR: workspace closed'
|
|
378
|
+
// Cheap pre-filter: reject absolute paths and `..` traversal, strip a leading `./`. The real
|
|
379
|
+
// boundary is the realpath jail check below (resolveInJail) — `safe` only normalizes the string
|
|
380
|
+
// form. `ws.dir` is passed for signature symmetry; the filter itself is root-independent.
|
|
381
|
+
const safe = (p: string): string | null => jailPath(ws.dir, p)
|
|
382
|
+
// Resolve `relPath` to an absolute path and assert it stays inside the workspace AFTER following
|
|
383
|
+
// symlinks (a repo symlink targeting /etc/passwd would otherwise escape the string-only jail).
|
|
384
|
+
// The target must exist (both callers read it first); a missing path throws and the caller
|
|
385
|
+
// surfaces the error message, matching the previous read-then-fail behavior.
|
|
386
|
+
const jailRoot = realpathSync(ws.dir)
|
|
387
|
+
const resolveInJail = (relPath: string): string | null => {
|
|
388
|
+
const real = realpathSync(join(ws.dir, relPath))
|
|
389
|
+
return isInsideJail(jailRoot, real) ? real : null
|
|
390
|
+
}
|
|
391
|
+
if (name === 'list_files') {
|
|
392
|
+
const sub = safe(String(args.dir ?? '')) ?? ''
|
|
393
|
+
const root = join(ws.dir, sub)
|
|
394
|
+
if (!existsSync(root)) return `(no such path: ${sub})`
|
|
395
|
+
const out: string[] = []
|
|
396
|
+
const walk = (d: string, depth: number) => {
|
|
397
|
+
if (depth > 2 || out.length > 240) return
|
|
398
|
+
let entries: string[] = []
|
|
399
|
+
try {
|
|
400
|
+
entries = readdirSync(d)
|
|
401
|
+
} catch {
|
|
402
|
+
return
|
|
403
|
+
}
|
|
404
|
+
for (const e of entries) {
|
|
405
|
+
if (e.startsWith('.') || e === 'node_modules' || e === '__pycache__') continue
|
|
406
|
+
const p = join(d, e)
|
|
407
|
+
let isDir = false
|
|
408
|
+
try {
|
|
409
|
+
isDir = lstatSync(p).isDirectory()
|
|
410
|
+
} catch {
|
|
411
|
+
continue
|
|
412
|
+
}
|
|
413
|
+
out.push(p.slice(ws.dir.length + 1) + (isDir ? '/' : ''))
|
|
414
|
+
if (isDir) walk(p, depth + 1)
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
walk(root, 0)
|
|
418
|
+
return out.slice(0, 240).join('\n') || '(empty)'
|
|
419
|
+
}
|
|
420
|
+
if (name === 'read_file') {
|
|
421
|
+
const p = safe(String(args.path ?? ''))
|
|
422
|
+
if (!p) return 'ERROR: invalid path'
|
|
423
|
+
let real: string | null
|
|
424
|
+
try {
|
|
425
|
+
real = resolveInJail(p)
|
|
426
|
+
} catch (e) {
|
|
427
|
+
return `(error: ${(e as Error).message})`
|
|
428
|
+
}
|
|
429
|
+
if (!real) return `ERROR: path ${p} escapes the workspace`
|
|
430
|
+
try {
|
|
431
|
+
const c = readFileSync(real, 'utf8')
|
|
432
|
+
return c.length > 24_000 ? `${c.slice(0, 24_000)}\n...[truncated]` : c
|
|
433
|
+
} catch (e) {
|
|
434
|
+
return `(error: ${(e as Error).message})`
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (name === 'edit_file') {
|
|
438
|
+
const p = safe(String(args.path ?? ''))
|
|
439
|
+
if (!p) return 'ERROR: invalid path'
|
|
440
|
+
if (isTestPath(p)) return 'REJECTED: editing test files is forbidden (the evaluation runs hidden tests).'
|
|
441
|
+
const oldStr = String(args.old_string ?? '')
|
|
442
|
+
const newStr = String(args.new_string ?? '')
|
|
443
|
+
let real: string | null
|
|
444
|
+
try {
|
|
445
|
+
real = resolveInJail(p)
|
|
446
|
+
} catch (e) {
|
|
447
|
+
return `(cannot read ${p}: ${(e as Error).message})`
|
|
448
|
+
}
|
|
449
|
+
if (!real) return `ERROR: path ${p} escapes the workspace`
|
|
450
|
+
let content: string
|
|
451
|
+
try {
|
|
452
|
+
content = readFileSync(real, 'utf8')
|
|
453
|
+
} catch (e) {
|
|
454
|
+
return `(cannot read ${p}: ${(e as Error).message})`
|
|
455
|
+
}
|
|
456
|
+
if (!oldStr) return 'ERROR: old_string is empty.'
|
|
457
|
+
const count = content.split(oldStr).length - 1
|
|
458
|
+
if (count === 0) return `ERROR: old_string not found in ${p}. read_file it and copy EXACT text.`
|
|
459
|
+
if (count > 1) return `ERROR: old_string appears ${count}× in ${p} — add surrounding context to make it unique.`
|
|
460
|
+
writeFileSync(real, content.replace(oldStr, newStr))
|
|
461
|
+
return `edited ${p}: replaced 1 occurrence`
|
|
462
|
+
}
|
|
463
|
+
if (enableRun && name === 'run') {
|
|
464
|
+
const cmd = String(args.cmd ?? '').trim()
|
|
465
|
+
if (!cmd) return 'ERROR: run requires a non-empty cmd'
|
|
466
|
+
const img = await resolveInstanceImage(ws, opts.expectedImageIdentities?.get(ws.task.id))
|
|
467
|
+
if (!img.ok) return `ERROR: run unavailable (${img.reason}) — continue with read_file/edit_file only`
|
|
468
|
+
const T = RUN_TIMEOUT_S
|
|
469
|
+
const containerName = `swe-run-${process.pid}-${Date.now()}-${runNameCounter++}`
|
|
470
|
+
// The whole toolchain is interpreted ONLY by the container's bash — the host never sees a shell
|
|
471
|
+
// (execFile + args array). The agent cmd rides in as $SWE_CMD (an env var, no host-quoting hazard).
|
|
472
|
+
// `{ …; } 2>&1` merges stderr into stdout preserving order; the group's exit = the last command's.
|
|
473
|
+
// conda.sh only DEFINES `conda` (unlike bin/activate, which would consume our positional args).
|
|
474
|
+
const script =
|
|
475
|
+
'{ source /opt/miniconda3/etc/profile.d/conda.sh && conda activate testbed && cd /testbed && ' +
|
|
476
|
+
'timeout -s KILL "$SWE_T"s bash -c "$SWE_CMD"; } 2>&1'
|
|
477
|
+
// READ-ONLY mount ⇒ the container physically cannot mutate the graded checkout (`run` is purely
|
|
478
|
+
// observational; the only writer stays edit_file). `--network none` + `--rm` + the dual timeout.
|
|
479
|
+
const dockerArgs = [
|
|
480
|
+
'run', '--rm', '--name', containerName, '--network', 'none',
|
|
481
|
+
'-v', `${ws.dir}:/testbed:ro`, '-w', '/testbed',
|
|
482
|
+
'-e', 'PYTHONDONTWRITEBYTECODE=1', '-e', `SWE_T=${T}`, '-e', `SWE_CMD=${cmd}`,
|
|
483
|
+
img.identity.id, 'bash', '-lc', script,
|
|
484
|
+
]
|
|
485
|
+
let code = 0
|
|
486
|
+
let out = ''
|
|
487
|
+
try {
|
|
488
|
+
const r = await exec('docker', dockerArgs, { timeout: (T + 20) * 1000, killSignal: 'SIGKILL', maxBuffer: 20_000_000 })
|
|
489
|
+
out = r.stdout
|
|
490
|
+
} catch (e) {
|
|
491
|
+
const err = e as { code?: number; killed?: boolean; stdout?: string; message?: string }
|
|
492
|
+
if (typeof err.code === 'number') {
|
|
493
|
+
code = err.code
|
|
494
|
+
out = err.stdout ?? ''
|
|
495
|
+
} else {
|
|
496
|
+
// Host-level kill (execFile timeout) or docker failed to spawn: reap any orphaned container.
|
|
497
|
+
exec('docker', ['rm', '-f', containerName], { timeout: 20_000 }).catch(() => {})
|
|
498
|
+
if (err.killed) {
|
|
499
|
+
code = 124
|
|
500
|
+
out = `${err.stdout ?? ''}\n[host timeout: docker run exceeded ${T + 20}s and was killed]`
|
|
501
|
+
} else {
|
|
502
|
+
return `ERROR: run failed to execute (${String(err.message ?? e).slice(0, 200)})`
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
// Docker infra failures (container couldn't start / daemon vanished) are tool-misuse, not a test
|
|
507
|
+
// result → keep the ERROR: prefix so they don't pollute the failing-test signal.
|
|
508
|
+
if (code === 125 || /Cannot connect to the Docker daemon/i.test(out)) {
|
|
509
|
+
return `ERROR: run unavailable (docker: ${truncateHeadTail(out, 400)})`
|
|
510
|
+
}
|
|
511
|
+
const note = code === 124 || code === 137 ? `\n[command hit the ${T}s time/kill limit]` : ''
|
|
512
|
+
return `exit=${code}${note}\n${truncateHeadTail(out, RUN_OUTPUT_LIMIT)}`
|
|
513
|
+
}
|
|
514
|
+
return `ERROR: unknown tool ${name}`
|
|
515
|
+
},
|
|
516
|
+
async score(_task, handle): Promise<SurfaceScore> {
|
|
517
|
+
const ws = workspaces.get(handle.id)
|
|
518
|
+
if (!ws) return { passes: 0, total: 1, errored: 1 }
|
|
519
|
+
let patch = ''
|
|
520
|
+
try {
|
|
521
|
+
const r = await exec('git', ['-C', ws.dir, 'diff'], { maxBuffer: 20_000_000, timeout: 60_000 })
|
|
522
|
+
patch = r.stdout
|
|
523
|
+
} catch {
|
|
524
|
+
patch = ''
|
|
525
|
+
}
|
|
526
|
+
if (!patch.trim()) return { passes: 0, total: 1, errored: 0 }
|
|
527
|
+
try {
|
|
528
|
+
const s = await adapter.judge(ws.task, patch)
|
|
529
|
+
return { passes: s.resolved ? 1 : 0, total: 1, errored: 0 }
|
|
530
|
+
} catch {
|
|
531
|
+
return { passes: 0, total: 1, errored: 1 }
|
|
532
|
+
}
|
|
533
|
+
},
|
|
534
|
+
async close(handle) {
|
|
535
|
+
const ws = workspaces.get(handle.id)
|
|
536
|
+
if (!ws) return
|
|
537
|
+
workspaces.delete(handle.id)
|
|
538
|
+
rmSync(ws.dir, { recursive: true, force: true })
|
|
539
|
+
},
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
const tasks = async (offset: number, n: number): Promise<AgenticTask[]> => {
|
|
543
|
+
const slice = pool.slice(offset, offset + n)
|
|
544
|
+
if (slice.length < n) throw new Error(`swe-bench-env: pool exhausted at offset ${offset} (need ${n}, have ${slice.length}; raise poolN)`)
|
|
545
|
+
return slice.map((bt) => ({
|
|
546
|
+
id: bt.id,
|
|
547
|
+
systemPrompt: enableRun ? SWE_SEED_PROMPT_WITH_RUN : SWE_SEED_PROMPT,
|
|
548
|
+
userPrompt: bt.prompt,
|
|
549
|
+
meta: { instanceId: bt.id },
|
|
550
|
+
}))
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
return { environment, tasks, adapter }
|
|
554
|
+
}
|