niceeval 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INDEX.md +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +69 -7
- package/dist/context/types.d.ts +32 -12
- package/dist/i18n/en.d.ts +54 -0
- package/dist/i18n/zh-CN.d.ts +57 -3
- package/dist/o11y/types.d.ts +16 -2
- package/dist/report/aggregate.d.ts +32 -24
- package/dist/report/aggregate.js +158 -50
- package/dist/report/built-in/index.d.ts +2 -0
- package/dist/report/built-in/index.js +8 -0
- package/dist/report/components.d.ts +93 -160
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +87 -81
- package/dist/report/compute.js +597 -417
- package/dist/report/flag.d.ts +32 -6
- package/dist/report/flag.js +92 -4
- package/dist/report/format.d.ts +19 -11
- package/dist/report/format.js +30 -13
- package/dist/report/index.d.ts +16 -16
- package/dist/report/index.js +20 -21
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +57 -33
- package/dist/report/locale.js +122 -56
- package/dist/report/metrics.d.ts +23 -4
- package/dist/report/metrics.js +110 -25
- package/dist/report/primitives.d.ts +48 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +9 -7
- package/dist/report/react/AttemptList.js +17 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +4 -4
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +10 -0
- package/dist/report/react/ExperimentComparison.js +12 -0
- package/dist/report/react/ExperimentList.d.ts +4 -3
- package/dist/report/react/ExperimentList.js +17 -18
- package/dist/report/react/MetricBars.js +5 -4
- package/dist/report/react/MetricLine.js +12 -5
- package/dist/report/react/MetricMatrix.js +1 -1
- package/dist/report/react/MetricScatter.js +59 -28
- package/dist/report/react/MetricTable.js +2 -12
- package/dist/report/react/ScopeSummary.d.ts +10 -0
- package/dist/report/react/ScopeSummary.js +17 -0
- package/dist/report/react/Scoreboard.js +6 -6
- package/dist/report/react/cell.js +2 -2
- package/dist/report/react/chart-math.d.ts +23 -6
- package/dist/report/react/chart-math.js +71 -19
- package/dist/report/react/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +110 -147
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -16
- package/dist/report/report.js +259 -28
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +253 -184
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +38 -7
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +247 -284
- package/dist/report/types.js +4 -3
- package/dist/report/web.d.ts +21 -5
- package/dist/report/web.js +42 -16
- package/dist/results/select.d.ts +38 -16
- package/dist/results/select.js +73 -25
- package/dist/results/types.d.ts +49 -14
- package/dist/runner/feedback/sink.d.ts +110 -0
- package/dist/runner/types.d.ts +513 -22
- package/dist/sandbox/docker.d.ts +23 -2
- package/dist/sandbox/e2b.d.ts +15 -1
- package/dist/sandbox/errors.d.ts +30 -3
- package/dist/sandbox/io-retry.d.ts +17 -0
- package/dist/sandbox/registry.d.ts +2 -0
- package/dist/sandbox/resolve.d.ts +18 -5
- package/dist/sandbox/retry.d.ts +11 -1
- package/dist/sandbox/types.d.ts +39 -5
- package/dist/sandbox/vercel.d.ts +7 -1
- package/dist/scoring/coverage.d.ts +30 -0
- package/dist/scoring/display.d.ts +21 -0
- package/dist/scoring/display.js +120 -0
- package/dist/scoring/types.d.ts +103 -20
- package/dist/shared/aggregate.d.ts +4 -2
- package/dist/shared/aggregate.js +8 -7
- package/dist/shared/types.d.ts +28 -0
- package/dist/tty-line.d.ts +0 -4
- package/dist/util.d.ts +23 -0
- package/docs-site/zh/README.md +44 -0
- package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
- package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
- package/docs-site/zh/examples/index.mdx +50 -0
- package/docs-site/zh/{concepts → explanation}/adapter.mdx +31 -13
- package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
- package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
- package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
- package/docs-site/zh/{concepts → explanation}/overview.mdx +11 -11
- package/docs-site/zh/{guides → explanation}/runner.mdx +18 -8
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +35 -33
- package/docs-site/zh/{guides → how-to}/authoring.mdx +35 -2
- package/docs-site/zh/{guides → how-to}/ci-integration.mdx +23 -12
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +47 -21
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +34 -39
- package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +25 -3
- package/docs-site/zh/{guides → how-to}/experiments.mdx +12 -5
- package/docs-site/zh/how-to/publish-report.mdx +105 -0
- package/docs-site/zh/{guides → how-to}/reporters.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +56 -7
- package/docs-site/zh/how-to/sandbox-providers.mdx +350 -0
- package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +4 -4
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +82 -39
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +6 -4
- package/docs-site/zh/{guides → how-to}/write-send.mdx +30 -14
- package/docs-site/zh/index.mdx +24 -26
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +32 -5
- package/docs-site/zh/reference/capabilities.mdx +8 -8
- package/docs-site/zh/reference/cli.mdx +40 -12
- package/docs-site/zh/reference/define-agent.mdx +58 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +42 -9
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/reference/expect.mdx +26 -1
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +32 -8
- package/docs-site/zh/{guides → reference}/report-components.mdx +45 -33
- package/docs-site/zh/{guides → reference}/results-data.mdx +21 -13
- package/docs-site/zh/troubleshooting/debug-sandbox.mdx +57 -0
- package/docs-site/zh/troubleshooting/debugging.mdx +212 -0
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +10 -2
- package/src/agents/ai-sdk-otel.test.ts +1 -0
- package/src/agents/ai-sdk.test.ts +3 -0
- package/src/agents/ai-sdk.ts +3 -0
- package/src/agents/bub-install-spec.test.ts +34 -0
- package/src/agents/bub-install-spec.ts +32 -0
- package/src/agents/bub.ts +31 -32
- package/src/agents/claude-code.test.ts +130 -9
- package/src/agents/claude-code.ts +76 -4
- package/src/agents/codex.test.ts +189 -40
- package/src/agents/codex.ts +155 -14
- package/src/agents/coding-cli-versions.test.ts +15 -0
- package/src/agents/coding-cli-versions.ts +3 -0
- package/src/agents/index.ts +13 -2
- package/src/agents/langgraph.test.ts +204 -0
- package/src/agents/langgraph.ts +495 -0
- package/src/agents/marketplace.ts +85 -0
- package/src/agents/native-config.test.ts +179 -0
- package/src/agents/native-config.ts +267 -0
- package/src/agents/openai-compat.test.ts +1 -0
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/openclaw.test.ts +31 -0
- package/src/agents/openclaw.ts +171 -0
- package/src/agents/plugin-config.test.ts +1 -0
- package/src/agents/sdk-streams.test.ts +79 -0
- package/src/agents/sdk-streams.ts +55 -10
- package/src/agents/skills.test.ts +1 -0
- package/src/agents/streaming.test.ts +3 -9
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +71 -8
- package/src/agents/ui-message-stream.test.ts +3 -0
- package/src/cli.ts +446 -124
- package/src/context/context.test.ts +51 -12
- package/src/context/context.ts +162 -30
- package/src/context/session.test.ts +2 -1
- package/src/context/session.ts +115 -7
- package/src/context/types.ts +30 -12
- package/src/define.test.ts +13 -8
- package/src/define.ts +25 -4
- package/src/expect/index.ts +53 -23
- package/src/i18n/en.ts +81 -17
- package/src/i18n/zh-CN.ts +80 -17
- package/src/o11y/cost.test.ts +1 -0
- package/src/o11y/execution-tree.test.ts +1 -20
- package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
- package/src/o11y/otlp/parse.test.ts +1 -0
- package/src/o11y/otlp/turn-otel.test.ts +1 -0
- package/src/o11y/parsers/bub.test.ts +1 -0
- package/src/o11y/parsers/claude-code.test.ts +1 -34
- package/src/o11y/parsers/openclaw.test.ts +154 -0
- package/src/o11y/parsers/openclaw.ts +310 -0
- package/src/o11y/prices.json +746 -311
- package/src/o11y/tool-names.test.ts +1 -0
- package/src/o11y/types.ts +16 -2
- package/src/report/aggregate.ts +178 -61
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -279
- package/src/report/compute.ts +723 -491
- package/src/report/dual-render.test.tsx +741 -1024
- package/src/report/flag.ts +104 -12
- package/src/report/format.ts +32 -12
- package/src/report/index.ts +119 -46
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +136 -65
- package/src/report/metrics.ts +108 -25
- package/src/report/primitives.tsx +196 -45
- package/src/report/react/AttemptList.tsx +30 -43
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +73 -0
- package/src/report/react/ExperimentList.tsx +50 -32
- package/src/report/react/MetricBars.tsx +5 -4
- package/src/report/react/MetricLine.tsx +13 -8
- package/src/report/react/MetricMatrix.tsx +2 -2
- package/src/report/react/MetricScatter.tsx +86 -34
- package/src/report/react/MetricTable.tsx +4 -76
- package/src/report/react/ScopeSummary.tsx +86 -0
- package/src/report/react/Scoreboard.tsx +28 -10
- package/src/report/react/cell.tsx +2 -2
- package/src/report/react/chart-math.test.ts +85 -0
- package/src/report/react/chart-math.ts +101 -22
- package/src/report/react/enhance.js +89 -5
- package/src/report/react/fixtures.ts +114 -154
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +138 -158
- package/src/report/react/styles.css +243 -82
- package/src/report/report.test.ts +779 -841
- package/src/report/report.ts +423 -41
- package/src/report/text/faces.ts +290 -193
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +44 -7
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +261 -271
- package/src/report/web.ts +63 -20
- package/src/results/annotated-source.test.ts +62 -9
- package/src/results/annotated-source.ts +64 -6
- package/src/results/attempt-evidence.test.ts +13 -11
- package/src/results/attempt-evidence.ts +20 -13
- package/src/results/attempt-source.ts +6 -3
- package/src/results/copy.ts +150 -60
- package/src/results/host-equivalence.test.ts +34 -20
- package/src/results/index.ts +12 -4
- package/src/results/locator.test.ts +1 -22
- package/src/results/open.ts +15 -5
- package/src/results/publish.ts +149 -0
- package/src/results/results.test.ts +89 -54
- package/src/results/select.ts +104 -34
- package/src/results/truncate.ts +90 -0
- package/src/results/types.ts +43 -14
- package/src/results/writer.ts +31 -13
- package/src/runner/attempt.test.ts +138 -7
- package/src/runner/attempt.ts +603 -104
- package/src/runner/discover.test.ts +47 -0
- package/src/runner/discover.ts +36 -2
- package/src/runner/eval-source.test.ts +1 -27
- package/src/runner/feedback/agent.test.ts +504 -0
- package/src/runner/feedback/agent.ts +409 -0
- package/src/runner/feedback/ci.test.ts +562 -0
- package/src/runner/feedback/ci.ts +401 -0
- package/src/runner/feedback/coordinator.test.ts +317 -0
- package/src/runner/feedback/coordinator.ts +397 -0
- package/src/runner/feedback/failure.ts +40 -0
- package/src/runner/feedback/human.test.ts +616 -0
- package/src/runner/feedback/human.ts +535 -0
- package/src/runner/feedback/index.ts +66 -0
- package/src/runner/feedback/io.ts +78 -0
- package/src/runner/feedback/profile.test.ts +50 -0
- package/src/runner/feedback/profile.ts +58 -0
- package/src/runner/feedback/reducer.test.ts +395 -0
- package/src/runner/feedback/reducer.ts +260 -0
- package/src/runner/feedback/renderer.ts +82 -0
- package/src/runner/feedback/sink.ts +203 -0
- package/src/runner/feedback/testing.ts +106 -0
- package/src/runner/ledger.test.ts +230 -0
- package/src/runner/ledger.ts +329 -0
- package/src/runner/report.test.ts +128 -3
- package/src/runner/report.ts +33 -9
- package/src/runner/reporters/artifacts.ts +8 -2
- package/src/runner/reporters/braintrust.test.ts +8 -7
- package/src/runner/reporters/braintrust.ts +9 -2
- package/src/runner/reporters/index.ts +2 -2
- package/src/runner/reporters/json.test.ts +162 -0
- package/src/runner/reporters/json.ts +35 -8
- package/src/runner/reporters/shared.ts +1 -5
- package/src/runner/run.test.ts +760 -3
- package/src/runner/run.ts +243 -37
- package/src/runner/sandbox-prep.ts +3 -42
- package/src/runner/timing.ts +158 -0
- package/src/runner/types.ts +518 -22
- package/src/sandbox/checkpoint.test.ts +55 -0
- package/src/sandbox/checkpoint.ts +29 -8
- package/src/sandbox/cli-commands.ts +407 -0
- package/src/sandbox/docker.ts +115 -16
- package/src/sandbox/e2b-agent-template.test.ts +56 -0
- package/src/sandbox/e2b-agent-template.ts +94 -0
- package/src/sandbox/e2b.ts +74 -9
- package/src/sandbox/errors.ts +111 -4
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/io-retry.test.ts +58 -0
- package/src/sandbox/io-retry.ts +45 -0
- package/src/sandbox/keep-registry.test.ts +86 -0
- package/src/sandbox/keep-registry.ts +142 -0
- package/src/sandbox/keep.ts +178 -0
- package/src/sandbox/paths.test.ts +1 -0
- package/src/sandbox/paths.ts +19 -8
- package/src/sandbox/registry.ts +20 -3
- package/src/sandbox/resolve.ts +76 -11
- package/src/sandbox/retry.test.ts +70 -0
- package/src/sandbox/retry.ts +46 -4
- package/src/sandbox/types.ts +44 -6
- package/src/sandbox/vercel.ts +43 -20
- package/src/scoring/collector.ts +60 -17
- package/src/scoring/coverage.ts +95 -0
- package/src/scoring/diff.ts +81 -0
- package/src/scoring/display.test.ts +121 -0
- package/src/scoring/display.ts +133 -0
- package/src/scoring/evidence.test.ts +189 -0
- package/src/scoring/judge.test.ts +142 -0
- package/src/scoring/judge.ts +15 -18
- package/src/scoring/scoped.ts +217 -50
- package/src/scoring/types.ts +117 -20
- package/src/scoring/verdict.ts +16 -4
- package/src/shared/aggregate.ts +8 -6
- package/src/shared/types.ts +31 -0
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +127 -56
- package/src/show/render.ts +662 -131
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +320 -54
- package/src/tty-line.ts +8 -26
- package/src/util.test.ts +1 -0
- package/src/util.ts +41 -0
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +423 -11
- package/src/view/app/components/CodeView.tsx +41 -14
- package/src/view/app/components/CopyControls.tsx +2 -2
- package/src/view/app/i18n.ts +37 -17
- package/src/view/app/lib/attempt-route.test.ts +1 -0
- package/src/view/app/lib/verdict.ts +7 -9
- package/src/view/app/main.tsx +13 -8
- package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
- package/src/view/app/types.ts +4 -1
- package/src/view/artifact-serving.test.ts +2 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +17 -17
- package/src/view/data.test.ts +10 -3
- package/src/view/data.ts +155 -49
- package/src/view/index.ts +56 -41
- package/src/view/server.ts +37 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +227 -0
- package/src/view/view-report.test.ts +167 -62
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -1
- package/dist/report/built-ins/experiment-comparison.js +0 -13
- package/dist/report/built-ins/index.d.ts +0 -1
- package/dist/report/built-ins/index.js +0 -2
- package/dist/report/react/GroupSummary.d.ts +0 -8
- package/dist/report/react/GroupSummary.js +0 -8
- package/dist/report/react/RunOverview.d.ts +0 -8
- package/dist/report/react/RunOverview.js +0 -12
- package/docs-site/zh/example/ai-agent-application.mdx +0 -152
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
- package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
- package/docs-site/zh/example/showcase.mdx +0 -39
- package/docs-site/zh/guides/publish-report.mdx +0 -91
- package/docs-site/zh/guides/sandbox-providers.mdx +0 -102
- package/src/report/built-in-user-parity.test.tsx +0 -640
- package/src/report/built-ins/experiment-comparison.tsx +0 -19
- package/src/report/built-ins/index.ts +0 -2
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- package/src/runner/reporters/console.ts +0 -70
- package/src/runner/reporters/live.test.ts +0 -56
- package/src/runner/reporters/live.ts +0 -247
- package/src/runner/reporters/quiet.test.ts +0 -66
- package/src/runner/reporters/quiet.ts +0 -49
- package/src/runner/reporters/table.ts +0 -277
- /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
- /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
package/src/cli.ts
CHANGED
|
@@ -5,23 +5,36 @@
|
|
|
5
5
|
// niceeval clean 删除 .niceeval/ 历史运行 artifact
|
|
6
6
|
|
|
7
7
|
import { spawn } from "node:child_process";
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
8
9
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
9
10
|
import { existsSync } from "node:fs";
|
|
10
|
-
import { join, relative } from "node:path";
|
|
11
|
+
import { join, relative, resolve as resolvePath } from "node:path";
|
|
11
12
|
import { pathToFileURL } from "node:url";
|
|
12
13
|
import { parseArgs as nodeParseArgs } from "node:util";
|
|
13
14
|
import { discoverEvals, discoverExperiments, makeFilter } from "./runner/discover.ts";
|
|
14
15
|
import { runEvals, type AgentRun } from "./runner/run.ts";
|
|
15
|
-
import { runWho } from "./runner/types.ts";
|
|
16
16
|
import { planCarry } from "./runner/fingerprint.ts";
|
|
17
|
+
import { failureDetailFromResult } from "./runner/feedback/failure.ts";
|
|
17
18
|
import { stopAllSandboxes, liveSandboxCount } from "./sandbox/registry.ts";
|
|
18
19
|
import { evalLevelStats } from "./shared/verdict.ts";
|
|
19
20
|
import { sandboxRecommendedConcurrency } from "./sandbox/resolve.ts";
|
|
20
|
-
import { Console as ConsoleReporter } from "./runner/reporters/console.ts";
|
|
21
|
-
import { Quiet as QuietReporter } from "./runner/reporters/quiet.ts";
|
|
22
21
|
import { Json, JUnit } from "./runner/reporters/json.ts";
|
|
23
|
-
import { Live as LiveReporter, type LiveRow } from "./runner/reporters/live.ts";
|
|
24
22
|
import { Artifacts as ArtifactsReporter } from "./runner/reporters/artifacts.ts";
|
|
23
|
+
import {
|
|
24
|
+
resolveOutputProfile,
|
|
25
|
+
createFeedbackCoordinator,
|
|
26
|
+
createNodeFeedbackIO,
|
|
27
|
+
createHumanRenderer,
|
|
28
|
+
createAgentRenderer,
|
|
29
|
+
createCiRenderer,
|
|
30
|
+
renderAgentPlanEnvelope,
|
|
31
|
+
renderHumanDryPlan,
|
|
32
|
+
renderCiDryPlan,
|
|
33
|
+
computeCiExitCode,
|
|
34
|
+
reportActivity,
|
|
35
|
+
type OutputProfileFlag,
|
|
36
|
+
type AgentPlanRow,
|
|
37
|
+
} from "./runner/feedback/index.ts";
|
|
25
38
|
import {
|
|
26
39
|
buildView,
|
|
27
40
|
startViewServer,
|
|
@@ -38,7 +51,17 @@ import { ReportLoadError } from "../dist/report/load.js";
|
|
|
38
51
|
import { runShow } from "./show/index.ts";
|
|
39
52
|
import { t } from "./i18n/index.ts";
|
|
40
53
|
import { formatThrown, upsertManagedBlock } from "./util.ts";
|
|
41
|
-
import type {
|
|
54
|
+
import type {
|
|
55
|
+
CompletionStatus,
|
|
56
|
+
Config,
|
|
57
|
+
DiscoveredExperiment,
|
|
58
|
+
ReporterError,
|
|
59
|
+
ReporterRegistration,
|
|
60
|
+
RunCompletion,
|
|
61
|
+
RunFeedbackPlan,
|
|
62
|
+
RunFeedbackState,
|
|
63
|
+
RunSummary,
|
|
64
|
+
} from "./types.ts";
|
|
42
65
|
|
|
43
66
|
/**
|
|
44
67
|
* view 的可预期用户错误:版本不同的报告(npx 提示)、位置参数/组合语义错误、
|
|
@@ -60,7 +83,8 @@ interface Flags {
|
|
|
60
83
|
timeout?: number;
|
|
61
84
|
earlyExit?: boolean;
|
|
62
85
|
dry: boolean;
|
|
63
|
-
|
|
86
|
+
/** 反馈 profile,已解析/校验(`auto` 默认值也算已解析——具体环境判定见 `resolveOutputProfile`)。 */
|
|
87
|
+
output: OutputProfileFlag;
|
|
64
88
|
force: boolean;
|
|
65
89
|
strict: boolean;
|
|
66
90
|
budget?: number;
|
|
@@ -73,15 +97,24 @@ interface Flags {
|
|
|
73
97
|
help: boolean;
|
|
74
98
|
version: boolean;
|
|
75
99
|
// ── show 专属(位置参数仍是 eval id 前缀 / `@<locator>`;这些 flag 选「怎么看」)──
|
|
76
|
-
|
|
100
|
+
source: boolean;
|
|
77
101
|
execution: boolean;
|
|
78
102
|
diff: boolean;
|
|
79
103
|
/** --diff=<路径>(必须 = 连写;空格形式会把路径当 eval id 前缀,按文档如此)。 */
|
|
80
104
|
diffPath?: string;
|
|
105
|
+
timing?: "summary" | "full";
|
|
106
|
+
keepSandbox?: "failed" | "all";
|
|
107
|
+
all: boolean;
|
|
108
|
+
allowSensitiveArtifacts: boolean;
|
|
109
|
+
window?: string;
|
|
110
|
+
sandboxPath?: string;
|
|
111
|
+
leaveRunning: boolean;
|
|
81
112
|
history: boolean;
|
|
82
113
|
experiment?: string;
|
|
83
|
-
|
|
114
|
+
results?: string;
|
|
115
|
+
snapshot?: string;
|
|
84
116
|
report?: string;
|
|
117
|
+
page?: string;
|
|
85
118
|
}
|
|
86
119
|
|
|
87
120
|
// 表驱动的 flag 定义(node:util parseArgs)。--no-x 显式声明,不依赖 allowNegative(需 Node 20.14+,
|
|
@@ -103,6 +136,16 @@ const FLAG_OPTIONS = {
|
|
|
103
136
|
timeout: { type: "string" },
|
|
104
137
|
/** 整次运行的预算上限(美元)。 */
|
|
105
138
|
budget: { type: "string" },
|
|
139
|
+
/** `exp` 命令专用:跑完留下 failed/errored attempt 的沙箱现场(= `--keep-sandbox=failed`);`--keep-sandbox=all` 连 passed 也留。事后用 `niceeval sandbox list/enter/stop` 查看与销毁。 */
|
|
140
|
+
"keep-sandbox": { type: "boolean" },
|
|
141
|
+
/** `sandbox stop` 专用:销毁全部留存沙箱。 */
|
|
142
|
+
all: { type: "boolean" },
|
|
143
|
+
/** `sandbox diff` 专用:只看某个 send 窗口(如 `--window s1/t2`);省略输出全部窗口的串联视图。 */
|
|
144
|
+
window: { type: "string" },
|
|
145
|
+
/** `sandbox diff` 专用:只看某个文件的 patch;省略输出该窗口的全部文件。 */
|
|
146
|
+
path: { type: "string" },
|
|
147
|
+
/** `sandbox enter` 专用:shell 退出后让现场保持运行,不送回休眠。 */
|
|
148
|
+
"leave-running": { type: "boolean" },
|
|
106
149
|
/** 只运行带有该 tag 的 eval(见 `defineEval` 的 `tags`)。 */
|
|
107
150
|
tag: { type: "string" },
|
|
108
151
|
/** 额外写一份 JUnit XML 报告到指定路径,供 CI 消费。 */
|
|
@@ -113,29 +156,37 @@ const FLAG_OPTIONS = {
|
|
|
113
156
|
out: { type: "string" },
|
|
114
157
|
/** `view` 命令专用:指定本地服务器监听端口。 */
|
|
115
158
|
port: { type: "string" },
|
|
116
|
-
|
|
159
|
+
/** `view --out` 专用:对非发布根(快照没有 publish:{redaction:"applied"} 标记)导出时的显式确认——静态站会原样携带未消毒的证据文件。 */
|
|
160
|
+
"allow-sensitive-artifacts": { type: "boolean" },
|
|
161
|
+
// show 的证据切面 / 时间轴 / 报告装载(docs-site/zh/how-to/viewing-results.mdx)。
|
|
117
162
|
// 证据切面只认 `@<locator>`(或收窄到单个 eval 的前缀)选出的那一个 attempt——不再有
|
|
118
163
|
// 数字 `--attempt`,选哪个 attempt 由 locator 精确指名,不是「先选 eval 再挑第几次」。
|
|
119
164
|
/** `show` 命令专用:该 attempt 运行时保存的 Eval 源码,gate/soft 断言标回源码行(证据切面)。 */
|
|
120
|
-
|
|
165
|
+
source: { type: "boolean" },
|
|
121
166
|
/** `show` 命令专用:该 attempt 的标准执行事件流(消息、thinking、Skill load、工具调用/结果);有 OTel 时同一节点补时间(证据切面)。 */
|
|
122
167
|
execution: { type: "boolean" },
|
|
168
|
+
/** `show` 命令专用:整个 Attempt 的统一时间树;裸 `--timing` 给有界诊断投影,`--timing=full` 逐节点展开全部 runner/已关联 OTel 节点。 */
|
|
169
|
+
timing: { type: "boolean" },
|
|
123
170
|
// --diff 是布尔;--diff=<路径> 在 parseArgs 前预扫成 diffPath(路径必须 = 连写,
|
|
124
171
|
// 空格形式的下一个 token 仍是位置参数 = eval id 前缀,与文档一致)。
|
|
125
172
|
/** `show` 命令专用:sandbox 里的文件改动摘要;`--diff=<文件路径>` 看单个文件的完整改动(路径必须 `=` 连写)。 */
|
|
126
173
|
diff: { type: "boolean" },
|
|
127
|
-
/** `show`
|
|
174
|
+
/** `show` 命令专用:执行时间轴——对匹配的每个 experiment × eval 分节,逐 attempt 列时间 / verdict / 摘要 / 耗时 / 成本 / locator;与 `--report` 互斥。 */
|
|
128
175
|
history: { type: "boolean" },
|
|
129
|
-
/** `show` / `view`
|
|
176
|
+
/** `show` / `view` 命令专用:按路径段前缀收窄 experiment;组名会选中组内全部配置。 */
|
|
130
177
|
experiment: { type: "string" },
|
|
131
|
-
/** `show` / `view`
|
|
132
|
-
|
|
133
|
-
/** `
|
|
178
|
+
/** `show` / `view` / `sandbox enter|list|stop` 共用:结果根目录(`.niceeval` 之外的另一个根,如 `copySnapshots` 产出的发布根)。 */
|
|
179
|
+
results: { type: "string" },
|
|
180
|
+
/** `view` 命令专用:只打开这一份快照文件(`snapshot.json`);文件不可读时命令失败(扫描模式只跳过)。 */
|
|
181
|
+
snapshot: { type: "string" },
|
|
182
|
+
/** `show` / `view` 命令专用:用文件默认导出的 `defineReport(...)` 替换两者共用的默认报告。 */
|
|
134
183
|
report: { type: "string" },
|
|
135
|
-
/**
|
|
184
|
+
/** `show` / `view` 命令专用:选择多页报告的页(页 id 见 `show --report` 的页索引);`view` 里定初始页。 */
|
|
185
|
+
page: { type: "string" },
|
|
186
|
+
/** 只打印本次会匹配到的 eval × 运行配置,不实际执行(按下面 `--output` 选中的 profile 给出预览)。 */
|
|
136
187
|
dry: { type: "boolean" },
|
|
137
|
-
/**
|
|
138
|
-
|
|
188
|
+
/** 反馈 profile:`auto`(默认)按环境自动选择,`human` / `agent` / `ci` 强制指定;只改变终端展示,不改变选择、调度、判定、artifact 或退出码。`auto` 依次判定:stderr 是 TTY → human;否则 `CI`(或其它常见 CI 平台环境变量)存在 → ci;否则 → agent。 */
|
|
189
|
+
output: { type: "string" },
|
|
139
190
|
/** 忽略上次运行结果,不跳过已通过的 (experiment, eval) 组合,强制全部重跑。 */
|
|
140
191
|
force: { type: "boolean" },
|
|
141
192
|
/** CI 中推荐使用:让软阈值(`soft`)失败也计入整条 eval 的 verdict。 */
|
|
@@ -162,17 +213,50 @@ function numberFlag(name: string, raw: string | undefined): number | undefined {
|
|
|
162
213
|
return n;
|
|
163
214
|
}
|
|
164
215
|
|
|
216
|
+
const OUTPUT_PROFILE_VALUES = ["auto", "human", "agent", "ci"] as const;
|
|
217
|
+
|
|
218
|
+
/** `--output` 解析仅接受 `auto|human|agent|ci`;非法值直接报清晰用法并退出,不静默回退到某个默认值。 */
|
|
219
|
+
function outputFlag(raw: string | undefined): OutputProfileFlag {
|
|
220
|
+
if (raw === undefined) return "auto";
|
|
221
|
+
if ((OUTPUT_PROFILE_VALUES as readonly string[]).includes(raw)) return raw as OutputProfileFlag;
|
|
222
|
+
process.stderr.write(t("cli.flag.invalidOutput", { value: raw }));
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
|
|
165
226
|
function parseArgs(argv: string[]): { command: string; positionals: string[]; flags: Flags } {
|
|
166
227
|
if (argv[0] === "--") argv = argv.slice(1);
|
|
167
228
|
|
|
168
229
|
// --diff=<路径> 预扫:diff 本体是布尔(裸 --diff = 文件级摘要),路径只接受 = 连写。
|
|
169
230
|
let diffPath: string | undefined;
|
|
231
|
+
// --keep-sandbox[=failed|all] 预扫:本体是布尔(裸 = failed 档),档位只接受 = 连写。
|
|
232
|
+
let keepSandboxTier: "failed" | "all" | undefined;
|
|
233
|
+
// --timing[=summary|full] 预扫:node:util 的单个 option 不支持 boolean|string 联合,
|
|
234
|
+
// 所以 mode 在严格 parseArgs 前提取,再把两种形式统一成布尔 --timing。
|
|
235
|
+
let timingMode: "summary" | "full" | undefined;
|
|
170
236
|
argv = argv.map((arg) => {
|
|
171
237
|
if (arg.startsWith("--diff=")) {
|
|
172
238
|
const path = arg.slice("--diff=".length);
|
|
173
239
|
if (path) diffPath = path;
|
|
174
240
|
return "--diff";
|
|
175
241
|
}
|
|
242
|
+
if (arg.startsWith("--keep-sandbox=")) {
|
|
243
|
+
const tier = arg.slice("--keep-sandbox=".length);
|
|
244
|
+
if (tier !== "failed" && tier !== "all") {
|
|
245
|
+
process.stderr.write(`--keep-sandbox only accepts "failed" (default) or "all", got "${tier}".\n`);
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
keepSandboxTier = tier;
|
|
249
|
+
return "--keep-sandbox";
|
|
250
|
+
}
|
|
251
|
+
if (arg.startsWith("--timing=")) {
|
|
252
|
+
const mode = arg.slice("--timing=".length);
|
|
253
|
+
if (mode !== "summary" && mode !== "full") {
|
|
254
|
+
process.stderr.write(`--timing only accepts "summary" (default) or "full", got "${mode}".\n`);
|
|
255
|
+
process.exit(1);
|
|
256
|
+
}
|
|
257
|
+
timingMode = mode;
|
|
258
|
+
return "--timing";
|
|
259
|
+
}
|
|
176
260
|
return arg;
|
|
177
261
|
});
|
|
178
262
|
|
|
@@ -188,7 +272,7 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
|
|
|
188
272
|
}
|
|
189
273
|
|
|
190
274
|
// 第一个位置参数若是已知命令,则为命令;其余是 eval id 前缀 / view 输入。
|
|
191
|
-
const commands = new Set(["exp", "show", "list", "view", "clean", "init", "watch", "run"]);
|
|
275
|
+
const commands = new Set(["exp", "show", "list", "view", "clean", "init", "watch", "run", "sandbox"]);
|
|
192
276
|
let command = "run";
|
|
193
277
|
let positionals = rawPositionals;
|
|
194
278
|
if (rawPositionals[0] && commands.has(rawPositionals[0])) {
|
|
@@ -209,25 +293,61 @@ function parseArgs(argv: string[]): { command: string; positionals: string[]; fl
|
|
|
209
293
|
out: values.out as string | undefined,
|
|
210
294
|
port: numberFlag("port", values.port as string | undefined),
|
|
211
295
|
dry: values.dry === true,
|
|
212
|
-
|
|
296
|
+
output: outputFlag(values.output as string | undefined),
|
|
213
297
|
force: values.force === true,
|
|
214
298
|
strict: values.strict === true,
|
|
215
299
|
earlyExit: values["no-early-exit"] === true ? false : values["early-exit"] === true ? true : undefined,
|
|
216
300
|
open: values["no-open"] === true ? false : values.open === true ? true : undefined,
|
|
217
301
|
help: values.help === true,
|
|
218
302
|
version: values.version === true,
|
|
219
|
-
|
|
303
|
+
source: values.source === true,
|
|
220
304
|
execution: values.execution === true,
|
|
221
305
|
diff: values.diff === true && diffPath === undefined,
|
|
222
306
|
diffPath,
|
|
307
|
+
timing: values.timing === true ? (timingMode ?? "summary") : undefined,
|
|
308
|
+
keepSandbox: values["keep-sandbox"] === true ? (keepSandboxTier ?? "failed") : undefined,
|
|
309
|
+
all: values.all === true,
|
|
310
|
+
allowSensitiveArtifacts: values["allow-sensitive-artifacts"] === true,
|
|
311
|
+
window: values.window as string | undefined,
|
|
312
|
+
sandboxPath: values.path as string | undefined,
|
|
313
|
+
leaveRunning: values["leave-running"] === true,
|
|
223
314
|
history: values.history === true,
|
|
224
315
|
experiment: values.experiment as string | undefined,
|
|
225
|
-
|
|
316
|
+
results: values.results as string | undefined,
|
|
317
|
+
snapshot: values.snapshot as string | undefined,
|
|
226
318
|
report: values.report as string | undefined,
|
|
319
|
+
page: values.page as string | undefined,
|
|
227
320
|
};
|
|
228
321
|
return { command, positionals, flags };
|
|
229
322
|
}
|
|
230
323
|
|
|
324
|
+
/**
|
|
325
|
+
* exp 只接受两类输入:位置参数选「跑哪些 eval」+ 调度/输出/机器出口 flag 选「对着哪个 agent、
|
|
326
|
+
* 怎么跑」。show / view 专属的证据切面(`--source`/`--execution`/`--diff`)、时间轴(`--history`)、
|
|
327
|
+
* Scope 收窄(`--experiment`/`--results`)、报告装载(`--report`/`--page`)、查看器
|
|
328
|
+
* (`--snapshot`/`--out`/`--port`/`--open`)不能被 exp 静默忽略(见 docs/feature/experiments/
|
|
329
|
+
* cli.md「用法错误」)。返回第一个被误用的 flag 及其归属命令(用于报错),没有误用返回 undefined。
|
|
330
|
+
*/
|
|
331
|
+
function firstViewerOnlyFlag(flags: Flags): { flag: string; command: string } | undefined {
|
|
332
|
+
const SHOW = "show";
|
|
333
|
+
const BOTH = "show / view";
|
|
334
|
+
const VIEW = "view";
|
|
335
|
+
if (flags.source) return { flag: "--source", command: SHOW };
|
|
336
|
+
if (flags.execution) return { flag: "--execution", command: SHOW };
|
|
337
|
+
if (flags.timing !== undefined) return { flag: "--timing", command: SHOW };
|
|
338
|
+
if (flags.diff || flags.diffPath !== undefined) return { flag: "--diff", command: SHOW };
|
|
339
|
+
if (flags.history) return { flag: "--history", command: SHOW };
|
|
340
|
+
if (flags.experiment !== undefined) return { flag: "--experiment", command: BOTH };
|
|
341
|
+
if (flags.results !== undefined) return { flag: "--results", command: BOTH };
|
|
342
|
+
if (flags.report !== undefined) return { flag: "--report", command: BOTH };
|
|
343
|
+
if (flags.page !== undefined) return { flag: "--page", command: BOTH };
|
|
344
|
+
if (flags.snapshot !== undefined) return { flag: "--snapshot", command: VIEW };
|
|
345
|
+
if (flags.out !== undefined) return { flag: "--out", command: VIEW };
|
|
346
|
+
if (flags.port !== undefined) return { flag: "--port", command: VIEW };
|
|
347
|
+
if (flags.open !== undefined) return { flag: "--open", command: VIEW };
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
|
|
231
351
|
/** 调度项的环境变量层(标志 > 环境变量 > config > 默认,见 docs/cli.md「配置优先级」)。 */
|
|
232
352
|
function envNumber(name: string): number | undefined {
|
|
233
353
|
const raw = process.env[name]?.trim();
|
|
@@ -283,7 +403,7 @@ const AGENT_RULES_CONTENT = [
|
|
|
283
403
|
"the bundled Chinese docs are the authoritative version matching this installation.",
|
|
284
404
|
"After a run, drill into failures with `niceeval show` — pick an `@<locator>` from the",
|
|
285
405
|
"compact index it prints, then `niceeval show @<locator>` for a compact overview, or add",
|
|
286
|
-
"`--
|
|
406
|
+
"`--source` / `--execution` / `--diff` for evidence; the snapshot directories the CLI prints",
|
|
287
407
|
"are the structured source of truth: `snapshot.json` holds the run's metadata and each",
|
|
288
408
|
"`<evalId>/a<attempt>/result.json` holds that attempt's verdict and assertions, next to",
|
|
289
409
|
"its artifact files (`events.json` / `trace.json` / `diff.json`).",
|
|
@@ -357,6 +477,76 @@ function evalsFilterFromExperiment(
|
|
|
357
477
|
return (id) => expFilter(id) && patternFilter(id);
|
|
358
478
|
}
|
|
359
479
|
|
|
480
|
+
/**
|
|
481
|
+
* evals 过滤器的指纹(进 ExperimentRunInfo.evalFilterFingerprint,供「配置没变」判断):
|
|
482
|
+
* 数组按内容、函数按函数体哈希;CLI 追加的位置参数前缀一并计入。不存过滤器本身——
|
|
483
|
+
* 求值结果在 selectedEvalIds(见 runEvals)。
|
|
484
|
+
*/
|
|
485
|
+
function fingerprintEvalsFilter(evals: DiscoveredExperiment["evals"], patterns: string[]): string {
|
|
486
|
+
const basis =
|
|
487
|
+
evals === undefined || evals === "*"
|
|
488
|
+
? "*"
|
|
489
|
+
: Array.isArray(evals)
|
|
490
|
+
? JSON.stringify([...evals].sort())
|
|
491
|
+
: evals.toString();
|
|
492
|
+
return createHash("sha256").update(JSON.stringify({ basis, patterns })).digest("hex").slice(0, 16);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* run 结束后把 coordinator 累计的诊断折成 `RunCompletion`(见 docs/feature/experiments/cli.md
|
|
497
|
+
* 「运行完成状态不只看 verdict 计数」)。只读已经真实发生过的诊断,不额外发明信号:
|
|
498
|
+
* - `"interrupted"` 诊断只在 run.ts 判定为真·中断(Effect exit 真实标记中断,不是「signal 被
|
|
499
|
+
* abort 过」这种更弱的信号)时才会出现,见 `runner/run.ts` 的 `reportInterrupted()` 调用点。
|
|
500
|
+
* - `"budget-exhausted:<experimentId>"` 诊断的 `count` 就是该 experiment 因预算耗尽未派发的
|
|
501
|
+
* attempt 数(见 `runner/feedback/reducer.ts` 对 `budget-exhausted` 事件的注释),跨
|
|
502
|
+
* experiment 求和得到 `unstarted`。
|
|
503
|
+
* - `"reporter-error:<reporter>"` 诊断转成 `ReporterError[]`;`required` 字段来自事件自带的
|
|
504
|
+
* `data.required`,直接反映这个 reporter 注册时的真实 required/best-effort 分类(见上面
|
|
505
|
+
* 构造 `reporters: ReporterRegistration[]` 的地方——artifacts / --json / --junit 恒
|
|
506
|
+
* `required: true`,`config.reporters` 恒 `false`),不是一个统一写死的占位值。
|
|
507
|
+
* - `earlyExitUnstarted` 从反馈状态的 attempt:early-exit 计数派生(减去 fail-fast 的那部分——
|
|
508
|
+
* 那是「未完整覆盖」,进 unstarted,不是「省下的重复验证」)。
|
|
509
|
+
*/
|
|
510
|
+
function assembleRunCompletion(state: RunFeedbackState): RunCompletion {
|
|
511
|
+
let unstarted = 0;
|
|
512
|
+
let failFastSkipped = 0;
|
|
513
|
+
let interrupted = false;
|
|
514
|
+
const reporterErrors: ReporterError[] = [];
|
|
515
|
+
for (const d of state.diagnostics) {
|
|
516
|
+
if (d.key === "interrupted") {
|
|
517
|
+
interrupted = true;
|
|
518
|
+
} else if (d.key.startsWith("budget-exhausted:")) {
|
|
519
|
+
unstarted += d.count;
|
|
520
|
+
} else if (d.key.startsWith("fail-fast:")) {
|
|
521
|
+
// run 级 fail-fast 造成的未派发同样计入 unstarted(结论落 incomplete,见
|
|
522
|
+
// docs/feature/experiments/architecture.md「Completion 与退出」)。
|
|
523
|
+
unstarted += d.count;
|
|
524
|
+
failFastSkipped += d.count;
|
|
525
|
+
} else if (d.key.startsWith("reporter-error:")) {
|
|
526
|
+
// required 决定这条错误是否写进 RunCompletion.reporterErrors 并让 completion 非 complete
|
|
527
|
+
// (见 docs/cli.md「required reporter」);best-effort reporter 的失败只保留为 diagnostic。
|
|
528
|
+
if (d.data?.required !== true) continue;
|
|
529
|
+
reporterErrors.push({
|
|
530
|
+
reporter: typeof d.data?.reporter === "string" ? d.data.reporter : d.key.slice("reporter-error:".length),
|
|
531
|
+
required: true,
|
|
532
|
+
message: d.message,
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
// 中断造成的未派发(仍在 queued 的 attempt)同样计入 unstarted(见 docs/feature/experiments/
|
|
537
|
+
// architecture.md「Completion 与退出」:budget 耗尽、fail-fast 或中断造成的未派发都不伪装成全绿)。
|
|
538
|
+
if (interrupted) unstarted += state.queued;
|
|
539
|
+
// attempt:early-exit 计数含 fail-fast 的未派发(反馈层同一事件驱动计数守恒);
|
|
540
|
+
// 「省下的重复验证」= 总数减去 fail-fast 那部分。
|
|
541
|
+
const earlyExitUnstarted = Math.max(0, state.earlyExitSkipped - failFastSkipped);
|
|
542
|
+
const status: CompletionStatus = interrupted
|
|
543
|
+
? "interrupted"
|
|
544
|
+
: unstarted > 0 || reporterErrors.length > 0
|
|
545
|
+
? "incomplete"
|
|
546
|
+
: "complete";
|
|
547
|
+
return { status, unstarted, earlyExitUnstarted, reporterErrors };
|
|
548
|
+
}
|
|
549
|
+
|
|
360
550
|
/** package.json 的 version 字段;-v/--version 直接回显这个号。 */
|
|
361
551
|
async function packageVersion(): Promise<string> {
|
|
362
552
|
const raw = await readFile(new URL("../package.json", import.meta.url), "utf-8");
|
|
@@ -380,11 +570,17 @@ async function main(): Promise<void> {
|
|
|
380
570
|
}
|
|
381
571
|
|
|
382
572
|
if (command === "view") {
|
|
383
|
-
//
|
|
384
|
-
//
|
|
573
|
+
// 位置参数只有一种含义:eval id 前缀(收窄报告槽 Scope)。结果根经 --results 递入,
|
|
574
|
+
// 单开一份快照经 --snapshot 递入;--report 整槽替换报告槽(与 show --report 吃同一个文件),
|
|
575
|
+
// --page 定初始页。文件与目录都不进位置参数(docs/feature/reports/view.md「打开与收窄」)。
|
|
576
|
+
// --out 与位置参数 / --experiment 的互斥在 buildView 内校验(单点,报错文案含 copySnapshots
|
|
577
|
+
// + filter 的下一步),经 exitOnViewUserError 按用法错误退出。
|
|
385
578
|
let viewInput: { input?: string; patterns: string[] };
|
|
386
579
|
try {
|
|
387
|
-
viewInput = resolveViewInput(cwd, positionals,
|
|
580
|
+
viewInput = resolveViewInput(cwd, positionals, {
|
|
581
|
+
...(flags.results !== undefined ? { results: flags.results } : {}),
|
|
582
|
+
...(flags.snapshot !== undefined ? { snapshot: flags.snapshot } : {}),
|
|
583
|
+
});
|
|
388
584
|
} catch (e) {
|
|
389
585
|
exitOnViewUserError(e);
|
|
390
586
|
}
|
|
@@ -392,9 +588,10 @@ async function main(): Promise<void> {
|
|
|
392
588
|
patterns: viewInput.patterns,
|
|
393
589
|
...(flags.experiment !== undefined ? { experiment: flags.experiment } : {}),
|
|
394
590
|
...(flags.report !== undefined ? { report: { path: flags.report, cwd } } : {}),
|
|
591
|
+
...(flags.page !== undefined ? { page: flags.page } : {}),
|
|
395
592
|
};
|
|
396
593
|
if (flags.out) {
|
|
397
|
-
const out = await buildView({ input: viewInput.input, out: flags.out, scan }).catch(exitOnViewUserError);
|
|
594
|
+
const out = await buildView({ input: viewInput.input, out: flags.out, allowSensitiveArtifacts: flags.allowSensitiveArtifacts, scan }).catch(exitOnViewUserError);
|
|
398
595
|
process.stdout.write(t("cli.view.exportedDir", { out }));
|
|
399
596
|
process.exit(0);
|
|
400
597
|
}
|
|
@@ -410,17 +607,34 @@ async function main(): Promise<void> {
|
|
|
410
607
|
await new Promise(() => {});
|
|
411
608
|
}
|
|
412
609
|
|
|
610
|
+
if (command === "sandbox") {
|
|
611
|
+
// sandbox 命令组不读 niceeval.config.ts、不发现 eval:只操作留存注册表与 provider 的
|
|
612
|
+
// detached 能力(见 docs/feature/sandbox/cli.md)。
|
|
613
|
+
const { runSandboxCommand } = await import("./sandbox/cli-commands.ts");
|
|
614
|
+
const code = await runSandboxCommand(cwd, positionals, {
|
|
615
|
+
all: flags.all,
|
|
616
|
+
window: flags.window,
|
|
617
|
+
path: flags.sandboxPath,
|
|
618
|
+
leaveRunning: flags.leaveRunning,
|
|
619
|
+
// CLI flag 是 --results(结果根);sandbox 命令组的内部选项名保持 run,值语义相同。
|
|
620
|
+
run: flags.results,
|
|
621
|
+
});
|
|
622
|
+
process.exit(code);
|
|
623
|
+
}
|
|
624
|
+
|
|
413
625
|
if (command === "show") {
|
|
414
|
-
// show 不依赖 niceeval.config.ts:读的是 .niceeval/(或 --
|
|
626
|
+
// show 不依赖 niceeval.config.ts:读的是 .niceeval/(或 --results 指定的结果根)的落盘结果。
|
|
415
627
|
const code = await runShow(cwd, positionals, {
|
|
416
|
-
|
|
628
|
+
source: flags.source,
|
|
417
629
|
execution: flags.execution,
|
|
630
|
+
timing: flags.timing,
|
|
418
631
|
diff: flags.diff,
|
|
419
632
|
diffPath: flags.diffPath,
|
|
420
633
|
history: flags.history,
|
|
421
634
|
experiment: flags.experiment,
|
|
422
|
-
|
|
635
|
+
results: flags.results,
|
|
423
636
|
report: flags.report,
|
|
637
|
+
page: flags.page,
|
|
424
638
|
});
|
|
425
639
|
process.exit(code);
|
|
426
640
|
}
|
|
@@ -453,15 +667,26 @@ async function main(): Promise<void> {
|
|
|
453
667
|
}
|
|
454
668
|
|
|
455
669
|
const agentRuns: AgentRun[] = [];
|
|
670
|
+
let experimentSelection = t("cli.all");
|
|
671
|
+
let availableExperimentGroups = t("cli.none");
|
|
456
672
|
|
|
457
673
|
if (command === "exp") {
|
|
458
674
|
if (flags.agent || flags.model) {
|
|
459
675
|
process.stderr.write(t("cli.exp.agentModelFlagUnsupported"));
|
|
460
676
|
process.exit(1);
|
|
461
677
|
}
|
|
678
|
+
const viewerFlag = firstViewerOnlyFlag(flags);
|
|
679
|
+
if (viewerFlag) {
|
|
680
|
+
process.stderr.write(t("cli.exp.viewerFlagUnsupported", { flag: viewerFlag.flag, command: viewerFlag.command }));
|
|
681
|
+
process.exit(1);
|
|
682
|
+
}
|
|
462
683
|
const experiments = await discoverExperiments(cwd);
|
|
463
684
|
const expArg = positionals[0];
|
|
464
685
|
const extraPatterns = positionals.slice(1);
|
|
686
|
+
experimentSelection = positionals.join(" ") || t("cli.all");
|
|
687
|
+
availableExperimentGroups = [...new Set(experiments.map((experiment) => experiment.group || experiment.id))]
|
|
688
|
+
.sort()
|
|
689
|
+
.join(", ") || t("cli.none");
|
|
465
690
|
const selected = expArg
|
|
466
691
|
? experiments.filter((e) => e.group === expArg || e.id === expArg || e.id.startsWith(expArg + "/"))
|
|
467
692
|
: experiments;
|
|
@@ -470,8 +695,21 @@ async function main(): Promise<void> {
|
|
|
470
695
|
arg: expArg ?? t("cli.all"),
|
|
471
696
|
experiments: experiments.map((e) => e.id).join(", ") || t("cli.none"),
|
|
472
697
|
}));
|
|
698
|
+
// show / view 是顶层命令。只有同名 experiment 确实不存在时才纠错,不能抢占合法 id。
|
|
699
|
+
if (expArg === "show" || expArg === "view") {
|
|
700
|
+
process.stderr.write(t("cli.experiment.viewerCommandHint", {
|
|
701
|
+
command: expArg,
|
|
702
|
+
args: extraPatterns.length > 0 ? ` ${extraPatterns.join(" ")}` : "",
|
|
703
|
+
}));
|
|
704
|
+
}
|
|
473
705
|
process.exit(1);
|
|
474
706
|
}
|
|
707
|
+
// 残留提醒:注册表里还有上次留下的沙箱时打一行(不阻塞、不清理)。
|
|
708
|
+
{
|
|
709
|
+
const { keptSandboxReminder } = await import("./sandbox/cli-commands.ts");
|
|
710
|
+
const reminder = await keptSandboxReminder(cwd).catch(() => undefined);
|
|
711
|
+
if (reminder) process.stderr.write(reminder);
|
|
712
|
+
}
|
|
475
713
|
for (const exp of selected) {
|
|
476
714
|
// 一个实验 = 一个配置(单 model)。跨模型对比写多个实验文件,各钉一个 model。
|
|
477
715
|
agentRuns.push({
|
|
@@ -486,6 +724,8 @@ async function main(): Promise<void> {
|
|
|
486
724
|
budget: flags.budget ?? envNumber("NICEEVAL_BUDGET") ?? exp.budget,
|
|
487
725
|
evalFilter: evalsFilterFromExperiment(exp.evals, extraPatterns),
|
|
488
726
|
experimentId: exp.id,
|
|
727
|
+
description: exp.description,
|
|
728
|
+
evalFilterFingerprint: fingerprintEvalsFilter(exp.evals, extraPatterns),
|
|
489
729
|
strict: flags.strict,
|
|
490
730
|
// 实验级并发上限:随 AgentRun 进调度器按实验单独限流(runner 两级信号量),
|
|
491
731
|
// 不再取所有选中实验的最小值钳全局——那会让一个串行实验拖慢整批基线。
|
|
@@ -513,69 +753,119 @@ async function main(): Promise<void> {
|
|
|
513
753
|
process.exit(1);
|
|
514
754
|
}
|
|
515
755
|
|
|
756
|
+
// profile 只改变反馈,不改变选择/调度/判定;显式值覆盖 auto 检测(见 resolveOutputProfile)。
|
|
757
|
+
// --dry 和真正开跑共用同一个已解析 profile。
|
|
758
|
+
const outputProfile = resolveOutputProfile({
|
|
759
|
+
explicit: flags.output,
|
|
760
|
+
isTTY: process.stderr.isTTY === true,
|
|
761
|
+
env: process.env,
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
// matchedByRun[i] 对应 agentRuns[i] 匹配到的 eval 集合;--dry 预览与真正开跑时的
|
|
765
|
+
// RunFeedbackPlan(总量、去重 eval 数)共用同一份计算,不重复过滤一遍。
|
|
766
|
+
const matchedByRun = agentRuns.map((run) => evals.filter((e) => run.evalFilter(e.id)));
|
|
767
|
+
const totalRuns = agentRuns.reduce((sum, run, i) => sum + matchedByRun[i]!.length * run.runs, 0);
|
|
768
|
+
const uniqueEvalIds = new Set(matchedByRun.flat().map((e) => e.id));
|
|
769
|
+
|
|
770
|
+
if (totalRuns === 0) {
|
|
771
|
+
process.stderr.write(t("cli.experiment.noEvalsSelected", {
|
|
772
|
+
selection: experimentSelection,
|
|
773
|
+
experiments: availableExperimentGroups,
|
|
774
|
+
}));
|
|
775
|
+
process.exit(1);
|
|
776
|
+
}
|
|
777
|
+
|
|
516
778
|
if (flags.dry) {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
779
|
+
// --dry 只按所选 profile 打印计划,不运行、不落盘 —— 三种 profile 各自的展示逻辑
|
|
780
|
+
// 都在 runner/feedback/{human,agent,ci}.ts 里,这里只负责拼数据、选函数、写流、退出。
|
|
781
|
+
if (outputProfile === "agent") {
|
|
782
|
+
const rows: AgentPlanRow[] = [];
|
|
783
|
+
for (let i = 0; i < agentRuns.length; i++) {
|
|
784
|
+
const run = agentRuns[i]!;
|
|
785
|
+
const label = run.experimentId ?? (run.model ? `${run.agent.name}/${run.model}` : run.agent.name);
|
|
786
|
+
for (const e of matchedByRun[i]!) rows.push({ label, evalId: e.id });
|
|
787
|
+
}
|
|
788
|
+
process.stdout.write(
|
|
789
|
+
renderAgentPlanEnvelope({
|
|
790
|
+
total: totalRuns,
|
|
791
|
+
evals: uniqueEvalIds.size,
|
|
792
|
+
configs: agentRuns.length,
|
|
793
|
+
runs: Math.max(1, ...agentRuns.map((r) => r.runs)),
|
|
794
|
+
rows,
|
|
795
|
+
}) + "\n",
|
|
796
|
+
);
|
|
797
|
+
} else if (outputProfile === "ci") {
|
|
798
|
+
process.stdout.write(
|
|
799
|
+
renderCiDryPlan({
|
|
800
|
+
total: totalRuns,
|
|
801
|
+
evals: uniqueEvalIds.size,
|
|
802
|
+
configs: agentRuns.length,
|
|
803
|
+
rows: agentRuns.map((run, i) => ({
|
|
804
|
+
experimentId: run.experimentId,
|
|
805
|
+
who: run.model ? `${run.agent.name}/${run.model}` : run.agent.name,
|
|
806
|
+
evalIds: matchedByRun[i]!.map((e) => e.id),
|
|
807
|
+
runs: run.runs,
|
|
808
|
+
})),
|
|
809
|
+
}),
|
|
810
|
+
);
|
|
811
|
+
} else {
|
|
812
|
+
process.stdout.write(
|
|
813
|
+
renderHumanDryPlan({
|
|
814
|
+
evals: evals.length,
|
|
815
|
+
configs: agentRuns.length,
|
|
816
|
+
rows: agentRuns.map((run, i) => ({
|
|
817
|
+
who: run.model ? `${run.agent.name}/${run.model}` : run.agent.name,
|
|
818
|
+
experimentSuffix: run.experimentId ? ` (exp ${run.experimentId})` : "",
|
|
819
|
+
evalIds: matchedByRun[i]!.map((e) => e.id),
|
|
820
|
+
runs: run.runs,
|
|
821
|
+
})),
|
|
822
|
+
}),
|
|
823
|
+
);
|
|
527
824
|
}
|
|
528
825
|
process.exit(0);
|
|
529
826
|
}
|
|
530
827
|
|
|
531
|
-
//
|
|
532
|
-
// (
|
|
533
|
-
//
|
|
534
|
-
//
|
|
535
|
-
// 显示的"携入"和 run.ts 实际调度的"携入"就会对不上。
|
|
828
|
+
// 提前算好携入计划:coordinator 的 plan 事件与 runEvals 内部
|
|
829
|
+
// 实际调度必须共用同一份 planCarry() 判断,否则两边各自算一遍,一旦不一致,dashboard/
|
|
830
|
+
// envelope 展示的"携入"就会和 run.ts 真实调度的"携入"对不上(见 memory 的
|
|
831
|
+
// live-carry-row-shows-waiting-forever)。
|
|
536
832
|
const priorResults = flags.force ? undefined : await loadLatestResultsPerEval(join(cwd, ".niceeval"));
|
|
537
833
|
const carryPlan = priorResults?.length ? await planCarry(evals, agentRuns, priorResults) : undefined;
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
834
|
+
const reusedFailures = (carryPlan?.carriedResults ?? [])
|
|
835
|
+
.map(failureDetailFromResult)
|
|
836
|
+
.filter((failure) => failure !== undefined);
|
|
542
837
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
const artifacts = ArtifactsReporter();
|
|
575
|
-
reporters.push( artifacts);
|
|
576
|
-
if (flags.junit) reporters.push(JUnit(flags.junit));
|
|
577
|
-
if (flags.json) reporters.push(Json(flags.json));
|
|
578
|
-
reporters.push(...(config.reporters ?? []));
|
|
838
|
+
// 无全局默认:并发上限由 sandbox provider 的推荐值决定(多个 agentRun 各有 sandbox 时取
|
|
839
|
+
// 最小值,最保守的 provider 决定上限)。同一个值既进 RunFeedbackPlan.shape,也传给 runEvals——
|
|
840
|
+
// 两处必须是同一个数字,dashboard 展示的并发上限不能和真实调度的并发上限对不上。
|
|
841
|
+
const sandboxRecs = agentRuns.map((r) => sandboxRecommendedConcurrency(r.sandbox));
|
|
842
|
+
const sandboxDefaultConcurrency = sandboxRecs.length > 0 ? Math.min(...sandboxRecs) : 10;
|
|
843
|
+
const maxConcurrency =
|
|
844
|
+
flags.maxConcurrency ??
|
|
845
|
+
envNumber("NICEEVAL_MAX_CONCURRENCY") ??
|
|
846
|
+
config.maxConcurrency ??
|
|
847
|
+
sandboxDefaultConcurrency;
|
|
848
|
+
|
|
849
|
+
const plan: RunFeedbackPlan = {
|
|
850
|
+
shape: { evals: uniqueEvalIds.size, configs: agentRuns.length, totalRuns, maxConcurrency },
|
|
851
|
+
reused: carryPlan?.carriedResults.length ?? 0,
|
|
852
|
+
reusedFailures,
|
|
853
|
+
};
|
|
854
|
+
|
|
855
|
+
// 一个 run 内只有一个终端协调者(见 docs/feature/experiments/cli.md「输出流和落盘节奏」):
|
|
856
|
+
// 三种 profile 各自的展示逻辑全部在 renderer 里,这里只按解析出的 profile 选一个构造好、
|
|
857
|
+
// 交给 coordinator。run:start 前(coordinator.start(plan) 之前)的一切都还没有活跃 sink,
|
|
858
|
+
// 出错走 bootstrap stderr;之后所有诊断都经它。
|
|
859
|
+
const io = createNodeFeedbackIO();
|
|
860
|
+
const commandLabel = ["niceeval", command, ...positionals].join(" ").trim();
|
|
861
|
+
const renderer =
|
|
862
|
+
outputProfile === "human"
|
|
863
|
+
? createHumanRenderer({ io, command: commandLabel })
|
|
864
|
+
: outputProfile === "agent"
|
|
865
|
+
? createAgentRenderer({ io })
|
|
866
|
+
: createCiRenderer({ io });
|
|
867
|
+
const coordinator = createFeedbackCoordinator({ profile: outputProfile, renderer, io });
|
|
868
|
+
coordinator.start(plan);
|
|
579
869
|
|
|
580
870
|
// Ctrl+C / kill 的三级响应,核心目标:任何情况下都不留下孤儿沙箱。
|
|
581
871
|
// 1 次:abort controller → runEvals 把它喂给 Effect signal → 各 attempt 的 Scope 跑 release
|
|
@@ -586,28 +876,29 @@ async function main(): Promise<void> {
|
|
|
586
876
|
// 3 次:真不耐烦了,硬退(此时多半已无可清理的)。
|
|
587
877
|
const ctrl = new AbortController();
|
|
588
878
|
let signalCount = 0;
|
|
589
|
-
// 兜底强清 + 退出:只跑一次,带超时(stopAllSandboxes 内每个 stop 各自有超时)
|
|
879
|
+
// 兜底强清 + 退出:只跑一次,带超时(stopAllSandboxes 内每个 stop 各自有超时)。先停 dashboard
|
|
880
|
+
// 的 tick/动态区域(coordinator.stopDynamic()),避免硬退时终端卡在半帧 ANSI 状态。
|
|
590
881
|
let forcing = false;
|
|
591
882
|
const forceCleanupAndExit = (code: number) => {
|
|
592
883
|
if (forcing) return;
|
|
593
884
|
forcing = true;
|
|
594
|
-
void stopAllSandboxes().finally(() => process.exit(code));
|
|
885
|
+
void Promise.all([coordinator.stopDynamic(), stopAllSandboxes()]).finally(() => process.exit(code));
|
|
595
886
|
};
|
|
596
887
|
for (const sig of ["SIGINT", "SIGTERM"] as const) {
|
|
597
888
|
process.on(sig, () => {
|
|
598
889
|
signalCount += 1;
|
|
599
890
|
if (signalCount === 1) {
|
|
600
|
-
|
|
891
|
+
reportActivity(t("cli.interruptCleanup").trimEnd());
|
|
601
892
|
ctrl.abort();
|
|
602
893
|
// 看门狗:graceful 清理 12s 还没让进程自己收口,就强清兜底。
|
|
603
894
|
setTimeout(() => {
|
|
604
895
|
if (liveSandboxCount() > 0) {
|
|
605
|
-
|
|
896
|
+
reportActivity(t("cli.fallbackCleanupTimeout").trimEnd());
|
|
606
897
|
forceCleanupAndExit(130);
|
|
607
898
|
}
|
|
608
899
|
}, 12_000).unref();
|
|
609
900
|
} else if (signalCount === 2) {
|
|
610
|
-
|
|
901
|
+
reportActivity(t("cli.forceCleanupExit").trimEnd());
|
|
611
902
|
forceCleanupAndExit(130);
|
|
612
903
|
} else {
|
|
613
904
|
process.exit(130); // 第三次:硬退
|
|
@@ -615,47 +906,78 @@ async function main(): Promise<void> {
|
|
|
615
906
|
});
|
|
616
907
|
}
|
|
617
908
|
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
sandboxDefaultConcurrency,
|
|
633
|
-
signal: ctrl.signal,
|
|
634
|
-
onProgress,
|
|
635
|
-
priorResults,
|
|
636
|
-
carryPlan,
|
|
909
|
+
// reporter 只剩正交的机器/artifact 出口:human/agent/ci 的展示完全由上面的 coordinator +
|
|
910
|
+
// renderer 负责,不再有 Console/Live/Quiet 这类兼职当 reporter 的展示层(见 docs 的
|
|
911
|
+
// 「CLI 只负责解析 profile、构造 coordinator/reporters、运行和退出」)。每个 reporter 在这里
|
|
912
|
+
// 按来源分类 required/best-effort(见 `ReporterRegistration` 的字段注释):默认落盘的
|
|
913
|
+
// artifacts、显式指定的 --json/--junit 是 agent/CI 读结果的唯一入口,写失败必须让
|
|
914
|
+
// completion/退出码判红;用户 `config.reporters` 只是补充观测,失败只折成一条 diagnostic,
|
|
915
|
+
// 不影响 completion。
|
|
916
|
+
const reporters: ReporterRegistration[] = [];
|
|
917
|
+
const artifacts = ArtifactsReporter();
|
|
918
|
+
reporters.push({ reporter: artifacts, name: "artifacts", required: true });
|
|
919
|
+
if (flags.junit) reporters.push({ reporter: JUnit(flags.junit), name: "junit", required: true, target: flags.junit });
|
|
920
|
+
if (flags.json) reporters.push({ reporter: Json(flags.json), name: "json", required: true, target: flags.json });
|
|
921
|
+
(config.reporters ?? []).forEach((reporter, i) => {
|
|
922
|
+
reporters.push({ reporter, name: `config-reporter-${i}`, required: false });
|
|
637
923
|
});
|
|
638
924
|
|
|
925
|
+
let summary: RunSummary;
|
|
926
|
+
try {
|
|
927
|
+
summary = await runEvals({
|
|
928
|
+
config,
|
|
929
|
+
evals,
|
|
930
|
+
agentRuns,
|
|
931
|
+
reporters,
|
|
932
|
+
maxConcurrency,
|
|
933
|
+
signal: ctrl.signal,
|
|
934
|
+
priorResults,
|
|
935
|
+
carryPlan,
|
|
936
|
+
keepSandbox: flags.keepSandbox,
|
|
937
|
+
niceevalRoot: resolvePath(cwd, ".niceeval"),
|
|
938
|
+
});
|
|
939
|
+
} catch (e) {
|
|
940
|
+
// 真崩溃前先撤下 dashboard,不让半帧 ANSI 状态和下面 main().catch 打印的错误交织。
|
|
941
|
+
await coordinator.stopDynamic();
|
|
942
|
+
throw e;
|
|
943
|
+
}
|
|
944
|
+
|
|
639
945
|
// 正常返回(含被中断后走部分汇总)后再兜一刀:Scope finalizer 没停掉的残留沙箱在这里强清。
|
|
640
946
|
// 跑顺利时登记表已空,是 no-op。
|
|
641
947
|
await stopAllSandboxes();
|
|
642
948
|
|
|
643
|
-
//
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
//
|
|
647
|
-
|
|
949
|
+
// completion 要先算好,--json/--junit 是否"这次真的写出"才有依据(见下)。
|
|
950
|
+
const completion = assembleRunCompletion(coordinator.state);
|
|
951
|
+
|
|
952
|
+
// --json/--junit 是正交机器出口,只在这次运行真的写出对应文件时才把路径交给 coordinator
|
|
953
|
+
// (它转发给 ci renderer 打印独立的 json=/junit= 行,见 docs「CI 怎么用」)。判据是
|
|
954
|
+
// completion.reporterErrors 里有没有这次 required reporter("json"/"junit")的失败记录——
|
|
955
|
+
// 不能用 existsSync 探测磁盘:atomicWriteFile(json.ts)失败时原地保留上一次运行遗留的旧文件,
|
|
956
|
+
// existsSync 只会看到"文件存在"就误判成这次写成功,把上一轮的陈旧内容当成本次结果打印出去。
|
|
957
|
+
const jsonPath = flags.json && !completion.reporterErrors.some((e) => e.reporter === "json") ? flags.json : undefined;
|
|
958
|
+
const junitPath =
|
|
959
|
+
flags.junit && !completion.reporterErrors.some((e) => e.reporter === "junit") ? flags.junit : undefined;
|
|
960
|
+
|
|
961
|
+
// agent 反馈闭环的入口:跑完直接给出每个已创建快照的目录,agent/ci 读 snapshot.json 与各
|
|
962
|
+
// attempt 的 result.json / artifact(events/trace/diff),不必解析人类向的流式输出。相对 cwd
|
|
963
|
+
// 的路径更友好;结果落在 cwd 外时(relative 路径以 .. 开头)原样打印绝对路径。打印本身由
|
|
964
|
+
// renderer 的 "saved" 处理完成,这里只负责把路径交给 coordinator。
|
|
965
|
+
const paths = artifacts.outputDirs().map(({ dir }) => {
|
|
648
966
|
const rel = relative(cwd, dir);
|
|
649
|
-
|
|
650
|
-
}
|
|
967
|
+
return rel && !rel.startsWith("..") ? rel : dir;
|
|
968
|
+
});
|
|
651
969
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
//
|
|
655
|
-
//
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
970
|
+
await coordinator.finish({ summary, completion, paths, json: jsonPath, junit: junitPath });
|
|
971
|
+
|
|
972
|
+
// 退出码统一走 CompletionStatus 驱动的语义(interrupted → 130、incomplete/required reporter
|
|
973
|
+
// 失败 → 1),不再只看 verdict 计数;三种 profile 共用同一套退出码,不是 ci 专属。failed/errored
|
|
974
|
+
// 先按 (experiment, eval) 折叠再喂给 computeCiExitCode——它只认 RunSummary 原始字段,不知道
|
|
975
|
+
// 「同一 eval 的重试轮不该重复计红」这条 eval 级判定规则(被 runs+earlyExit 重试吸收的失败,
|
|
976
|
+
// 先挂一次、后来过了,不该把进程判红,否则 CI 判定与 evalLevelStats 报表口径不一致;
|
|
977
|
+
// 见 memory 的 cli-exit-code-attempt-level-not-eval-level)。
|
|
978
|
+
const foldedStats = evalLevelStats(summary.results, (r) => `${r.experimentId ?? ""}|${r.id}`);
|
|
979
|
+
const exitCode = computeCiExitCode({ ...summary, failed: foldedStats.failed, errored: foldedStats.errored }, completion);
|
|
980
|
+
process.exit(exitCode);
|
|
659
981
|
}
|
|
660
982
|
|
|
661
983
|
main().catch(async (e) => {
|