@tangle-network/agent-bench 0.4.1 → 0.4.4

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 (195) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/adapters.d.ts +4 -11
  3. package/dist/adapters.js +78 -41
  4. package/dist/adapters.js.map +1 -1
  5. package/dist/benchmarks/_harness.d.ts +48 -66
  6. package/dist/benchmarks/_harness.js +329 -33
  7. package/dist/benchmarks/_harness.js.map +1 -1
  8. package/dist/benchmarks/aec-bench.d.ts +4 -25
  9. package/dist/benchmarks/aec-bench.js +242 -7
  10. package/dist/benchmarks/aec-bench.js.map +1 -1
  11. package/dist/benchmarks/agentbench.d.ts +5 -13
  12. package/dist/benchmarks/agentbench.js +114 -9
  13. package/dist/benchmarks/agentbench.js.map +1 -1
  14. package/dist/benchmarks/appworld.d.ts +9 -29
  15. package/dist/benchmarks/appworld.js +317 -12
  16. package/dist/benchmarks/appworld.js.map +1 -1
  17. package/dist/benchmarks/bfcl.d.ts +5 -15
  18. package/dist/benchmarks/bfcl.js +264 -9
  19. package/dist/benchmarks/bfcl.js.map +1 -1
  20. package/dist/benchmarks/cad-design.d.ts +16 -41
  21. package/dist/benchmarks/cad-design.js +512 -6
  22. package/dist/benchmarks/cad-design.js.map +1 -1
  23. package/dist/benchmarks/cadbench.d.ts +4 -17
  24. package/dist/benchmarks/cadbench.js +2 -8
  25. package/dist/benchmarks/cadgenbench.d.ts +4 -20
  26. package/dist/benchmarks/cadgenbench.js +2 -8
  27. package/dist/benchmarks/commit0.d.ts +5 -27
  28. package/dist/benchmarks/commit0.js +187 -9
  29. package/dist/benchmarks/commit0.js.map +1 -1
  30. package/dist/benchmarks/crag.d.ts +4 -12
  31. package/dist/benchmarks/crag.js +110 -8
  32. package/dist/benchmarks/crag.js.map +1 -1
  33. package/dist/benchmarks/dabstep.d.ts +5 -15
  34. package/dist/benchmarks/dabstep.js +177 -9
  35. package/dist/benchmarks/dabstep.js.map +1 -1
  36. package/dist/benchmarks/enterpriseops-gym.d.ts +5 -34
  37. package/dist/benchmarks/enterpriseops-gym.js +236 -9
  38. package/dist/benchmarks/enterpriseops-gym.js.map +1 -1
  39. package/dist/benchmarks/finresearchbench.d.ts +4 -13
  40. package/dist/benchmarks/finresearchbench.js +218 -7
  41. package/dist/benchmarks/finresearchbench.js.map +1 -1
  42. package/dist/benchmarks/finsearchcomp.d.ts +8 -39
  43. package/dist/benchmarks/finsearchcomp.js +267 -6
  44. package/dist/benchmarks/finsearchcomp.js.map +1 -1
  45. package/dist/benchmarks/frames.d.ts +15 -37
  46. package/dist/benchmarks/frames.js +408 -11
  47. package/dist/benchmarks/frames.js.map +1 -1
  48. package/dist/benchmarks/hotpotqa.d.ts +4 -24
  49. package/dist/benchmarks/hotpotqa.js +250 -14
  50. package/dist/benchmarks/hotpotqa.js.map +1 -1
  51. package/dist/benchmarks/humaneval.d.ts +19 -37
  52. package/dist/benchmarks/humaneval.js +279 -16
  53. package/dist/benchmarks/humaneval.js.map +1 -1
  54. package/dist/benchmarks/mind2web.d.ts +7 -34
  55. package/dist/benchmarks/mind2web.js +257 -8
  56. package/dist/benchmarks/mind2web.js.map +1 -1
  57. package/dist/benchmarks/nomiracl.d.ts +4 -13
  58. package/dist/benchmarks/nomiracl.js +146 -8
  59. package/dist/benchmarks/nomiracl.js.map +1 -1
  60. package/dist/benchmarks/open-rag-bench.d.ts +4 -12
  61. package/dist/benchmarks/open-rag-bench.js +110 -8
  62. package/dist/benchmarks/open-rag-bench.js.map +1 -1
  63. package/dist/benchmarks/programbench.d.ts +5 -29
  64. package/dist/benchmarks/programbench.js +161 -9
  65. package/dist/benchmarks/programbench.js.map +1 -1
  66. package/dist/benchmarks/rag-shared.d.ts +21 -20
  67. package/dist/benchmarks/rag-shared.js +245 -38
  68. package/dist/benchmarks/rag-shared.js.map +1 -1
  69. package/dist/benchmarks/ragbench.d.ts +4 -14
  70. package/dist/benchmarks/ragbench.js +127 -8
  71. package/dist/benchmarks/ragbench.js.map +1 -1
  72. package/dist/benchmarks/simpleqa.d.ts +17 -44
  73. package/dist/benchmarks/simpleqa.js +293 -10
  74. package/dist/benchmarks/simpleqa.js.map +1 -1
  75. package/dist/benchmarks/swe-bench.d.ts +23 -36
  76. package/dist/benchmarks/swe-bench.js +234 -13
  77. package/dist/benchmarks/swe-bench.js.map +1 -1
  78. package/dist/benchmarks/t2-ragbench.d.ts +4 -12
  79. package/dist/benchmarks/t2-ragbench.js +118 -8
  80. package/dist/benchmarks/t2-ragbench.js.map +1 -1
  81. package/dist/benchmarks/tau-bench-shared.d.ts +14 -22
  82. package/dist/benchmarks/tau-bench-shared.js +169 -9
  83. package/dist/benchmarks/tau-bench-shared.js.map +1 -1
  84. package/dist/benchmarks/tau2-bench.d.ts +5 -5
  85. package/dist/benchmarks/tau2-bench.js +28 -10
  86. package/dist/benchmarks/tau2-bench.js.map +1 -1
  87. package/dist/benchmarks/tau3-banking.d.ts +4 -13
  88. package/dist/benchmarks/tau3-banking.js +28 -8
  89. package/dist/benchmarks/tau3-banking.js.map +1 -1
  90. package/dist/benchmarks/terminal-bench.d.ts +4 -22
  91. package/dist/benchmarks/terminal-bench.js +140 -7
  92. package/dist/benchmarks/terminal-bench.js.map +1 -1
  93. package/dist/benchmarks/toollm.d.ts +5 -13
  94. package/dist/benchmarks/toollm.js +184 -9
  95. package/dist/benchmarks/toollm.js.map +1 -1
  96. package/dist/benchmarks/trata-hedge.d.ts +4 -30
  97. package/dist/benchmarks/trata-hedge.js +336 -6
  98. package/dist/benchmarks/trata-hedge.js.map +1 -1
  99. package/dist/benchmarks/types.d.ts +85 -94
  100. package/dist/benchmarks/types.js +1 -1
  101. package/dist/benchmarks/webarena-verified.d.ts +5 -13
  102. package/dist/benchmarks/webarena-verified.js +152 -9
  103. package/dist/benchmarks/webarena-verified.js.map +1 -1
  104. package/dist/cadbench-DpQWZHp4.js +285 -0
  105. package/dist/cadbench-DpQWZHp4.js.map +1 -0
  106. package/dist/cadgenbench-DRhczfsG.js +151 -0
  107. package/dist/cadgenbench-DRhczfsG.js.map +1 -0
  108. package/dist/index.d.ts +245 -293
  109. package/dist/index.js +1669 -1791
  110. package/dist/index.js.map +1 -1
  111. package/package.json +18 -15
  112. package/scripts/verify-packed-consumer.mjs +20 -17
  113. package/src/corpus.test.mts +13 -0
  114. package/src/corpus.ts +4 -0
  115. package/src/pier-trial-controller.test.mts +15 -7
  116. package/src/profile-coordinates.ts +2 -2
  117. package/src/rollout-ledger/backfill-swe-arena.mts +5 -1
  118. package/src/rollout-ledger/settle-capture.mts +7 -1
  119. package/src/search-bench/profiles.ts +1 -1
  120. package/src/skill-sandbox-smoke.mts +2 -1
  121. package/src/swe-arena/gepa-seat.mts +1 -1
  122. package/src/swe-arena/ledger-orphans.test.mts +36 -34
  123. package/dist/benchmarks/cadbench.js.map +0 -1
  124. package/dist/benchmarks/cadgenbench.js.map +0 -1
  125. package/dist/benchmarks/types.js.map +0 -1
  126. package/dist/chunk-53UPUNBZ.js +0 -325
  127. package/dist/chunk-53UPUNBZ.js.map +0 -1
  128. package/dist/chunk-5FEQDSCT.js +0 -211
  129. package/dist/chunk-5FEQDSCT.js.map +0 -1
  130. package/dist/chunk-5H5XV76F.js +0 -240
  131. package/dist/chunk-5H5XV76F.js.map +0 -1
  132. package/dist/chunk-67ACKDCX.js +0 -118
  133. package/dist/chunk-67ACKDCX.js.map +0 -1
  134. package/dist/chunk-7FKBWOQT.js +0 -147
  135. package/dist/chunk-7FKBWOQT.js.map +0 -1
  136. package/dist/chunk-BEN6IF2X.js +0 -221
  137. package/dist/chunk-BEN6IF2X.js.map +0 -1
  138. package/dist/chunk-BZY5QARD.js +0 -120
  139. package/dist/chunk-BZY5QARD.js.map +0 -1
  140. package/dist/chunk-C7T7WEK2.js +0 -103
  141. package/dist/chunk-C7T7WEK2.js.map +0 -1
  142. package/dist/chunk-CLIKAXKH.js +0 -276
  143. package/dist/chunk-CLIKAXKH.js.map +0 -1
  144. package/dist/chunk-CWIOBFSP.js +0 -197
  145. package/dist/chunk-CWIOBFSP.js.map +0 -1
  146. package/dist/chunk-CXDUTWQE.js +0 -318
  147. package/dist/chunk-CXDUTWQE.js.map +0 -1
  148. package/dist/chunk-DWALFME7.js +0 -182
  149. package/dist/chunk-DWALFME7.js.map +0 -1
  150. package/dist/chunk-EEOC6QPJ.js +0 -144
  151. package/dist/chunk-EEOC6QPJ.js.map +0 -1
  152. package/dist/chunk-EIETHPD5.js +0 -321
  153. package/dist/chunk-EIETHPD5.js.map +0 -1
  154. package/dist/chunk-GC2EPS6L.js +0 -130
  155. package/dist/chunk-GC2EPS6L.js.map +0 -1
  156. package/dist/chunk-GCHL6XPM.js +0 -169
  157. package/dist/chunk-GCHL6XPM.js.map +0 -1
  158. package/dist/chunk-HQ5HCCKF.js +0 -142
  159. package/dist/chunk-HQ5HCCKF.js.map +0 -1
  160. package/dist/chunk-HVW25KSX.js +0 -208
  161. package/dist/chunk-HVW25KSX.js.map +0 -1
  162. package/dist/chunk-INNOYXCP.js +0 -387
  163. package/dist/chunk-INNOYXCP.js.map +0 -1
  164. package/dist/chunk-J6BU3NTM.js +0 -251
  165. package/dist/chunk-J6BU3NTM.js.map +0 -1
  166. package/dist/chunk-JSQOUKXS.js +0 -149
  167. package/dist/chunk-JSQOUKXS.js.map +0 -1
  168. package/dist/chunk-JTHWEDEW.js +0 -32
  169. package/dist/chunk-JTHWEDEW.js.map +0 -1
  170. package/dist/chunk-NRMGT25X.js +0 -116
  171. package/dist/chunk-NRMGT25X.js.map +0 -1
  172. package/dist/chunk-PA2ZKHJC.js +0 -230
  173. package/dist/chunk-PA2ZKHJC.js.map +0 -1
  174. package/dist/chunk-PUIRNYI7.js +0 -189
  175. package/dist/chunk-PUIRNYI7.js.map +0 -1
  176. package/dist/chunk-QZZEAHWJ.js +0 -136
  177. package/dist/chunk-QZZEAHWJ.js.map +0 -1
  178. package/dist/chunk-SEVJPLZC.js +0 -260
  179. package/dist/chunk-SEVJPLZC.js.map +0 -1
  180. package/dist/chunk-TBKU5XQI.js +0 -228
  181. package/dist/chunk-TBKU5XQI.js.map +0 -1
  182. package/dist/chunk-UPAMRDX4.js +0 -233
  183. package/dist/chunk-UPAMRDX4.js.map +0 -1
  184. package/dist/chunk-VQRS7VUC.js +0 -342
  185. package/dist/chunk-VQRS7VUC.js.map +0 -1
  186. package/dist/chunk-WG7TM7UV.js +0 -30
  187. package/dist/chunk-WG7TM7UV.js.map +0 -1
  188. package/dist/chunk-X3BTXCJ4.js +0 -262
  189. package/dist/chunk-X3BTXCJ4.js.map +0 -1
  190. package/dist/chunk-XXFF3RRD.js +0 -162
  191. package/dist/chunk-XXFF3RRD.js.map +0 -1
  192. package/dist/chunk-ZFNOM7WR.js +0 -27
  193. package/dist/chunk-ZFNOM7WR.js.map +0 -1
  194. package/dist/chunk-ZNCCYTFG.js +0 -170
  195. package/dist/chunk-ZNCCYTFG.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,1908 +1,1786 @@
1
- import {
2
- ADAPTERS,
3
- resolveAdapter
4
- } from "./chunk-EEOC6QPJ.js";
5
- import "./chunk-CWIOBFSP.js";
6
- import "./chunk-VQRS7VUC.js";
7
- import "./chunk-XXFF3RRD.js";
8
- import {
9
- createRagBenchAdapter
10
- } from "./chunk-HQ5HCCKF.js";
11
- import "./chunk-SEVJPLZC.js";
12
- import {
13
- createSweBenchAdapter,
14
- scoreSweReport,
15
- sweEvaluationArgv,
16
- swePatchOutput
17
- } from "./chunk-J6BU3NTM.js";
18
- import {
19
- createT2RagBenchAdapter
20
- } from "./chunk-GC2EPS6L.js";
21
- import "./chunk-WG7TM7UV.js";
22
- import "./chunk-ZFNOM7WR.js";
23
- import "./chunk-GCHL6XPM.js";
24
- import "./chunk-JSQOUKXS.js";
25
- import "./chunk-INNOYXCP.js";
26
- import "./chunk-PA2ZKHJC.js";
27
- import "./chunk-5H5XV76F.js";
28
- import "./chunk-TBKU5XQI.js";
29
- import {
30
- createNoMiraclAdapter
31
- } from "./chunk-7FKBWOQT.js";
32
- import {
33
- createOpenRagBenchAdapter
34
- } from "./chunk-BZY5QARD.js";
35
- import "./chunk-QZZEAHWJ.js";
36
- import "./chunk-PUIRNYI7.js";
37
- import "./chunk-C7T7WEK2.js";
38
- import {
39
- runRefineLoop
40
- } from "./chunk-JTHWEDEW.js";
41
- import "./chunk-ZNCCYTFG.js";
42
- import {
43
- createCragAdapter
44
- } from "./chunk-NRMGT25X.js";
45
- import {
46
- FINAL_ANSWER_SENTINEL,
47
- answerScoreToBenchScore,
48
- contextBlock,
49
- contextsFrom,
50
- normalizeAnswer,
51
- parseCitations,
52
- parseFinalAnswer,
53
- ragAnswerOutput,
54
- scoreAnswerArtifact,
55
- tokenF1
56
- } from "./chunk-X3BTXCJ4.js";
57
- import "./chunk-DWALFME7.js";
58
- import "./chunk-5FEQDSCT.js";
59
- import "./chunk-HVW25KSX.js";
60
- import "./chunk-UPAMRDX4.js";
61
- import "./chunk-BEN6IF2X.js";
62
- import "./chunk-67ACKDCX.js";
63
- import "./chunk-CXDUTWQE.js";
64
- import "./chunk-CLIKAXKH.js";
65
- import {
66
- StagedJudgeError,
67
- runStagedJudge
68
- } from "./chunk-EIETHPD5.js";
69
- import "./chunk-53UPUNBZ.js";
70
-
71
- // src/run-benchmarks.ts
72
- import { mkdirSync, writeFileSync } from "fs";
73
- import { openSandboxRun } from "@tangle-network/agent-runtime/loops";
74
-
75
- // src/resolve-client.ts
76
- import {
77
- createExecutor,
78
- inlineSandboxClient,
79
- resolveSandboxClient
80
- } from "@tangle-network/agent-runtime/loops";
1
+ import { StagedJudgeError, runStagedJudge } from "./benchmarks/_harness.js";
2
+ import { FINAL_ANSWER_SENTINEL, answerScoreToBenchScore, contextBlock, contextsFrom, normalizeAnswer, parseCitations, parseFinalAnswer, ragAnswerOutput, scoreAnswerArtifact, tokenF1 } from "./benchmarks/rag-shared.js";
3
+ import { createCragAdapter } from "./benchmarks/crag.js";
4
+ import { createNoMiraclAdapter } from "./benchmarks/nomiracl.js";
5
+ import { createOpenRagBenchAdapter } from "./benchmarks/open-rag-bench.js";
6
+ import { createRagBenchAdapter } from "./benchmarks/ragbench.js";
7
+ import { createSweBenchAdapter, scoreSweReport, sweEvaluationArgv, swePatchOutput } from "./benchmarks/swe-bench.js";
8
+ import { createT2RagBenchAdapter } from "./benchmarks/t2-ragbench.js";
9
+ import { ADAPTERS, resolveAdapter } from "./adapters.js";
10
+ import { chmod, lstat, mkdir, mkdtemp, realpath, rm, writeFile } from "node:fs/promises";
11
+ import { dirname, isAbsolute, join, resolve } from "node:path";
12
+ import { execFileSync, spawn } from "node:child_process";
13
+ import { createHash } from "node:crypto";
14
+ import { accessSync, closeSync, constants, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
15
+ import { tmpdir } from "node:os";
16
+ import { fileURLToPath } from "node:url";
17
+ import { createExecutor, inlineSandboxClient, openSandboxRun, resolveSandboxClient } from "@tangle-network/agent-runtime/loops";
81
18
  import { Sandbox } from "@tangle-network/sandbox";
82
-
83
- // src/search-tool.ts
84
- var webSearchTool = {
85
- type: "function",
86
- function: {
87
- name: "web_search",
88
- description: "Search the live web for current facts, market data, filings, and news. Returns the top results as title, date, snippet, and URL. Call it as many times as needed, refining the query, before you answer.",
89
- parameters: {
90
- type: "object",
91
- properties: {
92
- query: {
93
- type: "string",
94
- description: "The search query. Be specific \u2014 include entity, metric, period, and units."
95
- }
96
- },
97
- required: ["query"]
98
- }
99
- }
19
+ import assert from "node:assert/strict";
20
+ import { executePreparedAgentCandidate } from "@tangle-network/agent-runtime";
21
+ import { canonicalJson } from "@tangle-network/agent-eval";
22
+ import { captureAgentCandidateWorkspaceFiles } from "@tangle-network/agent-runtime/candidate-execution";
23
+ //#region src/refine-loop.ts
24
+ const defaultDecide = (history) => history[history.length - 1]?.verdict?.valid === true;
25
+ async function runRefineLoop(spec) {
26
+ const rounds = Math.max(1, spec.rounds);
27
+ const decide = spec.decide ?? defaultDecide;
28
+ let ctxSet = false;
29
+ let ctx = void 0;
30
+ try {
31
+ if (spec.setup) {
32
+ ctx = await spec.setup();
33
+ ctxSet = true;
34
+ }
35
+ const history = [];
36
+ for (let r = 1; r <= rounds; r += 1) {
37
+ const prompt = spec.prompt(r, history, ctx);
38
+ const { artifact, note } = await spec.runShot(prompt, r, ctx);
39
+ const verdict = spec.judge ? await spec.judge(artifact, r) : void 0;
40
+ history.push({
41
+ round: r,
42
+ prompt,
43
+ artifact,
44
+ verdict,
45
+ note
46
+ });
47
+ if (decide(history)) break;
48
+ }
49
+ return {
50
+ rounds: history,
51
+ blind: history[0],
52
+ final: history[history.length - 1],
53
+ resolved: history.some((h) => h.verdict?.valid === true)
54
+ };
55
+ } finally {
56
+ if (ctxSet && spec.teardown) await spec.teardown(ctx);
57
+ }
58
+ }
59
+ //#endregion
60
+ //#region src/search-tool.ts
61
+ const webSearchTool = {
62
+ type: "function",
63
+ function: {
64
+ name: "web_search",
65
+ description: "Search the live web for current facts, market data, filings, and news. Returns the top results as title, date, snippet, and URL. Call it as many times as needed, refining the query, before you answer.",
66
+ parameters: {
67
+ type: "object",
68
+ properties: { query: {
69
+ type: "string",
70
+ description: "The search query. Be specific — include entity, metric, period, and units."
71
+ } },
72
+ required: ["query"]
73
+ }
74
+ }
100
75
  };
101
- var formatHits = (hits, maxHits) => {
102
- if (hits.length === 0) return "No results found for that query. Try a different query.";
103
- return hits.slice(0, maxHits).map((h, i) => {
104
- const date = h.publishedAt ? ` (${h.publishedAt.slice(0, 10)})` : "";
105
- return `[${i + 1}] ${h.title ?? "(untitled)"}${date}
106
- ${h.snippet ?? ""}
107
- ${h.url ?? ""}`;
108
- }).join("\n\n");
76
+ const formatHits = (hits, maxHits) => {
77
+ if (hits.length === 0) return "No results found for that query. Try a different query.";
78
+ return hits.slice(0, maxHits).map((h, i) => {
79
+ const date = h.publishedAt ? ` (${h.publishedAt.slice(0, 10)})` : "";
80
+ return `[${i + 1}] ${h.title ?? "(untitled)"}${date}\n${h.snippet ?? ""}\n${h.url ?? ""}`;
81
+ }).join("\n\n");
109
82
  };
110
- var makeSearchExecutor = (cfg) => {
111
- const maxHits = cfg.maxHits ?? 6;
112
- return async (name, args) => {
113
- if (name !== webSearchTool.function.name) {
114
- throw new Error(`search executor received unknown tool '${name}' (only '${webSearchTool.function.name}')`);
115
- }
116
- const query = typeof args.query === "string" ? args.query.trim() : "";
117
- if (!query) return 'web_search requires a non-empty "query" string.';
118
- const res = await fetch(`${cfg.routerBaseUrl}/search`, {
119
- method: "POST",
120
- headers: { "content-type": "application/json", authorization: `Bearer ${cfg.routerKey}` },
121
- body: JSON.stringify({ provider: cfg.provider, query })
122
- });
123
- if (!res.ok) {
124
- throw new Error(`web_search ${cfg.provider} HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
125
- }
126
- const body = await res.json();
127
- const hits = body.data ?? [];
128
- console.error(` [web_search:${cfg.provider}] "${query.slice(0, 80)}" -> ${hits.length} hits`);
129
- return formatHits(hits, maxHits);
130
- };
83
+ /**
84
+ * Build the `executeToolCall` handler for a `router-tools` worker. `provider` is a
85
+ * router search provider (`you` | `exa`). `maxHits` bounds the text folded back
86
+ * into the model's context per call.
87
+ */
88
+ const makeSearchExecutor = (cfg) => {
89
+ const maxHits = cfg.maxHits ?? 6;
90
+ return async (name, args) => {
91
+ if (name !== webSearchTool.function.name) throw new Error(`search executor received unknown tool '${name}' (only '${webSearchTool.function.name}')`);
92
+ const query = typeof args.query === "string" ? args.query.trim() : "";
93
+ if (!query) return "web_search requires a non-empty \"query\" string.";
94
+ const res = await fetch(`${cfg.routerBaseUrl}/search`, {
95
+ method: "POST",
96
+ headers: {
97
+ "content-type": "application/json",
98
+ authorization: `Bearer ${cfg.routerKey}`
99
+ },
100
+ body: JSON.stringify({
101
+ provider: cfg.provider,
102
+ query
103
+ })
104
+ });
105
+ if (!res.ok) throw new Error(`web_search ${cfg.provider} HTTP ${res.status}: ${(await res.text()).slice(0, 300)}`);
106
+ const hits = (await res.json()).data ?? [];
107
+ console.error(` [web_search:${cfg.provider}] "${query.slice(0, 80)}" -> ${hits.length} hits`);
108
+ return formatHits(hits, maxHits);
109
+ };
131
110
  };
132
-
133
- // src/resolve-client.ts
111
+ //#endregion
112
+ //#region src/resolve-client.ts
113
+ /**
114
+ * The ONE harness → worker-client mapping for the bench layer. Every bench entrypoint
115
+ * (rsi.ts, run.ts, …) derives the `SandboxClient` the kernel's `runLoop` drives from a single
116
+ * selector instead of hand-rolling the branch:
117
+ *
118
+ * - `router` → OFF-BOX: a router chat-completion as the leaf executor, presented
119
+ * as a SandboxClient (no sandbox dependency). For deployable-checker
120
+ * domains whose worker is a completion, or where box egress is blocked.
121
+ * - `router` + searchProvider → OFF-BOX `router-tools`: the same off-box loop with a live
122
+ * `web_search` tool (the capability axis research benches need).
123
+ * - `bridge` → OFF-BOX: a local cli-bridge fronting a harness CLI
124
+ * (opencode/kimi-code/…) as the leaf executor. Same resumable
125
+ * `bridgeExecutor` the supervisor uses; harness+model ride the
126
+ * bridge `model` id (`${harness}/${model}`).
127
+ * - anything else (`sandbox`/a BackendType) → IN-BOX: a real `Sandbox`. The in-box backend
128
+ * TYPE (opencode/codex/…) is set separately on the `AgentRunSpec`;
129
+ * this only decides off-box-vs-in-box transport for `runLoop`.
130
+ *
131
+ * Centralizing it here means a new entrypoint gets the full off-box/in-box matrix for free and
132
+ * the mapping can't drift between callers.
133
+ */
134
134
  function resolveBenchClient(opts) {
135
- const { backend, routerBaseUrl, routerKey, model, searchProvider } = opts;
136
- if (backend === "router") {
137
- if (searchProvider) {
138
- return inlineSandboxClient(
139
- createExecutor({
140
- backend: "router-tools",
141
- routerBaseUrl,
142
- routerKey,
143
- model,
144
- tools: [webSearchTool],
145
- executeToolCall: makeSearchExecutor({ routerBaseUrl, routerKey, provider: searchProvider })
146
- })
147
- );
148
- }
149
- return inlineSandboxClient(createExecutor({ backend: "router", routerBaseUrl, routerKey, model }));
150
- }
151
- if (backend === "bridge") {
152
- const bridgeBearer = opts.bridgeBearer ?? routerKey;
153
- if (!bridgeBearer) throw new Error("resolveBenchClient: backend 'bridge' needs bridgeBearer or routerKey");
154
- return resolveSandboxClient({
155
- backend: "bridge",
156
- bridge: { url: opts.bridgeUrl, bearer: bridgeBearer, model, timeoutMs: opts.timeoutMs }
157
- });
158
- }
159
- return new Sandbox({
160
- baseUrl: opts.sandboxBaseUrl ?? "https://sandbox.tangle.tools",
161
- apiKey: routerKey,
162
- timeoutMs: opts.timeoutMs ?? 12e5
163
- });
164
- }
165
-
166
- // src/run-pool.ts
135
+ const { backend, routerBaseUrl, routerKey, model, searchProvider } = opts;
136
+ if (backend === "router") {
137
+ if (searchProvider) return inlineSandboxClient(createExecutor({
138
+ backend: "router-tools",
139
+ routerBaseUrl,
140
+ routerKey,
141
+ model,
142
+ tools: [webSearchTool],
143
+ executeToolCall: makeSearchExecutor({
144
+ routerBaseUrl,
145
+ routerKey,
146
+ provider: searchProvider
147
+ })
148
+ }));
149
+ return inlineSandboxClient(createExecutor({
150
+ backend: "router",
151
+ routerBaseUrl,
152
+ routerKey,
153
+ model
154
+ }));
155
+ }
156
+ if (backend === "bridge") {
157
+ const bridgeBearer = opts.bridgeBearer ?? routerKey;
158
+ if (!bridgeBearer) throw new Error("resolveBenchClient: backend 'bridge' needs bridgeBearer or routerKey");
159
+ return resolveSandboxClient({
160
+ backend: "bridge",
161
+ bridge: {
162
+ url: opts.bridgeUrl,
163
+ bearer: bridgeBearer,
164
+ model,
165
+ timeoutMs: opts.timeoutMs
166
+ }
167
+ });
168
+ }
169
+ return new Sandbox({
170
+ baseUrl: opts.sandboxBaseUrl ?? "https://sandbox.tangle.tools",
171
+ apiKey: routerKey,
172
+ timeoutMs: opts.timeoutMs ?? 12e5
173
+ });
174
+ }
175
+ //#endregion
176
+ //#region src/run-pool.ts
167
177
  async function runPool(items, concurrency, worker, opts) {
168
- const results = new Array(items.length);
169
- let next = 0;
170
- const drain = async () => {
171
- while (next < items.length) {
172
- const index = next++;
173
- const item = items[index];
174
- let outcome;
175
- try {
176
- outcome = { index, item, ok: true, value: await worker(item, index) };
177
- } catch (err) {
178
- outcome = { index, item, ok: false, error: err instanceof Error ? err.message : String(err) };
179
- }
180
- results[index] = outcome;
181
- opts?.onResult?.(outcome);
182
- }
183
- };
184
- await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, drain));
185
- return results;
186
- }
187
-
188
- // src/run-benchmarks.ts
178
+ const results = new Array(items.length);
179
+ let next = 0;
180
+ const drain = async () => {
181
+ while (next < items.length) {
182
+ const index = next++;
183
+ const item = items[index];
184
+ let outcome;
185
+ try {
186
+ outcome = {
187
+ index,
188
+ item,
189
+ ok: true,
190
+ value: await worker(item, index)
191
+ };
192
+ } catch (err) {
193
+ outcome = {
194
+ index,
195
+ item,
196
+ ok: false,
197
+ error: err instanceof Error ? err.message : String(err)
198
+ };
199
+ }
200
+ results[index] = outcome;
201
+ opts?.onResult?.(outcome);
202
+ }
203
+ };
204
+ await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, drain));
205
+ return results;
206
+ }
207
+ //#endregion
208
+ //#region src/run-benchmarks.ts
209
+ /**
210
+ * `runBenchmarks` — the bench unifier.
211
+ *
212
+ * `gate.ts` runs ONE adapter as a diverse-vs-blind research gate through the recursive atom.
213
+ * This is the other half it references: run a SUBSET of the registry's benchmarks over a MATRIX of
214
+ * agent cells (harness × model × persona), each scored by the benchmark's OWN deterministic judge,
215
+ * and return a ranked leaderboard. It is the "which harness/model/persona combination wins on which
216
+ * benchmark" question, answered over an arbitrary subset in one call.
217
+ *
218
+ * It owns no new mechanism. Each cell is one `openSandboxRun` shot (the same per-run primitive the
219
+ * SWE worker uses) driven by `resolveBenchClient` (off-box router completion OR in-box Sandbox; the
220
+ * harness rides `sandboxOverrides.backend.type`). The deliverable is the adapter's OWN parser
221
+ * (`adapter.output`), defaulting to the final answer text — so `runBenchmarks` needs no
222
+ * per-benchmark branching. Concurrency is the shared `runPool`. The number comes from
223
+ * `adapter.judge`, never a self-authored judge.
224
+ *
225
+ * Subset = the `benchmarks` and `cells` arrays plus `n`/`ids`/`split`. A benchmark whose
226
+ * `preflight()` fails (missing Docker/venv/dataset) is recorded as unavailable and skipped — the
227
+ * sweep never aborts because one bench's harness is absent.
228
+ *
229
+ * const report = await runBenchmarks({
230
+ * benchmarks: ['humaneval', 'swe-bench'],
231
+ * cells: [
232
+ * { label: 'opencode/glm-4.6', model: 'glm-4.6', harness: 'opencode' },
233
+ * { label: 'codex/gpt-5', model: 'gpt-5', harness: 'codex' },
234
+ * ],
235
+ * routerBaseUrl, routerKey, n: 20,
236
+ * })
237
+ */
238
+ /** Last assistant text across the common event shapes (delta accumulation, then a terminal
239
+ * `result`/`done`/`agent` snapshot). The structured-deliverable case is handled by the adapter's
240
+ * own `output.parse`; this is the research/QA fallback. */
189
241
  function finalText(events) {
190
- let text = "";
191
- for (const ev of events) {
192
- const e = ev;
193
- const data = e.data ?? {};
194
- if (e.type === "message.part.updated") {
195
- const part = data.part;
196
- const partType = data.partType ?? part?.type;
197
- if (partType === "text") {
198
- if (typeof data.delta === "string") text += data.delta;
199
- else if (typeof part?.text === "string") text = part.text;
200
- }
201
- } else if (typeof data.finalText === "string") text = data.finalText;
202
- else if (typeof data.response === "string") text = data.response;
203
- }
204
- return text.trim();
205
- }
206
- var openSandboxShot = async ({ adapter, task, cell, prompt, routerBaseUrl, routerKey, bridgeUrl, bridgeBearer, sandboxBaseUrl, timeoutMs, resolveClient }) => {
207
- const client = (resolveClient ?? resolveBenchClient)({
208
- backend: cell.backend ?? "router",
209
- routerBaseUrl,
210
- routerKey,
211
- model: cell.model,
212
- ...bridgeUrl ? { bridgeUrl } : {},
213
- ...bridgeBearer ? { bridgeBearer } : {},
214
- ...sandboxBaseUrl ? { sandboxBaseUrl } : {},
215
- ...cell.searchProvider ? { searchProvider: cell.searchProvider } : {},
216
- ...timeoutMs ? { timeoutMs } : {}
217
- });
218
- const harness = cell.harness ?? cell.profile?.metadata?.backendType ?? "opencode";
219
- const profile = cell.profile ?? { name: cell.label, metadata: { backendType: harness } };
220
- const uniq = Math.random().toString(36).slice(2, 8);
221
- const agentRun = {
222
- profile,
223
- name: cell.label,
224
- taskToPrompt: () => "",
225
- sandboxOverrides: {
226
- name: `bench-${adapter.name}-${task.id}-${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 60),
227
- environment: "universal",
228
- backend: { type: harness, model: { provider: "openai", model: cell.model, baseUrl: routerBaseUrl } }
229
- }
230
- };
231
- const deliverable = {
232
- kind: "events",
233
- fromEvents: (events) => adapter.output ? adapter.output.parse(events) : finalText(events)
234
- };
235
- const controller = new AbortController();
236
- const timer = timeoutMs ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
237
- const runOptions = {
238
- agentRun,
239
- signal: controller.signal,
240
- runId: `bench:${adapter.name}:${task.id}:${uniq}`,
241
- scenarioId: task.id
242
- };
243
- const boxSetup = adapter.boxSetup;
244
- if (boxSetup) {
245
- runOptions.beforeStart = async ({ box, sessionId }) => {
246
- const setup = boxSetup(task);
247
- const sres = await box.exec(setup.command, {
248
- timeoutMs: 3e5,
249
- sessionId,
250
- ...setup.cwd ? { cwd: setup.cwd } : {}
251
- });
252
- if (sres.exitCode !== 0)
253
- throw new Error(
254
- `boxSetup failed (exit ${sres.exitCode}): ${(sres.stderr ?? "").slice(0, 200)}`
255
- );
256
- };
257
- }
258
- const run = await openSandboxRun(client, runOptions, deliverable);
259
- try {
260
- const turn = await run.start(prompt ?? task.prompt);
261
- let artifact = (turn.out ?? "").trim();
262
- let boxExtractError;
263
- if (adapter.boxExtract) {
264
- try {
265
- const ex = adapter.boxExtract(task);
266
- const res = await run.box.exec(ex.command, {
267
- timeoutMs: 12e4,
268
- sessionId: run.sessionId,
269
- ...ex.cwd ? { cwd: ex.cwd } : {}
270
- });
271
- const boxArtifact = (res.stdout ?? "").trim();
272
- if (res.exitCode !== 0)
273
- boxExtractError = `exit ${res.exitCode}: ${(res.stderr ?? "").slice(0, 160)}`;
274
- else if (boxArtifact.length > 0) artifact = boxArtifact;
275
- if (process.env.BENCH_ARTIFACT_DIR) {
276
- try {
277
- mkdirSync(process.env.BENCH_ARTIFACT_DIR, { recursive: true });
278
- const safe = `${adapter.name}_${task.id}_${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
279
- const map = await run.box.exec(
280
- 'echo "PWD:"; pwd; echo "LS:"; ls -la; echo "GITROOTS:"; find / -maxdepth 5 -type d -name .git 2>/dev/null; echo "SETTINGS:"; find / -maxdepth 8 -name global_settings.py -path "*conf*" 2>/dev/null',
281
- { timeoutMs: 6e4, sessionId: run.sessionId }
282
- ).catch((e) => ({ exitCode: -1, stdout: "", stderr: String(e) }));
283
- writeFileSync(
284
- `${process.env.BENCH_ARTIFACT_DIR}/${safe}.exec.json`,
285
- JSON.stringify(
286
- { sessionId: run.sessionId, extract: res, map: { exitCode: map.exitCode, stdout: map.stdout, stderr: map.stderr } },
287
- null,
288
- 2
289
- )
290
- );
291
- } catch {
292
- }
293
- }
294
- } catch (err) {
295
- boxExtractError = err instanceof Error ? err.message.slice(0, 160) : String(err);
296
- }
297
- }
298
- const detail = turn.readError !== void 0 ? `read: ${turn.readError.slice(0, 160)}` : boxExtractError !== void 0 ? `boxExtract: ${boxExtractError}` : void 0;
299
- if (process.env.BENCH_ARTIFACT_DIR) {
300
- try {
301
- mkdirSync(process.env.BENCH_ARTIFACT_DIR, { recursive: true });
302
- const safe = `${adapter.name}_${task.id}_${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
303
- writeFileSync(`${process.env.BENCH_ARTIFACT_DIR}/${safe}.patch`, artifact);
304
- } catch {
305
- }
306
- }
307
- return {
308
- artifact,
309
- ok: artifact.length > 0,
310
- ...detail ? { detail } : {}
311
- };
312
- } finally {
313
- if (timer) clearTimeout(timer);
314
- await run.close();
315
- }
242
+ let text = "";
243
+ for (const ev of events) {
244
+ const e = ev;
245
+ const data = e.data ?? {};
246
+ if (e.type === "message.part.updated") {
247
+ const part = data.part;
248
+ if ((data.partType ?? part?.type) === "text") {
249
+ if (typeof data.delta === "string") text += data.delta;
250
+ else if (typeof part?.text === "string") text = part.text;
251
+ }
252
+ } else if (typeof data.finalText === "string") text = data.finalText;
253
+ else if (typeof data.response === "string") text = data.response;
254
+ }
255
+ return text.trim();
256
+ }
257
+ /** The default real-agent shot: one `openSandboxRun` over the cell's harness+model, deliverable
258
+ * extracted by the adapter's parser (or final text), abortable on `timeoutMs`. */
259
+ const openSandboxShot = async ({ adapter, task, cell, prompt, routerBaseUrl, routerKey, bridgeUrl, bridgeBearer, sandboxBaseUrl, timeoutMs, resolveClient }) => {
260
+ const client = (resolveClient ?? resolveBenchClient)({
261
+ backend: cell.backend ?? "router",
262
+ routerBaseUrl,
263
+ routerKey,
264
+ model: cell.model,
265
+ ...bridgeUrl ? { bridgeUrl } : {},
266
+ ...bridgeBearer ? { bridgeBearer } : {},
267
+ ...sandboxBaseUrl ? { sandboxBaseUrl } : {},
268
+ ...cell.searchProvider ? { searchProvider: cell.searchProvider } : {},
269
+ ...timeoutMs ? { timeoutMs } : {}
270
+ });
271
+ const harness = cell.harness ?? cell.profile?.metadata?.backendType ?? "opencode";
272
+ const profile = cell.profile ?? {
273
+ name: cell.label,
274
+ metadata: { backendType: harness }
275
+ };
276
+ const uniq = Math.random().toString(36).slice(2, 8);
277
+ const agentRun = {
278
+ profile,
279
+ name: cell.label,
280
+ taskToPrompt: () => "",
281
+ sandboxOverrides: {
282
+ name: `bench-${adapter.name}-${task.id}-${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, "_").slice(0, 60),
283
+ environment: "universal",
284
+ backend: {
285
+ type: harness,
286
+ model: {
287
+ provider: "openai",
288
+ model: cell.model,
289
+ baseUrl: routerBaseUrl
290
+ }
291
+ }
292
+ }
293
+ };
294
+ const deliverable = {
295
+ kind: "events",
296
+ fromEvents: (events) => adapter.output ? adapter.output.parse(events) : finalText(events)
297
+ };
298
+ const controller = new AbortController();
299
+ const timer = timeoutMs ? setTimeout(() => controller.abort(), timeoutMs) : void 0;
300
+ const runOptions = {
301
+ agentRun,
302
+ signal: controller.signal,
303
+ runId: `bench:${adapter.name}:${task.id}:${uniq}`,
304
+ scenarioId: task.id
305
+ };
306
+ const boxSetup = adapter.boxSetup;
307
+ if (boxSetup) runOptions.beforeStart = async ({ box, sessionId }) => {
308
+ const setup = boxSetup(task);
309
+ const sres = await box.exec(setup.command, {
310
+ timeoutMs: 3e5,
311
+ sessionId,
312
+ ...setup.cwd ? { cwd: setup.cwd } : {}
313
+ });
314
+ if (sres.exitCode !== 0) throw new Error(`boxSetup failed (exit ${sres.exitCode}): ${(sres.stderr ?? "").slice(0, 200)}`);
315
+ };
316
+ const run = await openSandboxRun(client, runOptions, deliverable);
317
+ try {
318
+ const turn = await run.start(prompt ?? task.prompt);
319
+ let artifact = (turn.out ?? "").trim();
320
+ let boxExtractError;
321
+ if (adapter.boxExtract) try {
322
+ const ex = adapter.boxExtract(task);
323
+ const res = await run.box.exec(ex.command, {
324
+ timeoutMs: 12e4,
325
+ sessionId: run.sessionId,
326
+ ...ex.cwd ? { cwd: ex.cwd } : {}
327
+ });
328
+ const boxArtifact = (res.stdout ?? "").trim();
329
+ if (res.exitCode !== 0) boxExtractError = `exit ${res.exitCode}: ${(res.stderr ?? "").slice(0, 160)}`;
330
+ else if (boxArtifact.length > 0) artifact = boxArtifact;
331
+ if (process.env.BENCH_ARTIFACT_DIR) try {
332
+ mkdirSync(process.env.BENCH_ARTIFACT_DIR, { recursive: true });
333
+ const safe = `${adapter.name}_${task.id}_${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
334
+ const map = await run.box.exec("echo \"PWD:\"; pwd; echo \"LS:\"; ls -la; echo \"GITROOTS:\"; find / -maxdepth 5 -type d -name .git 2>/dev/null; echo \"SETTINGS:\"; find / -maxdepth 8 -name global_settings.py -path \"*conf*\" 2>/dev/null", {
335
+ timeoutMs: 6e4,
336
+ sessionId: run.sessionId
337
+ }).catch((e) => ({
338
+ exitCode: -1,
339
+ stdout: "",
340
+ stderr: String(e)
341
+ }));
342
+ writeFileSync(`${process.env.BENCH_ARTIFACT_DIR}/${safe}.exec.json`, JSON.stringify({
343
+ sessionId: run.sessionId,
344
+ extract: res,
345
+ map: {
346
+ exitCode: map.exitCode,
347
+ stdout: map.stdout,
348
+ stderr: map.stderr
349
+ }
350
+ }, null, 2));
351
+ } catch {}
352
+ } catch (err) {
353
+ boxExtractError = err instanceof Error ? err.message.slice(0, 160) : String(err);
354
+ }
355
+ const detail = turn.readError !== void 0 ? `read: ${turn.readError.slice(0, 160)}` : boxExtractError !== void 0 ? `boxExtract: ${boxExtractError}` : void 0;
356
+ if (process.env.BENCH_ARTIFACT_DIR) try {
357
+ mkdirSync(process.env.BENCH_ARTIFACT_DIR, { recursive: true });
358
+ const safe = `${adapter.name}_${task.id}_${uniq}`.replace(/[^a-zA-Z0-9_.-]/g, "_");
359
+ writeFileSync(`${process.env.BENCH_ARTIFACT_DIR}/${safe}.patch`, artifact);
360
+ } catch {}
361
+ return {
362
+ artifact,
363
+ ok: artifact.length > 0,
364
+ ...detail ? { detail } : {}
365
+ };
366
+ } finally {
367
+ if (timer) clearTimeout(timer);
368
+ await run.close();
369
+ }
316
370
  };
317
371
  function parseMaybeJson(value) {
318
- try {
319
- return JSON.parse(value);
320
- } catch {
321
- return value;
322
- }
372
+ try {
373
+ return JSON.parse(value);
374
+ } catch {
375
+ return value;
376
+ }
323
377
  }
324
378
  function redactJudgeLeak(value) {
325
- if (Array.isArray(value)) return value.map(redactJudgeLeak);
326
- if (!value || typeof value !== "object") return value;
327
- const out = {};
328
- for (const [key, child] of Object.entries(value)) {
329
- const normalizedKey = key.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
330
- if (/(^|_)(gold|expected|reference|solution|answer)(_|$)/.test(normalizedKey)) continue;
331
- out[key] = redactJudgeLeak(child);
332
- }
333
- return out;
379
+ if (Array.isArray(value)) return value.map(redactJudgeLeak);
380
+ if (!value || typeof value !== "object") return value;
381
+ const out = {};
382
+ for (const [key, child] of Object.entries(value)) {
383
+ const normalizedKey = key.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
384
+ if (/(^|_)(gold|expected|reference|solution|answer)(_|$)/.test(normalizedKey)) continue;
385
+ out[key] = redactJudgeLeak(child);
386
+ }
387
+ return out;
334
388
  }
335
389
  function safeFeedback(score) {
336
- return {
337
- resolved: score.resolved,
338
- score: score.score,
339
- ...score.detail ? { detail: redactJudgeLeak(parseMaybeJson(score.detail)) } : {}
340
- };
390
+ return {
391
+ resolved: score.resolved,
392
+ score: score.score,
393
+ ...score.detail ? { detail: redactJudgeLeak(parseMaybeJson(score.detail)) } : {}
394
+ };
341
395
  }
342
396
  function truncate(value, max = 4e3) {
343
- return value.length <= max ? value : `${value.slice(0, max)}
344
- ...[truncated ${value.length - max} chars]`;
397
+ return value.length <= max ? value : `${value.slice(0, max)}\n...[truncated ${value.length - max} chars]`;
345
398
  }
346
399
  function retryPrompt(task, history, scores) {
347
- const attempts = history.map((h) => {
348
- const score = scores.get(h.round);
349
- return [
350
- `Attempt ${h.round}:`,
351
- "Artifact:",
352
- truncate(h.artifact),
353
- score ? `Checker feedback: ${JSON.stringify(safeFeedback(score))}` : void 0
354
- ].filter(Boolean).join("\n");
355
- }).join("\n\n");
356
- return [
357
- "Retry the benchmark task. The previous artifact did not pass the checker.",
358
- "Use only the original task statement and supplied context. Do not invent facts.",
359
- "Return the corrected artifact in exactly the format requested by the original task.",
360
- "",
361
- "Original task:",
362
- task.prompt,
363
- "",
364
- "Previous attempts and safe checker feedback:",
365
- attempts
366
- ].join("\n");
400
+ const attempts = history.map((h) => {
401
+ const score = scores.get(h.round);
402
+ return [
403
+ `Attempt ${h.round}:`,
404
+ "Artifact:",
405
+ truncate(h.artifact),
406
+ score ? `Checker feedback: ${JSON.stringify(safeFeedback(score))}` : void 0
407
+ ].filter(Boolean).join("\n");
408
+ }).join("\n\n");
409
+ return [
410
+ "Retry the benchmark task. The previous artifact did not pass the checker.",
411
+ "Use only the original task statement and supplied context. Do not invent facts.",
412
+ "Return the corrected artifact in exactly the format requested by the original task.",
413
+ "",
414
+ "Original task:",
415
+ task.prompt,
416
+ "",
417
+ "Previous attempts and safe checker feedback:",
418
+ attempts
419
+ ].join("\n");
367
420
  }
368
421
  async function loopedShot(input, shot, attempts) {
369
- const scores = /* @__PURE__ */ new Map();
370
- const result = await runRefineLoop({
371
- rounds: attempts,
372
- prompt: (round, history) => round === 1 ? input.task.prompt : retryPrompt(input.task, history, scores),
373
- runShot: async (prompt, round) => {
374
- const out = await shot({ ...input, prompt, attempt: round });
375
- return { artifact: out.artifact, note: out.detail };
376
- },
377
- judge: async (artifact, round) => {
378
- const score = await input.adapter.judge(input.task, artifact);
379
- scores.set(round, score);
380
- return { valid: score.resolved, score: score.score };
381
- }
382
- });
383
- const best = result.rounds.reduce((winner, candidate) => {
384
- const a = scores.get(winner.round);
385
- const b = scores.get(candidate.round);
386
- if (!a) return candidate;
387
- if (!b) return winner;
388
- if (b.resolved && !a.resolved) return candidate;
389
- if (b.resolved === a.resolved && b.score > a.score) return candidate;
390
- return winner;
391
- }, result.rounds[0]);
392
- const bestScore = scores.get(best.round);
393
- return {
394
- artifact: best.artifact,
395
- ok: best.artifact.trim().length > 0,
396
- detail: JSON.stringify({
397
- mode: "refine-loop",
398
- attempts: result.rounds.length,
399
- selectedAttempt: best.round,
400
- resolvedDuringLoop: result.resolved,
401
- selectedScore: bestScore?.score ?? null,
402
- rounds: result.rounds.map((round) => ({
403
- attempt: round.round,
404
- score: scores.get(round.round)?.score ?? null,
405
- resolved: scores.get(round.round)?.resolved ?? null,
406
- note: round.note ?? null
407
- }))
408
- })
409
- };
422
+ const scores = /* @__PURE__ */ new Map();
423
+ const result = await runRefineLoop({
424
+ rounds: attempts,
425
+ prompt: (round, history) => round === 1 ? input.task.prompt : retryPrompt(input.task, history, scores),
426
+ runShot: async (prompt, round) => {
427
+ const out = await shot({
428
+ ...input,
429
+ prompt,
430
+ attempt: round
431
+ });
432
+ return {
433
+ artifact: out.artifact,
434
+ note: out.detail
435
+ };
436
+ },
437
+ judge: async (artifact, round) => {
438
+ const score = await input.adapter.judge(input.task, artifact);
439
+ scores.set(round, score);
440
+ return {
441
+ valid: score.resolved,
442
+ score: score.score
443
+ };
444
+ }
445
+ });
446
+ const best = result.rounds.reduce((winner, candidate) => {
447
+ const a = scores.get(winner.round);
448
+ const b = scores.get(candidate.round);
449
+ if (!a) return candidate;
450
+ if (!b) return winner;
451
+ if (b.resolved && !a.resolved) return candidate;
452
+ if (b.resolved === a.resolved && b.score > a.score) return candidate;
453
+ return winner;
454
+ }, result.rounds[0]);
455
+ const bestScore = scores.get(best.round);
456
+ return {
457
+ artifact: best.artifact,
458
+ ok: best.artifact.trim().length > 0,
459
+ detail: JSON.stringify({
460
+ mode: "refine-loop",
461
+ attempts: result.rounds.length,
462
+ selectedAttempt: best.round,
463
+ resolvedDuringLoop: result.resolved,
464
+ selectedScore: bestScore?.score ?? null,
465
+ rounds: result.rounds.map((round) => ({
466
+ attempt: round.round,
467
+ score: scores.get(round.round)?.score ?? null,
468
+ resolved: scores.get(round.round)?.resolved ?? null,
469
+ note: round.note ?? null
470
+ }))
471
+ })
472
+ };
410
473
  }
411
474
  function combineDetails(runDetail, scoreDetail) {
412
- if (runDetail && scoreDetail) {
413
- return JSON.stringify({ run: parseMaybeJson(runDetail), score: parseMaybeJson(scoreDetail) });
414
- }
415
- return runDetail ?? scoreDetail;
416
- }
417
- async function prepareBenchmarks(benchmarks, resolve4, opts) {
418
- const ready = [];
419
- const unavailable = [];
420
- for (const benchmark of benchmarks) {
421
- const adapter = resolve4(benchmark);
422
- try {
423
- await adapter.preflight();
424
- const tasks = await adapter.loadTasks({
425
- ...opts.n !== void 0 ? { limit: opts.n } : {},
426
- ...opts.ids ? { ids: opts.ids } : {},
427
- ...opts.split ? { split: opts.split } : {}
428
- });
429
- if (tasks.length === 0) {
430
- unavailable.push({ benchmark, reason: "loadTasks returned no tasks" });
431
- continue;
432
- }
433
- if (opts.verifyJudge !== false) {
434
- const gold = await adapter.goldArtifact(tasks[0]);
435
- if (gold !== void 0) {
436
- const verdict = await adapter.judge(tasks[0], gold);
437
- if (!verdict.resolved) {
438
- unavailable.push({ benchmark, reason: `judge rejected its own gold on ${tasks[0].id} \u2014 judge is miscalibrated` });
439
- continue;
440
- }
441
- }
442
- }
443
- ready.push({ benchmark, adapter, tasks });
444
- } catch (err) {
445
- unavailable.push({ benchmark, reason: err instanceof Error ? err.message : String(err) });
446
- }
447
- }
448
- return { ready, unavailable };
475
+ if (runDetail && scoreDetail) return JSON.stringify({
476
+ run: parseMaybeJson(runDetail),
477
+ score: parseMaybeJson(scoreDetail)
478
+ });
479
+ return runDetail ?? scoreDetail;
480
+ }
481
+ /** Resolve + preflight + (optionally) self-verify each benchmark once; load its tasks. A benchmark
482
+ * whose harness is absent or whose judge rejects its own gold is recorded unavailable, not run. */
483
+ async function prepareBenchmarks(benchmarks, resolve, opts) {
484
+ const ready = [];
485
+ const unavailable = [];
486
+ for (const benchmark of benchmarks) {
487
+ const adapter = resolve(benchmark);
488
+ try {
489
+ await adapter.preflight();
490
+ const tasks = await adapter.loadTasks({
491
+ ...opts.n !== void 0 ? { limit: opts.n } : {},
492
+ ...opts.ids ? { ids: opts.ids } : {},
493
+ ...opts.split ? { split: opts.split } : {}
494
+ });
495
+ if (tasks.length === 0) {
496
+ unavailable.push({
497
+ benchmark,
498
+ reason: "loadTasks returned no tasks"
499
+ });
500
+ continue;
501
+ }
502
+ if (opts.verifyJudge !== false) {
503
+ const gold = await adapter.goldArtifact(tasks[0]);
504
+ if (gold !== void 0) {
505
+ if (!(await adapter.judge(tasks[0], gold)).resolved) {
506
+ unavailable.push({
507
+ benchmark,
508
+ reason: `judge rejected its own gold on ${tasks[0].id} judge is miscalibrated`
509
+ });
510
+ continue;
511
+ }
512
+ }
513
+ }
514
+ ready.push({
515
+ benchmark,
516
+ adapter,
517
+ tasks
518
+ });
519
+ } catch (err) {
520
+ unavailable.push({
521
+ benchmark,
522
+ reason: err instanceof Error ? err.message : String(err)
523
+ });
524
+ }
525
+ }
526
+ return {
527
+ ready,
528
+ unavailable
529
+ };
449
530
  }
450
531
  async function runBenchmarks(opts) {
451
- if (opts.benchmarks.length === 0) throw new Error("runBenchmarks: no benchmarks selected");
452
- if (opts.cells.length === 0) throw new Error("runBenchmarks: no cells to run");
453
- const reps = Math.max(1, opts.reps ?? 1);
454
- const loopAttempts = Math.max(1, opts.loopAttempts ?? 1);
455
- const shot = opts.runShot ?? openSandboxShot;
456
- const { ready, unavailable } = await prepareBenchmarks(opts.benchmarks, opts.resolveAdapter ?? resolveAdapter, opts);
457
- const jobs = [];
458
- for (const { benchmark, adapter, tasks } of ready)
459
- for (const cell of opts.cells) for (const task of tasks) for (let rep = 0; rep < reps; rep += 1) jobs.push({ benchmark, adapter, cell, task, rep });
460
- const perTask = [];
461
- await runPool(jobs, Math.max(1, opts.concurrency ?? 4), async (job, index) => {
462
- const startedAt = Date.now();
463
- let result;
464
- try {
465
- const shotInput = {
466
- adapter: job.adapter,
467
- task: job.task,
468
- cell: job.cell,
469
- routerBaseUrl: opts.routerBaseUrl,
470
- routerKey: opts.routerKey,
471
- ...opts.bridgeUrl ? { bridgeUrl: opts.bridgeUrl } : {},
472
- ...opts.bridgeBearer ? { bridgeBearer: opts.bridgeBearer } : {},
473
- ...opts.sandboxBaseUrl ? { sandboxBaseUrl: opts.sandboxBaseUrl } : {},
474
- ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {},
475
- ...opts.resolveClient ? { resolveClient: opts.resolveClient } : {}
476
- };
477
- const out = loopAttempts > 1 ? await loopedShot(shotInput, shot, loopAttempts) : await shot(shotInput);
478
- const score = await job.adapter.judge(job.task, out.artifact);
479
- result = {
480
- benchmark: job.benchmark,
481
- cell: job.cell.label,
482
- taskId: job.task.id,
483
- rep: job.rep,
484
- resolved: out.ok && score.resolved,
485
- score: out.ok ? score.score : 0,
486
- ok: out.ok,
487
- ...out.detail ?? score.detail ? { detail: combineDetails(out.detail, score.detail) } : {},
488
- wallMs: Date.now() - startedAt
489
- };
490
- } catch (err) {
491
- result = {
492
- benchmark: job.benchmark,
493
- cell: job.cell.label,
494
- taskId: job.task.id,
495
- rep: job.rep,
496
- resolved: false,
497
- score: 0,
498
- ok: false,
499
- detail: err instanceof Error ? err.message.slice(0, 200) : String(err),
500
- wallMs: Date.now() - startedAt
501
- };
502
- }
503
- void index;
504
- perTask.push(result);
505
- opts.onResult?.(result);
506
- return result;
507
- });
508
- const rows = aggregate(perTask);
509
- return {
510
- rows,
511
- perTask,
512
- benchmarks: ready.map((r) => r.benchmark),
513
- cells: opts.cells.map((c) => c.label),
514
- unavailable
515
- };
532
+ if (opts.benchmarks.length === 0) throw new Error("runBenchmarks: no benchmarks selected");
533
+ if (opts.cells.length === 0) throw new Error("runBenchmarks: no cells to run");
534
+ const reps = Math.max(1, opts.reps ?? 1);
535
+ const loopAttempts = Math.max(1, opts.loopAttempts ?? 1);
536
+ const shot = opts.runShot ?? openSandboxShot;
537
+ const { ready, unavailable } = await prepareBenchmarks(opts.benchmarks, opts.resolveAdapter ?? resolveAdapter, opts);
538
+ const jobs = [];
539
+ for (const { benchmark, adapter, tasks } of ready) for (const cell of opts.cells) for (const task of tasks) for (let rep = 0; rep < reps; rep += 1) jobs.push({
540
+ benchmark,
541
+ adapter,
542
+ cell,
543
+ task,
544
+ rep
545
+ });
546
+ const perTask = [];
547
+ await runPool(jobs, Math.max(1, opts.concurrency ?? 4), async (job, index) => {
548
+ const startedAt = Date.now();
549
+ let result;
550
+ try {
551
+ const shotInput = {
552
+ adapter: job.adapter,
553
+ task: job.task,
554
+ cell: job.cell,
555
+ routerBaseUrl: opts.routerBaseUrl,
556
+ routerKey: opts.routerKey,
557
+ ...opts.bridgeUrl ? { bridgeUrl: opts.bridgeUrl } : {},
558
+ ...opts.bridgeBearer ? { bridgeBearer: opts.bridgeBearer } : {},
559
+ ...opts.sandboxBaseUrl ? { sandboxBaseUrl: opts.sandboxBaseUrl } : {},
560
+ ...opts.timeoutMs ? { timeoutMs: opts.timeoutMs } : {},
561
+ ...opts.resolveClient ? { resolveClient: opts.resolveClient } : {}
562
+ };
563
+ const out = loopAttempts > 1 ? await loopedShot(shotInput, shot, loopAttempts) : await shot(shotInput);
564
+ const score = await job.adapter.judge(job.task, out.artifact);
565
+ result = {
566
+ benchmark: job.benchmark,
567
+ cell: job.cell.label,
568
+ taskId: job.task.id,
569
+ rep: job.rep,
570
+ resolved: out.ok && score.resolved,
571
+ score: out.ok ? score.score : 0,
572
+ ok: out.ok,
573
+ ...out.detail ?? score.detail ? { detail: combineDetails(out.detail, score.detail) } : {},
574
+ wallMs: Date.now() - startedAt
575
+ };
576
+ } catch (err) {
577
+ result = {
578
+ benchmark: job.benchmark,
579
+ cell: job.cell.label,
580
+ taskId: job.task.id,
581
+ rep: job.rep,
582
+ resolved: false,
583
+ score: 0,
584
+ ok: false,
585
+ detail: err instanceof Error ? err.message.slice(0, 200) : String(err),
586
+ wallMs: Date.now() - startedAt
587
+ };
588
+ }
589
+ perTask.push(result);
590
+ opts.onResult?.(result);
591
+ return result;
592
+ });
593
+ return {
594
+ rows: aggregate(perTask),
595
+ perTask,
596
+ benchmarks: ready.map((r) => r.benchmark),
597
+ cells: opts.cells.map((c) => c.label),
598
+ unavailable
599
+ };
516
600
  }
517
601
  function aggregate(perTask) {
518
- const byKey = /* @__PURE__ */ new Map();
519
- for (const r of perTask) {
520
- const key = `${r.benchmark}\0${r.cell}`;
521
- const e = byKey.get(key) ?? { benchmark: r.benchmark, cell: r.cell, n: 0, resolved: 0, errored: 0, scoreSum: 0 };
522
- e.n += 1;
523
- if (!r.ok) e.errored += 1;
524
- else {
525
- if (r.resolved) e.resolved += 1;
526
- e.scoreSum += r.score;
527
- }
528
- byKey.set(key, e);
529
- }
530
- const rows = [...byKey.values()].map((e) => {
531
- const denom = Math.max(1, e.n - e.errored);
532
- return {
533
- benchmark: e.benchmark,
534
- cell: e.cell,
535
- n: e.n,
536
- resolved: e.resolved,
537
- errored: e.errored,
538
- resolveRate: e.resolved / denom,
539
- meanScore: e.scoreSum / denom
540
- };
541
- });
542
- rows.sort((a, b) => a.benchmark === b.benchmark ? b.resolveRate - a.resolveRate : a.benchmark < b.benchmark ? -1 : 1);
543
- return rows;
544
- }
602
+ const byKey = /* @__PURE__ */ new Map();
603
+ for (const r of perTask) {
604
+ const key = `${r.benchmark}\u0000${r.cell}`;
605
+ const e = byKey.get(key) ?? {
606
+ benchmark: r.benchmark,
607
+ cell: r.cell,
608
+ n: 0,
609
+ resolved: 0,
610
+ errored: 0,
611
+ scoreSum: 0
612
+ };
613
+ e.n += 1;
614
+ if (!r.ok) e.errored += 1;
615
+ else {
616
+ if (r.resolved) e.resolved += 1;
617
+ e.scoreSum += r.score;
618
+ }
619
+ byKey.set(key, e);
620
+ }
621
+ const rows = [...byKey.values()].map((e) => {
622
+ const denom = Math.max(1, e.n - e.errored);
623
+ return {
624
+ benchmark: e.benchmark,
625
+ cell: e.cell,
626
+ n: e.n,
627
+ resolved: e.resolved,
628
+ errored: e.errored,
629
+ resolveRate: e.resolved / denom,
630
+ meanScore: e.scoreSum / denom
631
+ };
632
+ });
633
+ rows.sort((a, b) => a.benchmark === b.benchmark ? b.resolveRate - a.resolveRate : a.benchmark < b.benchmark ? -1 : 1);
634
+ return rows;
635
+ }
636
+ /** Render the leaderboard as a fixed-width table for a CLI/log. */
545
637
  function printBenchmarksReport(report) {
546
- const lines = [];
547
- const w = Math.max(8, ...report.rows.map((r) => r.cell.length));
548
- const b = Math.max(9, ...report.rows.map((r) => r.benchmark.length));
549
- lines.push(`${"benchmark".padEnd(b)} ${"cell".padEnd(w)} resolve mean n err`);
550
- for (const r of report.rows)
551
- lines.push(
552
- `${r.benchmark.padEnd(b)} ${r.cell.padEnd(w)} ${(r.resolveRate * 100).toFixed(1).padStart(6)}% ${r.meanScore.toFixed(3).padStart(5)} ${String(r.n).padStart(3)} ${String(r.errored).padStart(3)}`
553
- );
554
- for (const u of report.unavailable) lines.push(`(skipped ${u.benchmark}: ${u.reason})`);
555
- return lines.join("\n");
556
- }
557
-
558
- // src/pier-agent.ts
559
- import assert from "assert/strict";
560
- import { createHash } from "crypto";
561
- import { chmod, lstat, mkdir as mkdir2, realpath as realpath2, writeFile } from "fs/promises";
562
- import { dirname, isAbsolute, join as join2, resolve as resolve2 } from "path";
563
- import { executePreparedAgentCandidate } from "@tangle-network/agent-runtime";
564
- import { canonicalJson } from "@tangle-network/agent-eval";
565
-
566
- // src/pier-task-outcome.ts
567
- import { spawn } from "child_process";
568
- import { mkdir, mkdtemp, realpath, rm } from "fs/promises";
569
- import { tmpdir } from "os";
570
- import { join, resolve } from "path";
571
- import {
572
- captureAgentCandidateWorkspaceFiles
573
- } from "@tangle-network/agent-runtime/candidate-execution";
638
+ const lines = [];
639
+ const w = Math.max(8, ...report.rows.map((r) => r.cell.length));
640
+ const b = Math.max(9, ...report.rows.map((r) => r.benchmark.length));
641
+ lines.push(`${"benchmark".padEnd(b)} ${"cell".padEnd(w)} resolve mean n err`);
642
+ for (const r of report.rows) lines.push(`${r.benchmark.padEnd(b)} ${r.cell.padEnd(w)} ${(r.resolveRate * 100).toFixed(1).padStart(6)}% ${r.meanScore.toFixed(3).padStart(5)} ${String(r.n).padStart(3)} ${String(r.errored).padStart(3)}`);
643
+ for (const u of report.unavailable) lines.push(`(skipped ${u.benchmark}: ${u.reason})`);
644
+ return lines.join("\n");
645
+ }
646
+ //#endregion
647
+ //#region src/pier-task-outcome.ts
648
+ /** Reconstruct a runtime task outcome from Pier's evaluator-captured binary patch. */
649
+ /** Apply the official patch in an isolated Git object directory and capture its exact tree. */
574
650
  async function capturePierTaskOutcome(input) {
575
- input.signal?.throwIfAborted();
576
- const repositoryRoot = resolve(input.repositoryRoot);
577
- const head = (await git(repositoryRoot, ["rev-parse", "HEAD"], void 0, {}, input.signal)).stdout.toString("utf8").trim();
578
- const headTree = (await git(repositoryRoot, ["rev-parse", "HEAD^{tree}"], void 0, {}, input.signal)).stdout.toString("utf8").trim();
579
- if (head !== input.baseCommit || headTree !== input.baseTree) {
580
- throw new Error("Pier task outcome repository drifted from its signed base");
581
- }
582
- const gitDir = resolve(
583
- (await git(repositoryRoot, ["rev-parse", "--absolute-git-dir"], void 0, {}, input.signal)).stdout.toString("utf8").trim()
584
- );
585
- if (await realpath(gitDir) !== gitDir || gitDir.includes(":")) {
586
- throw new Error("Pier task outcome Git directory is unsupported");
587
- }
588
- const replacements = (await git(
589
- repositoryRoot,
590
- ["for-each-ref", "--format=%(refname)", "refs/replace"],
591
- void 0,
592
- {},
593
- input.signal
594
- )).stdout.toString("utf8").trim();
595
- if (replacements) throw new Error("Pier task outcome repository contains Git replace refs");
596
- const temporary = await mkdtemp(join(tmpdir(), "pier-task-outcome-"));
597
- try {
598
- const objectDirectory = join(temporary, "objects");
599
- const indexFile = join(temporary, "index");
600
- await mkdir(objectDirectory);
601
- const environment = {
602
- GIT_INDEX_FILE: indexFile,
603
- GIT_OBJECT_DIRECTORY: objectDirectory,
604
- GIT_ALTERNATE_OBJECT_DIRECTORIES: join(gitDir, "objects")
605
- };
606
- await git(repositoryRoot, ["read-tree", input.baseTree], void 0, environment, input.signal);
607
- const patch = Uint8Array.from(input.patch);
608
- if (patch.byteLength > 0) {
609
- await git(
610
- repositoryRoot,
611
- ["apply", "--cached", "--binary", "--whitespace=nowarn", "-"],
612
- patch,
613
- environment,
614
- input.signal
615
- );
616
- }
617
- const resultTree = (await git(repositoryRoot, ["write-tree"], void 0, environment, input.signal)).stdout.toString("utf8").trim();
618
- const entries = parseTreeEntries(
619
- (await git(
620
- repositoryRoot,
621
- ["ls-tree", "-rz", "--full-tree", resultTree],
622
- void 0,
623
- environment,
624
- input.signal
625
- )).stdout
626
- );
627
- if (entries.length === 0) throw new Error("Pier task outcome tree cannot be empty");
628
- const files = await Promise.all(
629
- entries.map(async (entry) => {
630
- if (entry.type !== "blob" || entry.mode !== "100644" && entry.mode !== "100755") {
631
- throw new Error(`Pier task outcome contains a non-regular entry: ${entry.path}`);
632
- }
633
- return {
634
- path: safeGitPath(entry.path),
635
- mode: entry.mode === "100755" ? 493 : 420,
636
- bytes: (await git(
637
- repositoryRoot,
638
- ["cat-file", "blob", entry.object],
639
- void 0,
640
- environment,
641
- input.signal
642
- )).stdout
643
- };
644
- })
645
- );
646
- const captured = await captureAgentCandidateWorkspaceFiles(files, {
647
- limits: { maxEmbeddedArtifactBytes: 512 * 1024 * 1024 }
648
- });
649
- return {
650
- kind: "workspace",
651
- resultTree,
652
- afterState: captured.snapshot.material,
653
- archive: captured.archive,
654
- gitDiff: patch
655
- };
656
- } finally {
657
- await rm(temporary, { recursive: true, force: true });
658
- }
651
+ input.signal?.throwIfAborted();
652
+ const repositoryRoot = resolve(input.repositoryRoot);
653
+ const head = (await git(repositoryRoot, ["rev-parse", "HEAD"], void 0, {}, input.signal)).stdout.toString("utf8").trim();
654
+ const headTree = (await git(repositoryRoot, ["rev-parse", "HEAD^{tree}"], void 0, {}, input.signal)).stdout.toString("utf8").trim();
655
+ if (head !== input.baseCommit || headTree !== input.baseTree) throw new Error("Pier task outcome repository drifted from its signed base");
656
+ const gitDir = resolve((await git(repositoryRoot, ["rev-parse", "--absolute-git-dir"], void 0, {}, input.signal)).stdout.toString("utf8").trim());
657
+ if (await realpath(gitDir) !== gitDir || gitDir.includes(":")) throw new Error("Pier task outcome Git directory is unsupported");
658
+ if ((await git(repositoryRoot, [
659
+ "for-each-ref",
660
+ "--format=%(refname)",
661
+ "refs/replace"
662
+ ], void 0, {}, input.signal)).stdout.toString("utf8").trim()) throw new Error("Pier task outcome repository contains Git replace refs");
663
+ const temporary = await mkdtemp(join(tmpdir(), "pier-task-outcome-"));
664
+ try {
665
+ const objectDirectory = join(temporary, "objects");
666
+ const indexFile = join(temporary, "index");
667
+ await mkdir(objectDirectory);
668
+ const environment = {
669
+ GIT_INDEX_FILE: indexFile,
670
+ GIT_OBJECT_DIRECTORY: objectDirectory,
671
+ GIT_ALTERNATE_OBJECT_DIRECTORIES: join(gitDir, "objects")
672
+ };
673
+ await git(repositoryRoot, ["read-tree", input.baseTree], void 0, environment, input.signal);
674
+ const patch = Uint8Array.from(input.patch);
675
+ if (patch.byteLength > 0) await git(repositoryRoot, [
676
+ "apply",
677
+ "--cached",
678
+ "--binary",
679
+ "--whitespace=nowarn",
680
+ "-"
681
+ ], patch, environment, input.signal);
682
+ const resultTree = (await git(repositoryRoot, ["write-tree"], void 0, environment, input.signal)).stdout.toString("utf8").trim();
683
+ const entries = parseTreeEntries((await git(repositoryRoot, [
684
+ "ls-tree",
685
+ "-rz",
686
+ "--full-tree",
687
+ resultTree
688
+ ], void 0, environment, input.signal)).stdout);
689
+ if (entries.length === 0) throw new Error("Pier task outcome tree cannot be empty");
690
+ const captured = await captureAgentCandidateWorkspaceFiles(await Promise.all(entries.map(async (entry) => {
691
+ if (entry.type !== "blob" || entry.mode !== "100644" && entry.mode !== "100755") throw new Error(`Pier task outcome contains a non-regular entry: ${entry.path}`);
692
+ return {
693
+ path: safeGitPath(entry.path),
694
+ mode: entry.mode === "100755" ? 493 : 420,
695
+ bytes: (await git(repositoryRoot, [
696
+ "cat-file",
697
+ "blob",
698
+ entry.object
699
+ ], void 0, environment, input.signal)).stdout
700
+ };
701
+ })), { limits: { maxEmbeddedArtifactBytes: 512 * 1024 * 1024 } });
702
+ return {
703
+ kind: "workspace",
704
+ resultTree,
705
+ afterState: captured.snapshot.material,
706
+ archive: captured.archive,
707
+ gitDiff: patch
708
+ };
709
+ } finally {
710
+ await rm(temporary, {
711
+ recursive: true,
712
+ force: true
713
+ });
714
+ }
659
715
  }
660
716
  function parseTreeEntries(bytes) {
661
- const raw = Buffer.from(bytes);
662
- const decoded = raw.toString("utf8");
663
- if (!Buffer.from(decoded, "utf8").equals(raw)) {
664
- throw new Error("Pier task outcome contains a non-UTF-8 path");
665
- }
666
- return decoded.split("\0").filter(Boolean).map((row) => {
667
- const tab = row.indexOf(" ");
668
- const [mode, type, object] = row.slice(0, tab).split(" ");
669
- const path = row.slice(tab + 1);
670
- if (tab < 1 || !mode || !type || !object || !path) {
671
- throw new Error("Pier task outcome contains a malformed Git entry");
672
- }
673
- return { mode, type, object, path };
674
- });
717
+ const raw = Buffer.from(bytes);
718
+ const decoded = raw.toString("utf8");
719
+ if (!Buffer.from(decoded, "utf8").equals(raw)) throw new Error("Pier task outcome contains a non-UTF-8 path");
720
+ return decoded.split("\0").filter(Boolean).map((row) => {
721
+ const tab = row.indexOf(" ");
722
+ const [mode, type, object] = row.slice(0, tab).split(" ");
723
+ const path = row.slice(tab + 1);
724
+ if (tab < 1 || !mode || !type || !object || !path) throw new Error("Pier task outcome contains a malformed Git entry");
725
+ return {
726
+ mode,
727
+ type,
728
+ object,
729
+ path
730
+ };
731
+ });
675
732
  }
676
733
  function safeGitPath(path) {
677
- const parts = path.split("/");
678
- if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === "..") || parts[0]?.toLowerCase() === ".git") {
679
- throw new Error(`Pier task outcome contains an unsafe Git path: ${path}`);
680
- }
681
- return path;
734
+ const parts = path.split("/");
735
+ if (!path || path.startsWith("/") || path.includes("\\") || path.includes("\0") || parts.some((part) => !part || part === "." || part === "..") || parts[0]?.toLowerCase() === ".git") throw new Error(`Pier task outcome contains an unsafe Git path: ${path}`);
736
+ return path;
682
737
  }
683
738
  async function git(repositoryRoot, args, input, extraEnvironment, signal) {
684
- signal?.throwIfAborted();
685
- const environment = Object.fromEntries(
686
- Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_"))
687
- );
688
- Object.assign(environment, {
689
- GIT_CONFIG_NOSYSTEM: "1",
690
- GIT_CONFIG_GLOBAL: "/dev/null",
691
- GIT_CONFIG_SYSTEM: "/dev/null",
692
- GIT_TERMINAL_PROMPT: "0",
693
- GIT_NO_REPLACE_OBJECTS: "1",
694
- LC_ALL: "C",
695
- ...extraEnvironment
696
- });
697
- return await new Promise((resolveResult, reject) => {
698
- const child = spawn(
699
- "git",
700
- [
701
- "-c",
702
- "core.hooksPath=/dev/null",
703
- "-c",
704
- "protocol.file.allow=never",
705
- "-C",
706
- repositoryRoot,
707
- ...args
708
- ],
709
- { env: environment, stdio: ["pipe", "pipe", "pipe"], signal }
710
- );
711
- const stdout = [];
712
- const stderr = [];
713
- let outputBytes = 0;
714
- const collect = (target) => (chunk) => {
715
- outputBytes += chunk.byteLength;
716
- if (outputBytes > 64 * 1024 * 1024) {
717
- child.kill("SIGKILL");
718
- reject(new Error(`git ${args[0] ?? "<command>"} exceeded the output limit`));
719
- return;
720
- }
721
- target.push(Buffer.from(chunk));
722
- };
723
- child.stdout.on("data", collect(stdout));
724
- child.stderr.on("data", collect(stderr));
725
- child.once("error", reject);
726
- child.once("close", (code, childSignal) => {
727
- const out = Buffer.concat(stdout);
728
- const err = Buffer.concat(stderr);
729
- if (code !== 0) {
730
- reject(
731
- new Error(
732
- `git ${args[0] ?? "<command>"} failed (${childSignal ?? code}): ${err.toString("utf8").trim()}`
733
- )
734
- );
735
- return;
736
- }
737
- resolveResult({ stdout: out, stderr: err });
738
- });
739
- child.stdin.end(input);
740
- });
741
- }
742
-
743
- // src/pier-agent.ts
744
- var adapterImportPath = "pier_agents.tangle_candidate:TangleCandidateAgent";
745
- var sha256Pattern = /^sha256:[a-f0-9]{64}$/;
739
+ signal?.throwIfAborted();
740
+ const environment = Object.fromEntries(Object.entries(process.env).filter(([name]) => !name.startsWith("GIT_")));
741
+ Object.assign(environment, {
742
+ GIT_CONFIG_NOSYSTEM: "1",
743
+ GIT_CONFIG_GLOBAL: "/dev/null",
744
+ GIT_CONFIG_SYSTEM: "/dev/null",
745
+ GIT_TERMINAL_PROMPT: "0",
746
+ GIT_NO_REPLACE_OBJECTS: "1",
747
+ LC_ALL: "C",
748
+ ...extraEnvironment
749
+ });
750
+ return await new Promise((resolveResult, reject) => {
751
+ const child = spawn("git", [
752
+ "-c",
753
+ "core.hooksPath=/dev/null",
754
+ "-c",
755
+ "protocol.file.allow=never",
756
+ "-C",
757
+ repositoryRoot,
758
+ ...args
759
+ ], {
760
+ env: environment,
761
+ stdio: [
762
+ "pipe",
763
+ "pipe",
764
+ "pipe"
765
+ ],
766
+ signal
767
+ });
768
+ const stdout = [];
769
+ const stderr = [];
770
+ let outputBytes = 0;
771
+ const collect = (target) => (chunk) => {
772
+ outputBytes += chunk.byteLength;
773
+ if (outputBytes > 64 * 1024 * 1024) {
774
+ child.kill("SIGKILL");
775
+ reject(/* @__PURE__ */ new Error(`git ${args[0] ?? "<command>"} exceeded the output limit`));
776
+ return;
777
+ }
778
+ target.push(Buffer.from(chunk));
779
+ };
780
+ child.stdout.on("data", collect(stdout));
781
+ child.stderr.on("data", collect(stderr));
782
+ child.once("error", reject);
783
+ child.once("close", (code, childSignal) => {
784
+ const out = Buffer.concat(stdout);
785
+ const err = Buffer.concat(stderr);
786
+ if (code !== 0) {
787
+ reject(/* @__PURE__ */ new Error(`git ${args[0] ?? "<command>"} failed (${childSignal ?? code}): ${err.toString("utf8").trim()}`));
788
+ return;
789
+ }
790
+ resolveResult({
791
+ stdout: out,
792
+ stderr: err
793
+ });
794
+ });
795
+ child.stdin.end(input);
796
+ });
797
+ }
798
+ //#endregion
799
+ //#region src/pier-agent.ts
800
+ /** Pier transport for a runtime-prepared immutable candidate execution. */
801
+ const adapterImportPath = "pier_agents.tangle_candidate:TangleCandidateAgent";
802
+ const sha256Pattern$1 = /^sha256:[a-f0-9]{64}$/;
803
+ /** Recovery-only runtime executor for an expired attempt owned by another process. */
746
804
  function createPierCandidateRecoveryExecutor(controller) {
747
- return {
748
- execute: async () => {
749
- throw new Error("recovery-only Pier executor cannot start a candidate");
750
- },
751
- stop: async (request) => {
752
- assertTerminationAcknowledged(
753
- await controller.terminateAndWait({
754
- executionId: request.executionId,
755
- executionPlanDigest: request.executionPlanDigest
756
- })
757
- );
758
- return { stopped: true };
759
- },
760
- capture: async (request) => {
761
- const result = await controller.captureResult(request);
762
- if (!result) return {};
763
- return {
764
- evidence: Buffer.from(
765
- canonicalJson({
766
- schemaVersion: 1,
767
- kind: "pier-candidate-recovery-capture",
768
- executionPlanDigest: request.executionPlanDigest,
769
- officialResult: Buffer.from(result.resultBytes).toString("base64"),
770
- taskPatch: Buffer.from(result.taskPatch).toString("base64")
771
- })
772
- )
773
- };
774
- }
775
- };
776
- }
777
- function sha256(bytes) {
778
- return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
779
- }
780
- function nonEmpty(value, label) {
781
- if (!value || value.includes("\0")) throw new Error(`${label} must be non-empty without NUL`);
782
- return value;
805
+ return {
806
+ execute: async () => {
807
+ throw new Error("recovery-only Pier executor cannot start a candidate");
808
+ },
809
+ stop: async (request) => {
810
+ assertTerminationAcknowledged(await controller.terminateAndWait({
811
+ executionId: request.executionId,
812
+ executionPlanDigest: request.executionPlanDigest
813
+ }));
814
+ return { stopped: true };
815
+ },
816
+ capture: async (request) => {
817
+ const result = await controller.captureResult(request);
818
+ if (!result) return {};
819
+ return { evidence: Buffer.from(canonicalJson({
820
+ schemaVersion: 1,
821
+ kind: "pier-candidate-recovery-capture",
822
+ executionPlanDigest: request.executionPlanDigest,
823
+ officialResult: Buffer.from(result.resultBytes).toString("base64"),
824
+ taskPatch: Buffer.from(result.taskPatch).toString("base64")
825
+ })) };
826
+ }
827
+ };
828
+ }
829
+ function sha256$1(bytes) {
830
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
831
+ }
832
+ function nonEmpty$1(value, label) {
833
+ if (!value || value.includes("\0")) throw new Error(`${label} must be non-empty without NUL`);
834
+ return value;
783
835
  }
784
836
  function absoluteHostPath(value, label) {
785
- if (!isAbsolute(value)) throw new Error(`${label} must be an absolute host path`);
786
- return resolve2(value);
837
+ if (!isAbsolute(value)) throw new Error(`${label} must be an absolute host path`);
838
+ return resolve(value);
787
839
  }
788
840
  function digest(value, label) {
789
- if (!sha256Pattern.test(value)) throw new Error(`${label} is not a SHA-256 digest`);
790
- return value;
841
+ if (!sha256Pattern$1.test(value)) throw new Error(`${label} is not a SHA-256 digest`);
842
+ return value;
791
843
  }
792
844
  function record(value, label) {
793
- if (!value || typeof value !== "object" || Array.isArray(value)) {
794
- throw new Error(`${label} must be an object`);
795
- }
796
- return value;
845
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
846
+ return value;
797
847
  }
798
848
  function parseExactJson(bytes, label) {
799
- try {
800
- return JSON.parse(Buffer.from(bytes).toString("utf8"));
801
- } catch (error) {
802
- throw new Error(`${label} bytes are not UTF-8 JSON`, { cause: error });
803
- }
849
+ try {
850
+ return JSON.parse(Buffer.from(bytes).toString("utf8"));
851
+ } catch (error) {
852
+ throw new Error(`${label} bytes are not UTF-8 JSON`, { cause: error });
853
+ }
804
854
  }
805
855
  function safeRelativePath(value, label) {
806
- const parts = value.split("/");
807
- if (!value || value.includes("\0") || value.includes("\\") || isAbsolute(value) || parts.some((part) => !part || part === "." || part === "..")) {
808
- throw new Error(`${label} must be a canonical relative POSIX path`);
809
- }
810
- return value;
811
- }
812
- async function assertRealDirectory(path, label) {
813
- const stats = await lstat(path);
814
- if (!stats.isDirectory() || stats.isSymbolicLink()) {
815
- throw new Error(`${label} must be a real directory`);
816
- }
817
- if (await realpath2(path) !== path) {
818
- throw new Error(`${label} has a symlinked path component`);
819
- }
856
+ const parts = value.split("/");
857
+ if (!value || value.includes("\0") || value.includes("\\") || isAbsolute(value) || parts.some((part) => !part || part === "." || part === "..")) throw new Error(`${label} must be a canonical relative POSIX path`);
858
+ return value;
859
+ }
860
+ async function assertRealDirectory$1(path, label) {
861
+ const stats = await lstat(path);
862
+ if (!stats.isDirectory() || stats.isSymbolicLink()) throw new Error(`${label} must be a real directory`);
863
+ if (await realpath(path) !== path) throw new Error(`${label} has a symlinked path component`);
820
864
  }
821
865
  async function materializeExecutorFiles(root, files, expected, label) {
822
- const expectedByPath = new Map(expected.map((file) => [file.path, file]));
823
- if (expectedByPath.size !== expected.length || files.length !== expected.length) {
824
- throw new Error(`${label} file set differs from signed evidence`);
825
- }
826
- await mkdir2(root, { mode: 448 });
827
- await assertRealDirectory(root, label);
828
- for (const file of files) {
829
- const relative = safeRelativePath(file.path, `${label} file path`);
830
- const identity = expectedByPath.get(relative);
831
- if (!identity) throw new Error(`${label} contains unsigned file ${relative}`);
832
- if (file.mode !== identity.mode || sha256(file.bytes) !== identity.sha256 || identity.byteLength !== void 0 && file.bytes.byteLength !== identity.byteLength) {
833
- throw new Error(`${label} file ${relative} differs from signed evidence`);
834
- }
835
- const target = join2(root, relative);
836
- const parent = dirname(target);
837
- if (parent !== root) await mkdir2(parent, { recursive: true, mode: 448 });
838
- await writeFile(target, file.bytes, { flag: "wx", mode: 384 });
839
- await chmod(target, file.mode);
840
- const stats = await lstat(target);
841
- if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1 || (stats.mode & 511) !== file.mode) {
842
- throw new Error(`${label} file ${relative} is not a singly-linked regular file`);
843
- }
844
- }
866
+ const expectedByPath = new Map(expected.map((file) => [file.path, file]));
867
+ if (expectedByPath.size !== expected.length || files.length !== expected.length) throw new Error(`${label} file set differs from signed evidence`);
868
+ await mkdir(root, { mode: 448 });
869
+ await assertRealDirectory$1(root, label);
870
+ for (const file of files) {
871
+ const relative = safeRelativePath(file.path, `${label} file path`);
872
+ const identity = expectedByPath.get(relative);
873
+ if (!identity) throw new Error(`${label} contains unsigned file ${relative}`);
874
+ if (file.mode !== identity.mode || sha256$1(file.bytes) !== identity.sha256 || identity.byteLength !== void 0 && file.bytes.byteLength !== identity.byteLength) throw new Error(`${label} file ${relative} differs from signed evidence`);
875
+ const target = join(root, relative);
876
+ const parent = dirname(target);
877
+ if (parent !== root) await mkdir(parent, {
878
+ recursive: true,
879
+ mode: 448
880
+ });
881
+ await writeFile(target, file.bytes, {
882
+ flag: "wx",
883
+ mode: 384
884
+ });
885
+ await chmod(target, file.mode);
886
+ const stats = await lstat(target);
887
+ if (!stats.isFile() || stats.isSymbolicLink() || stats.nlink !== 1 || (stats.mode & 511) !== file.mode) throw new Error(`${label} file ${relative} is not a singly-linked regular file`);
888
+ }
845
889
  }
846
890
  function protectedEnvironment(prepared, request) {
847
- const protectedEntries = [];
848
- for (const [name, value] of Object.entries(request.launch.env)) {
849
- const publicValue = prepared.launch.env[name];
850
- if (publicValue === void 0) {
851
- protectedEntries.push([name, value]);
852
- } else if (publicValue !== value) {
853
- throw new Error(`executor request changed signed public environment ${name}`);
854
- }
855
- }
856
- for (const name of Object.keys(prepared.launch.env)) {
857
- if (!(name in request.launch.env)) {
858
- throw new Error(`executor request omitted signed public environment ${name}`);
859
- }
860
- }
861
- return Object.freeze(Object.fromEntries(protectedEntries));
862
- }
891
+ const protectedEntries = [];
892
+ for (const [name, value] of Object.entries(request.launch.env)) {
893
+ const publicValue = prepared.launch.env[name];
894
+ if (publicValue === void 0) protectedEntries.push([name, value]);
895
+ else if (publicValue !== value) throw new Error(`executor request changed signed public environment ${name}`);
896
+ }
897
+ for (const name of Object.keys(prepared.launch.env)) if (!(name in request.launch.env)) throw new Error(`executor request omitted signed public environment ${name}`);
898
+ return Object.freeze(Object.fromEntries(protectedEntries));
899
+ }
900
+ /**
901
+ * Persist the runtime's exact canonical bytes and return only Pier transport
902
+ * arguments. No benchmark-specific plan or candidate-authored telemetry exists.
903
+ */
904
+ /** @internal Package-private transport seam exercised by adapter tests. */
863
905
  async function stagePreparedPierCandidateExecution(options, request) {
864
- const { prepared } = options;
865
- nonEmpty(options.pierVersion, "pierVersion");
866
- const directory = absoluteHostPath(options.directory, "directory");
867
- await mkdir2(directory, { mode: 448 });
868
- await assertRealDirectory(directory, "directory");
869
- if (request.memory.mode !== "disabled") {
870
- throw new Error("Pier transport does not yet implement protected isolated memory");
871
- }
872
- const taskDirectory = join2(directory, "task");
873
- const candidateDirectory = request.inputs.candidate ? join2(directory, "candidate") : void 0;
874
- const profileDirectory = join2(directory, "profile");
875
- await materializeExecutorFiles(
876
- taskDirectory,
877
- request.inputs.task.files,
878
- request.inputs.task.snapshot.material.files,
879
- "task executor input"
880
- );
881
- if (request.inputs.candidate && candidateDirectory) {
882
- await materializeExecutorFiles(
883
- candidateDirectory,
884
- request.inputs.candidate.files,
885
- request.inputs.candidate.snapshot.material.files,
886
- "candidate executor input"
887
- );
888
- }
889
- await materializeExecutorFiles(
890
- profileDirectory,
891
- request.profileActivation.files.map((file) => ({
892
- path: file.path,
893
- mode: file.mode,
894
- bytes: Buffer.from(file.content, "utf8")
895
- })),
896
- request.profilePlan.value.material.files.map((file) => ({
897
- path: file.relPath,
898
- mode: file.mode,
899
- sha256: file.contentSha256
900
- })),
901
- "profile executor input"
902
- );
903
- const planDigest = digest(request.executionPlan.value.digest, "execution plan digest");
904
- if (sha256(request.executionPlan.bytes) !== planDigest) {
905
- throw new Error("prepared execution-plan bytes do not match their runtime digest");
906
- }
907
- assert.deepEqual(
908
- parseExactJson(request.executionPlan.bytes, "execution plan"),
909
- request.executionPlan.value.material,
910
- "prepared execution-plan bytes differ from runtime material"
911
- );
912
- const receiptDigest = digest(
913
- request.materializationReceipt.digest,
914
- "materialization receipt digest"
915
- );
916
- if (sha256(request.materializationReceipt.bytes) !== receiptDigest) {
917
- throw new Error("prepared materialization-receipt bytes do not match their runtime digest");
918
- }
919
- const { digest: _receiptDigest, ...receiptMaterial } = request.materializationReceipt.value;
920
- assert.deepEqual(
921
- parseExactJson(request.materializationReceipt.bytes, "materialization receipt"),
922
- receiptMaterial,
923
- "prepared receipt bytes differ from runtime material"
924
- );
925
- const planPath = join2(directory, "execution-plan.json");
926
- const receiptPath = join2(directory, "materialization-receipt.json");
927
- await Promise.all([
928
- writeFile(planPath, request.executionPlan.bytes, { mode: 384, flag: "wx" }),
929
- writeFile(receiptPath, request.materializationReceipt.bytes, { mode: 384, flag: "wx" })
930
- ]);
931
- const agentArgs = [
932
- "--agent-import-path",
933
- adapterImportPath,
934
- "--model",
935
- nonEmpty(request.resolvedModel.requested, "resolved requested model"),
936
- "--agent-kwarg",
937
- `plan_path=${planPath}`,
938
- "--agent-kwarg",
939
- `receipt_path=${receiptPath}`,
940
- "--agent-kwarg",
941
- `expected_receipt_digest=${receiptDigest}`,
942
- "--agent-kwarg",
943
- `trace_run_id=${nonEmpty(request.trace.runId, "trace run id")}`,
944
- "--agent-kwarg",
945
- `task_dir=${taskDirectory}`,
946
- "--agent-kwarg",
947
- `profile_dir=${profileDirectory}`,
948
- "--agent-kwarg",
949
- `pier_version=${options.pierVersion}`
950
- ];
951
- if (candidateDirectory !== void 0) {
952
- agentArgs.push("--agent-kwarg", `candidate_dir=${candidateDirectory}`);
953
- }
954
- return {
955
- executionId: request.executionId,
956
- directory,
957
- taskDirectory,
958
- ...candidateDirectory ? { candidateDirectory } : {},
959
- profileDirectory,
960
- planPath,
961
- receiptPath,
962
- agentArgs,
963
- evaluatorEnv: protectedEnvironment(prepared, request),
964
- attemptArgs: ["--n-attempts", "1", "--max-retries", "0"]
965
- };
906
+ const { prepared } = options;
907
+ nonEmpty$1(options.pierVersion, "pierVersion");
908
+ const directory = absoluteHostPath(options.directory, "directory");
909
+ await mkdir(directory, { mode: 448 });
910
+ await assertRealDirectory$1(directory, "directory");
911
+ if (request.memory.mode !== "disabled") throw new Error("Pier transport does not yet implement protected isolated memory");
912
+ const taskDirectory = join(directory, "task");
913
+ const candidateDirectory = request.inputs.candidate ? join(directory, "candidate") : void 0;
914
+ const profileDirectory = join(directory, "profile");
915
+ await materializeExecutorFiles(taskDirectory, request.inputs.task.files, request.inputs.task.snapshot.material.files, "task executor input");
916
+ if (request.inputs.candidate && candidateDirectory) await materializeExecutorFiles(candidateDirectory, request.inputs.candidate.files, request.inputs.candidate.snapshot.material.files, "candidate executor input");
917
+ await materializeExecutorFiles(profileDirectory, request.profileActivation.files.map((file) => ({
918
+ path: file.path,
919
+ mode: file.mode,
920
+ bytes: Buffer.from(file.content, "utf8")
921
+ })), request.profilePlan.value.material.files.map((file) => ({
922
+ path: file.relPath,
923
+ mode: file.mode,
924
+ sha256: file.contentSha256
925
+ })), "profile executor input");
926
+ const planDigest = digest(request.executionPlan.value.digest, "execution plan digest");
927
+ if (sha256$1(request.executionPlan.bytes) !== planDigest) throw new Error("prepared execution-plan bytes do not match their runtime digest");
928
+ assert.deepEqual(parseExactJson(request.executionPlan.bytes, "execution plan"), request.executionPlan.value.material, "prepared execution-plan bytes differ from runtime material");
929
+ const receiptDigest = digest(request.materializationReceipt.digest, "materialization receipt digest");
930
+ if (sha256$1(request.materializationReceipt.bytes) !== receiptDigest) throw new Error("prepared materialization-receipt bytes do not match their runtime digest");
931
+ const { digest: _receiptDigest, ...receiptMaterial } = request.materializationReceipt.value;
932
+ assert.deepEqual(parseExactJson(request.materializationReceipt.bytes, "materialization receipt"), receiptMaterial, "prepared receipt bytes differ from runtime material");
933
+ const planPath = join(directory, "execution-plan.json");
934
+ const receiptPath = join(directory, "materialization-receipt.json");
935
+ await Promise.all([writeFile(planPath, request.executionPlan.bytes, {
936
+ mode: 384,
937
+ flag: "wx"
938
+ }), writeFile(receiptPath, request.materializationReceipt.bytes, {
939
+ mode: 384,
940
+ flag: "wx"
941
+ })]);
942
+ const agentArgs = [
943
+ "--agent-import-path",
944
+ adapterImportPath,
945
+ "--model",
946
+ nonEmpty$1(request.resolvedModel.requested, "resolved requested model"),
947
+ "--agent-kwarg",
948
+ `plan_path=${planPath}`,
949
+ "--agent-kwarg",
950
+ `receipt_path=${receiptPath}`,
951
+ "--agent-kwarg",
952
+ `expected_receipt_digest=${receiptDigest}`,
953
+ "--agent-kwarg",
954
+ `trace_run_id=${nonEmpty$1(request.trace.runId, "trace run id")}`,
955
+ "--agent-kwarg",
956
+ `task_dir=${taskDirectory}`,
957
+ "--agent-kwarg",
958
+ `profile_dir=${profileDirectory}`,
959
+ "--agent-kwarg",
960
+ `pier_version=${options.pierVersion}`
961
+ ];
962
+ if (candidateDirectory !== void 0) agentArgs.push("--agent-kwarg", `candidate_dir=${candidateDirectory}`);
963
+ return {
964
+ executionId: request.executionId,
965
+ directory,
966
+ taskDirectory,
967
+ ...candidateDirectory ? { candidateDirectory } : {},
968
+ profileDirectory,
969
+ planPath,
970
+ receiptPath,
971
+ agentArgs,
972
+ evaluatorEnv: protectedEnvironment(prepared, request),
973
+ attemptArgs: [
974
+ "--n-attempts",
975
+ "1",
976
+ "--max-retries",
977
+ "0"
978
+ ]
979
+ };
966
980
  }
967
981
  function exceptionType(value) {
968
- const info = value && typeof value === "object" ? value : void 0;
969
- for (const key of ["exception_type", "type", "name"]) {
970
- if (typeof info?.[key] === "string") return info[key];
971
- }
972
- return void 0;
973
- }
982
+ const info = value && typeof value === "object" ? value : void 0;
983
+ for (const key of [
984
+ "exception_type",
985
+ "type",
986
+ "name"
987
+ ]) if (typeof info?.[key] === "string") return info[key];
988
+ }
989
+ /** Read only identity and termination from Pier; usage always comes from agent-eval. */
990
+ /** @internal Package-private result parser exercised by adapter tests. */
974
991
  function protectedCaptureFromPierResult(request, value) {
975
- const result = record(value, "Pier trial result");
976
- const agentResult = record(result.agent_result, "Pier agent_result");
977
- const metadata = record(agentResult.metadata, "Pier agent_result.metadata");
978
- const expected = {
979
- executionId: request.executionId,
980
- bundleDigest: request.executionPlan.value.material.runCell.bundleDigest,
981
- executionPlanDigest: request.executionPlan.value.digest,
982
- materializationReceiptDigest: request.materializationReceipt.digest
983
- };
984
- for (const [name, identity] of Object.entries(expected)) {
985
- if (metadata[name] !== identity) {
986
- throw new Error(`Pier result ${name} does not match the prepared execution`);
987
- }
988
- }
989
- const reported = record(metadata.termination, "Pier termination");
990
- const type = exceptionType(result.exception_info);
991
- if (type?.includes("AgentTimeout")) {
992
- return {
993
- executionId: request.executionId,
994
- termination: {
995
- kind: "timeout",
996
- timeoutMs: request.hardLimits.timeoutMs
997
- }
998
- };
999
- }
1000
- if (type?.includes("Cancelled") || reported.kind === "cancelled") {
1001
- return { executionId: request.executionId, termination: { kind: "cancelled" } };
1002
- }
1003
- if (reported.kind === "exit" && Number.isInteger(reported.exitCode)) {
1004
- return {
1005
- executionId: request.executionId,
1006
- termination: { kind: "exit", exitCode: reported.exitCode }
1007
- };
1008
- }
1009
- throw new Error(`Pier result has no recognized truthful termination${type ? ` (${type})` : ""}`);
992
+ const result = record(value, "Pier trial result");
993
+ const metadata = record(record(result.agent_result, "Pier agent_result").metadata, "Pier agent_result.metadata");
994
+ const expected = {
995
+ executionId: request.executionId,
996
+ bundleDigest: request.executionPlan.value.material.runCell.bundleDigest,
997
+ executionPlanDigest: request.executionPlan.value.digest,
998
+ materializationReceiptDigest: request.materializationReceipt.digest
999
+ };
1000
+ for (const [name, identity] of Object.entries(expected)) if (metadata[name] !== identity) throw new Error(`Pier result ${name} does not match the prepared execution`);
1001
+ const reported = record(metadata.termination, "Pier termination");
1002
+ const type = exceptionType(result.exception_info);
1003
+ if (type?.includes("AgentTimeout")) return {
1004
+ executionId: request.executionId,
1005
+ termination: {
1006
+ kind: "timeout",
1007
+ timeoutMs: request.hardLimits.timeoutMs
1008
+ }
1009
+ };
1010
+ if (type?.includes("Cancelled") || reported.kind === "cancelled") return {
1011
+ executionId: request.executionId,
1012
+ termination: { kind: "cancelled" }
1013
+ };
1014
+ if (reported.kind === "exit" && Number.isInteger(reported.exitCode)) return {
1015
+ executionId: request.executionId,
1016
+ termination: {
1017
+ kind: "exit",
1018
+ exitCode: reported.exitCode
1019
+ }
1020
+ };
1021
+ throw new Error(`Pier result has no recognized truthful termination${type ? ` (${type})` : ""}`);
1010
1022
  }
1011
1023
  function sealPierTrialResult(value) {
1012
- if (!(value.resultBytes instanceof Uint8Array) || !(value.taskPatch instanceof Uint8Array)) {
1013
- throw new Error("Pier trial result must contain raw result and patch bytes");
1014
- }
1015
- const parsed = parseExactJson(value.resultBytes, "Pier result.json");
1016
- assert.deepEqual(parsed, value.value, "Pier parsed result differs from result.json bytes");
1017
- const resultBytes = Uint8Array.from(value.resultBytes);
1018
- const taskPatch = Uint8Array.from(value.taskPatch);
1019
- return Object.freeze({
1020
- value: parsed,
1021
- get resultBytes() {
1022
- return Uint8Array.from(resultBytes);
1023
- },
1024
- get taskPatch() {
1025
- return Uint8Array.from(taskPatch);
1026
- }
1027
- });
1024
+ if (!(value.resultBytes instanceof Uint8Array) || !(value.taskPatch instanceof Uint8Array)) throw new Error("Pier trial result must contain raw result and patch bytes");
1025
+ const parsed = parseExactJson(value.resultBytes, "Pier result.json");
1026
+ assert.deepEqual(parsed, value.value, "Pier parsed result differs from result.json bytes");
1027
+ const resultBytes = Uint8Array.from(value.resultBytes);
1028
+ const taskPatch = Uint8Array.from(value.taskPatch);
1029
+ return Object.freeze({
1030
+ value: parsed,
1031
+ get resultBytes() {
1032
+ return Uint8Array.from(resultBytes);
1033
+ },
1034
+ get taskPatch() {
1035
+ return Uint8Array.from(taskPatch);
1036
+ }
1037
+ });
1028
1038
  }
1029
1039
  function abortReason(signal) {
1030
- const reason = signal.reason;
1031
- if (reason instanceof Error) return reason;
1032
- return new Error(`Pier candidate execution aborted${reason ? `: ${String(reason)}` : ""}`);
1040
+ const reason = signal.reason;
1041
+ if (reason instanceof Error) return reason;
1042
+ return /* @__PURE__ */ new Error(`Pier candidate execution aborted${reason ? `: ${String(reason)}` : ""}`);
1033
1043
  }
1034
1044
  function assertTerminationAcknowledged(acknowledgement) {
1035
- if (acknowledgement.processExited !== true || acknowledgement.containersRemoved !== true) {
1036
- throw new Error("Pier termination did not acknowledge process and container death");
1037
- }
1045
+ if (acknowledgement.processExited !== true || acknowledgement.containersRemoved !== true) throw new Error("Pier termination did not acknowledge process and container death");
1038
1046
  }
1047
+ /** @internal Package-private cancellation seam exercised by adapter tests. */
1039
1048
  async function awaitAbortableTrial(trial, signal) {
1040
- if (signal.aborted) {
1041
- assertTerminationAcknowledged(await trial.terminateAndWait());
1042
- throw abortReason(signal);
1043
- }
1044
- return await new Promise((resolveResult, reject) => {
1045
- let settled = false;
1046
- let aborting = false;
1047
- const settle = (callback) => {
1048
- if (settled) return;
1049
- settled = true;
1050
- signal.removeEventListener("abort", onAbort);
1051
- callback();
1052
- };
1053
- const onAbort = () => {
1054
- aborting = true;
1055
- void trial.terminateAndWait().then(
1056
- (acknowledgement) => {
1057
- try {
1058
- assertTerminationAcknowledged(acknowledgement);
1059
- settle(() => reject(abortReason(signal)));
1060
- } catch (error) {
1061
- settle(() => reject(error));
1062
- }
1063
- },
1064
- (error) => settle(() => reject(new Error("Pier termination failed", { cause: error })))
1065
- );
1066
- };
1067
- signal.addEventListener("abort", onAbort, { once: true });
1068
- trial.result.then(
1069
- (result) => {
1070
- if (!aborting && !signal.aborted) settle(() => resolveResult(result));
1071
- },
1072
- (error) => {
1073
- if (!aborting && !signal.aborted) settle(() => reject(error));
1074
- }
1075
- );
1076
- });
1077
- }
1049
+ if (signal.aborted) {
1050
+ assertTerminationAcknowledged(await trial.terminateAndWait());
1051
+ throw abortReason(signal);
1052
+ }
1053
+ return await new Promise((resolveResult, reject) => {
1054
+ let settled = false;
1055
+ let aborting = false;
1056
+ const settle = (callback) => {
1057
+ if (settled) return;
1058
+ settled = true;
1059
+ signal.removeEventListener("abort", onAbort);
1060
+ callback();
1061
+ };
1062
+ const onAbort = () => {
1063
+ aborting = true;
1064
+ trial.terminateAndWait().then((acknowledgement) => {
1065
+ try {
1066
+ assertTerminationAcknowledged(acknowledgement);
1067
+ settle(() => reject(abortReason(signal)));
1068
+ } catch (error) {
1069
+ settle(() => reject(error));
1070
+ }
1071
+ }, (error) => settle(() => reject(new Error("Pier termination failed", { cause: error }))));
1072
+ };
1073
+ signal.addEventListener("abort", onAbort, { once: true });
1074
+ trial.result.then((result) => {
1075
+ if (!aborting && !signal.aborted) settle(() => resolveResult(result));
1076
+ }, (error) => {
1077
+ if (!aborting && !signal.aborted) settle(() => reject(error));
1078
+ });
1079
+ });
1080
+ }
1081
+ /** Execute and finalize through the runtime's only gradable candidate path. */
1078
1082
  async function executePreparedPierCandidate(options) {
1079
- const trials = /* @__PURE__ */ new Map();
1080
- const officialResults = /* @__PURE__ */ new Map();
1081
- const trialIdentity = (executionId, executionPlanDigest) => JSON.stringify([executionId, executionPlanDigest]);
1082
- const executor = {
1083
- execute: async (request, context) => {
1084
- context.signal.throwIfAborted();
1085
- const staged = await stagePreparedPierCandidateExecution(options, request);
1086
- context.signal.throwIfAborted();
1087
- const identity = trialIdentity(request.executionId, request.executionPlan.value.digest);
1088
- if (trials.has(identity)) throw new Error("Pier trial identity is already active");
1089
- const trial = options.controller.start(staged, {
1090
- request,
1091
- traceStore: context.traceStore,
1092
- signal: context.signal,
1093
- deadlineAtMs: context.deadlineAtMs
1094
- });
1095
- if (trial.identity.executionId !== request.executionId || trial.identity.executionPlanDigest !== request.executionPlan.value.digest) {
1096
- assertTerminationAcknowledged(await trial.terminateAndWait());
1097
- throw new Error("Pier controller returned a different durable trial identity");
1098
- }
1099
- const active = { handle: trial };
1100
- trials.set(identity, active);
1101
- const result = sealPierTrialResult(await awaitAbortableTrial(trial, context.signal));
1102
- active.result = result;
1103
- officialResults.set(request.executionId, result);
1104
- return protectedCaptureFromPierResult(request, result.value);
1105
- },
1106
- stop: async (request) => {
1107
- assertTerminationAcknowledged(
1108
- await options.controller.terminateAndWait({
1109
- executionId: request.executionId,
1110
- executionPlanDigest: request.executionPlanDigest
1111
- })
1112
- );
1113
- return { stopped: true };
1114
- },
1115
- capture: async (request) => {
1116
- const identity = trialIdentity(request.executionId, request.executionPlanDigest);
1117
- const active = trials.get(identity);
1118
- let result = active?.result;
1119
- if (!result && active) {
1120
- try {
1121
- result = sealPierTrialResult(await active.handle.result);
1122
- } catch {
1123
- result = void 0;
1124
- }
1125
- }
1126
- if (!result) {
1127
- const recovered = await options.controller.captureResult(request);
1128
- result = recovered ? sealPierTrialResult(recovered) : void 0;
1129
- }
1130
- if (!result) return {};
1131
- officialResults.set(request.executionId, result);
1132
- const task = options.prepared.benchmark.task;
1133
- const outcome = task.outcome;
1134
- if (outcome.kind !== "workspace") {
1135
- throw new Error("Pier candidate execution requires a workspace task outcome");
1136
- }
1137
- const repository = task.repository;
1138
- if (!repository) throw new Error("Pier workspace task is missing repository identity");
1139
- const taskOutcome = await capturePierTaskOutcome({
1140
- repositoryRoot: options.prepared.roots.staging.taskRoot,
1141
- baseCommit: repository.baseCommit,
1142
- baseTree: repository.baseTree,
1143
- patch: result.taskPatch
1144
- });
1145
- trials.delete(identity);
1146
- return { taskOutcome };
1147
- }
1148
- };
1149
- const grader = {
1150
- name: options.grader.name,
1151
- version: options.grader.version,
1152
- artifact: options.grader.artifact,
1153
- run: async (input) => {
1154
- const official = officialResults.get(input.executionId);
1155
- if (!official) throw new Error("Pier official result is missing for executable grading");
1156
- return await options.grader.run({
1157
- ...input,
1158
- officialResult: {
1159
- value: parseExactJson(official.resultBytes, "Pier result.json"),
1160
- bytes: Uint8Array.from(official.resultBytes)
1161
- }
1162
- });
1163
- }
1164
- };
1165
- try {
1166
- return await executePreparedAgentCandidate(options.prepared, {
1167
- executor,
1168
- grader,
1169
- outputArtifacts: options.outputArtifacts,
1170
- traceStore: options.traceStore,
1171
- claimStore: options.claimStore
1172
- });
1173
- } finally {
1174
- officialResults.clear();
1175
- trials.clear();
1176
- }
1177
- }
1178
-
1179
- // src/pier-trial-controller.ts
1180
- import { execFileSync, spawn as spawn2 } from "child_process";
1181
- import { createHash as createHash2 } from "crypto";
1182
- import {
1183
- accessSync,
1184
- closeSync,
1185
- constants,
1186
- existsSync,
1187
- fsyncSync,
1188
- lstatSync,
1189
- mkdirSync as mkdirSync2,
1190
- openSync,
1191
- readFileSync,
1192
- readdirSync,
1193
- realpathSync,
1194
- renameSync,
1195
- rmSync,
1196
- writeFileSync as writeFileSync2
1197
- } from "fs";
1198
- import { isAbsolute as isAbsolute2, join as join3, resolve as resolve3 } from "path";
1199
- import { fileURLToPath } from "url";
1200
- var identityFile = "identity.json";
1201
- var terminalFile = "terminal.json";
1202
- var stopFile = "stop-requested";
1203
- var sha256Pattern2 = /^sha256:[a-f0-9]{64}$/;
1204
- var defaultDockerConnectionId = "local-default";
1205
- function nonEmpty2(value, label) {
1206
- if (!value || value.includes("\0")) throw new Error(`${label} must be non-empty without NUL`);
1207
- return value;
1083
+ const trials = /* @__PURE__ */ new Map();
1084
+ const officialResults = /* @__PURE__ */ new Map();
1085
+ const trialIdentity = (executionId, executionPlanDigest) => JSON.stringify([executionId, executionPlanDigest]);
1086
+ const executor = {
1087
+ execute: async (request, context) => {
1088
+ context.signal.throwIfAborted();
1089
+ const staged = await stagePreparedPierCandidateExecution(options, request);
1090
+ context.signal.throwIfAborted();
1091
+ const identity = trialIdentity(request.executionId, request.executionPlan.value.digest);
1092
+ if (trials.has(identity)) throw new Error("Pier trial identity is already active");
1093
+ const trial = options.controller.start(staged, {
1094
+ request,
1095
+ traceStore: context.traceStore,
1096
+ signal: context.signal,
1097
+ deadlineAtMs: context.deadlineAtMs
1098
+ });
1099
+ if (trial.identity.executionId !== request.executionId || trial.identity.executionPlanDigest !== request.executionPlan.value.digest) {
1100
+ assertTerminationAcknowledged(await trial.terminateAndWait());
1101
+ throw new Error("Pier controller returned a different durable trial identity");
1102
+ }
1103
+ const active = { handle: trial };
1104
+ trials.set(identity, active);
1105
+ const result = sealPierTrialResult(await awaitAbortableTrial(trial, context.signal));
1106
+ active.result = result;
1107
+ officialResults.set(request.executionId, result);
1108
+ return protectedCaptureFromPierResult(request, result.value);
1109
+ },
1110
+ stop: async (request) => {
1111
+ assertTerminationAcknowledged(await options.controller.terminateAndWait({
1112
+ executionId: request.executionId,
1113
+ executionPlanDigest: request.executionPlanDigest
1114
+ }));
1115
+ return { stopped: true };
1116
+ },
1117
+ capture: async (request) => {
1118
+ const identity = trialIdentity(request.executionId, request.executionPlanDigest);
1119
+ const active = trials.get(identity);
1120
+ let result = active?.result;
1121
+ if (!result && active) try {
1122
+ result = sealPierTrialResult(await active.handle.result);
1123
+ } catch {
1124
+ result = void 0;
1125
+ }
1126
+ if (!result) {
1127
+ const recovered = await options.controller.captureResult(request);
1128
+ result = recovered ? sealPierTrialResult(recovered) : void 0;
1129
+ }
1130
+ if (!result) return {};
1131
+ officialResults.set(request.executionId, result);
1132
+ const task = options.prepared.benchmark.task;
1133
+ if (task.outcome.kind !== "workspace") throw new Error("Pier candidate execution requires a workspace task outcome");
1134
+ const repository = task.repository;
1135
+ if (!repository) throw new Error("Pier workspace task is missing repository identity");
1136
+ const taskOutcome = await capturePierTaskOutcome({
1137
+ repositoryRoot: options.prepared.roots.staging.taskRoot,
1138
+ baseCommit: repository.baseCommit,
1139
+ baseTree: repository.baseTree,
1140
+ patch: result.taskPatch
1141
+ });
1142
+ trials.delete(identity);
1143
+ return { taskOutcome };
1144
+ }
1145
+ };
1146
+ const grader = {
1147
+ name: options.grader.name,
1148
+ version: options.grader.version,
1149
+ artifact: options.grader.artifact,
1150
+ run: async (input) => {
1151
+ const official = officialResults.get(input.executionId);
1152
+ if (!official) throw new Error("Pier official result is missing for executable grading");
1153
+ return await options.grader.run({
1154
+ ...input,
1155
+ officialResult: {
1156
+ value: parseExactJson(official.resultBytes, "Pier result.json"),
1157
+ bytes: Uint8Array.from(official.resultBytes)
1158
+ }
1159
+ });
1160
+ }
1161
+ };
1162
+ try {
1163
+ return await executePreparedAgentCandidate(options.prepared, {
1164
+ executor,
1165
+ grader,
1166
+ outputArtifacts: options.outputArtifacts,
1167
+ traceStore: options.traceStore,
1168
+ claimStore: options.claimStore
1169
+ });
1170
+ } finally {
1171
+ officialResults.clear();
1172
+ trials.clear();
1173
+ }
1174
+ }
1175
+ //#endregion
1176
+ //#region src/pier-trial-controller.ts
1177
+ /** Restart-safe evaluator ownership for one Pier process and its Docker projects. */
1178
+ const identityFile = "identity.json";
1179
+ const terminalFile = "terminal.json";
1180
+ const stopFile = "stop-requested";
1181
+ const sha256Pattern = /^sha256:[a-f0-9]{64}$/;
1182
+ const defaultDockerConnectionId = "local-default";
1183
+ function nonEmpty(value, label) {
1184
+ if (!value || value.includes("\0")) throw new Error(`${label} must be non-empty without NUL`);
1185
+ return value;
1208
1186
  }
1209
1187
  function stableDockerConnectionId(value) {
1210
- if (!/^[A-Za-z0-9._:-]{1,200}$/.test(value)) {
1211
- throw new Error("Docker connection id must be a non-secret stable name");
1212
- }
1213
- return value;
1188
+ if (!/^[A-Za-z0-9._:-]{1,200}$/.test(value)) throw new Error("Docker connection id must be a non-secret stable name");
1189
+ return value;
1214
1190
  }
1215
1191
  function absolutePath(value, label) {
1216
- if (!isAbsolute2(value)) throw new Error(`${label} must be an absolute path`);
1217
- return resolve3(value);
1192
+ if (!isAbsolute(value)) throw new Error(`${label} must be an absolute path`);
1193
+ return resolve(value);
1218
1194
  }
1219
1195
  function validateEnvironment(environment, label) {
1220
- for (const [name, value] of Object.entries(environment)) {
1221
- if (!name || name.includes("\0") || value?.includes("\0")) {
1222
- throw new Error(`${label} contains an invalid name or value`);
1223
- }
1224
- }
1196
+ for (const [name, value] of Object.entries(environment)) if (!name || name.includes("\0") || value?.includes("\0")) throw new Error(`${label} contains an invalid name or value`);
1225
1197
  }
1226
1198
  function resolveExecutable(command, searchPath, label) {
1227
- nonEmpty2(command, label);
1228
- const candidates = command.includes("/") ? [absolutePath(command, label)] : (searchPath ?? "").split(":").filter(Boolean).map((directory) => join3(directory, command));
1229
- for (const candidate of candidates) {
1230
- try {
1231
- accessSync(candidate, constants.X_OK);
1232
- return realpathSync(candidate);
1233
- } catch (error) {
1234
- if (error.code !== "ENOENT" && error.code !== "EACCES") {
1235
- throw error;
1236
- }
1237
- }
1238
- }
1239
- throw new Error(`${label} is not executable on the evaluator PATH`);
1199
+ nonEmpty(command, label);
1200
+ const candidates = command.includes("/") ? [absolutePath(command, label)] : (searchPath ?? "").split(":").filter(Boolean).map((directory) => join(directory, command));
1201
+ for (const candidate of candidates) try {
1202
+ accessSync(candidate, constants.X_OK);
1203
+ return realpathSync(candidate);
1204
+ } catch (error) {
1205
+ if (error.code !== "ENOENT" && error.code !== "EACCES") throw error;
1206
+ }
1207
+ throw new Error(`${label} is not executable on the evaluator PATH`);
1240
1208
  }
1241
1209
  function syncDirectory(directory) {
1242
- const fd = openSync(directory, "r");
1243
- try {
1244
- fsyncSync(fd);
1245
- } finally {
1246
- closeSync(fd);
1247
- }
1210
+ const fd = openSync(directory, "r");
1211
+ try {
1212
+ fsyncSync(fd);
1213
+ } finally {
1214
+ closeSync(fd);
1215
+ }
1248
1216
  }
1249
1217
  function writeJsonAtomic(directory, name, value) {
1250
- const target = join3(directory, name);
1251
- const temporary = join3(directory, `.${name}.${process.pid}.${Date.now()}.tmp`);
1252
- const fd = openSync(temporary, "wx", 384);
1253
- try {
1254
- writeFileSync2(fd, `${JSON.stringify(value)}
1255
- `, "utf8");
1256
- fsyncSync(fd);
1257
- } finally {
1258
- closeSync(fd);
1259
- }
1260
- renameSync(temporary, target);
1261
- syncDirectory(directory);
1218
+ const target = join(directory, name);
1219
+ const temporary = join(directory, `.${name}.${process.pid}.${Date.now()}.tmp`);
1220
+ const fd = openSync(temporary, "wx", 384);
1221
+ try {
1222
+ writeFileSync(fd, `${JSON.stringify(value)}\n`, "utf8");
1223
+ fsyncSync(fd);
1224
+ } finally {
1225
+ closeSync(fd);
1226
+ }
1227
+ renameSync(temporary, target);
1228
+ syncDirectory(directory);
1262
1229
  }
1263
1230
  function touchDurable(directory, name) {
1264
- try {
1265
- const fd = openSync(join3(directory, name), "wx", 384);
1266
- try {
1267
- fsyncSync(fd);
1268
- } finally {
1269
- closeSync(fd);
1270
- }
1271
- syncDirectory(directory);
1272
- } catch (error) {
1273
- if (error.code !== "EEXIST") throw error;
1274
- }
1231
+ try {
1232
+ const fd = openSync(join(directory, name), "wx", 384);
1233
+ try {
1234
+ fsyncSync(fd);
1235
+ } finally {
1236
+ closeSync(fd);
1237
+ }
1238
+ syncDirectory(directory);
1239
+ } catch (error) {
1240
+ if (error.code !== "EEXIST") throw error;
1241
+ }
1275
1242
  }
1276
1243
  function readJson(path, label) {
1277
- let value;
1278
- try {
1279
- value = JSON.parse(readFileSync(path, "utf8"));
1280
- } catch (error) {
1281
- throw new Error(`${label} is not valid JSON`, { cause: error });
1282
- }
1283
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1284
- throw new Error(`${label} must be an object`);
1285
- }
1286
- return value;
1244
+ let value;
1245
+ try {
1246
+ value = JSON.parse(readFileSync(path, "utf8"));
1247
+ } catch (error) {
1248
+ throw new Error(`${label} is not valid JSON`, { cause: error });
1249
+ }
1250
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
1251
+ return value;
1287
1252
  }
1288
1253
  function parseProcessIdentity(value, label) {
1289
- if (!value || typeof value !== "object" || Array.isArray(value)) {
1290
- throw new Error(`${label} must be an object`);
1291
- }
1292
- const record2 = value;
1293
- if (!Number.isSafeInteger(record2.pid) || !Number.isSafeInteger(record2.processGroupId) || !Number.isSafeInteger(record2.sessionId) || typeof record2.startTicks !== "string" || !/^\d+$/.test(record2.startTicks)) {
1294
- throw new Error(`${label} is malformed`);
1295
- }
1296
- return {
1297
- pid: record2.pid,
1298
- processGroupId: record2.processGroupId,
1299
- sessionId: record2.sessionId,
1300
- startTicks: record2.startTicks
1301
- };
1254
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
1255
+ const record = value;
1256
+ if (!Number.isSafeInteger(record.pid) || !Number.isSafeInteger(record.processGroupId) || !Number.isSafeInteger(record.sessionId) || typeof record.startTicks !== "string" || !/^\d+$/.test(record.startTicks)) throw new Error(`${label} is malformed`);
1257
+ return {
1258
+ pid: record.pid,
1259
+ processGroupId: record.processGroupId,
1260
+ sessionId: record.sessionId,
1261
+ startTicks: record.startTicks
1262
+ };
1302
1263
  }
1303
1264
  function parsePersistedIdentity(path) {
1304
- const record2 = readJson(path, "Pier trial identity");
1305
- if (record2.schemaVersion !== 1 || record2.kind !== "pier-trial-identity" || !["allocating", "starting", "running"].includes(record2.state) || typeof record2.executionId !== "string" || typeof record2.executionPlanDigest !== "string" || !sha256Pattern2.test(record2.executionPlanDigest) || typeof record2.jobsDirectory !== "string" || typeof record2.jobName !== "string" || typeof record2.dockerCommand !== "string" || typeof record2.dockerConnectionId !== "string") {
1306
- throw new Error("Pier trial identity is malformed");
1307
- }
1308
- return {
1309
- schemaVersion: 1,
1310
- kind: "pier-trial-identity",
1311
- state: record2.state,
1312
- executionId: record2.executionId,
1313
- executionPlanDigest: record2.executionPlanDigest,
1314
- ...record2.supervisor ? { supervisor: parseProcessIdentity(record2.supervisor, "Pier supervisor identity") } : {},
1315
- ...record2.pier ? { pier: parseProcessIdentity(record2.pier, "Pier process identity") } : {},
1316
- jobsDirectory: absolutePath(record2.jobsDirectory, "persisted jobs directory"),
1317
- jobName: nonEmpty2(record2.jobName, "persisted job name"),
1318
- dockerCommand: nonEmpty2(record2.dockerCommand, "persisted Docker command"),
1319
- dockerConnectionId: stableDockerConnectionId(record2.dockerConnectionId)
1320
- };
1265
+ const record = readJson(path, "Pier trial identity");
1266
+ if (record.schemaVersion !== 1 || record.kind !== "pier-trial-identity" || ![
1267
+ "allocating",
1268
+ "starting",
1269
+ "running"
1270
+ ].includes(record.state) || typeof record.executionId !== "string" || typeof record.executionPlanDigest !== "string" || !sha256Pattern.test(record.executionPlanDigest) || typeof record.jobsDirectory !== "string" || typeof record.jobName !== "string" || typeof record.dockerCommand !== "string" || typeof record.dockerConnectionId !== "string") throw new Error("Pier trial identity is malformed");
1271
+ return {
1272
+ schemaVersion: 1,
1273
+ kind: "pier-trial-identity",
1274
+ state: record.state,
1275
+ executionId: record.executionId,
1276
+ executionPlanDigest: record.executionPlanDigest,
1277
+ ...record.supervisor ? { supervisor: parseProcessIdentity(record.supervisor, "Pier supervisor identity") } : {},
1278
+ ...record.pier ? { pier: parseProcessIdentity(record.pier, "Pier process identity") } : {},
1279
+ jobsDirectory: absolutePath(record.jobsDirectory, "persisted jobs directory"),
1280
+ jobName: nonEmpty(record.jobName, "persisted job name"),
1281
+ dockerCommand: nonEmpty(record.dockerCommand, "persisted Docker command"),
1282
+ dockerConnectionId: stableDockerConnectionId(record.dockerConnectionId)
1283
+ };
1321
1284
  }
1322
1285
  function parseTerminal(path) {
1323
- const record2 = readJson(path, "Pier trial terminal");
1324
- const allowedKeys = /* @__PURE__ */ new Set([
1325
- "schemaVersion",
1326
- "kind",
1327
- "status",
1328
- "processExited",
1329
- "containersRemoved",
1330
- "removedContainers",
1331
- "exitCode",
1332
- "signal",
1333
- "error",
1334
- "recoveredByPid"
1335
- ]);
1336
- const hasMalformedOptionalField = record2.removedContainers !== void 0 && (!Number.isSafeInteger(record2.removedContainers) || record2.removedContainers < 0) || record2.exitCode !== void 0 && record2.exitCode !== null && !Number.isSafeInteger(record2.exitCode) || record2.signal !== void 0 && record2.signal !== null && (typeof record2.signal !== "string" || !/^SIG[A-Z0-9]+$/.test(record2.signal)) || record2.error !== void 0 && typeof record2.error !== "string" || record2.recoveredByPid !== void 0 && (!Number.isSafeInteger(record2.recoveredByPid) || record2.recoveredByPid < 1);
1337
- if (Object.keys(record2).some((key) => !allowedKeys.has(key)) || record2.schemaVersion !== 1 || record2.kind !== "pier-trial-terminal" || !["completed", "stopped", "failed"].includes(record2.status) || typeof record2.processExited !== "boolean" || typeof record2.containersRemoved !== "boolean" || hasMalformedOptionalField) {
1338
- throw new Error("Pier trial terminal is malformed");
1339
- }
1340
- return {
1341
- schemaVersion: 1,
1342
- kind: "pier-trial-terminal",
1343
- status: record2.status,
1344
- processExited: record2.processExited,
1345
- containersRemoved: record2.containersRemoved,
1346
- ...record2.removedContainers !== void 0 ? { removedContainers: record2.removedContainers } : {},
1347
- ...record2.exitCode !== void 0 ? { exitCode: record2.exitCode } : {},
1348
- ...record2.signal !== void 0 ? { signal: record2.signal } : {},
1349
- ...record2.error !== void 0 ? { error: record2.error } : {},
1350
- ...record2.recoveredByPid !== void 0 ? { recoveredByPid: record2.recoveredByPid } : {}
1351
- };
1286
+ const record = readJson(path, "Pier trial terminal");
1287
+ const allowedKeys = /* @__PURE__ */ new Set([
1288
+ "schemaVersion",
1289
+ "kind",
1290
+ "status",
1291
+ "processExited",
1292
+ "containersRemoved",
1293
+ "removedContainers",
1294
+ "exitCode",
1295
+ "signal",
1296
+ "error",
1297
+ "recoveredByPid"
1298
+ ]);
1299
+ const hasMalformedOptionalField = record.removedContainers !== void 0 && (!Number.isSafeInteger(record.removedContainers) || record.removedContainers < 0) || record.exitCode !== void 0 && record.exitCode !== null && !Number.isSafeInteger(record.exitCode) || record.signal !== void 0 && record.signal !== null && (typeof record.signal !== "string" || !/^SIG[A-Z0-9]+$/.test(record.signal)) || record.error !== void 0 && typeof record.error !== "string" || record.recoveredByPid !== void 0 && (!Number.isSafeInteger(record.recoveredByPid) || record.recoveredByPid < 1);
1300
+ if (Object.keys(record).some((key) => !allowedKeys.has(key)) || record.schemaVersion !== 1 || record.kind !== "pier-trial-terminal" || ![
1301
+ "completed",
1302
+ "stopped",
1303
+ "failed"
1304
+ ].includes(record.status) || typeof record.processExited !== "boolean" || typeof record.containersRemoved !== "boolean" || hasMalformedOptionalField) throw new Error("Pier trial terminal is malformed");
1305
+ return {
1306
+ schemaVersion: 1,
1307
+ kind: "pier-trial-terminal",
1308
+ status: record.status,
1309
+ processExited: record.processExited,
1310
+ containersRemoved: record.containersRemoved,
1311
+ ...record.removedContainers !== void 0 ? { removedContainers: record.removedContainers } : {},
1312
+ ...record.exitCode !== void 0 ? { exitCode: record.exitCode } : {},
1313
+ ...record.signal !== void 0 ? { signal: record.signal } : {},
1314
+ ...record.error !== void 0 ? { error: record.error } : {},
1315
+ ...record.recoveredByPid !== void 0 ? { recoveredByPid: record.recoveredByPid } : {}
1316
+ };
1352
1317
  }
1353
1318
  function processIdentity(pid) {
1354
- const text = readFileSync(`/proc/${pid}/stat`, "utf8");
1355
- const closing = text.lastIndexOf(")");
1356
- if (closing < 0) throw new Error(`cannot parse process identity for ${pid}`);
1357
- const fields = text.slice(closing + 2).trim().split(/\s+/);
1358
- const processGroupId = Number(fields[2]);
1359
- const sessionId = Number(fields[3]);
1360
- const startTicks = fields[19];
1361
- if (!Number.isSafeInteger(processGroupId) || !Number.isSafeInteger(sessionId) || !/^\d+$/.test(startTicks ?? "")) {
1362
- throw new Error(`cannot parse process identity for ${pid}`);
1363
- }
1364
- return { pid, processGroupId, sessionId, startTicks };
1319
+ const text = readFileSync(`/proc/${pid}/stat`, "utf8");
1320
+ const closing = text.lastIndexOf(")");
1321
+ if (closing < 0) throw new Error(`cannot parse process identity for ${pid}`);
1322
+ const fields = text.slice(closing + 2).trim().split(/\s+/);
1323
+ const processGroupId = Number(fields[2]);
1324
+ const sessionId = Number(fields[3]);
1325
+ const startTicks = fields[19];
1326
+ if (!Number.isSafeInteger(processGroupId) || !Number.isSafeInteger(sessionId) || !/^\d+$/.test(startTicks ?? "")) throw new Error(`cannot parse process identity for ${pid}`);
1327
+ return {
1328
+ pid,
1329
+ processGroupId,
1330
+ sessionId,
1331
+ startTicks
1332
+ };
1365
1333
  }
1366
1334
  function processMatches(identity) {
1367
- try {
1368
- const current = processIdentity(identity.pid);
1369
- return current.startTicks === identity.startTicks && current.processGroupId === identity.processGroupId && current.sessionId === identity.sessionId;
1370
- } catch (error) {
1371
- if (error.code === "ENOENT") return false;
1372
- throw error;
1373
- }
1335
+ try {
1336
+ const current = processIdentity(identity.pid);
1337
+ return current.startTicks === identity.startTicks && current.processGroupId === identity.processGroupId && current.sessionId === identity.sessionId;
1338
+ } catch (error) {
1339
+ if (error.code === "ENOENT") return false;
1340
+ throw error;
1341
+ }
1374
1342
  }
1375
1343
  function signalProcess(identity, signal, group) {
1376
- if (!processMatches(identity)) return;
1377
- try {
1378
- process.kill(group ? -identity.processGroupId : identity.pid, signal);
1379
- } catch (error) {
1380
- if (error.code !== "ESRCH") throw error;
1381
- }
1344
+ if (!processMatches(identity)) return;
1345
+ try {
1346
+ process.kill(group ? -identity.processGroupId : identity.pid, signal);
1347
+ } catch (error) {
1348
+ if (error.code !== "ESRCH") throw error;
1349
+ }
1382
1350
  }
1383
1351
  function sessionMembers(sessionId) {
1384
- const members = [];
1385
- for (const entry of readdirSync("/proc", { withFileTypes: true })) {
1386
- if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) continue;
1387
- try {
1388
- const identity = processIdentity(Number(entry.name));
1389
- if (identity.sessionId === sessionId) members.push(identity);
1390
- } catch (error) {
1391
- if (error.code !== "ENOENT") throw error;
1392
- }
1393
- }
1394
- return members;
1352
+ const members = [];
1353
+ for (const entry of readdirSync("/proc", { withFileTypes: true })) {
1354
+ if (!entry.isDirectory() || !/^\d+$/.test(entry.name)) continue;
1355
+ try {
1356
+ const identity = processIdentity(Number(entry.name));
1357
+ if (identity.sessionId === sessionId) members.push(identity);
1358
+ } catch (error) {
1359
+ if (error.code !== "ENOENT") throw error;
1360
+ }
1361
+ }
1362
+ return members;
1395
1363
  }
1396
1364
  async function signalSessionUntilEmpty(identity, signal, timeoutMs, intervalMs) {
1397
- const deadline = Date.now() + timeoutMs;
1398
- while (true) {
1399
- const members = sessionMembers(identity.sessionId);
1400
- if (members.length === 0) return true;
1401
- for (const member of members) signalProcess(member, signal, false);
1402
- if (Date.now() >= deadline) return false;
1403
- await sleep(intervalMs);
1404
- }
1365
+ const deadline = Date.now() + timeoutMs;
1366
+ while (true) {
1367
+ const members = sessionMembers(identity.sessionId);
1368
+ if (members.length === 0) return true;
1369
+ for (const member of members) signalProcess(member, signal, false);
1370
+ if (Date.now() >= deadline) return false;
1371
+ await sleep(intervalMs);
1372
+ }
1405
1373
  }
1406
1374
  function sanitizeProject(value) {
1407
- let project = value.toLowerCase();
1408
- if (!/^[a-z0-9]/.test(project)) project = `0${project}`;
1409
- return project.replace(/[^a-z0-9_-]/g, "-");
1375
+ let project = value.toLowerCase();
1376
+ if (!/^[a-z0-9]/.test(project)) project = `0${project}`;
1377
+ return project.replace(/[^a-z0-9_-]/g, "-");
1410
1378
  }
1411
1379
  function trialProjects(identity) {
1412
- const jobRoot = join3(identity.jobsDirectory, identity.jobName);
1413
- if (!existsSync(jobRoot)) return [];
1414
- return readdirSync(jobRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).map((entry) => sanitizeProject(entry.name));
1380
+ const jobRoot = join(identity.jobsDirectory, identity.jobName);
1381
+ if (!existsSync(jobRoot)) return [];
1382
+ return readdirSync(jobRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory() && !entry.isSymbolicLink()).map((entry) => sanitizeProject(entry.name));
1415
1383
  }
1416
1384
  function matchingContainers(identity, dockerEnvironment) {
1417
- const projects = trialProjects(identity);
1418
- if (projects.length === 0) return [];
1419
- const output = execFileSync(
1420
- identity.dockerCommand,
1421
- ["ps", "-a", "--format", '{{.ID}} {{.Label "com.docker.compose.project"}}'],
1422
- {
1423
- encoding: "utf8",
1424
- env: dockerEnvironment,
1425
- timeout: 3e4,
1426
- maxBuffer: 4 * 1024 * 1024
1427
- }
1428
- );
1429
- return output.split("\n").filter(Boolean).flatMap((line) => {
1430
- const tab = line.indexOf(" ");
1431
- if (tab < 1) return [];
1432
- const id = line.slice(0, tab);
1433
- const project = line.slice(tab + 1);
1434
- return projects.some(
1435
- (expected) => project === expected || project.startsWith(`${expected}__verifier__`)
1436
- ) ? [id] : [];
1437
- });
1385
+ const projects = trialProjects(identity);
1386
+ if (projects.length === 0) return [];
1387
+ return execFileSync(identity.dockerCommand, [
1388
+ "ps",
1389
+ "-a",
1390
+ "--format",
1391
+ "{{.ID}} {{.Label \"com.docker.compose.project\"}}"
1392
+ ], {
1393
+ encoding: "utf8",
1394
+ env: dockerEnvironment,
1395
+ timeout: 3e4,
1396
+ maxBuffer: 4 * 1024 * 1024
1397
+ }).split("\n").filter(Boolean).flatMap((line) => {
1398
+ const tab = line.indexOf(" ");
1399
+ if (tab < 1) return [];
1400
+ const id = line.slice(0, tab);
1401
+ const project = line.slice(tab + 1);
1402
+ return projects.some((expected) => project === expected || project.startsWith(`${expected}__verifier__`)) ? [id] : [];
1403
+ });
1438
1404
  }
1439
1405
  function removeTrialContainers(identity, dockerEnvironment) {
1440
- const containers = matchingContainers(identity, dockerEnvironment);
1441
- if (containers.length > 0) {
1442
- try {
1443
- execFileSync(identity.dockerCommand, ["rm", "-f", ...containers], {
1444
- encoding: "utf8",
1445
- env: dockerEnvironment,
1446
- timeout: 3e4,
1447
- maxBuffer: 4 * 1024 * 1024
1448
- });
1449
- } catch (error) {
1450
- if (matchingContainers(identity, dockerEnvironment).length > 0) throw error;
1451
- }
1452
- }
1453
- const remaining = matchingContainers(identity, dockerEnvironment);
1454
- if (remaining.length > 0) {
1455
- throw new Error(`Pier task containers survived removal: ${remaining.join(", ")}`);
1456
- }
1457
- return containers.length;
1406
+ const containers = matchingContainers(identity, dockerEnvironment);
1407
+ if (containers.length > 0) try {
1408
+ execFileSync(identity.dockerCommand, [
1409
+ "rm",
1410
+ "-f",
1411
+ ...containers
1412
+ ], {
1413
+ encoding: "utf8",
1414
+ env: dockerEnvironment,
1415
+ timeout: 3e4,
1416
+ maxBuffer: 4 * 1024 * 1024
1417
+ });
1418
+ } catch (error) {
1419
+ if (matchingContainers(identity, dockerEnvironment).length > 0) throw error;
1420
+ }
1421
+ const remaining = matchingContainers(identity, dockerEnvironment);
1422
+ if (remaining.length > 0) throw new Error(`Pier task containers survived removal: ${remaining.join(", ")}`);
1423
+ return containers.length;
1458
1424
  }
1459
1425
  function sleep(milliseconds) {
1460
- return new Promise((resolveSleep) => setTimeout(resolveSleep, milliseconds));
1426
+ return new Promise((resolveSleep) => setTimeout(resolveSleep, milliseconds));
1461
1427
  }
1462
1428
  async function waitUntil(predicate, timeoutMs, intervalMs) {
1463
- const deadline = Date.now() + timeoutMs;
1464
- while (!predicate()) {
1465
- if (Date.now() >= deadline) return false;
1466
- await sleep(intervalMs);
1467
- }
1468
- return true;
1429
+ const deadline = Date.now() + timeoutMs;
1430
+ while (!predicate()) {
1431
+ if (Date.now() >= deadline) return false;
1432
+ await sleep(intervalMs);
1433
+ }
1434
+ return true;
1469
1435
  }
1470
1436
  function trialKey(identity) {
1471
- if (!sha256Pattern2.test(identity.executionPlanDigest)) {
1472
- throw new Error("executionPlanDigest must be a SHA-256 digest");
1473
- }
1474
- return createHash2("sha256").update(JSON.stringify([nonEmpty2(identity.executionId, "executionId"), identity.executionPlanDigest])).digest("hex");
1437
+ if (!sha256Pattern.test(identity.executionPlanDigest)) throw new Error("executionPlanDigest must be a SHA-256 digest");
1438
+ return createHash("sha256").update(JSON.stringify([nonEmpty(identity.executionId, "executionId"), identity.executionPlanDigest])).digest("hex");
1475
1439
  }
1476
- function assertRealDirectory2(path, label) {
1477
- const stats = lstatSync(path);
1478
- if (!stats.isDirectory() || stats.isSymbolicLink() || realpathSync(path) !== path) {
1479
- throw new Error(`${label} must be a real canonical directory`);
1480
- }
1440
+ function assertRealDirectory(path, label) {
1441
+ const stats = lstatSync(path);
1442
+ if (!stats.isDirectory() || stats.isSymbolicLink() || realpathSync(path) !== path) throw new Error(`${label} must be a real canonical directory`);
1481
1443
  }
1482
1444
  var FilePierCandidateTrialController = class {
1483
- directory;
1484
- launch;
1485
- readResult;
1486
- dockerConnection;
1487
- supervisorPath;
1488
- pollIntervalMs;
1489
- constructor(options) {
1490
- this.directory = absolutePath(options.directory, "Pier controller directory");
1491
- this.launch = options.launch;
1492
- this.readResult = options.readResult;
1493
- const configuredDocker = options.dockerConnection;
1494
- if (configuredDocker?.id === defaultDockerConnectionId) {
1495
- throw new Error(`${defaultDockerConnectionId} is reserved for the implicit local connection`);
1496
- }
1497
- const dockerConnection = configuredDocker ?? {
1498
- id: defaultDockerConnectionId,
1499
- env: {}
1500
- };
1501
- const connectionId = stableDockerConnectionId(dockerConnection.id);
1502
- validateEnvironment(dockerConnection.env, "Docker connection environment");
1503
- this.dockerConnection = Object.freeze({
1504
- id: connectionId,
1505
- env: Object.freeze({ ...dockerConnection.env })
1506
- });
1507
- this.supervisorPath = absolutePath(
1508
- options.supervisorPath ?? fileURLToPath(new URL("./pier-trial-supervisor.mjs", import.meta.url)),
1509
- "Pier supervisor path"
1510
- );
1511
- this.pollIntervalMs = options.pollIntervalMs ?? 25;
1512
- if (!Number.isSafeInteger(this.pollIntervalMs) || this.pollIntervalMs < 1) {
1513
- throw new Error("pollIntervalMs must be a positive integer");
1514
- }
1515
- mkdirSync2(this.directory, { recursive: true, mode: 448 });
1516
- assertRealDirectory2(this.directory, "Pier controller directory");
1517
- }
1518
- start(staged, context) {
1519
- if (!this.launch) throw new Error("this Pier controller is recovery-only");
1520
- const identity = {
1521
- executionId: context.request.executionId,
1522
- executionPlanDigest: context.request.executionPlan.value.digest
1523
- };
1524
- if (staged.executionId !== identity.executionId) {
1525
- throw new Error("staged Pier execution does not match the runtime request");
1526
- }
1527
- const spec = this.launch(staged, context);
1528
- this.validateSpec(spec);
1529
- for (const [name, value] of Object.entries(this.dockerConnection.env)) {
1530
- if (Object.prototype.hasOwnProperty.call(spec.env, name) && spec.env[name] !== value) {
1531
- throw new Error(`Pier child environment conflicts with Docker connection variable ${name}`);
1532
- }
1533
- }
1534
- const pierEnvironment = Object.freeze({
1535
- ...spec.env,
1536
- ...this.dockerConnection.env
1537
- });
1538
- mkdirSync2(spec.jobsDirectory, { recursive: true, mode: 448 });
1539
- assertRealDirectory2(spec.jobsDirectory, "Pier jobs directory");
1540
- const dockerCommand = resolveExecutable(
1541
- spec.dockerCommand ?? "docker",
1542
- process.env.PATH,
1543
- "Docker command"
1544
- );
1545
- const pierCommand = resolveExecutable(spec.command, pierEnvironment.PATH, "Pier command");
1546
- const controlDirectory = this.controlDirectory(identity);
1547
- try {
1548
- mkdirSync2(controlDirectory, { mode: 448 });
1549
- } catch (error) {
1550
- if (error.code === "EEXIST") {
1551
- throw new Error("Pier trial identity already has durable control state");
1552
- }
1553
- throw error;
1554
- }
1555
- assertRealDirectory2(controlDirectory, "Pier trial control directory");
1556
- const jobRoot = join3(spec.jobsDirectory, spec.jobName);
1557
- let jobRootCreated = false;
1558
- try {
1559
- mkdirSync2(jobRoot, { mode: 448 });
1560
- jobRootCreated = true;
1561
- assertRealDirectory2(jobRoot, "Pier job directory");
1562
- } catch (error) {
1563
- rmSync(controlDirectory, { recursive: true, force: true });
1564
- if (jobRootCreated) rmSync(jobRoot, { recursive: true, force: true });
1565
- if (error.code === "EEXIST") {
1566
- throw new Error("Pier job name must be unique; its job directory already exists");
1567
- }
1568
- throw error;
1569
- }
1570
- const allocating = {
1571
- schemaVersion: 1,
1572
- kind: "pier-trial-identity",
1573
- state: "allocating",
1574
- ...identity,
1575
- jobsDirectory: absolutePath(spec.jobsDirectory, "Pier jobs directory"),
1576
- jobName: nonEmpty2(spec.jobName, "Pier job name"),
1577
- dockerCommand: nonEmpty2(dockerCommand, "Docker command"),
1578
- dockerConnectionId: this.dockerConnection.id
1579
- };
1580
- try {
1581
- writeJsonAtomic(controlDirectory, identityFile, allocating);
1582
- } catch (error) {
1583
- rmSync(controlDirectory, { recursive: true, force: true });
1584
- rmSync(jobRoot, { recursive: true, force: true });
1585
- throw error;
1586
- }
1587
- const supervisor = spawn2(process.execPath, [this.supervisorPath, controlDirectory], {
1588
- detached: true,
1589
- // Launch data arrives over fd 3. The trusted supervisor receives only
1590
- // explicitly declared process variables, never the evaluator environment.
1591
- env: { ...this.dockerConnection.env },
1592
- stdio: ["ignore", "ignore", "ignore", "pipe"]
1593
- });
1594
- if (supervisor.pid === void 0) throw new Error("Pier supervisor started without a pid");
1595
- const controlPipe = supervisor.stdio[3];
1596
- let supervisorIdentity;
1597
- try {
1598
- supervisorIdentity = processIdentity(supervisor.pid);
1599
- const persisted = {
1600
- ...allocating,
1601
- state: "starting",
1602
- supervisor: supervisorIdentity
1603
- };
1604
- writeJsonAtomic(controlDirectory, identityFile, persisted);
1605
- if (!controlPipe) throw new Error("Pier supervisor has no protected control pipe");
1606
- controlPipe.on("error", () => void 0);
1607
- controlPipe.end(
1608
- JSON.stringify({
1609
- command: pierCommand,
1610
- args: spec.args,
1611
- cwd: spec.cwd,
1612
- env: pierEnvironment,
1613
- jobsDirectory: spec.jobsDirectory,
1614
- jobName: spec.jobName,
1615
- dockerCommand
1616
- })
1617
- );
1618
- } catch (error) {
1619
- controlPipe?.destroy();
1620
- if (supervisorIdentity) signalProcess(supervisorIdentity, "SIGKILL", false);
1621
- else {
1622
- try {
1623
- process.kill(supervisor.pid, "SIGKILL");
1624
- } catch (killError) {
1625
- if (killError.code !== "ESRCH") throw killError;
1626
- }
1627
- }
1628
- throw error;
1629
- }
1630
- supervisor.unref();
1631
- controlPipe.unref?.();
1632
- const result = this.waitForResult(controlDirectory, spec, context.deadlineAtMs);
1633
- return {
1634
- identity,
1635
- result,
1636
- terminateAndWait: async () => await this.terminateAndWait(identity)
1637
- };
1638
- }
1639
- async terminateAndWait(requested) {
1640
- const controlDirectory = this.controlDirectory(requested);
1641
- assertRealDirectory2(controlDirectory, "Pier trial control directory");
1642
- const persisted = parsePersistedIdentity(join3(controlDirectory, identityFile));
1643
- if (persisted.executionId !== requested.executionId || persisted.executionPlanDigest !== requested.executionPlanDigest) {
1644
- throw new Error("persisted Pier trial identity differs from the stop request");
1645
- }
1646
- this.assertDockerConnection(persisted);
1647
- touchDurable(controlDirectory, stopFile);
1648
- if (!existsSync(join3(controlDirectory, terminalFile))) {
1649
- if (persisted.supervisor) signalProcess(persisted.supervisor, "SIGTERM", false);
1650
- }
1651
- const terminalAppeared = await waitUntil(
1652
- () => existsSync(join3(controlDirectory, terminalFile)),
1653
- persisted.supervisor ? 1e4 : 1,
1654
- this.pollIntervalMs
1655
- );
1656
- if (!terminalAppeared) await this.forceRecovery(controlDirectory);
1657
- let terminal = parseTerminal(join3(controlDirectory, terminalFile));
1658
- const latest = parsePersistedIdentity(join3(controlDirectory, identityFile));
1659
- const liveProcesses = latest.pier ? sessionMembers(latest.pier.sessionId).length : 0;
1660
- this.assertDockerConnection(latest);
1661
- const liveContainers = matchingContainers(latest, this.dockerConnection.env).length;
1662
- if (liveProcesses > 0 || liveContainers > 0) {
1663
- await this.forceRecovery(controlDirectory);
1664
- terminal = parseTerminal(join3(controlDirectory, terminalFile));
1665
- }
1666
- if (!terminal.processExited || !terminal.containersRemoved) {
1667
- throw new Error(
1668
- `Pier recovery did not prove process/container death${terminal.error ? `: ${terminal.error}` : ""}`
1669
- );
1670
- }
1671
- return { processExited: true, containersRemoved: true };
1672
- }
1673
- async captureResult(requested) {
1674
- const controlDirectory = this.controlDirectory(requested);
1675
- assertRealDirectory2(controlDirectory, "Pier trial control directory");
1676
- const persisted = parsePersistedIdentity(join3(controlDirectory, identityFile));
1677
- if (persisted.executionId !== requested.executionId || persisted.executionPlanDigest !== requested.executionPlanDigest) {
1678
- throw new Error("persisted Pier trial identity differs from the capture request");
1679
- }
1680
- this.assertDockerConnection(persisted);
1681
- const terminal = parseTerminal(join3(controlDirectory, terminalFile));
1682
- if (!terminal.processExited || !terminal.containersRemoved) {
1683
- throw new Error("Pier result capture requires proven process and container death");
1684
- }
1685
- if (!this.readResult) {
1686
- throw new Error("Pier result capture has no restart-safe result reader");
1687
- }
1688
- return await this.readResult({
1689
- jobsDirectory: persisted.jobsDirectory,
1690
- jobName: persisted.jobName
1691
- });
1692
- }
1693
- controlDirectory(identity) {
1694
- return join3(this.directory, trialKey(identity));
1695
- }
1696
- assertDockerConnection(identity) {
1697
- if (identity.dockerConnectionId !== this.dockerConnection.id) {
1698
- throw new Error(
1699
- `Pier trial requires Docker connection ${identity.dockerConnectionId}; configured ${this.dockerConnection.id}`
1700
- );
1701
- }
1702
- }
1703
- validateSpec(spec) {
1704
- nonEmpty2(spec.command, "Pier command");
1705
- if (!Array.isArray(spec.args) || spec.args.some((arg) => typeof arg !== "string" || arg.includes("\0"))) {
1706
- throw new Error("Pier arguments must be strings without NUL");
1707
- }
1708
- absolutePath(spec.cwd, "Pier cwd");
1709
- absolutePath(spec.jobsDirectory, "Pier jobs directory");
1710
- if (!/^[A-Za-z0-9._-]{1,200}$/.test(spec.jobName)) {
1711
- throw new Error("Pier job name must be filesystem-neutral");
1712
- }
1713
- validateEnvironment(spec.env, "Pier child environment");
1714
- }
1715
- async waitForResult(controlDirectory, spec, deadlineAtMs) {
1716
- const waitMs = Math.max(0, deadlineAtMs - Date.now());
1717
- const appeared = await waitUntil(
1718
- () => existsSync(join3(controlDirectory, terminalFile)),
1719
- waitMs,
1720
- this.pollIntervalMs
1721
- );
1722
- if (!appeared) throw new Error("Pier supervisor emitted no terminal acknowledgement");
1723
- const terminal = parseTerminal(join3(controlDirectory, terminalFile));
1724
- if (!terminal.processExited || !terminal.containersRemoved) {
1725
- throw new Error("Pier supervisor did not prove process and container death");
1726
- }
1727
- if (terminal.status !== "completed") {
1728
- throw new Error(terminal.error ? `Pier process failed: ${terminal.error}` : "Pier process stopped");
1729
- }
1730
- return await spec.readResult();
1731
- }
1732
- async forceRecovery(controlDirectory) {
1733
- const latest = parsePersistedIdentity(join3(controlDirectory, identityFile));
1734
- this.assertDockerConnection(latest);
1735
- const errors = [];
1736
- if (latest.pier) {
1737
- try {
1738
- await signalSessionUntilEmpty(latest.pier, "SIGTERM", 2e3, this.pollIntervalMs);
1739
- } catch (error) {
1740
- errors.push(`could not signal Pier session: ${error instanceof Error ? error.message : String(error)}`);
1741
- }
1742
- }
1743
- if (latest.supervisor) {
1744
- try {
1745
- signalProcess(latest.supervisor, "SIGKILL", false);
1746
- if (!await waitUntil(() => !processMatches(latest.supervisor), 5e3, this.pollIntervalMs)) {
1747
- errors.push(`Pier supervisor ${latest.supervisor.pid} survived SIGKILL`);
1748
- }
1749
- } catch (error) {
1750
- errors.push(`could not kill Pier supervisor: ${error instanceof Error ? error.message : String(error)}`);
1751
- }
1752
- }
1753
- if (latest.pier) {
1754
- try {
1755
- if (!await signalSessionUntilEmpty(
1756
- latest.pier,
1757
- "SIGKILL",
1758
- 5e3,
1759
- this.pollIntervalMs
1760
- )) {
1761
- errors.push(`Pier process session ${latest.pier.sessionId} survived SIGKILL`);
1762
- }
1763
- } catch (error) {
1764
- errors.push(`could not kill Pier session: ${error instanceof Error ? error.message : String(error)}`);
1765
- }
1766
- }
1767
- let removedContainers = 0;
1768
- try {
1769
- removedContainers = removeTrialContainers(latest, this.dockerConnection.env);
1770
- } catch (error) {
1771
- errors.push(`could not remove Pier containers: ${error instanceof Error ? error.message : String(error)}`);
1772
- }
1773
- if (errors.length > 0) {
1774
- throw new Error(`Pier recovery incomplete: ${errors.join("; ")}`);
1775
- }
1776
- writeJsonAtomic(controlDirectory, terminalFile, {
1777
- schemaVersion: 1,
1778
- kind: "pier-trial-terminal",
1779
- status: "stopped",
1780
- processExited: true,
1781
- containersRemoved: true,
1782
- removedContainers,
1783
- recoveredByPid: process.pid
1784
- });
1785
- }
1445
+ directory;
1446
+ launch;
1447
+ readResult;
1448
+ dockerConnection;
1449
+ supervisorPath;
1450
+ pollIntervalMs;
1451
+ constructor(options) {
1452
+ this.directory = absolutePath(options.directory, "Pier controller directory");
1453
+ this.launch = options.launch;
1454
+ this.readResult = options.readResult;
1455
+ const configuredDocker = options.dockerConnection;
1456
+ if (configuredDocker?.id === defaultDockerConnectionId) throw new Error(`${defaultDockerConnectionId} is reserved for the implicit local connection`);
1457
+ const dockerConnection = configuredDocker ?? {
1458
+ id: defaultDockerConnectionId,
1459
+ env: {}
1460
+ };
1461
+ const connectionId = stableDockerConnectionId(dockerConnection.id);
1462
+ validateEnvironment(dockerConnection.env, "Docker connection environment");
1463
+ this.dockerConnection = Object.freeze({
1464
+ id: connectionId,
1465
+ env: Object.freeze({ ...dockerConnection.env })
1466
+ });
1467
+ this.supervisorPath = absolutePath(options.supervisorPath ?? fileURLToPath(new URL("./pier-trial-supervisor.mjs", import.meta.url)), "Pier supervisor path");
1468
+ this.pollIntervalMs = options.pollIntervalMs ?? 25;
1469
+ if (!Number.isSafeInteger(this.pollIntervalMs) || this.pollIntervalMs < 1) throw new Error("pollIntervalMs must be a positive integer");
1470
+ mkdirSync(this.directory, {
1471
+ recursive: true,
1472
+ mode: 448
1473
+ });
1474
+ assertRealDirectory(this.directory, "Pier controller directory");
1475
+ }
1476
+ start(staged, context) {
1477
+ if (!this.launch) throw new Error("this Pier controller is recovery-only");
1478
+ const identity = {
1479
+ executionId: context.request.executionId,
1480
+ executionPlanDigest: context.request.executionPlan.value.digest
1481
+ };
1482
+ if (staged.executionId !== identity.executionId) throw new Error("staged Pier execution does not match the runtime request");
1483
+ const spec = this.launch(staged, context);
1484
+ this.validateSpec(spec);
1485
+ for (const [name, value] of Object.entries(this.dockerConnection.env)) if (Object.prototype.hasOwnProperty.call(spec.env, name) && spec.env[name] !== value) throw new Error(`Pier child environment conflicts with Docker connection variable ${name}`);
1486
+ const pierEnvironment = Object.freeze({
1487
+ ...spec.env,
1488
+ ...this.dockerConnection.env
1489
+ });
1490
+ mkdirSync(spec.jobsDirectory, {
1491
+ recursive: true,
1492
+ mode: 448
1493
+ });
1494
+ assertRealDirectory(spec.jobsDirectory, "Pier jobs directory");
1495
+ const dockerCommand = resolveExecutable(spec.dockerCommand ?? "docker", process.env.PATH, "Docker command");
1496
+ const pierCommand = resolveExecutable(spec.command, pierEnvironment.PATH, "Pier command");
1497
+ const controlDirectory = this.controlDirectory(identity);
1498
+ try {
1499
+ mkdirSync(controlDirectory, { mode: 448 });
1500
+ } catch (error) {
1501
+ if (error.code === "EEXIST") throw new Error("Pier trial identity already has durable control state");
1502
+ throw error;
1503
+ }
1504
+ assertRealDirectory(controlDirectory, "Pier trial control directory");
1505
+ const jobRoot = join(spec.jobsDirectory, spec.jobName);
1506
+ let jobRootCreated = false;
1507
+ try {
1508
+ mkdirSync(jobRoot, { mode: 448 });
1509
+ jobRootCreated = true;
1510
+ assertRealDirectory(jobRoot, "Pier job directory");
1511
+ } catch (error) {
1512
+ rmSync(controlDirectory, {
1513
+ recursive: true,
1514
+ force: true
1515
+ });
1516
+ if (jobRootCreated) rmSync(jobRoot, {
1517
+ recursive: true,
1518
+ force: true
1519
+ });
1520
+ if (error.code === "EEXIST") throw new Error("Pier job name must be unique; its job directory already exists");
1521
+ throw error;
1522
+ }
1523
+ const allocating = {
1524
+ schemaVersion: 1,
1525
+ kind: "pier-trial-identity",
1526
+ state: "allocating",
1527
+ ...identity,
1528
+ jobsDirectory: absolutePath(spec.jobsDirectory, "Pier jobs directory"),
1529
+ jobName: nonEmpty(spec.jobName, "Pier job name"),
1530
+ dockerCommand: nonEmpty(dockerCommand, "Docker command"),
1531
+ dockerConnectionId: this.dockerConnection.id
1532
+ };
1533
+ try {
1534
+ writeJsonAtomic(controlDirectory, identityFile, allocating);
1535
+ } catch (error) {
1536
+ rmSync(controlDirectory, {
1537
+ recursive: true,
1538
+ force: true
1539
+ });
1540
+ rmSync(jobRoot, {
1541
+ recursive: true,
1542
+ force: true
1543
+ });
1544
+ throw error;
1545
+ }
1546
+ const supervisor = spawn(process.execPath, [this.supervisorPath, controlDirectory], {
1547
+ detached: true,
1548
+ env: { ...this.dockerConnection.env },
1549
+ stdio: [
1550
+ "ignore",
1551
+ "ignore",
1552
+ "ignore",
1553
+ "pipe"
1554
+ ]
1555
+ });
1556
+ if (supervisor.pid === void 0) throw new Error("Pier supervisor started without a pid");
1557
+ const controlPipe = supervisor.stdio[3];
1558
+ let supervisorIdentity;
1559
+ try {
1560
+ supervisorIdentity = processIdentity(supervisor.pid);
1561
+ const persisted = {
1562
+ ...allocating,
1563
+ state: "starting",
1564
+ supervisor: supervisorIdentity
1565
+ };
1566
+ writeJsonAtomic(controlDirectory, identityFile, persisted);
1567
+ if (!controlPipe) throw new Error("Pier supervisor has no protected control pipe");
1568
+ controlPipe.on("error", () => void 0);
1569
+ controlPipe.end(JSON.stringify({
1570
+ command: pierCommand,
1571
+ args: spec.args,
1572
+ cwd: spec.cwd,
1573
+ env: pierEnvironment,
1574
+ jobsDirectory: spec.jobsDirectory,
1575
+ jobName: spec.jobName,
1576
+ dockerCommand
1577
+ }));
1578
+ } catch (error) {
1579
+ controlPipe?.destroy();
1580
+ if (supervisorIdentity) signalProcess(supervisorIdentity, "SIGKILL", false);
1581
+ else try {
1582
+ process.kill(supervisor.pid, "SIGKILL");
1583
+ } catch (killError) {
1584
+ if (killError.code !== "ESRCH") throw killError;
1585
+ }
1586
+ throw error;
1587
+ }
1588
+ supervisor.unref();
1589
+ controlPipe.unref?.();
1590
+ return {
1591
+ identity,
1592
+ result: this.waitForResult(controlDirectory, spec, context.deadlineAtMs),
1593
+ terminateAndWait: async () => await this.terminateAndWait(identity)
1594
+ };
1595
+ }
1596
+ async terminateAndWait(requested) {
1597
+ const controlDirectory = this.controlDirectory(requested);
1598
+ assertRealDirectory(controlDirectory, "Pier trial control directory");
1599
+ const persisted = parsePersistedIdentity(join(controlDirectory, identityFile));
1600
+ if (persisted.executionId !== requested.executionId || persisted.executionPlanDigest !== requested.executionPlanDigest) throw new Error("persisted Pier trial identity differs from the stop request");
1601
+ this.assertDockerConnection(persisted);
1602
+ touchDurable(controlDirectory, stopFile);
1603
+ if (!existsSync(join(controlDirectory, terminalFile))) {
1604
+ if (persisted.supervisor) signalProcess(persisted.supervisor, "SIGTERM", false);
1605
+ }
1606
+ if (!await waitUntil(() => existsSync(join(controlDirectory, terminalFile)), persisted.supervisor ? 1e4 : 1, this.pollIntervalMs)) await this.forceRecovery(controlDirectory);
1607
+ let terminal = parseTerminal(join(controlDirectory, terminalFile));
1608
+ const latest = parsePersistedIdentity(join(controlDirectory, identityFile));
1609
+ const liveProcesses = latest.pier ? sessionMembers(latest.pier.sessionId).length : 0;
1610
+ this.assertDockerConnection(latest);
1611
+ const liveContainers = matchingContainers(latest, this.dockerConnection.env).length;
1612
+ if (liveProcesses > 0 || liveContainers > 0) {
1613
+ await this.forceRecovery(controlDirectory);
1614
+ terminal = parseTerminal(join(controlDirectory, terminalFile));
1615
+ }
1616
+ if (!terminal.processExited || !terminal.containersRemoved) throw new Error(`Pier recovery did not prove process/container death${terminal.error ? `: ${terminal.error}` : ""}`);
1617
+ return {
1618
+ processExited: true,
1619
+ containersRemoved: true
1620
+ };
1621
+ }
1622
+ async captureResult(requested) {
1623
+ const controlDirectory = this.controlDirectory(requested);
1624
+ assertRealDirectory(controlDirectory, "Pier trial control directory");
1625
+ const persisted = parsePersistedIdentity(join(controlDirectory, identityFile));
1626
+ if (persisted.executionId !== requested.executionId || persisted.executionPlanDigest !== requested.executionPlanDigest) throw new Error("persisted Pier trial identity differs from the capture request");
1627
+ this.assertDockerConnection(persisted);
1628
+ const terminal = parseTerminal(join(controlDirectory, terminalFile));
1629
+ if (!terminal.processExited || !terminal.containersRemoved) throw new Error("Pier result capture requires proven process and container death");
1630
+ if (!this.readResult) throw new Error("Pier result capture has no restart-safe result reader");
1631
+ return await this.readResult({
1632
+ jobsDirectory: persisted.jobsDirectory,
1633
+ jobName: persisted.jobName
1634
+ });
1635
+ }
1636
+ controlDirectory(identity) {
1637
+ return join(this.directory, trialKey(identity));
1638
+ }
1639
+ assertDockerConnection(identity) {
1640
+ if (identity.dockerConnectionId !== this.dockerConnection.id) throw new Error(`Pier trial requires Docker connection ${identity.dockerConnectionId}; configured ${this.dockerConnection.id}`);
1641
+ }
1642
+ validateSpec(spec) {
1643
+ nonEmpty(spec.command, "Pier command");
1644
+ if (!Array.isArray(spec.args) || spec.args.some((arg) => typeof arg !== "string" || arg.includes("\0"))) throw new Error("Pier arguments must be strings without NUL");
1645
+ absolutePath(spec.cwd, "Pier cwd");
1646
+ absolutePath(spec.jobsDirectory, "Pier jobs directory");
1647
+ if (!/^[A-Za-z0-9._-]{1,200}$/.test(spec.jobName)) throw new Error("Pier job name must be filesystem-neutral");
1648
+ validateEnvironment(spec.env, "Pier child environment");
1649
+ }
1650
+ async waitForResult(controlDirectory, spec, deadlineAtMs) {
1651
+ if (!await waitUntil(() => existsSync(join(controlDirectory, terminalFile)), Math.max(0, deadlineAtMs - Date.now()), this.pollIntervalMs)) throw new Error("Pier supervisor emitted no terminal acknowledgement");
1652
+ const terminal = parseTerminal(join(controlDirectory, terminalFile));
1653
+ if (!terminal.processExited || !terminal.containersRemoved) throw new Error("Pier supervisor did not prove process and container death");
1654
+ if (terminal.status !== "completed") throw new Error(terminal.error ? `Pier process failed: ${terminal.error}` : "Pier process stopped");
1655
+ return await spec.readResult();
1656
+ }
1657
+ async forceRecovery(controlDirectory) {
1658
+ const latest = parsePersistedIdentity(join(controlDirectory, identityFile));
1659
+ this.assertDockerConnection(latest);
1660
+ const errors = [];
1661
+ if (latest.pier) try {
1662
+ await signalSessionUntilEmpty(latest.pier, "SIGTERM", 2e3, this.pollIntervalMs);
1663
+ } catch (error) {
1664
+ errors.push(`could not signal Pier session: ${error instanceof Error ? error.message : String(error)}`);
1665
+ }
1666
+ if (latest.supervisor) try {
1667
+ signalProcess(latest.supervisor, "SIGKILL", false);
1668
+ if (!await waitUntil(() => !processMatches(latest.supervisor), 5e3, this.pollIntervalMs)) errors.push(`Pier supervisor ${latest.supervisor.pid} survived SIGKILL`);
1669
+ } catch (error) {
1670
+ errors.push(`could not kill Pier supervisor: ${error instanceof Error ? error.message : String(error)}`);
1671
+ }
1672
+ if (latest.pier) try {
1673
+ if (!await signalSessionUntilEmpty(latest.pier, "SIGKILL", 5e3, this.pollIntervalMs)) errors.push(`Pier process session ${latest.pier.sessionId} survived SIGKILL`);
1674
+ } catch (error) {
1675
+ errors.push(`could not kill Pier session: ${error instanceof Error ? error.message : String(error)}`);
1676
+ }
1677
+ let removedContainers = 0;
1678
+ try {
1679
+ removedContainers = removeTrialContainers(latest, this.dockerConnection.env);
1680
+ } catch (error) {
1681
+ errors.push(`could not remove Pier containers: ${error instanceof Error ? error.message : String(error)}`);
1682
+ }
1683
+ if (errors.length > 0) throw new Error(`Pier recovery incomplete: ${errors.join("; ")}`);
1684
+ writeJsonAtomic(controlDirectory, terminalFile, {
1685
+ schemaVersion: 1,
1686
+ kind: "pier-trial-terminal",
1687
+ status: "stopped",
1688
+ processExited: true,
1689
+ containersRemoved: true,
1690
+ removedContainers,
1691
+ recoveredByPid: process.pid
1692
+ });
1693
+ }
1786
1694
  };
1787
-
1788
- // src/pier-result-grader.ts
1789
- import { spawn as spawn3 } from "child_process";
1790
- import { createHash as createHash3 } from "crypto";
1791
- import { chmod as chmod2, mkdtemp as mkdtemp2, rm as rm2, writeFile as writeFile2 } from "fs/promises";
1792
- import { tmpdir as tmpdir2 } from "os";
1793
- import { join as join4 } from "path";
1794
- var sha2562 = (bytes) => `sha256:${createHash3("sha256").update(bytes).digest("hex")}`;
1695
+ //#endregion
1696
+ //#region src/pier-result-grader.ts
1697
+ /** Execute the exact admitted Pier result parser bytes in a fresh Node process. */
1698
+ const sha256 = (bytes) => `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
1795
1699
  function createPierResultGrader(descriptor) {
1796
- return {
1797
- ...descriptor,
1798
- run: async (input) => {
1799
- input.signal.throwIfAborted();
1800
- const implementation = Uint8Array.from(input.implementation.bytes);
1801
- if (implementation.byteLength !== input.implementation.byteLength) {
1802
- throw new Error("Pier grader implementation length changed before execution");
1803
- }
1804
- const evidence = Uint8Array.from(input.officialResult.bytes);
1805
- const evaluation = await executeGrader(implementation, evidence, input.signal);
1806
- input.signal.throwIfAborted();
1807
- return {
1808
- evaluation,
1809
- evidence,
1810
- binding: {
1811
- implementationDigest: sha2562(implementation),
1812
- taskOutcomeDigest: input.outcome.evidence.digest,
1813
- outputDigest: sha2562(evidence)
1814
- }
1815
- };
1816
- }
1817
- };
1700
+ return {
1701
+ ...descriptor,
1702
+ run: async (input) => {
1703
+ input.signal.throwIfAborted();
1704
+ const implementation = Uint8Array.from(input.implementation.bytes);
1705
+ if (implementation.byteLength !== input.implementation.byteLength) throw new Error("Pier grader implementation length changed before execution");
1706
+ const evidence = Uint8Array.from(input.officialResult.bytes);
1707
+ const evaluation = await executeGrader(implementation, evidence, input.signal);
1708
+ input.signal.throwIfAborted();
1709
+ return {
1710
+ evaluation,
1711
+ evidence,
1712
+ binding: {
1713
+ implementationDigest: sha256(implementation),
1714
+ taskOutcomeDigest: input.outcome.evidence.digest,
1715
+ outputDigest: sha256(evidence)
1716
+ }
1717
+ };
1718
+ }
1719
+ };
1818
1720
  }
1819
1721
  async function executeGrader(implementation, resultBytes, signal) {
1820
- const directory = await mkdtemp2(join4(tmpdir2(), "pier-result-grader-"));
1821
- const script = join4(directory, "grader.mjs");
1822
- try {
1823
- await writeFile2(script, implementation, { flag: "wx", mode: 384 });
1824
- await chmod2(script, 320);
1825
- const stdout = await runNode(script, resultBytes, signal);
1826
- const parsed = JSON.parse(stdout.toString("utf8"));
1827
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1828
- throw new Error("Pier result grader returned a non-object evaluation");
1829
- }
1830
- return parsed;
1831
- } finally {
1832
- await rm2(directory, { recursive: true, force: true });
1833
- }
1722
+ const directory = await mkdtemp(join(tmpdir(), "pier-result-grader-"));
1723
+ const script = join(directory, "grader.mjs");
1724
+ try {
1725
+ await writeFile(script, implementation, {
1726
+ flag: "wx",
1727
+ mode: 384
1728
+ });
1729
+ await chmod(script, 320);
1730
+ const stdout = await runNode(script, resultBytes, signal);
1731
+ const parsed = JSON.parse(stdout.toString("utf8"));
1732
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Pier result grader returned a non-object evaluation");
1733
+ return parsed;
1734
+ } finally {
1735
+ await rm(directory, {
1736
+ recursive: true,
1737
+ force: true
1738
+ });
1739
+ }
1834
1740
  }
1835
1741
  async function runNode(script, input, signal) {
1836
- signal.throwIfAborted();
1837
- return await new Promise((resolveResult, reject) => {
1838
- const child = spawn3(process.execPath, ["--no-warnings", script], {
1839
- env: {
1840
- PATH: process.env.PATH,
1841
- HOME: process.env.HOME,
1842
- LANG: "C",
1843
- LC_ALL: "C"
1844
- },
1845
- stdio: ["pipe", "pipe", "pipe"],
1846
- signal
1847
- });
1848
- const stdout = [];
1849
- const stderr = [];
1850
- let bytes = 0;
1851
- const collect = (target) => (chunk) => {
1852
- bytes += chunk.byteLength;
1853
- if (bytes > 1024 * 1024) {
1854
- child.kill("SIGKILL");
1855
- reject(new Error("Pier result grader exceeded its output limit"));
1856
- return;
1857
- }
1858
- target.push(Buffer.from(chunk));
1859
- };
1860
- child.stdout.on("data", collect(stdout));
1861
- child.stderr.on("data", collect(stderr));
1862
- child.once("error", reject);
1863
- child.once("close", (code, childSignal) => {
1864
- if (code !== 0) {
1865
- reject(
1866
- new Error(
1867
- `Pier result grader failed (${childSignal ?? code}): ${Buffer.concat(stderr).toString("utf8").trim()}`
1868
- )
1869
- );
1870
- return;
1871
- }
1872
- resolveResult(Buffer.concat(stdout));
1873
- });
1874
- child.stdin.end(input);
1875
- });
1876
- }
1877
- export {
1878
- ADAPTERS,
1879
- FINAL_ANSWER_SENTINEL,
1880
- FilePierCandidateTrialController,
1881
- StagedJudgeError,
1882
- answerScoreToBenchScore,
1883
- contextBlock,
1884
- contextsFrom,
1885
- createCragAdapter,
1886
- createNoMiraclAdapter,
1887
- createOpenRagBenchAdapter,
1888
- createPierCandidateRecoveryExecutor,
1889
- createPierResultGrader,
1890
- createRagBenchAdapter,
1891
- createSweBenchAdapter,
1892
- createT2RagBenchAdapter,
1893
- executePreparedPierCandidate,
1894
- normalizeAnswer,
1895
- parseCitations,
1896
- parseFinalAnswer,
1897
- printBenchmarksReport,
1898
- ragAnswerOutput,
1899
- resolveAdapter,
1900
- runBenchmarks,
1901
- runStagedJudge,
1902
- scoreAnswerArtifact,
1903
- scoreSweReport,
1904
- sweEvaluationArgv,
1905
- swePatchOutput,
1906
- tokenF1
1907
- };
1742
+ signal.throwIfAborted();
1743
+ return await new Promise((resolveResult, reject) => {
1744
+ const child = spawn(process.execPath, ["--no-warnings", script], {
1745
+ env: {
1746
+ PATH: process.env.PATH,
1747
+ HOME: process.env.HOME,
1748
+ LANG: "C",
1749
+ LC_ALL: "C"
1750
+ },
1751
+ stdio: [
1752
+ "pipe",
1753
+ "pipe",
1754
+ "pipe"
1755
+ ],
1756
+ signal
1757
+ });
1758
+ const stdout = [];
1759
+ const stderr = [];
1760
+ let bytes = 0;
1761
+ const collect = (target) => (chunk) => {
1762
+ bytes += chunk.byteLength;
1763
+ if (bytes > 1024 * 1024) {
1764
+ child.kill("SIGKILL");
1765
+ reject(/* @__PURE__ */ new Error("Pier result grader exceeded its output limit"));
1766
+ return;
1767
+ }
1768
+ target.push(Buffer.from(chunk));
1769
+ };
1770
+ child.stdout.on("data", collect(stdout));
1771
+ child.stderr.on("data", collect(stderr));
1772
+ child.once("error", reject);
1773
+ child.once("close", (code, childSignal) => {
1774
+ if (code !== 0) {
1775
+ reject(/* @__PURE__ */ new Error(`Pier result grader failed (${childSignal ?? code}): ${Buffer.concat(stderr).toString("utf8").trim()}`));
1776
+ return;
1777
+ }
1778
+ resolveResult(Buffer.concat(stdout));
1779
+ });
1780
+ child.stdin.end(input);
1781
+ });
1782
+ }
1783
+ //#endregion
1784
+ export { ADAPTERS, FINAL_ANSWER_SENTINEL, FilePierCandidateTrialController, StagedJudgeError, answerScoreToBenchScore, contextBlock, contextsFrom, createCragAdapter, createNoMiraclAdapter, createOpenRagBenchAdapter, createPierCandidateRecoveryExecutor, createPierResultGrader, createRagBenchAdapter, createSweBenchAdapter, createT2RagBenchAdapter, executePreparedPierCandidate, normalizeAnswer, parseCitations, parseFinalAnswer, printBenchmarksReport, ragAnswerOutput, resolveAdapter, runBenchmarks, runStagedJudge, scoreAnswerArtifact, scoreSweReport, sweEvaluationArgv, swePatchOutput, tokenF1 };
1785
+
1908
1786
  //# sourceMappingURL=index.js.map