niceeval 0.6.1 → 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.
- package/dist/agents/types.d.ts +67 -5
- package/dist/context/types.d.ts +32 -12
- package/dist/i18n/en.d.ts +54 -0
- package/dist/i18n/zh-CN.d.ts +55 -1
- package/dist/o11y/types.d.ts +16 -2
- package/dist/report/aggregate.d.ts +5 -3
- package/dist/report/aggregate.js +32 -5
- package/dist/report/built-ins/experiment-comparison.d.ts +39 -1
- package/dist/report/built-ins/experiment-comparison.js +116 -10
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +1 -1
- package/dist/report/components.d.ts +8 -2
- package/dist/report/components.js +3 -3
- package/dist/report/compute.d.ts +11 -18
- package/dist/report/compute.js +54 -34
- package/dist/report/flag.d.ts +16 -1
- package/dist/report/flag.js +19 -1
- package/dist/report/format.d.ts +16 -8
- package/dist/report/format.js +27 -12
- package/dist/report/index.d.ts +4 -3
- package/dist/report/index.js +5 -4
- package/dist/report/locale.d.ts +11 -2
- package/dist/report/locale.js +23 -5
- package/dist/report/metrics.d.ts +13 -1
- package/dist/report/metrics.js +65 -14
- package/dist/report/primitives.d.ts +6 -0
- package/dist/report/react/AttemptList.d.ts +2 -2
- package/dist/report/react/AttemptList.js +5 -6
- package/dist/report/react/EvalList.d.ts +1 -1
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +8 -0
- package/dist/report/react/ExperimentComparison.js +11 -0
- package/dist/report/react/ExperimentList.d.ts +2 -1
- package/dist/report/react/ExperimentList.js +8 -10
- package/dist/report/react/MetricScatter.js +5 -11
- 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 +3 -3
- package/dist/report/react/fixtures.js +21 -14
- package/dist/report/report.d.ts +5 -1
- package/dist/report/report.js +6 -2
- package/dist/report/text/faces.d.ts +1 -1
- package/dist/report/text/faces.js +42 -41
- package/dist/report/text/table.js +36 -5
- package/dist/report/types.d.ts +39 -21
- package/dist/results/types.d.ts +11 -0
- 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 +1 -0
- package/dist/shared/aggregate.js +3 -3
- 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/concepts/adapter.mdx +22 -4
- package/docs-site/zh/concepts/experiment.mdx +1 -1
- package/docs-site/zh/concepts/overview.mdx +6 -6
- package/docs-site/zh/guides/agent-feedback-loop.mdx +28 -26
- package/docs-site/zh/guides/authoring.mdx +33 -0
- package/docs-site/zh/guides/ci-integration.mdx +23 -12
- package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
- package/docs-site/zh/guides/custom-reports.mdx +29 -34
- package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
- package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
- package/docs-site/zh/guides/debugging.mdx +210 -0
- package/docs-site/zh/guides/experiments.mdx +10 -3
- package/docs-site/zh/guides/official-adapters.mdx +26 -2
- package/docs-site/zh/guides/publish-report.mdx +30 -16
- package/docs-site/zh/guides/report-components.mdx +42 -30
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +17 -9
- package/docs-site/zh/guides/runner.mdx +17 -7
- package/docs-site/zh/guides/sandbox-agent.mdx +56 -7
- package/docs-site/zh/guides/sandbox-providers.mdx +257 -9
- package/docs-site/zh/guides/scoring-guide.mdx +4 -4
- package/docs-site/zh/guides/viewing-results.mdx +79 -36
- package/docs-site/zh/guides/write-experiment.mdx +5 -3
- package/docs-site/zh/guides/write-send.mdx +17 -1
- package/docs-site/zh/index.mdx +1 -1
- package/docs-site/zh/reference/builtin-agents.mdx +27 -0
- package/docs-site/zh/reference/capabilities.mdx +2 -2
- package/docs-site/zh/reference/cli.mdx +33 -7
- package/docs-site/zh/reference/define-agent.mdx +57 -4
- 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/expect.mdx +26 -1
- package/package.json +5 -1
- 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 +11 -0
- 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/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/types.ts +68 -5
- package/src/agents/ui-message-stream.test.ts +3 -0
- package/src/cli.ts +411 -108
- package/src/context/context.test.ts +51 -12
- package/src/context/context.ts +161 -29
- package/src/context/session.test.ts +1 -0
- package/src/context/session.ts +114 -6
- 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 +64 -2
- package/src/i18n/zh-CN.ts +65 -3
- 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 +34 -5
- package/src/report/built-in-user-parity.test.tsx +110 -153
- package/src/report/built-ins/experiment-comparison.tsx +173 -13
- package/src/report/built-ins/index.ts +6 -1
- package/src/report/components.tsx +9 -3
- package/src/report/compute.ts +70 -40
- package/src/report/dual-render.test.tsx +194 -67
- package/src/report/flag.ts +30 -2
- package/src/report/format.ts +35 -11
- package/src/report/index.ts +22 -4
- package/src/report/locale.ts +25 -5
- package/src/report/metrics.ts +67 -14
- package/src/report/primitives.tsx +6 -0
- package/src/report/react/AttemptList.tsx +6 -31
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +68 -0
- package/src/report/react/ExperimentList.tsx +15 -9
- package/src/report/react/MetricScatter.tsx +12 -14
- 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 +33 -1
- package/src/report/react/fixtures.ts +24 -17
- package/src/report/react/render.test.tsx +9 -64
- package/src/report/react/styles.css +73 -2
- package/src/report/report.test.ts +306 -98
- package/src/report/report.ts +6 -2
- package/src/report/text/faces.ts +47 -43
- package/src/report/text/table.ts +42 -5
- package/src/report/types.ts +41 -21
- package/src/results/annotated-source.test.ts +62 -9
- package/src/results/annotated-source.ts +64 -6
- package/src/results/attempt-evidence.test.ts +9 -7
- package/src/results/attempt-evidence.ts +15 -8
- package/src/results/attempt-source.ts +6 -3
- package/src/results/copy.ts +145 -55
- package/src/results/host-equivalence.test.ts +8 -6
- package/src/results/index.ts +2 -0
- package/src/results/locator.test.ts +1 -22
- package/src/results/open.ts +7 -1
- package/src/results/publish.ts +149 -0
- package/src/results/results.test.ts +85 -51
- package/src/results/truncate.ts +90 -0
- package/src/results/types.ts +7 -0
- 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 +242 -36
- 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 +3 -2
- package/src/shared/types.ts +31 -0
- package/src/show/compose.ts +2 -2
- package/src/show/index.ts +21 -1
- package/src/show/render.ts +619 -104
- package/src/show/show.test.ts +235 -19
- package/src/tty-line.ts +8 -26
- package/src/util.test.ts +1 -0
- package/src/util.ts +41 -0
- package/src/view/app/components/AttemptModal.tsx +153 -2
- package/src/view/app/components/CodeView.tsx +32 -11
- package/src/view/app/components/CopyControls.tsx +2 -2
- package/src/view/app/i18n.ts +6 -0
- package/src/view/app/lib/attempt-route.test.ts +1 -0
- package/src/view/app/lib/verdict.ts +7 -9
- 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 +1 -0
- package/src/view/data.ts +11 -1
- package/src/view/index.ts +11 -0
- package/src/view/server.ts +2 -0
- package/src/view/styles.css +3 -0
- package/src/view/view-report.test.ts +6 -5
- 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
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "defineEval:声明、配置并运行 NiceEval eval"
|
|
3
3
|
sidebarTitle: "defineEval"
|
|
4
|
-
description: "defineEval 参考:选项、test context t、Turn 返回值、sandbox helper
|
|
4
|
+
description: "defineEval 参考:选项、test context t、Turn 返回值、sandbox helper,以及数组和 keyed record 数据集导出。"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
`defineEval` 是编写 eval 的主要入口。每个 eval 文件调用一次,传入描述和 `test(t)`,并默认导出结果。
|
|
@@ -84,15 +84,28 @@ metadata?: Record<string, unknown>;
|
|
|
84
84
|
|
|
85
85
|
任意附加元数据,原样透传进 EvalResult,不参与调度或打分;供自定义 reporter 消费。
|
|
86
86
|
|
|
87
|
+
#### `diff`
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
diff?: { include?: string[]; ignore?: string[] };
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
调整 agent diff 的归因排除清单(仅沙箱型;见 docs/feature/eval/README.md):两个数组都是
|
|
94
|
+
gitignore 风格 glob(workdir 相对)。默认排除 .git/node_modules/构建产物/包管理器缓存;
|
|
95
|
+
`ignore` 在默认清单上追加排除;`include` 优先级最高,把匹配路径显式加回。
|
|
96
|
+
合成规则固定为「默认 ∪ ignore,再被 include 打洞」,清单在分类账锚点时冻结。
|
|
97
|
+
|
|
87
98
|
#### `setup`
|
|
88
99
|
|
|
89
100
|
```ts
|
|
90
|
-
setup?: (sandbox: Sandbox) => Promise<void | Cleanup> | void | Cleanup;
|
|
101
|
+
setup?: (sandbox: Sandbox, ctx: SandboxHookContext) => Promise<void | Cleanup> | void | Cleanup;
|
|
91
102
|
```
|
|
92
103
|
|
|
93
104
|
eval 级预置:拿到沙箱(已上传 workspace + git 基线 + 装好依赖前)。
|
|
94
105
|
默认命令以非 root 跑(agent 的自然环境);装系统依赖时给 `runCommand` 传 `{ root: true }`
|
|
95
106
|
(如 `runCommand("apt-get", ["install", …], { root: true })`),跨 provider 语义一致。
|
|
107
|
+
第二个参数是绑定到 `eval.setup` 的窄上下文(`ctx.progress` / `ctx.diagnostic`,
|
|
108
|
+
见 docs/feature/eval/README.md);可返回 cleanup 闭包,归因到 `eval.teardown`。
|
|
96
109
|
|
|
97
110
|
#### `test`
|
|
98
111
|
|
|
@@ -115,10 +128,10 @@ eval 主体:拿到 TestContext,驱动对话 / 沙箱操作并就地断言。
|
|
|
115
128
|
#### `send`
|
|
116
129
|
|
|
117
130
|
```ts
|
|
118
|
-
send(
|
|
131
|
+
send(input: SendInput): Promise<TurnHandle>;
|
|
119
132
|
```
|
|
120
133
|
|
|
121
|
-
|
|
134
|
+
向默认会话发一条消息(字符串或结构化消息),返回该轮的 TurnHandle。事件同时累加进默认会话的累计事件流,供下面的作用域断言使用。
|
|
122
135
|
|
|
123
136
|
#### `sendFile`
|
|
124
137
|
|
|
@@ -219,13 +232,31 @@ readonly flags: Readonly<Record<string, unknown>>;
|
|
|
219
232
|
|
|
220
233
|
本次 attempt 生效的实验 flags(experiment.flags 的只读视图;实验条件,非命令行开关)。
|
|
221
234
|
|
|
235
|
+
#### `progress`
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
progress(update: ProgressUpdate): void;
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
作用域反馈:报告 eval 自己执行的长步骤(上传 fixture、跑构建……)。短命状态,scope 固定
|
|
242
|
+
为 `eval.run`;只报告不断言(见 docs/feature/eval/library/context.md「向运行反馈长步骤」)。
|
|
243
|
+
|
|
244
|
+
#### `diagnostic`
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
diagnostic(input: DiagnosticInput): void;
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
作用域反馈:报告运行结束后仍需保留的问题(永久事件,落 attempt diagnostics)。
|
|
251
|
+
即使 level 为 "error" 也不自动改变 verdict——测试结论仍由断言决定。
|
|
252
|
+
|
|
222
253
|
#### `log`
|
|
223
254
|
|
|
224
255
|
```ts
|
|
225
256
|
log(msg: string): void;
|
|
226
257
|
```
|
|
227
258
|
|
|
228
|
-
|
|
259
|
+
`progress({ message: msg })` 的别名(调试日志),不出现在最终结果里。
|
|
229
260
|
|
|
230
261
|
#### `skip`
|
|
231
262
|
|
|
@@ -380,10 +411,10 @@ eventOrder(types: StreamEvent["type"][]): AssertionHandle;
|
|
|
380
411
|
#### `eventsSatisfy`
|
|
381
412
|
|
|
382
413
|
```ts
|
|
383
|
-
eventsSatisfy(predicate: (events: readonly StreamEvent[]) => boolean
|
|
414
|
+
eventsSatisfy(label: string, predicate: (events: readonly StreamEvent[]) => boolean): AssertionHandle;
|
|
384
415
|
```
|
|
385
416
|
|
|
386
|
-
|
|
417
|
+
用自定义谓词对默认会话累计的整段事件流断言;label 必填、进断言标题。
|
|
387
418
|
|
|
388
419
|
#### `sandbox`
|
|
389
420
|
|
|
@@ -633,10 +664,10 @@ eventOrder(types: StreamEvent["type"][]): AssertionHandle;
|
|
|
633
664
|
#### `eventsSatisfy`
|
|
634
665
|
|
|
635
666
|
```ts
|
|
636
|
-
eventsSatisfy(predicate: (events: readonly StreamEvent[]) => boolean
|
|
667
|
+
eventsSatisfy(label: string, predicate: (events: readonly StreamEvent[]) => boolean): AssertionHandle;
|
|
637
668
|
```
|
|
638
669
|
|
|
639
|
-
|
|
670
|
+
用自定义谓词对本轮整段事件流断言;label 必填、进断言标题(谓词不透明,解释责任在 label)。
|
|
640
671
|
|
|
641
672
|
#### `maxTokens`
|
|
642
673
|
|
|
@@ -678,3 +709,5 @@ export default rows.map((row) =>
|
|
|
678
709
|
```
|
|
679
710
|
|
|
680
711
|
数组导出会生成稳定 ID:`file/0000`、`file/0001` 等。
|
|
712
|
+
|
|
713
|
+
已有稳定业务 key 时也可以默认导出 `Record<string, EvalDef>`。例如 key `15193` 在 `swelancer.eval.ts` 中生成 `swelancer/15193`。key 必须是非空单一路径片段,不能是 `.` / `..`,不能含 `/`、`\\` 或控制字符;发现顺序按 key 字典序固定。
|
|
@@ -136,7 +136,7 @@ export function makeAssertion(spec: {
|
|
|
136
136
|
t.check(t.reply, includes("Paris"));
|
|
137
137
|
t.check(t.reply, includes(/order #\d+/i));
|
|
138
138
|
t.check(turn.data, equals({ intent: "refund" }));
|
|
139
|
-
t.check(
|
|
139
|
+
t.check(turn.data, matches(OrderSchema)); // Standard Schema / Zod 结构校验;正则匹配用 includes
|
|
140
140
|
t.check(t.reply, similarity("The answer explains the refund window").atLeast(0.8));
|
|
141
141
|
t.check(turn.data, satisfies((value) => Array.isArray(value)));
|
|
142
142
|
```
|
|
@@ -172,6 +172,22 @@ readonly severity: Severity;
|
|
|
172
172
|
readonly threshold?: number;
|
|
173
173
|
```
|
|
174
174
|
|
|
175
|
+
#### `isOptional`
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
readonly isOptional?: boolean;
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
`.optional()` 链过的标记:评不了只记 unavailable,不把 attempt 拖成 errored。
|
|
182
|
+
|
|
183
|
+
#### `expected`
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
readonly expected?: string;
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
期望条件的有界文本描述(如 `contains "Brooklyn"`),失败时进 AssertionResult.expected。
|
|
190
|
+
|
|
175
191
|
#### `score`
|
|
176
192
|
|
|
177
193
|
```ts
|
|
@@ -195,6 +211,15 @@ atLeast(threshold: number): ValueAssertion;
|
|
|
195
211
|
转成软阈值断言:未达 threshold 时该条记为 failed,但默认不拖累整条 eval 的 verdict;
|
|
196
212
|
`--strict` 运行下,软阈值失败也会把整条 eval 的 verdict 计为 failed。返回新实例,不改原对象。
|
|
197
213
|
|
|
214
|
+
#### `optional`
|
|
215
|
+
|
|
216
|
+
```ts
|
|
217
|
+
optional(): ValueAssertion;
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
允许这条断言证据缺席:评不了时只记录 `outcome: "unavailable"`,不影响判定。
|
|
221
|
+
与 severity 正交(severity 说影不影响质量判定,optional 说证据允不允许缺席)。返回新实例,不改原对象。
|
|
222
|
+
|
|
198
223
|
{/* GENERATED:END value-assertion */}
|
|
199
224
|
|
|
200
225
|
## 自定义 matcher
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "niceeval",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
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"
|
|
@@ -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
|
+
});
|