niceeval 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (378) hide show
  1. package/INDEX.md +23 -23
  2. package/README.zh.md +6 -6
  3. package/dist/agents/types.d.ts +69 -7
  4. package/dist/context/types.d.ts +32 -12
  5. package/dist/i18n/en.d.ts +54 -0
  6. package/dist/i18n/zh-CN.d.ts +57 -3
  7. package/dist/o11y/types.d.ts +16 -2
  8. package/dist/report/aggregate.d.ts +32 -24
  9. package/dist/report/aggregate.js +158 -50
  10. package/dist/report/built-in/index.d.ts +2 -0
  11. package/dist/report/built-in/index.js +8 -0
  12. package/dist/report/components.d.ts +93 -160
  13. package/dist/report/components.js +377 -114
  14. package/dist/report/compute.d.ts +87 -81
  15. package/dist/report/compute.js +597 -417
  16. package/dist/report/flag.d.ts +32 -6
  17. package/dist/report/flag.js +92 -4
  18. package/dist/report/format.d.ts +19 -11
  19. package/dist/report/format.js +30 -13
  20. package/dist/report/index.d.ts +16 -16
  21. package/dist/report/index.js +20 -21
  22. package/dist/report/load.js +3 -2
  23. package/dist/report/locale.d.ts +57 -33
  24. package/dist/report/locale.js +122 -56
  25. package/dist/report/metrics.d.ts +23 -4
  26. package/dist/report/metrics.js +110 -25
  27. package/dist/report/primitives.d.ts +48 -15
  28. package/dist/report/primitives.js +135 -26
  29. package/dist/report/react/AttemptList.d.ts +9 -7
  30. package/dist/report/react/AttemptList.js +17 -10
  31. package/dist/report/react/DeltaTable.js +19 -18
  32. package/dist/report/react/EvalList.d.ts +4 -4
  33. package/dist/report/react/EvalList.js +0 -0
  34. package/dist/report/react/ExperimentComparison.d.ts +10 -0
  35. package/dist/report/react/ExperimentComparison.js +12 -0
  36. package/dist/report/react/ExperimentList.d.ts +4 -3
  37. package/dist/report/react/ExperimentList.js +17 -18
  38. package/dist/report/react/MetricBars.js +5 -4
  39. package/dist/report/react/MetricLine.js +12 -5
  40. package/dist/report/react/MetricMatrix.js +1 -1
  41. package/dist/report/react/MetricScatter.js +59 -28
  42. package/dist/report/react/MetricTable.js +2 -12
  43. package/dist/report/react/ScopeSummary.d.ts +10 -0
  44. package/dist/report/react/ScopeSummary.js +17 -0
  45. package/dist/report/react/Scoreboard.js +6 -6
  46. package/dist/report/react/cell.js +2 -2
  47. package/dist/report/react/chart-math.d.ts +23 -6
  48. package/dist/report/react/chart-math.js +71 -19
  49. package/dist/report/react/fixtures.d.ts +5 -9
  50. package/dist/report/react/fixtures.js +110 -147
  51. package/dist/report/react/index.d.ts +15 -5
  52. package/dist/report/react/index.js +18 -7
  53. package/dist/report/report.d.ts +137 -16
  54. package/dist/report/report.js +259 -28
  55. package/dist/report/text/faces.d.ts +17 -19
  56. package/dist/report/text/faces.js +253 -184
  57. package/dist/report/text/plot.js +1 -1
  58. package/dist/report/text/table.js +38 -7
  59. package/dist/report/tree.d.ts +90 -40
  60. package/dist/report/tree.js +252 -94
  61. package/dist/report/types.d.ts +247 -284
  62. package/dist/report/types.js +4 -3
  63. package/dist/report/web.d.ts +21 -5
  64. package/dist/report/web.js +42 -16
  65. package/dist/results/select.d.ts +38 -16
  66. package/dist/results/select.js +73 -25
  67. package/dist/results/types.d.ts +49 -14
  68. package/dist/runner/feedback/sink.d.ts +110 -0
  69. package/dist/runner/types.d.ts +513 -22
  70. package/dist/sandbox/docker.d.ts +23 -2
  71. package/dist/sandbox/e2b.d.ts +15 -1
  72. package/dist/sandbox/errors.d.ts +30 -3
  73. package/dist/sandbox/io-retry.d.ts +17 -0
  74. package/dist/sandbox/registry.d.ts +2 -0
  75. package/dist/sandbox/resolve.d.ts +18 -5
  76. package/dist/sandbox/retry.d.ts +11 -1
  77. package/dist/sandbox/types.d.ts +39 -5
  78. package/dist/sandbox/vercel.d.ts +7 -1
  79. package/dist/scoring/coverage.d.ts +30 -0
  80. package/dist/scoring/display.d.ts +21 -0
  81. package/dist/scoring/display.js +120 -0
  82. package/dist/scoring/types.d.ts +103 -20
  83. package/dist/shared/aggregate.d.ts +4 -2
  84. package/dist/shared/aggregate.js +8 -7
  85. package/dist/shared/types.d.ts +28 -0
  86. package/dist/tty-line.d.ts +0 -4
  87. package/dist/util.d.ts +23 -0
  88. package/docs-site/zh/README.md +44 -0
  89. package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
  90. package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
  91. package/docs-site/zh/examples/index.mdx +50 -0
  92. package/docs-site/zh/{concepts → explanation}/adapter.mdx +31 -13
  93. package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
  94. package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
  95. package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
  96. package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
  97. package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
  98. package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
  99. package/docs-site/zh/{concepts → explanation}/overview.mdx +11 -11
  100. package/docs-site/zh/{guides → explanation}/runner.mdx +18 -8
  101. package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
  102. package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +35 -33
  103. package/docs-site/zh/{guides → how-to}/authoring.mdx +35 -2
  104. package/docs-site/zh/{guides → how-to}/ci-integration.mdx +23 -12
  105. package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
  106. package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +47 -21
  107. package/docs-site/zh/{guides → how-to}/custom-reports.mdx +34 -39
  108. package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +25 -3
  109. package/docs-site/zh/{guides → how-to}/experiments.mdx +12 -5
  110. package/docs-site/zh/how-to/publish-report.mdx +105 -0
  111. package/docs-site/zh/{guides → how-to}/reporters.mdx +2 -2
  112. package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +56 -7
  113. package/docs-site/zh/how-to/sandbox-providers.mdx +350 -0
  114. package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +4 -4
  115. package/docs-site/zh/{guides → how-to}/viewing-results.mdx +82 -39
  116. package/docs-site/zh/{guides → how-to}/write-experiment.mdx +6 -4
  117. package/docs-site/zh/{guides → how-to}/write-send.mdx +30 -14
  118. package/docs-site/zh/index.mdx +24 -26
  119. package/docs-site/zh/introduction.mdx +8 -8
  120. package/docs-site/zh/reference/builtin-agents.mdx +32 -5
  121. package/docs-site/zh/reference/capabilities.mdx +8 -8
  122. package/docs-site/zh/reference/cli.mdx +40 -12
  123. package/docs-site/zh/reference/define-agent.mdx +58 -5
  124. package/docs-site/zh/reference/define-config.mdx +1 -1
  125. package/docs-site/zh/reference/define-eval.mdx +42 -9
  126. package/docs-site/zh/reference/events.mdx +3 -3
  127. package/docs-site/zh/reference/expect.mdx +26 -1
  128. package/docs-site/zh/{guides → reference}/official-adapters.mdx +32 -8
  129. package/docs-site/zh/{guides → reference}/report-components.mdx +45 -33
  130. package/docs-site/zh/{guides → reference}/results-data.mdx +21 -13
  131. package/docs-site/zh/troubleshooting/debug-sandbox.mdx +57 -0
  132. package/docs-site/zh/troubleshooting/debugging.mdx +212 -0
  133. package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
  134. package/package.json +10 -2
  135. package/src/agents/ai-sdk-otel.test.ts +1 -0
  136. package/src/agents/ai-sdk.test.ts +3 -0
  137. package/src/agents/ai-sdk.ts +3 -0
  138. package/src/agents/bub-install-spec.test.ts +34 -0
  139. package/src/agents/bub-install-spec.ts +32 -0
  140. package/src/agents/bub.ts +31 -32
  141. package/src/agents/claude-code.test.ts +130 -9
  142. package/src/agents/claude-code.ts +76 -4
  143. package/src/agents/codex.test.ts +189 -40
  144. package/src/agents/codex.ts +155 -14
  145. package/src/agents/coding-cli-versions.test.ts +15 -0
  146. package/src/agents/coding-cli-versions.ts +3 -0
  147. package/src/agents/index.ts +13 -2
  148. package/src/agents/langgraph.test.ts +204 -0
  149. package/src/agents/langgraph.ts +495 -0
  150. package/src/agents/marketplace.ts +85 -0
  151. package/src/agents/native-config.test.ts +179 -0
  152. package/src/agents/native-config.ts +267 -0
  153. package/src/agents/openai-compat.test.ts +1 -0
  154. package/src/agents/openai-compat.ts +1 -1
  155. package/src/agents/openclaw.test.ts +31 -0
  156. package/src/agents/openclaw.ts +171 -0
  157. package/src/agents/plugin-config.test.ts +1 -0
  158. package/src/agents/sdk-streams.test.ts +79 -0
  159. package/src/agents/sdk-streams.ts +55 -10
  160. package/src/agents/skills.test.ts +1 -0
  161. package/src/agents/streaming.test.ts +3 -9
  162. package/src/agents/streaming.ts +2 -2
  163. package/src/agents/types.ts +71 -8
  164. package/src/agents/ui-message-stream.test.ts +3 -0
  165. package/src/cli.ts +446 -124
  166. package/src/context/context.test.ts +51 -12
  167. package/src/context/context.ts +162 -30
  168. package/src/context/session.test.ts +2 -1
  169. package/src/context/session.ts +115 -7
  170. package/src/context/types.ts +30 -12
  171. package/src/define.test.ts +13 -8
  172. package/src/define.ts +25 -4
  173. package/src/expect/index.ts +53 -23
  174. package/src/i18n/en.ts +81 -17
  175. package/src/i18n/zh-CN.ts +80 -17
  176. package/src/o11y/cost.test.ts +1 -0
  177. package/src/o11y/execution-tree.test.ts +1 -20
  178. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  179. package/src/o11y/otlp/parse.test.ts +1 -0
  180. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  181. package/src/o11y/parsers/bub.test.ts +1 -0
  182. package/src/o11y/parsers/claude-code.test.ts +1 -34
  183. package/src/o11y/parsers/openclaw.test.ts +154 -0
  184. package/src/o11y/parsers/openclaw.ts +310 -0
  185. package/src/o11y/prices.json +746 -311
  186. package/src/o11y/tool-names.test.ts +1 -0
  187. package/src/o11y/types.ts +16 -2
  188. package/src/report/aggregate.ts +178 -61
  189. package/src/report/built-in/index.tsx +9 -0
  190. package/src/report/components.tsx +625 -279
  191. package/src/report/compute.ts +723 -491
  192. package/src/report/dual-render.test.tsx +741 -1024
  193. package/src/report/flag.ts +104 -12
  194. package/src/report/format.ts +32 -12
  195. package/src/report/index.ts +119 -46
  196. package/src/report/load.ts +3 -2
  197. package/src/report/locale.ts +136 -65
  198. package/src/report/metrics.ts +108 -25
  199. package/src/report/primitives.tsx +196 -45
  200. package/src/report/react/AttemptList.tsx +30 -43
  201. package/src/report/react/DeltaTable.tsx +63 -45
  202. package/src/report/react/EvalList.tsx +0 -0
  203. package/src/report/react/ExperimentComparison.tsx +73 -0
  204. package/src/report/react/ExperimentList.tsx +50 -32
  205. package/src/report/react/MetricBars.tsx +5 -4
  206. package/src/report/react/MetricLine.tsx +13 -8
  207. package/src/report/react/MetricMatrix.tsx +2 -2
  208. package/src/report/react/MetricScatter.tsx +86 -34
  209. package/src/report/react/MetricTable.tsx +4 -76
  210. package/src/report/react/ScopeSummary.tsx +86 -0
  211. package/src/report/react/Scoreboard.tsx +28 -10
  212. package/src/report/react/cell.tsx +2 -2
  213. package/src/report/react/chart-math.test.ts +85 -0
  214. package/src/report/react/chart-math.ts +101 -22
  215. package/src/report/react/enhance.js +89 -5
  216. package/src/report/react/fixtures.ts +114 -154
  217. package/src/report/react/index.tsx +24 -39
  218. package/src/report/react/render.test.tsx +138 -158
  219. package/src/report/react/styles.css +243 -82
  220. package/src/report/report.test.ts +779 -841
  221. package/src/report/report.ts +423 -41
  222. package/src/report/text/faces.ts +290 -193
  223. package/src/report/text/plot.ts +1 -1
  224. package/src/report/text/table.ts +44 -7
  225. package/src/report/tree.ts +362 -104
  226. package/src/report/types.ts +261 -271
  227. package/src/report/web.ts +63 -20
  228. package/src/results/annotated-source.test.ts +62 -9
  229. package/src/results/annotated-source.ts +64 -6
  230. package/src/results/attempt-evidence.test.ts +13 -11
  231. package/src/results/attempt-evidence.ts +20 -13
  232. package/src/results/attempt-source.ts +6 -3
  233. package/src/results/copy.ts +150 -60
  234. package/src/results/host-equivalence.test.ts +34 -20
  235. package/src/results/index.ts +12 -4
  236. package/src/results/locator.test.ts +1 -22
  237. package/src/results/open.ts +15 -5
  238. package/src/results/publish.ts +149 -0
  239. package/src/results/results.test.ts +89 -54
  240. package/src/results/select.ts +104 -34
  241. package/src/results/truncate.ts +90 -0
  242. package/src/results/types.ts +43 -14
  243. package/src/results/writer.ts +31 -13
  244. package/src/runner/attempt.test.ts +138 -7
  245. package/src/runner/attempt.ts +603 -104
  246. package/src/runner/discover.test.ts +47 -0
  247. package/src/runner/discover.ts +36 -2
  248. package/src/runner/eval-source.test.ts +1 -27
  249. package/src/runner/feedback/agent.test.ts +504 -0
  250. package/src/runner/feedback/agent.ts +409 -0
  251. package/src/runner/feedback/ci.test.ts +562 -0
  252. package/src/runner/feedback/ci.ts +401 -0
  253. package/src/runner/feedback/coordinator.test.ts +317 -0
  254. package/src/runner/feedback/coordinator.ts +397 -0
  255. package/src/runner/feedback/failure.ts +40 -0
  256. package/src/runner/feedback/human.test.ts +616 -0
  257. package/src/runner/feedback/human.ts +535 -0
  258. package/src/runner/feedback/index.ts +66 -0
  259. package/src/runner/feedback/io.ts +78 -0
  260. package/src/runner/feedback/profile.test.ts +50 -0
  261. package/src/runner/feedback/profile.ts +58 -0
  262. package/src/runner/feedback/reducer.test.ts +395 -0
  263. package/src/runner/feedback/reducer.ts +260 -0
  264. package/src/runner/feedback/renderer.ts +82 -0
  265. package/src/runner/feedback/sink.ts +203 -0
  266. package/src/runner/feedback/testing.ts +106 -0
  267. package/src/runner/ledger.test.ts +230 -0
  268. package/src/runner/ledger.ts +329 -0
  269. package/src/runner/report.test.ts +128 -3
  270. package/src/runner/report.ts +33 -9
  271. package/src/runner/reporters/artifacts.ts +8 -2
  272. package/src/runner/reporters/braintrust.test.ts +8 -7
  273. package/src/runner/reporters/braintrust.ts +9 -2
  274. package/src/runner/reporters/index.ts +2 -2
  275. package/src/runner/reporters/json.test.ts +162 -0
  276. package/src/runner/reporters/json.ts +35 -8
  277. package/src/runner/reporters/shared.ts +1 -5
  278. package/src/runner/run.test.ts +760 -3
  279. package/src/runner/run.ts +243 -37
  280. package/src/runner/sandbox-prep.ts +3 -42
  281. package/src/runner/timing.ts +158 -0
  282. package/src/runner/types.ts +518 -22
  283. package/src/sandbox/checkpoint.test.ts +55 -0
  284. package/src/sandbox/checkpoint.ts +29 -8
  285. package/src/sandbox/cli-commands.ts +407 -0
  286. package/src/sandbox/docker.ts +115 -16
  287. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  288. package/src/sandbox/e2b-agent-template.ts +94 -0
  289. package/src/sandbox/e2b.ts +74 -9
  290. package/src/sandbox/errors.ts +111 -4
  291. package/src/sandbox/index.ts +2 -0
  292. package/src/sandbox/io-retry.test.ts +58 -0
  293. package/src/sandbox/io-retry.ts +45 -0
  294. package/src/sandbox/keep-registry.test.ts +86 -0
  295. package/src/sandbox/keep-registry.ts +142 -0
  296. package/src/sandbox/keep.ts +178 -0
  297. package/src/sandbox/paths.test.ts +1 -0
  298. package/src/sandbox/paths.ts +19 -8
  299. package/src/sandbox/registry.ts +20 -3
  300. package/src/sandbox/resolve.ts +76 -11
  301. package/src/sandbox/retry.test.ts +70 -0
  302. package/src/sandbox/retry.ts +46 -4
  303. package/src/sandbox/types.ts +44 -6
  304. package/src/sandbox/vercel.ts +43 -20
  305. package/src/scoring/collector.ts +60 -17
  306. package/src/scoring/coverage.ts +95 -0
  307. package/src/scoring/diff.ts +81 -0
  308. package/src/scoring/display.test.ts +121 -0
  309. package/src/scoring/display.ts +133 -0
  310. package/src/scoring/evidence.test.ts +189 -0
  311. package/src/scoring/judge.test.ts +142 -0
  312. package/src/scoring/judge.ts +15 -18
  313. package/src/scoring/scoped.ts +217 -50
  314. package/src/scoring/types.ts +117 -20
  315. package/src/scoring/verdict.ts +16 -4
  316. package/src/shared/aggregate.ts +8 -6
  317. package/src/shared/types.ts +31 -0
  318. package/src/show/compose.ts +50 -67
  319. package/src/show/index.ts +127 -56
  320. package/src/show/render.ts +662 -131
  321. package/src/show/report-host.test.ts +188 -0
  322. package/src/show/report-host.ts +375 -0
  323. package/src/show/show.test.ts +320 -54
  324. package/src/tty-line.ts +8 -26
  325. package/src/util.test.ts +1 -0
  326. package/src/util.ts +41 -0
  327. package/src/view/app/App.test.tsx +69 -0
  328. package/src/view/app/App.tsx +144 -48
  329. package/src/view/app/components/AttemptModal.tsx +423 -11
  330. package/src/view/app/components/CodeView.tsx +41 -14
  331. package/src/view/app/components/CopyControls.tsx +2 -2
  332. package/src/view/app/i18n.ts +37 -17
  333. package/src/view/app/lib/attempt-route.test.ts +1 -0
  334. package/src/view/app/lib/verdict.ts +7 -9
  335. package/src/view/app/main.tsx +13 -8
  336. package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
  337. package/src/view/app/types.ts +4 -1
  338. package/src/view/artifact-serving.test.ts +2 -1
  339. package/src/view/client-dist/app.css +1 -1
  340. package/src/view/client-dist/app.js +17 -17
  341. package/src/view/data.test.ts +10 -3
  342. package/src/view/data.ts +155 -49
  343. package/src/view/index.ts +56 -41
  344. package/src/view/server.ts +37 -15
  345. package/src/view/shared/types.ts +34 -5
  346. package/src/view/styles.css +227 -0
  347. package/src/view/view-report.test.ts +167 -62
  348. package/dist/report/built-ins/experiment-comparison.d.ts +0 -1
  349. package/dist/report/built-ins/experiment-comparison.js +0 -13
  350. package/dist/report/built-ins/index.d.ts +0 -1
  351. package/dist/report/built-ins/index.js +0 -2
  352. package/dist/report/react/GroupSummary.d.ts +0 -8
  353. package/dist/report/react/GroupSummary.js +0 -8
  354. package/dist/report/react/RunOverview.d.ts +0 -8
  355. package/dist/report/react/RunOverview.js +0 -12
  356. package/docs-site/zh/example/ai-agent-application.mdx +0 -152
  357. package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
  358. package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
  359. package/docs-site/zh/example/showcase.mdx +0 -39
  360. package/docs-site/zh/guides/publish-report.mdx +0 -91
  361. package/docs-site/zh/guides/sandbox-providers.mdx +0 -102
  362. package/src/report/built-in-user-parity.test.tsx +0 -640
  363. package/src/report/built-ins/experiment-comparison.tsx +0 -19
  364. package/src/report/built-ins/index.ts +0 -2
  365. package/src/report/react/GroupSummary.tsx +0 -66
  366. package/src/report/react/RunOverview.tsx +0 -109
  367. package/src/runner/reporters/console.ts +0 -70
  368. package/src/runner/reporters/live.test.ts +0 -56
  369. package/src/runner/reporters/live.ts +0 -247
  370. package/src/runner/reporters/quiet.test.ts +0 -66
  371. package/src/runner/reporters/quiet.ts +0 -49
  372. package/src/runner/reporters/table.ts +0 -277
  373. /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
  374. /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
  375. /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
  376. /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
  377. /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
  378. /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
@@ -36,7 +36,7 @@ pnpm exec niceeval init
36
36
  ```
37
37
 
38
38
  ### Adapter
39
- Adapter 是连接 Agent 与 [NiceEval](https://niceeval.com/) 的中间层。有三种不同程序的接入方式,从无侵入式到部分修改你的 Agent 代码,来解锁更有用的评估功能。 见[Tier](/zh/concepts/tier)。
39
+ Adapter 是连接 Agent 与 [NiceEval](https://niceeval.com/) 的中间层。有三种不同程序的接入方式,从无侵入式到部分修改你的 Agent 代码,来解锁更有用的评估功能。 见[Tier](/zh/explanation/tier)。
40
40
 
41
41
  ```ts
42
42
  // agents/my-bot.ts
@@ -101,23 +101,11 @@ pnpm exec niceeval view # 需要交互浏览时打开本地查看器
101
101
 
102
102
  到这里第一条 eval 已经跑通。
103
103
 
104
- 这个最小 adapter 是**单轮**的——第二轮不会记得第一轮说了什么,因为还没告诉应用怎么接上历史。多轮会话和工具调用事件(解锁 `t.calledTool()`)、HITL、tracing 一样,都是之后给 adapter 加的可选增量:顺着[接入你的 Agent](/zh/guides/connect-your-agent)逐层往下走即可,已写的 eval 不用改。
104
+ 这个最小 adapter 是**单轮**的——第二轮不会记得第一轮说了什么,因为还没告诉应用怎么接上历史。多轮会话和工具调用事件(解锁 `t.calledTool()`)、HITL、tracing 一样,都是之后给 adapter 加的可选增量:顺着[接入你的 Agent](/zh/how-to/connect-your-agent)逐层往下走即可,已写的 eval 不用改。
105
105
 
106
- ## 按场景走
106
+ ## 对照完整项目
107
107
 
108
- 已经知道自己要评什么的话,直接从对应场景开始:
109
-
110
- <CardGroup cols={3}>
111
- <Card title="如果你需要 eval 你的 Claude Code / Codex 插件" icon="plug" href="/zh/example/claude-code-codex-plugin">
112
- 适合插件、Hook、MCP server 和项目级 coding-agent 扩展。
113
- </Card>
114
- <Card title="如果你需要 eval 你的 Claude Code / Codex Skill" icon="wand-magic-sparkles" href="/zh/example/claude-code-codex-skill">
115
- 适合验证 Skill 是否被触发、是否按流程执行、是否真的提升任务成功率。
116
- </Card>
117
- <Card title="如果你需要 eval 你的 AI Agent 应用" icon="globe" href="/zh/example/ai-agent-application">
118
- 适合 AI SDK、LangGraph、Pi 或自研 agent 应用的完整示例。
119
- </Card>
120
- </CardGroup>
108
+ 第一条 Eval 跑通后,再到 [Examples](/zh/examples) 按被测对象选择完整项目。那里提供可运行源码;通用操作步骤仍以 How-to Guides 为准。
121
109
 
122
110
  ## 放进 CI
123
111
 
@@ -135,5 +123,5 @@ jobs:
135
123
  ```
136
124
 
137
125
  <Tip>
138
- 接下来读 [编写 eval](/zh/guides/authoring) 和 [评分指南](/zh/guides/scoring-guide),把示例替换成你的真实场景。
126
+ 接下来读 [编写 eval](/zh/how-to/authoring) 和 [评分指南](/zh/how-to/scoring-guide),把示例替换成你的真实场景。
139
127
  </Tip>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "niceeval",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Agent-native eval tool — eval agents, services, functions, and coding-agent fixtures",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -24,6 +24,10 @@
24
24
  "types": "./src/sandbox/index.ts",
25
25
  "import": "./src/sandbox/index.ts"
26
26
  },
27
+ "./sandbox/e2b-template": {
28
+ "types": "./src/sandbox/e2b-agent-template.ts",
29
+ "import": "./src/sandbox/e2b-agent-template.ts"
30
+ },
27
31
  "./adapter": {
28
32
  "types": "./src/agents/index.ts",
29
33
  "import": "./src/agents/index.ts"
@@ -56,6 +60,10 @@
56
60
  "types": "./dist/report/react/index.d.ts",
57
61
  "import": "./dist/report/react/index.js"
58
62
  },
63
+ "./report/built-in": {
64
+ "types": "./dist/report/built-in/index.d.ts",
65
+ "import": "./dist/report/built-in/index.js"
66
+ },
59
67
  "./report/react/styles.css": "./src/report/react/styles.css",
60
68
  "./report/react/enhance.js": "./src/report/react/enhance.js"
61
69
  },
@@ -165,7 +173,7 @@
165
173
  "build:report": "tsc -p tsconfig.report-build.json && node scripts/prune-report-dist.mjs",
166
174
  "site:dev": "cd site && node scripts/dev.mjs",
167
175
  "site:build": "cd site && next build",
168
- "docs:dev": "cd docs-site && npx --yes mint@latest dev",
176
+ "docs:dev": "node scripts/docs-dev.mjs",
169
177
  "docs:validate": "cd docs-site && npx --yes mint@latest validate",
170
178
  "docs:links": "cd docs-site && npx --yes mint@latest broken-links --check-anchors --check-redirects"
171
179
  }
@@ -1,3 +1,4 @@
1
+ // cases: docs/engineering/unit-tests/adapters/cases.md
1
2
  import { describe, expect, it } from "vitest";
2
3
 
3
4
  import { aiSdkOtel } from "./ai-sdk-otel.ts";
@@ -1,3 +1,4 @@
1
+ // cases: docs/engineering/unit-tests/adapters/cases.md
1
2
  import { describe, expect, it } from "vitest";
2
3
 
3
4
  import { aiSdkAgent, fromAiSdk } from "./ai-sdk.ts";
@@ -457,6 +458,8 @@ function fakeCtx(opts: { id?: string } = {}): AgentContext {
457
458
  flags: {},
458
459
  sandbox: undefined as unknown as AgentContext["sandbox"],
459
460
  session,
461
+ progress: () => {},
462
+ diagnostic: () => {},
460
463
  log: () => {},
461
464
  };
462
465
  }
@@ -13,6 +13,7 @@
13
13
  // (不在 steps 里),这里挖出来补成 `action.result` —— 拒绝(execution-denied)映射成
14
14
  // `status: "rejected"`,喂 `calledTool(..., { status: "rejected" })` 与 `noFailedActions()`。
15
15
 
16
+ import { completeCoverage } from "../scoring/coverage.ts";
16
17
  import { randomUUID } from "node:crypto";
17
18
 
18
19
  import { defineAgent } from "../define.ts";
@@ -466,6 +467,8 @@ export function aiSdkAgent<M = unknown>(options: AiSdkAgentOptions<M>): Agent {
466
467
 
467
468
  return defineAgent({
468
469
  name: options.name ?? "ai-sdk",
470
+ // 官方 SDK 完整 steps/output:全通道 complete(见 adapters/architecture/evidence.md)。
471
+ coverage: completeCoverage,
469
472
  // tracing 开了才让运行器为这个 agent 起 OTLP 接收器(ctx.telemetry 才会出现);
470
473
  // AgentTracing 的其余字段(env/configure/scope)这里都用不上,空对象即可。
471
474
  tracing: options.tracing ? {} : undefined,
@@ -0,0 +1,34 @@
1
+ // cases: docs/engineering/unit-tests/adapters/cases.md
2
+ import { describe, expect, it } from "vitest";
3
+ import { readFile } from "node:fs/promises";
4
+ import {
5
+ BUB_INSTALL_MARKER,
6
+ DEFAULT_BUB_OTEL_PLUGIN,
7
+ DEFAULT_BUB_OVERRIDE,
8
+ bubInstallHash,
9
+ bubInstallSpec,
10
+ } from "./bub-install-spec.ts";
11
+
12
+ describe("Bub install specification", () => {
13
+ it("uses immutable default Git commits", () => {
14
+ expect(DEFAULT_BUB_OVERRIDE).toMatch(/@[0-9a-f]{40}$/);
15
+ expect(DEFAULT_BUB_OTEL_PLUGIN).toMatch(/@[0-9a-f]{40}#subdirectory=/);
16
+ });
17
+
18
+ it("fingerprints plugins independent of caller ordering", () => {
19
+ const packages = ["alpha==1", "beta==2"];
20
+ expect(bubInstallHash(packages)).toBe(bubInstallHash([" beta==2 ", "alpha==1", "alpha==1"]));
21
+ expect(bubInstallSpec(packages)).toContain("--with alpha==1 --with beta==2");
22
+ expect(BUB_INSTALL_MARKER).toBe(".local/share/niceeval/bub-install-hash");
23
+ });
24
+
25
+ it("keeps the non-TypeScript Docker recipe in sync", async () => {
26
+ const [dockerfile, override] = await Promise.all([
27
+ readFile(new URL("../../sandbox/docker/Dockerfile", import.meta.url), "utf8"),
28
+ readFile(new URL("../../sandbox/docker/bub-override.txt", import.meta.url), "utf8"),
29
+ ]);
30
+ expect(override.trim()).toBe(DEFAULT_BUB_OVERRIDE);
31
+ expect(dockerfile).toContain(DEFAULT_BUB_OTEL_PLUGIN);
32
+ expect(dockerfile).toContain(bubInstallHash([]));
33
+ });
34
+ });
@@ -0,0 +1,32 @@
1
+ import { createHash } from "node:crypto";
2
+
3
+ export const DEFAULT_BUB_OVERRIDE =
4
+ "bub @ git+https://github.com/CorrectRoadH/bub.git@86fbd0febc1665353f5131173554e1f513e66b4c";
5
+
6
+ export const DEFAULT_BUB_OTEL_PLUGIN =
7
+ "git+https://github.com/bubbuild/bub-contrib.git@add4a6a133c5658aec8f167ef50804d9ee55d22e#subdirectory=packages/bub-tapestore-otel";
8
+
9
+ export const BUB_CHECKPOINT_SUBDIRS = [".local"] as const;
10
+ export const BUB_INSTALL_MARKER = ".local/share/niceeval/bub-install-hash";
11
+
12
+ export function normalizeBubPackages(packages: readonly string[]): string[] {
13
+ return [...new Set(packages.map((value) => value.trim()).filter(Boolean))].sort();
14
+ }
15
+
16
+ export function bubInstallSpec(
17
+ packages: readonly string[],
18
+ override = DEFAULT_BUB_OVERRIDE,
19
+ otelPlugin = DEFAULT_BUB_OTEL_PLUGIN,
20
+ ): string {
21
+ const normalized = normalizeBubPackages(packages);
22
+ const plugins = normalized.length ? ` --with ${normalized.join(" --with ")}` : "";
23
+ return `bub --override(${override}) --with ${otelPlugin}${plugins} --checkpoint(${BUB_CHECKPOINT_SUBDIRS.join(",")})`;
24
+ }
25
+
26
+ export function bubInstallHash(
27
+ packages: readonly string[],
28
+ override = DEFAULT_BUB_OVERRIDE,
29
+ otelPlugin = DEFAULT_BUB_OTEL_PLUGIN,
30
+ ): string {
31
+ return createHash("md5").update(bubInstallSpec(packages, override, otelPlugin)).digest("hex").slice(0, 12);
32
+ }
package/src/agents/bub.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { completeCoverage } from "../scoring/coverage.ts";
1
2
  import { defineSandboxAgent } from "../define.ts";
2
3
  import { requireEnv, getEnv } from "../util.ts";
3
4
  import { shared } from "./shared.ts";
@@ -16,6 +17,14 @@ import { readFile, writeFile, mkdir } from "node:fs/promises";
16
17
  import { homedir } from "node:os";
17
18
  import { join, dirname } from "node:path";
18
19
  import { t } from "../i18n/index.ts";
20
+ import {
21
+ BUB_CHECKPOINT_SUBDIRS,
22
+ BUB_INSTALL_MARKER,
23
+ DEFAULT_BUB_OTEL_PLUGIN,
24
+ DEFAULT_BUB_OVERRIDE,
25
+ bubInstallHash,
26
+ normalizeBubPackages,
27
+ } from "./bub-install-spec.ts";
19
28
 
20
29
  // ───────────────────────────────────────────────────────────────────────────
21
30
  // bub 的 agent adapter(沙箱型)。
@@ -64,50 +73,32 @@ const SKILL_DIR = ".agents/skills";
64
73
  // 可用 NICEEVAL_BUB_OVERRIDE / NICEEVAL_BUB_OTEL_PLUGIN 覆盖,不必改源码。
65
74
  const BUB_OVERRIDE =
66
75
  getEnv("NICEEVAL_BUB_OVERRIDE") ??
67
- "bub @ git+https://github.com/CorrectRoadH/bub.git@fix/tape-assistant-text-with-tool-calls";
76
+ DEFAULT_BUB_OVERRIDE;
68
77
  const BUB_OVERRIDE_FILE = "/tmp/bub-override.txt";
69
78
  // otel 插件跟上游 main 走(bub-contrib#50 起从 bub.tape 导入,要求 bub ≥ 0.3.10dev,
70
79
  // 与上面的 override 分支兼容)。插件不发 PyPI,git 依赖是唯一安装方式。
71
80
  const OTEL_PLUGIN =
72
81
  getEnv("NICEEVAL_BUB_OTEL_PLUGIN") ??
73
- "git+https://github.com/bubbuild/bub-contrib.git#subdirectory=packages/bub-tapestore-otel";
82
+ DEFAULT_BUB_OTEL_PLUGIN;
74
83
 
75
- // override 钉在 git ref 上时,镜像里烘焙的 bub 不可信:模板构建时间早于 ref 当前指向的
76
- // commit 的话,`command -v bub` 命中的就是修复前的旧构建 —— e2b 模板 fasteval-agents 上
77
- // 整轮 bub turn failed(send 后无 AI 回复)就是这么来的:override 分支修好了,但捷径
78
- // 让它从未被安装。所以 pinned 时绕开 PATH 捷径、恒走 uv 安装(有 checkpoint 缓存,
79
- // 不是每沙箱都全量装),且运行时钉死用 $HOME/.local/bin/bub —— 只改 ensureBub 不改这里
80
- // 的话,PATH 上的 /usr/local/bin/bub 仍会先于新装的被 command -v 找到,白装。
81
- const BUB_PINNED = BUB_OVERRIDE.includes("git+");
82
- // bub 二进制:pinned → 恒用 uv 装到 $HOME/.local/bin 的那个;非 pinned → 优先用镜像里
83
- // (预制模板)烘焙在 PATH 上的 bub(装到 /usr/local/bin,见 sandbox/docker/Dockerfile)。
84
- const BUB = BUB_PINNED ? "$HOME/.local/bin/bub" : "$(command -v bub || echo $HOME/.local/bin/bub)";
84
+ // NiceEval 的预制配方与运行时安装都写到 $HOME/.local;显式使用该路径,避免 PATH
85
+ // 另一个未知版本的 bub 抢先命中。
86
+ const BUB = "$HOME/.local/bin/bub";
85
87
 
86
88
  // checkpoint 只打 $HOME/.local:uv 装的 python 工具链、bub 的 tool venv 和 bin shim 全在
87
89
  // 这里,restore 后即可运行。~/.cache/uv 是 wheel/构建缓存,只在「下一次安装」有用,而
88
90
  // restore 场景 bub 已经装好、不会再装——打进去只是把单次 HTTP 传输撑到 100MB+,在 e2b
89
91
  // 文件 API 上超时/连接重置概率明显偏高。子目录列表参与 INSTALL_HASH:改它会换缓存文件
90
92
  // 名,不会继续复用老的大 checkpoint。
91
- const CHECKPOINT_SUBDIRS = [".local"];
93
+ const CHECKPOINT_SUBDIRS = BUB_CHECKPOINT_SUBDIRS;
92
94
 
93
95
  /** 规范化 python plugin:去空白、丢空串、去重 —— 安装命令与 checkpoint key 用同一份列表。 */
94
96
  function normalizePackages(plugins?: readonly PythonPluginSpec[]): string[] {
95
- const seen = new Set<string>();
96
- for (const p of plugins ?? []) {
97
- const pkg = p.package.trim();
98
- if (pkg) seen.add(pkg);
99
- }
100
- return [...seen].sort();
101
- }
102
-
103
- /** 安装环境的完整描述:bub 版本 + otel 插件 + python plugin 集合 + checkpoint 覆盖面。 */
104
- function installSpecOf(packages: readonly string[]): string {
105
- const plugins = packages.length ? ` --with ${packages.join(" --with ")}` : "";
106
- return `bub --override(${BUB_OVERRIDE}) --with ${OTEL_PLUGIN}${plugins} --checkpoint(${CHECKPOINT_SUBDIRS.join(",")})`;
97
+ return normalizeBubPackages((plugins ?? []).map((plugin) => plugin.package));
107
98
  }
108
99
 
109
100
  function installHashOf(packages: readonly string[]): string {
110
- return createHash("md5").update(installSpecOf(packages)).digest("hex").slice(0, 12);
101
+ return bubInstallHash(packages, BUB_OVERRIDE, OTEL_PLUGIN);
111
102
  }
112
103
 
113
104
  function diskCachePath(home: string, installHash: string): string {
@@ -127,15 +118,16 @@ async function ensureBub(
127
118
  log: AgentContext["log"],
128
119
  packages: readonly string[],
129
120
  ): Promise<void> {
130
- // 预制模板已把 bub 烘焙进镜像(PATH 上)→ 直接用,跳过 uv 安装 + checkpoint 全套。
131
- // pinned(git ref override)时不走此捷径:烘焙的 bub 无法验证是不是 ref 当前指向的
132
- // 构建,必须按 override 真装(见 BUB_PINNED 注释)。有 python plugin 时同理不能走捷径:
133
- // 镜像里烘焙的 bub 环境没有这些包。
134
- if (!BUB_PINNED && packages.length === 0 && (await sb.runShell("command -v bub >/dev/null 2>&1")).exitCode === 0) {
121
+ const installHash = installHashOf(packages);
122
+ const marker = `${home}/${BUB_INSTALL_MARKER}`;
123
+ // 只信任带完整安装规格指纹的预制环境。仅 command -v bub 无法证明版本、OTel 插件和
124
+ // 用户 pythonPlugins 一致;NiceEval 的 E2B Bub 配方和运行时安装都会写这个 marker。
125
+ if ((await sb.runShell(
126
+ `test -x ${BUB} && test "$(cat '${marker}' 2>/dev/null)" = '${installHash}'`,
127
+ )).exitCode === 0) {
135
128
  return;
136
129
  }
137
130
 
138
- const installHash = installHashOf(packages);
139
131
  const cacheKey = `${home}::${installHash}`;
140
132
  const withPlugins = packages.map((p) => `--with '${p}'`).join(" ");
141
133
  const checkpointPaths = CHECKPOINT_SUBDIRS.map((d) => `${home}/${d}`);
@@ -190,6 +182,11 @@ async function ensureBub(
190
182
  }));
191
183
  }
192
184
  }
185
+ const markerDir = marker.slice(0, marker.lastIndexOf("/"));
186
+ const mark = await sb.runShell(`mkdir -p '${markerDir}' && printf '%s' '${installHash}' > '${marker}'`);
187
+ if (mark.exitCode !== 0) {
188
+ throw new Error(`Failed to write Bub installation marker: ${mark.stderr || mark.stdout}`);
189
+ }
193
190
  // 到这里 bub 已装进本沙箱,checkpoint 只是给后续沙箱的缓存回填:capture/下载失败
194
191
  // (大 buffer 在 e2b 文件 API 上的瞬态错误)降级为警告,绝不反过来杀掉已就绪的 attempt。
195
192
  try {
@@ -224,6 +221,8 @@ export function bubAgent(config?: BubConfig): Agent {
224
221
 
225
222
  return defineSandboxAgent({
226
223
  name: "bub",
224
+ // 官方 adapter:transcript 经生命周期 fixture 验证,全通道 complete。
225
+ coverage: completeCoverage,
227
226
  spanMapper: mapBubSpans,
228
227
 
229
228
  tracing: {
@@ -1,19 +1,27 @@
1
+ // cases: docs/engineering/unit-tests/adapters/cases.md
1
2
  // Claude Code native plugin 安装(installPlugins)的单测:单 plugin 的命令构造、同名
2
- // marketplace 的去重、ref 钉定时改走 clone+checkout+本地路径连接、resolvedVersion 取不到
3
- // 时优雅省略、marketplace/plugin 安装失败的报错。沙箱是内存 fake,风格与
4
- // src/agents/skills.test.ts 一致(记命令的 FakeSandbox + 按命令前缀打脚本),不另起一套。
3
+ // marketplace 的去重、add 后的注册名回读校验、ref 钉定时改走 clone+checkout+本地路径连接、
4
+ // resolvedVersion 取不到时优雅省略、marketplace/plugin 安装失败的报错;外加 settingsFile
5
+ // (原生配置文件)的 setup 流。沙箱是内存 fake,风格与 src/agents/skills.test.ts 一致
6
+ // (记命令的 FakeSandbox + 按命令前缀打脚本),不另起一套。
5
7
  // 定稿见 docs/feature/adapters/architecture/coding-agent-extensions.md。
6
8
 
7
- import { describe, expect, it } from "vitest";
8
- import { installPlugins, type ClaudeCodePluginSpec } from "./claude-code.ts";
9
- import type { CommandResult, Sandbox, SandboxFile } from "../types.ts";
9
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
10
+ import { createHash } from "node:crypto";
11
+ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
12
+ import { tmpdir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { claudeCodeAgent, installPlugins, type ClaudeCodePluginSpec } from "./claude-code.ts";
15
+ import type { AgentContext, AgentSetupManifest, CommandResult, Sandbox, SandboxFile } from "../types.ts";
10
16
 
11
- /** 内存沙箱:runShell 记命令(可按命令包含的子串打脚本化输出) */
17
+ /** 内存沙箱:runShell 记命令(可按命令包含的子串打脚本化输出),uploadFile / writeFiles 记内容。 */
12
18
  class FakeSandbox implements Partial<Sandbox> {
13
19
  readonly workdir = "/workspace";
14
20
  readonly sandboxId = "fake";
15
21
  readonly otlpHost = null;
16
22
  readonly commands: string[] = [];
23
+ readonly uploads: { path: string; content: Buffer }[] = [];
24
+ readonly written: Record<string, string> = {};
17
25
  script: { match: string; result: (cmd: string) => Partial<CommandResult> }[] = [];
18
26
 
19
27
  async runShell(script: string): Promise<CommandResult> {
@@ -21,7 +29,12 @@ class FakeSandbox implements Partial<Sandbox> {
21
29
  const hit = this.script.find((s) => script.includes(s.match));
22
30
  return { stdout: "", stderr: "", exitCode: 0, ...hit?.result(script) };
23
31
  }
24
- async writeFiles(): Promise<void> {}
32
+ async writeFiles(files: Record<string, string>): Promise<void> {
33
+ Object.assign(this.written, files);
34
+ }
35
+ async uploadFile(path: string, content: Buffer): Promise<void> {
36
+ this.uploads.push({ path, content });
37
+ }
25
38
  async uploadFiles(_files: SandboxFile[]): Promise<void> {}
26
39
  async fileExists(): Promise<boolean> {
27
40
  return false;
@@ -33,7 +46,11 @@ class FakeSandbox implements Partial<Sandbox> {
33
46
 
34
47
  const sb = (s?: FakeSandbox["script"]): FakeSandbox => {
35
48
  const box = new FakeSandbox();
36
- if (s) box.script = s;
49
+ // add 后的注册名回读默认回到配置名(校验通过);mismatch 用例把自己的脚本放前面覆盖。
50
+ box.script = [
51
+ ...(s ?? []),
52
+ { match: "claude plugin marketplace list --json", result: () => ({ stdout: JSON.stringify([{ name: "acme" }]) }) },
53
+ ];
37
54
  return box;
38
55
  };
39
56
  const asSandbox = (box: FakeSandbox): Sandbox => box as unknown as Sandbox;
@@ -48,6 +65,7 @@ describe("claude-code installPlugins · 命令构造", () => {
48
65
 
49
66
  expect(box.commands).toEqual([
50
67
  "claude plugin marketplace add 'acme/claude-code-plugins'",
68
+ "claude plugin marketplace list --json",
51
69
  "claude plugin install 'safe-shell@acme'",
52
70
  "claude plugin list --json",
53
71
  ]);
@@ -150,3 +168,106 @@ describe("claude-code installPlugins · 失败语义", () => {
150
168
  ).rejects.toThrow(/safe-shell/);
151
169
  });
152
170
  });
171
+
172
+ describe("claude-code installPlugins · marketplace 名回读校验", () => {
173
+ const plugins: ClaudeCodePluginSpec[] = [
174
+ { marketplace: { name: "acme", source: "acme/claude-code-plugins" }, name: "safe-shell" },
175
+ ];
176
+
177
+ it("add 静默注册成别的名字(仓库 manifest 的真实 name)→ 立刻抛带两个名字的错误,不走到 plugin install", async () => {
178
+ const box = sb([
179
+ {
180
+ match: "claude plugin marketplace list --json",
181
+ result: () => ({ stdout: JSON.stringify([{ name: "duyet-claude-plugins" }]) }),
182
+ },
183
+ ]);
184
+ const err = await installPlugins(asSandbox(box), plugins).catch((e: Error) => e);
185
+ expect(err).toBeInstanceOf(Error);
186
+ expect((err as Error).message).toContain("acme");
187
+ expect((err as Error).message).toContain("duyet-claude-plugins");
188
+ expect(box.commands.some((c) => c.startsWith("claude plugin install"))).toBe(false);
189
+ });
190
+
191
+ it("回读命令失败 → 按回读失败抛错,不静默放行", async () => {
192
+ const box = sb([
193
+ { match: "claude plugin marketplace list --json", result: () => ({ exitCode: 1, stderr: "boom" }) },
194
+ ]);
195
+ await expect(installPlugins(asSandbox(box), plugins)).rejects.toThrow(/marketplace/);
196
+ });
197
+
198
+ it("回读输出解析不出(未知形状)→ 同样按回读失败抛错", async () => {
199
+ const box = sb([
200
+ { match: "claude plugin marketplace list --json", result: () => ({ stdout: "not json" }) },
201
+ ]);
202
+ await expect(installPlugins(asSandbox(box), plugins)).rejects.toThrow(/acme/);
203
+ });
204
+ });
205
+
206
+ describe("claudeCodeAgent settingsFile · setup", () => {
207
+ let root: string;
208
+ let cwdBefore: string;
209
+
210
+ beforeEach(async () => {
211
+ root = await mkdtemp(join(tmpdir(), "niceeval-claude-settings-"));
212
+ cwdBefore = process.cwd();
213
+ process.chdir(root); // settingsFile 相对项目根 = process.cwd()(docs 定稿口径)
214
+ });
215
+
216
+ afterEach(async () => {
217
+ process.chdir(cwdBefore);
218
+ await rm(root, { recursive: true, force: true });
219
+ });
220
+
221
+ const ctx = {} as AgentContext; // claude 的 setup 不读 ctx
222
+
223
+ it("原始字节原样上传并 mv 成用户级 ~/.claude/settings.json;manifest 记项目相对路径 + SHA-256,不落正文", async () => {
224
+ const body = '{\n "$schema": "https://json.schemastore.org/claude-code-settings.json",\n "permissions": { "deny": ["WebSearch", "WebFetch"] }\n}\n';
225
+ await mkdir(join(root, "configs"), { recursive: true });
226
+ await writeFile(join(root, "configs/no-web.json"), body);
227
+
228
+ const box = sb();
229
+ await claudeCodeAgent({ apiKey: "k", settingsFile: "./configs/no-web.json" }).setup!(asSandbox(box), ctx);
230
+
231
+ // 原始字节走 uploadFile(不走会补换行的 heredoc),再 mv 到用户层落点
232
+ expect(box.uploads).toHaveLength(1);
233
+ expect(box.uploads[0]!.content.toString("utf8")).toBe(body);
234
+ const mv = box.commands.find((c) => c.includes("~/.claude/settings.json"))!;
235
+ expect(mv).toContain(`mv ${box.uploads[0]!.path} ~/.claude/settings.json`);
236
+
237
+ const manifest = JSON.parse(box.written["__niceeval__/agent-setup.json"]!) as AgentSetupManifest;
238
+ expect(manifest.nativeConfigFile).toEqual({
239
+ agent: "claude-code",
240
+ path: "configs/no-web.json",
241
+ sha256: createHash("sha256").update(body).digest("hex"),
242
+ });
243
+ expect(box.written["__niceeval__/agent-setup.json"]).not.toContain("permissions");
244
+ });
245
+
246
+ it("保留键(model / env)出现在文件里 → setup 报错点名冲突键,什么都不上传", async () => {
247
+ await writeFile(join(root, "bad.json"), '{ "model": "opus", "permissions": {} }');
248
+ const box = sb();
249
+ await expect(
250
+ claudeCodeAgent({ apiKey: "k", settingsFile: "bad.json" }).setup!(asSandbox(box), ctx),
251
+ ).rejects.toThrow(/model/);
252
+ expect(box.uploads).toHaveLength(0);
253
+ expect(box.written["__niceeval__/agent-setup.json"]).toBeUndefined();
254
+ });
255
+
256
+ it("路径不存在 / 逃出项目根 → setup 报错(attempt errored 的通道)", async () => {
257
+ const box = sb();
258
+ await expect(
259
+ claudeCodeAgent({ apiKey: "k", settingsFile: "configs/nope.json" }).setup!(asSandbox(box), ctx),
260
+ ).rejects.toThrow(/configs\/nope\.json/);
261
+ await expect(
262
+ claudeCodeAgent({ apiKey: "k", settingsFile: "../escape.json" }).setup!(asSandbox(box), ctx),
263
+ ).rejects.toThrow(/相对路径|relative/);
264
+ });
265
+
266
+ it("没配 settingsFile 时不碰 settings.json,也不为它写 manifest", async () => {
267
+ const box = sb();
268
+ await claudeCodeAgent({ apiKey: "k" }).setup!(asSandbox(box), ctx);
269
+ expect(box.uploads).toHaveLength(0);
270
+ expect(box.commands.some((c) => c.includes("settings.json"))).toBe(false);
271
+ expect(box.written["__niceeval__/agent-setup.json"]).toBeUndefined();
272
+ });
273
+ });
@@ -1,10 +1,19 @@
1
+ import { completeCoverage } from "../scoring/coverage.ts";
1
2
  import { defineSandboxAgent } from "../define.ts";
2
3
  import { requireEnv, getEnv } from "../util.ts";
3
4
  import { shared } from "./shared.ts";
4
5
  import { cloneRepo, installSkills } from "./skills.ts";
5
6
  import { writeAgentSetupManifest } from "./manifest.ts";
7
+ import { verifyMarketplaceName } from "./marketplace.ts";
8
+ import {
9
+ assertJsonNativeConfig,
10
+ loadNativeConfigFile,
11
+ uploadNativeConfigFile,
12
+ type LoadedNativeConfig,
13
+ } from "./native-config.ts";
6
14
  import { mapClaudeCodeSpans } from "../o11y/otlp/mappers/claude-code.ts";
7
15
  import { t } from "../i18n/index.ts";
16
+ import { DEFAULT_CLAUDE_CODE_CLI_VERSION } from "./coding-cli-versions.ts";
8
17
  import type { Agent, AgentSetupManifest, McpServer, Sandbox, SkillSpec } from "../types.ts";
9
18
 
10
19
  // ───────────────────────────────────────────────────────────────────────────
@@ -18,6 +27,15 @@ import type { Agent, AgentSetupManifest, McpServer, Sandbox, SkillSpec } from ".
18
27
  /** Claude Code 的 skill 目录(project 级):CLI 原生扫描它,不需要额外的发现指引。 */
19
28
  const SKILL_DIR = ".claude/skills";
20
29
 
30
+ /** 沙箱里用户级 settings 的落点:`settingsFile` 的原始字节原样替换这份原本为空的用户层。 */
31
+ const SETTINGS_PATH = "~/.claude/settings.json";
32
+
33
+ /**
34
+ * `settingsFile` 的保留键:`model` 归 experiment(经 `--model` flag),`env` 归 Adapter
35
+ * (鉴权与 OTel 导出经进程环境变量注入)。清单定稿见 docs/feature/adapters/sdk/claude-code/README.md。
36
+ */
37
+ const RESERVED_SETTINGS_KEYS = ["model", "env"] as const;
38
+
21
39
  /**
22
40
  * Claude Code 的原生 Plugin —— **只属于 Claude Code**,不能传给 Codex(Codex 有自己的
23
41
  * {@link import("./codex.ts").CodexPluginSpec})。每一项同时声明 Marketplace 连接和其中的 Plugin 名:
@@ -61,6 +79,15 @@ export interface ClaudeCodeConfig {
61
79
  skills?: SkillSpec[];
62
80
  /** Claude Code 原生 Plugin(先连 Marketplace,再从中装指定 Plugin)。 */
63
81
  plugins?: ClaudeCodePluginSpec[];
82
+ /**
83
+ * 一份完整的 Claude Code `settings.json`(官方格式)在本地项目里的路径 —— 相对运行
84
+ * niceeval 的项目根(含 `niceeval.config.ts` 的目录)解析,不是 Sandbox 内路径;只接受
85
+ * 项目根内的相对路径,包含 `..` 的路径、绝对路径、`~` 路径和解析后逃出项目根的符号链接
86
+ * 都在 setup 阶段报错。原始字节原样上传为沙箱里原本为空的用户级 `~/.claude/settings.json`
87
+ * (不继承宿主机配置、不拼接、不重新序列化);保留键 `model` 与 `env` 出现在文件里
88
+ * setup 报错。manifest 只记项目相对路径与字节 SHA-256,不落正文。
89
+ */
90
+ settingsFile?: string;
64
91
  }
65
92
 
66
93
  export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
@@ -69,6 +96,8 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
69
96
 
70
97
  return defineSandboxAgent({
71
98
  name: "claude-code",
99
+ // 官方 adapter:transcript 经生命周期 fixture 验证,全通道 complete。
100
+ coverage: completeCoverage,
72
101
  spanMapper: mapClaudeCodeSpans,
73
102
 
74
103
  // claude CLI 原生 OTLP trace spans(beta):interaction / llm_request / tool 层级,
@@ -88,7 +117,27 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
88
117
 
89
118
  async setup(sb) {
90
119
  // 预制模板已把 claude 烘焙进镜像(PATH 上)就跳过安装;否则 npm 全局装。
91
- await sb.runShell("command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code");
120
+ await sb.runShell(
121
+ `command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code@${DEFAULT_CLAUDE_CODE_CLI_VERSION}`,
122
+ );
123
+
124
+ // 原生配置文件最先落(安装顺序契约的第 1 步):本地读原始字节 → 验 JSON 语法与保留键
125
+ // → 原样替换沙箱里原本为空的用户级 settings.json。字节 SHA-256 进 manifest 与安装
126
+ // checkpoint key(见 native-config.ts 的 nativeConfigCheckpointItem)。
127
+ let settings: LoadedNativeConfig | undefined;
128
+ if (config?.settingsFile !== undefined) {
129
+ settings = await loadNativeConfigFile({
130
+ agent: "claude-code",
131
+ field: "settingsFile",
132
+ path: config.settingsFile,
133
+ });
134
+ assertJsonNativeConfig(settings, {
135
+ agent: "claude-code",
136
+ field: "settingsFile",
137
+ reservedKeys: RESERVED_SETTINGS_KEYS,
138
+ });
139
+ await uploadNativeConfigFile(sb, settings, SETTINGS_PATH);
140
+ }
92
141
 
93
142
  if (config?.mcpServers?.length) {
94
143
  const servers: Record<string, object> = {};
@@ -119,8 +168,17 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
119
168
  ...(s.args?.length ? { args: [...s.args] } : {}),
120
169
  }));
121
170
  }
171
+ if (settings) {
172
+ // 只记来源路径与字节哈希,不落正文(任意官方配置都可能带敏感字符串)。
173
+ manifest.nativeConfigFile = { agent: "claude-code", path: settings.path, sha256: settings.sha256 };
174
+ }
122
175
  // 什么都没装就不写 manifest:空 artifact 不落文件(同 results 的落盘规则)。
123
- if (manifest.skills.length || manifest.nativePlugins?.length || manifest.mcpServers?.length) {
176
+ if (
177
+ manifest.skills.length ||
178
+ manifest.nativePlugins?.length ||
179
+ manifest.mcpServers?.length ||
180
+ manifest.nativeConfigFile
181
+ ) {
124
182
  await writeAgentSetupManifest(sb, manifest);
125
183
  }
126
184
  },
@@ -134,7 +192,12 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
134
192
  if (ctx.session.id) args.push("--resume", ctx.session.id);
135
193
  args.push(input.text);
136
194
 
137
- const env: Record<string, string> = { ANTHROPIC_API_KEY: getApiKey(), ...ctx.telemetry?.env };
195
+ const env: Record<string, string> = {
196
+ ANTHROPIC_API_KEY: getApiKey(),
197
+ // Eval runs must not silently change CLI version after the sandbox artifact was built.
198
+ DISABLE_AUTOUPDATER: "1",
199
+ ...ctx.telemetry?.env,
200
+ };
138
201
  const baseUrl = getBaseUrl();
139
202
  if (baseUrl) env["ANTHROPIC_BASE_URL"] = baseUrl;
140
203
 
@@ -153,7 +216,8 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
153
216
  }
154
217
 
155
218
  /**
156
- * 先按 `marketplace.name` 建立 Marketplace 连接(同名只连一次),再从该连接装指定 Plugin。
219
+ * 先按 `marketplace.name` 建立 Marketplace 连接(同名只连一次,add 后回读注册列表校验
220
+ * 名字真的注册上了),再从该连接装指定 Plugin。
157
221
  * `claude plugin marketplace add` 没有钉 ref 的入口,所以要钉 ref 时先自己按 ref clone 下来,
158
222
  * 再以本地路径连接(CLI 支持 path 形态的 marketplace 源)—— 「来源必须可复现」不因 CLI 少个
159
223
  * flag 就打折。
@@ -183,6 +247,14 @@ export async function installPlugins(
183
247
  }),
184
248
  );
185
249
  }
250
+ // add 静默按目标仓库 manifest 的 name 注册,错名会拖到 plugin install 才炸;
251
+ // 回读注册列表立刻校验(契约与真机复现见 marketplace.ts 顶部说明)。
252
+ await verifyMarketplaceName(sb, {
253
+ agent: "claude-code",
254
+ listCommand: "claude plugin marketplace list --json",
255
+ marketplace,
256
+ knownNames: connected,
257
+ });
186
258
  connected.add(marketplace.name);
187
259
  }
188
260