niceeval 0.6.1 → 0.7.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 (378) hide show
  1. package/INDEX.md +23 -23
  2. package/README.zh.md +6 -6
  3. package/dist/agents/types.d.ts +69 -7
  4. package/dist/context/types.d.ts +32 -12
  5. package/dist/i18n/en.d.ts +54 -0
  6. package/dist/i18n/zh-CN.d.ts +57 -3
  7. package/dist/o11y/types.d.ts +16 -2
  8. package/dist/report/aggregate.d.ts +32 -24
  9. package/dist/report/aggregate.js +158 -50
  10. package/dist/report/built-in/index.d.ts +2 -0
  11. package/dist/report/built-in/index.js +8 -0
  12. package/dist/report/components.d.ts +93 -160
  13. package/dist/report/components.js +377 -114
  14. package/dist/report/compute.d.ts +87 -81
  15. package/dist/report/compute.js +597 -417
  16. package/dist/report/flag.d.ts +32 -6
  17. package/dist/report/flag.js +92 -4
  18. package/dist/report/format.d.ts +19 -11
  19. package/dist/report/format.js +30 -13
  20. package/dist/report/index.d.ts +16 -16
  21. package/dist/report/index.js +20 -21
  22. package/dist/report/load.js +3 -2
  23. package/dist/report/locale.d.ts +57 -33
  24. package/dist/report/locale.js +122 -56
  25. package/dist/report/metrics.d.ts +23 -4
  26. package/dist/report/metrics.js +110 -25
  27. package/dist/report/primitives.d.ts +48 -15
  28. package/dist/report/primitives.js +135 -26
  29. package/dist/report/react/AttemptList.d.ts +9 -7
  30. package/dist/report/react/AttemptList.js +17 -10
  31. package/dist/report/react/DeltaTable.js +19 -18
  32. package/dist/report/react/EvalList.d.ts +4 -4
  33. package/dist/report/react/EvalList.js +0 -0
  34. package/dist/report/react/ExperimentComparison.d.ts +10 -0
  35. package/dist/report/react/ExperimentComparison.js +12 -0
  36. package/dist/report/react/ExperimentList.d.ts +4 -3
  37. package/dist/report/react/ExperimentList.js +17 -18
  38. package/dist/report/react/MetricBars.js +5 -4
  39. package/dist/report/react/MetricLine.js +12 -5
  40. package/dist/report/react/MetricMatrix.js +1 -1
  41. package/dist/report/react/MetricScatter.js +59 -28
  42. package/dist/report/react/MetricTable.js +2 -12
  43. package/dist/report/react/ScopeSummary.d.ts +10 -0
  44. package/dist/report/react/ScopeSummary.js +17 -0
  45. package/dist/report/react/Scoreboard.js +6 -6
  46. package/dist/report/react/cell.js +2 -2
  47. package/dist/report/react/chart-math.d.ts +23 -6
  48. package/dist/report/react/chart-math.js +71 -19
  49. package/dist/report/react/fixtures.d.ts +5 -9
  50. package/dist/report/react/fixtures.js +110 -147
  51. package/dist/report/react/index.d.ts +15 -5
  52. package/dist/report/react/index.js +18 -7
  53. package/dist/report/report.d.ts +137 -16
  54. package/dist/report/report.js +259 -28
  55. package/dist/report/text/faces.d.ts +17 -19
  56. package/dist/report/text/faces.js +253 -184
  57. package/dist/report/text/plot.js +1 -1
  58. package/dist/report/text/table.js +38 -7
  59. package/dist/report/tree.d.ts +90 -40
  60. package/dist/report/tree.js +252 -94
  61. package/dist/report/types.d.ts +247 -284
  62. package/dist/report/types.js +4 -3
  63. package/dist/report/web.d.ts +21 -5
  64. package/dist/report/web.js +42 -16
  65. package/dist/results/select.d.ts +38 -16
  66. package/dist/results/select.js +73 -25
  67. package/dist/results/types.d.ts +49 -14
  68. package/dist/runner/feedback/sink.d.ts +110 -0
  69. package/dist/runner/types.d.ts +513 -22
  70. package/dist/sandbox/docker.d.ts +23 -2
  71. package/dist/sandbox/e2b.d.ts +15 -1
  72. package/dist/sandbox/errors.d.ts +30 -3
  73. package/dist/sandbox/io-retry.d.ts +17 -0
  74. package/dist/sandbox/registry.d.ts +2 -0
  75. package/dist/sandbox/resolve.d.ts +18 -5
  76. package/dist/sandbox/retry.d.ts +11 -1
  77. package/dist/sandbox/types.d.ts +39 -5
  78. package/dist/sandbox/vercel.d.ts +7 -1
  79. package/dist/scoring/coverage.d.ts +30 -0
  80. package/dist/scoring/display.d.ts +21 -0
  81. package/dist/scoring/display.js +120 -0
  82. package/dist/scoring/types.d.ts +103 -20
  83. package/dist/shared/aggregate.d.ts +4 -2
  84. package/dist/shared/aggregate.js +8 -7
  85. package/dist/shared/types.d.ts +28 -0
  86. package/dist/tty-line.d.ts +0 -4
  87. package/dist/util.d.ts +23 -0
  88. package/docs-site/zh/README.md +44 -0
  89. package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
  90. package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
  91. package/docs-site/zh/examples/index.mdx +50 -0
  92. package/docs-site/zh/{concepts → explanation}/adapter.mdx +31 -13
  93. package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
  94. package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
  95. package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
  96. package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
  97. package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
  98. package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
  99. package/docs-site/zh/{concepts → explanation}/overview.mdx +11 -11
  100. package/docs-site/zh/{guides → explanation}/runner.mdx +18 -8
  101. package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
  102. package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +35 -33
  103. package/docs-site/zh/{guides → how-to}/authoring.mdx +35 -2
  104. package/docs-site/zh/{guides → how-to}/ci-integration.mdx +23 -12
  105. package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
  106. package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +47 -21
  107. package/docs-site/zh/{guides → how-to}/custom-reports.mdx +34 -39
  108. package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +25 -3
  109. package/docs-site/zh/{guides → how-to}/experiments.mdx +12 -5
  110. package/docs-site/zh/how-to/publish-report.mdx +105 -0
  111. package/docs-site/zh/{guides → how-to}/reporters.mdx +2 -2
  112. package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +56 -7
  113. package/docs-site/zh/how-to/sandbox-providers.mdx +350 -0
  114. package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +4 -4
  115. package/docs-site/zh/{guides → how-to}/viewing-results.mdx +82 -39
  116. package/docs-site/zh/{guides → how-to}/write-experiment.mdx +6 -4
  117. package/docs-site/zh/{guides → how-to}/write-send.mdx +30 -14
  118. package/docs-site/zh/index.mdx +24 -26
  119. package/docs-site/zh/introduction.mdx +8 -8
  120. package/docs-site/zh/reference/builtin-agents.mdx +32 -5
  121. package/docs-site/zh/reference/capabilities.mdx +8 -8
  122. package/docs-site/zh/reference/cli.mdx +40 -12
  123. package/docs-site/zh/reference/define-agent.mdx +58 -5
  124. package/docs-site/zh/reference/define-config.mdx +1 -1
  125. package/docs-site/zh/reference/define-eval.mdx +42 -9
  126. package/docs-site/zh/reference/events.mdx +3 -3
  127. package/docs-site/zh/reference/expect.mdx +26 -1
  128. package/docs-site/zh/{guides → reference}/official-adapters.mdx +32 -8
  129. package/docs-site/zh/{guides → reference}/report-components.mdx +45 -33
  130. package/docs-site/zh/{guides → reference}/results-data.mdx +21 -13
  131. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +57 -0
  132. package/docs-site/zh/troubleshooting/debugging.mdx +212 -0
  133. package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
  134. package/package.json +10 -2
  135. package/src/agents/ai-sdk-otel.test.ts +1 -0
  136. package/src/agents/ai-sdk.test.ts +3 -0
  137. package/src/agents/ai-sdk.ts +3 -0
  138. package/src/agents/bub-install-spec.test.ts +34 -0
  139. package/src/agents/bub-install-spec.ts +32 -0
  140. package/src/agents/bub.ts +31 -32
  141. package/src/agents/claude-code.test.ts +130 -9
  142. package/src/agents/claude-code.ts +76 -4
  143. package/src/agents/codex.test.ts +189 -40
  144. package/src/agents/codex.ts +155 -14
  145. package/src/agents/coding-cli-versions.test.ts +15 -0
  146. package/src/agents/coding-cli-versions.ts +3 -0
  147. package/src/agents/index.ts +13 -2
  148. package/src/agents/langgraph.test.ts +204 -0
  149. package/src/agents/langgraph.ts +495 -0
  150. package/src/agents/marketplace.ts +85 -0
  151. package/src/agents/native-config.test.ts +179 -0
  152. package/src/agents/native-config.ts +267 -0
  153. package/src/agents/openai-compat.test.ts +1 -0
  154. package/src/agents/openai-compat.ts +1 -1
  155. package/src/agents/openclaw.test.ts +31 -0
  156. package/src/agents/openclaw.ts +171 -0
  157. package/src/agents/plugin-config.test.ts +1 -0
  158. package/src/agents/sdk-streams.test.ts +79 -0
  159. package/src/agents/sdk-streams.ts +55 -10
  160. package/src/agents/skills.test.ts +1 -0
  161. package/src/agents/streaming.test.ts +3 -9
  162. package/src/agents/streaming.ts +2 -2
  163. package/src/agents/types.ts +71 -8
  164. package/src/agents/ui-message-stream.test.ts +3 -0
  165. package/src/cli.ts +446 -124
  166. package/src/context/context.test.ts +51 -12
  167. package/src/context/context.ts +162 -30
  168. package/src/context/session.test.ts +2 -1
  169. package/src/context/session.ts +115 -7
  170. package/src/context/types.ts +30 -12
  171. package/src/define.test.ts +13 -8
  172. package/src/define.ts +25 -4
  173. package/src/expect/index.ts +53 -23
  174. package/src/i18n/en.ts +81 -17
  175. package/src/i18n/zh-CN.ts +80 -17
  176. package/src/o11y/cost.test.ts +1 -0
  177. package/src/o11y/execution-tree.test.ts +1 -20
  178. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  179. package/src/o11y/otlp/parse.test.ts +1 -0
  180. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  181. package/src/o11y/parsers/bub.test.ts +1 -0
  182. package/src/o11y/parsers/claude-code.test.ts +1 -34
  183. package/src/o11y/parsers/openclaw.test.ts +154 -0
  184. package/src/o11y/parsers/openclaw.ts +310 -0
  185. package/src/o11y/prices.json +746 -311
  186. package/src/o11y/tool-names.test.ts +1 -0
  187. package/src/o11y/types.ts +16 -2
  188. package/src/report/aggregate.ts +178 -61
  189. package/src/report/built-in/index.tsx +9 -0
  190. package/src/report/components.tsx +625 -279
  191. package/src/report/compute.ts +723 -491
  192. package/src/report/dual-render.test.tsx +741 -1024
  193. package/src/report/flag.ts +104 -12
  194. package/src/report/format.ts +32 -12
  195. package/src/report/index.ts +119 -46
  196. package/src/report/load.ts +3 -2
  197. package/src/report/locale.ts +136 -65
  198. package/src/report/metrics.ts +108 -25
  199. package/src/report/primitives.tsx +196 -45
  200. package/src/report/react/AttemptList.tsx +30 -43
  201. package/src/report/react/DeltaTable.tsx +63 -45
  202. package/src/report/react/EvalList.tsx +0 -0
  203. package/src/report/react/ExperimentComparison.tsx +73 -0
  204. package/src/report/react/ExperimentList.tsx +50 -32
  205. package/src/report/react/MetricBars.tsx +5 -4
  206. package/src/report/react/MetricLine.tsx +13 -8
  207. package/src/report/react/MetricMatrix.tsx +2 -2
  208. package/src/report/react/MetricScatter.tsx +86 -34
  209. package/src/report/react/MetricTable.tsx +4 -76
  210. package/src/report/react/ScopeSummary.tsx +86 -0
  211. package/src/report/react/Scoreboard.tsx +28 -10
  212. package/src/report/react/cell.tsx +2 -2
  213. package/src/report/react/chart-math.test.ts +85 -0
  214. package/src/report/react/chart-math.ts +101 -22
  215. package/src/report/react/enhance.js +89 -5
  216. package/src/report/react/fixtures.ts +114 -154
  217. package/src/report/react/index.tsx +24 -39
  218. package/src/report/react/render.test.tsx +138 -158
  219. package/src/report/react/styles.css +243 -82
  220. package/src/report/report.test.ts +779 -841
  221. package/src/report/report.ts +423 -41
  222. package/src/report/text/faces.ts +290 -193
  223. package/src/report/text/plot.ts +1 -1
  224. package/src/report/text/table.ts +44 -7
  225. package/src/report/tree.ts +362 -104
  226. package/src/report/types.ts +261 -271
  227. package/src/report/web.ts +63 -20
  228. package/src/results/annotated-source.test.ts +62 -9
  229. package/src/results/annotated-source.ts +64 -6
  230. package/src/results/attempt-evidence.test.ts +13 -11
  231. package/src/results/attempt-evidence.ts +20 -13
  232. package/src/results/attempt-source.ts +6 -3
  233. package/src/results/copy.ts +150 -60
  234. package/src/results/host-equivalence.test.ts +34 -20
  235. package/src/results/index.ts +12 -4
  236. package/src/results/locator.test.ts +1 -22
  237. package/src/results/open.ts +15 -5
  238. package/src/results/publish.ts +149 -0
  239. package/src/results/results.test.ts +89 -54
  240. package/src/results/select.ts +104 -34
  241. package/src/results/truncate.ts +90 -0
  242. package/src/results/types.ts +43 -14
  243. package/src/results/writer.ts +31 -13
  244. package/src/runner/attempt.test.ts +138 -7
  245. package/src/runner/attempt.ts +603 -104
  246. package/src/runner/discover.test.ts +47 -0
  247. package/src/runner/discover.ts +36 -2
  248. package/src/runner/eval-source.test.ts +1 -27
  249. package/src/runner/feedback/agent.test.ts +504 -0
  250. package/src/runner/feedback/agent.ts +409 -0
  251. package/src/runner/feedback/ci.test.ts +562 -0
  252. package/src/runner/feedback/ci.ts +401 -0
  253. package/src/runner/feedback/coordinator.test.ts +317 -0
  254. package/src/runner/feedback/coordinator.ts +397 -0
  255. package/src/runner/feedback/failure.ts +40 -0
  256. package/src/runner/feedback/human.test.ts +616 -0
  257. package/src/runner/feedback/human.ts +535 -0
  258. package/src/runner/feedback/index.ts +66 -0
  259. package/src/runner/feedback/io.ts +78 -0
  260. package/src/runner/feedback/profile.test.ts +50 -0
  261. package/src/runner/feedback/profile.ts +58 -0
  262. package/src/runner/feedback/reducer.test.ts +395 -0
  263. package/src/runner/feedback/reducer.ts +260 -0
  264. package/src/runner/feedback/renderer.ts +82 -0
  265. package/src/runner/feedback/sink.ts +203 -0
  266. package/src/runner/feedback/testing.ts +106 -0
  267. package/src/runner/ledger.test.ts +230 -0
  268. package/src/runner/ledger.ts +329 -0
  269. package/src/runner/report.test.ts +128 -3
  270. package/src/runner/report.ts +33 -9
  271. package/src/runner/reporters/artifacts.ts +8 -2
  272. package/src/runner/reporters/braintrust.test.ts +8 -7
  273. package/src/runner/reporters/braintrust.ts +9 -2
  274. package/src/runner/reporters/index.ts +2 -2
  275. package/src/runner/reporters/json.test.ts +162 -0
  276. package/src/runner/reporters/json.ts +35 -8
  277. package/src/runner/reporters/shared.ts +1 -5
  278. package/src/runner/run.test.ts +760 -3
  279. package/src/runner/run.ts +243 -37
  280. package/src/runner/sandbox-prep.ts +3 -42
  281. package/src/runner/timing.ts +158 -0
  282. package/src/runner/types.ts +518 -22
  283. package/src/sandbox/checkpoint.test.ts +55 -0
  284. package/src/sandbox/checkpoint.ts +29 -8
  285. package/src/sandbox/cli-commands.ts +407 -0
  286. package/src/sandbox/docker.ts +115 -16
  287. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  288. package/src/sandbox/e2b-agent-template.ts +94 -0
  289. package/src/sandbox/e2b.ts +74 -9
  290. package/src/sandbox/errors.ts +111 -4
  291. package/src/sandbox/index.ts +2 -0
  292. package/src/sandbox/io-retry.test.ts +58 -0
  293. package/src/sandbox/io-retry.ts +45 -0
  294. package/src/sandbox/keep-registry.test.ts +86 -0
  295. package/src/sandbox/keep-registry.ts +142 -0
  296. package/src/sandbox/keep.ts +178 -0
  297. package/src/sandbox/paths.test.ts +1 -0
  298. package/src/sandbox/paths.ts +19 -8
  299. package/src/sandbox/registry.ts +20 -3
  300. package/src/sandbox/resolve.ts +76 -11
  301. package/src/sandbox/retry.test.ts +70 -0
  302. package/src/sandbox/retry.ts +46 -4
  303. package/src/sandbox/types.ts +44 -6
  304. package/src/sandbox/vercel.ts +43 -20
  305. package/src/scoring/collector.ts +60 -17
  306. package/src/scoring/coverage.ts +95 -0
  307. package/src/scoring/diff.ts +81 -0
  308. package/src/scoring/display.test.ts +121 -0
  309. package/src/scoring/display.ts +133 -0
  310. package/src/scoring/evidence.test.ts +189 -0
  311. package/src/scoring/judge.test.ts +142 -0
  312. package/src/scoring/judge.ts +15 -18
  313. package/src/scoring/scoped.ts +217 -50
  314. package/src/scoring/types.ts +117 -20
  315. package/src/scoring/verdict.ts +16 -4
  316. package/src/shared/aggregate.ts +8 -6
  317. package/src/shared/types.ts +31 -0
  318. package/src/show/compose.ts +50 -67
  319. package/src/show/index.ts +127 -56
  320. package/src/show/render.ts +662 -131
  321. package/src/show/report-host.test.ts +188 -0
  322. package/src/show/report-host.ts +375 -0
  323. package/src/show/show.test.ts +320 -54
  324. package/src/tty-line.ts +8 -26
  325. package/src/util.test.ts +1 -0
  326. package/src/util.ts +41 -0
  327. package/src/view/app/App.test.tsx +69 -0
  328. package/src/view/app/App.tsx +144 -48
  329. package/src/view/app/components/AttemptModal.tsx +423 -11
  330. package/src/view/app/components/CodeView.tsx +41 -14
  331. package/src/view/app/components/CopyControls.tsx +2 -2
  332. package/src/view/app/i18n.ts +37 -17
  333. package/src/view/app/lib/attempt-route.test.ts +1 -0
  334. package/src/view/app/lib/verdict.ts +7 -9
  335. package/src/view/app/main.tsx +13 -8
  336. package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
  337. package/src/view/app/types.ts +4 -1
  338. package/src/view/artifact-serving.test.ts +2 -1
  339. package/src/view/client-dist/app.css +1 -1
  340. package/src/view/client-dist/app.js +17 -17
  341. package/src/view/data.test.ts +10 -3
  342. package/src/view/data.ts +155 -49
  343. package/src/view/index.ts +56 -41
  344. package/src/view/server.ts +37 -15
  345. package/src/view/shared/types.ts +34 -5
  346. package/src/view/styles.css +227 -0
  347. package/src/view/view-report.test.ts +167 -62
  348. package/dist/report/built-ins/experiment-comparison.d.ts +0 -1
  349. package/dist/report/built-ins/experiment-comparison.js +0 -13
  350. package/dist/report/built-ins/index.d.ts +0 -1
  351. package/dist/report/built-ins/index.js +0 -2
  352. package/dist/report/react/GroupSummary.d.ts +0 -8
  353. package/dist/report/react/GroupSummary.js +0 -8
  354. package/dist/report/react/RunOverview.d.ts +0 -8
  355. package/dist/report/react/RunOverview.js +0 -12
  356. package/docs-site/zh/example/ai-agent-application.mdx +0 -152
  357. package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
  358. package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
  359. package/docs-site/zh/example/showcase.mdx +0 -39
  360. package/docs-site/zh/guides/publish-report.mdx +0 -91
  361. package/docs-site/zh/guides/sandbox-providers.mdx +0 -102
  362. package/src/report/built-in-user-parity.test.tsx +0 -640
  363. package/src/report/built-ins/experiment-comparison.tsx +0 -19
  364. package/src/report/built-ins/index.ts +0 -2
  365. package/src/report/react/GroupSummary.tsx +0 -66
  366. package/src/report/react/RunOverview.tsx +0 -109
  367. package/src/runner/reporters/console.ts +0 -70
  368. package/src/runner/reporters/live.test.ts +0 -56
  369. package/src/runner/reporters/live.ts +0 -247
  370. package/src/runner/reporters/quiet.test.ts +0 -66
  371. package/src/runner/reporters/quiet.ts +0 -49
  372. package/src/runner/reporters/table.ts +0 -277
  373. /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
  374. /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
  375. /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
  376. /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
  377. /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
  378. /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
@@ -0,0 +1,409 @@
1
+ // Agent profile renderer(见 docs/feature/experiments/cli.md「AI agent 怎么用」)。
2
+ //
3
+ // 目标读者是调用 niceeval 的 coding agent,不是人眼:
4
+ // - 运行中只有一条稳定 ASCII `key=value` envelope,`stderr` 追加,不含 ANSI、不依赖终端宽度、
5
+ // 字段名不随 `NICEEVAL_LANG` 变化(与 human 相对——human 走 i18n,agent 永远是这一份固定英文)。
6
+ // - 结束时 `stdout` 只有一个有界 handoff block:status、verdict summary、快照、最多 5 条失败、
7
+ // 每条失败的结构化主断言摘要(执行错误退回 reason)、以及可执行的 `show` 下钻命令。
8
+ // handoff 不内联 transcript/trace/源码/diff,
9
+ // agent 要看这些必须自己再发一条 `niceeval show @locator ...`(locator 是唯一的继续调查主键)。
10
+ //
11
+ // 不实现 `clearDynamic`/`redrawDynamic`/`activity`/`onLifecycle`:agent 没有「动态区域」概念,
12
+ // 不展示 active phase、不逐次输出 provisioning retry/backoff、不逐条打印 passed attempt —— 这些
13
+ // 目标行为由「不实现对应可选钩子」天然满足(见 renderer.ts 的接口注释),不需要在这里写
14
+ // `if (profile === "agent")` 分支。
15
+ //
16
+ // 与 human.ts 刻意不共享任何内部函数/类型(哪怕字面意思相同,如 group 推导、verdict 优先级)——
17
+ // 两个 renderer 各自独立成立,不应该因为共享一个私有 helper 而在未来被迫同步重构。
18
+ //
19
+ // 为什么「立即追加」的第一行不是在 "plan" 事件本身同步打印:
20
+ // reducer 处理 "plan" 事件之后,`running` 恒为 0(所有非携入 attempt 此刻都还在 `queued`,
21
+ // 还没有一个 `attempt:start` 落地)——这是 reducer 的确定性契约(见 reducer.test.ts),不是
22
+ // 这个 renderer 能绕过的。cli.md 给出的例子第一行却是 `running=4 queued=0`:那是「调度已经
23
+ // 把前几个 attempt 派发出去之后」的真实快照,不是 "plan" 那一刻的状态。硬要在 "plan" 上同步
24
+ // 打印,只会打印出一条与调度实际不符的假 "running=0" 行。这里改为:"plan" 到达时只记录一个
25
+ // 内部锚点(不打印),第一次 tick(默认 250ms 内)无条件打印一次(不受 30 秒空闲阈值约束)——
26
+ // 这样真实调度已经有机会把初始一批 attempt 派发出去,打印出的数字就是「此刻真实的调度快照」,
27
+ // 同时 elapsed 按整秒取整后仍然显示 "0s",满足「start 立即追加」——不是「精确到毫秒的
28
+ // plan 事件本身」,而是「第一次有意义的重画机会」。
29
+ //
30
+ // 为什么 error 的 checkpoint 行不像 cli.md 正文另一处例子那样拆 `code=`/`message=`:
31
+ // `FailureNotice` 的执行错误仍只有一个整句 `reason` 字段(如
32
+ // "sandbox-rate-limit: E2B sandbox allocation failed after 5 attempts"),没有独立的 error code
33
+ // 字段。把它按冒号切成 `code=`/`message=` 两段需要解析这句人类文案的格式约定,这正是
34
+ // plan 顶层「不接受」清单明确禁止的「Agent/CI 解析 human 文案」——即便这句文案*看起来*总是
35
+ // "code: message" 形状,依赖这个约定本身就是在解析。这里改为只输出类型里已有的结构化字段
36
+ // (locator/eval/experiment/phase/verdict)。failed assertion 已另带结构化摘要;error 的
37
+ // `reason` 留给有界的最终 handoff 逐条列出(那里是
38
+ // 独立的一行文本,不是 key=value 字段,不需要被下游再解析出子字段)。见本文件顶部大注释。
39
+
40
+ import type { FeedbackRenderer } from "./renderer.ts";
41
+ import type { FeedbackIO } from "./io.ts";
42
+ import type { DurableFeedbackEvent, FailureNotice, RunCompletion, RunFeedbackState, RunSummary } from "../types.ts";
43
+ import { assertionSummaryLines } from "../../scoring/display.ts";
44
+
45
+ /** 失败/errored checkpoint 与最终 handoff 共用同一个展开上限(cli.md「'立即追加'也必须有
46
+ * 上限」表:agent 前 5 条)。 */
47
+ const AGENT_FAILURE_CAP = 5;
48
+ /** 最终 handoff 的快照路径同样「有界」(checklist:「最终 stdout handoff 有界:...快照...」),
49
+ * 但 cli.md 没有给出具体数字——沿用与失败同一个上限,保持模块内单一、可预测的边界规则,
50
+ * 而不是为「快照」再发明一个不同的魔法数字。 */
51
+ const AGENT_SNAPSHOT_CAP = AGENT_FAILURE_CAP;
52
+ /** `--dry --output agent` 的 PLAN 行展开上限。cli.md 给出的唯一例子(5 个组合只展开 2 行 +
53
+ * "… 3 more")没有配套的文字规则(不像失败上限有明确的「agent 前 5 条」措辞),这个具体数字
54
+ * 是从那一个例子反推出来的,不是 docs 正文声明的产品规则——如果 stage G 接线时发现真实矩阵
55
+ * 需要不同的预览粒度,`renderAgentPlanEnvelope` 的 `rowCap` 参数可以覆盖这个默认值,不需要
56
+ * 改这个模块。 */
57
+ const AGENT_PLAN_ROW_CAP = 2;
58
+ /** 连续无永久事件多久才追加一条 heartbeat(cli.md:「连续 30 秒无永久事件才 heartbeat」)。 */
59
+ const AGENT_HEARTBEAT_IDLE_MS = 30_000;
60
+
61
+ export interface AgentRendererOptions {
62
+ io: FeedbackIO;
63
+ }
64
+
65
+ /**
66
+ * 创建 agent profile 的 `FeedbackRenderer`。只用 `io.stdout`/`io.stderr` 写文本,不读
67
+ * `io.stderr.isTTY`/`columns`/`rows`——agent 输出「不依赖终端宽度」,不像 human 那样有
68
+ * TTY/非 TTY 两个变体。
69
+ */
70
+ export function createAgentRenderer(options: AgentRendererOptions): FeedbackRenderer {
71
+ const { io } = options;
72
+
73
+ // 距上一次「有意义的输出」(第一次 tick 打印,或任意一次永久事件)过了多久,用来判断要不要
74
+ // 追加一条 heartbeat;由 appendDurable 无条件更新(见 checklist「failure/warning 后重置心跳
75
+ // 时钟」——这里把「warning」理解成任意永久事件,不止 diagnostic 一种,与 human 非 TTY 变体的
76
+ // 同名字段语义一致,但两个模块互不依赖,各自独立维护)。
77
+ let lastCheckpointAtMs = 0;
78
+ let printedFirstCheckpoint = false;
79
+ // "summary" 与 "saved" 是 coordinator.finish() 里连续 emit 的两个独立永久事件(见
80
+ // coordinator.ts 的 finish() 实现,中间不会插入其它事件)——handoff 需要两者合并成一个
81
+ // stdout block,所以在 "summary" 到达时先记下来,"saved" 到达时才真正拼出并写一次。
82
+ let pendingSummary: { summary: RunSummary; completion: RunCompletion; reused: number } | undefined;
83
+
84
+ function noteCheckpoint(atMs: number): void {
85
+ lastCheckpointAtMs = atMs;
86
+ }
87
+
88
+ return {
89
+ appendDurable(event, state) {
90
+ switch (event.type) {
91
+ case "plan":
92
+ // 不同步打印:见文件顶部「为什么第一行不是在 plan 上同步打印」。只记锚点,交给
93
+ // 第一次 tick 无条件打印。
94
+ noteCheckpoint(event.at);
95
+ return;
96
+
97
+ case "failure": {
98
+ noteCheckpoint(event.at);
99
+ writeFailureCheckpoint(io, event, state.freshFailureCount);
100
+ return;
101
+ }
102
+
103
+ case "diagnostic": {
104
+ noteCheckpoint(event.at);
105
+ if (!isFirstOccurrence(state, event.key)) return; // 去重后只追加一次(checklist)
106
+ io.stderr.write(
107
+ `${envelopeWord(event.severity)} ${kv("key", event.key)}${
108
+ event.identity?.evalId ? ` ${kv("eval", event.identity.evalId)}` : ""
109
+ }${event.identity?.experimentId ? ` ${kv("experiment", event.identity.experimentId)}` : ""} ${kv(
110
+ "message",
111
+ event.message,
112
+ )}\n`,
113
+ );
114
+ return;
115
+ }
116
+
117
+ case "budget-exhausted": {
118
+ noteCheckpoint(event.at);
119
+ if (!isFirstOccurrence(state, `budget-exhausted:${event.experimentId}`)) return;
120
+ io.stderr.write(
121
+ `NICEEVAL budget_exhausted ${kv("experiment", event.experimentId)} ${kv(
122
+ "spent",
123
+ event.spent.toFixed(2),
124
+ )} ${kv("unstarted", event.unstarted)}\n`,
125
+ );
126
+ return;
127
+ }
128
+
129
+ case "interrupted": {
130
+ noteCheckpoint(event.at);
131
+ if (!isFirstOccurrence(state, "interrupted")) return;
132
+ io.stderr.write(`NICEEVAL interrupted ${kv("elapsed", formatElapsedSeconds(state.elapsedMs))}\n`);
133
+ return;
134
+ }
135
+
136
+ case "reporter-error": {
137
+ noteCheckpoint(event.at);
138
+ if (!isFirstOccurrence(state, `reporter-error:${event.reporter}`)) return;
139
+ io.stderr.write(
140
+ `NICEEVAL reporter_error ${kv("reporter", event.reporter)} ${kv("required", event.required)} ${kv(
141
+ "message",
142
+ event.message,
143
+ )}\n`,
144
+ );
145
+ return;
146
+ }
147
+
148
+ case "summary":
149
+ // 只记录,不写——见上方 pendingSummary 注释。reused 只在 RunFeedbackState 上,
150
+ // RunSummary 本身没有这个字段,所以在这里(还能读到最新 state)一并存下来。
151
+ pendingSummary = { summary: event.summary, completion: event.completion, reused: state.reused };
152
+ return;
153
+
154
+ case "kept":
155
+ // 单行 kept 事件(与 run 事件同一 key=value 词法,见 docs/feature/sandbox/cli.md)。
156
+ io.stderr.write(
157
+ [
158
+ "NICEEVAL kept",
159
+ kv("locator", String(event.locator)),
160
+ kv("eval", event.identity.evalId),
161
+ kv("attempt", String(event.identity.attempt)),
162
+ kv("verdict", event.verdict),
163
+ kv("provider", event.provider),
164
+ kv("sandbox", event.sandboxId),
165
+ kv("enter", `niceeval sandbox enter ${event.sandboxId.slice(0, 12)}`),
166
+ ].join(" ") + "\n",
167
+ );
168
+ return;
169
+
170
+ case "saved":
171
+ writeHandoff(io, pendingSummary, event.paths, state.failures, state.kept.length > 0);
172
+ return;
173
+
174
+ default: {
175
+ // 穷尽性检查:新增 DurableFeedbackEvent 变体时这里编译期报错提醒补上对应分支。
176
+ const exhaustive: never = event;
177
+ return exhaustive;
178
+ }
179
+ }
180
+ },
181
+
182
+ onTick(event, state) {
183
+ const idle = event.at - lastCheckpointAtMs;
184
+ if (printedFirstCheckpoint && idle < AGENT_HEARTBEAT_IDLE_MS) return;
185
+ printedFirstCheckpoint = true;
186
+ noteCheckpoint(event.at);
187
+ io.stderr.write(progressLine(state, event.elapsedMs) + "\n");
188
+ },
189
+
190
+ // 没有 clearDynamic/redrawDynamic/activity/onLifecycle:见文件顶部注释——agent 不维护
191
+ // 动态区域、不逐次输出 provisioning retry/backoff、不逐条展示 active phase,这些由「不实现
192
+ // 对应可选钩子」天然满足(renderer.ts 的接口注释同样这样描述 agent/ci)。
193
+ };
194
+ }
195
+
196
+ /** 某个去重 key 在 `state.diagnostics` 里是不是第一次出现(count === 1)。budget-exhausted /
197
+ * interrupted / reporter-error 的去重 key 计算方式与 reducer.ts 完全一致(见该文件的
198
+ * `budget-exhausted:${experimentId}` / `"interrupted"` / `reporter-error:${reporter}`),
199
+ * 两处必须同源,不能各写一份 —— 这里选择直接复刻字符串拼法(而不是从 reducer.ts 导出常量),
200
+ * 因为这几个 key 是 `DurableFeedbackEvent` 自身语义的一部分(agent/ci/human 三个 renderer
201
+ * 都需要各自能算出同一个 key),不是 reducer 实现细节。 */
202
+ function isFirstOccurrence(state: RunFeedbackState, key: string): boolean {
203
+ return (state.diagnostics.find((d) => d.key === key)?.count ?? 0) <= 1;
204
+ }
205
+
206
+ function envelopeWord(severity: "warning" | "error"): string {
207
+ return severity === "error" ? "NICEEVAL error" : "NICEEVAL warning";
208
+ }
209
+
210
+ // ───────────────────────── 字段转义 / envelope 拼装(纯函数) ─────────────────────────
211
+
212
+ type FieldValue = string | number | boolean;
213
+
214
+ /** 字段值需要空格(或引号)时用 JSON string 转义,不依赖 locale、不需要下游猜测分隔规则
215
+ * (checklist:「字段值需要空格时使用 JSON string 转义」)。没有空格/引号的值保持裸 token,
216
+ * 这样最常见的 id/数字/枚举值(eval id、locator、phase……)仍然是最省 token 的形态。 */
217
+ function escapeFieldValue(raw: string): string {
218
+ return /[\s"]/.test(raw) ? JSON.stringify(raw) : raw;
219
+ }
220
+
221
+ function kv(key: string, value: FieldValue): string {
222
+ return typeof value === "string" ? `${key}=${escapeFieldValue(value)}` : `${key}=${value}`;
223
+ }
224
+
225
+ /** "0s" / "30s" / "75s" 风格——cli.md 全部 agent 例子只用到整秒,没有出现过 "1m 15s" 这种
226
+ * human 专用的分钟切换,这里就不实现那个格式,避免多一种 agent 从未用到的输出形态。 */
227
+ function formatElapsedSeconds(ms: number): string {
228
+ return `${Math.max(0, Math.floor(ms / 1000))}s`;
229
+ }
230
+
231
+ function progressLine(state: RunFeedbackState, elapsedMs: number): string {
232
+ return [
233
+ "NICEEVAL progress",
234
+ kv("elapsed", formatElapsedSeconds(elapsedMs)),
235
+ kv("total", state.total),
236
+ kv("reused", state.reused),
237
+ kv("running", state.running),
238
+ kv("queued", state.queued),
239
+ kv("completed", state.completed),
240
+ ].join(" ");
241
+ }
242
+
243
+ // ───────────────────────── failure / error checkpoint(stderr,立即追加,带上限) ─────────────────────────
244
+
245
+ function writeFailureCheckpoint(io: FeedbackIO, event: DurableFeedbackEvent & { type: "failure" }, countSoFar: number): void {
246
+ if (countSoFar === AGENT_FAILURE_CAP + 1) {
247
+ // 越过上限的第一条给一次 suppressed 提示,不是每条失败都重复一遍——不然「追加一次」会变成
248
+ // 新的刷屏源(与 human.ts 对同一权衡的处理方式一致,但这里是独立实现,不复用它的代码)。
249
+ // 必须在下面的「> CAP 一律静默」之前判断,否则这个分支永远不可达。
250
+ io.stderr.write(
251
+ `NICEEVAL warning ${kv("key", "failures-suppressed")} ${kv(
252
+ "message",
253
+ "further failed/errored attempts are not streamed individually; see the final result",
254
+ )}\n`,
255
+ );
256
+ return;
257
+ }
258
+ if (countSoFar > AGENT_FAILURE_CAP) return; // 上限之后完全静默,靠最终 handoff 给出准确总数
259
+ const parts = [
260
+ event.verdict === "errored" ? "NICEEVAL error" : "NICEEVAL failure",
261
+ kv("locator", event.locator),
262
+ kv("eval", event.identity.evalId),
263
+ ];
264
+ if (event.identity.experimentId) parts.push(kv("experiment", event.identity.experimentId));
265
+ if (event.phase) parts.push(kv("phase", event.phase));
266
+ parts.push(kv("verdict", event.verdict));
267
+ io.stderr.write(parts.join(" ") + "\n");
268
+ }
269
+
270
+ // ───────────────────────── 最终 handoff(stdout,有界,一次性写完) ─────────────────────────
271
+
272
+ /** required reporter(默认 artifacts、显式 --json/--junit)写失败必须让 handoff 判红,即便全部
273
+ * attempt 都通过——它不是 CompletionStatus 的第四个值(那个枚举只有
274
+ * complete/incomplete/interrupted 三态),但和 ci.ts 的 resultStatusWord() 同一条判断:
275
+ * 退出码已经因它非零(见 computeCiExitCode),handoff 不能反过来印一个会被误读成
276
+ * "全绿"的 passed。 */
277
+ function resultStatusWord(completion: RunCompletion, summary: RunSummary): string {
278
+ if (completion.status === "interrupted") return "interrupted";
279
+ if (completion.status === "incomplete") return "incomplete";
280
+ if (completion.reporterErrors.some((e) => e.required)) return "failed";
281
+ return summary.failed > 0 || summary.errored > 0 ? "failed" : "passed";
282
+ }
283
+
284
+ function summaryLine(summary: RunSummary, completion: RunCompletion, reused: number): string {
285
+ const paren = [`${reused} reused`];
286
+ if (completion.unstarted > 0) paren.push(`${completion.unstarted} unstarted`);
287
+ return `summary: ${summary.passed} passed, ${summary.failed} failed, ${summary.errored} errored (${paren.join(", ")})`;
288
+ }
289
+
290
+ /** 快照路径的共同「组」段(如 `.niceeval/compare/bub-e2b/<snapshot>` → `compare`),用于
291
+ * over-limit 提示里的 `niceeval view <group>`。多个 experiment 组混在一次 invocation 里时
292
+ * 组名不唯一,不猜一个可能错的 view 目标。与 human.ts 里同名逻辑刻意分开实现(见文件顶部
293
+ * 注释),不是共享同一个函数。 */
294
+ function deriveResultGroup(paths: readonly string[]): string | undefined {
295
+ const groups = new Set<string>();
296
+ for (const p of paths) {
297
+ const seg = p.split("/")[1];
298
+ if (seg) groups.add(seg);
299
+ }
300
+ return groups.size === 1 ? [...groups][0] : undefined;
301
+ }
302
+
303
+ function writeHandoff(
304
+ io: FeedbackIO,
305
+ pending: { summary: RunSummary; completion: RunCompletion; reused: number } | undefined,
306
+ paths: readonly string[],
307
+ failures: readonly FailureNotice[],
308
+ hasKept = false,
309
+ ): void {
310
+ if (!pending) return; // 不应发生:coordinator.finish() 恒先 emit "summary" 再 emit "saved"。
311
+ const { summary, completion, reused } = pending;
312
+ const lines: string[] = [
313
+ `NICEEVAL RESULT ${resultStatusWord(completion, summary)}`,
314
+ summaryLine(summary, completion, reused),
315
+ ];
316
+
317
+ if (paths.length > 0) {
318
+ lines.push("snapshots:");
319
+ for (const p of paths.slice(0, AGENT_SNAPSHOT_CAP)) lines.push(` - ${p}`);
320
+ if (paths.length > AGENT_SNAPSHOT_CAP) lines.push(` … ${paths.length - AGENT_SNAPSHOT_CAP} more`);
321
+ }
322
+
323
+ if (failures.length > 0) {
324
+ const shown = failures.slice(0, AGENT_FAILURE_CAP);
325
+ if (failures.length > AGENT_FAILURE_CAP) {
326
+ const group = deriveResultGroup(paths);
327
+ const hint = group ? `run \`niceeval view ${group}\`` : "check the results directory";
328
+ lines.push(`failures: ${failures.length} total, showing ${AGENT_FAILURE_CAP}`);
329
+ for (const f of shown) lines.push(...handoffFailureLines(f));
330
+ lines.push(` … ${failures.length - AGENT_FAILURE_CAP} more; inspect the JSON result or ${hint}`);
331
+ } else {
332
+ lines.push("failures:");
333
+ for (const f of shown) lines.push(...handoffFailureLines(f));
334
+ }
335
+ // 下钻命令只给第一条失败做示范,不逐条重复三行(与「有界 handoff」的整体原则一致——
336
+ // 逐条重复会让「有界」变成新的刷屏源)。
337
+ const first = shown[0]!;
338
+ lines.push("next:");
339
+ lines.push(` niceeval show ${first.locator}`);
340
+ lines.push(` niceeval show ${first.locator} --eval`);
341
+ lines.push(` niceeval show ${first.locator} --execution`);
342
+ lines.push(` niceeval show ${first.locator} --diff`);
343
+ if (hasKept) lines.push(" niceeval sandbox stop --all");
344
+ } else if (hasKept) {
345
+ lines.push("next:");
346
+ lines.push(" niceeval sandbox stop --all");
347
+ }
348
+
349
+ io.stdout.write(lines.join("\n") + "\n");
350
+ }
351
+
352
+ function handoffFailureLines(f: FailureNotice): string[] {
353
+ const bracket = f.identity.experimentId ?? f.who;
354
+ const summary = f.assertion ? assertionSummaryLines(f.assertion) : [f.reason];
355
+ return [
356
+ ` - ${f.locator} ${f.identity.evalId} [${bracket}]`,
357
+ ...summary.map((line, index) => `${index === 0 ? " " : " "}${line}`),
358
+ ];
359
+ }
360
+
361
+ // ───────────────────────── `--dry --output agent`:稳定 PLAN envelope ─────────────────────────
362
+
363
+ /** 一个 (config, eval) 组合在 PLAN 预览里的一行。`label` 推荐传完整 `experimentId`(带 group
364
+ * 前缀,如 `compare/bub-e2b`,不是 `runWho()` 的 basename——cli.md 的 PLAN 例子里两行分别是
365
+ * `compare/bub-e2b`/`compare/codex`,是完整 experimentId);没有 experiment 时调用方可以传
366
+ * `runWho()` 的结果做退回。这个模块不替调用方决定用哪个,只按给定的字符串渲染。 */
367
+ export interface AgentPlanRow {
368
+ label: string;
369
+ evalId: string;
370
+ }
371
+
372
+ export interface AgentDryPlanInput {
373
+ /** 总 attempt 数(= evals × configs × runs)。 */
374
+ total: number;
375
+ /** 去重后的 eval 数。 */
376
+ evals: number;
377
+ /** (agent, model, flags) 配置组合数。 */
378
+ configs: number;
379
+ /** 每个 (config, eval) 组合重复的次数(实验/CLI 的 `--runs`)。 */
380
+ runs: number;
381
+ /** 完整 (label, evalId) 组合清单,按展示顺序排列;不需要调用方预先截断。 */
382
+ rows: readonly AgentPlanRow[];
383
+ }
384
+
385
+ /**
386
+ * `--dry --output agent`(checklist:「输出稳定 PLAN envelope,不运行、不落盘」)。这是一个
387
+ * 独立的纯函数,不经过 `FeedbackRenderer`/coordinator——dry run 根本不派发 attempt,没有
388
+ * 「运行中的反馈状态」可言,调用方(CLI 接线阶段)直接拿 `AgentRun`/`DiscoveredEval` 展开出的
389
+ * 组合表调这个函数即可,不需要先构造一个整装的 coordinator 再假装 start 一次。
390
+ *
391
+ * `rowCap` 默认值(见上面 `AGENT_PLAN_ROW_CAP` 的注释)不是 docs 正文声明的强制数字,调用方
392
+ * 可以按真实场景覆盖。
393
+ */
394
+ export function renderAgentPlanEnvelope(input: AgentDryPlanInput, rowCap: number = AGENT_PLAN_ROW_CAP): string {
395
+ const lines = [
396
+ [
397
+ "NICEEVAL PLAN",
398
+ kv("total", input.total),
399
+ kv("evals", input.evals),
400
+ kv("configs", input.configs),
401
+ kv("runs", input.runs),
402
+ ].join(" "),
403
+ ];
404
+ const shown = input.rows.slice(0, Math.max(0, rowCap));
405
+ const labelWidth = shown.reduce((w, r) => Math.max(w, r.label.length), 0);
406
+ for (const row of shown) lines.push(`${row.label.padEnd(labelWidth)} ${row.evalId}`);
407
+ if (input.rows.length > shown.length) lines.push(`… ${input.rows.length - shown.length} more`);
408
+ return lines.join("\n");
409
+ }