niceeval 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (314) hide show
  1. package/dist/agents/types.d.ts +72 -6
  2. package/dist/context/types.d.ts +32 -12
  3. package/dist/i18n/en.d.ts +54 -0
  4. package/dist/i18n/zh-CN.d.ts +55 -1
  5. package/dist/o11y/types.d.ts +16 -2
  6. package/dist/report/aggregate.d.ts +5 -3
  7. package/dist/report/aggregate.js +32 -5
  8. package/dist/report/built-ins/experiment-comparison.d.ts +39 -0
  9. package/dist/report/built-ins/experiment-comparison.js +119 -0
  10. package/dist/report/built-ins/index.d.ts +2 -1
  11. package/dist/report/built-ins/index.js +2 -2
  12. package/dist/report/components.d.ts +10 -2
  13. package/dist/report/components.js +3 -3
  14. package/dist/report/compute.d.ts +11 -18
  15. package/dist/report/compute.js +68 -66
  16. package/dist/report/flag.d.ts +16 -1
  17. package/dist/report/flag.js +19 -1
  18. package/dist/report/format.d.ts +16 -14
  19. package/dist/report/format.js +28 -30
  20. package/dist/report/index.d.ts +5 -4
  21. package/dist/report/index.js +6 -5
  22. package/dist/report/locale.d.ts +23 -3
  23. package/dist/report/locale.js +47 -6
  24. package/dist/report/metrics.d.ts +13 -1
  25. package/dist/report/metrics.js +66 -15
  26. package/dist/report/primitives.d.ts +6 -0
  27. package/dist/report/react/AttemptList.d.ts +4 -4
  28. package/dist/report/react/AttemptList.js +8 -10
  29. package/dist/report/react/EvalList.d.ts +1 -1
  30. package/dist/report/react/EvalList.js +0 -0
  31. package/dist/report/react/ExperimentComparison.d.ts +8 -0
  32. package/dist/report/react/ExperimentComparison.js +11 -0
  33. package/dist/report/react/ExperimentList.d.ts +4 -2
  34. package/dist/report/react/ExperimentList.js +57 -7
  35. package/dist/report/react/MetricScatter.js +6 -16
  36. package/dist/report/react/chart-math.d.ts +23 -6
  37. package/dist/report/react/chart-math.js +71 -19
  38. package/dist/report/react/fixtures.d.ts +3 -3
  39. package/dist/report/react/fixtures.js +30 -18
  40. package/dist/report/react/format.d.ts +1 -1
  41. package/dist/report/react/format.js +1 -1
  42. package/dist/report/react/index.d.ts +1 -1
  43. package/dist/report/report.d.ts +5 -1
  44. package/dist/report/report.js +6 -2
  45. package/dist/report/text/faces.d.ts +1 -1
  46. package/dist/report/text/faces.js +100 -61
  47. package/dist/report/text/table.js +36 -5
  48. package/dist/report/types.d.ts +40 -34
  49. package/dist/results/types.d.ts +11 -0
  50. package/dist/runner/feedback/sink.d.ts +110 -0
  51. package/dist/runner/types.d.ts +513 -22
  52. package/dist/sandbox/docker.d.ts +23 -2
  53. package/dist/sandbox/e2b.d.ts +15 -1
  54. package/dist/sandbox/errors.d.ts +30 -3
  55. package/dist/sandbox/io-retry.d.ts +17 -0
  56. package/dist/sandbox/registry.d.ts +2 -0
  57. package/dist/sandbox/resolve.d.ts +18 -5
  58. package/dist/sandbox/retry.d.ts +11 -1
  59. package/dist/sandbox/types.d.ts +39 -5
  60. package/dist/sandbox/vercel.d.ts +7 -1
  61. package/dist/scoring/coverage.d.ts +30 -0
  62. package/dist/scoring/display.d.ts +21 -0
  63. package/dist/scoring/display.js +120 -0
  64. package/dist/scoring/types.d.ts +103 -20
  65. package/dist/shared/aggregate.d.ts +1 -0
  66. package/dist/shared/aggregate.js +3 -3
  67. package/dist/shared/types.d.ts +28 -0
  68. package/dist/tty-line.d.ts +0 -4
  69. package/dist/util.d.ts +23 -0
  70. package/docs-site/zh/concepts/adapter.mdx +24 -6
  71. package/docs-site/zh/concepts/assert.mdx +11 -10
  72. package/docs-site/zh/concepts/evals.mdx +7 -6
  73. package/docs-site/zh/concepts/experiment.mdx +1 -1
  74. package/docs-site/zh/concepts/overview.mdx +7 -7
  75. package/docs-site/zh/guides/agent-feedback-loop.mdx +35 -31
  76. package/docs-site/zh/guides/authoring.mdx +33 -0
  77. package/docs-site/zh/guides/ci-integration.mdx +23 -12
  78. package/docs-site/zh/guides/connect-your-agent.mdx +29 -3
  79. package/docs-site/zh/guides/custom-reports.mdx +29 -34
  80. package/docs-site/zh/guides/dataset-fanout.mdx +25 -3
  81. package/docs-site/zh/guides/debug-sandbox.mdx +57 -0
  82. package/docs-site/zh/guides/debugging.mdx +210 -0
  83. package/docs-site/zh/guides/experiments.mdx +10 -3
  84. package/docs-site/zh/guides/fixtures.mdx +3 -1
  85. package/docs-site/zh/guides/official-adapters.mdx +27 -3
  86. package/docs-site/zh/guides/publish-report.mdx +30 -16
  87. package/docs-site/zh/guides/report-components.mdx +49 -37
  88. package/docs-site/zh/guides/reporters.mdx +2 -2
  89. package/docs-site/zh/guides/results-data.mdx +42 -8
  90. package/docs-site/zh/guides/runner.mdx +17 -7
  91. package/docs-site/zh/guides/sandbox-agent.mdx +57 -7
  92. package/docs-site/zh/guides/sandbox-providers.mdx +258 -10
  93. package/docs-site/zh/guides/scoring-guide.mdx +4 -4
  94. package/docs-site/zh/guides/viewing-results.mdx +85 -41
  95. package/docs-site/zh/guides/write-experiment.mdx +5 -3
  96. package/docs-site/zh/guides/write-send.mdx +19 -2
  97. package/docs-site/zh/index.mdx +1 -1
  98. package/docs-site/zh/reference/builtin-agents.mdx +27 -0
  99. package/docs-site/zh/reference/capabilities.mdx +2 -2
  100. package/docs-site/zh/reference/cli.mdx +35 -9
  101. package/docs-site/zh/reference/define-agent.mdx +60 -5
  102. package/docs-site/zh/reference/define-config.mdx +1 -1
  103. package/docs-site/zh/reference/define-eval.mdx +42 -9
  104. package/docs-site/zh/reference/events.mdx +2 -2
  105. package/docs-site/zh/reference/expect.mdx +36 -6
  106. package/package.json +5 -1
  107. package/src/agents/ai-sdk-otel.test.ts +1 -0
  108. package/src/agents/ai-sdk.test.ts +3 -0
  109. package/src/agents/ai-sdk.ts +3 -0
  110. package/src/agents/bub-install-spec.test.ts +34 -0
  111. package/src/agents/bub-install-spec.ts +32 -0
  112. package/src/agents/bub.ts +31 -32
  113. package/src/agents/claude-code.test.ts +130 -9
  114. package/src/agents/claude-code.ts +76 -4
  115. package/src/agents/codex.test.ts +189 -40
  116. package/src/agents/codex.ts +155 -14
  117. package/src/agents/coding-cli-versions.test.ts +15 -0
  118. package/src/agents/coding-cli-versions.ts +3 -0
  119. package/src/agents/index.ts +11 -0
  120. package/src/agents/langgraph.test.ts +204 -0
  121. package/src/agents/langgraph.ts +495 -0
  122. package/src/agents/marketplace.ts +85 -0
  123. package/src/agents/native-config.test.ts +179 -0
  124. package/src/agents/native-config.ts +267 -0
  125. package/src/agents/openai-compat.test.ts +1 -0
  126. package/src/agents/openclaw.test.ts +31 -0
  127. package/src/agents/openclaw.ts +171 -0
  128. package/src/agents/plugin-config.test.ts +1 -0
  129. package/src/agents/sdk-streams.test.ts +79 -0
  130. package/src/agents/sdk-streams.ts +55 -10
  131. package/src/agents/skills.test.ts +1 -0
  132. package/src/agents/streaming.test.ts +3 -9
  133. package/src/agents/types.ts +73 -6
  134. package/src/agents/ui-message-stream.test.ts +3 -0
  135. package/src/cli.ts +411 -108
  136. package/src/context/context.test.ts +51 -12
  137. package/src/context/context.ts +161 -29
  138. package/src/context/session.test.ts +1 -0
  139. package/src/context/session.ts +114 -6
  140. package/src/context/types.ts +30 -12
  141. package/src/define.test.ts +13 -8
  142. package/src/define.ts +25 -4
  143. package/src/expect/index.ts +53 -23
  144. package/src/i18n/en.ts +65 -4
  145. package/src/i18n/zh-CN.ts +66 -4
  146. package/src/o11y/cost.test.ts +1 -0
  147. package/src/o11y/execution-tree.test.ts +1 -20
  148. package/src/o11y/otlp/mappers/claude-code.test.ts +1 -0
  149. package/src/o11y/otlp/parse.test.ts +1 -0
  150. package/src/o11y/otlp/turn-otel.test.ts +1 -0
  151. package/src/o11y/parsers/bub.test.ts +1 -0
  152. package/src/o11y/parsers/claude-code.test.ts +1 -34
  153. package/src/o11y/parsers/openclaw.test.ts +154 -0
  154. package/src/o11y/parsers/openclaw.ts +310 -0
  155. package/src/o11y/prices.json +746 -311
  156. package/src/o11y/tool-names.test.ts +1 -0
  157. package/src/o11y/types.ts +16 -2
  158. package/src/report/aggregate.ts +34 -5
  159. package/src/report/built-in-user-parity.test.tsx +127 -173
  160. package/src/report/built-ins/experiment-comparison.tsx +179 -0
  161. package/src/report/built-ins/index.ts +7 -2
  162. package/src/report/components.tsx +11 -3
  163. package/src/report/compute.ts +80 -74
  164. package/src/report/dual-render.test.tsx +222 -91
  165. package/src/report/flag.ts +30 -2
  166. package/src/report/format.ts +36 -27
  167. package/src/report/index.ts +23 -6
  168. package/src/report/locale.ts +49 -6
  169. package/src/report/metrics.ts +68 -15
  170. package/src/report/primitives.tsx +6 -0
  171. package/src/report/react/AttemptList.tsx +9 -36
  172. package/src/report/react/EvalList.tsx +0 -0
  173. package/src/report/react/ExperimentComparison.tsx +68 -0
  174. package/src/report/react/ExperimentList.tsx +173 -55
  175. package/src/report/react/MetricScatter.tsx +13 -25
  176. package/src/report/react/chart-math.test.ts +85 -0
  177. package/src/report/react/chart-math.ts +101 -22
  178. package/src/report/react/enhance.js +72 -1
  179. package/src/report/react/fixtures.ts +34 -21
  180. package/src/report/react/format.ts +1 -1
  181. package/src/report/react/index.tsx +0 -1
  182. package/src/report/react/render.test.tsx +30 -69
  183. package/src/report/react/styles.css +112 -14
  184. package/src/report/report.test.ts +308 -105
  185. package/src/report/report.ts +6 -2
  186. package/src/report/text/faces.ts +111 -67
  187. package/src/report/text/table.ts +42 -5
  188. package/src/report/types.ts +42 -34
  189. package/src/results/annotated-source.test.ts +62 -9
  190. package/src/results/annotated-source.ts +64 -6
  191. package/src/results/attempt-evidence.test.ts +9 -7
  192. package/src/results/attempt-evidence.ts +15 -8
  193. package/src/results/attempt-source.ts +6 -3
  194. package/src/results/copy.ts +145 -55
  195. package/src/results/host-equivalence.test.ts +11 -9
  196. package/src/results/index.ts +2 -0
  197. package/src/results/locator.test.ts +1 -22
  198. package/src/results/open.ts +7 -1
  199. package/src/results/publish.ts +149 -0
  200. package/src/results/results.test.ts +85 -51
  201. package/src/results/truncate.ts +90 -0
  202. package/src/results/types.ts +7 -0
  203. package/src/results/writer.ts +31 -13
  204. package/src/runner/attempt.test.ts +138 -7
  205. package/src/runner/attempt.ts +603 -104
  206. package/src/runner/discover.test.ts +47 -0
  207. package/src/runner/discover.ts +36 -2
  208. package/src/runner/eval-source.test.ts +1 -27
  209. package/src/runner/feedback/agent.test.ts +504 -0
  210. package/src/runner/feedback/agent.ts +409 -0
  211. package/src/runner/feedback/ci.test.ts +562 -0
  212. package/src/runner/feedback/ci.ts +401 -0
  213. package/src/runner/feedback/coordinator.test.ts +317 -0
  214. package/src/runner/feedback/coordinator.ts +397 -0
  215. package/src/runner/feedback/failure.ts +40 -0
  216. package/src/runner/feedback/human.test.ts +616 -0
  217. package/src/runner/feedback/human.ts +535 -0
  218. package/src/runner/feedback/index.ts +66 -0
  219. package/src/runner/feedback/io.ts +78 -0
  220. package/src/runner/feedback/profile.test.ts +50 -0
  221. package/src/runner/feedback/profile.ts +58 -0
  222. package/src/runner/feedback/reducer.test.ts +395 -0
  223. package/src/runner/feedback/reducer.ts +260 -0
  224. package/src/runner/feedback/renderer.ts +82 -0
  225. package/src/runner/feedback/sink.ts +203 -0
  226. package/src/runner/feedback/testing.ts +106 -0
  227. package/src/runner/ledger.test.ts +230 -0
  228. package/src/runner/ledger.ts +329 -0
  229. package/src/runner/report.test.ts +128 -3
  230. package/src/runner/report.ts +33 -9
  231. package/src/runner/reporters/artifacts.ts +8 -2
  232. package/src/runner/reporters/braintrust.test.ts +8 -7
  233. package/src/runner/reporters/braintrust.ts +9 -2
  234. package/src/runner/reporters/index.ts +2 -2
  235. package/src/runner/reporters/json.test.ts +162 -0
  236. package/src/runner/reporters/json.ts +35 -8
  237. package/src/runner/reporters/shared.ts +1 -5
  238. package/src/runner/run.test.ts +760 -3
  239. package/src/runner/run.ts +242 -36
  240. package/src/runner/sandbox-prep.ts +3 -42
  241. package/src/runner/timing.ts +158 -0
  242. package/src/runner/types.ts +518 -22
  243. package/src/sandbox/checkpoint.test.ts +55 -0
  244. package/src/sandbox/checkpoint.ts +29 -8
  245. package/src/sandbox/cli-commands.ts +407 -0
  246. package/src/sandbox/docker.ts +115 -16
  247. package/src/sandbox/e2b-agent-template.test.ts +56 -0
  248. package/src/sandbox/e2b-agent-template.ts +94 -0
  249. package/src/sandbox/e2b.ts +74 -9
  250. package/src/sandbox/errors.ts +111 -4
  251. package/src/sandbox/index.ts +2 -0
  252. package/src/sandbox/io-retry.test.ts +58 -0
  253. package/src/sandbox/io-retry.ts +45 -0
  254. package/src/sandbox/keep-registry.test.ts +86 -0
  255. package/src/sandbox/keep-registry.ts +142 -0
  256. package/src/sandbox/keep.ts +178 -0
  257. package/src/sandbox/paths.test.ts +1 -0
  258. package/src/sandbox/paths.ts +19 -8
  259. package/src/sandbox/registry.ts +20 -3
  260. package/src/sandbox/resolve.ts +76 -11
  261. package/src/sandbox/retry.test.ts +70 -0
  262. package/src/sandbox/retry.ts +46 -4
  263. package/src/sandbox/types.ts +44 -6
  264. package/src/sandbox/vercel.ts +43 -20
  265. package/src/scoring/collector.ts +60 -17
  266. package/src/scoring/coverage.ts +95 -0
  267. package/src/scoring/diff.ts +81 -0
  268. package/src/scoring/display.test.ts +121 -0
  269. package/src/scoring/display.ts +133 -0
  270. package/src/scoring/evidence.test.ts +189 -0
  271. package/src/scoring/judge.test.ts +142 -0
  272. package/src/scoring/judge.ts +15 -18
  273. package/src/scoring/scoped.ts +217 -50
  274. package/src/scoring/types.ts +117 -20
  275. package/src/scoring/verdict.ts +16 -4
  276. package/src/shared/aggregate.ts +3 -2
  277. package/src/shared/types.ts +31 -0
  278. package/src/show/compose.ts +2 -2
  279. package/src/show/index.ts +29 -16
  280. package/src/show/render.ts +626 -308
  281. package/src/show/show.test.ts +251 -36
  282. package/src/tty-line.ts +8 -26
  283. package/src/util.test.ts +1 -0
  284. package/src/util.ts +41 -0
  285. package/src/view/app/components/AttemptModal.tsx +153 -2
  286. package/src/view/app/components/CodeView.tsx +32 -11
  287. package/src/view/app/components/CopyControls.tsx +2 -2
  288. package/src/view/app/i18n.ts +6 -0
  289. package/src/view/app/lib/attempt-route.test.ts +1 -0
  290. package/src/view/app/lib/verdict.ts +7 -9
  291. package/src/view/artifact-serving.test.ts +2 -1
  292. package/src/view/client-dist/app.css +1 -1
  293. package/src/view/client-dist/app.js +17 -17
  294. package/src/view/data.test.ts +2 -1
  295. package/src/view/data.ts +17 -7
  296. package/src/view/index.ts +12 -1
  297. package/src/view/server.ts +2 -0
  298. package/src/view/shared/types.ts +1 -1
  299. package/src/view/styles.css +3 -0
  300. package/src/view/view-report.test.ts +11 -10
  301. package/dist/o11y/execution-tree.d.ts +0 -103
  302. package/dist/o11y/otlp/select.d.ts +0 -22
  303. package/dist/report/built-ins/cost-pass-rate-comparison.d.ts +0 -1
  304. package/dist/report/built-ins/cost-pass-rate-comparison.js +0 -17
  305. package/dist/results/annotated-source.d.ts +0 -61
  306. package/dist/results/attempt-evidence.d.ts +0 -69
  307. package/dist/results/attempt-source.d.ts +0 -15
  308. package/src/report/built-ins/cost-pass-rate-comparison.tsx +0 -23
  309. package/src/runner/reporters/console.ts +0 -70
  310. package/src/runner/reporters/live.test.ts +0 -56
  311. package/src/runner/reporters/live.ts +0 -247
  312. package/src/runner/reporters/quiet.test.ts +0 -66
  313. package/src/runner/reporters/quiet.ts +0 -49
  314. package/src/runner/reporters/table.ts +0 -277
@@ -1,18 +1,119 @@
1
- import type { Cleanup, LocalizedText, SourceArtifact } from "../shared/types.ts";
1
+ import type { Cleanup, JsonValue, LocalizedText, SourceArtifact } from "../shared/types.ts";
2
2
  import type { O11ySummary, StreamEvent, TraceSpan, Usage } from "../o11y/types.ts";
3
3
  import type { Agent, AgentSetupManifest } from "../agents/types.ts";
4
- import type { Sandbox, SandboxOption } from "../sandbox/types.ts";
5
- import type { AssertionResult, DiffData, JudgeConfig, Verdict } from "../scoring/types.ts";
4
+ import type { Sandbox, SandboxHookContext, SandboxOption } from "../sandbox/types.ts";
5
+ import type { AssertionResult, DiffArtifact, JudgeConfig, PrimaryAssertionSummary, Verdict } from "../scoring/types.ts";
6
6
  import type { TestContext } from "../context/types.ts";
7
7
  import type { CapturedEvalSource } from "./eval-source.ts";
8
+ import type { AttemptLocator } from "../results/locator.ts";
9
+ /**
10
+ * 解析后运行配置的**穷尽可序列化投影**——记录这次运行实际生效的值,不是原始 `ExperimentDef`
11
+ * (函数与 hooks 无法忠实落盘,存「原样」只能存谎)。`model` 与 `agent` 只在快照顶层存在,
12
+ * 这里不复制(见 docs/feature/results/architecture.md「snapshot.json」)。
13
+ */
8
14
  export interface ExperimentRunInfo {
9
- id?: string;
10
- flags?: Record<string, unknown>;
11
- runs?: number;
12
- earlyExit?: boolean;
13
- sandbox?: string;
15
+ description?: string;
16
+ reasoningEffort?: string;
17
+ flags?: Record<string, JsonValue>;
18
+ runs: number;
19
+ earlyExit: boolean;
14
20
  timeoutMs?: number;
15
21
  budget?: number;
22
+ maxConcurrency?: number;
23
+ /** 本次运行解析后实际选中的 eval id 全集——evals 过滤器(含函数形式)的求值结果,不存过滤器本身。 */
24
+ selectedEvalIds: string[];
25
+ /** evals 过滤器的指纹(数组内容 / 函数体哈希),供「配置没变」判断;与 selectedEvalIds 一起取代原过滤器。 */
26
+ evalFilterFingerprint?: string;
27
+ /** provider 名、provider 的公开参数投影与配置 fingerprint;参数只经投影落盘,token/凭据永不进来。 */
28
+ sandbox?: {
29
+ provider: string;
30
+ params?: Record<string, JsonValue>;
31
+ fingerprint?: string;
32
+ };
33
+ }
34
+ /**
35
+ * 一次 attempt 的生命周期词表——**全仓唯一一套**(见 docs/feature/results/architecture.md
36
+ * 「result.json」)。计时(`phases[].name`)、错误归因(`error.phase`)、诊断归属
37
+ * (`diagnostics[].phase`)、live 展示与 agent/ci envelope 的 `phase=` 都使用这同一个闭集,
38
+ * 不存在第二套词表。phase 是 runner 对真实 lifecycle 的单方面投影,不是 adapter / sandbox
39
+ * provider / 用户 hook 能直接设置的公共字段。
40
+ */
41
+ export type LifecyclePhase = "sandbox.queue" | "sandbox.create" | "sandbox.setup" | "workspace.baseline" | "eval.setup" | "agent.setup" | "telemetry.configure" | "eval.run" | "agent.run" | "workspace.diff" | "scoring.evaluate" | "telemetry.collect" | "eval.teardown" | "agent.teardown" | "sandbox.teardown" | "sandbox.suspend" | "sandbox.stop";
42
+ /** TimingNode 的种类(见 docs/feature/results/architecture.md「result.json」)。 */
43
+ export type TimingNodeKind = "hook" | "turn" | "command" | "provider" | "operation";
44
+ /**
45
+ * Runner 直接观察到的阶段内时间树节点;只供单 attempt 诊断,不做跨实验聚合。
46
+ * `startOffsetMs` 相对 attempt 单调时钟起点——并发 sibling 可据此还原重叠,
47
+ * 不能只靠数组顺序相加。
48
+ */
49
+ export interface TimingNode {
50
+ /** attempt 内唯一,供 children 与展示层稳定引用;不作为跨 attempt 身份。 */
51
+ id: string;
52
+ kind: TimingNodeKind;
53
+ /** 人读标签;hook 匿名时用 setup#<i>/teardown#<i>,turn 用 s<session>/t<turn>。 */
54
+ label: string;
55
+ /** 相对 attempt 单调时钟起点的偏移。 */
56
+ startOffsetMs: number;
57
+ durationMs: number;
58
+ failed?: true;
59
+ children?: TimingNode[];
60
+ /** kind=turn 时存在;把 runner 的 send 墙钟包络与 trace.json 中同一轮的 spans 显式关联。 */
61
+ sessionIndex?: number;
62
+ turnIndex?: number;
63
+ turnId?: string;
64
+ traceId?: string;
65
+ traceAttribution?: "traceparent" | "window" | "none";
66
+ /** kind=command 时的有界脱敏摘要;环境变量值与 stdout/stderr 不进入时间树。 */
67
+ command?: {
68
+ display: string;
69
+ exitCode?: number;
70
+ };
71
+ }
72
+ /** Runner 阶段计时,按执行顺序;只记录实际发生的阶段(见 docs/feature/results/architecture.md)。 */
73
+ export interface PhaseTiming {
74
+ name: LifecyclePhase;
75
+ /** 阶段耗时;失败阶段计到抛错或超时中断时。 */
76
+ durationMs: number;
77
+ /** 该阶段抛错或被超时中断。主链至多一条,其后无主链条目;收尾阶段各自独立标记,不改判定。 */
78
+ failed?: true;
79
+ /** Runner 直接观察到的阶段内时间树;只供单 attempt 诊断,不做跨实验聚合。 */
80
+ children?: TimingNode[];
81
+ }
82
+ /**
83
+ * 使 attempt 无法正常完成的唯一致命执行错误(见 docs/feature/results/architecture.md 的
84
+ * `AttemptError`)。`message` 是人可读的一层原因(不拼整份 SDK response);完整 stack 单放
85
+ * `stack`,`niceeval show @locator` 首页展开、终端即时反馈不整段打印。榜单只显示 `message`。
86
+ */
87
+ export interface AttemptError {
88
+ /** 稳定、可供 CI/Agent 分支处理的机器码;未知异常使用 `"unexpected-error"`。 */
89
+ code: string;
90
+ /** 人可读的一层原因,不拼接整份 SDK response。 */
91
+ message: string;
92
+ /** runner 在错误发生时已经打开的生命周期阶段。 */
93
+ phase: LifecyclePhase;
94
+ /** 原异常有 stack 时保留,供 show 展开;终端即时反馈不整段打印。 */
95
+ stack?: string;
96
+ /** 下层 SDK/OS 错误的有限摘要。 */
97
+ cause?: {
98
+ name?: string;
99
+ code?: string;
100
+ message: string;
101
+ };
102
+ }
103
+ /**
104
+ * 不一定改变 verdict、但运行后仍需回顾的有界诊断(见 docs/feature/results/architecture.md 的
105
+ * `DiagnosticRecord`)。`level` 表达消息严重度,不是 verdict 的别名 —— passed / failed / errored
106
+ * 任一 verdict 都可以带 cleanup / teardown 诊断。与运行级的 `DiagnosticNotice` 不同,这条挂在单个
107
+ * attempt 结果上、随 `result.json` 落盘。
108
+ */
109
+ export interface DiagnosticRecord {
110
+ code: string;
111
+ level: "warning" | "error";
112
+ message: string;
113
+ phase: LifecyclePhase;
114
+ data?: Readonly<Record<string, JsonValue>>;
115
+ /** 相同 dedupeKey 折叠后的出现次数;省略等于 1。 */
116
+ count?: number;
16
117
  }
17
118
  export interface EvalResult {
18
119
  id: string;
@@ -38,7 +139,12 @@ export interface EvalResult {
38
139
  assertions: AssertionResult[];
39
140
  usage?: Usage;
40
141
  estimatedCostUSD?: number;
41
- error?: string;
142
+ /** 使 attempt 进入 `errored` 的唯一致命执行错误(结构化);榜单显示 `error.message` 一层原因。 */
143
+ error?: AttemptError;
144
+ /** 本 attempt 的诊断(与 verdict 独立);teardown / cleanup 失败等挂在这里,不改判定。 */
145
+ diagnostics?: readonly DiagnosticRecord[];
146
+ /** Runner 阶段计时,按执行顺序;只记录实际发生的阶段(见 docs/feature/results/architecture.md)。 */
147
+ phases?: PhaseTiming[];
42
148
  skipReason?: string;
43
149
  events?: StreamEvent[];
44
150
  /** test 引用到的 eval 源码(按 loc 收集),供 view 渲染 github-diff 式代码视图。 */
@@ -48,7 +154,23 @@ export interface EvalResult {
48
154
  trace?: TraceSpan[];
49
155
  /** 本 attempt 的 agent setup 实际装了什么(Skill / native plugin / MCP / Python plugin);不参与评分。 */
50
156
  agentSetup?: AgentSetupManifest;
51
- diff?: DiffData;
157
+ /**
158
+ * attempt 级聚合的证据覆盖(各 turn 的最差值,unknown/unavailable < partial < complete),
159
+ * 报告据此展示证据覆盖徽标(见 docs/feature/adapters/architecture/evidence.md)。
160
+ */
161
+ coverage?: import("../scoring/coverage.ts").ResolvedCoverage;
162
+ /**
163
+ * 沙箱型 attempt 的执行环境标识:provider 名与实例 id,用于关联 provider 侧日志与留存现场;
164
+ * remote 型 agent 无此字段。`kept` 表示运行收尾时按 --keep-sandbox 留存了沙箱;之后的存活
165
+ * 状态归 `niceeval sandbox list` 回答,本记录一次写成、不回写。
166
+ */
167
+ sandbox?: {
168
+ provider: string;
169
+ sandboxId: string;
170
+ kept?: true;
171
+ };
172
+ /** agent 归因增量:逐 send 窗口的 delta 序列(落盘为 diff.json;文件级视图由读取面派生)。 */
173
+ diff?: DiffArtifact;
52
174
  rawTranscript?: string;
53
175
  /** 携带条目(--resume 合入)专用:artifact 目录(相对结果根目录),指向原快照里的落盘。 */
54
176
  artifactBase?: string;
@@ -63,8 +185,16 @@ export declare const RESULTS_FORMAT = "niceeval.results";
63
185
  * `5`(见 memory 的 attempt-locator-and-source-dedup 条目)= result.json 新增 `locator` 字段;
64
186
  * `sources.json` 从逐 attempt 内联全量内容改为「attempt 级引用 + 快照级 `sources/<sha256>.json`
65
187
  * 去重仓库」,`AttemptHandle.sources()` 的公开返回形状不变(仍是 `SourceArtifact[] | null`)。
188
+ * `6` = `error` 从自由字符串改为结构化 `AttemptError`,并新增有界 `diagnostics`。
189
+ * `8` = 断言记录改 outcome 判别联合(groupPath/optional/expected/received/unavailable+reason);
190
+ * 生命周期词表统一为 LifecyclePhase,`error.operation`/`diagnostics[].operation` 更名 `phase`;
191
+ * 新增 `phases`(阶段计时)、`coverage`(证据覆盖聚合)、`sandbox`(执行环境标识)字段;
192
+ * `ExperimentRunInfo` 改为解析后运行配置的穷尽投影(sandbox 从字符串改结构化投影对象);
193
+ * `diff.json` 落逐窗口 delta 序列(DiffWindow[]);events/trace 的字符串值统一 256 KiB 截断
194
+ * (结构化 `truncated` 标记);snapshot.json 新增发布拷贝的 `publish` 标记。
195
+ * 旧版快照按格式规则整份判为不兼容并在扫描时列为占位条目,不迁移不降级。
66
196
  */
67
- export declare const RESULTS_SCHEMA_VERSION = 5;
197
+ export declare const RESULTS_SCHEMA_VERSION = 8;
68
198
  /** 一次运行的纯运行时内存聚合(reporter 契约用);落盘格式契约在 niceeval/results 的 SnapshotMeta / AttemptRecord,见 docs/feature/results/architecture.md。 */
69
199
  export interface RunSummary {
70
200
  /** 项目名(来自 config.name),透传给 `niceeval view` 顶部 hero 显示。 */
@@ -95,6 +225,17 @@ export interface RunShape {
95
225
  /** 本次运行实际生效的全局并发数(flag/env/config/sandbox 默认值解析后的结果);
96
226
  * 实验级 maxConcurrency 只在该实验内部限流,不改这个全局值。 */
97
227
  maxConcurrency: number;
228
+ /**
229
+ * 本次 invocation 的快照身份锚点(ISO 时间戳),在调度任何 attempt 前确定。fresh
230
+ * `EvalResult.locator` 编码进去的 `snapshotStartedAt`(见 `results/locator.ts` 的
231
+ * `AttemptIdentity`)与 Artifacts writer 写进 `snapshot.json` 的 `startedAt` 共用
232
+ * 同一个值 —— 不同 experiment 在同一次 invocation 内共享它也不会碰撞(locator 身份
233
+ * 还含 experimentId)。`runEvals()` 恒在 `onRunStart` 触发前把它填进这里,这是它
234
+ * 从 run.ts 传给 Artifacts 等 reporter 的唯一途径;省略只出现在测试/第三方手写
235
+ * `RunShape` 的直调场景。见 docs/feature/experiments/cli.md「Locator 必须在
236
+ * result 发布前确定」。
237
+ */
238
+ snapshotStartedAt?: string;
98
239
  }
99
240
  export interface Reporter {
100
241
  onEvent?(event: ReporterEvent): void | Promise<void>;
@@ -104,6 +245,29 @@ export interface Reporter {
104
245
  onEvalComplete?(result: EvalResult): void | Promise<void>;
105
246
  onRunComplete?(summary: RunSummary): void | Promise<void>;
106
247
  }
248
+ /**
249
+ * 内部 reporter 注册项:CLI/runner 给每个 `Reporter` 实例附上「叫什么名字」「失败是否致命」
250
+ * 两条元数据,不改变用户实现 `Reporter` 的公共形状——`Reporter` 接口本身不变,用户只需要实现
251
+ * 上面那四个回调,从不需要知道 `ReporterRegistration` 的存在。`name` 是
252
+ * `reportReporterError()` / `DiagnosticNotice.key` 里 `reporter-error:<name>` 的稳定标识:
253
+ * 同一个 reporter 反复失败折叠成一条诊断、`count` 递增,不同 reporter 各自一条,由这个字段的
254
+ * 取值决定,不是「在哪个回调阶段失败」(onRunStart/onEvalComplete/…)决定——后者只作为
255
+ * 诊断消息里的次要上下文,不参与去重身份。
256
+ *
257
+ * `required` 语义(见 docs/feature/experiments/cli.md「运行完成状态不只看 verdict 计数」):
258
+ * - 默认 Artifacts reporter、CLI 显式 `--json` / `--junit`:`required: true`——它们的产物是
259
+ * agent/CI 读取权威结果的唯一入口,写失败必须让 `RunCompletion` 判红、CI 退出码非零。
260
+ * - 用户 `Config.reporters` / `EvalDef.reporters`:`required: false`——失败只折成一条
261
+ * diagnostic,不影响 completion,也不阻断其它 reporter 收尾或后续 attempt。
262
+ *
263
+ * `target` 是可选的落盘路径(如 `--json`/`--junit` 指定的文件),纯展示 / 排障用途,不参与判定。
264
+ */
265
+ export interface ReporterRegistration {
266
+ reporter: Reporter;
267
+ name: string;
268
+ required: boolean;
269
+ target?: string;
270
+ }
107
271
  export type ReporterEvent = {
108
272
  type: "run:start";
109
273
  evals: {
@@ -153,12 +317,24 @@ export interface EvalDef {
153
317
  timeoutMs?: number;
154
318
  /** 任意附加元数据,原样透传进 EvalResult,不参与调度或打分;供自定义 reporter 消费。 */
155
319
  metadata?: Record<string, unknown>;
320
+ /**
321
+ * 调整 agent diff 的归因排除清单(仅沙箱型;见 docs/feature/eval/README.md):两个数组都是
322
+ * gitignore 风格 glob(workdir 相对)。默认排除 .git/node_modules/构建产物/包管理器缓存;
323
+ * `ignore` 在默认清单上追加排除;`include` 优先级最高,把匹配路径显式加回。
324
+ * 合成规则固定为「默认 ∪ ignore,再被 include 打洞」,清单在分类账锚点时冻结。
325
+ */
326
+ diff?: {
327
+ include?: string[];
328
+ ignore?: string[];
329
+ };
156
330
  /**
157
331
  * eval 级预置:拿到沙箱(已上传 workspace + git 基线 + 装好依赖前)。
158
332
  * 默认命令以非 root 跑(agent 的自然环境);装系统依赖时给 `runCommand` 传 `{ root: true }`
159
333
  * (如 `runCommand("apt-get", ["install", …], { root: true })`),跨 provider 语义一致。
334
+ * 第二个参数是绑定到 `eval.setup` 的窄上下文(`ctx.progress` / `ctx.diagnostic`,
335
+ * 见 docs/feature/eval/README.md);可返回 cleanup 闭包,归因到 `eval.teardown`。
160
336
  */
161
- setup?: (sandbox: Sandbox) => Promise<void | Cleanup> | void | Cleanup;
337
+ setup?: (sandbox: Sandbox, ctx: SandboxHookContext) => Promise<void | Cleanup> | void | Cleanup;
162
338
  /** eval 主体:拿到 TestContext,驱动对话 / 沙箱操作并就地断言。 */
163
339
  test(t: TestContext): Promise<void> | void;
164
340
  }
@@ -189,8 +365,10 @@ export interface ExperimentDef {
189
365
  model?: string;
190
366
  /** 模型推理努力程度(如 "low"/"medium"/"high",取值由具体模型/adapter 决定);省略=用 agent 原生默认。经 ctx.reasoningEffort 透给 adapter 与 eval。 */
191
367
  reasoningEffort?: string;
192
- /** 实验条件(A/B 里的 feature flag),由实验文件声明;经 ctx.flags 透传给 adapter、t.flags 暴露给 eval。 */
193
- flags?: Record<string, unknown>;
368
+ /** 实验条件(A/B 里的 feature flag),由实验文件声明;必须是可 JSON 序列化的值
369
+ * (defineExperiment 解析时校验,非 JSON 直接报错),经 ctx.flags 透传给 adapter、
370
+ * t.flags 暴露给 eval,并原样进入结果快照的 ExperimentRunInfo.flags。 */
371
+ flags?: Record<string, JsonValue>;
194
372
  /** 同一 eval 重复跑几次(结果各计一条 attempt);省略/CLI `--runs` 覆盖时默认 1。 */
195
373
  runs?: number;
196
374
  /** 一次重复(runs > 1)里某次 attempt 失败后是否跳过剩余重复;省略默认 true(提前退出省钱)。 */
@@ -202,9 +380,8 @@ export interface ExperimentDef {
202
380
  /** 覆盖项目级 Config.sandbox,只对这个实验生效。 */
203
381
  sandbox?: SandboxOption;
204
382
  /**
205
- * 本实验的花费上限(USD)。调度器按「已花 + 在飞预估」的护栏口径逼近上限时限流,
206
- * 累计花费到顶后跳过这个实验剩下未起飞的 attempt 并上报一次 `run:budgetExceeded`
207
- * (已在飞的 attempt 仍会跑完)。
383
+ * 本实验的花费上限(USD)。调度器按「已完成 attempt 的实测花费」累计,到顶后跳过这个实验
384
+ * 剩下未起飞的 attempt 并上报一次 `run:budgetExceeded`(已在飞的 attempt 仍会跑完)。
208
385
  */
209
386
  budget?: number;
210
387
  /**
@@ -272,7 +449,16 @@ export interface PriceOverride {
272
449
  /**
273
450
  * 进度行 / 日志里标识一个 run 配置的短名。有 experiment 时用其 basename(唯一,
274
451
  * 能区分同 agent 同 model 的实验变体,如 xxx 与 xxx--agents-md;与汇总表口径一致);
275
- * 无 experiment 时退回 agent/modellive display 以它作行聚合 key,两处必须同源。
452
+ * 无 experiment 时退回 agent/model。现有 live display 以它(拼 evalId)作行聚合 key,
453
+ * 两处必须同源(改这里的格式要同步核对 live.ts 的 key 计算,见 memory 的
454
+ * live-who-key-mismatch-freezes-rows —— 上一次格式改动漏改 live.ts 自己手写的两处曾冻结整表)。
455
+ *
456
+ * 这是展示 label,不是 identity —— 两个不同的 (evalId, attempt) 可能巧合算出同一个 who
457
+ * (同 experiment 同 eval 的第 2 次重试与另一条 eval 的第 1 次重试,展示上都叫同一个 basename)。
458
+ * 反馈系统新的事件/状态(见上面 `AttemptRef` / `AttemptKey` / `encodeAttemptKey`)一律用
459
+ * `{experimentId, evalId, attempt}` 做 identity/Map key,`who` 只作为 `ActiveAttempt.who`
460
+ * 之类的展示字段附着,不参与去重或查找 —— 把展示 label 错当成 identity key 曾经是 live 表格
461
+ * 两个真实 bug 的根因(另见 memory 的 live-rows-fold-experiment-variants)。
276
462
  */
277
463
  export declare function runWho(run: {
278
464
  agentName: string;
@@ -284,7 +470,7 @@ export interface AgentRun {
284
470
  agent: Agent;
285
471
  model?: string;
286
472
  reasoningEffort?: string;
287
- flags: Record<string, unknown>;
473
+ flags: Record<string, JsonValue>;
288
474
  runs: number;
289
475
  earlyExit: boolean;
290
476
  sandbox?: SandboxOption;
@@ -292,6 +478,12 @@ export interface AgentRun {
292
478
  budget?: number;
293
479
  evalFilter: (id: string) => boolean;
294
480
  experimentId?: string;
481
+ /** 实验的一句话描述(ExperimentDef.description),进结果快照的 ExperimentRunInfo。 */
482
+ description?: string;
483
+ /** evals 过滤器的指纹(数组内容 / 函数体哈希),进 ExperimentRunInfo.evalFilterFingerprint。 */
484
+ evalFilterFingerprint?: string;
485
+ /** 本次运行解析后实际选中的 eval id 全集;runEvals 在调度前按 evalFilter 求值填入。 */
486
+ selectedEvalIds?: string[];
295
487
  strict?: boolean;
296
488
  /** 本配置自己的并发上限(来自 ExperimentDef.maxConcurrency):调度器为它单建信号量,
297
489
  * attempt 先过这道闸再占全局并发位;省略则只受全局并发约束。 */
@@ -301,11 +493,24 @@ export interface RunOptions {
301
493
  config: Config;
302
494
  evals: DiscoveredEval[];
303
495
  agentRuns: AgentRun[];
304
- reporters: Reporter[];
496
+ /**
497
+ * `--keep-sandbox` 的留存档位:failed 留 failed/errored(含硬超时的 errored),all 全部留;
498
+ * 省略 = 全部销毁(留存永远是显式选择)。留存决策在 verdict 定稿的收尾点按档位提交,
499
+ * 见 docs/feature/sandbox/architecture.md「留存(keep)与注册表」。
500
+ */
501
+ keepSandbox?: "failed" | "all";
502
+ /** 结果根目录(.niceeval;留存注册表 `.niceeval/sandboxes/` 挂在它下面)。省略 = cwd/.niceeval。 */
503
+ niceevalRoot?: string;
504
+ /**
505
+ * 已注册的 reporter,携带 name/required 元数据(见 `ReporterRegistration`)。这是内部编排
506
+ * 通道——调用方(今天只有 `cli.ts`)按来源(默认 artifacts / 显式 --json·--junit / 用户
507
+ * `Config.reporters`)把裸 `Reporter` 各自包一层元数据后传进来;eval 级 `EvalDef.reporters`
508
+ * 不经过这里,由 `runEvals()` 自己按 `scopeReporter()` 包装、统一记作 `required: false`
509
+ *(见 run.ts 的 scopedSets 处理)。
510
+ */
511
+ reporters: ReporterRegistration[];
305
512
  maxConcurrency: number;
306
513
  signal?: AbortSignal;
307
- /** TTY live display 的进度回调;设置后 attempt 的 log 消息路由到它而不是 stderr。 */
308
- onProgress?: (evalId: string, who: string, msg: string) => void;
309
514
  /** 上次运行的结果。verdict 为 passed/failed 的 (experimentId, evalId) 组合跳过重跑,结果直接合入本次汇总。 */
310
515
  priorResults?: EvalResult[];
311
516
  /**
@@ -329,4 +534,290 @@ export interface Attempt {
329
534
  /** agent+model+evalId,用于首过即停。 */
330
535
  key: string;
331
536
  fingerprint: string;
537
+ /**
538
+ * 构造 fresh attempt plan 时即算好的 Attempt 定位符(不是完成后写回):由 invocation 的
539
+ * snapshotStartedAt 与 attempt 身份派生,贯穿执行、留存登记与落盘——登记项、run 收尾反馈与
540
+ * result.json 从第一次写入起就用同一个值。裸 run(无 experimentId)不产出。
541
+ */
542
+ locator?: AttemptLocator;
543
+ }
544
+ /** 三种最终反馈 profile。`--output auto` 只是 CLI flag 的输入值,解析后必然落在这三者之一。 */
545
+ export type OutputProfile = "human" | "agent" | "ci";
546
+ /**
547
+ * 反馈系统里一次 attempt 的稳定身份:reducer 用它做 active map 的 key、事件的关联字段。
548
+ * 只含调度身份三元组 —— 不含 agent/model/展示 label(那是 `who`,来自 `runWho()`,
549
+ * 见该函数注释:展示 label 不能当 identity key 用,folding 两个不同 config 到同一个 key
550
+ * 曾经就是 live 表格两个真实 bug 的根因),也不含 `AttemptLocator` 需要的
551
+ * `snapshotStartedAt`(那是落盘身份,由 `results/locator.ts` 的 `AttemptIdentity` 独立管理;
552
+ * 完成/failure 事件在 locator 确定后直接携带派生好的 `AttemptLocator` 字符串,反馈层
553
+ * 不重新推导身份 —— 两个同名概念的 identity 类型故意不同名,以免和落盘身份互相看错)。
554
+ */
555
+ export interface AttemptRef {
556
+ /** 未挂靠 experiment 时为 undefined(直接指定 agent/model 跑,不经过 experiment);不用空字符串占位。 */
557
+ experimentId?: string;
558
+ evalId: string;
559
+ /** 0-indexed,与 `EvalResult.attempt` / `AttemptLocator` 的 attempt 同一口径。 */
560
+ attempt: number;
332
561
  }
562
+ /** `AttemptRef` 的确定性字符串编码,只作 `RunFeedbackState.active` 的 Map key 使用 ——
563
+ * 不是展示文本(那是 `who`),也不是 `AttemptLocator`(那需要额外的 `snapshotStartedAt`)。 */
564
+ export type AttemptKey = string & {
565
+ readonly __brand: "AttemptKey";
566
+ };
567
+ /** 由 `AttemptRef` 派生 `AttemptKey`;同一身份永远编码出同一个 key。 */
568
+ export declare function encodeAttemptKey(ref: AttemptRef): AttemptKey;
569
+ /**
570
+ * dashboard 当前可见的一个 active slot。`phase` 是正式状态,`detail` 只是该 phase 下的次要文本
571
+ *(如 `running` 阶段的 `tool: shell` / `turn 2`)—— 两者是两个字段,不把 adapter 的 raw progress
572
+ * string 直接当状态用;phase 变化时 `detail` 清空(旧阶段的次要文本不该残留到新阶段)。
573
+ */
574
+ export interface ActiveAttempt {
575
+ identity: AttemptRef;
576
+ /** 展示 label,等价 `runWho()` 的结果;渲染要用,但绝不作为 identity/key。 */
577
+ who: string;
578
+ phase: LifecyclePhase;
579
+ /** 进入当前 phase 的墙钟时间(epoch ms),用于渲染阶段耗时;每次 phase 变化都会更新。 */
580
+ phaseStartedAt: number;
581
+ detail?: string;
582
+ }
583
+ /**
584
+ * 一次失败/错误的永久通知:human 撤下 dashboard 后追加一行、agent/ci 立即追加一行,都读它。
585
+ * 字段全部结构化(locator / identity / verdict / phase 都是具名字段),profile renderer 不需要
586
+ * 解析 `reason` 之外的任何文本就能拼出机器可读的输出。
587
+ */
588
+ export interface FailureDetail {
589
+ locator: AttemptLocator;
590
+ identity: AttemptRef;
591
+ who: string;
592
+ verdict: "failed" | "errored";
593
+ /** 一层可行动摘要(gate 断言名、error 消息……),不是完整 stack/transcript;详情走 `niceeval show`。 */
594
+ reason: string;
595
+ /** failed / assertion-unavailable 时的结构化主断言摘要;机器 renderer 直接读字段。 */
596
+ assertion?: PrimaryAssertionSummary;
597
+ /** 仅 errored 使用:结构化执行错误发生时所在的阶段。failed 是断言 outcome,不带 phase。 */
598
+ phase?: LifecyclePhase;
599
+ }
600
+ /** 带发生时间的失败通知;复用失败以 FailureDetail 静态进入 plan,不伪装成刚发生的事件。 */
601
+ export interface FailureNotice extends FailureDetail {
602
+ at: number;
603
+ }
604
+ /**
605
+ * 去重后的诊断通知(warning/error):相同 `key` 的诊断只保留一条,`count` 累加受影响次数
606
+ *(见 docs/feature/experiments/cli.md「什么动态更新,什么逐条追加」的去重规则)。
607
+ * `data` 携带结构化字段(如 budget 的 experimentId/spent/unstarted),agent/ci 直接读取,
608
+ * 不解析 `message`(`message` 只是 human 展示用的一句话)。
609
+ */
610
+ export interface DiagnosticNotice {
611
+ at: number;
612
+ key: string;
613
+ severity: "warning" | "error";
614
+ message: string;
615
+ /** 相同 key 累计出现的次数,由 reducer 去重时递增。 */
616
+ count: number;
617
+ identity?: AttemptRef;
618
+ data?: Readonly<Record<string, JsonValue>>;
619
+ }
620
+ /** 运行完整性结论,独立于 verdict 计数。CI 退出码不能只看 failed/errored ——
621
+ * budget 未覆盖全部计划、用户中断、required reporter 失败都必须让 completion 非「complete」。 */
622
+ export type CompletionStatus = "complete" | "incomplete" | "interrupted";
623
+ /** 一个 reporter 收尾失败的记录;`required` 区分它是否让 completion 判红(见 ReporterRegistration)。 */
624
+ export interface ReporterError {
625
+ reporter: string;
626
+ required: boolean;
627
+ message: string;
628
+ }
629
+ export interface RunCompletion {
630
+ status: CompletionStatus;
631
+ /** budget 耗尽导致未派发的 attempt 数;不含首过即停省略的次数(见 `earlyExitUnstarted`)。 */
632
+ unstarted: number;
633
+ /** 首过即停在已知 verdict 下主动省略的计划次数 —— 这是「省下的重复验证」,不是「未完整覆盖」。 */
634
+ earlyExitUnstarted: number;
635
+ reporterErrors: readonly ReporterError[];
636
+ }
637
+ /**
638
+ * 事件 → 状态的纯 reducer 产出(见 `src/runner/feedback/reducer.ts`)。所有计数、active map、
639
+ * cost 累计、failure/diagnostic 去重都只在 reducer 里算一次;三种 profile 的 renderer 只读取
640
+ * 这份状态,不各自维护第二份推导。
641
+ *
642
+ * `total = reused + running + queued + completed` 在处理完每一个事件之后都成立,是 reducer 的
643
+ * 不变量(见 reducer.test.ts 的表驱动用例,每一步都断言,不只在流程末尾断言一次)。
644
+ */
645
+ export interface RunFeedbackState {
646
+ total: number;
647
+ reused: number;
648
+ running: number;
649
+ queued: number;
650
+ completed: number;
651
+ /** attempt:early-exit 事件的累计次数(首过即停省略 + fail-fast 未派发;后者由 fail-fast
652
+ * diagnostic 的 count 单独区分,见 cli.ts 的 assembleRunCompletion)。 */
653
+ earlyExitSkipped: number;
654
+ elapsedMs: number;
655
+ /** 仅本次实际派发 attempt 的 token;carry 结果的历史 usage 不进入这里。 */
656
+ newTokenCount?: number;
657
+ estimatedCostUSD?: number;
658
+ active: ReadonlyMap<AttemptKey, ActiveAttempt>;
659
+ failures: readonly FailureNotice[];
660
+ /** 本次实际派发后产生的去重失败数;复用失败不消耗 profile 的流式输出上限。 */
661
+ freshFailureCount: number;
662
+ diagnostics: readonly DiagnosticNotice[];
663
+ /** 留存授予的沙箱(--keep-sandbox);run 摘要后各 profile 追加输出。 */
664
+ kept: readonly KeptNotice[];
665
+ }
666
+ /** 一条留存授予的永久通知(见 docs/feature/sandbox/cli.md「run 收尾输出」)。 */
667
+ export interface KeptNotice {
668
+ at: number;
669
+ locator: AttemptLocator;
670
+ identity: AttemptRef;
671
+ who: string;
672
+ verdict: Verdict;
673
+ provider: string;
674
+ sandboxId: string;
675
+ enter?: string;
676
+ }
677
+ /** 一次 run 的初始计划。复用只暴露数量;失败明细仅用于静态初始化终局清单。 */
678
+ export interface RunFeedbackPlan {
679
+ shape: RunShape;
680
+ /** 携入(carry)结果数,直接计入 `RunFeedbackState.reused`,不需要重新调度。 */
681
+ reused: number;
682
+ /** 复用结果中的失败;plan 时静态注入,不产生“刚发生”的失败事件。 */
683
+ reusedFailures?: readonly FailureDetail[];
684
+ }
685
+ /**
686
+ * 只影响 dashboard 当前帧、reducer 不为它保留历史的事件:新值使旧值失去意义,所以覆盖而不是
687
+ * 追加(见 docs/feature/experiments/cli.md「什么动态更新,什么逐条追加」的判断标准)。
688
+ * `attempt:early-exit` 同样折进这一组 —— 它不打印永久行,只把已知 verdict 的省略次数收进
689
+ * `completed`(见 reducer 实现)。
690
+ */
691
+ export type AttemptLifecycleEvent = {
692
+ type: "attempt:queued";
693
+ at: number;
694
+ identity: AttemptRef;
695
+ who: string;
696
+ } | {
697
+ type: "attempt:start";
698
+ at: number;
699
+ identity: AttemptRef;
700
+ who: string;
701
+ phase: LifecyclePhase;
702
+ } | {
703
+ type: "attempt:phase";
704
+ at: number;
705
+ identity: AttemptRef;
706
+ phase: LifecyclePhase;
707
+ } | {
708
+ type: "attempt:progress";
709
+ at: number;
710
+ identity: AttemptRef;
711
+ detail: string;
712
+ } | {
713
+ type: "attempt:complete";
714
+ at: number;
715
+ identity: AttemptRef;
716
+ who: string;
717
+ verdict: Verdict;
718
+ /** 本次 attempt 的输入 + 输出 token;缺失表示 provider 未报告。 */
719
+ tokenCount?: number;
720
+ estimatedCostUSD?: number;
721
+ } | {
722
+ type: "attempt:early-exit";
723
+ at: number;
724
+ identity: AttemptRef;
725
+ who: string;
726
+ };
727
+ /**
728
+ * 运行级时钟 tick:唯一允许更新 `RunFeedbackState.elapsedMs` 的事件,由 coordinator 的定时器产出
729
+ *(见 plan 的可注入 `FeedbackIO` clock)。reducer 保持纯函数,不自己读 `Date.now()`,elapsedMs
730
+ * 因此只能通过事件携带的值前进 —— 这也让 reducer 测试可以喂任意 elapsed 值,不必真的等待。
731
+ */
732
+ export interface FeedbackTickEvent {
733
+ type: "tick";
734
+ at: number;
735
+ elapsedMs: number;
736
+ }
737
+ /**
738
+ * 永久事件:human 撤下 dashboard 后追加一行、agent 按 envelope 追加、ci 按 stdout 事件追加,
739
+ * 一旦发生就不会被后续状态覆盖掉(与上面按当前帧覆盖的 `AttemptLifecycleEvent` 相对)。
740
+ * 字段全部结构化,profile renderer 不解析 `message` 之外的任何文本、不解析 i18n 字符串。
741
+ */
742
+ export type DurableFeedbackEvent = {
743
+ type: "plan";
744
+ at: number;
745
+ plan: RunFeedbackPlan;
746
+ } | {
747
+ type: "failure";
748
+ at: number;
749
+ locator: AttemptLocator;
750
+ identity: AttemptRef;
751
+ who: string;
752
+ verdict: "failed" | "errored";
753
+ reason: string;
754
+ assertion?: PrimaryAssertionSummary;
755
+ phase?: LifecyclePhase;
756
+ } | {
757
+ type: "diagnostic";
758
+ at: number;
759
+ key: string;
760
+ severity: "warning" | "error";
761
+ message: string;
762
+ identity?: AttemptRef;
763
+ data?: Readonly<Record<string, JsonValue>>;
764
+ }
765
+ /**
766
+ * emitter 对每一个因 budget 到顶而不派发的 attempt 各发一次(与 `attempt:early-exit` 同构,
767
+ * 见 reducer 实现);`unstarted` 是 emitter 自己记的、发出这条时的累计未派发数,写进
768
+ * `DiagnosticNotice.data` 供 agent/ci 直接读取,不是 reducer 用来计算「这次要挪多少」的输入
769
+ *(reducer 只按事件触发次数折算,保持纯函数不需要额外记住上一次的值)。
770
+ */
771
+ | {
772
+ type: "budget-exhausted";
773
+ at: number;
774
+ experimentId: string;
775
+ spent: number;
776
+ unstarted: number;
777
+ }
778
+ /** 一次留存授予(--keep-sandbox):run 摘要后三种 profile 都追加输出(见 docs/feature/sandbox/cli.md)。 */
779
+ | {
780
+ type: "kept";
781
+ at: number;
782
+ locator: AttemptLocator;
783
+ identity: AttemptRef;
784
+ who: string;
785
+ verdict: Verdict;
786
+ provider: string;
787
+ sandboxId: string;
788
+ enter?: string;
789
+ } | {
790
+ type: "interrupted";
791
+ at: number;
792
+ } | {
793
+ type: "reporter-error";
794
+ at: number;
795
+ reporter: string;
796
+ required: boolean;
797
+ message: string;
798
+ } | {
799
+ type: "summary";
800
+ at: number;
801
+ summary: RunSummary;
802
+ completion: RunCompletion;
803
+ } | {
804
+ type: "saved";
805
+ at: number;
806
+ /** 本次 invocation 实际落盘的快照结果路径。不含 `--json`/`--junit` 聚合文件——那两个由
807
+ * `json`/`junit` 两个独立字段单独携带,而不是塞进这个数组后靠猜文件后缀去反推「哪一个
808
+ * 是聚合报告、哪些是快照目录」;CI 的 result 收尾需要把 `json=`/`junit=`/`snapshots=`
809
+ * 打成三条独立的行(见 docs/feature/experiments/cli.md「CI 怎么用」的字面例子),
810
+ * 结构化字段让它不需要解析路径字符串就能做到。 */
811
+ paths: readonly string[];
812
+ /** 实际写出的 `--json` 聚合报告路径。未传 `--json`,或写入失败(见 required reporter
813
+ * 语义),都省略这个字段——省略表示「不打印这一行」,不是打印一个空路径。 */
814
+ json?: string;
815
+ /** 实际写出的 `--junit` 聚合报告路径,语义同 `json`。 */
816
+ junit?: string;
817
+ };
818
+ /**
819
+ * runner → feedback coordinator 的内部事件通道,与公共 `Reporter` / `ReporterEvent` 分开:
820
+ * profile renderer 只消费这里的具名字段,不解析 `ReporterEvent` 里的 i18n 文案或表格列宽
821
+ *(见 docs/feature/experiments/cli.md「输出流和落盘节奏」)。
822
+ */
823
+ export type RunFeedbackEvent = AttemptLifecycleEvent | FeedbackTickEvent | DurableFeedbackEvent;