niceeval 0.11.2 → 0.11.3

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.
Files changed (172) hide show
  1. package/INDEX.md +1 -0
  2. package/README.md +4 -2
  3. package/README.zh.md +4 -2
  4. package/dist/agents/types.d.ts +2 -2
  5. package/dist/o11y/types.d.ts +3 -3
  6. package/dist/report/assets/colors.d.ts +1 -1
  7. package/dist/report/assets/colors.js +1 -1
  8. package/dist/report/components/attempt-detail/AttemptSource.js +1 -2
  9. package/dist/report/components/attempt-detail/compute.d.ts +4 -4
  10. package/dist/report/components/attempt-detail/compute.js +6 -6
  11. package/dist/report/components/attempt-detail/faces.d.ts +1 -1
  12. package/dist/report/components/attempt-detail/faces.js +3 -3
  13. package/dist/report/components/entity-lists/AttemptList.d.ts +3 -3
  14. package/dist/report/components/entity-lists/AttemptList.js +3 -3
  15. package/dist/report/components/entity-lists/ExperimentList.js +2 -2
  16. package/dist/report/components/entity-lists/compute.js +1 -1
  17. package/dist/report/components/entity-lists/faces.js +1 -1
  18. package/dist/report/components/entity-lists/index.d.ts +1 -1
  19. package/dist/report/components/entity-lists/index.js +1 -1
  20. package/dist/report/components/metric-views/chart-math.d.ts +2 -2
  21. package/dist/report/components/metric-views/chart-math.js +2 -2
  22. package/dist/report/components/metric-views/compute.d.ts +2 -3
  23. package/dist/report/components/metric-views/compute.js +3 -4
  24. package/dist/report/components/shared.d.ts +1 -1
  25. package/dist/report/components/site-components/compute.d.ts +5 -5
  26. package/dist/report/components/site-components/compute.js +6 -6
  27. package/dist/report/components/site-components/index.d.ts +7 -7
  28. package/dist/report/components/site-components/index.js +7 -7
  29. package/dist/report/components/site-components/scope-warnings.d.ts +1 -1
  30. package/dist/report/components/site-components/scope-warnings.js +3 -3
  31. package/dist/report/components/site-components/snapshot-diagnostics.js +1 -1
  32. package/dist/report/components/summaries/compute.d.ts +1 -1
  33. package/dist/report/components/summaries/compute.js +2 -2
  34. package/dist/report/components/summaries/faces.js +1 -1
  35. package/dist/report/components/summaries/index.d.ts +1 -1
  36. package/dist/report/components/summaries/index.js +1 -1
  37. package/dist/report/index.js +1 -1
  38. package/dist/report/model/aggregate.d.ts +2 -2
  39. package/dist/report/model/aggregate.js +2 -2
  40. package/dist/report/model/format.d.ts +2 -2
  41. package/dist/report/model/format.js +2 -2
  42. package/dist/report/model/metrics.js +3 -3
  43. package/dist/report/model/types.d.ts +11 -12
  44. package/dist/results/locator.js +1 -1
  45. package/dist/results/select.d.ts +4 -4
  46. package/dist/results/select.js +5 -5
  47. package/dist/results/types.d.ts +8 -8
  48. package/dist/runner/types.d.ts +14 -14
  49. package/dist/sandbox/resolve.d.ts +1 -1
  50. package/dist/sandbox/types.d.ts +1 -1
  51. package/docs-site/zh/reference/builtin-agents.mdx +21 -0
  52. package/docs-site/zh/reference/define-agent.mdx +1 -1
  53. package/docs-site/zh/reference/report-components.mdx +156 -74
  54. package/docs-site/zh/tutorials/custom-reports.mdx +112 -22
  55. package/docs-site/zh/tutorials/sandbox-agent.mdx +3 -4
  56. package/docs-site/zh/tutorials/sandbox-providers.mdx +15 -15
  57. package/docs-site/zh/tutorials/theming.mdx +169 -0
  58. package/package.json +2 -1
  59. package/src/agents/ai-sdk.test.ts +1 -1
  60. package/src/agents/bub-install-spec.ts +26 -8
  61. package/src/agents/bub.ts +40 -15
  62. package/src/agents/builtin.ts +11 -1
  63. package/src/agents/coding-cli-versions.ts +67 -0
  64. package/src/agents/hermes.ts +235 -0
  65. package/src/agents/index.ts +4 -0
  66. package/src/agents/langgraph.test.ts +1 -1
  67. package/src/agents/manifest.ts +1 -1
  68. package/src/agents/openai-compat.test.ts +1 -1
  69. package/src/agents/openclaw.ts +103 -24
  70. package/src/agents/opencode.ts +183 -0
  71. package/src/agents/sdk-streams.test.ts +1 -1
  72. package/src/agents/shared.ts +13 -2
  73. package/src/agents/types.ts +2 -2
  74. package/src/context/session.test.ts +1 -1
  75. package/src/context/session.ts +1 -1
  76. package/src/o11y/cost.ts +1 -1
  77. package/src/o11y/parsers/bub.test.ts +1 -1
  78. package/src/o11y/parsers/bub.ts +1 -1
  79. package/src/o11y/parsers/codex.test.ts +1 -1
  80. package/src/o11y/parsers/codex.ts +1 -1
  81. package/src/o11y/parsers/hermes.ts +198 -0
  82. package/src/o11y/parsers/openclaw.ts +25 -3
  83. package/src/o11y/parsers/opencode.ts +295 -0
  84. package/src/o11y/types.ts +3 -3
  85. package/src/report/assets/colors.ts +1 -1
  86. package/src/report/assets/styles.css +1 -1
  87. package/src/report/components/attempt-detail/AttemptAssertions.tsx +1 -1
  88. package/src/report/components/attempt-detail/AttemptConversation.tsx +1 -1
  89. package/src/report/components/attempt-detail/AttemptDiagnostics.tsx +1 -1
  90. package/src/report/components/attempt-detail/AttemptDiff.tsx +1 -1
  91. package/src/report/components/attempt-detail/AttemptError.tsx +1 -1
  92. package/src/report/components/attempt-detail/AttemptFixPrompt.tsx +1 -1
  93. package/src/report/components/attempt-detail/AttemptSource.tsx +2 -3
  94. package/src/report/components/attempt-detail/AttemptSummary.tsx +1 -1
  95. package/src/report/components/attempt-detail/AttemptTimeline.tsx +1 -1
  96. package/src/report/components/attempt-detail/AttemptTrace.tsx +1 -1
  97. package/src/report/components/attempt-detail/UsageTable.tsx +1 -1
  98. package/src/report/components/attempt-detail/attempt-components.test.tsx +1 -1
  99. package/src/report/components/attempt-detail/compute.ts +6 -6
  100. package/src/report/components/attempt-detail/faces.ts +3 -3
  101. package/src/report/components/attempt-detail/index.tsx +1 -1
  102. package/src/report/components/entity-lists/AttemptList.tsx +3 -3
  103. package/src/report/components/entity-lists/ExperimentList.tsx +2 -2
  104. package/src/report/components/entity-lists/compute.ts +1 -1
  105. package/src/report/components/entity-lists/faces.ts +1 -1
  106. package/src/report/components/entity-lists/index.tsx +1 -1
  107. package/src/report/components/metric-views/DeltaTable.tsx +1 -2
  108. package/src/report/components/metric-views/StabilityMatrix.tsx +1 -1
  109. package/src/report/components/metric-views/chart-math.test.ts +1 -1
  110. package/src/report/components/metric-views/chart-math.ts +2 -2
  111. package/src/report/components/metric-views/compute.ts +3 -4
  112. package/src/report/components/shared.ts +1 -1
  113. package/src/report/components/site-components/CopyFixPrompt.tsx +1 -1
  114. package/src/report/components/site-components/HeroCard.tsx +1 -1
  115. package/src/report/components/site-components/PoweredBy.tsx +1 -1
  116. package/src/report/components/site-components/ScopeWarnings.tsx +1 -1
  117. package/src/report/components/site-components/SnapshotDiagnostics.tsx +1 -1
  118. package/src/report/components/site-components/TraceWaterfall.tsx +1 -1
  119. package/src/report/components/site-components/compute.ts +6 -6
  120. package/src/report/components/site-components/index.tsx +7 -7
  121. package/src/report/components/site-components/scope-warnings.ts +3 -3
  122. package/src/report/components/site-components/snapshot-diagnostics.ts +1 -1
  123. package/src/report/components/summaries/ScopeSummary.tsx +2 -2
  124. package/src/report/components/summaries/compute.ts +2 -2
  125. package/src/report/components/summaries/faces.ts +1 -1
  126. package/src/report/components/summaries/index.tsx +1 -1
  127. package/src/report/index.ts +1 -1
  128. package/src/report/model/aggregate.ts +2 -2
  129. package/src/report/model/format.ts +2 -2
  130. package/src/report/model/metrics.ts +3 -3
  131. package/src/report/model/types.ts +12 -13
  132. package/src/results/annotated-source.test.ts +1 -1
  133. package/src/results/attempt-evidence.test.ts +1 -1
  134. package/src/results/copy.ts +3 -3
  135. package/src/results/format.ts +3 -3
  136. package/src/results/host-equivalence.test.ts +6 -6
  137. package/src/results/index.ts +1 -1
  138. package/src/results/locator.test.ts +2 -2
  139. package/src/results/locator.ts +1 -1
  140. package/src/results/open.ts +2 -2
  141. package/src/results/publish.ts +1 -1
  142. package/src/results/results.test.ts +6 -323
  143. package/src/results/select.test.ts +438 -0
  144. package/src/results/select.ts +6 -6
  145. package/src/results/skipped-notice.ts +1 -1
  146. package/src/results/truncate.ts +2 -2
  147. package/src/results/types.ts +9 -9
  148. package/src/results/writer.ts +4 -4
  149. package/src/runner/attempt.test.ts +2 -2
  150. package/src/runner/attempt.ts +6 -6
  151. package/src/runner/eval-source.test.ts +1 -1
  152. package/src/runner/eval-source.ts +1 -1
  153. package/src/runner/reporters/artifacts.ts +1 -1
  154. package/src/runner/run.ts +2 -2
  155. package/src/runner/timing.ts +1 -1
  156. package/src/runner/types.ts +14 -14
  157. package/src/sandbox/docker-agent-image.ts +36 -0
  158. package/src/sandbox/e2b-agent-template.test.ts +6 -21
  159. package/src/sandbox/e2b-agent-template.ts +44 -11
  160. package/src/sandbox/index.ts +8 -0
  161. package/src/sandbox/official-baselines.test.ts +175 -0
  162. package/src/sandbox/resolve.ts +1 -1
  163. package/src/sandbox/types.ts +1 -1
  164. package/src/scoring/diff.ts +1 -1
  165. package/src/scoring/types.ts +1 -1
  166. package/src/shared/facts.ts +1 -1
  167. package/src/show/index.ts +1 -1
  168. package/src/show/render.ts +1 -1
  169. package/src/show/show.test.ts +3 -3
  170. package/src/view/data.test.ts +2 -2
  171. package/src/view/data.ts +1 -1
  172. package/src/view/view-report.test.ts +1 -1
@@ -0,0 +1,295 @@
1
+ // OpenCode `run --format json` / `opencode export` 解析器。方言只住这里,不进 core
2
+ // (契约见 docs/feature/adapters/sdk/opencode/README.md)。
3
+
4
+ import type { StreamEvent, Usage, ToolName, JsonValue } from "../../types.ts";
5
+ import type { ParsedTranscript } from "./index.ts";
6
+ import { GENERIC_VERB_ALIASES, normalizeToolName as normalizeShared } from "../tool-names.ts";
7
+
8
+ export const OPENCODE_TOOL_ALIASES: Record<string, ToolName> = {
9
+ ...GENERIC_VERB_ALIASES,
10
+ read: "file_read",
11
+ write: "file_write",
12
+ create: "file_write",
13
+ edit: "file_edit",
14
+ patch: "file_edit",
15
+ // OpenCode 写文件主路径是 apply_patch;Add File 在下方按 patch 内容升成 file_write。
16
+ apply_patch: "file_edit",
17
+ bash: "shell",
18
+ shell: "shell",
19
+ webfetch: "web_fetch",
20
+ websearch: "web_search",
21
+ };
22
+
23
+ /** 从 apply_patch 的 patchText 抠 path,并把 Add File 升成 file_write。 */
24
+ function enrichApplyPatchInput(input: JsonValue): { input: JsonValue; tool: ToolName } {
25
+ const obj = input && typeof input === "object" && !Array.isArray(input)
26
+ ? { ...(input as Record<string, unknown>) }
27
+ : ({ patchText: input } as Record<string, unknown>);
28
+ const patchText = typeof obj.patchText === "string" ? obj.patchText : typeof obj.patch === "string" ? obj.patch : "";
29
+ const add = patchText.match(/\*\*\*\s*Add File:\s*(.+)/);
30
+ const update = patchText.match(/\*\*\*\s*(?:Update|Delete) File:\s*(.+)/);
31
+ const path = (add?.[1] ?? update?.[1] ?? "").trim();
32
+ if (path) obj.path = path;
33
+ return {
34
+ input: obj as JsonValue,
35
+ tool: add ? "file_write" : "file_edit",
36
+ };
37
+ }
38
+
39
+ function normalizeToolName(name: string): ToolName {
40
+ return normalizeShared(name, OPENCODE_TOOL_ALIASES);
41
+ }
42
+
43
+ function get(obj: unknown, key: string): unknown {
44
+ return obj && typeof obj === "object" ? (obj as Record<string, unknown>)[key] : undefined;
45
+ }
46
+
47
+ function str(v: unknown): string | undefined {
48
+ return typeof v === "string" && v ? v : undefined;
49
+ }
50
+
51
+ function num(obj: unknown, ...keys: string[]): number {
52
+ for (const k of keys) {
53
+ const v = get(obj, k);
54
+ if (typeof v === "number" && Number.isFinite(v)) return v;
55
+ }
56
+ return 0;
57
+ }
58
+
59
+ function coerceArgs(value: unknown): JsonValue {
60
+ if (typeof value === "string") {
61
+ try {
62
+ return JSON.parse(value) as JsonValue;
63
+ } catch {
64
+ return value;
65
+ }
66
+ }
67
+ return (value ?? {}) as JsonValue;
68
+ }
69
+
70
+ /**
71
+ * 从 stdout / export 的 JSONL(或夹杂日志的文本)里抽出 JSON 对象行。
72
+ */
73
+ export function extractOpenCodeJsonl(raw: string | undefined): string | undefined {
74
+ if (!raw?.trim()) return undefined;
75
+ const lines = raw.split("\n").filter((line) => {
76
+ const t = line.trim();
77
+ return t.startsWith("{") && t.endsWith("}");
78
+ });
79
+ return lines.length ? lines.join("\n") : undefined;
80
+ }
81
+
82
+ /** 事件行上的 sessionID / sessionId。 */
83
+ export function sessionIdFromOpenCodeTranscript(raw: string | undefined): string | undefined {
84
+ const jsonl = extractOpenCodeJsonl(raw) ?? raw;
85
+ if (!jsonl) return undefined;
86
+ for (const line of jsonl.split("\n")) {
87
+ const t = line.trim();
88
+ if (!t) continue;
89
+ try {
90
+ const obj = JSON.parse(t) as Record<string, unknown>;
91
+ const id = str(obj.sessionID) ?? str(obj.sessionId) ?? str(get(obj, "session_id"));
92
+ if (id) return id;
93
+ } catch {
94
+ // skip
95
+ }
96
+ }
97
+ return undefined;
98
+ }
99
+
100
+ /**
101
+ * OpenCode JSON 事件流 → 标准事件 + usage。
102
+ * 认 `tool_use` / `text` / `step_finish` 主路径,并兼容少量 legacy 形状。
103
+ */
104
+ export function parseOpenCodeTranscript(raw: string | undefined): ParsedTranscript {
105
+ const events: StreamEvent[] = [];
106
+ let inputTokens = 0;
107
+ let outputTokens = 0;
108
+ let cacheReadTokens = 0;
109
+ let cacheCreationTokens = 0;
110
+ let costUSD = 0;
111
+ let requests = 0;
112
+ let parseSuccess = true;
113
+ let synth = 0;
114
+ const nextSynthId = (): string => `oc_${++synth}`;
115
+ const pending: string[] = [];
116
+
117
+ const jsonl = extractOpenCodeJsonl(raw) ?? raw;
118
+ if (!jsonl?.trim()) {
119
+ return { events, usage: {}, compactions: 0, parseSuccess: true };
120
+ }
121
+
122
+ const addUsage = (usage: unknown): void => {
123
+ if (!usage || typeof usage !== "object") return;
124
+ const rawInput = num(usage, "input", "input_tokens", "inputTokens", "prompt_tokens");
125
+ const output = num(usage, "output", "output_tokens", "outputTokens", "completion_tokens");
126
+ let cacheRead = num(usage, "cacheRead", "cache_read", "cache_read_input_tokens", "cacheReadTokens");
127
+ if (cacheRead === 0) cacheRead = num(get(usage, "prompt_tokens_details"), "cached_tokens");
128
+ const cacheCreation = num(usage, "cacheWrite", "cache_write", "cache_creation_input_tokens");
129
+ const cost = num(usage, "cost", "costUSD", "total_cost");
130
+ if (rawInput === 0 && output === 0 && cacheRead === 0 && cacheCreation === 0 && cost === 0) return;
131
+ // Chat Completions 口径的 prompt_tokens 可能含缓存命中
132
+ const input = Math.max(0, rawInput - cacheRead);
133
+ inputTokens += input;
134
+ outputTokens += output;
135
+ cacheReadTokens += cacheRead;
136
+ cacheCreationTokens += cacheCreation;
137
+ costUSD += cost;
138
+ requests += 1;
139
+ };
140
+
141
+ for (const line of jsonl.split("\n")) {
142
+ const trimmed = line.trim();
143
+ if (!trimmed) continue;
144
+ let data: Record<string, unknown>;
145
+ try {
146
+ data = JSON.parse(trimmed) as Record<string, unknown>;
147
+ } catch {
148
+ parseSuccess = false;
149
+ continue;
150
+ }
151
+
152
+ try {
153
+ const eventType = str(data.type) ?? str(data.kind) ?? str(data.event);
154
+ const part = get(data, "part") as Record<string, unknown> | undefined;
155
+ const state = part ? (get(part, "state") as Record<string, unknown> | undefined) : undefined;
156
+
157
+ if (eventType === "tool_use" && part && str(get(part, "tool"))) {
158
+ const name = str(get(part, "tool"))!;
159
+ let input = coerceArgs(get(state, "input") ?? get(part, "input"));
160
+ let tool = normalizeToolName(name);
161
+ if (name.toLowerCase() === "apply_patch" || name.toLowerCase() === "patch") {
162
+ const enriched = enrichApplyPatchInput(input);
163
+ input = enriched.input;
164
+ tool = enriched.tool;
165
+ }
166
+ const callId =
167
+ str(get(part, "callID")) ??
168
+ str(get(part, "callId")) ??
169
+ str(get(part, "id")) ??
170
+ str(get(data, "id")) ??
171
+ nextSynthId();
172
+ if (!str(get(part, "callID")) && !str(get(part, "callId")) && !str(get(part, "id"))) {
173
+ pending.push(callId);
174
+ }
175
+ events.push({
176
+ type: "action.called",
177
+ callId,
178
+ name,
179
+ input,
180
+ tool,
181
+ });
182
+ const status = str(get(state, "status"));
183
+ if (status === "completed" || status === "error" || get(state, "output") !== undefined) {
184
+ const output = (get(state, "output") ?? get(state, "error") ?? null) as JsonValue;
185
+ const metadata = get(state, "metadata") as Record<string, unknown> | undefined;
186
+ const exitCode = typeof get(metadata, "exit") === "number" ? (get(metadata, "exit") as number) : undefined;
187
+ const isShell = ["bash", "shell", "exec"].includes(name.toLowerCase());
188
+ const success = isShell
189
+ ? exitCode === 0 || exitCode === undefined
190
+ : status !== "error" && !get(state, "error");
191
+ events.push({
192
+ type: "action.result",
193
+ callId,
194
+ output,
195
+ status: success ? "completed" : "failed",
196
+ });
197
+ }
198
+ continue;
199
+ }
200
+
201
+ if (eventType === "text") {
202
+ const text = str(get(part, "text")) ?? str(data.text) ?? str(data.content);
203
+ if (text?.trim()) events.push({ type: "message", role: "assistant", text });
204
+ continue;
205
+ }
206
+
207
+ if (eventType === "step_finish" || eventType === "step_start") {
208
+ // OpenCode 1.18 把用量放在 part.tokens({input,output,...}) + part.cost。
209
+ const tokens = get(part, "tokens") ?? get(data, "tokens");
210
+ if (tokens && typeof tokens === "object") {
211
+ addUsage({
212
+ ...(tokens as Record<string, unknown>),
213
+ cost: get(part, "cost") ?? get(data, "cost"),
214
+ });
215
+ } else {
216
+ addUsage(get(data, "usage") ?? get(part, "usage") ?? get(state, "usage"));
217
+ }
218
+ continue;
219
+ }
220
+
221
+ if (eventType === "message" || eventType === "assistant" || eventType === "response") {
222
+ const text =
223
+ str(get(get(data, "message"), "content")) ?? str(data.content) ?? str(data.text);
224
+ if (text?.trim()) events.push({ type: "message", role: "assistant", text });
225
+ addUsage(get(data, "usage"));
226
+ continue;
227
+ }
228
+
229
+ if (eventType === "error") {
230
+ const message =
231
+ str(get(get(data, "error"), "message")) ?? str(data.message) ?? str(data.content) ?? "opencode error";
232
+ events.push({ type: "error", message });
233
+ continue;
234
+ }
235
+
236
+ // export 形态:顶层 messages[]
237
+ const messages = get(data, "messages");
238
+ if (Array.isArray(messages)) {
239
+ for (const msg of messages) {
240
+ const info = get(msg, "info") as Record<string, unknown> | undefined;
241
+ const role = str(get(info, "role")) ?? str(get(msg, "role"));
242
+ const parts = get(msg, "parts");
243
+ if (Array.isArray(parts)) {
244
+ for (const p of parts) {
245
+ const pType = str(get(p, "type"));
246
+ if (pType === "text") {
247
+ const text = str(get(p, "text"));
248
+ if (text?.trim() && role !== "user") {
249
+ events.push({ type: "message", role: "assistant", text });
250
+ }
251
+ } else if (pType === "tool" || pType === "tool_use") {
252
+ const name = str(get(p, "tool")) ?? str(get(p, "name")) ?? "unknown";
253
+ const callId = str(get(p, "callID")) ?? str(get(p, "id")) ?? nextSynthId();
254
+ events.push({
255
+ type: "action.called",
256
+ callId,
257
+ name,
258
+ input: coerceArgs(get(get(p, "state"), "input") ?? get(p, "input")),
259
+ tool: normalizeToolName(name),
260
+ });
261
+ const st = get(p, "state") as Record<string, unknown> | undefined;
262
+ if (st && (str(get(st, "status")) === "completed" || get(st, "output") !== undefined)) {
263
+ events.push({
264
+ type: "action.result",
265
+ callId,
266
+ output: (get(st, "output") ?? null) as JsonValue,
267
+ status: str(get(st, "status")) === "error" ? "failed" : "completed",
268
+ });
269
+ }
270
+ }
271
+ }
272
+ } else {
273
+ const text = str(get(msg, "content")) ?? str(get(msg, "text"));
274
+ if (text?.trim() && role !== "user") {
275
+ events.push({ type: "message", role: "assistant", text });
276
+ }
277
+ }
278
+ addUsage(get(info, "usage") ?? get(msg, "usage"));
279
+ }
280
+ }
281
+ } catch {
282
+ parseSuccess = false;
283
+ }
284
+ }
285
+
286
+ const usage: Usage = {};
287
+ if (inputTokens) usage.inputTokens = inputTokens;
288
+ if (outputTokens) usage.outputTokens = outputTokens;
289
+ if (cacheReadTokens) usage.cacheReadTokens = cacheReadTokens;
290
+ if (cacheCreationTokens) usage.cacheCreationTokens = cacheCreationTokens;
291
+ if (costUSD) usage.costUSD = costUSD;
292
+ if (requests) usage.requests = requests;
293
+
294
+ return { events, usage, compactions: 0, parseSuccess };
295
+ }
package/src/o11y/types.ts CHANGED
@@ -8,7 +8,7 @@ import type { JsonValue, SourceLoc } from "../shared/types.ts";
8
8
  * send 的 `Turn.usage` 直接返回)。每个字段只在协议真实提供该值时存在——原始协议没有 usage 时
9
9
  * 省略,不编造数值;不存在「默认 0」或「默认 1」的字段。三个输入侧桶恒互斥:相加才是送进模型的
10
10
  * 完整上下文量;把协议原生口径归一到互斥是 adapter 的落值义务——OpenAI 系报「含缓存的输入总量 +
11
- * 缓存命中子集」,落 inputTokens 前先扣掉子集(docs/feature/results/architecture.md#usage,
11
+ * 缓存命中子集」,落 inputTokens 前先扣掉子集(docs/feature/record/architecture.md#usage,
12
12
  * 各协议明细见 docs/feature/adapters/sdk/<name>/cost.md)。
13
13
  */
14
14
  export interface Usage {
@@ -64,7 +64,7 @@ export interface InputRequest {
64
64
  }
65
65
 
66
66
  /**
67
- * 落盘截断的结构化标记(见 docs/feature/results/architecture.md「大值截断」):
67
+ * 落盘截断的结构化标记(见 docs/feature/record/architecture.md「大值截断」):
68
68
  * `path` 是被截断的位置(事件里是字段名,如 "output";span 里是 attribute key,如 "output.value"),
69
69
  * `originalBytes` 是截断前的 UTF-8 字节数。view 显示「输出过大,已截断」靠它,不正则匹配 marker。
70
70
  */
@@ -188,7 +188,7 @@ export interface TraceSpan {
188
188
  * 给人 / 给 EVAL.ts 看的 o11y 摘要(注入沙箱 __niceeval__/results.json)。从 `events.json` 派生的
189
189
  * 行为计数缓存,不是权威——同一 niceeval 版本写读,删除后可从 `events.json` 重算。token 用量、
190
190
  * 成本与耗时不在这里:权威唯一在 `result.json` 的 `Usage` / `estimatedCostUSD` 与 `durationMs`
191
- * / `phases`(见 docs/feature/results/architecture.md「o11y.json」)。
191
+ * / `phases`(见 docs/feature/record/architecture.md「o11y.json」)。
192
192
  */
193
193
  export interface O11ySummary {
194
194
  totalTurns: number;
@@ -36,7 +36,7 @@ export function colorIndexForKey(key: string): number {
36
36
  * 同一张图内 series 键集合的配色:每个键仍以稳定散列为起点(无冲突时与跨图配色一致),
37
37
  * 图内撞色时按传入顺序(图例顺序)线性探测下一个空色格——跨图稳定让位给图内可辨;
38
38
  * 键数超过色板后无空格可探,回落散列格(复用不可避免)。返回键 → 色板下标。
39
- * 契约见 docs/feature/reports/library/metric-views.md「MetricScatter」;
39
+ * 契约见 docs/feature/reports/components/README.md「系列色:分配单位是页」;
40
40
  * 修法台账见 memory/scatter-series-color-collision.md。
41
41
  */
42
42
  export function colorIndicesForKeys(keys: readonly string[]): Map<string, number> {
@@ -1064,7 +1064,7 @@ table.nre,
1064
1064
  }
1065
1065
 
1066
1066
  /* Source:GitHub diff 式整块横向滚动;与产品站示例卡同一套视觉语言(整行 8% 浅染 + 2px 左缘 +
1067
- 行号位图标,规范见 docs/feature/reports/library/attempt-detail.md)。普通行只是 div,
1067
+ 行号位图标,规范见 docs/feature/reports/components/attempt-detail/attempt-source.md)。普通行只是 div,
1068
1068
  只有有证据的行才是可点击 details。 */
1069
1069
  .nre-attempt-source {
1070
1070
  overflow: hidden;
@@ -1,5 +1,5 @@
1
1
  // AttemptAssertions:全量 assertion,非 passed 默认展开、passed 按 group 折叠计数。
2
- // 没有 assertion 时零输出(docs/feature/reports/library/attempt-detail.md)。
2
+ // 没有 assertion 时零输出(docs/feature/reports/components/attempt-detail/README.md)。
3
3
 
4
4
  import type { ReactElement } from "react";
5
5
  import type { AttemptAssertionsData } from "../../model/types.ts";
@@ -1,5 +1,5 @@
1
1
  // AttemptConversation:标准事件流按轮组织的完整分轮事件卡。没有 events 时零输出
2
- // (docs/feature/reports/library/attempt-detail.md)。
2
+ // (docs/feature/reports/components/attempt-detail/README.md)。
3
3
 
4
4
  import type { ReactElement, ReactNode } from "react";
5
5
  import type { AttemptConversationData, AttemptConversationReply, AttemptConversationRound } from "../../model/types.ts";
@@ -1,5 +1,5 @@
1
1
  // AttemptDiagnostics:lifecycle 分组的 diagnostics。没有 diagnostics 时零输出
2
- // (docs/feature/reports/library/attempt-detail.md)。
2
+ // (docs/feature/reports/components/attempt-detail/README.md)。
3
3
 
4
4
  import type { ReactElement } from "react";
5
5
  import type { AttemptDiagnosticsData } from "../../model/types.ts";
@@ -1,5 +1,5 @@
1
1
  // AttemptDiff:generated / modified / deleted 文件摘要与展开的行变化统计。没有变更时零输出
2
- // (docs/feature/reports/library/attempt-detail.md)。
2
+ // (docs/feature/reports/components/attempt-detail/README.md)。
3
3
 
4
4
  import type { ReactElement } from "react";
5
5
  import type { AttemptDiffData, AttemptDiffFileEntry } from "../../model/types.ts";
@@ -1,4 +1,4 @@
1
- // AttemptError:结构化 error 明细。没有 error 时零输出(docs/feature/reports/library/attempt-detail.md)。
1
+ // AttemptError:结构化 error 明细。没有 error 时零输出(docs/feature/reports/components/attempt-detail/README.md)。
2
2
  // 字段标签是低层技术标识,与终端 `niceeval show` 一样保持英文,不进 view 的 i18n 词典。
3
3
 
4
4
  import type { ReactElement } from "react";
@@ -1,5 +1,5 @@
1
1
  // AttemptFixPrompt:单条 attempt 的复制修复 prompt。passed 或没有可操作失败时零输出
2
- // (docs/feature/reports/library/attempt-detail.md)。与 CopyFixPrompt 同一套增强约定:
2
+ // (docs/feature/reports/components/attempt-detail/README.md)。与 CopyFixPrompt 同一套增强约定:
3
3
  // prompt 在 resolve 阶段算好、烘进静态 HTML,复制按钮是 enhance.js 的增强行为。
4
4
 
5
5
  import type { ReactElement } from "react";
@@ -1,6 +1,6 @@
1
1
  // AttemptSource:GitHub diff 式带标注 eval 源码。send / assertion 行按状态着色,点击
2
2
  // 原生 details 在调用点展开完整回复与 assertion 细节。没有 source 时零输出
3
- // (docs/feature/reports/library/attempt-detail.md)。
3
+ // (docs/feature/reports/components/attempt-detail/attempt-source.md)。
4
4
 
5
5
  import type { ReactElement, ReactNode } from "react";
6
6
  import type { AttemptSourceData, AttemptSourceLineData, AttemptSourceTurn } from "../../model/types.ts";
@@ -192,8 +192,7 @@ function AssertionDetail({ assertion, locale }: { assertion: AssertionResult & {
192
192
  }
193
193
 
194
194
  /** `t.score(label, n)` 调用行的展开区:给分记录本体(label、挣分、分组路径),不着判定色——
195
- * 给分是分数面事实,不是判定(docs/feature/reports/library/attempt-detail.md「AttemptSource
196
- * web 面视觉规范」给分行)。 */
195
+ * 给分是分数面事实,不是判定(docs/feature/reports/components/attempt-detail/attempt-source.md「给分行」)。 */
197
196
  function ScoreEntryDetail({ entry }: { entry: ScoreEntry }): ReactElement {
198
197
  return (
199
198
  <div className="nre-score-entry-row">
@@ -1,4 +1,4 @@
1
- // AttemptSummary:恒非空的身份/verdict/时间/成本卡(docs/feature/reports/library/attempt-detail.md)。
1
+ // AttemptSummary:恒非空的身份/verdict/时间/成本卡(docs/feature/reports/components/attempt-detail/README.md)。
2
2
 
3
3
  import type { ReactElement } from "react";
4
4
  import type { AttemptSummaryData } from "../../model/types.ts";
@@ -1,6 +1,6 @@
1
1
  // AttemptTimeline:runner phases 主链 + 收尾段,children(hook/command/turn)默认收合、
2
2
  // 失败节点带标记;turn 节点按 traceId 关联同一轮的 agent/model/tool span。没有 phase 时
3
- // 零输出(docs/feature/reports/library/attempt-detail.md)。
3
+ // 零输出(docs/feature/reports/components/attempt-detail/README.md)。
4
4
 
5
5
  import type { ReactElement } from "react";
6
6
  import type { AttemptTimelineData } from "../../model/types.ts";
@@ -1,5 +1,5 @@
1
1
  // AttemptTrace:不与 runner 节点混合的原始 OTel span 树。没有 trace 时零输出
2
- // (docs/feature/reports/library/attempt-detail.md)。
2
+ // (docs/feature/reports/components/attempt-detail/README.md)。
3
3
 
4
4
  import type { ReactElement } from "react";
5
5
  import type { AttemptTraceData } from "../../model/types.ts";
@@ -1,5 +1,5 @@
1
1
  // UsageTable:判定、轮数、工具调用数、token 拆分与成本摊成的用量明细。没有 usage 时零输出
2
- // (docs/feature/reports/library/attempt-detail.md#usagetable-组装口径单源)。web 面与 text 面
2
+ // (docs/feature/reports/components/attempt-detail/usage-table.md#组装口径单源)。web 面与 text 面
3
3
  // 单行摘要(usageTableText)是同一份 UsageTableData 的两种呈现,不重复组装口径。
4
4
 
5
5
  import type { ReactElement } from "react";
@@ -423,7 +423,7 @@ describe("Attempt 详情组件族:非空/空证据矩阵", () => {
423
423
  });
424
424
 
425
425
  it("UsageTable:桶恒互斥,inputTokens 落盘原样透传,不派生第二个输入字段", () => {
426
- // inputTokens 本身就是未缓存输入(docs/feature/results/architecture.md#usage),
426
+ // inputTokens 本身就是未缓存输入(docs/feature/record/architecture.md#usage),
427
427
  // "uncached in" 只是 face 层在 cacheReadTokens 在场时给的标注,data 不多一个字段。
428
428
  const both = usageTableData(evidenceOf({ result: resultOf({ usage: { inputTokens: 100, outputTokens: 1, cacheReadTokens: 40 } }) }))!;
429
429
  expect(both.usage?.inputTokens).toBe(100);
@@ -1,4 +1,4 @@
1
- // Attempt 详情组件族的计算函数(docs/feature/reports/library/attempt-detail.md)。每个
1
+ // Attempt 详情组件族的计算函数(docs/feature/reports/components/attempt-detail/README.md)。每个
2
2
  // `attempt*Data(evidence)` 都是纯同步派生:evidence 已经由 loadAttemptEvidence 一次性
3
3
  // 装配好全部证据,这里只做适合展示与序列化的取舍,不读文件、不 fetch、不重复调用
4
4
  // attempt.events() / trace() / diff()。
@@ -263,7 +263,7 @@ export function attemptSourceData(evidence: AttemptEvidence): AttemptSourceData
263
263
 
264
264
  /**
265
265
  * 单条 attempt 版的批量修复 prompt(与 CopyFixPrompt 的多条版本同一份步骤文案)。三态
266
- * (docs/feature/reports/library/attempt-detail.md「`AttemptFixPrompt`」):计分制丢分或中止 →
266
+ * (docs/feature/reports/components/attempt-detail/README.md「`AttemptFixPrompt`」):计分制丢分或中止 →
267
267
  * 非 null(围绕丢分检查点组装);计分制挣满且未中止、或通过制 passed → null;skipped 恒 null。
268
268
  */
269
269
  export function attemptFixPromptData(evidence: AttemptEvidence): AttemptFixPromptData | null {
@@ -303,7 +303,7 @@ export function attemptFixPromptData(evidence: AttemptEvidence): AttemptFixPromp
303
303
 
304
304
  // ───────────────────────── AttemptTimeline ─────────────────────────
305
305
 
306
- /** 收尾段的阶段名(见 docs/feature/results/architecture.md);两面渲染都把这些单列在主链之后,不计入主链总耗时。 */
306
+ /** 收尾段的阶段名(见 docs/feature/record/architecture.md);两面渲染都把这些单列在主链之后,不计入主链总耗时。 */
307
307
  export const TIMELINE_CLOSING_PHASES: ReadonlySet<string> = new Set([
308
308
  "eval.teardown",
309
309
  "agent.teardown",
@@ -340,7 +340,7 @@ function commandStartOffsetMs(phases: readonly PhaseTiming[] | undefined, timing
340
340
  }
341
341
 
342
342
  /**
343
- * 失败命令按关联 timing 节点的 `startOffsetMs` 排序(docs/feature/results/architecture.md
343
+ * 失败命令按关联 timing 节点的 `startOffsetMs` 排序(docs/feature/record/architecture.md
344
344
  * 「commandsjson」);关联不到 timing 节点的排在最后,组内保持 `commands.json` 原始顺序作稳定
345
345
  * tie-break,不按数组偶然顺序猜时间。
346
346
  */
@@ -355,7 +355,7 @@ function sortFailedCommands(
355
355
  }
356
356
 
357
357
  /**
358
- * 标准事件流按 `loc` 分轮(docs/feature/reports/library/attempt-detail.md「Attempt 详情组件」):
358
+ * 标准事件流按 `loc` 分轮(docs/feature/reports/components/attempt-detail/README.md「Attempt 详情组件」):
359
359
  * 带 loc 的 user 消息开一轮;无 loc 的 user 消息不开新轮——与当前轮 sent 同文本的回显直接
360
360
  * 吃掉,其它(stop-hook 反馈、skill 注入等轮内注入)作为回复条目留在当前轮。流首出现无 loc
361
361
  * 的 user 消息(没有当前轮可归入)时退化开一条 loc 缺省的兜底轮,不丢弃。未识别的事件类型
@@ -482,7 +482,7 @@ export function attemptDiagnosticsData(evidence: AttemptEvidence): AttemptDiagno
482
482
  // ───────────────────────── UsageTable ─────────────────────────
483
483
 
484
484
  /**
485
- * 组装口径单源:docs/feature/reports/library/attempt-detail.md#usagetable-组装口径单源。
485
+ * 组装口径单源:docs/feature/reports/components/attempt-detail/usage-table.md#组装口径单源。
486
486
  * identity 字段(locator/experimentId/evalId/attempt/verdict)恒有;turns/toolCalls 是 events
487
487
  * 派生(与 o11y.json 行为摘要同源,buildO11ySummary 与 o11y.json 落盘走同一份纯函数),没有
488
488
  * events 就整对省略——不因为其中一个恰好是 0 就当作"缺失"处理,0 是观测到的事实。
@@ -1,4 +1,4 @@
1
- // Attempt 详情组件族的 text 面(docs/feature/reports/library/attempt-detail.md「在 show 与
1
+ // Attempt 详情组件族的 text 面(docs/feature/reports/components/attempt-detail/README.md「在 show 与
2
2
  // view 怎样渲染」):与 web 面共享同一次 resolve 产出的 data 事实(verdict、计数、能力位、
3
3
  // 引用),允许把大块内容折成摘要 + 专用证据命令,但不得改变判定、计数或引用。
4
4
  // 零 react、零 IO、纯同步——text 宿主不需要 react-dom 的那一半。
@@ -304,7 +304,7 @@ export function attemptDiagnosticsText(data: AttemptDiagnosticsData | null, _ctx
304
304
  // ───────────────────────── UsageTable ─────────────────────────
305
305
 
306
306
  /**
307
- * 单行装配形态,与 docs/feature/reports/library/attempt-detail.md#usagetable-组装口径单源
307
+ * 单行装配形态,与 docs/feature/reports/components/attempt-detail/usage-table.md#组装口径单源
308
308
  * 的示例行文案是同一形态本身,不是它的近似摘要:
309
309
  * `usage: 6 turns · 21 tool calls · 62.3k uncached in + 942.6k cache read / 6.7k out · 24 requests · $1.14`
310
310
  * 每个片段独立地只在对应事实存在时出现,顺序保持不变;全部缺失时整行不出现(返回 ""),
@@ -332,7 +332,7 @@ export function usageTableText(data: UsageTableData | null, _ctx: TextContext):
332
332
  data.toolCalls !== undefined ? `${data.toolCalls} tool call${data.toolCalls === 1 ? "" : "s"}` : undefined,
333
333
  tokenSegment || undefined,
334
334
  // requests 只在协议真实提供时显示——协议不提供就整段省略,绝不凑一个 1
335
- // (docs/feature/reports/library/attempt-detail.md#usagetable-组装口径单源)。
335
+ // (docs/feature/reports/components/attempt-detail/usage-table.md#组装口径单源)。
336
336
  usage?.requests !== undefined ? `${usage.requests} requests` : undefined,
337
337
  data.estimatedCostUSD !== undefined ? formatUSD(data.estimatedCostUSD) : undefined,
338
338
  ].filter((s): s is string => s !== undefined);
@@ -1,4 +1,4 @@
1
- // Attempt 详情组件族的装配点(docs/feature/reports/library/attempt-detail.md)。11 个叶子
1
+ // Attempt 详情组件族的装配点(docs/feature/reports/components/attempt-detail/README.md)。11 个叶子
2
2
  // 组件都是同一份 spec/data 判别联合(AttemptSectionProps<Data>):省略 input 时取当前
3
3
  // attempt-input page 注入的 evidence;放在 scope-input page 且未显式传 input/data 时
4
4
  // resolve 报完整用户反馈并指引移到 attempt-input page 或传入 evidence。
@@ -15,7 +15,7 @@ import { cx } from "../shared.ts";
15
15
 
16
16
  /**
17
17
  * 时效标注(`↩` + 紧凑时距):历史执行(携带,或跨快照拼入)才渲染,新执行不标——subdued
18
- * 行内事实,不占框、不用警示色(docs/feature/reports/library/entity-lists.md「时效标注」)。
18
+ * 行内事实,不占框、不用警示色(docs/feature/reports/components/entity-lists/README.md「时效标注」)。
19
19
  * web 面 hover 显示完整执行时刻;ExperimentList / EvalList / AttemptList 三处共用。
20
20
  */
21
21
  export function HistoricalMark({
@@ -35,7 +35,7 @@ export function HistoricalMark({
35
35
 
36
36
  /**
37
37
  * Eval 父行的时效标注:全部 attempt 均为历史执行时,标最近一次执行(startedAt 最大)的时距;
38
- * 新旧混合时父行不标,子行各自可见(docs/feature/reports/library/entity-lists.md「时效标注」)。
38
+ * 新旧混合时父行不标,子行各自可见(docs/feature/reports/components/entity-lists/README.md「时效标注」)。
39
39
  * EvalList 与 ExperimentList 的 Eval 父行共用。
40
40
  */
41
41
  export function EvalHistoricalMark({
@@ -79,7 +79,7 @@ export function AttemptLocatorBadge({
79
79
  /**
80
80
  * failureSummary + moreFailures 的展示形态:摘要原样,+N 计数由 moreFailures 驱动。计分制
81
81
  * passed attempt 的非 null 摘要只可能来自丢分得分点(规则 6)——"+N more lost points",不是
82
- * "+N more failures":丢分不是失败,措辞分开(docs/feature/reports/library/entity-lists.md
82
+ * "+N more failures":丢分不是失败,措辞分开(docs/feature/reports/components/entity-lists/README.md
83
83
  * `AttemptListItem.moreFailures` 字段注释)。
84
84
  */
85
85
  export function failureSummaryText(item: Pick<AttemptListItem, "failureSummary" | "moreFailures" | "verdict">, locale: ReportLocale): string | null {
@@ -136,7 +136,7 @@ function EvalAttempts({
136
136
  /**
137
137
  * 覆盖缺口的占位行:状态列为 —,结果列为「当前配置下无结果」+ 可复制的补跑命令,无 attempt
138
138
  * 子行,不参与任何指标聚合——只把分母缺口摆进读者正在看的表里
139
- * (docs/feature/reports/library/entity-lists.md「ExperimentList」)。
139
+ * (docs/feature/reports/components/entity-lists/experiment-list.md)。
140
140
  */
141
141
  function MissingEvalRow({
142
142
  evalId,
@@ -279,7 +279,7 @@ export function ExperimentList({
279
279
  }): ReactElement {
280
280
  const experimentLabels = shortestUniqueLabels(data.map((item) => item.experimentId));
281
281
  // 主读数列按列表内题型构成选择;web/text 共用同一份判据,不各自重新判断
282
- // (docs/feature/reports/library/entity-lists.md「ExperimentList」)。
282
+ // (docs/feature/reports/components/entity-lists/experiment-list.md)。
283
283
  const composition = experimentListScoringComposition(data);
284
284
  const showPassRate = composition !== "points";
285
285
  const showTotalScore = composition !== "pass";
@@ -1,5 +1,5 @@
1
1
  // 计算函数(*Data):ReportInput → 一份组件数据。实体列表族(ExperimentList / EvalList /
2
- // AttemptList / FailureList)的 *Data 都住在这里(docs/feature/reports/library/entity-lists.md)。
2
+ // AttemptList / FailureList)的 *Data 都住在这里(docs/feature/reports/components/entity-lists/README.md)。
3
3
  //
4
4
  // 共同约定(docs/feature/reports/architecture.md「指标聚合不变量」):
5
5
  // - 第一参收 ReportInput = Scope | readonly Snapshot[];warnings 不进组件数据(宿主统一显示);
@@ -34,7 +34,7 @@ function locatorBadge(item: { locator: string; verdict: AttemptListItem["verdict
34
34
  /**
35
35
  * 时效标注(`↩` + 紧凑时距)的 text 面:历史执行(携带,或跨快照拼入)才输出,新执行为
36
36
  * 空串;三面(ExperimentList / EvalList / AttemptList)共用
37
- * (docs/feature/reports/library/entity-lists.md「时效标注」)。
37
+ * (docs/feature/reports/components/entity-lists/README.md「时效标注」)。
38
38
  */
39
39
  function historicalSuffix(item: Pick<AttemptListItem, "startedAt" | "historical">): string {
40
40
  return item.historical ? ` ↩ ${formatHistoricalGap(item.startedAt)}` : "";
@@ -220,7 +220,7 @@ export interface FailureListProps {
220
220
 
221
221
  /**
222
222
  * 「现在有哪些失败要处理」的成品组合件:内部就是 attemptListData → 过滤 → AttemptList
223
- * data 形态,与手写组合严格等价、没有私有能力(docs/feature/reports/library/entity-lists.md)。
223
+ * data 形态,与手写组合严格等价、没有私有能力(docs/feature/reports/components/entity-lists/README.md)。
224
224
  * verdict ∈ failed / errored,按 attempt 开始时间降序(同刻按 locator 字典序),
225
225
  * 截断到 limit(默认 20),total 报告截断前总数。
226
226
  */
@@ -1,7 +1,6 @@
1
1
  // DeltaTable:对照矩阵。每行一道 eval,每组列一个条件(首个是基准),尾部对基准的 Δ 列组;
2
2
  // 通过制显示 verdict、计分制在同一位置显示挣分,tokens / 成本恒显示;翻转标记 ⇄ 只在该行各
3
- // 条件判定不一致时出现;历史执行叠加 ↩ 标注(docs/feature/reports/library/metric-views.md
4
- // 「DeltaTable」)。任一侧缺数据时对应分量各自显示缺,不硬算成 0。
3
+ // 条件判定不一致时出现;历史执行叠加 ↩ 标注(docs/feature/reports/components/tables/delta-table.md)。任一侧缺数据时对应分量各自显示缺,不硬算成 0。
5
4
 
6
5
  import type { ReactElement } from "react";
7
6
  import type { DeltaData } from "../../model/types.ts";
@@ -1,6 +1,6 @@
1
1
  // StabilityMatrix:历史全执行的稳定性矩阵。行 = eval,列 = by 维度取值,格是该组合全部历史
2
2
  // 执行(跨快照按身份键去重、不设可比性门槛)的判定计数——回答「这道题历史上稳不稳」,不是
3
- // 现刻水位下「现在算不算过」(docs/feature/reports/library/metric-views.md「StabilityMatrix」)。
3
+ // 现刻水位下「现在算不算过」(docs/feature/reports/components/tables/stability-matrix.md)。
4
4
  // 稀疏格子:没有样本的格子不出现,不编三个 0 冒充跑过。
5
5
 
6
6
  import type { ReactElement } from "react";