niceeval 0.9.1 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/INDEX.md +31 -33
  2. package/dist/agents/types.d.ts +20 -17
  3. package/dist/context/types.d.ts +21 -21
  4. package/dist/i18n/en.d.ts +10 -2
  5. package/dist/i18n/zh-CN.d.ts +14 -6
  6. package/dist/o11y/execution-tree.d.ts +103 -0
  7. package/dist/o11y/otlp/select.d.ts +22 -0
  8. package/dist/report/{react → assets}/colors.d.ts +8 -0
  9. package/dist/report/{react → assets}/colors.js +23 -0
  10. package/dist/report/built-in/index.d.ts +2 -2
  11. package/dist/report/built-in/index.js +2 -2
  12. package/dist/report/built-in/standard.d.ts +8 -1
  13. package/dist/report/built-in/standard.js +17 -7
  14. package/dist/report/components/attempt-detail/AttemptAssertions.d.ts +6 -0
  15. package/dist/report/components/attempt-detail/AttemptAssertions.js +17 -0
  16. package/dist/report/components/attempt-detail/AttemptConversation.d.ts +10 -0
  17. package/dist/report/components/attempt-detail/AttemptConversation.js +74 -0
  18. package/dist/report/components/attempt-detail/AttemptDiagnostics.d.ts +6 -0
  19. package/dist/report/components/attempt-detail/AttemptDiagnostics.js +7 -0
  20. package/dist/report/components/attempt-detail/AttemptDiff.d.ts +6 -0
  21. package/dist/report/components/attempt-detail/AttemptDiff.js +11 -0
  22. package/dist/report/components/attempt-detail/AttemptError.d.ts +6 -0
  23. package/dist/report/components/attempt-detail/AttemptError.js +16 -0
  24. package/dist/report/components/attempt-detail/AttemptFixPrompt.d.ts +6 -0
  25. package/dist/report/components/attempt-detail/AttemptFixPrompt.js +7 -0
  26. package/dist/report/components/attempt-detail/AttemptSource.d.ts +6 -0
  27. package/dist/report/components/attempt-detail/AttemptSource.js +97 -0
  28. package/dist/report/components/attempt-detail/AttemptSummary.d.ts +8 -0
  29. package/dist/report/components/attempt-detail/AttemptSummary.js +20 -0
  30. package/dist/report/components/attempt-detail/AttemptTimeline.d.ts +6 -0
  31. package/dist/report/components/attempt-detail/AttemptTimeline.js +28 -0
  32. package/dist/report/components/attempt-detail/AttemptTrace.d.ts +6 -0
  33. package/dist/report/components/attempt-detail/AttemptTrace.js +29 -0
  34. package/dist/report/components/attempt-detail/AttemptUsage.d.ts +6 -0
  35. package/dist/report/components/attempt-detail/AttemptUsage.js +20 -0
  36. package/dist/report/components/attempt-detail/compute.d.ts +24 -0
  37. package/dist/report/components/attempt-detail/compute.js +297 -0
  38. package/dist/report/components/attempt-detail/faces.d.ts +14 -0
  39. package/dist/report/components/attempt-detail/faces.js +239 -0
  40. package/dist/report/components/attempt-detail/index.d.ts +38 -0
  41. package/dist/report/components/attempt-detail/index.js +547 -0
  42. package/dist/report/{react → components}/cell.d.ts +5 -3
  43. package/dist/report/{react → components}/cell.js +3 -3
  44. package/dist/report/{react → components/entity-lists}/AttemptList.d.ts +8 -4
  45. package/dist/report/{react → components/entity-lists}/AttemptList.js +16 -10
  46. package/dist/report/{react → components/entity-lists}/EvalList.d.ts +3 -3
  47. package/dist/report/{react → components/entity-lists}/EvalList.js +0 -0
  48. package/dist/report/{react → components/entity-lists}/ExperimentList.d.ts +4 -5
  49. package/dist/report/{react → components/entity-lists}/ExperimentList.js +12 -11
  50. package/dist/report/components/entity-lists/compute.d.ts +23 -0
  51. package/dist/report/components/entity-lists/compute.js +171 -0
  52. package/dist/report/components/entity-lists/faces.d.ts +5 -0
  53. package/dist/report/components/entity-lists/faces.js +175 -0
  54. package/dist/report/components/entity-lists/index.d.ts +49 -0
  55. package/dist/report/components/entity-lists/index.js +169 -0
  56. package/dist/report/{react → components}/fixtures.d.ts +2 -2
  57. package/dist/report/{react → components}/fixtures.js +1 -1
  58. package/dist/report/{react → components/metric-views}/DeltaTable.d.ts +2 -2
  59. package/dist/report/{react → components/metric-views}/DeltaTable.js +4 -4
  60. package/dist/report/{react → components/metric-views}/MetricBars.d.ts +3 -3
  61. package/dist/report/{react → components/metric-views}/MetricBars.js +3 -3
  62. package/dist/report/{react → components/metric-views}/MetricLine.d.ts +2 -2
  63. package/dist/report/{react → components/metric-views}/MetricLine.js +8 -5
  64. package/dist/report/{react → components/metric-views}/MetricMatrix.d.ts +3 -3
  65. package/dist/report/{react → components/metric-views}/MetricMatrix.js +4 -4
  66. package/dist/report/components/metric-views/MetricScatter.d.ts +11 -0
  67. package/dist/report/{react → components/metric-views}/MetricScatter.js +27 -60
  68. package/dist/report/{react → components/metric-views}/MetricTable.d.ts +3 -3
  69. package/dist/report/{react → components/metric-views}/MetricTable.js +4 -4
  70. package/dist/report/{react → components/metric-views}/Scoreboard.d.ts +2 -2
  71. package/dist/report/{react → components/metric-views}/Scoreboard.js +3 -3
  72. package/dist/report/components/metric-views/compute.d.ts +89 -0
  73. package/dist/report/{compute.js → components/metric-views/compute.js} +32 -415
  74. package/dist/report/components/metric-views/faces.d.ts +13 -0
  75. package/dist/report/components/metric-views/faces.js +381 -0
  76. package/dist/report/components/metric-views/index.d.ts +50 -0
  77. package/dist/report/components/metric-views/index.js +273 -0
  78. package/dist/report/{text → components/metric-views}/plot.js +1 -1
  79. package/dist/report/components/shared-compute.d.ts +22 -0
  80. package/dist/report/components/shared-compute.js +47 -0
  81. package/dist/report/components/shared-faces.d.ts +9 -0
  82. package/dist/report/components/shared-faces.js +26 -0
  83. package/dist/report/components/shared.d.ts +68 -0
  84. package/dist/report/components/shared.js +125 -0
  85. package/dist/report/{react → components/site-components}/CopyFixPrompt.d.ts +2 -2
  86. package/dist/report/{react → components/site-components}/CopyFixPrompt.js +2 -2
  87. package/dist/report/{react → components/site-components}/HeroCard.d.ts +2 -2
  88. package/dist/report/{react → components/site-components}/HeroCard.js +2 -2
  89. package/dist/report/{react → components/site-components}/ScopeWarnings.d.ts +2 -2
  90. package/dist/report/{react → components/site-components}/ScopeWarnings.js +3 -3
  91. package/dist/report/{react → components/site-components}/TraceWaterfall.d.ts +3 -3
  92. package/dist/report/{react → components/site-components}/TraceWaterfall.js +5 -5
  93. package/dist/report/components/site-components/compute.d.ts +28 -0
  94. package/dist/report/components/site-components/compute.js +132 -0
  95. package/dist/report/components/site-components/faces.d.ts +21 -0
  96. package/dist/report/components/site-components/faces.js +75 -0
  97. package/dist/report/components/site-components/index.d.ts +74 -0
  98. package/dist/report/components/site-components/index.js +220 -0
  99. package/dist/report/{scope-warnings.d.ts → components/site-components/scope-warnings.d.ts} +2 -2
  100. package/dist/report/{scope-warnings.js → components/site-components/scope-warnings.js} +2 -2
  101. package/dist/report/{react → components/summaries}/ScopeSummary.d.ts +2 -2
  102. package/dist/report/{react → components/summaries}/ScopeSummary.js +17 -8
  103. package/dist/report/components/summaries/compute.d.ts +7 -0
  104. package/dist/report/components/summaries/compute.js +51 -0
  105. package/dist/report/components/summaries/faces.d.ts +7 -0
  106. package/dist/report/components/summaries/faces.js +38 -0
  107. package/dist/report/components/summaries/index.d.ts +27 -0
  108. package/dist/report/components/summaries/index.js +78 -0
  109. package/dist/report/definition/grid-layout.d.ts +50 -0
  110. package/dist/report/definition/grid-layout.js +89 -0
  111. package/dist/report/{primitives.d.ts → definition/primitives.d.ts} +34 -4
  112. package/dist/report/{primitives.js → definition/primitives.js} +101 -10
  113. package/dist/report/{report.d.ts → definition/report.d.ts} +36 -65
  114. package/dist/report/{report.js → definition/report.js} +58 -90
  115. package/dist/report/{text/table.d.ts → definition/table-text.d.ts} +2 -2
  116. package/dist/report/{text/table.js → definition/table-text.js} +2 -2
  117. package/dist/report/{tree.d.ts → definition/tree.d.ts} +43 -11
  118. package/dist/report/{tree.js → definition/tree.js} +9 -11
  119. package/dist/report/index.d.ts +33 -18
  120. package/dist/report/index.js +23 -14
  121. package/dist/report/{aggregate.d.ts → model/aggregate.d.ts} +12 -4
  122. package/dist/report/{aggregate.js → model/aggregate.js} +28 -8
  123. package/dist/report/{flag.d.ts → model/flag.d.ts} +14 -1
  124. package/dist/report/{flag.js → model/flag.js} +35 -0
  125. package/dist/report/{format.d.ts → model/format.d.ts} +13 -7
  126. package/dist/report/{format.js → model/format.js} +79 -9
  127. package/dist/report/{locale.d.ts → model/locale.d.ts} +17 -17
  128. package/dist/report/{locale.js → model/locale.js} +31 -33
  129. package/dist/report/{metrics.d.ts → model/metrics.d.ts} +2 -2
  130. package/dist/report/{metrics.js → model/metrics.js} +2 -2
  131. package/dist/report/{types.d.ts → model/types.d.ts} +154 -20
  132. package/dist/report/react/index.d.ts +32 -21
  133. package/dist/report/react/index.js +33 -21
  134. package/dist/report/{load.d.ts → runtime/load.d.ts} +1 -1
  135. package/dist/report/{load.js → runtime/load.js} +1 -1
  136. package/dist/report/runtime/text.d.ts +67 -0
  137. package/dist/report/runtime/text.js +114 -0
  138. package/dist/report/runtime/web.d.ts +35 -0
  139. package/dist/report/runtime/web.js +70 -0
  140. package/dist/results/annotated-source.d.ts +87 -0
  141. package/dist/results/attempt-evidence.d.ts +74 -0
  142. package/dist/results/attempt-source.d.ts +15 -0
  143. package/dist/results/select.d.ts +11 -1
  144. package/dist/results/select.js +27 -5
  145. package/dist/runner/eval-selection.d.ts +26 -0
  146. package/dist/runner/feedback/sink.d.ts +26 -1
  147. package/dist/runner/types.d.ts +151 -24
  148. package/dist/sandbox/docker.d.ts +6 -0
  149. package/dist/sandbox/e2b.d.ts +0 -1
  150. package/dist/sandbox/types.d.ts +23 -21
  151. package/dist/scoring/display.d.ts +7 -2
  152. package/dist/scoring/display.js +42 -4
  153. package/dist/scoring/types.d.ts +3 -3
  154. package/dist/shared/aggregate.d.ts +14 -0
  155. package/dist/shared/aggregate.js +31 -0
  156. package/dist/shared/types.d.ts +6 -1
  157. package/docs-site/images/logo-dark.svg +7 -0
  158. package/docs-site/images/logo.svg +6 -5
  159. package/docs-site/zh/README.md +8 -7
  160. package/docs-site/zh/examples/ai-agent-application.mdx +5 -5
  161. package/docs-site/zh/examples/coding-agent-extensions.mdx +4 -4
  162. package/docs-site/zh/examples/index.mdx +3 -3
  163. package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +6 -6
  164. package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
  165. package/docs-site/zh/examples/integrations/codex-sdk.mdx +3 -3
  166. package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
  167. package/docs-site/zh/examples/integrations/pi-sdk.mdx +2 -2
  168. package/docs-site/zh/explanation/adapter.mdx +19 -19
  169. package/docs-site/zh/explanation/assert.mdx +9 -9
  170. package/docs-site/zh/explanation/drive.mdx +4 -4
  171. package/docs-site/zh/explanation/evals.mdx +8 -8
  172. package/docs-site/zh/explanation/experiment.mdx +8 -8
  173. package/docs-site/zh/explanation/hitl.mdx +13 -13
  174. package/docs-site/zh/explanation/judge.mdx +3 -3
  175. package/docs-site/zh/explanation/overview.mdx +7 -7
  176. package/docs-site/zh/explanation/runner.mdx +17 -13
  177. package/docs-site/zh/explanation/tier.mdx +6 -6
  178. package/docs-site/zh/index.mdx +14 -14
  179. package/docs-site/zh/introduction.mdx +13 -16
  180. package/docs-site/zh/reference/builtin-agents.mdx +61 -28
  181. package/docs-site/zh/reference/capabilities.mdx +7 -27
  182. package/docs-site/zh/reference/cli.mdx +36 -33
  183. package/docs-site/zh/reference/define-agent.mdx +32 -31
  184. package/docs-site/zh/reference/define-config.mdx +6 -6
  185. package/docs-site/zh/reference/define-eval.mdx +33 -20
  186. package/docs-site/zh/reference/events.mdx +4 -4
  187. package/docs-site/zh/reference/expect.mdx +3 -3
  188. package/docs-site/zh/reference/official-adapters.mdx +17 -17
  189. package/docs-site/zh/reference/report-components.mdx +195 -121
  190. package/docs-site/zh/reference/results-data.mdx +13 -13
  191. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +11 -11
  192. package/docs-site/zh/troubleshooting/debugging.mdx +40 -16
  193. package/docs-site/zh/{how-to → tutorials}/agent-feedback-loop.mdx +38 -38
  194. package/docs-site/zh/tutorials/agent-onboarding.mdx +100 -0
  195. package/docs-site/zh/tutorials/authoring.mdx +211 -0
  196. package/docs-site/zh/{how-to → tutorials}/ci-integration.mdx +8 -8
  197. package/docs-site/zh/{how-to → tutorials}/connect-otel.mdx +22 -22
  198. package/docs-site/zh/{how-to → tutorials}/connect-your-agent.mdx +58 -63
  199. package/docs-site/zh/tutorials/custom-reports.mdx +453 -0
  200. package/docs-site/zh/{how-to → tutorials}/dataset-fanout.mdx +4 -4
  201. package/docs-site/zh/tutorials/experiments.mdx +102 -0
  202. package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
  203. package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +6 -6
  204. package/docs-site/zh/tutorials/quickstart.mdx +18 -18
  205. package/docs-site/zh/{how-to → tutorials}/reporters.mdx +6 -6
  206. package/docs-site/zh/{how-to → tutorials}/sandbox-agent.mdx +6 -6
  207. package/docs-site/zh/{how-to → tutorials}/sandbox-providers.mdx +41 -19
  208. package/docs-site/zh/{how-to → tutorials}/scoring-guide.mdx +4 -4
  209. package/docs-site/zh/{how-to → tutorials}/viewing-results.mdx +60 -57
  210. package/docs-site/zh/tutorials/write-experiment.mdx +353 -0
  211. package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
  212. package/package.json +10 -7
  213. package/src/agents/bub.ts +21 -8
  214. package/src/agents/claude-code.ts +23 -10
  215. package/src/agents/codex.test.ts +22 -7
  216. package/src/agents/codex.ts +22 -9
  217. package/src/agents/index.ts +2 -2
  218. package/src/agents/openai-compat.ts +1 -1
  219. package/src/agents/post-setup.ts +45 -22
  220. package/src/agents/streaming.ts +1 -1
  221. package/src/agents/types.ts +20 -17
  222. package/src/agents/ui-message-stream.test.ts +10 -0
  223. package/src/agents/ui-message-stream.ts +12 -1
  224. package/src/cli.ts +75 -59
  225. package/src/context/types.ts +21 -21
  226. package/src/define.ts +13 -0
  227. package/src/i18n/en.ts +27 -12
  228. package/src/i18n/zh-CN.ts +27 -12
  229. package/src/index.ts +2 -0
  230. package/src/o11y/prices.json +176 -99
  231. package/src/report/{react → assets}/colors.ts +22 -0
  232. package/src/report/{react → assets}/enhance.js +1 -33
  233. package/src/report/assets/styles.css +1785 -0
  234. package/src/report/built-in/index.tsx +2 -2
  235. package/src/report/built-in/standard.tsx +18 -6
  236. package/src/report/components/attempt-detail/AttemptAssertions.tsx +67 -0
  237. package/src/report/components/attempt-detail/AttemptConversation.tsx +158 -0
  238. package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +38 -0
  239. package/src/report/components/attempt-detail/AttemptDiff.tsx +35 -0
  240. package/src/report/components/attempt-detail/AttemptError.tsx +31 -0
  241. package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +26 -0
  242. package/src/report/components/attempt-detail/AttemptSource.tsx +224 -0
  243. package/src/report/components/attempt-detail/AttemptSummary.tsx +74 -0
  244. package/src/report/components/attempt-detail/AttemptTimeline.tsx +108 -0
  245. package/src/report/components/attempt-detail/AttemptTrace.tsx +63 -0
  246. package/src/report/components/attempt-detail/AttemptUsage.tsx +29 -0
  247. package/src/report/components/attempt-detail/attempt-components.test.tsx +758 -0
  248. package/src/report/components/attempt-detail/compute.ts +336 -0
  249. package/src/report/components/attempt-detail/faces.ts +260 -0
  250. package/src/report/components/attempt-detail/index.tsx +600 -0
  251. package/src/report/components/attempt-detail/validate.test.ts +252 -0
  252. package/src/report/{react → components}/cell.tsx +6 -3
  253. package/src/report/{report.test.ts → components/compute.test.ts} +316 -46
  254. package/src/report/{react → components/entity-lists}/AttemptList.tsx +24 -16
  255. package/src/report/{react → components/entity-lists}/EvalList.tsx +0 -0
  256. package/src/report/{react → components/entity-lists}/ExperimentList.tsx +33 -27
  257. package/src/report/components/entity-lists/compute.ts +196 -0
  258. package/src/report/components/entity-lists/faces.ts +208 -0
  259. package/src/report/components/entity-lists/index.tsx +238 -0
  260. package/src/report/components/entity-lists/validate.test.ts +114 -0
  261. package/src/report/{react → components}/fixtures.ts +2 -2
  262. package/src/report/{react → components/metric-views}/DeltaTable.tsx +5 -5
  263. package/src/report/{react → components/metric-views}/MetricBars.tsx +5 -5
  264. package/src/report/{react → components/metric-views}/MetricLine.tsx +9 -6
  265. package/src/report/{react → components/metric-views}/MetricMatrix.tsx +6 -6
  266. package/src/report/{react → components/metric-views}/MetricScatter.tsx +42 -70
  267. package/src/report/{react → components/metric-views}/MetricTable.tsx +6 -6
  268. package/src/report/{react → components/metric-views}/Scoreboard.tsx +4 -4
  269. package/src/report/{compute.ts → components/metric-views/compute.ts} +51 -477
  270. package/src/report/components/metric-views/faces.ts +423 -0
  271. package/src/report/components/metric-views/index.tsx +352 -0
  272. package/src/report/{text → components/metric-views}/plot.ts +1 -1
  273. package/src/report/components/metric-views/validate.test.ts +211 -0
  274. package/src/report/{react → components}/render.test.tsx +66 -26
  275. package/src/report/components/shared-compute.ts +59 -0
  276. package/src/report/components/shared-faces.ts +30 -0
  277. package/src/report/components/shared.ts +192 -0
  278. package/src/report/{react → components/site-components}/CopyFixPrompt.tsx +3 -3
  279. package/src/report/{react → components/site-components}/HeroCard.tsx +3 -3
  280. package/src/report/{react → components/site-components}/ScopeWarnings.tsx +4 -4
  281. package/src/report/{react → components/site-components}/TraceWaterfall.tsx +13 -10
  282. package/src/report/components/site-components/compute.ts +144 -0
  283. package/src/report/components/site-components/faces.ts +80 -0
  284. package/src/report/components/site-components/index.tsx +265 -0
  285. package/src/report/{scope-warnings.ts → components/site-components/scope-warnings.ts} +3 -3
  286. package/src/report/{site-components.test.tsx → components/site-components/site-components.test.tsx} +23 -21
  287. package/src/report/components/site-components/validate.test.ts +89 -0
  288. package/src/report/{react → components/summaries}/ScopeSummary.tsx +26 -11
  289. package/src/report/components/summaries/compute.ts +56 -0
  290. package/src/report/components/summaries/faces.ts +48 -0
  291. package/src/report/components/summaries/index.tsx +127 -0
  292. package/src/report/components/summaries/validate.test.ts +49 -0
  293. package/src/report/definition/grid-layout.test.ts +124 -0
  294. package/src/report/definition/grid-layout.ts +146 -0
  295. package/src/report/{primitives.tsx → definition/primitives.tsx} +168 -18
  296. package/src/report/{report.ts → definition/report.ts} +98 -165
  297. package/src/report/{shell-head.test.ts → definition/shell-head.test.ts} +2 -2
  298. package/src/report/{text/table.ts → definition/table-text.ts} +4 -4
  299. package/src/report/{tree.ts → definition/tree.ts} +56 -20
  300. package/src/report/index.ts +128 -66
  301. package/src/report/{aggregate.ts → model/aggregate.ts} +31 -9
  302. package/src/report/{flag.ts → model/flag.ts} +39 -1
  303. package/src/report/{format.ts → model/format.ts} +89 -10
  304. package/src/report/{locale.ts → model/locale.ts} +34 -36
  305. package/src/report/{metrics.ts → model/metrics.ts} +3 -3
  306. package/src/report/{types.ts → model/types.ts} +159 -22
  307. package/src/report/react/index.tsx +54 -26
  308. package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +601 -53
  309. package/src/report/runtime/host.test.ts +44 -0
  310. package/src/report/runtime/host.ts +138 -0
  311. package/src/report/{load.ts → runtime/load.ts} +1 -1
  312. package/src/report/runtime/text.ts +192 -0
  313. package/src/report/runtime/web.ts +106 -0
  314. package/src/results/attempt-evidence.ts +5 -1
  315. package/src/results/host-equivalence.test.ts +53 -2
  316. package/src/results/select.ts +29 -5
  317. package/src/runner/attempt.test.ts +48 -1
  318. package/src/runner/attempt.ts +62 -42
  319. package/src/runner/cleanup-timeout.test.ts +16 -0
  320. package/src/runner/cleanup-timeout.ts +23 -0
  321. package/src/runner/discover.ts +1 -2
  322. package/src/runner/eval-selection.test.ts +187 -0
  323. package/src/runner/eval-selection.ts +109 -0
  324. package/src/runner/experiment-cleanup-registry.test.ts +89 -0
  325. package/src/runner/experiment-cleanup-registry.ts +39 -0
  326. package/src/runner/experiment-labels.test.ts +71 -0
  327. package/src/runner/feedback/agent.test.ts +33 -1
  328. package/src/runner/feedback/agent.ts +12 -16
  329. package/src/runner/feedback/ci.test.ts +35 -1
  330. package/src/runner/feedback/ci.ts +16 -18
  331. package/src/runner/feedback/coordinator.ts +28 -0
  332. package/src/runner/feedback/human.test.ts +108 -1
  333. package/src/runner/feedback/human.ts +71 -21
  334. package/src/runner/feedback/reducer.test.ts +64 -0
  335. package/src/runner/feedback/reducer.ts +26 -0
  336. package/src/runner/feedback/sink.ts +40 -1
  337. package/src/runner/fingerprint.ts +2 -1
  338. package/src/runner/report.test.ts +14 -0
  339. package/src/runner/report.ts +3 -1
  340. package/src/runner/run.test.ts +390 -12
  341. package/src/runner/run.ts +259 -26
  342. package/src/runner/sandbox-selection.test.ts +13 -3
  343. package/src/runner/sandbox-selection.ts +4 -2
  344. package/src/runner/types.ts +160 -24
  345. package/src/sandbox/docker.ts +7 -0
  346. package/src/sandbox/e2b-reconcile.test.ts +125 -0
  347. package/src/sandbox/e2b.ts +47 -12
  348. package/src/sandbox/types.ts +23 -21
  349. package/src/scoring/display.test.ts +28 -2
  350. package/src/scoring/display.ts +38 -5
  351. package/src/scoring/types.ts +3 -3
  352. package/src/shared/aggregate.test.ts +52 -0
  353. package/src/shared/aggregate.ts +29 -0
  354. package/src/shared/types.ts +6 -1
  355. package/src/show/command.test.ts +34 -0
  356. package/src/show/command.ts +16 -0
  357. package/src/show/compose.ts +1 -1
  358. package/src/show/index.ts +56 -20
  359. package/src/show/render.ts +15 -239
  360. package/src/show/show.test.ts +127 -34
  361. package/src/view/app/App.test.tsx +1 -1
  362. package/src/view/app/App.tsx +119 -47
  363. package/src/view/app/i18n.ts +7 -147
  364. package/src/view/app/lib/attempt-dialog.test.ts +65 -0
  365. package/src/view/app/lib/attempt-dialog.ts +69 -0
  366. package/src/view/app/main.tsx +0 -1
  367. package/src/view/app/types.ts +3 -86
  368. package/src/view/artifact-serving.test.ts +22 -38
  369. package/src/view/client-dist/app.css +1 -1
  370. package/src/view/client-dist/app.js +15 -22
  371. package/src/view/data.test.ts +48 -36
  372. package/src/view/data.ts +105 -83
  373. package/src/view/index.ts +1 -1
  374. package/src/view/server.ts +32 -2
  375. package/src/view/shared/types.ts +6 -31
  376. package/src/view/site-parity.test.ts +24 -1
  377. package/src/view/site.ts +144 -17
  378. package/src/view/styles.css +0 -805
  379. package/src/view/view-report.test.ts +143 -22
  380. package/dist/report/components.d.ts +0 -179
  381. package/dist/report/components.js +0 -544
  382. package/dist/report/compute.d.ts +0 -139
  383. package/dist/report/react/ExperimentComparison.d.ts +0 -10
  384. package/dist/report/react/ExperimentComparison.js +0 -12
  385. package/dist/report/react/MetricScatter.d.ts +0 -9
  386. package/dist/report/react/format.d.ts +0 -3
  387. package/dist/report/react/format.js +0 -7
  388. package/dist/report/text/faces.d.ts +0 -45
  389. package/dist/report/text/faces.js +0 -671
  390. package/dist/report/web.d.ts +0 -32
  391. package/dist/report/web.js +0 -48
  392. package/docs-site/zh/how-to/authoring.mdx +0 -162
  393. package/docs-site/zh/how-to/custom-reports.mdx +0 -414
  394. package/docs-site/zh/how-to/experiments.mdx +0 -86
  395. package/docs-site/zh/how-to/write-experiment.mdx +0 -164
  396. package/src/report/components.tsx +0 -925
  397. package/src/report/react/ExperimentComparison.tsx +0 -73
  398. package/src/report/react/format.ts +0 -9
  399. package/src/report/react/styles.css +0 -945
  400. package/src/report/text/faces.ts +0 -767
  401. package/src/report/web.ts +0 -77
  402. package/src/show/report-host.test.ts +0 -205
  403. package/src/show/report-host.ts +0 -389
  404. package/src/view/app/components/AttemptModal.tsx +0 -496
  405. package/src/view/app/components/CodeView.test.tsx +0 -142
  406. package/src/view/app/components/CodeView.tsx +0 -310
  407. package/src/view/app/components/CopyControls.tsx +0 -106
  408. package/src/view/app/components/Trace.tsx +0 -100
  409. package/src/view/app/components/Transcript.tsx +0 -157
  410. package/src/view/app/components/ui/badge.tsx +0 -21
  411. package/src/view/app/lib/artifact-url.ts +0 -17
  412. package/src/view/app/lib/attempt-route.test.ts +0 -80
  413. package/src/view/app/lib/attempt-route.ts +0 -52
  414. package/src/view/app/lib/format.ts +0 -70
  415. package/src/view/app/lib/guards.test.ts +0 -108
  416. package/src/view/app/lib/guards.ts +0 -71
  417. package/src/view/app/lib/rows.ts +0 -22
  418. package/src/view/app/lib/transcript-data.tsx +0 -151
  419. package/src/view/app/lib/verdict.ts +0 -23
  420. package/src/view/app/shared.ts +0 -8
  421. /package/dist/report/{react → components/metric-views}/chart-math.d.ts +0 -0
  422. /package/dist/report/{react → components/metric-views}/chart-math.js +0 -0
  423. /package/dist/report/{text → components/metric-views}/plot.d.ts +0 -0
  424. /package/dist/report/{react → components/site-components}/PoweredBy.d.ts +0 -0
  425. /package/dist/report/{react → components/site-components}/PoweredBy.js +0 -0
  426. /package/dist/report/{text/layout.d.ts → model/text-layout.d.ts} +0 -0
  427. /package/dist/report/{text/layout.js → model/text-layout.js} +0 -0
  428. /package/dist/report/{types.js → model/types.js} +0 -0
  429. /package/src/report/{react → components/metric-views}/chart-math.test.ts +0 -0
  430. /package/src/report/{react → components/metric-views}/chart-math.ts +0 -0
  431. /package/src/report/{react → components/site-components}/PoweredBy.tsx +0 -0
  432. /package/src/report/{text/layout.ts → model/text-layout.ts} +0 -0
@@ -18,8 +18,8 @@ import {
18
18
  MetricTable,
19
19
  Scoreboard,
20
20
  ScopeSummary,
21
- } from "./index.tsx";
22
- import { colorClassForKey, seriesClassForKey } from "./colors.ts";
21
+ } from "../react/index.tsx";
22
+ import { colorClassForKey, seriesClassForKey } from "../assets/colors.ts";
23
23
  import {
24
24
  attemptListItems,
25
25
  deltaData,
@@ -37,8 +37,13 @@ const attemptHref = (locator: AttemptLocator) => `/attempts/${locator}`;
37
37
  describe("ScopeSummary", () => {
38
38
  const html = renderToStaticMarkup(<ScopeSummary data={scopeSummaryData} />);
39
39
 
40
- it("KPI:experiment/eval/attempt 数、成功率与总成本原样渲染(不从计票重算)", () => {
40
+ it("KPI:experiment/eval/attempt 数、通过率与总成本原样渲染(不从计票重算)", () => {
41
41
  expect(html).toContain("nre-scope-summary");
42
+ expect(html).toContain("Pass rate");
43
+ expect(html).toContain("Experiments");
44
+ expect(html).toContain("Evals");
45
+ expect(html).toContain("Attempts");
46
+ expect(html).toContain("Eval results");
42
47
  expect(html).toContain("<dd>2</dd>"); // experiments
43
48
  expect(html).toContain("<dd>6</dd>"); // evals
44
49
  expect(html).toContain("<dd>9</dd>"); // attempts
@@ -47,6 +52,7 @@ describe("ScopeSummary", () => {
47
52
  expect(html).toContain("60%");
48
53
  expect(html).not.toContain("44%");
49
54
  expect(html).toContain("$1.50");
55
+ expect(html).toContain("Cost available for 8/9 attempts");
50
56
  });
51
57
 
52
58
  it("votes 默认 eval 级计票;votes='attempt' 切换显示但 data 不变", () => {
@@ -54,12 +60,22 @@ describe("ScopeSummary", () => {
54
60
  expect(html).toContain('data-votes="eval"');
55
61
  const attempts = renderToStaticMarkup(<ScopeSummary data={scopeSummaryData} votes="attempt" />);
56
62
  expect(attempts).toContain("4 passed");
63
+ expect(attempts).toContain("Attempt results");
57
64
  expect(attempts).toContain('data-votes="attempt"');
58
65
  });
59
66
 
60
- it("时间窗来自 range,不编造当前时间", () => {
61
- expect(html).toContain("2026-07-01T10:00:00Z");
62
- expect(html).toContain("2026-07-01T11:30:00Z");
67
+ it("时间窗带语义并按 locale 格式化,不暴露原始 ISO 或编造当前时间", () => {
68
+ expect(html).toContain("Run range ·");
69
+ expect(html).toContain("2026");
70
+ expect(html).not.toContain("2026-07-01T10:00:00Z");
71
+ expect(html).not.toContain("2026-07-01T11:30:00Z");
72
+
73
+ const zh = renderToStaticMarkup(<ScopeSummary data={scopeSummaryData} locale="zh-CN" />);
74
+ expect(zh).toContain("通过率");
75
+ expect(zh).toContain("实验");
76
+ expect(zh).toContain("Eval 结果");
77
+ expect(zh).toContain("8/9 次有成本数据");
78
+ expect(zh).toContain("运行范围 ·");
63
79
  });
64
80
  });
65
81
 
@@ -211,13 +227,17 @@ describe("MetricScatter", () => {
211
227
  expect(cx("compare/bub-low")).toBeLessThan(cx("compare/bub-high"));
212
228
  });
213
229
 
214
- it("同系列点连线,系列色走类名(nre-series-cN,CSS 上色跟随深浅主题),图例列系列名", () => {
215
- expect(html).toContain("<polyline");
216
- expect(html).toContain(`nre-scatter-line ${seriesClassForKey("bub")}`);
230
+ it("默认不连线;connect 显式开启才画折线,系列色走类名(nre-series-cN,CSS 上色跟随深浅主题),图例列系列名", () => {
231
+ expect(html).not.toContain("<polyline");
217
232
  // 渲染面零内联 hex:深色主题下由 CSS 变量切换
218
233
  expect(html).not.toMatch(/#[0-9a-f]{6}/i);
219
234
  expect(html).toContain(">bub</span>");
220
235
  expect(html).toContain(">codex</span>");
236
+
237
+ const connected = renderToStaticMarkup(<MetricScatter data={scatterData} connect />);
238
+ expect(connected).toContain("<polyline");
239
+ expect(connected).toContain('nre-scatter-line nre-series-c');
240
+ expect(connected).toContain('data-series="bub"');
221
241
  });
222
242
 
223
243
  it("niceTicks 网格与每点直接标签(末段唯一时缩成末段)", () => {
@@ -358,26 +378,37 @@ describe("ExperimentList", () => {
358
378
 
359
379
  it("web 面是带过滤框与八个固定列头的 experiment 比较表(成本列头是 Cost)", () => {
360
380
  expect(html).toContain('data-nre-experiment-filter=""');
361
- for (const header of ["Experiment", "Model", "Agent", "Avg duration", "End-to-end pass rate", "Tokens", "Cost", "Result"]) {
362
- expect(html).toContain(`>${header}</button>`);
381
+ for (const header of ["Experiment", "Model", "Agent", "Avg. time", "Pass rate", "Tokens", "Cost", "Results"]) {
382
+ expect(html).toContain(`class="nre-sort-label">${header}</span>`);
363
383
  }
364
384
  expect(html).not.toContain("Est. cost");
365
- expect(html).toContain('data-nre-experiment-sort="4" class="nre-sort-desc"');
385
+ expect(html).toContain('data-nre-experiment-sort="4" class="nre-num-head nre-sort-desc"');
386
+
387
+ const zh = renderToStaticMarkup(<ExperimentList data={experimentListItems} locale="zh-CN" />);
388
+ expect(zh).toContain('class="nre-sort-label">通过率</span>');
389
+ expect(zh).toContain("2 个 Eval");
390
+ expect(html).toContain("2 evals");
366
391
  });
367
392
 
368
- it("主行:身份、agent/model、官方两级聚合指标与 eval 级判定构成", () => {
369
- expect(html).toContain("compare/bub");
370
- expect(html).toContain("compare/codex");
393
+ it("主行:身份(最短唯一后缀)、agent/model、官方两级聚合指标与 eval 级判定构成;完整 id 仍是排序键", () => {
394
+ expect(html).toContain(">bub</b>");
395
+ expect(html).toContain(">codex</b>");
396
+ expect(html).toContain('data-sort-value="compare/bub"');
397
+ expect(html).toContain('data-sort-value="compare/codex"');
371
398
  expect(html).toContain("gpt-5.4");
372
399
  expect(html).toContain("50%"); // endToEndPassRate.display
373
- expect(html).toContain("1 passed / 1 failed");
400
+ expect(html).toContain("1 passed · 1 failed");
374
401
  expect(html).toContain("memory=true");
375
402
  });
376
403
 
377
404
  it("展开到 Eval:父行只有折叠判定与题级聚合,失败摘要只在 Attempt 子行", () => {
378
- expect(html).toContain("algebra/quadratic");
379
- expect(html).toContain('href="#/attempt/@1a4a4a4a"');
380
- expect(html).toContain('href="#/attempt/@1b5b5b5b"');
405
+ // 独立自有 React 场景:显式传 attemptHref 才产生外部链接(没有报告宿主注入 ctx.attemptHref)
406
+ const linked = renderToStaticMarkup(
407
+ <ExperimentList data={experimentListItems} filter attemptHref={(l) => `#/attempt/${l}`} />,
408
+ );
409
+ expect(linked).toContain("algebra/quadratic");
410
+ expect(linked).toContain('href="#/attempt/@1a4a4a4a"');
411
+ expect(linked).toContain('href="#/attempt/@1b5b5b5b"');
381
412
  expect(html.match(/algebra\/quadratic/g)).toHaveLength(1);
382
413
  expect(html).toContain("nre-experiment-eval-header");
383
414
  expect(html.match(/nre-experiment-attempt-row/g)).toHaveLength(4);
@@ -387,12 +418,20 @@ describe("ExperimentList", () => {
387
418
  expect(html.match(/gate: roots-correct/g)).toHaveLength(2);
388
419
  });
389
420
 
390
- it("relativeTo 去掉组前缀只显示末段;data-sort-value 仍是完整 id", () => {
391
- const relative = renderToStaticMarkup(<ExperimentList data={experimentListItems} relativeTo="compare" />);
392
- expect(relative).toContain(">bub</b>");
393
- expect(relative).toContain('data-sort-value="compare/bub"');
394
- // 不传 relativeTo 显示完整 id
395
- expect(html).toContain(">compare/bub</b>");
421
+ it("行标签默认缩成当前列表里的最短唯一后缀;末段重名时逐步加长到能区分,data-sort-value 仍是完整 id", () => {
422
+ // 两个不同 experiment 末段撞名(a/run b/run 都叫 run)时,两行都要加长到能区分为止。
423
+ const dupes = renderToStaticMarkup(
424
+ <ExperimentList
425
+ data={[
426
+ { ...experimentListItems[0]!, experimentId: "compare/a/run" },
427
+ { ...experimentListItems[1]!, experimentId: "compare/b/run" },
428
+ ]}
429
+ />,
430
+ );
431
+ expect(dupes).toContain(">a/run</b>");
432
+ expect(dupes).toContain(">b/run</b>");
433
+ expect(dupes).toContain('data-sort-value="compare/a/run"');
434
+ expect(dupes).toContain('data-sort-value="compare/b/run"');
396
435
  });
397
436
 
398
437
  it("零 JS 靠原生 <details>,无 <script>", () => {
@@ -408,7 +447,8 @@ describe("跨组件契约", () => {
408
447
  const matrix = renderToStaticMarkup(<MetricMatrix data={matrixData} />);
409
448
  const board = renderToStaticMarkup(<Scoreboard data={scoreboardData} />);
410
449
  const attempts = renderToStaticMarkup(<AttemptList data={attemptListItems} />);
411
- for (const html of [table, matrix, board, attempts]) {
450
+ const experiments = renderToStaticMarkup(<ExperimentList data={experimentListItems} />);
451
+ for (const html of [table, matrix, board, attempts, experiments]) {
412
452
  expect(html).toContain(cls);
413
453
  }
414
454
  const scatter = renderToStaticMarkup(<MetricScatter data={scatterData} />);
@@ -0,0 +1,59 @@
1
+ // 跨组件族共用的计算辅助:summaries(scopeSummaryData)、entity-lists(experimentListData)、
2
+ // metric-views(metricScatterData)三族的 *Data 函数共用同一条 selectedEvalIds 投影规则与
3
+ // 同一套组级统计折叠,住在这里而不是任一族自己的 compute.ts,避免三份重复实现分叉。
4
+
5
+ import type { Snapshot } from "../../results/types.ts";
6
+ import { selectedEvalIdsOf } from "../../results/select.ts";
7
+ import { evalLevelStats } from "../../shared/verdict.ts";
8
+ import { experimentIdOf, fullEvalKey, type Item } from "../model/aggregate.ts";
9
+ import type { VerdictTally } from "../model/types.ts";
10
+
11
+ /**
12
+ * 每个 experiment 只保留自己 `selectedEvalIds` 内的 eval/attempt——两个 experiment 声明不同
13
+ * eval 集时各自只统计自己选中的那部分,未选择的 eval(即使恰好在同一次运行里跑过)不进
14
+ * 分母、不污染另一个 experiment。第三方快照缺该字段时 `selectedEvalIdsOf` 退化为其实际
15
+ * evals,过滤天然是 no-op。宿主注入的 current() Scope 在合成时已按这条规则收窄,这里对
16
+ * 真实 Scope 是幂等的;只对作者手工拼的 Snapshot[] 真正生效。`experimentListData` /
17
+ * `scopeSummaryData` / `metricScatterData` 共用同一条规则,保证经 `ExperimentComparison`
18
+ * 展开后收到的 spec 与直接调用同一份 input 深相等。
19
+ */
20
+ export function selectedEvalsOnly(snapshots: readonly Snapshot[]): readonly Snapshot[] {
21
+ return snapshots.map((snapshot) => {
22
+ const selected = new Set(selectedEvalIdsOf(snapshot));
23
+ const evals = snapshot.evals.filter((ev) => selected.has(ev.id));
24
+ if (evals.length === snapshot.evals.length) return snapshot;
25
+ return { ...snapshot, evals, attempts: evals.flatMap((ev) => ev.attempts) };
26
+ });
27
+ }
28
+
29
+ export function tallyOf(): VerdictTally {
30
+ return { passed: 0, failed: 0, errored: 0, skipped: 0 };
31
+ }
32
+
33
+ /** 一批 Item 的组级统计(experimentListData / scopeSummaryData 共用)。 */
34
+ export function summarizeItems(items: Item[]): {
35
+ experiments: number;
36
+ evals: number;
37
+ attempts: number;
38
+ verdicts: VerdictTally;
39
+ lastRunAt: string | undefined;
40
+ } {
41
+ const experimentIds = new Set<string>();
42
+ for (const item of items) experimentIds.add(experimentIdOf(item));
43
+ const stats = evalLevelStats(
44
+ items.map((item) => ({ verdict: item.attempt.result.verdict, key: fullEvalKey(item) })),
45
+ (r) => r.key,
46
+ );
47
+ let lastRunAt: string | undefined;
48
+ for (const item of items) {
49
+ const startedAt = item.snapshot.startedAt;
50
+ if (lastRunAt === undefined || startedAt > lastRunAt) lastRunAt = startedAt;
51
+ }
52
+ return {
53
+ experiments: experimentIds.size,
54
+ evals: stats.evals,
55
+ attempts: items.length,
56
+ verdicts: { passed: stats.passed, failed: stats.failed, errored: stats.errored, skipped: stats.skipped },
57
+ lastRunAt,
58
+ };
59
+ }
@@ -0,0 +1,30 @@
1
+ // 跨组件族共用的 text 面辅助:MetricCell / VerdictTally 的文本渲染在 summaries、
2
+ // entity-lists、metric-views 三族的 text 面里都要用到,住在这里而不是任一族自己的
3
+ // faces.ts,避免三份重复实现分叉(与 ../cell.tsx 的 MetricCellView 是同一份契约的
4
+ // text/web 两面)。
5
+
6
+ import type { MetricCell, VerdictTally } from "../model/types.ts";
7
+ import { localeText, resolveLocalizedText, type ReportLocale } from "../model/locale.ts";
8
+
9
+ export const MISSING_MARK = "—";
10
+
11
+ /** 缺数据文案随 locale(en = "no data")。 */
12
+ export function missingText(locale: ReportLocale): string {
13
+ return localeText(locale, "cell.missing");
14
+ }
15
+
16
+ /** 格子的文本形态:缺数据 —,覆盖不全带 samples/total 角标;display 按 locale 解析。 */
17
+ export function cellText(cell: MetricCell, locale: ReportLocale): string {
18
+ if (cell.value === null) return MISSING_MARK;
19
+ const display = resolveLocalizedText(cell.display, locale);
20
+ return cell.samples < cell.total ? `${display} ${cell.samples}/${cell.total}` : display;
21
+ }
22
+
23
+ /** verdict 计票的紧凑文案("3 passed · 1 failed"):非零判定逐个列,全部为零如实 —。 */
24
+ export function verdictTallyText(tally: VerdictTally, locale: ReportLocale): string {
25
+ const parts: string[] = [];
26
+ for (const kind of ["passed", "failed", "errored", "skipped"] as const) {
27
+ if (tally[kind] > 0) parts.push(`${tally[kind]} ${localeText(locale, `verdict.${kind}`)}`);
28
+ }
29
+ return parts.length > 0 ? parts.join(" · ") : MISSING_MARK;
30
+ }
@@ -0,0 +1,192 @@
1
+ // 官方双面组件的共用装配机制:spec / data 双形态判别(DataProps)、data 结构校验的通用
2
+ // 原语(isObject / isLocalizedText / isCell / isTally / cellProblem / tallyProblem /
3
+ // arrayProblem / dataShapeError)、`makeDataComponent` 装配器与 `hrefOf` 证据室深链解析、
4
+ // `ChromeProps` 呈现选项基类、`cx` classname 拼接——每个组件族在自己的 index.tsx 里用这些
5
+ // 原语递归拼自己的 validate*Data(字段路径要覆盖到嵌套 MetricCell/Tally,不只顶层哨兵),
6
+ // 具体的 validate*Data 与组件导出留在各族。
7
+
8
+ import type { ReactNode } from "react";
9
+ import {
10
+ defineComponent,
11
+ memoFetchOf,
12
+ type ReportComponent,
13
+ type ResolveContext,
14
+ type TextContext,
15
+ type WebContext,
16
+ } from "../definition/tree.ts";
17
+ import type { ReportInput } from "../model/types.ts";
18
+ import type { ReportLocale } from "../model/locale.ts";
19
+ import type { AttemptLocator } from "../../results/locator.ts";
20
+
21
+ /** 拼 class 名:过滤空值,末尾接使用者透传的 className。 */
22
+ export function cx(...parts: (string | undefined | false)[]): string {
23
+ return parts.filter(Boolean).join(" ");
24
+ }
25
+
26
+ // ───────────────────────── DataProps 组合规则 ─────────────────────────
27
+
28
+ type Never<T> = { [K in keyof T]?: never };
29
+
30
+ /**
31
+ * 官方数据组件的统一 props 组合(docs/feature/reports/library/metric-views.md):
32
+ * data 形态(接收配套 *Data 的产物)或 spec 形态(Options 平铺 + 可选 input)。
33
+ */
34
+ export type DataProps<Data, Options, Presentation> =
35
+ | ({ data: Data; input?: never } & Never<Options> & Presentation)
36
+ | ({ data?: never; input?: ReportInput } & Options & Presentation);
37
+
38
+ // ───────────────────────── data 结构校验(版本漂移防线)─────────────────────────
39
+
40
+ export function isObject(value: unknown): value is Record<string, unknown> {
41
+ return typeof value === "object" && value !== null && !Array.isArray(value);
42
+ }
43
+
44
+ /** LocalizedText = string | Record<string, string>(src/shared/types.ts)。 */
45
+ export function isLocalizedText(value: unknown): boolean {
46
+ if (typeof value === "string") return true;
47
+ return isObject(value) && Object.values(value).every((entry) => typeof entry === "string");
48
+ }
49
+
50
+ /**
51
+ * 字段路径前缀的结构校验原语:通过为 `null`,否则给出带完整字段路径的具体问题
52
+ * (如 `"rows[2].cells.costUSD.samples" must be a number`)。每个族的 validate*Data
53
+ * 用这些原语递归拼自己的形状,不重新发明逐字段判断。
54
+ */
55
+ export function cellProblem(value: unknown, path: string): string | null {
56
+ if (!isObject(value)) return `"${path}" must be a MetricCell { value, display, samples, total, refs }`;
57
+ if (!(value.value === null || typeof value.value === "number")) return `"${path}.value" must be a number or null`;
58
+ if (!isLocalizedText(value.display)) return `"${path}.display" must be a LocalizedText`;
59
+ if (typeof value.samples !== "number") return `"${path}.samples" must be a number`;
60
+ if (typeof value.total !== "number") return `"${path}.total" must be a number`;
61
+ if (!Array.isArray(value.refs) || !value.refs.every((ref) => typeof ref === "string")) {
62
+ return `"${path}.refs" must be an array of locator strings`;
63
+ }
64
+ return null;
65
+ }
66
+
67
+ export function isCell(value: unknown): boolean {
68
+ return cellProblem(value, "cell") === null;
69
+ }
70
+
71
+ /** 四态 tally { passed, failed, errored, skipped } 的字段路径前缀校验。 */
72
+ export function tallyProblem(value: unknown, path: string): string | null {
73
+ if (!isObject(value)) return `"${path}" must be a tally { passed, failed, errored, skipped }`;
74
+ for (const key of ["passed", "failed", "errored", "skipped"] as const) {
75
+ if (typeof value[key] !== "number") return `"${path}.${key}" must be a number`;
76
+ }
77
+ return null;
78
+ }
79
+
80
+ export function isTally(value: unknown): boolean {
81
+ return tallyProblem(value, "tally") === null;
82
+ }
83
+
84
+ /** 数组的逐项校验:每项跑 `itemCheck(item, "path[i]")`,第一个非 null 问题即返回。 */
85
+ export function arrayProblem(
86
+ value: unknown,
87
+ path: string,
88
+ itemCheck: (item: unknown, itemPath: string) => string | null,
89
+ ): string | null {
90
+ if (!Array.isArray(value)) return `"${path}" must be an array`;
91
+ for (let i = 0; i < value.length; i++) {
92
+ const problem = itemCheck(value[i], `${path}[${i}]`);
93
+ if (problem !== null) return problem;
94
+ }
95
+ return null;
96
+ }
97
+
98
+ export type Validator = (data: unknown) => string | null;
99
+
100
+ export function dataShapeError(component: string, dataFnName: string, shape: string, problem: string): Error {
101
+ return new Error(
102
+ `<${component}> received data that does not match the current ${shape} shape: ${problem}. ` +
103
+ `It may have been computed by a different niceeval version (component data carries no schemaVersion; the support window is same-version write and read). ` +
104
+ `Recompute it with ${dataFnName}() from this niceeval version, then re-render.`,
105
+ );
106
+ }
107
+
108
+ // ───────────────────────── spec / data 双形态的通用装配 ─────────────────────────
109
+
110
+ export interface DataComponentDef<Data, Options, Presentation> {
111
+ name: string;
112
+ dataFnName: string;
113
+ shapeName: string;
114
+ dataFn: (input: ReportInput, options: Options) => Promise<Data>;
115
+ /** spec 形态的计算选项 prop 名(不含 input);未列出的 props 视为呈现选项原样保留。 */
116
+ specKeys: readonly string[];
117
+ validate: Validator;
118
+ web(props: { data: Data } & Presentation, ctx: WebContext): ReactNode;
119
+ text(props: { data: Data } & Presentation, ctx: TextContext): string;
120
+ }
121
+
122
+ export function makeDataComponent<Data, Options, Presentation>(
123
+ def: DataComponentDef<Data, Options, Presentation>,
124
+ ): ReportComponent<DataProps<Data, Options, Presentation>> {
125
+ type Props = Record<string, unknown>;
126
+ type Resolved = { data: Data } & Presentation;
127
+
128
+ const assertData = (data: unknown): Data => {
129
+ const problem = def.validate(data);
130
+ if (problem !== null) throw dataShapeError(def.name, def.dataFnName, def.shapeName, problem);
131
+ return data as Data;
132
+ };
133
+
134
+ const resolve = async (props: Props, ctx: ResolveContext): Promise<Resolved> => {
135
+ const givenSpec = def.specKeys.filter((key) => props[key] !== undefined);
136
+ if (props.data !== undefined) {
137
+ if (givenSpec.length > 0 || props.input !== undefined) {
138
+ const extras = [...givenSpec, ...(props.input !== undefined ? ["input"] : [])];
139
+ throw new Error(
140
+ `<${def.name}> got both \`data\` and spec field${extras.length > 1 ? "s" : ""} (${extras.join(", ")}) — the two data sources are exclusive and niceeval will not silently pick one. ` +
141
+ `Keep \`data\` (precomputed with ${def.dataFnName}()) and drop the spec fields, or drop \`data\` and let the pipeline compute from the spec.`,
142
+ );
143
+ }
144
+ assertData(props.data);
145
+ return props as unknown as Resolved;
146
+ }
147
+ const options: Record<string, unknown> = {};
148
+ for (const key of givenSpec) options[key] = props[key];
149
+ const input = (props.input as ReportInput | undefined) ?? ctx.input;
150
+ const data = await memoFetchOf(ctx)(def.dataFn, input, options, () =>
151
+ def.dataFn(input, options as Options),
152
+ );
153
+ const rest: Record<string, unknown> = { ...props };
154
+ delete rest.input;
155
+ for (const key of def.specKeys) delete rest[key];
156
+ return { ...rest, data } as unknown as Resolved;
157
+ };
158
+
159
+ const component = defineComponent<Props, Resolved>({
160
+ resolve,
161
+ web: (props, ctx) => {
162
+ assertData((props as { data?: unknown }).data);
163
+ return def.web(props, ctx);
164
+ },
165
+ text: (props, ctx) => {
166
+ assertData((props as { data?: unknown }).data);
167
+ return def.text(props, ctx);
168
+ },
169
+ }) as unknown as ReportComponent<DataProps<Data, Options, Presentation>>;
170
+ component.displayName = def.name;
171
+ return component;
172
+ }
173
+
174
+ /**
175
+ * 缺省接证据室,显式 prop 覆盖。`ctx.attemptHref` 本身已经是「有没有」的完整信号——
176
+ * 宿主外直接渲染、或宿主内但当前 definition 没有 attempt-input page 时它就是 undefined,
177
+ * 不需要再判断是否在宿主里。
178
+ */
179
+ export function hrefOf(
180
+ props: { attemptHref?: (locator: AttemptLocator) => string },
181
+ ctx: WebContext,
182
+ ): ((locator: AttemptLocator) => string) | undefined {
183
+ return props.attemptHref ?? ctx.attemptHref;
184
+ }
185
+
186
+ // ───────────────────────── 呈现选项类型 ─────────────────────────
187
+
188
+ export interface ChromeProps {
189
+ /** chrome 文案 locale;省略时随宿主上下文(宿主外默认 "en")。 */
190
+ locale?: ReportLocale;
191
+ className?: string;
192
+ }
@@ -4,9 +4,9 @@
4
4
  // (docs/feature/reports/library/site-components.md「CopyFixPrompt」)。
5
5
 
6
6
  import type { ReactElement } from "react";
7
- import type { CopyFixPromptData } from "../types.ts";
8
- import { DEFAULT_REPORT_LOCALE, countText, localeText, type ReportLocale } from "../locale.ts";
9
- import { cx } from "./format.ts";
7
+ import type { CopyFixPromptData } from "../../model/types.ts";
8
+ import { DEFAULT_REPORT_LOCALE, countText, localeText, type ReportLocale } from "../../model/locale.ts";
9
+ import { cx } from "../shared.ts";
10
10
 
11
11
  /**
12
12
  * 批量修复 prompt(纯 web 渲染面):折叠块内完整 prompt 文本 + 复制按钮(增强层)。
@@ -4,9 +4,9 @@
4
4
  // 组件只收 data 形态(docs/feature/reports/library/site-components.md「HeroCard」)。
5
5
 
6
6
  import type { ReactElement } from "react";
7
- import type { HeroData } from "../types.ts";
8
- import { DEFAULT_REPORT_LOCALE, localeText, resolveLocalizedText, type LocalizedText, type ReportLocale } from "../locale.ts";
9
- import { cx } from "./format.ts";
7
+ import type { HeroData } from "../../model/types.ts";
8
+ import { DEFAULT_REPORT_LOCALE, localeText, resolveLocalizedText, type LocalizedText, type ReportLocale } from "../../model/locale.ts";
9
+ import { cx } from "../shared.ts";
10
10
  import { PoweredBy } from "./PoweredBy.tsx";
11
11
 
12
12
  /** ISO 时间 → 按 locale 的「最后运行」显示(年月日 时:分);不可解析原样返回。 */
@@ -6,10 +6,10 @@
6
6
  // (docs/feature/reports/library/site-components.md「ScopeWarnings」)。
7
7
 
8
8
  import type { ReactElement } from "react";
9
- import type { ScopeWarning } from "../types.ts";
10
- import { DEFAULT_REPORT_LOCALE, type ReportLocale } from "../locale.ts";
11
- import { groupScopeWarnings, warningDetailsLabel } from "../scope-warnings.ts";
12
- import { cx } from "./format.ts";
9
+ import type { ScopeWarning } from "../../model/types.ts";
10
+ import { DEFAULT_REPORT_LOCALE, type ReportLocale } from "../../model/locale.ts";
11
+ import { groupScopeWarnings, warningDetailsLabel } from "./scope-warnings.ts";
12
+ import { cx } from "../shared.ts";
13
13
 
14
14
  /** 命令的可复制块:无 JS 时点击全选(user-select: all),enhance.js 在场时点击复制。 */
15
15
  function CommandBlock({ command }: { command: string }): ReactElement {
@@ -4,12 +4,11 @@
4
4
  // (docs/feature/reports/library/site-components.md「TraceWaterfall」)。
5
5
 
6
6
  import type { ReactElement } from "react";
7
- import type { TraceWaterfallRow } from "../types.ts";
8
- import type { AttemptLocator } from "../../results/locator.ts";
9
- import { DEFAULT_REPORT_LOCALE, countText, localeText, type ReportLocale } from "../locale.ts";
10
- import { cx, formatDurationMs } from "./format.ts";
11
-
12
- const DEFAULT_ATTEMPT_HREF = (locator: AttemptLocator): string => `#/attempt/${locator}`;
7
+ import type { TraceWaterfallRow } from "../../model/types.ts";
8
+ import type { AttemptLocator } from "../../../results/locator.ts";
9
+ import { DEFAULT_REPORT_LOCALE, countText, localeText, type ReportLocale } from "../../model/locale.ts";
10
+ import { formatDurationMs } from "../../model/format.ts";
11
+ import { cx } from "../shared.ts";
13
12
 
14
13
  function pct(part: number, total: number): string {
15
14
  if (total <= 0) return "0%";
@@ -22,7 +21,7 @@ function pct(part: number, total: number): string {
22
21
  */
23
22
  export function TraceWaterfall({
24
23
  data,
25
- attemptHref = DEFAULT_ATTEMPT_HREF,
24
+ attemptHref,
26
25
  className,
27
26
  locale = DEFAULT_REPORT_LOCALE,
28
27
  }: {
@@ -40,9 +39,13 @@ export function TraceWaterfall({
40
39
  return (
41
40
  <li key={row.locator} className="nre-waterfall-row">
42
41
  <div className="nre-waterfall-head">
43
- <a className="nre-locator" href={attemptHref(row.locator)}>
44
- {row.locator}
45
- </a>
42
+ {attemptHref ? (
43
+ <a className="nre-locator" href={attemptHref(row.locator)}>
44
+ {row.locator}
45
+ </a>
46
+ ) : (
47
+ <span className="nre-locator">{row.locator}</span>
48
+ )}
46
49
  <span className="nre-waterfall-eval">{row.evalId}</span>
47
50
  <span className="nre-waterfall-experiment">{row.experimentId}</span>
48
51
  {/* trace 缺失如实显示缺失,不猜值、不藏行 */}