niceeval 0.6.0 → 0.6.1
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 +5 -1
- package/dist/report/built-ins/experiment-comparison.d.ts +1 -0
- package/dist/report/built-ins/experiment-comparison.js +13 -0
- package/dist/report/built-ins/index.d.ts +1 -1
- package/dist/report/built-ins/index.js +1 -1
- package/dist/report/components.d.ts +3 -1
- package/dist/report/components.js +1 -1
- package/dist/report/compute.js +15 -33
- package/dist/report/format.d.ts +0 -6
- package/dist/report/format.js +1 -18
- package/dist/report/index.d.ts +2 -2
- package/dist/report/index.js +1 -1
- package/dist/report/locale.d.ts +13 -2
- package/dist/report/locale.js +26 -3
- package/dist/report/metrics.js +1 -1
- package/dist/report/react/AttemptList.d.ts +2 -2
- package/dist/report/react/AttemptList.js +3 -4
- package/dist/report/react/ExperimentList.d.ts +3 -2
- package/dist/report/react/ExperimentList.js +59 -7
- package/dist/report/react/MetricScatter.js +1 -5
- package/dist/report/react/fixtures.js +9 -4
- package/dist/report/react/format.d.ts +1 -1
- package/dist/report/react/format.js +1 -1
- package/dist/report/react/index.d.ts +1 -1
- package/dist/report/text/faces.js +71 -33
- package/dist/report/types.d.ts +3 -15
- package/docs-site/zh/concepts/adapter.mdx +2 -2
- package/docs-site/zh/concepts/assert.mdx +11 -10
- package/docs-site/zh/concepts/evals.mdx +7 -6
- package/docs-site/zh/concepts/overview.mdx +1 -1
- package/docs-site/zh/guides/agent-feedback-loop.mdx +9 -7
- package/docs-site/zh/guides/fixtures.mdx +3 -1
- package/docs-site/zh/guides/official-adapters.mdx +1 -1
- package/docs-site/zh/guides/publish-report.mdx +1 -1
- package/docs-site/zh/guides/report-components.mdx +13 -13
- package/docs-site/zh/guides/results-data.mdx +28 -2
- package/docs-site/zh/guides/sandbox-agent.mdx +1 -0
- package/docs-site/zh/guides/sandbox-providers.mdx +1 -1
- package/docs-site/zh/guides/viewing-results.mdx +6 -5
- package/docs-site/zh/guides/write-send.mdx +2 -1
- package/docs-site/zh/reference/cli.mdx +3 -3
- package/docs-site/zh/reference/define-agent.mdx +3 -1
- package/docs-site/zh/reference/events.mdx +2 -2
- package/docs-site/zh/reference/expect.mdx +10 -5
- package/package.json +1 -1
- package/src/agents/types.ts +5 -1
- package/src/i18n/en.ts +1 -2
- package/src/i18n/zh-CN.ts +1 -1
- package/src/report/built-in-user-parity.test.tsx +44 -47
- package/src/report/built-ins/experiment-comparison.tsx +19 -0
- package/src/report/built-ins/index.ts +1 -1
- package/src/report/components.tsx +3 -1
- package/src/report/compute.ts +12 -36
- package/src/report/dual-render.test.tsx +33 -29
- package/src/report/format.ts +1 -16
- package/src/report/index.ts +1 -2
- package/src/report/locale.ts +26 -3
- package/src/report/metrics.ts +1 -1
- package/src/report/react/AttemptList.tsx +4 -6
- package/src/report/react/ExperimentList.tsx +167 -55
- package/src/report/react/MetricScatter.tsx +1 -11
- package/src/report/react/enhance.js +39 -0
- package/src/report/react/fixtures.ts +10 -4
- package/src/report/react/format.ts +1 -1
- package/src/report/react/index.tsx +0 -1
- package/src/report/react/render.test.tsx +24 -8
- package/src/report/react/styles.css +40 -13
- package/src/report/report.test.ts +2 -7
- package/src/report/text/faces.ts +76 -36
- package/src/report/types.ts +3 -15
- package/src/results/host-equivalence.test.ts +3 -3
- package/src/show/index.ts +8 -15
- package/src/show/render.ts +7 -204
- package/src/show/show.test.ts +22 -23
- package/src/view/data.test.ts +1 -1
- package/src/view/data.ts +6 -6
- package/src/view/index.ts +1 -1
- package/src/view/shared/types.ts +1 -1
- package/src/view/view-report.test.ts +5 -5
- package/dist/o11y/execution-tree.d.ts +0 -103
- package/dist/o11y/otlp/select.d.ts +0 -22
- package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
- package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
- package/dist/results/annotated-source.d.ts +0 -61
- package/dist/results/attempt-evidence.d.ts +0 -69
- package/dist/results/attempt-source.d.ts +0 -15
- package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
|
@@ -73,6 +73,45 @@
|
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
+
// ExperimentList 的 web 面以原生 details 表达「八列主行 + 展开明细」,不是 table/tbody。
|
|
77
|
+
// 单独按 summary 的列排序、按整条 details 文本过滤;无 JS 时保持数据侧成功率降序。
|
|
78
|
+
document.addEventListener("click", function (e) {
|
|
79
|
+
var control = closest(e.target, "[data-nre-experiment-sort]");
|
|
80
|
+
if (!control) return;
|
|
81
|
+
var board = control.closest(".nre-experiment-table");
|
|
82
|
+
if (!board) return;
|
|
83
|
+
var index = Number(control.getAttribute("data-nre-experiment-sort"));
|
|
84
|
+
var dir = control.classList.contains("nre-sort-asc") ? "desc" : "asc";
|
|
85
|
+
var controls = board.querySelectorAll("[data-nre-experiment-sort]");
|
|
86
|
+
for (var i = 0; i < controls.length; i++) controls[i].classList.remove("nre-sort-asc", "nre-sort-desc");
|
|
87
|
+
control.classList.add(dir === "asc" ? "nre-sort-asc" : "nre-sort-desc");
|
|
88
|
+
var entries = Array.prototype.slice.call(board.querySelectorAll(":scope > .nre-experiment-entry"));
|
|
89
|
+
entries.sort(function (a, b) {
|
|
90
|
+
var ac = a.querySelector(".nre-experiment-summary").children[index];
|
|
91
|
+
var bc = b.querySelector(".nre-experiment-summary").children[index];
|
|
92
|
+
var av = ac ? ac.getAttribute("data-sort-value") || ac.textContent.trim() : "";
|
|
93
|
+
var bv = bc ? bc.getAttribute("data-sort-value") || bc.textContent.trim() : "";
|
|
94
|
+
if (av === "" && bv === "") return 0;
|
|
95
|
+
if (av === "") return 1;
|
|
96
|
+
if (bv === "") return -1;
|
|
97
|
+
var an = Number(av), bn = Number(bv);
|
|
98
|
+
var out = !isNaN(an) && !isNaN(bn) ? an - bn : String(av).localeCompare(String(bv));
|
|
99
|
+
return dir === "asc" ? out : -out;
|
|
100
|
+
});
|
|
101
|
+
for (var j = 0; j < entries.length; j++) board.appendChild(entries[j]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
document.addEventListener("input", function (e) {
|
|
105
|
+
var input = closest(e.target, "input[data-nre-experiment-filter]");
|
|
106
|
+
if (!input) return;
|
|
107
|
+
var scope = input.parentElement;
|
|
108
|
+
var entries = scope ? scope.querySelectorAll(".nre-experiment-entry") : [];
|
|
109
|
+
var query = input.value.trim().toLowerCase();
|
|
110
|
+
for (var i = 0; i < entries.length; i++) {
|
|
111
|
+
entries[i].classList.toggle("nre-row-hidden", query !== "" && entries[i].textContent.toLowerCase().indexOf(query) === -1);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
76
115
|
// ───────────────────────── tooltip:.nre-scatter-point / .nre-line-point ─────────────────────────
|
|
77
116
|
// 首次 hover 时把点内 <title> 的内容搬进 data-nre-title(避免与原生 tooltip 重影),
|
|
78
117
|
// 渲染样式化 tooltip div(定位在点上方,挂在所属 figure 里)。无 JS 时 <title> 原样生效。
|
|
@@ -330,7 +330,6 @@ const failedAttempt: AttemptListItem = {
|
|
|
330
330
|
durationMs: 32_000,
|
|
331
331
|
costUSD: 0.12,
|
|
332
332
|
locator: locator("@1a4a4a4"),
|
|
333
|
-
capabilities: { eval: true, execution: true, timing: true, diff: false },
|
|
334
333
|
};
|
|
335
334
|
|
|
336
335
|
const erroredAttempt: AttemptListItem = {
|
|
@@ -343,7 +342,13 @@ const erroredAttempt: AttemptListItem = {
|
|
|
343
342
|
assertions: [],
|
|
344
343
|
durationMs: 4_500,
|
|
345
344
|
locator: locator("@1c1c1c1"),
|
|
346
|
-
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const failedRetryAttempt: AttemptListItem = {
|
|
348
|
+
...failedAttempt,
|
|
349
|
+
attempt: 4,
|
|
350
|
+
durationMs: 35_000,
|
|
351
|
+
locator: locator("@1b5b5b5"),
|
|
347
352
|
};
|
|
348
353
|
|
|
349
354
|
export const attemptListItems: AttemptListItem[] = [failedAttempt, erroredAttempt];
|
|
@@ -376,13 +381,14 @@ export const experimentListItems: ExperimentListItem[] = [
|
|
|
376
381
|
experimentId: "compare/bub",
|
|
377
382
|
agent: "bub",
|
|
378
383
|
model: "gpt-5.4",
|
|
384
|
+
flags: { memory: true },
|
|
379
385
|
verdicts: { passed: 1, failed: 1, errored: 0, skipped: 0 },
|
|
380
386
|
passRate: { value: 0.5, display: "50%", samples: 2, total: 2, refs: [] },
|
|
381
387
|
cost: { value: 0.12, display: "$0.12", samples: 1, total: 2, refs: [failedAttempt.locator] },
|
|
382
388
|
duration: { value: 32_000, display: "32.0s", samples: 2, total: 2, refs: [] },
|
|
383
389
|
tokens: { value: null, display: "—", samples: 0, total: 2, refs: [] },
|
|
384
390
|
evals: 2,
|
|
385
|
-
attempts:
|
|
391
|
+
attempts: 3,
|
|
386
392
|
lastRunAt: "2026-07-01T10:00:00Z",
|
|
387
393
|
evalRows: [
|
|
388
394
|
{
|
|
@@ -391,7 +397,7 @@ export const experimentListItems: ExperimentListItem[] = [
|
|
|
391
397
|
reason: "roots-correct: expected x=2, got x=3",
|
|
392
398
|
duration: { value: 32_000, display: "32.0s", samples: 1, total: 1, refs: [failedAttempt.locator] },
|
|
393
399
|
cost: { value: 0.12, display: "$0.12", samples: 1, total: 1, refs: [failedAttempt.locator] },
|
|
394
|
-
attempts: [failedAttempt],
|
|
400
|
+
attempts: [failedAttempt, failedRetryAttempt],
|
|
395
401
|
},
|
|
396
402
|
{
|
|
397
403
|
evalId: "algebra/simple",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// web 面的小工具:展示格式化统一住在计算侧的 ../format.ts(两个渲染面同一份),
|
|
2
2
|
// 这里 re-export 并补 class 名拼接。MetricCell 一律自带 display,组件不重算。
|
|
3
3
|
|
|
4
|
-
export { MISSING_TEXT,
|
|
4
|
+
export { MISSING_TEXT, formatDurationMs, formatPercent, formatUSD, verdictMark } from "../format.ts";
|
|
5
5
|
|
|
6
6
|
/** 拼 class 名:过滤空值,末尾接使用者透传的 className。 */
|
|
7
7
|
export function cx(...parts: (string | undefined | false)[]): string {
|
|
@@ -271,7 +271,7 @@ describe("MetricScatter", () => {
|
|
|
271
271
|
expect(empty).toContain("4 points missing data");
|
|
272
272
|
});
|
|
273
273
|
|
|
274
|
-
it("恰好 1
|
|
274
|
+
it("恰好 1 个可画点:照常画出单点", () => {
|
|
275
275
|
const single = renderToStaticMarkup(
|
|
276
276
|
<MetricScatter
|
|
277
277
|
data={{
|
|
@@ -281,8 +281,8 @@ describe("MetricScatter", () => {
|
|
|
281
281
|
}}
|
|
282
282
|
/>,
|
|
283
283
|
);
|
|
284
|
-
expect(single).
|
|
285
|
-
expect(single).toContain("
|
|
284
|
+
expect(single).toContain("<svg");
|
|
285
|
+
expect(single).toContain("nre-scatter-point");
|
|
286
286
|
});
|
|
287
287
|
});
|
|
288
288
|
|
|
@@ -339,10 +339,10 @@ describe("AttemptList", () => {
|
|
|
339
339
|
expect(html).not.toContain("more not shown");
|
|
340
340
|
});
|
|
341
341
|
|
|
342
|
-
it("每条 attempt 带 locator +
|
|
342
|
+
it("每条 attempt 带 locator + 默认证据室深链,不追加字母缩写", () => {
|
|
343
343
|
expect(html).toContain('href="#/attempt/@1a4a4a4"');
|
|
344
344
|
expect(html).toContain('href="#/attempt/@1c1c1c1"');
|
|
345
|
-
expect(html).
|
|
345
|
+
expect(html).not.toMatch(/\[[EXD⏱,]+\]/);
|
|
346
346
|
});
|
|
347
347
|
|
|
348
348
|
it("长文本(evidence)收进 <details>,零 JS 可展开", () => {
|
|
@@ -373,18 +373,34 @@ describe("EvalList", () => {
|
|
|
373
373
|
});
|
|
374
374
|
|
|
375
375
|
describe("ExperimentList", () => {
|
|
376
|
-
const html = renderToStaticMarkup(<ExperimentList items={experimentListItems} />);
|
|
376
|
+
const html = renderToStaticMarkup(<ExperimentList items={experimentListItems} filter />);
|
|
377
377
|
|
|
378
|
-
it("
|
|
378
|
+
it("web 面是带过滤框与八个固定列头的 experiment 比较表", () => {
|
|
379
|
+
expect(html).toContain('data-nre-experiment-filter=""');
|
|
380
|
+
for (const header of ["Experiment", "Model", "Agent", "Avg duration", "Pass rate", "Tokens", "Est. cost", "Result"]) {
|
|
381
|
+
expect(html).toContain(`>${header}</button>`);
|
|
382
|
+
}
|
|
383
|
+
expect(html).toContain('data-nre-experiment-sort="4" class="nre-sort-desc"');
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("主行:身份、agent/model、官方两级聚合指标与判定构成", () => {
|
|
379
387
|
expect(html).toContain("compare/bub");
|
|
380
388
|
expect(html).toContain("compare/codex");
|
|
381
389
|
expect(html).toContain("gpt-5.4");
|
|
382
390
|
expect(html).toContain("50%"); // passRate.display
|
|
391
|
+
expect(html).toContain("1 passed / 1 failed");
|
|
392
|
+
expect(html).toContain("memory=true");
|
|
383
393
|
});
|
|
384
394
|
|
|
385
|
-
it("展开到 Eval
|
|
395
|
+
it("展开到 Eval:同一道题的全部 Attempt 逐条显示", () => {
|
|
386
396
|
expect(html).toContain("algebra/quadratic");
|
|
387
397
|
expect(html).toContain('href="#/attempt/@1a4a4a4"');
|
|
398
|
+
expect(html).toContain('href="#/attempt/@1b5b5b5"');
|
|
399
|
+
expect(html.match(/algebra\/quadratic/g)).toHaveLength(1);
|
|
400
|
+
expect(html).toContain("nre-experiment-eval-header");
|
|
401
|
+
expect(html.match(/nre-experiment-attempt-row/g)).toHaveLength(3);
|
|
402
|
+
expect(html).toContain("├─");
|
|
403
|
+
expect(html).toContain("└─");
|
|
388
404
|
});
|
|
389
405
|
|
|
390
406
|
it("零 JS 靠原生 <details>,无 <script>", () => {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
margin: 1rem 0;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/* ----
|
|
45
|
+
/* ---- locator 与判定符:AttemptList / EvalList / ExperimentList 共用的最小单元 ---- */
|
|
46
46
|
.nre .nre-locator { display:inline-flex; align-items:center; gap:4px; color:var(--nre-c0); font-variant-numeric:tabular-nums; text-decoration:none; white-space:nowrap; }
|
|
47
47
|
.nre .nre-locator:hover { text-decoration:underline; }
|
|
48
48
|
.nre .nre-locator-mark { font-weight:700; }
|
|
@@ -50,7 +50,6 @@
|
|
|
50
50
|
.nre .nre-locator.nre-verdict-failed .nre-locator-mark,
|
|
51
51
|
.nre .nre-locator.nre-verdict-errored .nre-locator-mark { color:var(--bad); }
|
|
52
52
|
.nre .nre-locator.nre-verdict-skipped .nre-locator-mark { color:var(--soft); }
|
|
53
|
-
.nre .nre-locator-caps { color:var(--muted); font-size:.85em; }
|
|
54
53
|
|
|
55
54
|
/* ---- AttemptList:实体列表的叶子层,一条卡片一个 Attempt ---- */
|
|
56
55
|
.nre-attempt-list .nre-attempts { list-style:none; margin:0; padding:0; }
|
|
@@ -93,25 +92,53 @@
|
|
|
93
92
|
.nre-eval-list .nre-attempts { list-style:none; margin:0 0 12px; padding:0 0 0 20px; border-left:2px solid color-mix(in oklch,var(--nre-c0),transparent 55%); }
|
|
94
93
|
.nre-eval-list .nre-eval-list-empty { border:1px dashed var(--line-strong); border-radius:8px; padding:18px; color:var(--muted); text-align:center; background:var(--panel); }
|
|
95
94
|
|
|
96
|
-
/* ---- ExperimentList
|
|
97
|
-
.nre-experiment-
|
|
95
|
+
/* ---- ExperimentList:web 面固定八列比较表,一条 <details> 一个 experiment ---- */
|
|
96
|
+
.nre-experiment-table { border:1px solid var(--line); border-radius:9px; overflow:hidden; background:var(--panel); }
|
|
97
|
+
.nre-experiment-head,
|
|
98
|
+
.nre-experiment-summary { display:grid; grid-template-columns:minmax(220px,1.25fr) minmax(120px,.8fr) minmax(90px,.6fr) minmax(100px,.65fr) minmax(90px,.55fr) minmax(110px,.7fr) minmax(90px,.6fr) minmax(160px,.9fr); column-gap:16px; align-items:center; }
|
|
99
|
+
.nre-experiment-head { padding:12px 20px; color:var(--muted); font-size:12px; font-weight:560; text-transform:uppercase; letter-spacing:.05em; border-bottom:1px solid var(--line); }
|
|
100
|
+
.nre-experiment-head button { appearance:none; border:0; padding:0; background:transparent; color:inherit; font:inherit; text-transform:inherit; letter-spacing:inherit; text-align:left; cursor:pointer; }
|
|
101
|
+
.nre-experiment-head button::after { content:"↕"; margin-left:6px; color:var(--soft); font-size:11px; }
|
|
102
|
+
.nre-experiment-head button.nre-sort-asc::after { content:"↑"; color:var(--text); }
|
|
103
|
+
.nre-experiment-head button.nre-sort-desc::after { content:"↓"; color:var(--text); }
|
|
104
|
+
.nre-experiment-table-wrap > .nre-filter { height:34px; min-width:260px; margin-bottom:16px; border:1px solid var(--line); border-radius:7px; padding:0 11px; background:var(--panel); color:var(--text); font:inherit; }
|
|
98
105
|
.nre-experiment-list .nre-experiment-entry { border-bottom:1px solid var(--line); }
|
|
99
106
|
.nre-experiment-list .nre-experiment-entry:last-child { border-bottom:0; }
|
|
100
|
-
.nre-experiment-list .nre-experiment-summary {
|
|
107
|
+
.nre-experiment-list .nre-experiment-summary { padding:13px 20px; cursor:pointer; list-style:none; transition:background .12s; }
|
|
101
108
|
.nre-experiment-list .nre-experiment-summary::-webkit-details-marker { display:none; }
|
|
102
109
|
.nre-experiment-list .nre-experiment-summary:hover { background:color-mix(in oklch,var(--panel-2),var(--panel) 25%); }
|
|
103
|
-
.nre-experiment-list .nre-experiment-
|
|
104
|
-
.nre-experiment-list .nre-experiment-
|
|
105
|
-
.nre-experiment-list .nre-experiment-
|
|
106
|
-
.nre-experiment-list .nre-experiment-
|
|
107
|
-
.nre-experiment-list .nre-experiment-
|
|
108
|
-
.nre-experiment-list .nre-experiment-
|
|
109
|
-
.nre-experiment-list .nre-
|
|
110
|
+
.nre-experiment-list .nre-experiment-summary > span { min-width:0; }
|
|
111
|
+
.nre-experiment-list .nre-experiment-name { position:relative; padding-left:30px; }
|
|
112
|
+
.nre-experiment-list .nre-experiment-name::before { content:""; position:absolute; left:5px; top:6px; width:7px; height:7px; border-right:1.5px solid var(--muted); border-bottom:1.5px solid var(--muted); transform:rotate(-45deg); transition:transform .15s; }
|
|
113
|
+
.nre-experiment-list .nre-experiment-entry[open] .nre-experiment-name::before { transform:rotate(45deg); }
|
|
114
|
+
.nre-experiment-list .nre-experiment-id { display:block; overflow-wrap:anywhere; font-weight:650; }
|
|
115
|
+
.nre-experiment-list .nre-experiment-name small { display:block; margin-top:3px; color:var(--muted); font-size:12px; font-weight:400; }
|
|
116
|
+
.nre-experiment-list .nre-num { font-variant-numeric:tabular-nums; font-feature-settings:"tnum"; white-space:nowrap; }
|
|
117
|
+
.nre-experiment-list .nre-good { color:var(--good); }
|
|
118
|
+
.nre-experiment-list .nre-warn { color:var(--warn); }
|
|
119
|
+
.nre-experiment-list .nre-bad { color:var(--bad); }
|
|
120
|
+
.nre-experiment-list .nre-experiment-pill { display:inline-flex; min-height:22px; align-items:center; border:1px solid var(--line); border-radius:999px; padding:0 8px; color:var(--muted); font-size:12px; white-space:nowrap; }
|
|
121
|
+
.nre-experiment-list .nre-experiment-detail { padding:18px 30px 22px; background:color-mix(in oklch,var(--panel-2),var(--panel) 24%); border-top:1px solid var(--line); }
|
|
122
|
+
.nre-experiment-list .nre-experiment-flags { display:flex; flex-wrap:wrap; gap:8px; margin:0 0 14px; color:var(--soft); font-size:12px; }
|
|
123
|
+
.nre-experiment-list .nre-experiment-flags b { border:1px solid color-mix(in oklch,var(--line),transparent 16%); border-radius:999px; padding:3px 8px; color:var(--text); font-weight:560; }
|
|
124
|
+
.nre-experiment-list .nre-experiment-evals { list-style:none; margin:0; padding:0; display:grid; gap:6px; }
|
|
125
|
+
.nre-experiment-list .nre-experiment-eval,
|
|
126
|
+
.nre-experiment-list .nre-experiment-attempts { list-style:none; margin:0; padding:0; }
|
|
127
|
+
.nre-experiment-list .nre-experiment-eval { padding:4px 0 9px; border-bottom:1px solid color-mix(in oklch,var(--line),transparent 28%); }
|
|
128
|
+
.nre-experiment-list .nre-experiment-eval:last-child { border-bottom:0; }
|
|
129
|
+
.nre-experiment-list .nre-experiment-eval-header,
|
|
130
|
+
.nre-experiment-list .nre-experiment-attempt-row { display:grid; grid-template-columns:20px minmax(220px,1fr) minmax(150px,.7fr) minmax(260px,1fr); column-gap:12px; align-items:baseline; font-size:13px; }
|
|
131
|
+
.nre-experiment-list .nre-experiment-eval-header { padding:7px 0 5px; }
|
|
132
|
+
.nre-experiment-list .nre-experiment-attempt-row { padding:5px 0 5px 20px; color:var(--muted); }
|
|
110
133
|
.nre-experiment-list .nre-eval-id { font-weight:560; overflow-wrap:anywhere; }
|
|
134
|
+
.nre-experiment-list .nre-eval-attempt-count { color:var(--soft); font-size:12px; }
|
|
135
|
+
.nre-experiment-list .nre-eval-summary { color:var(--muted); font-size:.92em; overflow-wrap:anywhere; }
|
|
136
|
+
.nre-experiment-list .nre-attempt-branch { color:var(--line-strong); font-variant-numeric:tabular-nums; }
|
|
111
137
|
.nre-experiment-list .nre-eval-attempt-badges { display:inline-flex; flex-wrap:wrap; gap:.6em; }
|
|
112
|
-
.nre-experiment-list .nre-eval-
|
|
138
|
+
.nre-experiment-list .nre-eval-attempt-metrics,
|
|
113
139
|
.nre-experiment-list .nre-eval-reason { color:var(--muted); font-size:.92em; }
|
|
114
140
|
.nre-experiment-list .nre-experiment-list-empty { border:1px dashed var(--line-strong); border-radius:8px; padding:18px; color:var(--muted); text-align:center; background:var(--panel); }
|
|
141
|
+
@media(max-width:1000px){.nre-experiment-table{overflow-x:auto}.nre-experiment-head,.nre-experiment-summary{min-width:1040px}.nre-experiment-detail{min-width:1040px}}
|
|
115
142
|
|
|
116
143
|
/* ---- verdict 判定色(AttemptList/EvalList/ExperimentList 的 verdict 文字与判定符共用) ---- */
|
|
117
144
|
.nre .nre-verdict-passed { color:var(--good); }
|
|
@@ -136,7 +136,7 @@ function selection(snapshots: Snapshot[], warnings: SelectionWarning[]): Selecti
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
describe("ExperimentList.data", () => {
|
|
139
|
-
it("身份、flags、Eval 判定构成、官方两级聚合汇总指标,展开到 Eval
|
|
139
|
+
it("身份、flags、Eval 判定构成、官方两级聚合汇总指标,展开到 Eval 携带全部 Attempt locator", async () => {
|
|
140
140
|
const s = snap({
|
|
141
141
|
experimentId: "group/codex-e2b--mempal",
|
|
142
142
|
agent: "codex",
|
|
@@ -181,8 +181,6 @@ describe("ExperimentList.data", () => {
|
|
|
181
181
|
expect(evalA.verdict).toBe("passed"); // 任一轮通过 → 该题通过
|
|
182
182
|
expect(evalA.attempts.map((a) => a.attempt)).toEqual([0, 1]);
|
|
183
183
|
expect(evalA.attempts[0]!.locator).toMatch(/^@1[0-9a-z]{7}$/);
|
|
184
|
-
expect(evalA.attempts[0]!.capabilities.execution).toBe(true); // attempt0 的 hasEvents:true
|
|
185
|
-
expect(evalA.attempts[1]!.capabilities.execution).toBe(false);
|
|
186
184
|
// 每次 attempt 独立编码,即使同一道题也不撞车
|
|
187
185
|
expect(evalA.attempts[0]!.locator).not.toBe(evalA.attempts[1]!.locator);
|
|
188
186
|
|
|
@@ -243,7 +241,7 @@ describe("EvalList.data", () => {
|
|
|
243
241
|
});
|
|
244
242
|
|
|
245
243
|
describe("AttemptList.data", () => {
|
|
246
|
-
it("每项一个 Attempt:身份、判定、assertions
|
|
244
|
+
it("每项一个 Attempt:身份、判定、assertions、耗时、成本与 locator", async () => {
|
|
247
245
|
const s = snap({
|
|
248
246
|
experimentId: "exp/x",
|
|
249
247
|
results: [
|
|
@@ -270,9 +268,6 @@ describe("AttemptList.data", () => {
|
|
|
270
268
|
});
|
|
271
269
|
expect(item.assertions).toHaveLength(1);
|
|
272
270
|
expect(item.locator).toMatch(/^@1[0-9a-z]{7}$/);
|
|
273
|
-
// 证据能力:cheap 计算,不调用完整 loadAttemptEvidence——execution 来自 hasEvents,
|
|
274
|
-
// eval 来自 hasSources,diff 懒加载(fixture 的 diff() 恒返回 null → false)。
|
|
275
|
-
expect(item.capabilities).toEqual({ eval: true, execution: true, timing: false, diff: false });
|
|
276
271
|
});
|
|
277
272
|
|
|
278
273
|
it("redact 作用于 error 与断言 detail/evidence,不作用于身份字段", async () => {
|
package/src/report/text/faces.ts
CHANGED
|
@@ -9,7 +9,6 @@ import type {
|
|
|
9
9
|
AttemptListItem,
|
|
10
10
|
DeltaData,
|
|
11
11
|
EvalListItem,
|
|
12
|
-
ExperimentListEvalRow,
|
|
13
12
|
ExperimentListItem,
|
|
14
13
|
GroupSummaryData,
|
|
15
14
|
LineData,
|
|
@@ -24,7 +23,6 @@ import type { TextContext } from "../tree.ts";
|
|
|
24
23
|
import type { TableColumn, TableRow } from "../primitives.tsx";
|
|
25
24
|
import {
|
|
26
25
|
attemptItemReason,
|
|
27
|
-
capabilityBadge,
|
|
28
26
|
formatDurationMs,
|
|
29
27
|
formatMetricValue,
|
|
30
28
|
formatPlainNumber,
|
|
@@ -325,11 +323,6 @@ export function scatterText(data: ScatterData, ctx: TextContext): string {
|
|
|
325
323
|
if (drawable.length === 0) {
|
|
326
324
|
return [localeText(locale, "scatter.noData", axes), ...footnotes].join("\n");
|
|
327
325
|
}
|
|
328
|
-
// 恰好 1 个可画点:成本 × 通过率的比较至少要两个实验,单点不成图。
|
|
329
|
-
if (drawable.length === 1) {
|
|
330
|
-
return [localeText(locale, "scatter.needTwo", axes), ...footnotes].join("\n");
|
|
331
|
-
}
|
|
332
|
-
|
|
333
326
|
// 点太密排不下时降级为坐标表,不硬挤
|
|
334
327
|
if (drawable.length > POINT_MARKS.length || ctx.width < 44) {
|
|
335
328
|
const table = renderCoordinateTable(
|
|
@@ -451,45 +444,93 @@ export function deltaText(data: DeltaData, ctx: TextContext): string {
|
|
|
451
444
|
|
|
452
445
|
// ───────────────────────── 实体列表(ExperimentList / EvalList / AttemptList)─────────────────────────
|
|
453
446
|
//
|
|
454
|
-
// 三面共用的紧凑标记:`locator
|
|
455
|
-
// 中间不留空格)——docs-site/zh/guides/report-components.mdx「终端输出形成反馈闭环」定的形态。
|
|
447
|
+
// 三面共用的紧凑标记:`locator✓`(判定符紧跟 locator,中间不留空格)。
|
|
456
448
|
// ExperimentList / EvalList 逐 attempt 只列这一个标记 + 各自的原因/耗时摘要,不重复整段
|
|
457
449
|
// niceeval show 命令;要看某个 attempt 的完整证据,agent 自己拼 `niceeval show <locator>`——
|
|
458
450
|
// 命令模板只在 AttemptList(叶子层)展示完整断言明细时才值得,不在中间层重复。
|
|
459
451
|
|
|
460
|
-
function locatorBadge(item: { locator: string; verdict: AttemptListItem["verdict"]
|
|
461
|
-
return `${item.locator}${verdictMark(item.verdict)}
|
|
452
|
+
function locatorBadge(item: { locator: string; verdict: AttemptListItem["verdict"] }): string {
|
|
453
|
+
return `${item.locator}${verdictMark(item.verdict)}`;
|
|
462
454
|
}
|
|
463
455
|
|
|
464
456
|
// ── ExperimentList ──
|
|
465
457
|
|
|
466
|
-
function
|
|
467
|
-
const
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
458
|
+
function experimentSummaryTable(items: ExperimentListItem[], ctx: TextContext): string {
|
|
459
|
+
const locale = ctx.locale;
|
|
460
|
+
const compact = ctx.width < 100;
|
|
461
|
+
const columns: TableColumn[] = [
|
|
462
|
+
{ key: "experiment", header: compact && locale === "en" ? "Exp." : localeText(locale, "experimentList.experiment") },
|
|
463
|
+
{ key: "model", header: localeText(locale, "table.model") },
|
|
464
|
+
{ key: "agent", header: localeText(locale, "table.agent") },
|
|
465
|
+
{ key: "duration", header: compact && locale === "en" ? "Avg" : localeText(locale, "experimentList.avgDuration"), align: "right" },
|
|
466
|
+
{ key: "passRate", header: compact && locale === "en" ? "Pass" : localeText(locale, "experimentList.passRate"), align: "right" },
|
|
467
|
+
{ key: "result", header: localeText(locale, "experimentList.result") },
|
|
468
|
+
{ key: "tokens", header: localeText(locale, "experimentList.tokens"), align: "right" },
|
|
469
|
+
{ key: "cost", header: compact && locale === "en" ? "Cost" : localeText(locale, "experimentList.estimatedCost"), align: "right" },
|
|
470
|
+
];
|
|
471
|
+
const rows: TableRow[] = items.map((item) => ({
|
|
472
|
+
key: item.experimentId,
|
|
473
|
+
cells: {
|
|
474
|
+
experiment: item.experimentId,
|
|
475
|
+
model: item.model ?? localeText(locale, "experimentList.defaultModel"),
|
|
476
|
+
agent: item.agent,
|
|
477
|
+
duration: cellText(item.duration),
|
|
478
|
+
passRate: cellText(item.passRate),
|
|
479
|
+
result: verdictTallyText(item.verdicts, locale),
|
|
480
|
+
tokens: cellText(item.tokens),
|
|
481
|
+
cost: cellText(item.cost),
|
|
482
|
+
},
|
|
483
|
+
}));
|
|
484
|
+
const metadata = items.flatMap((item) =>
|
|
485
|
+
wrapDisplay(
|
|
486
|
+
`${item.experimentId}: ${localeText(locale, "overview.evalsCount", { n: item.evals })} · ${localeText(locale, "overview.attemptsCount", { n: item.attempts })} · ${item.lastRunAt}`,
|
|
487
|
+
Math.max(8, ctx.width - 2),
|
|
488
|
+
).map((line) => ` ${line}`),
|
|
489
|
+
);
|
|
490
|
+
return [renderTableText({ columns, rows, locale }, ctx), metadata.join("\n")].join("\n");
|
|
475
491
|
}
|
|
476
492
|
|
|
477
|
-
|
|
493
|
+
function experimentDetailTable(item: ExperimentListItem, ctx: TextContext): string {
|
|
478
494
|
const locale = ctx.locale;
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
495
|
+
const columns: TableColumn[] = [
|
|
496
|
+
{ key: "status", header: localeText(locale, "experimentList.status") },
|
|
497
|
+
{ key: "entity", header: localeText(locale, "experimentList.evalAttempt") },
|
|
498
|
+
{ key: "result", header: localeText(locale, "experimentList.result") },
|
|
499
|
+
{ key: "duration", header: localeText(locale, "experimentList.duration"), align: "right" },
|
|
500
|
+
{ key: "cost", header: localeText(locale, "experimentList.cost"), align: "right" },
|
|
501
|
+
];
|
|
502
|
+
const rows: TableRow[] = item.evalRows.flatMap((row) => {
|
|
503
|
+
const parent: TableRow = {
|
|
504
|
+
key: row.evalId,
|
|
505
|
+
cells: {
|
|
506
|
+
status: `${verdictMark(row.verdict)} ${localeText(locale, `verdict.${row.verdict}`)}`,
|
|
507
|
+
entity: row.evalId,
|
|
508
|
+
result: "",
|
|
509
|
+
duration: "",
|
|
510
|
+
cost: "",
|
|
511
|
+
},
|
|
512
|
+
};
|
|
513
|
+
const attempts: TableRow[] = row.attempts.map((attempt, index) => ({
|
|
514
|
+
key: attempt.locator,
|
|
515
|
+
cells: {
|
|
516
|
+
status: ` ${verdictMark(attempt.verdict)}`,
|
|
517
|
+
entity: `${index === row.attempts.length - 1 ? "└─" : "├─"} ${attempt.locator}`,
|
|
518
|
+
result: attemptItemReason(attempt) ?? MISSING_MARK,
|
|
519
|
+
duration: attempt.verdict === "skipped" && attempt.durationMs === 0 ? null : formatDurationMs(attempt.durationMs),
|
|
520
|
+
cost: attempt.costUSD === undefined ? null : formatUSD(attempt.costUSD),
|
|
521
|
+
},
|
|
522
|
+
}));
|
|
523
|
+
return [parent, ...attempts];
|
|
491
524
|
});
|
|
492
|
-
|
|
525
|
+
const flags = item.flags && Object.keys(item.flags).length > 0
|
|
526
|
+
? `${localeText(locale, "experimentList.flags")} ${Object.entries(item.flags).map(([key, value]) => `${key}=${String(value)}`).join(" · ")}`
|
|
527
|
+
: undefined;
|
|
528
|
+
return [item.experimentId, flags, renderTableText({ columns, rows, locale }, ctx)].filter(Boolean).join("\n");
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
export function experimentListText(items: ExperimentListItem[], ctx: TextContext): string {
|
|
532
|
+
if (items.length === 0) return localeText(ctx.locale, "attemptList.empty");
|
|
533
|
+
return [experimentSummaryTable(items, ctx), ...items.map((item) => experimentDetailTable(item, ctx))].join("\n\n");
|
|
493
534
|
}
|
|
494
535
|
|
|
495
536
|
// ── EvalList ──
|
|
@@ -518,7 +559,7 @@ export function evalListText(items: EvalListItem[], ctx: TextContext): string {
|
|
|
518
559
|
|
|
519
560
|
// ── AttemptList ──
|
|
520
561
|
|
|
521
|
-
/** 一个 AttemptListItem 的完整 text 卡片:判定符 + locator + 身份 +
|
|
562
|
+
/** 一个 AttemptListItem 的完整 text 卡片:判定符 + locator + 身份 + 耗时/成本,
|
|
522
563
|
* 然后逐条断言(gate 与 soft 都列,与 web 面的 AttemptRow 同一份材料)。 */
|
|
523
564
|
function attemptListItemText(item: AttemptListItem, ctx: TextContext, locale: ReportLocale): string {
|
|
524
565
|
const head = [
|
|
@@ -527,7 +568,6 @@ function attemptListItemText(item: AttemptListItem, ctx: TextContext, locale: Re
|
|
|
527
568
|
item.experimentId,
|
|
528
569
|
formatDurationMs(item.durationMs),
|
|
529
570
|
...(item.costUSD !== undefined ? [formatUSD(item.costUSD)] : []),
|
|
530
|
-
...(capabilityBadge(item.capabilities) ? [capabilityBadge(item.capabilities)] : []),
|
|
531
571
|
].join(" · ");
|
|
532
572
|
const lines = [head];
|
|
533
573
|
if (item.error) {
|
package/src/report/types.ts
CHANGED
|
@@ -4,12 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
import type { AttemptHandle, SelectionWarning } from "../results/types.ts";
|
|
6
6
|
import type { AttemptLocator } from "../results/locator.ts";
|
|
7
|
-
import type { AttemptEvidenceCapabilities } from "../results/attempt-evidence.ts";
|
|
8
7
|
import type { AssertionResult, Verdict } from "../types.ts";
|
|
9
8
|
import type { LocalizedLabel, ReportLocale } from "./locale.ts";
|
|
10
9
|
|
|
11
10
|
export type { SelectionWarning };
|
|
12
|
-
export type { AttemptLocator
|
|
11
|
+
export type { AttemptLocator };
|
|
13
12
|
export type { LocalizedLabel, ReportLocale };
|
|
14
13
|
|
|
15
14
|
// ───────────────────────── 指标与聚合 ─────────────────────────
|
|
@@ -336,10 +335,8 @@ export interface DeltaData<K extends string = string> {
|
|
|
336
335
|
// `EvalListItem.attempts` 的元素,报告作者可以直接把这些嵌套数组喂给 `<AttemptList items={...} />`。
|
|
337
336
|
|
|
338
337
|
/**
|
|
339
|
-
* `AttemptList` 一项 = 一个 Attempt:身份、判定、断言、error
|
|
340
|
-
*
|
|
341
|
-
* 同一个类型,不重新发明四个布尔位的含义)。`ExperimentList` / `EvalList` 的下钻数组复用同一个
|
|
342
|
-
* 类型,不是各自的精简版。
|
|
338
|
+
* `AttemptList` 一项 = 一个 Attempt:身份、判定、断言、error、耗时、成本和 locator。
|
|
339
|
+
* `ExperimentList` / `EvalList` 的下钻数组复用同一个类型,不是各自的精简版。
|
|
343
340
|
*/
|
|
344
341
|
export interface AttemptListItem {
|
|
345
342
|
evalId: string;
|
|
@@ -352,15 +349,6 @@ export interface AttemptListItem {
|
|
|
352
349
|
durationMs: number;
|
|
353
350
|
costUSD?: number;
|
|
354
351
|
locator: AttemptLocator;
|
|
355
|
-
/**
|
|
356
|
-
* 证据能力标记:`eval`(运行时 Eval 源码已保存)、`execution`(执行事件非空)、
|
|
357
|
-
* `timing`(执行事件 + 这次运行接入过 OTel)、`diff`(工作区有文件改动)。逐位定义与
|
|
358
|
-
* `AttemptEvidence.capabilities` 完全一致——这里复用同一个类型,不重复声明四个布尔位的门槛。
|
|
359
|
-
* 计算函数从 `EvalResult` 上已有的 `hasEvents` / `hasSources` / `hasTrace` 摘要位与一次
|
|
360
|
-
* `attempt.diff()` 懒加载算出,不对每个 item 调用完整的 `loadAttemptEvidence`
|
|
361
|
-
* (那还会额外装配 Eval 源码标注与 ExecutionTree,这里只要四个布尔位)。
|
|
362
|
-
*/
|
|
363
|
-
capabilities: AttemptEvidenceCapabilities;
|
|
364
352
|
}
|
|
365
353
|
|
|
366
354
|
/**
|
|
@@ -418,7 +418,7 @@ describe("宿主接线 · show text 面与 view web 面反映同一批事实", (
|
|
|
418
418
|
expect(face).toContain("compare/bub");
|
|
419
419
|
expect(face).toContain("q2"); // 周一补齐的失败 eval,若宿主回退 results.latest() 就会消失
|
|
420
420
|
}
|
|
421
|
-
expect(text).toContain("1 passed
|
|
421
|
+
expect(text).toContain("1 passed / 1 failed");
|
|
422
422
|
expect(html).toContain("50%");
|
|
423
423
|
// 现刻水位覆盖齐全 → 两面都不出 partial-coverage(text: "verdicts cover"; html: data-kind)。
|
|
424
424
|
expect(hasPartialCoverageText(text)).toBe(false);
|
|
@@ -493,7 +493,7 @@ function hasPartialCoverageHtml(html: string): boolean {
|
|
|
493
493
|
return html.includes('data-kind="partial-coverage"');
|
|
494
494
|
}
|
|
495
495
|
|
|
496
|
-
describe("默认宿主事实对照 · show
|
|
496
|
+
describe("默认宿主事实对照 · show / view 渲染同一报告的两面", () => {
|
|
497
497
|
it("通过计数一致:同一 fixture 两面反映 3 过 1 败", async () => {
|
|
498
498
|
const root = await makeRoot();
|
|
499
499
|
await writeSnapshot(root, "2026-07-01T08-00-00-000Z", { experimentId: "compare/bub", startedAt: "2026-07-01T08:00:00.000Z" }, [
|
|
@@ -504,7 +504,7 @@ describe("默认宿主事实对照 · show 索引与 view 报告同口径", () =
|
|
|
504
504
|
]);
|
|
505
505
|
const text = await showText(root, []);
|
|
506
506
|
const html = await viewHtml(root);
|
|
507
|
-
expect(text).toContain("3 passed
|
|
507
|
+
expect(text).toContain("3 passed / 1 failed");
|
|
508
508
|
expect(html).toContain("75%");
|
|
509
509
|
for (const face of [text, html]) expect(face).toContain("compare/bub");
|
|
510
510
|
// 无残缺:两面都不出 partial-coverage,布尔一致(某面偷偷补/漏警告即失配)。
|
package/src/show/index.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// 宿主组合语义:docs/feature/reports/architecture.md「Selection 是计算入口」)。
|
|
3
3
|
//
|
|
4
4
|
// 位置参数 = eval id 前缀,或 `@<locator>`(精确指名单个 attempt,见 results/locator.ts):
|
|
5
|
-
// 裸跑 / 前缀
|
|
5
|
+
// 裸跑 / 多 eval 前缀 默认 ExperimentComparison 的 text 面(单 eval 前缀仍进入详情)
|
|
6
6
|
// 恰好一个 eval 单 eval 详情(attempt / 断言明细,宿主本体)
|
|
7
7
|
// @<locator> 精确 attempt:无证据 flag → 紧凑全景;带 flag → 对应证据切面
|
|
8
8
|
// --eval / --execution / --diff[=路径] 证据切面(宿主本体):出现即走证据室,不渲染报告槽
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// --run <目录> 结果根换成该目录;--experiment Selection 只留该实验
|
|
12
12
|
//
|
|
13
13
|
// 数据全部走 niceeval/results 的读取面(openResults + 合成 Selection + loadAttemptEvidence),
|
|
14
|
-
//
|
|
14
|
+
// 不自己爬目录;证据可用性只由 loadAttemptEvidence 在单 Attempt 页面计算。
|
|
15
15
|
|
|
16
16
|
import { existsSync } from "node:fs";
|
|
17
17
|
import { join, resolve } from "node:path";
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
MalformedLocatorError,
|
|
25
25
|
} from "../results/index.ts";
|
|
26
26
|
// report.ts / load.ts have no JSX of their own, but ReportDefinition / ReportLoadError must
|
|
27
|
-
// come from the SAME module instance
|
|
27
|
+
// come from the SAME module instance ExperimentComparison (built-ins, .tsx) is built
|
|
28
28
|
// against — `unique symbol` branding and `instanceof` are keyed by declaration site, so a raw
|
|
29
29
|
// src copy and the compiled dist copy of "the same" ReportDefinition are, to TypeScript and to
|
|
30
30
|
// `instanceof`, two different types. The package-owned report runtime ships as precompiled
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
// when niceeval is linked in — see tsconfig.report-build.json); show pulls all of it from
|
|
33
33
|
// there, not just the .tsx-touching pieces.
|
|
34
34
|
import { renderReportToText } from "../../dist/report/report.js";
|
|
35
|
+
import { ExperimentComparison } from "../../dist/report/built-ins/index.js";
|
|
35
36
|
import { ReportLoadError, loadReportFile } from "../../dist/report/load.js";
|
|
36
37
|
import { detectLocale, t } from "../i18n/index.ts";
|
|
37
38
|
import { foldEvalVerdict } from "../shared/verdict.ts";
|
|
@@ -43,7 +44,6 @@ import {
|
|
|
43
44
|
attemptIndexLine,
|
|
44
45
|
attemptOverviewText,
|
|
45
46
|
attemptsOfEval,
|
|
46
|
-
cheapCapabilities,
|
|
47
47
|
diffText,
|
|
48
48
|
evalDetailText,
|
|
49
49
|
evalHistoryText,
|
|
@@ -52,7 +52,6 @@ import {
|
|
|
52
52
|
experimentHistoryText,
|
|
53
53
|
pickDetailAttempt,
|
|
54
54
|
skippedRunsText,
|
|
55
|
-
showIndexText,
|
|
56
55
|
verdictReasonLine,
|
|
57
56
|
} from "./render.ts";
|
|
58
57
|
|
|
@@ -202,7 +201,7 @@ async function show(
|
|
|
202
201
|
// 证据切面是宿主本体:出现即走证据室,不渲染报告槽(与默认报告同规则)。
|
|
203
202
|
if (evidence) {
|
|
204
203
|
if (matchedEvalIds.length !== 1) {
|
|
205
|
-
// 撞多个 eval 时不止说「有几个」,直接给紧凑索引(locator +
|
|
204
|
+
// 撞多个 eval 时不止说「有几个」,直接给紧凑索引(locator + 失败原因)
|
|
206
205
|
// 让 agent 一步摘到 `@<locator>`,不必再跑一轮 `show <eval id>` 才知道选谁。
|
|
207
206
|
const index = matchedEvalIds
|
|
208
207
|
.map((evalId) => {
|
|
@@ -213,7 +212,6 @@ async function show(
|
|
|
213
212
|
evalId,
|
|
214
213
|
verdict,
|
|
215
214
|
locator: rep?.locator,
|
|
216
|
-
capabilities: rep ? cheapCapabilities(rep.result) : { eval: false, execution: false, timing: false, diff: false },
|
|
217
215
|
reason: rep ? verdictReasonLine(rep.result) : undefined,
|
|
218
216
|
});
|
|
219
217
|
})
|
|
@@ -277,16 +275,11 @@ async function show(
|
|
|
277
275
|
return;
|
|
278
276
|
}
|
|
279
277
|
|
|
280
|
-
// 裸 show
|
|
281
|
-
// --report
|
|
282
|
-
// locale = CLI 界面语言(NICEEVAL_LANG / LC_* / LANG 检测):报告 chrome 文案跟随
|
|
278
|
+
// 裸 show 与裸 view 选择同一个普通默认 definition,这里只是渲染 text 面。
|
|
279
|
+
// --report 替换同一个报告槽。locale = CLI 界面语言(NICEEVAL_LANG / LC_* / LANG 检测):报告 chrome 文案跟随
|
|
283
280
|
// 终端语言(docs/feature/reports/library.md「locale:渲染面的语言」);Locale 与 ReportLocale 同为
|
|
284
281
|
// "en" | "zh-CN",直接传递。
|
|
285
|
-
|
|
286
|
-
io.out(showIndexText(selection, io.width) + "\n");
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
const definition = await loadReportFile(cwd, flags.report);
|
|
282
|
+
const definition = flags.report === undefined ? ExperimentComparison : await loadReportFile(cwd, flags.report);
|
|
290
283
|
// attemptCommand 留给 renderReportToText 的默认值:AttemptLocator 已经是可直接 `niceeval show
|
|
291
284
|
// @<locator>` 的真实 CLI 语法,不需要再反查 eval id 拼一条近似命令(见 tree.ts 的默认实现)。
|
|
292
285
|
const text = await renderReportToText(definition, { selection, results }, { width: io.width, locale: detectLocale() });
|