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
@@ -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