niceeval 0.8.1 → 0.9.1
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 +77 -45
- package/dist/agents/types.d.ts +28 -6
- package/dist/i18n/en.d.ts +2 -0
- package/dist/i18n/zh-CN.d.ts +3 -1
- package/dist/report/built-in/index.d.ts +3 -2
- package/dist/report/built-in/index.js +7 -8
- package/dist/report/built-in/standard.d.ts +1 -0
- package/dist/report/built-in/standard.js +30 -0
- package/dist/report/components.d.ts +69 -2
- package/dist/report/components.js +152 -3
- package/dist/report/compute.d.ts +28 -1
- package/dist/report/compute.js +123 -0
- package/dist/report/index.d.ts +4 -4
- package/dist/report/index.js +3 -2
- package/dist/report/locale.d.ts +39 -1
- package/dist/report/locale.js +69 -0
- package/dist/report/react/AttemptList.d.ts +3 -1
- package/dist/report/react/AttemptList.js +3 -3
- package/dist/report/react/CopyFixPrompt.d.ts +12 -0
- package/dist/report/react/CopyFixPrompt.js +12 -0
- package/dist/report/react/HeroCard.d.ts +13 -0
- package/dist/report/react/HeroCard.js +35 -0
- package/dist/report/react/PoweredBy.d.ts +5 -0
- package/dist/report/react/PoweredBy.js +7 -0
- package/dist/report/react/ScopeWarnings.d.ts +12 -0
- package/dist/report/react/ScopeWarnings.js +18 -0
- package/dist/report/react/TraceWaterfall.d.ts +14 -0
- package/dist/report/react/TraceWaterfall.js +22 -0
- package/dist/report/react/index.d.ts +6 -1
- package/dist/report/react/index.js +6 -0
- package/dist/report/report.d.ts +22 -6
- package/dist/report/report.js +68 -55
- package/dist/report/scope-warnings.d.ts +28 -0
- package/dist/report/scope-warnings.js +101 -0
- package/dist/report/text/faces.d.ts +19 -1
- package/dist/report/text/faces.js +61 -0
- package/dist/report/tree.d.ts +1 -1
- package/dist/report/tree.js +7 -2
- package/dist/report/types.d.ts +45 -0
- package/dist/report/web.d.ts +5 -4
- package/dist/report/web.js +7 -22
- package/dist/results/select.d.ts +17 -4
- package/dist/results/select.js +76 -11
- package/dist/results/types.d.ts +26 -0
- package/dist/runner/fingerprint.d.ts +3 -3
- package/dist/runner/sandbox-selection.d.ts +12 -0
- package/dist/runner/types.d.ts +18 -6
- package/dist/sandbox/types.d.ts +12 -0
- package/dist/shared/aggregate.d.ts +1 -1
- package/dist/shared/aggregate.js +1 -1
- package/docs-site/zh/explanation/evals.mdx +2 -1
- package/docs-site/zh/explanation/experiment.mdx +2 -0
- package/docs-site/zh/how-to/custom-reports.mdx +23 -7
- package/docs-site/zh/how-to/experiments.mdx +2 -2
- package/docs-site/zh/how-to/publish-report.mdx +11 -4
- package/docs-site/zh/how-to/viewing-results.mdx +17 -8
- package/docs-site/zh/how-to/write-experiment.mdx +40 -1
- package/docs-site/zh/reference/builtin-agents.mdx +40 -3
- package/docs-site/zh/reference/cli.mdx +3 -3
- package/docs-site/zh/reference/define-eval.mdx +8 -0
- package/docs-site/zh/reference/official-adapters.mdx +10 -5
- package/docs-site/zh/troubleshooting/debugging.mdx +3 -3
- package/package.json +2 -1
- package/src/agents/bub.ts +13 -1
- package/src/agents/claude-code.test.ts +43 -1
- package/src/agents/claude-code.ts +32 -14
- package/src/agents/codex.test.ts +168 -1
- package/src/agents/codex.ts +51 -15
- package/src/agents/mcp.ts +31 -0
- package/src/agents/post-setup.ts +33 -0
- package/src/agents/types.ts +28 -7
- package/src/cli.ts +15 -14
- package/src/define.ts +3 -0
- package/src/i18n/en.ts +8 -5
- package/src/i18n/zh-CN.ts +8 -4
- package/src/index.ts +1 -0
- package/src/report/built-in/index.tsx +8 -7
- package/src/report/built-in/standard.tsx +59 -0
- package/src/report/components.tsx +218 -2
- package/src/report/compute.ts +138 -1
- package/src/report/dual-render.test.tsx +141 -14
- package/src/report/index.ts +20 -0
- package/src/report/locale.ts +83 -1
- package/src/report/react/AttemptList.tsx +13 -1
- package/src/report/react/CopyFixPrompt.tsx +37 -0
- package/src/report/react/HeroCard.tsx +59 -0
- package/src/report/react/PoweredBy.tsx +20 -0
- package/src/report/react/ScopeWarnings.tsx +74 -0
- package/src/report/react/TraceWaterfall.tsx +78 -0
- package/src/report/react/enhance.js +14 -0
- package/src/report/react/index.tsx +11 -0
- package/src/report/react/styles.css +193 -7
- package/src/report/report.ts +99 -64
- package/src/report/scope-warnings.ts +155 -0
- package/src/report/site-components.test.tsx +526 -0
- package/src/report/text/faces.ts +66 -0
- package/src/report/tree.ts +10 -3
- package/src/report/types.ts +51 -0
- package/src/report/web.ts +7 -40
- package/src/results/host-equivalence.test.ts +6 -2
- package/src/results/open.ts +5 -4
- package/src/results/results.test.ts +78 -1
- package/src/results/select.ts +80 -12
- package/src/results/types.ts +27 -0
- package/src/runner/attempt.ts +10 -9
- package/src/runner/discover.test.ts +9 -1
- package/src/runner/discover.ts +3 -3
- package/src/runner/fingerprint.ts +9 -4
- package/src/runner/ledger.test.ts +30 -1
- package/src/runner/ledger.ts +26 -4
- package/src/runner/run.ts +5 -1
- package/src/runner/sandbox-selection.test.ts +131 -0
- package/src/runner/sandbox-selection.ts +110 -0
- package/src/runner/types.ts +19 -2
- package/src/sandbox/types.ts +6 -0
- package/src/shared/aggregate.ts +1 -1
- package/src/show/index.ts +19 -12
- package/src/show/render.ts +12 -12
- package/src/show/report-host.test.ts +34 -17
- package/src/show/report-host.ts +6 -5
- package/src/show/show.test.ts +141 -4
- package/src/view/app/App.test.tsx +79 -17
- package/src/view/app/App.tsx +25 -74
- package/src/view/app/components/CopyControls.tsx +4 -42
- package/src/view/app/i18n.ts +5 -227
- package/src/view/app/lib/rows.ts +3 -21
- package/src/view/app/shared.ts +1 -3
- package/src/view/app/types.ts +2 -2
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +20 -20
- package/src/view/data.ts +21 -10
- package/src/view/index.ts +2 -12
- package/src/view/server.ts +4 -4
- package/src/view/shared/types.ts +11 -6
- package/src/view/styles.css +9 -252
- package/src/view/view-report.test.ts +99 -33
- package/src/view/app/components/LazyArtifact.tsx +0 -51
- package/src/view/app/components/SkippedRunsBanner.tsx +0 -140
- package/src/view/app/pages/AttemptsPage.tsx +0 -80
- package/src/view/app/pages/TracesPage.tsx +0 -35
|
@@ -66,7 +66,7 @@ Sandbox 创建、setup 或 teardown 错误不依赖 trace。`result.json` 保存
|
|
|
66
66
|
|
|
67
67
|
同一个实验多次跑会留下多份结果,不带 `@<locator>` 的默认视图只回答一个问题——「现在整体怎样」:每个 experiment × eval 取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来。按前缀只重跑了部分 eval 时,其余 eval 的判定从更早的运行补齐,报告永远是全局最新,不会因为一次局部重跑变残缺。合成是有标注的:每份判定都带上它产生的时间,能看出报告是从哪几次运行拼出来的。合成结果可能混着不同版本的被测代码——所以收工判定以 `--force` 全量重跑为准,迭代途中的 `show` 负责快、收尾的全量跑负责真。
|
|
68
68
|
|
|
69
|
-
单个 eval 视图里,多 experiment、多 Attempt 时的断言明细块默认挑最新一次失败的 Attempt 展开;没有失败就挑最新一次。这只是一个默认展开的启发式,不是精确选择——需要精确看某一次 Attempt,复制那一行的 `@<locator>` 直接 `show` 它即可。`--
|
|
69
|
+
单个 eval 视图里,多 experiment、多 Attempt 时的断言明细块默认挑最新一次失败的 Attempt 展开;没有失败就挑最新一次。这只是一个默认展开的启发式,不是精确选择——需要精确看某一次 Attempt,复制那一行的 `@<locator>` 直接 `show` 它即可。`--exp compare` 按路径段前缀把 Selection 收窄到整个 `compare` 组,`--exp compare/bub` 只留一个 experiment;`--results <目录>` 换结果根,`--snapshot <snapshot.json>` 只看某一次落盘的快照,`--history` 看跨 run 趋势。`--report <文件>` 同时替换 `show` / `view` 的默认报告。位置前缀、`--results`、`--exp` 对自定义报告同样生效;`--history` 与 `--report` 互斥。
|
|
70
70
|
|
|
71
71
|
`niceeval view` 的每个视图都有 CLI 对应物:
|
|
72
72
|
|
|
@@ -83,7 +83,7 @@ Sandbox 创建、setup 或 teardown 错误不依赖 trace。`result.json` 保存
|
|
|
83
83
|
|
|
84
84
|
### 默认分组比较报告
|
|
85
85
|
|
|
86
|
-
不带 flag 的 `niceeval show` 如果命中多个可比组,只列组索引和可直接执行的 `niceeval show --
|
|
86
|
+
不带 flag 的 `niceeval show` 如果命中多个可比组,只列组索引和可直接执行的 `niceeval show --exp <group>` 命令;Selection 只剩一个组时才输出该组的成本 × 端到端成功率图和实验列表。组的边界来自 experiment id 的完整父目录:`compare/*` 与 `dev-e2b/*` 不共享坐标系、连线、排序或汇总数字;顶层 experiment 各自形成单例组。
|
|
87
87
|
|
|
88
88
|
组内实验列表先给固定列汇总,再按 experiment → Eval → Attempt 展开。locator 只保留 `@<id>`,失败原因优先显示期望值/实际值或命令退出码;完整断言和 evidence 留在 `niceeval show @<locator>`。某组只有一个 experiment 时散点仍显示单点,不出现“至少两个实验才能比较”的空态。快照未完成、过旧或覆盖不全的 warning 位于组索引前,同一条 warning 只显示一次。
|
|
89
89
|
|
|
@@ -323,21 +323,21 @@ compare/codex-gpt-5.4 · 5 runs · passed 2/5
|
|
|
323
323
|
|
|
324
324
|
两次 run 的精确对比(这次修复具体翻转了哪些 eval)不做成 flag:用 `DeltaTable` 积木写一份报告递给 `--report`,几行就是一份自定义对比报告,终端和网页两扇门都认——内置命令只管固定摆法,自定义口径见[自定义报告](/zh/how-to/custom-reports)。
|
|
325
325
|
|
|
326
|
-
## `niceeval view
|
|
326
|
+
## `niceeval view`:在网页看证据
|
|
327
327
|
|
|
328
328
|
```bash
|
|
329
329
|
npx niceeval view
|
|
330
330
|
```
|
|
331
331
|
|
|
332
|
-
这会打开本地结果查看器。它的首页报告和 `niceeval show` 选结果的规则一模一样:对每个 experiment 的每道 eval,取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来;收窄范围(eval ID 前缀、`--
|
|
332
|
+
这会打开本地结果查看器。它的首页报告和 `niceeval show` 选结果的规则一模一样:对每个 experiment 的每道 eval,取时间上最新的那份判定,同一个 experiment 跨多次运行拼出来;收窄范围(eval ID 前缀、`--exp`)时按同一条规则收窄。你可以浏览 eval、查看 agent 的对话与工具调用、读 diff、检查断言结果。数据不会上传到外部服务。
|
|
333
333
|
|
|
334
334
|
<Tip>
|
|
335
335
|
失败后立刻运行 `npx niceeval view`,可以直接打开刚刚那次运行的 artifacts。
|
|
336
336
|
</Tip>
|
|
337
337
|
|
|
338
|
-
`view` 的首页是一份报告:不传 `--report` 时完整加载当前结果并显示全部可比组索引,选中一组后只显示该组的成本 × 端到端成功率散点图与实验表。切组只改变页面状态,不重新读取或计算;不同组不会混进同一张图或榜单。浏览器禁用 JS 时,每组作为独立的 `<details>` 完整可读;启用 JS 后一次聚焦一组。传了 `--report` 就换成你自己的报告(与 `show --report`
|
|
338
|
+
`view` 的首页是一份报告:不传 `--report` 时完整加载当前结果并显示全部可比组索引,选中一组后只显示该组的成本 × 端到端成功率散点图与实验表。切组只改变页面状态,不重新读取或计算;不同组不会混进同一张图或榜单。浏览器禁用 JS 时,每组作为独立的 `<details>` 完整可读;启用 JS 后一次聚焦一组。传了 `--report` 就换成你自己的报告(与 `show --report` 吃同一个文件),页面与导航完全由报告文件决定,见[自定义报告](/zh/how-to/custom-reports)。Attempt 详情(弹窗里的 transcript、时间树、trace、diff)始终可用——它是查看器自身的能力,报告里的每个数字点进去都是对应证据;默认报告另有 Attempts 列表页和追踪瀑布页,自己的报告要同款页面就摆同名组件。
|
|
339
339
|
|
|
340
|
-
网页版多几样浏览操作:切换可比组、点表头就地排序、在当前组的过滤框里筛行、点开一个 experiment 行看它每道题的判定与原因、悬停散点看数值——这些只影响眼前的视图,不改判定口径,刷新即恢复。Attempt 弹窗里有与 `show --timing` 同源的统一时间树:Sandbox 启动、setup hook 及其 shell、agent 安装命令、每轮 send 与可关联的 OTel model/tool
|
|
340
|
+
网页版多几样浏览操作:切换可比组、点表头就地排序、在当前组的过滤框里筛行、点开一个 experiment 行看它每道题的判定与原因、悬停散点看数值——这些只影响眼前的视图,不改判定口径,刷新即恢复。Attempt 弹窗里有与 `show --timing` 同源的统一时间树:Sandbox 启动、setup hook 及其 shell、agent 安装命令、每轮 send 与可关联的 OTel model/tool、评分与收尾都能逐层展开。默认报告页里有 **Copy fix prompt** 按钮(`CopyFixPrompt` 组件,自己的报告可以直接摆同款),把全部失败打包成可直接粘给 coding agent 的修复 prompt(attempt 弹窗里有单条版)。报告文案有中英两份,随界面语言切换。
|
|
341
341
|
|
|
342
342
|
## 导出与静态托管
|
|
343
343
|
|
|
@@ -349,7 +349,16 @@ npx niceeval view
|
|
|
349
349
|
npx niceeval view --out site
|
|
350
350
|
```
|
|
351
351
|
|
|
352
|
-
NiceEval 写入 `<dir>/index.html`,并把查看器要读取的 artifact(`sources.json`、`events.json`、`trace.json
|
|
352
|
+
NiceEval 写入 `<dir>/index.html`,并把查看器要读取的 artifact(`sources.json`、`events.json`、`trace.json`,有 `diff.json` 也带上)复制到 `<dir>/artifact/` 下。把整个目录交给任何静态托管(Vercel、GitHub Pages 或 `python3 -m http.server`),代码视图、transcript 和 trace 瀑布图都和本地 `niceeval view` 一致;表格排序、过滤和图表悬停也照常可用——所需脚本已内联进页面,浏览器禁用 JS 时页面仍完整可读,只是少这些浏览操作。
|
|
353
|
+
|
|
354
|
+
只想发布一部分结果时,把本地查看用的收窄直接交给导出:
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
npx niceeval view --exp compare --out site # 只发布 compare 可比组
|
|
358
|
+
npx niceeval view weather --out site # 只发布 weather 开头的 eval
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
出站的就是收窄到的:页面和 `artifact/` 证据都只含收窄后的范围,被滤掉实验的 transcript、源码和 trace 不会跟着出站。不收窄就是整站导出完整结果。
|
|
353
362
|
|
|
354
363
|
`--out` 只接受目录。没有单文件导出:代码视图、transcript 和 trace 依赖 artifact 文件,单个 HTML 装不下完整证据。要把结果发给别人,托管整站发链接即可。
|
|
355
364
|
|
|
@@ -357,7 +366,7 @@ NiceEval 写入 `<dir>/index.html`,并把查看器要读取的 artifact(`sou
|
|
|
357
366
|
|
|
358
367
|
两点限制:
|
|
359
368
|
|
|
360
|
-
- `
|
|
369
|
+
- `o11y.json` 不会被复制——报告数字已经算进页面,查看器不读取它。
|
|
361
370
|
- 用 `file://` 直接打开 `index.html` 时浏览器不允许 fetch artifact,代码视图会提示源码不可用。本地预览用 http 服务打开。
|
|
362
371
|
|
|
363
372
|
最简单的流程是在跑过 eval 的机器上导出,把产物目录直接部署,不需要额外步骤。要让站点随 push 自动更新,先用 `copySnapshots` 生成经过单文件预算检查的发布结果根,把它提交进仓库,再让 CI 对这个目录运行 `view --results <目录> --out`——workflow 与平台接线见[通过 CI 发布报告](/zh/how-to/publish-report)。
|
|
@@ -104,7 +104,7 @@ npx niceeval exp prompt-variants/concise
|
|
|
104
104
|
| `timeoutMs` | 单个 attempt 的超时 |
|
|
105
105
|
| `budget` | 这一格配置的预算上限 |
|
|
106
106
|
| `maxConcurrency` | 这一格配置的并发上限 |
|
|
107
|
-
| `sandbox` | sandbox agent
|
|
107
|
+
| `sandbox` | sandbox agent 使用的固定 spec;spec 可带 `environments` 表按 eval 的环境 profile 换预制产物,也可以链 `.setup()` / `.teardown()` 挂环境钩子 |
|
|
108
108
|
|
|
109
109
|
Experiment 本身是纯配置数据,没有 `setup` / `teardown` 这类字段。要在跑 agent 前按实验准备环境(装二进制、预热、跨 attempt 载入和回存状态),挂在 `sandbox` 字段的 spec 上:
|
|
110
110
|
|
|
@@ -122,4 +122,43 @@ export default defineExperiment({
|
|
|
122
122
|
|
|
123
123
|
钩子的执行时机、多钩子顺序和失败语义见 [沙箱 provider · 环境钩子](/zh/how-to/sandbox-providers#环境钩子)。
|
|
124
124
|
|
|
125
|
+
## 让不同 eval 使用不同预制环境
|
|
126
|
+
|
|
127
|
+
一批真实任务可能需要不同版本的运行时和依赖。eval 用 `environment` 声明一个与 provider 无关的 profile ID;sandbox spec 的 `environments` 表再把它映射到具体模板或快照:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
// evals/astropy-2021.eval.ts
|
|
131
|
+
export default defineEval({
|
|
132
|
+
environment: "python-3.9-astropy-4.2",
|
|
133
|
+
async test(t) {
|
|
134
|
+
// 驱动任务并验证结果
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
// experiments/shared.ts —— 一个 provider 一张表,所有实验共用
|
|
141
|
+
import { e2bSandbox } from "niceeval/sandbox";
|
|
142
|
+
|
|
143
|
+
export const e2b = e2bSandbox({
|
|
144
|
+
template: "codex-default", // 未声明 environment 的 eval 用它
|
|
145
|
+
environments: {
|
|
146
|
+
"python-3.9-astropy-4.2": { template: "codex-python39" },
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
// experiments/e2b.ts —— 实验保持一行 diff,覆盖全部 eval
|
|
153
|
+
import { defineExperiment } from "niceeval";
|
|
154
|
+
import { e2b } from "./shared";
|
|
155
|
+
|
|
156
|
+
export default defineExperiment({
|
|
157
|
+
agent: codexAgent(),
|
|
158
|
+
sandbox: e2b,
|
|
159
|
+
});
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`environment` 是非空的稳定字符串,不是包版本约束。`environments` 表的值就是该 provider 预制产物字段的覆盖(Docker 的 `image`、E2B 的 `template`、Vercel 的 `snapshotId`)。NiceEval 在启动任何 Sandbox 前对所有选中的 eval 完成查表;某条 eval 声明的 profile 缺表项会在启动时一次性报出全部缺项。remote Agent 不创建 Sandbox,不参与查表。因为映射是随 spec 复用的数据,同一个实验能覆盖全部 eval——分数和对比表不会因为环境不同被拆成多个实验。
|
|
163
|
+
|
|
125
164
|
跨配置比较的设计建议见[实验矩阵](/zh/how-to/experiments)。adapter 如何消费 `ctx.model` 和 `ctx.flags` 见[Adapter](/zh/explanation/adapter)。
|
|
@@ -41,7 +41,7 @@ description: "NiceEval 内置的 claude-code、codex、bub 适配器分别做到
|
|
|
41
41
|
|
|
42
42
|
### codex
|
|
43
43
|
|
|
44
|
-
- 连接方式:沙箱里跑 `codex exec --json`(续接时是 `codex exec resume <id> --json`),stdout JSONL 当 transcript
|
|
44
|
+
- 连接方式:沙箱里跑 `codex exec --json`(续接时是 `codex exec resume <id> --json`),stdout JSONL 当 transcript。命令带 `--dangerously-bypass-approvals-and-sandbox` 与 `--dangerously-bypass-hook-trust`:沙箱里没人能回答 codex 的交互确认,插件或 `postSetup` 装好的 hook 因此不需要交互授信就能生效。
|
|
45
45
|
- 鉴权:`CODEX_API_KEY`(不是 `OPENAI_API_KEY`),可选 `CODEX_BASE_URL` 接 OpenAI 兼容代理;配置项见下方 `CodexConfig`。
|
|
46
46
|
- `tracing` 通过 `~/.codex/config.toml` 的 `[otel.trace_exporter.otlp-http]` 段配置,协议 `http/json`。
|
|
47
47
|
|
|
@@ -93,7 +93,8 @@ mcpServers?: McpServer[];
|
|
|
93
93
|
```
|
|
94
94
|
|
|
95
95
|
额外 MCP server(每个沙箱 setup 时写进用户级 ~/.claude.json)。
|
|
96
|
-
|
|
96
|
+
stdio 形态写 command(可带 args / env);Streamable HTTP 形态写 url(可带 headers,
|
|
97
|
+
逐字进请求头),落成 { "type": "http", "url": …, "headers": … } 条目。
|
|
97
98
|
|
|
98
99
|
#### `skills`
|
|
99
100
|
|
|
@@ -125,6 +126,18 @@ niceeval 的项目根(含 `niceeval.config.ts` 的目录)解析,不是 Sandbox
|
|
|
125
126
|
(不继承宿主机配置、不拼接、不重新序列化);保留键 `model` 与 `env` 出现在文件里
|
|
126
127
|
setup 报错。manifest 只记项目相对路径与字节 SHA-256,不落正文。
|
|
127
128
|
|
|
129
|
+
#### `postSetup`
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
postSetup?: SandboxHook[];
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
安装后按数组顺序运行的用户钩子(复用 SandboxHook 的窄上下文):在写 settings、挂 MCP、
|
|
136
|
+
装 Skills / Plugin、写 manifest 全部完成后执行,适合跑插件自带的 setup 脚本这类
|
|
137
|
+
「安装产物就位后才能跑」的过程动作。钩子返回的 cleanup 按 LIFO 与 teardown 一起收尾;
|
|
138
|
+
抛错按基础设施错误计(attempt errored)。
|
|
139
|
+
见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。
|
|
140
|
+
|
|
128
141
|
### `CodexConfig`
|
|
129
142
|
|
|
130
143
|
#### `apiKey`
|
|
@@ -150,7 +163,8 @@ mcpServers?: McpServer[];
|
|
|
150
163
|
```
|
|
151
164
|
|
|
152
165
|
额外 MCP server(每个沙箱 setup 时追加进 ~/.codex/config.toml)。
|
|
153
|
-
|
|
166
|
+
stdio 形态(command/args/env)写 [mcp_servers.<name>] 的 command 行;
|
|
167
|
+
Streamable HTTP 形态(url/headers)写 url 行,headers 进 [mcp_servers.<name>.http_headers] 子表。
|
|
154
168
|
|
|
155
169
|
#### `skills`
|
|
156
170
|
|
|
@@ -184,6 +198,18 @@ niceeval 的项目根(含 `niceeval.config.ts` 的目录)解析,不是 Sandbox
|
|
|
184
198
|
`model_reasoning_effort`、`mcp_servers`、`otel` 出现在文件里 setup 报错。manifest 只记
|
|
185
199
|
项目相对路径与字节 SHA-256,不落正文。
|
|
186
200
|
|
|
201
|
+
#### `postSetup`
|
|
202
|
+
|
|
203
|
+
```ts
|
|
204
|
+
postSetup?: SandboxHook[];
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
安装后按数组顺序运行的用户钩子(复用 SandboxHook 的窄上下文):在写主配置、挂 MCP、
|
|
208
|
+
装 Skills / Plugin、写 manifest 全部完成后执行,适合跑插件自带的 setup 脚本这类
|
|
209
|
+
「安装产物就位后才能跑」的过程动作。钩子返回的 cleanup 按 LIFO 与 teardown 一起收尾;
|
|
210
|
+
抛错按基础设施错误计(attempt errored)。
|
|
211
|
+
见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。
|
|
212
|
+
|
|
187
213
|
### `BubConfig`
|
|
188
214
|
|
|
189
215
|
#### `apiKey`
|
|
@@ -221,6 +247,17 @@ pythonPlugins?: PythonPluginSpec[];
|
|
|
221
247
|
规范化后的 package 列表进安装 checkpoint key:plugin 集合不同的两个 agent 变体不会复用同一个
|
|
222
248
|
安装 checkpoint(否则第二个变体会静默拿到第一个变体的环境)。
|
|
223
249
|
|
|
250
|
+
#### `postSetup`
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
postSetup?: SandboxHook[];
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
安装后按数组顺序运行的用户钩子(复用 SandboxHook 的窄上下文):在装 bub、装 Skills /
|
|
257
|
+
Python package、写 manifest 全部完成后执行。钩子返回的 cleanup 按 LIFO 与 teardown 一起
|
|
258
|
+
收尾;抛错按基础设施错误计(attempt errored)。
|
|
259
|
+
见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。
|
|
260
|
+
|
|
224
261
|
{/* GENERATED:END builtin-agent-config */}
|
|
225
262
|
|
|
226
263
|
## `uiMessageStreamAgent`:接 AI SDK 应用的内建无侵入 adapter(含 HITL)
|
|
@@ -93,11 +93,11 @@ npx niceeval exp compare-models weather
|
|
|
93
93
|
| `--timing` | boolean | `show` 命令专用:整个 Attempt 的统一时间树;裸 `--timing` 给有界诊断投影,`--timing=full` 逐节点展开全部 runner/已关联 OTel 节点。 |
|
|
94
94
|
| `--diff` | boolean | `show` 命令专用:sandbox 里的文件改动摘要;`--diff=<文件路径>` 看单个文件的完整改动(路径必须 `=` 连写)。 |
|
|
95
95
|
| `--history` | boolean | `show` 命令专用:执行时间轴——对匹配的每个 experiment × eval 分节,逐 attempt 列时间 / verdict / 摘要 / 耗时 / 成本 / locator;与 `--report` 互斥。 |
|
|
96
|
-
| `--
|
|
96
|
+
| `--exp` | string | `show` / `view` 命令专用:按路径段前缀收窄 experiment(与 `niceeval exp` 位置参数同一套匹配);组名会选中组内全部配置。`view --out` 时同一收窄决定出站内容。 |
|
|
97
97
|
| `--results` | string | `show` / `view` / `sandbox enter\|list\|stop` 共用:结果根目录(`.niceeval` 之外的另一个根,如 `copySnapshots` 产出的发布根)。 |
|
|
98
98
|
| `--snapshot` | string | `view` 命令专用:只打开这一份快照文件(`snapshot.json`);文件不可读时命令失败(扫描模式只跳过)。 |
|
|
99
99
|
| `--report` | string | `show` / `view` 命令专用:用文件默认导出的 `defineReport(...)` 替换两者共用的默认报告。 |
|
|
100
|
-
| `--page` | string | `show` / `view`
|
|
100
|
+
| `--page` | string | `show` / `view` 命令专用:选择报告的初始页;`show` 渲染该页并在尾部附其余页索引,`view` 以它作初始路由。未命中的页 id 按用法错误退出并列出可用页 id。 |
|
|
101
101
|
| `--dry` | boolean | 只打印本次会匹配到的 eval × 运行配置,不实际执行(按下面 `--output` 选中的 profile 给出预览)。 |
|
|
102
102
|
| `--output` | string | 反馈 profile:`auto`(默认)按环境自动选择,`human` / `agent` / `ci` 强制指定;只改变终端展示,不改变选择、调度、判定、artifact 或退出码。`auto` 依次判定:stderr 是 TTY → human;否则 `CI`(或其它常见 CI 平台环境变量)存在 → ci;否则 → agent。 |
|
|
103
103
|
| `--force` | boolean | 忽略上次运行结果,不跳过已通过的 (experiment, eval) 组合,强制全部重跑。 |
|
|
@@ -151,7 +151,7 @@ npx niceeval exp compare --output ci --strict --json .niceeval/ci-summary.json -
|
|
|
151
151
|
npx niceeval view
|
|
152
152
|
```
|
|
153
153
|
|
|
154
|
-
打开本地结果查看器。它和 `show` 共用同一份默认报告和同一套默认选择——对每个 experiment、每个 eval,取历次运行里最新的那份判定;只补跑部分 eval 时,其余 eval 从更早的运行补齐。默认报告按 experiment id 的父目录分组,只在同组内比较。`show` 输出终端文本,`view` 输出网页并提供可交互的证据浏览;eval ID 前缀、`--
|
|
154
|
+
打开本地结果查看器。它和 `show` 共用同一份默认报告和同一套默认选择——对每个 experiment、每个 eval,取历次运行里最新的那份判定;只补跑部分 eval 时,其余 eval 从更早的运行补齐。默认报告按 experiment id 的父目录分组,只在同组内比较。`show` 输出终端文本,`view` 输出网页并提供可交互的证据浏览;eval ID 前缀、`--exp`、`--results` 对两者的收窄一致。完整说明见[查看结果](/zh/how-to/viewing-results)。
|
|
155
155
|
|
|
156
156
|
## `show [id-prefix...]`
|
|
157
157
|
|
|
@@ -52,6 +52,14 @@ tags?: string[];
|
|
|
52
52
|
|
|
53
53
|
标签,供 CLI `--tag` 过滤和 view 分类;与 id 前缀过滤是两套独立的筛选维度。
|
|
54
54
|
|
|
55
|
+
#### `environment`
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
environment?: string;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
这条 eval 需要的环境 profile id(provider-neutral,如 `"python-3.9-astropy-4.2"`);由 sandbox spec 的 `environments` 表翻译成该 provider 的预制产物。
|
|
62
|
+
|
|
55
63
|
#### `judge`
|
|
56
64
|
|
|
57
65
|
```ts
|
|
@@ -15,10 +15,11 @@ description: "NiceEval 内置的 Sandbox 和非 Sandbox 适配器分别是什么
|
|
|
15
15
|
### claude-code
|
|
16
16
|
|
|
17
17
|
- **鉴权**:`ANTHROPIC_API_KEY`(工厂参数 `apiKey` 可覆盖),可选 `ANTHROPIC_BASE_URL`(工厂参数 `baseUrl`)。
|
|
18
|
-
- **装 MCP server**:`mcpServers` 配置项,`setup` 阶段写进沙箱里用户级的 `~/.claude.json`(顶层 `mcpServers`
|
|
18
|
+
- **装 MCP server**:`mcpServers` 配置项,`setup` 阶段写进沙箱里用户级的 `~/.claude.json`(顶层 `mcpServers` 字段)。两种形态按字段区分:本地 stdio 进程写 `command`(可带 `args` / `env`);远程 Streamable HTTP 端点写 `url`(可带 `headers`,逐字进请求头,常用于 `Authorization`),写成 `{ "type": "http", "url": …, "headers": … }` 条目。`url` 要沙箱内可达:服务跑在你自己机器上时,先用 cloudflared / tailscale 这类隧道暴露成公网地址。
|
|
19
19
|
- **装 Skill**:`skills: SkillSpec[]`——本地 Skill(`{ kind: "local", path }`,从项目根读文件或目录)或 Repo Skill(`{ kind: "repo", source, ref, skills }`,可钉 commit/tag、可只启用多 Skill 仓库里的一部分)。装进沙箱的 project 级 `.claude/skills/<name>/`,claude CLI 原生发现(原生 `Skill` 工具调用被 adapter 归一为 `skill.loaded` 事件,不重复记成工具调用;用 `t.loadedSkill()` 断言,不是 `t.calledTool("Skill", ...)`)。
|
|
20
20
|
- **装原生 Plugin**:`plugins: ClaudeCodePluginSpec[]`,每一项声明 Marketplace 连接(`name` / `source` / 可选 `ref`)和其中的 Plugin 名。这个类型只属于 claude-code,传不进 codex。
|
|
21
21
|
- **官方配置文件**:`settingsFile` 是运行 NiceEval 的机器上的本地项目路径,不是 Sandbox 内路径;它指向一份完整的 Claude Code `settings.json`。路径相对项目根,只允许普通相对路径或 `./` 前缀;`..`、绝对路径、`~` 和解析后逃出项目根的符号链接都会报错。Adapter 从本地读取后上传文件,原样替换 Sandbox 中原本为空的用户级 `~/.claude/settings.json`;不继承宿主机配置,也不 deep merge 或重新序列化。`model` 和 `env` 归 Experiment 和 Adapter 管,出现在文件里会在 `setup` 阶段报错并点名冲突键。Secret 走环境变量,别写进配置文件。
|
|
22
|
+
- **安装后脚本**:`postSetup: SandboxHook[]`,在写 settings、挂 MCP、装 Skill 与 Plugin 全部完成后,按数组顺序在沙箱里跑你的钩子函数。典型用途是运行插件自带的 setup 脚本(比如它要往全局配置里登记 hook)——这类脚本必须等安装产物就位才能跑。钩子抛错算基础设施错误(Attempt 记 errored),不算 agent 答题失败。
|
|
22
23
|
- **tracing**:claude CLI 的 beta 原生遥测(`CLAUDE_CODE_ENHANCED_TELEMETRY_BETA`),span 只有结构和计时,细节见 [OTel 接入](/zh/how-to/connect-otel)。
|
|
23
24
|
|
|
24
25
|
例如,用 `configs/claude-code/no-web.json` 关闭内置联网检索:
|
|
@@ -60,15 +61,16 @@ export default defineExperiment({
|
|
|
60
61
|
### codex
|
|
61
62
|
|
|
62
63
|
- **鉴权**:`CODEX_API_KEY`(工厂参数 `apiKey` 可覆盖,不是 `OPENAI_API_KEY`),可选 `CODEX_BASE_URL`(工厂参数 `baseUrl`)接 OpenAI 兼容代理。
|
|
63
|
-
- **装 MCP server**:`mcpServers` 配置项,`setup` 阶段追加进 `~/.codex/config.toml` 的 `[mcp_servers.<name>]`
|
|
64
|
+
- **装 MCP server**:`mcpServers` 配置项,`setup` 阶段追加进 `~/.codex/config.toml` 的 `[mcp_servers.<name>]` 段。两种形态按字段区分:本地 stdio 进程写 `command`(可带 `args` / `env`);远程 Streamable HTTP 端点写 `url`(可带 `headers`,写成 `[mcp_servers.<name>.http_headers]` 子表)。`url` 要沙箱内可达:服务跑在你自己机器上时,先用 cloudflared / tailscale 这类隧道暴露成公网地址。
|
|
64
65
|
|
|
65
66
|
<Warning>
|
|
66
67
|
是复数 `mcp_servers`:单数 `[mcp_server.x]` 会被 codex CLI 静默忽略,MCP 压根挂不上,且不会报错——自查用 `codex mcp list`。
|
|
67
68
|
</Warning>
|
|
68
69
|
|
|
69
70
|
- **装 Skill**:`skills: SkillSpec[]`,与 claude-code 同一个类型。装进 `.agents/skills/<name>/`,并把发现指引写进 AGENTS.md——codex 没有 claude-code 那种原生 Skill 工具,只把文件装进去它不会主动去读;断言"用没用到"看它是否真的执行过读那个文件的 shell 命令,没有工具调用可以直接认。
|
|
70
|
-
- **装原生 Plugin**:`plugins: CodexPluginSpec[]`,声明 Marketplace 连接(`name` / `source` / 可选 `ref`)和其中的 Plugin
|
|
71
|
+
- **装原生 Plugin**:`plugins: CodexPluginSpec[]`,声明 Marketplace 连接(`name` / `source` / 可选 `ref` / 可选 `sparse`)和其中的 Plugin 名。`sparse` 是路径数组(如 `[".agents", "plugins/repo-map"]`),每项让 `codex plugin marketplace add` 带一个 `--sparse <path>`,大仓库只拉插件所需路径,装出来的内容不变。这个类型只属于 codex,传不进 claude-code。
|
|
71
72
|
- **官方配置文件**:`configFile` 是运行 NiceEval 的机器上的本地项目路径,不是 Sandbox 内路径;它指向一份完整的 Codex `config.toml`。路径相对项目根,只允许普通相对路径或 `./` 前缀;`..`、绝对路径、`~` 和解析后逃出项目根的符号链接都会报错。Adapter 从本地读取后上传文件,原样替换 Sandbox 中原本为空的用户级 `~/.codex/config.toml`;不继承宿主机配置,也不拼接、deep merge 或解析后重写。`model`、`model_provider`、`model_providers`、`model_reasoning_effort`、`mcp_servers`、`otel` 归 Experiment 和 Adapter 管,出现在文件里会在 `setup` 阶段报错并点名冲突键。Secret 走环境变量,别写进配置文件。
|
|
73
|
+
- **安装后脚本**:`postSetup: SandboxHook[]`,语义与 claude-code 相同:全部安装步骤完成后按序在沙箱里跑你的钩子函数,适合运行插件自带的 setup 脚本。脚本往 codex 全局配置登记的 hook 不需要交互式信任确认——运行时 `codex exec` 已绕过 hook 信任门槛,hook 直接生效。
|
|
72
74
|
- **tracing**:内置,通过 `config.toml` 的 `[otel.trace_exporter.otlp-http]` 段配置,协议 `http/json`。
|
|
73
75
|
|
|
74
76
|
例如,用 `configs/codex/no-web.toml` 关闭内置联网检索:
|
|
@@ -87,6 +89,7 @@ export default defineExperiment({
|
|
|
87
89
|
agent: codexAgent({
|
|
88
90
|
mcpServers: [
|
|
89
91
|
{ name: "browser", command: "npx", args: ["-y", "@anthropic/mcp-browser"] },
|
|
92
|
+
{ name: "team-memory", url: "https://mem.example.com/mcp/", headers: { Authorization: `Bearer ${process.env.MEM_API_KEY}` } },
|
|
90
93
|
],
|
|
91
94
|
skills: [{ kind: "repo", source: "Effect-TS/skills", ref: "8f3c1a2", skills: ["effect"] }],
|
|
92
95
|
plugins: [
|
|
@@ -109,6 +112,7 @@ export default defineExperiment({
|
|
|
109
112
|
- **装插件**:`pythonPlugins: PythonPluginSpec[]`(`{ package }`:PyPI 包、版本约束或 git URL),`setup` 阶段进 `uv tool install … --with <package>`。这个类型只属于 bub;package 集合进安装 checkpoint key,插件不同的两个变体不会复用同一份安装缓存。
|
|
110
113
|
- **预制 Bub**:NiceEval 的 E2B 配方会把 Bub、OTel 插件和 Python 插件集合算成安装指纹。Adapter 只复用指纹完全一致的环境;仅在 PATH 里放一个 `bub` 不足以证明兼容。构建入口见 [沙箱 provider · 从官方基线继续构建以提速](/zh/how-to/sandbox-providers#从官方基线继续构建以提速)。
|
|
111
114
|
- bub 没有 `mcpServers`——MCP 只属于支持它的 Adapter,Config 上压根没有这个字段。
|
|
115
|
+
- **安装后脚本**:`postSetup: SandboxHook[]`,语义与另外两个 Adapter 相同:全部安装步骤完成后按序在沙箱里跑你的钩子函数。
|
|
112
116
|
- **安装方式**:走 `uv tool install`(PyPI 包,不是 npm 包),首次安装会建 checkpoint 缓存加速后续沙箱。
|
|
113
117
|
- **tracing**:内置,通过环境变量注入,协议 `http/protobuf`。
|
|
114
118
|
|
|
@@ -132,15 +136,16 @@ export default defineExperiment({
|
|
|
132
136
|
| | claude-code | codex | bub |
|
|
133
137
|
|---|---|---|---|
|
|
134
138
|
| 鉴权环境变量 | `ANTHROPIC_API_KEY` | `CODEX_API_KEY` | `BUB_API_KEY` + `BUB_API_BASE` |
|
|
135
|
-
| MCP server | ✅ `mcpServers
|
|
139
|
+
| MCP server | ✅ `mcpServers`(stdio + HTTP) | ✅ `mcpServers`(stdio + HTTP) | ❌ |
|
|
136
140
|
| Skill | ✅ `skills: SkillSpec[]`(原生发现) | ✅ `skills: SkillSpec[]`(+ 发现指引) | ✅ `skills: SkillSpec[]`(+ 发现指引) |
|
|
137
141
|
| 原生 Plugin | ✅ `plugins: ClaudeCodePluginSpec[]` | ✅ `plugins: CodexPluginSpec[]` | ❌ |
|
|
138
142
|
| 官方配置文件 | ✅ `settingsFile`(完整 settings.json) | ✅ `configFile`(完整 config.toml) | ❌ |
|
|
139
143
|
| Python Plugin | ❌ | ❌ | ✅ `pythonPlugins: PythonPluginSpec[]` |
|
|
144
|
+
| 安装后脚本 | ✅ `postSetup: SandboxHook[]` | ✅ `postSetup: SandboxHook[]` | ✅ `postSetup: SandboxHook[]` |
|
|
140
145
|
| tracing | ✅(beta,仅结构与计时) | ✅ | ✅ |
|
|
141
146
|
| 安装方式 | npm 全局包 | npm 全局包 | `uv tool install`(PyPI) |
|
|
142
147
|
|
|
143
|
-
装了什么有据可查:Adapter 在 `setup` 收尾把安装清单写进沙箱的 `__niceeval__/agent-setup.json`,运行器把它存成 Attempt Artifact `agent-setup.json`(库里读 `attempt.agentSetup()`)。清单只记来源、ref、Skill/Plugin 名、解析出的版本,以及官方配置文件的项目相对路径和 SHA-256;不保存配置正文、API Key 或环境变量值。
|
|
148
|
+
装了什么有据可查:Adapter 在 `setup` 收尾把安装清单写进沙箱的 `__niceeval__/agent-setup.json`,运行器把它存成 Attempt Artifact `agent-setup.json`(库里读 `attempt.agentSetup()`)。清单只记来源、ref、Skill/Plugin 名、解析出的版本,以及官方配置文件的项目相对路径和 SHA-256;不保存配置正文、API Key 或环境变量值。MCP server 同理只记非敏感字段:stdio 形态记 `name` / `command` / `args` 不记 `env`,HTTP 形态记 `name` / `url` 不记 `headers`。
|
|
144
149
|
|
|
145
150
|
## 非 Sandbox 适配器
|
|
146
151
|
|
|
@@ -173,8 +173,8 @@ niceeval sandbox stop --all
|
|
|
173
173
|
**按实验回看当前水位。** 不记得定位符时,从实验入手:
|
|
174
174
|
|
|
175
175
|
```bash
|
|
176
|
-
niceeval show --
|
|
177
|
-
niceeval show memory/swelancer --
|
|
176
|
+
niceeval show --exp compare/bub # 这个实验每道题现在的判定
|
|
177
|
+
niceeval show memory/swelancer --exp compare/bub # 收窄到某道题
|
|
178
178
|
```
|
|
179
179
|
|
|
180
180
|
列表里每道题、每次 Attempt 都带定位符,接着往深处钻就回到上面的场景一 / 场景二。
|
|
@@ -208,5 +208,5 @@ niceeval view --results site-data/run
|
|
|
208
208
|
| 装依赖失败、CLI 起不来、跑一半超时 | 重跑该 eval 加 `--keep-sandbox` → `niceeval sandbox enter <id>` |
|
|
209
209
|
| 想看文件实际内容(agent 没改的、起始材料、`$HOME`) | 重跑加 `--keep-sandbox`(failed 也留)→ `sandbox enter` 进 workdir 看 |
|
|
210
210
|
| 留了哪些沙箱、清理 | `sandbox list` → `sandbox stop <id>` / `--all` |
|
|
211
|
-
| 复盘上周那次失败 | 翻出旧定位符 → `show @loc`;不记得就 `show --
|
|
211
|
+
| 复盘上周那次失败 | 翻出旧定位符 → `show @loc`;不记得就 `show --exp <实验>` |
|
|
212
212
|
| 一批失败一起看 | `niceeval view` → Attempt 详情 → Copy fix prompt |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "niceeval",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Agent-native eval tool — eval agents, services, functions, and coding-agent fixtures",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -172,6 +172,7 @@
|
|
|
172
172
|
"tiers:check": "node scripts/sync-tiers.mjs check",
|
|
173
173
|
"view:build": "vite build --config src/view/app/vite.config.ts",
|
|
174
174
|
"build:report": "tsc -p tsconfig.report-build.json && node scripts/prune-report-dist.mjs",
|
|
175
|
+
"build:index": "tsx scripts/generate-reference.ts --bundled-index",
|
|
175
176
|
"site:dev": "cd site && node scripts/dev.mjs",
|
|
176
177
|
"site:build": "cd site && next build",
|
|
177
178
|
"docs:dev": "node scripts/docs-dev.mjs",
|
package/src/agents/bub.ts
CHANGED
|
@@ -11,7 +11,8 @@ import {
|
|
|
11
11
|
import { writeAgentSetupManifest } from "./manifest.ts";
|
|
12
12
|
import { createCheckpoint, restoreCheckpoint } from "../sandbox/checkpoint.ts";
|
|
13
13
|
import { mapBubSpans } from "../o11y/otlp/mappers/bub.ts";
|
|
14
|
-
import
|
|
14
|
+
import { runPostSetupHooks } from "./post-setup.ts";
|
|
15
|
+
import type { Agent, AgentContext, AgentSetupManifest, Sandbox, SandboxHook, SkillSpec } from "../types.ts";
|
|
15
16
|
import { createHash, randomUUID } from "node:crypto";
|
|
16
17
|
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
17
18
|
import { homedir } from "node:os";
|
|
@@ -61,6 +62,13 @@ export interface BubConfig {
|
|
|
61
62
|
* 安装 checkpoint(否则第二个变体会静默拿到第一个变体的环境)。
|
|
62
63
|
*/
|
|
63
64
|
pythonPlugins?: PythonPluginSpec[];
|
|
65
|
+
/**
|
|
66
|
+
* 安装后按数组顺序运行的用户钩子(复用 SandboxHook 的窄上下文):在装 bub、装 Skills /
|
|
67
|
+
* Python package、写 manifest 全部完成后执行。钩子返回的 cleanup 按 LIFO 与 teardown 一起
|
|
68
|
+
* 收尾;抛错按基础设施错误计(attempt errored)。
|
|
69
|
+
* 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。
|
|
70
|
+
*/
|
|
71
|
+
postSetup?: SandboxHook[];
|
|
64
72
|
}
|
|
65
73
|
|
|
66
74
|
const UV = "$HOME/.local/bin/uv";
|
|
@@ -278,6 +286,10 @@ export function bubAgent(config?: BubConfig): Agent {
|
|
|
278
286
|
if (manifest.skills.length || manifest.pythonPlugins?.length) {
|
|
279
287
|
await writeAgentSetupManifest(sb, manifest);
|
|
280
288
|
}
|
|
289
|
+
|
|
290
|
+
// 安装后钩子(postSetup):排在 manifest 之后——manifest 审计 Adapter 自身的安装事实,
|
|
291
|
+
// 钩子失败不该丢掉这份证据。返回的合成 cleanup 交给 runner,与 teardown 一起 LIFO 收尾。
|
|
292
|
+
return await runPostSetupHooks(sb, ctx, config?.postSetup);
|
|
281
293
|
},
|
|
282
294
|
|
|
283
295
|
async send(input, ctx) {
|
|
@@ -218,7 +218,7 @@ describe("claudeCodeAgent settingsFile · setup", () => {
|
|
|
218
218
|
await rm(root, { recursive: true, force: true });
|
|
219
219
|
});
|
|
220
220
|
|
|
221
|
-
const ctx = {} as AgentContext; //
|
|
221
|
+
const ctx = {} as AgentContext; // 本组用例不配 postSetup,setup 不会读 ctx 的字段
|
|
222
222
|
|
|
223
223
|
it("原始字节原样上传并 mv 成用户级 ~/.claude/settings.json;manifest 记项目相对路径 + SHA-256,不落正文", async () => {
|
|
224
224
|
const body = '{\n "$schema": "https://json.schemastore.org/claude-code-settings.json",\n "permissions": { "deny": ["WebSearch", "WebFetch"] }\n}\n';
|
|
@@ -271,3 +271,45 @@ describe("claudeCodeAgent settingsFile · setup", () => {
|
|
|
271
271
|
expect(box.written["__niceeval__/agent-setup.json"]).toBeUndefined();
|
|
272
272
|
});
|
|
273
273
|
});
|
|
274
|
+
|
|
275
|
+
describe("claudeCodeAgent mcpServers · 形态落位", () => {
|
|
276
|
+
const ctx = {} as AgentContext;
|
|
277
|
+
|
|
278
|
+
it("HTTP 形态写成 ~/.claude.json 的 type http + url + headers 条目,stdio 条目不变;manifest 只记非 secret 字段", async () => {
|
|
279
|
+
const box = sb();
|
|
280
|
+
await claudeCodeAgent({
|
|
281
|
+
apiKey: "k",
|
|
282
|
+
mcpServers: [
|
|
283
|
+
{ name: "browser", command: "npx", args: ["-y", "server"], env: { TOKEN: "env-sekret" } },
|
|
284
|
+
{ name: "team-memory", url: "https://mem.example.com/mcp/", headers: { Authorization: "Bearer sekret" } },
|
|
285
|
+
],
|
|
286
|
+
}).setup!(asSandbox(box), ctx);
|
|
287
|
+
|
|
288
|
+
// 用户级 MCP 配置经 heredoc 写进 ~/.claude.json(shared.writeFile),内容在命令里。
|
|
289
|
+
const write = box.commands.find((c) => c.includes("cat > ~/.claude.json"))!;
|
|
290
|
+
expect(write).toContain('"type": "http"');
|
|
291
|
+
expect(write).toContain('"url": "https://mem.example.com/mcp/"');
|
|
292
|
+
expect(write).toContain('"Authorization": "Bearer sekret"');
|
|
293
|
+
expect(write).toContain('"command": "npx"');
|
|
294
|
+
|
|
295
|
+
const manifestRaw = box.written["__niceeval__/agent-setup.json"]!;
|
|
296
|
+
const manifest = JSON.parse(manifestRaw) as AgentSetupManifest;
|
|
297
|
+
expect(manifest.mcpServers).toEqual([
|
|
298
|
+
{ name: "browser", command: "npx", args: ["-y", "server"] },
|
|
299
|
+
{ name: "team-memory", url: "https://mem.example.com/mcp/" },
|
|
300
|
+
]);
|
|
301
|
+
expect(manifestRaw).not.toContain("sekret");
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("边界:HTTP 形态无 headers → 条目不带 headers 字段", async () => {
|
|
305
|
+
const box = sb();
|
|
306
|
+
await claudeCodeAgent({
|
|
307
|
+
apiKey: "k",
|
|
308
|
+
mcpServers: [{ name: "team-memory", url: "https://mem.example.com/mcp/" }],
|
|
309
|
+
}).setup!(asSandbox(box), ctx);
|
|
310
|
+
|
|
311
|
+
const write = box.commands.find((c) => c.includes("cat > ~/.claude.json"))!;
|
|
312
|
+
expect(write).toContain('"type": "http"');
|
|
313
|
+
expect(write).not.toContain("headers");
|
|
314
|
+
});
|
|
315
|
+
});
|
|
@@ -14,7 +14,9 @@ import {
|
|
|
14
14
|
import { mapClaudeCodeSpans } from "../o11y/otlp/mappers/claude-code.ts";
|
|
15
15
|
import { t } from "../i18n/index.ts";
|
|
16
16
|
import { DEFAULT_CLAUDE_CODE_CLI_VERSION } from "./coding-cli-versions.ts";
|
|
17
|
-
import
|
|
17
|
+
import { assertMcpServers, isHttpMcp, mcpManifestEntries } from "./mcp.ts";
|
|
18
|
+
import { runPostSetupHooks } from "./post-setup.ts";
|
|
19
|
+
import type { Agent, AgentSetupManifest, McpServer, Sandbox, SandboxHook, SkillSpec } from "../types.ts";
|
|
18
20
|
|
|
19
21
|
// ───────────────────────────────────────────────────────────────────────────
|
|
20
22
|
// Claude Code 的 agent adapter(沙箱型)。
|
|
@@ -69,7 +71,8 @@ export interface ClaudeCodeConfig {
|
|
|
69
71
|
maxTurns?: number;
|
|
70
72
|
/**
|
|
71
73
|
* 额外 MCP server(每个沙箱 setup 时写进用户级 ~/.claude.json)。
|
|
72
|
-
*
|
|
74
|
+
* stdio 形态写 command(可带 args / env);Streamable HTTP 形态写 url(可带 headers,
|
|
75
|
+
* 逐字进请求头),落成 { "type": "http", "url": …, "headers": … } 条目。
|
|
73
76
|
*/
|
|
74
77
|
mcpServers?: McpServer[];
|
|
75
78
|
/**
|
|
@@ -88,6 +91,14 @@ export interface ClaudeCodeConfig {
|
|
|
88
91
|
* setup 报错。manifest 只记项目相对路径与字节 SHA-256,不落正文。
|
|
89
92
|
*/
|
|
90
93
|
settingsFile?: string;
|
|
94
|
+
/**
|
|
95
|
+
* 安装后按数组顺序运行的用户钩子(复用 SandboxHook 的窄上下文):在写 settings、挂 MCP、
|
|
96
|
+
* 装 Skills / Plugin、写 manifest 全部完成后执行,适合跑插件自带的 setup 脚本这类
|
|
97
|
+
* 「安装产物就位后才能跑」的过程动作。钩子返回的 cleanup 按 LIFO 与 teardown 一起收尾;
|
|
98
|
+
* 抛错按基础设施错误计(attempt errored)。
|
|
99
|
+
* 见 docs/feature/adapters/library/coding-agent-extensions.md「安装后运行脚本」。
|
|
100
|
+
*/
|
|
101
|
+
postSetup?: SandboxHook[];
|
|
91
102
|
}
|
|
92
103
|
|
|
93
104
|
export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
|
|
@@ -115,7 +126,7 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
|
|
|
115
126
|
}),
|
|
116
127
|
},
|
|
117
128
|
|
|
118
|
-
async setup(sb) {
|
|
129
|
+
async setup(sb, ctx) {
|
|
119
130
|
// 预制模板已把 claude 烘焙进镜像(PATH 上)就跳过安装;否则 npm 全局装。
|
|
120
131
|
await sb.runShell(
|
|
121
132
|
`command -v claude >/dev/null 2>&1 || npm install -g @anthropic-ai/claude-code@${DEFAULT_CLAUDE_CODE_CLI_VERSION}`,
|
|
@@ -140,13 +151,20 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
|
|
|
140
151
|
}
|
|
141
152
|
|
|
142
153
|
if (config?.mcpServers?.length) {
|
|
154
|
+
assertMcpServers(config.mcpServers);
|
|
143
155
|
const servers: Record<string, object> = {};
|
|
144
156
|
for (const s of config.mcpServers) {
|
|
145
|
-
servers[s.name] =
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
157
|
+
servers[s.name] = isHttpMcp(s)
|
|
158
|
+
? {
|
|
159
|
+
type: "http",
|
|
160
|
+
url: s.url,
|
|
161
|
+
...(s.headers && Object.keys(s.headers).length && { headers: s.headers }),
|
|
162
|
+
}
|
|
163
|
+
: {
|
|
164
|
+
command: s.command,
|
|
165
|
+
...(s.args?.length && { args: s.args }),
|
|
166
|
+
...(s.env && { env: s.env }),
|
|
167
|
+
};
|
|
150
168
|
}
|
|
151
169
|
// 用户级 MCP 配置在 ~/.claude.json(顶层 mcpServers 字段),不是 ~/.claude/claude.json
|
|
152
170
|
// ——后者 claude CLI 根本不读,MCP 静默挂不上(本机 `claude mcp list` 可核对)。
|
|
@@ -161,12 +179,8 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
|
|
|
161
179
|
manifest.nativePlugins = await installPlugins(sb, config.plugins);
|
|
162
180
|
}
|
|
163
181
|
if (config?.mcpServers?.length) {
|
|
164
|
-
// manifest 里只记「挂了哪个 server
|
|
165
|
-
manifest.mcpServers = config.mcpServers
|
|
166
|
-
name: s.name,
|
|
167
|
-
command: s.command,
|
|
168
|
-
...(s.args?.length ? { args: [...s.args] } : {}),
|
|
169
|
-
}));
|
|
182
|
+
// manifest 里只记「挂了哪个 server、怎么连」;env / headers 里可能有 token,不落盘。
|
|
183
|
+
manifest.mcpServers = mcpManifestEntries(config.mcpServers);
|
|
170
184
|
}
|
|
171
185
|
if (settings) {
|
|
172
186
|
// 只记来源路径与字节哈希,不落正文(任意官方配置都可能带敏感字符串)。
|
|
@@ -181,6 +195,10 @@ export function claudeCodeAgent(config?: ClaudeCodeConfig): Agent {
|
|
|
181
195
|
) {
|
|
182
196
|
await writeAgentSetupManifest(sb, manifest);
|
|
183
197
|
}
|
|
198
|
+
|
|
199
|
+
// 安装后钩子(postSetup):排在 manifest 之后——manifest 审计 Adapter 自身的安装事实,
|
|
200
|
+
// 钩子失败不该丢掉这份证据。返回的合成 cleanup 交给 runner,与 teardown 一起 LIFO 收尾。
|
|
201
|
+
return await runPostSetupHooks(sb, ctx, config?.postSetup);
|
|
184
202
|
},
|
|
185
203
|
|
|
186
204
|
async send(input, ctx) {
|