niceeval 0.4.3 → 0.4.5

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 (179) hide show
  1. package/README.md +21 -12
  2. package/README.zh.md +8 -9
  3. package/docs-site/.mintignore +13 -0
  4. package/docs-site/AGENTS.md +46 -0
  5. package/docs-site/concepts/adapter.mdx +287 -0
  6. package/docs-site/concepts/assert.mdx +243 -0
  7. package/docs-site/concepts/drive.mdx +118 -0
  8. package/docs-site/concepts/evals.mdx +108 -0
  9. package/docs-site/concepts/experiment.mdx +37 -0
  10. package/docs-site/concepts/hitl.mdx +83 -0
  11. package/docs-site/concepts/judge.mdx +129 -0
  12. package/docs-site/concepts/overview.mdx +98 -0
  13. package/docs-site/concepts/tier.mdx +42 -0
  14. package/docs-site/docs-ref/00-index.md +23 -0
  15. package/docs-site/docs-ref/01-page-types.md +43 -0
  16. package/docs-site/docs-ref/03-style-rules.md +45 -0
  17. package/docs-site/docs-ref/04-code-examples.md +37 -0
  18. package/docs-site/docs-ref/05-dx-failure-paths.md +45 -0
  19. package/docs-site/docs-ref/06-checklists.md +53 -0
  20. package/docs-site/docs.json +256 -0
  21. package/docs-site/example/ai-agent-application.mdx +148 -0
  22. package/docs-site/example/claude-code-codex-plugin.mdx +162 -0
  23. package/docs-site/example/claude-code-codex-skill.mdx +147 -0
  24. package/docs-site/example/showcase.mdx +39 -0
  25. package/docs-site/example/tier1-ai-sdk-v7.mdx +136 -0
  26. package/docs-site/example/tier1-claude-sdk.mdx +119 -0
  27. package/docs-site/example/tier1-codex-sdk.mdx +111 -0
  28. package/docs-site/example/tier1-langgraph.mdx +119 -0
  29. package/docs-site/example/tier1-pi-sdk.mdx +119 -0
  30. package/docs-site/favicon.svg +5 -0
  31. package/docs-site/github-diff.css +135 -0
  32. package/docs-site/github-diff.js +11 -0
  33. package/docs-site/guides/authoring.mdx +129 -0
  34. package/docs-site/guides/ci-integration.mdx +97 -0
  35. package/docs-site/guides/connect-otel.mdx +209 -0
  36. package/docs-site/guides/connect-your-agent.mdx +221 -0
  37. package/docs-site/guides/dataset-fanout.mdx +98 -0
  38. package/docs-site/guides/experiments.mdx +71 -0
  39. package/docs-site/guides/fixtures.mdx +147 -0
  40. package/docs-site/guides/reporters.mdx +113 -0
  41. package/docs-site/guides/runner.mdx +79 -0
  42. package/docs-site/guides/sandbox-agent.mdx +138 -0
  43. package/docs-site/guides/sandbox-backends.mdx +64 -0
  44. package/docs-site/guides/scoring-guide.mdx +92 -0
  45. package/docs-site/guides/viewing-results.mdx +195 -0
  46. package/docs-site/guides/write-experiment.mdx +81 -0
  47. package/docs-site/guides/write-send.mdx +347 -0
  48. package/docs-site/images/adapter-tiers-zh.svg +60 -0
  49. package/docs-site/images/agent-turn-roundtrip-en.svg +62 -0
  50. package/docs-site/images/agent-turn-roundtrip-zh.svg +77 -0
  51. package/docs-site/images/hitl-handshake-zh.svg +89 -0
  52. package/docs-site/images/logo.svg +6 -0
  53. package/docs-site/index.mdx +181 -0
  54. package/docs-site/introduction.mdx +141 -0
  55. package/docs-site/quickstart.mdx +136 -0
  56. package/docs-site/reference/builtin-agents.mdx +161 -0
  57. package/docs-site/reference/capabilities.mdx +76 -0
  58. package/docs-site/reference/cli.mdx +120 -0
  59. package/docs-site/reference/define-agent.mdx +168 -0
  60. package/docs-site/reference/define-config.mdx +41 -0
  61. package/docs-site/reference/define-eval.mdx +160 -0
  62. package/docs-site/reference/events.mdx +131 -0
  63. package/docs-site/reference/expect.mdx +112 -0
  64. package/docs-site/tracker.js +8 -0
  65. package/docs-site/zh/concepts/adapter.mdx +286 -0
  66. package/docs-site/zh/concepts/assert.mdx +243 -0
  67. package/docs-site/zh/concepts/drive.mdx +118 -0
  68. package/docs-site/zh/concepts/evals.mdx +124 -0
  69. package/docs-site/zh/concepts/experiment.mdx +37 -0
  70. package/docs-site/zh/concepts/hitl.mdx +83 -0
  71. package/docs-site/zh/concepts/judge.mdx +130 -0
  72. package/docs-site/zh/concepts/overview.mdx +109 -0
  73. package/docs-site/zh/concepts/tier.mdx +44 -0
  74. package/docs-site/zh/example/ai-agent-application.mdx +152 -0
  75. package/docs-site/zh/example/claude-code-codex-plugin.mdx +163 -0
  76. package/docs-site/zh/example/claude-code-codex-skill.mdx +147 -0
  77. package/docs-site/zh/example/showcase.mdx +39 -0
  78. package/docs-site/zh/example/tier1-ai-sdk-v7.mdx +140 -0
  79. package/docs-site/zh/example/tier1-claude-sdk.mdx +122 -0
  80. package/docs-site/zh/example/tier1-codex-sdk.mdx +117 -0
  81. package/docs-site/zh/example/tier1-langgraph.mdx +123 -0
  82. package/docs-site/zh/example/tier1-pi-sdk.mdx +122 -0
  83. package/docs-site/zh/guides/agent-feedback-loop.mdx +79 -0
  84. package/docs-site/zh/guides/authoring.mdx +129 -0
  85. package/docs-site/zh/guides/ci-integration.mdx +97 -0
  86. package/docs-site/zh/guides/connect-otel.mdx +208 -0
  87. package/docs-site/zh/guides/connect-your-agent.mdx +226 -0
  88. package/docs-site/zh/guides/dataset-fanout.mdx +83 -0
  89. package/docs-site/zh/guides/experiments.mdx +72 -0
  90. package/docs-site/zh/guides/fixtures.mdx +147 -0
  91. package/docs-site/zh/guides/official-adapters.mdx +132 -0
  92. package/docs-site/zh/guides/reporters.mdx +113 -0
  93. package/docs-site/zh/guides/runner.mdx +82 -0
  94. package/docs-site/zh/guides/sandbox-agent.mdx +139 -0
  95. package/docs-site/zh/guides/sandbox-backends.mdx +75 -0
  96. package/docs-site/zh/guides/scoring-guide.mdx +92 -0
  97. package/docs-site/zh/guides/viewing-results.mdx +195 -0
  98. package/docs-site/zh/guides/write-experiment.mdx +101 -0
  99. package/docs-site/zh/guides/write-send.mdx +353 -0
  100. package/docs-site/zh/index.mdx +180 -0
  101. package/docs-site/zh/introduction.mdx +140 -0
  102. package/docs-site/zh/quickstart.mdx +137 -0
  103. package/docs-site/zh/reference/builtin-agents.mdx +364 -0
  104. package/docs-site/zh/reference/capabilities.mdx +76 -0
  105. package/docs-site/zh/reference/cli.mdx +140 -0
  106. package/docs-site/zh/reference/define-agent.mdx +461 -0
  107. package/docs-site/zh/reference/define-config.mdx +107 -0
  108. package/docs-site/zh/reference/define-eval.mdx +674 -0
  109. package/docs-site/zh/reference/events.mdx +252 -0
  110. package/docs-site/zh/reference/expect.mdx +208 -0
  111. package/package.json +3 -2
  112. package/src/agents/bub.ts +1 -1
  113. package/src/agents/claude-code.ts +8 -5
  114. package/src/agents/codex.ts +9 -5
  115. package/src/agents/sdk-streams.test.ts +4 -4
  116. package/src/agents/sdk-streams.ts +14 -9
  117. package/src/agents/types.ts +40 -1
  118. package/src/agents/ui-message-stream.ts +1 -0
  119. package/src/cli.ts +40 -10
  120. package/src/context/types.ts +140 -0
  121. package/src/expect/index.ts +15 -8
  122. package/src/i18n/en.ts +20 -6
  123. package/src/i18n/zh-CN.ts +20 -6
  124. package/src/o11y/parsers/bub.test.ts +71 -0
  125. package/src/o11y/parsers/bub.ts +5 -0
  126. package/src/o11y/types.ts +27 -2
  127. package/src/runner/attempt.ts +2 -1
  128. package/src/runner/reporters/artifacts.ts +11 -3
  129. package/src/runner/reporters/live.ts +45 -8
  130. package/src/runner/run.test.ts +50 -0
  131. package/src/runner/run.ts +135 -41
  132. package/src/runner/types.ts +55 -2
  133. package/src/sandbox/types.ts +18 -0
  134. package/src/scoring/types.ts +5 -0
  135. package/src/shared/types.ts +3 -0
  136. package/src/util.test.ts +26 -1
  137. package/src/util.ts +16 -0
  138. package/src/view/app/App.tsx +2 -2
  139. package/src/view/app/components/AttemptModal.tsx +2 -0
  140. package/src/view/app/components/CopyControls.tsx +87 -28
  141. package/src/view/app/i18n.ts +3 -3
  142. package/src/view/client-dist/app.css +1 -1
  143. package/src/view/client-dist/app.js +18 -18
  144. package/docs/README.md +0 -120
  145. package/docs/adapters/README.md +0 -60
  146. package/docs/adapters/authoring.md +0 -179
  147. package/docs/adapters/coding-agent-skills-plugins.md +0 -324
  148. package/docs/adapters/collection.md +0 -128
  149. package/docs/adapters/contract.md +0 -264
  150. package/docs/adapters/reference/agent-eval.md +0 -215
  151. package/docs/adapters/reference/agent-loop-apis.md +0 -69
  152. package/docs/adapters/reference/claude-code-otel-telemetry.md +0 -100
  153. package/docs/adapters/reference/eve-protocol.md +0 -127
  154. package/docs/adapters/reference/otel-genai.md +0 -107
  155. package/docs/adapters/reference/otel-instrumentation.md +0 -65
  156. package/docs/adapters/targets.md +0 -99
  157. package/docs/architecture.md +0 -140
  158. package/docs/assertions.md +0 -387
  159. package/docs/capabilities-by-construction.md +0 -48
  160. package/docs/cli.md +0 -173
  161. package/docs/concepts.md +0 -106
  162. package/docs/e2e-ci.md +0 -332
  163. package/docs/eval-authoring.md +0 -319
  164. package/docs/experiments.md +0 -157
  165. package/docs/getting-started.md +0 -224
  166. package/docs/multi-agent.md +0 -145
  167. package/docs/observability.md +0 -337
  168. package/docs/origin-integration.md +0 -195
  169. package/docs/references.md +0 -35
  170. package/docs/reports.md +0 -551
  171. package/docs/results-format.md +0 -228
  172. package/docs/results-lib.md +0 -191
  173. package/docs/runner.md +0 -104
  174. package/docs/sandbox.md +0 -276
  175. package/docs/scoring.md +0 -119
  176. package/docs/source-map.md +0 -126
  177. package/docs/tier-sync.md +0 -193
  178. package/docs/view.md +0 -194
  179. package/docs/vision.md +0 -88
package/src/i18n/en.ts CHANGED
@@ -15,7 +15,12 @@ export const en = {
15
15
  "cli.all": "(all)",
16
16
  "cli.browserOpenFailed": "Could not open the browser automatically. Open manually: {{url}}\n",
17
17
  "cli.clean.done": "Deleted .niceeval/ historical run artifacts.\n",
18
- "cli.config.missing": "Could not find niceeval.config.ts (run this from the project root).",
18
+ "cli.config.missing":
19
+ "Could not find niceeval.config.ts.\n" +
20
+ "Ways to fix:\n" +
21
+ " - [init] Run `npx niceeval init` to scaffold niceeval.config.ts and evals/\n" +
22
+ " - [cd] Run from the project root that contains niceeval.config.ts\n" +
23
+ " Docs: node_modules/niceeval/docs-site/quickstart.mdx",
19
24
  "cli.config.noDefault": "niceeval.config.ts must default export defineConfig(...).",
20
25
  "cli.dry.header": "\n[dry] {{evals}} evals × {{configs}} run configs:\n",
21
26
  "cli.dry.noMatches": "(no matches)",
@@ -25,7 +30,8 @@ export const en = {
25
30
  "runner.budgetUnenforceable":
26
31
  "budget for {{budgetKey}}: several attempts completed without any cost data (agent reports no usage and the model is not in the price table) — the budget cannot be enforced for this agent; continuing without the guard.\n",
27
32
  "judge.modelMissing":
28
- "No judge model configured. Set it in defineConfig({ judge: { model: \"...\" } }), the eval's judge config, or the NICEEVAL_JUDGE_MODEL environment variable (there is no built-in default model).",
33
+ "No judge model configured. Set it in defineConfig({ judge: { model: \"...\" } }), the eval's judge config, or the NICEEVAL_JUDGE_MODEL environment variable (there is no built-in default model).\n" +
34
+ " Docs: node_modules/niceeval/docs-site/guides/scoring-guide.mdx",
29
35
  "loaders.yamlMissing":
30
36
  "loadYaml(\"{{path}}\") needs a YAML parser: run `pnpm add yaml` first (or switch to loadJson with a JSON dataset).",
31
37
  "cli.flag.parseError": "{{message}}\nRun `niceeval --help` for usage.\n",
@@ -55,13 +61,16 @@ export const en = {
55
61
  "cli.experimentGroup": " group",
56
62
  "cli.fallbackCleanupTimeout": "\ngraceful cleanup timed out; force-cleaning sandboxes...\n",
57
63
  "cli.forceCleanupExit": "\nForce-cleaning sandboxes and exiting...\n",
58
- "cli.init.done": "Created evals/ and starter niceeval.config.ts.\n",
64
+ "cli.init.done": "Ready: evals/, niceeval.config.ts, and the niceeval agent-rules block in AGENTS.md (points coding agents at node_modules/niceeval/docs-site).\n",
59
65
  "cli.interruptCleanup": "\nInterrupted; cleaning up sandbox containers... (press again to force cleanup and exit)\n",
60
66
  "cli.list.header": "Discovered {{count}} evals:\n",
61
67
  "cli.noAgent": "No agent specified (use --agent <name>).\n",
62
68
  "cli.none": "(none)",
63
69
  "cli.pressCtrlC": "Press Ctrl+C to exit.\n",
64
- "cli.run.experimentRequired": "Run evals through an experiment: use `niceeval exp [group|config] [eval id prefix]`.\n",
70
+ "cli.resultsPath": "Structured results: {{path}} (each result's artifactsDir holds that attempt's events.json / trace.json / diff.json)\n",
71
+ "cli.run.experimentRequired":
72
+ "Run evals through an experiment: use `niceeval exp [group|config] [eval id prefix]`.\n" +
73
+ " Docs: node_modules/niceeval/docs-site/guides/write-experiment.mdx\n",
65
74
  "cli.run.experimentRequiredHint": "Hint: \"{{pattern}}\" is an experiment{{kind}}; you probably meant: niceeval exp {{pattern}}\n",
66
75
  "cli.run.experimentRequiredKnown": "Discovered experiments: {{experiments}}\n",
67
76
  "cli.sandboxFlagRemoved": "`--sandbox` is not a CLI flag. Set `sandbox` in the experiment (or `niceeval.config.ts` as a project-wide fallback) to dockerSandbox() / vercelSandbox() / e2bSandbox() (import from \"niceeval/sandbox\").\n",
@@ -70,7 +79,8 @@ export const en = {
70
79
  "cli.view.incompatible": "{{dir}}: written by niceeval {{producer}} (schemaVersion {{schemaVersion}}); this CLI reads schemaVersion {{supported}}.\nRun `{{command}}` to view it.\n",
71
80
  "cli.view.url": "niceeval view: {{url}}\n",
72
81
  "context.capabilityMissing":
73
- "Agent \"{{agent}}\" is not sandbox-backed (built with defineSandboxAgent), so t.{{method}} is unavailable. Use an agent built with defineSandboxAgent, or drop this assertion.",
82
+ "Agent \"{{agent}}\" is not sandbox-backed (built with defineSandboxAgent), so t.{{method}} is unavailable. Use an agent built with defineSandboxAgent, or drop this assertion.\n" +
83
+ " Docs: node_modules/niceeval/docs-site/guides/sandbox-agent.mdx",
74
84
  "context.skipEmpty": "skip() requires a non-empty reason.",
75
85
  "context.turnFailed": "This send returned failed (turn status = failed): {{message}}",
76
86
  "context.turnFailedDefault": "This send returned failed (turn status = failed)",
@@ -99,6 +109,7 @@ export const en = {
99
109
  "judge.httpError": "judge HTTP {{status}}: {{body}}",
100
110
  "judge.probeFailed": "judge precheck failed ({{model}}): {{error}}",
101
111
  "judge.probeMissingKey": "judge model {{model}} is missing an API key; configure {{envHint}}",
112
+ "live.more": "… {{hidden}} more ({{running}} running · {{waiting}} waiting · {{done}} done)",
102
113
  "live.running": " Running {{totalRuns}} attempts ({{evals}} evals × {{configs}} configs, concurrency {{concurrency}}) {{completed}}/{{total}} done",
103
114
  "live.runningUnknown": " Running... {{completed}}/{{total}} done",
104
115
  "live.waiting": "waiting for a slot...",
@@ -156,9 +167,12 @@ export const en = {
156
167
  "runner.timeout": "attempt timed out ({{timeoutMs}}ms)\nRecent progress:\n{{recentLogs}}",
157
168
  "runner.traceSelected": " -> kept {{count}} semantic spans",
158
169
  "runner.resumeCarry": " · reusing {{carried}} passing results from last run, re-running {{retry}} evals\n",
170
+ "runner.resumeCarryDetail": " carried [{{experiment}}] {{evals}}\n",
159
171
  "runner.useRemoteAgent": "using remote agent (no sandbox created)...",
160
172
  "sandbox.backendNotImplemented": "{{backend}} sandbox backend is not implemented; use docker, vercel, or e2b",
161
- "sandbox.missingSpec": "sandbox agent needs a sandbox, but none was given. niceeval no longer picks a default — set `sandbox` in defineExperiment()/defineConfig() to dockerSandbox() / vercelSandbox() / e2bSandbox() (import from \"niceeval/sandbox\").",
173
+ "sandbox.missingSpec":
174
+ "sandbox agent needs a sandbox, but none was given. niceeval no longer picks a default — set `sandbox` in defineExperiment()/defineConfig() to dockerSandbox() / vercelSandbox() / e2bSandbox() (import from \"niceeval/sandbox\").\n" +
175
+ " Docs: node_modules/niceeval/docs-site/guides/sandbox-backends.mdx",
162
176
  "sandbox.dependencyMissing.docker": "Docker sandbox requires 'dockerode'. Install it with: pnpm add dockerode @types/dockerode",
163
177
  "sandbox.dependencyMissing.e2b": "E2B sandbox requires 'e2b'. Install it with: pnpm add e2b",
164
178
  "sandbox.dependencyMissing.vercel": "Vercel sandbox requires '@vercel/sandbox'. Install it with: pnpm add @vercel/sandbox",
package/src/i18n/zh-CN.ts CHANGED
@@ -13,7 +13,12 @@ export const zhCN = {
13
13
  "cli.all": "(全部)",
14
14
  "cli.browserOpenFailed": "无法自动打开浏览器,请手动访问:{{url}}\n",
15
15
  "cli.clean.done": "已删除 .niceeval/ 历史运行工件。\n",
16
- "cli.config.missing": "找不到 niceeval.config.ts(请在项目根运行)。",
16
+ "cli.config.missing":
17
+ "找不到 niceeval.config.ts。\n" +
18
+ "修法:\n" +
19
+ " - [init] 运行 `npx niceeval init` 生成 niceeval.config.ts 和 evals/\n" +
20
+ " - [cd] 切到包含 niceeval.config.ts 的项目根再运行\n" +
21
+ " 文档:node_modules/niceeval/docs-site/zh/quickstart.mdx",
17
22
  "cli.config.noDefault": "niceeval.config.ts 需要 default export(defineConfig(...))。",
18
23
  "cli.dry.header": "\n[dry] {{evals}} 个 eval × {{configs}} 个运行配置:\n",
19
24
  "cli.dry.noMatches": "(无匹配)",
@@ -23,7 +28,8 @@ export const zhCN = {
23
28
  "runner.budgetUnenforceable":
24
29
  "{{budgetKey}} 的 budget:连续多个 attempt 完成后都拿不到成本数据(agent 不上报用量且模型不在价格表)——该 agent 的 budget 无法执行,取消护栏继续跑。\n",
25
30
  "judge.modelMissing":
26
- "judge 未配置模型:在 defineConfig({ judge: { model: \"...\" } })、eval 的 judge 配置或环境变量 NICEEVAL_JUDGE_MODEL 里指定评判模型(没有内置默认模型)",
31
+ "judge 未配置模型:在 defineConfig({ judge: { model: \"...\" } })、eval 的 judge 配置或环境变量 NICEEVAL_JUDGE_MODEL 里指定评判模型(没有内置默认模型)。\n" +
32
+ " 文档:node_modules/niceeval/docs-site/zh/guides/scoring-guide.mdx",
27
33
  "loaders.yamlMissing":
28
34
  "loadYaml(\"{{path}}\") 需要 YAML 解析器:请先 `pnpm add yaml`(或改用 loadJson + JSON 数据集)。",
29
35
  "cli.flag.parseError": "{{message}}\n运行 `niceeval --help` 查看用法。\n",
@@ -52,13 +58,16 @@ export const zhCN = {
52
58
  "cli.experimentGroup": "组",
53
59
  "cli.fallbackCleanupTimeout": "\ngraceful 清理超时,强制清理沙箱…\n",
54
60
  "cli.forceCleanupExit": "\n强制清理沙箱并退出…\n",
55
- "cli.init.done": "已创建 evals/ 和 niceeval.config.ts 起始文件。\n",
61
+ "cli.init.done": "已就绪:evals/、niceeval.config.ts,以及 AGENTS.md 里的 niceeval agent 指引区块(指向 node_modules/niceeval/docs-site)。\n",
56
62
  "cli.interruptCleanup": "\n收到中断,正在清理沙箱容器…(再按一次强制清理并退出)\n",
57
63
  "cli.list.header": "发现 {{count}} 个 eval:\n",
58
64
  "cli.noAgent": "未指定 agent(用 --agent <name>)。\n",
59
65
  "cli.none": "(无)",
60
66
  "cli.pressCtrlC": "按 Ctrl+C 退出。\n",
61
- "cli.run.experimentRequired": "运行 eval 必须通过 experiment:用 `niceeval exp [实验组|配置] [eval id 前缀]`。\n",
67
+ "cli.resultsPath": "结构化结果:{{path}}(每条结果的 artifactsDir 下是该次 attempt events.json / trace.json / diff.json)\n",
68
+ "cli.run.experimentRequired":
69
+ "运行 eval 必须通过 experiment:用 `niceeval exp [实验组|配置] [eval id 前缀]`。\n" +
70
+ " 文档:node_modules/niceeval/docs-site/zh/guides/write-experiment.mdx\n",
62
71
  "cli.run.experimentRequiredHint": "提示:\"{{pattern}}\" 是实验{{kind}},你大概想跑:niceeval exp {{pattern}}\n",
63
72
  "cli.run.experimentRequiredKnown": "已发现实验:{{experiments}}\n",
64
73
  "cli.sandboxFlagRemoved": "`--sandbox` 不是 CLI flag。请在 experiment(或 niceeval.config.ts 做全项目兜底)里把 sandbox 设成 dockerSandbox() / vercelSandbox() / e2bSandbox()(从 \"niceeval/sandbox\" 导入)。\n",
@@ -67,7 +76,8 @@ export const zhCN = {
67
76
  "cli.view.incompatible": "{{dir}}: 由 niceeval {{producer}} 写入(schemaVersion {{schemaVersion}}),当前 CLI 只读 schemaVersion {{supported}}。\n运行 `{{command}}` 查看这份报告。\n",
68
77
  "cli.view.url": "niceeval view: {{url}}\n",
69
78
  "context.capabilityMissing":
70
- "agent \"{{agent}}\" 不是沙箱型(defineSandboxAgent 构造),t.{{method}} 这类断言只有沙箱型 agent 可用。换用 defineSandboxAgent 构造的 agent,或去掉这条断言。",
79
+ "agent \"{{agent}}\" 不是沙箱型(defineSandboxAgent 构造),t.{{method}} 这类断言只有沙箱型 agent 可用。换用 defineSandboxAgent 构造的 agent,或去掉这条断言。\n" +
80
+ " 文档:node_modules/niceeval/docs-site/zh/guides/sandbox-agent.mdx",
71
81
  "context.skipEmpty": "skip() 需要一个非空理由。",
72
82
  "context.turnFailed": "本轮 send 返回 failed(turn status = failed):{{message}}",
73
83
  "context.turnFailedDefault": "本轮 send 返回 failed(turn status = failed)",
@@ -96,6 +106,7 @@ export const zhCN = {
96
106
  "judge.httpError": "judge HTTP {{status}}: {{body}}",
97
107
  "judge.probeFailed": "judge 预检失败({{model}}): {{error}}",
98
108
  "judge.probeMissingKey": "judge 模型 {{model}} 缺少 API key —— 请配置 {{envHint}}",
109
+ "live.more": "… 其余 {{hidden}} 项({{running}} 运行中 · {{waiting}} 等待 · {{done}} 已完成)",
99
110
  "live.running": " 正在运行 {{totalRuns}} 次 ({{evals}} eval × {{configs}} 配置, 并发 {{concurrency}}) {{completed}}/{{total}} 完成",
100
111
  "live.runningUnknown": " 正在运行… {{completed}}/{{total}} 完成",
101
112
  "live.waiting": "排队等待中…",
@@ -153,9 +164,12 @@ export const zhCN = {
153
164
  "runner.timeout": "attempt 超时({{timeoutMs}}ms)\n最近进度:\n{{recentLogs}}",
154
165
  "runner.traceSelected": " → 留 {{count}}(按语义)",
155
166
  "runner.resumeCarry": " · 复用上次 {{carried}} 个通过的结果,重跑 {{retry}} 个 eval\n",
167
+ "runner.resumeCarryDetail": " 复用 [{{experiment}}] {{evals}}\n",
156
168
  "runner.useRemoteAgent": "使用 remote agent(不创建沙箱)…",
157
169
  "sandbox.backendNotImplemented": "{{backend}} sandbox backend not implemented; use docker, vercel, or e2b",
158
- "sandbox.missingSpec": "沙箱型 agent 需要一个 sandbox,但没有提供。niceeval 不再自动选默认后端——请在 defineExperiment()/defineConfig() 里把 sandbox 设成 dockerSandbox() / vercelSandbox() / e2bSandbox()(从 \"niceeval/sandbox\" 导入)。",
170
+ "sandbox.missingSpec":
171
+ "沙箱型 agent 需要一个 sandbox,但没有提供。niceeval 不再自动选默认后端——请在 defineExperiment()/defineConfig() 里把 sandbox 设成 dockerSandbox() / vercelSandbox() / e2bSandbox()(从 \"niceeval/sandbox\" 导入)。\n" +
172
+ " 文档:node_modules/niceeval/docs-site/zh/guides/sandbox-backends.mdx",
159
173
  "sandbox.dependencyMissing.docker": "Docker sandbox requires 'dockerode'. Install it with: pnpm add dockerode @types/dockerode",
160
174
  "sandbox.dependencyMissing.e2b": "E2B sandbox requires 'e2b'. Install it with: pnpm add e2b",
161
175
  "sandbox.dependencyMissing.vercel": "Vercel sandbox requires '@vercel/sandbox'. Install it with: pnpm add @vercel/sandbox",
@@ -0,0 +1,71 @@
1
+ // bub tape 解析回归锁。
2
+ //
3
+ // 背景(2026-07 实测):模型在同一步里「先说话再调工具」时,bub 的 tape 把伴随文本
4
+ // 记在 tool_call 条目的 payload.content 上(修复前上游直接丢弃,transcript 里
5
+ // 每次 send 后面都看不到 AI 回复 —— 只有最后一步纯文本收尾能出现)。
6
+ // 解析器必须把这段文本还原成 assistant message 事件,且顺序在 action.called 之前。
7
+
8
+ import { describe, it, expect } from "vitest";
9
+ import { parseBubTranscript } from "./bub.ts";
10
+
11
+ const line = (obj: object) => JSON.stringify(obj);
12
+
13
+ describe("parseBubTranscript", () => {
14
+ it("tool_call 携带 payload.content 时,先 emit assistant message 再 emit action.called", () => {
15
+ const raw = [
16
+ line({ kind: "message", payload: { role: "user", content: "migrate the routes" } }),
17
+ line({
18
+ kind: "tool_call",
19
+ payload: {
20
+ content: "I'll start by listing the pages directory.",
21
+ calls: [
22
+ { id: "call_1", type: "function", function: { name: "bash", arguments: '{"cmd":"ls pages"}' } },
23
+ ],
24
+ },
25
+ }),
26
+ line({ kind: "tool_result", payload: { results: ["index.js\nabout.js"] } }),
27
+ ].join("\n");
28
+
29
+ const { events } = parseBubTranscript(raw);
30
+ const types = events.map((e) => e.type);
31
+ expect(types).toEqual(["message", "message", "action.called", "action.result"]);
32
+
33
+ const assistant = events[1];
34
+ expect(assistant).toMatchObject({
35
+ type: "message",
36
+ role: "assistant",
37
+ text: "I'll start by listing the pages directory.",
38
+ });
39
+
40
+ const call = events[2];
41
+ expect(call).toMatchObject({ type: "action.called", callId: "call_1", name: "bash", tool: "shell" });
42
+ });
43
+
44
+ it("tool_call 无 content(存量 tape)不产生幻影 assistant message", () => {
45
+ const raw = [
46
+ line({ kind: "message", payload: { role: "user", content: "do it" } }),
47
+ line({
48
+ kind: "tool_call",
49
+ payload: { calls: [{ id: "call_1", function: { name: "fs_read", arguments: '{"path":"a.ts"}' } }] },
50
+ }),
51
+ line({ kind: "tool_result", payload: { results: ["file body"] } }),
52
+ ].join("\n");
53
+
54
+ const { events } = parseBubTranscript(raw);
55
+ expect(events.map((e) => e.type)).toEqual(["message", "action.called", "action.result"]);
56
+ });
57
+
58
+ it("tool_call 的 content 为块数组时也能抽出文本", () => {
59
+ const raw = line({
60
+ kind: "tool_call",
61
+ payload: {
62
+ content: [{ type: "text", text: "Checking the config first." }],
63
+ calls: [{ id: "call_1", function: { name: "bash", arguments: "{}" } }],
64
+ },
65
+ });
66
+
67
+ const { events } = parseBubTranscript(raw);
68
+ expect(events[0]).toMatchObject({ type: "message", role: "assistant", text: "Checking the config first." });
69
+ expect(events[1]).toMatchObject({ type: "action.called", callId: "call_1" });
70
+ });
71
+ });
@@ -163,6 +163,11 @@ export function parseBubTranscript(raw: string | undefined): ParsedTranscript {
163
163
  const text = extractText(get(payload, "content") ?? get(payload, "text"));
164
164
  if (text) events.push({ type: "message", role, text });
165
165
  } else if (kind === "tool_call") {
166
+ // 模型「先说话再调工具」:伴随文本记在 tool_call 的 payload.content 上
167
+ // (bub 不给它单独的 message 条目)。先还原成 assistant message,再吐 calls,
168
+ // 否则 transcript 里每次 send 后面都看不到 AI 回复。
169
+ const accompanying = extractText(get(payload, "content"));
170
+ if (accompanying) events.push({ type: "message", role: "assistant", text: accompanying });
166
171
  const calls = get(payload, "calls");
167
172
  const list = Array.isArray(calls) ? calls : [payload];
168
173
  lastCallIds = [];
package/src/o11y/types.ts CHANGED
@@ -3,14 +3,23 @@
3
3
 
4
4
  import type { JsonValue, SourceLoc } from "../shared/types.ts";
5
5
 
6
- /** 一次运行的 token 用量(沙箱型从 transcript 抠,remote 由 send 返回)。 */
6
+ /** 一次运行的 token 用量(沙箱型从 transcript/OTel span 的 `gen_ai.usage.*` 属性抠,remote 由 send 的 `Turn.usage` 直接返回)。 */
7
7
  export interface Usage {
8
+ /** 输入(prompt)token 数,不含缓存命中部分。 */
8
9
  inputTokens: number;
10
+ /** 输出(completion)token 数。 */
9
11
  outputTokens: number;
12
+ /** 命中 prompt 缓存、按缓存价读取的 token 数(省略表示该 agent 不上报此项)。 */
10
13
  cacheReadTokens?: number;
14
+ /** 写入 prompt 缓存的 token 数(省略表示该 agent 不上报此项)。 */
11
15
  cacheWriteTokens?: number;
16
+ /** 本次运行触发的模型请求次数(多轮/重试可能大于 1)。 */
12
17
  requests?: number;
13
- /** 网关实测成本(若 agent 带回)——优先于价格表估算。 */
18
+ /**
19
+ * 网关/adapter 实测的真实美元成本(只能由 `Turn.usage.costUSD` 显式带回,从不从
20
+ * token 用量或 OTel span 反推得到)。存在时优先于按价格表(`defineConfig({ pricing })`)
21
+ * 估算的成本——见 `estimateCost` 的 `usage.costUSD ?? estimateCost(...)` 兜底顺序。
22
+ */
14
23
  costUSD?: number;
15
24
  }
16
25
 
@@ -28,12 +37,19 @@ export type ToolName =
28
37
  | "agent_task"
29
38
  | "unknown";
30
39
 
40
+ /** HITL 停轮请求的结构化描述,供 `t.requireInputRequest(filter)` / `t.respondAll` 按条件匹配。 */
31
41
  export interface InputRequest {
42
+ /** 请求的唯一标识;多个请求并停时,`InputResponse.requestId` 靠它对位。 */
32
43
  readonly id?: string;
44
+ /** 模型提出的原始问题/文本(用于 `stringMatches` 过滤)。 */
33
45
  readonly prompt?: string;
46
+ /** adapter 自定义的人类可读展示文案,与 `prompt` 二选一或并存,用于 UI/日志展示及过滤匹配。 */
34
47
  readonly display?: string;
48
+ /** 请求关联的动作类型(如某个待批准的工具调用名),供过滤用。 */
35
49
  readonly action?: string;
50
+ /** 请求携带的结构化输入(如待批准命令的参数)。 */
36
51
  readonly input?: JsonValue;
52
+ /** 若请求提供了预设选项(如批准/拒绝),逐项列出;`id` 对应 `InputResponse.optionId`。 */
37
53
  readonly options?: readonly { id: string; label?: string }[];
38
54
  }
39
55
 
@@ -42,19 +58,28 @@ export interface InputRequest {
42
58
  * 各 agent 五花八门的原始 transcript 映射成 StreamEvent[];映射完,整套断言免费。
43
59
  */
44
60
  export type StreamEvent =
61
+ /** 一条文本消息(assistant 回复或 user 输入);`loc` 是可选的源码位置,用于把消息叠回 eval 源码。 */
45
62
  | { type: "message"; role: "assistant" | "user"; text: string; loc?: SourceLoc }
63
+ /** 发起一次工具/动作调用;`tool` 是归一化后的规范工具名,原始名保留在 DerivedFacts.ToolCall.originalName。 */
46
64
  | { type: "action.called"; callId: string; name: string; input: JsonValue; tool?: ToolName }
65
+ /** 一次工具/动作调用的结果,按 `callId` 与对应的 `action.called` 对位。 */
47
66
  | {
48
67
  type: "action.result";
49
68
  callId: string;
50
69
  output?: JsonValue;
51
70
  status: "completed" | "failed" | "rejected";
52
71
  }
72
+ /** 发起一次子 agent 调用(如 Task 工具、远程 sub-agent);`remoteUrl` 仅远程子 agent 有。 */
53
73
  | { type: "subagent.called"; callId: string; name: string; remoteUrl?: string }
74
+ /** 一次子 agent 调用的结果,按 `callId` 与对应的 `subagent.called` 对位。 */
54
75
  | { type: "subagent.completed"; callId: string; output?: JsonValue; status: "completed" | "failed" }
76
+ /** 模型停下来向人请求输入(HITL);具体请求内容见 InputRequest。 */
55
77
  | { type: "input.requested"; request: InputRequest }
78
+ /** 模型的思考/推理文本(非最终回复)。 */
56
79
  | { type: "thinking"; text: string }
80
+ /** 上下文被压缩/摘要(如超长会话截断历史);`reason` 是可选的压缩原因说明。 */
57
81
  | { type: "compaction"; reason?: string }
82
+ /** 运行中出现的错误。 */
58
83
  | { type: "error"; message: string };
59
84
 
60
85
  /** core 从事件流折叠出的结构化事实(deriveRunFacts)。 */
@@ -36,6 +36,7 @@ import type {
36
36
  Telemetry,
37
37
  TraceSpan,
38
38
  } from "../types.ts";
39
+ import { runWho } from "./types.ts";
39
40
  import type { AgentRun, Attempt, RunOptions } from "./types.ts";
40
41
 
41
42
  export function runAttemptEffect(
@@ -72,7 +73,7 @@ export function runAttemptEffect(
72
73
 
73
74
  // 流式进度打到宿主 stderr(结果走 stdout,互不干扰)。容器主日志【不】放这些进度标记 ——
74
75
  // 那里留给 agent 的原始输出(adapter 给 agent 命令开 { stream: true })。
75
- const who = run.model ? `${run.agent.name}/${run.model}` : run.agent.name;
76
+ const who = runWho(run);
76
77
  // 同时保留最近 20 条进度消息,timeout 时嵌入 error 字段方便定位卡在哪一步。
77
78
  const recentLogs: string[] = [];
78
79
  const log = (m: string) => {
@@ -20,12 +20,15 @@ function producerVersion(): Promise<string | undefined> {
20
20
  return producerVersionPromise;
21
21
  }
22
22
 
23
- /** 一个 attempt 的工件子目录(相对 run 根):<evalId>/<agent>/<model>/a<attempt>。 */
23
+ /** 一个 attempt 的工件子目录(相对 run 根):<evalId>/<agent>/<model>[/<experiment>]/a<attempt>。 */
24
24
  function attemptDir(r: EvalResult): string {
25
25
  const safe = (s: string) => s.replace(/[^\w.@-]/g, "_");
26
26
  // evalId 里的 / 保留作目录层级,其余危险字符替换。
27
27
  const id = r.id.replace(/[^\w./@-]/g, "_");
28
- return `${id}/${safe(r.agent)}/${safe(r.model ?? "default")}/a${r.attempt}`;
28
+ // experiment 段:两个实验可以同 agentmodel、只差 flags(feature A/B),没有这一段
29
+ // 它们的工件会互相覆盖。experimentId 里的 / 不作层级(压成 _),整个实验一格。
30
+ const exp = r.experimentId ? `/${safe(r.experimentId)}` : "";
31
+ return `${id}/${safe(r.agent)}/${safe(r.model ?? "default")}${exp}/a${r.attempt}`;
29
32
  }
30
33
 
31
34
  /** summary.json 用的瘦身结果:去掉大数组(events/trace/o11y/diff/sources),换成目录引用 + 存在标记。 */
@@ -51,7 +54,10 @@ function slimResult(r: EvalResult): EvalResult {
51
54
  };
52
55
  }
53
56
 
54
- export function Artifacts(root = ".niceeval"): Reporter {
57
+ /** Artifacts 报告器额外暴露输出目录:CLI 在 run 结束时打出 summary.json 路径给 agent 直读。 */
58
+ export type ArtifactsReporter = Reporter & { outputDir(): string };
59
+
60
+ export function Artifacts(root = ".niceeval"): ArtifactsReporter {
55
61
  let outputDir = "";
56
62
  const ensureDir = async (): Promise<void> => {
57
63
  if (!outputDir) outputDir = join(root, safeTimestamp(new Date()));
@@ -59,6 +65,8 @@ export function Artifacts(root = ".niceeval"): Reporter {
59
65
  };
60
66
 
61
67
  return {
68
+ outputDir: () => outputDir,
69
+
62
70
  async onRunStart() {
63
71
  outputDir = join(root, safeTimestamp(new Date()));
64
72
  await mkdir(outputDir, { recursive: true });
@@ -102,20 +102,57 @@ export function Live(rows: LiveRow[], totalAttempts: number): LiveReporter {
102
102
  return `\x1B[2K${hdr}`;
103
103
  }
104
104
 
105
- function draw(frame: number) {
106
- if (!process.stderr.isTTY) return;
107
-
108
- if (drawnLines > 0) {
109
- process.stderr.write(`\x1B[${drawnLines}A`);
105
+ // 终端放不下全部行时,光标回跳会被屏幕顶端截断,导致每帧往下追加整表。
106
+ // 所以按终端高度截断:优先显示运行中的行,放不下的折叠成一行摘要。
107
+ function frameLines(frame: number): string[] {
108
+ const lines = [renderHeader()];
109
+ const termRows = process.stderr.rows || 30;
110
+ // 预留:1 行表头 + 1 行防止末尾换行触发滚动
111
+ const budget = Math.max(1, termRows - 2);
112
+
113
+ if (keyOrder.length <= budget) {
114
+ for (const k of keyOrder) lines.push(renderRow(stateMap.get(k)!, frame));
115
+ return lines;
110
116
  }
111
117
 
112
- let out = renderHeader() + "\n";
118
+ const running: string[] = [];
119
+ const waiting: string[] = [];
120
+ const done: string[] = [];
121
+ for (const k of keyOrder) {
122
+ const s = stateMap.get(k)!;
123
+ if (s.completed >= s.total) done.push(k);
124
+ else if (s.started) running.push(k);
125
+ else waiting.push(k);
126
+ }
127
+ // 选出要显示的 key(运行中 > 等待 > 已完成),但按原始顺序渲染,避免行来回跳动
128
+ const shown = new Set([...running, ...waiting, ...done].slice(0, budget - 1));
113
129
  for (const k of keyOrder) {
114
- out += renderRow(stateMap.get(k)!, frame) + "\n";
130
+ if (shown.has(k)) lines.push(renderRow(stateMap.get(k)!, frame));
115
131
  }
132
+ lines.push(
133
+ `\x1B[2K ${t("live.more", {
134
+ hidden: keyOrder.length - shown.size,
135
+ running: running.filter((k) => !shown.has(k)).length,
136
+ waiting: waiting.filter((k) => !shown.has(k)).length,
137
+ done: done.filter((k) => !shown.has(k)).length,
138
+ })}`,
139
+ );
140
+ return lines;
141
+ }
116
142
 
143
+ function draw(frame: number) {
144
+ if (!process.stderr.isTTY) return;
145
+
146
+ const lines = frameLines(frame);
147
+ let out = drawnLines > 0 ? `\x1B[${drawnLines}A` : "";
148
+ out += lines.join("\n") + "\n";
149
+ // 本帧比上帧短(行完成后折叠、终端拉高)时,清掉下方残留的旧行
150
+ const extra = drawnLines - lines.length;
151
+ if (extra > 0) {
152
+ out += "\x1B[2K\n".repeat(extra) + `\x1B[${extra}A`;
153
+ }
117
154
  process.stderr.write(out);
118
- drawnLines = 1 + keyOrder.length;
155
+ drawnLines = lines.length;
119
156
  }
120
157
 
121
158
  function clearDisplay() {
@@ -0,0 +1,50 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { judgeProbeTargets } from "./run.ts";
3
+ import type { JudgeConfig } from "../types.ts";
4
+
5
+ // judge 预检的目标收敛:只探测「实际要跑、且源码里出现 judge 字样」的 eval 的生效配置。
6
+ // 这是对 memory/judge-config-precheck-hard-fails-without-key 的修复守护——
7
+ // 全局配了 judge 但选中的 eval 都不用时,不能再因 judge key / 端点问题拦下整次运行。
8
+ describe("judgeProbeTargets", () => {
9
+ const configJudge: JudgeConfig = { model: "gpt-5.4" };
10
+
11
+ it("skips probing when no selected eval mentions judge", () => {
12
+ const evals = [
13
+ { source: `t.check(t.reply, includes("2"));`, judge: undefined },
14
+ { source: `await t.sandbox.exec("pnpm test");`, judge: undefined },
15
+ ];
16
+ expect(judgeProbeTargets(evals, configJudge)).toEqual([]);
17
+ });
18
+
19
+ it("probes config-level judge when a selected eval mentions judge", () => {
20
+ const evals = [
21
+ { source: `t.judge.autoevals.closedQA("did it summarize?");`, judge: undefined },
22
+ { source: `t.check(t.reply, includes("ok"));`, judge: undefined },
23
+ ];
24
+ expect(judgeProbeTargets(evals, configJudge)).toEqual([configJudge]);
25
+ });
26
+
27
+ it("resolves eval-level judge over config-level, like attempt resolution", () => {
28
+ const evalJudge: JudgeConfig = { model: "deepseek-v4", baseUrl: "http://localhost:8787/v1" };
29
+ const evals = [{ source: `t.judge.autoevals.factuality("2")`, judge: evalJudge }];
30
+ expect(judgeProbeTargets(evals, configJudge)).toEqual([evalJudge]);
31
+ });
32
+
33
+ it("dedupes identical effective configs across evals", () => {
34
+ const evals = [
35
+ { source: `t.judge.autoevals.closedQA("a")`, judge: undefined },
36
+ { source: `t.judge.autoevals.closedQA("b")`, judge: undefined },
37
+ ];
38
+ expect(judgeProbeTargets(evals, configJudge)).toEqual([configJudge]);
39
+ });
40
+
41
+ it("returns nothing when judge is used but no config exists (runtime env fallback)", () => {
42
+ const evals = [{ source: `t.judge.autoevals.closedQA("a")`, judge: undefined }];
43
+ expect(judgeProbeTargets(evals, undefined)).toEqual([]);
44
+ });
45
+
46
+ it("does not match judge as part of a longer identifier", () => {
47
+ const evals = [{ source: `const prejudged = true;`, judge: undefined }];
48
+ expect(judgeProbeTargets(evals, configJudge)).toEqual([]);
49
+ });
50
+ });