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
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
1
|
+
import { Fragment, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import type { ArtifactLoadState, T } from "../shared.ts";
|
|
3
|
-
import type { ViewResult } from "../types.ts";
|
|
3
|
+
import type { Assertion, Span, ViewResult } from "../types.ts";
|
|
4
4
|
import { artifactUrl } from "../lib/artifact-url.ts";
|
|
5
|
-
import { asEvents, asSources } from "../lib/guards.ts";
|
|
5
|
+
import { asEvents, asSources, asSpans } from "../lib/guards.ts";
|
|
6
6
|
import { verdictClass, verdictLabel } from "../lib/verdict.ts";
|
|
7
|
-
import { CodeView, NoSourceBody } from "./CodeView.tsx";
|
|
7
|
+
import { AssertDetail, assertTone, CodeView, NoSourceBody } from "./CodeView.tsx";
|
|
8
|
+
import { formatScore } from "../lib/format.ts";
|
|
8
9
|
import { CopyAttemptPrompt } from "./CopyControls.tsx";
|
|
9
|
-
import {
|
|
10
|
+
import { Trace } from "./Trace.tsx";
|
|
10
11
|
import { Dialog, DialogClose, DialogContent, DialogTitle } from "./ui/dialog.tsx";
|
|
11
12
|
import { Badge } from "./ui/badge.tsx";
|
|
12
13
|
|
|
@@ -14,6 +15,7 @@ export function AttemptModal({ result, onClose, t }: { result: ViewResult; onClo
|
|
|
14
15
|
const allAssertions = result.assertions || [];
|
|
15
16
|
const base = result.artifactBase;
|
|
16
17
|
const [data, setData] = useState<ArtifactLoadState>({ sources: null, events: null, status: "loading" });
|
|
18
|
+
const trace = useTraceSpans(base, result.hasTrace);
|
|
17
19
|
|
|
18
20
|
// Esc / 焦点陷阱 / 背景滚动锁 / 点遮罩关闭 都交给 Radix Dialog;这里只保留 artifact 拉取。
|
|
19
21
|
useEffect(() => {
|
|
@@ -34,10 +36,12 @@ export function AttemptModal({ result, onClose, t }: { result: ViewResult; onClo
|
|
|
34
36
|
const verdict = result.verdict;
|
|
35
37
|
const hasCode = Boolean(data.sources?.length);
|
|
36
38
|
|
|
39
|
+
// 区块顺序对齐 docs/feature/reports/view.md「Attempt 详情」:判定与断言(errored 的结构化错误
|
|
40
|
+
// 就是判定原因,归这一区)→ 紧凑时间树 → diagnostics → usage → 源码/对话 → trace 入口。
|
|
37
41
|
return (
|
|
38
42
|
<Dialog open onOpenChange={(o) => { if (!o) onClose(); }}>
|
|
39
43
|
<DialogContent aria-describedby={undefined}>
|
|
40
|
-
<div className="flex min-w-0 shrink-0 items-center justify-between gap-3 border-b border-line px-
|
|
44
|
+
<div className="flex min-w-0 shrink-0 items-center justify-between gap-3 border-b border-line px-7 pb-3 pt-4">
|
|
41
45
|
<div className="flex min-w-0 flex-col gap-[3px]">
|
|
42
46
|
<Badge tone={verdictClass(verdict)}>{verdictLabel(verdict, t)}</Badge>
|
|
43
47
|
<DialogTitle asChild>
|
|
@@ -53,22 +57,33 @@ export function AttemptModal({ result, onClose, t }: { result: ViewResult; onClo
|
|
|
53
57
|
x
|
|
54
58
|
</DialogClose>
|
|
55
59
|
</div>
|
|
56
|
-
<div className="flex-1 overflow-y-auto px-
|
|
57
|
-
{
|
|
60
|
+
<div className="flex-1 overflow-y-auto px-7 pb-7 pt-5">
|
|
61
|
+
{data.status !== "loading" && !hasCode ? <AssertionSection assertions={allAssertions} t={t} /> : null}
|
|
62
|
+
{result.error ? (
|
|
63
|
+
<div className="modal-error">
|
|
64
|
+
<ErrorDetailBlock error={result.error} />
|
|
65
|
+
</div>
|
|
66
|
+
) : null}
|
|
67
|
+
{result.phases && result.phases.length > 0 ? <PhaseTimingBlock phases={result.phases} trace={trace} t={t} /> : null}
|
|
68
|
+
{result.diagnostics && result.diagnostics.length > 0 ? (
|
|
69
|
+
<AttemptDiagnostics diagnostics={result.diagnostics} t={t} />
|
|
70
|
+
) : null}
|
|
71
|
+
<UsageDiffLine result={result} />
|
|
58
72
|
{data.status === "loading" ? <div className="conv-loading">{t("trace.loading")}</div> : null}
|
|
59
73
|
{hasCode ? (
|
|
60
74
|
<CodeView sources={data.sources ?? []} events={data.events || []} assertions={allAssertions} t={t} />
|
|
61
75
|
) : data.status !== "loading" ? (
|
|
62
76
|
// hasSources 为真却取不到 → 源码捕获过,是 artifact 文件在当前托管里缺失;和「从未捕获」分开提示。
|
|
77
|
+
// 断言明细此时由断言区(detailMode)内嵌呈现,这里只保留说明与会话流,不再重复 checks。
|
|
63
78
|
<NoSourceBody
|
|
64
|
-
assertions={
|
|
79
|
+
assertions={[]}
|
|
65
80
|
events={data.events || []}
|
|
66
81
|
message={t(result.hasSources && base ? "code.sourceUnavailable" : "code.noSource")}
|
|
67
82
|
t={t}
|
|
68
83
|
/>
|
|
69
84
|
) : null}
|
|
70
|
-
{
|
|
71
|
-
<
|
|
85
|
+
{trace.available ? (
|
|
86
|
+
<FullTraceEntry trace={trace} t={t} />
|
|
72
87
|
) : data.status !== "loading" ? (
|
|
73
88
|
<div className="mt-3 text-xs text-muted">
|
|
74
89
|
{t("trace.enableHint")}
|
|
@@ -82,3 +97,400 @@ export function AttemptModal({ result, onClose, t }: { result: ViewResult; onClo
|
|
|
82
97
|
</Dialog>
|
|
83
98
|
);
|
|
84
99
|
}
|
|
100
|
+
|
|
101
|
+
/* ───────────────────────── 判定与断言 ───────────────────────── */
|
|
102
|
+
|
|
103
|
+
/** 断言的源码位置标注(fallback 下无代码可跳,纯文本)。 */
|
|
104
|
+
function locLabel(a: Assertion): React.ReactNode {
|
|
105
|
+
const loc = a.loc;
|
|
106
|
+
if (!loc) return null;
|
|
107
|
+
const short = loc.file.split("/").pop() ?? loc.file;
|
|
108
|
+
return (
|
|
109
|
+
<span className="assert-loc" title={`${loc.file}:${loc.line}`}>
|
|
110
|
+
{short}:{loc.line}
|
|
111
|
+
</span>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 断言区 = 源码视图的 fallback(见 docs/feature/reports/view.md「Attempt 详情」):断言明细
|
|
117
|
+
* 单点住在源码行里,只有源码不可用(未捕获或 artifact 缺失)时这里才出现,呈现同一份事实——
|
|
118
|
+
* failed / unavailable 与没过阈值的 soft 每条一行、一次展开明细,第一条 failed 默认展开;
|
|
119
|
+
* passed 按 group 收进默认折叠区,只显示数量。
|
|
120
|
+
*/
|
|
121
|
+
function AssertionSection({ assertions, t }: { assertions: Assertion[]; t: T }) {
|
|
122
|
+
if (!assertions.length) return null;
|
|
123
|
+
const attention = assertions.filter((a) => a.outcome !== "passed");
|
|
124
|
+
const passed = assertions.filter((a) => a.outcome === "passed");
|
|
125
|
+
const firstFailed = attention.findIndex((a) => a.outcome === "failed");
|
|
126
|
+
const openIdx = firstFailed === -1 ? 0 : firstFailed;
|
|
127
|
+
const groups = new Map<string, Assertion[]>();
|
|
128
|
+
for (const a of passed) {
|
|
129
|
+
const g = a.groupPath?.join(" > ") ?? "";
|
|
130
|
+
groups.set(g, [...(groups.get(g) ?? []), a]);
|
|
131
|
+
}
|
|
132
|
+
return (
|
|
133
|
+
<div className="attempt-asserts">
|
|
134
|
+
<div className="modal-sect">{t("code.checks")}</div>
|
|
135
|
+
{attention.length ? (
|
|
136
|
+
<div className="attempt-asserts-open">
|
|
137
|
+
{attention.map((a, i) => (
|
|
138
|
+
<FailureRow key={i} a={a} defaultOpen={i === openIdx} t={t} />
|
|
139
|
+
))}
|
|
140
|
+
</div>
|
|
141
|
+
) : null}
|
|
142
|
+
{passed.length ? (
|
|
143
|
+
<details className="asserts-passed">
|
|
144
|
+
<summary>{t("assert.passedCollapsed", { count: passed.length })}</summary>
|
|
145
|
+
{[...groups.entries()].map(([g, list]) => (
|
|
146
|
+
<details key={g || "·"} className="asserts-passed-group">
|
|
147
|
+
<summary>
|
|
148
|
+
{g || "—"} · {list.length}
|
|
149
|
+
</summary>
|
|
150
|
+
<AssertDetail asserts={list.map(stripGroupPath)} t={t} anchor={locLabel} />
|
|
151
|
+
</details>
|
|
152
|
+
))}
|
|
153
|
+
</details>
|
|
154
|
+
) : null}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** 折叠区已按 group 分组,行内不再重复组前缀。 */
|
|
160
|
+
function stripGroupPath(a: Assertion): Assertion {
|
|
161
|
+
const { groupPath: _gp, ...rest } = a;
|
|
162
|
+
return rest as Assertion;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** fallback 断言区里的一行:标题(徽章/名字/matcher/分数/位置),明细一次展开可见。 */
|
|
166
|
+
function FailureRow({ a, defaultOpen, t }: { a: Assertion; defaultOpen: boolean; t: T }) {
|
|
167
|
+
return (
|
|
168
|
+
<details className="fail-row" open={defaultOpen || undefined}>
|
|
169
|
+
<summary className="fail-sum">
|
|
170
|
+
<span className={`abadge ${assertTone(a)}`}>
|
|
171
|
+
{a.outcome === "unavailable" ? t("assert.unavailable") : t("assert.fail")}
|
|
172
|
+
</span>
|
|
173
|
+
<span className="fail-name">
|
|
174
|
+
{a.groupPath?.length ? `${a.groupPath.join(" > ")} · ` : ""}
|
|
175
|
+
{a.name}
|
|
176
|
+
</span>
|
|
177
|
+
{a.detail && a.detail !== a.name ? <span className="fail-matcher">{a.detail}</span> : null}
|
|
178
|
+
{a.optional ? <span className="assert-sev">{t("assert.optional")}</span> : null}
|
|
179
|
+
{a.severity === "soft" ? <span className="assert-sev">{t("assert.soft")}</span> : null}
|
|
180
|
+
{a.outcome !== "unavailable" && a.threshold !== undefined ? (
|
|
181
|
+
<span className="assert-score">
|
|
182
|
+
{formatScore(a.score)} / {formatScore(a.threshold)}
|
|
183
|
+
</span>
|
|
184
|
+
) : null}
|
|
185
|
+
{locLabel(a)}
|
|
186
|
+
</summary>
|
|
187
|
+
<div className="fail-body">
|
|
188
|
+
{a.outcome === "unavailable" ? <div className="assert-reason">{a.reason}</div> : null}
|
|
189
|
+
{a.outcome !== "unavailable" && a.expected !== undefined ? (
|
|
190
|
+
<div className="assert-reason">expected: {a.expected}</div>
|
|
191
|
+
) : null}
|
|
192
|
+
{a.outcome !== "unavailable" && a.received !== undefined ? (
|
|
193
|
+
<div className="assert-reason">received: {a.received}</div>
|
|
194
|
+
) : null}
|
|
195
|
+
{a.outcome !== "unavailable" && a.evidence ? (
|
|
196
|
+
<details className="assert-evidence">
|
|
197
|
+
<summary>{t("assert.evidence")}</summary>
|
|
198
|
+
<pre className="assert-evidence-pre">{a.evidence}</pre>
|
|
199
|
+
</details>
|
|
200
|
+
) : null}
|
|
201
|
+
</div>
|
|
202
|
+
</details>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/* ───────────────────────── 结构化错误与 diagnostics ───────────────────────── */
|
|
207
|
+
|
|
208
|
+
/** errored attempt 的结构化 error 明细(见 docs/feature/reports/view.md「结构化错误」)。结构化
|
|
209
|
+
* `AttemptError`:operation / code / message + 可选 cause / stack。字段标签是低层技术标识,与终端
|
|
210
|
+
* `niceeval show` 一样保持英文,不进 view 的 i18n 词典。 */
|
|
211
|
+
function ErrorDetailBlock({ error }: { error: NonNullable<ViewResult["error"]> }) {
|
|
212
|
+
const rows: [string, string][] = [
|
|
213
|
+
["phase", error.phase],
|
|
214
|
+
["code", error.code],
|
|
215
|
+
["message", error.message],
|
|
216
|
+
];
|
|
217
|
+
if (error.cause) rows.push(["cause", error.cause.name ? `${error.cause.name} · ${error.cause.message}` : error.cause.message]);
|
|
218
|
+
const stack = error.stack?.replace(/\n+$/, "") ?? "";
|
|
219
|
+
return (
|
|
220
|
+
<div>
|
|
221
|
+
<dl className="grid grid-cols-[max-content_1fr] gap-x-3 gap-y-1 text-xs">
|
|
222
|
+
{rows.map(([k, v]) => (
|
|
223
|
+
<Fragment key={k}>
|
|
224
|
+
<dt className="text-muted">{k}</dt>
|
|
225
|
+
<dd className="min-w-0 break-words">{v}</dd>
|
|
226
|
+
</Fragment>
|
|
227
|
+
))}
|
|
228
|
+
</dl>
|
|
229
|
+
{stack ? (
|
|
230
|
+
<pre className="mt-2 overflow-x-auto whitespace-pre-wrap text-[11px] text-muted">{stack}</pre>
|
|
231
|
+
) : null}
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** attempt 级诊断,按 lifecycle 阶段分组(teardown/cleanup 等,与 verdict 独立;
|
|
237
|
+
* 见 docs/feature/reports/view.md「Attempt 详情」)。 */
|
|
238
|
+
function AttemptDiagnostics({ diagnostics, t }: { diagnostics: NonNullable<ViewResult["diagnostics"]>; t: T }) {
|
|
239
|
+
const groups = new Map<string, typeof diagnostics>();
|
|
240
|
+
for (const d of diagnostics) {
|
|
241
|
+
const list = groups.get(d.phase) ?? [];
|
|
242
|
+
groups.set(d.phase, [...list, d]);
|
|
243
|
+
}
|
|
244
|
+
return (
|
|
245
|
+
<div className="mt-3">
|
|
246
|
+
<div className="modal-sect">{t("attempt.diagnostics")}</div>
|
|
247
|
+
{[...groups.entries()].map(([phase, list]) => (
|
|
248
|
+
<div key={phase} className="mt-1">
|
|
249
|
+
<div className="text-[11px] text-muted">{phase}</div>
|
|
250
|
+
<ul className="mt-0.5 space-y-1 pl-3">
|
|
251
|
+
{list.map((d, i) => (
|
|
252
|
+
<li key={i} className="text-xs">
|
|
253
|
+
<span className="text-muted">
|
|
254
|
+
{d.level} · {d.code}
|
|
255
|
+
</span>
|
|
256
|
+
<div className="min-w-0 break-words">
|
|
257
|
+
{d.message}
|
|
258
|
+
{d.count && d.count > 1 ? ` (${d.count} occurrences)` : ""}
|
|
259
|
+
</div>
|
|
260
|
+
</li>
|
|
261
|
+
))}
|
|
262
|
+
</ul>
|
|
263
|
+
</div>
|
|
264
|
+
))}
|
|
265
|
+
</div>
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/* ───────────────────────── 统一时间树 ───────────────────────── */
|
|
270
|
+
|
|
271
|
+
const CLOSING_PHASES = new Set(["eval.teardown", "agent.teardown", "sandbox.teardown", "sandbox.suspend", "sandbox.stop"]);
|
|
272
|
+
|
|
273
|
+
function fmtMs(ms: number): string {
|
|
274
|
+
if (ms >= 60_000) return `${Math.floor(ms / 60_000)}m ${Math.round((ms % 60_000) / 1000)}s`;
|
|
275
|
+
if (ms >= 1000) return `${(ms / 1000).toFixed(1)}s`;
|
|
276
|
+
return `${ms}ms`;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
type PhaseEntry = NonNullable<ViewResult["phases"]>[number];
|
|
280
|
+
type TimingChild = NonNullable<PhaseEntry["children"]>[number];
|
|
281
|
+
|
|
282
|
+
/** trace.json 的共享按需装载:时间树的 turn 挂接与底部 trace 入口用同一份,只 fetch 一次。 */
|
|
283
|
+
interface TraceHook {
|
|
284
|
+
available: boolean;
|
|
285
|
+
status: "idle" | "loading" | "ready" | "failed";
|
|
286
|
+
spans: Span[] | null;
|
|
287
|
+
load: () => void;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
function useTraceSpans(base: string | undefined, hasTrace: boolean | undefined): TraceHook {
|
|
291
|
+
const [state, setState] = useState<{ spans: Span[] | null; status: TraceHook["status"] }>({ spans: null, status: "idle" });
|
|
292
|
+
const started = useRef(false);
|
|
293
|
+
const available = Boolean(base && hasTrace);
|
|
294
|
+
const load = useCallback(() => {
|
|
295
|
+
if (started.current || !base) return;
|
|
296
|
+
started.current = true;
|
|
297
|
+
setState({ spans: null, status: "loading" });
|
|
298
|
+
fetch(artifactUrl(`${base}/trace.json`))
|
|
299
|
+
.then((r) => (r.ok ? r.json() : Promise.reject(new Error(`HTTP ${r.status}`))))
|
|
300
|
+
.then((body) => setState({ spans: asSpans(body) ?? [], status: "ready" }))
|
|
301
|
+
.catch(() => setState({ spans: null, status: "failed" }));
|
|
302
|
+
}, [base]);
|
|
303
|
+
return useMemo(() => ({ available, load, ...state }), [available, load, state]);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const seriesClass = (i: number, failed?: true): string => (failed ? "phase-seg-bad" : `phase-seg-${(i % 6) + 1}`);
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 统一时间树(见 docs/feature/reports/view.md「Attempt 详情」):整区一个折叠块,收合时只占
|
|
310
|
+
* 一行(标题 + 总耗时),有失败 phase 的 attempt 默认展开;展开后主链先画一条分解条,列表里
|
|
311
|
+
* 每个 phase 的 children(hook / 沙箱命令 / session/turn)默认收合、可逐个展开。
|
|
312
|
+
* 收尾段单列,不计入总耗时。
|
|
313
|
+
*/
|
|
314
|
+
function PhaseTimingBlock({ phases, trace, t }: { phases: NonNullable<ViewResult["phases"]>; trace: TraceHook; t: T }) {
|
|
315
|
+
const main = phases.filter((p) => !CLOSING_PHASES.has(p.name));
|
|
316
|
+
const closing = phases.filter((p) => CLOSING_PHASES.has(p.name));
|
|
317
|
+
const total = main.reduce((sum, p) => sum + p.durationMs, 0);
|
|
318
|
+
const anyFailed = phases.some((p) => p.failed);
|
|
319
|
+
const [open, setOpen] = useState(anyFailed);
|
|
320
|
+
return (
|
|
321
|
+
<details className="attempt-timing" open={open} onToggle={(e) => setOpen(e.currentTarget.open)}>
|
|
322
|
+
<summary className="timing-sum">
|
|
323
|
+
<span className="modal-sect">{t("attempt.timing")}</span>
|
|
324
|
+
<span className="phase-dur num">{fmtMs(total)}</span>
|
|
325
|
+
{anyFailed ? <span className="phase-fail">✗</span> : null}
|
|
326
|
+
</summary>
|
|
327
|
+
{open ? (
|
|
328
|
+
<>
|
|
329
|
+
{total > 0 ? (
|
|
330
|
+
<div className="phase-bar" aria-hidden="true">
|
|
331
|
+
{main.map((p, i) => (
|
|
332
|
+
<div
|
|
333
|
+
key={i}
|
|
334
|
+
className={`phase-seg ${seriesClass(i, p.failed)}`}
|
|
335
|
+
style={{ flexGrow: Math.max(p.durationMs, total / 200) }}
|
|
336
|
+
title={`${p.name} ${fmtMs(p.durationMs)}`}
|
|
337
|
+
/>
|
|
338
|
+
))}
|
|
339
|
+
</div>
|
|
340
|
+
) : null}
|
|
341
|
+
<ul className="phase-list">
|
|
342
|
+
{main.map((p, i) => (
|
|
343
|
+
<PhaseRow key={i} phase={p} dotClass={seriesClass(i, p.failed)} trace={trace} t={t} />
|
|
344
|
+
))}
|
|
345
|
+
</ul>
|
|
346
|
+
{closing.length > 0 ? (
|
|
347
|
+
<div className="phase-closing">
|
|
348
|
+
<div className="phase-closing-head">{t("attempt.teardown")}</div>
|
|
349
|
+
<ul className="phase-list">
|
|
350
|
+
{closing.map((p, i) => (
|
|
351
|
+
<PhaseRow key={i} phase={p} trace={trace} t={t} />
|
|
352
|
+
))}
|
|
353
|
+
</ul>
|
|
354
|
+
</div>
|
|
355
|
+
) : null}
|
|
356
|
+
</>
|
|
357
|
+
) : null}
|
|
358
|
+
</details>
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function PhaseRow({ phase, dotClass, trace, t }: { phase: PhaseEntry; dotClass?: string; trace: TraceHook; t: T }) {
|
|
363
|
+
const kids = phase.children ?? [];
|
|
364
|
+
// 失败 phase 默认展开:失败线索直接给出,其余保持收合(契约:children 默认收合)。
|
|
365
|
+
const [open, setOpen] = useState(Boolean(phase.failed));
|
|
366
|
+
const head = (
|
|
367
|
+
<>
|
|
368
|
+
{dotClass ? <span className={`phase-dot ${dotClass}`} /> : <span className="phase-dot phase-dot-none" />}
|
|
369
|
+
<span className="phase-name">{phase.name}</span>
|
|
370
|
+
<span className="phase-dur num">{fmtMs(phase.durationMs)}</span>
|
|
371
|
+
{phase.failed ? <span className="phase-fail">✗</span> : null}
|
|
372
|
+
{kids.length > 0 ? <span className="phase-kids num">{countNodes(kids)}</span> : null}
|
|
373
|
+
</>
|
|
374
|
+
);
|
|
375
|
+
if (kids.length === 0) return <li className="phase-row">{head}</li>;
|
|
376
|
+
return (
|
|
377
|
+
<li>
|
|
378
|
+
<details className="phase-details" open={open} onToggle={(e) => setOpen(e.currentTarget.open)}>
|
|
379
|
+
<summary className="phase-row">{head}</summary>
|
|
380
|
+
{open ? (
|
|
381
|
+
<ul className="timing-kids">
|
|
382
|
+
{kids.map((node) => (
|
|
383
|
+
<TimingNodeRow key={node.id} node={node} trace={trace} t={t} />
|
|
384
|
+
))}
|
|
385
|
+
</ul>
|
|
386
|
+
) : null}
|
|
387
|
+
</details>
|
|
388
|
+
</li>
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function countNodes(nodes: TimingChild[]): number {
|
|
393
|
+
return nodes.reduce((sum, n) => sum + 1 + countNodes(n.children ?? []), 0);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function TimingNodeRow({ node, trace, t }: { node: TimingChild; trace: TraceHook; t: T }) {
|
|
397
|
+
const kids = node.children ?? [];
|
|
398
|
+
// turn 带 traceId 且这次运行有 trace.json 时,展开即挂接同一轮的 agent/model/tool spans。
|
|
399
|
+
const hooksTrace = node.kind === "turn" && Boolean(node.traceId) && trace.available;
|
|
400
|
+
const [open, setOpen] = useState(Boolean(node.failed) && (kids.length > 0 || hooksTrace));
|
|
401
|
+
const label =
|
|
402
|
+
node.kind === "command" && node.command ? `shell · ${node.command.display}` : node.kind === "turn" ? `turn ${node.label}` : node.label;
|
|
403
|
+
const head = (
|
|
404
|
+
<>
|
|
405
|
+
<span className="tnode-label" title={label}>{label}</span>
|
|
406
|
+
<span className="phase-dur num">{fmtMs(node.durationMs)}</span>
|
|
407
|
+
{node.failed ? <span className="phase-fail">✗</span> : null}
|
|
408
|
+
</>
|
|
409
|
+
);
|
|
410
|
+
if (kids.length === 0 && !hooksTrace) return <li className="tnode-row">{head}</li>;
|
|
411
|
+
return (
|
|
412
|
+
<li>
|
|
413
|
+
<details className="phase-details" open={open} onToggle={(e) => setOpen(e.currentTarget.open)}>
|
|
414
|
+
<summary className="tnode-row">{head}</summary>
|
|
415
|
+
{open ? (
|
|
416
|
+
<>
|
|
417
|
+
{kids.length > 0 ? (
|
|
418
|
+
<ul className="timing-kids">
|
|
419
|
+
{kids.map((child) => (
|
|
420
|
+
<TimingNodeRow key={child.id} node={child} trace={trace} t={t} />
|
|
421
|
+
))}
|
|
422
|
+
</ul>
|
|
423
|
+
) : null}
|
|
424
|
+
{hooksTrace ? <TurnSpans traceId={node.traceId!} trace={trace} t={t} /> : null}
|
|
425
|
+
</>
|
|
426
|
+
) : null}
|
|
427
|
+
</details>
|
|
428
|
+
</li>
|
|
429
|
+
);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function TurnSpans({ traceId, trace, t }: { traceId: string; trace: TraceHook; t: T }) {
|
|
433
|
+
useEffect(() => { trace.load(); }, [trace]);
|
|
434
|
+
if (trace.status === "idle" || trace.status === "loading") return <div className="tnode-meta">{t("trace.loading")}</div>;
|
|
435
|
+
if (trace.status === "failed") return <div className="tnode-meta">{t("trace.loadFailed")}</div>;
|
|
436
|
+
const spans = (trace.spans ?? []).filter((s) => s.traceId === traceId);
|
|
437
|
+
if (!spans.length) return <div className="tnode-meta">{t("trace.noSpans")}</div>;
|
|
438
|
+
return (
|
|
439
|
+
<div className="tnode-trace">
|
|
440
|
+
<Trace spans={spans} t={t} />
|
|
441
|
+
</div>
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/** 底部 trace 入口:被测 agent 的完整原始 span 瀑布,与时间树共享同一次 trace.json 拉取。 */
|
|
446
|
+
function FullTraceEntry({ trace, t }: { trace: TraceHook; t: T }) {
|
|
447
|
+
const [open, setOpen] = useState(false);
|
|
448
|
+
return (
|
|
449
|
+
<details
|
|
450
|
+
className="trace-details"
|
|
451
|
+
open={open}
|
|
452
|
+
onToggle={(e) => {
|
|
453
|
+
const isOpen = e.currentTarget.open;
|
|
454
|
+
setOpen(isOpen);
|
|
455
|
+
if (isOpen) trace.load();
|
|
456
|
+
}}
|
|
457
|
+
>
|
|
458
|
+
<summary>{t("trace.timing")}</summary>
|
|
459
|
+
<div className="trace-slot">
|
|
460
|
+
{trace.status === "failed" ? (
|
|
461
|
+
<div className="trace-span-meta">{t("trace.loadFailed")}</div>
|
|
462
|
+
) : trace.status !== "ready" ? (
|
|
463
|
+
<div className="trace-span-meta">{t("trace.loading")}</div>
|
|
464
|
+
) : (
|
|
465
|
+
<Trace spans={trace.spans ?? []} t={t} />
|
|
466
|
+
)}
|
|
467
|
+
</div>
|
|
468
|
+
</details>
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/* ───────────────────────── usage 行 ───────────────────────── */
|
|
473
|
+
|
|
474
|
+
/** usage / sandbox 元信息 chips(见 docs/feature/reports/view.md「Attempt 详情」)。 */
|
|
475
|
+
function UsageDiffLine({ result }: { result: ViewResult }) {
|
|
476
|
+
const chips: React.ReactNode[] = [];
|
|
477
|
+
if (result.usage) {
|
|
478
|
+
const tok = result.usage.inputTokens + result.usage.outputTokens;
|
|
479
|
+
chips.push(
|
|
480
|
+
<span className="meta-chip" key="usage">
|
|
481
|
+
usage <b>{tok.toLocaleString()} tok</b>
|
|
482
|
+
{result.usage.costUSD !== undefined ? <> · ${result.usage.costUSD.toFixed(4)}</> : null}
|
|
483
|
+
</span>,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
if (result.sandbox) {
|
|
487
|
+
chips.push(
|
|
488
|
+
<span className="meta-chip" key="sandbox">
|
|
489
|
+
sandbox <b>{result.sandbox.provider}</b> · {result.sandbox.sandboxId}
|
|
490
|
+
{result.sandbox.kept ? " · kept" : ""}
|
|
491
|
+
</span>,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
if (chips.length === 0) return null;
|
|
495
|
+
return <div className="meta-chips">{chips}</div>;
|
|
496
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState } from "react";
|
|
1
|
+
import { useCallback, useMemo, useState, type ReactNode } from "react";
|
|
2
2
|
import { AlertCircle, CheckCircle2, ChevronRight, MessageCircle, XCircle } from "lucide-react";
|
|
3
3
|
import type { T } from "../shared.ts";
|
|
4
4
|
import type { Assertion, CodeSource, SourceTurn, TranscriptEvent } from "../types.ts";
|
|
@@ -6,16 +6,23 @@ import { highlightTs, indexAsserts, indexTurns, locKey } from "../lib/transcript
|
|
|
6
6
|
import { formatScore } from "../lib/format.ts";
|
|
7
7
|
import { InputBlock, ToolBlock, Transcript } from "./Transcript.tsx";
|
|
8
8
|
|
|
9
|
-
/** soft 断言没过阈值不影响 verdict,颜色上跟 gate 失败(红)区分开,用 warn(黄)
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
/** soft 断言没过阈值不影响 verdict,颜色上跟 gate 失败(红)区分开,用 warn(黄);
|
|
10
|
+
* unavailable 用独立第三态(非红非绿)。 */
|
|
11
|
+
export function assertTone(a: Assertion): "good" | "warn" | "bad" | "na" {
|
|
12
|
+
if (a.outcome === "unavailable") return "na";
|
|
13
|
+
if (a.outcome === "passed") return "good";
|
|
12
14
|
return a.severity === "soft" ? "warn" : "bad";
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
export function CodeView({ sources, events, assertions, t }: { sources: CodeSource[]; events: TranscriptEvent[]; assertions: Assertion[]; t: T }) {
|
|
16
18
|
const turns = useMemo(() => indexTurns(events), [events]);
|
|
17
19
|
const asserts = useMemo(() => indexAsserts(assertions), [assertions]);
|
|
18
|
-
|
|
20
|
+
// 断言明细单点住在源码行(契约 docs/feature/reports/view.md「Attempt 详情」):
|
|
21
|
+
// 第一条失败行默认展开,打开页面即见「为什么失败」,不用逐行找红行点开。
|
|
22
|
+
const [open, setOpen] = useState<Set<string>>(() => {
|
|
23
|
+
const first = assertions.find((a) => a.outcome === "failed" && a.loc);
|
|
24
|
+
return new Set(first?.loc ? [locKey(first.loc.file, first.loc.line)] : []);
|
|
25
|
+
});
|
|
19
26
|
const toggle = useCallback((k: string) => {
|
|
20
27
|
setOpen((prev) => {
|
|
21
28
|
const next = new Set(prev);
|
|
@@ -118,13 +125,16 @@ export function CodeLine({
|
|
|
118
125
|
}) {
|
|
119
126
|
const hasReply = !!turn;
|
|
120
127
|
const hasAsserts = !!(asserts && asserts.length);
|
|
121
|
-
// 只有 gate 断言没过才算这一行真的"fail";只剩 soft 断言没过阈值时是"warn"(不影响 verdict)
|
|
128
|
+
// 只有 gate 断言没过才算这一行真的"fail";只剩 soft 断言没过阈值时是"warn"(不影响 verdict);
|
|
129
|
+
// 只剩 unavailable 时是第三态 "na"(非红非绿)。
|
|
122
130
|
const status = hasAsserts
|
|
123
|
-
? asserts?.every((a: Assertion) => a.passed)
|
|
131
|
+
? asserts?.every((a: Assertion) => a.outcome === "passed")
|
|
124
132
|
? "pass"
|
|
125
|
-
: asserts?.some((a: Assertion) =>
|
|
133
|
+
: asserts?.some((a: Assertion) => a.outcome === "failed" && a.severity === "gate")
|
|
126
134
|
? "fail"
|
|
127
|
-
: "
|
|
135
|
+
: asserts?.some((a: Assertion) => a.outcome === "failed")
|
|
136
|
+
? "warn"
|
|
137
|
+
: "na"
|
|
128
138
|
: null;
|
|
129
139
|
const clickable = hasReply || hasAsserts;
|
|
130
140
|
const rowCls =
|
|
@@ -149,6 +159,8 @@ export function CodeLine({
|
|
|
149
159
|
<CheckCircle2 className="gstat good" aria-hidden="true" />
|
|
150
160
|
) : status === "warn" ? (
|
|
151
161
|
<AlertCircle className="gstat warn" aria-hidden="true" />
|
|
162
|
+
) : status === "na" ? (
|
|
163
|
+
<AlertCircle className="gstat na" aria-hidden="true" />
|
|
152
164
|
) : (
|
|
153
165
|
<XCircle className="gstat bad" aria-hidden="true" />
|
|
154
166
|
)
|
|
@@ -174,6 +186,7 @@ export function CodeLine({
|
|
|
174
186
|
|
|
175
187
|
/** 行尾分数徽章:judge / 带阈值的断言显示分数(过绿不过红);纯 gate 断言靠行色 + gutter 勾叉。 */
|
|
176
188
|
export function AssertBadge({ a }: { a: Assertion }) {
|
|
189
|
+
if (a.outcome === "unavailable") return <span className="abadge na">n/a</span>;
|
|
177
190
|
const showPct = a.threshold !== undefined || (a.score > 0 && a.score < 1);
|
|
178
191
|
if (!showPct) return null;
|
|
179
192
|
return (
|
|
@@ -215,21 +228,35 @@ export function ReplyPanel({ turn, t }: { turn: SourceTurn; t: T }) {
|
|
|
215
228
|
);
|
|
216
229
|
}
|
|
217
230
|
|
|
218
|
-
export function AssertDetail({ asserts, t }: { asserts: Assertion[]; t: T }) {
|
|
231
|
+
export function AssertDetail({ asserts, t, anchor }: { asserts: Assertion[]; t: T; anchor?: (a: Assertion) => ReactNode }) {
|
|
219
232
|
return (
|
|
220
233
|
<div className="line-detail assert-detail">
|
|
221
234
|
{asserts.map((a: Assertion, i: number) => (
|
|
222
235
|
<div key={i} className="assert-row">
|
|
223
|
-
<span className={`abadge ${assertTone(a)}`}>
|
|
224
|
-
|
|
236
|
+
<span className={`abadge ${assertTone(a)}`}>
|
|
237
|
+
{a.outcome === "unavailable" ? t("assert.unavailable") : a.outcome === "passed" ? t("assert.pass") : t("assert.fail")}
|
|
238
|
+
</span>
|
|
239
|
+
<span className="assert-name">
|
|
240
|
+
{a.groupPath?.length ? `${a.groupPath.join(" > ")} · ` : ""}
|
|
241
|
+
{a.name}
|
|
242
|
+
{anchor?.(a)}
|
|
243
|
+
</span>
|
|
244
|
+
{a.optional ? <span className="assert-sev">{t("assert.optional")}</span> : null}
|
|
225
245
|
{a.severity === "soft" ? <span className="assert-sev">{t("assert.soft")}</span> : null}
|
|
226
|
-
{a.threshold !== undefined ? (
|
|
246
|
+
{a.outcome !== "unavailable" && a.threshold !== undefined ? (
|
|
227
247
|
<span className="assert-score">
|
|
228
248
|
{formatScore(a.score)} / {formatScore(a.threshold)}
|
|
229
249
|
</span>
|
|
230
250
|
) : null}
|
|
251
|
+
{a.outcome === "unavailable" ? <div className="assert-reason">{a.reason}</div> : null}
|
|
231
252
|
{a.detail ? <div className="assert-reason">{a.detail}</div> : null}
|
|
232
|
-
{a.
|
|
253
|
+
{a.outcome !== "unavailable" && a.expected !== undefined ? (
|
|
254
|
+
<div className="assert-reason">expected: {a.expected}</div>
|
|
255
|
+
) : null}
|
|
256
|
+
{a.outcome !== "unavailable" && a.received !== undefined ? (
|
|
257
|
+
<div className="assert-reason">received: {a.received}</div>
|
|
258
|
+
) : null}
|
|
259
|
+
{a.outcome !== "unavailable" && a.evidence ? (
|
|
233
260
|
<details className="assert-evidence">
|
|
234
261
|
<summary>{t("assert.evidence")}</summary>
|
|
235
262
|
<pre className="assert-evidence-pre">{a.evidence}</pre>
|
|
@@ -3,7 +3,7 @@ import { Check, Copy } from "lucide-react";
|
|
|
3
3
|
import type { T } from "../shared.ts";
|
|
4
4
|
import type { ViewResult, ViewSnapshot } from "../types.ts";
|
|
5
5
|
import { snapshotLabel } from "../lib/rows.ts";
|
|
6
|
-
import {
|
|
6
|
+
import { reasonFor } from "../lib/verdict.ts";
|
|
7
7
|
|
|
8
8
|
/** 修复 prompt 的一条失败条目;路径均相对 view 输入根(默认 `.niceeval/`)。 */
|
|
9
9
|
export interface FixPromptEntry {
|
|
@@ -20,7 +20,7 @@ export function toFixPromptEntry(r: ViewResult, experimentLabel: string): FixPro
|
|
|
20
20
|
experiment: r.experimentId ?? experimentLabel,
|
|
21
21
|
evalId: r.id,
|
|
22
22
|
verdict: r.verdict,
|
|
23
|
-
reason: reasonFor(r
|
|
23
|
+
reason: reasonFor(r),
|
|
24
24
|
artifactBase: r.artifactBase,
|
|
25
25
|
// result.json 与其它 artifact(events.json 等)同目录落盘,resultPath 因此复用同一个
|
|
26
26
|
// artifactBase(不再靠 attemptRef 的 {snapshot, attempt} 两段拼——locator 是不透明的,
|