niceeval 0.9.0 → 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 +15 -7
  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} +44 -12
  118. package/dist/report/{tree.js → definition/tree.js} +10 -12
  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 +12 -2
  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 +15 -1
  154. package/dist/shared/aggregate.js +32 -1
  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 +43 -19
  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 +11 -5
  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 +59 -50
  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 +77 -64
  224. package/src/context/types.ts +21 -21
  225. package/src/define.ts +13 -0
  226. package/src/i18n/en.ts +30 -15
  227. package/src/i18n/zh-CN.ts +30 -15
  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 +136 -112
  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} +58 -22
  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 +54 -3
  314. package/src/results/select.ts +30 -6
  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 +30 -1
  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 +58 -22
  356. package/src/show/render.ts +16 -240
  357. package/src/show/show.test.ts +128 -35
  358. package/src/view/app/App.test.tsx +8 -7
  359. package/src/view/app/App.tsx +135 -51
  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 +122 -89
  370. package/src/view/index.ts +3 -13
  371. package/src/view/server.ts +36 -6
  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 +18 -806
  376. package/src/view/view-report.test.ts +182 -34
  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
@@ -1,4 +1,4 @@
1
- import type { Cleanup, JsonValue, LocalizedText, SourceArtifact } from "../shared/types.ts";
1
+ import type { JsonValue, LocalizedText, ScopedFeedback, SourceArtifact } from "../shared/types.ts";
2
2
  import type { O11ySummary, StreamEvent, TraceSpan, Usage } from "../o11y/types.ts";
3
3
  import type { Agent, AgentSetupManifest } from "../agents/types.ts";
4
4
  import type { Sandbox, SandboxHookContext, SandboxOption } from "../sandbox/types.ts";
@@ -15,6 +15,8 @@ export interface ExperimentRunInfo {
15
15
  description?: string;
16
16
  reasoningEffort?: string;
17
17
  flags?: Record<string, JsonValue>;
18
+ /** 报告归类标注(ExperimentDef.labels 原样投影);不透传运行时,不参与可比性配置。 */
19
+ labels?: Record<string, string | number>;
18
20
  runs: number;
19
21
  earlyExit: boolean;
20
22
  timeoutMs?: number;
@@ -41,7 +43,7 @@ export interface SandboxRunInfo {
41
43
  * 不存在第二套词表。phase 是 runner 对真实 lifecycle 的单方面投影,不是 adapter / sandbox
42
44
  * provider / 用户 hook 能直接设置的公共字段。
43
45
  */
44
- export type LifecyclePhase = "sandbox.queue" | "sandbox.create" | "sandbox.setup" | "workspace.baseline" | "eval.setup" | "agent.setup" | "telemetry.configure" | "eval.run" | "agent.run" | "workspace.diff" | "scoring.evaluate" | "telemetry.collect" | "eval.teardown" | "agent.teardown" | "sandbox.teardown" | "sandbox.suspend" | "sandbox.stop";
46
+ export type LifecyclePhase = "experiment.setup" | "experiment.teardown" | "sandbox.queue" | "sandbox.create" | "sandbox.setup" | "workspace.baseline" | "eval.setup" | "agent.setup" | "telemetry.configure" | "eval.run" | "agent.run" | "workspace.diff" | "scoring.evaluate" | "telemetry.collect" | "eval.teardown" | "agent.teardown" | "sandbox.teardown" | "sandbox.suspend" | "sandbox.stop";
45
47
  /** TimingNode 的种类(见 docs/feature/results/architecture.md「result.json」)。 */
46
48
  export type TimingNodeKind = "hook" | "turn" | "command" | "provider" | "operation";
47
49
  /**
@@ -312,18 +314,18 @@ export interface EvalDef {
312
314
  description?: string;
313
315
  /** 标签,供 CLI `--tag` 过滤和 view 分类;与 id 前缀过滤是两套独立的筛选维度。 */
314
316
  tags?: string[];
315
- /** 这条 eval 需要的环境 profile id(provider-neutral,如 `"python-3.9-astropy-4.2"`);由 sandbox spec 的 `environments` 表翻译成该 provider 的预制产物。 */
317
+ /** 这条评估用例需要的环境 profile id(provider-neutral,如 `"python-3.9-astropy-4.2"`);由 sandbox spec 的 `environments` 表翻译成该 provider 的预制产物。 */
316
318
  environment?: string;
317
- /** 覆盖项目级 Config.judge,只对这一个 eval 生效(如换个更贵的评审模型)。 */
319
+ /** 覆盖项目级 Config.judge,只对这一条评估用例生效(如换个更贵的评审模型)。 */
318
320
  judge?: JudgeConfig;
319
- /** 覆盖 / 追加项目级 Config.reporters,只对这一个 eval 生效。 */
321
+ /** 覆盖 / 追加项目级 Config.reporters,只对这一条评估用例生效。 */
320
322
  reporters?: Reporter[];
321
- /** 覆盖项目级 / CLI 的单次 attempt 超时(毫秒),只对这一个 eval 生效。 */
323
+ /** 覆盖项目级 / CLI 的单次 attempt 超时(毫秒),只对这一条评估用例生效。 */
322
324
  timeoutMs?: number;
323
325
  /** 任意附加元数据,原样透传进 EvalResult,不参与调度或打分;供自定义 reporter 消费。 */
324
326
  metadata?: Record<string, unknown>;
325
327
  /**
326
- * 调整 agent diff 的归因排除清单(仅沙箱型;见 docs/feature/eval/README.md):两个数组都是
328
+ * 调整 agent diff 的归因排除清单( Sandbox 型;见 docs/feature/eval/README.md):两个数组都是
327
329
  * gitignore 风格 glob(workdir 相对)。默认排除 .git/node_modules/构建产物/包管理器缓存;
328
330
  * `ignore` 在默认清单上追加排除;`include` 优先级最高,把匹配路径显式加回。
329
331
  * 合成规则固定为「默认 ∪ ignore,再被 include 打洞」,清单在分类账锚点时冻结。
@@ -333,14 +335,23 @@ export interface EvalDef {
333
335
  ignore?: string[];
334
336
  };
335
337
  /**
336
- * eval 级预置:拿到沙箱(已上传 workspace + git 基线 + 装好依赖前)。
338
+ * 评估用例级预置:拿到 Sandbox(已上传 workspace + git 基线 + 装好依赖前)。
337
339
  * 默认命令以非 root 跑(agent 的自然环境);装系统依赖时给 `runCommand` 传 `{ root: true }`
338
340
  * (如 `runCommand("apt-get", ["install", …], { root: true })`),跨 provider 语义一致。
339
341
  * 第二个参数是绑定到 `eval.setup` 的窄上下文(`ctx.progress` / `ctx.diagnostic`,
340
- * 见 docs/feature/eval/README.md);可返回 cleanup 闭包,归因到 `eval.teardown`。
342
+ * 见 docs/feature/eval/README.md)。setup 不返回值;要把产物传给 teardown,
343
+ * 以 `sandbox` 实例作键存取(并发 attempt 共享同一模块,普通模块变量会互相覆写)。
341
344
  */
342
- setup?: (sandbox: Sandbox, ctx: SandboxHookContext) => Promise<void | Cleanup> | void | Cleanup;
343
- /** eval 主体:拿到 TestContext,驱动对话 / 沙箱操作并就地断言。 */
345
+ setup?: (sandbox: Sandbox, ctx: SandboxHookContext) => Promise<void> | void;
346
+ /**
347
+ * 评估用例级收尾:attempt 收尾链的第一段(`eval.teardown` → `agent.teardown` →
348
+ * `sandbox.teardown`),Sandbox 此刻还活着。当且仅当 `eval.setup` 时点走到过才执行——
349
+ * `setup` / `test` 抛错都不豁免,未声明 `setup` 不影响触发;抛错或超 30s 清理上限
350
+ * 只记 `teardown-failed` 诊断,不改判定。管 Sandbox 外的临时 Fixture(临时 repo / bucket),
351
+ * Sandbox 内的东西随销毁自动回收、不需要它。
352
+ */
353
+ teardown?: (sandbox: Sandbox, ctx: SandboxHookContext) => Promise<void> | void;
354
+ /** 评估用例主体:拿到 TestContext,驱动对话 / Sandbox 操作并就地断言。 */
344
355
  test(t: TestContext): Promise<void> | void;
345
356
  }
346
357
  /** 内部:发现后带上 id 的 eval。 */
@@ -356,6 +367,20 @@ export interface DiscoveredEval extends EvalDef {
356
367
  */
357
368
  source: CapturedEvalSource;
358
369
  }
370
+ /**
371
+ * `ExperimentDef.setup` / `teardown` 拿到的窄上下文。`progress` 更新本实验运行级
372
+ * active 行的次要文本(短命状态,agent/ci profile 不逐条输出),`diagnostic` 进运行级永久
373
+ * 事件流(实验级钩子不属于任何单个 attempt,诊断不落 attempt 的 `result.json`;setup 抛错
374
+ * 以每条 attempt 的结构化 `error` 落盘,失败仍可回顾)。钩子的起止本身由 runner 直接发布为
375
+ * 运行级反馈,不依赖这里的 `progress`(见 docs/feature/experiments/cli.md「实验级钩子的显示」)。
376
+ */
377
+ export interface ExperimentHookContext extends ScopedFeedback {
378
+ readonly experimentId: string;
379
+ /** 本实验解析后实际选中的 eval id 全集(evals 过滤器的求值结果)。 */
380
+ readonly selectedEvalIds: readonly string[];
381
+ /** 用户中断(Ctrl+C / kill)时 abort;长启动的 setup 应观察它提前退出。 */
382
+ readonly signal?: AbortSignal;
383
+ }
359
384
  export interface ExperimentDef {
360
385
  /** 路径推导,定义里禁止手写(defineExperiment 会拒绝显式传入)。 */
361
386
  id?: string;
@@ -374,12 +399,27 @@ export interface ExperimentDef {
374
399
  * (defineExperiment 解析时校验,非 JSON 直接报错),经 ctx.flags 透传给 adapter、
375
400
  * t.flags 暴露给 eval,并原样进入结果快照的 ExperimentRunInfo.flags。 */
376
401
  flags?: Record<string, JsonValue>;
402
+ /**
403
+ * 报告归类标注:实验在各对比轴上的坐标(如 `{ line: "codex", memory: "mempal" }`)。
404
+ * 值域 string | number(解析时校验)。与 `flags` 的分界是「会不会改变 attempt 里发生的事」:
405
+ * labels 不透传 ctx / t(agent 和 eval 看不见)、不参与可比性配置(改它不作废已有结果),
406
+ * 只原样投影进快照的 `ExperimentRunInfo.labels` 供报告维度(`label()` / `numericLabel()`)
407
+ * 分组。`line` 键被默认报告识别:组内任一实验声明了它,散点按线归类并连线。
408
+ * 见 docs/feature/experiments/library.md「labels」。
409
+ */
410
+ labels?: Record<string, string | number>;
377
411
  /** 同一 eval 重复跑几次(结果各计一条 attempt);省略/CLI `--runs` 覆盖时默认 1。 */
378
412
  runs?: number;
379
413
  /** 一次重复(runs > 1)里某次 attempt 失败后是否跳过剩余重复;省略默认 true(提前退出省钱)。 */
380
414
  earlyExit?: boolean;
381
- /** 这个实验覆盖哪些 eval:"*" 全部、字符串数组按 id 前缀、或自定义谓词;省略等价于 "*"。 */
382
- evals?: "*" | string[] | ((id: string) => boolean);
415
+ /**
416
+ * 这个实验覆盖哪些 eval:`"*"` 全部、字符串数组按 id 前缀、或自定义谓词(逐条收到发现并扇出后的
417
+ * 只读 `EvalDescriptor`,不暴露路径 / 执行字段);省略等价于 `"*"`。谓词对本次 invocation 的
418
+ * 候选 eval 各求值一次,解析结果作为 `selectedEvalIds` 落进快照——不是运行时反复调用的过滤器
419
+ * (见 docs/feature/eval/library.md「EvalDescriptor」、docs/feature/experiments/library.md
420
+ * 「evals:遍历发现结果,自定义选择」)。
421
+ */
422
+ evals?: "*" | readonly string[] | ((e: EvalDescriptor) => boolean);
383
423
  /** 覆盖项目级 / CLI 的单次 attempt 超时(毫秒),只对这个实验生效。 */
384
424
  timeoutMs?: number;
385
425
  /**
@@ -399,10 +439,40 @@ export interface ExperimentDef {
399
439
  * 或给撞 provider 限额的实验单独降速。
400
440
  */
401
441
  maxConcurrency?: number;
442
+ /**
443
+ * 实验级生命周期钩子对的 setup 侧:整场至多一次、宿主机侧,管「每实验一份、所有 attempt
444
+ * 共享」的宿主机资源(隧道、mock server、license 租约)。本实验第一个通过派发许可的
445
+ * attempt 触发(memoized,并发 attempt 等同一个结果;全部结果被 carry 携入时不执行)。
446
+ * setup 不返回值;产物写模块级变量,`teardown` 与同文件 agent / sandbox 钩子从闭包读,
447
+ * runner 不做值的中介。setup 抛错 → 本实验所有 attempt 记 `errored`
448
+ * (code `"experiment-setup-failed"`、phase `"experiment.setup"`),同批其它实验不受影响。
449
+ * 函数体不进 fingerprint,改了钩子逻辑用 `--force` 强制重跑。
450
+ * 见 docs/feature/experiments/architecture.md「实验级生命周期」。
451
+ */
452
+ setup?: (ctx: ExperimentHookContext) => void | Promise<void>;
453
+ /**
454
+ * 实验级生命周期钩子对的 teardown 侧:本实验全部 attempt 收尾后执行(运行被中断也执行),
455
+ * 当且仅当 setup 时点走到过——setup 抛错不豁免(半初始化现场同样要扫尾,teardown 对可能
456
+ * 未赋值的闭包变量做防御),未声明 setup 不影响触发;一个 attempt 都不派发则跳过。
457
+ * 抛错或超 30s 清理上限只记运行级 diagnostic(`experiment-teardown-failed`),不改判定。
458
+ */
459
+ teardown?: (ctx: ExperimentHookContext) => void | Promise<void>;
402
460
  }
403
461
  export interface DiscoveredExperiment extends ExperimentDef {
404
462
  id: string;
405
- group: string;
463
+ }
464
+ /**
465
+ * 用户谓词(`ExperimentDef.evals`)能看到的唯一形状——发现并扇出后的显式白名单投影,不透传
466
+ * `DiscoveredEval` 原对象(不暴露 `sourcePath` / `baseDir` / `test` / hooks 等内部路径与执行字段)。
467
+ * `tags` 缺省为冻结空数组;`metadata` 原样引用作者声明的对象(至少浅冻结),供 `tags.includes(...)` /
468
+ * `environment` / `metadata.<key>` 判断(见 docs/feature/eval/library.md「EvalDescriptor」)。
469
+ */
470
+ export interface EvalDescriptor {
471
+ readonly id: string;
472
+ readonly description?: string;
473
+ readonly tags: readonly string[];
474
+ readonly environment?: string;
475
+ readonly metadata?: Readonly<Record<string, unknown>>;
406
476
  }
407
477
  export interface Config {
408
478
  /**
@@ -410,11 +480,11 @@ export interface Config {
410
480
  * 可传字符串,或按 locale 提供多语言(如 `{ en: "...", "zh-CN": "..." }`),随 view 语言切换。
411
481
  */
412
482
  name?: LocalizedText;
413
- /** 项目级默认沙箱 provider(docker / vercel / e2b / custom);experiment 可覆盖。 */
483
+ /** 项目级默认 Sandbox provider(docker / vercel / e2b / custom);experiment 可覆盖。 */
414
484
  sandbox?: SandboxOption;
415
- /** 上传进沙箱的工作区根目录,省略则用项目根;eval sandbox 视图从这里起步。 */
485
+ /** 上传进 Sandbox 的工作区根目录,省略则用项目根;评估用例的 sandbox 视图从这里起步。 */
416
486
  workspace?: string;
417
- /** 项目级默认 judge 配置(model / baseUrl / apiKeyEnv);EvalDef.judge 可按 eval 覆盖。 */
487
+ /** 项目级默认 judge 配置(model / baseUrl / apiKeyEnv);EvalDef.judge 可按评估用例覆盖。 */
418
488
  judge?: JudgeConfig;
419
489
  /** 项目级默认 reporter 列表(如落盘 / 上传结果);EvalDef.reporters 会与它合并。 */
420
490
  reporters?: Reporter[];
@@ -425,11 +495,11 @@ export interface Config {
425
495
  /**
426
496
  * OTLP 接收配置,niceeval 项目内唯一入口(不读 NICEEVAL_OTLP_* 环境变量)。
427
497
  * `port` 钉住接收端口(固定端口模式:长驻服务把 OTEL_EXPORTER_OTLP_ENDPOINT 一次性指到
428
- * http://localhost:<port>/v1/traces,跑多少次 eval 都不用改)。省略 = 每次运行动态分配
498
+ * http://localhost:<port>/v1/traces,跑多少次评估用例都不用改)。省略 = 每次运行动态分配
429
499
  * 临时端口(经 ctx.telemetry 交给 adapter)。代价:固定端口下同机同时只能跑一个 niceeval 进程,
430
500
  * 且该端口被别的进程占用时会报错——换一个空闲端口写回这里即可。
431
501
  * `host` 是报给 adapter 的接收端 hostname(而非监听地址,监听地址恒为 0.0.0.0):默认
432
- * "127.0.0.1";docker 沙箱型 tracing 需要 "host.docker.internal" 之类的场景,或配了隧道
502
+ * "127.0.0.1";docker Sandbox tracing 需要 "host.docker.internal" 之类的场景,或配了隧道
433
503
  * 的远程接入,在这里覆盖。
434
504
  */
435
505
  telemetry?: {
@@ -486,18 +556,29 @@ export interface AgentRun {
486
556
  resolvedSandboxes?: Map<string, SandboxOption>;
487
557
  timeoutMs?: number;
488
558
  budget?: number;
489
- evalFilter: (id: string) => boolean;
490
559
  experimentId?: string;
491
560
  /** 实验的一句话描述(ExperimentDef.description),进结果快照的 ExperimentRunInfo。 */
492
561
  description?: string;
562
+ /** 报告归类标注(ExperimentDef.labels),原样进 ExperimentRunInfo.labels;不透传 ctx / t。 */
563
+ labels?: Record<string, string | number>;
493
564
  /** evals 过滤器的指纹(数组内容 / 函数体哈希),进 ExperimentRunInfo.evalFilterFingerprint。 */
494
565
  evalFilterFingerprint?: string;
495
- /** 本次运行解析后实际选中的 eval id 全集;runEvals 在调度前按 evalFilter 求值填入。 */
496
- selectedEvalIds?: string[];
566
+ /**
567
+ * 本次 invocation 解析后实际选中的 eval id 全集——CLI 在构造 AgentRun 时对候选 eval 各求值
568
+ * 一次算好(见 `eval-selection.ts` 的 `resolveExperimentEvals()`),下游(dry-run、sandbox 查表、
569
+ * fingerprint/carry、attempt 展开、hook ctx、落盘)只消费这份已解析结果,不重新调用用户谓词。
570
+ * 保持顺序 = discovery 稳定顺序,去重。
571
+ */
572
+ selectedEvalIds: readonly string[];
497
573
  strict?: boolean;
498
574
  /** 本配置自己的并发上限(来自 ExperimentDef.maxConcurrency):调度器为它单建信号量,
499
575
  * attempt 先过这道闸再占全局并发位;省略则只受全局并发约束。 */
500
576
  maxConcurrency?: number;
577
+ /** 实验级生命周期钩子对(来自 ExperimentDef.setup / .teardown):setup 整场至多一次,
578
+ * 调度器 memoize 执行;teardown 在全部 attempt 收尾后执行,当且仅当 setup 时点走到过
579
+ * (语义见 ExperimentDef 对应字段)。 */
580
+ setup?: (ctx: ExperimentHookContext) => void | Promise<void>;
581
+ teardown?: (ctx: ExperimentHookContext) => void | Promise<void>;
501
582
  }
502
583
  export interface RunOptions {
503
584
  config: Config;
@@ -592,6 +673,23 @@ export interface ActiveAttempt {
592
673
  phaseStartedAt: number;
593
674
  detail?: string;
594
675
  }
676
+ /** 实验级钩子只有 setup 与它返回的 teardown 两员,同一实验内两者永不并发
677
+ * (teardown 在全部 attempt 收尾后才触发),所以运行级行按 experimentId 建 key 就够。 */
678
+ export type ExperimentHookName = "setup" | "teardown";
679
+ /**
680
+ * dashboard 当前可见的一个实验级钩子运行级行(见 docs/feature/experiments/cli.md
681
+ * 「实验级钩子的显示」)。与 `ActiveAttempt` 分开建模:钩子不属于任何单个 attempt、不占并发位,
682
+ * 也不参与 `total = reused + running + queued + completed` 的计数不变量——等待 setup 的
683
+ * attempt 保持 `queued`,这行就是「为什么它们还在排队」的解释。`detail` 来自实验级
684
+ * `ctx.progress`,后一条覆盖前一条。
685
+ */
686
+ export interface ActiveExperimentHook {
687
+ experimentId: string;
688
+ hook: ExperimentHookName;
689
+ /** 钩子开始的墙钟时间(epoch ms),用于渲染运行级行的耗时。 */
690
+ startedAt: number;
691
+ detail?: string;
692
+ }
595
693
  /**
596
694
  * 一次失败/错误的永久通知:human 撤下 dashboard 后追加一行、agent/ci 立即追加一行,都读它。
597
695
  * 字段全部结构化(locator / identity / verdict / phase 都是具名字段),profile renderer 不需要
@@ -668,6 +766,9 @@ export interface RunFeedbackState {
668
766
  newTokenCount?: number;
669
767
  estimatedCostUSD?: number;
670
768
  active: ReadonlyMap<AttemptKey, ActiveAttempt>;
769
+ /** 在飞的实验级钩子(experimentId → 运行级行状态),由 "experiment-hook" 事件增删、
770
+ * "experiment:progress" 更新 detail(见 docs/feature/experiments/cli.md「实验级钩子的显示」)。 */
771
+ experimentHooks: ReadonlyMap<string, ActiveExperimentHook>;
671
772
  failures: readonly FailureNotice[];
672
773
  /** 本次实际派发后产生的去重失败数;复用失败不消耗 profile 的流式输出上限。 */
673
774
  freshFailureCount: number;
@@ -736,6 +837,16 @@ export type AttemptLifecycleEvent = {
736
837
  identity: AttemptRef;
737
838
  who: string;
738
839
  };
840
+ /**
841
+ * 实验级 `ctx.progress` 的短命投影:只覆盖对应运行级行的 `detail`,不追加永久行——与
842
+ * `attempt:progress` 同一判断标准(新值使旧值失去意义)。对应的运行级行不存在时静默忽略。
843
+ */
844
+ export interface ExperimentProgressEvent {
845
+ type: "experiment:progress";
846
+ at: number;
847
+ experimentId: string;
848
+ detail: string;
849
+ }
739
850
  /**
740
851
  * 运行级时钟 tick:唯一允许更新 `RunFeedbackState.elapsedMs` 的事件,由 coordinator 的定时器产出
741
852
  *(见 plan 的可注入 `FeedbackIO` clock)。reducer 保持纯函数,不自己读 `Date.now()`,elapsedMs
@@ -798,6 +909,21 @@ export type DurableFeedbackEvent = {
798
909
  provider: string;
799
910
  sandboxId: string;
800
911
  enter?: string;
912
+ }
913
+ /**
914
+ * 实验级钩子(`ExperimentDef.setup` / 它返回的 teardown)的起止,由 runner 在钩子真正
915
+ * 开始/结束时各发一次(见 docs/feature/experiments/cli.md「实验级钩子的显示」)。`failed`
916
+ * 只标记钩子自身的结局——setup 失败的每条 attempt 仍以 "failure" 事件逐条给出。human TTY
917
+ * 用它维护运行级 active 行(不写 scrollback),append-only profile 起止各追加一行。
918
+ */
919
+ | {
920
+ type: "experiment-hook";
921
+ at: number;
922
+ experimentId: string;
923
+ hook: ExperimentHookName;
924
+ status: "started" | "done" | "failed";
925
+ /** 只在 done / failed 上出现:钩子从开始到结束的耗时。 */
926
+ durationMs?: number;
801
927
  } | {
802
928
  type: "interrupted";
803
929
  at: number;
@@ -832,4 +958,4 @@ export type DurableFeedbackEvent = {
832
958
  * profile renderer 只消费这里的具名字段,不解析 `ReporterEvent` 里的 i18n 文案或表格列宽
833
959
  *(见 docs/feature/experiments/cli.md「输出流和落盘节奏」)。
834
960
  */
835
- export type RunFeedbackEvent = AttemptLifecycleEvent | FeedbackTickEvent | DurableFeedbackEvent;
961
+ export type RunFeedbackEvent = AttemptLifecycleEvent | ExperimentProgressEvent | FeedbackTickEvent | DurableFeedbackEvent;
@@ -91,6 +91,12 @@ export declare class DockerSandbox implements Sandbox {
91
91
  /**
92
92
  * 向容器任意路径写文件(二进制)。
93
93
  * 打成单文件 tar → putArchive 到目标目录,与 uploadFiles 同一机制但目标路径自由。
94
+ *
95
+ * 修正属主:putArchive 以 root 解包,不 chown 的话文件在容器里保持 root 属主——非 root
96
+ * 沙箱用户不仅不能编辑它,后续对它做 `mv`/`rm` 这类改动它所在目录项的操作,只要目标目录带
97
+ * sticky bit(如 `/tmp`),也会因为「非属主不能改别人的目录项」被内核拒成
98
+ * `Operation not permitted`(与 uploadFiles() 对整个目标目录 chown 是同一个属主问题,
99
+ * 这里只需精确 chown 这一个文件;真机复现见 memory/docker-uploadfile-tmp-mv-eperm.md)。
94
100
  */
95
101
  uploadFile(destPath: string, content: Buffer): Promise<void>;
96
102
  /** 销毁容器:显式 stop + remove(创建时不带 AutoRemove,见 createContainer 的注释)。 */
@@ -1,4 +1,4 @@
1
- import type { Cleanup, ScopedFeedback } from "../shared/types.ts";
1
+ import type { ScopedFeedback } from "../shared/types.ts";
2
2
  export interface CommandResult {
3
3
  stdout: string;
4
4
  stderr: string;
@@ -65,15 +65,16 @@ export type SandboxRuntime = "node20" | "node24";
65
65
  * **执行顺序**:沙箱就绪 → `sandbox.setup` 钩子 → workspace 上传 / git 基线 / `eval.setup` →
66
66
  * `agent.setup` → `agent.tracing.configure` → 逐轮 `send`。`sandbox.setup` 特意排在 git
67
67
  * 基线之前——它的改动会被提交进基线,不会被误算进 agent 产出的 diff。收尾按 LIFO:
68
- * agent 级 cleanup / `agent.teardown` 先跑,`sandbox.teardown` 钩子最后跑(沙箱销毁前)。
68
+ * `agent.teardown` 先跑,`sandbox.teardown` 钩子最后跑(沙箱销毁前)。
69
69
  *
70
70
  * **多钩子**:`.setup(a).setup(b)` 按追加顺序依次执行(a 先 b 后);`.teardown(x).teardown(y)`
71
71
  * 按追加的**逆序**执行(y 先 x 后)。每次调用都返回一个新 spec(不改变原对象),可继续链式。
72
+ * `setup` 链中途抛错时后续 `setup` 不再执行,`teardown` 链仍完整走完。
72
73
  *
73
74
  * **失败语义**:`setup` 钩子抛错按执行错误计——与 `eval.setup` / `agent.setup` 抛错走同一条
74
- * 路径,不新增错误分类;但不阻断该 attempt 已进入的收尾(已跑过的 setup 返回的 cleanup、
75
- * `teardown` 钩子仍会在 finally 里跑)。`teardown` 钩子报错只作诊断(吞掉 / 记 log),不改变
76
- * 已产出的结果——与 `agent.teardown` 现状一致。
75
+ * 路径,不新增错误分类;但不阻断该 attempt 已进入的收尾(已挂载的 `teardown` 钩子仍会在
76
+ * finally 里跑)。`teardown` 钩子报错只作诊断(吞掉 / 记 log),不改变已产出的结果——与
77
+ * `agent.teardown` 现状一致。
77
78
  */
78
79
  export interface SandboxHooks<Self> {
79
80
  /** 已挂载的 setup 钩子,按追加顺序保存(内部读取,一般用不到)。 */
@@ -96,8 +97,9 @@ export interface SandboxHookContext extends ScopedFeedback {
96
97
  /** 本次 attempt 的中止信号。 */
97
98
  readonly signal: AbortSignal;
98
99
  }
99
- /** 沙箱级生命周期钩子(`.setup()` / `.teardown()` 链式挂载);`setup` 可返回 cleanup 闭包。 */
100
- export type SandboxHook = (sandbox: Sandbox, ctx: SandboxHookContext) => void | Cleanup | Promise<void | Cleanup>;
100
+ /** 沙箱级生命周期钩子(`.setup()` / `.teardown()` 链式挂载);`setup` 不返回值——要把 `setup`
101
+ * 创建的句柄传给 `teardown`,以 `sandbox` 实例为键存取(见 {@link SandboxHooks}) */
102
+ export type SandboxHook = (sandbox: Sandbox, ctx: SandboxHookContext) => void | Promise<void>;
101
103
  /**
102
104
  * Sandbox 的「数据结构」定义 —— 与 agent 一样可带参数(见 docs/feature/sandbox/library.md)。
103
105
  * 必须用工厂函数构造(`dockerSandbox()` / `vercelSandbox()` / `e2bSandbox()` / `defineSandbox()`),
@@ -160,12 +162,12 @@ export type SandboxSpec = DockerSandboxSpec | VercelSandboxSpec | E2BSandboxSpec
160
162
  /** config / experiment 的 `sandbox` 字段:必须是工厂函数产出的 spec 数据结构;沙箱型 agent 不能省略。 */
161
163
  export type SandboxOption = SandboxSpec;
162
164
  export interface CommandOptions {
163
- /** 追加/覆盖本命令的环境变量(与沙箱默认环境叠加,不清空默认值;各 provider 会保留自己固定的 `PATH` 等变量,不保证能被这里覆盖)。 */
165
+ /** 追加/覆盖本命令的环境变量( Sandbox 默认环境叠加,不清空默认值;各 provider 会保留自己固定的 `PATH` 等变量,不保证能被这里覆盖)。 */
164
166
  env?: Record<string, string>;
165
167
  /** 本命令的工作目录;省略时落到 `Sandbox.workdir`。相对路径按 workdir 解析,绝对路径原样使用。 */
166
168
  cwd?: string;
167
169
  /**
168
- * 把本命令的输出也送进沙箱的「原生日志流」(于是 `docker logs` / Docker UI 的 Logs
170
+ * 把本命令的输出也送进 Sandbox 的「原生日志流」(于是 `docker logs` / Docker UI 的 Logs
169
171
  * 标签页能实时看到它)。给 agent 命令(codex exec / bub run / claude)开它,就能在容器
170
172
  * 日志里看到 agent 的【原始输出】。provider 各自实现(docker:tee 到 PID1 tail 的文件;
171
173
  * 不支持的 provider 忽略)—— 日志怎么浮现是 provider 的事,adapter 只声明意图。
@@ -180,7 +182,7 @@ export interface CommandOptions {
180
182
  /** `onStdout` 的 stderr 对应物;完整 stderr 仍保留在 `CommandResult`。 */
181
183
  onStderr?: (chunk: string) => void | Promise<void>;
182
184
  /**
183
- * 以 root 跑本命令。默认 `false` —— 命令以沙箱的标准**非 root** 用户跑(agent 的自然环境)。
185
+ * 以 root 跑本命令。默认 `false` —— 命令以 Sandbox 的标准**非 root** 用户跑(agent 的自然环境)。
184
186
  * 给 setup 阶段装系统依赖用(`apt-get install …`、`pip install --break-system-packages …`)。
185
187
  *
186
188
  * 语义跨 provider 一致:"本命令以 root 跑,否则以标准非 root 用户跑"。各 provider 映射到自己的原生机制
@@ -191,7 +193,7 @@ export interface CommandOptions {
191
193
  root?: boolean;
192
194
  }
193
195
  export interface Sandbox {
194
- /** 沙箱内项目/工作区根目录的绝对路径(agent 命令的默认 cwd,也是 git baseline 提交的位置)。各方法的相对路径都以此为基准解析,省略 `cwd`/`targetDir` 时也落到这里。 */
196
+ /** Sandbox 内项目/工作区根目录的绝对路径(agent 命令的默认 cwd,也是 git baseline 提交的位置)。各方法的相对路径都以此为基准解析,省略 `cwd`/`targetDir` 时也落到这里。 */
195
197
  readonly workdir: string;
196
198
  /**
197
199
  * 执行单个命令,`args` 作为独立 argv 传递、不经 shell 解释(无 `&&`、管道、通配符展开)。
@@ -203,31 +205,31 @@ export interface Sandbox {
203
205
  * 需要拼多条命令或做条件判断时用它。
204
206
  */
205
207
  runShell(script: string, opts?: CommandOptions): Promise<CommandResult>;
206
- /** 读取沙箱内文件的文本内容(UTF-8)。文件不存在时抛错,不返回空字符串——需要容错请自行 `.catch()`。 */
208
+ /** 读取 Sandbox 内文件的文本内容(UTF-8)。文件不存在时抛错,不返回空字符串——需要容错请自行 `.catch()`。 */
207
209
  readFile(path: string): Promise<string>;
208
- /** 检查沙箱内路径是否存在。跨 provider 语义不完全一致:仅保证对普通文件可靠,对目录路径的行为不同 provider 不保证一致。 */
210
+ /** 检查 Sandbox 内路径是否存在。跨 provider 语义不完全一致:仅保证对普通文件可靠,对目录路径的行为不同 provider 不保证一致。 */
209
211
  fileExists(path: string): Promise<boolean>;
210
212
  /**
211
213
  * 一次 shell 往返读全部源码文件(按扩展名收、按目录/文件名忽略)。
212
- * 取代每个 eval 目录里手写的 find + 逐文件 readFile。
214
+ * 取代每个评估用例目录里手写的 find + 逐文件 readFile。
213
215
  */
214
216
  readSourceFiles(opts?: ReadSourceFilesOptions): Promise<SourceFiles>;
215
217
  /** 写入若干文本文件(内容已在内存里的字符串);是 `uploadFiles` 的文本特化,省略 `targetDir` 落到 workdir。 */
216
218
  writeFiles(files: Record<string, string>, targetDir?: string): Promise<void>;
217
219
  /** 批量写入若干文件,内容可以是文本或二进制 Buffer;省略 `targetDir` 落到 workdir。 */
218
220
  uploadFiles(files: SandboxFile[], targetDir?: string): Promise<void>;
219
- /** 把本地磁盘上的一个目录整体上传进沙箱(递归读取本地文件后按 `uploadFiles` 写入);`opts.ignore` 是排除规则,省略 `targetDir` 落到 workdir。 */
221
+ /** 把本地磁盘上的一个目录整体上传进 Sandbox(递归读取本地文件后按 `uploadFiles` 写入);`opts.ignore` 是排除规则,省略 `targetDir` 落到 workdir。 */
220
222
  uploadDirectory(localDir: string, targetDir?: string, opts?: {
221
223
  ignore?: string[];
222
224
  }): Promise<void>;
223
- /** 销毁沙箱占用的计算资源(容器/microVM)。调用后沙箱不可再用;是否可安全重复调用因 provider 而异,不要依赖这一点。 */
225
+ /** 销毁 Sandbox 占用的计算资源(容器/microVM)。调用后 Sandbox 不可再用;是否可安全重复调用因 provider 而异,不要依赖这一点。 */
224
226
  stop(): Promise<void>;
225
- /** 本沙箱的稳定标识(各 provider 原生 ID,如 Docker 容器 ID 前缀);用于跨调用关联同一沙箱的会话状态,也用于日志展示。 */
227
+ /** 本 Sandbox 的稳定标识(各 provider 原生 ID,如 Docker 容器 ID 前缀);用于跨调用关联同一 Sandbox 的会话状态,也用于日志展示。 */
226
228
  readonly sandboxId: string;
227
229
  /**
228
230
  * 本地 OTLP 接收器的目标 host。
229
- * - `string`:沙箱内可通过该 hostname 回连宿主 OTLP 端口(如 docker 的 `host.docker.internal`)。
230
- * - `null`:沙箱运行在远程云端(如 e2b/vercel),无法访问宿主本地端口 → 跳过 tracing。
231
+ * - `string`:Sandbox 内可通过该 hostname 回连宿主 OTLP 端口(如 docker 的 `host.docker.internal`)。
232
+ * - `null`:Sandbox 运行在远程云端(如 e2b/vercel),无法访问宿主本地端口 → 跳过 tracing。
231
233
  * 可通过环境变量 `NICEEVAL_OTLP_HOST` 强制覆盖(如配置 tunnel 时)。
232
234
  */
233
235
  readonly otlpHost: string | null;
@@ -237,12 +239,12 @@ export interface Sandbox {
237
239
  */
238
240
  appendLog?(line: string): Promise<void>;
239
241
  /**
240
- * 从沙箱内任意路径读取文件,返回二进制 Buffer。
242
+ * Sandbox 内任意路径读取文件,返回二进制 Buffer。
241
243
  * 对应各 provider:Docker getArchive / Vercel readFileToBuffer / e2b files.read(bytes) / …
242
244
  */
243
245
  downloadFile(path: string): Promise<Buffer>;
244
246
  /**
245
- * 向沙箱内任意路径写入文件(二进制)。
247
+ * 向 Sandbox 内任意路径写入文件(二进制)。
246
248
  * 对应各 provider:Docker putArchive / Vercel fs.writeFile(Buffer) / e2b files.write / …
247
249
  */
248
250
  uploadFile(path: string, content: Buffer): Promise<void>;
@@ -8,8 +8,13 @@ export declare function summaryText(value: string): string;
8
8
  export declare function primaryAssertionSummary(assertions: readonly AssertionResult[], verdict: Verdict): PrimaryAssertionSummary | undefined;
9
9
  /** 摘要的事实层;Human/Agent 用作第二行,表格可把它接在标题后。 */
10
10
  export declare function assertionSummaryDetail(summary: PrimaryAssertionSummary): string | undefined;
11
- /** Human/Agent 的至多两层文本。 */
12
- export declare function assertionSummaryLines(summary: PrimaryAssertionSummary): [string] | [string, string];
11
+ /**
12
+ * Human/Agent 永久行的排版:标题独占一行;`matcher · expected`(含 score/threshold/reason)
13
+ * 独占下一行;`received` 能跟这行拼在一起仍在 DETAIL_LINE_MAX_CHARS 内就合并,放不下就单独
14
+ * 再起一行并硬截断。`+N more failures` 永远是独立尾行,不参与截断,也不拼进被截断的值——
15
+ * 截断处的 `…` 后面只会是值本身,不会被人误读成计数的一部分。
16
+ */
17
+ export declare function assertionSummaryLines(summary: PrimaryAssertionSummary): string[];
13
18
  /** 比较列表的单元格投影;无 group 时不重复 `gate:` 前缀。 */
14
19
  export declare function compactAssertionSummary(summary: PrimaryAssertionSummary): string;
15
20
  /**
@@ -5,6 +5,12 @@
5
5
  * 恰好是源码/工具输出时把多页内容灌进 scrollback;完整 AssertionResult 仍原样留给 show/view。
6
6
  */
7
7
  const SUMMARY_TEXT_MAX_CHARS = 240;
8
+ /**
9
+ * Human/Agent 永久行的单行事实预算,与 SUMMARY_TEXT_MAX_CHARS(单值上限)分开计:一条
10
+ * `matcher · expected · received` 拼起来很容易超过一屏宽,这里给的是「一行」的上限,不依赖
11
+ * 终端 columns——agent profile 的 handoff 不是 TTY,不能按运行时宽度变化。
12
+ */
13
+ const DETAIL_LINE_MAX_CHARS = 100;
8
14
  /** 摘要面的单值收口:折单行 + 240 字符上限。任何把断言事实放进「行」里的面共用这一条。 */
9
15
  export function summaryText(value) {
10
16
  const singleLine = value.replace(/\s+/g, " ").trim();
@@ -70,11 +76,43 @@ export function assertionSummaryDetail(summary) {
70
76
  parts.push(`+${summary.additionalFailures} more failures`);
71
77
  return parts.length > 0 ? parts.join(" · ") : undefined;
72
78
  }
73
- /** Human/Agent 的至多两层文本。 */
79
+ /**
80
+ * Human/Agent 永久行的排版:标题独占一行;`matcher · expected`(含 score/threshold/reason)
81
+ * 独占下一行;`received` 能跟这行拼在一起仍在 DETAIL_LINE_MAX_CHARS 内就合并,放不下就单独
82
+ * 再起一行并硬截断。`+N more failures` 永远是独立尾行,不参与截断,也不拼进被截断的值——
83
+ * 截断处的 `…` 后面只会是值本身,不会被人误读成计数的一部分。
84
+ */
74
85
  export function assertionSummaryLines(summary) {
75
- const head = `${summary.severity}: ${summary.assertion}`;
76
- const detail = assertionSummaryDetail(summary);
77
- return detail === undefined ? [head] : [head, detail];
86
+ const lines = [`${summary.severity}: ${summary.assertion}`];
87
+ const factParts = [];
88
+ if (summary.matcher !== undefined)
89
+ factParts.push(summary.matcher);
90
+ if (summary.expected !== undefined)
91
+ factParts.push(`expected ${summary.expected}`);
92
+ if (summary.score !== undefined)
93
+ factParts.push(`score ${summary.score}`);
94
+ if (summary.threshold !== undefined)
95
+ factParts.push(`threshold ${summary.threshold}`);
96
+ if (summary.reason !== undefined)
97
+ factParts.push(`reason ${summary.reason}`);
98
+ const facts = factParts.length > 0 ? factParts.join(" · ") : undefined;
99
+ if (summary.received !== undefined) {
100
+ const combined = facts !== undefined ? `${facts} · received ${summary.received}` : `received ${summary.received}`;
101
+ if (combined.length <= DETAIL_LINE_MAX_CHARS) {
102
+ lines.push(combined);
103
+ }
104
+ else {
105
+ if (facts !== undefined)
106
+ lines.push(facts);
107
+ lines.push(shrinkTo(`received: ${summary.received}`, DETAIL_LINE_MAX_CHARS));
108
+ }
109
+ }
110
+ else if (facts !== undefined) {
111
+ lines.push(facts);
112
+ }
113
+ if (summary.additionalFailures > 0)
114
+ lines.push(`+${summary.additionalFailures} more failures`);
115
+ return lines;
78
116
  }
79
117
  /** 比较列表的单元格投影;无 group 时不重复 `gate:` 前缀。 */
80
118
  export function compactAssertionSummary(summary) {
@@ -12,11 +12,11 @@ export interface ValueAssertion {
12
12
  /** 期望条件的有界文本描述(如 `contains "Brooklyn"`),失败时进 AssertionResult.expected。 */
13
13
  readonly expected?: string;
14
14
  score(value: unknown): number | Promise<number>;
15
- /** 转成硬门槛断言:未达阈值(省略 threshold 则按 score > 0 判定)整条 eval 判为 failed。返回新实例,不改原对象。 */
15
+ /** 转成硬门槛断言:未达阈值(省略 threshold 则按 score > 0 判定)整条评估用例判为 failed。返回新实例,不改原对象。 */
16
16
  gate(threshold?: number): ValueAssertion;
17
17
  /**
18
- * 转成软阈值断言:未达 threshold 时该条记为 failed,但默认不拖累整条 eval 的 verdict;
19
- * `--strict` 运行下,软阈值失败也会把整条 eval 的 verdict 计为 failed。返回新实例,不改原对象。
18
+ * 转成软阈值断言:未达 threshold 时该条记为 failed,但默认不拖累整条评估用例的 verdict;
19
+ * `--strict` 运行下,软阈值失败也会把整条评估用例的 verdict 计为 failed。返回新实例,不改原对象。
20
20
  */
21
21
  atLeast(threshold: number): ValueAssertion;
22
22
  /**
@@ -18,9 +18,23 @@ export declare function experimentGroupOf(experimentId: string): string | undefi
18
18
  /**
19
19
  * eval id 前缀过滤,同 CLI 位置参数语义(docs/feature/reports/show.md「打开与收窄」):
20
20
  * eval 位置参数是收窄过滤,按**裸前缀宽松匹配**——"algebra" 命中 "algebra"、"algebra/..."
21
- * 也命中 "algebra2",多命中正是它的用途(与 `--experiment` 的按路径段匹配有意不同)。
21
+ * 也命中 "algebra2",多命中正是它的用途(与 `--exp` 的按路径段匹配有意不同)。
22
22
  */
23
23
  export declare function evalPrefixPredicate(evals?: string | string[]): (id: string) => boolean;
24
+ /**
25
+ * 实验选择器解析:`niceeval exp` 位置参数与 `--exp` 共用同一条规则(docs/feature/experiments/cli.md
26
+ * 「实验选择器怎样解析」)。按序:①精确 id 优先,即使它同时是同目录内其它文件名的前缀;
27
+ * ②目录段精确前缀(含更深层目录),选中该目录下全部实验;③以上都不命中且选择器形如
28
+ * `目录/文件名前缀` 时,目录段精确匹配后按文件名段前缀选中一族配置——目录段永远精确,不跨目录
29
+ * 误配,文件名段裸前缀,与 `evalPrefixPredicate` 同一条"共享前缀即家族"的逻辑。
30
+ */
31
+ export declare function matchExperimentSelector(ids: readonly string[], selector: string): string[];
32
+ /**
33
+ * 零命中时展示的可浏览路径清单(docs/feature/experiments/cli.md「零命中」):按 id 的顶层目录段
34
+ * 去重、排序,带尾斜杠;没有目录段的顶层 experiment id 原样列出(它本身就是一个可选择的目标,
35
+ * 不是一段路径)。这是运行选择的展示投影,不是报告分组。
36
+ */
37
+ export declare function browsableExperimentPaths(ids: readonly string[]): string[];
24
38
  /** 无 experimentId 时的兜底标签。 */
25
39
  export declare function fallbackExperimentLabel(result: {
26
40
  experimentId?: string;
@@ -38,7 +38,7 @@ export function experimentGroupOf(experimentId) {
38
38
  /**
39
39
  * eval id 前缀过滤,同 CLI 位置参数语义(docs/feature/reports/show.md「打开与收窄」):
40
40
  * eval 位置参数是收窄过滤,按**裸前缀宽松匹配**——"algebra" 命中 "algebra"、"algebra/..."
41
- * 也命中 "algebra2",多命中正是它的用途(与 `--experiment` 的按路径段匹配有意不同)。
41
+ * 也命中 "algebra2",多命中正是它的用途(与 `--exp` 的按路径段匹配有意不同)。
42
42
  */
43
43
  export function evalPrefixPredicate(evals) {
44
44
  if (evals === undefined)
@@ -46,6 +46,37 @@ export function evalPrefixPredicate(evals) {
46
46
  const prefixes = Array.isArray(evals) ? evals : [evals];
47
47
  return (id) => prefixes.some((prefix) => id.startsWith(prefix));
48
48
  }
49
+ /**
50
+ * 实验选择器解析:`niceeval exp` 位置参数与 `--exp` 共用同一条规则(docs/feature/experiments/cli.md
51
+ * 「实验选择器怎样解析」)。按序:①精确 id 优先,即使它同时是同目录内其它文件名的前缀;
52
+ * ②目录段精确前缀(含更深层目录),选中该目录下全部实验;③以上都不命中且选择器形如
53
+ * `目录/文件名前缀` 时,目录段精确匹配后按文件名段前缀选中一族配置——目录段永远精确,不跨目录
54
+ * 误配,文件名段裸前缀,与 `evalPrefixPredicate` 同一条"共享前缀即家族"的逻辑。
55
+ */
56
+ export function matchExperimentSelector(ids, selector) {
57
+ const exact = ids.find((id) => id === selector);
58
+ if (exact !== undefined)
59
+ return [exact];
60
+ const dirMatches = ids.filter((id) => id.startsWith(selector + "/"));
61
+ if (dirMatches.length > 0)
62
+ return dirMatches;
63
+ const lastSlash = selector.lastIndexOf("/");
64
+ if (lastSlash === -1)
65
+ return [];
66
+ const dir = selector.slice(0, lastSlash);
67
+ const namePrefix = selector.slice(lastSlash + 1);
68
+ if (namePrefix === "")
69
+ return [];
70
+ return ids.filter((id) => id.startsWith(dir + "/") && id.slice(dir.length + 1).startsWith(namePrefix));
71
+ }
72
+ /**
73
+ * 零命中时展示的可浏览路径清单(docs/feature/experiments/cli.md「零命中」):按 id 的顶层目录段
74
+ * 去重、排序,带尾斜杠;没有目录段的顶层 experiment id 原样列出(它本身就是一个可选择的目标,
75
+ * 不是一段路径)。这是运行选择的展示投影,不是报告分组。
76
+ */
77
+ export function browsableExperimentPaths(ids) {
78
+ return [...new Set(ids.map((id) => (id.includes("/") ? `${id.split("/")[0]}/` : id)))].sort();
79
+ }
49
80
  /** 无 experimentId 时的兜底标签。 */
50
81
  export function fallbackExperimentLabel(result) {
51
82
  if (result.experimentId)
@@ -18,7 +18,12 @@ export interface SourceArtifact {
18
18
  path: string;
19
19
  content: string;
20
20
  }
21
- /** 通用清理闭包(setup 返回值 / teardown 的形状),异步同步皆可,统一在 finally 里执行。 */
21
+ /**
22
+ * 内部收尾闭包类型:供运行器内部的清理注册表使用(如 `postSetup` 钩子内部累积待收尾动作),
23
+ * 不出现在任何公开的生命周期钩子签名里——`SandboxHook`/`AgentSetup`/`AgentTeardown` 等公开
24
+ * 钩子一律 `void | Promise<void>`,setup 不返回值,收尾靠成对的 teardown(见
25
+ * docs/runner.md「环境预置不进运行器,但按顺序调它」)。
26
+ */
22
27
  export type Cleanup = () => Promise<void> | void;
23
28
  /** `ScopedFeedback.progress` 的入参:此刻正在做什么(短命状态,可被后续更新覆盖)。 */
24
29
  export interface ProgressUpdate {