niceeval 0.13.1 → 0.13.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 (548) hide show
  1. package/INDEX.md +25 -2
  2. package/README.md +9 -10
  3. package/README.zh.md +9 -10
  4. package/dist/agents/ai-sdk.cjs.map +1 -1
  5. package/dist/agents/ai-sdk.d.cts +1 -1
  6. package/dist/agents/ai-sdk.d.mts +1 -1
  7. package/dist/agents/ai-sdk.d.ts +1 -1
  8. package/dist/agents/bub-install-spec.cjs +19 -5
  9. package/dist/agents/bub-install-spec.cjs.map +1 -1
  10. package/dist/agents/bub-install-spec.d.cts +9 -2
  11. package/dist/agents/bub-install-spec.d.mts +9 -2
  12. package/dist/agents/bub-install-spec.d.ts +9 -2
  13. package/dist/agents/bub-install-spec.js +14 -12
  14. package/dist/agents/bub.cjs +10 -6
  15. package/dist/agents/bub.cjs.map +1 -1
  16. package/dist/agents/bub.d.cts +2 -2
  17. package/dist/agents/bub.d.mts +2 -2
  18. package/dist/agents/bub.d.ts +2 -2
  19. package/dist/agents/claude-code.cjs.map +1 -1
  20. package/dist/agents/claude-code.d.cts +2 -2
  21. package/dist/agents/claude-code.d.mts +2 -2
  22. package/dist/agents/claude-code.d.ts +2 -2
  23. package/dist/agents/codex.cjs +4 -4
  24. package/dist/agents/codex.cjs.map +1 -1
  25. package/dist/agents/codex.d.cts +3 -3
  26. package/dist/agents/codex.d.mts +3 -3
  27. package/dist/agents/codex.d.ts +3 -3
  28. package/dist/agents/coding-cli-versions.cjs +10 -1
  29. package/dist/agents/coding-cli-versions.cjs.map +1 -1
  30. package/dist/agents/coding-cli-versions.d.cts +8 -3
  31. package/dist/agents/coding-cli-versions.d.mts +8 -3
  32. package/dist/agents/coding-cli-versions.d.ts +8 -3
  33. package/dist/agents/coding-cli-versions.js +18 -12
  34. package/dist/agents/deepseek-harness.cjs +151 -0
  35. package/dist/agents/deepseek-harness.cjs.map +1 -0
  36. package/dist/agents/deepseek-harness.d.cts +14 -0
  37. package/dist/agents/deepseek-harness.d.mts +14 -0
  38. package/dist/agents/deepseek-harness.d.ts +14 -0
  39. package/dist/agents/deepseek-harness.js +7 -0
  40. package/dist/agents/deepseek-harness.js.map +1 -0
  41. package/dist/agents/exact-npm-plugins.cjs +28 -0
  42. package/dist/agents/exact-npm-plugins.cjs.map +1 -0
  43. package/dist/agents/exact-npm-plugins.d.cts +10 -0
  44. package/dist/agents/exact-npm-plugins.d.mts +10 -0
  45. package/dist/agents/exact-npm-plugins.d.ts +10 -0
  46. package/dist/agents/exact-npm-plugins.js +8 -0
  47. package/dist/agents/exact-npm-plugins.js.map +1 -0
  48. package/dist/agents/index.cjs +5 -1
  49. package/dist/agents/index.cjs.map +1 -1
  50. package/dist/agents/index.d.cts +4 -0
  51. package/dist/agents/index.d.mts +4 -0
  52. package/dist/agents/index.d.ts +4 -0
  53. package/dist/agents/index.js +54 -50
  54. package/dist/agents/index.mjs +54 -50
  55. package/dist/agents/omp.cjs +170 -0
  56. package/dist/agents/omp.cjs.map +1 -0
  57. package/dist/agents/omp.d.cts +14 -0
  58. package/dist/agents/omp.d.mts +14 -0
  59. package/dist/agents/omp.d.ts +14 -0
  60. package/dist/agents/omp.js +7 -0
  61. package/dist/agents/omp.js.map +1 -0
  62. package/dist/agents/openclaw.cjs +51 -2
  63. package/dist/agents/openclaw.cjs.map +1 -1
  64. package/dist/agents/openclaw.d.cts +2 -0
  65. package/dist/agents/openclaw.d.mts +2 -0
  66. package/dist/agents/openclaw.d.ts +2 -0
  67. package/dist/agents/types.cjs.map +1 -1
  68. package/dist/agents/types.d.cts +4 -4
  69. package/dist/agents/types.d.mts +4 -4
  70. package/dist/agents/types.d.ts +4 -4
  71. package/dist/agents/ui-message-stream.cjs +24 -7
  72. package/dist/agents/ui-message-stream.cjs.map +1 -1
  73. package/dist/analysis/api.cjs +28 -2
  74. package/dist/analysis/api.cjs.map +1 -1
  75. package/dist/analysis/api.d.cts +22 -1
  76. package/dist/analysis/api.d.mts +22 -1
  77. package/dist/analysis/api.d.ts +22 -1
  78. package/dist/analysis/api.js +8 -6
  79. package/dist/analysis/bindings.cjs +11 -18
  80. package/dist/analysis/bindings.cjs.map +1 -1
  81. package/dist/analysis/bindings.d.cts +177 -49
  82. package/dist/analysis/bindings.d.mts +177 -49
  83. package/dist/analysis/bindings.d.ts +177 -49
  84. package/dist/analysis/contracts.cjs +1 -0
  85. package/dist/analysis/contracts.cjs.map +1 -1
  86. package/dist/analysis/contracts.d.cts +43 -3
  87. package/dist/analysis/contracts.d.mts +43 -3
  88. package/dist/analysis/contracts.d.ts +43 -3
  89. package/dist/analysis/cost-projection.cjs +2 -1
  90. package/dist/analysis/cost-projection.cjs.map +1 -1
  91. package/dist/analysis/cost-projection.d.cts +1 -1
  92. package/dist/analysis/cost-projection.d.mts +1 -1
  93. package/dist/analysis/cost-projection.d.ts +1 -1
  94. package/dist/analysis/cost.cjs +13 -3
  95. package/dist/analysis/cost.cjs.map +1 -1
  96. package/dist/analysis/cost.d.cts +10 -3
  97. package/dist/analysis/cost.d.mts +10 -3
  98. package/dist/analysis/cost.d.ts +10 -3
  99. package/dist/analysis/experiment-groups.cjs +96 -0
  100. package/dist/analysis/experiment-groups.cjs.map +1 -0
  101. package/dist/analysis/experiment-groups.d.cts +9 -0
  102. package/dist/analysis/experiment-groups.d.mts +9 -0
  103. package/dist/analysis/experiment-groups.d.ts +9 -0
  104. package/dist/analysis/experiment-groups.js +10 -0
  105. package/dist/analysis/experiment-groups.js.map +1 -0
  106. package/dist/analysis/index.cjs +4 -1
  107. package/dist/analysis/index.cjs.map +1 -1
  108. package/dist/analysis/index.d.cts +4 -2
  109. package/dist/analysis/index.d.mts +4 -2
  110. package/dist/analysis/index.d.ts +4 -2
  111. package/dist/analysis/index.js +40 -36
  112. package/dist/analysis/index.mjs +40 -36
  113. package/dist/assertions/limits.cjs +4 -0
  114. package/dist/assertions/limits.cjs.map +1 -1
  115. package/dist/assertions/limits.d.cts +4 -0
  116. package/dist/assertions/limits.d.mts +4 -0
  117. package/dist/assertions/limits.d.ts +4 -0
  118. package/dist/assertions/match.cjs +63 -25
  119. package/dist/assertions/match.cjs.map +1 -1
  120. package/dist/assertions/match.d.cts +7 -0
  121. package/dist/assertions/match.d.mts +7 -0
  122. package/dist/assertions/match.d.ts +7 -0
  123. package/dist/assertions/record/attachment.cjs +32 -4
  124. package/dist/assertions/record/attachment.cjs.map +1 -1
  125. package/dist/assertions/record/diff.cjs +10 -4
  126. package/dist/assertions/record/diff.cjs.map +1 -1
  127. package/dist/assertions/record/producer.cjs +101 -4
  128. package/dist/assertions/record/producer.cjs.map +1 -1
  129. package/dist/assertions/record/producer.d.cts +4 -1
  130. package/dist/assertions/record/producer.d.mts +4 -1
  131. package/dist/assertions/record/producer.d.ts +4 -1
  132. package/dist/assertions/runtime.cjs +45 -5
  133. package/dist/assertions/runtime.cjs.map +1 -1
  134. package/dist/cli/record.cjs +113 -8
  135. package/dist/cli/record.cjs.map +1 -1
  136. package/dist/cli/record.d.cts +20 -1
  137. package/dist/cli/record.d.mts +20 -1
  138. package/dist/cli/record.d.ts +20 -1
  139. package/dist/cli/record.js +4 -2
  140. package/dist/cli.cjs +386 -31
  141. package/dist/cli.cjs.map +1 -1
  142. package/dist/cli.d.cts +10 -2
  143. package/dist/cli.d.mts +10 -2
  144. package/dist/cli.d.ts +10 -2
  145. package/dist/context/send-retry.cjs +4 -4
  146. package/dist/context/send-retry.cjs.map +1 -1
  147. package/dist/coordination/host/runtime.cjs +8 -30
  148. package/dist/coordination/host/runtime.cjs.map +1 -1
  149. package/dist/coordination/host/runtime.d.cts +3 -3
  150. package/dist/coordination/host/runtime.d.mts +3 -3
  151. package/dist/coordination/host/runtime.d.ts +3 -3
  152. package/dist/coordination/host/types.cjs.map +1 -1
  153. package/dist/coordination/host/types.d.cts +0 -5
  154. package/dist/coordination/host/types.d.mts +0 -5
  155. package/dist/coordination/host/types.d.ts +0 -5
  156. package/dist/coordination/platform/node.cjs +90 -9
  157. package/dist/coordination/platform/node.cjs.map +1 -1
  158. package/dist/define.cjs +25 -1
  159. package/dist/define.cjs.map +1 -1
  160. package/dist/experiment/host/index.cjs +1 -0
  161. package/dist/experiment/host/index.cjs.map +1 -1
  162. package/dist/i18n/en.cjs +26 -8
  163. package/dist/i18n/en.cjs.map +1 -1
  164. package/dist/i18n/en.d.cts +19 -6
  165. package/dist/i18n/en.d.mts +19 -6
  166. package/dist/i18n/en.d.ts +19 -6
  167. package/dist/index.cjs.map +1 -1
  168. package/dist/index.d.cts +1 -1
  169. package/dist/index.d.mts +1 -1
  170. package/dist/index.d.ts +1 -1
  171. package/dist/o11y/parsers/hermes.cjs +1 -1
  172. package/dist/o11y/parsers/hermes.cjs.map +1 -1
  173. package/dist/o11y/prices.json +1 -1
  174. package/dist/o11y/record/families.cjs +12 -3
  175. package/dist/o11y/record/families.cjs.map +1 -1
  176. package/dist/o11y/record/families.d.cts +8 -8
  177. package/dist/o11y/record/families.d.mts +8 -8
  178. package/dist/o11y/record/families.d.ts +8 -8
  179. package/dist/o11y/record/family-projectors.d.cts +66 -18
  180. package/dist/o11y/record/family-projectors.d.mts +66 -18
  181. package/dist/o11y/record/family-projectors.d.ts +66 -18
  182. package/dist/o11y/record/family-writers.d.cts +11 -3
  183. package/dist/o11y/record/family-writers.d.mts +11 -3
  184. package/dist/o11y/record/family-writers.d.ts +11 -3
  185. package/dist/o11y/record/runner-producer.cjs +14 -4
  186. package/dist/o11y/record/runner-producer.cjs.map +1 -1
  187. package/dist/record/codec/core.d.cts +2 -2
  188. package/dist/record/codec/core.d.mts +2 -2
  189. package/dist/record/codec/core.d.ts +2 -2
  190. package/dist/record/family/catalog.d.cts +44 -12
  191. package/dist/record/family/catalog.d.mts +44 -12
  192. package/dist/record/family/catalog.d.ts +44 -12
  193. package/dist/record/family/index.js +128 -124
  194. package/dist/record/family/observability-v1.cjs +35 -0
  195. package/dist/record/family/observability-v1.cjs.map +1 -0
  196. package/dist/record/family/observability-v1.d.cts +3 -0
  197. package/dist/record/family/observability-v1.d.mts +3 -0
  198. package/dist/record/family/observability-v1.d.ts +3 -0
  199. package/dist/record/family/observability-v1.js +6 -0
  200. package/dist/record/family/observability-v1.js.map +1 -0
  201. package/dist/record/family/observability.cjs +58 -9
  202. package/dist/record/family/observability.cjs.map +1 -1
  203. package/dist/record/family/observability.d.cts +1014 -15
  204. package/dist/record/family/observability.d.mts +1014 -15
  205. package/dist/record/family/observability.d.ts +1014 -15
  206. package/dist/record/family/observability.js +22 -18
  207. package/dist/record/host/runtime.cjs +600 -31
  208. package/dist/record/host/runtime.cjs.map +1 -1
  209. package/dist/record/host/types.cjs.map +1 -1
  210. package/dist/record/host/types.d.cts +33 -9
  211. package/dist/record/host/types.d.mts +33 -9
  212. package/dist/record/host/types.d.ts +33 -9
  213. package/dist/record/maintenance/runtime.cjs +1 -2
  214. package/dist/record/maintenance/runtime.cjs.map +1 -1
  215. package/dist/record/model/core.cjs.map +1 -1
  216. package/dist/record/model/core.d.cts +1 -1
  217. package/dist/record/model/core.d.mts +1 -1
  218. package/dist/record/model/core.d.ts +1 -1
  219. package/dist/record/model/definition.d.cts +6 -6
  220. package/dist/record/model/definition.d.mts +6 -6
  221. package/dist/record/model/definition.d.ts +6 -6
  222. package/dist/record/model/identifiers.cjs +1 -1
  223. package/dist/record/model/identifiers.cjs.map +1 -1
  224. package/dist/record/model/identifiers.d.cts +1 -1
  225. package/dist/record/model/identifiers.d.mts +1 -1
  226. package/dist/record/model/identifiers.d.ts +1 -1
  227. package/dist/record/platform/node.cjs +159 -23
  228. package/dist/record/platform/node.cjs.map +1 -1
  229. package/dist/record/platform/services.cjs.map +1 -1
  230. package/dist/record/platform/services.d.cts +15 -2
  231. package/dist/record/platform/services.d.mts +15 -2
  232. package/dist/record/platform/services.d.ts +15 -2
  233. package/dist/record/reader/errors.cjs +37 -1
  234. package/dist/record/reader/errors.cjs.map +1 -1
  235. package/dist/record/reader/errors.d.cts +49 -1
  236. package/dist/record/reader/errors.d.mts +49 -1
  237. package/dist/record/reader/errors.d.ts +49 -1
  238. package/dist/record/reader/errors.js +22 -12
  239. package/dist/record/reader/format.cjs +76 -17
  240. package/dist/record/reader/format.cjs.map +1 -1
  241. package/dist/record/reader/format.d.cts +11 -4
  242. package/dist/record/reader/format.d.mts +11 -4
  243. package/dist/record/reader/format.d.ts +11 -4
  244. package/dist/record/reader/format.js +2 -0
  245. package/dist/record/reader/index.js +32 -18
  246. package/dist/record/reader/runtime.cjs +64 -0
  247. package/dist/record/reader/runtime.cjs.map +1 -1
  248. package/dist/record/reader/runtime.d.cts +15 -0
  249. package/dist/record/reader/runtime.d.mts +15 -0
  250. package/dist/record/reader/runtime.d.ts +15 -0
  251. package/dist/record/reader/runtime.js +2 -0
  252. package/dist/report/assets/styles.css +53 -29
  253. package/dist/report/built-in/analysis-values.cjs +21 -13
  254. package/dist/report/built-in/analysis-values.cjs.map +1 -1
  255. package/dist/report/built-in/analysis-values.d.cts +11 -22
  256. package/dist/report/built-in/analysis-values.d.mts +11 -22
  257. package/dist/report/built-in/analysis-values.d.ts +11 -22
  258. package/dist/report/built-in/analysis-values.js +4 -6
  259. package/dist/report/built-in/index.cjs +1 -3
  260. package/dist/report/built-in/index.cjs.map +1 -1
  261. package/dist/report/built-in/index.d.cts +1 -1
  262. package/dist/report/built-in/index.d.mts +1 -1
  263. package/dist/report/built-in/index.d.ts +1 -1
  264. package/dist/report/built-in/index.js +4 -8
  265. package/dist/report/built-in/index.mjs +4 -8
  266. package/dist/report/built-in/machine.cjs +0 -2
  267. package/dist/report/built-in/machine.cjs.map +1 -1
  268. package/dist/report/built-in/machine.d.cts +0 -2
  269. package/dist/report/built-in/machine.d.mts +0 -2
  270. package/dist/report/built-in/machine.d.ts +0 -2
  271. package/dist/report/built-in/result-components.cjs +49 -51
  272. package/dist/report/built-in/result-components.cjs.map +1 -1
  273. package/dist/report/built-in/result-components.d.cts +16 -17
  274. package/dist/report/built-in/result-components.d.mts +16 -17
  275. package/dist/report/built-in/result-components.d.ts +16 -17
  276. package/dist/report/built-in/result-components.js +0 -4
  277. package/dist/report/built-in/run-membership-overview.cjs +126 -17
  278. package/dist/report/built-in/run-membership-overview.cjs.map +1 -1
  279. package/dist/report/built-in/run-membership-overview.d.cts +14 -0
  280. package/dist/report/built-in/run-membership-overview.d.mts +14 -0
  281. package/dist/report/built-in/run-membership-overview.d.ts +14 -0
  282. package/dist/report/built-in/run-membership-overview.js +4 -2
  283. package/dist/report/built-in/standard.cjs +80 -45
  284. package/dist/report/built-in/standard.cjs.map +1 -1
  285. package/dist/report/built-in/standard.d.cts +19 -41
  286. package/dist/report/built-in/standard.d.mts +19 -41
  287. package/dist/report/built-in/standard.d.ts +19 -41
  288. package/dist/report/built-in/standard.js +6 -6
  289. package/dist/report/components/attempt-detail/compute.cjs +78 -2
  290. package/dist/report/components/attempt-detail/compute.cjs.map +1 -1
  291. package/dist/report/components/attempt-detail/compute.d.cts +1 -1
  292. package/dist/report/components/attempt-detail/compute.d.mts +1 -1
  293. package/dist/report/components/attempt-detail/compute.d.ts +1 -1
  294. package/dist/report/components/attempt-detail/content.cjs +1 -1
  295. package/dist/report/components/attempt-detail/content.cjs.map +1 -1
  296. package/dist/report/components/entity-lists/compute.cjs +42 -5
  297. package/dist/report/components/entity-lists/compute.cjs.map +1 -1
  298. package/dist/report/components/entity-lists/compute.d.cts +10 -8
  299. package/dist/report/components/entity-lists/compute.d.mts +10 -8
  300. package/dist/report/components/entity-lists/compute.d.ts +10 -8
  301. package/dist/report/components/entity-lists/content.cjs +31 -6
  302. package/dist/report/components/entity-lists/content.cjs.map +1 -1
  303. package/dist/report/components/entity-lists/index.cjs +9 -4
  304. package/dist/report/components/entity-lists/index.cjs.map +1 -1
  305. package/dist/report/components/entity-lists/index.d.cts +4 -7
  306. package/dist/report/components/entity-lists/index.d.mts +4 -7
  307. package/dist/report/components/entity-lists/index.d.ts +4 -7
  308. package/dist/report/components/summaries/compute.cjs +8 -1
  309. package/dist/report/components/summaries/compute.cjs.map +1 -1
  310. package/dist/report/components/summaries/compute.d.cts +9 -1
  311. package/dist/report/components/summaries/compute.d.mts +9 -1
  312. package/dist/report/components/summaries/compute.d.ts +9 -1
  313. package/dist/report/components/summaries/index.cjs +48 -4
  314. package/dist/report/components/summaries/index.cjs.map +1 -1
  315. package/dist/report/components/summaries/index.d.cts +7 -4
  316. package/dist/report/components/summaries/index.d.mts +7 -4
  317. package/dist/report/components/summaries/index.d.ts +7 -4
  318. package/dist/report/definition/cell.cjs +3 -0
  319. package/dist/report/definition/cell.cjs.map +1 -1
  320. package/dist/report/definition/cell.d.cts +2 -0
  321. package/dist/report/definition/cell.d.mts +2 -0
  322. package/dist/report/definition/cell.d.ts +2 -0
  323. package/dist/report/definition/primitives/chart.cjs +8 -5
  324. package/dist/report/definition/primitives/chart.cjs.map +1 -1
  325. package/dist/report/definition/primitives/points-dataset.cjs +1 -0
  326. package/dist/report/definition/primitives/points-dataset.cjs.map +1 -1
  327. package/dist/report/definition/primitives.cjs +15 -2
  328. package/dist/report/definition/primitives.cjs.map +1 -1
  329. package/dist/report/definition/report.cjs +27 -3
  330. package/dist/report/definition/report.cjs.map +1 -1
  331. package/dist/report/definition/report.d.cts +8 -0
  332. package/dist/report/definition/report.d.mts +8 -0
  333. package/dist/report/definition/report.d.ts +8 -0
  334. package/dist/report/execution/machine.cjs +30 -14
  335. package/dist/report/execution/machine.cjs.map +1 -1
  336. package/dist/report/execution/machine.d.cts +20 -10
  337. package/dist/report/execution/machine.d.mts +20 -10
  338. package/dist/report/execution/machine.d.ts +20 -10
  339. package/dist/report/execution/machine.js +6 -6
  340. package/dist/report/host/execute.cjs +25 -16
  341. package/dist/report/host/execute.cjs.map +1 -1
  342. package/dist/report/host/execute.d.cts +4 -0
  343. package/dist/report/host/execute.d.mts +4 -0
  344. package/dist/report/host/execute.d.ts +4 -0
  345. package/dist/report/host/from-record.cjs +22 -5
  346. package/dist/report/host/from-record.cjs.map +1 -1
  347. package/dist/report/host/machine.cjs +53 -11
  348. package/dist/report/host/machine.cjs.map +1 -1
  349. package/dist/report/host/site-runtime.cjs +9 -8
  350. package/dist/report/host/site-runtime.cjs.map +1 -1
  351. package/dist/report/host/site-runtime.d.cts +1 -1
  352. package/dist/report/host/site-runtime.d.mts +1 -1
  353. package/dist/report/host/site-runtime.d.ts +1 -1
  354. package/dist/report/host/static.cjs +56 -9
  355. package/dist/report/host/static.cjs.map +1 -1
  356. package/dist/report/index.cjs.map +1 -1
  357. package/dist/report/index.d.cts +1 -1
  358. package/dist/report/index.d.mts +1 -1
  359. package/dist/report/index.d.ts +1 -1
  360. package/dist/report/model/dataset.cjs +7 -2
  361. package/dist/report/model/dataset.cjs.map +1 -1
  362. package/dist/report/model/locale.cjs +2 -0
  363. package/dist/report/model/locale.cjs.map +1 -1
  364. package/dist/report/model/locale.d.cts +1 -0
  365. package/dist/report/model/locale.d.mts +1 -0
  366. package/dist/report/model/locale.d.ts +1 -0
  367. package/dist/report/model/metrics.cjs +3 -1
  368. package/dist/report/model/metrics.cjs.map +1 -1
  369. package/dist/report/model/pricing.cjs +16 -11
  370. package/dist/report/model/pricing.cjs.map +1 -1
  371. package/dist/report/react/styles.css +53 -29
  372. package/dist/runner/adoption.cjs +1 -0
  373. package/dist/runner/adoption.cjs.map +1 -1
  374. package/dist/runner/attempt.cjs +44 -5
  375. package/dist/runner/attempt.cjs.map +1 -1
  376. package/dist/runner/attempt.d.cts +13 -1
  377. package/dist/runner/attempt.d.mts +13 -1
  378. package/dist/runner/attempt.d.ts +13 -1
  379. package/dist/runner/config-identity.cjs +10 -2
  380. package/dist/runner/config-identity.cjs.map +1 -1
  381. package/dist/runner/config-identity.d.cts +4 -2
  382. package/dist/runner/config-identity.d.mts +4 -2
  383. package/dist/runner/config-identity.d.ts +4 -2
  384. package/dist/runner/feedback/human.cjs +298 -59
  385. package/dist/runner/feedback/human.cjs.map +1 -1
  386. package/dist/runner/feedback/json.cjs +14 -6
  387. package/dist/runner/feedback/json.cjs.map +1 -1
  388. package/dist/runner/feedback/json.d.cts +3 -2
  389. package/dist/runner/feedback/json.d.mts +3 -2
  390. package/dist/runner/feedback/json.d.ts +3 -2
  391. package/dist/runner/feedback/reducer.cjs +2 -0
  392. package/dist/runner/feedback/reducer.cjs.map +1 -1
  393. package/dist/runner/judge-config.cjs +1 -1
  394. package/dist/runner/judge-config.cjs.map +1 -1
  395. package/dist/runner/ledger.cjs +124 -12
  396. package/dist/runner/ledger.cjs.map +1 -1
  397. package/dist/runner/project-current.cjs +13 -5
  398. package/dist/runner/project-current.cjs.map +1 -1
  399. package/dist/runner/run.cjs +564 -256
  400. package/dist/runner/run.cjs.map +1 -1
  401. package/dist/runner/sandbox-pool.cjs +161 -30
  402. package/dist/runner/sandbox-pool.cjs.map +1 -1
  403. package/dist/runner/sandbox-pool.d.cts +34 -2
  404. package/dist/runner/sandbox-pool.d.mts +34 -2
  405. package/dist/runner/sandbox-pool.d.ts +34 -2
  406. package/dist/runner/shared-state-lease.cjs +803 -0
  407. package/dist/runner/shared-state-lease.cjs.map +1 -0
  408. package/dist/runner/shared-state-lease.d.cts +121 -0
  409. package/dist/runner/shared-state-lease.d.mts +121 -0
  410. package/dist/runner/shared-state-lease.d.ts +121 -0
  411. package/dist/runner/shared-state-lease.js +24 -0
  412. package/dist/runner/shared-state-lease.js.map +1 -0
  413. package/dist/runner/teardown-registry.cjs +97 -34
  414. package/dist/runner/teardown-registry.cjs.map +1 -1
  415. package/dist/runner/teardown-registry.d.cts +27 -1
  416. package/dist/runner/teardown-registry.d.mts +27 -1
  417. package/dist/runner/teardown-registry.d.ts +27 -1
  418. package/dist/runner/teardown-registry.js +20 -16
  419. package/dist/runner/types.cjs.map +1 -1
  420. package/dist/runner/types.d.cts +31 -5
  421. package/dist/runner/types.d.mts +31 -5
  422. package/dist/runner/types.d.ts +31 -5
  423. package/dist/sample/analysis.cjs +249 -459
  424. package/dist/sample/analysis.cjs.map +1 -1
  425. package/dist/sample/capability.cjs +138 -7
  426. package/dist/sample/capability.cjs.map +1 -1
  427. package/dist/sample/capability.d.cts +3 -1
  428. package/dist/sample/capability.d.mts +3 -1
  429. package/dist/sample/capability.d.ts +3 -1
  430. package/dist/sample/capability.js +8 -6
  431. package/dist/sandbox/docker-agent-image.cjs +5 -1
  432. package/dist/sandbox/docker-agent-image.cjs.map +1 -1
  433. package/dist/sandbox/docker-agent-image.d.cts +2 -0
  434. package/dist/sandbox/docker-agent-image.d.mts +2 -0
  435. package/dist/sandbox/docker-agent-image.d.ts +2 -0
  436. package/dist/sandbox/docker-agent-image.js +14 -10
  437. package/dist/sandbox/docker-profile/schema.cjs +283 -534
  438. package/dist/sandbox/docker-profile/schema.cjs.map +1 -1
  439. package/dist/sandbox/docker-profile/schema.d.cts +10 -27
  440. package/dist/sandbox/docker-profile/schema.d.mts +10 -27
  441. package/dist/sandbox/docker-profile/schema.d.ts +10 -27
  442. package/dist/sandbox/e2b-agent-template.cjs +5 -2
  443. package/dist/sandbox/e2b-agent-template.cjs.map +1 -1
  444. package/dist/sandbox/index.cjs +4 -2
  445. package/dist/sandbox/index.cjs.map +1 -1
  446. package/dist/sandbox/index.d.cts +1 -1
  447. package/dist/sandbox/index.d.mts +1 -1
  448. package/dist/sandbox/index.d.ts +1 -1
  449. package/dist/sandbox/index.js +120 -116
  450. package/dist/sandbox/index.mjs +120 -116
  451. package/dist/sandbox/keep-registry.cjs +28 -66
  452. package/dist/sandbox/keep-registry.cjs.map +1 -1
  453. package/dist/sandbox/types.cjs.map +1 -1
  454. package/dist/sandbox/types.d.cts +1 -1
  455. package/dist/sandbox/types.d.mts +1 -1
  456. package/dist/sandbox/types.d.ts +1 -1
  457. package/dist/shared/aggregate.cjs +16 -6
  458. package/dist/shared/aggregate.cjs.map +1 -1
  459. package/dist/shared/aggregate.d.cts +12 -4
  460. package/dist/shared/aggregate.d.mts +12 -4
  461. package/dist/shared/aggregate.d.ts +12 -4
  462. package/dist/shared/turn-label.cjs +20 -2
  463. package/dist/shared/turn-label.cjs.map +1 -1
  464. package/dist/shared/turn-label.d.cts +2 -0
  465. package/dist/shared/turn-label.d.mts +2 -0
  466. package/dist/shared/turn-label.d.ts +2 -0
  467. package/dist/shared/turn-label.js +4 -2
  468. package/dist/vendor/remark/remark-gfm.cjs +7 -7
  469. package/dist/vendor/remark/remark-gfm.cjs.map +1 -1
  470. package/dist/vendor/remark/remark-parse.cjs +12 -12
  471. package/dist/vendor/remark/remark-parse.cjs.map +1 -1
  472. package/dist/vendor/remark/remark.cjs +13 -13
  473. package/dist/vendor/remark/remark.cjs.map +1 -1
  474. package/dist/view/server.cjs +6 -0
  475. package/dist/view/server.cjs.map +1 -1
  476. package/docs-site/images/hitl-handshake-zh.svg +3 -3
  477. package/docs-site/zh/examples/ai-agent-application.mdx +1 -1
  478. package/docs-site/zh/examples/coding-agent-extensions.mdx +1 -1
  479. package/docs-site/zh/examples/integrations/ai-sdk-v7.mdx +4 -3
  480. package/docs-site/zh/examples/integrations/claude-sdk.mdx +3 -3
  481. package/docs-site/zh/examples/integrations/codex-sdk.mdx +5 -5
  482. package/docs-site/zh/examples/integrations/langgraph.mdx +4 -4
  483. package/docs-site/zh/examples/integrations/pi-sdk.mdx +5 -5
  484. package/docs-site/zh/explanation/adapter.mdx +5 -5
  485. package/docs-site/zh/explanation/assert.mdx +7 -7
  486. package/docs-site/zh/explanation/drive.mdx +4 -2
  487. package/docs-site/zh/explanation/evals.mdx +10 -8
  488. package/docs-site/zh/explanation/experiment.mdx +2 -2
  489. package/docs-site/zh/explanation/hitl.mdx +9 -9
  490. package/docs-site/zh/explanation/judge.mdx +5 -5
  491. package/docs-site/zh/explanation/overview.mdx +3 -3
  492. package/docs-site/zh/explanation/runner.mdx +1 -1
  493. package/docs-site/zh/index.mdx +3 -3
  494. package/docs-site/zh/reference/builtin-agents.mdx +13 -13
  495. package/docs-site/zh/reference/capabilities.mdx +1 -1
  496. package/docs-site/zh/reference/cli.mdx +18 -12
  497. package/docs-site/zh/reference/define-agent.mdx +130 -5
  498. package/docs-site/zh/reference/define-config.mdx +2 -2
  499. package/docs-site/zh/reference/define-eval.mdx +4 -4
  500. package/docs-site/zh/reference/events.mdx +11 -11
  501. package/docs-site/zh/reference/official-adapters.mdx +6 -6
  502. package/docs-site/zh/reference/results-data.mdx +6 -0
  503. package/docs-site/zh/troubleshooting/debugging.mdx +1 -1
  504. package/docs-site/zh/troubleshooting/recover-after-kill.mdx +37 -5
  505. package/docs-site/zh/tutorials/accept-results.mdx +72 -0
  506. package/docs-site/zh/tutorials/agent-onboarding.mdx +6 -6
  507. package/docs-site/zh/tutorials/authoring.mdx +9 -13
  508. package/docs-site/zh/tutorials/ci-integration.mdx +6 -6
  509. package/docs-site/zh/tutorials/compare-runs.mdx +61 -0
  510. package/docs-site/zh/tutorials/concurrency.mdx +13 -12
  511. package/docs-site/zh/tutorials/configuration.mdx +4 -6
  512. package/docs-site/zh/tutorials/connect-your-agent.mdx +1 -1
  513. package/docs-site/zh/tutorials/control-run-cost.mdx +58 -0
  514. package/docs-site/zh/tutorials/criteria-files.mdx +2 -2
  515. package/docs-site/zh/tutorials/custom-reports.mdx +2 -2
  516. package/docs-site/zh/tutorials/debug-lifecycle-plan.mdx +46 -0
  517. package/docs-site/zh/tutorials/docker-in-docker.mdx +52 -43
  518. package/docs-site/zh/tutorials/eval-groups.mdx +6 -6
  519. package/docs-site/zh/tutorials/evaluation-kinds.mdx +3 -3
  520. package/docs-site/zh/tutorials/experiments.mdx +2 -3
  521. package/docs-site/zh/tutorials/handle-execution-failures.mdx +18 -10
  522. package/docs-site/zh/tutorials/install-custom-sandbox-agent.mdx +16 -8
  523. package/docs-site/zh/tutorials/nixos-managed-dind.mdx +160 -0
  524. package/docs-site/zh/tutorials/plugins.mdx +14 -0
  525. package/docs-site/zh/tutorials/rerun-and-cache.mdx +4 -2
  526. package/docs-site/zh/tutorials/sandbox-agent.mdx +17 -11
  527. package/docs-site/zh/tutorials/sandbox-providers.mdx +17 -13
  528. package/docs-site/zh/tutorials/sandbox-reuse.mdx +12 -19
  529. package/docs-site/zh/tutorials/select-and-preview.mdx +65 -0
  530. package/docs-site/zh/tutorials/verify-judge.mdx +78 -0
  531. package/docs-site/zh/tutorials/viewing-results.mdx +38 -1
  532. package/docs-site/zh/tutorials/write-experiment.mdx +7 -5
  533. package/docs-site/zh/tutorials/write-send.mdx +9 -8
  534. package/package.json +9 -5
  535. package/dist/report/built-in/overview.cjs +0 -44
  536. package/dist/report/built-in/overview.cjs.map +0 -1
  537. package/dist/report/built-in/overview.d.cts +0 -6
  538. package/dist/report/built-in/overview.d.mts +0 -6
  539. package/dist/report/built-in/overview.d.ts +0 -6
  540. package/dist/report/built-in/overview.js +0 -9
  541. package/dist/report/built-in/overview.js.map +0 -1
  542. package/dist/runner/gate-lease.cjs +0 -283
  543. package/dist/runner/gate-lease.cjs.map +0 -1
  544. package/dist/runner/gate-lease.d.cts +0 -62
  545. package/dist/runner/gate-lease.d.mts +0 -62
  546. package/dist/runner/gate-lease.d.ts +0 -62
  547. package/dist/runner/gate-lease.js +0 -22
  548. package/dist/runner/gate-lease.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":";;;AAqvBA,4DAGC;AACD,sDAKC;AAED,kDAMC;AA0CD,4CAMC;AAED,oCAIC;AAgJD,8DAGC;AAED,wDAMC;AAED,gDAQC;AAkGD,wBAQC;AA0LD,4CAEC;AAhzBD,mDAAmD;AACtC,QAAA,aAAa,GAAG,kBAAkB,CAAC;AAChD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACU,QAAA,qBAAqB,GAAG,EAAE,CAAC;AA0IxC,0FAA0F;AAC7E,QAAA,oBAAoB,GAAG,iBAA0B,CAAC;AAG/D;;;GAGG;AACH,MAAM,wBAAwB,GAAkB,MAAM,CAAC,iCAAiC,CAAC,CAAC;AAC1F,MAAM,eAAe,GAAkB,MAAM,CAAC,yBAAyB,CAAC,CAAC;AACzE,MAAM,8BAA8B,GAAkB,MAAM,CAAC,uCAAuC,CAAC,CAAC;AACtG,MAAM,qBAAqB,GAAkB,MAAM,CAAC,+BAA+B,CAAC,CAAC;AAyFrF,MAAM,qBAAqB,GAAkB,MAAM,CAAC,8BAA8B,CAAC,CAAC;AAiBpF,SAAgB,wBAAwB,CAAC,KAAqB;IAC1D,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,qBAAqB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAwB,CAAC;AACvD,CAAC;AACD,SAAgB,qBAAqB,CAAC,KAAc;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAC7C,KAEC,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC;AAC3C,CAAC;AACD,oDAAoD;AACpD,SAAgB,mBAAmB,CAAiF,KAGnH;IACG,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAA2C,CAAC;AAC1E,CAAC;AAyCD,oDAAoD;AACpD,SAAgB,gBAAgB,CAAC,KAAc;IAC3C,OAAO,CAAC,OAAO,KAAK,KAAK,QAAQ;QAC7B,KAAK,KAAK,IAAI;QACb,KAEC,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC,CAAC;AACtC,CAAC;AACD,uDAAuD;AACvD,SAAgB,YAAY,CAAC,UAA6B,EAAE,KAA0B;IAClF,MAAM,KAAK,GAAG,EAAE,GAAG,UAAU,EAAE,GAAG,KAAK,EAAE,CAAC;IAC1C,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAmB,CAAC;AAClD,CAAC;AA+ID,2CAA2C;AAC3C,SAAgB,yBAAyB,CAAC,KAA+D;IACrG,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,qBAAqB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAyB,CAAC;AACxD,CAAC;AACD,8DAA8D;AAC9D,SAAgB,sBAAsB,CAAC,KAAc;IACjD,OAAO,CAAC,OAAO,KAAK,KAAK,QAAQ;QAC7B,KAAK,KAAK,IAAI;QACb,KAEC,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC,CAAC;AAC5C,CAAC;AACD,4DAA4D;AAC5D,SAAgB,kBAAkB,CAAC,UAAgC,EAAE,MAIpE;IACG,MAAM,KAAK,GAAG,EAAE,GAAG,UAAU,EAAE,GAAG,MAAM,EAAE,CAAC;IAC3C,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,qBAAqB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAyB,CAAC;AACxD,CAAC;AAmFD,2DAA2D;AAC3D;;;;;;;;;;;;;GAaG;AACH,SAAgB,MAAM,CAAC,GAItB;IACG,IAAI,GAAG,CAAC,YAAY;QAChB,OAAO,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC;IAC9C,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC;AACvE,CAAC;AAyLD,uDAAuD;AACvD,SAAgB,gBAAgB,CAAC,GAAe;IAC5C,OAAO,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAgB,CAAC;AAClF,CAAC;AAgHD;;;;;GAKG;AACH;;;;GAIG;AACU,QAAA,oBAAoB,GAAG,iBAAiB,CAAC;AACzC,QAAA,2BAA2B,GAAG,wBAAwB,CAAC","sourcesContent":["// runner 域类型:结果 / 汇总 / reporter 契约,eval / experiment / config 定义,\n// 以及调度器的编排类型(AgentRun / RunOptions / Attempt)。\nimport type { Effect } from \"effect\";\nimport type { JsonValue, LocalizedText, ScopedFeedback, SourceArtifact, Verdict } from \"../shared/types.ts\";\nimport type { AttemptFailureClassifier } from \"../shared/failure-class.ts\";\nimport type { O11ySummary, StreamEvent, TraceSpan, Truncation, Usage } from \"../o11y/types.ts\";\nimport type { Agent, AgentSetupManifest } from \"../agents/types.ts\";\nimport type { SandboxLayer } from \"../sandbox/layer.ts\";\nimport type { LinkedRunPlan } from \"../sandbox/plan.ts\";\nimport type { BuildKey } from \"../sandbox/identity.ts\";\nimport type { EvaluationFactResult, DiffArtifact, JudgeConfig, JudgeDeclaration, ResolvedJudgeConfig, PrimaryFactSummary, ScoreFactAttemptOutcome, ScoreFactUseResult, VerdictFactUseResult, } from \"../assertions/types.ts\";\nimport type { ScoreTestContext, TestContext } from \"../context/types.ts\";\nimport type { CapturedEvalSource } from \"./eval-source.ts\";\nimport type { AttemptLocator } from \"../attempt-locator.ts\";\nimport type { RecordRoot } from \"../record/platform/root.ts\";\nimport type { CurrentReusedAttemptReadback } from \"./reuse-readback.ts\";\nimport type { PluginInstance, PluginOnUnavailable } from \"../plugin/contracts.ts\";\n// Report 的公开子路径是独立预编译单元;这里依赖作者 API 的公开 aggregate,避免把\n// host implementation 或旧的 JSX renderer type 拉回 runner 边界。\nimport type { ReportDefinition } from \"../report/index.ts\";\nimport type { ThemeDefinition } from \"../report/theme.ts\";\n// ───────────────────────── 结果 / 报告 ─────────────────────────\n/**\n * 解析后运行配置的**穷尽可序列化投影**——记录这次运行实际生效的值,不是原始 `ExperimentDef`\n * (函数与 hooks 无法忠实落盘,存「原样」只能存谎)。`model` 与 `agent` 只在快照顶层存在,\n * 这里不复制(见 docs/feature/record/architecture.md「run.json」)。\n */\nexport interface ExperimentRunInfo {\n description?: string;\n reasoningEffort?: string;\n flags?: globalThis.Record<string, JsonValue>;\n /** 报告归类标注(ExperimentDef.labels 原样投影);不透传运行时,不参与可比性配置。 */\n labels?: globalThis.Record<string, string | number>;\n /** Credential-free Plugin lifecycle behavior projection. */\n plugins?: JsonValue;\n attempts: number;\n earlyExit: boolean;\n timeoutMs?: number;\n budget?: number;\n maxConcurrency?: number;\n /** Experiment SandboxLayer 的纯数据身份;Direct 也以 command-only 的完整身份记录。 */\n sandboxLayer: JsonValue;\n /** 每个已选择 Eval 的完整 pair-owned physical plan;Direct 也有显式投影。 */\n sandboxPlansByEval: globalThis.Record<string, JsonValue>;\n /** Sandbox 是否在同一次 Run 内复用。 */\n sandboxReuse?: boolean;\n /** 解析后的 Judge 执行身份;只记录凭据选择器名,不记录凭据。 */\n judge?: Pick<JudgeConfig, \"model\" | \"baseUrl\" | \"apiKeyEnv\" | \"timeoutMs\">;\n /**\n * Agent Ensure 与精确配对 installer 的静态身份投影;按声明顺序完整落盘。\n * 实际 artifact digest/platform 属运行 provenance,不进入这里。\n */\n agentInstalls: JsonValue[];\n}\nexport interface SandboxRunInfo {\n provider: string;\n params?: globalThis.Record<string, JsonValue>;\n fingerprint?: string;\n}\n/**\n * Runner 保留的 attempt 生命周期锚点——闭集,不是扩展点(见 docs/feature/record/architecture.md\n * 「两层时间模型」)。计时(`phases[].name`)、错误与诊断的 attempt 锚点(见 TimingOrigin)、\n * live 当前步骤都由 Runner 绑定这同一个闭集;author、Adapter 与 provider 不能新增成员。\n * 可扩展的工作计时走开放 activity key,不进本词表。\n */\nexport type LifecyclePhase = \n// 运行级(派发前至多一次,宿主机侧;仅错误归因)\n\"judge.precheck\" // 判分预检;预检失败时是含 judge 断言的 eval 全部 attempt 的错误锚点\n// 实验级(整场一次,宿主机侧;仅错误/诊断归因)\n | \"experiment.setup\" // ExperimentDef.setup;setup 抛错时是本实验所有 attempt 的错误锚点\n | \"experiment.teardown\" // ExperimentDef.teardown;失败只产生运行级 diagnostic\n// 主链:从排队到 trace collect,覆盖到判定与主证据收集完成,按执行序\n | \"sandbox.queue\" // 等待并发信号量(调度等待,唯一不属于某个 owner 的成员)\n | \"sandbox.create\" // provider 物化沙箱实例(共享构建不在这里,它在 Run 级 activity)\n | \"sandbox.prepare\" // 两层作者 layer 的 prepare 链\n | \"sandbox.prepare.eval\" // 仅错误/诊断归因,不单列计时\n | \"sandbox.prepare.group\" // Eval Group 作者与 Plugin command 的错误/诊断归因\n | \"sandbox.prepare.experiment\" // 仅错误/诊断归因,不单列计时\n | \"agent.ensure\" // Runner 的 probe → 缺失才 install → 同一 probe 复检\n | \"workspace.baseline\" // 变更分类账锚点(runner 私有 git ledger 首笔 commit)\n | \"agent.setup\" // Adapter runtime 配置 / 凭据 / state setup\n | \"telemetry.configure\" // tracing 出口配置\n | \"eval.run\" // 整段 test(t),含所有 send 与手工命令\n | \"agent.run\" // 嵌套在 eval.run 内:adapter send 期间打开;只用于错误/诊断归因,不单列计时条目\n | \"workspace.diff\" // 从分类账折叠 agent 归因增量\n | \"assertions.evaluate\" // 断言 finalize + 判定,含 judge 调用\n | \"telemetry.collect\" // OTLP receiver settle / collect\n// 收尾段:无论主链成败都执行,不计入 durationMs 口径,按执行序\n | \"agent.teardown\" | \"sandbox.cleanup\" // 两层作者 layer 已登记 cleanup 全局 LIFO\n | \"sandbox.suspend\" // 留存提交后 provider 把现场转入休眠(docker stop / e2b pause)\n | \"sandbox.stop\"; // provider 销毁沙箱;与 sandbox.suspend 同一 attempt 互斥\n/**\n * 开放的工作计时节点,Run 与 attempt 共用同一形状(见 docs/feature/record/architecture.md\n * 「TimingActivity」)。`key` 是非空、以 `.` 分段的稳定机器 key;未知 key 原样保留并可通用展示。\n * offset 相对所在时钟域(RunMeta.timings 或单个 attempt)的单调时钟起点。\n */\nexport interface TimingActivity {\n /** 所在时钟域内唯一,供 origin、provenance 与展示层稳定引用;不作为跨 Run 身份。 */\n id: string;\n /** ActivityKey;官方词表见 architecture.md,第三方用自己的命名空间。 */\n key: string;\n /** 采集端写入的有界、脱敏人读标签;展示层不解析它重建语义。 */\n label: string;\n /** 相对所在时钟域单调时钟起点的偏移。 */\n startOffsetMs: number;\n durationMs: number;\n failed?: true;\n children?: TimingActivity[];\n /** key = \"agent.turn\" 时存在;把 runner 的 send 墙钟包络与 trace.json 中同一轮的 spans 显式关联。 */\n sessionIndex?: number;\n turnIndex?: number;\n turnId?: string;\n traceId?: string;\n traceAttribution?: \"traceparent\" | \"window\" | \"none\";\n /** key = \"agent.turn\" 时存在,该轮 `Turn.usage` 落盘原样(有记录才写)。 */\n usage?: Usage;\n /** key = \"sandbox.command\" 时的有界脱敏摘要;环境变量值与 stdout/stderr 不进入时间树。 */\n command?: {\n display: string;\n exitCode?: number;\n /** 是否由 checked run*OrThrow 公开调用产生;show/report 用它与 exitCode 推导展示语义。 */\n checked?: boolean;\n /** 这条命令这次生效的时限与它来自哪一层;四层解析链一个上限都没声明时缺席。 */\n limit?: CommandLimitAttribution;\n };\n}\n/**\n * 一条命令生效的时限归属(词表单源在 docs/feature/sandbox/architecture.md\n * 「时限归属:attempt deadline 是唯一默认」)。命令节点带着它,读者才不用靠「停在整 1m 0s」\n * 这种巧合反推是谁掐断了命令。\n */\nexport interface CommandLimitAttribution {\n /**\n * 生效上限的来源层:`attempt-deadline` 是 attempt 自己的线(未显式传 `timeout` 的命令拿它的\n * 剩余量),`command-timeout` 是用户给这条命令显式传的 `timeout`,`provider-limit` 是 provider\n * 固有的会话上限——它在派发前就按环境约束报出来,attempt 层不会撞上,列在词表里是给读面认。\n */\n source: \"attempt-deadline\" | \"command-timeout\" | \"provider-limit\";\n /** 该层对这条命令实际生效的上限,毫秒(attempt deadline 记的是命令开始时的剩余量)。 */\n limitMs: number;\n /** 这条命令正是撞上这条线才失败的(非零退出与传输失败都不是)。 */\n timedOut?: true;\n}\n/**\n * 错误与诊断的归属(见 docs/feature/record/architecture.md「TimingOrigin」)。\n * attempt 支绑定 Runner 打开的生命周期锚点;run 支指向 RunMeta.timings 里的 activity。\n */\nexport type TimingOrigin = {\n scope: \"attempt\";\n /** runner 在错误 / 诊断发生时已打开的生命周期锚点;producer 不能自行指定。 */\n phase: LifecyclePhase;\n /** 可选细化:锚点下具体的 activity(如失败的那条 sandbox.command)。 */\n timingNodeId?: string;\n} | {\n scope: \"run\";\n /** 指向 RunMeta.timings 里的 activity(如失败的 sandbox.build)。 */\n timingNodeId: string;\n};\n/**\n * 共享构建的 provenance,每个实际查询或构建过的 BuildKey 一条。\n * 时间只保存在 `RunMeta.timings`,本表经 `timingNodeId` 关联,不复制 duration。\n */\nexport interface SandboxBuildRecord {\n buildKey: string;\n provider: string;\n status: \"hit\" | \"built\" | \"failed\" | \"cancelled\";\n /** 关联 RunMeta.timings 里对应的 sandbox.build activity。 */\n timingNodeId: string;\n locator?: JsonValue;\n inputs: JsonValue;\n error?: {\n code: string;\n message: string;\n cause?: {\n name?: string;\n code?: string;\n message: string;\n };\n };\n}\n/** Runner 阶段计时,按执行顺序;只记录实际发生的阶段(见 docs/feature/record/architecture.md)。 */\nexport interface PhaseTiming {\n name: LifecyclePhase;\n /** 相对本 attempt 单调时钟起点的阶段起点;与 children 使用同一量化时钟。 */\n startOffsetMs: number;\n /** 阶段耗时;失败阶段计到抛错或超时中断时。 */\n durationMs: number;\n /** 该阶段抛错或被超时中断。主链至多一条,其后无主链条目;收尾阶段各自独立标记,不改判定。 */\n failed?: true;\n /** 锚点内的 activity 子树,offset 相对本 attempt 的单调时钟起点。 */\n children?: TimingActivity[];\n}\n/**\n * `commands.json` 的一条落盘记录(见 docs/feature/record/architecture.md「commandsjson」):\n * 四个公开 `Sandbox.run*()` 方法的最外层调用返回时,Runner 在 `CommandResult` 交还调用方\n * **之前**登记的完整证据——Eval 后续即使只把 `.slice(-N)` 拼进异常消息,这份证据仍然完整。\n * 成功与非零退出都记录;provider 内部实现步骤与 Agent 自己调用的 shell 不经过这层包装,\n * 不伪装成这里的命令。\n */\nexport interface CommandExitEvidence {\n /** 与 `PhaseTiming.children` 中 `key === \"sandbox.command\"` 的 `TimingActivity.id` 相同,唯一关联命令证据卡与 `--timing` 的 command 节点。 */\n timingNodeId: string;\n /** runner 在命令返回那一刻已经打开的生命周期阶段。 */\n phase: LifecyclePhase;\n /** 与该 `TimingActivity.command.display` 同一份有界脱敏命令摘要;不含 env value。 */\n display: string;\n exitCode: number;\n /** 是否由 checked run*OrThrow 公开调用产生;非零 + checked 才是失败展示语义。 */\n checked: boolean;\n /** `CommandOptions.sensitiveValues` 命中的已知值已替换为 `<redacted>`;超过每流 64 KiB 时\n * 在落盘序列化时截断,见 `truncated`。 */\n stdout: string;\n /** 同 `stdout` 的已知敏感值边界与截断规则。 */\n stderr: string;\n /** `stdout` / `stderr` 超过每流上限时的结构化截断标记(`path` 为 `\"stdout\"` 或 `\"stderr\"`);\n * 只由 writer 在落盘时刻写入,运行时与调用方拿到的仍是完整值。 */\n truncated?: Truncation[];\n}\n/** `commands.json` 的落盘形状。 */\nexport type CommandsArtifact = CommandExitEvidence[];\n/**\n * 使 attempt 无法正常完成的唯一致命执行错误(见 docs/feature/record/architecture.md 的\n * `AttemptError`)。`message` 是人可读的一层原因(不拼整份 SDK response);完整 stack 单放\n * `stack`,`niceeval show @locator` 首页展开、终端即时反馈不整段打印。默认报告只显示 `message`。\n */\nexport interface AttemptError {\n /** 稳定、可供 CI/Agent 分支处理的机器码;未知异常使用 `\"unexpected-error\"`。 */\n code: string;\n /** 人可读的一层原因,不拼接整份 SDK response。 */\n message: string;\n /**\n * 错误归属。attempt 内错误由 runner 绑定当时打开的生命周期锚点(attempt 形态);\n * attempt 开始前的共享构建失败引用 Run timing node(run 形态),不伪造 attempt 锚点。\n */\n origin: TimingOrigin;\n /** 原异常有 stack 时保留,供 show 展开;终端即时反馈不整段打印。 */\n stack?: string;\n /** 下层 SDK/OS 错误的有限摘要。 */\n cause?: {\n name?: string;\n code?: string;\n message: string;\n };\n /**\n * 超时打断产生的 `errored` 专用:这次撞的是哪层时限、上限值多少、值从哪一层解析而来。\n * 三样一起落盘,报错行与 `show --timing` 照实印这三样;归属规则单源在\n * docs/feature/sandbox/architecture.md「时限归属:attempt deadline 是唯一默认」。\n */\n timeout?: TimeoutAttribution;\n}\n/** 一次超时的归属事实,由 runner 在把 attempt 转成 `errored` 时写下。 */\nexport interface TimeoutAttribution {\n /**\n * 触发层:`attempt-deadline` 是 attempt 自己的上限(沙箱内一切时限都从它派生),\n * `command-timeout` 是用户给单条命令显式传的 `timeout`。provider 固有的会话上限在派发前\n * 就按环境约束报出来(见 `assertDeadlineFitsProvider`),attempt 层不会撞上它。\n */\n trigger: \"attempt-deadline\" | \"command-timeout\";\n /** 该层实际生效的上限,毫秒。 */\n limitMs: number;\n /**\n * 值来自哪一层:`attempt-deadline` 取 `timeoutMs` 解析链四层之一,`command-timeout` 只有\n * 命令显式声明一个来源。\n */\n source: \"flag\" | \"experiment\" | \"eval\" | \"config\" | \"command\";\n}\n/**\n * 不一定改变 verdict、但运行后仍需回顾的有界诊断(见 docs/feature/record/architecture.md 的\n * `DiagnosticRecord`)。`level` 表达写入方观察到的运行影响,不是 verdict 的别名 ——\n * passed / failed / errored 任一 verdict 都可以带 cleanup / teardown 诊断。\n * 与运行级的 `DiagnosticNotice` 不同,这条挂在单个 attempt 结果或 RunMeta 上落盘。\n */\nexport interface DiagnosticRecord {\n code: string;\n level: \"warning\" | \"error\";\n /**\n * 诊断归属。attempt 诊断由 runner 绑定当时打开的锚点;Run 诊断可引用 Run timing node,\n * 也可只带 `experiment.teardown` 这类归因锚点;没有 timing 记录的第三方 producer 可省略。\n */\n origin?: TimingOrigin;\n /** 写入时观察到的原始有界描述;不包含修复动作或呈现文案。 */\n detail: string;\n /** 支撑 code 的结构化原始上下文。 */\n context?: Readonly<globalThis.Record<string, JsonValue>>;\n /** 相同 dedupeKey 折叠后的出现次数;省略等于 1。 */\n count?: number;\n}\n/**\n * `--accept` 跨过的一条具名差异(`EvalResult.carriedAccepting` 的成员)。\n * `selector` 与 CLI 上写下的那个字面量同一个词表,`from` / `to` 是完整值的字符串投影;\n * 某一侧没有这个键(新增 / 删除)时该侧省略。\n */\nexport interface CarriedAcceptance {\n selector: string;\n from?: string;\n to?: string;\n}\n/** 一条人工接受结果的差异摘要。与 manifest 相减的 selector/value 投影保持同一词表。 */\nexport interface AcceptedDifference {\n selector: string;\n from?: string;\n to?: string;\n}\n/** 旧 opaque carryEpoch 指纹迁移到当前确定性指纹的已知来源。 */\nexport interface FingerprintMigration {\n kind: \"opaque-carry-epoch\";\n fingerprint: string;\n algorithmVersion: number;\n coverageVersion: number;\n}\n/** `niceeval accept @<locator>` 写入新结果的来源与重锚审计记录。 */\nexport interface AcceptedResult {\n /** 被复制的历史 attempt locator。 */\n locator: string;\n /** 被复制条目的旧指纹。 */\n fingerprint: string;\n /** 当前项目按本次配置重算出的指纹。 */\n acceptedFingerprint: string;\n /** 新旧 manifest/config 的完整差异清单。 */\n differences: AcceptedDifference[];\n}\n/** `niceeval exp rename` 写入新结果的来源与身份审计记录。 */\nexport interface RenamedResult {\n /** 被重绑结果原属的 experimentId。 */\n experimentId: string;\n /** 被重绑结果在旧结果树中的 locator。 */\n locator: string;\n /** 迁移前结果的 fingerprint。 */\n fingerprint: string;\n /** 发生重绑的时刻。 */\n at: string;\n}\n/** 自动重试吸收的一次物理 send 失败;不进入逻辑会话事件流。 */\nexport interface RetryAttemptRecord {\n sessionIndex: number;\n turnIndex: number;\n /** 同一逻辑 send 内从 0 开始;0 是首次发送。 */\n sendAttempt: number;\n startedAt: string;\n durationMs: number;\n failure: {\n type: \"agent-send-failed\";\n acceptance: \"rejected\";\n message: string;\n process?: {\n exitCode?: number;\n signal?: string;\n };\n };\n classification: {\n retryable: true;\n scope: \"attempt\" | \"eval\" | \"experiment\";\n reason?: string;\n };\n events: StreamEvent[];\n usage?: Usage;\n}\nexport interface EvalResult {\n id: string;\n description?: string;\n experimentId?: string;\n experiment?: ExperimentRunInfo;\n agent: string;\n model?: string;\n verdict: Verdict;\n fingerprint?: string;\n /** 产生本 Attempt 判定与计分结果的固定求值算法。 */\n evaluationAlgorithm: EvaluationAlgorithm;\n /** 产出该结果时的 Run 级配置身份。 */\n configHash?: string;\n attempt: number;\n /** 本 attempt 开始的墙钟时刻(ISO);view 按 eval 粒度展示「何时跑的」。 */\n startedAt?: string;\n /**\n * 不透明的 Attempt 定位符;由完整 durable AttemptId 确定性派生。\n */\n locator?: string;\n /**\n * `locator` 的来源 Run 身份。niceeval writer 对 fresh 条目恒写;carry 与 publish 原样保留,\n * 使同一 attempt 在多份落盘中仍共享一个 locator 身份。旧记录缺失时 reader 会沿\n * `artifactBase` 回溯来源,无法回溯才退回当前 Run。\n */\n locatorRunId?: string;\n durationMs: number;\n /** 自 sandbox.create 起、排除并发排队和收尾的执行耗时;旧记录缺失时携带保守回退 durationMs。 */\n executionMs?: number;\n /** The complete native Fact graph, persisted directly in result.json. */\n factResults: readonly EvaluationFactResult[];\n factUses: readonly (VerdictFactUseResult | ScoreFactUseResult)[];\n /** Structured Score Fact outcome for score Eval terminal semantics. */\n scoreResult?: ScoreFactAttemptOutcome;\n /**\n * 题型:`defineEval` → `\"pass\"`,`defineScoreEval` → `\"score\"`,定义期事实,与\n * `EvalDescriptor.evaluationKind` 同源。schema 18 必填。\n */\n evaluationKind: EvaluationKind;\n /** 自动重试吸收的物理 send 失败,按发生顺序完整保留。 */\n retryAttempts?: RetryAttemptRecord[];\n usage?: Usage;\n /**\n * 价目表估算成本,恒等于 `estimateCost(model, usage, config.pricing)`——永远独立计算,\n * 与 `usage.costUSD`(网关/adapter 显式回报的 observed 成本)是两个并存事实,互不覆盖、\n * 互不兜底:observed 存在时 estimatedCostUSD 也照常按 runtime/config price table 估算。\n */\n estimatedCostUSD?: number;\n /** 使 attempt 进入 `errored` 的唯一致命执行错误(结构化);默认报告显示 `error.message` 一层原因。 */\n error?: AttemptError;\n /** 本 attempt 的诊断(与 verdict 独立);teardown / cleanup 失败等挂在这里,不改判定。 */\n diagnostics?: readonly DiagnosticRecord[];\n /** Runner 阶段计时,按执行顺序;只记录实际发生的阶段(见 docs/feature/record/architecture.md)。 */\n phases?: PhaseTiming[];\n skipReason?: string;\n events?: StreamEvent[];\n /** test 引用到的 eval 源码(按 loc 收集),供 view 渲染 github-diff 式代码视图。 */\n sources?: SourceArtifact[];\n o11y?: O11ySummary;\n /** agent 经 OpenTelemetry 导出的运行追踪(有 tracing 能力且收到 span 时)。 */\n trace?: TraceSpan[];\n /** 本 attempt 的 agent setup 实际装了什么(Skill / native plugin / MCP / Python plugin);不参与评分。 */\n agentSetup?: AgentSetupManifest;\n /**\n * attempt 级聚合的证据覆盖(各 turn 的最差值,unavailable < partial < complete);必填。\n */\n evidenceCoverage: import(\"../agents/types.ts\").EvidenceCoverage;\n /**\n * 沙箱型 attempt 的执行环境标识:provider 名与实例 id,用于关联 provider 侧日志与留存现场;\n * remote 型 agent 无此字段。`kept` 表示运行收尾时按 --keep-sandbox 留存了沙箱;之后的存活\n * 状态归 `niceeval sandbox list` 回答,本记录一次写成、不回写。\n */\n sandbox?: {\n provider: string;\n sandboxId: string;\n kept?: true;\n /** 本次 Attempt 使用了同一 Invocation 中已创建的 Sandbox。 */\n reused?: true;\n /** 本次 Invocation 内该 Sandbox 的稳定编号(从 1 开始)。 */\n reuseSandbox?: number;\n /** 此 Sandbox 承接的 Attempt 序号(从 1 开始)。 */\n reuseOrdinal?: number;\n };\n /** agent 归因增量:逐 send 窗口的 delta 序列(落盘为 diff.json;文件级视图由读取面派生)。 */\n diff?: DiffArtifact;\n /** Sandbox 命令的 stdout/stderr 证据(落盘为 commands.json);成功与非零退出都记录,见 `CommandExitEvidence`。 */\n commands?: CommandExitEvidence[];\n rawTranscript?: string;\n /** 携带条目(--resume 合入)专用:artifact 目录(相对结果根目录),指向原快照里的落盘。 */\n artifactBase?: string;\n /**\n * 仅经 `--accept` 授权跨过指纹差异携入时留下的审计痕迹:跨过的每条差异各一项。\n * 它让「这条是在哪个口径下被采信的」跟着结果走,不随 Run 翻篇丢失——授权是把风险显式交给\n * 人,报告因此会在新配置身份下混入旧配置跑出的结果,这个字段是事后追认这笔账的唯一线索。\n */\n carriedAccepting?: CarriedAcceptance[];\n /** 当前确定性指纹替换已知旧 opaque carryEpoch 指纹时保留的来源。 */\n migratedFrom?: FingerprintMigration;\n /**\n * 人工 `niceeval accept @<locator>` 产生的新结果:来源 locator、旧/新 fingerprint\n * 与 manifest 差异摘要。与 `carriedAccepting`(旧版 --accept 携入痕迹)并列保留,\n * 读取旧记录时不做迁移。\n */\n acceptedFrom?: AcceptedResult;\n /** 实验身份改变但 fingerprint 保持不变时的重绑来源审计。 */\n renamedFrom?: RenamedResult;\n /**\n * writer 实际写出的按需 artifact 词干列表(词表与全部横切属性单源在\n * docs/feature/record/architecture.md「证据 registry」,如 [\"commands\", \"events\", \"sources\"])。\n * 省略等价于空列表;携带条目原样携带。读取面的懒加载语义(缺失返回 null)独立成立,\n * 本字段只服务「不 stat 磁盘就知道有什么」的消费方。\n */\n artifacts?: string[];\n}\n/** `run.json` 的格式标记;把 niceeval 报告和其它工具的同名文件区分开。 */\nexport const RECORD_FORMAT = \"niceeval.results\";\n/**\n * 结果格式版本,只在破坏兼容读取时递增;读取器只认相同版本。见 docs/feature/record/architecture.md。\n * `5`(见 memory 的 attempt-locator-and-source-dedup 条目)= result.json 新增 `locator` 字段;\n * `sources.json` 从逐 attempt 内联全量内容改为「attempt 级引用 + 快照级 `sources/<sha256>.json`\n * 去重仓库」,`AttemptHandle.sources()` 的公开返回形状不变(仍是 `SourceArtifact[] | null`)。\n * `6` = `error` 从自由字符串改为结构化 `AttemptError`,并新增有界 `diagnostics`。\n * `8` = 断言记录改 outcome 判别联合(groupPath/optional/expected/received/unavailable+reason);\n * 生命周期词表统一为 LifecyclePhase,`error.operation`/`diagnostics[].operation` 更名 `phase`;\n * 新增 `phases`(阶段计时)、`coverage`(证据覆盖聚合)、`sandbox`(执行环境标识)字段;\n * `ExperimentRunInfo` 改为解析后运行配置的穷尽投影(sandbox 从字符串改结构化投影对象);\n * `diff.json` 落逐窗口 delta 序列(DiffWindow[]);events/trace 的字符串值统一 256 KiB 截断\n * (结构化 `truncated` 标记);run.json 新增发布拷贝的 `publish` 标记。\n * `9` = `hasEvents`/`hasTrace`/`hasSources` 三个布尔删除,统一为 `artifacts`(writer 实际写出的\n * 按需 artifact 词干列表,单源在证据 registry);`O11ySummary` 删除 `usage`/`estimatedCostUSD`/\n * `durationMs`,正名为纯行为计数缓存,权威唯一在 `result.json` 的 `Usage`/`estimatedCostUSD`/\n * `durationMs`(见 memory 的 results-evidence-registry-ruling 条目)。\n * `12` = `diff.json` 的 `WindowChange.binary` 并入 `elided`。\n * `13` = 两层时间模型:`TimingNode` 封闭 kind 改为开放 key 的 `TimingActivity`;\n * `AttemptError.phase` / `DiagnosticRecord.phase` 改为 `origin: TimingOrigin`;\n * `RunMeta` 新增 `timings` 与 `sandboxBuilds`(见 memory 的 results-schema-version-history)。\n * `14` = result.json 的证据聚合字段从 `coverage` 破坏性重命名为 `evidenceCoverage`;\n * 六通道全部必填,不兼容旧 schema,也不做 normalize。\n * 旧版快照按格式规则整份判为不兼容并在扫描时列为占位条目,不迁移不降级。\n * `15` = commands.json 的命令退出事实新增 `checked`,区分公开 checked/unchecked 调用;\n * 旧版 commands.json 不做兼容读取。\n * `18` = Fact/use 原子记录、`evaluationAlgorithm: \"fact-use/v3\"`;旧格式完全不支持读取。\n * `renamedFrom` 是可选审计字段,删除运行期选题投影也不改变当前 reader 对旧结果的读取;\n * 两者都不是破坏性格式变化,因此不递增版本。\n */\nexport const RECORD_SCHEMA_VERSION = 18;\n/** 一次 Invocation 的纯运行时内存聚合(reporter 契约用);落盘格式契约在 niceeval/record 的 RunMeta / AttemptRecord,见 docs/feature/record/architecture.md。不携带顶层 `agent`/`model`——一次 Invocation 可能横跨多个 `(agent, model, flags)` 配置,塞一个顶层单值只能代表其中一份配置;需要时从 `results` 里逐条 `EvalResult.agent`/`.model` 去重派生。 */\nexport interface InvocationSummary {\n /** 项目名(来自 config.name),透传给 `niceeval view` 顶部 hero 显示。 */\n name?: LocalizedText;\n startedAt: string;\n completedAt: string;\n passed: number;\n /** 断言不通过的数量;不包含 errored。 */\n failed: number;\n skipped: number;\n /** 环境、超时、adapter、agent runtime 等执行错误数量;与 failed 互斥。 */\n errored: number;\n durationMs: number;\n /** 本次 Invocation fresh 结果的 token 汇总(只折叠 input/outputTokens);observed costUSD 不在这里汇总,逐条留在 `results[].usage.costUSD`。 */\n usage?: Usage;\n /** 本次 Invocation fresh 结果的 `estimatedCostUSD` 累计(价目表估算口径,见 EvalResult.estimatedCostUSD);observed cost 不进入本字段。 */\n estimatedCostUSD?: number;\n /** Current Record readbacks adopted by this invocation; these are never recreated EvalResults. */\n reusedAttempts: readonly CurrentReusedAttemptReadback[];\n results: EvalResult[];\n}\n/**\n * The only invocation-level durable hand-off. Record Runs retain every\n * result, diagnostic, and artifact; this receipt only identifies those Runs\n * and records the invocation lifecycle fact.\n */\nexport interface InvocationReceipt {\n readonly invocationId: string;\n readonly runIds: readonly string[];\n readonly startedAt: string;\n readonly completedAt?: string;\n readonly completion: \"completed\" | \"interrupted\" | \"failed\";\n}\n/** onInvocationStart 的运行规模:去重后 eval 数 × 配置(agent×model×flags)数 → 总 attempt 数。 */\nexport interface InvocationShape {\n /** 去重后实际要跑的 eval 数(= evals.length)。 */\n evals: number;\n /** (agent, model, flags) 配置组合数;compare 多 agent 时 > 1。 */\n configs: number;\n /** 总 attempt 数(evals × configs × attempts);逐行输出与汇总计数都按它。 */\n totalAttempts: number;\n /** 本次运行实际生效的全局并发数(flag/config/sandbox 默认值解析后的结果);\n * 实验级 maxConcurrency 只在该实验内部限流,不改这个全局值。 */\n maxConcurrency: number;\n /**\n * 调度前由 Record v1 draft 分配的真实持久化 Run 身份。其它 reporter 可用它关联\n * plan 期的 attempt locator 与最终 Run,但不能替换或重新生成它。\n */\n runIds?: ReadonlyMap<string, string>;\n}\nexport interface Reporter {\n onEvent?(event: ReporterEvent): void | Promise<void>;\n onInvocationStart?(evals: {\n id: string;\n }[], shape?: InvocationShape): void | Promise<void>;\n onEvalComplete?(result: EvalResult): void | Promise<void>;\n onInvocationComplete?(summary: InvocationSummary): void | Promise<void>;\n}\n/**\n * 内部 reporter 注册项:CLI/runner 给每个 `Reporter` 实例附上「叫什么名字」「失败是否致命」\n * 两条元数据,不改变用户实现 `Reporter` 的公共形状——`Reporter` 接口本身不变,用户只需要实现\n * 上面那四个回调,从不需要知道 `ReporterRegistration` 的存在。`name` 是\n * `reportReporterError()` / `DiagnosticNotice.key` 里 `reporter-error:<name>` 的稳定标识:\n * 同一个 reporter 反复失败折叠成一条诊断、`count` 递增,不同 reporter 各自一条,由这个字段的\n * 取值决定,不是「在哪个回调阶段失败」(onInvocationStart/onEvalComplete/…)决定——后者只作为\n * 诊断消息里的次要上下文,不参与去重身份。\n *\n * `required` 语义(见 docs/feature/experiments/cli.md「运行完成状态不只看 verdict 计数」):\n * - CLI 显式 `--junit`:`required: true`——这是调用者要求的附加聚合文件,写失败必须让\n * `InvocationCompletion` 判红、CI 退出码非零。CLI `--json` 是终端 receipt 流,不是 Reporter。\n * - 用户 `Config.reporters` / `EvalDef.reporters`:`required: false`——失败只折成一条\n * diagnostic,不影响 completion,也不阻断其它 reporter 收尾或后续 attempt。\n *\n * `target` 是可选的落盘路径(如 `--json`/`--junit` 指定的文件),纯展示 / 排障用途,不参与判定。\n */\nexport interface ReporterRegistration {\n reporter: Reporter;\n name: string;\n required: boolean;\n target?: string;\n}\nexport type ReporterEvent = {\n type: \"invocation:start\";\n evals: {\n id: string;\n }[];\n shape: InvocationShape;\n} | {\n type: \"eval:start\";\n eval: {\n id: string;\n };\n agent: Agent;\n model?: string;\n attempt: number;\n experimentId?: string;\n} | {\n type: \"eval:complete\";\n result: EvalResult;\n} | {\n type: \"invocation:earlyExit\";\n evalId: string;\n experimentId?: string;\n} | {\n type: \"invocation:budgetExceeded\";\n budget: number;\n spent: number;\n} | {\n type: \"invocation:saved\";\n summary: InvocationSummary;\n} | {\n type: \"invocation:summary\";\n summary: InvocationSummary;\n} | {\n /** 该 Experiment 的 teardown(若声明)完成之后、invocation:summary 之前触发。 */\n type: \"experiment:complete\";\n experimentId: string;\n /** 该 Experiment 封口时刻,即它的 Run completedAt。 */\n completedAt: string;\n /** 当前 Record 读取出的精确复用 Attempt;不伪造成历史 EvalResult。 */\n reusedAttempts: readonly CurrentReusedAttemptReadback[];\n /** 该 Experiment 域产生的全部诊断;空集合传空数组,不省略字段。 */\n diagnostics: readonly DiagnosticRecord[];\n /** Run 级共享工作时间树;与 completedAt 同批封口。省略 = 本 Run 没有共享 activity。 */\n timings?: readonly TimingActivity[];\n /** 共享构建 provenance;与 timings 经 timingNodeId 关联。省略 = 本 Run 没有查询或构建过 BuildKey。 */\n sandboxBuilds?: readonly SandboxBuildRecord[];\n /** 项目名(来自 config.name),整次 Invocation 内所有 Experiment 共享同一个值。 */\n name?: LocalizedText;\n};\n// ───────────────────────── eval / experiment / config 定义 ─────────────────────────\n/**\n * 计分粒度题型:`defineEval` 恒 `\"pass\"`(通过制,一题一分,读通过率),`defineScoreEval` 恒\n * `\"score\"`(计分制,题内叠加挣分,读总分)。定义期事实,发现期从 `EvalDefinition.evaluationKind` 直接读取,\n * 不靠执行 `test()` 推断(见 docs/feature/experiments/score-points.md)。\n */\nexport type EvaluationKind = \"pass\" | \"score\";\n/** The live Runner derives Pass and Score from one sealed Assert-first entry sequence. */\nexport const EVALUATION_ALGORITHM = \"assert-first/v2\" as const;\n/** `fact-use/v3` remains only as the historical Record reader/writer bridge. */\nexport type EvaluationAlgorithm = typeof EVALUATION_ALGORITHM | \"assert-first/v1\" | \"fact-use/v3\";\n/**\n * 作者输入里的派生字段用模块私有诊断类型,而不是 `never`:错误会说明字段属于哪个阶段。\n * 这些 symbol 不从包入口导出,因此包外没有可写入的同类值。\n */\nconst EVAL_CONTRACT_DIAGNOSTIC: unique symbol = Symbol(\"niceeval.evalContractDiagnostic\");\nconst EVAL_DEFINITION: unique symbol = Symbol(\"niceeval.evalDefinition\");\nconst EXPERIMENT_CONTRACT_DIAGNOSTIC: unique symbol = Symbol(\"niceeval.experimentContractDiagnostic\");\nconst EXPERIMENT_DEFINITION: unique symbol = Symbol(\"niceeval.experimentDefinition\");\ntype IdComesFromFilePath = {\n readonly [EVAL_CONTRACT_DIAGNOSTIC]: \"id comes from the file path\";\n};\ntype EvaluationKindComesFromFactory = {\n readonly [EVAL_CONTRACT_DIAGNOSTIC]: \"evaluationKind comes from defineEval / defineScoreEval\";\n};\ntype ConfigHashComesFromPlanning = {\n readonly [EVAL_CONTRACT_DIAGNOSTIC]: \"configHash comes from run planning\";\n};\ntype ExperimentIdComesFromFilePath = {\n readonly [EXPERIMENT_CONTRACT_DIAGNOSTIC]: \"id comes from the file path\";\n};\n/** Eval 作者自行选择的字段;不包含路径、factory 和规划期事实。 */\nexport interface EvalAuthorFields {\n /** 一句话描述,展示在 `niceeval list` 和 view 里;纯说明,不影响调度或打分。 */\n description?: string;\n /** 标签,供 CLI `--tag` 过滤和 view 分类;与 id 前缀过滤是两套独立的筛选维度。 */\n tags?: string[];\n /**\n * 这道题贡献的 Sandbox 声明层。省略等价于空 command-only layer,不提供隐式 template。\n * 每个实际 Eval x Experiment 配对必须恰好一方提供 template-bearing layer。\n */\n sandbox?: SandboxLayer;\n /** Explicit, immutable Eval Plugin occurrences; no directory inheritance exists. */\n plugins?: readonly PluginInstance<\"eval\">[];\n /** 声明 Judge capability;true 继承 Experiment/Config,对象同时声明并覆盖它们。 */\n judge?: JudgeDeclaration;\n /** 覆盖 / 追加项目级 Config.reporters,只对这一条评估用例生效。 */\n reporters?: Reporter[];\n /** 覆盖项目级 / CLI 的单次 attempt 超时(毫秒),只对这一条评估用例生效。 */\n timeoutMs?: number;\n /** 任意附加元数据,作为 Attempt Provenance 保存,不参与调度或打分;供自定义 reporter 消费。 */\n metadata?: globalThis.Record<string, JsonValue>;\n /**\n * 调整 agent diff 的归因排除清单(仅 Sandbox 型;见 docs/feature/eval/README.md):两个数组都是\n * gitignore 风格 glob(workdir 相对)。默认排除 .git/node_modules/构建产物/包管理器缓存;\n * `ignore` 在默认清单上追加排除;`include` 优先级最高,把匹配路径显式加回。\n * 合成规则固定为「默认 ∪ ignore,再被 include 打洞」,清单在分类账锚点时冻结。\n */\n diff?: {\n include?: string[];\n ignore?: string[];\n };\n}\n/** Authors may return an Effect; Runner executes it in the Attempt's owning fiber. */\ntype EvalTestReturn = void | Promise<void> | Effect.Effect<void, unknown, never>;\n/** 作者输入:id 归 discovery、evaluationKind 归 factory、configHash 归 planning,作者都不能填写。 */\nexport type EvalInput<Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> = Omit<EvalAuthorFields, \"sandbox\"> & {\n sandbox?: Sandbox;\n id?: IdComesFromFilePath;\n evaluationKind?: EvaluationKindComesFromFactory;\n configHash?: ConfigHashComesFromPlanning;\n test(t: TestContext): EvalTestReturn;\n};\n/** 计分制作者输入,只有 test 的上下文不同。 */\nexport type ScoreEvalInput<Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> = Omit<EvalAuthorFields, \"sandbox\"> & {\n sandbox?: Sandbox;\n id?: IdComesFromFilePath;\n evaluationKind?: EvaluationKindComesFromFactory;\n configHash?: ConfigHashComesFromPlanning;\n test(t: ScoreTestContext): EvalTestReturn;\n};\n/** Factory 完成默认归一后的 Eval 字段;Definition 不再复用作者输入的 optional 半状态。 */\nexport interface EvalDefinitionFields<Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> {\n readonly description?: string;\n readonly tags: readonly string[];\n /**\n * 保留“作者省略”和“作者显式声明空 layer”的来源差异:Direct Agent 只允许前者,\n * Sandbox link 则把省略侧视为 command-only。不能在 factory 阶段补成 sandboxLayer()。\n */\n readonly sandbox?: Sandbox;\n readonly plugins: readonly PluginInstance<\"eval\">[];\n readonly judge?: JudgeDeclaration;\n readonly reporters: readonly Reporter[];\n readonly timeoutMs?: number;\n readonly metadata: Readonly<globalThis.Record<string, JsonValue>>;\n readonly diff: {\n readonly include: readonly string[];\n readonly ignore: readonly string[];\n };\n}\n/** Factory 产物保留精确 evaluationKind / context,并带模块私有品牌,不能由对象字面量伪造。 */\nexport interface EvalDefinition<Kind extends EvaluationKind, Context, Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> extends EvalDefinitionFields<Sandbox> {\n readonly evaluationKind: Kind;\n test(t: Context): EvalTestReturn;\n readonly [EVAL_DEFINITION]: true;\n}\nexport type AnyEvalDefinition = EvalDefinition<\"pass\", TestContext, SandboxLayer | undefined> | EvalDefinition<\"score\", ScoreTestContext, SandboxLayer | undefined>;\nconst EVAL_GROUP_DEFINITION: unique symbol = Symbol(\"niceeval.evalGroupDefinition\");\n/** A group member must not own a Sandbox template or instance lifecycle. Runtime discovery revalidates this. */\nexport type EvalGroupMemberSandbox = SandboxLayer<\"command-only\", \"prepare-only\"> | undefined;\nexport type EvalGroupMember = EvalDefinition<\"pass\", TestContext, EvalGroupMemberSandbox> | EvalDefinition<\"score\", ScoreTestContext, EvalGroupMemberSandbox>;\nexport interface EvalGroupInput<Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> {\n readonly evals: readonly [\n EvalGroupMember,\n ...EvalGroupMember[]\n ];\n readonly sandbox?: Sandbox;\n /** Required physical-instance policy when a grouped Sandbox becomes unavailable. */\n readonly onUnavailable: PluginOnUnavailable;\n readonly plugins?: readonly PluginInstance<\"group\">[];\n}\nexport interface EvalGroupDefinition extends EvalGroupInput {\n readonly [EVAL_GROUP_DEFINITION]: true;\n}\nexport function brandEvalGroupDefinition(value: EvalGroupInput): EvalGroupDefinition {\n Object.defineProperty(value, EVAL_GROUP_DEFINITION, { value: true });\n return Object.freeze(value) as EvalGroupDefinition;\n}\nexport function isEvalGroupDefinition(value: unknown): value is EvalGroupDefinition {\n return typeof value === \"object\" && value !== null &&\n (value as {\n readonly [EVAL_GROUP_DEFINITION]?: unknown;\n })[EVAL_GROUP_DEFINITION] === true;\n}\n/** @internal 唯一写入 Definition 私有品牌的构造辅助;不从公共入口导出。 */\nexport function brandEvalDefinition<Kind extends EvaluationKind, Context, Sandbox extends SandboxLayer | undefined>(value: EvalDefinitionFields<Sandbox> & {\n evaluationKind: Kind;\n test(t: Context): EvalTestReturn;\n}): EvalDefinition<Kind, Context, Sandbox> {\n Object.defineProperty(value, EVAL_DEFINITION, { value: true });\n return Object.freeze(value) as EvalDefinition<Kind, Context, Sandbox>;\n}\n/** Definition 之后由 discovery 一次性补齐的不可变事实。 */\nexport interface DiscoveredEvalFacts {\n readonly id: string;\n /** 定义文件所在目录(解析相对 workspace 用)。 */\n readonly baseDir: string;\n /** 定义文件绝对路径,用于内容指纹缓存。 */\n readonly sourcePath: string;\n /** 发现期经 loadJson/loadYaml/loadText 读入的项目内数据文件(内容已在内存,指纹哈希内容)。 */\n readonly loaderDataPaths: readonly string[];\n /**\n * 发现期经 `loadCriteria` 登记的判据树文件(只登记不读入)。指纹按「项目根相对路径 ×\n * 内容流式哈希」进,与 `loaderDataPaths` 分两格是因为这一格的内容从不进内存。\n */\n readonly criteriaPaths: readonly string[];\n /**\n * 发现期经 `loadPrivate` 登记的永不上传路径(只登记不读入)。指纹口径与 `criteriaPaths`\n * 相同,分键存放——private 与 verifier 同属判据面,但不与 criteria 混成一张表。\n */\n readonly privatePaths: readonly string[];\n /**\n * discovery 时捕获的规范化源码(归一化文本 + 项目相对路径 + SHA-256),见 `eval-source.ts`。\n * 同一文件里多个 eval(数组默认导出)共享同一份引用——哈希与内容天然相同,不重复读盘。\n */\n readonly source: CapturedEvalSource;\n /** @internal Original factory object, used only for Eval Group identity resolution. */\n readonly definition: AnyEvalDefinition;\n /** Eval Group planning facts, present only for discovered group members. */\n readonly evalGroup?: {\n readonly id: string;\n readonly evalIds: readonly string[];\n readonly definitionHash: string;\n readonly sandbox?: SandboxLayer;\n readonly onUnavailable: PluginOnUnavailable;\n readonly plugins: readonly PluginInstance<\"group\">[];\n readonly sourcePath: string;\n readonly baseDir: string;\n };\n}\n/** discovery 保留 factory 的 evaluationKind 判别、私有品牌与对应 test context。 */\nexport type DiscoveredEval = (EvalDefinition<\"pass\", TestContext> & DiscoveredEvalFacts) | (EvalDefinition<\"score\", ScoreTestContext> & DiscoveredEvalFacts);\n/** @internal discovery 动态边界的品牌守卫;普通对象即使字段同形也不通过。 */\nexport function isEvalDefinition(value: unknown): value is AnyEvalDefinition {\n return (typeof value === \"object\" &&\n value !== null &&\n (value as {\n readonly [EVAL_DEFINITION]?: unknown;\n })[EVAL_DEFINITION] === true);\n}\n/** @internal discovery 构造唯一的不可变阶段三值,不回写 Definition。 */\nexport function discoverEval(definition: AnyEvalDefinition, facts: DiscoveredEvalFacts): DiscoveredEval {\n const value = { ...definition, ...facts };\n Object.defineProperty(value, EVAL_DEFINITION, { value: true });\n return Object.freeze(value) as DiscoveredEval;\n}\n/**\n * `ExperimentDefinition.setup` / `teardown` 拿到的窄上下文。`progress` 更新本实验运行级\n * active 行的次要文本(短命状态,JSON 机器流不逐条输出),`diagnostic` 进运行级永久\n * 事件流(实验级钩子不属于任何单个 attempt,诊断不落 attempt 的 `result.json`;setup 抛错\n * 以每条 attempt 的结构化 `error` 落盘,失败仍可回顾)。钩子的起止本身由 runner 直接发布为\n * 运行级反馈,不依赖这里的 `progress`(见 docs/feature/experiments/cli.md「实验级 Hook 的显示」)。\n */\nexport interface ExperimentHookContext extends ScopedFeedback {\n readonly experimentId: string;\n /** 本实验解析后实际选中的 eval id 全集(evals 过滤器的求值结果)。 */\n readonly selectedEvalIds: readonly string[];\n /** 用户中断(Ctrl+C / kill)时 abort;长启动的 setup 应观察它提前退出。 */\n readonly signal: AbortSignal;\n}\n/** Shared lifecycle callback shape for author and Experiment Plugin hooks. */\nexport type ExperimentHook = (ctx: ExperimentHookContext) => void | Promise<void>;\n/** Experiment 作者自行选择的字段;不包含路径 id 与 factory 品牌。 */\nexport interface ExperimentAuthorFields {\n /** 一句话描述,展示在 view / CLI 里;纯说明,不影响调度或打分。 */\n description?: string;\n /**\n * 必填:这个实验跑哪个 agent(defineSandboxAgent / defineAgent 的产物)。运行配置的\n * agent 归属完全由这里决定——EvalDefinition 不声明 agent。\n */\n agent: Agent;\n /** 单个模型(agent 留空时实验决定);省略=用 agent 原生默认。跨模型对比写多个实验文件,别用数组。 */\n model?: string;\n /** 模型推理努力程度(如 \"low\"/\"medium\"/\"high\",取值由具体模型/adapter 决定);省略=用 agent 原生默认。经 ctx.reasoningEffort 透给 adapter 与 eval。 */\n reasoningEffort?: string;\n /**\n * 本实验的 Judge 执行配置。只覆盖 model / endpoint / credential selector / 调用预算,\n * rubric、材料与消费阈值仍由 Eval 的 Fact/use 声明拥有。各字段按\n * Experiment → Eval → Config 解析。\n */\n judge?: JudgeConfig;\n /** 实验条件(A/B 里的 feature flag),由实验文件声明;必须是可 JSON 序列化的值\n * (defineExperiment 解析时校验,非 JSON 直接报错),经 ctx.flags 透传给 adapter、\n * t.flags 暴露给 eval,并原样进入结果快照的 ExperimentRunInfo.flags。 */\n flags?: globalThis.Record<string, JsonValue>;\n /**\n * 报告归类标注:实验在各对比轴上的坐标(如 `{ line: \"codex\", memory: \"mempal\" }`)。\n * 值域 string | number(解析时校验)。与 `flags` 的分界是「会不会改变 attempt 里发生的事」:\n * labels 不透传 ctx / t(agent 和 eval 看不见)、不参与可比性配置(改它不作废已有结果),\n * 只原样投影进快照的 `ExperimentRunInfo.labels` 供报告维度(`label()` / `numericLabel()`)\n * 分组。`line` 键被默认报告识别:组内任一实验声明了它,散点按线归类并连线。\n * 见 docs/feature/experiments/library.md「labels」。\n */\n labels?: globalThis.Record<string, string | number>;\n /** 同一 eval 重复跑几次(结果各计一条 attempt);省略/CLI `--attempts` 覆盖时默认 1。 */\n attempts?: number;\n /** 一次重复(attempts > 1)里某次 attempt 通过后是否跳过剩余重复;省略默认 false(`attempts` 跑满、测完整通过率),\n * 显式打开用于「只想知道能不能过」的省钱场景。 */\n earlyExit?: boolean;\n /**\n * 这个实验覆盖哪些 eval:`\"*\"` 全部、字符串数组按 id 前缀、或自定义谓词(逐条收到发现并扇出后的\n * 只读 `EvalDescriptor`,不暴露路径 / 执行字段);省略等价于 `\"*\"`。谓词对本次 invocation 的\n * 候选 eval 各求值一次,解析结果作为内存中的 `selectedEvalIds` 计划——不是运行时反复调用的过滤器\n * (见 docs/feature/eval/library.md「EvalDescriptor」、docs/feature/experiments/library.md\n * 「evals:遍历发现结果,自定义选择」)。\n */\n evals?: \"*\" | readonly string[] | ((e: EvalDescriptor) => boolean);\n /** 覆盖项目级 / CLI 的单次 attempt 超时(毫秒),只对这个实验生效。 */\n timeoutMs?: number;\n /**\n * 本实验贡献的 Sandbox 声明层。它与每条选中 Eval 的同名字段逐配对链接;\n * 每个配对恰好一方提供 template-bearing layer。\n */\n sandbox?: SandboxLayer;\n /** Explicit Experiment Plugin occurrences, normalized by defineExperiment(). */\n plugins?: readonly PluginInstance<\"experiment\">[];\n /** 同一 Run 内复用沙箱;这种运行与历史携带双向隔离。 */\n sandboxReuse?: boolean;\n /**\n * 本实验的花费上限(USD)。调度器按「已完成 attempt 的实测花费」累计,到顶后跳过这个实验\n * 剩下未起飞的 attempt 并上报一次 `run:budgetExceeded`(已在飞的 attempt 仍会跑完)。\n */\n budget?: number;\n /**\n * 本实验自己的并发上限:调度器只对这个实验的 attempt 限流,同批其它实验不受影响,\n * 仍按全局并发(CLI / env / config / 沙箱默认)跑。用于串行化有共享状态的实验\n * (如跨 eval 累积记忆:`maxConcurrency: 1` 保证 attempt 按 eval 顺序一个个跑),\n * 或给撞 provider 限额的实验单独降速。名额与 attempt 同生命周期:从沙箱创建前一直握到\n * teardown 与沙箱销毁完成才归还,中途任何等待(含 turn 重试退避)都不松手——\n * `maxConcurrency: 1` 因此是严格的临界区,不会被同实验的下一个 attempt 提前闯入。\n */\n maxConcurrency?: number;\n /**\n * 本实验的失败分类器:识别以第三方错误形态浮出的自家共享基建死因(对自家隧道 host 的拒连\n * 一类),返回 `undefined` 表示「不认识,交给后续链路」。本实验任意 per-attempt 阶段的失败\n * 都会问到它;send 失败链上它排在 adapter 的 `classifySendFailure` 之前——按自家坐标过滤的\n * 特异性高于协议通用形状,两者同时认领时空间轴才赢得下来。分类器要快、纯、不抛错(抛错按\n * `undefined` 回落并被吞掉);只声明决策轴与 `reason` 词,重试与落闸策略归执行体。\n * 见 docs/feature/error-classification/library.md「实验 / eval 作者:声明死因的波及范围」。\n */\n classifyFailure?: AttemptFailureClassifier;\n /**\n * 实验级生命周期钩子对的 setup 侧:整场至多一次、宿主机侧,管「每实验一份、所有 attempt\n * 共享」的宿主机资源(隧道、mock server、license 租约)。本实验第一个通过派发许可的\n * attempt 触发(memoized,并发 attempt 等同一个结果;全部结果被 carry 携入时不执行)。\n * setup 不返回值;产物写模块级变量,`teardown` 与同文件 agent / sandbox 钩子从闭包读,\n * runner 不做值的中介。setup 抛错 → 本实验所有 Attempt 形成 `errored` Verdict\n * (code `\"experiment-setup-failed\"`、phase `\"experiment.setup\"`),同批其它实验不受影响。\n * 函数体不进 fingerprint,改了钩子逻辑用 `--rerun all` 明确全部重跑。\n * 见 docs/feature/experiments/architecture.md「实验级生命周期」。\n */\n setup?: ExperimentHook;\n /**\n * 实验级生命周期钩子对的 teardown 侧:本实验全部 attempt 收尾后执行(运行被中断也执行),\n * 当且仅当 setup 时点走到过——setup 抛错不豁免(半初始化现场同样要扫尾,teardown 对可能\n * 未赋值的闭包变量做防御),未声明 setup 不影响触发;一个 attempt 都不派发则跳过。\n * 抛错或超 30s 清理上限只记运行级 diagnostic(`experiment-teardown-failed`),不改判定。\n */\n teardown?: ExperimentHook;\n}\n/** 作者输入:id 只能由发现阶段从文件路径推导。 */\nexport type ExperimentInput = ExperimentAuthorFields & {\n id?: ExperimentIdComesFromFilePath;\n};\n/** Factory 完成默认归一后的 Experiment 字段;无默认语义的 Hook 仍保持作者声明。 */\nexport interface ExperimentDefinition {\n readonly description?: string;\n readonly agent: Agent;\n readonly model?: string;\n readonly reasoningEffort?: string;\n readonly judge?: JudgeConfig;\n readonly flags: Readonly<globalThis.Record<string, JsonValue>>;\n readonly labels: Readonly<globalThis.Record<string, string | number>>;\n readonly attempts: number;\n readonly earlyExit: boolean;\n readonly evals: \"*\" | readonly string[] | ((e: EvalDescriptor) => boolean);\n readonly timeoutMs?: number;\n /** 省略本身是 link 阶段需要的来源事实,不能在 Definition 中归一成显式空 layer。 */\n readonly sandbox?: SandboxLayer;\n readonly plugins: readonly PluginInstance<\"experiment\">[];\n readonly sandboxReuse: boolean;\n readonly budget?: number;\n readonly maxConcurrency?: number;\n readonly classifyFailure?: AttemptFailureClassifier;\n readonly setup?: ExperimentHook;\n readonly teardown?: ExperimentHook;\n readonly [EXPERIMENT_DEFINITION]: true;\n}\n/** @internal 仅 defineExperiment 写入私有品牌。 */\nexport function brandExperimentDefinition(value: Omit<ExperimentDefinition, typeof EXPERIMENT_DEFINITION>): ExperimentDefinition {\n Object.defineProperty(value, EXPERIMENT_DEFINITION, { value: true });\n return Object.freeze(value) as ExperimentDefinition;\n}\n/** @internal discovery 只接受 defineExperiment 的原始产物,不做结构性兼容。 */\nexport function isExperimentDefinition(value: unknown): value is ExperimentDefinition {\n return (typeof value === \"object\" &&\n value !== null &&\n (value as {\n readonly [EXPERIMENT_DEFINITION]?: unknown;\n })[EXPERIMENT_DEFINITION] === true);\n}\n/** @internal discovery 构造阶段三的不可变投影;不向 Definition 回写路径事实。 */\nexport function discoverExperiment(definition: ExperimentDefinition, source: {\n readonly id: string;\n readonly baseDir: string;\n readonly sourcePath: string;\n}): DiscoveredExperiment {\n const value = { ...definition, ...source };\n Object.defineProperty(value, EXPERIMENT_DEFINITION, { value: true });\n return Object.freeze(value) as DiscoveredExperiment;\n}\n/** 发现期运行形状:Definition 加入路径与来源;规划期 configHash 不在这里。 */\nexport interface DiscoveredExperiment extends ExperimentDefinition {\n readonly id: string;\n /** 定义文件所在目录;解析 Experiment layer 中的相对本地路径。 */\n readonly baseDir: string;\n /** 定义文件绝对路径;link 诊断标注声明来源。 */\n readonly sourcePath: string;\n}\n/**\n * 用户谓词(`ExperimentDefinition.evals`)能看到的唯一形状——发现并扇出后的显式白名单投影,不透传\n * `DiscoveredEval` 原对象(不暴露 `sourcePath` / `baseDir` / `test` / hooks 等内部路径与执行字段)。\n * `tags` 缺省为冻结空数组;`metadata` 原样引用作者声明的对象(至少浅冻结),供 `tags.includes(...)` /\n * `metadata.<key>` 判断(见 docs/feature/eval/library.md「EvalDescriptor」)。\n */\nexport interface EvalDescriptor {\n readonly id: string;\n readonly description?: string;\n readonly tags: readonly string[];\n /**\n * 计分粒度题型,`defineEval` → `\"pass\"`,`defineScoreEval` → `\"score\"`。定义期事实,\n * 每条发现出的 eval 上都有确定值。供 `ExperimentDefinition.evals` 谓词按题型过滤(见\n * docs/feature/experiments/score-points.md「横截面聚合:同型实验,各读各的」)。\n */\n readonly evaluationKind: EvaluationKind;\n readonly metadata?: Readonly<globalThis.Record<string, JsonValue>>;\n}\nexport interface Config {\n /** view/show 的项目默认报告。 */\n report?: ReportDefinition;\n /** view 的 host-owned closed visual token declaration. */\n theme?: ThemeDefinition;\n /**\n * 项目名,显示在 `niceeval view` 顶部 hero(`<h1>`),省略则回退到通用标题。\n * 可传字符串,或按 locale 提供多语言(如 `{ en: \"...\", \"zh-CN\": \"...\" }`),随 view 语言切换。\n */\n name?: LocalizedText;\n /** 上传进 Sandbox 的工作区根目录,省略则用项目根;评估用例的 sandbox 视图从这里起步。 */\n workspace?: string;\n /** 项目级默认 judge 配置(model / baseUrl / apiKeyEnv);EvalDef.judge 可按评估用例覆盖。 */\n judge?: JudgeConfig;\n /** 项目级默认 reporter 列表(如落盘 / 上传结果);EvalDef.reporters 会与它合并。 */\n reporters?: Reporter[];\n /** 项目级默认并发上限;CLI flag / experiment 的同名设置优先级更高(没有环境变量层)。 */\n maxConcurrency?: number;\n /** Run 级 Sandbox 镜像准备并发;与 attempt 并发独立,省略时安全默认 2。 */\n maxBuildConcurrency?: number;\n /** 项目级默认单次 attempt 超时(毫秒);CLI flag / experiment / EvalDef 的同名设置优先级更高。 */\n timeoutMs?: number;\n /**\n * OTLP 接收配置,niceeval 项目内唯一入口(不读 NICEEVAL_OTLP_* 环境变量)。\n * `port` 钉住接收端口(固定端口模式:长驻服务把 OTEL_EXPORTER_OTLP_ENDPOINT 一次性指到\n * http://localhost:<port>/v1/traces,跑多少次评估用例都不用改)。省略 = 每次运行动态分配\n * 临时端口(经 ctx.telemetry 交给 adapter)。代价:固定端口下同机同时只能跑一个 niceeval 进程,\n * 且该端口被别的进程占用时会报错——换一个空闲端口写回这里即可。\n * `host` 是报给 adapter 的接收端 hostname(而非监听地址,监听地址恒为 0.0.0.0):默认\n * \"127.0.0.1\"。只有作者已经提供受控 tunnel / 可达路由时才在这里覆盖;Docker Sandbox\n * 默认把 receiver 放在 Sandbox 内,不依赖隐式宿主 gateway。\n */\n telemetry?: {\n host?: string;\n port?: number;\n };\n /**\n * 内置价格表(`o11y/prices.json`)之上的用户覆盖 / 补充,按 model 查(见 Observability\n * · 用量与成本)。key 支持精确 model 名或 `provider/*` 通配(自托管/网关折扣按 provider 批量覆盖);\n * 精确 key 优先于通配。pricing 只驱动 `estimatedCostUSD` 的估算(`estimateCost`),与\n * `usage.costUSD`(网关实测)无关——两者独立并存,互不兜底。它是 runtime/config 价目表,\n * 不是 Report 的成本投影:Report 不消费该字段(Report 侧使用自己的 PricingProfile)。\n */\n pricing?: globalThis.Record<string, PriceOverride>;\n}\n/** 每百万 token 的美元单价;省略的桶退回 `inputPerMTok`(cache token 本质也是 input)。 */\nexport interface PriceOverride {\n /** 普通输入 token 单价。 */\n inputPerMTok: number;\n /** 输出 token 单价。 */\n outputPerMTok: number;\n /** cache 命中(读)token 单价,省略则退回 inputPerMTok。 */\n cacheReadPerMTok?: number;\n /** cache 写入 token 单价,省略则退回 inputPerMTok。 */\n cacheWritePerMTok?: number;\n}\n// ───────────────────────── 调度编排 ─────────────────────────\n/**\n * 进度行 / 日志里标识一个 run 配置的短名。有 experiment 时用其 basename(唯一,\n * 能区分同 agent 同 model 的实验变体,如 xxx 与 xxx--agents-md;与汇总表口径一致);\n * 无 experiment 时退回 agent/model。现有 live display 以它(拼 evalId)作行聚合 key,\n * 两处必须同源(改这里的格式要同步核对 live.ts 的 key 计算,见 memory 的\n * live-who-key-mismatch-freezes-rows —— 上一次格式改动漏改 live.ts 自己手写的两处曾冻结整表)。\n *\n * 这是展示 label,不是 identity —— 两个不同的 (evalId, attempt) 可能巧合算出同一个 who\n * (同 experiment 同 eval 的第 2 次重试与另一条 eval 的第 1 次重试,展示上都叫同一个 basename)。\n * 反馈系统新的事件/状态(见上面 `AttemptRef` / `AttemptKey` / `encodeAttemptKey`)一律用\n * `{experimentId, evalId, attempt}` 做 identity/Map key,`who` 只作为 `ActiveAttempt.who`\n * 之类的展示字段附着,不参与去重或查找 —— 把展示 label 错当成 identity key 曾经是 live 表格\n * 两个真实 bug 的根因(另见 memory 的 live-rows-fold-experiment-variants)。\n */\nexport function runWho(run: {\n agentName: string;\n model?: string;\n experimentId?: string;\n}): string {\n if (run.experimentId)\n return run.experimentId.split(\"/\").pop()!;\n return run.model ? `${run.agentName}/${run.model}` : run.agentName;\n}\n/** 一个 (agent, model, flags) 的运行配置 —— 由 CLI / 实验展开。 */\nexport interface AgentRun {\n readonly agent: Agent;\n readonly model?: string;\n readonly reasoningEffort?: string;\n readonly flags: Readonly<globalThis.Record<string, JsonValue>>;\n readonly attempts: number;\n readonly earlyExit: boolean;\n /** Experiment 的作者 layer;省略在 link 输入归一为 command-only。 */\n readonly sandbox?: SandboxLayer;\n /** Experiment Plugin occurrences carried into zero-resource pair link. */\n readonly plugins?: readonly PluginInstance<\"experiment\">[];\n /** Canonical, credential-free Experiment Plugin behavior projection. */\n readonly pluginBehavior?: JsonValue;\n readonly sandboxReuse?: boolean;\n /** Experiment 声明的 judge 覆盖;与 Eval/Config 的逐字段解析在 pair 规划期完成。 */\n readonly judge?: JudgeConfig;\n /**\n * 运行侧已求值的单 attempt 超时上限:只含 `--timeout` 与 experiment 字段两层\n * (`resolveRunTimeout`)。**不许把 config 的值提前物化进来**——eval 与 config 两层由\n * `resolveAttemptTimeout` 在派发时接上,提前物化会让 eval 自己声明的上限永久短路\n * (见 timeout.ts 与 memory/multi-source-field-resolution-order.md)。\n */\n readonly timeoutMs?: number;\n /** `timeoutMs` 那个值来自哪一层,供超时消息标注出处;省略按 `experiment` 读。 */\n readonly timeoutSource?: \"flag\" | \"experiment\";\n readonly budget?: number;\n readonly experimentId: string;\n /** Experiment 定义文件目录;只用于解析 template 中的相对宿主路径。 */\n readonly experimentBaseDir: string;\n /** Experiment 定义文件路径;link 诊断来源。 */\n readonly experimentSourcePath: string;\n /** 实验的一句话描述(ExperimentDef.description),进结果快照的 ExperimentRunInfo。 */\n readonly description?: string;\n /** 报告归类标注(ExperimentDef.labels),原样进 ExperimentRunInfo.labels;不透传 ctx / t。 */\n readonly labels?: Readonly<globalThis.Record<string, string | number>>;\n /**\n * 本次 invocation 解析后实际选中的 eval id 全集——CLI 在构造 AgentRun 时对候选 eval 各求值\n * 一次算好(见 `eval-selection.ts` 的 `resolveExperimentEvals()`),下游(dry-run、sandbox 查表、\n * fingerprint/carry、attempt 展开、hook ctx、落盘)只消费这份已解析结果,不重新调用用户谓词。\n * 保持顺序 = discovery 稳定顺序,去重。\n */\n readonly selectedEvalIds: readonly string[];\n /** 本配置自己的并发上限(来自 ExperimentDef.maxConcurrency):调度器为它单建信号量,\n * attempt 先过这道闸再占全局并发位;省略则只受全局并发约束。 */\n readonly maxConcurrency?: number;\n /** 实验级生命周期钩子对(来自 ExperimentDef.setup / .teardown):setup 整场至多一次,\n * 调度器 memoize 执行;teardown 在全部 attempt 收尾后执行,当且仅当 setup 时点走到过\n * (语义见 ExperimentDef 对应字段)。 */\n readonly setup?: ExperimentHook;\n readonly teardown?: ExperimentHook;\n /** 实验声明的失败分类器(来自 ExperimentDef.classifyFailure):turn 链上排在 adapter 之前,\n * 生命周期链上排在抛出点声明之后;产出的空间轴由止损闸在 attempt 封口消费。 */\n readonly classifyFailure?: AttemptFailureClassifier;\n}\nexport interface RunOptions<RecordError = never, RecordRequirements = never> {\n config: Config;\n evals: readonly DiscoveredEval[];\n agentRuns: readonly AgentRun[];\n /**\n * `--keep-sandbox` 的留存档位:failed 留 failed/errored(含硬超时的 errored),all 全部留;\n * 省略 = 全部销毁(留存永远是显式选择)。留存决策在 verdict 定稿的收尾点按档位提交,\n * 见 docs/feature/sandbox/architecture.md「留存(keep)与注册表」。\n */\n keepSandbox?: \"failed\" | \"all\";\n /** --rerun 的本次调用携带口径。 */\n rerun?: \"failed\" | \"all\";\n /** `--accept` 本次授权跨过的差异 selector(`config:<字段路径>` 等)。 */\n accept?: readonly string[];\n /**\n * 本地协调根(默认 `cwd/.niceeval`)。session、execution lock、teardown 登记和\n * kept-sandbox registry 都在这里;它不是 portable Record 的一部分。\n */\n coordinationRoot?: string;\n /**\n * 已签发的实际 portable Record root。Record lease sidecar 由这个 root 推导到\n * `.niceeval/coordination/records/<recordKey>`,不能由 Runner 的执行协调目录代替。\n */\n recordRoot: RecordRoot;\n /** CLI 为 `niceeval exp` 提供的持久 Session 索引;只观察调度事件,不参与锁/闸判定。 */\n session?: import(\"./session.ts\").SessionTracker;\n /**\n * The current Record coordinator calls this after its frozen-view readback\n * has established the exact reusable attempts. The CLI uses this one-way\n * Effect hand-off to emit the invocation plan; callers cannot provide or\n * alter Record reuse authority through it.\n */\n onCurrentRecordReusePlan?: (input: {\n readonly reused: number;\n readonly reusedFailures: readonly FailureDetail[];\n }) => Effect.Effect<void, never>;\n /**\n * 已注册的 reporter,携带 name/required 元数据(见 `ReporterRegistration`)。这是内部编排\n * 通道——调用方(今天只有 `cli.ts`)按来源(显式 --junit / 用户 `Config.reporters`)把裸\n * `Reporter` 各自包一层元数据后传进来;eval 级 `EvalDef.reporters`\n * 不经过这里,由 `runEvals()` 自己按 `scopeReporter()` 包装、统一记作 `required: false`\n *(见 run.ts 的 scopedSets 处理)。\n */\n reporters: ReporterRegistration[];\n maxConcurrency: number;\n /** Run 级 Sandbox 镜像 lookup/build 并发;省略时安全默认 2。 */\n maxBuildConcurrency?: number;\n signal?: AbortSignal;\n /**\n * 非沙箱 tracing agent 的 run 级共享 OTLP 接收池(runEvals 创建并回收;\n * 每个 agent 一个 receiver,attempt 之间共享 —— 被测应用是长驻进程,端点不能随 attempt 换)。\n */\n otelPool?: import(\"../o11y/otlp/turn-otel.ts\").OtelReceiverPool;\n /**\n * Run 级共享构建准备。只含携带规划后仍需 fresh 执行的 BuildKey;\n * 省略时 runEvals 从 pair-owned ProviderPlan 自动收集\n * (Compose works 默认接 dockerComposeBuildProvider)。测试可显式注入假 provider。\n * 共享构建不占 attempt 并发位,不计入 executionMs。\n */\n buildPreparation?: {\n readonly works: readonly import(\"../sandbox/build-coordinator.ts\").SandboxBuildWork[];\n readonly provider: import(\"../sandbox/build-coordinator.ts\").SandboxBuildProvider;\n /** `${experimentId}|${evalId}` → 该 pair 的 fresh attempt 依赖的 BuildKey。 */\n readonly pairBuildKeys: Readonly<globalThis.Record<string, readonly BuildKey[]>>;\n readonly maxConcurrency?: number;\n readonly buildTimeoutMs?: number;\n readonly prepareBudgetMs?: number;\n };\n /**\n * Run 级 Agent artifact prepare 协调器。省略时 runEvals 为有 staged installer 的\n * sandbox agent 新建并接真 Run timing recorder;测试可注入。\n */\n artifactPrepare?: import(\"../agents/provisioner.ts\").ArtifactPrepareCoordinator;\n}\n/** 调度器内部的一次尝试:eval × run × 第几轮。 */\nexport interface Attempt {\n readonly evalDef: DiscoveredEval;\n readonly run: AgentRun;\n readonly attempt: number;\n /** agent+model+evalId,用于首过即停。 */\n readonly key: string;\n readonly fingerprint: string;\n readonly configHash: string;\n /** Planning 时唯一解析并冻结的 Judge capability/config。 */\n readonly judge: ResolvedJudgeConfig | undefined;\n /** 该 pair 的唯一、不可变规划产物;fingerprint / create / reuse 全部消费同一份值。 */\n readonly plan: LinkedRunPlan;\n /** 同一 Experiment 本次选中 Eval 的完整 plan 映射;run.json 不从当前 pair 猜全局默认值。 */\n readonly sandboxPlansByEval: Readonly<globalThis.Record<string, JsonValue>>;\n /**\n * 构造 fresh attempt plan 时即算好的 Attempt 定位符(不是完成后写回):由 invocation 的\n * 预分配 runId 与 attempt 身份派生,贯穿执行、留存登记与落盘——登记项、run 收尾反馈与\n * result.json 从第一次写入起就用同一个值。裸 run(无 experimentId)不产出。\n */\n locator?: AttemptLocator;\n}\n// ───────────────────────── 反馈 profile / 事件 / reducer 状态 ─────────────────────────\n// `niceeval exp` 的 human / json 反馈模型(见 docs/feature/experiments/cli.md)。\n// 本节定义 coordinator、纯 reducer、renderer 与 runner emitter 共用的事件及状态契约;实现分别\n// 位于 `runner/feedback/` 与 `runner/attempt.ts` / `runner/run.ts`,没有另一套阶段性事件模型。\n/** 两种反馈形态(见 docs/feature/experiments/cli.md「每条命令一个人读 text 面,`--json` 是机器面」):\n * `--json` 即机器面,否则人读文本(TTY live 面板 / 非 TTY 追加流,由渲染层内部按 `io.stderr.isTTY`\n * 再分派,不是第三个 profile)。 */\nexport type OutputProfile = \"human\" | \"json\";\n// 反馈系统的 attempt 阶段与落盘 / envelope 用同一套 `LifecyclePhase` 闭集(见上),\n// 不再有独立的 dashboard 词表;`waiting for a slot` 是 attempt 开始前的调度态,不属于闭集;\n// `passed` / `failed` / `errored` / `reused` / `early-exit` / `budget-unstarted` 是 outcome,\n// 发生在阶段结束后,也不塞进 phase 闭集。\n/**\n * 反馈系统里一次 attempt 的稳定身份:reducer 用它做 active map 的 key、事件的关联字段。\n * 只含调度身份三元组 —— 不含 agent/model/展示 label(那是 `who`,来自 `runWho()`,\n * 见该函数注释:展示 label 不能当 identity key 用,folding 两个不同 config 到同一个 key\n * 曾经就是 live 表格两个真实 bug 的根因),也不含落盘 `AttemptLocator`\n * 所需的持久化 `runId`。完成/failure 事件在 locator 确定后直接携带\n * 派生好的 `AttemptLocator` 字符串,反馈层\n * 不重新推导身份 —— 两个同名概念的 identity 类型故意不同名,以免和落盘身份互相看错)。\n */\nexport interface AttemptRef {\n /** 未挂靠 experiment 时为 undefined(直接指定 agent/model 跑,不经过 experiment);不用空字符串占位。 */\n experimentId?: string;\n evalId: string;\n /** 0-indexed,与 `EvalResult.attempt` / `AttemptLocator` 的 attempt 同一口径。 */\n attempt: number;\n}\n/** `AttemptRef` 的确定性字符串编码,只作 `RunFeedbackState.active` 的 Map key 使用 ——\n * 不是展示文本(那是 `who`),也不是 `AttemptLocator`(后者是 Record 的持久化身份)。 */\nexport type AttemptKey = string & {\n readonly __brand: \"AttemptKey\";\n};\n/** 由 `AttemptRef` 派生 `AttemptKey`;同一身份永远编码出同一个 key。 */\nexport function encodeAttemptKey(ref: AttemptRef): AttemptKey {\n return `${ref.experimentId ?? \"\"}|${ref.evalId}|${ref.attempt}` as AttemptKey;\n}\n/**\n * dashboard 当前可见的一个 active slot。`phase` 是正式状态,`detail` 只是该 phase 下的次要文本\n *(如 `running` 阶段的 `tool: shell` / `turn 2`)—— 两者是两个字段,不把 adapter 的 raw progress\n * string 直接当状态用;phase 变化时 `detail` 清空(旧阶段的次要文本不该残留到新阶段)。\n */\nexport interface ActiveAttempt {\n identity: AttemptRef;\n /** 展示 label,等价 `runWho()` 的结果;渲染要用,但绝不作为 identity/key。 */\n who: string;\n phase: LifecyclePhase;\n /**\n * 这条 attempt 被派发的墙钟时间(epoch ms,取 `attempt:start` 的 `at`)—— active 行时间列的\n * **唯一**基准,`attempt:phase` 不得改写它:live 面板不做 spinner 动画,存活性完全由这一列\n * 持续增长证明(见 docs/feature/experiments/cli.md「active 行的列序」),一列会归零的时间既\n * 证明不了存活,也让人误以为这条 eval 重跑了。阶段各自的耗时不进这里——它由结果的\n * `timing.phases` 完整落盘,live 面板要回答的是「这条还活着吗、跑了多久、正在干什么」。\n */\n startedAt: number;\n detail?: string;\n}\n/** 实验级钩子只有 setup 与它返回的 teardown 两员,同一实验内两者永不并发\n * (teardown 在全部 attempt 收尾后才触发),所以运行级行按 experimentId 建 key 就够。 */\nexport type ExperimentHookName = \"setup\" | \"teardown\";\n/**\n * dashboard 当前可见的一个实验级钩子运行级行(见 docs/feature/experiments/cli.md\n * 「实验级 Hook 的显示」)。与 `ActiveAttempt` 分开建模:钩子不属于任何单个 attempt、不占并发位,\n * 也不参与 `RunFeedbackState` 的计数不变量——等待 setup 的\n * attempt 保持 `queued`,这行就是「为什么它们还在排队」的解释。`detail` 来自实验级\n * `ctx.progress`,后一条覆盖前一条。\n */\n/**\n * dashboard 当前可见的 judge 预检运行级行(见 docs/feature/experiments/cli.md「judge 预检的显示」)。\n * 与 `ActiveExperimentHook` 分开建模:预检是 invocation 级、不挂任何 experimentId,只有一个在飞\n * 实例(整次运行至多一次预检)。`startedAt` 用于渲染运行级行持续增长的耗时,证明它还活着。\n */\nexport interface ActivePrecheck {\n /** 预检开始的墙钟时间(epoch ms),用于渲染运行级行的耗时。 */\n startedAt: number;\n}\nexport interface ActiveExperimentHook {\n experimentId: string;\n hook: ExperimentHookName;\n /** 钩子开始的墙钟时间(epoch ms),用于渲染运行级行的耗时。 */\n startedAt: number;\n detail?: string;\n /** 强杀后启动自愈补执行的 teardown(见 `DurableFeedbackEvent` 的 \"experiment-hook\" 变体)。 */\n recovery?: boolean;\n}\n/**\n * dashboard 当前可见的一个「等待并行 run」运行级行(见 docs/feature/experiments/cli.md\n * 「等待并发 run 的显示」)。用例锁的等待粒度是单个 `(experimentId, evalId)`,但运行级行按\n * experimentId 聚合展示——一个实验可能同时有多个用例撞锁,只占一行,给出条数与代表持有方。\n */\nexport interface ActiveLockWait {\n experimentId: string;\n /** 当前仍在等待的 evalId → 该用例开始等待的时间与持有方身份。`size` 就是运行级行要展示的\n * 等待条数;为空表示这个实验当前没有在等的用例(条目仍保留在 map 里,供非 TTY 聚合文案\n * 读取下面两个累计字段,直到下一次 \"started\" 事件开启新窗口时清零)。 */\n waiting: ReadonlyMap<string, {\n startedAt: number;\n holderPid?: number;\n holderHost?: string;\n }>;\n /** 本次「有等待用例」窗口内,累计已经 resolved 且携入 reused 的 attempt 数——供非 TTY 聚合\n * 收尾行(如 `lock wait resolved · compare/codex (2 carried · 1 to run, 1m 34s)`)读取。 */\n resolvedCarried: number;\n /** 同上,累计已经 resolved 且转为自跑(进入 queued)的 attempt 数。 */\n resolvedDispatched: number;\n}\n/**\n * dashboard 当前可见的一条 Run 级 activity 行(共享构建、制品准备等)。\n * 不占 attempt active 位,也不进五项恒等式计数;人读文本用 producer 的 `label`,不查\n * LifecyclePhase 锚点表(见 docs/feature/experiments/architecture.md「Run 级共享准备」)。\n */\nexport interface ActiveRunActivity {\n /** 与 TimingActivity.id 对齐,同一 Run 内唯一。 */\n id: string;\n /** ActivityKey;机器面分组用,不驱动人读标签切换。 */\n key: string;\n /** producer 写下的有界人读标签;展示层原样用。 */\n label: string;\n /** 开始的墙钟时间(epoch ms),用于渲染运行级行持续增长的耗时。 */\n startedAt: number;\n}\n/**\n * 一次失败/错误的永久通知:human 撤下 dashboard 后追加一行、JSON 机器流立即追加一行,都读它。\n * 字段全部结构化(locator / identity / verdict / phase 都是具名字段),profile renderer 不需要\n * 解析 `reason` 之外的任何文本就能拼出机器可读的输出。\n */\nexport interface FailureDetail {\n /** Canonical current Record Attempt locator. */\n locator: string;\n identity: AttemptRef;\n who: string;\n verdict: \"failed\" | \"errored\";\n /** 一层可行动摘要(gate 断言名、error 消息……),不是完整 stack/transcript;详情走 `niceeval show`。 */\n reason: string;\n /** failed / unavailable 时的结构化主 Fact/use 摘要。 */\n fact?: PrimaryFactSummary;\n /** 仅 errored 使用:结构化执行错误发生时所在的阶段。failed 是断言 outcome,不带 phase。 */\n phase?: LifecyclePhase;\n /** 仅 errored 且没有结构化主断言摘要(真正的执行错误,而非 assertion-unavailable)时携带:\n * `AttemptError.code` 原样透出,human 单行事实行拼成 `errored · <phase> · <code>`。 */\n code?: string;\n /** 完整时间归属;attempt 形态同时投影上面的 phase,run 形态保留共享 timing node。 */\n origin?: TimingOrigin;\n}\n/** 带发生时间的失败通知;复用失败以 FailureDetail 静态进入 plan,不伪装成刚发生的事件。 */\nexport interface FailureNotice extends FailureDetail {\n at: number;\n}\n/**\n * 去重后的即时通知(info/warning/error):相同 `key` 的通知只保留一条,`count` 累加受影响次数\n *(见 docs/feature/experiments/cli.md「什么动态更新,什么逐条追加」的去重规则)。\n * `data` 携带结构化字段(如 budget 的 experimentId/spent/unstarted),renderer 直接读取,\n * 不解析 `message`(`message` 只是 human 展示用的一句话)。\n */\n/**\n * 止损闸落闸诊断的稳定词法(`--json` 的 `warning.code`、`run.json` 的诊断 `code`,契约见\n * docs/feature/error-classification/architecture.md「止损执行体」)。emitter(run.ts)与两种\n * profile 的 renderer 共用这一个常量,谁都不在自己这边再写一遍字面量。\n */\nexport const HALT_DIAGNOSTIC_CODE = \"dispatch-halted\";\nexport const COORDINATION_RECOVERED_CODE = \"coordination-recovered\";\nexport type DiagnosticSeverity = \"info\" | \"warning\" | \"error\";\nexport interface DiagnosticNotice {\n at: number;\n key: string;\n /** 对外的稳定词法(`--json` 的 notice/warning `code`、human 通知行标题);省略 = 与 `key` 相同。\n * `key` 可以把折叠身份(experimentId / evalId)编进去,`code` 恒是干净字面量。 */\n code?: string;\n severity: DiagnosticSeverity;\n message: string;\n /** 相同 key 累计出现的次数,由 reducer 去重时递增。 */\n count: number;\n identity?: AttemptRef;\n data?: Readonly<globalThis.Record<string, JsonValue>>;\n}\n/** 运行完整性结论,独立于 verdict 计数。CI 退出码不能只看 failed/errored ——\n * budget 未覆盖全部计划、用户中断、required reporter 失败都必须让 completion 非「complete」。 */\nexport type CompletionStatus = \"complete\" | \"incomplete\" | \"interrupted\";\n/** 一个 reporter 收尾失败的记录;`required` 区分它是否让 completion 判红(见 ReporterRegistration)。 */\nexport interface ReporterError {\n reporter: string;\n required: boolean;\n message: string;\n}\nexport interface InvocationCompletion {\n status: CompletionStatus;\n /** budget 耗尽导致未派发的 attempt 数;不含首过即停省略的次数(见 `earlyExitUnstarted`)。 */\n unstarted: number;\n /** 首过即停在已知 verdict 下主动省略的计划次数 —— 这是「省下的重复验证」,不是「未完整覆盖」。 */\n earlyExitUnstarted: number;\n reporterErrors: readonly ReporterError[];\n}\n/**\n * 事件 → 状态的纯 reducer 产出(见 `src/runner/feedback/reducer.ts`)。所有计数、active map、\n * cost 累计、failure/diagnostic 去重都只在 reducer 里算一次;human/json renderer 只读取\n * 这份状态,不各自维护第二份推导。\n *\n * `total = reused + running + elsewhere + queued + passed + failed + errored + skipped`\n * (八项恒等式,见 docs/feature/experiments/cli.md「等待并发 run 的显示」)在处理完每一个事件\n * 之后都成立,是 reducer 的不变量:任何一次迁移都是「从一项减 x、往另一项加 x」,不存在两项\n * 同时计数或都不计数的中间态(见 reducer.test.ts 的表驱动用例,每一步都断言,不只在流程末尾\n * 断言一次)。\n */\nexport interface RunFeedbackState {\n total: number;\n reused: number;\n running: number;\n /** 正被并行 Invocation 持锁运行、本次在等待中的用例的 attempt 数(用例锁,见 `lock-wait`\n * 变体与 docs/feature/experiments/cli.md「等待并发 run 的显示」);与 `queued` 互斥——\n * `queued` 是「等本进程并发位/setup」,`elsewhere` 是「等别的进程」。\n * 恒等式(见接口注释)在处理完每一个事件之后都成立。 */\n elsewhere: number;\n queued: number;\n /** 以下四项是本次派发并已了结的 attempt 按 verdict 的划分——reducer 不保留一个笼统的\n * 「完成数」:盯着运行的人问的是「到现在为止挂了几个」,一个合计数回答不了。携入结果的\n * verdict 留在 `reused`,不摊进这四项(计数口径与成本口径一致地区分「本次派发」与\n * 「缓存携入」,见 docs/feature/experiments/cli.md「运行中的 live 面板」)。 */\n passed: number;\n failed: number;\n errored: number;\n /** 本次不产生 verdict 的了结:eval 自身 skip、首过即停省略的轮次、budget 未派发。\n * 它们不冒充 `passed`/`failed`;三者彼此的区别由结束结论与题目级 `eval` 事件给出。 */\n skipped: number;\n /** attempt:early-exit 事件的累计次数(首过即停省略 + fail-fast 未派发;后者由 fail-fast\n * diagnostic 的 count 单独区分,见 cli.ts 的 assembleRunCompletion)。 */\n earlyExitSkipped: number;\n /**\n * `attempt:early-exit` 事件的原始次数,按 `${experimentId ?? \"\"}|${evalId}` 分组(见\n * `feedback/eval-conclusions.ts` 的 `evalConclusionKey`)。这份计数**未**剔除 fail-fast 的\n * 份额(fail-fast 未派发同样发出这个事件类型,见 run.ts)——`evalConclusionRows()` 消费时\n * 对照 `diagnostics` 里的 `fail-fast:` 记录减去那部分,得到真正的首过即停省略次数,据此判断\n * 是否给出 `reason=early_exit`。不得把这份原始计数直接当作首过即停次数使用。\n */\n earlyExitByEval: ReadonlyMap<string, number>;\n elapsedMs: number;\n /** 仅本次实际派发 attempt 的 token;carry 结果的历史 usage 不进入这里。 */\n newTokenCount?: number;\n /** 仅本次实际派发 attempt 的 `estimatedCostUSD` 累计(价目表估算口径,与 newTokenCount 同口径);observed cost 不进入这里。 */\n estimatedCostUSD?: number;\n active: ReadonlyMap<AttemptKey, ActiveAttempt>;\n /** 在飞的 judge 预检运行级行(见 `DurableFeedbackEvent` 的 \"precheck\" 变体):`started` 置位、\n * `done` 清空。预检发生在任何 attempt 派发之前、作用于整次 invocation,不属于任何 attempt,\n * 也不参与五项恒等式计数——预检期间 attempt 保持 `queued`,\n * 这行就是「为什么它们还在排队」的解释。undefined = 当前没有在飞的预检。 */\n activePrecheck?: ActivePrecheck;\n /** 在飞的实验级钩子(experimentId → 运行级行状态),由 \"experiment-hook\" 事件增删、\n * \"experiment:progress\" 更新 detail(见 docs/feature/experiments/cli.md「实验级 Hook 的显示」)。 */\n experimentHooks: ReadonlyMap<string, ActiveExperimentHook>;\n /** 在飞的用例锁等待,按 experimentId 聚合(见 `ActiveLockWait`、docs/feature/experiments/cli.md\n * 「等待并发 run 的显示」)。由 \"lock-wait\" 事件增删/累计;没有等待用例的实验不出现在这个 map 里。 */\n lockWaits: ReadonlyMap<string, ActiveLockWait>;\n /** 在飞的 Run 级 activity(id → 运行级行状态),由 \"run-activity\" 事件增删。不占 attempt\n * active 位,也不进计数恒等式(见 `ActiveRunActivity`)。 */\n runActivities: ReadonlyMap<string, ActiveRunActivity>;\n failures: readonly FailureNotice[];\n /** 本次实际派发后产生的去重失败数;复用失败不消耗 profile 的流式输出上限。 */\n freshFailureCount: number;\n diagnostics: readonly DiagnosticNotice[];\n /** 留存授予的沙箱(--keep-sandbox);run 摘要后各 profile 追加输出。 */\n kept: readonly KeptNotice[];\n}\n/** 一条留存授予的永久通知(见 docs/feature/sandbox/cli.md「run 收尾输出」)。 */\nexport interface KeptNotice {\n at: number;\n locator: AttemptLocator;\n identity: AttemptRef;\n who: string;\n verdict: Verdict;\n provider: string;\n sandboxId: string;\n enter?: string;\n}\n/** 一次 run 的初始计划。复用只暴露数量;失败明细仅用于静态初始化终局清单。 */\nexport interface RunFeedbackPlan {\n shape: InvocationShape;\n /**\n * 声明了 `maxConcurrency` 的实验 → 各自的上限。只收声明了的实验(未声明的实验的有效宽度就是\n * 全局值,列出来只是噪音);一个都没声明时整个字段省略——`--json` 的 `start` 事件因此不会\n * 出现空对象,human `PLAN` 行也不加附注(见 docs/feature/experiments/cli.md\n * 「运行中的 live 面板」的 `concurrency` 附注段)。\n */\n experimentConcurrency?: Readonly<globalThis.Record<string, number>>;\n /** 携入(carry)结果数,直接计入 `RunFeedbackState.reused`,不需要重新调度。 */\n reused: number;\n /** 复用结果中的失败;plan 时静态注入,不产生“刚发生”的失败事件。 */\n reusedFailures?: readonly FailureDetail[];\n}\n/**\n * 只影响 dashboard 当前帧、reducer 不为它保留历史的事件:新值使旧值失去意义,所以覆盖而不是\n * 追加(见 docs/feature/experiments/cli.md「什么动态更新,什么逐条追加」的判断标准)。\n * `attempt:early-exit` 同样折进这一组 —— 它不打印永久行,只把已知 verdict 的省略次数收进\n * `skipped`(见 reducer 实现)。\n */\nexport type AttemptLifecycleEvent = {\n type: \"attempt:queued\";\n at: number;\n identity: AttemptRef;\n who: string;\n} | {\n type: \"attempt:start\";\n at: number;\n identity: AttemptRef;\n who: string;\n phase: LifecyclePhase;\n} | {\n type: \"attempt:phase\";\n at: number;\n identity: AttemptRef;\n phase: LifecyclePhase;\n} | {\n type: \"attempt:progress\";\n at: number;\n identity: AttemptRef;\n detail: string;\n} | {\n type: \"attempt:complete\";\n at: number;\n identity: AttemptRef;\n who: string;\n verdict: Verdict;\n /** 本次 attempt 的输入 + 输出 token;缺失表示 provider 未报告。 */\n tokenCount?: number;\n /** 本次 attempt 的价目表估算成本(`EvalResult.estimatedCostUSD` 口径);observed cost 不携带。 */\n estimatedCostUSD?: number;\n} | {\n type: \"attempt:early-exit\";\n at: number;\n identity: AttemptRef;\n who: string;\n};\n/**\n * 实验级 `ctx.progress` 的短命投影:只覆盖对应运行级行的 `detail`,不追加永久行——与\n * `attempt:progress` 同一判断标准(新值使旧值失去意义)。对应的运行级行不存在时静默忽略。\n */\nexport interface ExperimentProgressEvent {\n type: \"experiment:progress\";\n at: number;\n experimentId: string;\n detail: string;\n}\n/**\n * 运行级时钟 tick:唯一允许更新 `RunFeedbackState.elapsedMs` 的事件,由 coordinator 的定时器产出\n *(见 plan 的可注入 `FeedbackIO` clock)。reducer 保持纯函数,不自己读 `Date.now()`,elapsedMs\n * 因此只能通过事件携带的值前进 —— 这也让 reducer 测试可以喂任意 elapsed 值,不必真的等待。\n */\nexport interface FeedbackTickEvent {\n type: \"tick\";\n at: number;\n elapsedMs: number;\n}\n/**\n * 永久事件:human 撤下 dashboard 后追加一行、JSON renderer 在 stdout 追加 NDJSON 事件,\n * 一旦发生就不会被后续状态覆盖掉(与上面按当前帧覆盖的 `AttemptLifecycleEvent` 相对)。\n * 字段全部结构化,profile renderer 不解析 `message` 之外的任何文本、不解析 i18n 字符串。\n */\nexport type DurableFeedbackEvent = {\n type: \"plan\";\n at: number;\n plan: RunFeedbackPlan;\n} | {\n type: \"failure\";\n at: number;\n /** Writer-issued short alias for the exact durable AttemptId. */\n locator: string;\n identity: AttemptRef;\n who: string;\n verdict: \"failed\" | \"errored\";\n reason: string;\n fact?: PrimaryFactSummary;\n phase?: LifecyclePhase;\n code?: string;\n origin?: TimingOrigin;\n} | {\n type: \"diagnostic\";\n at: number;\n key: string;\n /** 对外稳定词法(见 `DiagnosticNotice.code`);省略 = 与 `key` 相同。 */\n code?: string;\n severity: DiagnosticSeverity;\n message: string;\n /** attempt 级诊断的归属身份。运行级诊断(实验闸 / eval 闸这类不属于任何单条 attempt 的\n * 事实)不许伪造 identity——它们的 experimentId / evalId 走 `data` 的同名字段,\n * `--json` 的 `warning` 事件两处都读、identity 优先。 */\n identity?: AttemptRef;\n data?: Readonly<globalThis.Record<string, JsonValue>>;\n}\n/**\n * emitter 对每一个因 budget 到顶而不派发的 attempt 各发一次(与 `attempt:early-exit` 同构,\n * 见 reducer 实现);`unstarted` 是 emitter 自己记的、发出这条时的累计未派发数,写进\n * `DiagnosticNotice.data` 供 renderer 直接读取,不是 reducer 用来计算「这次要挪多少」的输入\n *(reducer 只按事件触发次数折算,保持纯函数不需要额外记住上一次的值)。\n */\n | {\n type: \"budget-exhausted\";\n at: number;\n experimentId: string;\n spent: number;\n unstarted: number;\n}\n/** 一次留存授予(--keep-sandbox):run 摘要后 human/json 两种形态都追加输出(见 docs/feature/sandbox/cli.md)。 */\n | {\n type: \"kept\";\n at: number;\n locator: AttemptLocator;\n identity: AttemptRef;\n who: string;\n verdict: Verdict;\n provider: string;\n sandboxId: string;\n enter?: string;\n}\n/**\n * 实验级钩子(`ExperimentDef.setup` / 它返回的 teardown)的起止,由 runner 在钩子真正\n * 开始/结束时各发一次(见 docs/feature/experiments/cli.md「实验级 Hook 的显示」)。`failed`\n * 只标记钩子自身的结局——setup 失败的每条 attempt 仍以 \"failure\" 事件逐条给出。human TTY\n * 用它维护运行级 active 行(不写 scrollback),append-only profile 起止各追加一行。\n */\n | {\n type: \"experiment-hook\";\n at: number;\n experimentId: string;\n hook: ExperimentHookName;\n status: \"started\" | \"done\" | \"failed\";\n /** 只在 done / failed 上出现:钩子从开始到结束的耗时。 */\n durationMs?: number;\n /**\n * 只在 `hook: \"teardown\"` 的 `status: \"started\"` 上可能出现:标注这是强杀后的启动自愈\n * 补执行(见 docs/feature/experiments/architecture.md「强杀后的收尾兜底」),不是本次 run\n * 正常触发的收尾。省略 = 正常路径。\n */\n recovery?: boolean;\n}\n/**\n * judge 配置预检的起止,由 runner 在探测真正开始/结束时各发一次(见 docs/feature/experiments/\n * cli.md「judge 预检的显示」)。预检作用于整次 invocation、发生在任何 attempt 派发之前,不属于\n * 任何单个 attempt,也不触碰五项恒等式计数不变量。human\n * TTY 用它维护一条运行级 active 行(不写 scrollback),append-only profile 起止各追加一行。\n * `failed` 只标记预检本身的结局(与 `done` 一样清掉运行级行);受影响 eval 每条 attempt 的\n * `errored`(`judge-precheck-failed`)仍由 \"failure\" 事件逐条给出。\n */\n | {\n type: \"precheck\";\n at: number;\n status: \"started\" | \"done\" | \"failed\";\n durationMs?: number;\n}\n/**\n * 用例锁等待的起止(见 docs/feature/experiments/cli.md「等待并发 run 的显示」)。粒度是单个\n * `(experimentId, evalId)`——同一 eval 的全部 attempt 作为一个整体一起等、一起解决,不按\n * attempt 拆分。emitter(run.ts)只在这批 attempt 需要重查携带时才发这对事件:全携带用例\n * 不取锁、无竞争的全新取锁(锁目录里从没出现过这个 key)都不发——静态携带规划的结论不可能\n * 过时,没有理由重新读盘。撞上新鲜锁(真正等待)与接管一把无人竞争的过期锁(从未真正等待,\n * `waitedMs` 因此可能接近 0)都算「需要重查」,统一走这对事件——即便是瞬时接管,这批\n * attempt 也必须先经 \"started\" 迁入 `elsewhere`,\"resolved\" 才能把它们正确迁回\n * `reused`/`queued`,否则它们会永远卡在 `queued`、打破五项恒等式。\n */\n | {\n type: \"lock-wait\";\n at: number;\n experimentId: string;\n evalId: string;\n status: \"started\" | \"resolved\";\n /** status 为 \"started\" 时给出:锁持有方身份,以及这次撞锁进入 elsewhere 等待的 attempt 数\n * (该 eval 本轮需要真实派发、被这把锁挡住的 attempt 数;省略按 1 处理)。 */\n holderPid?: number;\n holderHost?: string;\n attempts?: number;\n /** status 为 \"resolved\" 时给出:锁释放后重查携带,分别有多少 attempt 从 elsewhere 迁入\n * reused(carried)、多少迁入 queued 转为自跑(dispatched)——`attempts` 下可能两者都非零\n * (部分携入部分补跑)。`--json` 的 `lock_wait` 事件把两者折成单一 `resolution` 字段:\n * `dispatched > 0` 记 \"dispatched\"(这个用例仍需要真实派发,等待没有让它完全免于执行),\n * 否则记 \"carried\"(全部由携带满足,零新成本)。 */\n carried?: number;\n dispatched?: number;\n waitedMs?: number;\n}\n/**\n * Run 级开放 activity 的起止(共享构建、制品准备等)。`key` / `label` 原样来自 producer:\n * 人读面与 `--json` 对未登记 key 用 `label` 通用投影,不需要 switch 穷尽,也不进\n * LifecyclePhase 锚点标签表。human TTY 用它维护运行级 active 行(不占 attempt slot、\n * 成功不写 scrollback);非 TTY 与 `--json` 起止/失败各追加一行有界永久事件。\n */\n | {\n type: \"run-activity\";\n at: number;\n id: string;\n key: string;\n label: string;\n status: \"started\" | \"done\" | \"failed\";\n /** 只在 done / failed 上出现。 */\n durationMs?: number;\n} | {\n type: \"interrupted\";\n at: number;\n} | {\n type: \"reporter-error\";\n at: number;\n reporter: string;\n required: boolean;\n message: string;\n} | {\n type: \"summary\";\n at: number;\n summary: InvocationSummary;\n completion: InvocationCompletion;\n} | {\n type: \"receipt\";\n at: number;\n receipt: InvocationReceipt;\n};\n/**\n * runner → feedback coordinator 的内部事件通道,与公共 `Reporter` / `ReporterEvent` 分开:\n * profile renderer 只消费这里的具名字段,不解析 `ReporterEvent` 里的 i18n 文案或表格列宽\n *(见 docs/feature/experiments/cli.md「输出流和落盘节奏」)。\n */\nexport type RunFeedbackEvent = AttemptLifecycleEvent | ExperimentProgressEvent | FeedbackTickEvent | DurableFeedbackEvent;\n"]}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["types.ts"],"names":[],"mappings":";;;AAuvBA,4DAGC;AACD,sDAKC;AAED,kDAMC;AA0CD,4CAMC;AAED,oCAIC;AAkKD,8DAGC;AAED,wDAMC;AAED,gDAQC;AAkGD,wBAQC;AA8LD,4CAEC;AAt0BD,mDAAmD;AACtC,QAAA,aAAa,GAAG,kBAAkB,CAAC;AAChD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACU,QAAA,qBAAqB,GAAG,EAAE,CAAC;AA0IxC,0FAA0F;AAC7E,QAAA,oBAAoB,GAAG,iBAA0B,CAAC;AAG/D;;;GAGG;AACH,MAAM,wBAAwB,GAAkB,MAAM,CAAC,iCAAiC,CAAC,CAAC;AAC1F,MAAM,eAAe,GAAkB,MAAM,CAAC,yBAAyB,CAAC,CAAC;AACzE,MAAM,8BAA8B,GAAkB,MAAM,CAAC,uCAAuC,CAAC,CAAC;AACtG,MAAM,qBAAqB,GAAkB,MAAM,CAAC,+BAA+B,CAAC,CAAC;AAyFrF,MAAM,qBAAqB,GAAkB,MAAM,CAAC,8BAA8B,CAAC,CAAC;AAiBpF,SAAgB,wBAAwB,CAAC,KAAqB;IAC1D,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,qBAAqB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAwB,CAAC;AACvD,CAAC;AACD,SAAgB,qBAAqB,CAAC,KAAc;IAChD,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAC7C,KAEC,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC;AAC3C,CAAC;AACD,oDAAoD;AACpD,SAAgB,mBAAmB,CAAiF,KAGnH;IACG,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAA2C,CAAC;AAC1E,CAAC;AAyCD,oDAAoD;AACpD,SAAgB,gBAAgB,CAAC,KAAc;IAC3C,OAAO,CAAC,OAAO,KAAK,KAAK,QAAQ;QAC7B,KAAK,KAAK,IAAI;QACb,KAEC,CAAC,eAAe,CAAC,KAAK,IAAI,CAAC,CAAC;AACtC,CAAC;AACD,uDAAuD;AACvD,SAAgB,YAAY,CAAC,UAA6B,EAAE,KAA0B;IAClF,MAAM,KAAK,GAAG,EAAE,GAAG,UAAU,EAAE,GAAG,KAAK,EAAE,CAAC;IAC1C,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,eAAe,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/D,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAmB,CAAC;AAClD,CAAC;AAiKD,2CAA2C;AAC3C,SAAgB,yBAAyB,CAAC,KAA+D;IACrG,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,qBAAqB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAyB,CAAC;AACxD,CAAC;AACD,8DAA8D;AAC9D,SAAgB,sBAAsB,CAAC,KAAc;IACjD,OAAO,CAAC,OAAO,KAAK,KAAK,QAAQ;QAC7B,KAAK,KAAK,IAAI;QACb,KAEC,CAAC,qBAAqB,CAAC,KAAK,IAAI,CAAC,CAAC;AAC5C,CAAC;AACD,4DAA4D;AAC5D,SAAgB,kBAAkB,CAAC,UAAgC,EAAE,MAIpE;IACG,MAAM,KAAK,GAAG,EAAE,GAAG,UAAU,EAAE,GAAG,MAAM,EAAE,CAAC;IAC3C,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,qBAAqB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,CAAyB,CAAC;AACxD,CAAC;AAmFD,2DAA2D;AAC3D;;;;;;;;;;;;;GAaG;AACH,SAAgB,MAAM,CAAC,GAItB;IACG,IAAI,GAAG,CAAC,YAAY;QAChB,OAAO,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAG,CAAC;IAC9C,OAAO,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC;AACvE,CAAC;AA6LD,uDAAuD;AACvD,SAAgB,gBAAgB,CAAC,GAAe;IAC5C,OAAO,GAAG,GAAG,CAAC,YAAY,IAAI,EAAE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAgB,CAAC;AAClF,CAAC;AAgHD;;;;;GAKG;AACH;;;;GAIG;AACU,QAAA,oBAAoB,GAAG,iBAAiB,CAAC;AACzC,QAAA,2BAA2B,GAAG,wBAAwB,CAAC","sourcesContent":["// runner 域类型:结果 / 汇总 / reporter 契约,eval / experiment / config 定义,\n// 以及调度器的编排类型(AgentRun / RunOptions / Attempt)。\nimport type { Effect } from \"effect\";\nimport type { JsonValue, LocalizedText, ScopedFeedback, SourceArtifact, Verdict } from \"../shared/types.ts\";\nimport type { AttemptFailureClassifier } from \"../shared/failure-class.ts\";\nimport type { O11ySummary, StreamEvent, TraceSpan, Truncation, Usage } from \"../o11y/types.ts\";\nimport type { Agent, AgentSetupManifest } from \"../agents/types.ts\";\nimport type { SandboxLayer } from \"../sandbox/layer.ts\";\nimport type { LinkedRunPlan } from \"../sandbox/plan.ts\";\nimport type { BuildKey } from \"../sandbox/identity.ts\";\nimport type { EvaluationFactResult, DiffArtifact, JudgeConfig, JudgeDeclaration, ResolvedJudgeConfig, PrimaryFactSummary, ScoreFactAttemptOutcome, ScoreFactUseResult, VerdictFactUseResult, } from \"../assertions/types.ts\";\nimport type { ScoreTestContext, TestContext } from \"../context/types.ts\";\nimport type { CapturedEvalSource } from \"./eval-source.ts\";\nimport type { AttemptLocator } from \"../attempt-locator.ts\";\nimport type { RecordRoot } from \"../record/platform/root.ts\";\nimport type { CurrentReusedAttemptReadback } from \"./reuse-readback.ts\";\nimport type { PluginInstance, PluginOnUnavailable } from \"../plugin/contracts.ts\";\n// Report 的公开子路径是独立预编译单元;这里依赖作者 API 的公开 aggregate,避免把\n// host implementation 或旧的 JSX renderer type 拉回 runner 边界。\nimport type { ReportDefinition } from \"../report/index.ts\";\nimport type { ThemeDefinition } from \"../report/theme.ts\";\n// ───────────────────────── 结果 / 报告 ─────────────────────────\n/**\n * 解析后运行配置的**穷尽可序列化投影**——记录这次运行实际生效的值,不是原始 `ExperimentDef`\n * (函数与 hooks 无法忠实落盘,存「原样」只能存谎)。`model` 与 `agent` 只在快照顶层存在,\n * 这里不复制(见 docs/feature/record/architecture.md「run.json」)。\n */\nexport interface ExperimentRunInfo {\n description?: string;\n reasoningEffort?: string;\n flags?: globalThis.Record<string, JsonValue>;\n /** 报告归类标注(ExperimentDef.labels 原样投影);不透传运行时,不参与可比性配置。 */\n labels?: globalThis.Record<string, string | number>;\n /** Credential-free Plugin lifecycle behavior projection. */\n plugins?: JsonValue;\n attempts: number;\n earlyExit: boolean;\n timeoutMs?: number;\n budget?: number;\n maxConcurrency?: number;\n /** Experiment SandboxLayer 的纯数据身份;Direct 也以 command-only 的完整身份记录。 */\n sandboxLayer: JsonValue;\n /** 每个已选择 Eval 的完整 pair-owned physical plan;Direct 也有显式投影。 */\n sandboxPlansByEval: globalThis.Record<string, JsonValue>;\n /** Sandbox 是否在同一次 Run 内复用。 */\n sandboxReuse?: boolean;\n /** 跨 Invocation 共享外部状态的互斥声明;只记录稳定、非凭据 key。 */\n sharedState?: SharedStateConfig;\n /** 解析后的 Judge 执行身份;只记录凭据选择器名,不记录凭据。 */\n judge?: Pick<JudgeConfig, \"model\" | \"baseUrl\" | \"apiKeyEnv\" | \"timeoutMs\">;\n /**\n * Agent Ensure 与精确配对 installer 的静态身份投影;按声明顺序完整落盘。\n * 实际 artifact digest/platform 属运行 provenance,不进入这里。\n */\n agentInstalls: JsonValue[];\n}\nexport interface SandboxRunInfo {\n provider: string;\n params?: globalThis.Record<string, JsonValue>;\n fingerprint?: string;\n}\n/**\n * Runner 保留的 attempt 生命周期锚点——闭集,不是扩展点(见 docs/feature/record/architecture.md\n * 「两层时间模型」)。计时(`phases[].name`)、错误与诊断的 attempt 锚点(见 TimingOrigin)、\n * live 当前步骤都由 Runner 绑定这同一个闭集;author、Adapter 与 provider 不能新增成员。\n * 可扩展的工作计时走开放 activity key,不进本词表。\n */\nexport type LifecyclePhase = \n// 运行级(派发前至多一次,宿主机侧;仅错误归因)\n\"judge.precheck\" // 判分预检;预检失败时是含 judge 断言的 eval 全部 attempt 的错误锚点\n// 实验级(整场一次,宿主机侧;仅错误/诊断归因)\n | \"experiment.setup\" // ExperimentDef.setup;setup 抛错时是本实验所有 attempt 的错误锚点\n | \"experiment.teardown\" // ExperimentDef.teardown;失败只产生运行级 diagnostic\n// 主链:从排队到 trace collect,覆盖到判定与主证据收集完成,按执行序\n | \"sandbox.queue\" // 等待并发信号量(调度等待,唯一不属于某个 owner 的成员)\n | \"sandbox.create\" // provider 物化沙箱实例(共享构建不在这里,它在 Run 级 activity)\n | \"sandbox.prepare\" // 两层作者 layer 的 prepare 链\n | \"sandbox.prepare.eval\" // 仅错误/诊断归因,不单列计时\n | \"sandbox.prepare.group\" // Eval Group 作者与 Plugin command 的错误/诊断归因\n | \"sandbox.prepare.experiment\" // 仅错误/诊断归因,不单列计时\n | \"agent.ensure\" // Runner 的 probe → 缺失才 install → 同一 probe 复检\n | \"workspace.baseline\" // 变更分类账锚点(runner 私有 git ledger 首笔 commit)\n | \"agent.setup\" // Adapter runtime 配置 / 凭据 / state setup\n | \"telemetry.configure\" // tracing 出口配置\n | \"eval.run\" // 整段 test(t),含所有 send 与手工命令\n | \"agent.run\" // 嵌套在 eval.run 内:adapter send 期间打开;只用于错误/诊断归因,不单列计时条目\n | \"workspace.diff\" // 从分类账折叠 agent 归因增量\n | \"assertions.evaluate\" // 断言 finalize + 判定,含 judge 调用\n | \"telemetry.collect\" // OTLP receiver settle / collect\n// 收尾段:无论主链成败都执行,不计入 durationMs 口径,按执行序\n | \"agent.teardown\" | \"sandbox.cleanup\" // 两层作者 layer 已登记 cleanup 全局 LIFO\n | \"sandbox.suspend\" // 留存提交后 provider 把现场转入休眠(docker stop / e2b pause)\n | \"sandbox.stop\"; // provider 销毁沙箱;与 sandbox.suspend 同一 attempt 互斥\n/**\n * 开放的工作计时节点,Run 与 attempt 共用同一形状(见 docs/feature/record/architecture.md\n * 「TimingActivity」)。`key` 是非空、以 `.` 分段的稳定机器 key;未知 key 原样保留并可通用展示。\n * offset 相对所在时钟域(RunMeta.timings 或单个 attempt)的单调时钟起点。\n */\nexport interface TimingActivity {\n /** 所在时钟域内唯一,供 origin、provenance 与展示层稳定引用;不作为跨 Run 身份。 */\n id: string;\n /** ActivityKey;官方词表见 architecture.md,第三方用自己的命名空间。 */\n key: string;\n /** 采集端写入的有界、脱敏人读标签;展示层不解析它重建语义。 */\n label: string;\n /** 相对所在时钟域单调时钟起点的偏移。 */\n startOffsetMs: number;\n durationMs: number;\n failed?: true;\n children?: TimingActivity[];\n /** key = \"agent.turn\" 时存在;把 runner 的 send 墙钟包络与 trace.json 中同一轮的 spans 显式关联。 */\n sessionIndex?: number;\n turnIndex?: number;\n turnId?: string;\n traceId?: string;\n traceAttribution?: \"traceparent\" | \"window\" | \"none\";\n /** key = \"agent.turn\" 时存在,该轮 `Turn.usage` 落盘原样(有记录才写)。 */\n usage?: Usage;\n /** key = \"sandbox.command\" 时的有界脱敏摘要;环境变量值与 stdout/stderr 不进入时间树。 */\n command?: {\n display: string;\n exitCode?: number;\n /** 是否由 checked run*OrThrow 公开调用产生;show/report 用它与 exitCode 推导展示语义。 */\n checked?: boolean;\n /** 这条命令这次生效的时限与它来自哪一层;四层解析链一个上限都没声明时缺席。 */\n limit?: CommandLimitAttribution;\n };\n}\n/**\n * 一条命令生效的时限归属(词表单源在 docs/feature/sandbox/architecture.md\n * 「时限归属:attempt deadline 是唯一默认」)。命令节点带着它,读者才不用靠「停在整 1m 0s」\n * 这种巧合反推是谁掐断了命令。\n */\nexport interface CommandLimitAttribution {\n /**\n * 生效上限的来源层:`attempt-deadline` 是 attempt 自己的线(未显式传 `timeout` 的命令拿它的\n * 剩余量),`command-timeout` 是用户给这条命令显式传的 `timeout`,`provider-limit` 是 provider\n * 固有的会话上限——它在派发前就按环境约束报出来,attempt 层不会撞上,列在词表里是给读面认。\n */\n source: \"attempt-deadline\" | \"command-timeout\" | \"provider-limit\";\n /** 该层对这条命令实际生效的上限,毫秒(attempt deadline 记的是命令开始时的剩余量)。 */\n limitMs: number;\n /** 这条命令正是撞上这条线才失败的(非零退出与传输失败都不是)。 */\n timedOut?: true;\n}\n/**\n * 错误与诊断的归属(见 docs/feature/record/architecture.md「TimingOrigin」)。\n * attempt 支绑定 Runner 打开的生命周期锚点;run 支指向 RunMeta.timings 里的 activity。\n */\nexport type TimingOrigin = {\n scope: \"attempt\";\n /** runner 在错误 / 诊断发生时已打开的生命周期锚点;producer 不能自行指定。 */\n phase: LifecyclePhase;\n /** 可选细化:锚点下具体的 activity(如失败的那条 sandbox.command)。 */\n timingNodeId?: string;\n} | {\n scope: \"run\";\n /** 指向 RunMeta.timings 里的 activity(如失败的 sandbox.build)。 */\n timingNodeId: string;\n};\n/**\n * 共享构建的 provenance,每个实际查询或构建过的 BuildKey 一条。\n * 时间只保存在 `RunMeta.timings`,本表经 `timingNodeId` 关联,不复制 duration。\n */\nexport interface SandboxBuildRecord {\n buildKey: string;\n provider: string;\n status: \"hit\" | \"built\" | \"failed\" | \"cancelled\";\n /** 关联 RunMeta.timings 里对应的 sandbox.build activity。 */\n timingNodeId: string;\n locator?: JsonValue;\n inputs: JsonValue;\n error?: {\n code: string;\n message: string;\n cause?: {\n name?: string;\n code?: string;\n message: string;\n };\n };\n}\n/** Runner 阶段计时,按执行顺序;只记录实际发生的阶段(见 docs/feature/record/architecture.md)。 */\nexport interface PhaseTiming {\n name: LifecyclePhase;\n /** 相对本 attempt 单调时钟起点的阶段起点;与 children 使用同一量化时钟。 */\n startOffsetMs: number;\n /** 阶段耗时;失败阶段计到抛错或超时中断时。 */\n durationMs: number;\n /** 该阶段抛错或被超时中断。主链至多一条,其后无主链条目;收尾阶段各自独立标记,不改判定。 */\n failed?: true;\n /** 锚点内的 activity 子树,offset 相对本 attempt 的单调时钟起点。 */\n children?: TimingActivity[];\n}\n/**\n * `commands.json` 的一条落盘记录(见 docs/feature/record/architecture.md「commandsjson」):\n * 四个公开 `Sandbox.run*()` 方法的最外层调用返回时,Runner 在 `CommandResult` 交还调用方\n * **之前**登记的完整证据——Eval 后续即使只把 `.slice(-N)` 拼进异常消息,这份证据仍然完整。\n * 成功与非零退出都记录;provider 内部实现步骤与 Agent 自己调用的 shell 不经过这层包装,\n * 不伪装成这里的命令。\n */\nexport interface CommandExitEvidence {\n /** 与 `PhaseTiming.children` 中 `key === \"sandbox.command\"` 的 `TimingActivity.id` 相同,唯一关联命令证据卡与 `--timing` 的 command 节点。 */\n timingNodeId: string;\n /** runner 在命令返回那一刻已经打开的生命周期阶段。 */\n phase: LifecyclePhase;\n /** 与该 `TimingActivity.command.display` 同一份有界脱敏命令摘要;不含 env value。 */\n display: string;\n exitCode: number;\n /** 是否由 checked run*OrThrow 公开调用产生;非零 + checked 才是失败展示语义。 */\n checked: boolean;\n /** `CommandOptions.sensitiveValues` 命中的已知值已替换为 `<redacted>`;超过每流 64 KiB 时\n * 在落盘序列化时截断,见 `truncated`。 */\n stdout: string;\n /** 同 `stdout` 的已知敏感值边界与截断规则。 */\n stderr: string;\n /** `stdout` / `stderr` 超过每流上限时的结构化截断标记(`path` 为 `\"stdout\"` 或 `\"stderr\"`);\n * 只由 writer 在落盘时刻写入,运行时与调用方拿到的仍是完整值。 */\n truncated?: Truncation[];\n}\n/** `commands.json` 的落盘形状。 */\nexport type CommandsArtifact = CommandExitEvidence[];\n/**\n * 使 attempt 无法正常完成的唯一致命执行错误(见 docs/feature/record/architecture.md 的\n * `AttemptError`)。`message` 是人可读的一层原因(不拼整份 SDK response);完整 stack 单放\n * `stack`,`niceeval show @locator` 首页展开、终端即时反馈不整段打印。默认报告只显示 `message`。\n */\nexport interface AttemptError {\n /** 稳定、可供 CI/Agent 分支处理的机器码;未知异常使用 `\"unexpected-error\"`。 */\n code: string;\n /** 人可读的一层原因,不拼接整份 SDK response。 */\n message: string;\n /**\n * 错误归属。attempt 内错误由 runner 绑定当时打开的生命周期锚点(attempt 形态);\n * attempt 开始前的共享构建失败引用 Run timing node(run 形态),不伪造 attempt 锚点。\n */\n origin: TimingOrigin;\n /** 原异常有 stack 时保留,供 show 展开;终端即时反馈不整段打印。 */\n stack?: string;\n /** 下层 SDK/OS 错误的有限摘要。 */\n cause?: {\n name?: string;\n code?: string;\n message: string;\n };\n /**\n * 超时打断产生的 `errored` 专用:这次撞的是哪层时限、上限值多少、值从哪一层解析而来。\n * 三样一起落盘,报错行与 `show --timing` 照实印这三样;归属规则单源在\n * docs/feature/sandbox/architecture.md「时限归属:attempt deadline 是唯一默认」。\n */\n timeout?: TimeoutAttribution;\n}\n/** 一次超时的归属事实,由 runner 在把 attempt 转成 `errored` 时写下。 */\nexport interface TimeoutAttribution {\n /**\n * 触发层:`attempt-deadline` 是 attempt 自己的上限(沙箱内一切时限都从它派生),\n * `command-timeout` 是用户给单条命令显式传的 `timeout`。provider 固有的会话上限在派发前\n * 就按环境约束报出来(见 `assertDeadlineFitsProvider`),attempt 层不会撞上它。\n */\n trigger: \"attempt-deadline\" | \"command-timeout\";\n /** 该层实际生效的上限,毫秒。 */\n limitMs: number;\n /**\n * 值来自哪一层:`attempt-deadline` 取 `timeoutMs` 解析链四层之一,`command-timeout` 只有\n * 命令显式声明一个来源。\n */\n source: \"flag\" | \"experiment\" | \"eval\" | \"config\" | \"command\";\n}\n/**\n * 不一定改变 verdict、但运行后仍需回顾的有界诊断(见 docs/feature/record/architecture.md 的\n * `DiagnosticRecord`)。`level` 表达写入方观察到的运行影响,不是 verdict 的别名 ——\n * passed / failed / errored 任一 verdict 都可以带 cleanup / teardown 诊断。\n * 与运行级的 `DiagnosticNotice` 不同,这条挂在单个 attempt 结果或 RunMeta 上落盘。\n */\nexport interface DiagnosticRecord {\n code: string;\n level: \"warning\" | \"error\";\n /**\n * 诊断归属。attempt 诊断由 runner 绑定当时打开的锚点;Run 诊断可引用 Run timing node,\n * 也可只带 `experiment.teardown` 这类归因锚点;没有 timing 记录的第三方 producer 可省略。\n */\n origin?: TimingOrigin;\n /** 写入时观察到的原始有界描述;不包含修复动作或呈现文案。 */\n detail: string;\n /** 支撑 code 的结构化原始上下文。 */\n context?: Readonly<globalThis.Record<string, JsonValue>>;\n /** 相同 dedupeKey 折叠后的出现次数;省略等于 1。 */\n count?: number;\n}\n/**\n * `--accept` 跨过的一条具名差异(`EvalResult.carriedAccepting` 的成员)。\n * `selector` 与 CLI 上写下的那个字面量同一个词表,`from` / `to` 是完整值的字符串投影;\n * 某一侧没有这个键(新增 / 删除)时该侧省略。\n */\nexport interface CarriedAcceptance {\n selector: string;\n from?: string;\n to?: string;\n}\n/** 一条人工接受结果的差异摘要。与 manifest 相减的 selector/value 投影保持同一词表。 */\nexport interface AcceptedDifference {\n selector: string;\n from?: string;\n to?: string;\n}\n/** 旧 opaque carryEpoch 指纹迁移到当前确定性指纹的已知来源。 */\nexport interface FingerprintMigration {\n kind: \"opaque-carry-epoch\";\n fingerprint: string;\n algorithmVersion: number;\n coverageVersion: number;\n}\n/** `niceeval accept @<locator>` 写入新结果的来源与重锚审计记录。 */\nexport interface AcceptedResult {\n /** 被复制的历史 attempt locator。 */\n locator: string;\n /** 被复制条目的旧指纹。 */\n fingerprint: string;\n /** 当前项目按本次配置重算出的指纹。 */\n acceptedFingerprint: string;\n /** 新旧 manifest/config 的完整差异清单。 */\n differences: AcceptedDifference[];\n}\n/** `niceeval exp rename` 写入新结果的来源与身份审计记录。 */\nexport interface RenamedResult {\n /** 被重绑结果原属的 experimentId。 */\n experimentId: string;\n /** 被重绑结果在旧结果树中的 locator。 */\n locator: string;\n /** 迁移前结果的 fingerprint。 */\n fingerprint: string;\n /** 发生重绑的时刻。 */\n at: string;\n}\n/** 自动重试吸收的一次物理 send 失败;不进入逻辑会话事件流。 */\nexport interface RetryAttemptRecord {\n sessionIndex: number;\n turnIndex: number;\n /** 同一逻辑 send 内从 0 开始;0 是首次发送。 */\n sendAttempt: number;\n startedAt: string;\n durationMs: number;\n failure: {\n type: \"agent-send-failed\";\n acceptance: \"rejected\";\n message: string;\n process?: {\n exitCode?: number;\n signal?: string;\n };\n };\n classification: {\n retryable: true;\n scope: \"attempt\" | \"eval\" | \"experiment\";\n reason?: string;\n };\n events: StreamEvent[];\n usage?: Usage;\n}\nexport interface EvalResult {\n id: string;\n description?: string;\n experimentId?: string;\n experiment?: ExperimentRunInfo;\n agent: string;\n model?: string;\n verdict: Verdict;\n fingerprint?: string;\n /** 产生本 Attempt 判定与计分结果的固定求值算法。 */\n evaluationAlgorithm: EvaluationAlgorithm;\n /** 产出该结果时的 Run 级配置身份。 */\n configHash?: string;\n attempt: number;\n /** 本 attempt 开始的墙钟时刻(ISO);view 按 eval 粒度展示「何时跑的」。 */\n startedAt?: string;\n /**\n * 不透明的 Attempt 定位符;由完整 durable AttemptId 确定性派生。\n */\n locator?: string;\n /**\n * `locator` 的来源 Run 身份。niceeval writer 对 fresh 条目恒写;carry 与 publish 原样保留,\n * 使同一 attempt 在多份落盘中仍共享一个 locator 身份。旧记录缺失时 reader 会沿\n * `artifactBase` 回溯来源,无法回溯才退回当前 Run。\n */\n locatorRunId?: string;\n durationMs: number;\n /** 自 sandbox.create 起、排除并发排队和收尾的执行耗时;旧记录缺失时携带保守回退 durationMs。 */\n executionMs?: number;\n /** The complete native Fact graph, persisted directly in result.json. */\n factResults: readonly EvaluationFactResult[];\n factUses: readonly (VerdictFactUseResult | ScoreFactUseResult)[];\n /** Structured Score Fact outcome for score Eval terminal semantics. */\n scoreResult?: ScoreFactAttemptOutcome;\n /**\n * 题型:`defineEval` → `\"pass\"`,`defineScoreEval` → `\"score\"`,定义期事实,与\n * `EvalDescriptor.evaluationKind` 同源。schema 18 必填。\n */\n evaluationKind: EvaluationKind;\n /** 自动重试吸收的物理 send 失败,按发生顺序完整保留。 */\n retryAttempts?: RetryAttemptRecord[];\n usage?: Usage;\n /**\n * 价目表估算成本,恒等于 `estimateCost(model, usage, config.pricing)`——永远独立计算,\n * 与 `usage.costUSD`(网关/adapter 显式回报的 observed 成本)是两个并存事实,互不覆盖、\n * 互不兜底:observed 存在时 estimatedCostUSD 也照常按 runtime/config price table 估算。\n */\n estimatedCostUSD?: number;\n /** 使 attempt 进入 `errored` 的唯一致命执行错误(结构化);默认报告显示 `error.message` 一层原因。 */\n error?: AttemptError;\n /** 本 attempt 的诊断(与 verdict 独立);teardown / cleanup 失败等挂在这里,不改判定。 */\n diagnostics?: readonly DiagnosticRecord[];\n /** Runner 阶段计时,按执行顺序;只记录实际发生的阶段(见 docs/feature/record/architecture.md)。 */\n phases?: PhaseTiming[];\n skipReason?: string;\n events?: StreamEvent[];\n /** test 引用到的 eval 源码(按 loc 收集),供 view 渲染 github-diff 式代码视图。 */\n sources?: SourceArtifact[];\n o11y?: O11ySummary;\n /** agent 经 OpenTelemetry 导出的运行追踪(有 tracing 能力且收到 span 时)。 */\n trace?: TraceSpan[];\n /** 本 attempt 的 agent setup 实际装了什么(Skill / native plugin / MCP / Python plugin);不参与评分。 */\n agentSetup?: AgentSetupManifest;\n /**\n * attempt 级聚合的证据覆盖(各 turn 的最差值,unavailable < partial < complete);必填。\n */\n evidenceCoverage: import(\"../agents/types.ts\").EvidenceCoverage;\n /**\n * 沙箱型 attempt 的执行环境标识:provider 名与实例 id,用于关联 provider 侧日志与留存现场;\n * remote 型 agent 无此字段。`kept` 表示运行收尾时按 --keep-sandbox 留存了沙箱;之后的存活\n * 状态归 `niceeval sandbox list` 回答,本记录一次写成、不回写。\n */\n sandbox?: {\n provider: string;\n sandboxId: string;\n kept?: true;\n /** 本次 Attempt 使用了同一 Invocation 中已创建的 Sandbox。 */\n reused?: true;\n /** 本次 Invocation 内该 Sandbox 的稳定编号(从 1 开始)。 */\n reuseSandbox?: number;\n /** 此 Sandbox 承接的 Attempt 序号(从 1 开始)。 */\n reuseOrdinal?: number;\n };\n /** agent 归因增量:逐 send 窗口的 delta 序列(落盘为 diff.json;文件级视图由读取面派生)。 */\n diff?: DiffArtifact;\n /** Sandbox 命令的 stdout/stderr 证据(落盘为 commands.json);成功与非零退出都记录,见 `CommandExitEvidence`。 */\n commands?: CommandExitEvidence[];\n rawTranscript?: string;\n /** 携带条目(--resume 合入)专用:artifact 目录(相对结果根目录),指向原快照里的落盘。 */\n artifactBase?: string;\n /**\n * 仅经 `--accept` 授权跨过指纹差异携入时留下的审计痕迹:跨过的每条差异各一项。\n * 它让「这条是在哪个口径下被采信的」跟着结果走,不随 Run 翻篇丢失——授权是把风险显式交给\n * 人,报告因此会在新配置身份下混入旧配置跑出的结果,这个字段是事后追认这笔账的唯一线索。\n */\n carriedAccepting?: CarriedAcceptance[];\n /** 当前确定性指纹替换已知旧 opaque carryEpoch 指纹时保留的来源。 */\n migratedFrom?: FingerprintMigration;\n /**\n * 人工 `niceeval accept @<locator>` 产生的新结果:来源 locator、旧/新 fingerprint\n * 与 manifest 差异摘要。与 `carriedAccepting`(旧版 --accept 携入痕迹)并列保留,\n * 读取旧记录时不做迁移。\n */\n acceptedFrom?: AcceptedResult;\n /** 实验身份改变但 fingerprint 保持不变时的重绑来源审计。 */\n renamedFrom?: RenamedResult;\n /**\n * writer 实际写出的按需 artifact 词干列表(词表与全部横切属性单源在\n * docs/feature/record/architecture.md「证据 registry」,如 [\"commands\", \"events\", \"sources\"])。\n * 省略等价于空列表;携带条目原样携带。读取面的懒加载语义(缺失返回 null)独立成立,\n * 本字段只服务「不 stat 磁盘就知道有什么」的消费方。\n */\n artifacts?: string[];\n}\n/** `run.json` 的格式标记;把 niceeval 报告和其它工具的同名文件区分开。 */\nexport const RECORD_FORMAT = \"niceeval.results\";\n/**\n * 结果格式版本,只在破坏兼容读取时递增;读取器只认相同版本。见 docs/feature/record/architecture.md。\n * `5`(见 memory 的 attempt-locator-and-source-dedup 条目)= result.json 新增 `locator` 字段;\n * `sources.json` 从逐 attempt 内联全量内容改为「attempt 级引用 + 快照级 `sources/<sha256>.json`\n * 去重仓库」,`AttemptHandle.sources()` 的公开返回形状不变(仍是 `SourceArtifact[] | null`)。\n * `6` = `error` 从自由字符串改为结构化 `AttemptError`,并新增有界 `diagnostics`。\n * `8` = 断言记录改 outcome 判别联合(groupPath/optional/expected/received/unavailable+reason);\n * 生命周期词表统一为 LifecyclePhase,`error.operation`/`diagnostics[].operation` 更名 `phase`;\n * 新增 `phases`(阶段计时)、`coverage`(证据覆盖聚合)、`sandbox`(执行环境标识)字段;\n * `ExperimentRunInfo` 改为解析后运行配置的穷尽投影(sandbox 从字符串改结构化投影对象);\n * `diff.json` 落逐窗口 delta 序列(DiffWindow[]);events/trace 的字符串值统一 256 KiB 截断\n * (结构化 `truncated` 标记);run.json 新增发布拷贝的 `publish` 标记。\n * `9` = `hasEvents`/`hasTrace`/`hasSources` 三个布尔删除,统一为 `artifacts`(writer 实际写出的\n * 按需 artifact 词干列表,单源在证据 registry);`O11ySummary` 删除 `usage`/`estimatedCostUSD`/\n * `durationMs`,正名为纯行为计数缓存,权威唯一在 `result.json` 的 `Usage`/`estimatedCostUSD`/\n * `durationMs`(见 memory 的 results-evidence-registry-ruling 条目)。\n * `12` = `diff.json` 的 `WindowChange.binary` 并入 `elided`。\n * `13` = 两层时间模型:`TimingNode` 封闭 kind 改为开放 key 的 `TimingActivity`;\n * `AttemptError.phase` / `DiagnosticRecord.phase` 改为 `origin: TimingOrigin`;\n * `RunMeta` 新增 `timings` 与 `sandboxBuilds`(见 memory 的 results-schema-version-history)。\n * `14` = result.json 的证据聚合字段从 `coverage` 破坏性重命名为 `evidenceCoverage`;\n * 六通道全部必填,不兼容旧 schema,也不做 normalize。\n * 旧版快照按格式规则整份判为不兼容并在扫描时列为占位条目,不迁移不降级。\n * `15` = commands.json 的命令退出事实新增 `checked`,区分公开 checked/unchecked 调用;\n * 旧版 commands.json 不做兼容读取。\n * `18` = Fact/use 原子记录、`evaluationAlgorithm: \"fact-use/v3\"`;旧格式完全不支持读取。\n * `renamedFrom` 是可选审计字段,删除运行期选题投影也不改变当前 reader 对旧结果的读取;\n * 两者都不是破坏性格式变化,因此不递增版本。\n */\nexport const RECORD_SCHEMA_VERSION = 18;\n/** 一次 Invocation 的纯运行时内存聚合(reporter 契约用);落盘格式契约在 niceeval/record 的 RunMeta / AttemptRecord,见 docs/feature/record/architecture.md。不携带顶层 `agent`/`model`——一次 Invocation 可能横跨多个 `(agent, model, flags)` 配置,塞一个顶层单值只能代表其中一份配置;需要时从 `results` 里逐条 `EvalResult.agent`/`.model` 去重派生。 */\nexport interface InvocationSummary {\n /** 项目名(来自 config.name),透传给 `niceeval view` 顶部 hero 显示。 */\n name?: LocalizedText;\n startedAt: string;\n completedAt: string;\n passed: number;\n /** 断言不通过的数量;不包含 errored。 */\n failed: number;\n skipped: number;\n /** 环境、超时、adapter、agent runtime 等执行错误数量;与 failed 互斥。 */\n errored: number;\n durationMs: number;\n /** 本次 Invocation fresh 结果的 token 汇总(只折叠 input/outputTokens);observed costUSD 不在这里汇总,逐条留在 `results[].usage.costUSD`。 */\n usage?: Usage;\n /** 本次 Invocation fresh 结果的 `estimatedCostUSD` 累计(价目表估算口径,见 EvalResult.estimatedCostUSD);observed cost 不进入本字段。 */\n estimatedCostUSD?: number;\n /** Current Record readbacks adopted by this invocation; these are never recreated EvalResults. */\n reusedAttempts: readonly CurrentReusedAttemptReadback[];\n results: EvalResult[];\n}\n/**\n * The only invocation-level durable hand-off. Record Runs retain every\n * result, diagnostic, and artifact; this receipt only identifies those Runs\n * and records the invocation lifecycle fact.\n */\nexport interface InvocationReceipt {\n readonly invocationId: string;\n readonly runIds: readonly string[];\n readonly startedAt: string;\n readonly completedAt?: string;\n readonly completion: \"completed\" | \"interrupted\" | \"failed\";\n}\n/** onInvocationStart 的运行规模:去重后 eval 数 × 配置(agent×model×flags)数 → 总 attempt 数。 */\nexport interface InvocationShape {\n /** 去重后实际要跑的 eval 数(= evals.length)。 */\n evals: number;\n /** (agent, model, flags) 配置组合数;compare 多 agent 时 > 1。 */\n configs: number;\n /** 总 attempt 数(evals × configs × attempts);逐行输出与汇总计数都按它。 */\n totalAttempts: number;\n /** 本次运行实际生效的全局并发数(flag/config/sandbox 默认值解析后的结果);\n * 实验级 maxConcurrency 只在该实验内部限流,不改这个全局值。 */\n maxConcurrency: number;\n /**\n * 调度前由 Record v1 draft 分配的真实持久化 Run 身份。其它 reporter 可用它关联\n * plan 期的 attempt locator 与最终 Run,但不能替换或重新生成它。\n */\n runIds?: ReadonlyMap<string, string>;\n}\nexport interface Reporter {\n onEvent?(event: ReporterEvent): void | Promise<void>;\n onInvocationStart?(evals: {\n id: string;\n }[], shape?: InvocationShape): void | Promise<void>;\n onEvalComplete?(result: EvalResult): void | Promise<void>;\n onInvocationComplete?(summary: InvocationSummary): void | Promise<void>;\n}\n/**\n * 内部 reporter 注册项:CLI/runner 给每个 `Reporter` 实例附上「叫什么名字」「失败是否致命」\n * 两条元数据,不改变用户实现 `Reporter` 的公共形状——`Reporter` 接口本身不变,用户只需要实现\n * 上面那四个回调,从不需要知道 `ReporterRegistration` 的存在。`name` 是\n * `reportReporterError()` / `DiagnosticNotice.key` 里 `reporter-error:<name>` 的稳定标识:\n * 同一个 reporter 反复失败折叠成一条诊断、`count` 递增,不同 reporter 各自一条,由这个字段的\n * 取值决定,不是「在哪个回调阶段失败」(onInvocationStart/onEvalComplete/…)决定——后者只作为\n * 诊断消息里的次要上下文,不参与去重身份。\n *\n * `required` 语义(见 docs/feature/experiments/cli.md「运行完成状态不只看 verdict 计数」):\n * - CLI 显式 `--junit`:`required: true`——这是调用者要求的附加聚合文件,写失败必须让\n * `InvocationCompletion` 判红、CI 退出码非零。CLI `--json` 是终端 receipt 流,不是 Reporter。\n * - 用户 `Config.reporters` / `EvalDef.reporters`:`required: false`——失败只折成一条\n * diagnostic,不影响 completion,也不阻断其它 reporter 收尾或后续 attempt。\n *\n * `target` 是可选的落盘路径(如 `--json`/`--junit` 指定的文件),纯展示 / 排障用途,不参与判定。\n */\nexport interface ReporterRegistration {\n reporter: Reporter;\n name: string;\n required: boolean;\n target?: string;\n}\nexport type ReporterEvent = {\n type: \"invocation:start\";\n evals: {\n id: string;\n }[];\n shape: InvocationShape;\n} | {\n type: \"eval:start\";\n eval: {\n id: string;\n };\n agent: Agent;\n model?: string;\n attempt: number;\n experimentId?: string;\n} | {\n type: \"eval:complete\";\n result: EvalResult;\n} | {\n type: \"invocation:earlyExit\";\n evalId: string;\n experimentId?: string;\n} | {\n type: \"invocation:budgetExceeded\";\n budget: number;\n spent: number;\n} | {\n type: \"invocation:saved\";\n summary: InvocationSummary;\n} | {\n type: \"invocation:summary\";\n summary: InvocationSummary;\n} | {\n /** 该 Experiment 的 teardown(若声明)完成之后、invocation:summary 之前触发。 */\n type: \"experiment:complete\";\n experimentId: string;\n /** 该 Experiment 封口时刻,即它的 Run completedAt。 */\n completedAt: string;\n /** 当前 Record 读取出的精确复用 Attempt;不伪造成历史 EvalResult。 */\n reusedAttempts: readonly CurrentReusedAttemptReadback[];\n /** 该 Experiment 域产生的全部诊断;空集合传空数组,不省略字段。 */\n diagnostics: readonly DiagnosticRecord[];\n /** Run 级共享工作时间树;与 completedAt 同批封口。省略 = 本 Run 没有共享 activity。 */\n timings?: readonly TimingActivity[];\n /** 共享构建 provenance;与 timings 经 timingNodeId 关联。省略 = 本 Run 没有查询或构建过 BuildKey。 */\n sandboxBuilds?: readonly SandboxBuildRecord[];\n /** 项目名(来自 config.name),整次 Invocation 内所有 Experiment 共享同一个值。 */\n name?: LocalizedText;\n};\n// ───────────────────────── eval / experiment / config 定义 ─────────────────────────\n/**\n * 计分粒度题型:`defineEval` 恒 `\"pass\"`(通过制,一题一分,读通过率),`defineScoreEval` 恒\n * `\"score\"`(计分制,题内叠加挣分,读总分)。定义期事实,发现期从 `EvalDefinition.evaluationKind` 直接读取,\n * 不靠执行 `test()` 推断(见 docs/feature/experiments/score-points.md)。\n */\nexport type EvaluationKind = \"pass\" | \"score\";\n/** The live Runner derives Pass and Score from one sealed Assert-first entry sequence. */\nexport const EVALUATION_ALGORITHM = \"assert-first/v2\" as const;\n/** `fact-use/v3` remains only as the historical Record reader/writer bridge. */\nexport type EvaluationAlgorithm = typeof EVALUATION_ALGORITHM | \"assert-first/v1\" | \"fact-use/v3\";\n/**\n * 作者输入里的派生字段用模块私有诊断类型,而不是 `never`:错误会说明字段属于哪个阶段。\n * 这些 symbol 不从包入口导出,因此包外没有可写入的同类值。\n */\nconst EVAL_CONTRACT_DIAGNOSTIC: unique symbol = Symbol(\"niceeval.evalContractDiagnostic\");\nconst EVAL_DEFINITION: unique symbol = Symbol(\"niceeval.evalDefinition\");\nconst EXPERIMENT_CONTRACT_DIAGNOSTIC: unique symbol = Symbol(\"niceeval.experimentContractDiagnostic\");\nconst EXPERIMENT_DEFINITION: unique symbol = Symbol(\"niceeval.experimentDefinition\");\ntype IdComesFromFilePath = {\n readonly [EVAL_CONTRACT_DIAGNOSTIC]: \"id comes from the file path\";\n};\ntype EvaluationKindComesFromFactory = {\n readonly [EVAL_CONTRACT_DIAGNOSTIC]: \"evaluationKind comes from defineEval / defineScoreEval\";\n};\ntype ConfigHashComesFromPlanning = {\n readonly [EVAL_CONTRACT_DIAGNOSTIC]: \"configHash comes from run planning\";\n};\ntype ExperimentIdComesFromFilePath = {\n readonly [EXPERIMENT_CONTRACT_DIAGNOSTIC]: \"id comes from the file path\";\n};\n/** Eval 作者自行选择的字段;不包含路径、factory 和规划期事实。 */\nexport interface EvalAuthorFields {\n /** 一句话描述,展示在 `niceeval list` 和 view 里;纯说明,不影响调度或打分。 */\n description?: string;\n /** 标签,供 CLI `--tag` 过滤和 view 分类;与 id 前缀过滤是两套独立的筛选维度。 */\n tags?: string[];\n /**\n * 这道题贡献的 Sandbox 声明层。省略等价于空 command-only layer,不提供隐式 template。\n * 每个实际评估用例 × Experiment 配对必须恰好一方提供 template-bearing layer。\n */\n sandbox?: SandboxLayer;\n /** 显式且不可变的评估用例 Plugin occurrence;不存在目录继承。 */\n plugins?: readonly PluginInstance<\"eval\">[];\n /** 声明 Judge capability;true 继承 Experiment/Config,对象同时声明并覆盖它们。 */\n judge?: JudgeDeclaration;\n /** 覆盖 / 追加项目级 Config.reporters,只对这一条评估用例生效。 */\n reporters?: Reporter[];\n /** 覆盖项目级 / CLI 的单次 attempt 超时(毫秒),只对这一条评估用例生效。 */\n timeoutMs?: number;\n /** 任意附加元数据,作为 Attempt Provenance 保存,不参与调度或打分;供自定义 reporter 消费。 */\n metadata?: globalThis.Record<string, JsonValue>;\n /**\n * 调整 agent diff 的归因排除清单(仅 Sandbox 型;见 docs/feature/eval/README.md):两个数组都是\n * gitignore 风格 glob(workdir 相对)。默认排除 .git/node_modules/构建产物/包管理器缓存;\n * `ignore` 在默认清单上追加排除;`include` 优先级最高,把匹配路径显式加回。\n * 合成规则固定为「默认 ∪ ignore,再被 include 打洞」,清单在分类账锚点时冻结。\n */\n diff?: {\n include?: string[];\n ignore?: string[];\n };\n}\n/** Authors may return an Effect; Runner executes it in the Attempt's owning fiber. */\ntype EvalTestReturn = void | Promise<void> | Effect.Effect<void, unknown, never>;\n/** 作者输入:id 归 discovery、evaluationKind 归 factory、configHash 归 planning,作者都不能填写。 */\nexport type EvalInput<Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> = Omit<EvalAuthorFields, \"sandbox\"> & {\n sandbox?: Sandbox;\n id?: IdComesFromFilePath;\n evaluationKind?: EvaluationKindComesFromFactory;\n configHash?: ConfigHashComesFromPlanning;\n test(t: TestContext): EvalTestReturn;\n};\n/** 计分制作者输入,只有 test 的上下文不同。 */\nexport type ScoreEvalInput<Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> = Omit<EvalAuthorFields, \"sandbox\"> & {\n sandbox?: Sandbox;\n id?: IdComesFromFilePath;\n evaluationKind?: EvaluationKindComesFromFactory;\n configHash?: ConfigHashComesFromPlanning;\n test(t: ScoreTestContext): EvalTestReturn;\n};\n/** Factory 完成默认归一后的 Eval 字段;Definition 不再复用作者输入的 optional 半状态。 */\nexport interface EvalDefinitionFields<Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> {\n readonly description?: string;\n readonly tags: readonly string[];\n /**\n * 保留“作者省略”和“作者显式声明空 layer”的来源差异:Direct Agent 只允许前者,\n * Sandbox link 则把省略侧视为 command-only。不能在 factory 阶段补成 sandboxLayer()。\n */\n readonly sandbox?: Sandbox;\n readonly plugins: readonly PluginInstance<\"eval\">[];\n readonly judge?: JudgeDeclaration;\n readonly reporters: readonly Reporter[];\n readonly timeoutMs?: number;\n readonly metadata: Readonly<globalThis.Record<string, JsonValue>>;\n readonly diff: {\n readonly include: readonly string[];\n readonly ignore: readonly string[];\n };\n}\n/** Factory 产物保留精确 evaluationKind / context,并带模块私有品牌,不能由对象字面量伪造。 */\nexport interface EvalDefinition<Kind extends EvaluationKind, Context, Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> extends EvalDefinitionFields<Sandbox> {\n readonly evaluationKind: Kind;\n test(t: Context): EvalTestReturn;\n readonly [EVAL_DEFINITION]: true;\n}\nexport type AnyEvalDefinition = EvalDefinition<\"pass\", TestContext, SandboxLayer | undefined> | EvalDefinition<\"score\", ScoreTestContext, SandboxLayer | undefined>;\nconst EVAL_GROUP_DEFINITION: unique symbol = Symbol(\"niceeval.evalGroupDefinition\");\n/** A group member must not own a Sandbox template or instance lifecycle. Runtime discovery revalidates this. */\nexport type EvalGroupMemberSandbox = SandboxLayer<\"command-only\", \"prepare-only\"> | undefined;\nexport type EvalGroupMember = EvalDefinition<\"pass\", TestContext, EvalGroupMemberSandbox> | EvalDefinition<\"score\", ScoreTestContext, EvalGroupMemberSandbox>;\nexport interface EvalGroupInput<Sandbox extends SandboxLayer | undefined = SandboxLayer | undefined> {\n readonly evals: readonly [\n EvalGroupMember,\n ...EvalGroupMember[]\n ];\n readonly sandbox?: Sandbox;\n /** Required physical-instance policy when a grouped Sandbox becomes unavailable. */\n readonly onUnavailable: PluginOnUnavailable;\n readonly plugins?: readonly PluginInstance<\"group\">[];\n}\nexport interface EvalGroupDefinition extends EvalGroupInput {\n readonly [EVAL_GROUP_DEFINITION]: true;\n}\nexport function brandEvalGroupDefinition(value: EvalGroupInput): EvalGroupDefinition {\n Object.defineProperty(value, EVAL_GROUP_DEFINITION, { value: true });\n return Object.freeze(value) as EvalGroupDefinition;\n}\nexport function isEvalGroupDefinition(value: unknown): value is EvalGroupDefinition {\n return typeof value === \"object\" && value !== null &&\n (value as {\n readonly [EVAL_GROUP_DEFINITION]?: unknown;\n })[EVAL_GROUP_DEFINITION] === true;\n}\n/** @internal 唯一写入 Definition 私有品牌的构造辅助;不从公共入口导出。 */\nexport function brandEvalDefinition<Kind extends EvaluationKind, Context, Sandbox extends SandboxLayer | undefined>(value: EvalDefinitionFields<Sandbox> & {\n evaluationKind: Kind;\n test(t: Context): EvalTestReturn;\n}): EvalDefinition<Kind, Context, Sandbox> {\n Object.defineProperty(value, EVAL_DEFINITION, { value: true });\n return Object.freeze(value) as EvalDefinition<Kind, Context, Sandbox>;\n}\n/** Definition 之后由 discovery 一次性补齐的不可变事实。 */\nexport interface DiscoveredEvalFacts {\n readonly id: string;\n /** 定义文件所在目录(解析相对 workspace 用)。 */\n readonly baseDir: string;\n /** 定义文件绝对路径,用于内容指纹缓存。 */\n readonly sourcePath: string;\n /** 发现期经 loadJson/loadYaml/loadText 读入的项目内数据文件(内容已在内存,指纹哈希内容)。 */\n readonly loaderDataPaths: readonly string[];\n /**\n * 发现期经 `loadCriteria` 登记的判据树文件(只登记不读入)。指纹按「项目根相对路径 ×\n * 内容流式哈希」进,与 `loaderDataPaths` 分两格是因为这一格的内容从不进内存。\n */\n readonly criteriaPaths: readonly string[];\n /**\n * 发现期经 `loadPrivate` 登记的永不上传路径(只登记不读入)。指纹口径与 `criteriaPaths`\n * 相同,分键存放——private 与 verifier 同属判据面,但不与 criteria 混成一张表。\n */\n readonly privatePaths: readonly string[];\n /**\n * discovery 时捕获的规范化源码(归一化文本 + 项目相对路径 + SHA-256),见 `eval-source.ts`。\n * 同一文件里多个 eval(数组默认导出)共享同一份引用——哈希与内容天然相同,不重复读盘。\n */\n readonly source: CapturedEvalSource;\n /** @internal Original factory object, used only for Eval Group identity resolution. */\n readonly definition: AnyEvalDefinition;\n /** Eval Group planning facts, present only for discovered group members. */\n readonly evalGroup?: {\n readonly id: string;\n readonly evalIds: readonly string[];\n readonly definitionHash: string;\n readonly sandbox?: SandboxLayer;\n readonly onUnavailable: PluginOnUnavailable;\n readonly plugins: readonly PluginInstance<\"group\">[];\n readonly sourcePath: string;\n readonly baseDir: string;\n };\n}\n/** discovery 保留 factory 的 evaluationKind 判别、私有品牌与对应 test context。 */\nexport type DiscoveredEval = (EvalDefinition<\"pass\", TestContext> & DiscoveredEvalFacts) | (EvalDefinition<\"score\", ScoreTestContext> & DiscoveredEvalFacts);\n/** @internal discovery 动态边界的品牌守卫;普通对象即使字段同形也不通过。 */\nexport function isEvalDefinition(value: unknown): value is AnyEvalDefinition {\n return (typeof value === \"object\" &&\n value !== null &&\n (value as {\n readonly [EVAL_DEFINITION]?: unknown;\n })[EVAL_DEFINITION] === true);\n}\n/** @internal discovery 构造唯一的不可变阶段三值,不回写 Definition。 */\nexport function discoverEval(definition: AnyEvalDefinition, facts: DiscoveredEvalFacts): DiscoveredEval {\n const value = { ...definition, ...facts };\n Object.defineProperty(value, EVAL_DEFINITION, { value: true });\n return Object.freeze(value) as DiscoveredEval;\n}\n/**\n * `ExperimentDefinition.setup` / `teardown` 拿到的窄上下文。`progress` 更新本实验运行级\n * active 行的次要文本(短命状态,JSON 机器流不逐条输出),`diagnostic` 进运行级永久\n * 事件流(实验级钩子不属于任何单个 attempt,诊断不落 attempt 的 `result.json`;setup 抛错\n * 以每条 attempt 的结构化 `error` 落盘,失败仍可回顾)。钩子的起止本身由 runner 直接发布为\n * 运行级反馈,不依赖这里的 `progress`(见 docs/feature/experiments/cli.md「实验级 Hook 的显示」)。\n */\nexport interface ExperimentHookContext extends ScopedFeedback {\n readonly experimentId: string;\n /** 本实验解析后实际选中的 eval id 全集(evals 过滤器的求值结果)。 */\n readonly selectedEvalIds: readonly string[];\n /** 用户中断(Ctrl+C / kill)时 abort;长启动的 setup 应观察它提前退出。 */\n readonly signal: AbortSignal;\n}\n/** Shared lifecycle callback shape for author and Experiment Plugin hooks. */\nexport type ExperimentHook = (ctx: ExperimentHookContext) => void | Promise<void>;\n/**\n * 保护同一项目内共享的外部可变状态的互斥声明。它只提供跨 Invocation 的独占,不存储\n * 状态、不提供事务,也不协调不同机器;`key` 必须稳定且不含凭据,并进入配置身份。\n */\nexport interface SharedStateConfig {\n readonly key: string;\n}\n/** Experiment 作者自行选择的字段;不包含路径 id 与 factory 品牌。 */\nexport interface ExperimentAuthorFields {\n /** 一句话描述,展示在 view / CLI 里;纯说明,不影响调度或打分。 */\n description?: string;\n /**\n * 必填:这个实验跑哪个 agent(defineSandboxAgent / defineAgent 的产物)。运行配置的\n * agent 归属完全由这里决定——EvalDefinition 不声明 agent。\n */\n agent: Agent;\n /** 单个模型(agent 留空时实验决定);省略=用 agent 原生默认。跨模型对比写多个实验文件,别用数组。 */\n model?: string;\n /** 模型推理努力程度(如 \"low\"/\"medium\"/\"high\",取值由具体模型/adapter 决定);省略=用 agent 原生默认。经 ctx.reasoningEffort 透给 adapter 与 eval。 */\n reasoningEffort?: string;\n /**\n * 本实验的 Judge 执行配置。只覆盖 model / endpoint / credential selector / 调用预算,\n * rubric、材料与消费阈值仍由 Eval 的 Fact/use 声明拥有。各字段按\n * Experiment → Eval → Config 解析。\n */\n judge?: JudgeConfig;\n /** 实验条件(A/B 里的 feature flag),由实验文件声明;必须是可 JSON 序列化的值\n * (defineExperiment 解析时校验,非 JSON 直接报错),经 ctx.flags 透传给 adapter、\n * t.flags 暴露给 eval,并原样进入结果快照的 ExperimentRunInfo.flags。 */\n flags?: globalThis.Record<string, JsonValue>;\n /**\n * 报告归类标注:实验在各对比轴上的坐标(如 `{ line: \"codex\", memory: \"mempal\" }`)。\n * 值域 string | number(解析时校验)。与 `flags` 的分界是「会不会改变 attempt 里发生的事」:\n * labels 不透传 ctx / t(agent 和 eval 看不见)、不参与可比性配置(改它不作废已有结果),\n * 只原样投影进快照的 `ExperimentRunInfo.labels` 供报告维度(`label()` / `numericLabel()`)\n * 分组。`line` 键被默认报告识别:组内任一实验声明了它,散点按线归类并连线。\n * 见 docs/feature/experiments/library.md「labels」。\n */\n labels?: globalThis.Record<string, string | number>;\n /** 同一 eval 重复跑几次(结果各计一条 attempt);省略/CLI `--attempts` 覆盖时默认 1。 */\n attempts?: number;\n /** 一次重复(attempts > 1)里某次 attempt 通过后是否跳过剩余重复;省略默认 false(`attempts` 跑满、测完整通过率),\n * 显式打开用于「只想知道能不能过」的省钱场景。 */\n earlyExit?: boolean;\n /**\n * 这个实验覆盖哪些 eval:`\"*\"` 全部、字符串数组按 id 前缀、或自定义谓词(逐条收到发现并扇出后的\n * 只读 `EvalDescriptor`,不暴露路径 / 执行字段);省略等价于 `\"*\"`。谓词对本次 invocation 的\n * 候选 eval 各求值一次,解析结果作为内存中的 `selectedEvalIds` 计划——不是运行时反复调用的过滤器\n * (见 docs/feature/eval/library.md「EvalDescriptor」、docs/feature/experiments/library.md\n * 「evals:遍历发现结果,自定义选择」)。\n */\n evals?: \"*\" | readonly string[] | ((e: EvalDescriptor) => boolean);\n /** 覆盖项目级 / CLI 的单次 attempt 超时(毫秒),只对这个实验生效。 */\n timeoutMs?: number;\n /**\n * 本实验贡献的 Sandbox 声明层。它与每条选中 Eval 的同名字段逐配对链接;\n * 每个配对恰好一方提供 template-bearing layer。\n */\n sandbox?: SandboxLayer;\n /** Explicit Experiment Plugin occurrences, normalized by defineExperiment(). */\n plugins?: readonly PluginInstance<\"experiment\">[];\n /** 同一 Run 内复用沙箱;这种运行与历史携带双向隔离。 */\n sandboxReuse?: boolean;\n /**\n * 声明本 Experiment 需要独占的共享外部状态。相同 key 的 Invocation 在同一项目\n * Coordination 域内从 Experiment/Sandbox setup 前一直互斥到 Sandbox teardown、\n * provider finalizer 与 Experiment teardown 全部完成;等待者不会先创建 Sandbox,\n * 取得租约后继续自己的 plan。它不是状态存储、事务或跨机器锁。\n */\n sharedState?: SharedStateConfig;\n /**\n * 本实验的估算花费上限(USD)。调度器累计已完成 attempt 的 `estimatedCostUSD`;该值由\n * model、token usage 与 runtime/config pricing table 计算,与 Provider / Adapter 回报的\n * observed `usage.costUSD` 独立,后者不驱动 budget。估算到顶后跳过这个实验剩下未起飞的\n * attempt 并上报一次 `run:budgetExceeded`(已在飞的 attempt 仍会跑完)。\n */\n budget?: number;\n /**\n * 本 Invocation 内本实验自己的并发上限:调度器只对这个实验的 attempt 限流,同批其它实验不受影响,\n * 仍按全局并发(CLI / env / config / 沙箱默认)跑。用于串行化有共享状态的实验\n * (如跨 eval 累积记忆:`maxConcurrency: 1` 保证 attempt 按 eval 顺序一个个跑),\n * 或给撞 provider 限额的实验单独降速。名额与 attempt 同生命周期:从沙箱创建前一直握到\n * teardown 与沙箱销毁完成才归还,中途任何等待(含 turn 重试退避)都不松手——\n * `maxConcurrency: 1` 因此是严格的临界区,不会被同实验的下一个 attempt 提前闯入。\n * 它不跨 Invocation;跨 Invocation 的共享外部状态请声明 `sharedState`。\n */\n maxConcurrency?: number;\n /**\n * 本实验的失败分类器:识别以第三方错误形态浮出的自家共享基建死因(对自家隧道 host 的拒连\n * 一类),返回 `undefined` 表示「不认识,交给后续链路」。本实验任意 per-attempt 阶段的失败\n * 都会问到它;send 失败链上它排在 adapter 的 `classifySendFailure` 之前——按自家坐标过滤的\n * 特异性高于协议通用形状,两者同时认领时空间轴才赢得下来。分类器要快、纯、不抛错(抛错按\n * `undefined` 回落并被吞掉);只声明决策轴与 `reason` 词,重试与落闸策略归执行体。\n * 见 docs/feature/error-classification/library.md「实验 / eval 作者:声明死因的波及范围」。\n */\n classifyFailure?: AttemptFailureClassifier;\n /**\n * 实验级生命周期钩子对的 setup 侧:整场至多一次、宿主机侧,管「每实验一份、所有 attempt\n * 共享」的宿主机资源(隧道、mock server、license 租约)。本实验第一个通过派发许可的\n * attempt 触发(memoized,并发 attempt 等同一个结果;全部结果被 carry 携入时不执行)。\n * setup 不返回值;产物写模块级变量,`teardown` 与同文件 agent / sandbox 钩子从闭包读,\n * runner 不做值的中介。setup 抛错 → 本实验所有 Attempt 形成 `errored` Verdict\n * (code `\"experiment-setup-failed\"`、phase `\"experiment.setup\"`),同批其它实验不受影响。\n * 函数体不进 fingerprint,改了钩子逻辑用 `--rerun all` 明确全部重跑。\n * 见 docs/feature/experiments/architecture.md「实验级生命周期」。\n */\n setup?: ExperimentHook;\n /**\n * 实验级生命周期钩子对的 teardown 侧:本实验全部 attempt 收尾后执行(运行被中断也执行),\n * 当且仅当 setup 时点走到过——setup 抛错不豁免(半初始化现场同样要扫尾,teardown 对可能\n * 未赋值的闭包变量做防御),未声明 setup 不影响触发;一个 attempt 都不派发则跳过。\n * 抛错或超 30s 清理上限只记运行级 diagnostic(`experiment-teardown-failed`),不改判定。\n */\n teardown?: ExperimentHook;\n}\n/** 作者输入:id 只能由发现阶段从文件路径推导。 */\nexport type ExperimentInput = ExperimentAuthorFields & {\n id?: ExperimentIdComesFromFilePath;\n};\n/** Factory 完成默认归一后的 Experiment 字段;无默认语义的 Hook 仍保持作者声明。 */\nexport interface ExperimentDefinition {\n readonly description?: string;\n readonly agent: Agent;\n readonly model?: string;\n readonly reasoningEffort?: string;\n readonly judge?: JudgeConfig;\n readonly flags: Readonly<globalThis.Record<string, JsonValue>>;\n readonly labels: Readonly<globalThis.Record<string, string | number>>;\n readonly attempts: number;\n readonly earlyExit: boolean;\n readonly evals: \"*\" | readonly string[] | ((e: EvalDescriptor) => boolean);\n readonly timeoutMs?: number;\n /** 省略本身是 link 阶段需要的来源事实,不能在 Definition 中归一成显式空 layer。 */\n readonly sandbox?: SandboxLayer;\n readonly plugins: readonly PluginInstance<\"experiment\">[];\n readonly sandboxReuse: boolean;\n readonly sharedState?: SharedStateConfig;\n readonly budget?: number;\n readonly maxConcurrency?: number;\n readonly classifyFailure?: AttemptFailureClassifier;\n readonly setup?: ExperimentHook;\n readonly teardown?: ExperimentHook;\n readonly [EXPERIMENT_DEFINITION]: true;\n}\n/** @internal 仅 defineExperiment 写入私有品牌。 */\nexport function brandExperimentDefinition(value: Omit<ExperimentDefinition, typeof EXPERIMENT_DEFINITION>): ExperimentDefinition {\n Object.defineProperty(value, EXPERIMENT_DEFINITION, { value: true });\n return Object.freeze(value) as ExperimentDefinition;\n}\n/** @internal discovery 只接受 defineExperiment 的原始产物,不做结构性兼容。 */\nexport function isExperimentDefinition(value: unknown): value is ExperimentDefinition {\n return (typeof value === \"object\" &&\n value !== null &&\n (value as {\n readonly [EXPERIMENT_DEFINITION]?: unknown;\n })[EXPERIMENT_DEFINITION] === true);\n}\n/** @internal discovery 构造阶段三的不可变投影;不向 Definition 回写路径事实。 */\nexport function discoverExperiment(definition: ExperimentDefinition, source: {\n readonly id: string;\n readonly baseDir: string;\n readonly sourcePath: string;\n}): DiscoveredExperiment {\n const value = { ...definition, ...source };\n Object.defineProperty(value, EXPERIMENT_DEFINITION, { value: true });\n return Object.freeze(value) as DiscoveredExperiment;\n}\n/** 发现期运行形状:Definition 加入路径与来源;规划期 configHash 不在这里。 */\nexport interface DiscoveredExperiment extends ExperimentDefinition {\n readonly id: string;\n /** 定义文件所在目录;解析 Experiment layer 中的相对本地路径。 */\n readonly baseDir: string;\n /** 定义文件绝对路径;link 诊断标注声明来源。 */\n readonly sourcePath: string;\n}\n/**\n * 用户谓词(`ExperimentDefinition.evals`)能看到的唯一形状——发现并扇出后的显式白名单投影,不透传\n * `DiscoveredEval` 原对象(不暴露 `sourcePath` / `baseDir` / `test` / hooks 等内部路径与执行字段)。\n * `tags` 缺省为冻结空数组;`metadata` 原样引用作者声明的对象(至少浅冻结),供 `tags.includes(...)` /\n * `metadata.<key>` 判断(见 docs/feature/eval/library.md「EvalDescriptor」)。\n */\nexport interface EvalDescriptor {\n readonly id: string;\n readonly description?: string;\n readonly tags: readonly string[];\n /**\n * 计分粒度题型,`defineEval` → `\"pass\"`,`defineScoreEval` → `\"score\"`。定义期事实,\n * 每条发现出的 eval 上都有确定值。供 `ExperimentDefinition.evals` 谓词按题型过滤(见\n * docs/feature/experiments/score-points.md「横截面聚合:同型实验,各读各的」)。\n */\n readonly evaluationKind: EvaluationKind;\n readonly metadata?: Readonly<globalThis.Record<string, JsonValue>>;\n}\nexport interface Config {\n /** view/show 的项目默认报告。 */\n report?: ReportDefinition;\n /** view 的 host-owned closed visual token declaration. */\n theme?: ThemeDefinition;\n /**\n * 项目名,显示在 `niceeval view` 顶部 hero(`<h1>`),省略则回退到通用标题。\n * 可传字符串,或按 locale 提供多语言(如 `{ en: \"...\", \"zh-CN\": \"...\" }`),随 view 语言切换。\n */\n name?: LocalizedText;\n /** 上传进 Sandbox 的工作区根目录,省略则用项目根;评估用例的 sandbox 视图从这里起步。 */\n workspace?: string;\n /** 项目级默认 judge 配置(model / baseUrl / apiKeyEnv);EvalDef.judge 可按评估用例覆盖。 */\n judge?: JudgeConfig;\n /** 项目级默认 reporter 列表(如落盘 / 上传结果);EvalDef.reporters 会与它合并。 */\n reporters?: Reporter[];\n /** 项目级默认并发上限;CLI flag / experiment 的同名设置优先级更高(没有环境变量层)。 */\n maxConcurrency?: number;\n /** Run 级 Sandbox 镜像准备并发;与 attempt 并发独立,省略时安全默认 2。 */\n maxBuildConcurrency?: number;\n /** 项目级默认单次 attempt 超时(毫秒);CLI flag / experiment / EvalDef 的同名设置优先级更高。 */\n timeoutMs?: number;\n /**\n * OTLP 接收配置,niceeval 项目内唯一入口(不读 NICEEVAL_OTLP_* 环境变量)。\n * `port` 钉住接收端口(固定端口模式:长驻服务把 OTEL_EXPORTER_OTLP_ENDPOINT 一次性指到\n * http://localhost:<port>/v1/traces,跑多少次评估用例都不用改)。省略 = 每次运行动态分配\n * 临时端口(经 ctx.telemetry 交给 adapter)。代价:固定端口下同机同时只能跑一个 niceeval 进程,\n * 且该端口被别的进程占用时会报错——换一个空闲端口写回这里即可。\n * `host` 是报给 adapter 的接收端 hostname(而非监听地址,监听地址恒为 0.0.0.0):默认\n * \"127.0.0.1\"。只有作者已经提供受控 tunnel / 可达路由时才在这里覆盖;Docker Sandbox\n * 默认把 receiver 放在 Sandbox 内,不依赖隐式宿主 gateway。\n */\n telemetry?: {\n host?: string;\n port?: number;\n };\n /**\n * 内置价格表(`o11y/prices.json`)之上的用户覆盖 / 补充,按 model 查(见 Observability\n * · 用量与成本)。key 支持精确 model 名或 `provider/*` 通配(自托管/网关折扣按 provider 批量覆盖);\n * 精确 key 优先于通配。pricing 只驱动 `estimatedCostUSD` 的估算(`estimateCost`),与\n * `usage.costUSD`(网关实测)无关——两者独立并存,互不兜底。它是 runtime/config 价目表,\n * 不是 Report 的成本投影:Report 不消费该字段(Report 侧使用自己的 PricingProfile)。\n */\n pricing?: globalThis.Record<string, PriceOverride>;\n}\n/** 每百万 token 的美元单价;省略的桶退回 `inputPerMTok`(cache token 本质也是 input)。 */\nexport interface PriceOverride {\n /** 普通输入 token 单价。 */\n inputPerMTok: number;\n /** 输出 token 单价。 */\n outputPerMTok: number;\n /** cache 命中(读)token 单价,省略则退回 inputPerMTok。 */\n cacheReadPerMTok?: number;\n /** cache 写入 token 单价,省略则退回 inputPerMTok。 */\n cacheWritePerMTok?: number;\n}\n// ───────────────────────── 调度编排 ─────────────────────────\n/**\n * 进度行 / 日志里标识一个 run 配置的短名。有 experiment 时用其 basename(唯一,\n * 能区分同 agent 同 model 的实验变体,如 xxx 与 xxx--agents-md;与汇总表口径一致);\n * 无 experiment 时退回 agent/model。现有 live display 以它(拼 evalId)作行聚合 key,\n * 两处必须同源(改这里的格式要同步核对 live.ts 的 key 计算,见 memory 的\n * live-who-key-mismatch-freezes-rows —— 上一次格式改动漏改 live.ts 自己手写的两处曾冻结整表)。\n *\n * 这是展示 label,不是 identity —— 两个不同的 (evalId, attempt) 可能巧合算出同一个 who\n * (同 experiment 同 eval 的第 2 次重试与另一条 eval 的第 1 次重试,展示上都叫同一个 basename)。\n * 反馈系统新的事件/状态(见上面 `AttemptRef` / `AttemptKey` / `encodeAttemptKey`)一律用\n * `{experimentId, evalId, attempt}` 做 identity/Map key,`who` 只作为 `ActiveAttempt.who`\n * 之类的展示字段附着,不参与去重或查找 —— 把展示 label 错当成 identity key 曾经是 live 表格\n * 两个真实 bug 的根因(另见 memory 的 live-rows-fold-experiment-variants)。\n */\nexport function runWho(run: {\n agentName: string;\n model?: string;\n experimentId?: string;\n}): string {\n if (run.experimentId)\n return run.experimentId.split(\"/\").pop()!;\n return run.model ? `${run.agentName}/${run.model}` : run.agentName;\n}\n/** 一个 (agent, model, flags) 的运行配置 —— 由 CLI / 实验展开。 */\nexport interface AgentRun {\n readonly agent: Agent;\n readonly model?: string;\n readonly reasoningEffort?: string;\n readonly flags: Readonly<globalThis.Record<string, JsonValue>>;\n readonly attempts: number;\n readonly earlyExit: boolean;\n /** Experiment 的作者 layer;省略在 link 输入归一为 command-only。 */\n readonly sandbox?: SandboxLayer;\n /** Experiment Plugin occurrences carried into zero-resource pair link. */\n readonly plugins?: readonly PluginInstance<\"experiment\">[];\n /** Canonical, credential-free Experiment Plugin behavior projection. */\n readonly pluginBehavior?: JsonValue;\n readonly sandboxReuse?: boolean;\n /** 跨 Invocation 共享外部状态的互斥声明;值进入 configHash。 */\n readonly sharedState?: SharedStateConfig;\n /** Experiment 声明的 judge 覆盖;与 Eval/Config 的逐字段解析在 pair 规划期完成。 */\n readonly judge?: JudgeConfig;\n /**\n * 运行侧已求值的单 attempt 超时上限:只含 `--timeout` 与 experiment 字段两层\n * (`resolveRunTimeout`)。**不许把 config 的值提前物化进来**——eval 与 config 两层由\n * `resolveAttemptTimeout` 在派发时接上,提前物化会让 eval 自己声明的上限永久短路\n * (见 timeout.ts 与 memory/multi-source-field-resolution-order.md)。\n */\n readonly timeoutMs?: number;\n /** `timeoutMs` 那个值来自哪一层,供超时消息标注出处;省略按 `experiment` 读。 */\n readonly timeoutSource?: \"flag\" | \"experiment\";\n readonly budget?: number;\n readonly experimentId: string;\n /** Experiment 定义文件目录;只用于解析 template 中的相对宿主路径。 */\n readonly experimentBaseDir: string;\n /** Experiment 定义文件路径;link 诊断来源。 */\n readonly experimentSourcePath: string;\n /** 实验的一句话描述(ExperimentDef.description),进结果快照的 ExperimentRunInfo。 */\n readonly description?: string;\n /** 报告归类标注(ExperimentDef.labels),原样进 ExperimentRunInfo.labels;不透传 ctx / t。 */\n readonly labels?: Readonly<globalThis.Record<string, string | number>>;\n /**\n * 本次 invocation 解析后实际选中的 eval id 全集——CLI 在构造 AgentRun 时对候选 eval 各求值\n * 一次算好(见 `eval-selection.ts` 的 `resolveExperimentEvals()`),下游(dry-run、sandbox 查表、\n * fingerprint/carry、attempt 展开、hook ctx、落盘)只消费这份已解析结果,不重新调用用户谓词。\n * 保持顺序 = discovery 稳定顺序,去重。\n */\n readonly selectedEvalIds: readonly string[];\n /** 本配置自己的并发上限(来自 ExperimentDef.maxConcurrency):调度器为它单建信号量,\n * attempt 先过这道闸再占全局并发位;省略则只受全局并发约束。 */\n readonly maxConcurrency?: number;\n /** 实验级生命周期钩子对(来自 ExperimentDef.setup / .teardown):setup 整场至多一次,\n * 调度器 memoize 执行;teardown 在全部 attempt 收尾后执行,当且仅当 setup 时点走到过\n * (语义见 ExperimentDef 对应字段)。 */\n readonly setup?: ExperimentHook;\n readonly teardown?: ExperimentHook;\n /** 实验声明的失败分类器(来自 ExperimentDef.classifyFailure):turn 链上排在 adapter 之前,\n * 生命周期链上排在抛出点声明之后;产出的空间轴由止损闸在 attempt 封口消费。 */\n readonly classifyFailure?: AttemptFailureClassifier;\n}\nexport interface RunOptions<RecordError = never, RecordRequirements = never> {\n config: Config;\n evals: readonly DiscoveredEval[];\n agentRuns: readonly AgentRun[];\n /**\n * `--keep-sandbox` 的留存档位:failed 留 failed/errored(含硬超时的 errored),all 全部留;\n * 省略 = 全部销毁(留存永远是显式选择)。留存决策在 verdict 定稿的收尾点按档位提交,\n * 见 docs/feature/sandbox/architecture.md「留存(keep)与注册表」。\n */\n keepSandbox?: \"failed\" | \"all\";\n /** --rerun 的本次调用携带口径。 */\n rerun?: \"failed\" | \"all\";\n /** `--accept` 本次授权跨过的差异 selector(`config:<字段路径>` 等)。 */\n accept?: readonly string[];\n /**\n * 本地协调根(默认 `cwd/.niceeval`)。session、execution lock、teardown 登记和\n * kept-sandbox registry 都在这里;它不是 portable Record 的一部分。\n */\n coordinationRoot?: string;\n /**\n * 已签发的实际 portable Record root。Record lease sidecar 由这个 root 推导到\n * `.niceeval/coordination/records/<recordKey>`,不能由 Runner 的执行协调目录代替。\n */\n recordRoot: RecordRoot;\n /** CLI 为 `niceeval exp` 提供的持久 Session 索引;只观察调度事件,不参与锁/闸判定。 */\n session?: import(\"./session.ts\").SessionTracker;\n /**\n * The current Record coordinator calls this after its frozen-view readback\n * has established the exact reusable attempts. The CLI uses this one-way\n * Effect hand-off to emit the invocation plan; callers cannot provide or\n * alter Record reuse authority through it.\n */\n onCurrentRecordReusePlan?: (input: {\n readonly reused: number;\n readonly reusedFailures: readonly FailureDetail[];\n /** Draft identities become Human-visible only if the final receipt confirms publication. */\n readonly runIds: ReadonlyMap<string, string>;\n }) => Effect.Effect<void, never>;\n /**\n * 已注册的 reporter,携带 name/required 元数据(见 `ReporterRegistration`)。这是内部编排\n * 通道——调用方(今天只有 `cli.ts`)按来源(显式 --junit / 用户 `Config.reporters`)把裸\n * `Reporter` 各自包一层元数据后传进来;eval 级 `EvalDef.reporters`\n * 不经过这里,由 `runEvals()` 自己按 `scopeReporter()` 包装、统一记作 `required: false`\n *(见 run.ts 的 scopedSets 处理)。\n */\n reporters: ReporterRegistration[];\n maxConcurrency: number;\n /** Run 级 Sandbox 镜像 lookup/build 并发;省略时安全默认 2。 */\n maxBuildConcurrency?: number;\n signal?: AbortSignal;\n /**\n * 非沙箱 tracing agent 的 run 级共享 OTLP 接收池(runEvals 创建并回收;\n * 每个 agent 一个 receiver,attempt 之间共享 —— 被测应用是长驻进程,端点不能随 attempt 换)。\n */\n otelPool?: import(\"../o11y/otlp/turn-otel.ts\").OtelReceiverPool;\n /**\n * Run 级共享构建准备。只含携带规划后仍需 fresh 执行的 BuildKey;\n * 省略时 runEvals 从 pair-owned ProviderPlan 自动收集\n * (Compose works 默认接 dockerComposeBuildProvider)。测试可显式注入假 provider。\n * 共享构建不占 attempt 并发位,不计入 executionMs。\n */\n buildPreparation?: {\n readonly works: readonly import(\"../sandbox/build-coordinator.ts\").SandboxBuildWork[];\n readonly provider: import(\"../sandbox/build-coordinator.ts\").SandboxBuildProvider;\n /** `${experimentId}|${evalId}` → 该 pair 的 fresh attempt 依赖的 BuildKey。 */\n readonly pairBuildKeys: Readonly<globalThis.Record<string, readonly BuildKey[]>>;\n readonly maxConcurrency?: number;\n readonly buildTimeoutMs?: number;\n readonly prepareBudgetMs?: number;\n };\n /**\n * Run 级 Agent artifact prepare 协调器。省略时 runEvals 为有 staged installer 的\n * sandbox agent 新建并接真 Run timing recorder;测试可注入。\n */\n artifactPrepare?: import(\"../agents/provisioner.ts\").ArtifactPrepareCoordinator;\n}\n/** 调度器内部的一次尝试:eval × run × 第几轮。 */\nexport interface Attempt {\n readonly evalDef: DiscoveredEval;\n readonly run: AgentRun;\n readonly attempt: number;\n /** agent+model+evalId,用于首过即停。 */\n readonly key: string;\n readonly fingerprint: string;\n readonly configHash: string;\n /** Planning 时唯一解析并冻结的 Judge capability/config。 */\n readonly judge: ResolvedJudgeConfig | undefined;\n /** 该 pair 的唯一、不可变规划产物;fingerprint / create / reuse 全部消费同一份值。 */\n readonly plan: LinkedRunPlan;\n /** 同一 Experiment 本次选中 Eval 的完整 plan 映射;run.json 不从当前 pair 猜全局默认值。 */\n readonly sandboxPlansByEval: Readonly<globalThis.Record<string, JsonValue>>;\n /**\n * 构造 fresh attempt plan 时即算好的 Attempt 定位符(不是完成后写回):由 invocation 的\n * 预分配 runId 与 attempt 身份派生,贯穿执行、留存登记与落盘——登记项、run 收尾反馈与\n * result.json 从第一次写入起就用同一个值。裸 run(无 experimentId)不产出。\n */\n locator?: AttemptLocator;\n}\n// ───────────────────────── 反馈 profile / 事件 / reducer 状态 ─────────────────────────\n// `niceeval exp` 的 human / json 反馈模型(见 docs/feature/experiments/cli.md)。\n// 本节定义 coordinator、纯 reducer、renderer 与 runner emitter 共用的事件及状态契约;实现分别\n// 位于 `runner/feedback/` 与 `runner/attempt.ts` / `runner/run.ts`,没有另一套阶段性事件模型。\n/** 两种反馈形态(见 docs/feature/experiments/cli.md「每条命令一个人读 text 面,`--json` 是机器面」):\n * `--json` 即机器面,否则人读文本(TTY live 面板 / 非 TTY 追加流,由渲染层内部按 `io.stderr.isTTY`\n * 再分派,不是第三个 profile)。 */\nexport type OutputProfile = \"human\" | \"json\";\n// 反馈系统的 attempt 阶段与落盘 / envelope 用同一套 `LifecyclePhase` 闭集(见上),\n// 不再有独立的 dashboard 词表;`waiting for a slot` 是 attempt 开始前的调度态,不属于闭集;\n// `passed` / `failed` / `errored` / `reused` / `early-exit` / `budget-unstarted` 是 outcome,\n// 发生在阶段结束后,也不塞进 phase 闭集。\n/**\n * 反馈系统里一次 attempt 的稳定身份:reducer 用它做 active map 的 key、事件的关联字段。\n * 只含调度身份三元组 —— 不含 agent/model/展示 label(那是 `who`,来自 `runWho()`,\n * 见该函数注释:展示 label 不能当 identity key 用,folding 两个不同 config 到同一个 key\n * 曾经就是 live 表格两个真实 bug 的根因),也不含落盘 `AttemptLocator`\n * 所需的持久化 `runId`。完成/failure 事件在 locator 确定后直接携带\n * 派生好的 `AttemptLocator` 字符串,反馈层\n * 不重新推导身份 —— 两个同名概念的 identity 类型故意不同名,以免和落盘身份互相看错)。\n */\nexport interface AttemptRef {\n /** 未挂靠 experiment 时为 undefined(直接指定 agent/model 跑,不经过 experiment);不用空字符串占位。 */\n experimentId?: string;\n evalId: string;\n /** 0-indexed,与 `EvalResult.attempt` / `AttemptLocator` 的 attempt 同一口径。 */\n attempt: number;\n}\n/** `AttemptRef` 的确定性字符串编码,只作 `RunFeedbackState.active` 的 Map key 使用 ——\n * 不是展示文本(那是 `who`),也不是 `AttemptLocator`(后者是 Record 的持久化身份)。 */\nexport type AttemptKey = string & {\n readonly __brand: \"AttemptKey\";\n};\n/** 由 `AttemptRef` 派生 `AttemptKey`;同一身份永远编码出同一个 key。 */\nexport function encodeAttemptKey(ref: AttemptRef): AttemptKey {\n return `${ref.experimentId ?? \"\"}|${ref.evalId}|${ref.attempt}` as AttemptKey;\n}\n/**\n * dashboard 当前可见的一个 active slot。`phase` 是正式状态,`detail` 只是该 phase 下的次要文本\n *(如 `running` 阶段的 `tool: shell` / `turn 2`)—— 两者是两个字段,不把 adapter 的 raw progress\n * string 直接当状态用;phase 变化时 `detail` 清空(旧阶段的次要文本不该残留到新阶段)。\n */\nexport interface ActiveAttempt {\n identity: AttemptRef;\n /** 展示 label,等价 `runWho()` 的结果;渲染要用,但绝不作为 identity/key。 */\n who: string;\n phase: LifecyclePhase;\n /**\n * 这条 attempt 被派发的墙钟时间(epoch ms,取 `attempt:start` 的 `at`)—— active 行时间列的\n * **唯一**基准,`attempt:phase` 不得改写它:live 面板不做 spinner 动画,存活性完全由这一列\n * 持续增长证明(见 docs/feature/experiments/cli.md「active 行的列序」),一列会归零的时间既\n * 证明不了存活,也让人误以为这条 eval 重跑了。阶段各自的耗时不进这里——它由结果的\n * `timing.phases` 完整落盘,live 面板要回答的是「这条还活着吗、跑了多久、正在干什么」。\n */\n startedAt: number;\n detail?: string;\n}\n/** 实验级钩子只有 setup 与它返回的 teardown 两员,同一实验内两者永不并发\n * (teardown 在全部 attempt 收尾后才触发),所以运行级行按 experimentId 建 key 就够。 */\nexport type ExperimentHookName = \"setup\" | \"teardown\";\n/**\n * dashboard 当前可见的一个实验级钩子运行级行(见 docs/feature/experiments/cli.md\n * 「实验级 Hook 的显示」)。与 `ActiveAttempt` 分开建模:钩子不属于任何单个 attempt、不占并发位,\n * 也不参与 `RunFeedbackState` 的计数不变量——等待 setup 的\n * attempt 保持 `queued`,这行就是「为什么它们还在排队」的解释。`detail` 来自实验级\n * `ctx.progress`,后一条覆盖前一条。\n */\n/**\n * dashboard 当前可见的 judge 预检运行级行(见 docs/feature/experiments/cli.md「judge 预检的显示」)。\n * 与 `ActiveExperimentHook` 分开建模:预检是 invocation 级、不挂任何 experimentId,只有一个在飞\n * 实例(整次运行至多一次预检)。`startedAt` 用于渲染运行级行持续增长的耗时,证明它还活着。\n */\nexport interface ActivePrecheck {\n /** 预检开始的墙钟时间(epoch ms),用于渲染运行级行的耗时。 */\n startedAt: number;\n}\nexport interface ActiveExperimentHook {\n experimentId: string;\n hook: ExperimentHookName;\n /** 钩子开始的墙钟时间(epoch ms),用于渲染运行级行的耗时。 */\n startedAt: number;\n detail?: string;\n /** 强杀后启动自愈补执行的 teardown(见 `DurableFeedbackEvent` 的 \"experiment-hook\" 变体)。 */\n recovery?: boolean;\n}\n/**\n * dashboard 当前可见的一个「等待并行 run」运行级行(见 docs/feature/experiments/cli.md\n * 「等待并发 run 的显示」)。用例锁的等待粒度是单个 `(experimentId, evalId)`,但运行级行按\n * experimentId 聚合展示——一个实验可能同时有多个用例撞锁,只占一行,给出条数与代表持有方。\n */\nexport interface ActiveLockWait {\n experimentId: string;\n /** 当前仍在等待的 evalId → 该用例开始等待的时间与持有方身份。`size` 就是运行级行要展示的\n * 等待条数;为空表示这个实验当前没有在等的用例(条目仍保留在 map 里,供非 TTY 聚合文案\n * 读取下面两个累计字段,直到下一次 \"started\" 事件开启新窗口时清零)。 */\n waiting: ReadonlyMap<string, {\n startedAt: number;\n holderPid?: number;\n holderHost?: string;\n }>;\n /** 本次「有等待用例」窗口内,累计已经 resolved 且携入 reused 的 attempt 数——供非 TTY 聚合\n * 收尾行(如 `lock wait resolved · compare/codex (2 carried · 1 to run, 1m 34s)`)读取。 */\n resolvedCarried: number;\n /** 同上,累计已经 resolved 且转为自跑(进入 queued)的 attempt 数。 */\n resolvedDispatched: number;\n}\n/**\n * dashboard 当前可见的一条 Run 级 activity 行(共享构建、制品准备等)。\n * 不占 attempt active 位,也不进五项恒等式计数;人读文本用 producer 的 `label`,不查\n * LifecyclePhase 锚点表(见 docs/feature/experiments/architecture.md「Run 级共享准备」)。\n */\nexport interface ActiveRunActivity {\n /** 与 TimingActivity.id 对齐,同一 Run 内唯一。 */\n id: string;\n /** ActivityKey;机器面分组用,不驱动人读标签切换。 */\n key: string;\n /** producer 写下的有界人读标签;展示层原样用。 */\n label: string;\n /** 开始的墙钟时间(epoch ms),用于渲染运行级行持续增长的耗时。 */\n startedAt: number;\n}\n/**\n * 一次失败/错误的永久通知:human 撤下 dashboard 后追加一行、JSON 机器流立即追加一行,都读它。\n * 字段全部结构化(locator / identity / verdict / phase 都是具名字段),profile renderer 不需要\n * 解析 `reason` 之外的任何文本就能拼出机器可读的输出。\n */\nexport interface FailureDetail {\n /** Canonical current Record Attempt locator. */\n locator: string;\n identity: AttemptRef;\n who: string;\n verdict: \"failed\" | \"errored\";\n /** 一层可行动摘要(gate 断言名、error 消息……),不是完整 stack/transcript;详情走 `niceeval show`。 */\n reason: string;\n /** failed / unavailable 时的结构化主 Fact/use 摘要。 */\n fact?: PrimaryFactSummary;\n /** 仅 errored 使用:结构化执行错误发生时所在的阶段。failed 是断言 outcome,不带 phase。 */\n phase?: LifecyclePhase;\n /** 仅 errored 且没有结构化主断言摘要(真正的执行错误,而非 assertion-unavailable)时携带:\n * `AttemptError.code` 原样透出,human 单行事实行拼成 `errored · <phase> · <code>`。 */\n code?: string;\n /** 完整时间归属;attempt 形态同时投影上面的 phase,run 形态保留共享 timing node。 */\n origin?: TimingOrigin;\n}\n/** 带发生时间的失败通知;复用失败以 FailureDetail 静态进入 plan,不伪装成刚发生的事件。 */\nexport interface FailureNotice extends FailureDetail {\n at: number;\n}\n/**\n * 去重后的即时通知(info/warning/error):相同 `key` 的通知只保留一条,`count` 累加受影响次数\n *(见 docs/feature/experiments/cli.md「什么动态更新,什么逐条追加」的去重规则)。\n * `data` 携带结构化字段(如 budget 的 experimentId/spent/unstarted),renderer 直接读取,\n * 不解析 `message`(`message` 只是 human 展示用的一句话)。\n */\n/**\n * 止损闸落闸诊断的稳定词法(`--json` 的 `warning.code`、`run.json` 的诊断 `code`,契约见\n * docs/feature/error-classification/architecture.md「止损执行体」)。emitter(run.ts)与两种\n * profile 的 renderer 共用这一个常量,谁都不在自己这边再写一遍字面量。\n */\nexport const HALT_DIAGNOSTIC_CODE = \"dispatch-halted\";\nexport const COORDINATION_RECOVERED_CODE = \"coordination-recovered\";\nexport type DiagnosticSeverity = \"info\" | \"warning\" | \"error\";\nexport interface DiagnosticNotice {\n at: number;\n key: string;\n /** 对外的稳定词法(`--json` 的 notice/warning `code`、human 通知行标题);省略 = 与 `key` 相同。\n * `key` 可以把折叠身份(experimentId / evalId)编进去,`code` 恒是干净字面量。 */\n code?: string;\n severity: DiagnosticSeverity;\n message: string;\n /** 相同 key 累计出现的次数,由 reducer 去重时递增。 */\n count: number;\n identity?: AttemptRef;\n data?: Readonly<globalThis.Record<string, JsonValue>>;\n}\n/** 运行完整性结论,独立于 verdict 计数。CI 退出码不能只看 failed/errored ——\n * budget 未覆盖全部计划、用户中断、required reporter 失败都必须让 completion 非「complete」。 */\nexport type CompletionStatus = \"complete\" | \"incomplete\" | \"interrupted\";\n/** 一个 reporter 收尾失败的记录;`required` 区分它是否让 completion 判红(见 ReporterRegistration)。 */\nexport interface ReporterError {\n reporter: string;\n required: boolean;\n message: string;\n}\nexport interface InvocationCompletion {\n status: CompletionStatus;\n /** budget 耗尽导致未派发的 attempt 数;不含首过即停省略的次数(见 `earlyExitUnstarted`)。 */\n unstarted: number;\n /** 首过即停在已知 verdict 下主动省略的计划次数 —— 这是「省下的重复验证」,不是「未完整覆盖」。 */\n earlyExitUnstarted: number;\n reporterErrors: readonly ReporterError[];\n}\n/**\n * 事件 → 状态的纯 reducer 产出(见 `src/runner/feedback/reducer.ts`)。所有计数、active map、\n * cost 累计、failure/diagnostic 去重都只在 reducer 里算一次;human/json renderer 只读取\n * 这份状态,不各自维护第二份推导。\n *\n * `total = reused + running + elsewhere + queued + passed + failed + errored + skipped`\n * (八项恒等式,见 docs/feature/experiments/cli.md「等待并发 run 的显示」)在处理完每一个事件\n * 之后都成立,是 reducer 的不变量:任何一次迁移都是「从一项减 x、往另一项加 x」,不存在两项\n * 同时计数或都不计数的中间态(见 reducer.test.ts 的表驱动用例,每一步都断言,不只在流程末尾\n * 断言一次)。\n */\nexport interface RunFeedbackState {\n /** Draft 期的 Experiment → Run 映射;Human 只在 receipt 确认对应 Run 已发布后使用。 */\n runIdsByExperiment: ReadonlyMap<string, string>;\n total: number;\n reused: number;\n running: number;\n /** 正被并行 Invocation 持锁运行、本次在等待中的用例的 attempt 数(用例锁,见 `lock-wait`\n * 变体与 docs/feature/experiments/cli.md「等待并发 run 的显示」);与 `queued` 互斥——\n * `queued` 是「等本进程并发位/setup」,`elsewhere` 是「等别的进程」。\n * 恒等式(见接口注释)在处理完每一个事件之后都成立。 */\n elsewhere: number;\n queued: number;\n /** 以下四项是本次派发并已了结的 attempt 按 verdict 的划分——reducer 不保留一个笼统的\n * 「完成数」:盯着运行的人问的是「到现在为止挂了几个」,一个合计数回答不了。携入结果的\n * verdict 留在 `reused`,不摊进这四项(计数口径与成本口径一致地区分「本次派发」与\n * 「缓存携入」,见 docs/feature/experiments/cli.md「运行中的 live 面板」)。 */\n passed: number;\n failed: number;\n errored: number;\n /** 本次不产生 verdict 的了结:eval 自身 skip、首过即停省略的轮次、budget 未派发。\n * 它们不冒充 `passed`/`failed`;三者彼此的区别由结束结论与题目级 `eval` 事件给出。 */\n skipped: number;\n /** attempt:early-exit 事件的累计次数(首过即停省略 + fail-fast 未派发;后者由 fail-fast\n * diagnostic 的 count 单独区分,见 cli.ts 的 assembleRunCompletion)。 */\n earlyExitSkipped: number;\n /**\n * `attempt:early-exit` 事件的原始次数,按 `${experimentId ?? \"\"}|${evalId}` 分组(见\n * `feedback/eval-conclusions.ts` 的 `evalConclusionKey`)。这份计数**未**剔除 fail-fast 的\n * 份额(fail-fast 未派发同样发出这个事件类型,见 run.ts)——`evalConclusionRows()` 消费时\n * 对照 `diagnostics` 里的 `fail-fast:` 记录减去那部分,得到真正的首过即停省略次数,据此判断\n * 是否给出 `reason=early_exit`。不得把这份原始计数直接当作首过即停次数使用。\n */\n earlyExitByEval: ReadonlyMap<string, number>;\n elapsedMs: number;\n /** 仅本次实际派发 attempt 的 token;carry 结果的历史 usage 不进入这里。 */\n newTokenCount?: number;\n /** 仅本次实际派发 attempt 的 `estimatedCostUSD` 累计(价目表估算口径,与 newTokenCount 同口径);observed cost 不进入这里。 */\n estimatedCostUSD?: number;\n active: ReadonlyMap<AttemptKey, ActiveAttempt>;\n /** 在飞的 judge 预检运行级行(见 `DurableFeedbackEvent` 的 \"precheck\" 变体):`started` 置位、\n * `done` 清空。预检发生在任何 attempt 派发之前、作用于整次 invocation,不属于任何 attempt,\n * 也不参与五项恒等式计数——预检期间 attempt 保持 `queued`,\n * 这行就是「为什么它们还在排队」的解释。undefined = 当前没有在飞的预检。 */\n activePrecheck?: ActivePrecheck;\n /** 在飞的实验级钩子(experimentId → 运行级行状态),由 \"experiment-hook\" 事件增删、\n * \"experiment:progress\" 更新 detail(见 docs/feature/experiments/cli.md「实验级 Hook 的显示」)。 */\n experimentHooks: ReadonlyMap<string, ActiveExperimentHook>;\n /** 在飞的用例锁等待,按 experimentId 聚合(见 `ActiveLockWait`、docs/feature/experiments/cli.md\n * 「等待并发 run 的显示」)。由 \"lock-wait\" 事件增删/累计;没有等待用例的实验不出现在这个 map 里。 */\n lockWaits: ReadonlyMap<string, ActiveLockWait>;\n /** 在飞的 Run 级 activity(id → 运行级行状态),由 \"run-activity\" 事件增删。不占 attempt\n * active 位,也不进计数恒等式(见 `ActiveRunActivity`)。 */\n runActivities: ReadonlyMap<string, ActiveRunActivity>;\n failures: readonly FailureNotice[];\n /** 本次实际派发后产生的去重失败数;复用失败不消耗 profile 的流式输出上限。 */\n freshFailureCount: number;\n diagnostics: readonly DiagnosticNotice[];\n /** 留存授予的沙箱(--keep-sandbox);run 摘要后各 profile 追加输出。 */\n kept: readonly KeptNotice[];\n}\n/** 一条留存授予的永久通知(见 docs/feature/sandbox/cli.md「run 收尾输出」)。 */\nexport interface KeptNotice {\n at: number;\n locator: AttemptLocator;\n identity: AttemptRef;\n who: string;\n verdict: Verdict;\n provider: string;\n sandboxId: string;\n enter?: string;\n}\n/** 一次 run 的初始计划。复用只暴露数量;失败明细仅用于静态初始化终局清单。 */\nexport interface RunFeedbackPlan {\n shape: InvocationShape;\n /**\n * 声明了 `maxConcurrency` 的实验 → 各自的上限。只收声明了的实验(未声明的实验的有效宽度就是\n * 全局值,列出来只是噪音);一个都没声明时整个字段省略——`--json` 的 `start` 事件因此不会\n * 出现空对象,human `PLAN` 行也不加附注(见 docs/feature/experiments/cli.md\n * 「运行中的 live 面板」的 `concurrency` 附注段)。\n */\n experimentConcurrency?: Readonly<globalThis.Record<string, number>>;\n /** 携入(carry)结果数,直接计入 `RunFeedbackState.reused`,不需要重新调度。 */\n reused: number;\n /** 复用结果中的失败;plan 时静态注入,不产生“刚发生”的失败事件。 */\n reusedFailures?: readonly FailureDetail[];\n}\n/**\n * 只影响 dashboard 当前帧、reducer 不为它保留历史的事件:新值使旧值失去意义,所以覆盖而不是\n * 追加(见 docs/feature/experiments/cli.md「什么动态更新,什么逐条追加」的判断标准)。\n * `attempt:early-exit` 同样折进这一组 —— 它不打印永久行,只把已知 verdict 的省略次数收进\n * `skipped`(见 reducer 实现)。\n */\nexport type AttemptLifecycleEvent = {\n type: \"attempt:queued\";\n at: number;\n identity: AttemptRef;\n who: string;\n} | {\n type: \"attempt:start\";\n at: number;\n identity: AttemptRef;\n who: string;\n phase: LifecyclePhase;\n} | {\n type: \"attempt:phase\";\n at: number;\n identity: AttemptRef;\n phase: LifecyclePhase;\n} | {\n type: \"attempt:progress\";\n at: number;\n identity: AttemptRef;\n detail: string;\n} | {\n type: \"attempt:complete\";\n at: number;\n identity: AttemptRef;\n who: string;\n verdict: Verdict;\n /** 本次 attempt 的输入 + 输出 token;缺失表示 provider 未报告。 */\n tokenCount?: number;\n /** 本次 attempt 的价目表估算成本(`EvalResult.estimatedCostUSD` 口径);observed cost 不携带。 */\n estimatedCostUSD?: number;\n} | {\n type: \"attempt:early-exit\";\n at: number;\n identity: AttemptRef;\n who: string;\n};\n/**\n * 实验级 `ctx.progress` 的短命投影:只覆盖对应运行级行的 `detail`,不追加永久行——与\n * `attempt:progress` 同一判断标准(新值使旧值失去意义)。对应的运行级行不存在时静默忽略。\n */\nexport interface ExperimentProgressEvent {\n type: \"experiment:progress\";\n at: number;\n experimentId: string;\n detail: string;\n}\n/**\n * 运行级时钟 tick:唯一允许更新 `RunFeedbackState.elapsedMs` 的事件,由 coordinator 的定时器产出\n *(见 plan 的可注入 `FeedbackIO` clock)。reducer 保持纯函数,不自己读 `Date.now()`,elapsedMs\n * 因此只能通过事件携带的值前进 —— 这也让 reducer 测试可以喂任意 elapsed 值,不必真的等待。\n */\nexport interface FeedbackTickEvent {\n type: \"tick\";\n at: number;\n elapsedMs: number;\n}\n/**\n * 永久事件:human 撤下 dashboard 后追加一行、JSON renderer 在 stdout 追加 NDJSON 事件,\n * 一旦发生就不会被后续状态覆盖掉(与上面按当前帧覆盖的 `AttemptLifecycleEvent` 相对)。\n * 字段全部结构化,profile renderer 不解析 `message` 之外的任何文本、不解析 i18n 字符串。\n */\nexport type DurableFeedbackEvent = {\n type: \"plan\";\n at: number;\n plan: RunFeedbackPlan;\n} | {\n type: \"failure\";\n at: number;\n /** Writer-issued short alias for the exact durable AttemptId. */\n locator: string;\n identity: AttemptRef;\n who: string;\n verdict: \"failed\" | \"errored\";\n reason: string;\n fact?: PrimaryFactSummary;\n phase?: LifecyclePhase;\n code?: string;\n origin?: TimingOrigin;\n} | {\n type: \"diagnostic\";\n at: number;\n key: string;\n /** 对外稳定词法(见 `DiagnosticNotice.code`);省略 = 与 `key` 相同。 */\n code?: string;\n severity: DiagnosticSeverity;\n message: string;\n /** attempt 级诊断的归属身份。运行级诊断(实验闸 / eval 闸这类不属于任何单条 attempt 的\n * 事实)不许伪造 identity——它们的 experimentId / evalId 走 `data` 的同名字段,\n * `--json` 的 `warning` 事件两处都读、identity 优先。 */\n identity?: AttemptRef;\n data?: Readonly<globalThis.Record<string, JsonValue>>;\n}\n/**\n * emitter 对每一个因 budget 到顶而不派发的 attempt 各发一次(与 `attempt:early-exit` 同构,\n * 见 reducer 实现);`unstarted` 是 emitter 自己记的、发出这条时的累计未派发数,写进\n * `DiagnosticNotice.data` 供 renderer 直接读取,不是 reducer 用来计算「这次要挪多少」的输入\n *(reducer 只按事件触发次数折算,保持纯函数不需要额外记住上一次的值)。\n */\n | {\n type: \"budget-exhausted\";\n at: number;\n experimentId: string;\n spent: number;\n unstarted: number;\n}\n/** 一次留存授予(--keep-sandbox):run 摘要后 human/json 两种形态都追加输出(见 docs/feature/sandbox/cli.md)。 */\n | {\n type: \"kept\";\n at: number;\n locator: AttemptLocator;\n identity: AttemptRef;\n who: string;\n verdict: Verdict;\n provider: string;\n sandboxId: string;\n enter?: string;\n}\n/**\n * 实验级钩子(`ExperimentDef.setup` / 它返回的 teardown)的起止,由 runner 在钩子真正\n * 开始/结束时各发一次(见 docs/feature/experiments/cli.md「实验级 Hook 的显示」)。`failed`\n * 只标记钩子自身的结局——setup 失败的每条 attempt 仍以 \"failure\" 事件逐条给出。human TTY\n * 用它维护运行级 active 行(不写 scrollback),append-only profile 起止各追加一行。\n */\n | {\n type: \"experiment-hook\";\n at: number;\n experimentId: string;\n hook: ExperimentHookName;\n status: \"started\" | \"done\" | \"failed\";\n /** 只在 done / failed 上出现:钩子从开始到结束的耗时。 */\n durationMs?: number;\n /**\n * 只在 `hook: \"teardown\"` 的 `status: \"started\"` 上可能出现:标注这是强杀后的启动自愈\n * 补执行(见 docs/feature/experiments/architecture.md「强杀后的收尾兜底」),不是本次 run\n * 正常触发的收尾。省略 = 正常路径。\n */\n recovery?: boolean;\n}\n/**\n * judge 配置预检的起止,由 runner 在探测真正开始/结束时各发一次(见 docs/feature/experiments/\n * cli.md「judge 预检的显示」)。预检作用于整次 invocation、发生在任何 attempt 派发之前,不属于\n * 任何单个 attempt,也不触碰五项恒等式计数不变量。human\n * TTY 用它维护一条运行级 active 行(不写 scrollback),append-only profile 起止各追加一行。\n * `failed` 只标记预检本身的结局(与 `done` 一样清掉运行级行);受影响 eval 每条 attempt 的\n * `errored`(`judge-precheck-failed`)仍由 \"failure\" 事件逐条给出。\n */\n | {\n type: \"precheck\";\n at: number;\n status: \"started\" | \"done\" | \"failed\";\n durationMs?: number;\n}\n/**\n * 用例锁等待的起止(见 docs/feature/experiments/cli.md「等待并发 run 的显示」)。粒度是单个\n * `(experimentId, evalId)`——同一 eval 的全部 attempt 作为一个整体一起等、一起解决,不按\n * attempt 拆分。emitter(run.ts)只在这批 attempt 需要重查携带时才发这对事件:全携带用例\n * 不取锁、无竞争的全新取锁(锁目录里从没出现过这个 key)都不发——静态携带规划的结论不可能\n * 过时,没有理由重新读盘。撞上新鲜锁(真正等待)与接管一把无人竞争的过期锁(从未真正等待,\n * `waitedMs` 因此可能接近 0)都算「需要重查」,统一走这对事件——即便是瞬时接管,这批\n * attempt 也必须先经 \"started\" 迁入 `elsewhere`,\"resolved\" 才能把它们正确迁回\n * `reused`/`queued`,否则它们会永远卡在 `queued`、打破五项恒等式。\n */\n | {\n type: \"lock-wait\";\n at: number;\n experimentId: string;\n evalId: string;\n status: \"started\" | \"resolved\";\n /** status 为 \"started\" 时给出:锁持有方身份,以及这次撞锁进入 elsewhere 等待的 attempt 数\n * (该 eval 本轮需要真实派发、被这把锁挡住的 attempt 数;省略按 1 处理)。 */\n holderPid?: number;\n holderHost?: string;\n attempts?: number;\n /** status 为 \"resolved\" 时给出:锁释放后重查携带,分别有多少 attempt 从 elsewhere 迁入\n * reused(carried)、多少迁入 queued 转为自跑(dispatched)——`attempts` 下可能两者都非零\n * (部分携入部分补跑)。`--json` 的 `lock_wait` 事件把两者折成单一 `resolution` 字段:\n * `dispatched > 0` 记 \"dispatched\"(这个用例仍需要真实派发,等待没有让它完全免于执行),\n * 否则记 \"carried\"(全部由携带满足,零新成本)。 */\n carried?: number;\n dispatched?: number;\n waitedMs?: number;\n}\n/**\n * Run 级开放 activity 的起止(共享构建、制品准备等)。`key` / `label` 原样来自 producer:\n * 人读面与 `--json` 对未登记 key 用 `label` 通用投影,不需要 switch 穷尽,也不进\n * LifecyclePhase 锚点标签表。human TTY 用它维护运行级 active 行(不占 attempt slot、\n * 成功不写 scrollback);非 TTY 与 `--json` 起止/失败各追加一行有界永久事件。\n */\n | {\n type: \"run-activity\";\n at: number;\n id: string;\n key: string;\n label: string;\n status: \"started\" | \"done\" | \"failed\";\n /** 只在 done / failed 上出现。 */\n durationMs?: number;\n} | {\n type: \"interrupted\";\n at: number;\n} | {\n type: \"reporter-error\";\n at: number;\n reporter: string;\n required: boolean;\n message: string;\n} | {\n type: \"summary\";\n at: number;\n summary: InvocationSummary;\n completion: InvocationCompletion;\n} | {\n type: \"receipt\";\n at: number;\n receipt: InvocationReceipt;\n};\n/**\n * runner → feedback coordinator 的内部事件通道,与公共 `Reporter` / `ReporterEvent` 分开:\n * profile renderer 只消费这里的具名字段,不解析 `ReporterEvent` 里的 i18n 文案或表格列宽\n *(见 docs/feature/experiments/cli.md「输出流和落盘节奏」)。\n */\nexport type RunFeedbackEvent = AttemptLifecycleEvent | ExperimentProgressEvent | FeedbackTickEvent | DurableFeedbackEvent;\n"]}