niceeval 0.6.2 → 0.7.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/INDEX.md +23 -23
- package/README.zh.md +6 -6
- package/dist/agents/types.d.ts +2 -2
- package/dist/i18n/zh-CN.d.ts +3 -3
- package/dist/report/aggregate.d.ts +32 -26
- package/dist/report/aggregate.js +157 -76
- 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 +91 -164
- package/dist/report/components.js +377 -114
- package/dist/report/compute.d.ts +86 -73
- package/dist/report/compute.js +592 -432
- package/dist/report/flag.d.ts +28 -17
- package/dist/report/flag.js +86 -16
- package/dist/report/format.d.ts +11 -11
- package/dist/report/format.js +17 -15
- package/dist/report/index.d.ts +16 -17
- package/dist/report/index.js +20 -22
- package/dist/report/load.js +3 -2
- package/dist/report/locale.d.ts +49 -34
- package/dist/report/locale.js +106 -58
- package/dist/report/metrics.d.ts +10 -3
- package/dist/report/metrics.js +46 -12
- package/dist/report/primitives.d.ts +42 -15
- package/dist/report/primitives.js +135 -26
- package/dist/report/react/AttemptList.d.ts +10 -8
- package/dist/report/react/AttemptList.js +18 -10
- package/dist/report/react/DeltaTable.js +19 -18
- package/dist/report/react/EvalList.d.ts +3 -3
- package/dist/report/react/EvalList.js +0 -0
- package/dist/report/react/ExperimentComparison.d.ts +4 -2
- package/dist/report/react/ExperimentComparison.js +5 -4
- package/dist/report/react/ExperimentList.d.ts +3 -3
- package/dist/report/react/ExperimentList.js +16 -15
- 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 +54 -17
- 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/fixtures.d.ts +5 -9
- package/dist/report/react/fixtures.js +105 -149
- package/dist/report/react/index.d.ts +15 -5
- package/dist/report/react/index.js +18 -7
- package/dist/report/report.d.ts +137 -20
- package/dist/report/report.js +261 -34
- package/dist/report/text/faces.d.ts +17 -19
- package/dist/report/text/faces.js +225 -157
- package/dist/report/text/plot.js +1 -1
- package/dist/report/text/table.js +2 -2
- package/dist/report/tree.d.ts +90 -40
- package/dist/report/tree.js +252 -94
- package/dist/report/types.d.ts +245 -300
- 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 +38 -14
- package/dist/shared/aggregate.d.ts +3 -2
- package/dist/shared/aggregate.js +5 -4
- 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 +11 -11
- 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 +5 -5
- package/docs-site/zh/{guides → explanation}/runner.mdx +1 -1
- package/docs-site/zh/{concepts → explanation}/tier.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/agent-feedback-loop.mdx +7 -7
- package/docs-site/zh/{guides → how-to}/authoring.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/connect-otel.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/connect-your-agent.mdx +18 -18
- package/docs-site/zh/{guides → how-to}/custom-reports.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/experiments.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/publish-report.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-agent.mdx +2 -2
- package/docs-site/zh/{guides → how-to}/sandbox-providers.mdx +1 -1
- package/docs-site/zh/{guides → how-to}/viewing-results.mdx +6 -6
- package/docs-site/zh/{guides → how-to}/write-experiment.mdx +3 -3
- package/docs-site/zh/{guides → how-to}/write-send.mdx +13 -13
- package/docs-site/zh/index.mdx +23 -25
- package/docs-site/zh/introduction.mdx +8 -8
- package/docs-site/zh/reference/builtin-agents.mdx +5 -5
- package/docs-site/zh/reference/capabilities.mdx +6 -6
- package/docs-site/zh/reference/cli.mdx +9 -7
- package/docs-site/zh/reference/define-agent.mdx +1 -1
- package/docs-site/zh/reference/events.mdx +3 -3
- package/docs-site/zh/{guides → reference}/official-adapters.mdx +7 -7
- package/docs-site/zh/{guides → reference}/report-components.mdx +5 -5
- package/docs-site/zh/{guides → reference}/results-data.mdx +5 -5
- package/docs-site/zh/{guides → troubleshooting}/debug-sandbox.mdx +2 -2
- package/docs-site/zh/{guides → troubleshooting}/debugging.mdx +4 -2
- package/docs-site/zh/{quickstart.mdx → tutorials/quickstart.mdx} +5 -17
- package/package.json +6 -2
- package/src/agents/index.ts +2 -2
- package/src/agents/openai-compat.ts +1 -1
- package/src/agents/streaming.ts +2 -2
- package/src/agents/types.ts +3 -3
- package/src/cli.ts +42 -23
- package/src/context/context.ts +1 -1
- package/src/context/session.test.ts +1 -1
- package/src/context/session.ts +1 -1
- package/src/i18n/en.ts +18 -16
- package/src/i18n/zh-CN.ts +16 -15
- package/src/report/aggregate.ts +175 -87
- package/src/report/built-in/index.tsx +9 -0
- package/src/report/components.tsx +625 -285
- package/src/report/compute.ts +717 -515
- package/src/report/dual-render.test.tsx +738 -1148
- package/src/report/flag.ts +97 -33
- package/src/report/format.ts +18 -22
- package/src/report/index.ts +113 -58
- package/src/report/load.ts +3 -2
- package/src/report/locale.ts +120 -69
- package/src/report/metrics.ts +42 -12
- package/src/report/primitives.tsx +190 -45
- package/src/report/react/AttemptList.tsx +32 -20
- package/src/report/react/DeltaTable.tsx +63 -45
- package/src/report/react/EvalList.tsx +0 -0
- package/src/report/react/ExperimentComparison.tsx +12 -7
- package/src/report/react/ExperimentList.tsx +38 -26
- 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 +74 -20
- 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/enhance.js +57 -5
- package/src/report/react/fixtures.ts +109 -156
- package/src/report/react/index.tsx +24 -39
- package/src/report/react/render.test.tsx +139 -104
- package/src/report/react/styles.css +181 -91
- package/src/report/report.test.ts +761 -1031
- package/src/report/report.ts +425 -47
- package/src/report/text/faces.ts +257 -164
- package/src/report/text/plot.ts +1 -1
- package/src/report/text/table.ts +2 -2
- package/src/report/tree.ts +362 -104
- package/src/report/types.ts +257 -287
- package/src/report/web.ts +63 -20
- package/src/results/attempt-evidence.test.ts +4 -4
- package/src/results/attempt-evidence.ts +5 -5
- package/src/results/copy.ts +6 -6
- package/src/results/host-equivalence.test.ts +26 -14
- package/src/results/index.ts +10 -4
- package/src/results/open.ts +8 -4
- package/src/results/results.test.ts +4 -3
- package/src/results/select.ts +104 -34
- package/src/results/types.ts +36 -14
- package/src/runner/feedback/human.test.ts +1 -1
- package/src/runner/run.ts +1 -1
- package/src/sandbox/cli-commands.ts +2 -2
- package/src/scoring/judge.test.ts +1 -1
- package/src/shared/aggregate.ts +5 -4
- package/src/show/compose.ts +50 -67
- package/src/show/index.ts +107 -56
- package/src/show/render.ts +43 -27
- package/src/show/report-host.test.ts +188 -0
- package/src/show/report-host.ts +375 -0
- package/src/show/show.test.ts +86 -36
- package/src/view/app/App.test.tsx +69 -0
- package/src/view/app/App.tsx +144 -48
- package/src/view/app/components/AttemptModal.tsx +324 -63
- package/src/view/app/components/CodeView.tsx +10 -4
- package/src/view/app/i18n.ts +31 -17
- package/src/view/app/lib/artifact-url.ts +14 -3
- 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 +21 -1
- package/src/view/client-dist/app.css +1 -1
- package/src/view/client-dist/app.js +6 -6
- package/src/view/data.test.ts +9 -3
- package/src/view/data.ts +145 -49
- package/src/view/index.ts +48 -44
- package/src/view/server.ts +35 -15
- package/src/view/shared/types.ts +34 -5
- package/src/view/styles.css +224 -0
- package/src/view/view-report.test.ts +161 -57
- package/dist/report/built-ins/experiment-comparison.d.ts +0 -39
- package/dist/report/built-ins/experiment-comparison.js +0 -119
- package/dist/report/built-ins/index.d.ts +0 -2
- 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/src/report/built-in-user-parity.test.tsx +0 -597
- package/src/report/built-ins/experiment-comparison.tsx +0 -179
- package/src/report/built-ins/index.ts +0 -7
- package/src/report/react/GroupSummary.tsx +0 -66
- package/src/report/react/RunOverview.tsx +0 -109
- /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}/ci-integration.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/dataset-fanout.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/fixtures.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/reporters.mdx +0 -0
- /package/docs-site/zh/{guides → how-to}/scoring-guide.mdx +0 -0
package/src/report/web.ts
CHANGED
|
@@ -6,12 +6,20 @@ import * as React from "react";
|
|
|
6
6
|
import type { ReactNode } from "react";
|
|
7
7
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
8
8
|
import type { AttemptLocator } from "../results/locator.ts";
|
|
9
|
-
import type {
|
|
10
|
-
import {
|
|
9
|
+
import type { Scope } from "../results/types.ts";
|
|
10
|
+
import {
|
|
11
|
+
resolveReportTree,
|
|
12
|
+
runWithWebContext,
|
|
13
|
+
validateReportTree,
|
|
14
|
+
ResolveMemo,
|
|
15
|
+
type WebContext,
|
|
16
|
+
} from "./tree.ts";
|
|
11
17
|
import { DEFAULT_REPORT_LOCALE, type ReportLocale } from "./locale.ts";
|
|
12
|
-
import
|
|
18
|
+
import { buildReportMeta, pickReportPage, type ReportDefinition, type ReportHostContext } from "./report.ts";
|
|
13
19
|
|
|
14
20
|
export interface StaticHtmlOptions {
|
|
21
|
+
/** 渲染哪一页;缺省第一页。未命中抛 ReportPageNotFoundError。 */
|
|
22
|
+
pageId?: string;
|
|
15
23
|
/** 证据室深链;缺省用 view 的 attempt 路由 `#/attempt/@<locator>`(单段、不透明)。 */
|
|
16
24
|
attemptHref?: (locator: AttemptLocator) => string;
|
|
17
25
|
/** 官方组件 chrome 文案的 locale;默认 "en"。 */
|
|
@@ -19,12 +27,12 @@ export interface StaticHtmlOptions {
|
|
|
19
27
|
}
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
|
-
* 挑选警告的 HTML
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* message
|
|
30
|
+
* 挑选警告的 HTML 形态:宿主级前置块(`.nre nre-report-warnings` 外壳内一个
|
|
31
|
+
* `ul.nre-warnings` + `li.nre-warning[data-kind]`,复用 styles.css 已有样式)。
|
|
32
|
+
* 带 `command` 的警告把命令渲染为可复制块(`.nre-warning-command`);无 command 的
|
|
33
|
+
* 只显示 message,不硬造动作。经 renderToStaticMarkup 走 React,文本自动转义。
|
|
26
34
|
*/
|
|
27
|
-
function
|
|
35
|
+
function renderScopeWarningsHtml(scope: Scope): string {
|
|
28
36
|
return renderToStaticMarkup(
|
|
29
37
|
React.createElement(
|
|
30
38
|
"div",
|
|
@@ -32,8 +40,15 @@ function renderSelectionWarningsHtml(warnings: SelectionWarning[]): string {
|
|
|
32
40
|
React.createElement(
|
|
33
41
|
"ul",
|
|
34
42
|
{ className: "nre-warnings" },
|
|
35
|
-
warnings.map((w, i) =>
|
|
36
|
-
React.createElement(
|
|
43
|
+
scope.warnings.map((w, i) =>
|
|
44
|
+
React.createElement(
|
|
45
|
+
"li",
|
|
46
|
+
{ key: i, className: "nre-warning", "data-kind": w.kind },
|
|
47
|
+
w.message,
|
|
48
|
+
"command" in w && w.command
|
|
49
|
+
? React.createElement("code", { className: "nre-warning-command", "data-nre-copy": w.command }, w.command)
|
|
50
|
+
: null,
|
|
51
|
+
),
|
|
37
52
|
),
|
|
38
53
|
),
|
|
39
54
|
),
|
|
@@ -41,27 +56,55 @@ function renderSelectionWarningsHtml(warnings: SelectionWarning[]): string {
|
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
/**
|
|
44
|
-
*
|
|
45
|
-
* web 面;
|
|
46
|
-
*
|
|
59
|
+
* web 宿主的装载语义:选页 → resolve(组合展开 + spec 取数,唯一的 await 边界)→
|
|
60
|
+
* 树校验(与 text 宿主同一遍)→ 静态渲染 web 面;Scope 有挑选警告时在报告顶部前置
|
|
61
|
+
* 一块警告 HTML(宿主是 warning 的唯一呈现者,组件数据不复制 warning)。
|
|
47
62
|
*/
|
|
48
63
|
export async function renderReportToStaticHtml(
|
|
49
64
|
definition: ReportDefinition,
|
|
50
|
-
ctx:
|
|
65
|
+
ctx: ReportHostContext,
|
|
51
66
|
options?: StaticHtmlOptions,
|
|
52
67
|
): Promise<string> {
|
|
53
|
-
const
|
|
54
|
-
const
|
|
68
|
+
const page = pickReportPage(definition, options?.pageId);
|
|
69
|
+
const meta = buildReportMeta(definition, ctx.scope, page.id);
|
|
70
|
+
const resolved = await resolveReportTree(page.content, {
|
|
71
|
+
scope: ctx.scope,
|
|
72
|
+
results: ctx.results,
|
|
73
|
+
report: meta,
|
|
74
|
+
memo: new ResolveMemo(),
|
|
75
|
+
});
|
|
55
76
|
validateReportTree(resolved);
|
|
56
77
|
const webCtx: WebContext = {
|
|
57
78
|
attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
|
|
58
79
|
locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
|
|
59
80
|
};
|
|
60
81
|
const body = runWithWebContext(webCtx, () => renderToStaticMarkup(resolved as ReactNode));
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
82
|
+
const warnings = ctx.scope.warnings.length > 0 ? renderScopeWarningsHtml(ctx.scope) : "";
|
|
83
|
+
return warnings + body;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 渲染一页报告树的 web 面(宿主逐页调用;页选择归宿主):resolve → validate → 静态渲染。
|
|
88
|
+
* Scope 有挑选警告时在页顶前置警告块(带 command 的警告渲染为可复制命令)——宿主是
|
|
89
|
+
* warning 的唯一呈现者,组件数据不复制 warning。ctx.report 是宿主规范化后的声明。
|
|
90
|
+
*/
|
|
91
|
+
export async function renderReportTreeToStaticHtml(
|
|
92
|
+
tree: import("./tree.ts").ReportNode,
|
|
93
|
+
ctx: { scope: Scope; results: import("../results/types.ts").Results; report: import("./report.ts").ReportMeta },
|
|
94
|
+
options?: { attemptHref?: (locator: AttemptLocator) => string; locale?: ReportLocale },
|
|
95
|
+
): Promise<string> {
|
|
96
|
+
const resolved = await resolveReportTree(tree, {
|
|
97
|
+
scope: ctx.scope,
|
|
98
|
+
results: ctx.results,
|
|
99
|
+
report: ctx.report,
|
|
100
|
+
memo: new ResolveMemo(),
|
|
64
101
|
});
|
|
65
|
-
|
|
102
|
+
validateReportTree(resolved);
|
|
103
|
+
const webCtx: WebContext = {
|
|
104
|
+
attemptHref: options?.attemptHref ?? ((locator) => `#/attempt/${locator}`),
|
|
105
|
+
locale: options?.locale ?? DEFAULT_REPORT_LOCALE,
|
|
106
|
+
};
|
|
107
|
+
const body = runWithWebContext(webCtx, () => renderToStaticMarkup(resolved as ReactNode));
|
|
108
|
+
const warnings = ctx.scope.warnings.length > 0 ? renderScopeWarningsHtml(ctx.scope) : "";
|
|
66
109
|
return warnings + body;
|
|
67
110
|
}
|
|
@@ -70,7 +70,7 @@ async function seedAttempt(
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
describe("loadAttemptEvidence", () => {
|
|
73
|
-
it("四个 capability 全部具备:
|
|
73
|
+
it("四个 capability 全部具备:source / execution / timing / diff", async () => {
|
|
74
74
|
const root = await makeRoot();
|
|
75
75
|
const attempt = await seedAttempt(
|
|
76
76
|
root,
|
|
@@ -90,10 +90,10 @@ describe("loadAttemptEvidence", () => {
|
|
|
90
90
|
expect((actionNode as { span?: TraceSpan }).span).toBeDefined();
|
|
91
91
|
expect(evidence.diff?.windows).toEqual(NONEMPTY_DIFF);
|
|
92
92
|
|
|
93
|
-
expect(evidence.capabilities).toEqual({
|
|
93
|
+
expect(evidence.capabilities).toEqual({ source: true, execution: true, timing: true, diff: true });
|
|
94
94
|
});
|
|
95
95
|
|
|
96
|
-
it("四个 capability 全部缺失:没有
|
|
96
|
+
it("四个 capability 全部缺失:没有 sources / events / trace / diff,不崩溃", async () => {
|
|
97
97
|
const root = await makeRoot();
|
|
98
98
|
const attempt = await seedAttempt(root, { id: "weather/queens" });
|
|
99
99
|
|
|
@@ -102,7 +102,7 @@ describe("loadAttemptEvidence", () => {
|
|
|
102
102
|
expect(evidence.evalSource).toBeNull();
|
|
103
103
|
expect(evidence.execution).toBeNull();
|
|
104
104
|
expect(evidence.diff).toBeNull();
|
|
105
|
-
expect(evidence.capabilities).toEqual({
|
|
105
|
+
expect(evidence.capabilities).toEqual({ source: false, execution: false, timing: false, diff: false });
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
it("有 events 没有 phases(旧 runner 产出):execution 为真、timing 为假", async () => {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// "有什么可看"这件事产生同一个答案(没有),只是原因不同,原因留给 unavailable 文案区分,
|
|
13
13
|
// 不留给 capability 位区分(capability 位只回答"要不要显示这个证据切面")。
|
|
14
14
|
//
|
|
15
|
-
//
|
|
15
|
+
// source —— evalSource 非空 AND (至少有一行源码 或 至少有一条断言);理论上
|
|
16
16
|
// buildAnnotatedEvalSource 对空文件也会产出一行 ""(split("\n") 的自然结果),
|
|
17
17
|
// 所以这条件实践中约等于"evalSource 非空",按文档口径原样写出两个子句,
|
|
18
18
|
// 不依赖这个实现细节。
|
|
@@ -59,8 +59,8 @@ export interface EvidencePaths {
|
|
|
59
59
|
* 对应的证据切面,不自己重新判断"这个 attempt 有没有 X"。
|
|
60
60
|
*/
|
|
61
61
|
export interface AttemptEvidenceCapabilities {
|
|
62
|
-
/** evalSource 非空且有内容(源码行或断言二选一非空)
|
|
63
|
-
|
|
62
|
+
/** evalSource 非空且有内容(源码行或断言二选一非空);对应 CLI 的 `--source` 证据切面。 */
|
|
63
|
+
source: boolean;
|
|
64
64
|
/** events 非空数组——有骨架可看。 */
|
|
65
65
|
execution: boolean;
|
|
66
66
|
/** result.json 带 phases(阶段计时)——`--timing` 时间树可渲染。 */
|
|
@@ -79,7 +79,7 @@ export interface AttemptEvidence {
|
|
|
79
79
|
* 的调用方直接读结构化字段(如按 experimentId 分组、按时间排序)。 */
|
|
80
80
|
identity: AttemptIdentity;
|
|
81
81
|
result: EvalResult;
|
|
82
|
-
/** 运行时保存的 Eval 源码标注(`--
|
|
82
|
+
/** 运行时保存的 Eval 源码标注(`--source` 切面);没有 sources() 就是 null,不伪造空文档。 */
|
|
83
83
|
evalSource: AnnotatedEvalSource | null;
|
|
84
84
|
/** 标准事件流 + OTel enrichment(`--execution` 切面);没有非空 events 就是 null,
|
|
85
85
|
* 不产出一棵只剩 telemetry-only 节点、没有真实骨架的误导性树(见头注 execution 门槛)。 */
|
|
@@ -144,7 +144,7 @@ export async function loadAttemptEvidence(attempt: AttemptHandle): Promise<Attem
|
|
|
144
144
|
trace,
|
|
145
145
|
artifactPaths: { dir: attemptArtifactDir(attempt) },
|
|
146
146
|
capabilities: {
|
|
147
|
-
|
|
147
|
+
source: evalCapable,
|
|
148
148
|
execution: execution !== null,
|
|
149
149
|
timing: (attempt.result.phases?.length ?? 0) > 0,
|
|
150
150
|
diff: diffCapable,
|
package/src/results/copy.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
redactSpans,
|
|
25
25
|
type Redactor,
|
|
26
26
|
} from "./publish.ts";
|
|
27
|
-
import type { ArtifactKind, AttemptHandle,
|
|
27
|
+
import type { ArtifactKind, AttemptHandle, Scope, Snapshot, SnapshotMeta } from "./types.ts";
|
|
28
28
|
|
|
29
29
|
/** 缺省携带的 artifact:events / trace / o11y / agentSetup / sources;diff 不截断、可达百 MB,缺省不带。 */
|
|
30
30
|
const DEFAULT_PUBLISH_ARTIFACTS: ArtifactKind[] = ["events", "trace", "o11y", "agentSetup", "sources"];
|
|
@@ -50,15 +50,15 @@ export interface CopySnapshotsResult {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* 把选中快照复制成 `destDir` 下的一个标准结果根目录(`<experiment-dir>/<源快照目录名>/`,
|
|
53
|
-
* 快照目录名原样保留,身份不变)。输入收
|
|
53
|
+
* 快照目录名原样保留,身份不变)。输入收 Scope 或手工挑的 Snapshot[]
|
|
54
54
|
* (与 Reports 计算函数同一输入约定)。目标目录非空即报错,不静默覆盖、不合并。
|
|
55
55
|
*/
|
|
56
56
|
export async function copySnapshots(
|
|
57
|
-
|
|
57
|
+
scope: Scope | readonly Snapshot[],
|
|
58
58
|
destDir: string,
|
|
59
59
|
opts: CopySnapshotsOptions,
|
|
60
60
|
): Promise<CopySnapshotsResult> {
|
|
61
|
-
const selected = Array.isArray(
|
|
61
|
+
const selected = Array.isArray(scope) ? (scope as readonly Snapshot[]) : (scope as Scope).snapshots;
|
|
62
62
|
if (selected.length === 0) {
|
|
63
63
|
throw new Error(
|
|
64
64
|
"copySnapshots got no snapshots to copy. Check the experiments filter, or pass snapshots from openResults().latest().",
|
|
@@ -92,7 +92,7 @@ export async function copySnapshots(
|
|
|
92
92
|
continue;
|
|
93
93
|
}
|
|
94
94
|
warnings.push(
|
|
95
|
-
`warning: multiple snapshots selected for experiment "${snapshot.experimentId}"; kept the newest one, dropped the rest. Dedupe with
|
|
95
|
+
`warning: multiple snapshots selected for experiment "${snapshot.experimentId}"; kept the newest one, dropped the rest. Dedupe with Scope.filter() or pick a single snapshot per experiment before copySnapshots to avoid this.`,
|
|
96
96
|
);
|
|
97
97
|
if (isNewerSnapshot(snapshot, existing)) byExperiment.set(snapshot.experimentId, snapshot);
|
|
98
98
|
}
|
|
@@ -101,7 +101,7 @@ export async function copySnapshots(
|
|
|
101
101
|
// 超过 PUBLISH_FILE_MAX_BYTES 就整体失败,不留半成品目标目录。
|
|
102
102
|
const planned: PlannedFile[] = [];
|
|
103
103
|
for (const snapshot of byExperiment.values()) {
|
|
104
|
-
planned.push(...(await planOneSnapshot(snapshot, selected, dest, kinds, redactor)));
|
|
104
|
+
planned.push(...(await planOneSnapshot(snapshot, [...selected], dest, kinds, redactor)));
|
|
105
105
|
}
|
|
106
106
|
const oversized = planned.filter((f) => f.bytes.byteLength > PUBLISH_FILE_MAX_BYTES);
|
|
107
107
|
if (oversized.length > 0) {
|
|
@@ -24,7 +24,7 @@ import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
|
24
24
|
import { tmpdir } from "node:os";
|
|
25
25
|
import { join } from "node:path";
|
|
26
26
|
import { openResults } from "./index.ts";
|
|
27
|
-
import type {
|
|
27
|
+
import type { Scope, ScopeWarning } from "./index.ts";
|
|
28
28
|
import { selectCurrentResults, type ResultScope } from "./select.ts";
|
|
29
29
|
import { RESULTS_FORMAT, RESULTS_SCHEMA_VERSION, type EvalResult, type Verdict } from "../types.ts";
|
|
30
30
|
import { runShow, type ShowFlags } from "../show/index.ts";
|
|
@@ -132,7 +132,7 @@ interface NormSelection {
|
|
|
132
132
|
experiments: NormExperiment[];
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
function normalizeWarning(w:
|
|
135
|
+
function normalizeWarning(w: ScopeWarning): NormWarning {
|
|
136
136
|
switch (w.kind) {
|
|
137
137
|
case "partial-coverage":
|
|
138
138
|
return { kind: w.kind, experimentId: w.experimentId, covered: w.covered, total: w.total };
|
|
@@ -144,7 +144,7 @@ function normalizeWarning(w: SelectionWarning): NormWarning {
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
-
function normalizeSelection(selection:
|
|
147
|
+
function normalizeSelection(selection: Scope): NormSelection {
|
|
148
148
|
return {
|
|
149
149
|
warnings: selection.warnings.map(normalizeWarning),
|
|
150
150
|
experiments: selection.snapshots.map((snapshot) => ({
|
|
@@ -167,7 +167,7 @@ function normalizeSelection(selection: Selection): NormSelection {
|
|
|
167
167
|
async function showText(root: string, patterns: string[], flags: ShowFlags = {}): Promise<string> {
|
|
168
168
|
let out = "";
|
|
169
169
|
let err = "";
|
|
170
|
-
const code = await runShow(root, patterns, {
|
|
170
|
+
const code = await runShow(root, patterns, { results: root, ...flags }, {
|
|
171
171
|
out: (s) => (out += s),
|
|
172
172
|
err: (s) => (err += s),
|
|
173
173
|
width: 120,
|
|
@@ -179,8 +179,8 @@ async function showText(root: string, patterns: string[], flags: ShowFlags = {})
|
|
|
179
179
|
|
|
180
180
|
/** view 的 web 面:loadViewScan → 报告槽 HTML(en)。 */
|
|
181
181
|
async function viewHtml(root: string, opts: ViewScanOptions = {}): Promise<string> {
|
|
182
|
-
const {
|
|
183
|
-
return
|
|
182
|
+
const { reportPages } = await loadViewScan(root, opts);
|
|
183
|
+
return reportPages.map((page) => page.html.en).join("\n");
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
/** 两个宿主构造给选择器的 scope 完全同形:验证读源无误,避免"我以为它们一样"。 */
|
|
@@ -453,20 +453,32 @@ describe("宿主接线 · show text 面与 view web 面反映同一批事实", (
|
|
|
453
453
|
await writeFile(
|
|
454
454
|
reportPath,
|
|
455
455
|
[
|
|
456
|
+
"// 手搭新 defineReport 产物(kind + brand + 规范化页列表);组合组件从 ctx.scope 读注入范围。",
|
|
456
457
|
'const FACES = Symbol.for("niceeval.report.faces");',
|
|
457
|
-
|
|
458
|
-
"
|
|
458
|
+
'const COMPOSE = Symbol.for("niceeval.report.compose");',
|
|
459
|
+
"const Leaf = (props) => Leaf[FACES].web(props);",
|
|
460
|
+
"Leaf[FACES] = {",
|
|
459
461
|
' web: (props) => "EVALS[" + props.ids + "]",',
|
|
460
462
|
' text: (props) => "EVALS[" + props.ids + "]",',
|
|
461
463
|
"};",
|
|
464
|
+
"const Echo = () => { throw new Error(\"compose only\"); };",
|
|
465
|
+
"Echo[COMPOSE] = (_props, ctx) => ({",
|
|
466
|
+
' $$typeof: Symbol.for("react.transitional.element"),',
|
|
467
|
+
" type: Leaf,",
|
|
468
|
+
' props: { ids: ctx.scope.snapshots.flatMap((s) => s.evals.map((e) => e.id)).sort().join(",") },',
|
|
469
|
+
" key: null,",
|
|
470
|
+
"});",
|
|
462
471
|
"export default {",
|
|
472
|
+
' kind: "report",',
|
|
463
473
|
' [Symbol.for("niceeval.report.definition")]: true,',
|
|
464
|
-
"
|
|
465
|
-
|
|
466
|
-
"
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
"
|
|
474
|
+
" links: [],",
|
|
475
|
+
" scripts: [],",
|
|
476
|
+
" styles: [],",
|
|
477
|
+
" pages: [{",
|
|
478
|
+
' id: "report",',
|
|
479
|
+
' title: "Report",',
|
|
480
|
+
' content: { $$typeof: Symbol.for("react.transitional.element"), type: Echo, props: {}, key: null },',
|
|
481
|
+
" }],",
|
|
470
482
|
"};",
|
|
471
483
|
"",
|
|
472
484
|
].join("\n"),
|
package/src/results/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// niceeval/results —— 实验结果数据的读写库(定稿见 docs/feature/results/library.md、docs/feature/results/architecture.md)。
|
|
2
2
|
//
|
|
3
|
-
// 读:openResults(实验 → 快照 → eval → attempt 分层、skipped、latest()
|
|
3
|
+
// 读:openResults(实验 → 快照 → eval → attempt 分层、skipped、latest() / current() Scope);
|
|
4
4
|
// 写:createResultsWriter(快照声明 + attempt 增量落盘 + finish 补 completedAt);
|
|
5
5
|
// 发布:copySnapshots(格式感知复制 + knownEvalIds 补记);
|
|
6
6
|
// 身份:dedupeAttempts(跨快照聚合前按 (experimentId, evalId, attempt, startedAt) 去重)、isNewerSnapshot。
|
|
@@ -44,7 +44,13 @@ export {
|
|
|
44
44
|
} from "./attempt-evidence.ts";
|
|
45
45
|
export { hashEvalSource, normalizeEvalSource } from "./source-hash.ts";
|
|
46
46
|
export { groupIncompatibleVersionSkips, type SkippedVersionGroup } from "./skipped-notice.ts";
|
|
47
|
-
export {
|
|
47
|
+
export {
|
|
48
|
+
comparabilityConfigOf,
|
|
49
|
+
dedupeAttempts,
|
|
50
|
+
deepEqualJson,
|
|
51
|
+
isNewerSnapshot,
|
|
52
|
+
type ComparabilityConfig,
|
|
53
|
+
} from "./select.ts";
|
|
48
54
|
export { copySnapshots, type CopySnapshotsOptions, type CopySnapshotsResult } from "./copy.ts";
|
|
49
55
|
export {
|
|
50
56
|
createResultsWriter,
|
|
@@ -73,8 +79,8 @@ export {
|
|
|
73
79
|
type Eval,
|
|
74
80
|
type Experiment,
|
|
75
81
|
type Results,
|
|
76
|
-
type
|
|
77
|
-
type
|
|
82
|
+
type Scope,
|
|
83
|
+
type ScopeWarning,
|
|
78
84
|
type SkippedDir,
|
|
79
85
|
type Snapshot,
|
|
80
86
|
type SnapshotMeta,
|
package/src/results/open.ts
CHANGED
|
@@ -13,7 +13,7 @@ import type { O11ySummary, StreamEvent, TraceSpan } from "../types.ts";
|
|
|
13
13
|
import type { AgentSetupManifest, DiffData, SourceArtifact } from "../types.ts";
|
|
14
14
|
import { deriveDiffData } from "../scoring/diff.ts";
|
|
15
15
|
import { RESULT_FILE, SNAPSHOT_FILE, artifactFileOf, classifySnapshot } from "./format.ts";
|
|
16
|
-
import { isNewerSnapshot, selectLatest } from "./select.ts";
|
|
16
|
+
import { isNewerSnapshot, selectCurrentResults, selectLatest } from "./select.ts";
|
|
17
17
|
import {
|
|
18
18
|
encodeAttemptLocator,
|
|
19
19
|
resolveAttemptLocator,
|
|
@@ -27,7 +27,7 @@ import type {
|
|
|
27
27
|
Eval,
|
|
28
28
|
Experiment,
|
|
29
29
|
Results,
|
|
30
|
-
|
|
30
|
+
Scope,
|
|
31
31
|
SkippedDir,
|
|
32
32
|
Snapshot,
|
|
33
33
|
SnapshotMeta,
|
|
@@ -171,13 +171,17 @@ export async function openResults(dir: string): Promise<Results> {
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
function makeResults(experiments: Experiment[], skipped: SkippedDir[]): Results {
|
|
174
|
-
|
|
174
|
+
const results: Results = {
|
|
175
175
|
experiments,
|
|
176
176
|
skipped,
|
|
177
|
-
latest(opts?: { experiments?: string | string[] }):
|
|
177
|
+
latest(opts?: { experiments?: string | string[] }): Scope {
|
|
178
178
|
return selectLatest(experiments, opts);
|
|
179
179
|
},
|
|
180
|
+
current(opts?: { experiments?: string | string[] }): Scope {
|
|
181
|
+
return selectCurrentResults(results, { experiment: opts?.experiments });
|
|
182
|
+
},
|
|
180
183
|
};
|
|
184
|
+
return results;
|
|
181
185
|
}
|
|
182
186
|
|
|
183
187
|
// ───────────────────────── 目录扫描 ─────────────────────────
|
|
@@ -343,7 +343,7 @@ describe("results.latest() · Selection", () => {
|
|
|
343
343
|
const stale = latest.warnings.filter((w) => w.kind === "stale-snapshot");
|
|
344
344
|
expect(stale).toHaveLength(1);
|
|
345
345
|
expect(stale[0]).toMatchObject({ experimentId: "mid/a", startedAt: "2026-07-01T08:00:00.000Z", latestStartedAt: "2026-07-05T08:00:00.000Z" });
|
|
346
|
-
expect(stale[0].message).toContain("predates the latest run in this
|
|
346
|
+
expect(stale[0].message).toContain("predates the latest run in this scope by 4 days");
|
|
347
347
|
});
|
|
348
348
|
|
|
349
349
|
it("unfinished-snapshot:选中快照缺 completedAt", async () => {
|
|
@@ -976,8 +976,9 @@ describe("AttemptLocator · 落盘 / 读取 / 携带 / 撞车", () => {
|
|
|
976
976
|
const handMadeResults: Results = {
|
|
977
977
|
experiments: [{ id: "e", snapshots: [snapshot], latest: snapshot, evalIds: ["q1"] }],
|
|
978
978
|
skipped: [],
|
|
979
|
-
// filter() 本测试不调用,用不到,给个占位实现即可满足
|
|
980
|
-
latest: () => ({ snapshots: [snapshot], warnings: [], filter: () => { throw new Error("not implemented"); } }),
|
|
979
|
+
// filter() 本测试不调用,用不到,给个占位实现即可满足 Scope 接口。
|
|
980
|
+
latest: () => ({ mode: "latest-snapshots" as const, snapshots: [snapshot], attempts: snapshot.attempts, warnings: [], filter: () => { throw new Error("not implemented"); } }),
|
|
981
|
+
current: () => { throw new Error("not implemented"); },
|
|
981
982
|
};
|
|
982
983
|
// 这份 locator 语法合法、甚至真的对应 handMadeResults 里那个 attempt 的身份,
|
|
983
984
|
// 但 handMadeResults 没经过 openResults(),locatorIndexByResults 里查不到它 —— 空索引,not-found。
|