@tangle-network/agent-bench 0.3.6 → 0.3.8

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 (265) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/HARNESS.md +43 -0
  3. package/dist/adapters.js +24 -24
  4. package/dist/benchmarks/_harness.d.ts +1 -1
  5. package/dist/benchmarks/_harness.js +1 -1
  6. package/dist/benchmarks/aec-bench.js +2 -2
  7. package/dist/benchmarks/agentbench.js +2 -2
  8. package/dist/benchmarks/appworld.js +2 -2
  9. package/dist/benchmarks/bfcl.js +2 -2
  10. package/dist/benchmarks/commit0.js +2 -2
  11. package/dist/benchmarks/crag.js +2 -2
  12. package/dist/benchmarks/dabstep.js +2 -2
  13. package/dist/benchmarks/enterpriseops-gym.js +2 -2
  14. package/dist/benchmarks/finresearchbench.js +2 -2
  15. package/dist/benchmarks/humaneval.d.ts +10 -1
  16. package/dist/benchmarks/humaneval.js +5 -3
  17. package/dist/benchmarks/nomiracl.js +2 -2
  18. package/dist/benchmarks/open-rag-bench.js +2 -2
  19. package/dist/benchmarks/programbench.js +2 -2
  20. package/dist/benchmarks/ragbench.js +2 -2
  21. package/dist/benchmarks/swe-bench.js +2 -2
  22. package/dist/benchmarks/t2-ragbench.js +2 -2
  23. package/dist/benchmarks/tau-bench-shared.js +2 -2
  24. package/dist/benchmarks/tau2-bench.js +3 -3
  25. package/dist/benchmarks/tau3-banking.js +3 -3
  26. package/dist/benchmarks/terminal-bench.js +2 -2
  27. package/dist/benchmarks/toollm.js +2 -2
  28. package/dist/benchmarks/webarena-verified.js +2 -2
  29. package/dist/{chunk-CKUVRZ2T.js → chunk-3U5TXJZS.js} +2 -2
  30. package/dist/{chunk-PPYSEKFM.js → chunk-5H5XV76F.js} +73 -15
  31. package/dist/chunk-5H5XV76F.js.map +1 -0
  32. package/dist/{chunk-YCGY7UIZ.js → chunk-7GRVHU22.js} +2 -2
  33. package/dist/{chunk-Z7ML6L77.js → chunk-HWST3SED.js} +2 -2
  34. package/dist/{chunk-SYDW647C.js → chunk-IA2FBTWC.js} +2 -2
  35. package/dist/{chunk-R67DFVLO.js → chunk-IFVINJ4B.js} +2 -2
  36. package/dist/{chunk-R36V2VP7.js → chunk-IZ5M6OAC.js} +2 -2
  37. package/dist/{chunk-ODT47UAY.js → chunk-K3BQGZCT.js} +2 -2
  38. package/dist/{chunk-IFAV6KEM.js → chunk-KP5KD6EN.js} +2 -2
  39. package/dist/{chunk-ZEWMTR5M.js → chunk-MQMRLGOG.js} +2 -2
  40. package/dist/{chunk-TSWPNOYM.js → chunk-NQG5XDSB.js} +2 -2
  41. package/dist/{chunk-7WSD27QQ.js → chunk-PB64GYIG.js} +2 -2
  42. package/dist/{chunk-HBSWHQNJ.js → chunk-RCYQEFNX.js} +3 -3
  43. package/dist/{chunk-J3KDJNX2.js → chunk-RH5F53JT.js} +2 -2
  44. package/dist/{chunk-UAIOHCUK.js → chunk-SFLA7OH3.js} +3 -3
  45. package/dist/{chunk-Y6O2OCUO.js → chunk-SHM6MRRF.js} +2 -2
  46. package/dist/{chunk-KDIKRJGB.js → chunk-SHYIRB7I.js} +2 -2
  47. package/dist/{chunk-HHXFIHXC.js → chunk-SVR2LKYI.js} +2 -2
  48. package/dist/{chunk-5SBJCB6W.js → chunk-V7AEBY6U.js} +22 -22
  49. package/dist/{chunk-LRRD7NAG.js → chunk-WSKWVEQB.js} +18 -2
  50. package/dist/chunk-WSKWVEQB.js.map +1 -0
  51. package/dist/{chunk-2PVVP7GN.js → chunk-XKEFIFIC.js} +2 -2
  52. package/dist/{chunk-JRWWGMK7.js → chunk-XYA4XSNU.js} +2 -2
  53. package/dist/{chunk-X5YKXC6V.js → chunk-YSMEKBTD.js} +2 -2
  54. package/dist/{chunk-2XU6OGEN.js → chunk-Z4TZ76N7.js} +2 -2
  55. package/dist/index.js +24 -24
  56. package/package.json +6 -5
  57. package/scripts/run-package-tests.mjs +30 -8
  58. package/scripts/verify-packed-consumer.mjs +1 -1
  59. package/scripts/verify-pier-agent.mts +1 -0
  60. package/src/benchmarks/_harness.ts +20 -2
  61. package/src/benchmarks/humaneval.test.mts +122 -0
  62. package/src/benchmarks/humaneval.ts +100 -27
  63. package/src/david-attribution.mts +78 -0
  64. package/src/david-goliath.mts +149 -0
  65. package/src/hev-improve.mts +25 -6
  66. package/src/humaneval-object-ablation.mts +201 -0
  67. package/src/live-improve-campaign-mbpp.mts +641 -0
  68. package/src/live-improve-campaign.mts +500 -0
  69. package/src/mbpp-structural.mts +12 -7
  70. package/src/quant-arena/README.md +144 -0
  71. package/src/quant-arena/backtest.test.mts +135 -0
  72. package/src/quant-arena/backtest.ts +218 -0
  73. package/src/quant-arena/data.test.mts +44 -0
  74. package/src/quant-arena/data.ts +141 -0
  75. package/src/quant-arena/driver.test.mts +253 -0
  76. package/src/quant-arena/driver.ts +219 -0
  77. package/src/quant-arena/fixtures/data/PROVENANCE.md +26 -0
  78. package/src/quant-arena/fixtures/data/holdout/IDX.csv +523 -0
  79. package/src/quant-arena/fixtures/data/holdout/S01.csv +523 -0
  80. package/src/quant-arena/fixtures/data/holdout/S02.csv +523 -0
  81. package/src/quant-arena/fixtures/data/holdout/S03.csv +523 -0
  82. package/src/quant-arena/fixtures/data/holdout/S04.csv +523 -0
  83. package/src/quant-arena/fixtures/data/holdout/S05.csv +523 -0
  84. package/src/quant-arena/fixtures/data/holdout/S06.csv +523 -0
  85. package/src/quant-arena/fixtures/data/holdout/S07.csv +523 -0
  86. package/src/quant-arena/fixtures/data/holdout/S08.csv +523 -0
  87. package/src/quant-arena/fixtures/data/holdout/S09.csv +523 -0
  88. package/src/quant-arena/fixtures/data/holdout/S10.csv +523 -0
  89. package/src/quant-arena/fixtures/data/insample/IDX.csv +2087 -0
  90. package/src/quant-arena/fixtures/data/insample/S01.csv +2087 -0
  91. package/src/quant-arena/fixtures/data/insample/S02.csv +2087 -0
  92. package/src/quant-arena/fixtures/data/insample/S03.csv +2087 -0
  93. package/src/quant-arena/fixtures/data/insample/S04.csv +2087 -0
  94. package/src/quant-arena/fixtures/data/insample/S05.csv +2087 -0
  95. package/src/quant-arena/fixtures/data/insample/S06.csv +2087 -0
  96. package/src/quant-arena/fixtures/data/insample/S07.csv +2087 -0
  97. package/src/quant-arena/fixtures/data/insample/S08.csv +2087 -0
  98. package/src/quant-arena/fixtures/data/insample/S09.csv +2087 -0
  99. package/src/quant-arena/fixtures/data/insample/S10.csv +2087 -0
  100. package/src/quant-arena/fixtures/demo-campaign/cost-ledger.jsonl +16 -0
  101. package/src/quant-arena/fixtures/demo-campaign/notebook.jsonl +5 -0
  102. package/src/quant-arena/fixtures/demo-campaign/rollout-manifest.json +171 -0
  103. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-001-default-author/strategy.ts +119 -0
  104. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-002-default-author/strategy.ts +119 -0
  105. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-003-quant-researcher/strategy.ts +105 -0
  106. package/src/quant-arena/fixtures/demo-campaign/strategies/cand-004-quant-researcher/strategy.ts +102 -0
  107. package/src/quant-arena/fixtures/demo-campaign-v2/cost-ledger.jsonl +4 -0
  108. package/src/quant-arena/fixtures/demo-campaign-v2/notebook.jsonl +2 -0
  109. package/src/quant-arena/fixtures/demo-campaign-v2/rollout-manifest.json +84 -0
  110. package/src/quant-arena/fixtures/demo-campaign-v2/strategies/cand-001-quant-researcher/strategy.ts +117 -0
  111. package/src/quant-arena/holdout-certify.mts +206 -0
  112. package/src/quant-arena/holdout-certify.test.mts +82 -0
  113. package/src/quant-arena/leak-audit.test.mts +79 -0
  114. package/src/quant-arena/leak-audit.ts +95 -0
  115. package/src/quant-arena/make-fixtures.mts +161 -0
  116. package/src/quant-arena/multiplicity.test.mts +68 -0
  117. package/src/quant-arena/multiplicity.ts +87 -0
  118. package/src/quant-arena/nautilus-certify.ts +31 -0
  119. package/src/quant-arena/oms.ts +90 -0
  120. package/src/quant-arena/profiles/quant-researcher.profile.json +7 -0
  121. package/src/quant-arena/python/pyproject.toml +8 -0
  122. package/src/quant-arena/python/uv.lock +1297 -0
  123. package/src/quant-arena/python/vbt-worker.py +192 -0
  124. package/src/quant-arena/quant-loop.mts +813 -0
  125. package/src/quant-arena/quant-loop.test.mts +75 -0
  126. package/src/quant-arena/strategies/buy-hold-index/strategy.ts +11 -0
  127. package/src/quant-arena/strategies/equal-weight/strategy.ts +20 -0
  128. package/src/quant-arena/strategies/sma-crossover/strategy.ts +42 -0
  129. package/src/quant-arena/types.ts +133 -0
  130. package/src/quant-arena/vbt-client.ts +321 -0
  131. package/src/quant-arena/vbt-parity.test.mts +183 -0
  132. package/src/quant-arena/windows.test.mts +45 -0
  133. package/src/quant-arena/windows.ts +54 -0
  134. package/src/rollout-ledger/backfill-swe-arena.mts +606 -0
  135. package/src/rollout-ledger/backfill-swe-arena.test.mts +338 -0
  136. package/src/rollout-ledger/settle-capture.mts +442 -0
  137. package/src/rollout-ledger/settle-capture.test.mts +270 -0
  138. package/src/stream-observe.py +45 -0
  139. package/src/stream-observe.tpl.html +247 -0
  140. package/src/supervisor-arena.mts +816 -0
  141. package/src/swe-arena/activation.mts +228 -0
  142. package/src/swe-arena/activation.test.mts +303 -0
  143. package/src/swe-arena/analyze.ts +211 -0
  144. package/src/swe-arena/arms.ts +804 -0
  145. package/src/swe-arena/bootstrap-meta.mts +188 -0
  146. package/src/swe-arena/bootstrap-meta.test.mts +51 -0
  147. package/src/swe-arena/briefing.mts +217 -0
  148. package/src/swe-arena/briefing.test.mts +178 -0
  149. package/src/swe-arena/calibrate.ts +217 -0
  150. package/src/swe-arena/capabilities.mts +76 -0
  151. package/src/swe-arena/capabilities.test.mts +57 -0
  152. package/src/swe-arena/capacity.ts +194 -0
  153. package/src/swe-arena/cell-evidence.mts +437 -0
  154. package/src/swe-arena/cell-evidence.test.mts +248 -0
  155. package/src/swe-arena/diagnosis-ensemble.test.mts +210 -0
  156. package/src/swe-arena/diagnosis-ensemble.ts +520 -0
  157. package/src/swe-arena/execution.test.mts +1170 -0
  158. package/src/swe-arena/factory-command-container.ts +284 -0
  159. package/src/swe-arena/factory-judge-child.mts +228 -0
  160. package/src/swe-arena/factory.test.mts +643 -0
  161. package/src/swe-arena/fixtures/analyze.py +80 -0
  162. package/src/swe-arena/fixtures/excludes.txt +8 -0
  163. package/src/swe-arena/fixtures/factory/agent-eval-309/calibration.md +51 -0
  164. package/src/swe-arena/fixtures/factory/agent-eval-309/manifest.json +29 -0
  165. package/src/swe-arena/fixtures/factory/agent-eval-309/spec.md +64 -0
  166. package/src/swe-arena/fixtures/factory/agent-runtime-232/calibration.md +48 -0
  167. package/src/swe-arena/fixtures/factory/agent-runtime-232/manifest.json +29 -0
  168. package/src/swe-arena/fixtures/factory/agent-runtime-232/spec.md +48 -0
  169. package/src/swe-arena/fixtures/factory/loops-28/calibration.md +47 -0
  170. package/src/swe-arena/fixtures/factory/loops-28/manifest.json +30 -0
  171. package/src/swe-arena/fixtures/factory/loops-28/spec.md +50 -0
  172. package/src/swe-arena/fixtures/gen1-salvage/README.md +45 -0
  173. package/src/swe-arena/fixtures/gen1-salvage/cand0-e6d7361.diff +116 -0
  174. package/src/swe-arena/fixtures/gen1-salvage/cand1-76a8590.diff +293 -0
  175. package/src/swe-arena/fixtures/holdout-preregister.log +12 -0
  176. package/src/swe-arena/fixtures/holdout.json +44 -0
  177. package/src/swe-arena/fixtures/instances.json +146 -0
  178. package/src/swe-arena/fixtures/ledger.jsonl +12 -0
  179. package/src/swe-arena/fixtures/patches/pallets__flask-5014.solo.patch +36 -0
  180. package/src/swe-arena/fixtures/patches/pydata__xarray-4687.sup.patch +33 -0
  181. package/src/swe-arena/fixtures/rejudge.jsonl +15 -0
  182. package/src/swe-arena/fixtures/rematch.jsonl +3 -0
  183. package/src/swe-arena/fixtures/rematch2.jsonl +3 -0
  184. package/src/swe-arena/fixtures/rematch3.jsonl +3 -0
  185. package/src/swe-arena/fixtures/run-report/README.md +43 -0
  186. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.json +173 -0
  187. package/src/swe-arena/fixtures/run-report/factory-agent-eval-309-FSUP0.md +100 -0
  188. package/src/swe-arena/fixtures/run-report/gen3-rollup.json +551 -0
  189. package/src/swe-arena/fixtures/run-report/gen3-rollup.md +64 -0
  190. package/src/swe-arena/fixtures/sup-journal-true.json +19 -0
  191. package/src/swe-arena/fixtures/verify/astropy__astropy-13033.sh +48 -0
  192. package/src/swe-arena/fixtures/verify/django__django-11532.sh +50 -0
  193. package/src/swe-arena/fixtures/verify/matplotlib__matplotlib-20826.sh +76 -0
  194. package/src/swe-arena/fixtures/verify/pydata__xarray-4687.sh +44 -0
  195. package/src/swe-arena/fixtures/verify/pytest-dev__pytest-6197.sh +32 -0
  196. package/src/swe-arena/fixtures/verify/sphinx-doc__sphinx-9658.sh +51 -0
  197. package/src/swe-arena/fixtures/worker-tokens.json +42 -0
  198. package/src/swe-arena/fixtures.ts +237 -0
  199. package/src/swe-arena/gepa-seat.mts +583 -0
  200. package/src/swe-arena/gepa-seat.test.mts +635 -0
  201. package/src/swe-arena/holdout-certify.mts +408 -0
  202. package/src/swe-arena/holdout-certify.test.mts +160 -0
  203. package/src/swe-arena/judge-child.mts +37 -0
  204. package/src/swe-arena/ledger-orphans.mts +77 -0
  205. package/src/swe-arena/ledger-orphans.test.mts +147 -0
  206. package/src/swe-arena/lineage-record.mts +164 -0
  207. package/src/swe-arena/lineage-record.test.mts +115 -0
  208. package/src/swe-arena/manifest.mts +293 -0
  209. package/src/swe-arena/manifest.test.mts +169 -0
  210. package/src/swe-arena/materialize.ts +142 -0
  211. package/src/swe-arena/outer-loop.mts +2795 -0
  212. package/src/swe-arena/outer-loop.test.mts +696 -0
  213. package/src/swe-arena/parity.test.mts +87 -0
  214. package/src/swe-arena/premeasured-from-cells.mts +281 -0
  215. package/src/swe-arena/premeasured-from-cells.test.mts +180 -0
  216. package/src/swe-arena/proc.test.mts +174 -0
  217. package/src/swe-arena/proc.ts +260 -0
  218. package/src/swe-arena/profiles/default-author.profile.json +4 -0
  219. package/src/swe-arena/proposer-fanout.mts +770 -0
  220. package/src/swe-arena/proposer-fanout.test.mts +619 -0
  221. package/src/swe-arena/proposer-provenance.mts +177 -0
  222. package/src/swe-arena/proposer-provenance.test.mts +106 -0
  223. package/src/swe-arena/reconcile.ts +0 -0
  224. package/src/swe-arena/replay.mts +183 -0
  225. package/src/swe-arena/replay.test.mts +300 -0
  226. package/src/swe-arena/run-experiment.mts +727 -0
  227. package/src/swe-arena/run-report.mts +75 -0
  228. package/src/swe-arena/run-supervisor.mjs +297 -0
  229. package/src/swe-arena/run-supervisor.test.mts +500 -0
  230. package/src/swe-arena/score-split.mts +140 -0
  231. package/src/swe-arena/score-split.test.mts +123 -0
  232. package/src/swe-arena/serialized-judge.ts +414 -0
  233. package/src/swe-arena/types.ts +218 -0
  234. package/src/swe-code-improve.mts +328 -0
  235. package/src/swe-emit-patch.mts +104 -0
  236. package/src/swe-improve.mts +232 -0
  237. package/src/swe-jail.ts +2 -2
  238. package/src/swe-local-proof.mts +169 -0
  239. package/src/swe-repro-calibrate.mts +446 -0
  240. package/src/swe-stream.mts +1497 -0
  241. package/src/swe-structural.mts +245 -837
  242. package/dist/chunk-LRRD7NAG.js.map +0 -1
  243. package/dist/chunk-PPYSEKFM.js.map +0 -1
  244. /package/dist/{chunk-CKUVRZ2T.js.map → chunk-3U5TXJZS.js.map} +0 -0
  245. /package/dist/{chunk-YCGY7UIZ.js.map → chunk-7GRVHU22.js.map} +0 -0
  246. /package/dist/{chunk-Z7ML6L77.js.map → chunk-HWST3SED.js.map} +0 -0
  247. /package/dist/{chunk-SYDW647C.js.map → chunk-IA2FBTWC.js.map} +0 -0
  248. /package/dist/{chunk-R67DFVLO.js.map → chunk-IFVINJ4B.js.map} +0 -0
  249. /package/dist/{chunk-R36V2VP7.js.map → chunk-IZ5M6OAC.js.map} +0 -0
  250. /package/dist/{chunk-ODT47UAY.js.map → chunk-K3BQGZCT.js.map} +0 -0
  251. /package/dist/{chunk-IFAV6KEM.js.map → chunk-KP5KD6EN.js.map} +0 -0
  252. /package/dist/{chunk-ZEWMTR5M.js.map → chunk-MQMRLGOG.js.map} +0 -0
  253. /package/dist/{chunk-TSWPNOYM.js.map → chunk-NQG5XDSB.js.map} +0 -0
  254. /package/dist/{chunk-7WSD27QQ.js.map → chunk-PB64GYIG.js.map} +0 -0
  255. /package/dist/{chunk-HBSWHQNJ.js.map → chunk-RCYQEFNX.js.map} +0 -0
  256. /package/dist/{chunk-J3KDJNX2.js.map → chunk-RH5F53JT.js.map} +0 -0
  257. /package/dist/{chunk-UAIOHCUK.js.map → chunk-SFLA7OH3.js.map} +0 -0
  258. /package/dist/{chunk-Y6O2OCUO.js.map → chunk-SHM6MRRF.js.map} +0 -0
  259. /package/dist/{chunk-KDIKRJGB.js.map → chunk-SHYIRB7I.js.map} +0 -0
  260. /package/dist/{chunk-HHXFIHXC.js.map → chunk-SVR2LKYI.js.map} +0 -0
  261. /package/dist/{chunk-5SBJCB6W.js.map → chunk-V7AEBY6U.js.map} +0 -0
  262. /package/dist/{chunk-2PVVP7GN.js.map → chunk-XKEFIFIC.js.map} +0 -0
  263. /package/dist/{chunk-JRWWGMK7.js.map → chunk-XYA4XSNU.js.map} +0 -0
  264. /package/dist/{chunk-X5YKXC6V.js.map → chunk-YSMEKBTD.js.map} +0 -0
  265. /package/dist/{chunk-2XU6OGEN.js.map → chunk-Z4TZ76N7.js.map} +0 -0
@@ -0,0 +1,232 @@
1
+ /**
2
+ * SELF-IMPROVEMENT on the SEE-able LOCAL SWE-bench path — NO tangle sandbox.
3
+ *
4
+ * Composes the three proven pieces into ONE held-out-gated improvement generation:
5
+ * 1. `improve({ surface: 'prompt' })` (agent-runtime) drives the loop: it asks
6
+ * `gepaProposer` to EVOLVE the SWE agent's system prompt, then measures each
7
+ * candidate prompt on real instances and gates the winner on a held-out split.
8
+ * 2. Per candidate + scenario, the `agent` fn runs the LOCAL SWE env
9
+ * (`createSweBenchEnvironment` + `runAgentic`): clone the instance repo to a
10
+ * host tmpdir, run the jailed list/read/edit tool loop with the CANDIDATE
11
+ * prompt as the system prompt, and return the `git diff` as the artifact.
12
+ * 3. The `judge` scores that patch with the OFFICIAL swebench Docker harness
13
+ * (`adapter.judge` → resolved 0/1). The only remote call is the model
14
+ * completion via the router; nothing touches sandbox.tangle.tools.
15
+ *
16
+ * IN-LOOP score is a cheap patch-exists proxy (NOT the Docker judge) so the ONLY
17
+ * Docker run per cell is the improve judge — one deterministic verdict per cell.
18
+ *
19
+ * Cost per run = T·(1 + G·P) + 2·H cells, each = 1 clone + 1 runAgentic + 1 judge.
20
+ *
21
+ * TANGLE_API_KEY=… dotenvx run -f …/agent-state.env -- \
22
+ * TRAIN_IDS=psf__requests-2931 HOLDOUT_IDS=psf__requests-1142 \
23
+ * GENERATIONS=1 POPULATION=1 WORKER_MODEL=glm-4.6 REFLECT_MODEL=glm-4.6 \
24
+ * node_modules/.bin/tsx bench/src/swe-improve.mts
25
+ */
26
+ import { execFile } from 'node:child_process'
27
+ import { promisify } from 'node:util'
28
+ import { improve } from '@tangle-network/agent-runtime'
29
+ import type { AgentProfile } from '@tangle-network/agent-interface'
30
+ import type { AgenticSurface, ArtifactHandle, SurfaceScore } from '@tangle-network/agent-runtime/loops'
31
+ import { refine, runAgentic } from '@tangle-network/agent-runtime/loops'
32
+ import type { DispatchContext, JudgeConfig, Scenario } from '@tangle-network/agent-eval/contract'
33
+ import { gepaProposer } from '@tangle-network/agent-eval/campaign'
34
+ import { createSweBenchAdapter } from './benchmarks/swe-bench'
35
+ import type { BenchTask } from './benchmarks/types'
36
+ import { createSweBenchEnvironment, SWE_SEED_PROMPT, SWE_SEED_PROMPT_WITH_RUN } from './swe-bench-env'
37
+
38
+ const exec = promisify(execFile)
39
+
40
+ async function main(): Promise<void> {
41
+ const routerKey = process.env.TANGLE_API_KEY
42
+ if (!routerKey) throw new Error('TANGLE_API_KEY required (the worker + reflection call the router)')
43
+ const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
44
+ const workerModel = process.env.WORKER_MODEL ?? 'glm-4.6'
45
+ const reflectModel = process.env.REFLECT_MODEL ?? 'glm-4.6'
46
+ const trainIds = (process.env.TRAIN_IDS ?? 'psf__requests-2931').split(',').map((s) => s.trim()).filter(Boolean)
47
+ const holdoutIds = (process.env.HOLDOUT_IDS ?? 'psf__requests-1142').split(',').map((s) => s.trim()).filter(Boolean)
48
+ const generations = Number(process.env.GENERATIONS ?? 1)
49
+ const population = Number(process.env.POPULATION ?? 1)
50
+ const innerTurns = Number(process.env.INNER_TURNS ?? 40)
51
+ const workerMaxTokens = Number(process.env.MAX_TOKENS ?? 8000)
52
+ const reflectMaxTokens = Number(process.env.REFLECT_MAX_TOKENS ?? 12000)
53
+ const maxConcurrency = Number(process.env.MAX_CONCURRENCY ?? 1)
54
+ const budgetShots = Number(process.env.BUDGET ?? 1)
55
+ // WITH-TOOLS arm: RUN_TOOL=1 exposes the jailed `run` tool AND swaps the seed to the run-aware prompt.
56
+ // Default OFF ⇒ reproduces the read/edit-only baseline denominator unchanged.
57
+ const enableRun = ['1', 'true', 'yes'].includes((process.env.RUN_TOOL ?? '').toLowerCase())
58
+ const SEED_PROMPT = enableRun ? SWE_SEED_PROMPT_WITH_RUN : SWE_SEED_PROMPT
59
+
60
+ const allIds = [...new Set([...trainIds, ...holdoutIds])]
61
+ const cellsMax = trainIds.length * (1 + generations * population) + 2 * holdoutIds.length
62
+
63
+ console.log('═══ SWE-bench self-improvement — SEE-able LOCAL (no tangle sandbox) ═══')
64
+ console.log(`worker=${workerModel} reflect=${reflectModel} router=${routerBaseUrl}`)
65
+ console.log(`train=[${trainIds.join(', ')}] holdout=[${holdoutIds.join(', ')}]`)
66
+ console.log(`generations=${generations} population=${population} innerTurns=${innerTurns} workerMaxTokens=${workerMaxTokens} reflectMaxTokens=${reflectMaxTokens} runTool=${enableRun}`)
67
+ console.log(`≈ ${cellsMax} cells max (each = 1 clone + 1 runAgentic + 1 Docker judge)\n`)
68
+
69
+ if (process.env.DRYRUN) {
70
+ // Import + wiring smoke: prove every module resolves and the plan is well-formed
71
+ // WITHOUT paying for a clone / model call / Docker judge.
72
+ console.log(`DRYRUN: imports OK (improve=${typeof improve}, gepaProposer=${typeof gepaProposer}, runAgentic=${typeof runAgentic}, refine=${typeof refine})`)
73
+ return
74
+ }
75
+
76
+ const { environment, adapter } = await createSweBenchEnvironment(allIds.length, { ids: allIds, enableRun })
77
+ const pool = await adapter.loadTasks({ ids: allIds, split: 'test' })
78
+ const byId = new Map<string, BenchTask>(pool.map((t) => [t.id, t]))
79
+ for (const id of allIds) if (!byId.has(id)) throw new Error(`instance not found in Verified: ${id}`)
80
+
81
+ // The agent under improvement: run the LOCAL SWE env with the CANDIDATE prompt on
82
+ // one instance, return the git-diff patch. A per-call proxy captures the patch in
83
+ // score() BEFORE runAgentic closes (rm) the workspace; its score is a cheap
84
+ // patch-exists proxy so the ONLY Docker run per cell is the improve judge.
85
+ const agent = async (surface: unknown, scenario: Scenario, ctx: DispatchContext): Promise<string | null> => {
86
+ const promptText = String(surface)
87
+ const bt = byId.get(scenario.id)
88
+ if (!bt) throw new Error(`agent: unknown scenario ${scenario.id}`)
89
+ const task = { id: bt.id, systemPrompt: promptText, userPrompt: bt.prompt, meta: { instanceId: bt.id } }
90
+ let capturedPatch = ''
91
+ const stats = { list: 0, read: 0, edit_ok: 0, edit_fail: 0, run: 0, run_err: 0 }
92
+ const proxy: AgenticSurface = {
93
+ ...environment,
94
+ async call(handle, name, args) {
95
+ const res = await environment.call(handle, name, args)
96
+ const r = String(res)
97
+ if (name === 'list_files') stats.list += 1
98
+ else if (name === 'read_file') stats.read += 1
99
+ else if (name === 'edit_file') r.startsWith('edited ') ? (stats.edit_ok += 1) : (stats.edit_fail += 1)
100
+ else if (name === 'run') r.startsWith('ERROR:') ? (stats.run_err += 1) : (stats.run += 1)
101
+ return res
102
+ },
103
+ async score(_t, handle: ArtifactHandle): Promise<SurfaceScore> {
104
+ try {
105
+ const diff = await exec('git', ['-C', handle.id, 'diff'], { maxBuffer: 40_000_000, timeout: 60_000 })
106
+ if (!capturedPatch.trim() && diff.stdout.trim()) capturedPatch = diff.stdout
107
+ } catch {
108
+ /* workspace gone or git error → treat as no patch */
109
+ }
110
+ return { passes: capturedPatch.trim() ? 1 : 0, total: 1, errored: 0 }
111
+ },
112
+ }
113
+ const t0 = Date.now()
114
+ const r = await runAgentic({
115
+ surface: proxy,
116
+ task,
117
+ strategy: refine,
118
+ routerBaseUrl,
119
+ routerKey,
120
+ model: workerModel,
121
+ maxTokens: workerMaxTokens,
122
+ innerTurns,
123
+ budget: budgetShots,
124
+ })
125
+ // Report REAL cost/tokens so the backend-integrity guard sees a real backend
126
+ // rather than a silent-zero stub. A glm-5.2 turn occasionally returns a real
127
+ // patch with an UNPOPULATED usage block (a router telemetry gap on some
128
+ // reasoning-model responses — NOT a stub: the cell made real tool calls and
129
+ // produced a patch). In that gap case report a nominal floor so the stub-guard
130
+ // (artifact + zero usage) cannot abort the whole campaign on a telemetry gap.
131
+ // The lift metric is judge-derived, so a floored count does not distort it; only
132
+ // cost accounting undercounts those few cells (disclosed). No-patch cells return
133
+ // null below and are skipped by the guard's own contract, so this floor only
134
+ // ever applies to a cell that genuinely produced a patch.
135
+ const zeroUsage = (r.tokens.input ?? 0) === 0 && (r.tokens.output ?? 0) === 0
136
+ const hasPatch = capturedPatch.trim().length > 0
137
+ ctx.cost.observe(zeroUsage && hasPatch ? Math.max(r.usd ?? 0, 0.0001) : r.usd ?? 0, workerModel)
138
+ ctx.cost.observeTokens(
139
+ zeroUsage && hasPatch
140
+ ? { input: Math.max(r.tokens.input ?? 0, 1), output: Math.max(r.tokens.output ?? 0, 1) }
141
+ : { input: r.tokens.input, output: r.tokens.output },
142
+ )
143
+ const files = capturedPatch ? [...capturedPatch.matchAll(/^diff --git a\/(\S+)/gm)].map((m) => m[1]) : []
144
+ console.log(
145
+ ` [agent] ${scenario.id} prompt=${promptText.length}c tools(l/r/e+/e-/run/run!)=${stats.list}/${stats.read}/${stats.edit_ok}/${stats.edit_fail}/${stats.run}/${stats.run_err} ` +
146
+ `patch=${capturedPatch.length}b files=[${files.join(', ') || 'none'}] tok=in:${r.tokens.input}/out:${r.tokens.output} usd=${r.usd} ${Math.round((Date.now() - t0) / 1000)}s` +
147
+ `${zeroUsage ? (hasPatch ? ' [zero-usage telemetry gap: patch kept, usage floored]' : ' [zero-usage cell: empty completion — scored as no-patch]') : ''}`,
148
+ )
149
+ // A cell with no patch produced NO artifact. Return null (not '') so the
150
+ // backend-integrity guard's own contract (`artifact == null → skip`) applies:
151
+ // a glm-4.6 empty-content turn is scored 0 like the baseline, instead of
152
+ // aborting the whole campaign as a false-positive "stub cell". Any cell that
153
+ // DOES produce a patch still returns it and must report real usage or fire.
154
+ return capturedPatch.trim() ? capturedPatch : null
155
+ }
156
+
157
+ // The judge: the OFFICIAL swebench Docker harness. Deterministic FAIL_TO_PASS +
158
+ // PASS_TO_PASS → resolved 0/1. This is the held-out gate's scoring axis.
159
+ const judge: JudgeConfig<string, Scenario> = {
160
+ name: 'swebench-docker',
161
+ dimensions: [{ key: 'resolved', description: 'FAIL_TO_PASS + PASS_TO_PASS resolved by the official swebench Docker harness' }],
162
+ async score({ artifact, scenario }) {
163
+ const patch = String(artifact ?? '')
164
+ if (!patch.trim()) {
165
+ console.log(` [judge] ${scenario.id} resolved=0 (no patch)`)
166
+ return { dimensions: { resolved: 0 }, composite: 0, notes: 'no patch emitted' }
167
+ }
168
+ const bt = byId.get(scenario.id)
169
+ if (!bt) throw new Error(`judge: unknown scenario ${scenario.id}`)
170
+ const s = await adapter.judge(bt, patch)
171
+ console.log(` [judge] ${scenario.id} resolved=${s.resolved ? 1 : 0}`)
172
+ // 1500 chars keeps the whole swebench report JSON (a flat summary object —
173
+ // it has no separate failure section to extract); the old 200 clipped it to
174
+ // an uninformative head, leaving GEPA reflection trace-blind.
175
+ return { dimensions: { resolved: s.resolved ? 1 : 0 }, composite: s.resolved ? 1 : 0, notes: (s.detail ?? '').slice(0, 1500) }
176
+ },
177
+ }
178
+
179
+ const profile: AgentProfile = { name: 'swe-agent-glm46', prompt: { systemPrompt: SEED_PROMPT } }
180
+ const proposer = gepaProposer({
181
+ llm: { baseUrl: routerBaseUrl, apiKey: routerKey },
182
+ model: reflectModel,
183
+ target: 'the system prompt of a coding agent that fixes real GitHub bugs via list_files/read_file/edit_file tools',
184
+ maxTokens: reflectMaxTokens,
185
+ temperature: 0.7,
186
+ })
187
+
188
+ const scenarios: Scenario[] = allIds.map((id) => ({ id, kind: 'swe-bench-verified' }))
189
+ const holdoutScenarios: Scenario[] = holdoutIds.map((id) => ({ id, kind: 'swe-bench-verified' }))
190
+
191
+ const out = await improve(profile, [], {
192
+ surface: 'prompt',
193
+ gate: 'holdout',
194
+ generator: proposer,
195
+ scenarios,
196
+ judge,
197
+ agent,
198
+ // glm-5.2 occasionally returns a real patch with an unpopulated usage block
199
+ // (a router telemetry gap on some reasoning-model responses — NOT a stub: the
200
+ // cell made real tool calls and produced a patch). 'assert' would abort the
201
+ // whole campaign on such a cell; 'warn' logs it and continues. The lift metric
202
+ // (resolved) is judge-derived, so a missing token count does not distort it —
203
+ // only the cost accounting undercounts those cells, which is disclosed.
204
+ expectUsage: 'warn',
205
+ budget: { generations, populationSize: population, holdoutScenarios, maxConcurrency, reps: 1 },
206
+ llm: { baseUrl: routerBaseUrl, apiKey: routerKey, model: reflectModel },
207
+ })
208
+
209
+ console.log('\n═══ RESULT ═══')
210
+ console.log(`gateDecision=${out.gateDecision} shipped=${out.shipped} lift=${out.lift}`)
211
+ console.log(`baseline holdout composite = ${out.raw.baseline.compositeMean}`)
212
+ console.log(`winner holdout composite = ${out.raw.winner.compositeMean}`)
213
+ console.log(`baseline per-scenario: ${JSON.stringify(out.raw.baseline.perScenario)}`)
214
+ console.log(`winner per-scenario: ${JSON.stringify(out.raw.winner.perScenario)}`)
215
+ if (out.raw.winner.label) console.log(`winner label : ${out.raw.winner.label}`)
216
+ if (out.raw.winner.rationale) console.log(`winner rationale: ${out.raw.winner.rationale}`)
217
+
218
+ // Per-candidate verdicts on the train set (the "real swebench verdict per candidate").
219
+ for (const gen of out.raw.generations ?? []) {
220
+ console.log(`\n── generation ${gen.record.generationIndex} candidates ──`)
221
+ for (const c of gen.record.candidates) {
222
+ const perScenario = (c as { scenarios?: Array<{ scenarioId: string; composite: number }> }).scenarios ?? []
223
+ const detail = perScenario.map((s) => `${s.scenarioId}=${s.composite}`).join(' ')
224
+ console.log(` candidate ${c.surfaceHash.slice(0, 8)} composite=${c.composite}${c.label ? ` "${c.label}"` : ''} [${detail}]`)
225
+ }
226
+ }
227
+ }
228
+
229
+ main().catch((e) => {
230
+ console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
231
+ process.exit(1)
232
+ })
package/src/swe-jail.ts CHANGED
@@ -9,9 +9,9 @@
9
9
  */
10
10
  import { execFile } from 'node:child_process'
11
11
  import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
12
+ import { tmpdir } from 'node:os'
12
13
  import { join } from 'node:path'
13
14
  import { promisify } from 'node:util'
14
- import { absoluteSweTempDir } from './swe-temp'
15
15
 
16
16
  const exec = promisify(execFile)
17
17
 
@@ -143,7 +143,7 @@ export async function runPyInJail(
143
143
  applyPatch?: string,
144
144
  opts: { timeoutS?: number } = {},
145
145
  ): Promise<JailRun> {
146
- const scriptDir = mkdtempSync(join(absoluteSweTempDir(), 'swe-repro-'))
146
+ const scriptDir = mkdtempSync(join(tmpdir(), 'swe-repro-'))
147
147
  writeFileSync(join(scriptDir, 'repro.py'), pyScript)
148
148
  if (applyPatch) writeFileSync(join(scriptDir, 'ride.patch'), applyPatch.endsWith('\n') ? applyPatch : `${applyPatch}\n`)
149
149
  const T = opts.timeoutS ?? 120
@@ -0,0 +1,169 @@
1
+ /**
2
+ * SEE-able LOCAL proof of the SWE-bench Verified pipeline — NO tangle sandbox.
3
+ *
4
+ * The whole loop runs on infra we can watch: the repo is cloned into a host tmpdir, the agent is a
5
+ * router-driven tool loop (`runAgentic` + the swe-bench `AgenticSurface`'s list/read/edit tools —
6
+ * jailed to the checkout), the patch is a plain `git diff` of the agent's edits, and the score is
7
+ * the OFFICIAL swebench Docker harness (`adapter.judge`). The only remote call is the model
8
+ * completion via the router. Nothing touches sandbox.tangle.tools.
9
+ *
10
+ * We proxy the surface's `score()` so we can SEE the exact bytes the judge grades: the extracted
11
+ * patch, whether it applies to a clean base checkout (`git apply --check`), and the swebench verdict.
12
+ * With TRACE=1 we also log every tool call so we can tell "agent never edited" from "agent can't edit".
13
+ *
14
+ * TANGLE_API_KEY=… dotenvx run -f …/agent-state.env -- \
15
+ * IDS=django__django-12419 WORKER_MODEL=glm-4.6 \
16
+ * node_modules/.bin/tsx bench/src/swe-local-proof.mts
17
+ */
18
+ import { execFile } from 'node:child_process'
19
+ import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'
20
+ import { tmpdir } from 'node:os'
21
+ import { join } from 'node:path'
22
+ import { promisify } from 'node:util'
23
+ import type { AgenticSurface, ArtifactHandle, SurfaceScore } from '@tangle-network/agent-runtime/loops'
24
+ import { refine, runAgentic } from '@tangle-network/agent-runtime/loops'
25
+ import type { BenchScore } from './benchmarks/types'
26
+ import { createSweBenchEnvironment } from './swe-bench-env'
27
+
28
+ const exec = promisify(execFile)
29
+
30
+ async function main(): Promise<void> {
31
+ const routerKey = process.env.TANGLE_API_KEY
32
+ if (!routerKey) throw new Error('TANGLE_API_KEY required (the worker calls the router)')
33
+ const routerBaseUrl = process.env.ROUTER_BASE ?? 'https://router.tangle.tools/v1'
34
+ const model = process.env.WORKER_MODEL ?? 'glm-4.6'
35
+ const ids = (process.env.IDS ?? 'django__django-12419').split(',').map((s) => s.trim()).filter(Boolean)
36
+ const innerTurns = Number(process.env.INNER_TURNS ?? 40)
37
+ const maxTokens = Number(process.env.MAX_TOKENS ?? 8000)
38
+ const budget = Number(process.env.BUDGET ?? 1)
39
+ // WITH-TOOLS arm: RUN_TOOL=1 exposes the jailed `run` tool + the run-aware prompt. Default OFF ⇒
40
+ // the read/edit-only baseline (glm-5.2 7/12) unchanged.
41
+ const enableRun = ['1', 'true', 'yes'].includes((process.env.RUN_TOOL ?? '').toLowerCase())
42
+
43
+ console.log(`═══ SWE-bench Verified — SEE-able LOCAL proof (no tangle sandbox) ═══`)
44
+ console.log(`model=${model} ids=${ids.join(',')} innerTurns=${innerTurns} maxTokens=${maxTokens} budget=${budget} runTool=${enableRun}`)
45
+ console.log(`router=${routerBaseUrl}`)
46
+
47
+ const { environment, tasks, adapter } = await createSweBenchEnvironment(ids.length, { ids, enableRun })
48
+ const taskList = await tasks(0, ids.length)
49
+
50
+ // One shot per pinned id: proxy score() to capture the exact judged bytes + a NON-DESTRUCTIVE
51
+ // apply-coherence check, then delegate the verdict to the real Docker judge.
52
+ //
53
+ // CRITICAL: score() is called MORE THAN ONCE per task by the driver — the shotExecutor scores
54
+ // the handle to drive its loop, and depthStrategy scores it again at the end. So this proxy MUST
55
+ // be non-destructive (never mutate the working tree the agent edited) and idempotent (never
56
+ // clobber a captured non-empty patch with a later empty read). It also caches the judge by patch
57
+ // so we don't run the Docker harness twice for the identical diff.
58
+ type Rec = { patch: string; applied: boolean; applyErr?: string; applyChecked?: boolean; score?: BenchScore }
59
+ const captured = new Map<string, Rec>()
60
+ const judged = new Map<string, BenchScore>()
61
+ const toolStats = new Map<string, { list: number; read: number; edit_ok: number; edit_fail: number; run: number; run_err: number }>()
62
+ const proxy: AgenticSurface = {
63
+ ...environment,
64
+ async call(handle, name, args) {
65
+ const res = await environment.call(handle, name, args)
66
+ // Count tool usage per workspace so we can SEE whether the agent ever edited, and whether
67
+ // edits succeeded or bounced off old_string matching. handle.id keys the workspace.
68
+ const st = toolStats.get(handle.id) ?? { list: 0, read: 0, edit_ok: 0, edit_fail: 0, run: 0, run_err: 0 }
69
+ const r = String(res)
70
+ if (name === 'list_files') st.list += 1
71
+ else if (name === 'read_file') st.read += 1
72
+ else if (name === 'edit_file') {
73
+ if (r.startsWith('edited ')) st.edit_ok += 1
74
+ else st.edit_fail += 1
75
+ } else if (name === 'run') {
76
+ if (r.startsWith('ERROR:')) st.run_err += 1
77
+ else st.run += 1
78
+ }
79
+ toolStats.set(handle.id, st)
80
+ if (process.env.TRACE) {
81
+ const a = JSON.stringify(args).slice(0, 200)
82
+ console.error(`[TOOL] ${name} args=${a} -> ${r.slice(0, 200).replace(/\n/g, '⏎')}`)
83
+ }
84
+ return res
85
+ },
86
+ async score(task, handle: ArtifactHandle): Promise<SurfaceScore> {
87
+ const dir = handle.id
88
+ const diff = await exec('git', ['-C', dir, 'diff'], { maxBuffer: 40_000_000, timeout: 60_000 })
89
+ const patch = diff.stdout
90
+ // Mirror tool stats onto the task id (handle.id == dir).
91
+ const st = toolStats.get(dir)
92
+ if (st) toolStats.set(task.id, st)
93
+ // Idempotent capture: keep the FIRST non-empty patch; never overwrite it with a later empty read.
94
+ const prev = captured.get(task.id)
95
+ let rec: Rec
96
+ if (!prev) {
97
+ rec = { patch, applied: false }
98
+ captured.set(task.id, rec)
99
+ } else {
100
+ rec = prev
101
+ if (!rec.patch.trim() && patch.trim()) rec.patch = patch
102
+ }
103
+ const effPatch = rec.patch
104
+ if (!effPatch.trim()) return { passes: 0, total: 1, errored: 0 }
105
+ // NON-DESTRUCTIVE apply-coherence check (once): the diff was produced from this tree vs HEAD,
106
+ // so `git apply --check -R` proves it is a clean, self-consistent patch without touching the
107
+ // working tree. The REAL forward-apply-to-clean-base proof is the swebench judge below, which
108
+ // applies the patch in a fresh Docker checkout and reports apply failures.
109
+ if (!rec.applyChecked) {
110
+ rec.applyChecked = true
111
+ const pf = join(mkdtempSync(join(tmpdir(), 'swe-apply-')), 'p.diff')
112
+ writeFileSync(pf, effPatch)
113
+ try {
114
+ await exec('git', ['-C', dir, 'apply', '--check', '-R', pf], { timeout: 60_000 })
115
+ rec.applied = true
116
+ } catch (e) {
117
+ rec.applied = false
118
+ rec.applyErr = e instanceof Error ? e.message.slice(0, 200) : String(e)
119
+ } finally {
120
+ rmSync(pf, { force: true })
121
+ }
122
+ }
123
+ // Cached judge: identical patch ⇒ identical verdict; don't pay for a second Docker run.
124
+ let s = judged.get(effPatch)
125
+ if (!s) {
126
+ s = await adapter.judge(task, effPatch)
127
+ judged.set(effPatch, s)
128
+ }
129
+ rec.score = s
130
+ return { passes: s.resolved ? 1 : 0, total: 1, errored: 0 }
131
+ },
132
+ }
133
+
134
+ let anyResolved = 0
135
+ for (const task of taskList) {
136
+ const t0 = Date.now()
137
+ const r = await runAgentic({
138
+ surface: proxy,
139
+ task,
140
+ strategy: refine,
141
+ routerBaseUrl,
142
+ routerKey,
143
+ model,
144
+ maxTokens,
145
+ innerTurns,
146
+ budget,
147
+ })
148
+ const rec = captured.get(task.id)
149
+ const st = toolStats.get(task.id)
150
+ const patchBytes = rec?.patch.length ?? 0
151
+ const patchLines = rec?.patch ? rec.patch.split('\n').length : 0
152
+ const files = rec?.patch ? [...rec.patch.matchAll(/^diff --git a\/(\S+)/gm)].map((m) => m[1]) : []
153
+ const resolved = rec?.score?.resolved ?? false
154
+ if (resolved) anyResolved += 1
155
+ console.log(`\n──── ${task.id} ────`)
156
+ console.log(` agent shots=${r.shots} completions=${r.completions} tokens=in:${r.tokens.input}/out:${r.tokens.output} usd=${r.usd} wall=${Math.round((Date.now() - t0) / 1000)}s`)
157
+ console.log(` tools: list=${st?.list ?? 0} read=${st?.read ?? 0} edit_ok=${st?.edit_ok ?? 0} edit_fail=${st?.edit_fail ?? 0} run=${st?.run ?? 0} run_err=${st?.run_err ?? 0}`)
158
+ console.log(` patch: ${patchBytes} bytes, ${patchLines} lines, files=[${files.join(', ') || '(none)'}]`)
159
+ console.log(` patch APPLIED (git apply --check on clean base): ${rec?.applied ? 'YES' : 'NO'}${rec?.applyErr ? ` (${rec.applyErr})` : ''}`)
160
+ console.log(` swebench judge RESOLVED: ${resolved ? '1' : '0'}`)
161
+ if (rec?.score?.detail) console.log(` judge report: ${rec.score.detail.slice(0, 400)}`)
162
+ }
163
+ console.log(`\n>>> resolved ${anyResolved}/${taskList.length}`)
164
+ }
165
+
166
+ main().catch((e) => {
167
+ console.error(e instanceof Error ? (e.stack ?? e.message) : String(e))
168
+ process.exit(1)
169
+ })