niceeval 0.6.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INDEX.md +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +69 -7
- package/dist/context/types.d.ts +32 -12
- package/dist/i18n/en.d.ts +54 -0
- package/dist/i18n/zh-CN.d.ts +57 -3
- package/dist/o11y/types.d.ts +16 -2
- package/dist/report/aggregate.d.ts +32 -24
- package/dist/report/aggregate.js +158 -50
- package/dist/report/built-in/index.d.ts +2 -0
- package/dist/report/built-in/index.js +8 -0
- package/dist/report/components.d.ts +93 -160
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +87 -81
- package/dist/report/compute.js +597 -417
- package/dist/report/flag.d.ts +32 -6
- package/dist/report/flag.js +92 -4
- package/dist/report/format.d.ts +19 -11
- package/dist/report/format.js +30 -13
- package/dist/report/index.d.ts +16 -16
- package/dist/report/index.js +20 -21
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +57 -33
- package/dist/report/locale.js +122 -56
- package/dist/report/metrics.d.ts +23 -4
- package/dist/report/metrics.js +110 -25
- package/dist/report/primitives.d.ts +48 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +9 -7
- package/dist/report/react/AttemptList.js +17 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +4 -4
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +10 -0
- package/dist/report/react/ExperimentComparison.js +12 -0
- package/dist/report/react/ExperimentList.d.ts +4 -3
- package/dist/report/react/ExperimentList.js +17 -18
- package/dist/report/react/MetricBars.js +5 -4
- package/dist/report/react/MetricLine.js +12 -5
- package/dist/report/react/MetricMatrix.js +1 -1
- package/dist/report/react/MetricScatter.js +59 -28
- package/dist/report/react/MetricTable.js +2 -12
- package/dist/report/react/ScopeSummary.d.ts +10 -0
- package/dist/report/react/ScopeSummary.js +17 -0
- package/dist/report/react/Scoreboard.js +6 -6
- package/dist/report/react/cell.js +2 -2
- package/dist/report/react/chart-math.d.ts +23 -6
- package/dist/report/react/chart-math.js +71 -19
- package/dist/report/react/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +110 -147
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -16
- package/dist/report/report.js +259 -28
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +253 -184
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +38 -7
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +247 -284
- package/dist/report/types.js +4 -3
- package/dist/report/web.d.ts +21 -5
- package/dist/report/web.js +42 -16
- package/dist/results/select.d.ts +38 -16
- package/dist/results/select.js +73 -25
- package/dist/results/types.d.ts +49 -14
- package/dist/runner/feedback/sink.d.ts +110 -0
- package/dist/runner/types.d.ts +513 -22
- package/dist/sandbox/docker.d.ts +23 -2
- package/dist/sandbox/e2b.d.ts +15 -1
- package/dist/sandbox/errors.d.ts +30 -3
- package/dist/sandbox/io-retry.d.ts +17 -0
- package/dist/sandbox/registry.d.ts +2 -0
- package/dist/sandbox/resolve.d.ts +18 -5
- package/dist/sandbox/retry.d.ts +11 -1
- package/dist/sandbox/types.d.ts +39 -5
- package/dist/sandbox/vercel.d.ts +7 -1
- package/dist/scoring/coverage.d.ts +30 -0
- package/dist/scoring/display.d.ts +21 -0
- package/dist/scoring/display.js +120 -0
- package/dist/scoring/types.d.ts +103 -20
- package/dist/shared/aggregate.d.ts +4 -2
- package/dist/shared/aggregate.js +8 -7
- package/dist/shared/types.d.ts +28 -0
- package/dist/tty-line.d.ts +0 -4
- package/dist/util.d.ts +23 -0
- package/docs-site/zh/README.md +44 -0
- package/docs-site/zh/examples/ai-agent-application.mdx +63 -0
- package/docs-site/zh/examples/coding-agent-extensions.mdx +57 -0
- package/docs-site/zh/examples/index.mdx +50 -0
- package/docs-site/zh/{concepts → explanation}/adapter.mdx +31 -13
- package/docs-site/zh/{concepts → explanation}/assert.mdx +7 -7
- package/docs-site/zh/{concepts → explanation}/drive.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/evals.mdx +4 -4
- package/docs-site/zh/{concepts → explanation}/experiment.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/hitl.mdx +8 -8
- package/docs-site/zh/{concepts → explanation}/judge.mdx +5 -5
- package/docs-site/zh/{concepts → explanation}/overview.mdx +11 -11
- package/docs-site/zh/{guides → explanation}/runner.mdx +18 -8
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +35 -33
- package/docs-site/zh/{guides → how-to}/authoring.mdx +35 -2
- package/docs-site/zh/{guides → how-to}/ci-integration.mdx +23 -12
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +47 -21
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +34 -39
- package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +25 -3
- package/docs-site/zh/{guides → how-to}/experiments.mdx +12 -5
- package/docs-site/zh/how-to/publish-report.mdx +105 -0
- package/docs-site/zh/{guides → how-to}/reporters.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +56 -7
- package/docs-site/zh/how-to/sandbox-providers.mdx +350 -0
- package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +4 -4
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +82 -39
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +6 -4
- package/docs-site/zh/{guides → how-to}/write-send.mdx +30 -14
- package/docs-site/zh/index.mdx +24 -26
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +32 -5
- package/docs-site/zh/reference/capabilities.mdx +8 -8
- package/docs-site/zh/reference/cli.mdx +40 -12
- package/docs-site/zh/reference/define-agent.mdx +58 -5
- package/docs-site/zh/reference/define-config.mdx +1 -1
- package/docs-site/zh/reference/define-eval.mdx +42 -9
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/reference/expect.mdx +26 -1
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +32 -8
- package/docs-site/zh/{guides → reference}/report-components.mdx +45 -33
- package/docs-site/zh/{guides → reference}/results-data.mdx +21 -13
- package/docs-site/zh/troubleshooting/debug-sandbox.mdx +57 -0
- package/docs-site/zh/troubleshooting/debugging.mdx +212 -0
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +10 -2
- package/src/agents/ai-sdk-otel.test.ts +1 -0
- package/src/agents/ai-sdk.test.ts +3 -0
- package/src/agents/ai-sdk.ts +3 -0
- package/src/agents/bub-install-spec.test.ts +34 -0
- package/src/agents/bub-install-spec.ts +32 -0
- package/src/agents/bub.ts +31 -32
- package/src/agents/claude-code.test.ts +130 -9
- package/src/agents/claude-code.ts +76 -4
- package/src/agents/codex.test.ts +189 -40
- package/src/agents/codex.ts +155 -14
- package/src/agents/coding-cli-versions.test.ts +15 -0
- package/src/agents/coding-cli-versions.ts +3 -0
- package/src/agents/index.ts +13 -2
- package/src/agents/langgraph.test.ts +204 -0
- package/src/agents/langgraph.ts +495 -0
- package/src/agents/marketplace.ts +85 -0
- package/src/agents/native-config.test.ts +179 -0
- package/src/agents/native-config.ts +267 -0
- package/src/agents/openai-compat.test.ts +1 -0
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/openclaw.test.ts +31 -0
- package/src/agents/openclaw.ts +171 -0
- package/src/agents/plugin-config.test.ts +1 -0
- package/src/agents/sdk-streams.test.ts +79 -0
- package/src/agents/sdk-streams.ts +55 -10
- package/src/agents/skills.test.ts +1 -0
- package/src/agents/streaming.test.ts +3 -9
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +71 -8
- package/src/agents/ui-message-stream.test.ts +3 -0
- package/src/cli.ts +446 -124
- package/src/context/context.test.ts +51 -12
- package/src/context/context.ts +162 -30
- package/src/context/session.test.ts +2 -1
- package/src/context/session.ts +115 -7
- package/src/context/types.ts +30 -12
- package/src/define.test.ts +13 -8
- package/src/define.ts +25 -4
- package/src/expect/index.ts +53 -23
- package/src/i18n/en.ts +81 -17
- package/src/i18n/zh-CN.ts +80 -17
- package/src/o11y/cost.test.ts +1 -0
- package/src/o11y/execution-tree.test.ts +1 -20
- package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
- package/src/o11y/otlp/parse.test.ts +1 -0
- package/src/o11y/otlp/turn-otel.test.ts +1 -0
- package/src/o11y/parsers/bub.test.ts +1 -0
- package/src/o11y/parsers/claude-code.test.ts +1 -34
- package/src/o11y/parsers/openclaw.test.ts +154 -0
- package/src/o11y/parsers/openclaw.ts +310 -0
- package/src/o11y/prices.json +746 -311
- package/src/o11y/tool-names.test.ts +1 -0
- package/src/o11y/types.ts +16 -2
- package/src/report/aggregate.ts +178 -61
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -279
- package/src/report/compute.ts +723 -491
- package/src/report/dual-render.test.tsx +741 -1024
- package/src/report/flag.ts +104 -12
- package/src/report/format.ts +32 -12
- package/src/report/index.ts +119 -46
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +136 -65
- package/src/report/metrics.ts +108 -25
- package/src/report/primitives.tsx +196 -45
- package/src/report/react/AttemptList.tsx +30 -43
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +73 -0
- package/src/report/react/ExperimentList.tsx +50 -32
- package/src/report/react/MetricBars.tsx +5 -4
- package/src/report/react/MetricLine.tsx +13 -8
- package/src/report/react/MetricMatrix.tsx +2 -2
- package/src/report/react/MetricScatter.tsx +86 -34
- package/src/report/react/MetricTable.tsx +4 -76
- package/src/report/react/ScopeSummary.tsx +86 -0
- package/src/report/react/Scoreboard.tsx +28 -10
- package/src/report/react/cell.tsx +2 -2
- package/src/report/react/chart-math.test.ts +85 -0
- package/src/report/react/chart-math.ts +101 -22
- package/src/report/react/enhance.js +89 -5
- package/src/report/react/fixtures.ts +114 -154
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +138 -158
- package/src/report/react/styles.css +243 -82
- package/src/report/report.test.ts +779 -841
- package/src/report/report.ts +423 -41
- package/src/report/text/faces.ts +290 -193
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +44 -7
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +261 -271
- package/src/report/web.ts +63 -20
- package/src/results/annotated-source.test.ts +62 -9
- package/src/results/annotated-source.ts +64 -6
- package/src/results/attempt-evidence.test.ts +13 -11
- package/src/results/attempt-evidence.ts +20 -13
- package/src/results/attempt-source.ts +6 -3
- package/src/results/copy.ts +150 -60
- package/src/results/host-equivalence.test.ts +34 -20
- package/src/results/index.ts +12 -4
- package/src/results/locator.test.ts +1 -22
- package/src/results/open.ts +15 -5
- package/src/results/publish.ts +149 -0
- package/src/results/results.test.ts +89 -54
- package/src/results/select.ts +104 -34
- package/src/results/truncate.ts +90 -0
- package/src/results/types.ts +43 -14
- package/src/results/writer.ts +31 -13
- package/src/runner/attempt.test.ts +138 -7
- package/src/runner/attempt.ts +603 -104
- package/src/runner/discover.test.ts +47 -0
- package/src/runner/discover.ts +36 -2
- package/src/runner/eval-source.test.ts +1 -27
- package/src/runner/feedback/agent.test.ts +504 -0
- package/src/runner/feedback/agent.ts +409 -0
- package/src/runner/feedback/ci.test.ts +562 -0
- package/src/runner/feedback/ci.ts +401 -0
- package/src/runner/feedback/coordinator.test.ts +317 -0
- package/src/runner/feedback/coordinator.ts +397 -0
- package/src/runner/feedback/failure.ts +40 -0
- package/src/runner/feedback/human.test.ts +616 -0
- package/src/runner/feedback/human.ts +535 -0
- package/src/runner/feedback/index.ts +66 -0
- package/src/runner/feedback/io.ts +78 -0
- package/src/runner/feedback/profile.test.ts +50 -0
- package/src/runner/feedback/profile.ts +58 -0
- package/src/runner/feedback/reducer.test.ts +395 -0
- package/src/runner/feedback/reducer.ts +260 -0
- package/src/runner/feedback/renderer.ts +82 -0
- package/src/runner/feedback/sink.ts +203 -0
- package/src/runner/feedback/testing.ts +106 -0
- package/src/runner/ledger.test.ts +230 -0
- package/src/runner/ledger.ts +329 -0
- package/src/runner/report.test.ts +128 -3
- package/src/runner/report.ts +33 -9
- package/src/runner/reporters/artifacts.ts +8 -2
- package/src/runner/reporters/braintrust.test.ts +8 -7
- package/src/runner/reporters/braintrust.ts +9 -2
- package/src/runner/reporters/index.ts +2 -2
- package/src/runner/reporters/json.test.ts +162 -0
- package/src/runner/reporters/json.ts +35 -8
- package/src/runner/reporters/shared.ts +1 -5
- package/src/runner/run.test.ts +760 -3
- package/src/runner/run.ts +243 -37
- package/src/runner/sandbox-prep.ts +3 -42
- package/src/runner/timing.ts +158 -0
- package/src/runner/types.ts +518 -22
- package/src/sandbox/checkpoint.test.ts +55 -0
- package/src/sandbox/checkpoint.ts +29 -8
- package/src/sandbox/cli-commands.ts +407 -0
- package/src/sandbox/docker.ts +115 -16
- package/src/sandbox/e2b-agent-template.test.ts +56 -0
- package/src/sandbox/e2b-agent-template.ts +94 -0
- package/src/sandbox/e2b.ts +74 -9
- package/src/sandbox/errors.ts +111 -4
- package/src/sandbox/index.ts +2 -0
- package/src/sandbox/io-retry.test.ts +58 -0
- package/src/sandbox/io-retry.ts +45 -0
- package/src/sandbox/keep-registry.test.ts +86 -0
- package/src/sandbox/keep-registry.ts +142 -0
- package/src/sandbox/keep.ts +178 -0
- package/src/sandbox/paths.test.ts +1 -0
- package/src/sandbox/paths.ts +19 -8
- package/src/sandbox/registry.ts +20 -3
- package/src/sandbox/resolve.ts +76 -11
- package/src/sandbox/retry.test.ts +70 -0
- package/src/sandbox/retry.ts +46 -4
- package/src/sandbox/types.ts +44 -6
- package/src/sandbox/vercel.ts +43 -20
- package/src/scoring/collector.ts +60 -17
- package/src/scoring/coverage.ts +95 -0
- package/src/scoring/diff.ts +81 -0
- package/src/scoring/display.test.ts +121 -0
- package/src/scoring/display.ts +133 -0
- package/src/scoring/evidence.test.ts +189 -0
- package/src/scoring/judge.test.ts +142 -0
- package/src/scoring/judge.ts +15 -18
- package/src/scoring/scoped.ts +217 -50
- package/src/scoring/types.ts +117 -20
- package/src/scoring/verdict.ts +16 -4
- package/src/shared/aggregate.ts +8 -6
- package/src/shared/types.ts +31 -0
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +127 -56
- package/src/show/render.ts +662 -131
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +320 -54
- package/src/tty-line.ts +8 -26
- package/src/util.test.ts +1 -0
- package/src/util.ts +41 -0
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +423 -11
- package/src/view/app/components/CodeView.tsx +41 -14
- package/src/view/app/components/CopyControls.tsx +2 -2
- package/src/view/app/i18n.ts +37 -17
- package/src/view/app/lib/attempt-route.test.ts +1 -0
- package/src/view/app/lib/verdict.ts +7 -9
- package/src/view/app/main.tsx +13 -8
- package/src/view/app/pages/{RunsPage.tsx → AttemptsPage.tsx} +6 -6
- package/src/view/app/types.ts +4 -1
- package/src/view/artifact-serving.test.ts +2 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +17 -17
- package/src/view/data.test.ts +10 -3
- package/src/view/data.ts +155 -49
- package/src/view/index.ts +56 -41
- package/src/view/server.ts +37 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +227 -0
- package/src/view/view-report.test.ts +167 -62
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -1
- package/dist/report/built-ins/experiment-comparison.js +0 -13
- package/dist/report/built-ins/index.d.ts +0 -1
- package/dist/report/built-ins/index.js +0 -2
- package/dist/report/react/GroupSummary.d.ts +0 -8
- package/dist/report/react/GroupSummary.js +0 -8
- package/dist/report/react/RunOverview.d.ts +0 -8
- package/dist/report/react/RunOverview.js +0 -12
- package/docs-site/zh/example/ai-agent-application.mdx +0 -152
- package/docs-site/zh/example/claude-code-codex-plugin.mdx +0 -167
- package/docs-site/zh/example/claude-code-codex-skill.mdx +0 -152
- package/docs-site/zh/example/showcase.mdx +0 -39
- package/docs-site/zh/guides/publish-report.mdx +0 -91
- package/docs-site/zh/guides/sandbox-providers.mdx +0 -102
- package/src/report/built-in-user-parity.test.tsx +0 -640
- package/src/report/built-ins/experiment-comparison.tsx +0 -19
- package/src/report/built-ins/index.ts +0 -2
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- package/src/runner/reporters/console.ts +0 -70
- package/src/runner/reporters/live.test.ts +0 -56
- package/src/runner/reporters/live.ts +0 -247
- package/src/runner/reporters/quiet.test.ts +0 -66
- package/src/runner/reporters/quiet.ts +0 -49
- package/src/runner/reporters/table.ts +0 -277
- /package/docs-site/zh/{example/tier1-ai-sdk-v7.mdx → examples/integrations/ai-sdk-v7.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-claude-sdk.mdx → examples/integrations/claude-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-codex-sdk.mdx → examples/integrations/codex-sdk.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-langgraph.mdx → examples/integrations/langgraph.mdx} +0 -0
- /package/docs-site/zh/{example/tier1-pi-sdk.mdx → examples/integrations/pi-sdk.mdx} +0 -0
- /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
// FeedbackCoordinator:一个 run 内唯一的终端协调者(见 docs/feature/experiments/cli.md
|
|
2
|
+
// 「输出流和落盘节奏」与 plan 第 2 节「一个 run 内只有一个终端协调者」)。
|
|
3
|
+
//
|
|
4
|
+
// 职责边界:
|
|
5
|
+
// - 维护 RunFeedbackState(纯 reducer,见 reducer.ts)——每次 emit() 后同步更新,
|
|
6
|
+
// `coordinator.state` 永远读到最新值,不经过下面的异步队列。
|
|
7
|
+
// - 把每个事件按「lifecycle / tick / durable」分派给当前 profile 的 FeedbackRenderer
|
|
8
|
+
// (renderer.ts),并对 durable 事件保证 clearDynamic → appendDurable → redrawDynamic
|
|
9
|
+
// 三步原子执行(内部单队列串行处理,不会被下一个事件插入打断)。
|
|
10
|
+
// - 驱动 tick 定时器(周期性推进 elapsedMs、给 renderer 重画/heartbeat 判断的机会),
|
|
11
|
+
// 在 stopDynamic() 时停止,停止后保证不再有 timer 驱动的重画。
|
|
12
|
+
// - 通过 sink.ts 的活跃栈,成为底层模块(sandbox provider、budget 记账、reporter 兜底……)
|
|
13
|
+
// 的统一诊断出口 —— start() 时激活自己,finish() 时退出。
|
|
14
|
+
//
|
|
15
|
+
// 不负责(留给后续阶段):
|
|
16
|
+
// - 具体怎么画(ANSI dashboard、agent envelope、CI 事件行……)—— 那是各 renderer 自己的事,
|
|
17
|
+
// coordinator 只保证「什么时候、按什么顺序」调用 renderer。
|
|
18
|
+
// - 从 runner 实际生命周期(sandbox provision / agent setup / …)推导 AttemptPhase 并发出
|
|
19
|
+
// attempt:start/phase/progress 事件 —— 那是 A2 阶段的 lifecycle 接线;coordinator 只是
|
|
20
|
+
// 这些事件的消费者,不关心它们从哪来。
|
|
21
|
+
// - 在 cli.ts 里决定何时构造 coordinator、传哪个 renderer —— 那是 CLI 接线阶段的事。
|
|
22
|
+
|
|
23
|
+
import { createInitialRunFeedbackState, reduceRunFeedback } from "./reducer.ts";
|
|
24
|
+
import {
|
|
25
|
+
activateFeedbackSink,
|
|
26
|
+
type BudgetExhaustedInput,
|
|
27
|
+
type DiagnosticInput,
|
|
28
|
+
type FailureInput,
|
|
29
|
+
type FeedbackSink,
|
|
30
|
+
type KeptInput,
|
|
31
|
+
} from "./sink.ts";
|
|
32
|
+
import type { FeedbackIO, FeedbackTimerHandle } from "./io.ts";
|
|
33
|
+
import type { FeedbackRenderer } from "./renderer.ts";
|
|
34
|
+
import { writeStderrLine } from "../../tty-line.ts";
|
|
35
|
+
import { t } from "../../i18n/index.ts";
|
|
36
|
+
import type {
|
|
37
|
+
AttemptLifecycleEvent,
|
|
38
|
+
DurableFeedbackEvent,
|
|
39
|
+
FeedbackTickEvent,
|
|
40
|
+
OutputProfile,
|
|
41
|
+
RunCompletion,
|
|
42
|
+
RunFeedbackEvent,
|
|
43
|
+
RunFeedbackPlan,
|
|
44
|
+
RunFeedbackState,
|
|
45
|
+
RunSummary,
|
|
46
|
+
} from "../types.ts";
|
|
47
|
+
|
|
48
|
+
/** 默认 tick 周期:250ms(每秒最多 4 次重画机会),对应 docs 里 human dashboard「最多每秒
|
|
49
|
+
* 4 帧」的上限 —— coordinator 只保证「不超过这个频率的重画机会」,合并/节流到「真的要不要
|
|
50
|
+
* 重画这一帧」由 renderer 自己判断(见 renderer.ts 的 onTick 注释)。 */
|
|
51
|
+
const DEFAULT_TICK_INTERVAL_MS = 250;
|
|
52
|
+
|
|
53
|
+
export interface FeedbackCoordinatorOptions {
|
|
54
|
+
profile: OutputProfile;
|
|
55
|
+
renderer: FeedbackRenderer;
|
|
56
|
+
io: FeedbackIO;
|
|
57
|
+
/** 覆盖默认 tick 周期;测试/未来某个 profile 需要更粗或更细的节奏时用。 */
|
|
58
|
+
tickIntervalMs?: number;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface FeedbackCoordinator extends FeedbackSink {
|
|
62
|
+
readonly profile: OutputProfile;
|
|
63
|
+
/** 当前 reducer 状态快照;每次 emit() 后同步更新(不经过内部异步队列,读到的永远是
|
|
64
|
+
* emit() 调用那一刻算出的最新值)。 */
|
|
65
|
+
readonly state: RunFeedbackState;
|
|
66
|
+
/**
|
|
67
|
+
* run:start 等价物:emit 一次 "plan" 永久事件、把自己注册为 sink.ts 的活跃目的地(此后
|
|
68
|
+
* `reportActivity`/`reportDiagnostic`/… 转发给它)、启动 tick 定时器。只能调用一次 ——
|
|
69
|
+
* 重复调用是编程错误(意味着同一个 coordinator 实例被跨 run 复用),直接抛错。
|
|
70
|
+
*/
|
|
71
|
+
start(plan: RunFeedbackPlan): void;
|
|
72
|
+
/**
|
|
73
|
+
* 任意 `RunFeedbackEvent` 的通用入口:lifecycle/tick/durable 都走这里。`diagnostic()`/
|
|
74
|
+
* `activity()`/`interrupted()`/`reporterError()` 是构造 durable 事件的便捷封装,内部
|
|
75
|
+
* 也调用这个方法,保证两条路径(未来的 typed lifecycle 接线 vs 今天的 sink.ts 迁移出口)
|
|
76
|
+
* 共用同一套 reducer 更新与 renderer 分派逻辑。
|
|
77
|
+
*/
|
|
78
|
+
emit(event: RunFeedbackEvent): void;
|
|
79
|
+
/**
|
|
80
|
+
* 关闭顺序第 1-2 步:停止 tick 定时器、清空 dynamic 区域(见 docs「明确 sink 关闭顺序」)。
|
|
81
|
+
* 之后 `diagnostic()`/`activity()` 仍可调用(reporter 收尾阶段可能还有诊断要报),但不会
|
|
82
|
+
* 再触发 clearDynamic/redrawDynamic —— 只是纯追加,不会有 timer 再驱动一次重画。幂等:
|
|
83
|
+
* 重复调用只有第一次生效。
|
|
84
|
+
*/
|
|
85
|
+
stopDynamic(): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* 关闭顺序最后一步:确保 stopDynamic() 已执行,追加 "summary"/"saved" 两个永久事件,
|
|
88
|
+
* 排空内部队列,通知 `renderer.close()`,并把自己从 sink.ts 的活跃栈里摘下(之后
|
|
89
|
+
* `reportActivity()` 等调用会落回 bootstrap 兜底)。resolve 后保证不会再有任何 timer 或
|
|
90
|
+
* 排队任务写终端 —— 调用方(CLI 接线阶段)应在这一步之前完成全部 reporter 收尾
|
|
91
|
+
* (`onRunComplete` 等),让 reporter 收尾期间产生的诊断仍能走 stopDynamic() 之后的
|
|
92
|
+
* 纯追加路径,而不是彻底没有出口。
|
|
93
|
+
*/
|
|
94
|
+
finish(input: {
|
|
95
|
+
summary: RunSummary;
|
|
96
|
+
completion: RunCompletion;
|
|
97
|
+
paths: readonly string[];
|
|
98
|
+
/** 实际写出的 `--json` 聚合报告路径;省略表示没有写出(未传 `--json` 或写入失败),
|
|
99
|
+
* 转发进 "saved" 永久事件的同名字段,供 ci renderer 打印独立的 `json=` 行(见
|
|
100
|
+
* docs/feature/experiments/cli.md「CI 怎么用」)。human/agent renderer 不读这两个字段。 */
|
|
101
|
+
json?: string;
|
|
102
|
+
/** 实际写出的 `--junit` 聚合报告路径,语义同 `json`。 */
|
|
103
|
+
junit?: string;
|
|
104
|
+
}): Promise<void>;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
type Phase = "idle" | "active" | "dynamicStopped" | "finished";
|
|
108
|
+
|
|
109
|
+
export function createFeedbackCoordinator(options: FeedbackCoordinatorOptions): FeedbackCoordinator {
|
|
110
|
+
const { profile, renderer, io } = options;
|
|
111
|
+
const tickIntervalMs = options.tickIntervalMs ?? DEFAULT_TICK_INTERVAL_MS;
|
|
112
|
+
|
|
113
|
+
let phase: Phase = "idle";
|
|
114
|
+
let state: RunFeedbackState = createInitialRunFeedbackState();
|
|
115
|
+
let startedAtMs = 0;
|
|
116
|
+
let tickHandle: FeedbackTimerHandle | undefined;
|
|
117
|
+
let deactivate: (() => void) | undefined;
|
|
118
|
+
const queue = new SerialQueue();
|
|
119
|
+
|
|
120
|
+
function guardWritable(): boolean {
|
|
121
|
+
if (phase === "idle") {
|
|
122
|
+
throw new Error("FeedbackCoordinator: cannot report before start() has been called.");
|
|
123
|
+
}
|
|
124
|
+
return phase !== "finished";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function emit(event: RunFeedbackEvent): void {
|
|
128
|
+
if (!guardWritable()) return;
|
|
129
|
+
// dynamic 区域是否该围着这次投递做 clear/redraw,由「emit() 被调用那一刻」的 phase 决定,
|
|
130
|
+
// 不是「排队任务真正执行那一刻」—— 队列是异步的,stopDynamic() 可能在这次投递已经入队、
|
|
131
|
+
// 但还没轮到执行之前就同步翻转 phase。若在任务体内才读 phase,会让「stopDynamic() 调用
|
|
132
|
+
// 前就已经合法入队」的 clear/redraw 被事后取消,产生「明明还在 active 阶段发生的事件,
|
|
133
|
+
// 却因为收尾快了一步而丢了动态区域重建」的错误行为。这里同步捕获、随事件一起传下去,
|
|
134
|
+
// 让「这次投递该不该有动态区域包裹」在入队瞬间就已经确定,不受后续 phase 变化影响。
|
|
135
|
+
const bracket = phase === "active";
|
|
136
|
+
state = reduceRunFeedback(state, event);
|
|
137
|
+
const snapshot = state;
|
|
138
|
+
switch (event.type) {
|
|
139
|
+
case "attempt:queued":
|
|
140
|
+
case "attempt:start":
|
|
141
|
+
case "attempt:phase":
|
|
142
|
+
case "attempt:progress":
|
|
143
|
+
case "attempt:complete":
|
|
144
|
+
case "attempt:early-exit":
|
|
145
|
+
queue.push(() => renderer.onLifecycle?.(event, snapshot));
|
|
146
|
+
return;
|
|
147
|
+
case "tick":
|
|
148
|
+
queue.push(() => renderer.onTick?.(event, snapshot));
|
|
149
|
+
return;
|
|
150
|
+
default: {
|
|
151
|
+
// 穷尽性检查:剩下的只能是 DurableFeedbackEvent 的某个变体。新增 lifecycle/tick 事件
|
|
152
|
+
// 类型时,这里会在编译期报错提醒补上对应 case —— 不会被无声地误分类成 durable
|
|
153
|
+
// 并因此错误地触发 clearDynamic/redrawDynamic。反过来,新增 DurableFeedbackEvent
|
|
154
|
+
// 的变体不需要动这个 switch(default 分支天然覆盖)。
|
|
155
|
+
const durable: DurableFeedbackEvent = event;
|
|
156
|
+
queue.push(() => deliverDurable(durable, snapshot, bracket));
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function deliverDurable(
|
|
163
|
+
event: DurableFeedbackEvent,
|
|
164
|
+
snapshot: RunFeedbackState,
|
|
165
|
+
bracket: boolean,
|
|
166
|
+
): Promise<void> {
|
|
167
|
+
try {
|
|
168
|
+
if (bracket) await renderer.clearDynamic?.();
|
|
169
|
+
await renderer.appendDurable(event, snapshot);
|
|
170
|
+
if (bracket) await renderer.redrawDynamic?.(snapshot);
|
|
171
|
+
} catch (e) {
|
|
172
|
+
onRendererError(e, event.type, fallbackTextFor(event));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function activity(text: string): void {
|
|
177
|
+
if (!guardWritable()) return;
|
|
178
|
+
const bracket = phase === "active"; // 同上,入队瞬间捕获。
|
|
179
|
+
const snapshot = state;
|
|
180
|
+
queue.push(async () => {
|
|
181
|
+
try {
|
|
182
|
+
if (bracket) await renderer.clearDynamic?.();
|
|
183
|
+
await renderer.activity?.(text, snapshot);
|
|
184
|
+
if (bracket) await renderer.redrawDynamic?.(snapshot);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
onRendererError(e, "activity", text);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function diagnostic(input: DiagnosticInput): void {
|
|
192
|
+
emit({
|
|
193
|
+
type: "diagnostic",
|
|
194
|
+
at: io.clock.now(),
|
|
195
|
+
key: input.key,
|
|
196
|
+
severity: input.severity,
|
|
197
|
+
message: input.message,
|
|
198
|
+
identity: input.identity,
|
|
199
|
+
data: input.data,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function interrupted(): void {
|
|
204
|
+
emit({ type: "interrupted", at: io.clock.now() });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function failure(input: FailureInput): void {
|
|
208
|
+
emit({
|
|
209
|
+
type: "failure",
|
|
210
|
+
at: io.clock.now(),
|
|
211
|
+
locator: input.locator,
|
|
212
|
+
identity: input.identity,
|
|
213
|
+
who: input.who,
|
|
214
|
+
verdict: input.verdict,
|
|
215
|
+
reason: input.reason,
|
|
216
|
+
...(input.assertion !== undefined ? { assertion: input.assertion } : {}),
|
|
217
|
+
...(input.phase !== undefined ? { phase: input.phase } : {}),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function kept(input: KeptInput): void {
|
|
222
|
+
emit({
|
|
223
|
+
type: "kept",
|
|
224
|
+
at: io.clock.now(),
|
|
225
|
+
locator: input.locator,
|
|
226
|
+
identity: input.identity,
|
|
227
|
+
who: input.who,
|
|
228
|
+
verdict: input.verdict,
|
|
229
|
+
provider: input.provider,
|
|
230
|
+
sandboxId: input.sandboxId,
|
|
231
|
+
...(input.enter !== undefined ? { enter: input.enter } : {}),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function budgetExhausted(input: BudgetExhaustedInput): void {
|
|
236
|
+
emit({
|
|
237
|
+
type: "budget-exhausted",
|
|
238
|
+
at: io.clock.now(),
|
|
239
|
+
experimentId: input.experimentId,
|
|
240
|
+
spent: input.spent,
|
|
241
|
+
unstarted: input.unstarted,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function reporterError(input: { reporter: string; required: boolean; message: string }): void {
|
|
246
|
+
emit({
|
|
247
|
+
type: "reporter-error",
|
|
248
|
+
at: io.clock.now(),
|
|
249
|
+
reporter: input.reporter,
|
|
250
|
+
required: input.required,
|
|
251
|
+
message: input.message,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/** `FeedbackSink.lifecycle` 实现:`emit` 已经穷尽处理 `AttemptLifecycleEvent`(见上面的
|
|
256
|
+
* switch),这里只是把 sink.ts 的窄接口对上同一个入口 —— 不是第二套分派逻辑。 */
|
|
257
|
+
function lifecycle(event: AttemptLifecycleEvent): void {
|
|
258
|
+
emit(event);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function onRendererError(e: unknown, context: string, fallbackMessage: string | undefined): void {
|
|
262
|
+
const detail = e instanceof Error ? e.message : String(e);
|
|
263
|
+
// renderer 自己崩了:这是「feedback sink 自己」的兜底出口,允许保留裸写(见 docs 的
|
|
264
|
+
// 「删除与搜索验收」允许清单)—— 没有更下层的 coordinator 可以再兜一次。
|
|
265
|
+
writeStderrLine(t("feedback.rendererError", { context, message: detail }));
|
|
266
|
+
if (fallbackMessage) writeStderrLine(` · ${fallbackMessage}\n`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function start(plan: RunFeedbackPlan): void {
|
|
270
|
+
if (phase !== "idle") {
|
|
271
|
+
throw new Error("FeedbackCoordinator: start() called more than once.");
|
|
272
|
+
}
|
|
273
|
+
phase = "active";
|
|
274
|
+
startedAtMs = io.clock.now();
|
|
275
|
+
deactivate = activateFeedbackSink({
|
|
276
|
+
activity,
|
|
277
|
+
diagnostic,
|
|
278
|
+
interrupted,
|
|
279
|
+
reporterError,
|
|
280
|
+
failure,
|
|
281
|
+
budgetExhausted,
|
|
282
|
+
kept,
|
|
283
|
+
lifecycle,
|
|
284
|
+
});
|
|
285
|
+
emit({ type: "plan", at: startedAtMs, plan });
|
|
286
|
+
tickHandle = io.clock.setInterval(() => {
|
|
287
|
+
if (phase !== "active") return;
|
|
288
|
+
const now = io.clock.now();
|
|
289
|
+
emit({ type: "tick", at: now, elapsedMs: now - startedAtMs });
|
|
290
|
+
}, tickIntervalMs);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function stopDynamic(): Promise<void> {
|
|
294
|
+
if (phase === "idle") {
|
|
295
|
+
throw new Error("FeedbackCoordinator: stopDynamic() called before start().");
|
|
296
|
+
}
|
|
297
|
+
if (phase !== "active") return; // 已经 stopDynamic 过(dynamicStopped)或已 finish,幂等
|
|
298
|
+
phase = "dynamicStopped";
|
|
299
|
+
if (tickHandle !== undefined) {
|
|
300
|
+
io.clock.clearInterval(tickHandle);
|
|
301
|
+
tickHandle = undefined;
|
|
302
|
+
}
|
|
303
|
+
// 无条件再清一次:即便前面某个仍在队列里的任务已经清过,重复调用 clearDynamic 必须安全
|
|
304
|
+
// (renderer.ts 对它的幂等性有明确要求)。这一步保证「不管前面发生了什么交错,收尾时
|
|
305
|
+
// dynamic 区域一定是空的」。
|
|
306
|
+
queue.push(() => renderer.clearDynamic?.());
|
|
307
|
+
await queue.drain();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function finish(input: {
|
|
311
|
+
summary: RunSummary;
|
|
312
|
+
completion: RunCompletion;
|
|
313
|
+
paths: readonly string[];
|
|
314
|
+
json?: string;
|
|
315
|
+
junit?: string;
|
|
316
|
+
}): Promise<void> {
|
|
317
|
+
if (phase === "finished") return; // 幂等
|
|
318
|
+
await stopDynamic();
|
|
319
|
+
emit({ type: "summary", at: io.clock.now(), summary: input.summary, completion: input.completion });
|
|
320
|
+
emit({ type: "saved", at: io.clock.now(), paths: input.paths, json: input.json, junit: input.junit });
|
|
321
|
+
await queue.drain();
|
|
322
|
+
try {
|
|
323
|
+
await renderer.close?.();
|
|
324
|
+
} catch (e) {
|
|
325
|
+
onRendererError(e, "close", undefined);
|
|
326
|
+
}
|
|
327
|
+
phase = "finished";
|
|
328
|
+
deactivate?.();
|
|
329
|
+
deactivate = undefined;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
profile,
|
|
334
|
+
get state() {
|
|
335
|
+
return state;
|
|
336
|
+
},
|
|
337
|
+
start,
|
|
338
|
+
emit,
|
|
339
|
+
activity,
|
|
340
|
+
diagnostic,
|
|
341
|
+
interrupted,
|
|
342
|
+
reporterError,
|
|
343
|
+
failure,
|
|
344
|
+
budgetExhausted,
|
|
345
|
+
kept,
|
|
346
|
+
lifecycle,
|
|
347
|
+
stopDynamic,
|
|
348
|
+
finish,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** 渲染失败时的兜底文本 —— 保证一条真正的失败/诊断证据不会因为 renderer 自己抛错而彻底
|
|
353
|
+
* 消失。"plan"/"summary"/"saved" 不需要兜底文本:它们描述的信息在 RunFeedbackState / 落盘
|
|
354
|
+
* 结果里仍然完整,renderer 崩溃只是丢了这一次的终端展示,不是丢了数据本身。 */
|
|
355
|
+
function fallbackTextFor(event: DurableFeedbackEvent): string | undefined {
|
|
356
|
+
switch (event.type) {
|
|
357
|
+
case "failure":
|
|
358
|
+
return `${event.who} ${event.verdict}: ${event.reason}`;
|
|
359
|
+
case "diagnostic":
|
|
360
|
+
return event.message;
|
|
361
|
+
case "budget-exhausted":
|
|
362
|
+
return `budget exhausted for ${event.experimentId} (spent ${event.spent}, unstarted ${event.unstarted})`;
|
|
363
|
+
case "interrupted":
|
|
364
|
+
return "run interrupted";
|
|
365
|
+
case "reporter-error":
|
|
366
|
+
return `reporter "${event.reporter}" failed: ${event.message}`;
|
|
367
|
+
case "plan":
|
|
368
|
+
case "summary":
|
|
369
|
+
case "saved":
|
|
370
|
+
return undefined;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* 一个先进先出的异步串行队列:保证一批 push() 进来的任务严格按顺序、一个跑完才跑下一个 ——
|
|
376
|
+
* durable 事件的 clear→append→redraw 三步,以及跨多个事件的整体顺序,都靠它保证原子性,
|
|
377
|
+
* 不需要每个调用方自己加锁。单个任务抛错不会让队列卡死(catch 后继续跑下一个)—— 各调用方
|
|
378
|
+
* (deliverDurable/activity)已经各自兜错,这里的 catch 只是双保险,防止兜底逻辑本身的
|
|
379
|
+
* bug poison 掉整条链。
|
|
380
|
+
*/
|
|
381
|
+
class SerialQueue {
|
|
382
|
+
private tail: Promise<void> = Promise.resolve();
|
|
383
|
+
|
|
384
|
+
push(task: () => void | Promise<void>): void {
|
|
385
|
+
this.tail = this.tail.then(async () => {
|
|
386
|
+
try {
|
|
387
|
+
await task();
|
|
388
|
+
} catch {
|
|
389
|
+
// 见类内注释:双保险,不应该正常触发。
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async drain(): Promise<void> {
|
|
395
|
+
await this.tail;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { decodeAttemptLocator, type AttemptLocator } from "../../results/locator.ts";
|
|
2
|
+
import { compactAssertionSummary, primaryAssertionSummary } from "../../scoring/display.ts";
|
|
3
|
+
import type { EvalResult } from "../../types.ts";
|
|
4
|
+
import { firstLine } from "../../util.ts";
|
|
5
|
+
import { runWho, type FailureDetail } from "../types.ts";
|
|
6
|
+
|
|
7
|
+
function isAttemptLocator(value: string): value is AttemptLocator {
|
|
8
|
+
return decodeAttemptLocator(value).valid;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 把落定的结果投影成反馈层失败事实。fresh 与 carry 共用这一处,区别只在消费者:
|
|
13
|
+
* fresh 作为 durable event,carry 作为 plan seed,因此不会把历史失败重放成实时事件。
|
|
14
|
+
*/
|
|
15
|
+
export function failureDetailFromResult(result: EvalResult): FailureDetail | undefined {
|
|
16
|
+
const locator = result.locator;
|
|
17
|
+
if (!locator || !isAttemptLocator(locator) || (result.verdict !== "failed" && result.verdict !== "errored")) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const assertion = result.error === undefined
|
|
22
|
+
? primaryAssertionSummary(result.assertions, result.verdict)
|
|
23
|
+
: undefined;
|
|
24
|
+
const reason = result.verdict === "errored"
|
|
25
|
+
? firstLine(result.error?.message ?? result.verdict)
|
|
26
|
+
: assertion
|
|
27
|
+
? compactAssertionSummary(assertion)
|
|
28
|
+
: firstLine(result.error?.message ?? result.verdict);
|
|
29
|
+
const phase = result.verdict === "errored" ? result.error?.phase : undefined;
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
locator,
|
|
33
|
+
identity: { experimentId: result.experimentId, evalId: result.id, attempt: result.attempt },
|
|
34
|
+
who: runWho({ agentName: result.agent, model: result.model, experimentId: result.experimentId }),
|
|
35
|
+
verdict: result.verdict,
|
|
36
|
+
reason,
|
|
37
|
+
...(assertion !== undefined ? { assertion } : {}),
|
|
38
|
+
...(phase !== undefined ? { phase } : {}),
|
|
39
|
+
};
|
|
40
|
+
}
|