niceeval 0.9.0 → 0.10.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 +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 +15 -7
- 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 +6 -0
- package/dist/report/components/attempt-detail/AttemptConversation.js +34 -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 +27 -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 +254 -0
- package/dist/report/components/attempt-detail/faces.d.ts +14 -0
- package/dist/report/components/attempt-detail/faces.js +235 -0
- package/dist/report/components/attempt-detail/index.d.ts +38 -0
- package/dist/report/components/attempt-detail/index.js +527 -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 +3 -3
- package/dist/report/{react → components/entity-lists}/ExperimentList.js +10 -10
- 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 +174 -0
- package/dist/report/components/entity-lists/index.d.ts +51 -0
- package/dist/report/components/entity-lists/index.js +165 -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 +26 -23
- 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} +44 -12
- package/dist/report/{tree.js → definition/tree.js} +10 -12
- 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} +11 -5
- package/dist/report/{format.js → model/format.js} +48 -4
- 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} +140 -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 +12 -2
- 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 +149 -23
- package/dist/sandbox/docker.d.ts +6 -0
- 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 +15 -1
- package/dist/shared/aggregate.js +32 -1
- 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 +1 -1
- 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 +11 -11
- 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 +35 -32
- 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 +193 -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 +43 -19
- 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 +103 -0
- package/docs-site/zh/{how-to → tutorials}/fixtures.mdx +9 -9
- package/docs-site/zh/{how-to → tutorials}/publish-report.mdx +11 -5
- 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 +59 -50
- package/docs-site/zh/tutorials/write-experiment.mdx +355 -0
- package/docs-site/zh/{how-to → tutorials}/write-send.mdx +40 -40
- package/package.json +8 -4
- 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 +77 -64
- package/src/context/types.ts +21 -21
- package/src/define.ts +13 -0
- package/src/i18n/en.ts +30 -15
- package/src/i18n/zh-CN.ts +30 -15
- package/src/index.ts +2 -0
- package/src/report/{react → assets}/colors.ts +22 -0
- package/src/report/{react → assets}/enhance.js +1 -33
- package/src/report/{react → assets}/styles.css +136 -112
- 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 +110 -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 +80 -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 +680 -0
- package/src/report/components/attempt-detail/compute.ts +287 -0
- package/src/report/components/attempt-detail/faces.ts +257 -0
- package/src/report/components/attempt-detail/index.tsx +583 -0
- package/src/report/components/attempt-detail/validate.test.ts +235 -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 +23 -20
- package/src/report/components/entity-lists/compute.ts +196 -0
- package/src/report/components/entity-lists/faces.ts +207 -0
- package/src/report/components/entity-lists/index.tsx +241 -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 +41 -33
- 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 +47 -17
- 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} +58 -22
- package/src/report/index.ts +126 -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} +59 -5
- 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} +143 -22
- package/src/report/react/index.tsx +52 -26
- package/src/report/{dual-render.test.tsx → runtime/dual-render.test.tsx} +564 -49
- 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 +54 -3
- package/src/results/select.ts +30 -6
- 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 +158 -23
- package/src/sandbox/docker.ts +7 -0
- package/src/sandbox/e2b.ts +12 -13
- 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 +30 -1
- 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 +58 -22
- package/src/show/render.ts +16 -240
- package/src/show/show.test.ts +128 -35
- package/src/view/app/App.test.tsx +8 -7
- package/src/view/app/App.tsx +135 -51
- 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 +122 -89
- package/src/view/index.ts +3 -13
- package/src/view/server.ts +36 -6
- 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 +18 -806
- package/src/view/view-report.test.ts +182 -34
- 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/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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// 纯选题边界:发现结果 → EvalDescriptor 投影 → 用户谓词求值 → selectedEvalIds。
|
|
2
|
+
// CLI 在构造每个 AgentRun 前对候选 eval 各调用一次谓词并把结果落进这里;下游(dry-run、
|
|
3
|
+
// sandbox 查表、fingerprint/carry、attempt 展开、hook ctx、快照)只消费返回的
|
|
4
|
+
// selectedEvalIds,不重新调用用户谓词(见 docs/feature/experiments/library.md「evals」)。
|
|
5
|
+
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
import { evalPrefixPredicate } from "../shared/aggregate.ts";
|
|
8
|
+
import type { AgentRun, DiscoveredEval, EvalDescriptor, ExperimentDef } from "./types.ts";
|
|
9
|
+
|
|
10
|
+
/** `DiscoveredEval` → 用户谓词可见的显式白名单投影;不透传内部路径/执行字段。 */
|
|
11
|
+
export function evalDescriptorOf(evalDef: DiscoveredEval): EvalDescriptor {
|
|
12
|
+
return Object.freeze({
|
|
13
|
+
id: evalDef.id,
|
|
14
|
+
...(evalDef.description !== undefined ? { description: evalDef.description } : {}),
|
|
15
|
+
tags: Object.freeze([...(evalDef.tags ?? [])]),
|
|
16
|
+
...(evalDef.environment !== undefined ? { environment: evalDef.environment } : {}),
|
|
17
|
+
...(evalDef.metadata !== undefined ? { metadata: Object.freeze({ ...evalDef.metadata }) } : {}),
|
|
18
|
+
}) as EvalDescriptor;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ResolveExperimentEvalsInput {
|
|
22
|
+
experimentId: string;
|
|
23
|
+
selector: ExperimentDef["evals"];
|
|
24
|
+
cliPatterns: readonly string[];
|
|
25
|
+
evals: readonly DiscoveredEval[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ResolveExperimentEvalsResult {
|
|
29
|
+
selectedEvals: readonly DiscoveredEval[];
|
|
30
|
+
selectedEvalIds: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 对某个 experiment 的候选 eval 集合各求值谓词一次,与 CLI 追加的位置参数前缀取交集。
|
|
35
|
+
* 返回顺序 = discovery 稳定顺序,id 去重——这是 dry-run、attempt 派发顺序与落盘
|
|
36
|
+
* `selectedEvalIds` 的共同来源(见 docs/feature/eval/README.md「路径即身份」)。
|
|
37
|
+
*/
|
|
38
|
+
export function resolveExperimentEvals(input: ResolveExperimentEvalsInput): ResolveExperimentEvalsResult {
|
|
39
|
+
const { experimentId, selector, cliPatterns, evals } = input;
|
|
40
|
+
const patternFilter = evalPrefixPredicate(cliPatterns.length > 0 ? [...cliPatterns] : undefined);
|
|
41
|
+
|
|
42
|
+
let selectorFilter: (evalDef: DiscoveredEval) => boolean;
|
|
43
|
+
if (selector === undefined || selector === "*") {
|
|
44
|
+
selectorFilter = () => true;
|
|
45
|
+
} else if (typeof selector === "function") {
|
|
46
|
+
const predicate = selector;
|
|
47
|
+
selectorFilter = (evalDef) => {
|
|
48
|
+
let result: unknown;
|
|
49
|
+
try {
|
|
50
|
+
result = predicate(evalDescriptorOf(evalDef));
|
|
51
|
+
} catch (e) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`experiment "${experimentId}" evals predicate threw for eval "${evalDef.id}": ` +
|
|
54
|
+
`${e instanceof Error ? e.message : String(e)}`,
|
|
55
|
+
{ cause: e },
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (result instanceof Promise) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`experiment "${experimentId}" evals predicate returned a Promise for eval "${evalDef.id}"; ` +
|
|
61
|
+
"the predicate must be synchronous — do not await inside evals().",
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
if (typeof result !== "boolean") {
|
|
65
|
+
throw new Error(
|
|
66
|
+
`experiment "${experimentId}" evals predicate returned ${JSON.stringify(result)} (not a boolean) ` +
|
|
67
|
+
`for eval "${evalDef.id}".`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
};
|
|
72
|
+
} else {
|
|
73
|
+
const arrayFilter = evalPrefixPredicate([...selector]);
|
|
74
|
+
selectorFilter = (evalDef) => arrayFilter(evalDef.id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const seen = new Set<string>();
|
|
78
|
+
const selectedEvals: DiscoveredEval[] = [];
|
|
79
|
+
for (const evalDef of evals) {
|
|
80
|
+
if (seen.has(evalDef.id)) continue;
|
|
81
|
+
if (!selectorFilter(evalDef) || !patternFilter(evalDef.id)) continue;
|
|
82
|
+
seen.add(evalDef.id);
|
|
83
|
+
selectedEvals.push(evalDef);
|
|
84
|
+
}
|
|
85
|
+
return { selectedEvals, selectedEvalIds: selectedEvals.map((e) => e.id) };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* `evals` 选择器的审计指纹(数组内容 / 函数体哈希),进 `ExperimentRunInfo.evalFilterFingerprint`,
|
|
90
|
+
* 供「配置没变」判断;不存选择器本身、不参与报告选题(选题权威是 `selectedEvalIds`)。
|
|
91
|
+
*/
|
|
92
|
+
export function fingerprintEvalsFilter(evals: ExperimentDef["evals"], patterns: readonly string[]): string {
|
|
93
|
+
const basis =
|
|
94
|
+
evals === undefined || evals === "*"
|
|
95
|
+
? "*"
|
|
96
|
+
: Array.isArray(evals)
|
|
97
|
+
? JSON.stringify([...evals].sort())
|
|
98
|
+
: evals.toString();
|
|
99
|
+
return createHash("sha256").update(JSON.stringify({ basis, patterns })).digest("hex").slice(0, 16);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** 所有消费者按已解析的 `selectedEvalIds` 取 eval;不持有、不调用用户谓词。 */
|
|
103
|
+
export function selectedEvalsForRun(
|
|
104
|
+
all: readonly DiscoveredEval[],
|
|
105
|
+
run: Pick<AgentRun, "selectedEvalIds">,
|
|
106
|
+
): DiscoveredEval[] {
|
|
107
|
+
const ids = new Set(run.selectedEvalIds);
|
|
108
|
+
return all.filter((e) => ids.has(e.id));
|
|
109
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/experiments-runner/cases.md
|
|
2
|
+
// bug: memory/force-exit-skips-experiment-teardown.md
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
drainExperimentTeardowns,
|
|
6
|
+
pendingExperimentTeardownCount,
|
|
7
|
+
registerExperimentTeardown,
|
|
8
|
+
unregisterExperimentTeardown,
|
|
9
|
+
} from "./experiment-cleanup-registry.ts";
|
|
10
|
+
|
|
11
|
+
/** 模拟 runner 登记的执行体形态(见 run.ts 的 runExperimentTeardown):memoized 一次性
|
|
12
|
+
* promise,settle 后自行注销——注册表契约的一半在登记方,测试按同一形态构造。 */
|
|
13
|
+
function registerMemoizedEntry(id: string, body: () => Promise<void>): () => Promise<void> {
|
|
14
|
+
let p: Promise<void> | undefined;
|
|
15
|
+
const run = () =>
|
|
16
|
+
(p ??= body()
|
|
17
|
+
.catch(() => {})
|
|
18
|
+
.finally(() => unregisterExperimentTeardown(id)));
|
|
19
|
+
registerExperimentTeardown(id, run);
|
|
20
|
+
return run;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("experiment-cleanup-registry", () => {
|
|
24
|
+
it("drain 启动所有登记条目各一次并等到 settle;条目自注销后再次 drain 无动作", async () => {
|
|
25
|
+
const calls: string[] = [];
|
|
26
|
+
registerMemoizedEntry("exp-a", async () => {
|
|
27
|
+
calls.push("a");
|
|
28
|
+
});
|
|
29
|
+
registerMemoizedEntry("exp-b", async () => {
|
|
30
|
+
calls.push("b");
|
|
31
|
+
});
|
|
32
|
+
expect(pendingExperimentTeardownCount()).toBe(2);
|
|
33
|
+
|
|
34
|
+
expect(await drainExperimentTeardowns()).toBe(2);
|
|
35
|
+
expect(calls.sort()).toEqual(["a", "b"]);
|
|
36
|
+
expect(pendingExperimentTeardownCount()).toBe(0);
|
|
37
|
+
|
|
38
|
+
expect(await drainExperimentTeardowns()).toBe(0);
|
|
39
|
+
expect(calls).toHaveLength(2);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("drain 与正常路径并发到达同一入口:执行恰好一次,双方都等到 settle", async () => {
|
|
43
|
+
let ran = 0;
|
|
44
|
+
let release!: () => void;
|
|
45
|
+
const gate = new Promise<void>((r) => {
|
|
46
|
+
release = r;
|
|
47
|
+
});
|
|
48
|
+
const run = registerMemoizedEntry("exp-concurrent", async () => {
|
|
49
|
+
ran += 1;
|
|
50
|
+
await gate;
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
let normalSettled = false;
|
|
54
|
+
let drainSettled = false;
|
|
55
|
+
const normal = run().then(() => {
|
|
56
|
+
normalSettled = true;
|
|
57
|
+
});
|
|
58
|
+
const drain = drainExperimentTeardowns().then(() => {
|
|
59
|
+
drainSettled = true;
|
|
60
|
+
});
|
|
61
|
+
// 双方都在等同一个在飞 promise:gate 未放行前谁都不 settle。
|
|
62
|
+
await Promise.resolve();
|
|
63
|
+
expect(ran).toBe(1);
|
|
64
|
+
expect(normalSettled).toBe(false);
|
|
65
|
+
expect(drainSettled).toBe(false);
|
|
66
|
+
|
|
67
|
+
release();
|
|
68
|
+
await Promise.all([normal, drain]);
|
|
69
|
+
expect(ran).toBe(1);
|
|
70
|
+
expect(pendingExperimentTeardownCount()).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("settle 已注销(正常路径收尾完成)后 drain 无动作;执行体抛错不让 drain 抛", async () => {
|
|
74
|
+
let ran = 0;
|
|
75
|
+
const run = registerMemoizedEntry("exp-consumed", async () => {
|
|
76
|
+
ran += 1;
|
|
77
|
+
});
|
|
78
|
+
await run(); // 正常路径先收尾:settle 后自注销
|
|
79
|
+
expect(pendingExperimentTeardownCount()).toBe(0);
|
|
80
|
+
|
|
81
|
+
registerMemoizedEntry("exp-throws", async () => {
|
|
82
|
+
throw new Error("boom");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
expect(await drainExperimentTeardowns()).toBe(1);
|
|
86
|
+
expect(ran).toBe(1);
|
|
87
|
+
expect(pendingExperimentTeardownCount()).toBe(0);
|
|
88
|
+
});
|
|
89
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// 实验级 teardown 的宿主机侧登记表 + 强清兜底,与 sandbox/registry.ts 同一模式。
|
|
2
|
+
//
|
|
3
|
+
// 正常路径(per-attempt 计数归零 / run 收尾扫尾)在 runner 的 fiber 里执行 teardown;强清退出
|
|
4
|
+
// (二次 Ctrl+C / 看门狗 / main() 崩溃路径)时 fiber 可能来不及走到那一步,cli 需要一个独立于
|
|
5
|
+
// Effect 的入口把「还没 settle 的实验级 teardown」收口,否则隧道/容器这类宿主机资源随
|
|
6
|
+
// process.exit 变成孤儿(契约见 docs/cli.md「中断:三级响应」)。
|
|
7
|
+
//
|
|
8
|
+
// 登记的闭包由 runner 构造,执行体是 memoized 的一次性 promise(见 run.ts 的
|
|
9
|
+
// runExperimentTeardown):正常路径、drain、崩溃路径谁先到都启动同一个 promise,后到者等到
|
|
10
|
+
// 同一个结果——不双跑、也不空转。条目在 settle 后由闭包自己注销,所以 drain 的完整语义就是
|
|
11
|
+
// 「启动全部未启动 + 等待全部未 settle」;在飞中的 teardown 对 drain 同样可等待,这正是
|
|
12
|
+
// 强清「事件驱动收口」的数据基础。
|
|
13
|
+
|
|
14
|
+
const pending = new Map<string, () => Promise<void>>();
|
|
15
|
+
|
|
16
|
+
/** 登记一个实验的 teardown 入口(实验生命周期触发时点调用;setup 尚未完成也已可达)。 */
|
|
17
|
+
export function registerExperimentTeardown(experimentId: string, run: () => Promise<void>): void {
|
|
18
|
+
pending.set(experimentId, run);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** teardown settle 后注销;不存在时是 no-op。 */
|
|
22
|
+
export function unregisterExperimentTeardown(experimentId: string): void {
|
|
23
|
+
pending.delete(experimentId);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function pendingExperimentTeardownCount(): number {
|
|
27
|
+
return pending.size;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 强清兜底:启动所有还登记着的实验级 teardown 并等待它们 settle(各执行体自己有界、自己兜错,
|
|
32
|
+
* 绝不抛;已在飞的返回同一个 memoized promise,等待而非重跑)。返回本次等待的条目数。
|
|
33
|
+
* 与 stopAllSandboxes 同语义:重复调用安全,表空时是 no-op。
|
|
34
|
+
*/
|
|
35
|
+
export async function drainExperimentTeardowns(): Promise<number> {
|
|
36
|
+
const entries = [...pending.values()];
|
|
37
|
+
await Promise.allSettled(entries.map((run) => run()));
|
|
38
|
+
return entries.length;
|
|
39
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// cases: docs/engineering/unit-tests/experiments-runner/cases.md
|
|
2
|
+
// 登记行:ExperimentDef.labels 值域 string | number(解析时校验),原样投影进快照
|
|
3
|
+
// ExperimentRunInfo.labels;不透传 ctx / t,不参与可比性配置。
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
|
|
7
|
+
import { defineExperiment } from "../define.ts";
|
|
8
|
+
import { experimentRunInfo } from "./attempt.ts";
|
|
9
|
+
import { comparabilityConfigOf, deepEqualJson } from "../results/select.ts";
|
|
10
|
+
import type { Snapshot } from "../results/types.ts";
|
|
11
|
+
import type { Agent } from "../agents/types.ts";
|
|
12
|
+
import type { AgentRun } from "./types.ts";
|
|
13
|
+
|
|
14
|
+
const fakeAgent = { name: "fake" } as unknown as Agent;
|
|
15
|
+
|
|
16
|
+
function runWith(labels?: Record<string, string | number>): AgentRun {
|
|
17
|
+
return {
|
|
18
|
+
agent: fakeAgent,
|
|
19
|
+
flags: {},
|
|
20
|
+
runs: 1,
|
|
21
|
+
earlyExit: true,
|
|
22
|
+
selectedEvalIds: [],
|
|
23
|
+
...(labels !== undefined ? { labels } : {}),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
describe("ExperimentDef.labels", () => {
|
|
28
|
+
it("值域 string | number:合法值通过,布尔 / 对象在解析时报错", () => {
|
|
29
|
+
expect(() =>
|
|
30
|
+
defineExperiment({ agent: fakeAgent, labels: { line: "codex", contextK: 32 } }),
|
|
31
|
+
).not.toThrow();
|
|
32
|
+
expect(() =>
|
|
33
|
+
defineExperiment({ agent: fakeAgent, labels: { on: true as unknown as string } }),
|
|
34
|
+
).toThrow(/labels\.on/);
|
|
35
|
+
expect(() =>
|
|
36
|
+
defineExperiment({ agent: fakeAgent, labels: { nested: { a: 1 } as unknown as string } }),
|
|
37
|
+
).toThrow(/labels\.nested/);
|
|
38
|
+
expect(() =>
|
|
39
|
+
defineExperiment({ agent: fakeAgent, labels: { nan: Number.NaN } }),
|
|
40
|
+
).toThrow(/labels\.nan/);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("原样投影进 ExperimentRunInfo.labels;未声明时字段缺省", () => {
|
|
44
|
+
const labels = { line: "codex", memory: "mempal" };
|
|
45
|
+
expect(experimentRunInfo(runWith(labels))?.labels).toEqual(labels);
|
|
46
|
+
expect(experimentRunInfo(runWith())?.labels).toBeUndefined();
|
|
47
|
+
// 空对象不落盘(与 flags 同一态度:不写空壳字段)
|
|
48
|
+
expect(experimentRunInfo(runWith({}))?.labels).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("不参与可比性配置:仅 labels 不同的两快照仍互相可比", () => {
|
|
52
|
+
const snapshotWith = (labels?: Record<string, string | number>): Snapshot =>
|
|
53
|
+
({
|
|
54
|
+
experimentId: "mem/codex",
|
|
55
|
+
agent: "codex",
|
|
56
|
+
model: "gpt-5.4",
|
|
57
|
+
experiment: {
|
|
58
|
+
runs: 1,
|
|
59
|
+
earlyExit: true,
|
|
60
|
+
selectedEvalIds: [],
|
|
61
|
+
flags: { web: true },
|
|
62
|
+
...(labels !== undefined ? { labels } : {}),
|
|
63
|
+
},
|
|
64
|
+
}) as unknown as Snapshot;
|
|
65
|
+
const a = comparabilityConfigOf(snapshotWith({ line: "codex" }));
|
|
66
|
+
const b = comparabilityConfigOf(snapshotWith({ line: "renamed", memory: "mempal" }));
|
|
67
|
+
const c = comparabilityConfigOf(snapshotWith());
|
|
68
|
+
expect(deepEqualJson(a, b)).toBe(true);
|
|
69
|
+
expect(deepEqualJson(a, c)).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
@@ -406,7 +406,7 @@ describe("最终 stdout handoff:status/summary/快照/最多 5 个失败/show
|
|
|
406
406
|
expect(text).toContain("failures: 12 total, showing 5");
|
|
407
407
|
for (let i = 0; i < 5; i++) expect(text).toContain(`memory/eval-${i}`);
|
|
408
408
|
expect(text).not.toContain("memory/eval-5 ");
|
|
409
|
-
expect(text).toContain("… 7 more; inspect the JSON result or run `niceeval view
|
|
409
|
+
expect(text).toContain("… 7 more; inspect the JSON result or run `niceeval view`");
|
|
410
410
|
});
|
|
411
411
|
|
|
412
412
|
it("快照路径超过上限时折叠成 '… N more'", async () => {
|
|
@@ -502,3 +502,35 @@ describe("`--dry --output agent`:不经 coordinator,直接渲染 PLAN envelope",
|
|
|
502
502
|
expect(text.split("\n")).toHaveLength(3);
|
|
503
503
|
});
|
|
504
504
|
});
|
|
505
|
+
|
|
506
|
+
// ───────────────────────── 实验级钩子起止行(cases.md「实验级生命周期」) ─────────────────────────
|
|
507
|
+
|
|
508
|
+
describe("agent: 实验级钩子起止各追加一行", () => {
|
|
509
|
+
it("experiment_setup / experiment_teardown 各含 experiment 与 status 字段,done/failed 带 duration", async () => {
|
|
510
|
+
const { fake, coordinator } = setup();
|
|
511
|
+
coordinator.start(plan({ totalRuns: 2, evals: 2 }));
|
|
512
|
+
coordinator.experimentHook({ experimentId: "compare/bub-e2b", hook: "setup", status: "started" });
|
|
513
|
+
coordinator.experimentHook({ experimentId: "compare/bub-e2b", hook: "setup", status: "done", durationMs: 42_000 });
|
|
514
|
+
coordinator.experimentHook({ experimentId: "compare/bub-e2b", hook: "teardown", status: "started" });
|
|
515
|
+
coordinator.experimentHook({ experimentId: "compare/bub-e2b", hook: "teardown", status: "failed", durationMs: 3_000 });
|
|
516
|
+
await flush();
|
|
517
|
+
const lines = fake.stderr.writes.join("").split("\n");
|
|
518
|
+
expect(lines).toContain("NICEEVAL experiment_setup experiment=compare/bub-e2b status=started");
|
|
519
|
+
expect(lines).toContain("NICEEVAL experiment_setup experiment=compare/bub-e2b status=done duration=42s");
|
|
520
|
+
expect(lines).toContain("NICEEVAL experiment_teardown experiment=compare/bub-e2b status=started");
|
|
521
|
+
expect(lines).toContain("NICEEVAL experiment_teardown experiment=compare/bub-e2b status=failed duration=3s");
|
|
522
|
+
await coordinator.finish({ summary: summary(), completion: completion(), paths: [] });
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("实验级 progress 与 activity 在 agent 下零输出(短命状态不进 checkpoint 流)", async () => {
|
|
526
|
+
const { fake, coordinator } = setup();
|
|
527
|
+
coordinator.start(plan());
|
|
528
|
+
await flush(); // 先排空 start 事件的队列任务,再取零输出基线
|
|
529
|
+
const before = fake.stderr.writes.length;
|
|
530
|
+
coordinator.experimentProgress({ experimentId: "compare/bub-e2b", detail: "starting tunnel" });
|
|
531
|
+
coordinator.activity("prechecking judge config...");
|
|
532
|
+
await flush();
|
|
533
|
+
expect(fake.stderr.writes.slice(before).join("")).toBe("");
|
|
534
|
+
await coordinator.finish({ summary: summary(), completion: completion(), paths: [] });
|
|
535
|
+
});
|
|
536
|
+
});
|
|
@@ -126,6 +126,17 @@ export function createAgentRenderer(options: AgentRendererOptions): FeedbackRend
|
|
|
126
126
|
return;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
case "experiment-hook": {
|
|
130
|
+
// 实验级钩子起止各一行(见 cli.md「实验级钩子的显示」):agent 没有动态区域,
|
|
131
|
+
// 长 setup 期间只有 heartbeat 的日志无法区分「钩子在跑」和「挂死」。
|
|
132
|
+
noteCheckpoint(event.at);
|
|
133
|
+
const word = event.hook === "setup" ? "experiment_setup" : "experiment_teardown";
|
|
134
|
+
const parts = [`NICEEVAL ${word}`, kv("experiment", event.experimentId), kv("status", event.status)];
|
|
135
|
+
if (event.durationMs !== undefined) parts.push(kv("duration", formatElapsedSeconds(event.durationMs)));
|
|
136
|
+
io.stderr.write(parts.join(" ") + "\n");
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
129
140
|
case "interrupted": {
|
|
130
141
|
noteCheckpoint(event.at);
|
|
131
142
|
if (!isFirstOccurrence(state, "interrupted")) return;
|
|
@@ -287,19 +298,6 @@ function summaryLine(summary: RunSummary, completion: RunCompletion, reused: num
|
|
|
287
298
|
return `summary: ${summary.passed} passed, ${summary.failed} failed, ${summary.errored} errored (${paren.join(", ")})`;
|
|
288
299
|
}
|
|
289
300
|
|
|
290
|
-
/** 快照路径的共同「组」段(如 `.niceeval/compare/bub-e2b/<snapshot>` → `compare`),用于
|
|
291
|
-
* over-limit 提示里的 `niceeval view <group>`。多个 experiment 组混在一次 invocation 里时
|
|
292
|
-
* 组名不唯一,不猜一个可能错的 view 目标。与 human.ts 里同名逻辑刻意分开实现(见文件顶部
|
|
293
|
-
* 注释),不是共享同一个函数。 */
|
|
294
|
-
function deriveResultGroup(paths: readonly string[]): string | undefined {
|
|
295
|
-
const groups = new Set<string>();
|
|
296
|
-
for (const p of paths) {
|
|
297
|
-
const seg = p.split("/")[1];
|
|
298
|
-
if (seg) groups.add(seg);
|
|
299
|
-
}
|
|
300
|
-
return groups.size === 1 ? [...groups][0] : undefined;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
301
|
function writeHandoff(
|
|
304
302
|
io: FeedbackIO,
|
|
305
303
|
pending: { summary: RunSummary; completion: RunCompletion; reused: number } | undefined,
|
|
@@ -323,11 +321,9 @@ function writeHandoff(
|
|
|
323
321
|
if (failures.length > 0) {
|
|
324
322
|
const shown = failures.slice(0, AGENT_FAILURE_CAP);
|
|
325
323
|
if (failures.length > AGENT_FAILURE_CAP) {
|
|
326
|
-
const group = deriveResultGroup(paths);
|
|
327
|
-
const hint = group ? `run \`niceeval view ${group}\`` : "check the results directory";
|
|
328
324
|
lines.push(`failures: ${failures.length} total, showing ${AGENT_FAILURE_CAP}`);
|
|
329
325
|
for (const f of shown) lines.push(...handoffFailureLines(f));
|
|
330
|
-
lines.push(` … ${failures.length - AGENT_FAILURE_CAP} more; inspect the JSON result or
|
|
326
|
+
lines.push(` … ${failures.length - AGENT_FAILURE_CAP} more; inspect the JSON result or run \`niceeval view\``);
|
|
331
327
|
} else {
|
|
332
328
|
lines.push("failures:");
|
|
333
329
|
for (const f of shown) lines.push(...handoffFailureLines(f));
|
|
@@ -197,7 +197,7 @@ describe("start 立即追加;仅连续 60 秒无永久事件才 heartbeat;failur
|
|
|
197
197
|
"niceeval: result=failed passed=23 failed=1 errored=0 reused=18 duration=128s",
|
|
198
198
|
"niceeval: json=.niceeval/ci-summary.json",
|
|
199
199
|
"niceeval: junit=.niceeval/junit.xml",
|
|
200
|
-
"niceeval: snapshots
|
|
200
|
+
"niceeval: snapshots=<3 snapshots>",
|
|
201
201
|
]);
|
|
202
202
|
expect(fake.stderr.writes.join("")).toBe("");
|
|
203
203
|
});
|
|
@@ -560,3 +560,37 @@ describe("集成测试覆盖 cli.md「CI 常见 case」的三条命令", () => {
|
|
|
560
560
|
expect(computeCiExitCode(summary({ passed: 36, failed: 0, errored: 0 }), runCompletion)).toBe(1);
|
|
561
561
|
});
|
|
562
562
|
});
|
|
563
|
+
|
|
564
|
+
// ───────────────────────── 实验级钩子起止行(cases.md「实验级生命周期」) ─────────────────────────
|
|
565
|
+
|
|
566
|
+
describe("ci: 实验级钩子起止各追加一行", () => {
|
|
567
|
+
it("experiment_setup / experiment_teardown 各含 experiment 与 status 字段,done/failed 带 duration", async () => {
|
|
568
|
+
const { fake, coordinator } = setup();
|
|
569
|
+
coordinator.start(plan({ totalRuns: 2, evals: 2 }));
|
|
570
|
+
coordinator.experimentHook({ experimentId: "ci/claude", hook: "setup", status: "started" });
|
|
571
|
+
coordinator.experimentHook({ experimentId: "ci/claude", hook: "setup", status: "done", durationMs: 42_000 });
|
|
572
|
+
coordinator.experimentHook({ experimentId: "ci/claude", hook: "teardown", status: "started" });
|
|
573
|
+
coordinator.experimentHook({ experimentId: "ci/claude", hook: "teardown", status: "failed", durationMs: 3_000 });
|
|
574
|
+
await flush();
|
|
575
|
+
const lines = fake.stdout.writes.join("").split("\n");
|
|
576
|
+
expect(lines).toContain("niceeval: experiment_setup experiment=ci/claude status=started");
|
|
577
|
+
expect(lines).toContain("niceeval: experiment_setup experiment=ci/claude status=done duration=42s");
|
|
578
|
+
expect(lines).toContain("niceeval: experiment_teardown experiment=ci/claude status=started");
|
|
579
|
+
expect(lines).toContain("niceeval: experiment_teardown experiment=ci/claude status=failed duration=3s");
|
|
580
|
+
await coordinator.finish({ summary: summary(), completion: completion(), paths: [] });
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("实验级 progress 与 activity 在 ci 下零输出(短命状态不进事件流)", async () => {
|
|
584
|
+
const { fake, coordinator } = setup();
|
|
585
|
+
coordinator.start(plan());
|
|
586
|
+
await flush(); // 先让 start 行落盘,再取零输出基线
|
|
587
|
+
const beforeOut = fake.stdout.writes.length;
|
|
588
|
+
const beforeErr = fake.stderr.writes.length;
|
|
589
|
+
coordinator.experimentProgress({ experimentId: "ci/claude", detail: "starting tunnel" });
|
|
590
|
+
coordinator.activity("prechecking judge config...");
|
|
591
|
+
await flush();
|
|
592
|
+
expect(fake.stdout.writes.slice(beforeOut).join("")).toBe("");
|
|
593
|
+
expect(fake.stderr.writes.slice(beforeErr).join("")).toBe("");
|
|
594
|
+
await coordinator.finish({ summary: summary(), completion: completion(), paths: [] });
|
|
595
|
+
});
|
|
596
|
+
});
|
|
@@ -137,6 +137,17 @@ export function createCiRenderer(options: CiRendererOptions): FeedbackRenderer {
|
|
|
137
137
|
return;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
+
case "experiment-hook": {
|
|
141
|
+
// 实验级钩子起止各一行(见 cli.md「实验级钩子的显示」):CI 日志没有动态区域,
|
|
142
|
+
// 长 setup 期间只有 heartbeat 无法区分「钩子在跑」和「挂死」。
|
|
143
|
+
noteCheckpoint(event.at);
|
|
144
|
+
const word = event.hook === "setup" ? "experiment_setup" : "experiment_teardown";
|
|
145
|
+
const parts = [`niceeval: ${word}`, kv("experiment", event.experimentId), kv("status", event.status)];
|
|
146
|
+
if (event.durationMs !== undefined) parts.push(kv("duration", formatCiSeconds(event.durationMs)));
|
|
147
|
+
io.stdout.write(parts.join(" ") + "\n");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
140
151
|
case "interrupted": {
|
|
141
152
|
noteCheckpoint(event.at);
|
|
142
153
|
if (!isFirstOccurrence(state, "interrupted")) return;
|
|
@@ -299,26 +310,13 @@ function resultLine(summary: RunSummary, completion: RunCompletion, reused: numb
|
|
|
299
310
|
return parts.join(" ");
|
|
300
311
|
}
|
|
301
312
|
|
|
302
|
-
/**
|
|
303
|
-
*
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
-
function deriveResultGroup(paths: readonly string[]): string | undefined {
|
|
307
|
-
const groups = new Set<string>();
|
|
308
|
-
for (const p of paths) {
|
|
309
|
-
const seg = p.split("/")[1];
|
|
310
|
-
if (seg) groups.add(seg);
|
|
311
|
-
}
|
|
312
|
-
return groups.size === 1 ? [...groups][0] : undefined;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/** 单条路径直接原样打印;多条折叠成 `<N snapshots>`(见 cli.md「CI 怎么用」的字面例子:
|
|
316
|
-
* `.niceeval/ci/<3 snapshots>`)——CI「同一种事件一行」的整体约束(checklist/docs 都强调
|
|
317
|
-
* 一行一事件,方便日志搜索)决定了这里不能像 human/agent 那样逐条缩进列出。 */
|
|
313
|
+
/** 单条路径直接原样打印;多条折叠成不带路径前缀的 `<N snapshots>`(见 cli.md「CI 怎么用」的
|
|
314
|
+
* 字面例子)——不再从路径推导「共同目录段」去拼前缀,那是运行选择的展示,不是报告分组,
|
|
315
|
+
* 也不该由这里悄悄猜一个可能跨实验误配的前缀。CI「同一种事件一行」的整体约束(checklist/docs
|
|
316
|
+
* 都强调一行一事件,方便日志搜索)决定了这里不能像 human/agent 那样逐条缩进列出。 */
|
|
318
317
|
function snapshotsValue(paths: readonly string[]): string {
|
|
319
318
|
if (paths.length === 1) return paths[0]!;
|
|
320
|
-
|
|
321
|
-
return group ? `.niceeval/${group}/<${paths.length} snapshots>` : `<${paths.length} snapshots>`;
|
|
319
|
+
return `<${paths.length} snapshots>`;
|
|
322
320
|
}
|
|
323
321
|
|
|
324
322
|
function writeResultBlock(
|
|
@@ -25,6 +25,8 @@ import {
|
|
|
25
25
|
activateFeedbackSink,
|
|
26
26
|
type BudgetExhaustedInput,
|
|
27
27
|
type DiagnosticInput,
|
|
28
|
+
type ExperimentHookInput,
|
|
29
|
+
type ExperimentProgressInput,
|
|
28
30
|
type FailureInput,
|
|
29
31
|
type FeedbackSink,
|
|
30
32
|
type KeptInput,
|
|
@@ -144,6 +146,10 @@ export function createFeedbackCoordinator(options: FeedbackCoordinatorOptions):
|
|
|
144
146
|
case "attempt:early-exit":
|
|
145
147
|
queue.push(() => renderer.onLifecycle?.(event, snapshot));
|
|
146
148
|
return;
|
|
149
|
+
case "experiment:progress":
|
|
150
|
+
// 短命状态,只进 reducer(上面已更新 state.experimentHooks 的 detail);下一次 tick 的
|
|
151
|
+
// 重画会读到新值,不为每条 progress 单独排一次渲染任务。
|
|
152
|
+
return;
|
|
147
153
|
case "tick":
|
|
148
154
|
queue.push(() => renderer.onTick?.(event, snapshot));
|
|
149
155
|
return;
|
|
@@ -242,6 +248,21 @@ export function createFeedbackCoordinator(options: FeedbackCoordinatorOptions):
|
|
|
242
248
|
});
|
|
243
249
|
}
|
|
244
250
|
|
|
251
|
+
function experimentHook(input: ExperimentHookInput): void {
|
|
252
|
+
emit({
|
|
253
|
+
type: "experiment-hook",
|
|
254
|
+
at: io.clock.now(),
|
|
255
|
+
experimentId: input.experimentId,
|
|
256
|
+
hook: input.hook,
|
|
257
|
+
status: input.status,
|
|
258
|
+
...(input.durationMs !== undefined ? { durationMs: input.durationMs } : {}),
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function experimentProgress(input: ExperimentProgressInput): void {
|
|
263
|
+
emit({ type: "experiment:progress", at: io.clock.now(), experimentId: input.experimentId, detail: input.detail });
|
|
264
|
+
}
|
|
265
|
+
|
|
245
266
|
function reporterError(input: { reporter: string; required: boolean; message: string }): void {
|
|
246
267
|
emit({
|
|
247
268
|
type: "reporter-error",
|
|
@@ -280,6 +301,8 @@ export function createFeedbackCoordinator(options: FeedbackCoordinatorOptions):
|
|
|
280
301
|
failure,
|
|
281
302
|
budgetExhausted,
|
|
282
303
|
kept,
|
|
304
|
+
experimentHook,
|
|
305
|
+
experimentProgress,
|
|
283
306
|
lifecycle,
|
|
284
307
|
});
|
|
285
308
|
emit({ type: "plan", at: startedAtMs, plan });
|
|
@@ -343,6 +366,8 @@ export function createFeedbackCoordinator(options: FeedbackCoordinatorOptions):
|
|
|
343
366
|
failure,
|
|
344
367
|
budgetExhausted,
|
|
345
368
|
kept,
|
|
369
|
+
experimentHook,
|
|
370
|
+
experimentProgress,
|
|
346
371
|
lifecycle,
|
|
347
372
|
stopDynamic,
|
|
348
373
|
finish,
|
|
@@ -367,6 +392,9 @@ function fallbackTextFor(event: DurableFeedbackEvent): string | undefined {
|
|
|
367
392
|
case "plan":
|
|
368
393
|
case "summary":
|
|
369
394
|
case "saved":
|
|
395
|
+
case "experiment-hook":
|
|
396
|
+
// 钩子起止不是失败证据:setup 失败的每条 attempt 另有 "failure" 事件兜底,
|
|
397
|
+
// renderer 崩溃丢一行起止不丢数据。
|
|
370
398
|
return undefined;
|
|
371
399
|
}
|
|
372
400
|
}
|