reeval 0.0.1

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 (213) hide show
  1. package/LICENSE +202 -0
  2. package/THIRD_PARTY_NOTICES.md +2160 -0
  3. package/assets/package.json +9 -0
  4. package/assets/pnpm-lock.yaml +1336 -0
  5. package/dist/agent-DVHkx8aT.js +532 -0
  6. package/dist/agent-DVHkx8aT.js.map +1 -0
  7. package/dist/analysis-CxSCt1YY.js +16 -0
  8. package/dist/analysis-CxSCt1YY.js.map +1 -0
  9. package/dist/assemble-B-nmz8oH.js +695 -0
  10. package/dist/assemble-B-nmz8oH.js.map +1 -0
  11. package/dist/assemble-CmRVDZ2Q.js +2 -0
  12. package/dist/attempt-BdRQ9jKX.js +176 -0
  13. package/dist/attempt-BdRQ9jKX.js.map +1 -0
  14. package/dist/bridge/index.mjs +1911 -0
  15. package/dist/bridge/package.json +13 -0
  16. package/dist/bridge/pnpm-lock.yaml +1041 -0
  17. package/dist/bridge/pnpm-workspace.yaml +2 -0
  18. package/dist/budget-DBUrH4jX.js +8 -0
  19. package/dist/budget-DBUrH4jX.js.map +1 -0
  20. package/dist/cli--CQ3Iy9q.js +1865 -0
  21. package/dist/cli--CQ3Iy9q.js.map +1 -0
  22. package/dist/cli-spec-DKAu09Gi.js +478 -0
  23. package/dist/cli-spec-DKAu09Gi.js.map +1 -0
  24. package/dist/cli.d.ts +1 -0
  25. package/dist/cli.js +8 -0
  26. package/dist/cli.js.map +1 -0
  27. package/dist/colors-QV9dFVhN.js +34 -0
  28. package/dist/colors-QV9dFVhN.js.map +1 -0
  29. package/dist/command-BSJzHMtP.js +280 -0
  30. package/dist/command-BSJzHMtP.js.map +1 -0
  31. package/dist/command-CEm8PZ1N.js +1043 -0
  32. package/dist/command-CEm8PZ1N.js.map +1 -0
  33. package/dist/command-D-jnQWVG.js +673 -0
  34. package/dist/command-D-jnQWVG.js.map +1 -0
  35. package/dist/command-DQAvbG7d.js +432 -0
  36. package/dist/command-DQAvbG7d.js.map +1 -0
  37. package/dist/commands-A4udfyKZ.js +2 -0
  38. package/dist/commands-CF8mOfiu.js +207 -0
  39. package/dist/commands-CF8mOfiu.js.map +1 -0
  40. package/dist/config-Cuj0kVYj.js +415 -0
  41. package/dist/config-Cuj0kVYj.js.map +1 -0
  42. package/dist/context-loi9OCZv.js +106 -0
  43. package/dist/context-loi9OCZv.js.map +1 -0
  44. package/dist/core-tLN-z1Be.js +96 -0
  45. package/dist/core-tLN-z1Be.js.map +1 -0
  46. package/dist/db-Cjj2ZsfQ.js +2 -0
  47. package/dist/db-DxDzMhtB.js +827 -0
  48. package/dist/db-DxDzMhtB.js.map +1 -0
  49. package/dist/debug-D24NOQH1.js +89 -0
  50. package/dist/debug-D24NOQH1.js.map +1 -0
  51. package/dist/debug-SEmW4w8f.js +2 -0
  52. package/dist/dist-BZXYHN2X.js +1673 -0
  53. package/dist/dist-BZXYHN2X.js.map +1 -0
  54. package/dist/docker-DQDZWXF9.js +20 -0
  55. package/dist/docker-DQDZWXF9.js.map +1 -0
  56. package/dist/docker-DnWAVdEm.js +2 -0
  57. package/dist/docker-oSbCExHL.js +153 -0
  58. package/dist/docker-oSbCExHL.js.map +1 -0
  59. package/dist/doctor-CxlH1pDM.js +525 -0
  60. package/dist/doctor-CxlH1pDM.js.map +1 -0
  61. package/dist/egress-73SQfKdx.js +619 -0
  62. package/dist/egress-73SQfKdx.js.map +1 -0
  63. package/dist/env-BQqj93gu.js +21 -0
  64. package/dist/env-BQqj93gu.js.map +1 -0
  65. package/dist/environment-Mz7wvrtD.js +552 -0
  66. package/dist/environment-Mz7wvrtD.js.map +1 -0
  67. package/dist/errors-C9w_ud7g.js +160 -0
  68. package/dist/errors-C9w_ud7g.js.map +1 -0
  69. package/dist/errors-CSxfJ4qu.js +11 -0
  70. package/dist/errors-CSxfJ4qu.js.map +1 -0
  71. package/dist/files--6CEUXm7.js +2 -0
  72. package/dist/files-BLlrhU4-.js +62 -0
  73. package/dist/files-BLlrhU4-.js.map +1 -0
  74. package/dist/files-Bd25OAQH.js +2 -0
  75. package/dist/files-BvHCFWgD.js +2 -0
  76. package/dist/files-D2-qn_Dl.js +275 -0
  77. package/dist/files-D2-qn_Dl.js.map +1 -0
  78. package/dist/files-bF2fPuT9.js +99 -0
  79. package/dist/files-bF2fPuT9.js.map +1 -0
  80. package/dist/git-D6hMeieS.js +44 -0
  81. package/dist/git-D6hMeieS.js.map +1 -0
  82. package/dist/harness-ls5aX1UO.js +131 -0
  83. package/dist/harness-ls5aX1UO.js.map +1 -0
  84. package/dist/harness.d.ts +74 -0
  85. package/dist/harness.js +2 -0
  86. package/dist/header-DzFDoMCI.js +78 -0
  87. package/dist/header-DzFDoMCI.js.map +1 -0
  88. package/dist/ids-Bp6ut7ZD.js +49 -0
  89. package/dist/ids-Bp6ut7ZD.js.map +1 -0
  90. package/dist/images-Cc1-coFc.js +286 -0
  91. package/dist/images-Cc1-coFc.js.map +1 -0
  92. package/dist/index.d.ts +97 -0
  93. package/dist/index.js +1959 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/init-CFljS8BJ.js +344 -0
  96. package/dist/init-CFljS8BJ.js.map +1 -0
  97. package/dist/init-z2ZPn9Tj.js +2 -0
  98. package/dist/invocations-rXktgSAn.js +157 -0
  99. package/dist/invocations-rXktgSAn.js.map +1 -0
  100. package/dist/jiti-BOLXpKyG.js +46 -0
  101. package/dist/jiti-BOLXpKyG.js.map +1 -0
  102. package/dist/judge-D8Uf4dA9.js +146 -0
  103. package/dist/judge-D8Uf4dA9.js.map +1 -0
  104. package/dist/judge.d.ts +21 -0
  105. package/dist/judge.js +19 -0
  106. package/dist/judge.js.map +1 -0
  107. package/dist/live-Bs3LReMV.js +98 -0
  108. package/dist/live-Bs3LReMV.js.map +1 -0
  109. package/dist/matchers-DyLvm7EG.js +374 -0
  110. package/dist/matchers-DyLvm7EG.js.map +1 -0
  111. package/dist/model-BdO79gWz.js +44 -0
  112. package/dist/model-BdO79gWz.js.map +1 -0
  113. package/dist/model-CDL98xue.js +725 -0
  114. package/dist/model-CDL98xue.js.map +1 -0
  115. package/dist/model-v-0_Ik7S.js +2 -0
  116. package/dist/node-kG64XxdE.js +506 -0
  117. package/dist/node-kG64XxdE.js.map +1 -0
  118. package/dist/opencode-providers-DZuD_oYt.js +3149 -0
  119. package/dist/opencode-providers-DZuD_oYt.js.map +1 -0
  120. package/dist/options-CAQhu--C.js +452 -0
  121. package/dist/options-CAQhu--C.js.map +1 -0
  122. package/dist/patch-BkKje4cp.js +69 -0
  123. package/dist/patch-BkKje4cp.js.map +1 -0
  124. package/dist/paths-CBvm6pap.js +72 -0
  125. package/dist/paths-CBvm6pap.js.map +1 -0
  126. package/dist/plan-gwk6dJ8c.js +237 -0
  127. package/dist/plan-gwk6dJ8c.js.map +1 -0
  128. package/dist/preflight-BtubXKQ8.js +2 -0
  129. package/dist/preflight-BypZMNBG.js +185 -0
  130. package/dist/preflight-BypZMNBG.js.map +1 -0
  131. package/dist/prepare-Cwv0bHs_.js +3 -0
  132. package/dist/prepare-D6Uzcubb.js +213 -0
  133. package/dist/prepare-D6Uzcubb.js.map +1 -0
  134. package/dist/provenance-Dfb8YY5Y.js +129 -0
  135. package/dist/provenance-Dfb8YY5Y.js.map +1 -0
  136. package/dist/provider-BDbNOQhQ.js +2 -0
  137. package/dist/provider-CmAYTQQs.js +14 -0
  138. package/dist/provider-CmAYTQQs.js.map +1 -0
  139. package/dist/providers-2p3ylMHI.js +107 -0
  140. package/dist/providers-2p3ylMHI.js.map +1 -0
  141. package/dist/providers-BUOxitcT.js +2 -0
  142. package/dist/query-CFATHZmV.js +91 -0
  143. package/dist/query-CFATHZmV.js.map +1 -0
  144. package/dist/query-DR4m6CXf.js +2 -0
  145. package/dist/record-DQ8wzyco.js +99 -0
  146. package/dist/record-DQ8wzyco.js.map +1 -0
  147. package/dist/redact-C4fL_bZP.js +167 -0
  148. package/dist/redact-C4fL_bZP.js.map +1 -0
  149. package/dist/results-CeXb8Wde.js +37 -0
  150. package/dist/results-CeXb8Wde.js.map +1 -0
  151. package/dist/root-CFDjpWuc.js +110 -0
  152. package/dist/root-CFDjpWuc.js.map +1 -0
  153. package/dist/sandbox-By2gkdkC.js +133 -0
  154. package/dist/sandbox-By2gkdkC.js.map +1 -0
  155. package/dist/sandbox.d.ts +41 -0
  156. package/dist/sandbox.js +2 -0
  157. package/dist/server-Bi61uiY0.js +350 -0
  158. package/dist/server-Bi61uiY0.js.map +1 -0
  159. package/dist/server-CsSBL8NC.js +2 -0
  160. package/dist/source-BXzrmof5.js +2 -0
  161. package/dist/source-Bsfa3LZH.js +40 -0
  162. package/dist/source-Bsfa3LZH.js.map +1 -0
  163. package/dist/src-55j1zNHH.js +3 -0
  164. package/dist/src-AEwxd1UP.js +602 -0
  165. package/dist/src-AEwxd1UP.js.map +1 -0
  166. package/dist/src-BDwSuyIo.js +3 -0
  167. package/dist/src-BQyMiWri.js +490 -0
  168. package/dist/src-BQyMiWri.js.map +1 -0
  169. package/dist/src-C-LHyTSV.js +3 -0
  170. package/dist/src-Cmp-7YrD.js +1136 -0
  171. package/dist/src-Cmp-7YrD.js.map +1 -0
  172. package/dist/src-D0RdqpNb.js +252 -0
  173. package/dist/src-D0RdqpNb.js.map +1 -0
  174. package/dist/src-D8KYzxBX.js +793 -0
  175. package/dist/src-D8KYzxBX.js.map +1 -0
  176. package/dist/src-DEgff9Z7.js +4 -0
  177. package/dist/src-DXx_29pq.js +567 -0
  178. package/dist/src-DXx_29pq.js.map +1 -0
  179. package/dist/src-DwwiVLqv.js +2937 -0
  180. package/dist/src-DwwiVLqv.js.map +1 -0
  181. package/dist/src-N7E_mfcZ.js +2 -0
  182. package/dist/src-W9g-y5X5.js +3 -0
  183. package/dist/src-daS0wKH2.js +3 -0
  184. package/dist/state-Cyw3QTsN.js +2 -0
  185. package/dist/state-DZ7eqb1F.js +167 -0
  186. package/dist/state-DZ7eqb1F.js.map +1 -0
  187. package/dist/stats-BNAHlNMD.js +114 -0
  188. package/dist/stats-BNAHlNMD.js.map +1 -0
  189. package/dist/studio-app/assets/index-D6XyfFSH.js +58 -0
  190. package/dist/studio-app/assets/index-xwzWdcE_.css +2 -0
  191. package/dist/studio-app/favicon.svg +1 -0
  192. package/dist/studio-app/index.html +15 -0
  193. package/dist/summary-D7Ttky0z.js +166 -0
  194. package/dist/summary-D7Ttky0z.js.map +1 -0
  195. package/dist/text-gEcKjAyp.js +29 -0
  196. package/dist/text-gEcKjAyp.js.map +1 -0
  197. package/dist/types-C5vI3g1C.js +9 -0
  198. package/dist/types-C5vI3g1C.js.map +1 -0
  199. package/dist/types-DLec-6Cx.d.ts +1535 -0
  200. package/dist/usage-DsYUqIUM.js +24 -0
  201. package/dist/usage-DsYUqIUM.js.map +1 -0
  202. package/dist/view/index.d.ts +645 -0
  203. package/dist/view/index.js +8 -0
  204. package/dist/views-BrHOv6wg.js +220 -0
  205. package/dist/views-BrHOv6wg.js.map +1 -0
  206. package/dist/views-CwCWLWqo.js +3 -0
  207. package/dist/worker-entry-DnYkANPG.js +506 -0
  208. package/dist/worker-entry-DnYkANPG.js.map +1 -0
  209. package/dist/workspace-C2S0Z6gl.js +327 -0
  210. package/dist/workspace-C2S0Z6gl.js.map +1 -0
  211. package/dist/workspace-CyoXG6CY.js +2 -0
  212. package/docker/Dockerfile +19 -0
  213. package/package.json +79 -0
@@ -0,0 +1,1865 @@
1
+ import { t as UsageError } from "./errors-CSxfJ4qu.js";
2
+ import { x as parseDuration, y as workspaceLabel } from "./environment-Mz7wvrtD.js";
3
+ import { n as loadEnvFiles, t as inCI } from "./env-BQqj93gu.js";
4
+ import { a as registerSecret, i as redactSecrets } from "./redact-C4fL_bZP.js";
5
+ import { o as PACKAGE_VERSION } from "./root-CFDjpWuc.js";
6
+ import { a as fromCwd, i as evalSlug, n as attemptKey, o as newRunId } from "./ids-Bp6ut7ZD.js";
7
+ import { I as LABEL, X as imageInfo } from "./src-DwwiVLqv.js";
8
+ import { s as harnessId, u as sandboxId } from "./options-CAQhu--C.js";
9
+ import { c as hostCapacity, d as missingCredentials, f as resolveConcurrency, i as configPathFor, l as loadConfig, p as selectProfile, r as configLabel, s as findConfig, u as malformedCredentials } from "./config-Cuj0kVYj.js";
10
+ import { s as debugTime } from "./debug-D24NOQH1.js";
11
+ import { i as sandboxWarningLine, n as clip, r as formatDuration } from "./text-gEcKjAyp.js";
12
+ import { t as PrepareError } from "./provider-CmAYTQQs.js";
13
+ import { N as effortLabel, O as cellContrast, b as runOutcome, h as hasUngatedCells, p as formatUsage, v as isUngatedMiss } from "./model-CDL98xue.js";
14
+ import { n as LIVE_FILE, o as liveCounts } from "./live-Bs3LReMV.js";
15
+ import { n as FLAGS, o as helpText, r as GLOBAL, t as COMMANDS } from "./cli-spec-DKAu09Gi.js";
16
+ import { i as liftLabel, r as gateLabel } from "./summary-D7Ttky0z.js";
17
+ import { n as gitInfo, r as runnerInfo, t as author } from "./provenance-Dfb8YY5Y.js";
18
+ import { t as budgetMarker } from "./budget-DBUrH4jX.js";
19
+ import { n as prepareAll, o as writePrepared, r as prepareTargets, t as RunStopError } from "./preflight-BypZMNBG.js";
20
+ import { r as imageHarness } from "./prepare-D6Uzcubb.js";
21
+ import { n as createProviders } from "./providers-2p3ylMHI.js";
22
+ import { a as redactArgv, r as commandOf, t as logInvocation } from "./invocations-rXktgSAn.js";
23
+ import { n as stripAnsi, t as colors } from "./colors-QV9dFVhN.js";
24
+ import { existsSync, mkdirSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
25
+ import { hostname } from "node:os";
26
+ import { dirname, join } from "node:path";
27
+ import { appendFile, readFile, rm } from "node:fs/promises";
28
+ import { inspect } from "node:util";
29
+ import { detectAgent } from "std-env";
30
+ import { Command, Option } from "commander";
31
+ //#region src/cli-parse.ts
32
+ const OPTIONS = Object.fromEntries(Object.entries(FLAGS).map(([name, spec]) => {
33
+ const short = "short" in spec ? `-${spec.short}, ` : "";
34
+ const value = spec.type === "string" ? " <value>" : "";
35
+ return [name, () => {
36
+ const option = new Option(`${short}--${name}${value}`);
37
+ if ("multiple" in spec && spec.multiple) option.argParser((v, prev = []) => [...prev, spec.type === "string" ? v : true]);
38
+ return option;
39
+ }];
40
+ }));
41
+ /**
42
+ * Parses `argv` against the CLI spec with commander. A flag belongs to the commands that list it
43
+ * (and `--config`, `--help` and `--version` to all of them); a flag of another command, a
44
+ * missing value or too many arguments is a UsageError. `--help` and `--version` skip the
45
+ * argument count, so the dispatcher can print them.
46
+ */
47
+ function parseCli(argv) {
48
+ let parsed;
49
+ const program = configure(new Command("reeval"), "reeval", GLOBAL);
50
+ program.argument("[args...]").action(() => {
51
+ parsed = {
52
+ positionals: program.args,
53
+ values: valuesOf(program, GLOBAL)
54
+ };
55
+ });
56
+ for (const [name, spec] of Object.entries(COMMANDS)) {
57
+ const command = configure(program.command(name), `reeval ${name}`, spec.flags);
58
+ command.argument("[args...]").action(() => {
59
+ const max = spec.sub && !spec.help ? Number.POSITIVE_INFINITY : spec.positionals;
60
+ parsed = done(program, command, [name], spec.flags, max);
61
+ });
62
+ for (const [subName, sub] of Object.entries(spec.sub ?? {})) {
63
+ const subcommand = configure(command.command(subName), `reeval ${name} ${subName}`, sub.flags);
64
+ subcommand.argument("[args...]").action(() => {
65
+ const [stray] = Object.keys(valuesOf(command, spec.flags));
66
+ if (stray) throw new UsageError(`--${stray} isn't an option of reeval ${name} ${subName}`);
67
+ parsed = done(program, subcommand, [name, subName], sub.flags, sub.positionals);
68
+ });
69
+ }
70
+ }
71
+ program.parse(argv, { from: "user" });
72
+ if (!parsed) throw new UsageError("Run reeval --help.");
73
+ return parsed;
74
+ }
75
+ function configure(command, name, flags) {
76
+ command.helpOption(false).helpCommand(false).allowExcessArguments().configureOutput({
77
+ writeOut: () => {},
78
+ writeErr: () => {},
79
+ outputError: () => {}
80
+ }).exitOverride((error) => {
81
+ throw usageError(error, name);
82
+ });
83
+ for (const flag of flags) command.addOption(OPTIONS[flag]());
84
+ return command;
85
+ }
86
+ function done(program, command, path, flags, max) {
87
+ const values = {
88
+ ...valuesOf(program, GLOBAL),
89
+ ...valuesOf(command, flags)
90
+ };
91
+ const args = command.args;
92
+ if (!values.help && !values.version && args.length > max) {
93
+ const name = `reeval ${path.join(" ")}`;
94
+ throw new UsageError(max === 0 ? `${name} takes no arguments (got ${args.join(" ")})` : `${name} takes at most ${max} argument${max === 1 ? "" : "s"} (got ${args.join(" ")})`);
95
+ }
96
+ return {
97
+ positionals: [...path, ...args],
98
+ values
99
+ };
100
+ }
101
+ /** The flags given, under their spec names; a `--no-*` flag is true when given. */
102
+ function valuesOf(command, flags) {
103
+ const opts = command.opts();
104
+ const values = {};
105
+ for (const flag of flags) {
106
+ const option = command.options.find((o) => o.long === `--${flag}`);
107
+ const value = option && opts[option.attributeName()];
108
+ if (option?.negate) {
109
+ if (value === false) values[flag] = true;
110
+ } else if (value !== void 0) values[flag] = value;
111
+ }
112
+ return values;
113
+ }
114
+ /** Commander's error, in reeval's words, naming the command whose flag it is. */
115
+ function usageError(error, command) {
116
+ const [first = "", ...rest] = error.message.replace(/^error: /, "").split("\n");
117
+ if (error.code === "commander.unknownOption") {
118
+ const arg = /'([^']+)'/.exec(first)?.[1] ?? "";
119
+ const [flag = "", value] = arg.replace(/^--?/, "").split("=");
120
+ const known = arg.startsWith("--") ? FLAGS[flag] : Object.entries(FLAGS).find(([, s]) => "short" in s && s.short === flag)?.[1];
121
+ const long = arg.startsWith("--") ? flag : Object.entries(FLAGS).find(([, s]) => "short" in s && s.short === flag)?.[0];
122
+ if (known && value !== void 0 && known.type === "boolean") return new UsageError(`Option '--${flag}' does not take an argument`);
123
+ if (known) return new UsageError(`--${long} isn't an option of ${command}. Run reeval --help.`);
124
+ const hint = rest.join(" ").replace(/^\(|\)$/g, "");
125
+ return new UsageError(`Unknown option '${arg}'. ${hint ? `${hint} ` : ""}Run reeval --help.`);
126
+ }
127
+ return new UsageError(first.charAt(0).toUpperCase() + first.slice(1));
128
+ }
129
+ function finished(result) {
130
+ const outcome = result.meta?.outcome;
131
+ if (result.state === "skipped") return outcome === "fail" || outcome === "infra_error" ? outcome : "skipped";
132
+ if (outcome === "infra_error") return "infra_error";
133
+ return result.state === "passed" ? "pass" : "fail";
134
+ }
135
+ /**
136
+ * Renames `from` over `to`. Windows refuses while a reader (`reeval studio`) has `to` open, for a
137
+ * moment: try again briefly rather than drop the update.
138
+ */
139
+ function replace(from, to, rename = renameSync) {
140
+ for (let attempt = 1;; attempt++) try {
141
+ rename(from, to);
142
+ return;
143
+ } catch (error) {
144
+ const code = error.code;
145
+ if (attempt >= 20 || !(code === "EPERM" || code === "EACCES" || code === "EBUSY")) throw error;
146
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, attempt);
147
+ }
148
+ }
149
+ /** Writes the file at once, then on every change and heartbeat. Write errors never fail the run. */
150
+ function writeLiveProgress(options) {
151
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
152
+ const file = join(options.dir, LIVE_FILE);
153
+ const tmp = `${file}.${process.pid}.tmp`;
154
+ const attempts = new Map(options.planned.map((p) => [p.key, {
155
+ key: p.key,
156
+ evalId: p.id,
157
+ name: p.attempt.name,
158
+ file: p.file,
159
+ cell: p.attempt.cell.key,
160
+ n: p.attempt.n,
161
+ status: p.attempt.plan.skip ? "skipped" : "queued",
162
+ startedAt: null,
163
+ durationMs: null
164
+ }]));
165
+ let phase = "running";
166
+ let stopped = false;
167
+ const read = () => {
168
+ const list = [...attempts.values()];
169
+ return {
170
+ schemaVersion: 1,
171
+ runId: options.runId,
172
+ startedAt: options.startedAt.toISOString(),
173
+ pid: process.pid,
174
+ host: hostname(),
175
+ heartbeatAt: now().toISOString(),
176
+ phase,
177
+ planned: list.length,
178
+ counts: liveCounts(list),
179
+ attempts: list
180
+ };
181
+ };
182
+ const write = () => {
183
+ if (stopped) return;
184
+ try {
185
+ writeFileSync(tmp, JSON.stringify(read()));
186
+ replace(tmp, file);
187
+ } catch {}
188
+ };
189
+ try {
190
+ mkdirSync(options.dir, { recursive: true });
191
+ } catch {}
192
+ write();
193
+ const heartbeat = setInterval(write, options.heartbeatMs ?? 2e3);
194
+ heartbeat.unref();
195
+ const stop = () => {
196
+ stopped = true;
197
+ clearInterval(heartbeat);
198
+ };
199
+ return {
200
+ start(key, at) {
201
+ const a = attempts.get(key);
202
+ if (!a) return;
203
+ a.status = "running";
204
+ a.startedAt = new Date(at).toISOString();
205
+ write();
206
+ },
207
+ end(result) {
208
+ const a = attempts.get(result.key);
209
+ if (!a) return;
210
+ a.status = finished(result);
211
+ a.durationMs = result.durationMs;
212
+ write();
213
+ },
214
+ phase(next) {
215
+ phase = next;
216
+ write();
217
+ },
218
+ stop,
219
+ remove() {
220
+ stop();
221
+ rmSync(file, { force: true });
222
+ rmSync(tmp, { force: true });
223
+ },
224
+ read
225
+ };
226
+ }
227
+ //#endregion
228
+ //#region src/preflight.ts
229
+ /** How the terminal names a sandbox to prepare: its cell id, `+ install`, and a harness other than Cursor. */
230
+ function prepareLabel({ sandbox, harness }) {
231
+ if (sandbox.kind !== "docker") return sandboxId(sandbox);
232
+ const baked = imageHarness(harness);
233
+ const install = sandbox.install?.length ? " + install" : "";
234
+ return `${sandboxId(sandbox)}${install}${baked === "cursor" ? "" : ` · ${baked}`}`;
235
+ }
236
+ /** A run that stopped in preflight on an infra failure: exit 3. */
237
+ var PreflightInfraError = class extends Error {
238
+ name = "PreflightInfraError";
239
+ };
240
+ /**
241
+ * Before any eval starts: checks each sandbox provider, then prepares every distinct sandbox the
242
+ * planned attempts need (building Docker images), in parallel, and writes the results for the
243
+ * run's workers. A config-level sandbox that fails stops the run (exit 2 for a mistake in the
244
+ * config, exit 3 otherwise); any other failure fails only the attempts that need it.
245
+ */
246
+ async function preflight(uses, options) {
247
+ const providers = options.providers ?? createProviders();
248
+ const targets = prepareTargets(uses, providers, prepareLabel);
249
+ if (!targets.some((t) => t.kind !== "fake")) return { images: [] };
250
+ let outcomes;
251
+ try {
252
+ outcomes = await prepareAll(targets, {
253
+ providers,
254
+ root: options.root,
255
+ ...options.signal ? { signal: options.signal } : {},
256
+ ...options.delays ? { delays: options.delays } : {},
257
+ ...options.onEvent ? { onEvent: options.onEvent } : {},
258
+ log: (line) => process.env.REEVAL_DEBUG && console.error(line)
259
+ });
260
+ } catch (error) {
261
+ if (error instanceof PrepareError) throw error.failure.usage ? new UsageError(error.message) : new PreflightInfraError(error.message);
262
+ if (error instanceof RunStopError) throw error.outcome.failure.usage ? new UsageError(error.message) : new PreflightInfraError(error.message);
263
+ throw error;
264
+ }
265
+ writePrepared(options.root, options.runId, outcomes);
266
+ const images = [];
267
+ for (const o of outcomes) {
268
+ if (!o.ok || o.target.kind !== "docker") continue;
269
+ const { ref, id, agentCli } = o.handle;
270
+ if (images.some((i) => i.id === id)) continue;
271
+ const info = await imageInfo(ref);
272
+ images.push({
273
+ ref,
274
+ id,
275
+ repoDigests: info?.repoDigests ?? [],
276
+ agentCli: agentCli ?? info?.labels[LABEL.agentVersion] ?? null
277
+ });
278
+ }
279
+ return { images };
280
+ }
281
+ //#endregion
282
+ //#region src/ui/format.ts
283
+ /** Most detail lines a `-vv` payload prints under its line. */
284
+ const DEBUG_DETAIL_LINES = 40;
285
+ /**
286
+ * A debug line as the terminal shows it, `12:50:01 image cache hit …` (with milliseconds under
287
+ * `-vv`), then its detail indented under it.
288
+ */
289
+ function debugLines(c, line, ms) {
290
+ const phase = line.phase.padEnd(9);
291
+ const head = `${c.dim(debugTime(line.at, ms))} ${c.dim(phase)} ${line.level === 2 ? c.dim(line.text) : line.text}`;
292
+ const detail = (line.detail ?? "").split("\n").filter((l) => l.trim());
293
+ const shown = detail.slice(0, DEBUG_DETAIL_LINES).map((l) => c.dim(` ${l}`));
294
+ const more = detail.length - shown.length;
295
+ return [
296
+ head,
297
+ ...shown,
298
+ ...more > 0 ? [c.dim(` … ${more} more lines in trace.ndjson`)] : []
299
+ ];
300
+ }
301
+ /** Offset into an attempt, `+1:05`. */
302
+ function offset(ms) {
303
+ const s = Math.max(0, Math.floor(ms / 1e3));
304
+ return `+${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
305
+ }
306
+ function outcomeOf(e) {
307
+ if (e.state === "skipped") return "skipped";
308
+ if (e.meta?.errorKind === "aborted") return "cancelled";
309
+ return e.meta?.outcome ?? (e.state === "passed" ? "pass" : "fail");
310
+ }
311
+ function attemptOutcome(a) {
312
+ return a.error?.kind === "aborted" ? "cancelled" : a.outcome;
313
+ }
314
+ /** What tells an eval's attempts apart: the axes that vary (`cursor:composer-2.5 · with-skill`), else the harness. */
315
+ function attemptAxes(a, fallback) {
316
+ return a?.label.replace(/\s*#\d+$/, "") || a?.harness.kind || fallback;
317
+ }
318
+ function mark(c, outcome) {
319
+ return {
320
+ pass: c.green("✓"),
321
+ fail: c.red("✗"),
322
+ infra_error: c.yellow("!"),
323
+ skipped: c.dim("↓"),
324
+ cancelled: c.yellow("✗")
325
+ }[outcome];
326
+ }
327
+ /** An expected miss's mark: neutral, since it doesn't fail the run. */
328
+ const MISS_MARK = (c) => c.dim("○");
329
+ const plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
330
+ /** A command on one line: the first line, cut to 80 characters at a word boundary. */
331
+ function oneLine(text, max = 80) {
332
+ const [first = ""] = text.trim().split("\n");
333
+ const cut = clip(first, max);
334
+ return cut === first && text.trim().includes("\n") ? `${first}…` : cut;
335
+ }
336
+ /** One line per worker progress event, without prefix or offset. */
337
+ function progressLine(c, p) {
338
+ switch (p.kind) {
339
+ case "sandbox": return c.dim("sandbox starting");
340
+ case "sandbox-ready": return `sandbox ready ${c.dim(formatDuration(p.ms))}`;
341
+ case "turn-start": return `turn ${p.turn} started`;
342
+ case "turn-end": {
343
+ const status = p.status === "completed" ? "done" : c.red(p.status);
344
+ const activity = [plural(p.tools, "tool call"), plural(p.fileEdits, "file edit")].join(", ");
345
+ const error = p.error ? `: ${c.red(oneLine(p.error, 160))}` : "";
346
+ return `turn ${p.turn} ${status}${error} ${c.dim(`${formatDuration(p.ms)} · ${activity}`)}`;
347
+ }
348
+ case "check-start": return c.dim(`$ ${oneLine(p.command)}`);
349
+ case "check-end": {
350
+ const exit = p.exitCode === 0 ? c.dim("exit 0") : c.red(`exit ${p.exitCode}`);
351
+ return `$ ${oneLine(p.command)} ${exit} ${c.dim(formatDuration(p.ms))}`;
352
+ }
353
+ case "judge-start": return c.dim(`judging: ${oneLine(p.question, 120)}`);
354
+ case "judge-end": return judgeLine(c, p);
355
+ case "cleanup": return c.dim("cleaning up");
356
+ }
357
+ }
358
+ function judgeLine(c, j) {
359
+ const verdict = j.verdict === "pass" ? c.green("pass") : c.red(j.verdict);
360
+ const score = j.score === null ? "" : ` ${j.score.toFixed(2)}`;
361
+ const threshold = j.threshold === null ? "" : ` ${c.dim(`(≥ ${j.threshold})`)}`;
362
+ return `${c.dim("judge")} ${verdict}${score}${threshold} ${j.question}`;
363
+ }
364
+ /** The phase a running attempt is in, for the heartbeat. */
365
+ function phaseOf(p) {
366
+ switch (p?.kind) {
367
+ case void 0: return "starting";
368
+ case "sandbox": return "sandbox";
369
+ case "sandbox-ready": return "sandbox ready";
370
+ case "turn-start": return `turn ${p.turn}`;
371
+ case "turn-end": return `turn ${p.turn} done`;
372
+ case "check-start":
373
+ case "check-end": return "checks";
374
+ case "judge-start":
375
+ case "judge-end": return "judging";
376
+ case "cleanup": return "cleanup";
377
+ }
378
+ }
379
+ function capLines(text, max) {
380
+ const lines = text.replace(/\s+$/, "").split("\n");
381
+ if (lines.length <= max) return lines;
382
+ return [...lines.slice(0, max), `(+${lines.length - max} more lines)`];
383
+ }
384
+ /** An infra error's headline, then its last lines: the cause is at the end. */
385
+ function tailLines(text, max) {
386
+ const lines = text.replace(/\s+$/, "").split("\n");
387
+ if (lines.length <= max) return lines;
388
+ const rest = Math.max(1, max - 1);
389
+ return [
390
+ lines[0] ?? "",
391
+ `(${lines.length - 1 - rest} more lines)`,
392
+ ...lines.slice(-rest)
393
+ ];
394
+ }
395
+ /**
396
+ * Why an eval failed, as lines: the judge verdict (and reason) for judge failures, Vitest's
397
+ * formatted error (diff, code frame) for assertions, the tail of the message for infra errors.
398
+ */
399
+ function failureLines(c, e, options) {
400
+ const outcome = outcomeOf(e);
401
+ if (outcome === "cancelled") return [c.yellow("cancelled")];
402
+ const message = e.errors[0] ?? "";
403
+ const turns = (e.meta?.turnErrors ?? []).filter((t) => !message.includes(t.replace(/^turn \d+ error: /, ""))).map((t) => c.yellow(t));
404
+ return [...failureBody(c, e, outcome, message, options), ...turns];
405
+ }
406
+ function failureBody(c, e, outcome, message, options) {
407
+ if (outcome === "infra_error") {
408
+ if (e.meta?.prepare) {
409
+ const lines = message.trimEnd().split("\n");
410
+ return [
411
+ c.yellow("infra error"),
412
+ lines[0] ?? "",
413
+ lines.at(-1) ?? ""
414
+ ].filter((l, i, all) => all.indexOf(l) === i);
415
+ }
416
+ return [`${c.yellow("infra error")}`, ...tailLines(message, options.max)];
417
+ }
418
+ const failed = (e.meta?.judgements ?? []).filter((j) => j.verdict !== "pass");
419
+ if (e.meta?.errorKind === "judge" && failed.length) return failed.flatMap((j, i) => {
420
+ const reason = options.reasons?.[i];
421
+ return [judgeLine(c, {
422
+ ...j,
423
+ threshold: j.threshold
424
+ }), ...reason ? [` ${reason}`] : []];
425
+ });
426
+ const formatted = e.formattedErrors[0];
427
+ const body = formatted && e.meta?.errorKind !== "timeout" ? formatted : message;
428
+ return capLines((c.enabled ? body : stripAnsi(body)).replace(/^\n+/, ""), options.max).map((l) => c.enabled && body === message ? c.red(l) : l);
429
+ }
430
+ /** Preflight's lines: which sandboxes it prepares, the slow ones when ready, and each failure. */
431
+ function prepareLines(c, e, max) {
432
+ if (e.state === "start") {
433
+ const n = e.targets.length;
434
+ const names = e.targets.map((t) => t.label).join(", ");
435
+ return [c.dim(`preparing ${n} sandbox${n === 1 ? "" : "es"}: ${names}`)];
436
+ }
437
+ const { outcome } = e;
438
+ if (outcome.ok) return outcome.ms >= 1e3 ? [c.dim(`${c.green("✓")} ${outcome.target.label} ready ${formatDuration(outcome.ms)}`)] : [];
439
+ const who = outcome.target.setBy.join(", ");
440
+ return [`${c.red("✗")} ${outcome.target.label} failed to prepare ${c.dim(`(set by ${who}; only its attempts fail)`)}`, ...tailLines(outcome.failure.message, max).map((l) => ` ${c.yellow(l)}`)];
441
+ }
442
+ //#endregion
443
+ //#region src/ui/summary.ts
444
+ const FAILURE_LINES = 30;
445
+ const KINDS = [
446
+ {
447
+ title: "Judge failures",
448
+ test: (a) => a.error?.kind === "judge"
449
+ },
450
+ {
451
+ title: "Assertion failures",
452
+ test: (a) => a.error?.kind === "assertion"
453
+ },
454
+ {
455
+ title: "Timeouts",
456
+ test: (a) => a.error?.kind === "timeout"
457
+ },
458
+ {
459
+ title: "Infra errors",
460
+ test: (_, o) => o === "infra_error"
461
+ },
462
+ {
463
+ title: "Cancelled",
464
+ test: (_, o) => o === "cancelled"
465
+ }
466
+ ];
467
+ /** The engine's result for an attempt, or one rebuilt from the record (e.g. cancelled ones). */
468
+ function resultFor(entry, attempt, results) {
469
+ const key = attemptKey(entry.id, attempt.cell, attempt.n);
470
+ const found = results.find((r) => r.key === key);
471
+ const errorKind = attempt.error?.kind ?? null;
472
+ const meta = {
473
+ outcome: attempt.outcome === "skipped" ? "pass" : attempt.outcome,
474
+ errorKind,
475
+ judgements: attempt.judgements.map((j) => ({ ...j })),
476
+ turnErrors: attempt.turns.flatMap((t, i) => t.error ? [`turn ${i + 1} error: ${t.error}`] : []),
477
+ recordPath: ""
478
+ };
479
+ if (found) return {
480
+ ...found,
481
+ meta: found.meta ? {
482
+ ...found.meta,
483
+ errorKind
484
+ } : meta
485
+ };
486
+ return {
487
+ id: entry.id,
488
+ key,
489
+ file: entry.file,
490
+ group: entry.group,
491
+ name: entry.name,
492
+ attempt: void 0,
493
+ state: attempt.outcome === "pass" ? "passed" : attempt.outcome === "skipped" ? "skipped" : "failed",
494
+ durationMs: attempt.durationMs,
495
+ errors: attempt.error ? [attempt.error.message] : [],
496
+ formattedErrors: [],
497
+ meta
498
+ };
499
+ }
500
+ function entryOutcome(e) {
501
+ const outcomes = e.attempts.map(attemptOutcome);
502
+ if (outcomes.length && outcomes.every((o) => o === "cancelled")) return "cancelled";
503
+ return e.outcome;
504
+ }
505
+ const pct = (rate) => rate === null ? " – " : `${Math.round(rate * 100)}%`.padStart(4);
506
+ function evalColumns(c, e) {
507
+ const [attempt, ...more] = e.attempts;
508
+ if (e.todo) return c.dim("todo");
509
+ if (!attempt || e.outcome === "skipped") return c.dim("skipped");
510
+ if (more.length) {
511
+ const pass = e.attempts.filter((a) => a.outcome === "pass").length;
512
+ const counted = e.attempts.filter((a) => a.outcome === "pass" || a.outcome === "fail").length;
513
+ const rate = hasUngatedCells(e.cells) ? ` · ${pct(e.passRate).trim()} gated` : ` ${pct(e.passRate)}`;
514
+ return c.dim(`${`${pass}/${counted}`.padStart(7)}${rate} ${e.attempts.length} attempts`);
515
+ }
516
+ if (attemptOutcome(attempt) === "cancelled") return c.yellow("cancelled");
517
+ const cols = [formatDuration(attempt.durationMs).padStart(7)];
518
+ const scores = attempt.judgements.map((j) => j.score);
519
+ if (scores.length) cols.push(`judge ${Math.min(...scores).toFixed(2)}`);
520
+ const turns = attempt.turns.length;
521
+ cols.push(`${turns} turn${turns === 1 ? "" : "s"}`);
522
+ return c.dim(cols.join(" "));
523
+ }
524
+ /** Pass rate per cell for evals with several attempts; with compare, each cell's role and lift. */
525
+ function cellRows(c, e, indent) {
526
+ if (e.attempts.length < 2) return [];
527
+ const compare = e.cells.some((cell) => cell.role === "variant");
528
+ const width = Math.max(...e.cells.map((cell) => cell.key.length));
529
+ return e.cells.flatMap((cell) => {
530
+ const role = compare ? ` ${cell.role.padEnd(7)}` : "";
531
+ const counts = `${cell.pass}/${cell.pass + cell.fail}`.padStart(5);
532
+ const infra = cell.infraError ? c.yellow(` ${cell.infraError} infra`) : "";
533
+ let note = "";
534
+ if (cell.lift) {
535
+ const { minLift } = cell.lift;
536
+ const below = minLift !== void 0 && cell.lift.interval.lo < minLift;
537
+ const text = `${liftLabel(cell.lift)}${minLift !== void 0 ? ` (minLift ${Math.round(minLift * 100)})` : ""}`;
538
+ note = ` ${below ? c.red(`${text} ✗`) : text}`;
539
+ } else if (compare && cell.gate === "none" && cell.fail > 0) note = ` ${c.dim("not gated")}`;
540
+ if (cell.gate !== "all" && cell.gate !== "none") note += ` ${c.dim(`gate ${gateLabel(cell.gate)}`)}`;
541
+ const rows = [`${indent}${c.dim(cell.key.padEnd(width))}${role} ${counts} ${pct(cell.passRate)}${note}${infra}`];
542
+ const effort = cellContrast(e, cell.key);
543
+ if (effort) rows.push(`${indent} ${c.dim(`effort: ${effortLabel(effort)}`)}`);
544
+ if (cell.overrides.length) rows.push(`${indent} ${c.dim(`overrides: ${cell.overrides.join(", ")}`)}`);
545
+ return rows;
546
+ });
547
+ }
548
+ /** The run's evals as a file › group tree, one row per eval and per cell of a matrix. */
549
+ function tree(c, evals) {
550
+ const rows = [];
551
+ let path = [];
552
+ for (const e of evals) {
553
+ const next = [e.file, ...e.group];
554
+ let same = 0;
555
+ while (same < path.length && same < next.length && path[same] === next[same]) same++;
556
+ for (let i = same; i < next.length; i++) rows.push({
557
+ indent: i,
558
+ text: i === 0 ? c.bold(next[i] ?? "") : next[i] ?? ""
559
+ });
560
+ path = next;
561
+ rows.push({
562
+ indent: next.length,
563
+ text: e.name,
564
+ entry: e
565
+ });
566
+ }
567
+ const width = Math.max(...rows.filter((r) => r.entry).map((r) => r.indent * 2 + 2 + r.text.length));
568
+ return rows.flatMap(({ indent, text, entry }) => {
569
+ const pad = " ".repeat(indent);
570
+ if (!entry) return [` ${pad}${text}`];
571
+ return [` ${pad}${`${mark(c, entryOutcome(entry))} ${text}`}${" ".repeat(Math.max(0, width - indent * 2 - 2 - text.length))} ${evalColumns(c, entry)}`, ...cellRows(c, entry, ` ${pad} `)];
572
+ });
573
+ }
574
+ function totals(c, s, cancelled = 0) {
575
+ const fail = s.fail - s.notGated - cancelled;
576
+ const parts = [
577
+ s.pass && c.green(`${s.pass} passed`),
578
+ fail > 0 && c.red(`${fail} failed`),
579
+ s.notGated > 0 && c.dim(`${s.notGated} missed, not gated`),
580
+ s.infraError && c.yellow(`${s.infraError} infra error`),
581
+ cancelled && c.yellow(`${cancelled} cancelled`),
582
+ s.skipped && c.dim(`${s.skipped} skipped`)
583
+ ].filter(Boolean);
584
+ const scope = s.attempts === s.evals ? `${s.evals}` : `${s.attempts} attempts, ${s.evals} evals`;
585
+ return parts.length ? `${parts.join(c.dim(" · "))} ${c.dim(`(${scope})`)}` : "no evals ran";
586
+ }
587
+ /** Failing attempts, in run order, including expected misses. */
588
+ function failingAttempts(evals) {
589
+ return evals.flatMap((entry) => entry.attempts.map((attempt) => ({
590
+ entry,
591
+ attempt,
592
+ outcome: attemptOutcome(attempt)
593
+ })).filter(({ outcome }) => outcome !== "pass" && outcome !== "skipped"));
594
+ }
595
+ function renderSummary(c, e, attemptDir) {
596
+ const { record } = e;
597
+ const lines = [];
598
+ if (record.evals.length) lines.push(c.bold("Summary"), ...tree(c, record.evals));
599
+ const all = failingAttempts(record.evals);
600
+ const missed = all.filter(({ entry, attempt }) => isUngatedMiss(entry, attempt));
601
+ const failing = all.filter((f) => !missed.includes(f));
602
+ if (failing.length) {
603
+ lines.push("", c.bold(c.red(`Failures (${failing.length})`)));
604
+ for (const kind of KINDS) {
605
+ const members = failing.filter(({ attempt, outcome }) => kind.test(attempt, outcome));
606
+ if (!members.length) continue;
607
+ lines.push("", c.bold(`${kind.title} (${members.length})`));
608
+ for (const { entry, attempt, outcome } of members) {
609
+ const result = resultFor(entry, attempt, e.results);
610
+ const which = entry.attempts.length > 1 ? ` ${c.dim(attempt.id)}` : "";
611
+ const gated = entry.cells.find((x) => x.key === attempt.cell)?.gate !== "none";
612
+ lines.push(` ${mark(c, outcome)} ${entry.id}${which}${gated ? "" : ` ${c.dim("(not gated)")}`}`);
613
+ if (outcome !== "cancelled") for (const line of failureLines(c, result, { max: FAILURE_LINES })) lines.push(` ${line}`);
614
+ if (!attempt.error?.prepare) lines.push(` ${c.dim("record")} ${attemptDir(entry.id, attempt.cell, attempt.n)}`);
615
+ }
616
+ }
617
+ }
618
+ if (missed.length) {
619
+ lines.push("", c.bold(`Expected misses, not gated (${missed.length})`));
620
+ for (const { entry, attempt } of missed) {
621
+ const which = entry.attempts.length > 1 ? ` ${c.dim(attempt.id)}` : "";
622
+ const [reason] = failureLines(colors(false), resultFor(entry, attempt, e.results), { max: 1 }).filter((l) => l.trim());
623
+ lines.push(` ${MISS_MARK(c)} ${entry.id}${which}`);
624
+ if (reason) lines.push(` ${c.dim(reason.trim())}`);
625
+ lines.push(` ${c.dim("record")} ${attemptDir(entry.id, attempt.cell, attempt.n)}`);
626
+ }
627
+ }
628
+ for (const error of e.unhandledErrors) lines.push("", c.red(`error: ${error}`));
629
+ const cancelled = failing.filter((f) => f.outcome === "cancelled").length;
630
+ const label = record.summary.attempts === record.summary.evals ? " Evals" : "Attempts";
631
+ const lifted = record.evals.flatMap((entry) => entry.cells.flatMap((cell) => cell.lift?.minLift !== void 0 && cell.lift.interval.lo < cell.lift.minLift ? [`${entry.id} ${cell.key} ${liftLabel(cell.lift)}: its lower bound is below minLift ${Math.round(cell.lift.minLift * 100)}`] : []));
632
+ if (lifted.length) {
633
+ lines.push("", c.bold(c.red(`Lift below minLift (${lifted.length})`)));
634
+ for (const line of lifted) lines.push(` ${c.red("✗")} ${line}`);
635
+ }
636
+ if (record.status === "budget") lines.push("", c.yellow("Budget reached: attempts that hadn’t started were skipped"));
637
+ lines.push("", `${c.dim(label)} ${totals(c, record.summary, cancelled)}`, `${c.dim("Duration")} ${formatDuration(record.durationMs)}`);
638
+ const tokens = formatUsage(record.cost.usage);
639
+ if (tokens) lines.push(` ${c.dim("Tokens")} ${tokens}`);
640
+ lines.push(` ${c.dim("Results")} ${e.summaryPath}`);
641
+ return lines;
642
+ }
643
+ //#endregion
644
+ //#region src/ui/tags.ts
645
+ const STOPWORDS = new Set("a an the and or of to in on for with without from by at as is are be it its that this when than then into via not no".split(" "));
646
+ const MAX_WORDS = 3;
647
+ const MAX_LENGTH = 24;
648
+ const words = (name) => name.toLowerCase().split(/[^\p{L}\p{N}]+/u).filter((w) => w && !STOPWORDS.has(w));
649
+ /**
650
+ * Short handles for the evals of one file, e.g. `greet`: the first distinctive words of each
651
+ * name, where words shared by most of the file's evals ("recommends Genkit over …") don't
652
+ * count. Depends only on the file's evals, so a tag stays the same across runs and filters.
653
+ */
654
+ function fileTags(evals, taken) {
655
+ const lists = evals.map((e) => words(e.name));
656
+ const df = /* @__PURE__ */ new Map();
657
+ for (const list of lists) for (const w of new Set(list)) df.set(w, (df.get(w) ?? 0) + 1);
658
+ const common = (w) => evals.length > 1 && (df.get(w) ?? 0) > evals.length / 2;
659
+ const candidates = lists.map((list) => {
660
+ const rare = list.filter((w) => !common(w));
661
+ return rare.length ? rare : list.length ? list : ["eval"];
662
+ });
663
+ const tags = /* @__PURE__ */ new Map();
664
+ const clip = (tag) => tag.slice(0, MAX_LENGTH).replace(/-+$/, "");
665
+ const prefix = (list, n) => clip(list.slice(0, n).join("-"));
666
+ evals.forEach((e, i) => {
667
+ const own = candidates[i] ?? ["eval"];
668
+ let tag = prefix(own, 1);
669
+ for (let n = 1; n <= MAX_WORDS; n++) {
670
+ tag = prefix(own, n);
671
+ if (!candidates.some((other, j) => j !== i && prefix(other, n) === tag) && !taken.has(tag)) break;
672
+ }
673
+ let unique = tag;
674
+ for (let k = 2; taken.has(unique); k++) unique = `${tag}-${k}`;
675
+ taken.add(unique);
676
+ tags.set(e.id, unique);
677
+ });
678
+ return tags;
679
+ }
680
+ /** Tags evals as their files are collected; later files never rename earlier tags. */
681
+ var Tagger = class {
682
+ #tags = /* @__PURE__ */ new Map();
683
+ #taken = /* @__PURE__ */ new Set();
684
+ addFile(attempts) {
685
+ const fresh = [...new Map(attempts.map((a) => [a.id, a])).values()].filter((e) => !this.#tags.has(e.id));
686
+ for (const [id, tag] of fileTags(fresh, this.#taken)) this.#tags.set(id, tag);
687
+ }
688
+ /** `greet#1`: the eval's tag and run number. */
689
+ label(id, attempt) {
690
+ let tag = this.#tags.get(id);
691
+ if (!tag) {
692
+ this.addFile([{
693
+ id,
694
+ name: id.split(" › ").at(-1) ?? id
695
+ }]);
696
+ tag = this.#tags.get(id) ?? "eval";
697
+ }
698
+ return `${tag}#${attempt}`;
699
+ }
700
+ };
701
+ //#endregion
702
+ //#region src/ui/agent.ts
703
+ const MAX_LINES = 12;
704
+ const WORD = {
705
+ fail: "FAIL",
706
+ infra_error: "INFRA",
707
+ cancelled: "CANCELLED"
708
+ };
709
+ var AgentRenderer = class {
710
+ #o;
711
+ #c = colors(false);
712
+ #logs = /* @__PURE__ */ new Map();
713
+ #printed = /* @__PURE__ */ new Set();
714
+ /** `[key]` prefixes for `-v` lines, from each attempt's start. */
715
+ #labels = /* @__PURE__ */ new Map();
716
+ #tags = new Tagger();
717
+ constructor(options) {
718
+ this.#o = options;
719
+ }
720
+ #write(text) {
721
+ this.#o.out.write(`${text.trimEnd()}\n`);
722
+ }
723
+ #failure(result, cell, n) {
724
+ const outcome = outcomeOf(result);
725
+ if (outcome === "pass" || outcome === "skipped") return;
726
+ this.#printed.add(result.key);
727
+ const kind = outcome === "fail" ? result.meta?.errorKind ?? "assertion" : null;
728
+ const detail = [
729
+ formatDuration(result.durationMs),
730
+ kind,
731
+ result.attempt?.gate === "none" ? "not gated" : ""
732
+ ].filter(Boolean).join(", ");
733
+ const which = result.attempt?.label ? ` [${result.attempt.label}]` : "";
734
+ const missed = outcome === "fail" && result.attempt?.gate === "none";
735
+ this.#write(`${missed ? "MISS" : WORD[outcome]} ${result.id}${which} (${detail})`);
736
+ if (outcome !== "cancelled") {
737
+ const lines = failureLines(this.#c, result, { max: MAX_LINES });
738
+ for (const line of missed ? lines.filter((l) => l.trim()).slice(0, 1) : lines) this.#write(` ${line}`);
739
+ }
740
+ const logs = missed ? void 0 : this.#logs.get(result.key);
741
+ if (logs?.length) {
742
+ this.#write(" console:");
743
+ for (const line of capLines(logs.join("\n"), MAX_LINES)) this.#write(` ${line}`);
744
+ }
745
+ if (!result.meta?.prepare) this.#write(` record: ${this.#o.attemptDir(result.id, cell, n)}`);
746
+ }
747
+ onEvent(e) {
748
+ switch (e.type) {
749
+ case "plan":
750
+ for (const line of e.lines) this.#write(line);
751
+ for (const w of e.sandboxWarnings ?? []) this.#write(sandboxWarningLine(w));
752
+ return;
753
+ case "run-start":
754
+ this.#write(`reeval run: ${e.files} eval file${e.files === 1 ? "" : "s"}`);
755
+ return;
756
+ case "collected":
757
+ this.#tags.addFile(e.evals);
758
+ return;
759
+ case "eval-start": {
760
+ const tag = this.#tags.label(e.id, e.attempt?.n ?? 1);
761
+ this.#labels.set(e.key, `[${tag} ${attemptAxes(e.attempt, "agent")}]`);
762
+ return;
763
+ }
764
+ case "debug": {
765
+ const label = this.#labels.get(e.key) ?? `[${e.key}]`;
766
+ for (const line of debugLines(this.#c, e.line, this.#o.verbosity === 2)) this.#write(`${label} ${line}`);
767
+ return;
768
+ }
769
+ case "log": {
770
+ if (!e.key) return;
771
+ const logs = this.#logs.get(e.key) ?? [];
772
+ logs.push(...e.text.replace(/\n$/, "").split("\n"));
773
+ this.#logs.set(e.key, logs);
774
+ return;
775
+ }
776
+ case "eval-end": {
777
+ const { attempt } = e.result;
778
+ this.#failure(e.result, attempt?.cell.key ?? "", attempt?.n ?? 1);
779
+ return;
780
+ }
781
+ case "prepare":
782
+ for (const line of prepareLines(this.#c, e, MAX_LINES)) this.#write(line);
783
+ return;
784
+ case "notice":
785
+ if (e.level !== "info") this.#write(e.text);
786
+ return;
787
+ case "run-end": {
788
+ const failing = failingAttempts(e.record.evals);
789
+ for (const { entry, attempt, outcome } of failing) {
790
+ const result = resultFor(entry, attempt, e.results);
791
+ if (outcome === "cancelled" && !this.#printed.has(result.key)) this.#failure(result, attempt.cell, attempt.n);
792
+ }
793
+ for (const error of e.unhandledErrors) this.#write(`ERROR ${error}`);
794
+ const s = e.record.summary;
795
+ const cancelled = failing.filter((f) => f.outcome === "cancelled").length;
796
+ const parts = [
797
+ s.pass && `${s.pass} passed`,
798
+ s.fail - s.notGated - cancelled > 0 && `${s.fail - s.notGated - cancelled} failed`,
799
+ s.notGated > 0 && `${s.notGated} missed, not gated`,
800
+ s.infraError && `${s.infraError} infra error`,
801
+ cancelled && `${cancelled} cancelled`,
802
+ s.skipped && `${s.skipped} skipped`
803
+ ].filter(Boolean);
804
+ const scope = s.attempts === s.evals ? "" : ` (${s.attempts} attempts, ${s.evals} evals)`;
805
+ this.#write(parts.length ? `${parts.join(", ")}${scope}` : "no evals ran");
806
+ this.#write(`summary: ${e.summaryPath}`);
807
+ return;
808
+ }
809
+ default: return;
810
+ }
811
+ }
812
+ close() {}
813
+ };
814
+ //#endregion
815
+ //#region src/ui/plain.ts
816
+ /** In a grouped block, an end line replaces its start line and keeps its start time. */
817
+ const STARTS = {
818
+ "sandbox-ready": "sandbox",
819
+ "turn-end": "turn-start",
820
+ "check-end": "check-start",
821
+ "judge-end": "judge-start"
822
+ };
823
+ const ERROR_LINES = 12;
824
+ /** GitHub Actions workflow-command escaping. */
825
+ const ghEscape = (s, property = false) => {
826
+ const out = s.replace(/%/g, "%25").replace(/\r/g, "%0D").replace(/\n/g, "%0A");
827
+ return property ? out.replace(/:/g, "%3A").replace(/,/g, "%2C") : out;
828
+ };
829
+ /** The eval file's line in Vitest's formatted error (`❯ evals/a.eval.ts:7:33`), if any. */
830
+ function locate(result) {
831
+ const text = stripAnsi(result.formattedErrors[0] ?? "");
832
+ const file = result.file.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
833
+ const match = new RegExp(`❯ ${file}:(\\d+):(\\d+)`).exec(text);
834
+ return match?.[1] && match[2] ? {
835
+ line: match[1],
836
+ col: match[2]
837
+ } : void 0;
838
+ }
839
+ var PlainRenderer = class {
840
+ #o;
841
+ #c;
842
+ #tags = new Tagger();
843
+ #running = /* @__PURE__ */ new Map();
844
+ #now;
845
+ #queued = /* @__PURE__ */ new Set();
846
+ #runStartedAt = 0;
847
+ #lastWrite = 0;
848
+ #timer;
849
+ constructor(options) {
850
+ this.#o = options;
851
+ this.#c = options.colors;
852
+ this.#now = options.now ?? Date.now;
853
+ const { heartbeatMs } = options.output;
854
+ if (options.timers !== false && heartbeatMs > 0) {
855
+ this.#timer = setInterval(() => this.tick(), Math.min(heartbeatMs, 5e3));
856
+ this.#timer.unref();
857
+ }
858
+ }
859
+ #write(text) {
860
+ this.#o.out.write(`${text.trimEnd()}\n`);
861
+ this.#lastWrite = this.#now();
862
+ }
863
+ #attempt(ref, startedAt) {
864
+ const n = ref.attempt?.n ?? 1;
865
+ const label = `[${this.#tags.label(ref.id, n)} ${attemptAxes(ref.attempt, this.#o.harness)}]`;
866
+ const ungated = ref.attempt?.gate === "none";
867
+ return {
868
+ key: ref.key,
869
+ id: ref.id,
870
+ label,
871
+ startedAt,
872
+ lines: [],
873
+ last: void 0,
874
+ ungated
875
+ };
876
+ }
877
+ #prefix(a) {
878
+ return this.#c.tag(a.label)(a.label);
879
+ }
880
+ #detail(a, line, prefixed) {
881
+ const body = line.debug ? ` ${line.text}` : ` ${this.#c.dim(offset(line.at - a.startedAt))} ${line.text}`;
882
+ return prefixed ? `${this.#prefix(a)}${body}` : body.slice(1);
883
+ }
884
+ get #streaming() {
885
+ return this.#o.output.logOrder === "stream" && this.#o.output.outputLogs === "full";
886
+ }
887
+ #add(a, line) {
888
+ a.lines.push(line);
889
+ if (this.#streaming) this.#write(this.#detail(a, line, true));
890
+ }
891
+ onEvent(e) {
892
+ const c = this.#c;
893
+ switch (e.type) {
894
+ case "run-start": {
895
+ this.#runStartedAt = e.at;
896
+ const files = `${e.files} eval file${e.files === 1 ? "" : "s"}`;
897
+ const order = `${this.#o.output.logOrder} logs`;
898
+ const setup = `harness ${e.harness} · sandbox ${e.sandbox} · concurrency ${e.concurrency}`;
899
+ this.#write(c.dim(`reeval ${e.version} · ${files} · ${setup} · ${order}`));
900
+ return;
901
+ }
902
+ case "plan":
903
+ for (const [i, line] of e.lines.entries()) this.#write(line.startsWith("warning:") ? c.yellow(line) : i === 0 ? line : c.dim(line));
904
+ for (const w of e.sandboxWarnings ?? []) this.#sandboxWarning(w);
905
+ return;
906
+ case "collected":
907
+ this.#tags.addFile(e.evals);
908
+ for (const x of e.evals) if (!x.skipped) this.#queued.add(x.key);
909
+ return;
910
+ case "eval-start": {
911
+ const a = this.#attempt(e, e.at);
912
+ a.last = e;
913
+ this.#queued.delete(e.key);
914
+ this.#running.set(e.key, a);
915
+ this.#write(`${this.#prefix(a)} ${c.dim("▶")} ${e.id}`);
916
+ return;
917
+ }
918
+ case "eval-progress": {
919
+ const a = this.#running.get(e.key);
920
+ if (!a) return;
921
+ a.last = e;
922
+ const { progress } = e;
923
+ const paint = a.ungated ? {
924
+ ...c,
925
+ red: c.dim
926
+ } : c;
927
+ const line = {
928
+ at: progress.at,
929
+ text: progressLine(paint, progress),
930
+ kind: progress.kind
931
+ };
932
+ const start = STARTS[progress.kind];
933
+ const open = start && a.lines.findLastIndex((l) => l.kind === start);
934
+ if (!this.#streaming && open !== void 0 && open >= 0) {
935
+ line.at = a.lines[open]?.at ?? line.at;
936
+ a.lines.splice(open, 1);
937
+ }
938
+ this.#add(a, line);
939
+ return;
940
+ }
941
+ case "debug": {
942
+ const a = this.#running.get(e.key);
943
+ if (!a) return;
944
+ for (const text of debugLines(c, e.line, this.#o.verbosity === 2)) this.#add(a, {
945
+ at: e.line.at,
946
+ text,
947
+ debug: true
948
+ });
949
+ return;
950
+ }
951
+ case "log": {
952
+ const a = e.key ? this.#running.get(e.key) : void 0;
953
+ const label = e.stream === "stderr" ? c.yellow("stderr |") : c.dim("stdout |");
954
+ for (const text of e.text.replace(/\n$/, "").split("\n")) if (a) this.#add(a, {
955
+ at: e.at,
956
+ text: `${label} ${text}`
957
+ });
958
+ else this.#write(`${label} ${text}`);
959
+ return;
960
+ }
961
+ case "eval-end":
962
+ this.#end(e.result, e.at);
963
+ return;
964
+ case "prepare":
965
+ for (const line of prepareLines(c, e, ERROR_LINES)) this.#write(line);
966
+ if (e.state === "failed" && this.#o.output.githubActions) {
967
+ const { target, failure } = e.outcome;
968
+ const what = target.kind === "workspace" ? "The" : "Sandbox";
969
+ const title = ghEscape(`${what} ${target.label} failed to prepare`, true);
970
+ this.#write(`::error title=${title}::${ghEscape(failure.message)}`);
971
+ }
972
+ return;
973
+ case "notice": {
974
+ const paint = {
975
+ info: c.dim,
976
+ warn: c.yellow,
977
+ error: c.red
978
+ }[e.level];
979
+ this.#write(paint(e.text));
980
+ return;
981
+ }
982
+ case "run-end":
983
+ this.#write("");
984
+ for (const line of renderSummary(c, e, this.#o.attemptDir)) this.#write(line);
985
+ return;
986
+ }
987
+ }
988
+ #sandboxWarning(w) {
989
+ const c = this.#c;
990
+ const line = sandboxWarningLine(w);
991
+ this.#write(w.kind === "root" ? c.bold(c.red(line)) : c.yellow(line));
992
+ if (!this.#o.output.githubActions) return;
993
+ const file = w.file ? `file=${ghEscape(w.file, true)},` : "";
994
+ const title = w.kind === "root" ? "Sandbox runs as root" : "Sandbox has extra capabilities";
995
+ this.#write(`::warning ${file}title=${ghEscape(title, true)}::${ghEscape(w.message)}`);
996
+ }
997
+ #end(result, at) {
998
+ const c = this.#c;
999
+ const a = this.#running.get(result.key) ?? this.#attempt(result, at - result.durationMs);
1000
+ this.#running.delete(result.key);
1001
+ this.#queued.delete(result.key);
1002
+ const outcome = outcomeOf(result);
1003
+ const failed = outcome !== "pass" && outcome !== "skipped";
1004
+ const time = outcome === "skipped" ? "" : ` ${c.dim(formatDuration(result.durationMs))}`;
1005
+ const ungated = failed && result.attempt?.gate === "none" ? ` ${c.dim("(not gated)")}` : "";
1006
+ const missed = ungated !== "" && outcome === "fail";
1007
+ const head = `${this.#prefix(a)} ${missed ? MISS_MARK(c) : mark(c, outcome)} ${result.id}${time}${ungated}`;
1008
+ const { outputLogs, githubActions, logPrefix } = this.#o.output;
1009
+ const showDetail = !this.#streaming && a.lines.length > 0 && (outputLogs === "full" || outputLogs === "errors-only" && failed);
1010
+ const judged = result.meta?.errorKind === "judge" && (showDetail || this.#streaming);
1011
+ const errors = failed && outputLogs !== "none" && !judged ? missed ? failureLines(colors(false), result, { max: 1 }).filter((l) => l.trim()).slice(0, 1).map((l) => c.dim(l)) : failureLines(c, result, { max: ERROR_LINES }) : [];
1012
+ const lines = a.lines.filter((l) => l.kind !== "cleanup").sort((x, y) => x.at - y.at);
1013
+ const prefixed = logPrefix === "task";
1014
+ const group = githubActions && showDetail && (!failed || missed);
1015
+ this.#write(group ? `::group::${stripAnsi(head)}` : head);
1016
+ if (showDetail) for (const line of lines) this.#write(this.#detail(a, line, prefixed));
1017
+ for (const line of errors) this.#write(prefixed ? `${this.#prefix(a)} ${line}` : ` ${line}`);
1018
+ if (group) this.#write("::endgroup::");
1019
+ if (githubActions && failed && !ungated && !result.meta?.prepare) {
1020
+ const which = result.attempt?.label ? ` [${result.attempt.label}]` : "";
1021
+ const title = ghEscape(`${result.id}${which}`, true);
1022
+ const message = ghEscape(failureLines(colors(false), result, { max: ERROR_LINES }).join("\n"));
1023
+ const at = locate(result);
1024
+ const where = at ? `,line=${at.line},col=${at.col}` : "";
1025
+ this.#write(`::error file=${ghEscape(result.file, true)}${where},title=${title}::${message}`);
1026
+ }
1027
+ }
1028
+ /** Prints the heartbeat if nothing was written for `heartbeatMs` and evals are running. */
1029
+ tick(now = this.#now()) {
1030
+ const { heartbeatMs } = this.#o.output;
1031
+ if (heartbeatMs <= 0 || this.#running.size === 0) return;
1032
+ if (now - this.#lastWrite < heartbeatMs) return;
1033
+ const c = this.#c;
1034
+ const running = [...this.#running.values()].map((a) => {
1035
+ const progress = a.last?.type === "eval-progress" ? a.last.progress : void 0;
1036
+ return `${this.#prefix(a)} ${phaseOf(progress)} ${c.dim(formatDuration(now - a.startedAt))}`;
1037
+ });
1038
+ const queued = this.#queued.size ? `, ${this.#queued.size} queued` : "";
1039
+ const elapsed = formatDuration(now - this.#runStartedAt);
1040
+ this.#write(`${c.dim(`… ${this.#running.size} running${queued} after ${elapsed}:`)} ${running.join(c.dim(", "))}`);
1041
+ }
1042
+ close() {
1043
+ clearInterval(this.#timer);
1044
+ }
1045
+ };
1046
+ //#endregion
1047
+ //#region src/ui/mode.ts
1048
+ const set = (env, name) => {
1049
+ const value = env[name];
1050
+ return !!value && value !== "false" && value !== "0";
1051
+ };
1052
+ const CI_VARS = [
1053
+ "BUILD_ID",
1054
+ "BUILD_NUMBER",
1055
+ "CI",
1056
+ "CI_APP_ID",
1057
+ "CI_BUILD_ID",
1058
+ "CI_BUILD_NUMBER",
1059
+ "CI_NAME",
1060
+ "CONTINUOUS_INTEGRATION",
1061
+ "RUN_ID",
1062
+ "TEAMCITY_VERSION",
1063
+ "GITHUB_ACTIONS"
1064
+ ];
1065
+ const isCI = (env) => CI_VARS.some((name) => set(env, name));
1066
+ function pick(name, value, allowed) {
1067
+ if (value === void 0 || value === "") return void 0;
1068
+ if (value === "auto" || allowed.includes(value)) return value;
1069
+ throw new UsageError(`${name} must be one of ${[...allowed, "auto"].join(", ")}`);
1070
+ }
1071
+ function parseHeartbeat(value) {
1072
+ if (!value) return 6e4;
1073
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m)?$/.exec(value);
1074
+ if (!match) throw new UsageError(`REEVAL_HEARTBEAT must be a duration like 30s or 0`);
1075
+ const unit = {
1076
+ ms: 1,
1077
+ s: 1e3,
1078
+ m: 6e4
1079
+ }[match[2] ?? "s"] ?? 1e3;
1080
+ return Number(match[1]) * unit;
1081
+ }
1082
+ function resolveOutput(input) {
1083
+ const { flags, env } = input;
1084
+ const ci = isCI(env);
1085
+ const githubActions = set(env, "GITHUB_ACTIONS");
1086
+ const ui = pick("--ui", flags.ui ?? env.REEVAL_UI, [
1087
+ "live",
1088
+ "plain",
1089
+ "agent"
1090
+ ]);
1091
+ const requested = ui && ui !== "auto" ? ui : input.agent ? "agent" : ci || !input.isTTY ? "plain" : "live";
1092
+ const mode = requested === "live" ? "plain" : requested;
1093
+ const order = pick("--log-order", flags.logOrder ?? env.REEVAL_LOG_ORDER, ["stream", "grouped"]);
1094
+ const logOrder = order && order !== "auto" ? order : ci ? "grouped" : "stream";
1095
+ const prefix = pick("--log-prefix", flags.logPrefix ?? env.REEVAL_LOG_PREFIX, ["task", "none"]);
1096
+ const logPrefix = prefix && prefix !== "auto" ? prefix : githubActions && logOrder === "grouped" ? "none" : "task";
1097
+ const logs = pick("--output-logs", flags.outputLogs ?? env.REEVAL_OUTPUT_LOGS, [
1098
+ "full",
1099
+ "errors-only",
1100
+ "none"
1101
+ ]);
1102
+ return {
1103
+ requested,
1104
+ mode,
1105
+ logOrder,
1106
+ logPrefix,
1107
+ outputLogs: logs && logs !== "auto" ? logs : "full",
1108
+ color: env.NO_COLOR ? false : env.FORCE_COLOR !== void 0 && env.FORCE_COLOR !== "" ? set(env, "FORCE_COLOR") : mode === "agent" ? false : input.isTTY || githubActions,
1109
+ githubActions,
1110
+ heartbeatMs: mode === "agent" ? 0 : parseHeartbeat(env.REEVAL_HEARTBEAT)
1111
+ };
1112
+ }
1113
+ //#endregion
1114
+ //#region src/ui/index.ts
1115
+ function createRenderer(options) {
1116
+ const out = { write: (text) => options.out.write(redactSecrets(text)) };
1117
+ if (options.output.mode === "agent") return new AgentRenderer({
1118
+ ...options,
1119
+ out
1120
+ });
1121
+ return new PlainRenderer({
1122
+ ...options,
1123
+ out,
1124
+ colors: colors(options.output.color)
1125
+ });
1126
+ }
1127
+ //#endregion
1128
+ //#region src/cli.ts
1129
+ const HISTORY_COMMANDS = {
1130
+ record: 1,
1131
+ history: 1,
1132
+ show: 1,
1133
+ compare: 1,
1134
+ checkout: 1,
1135
+ query: 1,
1136
+ analyze: 1
1137
+ };
1138
+ const EXPECTED_ERRORS = [
1139
+ "CommandError",
1140
+ "RefError",
1141
+ "HistoryError",
1142
+ "StudioBuildError"
1143
+ ];
1144
+ const EXIT = {
1145
+ ok: 0,
1146
+ failed: 1,
1147
+ usage: 2,
1148
+ infra: 3,
1149
+ interrupted: 130,
1150
+ terminated: 143
1151
+ };
1152
+ const version = PACKAGE_VERSION;
1153
+ const HELP = helpText(version);
1154
+ const { green, red, dim } = colors(!!process.stdout.isTTY && !process.env.NO_COLOR);
1155
+ const startedAt = Date.now();
1156
+ const argv = process.argv.slice(2);
1157
+ /** What the invocation log needs from the command as it goes. */
1158
+ const invocation = {
1159
+ runId: null,
1160
+ unhandledErrors: []
1161
+ };
1162
+ /** Set by the first Ctrl-C or SIGTERM of `reeval run`. */
1163
+ let interrupted = false;
1164
+ /** The last signal that cancelled the run: 130 after SIGINT, 143 after SIGTERM. */
1165
+ let stopCode = EXIT.interrupted;
1166
+ async function run(argv) {
1167
+ const { values, positionals } = parseCli(argv);
1168
+ invocation.flags = values;
1169
+ if (values.version) {
1170
+ console.log(version);
1171
+ return EXIT.ok;
1172
+ }
1173
+ const [command, ...filters] = positionals;
1174
+ if (values.help || !command) {
1175
+ console.log(HELP);
1176
+ return values.help ? EXIT.ok : EXIT.usage;
1177
+ }
1178
+ if (command === "init") {
1179
+ const { initCommand } = await import("./init-z2ZPn9Tj.js");
1180
+ return initCommand(values, filters, {
1181
+ out: (s) => console.log(s),
1182
+ input: process.stdin,
1183
+ output: process.stdout,
1184
+ cwd: process.cwd()
1185
+ });
1186
+ }
1187
+ if (command === "doctor") {
1188
+ const { doctorCommand } = await import("./doctor-CxlH1pDM.js");
1189
+ return doctorCommand(values, {
1190
+ out: (s) => console.log(s),
1191
+ color: !!process.stdout.isTTY && !process.env.NO_COLOR,
1192
+ confirm: process.stdin.isTTY && process.stdout.isTTY ? confirm : void 0
1193
+ });
1194
+ }
1195
+ if (command === "report") {
1196
+ const { reportCommand } = await import("./command-D-jnQWVG.js");
1197
+ return reportCommand(values, filters, {
1198
+ out: (s) => console.log(s),
1199
+ err: (s) => console.error(dim(s)),
1200
+ interactive: !!process.stdin.isTTY && !!process.stdout.isTTY,
1201
+ confirm: process.stdin.isTTY && process.stdout.isTTY ? confirm : void 0
1202
+ });
1203
+ }
1204
+ const configPath = configPathFor(values.config);
1205
+ if (command === "image") {
1206
+ if (filters[0] !== "build") throw new UsageError(`Unknown command "image ${filters[0] ?? ""}". Did you mean reeval image build?`);
1207
+ return imageBuildCommand(values, configPath, filters.slice(1));
1208
+ }
1209
+ if (command === "studio") {
1210
+ const config = await loadConfig(configPath, "studio");
1211
+ const { studioCommand } = await import("./command-BSJzHMtP.js");
1212
+ return studioCommand({
1213
+ config,
1214
+ flags: values,
1215
+ version,
1216
+ build: filters[0] === "build"
1217
+ });
1218
+ }
1219
+ if (command === "mcp") {
1220
+ if (!values.http) console.log = console.info = console.debug = console.error;
1221
+ const config = await loadConfig(configPath, "mcp");
1222
+ const { mcpCommand } = await import("./command-DQAvbG7d.js");
1223
+ return mcpCommand({
1224
+ config,
1225
+ flags: values,
1226
+ version
1227
+ });
1228
+ }
1229
+ if (command in HISTORY_COMMANDS) {
1230
+ const config = await loadConfig(configPath, command);
1231
+ const commands = await import("./commands-A4udfyKZ.js");
1232
+ const ctx = {
1233
+ config,
1234
+ flags: values,
1235
+ out: (s) => console.log(s)
1236
+ };
1237
+ switch (command) {
1238
+ case "record": return commands.recordCommand(ctx, filters);
1239
+ case "history": return commands.historyCommand(ctx, filters);
1240
+ case "show": return commands.showCommand(ctx, filters);
1241
+ case "compare": return commands.compareCommand(ctx, filters);
1242
+ case "checkout": return commands.checkoutCommand(ctx, filters);
1243
+ case "query": {
1244
+ const { queryCommand } = await import("./query-DR4m6CXf.js");
1245
+ return queryCommand({
1246
+ ...ctx,
1247
+ err: (s) => console.error(dim(s))
1248
+ }, filters);
1249
+ }
1250
+ case "analyze": {
1251
+ loadEnvFiles(configPath);
1252
+ const { analyzeCommand } = await import("./command-CEm8PZ1N.js");
1253
+ return analyzeCommand({
1254
+ ...ctx,
1255
+ err: (s) => console.error(dim(s))
1256
+ }, filters);
1257
+ }
1258
+ }
1259
+ }
1260
+ if (command === "clean") return cleanCommand(values, configPath);
1261
+ if (command !== "run") throw new UsageError(`Unknown command "${command}". Run reeval --help.`);
1262
+ return runCommand(values, configPath, filters);
1263
+ }
1264
+ /** Asks a yes/no question on the terminal; anything but y or yes is no. */
1265
+ async function confirm(question) {
1266
+ const { createInterface } = await import("node:readline/promises");
1267
+ const rl = createInterface({
1268
+ input: process.stdin,
1269
+ output: process.stdout
1270
+ });
1271
+ try {
1272
+ return /^y(es)?$/i.test((await rl.question(question)).trim());
1273
+ } finally {
1274
+ rl.close();
1275
+ }
1276
+ }
1277
+ /** `reeval clean`: removes local debug data, all of it or all but the newest `--keep` runs. */
1278
+ async function cleanCommand(flags, configPath) {
1279
+ const config = await loadConfig(configPath, "clean");
1280
+ let keep = 0;
1281
+ if (flags.keep !== void 0) {
1282
+ keep = Number(flags.keep);
1283
+ if (!(Number.isInteger(keep) && keep >= 0)) throw new UsageError(`--keep must be a whole number (got ${flags.keep})`);
1284
+ }
1285
+ const { debugRoot, pruneDebug } = await import("./files-BvHCFWgD.js");
1286
+ const { formatBytes } = await import("./debug-SEmW4w8f.js");
1287
+ const { runs, bytes } = await pruneDebug(config.root, keep);
1288
+ const where = fromCwd(debugRoot(config.root)) || ".";
1289
+ console.log(runs.length ? `Removed debug data for ${runs.length} run${runs.length === 1 ? "" : "s"} (${formatBytes(bytes)}) from ${where}` : `No debug data to remove in ${where}`);
1290
+ if (flags.images) {
1291
+ const { pruneImages } = await import("./src-N7E_mfcZ.js");
1292
+ const { requireDocker } = await import("./providers-BUOxitcT.js");
1293
+ await requireDocker();
1294
+ const removed = await pruneImages();
1295
+ console.log(removed ? `Removed ${removed} image${removed === 1 ? "" : "s"} reeval built that nothing uses` : "No leftover images to remove");
1296
+ }
1297
+ return EXIT.ok;
1298
+ }
1299
+ /** `-v` is 1 and `-vv` (or `--trace`) is 2. */
1300
+ function verbosityOf(flags) {
1301
+ if (flags.trace) return 2;
1302
+ return Math.min(2, flags.verbose?.length ?? 0);
1303
+ }
1304
+ /**
1305
+ * `reeval image build`: collects the evals (as `--dry-run` does) and builds every image their Docker
1306
+ * sandboxes need; with `--dry-run`, lists them instead. Without a config, just reeval's image.
1307
+ */
1308
+ async function imageBuildCommand(flags, configPath, filters) {
1309
+ const { buildImages, imagePlanLines, imageTargets } = await import("./images-Cc1-coFc.js");
1310
+ const { docker } = await import("./sandbox.js");
1311
+ let root = process.cwd();
1312
+ const { fakeHarness } = await import("./harness.js");
1313
+ let uses = [{
1314
+ id: "",
1315
+ harness: fakeHarness(),
1316
+ sandbox: docker()
1317
+ }];
1318
+ if (configPath) {
1319
+ const { runId, stagingDir, config, runs, tags, concurrency, profile } = await startRun(await prepareRun({
1320
+ ...flags,
1321
+ "dry-run": true
1322
+ }, configPath));
1323
+ root = config.root;
1324
+ const { runEngine } = await import("./node-kG64XxdE.js");
1325
+ let planned = [];
1326
+ await runEngine({
1327
+ run: JSON.parse(JSON.stringify({
1328
+ runId,
1329
+ stagingDir,
1330
+ config,
1331
+ runs,
1332
+ tags,
1333
+ concurrency,
1334
+ profile
1335
+ })),
1336
+ filters,
1337
+ onPlan: async (collected) => {
1338
+ if (collected.unmatched.length) throw unmatchedFilter(config, collected.unmatched);
1339
+ planned = collected.planned.filter((p) => !p.attempt.plan.skip);
1340
+ return false;
1341
+ }
1342
+ });
1343
+ uses = planned.length ? planned.map((p) => ({
1344
+ id: p.id,
1345
+ harness: p.attempt.harness,
1346
+ sandbox: p.attempt.sandbox
1347
+ })) : config.harness.map((harness) => ({
1348
+ id: "",
1349
+ harness,
1350
+ sandbox: config.sandbox
1351
+ }));
1352
+ }
1353
+ const targets = imageTargets(uses);
1354
+ if (targets.length === 0) {
1355
+ console.log("No eval runs in a Docker sandbox, so there are no images to build.");
1356
+ return EXIT.ok;
1357
+ }
1358
+ if (flags["dry-run"]) {
1359
+ for (const line of await imagePlanLines(targets, root)) console.log(line);
1360
+ return EXIT.ok;
1361
+ }
1362
+ const { PrepareError } = await import("./provider-BDbNOQhQ.js");
1363
+ let built;
1364
+ try {
1365
+ built = await buildImages(targets, root, {
1366
+ log: (line) => console.log(dim(line)),
1367
+ force: !!flags.force
1368
+ });
1369
+ } catch (error) {
1370
+ if (error instanceof PrepareError && error.failure.usage) throw new UsageError(error.message);
1371
+ throw error;
1372
+ }
1373
+ for (const b of built) {
1374
+ if (b.ok) {
1375
+ const id = b.image.id.replace(/^sha256:/, "").slice(0, 12);
1376
+ console.log(`${green("✓")} ${green(`${b.id}: ${b.image.ref}`)}${dim(` ${id}`)}`);
1377
+ continue;
1378
+ }
1379
+ const uses = b.evals ? ` (${b.evals} eval${b.evals === 1 ? "" : "s"})` : "";
1380
+ const [headline = "", ...rest] = b.failure.message.split("\n");
1381
+ console.log(`${red("✗")} ${red(`${b.id}${uses}: ${headline}`)}`);
1382
+ for (const line of rest) console.log(` ${line}`);
1383
+ }
1384
+ const failed = built.filter((b) => !b.ok);
1385
+ const n = built.length;
1386
+ console.log(failed.length ? red(`${failed.length} of ${n} image${n === 1 ? "" : "s"} failed to build`) : dim(`${n} image${n === 1 ? "" : "s"} ready`));
1387
+ if (!failed.length) return EXIT.ok;
1388
+ return failed.every((b) => !b.ok && b.failure.usage) ? EXIT.usage : EXIT.infra;
1389
+ }
1390
+ /** `reeval run`: check, plan, run the engine, then write the run and pick the exit code. */
1391
+ async function runCommand(flags, configPath, filters) {
1392
+ const setup = await prepareRun(flags, configPath);
1393
+ const ctx = await startRun(setup);
1394
+ invocation.runId = ctx.runId;
1395
+ const { engineVersions } = await import("./node-kG64XxdE.js");
1396
+ const renderer = createRenderer({
1397
+ out: setup.json ? { write: () => true } : process.stdout,
1398
+ output: setup.output,
1399
+ verbosity: ctx.verbosity ?? 0,
1400
+ harness: setup.config.harness[0]?.kind ?? "agent",
1401
+ attemptDir: (id, cell, n) => fromCwd(join(ctx.scratch, ctx.runId, evalSlug(id), evalSlug(cell), String(n)))
1402
+ });
1403
+ let executed;
1404
+ try {
1405
+ executed = await executeRun(ctx, setup, filters, renderer);
1406
+ } catch (error) {
1407
+ renderer.close();
1408
+ await rm(ctx.stagingDir, {
1409
+ recursive: true,
1410
+ force: true
1411
+ });
1412
+ throw error;
1413
+ }
1414
+ const { engineRun, plan, sandboxVersions, budgetReached, live } = executed;
1415
+ invocation.unhandledErrors = engineRun.unhandledErrors;
1416
+ if ((interrupted || engineRun.cancelled) && plan?.sandboxes.some((s) => s.kind === "docker")) {
1417
+ const { removeRunContainers } = await import("./docker-DnWAVdEm.js");
1418
+ await removeRunContainers(ctx.runId);
1419
+ }
1420
+ if (engineRun.stopped || !plan) {
1421
+ live?.remove();
1422
+ renderer.close();
1423
+ return EXIT.ok;
1424
+ }
1425
+ const { lockfiles } = await import("./state-Cyw3QTsN.js");
1426
+ const { writeScratchRun } = await import("./results-CeXb8Wde.js");
1427
+ const run = await writeScratchRun({
1428
+ ctx,
1429
+ author: ctx.author,
1430
+ engine: engineRun,
1431
+ startedAt: ctx.startedAt,
1432
+ endedAt: /* @__PURE__ */ new Date(),
1433
+ interrupted: interrupted || engineRun.cancelled,
1434
+ budgetReached: budgetReached(),
1435
+ plan,
1436
+ es: version,
1437
+ enginePackages: engineVersions,
1438
+ sandbox: sandboxVersions,
1439
+ worktreePatch: ctx.worktreePatch,
1440
+ lockfiles: await lockfiles(ctx.config.root),
1441
+ git: ctx.git,
1442
+ runner: runnerInfo()
1443
+ }, () => live?.stop());
1444
+ const { record } = run;
1445
+ const { pruneDebug } = await import("./files-BvHCFWgD.js");
1446
+ await pruneDebug(ctx.config.root, ctx.config.debug.keep).catch(() => {});
1447
+ const summary = run.artifactDir ? join(run.artifactDir, "summary.md") : run.recordPath;
1448
+ renderer.onEvent({
1449
+ type: "run-end",
1450
+ at: Date.now(),
1451
+ record,
1452
+ summaryPath: fromCwd(summary),
1453
+ results: engineRun.evals,
1454
+ unhandledErrors: engineRun.unhandledErrors
1455
+ });
1456
+ renderer.close();
1457
+ const stepSummary = process.env.GITHUB_STEP_SUMMARY;
1458
+ if (stepSummary && run.artifactDir) await appendFile(stepSummary, await readFile(summary)).catch(() => {});
1459
+ const { config } = ctx;
1460
+ const mode = setup.record;
1461
+ if ((flags.record || mode === "always" || mode === "ci" && inCI()) && record.status !== "interrupted") {
1462
+ const { record: recordCmd } = await import("./commands-A4udfyKZ.js");
1463
+ const allow = record.status === "budget" ? { "allow-incomplete": true } : {};
1464
+ console.log(await recordCmd(config, record.runId, {
1465
+ ...flags,
1466
+ ...allow
1467
+ }));
1468
+ }
1469
+ if (interrupted || engineRun.cancelled) return stopCode;
1470
+ if (engineRun.unhandledErrors.length > 0) return EXIT.failed;
1471
+ const outcome = runOutcome(record);
1472
+ return outcome === "fail" ? EXIT.failed : outcome === "infra_error" ? EXIT.infra : EXIT.ok;
1473
+ }
1474
+ /** Everything that can fail before the evals are collected: output mode, config, profile, flags. */
1475
+ async function prepareRun(flags, configPath) {
1476
+ const output = resolveOutput({
1477
+ flags: {
1478
+ ui: flags.ui,
1479
+ logOrder: flags["log-order"],
1480
+ logPrefix: flags["log-prefix"],
1481
+ outputLogs: flags["output-logs"]
1482
+ },
1483
+ env: process.env,
1484
+ isTTY: !!process.stdout.isTTY,
1485
+ agent: detectAgent().name
1486
+ });
1487
+ if (flags.json && !flags["dry-run"]) throw new UsageError("reeval run --json only prints the plan: add --dry-run. A run writes its record to .reeval/runs; read it with reeval show --json.");
1488
+ loadEnvFiles(configPath);
1489
+ const named = flags.profile ?? (process.env.REEVAL_PROFILE || void 0);
1490
+ const config = await loadConfig(configPath, "run", named);
1491
+ const profile = selectProfile(config, flags.profile);
1492
+ let runs;
1493
+ if (flags.runs !== void 0) {
1494
+ runs = Number(flags.runs);
1495
+ if (!(Number.isInteger(runs) && runs >= 1)) throw new UsageError(`--runs must be a whole number of at least 1 (got ${flags.runs})`);
1496
+ }
1497
+ const sandboxes = [config.sandbox, ...profile?.defaults.sandbox ? [profile.defaults.sandbox] : []];
1498
+ const concurrency = await resolveConcurrency(config, flags.concurrency, async () => {
1499
+ if (!sandboxes.some((s) => s.kind === "docker")) return hostCapacity();
1500
+ const { dockerCapacity } = await import("./src-N7E_mfcZ.js");
1501
+ return await dockerCapacity() ?? hostCapacity();
1502
+ });
1503
+ return {
1504
+ output,
1505
+ config,
1506
+ profile,
1507
+ runs,
1508
+ tags: [...flags.tag ?? [], ...profile?.tags ?? []],
1509
+ concurrency,
1510
+ budget: profile?.budget ?? config.budget,
1511
+ record: profile?.record ?? config.record,
1512
+ dryRun: !!flags["dry-run"],
1513
+ json: !!flags.json,
1514
+ force: !!flags.force,
1515
+ verbosity: verbosityOf(flags),
1516
+ configFile: configPath && configLabel(configPath),
1517
+ /** Named in the plan line when the project root isn't the current directory. */
1518
+ configElsewhere: !!configPath && dirname(configPath) !== process.cwd()
1519
+ };
1520
+ }
1521
+ /** The run's id and the state it starts from: commit, uncommitted changes, author. */
1522
+ async function startRun({ config, profile, runs, tags, concurrency, verbosity, configFile }) {
1523
+ const startedAt = /* @__PURE__ */ new Date();
1524
+ const git = await gitInfo(config.root, [config.history.dir]);
1525
+ const { worktreePatch } = await import("./state-Cyw3QTsN.js");
1526
+ const patch = git?.dirty ? await worktreePatch(config.root, [config.history.dir]) : null;
1527
+ const who = await author(config.root);
1528
+ const { stagingDir, sweepStaging } = await import("./results-CeXb8Wde.js");
1529
+ const { scratchDir } = await import("./files--6CEUXm7.js");
1530
+ const scratch = scratchDir(config.root);
1531
+ const stamps = new Set([scratch, config.history.dir].flatMap((dir) => existsSync(dir) ? readdirSync(dir).map((name) => name.slice(0, 16)) : []));
1532
+ let runId = newRunId(startedAt, git?.sha ?? null, who);
1533
+ for (let n = 1; stamps.has(runId.slice(0, 16)); n++) runId = newRunId(new Date(startedAt.getTime() + n * 1e3), git?.sha ?? null, who);
1534
+ await sweepStaging(config.root);
1535
+ return {
1536
+ runId,
1537
+ stagingDir: stagingDir(config.root, runId),
1538
+ config,
1539
+ runs,
1540
+ tags,
1541
+ concurrency,
1542
+ profile,
1543
+ verbosity,
1544
+ configFile,
1545
+ startedAt,
1546
+ git,
1547
+ worktreePatch: patch,
1548
+ author: who,
1549
+ scratch
1550
+ };
1551
+ }
1552
+ function noMatch(config, filters, tags) {
1553
+ const narrowed = [filters.length ? `filter "${filters.join(" ")}"` : "", tags.length ? `tags ${tags.join(", ")}` : ""].filter(Boolean);
1554
+ return new UsageError(`No evals matched ${config.include.join(", ")}${narrowed.length ? ` with ${narrowed.join(" and ")}` : ""}`);
1555
+ }
1556
+ function unmatchedFilter(config, parts) {
1557
+ const which = parts.map((p) => `"${p}"`).join(", ");
1558
+ return new UsageError(`filter ${which} ${parts.length === 1 ? "matches" : "match"} no eval file, eval name or cell in ${config.include.join(", ")}`);
1559
+ }
1560
+ /**
1561
+ * Collects the evals and prints the plan; stops there for `--dry-run`, or on a budget, credential
1562
+ * or Docker problem (exit 2). Then runs the engine with live output and Ctrl-C handling.
1563
+ */
1564
+ async function executeRun(ctx, setup, filters, renderer) {
1565
+ const { runId, stagingDir, config, runs, tags, concurrency, profile, verbosity, configFile } = ctx;
1566
+ const { runEngine } = await import("./node-kG64XxdE.js");
1567
+ const { buildPlan, checkAttemptBudget, planDocument, planLine, planTable } = await import("./plan-gwk6dJ8c.js");
1568
+ let plan;
1569
+ let sandboxVersions = { images: [] };
1570
+ let preflightStop;
1571
+ let reached = false;
1572
+ let budgetTimer;
1573
+ let toRun = [];
1574
+ let live;
1575
+ const onPlan = async ({ planned, excluded, matched, unmatched }) => {
1576
+ if (unmatched.length) throw unmatchedFilter(config, unmatched);
1577
+ if (matched === 0) throw noMatch(config, filters, tags);
1578
+ toRun = planned;
1579
+ for (const p of planned) for (const name of p.attempt.secretVars) registerSecret(process.env[name]);
1580
+ plan = await buildPlan({
1581
+ planned,
1582
+ profile,
1583
+ budget: setup.budget,
1584
+ concurrency,
1585
+ root: config.root,
1586
+ historyDir: config.history.dir
1587
+ });
1588
+ const { imageStages, imageTargets } = await import("./images-Cc1-coFc.js");
1589
+ const targets = imageTargets(planned.filter((p) => !p.attempt.plan.skip).map((p) => ({
1590
+ id: p.id,
1591
+ harness: p.attempt.harness,
1592
+ sandbox: p.attempt.sandbox
1593
+ })));
1594
+ if (setup.json) {
1595
+ const images = await Promise.all(targets.map(async (t) => ({
1596
+ id: t.id,
1597
+ harness: t.harness,
1598
+ evals: t.evals,
1599
+ stages: await imageStages(t, config.root)
1600
+ })));
1601
+ const doc = planDocument(plan, planned, excluded, images);
1602
+ process.stdout.write(`${JSON.stringify(doc, null, 2)}\n`);
1603
+ return false;
1604
+ }
1605
+ const lines = [planLine(plan, setup.configElsewhere ? configFile : void 0)];
1606
+ if (setup.dryRun) {
1607
+ const todos = excluded.filter((e) => e.mode === "todo").map((e) => e.id);
1608
+ lines.push(...planTable(plan, todos));
1609
+ const { imageStatusLines } = await import("./images-Cc1-coFc.js");
1610
+ const { dockerAvailable } = await import("./src-N7E_mfcZ.js");
1611
+ if (targets.length && await dockerAvailable()) lines.push(...await imageStatusLines(targets, config.root));
1612
+ else if (targets.length) lines.push("images not checked: Docker is not running");
1613
+ }
1614
+ lines.push(...plan.warnings.map((w) => `warning: ${w}`));
1615
+ lines.push(...plan.hints.map((h) => `hint: ${h}`));
1616
+ renderer.onEvent({
1617
+ type: "plan",
1618
+ at: Date.now(),
1619
+ lines,
1620
+ sandboxWarnings: plan.sandboxWarnings
1621
+ });
1622
+ if (setup.dryRun) return false;
1623
+ checkAttemptBudget(plan, setup.force, config.file);
1624
+ const missing = missingCredentials(config, process.env, {
1625
+ harnesses: plan.harnessDescriptors,
1626
+ profile
1627
+ });
1628
+ if (missing.length) throw new UsageError(`Missing credentials: ${missing.join(", ")}. Set them in the environment or .env.local.`);
1629
+ const malformed = malformedCredentials(config, process.env, { harnesses: plan.harnessDescriptors });
1630
+ if (malformed.length) throw new UsageError(`Credentials with whitespace or a line break in them: ${malformed.join(", ")}. A key copied from a wrapped terminal line picks these up; set it again as one line.`);
1631
+ const { prepareWorkspaces, workspaceKey } = await import("./workspace-CyoXG6CY.js");
1632
+ const live = planned.filter((p) => !p.attempt.plan.skip);
1633
+ const outcomes = await prepareWorkspaces(live.flatMap(({ attempt: a }) => a.workspace ? [{
1634
+ workspace: a.workspace,
1635
+ environment: a.cell.environment
1636
+ }] : []), {
1637
+ root: config.root,
1638
+ runId
1639
+ });
1640
+ for (const o of outcomes) {
1641
+ const at = Date.now();
1642
+ if (o.ok) {
1643
+ const text = `workspace ${workspaceLabel(o.workspace)} at ${o.prepared.commit.slice(0, 12)}`;
1644
+ renderer.onEvent({
1645
+ type: "notice",
1646
+ at,
1647
+ level: "info",
1648
+ text
1649
+ });
1650
+ continue;
1651
+ }
1652
+ const key = workspaceKey(o.workspace);
1653
+ const user = live.find((p) => p.attempt.workspace && workspaceKey(p.attempt.workspace) === key);
1654
+ if (!user) continue;
1655
+ const target = {
1656
+ kind: "workspace",
1657
+ key: o.label,
1658
+ label: `workspace ${o.label}`,
1659
+ sandbox: user.attempt.sandbox,
1660
+ harness: user.attempt.harness,
1661
+ setBy: o.setBy,
1662
+ runWide: false
1663
+ };
1664
+ renderer.onEvent({
1665
+ type: "prepare",
1666
+ at,
1667
+ state: "failed",
1668
+ outcome: {
1669
+ target,
1670
+ ok: false,
1671
+ failure: o.failure,
1672
+ ms: 0
1673
+ }
1674
+ });
1675
+ }
1676
+ try {
1677
+ sandboxVersions = await preflight(planned.filter((p) => !p.attempt.plan.skip).map((p) => ({
1678
+ harness: p.attempt.harness,
1679
+ sandbox: p.attempt.sandbox,
1680
+ setBy: p.attempt.setBy
1681
+ })), {
1682
+ root: config.root,
1683
+ runId,
1684
+ onEvent: (e) => renderer.onEvent({
1685
+ type: "prepare",
1686
+ at: Date.now(),
1687
+ ...e
1688
+ })
1689
+ });
1690
+ } catch (error) {
1691
+ if (error.name !== "PreflightInfraError") throw error;
1692
+ preflightStop = error;
1693
+ return false;
1694
+ }
1695
+ return true;
1696
+ };
1697
+ const startBudget = () => {
1698
+ const { duration } = setup.budget;
1699
+ if (duration === void 0 || budgetTimer) return;
1700
+ budgetTimer = setTimeout(() => {
1701
+ reached = true;
1702
+ mkdirSync(stagingDir, { recursive: true });
1703
+ writeFileSync(budgetMarker(stagingDir), `The run reached budget.duration ${duration} before this attempt started`);
1704
+ renderer.onEvent({
1705
+ type: "notice",
1706
+ at: Date.now(),
1707
+ level: "warn",
1708
+ text: `Budget: ${duration} reached; running attempts finish, queued ones are skipped`
1709
+ });
1710
+ }, parseDuration(duration));
1711
+ budgetTimer.unref();
1712
+ };
1713
+ let engineRun;
1714
+ try {
1715
+ engineRun = await runEngine({
1716
+ run: JSON.parse(JSON.stringify({
1717
+ runId,
1718
+ stagingDir,
1719
+ config,
1720
+ runs,
1721
+ tags,
1722
+ concurrency,
1723
+ profile,
1724
+ verbosity,
1725
+ configFile
1726
+ })),
1727
+ filters,
1728
+ onPlan,
1729
+ onRunStart: (files) => {
1730
+ live = writeLiveProgress({
1731
+ dir: stagingDir,
1732
+ runId,
1733
+ startedAt: ctx.startedAt,
1734
+ planned: toRun
1735
+ });
1736
+ renderer.onEvent({
1737
+ type: "run-start",
1738
+ at: Date.now(),
1739
+ version,
1740
+ files,
1741
+ harness: (plan?.harnesses ?? config.harness.map(harnessId)).join(", "),
1742
+ sandbox: sandboxId(config.sandbox),
1743
+ concurrency
1744
+ });
1745
+ },
1746
+ onEvent: (e) => {
1747
+ if (e.type === "eval-start") {
1748
+ startBudget();
1749
+ live?.start(e.key, e.at);
1750
+ }
1751
+ if (e.type === "eval-end") live?.end(e.result);
1752
+ if (interrupted && e.type === "eval-end" && e.result.state === "skipped") return;
1753
+ renderer.onEvent(e);
1754
+ },
1755
+ onCancellable: (cancel) => {
1756
+ const onSignal = (signal) => {
1757
+ stopCode = signal === "SIGTERM" ? EXIT.terminated : EXIT.interrupted;
1758
+ if (interrupted) {
1759
+ renderer.close();
1760
+ process.exit(stopCode);
1761
+ }
1762
+ interrupted = true;
1763
+ live?.phase("cancelling");
1764
+ renderer.onEvent({
1765
+ type: "notice",
1766
+ at: Date.now(),
1767
+ level: "warn",
1768
+ text: "Cancelling: stopping agents and destroying sandboxes (Ctrl-C again to force)"
1769
+ });
1770
+ cancel();
1771
+ };
1772
+ process.on("SIGINT", onSignal);
1773
+ process.on("SIGTERM", onSignal);
1774
+ }
1775
+ });
1776
+ live?.phase("finishing");
1777
+ } catch (error) {
1778
+ live?.remove();
1779
+ throw error;
1780
+ } finally {
1781
+ clearTimeout(budgetTimer);
1782
+ const { removePrepared } = await import("./preflight-BtubXKQ8.js");
1783
+ removePrepared(config.root, runId);
1784
+ const { removePreparedWorkspaces } = await import("./workspace-CyoXG6CY.js");
1785
+ removePreparedWorkspaces(config.root, runId);
1786
+ }
1787
+ if (preflightStop) throw preflightStop;
1788
+ if (engineRun.noFiles || !engineRun.stopped && engineRun.evals.length === 0 && !interrupted) live?.remove();
1789
+ if (engineRun.noFiles && !filters.length && !tags.length) throw new UsageError(`No eval files match ${config.include.join(", ")} (config.include) in ${config.root}. Name eval files to match it, or set include in ${config.file}.`);
1790
+ if (engineRun.noFiles || !engineRun.stopped && engineRun.evals.length === 0 && !interrupted) throw noMatch(config, filters, tags);
1791
+ return {
1792
+ engineRun,
1793
+ plan,
1794
+ sandboxVersions,
1795
+ budgetReached: () => reached,
1796
+ live
1797
+ };
1798
+ }
1799
+ process.stdout.on("error", (error) => {
1800
+ if (error.code !== "EPIPE") throw error;
1801
+ });
1802
+ /**
1803
+ * Logs the command for `reeval report` and, when it looks like reeval broke (a crash, an unexpected
1804
+ * error or an infra error), prints one line suggesting `reeval report`. Not for agents or JSON.
1805
+ */
1806
+ function finish(code, error, kind) {
1807
+ const flags = invocation.flags;
1808
+ let root = process.cwd();
1809
+ try {
1810
+ const configPath = flags ? configPathFor(flags.config) : findConfig();
1811
+ if (configPath) root = dirname(configPath);
1812
+ } catch {}
1813
+ const command = commandOf(argv);
1814
+ logInvocation(root, {
1815
+ at: new Date(startedAt).toISOString(),
1816
+ es: version,
1817
+ argv: redactArgv(argv),
1818
+ command,
1819
+ exitCode: code,
1820
+ durationMs: Date.now() - startedAt,
1821
+ runId: invocation.runId,
1822
+ error: error && kind ? {
1823
+ kind,
1824
+ name: error instanceof Error ? error.name : "Error",
1825
+ message: redactSecrets(error instanceof Error ? error.message : String(error)),
1826
+ stack: error instanceof Error && error.stack ? redactSecrets(error.stack) : null
1827
+ } : null,
1828
+ unhandledErrors: invocation.unhandledErrors.map((e) => redactSecrets(e))
1829
+ });
1830
+ const broke = kind === "crash" || code === EXIT.infra || invocation.unhandledErrors.length > 0;
1831
+ const quiet = !!process.env.REEVAL_NO_REPORT_HINT || command === "report" || !!flags?.json || flags?.ui === "agent" || process.env.REEVAL_UI === "agent" || !flags?.ui && !process.env.REEVAL_UI && !!detectAgent().name;
1832
+ if (broke && !quiet) console.error(dim("hint: if this looks like a bug in reeval, reeval report collects a redacted report to file (REEVAL_NO_REPORT_HINT=1 hides this)"));
1833
+ }
1834
+ const crash = (error) => {
1835
+ console.error(red(`reeval: ${redactSecrets(error instanceof Error ? error.message : String(error))}`));
1836
+ if (process.env.REEVAL_DEBUG) console.error(redactSecrets(inspect(error)));
1837
+ finish(EXIT.infra, error, "crash");
1838
+ process.exit(EXIT.infra);
1839
+ };
1840
+ process.on("unhandledRejection", crash);
1841
+ process.on("uncaughtException", crash);
1842
+ let settled = false;
1843
+ process.on("exit", (code) => {
1844
+ if (settled || code !== 0) return;
1845
+ process.stderr.write(red("reeval: stopped before it finished: nothing was left for it to wait on. That's a bug in reeval, or in code the config or an eval ran; reeval report collects a redacted report.\n"));
1846
+ process.exitCode = EXIT.infra;
1847
+ });
1848
+ run(process.argv.slice(2)).finally(() => {
1849
+ settled = true;
1850
+ }).then((code) => {
1851
+ finish(code);
1852
+ process.exitCode = code;
1853
+ }, (error) => {
1854
+ const name = error.name;
1855
+ const expected = error instanceof UsageError || EXPECTED_ERRORS.includes(name) || name === "PreflightInfraError";
1856
+ console.error(red(`reeval: ${redactSecrets(error.message)}`));
1857
+ if (!expected && process.env.REEVAL_DEBUG) console.error(redactSecrets(inspect(error)));
1858
+ const code = name === "PreflightInfraError" ? EXIT.infra : EXIT.usage;
1859
+ finish(code, error, error instanceof UsageError ? "usage" : expected ? "expected" : "crash");
1860
+ process.exitCode = code;
1861
+ });
1862
+ //#endregion
1863
+ export {};
1864
+
1865
+ //# sourceMappingURL=cli--CQ3Iy9q.js.map