@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.
Files changed (317) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/HARNESS.md +302 -0
  3. package/README.md +39 -2
  4. package/dist/adapters.d.ts +15 -0
  5. package/dist/adapters.js +43 -0
  6. package/dist/adapters.js.map +1 -0
  7. package/dist/benchmarks/_harness.d.ts +125 -0
  8. package/dist/benchmarks/_harness.js +33 -0
  9. package/dist/benchmarks/_harness.js.map +1 -0
  10. package/dist/benchmarks/aec-bench.d.ts +27 -0
  11. package/dist/benchmarks/aec-bench.js +8 -0
  12. package/dist/benchmarks/aec-bench.js.map +1 -0
  13. package/dist/benchmarks/agentbench.d.ts +16 -0
  14. package/dist/benchmarks/agentbench.js +10 -0
  15. package/dist/benchmarks/agentbench.js.map +1 -0
  16. package/dist/benchmarks/appworld.d.ts +37 -0
  17. package/dist/benchmarks/appworld.js +14 -0
  18. package/dist/benchmarks/appworld.js.map +1 -0
  19. package/dist/benchmarks/bfcl.d.ts +18 -0
  20. package/dist/benchmarks/bfcl.js +10 -0
  21. package/dist/benchmarks/bfcl.js.map +1 -0
  22. package/dist/benchmarks/cad-design.d.ts +45 -0
  23. package/dist/benchmarks/cad-design.js +7 -0
  24. package/dist/benchmarks/cad-design.js.map +1 -0
  25. package/dist/benchmarks/cadbench.d.ts +19 -0
  26. package/dist/benchmarks/cadbench.js +8 -0
  27. package/dist/benchmarks/cadbench.js.map +1 -0
  28. package/dist/benchmarks/cadgenbench.d.ts +22 -0
  29. package/dist/benchmarks/cadgenbench.js +8 -0
  30. package/dist/benchmarks/cadgenbench.js.map +1 -0
  31. package/dist/benchmarks/commit0.d.ts +31 -0
  32. package/dist/benchmarks/commit0.js +10 -0
  33. package/dist/benchmarks/commit0.js.map +1 -0
  34. package/dist/benchmarks/crag.d.ts +14 -0
  35. package/dist/benchmarks/crag.js +9 -0
  36. package/dist/benchmarks/crag.js.map +1 -0
  37. package/dist/benchmarks/dabstep.d.ts +18 -0
  38. package/dist/benchmarks/dabstep.js +10 -0
  39. package/dist/benchmarks/dabstep.js.map +1 -0
  40. package/dist/benchmarks/enterpriseops-gym.d.ts +38 -0
  41. package/dist/benchmarks/enterpriseops-gym.js +10 -0
  42. package/dist/benchmarks/enterpriseops-gym.js.map +1 -0
  43. package/dist/benchmarks/finresearchbench.d.ts +15 -0
  44. package/dist/benchmarks/finresearchbench.js +8 -0
  45. package/dist/benchmarks/finresearchbench.js.map +1 -0
  46. package/dist/benchmarks/finsearchcomp.d.ts +49 -0
  47. package/dist/benchmarks/finsearchcomp.js +7 -0
  48. package/dist/benchmarks/finsearchcomp.js.map +1 -0
  49. package/dist/benchmarks/frames.d.ts +59 -0
  50. package/dist/benchmarks/frames.js +13 -0
  51. package/dist/benchmarks/frames.js.map +1 -0
  52. package/dist/benchmarks/hotpotqa.d.ts +48 -0
  53. package/dist/benchmarks/hotpotqa.js +15 -0
  54. package/dist/benchmarks/hotpotqa.js.map +1 -0
  55. package/dist/benchmarks/humaneval.d.ts +53 -0
  56. package/dist/benchmarks/humaneval.js +15 -0
  57. package/dist/benchmarks/humaneval.js.map +1 -0
  58. package/dist/benchmarks/mind2web.d.ts +41 -0
  59. package/dist/benchmarks/mind2web.js +9 -0
  60. package/dist/benchmarks/mind2web.js.map +1 -0
  61. package/dist/benchmarks/nomiracl.d.ts +15 -0
  62. package/dist/benchmarks/nomiracl.js +9 -0
  63. package/dist/benchmarks/nomiracl.js.map +1 -0
  64. package/dist/benchmarks/open-rag-bench.d.ts +14 -0
  65. package/dist/benchmarks/open-rag-bench.js +9 -0
  66. package/dist/benchmarks/open-rag-bench.js.map +1 -0
  67. package/dist/benchmarks/programbench.d.ts +38 -0
  68. package/dist/benchmarks/programbench.js +10 -0
  69. package/dist/benchmarks/programbench.js.map +1 -0
  70. package/dist/benchmarks/rag-shared.d.ts +42 -0
  71. package/dist/benchmarks/rag-shared.js +39 -0
  72. package/dist/benchmarks/rag-shared.js.map +1 -0
  73. package/dist/benchmarks/ragbench.d.ts +16 -0
  74. package/dist/benchmarks/ragbench.js +9 -0
  75. package/dist/benchmarks/ragbench.js.map +1 -0
  76. package/dist/benchmarks/simpleqa.d.ts +64 -0
  77. package/dist/benchmarks/simpleqa.js +11 -0
  78. package/dist/benchmarks/simpleqa.js.map +1 -0
  79. package/dist/benchmarks/swe-bench.d.ts +56 -0
  80. package/dist/benchmarks/swe-bench.js +14 -0
  81. package/dist/benchmarks/swe-bench.js.map +1 -0
  82. package/dist/benchmarks/t2-ragbench.d.ts +14 -0
  83. package/dist/benchmarks/t2-ragbench.js +9 -0
  84. package/dist/benchmarks/t2-ragbench.js.map +1 -0
  85. package/dist/benchmarks/tau-bench-shared.d.ts +26 -0
  86. package/dist/benchmarks/tau-bench-shared.js +10 -0
  87. package/dist/benchmarks/tau-bench-shared.js.map +1 -0
  88. package/dist/benchmarks/tau2-bench.d.ts +7 -0
  89. package/dist/benchmarks/tau2-bench.js +11 -0
  90. package/dist/benchmarks/tau2-bench.js.map +1 -0
  91. package/dist/benchmarks/tau3-banking.d.ts +15 -0
  92. package/dist/benchmarks/tau3-banking.js +9 -0
  93. package/dist/benchmarks/tau3-banking.js.map +1 -0
  94. package/dist/benchmarks/terminal-bench.d.ts +24 -0
  95. package/dist/benchmarks/terminal-bench.js +8 -0
  96. package/dist/benchmarks/terminal-bench.js.map +1 -0
  97. package/dist/benchmarks/toollm.d.ts +16 -0
  98. package/dist/benchmarks/toollm.js +10 -0
  99. package/dist/benchmarks/toollm.js.map +1 -0
  100. package/dist/benchmarks/trata-hedge.d.ts +32 -0
  101. package/dist/benchmarks/trata-hedge.js +7 -0
  102. package/dist/benchmarks/trata-hedge.js.map +1 -0
  103. package/dist/benchmarks/types.d.ts +107 -0
  104. package/dist/benchmarks/types.js +1 -0
  105. package/dist/benchmarks/types.js.map +1 -0
  106. package/dist/benchmarks/webarena-verified.d.ts +16 -0
  107. package/dist/benchmarks/webarena-verified.js +10 -0
  108. package/dist/benchmarks/webarena-verified.js.map +1 -0
  109. package/dist/chunk-2PVVP7GN.js +197 -0
  110. package/dist/chunk-2PVVP7GN.js.map +1 -0
  111. package/dist/chunk-2XU6OGEN.js +170 -0
  112. package/dist/chunk-2XU6OGEN.js.map +1 -0
  113. package/dist/chunk-53UPUNBZ.js +325 -0
  114. package/dist/chunk-53UPUNBZ.js.map +1 -0
  115. package/dist/chunk-5SBJCB6W.js +144 -0
  116. package/dist/chunk-5SBJCB6W.js.map +1 -0
  117. package/dist/chunk-7WSD27QQ.js +118 -0
  118. package/dist/chunk-7WSD27QQ.js.map +1 -0
  119. package/dist/chunk-C7T7WEK2.js +103 -0
  120. package/dist/chunk-C7T7WEK2.js.map +1 -0
  121. package/dist/chunk-CKUVRZ2T.js +251 -0
  122. package/dist/chunk-CKUVRZ2T.js.map +1 -0
  123. package/dist/chunk-HBSWHQNJ.js +30 -0
  124. package/dist/chunk-HBSWHQNJ.js.map +1 -0
  125. package/dist/chunk-HHXFIHXC.js +116 -0
  126. package/dist/chunk-HHXFIHXC.js.map +1 -0
  127. package/dist/chunk-IFAV6KEM.js +276 -0
  128. package/dist/chunk-IFAV6KEM.js.map +1 -0
  129. package/dist/chunk-INNOYXCP.js +387 -0
  130. package/dist/chunk-INNOYXCP.js.map +1 -0
  131. package/dist/chunk-J3KDJNX2.js +182 -0
  132. package/dist/chunk-J3KDJNX2.js.map +1 -0
  133. package/dist/chunk-JRWWGMK7.js +148 -0
  134. package/dist/chunk-JRWWGMK7.js.map +1 -0
  135. package/dist/chunk-JTHWEDEW.js +32 -0
  136. package/dist/chunk-JTHWEDEW.js.map +1 -0
  137. package/dist/chunk-KDIKRJGB.js +120 -0
  138. package/dist/chunk-KDIKRJGB.js.map +1 -0
  139. package/dist/chunk-LRRD7NAG.js +301 -0
  140. package/dist/chunk-LRRD7NAG.js.map +1 -0
  141. package/dist/chunk-ODT47UAY.js +221 -0
  142. package/dist/chunk-ODT47UAY.js.map +1 -0
  143. package/dist/chunk-PA2ZKHJC.js +230 -0
  144. package/dist/chunk-PA2ZKHJC.js.map +1 -0
  145. package/dist/chunk-PPYSEKFM.js +182 -0
  146. package/dist/chunk-PPYSEKFM.js.map +1 -0
  147. package/dist/chunk-PUIRNYI7.js +189 -0
  148. package/dist/chunk-PUIRNYI7.js.map +1 -0
  149. package/dist/chunk-R36V2VP7.js +169 -0
  150. package/dist/chunk-R36V2VP7.js.map +1 -0
  151. package/dist/chunk-R67DFVLO.js +142 -0
  152. package/dist/chunk-R67DFVLO.js.map +1 -0
  153. package/dist/chunk-SEVJPLZC.js +260 -0
  154. package/dist/chunk-SEVJPLZC.js.map +1 -0
  155. package/dist/chunk-SYDW647C.js +318 -0
  156. package/dist/chunk-SYDW647C.js.map +1 -0
  157. package/dist/chunk-TBKU5XQI.js +228 -0
  158. package/dist/chunk-TBKU5XQI.js.map +1 -0
  159. package/dist/chunk-TSWPNOYM.js +147 -0
  160. package/dist/chunk-TSWPNOYM.js.map +1 -0
  161. package/dist/chunk-UAIOHCUK.js +27 -0
  162. package/dist/chunk-UAIOHCUK.js.map +1 -0
  163. package/dist/chunk-UPAMRDX4.js +233 -0
  164. package/dist/chunk-UPAMRDX4.js.map +1 -0
  165. package/dist/chunk-VQRS7VUC.js +342 -0
  166. package/dist/chunk-VQRS7VUC.js.map +1 -0
  167. package/dist/chunk-X3BTXCJ4.js +262 -0
  168. package/dist/chunk-X3BTXCJ4.js.map +1 -0
  169. package/dist/chunk-X5YKXC6V.js +211 -0
  170. package/dist/chunk-X5YKXC6V.js.map +1 -0
  171. package/dist/chunk-Y6O2OCUO.js +130 -0
  172. package/dist/chunk-Y6O2OCUO.js.map +1 -0
  173. package/dist/chunk-YCGY7UIZ.js +208 -0
  174. package/dist/chunk-YCGY7UIZ.js.map +1 -0
  175. package/dist/chunk-Z7ML6L77.js +162 -0
  176. package/dist/chunk-Z7ML6L77.js.map +1 -0
  177. package/dist/chunk-ZEWMTR5M.js +136 -0
  178. package/dist/chunk-ZEWMTR5M.js.map +1 -0
  179. package/dist/index.d.ts +355 -0
  180. package/dist/index.js +1908 -0
  181. package/dist/index.js.map +1 -0
  182. package/fixtures/aec-bench.json +18 -0
  183. package/fixtures/agentbench-dbbench.json +22 -0
  184. package/fixtures/bfcl.json +45 -0
  185. package/fixtures/commit0.json +72 -0
  186. package/fixtures/crag.json +10 -0
  187. package/fixtures/dabstep.json +22 -0
  188. package/fixtures/enterpriseops-gym.json +103 -0
  189. package/fixtures/finresearchbench.json +21 -0
  190. package/fixtures/finsearchcomp.json +66 -0
  191. package/fixtures/frames.json +26 -0
  192. package/fixtures/hotpotqa.json +182 -0
  193. package/fixtures/nomiracl.json +26 -0
  194. package/fixtures/open-rag-bench.json +16 -0
  195. package/fixtures/pier-agent/no-model-task/environment/Dockerfile +16 -0
  196. package/fixtures/pier-agent/no-model-task/environment/seed/src/status.txt +1 -0
  197. package/fixtures/pier-agent/no-model-task/instruction.md +6 -0
  198. package/fixtures/pier-agent/no-model-task/pre_artifacts.sh +6 -0
  199. package/fixtures/pier-agent/no-model-task/task.toml +35 -0
  200. package/fixtures/pier-agent/no-model-task/tests/Dockerfile +17 -0
  201. package/fixtures/pier-agent/no-model-task/tests/seed/src/status.txt +1 -0
  202. package/fixtures/pier-agent/no-model-task/tests/test.sh +19 -0
  203. package/fixtures/programbench.json +17 -0
  204. package/fixtures/ragbench.json +21 -0
  205. package/fixtures/simpleqa.json +121 -0
  206. package/fixtures/t2-ragbench.json +13 -0
  207. package/fixtures/tau2-bench.json +16 -0
  208. package/fixtures/tau3-banking.json +16 -0
  209. package/fixtures/toollm.json +28 -0
  210. package/fixtures/webarena-verified.json +20 -0
  211. package/package.json +60 -20
  212. package/pier_agents/__init__.py +18 -0
  213. package/pier_agents/candidate_contract.py +755 -0
  214. package/pier_agents/process_boundary.py +321 -0
  215. package/pier_agents/tangle_candidate.py +907 -0
  216. package/pier_agents/workspace_boundary.py +368 -0
  217. package/scripts/appworld_driver.py +359 -0
  218. package/scripts/cadbench_prepare.py +22 -0
  219. package/scripts/cadgenbench_hard_parts.py +48 -0
  220. package/scripts/clbench_codebase_judge.py +73 -0
  221. package/scripts/commit0_judge.py +170 -0
  222. package/scripts/dabstep_judge.py +42 -0
  223. package/scripts/enterpriseops_gym_judge.py +281 -0
  224. package/scripts/programbench_judge.py +120 -0
  225. package/scripts/render-gate-chart.mjs +176 -0
  226. package/scripts/run-package-tests.mjs +56 -0
  227. package/scripts/terminate-pier-trial.mts +66 -0
  228. package/scripts/trata-hedge/README.md +56 -0
  229. package/scripts/trata-hedge/run.sh +60 -0
  230. package/scripts/trata-hedge/solve.py +83 -0
  231. package/scripts/verify-packed-consumer.mjs +235 -0
  232. package/scripts/verify-pier-agent.mts +715 -0
  233. package/scripts/verify-pier-pair.mts +74 -0
  234. package/scripts/verify-pier-recovery.mts +139 -0
  235. package/src/adapters.ts +26 -0
  236. package/src/benchmarks/_harness.test.mts +178 -0
  237. package/src/benchmarks/_harness.ts +239 -16
  238. package/src/benchmarks/agentbench.ts +163 -0
  239. package/src/benchmarks/appworld.test.mts +15 -9
  240. package/src/benchmarks/bfcl.ts +346 -0
  241. package/src/benchmarks/crag.ts +137 -0
  242. package/src/benchmarks/dabstep.test.mts +70 -0
  243. package/src/benchmarks/dabstep.ts +212 -0
  244. package/src/benchmarks/external-adapters.test.mts +150 -0
  245. package/src/benchmarks/finresearchbench.ts +269 -0
  246. package/src/benchmarks/humaneval.ts +20 -8
  247. package/src/benchmarks/nomiracl.ts +180 -0
  248. package/src/benchmarks/open-rag-bench.ts +153 -0
  249. package/src/benchmarks/rag-benchmarks.test.mts +138 -0
  250. package/src/benchmarks/rag-shared.ts +327 -0
  251. package/src/benchmarks/ragbench.ts +171 -0
  252. package/src/benchmarks/swe-bench.test.mts +61 -0
  253. package/src/benchmarks/swe-bench.ts +201 -19
  254. package/src/benchmarks/t2-ragbench.ts +166 -0
  255. package/src/benchmarks/tau-bench-shared.ts +214 -0
  256. package/src/benchmarks/tau2-bench.ts +30 -0
  257. package/src/benchmarks/tau3-banking.ts +29 -0
  258. package/src/benchmarks/terminal-bench.test.mts +33 -0
  259. package/src/benchmarks/terminal-bench.ts +23 -8
  260. package/src/benchmarks/toollm.ts +254 -0
  261. package/src/benchmarks/types.ts +42 -0
  262. package/src/benchmarks/webarena-verified.ts +200 -0
  263. package/src/commit0-prereqs.sh +0 -0
  264. package/src/coordination-mcp-container-reach.mts +181 -0
  265. package/src/decoder-live.mts +1 -1
  266. package/src/examples/README.md +103 -39
  267. package/src/examples/benchmark-matrix.mts +101 -0
  268. package/src/examples/lean-proof-gate.README.md +77 -0
  269. package/src/examples/lean-proof-gate.mts +162 -0
  270. package/src/examples/lean-verify.ts +95 -0
  271. package/src/examples/lean.Dockerfile +12 -0
  272. package/src/examples/math-demo.mts +9 -7
  273. package/src/examples/strategy-demo.mts +10 -12
  274. package/src/gate.ts +3 -2
  275. package/src/hev-eval.mts +69 -0
  276. package/src/hev-improve.mts +169 -0
  277. package/src/hev-structural.mts +688 -0
  278. package/src/index.ts +73 -0
  279. package/src/mbpp-structural.mts +662 -0
  280. package/src/pier-agent.test-fixtures.mts +19 -0
  281. package/src/pier-agent.test.mts +363 -0
  282. package/src/pier-agent.ts +657 -0
  283. package/src/pier-result-grader.mjs +30 -0
  284. package/src/pier-result-grader.test.mts +62 -0
  285. package/src/pier-result-grader.ts +108 -0
  286. package/src/pier-task-outcome.test.mts +117 -0
  287. package/src/pier-task-outcome.ts +240 -0
  288. package/src/pier-trial-controller.test.mts +412 -0
  289. package/src/pier-trial-controller.ts +858 -0
  290. package/src/pier-trial-supervisor.mjs +352 -0
  291. package/src/resolve-client.ts +25 -2
  292. package/src/run-benchmarks-cli.mts +72 -0
  293. package/src/run-benchmarks-report.ts +66 -0
  294. package/src/run-benchmarks.test.mts +231 -0
  295. package/src/run-benchmarks.ts +589 -0
  296. package/src/smoke-structural-rollout.mts +393 -0
  297. package/src/swe-bench-env.test.ts +207 -0
  298. package/src/swe-bench-env.ts +554 -0
  299. package/src/swe-jail.ts +293 -0
  300. package/src/swe-self-improve.mts +84 -0
  301. package/src/swe-structural-judge-policy.test.ts +117 -0
  302. package/src/swe-structural-judge-policy.ts +133 -0
  303. package/src/swe-structural-policy.test.ts +124 -0
  304. package/src/swe-structural-policy.ts +132 -0
  305. package/src/swe-structural-provenance.test.ts +93 -0
  306. package/src/swe-structural-provenance.ts +138 -0
  307. package/src/swe-structural.mts +1260 -0
  308. package/src/swe-temp.ts +14 -0
  309. package/src/tb-container-executor.mts +234 -0
  310. package/src/tb-container-executor.test.mts +99 -0
  311. package/src/tb-supervisor-sidecar.mts +222 -0
  312. package/src/trata-gepa.mts +1 -1
  313. package/steerers/eops-itsm-population.json +1 -0
  314. package/tb_agents/opencode_refine_agent.py +117 -0
  315. package/tb_agents/opencode_router_agent.py +406 -0
  316. package/tb_agents/opencode_supervisor_agent.py +239 -0
  317. package/tb_agents/script_agent.py +66 -0
@@ -17,19 +17,49 @@
17
17
  */
18
18
 
19
19
  import { execFile, spawn } from 'node:child_process'
20
- import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'
20
+ import { createHash } from 'node:crypto'
21
+ import {
22
+ cp,
23
+ lstat,
24
+ mkdir,
25
+ mkdtemp,
26
+ readFile,
27
+ readlink,
28
+ readdir,
29
+ rename,
30
+ rm,
31
+ writeFile,
32
+ } from 'node:fs/promises'
21
33
  import { tmpdir } from 'node:os'
22
- import { join } from 'node:path'
34
+ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path'
23
35
  import { fileURLToPath } from 'node:url'
24
36
  import { promisify } from 'node:util'
25
- import type { BenchScore } from './types'
37
+ import type {
38
+ BenchScore,
39
+ JudgeArtifactFileReceipt,
40
+ JudgeArtifactReceipt,
41
+ } from './types'
26
42
 
27
43
  const execFileAsync = promisify(execFile)
28
44
 
29
45
  /** Repo root for the bench package (…/bench), so `.venv` and `fixtures` resolve. */
30
46
  export const benchRoot = fileURLToPath(new URL('../..', import.meta.url))
31
- /** The bench venv interpreter every python-backed evaluator runs through. */
32
- export const venvPython = join(benchRoot, '.venv', 'bin', 'python')
47
+
48
+ /** Resolve the shared interpreter without requiring an installed package to contain a venv. */
49
+ export function resolveBenchPython(
50
+ env: Readonly<{ AGENT_BENCH_PYTHON?: string }> = process.env,
51
+ root: string = benchRoot,
52
+ ): string {
53
+ const configured = env.AGENT_BENCH_PYTHON
54
+ if (configured === undefined) return join(root, '.venv', 'bin', 'python')
55
+ if (!isAbsolute(configured)) {
56
+ throw new Error('AGENT_BENCH_PYTHON must be an absolute path')
57
+ }
58
+ return configured
59
+ }
60
+
61
+ /** The shared interpreter every Python-backed evaluator runs through. */
62
+ export const venvPython = resolveBenchPython()
33
63
 
34
64
  /** Interpreter for a NAMED isolated venv (e.g. `.venv-commit0`). Benches whose pip
35
65
  * deps conflict with the shared `.venv` (commit0 downgrades pydantic/sqlalchemy)
@@ -149,10 +179,160 @@ export interface StagedRunSpec {
149
179
  * if the expected report is absent/malformed (fail loud — no default score).
150
180
  */
151
181
  parseReport(dir: string): Promise<BenchScore>
182
+ /**
183
+ * Copy the complete evaluator directory plus raw process stdout/stderr to this
184
+ * caller-owned directory before cleanup. The destination must not exist.
185
+ */
186
+ capture?: StagedRunCaptureSpec
152
187
  /** Keep the temp dir on disk (debugging). Default false → always cleaned up. */
153
188
  keepTmp?: boolean
154
189
  }
155
190
 
191
+ export interface StagedRunCaptureSpec {
192
+ /** Destination for `evaluator/`, `process/`, and the hashed `receipt.json`. */
193
+ destination: string
194
+ }
195
+
196
+ /** A staged run failed after any requested evidence was durably retained. */
197
+ export class StagedJudgeError extends Error {
198
+ readonly judgeArtifacts?: JudgeArtifactReceipt
199
+
200
+ constructor(message: string, judgeArtifacts?: JudgeArtifactReceipt, options?: ErrorOptions) {
201
+ super(message, options)
202
+ this.name = 'StagedJudgeError'
203
+ this.judgeArtifacts = judgeArtifacts
204
+ }
205
+ }
206
+
207
+ function sha256(bytes: Uint8Array): `sha256:${string}` {
208
+ return `sha256:${createHash('sha256').update(bytes).digest('hex')}`
209
+ }
210
+
211
+ function portablePath(path: string): string {
212
+ return path.split(sep).join('/')
213
+ }
214
+
215
+ function compareText(left: string, right: string): number {
216
+ return left < right ? -1 : left > right ? 1 : 0
217
+ }
218
+
219
+ async function collectArtifactFiles(
220
+ root: string,
221
+ current: string,
222
+ ): Promise<JudgeArtifactFileReceipt[]> {
223
+ const absolute = join(root, current)
224
+ const entries = await readdir(absolute, { withFileTypes: true })
225
+ const files: JudgeArtifactFileReceipt[] = []
226
+ for (const entry of entries.sort((left, right) => compareText(left.name, right.name))) {
227
+ const relativePath = join(current, entry.name)
228
+ const path = join(root, relativePath)
229
+ if (entry.isDirectory()) {
230
+ files.push(...await collectArtifactFiles(root, relativePath))
231
+ continue
232
+ }
233
+ if (entry.isFile()) {
234
+ const bytes = await readFile(path)
235
+ files.push({
236
+ path: portablePath(relativePath),
237
+ byteLength: bytes.byteLength,
238
+ sha256: sha256(bytes),
239
+ kind: 'file',
240
+ })
241
+ continue
242
+ }
243
+ if (entry.isSymbolicLink()) {
244
+ const targetBytes = await readlink(path, { encoding: 'buffer' })
245
+ files.push({
246
+ path: portablePath(relativePath),
247
+ byteLength: targetBytes.byteLength,
248
+ sha256: sha256(targetBytes),
249
+ kind: 'symlink',
250
+ })
251
+ continue
252
+ }
253
+ throw new Error(`staged judge capture does not support ${relativePath}`)
254
+ }
255
+ return files
256
+ }
257
+
258
+ function isWithin(parent: string, candidate: string): boolean {
259
+ const path = relative(parent, candidate)
260
+ return path === '' || (!path.startsWith(`..${sep}`) && path !== '..' && !isAbsolute(path))
261
+ }
262
+
263
+ async function assertDestinationAbsent(destination: string): Promise<void> {
264
+ try {
265
+ await lstat(destination)
266
+ } catch (error) {
267
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return
268
+ throw error
269
+ }
270
+ throw new Error(`staged judge capture destination already exists: ${destination}`)
271
+ }
272
+
273
+ async function captureStagedRun(
274
+ sourceDirectory: string,
275
+ spec: StagedRunCaptureSpec,
276
+ processOutput: Readonly<{ stdout: Buffer; stderr: Buffer }>,
277
+ evaluatorSucceeded: boolean,
278
+ ): Promise<JudgeArtifactReceipt> {
279
+ const source = resolve(sourceDirectory)
280
+ const destination = resolve(spec.destination)
281
+ if (isWithin(source, destination)) {
282
+ throw new Error('staged judge capture destination must be outside the evaluator directory')
283
+ }
284
+ await mkdir(dirname(destination), { recursive: true })
285
+ await assertDestinationAbsent(destination)
286
+ const staging = await mkdtemp(join(dirname(destination), `.${basename(destination)}-capture-`))
287
+ try {
288
+ await cp(source, join(staging, 'evaluator'), {
289
+ recursive: true,
290
+ errorOnExist: true,
291
+ force: false,
292
+ preserveTimestamps: true,
293
+ verbatimSymlinks: true,
294
+ })
295
+ await mkdir(join(staging, 'process'))
296
+ await writeFile(join(staging, 'process', 'stdout.bin'), processOutput.stdout)
297
+ await writeFile(join(staging, 'process', 'stderr.bin'), processOutput.stderr)
298
+
299
+ const files = [
300
+ ...await collectArtifactFiles(staging, 'evaluator'),
301
+ ...await collectArtifactFiles(staging, 'process'),
302
+ ].sort((left, right) => compareText(left.path, right.path))
303
+ const byteLength = files.reduce((total, file) => total + file.byteLength, 0)
304
+ const treeBytes = Buffer.from(
305
+ files
306
+ .map((file) => `${file.path}\0${file.kind}\0${file.byteLength}\0${file.sha256}\n`)
307
+ .join(''),
308
+ 'utf8',
309
+ )
310
+ const receipt: JudgeArtifactReceipt = {
311
+ schema: 'agent-bench/judge-artifacts/v1',
312
+ directory: destination,
313
+ evaluatorDirectory: join(destination, 'evaluator'),
314
+ manifestPath: join(destination, 'receipt.json'),
315
+ evaluatorSucceeded,
316
+ files,
317
+ fileCount: files.length,
318
+ byteLength,
319
+ treeSha256: sha256(treeBytes),
320
+ }
321
+ await writeFile(join(staging, 'receipt.json'), `${JSON.stringify(receipt, null, 2)}\n`)
322
+ await rename(staging, destination)
323
+ return receipt
324
+ } catch (error) {
325
+ await rm(staging, { recursive: true, force: true }).catch(() => {})
326
+ throw error
327
+ }
328
+ }
329
+
330
+ function processBytes(value: unknown): Buffer {
331
+ if (Buffer.isBuffer(value)) return value
332
+ if (value === undefined || value === null) return Buffer.alloc(0)
333
+ return Buffer.from(String(value), 'utf8')
334
+ }
335
+
156
336
  /**
157
337
  * The shared judge body: mkdtemp → stage → spawn evaluator → parseReport →
158
338
  * cleanup. The evaluator's stdout/stderr is surfaced on failure; the temp dir is
@@ -160,23 +340,66 @@ export interface StagedRunSpec {
160
340
  */
161
341
  export async function runStagedJudge(spec: StagedRunSpec): Promise<BenchScore> {
162
342
  const dir = await mkdtemp(join(tmpdir(), spec.tmpPrefix))
343
+ let stdout: Buffer<ArrayBufferLike> = Buffer.alloc(0)
344
+ let stderr: Buffer<ArrayBufferLike> = Buffer.alloc(0)
345
+ let evaluatorSucceeded = false
346
+ let score: BenchScore | undefined
347
+ let failure: unknown
163
348
  try {
164
- await spec.stage(dir)
165
- const bin = spec.bin ?? venvPython
166
349
  try {
167
- await execFileAsync(bin, spec.argv(dir), {
168
- cwd: spec.cwd ? spec.cwd(dir) : dir,
169
- maxBuffer: bigBuffer,
170
- ...(spec.timeoutMs ? { timeout: spec.timeoutMs } : {}),
171
- })
350
+ await spec.stage(dir)
351
+ const bin = spec.bin ?? venvPython
352
+ const argv = spec.argv(dir)
353
+ try {
354
+ const output = await execFileAsync(bin, argv, {
355
+ cwd: spec.cwd ? spec.cwd(dir) : dir,
356
+ encoding: 'buffer',
357
+ maxBuffer: bigBuffer,
358
+ ...(spec.timeoutMs ? { timeout: spec.timeoutMs } : {}),
359
+ })
360
+ stdout = processBytes(output.stdout)
361
+ stderr = processBytes(output.stderr)
362
+ evaluatorSucceeded = true
363
+ } catch (err) {
364
+ const e = err as { stderr?: unknown; stdout?: unknown; message?: string }
365
+ stdout = processBytes(e.stdout)
366
+ stderr = processBytes(e.stderr)
367
+ const detail = processBytes(e.stderr ?? e.stdout ?? e.message ?? String(err))
368
+ .toString('utf8')
369
+ .slice(0, 2000)
370
+ throw new Error(`${spec.tmpPrefix.replace(/-$/, '')} evaluator failed (${bin} ${argv.join(' ')}):\n${detail}`)
371
+ }
372
+ score = await spec.parseReport(dir)
172
373
  } catch (err) {
173
- const e = err as { stderr?: string; stdout?: string; message?: string }
174
- const detail = (e.stderr || e.stdout || e.message || String(err)).slice(0, 2000)
175
- throw new Error(`${spec.tmpPrefix.replace(/-$/, '')} evaluator failed (${bin} ${spec.argv(dir).join(' ')}):\n${detail}`)
374
+ failure = err
176
375
  }
177
- return await spec.parseReport(dir)
178
376
  } finally {
377
+ let judgeArtifacts: JudgeArtifactReceipt | undefined
378
+ if (spec.capture) {
379
+ try {
380
+ judgeArtifacts = await captureStagedRun(
381
+ dir,
382
+ spec.capture,
383
+ { stdout, stderr },
384
+ evaluatorSucceeded,
385
+ )
386
+ } catch (captureError) {
387
+ failure = new Error(
388
+ `staged judge failed to capture evaluator artifacts: ${captureError instanceof Error ? captureError.message : captureError}`,
389
+ { cause: failure ?? captureError },
390
+ )
391
+ }
392
+ }
179
393
  if (!spec.keepTmp) await rm(dir, { recursive: true, force: true }).catch(() => {})
394
+ if (failure) {
395
+ throw new StagedJudgeError(
396
+ failure instanceof Error ? failure.message : String(failure),
397
+ judgeArtifacts,
398
+ { cause: failure },
399
+ )
400
+ }
401
+ if (!score) throw new StagedJudgeError('staged judge completed without a score', judgeArtifacts)
402
+ return judgeArtifacts ? { ...score, judgeArtifacts } : score
180
403
  }
181
404
  }
182
405
 
@@ -0,0 +1,163 @@
1
+ /**
2
+ * AgentBench deterministic subset adapter.
3
+ *
4
+ * This targets AgentBench DBBench rows only: question + table + published label.
5
+ * It does not wrap AgentBench's controller protocol or the non-deterministic game
6
+ * environments. Worker artifact = final answer text. Judge = exact match against
7
+ * the official DBBench label list after light whitespace/case normalization.
8
+ */
9
+
10
+ import { readFile } from 'node:fs/promises'
11
+ import { join } from 'node:path'
12
+ import type { OutputAdapter } from '@tangle-network/agent-runtime/loops'
13
+ import { benchRoot } from './_harness'
14
+ import type { BenchmarkAdapter, BenchScore, BenchTask, LoadOptions } from './types'
15
+
16
+ const FIXTURES = join(benchRoot, 'fixtures', 'agentbench-dbbench.json')
17
+ const DEFAULT_SPLIT = 'dev'
18
+
19
+ interface AgentBenchDbRow {
20
+ description: string
21
+ label: string[]
22
+ table?: {
23
+ table_name?: string
24
+ table_info?: {
25
+ columns?: Array<{ name: string; type?: string }>
26
+ rows?: unknown[][]
27
+ }
28
+ }
29
+ }
30
+
31
+ interface AgentBenchMeta {
32
+ labels: string[]
33
+ split: string
34
+ subset: 'dbbench'
35
+ table?: AgentBenchDbRow['table']
36
+ }
37
+
38
+ const agentbenchDir = (): string | undefined => process.env.AGENTBENCH_DIR
39
+
40
+ export const agentbenchAnswerOutput: OutputAdapter<string> = {
41
+ parse(events) {
42
+ let text = ''
43
+ for (const ev of events) {
44
+ const d = (ev as { data?: Record<string, unknown> })?.data
45
+ const t = d?.finalText ?? d?.text ?? d?.result
46
+ if (typeof t === 'string' && t.length > 0) text = t
47
+ }
48
+ const fences = [...text.matchAll(/```(?:text|answer)?\s*\n([\s\S]*?)```/g)]
49
+ return (fences.at(-1)?.[1] ?? text).trim()
50
+ },
51
+ }
52
+
53
+ function normalizeAnswer(value: string): string {
54
+ return value
55
+ .toLowerCase()
56
+ .replace(/\s+/g, ' ')
57
+ .replace(/^["'`]+|["'`]+$/g, '')
58
+ .trim()
59
+ }
60
+
61
+ function rowToTask(row: AgentBenchDbRow, index: number, split: string): BenchTask {
62
+ const columns = row.table?.table_info?.columns?.map((c) => `${c.name}${c.type ? ` (${c.type})` : ''}`).join(', ')
63
+ const sampleRows = row.table?.table_info?.rows?.slice(0, 40)
64
+ const meta: AgentBenchMeta = {
65
+ labels: row.label,
66
+ split,
67
+ subset: 'dbbench',
68
+ table: row.table,
69
+ }
70
+ return {
71
+ id: `dbbench-${split}-${index}`,
72
+ split,
73
+ prompt: [
74
+ 'Answer this AgentBench DBBench question using the table below.',
75
+ 'Return only the answer value.',
76
+ '',
77
+ `Question: ${row.description}`,
78
+ row.table?.table_name ? `Table: ${row.table.table_name}` : undefined,
79
+ columns ? `Columns: ${columns}` : undefined,
80
+ sampleRows ? `Rows JSON: ${JSON.stringify(sampleRows)}` : undefined,
81
+ ]
82
+ .filter(Boolean)
83
+ .join('\n'),
84
+ metadata: meta as unknown as Record<string, unknown>,
85
+ }
86
+ }
87
+
88
+ function readMeta(task: BenchTask): AgentBenchMeta {
89
+ const md = task.metadata
90
+ if (!md || !Array.isArray(md.labels)) {
91
+ throw new Error(`agentbench task ${task.id} missing metadata — loadTasks did not populate it`)
92
+ }
93
+ return md as unknown as AgentBenchMeta
94
+ }
95
+
96
+ function selectRows(rows: AgentBenchDbRow[], opts: LoadOptions, split: string): BenchTask[] {
97
+ let tasks = rows.map((row, index) => rowToTask(row, index, split))
98
+ if (opts.ids) {
99
+ const want = new Set(opts.ids)
100
+ tasks = tasks.filter((task) => want.has(task.id))
101
+ } else if (opts.limit !== undefined) {
102
+ tasks = tasks.slice(0, opts.limit)
103
+ }
104
+ if (tasks.length === 0) throw new Error(`AgentBench DBBench: no tasks matched ${JSON.stringify(opts)}`)
105
+ return tasks
106
+ }
107
+
108
+ async function loadJsonl(path: string): Promise<AgentBenchDbRow[]> {
109
+ const raw = await readFile(path, 'utf8')
110
+ return raw
111
+ .split('\n')
112
+ .map((line) => line.trim())
113
+ .filter(Boolean)
114
+ .map((line) => JSON.parse(line) as AgentBenchDbRow)
115
+ }
116
+
117
+ async function loadFixtures(opts: LoadOptions, split: string): Promise<BenchTask[]> {
118
+ const rows = JSON.parse(await readFile(FIXTURES, 'utf8')) as AgentBenchDbRow[]
119
+ console.warn(`[agentbench] AGENTBENCH_FIXTURES=1 — loading ${rows.length} DBBench adapter fixtures`)
120
+ return selectRows(rows, opts, split)
121
+ }
122
+
123
+ export function createAgentBenchAdapter(): BenchmarkAdapter {
124
+ const fixturesMode = process.env.AGENTBENCH_FIXTURES === '1'
125
+
126
+ return {
127
+ name: 'agentbench',
128
+ output: agentbenchAnswerOutput,
129
+
130
+ async preflight() {
131
+ if (fixturesMode) return
132
+ const dir = agentbenchDir()
133
+ if (!dir) {
134
+ throw new Error('AGENTBENCH_DIR is required. Fix: clone https://github.com/THUDM/AgentBench and set AGENTBENCH_DIR=/path/to/AgentBench.')
135
+ }
136
+ await loadJsonl(join(dir, 'data', 'dbbench', `${DEFAULT_SPLIT}.jsonl`))
137
+ },
138
+
139
+ async loadTasks(opts: LoadOptions = {}) {
140
+ const split = opts.split ?? DEFAULT_SPLIT
141
+ if (fixturesMode) return loadFixtures(opts, split)
142
+ const dir = agentbenchDir()
143
+ if (!dir) throw new Error('AGENTBENCH_DIR is required to load AgentBench DBBench tasks')
144
+ return selectRows(await loadJsonl(join(dir, 'data', 'dbbench', `${split}.jsonl`)), opts, split)
145
+ },
146
+
147
+ async goldArtifact(task: BenchTask) {
148
+ return readMeta(task).labels[0]
149
+ },
150
+
151
+ async judge(task: BenchTask, artifact: string): Promise<BenchScore> {
152
+ const meta = readMeta(task)
153
+ const got = normalizeAnswer(artifact)
154
+ const expected = meta.labels.map(normalizeAnswer)
155
+ const resolved = expected.includes(got)
156
+ return {
157
+ resolved,
158
+ score: resolved ? 1 : 0,
159
+ detail: JSON.stringify({ subset: meta.subset, split: meta.split, expected: meta.labels, got: artifact }),
160
+ }
161
+ },
162
+ }
163
+ }
@@ -27,19 +27,25 @@ test('goldArtifact is undefined — reference solution ships only inside the eng
27
27
  assert.equal(await a.goldArtifact({ id: 't', prompt: '', metadata: { taskId: 't', split: 'dev' } }), undefined)
28
28
  })
29
29
 
30
- test('preflight FAILS LOUD with the install + download-data fix when the engine is absent', async () => {
30
+ test('preflight passes when installed or FAILS LOUD with the install + download-data fix', async () => {
31
31
  const a = createAppWorldAdapter()
32
- await assert.rejects(a.preflight(), (e: Error) => {
32
+ try {
33
+ await a.preflight()
34
+ } catch (err) {
35
+ const e = err as Error
33
36
  assert.match(e.message, /pip install appworld/)
34
37
  assert.match(e.message, /appworld download data/)
35
- return true
36
- })
38
+ }
37
39
  })
38
40
 
39
- test('loadTasks FAILS LOUD (engine enumeration) rather than fabricating tasks offline', async () => {
41
+ test('loadTasks either enumerates live engine rows or FAILS LOUD without fabricating tasks', async () => {
40
42
  const a = createAppWorldAdapter()
41
- await assert.rejects(a.loadTasks({ limit: 1 }), (e: Error) => {
42
- assert.match(e.message, /appworld driver failed|appworld import failed/)
43
- return true
44
- })
43
+ try {
44
+ const tasks = await a.loadTasks({ limit: 1 })
45
+ assert.equal(tasks.length, 1)
46
+ assert.ok(tasks[0].id.length > 0)
47
+ assert.match(tasks[0].prompt, /Solve this by writing Python/)
48
+ } catch (err) {
49
+ assert.match((err as Error).message, /appworld driver failed|appworld import failed/)
50
+ }
45
51
  })