niceeval 0.6.0 → 0.6.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 (314) hide show
  1. package/dist/agents/types.d.ts +72 -6
  2. package/dist/context/types.d.ts +32 -12
  3. package/dist/i18n/en.d.ts +54 -0
  4. package/dist/i18n/zh-CN.d.ts +55 -1
  5. package/dist/o11y/types.d.ts +16 -2
  6. package/dist/report/aggregate.d.ts +5 -3
  7. package/dist/report/aggregate.js +32 -5
  8. package/dist/report/built-ins/experiment-comparison.d.ts +39 -0
  9. package/dist/report/built-ins/experiment-comparison.js +119 -0
  10. package/dist/report/built-ins/index.d.ts +2 -1
  11. package/dist/report/built-ins/index.js +2 -2
  12. package/dist/report/components.d.ts +10 -2
  13. package/dist/report/components.js +3 -3
  14. package/dist/report/compute.d.ts +11 -18
  15. package/dist/report/compute.js +68 -66
  16. package/dist/report/flag.d.ts +16 -1
  17. package/dist/report/flag.js +19 -1
  18. package/dist/report/format.d.ts +16 -14
  19. package/dist/report/format.js +28 -30
  20. package/dist/report/index.d.ts +5 -4
  21. package/dist/report/index.js +6 -5
  22. package/dist/report/locale.d.ts +23 -3
  23. package/dist/report/locale.js +47 -6
  24. package/dist/report/metrics.d.ts +13 -1
  25. package/dist/report/metrics.js +66 -15
  26. package/dist/report/primitives.d.ts +6 -0
  27. package/dist/report/react/AttemptList.d.ts +4 -4
  28. package/dist/report/react/AttemptList.js +8 -10
  29. package/dist/report/react/EvalList.d.ts +1 -1
  30. package/dist/report/react/EvalList.js +0 -0
  31. package/dist/report/react/ExperimentComparison.d.ts +8 -0
  32. package/dist/report/react/ExperimentComparison.js +11 -0
  33. package/dist/report/react/ExperimentList.d.ts +4 -2
  34. package/dist/report/react/ExperimentList.js +57 -7
  35. package/dist/report/react/MetricScatter.js +6 -16
  36. package/dist/report/react/chart-math.d.ts +23 -6
  37. package/dist/report/react/chart-math.js +71 -19
  38. package/dist/report/react/fixtures.d.ts +3 -3
  39. package/dist/report/react/fixtures.js +30 -18
  40. package/dist/report/react/format.d.ts +1 -1
  41. package/dist/report/react/format.js +1 -1
  42. package/dist/report/react/index.d.ts +1 -1
  43. package/dist/report/report.d.ts +5 -1
  44. package/dist/report/report.js +6 -2
  45. package/dist/report/text/faces.d.ts +1 -1
  46. package/dist/report/text/faces.js +100 -61
  47. package/dist/report/text/table.js +36 -5
  48. package/dist/report/types.d.ts +40 -34
  49. package/dist/results/types.d.ts +11 -0
  50. package/dist/runner/feedback/sink.d.ts +110 -0
  51. package/dist/runner/types.d.ts +513 -22
  52. package/dist/sandbox/docker.d.ts +23 -2
  53. package/dist/sandbox/e2b.d.ts +15 -1
  54. package/dist/sandbox/errors.d.ts +30 -3
  55. package/dist/sandbox/io-retry.d.ts +17 -0
  56. package/dist/sandbox/registry.d.ts +2 -0
  57. package/dist/sandbox/resolve.d.ts +18 -5
  58. package/dist/sandbox/retry.d.ts +11 -1
  59. package/dist/sandbox/types.d.ts +39 -5
  60. package/dist/sandbox/vercel.d.ts +7 -1
  61. package/dist/scoring/coverage.d.ts +30 -0
  62. package/dist/scoring/display.d.ts +21 -0
  63. package/dist/scoring/display.js +120 -0
  64. package/dist/scoring/types.d.ts +103 -20
  65. package/dist/shared/aggregate.d.ts +1 -0
  66. package/dist/shared/aggregate.js +3 -3
  67. package/dist/shared/types.d.ts +28 -0
  68. package/dist/tty-line.d.ts +0 -4
  69. package/dist/util.d.ts +23 -0
  70. package/docs-site/zh/concepts/adapter.mdx +24 -6
  71. package/docs-site/zh/concepts/assert.mdx +11 -10
  72. package/docs-site/zh/concepts/evals.mdx +7 -6
  73. package/docs-site/zh/concepts/experiment.mdx +1 -1
  74. package/docs-site/zh/concepts/overview.mdx +7 -7
  75. package/docs-site/zh/guides/agent-feedback-loop.mdx +35 -31
  76. package/docs-site/zh/guides/authoring.mdx +33 -0
  77. package/docs-site/zh/guides/ci-integration.mdx +23 -12
  78. package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
  79. package/docs-site/zh/guides/custom-reports.mdx +29 -34
  80. package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
  81. package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
  82. package/docs-site/zh/guides/debugging.mdx +210 -0
  83. package/docs-site/zh/guides/experiments.mdx +10 -3
  84. package/docs-site/zh/guides/fixtures.mdx +3 -1
  85. package/docs-site/zh/guides/official-adapters.mdx +27 -3
  86. package/docs-site/zh/guides/publish-report.mdx +30 -16
  87. package/docs-site/zh/guides/report-components.mdx +49 -37
  88. package/docs-site/zh/guides/reporters.mdx +2 -2
  89. package/docs-site/zh/guides/results-data.mdx +42 -8
  90. package/docs-site/zh/guides/runner.mdx +17 -7
  91. package/docs-site/zh/guides/sandbox-agent.mdx +57 -7
  92. package/docs-site/zh/guides/sandbox-providers.mdx +258 -10
  93. package/docs-site/zh/guides/scoring-guide.mdx +4 -4
  94. package/docs-site/zh/guides/viewing-results.mdx +85 -41
  95. package/docs-site/zh/guides/write-experiment.mdx +5 -3
  96. package/docs-site/zh/guides/write-send.mdx +19 -2
  97. package/docs-site/zh/index.mdx +1 -1
  98. package/docs-site/zh/reference/builtin-agents.mdx +27 -0
  99. package/docs-site/zh/reference/capabilities.mdx +2 -2
  100. package/docs-site/zh/reference/cli.mdx +35 -9
  101. package/docs-site/zh/reference/define-agent.mdx +60 -5
  102. package/docs-site/zh/reference/define-config.mdx +1 -1
  103. package/docs-site/zh/reference/define-eval.mdx +42 -9
  104. package/docs-site/zh/reference/events.mdx +2 -2
  105. package/docs-site/zh/reference/expect.mdx +36 -6
  106. package/package.json +5 -1
  107. package/src/agents/ai-sdk-otel.test.ts +1 -0
  108. package/src/agents/ai-sdk.test.ts +3 -0
  109. package/src/agents/ai-sdk.ts +3 -0
  110. package/src/agents/bub-install-spec.test.ts +34 -0
  111. package/src/agents/bub-install-spec.ts +32 -0
  112. package/src/agents/bub.ts +31 -32
  113. package/src/agents/claude-code.test.ts +130 -9
  114. package/src/agents/claude-code.ts +76 -4
  115. package/src/agents/codex.test.ts +189 -40
  116. package/src/agents/codex.ts +155 -14
  117. package/src/agents/coding-cli-versions.test.ts +15 -0
  118. package/src/agents/coding-cli-versions.ts +3 -0
  119. package/src/agents/index.ts +11 -0
  120. package/src/agents/langgraph.test.ts +204 -0
  121. package/src/agents/langgraph.ts +495 -0
  122. package/src/agents/marketplace.ts +85 -0
  123. package/src/agents/native-config.test.ts +179 -0
  124. package/src/agents/native-config.ts +267 -0
  125. package/src/agents/openai-compat.test.ts +1 -0
  126. package/src/agents/openclaw.test.ts +31 -0
  127. package/src/agents/openclaw.ts +171 -0
  128. package/src/agents/plugin-config.test.ts +1 -0
  129. package/src/agents/sdk-streams.test.ts +79 -0
  130. package/src/agents/sdk-streams.ts +55 -10
  131. package/src/agents/skills.test.ts +1 -0
  132. package/src/agents/streaming.test.ts +3 -9
  133. package/src/agents/types.ts +73 -6
  134. package/src/agents/ui-message-stream.test.ts +3 -0
  135. package/src/cli.ts +411 -108
  136. package/src/context/context.test.ts +51 -12
  137. package/src/context/context.ts +161 -29
  138. package/src/context/session.test.ts +1 -0
  139. package/src/context/session.ts +114 -6
  140. package/src/context/types.ts +30 -12
  141. package/src/define.test.ts +13 -8
  142. package/src/define.ts +25 -4
  143. package/src/expect/index.ts +53 -23
  144. package/src/i18n/en.ts +65 -4
  145. package/src/i18n/zh-CN.ts +66 -4
  146. package/src/o11y/cost.test.ts +1 -0
  147. package/src/o11y/execution-tree.test.ts +1 -20
  148. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  149. package/src/o11y/otlp/parse.test.ts +1 -0
  150. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  151. package/src/o11y/parsers/bub.test.ts +1 -0
  152. package/src/o11y/parsers/claude-code.test.ts +1 -34
  153. package/src/o11y/parsers/openclaw.test.ts +154 -0
  154. package/src/o11y/parsers/openclaw.ts +310 -0
  155. package/src/o11y/prices.json +746 -311
  156. package/src/o11y/tool-names.test.ts +1 -0
  157. package/src/o11y/types.ts +16 -2
  158. package/src/report/aggregate.ts +34 -5
  159. package/src/report/built-in-user-parity.test.tsx +127 -173
  160. package/src/report/built-ins/experiment-comparison.tsx +179 -0
  161. package/src/report/built-ins/index.ts +7 -2
  162. package/src/report/components.tsx +11 -3
  163. package/src/report/compute.ts +80 -74
  164. package/src/report/dual-render.test.tsx +222 -91
  165. package/src/report/flag.ts +30 -2
  166. package/src/report/format.ts +36 -27
  167. package/src/report/index.ts +23 -6
  168. package/src/report/locale.ts +49 -6
  169. package/src/report/metrics.ts +68 -15
  170. package/src/report/primitives.tsx +6 -0
  171. package/src/report/react/AttemptList.tsx +9 -36
  172. package/src/report/react/EvalList.tsx +0 -0
  173. package/src/report/react/ExperimentComparison.tsx +68 -0
  174. package/src/report/react/ExperimentList.tsx +173 -55
  175. package/src/report/react/MetricScatter.tsx +13 -25
  176. package/src/report/react/chart-math.test.ts +85 -0
  177. package/src/report/react/chart-math.ts +101 -22
  178. package/src/report/react/enhance.js +72 -1
  179. package/src/report/react/fixtures.ts +34 -21
  180. package/src/report/react/format.ts +1 -1
  181. package/src/report/react/index.tsx +0 -1
  182. package/src/report/react/render.test.tsx +30 -69
  183. package/src/report/react/styles.css +112 -14
  184. package/src/report/report.test.ts +308 -105
  185. package/src/report/report.ts +6 -2
  186. package/src/report/text/faces.ts +111 -67
  187. package/src/report/text/table.ts +42 -5
  188. package/src/report/types.ts +42 -34
  189. package/src/results/annotated-source.test.ts +62 -9
  190. package/src/results/annotated-source.ts +64 -6
  191. package/src/results/attempt-evidence.test.ts +9 -7
  192. package/src/results/attempt-evidence.ts +15 -8
  193. package/src/results/attempt-source.ts +6 -3
  194. package/src/results/copy.ts +145 -55
  195. package/src/results/host-equivalence.test.ts +11 -9
  196. package/src/results/index.ts +2 -0
  197. package/src/results/locator.test.ts +1 -22
  198. package/src/results/open.ts +7 -1
  199. package/src/results/publish.ts +149 -0
  200. package/src/results/results.test.ts +85 -51
  201. package/src/results/truncate.ts +90 -0
  202. package/src/results/types.ts +7 -0
  203. package/src/results/writer.ts +31 -13
  204. package/src/runner/attempt.test.ts +138 -7
  205. package/src/runner/attempt.ts +603 -104
  206. package/src/runner/discover.test.ts +47 -0
  207. package/src/runner/discover.ts +36 -2
  208. package/src/runner/eval-source.test.ts +1 -27
  209. package/src/runner/feedback/agent.test.ts +504 -0
  210. package/src/runner/feedback/agent.ts +409 -0
  211. package/src/runner/feedback/ci.test.ts +562 -0
  212. package/src/runner/feedback/ci.ts +401 -0
  213. package/src/runner/feedback/coordinator.test.ts +317 -0
  214. package/src/runner/feedback/coordinator.ts +397 -0
  215. package/src/runner/feedback/failure.ts +40 -0
  216. package/src/runner/feedback/human.test.ts +616 -0
  217. package/src/runner/feedback/human.ts +535 -0
  218. package/src/runner/feedback/index.ts +66 -0
  219. package/src/runner/feedback/io.ts +78 -0
  220. package/src/runner/feedback/profile.test.ts +50 -0
  221. package/src/runner/feedback/profile.ts +58 -0
  222. package/src/runner/feedback/reducer.test.ts +395 -0
  223. package/src/runner/feedback/reducer.ts +260 -0
  224. package/src/runner/feedback/renderer.ts +82 -0
  225. package/src/runner/feedback/sink.ts +203 -0
  226. package/src/runner/feedback/testing.ts +106 -0
  227. package/src/runner/ledger.test.ts +230 -0
  228. package/src/runner/ledger.ts +329 -0
  229. package/src/runner/report.test.ts +128 -3
  230. package/src/runner/report.ts +33 -9
  231. package/src/runner/reporters/artifacts.ts +8 -2
  232. package/src/runner/reporters/braintrust.test.ts +8 -7
  233. package/src/runner/reporters/braintrust.ts +9 -2
  234. package/src/runner/reporters/index.ts +2 -2
  235. package/src/runner/reporters/json.test.ts +162 -0
  236. package/src/runner/reporters/json.ts +35 -8
  237. package/src/runner/reporters/shared.ts +1 -5
  238. package/src/runner/run.test.ts +760 -3
  239. package/src/runner/run.ts +242 -36
  240. package/src/runner/sandbox-prep.ts +3 -42
  241. package/src/runner/timing.ts +158 -0
  242. package/src/runner/types.ts +518 -22
  243. package/src/sandbox/checkpoint.test.ts +55 -0
  244. package/src/sandbox/checkpoint.ts +29 -8
  245. package/src/sandbox/cli-commands.ts +407 -0
  246. package/src/sandbox/docker.ts +115 -16
  247. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  248. package/src/sandbox/e2b-agent-template.ts +94 -0
  249. package/src/sandbox/e2b.ts +74 -9
  250. package/src/sandbox/errors.ts +111 -4
  251. package/src/sandbox/index.ts +2 -0
  252. package/src/sandbox/io-retry.test.ts +58 -0
  253. package/src/sandbox/io-retry.ts +45 -0
  254. package/src/sandbox/keep-registry.test.ts +86 -0
  255. package/src/sandbox/keep-registry.ts +142 -0
  256. package/src/sandbox/keep.ts +178 -0
  257. package/src/sandbox/paths.test.ts +1 -0
  258. package/src/sandbox/paths.ts +19 -8
  259. package/src/sandbox/registry.ts +20 -3
  260. package/src/sandbox/resolve.ts +76 -11
  261. package/src/sandbox/retry.test.ts +70 -0
  262. package/src/sandbox/retry.ts +46 -4
  263. package/src/sandbox/types.ts +44 -6
  264. package/src/sandbox/vercel.ts +43 -20
  265. package/src/scoring/collector.ts +60 -17
  266. package/src/scoring/coverage.ts +95 -0
  267. package/src/scoring/diff.ts +81 -0
  268. package/src/scoring/display.test.ts +121 -0
  269. package/src/scoring/display.ts +133 -0
  270. package/src/scoring/evidence.test.ts +189 -0
  271. package/src/scoring/judge.test.ts +142 -0
  272. package/src/scoring/judge.ts +15 -18
  273. package/src/scoring/scoped.ts +217 -50
  274. package/src/scoring/types.ts +117 -20
  275. package/src/scoring/verdict.ts +16 -4
  276. package/src/shared/aggregate.ts +3 -2
  277. package/src/shared/types.ts +31 -0
  278. package/src/show/compose.ts +2 -2
  279. package/src/show/index.ts +29 -16
  280. package/src/show/render.ts +626 -308
  281. package/src/show/show.test.ts +251 -36
  282. package/src/tty-line.ts +8 -26
  283. package/src/util.test.ts +1 -0
  284. package/src/util.ts +41 -0
  285. package/src/view/app/components/AttemptModal.tsx +153 -2
  286. package/src/view/app/components/CodeView.tsx +32 -11
  287. package/src/view/app/components/CopyControls.tsx +2 -2
  288. package/src/view/app/i18n.ts +6 -0
  289. package/src/view/app/lib/attempt-route.test.ts +1 -0
  290. package/src/view/app/lib/verdict.ts +7 -9
  291. package/src/view/artifact-serving.test.ts +2 -1
  292. package/src/view/client-dist/app.css +1 -1
  293. package/src/view/client-dist/app.js +17 -17
  294. package/src/view/data.test.ts +2 -1
  295. package/src/view/data.ts +17 -7
  296. package/src/view/index.ts +12 -1
  297. package/src/view/server.ts +2 -0
  298. package/src/view/shared/types.ts +1 -1
  299. package/src/view/styles.css +3 -0
  300. package/src/view/view-report.test.ts +11 -10
  301. package/dist/o11y/execution-tree.d.ts +0 -103
  302. package/dist/o11y/otlp/select.d.ts +0 -22
  303. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
  304. package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
  305. package/dist/results/annotated-source.d.ts +0 -61
  306. package/dist/results/attempt-evidence.d.ts +0 -69
  307. package/dist/results/attempt-source.d.ts +0 -15
  308. package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
  309. package/src/runner/reporters/console.ts +0 -70
  310. package/src/runner/reporters/live.test.ts +0 -56
  311. package/src/runner/reporters/live.ts +0 -247
  312. package/src/runner/reporters/quiet.test.ts +0 -66
  313. package/src/runner/reporters/quiet.ts +0 -49
  314. package/src/runner/reporters/table.ts +0 -277
@@ -1,56 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from "vitest";
2
- import { Live, type LiveRow } from "./live.ts";
3
-
4
- function withMockTty<T>(fn: () => T): { writes: string[]; result: T } {
5
- const writes: string[] = [];
6
- vi.spyOn(process.stderr, "write").mockImplementation((chunk: unknown) => {
7
- writes.push(String(chunk));
8
- return true;
9
- });
10
- Object.defineProperty(process.stderr, "isTTY", { value: true, configurable: true });
11
- Object.defineProperty(process.stderr, "columns", { value: 120, configurable: true });
12
- Object.defineProperty(process.stderr, "rows", { value: 40, configurable: true });
13
- return { writes, result: fn() };
14
- }
15
-
16
- describe("Live carried rows", () => {
17
- afterEach(() => {
18
- vi.restoreAllMocks();
19
- });
20
-
21
- it("renders a carried row as already-done from the first frame, not waiting for a slot", () => {
22
- const rows: LiveRow[] = [
23
- { evalId: "memory/carried-fail", who: "codex-e2b", total: 1, carriedVerdict: "failed" },
24
- { evalId: "memory/fresh", who: "codex-e2b", total: 1 },
25
- ];
26
- const live = Live(rows, 2);
27
-
28
- const { writes } = withMockTty(() => {
29
- live.onRunStart?.([], { name: "codex", kind: "sandbox" } as never, {
30
- evals: 2,
31
- configs: 1,
32
- totalRuns: 1,
33
- maxConcurrency: 5,
34
- });
35
- });
36
- vi.spyOn(process.stdout, "write").mockImplementation(() => true);
37
- live.onRunComplete?.({
38
- startedAt: "",
39
- finishedAt: "",
40
- durationMs: 0,
41
- results: [],
42
- totalRuns: 1,
43
- } as never);
44
-
45
- const out = writes.join("");
46
- const carriedLine = out.split("\n").find((l) => l.includes("memory/carried-fail"));
47
- const freshLine = out.split("\n").find((l) => l.includes("memory/fresh"));
48
-
49
- expect(carriedLine).toBeDefined();
50
- expect(carriedLine).toContain("✗");
51
- expect(carriedLine).not.toContain("waiting");
52
-
53
- expect(freshLine).toBeDefined();
54
- expect(freshLine).toContain("waiting");
55
- });
56
- });
@@ -1,247 +0,0 @@
1
- // Live terminal reporter:在 TTY 终端里渲染实时状态表,每个 (eval, who) 对占一行。
2
- // spinner 每 80ms 刷新;attempt 完成后行内显示 ✓/✗/~ 符号。
3
- // onRunComplete 时清除状态表,打印和网页榜单同口径的表格报告。
4
-
5
- import type { Reporter, ReporterEvent, RunShape, RunSummary } from "../../types.ts";
6
- import { t } from "../../i18n/index.ts";
7
- import { renderRunReport } from "./table.ts";
8
- import { verdictSymbol, WAITING_SYM } from "./shared.ts";
9
- import { runWho } from "../types.ts";
10
- import { onBeforeExternalTerminalWrite } from "../../tty-line.ts";
11
-
12
- const SPINNER = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
13
-
14
- // 进度日志里的基础设施噪声:OTLP 端口、remote-agent 启动提示、trace span 计数。
15
- // 这些在行尾显示毫无意义,直接丢弃。
16
- const NOISE_PATTERNS = [
17
- /^OTLP /,
18
- /^使用 remote agent/,
19
- /^using remote agent/,
20
- /^驱动 agent/,
21
- /^driving agent/,
22
- /^trace:\d/,
23
- /^agent tracing/,
24
- /^agent setup/,
25
- ];
26
-
27
- function isNoise(msg: string): boolean {
28
- return NOISE_PATTERNS.some((p) => p.test(msg));
29
- }
30
-
31
- export interface LiveRow {
32
- evalId: string;
33
- who: string;
34
- /** 该 (evalId, who) 对的总 attempt 数(含 earlyExit 估算值)。 */
35
- total: number;
36
- /**
37
- * 设置时表示这一行从第一帧起就是携入(carry)的结果,不会真的调度 attempt——直接按这个
38
- * verdict 渲染成已完成,不经过 waiting → spinner 的过程。没有它,carry 掉的行会卡在
39
- * "waiting for a slot"直到进程退出:它永远等不到 eval:start,因为 run.ts 压根不会为
40
- * 它派发 attempt。
41
- */
42
- carriedVerdict?: string;
43
- }
44
-
45
- export interface LiveReporter extends Reporter {
46
- /** 被 RunOptions.onProgress 调用,更新行尾的 lastMsg。 */
47
- progress(evalId: string, who: string, msg: string): void;
48
- }
49
-
50
- interface RowState extends LiveRow {
51
- completed: number;
52
- lastMsg: string;
53
- dominantVerdict: string | undefined;
54
- /** true 一旦拿到并发名额、attempt effect 真正开始跑;之前是排队等待,不该转圈误导。 */
55
- started: boolean;
56
- }
57
-
58
- export function Live(rows: LiveRow[], totalAttempts: number): LiveReporter {
59
- const stateMap = new Map<string, RowState>();
60
- const keyOrder: string[] = [];
61
- let totalCompleted = 0;
62
-
63
- for (const r of rows) {
64
- const k = `${r.evalId}|${r.who}`;
65
- if (!stateMap.has(k)) {
66
- const carried = r.carriedVerdict !== undefined;
67
- stateMap.set(k, {
68
- ...r,
69
- completed: carried ? r.total : 0,
70
- lastMsg: "",
71
- dominantVerdict: r.carriedVerdict,
72
- started: carried,
73
- });
74
- keyOrder.push(k);
75
- if (carried) totalCompleted += r.total;
76
- } else {
77
- // 同一 (evalId, who) 可能在多个 agentRun 里出现(不应发生,但做防御)
78
- stateMap.get(k)!.total += r.total;
79
- }
80
- }
81
-
82
- let spinFrame = 0;
83
- let drawnLines = 0; // 上次 draw() 写了多少行,用于 \x1B[nA 回跳
84
- let intervalId: ReturnType<typeof setInterval> | undefined;
85
- let shape: RunShape | undefined;
86
- let unsubscribeExternalWrite: (() => void) | undefined;
87
-
88
- const cols = () => process.stderr.columns || 100;
89
-
90
- function renderRow(state: RowState, frame: number): string {
91
- const done = state.completed >= state.total;
92
- const sym = done
93
- ? verdictSymbol(state.dominantVerdict ?? "")
94
- : state.started
95
- ? SPINNER[frame % SPINNER.length]
96
- : WAITING_SYM;
97
-
98
- const evalCol = state.evalId.slice(0, 24).padEnd(24);
99
- const whoCol = `[${state.who}]`.slice(0, 26).padEnd(26);
100
- const cntCol = `${state.completed}/${state.total}`.padEnd(5);
101
-
102
- const prefix = ` ${sym} ${evalCol} ${whoCol} ${cntCol} `;
103
- const budget = Math.max(0, cols() - prefix.length - 1);
104
- const msg = done ? "" : state.started ? state.lastMsg.slice(0, budget) : t("live.waiting").slice(0, budget);
105
-
106
- return `\x1B[2K${prefix}${msg}`;
107
- }
108
-
109
- function renderHeader(): string {
110
- const hdr = shape
111
- ? t("live.running", {
112
- totalRuns: shape.totalRuns,
113
- evals: shape.evals,
114
- configs: shape.configs,
115
- concurrency: shape.maxConcurrency,
116
- completed: totalCompleted,
117
- total: totalAttempts,
118
- })
119
- : t("live.runningUnknown", { completed: totalCompleted, total: totalAttempts });
120
- return `\x1B[2K${hdr}`;
121
- }
122
-
123
- // 终端放不下全部行时,光标回跳会被屏幕顶端截断,导致每帧往下追加整表。
124
- // 所以按终端高度截断:优先显示运行中的行,放不下的折叠成一行摘要。
125
- function frameLines(frame: number): string[] {
126
- const lines = [renderHeader()];
127
- const termRows = process.stderr.rows || 30;
128
- // 预留:1 行表头 + 1 行防止末尾换行触发滚动
129
- const budget = Math.max(1, termRows - 2);
130
-
131
- if (keyOrder.length <= budget) {
132
- for (const k of keyOrder) lines.push(renderRow(stateMap.get(k)!, frame));
133
- return lines;
134
- }
135
-
136
- const running: string[] = [];
137
- const waiting: string[] = [];
138
- const done: string[] = [];
139
- for (const k of keyOrder) {
140
- const s = stateMap.get(k)!;
141
- if (s.completed >= s.total) done.push(k);
142
- else if (s.started) running.push(k);
143
- else waiting.push(k);
144
- }
145
- // 选出要显示的 key(运行中 > 等待 > 已完成),但按原始顺序渲染,避免行来回跳动
146
- const shown = new Set([...running, ...waiting, ...done].slice(0, budget - 1));
147
- for (const k of keyOrder) {
148
- if (shown.has(k)) lines.push(renderRow(stateMap.get(k)!, frame));
149
- }
150
- lines.push(
151
- `\x1B[2K ${t("live.more", {
152
- hidden: keyOrder.length - shown.size,
153
- running: running.filter((k) => !shown.has(k)).length,
154
- waiting: waiting.filter((k) => !shown.has(k)).length,
155
- done: done.filter((k) => !shown.has(k)).length,
156
- })}`,
157
- );
158
- return lines;
159
- }
160
-
161
- function draw(frame: number) {
162
- if (!process.stderr.isTTY) return;
163
-
164
- const lines = frameLines(frame);
165
- let out = drawnLines > 0 ? `\x1B[${drawnLines}A` : "";
166
- out += lines.join("\n") + "\n";
167
- // 本帧比上帧短(行完成后折叠、终端拉高)时,清掉下方残留的旧行
168
- const extra = drawnLines - lines.length;
169
- if (extra > 0) {
170
- out += "\x1B[2K\n".repeat(extra) + `\x1B[${extra}A`;
171
- }
172
- process.stderr.write(out);
173
- drawnLines = lines.length;
174
- }
175
-
176
- function clearDisplay() {
177
- if (!process.stderr.isTTY || drawnLines === 0) return;
178
- // 回到起点,逐行清空
179
- process.stderr.write(`\x1B[${drawnLines}A`);
180
- for (let i = 0; i < drawnLines; i++) {
181
- process.stderr.write("\x1B[2K\n");
182
- }
183
- process.stderr.write(`\x1B[${drawnLines}A`);
184
- drawnLines = 0;
185
- }
186
-
187
- return {
188
- progress(evalId, who, msg) {
189
- if (isNoise(msg)) return;
190
- const state = stateMap.get(`${evalId}|${who}`);
191
- if (state) state.lastMsg = msg;
192
- // 不在这里 draw();由 interval 驱动,避免每条日志都刷屏
193
- },
194
-
195
- onEvent(event: ReporterEvent) {
196
- if (event.type !== "eval:start") return;
197
- const who = runWho({ agentName: event.agent.name, model: event.model, experimentId: event.experimentId });
198
- const state = stateMap.get(`${event.eval.id}|${who}`);
199
- if (state) state.started = true;
200
- },
201
-
202
- onRunStart(_evals, _agent, s) {
203
- shape = s;
204
- // sandbox teardown 失败、budget 不可执行这类独立诊断行可能绕开 progress()/onEvalComplete
205
- // 直接落地(见 tty-line.ts)。订阅后先把已画的表格清掉、drawnLines 归零,下一帧从新起点
206
- // 重画,不然回跳量和实际光标错位,越滚越多。
207
- unsubscribeExternalWrite = onBeforeExternalTerminalWrite(() => clearDisplay());
208
- // 初始渲染:让用户看到行表
209
- draw(0);
210
- intervalId = setInterval(() => {
211
- spinFrame = (spinFrame + 1) % SPINNER.length;
212
- draw(spinFrame);
213
- }, 80);
214
- },
215
-
216
- onEvalComplete(result) {
217
- const who = runWho({ agentName: result.agent, model: result.model, experimentId: result.experimentId });
218
- const state = stateMap.get(`${result.id}|${who}`);
219
- if (state) {
220
- state.completed += 1;
221
- totalCompleted += 1;
222
- const prev = state.dominantVerdict;
223
- if (!prev || (prev === "passed" && result.verdict !== "passed")) {
224
- state.dominantVerdict = result.verdict;
225
- }
226
- } else {
227
- totalCompleted += 1;
228
- }
229
- },
230
-
231
- onRunComplete(summary) {
232
- if (intervalId) {
233
- clearInterval(intervalId);
234
- intervalId = undefined;
235
- }
236
- if (unsubscribeExternalWrite) {
237
- unsubscribeExternalWrite();
238
- unsubscribeExternalWrite = undefined;
239
- }
240
- // 最后刷一帧(所有行都 done)
241
- draw(spinFrame);
242
- clearDisplay();
243
-
244
- process.stdout.write(renderRunReport(summary));
245
- },
246
- };
247
- }
@@ -1,66 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { quietLine } from "./quiet.ts";
3
- import type { EvalResult } from "../../types.ts";
4
-
5
- function baseResult(overrides: Partial<EvalResult> = {}): EvalResult {
6
- return {
7
- id: "algebra/quadratic",
8
- agent: "codex",
9
- verdict: "passed",
10
- attempt: 0,
11
- durationMs: 42_000,
12
- assertions: [],
13
- ...overrides,
14
- };
15
- }
16
-
17
- describe("quietLine", () => {
18
- it("passed / skipped 静默(返回 undefined)", () => {
19
- expect(quietLine(baseResult())).toBeUndefined();
20
- expect(quietLine(baseResult({ verdict: "skipped", skipReason: "no api key" }))).toBeUndefined();
21
- });
22
-
23
- it("errored 带 eval id、[who] 与截断后的 error", () => {
24
- const line = quietLine(
25
- baseResult({
26
- verdict: "errored",
27
- model: "gpt-5",
28
- error: "sandbox create failed: e2b timeout after 3.9s",
29
- }),
30
- );
31
- expect(line).toContain("algebra/quadratic");
32
- expect(line).toContain("[codex/gpt-5]");
33
- expect(line).toContain("errored");
34
- expect(line).toContain("e2b timeout after 3.9s");
35
- });
36
-
37
- it("[who] 与进度行同源:有 experimentId 时用其 basename", () => {
38
- const line = quietLine(
39
- baseResult({ verdict: "errored", experimentId: "compare/xxx--agents-md", error: "boom" }),
40
- );
41
- expect(line).toContain("[xxx--agents-md]");
42
- });
43
-
44
- it("failed 无 error 时取首个失败断言(severity、阈值、detail)", () => {
45
- const line = quietLine(
46
- baseResult({
47
- verdict: "failed",
48
- assertions: [
49
- { name: "compiles", severity: "gate", score: 1, passed: true },
50
- { name: "closedQA", severity: "gate", score: 0.2, passed: false, threshold: 0.7, detail: "wrong city" },
51
- ],
52
- }),
53
- );
54
- expect(line).toContain("failed");
55
- expect(line).toContain("closedQA");
56
- expect(line).toContain("0.20");
57
- expect(line).toContain("wrong city");
58
- expect(line).not.toContain("compiles");
59
- });
60
-
61
- it("超长 error 截断到 200 字符并加省略号", () => {
62
- const line = quietLine(baseResult({ verdict: "errored", error: "x".repeat(500) }))!;
63
- expect(line).toContain("…");
64
- expect(line.length).toBeLessThan(300);
65
- });
66
- });
@@ -1,49 +0,0 @@
1
- // Quiet 报告器:--quiet 下的最小结果流。进度流照旧(attempt 无 onProgress 时直写 stderr),
2
- // 这里只补「坏结果」:verdict 为 errored / failed 的结果各写一行 stderr,passed / skipped
3
- // 静默 —— 保证 --quiet 下起沙箱失败这类执行错不会全程无声、只能事后读 summary.json。
4
- // 结果流仍走统一的 Reporter 管线,不在 attempt 里散写。
5
-
6
- import type { EvalResult, Reporter } from "../../types.ts";
7
- import { t } from "../../i18n/index.ts";
8
- import { runWho } from "../types.ts";
9
- import { verdictSymbol } from "./shared.ts";
10
-
11
- /** error / 断言 detail 的截断上限;比 Console 的 400 更紧,--quiet 只要能定位问题。 */
12
- const DETAIL_MAX = 200;
13
-
14
- /** 纯函数:一条结果 → 该写 stderr 的行;passed / skipped 返回 undefined(静默)。 */
15
- export function quietLine(result: EvalResult): string | undefined {
16
- if (result.verdict !== "errored" && result.verdict !== "failed") return undefined;
17
- // [who] 与 attempt 进度行同源(runWho),两条流才能对上同一个运行配置。
18
- const who = runWho({ agentName: result.agent, model: result.model, experimentId: result.experimentId });
19
- const verdict = result.verdict === "errored" ? t("report.errored") : t("report.failed");
20
- const detail =
21
- result.error !== undefined
22
- ? `${t("report.error")}: ${truncate(result.error, DETAIL_MAX)}`
23
- : firstFailedAssertion(result);
24
- return ` ${verdictSymbol(result.verdict)} ${result.id} ${verdict} [${who}]${detail ? ` ${detail}` : ""}\n`;
25
- }
26
-
27
- function firstFailedAssertion(result: EvalResult): string | undefined {
28
- const a = result.assertions.find((x) => !x.passed);
29
- if (!a) return undefined;
30
- const sev = a.severity === "gate" ? t("report.gate") : t("report.soft");
31
- const thr =
32
- a.threshold !== undefined
33
- ? t("report.assertionThreshold", { score: a.score.toFixed(2), threshold: a.threshold })
34
- : "";
35
- return `${sev}: ${a.name}${thr}${a.detail ? ` — ${truncate(a.detail, DETAIL_MAX)}` : ""}`;
36
- }
37
-
38
- function truncate(s: string, n: number): string {
39
- return s.length > n ? s.slice(0, n) + "…" : s;
40
- }
41
-
42
- export function Quiet(): Reporter {
43
- return {
44
- onEvalComplete(result: EvalResult) {
45
- const line = quietLine(result);
46
- if (line !== undefined) process.stderr.write(line);
47
- },
48
- };
49
- }
@@ -1,277 +0,0 @@
1
- import type { EvalResult, RunSummary, Usage } from "../../types.ts";
2
- import { t } from "../../i18n/index.ts";
3
- import { verdictSymbol } from "./shared.ts";
4
- import { foldEvalVerdict, evalLevelStats as sharedEvalLevelStats } from "../../shared/verdict.ts";
5
- // 展示口径(耗时/成本/百分比)与聚合小工具(标签/求和/排序)与 view 共用一份实现;
6
- // console.ts 经这里 re-export formatDuration。
7
- import { formatCost, formatDuration, formatPercent } from "../../shared/format.ts";
8
- import { VERDICT_ORDER, avg, displayExperimentName, fallbackExperimentLabel, sumMaybe, totalTokens } from "../../shared/aggregate.ts";
9
-
10
- export { formatCost, formatDuration } from "../../shared/format.ts";
11
-
12
- interface ExperimentRow {
13
- key: string;
14
- label: string;
15
- model: string;
16
- agent: string;
17
- avgDurationMs: number;
18
- passRate: number;
19
- tokens: number;
20
- cost?: number;
21
- evals: number;
22
- passed: number;
23
- failed: number;
24
- errored: number;
25
- skipped: number;
26
- attempts: number;
27
- results: EvalResult[];
28
- }
29
-
30
- interface EvalRow {
31
- id: string;
32
- verdict: EvalResult["verdict"];
33
- reason: string;
34
- durationMs: number;
35
- tokens: number;
36
- cost?: number;
37
- passedAttempts: number;
38
- attempts: number;
39
- }
40
-
41
- export function renderRunReport(summary: RunSummary): string {
42
- const rows = aggregateExperimentRows(summary.results);
43
- const lines: string[] = ["", t("report.table.experimentsTitle")];
44
- lines.push(renderTable(
45
- [
46
- t("report.table.experiment"),
47
- t("report.table.model"),
48
- t("report.table.agent"),
49
- t("report.table.avgDuration"),
50
- t("report.table.successRate"),
51
- t("report.table.tokens"),
52
- t("report.table.cost"),
53
- t("report.table.result"),
54
- ],
55
- rows.map((row) => [
56
- row.label,
57
- row.model,
58
- row.agent,
59
- formatDuration(row.avgDurationMs),
60
- formatPercent(row.passRate),
61
- formatTokens(row.tokens),
62
- formatCost(row.cost),
63
- formatResult(row),
64
- ]),
65
- ));
66
-
67
- for (const row of rows) {
68
- lines.push("", `${t("report.table.evalTitle")} ${row.label}`);
69
- lines.push(renderTable(
70
- [
71
- t("report.table.status"),
72
- t("report.table.eval"),
73
- t("report.table.reason"),
74
- t("report.table.duration"),
75
- t("report.table.tokens"),
76
- t("report.table.cost"),
77
- t("report.table.runs"),
78
- ],
79
- aggregateEvalRows(row.results).map((evalRow) => [
80
- formatVerdict(evalRow.verdict),
81
- evalRow.id,
82
- evalRow.reason,
83
- formatDuration(evalRow.durationMs),
84
- formatTokens(evalRow.tokens),
85
- formatCost(evalRow.cost),
86
- `${evalRow.passedAttempts}/${evalRow.attempts}`,
87
- ]),
88
- { maxWidth: 120, flexibleColumn: 2 },
89
- ));
90
- }
91
-
92
- const tok = (summary.usage?.inputTokens ?? 0) + (summary.usage?.outputTokens ?? 0);
93
- const tokStr = tok > 0 ? `${formatTokens(tok)} tok` : "— tok";
94
- const cost = summary.estimatedCostUSD !== undefined ? ` · ${formatCost(summary.estimatedCostUSD)}` : "";
95
- const parts = [
96
- t("report.summary.passed", { count: summary.passed }),
97
- t("report.summary.failed", { count: summary.failed }),
98
- ...(summary.errored > 0 ? [t("report.summary.errored", { count: summary.errored })] : []),
99
- t("report.summary.skipped", { count: summary.skipped }),
100
- ];
101
- lines.push(t("report.result", {
102
- parts: parts.join(", "),
103
- duration: formatDuration(summary.durationMs),
104
- tokens: tokStr,
105
- cost,
106
- }).trimEnd());
107
- lines.push(t("report.viewHint").trimEnd());
108
-
109
- return `${lines.join("\n")}\n\n`;
110
- }
111
-
112
- export function formatTokens(n: number): string {
113
- if (n >= 1000) return `${(n / 1000).toFixed(1)}k`;
114
- return String(n);
115
- }
116
-
117
- export function formatVerdict(verdict: string): string {
118
- const sym = verdictSymbol(verdict);
119
- switch (verdict) {
120
- case "passed": return `${sym} ${t("report.passed")}`;
121
- case "failed": return `${sym} ${t("report.failed")}`;
122
- case "errored": return `${sym} ${t("report.errored")}`;
123
- case "skipped": return `${sym} ${t("report.skipped")}`;
124
- default: return `${sym} ${verdict}`;
125
- }
126
- }
127
-
128
- function aggregateExperimentRows(results: EvalResult[]): ExperimentRow[] {
129
- const groups = new Map<string, EvalResult[]>();
130
- for (const result of results) {
131
- const key = result.experimentId ? `exp|||${result.experimentId}` : `legacy|||${result.agent}|||${result.model ?? ""}`;
132
- groups.set(key, [...(groups.get(key) ?? []), result]);
133
- }
134
-
135
- return [...groups.entries()]
136
- .map(([key, groupResults]) => {
137
- const first = groupResults[0]!;
138
- const stats = evalLevelStats(groupResults);
139
- return {
140
- key,
141
- label: displayExperimentName(first.experimentId) ?? fallbackExperimentLabel(first),
142
- model: first.model ?? t("report.table.default"),
143
- agent: first.agent,
144
- avgDurationMs: avg(groupResults.map((r) => r.durationMs)),
145
- passRate: stats.passRate,
146
- tokens: totalTokens(groupResults.map((r) => r.usage)),
147
- cost: sumMaybe(groupResults.map((r) => r.estimatedCostUSD)),
148
- evals: stats.evals,
149
- passed: stats.passed,
150
- failed: stats.failed,
151
- errored: stats.errored,
152
- skipped: stats.skipped,
153
- attempts: groupResults.length,
154
- results: groupResults.slice().sort((a, b) => a.id.localeCompare(b.id) || a.attempt - b.attempt),
155
- };
156
- })
157
- .sort((a, b) => b.passRate - a.passRate || a.label.localeCompare(b.label));
158
- }
159
-
160
- function aggregateEvalRows(results: EvalResult[]): EvalRow[] {
161
- const byEval = new Map<string, EvalResult[]>();
162
- for (const result of results) byEval.set(result.id, [...(byEval.get(result.id) ?? []), result]);
163
- return [...byEval.entries()]
164
- .map(([id, evalResults]) => {
165
- const verdict = foldEvalVerdict(evalResults);
166
- const representative =
167
- evalResults.find((r) => r.verdict === verdict) ??
168
- evalResults.find((r) => r.verdict !== "passed") ??
169
- evalResults[0]!;
170
- return {
171
- id,
172
- verdict,
173
- reason: verdict === "passed" ? t("report.passed") : reasonFor(representative),
174
- durationMs: avg(evalResults.map((r) => r.durationMs)),
175
- tokens: totalTokens(evalResults.map((r) => r.usage)),
176
- cost: sumMaybe(evalResults.map((r) => r.estimatedCostUSD)),
177
- passedAttempts: evalResults.filter((r) => r.verdict === "passed").length,
178
- attempts: evalResults.length,
179
- };
180
- })
181
- .sort((a, b) => VERDICT_ORDER[a.verdict] - VERDICT_ORDER[b.verdict] || a.id.localeCompare(b.id));
182
- }
183
-
184
- // 折叠 / 计票口径与 view 同一份实现(src/shared/verdict.ts),CLI 表格和网页榜单永不打架。
185
- function evalLevelStats(results: EvalResult[]) {
186
- return sharedEvalLevelStats(results, (r) => r.id);
187
- }
188
-
189
- function reasonFor(result: EvalResult): string {
190
- if (result.skipReason) return result.skipReason;
191
- if (result.error) return truncateOneLine(result.error, 120);
192
- const failed = result.assertions.filter((a) => !a.passed && a.severity === "gate");
193
- const assertions = failed.length ? failed : result.assertions.slice(0, 2);
194
- if (!assertions.length) return "";
195
- return truncateOneLine(
196
- assertions
197
- .map((a) => {
198
- const threshold = a.threshold !== undefined ? ` ${t("report.assertionThreshold", { score: a.score.toFixed(2), threshold: a.threshold }).trim()}` : "";
199
- return `${a.severity}: ${a.name}${threshold}`;
200
- })
201
- .join(" · "),
202
- 120,
203
- );
204
- }
205
-
206
- function formatResult(row: ExperimentRow): string {
207
- const failures = row.failed + row.errored;
208
- const suffix = row.skipped > 0 ? ` · ${row.skipped} ${t("report.skipped")}` : "";
209
- return `${row.passed}/${row.evals} ${t("report.passed")} · ${failures} ${t("report.failed")}${suffix}`;
210
- }
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
- function renderTable(headers: string[], rows: string[][], opts: { maxWidth?: number; flexibleColumn?: number } = {}): string {
219
- const rawRows = [headers, ...rows];
220
- const widths = headers.map((_, col) => Math.max(...rawRows.map((row) => displayWidth(row[col] ?? ""))));
221
- const maxWidth = opts.maxWidth ?? 140;
222
- const tableWidth = widths.reduce((sum, w) => sum + w, 0) + (headers.length - 1) * 3;
223
- if (tableWidth > maxWidth && opts.flexibleColumn !== undefined) {
224
- const over = tableWidth - maxWidth;
225
- widths[opts.flexibleColumn] = Math.max(18, widths[opts.flexibleColumn]! - over);
226
- }
227
-
228
- const lines = [formatRow(headers, widths), widths.map((w) => "-".repeat(w)).join("-+-")];
229
- for (const row of rows) lines.push(formatRow(row, widths));
230
- return lines.join("\n");
231
- }
232
-
233
- function formatRow(row: string[], widths: number[]): string {
234
- return row.map((cell, i) => padEnd(truncateToWidth(cell ?? "", widths[i]!), widths[i]!)).join(" | ");
235
- }
236
-
237
- function truncateOneLine(s: string, width: number): string {
238
- return truncateToWidth(s.replace(/\s+/g, " ").trim(), width);
239
- }
240
-
241
- function truncateToWidth(s: string, width: number): string {
242
- if (displayWidth(s) <= width) return s;
243
- const ellipsis = "…";
244
- let out = "";
245
- for (const char of s) {
246
- if (displayWidth(out + char + ellipsis) > width) break;
247
- out += char;
248
- }
249
- return out + ellipsis;
250
- }
251
-
252
- function padEnd(s: string, width: number): string {
253
- return s + " ".repeat(Math.max(0, width - displayWidth(s)));
254
- }
255
-
256
- function displayWidth(s: string): number {
257
- let width = 0;
258
- for (const char of s) {
259
- const code = char.codePointAt(0) ?? 0;
260
- width += isWide(code) ? 2 : 1;
261
- }
262
- return width;
263
- }
264
-
265
- function isWide(code: number): boolean {
266
- return (
267
- (code >= 0x1100 && code <= 0x115f) ||
268
- (code >= 0x2e80 && code <= 0xa4cf) ||
269
- (code >= 0xac00 && code <= 0xd7a3) ||
270
- (code >= 0xf900 && code <= 0xfaff) ||
271
- (code >= 0xfe10 && code <= 0xfe19) ||
272
- (code >= 0xfe30 && code <= 0xfe6f) ||
273
- (code >= 0xff00 && code <= 0xff60) ||
274
- (code >= 0xffe0 && code <= 0xffe6) ||
275
- (code >= 0x1f300 && code <= 0x1faff)
276
- );
277
- }