niceeval 0.5.3 → 0.6.0

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 (344) hide show
  1. package/INDEX.md +55 -0
  2. package/dist/agents/types.d.ts +317 -0
  3. package/dist/context/types.d.ts +335 -0
  4. package/dist/i18n/core.d.ts +11 -0
  5. package/dist/i18n/en.d.ts +181 -0
  6. package/dist/i18n/index.d.ts +5 -0
  7. package/dist/i18n/zh-CN.d.ts +183 -0
  8. package/dist/o11y/execution-tree.d.ts +103 -0
  9. package/dist/o11y/otlp/parse.d.ts +3 -0
  10. package/dist/o11y/otlp/receiver.d.ts +21 -0
  11. package/dist/o11y/otlp/select.d.ts +22 -0
  12. package/dist/o11y/otlp/turn-otel.d.ts +43 -0
  13. package/dist/o11y/types.d.ts +190 -0
  14. package/dist/report/aggregate.d.ts +57 -0
  15. package/dist/report/aggregate.js +212 -0
  16. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
  17. package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
  18. package/dist/report/built-ins/index.d.ts +1 -0
  19. package/dist/report/built-ins/index.js +2 -0
  20. package/dist/report/components.d.ts +178 -0
  21. package/dist/report/components.js +132 -0
  22. package/dist/report/compute.d.ts +106 -0
  23. package/dist/report/compute.js +618 -0
  24. package/dist/report/flag.d.ts +6 -0
  25. package/dist/report/flag.js +8 -0
  26. package/dist/report/format.d.ts +32 -0
  27. package/dist/report/format.js +126 -0
  28. package/dist/report/index.d.ts +19 -0
  29. package/dist/report/index.js +30 -0
  30. package/dist/report/load.d.ts +12 -0
  31. package/dist/report/load.js +56 -0
  32. package/dist/report/locale.d.ts +83 -0
  33. package/dist/report/locale.js +160 -0
  34. package/dist/report/metrics.d.ts +21 -0
  35. package/dist/report/metrics.js +115 -0
  36. package/dist/report/primitives.d.ts +64 -0
  37. package/dist/report/primitives.js +113 -0
  38. package/dist/report/react/AttemptList.d.ts +23 -0
  39. package/dist/report/react/AttemptList.js +20 -0
  40. package/dist/report/react/DeltaTable.d.ts +8 -0
  41. package/dist/report/react/DeltaTable.js +24 -0
  42. package/dist/report/react/EvalList.d.ts +10 -0
  43. package/dist/report/react/EvalList.js +0 -0
  44. package/dist/report/react/ExperimentList.d.ts +10 -0
  45. package/dist/report/react/ExperimentList.js +15 -0
  46. package/dist/report/react/GroupSummary.d.ts +8 -0
  47. package/dist/report/react/GroupSummary.js +8 -0
  48. package/dist/report/react/MetricBars.d.ts +10 -0
  49. package/dist/report/react/MetricBars.js +48 -0
  50. package/dist/report/react/MetricLine.d.ts +9 -0
  51. package/dist/report/react/MetricLine.js +68 -0
  52. package/dist/report/react/MetricMatrix.d.ts +10 -0
  53. package/dist/report/react/MetricMatrix.js +23 -0
  54. package/dist/report/react/MetricScatter.d.ts +9 -0
  55. package/dist/report/react/MetricScatter.js +100 -0
  56. package/dist/report/react/MetricTable.d.ts +11 -0
  57. package/dist/report/react/MetricTable.js +26 -0
  58. package/dist/report/react/RunOverview.d.ts +8 -0
  59. package/dist/report/react/RunOverview.js +12 -0
  60. package/dist/report/react/Scoreboard.d.ts +8 -0
  61. package/dist/report/react/Scoreboard.js +26 -0
  62. package/dist/report/react/cell.d.ts +9 -0
  63. package/dist/report/react/cell.js +9 -0
  64. package/dist/report/react/chart-math.d.ts +14 -0
  65. package/dist/report/react/chart-math.js +68 -0
  66. package/dist/report/react/colors.d.ts +14 -0
  67. package/dist/report/react/colors.js +45 -0
  68. package/dist/report/react/fixtures.d.ts +19 -0
  69. package/dist/report/react/fixtures.js +393 -0
  70. package/dist/report/react/format.d.ts +3 -0
  71. package/dist/report/react/format.js +7 -0
  72. package/dist/report/react/index.d.ts +6 -0
  73. package/dist/report/react/index.js +14 -0
  74. package/dist/report/report.d.ts +24 -0
  75. package/dist/report/report.js +49 -0
  76. package/dist/report/text/faces.d.ts +29 -0
  77. package/dist/report/text/faces.js +503 -0
  78. package/dist/report/text/layout.d.ts +75 -0
  79. package/dist/report/text/layout.js +173 -0
  80. package/dist/report/text/plot.d.ts +37 -0
  81. package/dist/report/text/plot.js +81 -0
  82. package/dist/report/text/table.d.ts +8 -0
  83. package/dist/report/text/table.js +86 -0
  84. package/dist/report/tree.d.ts +99 -0
  85. package/dist/report/tree.js +228 -0
  86. package/dist/report/types.d.ts +420 -0
  87. package/dist/report/types.js +4 -0
  88. package/dist/report/web.d.ts +15 -0
  89. package/dist/report/web.js +37 -0
  90. package/dist/results/annotated-source.d.ts +61 -0
  91. package/dist/results/attempt-evidence.d.ts +69 -0
  92. package/dist/results/attempt-source.d.ts +15 -0
  93. package/dist/results/locator.d.ts +75 -0
  94. package/dist/results/locator.js +0 -0
  95. package/dist/results/select.d.ts +45 -0
  96. package/dist/results/select.js +230 -0
  97. package/dist/results/source-hash.d.ts +9 -0
  98. package/dist/results/types.d.ts +204 -0
  99. package/dist/runner/eval-source.d.ts +22 -0
  100. package/dist/runner/fingerprint.d.ts +23 -0
  101. package/dist/runner/types.d.ts +332 -0
  102. package/dist/sandbox/docker-stream.d.ts +24 -0
  103. package/dist/sandbox/docker.d.ts +84 -0
  104. package/dist/sandbox/e2b.d.ts +31 -0
  105. package/dist/sandbox/errors.d.ts +4 -0
  106. package/dist/sandbox/local-files.d.ts +6 -0
  107. package/dist/sandbox/paths.d.ts +4 -0
  108. package/dist/sandbox/registry.d.ts +13 -0
  109. package/dist/sandbox/resolve.d.ts +40 -0
  110. package/dist/sandbox/retry.d.ts +10 -0
  111. package/dist/sandbox/shell.d.ts +11 -0
  112. package/dist/sandbox/source-files.d.ts +13 -0
  113. package/dist/sandbox/types.d.ts +203 -0
  114. package/dist/sandbox/vercel.d.ts +37 -0
  115. package/dist/scoring/types.d.ts +70 -0
  116. package/dist/shared/aggregate.d.ts +29 -0
  117. package/dist/shared/aggregate.js +55 -0
  118. package/dist/shared/types.d.ts +27 -0
  119. package/dist/shared/verdict.d.ts +27 -0
  120. package/dist/shared/verdict.js +34 -0
  121. package/dist/tty-line.d.ts +6 -0
  122. package/dist/types.d.ts +7 -0
  123. package/dist/util.d.ts +22 -0
  124. package/docs-site/zh/concepts/adapter.mdx +9 -4
  125. package/docs-site/zh/concepts/assert.mdx +8 -8
  126. package/docs-site/zh/concepts/evals.mdx +5 -5
  127. package/docs-site/zh/concepts/experiment.mdx +3 -1
  128. package/docs-site/zh/concepts/judge.mdx +9 -9
  129. package/docs-site/zh/concepts/overview.mdx +2 -2
  130. package/docs-site/zh/concepts/tier.mdx +6 -6
  131. package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
  132. package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
  133. package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
  134. package/docs-site/zh/guides/connect-otel.mdx +3 -3
  135. package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
  136. package/docs-site/zh/guides/custom-reports.mdx +196 -36
  137. package/docs-site/zh/guides/experiments.mdx +7 -0
  138. package/docs-site/zh/guides/fixtures.mdx +1 -1
  139. package/docs-site/zh/guides/official-adapters.mdx +31 -9
  140. package/docs-site/zh/guides/publish-report.mdx +4 -4
  141. package/docs-site/zh/guides/report-components.mdx +194 -55
  142. package/docs-site/zh/guides/reporters.mdx +2 -2
  143. package/docs-site/zh/guides/results-data.mdx +35 -34
  144. package/docs-site/zh/guides/runner.mdx +2 -2
  145. package/docs-site/zh/guides/sandbox-agent.mdx +10 -5
  146. package/docs-site/zh/guides/sandbox-providers.mdx +102 -0
  147. package/docs-site/zh/guides/viewing-results.mdx +199 -79
  148. package/docs-site/zh/guides/write-experiment.mdx +23 -1
  149. package/docs-site/zh/guides/write-send.mdx +2 -2
  150. package/docs-site/zh/index.mdx +4 -3
  151. package/docs-site/zh/introduction.mdx +2 -1
  152. package/docs-site/zh/quickstart.mdx +5 -3
  153. package/docs-site/zh/reference/builtin-agents.mdx +38 -13
  154. package/docs-site/zh/reference/capabilities.mdx +1 -1
  155. package/docs-site/zh/reference/cli.mdx +36 -13
  156. package/docs-site/zh/reference/define-agent.mdx +24 -12
  157. package/docs-site/zh/reference/define-config.mdx +1 -1
  158. package/docs-site/zh/reference/define-eval.mdx +3 -3
  159. package/docs-site/zh/reference/events.mdx +7 -1
  160. package/docs-site/zh/reference/expect.mdx +3 -3
  161. package/package.json +10 -6
  162. package/src/agents/bub.ts +134 -43
  163. package/src/agents/claude-code.test.ts +152 -0
  164. package/src/agents/claude-code.ts +127 -13
  165. package/src/agents/codex.test.ts +192 -0
  166. package/src/agents/codex.ts +151 -12
  167. package/src/agents/index.ts +11 -3
  168. package/src/agents/manifest.ts +41 -0
  169. package/src/agents/plugin-config.test.ts +94 -0
  170. package/src/agents/shared.ts +33 -2
  171. package/src/agents/skills.test.ts +214 -0
  172. package/src/agents/skills.ts +304 -0
  173. package/src/agents/streaming.ts +5 -5
  174. package/src/agents/types.ts +62 -2
  175. package/src/cli.ts +74 -47
  176. package/src/context/context.test.ts +20 -2
  177. package/src/context/context.ts +3 -0
  178. package/src/context/control-flow.ts +1 -1
  179. package/src/context/session.test.ts +92 -1
  180. package/src/context/session.ts +26 -1
  181. package/src/context/types.ts +4 -4
  182. package/src/define.test.ts +82 -0
  183. package/src/define.ts +72 -11
  184. package/src/expect/index.ts +5 -5
  185. package/src/i18n/en.ts +36 -15
  186. package/src/i18n/zh-CN.ts +39 -21
  187. package/src/index.ts +1 -1
  188. package/src/o11y/execution-tree.test.ts +452 -0
  189. package/src/o11y/execution-tree.ts +367 -0
  190. package/src/o11y/otlp/receiver.ts +1 -1
  191. package/src/o11y/otlp/select.ts +7 -3
  192. package/src/o11y/parsers/claude-code.test.ts +175 -0
  193. package/src/o11y/parsers/claude-code.ts +32 -0
  194. package/src/o11y/types.ts +6 -1
  195. package/src/report/aggregate.ts +26 -20
  196. package/src/report/built-in-user-parity.test.tsx +643 -0
  197. package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
  198. package/src/report/built-ins/index.ts +2 -0
  199. package/src/report/components.tsx +223 -50
  200. package/src/report/compute.ts +358 -53
  201. package/src/report/dual-render.test.tsx +1125 -0
  202. package/src/report/flag.ts +1 -1
  203. package/src/report/format.ts +50 -2
  204. package/src/report/index.ts +48 -14
  205. package/src/report/load.ts +1 -1
  206. package/src/report/locale.ts +206 -0
  207. package/src/report/metrics.ts +39 -15
  208. package/src/report/primitives.tsx +100 -2
  209. package/src/report/react/AttemptList.tsx +115 -0
  210. package/src/report/react/DeltaTable.tsx +9 -6
  211. package/src/report/react/EvalList.tsx +0 -0
  212. package/src/report/react/ExperimentList.tsx +108 -0
  213. package/src/report/react/GroupSummary.tsx +66 -0
  214. package/src/report/react/MetricBars.tsx +24 -13
  215. package/src/report/react/MetricLine.tsx +21 -21
  216. package/src/report/react/MetricMatrix.tsx +8 -4
  217. package/src/report/react/MetricScatter.tsx +136 -96
  218. package/src/report/react/MetricTable.tsx +115 -15
  219. package/src/report/react/RunOverview.tsx +48 -26
  220. package/src/report/react/Scoreboard.tsx +15 -7
  221. package/src/report/react/cell.tsx +18 -9
  222. package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
  223. package/src/report/react/colors.ts +24 -15
  224. package/src/report/react/enhance.js +129 -0
  225. package/src/report/react/fixtures.ts +163 -34
  226. package/src/report/react/format.ts +1 -1
  227. package/src/report/react/index.tsx +22 -6
  228. package/src/report/react/render.test.tsx +164 -31
  229. package/src/report/react/styles.css +405 -213
  230. package/src/report/report.test.ts +403 -94
  231. package/src/report/report.ts +29 -11
  232. package/src/report/text/faces.ts +303 -95
  233. package/src/report/text/layout.ts +76 -11
  234. package/src/report/text/table.ts +98 -0
  235. package/src/report/tree.ts +103 -20
  236. package/src/report/types.ts +196 -25
  237. package/src/report/web.ts +46 -19
  238. package/src/results/annotated-source.test.ts +146 -0
  239. package/src/results/annotated-source.ts +128 -0
  240. package/src/results/attempt-evidence.test.ts +158 -0
  241. package/src/results/attempt-evidence.ts +158 -0
  242. package/src/results/attempt-source.ts +48 -0
  243. package/src/results/copy.ts +154 -139
  244. package/src/results/format.ts +54 -47
  245. package/src/results/host-equivalence.test.ts +527 -0
  246. package/src/results/index.ts +56 -14
  247. package/src/results/locator.test.ts +198 -0
  248. package/src/results/locator.ts +0 -0
  249. package/src/results/open.ts +392 -175
  250. package/src/results/results.test.ts +943 -494
  251. package/src/results/select.ts +129 -23
  252. package/src/results/skipped-notice.ts +0 -0
  253. package/src/results/source-hash.ts +28 -0
  254. package/src/results/types.ts +98 -62
  255. package/src/results/writer.ts +304 -176
  256. package/src/runner/attempt.test.ts +170 -0
  257. package/src/runner/attempt.ts +97 -15
  258. package/src/runner/discover.test.ts +65 -0
  259. package/src/runner/discover.ts +6 -2
  260. package/src/runner/eval-source.test.ts +121 -0
  261. package/src/runner/eval-source.ts +45 -0
  262. package/src/runner/fingerprint.ts +54 -1
  263. package/src/runner/remote-sandbox.ts +1 -1
  264. package/src/runner/report.test.ts +6 -8
  265. package/src/runner/report.ts +5 -4
  266. package/src/runner/reporters/artifacts.ts +27 -41
  267. package/src/runner/reporters/braintrust.test.ts +3 -3
  268. package/src/runner/reporters/braintrust.ts +1 -1
  269. package/src/runner/reporters/console.ts +6 -6
  270. package/src/runner/reporters/json.ts +4 -4
  271. package/src/runner/reporters/live.test.ts +56 -0
  272. package/src/runner/reporters/live.ts +36 -10
  273. package/src/runner/reporters/quiet.test.ts +66 -0
  274. package/src/runner/reporters/quiet.ts +49 -0
  275. package/src/runner/reporters/shared.ts +5 -5
  276. package/src/runner/reporters/table.ts +17 -17
  277. package/src/runner/run.ts +61 -102
  278. package/src/runner/types.ts +43 -32
  279. package/src/sandbox/checkpoint.ts +1 -1
  280. package/src/sandbox/docker.ts +15 -2
  281. package/src/sandbox/e2b.ts +11 -5
  282. package/src/sandbox/errors.ts +13 -0
  283. package/src/sandbox/index.ts +3 -3
  284. package/src/sandbox/local-files.ts +1 -1
  285. package/src/sandbox/registry.ts +6 -5
  286. package/src/sandbox/resolve.ts +38 -23
  287. package/src/sandbox/retry.test.ts +68 -0
  288. package/src/sandbox/retry.ts +44 -0
  289. package/src/sandbox/types.ts +69 -28
  290. package/src/sandbox/vercel.ts +17 -3
  291. package/src/scoring/collector.ts +3 -3
  292. package/src/scoring/judge.ts +2 -2
  293. package/src/scoring/scoped.ts +13 -1
  294. package/src/scoring/types.ts +7 -7
  295. package/src/scoring/verdict.ts +4 -4
  296. package/src/shared/aggregate.ts +23 -3
  297. package/src/shared/types.ts +2 -2
  298. package/src/shared/{outcome.ts → verdict.ts} +15 -15
  299. package/src/show/compose.ts +12 -132
  300. package/src/show/index.ts +125 -94
  301. package/src/show/render.ts +660 -160
  302. package/src/show/show.test.ts +472 -253
  303. package/src/source-loc.ts +1 -1
  304. package/src/tty-line.ts +32 -0
  305. package/src/types.ts +2 -2
  306. package/src/util.test.ts +21 -1
  307. package/src/util.ts +5 -1
  308. package/src/view/app/App.tsx +33 -198
  309. package/src/view/app/components/AttemptModal.tsx +6 -6
  310. package/src/view/app/components/CodeView.tsx +2 -2
  311. package/src/view/app/components/CopyControls.tsx +32 -36
  312. package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
  313. package/src/view/app/components/ui/badge.tsx +1 -1
  314. package/src/view/app/components/ui/dialog.tsx +2 -2
  315. package/src/view/app/i18n.ts +40 -31
  316. package/src/view/app/lib/artifact-url.ts +1 -1
  317. package/src/view/app/lib/attempt-route.test.ts +37 -47
  318. package/src/view/app/lib/attempt-route.ts +26 -26
  319. package/src/view/app/lib/rows.ts +9 -112
  320. package/src/view/app/lib/verdict.ts +25 -0
  321. package/src/view/app/main.tsx +9 -12
  322. package/src/view/app/pages/RunsPage.tsx +4 -4
  323. package/src/view/app/pages/TracesPage.tsx +3 -3
  324. package/src/view/app/shared.ts +0 -1
  325. package/src/view/app/types.ts +6 -51
  326. package/src/view/artifact-serving.test.ts +136 -0
  327. package/src/view/client-dist/app.css +1 -1
  328. package/src/view/client-dist/app.js +21 -21
  329. package/src/view/data.test.ts +214 -143
  330. package/src/view/data.ts +166 -120
  331. package/src/view/index.ts +23 -11
  332. package/src/view/server.ts +52 -17
  333. package/src/view/shared/types.ts +22 -31
  334. package/src/view/styles.css +117 -327
  335. package/src/view/view-report.test.ts +146 -98
  336. package/docs-site/zh/guides/sandbox-backends.mdx +0 -75
  337. package/src/report/default-report.tsx +0 -222
  338. package/src/report/dual-face.test.tsx +0 -527
  339. package/src/report/react/CaseList.tsx +0 -70
  340. package/src/view/app/components/CostScoreChart.tsx +0 -147
  341. package/src/view/app/components/ExperimentTable.tsx +0 -284
  342. package/src/view/app/components/GroupSelector.tsx +0 -61
  343. package/src/view/app/components/primitives.tsx +0 -43
  344. package/src/view/app/lib/outcome.ts +0 -82
@@ -1,5 +1,7 @@
1
- // sandbox 域类型:Sandbox 接口、后端 spec(可辨识联合)、命令与文件 IO 的形状。
2
- // 「在哪里跑、如何隔离」的全部契约在这里;后端实现见本目录各文件,分发见 resolve.ts。
1
+ // sandbox 域类型:Sandbox 接口、provider spec(可辨识联合)、命令与文件 IO 的形状。
2
+ // 「在哪里跑、如何隔离」的全部契约在这里;provider 实现见本目录各文件,分发见 resolve.ts。
3
+
4
+ import type { AgentSetup, AgentTeardown } from "../agents/types.ts";
3
5
 
4
6
  export interface CommandResult {
5
7
  stdout: string;
@@ -45,43 +47,82 @@ export interface ReadSourceFilesOptions {
45
47
  ignoreFiles?: string[];
46
48
  }
47
49
 
48
- /** 内置后端名;只在 sandbox/resolve.ts 内部做环境探测和 CLI `--sandbox` 解析用,不出现在 `sandbox` 字段的类型里。 */
49
- export type SandboxBackend = "docker" | "vercel" | "e2b";
50
+ /** 内置 provider 名;不出现在 `sandbox` 字段的类型里(spec 用各自的 `provider` 判别字段区分)。 */
51
+ export type SandboxProvider = "docker" | "vercel" | "e2b";
50
52
 
51
53
  /** 镜像/模板里的 Node 运行时版本。 */
52
54
  export type SandboxRuntime = "node20" | "node24";
53
55
 
54
56
  /**
55
- * Sandbox 的「数据结构」定义 —— 与 agent 一样可带参数( docs/sandbox.md)。
57
+ * SandboxSpec 四个变体共有的沙箱生命周期钩子挂载点:`.setup(fn)` / `.teardown(fn)` 链式方法,
58
+ * 由 `dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` / `defineSandbox()` 产出的对象上
59
+ * 直接可调。
60
+ *
61
+ * **语义**:环境预置层,俗称「动态镜像层」——本想直接烘进镜像/快照/模板,但内容要按实验
62
+ * (`ctx.flags` / `ctx.experimentId`)动态变化的东西:装某个实验专属的二进制、预热缓存、
63
+ * 写运行期才知道内容的 hook 文件、按 `ctx.experimentId` 载入/回存跨 attempt 的状态。
64
+ *
65
+ * **与另外两层 setup 的分工**(三者都在同一个沙箱生命周期里,各管一层):
66
+ * · `sandbox.setup`(这里)—— 环境层,不知道具体跑哪个 eval / 接哪个 agent;
67
+ * · `eval.setup` —— 任务层,准备这次 eval 的素材(如 `npm install` 起始项目依赖);
68
+ * · `agent.setup` —— 协议层,接入被测 agent(装 CLI、写鉴权 / 模型配置)。
69
+ *
70
+ * **执行顺序**:沙箱就绪 → `sandbox.setup` 钩子 → workspace 上传 / git 基线 / `eval.setup` →
71
+ * `agent.setup` → `agent.tracing.configure` → 逐轮 `send`。`sandbox.setup` 特意排在 git
72
+ * 基线之前——它的改动会被提交进基线,不会被误算进 agent 产出的 diff。收尾按 LIFO:
73
+ * agent 级 cleanup / `agent.teardown` 先跑,`sandbox.teardown` 钩子最后跑(沙箱销毁前)。
74
+ *
75
+ * **多钩子**:`.setup(a).setup(b)` 按追加顺序依次执行(a 先 b 后);`.teardown(x).teardown(y)`
76
+ * 按追加的**逆序**执行(y 先 x 后)。每次调用都返回一个新 spec(不改变原对象),可继续链式。
77
+ *
78
+ * **失败语义**:`setup` 钩子抛错按执行错误计——与 `eval.setup` / `agent.setup` 抛错走同一条
79
+ * 路径,不新增错误分类;但不阻断该 attempt 已进入的收尾(已跑过的 setup 返回的 cleanup、
80
+ * `teardown` 钩子仍会在 finally 里跑)。`teardown` 钩子报错只作诊断(吞掉 / 记 log),不改变
81
+ * 已产出的结果——与 `agent.teardown` 现状一致。
82
+ */
83
+ export interface SandboxHooks<Self> {
84
+ /** 已挂载的 setup 钩子,按追加顺序保存(内部读取,一般用不到)。 */
85
+ readonly setupHooks?: readonly AgentSetup[];
86
+ /** 已挂载的 teardown 钩子,按追加顺序保存,执行时逆序(内部读取,一般用不到)。 */
87
+ readonly teardownHooks?: readonly AgentTeardown[];
88
+ /** 追加一个沙箱级 setup 钩子,返回新 spec;详细契约见 {@link SandboxHooks}。 */
89
+ setup(fn: AgentSetup): Self;
90
+ /** 追加一个沙箱级 teardown 钩子,返回新 spec;详细契约见 {@link SandboxHooks}。 */
91
+ teardown(fn: AgentTeardown): Self;
92
+ }
93
+
94
+ /**
95
+ * Sandbox 的「数据结构」定义 —— 与 agent 一样可带参数(见 docs/feature/sandbox/library.md)。
56
96
  * 必须用工厂函数构造(`dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` / `defineSandbox()`),
57
97
  * 放进 config / experiment 的 `sandbox` 字段 —— 字段类型只接受这个数据结构,不接受裸字符串。
58
- * 各后端的参数互不相同 —— 这是个按 `backend` 区分的可辨识联合(discriminated union)。
98
+ * provider 的参数互不相同 —— 这是个按 `provider` 区分的可辨识联合(discriminated union)。
99
+ * 四个变体都带 {@link SandboxHooks} 的 `.setup()` / `.teardown()` 链式方法。
59
100
  */
60
- export interface DockerSandboxSpec {
61
- readonly backend: "docker";
101
+ export interface DockerSandboxSpec extends SandboxHooks<DockerSandboxSpec> {
102
+ readonly provider: "docker";
62
103
  /** 覆盖默认镜像;默认按 runtime 选 `node:*-slim`。预制模板:传烘焙好 agent CLI 的镜像名。 */
63
104
  readonly image?: string;
64
105
  readonly runtime?: SandboxRuntime;
65
106
  }
66
- export interface VercelSandboxSpec {
67
- readonly backend: "vercel";
107
+ export interface VercelSandboxSpec extends SandboxHooks<VercelSandboxSpec> {
108
+ readonly provider: "vercel";
68
109
  /** 从已有快照起 microVM。预制模板:烘焙好 agent CLI 的 snapshotId。 */
69
110
  readonly snapshotId?: string;
70
111
  readonly runtime?: SandboxRuntime;
71
112
  }
72
- export interface E2BSandboxSpec {
73
- readonly backend: "e2b";
113
+ export interface E2BSandboxSpec extends SandboxHooks<E2BSandboxSpec> {
114
+ readonly provider: "e2b";
74
115
  /** e2b 模板名/ID。预制模板:烘焙好 agent CLI 的模板(如 `"niceeval-agents"`)。省略用 e2b 默认 `"base"`。 */
75
116
  readonly template?: string;
76
117
  /** 仅作记录;e2b 的 node 版本由模板决定,不在创建时选。 */
77
118
  readonly runtime?: SandboxRuntime;
78
119
  }
79
120
  /**
80
- * 用户自定义后端:`create` 直接产出一个 `Sandbox` 实例,不经 resolve.ts 的内置 backend switch。
81
- * 用 `defineSandbox()` 构造(见 src/define.ts)。`backend` 只用于展示 / 日志,不参与分发。
121
+ * 用户自定义 provider:`create` 直接产出一个 `Sandbox` 实例,不经 resolve.ts 的内置 provider switch。
122
+ * 用 `defineSandbox()` 构造(见 src/define.ts)。`provider` 只用于展示 / 日志,不参与分发。
82
123
  */
83
- export interface CustomSandboxSpec {
84
- readonly backend: string;
124
+ export interface CustomSandboxSpec extends SandboxHooks<CustomSandboxSpec> {
125
+ readonly provider: string;
85
126
  readonly runtime?: SandboxRuntime;
86
127
  readonly recommendedConcurrency?: number;
87
128
  readonly create: (opts: { timeout?: number; runtime?: SandboxRuntime }) => Promise<Sandbox>;
@@ -93,25 +134,25 @@ export type SandboxSpec = DockerSandboxSpec | VercelSandboxSpec | E2BSandboxSpec
93
134
  export type SandboxOption = SandboxSpec;
94
135
 
95
136
  export interface CommandOptions {
96
- /** 追加/覆盖本命令的环境变量(与沙箱默认环境叠加,不清空默认值;各后端会保留自己固定的 `PATH` 等变量,不保证能被这里覆盖)。 */
137
+ /** 追加/覆盖本命令的环境变量(与沙箱默认环境叠加,不清空默认值;各 provider 会保留自己固定的 `PATH` 等变量,不保证能被这里覆盖)。 */
97
138
  env?: Record<string, string>;
98
139
  /** 本命令的工作目录;省略时落到 `Sandbox.workdir`。相对路径按 workdir 解析,绝对路径原样使用。 */
99
140
  cwd?: string;
100
141
  /**
101
142
  * 把本命令的输出也送进沙箱的「原生日志流」(于是 `docker logs` / Docker UI 的 Logs
102
143
  * 标签页能实时看到它)。给 agent 命令(codex exec / bub run / claude)开它,就能在容器
103
- * 日志里看到 agent 的【原始输出】。后端各自实现(docker:tee 到 PID1 tail 的文件;
104
- * 不支持的后端忽略)—— 日志怎么浮现是 backend 的事,adapter 只声明意图。
144
+ * 日志里看到 agent 的【原始输出】。provider 各自实现(docker:tee 到 PID1 tail 的文件;
145
+ * 不支持的 provider 忽略)—— 日志怎么浮现是 provider 的事,adapter 只声明意图。
105
146
  */
106
147
  stream?: boolean;
107
148
  /**
108
149
  * 以 root 跑本命令。默认 `false` —— 命令以沙箱的标准**非 root** 用户跑(agent 的自然环境)。
109
150
  * 给 setup 阶段装系统依赖用(`apt-get install …`、`pip install --break-system-packages …`)。
110
151
  *
111
- * 语义跨后端一致:"本命令以 root 跑,否则以标准非 root 用户跑"。各后端映射到自己的原生机制
152
+ * 语义跨 provider 一致:"本命令以 root 跑,否则以标准非 root 用户跑"。各 provider 映射到自己的原生机制
112
153
  * (docker:`exec --user root`;E2B:`{ user: "root" }`;Vercel:`{ sudo: true }`;Daytona:`{ user }`)。
113
- * 本就全程 root 的后端(如 Modal)视作 no-op;完全无法提权的后端可不支持(抛错)—— 但**默认值与
114
- * 语义保持一致**,不因后端而变。
154
+ * 本就全程 root 的 provider(如 Modal)视作 no-op;完全无法提权的 provider 可不支持(抛错)—— 但**默认值与
155
+ * 语义保持一致**,不因 provider 而变。
115
156
  */
116
157
  root?: boolean;
117
158
  }
@@ -131,7 +172,7 @@ export interface Sandbox {
131
172
  runShell(script: string, opts?: CommandOptions): Promise<CommandResult>;
132
173
  /** 读取沙箱内文件的文本内容(UTF-8)。文件不存在时抛错,不返回空字符串——需要容错请自行 `.catch()`。 */
133
174
  readFile(path: string): Promise<string>;
134
- /** 检查沙箱内路径是否存在。跨后端语义不完全一致:仅保证对普通文件可靠,对目录路径的行为不同后端不保证一致。 */
175
+ /** 检查沙箱内路径是否存在。跨 provider 语义不完全一致:仅保证对普通文件可靠,对目录路径的行为不同 provider 不保证一致。 */
135
176
  fileExists(path: string): Promise<boolean>;
136
177
  /**
137
178
  * 一次 shell 往返读全部源码文件(按扩展名收、按目录/文件名忽略)。
@@ -144,9 +185,9 @@ export interface Sandbox {
144
185
  uploadFiles(files: SandboxFile[], targetDir?: string): Promise<void>;
145
186
  /** 把本地磁盘上的一个目录整体上传进沙箱(递归读取本地文件后按 `uploadFiles` 写入);`opts.ignore` 是排除规则,省略 `targetDir` 落到 workdir。 */
146
187
  uploadDirectory(localDir: string, targetDir?: string, opts?: { ignore?: string[] }): Promise<void>;
147
- /** 销毁沙箱占用的计算资源(容器/microVM)。调用后沙箱不可再用;是否可安全重复调用因后端而异,不要依赖这一点。 */
188
+ /** 销毁沙箱占用的计算资源(容器/microVM)。调用后沙箱不可再用;是否可安全重复调用因 provider 而异,不要依赖这一点。 */
148
189
  stop(): Promise<void>;
149
- /** 本沙箱的稳定标识(各后端原生 ID,如 Docker 容器 ID 前缀);用于跨调用关联同一沙箱的会话状态,也用于日志展示。 */
190
+ /** 本沙箱的稳定标识( provider 原生 ID,如 Docker 容器 ID 前缀);用于跨调用关联同一沙箱的会话状态,也用于日志展示。 */
150
191
  readonly sandboxId: string;
151
192
  /**
152
193
  * 本地 OTLP 接收器的目标 host。
@@ -158,19 +199,19 @@ export interface Sandbox {
158
199
 
159
200
  /**
160
201
  * 可选:把一行写进容器的「主日志」(PID1 在 tail 它)——于是 `docker logs` /
161
- * Docker UI 的 Logs 标签页能实时看到 agent 逐轮活动。docker 后端实现,其它可省略。
202
+ * Docker UI 的 Logs 标签页能实时看到 agent 逐轮活动。docker provider 实现,其它可省略。
162
203
  */
163
204
  appendLog?(line: string): Promise<void>;
164
205
 
165
206
  /**
166
207
  * 从沙箱内任意路径读取文件,返回二进制 Buffer。
167
- * 对应各 backend:Docker getArchive / Vercel readFileToBuffer / e2b files.read(bytes) / …
208
+ * 对应各 provider:Docker getArchive / Vercel readFileToBuffer / e2b files.read(bytes) / …
168
209
  */
169
210
  downloadFile(path: string): Promise<Buffer>;
170
211
 
171
212
  /**
172
213
  * 向沙箱内任意路径写入文件(二进制)。
173
- * 对应各 backend:Docker putArchive / Vercel fs.writeFile(Buffer) / e2b files.write / …
214
+ * 对应各 provider:Docker putArchive / Vercel fs.writeFile(Buffer) / e2b files.write / …
174
215
  */
175
216
  uploadFile(path: string, content: Buffer): Promise<void>;
176
217
  }
@@ -1,7 +1,7 @@
1
- // Vercel Sandbox 后端:用 @vercel/sandbox SDK 把 Vercel microVM 当隔离工作区跑 eval。
1
+ // Vercel Sandbox provider:用 @vercel/sandbox SDK 把 Vercel microVM 当隔离工作区跑 eval。
2
2
  // 契约对齐 ../types.ts 的 Sandbox 接口,与 DockerSandbox 可互换。
3
3
 
4
- import { Sandbox as VSandbox } from "@vercel/sandbox";
4
+ import { Sandbox as VSandbox, APIError } from "@vercel/sandbox";
5
5
  import type {
6
6
  Sandbox,
7
7
  CommandResult,
@@ -14,6 +14,17 @@ import { readSourceFilesByList } from "./source-files.ts";
14
14
  import { collectLocalFiles } from "./local-files.ts";
15
15
  import { resolveSandboxPath } from "./paths.ts";
16
16
  import { t } from "../i18n/index.ts";
17
+ import { beforeExternalTerminalWrite } from "../tty-line.ts";
18
+ import type { SandboxProvisionErrorKind } from "./errors.ts";
19
+
20
+ /**
21
+ * vercel SDK 对单次 fetch 的 429 已有内部重试(见 @vercel/sandbox 的 with-retry.js,
22
+ * 5 次指数退避);这里再分类是为了给 create() 整体重试兜底——耗尽内部重试后仍返回 429
23
+ * 响应的 APIError,或 create() 轮询 session 状态过程里撞到的限流,都会走到这里。
24
+ */
25
+ export function classifyProvisionError(e: unknown): SandboxProvisionErrorKind {
26
+ return e instanceof APIError && e.response.status === 429 ? "rate_limit" : "unknown";
27
+ }
17
28
 
18
29
  // Vercel Sandbox 的默认工作区路径(SDK writeFiles 默认落这里)。
19
30
  const VERCEL_WORKDIR = "/vercel/sandbox";
@@ -111,15 +122,18 @@ export class VercelSandbox implements Sandbox {
111
122
  // 挂起的 stop(最长 15s)不该拖住触发 rotate 的那条命令,还烧新 session 的时长。
112
123
  // 失败只警告不静默(旧的到 session timeout 也会被平台回收)。
113
124
  void withTimeout(oldVsb.stop(), STOP_OLD_SESSION_TIMEOUT_MS).catch((stopErr) => {
125
+ beforeExternalTerminalWrite();
114
126
  console.error(
115
127
  `[VercelSandbox] warning: failed to stop rotated-out session, microVM may leak until session timeout: ${String(stopErr)}`,
116
128
  );
117
129
  });
130
+ beforeExternalTerminalWrite();
118
131
  console.error(t("vercel.rotated", {
119
132
  seconds: Math.round(elapsed / 1000),
120
133
  sessionId: newVsb.currentSession().sessionId,
121
134
  }));
122
135
  } catch (err) {
136
+ beforeExternalTerminalWrite();
123
137
  console.error(t("vercel.rotateFailed", {
124
138
  seconds: Math.round(elapsed / 1000),
125
139
  error: String(err),
@@ -176,7 +190,7 @@ export class VercelSandbox implements Sandbox {
176
190
  }
177
191
 
178
192
  // targetDir 已由 paths.ts 的 normalizeSandboxPaths 解析成绝对路径;这里再解析一次
179
- // 只是对直接使用后端实例(未包 normalize)的幂等防御,提到 map 外只算一次。
193
+ // 只是对直接使用 provider 实例(未包 normalize)的幂等防御,提到 map 外只算一次。
180
194
  async writeFiles(files: Record<string, string>, targetDir?: string): Promise<void> {
181
195
  const base = resolveSandboxPath(this.workdir, targetDir);
182
196
  const entries = Object.entries(files).map(([p, content]) => ({
@@ -1,5 +1,5 @@
1
- // 断言收集器:test 期间记录断言(值级就地、作用域延迟),test 结束后对完整运行
2
- // 结果(ScoringContext)统一 finalize 成 AssertionResult[],再交判决。
1
+ // 断言收集器:test 期间记录断言(值断言就地、作用域断言延迟),test 结束后对完整运行
2
+ // 结果(ScoringContext)统一 finalize 成 AssertionResult[],再交判定。
3
3
 
4
4
  import type { AssertionResult, ScoringContext, Severity, SourceLoc } from "../types.ts";
5
5
  import { captureLoc } from "../source-loc.ts";
@@ -26,7 +26,7 @@ export interface Spec {
26
26
  evaluate(ctx: ScoringContext): number | EvalScore | Promise<number | EvalScore>;
27
27
  }
28
28
 
29
- /** 作者拿到的可链式句柄,改严重级 / 阈值(回头改 spec)。 */
29
+ /** 作者拿到的可链式句柄,改严重度 / 阈值(回头改 spec)。 */
30
30
  export interface RecordHandle {
31
31
  atLeast(threshold: number): RecordHandle;
32
32
  gate(threshold?: number): RecordHandle;
@@ -1,6 +1,6 @@
1
- // LLM-as-judge:用一个与被测 agent 完全分离的评判模型做结构化 autoevals 评分。
1
+ // LLM-as-judge:用一个与被测 agent 完全分离的裁判模型做结构化 autoevals 评分。
2
2
  //
3
- // 评判模型走 OpenAI 兼容的 /chat/completions。base_url + key 解析优先级:
3
+ // 裁判模型走 OpenAI 兼容的 /chat/completions。base_url + key 解析优先级:
4
4
  // judge.baseUrl / judge.apiKeyEnv → NICEEVAL_JUDGE_BASE / CODEX_BASE_URL → OpenAI 官方
5
5
  //
6
6
  // closedQA / factuality / summarizes 直接用 autoevals 库(braintrust)。
@@ -191,7 +191,19 @@ export function maxToolCalls(max: number): Spec {
191
191
  }
192
192
 
193
193
  export function loadedSkill(skill: string): Spec {
194
- return calledTool("load_skill", { input: { skill } });
194
+ return {
195
+ name: `loadedSkill(${skill})`,
196
+ severity: "gate",
197
+ // 读 skill.loaded 一等事件,不按名字猜工具调用:各 agent 表达 Skill 加载的原生形态不同
198
+ // (Claude Code 是 Skill tool_use、eve 是 load-skill action kind),归一化的责任在 parser。
199
+ evaluate: (ctx) => {
200
+ const loaded = ctx.events.filter((e): e is Extract<StreamEvent, { type: "skill.loaded" }> => e.type === "skill.loaded");
201
+ const matched = loaded.filter((e) => e.skill === skill);
202
+ // 没命中时把实际加载过的 skill 列出来(常见失败是名字对不上,而不是一个都没加载)。
203
+ const shown = matched.length ? matched : loaded;
204
+ return { score: matched.length ? 1 : 0, evidence: shown.length ? shown.map((e) => e.skill).join(", ") : undefined };
205
+ },
206
+ };
195
207
  }
196
208
 
197
209
  export function noFailedActions(): Spec {
@@ -1,9 +1,9 @@
1
- // scoring 域类型:值级断言(expect 匹配器)、断言记录与结果、评分上下文、judge 配置。
1
+ // scoring 域类型:值断言(expect 匹配器)、断言记录与结果、评分上下文、judge 配置。
2
2
 
3
3
  import type { Severity, SourceLoc } from "../shared/types.ts";
4
4
  import type { DerivedFacts, StreamEvent, Usage } from "../o11y/types.ts";
5
5
 
6
- /** 值级断言(expect 匹配器)。纯函数 score + 可链式改严重级 / 阈值。 */
6
+ /** 值断言(expect 匹配器)。纯函数 score + 可链式改严重度 / 阈值。 */
7
7
  export interface ValueAssertion {
8
8
  readonly name: string;
9
9
  readonly severity: Severity;
@@ -12,8 +12,8 @@ export interface ValueAssertion {
12
12
  /** 转成硬门槛断言:未达阈值(省略 threshold 则按 score > 0 判定)整条 eval 判为 failed。返回新实例,不改原对象。 */
13
13
  gate(threshold?: number): ValueAssertion;
14
14
  /**
15
- * 转成软阈值断言:未达 threshold 时该条记为 failed,但默认不拖累整条 eval 的 outcome;
16
- * `--strict` 运行下,软阈值失败也会把整条 eval 的 outcome 计为 failed。返回新实例,不改原对象。
15
+ * 转成软阈值断言:未达 threshold 时该条记为 failed,但默认不拖累整条 eval 的 verdict;
16
+ * `--strict` 运行下,软阈值失败也会把整条 eval 的 verdict 计为 failed。返回新实例,不改原对象。
17
17
  */
18
18
  atLeast(threshold: number): ValueAssertion;
19
19
  }
@@ -27,7 +27,7 @@ export interface AssertionSpec {
27
27
  evaluate(ctx: ScoringContext): Promise<number> | number;
28
28
  }
29
29
 
30
- /** 断言评估完的结果(进判决 / 报告)。 */
30
+ /** 断言评估完的结果(进判定 / 报告)。 */
31
31
  export interface AssertionResult {
32
32
  name: string;
33
33
  severity: Severity;
@@ -39,7 +39,7 @@ export interface AssertionResult {
39
39
  evidence?: string;
40
40
  /** 所属分组(t.group 标题)。纯报告用,不影响 passed/score。 */
41
41
  group?: string;
42
- /** 断言在 eval 源码里的调用点(栈回溯抠出);view 把判决叠回这一行。 */
42
+ /** 断言在 eval 源码里的调用点(栈回溯抠出);view 把判定叠回这一行。 */
43
43
  loc?: SourceLoc;
44
44
  }
45
45
 
@@ -71,7 +71,7 @@ export interface DiffData {
71
71
  deletedFiles: string[];
72
72
  }
73
73
 
74
- export type ResultOutcome = "passed" | "failed" | "errored" | "skipped";
74
+ export type Verdict = "passed" | "failed" | "errored" | "skipped";
75
75
 
76
76
  export interface JudgeConfig {
77
77
  model: string;
@@ -1,13 +1,13 @@
1
- // 判决:把执行结果 + 断言 + 跳过原因折叠成一个 Outcome(见 docs/scoring.md)。
1
+ // 判定:把执行结果 + 断言 + 跳过原因折叠成一个 Verdict(见 docs/feature/scoring/README.md)。
2
2
 
3
- import type { AssertionResult, ResultOutcome } from "../types.ts";
3
+ import type { AssertionResult, Verdict } from "../types.ts";
4
4
 
5
- export function computeOutcome(input: {
5
+ export function computeVerdict(input: {
6
6
  error?: string;
7
7
  assertions: readonly AssertionResult[];
8
8
  skipReason?: string;
9
9
  strict?: boolean;
10
- }): ResultOutcome {
10
+ }): Verdict {
11
11
  if (input.error !== undefined) return "errored";
12
12
 
13
13
  for (const a of input.assertions) {
@@ -1,13 +1,13 @@
1
1
  // CLI 表格(runner/reporters/table.ts)与 view 榜单(view/aggregate.ts)共用的聚合小工具。
2
- // 实验标签推导、token/成本求和、outcome 排序各只有一份 —— 否则同一个实验在终端和网页上
2
+ // 实验标签推导、token/成本求和、verdict 排序各只有一份 —— 否则同一个实验在终端和网页上
3
3
  // 会显示成两个名字 / 两组数。保持环境无关(纯函数,只 type import)。
4
4
 
5
5
  import type { Usage } from "../o11y/types.ts";
6
6
  import type { EvalResult, ExperimentRunInfo } from "../runner/types.ts";
7
- import type { ResultOutcome } from "../scoring/types.ts";
7
+ import type { Verdict } from "../scoring/types.ts";
8
8
 
9
9
  /** 明细行排序:失败最靠前(failed > errored > skipped > passed 的紧急程度)。 */
10
- export const OUTCOME_ORDER: Record<ResultOutcome, number> = {
10
+ export const VERDICT_ORDER: Record<Verdict, number> = {
11
11
  failed: 0,
12
12
  errored: 1,
13
13
  skipped: 2,
@@ -34,6 +34,26 @@ export function displayExperimentName(id: string | undefined): string | undefine
34
34
  return id.split("/").filter(Boolean).at(-1) ?? id;
35
35
  }
36
36
 
37
+ /**
38
+ * 实验 id 的组推导:去掉末段的目录前缀("compare/bub-low" → "compare");
39
+ * 无 "/" 的顶层实验不属于任何组,返回 undefined。view 榜单分组与自定义报告
40
+ * 的分组用同一份,两边的「组」永远指同一个东西。
41
+ */
42
+ export function experimentGroupOf(experimentId: string): string | undefined {
43
+ if (!experimentId.includes("/")) return undefined;
44
+ return experimentId.split("/").slice(0, -1).join("/");
45
+ }
46
+
47
+ /**
48
+ * eval id 前缀过滤,同 CLI 位置参数的分段语义(src/runner/discover.ts):
49
+ * "algebra" 匹配自身与 "algebra/..." 子级,不误配 "algebra2";允许 "algebra/" 尾斜杠写法,等价。
50
+ */
51
+ export function evalPrefixPredicate(evals?: string | string[]): (id: string) => boolean {
52
+ if (evals === undefined) return () => true;
53
+ const prefixes = (Array.isArray(evals) ? evals : [evals]).map((p) => p.replace(/\/+$/, ""));
54
+ return (id) => prefixes.some((prefix) => id === prefix || id.startsWith(prefix + "/"));
55
+ }
56
+
37
57
  /** 无 experimentId 时的兜底标签。 */
38
58
  export function fallbackExperimentLabel(result: {
39
59
  experiment?: ExperimentRunInfo;
@@ -1,4 +1,4 @@
1
- // 真正跨域的原子类型:序列化 / 严重级 / 源码位置 / 生命周期。
1
+ // 真正跨域的原子类型:序列化 / 严重度 / 源码位置 / 生命周期。
2
2
  // 各域的类型住在各自目录的 types.ts(o11y / sandbox / agents / scoring / context / runner),
3
3
  // src/types.ts 是聚合 facade —— 模块代码统一从那里 import,不必记住每个类型的家。
4
4
 
@@ -11,7 +11,7 @@ export type JsonValue =
11
11
  | JsonValue[]
12
12
  | { [key: string]: JsonValue };
13
13
 
14
- /** 断言的严重级:"gate" 失败必判整轮 failed;"soft" 默认只记录不拦截,仅在 `--strict` 模式或显式设阈值未达标时才计入失败。 */
14
+ /** 断言的严重度:"gate" 失败必判整轮 failed;"soft" 默认只记录不拦截,仅在 `--strict` 模式或显式设阈值未达标时才计入失败。 */
15
15
  export type Severity = "gate" | "soft";
16
16
 
17
17
  /**
@@ -1,23 +1,23 @@
1
- // server(aggregate.ts)与前端(app/lib/outcome.ts)共用的判决折叠口径。
1
+ // server(aggregate.ts)与前端(app/lib/verdict.ts)共用的判定折叠口径。
2
2
  // 必须保持环境无关且只依赖 type import,vite 前端会直接打包它。
3
3
  // 单独成模块的原因:折叠/计票口径两边必须逐字一致,否则折叠行状态会和 KPI / 成功率对不上。
4
4
 
5
- import type { ResultOutcome } from "../types.ts";
5
+ import type { Verdict } from "../types.ts";
6
6
 
7
- /** 折叠/计票只需要 outcome 字段;server 传 EvalResult,前端传 ViewResult 都满足。 */
8
- export interface OutcomeLike {
9
- outcome: ResultOutcome;
7
+ /** 折叠/计票只需要 verdict 字段;server 传 EvalResult,前端传 ViewResult 都满足。 */
8
+ export interface VerdictLike {
9
+ verdict: Verdict;
10
10
  }
11
11
 
12
12
  /**
13
- * 把同一个 eval 的多轮 attempt 折叠成单一判决:任一轮通过 → 该 eval 通过(对齐 earlyExit
13
+ * 把同一个 eval 的多轮 attempt 折叠成单一判定:任一轮通过 → 该 eval 通过(对齐 earlyExit
14
14
  * 「先过一次即停」语义),否则按 failed > errored > skipped 取最严重的一个。
15
15
  */
16
- export function foldEvalOutcome(attempts: OutcomeLike[]): ResultOutcome {
17
- const outcomes = attempts.map((a) => a.outcome);
18
- if (outcomes.some((o) => o === "passed")) return "passed";
19
- if (outcomes.some((o) => o === "failed")) return "failed";
20
- if (outcomes.some((o) => o === "errored")) return "errored";
16
+ export function foldEvalVerdict(attempts: VerdictLike[]): Verdict {
17
+ const verdicts = attempts.map((a) => a.verdict);
18
+ if (verdicts.some((o) => o === "passed")) return "passed";
19
+ if (verdicts.some((o) => o === "failed")) return "failed";
20
+ if (verdicts.some((o) => o === "errored")) return "errored";
21
21
  return "skipped";
22
22
  }
23
23
 
@@ -33,16 +33,16 @@ export interface EvalLevelStats {
33
33
 
34
34
  /**
35
35
  * 通过率与 passed/failed 一律按 eval 计票,不按 attempt:每个 eval 不管跑几轮都只占一票,先把它
36
- * 的多轮折叠成单一判决再计数。否则 runs>1 时同一 eval 的 N 次 attempt 各算一票 —— 尤其 earlyExit
36
+ * 的多轮折叠成单一判定再计数。否则 runs>1 时同一 eval 的 N 次 attempt 各算一票 —— 尤其 earlyExit
37
37
  * 开时通过的 eval 只留 1 次、失败的 eval 跑满 N 次,失败 eval 被重复计入分母,把通过率拉低
38
- * (见 docs/runner.md、docs/scoring.md)。keyOf 决定「一个 eval」的粒度:单实验按 eval id,
38
+ * (见 docs/runner.md、docs/feature/scoring/README.md)。keyOf 决定「一个 eval」的粒度:单实验按 eval id,
39
39
  * 跨实验组按 experimentId|eval id。
40
40
  */
41
- export function evalLevelStats<T extends OutcomeLike>(results: T[], keyOf: (r: T) => string): EvalLevelStats {
41
+ export function evalLevelStats<T extends VerdictLike>(results: T[], keyOf: (r: T) => string): EvalLevelStats {
42
42
  const byEval = new Map<string, T[]>();
43
43
  for (const r of results) byEval.set(keyOf(r), [...(byEval.get(keyOf(r)) ?? []), r]);
44
44
  const counts = { passed: 0, failed: 0, errored: 0, skipped: 0 };
45
- for (const group of byEval.values()) counts[foldEvalOutcome(group)] += 1;
45
+ for (const group of byEval.values()) counts[foldEvalVerdict(group)] += 1;
46
46
  const ran = counts.passed + counts.failed + counts.errored; // skipped 不进分母
47
47
  return { evals: byEval.size, ...counts, passRate: ran ? counts.passed / ran : 0 };
48
48
  }