niceeval 0.10.3-canary.7 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents/types.d.ts +5 -4
- package/dist/context/turn-errors.d.ts +27 -23
- package/dist/i18n/en.d.ts +14 -0
- package/dist/i18n/en.js +15 -1
- package/dist/i18n/zh-CN.d.ts +15 -1
- package/dist/i18n/zh-CN.js +15 -1
- package/dist/o11y/derive.js +28 -24
- package/dist/o11y/types.d.ts +8 -5
- package/dist/report/components/attempt-detail/UsageTable.js +4 -7
- package/dist/report/components/attempt-detail/compute.d.ts +2 -2
- package/dist/report/components/attempt-detail/compute.js +2 -6
- package/dist/report/components/attempt-detail/faces.js +7 -7
- package/dist/report/components/attempt-detail/index.js +0 -3
- package/dist/report/components/entity-lists/EvalList.js +0 -0
- package/dist/report/components/metric-views/compute.js +1 -1
- package/dist/report/model/types.d.ts +3 -4
- package/dist/results/locator.js +0 -0
- package/dist/results/select.d.ts +6 -0
- package/dist/results/select.js +8 -0
- package/dist/runner/feedback/sink.d.ts +26 -1
- package/dist/runner/fingerprint.d.ts +23 -0
- package/dist/runner/types.d.ts +105 -7
- package/dist/sandbox/errors.d.ts +29 -0
- package/dist/sandbox/resolve.d.ts +9 -0
- package/dist/shared/failure-class.d.ts +91 -0
- package/dist/types.d.ts +1 -0
- package/dist/util.d.ts +3 -2
- package/dist/util.js +31 -5
- package/docs-site/zh/explanation/runner.mdx +35 -0
- package/docs-site/zh/reference/cli.mdx +3 -3
- package/docs-site/zh/reference/events.mdx +4 -4
- package/docs-site/zh/troubleshooting/debugging.mdx +2 -2
- package/docs-site/zh/tutorials/viewing-results.mdx +4 -5
- package/package.json +4 -12
- package/src/agents/ai-sdk.test.ts +26 -0
- package/src/agents/ai-sdk.ts +7 -4
- package/src/agents/index.ts +5 -3
- package/src/agents/langgraph.test.ts +30 -0
- package/src/agents/langgraph.ts +5 -2
- package/src/agents/openai-compat.test.ts +35 -0
- package/src/agents/openai-compat.ts +16 -4
- package/src/agents/sdk-streams.test.ts +66 -0
- package/src/agents/sdk-streams.ts +3 -1
- package/src/agents/types.ts +5 -4
- package/src/cli.ts +55 -14
- package/src/context/context.test.ts +34 -0
- package/src/context/context.ts +14 -5
- package/src/context/send-retry.test.ts +86 -0
- package/src/context/send-retry.ts +37 -12
- package/src/context/session.ts +24 -0
- package/src/context/turn-errors.test.ts +124 -17
- package/src/context/turn-errors.ts +60 -50
- package/src/define.ts +5 -0
- package/src/i18n/en.ts +20 -1
- package/src/i18n/zh-CN.ts +20 -1
- package/src/index.ts +8 -0
- package/src/o11y/cost.ts +4 -2
- package/src/o11y/derive.test.ts +40 -0
- package/src/o11y/derive.ts +28 -22
- package/src/o11y/otlp/sandbox-receiver.test.ts +201 -0
- package/src/o11y/otlp/sandbox-receiver.ts +73 -27
- package/src/o11y/parsers/bub.test.ts +30 -0
- package/src/o11y/parsers/bub.ts +5 -2
- package/src/o11y/parsers/codex.test.ts +19 -0
- package/src/o11y/parsers/codex.ts +5 -2
- package/src/o11y/types.ts +8 -5
- package/src/report/components/attempt-detail/UsageTable.tsx +4 -6
- package/src/report/components/attempt-detail/attempt-components.test.tsx +5 -8
- package/src/report/components/attempt-detail/compute.ts +2 -7
- package/src/report/components/attempt-detail/faces.ts +7 -7
- package/src/report/components/attempt-detail/index.tsx +0 -3
- package/src/report/components/entity-lists/EvalList.tsx +0 -0
- package/src/report/components/metric-views/compute.ts +1 -1
- package/src/report/model/types.ts +3 -4
- package/src/results/format.ts +9 -2
- package/src/results/index.ts +2 -0
- package/src/results/locator.ts +0 -0
- package/src/results/open.ts +132 -21
- package/src/results/select.ts +12 -0
- package/src/results/skipped-notice.ts +0 -0
- package/src/runner/attempt.test.ts +116 -0
- package/src/runner/attempt.ts +89 -8
- package/src/runner/discover.ts +21 -3
- package/src/runner/feedback/coordinator.ts +24 -2
- package/src/runner/feedback/eval-conclusions.ts +6 -3
- package/src/runner/feedback/human.test.ts +300 -6
- package/src/runner/feedback/human.ts +132 -30
- package/src/runner/feedback/json.test.ts +127 -2
- package/src/runner/feedback/json.ts +51 -3
- package/src/runner/feedback/reducer.test.ts +328 -29
- package/src/runner/feedback/reducer.ts +84 -9
- package/src/runner/feedback/sink.ts +39 -1
- package/src/runner/fingerprint.ts +49 -19
- package/src/runner/gate-lease.test.ts +510 -0
- package/src/runner/gate-lease.ts +350 -0
- package/src/runner/lock.test.ts +454 -0
- package/src/runner/lock.ts +288 -0
- package/src/runner/report.test.ts +1 -0
- package/src/runner/run.test.ts +2044 -9
- package/src/runner/run.ts +825 -61
- package/src/runner/teardown-registry.ts +20 -78
- package/src/runner/types.ts +103 -7
- package/src/sandbox/errors.test.ts +72 -0
- package/src/sandbox/errors.ts +83 -0
- package/src/sandbox/keep-registry.ts +22 -46
- package/src/sandbox/resolve.test.ts +100 -0
- package/src/sandbox/resolve.ts +84 -27
- package/src/shared/entry-file-store.test.ts +149 -0
- package/src/shared/entry-file-store.ts +117 -0
- package/src/shared/failure-class.test.ts +137 -0
- package/src/shared/failure-class.ts +175 -0
- package/src/show/index.ts +5 -6
- package/src/show/render.test.ts +116 -15
- package/src/show/render.ts +124 -35
- package/src/types.ts +9 -0
- package/src/util.ts +31 -4
- package/src/view/app/App.tsx +5 -1
- package/src/view/client-dist/app.js +1 -1
- package/src/view/data.ts +5 -9
- package/src/view/shared/types.ts +7 -1
- package/src/view/view-report.test.ts +54 -0
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
// docs/engineering/testing/e2e/cli.md「反馈输出格式」在真实进程输出上验收。
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it } from "vitest";
|
|
11
|
-
import { computeExitCode, renderJsonPlanDocument } from "./json.ts";
|
|
12
|
-
import
|
|
11
|
+
import { computeExitCode, createJsonRenderer, renderJsonPlanDocument } from "./json.ts";
|
|
12
|
+
import { createInitialRunFeedbackState, reduceRunFeedback } from "./reducer.ts";
|
|
13
|
+
import { createFakeFeedbackIO } from "./testing.ts";
|
|
14
|
+
import { HALT_DIAGNOSTIC_CODE, type DurableFeedbackEvent, type InvocationCompletion, type InvocationSummary } from "../types.ts";
|
|
13
15
|
|
|
14
16
|
function summary(overrides: Partial<InvocationSummary> = {}): InvocationSummary {
|
|
15
17
|
return {
|
|
@@ -77,6 +79,113 @@ describe("computeExitCode:CompletionStatus 驱动退出码,不只看 failed/erro
|
|
|
77
79
|
});
|
|
78
80
|
});
|
|
79
81
|
|
|
82
|
+
/** 依次喂进 reducer 再交给 json renderer(与生产的 coordinator 同序:先 reduce 后 render),
|
|
83
|
+
* 返回逐行解析出的事件对象。 */
|
|
84
|
+
function emitDurable(events: readonly DurableFeedbackEvent[]): Record<string, unknown>[] {
|
|
85
|
+
const { io, stdout } = createFakeFeedbackIO();
|
|
86
|
+
const renderer = createJsonRenderer({ io });
|
|
87
|
+
let state = createInitialRunFeedbackState();
|
|
88
|
+
for (const event of events) {
|
|
89
|
+
state = reduceRunFeedback(state, event);
|
|
90
|
+
renderer.appendDurable(event, state);
|
|
91
|
+
}
|
|
92
|
+
return stdout.writes
|
|
93
|
+
.join("")
|
|
94
|
+
.split("\n")
|
|
95
|
+
.filter(Boolean)
|
|
96
|
+
.map((line) => JSON.parse(line) as Record<string, unknown>);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
describe("warning 事件:code 是稳定词法,折叠身份走具名字段", () => {
|
|
100
|
+
it("code 用 DiagnosticInput.code 的干净字面量,不透传编了身份的去重 key", () => {
|
|
101
|
+
const [warning] = emitDurable([
|
|
102
|
+
{
|
|
103
|
+
type: "diagnostic",
|
|
104
|
+
at: 1,
|
|
105
|
+
key: "lock-taken-over:compare/codex|memory/retention",
|
|
106
|
+
code: "lock-taken-over",
|
|
107
|
+
severity: "warning",
|
|
108
|
+
message: "took over a stale lock",
|
|
109
|
+
data: { experimentId: "compare/codex", evalId: "memory/retention" },
|
|
110
|
+
},
|
|
111
|
+
]);
|
|
112
|
+
expect(warning).toMatchObject({
|
|
113
|
+
event: "warning",
|
|
114
|
+
code: "lock-taken-over",
|
|
115
|
+
level: "warning",
|
|
116
|
+
experimentId: "compare/codex",
|
|
117
|
+
evalId: "memory/retention",
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("没给 code 的诊断回落到 key(折叠身份本就不进 key 的那些天生是干净字面量)", () => {
|
|
122
|
+
const [warning] = emitDurable([
|
|
123
|
+
{ type: "diagnostic", at: 1, key: "memory-warmup-degraded", severity: "warning", message: "cold index" },
|
|
124
|
+
]);
|
|
125
|
+
expect(warning).toMatchObject({ code: "memory-warmup-degraded" });
|
|
126
|
+
expect(warning).not.toHaveProperty("experimentId");
|
|
127
|
+
expect(warning).not.toHaveProperty("evalId");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("eval 闸的 dispatch-halted:code 干净、evalId 与 phase 都在事件流里透得出", () => {
|
|
131
|
+
const [warning] = emitDurable([
|
|
132
|
+
{
|
|
133
|
+
type: "diagnostic",
|
|
134
|
+
at: 1,
|
|
135
|
+
key: "dispatch-halted:eval:compare/codex|memory/retention",
|
|
136
|
+
code: HALT_DIAGNOSTIC_CODE,
|
|
137
|
+
severity: "error",
|
|
138
|
+
message: "eval halted: fixture db is empty; run scripts/seed.ts",
|
|
139
|
+
data: {
|
|
140
|
+
experimentId: "compare/codex",
|
|
141
|
+
scope: "eval",
|
|
142
|
+
evalId: "memory/retention",
|
|
143
|
+
phase: "eval.run",
|
|
144
|
+
unstarted: 0,
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
148
|
+
expect(warning).toMatchObject({
|
|
149
|
+
event: "warning",
|
|
150
|
+
code: "dispatch-halted",
|
|
151
|
+
level: "error",
|
|
152
|
+
phase: "eval.run",
|
|
153
|
+
experimentId: "compare/codex",
|
|
154
|
+
evalId: "memory/retention",
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("身份从 data 取(闸不是 attempt 级、不伪造 identity);有 identity 时 identity 优先", () => {
|
|
159
|
+
const [fromIdentity] = emitDurable([
|
|
160
|
+
{
|
|
161
|
+
type: "diagnostic",
|
|
162
|
+
at: 1,
|
|
163
|
+
key: "fail-fast:x",
|
|
164
|
+
code: "fail-fast",
|
|
165
|
+
severity: "warning",
|
|
166
|
+
message: "deterministic failure",
|
|
167
|
+
identity: { experimentId: "compare/codex", evalId: "memory/a", attempt: 0 },
|
|
168
|
+
data: { experimentId: "other/exp", evalId: "memory/z" },
|
|
169
|
+
},
|
|
170
|
+
]);
|
|
171
|
+
expect(fromIdentity).toMatchObject({ experimentId: "compare/codex", evalId: "memory/a" });
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("同一 dedupeKey 只追加一次:emitter 为刷新 data.unstarted 反复报同一条闸,事件流不重复", () => {
|
|
175
|
+
const halted = (at: number, unstarted: number): DurableFeedbackEvent => ({
|
|
176
|
+
type: "diagnostic",
|
|
177
|
+
at,
|
|
178
|
+
key: "dispatch-halted:experiment:compare/codex",
|
|
179
|
+
code: HALT_DIAGNOSTIC_CODE,
|
|
180
|
+
severity: "error",
|
|
181
|
+
message: "experiment halted (dispatch-halted): shared service is down",
|
|
182
|
+
data: { experimentId: "compare/codex", scope: "experiment", phase: "eval.run", unstarted },
|
|
183
|
+
});
|
|
184
|
+
const events = emitDurable([halted(1, 0), halted(2, 1), halted(3, 2)]);
|
|
185
|
+
expect(events.filter((e) => e.event === "warning")).toHaveLength(1);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
80
189
|
describe("renderJsonPlanDocument:单个 ExpPlanDocument,不是事件流", () => {
|
|
81
190
|
it("输出恰好一行 JSON,可解析为单个对象而不是逐行事件序列", () => {
|
|
82
191
|
const text = renderJsonPlanDocument({
|
|
@@ -101,6 +210,22 @@ describe("renderJsonPlanDocument:单个 ExpPlanDocument,不是事件流", () =>
|
|
|
101
210
|
expect(doc.matrix).toHaveLength(2);
|
|
102
211
|
});
|
|
103
212
|
|
|
213
|
+
it("locked 为 true 的行原样透传;省略的行不出现 locked 字段(JSON.stringify 丢弃 undefined 属性)", () => {
|
|
214
|
+
const text = renderJsonPlanDocument({
|
|
215
|
+
total: 2,
|
|
216
|
+
evals: 2,
|
|
217
|
+
configs: 1,
|
|
218
|
+
runs: 1,
|
|
219
|
+
matrix: [
|
|
220
|
+
{ experimentId: "compare/codex", evalId: "memory/a", reused: false, locked: true },
|
|
221
|
+
{ experimentId: "compare/codex", evalId: "memory/b", reused: false },
|
|
222
|
+
],
|
|
223
|
+
});
|
|
224
|
+
const doc = JSON.parse(text);
|
|
225
|
+
expect(doc.matrix[0]).toMatchObject({ evalId: "memory/a", locked: true });
|
|
226
|
+
expect(doc.matrix[1]).not.toHaveProperty("locked");
|
|
227
|
+
});
|
|
228
|
+
|
|
104
229
|
it("reused 是 matrix 逐行 reused 之和(命中数量,不是 attempt 数)", () => {
|
|
105
230
|
const text = renderJsonPlanDocument({
|
|
106
231
|
total: 3,
|
|
@@ -104,13 +104,24 @@ export function createJsonRenderer(options: JsonRendererOptions): FeedbackRender
|
|
|
104
104
|
noteCheckpoint(event.at);
|
|
105
105
|
if (!isFirstOccurrence(state, event.key)) return; // 去重后只追加一次(cli.md)
|
|
106
106
|
const phase = typeof event.data?.phase === "string" ? (event.data.phase as LifecyclePhase) : undefined;
|
|
107
|
+
// `code` 是 cli.md `WarningEvent` 里那个稳定词法(`lock-taken-over` / `dispatch-halted`),
|
|
108
|
+
// **不是**去重 key:去重 key 常把折叠身份编进去(`lock-taken-over:<exp>|<eval>`),原样
|
|
109
|
+
// 透出会让消费方拿到一个每次运行都不同的 code、没法按值分支。折叠到哪一条实验/用例
|
|
110
|
+
// 由下面的 experimentId/evalId 两个具名字段回答。
|
|
111
|
+
const code = event.code ?? event.key;
|
|
112
|
+
// 身份两字段:attempt 级诊断从 identity 取;运行级诊断(实验闸 / eval 闸这类不属于
|
|
113
|
+
// 任何单条 attempt 的事实)不许伪造 identity,从 `data` 的同名字段取(见 ../types.ts
|
|
114
|
+
// "diagnostic" 变体的 identity 注释)。
|
|
115
|
+
const experimentId = event.identity?.experimentId ?? stringField(event.data?.experimentId);
|
|
116
|
+
const evalId = event.identity?.evalId ?? stringField(event.data?.evalId);
|
|
107
117
|
writeEvent(io, {
|
|
108
118
|
event: "warning",
|
|
109
|
-
code
|
|
119
|
+
code,
|
|
110
120
|
level: event.severity,
|
|
111
121
|
message: event.message,
|
|
112
122
|
...(phase !== undefined ? { phase } : {}),
|
|
113
|
-
...(
|
|
123
|
+
...(experimentId !== undefined ? { experimentId } : {}),
|
|
124
|
+
...(evalId !== undefined ? { evalId } : {}),
|
|
114
125
|
});
|
|
115
126
|
return;
|
|
116
127
|
}
|
|
@@ -148,6 +159,26 @@ export function createJsonRenderer(options: JsonRendererOptions): FeedbackRender
|
|
|
148
159
|
return;
|
|
149
160
|
}
|
|
150
161
|
|
|
162
|
+
case "lock-wait": {
|
|
163
|
+
noteCheckpoint(event.at);
|
|
164
|
+
writeEvent(io, {
|
|
165
|
+
event: "lock_wait",
|
|
166
|
+
experimentId: event.experimentId,
|
|
167
|
+
evalId: event.evalId,
|
|
168
|
+
status: event.status,
|
|
169
|
+
...(event.holderPid !== undefined ? { holderPid: event.holderPid } : {}),
|
|
170
|
+
...(event.holderHost !== undefined ? { holderHost: event.holderHost } : {}),
|
|
171
|
+
// 折叠 carried/dispatched 两个内部计数为一个公开 resolution:仍有 attempt 需要真实
|
|
172
|
+
// 派发(dispatched > 0)记 "dispatched",全部由携带满足才记 "carried"
|
|
173
|
+
// (见 ../types.ts 里 DurableFeedbackEvent "lock-wait" 变体的字段注释)。
|
|
174
|
+
...(event.status === "resolved"
|
|
175
|
+
? { resolution: (event.dispatched ?? 0) > 0 ? "dispatched" : "carried" }
|
|
176
|
+
: {}),
|
|
177
|
+
...(event.waitedMs !== undefined ? { waitedMs: event.waitedMs } : {}),
|
|
178
|
+
});
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
|
|
151
182
|
case "interrupted": {
|
|
152
183
|
noteCheckpoint(event.at);
|
|
153
184
|
if (!isFirstOccurrence(state, "interrupted")) return;
|
|
@@ -208,8 +239,14 @@ export function createJsonRenderer(options: JsonRendererOptions): FeedbackRender
|
|
|
208
239
|
total: state.total,
|
|
209
240
|
reused: state.reused,
|
|
210
241
|
running: state.running,
|
|
242
|
+
elsewhere: state.elsewhere,
|
|
211
243
|
queued: state.queued,
|
|
212
|
-
|
|
244
|
+
// 与 human 首行同一份状态、同一套划分:已了结的 attempt 按 verdict 分项,消费方不必
|
|
245
|
+
// 自己从合计数里猜成败(契约见 docs/feature/experiments/cli.md 的 ProgressEvent)。
|
|
246
|
+
passed: state.passed,
|
|
247
|
+
failed: state.failed,
|
|
248
|
+
errored: state.errored,
|
|
249
|
+
skipped: state.skipped,
|
|
213
250
|
});
|
|
214
251
|
},
|
|
215
252
|
|
|
@@ -225,6 +262,12 @@ function isFirstOccurrence(state: RunFeedbackState, key: string): boolean {
|
|
|
225
262
|
return (state.diagnostics.find((d) => d.key === key)?.count ?? 0) <= 1;
|
|
226
263
|
}
|
|
227
264
|
|
|
265
|
+
/** `data` 里的一个字符串字段;非字符串(或缺失)一律当没有,不把数字/对象硬塞进只接受
|
|
266
|
+
* 字符串的事件字段里。 */
|
|
267
|
+
function stringField(value: JsonValue | undefined): string | undefined {
|
|
268
|
+
return typeof value === "string" ? value : undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
228
271
|
// ───────────────────────── failure / error 事件(不设上限) ─────────────────────────
|
|
229
272
|
|
|
230
273
|
function writeFailureOrError(io: FeedbackIO, event: DurableFeedbackEvent & { type: "failure" }): void {
|
|
@@ -346,6 +389,11 @@ export interface JsonPlanRow {
|
|
|
346
389
|
evalId: string;
|
|
347
390
|
/** 命中缓存指纹,本次不会派发新 attempt。 */
|
|
348
391
|
reused: boolean;
|
|
392
|
+
/** 该用例正被另一条并行 Invocation 持锁运行,真实运行时将等待后携带或补跑(见
|
|
393
|
+
* docs/feature/experiments/architecture.md「并发 Invocation:用例锁」)。`--dry` 只读锁
|
|
394
|
+
* 目录,不取锁、不等待;省略等于 `false`(JSON.stringify 丢弃 `undefined` 属性,
|
|
395
|
+
* 天然满足这条省略语义,不需要显式写 `locked: false`)。 */
|
|
396
|
+
locked?: boolean;
|
|
349
397
|
}
|
|
350
398
|
|
|
351
399
|
export interface JsonPlanInput {
|