niceeval 0.9.1 → 0.10.2

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 (432) hide show
  1. package/INDEX.md +31 -33
  2. package/dist/agents/types.d.ts +20 -17
  3. package/dist/context/types.d.ts +21 -21
  4. package/dist/i18n/en.d.ts +10 -2
  5. package/dist/i18n/zh-CN.d.ts +14 -6
  6. package/dist/o11y/execution-tree.d.ts +103 -0
  7. package/dist/o11y/otlp/select.d.ts +22 -0
  8. package/dist/report/{react → assets}/colors.d.ts +8 -0
  9. package/dist/report/{react → assets}/colors.js +23 -0
  10. package/dist/report/built-in/index.d.ts +2 -2
  11. package/dist/report/built-in/index.js +2 -2
  12. package/dist/report/built-in/standard.d.ts +8 -1
  13. package/dist/report/built-in/standard.js +17 -7
  14. package/dist/report/components/attempt-detail/AttemptAssertions.d.ts +6 -0
  15. package/dist/report/components/attempt-detail/AttemptAssertions.js +17 -0
  16. package/dist/report/components/attempt-detail/AttemptConversation.d.ts +10 -0
  17. package/dist/report/components/attempt-detail/AttemptConversation.js +74 -0
  18. package/dist/report/components/attempt-detail/AttemptDiagnostics.d.ts +6 -0
  19. package/dist/report/components/attempt-detail/AttemptDiagnostics.js +7 -0
  20. package/dist/report/components/attempt-detail/AttemptDiff.d.ts +6 -0
  21. package/dist/report/components/attempt-detail/AttemptDiff.js +11 -0
  22. package/dist/report/components/attempt-detail/AttemptError.d.ts +6 -0
  23. package/dist/report/components/attempt-detail/AttemptError.js +16 -0
  24. package/dist/report/components/attempt-detail/AttemptFixPrompt.d.ts +6 -0
  25. package/dist/report/components/attempt-detail/AttemptFixPrompt.js +7 -0
  26. package/dist/report/components/attempt-detail/AttemptSource.d.ts +6 -0
  27. package/dist/report/components/attempt-detail/AttemptSource.js +97 -0
  28. package/dist/report/components/attempt-detail/AttemptSummary.d.ts +8 -0
  29. package/dist/report/components/attempt-detail/AttemptSummary.js +20 -0
  30. package/dist/report/components/attempt-detail/AttemptTimeline.d.ts +6 -0
  31. package/dist/report/components/attempt-detail/AttemptTimeline.js +28 -0
  32. package/dist/report/components/attempt-detail/AttemptTrace.d.ts +6 -0
  33. package/dist/report/components/attempt-detail/AttemptTrace.js +29 -0
  34. package/dist/report/components/attempt-detail/AttemptUsage.d.ts +6 -0
  35. package/dist/report/components/attempt-detail/AttemptUsage.js +20 -0
  36. package/dist/report/components/attempt-detail/compute.d.ts +24 -0
  37. package/dist/report/components/attempt-detail/compute.js +297 -0
  38. package/dist/report/components/attempt-detail/faces.d.ts +14 -0
  39. package/dist/report/components/attempt-detail/faces.js +239 -0
  40. package/dist/report/components/attempt-detail/index.d.ts +38 -0
  41. package/dist/report/components/attempt-detail/index.js +547 -0
  42. package/dist/report/{react → components}/cell.d.ts +5 -3
  43. package/dist/report/{react → components}/cell.js +3 -3
  44. package/dist/report/{react → components/entity-lists}/AttemptList.d.ts +8 -4
  45. package/dist/report/{react → components/entity-lists}/AttemptList.js +16 -10
  46. package/dist/report/{react → components/entity-lists}/EvalList.d.ts +3 -3
  47. package/dist/report/{react → components/entity-lists}/EvalList.js +0 -0
  48. package/dist/report/{react → components/entity-lists}/ExperimentList.d.ts +4 -5
  49. package/dist/report/{react → components/entity-lists}/ExperimentList.js +12 -11
  50. package/dist/report/components/entity-lists/compute.d.ts +23 -0
  51. package/dist/report/components/entity-lists/compute.js +171 -0
  52. package/dist/report/components/entity-lists/faces.d.ts +5 -0
  53. package/dist/report/components/entity-lists/faces.js +175 -0
  54. package/dist/report/components/entity-lists/index.d.ts +49 -0
  55. package/dist/report/components/entity-lists/index.js +169 -0
  56. package/dist/report/{react → components}/fixtures.d.ts +2 -2
  57. package/dist/report/{react → components}/fixtures.js +1 -1
  58. package/dist/report/{react → components/metric-views}/DeltaTable.d.ts +2 -2
  59. package/dist/report/{react → components/metric-views}/DeltaTable.js +4 -4
  60. package/dist/report/{react → components/metric-views}/MetricBars.d.ts +3 -3
  61. package/dist/report/{react → components/metric-views}/MetricBars.js +3 -3
  62. package/dist/report/{react → components/metric-views}/MetricLine.d.ts +2 -2
  63. package/dist/report/{react → components/metric-views}/MetricLine.js +8 -5
  64. package/dist/report/{react → components/metric-views}/MetricMatrix.d.ts +3 -3
  65. package/dist/report/{react → components/metric-views}/MetricMatrix.js +4 -4
  66. package/dist/report/components/metric-views/MetricScatter.d.ts +11 -0
  67. package/dist/report/{react → components/metric-views}/MetricScatter.js +27 -60
  68. package/dist/report/{react → components/metric-views}/MetricTable.d.ts +3 -3
  69. package/dist/report/{react → components/metric-views}/MetricTable.js +4 -4
  70. package/dist/report/{react → components/metric-views}/Scoreboard.d.ts +2 -2
  71. package/dist/report/{react → components/metric-views}/Scoreboard.js +3 -3
  72. package/dist/report/components/metric-views/compute.d.ts +89 -0
  73. package/dist/report/{compute.js → components/metric-views/compute.js} +32 -415
  74. package/dist/report/components/metric-views/faces.d.ts +13 -0
  75. package/dist/report/components/metric-views/faces.js +381 -0
  76. package/dist/report/components/metric-views/index.d.ts +50 -0
  77. package/dist/report/components/metric-views/index.js +273 -0
  78. package/dist/report/{text → components/metric-views}/plot.js +1 -1
  79. package/dist/report/components/shared-compute.d.ts +22 -0
  80. package/dist/report/components/shared-compute.js +47 -0
  81. package/dist/report/components/shared-faces.d.ts +9 -0
  82. package/dist/report/components/shared-faces.js +26 -0
  83. package/dist/report/components/shared.d.ts +68 -0
  84. package/dist/report/components/shared.js +125 -0
  85. package/dist/report/{react → components/site-components}/CopyFixPrompt.d.ts +2 -2
  86. package/dist/report/{react → components/site-components}/CopyFixPrompt.js +2 -2
  87. package/dist/report/{react → components/site-components}/HeroCard.d.ts +2 -2
  88. package/dist/report/{react → components/site-components}/HeroCard.js +2 -2
  89. package/dist/report/{react → components/site-components}/ScopeWarnings.d.ts +2 -2
  90. package/dist/report/{react → components/site-components}/ScopeWarnings.js +3 -3
  91. package/dist/report/{react → components/site-components}/TraceWaterfall.d.ts +3 -3
  92. package/dist/report/{react → components/site-components}/TraceWaterfall.js +5 -5
  93. package/dist/report/components/site-components/compute.d.ts +28 -0
  94. package/dist/report/components/site-components/compute.js +132 -0
  95. package/dist/report/components/site-components/faces.d.ts +21 -0
  96. package/dist/report/components/site-components/faces.js +75 -0
  97. package/dist/report/components/site-components/index.d.ts +74 -0
  98. package/dist/report/components/site-components/index.js +220 -0
  99. package/dist/report/{scope-warnings.d.ts → components/site-components/scope-warnings.d.ts} +2 -2
  100. package/dist/report/{scope-warnings.js → components/site-components/scope-warnings.js} +2 -2
  101. package/dist/report/{react → components/summaries}/ScopeSummary.d.ts +2 -2
  102. package/dist/report/{react → components/summaries}/ScopeSummary.js +17 -8
  103. package/dist/report/components/summaries/compute.d.ts +7 -0
  104. package/dist/report/components/summaries/compute.js +51 -0
  105. package/dist/report/components/summaries/faces.d.ts +7 -0
  106. package/dist/report/components/summaries/faces.js +38 -0
  107. package/dist/report/components/summaries/index.d.ts +27 -0
  108. package/dist/report/components/summaries/index.js +78 -0
  109. package/dist/report/definition/grid-layout.d.ts +50 -0
  110. package/dist/report/definition/grid-layout.js +89 -0
  111. package/dist/report/{primitives.d.ts → definition/primitives.d.ts} +34 -4
  112. package/dist/report/{primitives.js → definition/primitives.js} +101 -10
  113. package/dist/report/{report.d.ts → definition/report.d.ts} +36 -65
  114. package/dist/report/{report.js → definition/report.js} +58 -90
  115. package/dist/report/{text/table.d.ts → definition/table-text.d.ts} +2 -2
  116. package/dist/report/{text/table.js → definition/table-text.js} +2 -2
  117. package/dist/report/{tree.d.ts → definition/tree.d.ts} +43 -11
  118. package/dist/report/{tree.js → definition/tree.js} +9 -11
  119. package/dist/report/index.d.ts +33 -18
  120. package/dist/report/index.js +23 -14
  121. package/dist/report/{aggregate.d.ts → model/aggregate.d.ts} +12 -4
  122. package/dist/report/{aggregate.js → model/aggregate.js} +28 -8
  123. package/dist/report/{flag.d.ts → model/flag.d.ts} +14 -1
  124. package/dist/report/{flag.js → model/flag.js} +35 -0
  125. package/dist/report/{format.d.ts → model/format.d.ts} +13 -7
  126. package/dist/report/{format.js → model/format.js} +79 -9
  127. package/dist/report/{locale.d.ts → model/locale.d.ts} +17 -17
  128. package/dist/report/{locale.js → model/locale.js} +31 -33
  129. package/dist/report/{metrics.d.ts → model/metrics.d.ts} +2 -2
  130. package/dist/report/{metrics.js → model/metrics.js} +2 -2
  131. package/dist/report/{types.d.ts → model/types.d.ts} +154 -20
  132. package/dist/report/react/index.d.ts +32 -21
  133. package/dist/report/react/index.js +33 -21
  134. package/dist/report/{load.d.ts → runtime/load.d.ts} +1 -1
  135. package/dist/report/{load.js → runtime/load.js} +1 -1
  136. package/dist/report/runtime/text.d.ts +67 -0
  137. package/dist/report/runtime/text.js +114 -0
  138. package/dist/report/runtime/web.d.ts +35 -0
  139. package/dist/report/runtime/web.js +70 -0
  140. package/dist/results/annotated-source.d.ts +87 -0
  141. package/dist/results/attempt-evidence.d.ts +74 -0
  142. package/dist/results/attempt-source.d.ts +15 -0
  143. package/dist/results/select.d.ts +11 -1
  144. package/dist/results/select.js +27 -5
  145. package/dist/runner/eval-selection.d.ts +26 -0
  146. package/dist/runner/feedback/sink.d.ts +26 -1
  147. package/dist/runner/types.d.ts +151 -24
  148. package/dist/sandbox/docker.d.ts +6 -0
  149. package/dist/sandbox/e2b.d.ts +0 -1
  150. package/dist/sandbox/types.d.ts +23 -21
  151. package/dist/scoring/display.d.ts +7 -2
  152. package/dist/scoring/display.js +42 -4
  153. package/dist/scoring/types.d.ts +3 -3
  154. package/dist/shared/aggregate.d.ts +14 -0
  155. package/dist/shared/aggregate.js +31 -0
  156. package/dist/shared/types.d.ts +6 -1
  157. package/docs-site/images/logo-dark.svg +7 -0
  158. package/docs-site/images/logo.svg +6 -5
  159. package/docs-site/zh/README.md +8 -7
  160. package/docs-site/zh/examples/ai-agent-application.mdx +5 -5
  161. package/docs-site/zh/examples/coding-agent-extensions.mdx +4 -4
  162. package/docs-site/zh/examples/index.mdx +3 -3
  163. package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +6 -6
  164. package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
  165. package/docs-site/zh/examples/integrations/codex-sdk.mdx +3 -3
  166. package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
  167. package/docs-site/zh/examples/integrations/pi-sdk.mdx +2 -2
  168. package/docs-site/zh/explanation/adapter.mdx +19 -19
  169. package/docs-site/zh/explanation/assert.mdx +9 -9
  170. package/docs-site/zh/explanation/drive.mdx +4 -4
  171. package/docs-site/zh/explanation/evals.mdx +8 -8
  172. package/docs-site/zh/explanation/experiment.mdx +8 -8
  173. package/docs-site/zh/explanation/hitl.mdx +13 -13
  174. package/docs-site/zh/explanation/judge.mdx +3 -3
  175. package/docs-site/zh/explanation/overview.mdx +7 -7
  176. package/docs-site/zh/explanation/runner.mdx +17 -13
  177. package/docs-site/zh/explanation/tier.mdx +6 -6
  178. package/docs-site/zh/index.mdx +14 -14
  179. package/docs-site/zh/introduction.mdx +13 -16
  180. package/docs-site/zh/reference/builtin-agents.mdx +61 -28
  181. package/docs-site/zh/reference/capabilities.mdx +7 -27
  182. package/docs-site/zh/reference/cli.mdx +36 -33
  183. package/docs-site/zh/reference/define-agent.mdx +32 -31
  184. package/docs-site/zh/reference/define-config.mdx +6 -6
  185. package/docs-site/zh/reference/define-eval.mdx +33 -20
  186. package/docs-site/zh/reference/events.mdx +4 -4
  187. package/docs-site/zh/reference/expect.mdx +3 -3
  188. package/docs-site/zh/reference/official-adapters.mdx +17 -17
  189. package/docs-site/zh/reference/report-components.mdx +195 -121
  190. package/docs-site/zh/reference/results-data.mdx +13 -13
  191. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +11 -11
  192. package/docs-site/zh/troubleshooting/debugging.mdx +40 -16
  193. package/docs-site/zh/{how-to → tutorials}/agent-feedback-loop.mdx +38 -38
  194. package/docs-site/zh/tutorials/agent-onboarding.mdx +100 -0
  195. package/docs-site/zh/tutorials/authoring.mdx +211 -0
  196. package/docs-site/zh/{how-to → tutorials}/ci-integration.mdx +8 -8
  197. package/docs-site/zh/{how-to → tutorials}/connect-otel.mdx +22 -22
  198. package/docs-site/zh/{how-to → tutorials}/connect-your-agent.mdx +58 -63
  199. package/docs-site/zh/tutorials/custom-reports.mdx +453 -0
  200. package/docs-site/zh/{how-to → tutorials}/dataset-fanout.mdx +4 -4
  201. package/docs-site/zh/tutorials/experiments.mdx +102 -0
  202. package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
  203. package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +6 -6
  204. package/docs-site/zh/tutorials/quickstart.mdx +18 -18
  205. package/docs-site/zh/{how-to → tutorials}/reporters.mdx +6 -6
  206. package/docs-site/zh/{how-to → tutorials}/sandbox-agent.mdx +6 -6
  207. package/docs-site/zh/{how-to → tutorials}/sandbox-providers.mdx +41 -19
  208. package/docs-site/zh/{how-to → tutorials}/scoring-guide.mdx +4 -4
  209. package/docs-site/zh/{how-to → tutorials}/viewing-results.mdx +60 -57
  210. package/docs-site/zh/tutorials/write-experiment.mdx +353 -0
  211. package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
  212. package/package.json +10 -7
  213. package/src/agents/bub.ts +21 -8
  214. package/src/agents/claude-code.ts +23 -10
  215. package/src/agents/codex.test.ts +22 -7
  216. package/src/agents/codex.ts +22 -9
  217. package/src/agents/index.ts +2 -2
  218. package/src/agents/openai-compat.ts +1 -1
  219. package/src/agents/post-setup.ts +45 -22
  220. package/src/agents/streaming.ts +1 -1
  221. package/src/agents/types.ts +20 -17
  222. package/src/agents/ui-message-stream.test.ts +10 -0
  223. package/src/agents/ui-message-stream.ts +12 -1
  224. package/src/cli.ts +75 -59
  225. package/src/context/types.ts +21 -21
  226. package/src/define.ts +13 -0
  227. package/src/i18n/en.ts +27 -12
  228. package/src/i18n/zh-CN.ts +27 -12
  229. package/src/index.ts +2 -0
  230. package/src/o11y/prices.json +176 -99
  231. package/src/report/{react → assets}/colors.ts +22 -0
  232. package/src/report/{react → assets}/enhance.js +1 -33
  233. package/src/report/assets/styles.css +1785 -0
  234. package/src/report/built-in/index.tsx +2 -2
  235. package/src/report/built-in/standard.tsx +18 -6
  236. package/src/report/components/attempt-detail/AttemptAssertions.tsx +67 -0
  237. package/src/report/components/attempt-detail/AttemptConversation.tsx +158 -0
  238. package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +38 -0
  239. package/src/report/components/attempt-detail/AttemptDiff.tsx +35 -0
  240. package/src/report/components/attempt-detail/AttemptError.tsx +31 -0
  241. package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +26 -0
  242. package/src/report/components/attempt-detail/AttemptSource.tsx +224 -0
  243. package/src/report/components/attempt-detail/AttemptSummary.tsx +74 -0
  244. package/src/report/components/attempt-detail/AttemptTimeline.tsx +108 -0
  245. package/src/report/components/attempt-detail/AttemptTrace.tsx +63 -0
  246. package/src/report/components/attempt-detail/AttemptUsage.tsx +29 -0
  247. package/src/report/components/attempt-detail/attempt-components.test.tsx +758 -0
  248. package/src/report/components/attempt-detail/compute.ts +336 -0
  249. package/src/report/components/attempt-detail/faces.ts +260 -0
  250. package/src/report/components/attempt-detail/index.tsx +600 -0
  251. package/src/report/components/attempt-detail/validate.test.ts +252 -0
  252. package/src/report/{react → components}/cell.tsx +6 -3
  253. package/src/report/{report.test.ts → components/compute.test.ts} +316 -46
  254. package/src/report/{react → components/entity-lists}/AttemptList.tsx +24 -16
  255. package/src/report/{react → components/entity-lists}/EvalList.tsx +0 -0
  256. package/src/report/{react → components/entity-lists}/ExperimentList.tsx +33 -27
  257. package/src/report/components/entity-lists/compute.ts +196 -0
  258. package/src/report/components/entity-lists/faces.ts +208 -0
  259. package/src/report/components/entity-lists/index.tsx +238 -0
  260. package/src/report/components/entity-lists/validate.test.ts +114 -0
  261. package/src/report/{react → components}/fixtures.ts +2 -2
  262. package/src/report/{react → components/metric-views}/DeltaTable.tsx +5 -5
  263. package/src/report/{react → components/metric-views}/MetricBars.tsx +5 -5
  264. package/src/report/{react → components/metric-views}/MetricLine.tsx +9 -6
  265. package/src/report/{react → components/metric-views}/MetricMatrix.tsx +6 -6
  266. package/src/report/{react → components/metric-views}/MetricScatter.tsx +42 -70
  267. package/src/report/{react → components/metric-views}/MetricTable.tsx +6 -6
  268. package/src/report/{react → components/metric-views}/Scoreboard.tsx +4 -4
  269. package/src/report/{compute.ts → components/metric-views/compute.ts} +51 -477
  270. package/src/report/components/metric-views/faces.ts +423 -0
  271. package/src/report/components/metric-views/index.tsx +352 -0
  272. package/src/report/{text → components/metric-views}/plot.ts +1 -1
  273. package/src/report/components/metric-views/validate.test.ts +211 -0
  274. package/src/report/{react → components}/render.test.tsx +66 -26
  275. package/src/report/components/shared-compute.ts +59 -0
  276. package/src/report/components/shared-faces.ts +30 -0
  277. package/src/report/components/shared.ts +192 -0
  278. package/src/report/{react → components/site-components}/CopyFixPrompt.tsx +3 -3
  279. package/src/report/{react → components/site-components}/HeroCard.tsx +3 -3
  280. package/src/report/{react → components/site-components}/ScopeWarnings.tsx +4 -4
  281. package/src/report/{react → components/site-components}/TraceWaterfall.tsx +13 -10
  282. package/src/report/components/site-components/compute.ts +144 -0
  283. package/src/report/components/site-components/faces.ts +80 -0
  284. package/src/report/components/site-components/index.tsx +265 -0
  285. package/src/report/{scope-warnings.ts → components/site-components/scope-warnings.ts} +3 -3
  286. package/src/report/{site-components.test.tsx → components/site-components/site-components.test.tsx} +23 -21
  287. package/src/report/components/site-components/validate.test.ts +89 -0
  288. package/src/report/{react → components/summaries}/ScopeSummary.tsx +26 -11
  289. package/src/report/components/summaries/compute.ts +56 -0
  290. package/src/report/components/summaries/faces.ts +48 -0
  291. package/src/report/components/summaries/index.tsx +127 -0
  292. package/src/report/components/summaries/validate.test.ts +49 -0
  293. package/src/report/definition/grid-layout.test.ts +124 -0
  294. package/src/report/definition/grid-layout.ts +146 -0
  295. package/src/report/{primitives.tsx → definition/primitives.tsx} +168 -18
  296. package/src/report/{report.ts → definition/report.ts} +98 -165
  297. package/src/report/{shell-head.test.ts → definition/shell-head.test.ts} +2 -2
  298. package/src/report/{text/table.ts → definition/table-text.ts} +4 -4
  299. package/src/report/{tree.ts → definition/tree.ts} +56 -20
  300. package/src/report/index.ts +128 -66
  301. package/src/report/{aggregate.ts → model/aggregate.ts} +31 -9
  302. package/src/report/{flag.ts → model/flag.ts} +39 -1
  303. package/src/report/{format.ts → model/format.ts} +89 -10
  304. package/src/report/{locale.ts → model/locale.ts} +34 -36
  305. package/src/report/{metrics.ts → model/metrics.ts} +3 -3
  306. package/src/report/{types.ts → model/types.ts} +159 -22
  307. package/src/report/react/index.tsx +54 -26
  308. package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +601 -53
  309. package/src/report/runtime/host.test.ts +44 -0
  310. package/src/report/runtime/host.ts +138 -0
  311. package/src/report/{load.ts → runtime/load.ts} +1 -1
  312. package/src/report/runtime/text.ts +192 -0
  313. package/src/report/runtime/web.ts +106 -0
  314. package/src/results/attempt-evidence.ts +5 -1
  315. package/src/results/host-equivalence.test.ts +53 -2
  316. package/src/results/select.ts +29 -5
  317. package/src/runner/attempt.test.ts +48 -1
  318. package/src/runner/attempt.ts +62 -42
  319. package/src/runner/cleanup-timeout.test.ts +16 -0
  320. package/src/runner/cleanup-timeout.ts +23 -0
  321. package/src/runner/discover.ts +1 -2
  322. package/src/runner/eval-selection.test.ts +187 -0
  323. package/src/runner/eval-selection.ts +109 -0
  324. package/src/runner/experiment-cleanup-registry.test.ts +89 -0
  325. package/src/runner/experiment-cleanup-registry.ts +39 -0
  326. package/src/runner/experiment-labels.test.ts +71 -0
  327. package/src/runner/feedback/agent.test.ts +33 -1
  328. package/src/runner/feedback/agent.ts +12 -16
  329. package/src/runner/feedback/ci.test.ts +35 -1
  330. package/src/runner/feedback/ci.ts +16 -18
  331. package/src/runner/feedback/coordinator.ts +28 -0
  332. package/src/runner/feedback/human.test.ts +108 -1
  333. package/src/runner/feedback/human.ts +71 -21
  334. package/src/runner/feedback/reducer.test.ts +64 -0
  335. package/src/runner/feedback/reducer.ts +26 -0
  336. package/src/runner/feedback/sink.ts +40 -1
  337. package/src/runner/fingerprint.ts +2 -1
  338. package/src/runner/report.test.ts +14 -0
  339. package/src/runner/report.ts +3 -1
  340. package/src/runner/run.test.ts +390 -12
  341. package/src/runner/run.ts +259 -26
  342. package/src/runner/sandbox-selection.test.ts +13 -3
  343. package/src/runner/sandbox-selection.ts +4 -2
  344. package/src/runner/types.ts +160 -24
  345. package/src/sandbox/docker.ts +7 -0
  346. package/src/sandbox/e2b-reconcile.test.ts +125 -0
  347. package/src/sandbox/e2b.ts +47 -12
  348. package/src/sandbox/types.ts +23 -21
  349. package/src/scoring/display.test.ts +28 -2
  350. package/src/scoring/display.ts +38 -5
  351. package/src/scoring/types.ts +3 -3
  352. package/src/shared/aggregate.test.ts +52 -0
  353. package/src/shared/aggregate.ts +29 -0
  354. package/src/shared/types.ts +6 -1
  355. package/src/show/command.test.ts +34 -0
  356. package/src/show/command.ts +16 -0
  357. package/src/show/compose.ts +1 -1
  358. package/src/show/index.ts +56 -20
  359. package/src/show/render.ts +15 -239
  360. package/src/show/show.test.ts +127 -34
  361. package/src/view/app/App.test.tsx +1 -1
  362. package/src/view/app/App.tsx +119 -47
  363. package/src/view/app/i18n.ts +7 -147
  364. package/src/view/app/lib/attempt-dialog.test.ts +65 -0
  365. package/src/view/app/lib/attempt-dialog.ts +69 -0
  366. package/src/view/app/main.tsx +0 -1
  367. package/src/view/app/types.ts +3 -86
  368. package/src/view/artifact-serving.test.ts +22 -38
  369. package/src/view/client-dist/app.css +1 -1
  370. package/src/view/client-dist/app.js +15 -22
  371. package/src/view/data.test.ts +48 -36
  372. package/src/view/data.ts +105 -83
  373. package/src/view/index.ts +1 -1
  374. package/src/view/server.ts +32 -2
  375. package/src/view/shared/types.ts +6 -31
  376. package/src/view/site-parity.test.ts +24 -1
  377. package/src/view/site.ts +144 -17
  378. package/src/view/styles.css +0 -805
  379. package/src/view/view-report.test.ts +143 -22
  380. package/dist/report/components.d.ts +0 -179
  381. package/dist/report/components.js +0 -544
  382. package/dist/report/compute.d.ts +0 -139
  383. package/dist/report/react/ExperimentComparison.d.ts +0 -10
  384. package/dist/report/react/ExperimentComparison.js +0 -12
  385. package/dist/report/react/MetricScatter.d.ts +0 -9
  386. package/dist/report/react/format.d.ts +0 -3
  387. package/dist/report/react/format.js +0 -7
  388. package/dist/report/text/faces.d.ts +0 -45
  389. package/dist/report/text/faces.js +0 -671
  390. package/dist/report/web.d.ts +0 -32
  391. package/dist/report/web.js +0 -48
  392. package/docs-site/zh/how-to/authoring.mdx +0 -162
  393. package/docs-site/zh/how-to/custom-reports.mdx +0 -414
  394. package/docs-site/zh/how-to/experiments.mdx +0 -86
  395. package/docs-site/zh/how-to/write-experiment.mdx +0 -164
  396. package/src/report/components.tsx +0 -925
  397. package/src/report/react/ExperimentComparison.tsx +0 -73
  398. package/src/report/react/format.ts +0 -9
  399. package/src/report/react/styles.css +0 -945
  400. package/src/report/text/faces.ts +0 -767
  401. package/src/report/web.ts +0 -77
  402. package/src/show/report-host.test.ts +0 -205
  403. package/src/show/report-host.ts +0 -389
  404. package/src/view/app/components/AttemptModal.tsx +0 -496
  405. package/src/view/app/components/CodeView.test.tsx +0 -142
  406. package/src/view/app/components/CodeView.tsx +0 -310
  407. package/src/view/app/components/CopyControls.tsx +0 -106
  408. package/src/view/app/components/Trace.tsx +0 -100
  409. package/src/view/app/components/Transcript.tsx +0 -157
  410. package/src/view/app/components/ui/badge.tsx +0 -21
  411. package/src/view/app/lib/artifact-url.ts +0 -17
  412. package/src/view/app/lib/attempt-route.test.ts +0 -80
  413. package/src/view/app/lib/attempt-route.ts +0 -52
  414. package/src/view/app/lib/format.ts +0 -70
  415. package/src/view/app/lib/guards.test.ts +0 -108
  416. package/src/view/app/lib/guards.ts +0 -71
  417. package/src/view/app/lib/rows.ts +0 -22
  418. package/src/view/app/lib/transcript-data.tsx +0 -151
  419. package/src/view/app/lib/verdict.ts +0 -23
  420. package/src/view/app/shared.ts +0 -8
  421. /package/dist/report/{react → components/metric-views}/chart-math.d.ts +0 -0
  422. /package/dist/report/{react → components/metric-views}/chart-math.js +0 -0
  423. /package/dist/report/{text → components/metric-views}/plot.d.ts +0 -0
  424. /package/dist/report/{react → components/site-components}/PoweredBy.d.ts +0 -0
  425. /package/dist/report/{react → components/site-components}/PoweredBy.js +0 -0
  426. /package/dist/report/{text/layout.d.ts → model/text-layout.d.ts} +0 -0
  427. /package/dist/report/{text/layout.js → model/text-layout.js} +0 -0
  428. /package/dist/report/{types.js → model/types.js} +0 -0
  429. /package/src/report/{react → components/metric-views}/chart-math.test.ts +0 -0
  430. /package/src/report/{react → components/metric-views}/chart-math.ts +0 -0
  431. /package/src/report/{react → components/site-components}/PoweredBy.tsx +0 -0
  432. /package/src/report/{text/layout.ts → model/text-layout.ts} +0 -0
package/src/cli.ts CHANGED
@@ -5,17 +5,20 @@
5
5
  // niceeval clean 删除 .niceeval/ 历史运行 artifact
6
6
 
7
7
  import { spawn } from "node:child_process";
8
- import { createHash } from "node:crypto";
9
8
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
10
9
  import { existsSync } from "node:fs";
11
10
  import { join, relative, resolve as resolvePath } from "node:path";
12
11
  import { pathToFileURL } from "node:url";
13
12
  import { parseArgs as nodeParseArgs } from "node:util";
14
- import { discoverEvals, discoverExperiments, makeFilter } from "./runner/discover.ts";
13
+ import { discoverEvals, discoverExperiments } from "./runner/discover.ts";
14
+ import { browsableExperimentPaths, matchExperimentSelector } from "./shared/aggregate.ts";
15
15
  import { runEvals, type AgentRun } from "./runner/run.ts";
16
16
  import { planCarry } from "./runner/fingerprint.ts";
17
+ import { fingerprintEvalsFilter, resolveExperimentEvals, selectedEvalsForRun } from "./runner/eval-selection.ts";
17
18
  import { failureDetailFromResult } from "./runner/feedback/failure.ts";
18
19
  import { stopAllSandboxes, liveSandboxCount } from "./sandbox/registry.ts";
20
+ import { drainExperimentTeardowns } from "./runner/experiment-cleanup-registry.ts";
21
+ import { CLEANUP_TIMEOUT_MS } from "./runner/cleanup-timeout.ts";
19
22
  import { evalLevelStats } from "./shared/verdict.ts";
20
23
  import { prepareRunSandboxes, resolvedSandboxRecommendedConcurrency } from "./runner/sandbox-selection.ts";
21
24
  import { Json, JUnit } from "./runner/reporters/json.ts";
@@ -47,14 +50,13 @@ import {
47
50
  // --report 用的那份(dist/report/**,见 tsconfig.report-build.json)是同一个模块实例——
48
51
  // `unique symbol` 品牌与 class 的 instanceof 都按声明处的模块身份判定,raw src 和编译产物
49
52
  // 是两份不同源码位置,即使运行时同名同形,TS 类型与 instanceof 都不认。
50
- import { ReportLoadError } from "../dist/report/load.js";
53
+ import { ReportLoadError } from "../dist/report/runtime/load.js";
51
54
  import { runShow } from "./show/index.ts";
52
55
  import { t } from "./i18n/index.ts";
53
56
  import { formatThrown, upsertManagedBlock } from "./util.ts";
54
57
  import type {
55
58
  CompletionStatus,
56
59
  Config,
57
- DiscoveredExperiment,
58
60
  ReporterError,
59
61
  ReporterRegistration,
60
62
  RunCompletion,
@@ -135,9 +137,9 @@ const FLAG_OPTIONS = {
135
137
  timeout: { type: "string" },
136
138
  /** 整次运行的预算上限(美元)。 */
137
139
  budget: { type: "string" },
138
- /** `exp` 命令专用:跑完留下 failed/errored attempt 的沙箱现场(= `--keep-sandbox=failed`);`--keep-sandbox=all` 连 passed 也留。事后用 `niceeval sandbox list/enter/stop` 查看与销毁。 */
140
+ /** `exp` 命令专用:跑完留下 failed/errored attempt 的 Sandbox 现场(= `--keep-sandbox=failed`);`--keep-sandbox=all` 连 passed 也留。事后用 `niceeval sandbox list/enter/stop` 查看与销毁。 */
139
141
  "keep-sandbox": { type: "boolean" },
140
- /** `sandbox stop` 专用:销毁全部留存沙箱。 */
142
+ /** `sandbox stop` 专用:销毁全部留存 Sandbox。 */
141
143
  all: { type: "boolean" },
142
144
  /** `sandbox diff` 专用:只看某个 send 窗口(如 `--window s1/t2`);省略输出全部窗口的串联视图。 */
143
145
  window: { type: "string" },
@@ -155,7 +157,7 @@ const FLAG_OPTIONS = {
155
157
  out: { type: "string" },
156
158
  /** `view` 命令专用:指定本地服务器监听端口。 */
157
159
  port: { type: "string" },
158
- // show 的证据切面 / 时间轴 / 报告装载(docs-site/zh/how-to/viewing-results.mdx)。
160
+ // show 的证据切面 / 时间轴 / 报告装载(docs-site/zh/tutorials/viewing-results.mdx)。
159
161
  // 证据切面只认 `@<locator>`(或收窄到单个 eval 的前缀)选出的那一个 attempt——不再有
160
162
  // 数字 `--attempt`,选哪个 attempt 由 locator 精确指名,不是「先选 eval 再挑第几次」。
161
163
  /** `show` 命令专用:该 attempt 运行时保存的 Eval 源码,gate/soft 断言标回源码行(证据切面)。 */
@@ -170,7 +172,7 @@ const FLAG_OPTIONS = {
170
172
  diff: { type: "boolean" },
171
173
  /** `show` 命令专用:执行时间轴——对匹配的每个 experiment × eval 分节,逐 attempt 列时间 / verdict / 摘要 / 耗时 / 成本 / locator;与 `--report` 互斥。 */
172
174
  history: { type: "boolean" },
173
- /** `show` / `view` 命令专用:按路径段前缀收窄 experiment(与 `niceeval exp` 位置参数同一套匹配);组名会选中组内全部配置。`view --out` 时同一收窄决定出站内容。 */
175
+ /** `show` / `view` 命令专用:按路径段前缀收窄 experiment(与 `niceeval exp` 位置参数同一套匹配);目录路径会选中其下全部配置。`view --out` 时同一收窄决定出站内容。 */
174
176
  exp: { type: "string" },
175
177
  /** `show` / `view` / `sandbox enter|list|stop` 共用:结果根目录(`.niceeval` 之外的另一个根,如 `copySnapshots` 产出的发布根)。 */
176
178
  results: { type: "string" },
@@ -188,8 +190,9 @@ const FLAG_OPTIONS = {
188
190
  force: { type: "boolean" },
189
191
  /** CI 中推荐使用:让软阈值(`soft`)失败也计入整条 eval 的 verdict。 */
190
192
  strict: { type: "boolean" },
191
- /** 某个 eval 的一次 attempt 通过后,停止该 eval 剩余的 attempts。 */
193
+ /** 某个 eval 的一次 attempt 通过后,停止该 eval 剩余的 attempts;省略默认关(`runs` 默认跑满、测完整通过率)。 */
192
194
  "early-exit": { type: "boolean" },
195
+ /** 强制关闭首过即停,即使实验文件里写了 `earlyExit: true`。 */
193
196
  "no-early-exit": { type: "boolean" },
194
197
  /** `view` 命令专用:启动后自动打开浏览器(默认行为)。 */
195
198
  open: { type: "boolean" },
@@ -462,32 +465,6 @@ async function openBrowser(url: string): Promise<boolean> {
462
465
  });
463
466
  }
464
467
 
465
- function evalsFilterFromExperiment(
466
- evals: DiscoveredExperiment["evals"],
467
- patterns: string[],
468
- ): (id: string) => boolean {
469
- const patternFilter = makeFilter(patterns);
470
- let expFilter: (id: string) => boolean = () => true;
471
- if (Array.isArray(evals)) expFilter = makeFilter(evals);
472
- else if (typeof evals === "function") expFilter = evals;
473
- return (id) => expFilter(id) && patternFilter(id);
474
- }
475
-
476
- /**
477
- * evals 过滤器的指纹(进 ExperimentRunInfo.evalFilterFingerprint,供「配置没变」判断):
478
- * 数组按内容、函数按函数体哈希;CLI 追加的位置参数前缀一并计入。不存过滤器本身——
479
- * 求值结果在 selectedEvalIds(见 runEvals)。
480
- */
481
- function fingerprintEvalsFilter(evals: DiscoveredExperiment["evals"], patterns: string[]): string {
482
- const basis =
483
- evals === undefined || evals === "*"
484
- ? "*"
485
- : Array.isArray(evals)
486
- ? JSON.stringify([...evals].sort())
487
- : evals.toString();
488
- return createHash("sha256").update(JSON.stringify({ basis, patterns })).digest("hex").slice(0, 16);
489
- }
490
-
491
468
  /**
492
469
  * run 结束后把 coordinator 累计的诊断折成 `RunCompletion`(见 docs/feature/experiments/cli.md
493
470
  * 「运行完成状态不只看 verdict 计数」)。只读已经真实发生过的诊断,不额外发明信号:
@@ -663,7 +640,7 @@ async function main(): Promise<void> {
663
640
 
664
641
  const agentRuns: AgentRun[] = [];
665
642
  let experimentSelection = t("cli.all");
666
- let availableExperimentGroups = t("cli.none");
643
+ let availableExperimentPaths = t("cli.none");
667
644
 
668
645
  if (command === "exp") {
669
646
  if (flags.agent || flags.model) {
@@ -679,16 +656,13 @@ async function main(): Promise<void> {
679
656
  const expArg = positionals[0];
680
657
  const extraPatterns = positionals.slice(1);
681
658
  experimentSelection = positionals.join(" ") || t("cli.all");
682
- availableExperimentGroups = [...new Set(experiments.map((experiment) => experiment.group || experiment.id))]
683
- .sort()
684
- .join(", ") || t("cli.none");
685
- const selected = expArg
686
- ? experiments.filter((e) => e.group === expArg || e.id === expArg || e.id.startsWith(expArg + "/"))
687
- : experiments;
659
+ availableExperimentPaths = browsableExperimentPaths(experiments.map((e) => e.id)).join(", ") || t("cli.none");
660
+ const selectedIds = expArg ? new Set(matchExperimentSelector(experiments.map((e) => e.id), expArg)) : undefined;
661
+ const selected = selectedIds ? experiments.filter((e) => selectedIds.has(e.id)) : experiments;
688
662
  if (selected.length === 0) {
689
663
  process.stderr.write(t("cli.experiment.noMatch", {
690
664
  arg: expArg ?? t("cli.all"),
691
- experiments: experiments.map((e) => e.id).join(", ") || t("cli.none"),
665
+ experiments: availableExperimentPaths,
692
666
  }));
693
667
  // show / view 是顶层命令。只有同名 experiment 确实不存在时才纠错,不能抢占合法 id。
694
668
  if (expArg === "show" || expArg === "view") {
@@ -707,33 +681,45 @@ async function main(): Promise<void> {
707
681
  }
708
682
  for (const exp of selected) {
709
683
  // 一个实验 = 一个配置(单 model)。跨模型对比写多个实验文件,各钉一个 model。
684
+ // evals 谓词在这里对本次 invocation 的候选 eval 各求值一次;下游(dry-run、sandbox 查表、
685
+ // fingerprint/carry、attempt 展开)只消费 selectedEvalIds,不重新调用谓词
686
+ // (见 docs/feature/experiments/library.md「evals」)。
687
+ const { selectedEvalIds } = resolveExperimentEvals({
688
+ experimentId: exp.id,
689
+ selector: exp.evals,
690
+ cliPatterns: extraPatterns,
691
+ evals,
692
+ });
710
693
  agentRuns.push({
711
694
  agent: exp.agent,
712
695
  model: exp.model,
713
696
  reasoningEffort: exp.reasoningEffort,
714
697
  flags: exp.flags ?? {},
715
698
  runs: flags.runs ?? envNumber("NICEEVAL_RUNS") ?? exp.runs ?? 1,
716
- earlyExit: flags.earlyExit ?? exp.earlyExit ?? true,
699
+ earlyExit: flags.earlyExit ?? exp.earlyExit ?? false,
717
700
  sandbox: exp.sandbox ?? config.sandbox,
718
701
  timeoutMs: flags.timeout ?? envNumber("NICEEVAL_TIMEOUT") ?? exp.timeoutMs ?? config.timeoutMs,
719
702
  budget: flags.budget ?? envNumber("NICEEVAL_BUDGET") ?? exp.budget,
720
- evalFilter: evalsFilterFromExperiment(exp.evals, extraPatterns),
703
+ selectedEvalIds,
721
704
  experimentId: exp.id,
722
705
  description: exp.description,
706
+ labels: exp.labels,
723
707
  evalFilterFingerprint: fingerprintEvalsFilter(exp.evals, extraPatterns),
724
708
  strict: flags.strict,
725
709
  // 实验级并发上限:随 AgentRun 进调度器按实验单独限流(runner 两级信号量),
726
710
  // 不再取所有选中实验的最小值钳全局——那会让一个串行实验拖慢整批基线。
727
711
  maxConcurrency: exp.maxConcurrency,
712
+ setup: exp.setup,
713
+ teardown: exp.teardown,
728
714
  });
729
715
  }
730
716
  } else {
731
717
  // 裸 run / `niceeval <eval>` 不再执行。运行配置必须来自 experiments/,
732
718
  // 这样 agent/model/flags/runs/budget 与结果聚合都有可签入的身份。
733
719
  const experiments = await discoverExperiments(cwd);
734
- const asExp = experiments.filter((e) =>
735
- positionals.some((p) => e.group === p || e.id === p || e.id.startsWith(p + "/")),
736
- );
720
+ const ids = experiments.map((e) => e.id);
721
+ const matchedIds = new Set(positionals.flatMap((p) => matchExperimentSelector(ids, p)));
722
+ const asExp = experiments.filter((e) => matchedIds.has(e.id));
737
723
  process.stderr.write(t("cli.run.experimentRequired"));
738
724
  if (asExp.length > 0) {
739
725
  process.stderr.write(t("cli.run.experimentRequiredHint", {
@@ -758,14 +744,14 @@ async function main(): Promise<void> {
758
744
 
759
745
  // matchedByRun[i] 对应 agentRuns[i] 匹配到的 eval 集合;--dry 预览与真正开跑时的
760
746
  // RunFeedbackPlan(总量、去重 eval 数)共用同一份计算,不重复过滤一遍。
761
- const matchedByRun = agentRuns.map((run) => evals.filter((e) => run.evalFilter(e.id)));
747
+ const matchedByRun = agentRuns.map((run) => selectedEvalsForRun(evals, run));
762
748
  const totalRuns = agentRuns.reduce((sum, run, i) => sum + matchedByRun[i]!.length * run.runs, 0);
763
749
  const uniqueEvalIds = new Set(matchedByRun.flat().map((e) => e.id));
764
750
 
765
751
  if (totalRuns === 0) {
766
752
  process.stderr.write(t("cli.experiment.noEvalsSelected", {
767
753
  selection: experimentSelection,
768
- experiments: availableExperimentGroups,
754
+ experiments: availableExperimentPaths,
769
755
  }));
770
756
  process.exit(1);
771
757
  }
@@ -873,13 +859,34 @@ async function main(): Promise<void> {
873
859
  // 3 次:真不耐烦了,硬退(此时多半已无可清理的)。
874
860
  const ctrl = new AbortController();
875
861
  let signalCount = 0;
876
- // 兜底强清 + 退出:只跑一次,带超时(stopAllSandboxes 内每个 stop 各自有超时)。先停 dashboard
877
- // tick/动态区域(coordinator.stopDynamic()),避免硬退时终端卡在半帧 ANSI 状态。
862
+ // 强清 = 加速收尾,不是绕过收尾(docs/cli.md「中断:三级响应」)。顺序:先强停沙箱(卡在
863
+ // 沙箱 I/O 上的收尾立刻失败返回),然后事件驱动收口——并发等待「在飞收尾链 settle」与
864
+ // 「实验级 teardown 注册表排空」(drain 会启动未启动的、等待在飞的同一 memoized promise),
865
+ // 两者都 settle 即退。兜底上限从单可调用体清理上限推导(docs 声明的不等式:provider stop 8s
866
+ // < 看门狗 < CLEANUP_TIMEOUT_MS ≤ 本上限),不是第 2 级的语义——settle 才是——只拦
867
+ // 「收尾可调用体绕过了自己的超时」的失守病态,到点放弃退出(职责同第 3 级硬退)。
868
+ // 只跑一次;先停 dashboard 的 tick/动态区域(coordinator.stopDynamic()),
869
+ // 避免硬退时终端卡在半帧 ANSI 状态。
870
+ const FORCE_SETTLE_CAP_MS = CLEANUP_TIMEOUT_MS * 2;
871
+ let runInFlight: Promise<unknown> | undefined;
878
872
  let forcing = false;
879
873
  const forceCleanupAndExit = (code: number) => {
880
874
  if (forcing) return;
881
875
  forcing = true;
882
- void Promise.all([coordinator.stopDynamic(), stopAllSandboxes()]).finally(() => process.exit(code));
876
+ void (async () => {
877
+ await Promise.all([coordinator.stopDynamic(), stopAllSandboxes()]);
878
+ const settled = Promise.allSettled([
879
+ ...(runInFlight ? [runInFlight] : []),
880
+ drainExperimentTeardowns(),
881
+ ]);
882
+ await Promise.race([
883
+ settled.then(() => {}),
884
+ new Promise<void>((r) => {
885
+ setTimeout(r, FORCE_SETTLE_CAP_MS).unref();
886
+ }),
887
+ ]);
888
+ process.exit(code);
889
+ })();
883
890
  };
884
891
  for (const sig of ["SIGINT", "SIGTERM"] as const) {
885
892
  process.on(sig, () => {
@@ -887,13 +894,16 @@ async function main(): Promise<void> {
887
894
  if (signalCount === 1) {
888
895
  reportActivity(t("cli.interruptCleanup").trimEnd());
889
896
  ctrl.abort();
890
- // 看门狗:graceful 清理 12s 还没让进程自己收口,就强清兜底。
897
+ // 看门狗:graceful 清理迟迟没让进程自己收口,就强清兜底。取值在 docs/cli.md 声明的
898
+ // 不等式链里:> provider stop 超时(8s,一次正常停容器超时后才升级,不误伤),
899
+ // < CLEANUP_TIMEOUT_MS(30s)。
900
+ const GRACEFUL_WATCHDOG_MS = 12_000;
891
901
  setTimeout(() => {
892
902
  if (liveSandboxCount() > 0) {
893
903
  reportActivity(t("cli.fallbackCleanupTimeout").trimEnd());
894
904
  forceCleanupAndExit(130);
895
905
  }
896
- }, 12_000).unref();
906
+ }, GRACEFUL_WATCHDOG_MS).unref();
897
907
  } else if (signalCount === 2) {
898
908
  reportActivity(t("cli.forceCleanupExit").trimEnd());
899
909
  forceCleanupAndExit(130);
@@ -921,7 +931,7 @@ async function main(): Promise<void> {
921
931
 
922
932
  let summary: RunSummary;
923
933
  try {
924
- summary = await runEvals({
934
+ const inFlight = runEvals({
925
935
  config,
926
936
  evals,
927
937
  agentRuns,
@@ -933,15 +943,20 @@ async function main(): Promise<void> {
933
943
  keepSandbox: flags.keepSandbox,
934
944
  niceevalRoot: resolvePath(cwd, ".niceeval"),
935
945
  });
946
+ // 交给强清路径一个可等待的收尾句柄:二次中断/看门狗强清时先有界等它收口,让在飞的
947
+ // teardown 链跑完,而不是 process.exit 把它们连同进程一起杀掉。
948
+ runInFlight = inFlight;
949
+ summary = await inFlight;
936
950
  } catch (e) {
937
951
  // 真崩溃前先撤下 dashboard,不让半帧 ANSI 状态和下面 main().catch 打印的错误交织。
938
952
  await coordinator.stopDynamic();
939
953
  throw e;
940
954
  }
941
955
 
942
- // 正常返回(含被中断后走部分汇总)后再兜一刀:Scope finalizer 没停掉的残留沙箱在这里强清。
943
- // 跑顺利时登记表已空,是 no-op。
956
+ // 正常返回(含被中断后走部分汇总)后再兜一刀:Scope finalizer 没停掉的残留沙箱、没被运行
957
+ // 路径消费的实验级 cleanup 在这里强清。跑顺利时两份登记表都已空,是 no-op。
944
958
  await stopAllSandboxes();
959
+ await drainExperimentTeardowns();
945
960
 
946
961
  // completion 要先算好,--json/--junit 是否"这次真的写出"才有依据(见下)。
947
962
  const completion = assembleRunCompletion(coordinator.state);
@@ -979,7 +994,8 @@ async function main(): Promise<void> {
979
994
 
980
995
  main().catch(async (e) => {
981
996
  process.stderr.write(t("cli.error", { error: formatThrown(e) }));
982
- // 真·崩溃路径也别留孤儿:强清还活着的沙箱(带超时),再退。
997
+ // 真·崩溃路径也别留孤儿:强清还活着的沙箱(带超时)、排空实验级 cleanup 注册表,再退。
983
998
  await stopAllSandboxes();
999
+ await drainExperimentTeardowns();
984
1000
  process.exit(2);
985
1001
  });
@@ -140,35 +140,35 @@ export interface RespondAnswer {
140
140
  readonly text?: string;
141
141
  }
142
142
 
143
- /** eval 作者可见的受限沙箱视图:能执行命令 / 文件 IO / 读最终 diff,但不能 stop。 */
143
+ /** 评估用例作者可见的受限 Sandbox 视图:能执行命令 / 文件 IO / 读最终 diff,但不能 stop。 */
144
144
  export interface SandboxHandle {
145
- /** 沙箱内的工作目录绝对路径。 */
145
+ /** Sandbox 内的工作目录绝对路径。 */
146
146
  readonly workdir: string;
147
- /** 在沙箱里执行一条命令(argv 形式,不经 shell)。装系统依赖等需要 root 时传 `{ root: true }`。 */
147
+ /** 在 Sandbox 里执行一条命令(argv 形式,不经 shell)。装系统依赖等需要 root 时传 `{ root: true }`。 */
148
148
  runCommand(cmd: string, args?: string[], opts?: CommandOptions): Promise<CommandResult>;
149
- /** 在沙箱里执行一段 shell 脚本(经 shell 解释,支持管道 / 重定向)。 */
149
+ /** Sandbox 里执行一段 shell 脚本(经 shell 解释,支持管道 / 重定向)。 */
150
150
  runShell(script: string, opts?: CommandOptions): Promise<CommandResult>;
151
- /** 读沙箱内某文件此刻的文本内容(实时读,不是 diff 快照)。 */
151
+ /** 读 Sandbox 内某文件此刻的文本内容(实时读,不是 diff 快照)。 */
152
152
  readFile(path: string): Promise<string>;
153
- /** 沙箱内某路径此刻是否存在。 */
153
+ /** Sandbox 内某路径此刻是否存在。 */
154
154
  fileExists(path: string): Promise<boolean>;
155
- /** 按选项批量读回沙箱里的源文件,供比对 / judge 用材料。 */
155
+ /** 按选项批量读回 Sandbox 里的源文件,供比对 / judge 用材料。 */
156
156
  readSourceFiles(opts?: ReadSourceFilesOptions): Promise<SourceFiles>;
157
- /** 把一组内容写进沙箱(路径相对 targetDir,默认 workdir)。 */
157
+ /** 把一组内容写进 Sandbox(路径相对 targetDir,默认 workdir)。 */
158
158
  writeFiles(files: Record<string, string>, targetDir?: string): Promise<void>;
159
- /** 把一批内存中的文件上传进沙箱。 */
159
+ /** 把一批内存中的文件上传进 Sandbox。 */
160
160
  uploadFiles(files: SandboxFile[], targetDir?: string): Promise<void>;
161
- /** 把本地目录整体上传进沙箱;`opts.ignore` 排除指定路径。 */
161
+ /** 把本地目录整体上传进 Sandbox;`opts.ignore` 排除指定路径。 */
162
162
  uploadDirectory(localDir: string, targetDir?: string, opts?: { ignore?: string[] }): Promise<void>;
163
- /** 从沙箱下载某文件内容。 */
163
+ /** Sandbox 下载某文件内容。 */
164
164
  downloadFile(path: string): Promise<Buffer>;
165
- /** 把一段内容上传成沙箱里的单个文件。 */
165
+ /** 把一段内容上传成 Sandbox 里的单个文件。 */
166
166
  uploadFile(path: string, content: Buffer): Promise<void>;
167
- /** 沙箱 provider 分配的实例 id,用于排查 / 关联日志。 */
167
+ /** Sandbox provider 分配的实例 id,用于排查 / 关联日志。 */
168
168
  readonly sandboxId: string;
169
169
  /** 相对 git 基线的最终 diff 视图(test() 跑完、finalize 前才落定)。 */
170
170
  readonly diff: DiffView;
171
- /** 取沙箱内某文件的最终内容,占位延迟到 finalize 才真正读取;只能配合 t.check 使用。 */
171
+ /** Sandbox 内某文件的最终内容,占位延迟到 finalize 才真正读取;只能配合 t.check 使用。 */
172
172
  file(path: string): string;
173
173
  /** 断言 diff 里某路径发生了变化(新增或修改)。 */
174
174
  fileChanged(path: string): AssertionHandle;
@@ -176,7 +176,7 @@ export interface SandboxHandle {
176
176
  fileDeleted(path: string): AssertionHandle;
177
177
  /** 断言 diff 的路径与内容都不匹配给定正则(常用来否定式检查不该出现的改动)。 */
178
178
  notInDiff(re: RegExp): AssertionHandle;
179
- /** 断言沙箱里执行过的命令都没有失败退出。 */
179
+ /** 断言 Sandbox 里执行过的命令都没有失败退出。 */
180
180
  noFailedShellCommands(): AssertionHandle;
181
181
  }
182
182
 
@@ -285,7 +285,7 @@ export interface TestContext {
285
285
  /** 本次 attempt 生效的实验 flags(experiment.flags 的只读视图;实验条件,非命令行开关)。 */
286
286
  readonly flags: Readonly<Record<string, unknown>>;
287
287
  /**
288
- * 作用域反馈:报告 eval 自己执行的长步骤(上传 fixture、跑构建……)。短命状态,scope 固定
288
+ * 作用域反馈:报告评估用例自己执行的长步骤(上传 Fixture、跑构建……)。短命状态,scope 固定
289
289
  * 为 `eval.run`;只报告不断言(见 docs/feature/eval/library/context.md「向运行反馈长步骤」)。
290
290
  */
291
291
  progress(update: ProgressUpdate): void;
@@ -296,18 +296,18 @@ export interface TestContext {
296
296
  diagnostic(input: DiagnosticInput): void;
297
297
  /** `progress({ message: msg })` 的别名(调试日志),不出现在最终结果里。 */
298
298
  log(msg: string): void;
299
- /** 立即中止本 eval 并标记为 skipped(verdict / EvalResult.skipReason),reason 不能为空。 */
299
+ /** 立即中止本评估用例并标记为 skipped(verdict / EvalResult.skipReason),reason 不能为空。 */
300
300
  skip(reason: string): never;
301
301
 
302
302
  // 值断言
303
303
  /**
304
304
  * 对任意值跑一个 ValueAssertion,返回可链 `.gate()` / `.atLeast()` 的 AssertionHandle。
305
- * 打分延迟到 eval 结束后统一 finalize,调用本身同步、不抛错——不通过只是记一条失败断言,
306
- * 不会中止后续代码。要「不满足就立即中止 eval」用 require。
305
+ * 打分延迟到评估用例结束后统一 finalize,调用本身同步、不抛错——不通过只是记一条失败断言,
306
+ * 不会中止后续代码。要「不满足就立即中止评估用例」用 require。
307
307
  */
308
308
  check(value: unknown, assertion: ValueAssertion): AssertionHandle;
309
309
  /**
310
- * 对任意值跑一个 ValueAssertion,立即(await 时)求值;不满足就抛错中止整个 eval 剩余步骤
310
+ * 对任意值跑一个 ValueAssertion,立即(await 时)求值;不满足就抛错中止整个评估用例剩余步骤
311
311
  * (仍会把这条断言计入报告,不影响已记录的其它断言)。跟 check 的区别:check 只记录、
312
312
  * 从不抛错,打分留到最后统一算;require 当场判定、失败即中止,适合「前置条件不满足,
313
313
  * 后面写了也没意义」的场景。
@@ -352,7 +352,7 @@ export interface TestContext {
352
352
  eventsSatisfy(label: string, predicate: (events: readonly StreamEvent[]) => boolean): AssertionHandle;
353
353
 
354
354
  // 工作区 / 沙箱
355
- /** 受限沙箱视图:能执行命令 / 读写文件 / 看最终 diff,不能 stop 沙箱本身(见 SandboxHandle)。 */
355
+ /** 受限 Sandbox 视图:能执行命令 / 读写文件 / 看最终 diff,不能 stop Sandbox 本身(见 SandboxHandle)。 */
356
356
  readonly sandbox: SandboxHandle;
357
357
 
358
358
  // 效率 / 成本
package/src/define.ts CHANGED
@@ -67,6 +67,11 @@ export function defineExperiment(def: ExperimentDef): ExperimentDef {
67
67
  throw new Error(t("define.experimentIdRejected"));
68
68
  }
69
69
  if (!def.agent) throw new Error(t("define.experimentAgentRequired"));
70
+ // setup 是实验级生命周期钩子(整场一次,宿主机侧,见 runner/types.ts 的 ExperimentDef.setup);
71
+ // 传成非函数(如误把 sandbox 钩子对象塞进来)在解析时就报,不等到调度才炸。
72
+ if (def.setup !== undefined && typeof def.setup !== "function") {
73
+ throw new Error(t("define.experimentSetupNotFunction"));
74
+ }
70
75
  // flags 必须可 JSON 序列化(进结果快照的 ExperimentRunInfo.flags):解析时即校验,
71
76
  // 非 JSON 值(函数 / undefined / 循环引用 / bigint)直接报错,不等到落盘才炸。
72
77
  if (def.flags !== undefined) {
@@ -76,6 +81,14 @@ export function defineExperiment(def: ExperimentDef): ExperimentDef {
76
81
  }
77
82
  }
78
83
  }
84
+ // labels 是报告归类坐标(进 ExperimentRunInfo.labels,不透传 ctx/t):值域 string | number,
85
+ // 解析时即校验,布尔 / 对象 / NaN 直接报错,不等到落盘或报告分组才炸。
86
+ if (def.labels !== undefined) {
87
+ for (const [key, value] of Object.entries(def.labels)) {
88
+ const ok = typeof value === "string" || (typeof value === "number" && Number.isFinite(value));
89
+ if (!ok) throw new Error(t("define.experimentLabelInvalid", { key }));
90
+ }
91
+ }
79
92
  return def;
80
93
  }
81
94
 
package/src/i18n/en.ts CHANGED
@@ -60,9 +60,16 @@ export const en = {
60
60
  "cli.flag.invalidOutput": "Flag --output expects one of auto|human|agent|ci, got \"{{value}}\".\n",
61
61
  "runner.budgetUnenforceable":
62
62
  "budget for {{budgetKey}}: several attempts completed without any cost data (agent reports no usage and the model is not in the price table) — the budget cannot be enforced for this agent; continuing without the guard.\n",
63
+ "runner.experimentTeardownFailed":
64
+ "teardown for experiment {{experimentId}} failed: {{message}}. Results are unaffected, but host-side resources started by this experiment may not have been released; check manually.\n",
65
+ "runner.cleanupTimeout": "cleanup timed out after {{timeoutMs}}ms\n",
66
+ "runner.setupReturnedCleanup":
67
+ "{{layer}} returned a function. setup does not carry cleanup and the returned value will not be executed — put the cleanup in the paired teardown of the same layer ({{hint}}); see the experiments tutorial on docs-site or docs/runner.md.\n",
68
+ "runner.experimentTeardownLate":
69
+ "experiment {{experimentId}}'s teardown was not triggered by the normal countdown path; it has been executed by the end-of-run sweep instead. Results are unaffected; seeing this line means an unlocated intermittent scheduling issue fired — please record this run in the memory ledger.\n",
63
70
  "judge.modelMissing":
64
71
  "No judge model configured. Set it in defineConfig({ judge: { model: \"...\" } }), the eval's judge config, or the NICEEVAL_JUDGE_MODEL environment variable (there is no built-in default model).\n" +
65
- " Docs: node_modules/niceeval/docs-site/zh/how-to/scoring-guide.mdx",
72
+ " Docs: node_modules/niceeval/docs-site/zh/tutorials/scoring-guide.mdx",
66
73
  "loaders.yamlMissing":
67
74
  "loadYaml(\"{{path}}\") needs a YAML parser: run `pnpm add yaml` first (or switch to loadJson with a JSON dataset).",
68
75
  "cli.flag.parseError": "{{message}}\nRun `niceeval --help` for usage.\n",
@@ -70,7 +77,7 @@ export const en = {
70
77
  "cli.help":
71
78
  "niceeval — agent-native evals\n\n" +
72
79
  "Usage:\n" +
73
- " niceeval exp [group|experiment] [eval-id-prefix…] run experiments\n" +
80
+ " niceeval exp [path|experiment] [eval-id-prefix…] run experiments\n" +
74
81
  " niceeval show [eval-id-prefix… | @<locator>] read results in the terminal\n" +
75
82
  " bare: current verdicts per experiment (composed across runs), each row\n" +
76
83
  " with a compact attempt index (locator + failure reason)\n" +
@@ -121,10 +128,14 @@ export const en = {
121
128
  "cli.eval.noMatchKnown": "Discovered {{count}} evals: {{evals}}\n",
122
129
  "cli.exp.agentModelFlagUnsupported": "experiment runs do not support --agent / --model. Add or copy an experiment file and change its model instead.\n",
123
130
  "cli.exp.viewerFlagUnsupported": "`{{flag}}` only applies to niceeval {{command}}, not niceeval exp.\n",
124
- "cli.experiment.noMatch": "No experiment matched: {{arg}}. Discovered: {{experiments}}\n",
131
+ "cli.experiment.noMatch":
132
+ "No experiment matched: {{arg}}. Available paths: {{experiments}}.\n" +
133
+ "Run `niceeval exp <path> --dry` to preview a plan.\n",
125
134
  "cli.experiment.viewerCommandHint": "Did you mean: niceeval {{command}}{{args}}\n",
126
- "cli.experiment.noEvalsSelected": "No evals selected: {{selection}} matched 0 evals. Available experiments: {{experiments}}.\n",
127
- "cli.experimentGroup": " group",
135
+ "cli.experiment.noEvalsSelected":
136
+ "No evals selected: {{selection}} matched 0 evals. Available eval prefixes: {{experiments}}.\n" +
137
+ "Run `niceeval exp {{selection}} --dry` to see what it covers, or drop the eval filter to run every eval selected by those experiments.\n",
138
+ "cli.experimentGroup": " path",
128
139
  "cli.fallbackCleanupTimeout": "\ngraceful cleanup timed out; force-cleaning sandboxes...\n",
129
140
  "cli.forceCleanupExit": "\nForce-cleaning sandboxes and exiting...\n",
130
141
  "cli.init.done": "Ready: evals/, niceeval.config.ts, and the niceeval agent-rules block in AGENTS.md (points coding agents at node_modules/niceeval/docs-site/zh).\n",
@@ -135,8 +146,8 @@ export const en = {
135
146
  "cli.pressCtrlC": "Press Ctrl+C to exit.\n",
136
147
  "cli.resultsPath": "Structured results: {{path}} (snapshot.json + per-attempt result.json / events.json / trace.json / diff.json)\n",
137
148
  "cli.run.experimentRequired":
138
- "Run evals through an experiment: use `niceeval exp [group|config] [eval id prefix]`.\n" +
139
- " Docs: node_modules/niceeval/docs-site/zh/how-to/write-experiment.mdx\n",
149
+ "Run evals through an experiment: use `niceeval exp [path|config] [eval id prefix]`.\n" +
150
+ " Docs: node_modules/niceeval/docs-site/zh/tutorials/write-experiment.mdx\n",
140
151
  "cli.run.experimentRequiredHint": "Hint: \"{{pattern}}\" is an experiment{{kind}}; you probably meant: niceeval exp {{pattern}}\n",
141
152
  "cli.run.experimentRequiredKnown": "Discovered experiments: {{experiments}}\n",
142
153
  "cli.unimplemented": "Command \"{{command}}\" is not implemented yet (MVP).\n",
@@ -147,7 +158,7 @@ export const en = {
147
158
  "cli.view.url": "niceeval view: {{url}}\n",
148
159
  "context.capabilityMissing":
149
160
  "Agent \"{{agent}}\" is not sandbox-backed (built with defineSandboxAgent), so t.{{method}} is unavailable. Use an agent built with defineSandboxAgent, or drop this assertion.\n" +
150
- " Docs: node_modules/niceeval/docs-site/zh/how-to/sandbox-agent.mdx",
161
+ " Docs: node_modules/niceeval/docs-site/zh/tutorials/sandbox-agent.mdx",
151
162
  "context.skipEmpty": "skip() requires a non-empty reason.",
152
163
  "context.turnFailed": "This send returned failed (turn status = failed): {{message}}",
153
164
  "context.turnFailedDefault": "This send returned failed (turn status = failed)",
@@ -157,6 +168,8 @@ export const en = {
157
168
  "define.evalTestRequired": "defineEval requires an async test(t) function.",
158
169
  "define.experimentAgentRequired": "defineExperiment requires agent.",
159
170
  "define.experimentFlagNotJson": "experiment.flags.{{key}} is not JSON-serializable (functions / undefined / cycles / bigint are not allowed); flags are persisted verbatim into result snapshots and must be plain JSON.",
171
+ "define.experimentLabelInvalid": "experiment.labels.{{key}} must be a string or a finite number; labels are report-side grouping coordinates persisted verbatim into result snapshots.",
172
+ "define.experimentSetupNotFunction": "experiment.setup must be a function ((ctx) => void); use experiment.teardown for cleanup; to prepare the in-sandbox environment per experiment, chain .setup() hooks on the sandbox spec instead.",
160
173
  "define.experimentIdRejected": "defineExperiment does not accept id; ids are derived from file paths.",
161
174
  "define.sandboxAgentNameRequired": "defineSandboxAgent requires name.",
162
175
  "define.sandboxCreateRequired": "defineSandbox requires a create() function.",
@@ -169,7 +182,7 @@ export const en = {
169
182
  "docker.unsupportedRuntime": "Unsupported runtime: {{runtime}}",
170
183
  "feedback.human.active": "ACTIVE",
171
184
  "feedback.human.budgetExhausted": "budget exhausted for {{experimentId}} (spent {{spent}}, unstarted {{unstarted}})",
172
- "feedback.human.compare": "Compare: niceeval view {{group}}",
185
+ "feedback.human.compare": "Compare: niceeval view",
173
186
  "feedback.human.counts": "{{total}} total · {{reused}} reused · {{running}} running · {{queued}} queued · {{completed}} completed",
174
187
  "feedback.human.diffHint": "Diff: niceeval show {{locator}} --diff",
175
188
  "feedback.human.evalHint": "Eval: niceeval show {{locator}} --source",
@@ -194,6 +207,10 @@ export const en = {
194
207
  "feedback.phase.evalSetup": "eval setup",
195
208
  "feedback.phase.sandboxCreate": "creating sandbox",
196
209
  "feedback.phase.sandboxQueue": "queued for sandbox",
210
+ "feedback.phase.experimentSetup": "experiment setup",
211
+ "feedback.phase.experimentTeardown": "experiment teardown",
212
+ "feedback.human.hookDone": "done",
213
+ "feedback.human.hookFailed": "failed",
197
214
  "feedback.phase.sandboxSetup": "sandbox setup",
198
215
  "feedback.phase.scoring": "scoring",
199
216
  "feedback.phase.teardown": "cleaning up",
@@ -273,13 +290,11 @@ export const en = {
273
290
  "runner.startSandboxTeardown": "sandbox teardown (environment provisioning hooks)...",
274
291
  "runner.timeout": "attempt timed out ({{timeoutMs}}ms)\nRecent progress:\n{{recentLogs}}",
275
292
  "runner.traceSelected": " -> kept {{count}} semantic spans",
276
- "runner.resumeCarry": " · reusing {{carried}} settled results from last run, re-running {{retry}} evals\n",
277
- "runner.resumeCarryDetail": " carried [{{experiment}}] {{evals}}\n",
278
293
  "runner.useRemoteAgent": "using remote agent (no sandbox created)...",
279
294
  "sandbox.providerNotImplemented": "{{provider}} sandbox provider is not implemented; use docker, vercel, or e2b",
280
295
  "sandbox.missingSpec":
281
296
  "sandbox agent needs a sandbox, but none was given. niceeval no longer picks a default — set `sandbox` in defineExperiment()/defineConfig() to dockerSandbox() / vercelSandbox() / e2bSandbox() (import from \"niceeval/sandbox\").\n" +
282
- " Docs: node_modules/niceeval/docs-site/zh/how-to/sandbox-providers.mdx",
297
+ " Docs: node_modules/niceeval/docs-site/zh/tutorials/sandbox-providers.mdx",
283
298
  "sandbox.dependencyMissing.docker": "Docker sandbox requires 'dockerode'. Install it with: pnpm add dockerode @types/dockerode",
284
299
  "sandbox.dependencyMissing.e2b": "E2B sandbox requires 'e2b'. Install it with: pnpm add e2b",
285
300
  "sandbox.dependencyMissing.vercel": "Vercel sandbox requires '@vercel/sandbox'. Install it with: pnpm add @vercel/sandbox",