niceeval 0.5.4 → 0.6.1

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 (337) hide show
  1. package/INDEX.md +55 -0
  2. package/dist/agents/types.d.ts +321 -0
  3. package/dist/context/types.d.ts +335 -0
  4. package/dist/i18n/core.d.ts +11 -0
  5. package/dist/i18n/en.d.ts +181 -0
  6. package/dist/i18n/index.d.ts +5 -0
  7. package/dist/i18n/zh-CN.d.ts +183 -0
  8. package/dist/o11y/otlp/parse.d.ts +3 -0
  9. package/dist/o11y/otlp/receiver.d.ts +21 -0
  10. package/dist/o11y/otlp/turn-otel.d.ts +43 -0
  11. package/dist/o11y/types.d.ts +190 -0
  12. package/dist/report/aggregate.d.ts +57 -0
  13. package/dist/report/aggregate.js +212 -0
  14. package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
  15. package/dist/report/built-ins/experiment-comparison.js +13 -0
  16. package/dist/report/built-ins/index.d.ts +1 -0
  17. package/dist/report/built-ins/index.js +2 -0
  18. package/dist/report/components.d.ts +180 -0
  19. package/dist/report/components.js +132 -0
  20. package/dist/report/compute.d.ts +106 -0
  21. package/dist/report/compute.js +600 -0
  22. package/dist/report/flag.d.ts +6 -0
  23. package/dist/report/flag.js +8 -0
  24. package/dist/report/format.d.ts +26 -0
  25. package/dist/report/format.js +109 -0
  26. package/dist/report/index.d.ts +19 -0
  27. package/dist/report/index.js +30 -0
  28. package/dist/report/load.d.ts +12 -0
  29. package/dist/report/load.js +56 -0
  30. package/dist/report/locale.d.ts +94 -0
  31. package/dist/report/locale.js +183 -0
  32. package/dist/report/metrics.d.ts +21 -0
  33. package/dist/report/metrics.js +115 -0
  34. package/dist/report/primitives.d.ts +64 -0
  35. package/dist/report/primitives.js +113 -0
  36. package/dist/report/react/AttemptList.d.ts +23 -0
  37. package/dist/report/react/AttemptList.js +19 -0
  38. package/dist/report/react/DeltaTable.d.ts +8 -0
  39. package/dist/report/react/DeltaTable.js +24 -0
  40. package/dist/report/react/EvalList.d.ts +10 -0
  41. package/dist/report/react/EvalList.js +0 -0
  42. package/dist/report/react/ExperimentList.d.ts +11 -0
  43. package/dist/report/react/ExperimentList.js +67 -0
  44. package/dist/report/react/GroupSummary.d.ts +8 -0
  45. package/dist/report/react/GroupSummary.js +8 -0
  46. package/dist/report/react/MetricBars.d.ts +10 -0
  47. package/dist/report/react/MetricBars.js +48 -0
  48. package/dist/report/react/MetricLine.d.ts +9 -0
  49. package/dist/report/react/MetricLine.js +68 -0
  50. package/dist/report/react/MetricMatrix.d.ts +10 -0
  51. package/dist/report/react/MetricMatrix.js +23 -0
  52. package/dist/report/react/MetricScatter.d.ts +9 -0
  53. package/dist/report/react/MetricScatter.js +96 -0
  54. package/dist/report/react/MetricTable.d.ts +11 -0
  55. package/dist/report/react/MetricTable.js +26 -0
  56. package/dist/report/react/RunOverview.d.ts +8 -0
  57. package/dist/report/react/RunOverview.js +12 -0
  58. package/dist/report/react/Scoreboard.d.ts +8 -0
  59. package/dist/report/react/Scoreboard.js +26 -0
  60. package/dist/report/react/cell.d.ts +9 -0
  61. package/dist/report/react/cell.js +9 -0
  62. package/dist/report/react/chart-math.d.ts +14 -0
  63. package/dist/report/react/chart-math.js +68 -0
  64. package/dist/report/react/colors.d.ts +14 -0
  65. package/dist/report/react/colors.js +45 -0
  66. package/dist/report/react/fixtures.d.ts +19 -0
  67. package/dist/report/react/fixtures.js +398 -0
  68. package/dist/report/react/format.d.ts +3 -0
  69. package/dist/report/react/format.js +7 -0
  70. package/dist/report/react/index.d.ts +6 -0
  71. package/dist/report/react/index.js +14 -0
  72. package/dist/report/report.d.ts +24 -0
  73. package/dist/report/report.js +49 -0
  74. package/dist/report/text/faces.d.ts +29 -0
  75. package/dist/report/text/faces.js +541 -0
  76. package/dist/report/text/layout.d.ts +75 -0
  77. package/dist/report/text/layout.js +173 -0
  78. package/dist/report/text/plot.d.ts +37 -0
  79. package/dist/report/text/plot.js +81 -0
  80. package/dist/report/text/table.d.ts +8 -0
  81. package/dist/report/text/table.js +86 -0
  82. package/dist/report/tree.d.ts +99 -0
  83. package/dist/report/tree.js +228 -0
  84. package/dist/report/types.d.ts +408 -0
  85. package/dist/report/types.js +4 -0
  86. package/dist/report/web.d.ts +15 -0
  87. package/dist/report/web.js +37 -0
  88. package/dist/results/locator.d.ts +75 -0
  89. package/dist/results/locator.js +0 -0
  90. package/dist/results/select.d.ts +45 -0
  91. package/dist/results/select.js +230 -0
  92. package/dist/results/source-hash.d.ts +9 -0
  93. package/dist/results/types.d.ts +204 -0
  94. package/dist/runner/eval-source.d.ts +22 -0
  95. package/dist/runner/fingerprint.d.ts +23 -0
  96. package/dist/runner/types.d.ts +332 -0
  97. package/dist/sandbox/docker-stream.d.ts +24 -0
  98. package/dist/sandbox/docker.d.ts +84 -0
  99. package/dist/sandbox/e2b.d.ts +31 -0
  100. package/dist/sandbox/errors.d.ts +4 -0
  101. package/dist/sandbox/local-files.d.ts +6 -0
  102. package/dist/sandbox/paths.d.ts +4 -0
  103. package/dist/sandbox/registry.d.ts +13 -0
  104. package/dist/sandbox/resolve.d.ts +40 -0
  105. package/dist/sandbox/retry.d.ts +10 -0
  106. package/dist/sandbox/shell.d.ts +11 -0
  107. package/dist/sandbox/source-files.d.ts +13 -0
  108. package/dist/sandbox/types.d.ts +203 -0
  109. package/dist/sandbox/vercel.d.ts +37 -0
  110. package/dist/scoring/types.d.ts +70 -0
  111. package/dist/shared/aggregate.d.ts +29 -0
  112. package/dist/shared/aggregate.js +55 -0
  113. package/dist/shared/types.d.ts +27 -0
  114. package/dist/shared/verdict.d.ts +27 -0
  115. package/dist/shared/verdict.js +34 -0
  116. package/dist/tty-line.d.ts +6 -0
  117. package/dist/types.d.ts +7 -0
  118. package/dist/util.d.ts +22 -0
  119. package/docs-site/zh/concepts/adapter.mdx +7 -7
  120. package/docs-site/zh/concepts/assert.mdx +19 -18
  121. package/docs-site/zh/concepts/evals.mdx +12 -11
  122. package/docs-site/zh/concepts/experiment.mdx +2 -2
  123. package/docs-site/zh/concepts/judge.mdx +9 -9
  124. package/docs-site/zh/concepts/overview.mdx +3 -3
  125. package/docs-site/zh/concepts/tier.mdx +6 -6
  126. package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
  127. package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
  128. package/docs-site/zh/guides/agent-feedback-loop.mdx +237 -78
  129. package/docs-site/zh/guides/connect-otel.mdx +3 -3
  130. package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
  131. package/docs-site/zh/guides/custom-reports.mdx +196 -36
  132. package/docs-site/zh/guides/experiments.mdx +7 -1
  133. package/docs-site/zh/guides/fixtures.mdx +4 -2
  134. package/docs-site/zh/guides/official-adapters.mdx +31 -9
  135. package/docs-site/zh/guides/publish-report.mdx +4 -4
  136. package/docs-site/zh/guides/report-components.mdx +194 -55
  137. package/docs-site/zh/guides/reporters.mdx +2 -2
  138. package/docs-site/zh/guides/results-data.mdx +61 -34
  139. package/docs-site/zh/guides/runner.mdx +2 -2
  140. package/docs-site/zh/guides/sandbox-agent.mdx +3 -2
  141. package/docs-site/zh/guides/{sandbox-backends.mdx → sandbox-providers.mdx} +12 -12
  142. package/docs-site/zh/guides/viewing-results.mdx +200 -79
  143. package/docs-site/zh/guides/write-experiment.mdx +9 -1
  144. package/docs-site/zh/guides/write-send.mdx +4 -3
  145. package/docs-site/zh/index.mdx +4 -3
  146. package/docs-site/zh/introduction.mdx +2 -1
  147. package/docs-site/zh/quickstart.mdx +5 -3
  148. package/docs-site/zh/reference/builtin-agents.mdx +38 -13
  149. package/docs-site/zh/reference/capabilities.mdx +1 -1
  150. package/docs-site/zh/reference/cli.mdx +36 -13
  151. package/docs-site/zh/reference/define-agent.mdx +15 -13
  152. package/docs-site/zh/reference/define-config.mdx +1 -1
  153. package/docs-site/zh/reference/define-eval.mdx +3 -3
  154. package/docs-site/zh/reference/events.mdx +9 -3
  155. package/docs-site/zh/reference/expect.mdx +13 -8
  156. package/package.json +10 -6
  157. package/src/agents/bub.ts +129 -42
  158. package/src/agents/claude-code.test.ts +152 -0
  159. package/src/agents/claude-code.ts +127 -13
  160. package/src/agents/codex.test.ts +192 -0
  161. package/src/agents/codex.ts +151 -12
  162. package/src/agents/index.ts +9 -3
  163. package/src/agents/manifest.ts +41 -0
  164. package/src/agents/plugin-config.test.ts +94 -0
  165. package/src/agents/shared.ts +33 -2
  166. package/src/agents/skills.test.ts +214 -0
  167. package/src/agents/skills.ts +304 -0
  168. package/src/agents/streaming.ts +5 -5
  169. package/src/agents/types.ts +59 -3
  170. package/src/cli.ts +74 -47
  171. package/src/context/context.test.ts +20 -2
  172. package/src/context/control-flow.ts +1 -1
  173. package/src/context/session.test.ts +92 -1
  174. package/src/context/session.ts +23 -1
  175. package/src/context/types.ts +4 -4
  176. package/src/define.test.ts +8 -8
  177. package/src/define.ts +11 -11
  178. package/src/expect/index.ts +5 -5
  179. package/src/i18n/en.ts +33 -15
  180. package/src/i18n/zh-CN.ts +37 -21
  181. package/src/index.ts +1 -1
  182. package/src/o11y/execution-tree.test.ts +452 -0
  183. package/src/o11y/execution-tree.ts +367 -0
  184. package/src/o11y/otlp/receiver.ts +1 -1
  185. package/src/o11y/otlp/select.ts +7 -3
  186. package/src/o11y/parsers/claude-code.test.ts +175 -0
  187. package/src/o11y/parsers/claude-code.ts +32 -0
  188. package/src/o11y/types.ts +6 -1
  189. package/src/report/aggregate.ts +26 -20
  190. package/src/report/built-in-user-parity.test.tsx +640 -0
  191. package/src/report/built-ins/experiment-comparison.tsx +19 -0
  192. package/src/report/built-ins/index.ts +2 -0
  193. package/src/report/components.tsx +225 -50
  194. package/src/report/compute.ts +335 -54
  195. package/src/report/dual-render.test.tsx +1129 -0
  196. package/src/report/flag.ts +1 -1
  197. package/src/report/format.ts +35 -2
  198. package/src/report/index.ts +47 -14
  199. package/src/report/load.ts +1 -1
  200. package/src/report/locale.ts +229 -0
  201. package/src/report/metrics.ts +39 -15
  202. package/src/report/primitives.tsx +100 -2
  203. package/src/report/react/AttemptList.tsx +113 -0
  204. package/src/report/react/DeltaTable.tsx +9 -6
  205. package/src/report/react/EvalList.tsx +0 -0
  206. package/src/report/react/ExperimentList.tsx +220 -0
  207. package/src/report/react/GroupSummary.tsx +66 -0
  208. package/src/report/react/MetricBars.tsx +24 -13
  209. package/src/report/react/MetricLine.tsx +21 -21
  210. package/src/report/react/MetricMatrix.tsx +8 -4
  211. package/src/report/react/MetricScatter.tsx +126 -96
  212. package/src/report/react/MetricTable.tsx +115 -15
  213. package/src/report/react/RunOverview.tsx +48 -26
  214. package/src/report/react/Scoreboard.tsx +15 -7
  215. package/src/report/react/cell.tsx +18 -9
  216. package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
  217. package/src/report/react/colors.ts +24 -15
  218. package/src/report/react/enhance.js +168 -0
  219. package/src/report/react/fixtures.ts +169 -34
  220. package/src/report/react/format.ts +1 -1
  221. package/src/report/react/index.tsx +21 -6
  222. package/src/report/react/render.test.tsx +180 -31
  223. package/src/report/react/styles.css +432 -213
  224. package/src/report/report.test.ts +398 -94
  225. package/src/report/report.ts +29 -11
  226. package/src/report/text/faces.ts +344 -96
  227. package/src/report/text/layout.ts +76 -11
  228. package/src/report/text/table.ts +98 -0
  229. package/src/report/tree.ts +103 -20
  230. package/src/report/types.ts +184 -25
  231. package/src/report/web.ts +46 -19
  232. package/src/results/annotated-source.test.ts +146 -0
  233. package/src/results/annotated-source.ts +128 -0
  234. package/src/results/attempt-evidence.test.ts +158 -0
  235. package/src/results/attempt-evidence.ts +158 -0
  236. package/src/results/attempt-source.ts +48 -0
  237. package/src/results/copy.ts +154 -139
  238. package/src/results/format.ts +54 -47
  239. package/src/results/host-equivalence.test.ts +527 -0
  240. package/src/results/index.ts +56 -14
  241. package/src/results/locator.test.ts +198 -0
  242. package/src/results/locator.ts +0 -0
  243. package/src/results/open.ts +392 -175
  244. package/src/results/results.test.ts +943 -494
  245. package/src/results/select.ts +129 -23
  246. package/src/results/skipped-notice.ts +0 -0
  247. package/src/results/source-hash.ts +28 -0
  248. package/src/results/types.ts +98 -62
  249. package/src/results/writer.ts +304 -176
  250. package/src/runner/attempt.test.ts +170 -0
  251. package/src/runner/attempt.ts +48 -13
  252. package/src/runner/discover.test.ts +65 -0
  253. package/src/runner/discover.ts +6 -2
  254. package/src/runner/eval-source.test.ts +121 -0
  255. package/src/runner/eval-source.ts +45 -0
  256. package/src/runner/fingerprint.ts +54 -1
  257. package/src/runner/remote-sandbox.ts +1 -1
  258. package/src/runner/report.test.ts +6 -8
  259. package/src/runner/report.ts +5 -4
  260. package/src/runner/reporters/artifacts.ts +27 -41
  261. package/src/runner/reporters/braintrust.test.ts +3 -3
  262. package/src/runner/reporters/braintrust.ts +1 -1
  263. package/src/runner/reporters/console.ts +6 -6
  264. package/src/runner/reporters/json.ts +4 -4
  265. package/src/runner/reporters/live.test.ts +56 -0
  266. package/src/runner/reporters/live.ts +36 -10
  267. package/src/runner/reporters/quiet.test.ts +66 -0
  268. package/src/runner/reporters/quiet.ts +49 -0
  269. package/src/runner/reporters/shared.ts +5 -5
  270. package/src/runner/reporters/table.ts +17 -17
  271. package/src/runner/run.ts +61 -102
  272. package/src/runner/types.ts +43 -32
  273. package/src/sandbox/checkpoint.ts +1 -1
  274. package/src/sandbox/docker.ts +15 -2
  275. package/src/sandbox/e2b.ts +11 -5
  276. package/src/sandbox/errors.ts +13 -0
  277. package/src/sandbox/index.ts +3 -3
  278. package/src/sandbox/local-files.ts +1 -1
  279. package/src/sandbox/registry.ts +6 -5
  280. package/src/sandbox/resolve.ts +38 -23
  281. package/src/sandbox/retry.test.ts +68 -0
  282. package/src/sandbox/retry.ts +44 -0
  283. package/src/sandbox/types.ts +24 -24
  284. package/src/sandbox/vercel.ts +17 -3
  285. package/src/scoring/collector.ts +3 -3
  286. package/src/scoring/judge.ts +2 -2
  287. package/src/scoring/scoped.ts +13 -1
  288. package/src/scoring/types.ts +7 -7
  289. package/src/scoring/verdict.ts +4 -4
  290. package/src/shared/aggregate.ts +23 -3
  291. package/src/shared/types.ts +2 -2
  292. package/src/shared/{outcome.ts → verdict.ts} +15 -15
  293. package/src/show/compose.ts +12 -132
  294. package/src/show/index.ts +119 -95
  295. package/src/show/render.ts +462 -159
  296. package/src/show/show.test.ts +471 -253
  297. package/src/source-loc.ts +1 -1
  298. package/src/tty-line.ts +32 -0
  299. package/src/types.ts +2 -2
  300. package/src/util.test.ts +21 -1
  301. package/src/util.ts +5 -1
  302. package/src/view/app/App.tsx +33 -198
  303. package/src/view/app/components/AttemptModal.tsx +6 -6
  304. package/src/view/app/components/CodeView.tsx +2 -2
  305. package/src/view/app/components/CopyControls.tsx +32 -36
  306. package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
  307. package/src/view/app/components/ui/badge.tsx +1 -1
  308. package/src/view/app/components/ui/dialog.tsx +2 -2
  309. package/src/view/app/i18n.ts +40 -31
  310. package/src/view/app/lib/artifact-url.ts +1 -1
  311. package/src/view/app/lib/attempt-route.test.ts +37 -47
  312. package/src/view/app/lib/attempt-route.ts +26 -26
  313. package/src/view/app/lib/rows.ts +9 -112
  314. package/src/view/app/lib/verdict.ts +25 -0
  315. package/src/view/app/main.tsx +9 -12
  316. package/src/view/app/pages/RunsPage.tsx +4 -4
  317. package/src/view/app/pages/TracesPage.tsx +3 -3
  318. package/src/view/app/shared.ts +0 -1
  319. package/src/view/app/types.ts +6 -51
  320. package/src/view/artifact-serving.test.ts +136 -0
  321. package/src/view/client-dist/app.css +1 -1
  322. package/src/view/client-dist/app.js +21 -21
  323. package/src/view/data.test.ts +214 -143
  324. package/src/view/data.ts +166 -120
  325. package/src/view/index.ts +23 -11
  326. package/src/view/server.ts +52 -17
  327. package/src/view/shared/types.ts +22 -31
  328. package/src/view/styles.css +117 -327
  329. package/src/view/view-report.test.ts +146 -98
  330. package/src/report/default-report.tsx +0 -222
  331. package/src/report/dual-face.test.tsx +0 -527
  332. package/src/report/react/CaseList.tsx +0 -70
  333. package/src/view/app/components/CostScoreChart.tsx +0 -147
  334. package/src/view/app/components/ExperimentTable.tsx +0 -284
  335. package/src/view/app/components/GroupSelector.tsx +0 -61
  336. package/src/view/app/components/primitives.tsx +0 -43
  337. package/src/view/app/lib/outcome.ts +0 -82
package/src/cli.ts CHANGED
@@ -2,21 +2,23 @@
2
2
  // niceeval exp [组|配置] [pattern] 跑实验
3
3
  // niceeval show [pattern] 终端读结果:榜单 / 单 eval / 证据切面 / 时间轴 / --report
4
4
  // niceeval list 只列出发现到的 eval
5
- // niceeval clean 删除 .niceeval/ 历史运行工件
5
+ // niceeval clean 删除 .niceeval/ 历史运行 artifact
6
6
 
7
7
  import { spawn } from "node:child_process";
8
8
  import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
9
9
  import { existsSync } from "node:fs";
10
- import { join } from "node:path";
10
+ import { join, relative } from "node:path";
11
11
  import { pathToFileURL } from "node:url";
12
12
  import { parseArgs as nodeParseArgs } from "node:util";
13
13
  import { discoverEvals, discoverExperiments, makeFilter } from "./runner/discover.ts";
14
14
  import { runEvals, type AgentRun } from "./runner/run.ts";
15
15
  import { runWho } from "./runner/types.ts";
16
+ import { planCarry } from "./runner/fingerprint.ts";
16
17
  import { stopAllSandboxes, liveSandboxCount } from "./sandbox/registry.ts";
17
- import { evalLevelStats } from "./shared/outcome.ts";
18
+ import { evalLevelStats } from "./shared/verdict.ts";
18
19
  import { sandboxRecommendedConcurrency } from "./sandbox/resolve.ts";
19
20
  import { Console as ConsoleReporter } from "./runner/reporters/console.ts";
21
+ import { Quiet as QuietReporter } from "./runner/reporters/quiet.ts";
20
22
  import { Json, JUnit } from "./runner/reporters/json.ts";
21
23
  import { Live as LiveReporter, type LiveRow } from "./runner/reporters/live.ts";
22
24
  import { Artifacts as ArtifactsReporter } from "./runner/reporters/artifacts.ts";
@@ -28,7 +30,11 @@ import {
28
30
  IncompatibleResultsError,
29
31
  ViewInputError,
30
32
  } from "./view/index.ts";
31
- import { ReportLoadError } from "./report/load.ts";
33
+ // load.ts 本身没有 JSX,但它的 ReportDefinition/ReportLoadError 要和 view 报告槽实际装载
34
+ // --report 用的那份(dist/report/**,见 tsconfig.report-build.json)是同一个模块实例——
35
+ // `unique symbol` 品牌与 class 的 instanceof 都按声明处的模块身份判定,raw src 和编译产物
36
+ // 是两份不同源码位置,即使运行时同名同形,TS 类型与 instanceof 都不认。
37
+ import { ReportLoadError } from "../dist/report/load.js";
32
38
  import { runShow } from "./show/index.ts";
33
39
  import { t } from "./i18n/index.ts";
34
40
  import { formatThrown, upsertManagedBlock } from "./util.ts";
@@ -66,15 +72,14 @@ interface Flags {
66
72
  port?: number;
67
73
  help: boolean;
68
74
  version: boolean;
69
- // ── show 专属(位置参数仍是 eval id 前缀;这些 flag 选「怎么看」)──
70
- transcript: boolean;
71
- trace: boolean;
75
+ // ── show 专属(位置参数仍是 eval id 前缀 / `@<locator>`;这些 flag 选「怎么看」)──
76
+ eval: boolean;
77
+ execution: boolean;
72
78
  diff: boolean;
73
79
  /** --diff=<路径>(必须 = 连写;空格形式会把路径当 eval id 前缀,按文档如此)。 */
74
80
  diffPath?: string;
75
81
  history: boolean;
76
82
  experiment?: string;
77
- attempt?: number;
78
83
  run?: string;
79
84
  report?: string;
80
85
  }
@@ -109,31 +114,31 @@ const FLAG_OPTIONS = {
109
114
  /** `view` 命令专用:指定本地服务器监听端口。 */
110
115
  port: { type: "string" },
111
116
  // show 的证据切面 / 时间轴 / 报告装载(docs-site/zh/guides/viewing-results.mdx)。
112
- /** `show` 命令专用:渲染单个 eval 的完整对话与工具调用(证据切面) */
113
- transcript: { type: "boolean" },
114
- /** `show` 命令专用:渲染单个 eval trace 瀑布文本版(证据切面)。 */
115
- trace: { type: "boolean" },
117
+ // 证据切面只认 `@<locator>`(或收窄到单个 eval 的前缀)选出的那一个 attempt——不再有
118
+ // 数字 `--attempt`,选哪个 attempt 由 locator 精确指名,不是「先选 eval 再挑第几次」。
119
+ /** `show` 命令专用:该 attempt 运行时保存的 Eval 源码,gate/soft 断言标回源码行(证据切面)。 */
120
+ eval: { type: "boolean" },
121
+ /** `show` 命令专用:该 attempt 的标准执行事件流(消息、thinking、Skill load、工具调用/结果);有 OTel 时同一节点补时间(证据切面)。 */
122
+ execution: { type: "boolean" },
116
123
  // --diff 是布尔;--diff=<路径> 在 parseArgs 前预扫成 diffPath(路径必须 = 连写,
117
124
  // 空格形式的下一个 token 仍是位置参数 = eval id 前缀,与文档一致)。
118
125
  /** `show` 命令专用:sandbox 里的文件改动摘要;`--diff=<文件路径>` 看单个文件的完整改动(路径必须 `=` 连写)。 */
119
126
  diff: { type: "boolean" },
120
127
  /** `show` 命令专用:跨 run 时间轴,只列真实执行;与 `--report` 互斥。 */
121
128
  history: { type: "boolean" },
122
- /** `show` / `view` 命令专用:选集只留该实验。 */
129
+ /** `show` / `view` 命令专用:Selection 只留该实验。 */
123
130
  experiment: { type: "string" },
124
- /** `show` 命令专用:指定详情 / 证据切面看第几次 attempt(与展示一致的 1 计序号)。 */
125
- attempt: { type: "string" },
126
- /** `show` / `view` 命令专用:钉死看某一个结果目录(历史 run 或 `copySnapshots` 产物)。 */
131
+ /** `show` / `view` 命令专用:钉死看某一个结果目录(某次快照或 `copySnapshots` 产物)。 */
127
132
  run: { type: "string" },
128
- /** `show` / `view` 命令专用:用你的报告文件替换默认报告(文件默认导出 `defineReport(...)`) */
133
+ /** `show` / `view` 命令专用:渲染你的报告文件(文件默认导出 `defineReport(...)`);show 用它替换 Attempt 索引,view 用它替换默认分析报告。 */
129
134
  report: { type: "string" },
130
135
  /** 只打印本次会匹配到的 eval × 运行配置,不实际执行。 */
131
136
  dry: { type: "boolean" },
132
- /** 关闭控制台 / live 进度输出(reporter 仍会写 artifacts)。 */
137
+ /** 关闭控制台 / live 的逐条结果与末尾汇总(attempt 进度行仍写 stderr);errored / failed 的结果各在 stderr 补一行摘要,passed / skipped 静默;reporter 仍会写 artifacts。 */
133
138
  quiet: { type: "boolean" },
134
139
  /** 忽略上次运行结果,不跳过已通过的 (experiment, eval) 组合,强制全部重跑。 */
135
140
  force: { type: "boolean" },
136
- /** CI 中推荐使用:让软阈值(`soft`)失败也计入整条 eval 的 outcome。 */
141
+ /** CI 中推荐使用:让软阈值(`soft`)失败也计入整条 eval 的 verdict。 */
137
142
  strict: { type: "boolean" },
138
143
  /** 某个 eval 的一次 attempt 通过后,停止该 eval 剩余的 attempts。 */
139
144
  "early-exit": { type: "boolean" },
@@ -211,13 +216,12 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
211
216
  open: values["no-open"] === true ? false : values.open === true ? true : undefined,
212
217
  help: values.help === true,
213
218
  version: values.version === true,
214
- transcript: values.transcript === true,
215
- trace: values.trace === true,
219
+ eval: values.eval === true,
220
+ execution: values.execution === true,
216
221
  diff: values.diff === true && diffPath === undefined,
217
222
  diffPath,
218
223
  history: values.history === true,
219
224
  experiment: values.experiment as string | undefined,
220
- attempt: numberFlag("attempt", values.attempt as string | undefined),
221
225
  run: values.run as string | undefined,
222
226
  report: values.report as string | undefined,
223
227
  };
@@ -273,13 +277,16 @@ const AGENT_RULES_END = "<!-- END:niceeval-agent-rules -->";
273
277
  const AGENT_RULES_CONTENT = [
274
278
  "# niceeval is NOT in your training data",
275
279
  "",
276
- "Its APIs and conventions may differ from anything you have seen. Read the relevant",
277
- "guide in `node_modules/niceeval/docs-site/zh/` before writing any eval, experiment,",
278
- "adapter, or niceeval config. The bundled docs are Chinese-only — that is the single",
279
- "authoritative, always-current version; read it regardless of your working language.",
280
- "After a run, drill into failures with `niceeval show <eval id>` (add `--transcript` /",
281
- "`--trace` / `--diff` for evidence); the `summary.json` path the CLI prints and the",
282
- "artifact files it references are the structured source of truth.",
280
+ "Its APIs and conventions may differ from anything you have seen. Start with",
281
+ "`node_modules/niceeval/INDEX.md`, then read the task-specific bundled guides it points",
282
+ "to before writing any eval, experiment, adapter, or niceeval config. That index and",
283
+ "the bundled Chinese docs are the authoritative version matching this installation.",
284
+ "After a run, drill into failures with `niceeval show` pick an `@<locator>` from the",
285
+ "compact index it prints, then `niceeval show @<locator>` for a compact overview, or add",
286
+ "`--eval` / `--execution` / `--diff` for evidence; the snapshot directories the CLI prints",
287
+ "are the structured source of truth: `snapshot.json` holds the run's metadata and each",
288
+ "`<evalId>/a<attempt>/result.json` holds that attempt's verdict and assertions, next to",
289
+ "its artifact files (`events.json` / `trace.json` / `diff.json`).",
283
290
  ].join("\n");
284
291
 
285
292
  // 优先复用已有的 AGENTS.md;项目只有 CLAUDE.md(没有 AGENTS.md)时改写 CLAUDE.md 本身,
@@ -373,7 +380,7 @@ async function main(): Promise<void> {
373
380
  }
374
381
 
375
382
  if (command === "view") {
376
- // 位置参数 = eval id 前缀(收窄报告槽选集);存在的文件路径 = 单文件模式;
383
+ // 位置参数 = eval id 前缀(收窄报告槽 Selection);存在的文件路径 = 单文件模式;
377
384
  // 结果目录经 --run 递入;--report 整槽替换报告槽(与 show --report 吃同一个文件)。
378
385
  let viewInput: { input?: string; patterns: string[] };
379
386
  try {
@@ -404,15 +411,14 @@ async function main(): Promise<void> {
404
411
  }
405
412
 
406
413
  if (command === "show") {
407
- // show 不依赖 niceeval.config.ts:读的是 .niceeval/(或 --run 指定目录)的落盘结果。
414
+ // show 不依赖 niceeval.config.ts:读的是 .niceeval/(或 --run 指定的某个快照目录)的落盘结果。
408
415
  const code = await runShow(cwd, positionals, {
409
- transcript: flags.transcript,
410
- trace: flags.trace,
416
+ eval: flags.eval,
417
+ execution: flags.execution,
411
418
  diff: flags.diff,
412
419
  diffPath: flags.diffPath,
413
420
  history: flags.history,
414
421
  experiment: flags.experiment,
415
- attempt: flags.attempt,
416
422
  run: flags.run,
417
423
  report: flags.report,
418
424
  });
@@ -522,6 +528,18 @@ async function main(): Promise<void> {
522
528
  process.exit(0);
523
529
  }
524
530
 
531
+ // 提前算好(而不是等 runEvals 内部算):live 表格要在第一帧就知道哪些行会被携入
532
+ // (carry),直接渲染成已完成,不然会显示"waiting for a slot"直到进程退出——它们
533
+ // 永远等不到 eval:start,run.ts 压根不会为携入的 (experimentId, evalId) 派发 attempt。
534
+ // 两处必须共用同一份 planCarry() 判断,否则各自算一遍,一旦判断不一致,live 表格
535
+ // 显示的"携入"和 run.ts 实际调度的"携入"就会对不上。
536
+ const priorResults = flags.force ? undefined : await loadLatestResultsPerEval(join(cwd, ".niceeval"));
537
+ const carryPlan = priorResults?.length ? await planCarry(evals, agentRuns, priorResults) : undefined;
538
+ const carriedVerdictByKey = new Map<string, string>();
539
+ for (const r of carryPlan?.carriedResults ?? []) {
540
+ if (r.experimentId) carriedVerdictByKey.set(`${r.experimentId}|${r.id}`, r.verdict);
541
+ }
542
+
525
543
  const reporters: Reporter[] = [];
526
544
  let onProgress: ((evalId: string, who: string, msg: string) => void) | undefined;
527
545
 
@@ -532,10 +550,13 @@ async function main(): Promise<void> {
532
550
  for (const agentRun of agentRuns) {
533
551
  // who 必须与 attempt.ts 的进度上报同源(runWho):曾用 agent/model,同 agent 同 model
534
552
  // 的实验变体(xxx 与 xxx--agents-md)会被折叠成一行,0/2 看起来像同一 eval 跑两次。
535
- const who = runWho(agentRun);
553
+ const who = runWho({ agentName: agentRun.agent.name, model: agentRun.model, experimentId: agentRun.experimentId });
536
554
  const matched = evals.filter((e) => agentRun.evalFilter(e.id));
537
555
  for (const evalDef of matched) {
538
- liveRows.push({ evalId: evalDef.id, who, total: agentRun.runs });
556
+ const carriedVerdict = agentRun.experimentId
557
+ ? carriedVerdictByKey.get(`${agentRun.experimentId}|${evalDef.id}`)
558
+ : undefined;
559
+ liveRows.push({ evalId: evalDef.id, who, total: agentRun.runs, carriedVerdict });
539
560
  }
540
561
  }
541
562
  const totalAttempts = liveRows.reduce((s, r) => s + r.total, 0);
@@ -545,9 +566,13 @@ async function main(): Promise<void> {
545
566
  } else {
546
567
  reporters.push(ConsoleReporter());
547
568
  }
569
+ } else {
570
+ // --quiet:进度流照旧直写 stderr,结果流换成最小报告器 —— errored / failed 各补一行
571
+ // stderr,passed / skipped 静默。没有它,attempt 出执行错时控制台会全程无声。
572
+ reporters.push(QuietReporter());
548
573
  }
549
574
  const artifacts = ArtifactsReporter();
550
- reporters.push(artifacts);
575
+ reporters.push( artifacts);
551
576
  if (flags.junit) reporters.push(JUnit(flags.junit));
552
577
  if (flags.json) reporters.push(Json(flags.json));
553
578
  reporters.push(...(config.reporters ?? []));
@@ -590,13 +615,11 @@ async function main(): Promise<void> {
590
615
  });
591
616
  }
592
617
 
593
- // 无全局默认:并发上限由 sandbox 后端的推荐值决定。
594
- // 多个 agentRun 各有 sandbox 时取最小值(最保守的后端决定上限)。
618
+ // 无全局默认:并发上限由 sandbox provider 的推荐值决定。
619
+ // 多个 agentRun 各有 sandbox 时取最小值(最保守的 provider 决定上限)。
595
620
  const sandboxRecs = agentRuns.map((r) => sandboxRecommendedConcurrency(r.sandbox));
596
621
  const sandboxDefaultConcurrency = sandboxRecs.length > 0 ? Math.min(...sandboxRecs) : 10;
597
622
 
598
- const priorResults = flags.force ? undefined : await loadLatestResultsPerEval(join(cwd, ".niceeval"));
599
-
600
623
  const summary = await runEvals({
601
624
  config,
602
625
  evals,
@@ -610,22 +633,26 @@ async function main(): Promise<void> {
610
633
  signal: ctrl.signal,
611
634
  onProgress,
612
635
  priorResults,
636
+ carryPlan,
613
637
  });
614
638
 
615
639
  // 正常返回(含被中断后走部分汇总)后再兜一刀:Scope finalizer 没停掉的残留沙箱在这里强清。
616
640
  // 跑顺利时登记表已空,是 no-op。
617
641
  await stopAllSandboxes();
618
642
 
619
- // agent 反馈闭环的入口:跑完直接给出结构化结果路径,agent 读 summary.json 与各
620
- // attempt 的工件(events/trace/diff),不必解析人类向的流式输出。--quiet 下也输出。
621
- if (artifacts.outputDir()) {
622
- process.stdout.write(t("cli.resultsPath", { path: join(artifacts.outputDir(), "summary.json") }));
643
+ // agent 反馈闭环的入口:跑完直接给出每个已创建快照的目录,agent 读 snapshot.json 与各
644
+ // attempt 的 result.json / artifact(events/trace/diff),不必解析人类向的流式输出。
645
+ // --quiet 下也输出。相对 cwd 的路径更友好;结果落在 cwd 外时(relative 路径以 .. 开头)
646
+ // 原样打印绝对路径。
647
+ for (const { dir } of artifacts.outputDirs()) {
648
+ const rel = relative(cwd, dir);
649
+ process.stdout.write(t("cli.resultsPath", { path: rel && !rel.startsWith("..") ? rel : dir }));
623
650
  }
624
651
 
625
- // 退出码按 eval 级判决,不按 attempt:summary.failed/errored 统计的是每次 attempt,
652
+ // 退出码按 eval 级判定,不按 attempt:summary.failed/errored 统计的是每次 attempt,
626
653
  // 被 runs+earlyExit 重试吸收的失败(先挂一次、后来过了)不该把进程判红——否则
627
654
  // 「runs 吸收单次抖动」在 CI 退出码这层永远不成立。折叠口径与报表/view 共用
628
- // foldEvalOutcome(任一轮通过 → 该 eval 通过),粒度 experimentId|eval id。
655
+ // foldEvalVerdict(任一轮通过 → 该 eval 通过),粒度 experimentId|eval id。
629
656
  const stats = evalLevelStats(summary.results, (r) => `${r.experimentId ?? ""}|${r.id}`);
630
657
  const failedExit = stats.failed > 0 || stats.errored > 0;
631
658
  process.exit(failedExit ? 1 : 0);
@@ -352,11 +352,11 @@ describe("TurnHandle scoped assertions (parked/loadedSkill/noFailedActions/maxTo
352
352
  expect(cost.passed).toBe(false);
353
353
  });
354
354
 
355
- it("turn.loadedSkill() checks a load_skill call scoped to this turn", async () => {
355
+ it("turn.loadedSkill() reads the skill.loaded event scoped to this turn", async () => {
356
356
  const agent = scriptedAgent([
357
357
  {
358
358
  status: "completed",
359
- events: [{ type: "action.called", callId: "c1", name: "load_skill", input: { skill: "pdf-export" } }],
359
+ events: [{ type: "skill.loaded", skill: "pdf-export", callId: "c1" }],
360
360
  },
361
361
  ]);
362
362
  const { context, state } = makeContext(agent);
@@ -366,4 +366,22 @@ describe("TurnHandle scoped assertions (parked/loadedSkill/noFailedActions/maxTo
366
366
  const [result] = await state.collector.finalize(baseScoringContext(state));
367
367
  expect(result.passed).toBe(true);
368
368
  });
369
+
370
+ // Skill 加载是一等事件,不是「名字叫 load_skill 的工具调用」——adapter 负责归一
371
+ // (claude-code parser 就把 Skill tool_use 直接吐成 skill.loaded)。伪装成工具调用的
372
+ // 加载断言侧看不见,这是 adapter 违约,不是断言该兜的底(见 docs/feature/adapters/architecture/events.md)。
373
+ it("turn.loadedSkill() does not match a tool call that merely happens to be named load_skill", async () => {
374
+ const agent = scriptedAgent([
375
+ {
376
+ status: "completed",
377
+ events: [{ type: "action.called", callId: "c1", name: "load_skill", input: { skill: "pdf-export" } }],
378
+ },
379
+ ]);
380
+ const { context, state } = makeContext(agent);
381
+ const turn = await context.send("export as pdf");
382
+ turn.loadedSkill("pdf-export");
383
+
384
+ const [result] = await state.collector.finalize(baseScoringContext(state));
385
+ expect(result.passed).toBe(false);
386
+ });
369
387
  });
@@ -10,7 +10,7 @@ export class EvalSkipped extends Error {
10
10
  }
11
11
  }
12
12
 
13
- /** t.require / turn.expectOk 不过:正常的断言失败,中止后续,但已记录的断言决定判决。 */
13
+ /** t.require / turn.expectOk 不过:正常的断言失败,中止后续,但已记录的断言决定判定。 */
14
14
  export class EvalRequirementFailed extends Error {
15
15
  constructor(public readonly assertionName: string) {
16
16
  super(`requirement failed: ${assertionName}`);
@@ -1,11 +1,102 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
 
3
- import { createAgentSession } from "./session.ts";
3
+ import { createAgentSession, SessionManager } from "./session.ts";
4
+ import type { Agent, Sandbox, StreamEvent, Turn, TurnInput } from "../types.ts";
4
5
 
5
6
  // createAgentSession() 是 ctx.session 的实现——一条会话线的存取器(见
6
7
  // docs-site/zh/concepts/adapter.mdx 的 AgentSession 契约)。这里直接测存取器本身;
7
8
  // 端到端的「同一条线同一个 ctx.session」由 SessionManager / RunSession 保证。
8
9
 
10
+ function fakeSandbox(): Sandbox {
11
+ return {
12
+ workdir: "/sandbox/work",
13
+ runCommand: async () => { throw new Error("not implemented"); },
14
+ runShell: async () => { throw new Error("not implemented"); },
15
+ readFile: async () => "",
16
+ fileExists: async () => false,
17
+ readSourceFiles: async () => Object.assign([], {
18
+ text: () => "",
19
+ code: () => "",
20
+ fileMatching: () => undefined,
21
+ fileMatchingAll: () => undefined,
22
+ hasPath: () => false,
23
+ }),
24
+ writeFiles: async () => {},
25
+ uploadFiles: async () => {},
26
+ uploadDirectory: async () => {},
27
+ stop: async () => {},
28
+ sandboxId: "fake",
29
+ otlpHost: null,
30
+ downloadFile: async () => Buffer.from(""),
31
+ uploadFile: async () => {},
32
+ };
33
+ }
34
+
35
+ function agentReturning(turn: Turn): Agent {
36
+ return {
37
+ name: "fake-agent",
38
+ kind: "remote",
39
+ async send(_input: TurnInput): Promise<Turn> {
40
+ return turn;
41
+ },
42
+ };
43
+ }
44
+
45
+ function makeManager(turn: Turn) {
46
+ const lines: string[] = [];
47
+ const manager = new SessionManager({
48
+ agent: agentReturning(turn),
49
+ sandbox: fakeSandbox(),
50
+ flags: {},
51
+ signal: new AbortController().signal,
52
+ log: (msg) => lines.push(msg),
53
+ });
54
+ return { manager, lines };
55
+ }
56
+
57
+ describe("SessionManager.send() 进度行", () => {
58
+ it("failed 轮:进度行末尾追加最后一条 error 事件的 message", async () => {
59
+ const events: StreamEvent[] = [
60
+ { type: "message", role: "assistant", text: "" },
61
+ { type: "error", message: "402 Insufficient Balance" },
62
+ ];
63
+ const { manager, lines } = makeManager({ events, status: "failed" });
64
+ await manager.send(manager.primary, "hi");
65
+
66
+ const progressLine = lines.find((l) => l.includes("← failed"));
67
+ expect(progressLine).toContain("402 Insufficient Balance");
68
+ });
69
+
70
+ it("failed 轮但没有 error 事件:不追加空后缀,行尾保持原格式", async () => {
71
+ const { manager, lines } = makeManager({ events: [], status: "failed" });
72
+ await manager.send(manager.primary, "hi");
73
+
74
+ const progressLine = lines.find((l) => l.includes("← failed"));
75
+ expect(progressLine).toMatch(/\ds$/); // 以 "Ns" 收尾,没有多余的 " · " 后缀
76
+ });
77
+
78
+ it("completed 轮:即使事件里混了 error 事件也不提取原因(只在 failed 时生效)", async () => {
79
+ const events: StreamEvent[] = [{ type: "error", message: "不该出现在这里" }];
80
+ const { manager, lines } = makeManager({ events, status: "completed" });
81
+ await manager.send(manager.primary, "hi");
82
+
83
+ const progressLine = lines.find((l) => l.includes("← completed"));
84
+ expect(progressLine).not.toContain("不该出现在这里");
85
+ });
86
+
87
+ it("原因文本压成单行并截断到 120 字符", async () => {
88
+ const long = "x".repeat(200);
89
+ const events: StreamEvent[] = [{ type: "error", message: long }];
90
+ const { manager, lines } = makeManager({ events, status: "failed" });
91
+ await manager.send(manager.primary, "hi");
92
+
93
+ const progressLine = lines.find((l) => l.includes("← failed"))!;
94
+ const suffix = progressLine.split(" · ").at(-1)!;
95
+ expect(suffix.length).toBeLessThanOrEqual(120);
96
+ expect(suffix.endsWith("…")).toBe(true);
97
+ });
98
+ });
99
+
9
100
  describe("createAgentSession", () => {
10
101
  describe("history()", () => {
11
102
  it("新线 get() 是空数组;commit 之后同一条线的 get() 能看见", () => {
@@ -184,8 +184,9 @@ export class SessionManager {
184
184
 
185
185
  const tok = (turn.usage?.inputTokens ?? 0) + (turn.usage?.outputTokens ?? 0);
186
186
  const tools = turn.events.filter((e) => e.type === "action.called").length;
187
+ const reason = turn.status === "failed" ? failureReason(turn.events) : undefined;
187
188
  this.deps.log(
188
- `${turnLabel} ← ${turn.status} · ${t("session.tools", { count: tools })} · ${tok} tok · ${Math.round((Date.now() - t0) / 1000)}s`,
189
+ `${turnLabel} ← ${turn.status} · ${t("session.tools", { count: tools })} · ${tok} tok · ${Math.round((Date.now() - t0) / 1000)}s${reason ? ` · ${reason}` : ""}`,
189
190
  );
190
191
  return turn;
191
192
  }
@@ -221,6 +222,27 @@ export class SessionManager {
221
222
  }
222
223
  }
223
224
 
225
+ /**
226
+ * 失败轮的进度行原因摘要:取本轮事件流里最后一个 `type: "error"` 事件的 message
227
+ * (与 TurnHandle.expectOk() / src/agents/shared.ts 的 diagnoseFailure 同一口径——
228
+ * 都认「最后一条 error 事件」为本轮失败的权威原因),压成单行并截断,避免 402/超时
229
+ * 这类关键信息只能事后翻落盘的 result.json 才看得到。提不到时返回 undefined,调用方不补空后缀。
230
+ */
231
+ function failureReason(events: readonly StreamEvent[]): string | undefined {
232
+ for (let i = events.length - 1; i >= 0; i--) {
233
+ const e = events[i];
234
+ if (e.type === "error") return truncateOneLine(e.message, 120);
235
+ }
236
+ return undefined;
237
+ }
238
+
239
+ /** 单行截断,口径同 src/runner/reporters/table.ts 的 truncateOneLine(折叠空白 + 120 字符上限)。 */
240
+ function truncateOneLine(s: string, width: number): string {
241
+ const clean = s.replace(/\s+/g, " ").trim();
242
+ if (clean.length <= width) return clean;
243
+ return `${clean.slice(0, width - 1)}…`;
244
+ }
245
+
224
246
  export function lastAssistantText(events: readonly StreamEvent[]): string | undefined {
225
247
  for (let i = events.length - 1; i >= 0; i--) {
226
248
  const e = events[i];
@@ -156,7 +156,7 @@ export interface SandboxHandle {
156
156
  downloadFile(path: string): Promise<Buffer>;
157
157
  /** 把一段内容上传成沙箱里的单个文件。 */
158
158
  uploadFile(path: string, content: Buffer): Promise<void>;
159
- /** 沙箱后端分配的实例 id,用于排查 / 关联日志。 */
159
+ /** 沙箱 provider 分配的实例 id,用于排查 / 关联日志。 */
160
160
  readonly sandboxId: string;
161
161
  /** 相对 git 基线的最终 diff 视图(test() 跑完、finalize 前才落定)。 */
162
162
  readonly diff: DiffView;
@@ -268,7 +268,7 @@ export interface TestContext {
268
268
  newSession(): SessionHandle;
269
269
 
270
270
  // 运行上下文
271
- /** 本次 attempt 的中止信号;超时 / 早停 / 用户 Ctrl-C 时触发,传给 adapter 的长耗时调用做取消。 */
271
+ /** 本次 attempt 的中止信号;超时 / 首过即停 / 用户 Ctrl-C 时触发,传给 adapter 的长耗时调用做取消。 */
272
272
  readonly signal: AbortSignal;
273
273
  /** 本次 attempt 使用的模型名(由 experiment/CLI flag 决定);省略即 agent 原生默认,不代表「无模型」。 */
274
274
  readonly model?: string;
@@ -278,10 +278,10 @@ export interface TestContext {
278
278
  readonly flags: Readonly<Record<string, unknown>>;
279
279
  /** 打一行调试日志;有 live 进度回调时走该回调,否则落到 stderr,不出现在最终结果里。 */
280
280
  log(msg: string): void;
281
- /** 立即中止本 eval 并标记为 skipped(outcome / EvalResult.skipReason),reason 不能为空。 */
281
+ /** 立即中止本 eval 并标记为 skipped(verdict / EvalResult.skipReason),reason 不能为空。 */
282
282
  skip(reason: string): never;
283
283
 
284
- // 值级断言
284
+ // 值断言
285
285
  /**
286
286
  * 对任意值跑一个 ValueAssertion,返回可链 `.gate()` / `.atLeast()` 的 AssertionHandle。
287
287
  * 打分延迟到 eval 结束后统一 finalize,调用本身同步、不抛错——不通过只是记一条失败断言,
@@ -12,7 +12,7 @@ const noopTeardown: AgentTeardown = () => {};
12
12
  describe("sandbox factories: .setup()/.teardown() chain", () => {
13
13
  it("dockerSandbox() starts with empty hook arrays", () => {
14
14
  const spec = dockerSandbox({ image: "custom:latest" });
15
- expect(spec.backend).toBe("docker");
15
+ expect(spec.provider).toBe("docker");
16
16
  expect(spec.image).toBe("custom:latest");
17
17
  expect(spec.setupHooks).toEqual([]);
18
18
  expect(spec.teardownHooks).toEqual([]);
@@ -40,9 +40,9 @@ describe("sandbox factories: .setup()/.teardown() chain", () => {
40
40
  expect(spec.teardownHooks).toEqual([x, y]);
41
41
  });
42
42
 
43
- it("chaining preserves backend-specific fields and stacks across calls", () => {
43
+ it("chaining preserves provider-specific fields and stacks across calls", () => {
44
44
  const spec = dockerSandbox({ image: "img" }).setup(noopSetup).teardown(noopTeardown).setup(noopSetup);
45
- expect(spec.backend).toBe("docker");
45
+ expect(spec.provider).toBe("docker");
46
46
  expect(spec.image).toBe("img");
47
47
  expect(spec.setupHooks).toHaveLength(2);
48
48
  expect(spec.teardownHooks).toHaveLength(1);
@@ -50,22 +50,22 @@ describe("sandbox factories: .setup()/.teardown() chain", () => {
50
50
 
51
51
  it("vercelSandbox() / e2bSandbox() chain the same way and keep their own fields", () => {
52
52
  const vercel = vercelSandbox({ snapshotId: "snap-1" }).setup(noopSetup);
53
- expect(vercel.backend).toBe("vercel");
53
+ expect(vercel.provider).toBe("vercel");
54
54
  expect(vercel.snapshotId).toBe("snap-1");
55
55
  expect(vercel.setupHooks).toEqual([noopSetup]);
56
56
 
57
57
  const e2b = e2bSandbox({ template: "niceeval-agents" }).teardown(noopTeardown);
58
- expect(e2b.backend).toBe("e2b");
58
+ expect(e2b.provider).toBe("e2b");
59
59
  expect(e2b.template).toBe("niceeval-agents");
60
60
  expect(e2b.teardownHooks).toEqual([noopTeardown]);
61
61
  });
62
62
 
63
- it("defineSandbox() (custom backend) chains too and keeps create()/name", async () => {
63
+ it("defineSandbox() (custom provider) chains too and keeps create()/name", async () => {
64
64
  const create = async () => {
65
65
  throw new Error("not called in this test");
66
66
  };
67
- const spec = defineSandbox({ name: "my-backend", create }).setup(noopSetup).setup(noopSetup);
68
- expect(spec.backend).toBe("my-backend");
67
+ const spec = defineSandbox({ name: "my-provider", create }).setup(noopSetup).setup(noopSetup);
68
+ expect(spec.provider).toBe("my-provider");
69
69
  expect(spec.create).toBe(create);
70
70
  expect(spec.setupHooks).toHaveLength(2);
71
71
  });
package/src/define.ts CHANGED
@@ -72,8 +72,8 @@ export function defineConfig(config: Config): Config {
72
72
  }
73
73
 
74
74
  // ───────────────────────── Sandbox 工厂 ─────────────────────────
75
- // Sandbox 与 agent 一样用数据结构带参数(见 docs/sandbox.md)。这些工厂只是把
76
- // 后端 + 参数包成 spec 对象;真正的行为在 sandbox/<backend>.ts 里,由 resolve.ts 派发。
75
+ // Sandbox 与 agent 一样用数据结构带参数(见 docs/feature/sandbox/library.md)。这些工厂只是把
76
+ // provider + 参数包成 spec 对象;真正的行为在 sandbox/<provider>.ts 里,由 resolve.ts 派发。
77
77
  //
78
78
  // 四个工厂都要挂上 `.setup()` / `.teardown()` 链式方法(见 sandbox/types.ts 的
79
79
  // SandboxHooks<Self>):累积的钩子数组随 `HookState` 传递,每次链式调用都重新调
@@ -100,10 +100,10 @@ const EMPTY_HOOKS: HookState = { setupHooks: [], teardownHooks: [] };
100
100
 
101
101
  /** Docker 沙箱:本地容器。`image` 可覆盖默认 `node:*-slim`(预制模板:烘焙好 agent CLI 的镜像)。 */
102
102
  export function dockerSandbox(
103
- opts: Omit<DockerSandboxSpec, "backend" | keyof SandboxHooks<unknown>> = {},
103
+ opts: Omit<DockerSandboxSpec, "provider" | keyof SandboxHooks<unknown>> = {},
104
104
  ): DockerSandboxSpec {
105
105
  const build = (state: HookState): DockerSandboxSpec => ({
106
- backend: "docker",
106
+ provider: "docker",
107
107
  ...opts,
108
108
  setupHooks: state.setupHooks,
109
109
  teardownHooks: state.teardownHooks,
@@ -114,10 +114,10 @@ export function dockerSandbox(
114
114
 
115
115
  /** Vercel Sandbox:microVM。`snapshotId` 从已有快照起(预制模板:烘焙好 agent CLI 的快照)。 */
116
116
  export function vercelSandbox(
117
- opts: Omit<VercelSandboxSpec, "backend" | keyof SandboxHooks<unknown>> = {},
117
+ opts: Omit<VercelSandboxSpec, "provider" | keyof SandboxHooks<unknown>> = {},
118
118
  ): VercelSandboxSpec {
119
119
  const build = (state: HookState): VercelSandboxSpec => ({
120
- backend: "vercel",
120
+ provider: "vercel",
121
121
  ...opts,
122
122
  setupHooks: state.setupHooks,
123
123
  teardownHooks: state.teardownHooks,
@@ -128,10 +128,10 @@ export function vercelSandbox(
128
128
 
129
129
  /** E2B 沙箱。`template` 选 e2b 模板名/ID(预制模板:如 `"niceeval-agents"`);省略用 e2b 默认 `"base"`。 */
130
130
  export function e2bSandbox(
131
- opts: Omit<E2BSandboxSpec, "backend" | keyof SandboxHooks<unknown>> = {},
131
+ opts: Omit<E2BSandboxSpec, "provider" | keyof SandboxHooks<unknown>> = {},
132
132
  ): E2BSandboxSpec {
133
133
  const build = (state: HookState): E2BSandboxSpec => ({
134
- backend: "e2b",
134
+ provider: "e2b",
135
135
  ...opts,
136
136
  setupHooks: state.setupHooks,
137
137
  teardownHooks: state.teardownHooks,
@@ -141,8 +141,8 @@ export function e2bSandbox(
141
141
  }
142
142
 
143
143
  /**
144
- * 自定义沙箱后端:`create` 直接返回一个实现 `Sandbox` 接口的实例,不需要 niceeval 内置支持
145
- * 这个后端名字。用于接入 docker/vercel/e2b 之外的运行环境(自建 VM、Modal、Fly 等)。
144
+ * 自定义沙箱 provider:`create` 直接返回一个实现 `Sandbox` 接口的实例,不需要 niceeval 内置支持
145
+ * 这个 provider 名字。用于接入 docker/vercel/e2b 之外的运行环境(自建 VM、Modal、Fly 等)。
146
146
  */
147
147
  export function defineSandbox(def: {
148
148
  name: string;
@@ -152,7 +152,7 @@ export function defineSandbox(def: {
152
152
  if (!def.name) throw new Error(t("define.sandboxNameRequired"));
153
153
  if (typeof def.create !== "function") throw new Error(t("define.sandboxCreateRequired"));
154
154
  const build = (state: HookState): CustomSandboxSpec => ({
155
- backend: def.name,
155
+ provider: def.name,
156
156
  create: def.create,
157
157
  recommendedConcurrency: def.recommendedConcurrency,
158
158
  setupHooks: state.setupHooks,
@@ -1,11 +1,11 @@
1
- // 值级断言匹配器(expect)。每个匹配器产出一个 ValueAssertion:纯函数 score +
2
- // 可链式改严重级 / 阈值。链式方法返回全新的不可变 ValueAssertion,复用同一个 score。
1
+ // 值断言匹配器(expect)。每个匹配器产出一个 ValueAssertion:纯函数 score +
2
+ // 可链式改严重度 / 阈值。链式方法返回全新的不可变 ValueAssertion,复用同一个 score。
3
3
 
4
4
  import type { Severity, ValueAssertion } from "../types.ts";
5
5
  import { stripComments } from "../util.ts";
6
6
  import { deepEqual, validateSchema } from "../scoring/match.ts";
7
7
 
8
- // 自定义匹配器作者用的公共类型(docs/scoring.md 的 `Assertion` 即它)。
8
+ // 自定义匹配器作者用的公共类型(docs/feature/scoring/README.md 的 `Assertion` 即它)。
9
9
  export type { Severity, ValueAssertion } from "../types.ts";
10
10
  export type { ValueAssertion as Assertion } from "../types.ts";
11
11
 
@@ -33,7 +33,7 @@ function createAssertion(
33
33
  score,
34
34
  // 转成硬门槛(失败即整条 eval 不通过)。
35
35
  gate: (t?: number) => createAssertion(name, "gate", score, t),
36
- // 软阈值:默认不改变 outcome;--strict 下软阈值失败也会使 outcome=failed。
36
+ // 软阈值:默认不改变 verdict;--strict 下软阈值失败也会使 verdict=failed。
37
37
  atLeast: (t: number) => createAssertion(name, "soft", score, t),
38
38
  };
39
39
  return Object.freeze(self);
@@ -174,7 +174,7 @@ export function isFalse(label?: string): ValueAssertion {
174
174
  }
175
175
 
176
176
  /**
177
- * 自定义断言工厂:直接给名字 / 严重级 / 阈值 / score,一次调用即返回可用的 ValueAssertion——
177
+ * 自定义断言工厂:直接给名字 / 严重度 / 阈值 / score,一次调用即返回可用的 ValueAssertion——
178
178
  * 不像 gate()/atLeast() 那样需要二段链式调用来定级。severity 省略默认 gate。
179
179
  */
180
180
  export function makeAssertion(spec: {