niceeval 0.9.1 → 0.10.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INDEX.md +31 -33
- package/dist/agents/types.d.ts +20 -17
- package/dist/context/types.d.ts +21 -21
- package/dist/i18n/en.d.ts +10 -2
- package/dist/i18n/zh-CN.d.ts +14 -6
- package/dist/o11y/execution-tree.d.ts +103 -0
- package/dist/o11y/otlp/select.d.ts +22 -0
- package/dist/report/{react → assets}/colors.d.ts +8 -0
- package/dist/report/{react → assets}/colors.js +23 -0
- package/dist/report/built-in/index.d.ts +2 -2
- package/dist/report/built-in/index.js +2 -2
- package/dist/report/built-in/standard.d.ts +8 -1
- package/dist/report/built-in/standard.js +17 -7
- package/dist/report/components/attempt-detail/AttemptAssertions.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptAssertions.js +17 -0
- package/dist/report/components/attempt-detail/AttemptConversation.d.ts +10 -0
- package/dist/report/components/attempt-detail/AttemptConversation.js +74 -0
- package/dist/report/components/attempt-detail/AttemptDiagnostics.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptDiagnostics.js +7 -0
- package/dist/report/components/attempt-detail/AttemptDiff.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptDiff.js +11 -0
- package/dist/report/components/attempt-detail/AttemptError.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptError.js +16 -0
- package/dist/report/components/attempt-detail/AttemptFixPrompt.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptFixPrompt.js +7 -0
- package/dist/report/components/attempt-detail/AttemptSource.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptSource.js +97 -0
- package/dist/report/components/attempt-detail/AttemptSummary.d.ts +8 -0
- package/dist/report/components/attempt-detail/AttemptSummary.js +20 -0
- package/dist/report/components/attempt-detail/AttemptTimeline.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptTimeline.js +28 -0
- package/dist/report/components/attempt-detail/AttemptTrace.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptTrace.js +29 -0
- package/dist/report/components/attempt-detail/AttemptUsage.d.ts +6 -0
- package/dist/report/components/attempt-detail/AttemptUsage.js +20 -0
- package/dist/report/components/attempt-detail/compute.d.ts +24 -0
- package/dist/report/components/attempt-detail/compute.js +297 -0
- package/dist/report/components/attempt-detail/faces.d.ts +14 -0
- package/dist/report/components/attempt-detail/faces.js +239 -0
- package/dist/report/components/attempt-detail/index.d.ts +38 -0
- package/dist/report/components/attempt-detail/index.js +547 -0
- package/dist/report/{react → components}/cell.d.ts +5 -3
- package/dist/report/{react → components}/cell.js +3 -3
- package/dist/report/{react → components/entity-lists}/AttemptList.d.ts +8 -4
- package/dist/report/{react → components/entity-lists}/AttemptList.js +16 -10
- package/dist/report/{react → components/entity-lists}/EvalList.d.ts +3 -3
- package/dist/report/{react → components/entity-lists}/EvalList.js +0 -0
- package/dist/report/{react → components/entity-lists}/ExperimentList.d.ts +4 -5
- package/dist/report/{react → components/entity-lists}/ExperimentList.js +12 -11
- package/dist/report/components/entity-lists/compute.d.ts +23 -0
- package/dist/report/components/entity-lists/compute.js +171 -0
- package/dist/report/components/entity-lists/faces.d.ts +5 -0
- package/dist/report/components/entity-lists/faces.js +175 -0
- package/dist/report/components/entity-lists/index.d.ts +49 -0
- package/dist/report/components/entity-lists/index.js +169 -0
- package/dist/report/{react → components}/fixtures.d.ts +2 -2
- package/dist/report/{react → components}/fixtures.js +1 -1
- package/dist/report/{react → components/metric-views}/DeltaTable.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/DeltaTable.js +4 -4
- package/dist/report/{react → components/metric-views}/MetricBars.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricBars.js +3 -3
- package/dist/report/{react → components/metric-views}/MetricLine.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/MetricLine.js +8 -5
- package/dist/report/{react → components/metric-views}/MetricMatrix.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricMatrix.js +4 -4
- package/dist/report/components/metric-views/MetricScatter.d.ts +11 -0
- package/dist/report/{react → components/metric-views}/MetricScatter.js +27 -60
- package/dist/report/{react → components/metric-views}/MetricTable.d.ts +3 -3
- package/dist/report/{react → components/metric-views}/MetricTable.js +4 -4
- package/dist/report/{react → components/metric-views}/Scoreboard.d.ts +2 -2
- package/dist/report/{react → components/metric-views}/Scoreboard.js +3 -3
- package/dist/report/components/metric-views/compute.d.ts +89 -0
- package/dist/report/{compute.js → components/metric-views/compute.js} +32 -415
- package/dist/report/components/metric-views/faces.d.ts +13 -0
- package/dist/report/components/metric-views/faces.js +381 -0
- package/dist/report/components/metric-views/index.d.ts +50 -0
- package/dist/report/components/metric-views/index.js +273 -0
- package/dist/report/{text → components/metric-views}/plot.js +1 -1
- package/dist/report/components/shared-compute.d.ts +22 -0
- package/dist/report/components/shared-compute.js +47 -0
- package/dist/report/components/shared-faces.d.ts +9 -0
- package/dist/report/components/shared-faces.js +26 -0
- package/dist/report/components/shared.d.ts +68 -0
- package/dist/report/components/shared.js +125 -0
- package/dist/report/{react → components/site-components}/CopyFixPrompt.d.ts +2 -2
- package/dist/report/{react → components/site-components}/CopyFixPrompt.js +2 -2
- package/dist/report/{react → components/site-components}/HeroCard.d.ts +2 -2
- package/dist/report/{react → components/site-components}/HeroCard.js +2 -2
- package/dist/report/{react → components/site-components}/ScopeWarnings.d.ts +2 -2
- package/dist/report/{react → components/site-components}/ScopeWarnings.js +3 -3
- package/dist/report/{react → components/site-components}/TraceWaterfall.d.ts +3 -3
- package/dist/report/{react → components/site-components}/TraceWaterfall.js +5 -5
- package/dist/report/components/site-components/compute.d.ts +28 -0
- package/dist/report/components/site-components/compute.js +132 -0
- package/dist/report/components/site-components/faces.d.ts +21 -0
- package/dist/report/components/site-components/faces.js +75 -0
- package/dist/report/components/site-components/index.d.ts +74 -0
- package/dist/report/components/site-components/index.js +220 -0
- package/dist/report/{scope-warnings.d.ts → components/site-components/scope-warnings.d.ts} +2 -2
- package/dist/report/{scope-warnings.js → components/site-components/scope-warnings.js} +2 -2
- package/dist/report/{react → components/summaries}/ScopeSummary.d.ts +2 -2
- package/dist/report/{react → components/summaries}/ScopeSummary.js +17 -8
- package/dist/report/components/summaries/compute.d.ts +7 -0
- package/dist/report/components/summaries/compute.js +51 -0
- package/dist/report/components/summaries/faces.d.ts +7 -0
- package/dist/report/components/summaries/faces.js +38 -0
- package/dist/report/components/summaries/index.d.ts +27 -0
- package/dist/report/components/summaries/index.js +78 -0
- package/dist/report/definition/grid-layout.d.ts +50 -0
- package/dist/report/definition/grid-layout.js +89 -0
- package/dist/report/{primitives.d.ts → definition/primitives.d.ts} +34 -4
- package/dist/report/{primitives.js → definition/primitives.js} +101 -10
- package/dist/report/{report.d.ts → definition/report.d.ts} +36 -65
- package/dist/report/{report.js → definition/report.js} +58 -90
- package/dist/report/{text/table.d.ts → definition/table-text.d.ts} +2 -2
- package/dist/report/{text/table.js → definition/table-text.js} +2 -2
- package/dist/report/{tree.d.ts → definition/tree.d.ts} +43 -11
- package/dist/report/{tree.js → definition/tree.js} +9 -11
- package/dist/report/index.d.ts +33 -18
- package/dist/report/index.js +23 -14
- package/dist/report/{aggregate.d.ts → model/aggregate.d.ts} +12 -4
- package/dist/report/{aggregate.js → model/aggregate.js} +28 -8
- package/dist/report/{flag.d.ts → model/flag.d.ts} +14 -1
- package/dist/report/{flag.js → model/flag.js} +35 -0
- package/dist/report/{format.d.ts → model/format.d.ts} +13 -7
- package/dist/report/{format.js → model/format.js} +79 -9
- package/dist/report/{locale.d.ts → model/locale.d.ts} +17 -17
- package/dist/report/{locale.js → model/locale.js} +31 -33
- package/dist/report/{metrics.d.ts → model/metrics.d.ts} +2 -2
- package/dist/report/{metrics.js → model/metrics.js} +2 -2
- package/dist/report/{types.d.ts → model/types.d.ts} +154 -20
- package/dist/report/react/index.d.ts +32 -21
- package/dist/report/react/index.js +33 -21
- package/dist/report/{load.d.ts → runtime/load.d.ts} +1 -1
- package/dist/report/{load.js → runtime/load.js} +1 -1
- package/dist/report/runtime/text.d.ts +67 -0
- package/dist/report/runtime/text.js +114 -0
- package/dist/report/runtime/web.d.ts +35 -0
- package/dist/report/runtime/web.js +70 -0
- package/dist/results/annotated-source.d.ts +87 -0
- package/dist/results/attempt-evidence.d.ts +74 -0
- package/dist/results/attempt-source.d.ts +15 -0
- package/dist/results/select.d.ts +11 -1
- package/dist/results/select.js +27 -5
- package/dist/runner/eval-selection.d.ts +26 -0
- package/dist/runner/feedback/sink.d.ts +26 -1
- package/dist/runner/types.d.ts +151 -24
- package/dist/sandbox/docker.d.ts +6 -0
- package/dist/sandbox/e2b.d.ts +0 -1
- package/dist/sandbox/types.d.ts +23 -21
- package/dist/scoring/display.d.ts +7 -2
- package/dist/scoring/display.js +42 -4
- package/dist/scoring/types.d.ts +3 -3
- package/dist/shared/aggregate.d.ts +14 -0
- package/dist/shared/aggregate.js +31 -0
- package/dist/shared/types.d.ts +6 -1
- package/docs-site/images/logo-dark.svg +7 -0
- package/docs-site/images/logo.svg +6 -5
- package/docs-site/zh/README.md +8 -7
- package/docs-site/zh/examples/ai-agent-application.mdx +5 -5
- package/docs-site/zh/examples/coding-agent-extensions.mdx +4 -4
- package/docs-site/zh/examples/index.mdx +3 -3
- package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +6 -6
- package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
- package/docs-site/zh/examples/integrations/codex-sdk.mdx +3 -3
- package/docs-site/zh/examples/integrations/langgraph.mdx +3 -3
- package/docs-site/zh/examples/integrations/pi-sdk.mdx +2 -2
- package/docs-site/zh/explanation/adapter.mdx +19 -19
- package/docs-site/zh/explanation/assert.mdx +9 -9
- package/docs-site/zh/explanation/drive.mdx +4 -4
- package/docs-site/zh/explanation/evals.mdx +8 -8
- package/docs-site/zh/explanation/experiment.mdx +8 -8
- package/docs-site/zh/explanation/hitl.mdx +13 -13
- package/docs-site/zh/explanation/judge.mdx +3 -3
- package/docs-site/zh/explanation/overview.mdx +7 -7
- package/docs-site/zh/explanation/runner.mdx +17 -13
- package/docs-site/zh/explanation/tier.mdx +6 -6
- package/docs-site/zh/index.mdx +14 -14
- package/docs-site/zh/introduction.mdx +13 -16
- package/docs-site/zh/reference/builtin-agents.mdx +61 -28
- package/docs-site/zh/reference/capabilities.mdx +7 -27
- package/docs-site/zh/reference/cli.mdx +36 -33
- package/docs-site/zh/reference/define-agent.mdx +32 -31
- package/docs-site/zh/reference/define-config.mdx +6 -6
- package/docs-site/zh/reference/define-eval.mdx +33 -20
- package/docs-site/zh/reference/events.mdx +4 -4
- package/docs-site/zh/reference/expect.mdx +3 -3
- package/docs-site/zh/reference/official-adapters.mdx +17 -17
- package/docs-site/zh/reference/report-components.mdx +195 -121
- package/docs-site/zh/reference/results-data.mdx +13 -13
- package/docs-site/zh/troubleshooting/debug-sandbox.mdx +11 -11
- package/docs-site/zh/troubleshooting/debugging.mdx +40 -16
- package/docs-site/zh/{how-to → tutorials}/agent-feedback-loop.mdx +38 -38
- package/docs-site/zh/tutorials/agent-onboarding.mdx +100 -0
- package/docs-site/zh/tutorials/authoring.mdx +211 -0
- package/docs-site/zh/{how-to → tutorials}/ci-integration.mdx +8 -8
- package/docs-site/zh/{how-to → tutorials}/connect-otel.mdx +22 -22
- package/docs-site/zh/{how-to → tutorials}/connect-your-agent.mdx +58 -63
- package/docs-site/zh/tutorials/custom-reports.mdx +453 -0
- package/docs-site/zh/{how-to → tutorials}/dataset-fanout.mdx +4 -4
- package/docs-site/zh/tutorials/experiments.mdx +102 -0
- package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
- package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +6 -6
- package/docs-site/zh/tutorials/quickstart.mdx +18 -18
- package/docs-site/zh/{how-to → tutorials}/reporters.mdx +6 -6
- package/docs-site/zh/{how-to → tutorials}/sandbox-agent.mdx +6 -6
- package/docs-site/zh/{how-to → tutorials}/sandbox-providers.mdx +41 -19
- package/docs-site/zh/{how-to → tutorials}/scoring-guide.mdx +4 -4
- package/docs-site/zh/{how-to → tutorials}/viewing-results.mdx +60 -57
- package/docs-site/zh/tutorials/write-experiment.mdx +353 -0
- package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
- package/package.json +10 -7
- package/src/agents/bub.ts +21 -8
- package/src/agents/claude-code.ts +23 -10
- package/src/agents/codex.test.ts +22 -7
- package/src/agents/codex.ts +22 -9
- package/src/agents/index.ts +2 -2
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/post-setup.ts +45 -22
- package/src/agents/streaming.ts +1 -1
- package/src/agents/types.ts +20 -17
- package/src/agents/ui-message-stream.test.ts +10 -0
- package/src/agents/ui-message-stream.ts +12 -1
- package/src/cli.ts +75 -59
- package/src/context/types.ts +21 -21
- package/src/define.ts +13 -0
- package/src/i18n/en.ts +27 -12
- package/src/i18n/zh-CN.ts +27 -12
- package/src/index.ts +2 -0
- package/src/o11y/prices.json +176 -99
- package/src/report/{react → assets}/colors.ts +22 -0
- package/src/report/{react → assets}/enhance.js +1 -33
- package/src/report/assets/styles.css +1785 -0
- package/src/report/built-in/index.tsx +2 -2
- package/src/report/built-in/standard.tsx +18 -6
- package/src/report/components/attempt-detail/AttemptAssertions.tsx +67 -0
- package/src/report/components/attempt-detail/AttemptConversation.tsx +158 -0
- package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +38 -0
- package/src/report/components/attempt-detail/AttemptDiff.tsx +35 -0
- package/src/report/components/attempt-detail/AttemptError.tsx +31 -0
- package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +26 -0
- package/src/report/components/attempt-detail/AttemptSource.tsx +224 -0
- package/src/report/components/attempt-detail/AttemptSummary.tsx +74 -0
- package/src/report/components/attempt-detail/AttemptTimeline.tsx +108 -0
- package/src/report/components/attempt-detail/AttemptTrace.tsx +63 -0
- package/src/report/components/attempt-detail/AttemptUsage.tsx +29 -0
- package/src/report/components/attempt-detail/attempt-components.test.tsx +758 -0
- package/src/report/components/attempt-detail/compute.ts +336 -0
- package/src/report/components/attempt-detail/faces.ts +260 -0
- package/src/report/components/attempt-detail/index.tsx +600 -0
- package/src/report/components/attempt-detail/validate.test.ts +252 -0
- package/src/report/{react → components}/cell.tsx +6 -3
- package/src/report/{report.test.ts → components/compute.test.ts} +316 -46
- package/src/report/{react → components/entity-lists}/AttemptList.tsx +24 -16
- package/src/report/{react → components/entity-lists}/EvalList.tsx +0 -0
- package/src/report/{react → components/entity-lists}/ExperimentList.tsx +33 -27
- package/src/report/components/entity-lists/compute.ts +196 -0
- package/src/report/components/entity-lists/faces.ts +208 -0
- package/src/report/components/entity-lists/index.tsx +238 -0
- package/src/report/components/entity-lists/validate.test.ts +114 -0
- package/src/report/{react → components}/fixtures.ts +2 -2
- package/src/report/{react → components/metric-views}/DeltaTable.tsx +5 -5
- package/src/report/{react → components/metric-views}/MetricBars.tsx +5 -5
- package/src/report/{react → components/metric-views}/MetricLine.tsx +9 -6
- package/src/report/{react → components/metric-views}/MetricMatrix.tsx +6 -6
- package/src/report/{react → components/metric-views}/MetricScatter.tsx +42 -70
- package/src/report/{react → components/metric-views}/MetricTable.tsx +6 -6
- package/src/report/{react → components/metric-views}/Scoreboard.tsx +4 -4
- package/src/report/{compute.ts → components/metric-views/compute.ts} +51 -477
- package/src/report/components/metric-views/faces.ts +423 -0
- package/src/report/components/metric-views/index.tsx +352 -0
- package/src/report/{text → components/metric-views}/plot.ts +1 -1
- package/src/report/components/metric-views/validate.test.ts +211 -0
- package/src/report/{react → components}/render.test.tsx +66 -26
- package/src/report/components/shared-compute.ts +59 -0
- package/src/report/components/shared-faces.ts +30 -0
- package/src/report/components/shared.ts +192 -0
- package/src/report/{react → components/site-components}/CopyFixPrompt.tsx +3 -3
- package/src/report/{react → components/site-components}/HeroCard.tsx +3 -3
- package/src/report/{react → components/site-components}/ScopeWarnings.tsx +4 -4
- package/src/report/{react → components/site-components}/TraceWaterfall.tsx +13 -10
- package/src/report/components/site-components/compute.ts +144 -0
- package/src/report/components/site-components/faces.ts +80 -0
- package/src/report/components/site-components/index.tsx +265 -0
- package/src/report/{scope-warnings.ts → components/site-components/scope-warnings.ts} +3 -3
- package/src/report/{site-components.test.tsx → components/site-components/site-components.test.tsx} +23 -21
- package/src/report/components/site-components/validate.test.ts +89 -0
- package/src/report/{react → components/summaries}/ScopeSummary.tsx +26 -11
- package/src/report/components/summaries/compute.ts +56 -0
- package/src/report/components/summaries/faces.ts +48 -0
- package/src/report/components/summaries/index.tsx +127 -0
- package/src/report/components/summaries/validate.test.ts +49 -0
- package/src/report/definition/grid-layout.test.ts +124 -0
- package/src/report/definition/grid-layout.ts +146 -0
- package/src/report/{primitives.tsx → definition/primitives.tsx} +168 -18
- package/src/report/{report.ts → definition/report.ts} +98 -165
- package/src/report/{shell-head.test.ts → definition/shell-head.test.ts} +2 -2
- package/src/report/{text/table.ts → definition/table-text.ts} +4 -4
- package/src/report/{tree.ts → definition/tree.ts} +56 -20
- package/src/report/index.ts +128 -66
- package/src/report/{aggregate.ts → model/aggregate.ts} +31 -9
- package/src/report/{flag.ts → model/flag.ts} +39 -1
- package/src/report/{format.ts → model/format.ts} +89 -10
- package/src/report/{locale.ts → model/locale.ts} +34 -36
- package/src/report/{metrics.ts → model/metrics.ts} +3 -3
- package/src/report/{types.ts → model/types.ts} +159 -22
- package/src/report/react/index.tsx +54 -26
- package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +601 -53
- package/src/report/runtime/host.test.ts +44 -0
- package/src/report/runtime/host.ts +138 -0
- package/src/report/{load.ts → runtime/load.ts} +1 -1
- package/src/report/runtime/text.ts +192 -0
- package/src/report/runtime/web.ts +106 -0
- package/src/results/attempt-evidence.ts +5 -1
- package/src/results/host-equivalence.test.ts +53 -2
- package/src/results/select.ts +29 -5
- package/src/runner/attempt.test.ts +48 -1
- package/src/runner/attempt.ts +62 -42
- package/src/runner/cleanup-timeout.test.ts +16 -0
- package/src/runner/cleanup-timeout.ts +23 -0
- package/src/runner/discover.ts +1 -2
- package/src/runner/eval-selection.test.ts +187 -0
- package/src/runner/eval-selection.ts +109 -0
- package/src/runner/experiment-cleanup-registry.test.ts +89 -0
- package/src/runner/experiment-cleanup-registry.ts +39 -0
- package/src/runner/experiment-labels.test.ts +71 -0
- package/src/runner/feedback/agent.test.ts +33 -1
- package/src/runner/feedback/agent.ts +12 -16
- package/src/runner/feedback/ci.test.ts +35 -1
- package/src/runner/feedback/ci.ts +16 -18
- package/src/runner/feedback/coordinator.ts +28 -0
- package/src/runner/feedback/human.test.ts +108 -1
- package/src/runner/feedback/human.ts +71 -21
- package/src/runner/feedback/reducer.test.ts +64 -0
- package/src/runner/feedback/reducer.ts +26 -0
- package/src/runner/feedback/sink.ts +40 -1
- package/src/runner/fingerprint.ts +2 -1
- package/src/runner/report.test.ts +14 -0
- package/src/runner/report.ts +3 -1
- package/src/runner/run.test.ts +390 -12
- package/src/runner/run.ts +259 -26
- package/src/runner/sandbox-selection.test.ts +13 -3
- package/src/runner/sandbox-selection.ts +4 -2
- package/src/runner/types.ts +160 -24
- package/src/sandbox/docker.ts +7 -0
- package/src/sandbox/e2b-reconcile.test.ts +125 -0
- package/src/sandbox/e2b.ts +47 -12
- package/src/sandbox/types.ts +23 -21
- package/src/scoring/display.test.ts +28 -2
- package/src/scoring/display.ts +38 -5
- package/src/scoring/types.ts +3 -3
- package/src/shared/aggregate.test.ts +52 -0
- package/src/shared/aggregate.ts +29 -0
- package/src/shared/types.ts +6 -1
- package/src/show/command.test.ts +34 -0
- package/src/show/command.ts +16 -0
- package/src/show/compose.ts +1 -1
- package/src/show/index.ts +56 -20
- package/src/show/render.ts +15 -239
- package/src/show/show.test.ts +127 -34
- package/src/view/app/App.test.tsx +1 -1
- package/src/view/app/App.tsx +119 -47
- package/src/view/app/i18n.ts +7 -147
- package/src/view/app/lib/attempt-dialog.test.ts +65 -0
- package/src/view/app/lib/attempt-dialog.ts +69 -0
- package/src/view/app/main.tsx +0 -1
- package/src/view/app/types.ts +3 -86
- package/src/view/artifact-serving.test.ts +22 -38
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +15 -22
- package/src/view/data.test.ts +48 -36
- package/src/view/data.ts +105 -83
- package/src/view/index.ts +1 -1
- package/src/view/server.ts +32 -2
- package/src/view/shared/types.ts +6 -31
- package/src/view/site-parity.test.ts +24 -1
- package/src/view/site.ts +144 -17
- package/src/view/styles.css +0 -805
- package/src/view/view-report.test.ts +143 -22
- package/dist/report/components.d.ts +0 -179
- package/dist/report/components.js +0 -544
- package/dist/report/compute.d.ts +0 -139
- package/dist/report/react/ExperimentComparison.d.ts +0 -10
- package/dist/report/react/ExperimentComparison.js +0 -12
- package/dist/report/react/MetricScatter.d.ts +0 -9
- package/dist/report/react/format.d.ts +0 -3
- package/dist/report/react/format.js +0 -7
- package/dist/report/text/faces.d.ts +0 -45
- package/dist/report/text/faces.js +0 -671
- package/dist/report/web.d.ts +0 -32
- package/dist/report/web.js +0 -48
- package/docs-site/zh/how-to/authoring.mdx +0 -162
- package/docs-site/zh/how-to/custom-reports.mdx +0 -414
- package/docs-site/zh/how-to/experiments.mdx +0 -86
- package/docs-site/zh/how-to/write-experiment.mdx +0 -164
- package/src/report/components.tsx +0 -925
- package/src/report/react/ExperimentComparison.tsx +0 -73
- package/src/report/react/format.ts +0 -9
- package/src/report/react/styles.css +0 -945
- package/src/report/text/faces.ts +0 -767
- package/src/report/web.ts +0 -77
- package/src/show/report-host.test.ts +0 -205
- package/src/show/report-host.ts +0 -389
- package/src/view/app/components/AttemptModal.tsx +0 -496
- package/src/view/app/components/CodeView.test.tsx +0 -142
- package/src/view/app/components/CodeView.tsx +0 -310
- package/src/view/app/components/CopyControls.tsx +0 -106
- package/src/view/app/components/Trace.tsx +0 -100
- package/src/view/app/components/Transcript.tsx +0 -157
- package/src/view/app/components/ui/badge.tsx +0 -21
- package/src/view/app/lib/artifact-url.ts +0 -17
- package/src/view/app/lib/attempt-route.test.ts +0 -80
- package/src/view/app/lib/attempt-route.ts +0 -52
- package/src/view/app/lib/format.ts +0 -70
- package/src/view/app/lib/guards.test.ts +0 -108
- package/src/view/app/lib/guards.ts +0 -71
- package/src/view/app/lib/rows.ts +0 -22
- package/src/view/app/lib/transcript-data.tsx +0 -151
- package/src/view/app/lib/verdict.ts +0 -23
- package/src/view/app/shared.ts +0 -8
- /package/dist/report/{react → components/metric-views}/chart-math.d.ts +0 -0
- /package/dist/report/{react → components/metric-views}/chart-math.js +0 -0
- /package/dist/report/{text → components/metric-views}/plot.d.ts +0 -0
- /package/dist/report/{react → components/site-components}/PoweredBy.d.ts +0 -0
- /package/dist/report/{react → components/site-components}/PoweredBy.js +0 -0
- /package/dist/report/{text/layout.d.ts → model/text-layout.d.ts} +0 -0
- /package/dist/report/{text/layout.js → model/text-layout.js} +0 -0
- /package/dist/report/{types.js → model/types.js} +0 -0
- /package/src/report/{react → components/metric-views}/chart-math.test.ts +0 -0
- /package/src/report/{react → components/metric-views}/chart-math.ts +0 -0
- /package/src/report/{react → components/site-components}/PoweredBy.tsx +0 -0
- /package/src/report/{text/layout.ts → model/text-layout.ts} +0 -0
package/src/results/select.ts
CHANGED
|
@@ -16,7 +16,7 @@ import type {
|
|
|
16
16
|
Snapshot,
|
|
17
17
|
} from "./types.ts";
|
|
18
18
|
import type { ExperimentRunInfo, JsonValue } from "../types.ts";
|
|
19
|
-
import { evalPrefixPredicate } from "../shared/aggregate.ts";
|
|
19
|
+
import { evalPrefixPredicate, matchExperimentSelector } from "../shared/aggregate.ts";
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Results.latest() 的实现:每个实验取最新一次快照(= exp.snapshots[0]),生成挑选警告。
|
|
@@ -136,6 +136,15 @@ export function deepEqualJson(a: unknown, b: unknown): boolean {
|
|
|
136
136
|
return false;
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* 一个快照实际选中的 eval id 全集:优先读落盘的 `experiment.selectedEvalIds`(niceeval 自己的
|
|
141
|
+
* 写入面必有此字段);第三方 harness 未实现该字段时退化为该快照实际写出的 `evals`,不把整份
|
|
142
|
+
* 来源排除在外(见 docs/feature/results/architecture.md「selectedEvalIds」)。
|
|
143
|
+
*/
|
|
144
|
+
export function selectedEvalIdsOf(snapshot: Snapshot): readonly string[] {
|
|
145
|
+
return snapshot.experiment?.selectedEvalIds ?? snapshot.evals.map((ev) => ev.id);
|
|
146
|
+
}
|
|
147
|
+
|
|
139
148
|
/**
|
|
140
149
|
* 两个宿主(show / view)共用的现刻水位选择器:每个 experiment × eval 取「包含该 eval 的
|
|
141
150
|
* 最新快照」里的全部 attempt,跨 run 合成。results.latest() 只挑「每实验最新快照」,带 eval
|
|
@@ -166,8 +175,12 @@ export function selectCurrentResults(results: Results, scope: ResultScope = {}):
|
|
|
166
175
|
const taken = new Map<string, { ev: Eval; snapshot: Snapshot }>();
|
|
167
176
|
for (const snapshot of exp.snapshots) {
|
|
168
177
|
if (!deepEqualJson(comparabilityConfigOf(snapshot), baseline)) continue;
|
|
178
|
+
// 一个来源快照只贡献它自己选中的 eval——不在其 selectedEvalIds(或第三方退化后的实际
|
|
179
|
+
// evals)内的历史 attempt 不进入合成结果,即使它恰好出现在 snapshot.evals 里。第三方
|
|
180
|
+
// 无该字段时 selectedEvalIdsOf 退化为快照实际 evals,这里的过滤天然是 no-op。
|
|
181
|
+
const selectedIds = new Set(selectedEvalIdsOf(snapshot));
|
|
169
182
|
for (const ev of snapshot.evals) {
|
|
170
|
-
if (!match(ev.id) || taken.has(ev.id)) continue;
|
|
183
|
+
if (!selectedIds.has(ev.id) || !match(ev.id) || taken.has(ev.id)) continue;
|
|
171
184
|
taken.set(ev.id, { ev, snapshot });
|
|
172
185
|
}
|
|
173
186
|
}
|
|
@@ -184,12 +197,17 @@ export function selectCurrentResults(results: Results, scope: ResultScope = {}):
|
|
|
184
197
|
}
|
|
185
198
|
const evals = picks.map((p) => p.ev);
|
|
186
199
|
const base = exp.latest;
|
|
200
|
+
// 合成快照的 selectedEvalIds 重建为最终 picks 的有序 id 列表——不是照抄某一来源快照
|
|
201
|
+
// (通常是最新快照)的局部选择。base.experiment 缺失(第三方无 ExperimentRunInfo)时不
|
|
202
|
+
// 伪造一份不完整投影,继续靠 report 侧 fallback(见 selectedEvalIdsOf)。
|
|
203
|
+
const experiment =
|
|
204
|
+
base.experiment !== undefined ? { ...base.experiment, selectedEvalIds: evals.map((ev) => ev.id) } : undefined;
|
|
187
205
|
snapshots.push({
|
|
188
206
|
experimentId: exp.id,
|
|
189
207
|
startedAt,
|
|
190
208
|
agent: base.agent,
|
|
191
209
|
...(base.model !== undefined ? { model: base.model } : {}),
|
|
192
|
-
...(
|
|
210
|
+
...(experiment !== undefined ? { experiment } : {}),
|
|
193
211
|
...(base.name !== undefined ? { name: base.name } : {}),
|
|
194
212
|
producer: base.producer,
|
|
195
213
|
schemaVersion: base.schemaVersion,
|
|
@@ -368,12 +386,18 @@ export function isNewerSnapshot(a: Snapshot, b: Snapshot): boolean {
|
|
|
368
386
|
return a.dir.localeCompare(b.dir) > 0;
|
|
369
387
|
}
|
|
370
388
|
|
|
371
|
-
/**
|
|
389
|
+
/**
|
|
390
|
+
* experiment 选择器过滤(--exp / latest({ experiments }) 同一语义,与 `niceeval exp` 位置参数
|
|
391
|
+
* 共用 matchExperimentSelector,见 docs/feature/experiments/cli.md「实验选择器怎样解析」);
|
|
392
|
+
* 包内使用,不进公共 barrel。
|
|
393
|
+
*/
|
|
372
394
|
export function filterExperiments(experiments: Experiment[], filter?: string | string[]): Experiment[] {
|
|
373
395
|
if (filter === undefined) return experiments;
|
|
374
396
|
// 允许 "compare/" 这种带尾斜杠的写法,与 "compare" 等价;分段匹配不误配 "compare2"。
|
|
375
397
|
const prefixes = (Array.isArray(filter) ? filter : [filter]).map((p) => p.replace(/\/+$/, ""));
|
|
376
|
-
|
|
398
|
+
const ids = experiments.map((exp) => exp.id);
|
|
399
|
+
const matched = new Set(prefixes.flatMap((p) => matchExperimentSelector(ids, p)));
|
|
400
|
+
return experiments.filter((exp) => matched.has(exp.id));
|
|
377
401
|
}
|
|
378
402
|
|
|
379
403
|
/**
|
|
@@ -100,7 +100,7 @@ async function runOnce(
|
|
|
100
100
|
// 自定义 provider:create() 直接返回内存 fake,绕开真实沙箱 provider。
|
|
101
101
|
sandbox: defineSandbox({ name: "fake-provider", create: async () => asSandbox(box) }),
|
|
102
102
|
timeoutMs: 5_000,
|
|
103
|
-
|
|
103
|
+
selectedEvalIds: [evalDef.id],
|
|
104
104
|
};
|
|
105
105
|
const attempt: Attempt = { evalDef, run, attempt: 0, key: "fake/eval", fingerprint: "" };
|
|
106
106
|
const config: Config = {};
|
|
@@ -275,6 +275,53 @@ describe("runAttemptEffect · onPhase 回调随 enterPhase 同步触发", () =>
|
|
|
275
275
|
});
|
|
276
276
|
});
|
|
277
277
|
|
|
278
|
+
// eval.teardown 的触发条件是「eval.setup 时点走到过」,不是「setup 声明且成功」(成对触发规则,
|
|
279
|
+
// 见 docs/runner.md「环境预置不进运行器,但按顺序调它」)。时点在 attempt.ts 里于调用
|
|
280
|
+
// evalDef.setup 之前就置位,所以 setup 抛错、乃至压根没声明 setup,都不豁免 teardown。
|
|
281
|
+
describe("runAttemptEffect · eval.teardown 的触发规则", () => {
|
|
282
|
+
it("eval.setup 抛错时,eval.teardown 仍被调用(半初始化现场同样要扫尾)", async () => {
|
|
283
|
+
const agent = defineSandboxAgent({
|
|
284
|
+
name: "fake-agent-eval-setup-throws",
|
|
285
|
+
send: async () => ({ events: [], status: "completed" }),
|
|
286
|
+
});
|
|
287
|
+
let teardownCalls = 0;
|
|
288
|
+
const box = new FakeSandbox();
|
|
289
|
+
const result = await runOnce(agent, box, {
|
|
290
|
+
evalDefOverrides: {
|
|
291
|
+
setup: async () => {
|
|
292
|
+
throw new Error("boom-from-eval-setup");
|
|
293
|
+
},
|
|
294
|
+
teardown: async () => {
|
|
295
|
+
teardownCalls += 1;
|
|
296
|
+
},
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
expect(result.error?.message).toContain("boom-from-eval-setup");
|
|
301
|
+
expect(result.error?.phase).toBe("eval.setup");
|
|
302
|
+
expect(teardownCalls).toBe(1);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it("未声明 eval.setup 时,eval.teardown 依然触发(时点走到不依赖 setup 是否声明)", async () => {
|
|
306
|
+
const agent = defineSandboxAgent({
|
|
307
|
+
name: "fake-agent-no-eval-setup",
|
|
308
|
+
send: async () => ({ events: [], status: "completed" }),
|
|
309
|
+
});
|
|
310
|
+
let teardownCalls = 0;
|
|
311
|
+
const box = new FakeSandbox();
|
|
312
|
+
const result = await runOnce(agent, box, {
|
|
313
|
+
evalDefOverrides: {
|
|
314
|
+
teardown: async () => {
|
|
315
|
+
teardownCalls += 1;
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
expect(result.error).toBeUndefined();
|
|
321
|
+
expect(teardownCalls).toBe(1);
|
|
322
|
+
});
|
|
323
|
+
});
|
|
324
|
+
|
|
278
325
|
describe("runAttemptEffect · 主链与 Scope 收尾的计时边界", () => {
|
|
279
326
|
it("sandbox.stop 只计入收尾,主链 phase 合计不超过 durationMs", async () => {
|
|
280
327
|
const agent = defineSandboxAgent({
|
package/src/runner/attempt.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { readFile as readSourceFile } from "node:fs/promises";
|
|
|
8
8
|
import { Effect, Cause, Duration } from "effect";
|
|
9
9
|
import { createSandbox, resolveSandbox } from "../sandbox/resolve.ts";
|
|
10
10
|
import { stopSandbox, unregisterSandbox } from "../sandbox/registry.ts";
|
|
11
|
+
import { withCleanupTimeout } from "./cleanup-timeout.ts";
|
|
11
12
|
import { KEEPABLE_PROVIDERS, nativeEnterCommand, suspendSandbox } from "../sandbox/keep.ts";
|
|
12
13
|
import { keptEntryId, updateKeptEntry, writeKeptEntry } from "../sandbox/keep-registry.ts";
|
|
13
14
|
import { createTraceReceiver, type TraceReceiver } from "../o11y/otlp/receiver.ts";
|
|
@@ -31,7 +32,6 @@ import type { CapturedEvalSource } from "./eval-source.ts";
|
|
|
31
32
|
import type {
|
|
32
33
|
AgentContext,
|
|
33
34
|
AgentSetupManifest,
|
|
34
|
-
Cleanup,
|
|
35
35
|
Config,
|
|
36
36
|
DiagnosticInput,
|
|
37
37
|
DiffArtifact,
|
|
@@ -456,7 +456,7 @@ export function runAttemptEffect(
|
|
|
456
456
|
* 生命周期阶段(极早期就挂、还没跨进任何阶段时兜底 `eval.run`——phase 是必填字段,不留空);
|
|
457
457
|
* code 目前只对确定已知的类别赋稳定码,其余走 `"unexpected-error"`——provider 专属的限流码
|
|
458
458
|
* 分类留在各 provider 的 `classifyProvisionError`,没有中性入口能在这里复算,不猜一个可能错的码。 */
|
|
459
|
-
function errorFromThrown(e: unknown, phase: LifecyclePhase | undefined): AttemptError {
|
|
459
|
+
export function errorFromThrown(e: unknown, phase: LifecyclePhase | undefined): AttemptError {
|
|
460
460
|
const { message, stack, cause } = describeError(e);
|
|
461
461
|
return {
|
|
462
462
|
code: "unexpected-error",
|
|
@@ -551,22 +551,21 @@ async function runAttemptBody(
|
|
|
551
551
|
progress: feedback.progress,
|
|
552
552
|
diagnostic: feedback.diagnostic,
|
|
553
553
|
};
|
|
554
|
-
let agentCleanup: Cleanup | void = undefined;
|
|
555
554
|
let agentDidSetup = false;
|
|
555
|
+
/** agent.setup 时点已走到(未声明 setup 也置位)——agent.teardown 的触发条件(成对触发规则)。 */
|
|
556
|
+
let agentSetupReached = false;
|
|
556
557
|
/** agent.setup 写进沙箱的安装清单(装了 Skill / plugin / MCP 的沙箱型 adapter 才有)。 */
|
|
557
558
|
let agentSetup: AgentSetupManifest | undefined;
|
|
558
|
-
|
|
559
|
-
let
|
|
559
|
+
/** eval.setup 时点已走到(分类账锚点之后;未声明 setup 也置位)——eval.teardown 的触发条件。 */
|
|
560
|
+
let evalSetupReached = false;
|
|
560
561
|
// 变更分类账(仅沙箱型;workspace.baseline 阶段建立)。
|
|
561
562
|
let ledger: ChangeLedger | undefined;
|
|
562
|
-
// SandboxSpec.setup() 返回的 cleanup 闭包,按调用顺序收集;finally 里 LIFO 跑(见下)。
|
|
563
|
-
const sandboxCleanups: Cleanup[] = [];
|
|
564
563
|
try {
|
|
565
564
|
if (usesSandbox) {
|
|
566
565
|
// 沙箱级生命周期钩子(SandboxSpec.setup):环境预置层,先于 workspace 上传 / git 基线 /
|
|
567
566
|
// eval.setup 跑——改动进 git 基线,不会被误算进 agent 产出的 diff。按追加顺序依次执行;
|
|
568
|
-
// 单个抛错走下面的执行错误路径(与 eval.setup / agent.setup 同一条)
|
|
569
|
-
// sandbox.teardown
|
|
567
|
+
// 单个抛错走下面的执行错误路径(与 eval.setup / agent.setup 同一条),后续 setup 不再执行,
|
|
568
|
+
// sandbox.teardown 钩子链仍在 finally 里完整跑(见 catch/finally)——半初始化的沙箱同样要扫尾。
|
|
570
569
|
if (sandboxSetupHooks.length > 0) {
|
|
571
570
|
enterPhase("sandbox.setup");
|
|
572
571
|
log(t("runner.startSandboxSetup"));
|
|
@@ -582,8 +581,17 @@ async function runAttemptBody(
|
|
|
582
581
|
});
|
|
583
582
|
if (hookNode) recorder.pushParent(hookNode);
|
|
584
583
|
try {
|
|
585
|
-
|
|
586
|
-
|
|
584
|
+
// 迁移护栏:tsx 用户没有类型检查,旧式「setup 返回 cleanup」会被静默忽略——命中即报
|
|
585
|
+
// 清晰错误(attempt errored),指向成对 teardown 写法。下方 eval.setup / agent.setup 同。
|
|
586
|
+
const returned = (await hook(sandbox, hookCtx)) as unknown;
|
|
587
|
+
if (typeof returned === "function") {
|
|
588
|
+
throw new Error(
|
|
589
|
+
t("runner.setupReturnedCleanup", {
|
|
590
|
+
layer: `SandboxSpec.setup() hook #${i}`,
|
|
591
|
+
hint: "SandboxSpec.teardown(fn)",
|
|
592
|
+
}).trimEnd(),
|
|
593
|
+
);
|
|
594
|
+
}
|
|
587
595
|
} catch (e) {
|
|
588
596
|
if (hookNode) hookNode.failed = true;
|
|
589
597
|
throw e;
|
|
@@ -600,20 +608,41 @@ async function runAttemptBody(
|
|
|
600
608
|
ledger = await createChangeLedger(sandbox, evalDef.diff);
|
|
601
609
|
|
|
602
610
|
// eval 级 setup(starter prep:npm install / 装系统依赖等)。命令默认非 root;
|
|
603
|
-
// setup 里需要 root 的(apt/pip)自己传 { root: true }
|
|
611
|
+
// setup 里需要 root 的(apt/pip)自己传 { root: true }。时点在此走到——
|
|
612
|
+
// teardown 的触发条件是时点,不是「setup 声明且成功」(成对触发规则,见
|
|
613
|
+
// docs/runner.md「环境预置不进运行器,但按顺序调它」)。
|
|
614
|
+
evalSetupReached = true;
|
|
604
615
|
if (evalDef.setup) {
|
|
605
616
|
enterPhase("eval.setup");
|
|
606
617
|
log(t("runner.evalSetup"));
|
|
607
|
-
|
|
618
|
+
const returned = (await evalDef.setup(withEvalLocalPaths(sandbox, evalDef.baseDir), hookCtx)) as unknown;
|
|
619
|
+
if (typeof returned === "function") {
|
|
620
|
+
throw new Error(
|
|
621
|
+
t("runner.setupReturnedCleanup", {
|
|
622
|
+
layer: `EvalDef.setup (${evalDef.id})`,
|
|
623
|
+
hint: "EvalDef.teardown",
|
|
624
|
+
}).trimEnd(),
|
|
625
|
+
);
|
|
626
|
+
}
|
|
608
627
|
}
|
|
609
628
|
}
|
|
610
629
|
|
|
611
630
|
// agent 自己的 lifecycle:装 CLI、写 config(每个沙箱一次,不在每轮 send 里)。
|
|
631
|
+
// 时点在此走到(未声明 setup 也算)——agent.teardown 据此触发。
|
|
632
|
+
agentSetupReached = true;
|
|
612
633
|
if (run.agent.setup) {
|
|
613
634
|
enterPhase("agent.setup");
|
|
614
635
|
log(t("runner.startAgentSetup"));
|
|
615
636
|
agentDidSetup = true;
|
|
616
|
-
|
|
637
|
+
const returned = (await run.agent.setup(sandbox, attemptCtx)) as unknown;
|
|
638
|
+
if (typeof returned === "function") {
|
|
639
|
+
throw new Error(
|
|
640
|
+
t("runner.setupReturnedCleanup", {
|
|
641
|
+
layer: `Agent.setup (${run.agent.name})`,
|
|
642
|
+
hint: "Agent.teardown",
|
|
643
|
+
}).trimEnd(),
|
|
644
|
+
);
|
|
645
|
+
}
|
|
617
646
|
}
|
|
618
647
|
|
|
619
648
|
// 安装 manifest:adapter 在 setup 收尾写进沙箱的固定路径,核心只把它抬成 attempt artifact
|
|
@@ -856,14 +885,17 @@ async function runAttemptBody(
|
|
|
856
885
|
// (不计入 durationMs 口径,见 docs/feature/results/architecture.md)。执行序与 LifecyclePhase
|
|
857
886
|
// 闭集声明一致:eval.teardown → agent.teardown → sandbox.teardown;各段可独立标 failed。
|
|
858
887
|
// 沙箱 stop / 接收器 close 不在这里 —— 由 runAttemptEffect 的 Scope 在本函数返回后回收,
|
|
859
|
-
// 并经 finalizer 计成 sandbox.stop
|
|
860
|
-
|
|
861
|
-
|
|
888
|
+
// 并经 finalizer 计成 sandbox.stop。触发规则统一是「同层 setup 时点走到过」(setup / test
|
|
889
|
+
// 抛错不豁免,见 docs/runner.md「环境预置不进运行器,但按顺序调它」);没有对应 teardown
|
|
890
|
+
// 的段直接跳过,不产生空阶段。
|
|
891
|
+
if (evalSetupReached && evalDef.teardown) {
|
|
862
892
|
enterPhase("eval.teardown");
|
|
863
893
|
await recorder
|
|
864
894
|
.measureClosing("eval.teardown", async () => {
|
|
865
895
|
try {
|
|
866
|
-
|
|
896
|
+
// 收尾可调用体一律有界(docs/cli.md「中断:三级响应」的有界性前提):挂起的 teardown
|
|
897
|
+
// 到点按本段失败语义收束,后续段照常执行,收尾链不能无限拖住退出。下同。
|
|
898
|
+
await withCleanupTimeout(() => evalDef.teardown!(withEvalLocalPaths(sandbox, evalDef.baseDir), hookCtx));
|
|
867
899
|
} catch (e) {
|
|
868
900
|
// 收尾失败只是 diagnostic,不改判定 —— 挂到 attempt.diagnostics(见 finally 末尾并入)。
|
|
869
901
|
diagnostics.push(teardownDiagnostic("eval.teardown", e));
|
|
@@ -872,14 +904,12 @@ async function runAttemptBody(
|
|
|
872
904
|
})
|
|
873
905
|
.catch(() => {});
|
|
874
906
|
}
|
|
875
|
-
|
|
876
|
-
if (agentCleanupFn !== undefined || (agentDidSetup && run.agent.teardown !== undefined)) {
|
|
907
|
+
if (agentSetupReached && run.agent.teardown) {
|
|
877
908
|
enterPhase("agent.teardown");
|
|
878
909
|
await recorder
|
|
879
910
|
.measureClosing("agent.teardown", async () => {
|
|
880
911
|
try {
|
|
881
|
-
|
|
882
|
-
if (agentDidSetup) await run.agent.teardown?.(sandbox, attemptCtx);
|
|
912
|
+
await withCleanupTimeout(() => run.agent.teardown!(sandbox, attemptCtx));
|
|
883
913
|
} catch (e) {
|
|
884
914
|
diagnostics.push(teardownDiagnostic("agent.teardown", e));
|
|
885
915
|
throw e;
|
|
@@ -887,33 +917,22 @@ async function runAttemptBody(
|
|
|
887
917
|
})
|
|
888
918
|
.catch(() => {});
|
|
889
919
|
}
|
|
890
|
-
if (usesSandbox &&
|
|
920
|
+
if (usesSandbox && sandboxTeardownHooks.length > 0) {
|
|
891
921
|
enterPhase("sandbox.teardown");
|
|
892
922
|
await recorder
|
|
893
923
|
.measureClosing("sandbox.teardown", async () => {
|
|
894
924
|
const before = diagnostics.length;
|
|
895
|
-
// sandbox.
|
|
896
|
-
|
|
925
|
+
// sandbox.teardown 钩子:按追加的逆序执行(LIFO),沙箱销毁前最后一步。setup 链中途
|
|
926
|
+
// 抛错不影响本链完整走完——半初始化的沙箱同样要扫尾,钩子自己对未初始化状态防御。
|
|
927
|
+
// 逐钩子有界:一个挂起的钩子不阻塞链上其余钩子拿到执行机会。
|
|
928
|
+
log(t("runner.startSandboxTeardown"));
|
|
929
|
+
for (let i = sandboxTeardownHooks.length - 1; i >= 0; i--) {
|
|
897
930
|
try {
|
|
898
|
-
await
|
|
931
|
+
await withCleanupTimeout(() => sandboxTeardownHooks[i](sandbox, hookCtx));
|
|
899
932
|
} catch (e) {
|
|
900
933
|
diagnostics.push(teardownDiagnostic("sandbox.teardown", e));
|
|
901
934
|
}
|
|
902
935
|
}
|
|
903
|
-
// sandbox.teardown 钩子:按追加的逆序执行,沙箱销毁前最后一步。
|
|
904
|
-
if (sandboxTeardownHooks.length > 0) {
|
|
905
|
-
log(t("runner.startSandboxTeardown"));
|
|
906
|
-
for (let i = sandboxTeardownHooks.length - 1; i >= 0; i--) {
|
|
907
|
-
try {
|
|
908
|
-
const teardownCleanup = await sandboxTeardownHooks[i](sandbox, hookCtx);
|
|
909
|
-
// SandboxHook 类型允许返回 Cleanup(与 setup 同一签名);teardown 返回的
|
|
910
|
-
// cleanup 没有更晚的挂点,立即执行。
|
|
911
|
-
if (typeof teardownCleanup === "function") await teardownCleanup();
|
|
912
|
-
} catch (e) {
|
|
913
|
-
diagnostics.push(teardownDiagnostic("sandbox.teardown", e));
|
|
914
|
-
}
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
936
|
if (diagnostics.length > before) throw new Error("sandbox teardown diagnostics");
|
|
918
937
|
})
|
|
919
938
|
.catch(() => {});
|
|
@@ -1017,17 +1036,18 @@ async function collectSources(
|
|
|
1017
1036
|
|
|
1018
1037
|
/** 解析后运行配置的穷尽投影(ExperimentRunInfo,见 docs/feature/results/architecture.md):
|
|
1019
1038
|
* agent/model 只在快照顶层,这里不复制;sandbox 只经 provider 的公开参数投影落盘。 */
|
|
1020
|
-
function experimentRunInfo(run: AgentRun, configSandbox?: Config["sandbox"]): EvalResult["experiment"] {
|
|
1039
|
+
export function experimentRunInfo(run: AgentRun, configSandbox?: Config["sandbox"]): EvalResult["experiment"] {
|
|
1021
1040
|
return {
|
|
1022
1041
|
...(run.description !== undefined ? { description: run.description } : {}),
|
|
1023
1042
|
...(run.reasoningEffort !== undefined ? { reasoningEffort: run.reasoningEffort } : {}),
|
|
1024
1043
|
...(Object.keys(run.flags).length > 0 ? { flags: run.flags } : {}),
|
|
1044
|
+
...(run.labels !== undefined && Object.keys(run.labels).length > 0 ? { labels: run.labels } : {}),
|
|
1025
1045
|
runs: run.runs,
|
|
1026
1046
|
earlyExit: run.earlyExit,
|
|
1027
1047
|
...(run.timeoutMs !== undefined ? { timeoutMs: run.timeoutMs } : {}),
|
|
1028
1048
|
...(run.budget !== undefined ? { budget: run.budget } : {}),
|
|
1029
1049
|
...(run.maxConcurrency !== undefined ? { maxConcurrency: run.maxConcurrency } : {}),
|
|
1030
|
-
selectedEvalIds: run.selectedEvalIds
|
|
1050
|
+
selectedEvalIds: [...run.selectedEvalIds],
|
|
1031
1051
|
...(run.evalFilterFingerprint !== undefined ? { evalFilterFingerprint: run.evalFilterFingerprint } : {}),
|
|
1032
1052
|
...sandboxProjection(run, configSandbox),
|
|
1033
1053
|
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/experiments-runner/cases.md
|
|
2
|
+
// (「cleanup 执行有界」行;sandbox/cases.md 的「收尾链每个可调用体各有清理超时」行同样由本文件证明机制)
|
|
3
|
+
// bug: memory/force-exit-skips-experiment-teardown.md
|
|
4
|
+
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { withCleanupTimeout } from "./cleanup-timeout.ts";
|
|
6
|
+
|
|
7
|
+
describe("withCleanupTimeout", () => {
|
|
8
|
+
it("挂起的可调用体到点抛超时错误(消息含超时毫秒数)", async () => {
|
|
9
|
+
await expect(withCleanupTimeout(() => new Promise<never>(() => {}), 20)).rejects.toThrow(/20ms/);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("按时完成的可调用体原样透传返回值;抛错原样透传,不被包装", async () => {
|
|
13
|
+
await expect(withCleanupTimeout(async () => "ok", 1_000)).resolves.toBe("ok");
|
|
14
|
+
await expect(withCleanupTimeout(() => Promise.reject(new Error("boom")), 1_000)).rejects.toThrow("boom");
|
|
15
|
+
});
|
|
16
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// 收尾可调用体的有界执行:eval/agent/sandbox 各段的 cleanup、钩子与实验级 cleanup 共用。
|
|
2
|
+
// 有界性是「强清 = 加速收尾」设计的前提(docs/cli.md「中断:三级响应」):任何一个挂起的收尾
|
|
3
|
+
// 都不能无限拖住退出,到点按该段自己的失败语义收束(teardown-failed / experiment-teardown-failed
|
|
4
|
+
// 诊断),超时后遗留的 promise 悬空,随进程退出消亡。
|
|
5
|
+
|
|
6
|
+
import { t } from "../i18n/index.ts";
|
|
7
|
+
|
|
8
|
+
/** 单个收尾可调用体的清理超时(docs 声明值,provider stop 另有自己的 8s 超时)。 */
|
|
9
|
+
export const CLEANUP_TIMEOUT_MS = 30_000;
|
|
10
|
+
|
|
11
|
+
export async function withCleanupTimeout<T>(fn: () => Promise<T> | T, timeoutMs = CLEANUP_TIMEOUT_MS): Promise<T> {
|
|
12
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
13
|
+
try {
|
|
14
|
+
return await Promise.race([
|
|
15
|
+
Promise.resolve().then(fn),
|
|
16
|
+
new Promise<never>((_, reject) => {
|
|
17
|
+
timer = setTimeout(() => reject(new Error(t("runner.cleanupTimeout", { timeoutMs }))), timeoutMs);
|
|
18
|
+
}),
|
|
19
|
+
]);
|
|
20
|
+
} finally {
|
|
21
|
+
if (timer) clearTimeout(timer);
|
|
22
|
+
}
|
|
23
|
+
}
|
package/src/runner/discover.ts
CHANGED
|
@@ -103,8 +103,7 @@ export async function discoverExperiments(root: string): Promise<DiscoveredExper
|
|
|
103
103
|
.replace(/\.experiment$/, "")
|
|
104
104
|
.split(sep)
|
|
105
105
|
.join("/");
|
|
106
|
-
|
|
107
|
-
out.push({ ...def, id, group });
|
|
106
|
+
out.push({ ...def, id });
|
|
108
107
|
}
|
|
109
108
|
return out;
|
|
110
109
|
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/experiments-runner/cases.md
|
|
2
|
+
// 纯选题边界:EvalDescriptor 投影 + resolveExperimentEvals 求值(定稿见
|
|
3
|
+
// docs/feature/eval/library.md「EvalDescriptor」、docs/feature/experiments/library.md「evals」)。
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { evalDescriptorOf, resolveExperimentEvals, selectedEvalsForRun } from "./eval-selection.ts";
|
|
7
|
+
import type { DiscoveredEval, EvalDescriptor } from "./types.ts";
|
|
8
|
+
|
|
9
|
+
const source = { path: "evals/fake.eval.ts", content: "export default { test() {} };\n", sha256: "fake" };
|
|
10
|
+
|
|
11
|
+
function makeEval(id: string, overrides: Partial<DiscoveredEval> = {}): DiscoveredEval {
|
|
12
|
+
return {
|
|
13
|
+
id,
|
|
14
|
+
baseDir: "/project/evals",
|
|
15
|
+
sourcePath: `/project/evals/${id}.eval.ts`,
|
|
16
|
+
source,
|
|
17
|
+
test() {},
|
|
18
|
+
...overrides,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe("evalDescriptorOf", () => {
|
|
23
|
+
it("显式白名单投影:不暴露 sourcePath / baseDir / test 等内部字段", () => {
|
|
24
|
+
const evalDef = makeEval("coding/fix-button", {
|
|
25
|
+
description: "fix the button",
|
|
26
|
+
tags: ["coding", "frontend"],
|
|
27
|
+
environment: "node-22",
|
|
28
|
+
metadata: { owner: "team-a" },
|
|
29
|
+
});
|
|
30
|
+
const descriptor = evalDescriptorOf(evalDef);
|
|
31
|
+
expect(descriptor).toEqual({
|
|
32
|
+
id: "coding/fix-button",
|
|
33
|
+
description: "fix the button",
|
|
34
|
+
tags: ["coding", "frontend"],
|
|
35
|
+
environment: "node-22",
|
|
36
|
+
metadata: { owner: "team-a" },
|
|
37
|
+
});
|
|
38
|
+
expect(descriptor).not.toHaveProperty("sourcePath");
|
|
39
|
+
expect(descriptor).not.toHaveProperty("baseDir");
|
|
40
|
+
expect(descriptor).not.toHaveProperty("source");
|
|
41
|
+
expect(descriptor).not.toHaveProperty("test");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("未声明 tags 得到只读空数组;冻结,mutation 不影响下一次读取", () => {
|
|
45
|
+
const evalDef = makeEval("bare/eval");
|
|
46
|
+
const d1 = evalDescriptorOf(evalDef);
|
|
47
|
+
expect(d1.tags).toEqual([]);
|
|
48
|
+
expect(Object.isFrozen(d1)).toBe(true);
|
|
49
|
+
expect(Object.isFrozen(d1.tags)).toBe(true);
|
|
50
|
+
|
|
51
|
+
// 每次投影都是新对象,一次调用拿到的引用被 mutate(TS 层本应拒绝,这里模拟无类型 JS)
|
|
52
|
+
// 不会影响下一次对同一 evalDef 的投影。
|
|
53
|
+
const mutable = d1 as unknown as { tags: string[] };
|
|
54
|
+
expect(() => mutable.tags.push("x")).toThrow(); // 冻结数组:push 直接抛错
|
|
55
|
+
const d2 = evalDescriptorOf(evalDef);
|
|
56
|
+
expect(d2.tags).toEqual([]);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("resolveExperimentEvals", () => {
|
|
61
|
+
const codingFixButton = makeEval("coding/fix-button", { tags: ["coding", "frontend"], environment: "node-22" });
|
|
62
|
+
const researchGpu = makeEval("research/gpu-literature", { tags: ["research"], environment: "gpu" });
|
|
63
|
+
const evals = [codingFixButton, researchGpu];
|
|
64
|
+
|
|
65
|
+
it("谓词同时可读 id / tags / environment / metadata,只命中匹配的 eval", () => {
|
|
66
|
+
const { selectedEvalIds } = resolveExperimentEvals({
|
|
67
|
+
experimentId: "exp/coding-only",
|
|
68
|
+
selector: (e) => e.id.startsWith("coding/") && e.tags.includes("coding") && e.environment !== "gpu",
|
|
69
|
+
cliPatterns: [],
|
|
70
|
+
evals,
|
|
71
|
+
});
|
|
72
|
+
expect(selectedEvalIds).toEqual(["coding/fix-button"]);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('"*" 或省略 selector 全选;数组按裸前缀;CLI patterns 与 selector 取交集', () => {
|
|
76
|
+
expect(
|
|
77
|
+
resolveExperimentEvals({ experimentId: "e", selector: "*", cliPatterns: [], evals }).selectedEvalIds,
|
|
78
|
+
).toEqual(["coding/fix-button", "research/gpu-literature"]);
|
|
79
|
+
expect(
|
|
80
|
+
resolveExperimentEvals({ experimentId: "e", selector: undefined, cliPatterns: [], evals }).selectedEvalIds,
|
|
81
|
+
).toEqual(["coding/fix-button", "research/gpu-literature"]);
|
|
82
|
+
expect(
|
|
83
|
+
resolveExperimentEvals({ experimentId: "e", selector: ["coding/"], cliPatterns: [], evals }).selectedEvalIds,
|
|
84
|
+
).toEqual(["coding/fix-button"]);
|
|
85
|
+
// selector 选中两条,CLI 追加前缀再收窄到一条:两层是交集。
|
|
86
|
+
expect(
|
|
87
|
+
resolveExperimentEvals({ experimentId: "e", selector: "*", cliPatterns: ["research/"], evals }).selectedEvalIds,
|
|
88
|
+
).toEqual(["research/gpu-literature"]);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("零命中时 selectedEvalIds 为空(不报错)", () => {
|
|
92
|
+
const { selectedEvals, selectedEvalIds } = resolveExperimentEvals({
|
|
93
|
+
experimentId: "e",
|
|
94
|
+
selector: () => false,
|
|
95
|
+
cliPatterns: [],
|
|
96
|
+
evals,
|
|
97
|
+
});
|
|
98
|
+
expect(selectedEvals).toEqual([]);
|
|
99
|
+
expect(selectedEvalIds).toEqual([]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("返回顺序 = discovery 稳定顺序,不随谓词命中顺序改变;id 去重", () => {
|
|
103
|
+
const a = makeEval("z/a");
|
|
104
|
+
const b = makeEval("a/b");
|
|
105
|
+
const { selectedEvalIds } = resolveExperimentEvals({
|
|
106
|
+
experimentId: "e",
|
|
107
|
+
selector: "*",
|
|
108
|
+
cliPatterns: [],
|
|
109
|
+
evals: [a, b],
|
|
110
|
+
});
|
|
111
|
+
expect(selectedEvalIds).toEqual(["z/a", "a/b"]); // discovery 顺序,不是字典序
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("每个候选 eval 只求值谓词恰好一次", () => {
|
|
115
|
+
let calls = 0;
|
|
116
|
+
resolveExperimentEvals({
|
|
117
|
+
experimentId: "e",
|
|
118
|
+
selector: () => {
|
|
119
|
+
calls += 1;
|
|
120
|
+
return true;
|
|
121
|
+
},
|
|
122
|
+
cliPatterns: [],
|
|
123
|
+
evals,
|
|
124
|
+
});
|
|
125
|
+
expect(calls).toBe(evals.length);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("谓词返回非 boolean:报错携带 experiment id 与 eval id", () => {
|
|
129
|
+
expect(() =>
|
|
130
|
+
resolveExperimentEvals({
|
|
131
|
+
experimentId: "exp/bad-return",
|
|
132
|
+
selector: () => "yes" as unknown as boolean,
|
|
133
|
+
cliPatterns: [],
|
|
134
|
+
evals: [codingFixButton],
|
|
135
|
+
}),
|
|
136
|
+
).toThrow(/exp\/bad-return.*coding\/fix-button/s);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("谓词返回 Promise:不按 truthy 接受,报错说明必须同步", () => {
|
|
140
|
+
expect(() =>
|
|
141
|
+
resolveExperimentEvals({
|
|
142
|
+
experimentId: "exp/async",
|
|
143
|
+
selector: () => Promise.resolve(true) as unknown as boolean,
|
|
144
|
+
cliPatterns: [],
|
|
145
|
+
evals: [codingFixButton],
|
|
146
|
+
}),
|
|
147
|
+
).toThrow(/Promise/);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("谓词抛错:错误携带 experiment id + eval id,并保留原 cause", () => {
|
|
151
|
+
const original = new Error("boom");
|
|
152
|
+
let thrown: unknown;
|
|
153
|
+
try {
|
|
154
|
+
resolveExperimentEvals({
|
|
155
|
+
experimentId: "exp/throws",
|
|
156
|
+
selector: (): boolean => {
|
|
157
|
+
throw original;
|
|
158
|
+
},
|
|
159
|
+
cliPatterns: [],
|
|
160
|
+
evals: [codingFixButton],
|
|
161
|
+
});
|
|
162
|
+
} catch (e) {
|
|
163
|
+
thrown = e;
|
|
164
|
+
}
|
|
165
|
+
expect(thrown).toBeInstanceOf(Error);
|
|
166
|
+
expect((thrown as Error).message).toMatch(/exp\/throws/);
|
|
167
|
+
expect((thrown as Error).message).toMatch(/coding\/fix-button/);
|
|
168
|
+
expect((thrown as Error).cause).toBe(original);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe("selectedEvalsForRun", () => {
|
|
173
|
+
it("按已解析的 selectedEvalIds 取 eval,保持 discovery 顺序", () => {
|
|
174
|
+
const a = makeEval("a");
|
|
175
|
+
const b = makeEval("b");
|
|
176
|
+
const c = makeEval("c");
|
|
177
|
+
const picked = selectedEvalsForRun([a, b, c], { selectedEvalIds: ["c", "a"] });
|
|
178
|
+
expect(picked.map((e) => e.id)).toEqual(["a", "c"]); // 顺序随 all,不随 selectedEvalIds 声明序
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
describe("EvalDescriptor 类型可从公开入口导入", () => {
|
|
183
|
+
it("类型层守卫:占位断言(真正的推断检查在 pnpm typecheck)", () => {
|
|
184
|
+
const descriptor: EvalDescriptor = { id: "x", tags: [] };
|
|
185
|
+
expect(descriptor.id).toBe("x");
|
|
186
|
+
});
|
|
187
|
+
});
|