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.
- package/INDEX.md +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +69 -7
- package/dist/context/types.d.ts +32 -12
- package/dist/i18n/en.d.ts +54 -0
- package/dist/i18n/zh-CN.d.ts +57 -3
- package/dist/o11y/types.d.ts +16 -2
- package/dist/report/aggregate.d.ts +32 -24
- package/dist/report/aggregate.js +158 -50
- package/dist/report/built-in/index.d.ts +2 -0
- package/dist/report/built-in/index.js +8 -0
- package/dist/report/components.d.ts +93 -160
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +87 -81
- package/dist/report/compute.js +597 -417
- package/dist/report/flag.d.ts +32 -6
- package/dist/report/flag.js +92 -4
- package/dist/report/format.d.ts +19 -11
- package/dist/report/format.js +30 -13
- package/dist/report/index.d.ts +16 -16
- package/dist/report/index.js +20 -21
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +57 -33
- package/dist/report/locale.js +122 -56
- package/dist/report/metrics.d.ts +23 -4
- package/dist/report/metrics.js +110 -25
- package/dist/report/primitives.d.ts +48 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +9 -7
- package/dist/report/react/AttemptList.js +17 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +4 -4
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +10 -0
- package/dist/report/react/ExperimentComparison.js +12 -0
- package/dist/report/react/ExperimentList.d.ts +4 -3
- package/dist/report/react/ExperimentList.js +17 -18
- package/dist/report/react/MetricBars.js +5 -4
- package/dist/report/react/MetricLine.js +12 -5
- package/dist/report/react/MetricMatrix.js +1 -1
- package/dist/report/react/MetricScatter.js +59 -28
- package/dist/report/react/MetricTable.js +2 -12
- package/dist/report/react/ScopeSummary.d.ts +10 -0
- package/dist/report/react/ScopeSummary.js +17 -0
- package/dist/report/react/Scoreboard.js +6 -6
- package/dist/report/react/cell.js +2 -2
- package/dist/report/react/chart-math.d.ts +23 -6
- package/dist/report/react/chart-math.js +71 -19
- package/dist/report/react/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +110 -147
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -16
- package/dist/report/report.js +259 -28
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +253 -184
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +38 -7
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +247 -284
- package/dist/report/types.js +4 -3
- package/dist/report/web.d.ts +21 -5
- package/dist/report/web.js +42 -16
- package/dist/results/select.d.ts +38 -16
- package/dist/results/select.js +73 -25
- package/dist/results/types.d.ts +49 -14
- package/dist/runner/feedback/sink.d.ts +110 -0
- package/dist/runner/types.d.ts +513 -22
- package/dist/sandbox/docker.d.ts +23 -2
- package/dist/sandbox/e2b.d.ts +15 -1
- package/dist/sandbox/errors.d.ts +30 -3
- package/dist/sandbox/io-retry.d.ts +17 -0
- package/dist/sandbox/registry.d.ts +2 -0
- package/dist/sandbox/resolve.d.ts +18 -5
- package/dist/sandbox/retry.d.ts +11 -1
- package/dist/sandbox/types.d.ts +39 -5
- package/dist/sandbox/vercel.d.ts +7 -1
- package/dist/scoring/coverage.d.ts +30 -0
- package/dist/scoring/display.d.ts +21 -0
- package/dist/scoring/display.js +120 -0
- package/dist/scoring/types.d.ts +103 -20
- package/dist/shared/aggregate.d.ts +4 -2
- package/dist/shared/aggregate.js +8 -7
- package/dist/shared/types.d.ts +28 -0
- package/dist/tty-line.d.ts +0 -4
- package/dist/util.d.ts +23 -0
- package/docs-site/zh/README.md +44 -0
- package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
- package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
- package/docs-site/zh/examples/index.mdx +50 -0
- package/docs-site/zh/{concepts → explanation}/adapter.mdx +31 -13
- package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
- package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
- package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
- package/docs-site/zh/{concepts → explanation}/overview.mdx +11 -11
- package/docs-site/zh/{guides → explanation}/runner.mdx +18 -8
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +35 -33
- package/docs-site/zh/{guides → how-to}/authoring.mdx +35 -2
- package/docs-site/zh/{guides → how-to}/ci-integration.mdx +23 -12
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +47 -21
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +34 -39
- package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +25 -3
- package/docs-site/zh/{guides → how-to}/experiments.mdx +12 -5
- package/docs-site/zh/how-to/publish-report.mdx +105 -0
- package/docs-site/zh/{guides → how-to}/reporters.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +56 -7
- package/docs-site/zh/how-to/sandbox-providers.mdx +350 -0
- package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +4 -4
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +82 -39
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +6 -4
- package/docs-site/zh/{guides → how-to}/write-send.mdx +30 -14
- package/docs-site/zh/index.mdx +24 -26
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +32 -5
- package/docs-site/zh/reference/capabilities.mdx +8 -8
- package/docs-site/zh/reference/cli.mdx +40 -12
- package/docs-site/zh/reference/define-agent.mdx +58 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +42 -9
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/reference/expect.mdx +26 -1
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +32 -8
- package/docs-site/zh/{guides → reference}/report-components.mdx +45 -33
- package/docs-site/zh/{guides → reference}/results-data.mdx +21 -13
- package/docs-site/zh/troubleshooting/debug-sandbox.mdx +57 -0
- package/docs-site/zh/troubleshooting/debugging.mdx +212 -0
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +10 -2
- package/src/agents/ai-sdk-otel.test.ts +1 -0
- package/src/agents/ai-sdk.test.ts +3 -0
- package/src/agents/ai-sdk.ts +3 -0
- package/src/agents/bub-install-spec.test.ts +34 -0
- package/src/agents/bub-install-spec.ts +32 -0
- package/src/agents/bub.ts +31 -32
- package/src/agents/claude-code.test.ts +130 -9
- package/src/agents/claude-code.ts +76 -4
- package/src/agents/codex.test.ts +189 -40
- package/src/agents/codex.ts +155 -14
- package/src/agents/coding-cli-versions.test.ts +15 -0
- package/src/agents/coding-cli-versions.ts +3 -0
- package/src/agents/index.ts +13 -2
- package/src/agents/langgraph.test.ts +204 -0
- package/src/agents/langgraph.ts +495 -0
- package/src/agents/marketplace.ts +85 -0
- package/src/agents/native-config.test.ts +179 -0
- package/src/agents/native-config.ts +267 -0
- package/src/agents/openai-compat.test.ts +1 -0
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/openclaw.test.ts +31 -0
- package/src/agents/openclaw.ts +171 -0
- package/src/agents/plugin-config.test.ts +1 -0
- package/src/agents/sdk-streams.test.ts +79 -0
- package/src/agents/sdk-streams.ts +55 -10
- package/src/agents/skills.test.ts +1 -0
- package/src/agents/streaming.test.ts +3 -9
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +71 -8
- package/src/agents/ui-message-stream.test.ts +3 -0
- package/src/cli.ts +446 -124
- package/src/context/context.test.ts +51 -12
- package/src/context/context.ts +162 -30
- package/src/context/session.test.ts +2 -1
- package/src/context/session.ts +115 -7
- package/src/context/types.ts +30 -12
- package/src/define.test.ts +13 -8
- package/src/define.ts +25 -4
- package/src/expect/index.ts +53 -23
- package/src/i18n/en.ts +81 -17
- package/src/i18n/zh-CN.ts +80 -17
- package/src/o11y/cost.test.ts +1 -0
- package/src/o11y/execution-tree.test.ts +1 -20
- package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
- package/src/o11y/otlp/parse.test.ts +1 -0
- package/src/o11y/otlp/turn-otel.test.ts +1 -0
- package/src/o11y/parsers/bub.test.ts +1 -0
- package/src/o11y/parsers/claude-code.test.ts +1 -34
- package/src/o11y/parsers/openclaw.test.ts +154 -0
- package/src/o11y/parsers/openclaw.ts +310 -0
- package/src/o11y/prices.json +746 -311
- package/src/o11y/tool-names.test.ts +1 -0
- package/src/o11y/types.ts +16 -2
- package/src/report/aggregate.ts +178 -61
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -279
- package/src/report/compute.ts +723 -491
- package/src/report/dual-render.test.tsx +741 -1024
- package/src/report/flag.ts +104 -12
- package/src/report/format.ts +32 -12
- package/src/report/index.ts +119 -46
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +136 -65
- package/src/report/metrics.ts +108 -25
- package/src/report/primitives.tsx +196 -45
- package/src/report/react/AttemptList.tsx +30 -43
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +73 -0
- package/src/report/react/ExperimentList.tsx +50 -32
- package/src/report/react/MetricBars.tsx +5 -4
- package/src/report/react/MetricLine.tsx +13 -8
- package/src/report/react/MetricMatrix.tsx +2 -2
- package/src/report/react/MetricScatter.tsx +86 -34
- package/src/report/react/MetricTable.tsx +4 -76
- package/src/report/react/ScopeSummary.tsx +86 -0
- package/src/report/react/Scoreboard.tsx +28 -10
- package/src/report/react/cell.tsx +2 -2
- package/src/report/react/chart-math.test.ts +85 -0
- package/src/report/react/chart-math.ts +101 -22
- package/src/report/react/enhance.js +89 -5
- package/src/report/react/fixtures.ts +114 -154
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +138 -158
- package/src/report/react/styles.css +243 -82
- package/src/report/report.test.ts +779 -841
- package/src/report/report.ts +423 -41
- package/src/report/text/faces.ts +290 -193
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +44 -7
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +261 -271
- package/src/report/web.ts +63 -20
- package/src/results/annotated-source.test.ts +62 -9
- package/src/results/annotated-source.ts +64 -6
- package/src/results/attempt-evidence.test.ts +13 -11
- package/src/results/attempt-evidence.ts +20 -13
- package/src/results/attempt-source.ts +6 -3
- package/src/results/copy.ts +150 -60
- package/src/results/host-equivalence.test.ts +34 -20
- package/src/results/index.ts +12 -4
- package/src/results/locator.test.ts +1 -22
- package/src/results/open.ts +15 -5
- package/src/results/publish.ts +149 -0
- package/src/results/results.test.ts +89 -54
- package/src/results/select.ts +104 -34
- package/src/results/truncate.ts +90 -0
- package/src/results/types.ts +43 -14
- package/src/results/writer.ts +31 -13
- package/src/runner/attempt.test.ts +138 -7
- package/src/runner/attempt.ts +603 -104
- package/src/runner/discover.test.ts +47 -0
- package/src/runner/discover.ts +36 -2
- package/src/runner/eval-source.test.ts +1 -27
- package/src/runner/feedback/agent.test.ts +504 -0
- package/src/runner/feedback/agent.ts +409 -0
- package/src/runner/feedback/ci.test.ts +562 -0
- package/src/runner/feedback/ci.ts +401 -0
- package/src/runner/feedback/coordinator.test.ts +317 -0
- package/src/runner/feedback/coordinator.ts +397 -0
- package/src/runner/feedback/failure.ts +40 -0
- package/src/runner/feedback/human.test.ts +616 -0
- package/src/runner/feedback/human.ts +535 -0
- package/src/runner/feedback/index.ts +66 -0
- package/src/runner/feedback/io.ts +78 -0
- package/src/runner/feedback/profile.test.ts +50 -0
- package/src/runner/feedback/profile.ts +58 -0
- package/src/runner/feedback/reducer.test.ts +395 -0
- package/src/runner/feedback/reducer.ts +260 -0
- package/src/runner/feedback/renderer.ts +82 -0
- package/src/runner/feedback/sink.ts +203 -0
- package/src/runner/feedback/testing.ts +106 -0
- package/src/runner/ledger.test.ts +230 -0
- package/src/runner/ledger.ts +329 -0
- package/src/runner/report.test.ts +128 -3
- package/src/runner/report.ts +33 -9
- package/src/runner/reporters/artifacts.ts +8 -2
- package/src/runner/reporters/braintrust.test.ts +8 -7
- package/src/runner/reporters/braintrust.ts +9 -2
- package/src/runner/reporters/index.ts +2 -2
- package/src/runner/reporters/json.test.ts +162 -0
- package/src/runner/reporters/json.ts +35 -8
- package/src/runner/reporters/shared.ts +1 -5
- package/src/runner/run.test.ts +760 -3
- package/src/runner/run.ts +243 -37
- package/src/runner/sandbox-prep.ts +3 -42
- package/src/runner/timing.ts +158 -0
- package/src/runner/types.ts +518 -22
- package/src/sandbox/checkpoint.test.ts +55 -0
- package/src/sandbox/checkpoint.ts +29 -8
- package/src/sandbox/cli-commands.ts +407 -0
- package/src/sandbox/docker.ts +115 -16
- package/src/sandbox/e2b-agent-template.test.ts +56 -0
- package/src/sandbox/e2b-agent-template.ts +94 -0
- package/src/sandbox/e2b.ts +74 -9
- package/src/sandbox/errors.ts +111 -4
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/io-retry.test.ts +58 -0
- package/src/sandbox/io-retry.ts +45 -0
- package/src/sandbox/keep-registry.test.ts +86 -0
- package/src/sandbox/keep-registry.ts +142 -0
- package/src/sandbox/keep.ts +178 -0
- package/src/sandbox/paths.test.ts +1 -0
- package/src/sandbox/paths.ts +19 -8
- package/src/sandbox/registry.ts +20 -3
- package/src/sandbox/resolve.ts +76 -11
- package/src/sandbox/retry.test.ts +70 -0
- package/src/sandbox/retry.ts +46 -4
- package/src/sandbox/types.ts +44 -6
- package/src/sandbox/vercel.ts +43 -20
- package/src/scoring/collector.ts +60 -17
- package/src/scoring/coverage.ts +95 -0
- package/src/scoring/diff.ts +81 -0
- package/src/scoring/display.test.ts +121 -0
- package/src/scoring/display.ts +133 -0
- package/src/scoring/evidence.test.ts +189 -0
- package/src/scoring/judge.test.ts +142 -0
- package/src/scoring/judge.ts +15 -18
- package/src/scoring/scoped.ts +217 -50
- package/src/scoring/types.ts +117 -20
- package/src/scoring/verdict.ts +16 -4
- package/src/shared/aggregate.ts +8 -6
- package/src/shared/types.ts +31 -0
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +127 -56
- package/src/show/render.ts +662 -131
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +320 -54
- package/src/tty-line.ts +8 -26
- package/src/util.test.ts +1 -0
- package/src/util.ts +41 -0
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +423 -11
- package/src/view/app/components/CodeView.tsx +41 -14
- package/src/view/app/components/CopyControls.tsx +2 -2
- package/src/view/app/i18n.ts +37 -17
- package/src/view/app/lib/attempt-route.test.ts +1 -0
- package/src/view/app/lib/verdict.ts +7 -9
- package/src/view/app/main.tsx +13 -8
- package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
- package/src/view/app/types.ts +4 -1
- package/src/view/artifact-serving.test.ts +2 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +17 -17
- package/src/view/data.test.ts +10 -3
- package/src/view/data.ts +155 -49
- package/src/view/index.ts +56 -41
- package/src/view/server.ts +37 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +227 -0
- package/src/view/view-report.test.ts +167 -62
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -1
- package/dist/report/built-ins/experiment-comparison.js +0 -13
- package/dist/report/built-ins/index.d.ts +0 -1
- package/dist/report/built-ins/index.js +0 -2
- package/dist/report/react/GroupSummary.d.ts +0 -8
- package/dist/report/react/GroupSummary.js +0 -8
- package/dist/report/react/RunOverview.d.ts +0 -8
- package/dist/report/react/RunOverview.js +0 -12
- package/docs-site/zh/example/ai-agent-application.mdx +0 -152
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
- package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
- package/docs-site/zh/example/showcase.mdx +0 -39
- package/docs-site/zh/guides/publish-report.mdx +0 -91
- package/docs-site/zh/guides/sandbox-providers.mdx +0 -102
- package/src/report/built-in-user-parity.test.tsx +0 -640
- package/src/report/built-ins/experiment-comparison.tsx +0 -19
- package/src/report/built-ins/index.ts +0 -2
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- package/src/runner/reporters/console.ts +0 -70
- package/src/runner/reporters/live.test.ts +0 -56
- package/src/runner/reporters/live.ts +0 -247
- package/src/runner/reporters/quiet.test.ts +0 -66
- package/src/runner/reporters/quiet.ts +0 -49
- package/src/runner/reporters/table.ts +0 -277
- /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
- /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/
|
|
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/
|
|
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/
|
|
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.
|
|
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": "
|
|
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 { 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
|
}
|
package/src/agents/ai-sdk.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
82
|
+
DEFAULT_BUB_OTEL_PLUGIN;
|
|
74
83
|
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
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 =
|
|
93
|
+
const CHECKPOINT_SUBDIRS = BUB_CHECKPOINT_SUBDIRS;
|
|
92
94
|
|
|
93
95
|
/** 规范化 python plugin:去空白、丢空串、去重 —— 安装命令与 checkpoint key 用同一份列表。 */
|
|
94
96
|
function normalizePackages(plugins?: readonly PythonPluginSpec[]): string[] {
|
|
95
|
-
|
|
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
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
if (
|
|
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+本地路径连接、
|
|
3
|
-
//
|
|
4
|
-
// src/agents/skills.test.ts 一致
|
|
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 {
|
|
9
|
-
import
|
|
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
|
-
|
|
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(
|
|
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 (
|
|
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> = {
|
|
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 连接(
|
|
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
|
|