niceeval 0.9.1 → 0.10.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 (428) 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 +6 -0
  17. package/dist/report/components/attempt-detail/AttemptConversation.js +34 -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 +27 -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 +254 -0
  38. package/dist/report/components/attempt-detail/faces.d.ts +14 -0
  39. package/dist/report/components/attempt-detail/faces.js +235 -0
  40. package/dist/report/components/attempt-detail/index.d.ts +38 -0
  41. package/dist/report/components/attempt-detail/index.js +527 -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 +3 -3
  49. package/dist/report/{react → components/entity-lists}/ExperimentList.js +10 -10
  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 +174 -0
  54. package/dist/report/components/entity-lists/index.d.ts +51 -0
  55. package/dist/report/components/entity-lists/index.js +165 -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 +26 -23
  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} +11 -5
  126. package/dist/report/{format.js → model/format.js} +48 -4
  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} +140 -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 +149 -23
  148. package/dist/sandbox/docker.d.ts +6 -0
  149. package/dist/sandbox/types.d.ts +23 -21
  150. package/dist/scoring/display.d.ts +7 -2
  151. package/dist/scoring/display.js +42 -4
  152. package/dist/scoring/types.d.ts +3 -3
  153. package/dist/shared/aggregate.d.ts +14 -0
  154. package/dist/shared/aggregate.js +31 -0
  155. package/dist/shared/types.d.ts +6 -1
  156. package/docs-site/images/logo-dark.svg +7 -0
  157. package/docs-site/images/logo.svg +6 -5
  158. package/docs-site/zh/README.md +8 -7
  159. package/docs-site/zh/examples/ai-agent-application.mdx +5 -5
  160. package/docs-site/zh/examples/coding-agent-extensions.mdx +4 -4
  161. package/docs-site/zh/examples/index.mdx +3 -3
  162. package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +1 -1
  163. package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
  164. package/docs-site/zh/examples/integrations/codex-sdk.mdx +3 -3
  165. package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
  166. package/docs-site/zh/examples/integrations/pi-sdk.mdx +2 -2
  167. package/docs-site/zh/explanation/adapter.mdx +19 -19
  168. package/docs-site/zh/explanation/assert.mdx +9 -9
  169. package/docs-site/zh/explanation/drive.mdx +4 -4
  170. package/docs-site/zh/explanation/evals.mdx +8 -8
  171. package/docs-site/zh/explanation/experiment.mdx +8 -8
  172. package/docs-site/zh/explanation/hitl.mdx +13 -13
  173. package/docs-site/zh/explanation/judge.mdx +3 -3
  174. package/docs-site/zh/explanation/overview.mdx +7 -7
  175. package/docs-site/zh/explanation/runner.mdx +11 -11
  176. package/docs-site/zh/explanation/tier.mdx +6 -6
  177. package/docs-site/zh/index.mdx +14 -14
  178. package/docs-site/zh/introduction.mdx +13 -16
  179. package/docs-site/zh/reference/builtin-agents.mdx +61 -28
  180. package/docs-site/zh/reference/capabilities.mdx +7 -27
  181. package/docs-site/zh/reference/cli.mdx +35 -32
  182. package/docs-site/zh/reference/define-agent.mdx +32 -31
  183. package/docs-site/zh/reference/define-config.mdx +6 -6
  184. package/docs-site/zh/reference/define-eval.mdx +33 -20
  185. package/docs-site/zh/reference/events.mdx +4 -4
  186. package/docs-site/zh/reference/expect.mdx +3 -3
  187. package/docs-site/zh/reference/official-adapters.mdx +17 -17
  188. package/docs-site/zh/reference/report-components.mdx +193 -121
  189. package/docs-site/zh/reference/results-data.mdx +13 -13
  190. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +11 -11
  191. package/docs-site/zh/troubleshooting/debugging.mdx +40 -16
  192. package/docs-site/zh/{how-to → tutorials}/agent-feedback-loop.mdx +38 -38
  193. package/docs-site/zh/tutorials/agent-onboarding.mdx +100 -0
  194. package/docs-site/zh/tutorials/authoring.mdx +211 -0
  195. package/docs-site/zh/{how-to → tutorials}/ci-integration.mdx +8 -8
  196. package/docs-site/zh/{how-to → tutorials}/connect-otel.mdx +22 -22
  197. package/docs-site/zh/{how-to → tutorials}/connect-your-agent.mdx +58 -63
  198. package/docs-site/zh/tutorials/custom-reports.mdx +453 -0
  199. package/docs-site/zh/{how-to → tutorials}/dataset-fanout.mdx +4 -4
  200. package/docs-site/zh/tutorials/experiments.mdx +103 -0
  201. package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
  202. package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +6 -6
  203. package/docs-site/zh/tutorials/quickstart.mdx +18 -18
  204. package/docs-site/zh/{how-to → tutorials}/reporters.mdx +6 -6
  205. package/docs-site/zh/{how-to → tutorials}/sandbox-agent.mdx +6 -6
  206. package/docs-site/zh/{how-to → tutorials}/sandbox-providers.mdx +41 -19
  207. package/docs-site/zh/{how-to → tutorials}/scoring-guide.mdx +4 -4
  208. package/docs-site/zh/{how-to → tutorials}/viewing-results.mdx +49 -49
  209. package/docs-site/zh/tutorials/write-experiment.mdx +355 -0
  210. package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
  211. package/package.json +8 -4
  212. package/src/agents/bub.ts +21 -8
  213. package/src/agents/claude-code.ts +23 -10
  214. package/src/agents/codex.test.ts +22 -7
  215. package/src/agents/codex.ts +22 -9
  216. package/src/agents/index.ts +2 -2
  217. package/src/agents/openai-compat.ts +1 -1
  218. package/src/agents/post-setup.ts +45 -22
  219. package/src/agents/streaming.ts +1 -1
  220. package/src/agents/types.ts +20 -17
  221. package/src/agents/ui-message-stream.test.ts +10 -0
  222. package/src/agents/ui-message-stream.ts +12 -1
  223. package/src/cli.ts +71 -57
  224. package/src/context/types.ts +21 -21
  225. package/src/define.ts +13 -0
  226. package/src/i18n/en.ts +27 -12
  227. package/src/i18n/zh-CN.ts +27 -12
  228. package/src/index.ts +2 -0
  229. package/src/report/{react → assets}/colors.ts +22 -0
  230. package/src/report/{react → assets}/enhance.js +1 -33
  231. package/src/report/{react → assets}/styles.css +124 -106
  232. package/src/report/built-in/index.tsx +2 -2
  233. package/src/report/built-in/standard.tsx +18 -6
  234. package/src/report/components/attempt-detail/AttemptAssertions.tsx +67 -0
  235. package/src/report/components/attempt-detail/AttemptConversation.tsx +110 -0
  236. package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +38 -0
  237. package/src/report/components/attempt-detail/AttemptDiff.tsx +35 -0
  238. package/src/report/components/attempt-detail/AttemptError.tsx +31 -0
  239. package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +26 -0
  240. package/src/report/components/attempt-detail/AttemptSource.tsx +80 -0
  241. package/src/report/components/attempt-detail/AttemptSummary.tsx +74 -0
  242. package/src/report/components/attempt-detail/AttemptTimeline.tsx +108 -0
  243. package/src/report/components/attempt-detail/AttemptTrace.tsx +63 -0
  244. package/src/report/components/attempt-detail/AttemptUsage.tsx +29 -0
  245. package/src/report/components/attempt-detail/attempt-components.test.tsx +680 -0
  246. package/src/report/components/attempt-detail/compute.ts +287 -0
  247. package/src/report/components/attempt-detail/faces.ts +257 -0
  248. package/src/report/components/attempt-detail/index.tsx +583 -0
  249. package/src/report/components/attempt-detail/validate.test.ts +235 -0
  250. package/src/report/{react → components}/cell.tsx +6 -3
  251. package/src/report/{report.test.ts → components/compute.test.ts} +316 -46
  252. package/src/report/{react → components/entity-lists}/AttemptList.tsx +24 -16
  253. package/src/report/{react → components/entity-lists}/EvalList.tsx +0 -0
  254. package/src/report/{react → components/entity-lists}/ExperimentList.tsx +23 -20
  255. package/src/report/components/entity-lists/compute.ts +196 -0
  256. package/src/report/components/entity-lists/faces.ts +207 -0
  257. package/src/report/components/entity-lists/index.tsx +241 -0
  258. package/src/report/components/entity-lists/validate.test.ts +114 -0
  259. package/src/report/{react → components}/fixtures.ts +2 -2
  260. package/src/report/{react → components/metric-views}/DeltaTable.tsx +5 -5
  261. package/src/report/{react → components/metric-views}/MetricBars.tsx +5 -5
  262. package/src/report/{react → components/metric-views}/MetricLine.tsx +9 -6
  263. package/src/report/{react → components/metric-views}/MetricMatrix.tsx +6 -6
  264. package/src/report/{react → components/metric-views}/MetricScatter.tsx +41 -33
  265. package/src/report/{react → components/metric-views}/MetricTable.tsx +6 -6
  266. package/src/report/{react → components/metric-views}/Scoreboard.tsx +4 -4
  267. package/src/report/{compute.ts → components/metric-views/compute.ts} +51 -477
  268. package/src/report/components/metric-views/faces.ts +423 -0
  269. package/src/report/components/metric-views/index.tsx +352 -0
  270. package/src/report/{text → components/metric-views}/plot.ts +1 -1
  271. package/src/report/components/metric-views/validate.test.ts +211 -0
  272. package/src/report/{react → components}/render.test.tsx +47 -17
  273. package/src/report/components/shared-compute.ts +59 -0
  274. package/src/report/components/shared-faces.ts +30 -0
  275. package/src/report/components/shared.ts +192 -0
  276. package/src/report/{react → components/site-components}/CopyFixPrompt.tsx +3 -3
  277. package/src/report/{react → components/site-components}/HeroCard.tsx +3 -3
  278. package/src/report/{react → components/site-components}/ScopeWarnings.tsx +4 -4
  279. package/src/report/{react → components/site-components}/TraceWaterfall.tsx +13 -10
  280. package/src/report/components/site-components/compute.ts +144 -0
  281. package/src/report/components/site-components/faces.ts +80 -0
  282. package/src/report/components/site-components/index.tsx +265 -0
  283. package/src/report/{scope-warnings.ts → components/site-components/scope-warnings.ts} +3 -3
  284. package/src/report/{site-components.test.tsx → components/site-components/site-components.test.tsx} +23 -21
  285. package/src/report/components/site-components/validate.test.ts +89 -0
  286. package/src/report/{react → components/summaries}/ScopeSummary.tsx +26 -11
  287. package/src/report/components/summaries/compute.ts +56 -0
  288. package/src/report/components/summaries/faces.ts +48 -0
  289. package/src/report/components/summaries/index.tsx +127 -0
  290. package/src/report/components/summaries/validate.test.ts +49 -0
  291. package/src/report/definition/grid-layout.test.ts +124 -0
  292. package/src/report/definition/grid-layout.ts +146 -0
  293. package/src/report/{primitives.tsx → definition/primitives.tsx} +168 -18
  294. package/src/report/{report.ts → definition/report.ts} +98 -165
  295. package/src/report/{shell-head.test.ts → definition/shell-head.test.ts} +2 -2
  296. package/src/report/{text/table.ts → definition/table-text.ts} +4 -4
  297. package/src/report/{tree.ts → definition/tree.ts} +56 -20
  298. package/src/report/index.ts +126 -66
  299. package/src/report/{aggregate.ts → model/aggregate.ts} +31 -9
  300. package/src/report/{flag.ts → model/flag.ts} +39 -1
  301. package/src/report/{format.ts → model/format.ts} +59 -5
  302. package/src/report/{locale.ts → model/locale.ts} +34 -36
  303. package/src/report/{metrics.ts → model/metrics.ts} +3 -3
  304. package/src/report/{types.ts → model/types.ts} +143 -22
  305. package/src/report/react/index.tsx +52 -26
  306. package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +564 -49
  307. package/src/report/runtime/host.test.ts +44 -0
  308. package/src/report/runtime/host.ts +138 -0
  309. package/src/report/{load.ts → runtime/load.ts} +1 -1
  310. package/src/report/runtime/text.ts +192 -0
  311. package/src/report/runtime/web.ts +106 -0
  312. package/src/results/attempt-evidence.ts +5 -1
  313. package/src/results/host-equivalence.test.ts +53 -2
  314. package/src/results/select.ts +29 -5
  315. package/src/runner/attempt.test.ts +48 -1
  316. package/src/runner/attempt.ts +62 -42
  317. package/src/runner/cleanup-timeout.test.ts +16 -0
  318. package/src/runner/cleanup-timeout.ts +23 -0
  319. package/src/runner/discover.ts +1 -2
  320. package/src/runner/eval-selection.test.ts +187 -0
  321. package/src/runner/eval-selection.ts +109 -0
  322. package/src/runner/experiment-cleanup-registry.test.ts +89 -0
  323. package/src/runner/experiment-cleanup-registry.ts +39 -0
  324. package/src/runner/experiment-labels.test.ts +71 -0
  325. package/src/runner/feedback/agent.test.ts +33 -1
  326. package/src/runner/feedback/agent.ts +12 -16
  327. package/src/runner/feedback/ci.test.ts +35 -1
  328. package/src/runner/feedback/ci.ts +16 -18
  329. package/src/runner/feedback/coordinator.ts +28 -0
  330. package/src/runner/feedback/human.test.ts +108 -1
  331. package/src/runner/feedback/human.ts +71 -21
  332. package/src/runner/feedback/reducer.test.ts +64 -0
  333. package/src/runner/feedback/reducer.ts +26 -0
  334. package/src/runner/feedback/sink.ts +40 -1
  335. package/src/runner/fingerprint.ts +2 -1
  336. package/src/runner/report.test.ts +14 -0
  337. package/src/runner/report.ts +3 -1
  338. package/src/runner/run.test.ts +390 -12
  339. package/src/runner/run.ts +259 -26
  340. package/src/runner/sandbox-selection.test.ts +13 -3
  341. package/src/runner/sandbox-selection.ts +4 -2
  342. package/src/runner/types.ts +158 -23
  343. package/src/sandbox/docker.ts +7 -0
  344. package/src/sandbox/e2b.ts +12 -13
  345. package/src/sandbox/types.ts +23 -21
  346. package/src/scoring/display.test.ts +28 -2
  347. package/src/scoring/display.ts +38 -5
  348. package/src/scoring/types.ts +3 -3
  349. package/src/shared/aggregate.test.ts +52 -0
  350. package/src/shared/aggregate.ts +29 -0
  351. package/src/shared/types.ts +6 -1
  352. package/src/show/command.test.ts +34 -0
  353. package/src/show/command.ts +16 -0
  354. package/src/show/compose.ts +1 -1
  355. package/src/show/index.ts +56 -20
  356. package/src/show/render.ts +15 -239
  357. package/src/show/show.test.ts +127 -34
  358. package/src/view/app/App.test.tsx +1 -1
  359. package/src/view/app/App.tsx +119 -47
  360. package/src/view/app/i18n.ts +7 -147
  361. package/src/view/app/lib/attempt-dialog.test.ts +65 -0
  362. package/src/view/app/lib/attempt-dialog.ts +69 -0
  363. package/src/view/app/main.tsx +0 -1
  364. package/src/view/app/types.ts +3 -86
  365. package/src/view/artifact-serving.test.ts +22 -38
  366. package/src/view/client-dist/app.css +1 -1
  367. package/src/view/client-dist/app.js +15 -22
  368. package/src/view/data.test.ts +48 -36
  369. package/src/view/data.ts +105 -83
  370. package/src/view/index.ts +1 -1
  371. package/src/view/server.ts +32 -2
  372. package/src/view/shared/types.ts +6 -31
  373. package/src/view/site-parity.test.ts +24 -1
  374. package/src/view/site.ts +144 -17
  375. package/src/view/styles.css +0 -805
  376. package/src/view/view-report.test.ts +141 -22
  377. package/dist/report/components.d.ts +0 -179
  378. package/dist/report/components.js +0 -544
  379. package/dist/report/compute.d.ts +0 -139
  380. package/dist/report/react/ExperimentComparison.d.ts +0 -10
  381. package/dist/report/react/ExperimentComparison.js +0 -12
  382. package/dist/report/react/MetricScatter.d.ts +0 -9
  383. package/dist/report/react/format.d.ts +0 -3
  384. package/dist/report/react/format.js +0 -7
  385. package/dist/report/text/faces.d.ts +0 -45
  386. package/dist/report/text/faces.js +0 -671
  387. package/dist/report/web.d.ts +0 -32
  388. package/dist/report/web.js +0 -48
  389. package/docs-site/zh/how-to/authoring.mdx +0 -162
  390. package/docs-site/zh/how-to/custom-reports.mdx +0 -414
  391. package/docs-site/zh/how-to/experiments.mdx +0 -86
  392. package/docs-site/zh/how-to/write-experiment.mdx +0 -164
  393. package/src/report/components.tsx +0 -925
  394. package/src/report/react/ExperimentComparison.tsx +0 -73
  395. package/src/report/react/format.ts +0 -9
  396. package/src/report/text/faces.ts +0 -767
  397. package/src/report/web.ts +0 -77
  398. package/src/show/report-host.test.ts +0 -205
  399. package/src/show/report-host.ts +0 -389
  400. package/src/view/app/components/AttemptModal.tsx +0 -496
  401. package/src/view/app/components/CodeView.test.tsx +0 -142
  402. package/src/view/app/components/CodeView.tsx +0 -310
  403. package/src/view/app/components/CopyControls.tsx +0 -106
  404. package/src/view/app/components/Trace.tsx +0 -100
  405. package/src/view/app/components/Transcript.tsx +0 -157
  406. package/src/view/app/components/ui/badge.tsx +0 -21
  407. package/src/view/app/lib/artifact-url.ts +0 -17
  408. package/src/view/app/lib/attempt-route.test.ts +0 -80
  409. package/src/view/app/lib/attempt-route.ts +0 -52
  410. package/src/view/app/lib/format.ts +0 -70
  411. package/src/view/app/lib/guards.test.ts +0 -108
  412. package/src/view/app/lib/guards.ts +0 -71
  413. package/src/view/app/lib/rows.ts +0 -22
  414. package/src/view/app/lib/transcript-data.tsx +0 -151
  415. package/src/view/app/lib/verdict.ts +0 -23
  416. package/src/view/app/shared.ts +0 -8
  417. /package/dist/report/{react → components/metric-views}/chart-math.d.ts +0 -0
  418. /package/dist/report/{react → components/metric-views}/chart-math.js +0 -0
  419. /package/dist/report/{text → components/metric-views}/plot.d.ts +0 -0
  420. /package/dist/report/{react → components/site-components}/PoweredBy.d.ts +0 -0
  421. /package/dist/report/{react → components/site-components}/PoweredBy.js +0 -0
  422. /package/dist/report/{text/layout.d.ts → model/text-layout.d.ts} +0 -0
  423. /package/dist/report/{text/layout.js → model/text-layout.js} +0 -0
  424. /package/dist/report/{types.js → model/types.js} +0 -0
  425. /package/src/report/{react → components/metric-views}/chart-math.test.ts +0 -0
  426. /package/src/report/{react → components/metric-views}/chart-math.ts +0 -0
  427. /package/src/report/{react → components/site-components}/PoweredBy.tsx +0 -0
  428. /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" },
@@ -462,32 +464,6 @@ async function openBrowser(url: string): Promise<boolean> {
462
464
  });
463
465
  }
464
466
 
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
467
  /**
492
468
  * run 结束后把 coordinator 累计的诊断折成 `RunCompletion`(见 docs/feature/experiments/cli.md
493
469
  * 「运行完成状态不只看 verdict 计数」)。只读已经真实发生过的诊断,不额外发明信号:
@@ -663,7 +639,7 @@ async function main(): Promise<void> {
663
639
 
664
640
  const agentRuns: AgentRun[] = [];
665
641
  let experimentSelection = t("cli.all");
666
- let availableExperimentGroups = t("cli.none");
642
+ let availableExperimentPaths = t("cli.none");
667
643
 
668
644
  if (command === "exp") {
669
645
  if (flags.agent || flags.model) {
@@ -679,16 +655,13 @@ async function main(): Promise<void> {
679
655
  const expArg = positionals[0];
680
656
  const extraPatterns = positionals.slice(1);
681
657
  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;
658
+ availableExperimentPaths = browsableExperimentPaths(experiments.map((e) => e.id)).join(", ") || t("cli.none");
659
+ const selectedIds = expArg ? new Set(matchExperimentSelector(experiments.map((e) => e.id), expArg)) : undefined;
660
+ const selected = selectedIds ? experiments.filter((e) => selectedIds.has(e.id)) : experiments;
688
661
  if (selected.length === 0) {
689
662
  process.stderr.write(t("cli.experiment.noMatch", {
690
663
  arg: expArg ?? t("cli.all"),
691
- experiments: experiments.map((e) => e.id).join(", ") || t("cli.none"),
664
+ experiments: availableExperimentPaths,
692
665
  }));
693
666
  // show / view 是顶层命令。只有同名 experiment 确实不存在时才纠错,不能抢占合法 id。
694
667
  if (expArg === "show" || expArg === "view") {
@@ -707,6 +680,15 @@ async function main(): Promise<void> {
707
680
  }
708
681
  for (const exp of selected) {
709
682
  // 一个实验 = 一个配置(单 model)。跨模型对比写多个实验文件,各钉一个 model。
683
+ // evals 谓词在这里对本次 invocation 的候选 eval 各求值一次;下游(dry-run、sandbox 查表、
684
+ // fingerprint/carry、attempt 展开)只消费 selectedEvalIds,不重新调用谓词
685
+ // (见 docs/feature/experiments/library.md「evals」)。
686
+ const { selectedEvalIds } = resolveExperimentEvals({
687
+ experimentId: exp.id,
688
+ selector: exp.evals,
689
+ cliPatterns: extraPatterns,
690
+ evals,
691
+ });
710
692
  agentRuns.push({
711
693
  agent: exp.agent,
712
694
  model: exp.model,
@@ -717,23 +699,25 @@ async function main(): Promise<void> {
717
699
  sandbox: exp.sandbox ?? config.sandbox,
718
700
  timeoutMs: flags.timeout ?? envNumber("NICEEVAL_TIMEOUT") ?? exp.timeoutMs ?? config.timeoutMs,
719
701
  budget: flags.budget ?? envNumber("NICEEVAL_BUDGET") ?? exp.budget,
720
- evalFilter: evalsFilterFromExperiment(exp.evals, extraPatterns),
702
+ selectedEvalIds,
721
703
  experimentId: exp.id,
722
704
  description: exp.description,
705
+ labels: exp.labels,
723
706
  evalFilterFingerprint: fingerprintEvalsFilter(exp.evals, extraPatterns),
724
707
  strict: flags.strict,
725
708
  // 实验级并发上限:随 AgentRun 进调度器按实验单独限流(runner 两级信号量),
726
709
  // 不再取所有选中实验的最小值钳全局——那会让一个串行实验拖慢整批基线。
727
710
  maxConcurrency: exp.maxConcurrency,
711
+ setup: exp.setup,
728
712
  });
729
713
  }
730
714
  } else {
731
715
  // 裸 run / `niceeval <eval>` 不再执行。运行配置必须来自 experiments/,
732
716
  // 这样 agent/model/flags/runs/budget 与结果聚合都有可签入的身份。
733
717
  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
- );
718
+ const ids = experiments.map((e) => e.id);
719
+ const matchedIds = new Set(positionals.flatMap((p) => matchExperimentSelector(ids, p)));
720
+ const asExp = experiments.filter((e) => matchedIds.has(e.id));
737
721
  process.stderr.write(t("cli.run.experimentRequired"));
738
722
  if (asExp.length > 0) {
739
723
  process.stderr.write(t("cli.run.experimentRequiredHint", {
@@ -758,14 +742,14 @@ async function main(): Promise<void> {
758
742
 
759
743
  // matchedByRun[i] 对应 agentRuns[i] 匹配到的 eval 集合;--dry 预览与真正开跑时的
760
744
  // RunFeedbackPlan(总量、去重 eval 数)共用同一份计算,不重复过滤一遍。
761
- const matchedByRun = agentRuns.map((run) => evals.filter((e) => run.evalFilter(e.id)));
745
+ const matchedByRun = agentRuns.map((run) => selectedEvalsForRun(evals, run));
762
746
  const totalRuns = agentRuns.reduce((sum, run, i) => sum + matchedByRun[i]!.length * run.runs, 0);
763
747
  const uniqueEvalIds = new Set(matchedByRun.flat().map((e) => e.id));
764
748
 
765
749
  if (totalRuns === 0) {
766
750
  process.stderr.write(t("cli.experiment.noEvalsSelected", {
767
751
  selection: experimentSelection,
768
- experiments: availableExperimentGroups,
752
+ experiments: availableExperimentPaths,
769
753
  }));
770
754
  process.exit(1);
771
755
  }
@@ -873,13 +857,34 @@ async function main(): Promise<void> {
873
857
  // 3 次:真不耐烦了,硬退(此时多半已无可清理的)。
874
858
  const ctrl = new AbortController();
875
859
  let signalCount = 0;
876
- // 兜底强清 + 退出:只跑一次,带超时(stopAllSandboxes 内每个 stop 各自有超时)。先停 dashboard
877
- // tick/动态区域(coordinator.stopDynamic()),避免硬退时终端卡在半帧 ANSI 状态。
860
+ // 强清 = 加速收尾,不是绕过收尾(docs/cli.md「中断:三级响应」)。顺序:先强停沙箱(卡在
861
+ // 沙箱 I/O 上的收尾立刻失败返回),然后事件驱动收口——并发等待「在飞收尾链 settle」与
862
+ // 「实验级 teardown 注册表排空」(drain 会启动未启动的、等待在飞的同一 memoized promise),
863
+ // 两者都 settle 即退。兜底上限从单可调用体清理上限推导(docs 声明的不等式:provider stop 8s
864
+ // < 看门狗 < CLEANUP_TIMEOUT_MS ≤ 本上限),不是第 2 级的语义——settle 才是——只拦
865
+ // 「收尾可调用体绕过了自己的超时」的失守病态,到点放弃退出(职责同第 3 级硬退)。
866
+ // 只跑一次;先停 dashboard 的 tick/动态区域(coordinator.stopDynamic()),
867
+ // 避免硬退时终端卡在半帧 ANSI 状态。
868
+ const FORCE_SETTLE_CAP_MS = CLEANUP_TIMEOUT_MS * 2;
869
+ let runInFlight: Promise<unknown> | undefined;
878
870
  let forcing = false;
879
871
  const forceCleanupAndExit = (code: number) => {
880
872
  if (forcing) return;
881
873
  forcing = true;
882
- void Promise.all([coordinator.stopDynamic(), stopAllSandboxes()]).finally(() => process.exit(code));
874
+ void (async () => {
875
+ await Promise.all([coordinator.stopDynamic(), stopAllSandboxes()]);
876
+ const settled = Promise.allSettled([
877
+ ...(runInFlight ? [runInFlight] : []),
878
+ drainExperimentTeardowns(),
879
+ ]);
880
+ await Promise.race([
881
+ settled.then(() => {}),
882
+ new Promise<void>((r) => {
883
+ setTimeout(r, FORCE_SETTLE_CAP_MS).unref();
884
+ }),
885
+ ]);
886
+ process.exit(code);
887
+ })();
883
888
  };
884
889
  for (const sig of ["SIGINT", "SIGTERM"] as const) {
885
890
  process.on(sig, () => {
@@ -887,13 +892,16 @@ async function main(): Promise<void> {
887
892
  if (signalCount === 1) {
888
893
  reportActivity(t("cli.interruptCleanup").trimEnd());
889
894
  ctrl.abort();
890
- // 看门狗:graceful 清理 12s 还没让进程自己收口,就强清兜底。
895
+ // 看门狗:graceful 清理迟迟没让进程自己收口,就强清兜底。取值在 docs/cli.md 声明的
896
+ // 不等式链里:> provider stop 超时(8s,一次正常停容器超时后才升级,不误伤),
897
+ // < CLEANUP_TIMEOUT_MS(30s)。
898
+ const GRACEFUL_WATCHDOG_MS = 12_000;
891
899
  setTimeout(() => {
892
900
  if (liveSandboxCount() > 0) {
893
901
  reportActivity(t("cli.fallbackCleanupTimeout").trimEnd());
894
902
  forceCleanupAndExit(130);
895
903
  }
896
- }, 12_000).unref();
904
+ }, GRACEFUL_WATCHDOG_MS).unref();
897
905
  } else if (signalCount === 2) {
898
906
  reportActivity(t("cli.forceCleanupExit").trimEnd());
899
907
  forceCleanupAndExit(130);
@@ -921,7 +929,7 @@ async function main(): Promise<void> {
921
929
 
922
930
  let summary: RunSummary;
923
931
  try {
924
- summary = await runEvals({
932
+ const inFlight = runEvals({
925
933
  config,
926
934
  evals,
927
935
  agentRuns,
@@ -933,15 +941,20 @@ async function main(): Promise<void> {
933
941
  keepSandbox: flags.keepSandbox,
934
942
  niceevalRoot: resolvePath(cwd, ".niceeval"),
935
943
  });
944
+ // 交给强清路径一个可等待的收尾句柄:二次中断/看门狗强清时先有界等它收口,让在飞的
945
+ // teardown 链跑完,而不是 process.exit 把它们连同进程一起杀掉。
946
+ runInFlight = inFlight;
947
+ summary = await inFlight;
936
948
  } catch (e) {
937
949
  // 真崩溃前先撤下 dashboard,不让半帧 ANSI 状态和下面 main().catch 打印的错误交织。
938
950
  await coordinator.stopDynamic();
939
951
  throw e;
940
952
  }
941
953
 
942
- // 正常返回(含被中断后走部分汇总)后再兜一刀:Scope finalizer 没停掉的残留沙箱在这里强清。
943
- // 跑顺利时登记表已空,是 no-op。
954
+ // 正常返回(含被中断后走部分汇总)后再兜一刀:Scope finalizer 没停掉的残留沙箱、没被运行
955
+ // 路径消费的实验级 cleanup 在这里强清。跑顺利时两份登记表都已空,是 no-op。
944
956
  await stopAllSandboxes();
957
+ await drainExperimentTeardowns();
945
958
 
946
959
  // completion 要先算好,--json/--junit 是否"这次真的写出"才有依据(见下)。
947
960
  const completion = assembleRunCompletion(coordinator.state);
@@ -979,7 +992,8 @@ async function main(): Promise<void> {
979
992
 
980
993
  main().catch(async (e) => {
981
994
  process.stderr.write(t("cli.error", { error: formatThrown(e) }));
982
- // 真·崩溃路径也别留孤儿:强清还活着的沙箱(带超时),再退。
995
+ // 真·崩溃路径也别留孤儿:强清还活着的沙箱(带超时)、排空实验级 cleanup 注册表,再退。
983
996
  await stopAllSandboxes();
997
+ await drainExperimentTeardowns();
984
998
  process.exit(2);
985
999
  });
@@ -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",