niceeval 0.5.3 → 0.6.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 +55 -0
- package/dist/agents/types.d.ts +317 -0
- package/dist/context/types.d.ts +335 -0
- package/dist/i18n/core.d.ts +11 -0
- package/dist/i18n/en.d.ts +181 -0
- package/dist/i18n/index.d.ts +5 -0
- package/dist/i18n/zh-CN.d.ts +183 -0
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/parse.d.ts +3 -0
- package/dist/o11y/otlp/receiver.d.ts +21 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/o11y/otlp/turn-otel.d.ts +43 -0
- package/dist/o11y/types.d.ts +190 -0
- package/dist/report/aggregate.d.ts +57 -0
- package/dist/report/aggregate.js +212 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +1 -0
- package/dist/report/built-ins/cost-pass-rate-comparison.js +17 -0
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +2 -0
- package/dist/report/components.d.ts +178 -0
- package/dist/report/components.js +132 -0
- package/dist/report/compute.d.ts +106 -0
- package/dist/report/compute.js +618 -0
- package/dist/report/flag.d.ts +6 -0
- package/dist/report/flag.js +8 -0
- package/dist/report/format.d.ts +32 -0
- package/dist/report/format.js +126 -0
- package/dist/report/index.d.ts +19 -0
- package/dist/report/index.js +30 -0
- package/dist/report/load.d.ts +12 -0
- package/dist/report/load.js +56 -0
- package/dist/report/locale.d.ts +83 -0
- package/dist/report/locale.js +160 -0
- package/dist/report/metrics.d.ts +21 -0
- package/dist/report/metrics.js +115 -0
- package/dist/report/primitives.d.ts +64 -0
- package/dist/report/primitives.js +113 -0
- package/dist/report/react/AttemptList.d.ts +23 -0
- package/dist/report/react/AttemptList.js +20 -0
- package/dist/report/react/DeltaTable.d.ts +8 -0
- package/dist/report/react/DeltaTable.js +24 -0
- package/dist/report/react/EvalList.d.ts +10 -0
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentList.d.ts +10 -0
- package/dist/report/react/ExperimentList.js +15 -0
- package/dist/report/react/GroupSummary.d.ts +8 -0
- package/dist/report/react/GroupSummary.js +8 -0
- package/dist/report/react/MetricBars.d.ts +10 -0
- package/dist/report/react/MetricBars.js +48 -0
- package/dist/report/react/MetricLine.d.ts +9 -0
- package/dist/report/react/MetricLine.js +68 -0
- package/dist/report/react/MetricMatrix.d.ts +10 -0
- package/dist/report/react/MetricMatrix.js +23 -0
- package/dist/report/react/MetricScatter.d.ts +9 -0
- package/dist/report/react/MetricScatter.js +100 -0
- package/dist/report/react/MetricTable.d.ts +11 -0
- package/dist/report/react/MetricTable.js +26 -0
- package/dist/report/react/RunOverview.d.ts +8 -0
- package/dist/report/react/RunOverview.js +12 -0
- package/dist/report/react/Scoreboard.d.ts +8 -0
- package/dist/report/react/Scoreboard.js +26 -0
- package/dist/report/react/cell.d.ts +9 -0
- package/dist/report/react/cell.js +9 -0
- package/dist/report/react/chart-math.d.ts +14 -0
- package/dist/report/react/chart-math.js +68 -0
- package/dist/report/react/colors.d.ts +14 -0
- package/dist/report/react/colors.js +45 -0
- package/dist/report/react/fixtures.d.ts +19 -0
- package/dist/report/react/fixtures.js +393 -0
- package/dist/report/react/format.d.ts +3 -0
- package/dist/report/react/format.js +7 -0
- package/dist/report/react/index.d.ts +6 -0
- package/dist/report/react/index.js +14 -0
- package/dist/report/report.d.ts +24 -0
- package/dist/report/report.js +49 -0
- package/dist/report/text/faces.d.ts +29 -0
- package/dist/report/text/faces.js +503 -0
- package/dist/report/text/layout.d.ts +75 -0
- package/dist/report/text/layout.js +173 -0
- package/dist/report/text/plot.d.ts +37 -0
- package/dist/report/text/plot.js +81 -0
- package/dist/report/text/table.d.ts +8 -0
- package/dist/report/text/table.js +86 -0
- package/dist/report/tree.d.ts +99 -0
- package/dist/report/tree.js +228 -0
- package/dist/report/types.d.ts +420 -0
- package/dist/report/types.js +4 -0
- package/dist/report/web.d.ts +15 -0
- package/dist/report/web.js +37 -0
- package/dist/results/annotated-source.d.ts +61 -0
- package/dist/results/attempt-evidence.d.ts +69 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/locator.d.ts +75 -0
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +45 -0
- package/dist/results/select.js +230 -0
- package/dist/results/source-hash.d.ts +9 -0
- package/dist/results/types.d.ts +204 -0
- package/dist/runner/eval-source.d.ts +22 -0
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +332 -0
- package/dist/sandbox/docker-stream.d.ts +24 -0
- package/dist/sandbox/docker.d.ts +84 -0
- package/dist/sandbox/e2b.d.ts +31 -0
- package/dist/sandbox/errors.d.ts +4 -0
- package/dist/sandbox/local-files.d.ts +6 -0
- package/dist/sandbox/paths.d.ts +4 -0
- package/dist/sandbox/registry.d.ts +13 -0
- package/dist/sandbox/resolve.d.ts +40 -0
- package/dist/sandbox/retry.d.ts +10 -0
- package/dist/sandbox/shell.d.ts +11 -0
- package/dist/sandbox/source-files.d.ts +13 -0
- package/dist/sandbox/types.d.ts +203 -0
- package/dist/sandbox/vercel.d.ts +37 -0
- package/dist/scoring/types.d.ts +70 -0
- package/dist/shared/aggregate.d.ts +29 -0
- package/dist/shared/aggregate.js +55 -0
- package/dist/shared/types.d.ts +27 -0
- package/dist/shared/verdict.d.ts +27 -0
- package/dist/shared/verdict.js +34 -0
- package/dist/tty-line.d.ts +6 -0
- package/dist/types.d.ts +7 -0
- package/dist/util.d.ts +22 -0
- package/docs-site/zh/concepts/adapter.mdx +9 -4
- package/docs-site/zh/concepts/assert.mdx +8 -8
- package/docs-site/zh/concepts/evals.mdx +5 -5
- package/docs-site/zh/concepts/experiment.mdx +3 -1
- package/docs-site/zh/concepts/judge.mdx +9 -9
- package/docs-site/zh/concepts/overview.mdx +2 -2
- package/docs-site/zh/concepts/tier.mdx +6 -6
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +9 -5
- package/docs-site/zh/example/claude-code-codex-skill.mdx +12 -7
- package/docs-site/zh/guides/agent-feedback-loop.mdx +235 -78
- package/docs-site/zh/guides/connect-otel.mdx +3 -3
- package/docs-site/zh/guides/connect-your-agent.mdx +2 -2
- package/docs-site/zh/guides/custom-reports.mdx +196 -36
- package/docs-site/zh/guides/experiments.mdx +7 -0
- package/docs-site/zh/guides/fixtures.mdx +1 -1
- package/docs-site/zh/guides/official-adapters.mdx +31 -9
- package/docs-site/zh/guides/publish-report.mdx +4 -4
- package/docs-site/zh/guides/report-components.mdx +194 -55
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +35 -34
- package/docs-site/zh/guides/runner.mdx +2 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +10 -5
- package/docs-site/zh/guides/sandbox-providers.mdx +102 -0
- package/docs-site/zh/guides/viewing-results.mdx +199 -79
- package/docs-site/zh/guides/write-experiment.mdx +23 -1
- package/docs-site/zh/guides/write-send.mdx +2 -2
- package/docs-site/zh/index.mdx +4 -3
- package/docs-site/zh/introduction.mdx +2 -1
- package/docs-site/zh/quickstart.mdx +5 -3
- package/docs-site/zh/reference/builtin-agents.mdx +38 -13
- package/docs-site/zh/reference/capabilities.mdx +1 -1
- package/docs-site/zh/reference/cli.mdx +36 -13
- package/docs-site/zh/reference/define-agent.mdx +24 -12
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +7 -1
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/package.json +10 -6
- package/src/agents/bub.ts +134 -43
- package/src/agents/claude-code.test.ts +152 -0
- package/src/agents/claude-code.ts +127 -13
- package/src/agents/codex.test.ts +192 -0
- package/src/agents/codex.ts +151 -12
- package/src/agents/index.ts +11 -3
- package/src/agents/manifest.ts +41 -0
- package/src/agents/plugin-config.test.ts +94 -0
- package/src/agents/shared.ts +33 -2
- package/src/agents/skills.test.ts +214 -0
- package/src/agents/skills.ts +304 -0
- package/src/agents/streaming.ts +5 -5
- package/src/agents/types.ts +62 -2
- package/src/cli.ts +74 -47
- package/src/context/context.test.ts +20 -2
- package/src/context/context.ts +3 -0
- package/src/context/control-flow.ts +1 -1
- package/src/context/session.test.ts +92 -1
- package/src/context/session.ts +26 -1
- package/src/context/types.ts +4 -4
- package/src/define.test.ts +82 -0
- package/src/define.ts +72 -11
- package/src/expect/index.ts +5 -5
- package/src/i18n/en.ts +36 -15
- package/src/i18n/zh-CN.ts +39 -21
- package/src/index.ts +1 -1
- package/src/o11y/execution-tree.test.ts +452 -0
- package/src/o11y/execution-tree.ts +367 -0
- package/src/o11y/otlp/receiver.ts +1 -1
- package/src/o11y/otlp/select.ts +7 -3
- package/src/o11y/parsers/claude-code.test.ts +175 -0
- package/src/o11y/parsers/claude-code.ts +32 -0
- package/src/o11y/types.ts +6 -1
- package/src/report/aggregate.ts +26 -20
- package/src/report/built-in-user-parity.test.tsx +643 -0
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +23 -0
- package/src/report/built-ins/index.ts +2 -0
- package/src/report/components.tsx +223 -50
- package/src/report/compute.ts +358 -53
- package/src/report/dual-render.test.tsx +1125 -0
- package/src/report/flag.ts +1 -1
- package/src/report/format.ts +50 -2
- package/src/report/index.ts +48 -14
- package/src/report/load.ts +1 -1
- package/src/report/locale.ts +206 -0
- package/src/report/metrics.ts +39 -15
- package/src/report/primitives.tsx +100 -2
- package/src/report/react/AttemptList.tsx +115 -0
- package/src/report/react/DeltaTable.tsx +9 -6
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentList.tsx +108 -0
- package/src/report/react/GroupSummary.tsx +66 -0
- package/src/report/react/MetricBars.tsx +24 -13
- package/src/report/react/MetricLine.tsx +21 -21
- package/src/report/react/MetricMatrix.tsx +8 -4
- package/src/report/react/MetricScatter.tsx +136 -96
- package/src/report/react/MetricTable.tsx +115 -15
- package/src/report/react/RunOverview.tsx +48 -26
- package/src/report/react/Scoreboard.tsx +15 -7
- package/src/report/react/cell.tsx +18 -9
- package/src/{view/app/lib/chart.ts → report/react/chart-math.ts} +2 -6
- package/src/report/react/colors.ts +24 -15
- package/src/report/react/enhance.js +129 -0
- package/src/report/react/fixtures.ts +163 -34
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +22 -6
- package/src/report/react/render.test.tsx +164 -31
- package/src/report/react/styles.css +405 -213
- package/src/report/report.test.ts +403 -94
- package/src/report/report.ts +29 -11
- package/src/report/text/faces.ts +303 -95
- package/src/report/text/layout.ts +76 -11
- package/src/report/text/table.ts +98 -0
- package/src/report/tree.ts +103 -20
- package/src/report/types.ts +196 -25
- package/src/report/web.ts +46 -19
- package/src/results/annotated-source.test.ts +146 -0
- package/src/results/annotated-source.ts +128 -0
- package/src/results/attempt-evidence.test.ts +158 -0
- package/src/results/attempt-evidence.ts +158 -0
- package/src/results/attempt-source.ts +48 -0
- package/src/results/copy.ts +154 -139
- package/src/results/format.ts +54 -47
- package/src/results/host-equivalence.test.ts +527 -0
- package/src/results/index.ts +56 -14
- package/src/results/locator.test.ts +198 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +392 -175
- package/src/results/results.test.ts +943 -494
- package/src/results/select.ts +129 -23
- package/src/results/skipped-notice.ts +0 -0
- package/src/results/source-hash.ts +28 -0
- package/src/results/types.ts +98 -62
- package/src/results/writer.ts +304 -176
- package/src/runner/attempt.test.ts +170 -0
- package/src/runner/attempt.ts +97 -15
- package/src/runner/discover.test.ts +65 -0
- package/src/runner/discover.ts +6 -2
- package/src/runner/eval-source.test.ts +121 -0
- package/src/runner/eval-source.ts +45 -0
- package/src/runner/fingerprint.ts +54 -1
- package/src/runner/remote-sandbox.ts +1 -1
- package/src/runner/report.test.ts +6 -8
- package/src/runner/report.ts +5 -4
- package/src/runner/reporters/artifacts.ts +27 -41
- package/src/runner/reporters/braintrust.test.ts +3 -3
- package/src/runner/reporters/braintrust.ts +1 -1
- package/src/runner/reporters/console.ts +6 -6
- package/src/runner/reporters/json.ts +4 -4
- package/src/runner/reporters/live.test.ts +56 -0
- package/src/runner/reporters/live.ts +36 -10
- package/src/runner/reporters/quiet.test.ts +66 -0
- package/src/runner/reporters/quiet.ts +49 -0
- package/src/runner/reporters/shared.ts +5 -5
- package/src/runner/reporters/table.ts +17 -17
- package/src/runner/run.ts +61 -102
- package/src/runner/types.ts +43 -32
- package/src/sandbox/checkpoint.ts +1 -1
- package/src/sandbox/docker.ts +15 -2
- package/src/sandbox/e2b.ts +11 -5
- package/src/sandbox/errors.ts +13 -0
- package/src/sandbox/index.ts +3 -3
- package/src/sandbox/local-files.ts +1 -1
- package/src/sandbox/registry.ts +6 -5
- package/src/sandbox/resolve.ts +38 -23
- package/src/sandbox/retry.test.ts +68 -0
- package/src/sandbox/retry.ts +44 -0
- package/src/sandbox/types.ts +69 -28
- package/src/sandbox/vercel.ts +17 -3
- package/src/scoring/collector.ts +3 -3
- package/src/scoring/judge.ts +2 -2
- package/src/scoring/scoped.ts +13 -1
- package/src/scoring/types.ts +7 -7
- package/src/scoring/verdict.ts +4 -4
- package/src/shared/aggregate.ts +23 -3
- package/src/shared/types.ts +2 -2
- package/src/shared/{outcome.ts → verdict.ts} +15 -15
- package/src/show/compose.ts +12 -132
- package/src/show/index.ts +125 -94
- package/src/show/render.ts +660 -160
- package/src/show/show.test.ts +472 -253
- package/src/source-loc.ts +1 -1
- package/src/tty-line.ts +32 -0
- package/src/types.ts +2 -2
- package/src/util.test.ts +21 -1
- package/src/util.ts +5 -1
- package/src/view/app/App.tsx +33 -198
- package/src/view/app/components/AttemptModal.tsx +6 -6
- package/src/view/app/components/CodeView.tsx +2 -2
- package/src/view/app/components/CopyControls.tsx +32 -36
- package/src/view/app/components/SkippedRunsBanner.tsx +140 -0
- package/src/view/app/components/ui/badge.tsx +1 -1
- package/src/view/app/components/ui/dialog.tsx +2 -2
- package/src/view/app/i18n.ts +40 -31
- package/src/view/app/lib/artifact-url.ts +1 -1
- package/src/view/app/lib/attempt-route.test.ts +37 -47
- package/src/view/app/lib/attempt-route.ts +26 -26
- package/src/view/app/lib/rows.ts +9 -112
- package/src/view/app/lib/verdict.ts +25 -0
- package/src/view/app/main.tsx +9 -12
- package/src/view/app/pages/RunsPage.tsx +4 -4
- package/src/view/app/pages/TracesPage.tsx +3 -3
- package/src/view/app/shared.ts +0 -1
- package/src/view/app/types.ts +6 -51
- package/src/view/artifact-serving.test.ts +136 -0
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +21 -21
- package/src/view/data.test.ts +214 -143
- package/src/view/data.ts +166 -120
- package/src/view/index.ts +23 -11
- package/src/view/server.ts +52 -17
- package/src/view/shared/types.ts +22 -31
- package/src/view/styles.css +117 -327
- package/src/view/view-report.test.ts +146 -98
- package/docs-site/zh/guides/sandbox-backends.mdx +0 -75
- package/src/report/default-report.tsx +0 -222
- package/src/report/dual-face.test.tsx +0 -527
- package/src/report/react/CaseList.tsx +0 -70
- package/src/view/app/components/CostScoreChart.tsx +0 -147
- package/src/view/app/components/ExperimentTable.tsx +0 -284
- package/src/view/app/components/GroupSelector.tsx +0 -61
- package/src/view/app/components/primitives.tsx +0 -43
- package/src/view/app/lib/outcome.ts +0 -82
package/src/cli.ts
CHANGED
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
// niceeval exp [组|配置] [pattern] 跑实验
|
|
3
3
|
// niceeval show [pattern] 终端读结果:榜单 / 单 eval / 证据切面 / 时间轴 / --report
|
|
4
4
|
// niceeval list 只列出发现到的 eval
|
|
5
|
-
// niceeval clean 删除 .niceeval/
|
|
5
|
+
// niceeval clean 删除 .niceeval/ 历史运行 artifact
|
|
6
6
|
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
8
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
9
9
|
import { existsSync } from "node:fs";
|
|
10
|
-
import { join } from "node:path";
|
|
10
|
+
import { join, relative } from "node:path";
|
|
11
11
|
import { pathToFileURL } from "node:url";
|
|
12
12
|
import { parseArgs as nodeParseArgs } from "node:util";
|
|
13
13
|
import { discoverEvals, discoverExperiments, makeFilter } from "./runner/discover.ts";
|
|
14
14
|
import { runEvals, type AgentRun } from "./runner/run.ts";
|
|
15
15
|
import { runWho } from "./runner/types.ts";
|
|
16
|
+
import { planCarry } from "./runner/fingerprint.ts";
|
|
16
17
|
import { stopAllSandboxes, liveSandboxCount } from "./sandbox/registry.ts";
|
|
17
|
-
import { evalLevelStats } from "./shared/
|
|
18
|
+
import { evalLevelStats } from "./shared/verdict.ts";
|
|
18
19
|
import { sandboxRecommendedConcurrency } from "./sandbox/resolve.ts";
|
|
19
20
|
import { Console as ConsoleReporter } from "./runner/reporters/console.ts";
|
|
21
|
+
import { Quiet as QuietReporter } from "./runner/reporters/quiet.ts";
|
|
20
22
|
import { Json, JUnit } from "./runner/reporters/json.ts";
|
|
21
23
|
import { Live as LiveReporter, type LiveRow } from "./runner/reporters/live.ts";
|
|
22
24
|
import { Artifacts as ArtifactsReporter } from "./runner/reporters/artifacts.ts";
|
|
@@ -28,7 +30,11 @@ import {
|
|
|
28
30
|
IncompatibleResultsError,
|
|
29
31
|
ViewInputError,
|
|
30
32
|
} from "./view/index.ts";
|
|
31
|
-
|
|
33
|
+
// load.ts 本身没有 JSX,但它的 ReportDefinition/ReportLoadError 要和 view 报告槽实际装载
|
|
34
|
+
// --report 用的那份(dist/report/**,见 tsconfig.report-build.json)是同一个模块实例——
|
|
35
|
+
// `unique symbol` 品牌与 class 的 instanceof 都按声明处的模块身份判定,raw src 和编译产物
|
|
36
|
+
// 是两份不同源码位置,即使运行时同名同形,TS 类型与 instanceof 都不认。
|
|
37
|
+
import { ReportLoadError } from "../dist/report/load.js";
|
|
32
38
|
import { runShow } from "./show/index.ts";
|
|
33
39
|
import { t } from "./i18n/index.ts";
|
|
34
40
|
import { formatThrown, upsertManagedBlock } from "./util.ts";
|
|
@@ -66,15 +72,14 @@ interface Flags {
|
|
|
66
72
|
port?: number;
|
|
67
73
|
help: boolean;
|
|
68
74
|
version: boolean;
|
|
69
|
-
// ── show 专属(位置参数仍是 eval id
|
|
70
|
-
|
|
71
|
-
|
|
75
|
+
// ── show 专属(位置参数仍是 eval id 前缀 / `@<locator>`;这些 flag 选「怎么看」)──
|
|
76
|
+
eval: boolean;
|
|
77
|
+
execution: boolean;
|
|
72
78
|
diff: boolean;
|
|
73
79
|
/** --diff=<路径>(必须 = 连写;空格形式会把路径当 eval id 前缀,按文档如此)。 */
|
|
74
80
|
diffPath?: string;
|
|
75
81
|
history: boolean;
|
|
76
82
|
experiment?: string;
|
|
77
|
-
attempt?: number;
|
|
78
83
|
run?: string;
|
|
79
84
|
report?: string;
|
|
80
85
|
}
|
|
@@ -109,31 +114,31 @@ const FLAG_OPTIONS = {
|
|
|
109
114
|
/** `view` 命令专用:指定本地服务器监听端口。 */
|
|
110
115
|
port: { type: "string" },
|
|
111
116
|
// show 的证据切面 / 时间轴 / 报告装载(docs-site/zh/guides/viewing-results.mdx)。
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
/** `show`
|
|
115
|
-
|
|
117
|
+
// 证据切面只认 `@<locator>`(或收窄到单个 eval 的前缀)选出的那一个 attempt——不再有
|
|
118
|
+
// 数字 `--attempt`,选哪个 attempt 由 locator 精确指名,不是「先选 eval 再挑第几次」。
|
|
119
|
+
/** `show` 命令专用:该 attempt 运行时保存的 Eval 源码,gate/soft 断言标回源码行(证据切面)。 */
|
|
120
|
+
eval: { type: "boolean" },
|
|
121
|
+
/** `show` 命令专用:该 attempt 的标准执行事件流(消息、thinking、Skill load、工具调用/结果);有 OTel 时同一节点补时间(证据切面)。 */
|
|
122
|
+
execution: { type: "boolean" },
|
|
116
123
|
// --diff 是布尔;--diff=<路径> 在 parseArgs 前预扫成 diffPath(路径必须 = 连写,
|
|
117
124
|
// 空格形式的下一个 token 仍是位置参数 = eval id 前缀,与文档一致)。
|
|
118
125
|
/** `show` 命令专用:sandbox 里的文件改动摘要;`--diff=<文件路径>` 看单个文件的完整改动(路径必须 `=` 连写)。 */
|
|
119
126
|
diff: { type: "boolean" },
|
|
120
127
|
/** `show` 命令专用:跨 run 时间轴,只列真实执行;与 `--report` 互斥。 */
|
|
121
128
|
history: { type: "boolean" },
|
|
122
|
-
/** `show` / `view`
|
|
129
|
+
/** `show` / `view` 命令专用:Selection 只留该实验。 */
|
|
123
130
|
experiment: { type: "string" },
|
|
124
|
-
/** `show`
|
|
125
|
-
attempt: { type: "string" },
|
|
126
|
-
/** `show` / `view` 命令专用:钉死看某一个结果目录(历史 run 或 `copySnapshots` 产物)。 */
|
|
131
|
+
/** `show` / `view` 命令专用:钉死看某一个结果目录(某次快照或 `copySnapshots` 产物)。 */
|
|
127
132
|
run: { type: "string" },
|
|
128
|
-
/** `show` / `view`
|
|
133
|
+
/** `show` / `view` 命令专用:渲染你的报告文件(文件默认导出 `defineReport(...)`);show 用它替换 Attempt 索引,view 用它替换默认分析报告。 */
|
|
129
134
|
report: { type: "string" },
|
|
130
135
|
/** 只打印本次会匹配到的 eval × 运行配置,不实际执行。 */
|
|
131
136
|
dry: { type: "boolean" },
|
|
132
|
-
/** 关闭控制台 / live
|
|
137
|
+
/** 关闭控制台 / live 的逐条结果与末尾汇总(attempt 进度行仍写 stderr);errored / failed 的结果各在 stderr 补一行摘要,passed / skipped 静默;reporter 仍会写 artifacts。 */
|
|
133
138
|
quiet: { type: "boolean" },
|
|
134
139
|
/** 忽略上次运行结果,不跳过已通过的 (experiment, eval) 组合,强制全部重跑。 */
|
|
135
140
|
force: { type: "boolean" },
|
|
136
|
-
/** CI 中推荐使用:让软阈值(`soft`)失败也计入整条 eval 的
|
|
141
|
+
/** CI 中推荐使用:让软阈值(`soft`)失败也计入整条 eval 的 verdict。 */
|
|
137
142
|
strict: { type: "boolean" },
|
|
138
143
|
/** 某个 eval 的一次 attempt 通过后,停止该 eval 剩余的 attempts。 */
|
|
139
144
|
"early-exit": { type: "boolean" },
|
|
@@ -211,13 +216,12 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
|
|
|
211
216
|
open: values["no-open"] === true ? false : values.open === true ? true : undefined,
|
|
212
217
|
help: values.help === true,
|
|
213
218
|
version: values.version === true,
|
|
214
|
-
|
|
215
|
-
|
|
219
|
+
eval: values.eval === true,
|
|
220
|
+
execution: values.execution === true,
|
|
216
221
|
diff: values.diff === true && diffPath === undefined,
|
|
217
222
|
diffPath,
|
|
218
223
|
history: values.history === true,
|
|
219
224
|
experiment: values.experiment as string | undefined,
|
|
220
|
-
attempt: numberFlag("attempt", values.attempt as string | undefined),
|
|
221
225
|
run: values.run as string | undefined,
|
|
222
226
|
report: values.report as string | undefined,
|
|
223
227
|
};
|
|
@@ -273,13 +277,16 @@ const AGENT_RULES_END = "<!-- END:niceeval-agent-rules -->";
|
|
|
273
277
|
const AGENT_RULES_CONTENT = [
|
|
274
278
|
"# niceeval is NOT in your training data",
|
|
275
279
|
"",
|
|
276
|
-
"Its APIs and conventions may differ from anything you have seen.
|
|
277
|
-
"
|
|
278
|
-
"adapter, or niceeval config.
|
|
279
|
-
"
|
|
280
|
-
"After a run, drill into failures with `niceeval show
|
|
281
|
-
"
|
|
282
|
-
"
|
|
280
|
+
"Its APIs and conventions may differ from anything you have seen. Start with",
|
|
281
|
+
"`node_modules/niceeval/INDEX.md`, then read the task-specific bundled guides it points",
|
|
282
|
+
"to before writing any eval, experiment, adapter, or niceeval config. That index and",
|
|
283
|
+
"the bundled Chinese docs are the authoritative version matching this installation.",
|
|
284
|
+
"After a run, drill into failures with `niceeval show` — pick an `@<locator>` from the",
|
|
285
|
+
"compact index it prints, then `niceeval show @<locator>` for a compact overview, or add",
|
|
286
|
+
"`--eval` / `--execution` / `--diff` for evidence; the snapshot directories the CLI prints",
|
|
287
|
+
"are the structured source of truth: `snapshot.json` holds the run's metadata and each",
|
|
288
|
+
"`<evalId>/a<attempt>/result.json` holds that attempt's verdict and assertions, next to",
|
|
289
|
+
"its artifact files (`events.json` / `trace.json` / `diff.json`).",
|
|
283
290
|
].join("\n");
|
|
284
291
|
|
|
285
292
|
// 优先复用已有的 AGENTS.md;项目只有 CLAUDE.md(没有 AGENTS.md)时改写 CLAUDE.md 本身,
|
|
@@ -373,7 +380,7 @@ async function main(): Promise<void> {
|
|
|
373
380
|
}
|
|
374
381
|
|
|
375
382
|
if (command === "view") {
|
|
376
|
-
// 位置参数 = eval id 前缀(
|
|
383
|
+
// 位置参数 = eval id 前缀(收窄报告槽 Selection);存在的文件路径 = 单文件模式;
|
|
377
384
|
// 结果目录经 --run 递入;--report 整槽替换报告槽(与 show --report 吃同一个文件)。
|
|
378
385
|
let viewInput: { input?: string; patterns: string[] };
|
|
379
386
|
try {
|
|
@@ -404,15 +411,14 @@ async function main(): Promise<void> {
|
|
|
404
411
|
}
|
|
405
412
|
|
|
406
413
|
if (command === "show") {
|
|
407
|
-
// show 不依赖 niceeval.config.ts:读的是 .niceeval/(或 --run
|
|
414
|
+
// show 不依赖 niceeval.config.ts:读的是 .niceeval/(或 --run 指定的某个快照目录)的落盘结果。
|
|
408
415
|
const code = await runShow(cwd, positionals, {
|
|
409
|
-
|
|
410
|
-
|
|
416
|
+
eval: flags.eval,
|
|
417
|
+
execution: flags.execution,
|
|
411
418
|
diff: flags.diff,
|
|
412
419
|
diffPath: flags.diffPath,
|
|
413
420
|
history: flags.history,
|
|
414
421
|
experiment: flags.experiment,
|
|
415
|
-
attempt: flags.attempt,
|
|
416
422
|
run: flags.run,
|
|
417
423
|
report: flags.report,
|
|
418
424
|
});
|
|
@@ -522,6 +528,18 @@ async function main(): Promise<void> {
|
|
|
522
528
|
process.exit(0);
|
|
523
529
|
}
|
|
524
530
|
|
|
531
|
+
// 提前算好(而不是等 runEvals 内部算):live 表格要在第一帧就知道哪些行会被携入
|
|
532
|
+
// (carry),直接渲染成已完成,不然会显示"waiting for a slot"直到进程退出——它们
|
|
533
|
+
// 永远等不到 eval:start,run.ts 压根不会为携入的 (experimentId, evalId) 派发 attempt。
|
|
534
|
+
// 两处必须共用同一份 planCarry() 判断,否则各自算一遍,一旦判断不一致,live 表格
|
|
535
|
+
// 显示的"携入"和 run.ts 实际调度的"携入"就会对不上。
|
|
536
|
+
const priorResults = flags.force ? undefined : await loadLatestResultsPerEval(join(cwd, ".niceeval"));
|
|
537
|
+
const carryPlan = priorResults?.length ? await planCarry(evals, agentRuns, priorResults) : undefined;
|
|
538
|
+
const carriedVerdictByKey = new Map<string, string>();
|
|
539
|
+
for (const r of carryPlan?.carriedResults ?? []) {
|
|
540
|
+
if (r.experimentId) carriedVerdictByKey.set(`${r.experimentId}|${r.id}`, r.verdict);
|
|
541
|
+
}
|
|
542
|
+
|
|
525
543
|
const reporters: Reporter[] = [];
|
|
526
544
|
let onProgress: ((evalId: string, who: string, msg: string) => void) | undefined;
|
|
527
545
|
|
|
@@ -532,10 +550,13 @@ async function main(): Promise<void> {
|
|
|
532
550
|
for (const agentRun of agentRuns) {
|
|
533
551
|
// who 必须与 attempt.ts 的进度上报同源(runWho):曾用 agent/model,同 agent 同 model
|
|
534
552
|
// 的实验变体(xxx 与 xxx--agents-md)会被折叠成一行,0/2 看起来像同一 eval 跑两次。
|
|
535
|
-
const who = runWho(agentRun);
|
|
553
|
+
const who = runWho({ agentName: agentRun.agent.name, model: agentRun.model, experimentId: agentRun.experimentId });
|
|
536
554
|
const matched = evals.filter((e) => agentRun.evalFilter(e.id));
|
|
537
555
|
for (const evalDef of matched) {
|
|
538
|
-
|
|
556
|
+
const carriedVerdict = agentRun.experimentId
|
|
557
|
+
? carriedVerdictByKey.get(`${agentRun.experimentId}|${evalDef.id}`)
|
|
558
|
+
: undefined;
|
|
559
|
+
liveRows.push({ evalId: evalDef.id, who, total: agentRun.runs, carriedVerdict });
|
|
539
560
|
}
|
|
540
561
|
}
|
|
541
562
|
const totalAttempts = liveRows.reduce((s, r) => s + r.total, 0);
|
|
@@ -545,9 +566,13 @@ async function main(): Promise<void> {
|
|
|
545
566
|
} else {
|
|
546
567
|
reporters.push(ConsoleReporter());
|
|
547
568
|
}
|
|
569
|
+
} else {
|
|
570
|
+
// --quiet:进度流照旧直写 stderr,结果流换成最小报告器 —— errored / failed 各补一行
|
|
571
|
+
// stderr,passed / skipped 静默。没有它,attempt 出执行错时控制台会全程无声。
|
|
572
|
+
reporters.push(QuietReporter());
|
|
548
573
|
}
|
|
549
574
|
const artifacts = ArtifactsReporter();
|
|
550
|
-
reporters.push(artifacts);
|
|
575
|
+
reporters.push( artifacts);
|
|
551
576
|
if (flags.junit) reporters.push(JUnit(flags.junit));
|
|
552
577
|
if (flags.json) reporters.push(Json(flags.json));
|
|
553
578
|
reporters.push(...(config.reporters ?? []));
|
|
@@ -590,13 +615,11 @@ async function main(): Promise<void> {
|
|
|
590
615
|
});
|
|
591
616
|
}
|
|
592
617
|
|
|
593
|
-
// 无全局默认:并发上限由 sandbox
|
|
594
|
-
// 多个 agentRun 各有 sandbox 时取最小值(
|
|
618
|
+
// 无全局默认:并发上限由 sandbox provider 的推荐值决定。
|
|
619
|
+
// 多个 agentRun 各有 sandbox 时取最小值(最保守的 provider 决定上限)。
|
|
595
620
|
const sandboxRecs = agentRuns.map((r) => sandboxRecommendedConcurrency(r.sandbox));
|
|
596
621
|
const sandboxDefaultConcurrency = sandboxRecs.length > 0 ? Math.min(...sandboxRecs) : 10;
|
|
597
622
|
|
|
598
|
-
const priorResults = flags.force ? undefined : await loadLatestResultsPerEval(join(cwd, ".niceeval"));
|
|
599
|
-
|
|
600
623
|
const summary = await runEvals({
|
|
601
624
|
config,
|
|
602
625
|
evals,
|
|
@@ -610,22 +633,26 @@ async function main(): Promise<void> {
|
|
|
610
633
|
signal: ctrl.signal,
|
|
611
634
|
onProgress,
|
|
612
635
|
priorResults,
|
|
636
|
+
carryPlan,
|
|
613
637
|
});
|
|
614
638
|
|
|
615
639
|
// 正常返回(含被中断后走部分汇总)后再兜一刀:Scope finalizer 没停掉的残留沙箱在这里强清。
|
|
616
640
|
// 跑顺利时登记表已空,是 no-op。
|
|
617
641
|
await stopAllSandboxes();
|
|
618
642
|
|
|
619
|
-
// agent
|
|
620
|
-
// attempt
|
|
621
|
-
|
|
622
|
-
|
|
643
|
+
// agent 反馈闭环的入口:跑完直接给出每个已创建快照的目录,agent 读 snapshot.json 与各
|
|
644
|
+
// attempt 的 result.json / artifact(events/trace/diff),不必解析人类向的流式输出。
|
|
645
|
+
// --quiet 下也输出。相对 cwd 的路径更友好;结果落在 cwd 外时(relative 路径以 .. 开头)
|
|
646
|
+
// 原样打印绝对路径。
|
|
647
|
+
for (const { dir } of artifacts.outputDirs()) {
|
|
648
|
+
const rel = relative(cwd, dir);
|
|
649
|
+
process.stdout.write(t("cli.resultsPath", { path: rel && !rel.startsWith("..") ? rel : dir }));
|
|
623
650
|
}
|
|
624
651
|
|
|
625
|
-
// 退出码按 eval
|
|
652
|
+
// 退出码按 eval 级判定,不按 attempt:summary.failed/errored 统计的是每次 attempt,
|
|
626
653
|
// 被 runs+earlyExit 重试吸收的失败(先挂一次、后来过了)不该把进程判红——否则
|
|
627
654
|
// 「runs 吸收单次抖动」在 CI 退出码这层永远不成立。折叠口径与报表/view 共用
|
|
628
|
-
//
|
|
655
|
+
// foldEvalVerdict(任一轮通过 → 该 eval 通过),粒度 experimentId|eval id。
|
|
629
656
|
const stats = evalLevelStats(summary.results, (r) => `${r.experimentId ?? ""}|${r.id}`);
|
|
630
657
|
const failedExit = stats.failed > 0 || stats.errored > 0;
|
|
631
658
|
process.exit(failedExit ? 1 : 0);
|
|
@@ -352,11 +352,11 @@ describe("TurnHandle scoped assertions (parked/loadedSkill/noFailedActions/maxTo
|
|
|
352
352
|
expect(cost.passed).toBe(false);
|
|
353
353
|
});
|
|
354
354
|
|
|
355
|
-
it("turn.loadedSkill()
|
|
355
|
+
it("turn.loadedSkill() reads the skill.loaded event scoped to this turn", async () => {
|
|
356
356
|
const agent = scriptedAgent([
|
|
357
357
|
{
|
|
358
358
|
status: "completed",
|
|
359
|
-
events: [{ type: "
|
|
359
|
+
events: [{ type: "skill.loaded", skill: "pdf-export", callId: "c1" }],
|
|
360
360
|
},
|
|
361
361
|
]);
|
|
362
362
|
const { context, state } = makeContext(agent);
|
|
@@ -366,4 +366,22 @@ describe("TurnHandle scoped assertions (parked/loadedSkill/noFailedActions/maxTo
|
|
|
366
366
|
const [result] = await state.collector.finalize(baseScoringContext(state));
|
|
367
367
|
expect(result.passed).toBe(true);
|
|
368
368
|
});
|
|
369
|
+
|
|
370
|
+
// Skill 加载是一等事件,不是「名字叫 load_skill 的工具调用」——adapter 负责归一
|
|
371
|
+
// (claude-code parser 就把 Skill tool_use 直接吐成 skill.loaded)。伪装成工具调用的
|
|
372
|
+
// 加载断言侧看不见,这是 adapter 违约,不是断言该兜的底(见 docs/feature/adapters/architecture/events.md)。
|
|
373
|
+
it("turn.loadedSkill() does not match a tool call that merely happens to be named load_skill", async () => {
|
|
374
|
+
const agent = scriptedAgent([
|
|
375
|
+
{
|
|
376
|
+
status: "completed",
|
|
377
|
+
events: [{ type: "action.called", callId: "c1", name: "load_skill", input: { skill: "pdf-export" } }],
|
|
378
|
+
},
|
|
379
|
+
]);
|
|
380
|
+
const { context, state } = makeContext(agent);
|
|
381
|
+
const turn = await context.send("export as pdf");
|
|
382
|
+
turn.loadedSkill("pdf-export");
|
|
383
|
+
|
|
384
|
+
const [result] = await state.collector.finalize(baseScoringContext(state));
|
|
385
|
+
expect(result.passed).toBe(false);
|
|
386
|
+
});
|
|
369
387
|
});
|
package/src/context/context.ts
CHANGED
|
@@ -62,6 +62,8 @@ export interface ContextDeps {
|
|
|
62
62
|
model?: string;
|
|
63
63
|
reasoningEffort?: string;
|
|
64
64
|
flags: Record<string, unknown>;
|
|
65
|
+
/** 路径推导出的实验 id(经 send ctx 透给 adapter,见 AgentContext.experimentId)。 */
|
|
66
|
+
experimentId?: string;
|
|
65
67
|
signal: AbortSignal;
|
|
66
68
|
log(msg: string): void;
|
|
67
69
|
judge: JudgeConfig | undefined;
|
|
@@ -93,6 +95,7 @@ export function createEvalContext(deps: ContextDeps): { context: TestContext; st
|
|
|
93
95
|
model: deps.model,
|
|
94
96
|
reasoningEffort: deps.reasoningEffort,
|
|
95
97
|
flags: deps.flags,
|
|
98
|
+
experimentId: deps.experimentId,
|
|
96
99
|
signal: deps.signal,
|
|
97
100
|
log: deps.log,
|
|
98
101
|
telemetry: deps.telemetry,
|
|
@@ -10,7 +10,7 @@ export class EvalSkipped extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
/** t.require / turn.expectOk
|
|
13
|
+
/** t.require / turn.expectOk 不过:正常的断言失败,中止后续,但已记录的断言决定判定。 */
|
|
14
14
|
export class EvalRequirementFailed extends Error {
|
|
15
15
|
constructor(public readonly assertionName: string) {
|
|
16
16
|
super(`requirement failed: ${assertionName}`);
|
|
@@ -1,11 +1,102 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
|
|
3
|
-
import { createAgentSession } from "./session.ts";
|
|
3
|
+
import { createAgentSession, SessionManager } from "./session.ts";
|
|
4
|
+
import type { Agent, Sandbox, StreamEvent, Turn, TurnInput } from "../types.ts";
|
|
4
5
|
|
|
5
6
|
// createAgentSession() 是 ctx.session 的实现——一条会话线的存取器(见
|
|
6
7
|
// docs-site/zh/concepts/adapter.mdx 的 AgentSession 契约)。这里直接测存取器本身;
|
|
7
8
|
// 端到端的「同一条线同一个 ctx.session」由 SessionManager / RunSession 保证。
|
|
8
9
|
|
|
10
|
+
function fakeSandbox(): Sandbox {
|
|
11
|
+
return {
|
|
12
|
+
workdir: "/sandbox/work",
|
|
13
|
+
runCommand: async () => { throw new Error("not implemented"); },
|
|
14
|
+
runShell: async () => { throw new Error("not implemented"); },
|
|
15
|
+
readFile: async () => "",
|
|
16
|
+
fileExists: async () => false,
|
|
17
|
+
readSourceFiles: async () => Object.assign([], {
|
|
18
|
+
text: () => "",
|
|
19
|
+
code: () => "",
|
|
20
|
+
fileMatching: () => undefined,
|
|
21
|
+
fileMatchingAll: () => undefined,
|
|
22
|
+
hasPath: () => false,
|
|
23
|
+
}),
|
|
24
|
+
writeFiles: async () => {},
|
|
25
|
+
uploadFiles: async () => {},
|
|
26
|
+
uploadDirectory: async () => {},
|
|
27
|
+
stop: async () => {},
|
|
28
|
+
sandboxId: "fake",
|
|
29
|
+
otlpHost: null,
|
|
30
|
+
downloadFile: async () => Buffer.from(""),
|
|
31
|
+
uploadFile: async () => {},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function agentReturning(turn: Turn): Agent {
|
|
36
|
+
return {
|
|
37
|
+
name: "fake-agent",
|
|
38
|
+
kind: "remote",
|
|
39
|
+
async send(_input: TurnInput): Promise<Turn> {
|
|
40
|
+
return turn;
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function makeManager(turn: Turn) {
|
|
46
|
+
const lines: string[] = [];
|
|
47
|
+
const manager = new SessionManager({
|
|
48
|
+
agent: agentReturning(turn),
|
|
49
|
+
sandbox: fakeSandbox(),
|
|
50
|
+
flags: {},
|
|
51
|
+
signal: new AbortController().signal,
|
|
52
|
+
log: (msg) => lines.push(msg),
|
|
53
|
+
});
|
|
54
|
+
return { manager, lines };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
describe("SessionManager.send() 进度行", () => {
|
|
58
|
+
it("failed 轮:进度行末尾追加最后一条 error 事件的 message", async () => {
|
|
59
|
+
const events: StreamEvent[] = [
|
|
60
|
+
{ type: "message", role: "assistant", text: "" },
|
|
61
|
+
{ type: "error", message: "402 Insufficient Balance" },
|
|
62
|
+
];
|
|
63
|
+
const { manager, lines } = makeManager({ events, status: "failed" });
|
|
64
|
+
await manager.send(manager.primary, "hi");
|
|
65
|
+
|
|
66
|
+
const progressLine = lines.find((l) => l.includes("← failed"));
|
|
67
|
+
expect(progressLine).toContain("402 Insufficient Balance");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("failed 轮但没有 error 事件:不追加空后缀,行尾保持原格式", async () => {
|
|
71
|
+
const { manager, lines } = makeManager({ events: [], status: "failed" });
|
|
72
|
+
await manager.send(manager.primary, "hi");
|
|
73
|
+
|
|
74
|
+
const progressLine = lines.find((l) => l.includes("← failed"));
|
|
75
|
+
expect(progressLine).toMatch(/\ds$/); // 以 "Ns" 收尾,没有多余的 " · " 后缀
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("completed 轮:即使事件里混了 error 事件也不提取原因(只在 failed 时生效)", async () => {
|
|
79
|
+
const events: StreamEvent[] = [{ type: "error", message: "不该出现在这里" }];
|
|
80
|
+
const { manager, lines } = makeManager({ events, status: "completed" });
|
|
81
|
+
await manager.send(manager.primary, "hi");
|
|
82
|
+
|
|
83
|
+
const progressLine = lines.find((l) => l.includes("← completed"));
|
|
84
|
+
expect(progressLine).not.toContain("不该出现在这里");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("原因文本压成单行并截断到 120 字符", async () => {
|
|
88
|
+
const long = "x".repeat(200);
|
|
89
|
+
const events: StreamEvent[] = [{ type: "error", message: long }];
|
|
90
|
+
const { manager, lines } = makeManager({ events, status: "failed" });
|
|
91
|
+
await manager.send(manager.primary, "hi");
|
|
92
|
+
|
|
93
|
+
const progressLine = lines.find((l) => l.includes("← failed"))!;
|
|
94
|
+
const suffix = progressLine.split(" · ").at(-1)!;
|
|
95
|
+
expect(suffix.length).toBeLessThanOrEqual(120);
|
|
96
|
+
expect(suffix.endsWith("…")).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
9
100
|
describe("createAgentSession", () => {
|
|
10
101
|
describe("history()", () => {
|
|
11
102
|
it("新线 get() 是空数组;commit 之后同一条线的 get() 能看见", () => {
|
package/src/context/session.ts
CHANGED
|
@@ -91,6 +91,8 @@ export interface SessionDeps {
|
|
|
91
91
|
flags: Record<string, unknown>;
|
|
92
92
|
signal: AbortSignal;
|
|
93
93
|
log(msg: string): void;
|
|
94
|
+
/** 路径推导出的实验 id(经 send ctx 透给 adapter,见 AgentContext.experimentId)。 */
|
|
95
|
+
experimentId?: string;
|
|
94
96
|
/** tracing agent 的 OTLP 端点(经 send ctx 透给 adapter,用于注入导出 env)。 */
|
|
95
97
|
telemetry?: Telemetry;
|
|
96
98
|
/** 非沙箱 tracing agent 的共享 OTLP 通道(runner 从 run 级池取,经它做逐轮 span 归属)。 */
|
|
@@ -139,6 +141,7 @@ export class SessionManager {
|
|
|
139
141
|
model: this.deps.model,
|
|
140
142
|
reasoningEffort: this.deps.reasoningEffort,
|
|
141
143
|
flags: this.deps.flags,
|
|
144
|
+
experimentId: this.deps.experimentId,
|
|
142
145
|
sandbox: this.deps.sandbox,
|
|
143
146
|
session,
|
|
144
147
|
telemetry: this.deps.telemetry,
|
|
@@ -181,8 +184,9 @@ export class SessionManager {
|
|
|
181
184
|
|
|
182
185
|
const tok = (turn.usage?.inputTokens ?? 0) + (turn.usage?.outputTokens ?? 0);
|
|
183
186
|
const tools = turn.events.filter((e) => e.type === "action.called").length;
|
|
187
|
+
const reason = turn.status === "failed" ? failureReason(turn.events) : undefined;
|
|
184
188
|
this.deps.log(
|
|
185
|
-
`${turnLabel} ← ${turn.status} · ${t("session.tools", { count: tools })} · ${tok} tok · ${Math.round((Date.now() - t0) / 1000)}s`,
|
|
189
|
+
`${turnLabel} ← ${turn.status} · ${t("session.tools", { count: tools })} · ${tok} tok · ${Math.round((Date.now() - t0) / 1000)}s${reason ? ` · ${reason}` : ""}`,
|
|
186
190
|
);
|
|
187
191
|
return turn;
|
|
188
192
|
}
|
|
@@ -218,6 +222,27 @@ export class SessionManager {
|
|
|
218
222
|
}
|
|
219
223
|
}
|
|
220
224
|
|
|
225
|
+
/**
|
|
226
|
+
* 失败轮的进度行原因摘要:取本轮事件流里最后一个 `type: "error"` 事件的 message
|
|
227
|
+
* (与 TurnHandle.expectOk() / src/agents/shared.ts 的 diagnoseFailure 同一口径——
|
|
228
|
+
* 都认「最后一条 error 事件」为本轮失败的权威原因),压成单行并截断,避免 402/超时
|
|
229
|
+
* 这类关键信息只能事后翻落盘的 result.json 才看得到。提不到时返回 undefined,调用方不补空后缀。
|
|
230
|
+
*/
|
|
231
|
+
function failureReason(events: readonly StreamEvent[]): string | undefined {
|
|
232
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
233
|
+
const e = events[i];
|
|
234
|
+
if (e.type === "error") return truncateOneLine(e.message, 120);
|
|
235
|
+
}
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** 单行截断,口径同 src/runner/reporters/table.ts 的 truncateOneLine(折叠空白 + 120 字符上限)。 */
|
|
240
|
+
function truncateOneLine(s: string, width: number): string {
|
|
241
|
+
const clean = s.replace(/\s+/g, " ").trim();
|
|
242
|
+
if (clean.length <= width) return clean;
|
|
243
|
+
return `${clean.slice(0, width - 1)}…`;
|
|
244
|
+
}
|
|
245
|
+
|
|
221
246
|
export function lastAssistantText(events: readonly StreamEvent[]): string | undefined {
|
|
222
247
|
for (let i = events.length - 1; i >= 0; i--) {
|
|
223
248
|
const e = events[i];
|
package/src/context/types.ts
CHANGED
|
@@ -156,7 +156,7 @@ export interface SandboxHandle {
|
|
|
156
156
|
downloadFile(path: string): Promise<Buffer>;
|
|
157
157
|
/** 把一段内容上传成沙箱里的单个文件。 */
|
|
158
158
|
uploadFile(path: string, content: Buffer): Promise<void>;
|
|
159
|
-
/**
|
|
159
|
+
/** 沙箱 provider 分配的实例 id,用于排查 / 关联日志。 */
|
|
160
160
|
readonly sandboxId: string;
|
|
161
161
|
/** 相对 git 基线的最终 diff 视图(test() 跑完、finalize 前才落定)。 */
|
|
162
162
|
readonly diff: DiffView;
|
|
@@ -268,7 +268,7 @@ export interface TestContext {
|
|
|
268
268
|
newSession(): SessionHandle;
|
|
269
269
|
|
|
270
270
|
// 运行上下文
|
|
271
|
-
/** 本次 attempt 的中止信号;超时 /
|
|
271
|
+
/** 本次 attempt 的中止信号;超时 / 首过即停 / 用户 Ctrl-C 时触发,传给 adapter 的长耗时调用做取消。 */
|
|
272
272
|
readonly signal: AbortSignal;
|
|
273
273
|
/** 本次 attempt 使用的模型名(由 experiment/CLI flag 决定);省略即 agent 原生默认,不代表「无模型」。 */
|
|
274
274
|
readonly model?: string;
|
|
@@ -278,10 +278,10 @@ export interface TestContext {
|
|
|
278
278
|
readonly flags: Readonly<Record<string, unknown>>;
|
|
279
279
|
/** 打一行调试日志;有 live 进度回调时走该回调,否则落到 stderr,不出现在最终结果里。 */
|
|
280
280
|
log(msg: string): void;
|
|
281
|
-
/** 立即中止本 eval 并标记为 skipped(
|
|
281
|
+
/** 立即中止本 eval 并标记为 skipped(verdict / EvalResult.skipReason),reason 不能为空。 */
|
|
282
282
|
skip(reason: string): never;
|
|
283
283
|
|
|
284
|
-
//
|
|
284
|
+
// 值断言
|
|
285
285
|
/**
|
|
286
286
|
* 对任意值跑一个 ValueAssertion,返回可链 `.gate()` / `.atLeast()` 的 AssertionHandle。
|
|
287
287
|
* 打分延迟到 eval 结束后统一 finalize,调用本身同步、不抛错——不通过只是记一条失败断言,
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// SandboxSpec 链式钩子(.setup()/.teardown())的构造期契约:不可变、多次追加按顺序累加。
|
|
2
|
+
// 执行顺序(setup 正序 / teardown 逆序 / LIFO cleanup)是 runner 的事,见
|
|
3
|
+
// test/e2e-sandbox-hooks.test.ts;这里只测 dockerSandbox()/vercelSandbox()/e2bSandbox()/
|
|
4
|
+
// defineSandbox() 这四个工厂产出的 spec 对象本身的构造行为。
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { dockerSandbox, e2bSandbox, vercelSandbox, defineSandbox } from "./define.ts";
|
|
7
|
+
import type { AgentSetup, AgentTeardown } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
const noopSetup: AgentSetup = () => {};
|
|
10
|
+
const noopTeardown: AgentTeardown = () => {};
|
|
11
|
+
|
|
12
|
+
describe("sandbox factories: .setup()/.teardown() chain", () => {
|
|
13
|
+
it("dockerSandbox() starts with empty hook arrays", () => {
|
|
14
|
+
const spec = dockerSandbox({ image: "custom:latest" });
|
|
15
|
+
expect(spec.provider).toBe("docker");
|
|
16
|
+
expect(spec.image).toBe("custom:latest");
|
|
17
|
+
expect(spec.setupHooks).toEqual([]);
|
|
18
|
+
expect(spec.teardownHooks).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it(".setup() returns a new spec and does not mutate the original", () => {
|
|
22
|
+
const base = dockerSandbox();
|
|
23
|
+
const withSetup = base.setup(noopSetup);
|
|
24
|
+
expect(withSetup).not.toBe(base);
|
|
25
|
+
expect(base.setupHooks).toEqual([]);
|
|
26
|
+
expect(withSetup.setupHooks).toEqual([noopSetup]);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("multiple .setup() calls accumulate in append order", () => {
|
|
30
|
+
const a: AgentSetup = () => {};
|
|
31
|
+
const b: AgentSetup = () => {};
|
|
32
|
+
const spec = dockerSandbox().setup(a).setup(b);
|
|
33
|
+
expect(spec.setupHooks).toEqual([a, b]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("multiple .teardown() calls accumulate in append order (execution order is the runner's job)", () => {
|
|
37
|
+
const x: AgentTeardown = () => {};
|
|
38
|
+
const y: AgentTeardown = () => {};
|
|
39
|
+
const spec = dockerSandbox().teardown(x).teardown(y);
|
|
40
|
+
expect(spec.teardownHooks).toEqual([x, y]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("chaining preserves provider-specific fields and stacks across calls", () => {
|
|
44
|
+
const spec = dockerSandbox({ image: "img" }).setup(noopSetup).teardown(noopTeardown).setup(noopSetup);
|
|
45
|
+
expect(spec.provider).toBe("docker");
|
|
46
|
+
expect(spec.image).toBe("img");
|
|
47
|
+
expect(spec.setupHooks).toHaveLength(2);
|
|
48
|
+
expect(spec.teardownHooks).toHaveLength(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("vercelSandbox() / e2bSandbox() chain the same way and keep their own fields", () => {
|
|
52
|
+
const vercel = vercelSandbox({ snapshotId: "snap-1" }).setup(noopSetup);
|
|
53
|
+
expect(vercel.provider).toBe("vercel");
|
|
54
|
+
expect(vercel.snapshotId).toBe("snap-1");
|
|
55
|
+
expect(vercel.setupHooks).toEqual([noopSetup]);
|
|
56
|
+
|
|
57
|
+
const e2b = e2bSandbox({ template: "niceeval-agents" }).teardown(noopTeardown);
|
|
58
|
+
expect(e2b.provider).toBe("e2b");
|
|
59
|
+
expect(e2b.template).toBe("niceeval-agents");
|
|
60
|
+
expect(e2b.teardownHooks).toEqual([noopTeardown]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("defineSandbox() (custom provider) chains too and keeps create()/name", async () => {
|
|
64
|
+
const create = async () => {
|
|
65
|
+
throw new Error("not called in this test");
|
|
66
|
+
};
|
|
67
|
+
const spec = defineSandbox({ name: "my-provider", create }).setup(noopSetup).setup(noopSetup);
|
|
68
|
+
expect(spec.provider).toBe("my-provider");
|
|
69
|
+
expect(spec.create).toBe(create);
|
|
70
|
+
expect(spec.setupHooks).toHaveLength(2);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("each chain call produces an independent object (no shared mutable state)", () => {
|
|
74
|
+
const base = dockerSandbox();
|
|
75
|
+
const other: AgentSetup = () => {};
|
|
76
|
+
const branchA = base.setup(noopSetup);
|
|
77
|
+
const branchB = base.setup(other);
|
|
78
|
+
expect(branchA.setupHooks).toEqual([noopSetup]);
|
|
79
|
+
expect(branchB.setupHooks).toEqual([other]);
|
|
80
|
+
expect(branchA.setupHooks).not.toBe(branchB.setupHooks);
|
|
81
|
+
});
|
|
82
|
+
});
|