niceeval 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/types.d.ts +67 -5
- package/dist/context/types.d.ts +32 -12
- package/dist/i18n/en.d.ts +54 -0
- package/dist/i18n/zh-CN.d.ts +55 -1
- package/dist/o11y/types.d.ts +16 -2
- package/dist/report/aggregate.d.ts +5 -3
- package/dist/report/aggregate.js +32 -5
- package/dist/report/built-ins/experiment-comparison.d.ts +39 -1
- package/dist/report/built-ins/experiment-comparison.js +116 -10
- package/dist/report/built-ins/index.d.ts +1 -0
- package/dist/report/built-ins/index.js +1 -1
- package/dist/report/components.d.ts +8 -2
- package/dist/report/components.js +3 -3
- package/dist/report/compute.d.ts +11 -18
- package/dist/report/compute.js +54 -34
- package/dist/report/flag.d.ts +16 -1
- package/dist/report/flag.js +19 -1
- package/dist/report/format.d.ts +16 -8
- package/dist/report/format.js +27 -12
- package/dist/report/index.d.ts +4 -3
- package/dist/report/index.js +5 -4
- package/dist/report/locale.d.ts +11 -2
- package/dist/report/locale.js +23 -5
- package/dist/report/metrics.d.ts +13 -1
- package/dist/report/metrics.js +65 -14
- package/dist/report/primitives.d.ts +6 -0
- package/dist/report/react/AttemptList.d.ts +2 -2
- package/dist/report/react/AttemptList.js +5 -6
- package/dist/report/react/EvalList.d.ts +1 -1
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +8 -0
- package/dist/report/react/ExperimentComparison.js +11 -0
- package/dist/report/react/ExperimentList.d.ts +2 -1
- package/dist/report/react/ExperimentList.js +8 -10
- package/dist/report/react/MetricScatter.js +5 -11
- 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 +3 -3
- package/dist/report/react/fixtures.js +21 -14
- package/dist/report/report.d.ts +5 -1
- package/dist/report/report.js +6 -2
- package/dist/report/text/faces.d.ts +1 -1
- package/dist/report/text/faces.js +42 -41
- package/dist/report/text/table.js +36 -5
- package/dist/report/types.d.ts +39 -21
- package/dist/results/types.d.ts +11 -0
- 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 +1 -0
- package/dist/shared/aggregate.js +3 -3
- 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/concepts/adapter.mdx +22 -4
- package/docs-site/zh/concepts/experiment.mdx +1 -1
- package/docs-site/zh/concepts/overview.mdx +6 -6
- package/docs-site/zh/guides/agent-feedback-loop.mdx +28 -26
- package/docs-site/zh/guides/authoring.mdx +33 -0
- package/docs-site/zh/guides/ci-integration.mdx +23 -12
- package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
- package/docs-site/zh/guides/custom-reports.mdx +29 -34
- package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
- package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
- package/docs-site/zh/guides/debugging.mdx +210 -0
- package/docs-site/zh/guides/experiments.mdx +10 -3
- package/docs-site/zh/guides/official-adapters.mdx +26 -2
- package/docs-site/zh/guides/publish-report.mdx +30 -16
- package/docs-site/zh/guides/report-components.mdx +42 -30
- package/docs-site/zh/guides/reporters.mdx +2 -2
- package/docs-site/zh/guides/results-data.mdx +17 -9
- package/docs-site/zh/guides/runner.mdx +17 -7
- package/docs-site/zh/guides/sandbox-agent.mdx +56 -7
- package/docs-site/zh/guides/sandbox-providers.mdx +257 -9
- package/docs-site/zh/guides/scoring-guide.mdx +4 -4
- package/docs-site/zh/guides/viewing-results.mdx +79 -36
- package/docs-site/zh/guides/write-experiment.mdx +5 -3
- package/docs-site/zh/guides/write-send.mdx +17 -1
- package/docs-site/zh/index.mdx +1 -1
- package/docs-site/zh/reference/builtin-agents.mdx +27 -0
- package/docs-site/zh/reference/capabilities.mdx +2 -2
- package/docs-site/zh/reference/cli.mdx +33 -7
- package/docs-site/zh/reference/define-agent.mdx +57 -4
- 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/expect.mdx +26 -1
- package/package.json +5 -1
- 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 +11 -0
- 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/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/types.ts +68 -5
- package/src/agents/ui-message-stream.test.ts +3 -0
- package/src/cli.ts +411 -108
- package/src/context/context.test.ts +51 -12
- package/src/context/context.ts +161 -29
- package/src/context/session.test.ts +1 -0
- package/src/context/session.ts +114 -6
- 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 +64 -2
- package/src/i18n/zh-CN.ts +65 -3
- 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 +34 -5
- package/src/report/built-in-user-parity.test.tsx +110 -153
- package/src/report/built-ins/experiment-comparison.tsx +173 -13
- package/src/report/built-ins/index.ts +6 -1
- package/src/report/components.tsx +9 -3
- package/src/report/compute.ts +70 -40
- package/src/report/dual-render.test.tsx +194 -67
- package/src/report/flag.ts +30 -2
- package/src/report/format.ts +35 -11
- package/src/report/index.ts +22 -4
- package/src/report/locale.ts +25 -5
- package/src/report/metrics.ts +67 -14
- package/src/report/primitives.tsx +6 -0
- package/src/report/react/AttemptList.tsx +6 -31
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +68 -0
- package/src/report/react/ExperimentList.tsx +15 -9
- package/src/report/react/MetricScatter.tsx +12 -14
- 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 +33 -1
- package/src/report/react/fixtures.ts +24 -17
- package/src/report/react/render.test.tsx +9 -64
- package/src/report/react/styles.css +73 -2
- package/src/report/report.test.ts +306 -98
- package/src/report/report.ts +6 -2
- package/src/report/text/faces.ts +47 -43
- package/src/report/text/table.ts +42 -5
- package/src/report/types.ts +41 -21
- package/src/results/annotated-source.test.ts +62 -9
- package/src/results/annotated-source.ts +64 -6
- package/src/results/attempt-evidence.test.ts +9 -7
- package/src/results/attempt-evidence.ts +15 -8
- package/src/results/attempt-source.ts +6 -3
- package/src/results/copy.ts +145 -55
- package/src/results/host-equivalence.test.ts +8 -6
- package/src/results/index.ts +2 -0
- package/src/results/locator.test.ts +1 -22
- package/src/results/open.ts +7 -1
- package/src/results/publish.ts +149 -0
- package/src/results/results.test.ts +85 -51
- package/src/results/truncate.ts +90 -0
- package/src/results/types.ts +7 -0
- 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 +242 -36
- 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 +3 -2
- package/src/shared/types.ts +31 -0
- package/src/show/compose.ts +2 -2
- package/src/show/index.ts +21 -1
- package/src/show/render.ts +619 -104
- package/src/show/show.test.ts +235 -19
- package/src/tty-line.ts +8 -26
- package/src/util.test.ts +1 -0
- package/src/util.ts +41 -0
- package/src/view/app/components/AttemptModal.tsx +153 -2
- package/src/view/app/components/CodeView.tsx +32 -11
- package/src/view/app/components/CopyControls.tsx +2 -2
- package/src/view/app/i18n.ts +6 -0
- package/src/view/app/lib/attempt-route.test.ts +1 -0
- package/src/view/app/lib/verdict.ts +7 -9
- 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 +1 -0
- package/src/view/data.ts +11 -1
- package/src/view/index.ts +11 -0
- package/src/view/server.ts +2 -0
- package/src/view/styles.css +3 -0
- package/src/view/view-report.test.ts +6 -5
- 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
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
// 通用图表数值工具:round-number
|
|
2
|
-
// 从 src/view/app/lib/chart.ts 迁来(view 侧从这里 re-export),报告组件与 view 共用同一份。
|
|
1
|
+
// 通用图表数值工具:round-number 刻度生成与点标签布局,零依赖纯函数。
|
|
3
2
|
function niceNumber(range, round) {
|
|
4
3
|
if (range <= 0)
|
|
5
4
|
return 1;
|
|
@@ -43,26 +42,79 @@ export function niceTicks(min, max, count = 5) {
|
|
|
43
42
|
ticks.push(Number(v.toFixed(10)));
|
|
44
43
|
return ticks;
|
|
45
44
|
}
|
|
45
|
+
/** 标签框高度(基线上方 10px、下方 2px,对应 11px 字号)。 */
|
|
46
|
+
const LABEL_ASCENT = 10;
|
|
47
|
+
const LABEL_DESCENT = 2;
|
|
48
|
+
/** 数据点视为边长 2×DOT_R 的方形障碍(点半径 4.5 + 命中余量)。 */
|
|
49
|
+
const DOT_R = 6;
|
|
50
|
+
/** 紧邻位与点的水平间距;候选环按它逐环外扩。 */
|
|
51
|
+
const NEAR = 10;
|
|
52
|
+
const RINGS = [NEAR, NEAR + 12, NEAR + 26];
|
|
53
|
+
function labelBoxAt(x, y, anchor, width) {
|
|
54
|
+
const x0 = anchor === "start" ? x : anchor === "end" ? x - width : x - width / 2;
|
|
55
|
+
return { x0, y0: y - LABEL_ASCENT, x1: x0 + width, y1: y + LABEL_DESCENT };
|
|
56
|
+
}
|
|
57
|
+
function overlapArea(a, b) {
|
|
58
|
+
const w = Math.min(a.x1, b.x1) - Math.max(a.x0, b.x0);
|
|
59
|
+
const h = Math.min(a.y1, b.y1) - Math.max(a.y0, b.y0);
|
|
60
|
+
return w > 0 && h > 0 ? w * h : 0;
|
|
61
|
+
}
|
|
62
|
+
function outOfBoundsArea(box, bounds) {
|
|
63
|
+
const total = (box.x1 - box.x0) * (box.y1 - box.y0);
|
|
64
|
+
const w = Math.min(box.x1, bounds.x1) - Math.max(box.x0, bounds.x0);
|
|
65
|
+
const h = Math.min(box.y1, bounds.y1) - Math.max(box.y0, bounds.y0);
|
|
66
|
+
const inside = w > 0 && h > 0 ? w * h : 0;
|
|
67
|
+
return total - inside;
|
|
68
|
+
}
|
|
69
|
+
/** 一个点在距离 d 的候选环:左右紧邻、四个斜角、正上正下。顺序即同代价时的偏好序。 */
|
|
70
|
+
function ringCandidates(p, d) {
|
|
71
|
+
const diag = d * 0.75;
|
|
72
|
+
return [
|
|
73
|
+
{ x: p.cx + d, y: p.cy + 4, anchor: "start" },
|
|
74
|
+
{ x: p.cx - d, y: p.cy + 4, anchor: "end" },
|
|
75
|
+
{ x: p.cx + diag, y: p.cy - diag - 2, anchor: "start" },
|
|
76
|
+
{ x: p.cx - diag, y: p.cy - diag - 2, anchor: "end" },
|
|
77
|
+
{ x: p.cx, y: p.cy - d - LABEL_DESCENT - 2, anchor: "middle" },
|
|
78
|
+
{ x: p.cx + diag, y: p.cy + diag + LABEL_ASCENT, anchor: "start" },
|
|
79
|
+
{ x: p.cx - diag, y: p.cy + diag + LABEL_ASCENT, anchor: "end" },
|
|
80
|
+
{ x: p.cx, y: p.cy + d + LABEL_ASCENT + 2, anchor: "middle" },
|
|
81
|
+
];
|
|
82
|
+
}
|
|
46
83
|
/**
|
|
47
|
-
*
|
|
48
|
-
*
|
|
84
|
+
* 散点直接标签的候选位择优布局(docs/feature/reports/library.md「MetricScatter」):
|
|
85
|
+
* 每个标签在点四周由近及远的候选环上打分——与已放置标签的重叠、与任何数据点的重叠、
|
|
86
|
+
* 越出画布的面积、离点距离逐项累加,取代价最小的候选。存在无冲突候选时标签必不遮点、
|
|
87
|
+
* 不叠标签、不越界;全候选冲突时取重叠最小者,绝不丢标签。重合点簇因此向不同方向散开,
|
|
88
|
+
* 而不是单向堆叠压到下方的数据点上。
|
|
49
89
|
*/
|
|
50
|
-
export function
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
90
|
+
export function placePointLabels(points, bounds) {
|
|
91
|
+
const dots = points.map((p) => ({ x0: p.cx - DOT_R, y0: p.cy - DOT_R, x1: p.cx + DOT_R, y1: p.cy + DOT_R }));
|
|
92
|
+
const placedBoxes = [];
|
|
93
|
+
const result = new Array(points.length);
|
|
94
|
+
// 由上到下、由左到右逐个放置,布局确定可复现
|
|
95
|
+
const order = [...points.keys()].sort((a, b) => points[a].cy - points[b].cy || points[a].cx - points[b].cx);
|
|
54
96
|
for (const i of order) {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
const p = points[i];
|
|
98
|
+
let best = null;
|
|
99
|
+
for (let ring = 0; ring < RINGS.length; ring++) {
|
|
100
|
+
const candidates = ringCandidates(p, RINGS[ring]);
|
|
101
|
+
for (let c = 0; c < candidates.length; c++) {
|
|
102
|
+
const candidate = candidates[c];
|
|
103
|
+
const box = labelBoxAt(candidate.x, candidate.y, candidate.anchor, p.width);
|
|
104
|
+
let cost = 0;
|
|
105
|
+
for (const placed of placedBoxes)
|
|
106
|
+
cost += overlapArea(box, placed) * 4;
|
|
107
|
+
for (const dot of dots)
|
|
108
|
+
cost += overlapArea(box, dot) * 3;
|
|
109
|
+
cost += outOfBoundsArea(box, bounds) * 6;
|
|
110
|
+
cost += ring * 8 + c * 0.5; // 近环、偏好序靠前者优先,只作同冲突程度下的排序
|
|
111
|
+
if (best === null || cost < best.cost) {
|
|
112
|
+
best = { candidate, box, cost, leader: !(ring === 0 && c <= 1) };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
63
115
|
}
|
|
64
|
-
|
|
65
|
-
|
|
116
|
+
placedBoxes.push(best.box);
|
|
117
|
+
result[i] = { ...best.candidate, leader: best.leader };
|
|
66
118
|
}
|
|
67
|
-
return
|
|
119
|
+
return result;
|
|
68
120
|
}
|
|
@@ -6,14 +6,14 @@ export declare const overviewData: OverviewData;
|
|
|
6
6
|
export declare const overviewWithCost: OverviewData;
|
|
7
7
|
/** GroupSummary.data 的产物形态:eval 级折叠计票 + 旧 GroupSelector 口径的通过率(包成 MetricCell)。 */
|
|
8
8
|
export declare const groupSummaryData: GroupSummaryData;
|
|
9
|
-
export declare const tableData: TableData<"pass-rate" | "code-lines">;
|
|
9
|
+
export declare const tableData: TableData<"task-pass-rate" | "code-lines">;
|
|
10
10
|
/** rows: "experiment" 的榜单形态:行携带 agent/model 元信息与 eval 级折叠计票。 */
|
|
11
|
-
export declare const tableDataWithMeta: TableData<"pass-rate">;
|
|
11
|
+
export declare const tableDataWithMeta: TableData<"task-pass-rate">;
|
|
12
12
|
export declare const matrixData: MatrixData;
|
|
13
13
|
export declare const scoreboardData: ScoreboardData;
|
|
14
14
|
export declare const scatterData: ScatterData;
|
|
15
15
|
export declare const lineData: LineData;
|
|
16
|
-
export declare const deltaData: DeltaData<"pass-rate" | "cost">;
|
|
16
|
+
export declare const deltaData: DeltaData<"task-pass-rate" | "cost">;
|
|
17
17
|
export declare const attemptListItems: AttemptListItem[];
|
|
18
18
|
export declare const evalListItems: EvalListItem[];
|
|
19
19
|
export declare const experimentListItems: ExperimentListItem[];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// 数字刻意覆盖诚实细节:coverage 角标(samples<total)、全 null 格子、
|
|
4
4
|
// 稀疏矩阵、缺数据的散点、delta 的 null 不硬算、truncated 计数。
|
|
5
5
|
const locator = (s) => s;
|
|
6
|
-
export const passRateColumn = { key: "pass-rate", label: "pass rate", unit: "%", better: "higher" };
|
|
6
|
+
export const passRateColumn = { key: "task-pass-rate", label: "pass rate", unit: "%", better: "higher" };
|
|
7
7
|
export const codeLinesColumn = { key: "code-lines", label: "code lines", unit: "lines", better: "lower" };
|
|
8
8
|
export const costColumn = { key: "cost", label: "cost", unit: "$", better: "lower" };
|
|
9
9
|
export const overviewData = {
|
|
@@ -59,7 +59,7 @@ export const tableData = {
|
|
|
59
59
|
{
|
|
60
60
|
key: "codex",
|
|
61
61
|
cells: {
|
|
62
|
-
"pass-rate": { value: 0.5, display: "50%", samples: 6, total: 6, refs: [] },
|
|
62
|
+
"task-pass-rate": { value: 0.5, display: "50%", samples: 6, total: 6, refs: [] },
|
|
63
63
|
// 全 null:一个有效样本都没有 → 缺数据文案,绝不画 0
|
|
64
64
|
"code-lines": { value: null, display: "—", samples: 0, total: 6, refs: [] },
|
|
65
65
|
},
|
|
@@ -67,7 +67,7 @@ export const tableData = {
|
|
|
67
67
|
{
|
|
68
68
|
key: "bub",
|
|
69
69
|
cells: {
|
|
70
|
-
"pass-rate": {
|
|
70
|
+
"task-pass-rate": {
|
|
71
71
|
value: 0.87,
|
|
72
72
|
display: "87%",
|
|
73
73
|
samples: 6,
|
|
@@ -87,7 +87,7 @@ export const tableDataWithMeta = {
|
|
|
87
87
|
rows: [
|
|
88
88
|
{
|
|
89
89
|
key: "compare/bub",
|
|
90
|
-
cells: { "pass-rate": { value: 0.5, display: "50%", samples: 2, total: 2, refs: [] } },
|
|
90
|
+
cells: { "task-pass-rate": { value: 0.5, display: "50%", samples: 2, total: 2, refs: [] } },
|
|
91
91
|
meta: {
|
|
92
92
|
agent: "bub",
|
|
93
93
|
model: "gpt-5.4",
|
|
@@ -96,7 +96,7 @@ export const tableDataWithMeta = {
|
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
key: "compare/codex",
|
|
99
|
-
cells: { "pass-rate": { value: 1, display: "100%", samples: 2, total: 2, refs: [] } },
|
|
99
|
+
cells: { "task-pass-rate": { value: 1, display: "100%", samples: 2, total: 2, refs: [] } },
|
|
100
100
|
meta: {
|
|
101
101
|
agent: "codex",
|
|
102
102
|
verdicts: { passed: 2, failed: 0, errored: 0, skipped: 0 },
|
|
@@ -242,7 +242,7 @@ export const deltaData = {
|
|
|
242
242
|
b: { experimentId: "compare/bub--agents-md" },
|
|
243
243
|
cells: {
|
|
244
244
|
// 通过率 +12pp:better higher → 好(绿)
|
|
245
|
-
"pass-rate": {
|
|
245
|
+
"task-pass-rate": {
|
|
246
246
|
a: { value: 0.5, display: "50%", samples: 6, total: 6, refs: [] },
|
|
247
247
|
b: { value: 0.62, display: "62%", samples: 6, total: 6, refs: [] },
|
|
248
248
|
delta: 0.12,
|
|
@@ -262,7 +262,7 @@ export const deltaData = {
|
|
|
262
262
|
a: { experimentId: "compare/codex" },
|
|
263
263
|
b: { experimentId: "compare/codex--agents-md" },
|
|
264
264
|
cells: {
|
|
265
|
-
"pass-rate": {
|
|
265
|
+
"task-pass-rate": {
|
|
266
266
|
a: { value: 0.4, display: "40%", samples: 6, total: 6, refs: [] },
|
|
267
267
|
b: { value: 0.4, display: "40%", samples: 6, total: 6, refs: [] },
|
|
268
268
|
delta: 0,
|
|
@@ -292,8 +292,9 @@ const failedAttempt = {
|
|
|
292
292
|
name: "roots-correct",
|
|
293
293
|
severity: "gate",
|
|
294
294
|
score: 0,
|
|
295
|
-
|
|
296
|
-
|
|
295
|
+
outcome: "failed",
|
|
296
|
+
expected: "x=2",
|
|
297
|
+
received: "x=3",
|
|
297
298
|
evidence: "judge: sign flipped when substituting into the quadratic formula",
|
|
298
299
|
},
|
|
299
300
|
],
|
|
@@ -307,7 +308,17 @@ const erroredAttempt = {
|
|
|
307
308
|
attempt: 0,
|
|
308
309
|
agent: "codex",
|
|
309
310
|
verdict: "errored",
|
|
310
|
-
|
|
311
|
+
// 结构化 error:列表只渲染 message 一层摘要;cause/stack 与 diagnostics 是下钻详情,随数据携带
|
|
312
|
+
error: {
|
|
313
|
+
code: "unexpected-error",
|
|
314
|
+
message: "TypeError: cannot read properties of undefined (reading 'foo')",
|
|
315
|
+
phase: "eval.run",
|
|
316
|
+
stack: "TypeError: cannot read properties of undefined (reading 'foo')\n at run (adapter.ts:42:7)",
|
|
317
|
+
cause: { name: "TypeError", message: "cannot read properties of undefined (reading 'foo')" },
|
|
318
|
+
},
|
|
319
|
+
diagnostics: [
|
|
320
|
+
{ code: "sandbox-teardown-failed", level: "warning", message: "sandbox teardown timed out", phase: "sandbox.teardown" },
|
|
321
|
+
],
|
|
311
322
|
assertions: [],
|
|
312
323
|
durationMs: 4_500,
|
|
313
324
|
locator: locator("@1c1c1c1"),
|
|
@@ -324,7 +335,6 @@ export const evalListItems = [
|
|
|
324
335
|
evalId: "algebra/quadratic",
|
|
325
336
|
experimentId: "compare/bub",
|
|
326
337
|
verdict: "failed",
|
|
327
|
-
reason: "roots-correct: expected x=2, got x=3",
|
|
328
338
|
score: { value: 0, display: "0%", samples: 1, total: 1, refs: [failedAttempt.locator] },
|
|
329
339
|
duration: { value: 32_000, display: "32.0s", samples: 1, total: 1, refs: [failedAttempt.locator] },
|
|
330
340
|
cost: { value: 0.12, display: "$0.12", samples: 1, total: 1, refs: [failedAttempt.locator] },
|
|
@@ -334,7 +344,6 @@ export const evalListItems = [
|
|
|
334
344
|
evalId: "geometry/angles",
|
|
335
345
|
experimentId: "compare/codex",
|
|
336
346
|
verdict: "errored",
|
|
337
|
-
reason: erroredAttempt.error,
|
|
338
347
|
score: { value: 0, display: "0%", samples: 1, total: 1, refs: [erroredAttempt.locator] },
|
|
339
348
|
duration: { value: 4_500, display: "4.5s", samples: 1, total: 1, refs: [erroredAttempt.locator] },
|
|
340
349
|
cost: { value: null, display: "—", samples: 0, total: 1, refs: [] },
|
|
@@ -359,7 +368,6 @@ export const experimentListItems = [
|
|
|
359
368
|
{
|
|
360
369
|
evalId: "algebra/quadratic",
|
|
361
370
|
verdict: "failed",
|
|
362
|
-
reason: "roots-correct: expected x=2, got x=3",
|
|
363
371
|
duration: { value: 32_000, display: "32.0s", samples: 1, total: 1, refs: [failedAttempt.locator] },
|
|
364
372
|
cost: { value: 0.12, display: "$0.12", samples: 1, total: 1, refs: [failedAttempt.locator] },
|
|
365
373
|
attempts: [failedAttempt, failedRetryAttempt],
|
|
@@ -388,7 +396,6 @@ export const experimentListItems = [
|
|
|
388
396
|
{
|
|
389
397
|
evalId: "geometry/angles",
|
|
390
398
|
verdict: "errored",
|
|
391
|
-
reason: erroredAttempt.error,
|
|
392
399
|
duration: { value: 4_500, display: "4.5s", samples: 1, total: 1, refs: [erroredAttempt.locator] },
|
|
393
400
|
cost: { value: null, display: "—", samples: 0, total: 1, refs: [] },
|
|
394
401
|
attempts: [erroredAttempt],
|
package/dist/report/report.d.ts
CHANGED
|
@@ -13,7 +13,11 @@ export interface ReportDefinition {
|
|
|
13
13
|
[REPORT_DEFINITION]: true;
|
|
14
14
|
}
|
|
15
15
|
export declare function defineReport(build: (ctx: ReportContext) => ReportNode | Promise<ReportNode>): ReportDefinition;
|
|
16
|
-
/**
|
|
16
|
+
/**
|
|
17
|
+
* 宿主装载报告文件时用:默认导出是不是 ReportDefinition。defineReport 的产物是普通对象;
|
|
18
|
+
* 「组件兼报告」(把 defineReport 产物 Object.assign 到双面组件上)是挂了 build 面的
|
|
19
|
+
* 可调用函数,同样算数——判据只看 build 面与标记,不看宿主形态。
|
|
20
|
+
*/
|
|
17
21
|
export declare function isReportDefinition(value: unknown): value is ReportDefinition;
|
|
18
22
|
/**
|
|
19
23
|
* text 宿主的装载语义:build → 渲染前解析数据组件(唯一的 await 边界)→ 树校验 → 遍历渲染
|
package/dist/report/report.js
CHANGED
|
@@ -14,9 +14,13 @@ export function defineReport(build) {
|
|
|
14
14
|
}
|
|
15
15
|
return { build, [REPORT_DEFINITION]: true };
|
|
16
16
|
}
|
|
17
|
-
/**
|
|
17
|
+
/**
|
|
18
|
+
* 宿主装载报告文件时用:默认导出是不是 ReportDefinition。defineReport 的产物是普通对象;
|
|
19
|
+
* 「组件兼报告」(把 defineReport 产物 Object.assign 到双面组件上)是挂了 build 面的
|
|
20
|
+
* 可调用函数,同样算数——判据只看 build 面与标记,不看宿主形态。
|
|
21
|
+
*/
|
|
18
22
|
export function isReportDefinition(value) {
|
|
19
|
-
return (typeof value === "object" &&
|
|
23
|
+
return ((typeof value === "object" || typeof value === "function") &&
|
|
20
24
|
value !== null &&
|
|
21
25
|
value[REPORT_DEFINITION] === true &&
|
|
22
26
|
typeof value.build === "function");
|
|
@@ -24,6 +24,6 @@ export declare function scoreboardText(data: ScoreboardData, ctx: TextContext):
|
|
|
24
24
|
export declare function scatterText(data: ScatterData, ctx: TextContext): string;
|
|
25
25
|
export declare function lineText(data: LineData, ctx: TextContext): string;
|
|
26
26
|
export declare function deltaText(data: DeltaData, ctx: TextContext): string;
|
|
27
|
-
export declare function experimentListText(items: ExperimentListItem[], ctx: TextContext): string;
|
|
27
|
+
export declare function experimentListText(items: ExperimentListItem[], ctx: TextContext, relativeTo?: string): string;
|
|
28
28
|
export declare function evalListText(items: EvalListItem[], ctx: TextContext): string;
|
|
29
29
|
export declare function attemptListText(items: AttemptListItem[], total: number | undefined, ctx: TextContext): string;
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
// 零 react、零 IO、纯同步 —— 这是 text 宿主不需要 react-dom 的那一半。
|
|
5
5
|
// chrome 文案(注脚、verdict 词、截断提示)经 ctx.locale 查 locale 字典,
|
|
6
6
|
// 默认 en 与历史输出逐字一致;数据(display、键、warnings message)不本地化。
|
|
7
|
-
import { attemptItemReason, formatDurationMs, formatMetricValue, formatPlainNumber, formatUSD, verdictMark, } from "../format.js";
|
|
7
|
+
import { attemptItemReason, experimentDisplayName, formatDurationMs, formatMetricValue, formatPlainNumber, formatUSD, verdictMark, } from "../format.js";
|
|
8
8
|
import { countText, localeText, resolveMetricLabel } from "../locale.js";
|
|
9
|
-
import {
|
|
9
|
+
import { padDisplay, stringWidth, textBar, wrapDisplay } from "./layout.js";
|
|
10
10
|
import { renderTableText } from "./table.js";
|
|
11
11
|
import { renderCharPlot, renderCoordinateTable } from "./plot.js";
|
|
12
12
|
const MISSING_MARK = "—";
|
|
@@ -399,12 +399,12 @@ export function deltaText(data, ctx) {
|
|
|
399
399
|
// 三面共用的紧凑标记:`locator✓`(判定符紧跟 locator,中间不留空格)。
|
|
400
400
|
// ExperimentList / EvalList 逐 attempt 只列这一个标记 + 各自的原因/耗时摘要,不重复整段
|
|
401
401
|
// niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼 `niceeval show <locator>`——
|
|
402
|
-
//
|
|
402
|
+
// 比较列表都不内联完整断言或命令模板。
|
|
403
403
|
function locatorBadge(item) {
|
|
404
404
|
return `${item.locator}${verdictMark(item.verdict)}`;
|
|
405
405
|
}
|
|
406
406
|
// ── ExperimentList ──
|
|
407
|
-
function experimentSummaryTable(items, ctx) {
|
|
407
|
+
function experimentSummaryTable(items, ctx, relativeTo) {
|
|
408
408
|
const locale = ctx.locale;
|
|
409
409
|
const compact = ctx.width < 100;
|
|
410
410
|
const columns = [
|
|
@@ -420,7 +420,7 @@ function experimentSummaryTable(items, ctx) {
|
|
|
420
420
|
const rows = items.map((item) => ({
|
|
421
421
|
key: item.experimentId,
|
|
422
422
|
cells: {
|
|
423
|
-
experiment: item.experimentId,
|
|
423
|
+
experiment: experimentDisplayName(item.experimentId, relativeTo),
|
|
424
424
|
model: item.model ?? localeText(locale, "experimentList.defaultModel"),
|
|
425
425
|
agent: item.agent,
|
|
426
426
|
duration: cellText(item.duration),
|
|
@@ -430,18 +430,25 @@ function experimentSummaryTable(items, ctx) {
|
|
|
430
430
|
cost: cellText(item.cost),
|
|
431
431
|
},
|
|
432
432
|
}));
|
|
433
|
-
const metadata = items.flatMap((item) => wrapDisplay(`${item.experimentId}: ${localeText(locale, "overview.evalsCount", { n: item.evals })} · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })} · ${item.lastRunAt}`, Math.max(8, ctx.width - 2)).map((line) => ` ${line}`));
|
|
433
|
+
const metadata = items.flatMap((item) => wrapDisplay(`${experimentDisplayName(item.experimentId, relativeTo)}: ${localeText(locale, "overview.evalsCount", { n: item.evals })} · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })} · ${item.lastRunAt}`, Math.max(8, ctx.width - 2)).map((line) => ` ${line}`));
|
|
434
434
|
return [renderTableText({ columns, rows, locale }, ctx), metadata.join("\n")].join("\n");
|
|
435
435
|
}
|
|
436
|
-
function experimentDetailTable(item, ctx) {
|
|
436
|
+
function experimentDetailTable(item, ctx, relativeTo) {
|
|
437
437
|
const locale = ctx.locale;
|
|
438
438
|
const columns = [
|
|
439
439
|
{ key: "status", header: localeText(locale, "experimentList.status") },
|
|
440
440
|
{ key: "entity", header: localeText(locale, "experimentList.evalAttempt") },
|
|
441
|
-
|
|
441
|
+
// Result 是可扫读的失败预览,不是证据面:两行放不下的以 … 收口,完整值走 locator 下钻。
|
|
442
|
+
{ key: "result", header: localeText(locale, "experimentList.result"), maxLines: 2 },
|
|
442
443
|
{ key: "duration", header: localeText(locale, "experimentList.duration"), align: "right" },
|
|
443
444
|
{ key: "cost", header: localeText(locale, "experimentList.cost"), align: "right" },
|
|
444
445
|
];
|
|
446
|
+
// Result 的字符预算 ≈ 两行 × 它能分到的列宽(总宽减其它列的自然宽与列距)。这里只做
|
|
447
|
+
// 优先级让位(标题先截、received 最后截)的粗预算;精确的按宽度收口由列的 maxLines 兜底。
|
|
448
|
+
const statusWidth = Math.max(stringWidth(localeText(locale, "experimentList.status")), ...item.evalRows.map((row) => stringWidth(`${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`)));
|
|
449
|
+
const entityWidth = Math.max(stringWidth(localeText(locale, "experimentList.evalAttempt")), ...item.evalRows.flatMap((row) => [stringWidth(row.evalId), ...row.attempts.map((a) => stringWidth(a.locator) + 3)]));
|
|
450
|
+
const fixedWidth = statusWidth + entityWidth + 8 /* duration */ + 6 /* cost */ + 3 * 4; /* 4 段列距 */
|
|
451
|
+
const resultBudget = Math.max(24, (ctx.width - fixedWidth) * 2);
|
|
445
452
|
const rows = item.evalRows.flatMap((row) => {
|
|
446
453
|
const parent = {
|
|
447
454
|
key: row.evalId,
|
|
@@ -449,8 +456,8 @@ function experimentDetailTable(item, ctx) {
|
|
|
449
456
|
status: `${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`,
|
|
450
457
|
entity: row.evalId,
|
|
451
458
|
result: "",
|
|
452
|
-
duration: "",
|
|
453
|
-
cost: "",
|
|
459
|
+
duration: localeText(locale, "entityList.average", { value: cellText(row.duration) }),
|
|
460
|
+
cost: localeText(locale, "entityList.average", { value: cellText(row.cost) }),
|
|
454
461
|
},
|
|
455
462
|
};
|
|
456
463
|
const attempts = row.attempts.map((attempt, index) => ({
|
|
@@ -458,7 +465,7 @@ function experimentDetailTable(item, ctx) {
|
|
|
458
465
|
cells: {
|
|
459
466
|
status: ` ${verdictMark(attempt.verdict)}`,
|
|
460
467
|
entity: `${index === row.attempts.length - 1 ? "└─" : "├─"} ${attempt.locator}`,
|
|
461
|
-
result: attemptItemReason(attempt) ?? MISSING_MARK,
|
|
468
|
+
result: attemptItemReason(attempt, resultBudget) ?? MISSING_MARK,
|
|
462
469
|
duration: attempt.verdict === "skipped" && attempt.durationMs === 0 ? null : formatDurationMs(attempt.durationMs),
|
|
463
470
|
cost: attempt.costUSD === undefined ? null : formatUSD(attempt.costUSD),
|
|
464
471
|
},
|
|
@@ -468,16 +475,22 @@ function experimentDetailTable(item, ctx) {
|
|
|
468
475
|
const flags = item.flags && Object.keys(item.flags).length > 0
|
|
469
476
|
? `${localeText(locale, "experimentList.flags")} ${Object.entries(item.flags).map(([key, value]) => `${key}=${String(value)}`).join(" · ")}`
|
|
470
477
|
: undefined;
|
|
471
|
-
return [item.experimentId, flags, renderTableText({ columns, rows, locale }, ctx)]
|
|
478
|
+
return [experimentDisplayName(item.experimentId, relativeTo), flags, renderTableText({ columns, rows, locale }, ctx)]
|
|
479
|
+
.filter(Boolean)
|
|
480
|
+
.join("\n");
|
|
472
481
|
}
|
|
473
|
-
export function experimentListText(items, ctx) {
|
|
482
|
+
export function experimentListText(items, ctx, relativeTo) {
|
|
474
483
|
if (items.length === 0)
|
|
475
484
|
return localeText(ctx.locale, "attemptList.empty");
|
|
476
|
-
return [
|
|
485
|
+
return [
|
|
486
|
+
experimentSummaryTable(items, ctx, relativeTo),
|
|
487
|
+
...items.map((item) => experimentDetailTable(item, ctx, relativeTo)),
|
|
488
|
+
].join("\n\n");
|
|
477
489
|
}
|
|
478
490
|
// ── EvalList ──
|
|
479
|
-
function evalListAttemptLine(item) {
|
|
480
|
-
|
|
491
|
+
function evalListAttemptLine(item, ctx) {
|
|
492
|
+
// 行式列表同守「Result 最多两行」:预算 = 两行终端宽,超出由 fit 投影按优先级让位。
|
|
493
|
+
const reason = attemptItemReason(item, Math.max(24, ctx.width * 2 - stringWidth(locatorBadge(item)) - 6));
|
|
481
494
|
return ` ${locatorBadge(item)}${reason ? ` · ${reason}` : ""}`;
|
|
482
495
|
}
|
|
483
496
|
export function evalListText(items, ctx) {
|
|
@@ -489,18 +502,21 @@ export function evalListText(items, ctx) {
|
|
|
489
502
|
const summary = [
|
|
490
503
|
localeText(locale, "attemptList.score", { score: cellText(item.score) }),
|
|
491
504
|
localeText(locale, "overview.attemptsCount", { n: item.attempts.length }),
|
|
492
|
-
|
|
493
|
-
|
|
505
|
+
localeText(locale, "entityList.average", {
|
|
506
|
+
value: item.duration.value === null ? missingText(locale) : formatDurationMs(item.duration.value),
|
|
507
|
+
}),
|
|
508
|
+
localeText(locale, "entityList.average", {
|
|
509
|
+
value: item.cost.value === null ? missingText(locale) : formatUSD(item.cost.value),
|
|
510
|
+
}),
|
|
494
511
|
].join(" · ");
|
|
495
|
-
const attemptLines = item.attempts.map(evalListAttemptLine);
|
|
512
|
+
const attemptLines = item.attempts.map((attempt) => evalListAttemptLine(attempt, ctx));
|
|
496
513
|
return [identity, ` ${summary}`, ...attemptLines].join("\n");
|
|
497
514
|
});
|
|
498
515
|
return blocks.join("\n\n");
|
|
499
516
|
}
|
|
500
517
|
// ── AttemptList ──
|
|
501
|
-
/**
|
|
502
|
-
|
|
503
|
-
function attemptListItemText(item, ctx, locale) {
|
|
518
|
+
/** Attempt 比较卡片:只显示一条主失败摘要(至多两行终端宽);完整 assertions 走 locator 下钻。 */
|
|
519
|
+
function attemptListItemText(item, ctx) {
|
|
504
520
|
const head = [
|
|
505
521
|
`${verdictMark(item.verdict)} ${item.locator}`,
|
|
506
522
|
item.evalId,
|
|
@@ -509,31 +525,16 @@ function attemptListItemText(item, ctx, locale) {
|
|
|
509
525
|
...(item.costUSD !== undefined ? [formatUSD(item.costUSD)] : []),
|
|
510
526
|
].join(" · ");
|
|
511
527
|
const lines = [head];
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
for (const assertion of item.assertions) {
|
|
516
|
-
const scoreText = assertion.threshold !== undefined
|
|
517
|
-
? `${formatPlainNumber(assertion.score)}/${formatPlainNumber(assertion.threshold)}`
|
|
518
|
-
: formatPlainNumber(assertion.score);
|
|
519
|
-
lines.push(` ${assertion.severity} ${assertion.name} · ${localeText(locale, `verdict.${assertion.passed ? "passed" : "failed"}`)}${assertion.severity === "soft" ? ` ${scoreText}` : ""}`);
|
|
520
|
-
if (assertion.detail)
|
|
521
|
-
lines.push(indentBlock(wrapDisplay(assertion.detail, ctx.width - 4).join("\n"), " "));
|
|
522
|
-
if (assertion.evidence) {
|
|
523
|
-
const limit = Math.max(240, ctx.width * 5);
|
|
524
|
-
const evidence = assertion.evidence.length <= limit
|
|
525
|
-
? assertion.evidence
|
|
526
|
-
: `${assertion.evidence.slice(0, limit)}… (${assertion.evidence.length - limit} more chars; open ${item.locator} for full evidence)`;
|
|
527
|
-
lines.push(indentBlock(wrapDisplay(evidence, ctx.width - 6).join("\n"), " "));
|
|
528
|
-
}
|
|
529
|
-
}
|
|
528
|
+
const reason = attemptItemReason(item, Math.max(24, ctx.width * 2 - 4));
|
|
529
|
+
if (reason)
|
|
530
|
+
lines.push(` ${reason}`);
|
|
530
531
|
return lines.join("\n");
|
|
531
532
|
}
|
|
532
533
|
export function attemptListText(items, total, ctx) {
|
|
533
534
|
const locale = ctx.locale;
|
|
534
535
|
if (items.length === 0)
|
|
535
536
|
return localeText(locale, "attemptList.empty");
|
|
536
|
-
const blocks = items.map((item) => attemptListItemText(item, ctx
|
|
537
|
+
const blocks = items.map((item) => attemptListItemText(item, ctx));
|
|
537
538
|
const remaining = (total ?? items.length) - items.length;
|
|
538
539
|
if (remaining > 0)
|
|
539
540
|
blocks.push(localeText(locale, "attemptList.truncatedText", { n: remaining }));
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
// 列宽按显示宽度算(CJK 记 2 列);null 渲染 —,不补 0;超宽先折最宽的左对齐列,
|
|
7
7
|
// 压到下限仍放不下就从右侧丢列并如实报数(「截断报剩余」是既有契约,不在这里破例)。
|
|
8
8
|
import { countText, localeText } from "../locale.js";
|
|
9
|
-
import { renderAlignedRows, stringWidth, wrapDisplay } from "./layout.js";
|
|
9
|
+
import { charDisplayWidth, renderAlignedRows, stringWidth, wrapDisplay } from "./layout.js";
|
|
10
10
|
const MISSING_MARK = "—";
|
|
11
11
|
/** 列间距,与 renderAlignedRows 的 join(" ") 一致。 */
|
|
12
12
|
const COLUMN_GAP = 3;
|
|
@@ -47,12 +47,36 @@ function fitWidths(natural, align, available) {
|
|
|
47
47
|
}
|
|
48
48
|
return { widths, hidden };
|
|
49
49
|
}
|
|
50
|
-
/**
|
|
51
|
-
function
|
|
50
|
+
/** 按显示宽度截到 width(不够放 `…` 时原样返回)。 */
|
|
51
|
+
function truncateDisplay(text, width) {
|
|
52
|
+
if (stringWidth(text) <= width)
|
|
53
|
+
return text;
|
|
54
|
+
let out = "";
|
|
55
|
+
let used = 0;
|
|
56
|
+
for (const ch of text) {
|
|
57
|
+
const w = charDisplayWidth(ch.codePointAt(0));
|
|
58
|
+
if (used + w > width)
|
|
59
|
+
break;
|
|
60
|
+
out += ch;
|
|
61
|
+
used += w;
|
|
62
|
+
}
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
/** 列的 maxLines 收口:超出的行丢弃,最后一行以 `…` 如实标注被收口。 */
|
|
66
|
+
function clampCellLines(lines, maxLines, width) {
|
|
67
|
+
if (maxLines === undefined || lines.length <= maxLines)
|
|
68
|
+
return lines;
|
|
69
|
+
const kept = lines.slice(0, Math.max(1, maxLines));
|
|
70
|
+
const last = kept[kept.length - 1];
|
|
71
|
+
kept[kept.length - 1] = `${truncateDisplay(last, Math.max(1, width - 1))}…`;
|
|
72
|
+
return kept;
|
|
73
|
+
}
|
|
74
|
+
/** 逻辑行 → 物理行:每格折到自己的列宽,列带 maxLines 就收口,行高取最高的那格,矮格补空串。 */
|
|
75
|
+
function toPhysicalRows(cells, widths, maxLines) {
|
|
52
76
|
const wrapped = cells.map((cell, c) =>
|
|
53
77
|
// 放得下就原样保留:wrapDisplay 会把连续空格折成一个,而 DeltaTable 的
|
|
54
78
|
// "50% → 62% +12pp" 这类格子内含固定间距 —— 不折行时一个字节都不能动。
|
|
55
|
-
!cell.includes("\n") && stringWidth(cell) <= widths[c] ? [cell] : wrapDisplay(cell, widths[c]));
|
|
79
|
+
clampCellLines(!cell.includes("\n") && stringWidth(cell) <= widths[c] ? [cell] : wrapDisplay(cell, widths[c]), maxLines[c], widths[c]));
|
|
56
80
|
const height = Math.max(...wrapped.map((lines) => lines.length), 1);
|
|
57
81
|
const out = [];
|
|
58
82
|
for (let i = 0; i < height; i++)
|
|
@@ -80,7 +104,14 @@ export function renderTableText(props, ctx) {
|
|
|
80
104
|
const matrix = [header, ...body];
|
|
81
105
|
const natural = header.map((_, c) => Math.max(...matrix.map((row) => stringWidth(row[c] ?? ""))));
|
|
82
106
|
const { widths, hidden } = fitWidths(natural, align, ctx.width);
|
|
83
|
-
const
|
|
107
|
+
const maxLines = props.columns.map((column) => column.maxLines);
|
|
108
|
+
if (hasLocator)
|
|
109
|
+
maxLines.push(undefined);
|
|
110
|
+
// 表头不参与 maxLines 收口:表头是自己写的短词,收口只服务数据格。
|
|
111
|
+
const physical = [
|
|
112
|
+
...toPhysicalRows(header.slice(0, widths.length), widths, widths.map(() => undefined)),
|
|
113
|
+
...body.flatMap((row) => toPhysicalRows(row.slice(0, widths.length), widths, maxLines)),
|
|
114
|
+
];
|
|
84
115
|
const table = renderAlignedRows(physical, align);
|
|
85
116
|
return hidden > 0 ? `${table}\n${countText(locale, "table.columnsHidden", hidden)}` : table;
|
|
86
117
|
}
|